@erclx/aitk 3.43.1 → 3.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/claude/.claude-plugin/plugin.json +1 -1
  2. package/claude/skills/{toolkit-cli → aitk-cli}/REQUIREMENT.md +4 -4
  3. package/claude/skills/{toolkit-cli → aitk-cli}/SKILL.md +1 -1
  4. package/claude/skills/{toolkit-feedback → aitk-feedback-file}/REQUIREMENT.md +3 -3
  5. package/claude/skills/{toolkit-feedback → aitk-feedback-file}/SKILL.md +2 -2
  6. package/claude/skills/{toolkit-triage → aitk-feedback-triage}/REQUIREMENT.md +3 -3
  7. package/claude/skills/{toolkit-triage → aitk-feedback-triage}/SKILL.md +3 -3
  8. package/claude/skills/{toolkit-operator → aitk-operator}/REQUIREMENT.md +3 -3
  9. package/claude/skills/{toolkit-operator → aitk-operator}/SKILL.md +3 -3
  10. package/claude/skills/{claude-screencast → aitk-screencast}/REQUIREMENT.md +3 -3
  11. package/claude/skills/{claude-screencast → aitk-screencast}/SKILL.md +2 -2
  12. package/claude/skills/{claude-slides-draft → aitk-slides-draft}/REQUIREMENT.md +3 -3
  13. package/claude/skills/{claude-slides-draft → aitk-slides-draft}/SKILL.md +1 -1
  14. package/claude/skills/{cli-script → bash-cli-script}/REQUIREMENT.md +2 -2
  15. package/claude/skills/{cli-script → bash-cli-script}/SKILL.md +2 -2
  16. package/claude/skills/bash-script/REQUIREMENT.md +2 -2
  17. package/claude/skills/bash-script/SKILL.md +2 -2
  18. package/claude/skills/ci-workflow/REQUIREMENT.md +1 -1
  19. package/claude/skills/claude-memory-review/SKILL.md +2 -2
  20. package/claude/skills/claude-memory-review/references/receipt-format.md +1 -1
  21. package/claude/skills/claude-seed-sync/REQUIREMENT.md +1 -1
  22. package/claude/skills/claude-seed-sync/SKILL.md +1 -1
  23. package/claude/skills/git-issue/REQUIREMENT.md +2 -2
  24. package/claude/skills/git-issue/SKILL.md +1 -1
  25. package/claude/skills/git-ship/REQUIREMENT.md +1 -1
  26. package/claude/skills/git-ship/SKILL.md +1 -2
  27. package/claude/skills/git-worktree/REQUIREMENT.md +1 -1
  28. package/claude/skills/git-worktree/SKILL.md +7 -4
  29. package/claude/skills/{restate → restate-plainly}/REQUIREMENT.md +2 -2
  30. package/claude/skills/{restate → restate-plainly}/SKILL.md +2 -2
  31. package/claude/skills/setup-init/REQUIREMENT.md +1 -1
  32. package/claude/skills/setup-init/SKILL.md +1 -1
  33. package/claude/skills/write-human/REQUIREMENT.md +1 -1
  34. package/claude/skills/write-human/SKILL.md +1 -1
  35. package/docs/agents/demo.md +1 -1
  36. package/docs/agents/index.md +1 -0
  37. package/docs/agents/overview.md +2 -2
  38. package/docs/agents/scripting.md +1 -1
  39. package/docs/agents/sessions.md +11 -5
  40. package/docs/agents/targets.md +83 -0
  41. package/docs/ai-workflow.md +16 -16
  42. package/docs/target-projects.md +1 -1
  43. package/governance/rules/lang/120-bash.md +1 -1
  44. package/package.json +1 -1
  45. package/scripts/core/regen-tooling-paths.sh +1 -1
  46. package/scripts/core/verify.sh +1 -1
  47. package/src/claude/cases/authoring.ts +2 -2
  48. package/src/claude/cases/claude-workflow.ts +2 -2
  49. package/src/claude/cases/setup.ts +6 -6
  50. package/src/cli.ts +3 -0
  51. package/src/commands/demo.ts +1 -1
  52. package/src/commands/sessions.ts +24 -8
  53. package/src/commands/targets.ts +319 -0
  54. package/src/demo/beats.ts +1 -1
  55. package/src/sessions/claim.ts +7 -0
  56. package/src/sync/stamp.ts +9 -0
  57. package/src/targets/pulls.ts +250 -0
  58. package/src/targets/registry.ts +161 -0
  59. package/src/targets/resolve.ts +145 -0
  60. package/src/targets/sweep.ts +246 -0
  61. package/standards/issue.md +1 -1
  62. /package/claude/skills/{cli-script → bash-cli-script}/references/template.md +0 -0
