@erclx/aitk 0.33.0 → 0.35.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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "aitk",
3
3
  "description": "Automated governance, versioning, and discovery tools for Claude Code.",
4
- "version": "0.33.0",
4
+ "version": "0.35.0",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -12,7 +12,8 @@ Read `${CLAUDE_SKILL_DIR}/references/folder-format.md` before writing any file i
12
12
  ## Guards
13
13
 
14
14
  - If no topic is given, stop: `❌ No topic. Name what needs measuring.`
15
- - Apply the qualifying test before creating anything. Two of these three must hold: the current state is not known, more than one approach is live, and committing wrong costs more than a day of measuring. When one or fewer holds, stop: `❌ Already decided enough to plan. Run /claude-feature instead.`
15
+ - Apply the qualifying test in open mode alone, after Step 1 resolves the mode and before the folder is created. Two of these three must hold: the current state is not known, more than one approach is live, and committing wrong costs more than a day of measuring. When one or fewer holds, stop: `❌ Already decided enough to plan. Run /claude-feature instead.`
16
+ - Resume and close are exempt from the test above. A track that has already been measured fails it by definition, since its current state is now known and its approaches have narrowed, so applying the test to either mode refuses the folder that same test admitted.
16
17
  - Do not pause for approval between steps. The write scope below is what makes that safe.
17
18
 
18
19
  ## Write scope
@@ -15,6 +15,7 @@ Without this skill, the memory folder grows and never drains. Entries pile up re
15
15
  - Verify the rule is not already stated or implied in the target before proposing a promotion, by reading the target rather than trusting the memory's claim about it
16
16
  - Rewrite a rule into the destination's voice instead of moving it unchanged
17
17
  - Write the proposal to a receipt on disk and take no action until the user decides per item
18
+ - Route every decision channel through the receipt, writing a decision given in chat into its item's slot before the parse reads the file, so a channel the skill advertises drains the pen rather than passing over it
18
19
  - Keep a promotion on its own commit, since a change to how the agent operates should not ride inside a feature a reviewer is vetting for something else
19
20
  - Confine the pass that runs after application to the one receipt it tested, so tidying up removes a file whose decisions are known to be resolved and leaves the pen and every untested receipt alone
20
21
 
@@ -181,7 +181,7 @@ Apply edits one at a time via `Edit`. Claude Code's tool permission dialog is th
181
181
 
182
182
  As each item resolves, update its status in the review file: flip the H2 emoji from 📝 to ✅ for applied, ⏭ for skipped, 🗑 for deleted, or 🤝 for handed off. Refresh the summary block counts at the top. Do not delete the review file. It stays as a receipt until Cleanup runs or the next Propose pass overwrites it.
183
183
 
184
- **Chat shortcut:** the user replies with `all`, `none`, or a comma-separated list of numbers. Apply only the numbered items.
184
+ **Chat shortcut:** the user replies with `all`, `none`, a comma-separated list of numbers, or `skip <nums>`. Write the matching verb into the `Decision:` slot of every item the reply names, `apply` for `all` or a bare list and `skip` for a `skip` reply, then run the parse above against the file. A reply of `none` writes nothing. A slot the reply does not name keeps its own value, so the receipt stays the source of truth and an empty slot still means take no action.
185
185
 
186
186
  End with: `✅ Applied: <nums> | ⏭ Skipped: <nums> | 📝 Pending: <nums>`. Omit empty buckets. If anything is pending, remind the user they can refine `Decision:` lines and re-ping, run "discuss" for question items, or commit a skip with `skip <nums>` in chat.
187
187
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: claude-orchestrate
3
- description: Asserts the orchestrator role for the current session, holds the build loop and the queue-refill sweep, and dispatches to the roadmap, feature, review, and worktree skills. Use when asked to "be the orchestrator", "run the orchestrator", "orchestrate this project", or to set up the control session for parallel feature builds. Do NOT build features or merge PRs in this session.
3
+ description: Asserts the orchestrator role for the current session, holds the build loop and the queue-refill sweep, and dispatches to the feature, review, and worktree skills. Use when asked to "be the orchestrator", "run the orchestrator", "orchestrate this project", or to set up the control session for parallel feature builds. Do NOT build features or merge PRs in this session.
4
4
  disable-model-invocation: true
5
5
  ---
6
6
 
@@ -70,7 +70,7 @@ That command returns nothing for a roadmap that exists but has never been commit
70
70
 
71
71
  ## The loop
72
72
 
73
- 1. Own the roadmap while a scope exists to sequence. Run `claude-roadmap` to draft or resequence `.claude/ROADMAP.md` from the MVP list in `.claude/REQUIREMENTS.md`, and skip it once that list has shipped, since later work then arrives as discrete items rather than as versions. Capture a needed resequence in the plan or a task file for a worker to apply in its branch, so the tracked edit ships in a PR rather than dirtying main.
73
+ 1. Own the roadmap while a scope exists to sequence. Capture a needed draft or resequence of `.claude/ROADMAP.md` in the plan or a task file, naming the MVP list in `.claude/REQUIREMENTS.md` as the source, so a worker runs `claude-roadmap` in its branch and the tracked edit ships in a PR rather than dirtying main. Stop owning it once that list has shipped, since later work then arrives as discrete items rather than as versions.
74
74
  2. Plan the next feature. Run `claude-feature` here, with the cross-feature context, to write a plan to `.claude/plans/`. Planning stays in this warm session so the plan front-loads reasoning a cold worker would otherwise re-derive.
