App-karr

 view release on metacpan or  search on metacpan

.claude/skills/karr-foundation-cli/SKILL.md  view on Meta::CPAN

Place in repo root. All keys optional. Agent execution is opt-in: a board runs
an agent only if it has `command` **or** `claude: true`. With no agent on any
board, `karr-foundation` prints a read-only overview instead of running
anything (see "Overview").

```yaml
claude: true              # synthesize the canonical claude command (opt-in)
claude_bin: claude        # binary for claude: true (default: claude)
claude_max_turns: 30      # --max-turns for claude: true (default: 30)
claude_permission_mode: bypassPermissions   # (default: bypassPermissions)
prompt: >-                # agent instruction, exposed to the command as $PROMPT
  Use the karr-coordinator skill: pick the next actionable task and move it.
# command: claude -p "$PROMPT"   # explicit command; wins over claude: true
on_idle: skip             # 'skip' (default) | 'always-run'
drain: true               # loop until drained (default) | false for single run
max_runtime: 1800         # seconds: per-command SIGKILL (0 = no timeout)
max_attempts: 2           # stalls on one task before auto-block (default: 2)
max_iterations: 50        # hard cap on drain iterations / drain budget (default: 50)
cooldown_base: 1          # cooldown minutes at level 0 (default: 1)
cooldown_max: 64          # cooldown ceiling in minutes (default: 64)
error_patterns:           # extra case-insensitive substrings → common-error
  - my custom api error
```

`claude`, `claude_bin`, the `claude_*` knobs, `command` and `prompt` may also be
set globally in `config.yml` (`default_command` / `default_prompt`); the per-repo
`.karr` value wins.

## Overview

`karr-foundation --status` (and the default when no board has an agent) prints a
read-only dashboard of every board: status counts, in-progress/blocked tasks,
and lock/cooldown state. No agent is run — usable by a human to coordinate work.

## Options

.claude/skills/karr-foundation-cli/SKILL.md  view on Meta::CPAN

.karr.log     # run log
```

## Environment

During agent execution foundation sets:

- `KARR_REPO` — the repo path
- `KARR_ROLE=agent` — so nested `karr` calls log under the `agent` identity
  (`refs/karr/log/agent/<email>`); a human defaults to `user`
- `PROMPT` — the resolved agent instruction (`prompt` / `default_prompt` /
  built-in default), referenced as `$PROMPT` in the command template

## Cron example

```bash
# Every 5 minutes, all repos
*/5 * * * * karr-foundation

# With verbose logging to syslog
*/5 * * * * karr-foundation --verbose 2>&1 | logger -t karr-foundation

Changes  view on Meta::CPAN

      (max_runtime: 0 disables it entirely). Output is always appended to
      .karr.log regardless of TTY.
    - karr-foundation is now a multi-board coordinator, not just an agent
      runner. Agent execution is opt-in: with no agent configured on any
      board, the default action is a read-only overview of every board
      (status counts, in-progress/blocked, lock/cooldown state). `--status`
      forces that overview regardless of configuration.
    - karr-foundation: `claude: true` synthesizes the canonical claude
      invocation so you needn't retype it; `claude_bin`, `claude_max_turns`
      and `claude_permission_mode` override the parts. The agent instruction
      is exposed as the `$PROMPT` substitution variable (settable via `prompt`
      in .karr or `default_prompt` in config), usable in any command template.
    - Activity log entries are now keyed by a role-qualified identity
      (`refs/karr/log/<role>/<email>`, role `user` or `agent`) so a human and
      an AI sharing one Git config are told apart. The role propagates to
      nested karr calls via the KARR_ROLE env var (foundation sets `agent`);
      pre-existing bare-email logs are still read for the `user` role.
    - karr show: with no ID shows the single most recently updated task;
      `--last N` widens that, `--me` shows the task(s) the current identity
      most recently acted on (via the activity log), and `--agent NAME` shows
      the task(s) most recently claimed by that agent name.
    - karr board: hide the `@claimed_by` badge and claimed-count for tasks in

lib/App/karr/Cmd/Delete.pm  view on Meta::CPAN

Deletes one or more task files from the board. This is the destructive
alternative to L<App::karr::Cmd::Archive>, which only changes the status to
C<archived>.