@@ -0,0 +1,83 @@
1
+ ---
2
+ title: Targets
3
+ description: The projects this toolkit installed into, the record the install writes against the sweep that backs it, what bounds each answer, and the cross-target pull request read
4
+ ---
5
+
6
+ # Targets
7
+
8
+ ## List
9
+
10
+ `aitk targets list` reports the projects this toolkit has installed into, with where each answer came from.
11
+
12
+ ```bash
13
+ aitk targets list
14
+ aitk targets list --json
15
+ aitk targets list --sweep ~/repos --json
16
+ ```
17
+
18
+ | Option | Behavior |
19
+ | ----------------- | ------------------------------------------------------- |
20
+ | `--json` | Add a machine-readable record on stdout |
21
+ | `--sweep <path…>` | Also walk these roots for targets the record never held |
22
+ | `--depth <n>` | How deep below each swept root to walk, defaulting to 4 |
23
+
24
+ Exit codes: `0` the population was read, `1` refused. A refusal carries a `reason` of `bad-depth`, and an absent index reports as unknown rather than as no targets.
25
+
26
+ An exit code says nothing about a call made from a session, since a shell profile may wrap the binary in a function taking its status from a later command. Read the record's fields rather than the exit when a skill consumes this.
27
+
28
+ ## Where the answer comes from
29
+
30
+ Every sync that stamps a target records it in a machine-level index at `$XDG_STATE_HOME/aitk/targets.json`, falling back to `~/.local/state/aitk/targets.json`. A project installed into since that shipped is therefore known without anyone naming it, and each row carries `stampedAt` from the sync that last touched it.
31
+
32
+ `--sweep` walks the roots given and finds what the record never held, which is every target installed before the index existed. A row says which of the two found it, and a row the record knows keeps that source even when the sweep reaches it too.
33
+
34
+ The record is the primary source and the sweep is the fallback, because they fail in opposite directions. A record only knows an install that ran after it shipped. A walk only knows the paths someone thought to name, which is how a hand census of this population was wrong in both directions at once: it counted two folders that had never been targets, counted one clone of a target already counted, and missed the clone a repair had actually run in.
35
+
36
+ ## What bounds a sweep
37
+
38
+ A `--sweep` run carries a `bound` object naming the roots walked, the depth, the folders the walk stopped at on reaching the cap, the roots it could not read, and the directory symlinks it did not follow. Read it before treating a count as the population.
39
+
40
+ The bound a sweep can never state is the machine. It reads this one, so a target on another machine or under a path nobody named is outside every answer it gives. That is the same limit the hand census had, and the record above is what closes it going forward rather than the sweep.
41
+
42
+ `--depth` refuses anything that is not a whole number rather than falling back to the default. A value that is not one leaves every depth test false, so the walk runs to the bottom of its root while the bound claims a cap it never applied.
43
+
44
+ ## A project held in more than one clone
45
+
46
+ A target cloned twice is one target. The sweep reads each checkout's `origin`, trims it to a form an ssh URL and an https URL of one repository agree on, and reports a single row holding every path.
47
+
48
+ The row leads with the clone the record knows, because the record only names one a sync actually ran in. Every caller reading a single path takes the first, and picking that by sort order is the shape behind a repair that ran in one clone while a count was taken against another, leaving the target reported as untouched.
49
+
50
+ Nothing removes a row. A project deleted, moved, or that dropped the toolkit stays in the index, so a count drifts upward over time. It surfaces on use rather than silently, since the read below refuses a path it cannot open.
51
+
52
+ ## Pulls
53
+
54
+ `aitk targets pulls` reports, per target, every open pull request with its checks and the heading its newest review pass carries.
55
+
56
+ ```bash
57
+ aitk targets pulls
58
+ aitk targets pulls ../caret ../stackr --json
59
+ ```
60
+
61
+ Naming paths reads those and looks up nothing. Naming none reads every target `aitk targets list` reports. One clone per project is read, since two checkouts sharing an origin answer the same query and reading both spends the rate limit to print one answer twice.
62
+
63
+ Exit codes: `0` at least one target was read, `1` refused or every target refused.
64
+
65
+ | Field | Holds |
66
+ | ---------------- | ----------------------------------------------------------------------- |
67
+ | `checks` | `passing`, `failing`, `pending`, or `null` when GitHub reported none |
68
+ | `review` | `open`, `closed`, or `null` when no pass has landed |
69
+ | `reviewReadable` | `false` when the review query failed, leaving `review` covering nothing |
70
+
71
+ `checks` is `null` rather than `passing` when no check ran at all, which is not the same answer. A failure outranks a run still going, since a job that already failed cannot be cleared by one still in flight.
72
+
73
+ `review` reads the first line of the newest pass carrying `## Review` or `## Review closed`, which `claude-pr-review` owns and posts. A target leaves a wave on `closed` rather than on a worker's reply.
74
+
75
+ A target that could not be read carries a `reason` rather than an empty pull list. Reading a failed query as no open work is what reports a target as done having read nothing, which is the failure mode of the hand-written shell loop this replaces.
76
+
77
+ | Reason | Means |
78
+ | ----------------- | ------------------------------------------------------ |
79
+ | `not-a-directory` | the path is not a directory, so nothing was read there |
80
+ | `gh-unavailable` | `gh` is not on the path |
81
+ | `list-failed` | the open pull request list could not be read |
82
+
83
+ The reads run one target at a time. They share one GitHub API quota, and a wave firing a dozen at once meets the secondary rate limit rather than an answer.
@@ -185,7 +185,7 @@ This section is the corpus the coverage claim is measured against: every name `a
185
185
  | Skill | When to use |
186
186
  | ---------------------------- | --------------------------------------------------------------------------------------- |
187
187
  | `aitk:setup-init` | On a fresh scaffold, to detect the stack and run the whole install chain in one pass |
188
- | `aitk:toolkit-operator` | On a project that already exists, to read what it carries before an install is picked |
188
+ | `aitk:aitk-operator` | On a project that already exists, to read what it carries before an install is picked |
189
189
  | `aitk:setup-gov` | When the governance rules are wanted without the tooling chain |
190
190
  | `aitk:setup-indexes` | When a markdown-heavy folder needs an `index.md` a session can browse |
191
191
  | `aitk:setup-plugins` | On a new machine, to install the community and official plugins user-scoped |
@@ -266,32 +266,32 @@ This section is the corpus the coverage claim is measured against: every name `a
266
266
  | `aitk:migration-context` | When `docs/` holds agent-flavored files belonging in `.claude/context/` |
