App-karr

 view release on metacpan or  search on metacpan

t/187-foundation-result-json.t  view on Meta::CPAN

    git => App::karr::Git->new( dir => "$repo" ) );
  for my $spec ( @specs ) {
    my $id = $store->allocate_next_id;
    $store->save_task( App::karr::Task->new(
      id => $id, status => 'backlog', title => "task $id",
      ref $spec ? %$spec : ( title => $spec ) ) );
  }
}

sub tasks_of {
  my ( $repo ) = @_;
  return App::karr::BoardStore->new(
    git => App::karr::Git->new( dir => "$repo" ) )->load_tasks;
}

sub task_by_id {
  my ( $repo, $id ) = @_;
  return App::karr::BoardStore->new(
    git => App::karr::Git->new( dir => "$repo" ) )->find_task( $id );
}

sub state_data {
  my ( $repo ) = @_;
  my $file = path( $repo )->child('.karr.state');
  return {} unless $file->exists;
  return json_decode( $file->slurp_utf8 );
}

sub log_of {
  my ( $repo ) = @_;
  my $file = path( $repo )->child('.karr.log');
  return $file->exists ? $file->slurp_utf8 : '';
}

# A result object in the shape `claude -p --output-format json` really emits
# (2.1.233): the fields this classification reads, with the rest of the real
# payload's bulk left out. Written as one line, which is how it arrives.
sub result_json {
  my ( %over ) = @_;
  return json_encode( {
    type             => 'result',
    subtype          => 'success',
    is_error         => \0,
    num_turns        => 7,
    duration_ms      => 1490,
    total_cost_usd   => 0.04704,
    session_id       => '10efe29e-1fad-4dce-8026-b4086b5c37f5',
    stop_reason      => 'end_turn',
    terminal_reason  => 'completed',
    api_error_status => undef,
    result           => 'done',
    %over,
  } );
}

# What #160's corpus proves an ordinary agent prints: its own board, including
# a backlog title with a symptom word in it, and a genuine-looking API error
# line. Neither may reach the classifier once the run has reported for itself.
my $NOISE = <<'NOISE';
#3 backlog retry the network fetch on 503
#4 backlog invalid credentials in the auth path
API Error: 429 {"type":"error","error":{"type":"rate_limit_error"}}
NOISE

# A harmless fake agent: it prints what the mode tells it to, optionally moves
# one card through karr's own store, and exits. It never leaves the temp repo
# and it never calls anything real.
sub write_fake_agent {
  my ( $dir ) = @_;
  my $lib    = path('lib')->absolute->stringify;
  my $script = path($dir)->child('fake-agent.pl');
  $script->spew_utf8(<<'PERL');
use strict;
use warnings;
require App::karr::Git;
require App::karr::BoardStore;
require App::karr::ActivityLog;

my $repo   = $ENV{KARR_REPO} or die "no KARR_REPO\n";
my $noise  = $ENV{KARR_FAKE_NOISE}  // '';
my $result = $ENV{KARR_FAKE_RESULT} // '';
my $where  = $ENV{KARR_FAKE_WHERE}  // 'last';   # last | middle | none
my $move   = $ENV{KARR_FAKE_MOVE}   // '';
my $code   = $ENV{KARR_FAKE_EXIT}   // 0;

if ( $move ) {
  my $store = App::karr::BoardStore->new(
    git => App::karr::Git->new( dir => $repo ) );
  my ( $t ) = grep {
    $_ && !$_->has_blocked && $_->status ne 'done' && $_->status ne 'archived'
  } $store->load_tasks;
  if ( $t ) {
    $t->status('done');
    $store->save_task( $t );
    App::karr::ActivityLog->new( git => $store->git, role => 'agent' )
      ->log_entry( agent => 'fake-agent', action => 'move',
                   task_id => $t->id + 0, detail => 'done' );
  }
}

print "$result\n" if $result && $where eq 'middle';
print $noise if length $noise;
print "$result\n" if $result && $where eq 'last';
exit $code;
PERL
  return qq{$^X -I"$lib" "$script"};
}

# ---------------------------------------------------------------------------
# Unit: finding the report
# ---------------------------------------------------------------------------

subtest 'a result object is found at the tail of the output, and only there' => sub {
  my $f  = App::karr::Foundation->new( _config_data => {} );
  my $js = result_json();

  is $f->_run_result( undef ), undef, 'no output, no report';
  is $f->_run_result( '' ),    undef, 'empty output, no report';

  my $bare = $f->_run_result( "$js\n" );
  is ref $bare, 'HASH', 'the object on its own is read';



( run in 1.577 second using v1.01-cache-2.11-cpan-007c89162af )