App-karr

 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)

Changes  view on Meta::CPAN

      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';
};

t/17-log.t  view on Meta::CPAN

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);



( run in 0.627 second using v1.01-cache-2.11-cpan-e1769b4cff6 )