75
75
  3. Decide parallelism and merge order. Note which plans touch a shared wiring seam so their PRs merge in sequence, not at once.
76
76
  4. Verify the plan against the tree. Reading it is not enough, since a plan goes stale from whatever merged after it was written. Grep for each construct it names and count the sites against the count it claims. Check that every phase label it cites is still open. Open each file it describes rather than trusting its account of the contents. Correct the plan before handing it over.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: claude-pr-review
3
- description: What the independent pull request review is for, the gaps it closes, and why it posts twice
3
+ description: What the independent pull request review is for, the gaps it closes, and why it posts until nothing is open
4
4
  ---
5
5
 
6
6
  # Claude PR review requirement
@@ -11,11 +11,12 @@ Without this skill, a pull request is reviewed only by the session that wrote it
11
11
 
12
12
  ## Must
13
13
 
14
- - Post both passes. A first pass opens the review against the whole change, and a close-out confirms the prior findings landed.
14
+ - Post until the review closes. A first pass opens against the whole change, and each later pass checks whether the prior findings landed.
15
+ - Set the heading from the finding count rather than the pass number, so the most recent review comment's heading reports whether anything is open
15
16
  - Detect the pass from the thread rather than taking it from the caller, matching the heading for equality so a neighboring comment cannot be read as a prior pass
16
- - Scope a close-out to the commits added since the prior pass, once that commit is confirmed to still reach the head
17
+ - Scope a later pass to the commits added since the prior one, once that commit is confirmed to still reach the head
17
18
  - Apply the integration, contract, and consumer lenses a self-review structurally cannot
18
- - Post a close-out even with nothing to report, since a first pass left unanswered reads as a review nobody closed
19
+ - Post the closing pass even with nothing to report, since a review left unanswered reads as one nobody closed
19
20
  - Key the body file on both the pull request number and the head commit, so no two passes overwrite each other
20
21
  - Scan the comment for banned characters and internal phase labels before posting, since a finding phrased against a phase label reaches a reader with no task board
21
22
 
@@ -23,7 +24,7 @@ Without this skill, a pull request is reviewed only by the session that wrote it
23
24
 
24
25
  - Merge. Review and post, and leave the gate to the human.
25
26
  - Publish a claim the skill did not check. A failed fetch and a rebase both strand the prior commit, and only one of them is a rebase.
26
- - Invent a third heading, or append a number GitHub already renders
27
+ - Invent a heading beyond the two it posts and the response heading `claude-address-review` owns, or append a number GitHub already renders
27
28
  - Review local uncommitted changes
28
29
  - Lecture on process. The lenses land as findings, not as asides.
29
30
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: claude-pr-review
3
- description: Reviews an open pull request from an independent session and posts findings as a review comment on the PR. Posts a first pass under `## Review`, and a close-out under `## Review closed` that reads only the commits added since. Reads project docs and the roadmap for cross-feature context a self-review lacks. Use when asked to "review the PR", "review this feature's PR", "post a PR review", "re-review the PR", "close out the review", "confirm the findings are fixed", or acting as the orchestrator reviewing a worker's PR. Do NOT use to review local uncommitted changes. That is `claude-review`.
3
+ description: Reviews an open pull request from an independent session and posts findings as a review comment on the PR. Posts a first pass against the whole change and every later pass against only the commits added since, under `## Review` while anything is open and `## Review closed` once nothing is. Reads project docs and the roadmap for cross-feature context a self-review lacks. Use when asked to "review the PR", "review this feature's PR", "post a PR review", "re-review the PR", "close out the review", "confirm the findings are fixed", or acting as the orchestrator reviewing a worker's PR. Do NOT use to review local uncommitted changes. That is `claude-review`.
4
4
  ---
5
5
 
6
6
  # Claude PR review
@@ -10,9 +10,12 @@ This is the orchestrator's independent review, distinct from `claude-review`.
10
10
  to disk. This one reviews an open PR the session did not write and posts the
11
11
  findings to the PR, so the vantage is independent and the output is durable.
12
12
 
13
- It posts twice over a pull request's life. A first pass opens the review against
14
- the whole change. A close-out confirms the findings are closed and reads only
15
- the commits added since. Both are this skill, and the pass is detected from the
13
+ It posts at least twice over a pull request's life. A first pass opens the
14
+ review against the whole change, and every later pass reads only the commits
15
+ added since. The heading reports state rather than pass number: a pass carrying
16
+ findings takes `## Review`, and `## Review closed` is reserved for a pass
17
+ carrying none, so the most recent comment's heading reports where the review
18
+ stands. Every pass is this skill, and which one it is gets detected from the
16
19
  thread rather than named by the caller.
17
20
 
18
21
  ## Guards
@@ -42,7 +45,7 @@ Find the commit the last pass covered:
42
45
  gh pr view <number> --json reviews --jq '[.reviews[] | select(.body // "" | split("\n")[0] | rtrimstr("\r") | . == "## Review" or . == "## Review closed")] | last | .commit.oid'
43
46
  ```
44
47
 
45
- Match the first line for equality against the two headings this skill posts. A prefix test also matches `## Review response` and any heading merely starting with those words, which would scope the close-out to whatever commit that comment carried. The `\r` trim covers a body composed in the GitHub web editor, which stores CRLF.
48
+ Match the first line for equality against the two headings this skill posts. A prefix test also matches `## Review response` and any heading merely starting with those words, which would scope the pass to whatever commit that comment carried. The `\r` trim covers a body composed in the GitHub web editor, which stores CRLF.
46
49
 
