App-karr

 view release on metacpan or  search on metacpan

t/263-error-shows-working-command.t  view on Meta::CPAN

    _hint_is_last( $empty->{stderr}, '  karr needs --board NAME=PATH',
        'with no name typed, both halves stay placeholders' );
};

subtest 'needs for an id that is not on the board' => sub {
    my $repo = _board_repo('a card');

    my $rv = _run_karr( $repo, 'needs', 99 );
    is( $rv->{exit}, 1, 'a missing card is a runtime failure (1), unchanged' )
        or diag $rv->{stdout} . $rv->{stderr};
    like( $rv->{stderr}, qr/\QTask 99 not found on this board\E/, 'the id is named' );
    _hint_is_last( $rv->{stderr}, '  karr list --compact',
        'and the way to find the ids that do exist is the last line' );
};

#### import

subtest 'import spells out --yes instead of asking for it' => sub {
    my $repo = _board_repo('a card');

    my $rv = _run_karr( $repo, 'import' );
    is( $rv->{exit}, 1, 'a refused destructive operation is still 1' )
        or diag $rv->{stdout} . $rv->{stderr};
    _hint_is_last( $rv->{stderr}, '  karr import --yes',
        'the whole command, not "re-run with --yes"' );
};

subtest 'import with no file view offers the command that writes one' => sub {
    my $repo = _board_repo('a card');

    my $rv = _run_karr( $repo, 'import', '--yes' );
    is( $rv->{exit}, 1, 'exit 1' ) or diag $rv->{stdout} . $rv->{stderr};
    like( $rv->{stderr}, qr/\QNo materialized task view found\E/, 'says what is missing' );
    _hint_is_last( $rv->{stderr}, '  karr materialize', 'suggestion last' );
};

subtest 'an empty file view offers both intents, in order' => sub {
    my $repo = _board_repo('a card');
    path($repo)->child('tasks')->mkpath;

    my $rv = _run_karr( $repo, 'import', '--yes' );
    is( $rv->{exit}, 1, 'exit 1' ) or diag $rv->{stdout} . $rv->{stderr};
    like( $rv->{stderr}, qr/\Qthe file view is empty\E/, 'says what it found' );

    my @lines = _lines( $rv->{stderr} );
    is_deeply( [ @lines[ -2, -1 ] ], [ '  karr materialize', '  karr delete ID' ],
        'refresh the view first, delete deliberately second -- both last' )
        or diag $rv->{stderr};
};

#### materialize and repair

# Five commands raise this, four off App::karr::BoardStore/has_board_refs and
# one through App::karr::Role::BoardDiscovery/require_board. They are asserted
# together and character for character: the first pass of k263 converted only
# materialize and repair, which left the same sentence spelled two ways
# depending on which command a caller happened to type -- worse than either
# wording on its own, and invisible to a test that checks one command at a time.
subtest 'every board-less repository gets the same message, word for word' => sub {
    my @argv = (
        [ 'backup' ],
        [ 'destroy', '--yes' ],
        [ 'materialize' ],
        [ 'repair' ],
        [ 'create', 'a card' ],   # via require_board, not has_board_refs
    );

    for my $argv (@argv) {
        my $repo = _bare_repo();
        my $name = join ' ', 'karr', @$argv;
        my $rv   = _run_karr( $repo, @$argv );

        is( $rv->{exit}, 1, "$name: a missing board is a runtime failure (1), unchanged" )
            or diag $rv->{stdout} . $rv->{stderr};
        is_deeply( [ _lines( $rv->{stderr} ) ],
            [ 'No karr board found:', '  karr init' ],
            "$name: the sentence, then the command, and nothing else" )
            or diag $rv->{stderr};
    }
};

subtest 'repair dry run ends on the command that applies it' => sub {
    my $repo = _board_repo('a card');

    # A bare-date `started` that precedes the card's own `created` is what the
    # clamp pass reports; without it the dry run has nothing to offer. `created`
    # is set at construction, so the card is rebuilt rather than edited.
    App::karr::Git->new( dir => $repo )->save_task_ref(
        do {
            my $t = App::karr::Task->new(
                id      => 1,
                title   => 'a card',
                status  => 'done',
                created => '2026-07-02T10:00:00Z',
                updated => '2026-07-09T18:00:00Z',
            );
            $t->started('2026-07-02');
            $t;
        } );

    my $rv = _run_karr( $repo, 'repair' );
    is( $rv->{exit}, 0, 'a dry run still exits 0' ) or diag $rv->{stderr};
    _hint_is_last( $rv->{stdout}, '  karr repair --yes',
        'the offer is a command, and it is the last thing printed' );
};

#### Part B: where the answer stands

# The ticket's second fault, and the one that is only about placement. The
# answer was always in the output -- it just sat above a fifteen-line usage
# block, and the agent that hit it was reading through `tail -3`:
#
#   $ karr move 1 in-progress --claim
#   Option claim requires an argument            <- the answer
#   USAGE: karr move ID[,ID,...] STATUS [--claim NAME] [--next|--prev]
#       --claim=String  Claim task for an agent  <- the second answer
#       ... thirteen more lines ...
#       --man           show the manual          <- all `tail -3` saw
#
# App::karr::Role::ExitCodes buffers STDERR for the length of new_with_options
# and puts the diagnostic back AFTER the block, with the invocation that would



( run in 0.243 second using v1.01-cache-2.11-cpan-4ef0a570458 )