267
267
  | `aitk:migration-superseded` | When a drift report names a `.claude/` file a folder has replaced |
268
268
  | `aitk:migration-standards-drop` | When the project still holds an installed `.claude/standards/` tree |
269
- | `aitk:toolkit-feedback` | When something in the toolkit is broken, missing, or off |
270
- | `aitk:toolkit-triage` | In the toolkit repo, to work through the open feedback issues |
269
+ | `aitk:aitk-feedback-file` | When something in the toolkit is broken, missing, or off |
270
+ | `aitk:aitk-feedback-triage` | In the toolkit repo, to work through the open feedback issues |
271
271
 
272
272
  ### Generate an artifact on demand
273
273
 
274
- | Skill | When to use |
275
- | -------------------------- | --------------------------------------------------------------------- |
276
- | `aitk:create-rule` | For a project-specific governance rule the toolkit does not ship |
277
- | `aitk:create-skill` | For a new `SKILL.md` |
278
- | `aitk:create-snippet` | For a reusable prompt |
279
- | `aitk:create-standard` | For a new authoring convention |
280
- | `aitk:bash-script` | For an interactive, human-facing shell tool |
281
- | `aitk:cli-script` | For a non-interactive automation, CI, or pipeline script |
282
- | `aitk:ci-workflow` | For a GitHub Actions workflow file |
283
- | `aitk:claude-slides-draft` | For a deck, drafted as `.claude/SLIDES.md` and rendered to PowerPoint |
284
- | `aitk:claude-screencast` | For a recording script with beats and defaults already seeded |
274
+ | Skill | When to use |
275
+ | ------------------------ | --------------------------------------------------------------------- |
276
+ | `aitk:create-rule` | For a project-specific governance rule the toolkit does not ship |
277
+ | `aitk:create-skill` | For a new `SKILL.md` |
278
+ | `aitk:create-snippet` | For a reusable prompt |
279
+ | `aitk:create-standard` | For a new authoring convention |
280
+ | `aitk:bash-script` | For an interactive, human-facing shell tool |
281
+ | `aitk:bash-cli-script` | For a non-interactive automation, CI, or pipeline script |
282
+ | `aitk:ci-workflow` | For a GitHub Actions workflow file |
283
+ | `aitk:aitk-slides-draft` | For a deck, drafted as `.claude/SLIDES.md` and rendered to PowerPoint |
284
+ | `aitk:aitk-screencast` | For a recording script with beats and defaults already seeded |
285
285
 
