view release on metacpan or search on metacpan
.claude/agents/perl-release-check.md view on Meta::CPAN
ls dist.ini && echo "Dist::Zilla" || echo "Plain"
```
## For Dist::Zilla Distributions
Use `dzil-release-check` skills for:
- Understanding dist.ini configuration
- @Author::GETTY conventions
- Plugin bundle options
Run: `dzil test` and `dzil build` to verify
## For Plain Distributions
Check manually:
- `Makefile.PL` or `Build.PL`
- `cpanfile` or `META.json`
- `lib/` directory structure
- `t/` tests
## Checklist (Both Types)
checks each repo for board changes or open tasks, and invokes the
per-repo `.karr` command. Supports `--force`, `--dry-run`, `--verbose`.
Per-repo state in `.karr.state` / `.karr.lock` / `.karr.log` (gitignored).
0.202 2026-05-17 05:17:07Z
- Fix `karr list` crashing with "Can't locate object method 'load_tasks'":
Cmd::List was missing `with 'App::karr::Role::BoardAccess'` (the role was
`use`d but never consumed). Surfaced while writing worktree tests.
- Add t/29-worktree.t covering init/create/list inside `git worktree`
directories and verifying refs/karr/* are correctly shared between the
main work-tree and additional worktrees.
0.200 2026-05-16 17:45:23Z
- Centralize config knowledge: priority_order(), class_order(),
terminal_statuses(), is_terminal_status(), status_requires_claim()
moved to Config and BoardStore (no more duplication across commands).
- Add all_status_names(), status_requires_claim(), is_terminal_status()
to BoardStore for encapsulated status config access.
- Convert all require Time::Piece to use Time::Piece (Pick, Move, Edit).
docs/superpowers/plans/2026-03-19-v0004-implementation.md view on Meta::CPAN
ok $sub_git->is_repo, 'detects git repo from subdirectory';
# Test non-repo
my $non_repo = tempdir( CLEANUP => 1 );
my $non_git = App::karr::Git->new( dir => $non_repo );
ok !$non_git->is_repo, 'non-repo returns false';
done_testing;
```
- [ ] **Step 2: Run test to verify it fails**
Run: `prove -l t/13-git-refs.t`
Expected: FAIL (is_repo uses old `.git` child check, subdirectory fails)
- [ ] **Step 3: Implement `_git_cmd`, `_git_cmd_stdin`, and fixed `is_repo`**
Replace the entire `Git.pm` with safe execution:
```perl
# ABSTRACT: Git operations for karr sync (via CLI)
docs/superpowers/plans/2026-03-19-v0004-implementation.md view on Meta::CPAN
my %ids;
for (split /\n/, $output) {
$ids{$1} = 1 if m{refs/karr/tasks/(\d+)/};
}
return sort { $a <=> $b } keys %ids;
}
1;
```
- [ ] **Step 4: Run test to verify it passes**
Run: `prove -l t/13-git-refs.t`
Expected: PASS
- [ ] **Step 5: Extend test for commit-wrapped write_ref/read_ref roundtrip**
Append to `t/13-git-refs.t`:
```perl
# Test write_ref / read_ref roundtrip with commit-wrapped refs
docs/superpowers/plans/2026-03-19-v0004-implementation.md view on Meta::CPAN
# Test read of nonexistent ref
my $missing = $git->read_ref('refs/karr/nonexistent');
is $missing, '', 'missing ref returns empty string';
# Test delete_ref
$git->delete_ref('refs/karr/test/data');
my $after_delete = $git->read_ref('refs/karr/test/data');
is $after_delete, '', 'deleted ref returns empty string';
```
- [ ] **Step 6: Run test to verify it passes**
Run: `prove -l t/13-git-refs.t`
Expected: PASS
- [ ] **Step 7: Run all existing tests to verify no regressions**
Run: `prove -l t/`
Expected: All pass (Git.pm interface is backward-compatible for existing callers)
- [ ] **Step 8: Commit**
```bash
git add lib/App/karr/Git.pm t/13-git-refs.t
git commit -m "feat: rewrite Git.pm with safe execution and commit-wrapped refs"
```
docs/superpowers/plans/2026-03-19-v0004-implementation.md view on Meta::CPAN
my $file_task = App::karr::Task->from_file($file);
is $file_task->id, $task->id, 'from_file matches from_string: id';
is $file_task->title, $task->title, 'from_file matches from_string: title';
is $file_task->body, $task->body, 'from_file matches from_string: body';
ok $file_task->has_file_path, 'from_file: has file_path';
done_testing;
```
- [ ] **Step 2: Run test to verify it fails**
Run: `prove -l t/14-task-parse.t`
Expected: FAIL (`from_string` method does not exist yet)
- [ ] **Step 3: Implement `_parse_content` and `from_string`, refactor `from_file`**
In `lib/App/karr/Task.pm`, replace the existing `from_file` with:
```perl
sub _parse_content {
docs/superpowers/plans/2026-03-19-v0004-implementation.md view on Meta::CPAN
}
sub from_file {
my ($class, $file) = @_;
$file = path($file);
my ($fm, $body) = $class->_parse_content($file->slurp_utf8);
return $class->new(%$fm, body => $body, file_path => $file);
}
```
- [ ] **Step 4: Run test to verify it passes**
Run: `prove -l t/14-task-parse.t`
Expected: PASS
- [ ] **Step 5: Run all existing tests for regressions**
Run: `prove -l t/`
Expected: All pass (from_file behavior unchanged)
- [ ] **Step 6: Commit**
docs/superpowers/plans/2026-03-19-v0004-implementation.md view on Meta::CPAN
# Verify content matches
my $reloaded = App::karr::Task->from_file($files[0]);
is $reloaded->id, 1, 'materialized task 1: id';
is $reloaded->title, 'Local task one', 'materialized task 1: title';
is $reloaded->body, 'Body one', 'materialized task 1: body';
done_testing;
```
- [ ] **Step 2: Run test to verify it passes (uses already-implemented Git methods)**
Run: `prove -l t/15-sync.t`
Expected: PASS (this tests the pattern, not the role methods yet)
- [ ] **Step 3: Implement sync helpers in BoardAccess role**
Add to `lib/App/karr/Role/BoardAccess.pm`:
```perl
sub sync_before {
docs/superpowers/plans/2026-03-22-ref-first-board-implementation.md view on Meta::CPAN
### Task 4: Make `init` Git-only and ref-only
**Files:**
- Modify: `lib/App/karr/Cmd/Init.pm`
- Modify: `t/25-init-ref-first.t`
- [ ] **Step 1: Write failing tests for `karr init` outside Git, inside Git, and re-init on existing refs**
- [ ] **Step 2: Run `prove -l t/25-init-ref-first.t`**
Expected: FAIL because `init` still creates `karr/`.
- [ ] **Step 3: Update `karr init` to verify Git, write `refs/karr/config`, write `refs/karr/meta/next-id`, and skip any persistent board directory**
- [ ] **Step 4: Preserve optional skill installation as a separate concern**
- [ ] **Step 5: Run `prove -l t/25-init-ref-first.t`**
Expected: PASS.
## Chunk 3: Move commands onto the ref-backed store
### Task 5: Convert read commands
**Files:**
- Modify: `lib/App/karr/Cmd/List.pm`
docs/superpowers/plans/2026-03-22-ref-first-board-implementation.md view on Meta::CPAN
## Chunk 5: Full verification
### Task 9: Run final verification
**Files:**
- Modify: repository working tree as needed
- [ ] **Step 1: Run `prove -l t`**
- [ ] **Step 2: Run `podchecker lib/App/karr.pm lib/App/karr/Cmd/*.pm lib/App/karr/*.pm lib/App/karr/Role/*.pm`**
- [ ] **Step 3: Run `find lib -name '*.pm' -print0 | xargs -0 -n1 perl -c`**
- [ ] **Step 4: Run `dzil build` and verify Docker images still build**
- [ ] **Step 5: Commit the ref-first migration with Codex trailers**
docs/superpowers/specs/2026-03-19-v0004-release-design.md view on Meta::CPAN
docker run --rm -v $(pwd):/work \
-v $HOME/.gitconfig:/root/.gitconfig:ro \
-v $HOME/.ssh:/root/.ssh:ro \
raudssus/karr sync
```
## Test Coverage Requirements
The ref-based sync is the highest-risk change. Required tests:
1. **`write_ref`/`read_ref` roundtrip** â verify commit-wrapped refs can be written and read back
2. **`push`/`fetch` with commit-wrapped refs** â verify refs actually transfer between repos (test with `git clone --bare` + two working copies)
3. **Materialize/serialize roundtrip** â create task via file, serialize to ref, delete file, materialize from ref, verify content matches
4. **Pick with Lock** â two sequential picks should claim different tasks
5. **Log append + read** â append entries from two "agents", verify merged output is sorted
6. **`from_string`/`from_file` parity** â same content parsed both ways produces identical Task objects
7. **`is_repo` from subdirectory** â verify detection works from `karr/` inside a git repo
8. **Config sync** â `next_id` collision prevention across two agents
Existing tests (`t/01-task.t` through `t/07-context.t`) must continue to pass unchanged.
## Out of Scope (v0.005+)
- `metrics` command (throughput, cycle time)
- `sync --watch` (background daemon polling)
- Dependency checking (block tasks with unsatisfied deps)
- Self-healing IDs
docs/superpowers/specs/2026-03-22-ref-first-board-design.md view on Meta::CPAN
`next_id` must not live in config, because it changes frequently and should not
conflict with actual board settings.
## Command behavior
### Init
`karr init` requires a Git repository. It should:
- verify the current directory is inside a Git worktree
- fail if `refs/karr/config` already exists
- write the initial config override ref
- write `refs/karr/meta/next-id`
- optionally push those refs if that becomes the standard write path for all
mutating commands
It should not create `karr/` in the working tree.
### Read commands
t/03-archive.t view on Meta::CPAN
$board->mkpath;
my $tasks = $board->child('tasks');
$tasks->mkpath;
DumpFile($board->child('config.yml')->stringify, App::karr::Config->default_config);
my $task = App::karr::Task->new(
id => 1, title => 'Test Archive', status => 'done',
);
$task->save($tasks->stringify);
# Reload and verify
my $loaded = App::karr::Task->from_file($tasks->child('001-test-archive.md'));
is $loaded->status, 'done', 'starts as done';
# Simulate archive
$loaded->status('archived');
$loaded->save;
my $after = App::karr::Task->from_file($tasks->child('001-test-archive.md'));
is $after->status, 'archived', 'now archived';
};
t/07-context.t view on Meta::CPAN
App::karr::Task->new(
id => 2, title => 'Blocked Task', status => 'in-progress',
priority => 'medium', blocked => 'waiting on API',
)->save($tasks->stringify);
App::karr::Task->new(
id => 3, title => 'Done Task', status => 'done',
priority => 'low', completed => gmtime->strftime('%Y-%m-%d'),
)->save($tasks->stringify);
# Load and verify tasks
my @files = sort $tasks->children(qr/\.md$/);
is scalar @files, 3, 'three task files created';
my @loaded = map { App::karr::Task->from_file($_) } @files;
my @in_progress = grep { $_->status eq 'in-progress' && !$_->has_blocked } @loaded;
is scalar @in_progress, 1, 'one active non-blocked task';
my @blocked = grep { $_->has_blocked } @loaded;
is scalar @blocked, 1, 'one blocked task';
};
my $line1 = '{"ts":"2026-03-19T10:00:00Z","agent":"agent-a","action":"pick","task_id":1}';
$git->write_ref($ref_a, $line1);
my $line2 = '{"ts":"2026-03-19T10:05:00Z","agent":"agent-a","action":"handoff","task_id":1}';
$git->write_ref($ref_a, "$line1\n$line2");
my $ref_b = 'refs/karr/log/agent-b_test.com';
my $line3 = '{"ts":"2026-03-19T10:02:00Z","agent":"agent-b","action":"pick","task_id":2}';
$git->write_ref($ref_b, $line3);
# Read and verify
my $output_a = $git->read_ref($ref_a);
my $output_b = $git->read_ref($ref_b);
ok $output_a, 'agent-a log ref exists';
ok $output_b, 'agent-b log ref exists';
# Parse and merge
my @entries;
for my $log_content ($output_a, $output_b) {
for my $line (split /\n/, $log_content) {
push @entries, decode_json($line);