47
50
  An empty result is a first pass. Read the whole change:
48
51
 
@@ -54,7 +57,7 @@ gh pr diff <number>
54
57
  gh pr diff <number> --name-only
55
58
  ```
56
59
 
57
- A commit is a close-out. Fetch the pull request head so both commits are local:
60
+ A commit is a later pass. Fetch the pull request head so both commits are local:
58
61
 
59
62
  ```bash
60
63
  git fetch -q origin pull/<number>/head
@@ -82,7 +85,7 @@ Review the diff and files for the same axes as `claude-review` (bugs, edge cases
82
85
 
83
86
  Apply the high-signal filter: flag only what will cause incorrect behavior, break a documented rule, or mislead a downstream feature. If uncertain, do not flag.
84
87
 
85
- A close-out applies the same axes to the delta, and adds one check the first pass cannot make: did each prior finding land, and did the fix regress anything it touched. Findings of its own are normal findings, stated at the same severity and counted the same way.
88
+ A later pass applies the same axes to the delta, and adds one check the first pass cannot make: did each prior finding land, and did the fix regress anything it touched. Findings of its own are normal findings, stated at the same severity and counted the same way. That count is what Step 4 reads to pick the heading, so a pass raising one of its own is not a close-out.
86
89
 
87
90
  Use severity: `critical` (blocks merge), `should-fix` (fix before merge), `minor` (visibility only).
88
91
 
@@ -109,10 +112,10 @@ X critical, Y should-fix, Z minor. Reviewed against project docs and roadmap.
109
112
  🤖 Reviewed by Claude Code
110
113
  ```
111
114
 
112
- A close-out keeps that shape and changes the heading and the summary line:
115
+ A later pass carrying findings keeps that shape and changes only the summary line:
113
116
 
114
117
  ```markdown
115
- ## Review closed
118
+ ## Review
116
119
 
117
120
  Re-reviewed `<short-sha>`, N commits since the prior pass. X critical, Y should-fix, Z minor.
118
121
 
@@ -123,9 +126,13 @@ Re-reviewed `<short-sha>`, N commits since the prior pass. X critical, Y should-
123
126
  🤖 Reviewed by Claude Code
124
127
  ```
125
128
 
126
- Open a first pass with `## Review` and a close-out with `## Review closed`, so a reader scanning the thread can tell an open review from a confirmation that its findings are closed without opening either. Both anchor as a section distinct from human threads. Do not invent a third heading, and do not append the PR number, which GitHub already renders above the comment.
129
+ The heading reports whether anything is open and the summary line reports which pass this is. Post under `## Review` whenever the pass carries a finding at any severity, whether it is the first pass or the fourth. Reserve `## Review closed` for a pass carrying none, which makes it the marker worth scanning for rather than a label on a kind of pass. A pull request thread then reads as `## Review`, the worker's answer under `## Review response` from `claude-address-review`, another `## Review` while anything is still open, and `## Review closed` when nothing is.
130
+
131
+ Read the state off the most recent review comment rather than off the presence of a closed one. A close-out does not close the pull request, so a commit pushed after it gets its own pass, and that pass reopens the review under `## Review` when it raises anything.
127
132
 
128
- Name the scope in the close-out summary line, since a reader cannot otherwise tell a narrow read from a full one. When the fallback in Step 2 fired, replace the commit count with `Re-reviewed the full change, the prior pass's commit is no longer on the branch`. Budget the body. State each finding as the failure and the fix in two or three sentences, not a paragraph of reasoning. Omit files with no findings. Do not lecture on process. The integration, contract, and consumer lenses stay, but as findings, not asides.
133
+ Both of this skill's headings anchor as a section distinct from human threads. Do not invent one beyond those two and the `## Review response` a sibling owns, and do not append the PR number, which GitHub already renders above the comment.
134
+
135
+ Name the scope in every summary line after the first pass, since a reader cannot otherwise tell a narrow read from a full one. When the fallback in Step 2 fired, replace the commit count with `Re-reviewed the full change, the prior pass's commit is no longer on the branch`. Budget the body. State each finding as the failure and the fix in two or three sentences, not a paragraph of reasoning. Omit files with no findings. Do not lecture on process. The integration, contract, and consumer lenses stay, but as findings, not asides.
129
136
 
130
137
  The `What is right` section is optional, capped at three bullets, and included only when it changes the merge decision. Drop it otherwise and let the summary line carry the approval.
131
138
 
@@ -137,9 +144,7 @@ Before posting, run the scan in `.claude/standards/publish.md` against the body,
137
144
  gh pr review <number> --comment --body-file .claude/.tmp/pr-review/body-<number>-<short-sha>.md
138
145
  ```
139
146
 
140
- If a first pass has no findings, post this body instead, under the same `## Review` heading and with the footer included: `✅ No blocking findings. Reviewed against project docs and roadmap.` followed by the footer line.
141
-
142
- If a close-out has no findings, post `✅ Prior findings addressed. Re-reviewed <short-sha>, N commits since the prior pass.` under `## Review closed`, again with the footer. Post it even when there is nothing to report. A first pass left with no closing comment reads as a review nobody answered.
147
+ A pass with no findings takes `## Review closed` and a short body, with the footer line included either way. On a first pass, post `✅ No blocking findings. Reviewed against project docs and roadmap.` On a later pass, post `✅ Prior findings addressed. Re-reviewed <short-sha>, N commits since the prior pass.` Post that one even when there is nothing to report. A review left with no closing comment reads as one nobody answered.
143
148
 
144
149
  ## Step 5: output
145
150
 
@@ -121,7 +121,7 @@ Re-read the review file as source of truth. For each item, parse `Decision:`:
121
121
  - `defer` or empty: leave 📝 pending, no action.
122
122
  - Contains `?` or unrecognized verb: leave 📝 pending, no action.
123
123
 
124
- Chat shortcut: the user replies with `all`, `none`, or a comma-separated list of numbers. Apply only the numbered items.
124
+ Chat shortcut: the user replies with `all`, `none`, a comma-separated list of numbers, or `skip <nums>`. Write the matching verb into the `Decision:` slot of every item the reply names, `apply` for `all` or a bare list and `skip` for a `skip` reply, then run the parse above against the file. A reply of `none` writes nothing. A slot the reply does not name keeps its own value, so the receipt stays the source of truth and an empty slot still means take no action.
125
125
 
126
126
  Apply edits one at a time via `Edit`, replacing one section at a time. Never rewrite a whole file. Claude Code's tool permission dialog is the confirmation gate per edit.
127
127
 
package/docs/agents.md CHANGED
@@ -80,7 +80,7 @@ Full help: `aitk <command> --help`.
80
80
  | `aitk transcripts <url>` | Fetch a YouTube transcript with metadata frontmatter (needs `yt-dlp`) |
81
81
  | `aitk tasks archive` | Move a shipped task off the board, clear its ordering row, and regenerate the index |
82
82
  | `aitk comments scan` | Measure comment density by language and comment kind, with a trend recomputed from git |
83
- | `aitk context audit` | Report entry length, depth, cited-path resolution, provenance, and index drift |
83
+ | `aitk context audit` | Report entry length, depth, bullet weight, cited-path resolution, provenance, and index drift |
84
84
  | `aitk capture [source]` | Render HTML capture sources to PNG, toolkit-only and absent from an installed package |
85
85
 
86
86
  ### Domain commands
@@ -387,7 +387,7 @@ aitk context audit --folder context,diagrams
387
387
 
388
388
  Scope defaults to `context`, `diagrams`, and `wireframes`, and a folder the project does not carry is skipped rather than reported. A domain that outgrew one file and split into `<domain>/` is audited as its own folder, so a split entry measures at the same grain as a flat one.
389
389
 
390
- Exit codes are `0` for a clean run, `1` for a refusal, and `2` for an unresolved citation. Only the citation check sets a failing code. Length, depth, table, provenance, and index findings print and return `0`, because each is a judgment and failing a push on one would make the check something to route around.
390
+ Exit codes are `0` for a clean run, `1` for a refusal, and `2` for an unresolved citation. Only the citation check sets a failing code. Length, depth, bullet weight, table, provenance, and index findings print and return `0`, because each is a judgment and failing a push on one would make the check something to route around.
391
391
 
392
392
  ### What each check reports
393
393
 
@@ -395,11 +395,13 @@ Length and depth quote their checkpoints from `.claude/standards/context.md`: ro
395
395
 
396
396
  Both checks count rendered lines rather than source lines, wrapping each line at 80 columns and summing the heights. Entries here are authored one line per bullet, so a block of fifteen paragraph-bullets occupies fifteen source lines and renders past sixty, which source counting cannot see. Measuring one checkpoint in each unit would put an entry length beside a run length that mean different things. Their exclusions still differ: the file measure counts fenced blocks and frontmatter, while the run measure skips a fence so an example cannot break the run around it. A reference-heavy entry therefore ranks by its examples, which the length legend states on every run. Runs count blank lines, which the standard leaves open, so a hand reader who drops them lands a line or two lower. Both sections state the width on every run, since a number in rendered lines cannot be reproduced without it.
397
397
 
398
+ The bullet check reports a top-level bullet past roughly 400 characters, which is where a bullet stops carrying a decision alone and starts carrying the incident that motivated it beside the decision. Continuation lines fold into the bullet they belong to, so a heavy bullet cannot fall under the checkpoint by wrapping across two source lines, while a nested item is left out because the parent's own text is what the checkpoint asks about. Findings group by entry and narrow to `.claude/context/` for the reasons the provenance ones do both. Unlike the peer-list threshold above it, this corpus has no gap behind the number: bullet weight decays smoothly from a median near 170, so the number is a judgment where that one was a measurement, and a bullet reading well past it means the number is wrong rather than the rule.
399
+
398
400
  The table check reports a catalog that grows a row per shipped thing, not a table count. A fixed comparison table never reflows, so its size costs nothing. A table qualifies at six or more body rows whose first column mostly carries a path, command, or link, which is what separates a catalog from a comparison without reading the prose.
399
401
 
400
402
  The provenance check reports the markers narrating how a domain reached its shape rather than describing what it is: a date, a change number, or a release label. The standard admits a rejected alternative and the reasoning that killed it while refusing the provenance attached to it, so a marker names a line to read rather than a line to delete. Findings group by entry and sort left to right within a line, since what a reader acts on is which file to open. Fenced blocks are excluded, which keeps a pinned version in an install command from reading as a claim the entry makes. Frontmatter is excluded with them, since the content checks read the body alone, and that is what keeps a diagram entry's dated `verified` stamp a record of its last check rather than a marker to settle. Length is the exception, counting the whole file, so a reader applying the 150-rendered-line checkpoint against the body alone lands a few lines under what the tool reports.
401
403
 
402
- This one check covers `.claude/context/` alone, while length, depth, and the table finding reach every audited folder. The rule is stated in `.claude/standards/context.md`, which opens its scope by handing diagrams and wireframes to `diagrams.md` and `wireframes.md`, and the sibling standards do not restate it. A marker reported in a diagram entry would cite a rule that entry's own standard routes elsewhere. The split is between kinds of rule rather than kinds of folder: a threshold on how far a reader travels generalizes across entry types, while a rule about what an entry may say is the jurisdiction a scope statement exists to settle. The scoping key is the folder an entry was audited under, so `--folder` still reaches a folder the default list does not carry, and a domain split into `context/<sub-area>/` is governed as `context`. Every run states the reach, including a run where no audited folder is the governed one. The JSON record carries it as `checkpoints.provenanceFolder` and a per-folder `governsContent`.
404
+ The provenance and bullet-weight checks cover `.claude/context/` alone, while length, depth, and the table finding reach every audited folder. The rule is stated in `.claude/standards/context.md`, which opens its scope by handing diagrams and wireframes to `diagrams.md` and `wireframes.md`, and the sibling standards do not restate it. A marker reported in a diagram entry would cite a rule that entry's own standard routes elsewhere. The split is between kinds of rule rather than kinds of folder, and what decides it is whether the remedy is actionable rather than what the check measures. Subdividing a run and splitting a file mean something in any entry, so length and depth generalize. Moving an incident out of a bullet and keeping the decision means nothing in a folder whose entries declare no decisions, which is why bullet weight narrows despite measuring a distance like the two that do not. The scoping key is the folder an entry was audited under, so `--folder` still reaches a folder the default list does not carry, and a domain split into `context/<sub-area>/` is governed as `context`. Every run states the reach, including a run where no audited folder is the governed one. The JSON record carries it as `checkpoints.provenanceFolder` and a per-folder `governsContent`.
403
405
 
404
406
  Index drift compares an index against its siblings in both directions. An entry the index does not link is invisible to a session choosing what to open, and a linked name resolving to nothing sends one to a path that opens nothing.
405
407
 
@@ -68,7 +68,7 @@ When features are independent, run them in parallel instead of sequentially. Use
68
68
  - Ship each worktree separately with `aitk:git-ship`
69
69
  - For full autonomy per worktree, invoke `aitk:claude-autoship` instead of the manual chain. Approve the plan, walk away, come back to draft PRs.
70
70
 
71
- To run several worktrees as a coordinated flow rather than ad hoc, assert the orchestrator role in one warm session with `aitk:claude-orchestrate`. It owns the roadmap via `aitk:claude-roadmap`, plans each feature, refills the ready queue so a free worker never waits, and reviews each worker's PR with `aitk:claude-pr-review`, while workers address the posted findings with `aitk:claude-address-review`. The human launches workers and merges. See [operating model](operating-model.md) for the full loop.
71
+ To run several worktrees as a coordinated flow rather than ad hoc, assert the orchestrator role in one warm session with `aitk:claude-orchestrate`. It owns the roadmap, handing a needed draft or resequence to a worker that runs `aitk:claude-roadmap` in its branch, plans each feature, refills the ready queue so a free worker never waits, and reviews each worker's PR with `aitk:claude-pr-review`, while workers address the posted findings with `aitk:claude-address-review`. The human launches workers and merges. See [operating model](operating-model.md) for the full loop.
72
72
 
73
73
  Roadmap ownership holds while a scope exists to sequence. Once the MVP list in `.claude/REQUIREMENTS.md` has shipped, later work arrives as discrete items and the orchestrator reads `.claude/tasks/priority.md` for execution order instead.
74
74
 
@@ -131,11 +131,11 @@ Before the first feature session on a UI-heavy project, pick a design tier. The
131
131
  | `aitk:claude-groundwork` | Before a plan is warranted, measure an unknown in a scratch folder under `.claude/.tmp/groundwork/` |
132
132
  | `aitk:claude-feature` | Before implementation, scan for conflicts and ambiguities |
133
133
  | `aitk:claude-roadmap` | Sequence MVP scope into ordered versions in `.claude/ROADMAP.md` |
134
- | `aitk:claude-orchestrate` | Assert the orchestrator role, refill the ready queue, and dispatch the roadmap, feature, and review skills |
134
+ | `aitk:claude-orchestrate` | Assert the orchestrator role, refill the ready queue, and dispatch the feature, review, and worktree skills |
135
135
  | `aitk:claude-diagram` | Draft per-kind mermaid entries under `.claude/diagrams/` from architecture and code, then verify each rendered image |
136
136
  | `aitk:claude-design-extract` | Draft `.claude/DESIGN.md`, sourcing tokens from existing UI code or proposing them from requirements on day one |
137
137
  | `aitk:claude-review` | In a fresh session, review all changes since main |
138
- | `aitk:claude-pr-review` | Review an open PR from an independent session, then close the review out against the commits added since |
138
+ | `aitk:claude-pr-review` | Review an open PR from an independent session, then re-review the commits added since until nothing is open |
139
139
  | `aitk:claude-address-review` | Address PR findings and CI status, refresh stale docs, then push a follow-up |
140
140
  | `aitk:claude-tasks` | Add a task to `.claude/tasks/` or archive a shipped one out of the folder |
141
141
  | `aitk:claude-docs` | When decisions diverged from plan, update `.claude/` docs |
@@ -29,12 +29,12 @@ dispatch, not logic.
29
29
 
30
30
  One feature travels this path end to end.
31
31
 
32
- 1. Orchestrator drafts or resequences the roadmap with `claude-roadmap`, reading scope from `.claude/REQUIREMENTS.md`. The skill stops when that file carries a later scope section, since the MVP list it sequences has shipped and a fresh requirements pass owns what follows.
32
+ 1. Orchestrator captures a needed roadmap draft or resequence in the plan or a task file, naming `.claude/REQUIREMENTS.md` as the scope source, and a worker runs `claude-roadmap` in its branch so the tracked edit ships in a PR. The skill stops when that file carries a later scope section, since the MVP list it sequences has shipped and a fresh requirements pass owns what follows.
33
33
  2. Orchestrator plans the next feature with `claude-feature`, writing a plan to `.claude/plans/`. Planning stays in the warm session because good planning is cross-feature. It needs the contract other features consume and the shared wiring seam. A cold session would re-derive or guess.
34
34
  3. The human opens a worker worktree with `claude-worktree` and runs `claude-autoship` against the plan. The worker builds, self-checks, opens a PR, and stops at the PR boundary.
35
35
  4. Orchestrator reviews the PR with `claude-pr-review` and posts findings to it.
36
36
  5. Worker addresses the findings with `claude-address-review`, then pushes a follow-up.
37
- 6. Orchestrator closes the review out with `claude-pr-review` again. The second pass reads only the commits the follow-up added and posts under `## Review closed`, so the thread shows one review ending rather than a second one opening.
37
+ 6. Orchestrator closes the review out with `claude-pr-review` again. The second pass reads only the commits the follow-up added, and posts under `## Review closed` when it finds nothing open or under `## Review` when it does, so a reader learns the state from the heading. Repeat from step 5 until the review closes.
38
38
  7. The human reads the result and merges. The orchestrator tells any trailing worker to rebase when its branch shares a seam with the merged one.
39
39
 
40
40
  There is no loop construct here. Each worker is a single build that halts at the
@@ -58,11 +58,12 @@ merge is the final gate. No layer repeats another.
58
58
 
59
59
  Review travels on the PR, not through chat. `claude-pr-review` posts findings to
60
60
  the PR. `claude-address-review` reads them back, fixes each, replies or resolves
61
- the threads, and pushes a follow-up. `claude-pr-review` then runs a second time
62
- to close the review out, reading only what the follow-up added.
61
+ the threads, and pushes a follow-up. `claude-pr-review` then runs again, reading
62
+ only what the follow-up added.
63
63
 
64
- The two passes carry different headings, `## Review` and `## Review closed`, so
65
- a thread can be scanned for state without opening either comment. The feedback
64
+ The heading carries the state rather than the pass number. A pass with a finding
65
+ takes `## Review` and a pass with none takes `## Review closed`, so a thread can
66
+ be scanned for what is still open without opening a comment. The feedback
66
67
  becomes a durable artifact both sessions read, survives a session ending, and
67
68
  anchors to the change. That removes the copy-paste that otherwise routes review
68
69
  through the human between two sessions.
@@ -10,6 +10,7 @@ paths:
10
10
 
11
11
  - Before non-trivial work in a domain, read its `.claude/context/<domain>.md` entry. Use `.claude/context/index.md` to find it.
12
12
  - Leave the entry conforming when work in its domain changes what it describes.
13
+ - Rewrite the decision a change supersedes rather than appending a second one narrating the transition.
13
14
 
14
15
  ## Authority
15
16
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/aitk",
3
3
  "type": "module",
4
- "version": "0.33.0",
4
+ "version": "0.35.0",
5
5
  "description": "Infrastructure and quality tooling for developer workflows",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -1,6 +1,7 @@
1
1
  import { resolve } from 'node:path'
2
2
  import type { Command } from 'commander'
3
3
  import {
4
+ BULLET_CHECKPOINT,
4
5
  type EntryReport,
5
6
  governsContent,
6
7
  LENGTH_CHECKPOINT,
@@ -50,7 +51,7 @@ export function register(program: Command): void {
50
51
  context
51
52
  .command('audit')
52
53
  .description(
53
- 'Report entry length, depth, citations, provenance, and index drift',
54
+ 'Report entry length, depth, bullet weight, citations, provenance, and index drift',
54
55
  )
55
56
  .argument('[path]', 'Project root, defaulting to the current directory')
56
57
  .helpOption('-h, --help', 'Show this help message')
@@ -67,7 +68,7 @@ export function register(program: Command): void {
67
68
  ' 2 a cited path did not resolve',
68
69
  '',
69
70
  'Only unresolved citations set a failing exit code. Length, depth,',
70
- 'table, provenance, and index findings are advisory.',
71
+ 'bullet, table, provenance, and index findings are advisory.',
71
72
  '',
72
73
  'Examples:',
73
74
  ' aitk context audit',
@@ -139,6 +140,7 @@ async function runAudit(
139
140
  reportCitations(citations)
140
141
  reportLength(entries)
141
142
  reportDepth(entries)
143
+ reportBullets(entries, folders)
142
144
  reportTables(entries)
143
145
  reportProvenance(entries, folders)
144
146
  reportDrift(drift)
@@ -167,6 +169,7 @@ async function runAudit(
167
169
  runCountsBlankLines: true,
168
170
  renderWidth: RENDER_WIDTH,
169
171
  peerBullet: PEER_BULLET_CHECKPOINT,
172
+ bullet: BULLET_CHECKPOINT,
170
173
  provenanceFolder: PROVENANCE_FOLDER,
171
174
  },
172
175
  })}\n`,
@@ -311,6 +314,66 @@ function reportDepth(entries: readonly EntryReport[]): void {
311
314
  )
312
315
  }
313
316
 
317
+ /**
318
+ * Groups by entry, and states its reach on every run, for the reasons the
319
+ * provenance report does both.
320
+ *
321
+ * Bullets past the checkpoint cluster in a handful of entries and the heaviest
322
+ * entries carry them a dozen at a time, so a flat list of bullets buries the
323
+ * entry holding one. What a reader acts on is which file to open.
324
+ */
325
+ function reportBullets(
326
+ entries: readonly EntryReport[],
327
+ folders: readonly AuditedFolder[],
328
+ ): void {
329
+ logStep('Bullets')
330
+
331
+ const governed = folders.filter(governsContent)
332
+ if (governed.length === 0) {
333
+ logInfo(
334
+ `Out of scope. The rule is stated in the standard governing .claude/${PROVENANCE_FOLDER}/, and no audited folder is that one.`,
335
+ )
336
+ return
337
+ }
338
+
339
+ logInfo(
340
+ `Covers .claude/${PROVENANCE_FOLDER}/ alone, since moving an incident out of a bullet needs a decision to keep.`,
341
+ )
342
+ logInfo(
343
+ 'Top-level bullets measure characters, folding in continuation lines.',
344
+ )
345
+ logInfo(
346
+ 'Nested items and fenced blocks are excluded. Weight is a judgment, never a defect.',
347
+ )
348
+
349
+ const carrying = entries
350
+ .filter((entry) => entry.heavyBullets.length > 0)
351
+ .sort((a, b) => b.heavyBullets.length - a.heavyBullets.length)
352
+
353
+ if (carrying.length === 0) {
354
+ logInfo(`No bullet past the ${BULLET_CHECKPOINT}-character checkpoint.`)
355
+ return
356
+ }
357
+
358
+ const total = carrying.reduce(
359
+ (sum, entry) => sum + entry.heavyBullets.length,
360
+ 0,
361
+ )
362
+ logWarn(
363
+ `${plural(total, 'bullet')} past the ${BULLET_CHECKPOINT}-character checkpoint across ${carrying.length} ${carrying.length === 1 ? 'entry' : 'entries'}`,
364
+ )
365
+ pipeOutput(
366
+ carrying
367
+ .map(
368
+ (entry) =>
369
+ `${entry.rel} ${plural(entry.heavyBullets.length, 'bullet')}\n${entry.heavyBullets
370
+ .map((found) => ` :${found.line} ${found.characters} characters`)
371
+ .join('\n')}`,
372
+ )
373
+ .join('\n'),
374
+ )
375
+ }
376
+
314
377
  function reportTables(entries: readonly EntryReport[]): void {
315
378
  logStep('Tables')
316
379
 
@@ -27,6 +27,18 @@ export const RENDER_WIDTH = 80
27
27
  */
28
28
  export const PEER_BULLET_CHECKPOINT = 130
29
29
 
30
+ /**
31
+ * Characters a bullet carries before it holds more than the decision itself.
32
+ *
33
+ * Unlike the peer-list checkpoint above, this corpus has no gap behind the
34
+ * number. Bullet weight decays smoothly from a median near 170 with the
35
+ * steepest relative fall across this boundary and nothing resembling two
36
+ * populations, so the number is a judgment where that one was a measurement.
37
+ * A bullet that reads well past it means the number is wrong rather than the
38
+ * rule, which is why this reports and never gates.
39
+ */
40
+ export const BULLET_CHECKPOINT = 400
41
+
30
42
  /**
31
43
  * A table this size or larger whose first column mostly names artifacts reads
32
44
  * as a catalog that grows a row per shipped thing, which is the shape the
@@ -71,6 +83,14 @@ const PROVENANCE: readonly { kind: ProvenanceKind; pattern: RegExp }[] = [
71
83
  * audited folder, because a threshold on how far a reader travels generalizes
72
84
  * across entry types while a rule about what an entry may say does not.
73
85
  *
86
+ * Bullet weight sits on this side of the split with provenance, which the
87
+ * measure alone would not predict. What decides it is whether the remedy is
88
+ * actionable: subdividing a run and splitting a file mean something in any
89
+ * entry, while moving an incident out of a bullet and keeping the decision
90
+ * means nothing in a folder whose entries carry no decisions to keep. No
91
+ * diagram entry declares `## Decisions` or `## Gotchas`, so the scope of the
92
+ * rule is what narrows the finding rather than the shape of the number.
93
+ *
74
94
  * Restating the exclusion in the sibling standards was the alternative. It
75
95
  * duplicates one knowledge item across three surfaces, which the root
76
96
  * instruction file forbids, and pointing is not available because the surface
@@ -87,6 +107,12 @@ export interface TableFinding {
87
107
  readonly rows: number
88
108
  }
89
109
 
110
+ export interface BulletFinding {
111
+ readonly line: number
112
+ /** Weight as folded, so a report says how far past the checkpoint it sits. */
113
+ readonly characters: number
114
+ }
115
+
90
116
  export interface ProvenanceFinding {
91
117
  readonly line: number
92
118
  readonly kind: ProvenanceKind
@@ -116,6 +142,8 @@ export interface EntryReport {
116
142
  readonly catalogTables: readonly TableFinding[]
117
143
  /** Empty for an entry no standard bans a change narrative in. */
118
144
  readonly provenance: readonly ProvenanceFinding[]
145
+ /** Empty for the same reason `provenance` is, and under the same folder. */
146
+ readonly heavyBullets: readonly BulletFinding[]
119
147
  }
120
148
 
121
149
  interface BodyLine {
@@ -302,6 +330,60 @@ function catalogTables(lines: readonly BodyLine[]): TableFinding[] {
302
330
  return findings
303
331
  }
304
332
 
333
+ /**
334
+ * Finds the top-level bullets carrying more than a decision.
335
+ *
336
+ * A nested item is left out rather than folded into its parent, since the
337
+ * checkpoint asks what one bullet carries and a child carries its own. Lines
338
+ * continuing a bullet do fold in, so a heavy bullet cannot fall under the
339
+ * checkpoint by being wrapped across two source lines. Fenced blocks are
340
+ * skipped for the reason the scans above skip them: a sample an entry displays
341
+ * is not a claim it makes.
342
+ */
343
+ function heavyBullets(lines: readonly BodyLine[]): BulletFinding[] {
344
+ const findings: BulletFinding[] = []
345
+ let open: BulletFinding | null = null
346
+ let fenced = false
347
+
348
+ const close = (): void => {
349
+ if (open && open.characters > BULLET_CHECKPOINT) findings.push(open)
350
+ open = null
351
+ }
352
+
353
+ for (const line of lines) {
354
+ if (FENCE.test(line.text)) {
355
+ fenced = !fenced
356
+ close()
357
+ continue
358
+ }
359
+ if (fenced) continue
360
+
361
+ const item = line.text.match(LIST_ITEM)
362
+ const text = line.text.trim()
363
+
364
+ if (item) {
365
+ close()
366
+ if (item[1].length === 0) {
367
+ open = { line: line.number, characters: text.length }
368
+ }
369
+ continue
370
+ }
371
+
372
+ if (text === '' || HEADING.test(line.text) || TABLE_ROW.test(line.text)) {
373
+ close()
374
+ continue
375
+ }
376
+
377
+ // The joining space a wrapped line would have carried, so folding two
378
+ // source lines measures what one unwrapped line would have.
379
+ if (open) open = { ...open, characters: open.characters + text.length + 1 }
380
+ }
381
+
382
+ close()
383
+
384
+ return findings
385
+ }
386
+
305
387
  /**
306
388
  * Finds the markers narrating a change rather than describing the domain.
307
389
  *
@@ -365,6 +447,7 @@ export function measureEntry(
365
447
  longestRunLine: run.line,
366
448
  catalogTables: catalogTables(lines),
367
449
  provenance: governsContent ? provenance(lines) : [],
450
+ heavyBullets: governsContent ? heavyBullets(lines) : [],
368
451
  }
369
452
  }
370
453
 
@@ -104,7 +104,11 @@ Only the `development` entry carries this section. It is not a general-purpose h
104
104
  - Past roughly 40 rendered lines with no heading of any level breaking them, add a subheading at the seam. Measure the longest such run rather than everything under one `##`, and exclude fenced code blocks. The number is a checkpoint like the 150 above, not a cap.
105
105
  - Both checkpoints count rendered lines, so wrap each source line at 80 columns and sum the heights. Source lines undercount an entry authored one line per bullet, where a block of fifteen paragraph-bullets occupies fifteen lines and renders past sixty. Counting the two checkpoints in different units would put a file measured one way beside a run measured another.
106
106
  - Exempt a block whose lines are all list items at one level averaging under roughly 130 characters. A flat list of short peers is already navigable, and a subheading dropped into it splits a set that belongs together. Bullet count says nothing on its own, since a catalog of one-liners and a stack of paragraphs reach the same count and read nothing alike, so weight is what decides. Mixing prose with the list, or nesting levels inside it, ends the exemption at any weight.
107
+ - Past roughly 400 characters in one top-level bullet, counting the lines that continue it and excluding any bullet nested under it, check whether the incident that motivated the decision sits beside the decision itself. Keep the current design and the alternative that lost, and move the incident to the change that introduced it, the issue that tracked it, or the research record behind it. The number is a checkpoint like the two above, and a bullet reading well past it means the number is wrong rather than the rule.
108
+ - Collapse a stack of bullets narrating one subsystem into a single `###` subsection carrying one narrative. Splitting a heavy bullet into three light ones satisfies the checkpoint above and leaves the reader no better off, and subdividing a block does not lighten the bullets inside it, so the two rules answer different defects.
107
109
  - Never cut a `## Decisions` or `## Gotchas` entry to shorten a file. Cut a `## Layout` or `## CLI` section instead.
110
+ - Retire a decision or gotcha once its subject is gone, rewriting the bullet to state the current design rather than leaving the narration of what it replaced beside it. A rejected alternative is not a retired one, so what was tried and why it lost stays whatever its age. The rule above protects content whose subject is live, and this one releases content whose subject is not.
111
+ - Rewrite a decision a later one replaced rather than appending the replacement beside it. The subject is still live, so the rule above does not reach it, and two bullets on one subject leave a reader to work out which of them is current. State the design that stands and keep the superseded reasoning only where it is the alternative that lost.
108
112
  - Split into a folder (`.claude/context/<domain>/<sub-area>.md`) when a domain has three or more sub-areas that do not fit cleanly in one file. That split is the natural ceiling.
109
113
  - Keep a split domain's `index.md` generated. The catalog body is rewritten on every regen, so the domain's own overview and layout belong in a sibling file rather than in it, and the `subtitle` is what names the file to start with.
110
114
  - Author a catalog that grows a row per shipped thing as a bullet list, never a markdown table. A table pads its columns to a shared width, so one row outgrowing its column reflows every row and turns a one-line edit into a whole-file rewrite that conflicts with any sibling branch. This is what forced the split above.