286
286
  ### Answer a question at any point
287
287
 
288
288
  | Skill | When to use |
289
289
  | -------------------------- | ------------------------------------------------------------------------------ |
290
- | `aitk:toolkit-cli` | Before a sync or install, to learn what it overwrites, merges, or leaves alone |
290
+ | `aitk:aitk-cli` | Before a sync or install, to learn what it overwrites, merges, or leaves alone |
291
291
  | `aitk:youtube-transcripts` | When a video transcript is wanted in the repo as context |
292
292
  | `aitk:claude-teach` | To learn a subject across sessions, in a workspace that holds the progress |
293
293
  | `aitk:write-human` | Before drafting or revising prose, for voice, rhythm, and density |
294
- | `aitk:restate` | When an answer or a document has to be read again in plain words |
294
+ | `aitk:restate-plainly` | When an answer or a document has to be read again in plain words |
295
295
 
296
296
  Every row answers a question rather than marking a point in a project's life, so a phase above would send a reader to the wrong group.
297
297
 
@@ -49,7 +49,7 @@ The chain is:
49
49
  - The agent reads `aitk tooling reference <stack>` (plus parents) as its audit context, follows it to generate eslint, vitest, playwright configs and the stack's setup script, and extends `.claude/context/ci.md` and `.claude/context/development.md` per the reference's extend sections <!-- audit-ignore-citations: .claude/context/development.md -->
50
50
  - `setup-verify` runs the installed `package.json` scripts (lint, typecheck, check, test, build) and reports pass or fail
51
51
 
52
- The chain serves a fresh scaffold and names a destination for the three states it does not. An existing project goes to `aitk:toolkit-operator`, which reads what the project already carries before naming a per-domain command. An install wanting the Claude layer without the tooling chain runs `aitk claude init` for the seed docs and then `aitk:setup-indexes` for the index system. A language the toolkit ships no stack for is the one of the three the chain still runs for, on `base`, with the fallback marked in the preview so it can be declined there.
52
+ The chain serves a fresh scaffold and names a destination for the three states it does not. An existing project goes to `aitk:aitk-operator`, which reads what the project already carries before naming a per-domain command. An install wanting the Claude layer without the tooling chain runs `aitk claude init` for the seed docs and then `aitk:setup-indexes` for the index system. A language the toolkit ships no stack for is the one of the three the chain still runs for, on `base`, with the fallback marked in the preview so it can be declined there.
53
53
 
54
54
  Keep the `## Scripts` table in `.claude/context/development.md` current as scripts are added. Base tooling seeds that entry with the commands it installs, and each stack reference extends the table. `project-commands` reads it to start the app or run a check on request, so a command missing from the table cannot be run that way. A project whose entry outgrew one file and split into `.claude/context/development/` keeps the table in `overview.md`, which is where the skill looks next. <!-- audit-ignore-citations: .claude/context/development.md -->
55
55
 
@@ -9,7 +9,7 @@ paths:
9
9
  ## Skill routing
10
10
 
