App-karr
view release on metacpan or search on metacpan
- Add karr log command for activity trail (per-agent NDJSON refs)
- Add --claimed-by filter to list command
- Add Task->from_string for ref-based loading
- Rewrite Git.pm with safe execution (_git_cmd, no shell injection)
- Fix write_ref to create commit-wrapped refs (blobâtreeâcommit)
- Fix is_repo to work from subdirectories (git rev-parse)
- Fix push refspec to refs/karr/*:refs/karr/*
- Pick command uses Lock for atomic task claiming
- Remove .gitignore manipulation from init
- Extract sync_before/sync_after into BoardAccess role
- Extract _parse_timeout/_claim_expired into ClaimTimeout role
- Refactor Lock.pm to accept pre-built Git object
- Docker: add default git identity ENV vars
- Add karr sync command (--push, --pull)
- Add auto-sync to write commands (create, move, edit, delete, pick, handoff, archive)
- Add App::karr::Git for Git operations via CLI
- Add App::karr::Lock for task locking via refs/karr/tasks/<id>/lock
- Add Docker support (raudssus/karr on GHCR)
- Initial release
- Implemented archive command (soft-delete to archived status)
- Implemented handoff command (move to review with claim, note, block/release)
lib/App/karr/Cmd/Pick.pm
lib/App/karr/Cmd/Restore.pm
lib/App/karr/Cmd/SetRefs.pm
lib/App/karr/Cmd/Show.pm
lib/App/karr/Cmd/Skill.pm
lib/App/karr/Cmd/Sync.pm
lib/App/karr/Config.pm
lib/App/karr/Git.pm
lib/App/karr/Lock.pm
lib/App/karr/Role/BoardAccess.pm
lib/App/karr/Role/ClaimTimeout.pm
lib/App/karr/Role/Output.pm
lib/App/karr/Task.pm
share/claude-skill.md
t/00-load.t
t/01-task.t
t/02-config.t
t/03-archive.t
t/04-handoff.t
t/05-agent-name.t
t/06-config-cmd.t
docs/superpowers/plans/2026-03-19-v0004-implementation.md view on Meta::CPAN
| `lib/App/karr/Cmd/Sync.pm` | Sync command | Rewrite: full sync with materialization |
| `lib/App/karr/Cmd/Pick.pm` | Pick command | Rewrite: Lock integration with immediate push |
| `lib/App/karr/Cmd/List.pm` | List command | Add: `--claimed-by` filter |
| `lib/App/karr/Cmd/Init.pm` | Init command | Remove `.gitignore` manipulation |
| `lib/App/karr/Cmd/Create.pm` | Create command | Remove `_sync_after`, use role's `sync_before`/`sync_after` |
| `lib/App/karr/Cmd/Move.pm` | Move command | Same |
| `lib/App/karr/Cmd/Edit.pm` | Edit command | Same |
| `lib/App/karr/Cmd/Delete.pm` | Delete command | Same |
| `lib/App/karr/Cmd/Archive.pm` | Archive command | Same |
| `lib/App/karr/Cmd/Handoff.pm` | Handoff command | Remove `_sync_after`, `_parse_timeout`, `_claim_expired`; use shared role |
| `lib/App/karr/Role/ClaimTimeout.pm` | Shared claim timeout logic | New: `_parse_timeout`, `_claim_expired` consumed by Pick + Handoff |
| `Dockerfile` | Docker image | Add git identity ENV vars |
| `Changes` | Changelog | Remove "experimental", add v0.004 entries |
### New Files
| File | Responsibility |
|------|---------------|
| `lib/App/karr/Cmd/Log.pm` | Activity log command |
| `t/13-git-refs.t` | Git ref roundtrip tests (commit-wrapped write/read) |
| `t/14-task-parse.t` | `from_string`/`from_file` parity tests |
docs/superpowers/plans/2026-03-19-v0004-implementation.md view on Meta::CPAN
Remove `_sync_after` sub (lines 93-100). Replace line 106 with `$self->sync_before;`. Add `$self->sync_after;` before the json output block.
- [ ] **Step 4: Update Delete.pm**
Remove `_sync_after` sub (lines 22-29). Replace line 35 with `$self->sync_before;`. Add `$self->sync_after;` before the json output block.
- [ ] **Step 5: Update Archive.pm**
Remove `_sync_after` sub (lines 16-23). Replace line 29 with `$self->sync_before;`. Add `$self->sync_after;` before the json output block.
- [ ] **Step 6a: Create ClaimTimeout role**
Create `lib/App/karr/Role/ClaimTimeout.pm` to share `_parse_timeout` and `_claim_expired` between Pick and Handoff:
```perl
# ABSTRACT: Shared claim timeout logic
package App::karr::Role::ClaimTimeout;
use Moo::Role;
use Time::Piece;
sub _parse_timeout {
my ($self, $timeout_str) = @_;
return 3600 unless $timeout_str;
if ($timeout_str =~ /^(\d+)h$/) { return $1 * 3600; }
if ($timeout_str =~ /^(\d+)m$/) { return $1 * 60; }
return 3600;
docs/superpowers/plans/2026-03-19-v0004-implementation.md view on Meta::CPAN
my $claimed = eval { Time::Piece->strptime($task->claimed_at =~ s/Z$//r, '%Y-%m-%dT%H:%M:%S') };
return 0 unless $claimed;
return (gmtime() - $claimed) > $timeout_secs;
}
1;
```
- [ ] **Step 6b: Update Handoff.pm**
Remove `_sync_after` sub. Remove `_parse_timeout` and `_claim_expired`. Add `with 'App::karr::Role::ClaimTimeout';` to the `with` line (alongside BoardAccess and Output). Replace sync call with `$self->sync_before;`. Add `$self->sync_after;` after sa...
- [ ] **Step 7: Update Pick.pm (minimal â full rewrite in Task 8)**
Remove `_sync_after` sub. Remove `_parse_timeout` and `_claim_expired`. Add `with 'App::karr::Role::ClaimTimeout';`. Replace sync call with `$self->sync_before;`. Add `$self->sync_after;` after the json output block.
- [ ] **Step 8: Run all tests**
Run: `prove -l t/`
Expected: All pass
- [ ] **Step 9: Commit**
```bash
git add lib/App/karr/Cmd/Create.pm lib/App/karr/Cmd/Move.pm lib/App/karr/Cmd/Edit.pm \
lib/App/karr/Cmd/Handoff.pm view on Meta::CPAN
use MooX::Cmd;
use MooX::Options (
usage_string => 'USAGE: karr handoff ID --claim NAME [--note TEXT] [--block REASON] [--release]',
);
use App::karr::Role::BoardAccess;
use App::karr::Role::Output;
use App::karr::Task;
use App::karr::Config;
use Time::Piece;
with 'App::karr::Role::BoardAccess', 'App::karr::Role::Output', 'App::karr::Role::ClaimTimeout';
option claim => (
is => 'ro',
format => 's',
required => 1,
doc => 'Agent name claiming the task',
);
option note => (
lib/App/karr/Cmd/Pick.pm view on Meta::CPAN
use MooX::Cmd;
use MooX::Options (
usage_string => 'USAGE: karr pick --claim NAME [--move STATUS] [--status LIST] [--tags LIST]',
);
use App::karr::Role::BoardAccess;
use App::karr::Role::Output;
use App::karr::Task;
use App::karr::Config;
use Time::Piece;
with 'App::karr::Role::BoardAccess', 'App::karr::Role::Output', 'App::karr::Role::ClaimTimeout';
option claim => (
is => 'ro',
format => 's',
required => 1,
doc => 'Agent name to claim the task for',
);
option status => (
lib/App/karr/Role/ClaimTimeout.pm view on Meta::CPAN
# ABSTRACT: Shared claim timeout logic
package App::karr::Role::ClaimTimeout;
our $VERSION = '0.102';
use Moo::Role;
use Time::Piece;
sub _parse_timeout {
my ($self, $timeout_str) = @_;
return 3600 unless $timeout_str;
if ($timeout_str =~ /^(\d+)h$/) { return $1 * 3600; }
if ($timeout_str =~ /^(\d+)m$/) { return $1 * 60; }
lib/App/karr/Role/ClaimTimeout.pm view on Meta::CPAN
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
App::karr::Role::ClaimTimeout - Shared claim timeout logic
=head1 VERSION
version 0.102
=head1 DESCRIPTION
Shared helper role for commands that need to interpret C<claim_timeout> values
and determine whether an existing claim should still block other agents.
( run in 1.661 second using v1.01-cache-2.11-cpan-39bf76dae61 )