=head1 OPTIONS

=over 4

=item * C<--yes>

Skips the interactive confirmation prompt for each task.

=back

=head1 SEE ALSO

L<karr>, L<App::karr>, L<App::karr::Cmd::Archive>,
L<App::karr::Cmd::Backup>, L<App::karr::Cmd::Destroy>

=head1 SUPPORT

lib/App/karr/Foundation.pm  view on Meta::CPAN

# claude_permission_mode override the defaults (per-repo, then global).
sub _claude_command {
  my ( $self, $karr ) = @_;
  my $cfg = $self->_config_data;
  my $bin   = $karr->{claude_bin}             // $cfg->{claude_bin}             // 'claude';
  my $turns = $karr->{claude_max_turns}       // $cfg->{claude_max_turns}       // 30;
  my $perm  = $karr->{claude_permission_mode} // $cfg->{claude_permission_mode} // 'bypassPermissions';
  return qq{$bin -p "\$PROMPT" --permission-mode $perm --max-turns $turns};
}

# The agent instruction exposed as $PROMPT. .karr 'prompt' > config
# 'default_prompt' > the built-in default.
sub _prompt_for {
  my ( $self, $karr ) = @_;
  return $karr->{prompt}
      // $self->_config_data->{default_prompt}
      // $DEFAULT_PROMPT;
}

1;

__END__

=pod

=encoding UTF-8

lib/App/karr/Foundation.pm  view on Meta::CPAN


  scan:
    - /path/to/parent-dir   # finds all direct subdirs that have a .karr file

B<Per-repo .karr file:>

  claude: true              # synthesize the canonical claude command (opt-in)
  claude_bin: claude        # binary for claude: true (default: claude)
  claude_max_turns: 30      # --max-turns for claude: true (default: 30)
  claude_permission_mode: bypassPermissions   # (default: bypassPermissions)
  prompt: >-                # agent instruction, exposed as $PROMPT
    Use the karr-coordinator skill: pick the next actionable task and move it.
  command: claude -p "$PROMPT"   # explicit command; wins over claude: true
  on_idle: skip             # 'skip' (default) | 'always-run'
  max_runtime: 1800         # seconds: per-command SIGKILL (0 = no limit)
  drain: true               # loop until drained (default) | false for single run
  max_attempts: 2           # stalls on one task before auto-block (default: 2)
  max_iterations: 50        # hard cap on drain iterations (default: 50)
  cooldown_base: 1          # cooldown minutes at level 0 (default: 1)
  cooldown_max: 64          # cooldown ceiling in minutes (default: 64)
  error_patterns:           # extra case-insensitive substrings → common-error
    - my custom api error

C<claude>, C<claude_bin>, C<claude_max_turns>, C<claude_permission_mode>,
C<command> and C<prompt>/C<default_prompt> may also be set globally in the
config file; the per-repo F<.karr> value wins.

B<Coordinator and overview.> Agent execution is opt-in — a board runs an agent
only via C<command> or C<< claude: true >>. When B<no> board has an agent
configured, the default action is a read-only B<overview> of every board
(status counts, in-progress/blocked tasks, lock and cooldown state); a human
can use foundation purely to coordinate their own work. C<--status> forces the
overview regardless of configuration.

B<Live output.> When run interactively (TTY) or with C<--verbose>, the agent's