11
11
  - Use `aitk:bash-script` for an interactive or human-facing script: prompts, a visual timeline UI, framed terminal output.
12
- - Use `aitk:cli-script` for a non-interactive script: automation, CI, cron, a pipeline helper, or anything run by an agent rather than watched by a person.
12
+ - Use `aitk:bash-cli-script` for a non-interactive script: automation, CI, cron, a pipeline helper, or anything run by an agent rather than watched by a person.
13
13
  - Load the matched skill's own reference templates rather than hand-rolling interactivity or logging patterns outside them.
14
14
  - Report it rather than proceeding silently when the matched skill does not resolve. Both ship with the plugin and this rule ships with the CLI, so a project that installed governance alone does not have them.
15
15
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/aitk",
3
3
  "type": "module",
4
- "version": "3.43.1",
4
+ "version": "3.44.0",
5
5
  "description": "Infrastructure and quality tooling for developer workflows",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -15,7 +15,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
15
15
  PROJECT_ROOT="${PROJECT_ROOT:-$(cd "$SCRIPT_DIR/../.." && pwd)}"
16
16
 
17
17
  TOOLING_DIR="$PROJECT_ROOT/tooling"
18
- CONTRACT="$PROJECT_ROOT/claude/skills/toolkit-cli/SKILL.md"
18
+ CONTRACT="$PROJECT_ROOT/claude/skills/aitk-cli/SKILL.md"
19
19
 
20
20
  BEGIN="<!-- generated:tooling-paths -->"
21
21
  END="<!-- /generated:tooling-paths -->"
