#!/usr/bin/env perl
# PODNAME: karr
# ABSTRACT: Kanban Assignment & Responsibility Registry

use strict;
use warnings;
our $VERSION = '0.500';
use App::karr;
use App::karr::SyncGuard;
use App::karr::Encoding qw( decode_argv enable_std_utf8 );
use App::karr::Error qw( is_usage_error );

# The character/octet boundary (ticket #53). Everything above this line is
# bytes, everything below is Perl characters: @ARGV comes in decoded, STDOUT and
# STDERR encode on the way out, and no command body encodes anything itself.
# STDIN stays raw on purpose -- karr restore decodes its own payload.
enable_std_utf8();
decode_argv();

# Push insurance, flushed at the last safe moment (ticket #37).
#
# App::karr::Role::SyncLifecycle arms an App::karr::SyncGuard for every writing
# command and stashes it on the command object, so that a body dying before
# sync_after still pushes what it wrote. On this CLI that never fired: the run
# below is wrapped in an eval, the handler exits, and MooX::Cmd's command chain
# holds the command object -- so the guard was first reaped in global
# destruction, where App::karr::Git refuses to work at all (#34) and the guard
# can do nothing but print "run karr sync".
#
# END is the fix: it runs before global destruction with the interpreter still
# whole, and unlike the error handler it also covers the exit() calls inside
# command bodies and the option-parse exits from App::karr::Role::ExitCodes.
#
# $? is localized because the exit code is a documented contract (see EXIT CODES
# below) and the push may shell out to `git` on App::karr::Git's CLI-fallback
# path, whose open3/waitpid would otherwise overwrite it. flush_armed is
# documented never to die, which is the other half of protecting that contract:
# an exception here would abort perl's END queue and set its own exit status.
END {
    local $?;
    App::karr::SyncGuard->flush_armed;
}

my $ran = eval { App::karr->new_with_cmd; 1 };
if ( !$ran ) {
    my $err = $@;

    # Exit-code contract (ADR 0002): 0 success / 1 runtime failure / 2 usage
    # error. This is the central handler the ADR calls for: it catches every
    # uncaught die from a command body and turns it into a deterministic 1 or 2,
    # replacing the accidental 255 an uncaught die used to leak.
    #
    # Usage-error dies carry one of these stable leading markers:
    #   "Unknown command:"          the dispatch guard in App::karr
    #   "unexpected extra argument"  surplus positionals (Role::CliArgs)
    #   "Usage:"                     a missing required positional
    #   "Usage error:"               anything a command rejects as misuse that
    #                                is not one of the shapes above -- raised
    #                                via App::karr::Role::ExitCodes'
    #                                usage_error(), which is the generic entry
    #                                point for e.g. an out-of-range option value
    # Any new usage-error die must start with one of these; prefer usage_error()
    # over inventing a fifth marker. Everything else -- task not found, board
    # missing, a Git/sync failure, a refused destructive operation -- is a
    # runtime failure (1).
    #
    # The markers themselves live in App::karr::Error::is_usage_error, because
    # this handler is no longer their only reader: the batch runner in
    # App::karr::Role::TaskMutation asks the same question to decide whether a
    # failure belongs to one id or to the whole invocation (ticket #61).
    #
    # Option-parse errors (unknown option, unparseable option value) never reach
    # here: MooX::Options exits 2 directly via App::karr::Role::ExitCodes (and
    # the root's _print_help), so those exits bypass this eval.
    my $is_usage = is_usage_error($err);

    print STDERR $err if defined $err && length $err;
    exit( $is_usage ? 2 : 1 );
}

__END__

=pod

=encoding UTF-8

=head1 NAME

karr - Kanban Assignment & Responsibility Registry

=head1 VERSION

version 0.500

=head1 SYNOPSIS

    karr init --name "My Project"
    karr create "Fix login bug" --priority high
    karr move 1 in-progress --claim swift-fox
    karr board
    karr backup > karr-backup.yml