lib/App/karr/Foundation/Runner.pm  view on Meta::CPAN

  my ( $self, $repo, $karr, $cmd ) = @_;
  my $command      = $cmd // $karr->{command};
  my $max_runtime  = $karr->{max_runtime} // 1800;
  my $stream_terms = $self->foundation->_stream_to_terminal;

  # Environment for the child (and all karr calls it spawns). Set before the
  # substitution so a command template — including the synthesized claude
  # command — can reference $PROMPT, $KARR_REPO, etc.
  local $ENV{KARR_REPO} = "$repo";
  local $ENV{KARR_ROLE} = 'agent';
  local $ENV{PROMPT}    = $self->foundation->_prompt_for($karr);

  # Env-var substitution in command string
  $command =~ s/\$\{(\w+)\}/$ENV{$1} \/\/ ''/ge;
  $command =~ s/\$(\w+)/$ENV{$1} \/\/ ''/ge;

  $self->foundation->_append_log( $repo, "START command=$command" );
  $self->foundation->_say_verbose("exec in $repo: $command");

  if ( $self->foundation->dry_run ) {
    $self->foundation->_append_log( $repo, "DRY-RUN (skipped)" );

lib/App/karr/Foundation/Runner.pm  view on Meta::CPAN


=head1 DESCRIPTION

L<App::karr::Foundation::Runner> runs a single agent command for
L<App::karr::Foundation>. It forks the command under C</bin/sh -c>, reads its
combined stdout/stderr over a native pipe, and tees each chunk to the
persistent C<.karr.log>, the terminal (when streaming), and an in-memory buffer
used for error scanning, enforcing the per-run C<max_runtime> timeout. It also
classifies observable common errors (rate limit, auth, network, 5xx, ...). A
weak back-reference to the owning foundation supplies shared options and helpers
(C<dry_run>, C<_stream_to_terminal>, C<_prompt_for>, C<_append_log>,
C<_say_verbose>).

=head1 SUPPORT

=head2 Issues

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

=head2 IRC

t/33-foundation-run.t  view on Meta::CPAN

  my ( $code, $out ) = $f->_run_command( $repo, { max_runtime => 0 }, 'echo nolimit' );
  is $code, 0, 'runs to completion with no deadline';
  like $out, qr/nolimit/, 'output captured under undef-timeout path';
};

subtest 'timeout sends SIGKILL and reports exit -1' => sub {
  my $repo = path( tempdir( CLEANUP => 1 ) );
  my $start = time;
  my ( $code, $out ) = $f->_run_command( $repo, { max_runtime => 1 }, 'sleep 30' );
  is $code, -1, 'timed-out run reports exit -1';
  ok time - $start < 10, 'killed promptly, not after the full sleep';
};

done_testing;

t/34-foundation-command.t  view on Meta::CPAN


  my $fc = App::karr::Foundation->new( command => 'cli', _config_data => { default_command => 'gd' } );
  is $fc->_agent_command( $repo, { command => 'foo' } ), 'cli',
    'CLI --command wins over everything';

  my $fg = App::karr::Foundation->new( _config_data => { claude => 1 } );
  like $fg->_agent_command( $repo, {} ), qr/^claude -p "\$PROMPT"/,
    'global claude: true applies when no per-repo agent';
};

subtest '_prompt_for precedence' => sub {
  my $f = App::karr::Foundation->new( _config_data => { default_prompt => 'CFG' } );
  is $f->_prompt_for( { prompt => 'KARR' } ), 'KARR', '.karr prompt wins';
  is $f->_prompt_for( {} ), 'CFG', 'config default_prompt next';

  my $f2 = App::karr::Foundation->new( _config_data => {} );
  like $f2->_prompt_for( {} ), qr/karr-coordinator skill/, 'built-in default last';
};

subtest '$PROMPT is substituted into the command at run time' => sub {
  my $rdir = path( tempdir( CLEANUP => 1 ) );
  my $f = App::karr::Foundation->new( _config_data => {} );

  my ( $code, $out ) = $f->_run_command( $rdir, {}, 'printf "%s" "$PROMPT"' );
  like $out, qr/karr-coordinator skill/, 'default prompt reaches $PROMPT';

  my ( $c2, $o2 ) = $f->_run_command( $rdir, { prompt => 'CUSTOM-PROMPT' }, 'printf "%s" "$PROMPT"' );
  is $o2, 'CUSTOM-PROMPT', 'per-repo prompt overrides';
};

subtest '_print_overview renders a board summary' => sub {
  my $rdir = path( tempdir( CLEANUP => 1 ) );
  system( 'git', 'init', '-q', "$rdir" );
  system( 'git', '-C', "$rdir", 'config', 'user.email', 'o@test.com' );
  system( 'git', '-C', "$rdir", 'config', 'user.name', 'O' );
  my $git = App::karr::Git->new( dir => "$rdir" );
  $git->write_ref( 'refs/karr/config', Dump( { version => 1 } ) );
  $git->write_ref( 'refs/karr/meta/next-id', "3\n" );



( run in 0.656 second using v1.01-cache-2.11-cpan-0b5f733616e )