@@ -298,7 +298,7 @@ main() {
298
298
 
299
299
  log_step "Tooling paths"
300
300
  run_check "bash $PROJECT_ROOT/scripts/core/regen-tooling-paths.sh" "Tooling-path regen failed"
301
- assert_no_drift "claude/skills/toolkit-cli/SKILL.md" "The overwrite contract drifted from what the stacks hold. Run bun run check and commit claude/skills/toolkit-cli/SKILL.md."
301
+ assert_no_drift "claude/skills/aitk-cli/SKILL.md" "The overwrite contract drifted from what the stacks hold. Run bun run check and commit claude/skills/aitk-cli/SKILL.md."
302
302
  log_info "Tooling paths clean"
303
303
 
304
304
  # The claude manifest is the only route a target's ignore set travels, and it
@@ -18,7 +18,7 @@ export const AUTHORING_CASES: readonly SkillCase[] = [
18
18
  {
19
19
  prompt:
20
20
  'Write me a headless automation script with no interactive prompts.',
21
- expect: 'cli-script',
21
+ expect: 'bash-cli-script',
22
22
  },
23
23
  {
24
24
  prompt: 'Scaffold a brand-new SKILL.md for this capability.',
@@ -39,7 +39,7 @@ export const AUTHORING_CASES: readonly SkillCase[] = [
39
39
  },
40
40
  {
41
41
  prompt: 'Say what that dense answer actually means in plain terms.',
42
- expect: 'restate',
42
+ expect: 'restate-plainly',
43
43
  },
44
44
  {
45
45
  prompt: 'This passage reads flat and robotic, give it some real cadence.',
@@ -81,7 +81,7 @@ export const CLAUDE_WORKFLOW_CASES: readonly SkillCase[] = [
81
81
  },
82
82
  {
83
83
  prompt: 'Draft me a script with beats for a screencast recording.',
84
- expect: 'claude-screencast',
84
+ expect: 'aitk-screencast',
85
85
  },
86
86
  {
87
87
  prompt:
@@ -90,7 +90,7 @@ export const CLAUDE_WORKFLOW_CASES: readonly SkillCase[] = [
90
90
  },
91
91
  {
92
92
  prompt: 'Turn this topic into a slide deck I can render.',
93
- expect: 'claude-slides-draft',
93
+ expect: 'aitk-slides-draft',
94
94
  },
95
95
  {
96
96
  prompt:
@@ -1,7 +1,7 @@
1
1
  import type { SkillCase } from '@/claude/skills-rank'
2
2
 
3
3
  /**
4
- * `setup-*`, `migration-*`, `toolkit-*`, and `create-rule`: scaffolding,
4
+ * `setup-*`, `migration-*`, `aitk-*`, and `create-rule`: scaffolding,
5
5
  * proposal-only migrations, and the toolkit's own reference and feedback
6
6
  * surfaces.
7
7
  */
@@ -53,20 +53,20 @@ export const SETUP_CASES: readonly SkillCase[] = [
53
53
  {
54
54
  prompt:
55
55
  "Before I run this sync, tell me exactly what it's going to overwrite.",
56
- expect: 'toolkit-cli',
56
+ expect: 'aitk-cli',
57
57
  },
58
58
  {
59
59
  prompt:
60
60
  'Something about the toolkit itself is broken, write it up and send it back to the maintainers.',
61
- expect: 'toolkit-feedback',
61
+ expect: 'aitk-feedback-file',
62
62
  },
63
63
  {
64
64
  prompt:
65
65
  "I don't know which specific toolkit skill I need, just handle it for me.",
66
- expect: 'toolkit-operator',
66
+ expect: 'aitk-operator',
67
67
  },
68
68
  // The performing skill wins a phrase naming its operation over the front
69
- // door's own routing framing, even though toolkit-operator's description
69
+ // door's own routing framing, even though aitk-operator's description
70
70
  // quotes a phrase near this one. See .claude/context/cli/audits.md.
71
71
  {
72
72
  prompt:
@@ -76,7 +76,7 @@ export const SETUP_CASES: readonly SkillCase[] = [
76
76
  {
77
77
  prompt:
78
78
  'Work through the open feedback issues on the toolkit repo one by one.',
79
- expect: 'toolkit-triage',
79
+ expect: 'aitk-feedback-triage',
80
80
  },
81
81
  {
82
82
  prompt:
package/src/cli.ts CHANGED
@@ -33,6 +33,7 @@ import { register as secrets } from '@/commands/secrets'
33
33
  import { register as deps } from '@/commands/deps'
34
34
  import { register as labels } from '@/commands/labels'
35
35
  import { register as census } from '@/commands/census'
36
+ import { register as targets } from '@/commands/targets'
36
37
  import { register as upgrade } from '@/commands/upgrade'
37
38
  import { readInstalled, UNKNOWN_LABEL } from '@/version/installed'
38
39
  import { palette } from '@/ui'
@@ -72,6 +73,7 @@ function showHelp(): void {
72
73
  `${GREY}│${NC} markdown [cmd] ${GREY}# Report markdown against the attribute standards (audit)${NC}`,
73
74
  `${GREY}│${NC} records [cmd] ${GREY}# Session records under .claude/ (validate, size, push, pull)${NC}`,
74
75
  `${GREY}│${NC} sessions [cmd] ${GREY}# Resolve live sessions to worktree and branch (list)${NC}`,
76
+ `${GREY}│${NC} targets [cmd] ${GREY}# Report the projects this toolkit installed into (list, pulls)${NC}`,
75
77
  `${GREY}│${NC} worktrees [cmd] ${GREY}# Report which worktrees are reclaimable (list)${NC}`,
76
78
  `${GREY}│${NC} secrets [cmd] ${GREY}# Read the shipped tree for credential-shaped values (scan)${NC}`,
77
79
  `${GREY}│${NC} deps [cmd] ${GREY}# Read the resolved dependency set for advisories (audit)${NC}`,
@@ -170,6 +172,7 @@ context(program)
170
172
  markdown(program)
171
173
  records(program)
172
174
  sessions(program)
175
+ targets(program)
173
176
  worktrees(program)
174
177
  secrets(program)
175
178
  deps(program)
@@ -42,7 +42,7 @@ export function register(program: Command): void {
42
42
  demo
43
43
  .command('compile')
44
44
  .description('Turn a screencast draft into a plan a run can drive')
45
- .argument('<draft>', 'Screencast draft written by claude-screencast')
45
+ .argument('<draft>', 'Screencast draft written by aitk-screencast')
46
46
  .helpOption('-h, --help', 'Show this help message')
47
47
  .option('-o, --out <dir>', 'Directory the plan is written to', DEFAULT_OUT)
48
48
  .option('-s, --slug <slug>', 'Plan name, defaulting to the draft filename')
@@ -1,3 +1,4 @@
1
+ import { resolve } from 'node:path'
1
2
  import type { Command } from 'commander'
2
3
  import { checkClaim, type ClaimReport } from '@/sessions/claim'
3
4
  import {
@@ -19,6 +20,7 @@ import {
19
20
  interface ListCommandOptions {
20
21
  readonly json?: boolean
21
22
  readonly branch?: string
23
+ readonly repository?: string
22
24
  }
23
25
 
24
26
  const REASONS: Record<string, string> = {
@@ -46,6 +48,10 @@ export function register(program: Command): void {
46
48
  '--branch <name>',
47
49
  'Report only the sessions holding this branch in this repository',
48
50
  )
51
+ .option(
52
+ '--repository <path>',
53
+ 'Answer about this project rather than the working one',
54
+ )
49
55
  .addHelpText(
50
56
  'after',
51
57
  [
@@ -54,11 +60,18 @@ export function register(program: Command): void {
54
60
  ' 0 the roster was read',
55
61
  ' 1 refused, with the reason on stderr',
56
62
  '',
57
- '--branch scopes the match to the repository the command runs in, since',
58
- 'a branch name identifies a branch there and nothing across a machine.',
63
+ '--branch scopes the match to one repository, since a branch name',
64
+ 'identifies a branch there and nothing across a machine. That is the',
65
+ 'repository the command runs in unless --repository names another.',
59
66
  'A bare run reports every repository and carries the repository field,',
60
67
  'so a caller filtering by hand has something that identifies one.',
61
68
  '',
69
+ '--repository moves every reading to the project at that path, the',
70
+ 'session match and the worktree and ref reads alike. The roster is',
71
+ 'machine-wide already, so this is what lets a dispatcher in one project',
72
+ 'see a branch a live session holds in another rather than reading it as',
73
+ 'unclaimed and sending a second session onto it.',
74
+ '',
62
75
  'With --branch, the JSON also carries "worktree" (the path of any',
63
76
  'worktree already checked out to it, or null), "refs" (the refs that',
64
77
  'already name it, local head and origin remote-tracking alike), and',
@@ -94,6 +107,7 @@ export function register(program: Command): void {
94
107
  ' aitk sessions list',
95
108
  ' aitk sessions list --json',
96
109
  ' aitk sessions list --branch feat/parser --json',
110
+ ' aitk sessions list --branch chore/agents --repository ../caret --json',
97
111
  '',
98
112
  ].join('\n'),
99
113
  )
@@ -125,13 +139,18 @@ async function runList(opts: ListCommandOptions): Promise<number> {
125
139
  // A branch name identifies a branch inside one repository and nothing across
126
140
  // a machine, so an unscoped match reaches a session working in a different
127
141
  // project. `main` is the name that collides on every machine running two.
128
- const repository = opts.branch ? await repositoryOf(process.cwd()) : null
142
+ //
143
+ // Which repository that is is the caller's to name. The roster this filters
144
+ // is machine-wide already, so a dispatcher asking about another project was
145
+ // answered "unclaimed" about a branch a live session there was holding.
146
+ const at = opts.repository ? resolve(opts.repository) : process.cwd()
147
+ const repository = opts.branch ? await repositoryOf(at) : null
129
148
 
130
149
  if (opts.branch && repository === null) {
131
150
  intro('aitk sessions list')
132
151
  logStep('Refused')
133
152
  logWarn(
134
- '--branch scopes the match to the repository this command runs in, and no repository resolved here. Run it inside one, or read the whole roster and filter on the repository field.',
153
+ `--branch scopes the match to one repository, and none resolved at ${at}. Run it inside one, name another with --repository, or read the whole roster and filter on the repository field.`,
135
154
  )
136
155
  outro()
137
156
 
@@ -152,10 +171,7 @@ async function runList(opts: ListCommandOptions): Promise<number> {
152
171
  : report.sessions
153
172
 
154
173
  const claim = opts.branch
155
- ? await checkClaim(opts.branch, {
156
- cwd: process.cwd(),
157
- resolve: async () => report,
158
- })
174
+ ? await checkClaim(opts.branch, { cwd: at, resolve: async () => report })
159
175
  : null
160
176
 
161
177
  intro('aitk sessions list')