=head1 DESCRIPTION

F<karr> is the primary command line interface for L<App::karr>. It manages a
Git-native kanban board whose canonical state lives in C<refs/karr/*>, plus
optional helper payloads in non-protected refs outside that namespace.

Run it from inside a Git repository. Commands discover the repository root,
pull the current board refs, materialize a temporary board view for the command
run, and then write changes back to Git refs. In normal use this means you can
interact with shared task state without checking a persistent F<karr/>
directory into the work tree.

The script is the best starting point when you want to understand the CLI as a
user. For architecture notes, Docker background, and Perl-facing examples, see
L<App::karr>.

=head1 CLI WORKFLOW

A typical session looks like this:

=over 4

=item 1.

Create the board once with L<App::karr::Cmd::Init>.

=item 2.

Add and inspect tasks with L<App::karr::Cmd::Create>,
L<App::karr::Cmd::List>, L<App::karr::Cmd::Show>, and
L<App::karr::Cmd::Board>.

=item 3.

Progress work with L<App::karr::Cmd::Move>, L<App::karr::Cmd::Edit>,
L<App::karr::Cmd::Pick>, and L<App::karr::Cmd::Handoff>.

=item 4.

Export, restore, or remove board state with L<App::karr::Cmd::Backup>,
L<App::karr::Cmd::Restore>, and L<App::karr::Cmd::Destroy>.

=back

=head1 COMMANDS

=head2 Board Setup And Configuration

=over 4

=item * L<App::karr::Cmd::Init>

Creates the initial board in C<refs/karr/*> and can install the bundled
project skill for Claude Code.

=item * L<App::karr::Cmd::Config>

Shows or updates writable board settings such as the board name, defaults, and
claim timeout.

=item * L<App::karr::Cmd::Disable>

Turns off automated agent runs for this board (L<karr-foundation>), optionally
recording a reason. The flag lives in C<refs/karr/config> and syncs with the
board.

=item * L<App::karr::Cmd::Enable>

Turns automated agent runs back on.

=item * L<App::karr::Cmd::Context>

Generates a compact board summary for embedding into files such as F<AGENTS.md>.

=item * L<App::karr::Cmd::Skill>

Installs, checks, updates, or prints the bundled skill file for Claude Code,
Codex, and Cursor.

=back

=head2 Task Lifecycle

=over 4

=item * L<App::karr::Cmd::Create>

Creates a new task card.

=item * L<App::karr::Cmd::List>

Lists tasks with filters for status, priority, tags, claims, and text search.

=item * L<App::karr::Cmd::Show>

Shows the full task document for one task id.

=item * L<App::karr::Cmd::Edit>

Updates task metadata, body text, claims, and blocked state.

=item * L<App::karr::Cmd::Move>

Moves tasks between statuses, including relative moves with C<--next> and
C<--prev>.

=item * L<App::karr::Cmd::Delete>

Deletes task refs permanently.

=item * L<App::karr::Cmd::Archive>

Soft-deletes tasks by moving them to the archived status.

=back

=head2 Board Flow And Coordination

=over 4

=item * L<App::karr::Cmd::Board>

Shows the grouped board view.

=item * L<App::karr::Cmd::Pick>

Finds the next suitable task and claims it for an agent.

=item * L<App::karr::Cmd::Unlock>

Shows the pick locks currently held on the board, and breaks them. The escape
hatch for a lock left behind by an agent that died mid-pick.

=item * L<App::karr::Cmd::Handoff>

Moves work into review and optionally appends a timestamped note.

=item * L<App::karr::Cmd::Log>

Prints activity log entries from C<refs/karr/log/*>.

=item * L<App::karr::Cmd::AgentName>

Generates short random agent names for multi-agent workflows.

=item * L<App::karr::Cmd::Sync>

Explicitly pulls or pushes C<refs/karr/*> when you want to inspect or control
sync separately.

=back

=head2 Snapshot And Recovery

=over 4

=item * L<App::karr::Cmd::Backup>

Exports the entire C<refs/karr/*> namespace as YAML.

=item * L<App::karr::Cmd::Restore>

Replaces the current board refs from a YAML snapshot. This is intentionally
destructive and requires explicit confirmation flags.

=item * L<App::karr::Cmd::Destroy>

Deletes the whole board namespace from the repository.

=item * L<App::karr::Cmd::Repair>

Migrates a board written by karr 0.402 or earlier off double-encoded UTF-8.
Reports by default; C<--yes> rewrites the affected refs. Idempotent, and a
no-op on a board that is already current.

=back

=head2 Helper Refs

=over 4

=item * L<App::karr::Cmd::SetRefs>

Stores arbitrary helper payloads in allowed refs outside protected namespaces.

=item * L<App::karr::Cmd::GetRefs>

Fetches one helper ref and prints only its payload to standard output.

=back

=head1 EXIT CODES

F<karr>'s primary callers are agents scripting the CLI, for whom C<$?> is part
of the interface. The exit code is therefore a stable contract (see
F<docs/adr/0002-exit-code-contract.md>):

=over 4

=item * B<0> — success, including no-op successes such as re-archiving a task
that is already archived.

=item * B<1> — runtime failure: a task id was not found, the board is missing,
a Git or sync operation failed, a destructive command was refused for want of
C<--yes>, or a batch committed partial work but at least one item failed.

=item * B<2> — usage error: an unknown command, an unknown option, an invalid
option value, or a surplus or missing positional argument.

=back

The C<1>-versus-C<2> split is what lets a scripting agent tell "I called this
wrong" apart from "the operation failed". This deviates deliberately from
kanban-md, which exits C<1> for everything.

=head1 DOCKER USAGE

The CLI works well through the published Docker image. A common alias is:

    alias karr='docker run --rm -it -w /work -e HOME=/home/karr \
      -v "$(pwd):/work" \
      -v "$HOME/.gitconfig:/home/karr/.gitconfig:ro" \
      -v "$HOME/.claude:/home/karr/.claude" \
      -v "$HOME/.codex:/home/karr/.codex" \
      raudssus/karr:latest'

That keeps the current repository mounted at F</work> and lets the default
image adapt its runtime uid and gid to the owner of the mounted workspace. The
CLI syntax stays the same after that:

    karr board
    karr skill install --agent codex --global --force

If you prefer a fixed non-root runtime, use C<raudssus/karr:user> instead. That
image defaults to uid and gid C<1000:1000> and is intended as the predictable
base for downstream custom images. Custom fixed-user derivatives can override
C<KARR_UID> and C<KARR_GID> at build time.

=head1 EXAMPLES

Initialize a new board and install the project-local Claude Code skill:

    karr init --name "HandyIntelligence Prototype" --claude-skill

Claim and start the next task for a generated agent:

    NAME=$(karr agentname)
    karr pick --claim "$NAME" --move in-progress

Export the board before trying a destructive restore:

    karr backup > karr-backup.yml
    karr restore --yes < karr-backup.yml

=head1 SEE ALSO

L<App::karr>, L<App::karr::Task>, L<App::karr::BoardStore>,
L<App::karr::Git>, L<App::karr::Cmd::Init>, L<App::karr::Cmd::Skill>,
L<App::karr::Cmd::Backup>, L<App::karr::Cmd::Restore>

=head1 SUPPORT

=head2 Issues

Please report bugs and feature requests on GitHub at
L<https://github.com/Getty/karr/issues>.

=head2 IRC

Join C<#langertha> on C<irc.perl.org> or message Getty directly.

=head1 CONTRIBUTING

Contributions are welcome! Please fork the repository and submit a pull request.

=head1 AUTHOR

Torsten Raudssus <getty@cpan.org>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2026 by Torsten Raudssus <torsten@raudssus.de> L<https://raudssus.de/>.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut
