@erclx/aitk 0.67.0 → 0.69.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.67.0",
4
+ "version": "0.69.0",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -11,6 +11,10 @@ Without this skill, the session holding the cross-feature picture starts buildin
11
11
 
12
12
  The queue fails in both directions. It empties and a free worker waits, or it fills with whatever is oldest rather than with what establishes a mechanism. A plan handed over unverified is the quiet one, since it goes stale from whatever merged after it was written, and a worker that trusts its account of the tree builds against a shape that no longer exists. A second orchestrator makes all of this unrecoverable, because the board is gitignored and neither session can read the other's writes.
13
13
 
14
+ Refilling on a merge and on a thinning ready list leaves the window between them unwatched. A wave mid-build has merged nothing and has moved no rows, so both conditions stay silent across the whole build and the wave finishes into an empty queue, which lands planning on the critical path directly after the stretch where it would have cost nothing.
15
+
16
+ Writing those plans against the tree alone is the second half, since several branches are already changing the shape a plan describes. A plan carrying a bare path list rather than a constraint per in-flight track leaves the worker guessing which act each path forbids, which is the dangling citation the rule against bare paths already exists to prevent.
17
+
14
18
  The session also writes surfaces it is told only how to read. Refilling the queue promotes and demotes rows in the priority file with no stated method, so a session invents one, and an inline rewrite exits clean when it matches nothing and leaves the board wrong with nothing reporting it. A row carrying prose where a plan pointer belongs costs a worker dispatch, because the ship chain refuses at its guard after the worktree is already open. A plan archived from a worker's own branch strands the pointer the board still carries, and the row reads as correct until someone follows it.
15
19
 
16
20
  The ban on writing at all fails on a different axis, which is that nothing enforces it and a session weighs proportionality against it. A one-line prose correction found while orchestrating satisfies the root instruction to handle a small edit immediately and violates this rule, and a session given no statement of which one wins takes the smaller apparent cost and authors the change. That removes the independent pass the repository built to catch what a self-review misses, and the vantage does not come back, since no later session can review the change without re-deriving the context that produced it. A correction no open task owns has nowhere to go either, so the route matters as much as the ban.
@@ -19,6 +23,8 @@ Output drifts everywhere the contract stops. The specified shape covers invocati
19
23
 
20
24
  The runbooks the session cannot run from memory fail on a third axis, which is where they are kept. A runbook reached by an installed path is a dependency on a channel this skill does not travel on, so a project holding the plugin and running no install follows the citation to nothing, and nothing reports the break because a missing file produces no error until someone opens the path. Every one of them therefore has to ship inside the skill, the three covering the moments the loop cannot detect and the fourth holding the review trigger. That fourth one fails on an axis of its own, since it names one client's command as the way to start the loop, so a session holding a different recurring-prompt scheduler reaches no path and a client without that command reaches none either, and the file ships to every target holding the plugin.
21
25
 
26
+ The session also records nothing of what it learns. Both other callers of memory capture are ship-chain skills and this one never ships, so the session taking every operator correction is the session with no moment that writes one down. Hanging that moment on the merge sweep answers it and bills the operator a capture pass per batch of merges while nothing is being built, which is a cost paid on the days shipping is fastest.
27
+
22
28
  ## Must
23
29
 
24
30
  - Read the priority file for execution order, since the index sorts by filename and states no order
@@ -28,11 +34,14 @@ The runbooks the session cannot run from memory fail on a third axis, which is w
28
34
  - Place every finding a merged pull request produced against the surface that owns it
29
35
  - Date the roadmap line from that file's last commit, so an old sequence reads as old
30
36
  - Keep one planned, non-conflicting task in reserve beyond what is running
37
+ - Refill the queue while a wave is still building, rather than on a merge and a thinning list alone, since neither of those fires across the window planning costs nothing
38
+ - Carry a constraint naming each in-flight track's file set in every plan written from here, stating per set which of the two acts it forbids, since a bare path list leaves a broken citation in place
31
39
  - Write the priority file with an editing tool that errors on a non-match, since the board is where a silent failure costs a dispatch
32
40
  - Carry a plan pointer in the Plan column and a file set in the Touches column, so a row's readiness and disjointness claims stay checkable
33
41
  - Re-resolve the board's plan pointers after any archive, since the archiving skill rewrites the task file and knows nothing about the board
34
42
  - Lead a sweep report, a board report, and an analysis with the state, the open decisions, and the next action, keeping the evidence below them
35
43
  - Write the pre-compact handoff with what no other file carries, and name the runbook that reads it back
44
+ - Capture what the session learned at the handoff rather than in the merge sweep, and have the sweep report the debt dated from the last handoff, since a capture per batch of merges bills the operator a wait while nothing ships and an undated row reads the same however long the debt has run
36
45
  - Carry every runbook the session cannot run from memory inside the skill, so each citation resolves for a project holding the plugin and nothing else
37
46
  - State which rule wins where the tracked-file ban collides with the root instruction on small edits, and where a correction no open task owns goes
38
47
  - State the review trigger as a recurring prompt rather than as one client's command, since the runbook ships to targets running whatever client they run
@@ -35,7 +35,7 @@ Then output the state of play so the human knows what to launch, review, and mer
35
35
 
36
36
  The roadmap is optional and this skill does not require it. It carries why a sequence is what it is, changes only when strategy changes, and is absent in a project whose scope has already shipped. Report what it says and name it as the source. Never assert an active version the file does not state, and say nothing about one when the file is missing.
37
37
 
38
- A compaction is a moment this skill cannot detect, so the human asks for each side of it and this skill reads the matching runbook when they do. On a request to write the handoff or save the session, read `${CLAUDE_SKILL_DIR}/references/orchestrator-handoff.md` and follow it. It writes `.claude/tasks/session.md` with the state of play, the decisions taken under delegated authority, the mistakes worth not repeating, and the standing cautions. On a request to resume after a compaction, read `${CLAUDE_SKILL_DIR}/references/orchestrator-resume.md`, which reads that file back with the board and the groundwork behind the live work. Write nothing to the handoff that the board, a task file, or a groundwork folder already carries.
38
+ A compaction is a moment this skill cannot detect, so the human asks for each side of it and this skill reads the matching runbook when they do. On a request to write the handoff or save the session, read `${CLAUDE_SKILL_DIR}/references/orchestrator-handoff.md` and follow it. It captures what the session learned, then writes `.claude/tasks/session.md` with the state of play, the decisions taken under delegated authority, the mistakes worth not repeating, and the standing cautions. That capture is the only one this session runs, since the refill sweep reports it as owed rather than paying it. On a request to resume after a compaction, read `${CLAUDE_SKILL_DIR}/references/orchestrator-resume.md`, which reads that file back with the board and the groundwork behind the live work. Write nothing to the handoff that the board, a task file, or a groundwork folder already carries.
39
39
 
40
40
  The review trigger takes the same shape. `references/orchestrator-poll.md` holds the loop prompt and the condition under which the poll runs, and `scripts/poll.sh` is what the prompt invokes. A session holding a recurring-prompt scheduler starts and cancels that loop itself, and no hook or check does, so the condition holds only while whoever holds the loop applies it.
41
41
 
@@ -94,13 +94,15 @@ Write no shape for a correction. A correction is a sentence, and a format for ad
94
94
  ## The loop
95
95
 
96
96
  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.
97
- 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. A constraint supplied from here that names a surface to leave alone states which of two acts it forbids, and the rule governing that is Step 3 of `claude-feature` under Constraints.
97
+ 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. Every plan written from here also carries a constraint per track in flight, which the paragraph below this list states.
98
98
  3. Decide parallelism and merge order. Note which plans touch a shared wiring seam so their PRs merge in sequence, not at once.
99
99
  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.
100
100
  5. Hand off. The human opens a worker worktree with `claude-worktree` and runs `claude-autoship` against the plan. The orchestrator does not spawn workers.
101
101
  6. Review the PR. When a worker opens a PR, run `claude-pr-review` to post findings to it. This is the deep, independent pass. The worker's autoship self-review was only the green gate. Learning that a PR moved is the mechanical half, so read `${CLAUDE_SKILL_DIR}/references/orchestrator-poll.md` and start the poll it carries on the first dispatch rather than checking the board by hand. It routes a moved or answered pull request straight to a re-review and reports an opened one without acting, which keeps every first pass a batched judgment this session triggers.
102
102
  7. Close the loop. After the worker runs `claude-address-review`, re-review if needed, then the human merges. Tell the trailing worker to rebase when its branch shares a seam with the merged one.
103
103
 
104
+ A plan written here is written against a tree several branches are already changing, so it names the file set of every track in flight as a constraint, one set per track, read from the Touches column of that track's row. State for each set which of the two acts it forbids, per Constraints in `.claude/standards/plan.md`, or `${CLAUDE_SKILL_DIR}/../../standards/plan.md` when the project does not have it. A bare path list leaves the worker guessing, which is how a plan ends up forbidding the repair of a citation the change broke.
105
+
104
106
  ## Boundaries
105
107
 
106
108
  - Run one orchestrator at a time. The board is gitignored, so a second session sees none of this one's writes: two task files land minutes apart under different labels for the same work, one session archives a task mid-sweep in the other, and each archives a plan the other had retargeted. An Owner column does not fix this, since neither session can read the other's rows.
@@ -114,11 +116,13 @@ The tracked-file boundary collides with `CLAUDE.md`, which says to handle a smal
114
116
 
115
117
  ## Refilling the ready queue
116
118
 
117
- Keep enough planned, non-conflicting tasks available that a free worker never waits, and place the findings the last merge produced before promoting anything new. Run this after every merge and whenever the ready list thins. `${CLAUDE_SKILL_DIR}/references/orchestrator-sweep.md` wraps this procedure for a batch of merges and adds the plan re-verification that a merge invalidates.
119
+ Keep enough planned, non-conflicting tasks available that a free worker never waits, and place the findings the last merge produced before promoting anything new. Run this after every merge, whenever the ready list thins, and whenever a wave is in flight with fewer unclaimed plans than there are workers building. `${CLAUDE_SKILL_DIR}/references/orchestrator-sweep.md` wraps this procedure for a batch of merges and adds the plan re-verification that a merge invalidates.
120
+
121
+ The third trigger reaches a window the first two cannot. A session with three workers mid-build has merged nothing and has watched its ready list sit still, so both reactive triggers stay silent across the one stretch where planning costs the session nothing, and the wave finishes into an empty `## Run now`. Nothing starts this pass, in the way nothing starts the review poll, so it holds only while the session applies it.
118
122
 
119
- Open the sweep by invoking `aitk:claude-memory-capture`. Both other callers are ship-chain skills and this session never ships, so without this the session that receives every operator correction is the one session that records none. The sweep is the closest bounded moment this session has to a ship, and it already runs once per batch of merges, which beats an end-of-session moment a compaction can cut short.
123
+ It counts unclaimed plans against workers rather than reading the reserve in step 4, which is sized for one worker finishing and falls short of a wave landing together. A plan a worker has already taken serves nobody who finishes next, so counting it is what lets the queue read full while it is about to empty. How many to write forward past that floor is the parallelism call rather than a count of free slots, and `## Parallelism` below states what binds it.
120
124
 
121
- Capture is told this session does not commit, so it skips routing and writes memory files alone. A routed fact lands in a context entry, which is a tracked file, and Boundaries below forbids writing one from here. That is the correct split rather than a limitation: a domain fact belongs to the task that owns the surface and goes in that task's Findings, while what this session produces is feedback about how to work, which is exactly the class the memory folder keeps.
125
+ ### Running the refill
122
126
 
123
127
  1. Run `gh pr list --state open` and `git log --oneline -8`. Report any pull request whose review has not been posted and stop for that one first.
124
128
  2. For each pull request merged since the last sweep, place every finding it produced. Route a finding that changes a rule to the standard or rule that states it, one that changes another task to that task's Findings, and one that overturns a groundwork lean to that folder marked answered. Never leave a finding in a pull request thread alone.
@@ -126,10 +130,10 @@ Capture is told this session does not commit, so it skips routing and writes mem
126
130
  4. Read `.claude/tasks/priority.md` and count entries under its `## Run now` heading that carry a written plan. Keep one in reserve beyond what is running.
127
131
  5. Promote by whether a task establishes functionality rather than by age. Prefer a task that adds or proves a mechanism over one that trims, tidies, or audits an existing surface.
128
132
  6. Before promoting a candidate, list the files it touches against every task already running, per Parallelism below. Name the overlap and serialize when the sets are not disjoint.
129
- 7. Write a plan for each newly promoted task with `claude-feature`, then report:
133
+ 7. Write a plan for each newly promoted task with `claude-feature`, carrying the in-flight constraint that The loop above states, then report:
130
134
 
131
135
  ```plaintext
132
- Captured: <memory file> (<type>), or "nothing worth capturing"
136
+ Capture: owed since <the last handoff, or session start when none has run>
133
137
  Findings placed: <finding> → <destination>
134
138
  Archived: <task>
135
139
  Promoted: <task>, touches <surfaces>, parallel with <task> because <disjoint sets>
@@ -137,6 +141,8 @@ Serialized: <task> behind <task>, both write <file>
137
141
  Ready now: <tasks with plans, and what each waits on>
138
142
  ```
139
143
 
144
+ The capture row states a standing debt rather than a per-run result. Running capture from here costs the operator a pass between merges while nothing ships, so the row leaves the timing to them and dates the debt, since a capture owed for twenty minutes and one owed all day want different answers and undated text reads the same either way. `${CLAUDE_SKILL_DIR}/references/orchestrator-handoff.md` holds the step that pays it.
145
+
140
146
  That block is the detail. Lead the reply with the three slots under Every later turn above, so the human reads what they own before the evidence for it.
141
147
 
142
148
  Treat a task that edits `.claude/context/` entries wholesale as conflicting with every other task, because the root instruction file requires each task to update its own domain entry as it lands.
@@ -1,13 +1,14 @@
1
1
  ---
2
2
  title: Orchestrator handoff runbook
3
- description: What to write to .claude/tasks/session.md before a compaction, and what to leave to the board
3
+ description: Memory capture at the close of a session, what to write to .claude/tasks/session.md before a compaction, and what to leave to the board
4
4
  ---
5
5
 
6
- Write the pre-compact handoff as orchestrator. Do this before a compaction, because a compaction keeps conclusions and drops the reasoning that produced them, and no other file in the repository carries that reasoning.
6
+ Capture what the session learned, then write the pre-compact handoff as orchestrator. Do both before a compaction, because a compaction keeps conclusions and drops the reasoning that produced them, and no other file in the repository carries that reasoning.
7
7
 
8
- 1. Resolve the main worktree root with `git worktree list --porcelain | grep -m 1 '^worktree ' | cut -d' ' -f2-`, falling back to `pwd`. Write `.claude/tasks/session.md` under it.
9
- 2. Write only what a compaction destroys and no other file already carries. The board holds the ordering and what each task waits on, a task file holds its own findings, and a groundwork folder holds its track.
10
- 3. Use this shape:
8
+ 1. Invoke `aitk:claude-memory-capture` and tell it this session does not commit. Capture reads the session and this handoff summarizes it, so capturing first lets the handoff cite what was written instead of restating the same lesson in prose.
9
+ 2. Resolve the main worktree root with `git worktree list --porcelain | grep -m 1 '^worktree ' | cut -d' ' -f2-`, falling back to `pwd`. Write `.claude/tasks/session.md` under it.
10
+ 3. Write only what a compaction destroys and no other file already carries. The board holds the ordering and what each task waits on, a task file holds its own findings, and a groundwork folder holds its track.
11
+ 4. Use this shape:
11
12
 
12
13
  ```markdown
13
14
  ---
@@ -36,7 +37,11 @@ description: <what the board cannot show, and the date it was written>
36
37
  <commands that lie, tools that measure the wrong tree, and anything unbacked>
37
38
  ```
38
39
 
39
- 4. Cite a commit, a task, or a file and line for every claim, so the next session can tell a read from a recall.
40
- 5. Overwrite the previous handoff rather than appending to it. A stale entry read as current is worse than no handoff.
40
+ 5. Cite a commit, a task, or a file and line for every claim, so the next session can tell a read from a recall.
41
+ 6. Overwrite the previous handoff rather than appending to it. A stale entry read as current is worse than no handoff.
41
42
 
42
43
  Add a section only for content that fits none of the four and would otherwise be lost. Do not restate the board, and do not summarize the work that shipped, because git already carries it.
44
+
45
+ Step 1 exists because both other callers of capture are ship-chain skills and this session never ships. Without a call here, the session that receives every operator correction is the one session that records none. A compaction arriving with no warning takes the capture with it, and firing it once per batch of merges leaves the same window open across a long planning stretch, since a sweep runs only on a merge. The refill sweep reports the debt between handoffs so the operator knows one is owed.
46
+
47
+ Capture is told this session does not commit, so it skips routing and writes memory files alone. A routed fact lands in a context entry, which is a tracked file, and the orchestrator's boundaries forbid writing one from this session. That split is correct rather than a limitation, since a domain fact belongs to the task that owns the surface and goes in that task's Findings, while what this session produces is feedback about how to work, which is the class the memory folder keeps.
@@ -1,23 +1,31 @@
1
1
  ---
2
2
  name: project-commands
3
- description: Runs a command the project documents in `.claude/context/development.md` and stops there. Use when asked to "start the app", "start the dev server", "run the checks", "run the build", "spin it up", or "what commands does this project have". Do NOT use to confirm a change works in a running app, which needs verification past the launch. Do NOT use to deploy, publish, or release.
3
+ description: Runs a command the project documents in its development context entry and stops there. Use when asked to "start the app", "start the dev server", "run the checks", "run the build", "spin it up", or "what commands does this project have". Do NOT use to confirm a change works in a running app, which needs verification past the launch. Do NOT use to deploy, publish, or release.
4
4
  ---
5
5
 
6
6
  # Project commands
7
7
 
8
- Read `.claude/context/development.md` from the project root, run the command the user named, report where it landed, and stop.
8
+ Read the project's development context entry, run the command the user named, report where it landed, and stop.
9
9
 
10
10
  The value is the stop. A launch that continues into log inspection, browser checks, or unrelated defects is the failure this skill exists to avoid.
11
11
 
12
12
  ## Guards
13
13
 
14
- - Check that `.claude/context/development.md` exists before anything else. If it does not, stop: `❌ No .claude/context/development.md. This project has no documented dev loop.` Name the missing file and let the user decide. Do not read another file to reconstruct it, because a guess is worse than a stop when the user cannot see it was a guess.
14
+ - Resolve the entry before anything else. Take the flat `development.md` under `.claude/context/` when it exists, and `.claude/context/development/overview.md` when the domain outgrew one file and split into a folder, which is where the `## Scripts` table lands in a split. Test both paths, then read one. If neither resolves, stop with the line below and let the user decide. Do not read another file to reconstruct it, because a guess is worse than a stop when the user cannot see it was a guess.
15
15
  - If the entry documents no command matching the request, stop and list what it does document. Do not infer a command from a filename or a framework.
16
16
  - If the resolved command has an effect that outlives the process and stopping it does not undo, print it for the user to run and stop. Deploying, publishing, releasing, migrating, and resetting are the common shapes, and the test is the effect rather than the name. A script called `infra:apply` or `promote` qualifies.
17
17
 
18
+ The stop names the flat path in both cases, since a project carrying neither has no entry to point at and the flat one is where a project without a split keeps it:
19
+
20
+ ```plaintext
21
+ ❌ No .claude/context/development.md. This project has no documented dev loop.
22
+ ```
23
+
18
24
  ## Step 1: read the entry
19
25
 
20
- Read `.claude/context/development.md` from the project root, the whole entry rather than a named section. That file and no others. A second file is a discovery chain, and this skill has none.
26
+ Read the path the guard resolved, from the project root, the whole entry rather than a named section. That file and no others. A second file is a discovery chain, and this skill has none. Testing two candidate paths is not one, since the test happens before any read and exactly one file is opened.
27
+
28
+ A split domain keeps its other sub-area files out of reach on purpose. `overview.md` carries the run commands, and the siblings beside it cover verification stages, hooks, and scratch, none of which this skill runs.
21
29
 
22
30
  ## Step 2: resolve the command
23
31
 
@@ -67,7 +75,7 @@ One block per command when the request resolved to more than one.
67
75
  For a request with no command to run, list what the entry documents instead:
68
76
 
69
77
  ```plaintext
70
- 📋 Documented in .claude/context/development.md
78
+ 📋 Documented in <the entry path the guard resolved>
71
79
 
72
80
  - `<command>`: <purpose as the entry states it>
73
81
  ```
@@ -33,7 +33,7 @@ Full help: `aitk <command> --help`. Behavior notes for the install and sync verb
33
33
  | `aitk records validate` | Report a session record against the standard governing it, per kind (`--json`) |
34
34
  | `aitk comments scan` | Measure comment density by language and comment kind, with a trend recomputed from git |
35
35
  | `aitk context audit` | Report required sections, length, cited paths, catalog tables, provenance, superseded-decision narration, and index drift |
36
- | `aitk markdown audit` | Report any markdown path against the character bans, word bans, and structural checkpoints |
36
+ | `aitk markdown audit` | Fail any markdown path on a banned character, word, or spelling, and report the structural checkpoints |
37
37
  | `aitk claude skills audit` | Report both skill corpora against the mechanical rules in `standards/skill.md` |
38
38
  | `aitk capture [source]` | Render HTML capture sources to PNG, toolkit-only and absent from an installed package |
39
39
 
@@ -74,6 +74,10 @@ The citation check resolves every path into an audited folder that appears anywh
74
74
 
75
75
  Three exclusions keep it from firing on prose about paths. Fenced blocks are skipped in markdown, which covers a standard displaying a path as an example. Fixture and harness trees are skipped by location, covering sandbox scenarios that describe their own scratch tree, the eval harness naming its target project, and `*.test.ts`. A path into a folder the project does not carry is skipped, so a skill directing a reader to `.claude/wireframes/index.md` stays valid in a project that has wireframes and silent in one that does not.
76
76
 
77
- What remains is a sentence naming a hypothetical entry to show the shape of a name, which no syntax separates from a real reference. Append `<!-- audit-ignore-citations -->` to that source line. The marker suppresses citation checking for its own line only.
77
+ Two cases remain, and no syntax separates either from a real reference. One is a sentence naming a hypothetical entry to show the shape of a name. The other is a line instructing a target project about its own tree, which resolves against the target rather than against this repository and passes here only while the two layouts agree. Splitting a seeded domain into a folder ends that agreement and turns every such line red at once.
78
+
79
+ Append `<!-- audit-ignore-citations -->` to the source line in either case. The marker suppresses citation checking for its own line only.
80
+
81
+ The marker itself stays out of anything that installs. A seed, a plugin skill body, and a stack reference all reach a target, so a marker there lands as toolkit bookkeeping in someone else's tree. Reword those lines to drop the path instead, and where a stop message has to spell it, move that message into a fenced block, which this check already skips.
78
82
 
79
83
  The pattern spells the `.claude/` prefix, so a folder resolved at the project root is measured by every other check and contributes nothing here. Widening it to a bare `docs/x.md` would match prose that references nothing, which is a separate decision from where entries come from. A run whose folders all resolved at the root says the check is out of scope rather than reporting that zero paths resolved, and the same run under `--citations-only` refuses, because a gate exiting clean on a scope it could not build is the failure the gate exists to catch.
@@ -16,7 +16,7 @@ CLI catalog and invocation rules for agents, split by command domain. Start with
16
16
  - [Docs](docs.md): How aitk docs resolves the toolkit's own reference surface from an install root, and how a split domain is named
17
17
  - [Indexes](indexes.md): Flags, exit codes, and JSON shape for aitk indexes regen, plus when it auto-stages what it rewrote
18
18
  - [Install and sync](install-and-sync.md): What each install and sync verb writes, refuses, or leaves alone, and how drift is attributed in a target project
19
- - [Markdown audit](markdown-audit.md): Running the audit over any markdown path, where its bans and checkpoints are read from, what each check reports, and why nothing gates yet
19
+ - [Markdown audit](markdown-audit.md): Running the audit over any markdown path, where its bans and checkpoints are read from, what each check reports, and why the ban half gates while the structural half reports
20
20
  - [Output shape](output-shape.md): Two framed shapes every command renders into, how JSON and --names modes keep stdout clean, and the exit discipline that lets piped output drain
21
21
  - [Overview](overview.md): What this folder covers, the invocation rules every command inherits, and where domain behavior is documented instead
22
22
  - [Records](records.md): Validating the gitignored session records under .claude/, the per-kind checks, the refusal reasons, and why the root defaults to the main worktree
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Markdown audit
3
- description: Running the audit over any markdown path, where its bans and checkpoints are read from, what each check reports, and why nothing gates yet
3
+ description: Running the audit over any markdown path, where its bans and checkpoints are read from, what each check reports, and why the ban half gates while the structural half reports
4
4
  ---
5
5
 
6
6
  # Markdown audit
@@ -49,7 +49,7 @@ Two ban shapes stay unmeasured and the report says so on every run. A phrase ban
49
49
 
50
50
  ### Bullets, paragraphs, and depth
51
51
 
52
- Bullet weight and depth are the checks that moved off `aitk context audit`, unchanged in what they measure. A top-level bullet reports past roughly 400 characters with continuation lines folded in and nested items left out. A run of lines no heading breaks reports past roughly 40 rendered lines, measured at 80 columns, skipping fenced blocks and exempting a flat peer list averaging under 130 characters a bullet and a run that is entirely table rows.
52
+ Bullet weight and depth are the checks that moved off `aitk context audit`, unchanged in what they measure. A top-level bullet reports past roughly 400 characters with continuation lines folded in and nested items left out. A run of lines no heading breaks reports past roughly 40 rendered lines, measured at 80 columns, skipping fenced blocks and exempting a flat peer list averaging under 130 characters a bullet and a run that is entirely table rows. Each file reports its longest run alone, so a second run past the checkpoint in the same file is never named.
53
53
 
54
54
  Every weight and depth measure counts the text a reader is shown. A link reduces to its anchor text and an autolink drops whole, since no reader is shown either destination. A backticked path stays counted, which is where these measures part from the ban scan above: that one blanks a code span so a standard quoting its own banned character does not report itself, and discounting the same span here would under-report a paragraph carrying several. One file holds both span sets and each answers its own question.
55
55
 
@@ -82,13 +82,33 @@ A bullet, a heading, a table row, a blockquote, a blank line, and a fence each e
82
82
 
83
83
  ## Exit codes
84
84
 
85
- Exit codes are `0` for a completed run and `1` for a refusal. Every finding reports and none gates.
85
+ Exit codes are `0` for a completed run with no gating finding, `1` for a refusal, and `2` for a ban hit. A banned character, word, or spelling fails the run. Bullet, paragraph, and depth weight are judgments a reader settles, so all three report under every code.
86
86
 
87
- A banned character is a fact rather than a judgment, which is the test that would ordinarily make it gate. What holds it back is that gating on day one against a corpus never checked mechanically fails loudly on work nobody has had a chance to fix. The order is to land the verb reporting, measure the corpus once, fix what it finds, and turn the gate on as its own change. Bullet, paragraph, and depth weight are judgments and stay advisory under any later gate.
87
+ `2` rather than `1` for the gate keeps a measurement that succeeded and found something distinct from the audit declining to measure at all. A caller reading one as the other sends a reader hunting a defect that does not exist, which is the distinction `aitk context audit` and the `verify.sh` seed stage already draw between the same two codes.
88
88
 
89
- Measured at `4b7b13a2` across 444 files with the paragraph checkpoint at 600: 8 word hits, no character or spelling hits, 119 heavy bullets, 221 heavy paragraphs across 86 files, and 41 files carrying a run past the depth checkpoint. Of the paragraphs, 88 fire on weight alone. This is the baseline the corpus sweep tracks its work against. The ban count is what a gate would have to hold at zero, and it is the only one of the five a gate should ever read.
89
+ A banned character is a fact rather than a judgment, which is the test that admits it to a gate. What held it back was that gating on day one against a corpus never checked mechanically fails loudly on work nobody has had a chance to fix. The order was to land the verb reporting, measure the corpus once, fix what it finds, and turn the gate on as its own change, and the gate is the last of the four.
90
90
 
91
- The ban half of that baseline now reports no character, word, or spelling hit, which is the precondition the gate was waiting on. The other four moved with the corpus rather than with any decision, so read them from a run rather than from this paragraph.
91
+ Measured at `4b7b13a2` across 444 files with the paragraph checkpoint at 600: 8 word hits, no character or spelling hits, 119 heavy bullets, 221 heavy paragraphs across 86 files, and 41 files carrying a run past the depth checkpoint. Of the paragraphs, 88 fire on weight alone. This is the baseline the corpus sweep tracked its work against. The ban count is what the gate holds at zero, and it is the only one of the five a gate should ever read.
92
+
93
+ The ban half of that baseline reached zero, which was the precondition the gate waited on, and it was re-measured against the same corpus at the moment the gate landed. The other four moved with the corpus rather than with any decision, so read them from a run rather than from this paragraph.
94
+
95
+ ### What a hit asks of an author
96
+
97
+ Rewrite the sentence rather than swapping the banned token for a near-synonym. The rule is about the sense the token carries, so a swap that keeps the sense clears the report without clearing the violation.
98
+
99
+ A code span clears the report too, since the ban scan walks around one, and it is the answer only where the token is genuinely an identifier under discussion. `## Code and identifiers` in `markdown.md` reserves the span for commands, API names, file paths, and identifiers, so backticking a quoted utterance spends one rule to satisfy another and leaves the corpus no cleaner.
100
+
101
+ A hit the closed set cannot separate from correct prose is the case with no third option. `prose.md` bans vague qualifiers and lists the tokens those qualifiers happen to spell, so the temporal `just` reports as the vague one. The rule as written reaches neither, and rewriting the sentence is what the toolkit settled on over building an exemption path, for the reasons below.
102
+
103
+ ### Where the rules are enforced
104
+
105
+ Four surfaces read the ban sets and two of them go through this verb. `.claude/hooks/standards-audit.sh` runs it against a single file after each markdown edit, and the `Markdown bans` stage in `scripts/core/verify.sh` runs it across the whole corpus before a push. The hook parsed its own copy of the word bans in awk until the gate landed, which left a British spelling passing at edit time and failing the push with nothing in between explaining the difference.
106
+
107
+ The other two read the standards directly and neither is a consolidation left half done. `claude/skills/claude-standards-audit/SKILL.md` greps the banned tokens agent-side, which is a session reading prose rather than a process it can shell out to, and it ships to every target. The seed copy of the hook keeps its awk, because a scaffolded project may carry no `aitk` and `scripts/core/check-seed-independence.sh` exists to catch seed content depending on the toolkit CLI. Both are the likelier place for the next drift, since nothing compares either against the verb.
108
+
109
+ The hook prefers a checkout's own `src/cli.ts` over a globally installed binary, so it and the push stage read one build. A published binary lags a branch by whatever has not been released, which would put a ban kind added on the branch into the push and not into the edit. It reads its findings out of the `--json` record rather than off the exit code, so an older binary still reports where the fallback applies. A machine with neither runner gets no enforcement at edit time rather than a blocked edit, and the push stage still holds.
110
+
111
+ The stage measures the whole corpus rather than the changed files. A `Do not use` bullet added to a standard bans a token retroactively, and no file in the push that adds the bullet was edited.
92
112
 
93
113
  ### Why a recorded count goes stale
94
114
 
@@ -106,7 +126,7 @@ The other five were correct prose the closed set cannot separate from a violatio
106
126
 
107
127
  ### Why they were rewritten rather than exempted
108
128
 
109
- Rewriting all five is what settled them, over building an exemption path. An exemption has three consumers, `src/markdown/scan.ts` for the patterns, `src/markdown/bans.ts` for the sets, and `.claude/hooks/standards-audit.sh`, which greps its own copy of the word bans in awk. A mechanism landing in the verb and not the hook leaves an exempted line still failing on edit, which is the surface an author actually meets. Five sentences lost a small amount of naturalness and the count now means what it says.
129
+ Rewriting all five is what settled them, over building an exemption path. An exemption has three consumers, `src/markdown/scan.ts` for the patterns, `src/markdown/bans.ts` for the sets, and `.claude/hooks/standards-audit.sh`, which held its own copy of the word bans in awk at that point. A mechanism landing in the verb and not the hook leaves an exempted line still failing on edit, which is the surface an author actually meets. Five sentences lost a small amount of naturalness and the count now means what it says.
110
130
 
111
131
  A code span was the first answer for the quoted anti-pattern and it was the wrong one. The ban scan walks around a code span, so backticking a quotation clears the report, and `## Code and identifiers` reserves the span for commands, API names, file paths, and identifiers, which a quoted utterance is none of. Spending one rule to satisfy another leaves the corpus no cleaner than dropping the qualifier does.
112
132
 
@@ -46,10 +46,10 @@ The chain is:
46
46
 
47
47
  - `aitk init` installs base tooling, Claude seeds, and governance rules into `.claude/rules/` in the same pass
48
48
  - `aitk tooling sync <stack>` adds stack-specific deps, scripts, gitignore entries, and drops `.claude/tooling/<stack>.md` (plus parents) as the agent's audit context
49
- - The agent follows the reference 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
49
+ - The agent follows the reference 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 -->
50
50
  - `setup-verify` runs the installed `package.json` scripts (lint, typecheck, check, test, build) and reports pass or fail
51
51
 
52
- 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.
52
+ 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 -->
53
53
 
54
54
  ### From scaffold to first feature
55
55
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/aitk",
3
3
  "type": "module",
4
- "version": "0.67.0",
4
+ "version": "0.69.0",
5
5
  "description": "Infrastructure and quality tooling for developer workflows",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -272,6 +272,40 @@ main() {
272
272
  run_check "cd $PROJECT_ROOT && bun src/cli.ts context audit --citations-only" "A cited context path does not resolve. Run bun src/cli.ts context audit."
273
273
  log_info "Context citations resolve"
274
274
 
275
+ # A banned character, word, or spelling is a fact rather than a threshold, so
276
+ # it fails the push while bullet, paragraph, and depth weight stay advisory
277
+ # for the reason the stage above leaves its own thresholds so.
278
+ #
279
+ # The whole corpus is measured rather than the changed files, because a
280
+ # `Do not use` bullet added to a standard bans a token retroactively and no
281
+ # file in the push that adds it was edited.
282
+ #
283
+ # `--json` sends the record to stdout and the frame to stderr, so a passing
284
+ # run stays silent and a failing one is re-run for its frame rather than
285
+ # parsed out of a stream this script would have to strip. `bun src/cli.ts`
286
+ # rather than `aitk` for the reason the stage above uses it.
287
+ log_step "Markdown bans"
288
+ local ban_status=0 ban_frame
289
+ (cd "$PROJECT_ROOT" && bun src/cli.ts markdown audit --json >/dev/null 2>&1) || ban_status=$?
290
+ case $ban_status in
291
+ 0)
292
+ log_info "No banned character, word, or spelling"
293
+ ;;
294
+ 1)
295
+ log_warn "Skipped, the markdown audit refused and measured nothing"
296
+ ;;
297
+ 2)
298
+ # `|| true` because the re-run exits non-zero by construction, and `set -e`
299
+ # would take the script down before log_error names the remedy.
300
+ ban_frame=$(cd "$PROJECT_ROOT" && bun src/cli.ts markdown audit 2>&1 || true)
301
+ echo "$ban_frame" | pipe_output
302
+ log_error "Markdown prose carries a banned character, word, or spelling. Rewrite the sentence, and reach for a code span only where the token is genuinely an identifier under discussion."
303
+ ;;
304
+ *)
305
+ log_error "The markdown audit exited $ban_status, which is neither a pass nor a finding."
306
+ ;;
307
+ esac
308
+
275
309
  # The stage above audits this repository. Its seed tree ships into every
276
310
  # scaffolded project, so a seed breaking the standard it seeds propagates
277
311
  # instead of sitting still, and no rule path reaches the tree to report it.
@@ -3,6 +3,7 @@ import { resolve } from 'node:path'
3
3
  import type { Command } from 'commander'
4
4
  import { type BanReport, banReport, loadStandards } from '@/markdown/bans'
5
5
  import { resolveMarkdown } from '@/markdown/files'
6
+ import { isGating } from '@/markdown/gate'
6
7
  import { type BanFinding, bodyLines, scanBans } from '@/markdown/scan'
7
8
  import {
8
9
  type Checkpoints,
@@ -20,6 +21,8 @@ import {
20
21
  plural,
21
22
  } from '@/ui'
22
23
 
24
+ const EXIT_GATE = 2
25
+
23
26
  interface AuditCommandOptions {
24
27
  readonly json?: boolean
25
28
  }
@@ -39,7 +42,7 @@ export function register(program: Command): void {
39
42
  markdown
40
43
  .command('audit')
41
44
  .description(
42
- 'Report the character bans, the word bans, and bullet, paragraph, and depth weight',
45
+ 'Fail on a banned character, word, or spelling, and report bullet, paragraph, and depth weight',
43
46
  )
44
47
  .argument(
45
48
  '[path...]',
@@ -52,12 +55,18 @@ export function register(program: Command): void {
52
55
  [
53
56
  '',
54
57
  'Exit codes:',
55
- ' 0 the audit completed',
58
+ ' 0 the audit completed with no gating finding',
56
59
  ' 1 refused, with the reason on stderr',
60
+ ' 2 a banned character, word, or spelling is present',
61
+ '',
62
+ 'A ban hit is a fact and gates unconditionally. Bullet, paragraph, and',
63
+ 'depth weight are judgments a reader settles, so all three report and',
64
+ 'none of them fails a run.',
57
65
  '',
58
- 'Every finding reports and none gates. A banned character is a fact and',
59
- 'will gate once the corpus has been measured and fixed, which is the',
60
- 'follow-up rather than this command.',
66
+ 'Rewrite the sentence carrying a hit rather than swapping the token for',
67
+ 'a near-synonym. A code span clears the report and is the answer only',
68
+ 'where the token is genuinely an identifier under discussion, which is',
69
+ 'what markdown.md reserves the span for.',
61
70
  '',
62
71
  'Bans and checkpoints are read from markdown.md and prose.md, resolved',
63
72
  'under .claude/standards/ then standards/. No folder has to resolve and',
@@ -156,7 +165,12 @@ async function runAudit(
156
165
  )
157
166
  }
158
167
 
159
- return 0
168
+ const gating = isGating({
169
+ bans: reports.flatMap((report) => report.bans),
170
+ structure: reports.map((report) => report.structure),
171
+ })
172
+
173
+ return gating ? EXIT_GATE : 0
160
174
  }
161
175
 
162
176
  function refuse(message: string): number {
@@ -218,6 +232,13 @@ function reportBans(reports: readonly FileReport[], bans: BanReport): void {
218
232
 
219
233
  const total = carrying.reduce((sum, report) => sum + report.bans.length, 0)
220
234
  logWarn(`${plural(total, 'hit')} across ${plural(carrying.length, 'file')}`)
235
+ logWarn('This fails the run. Every other check below reports.')
236
+ logInfo(
237
+ 'Rewrite the sentence rather than swapping the token for a near-synonym.',
238
+ )
239
+ logInfo(
240
+ 'A code span clears the report and is the answer only where the token is genuinely an identifier under discussion, which is what markdown.md reserves the span for.',
241
+ )
221
242
  pipeOutput(
222
243
  carrying
223
244
  .map(
@@ -0,0 +1,28 @@
1
+ import type { BanFinding } from '@/markdown/scan'
2
+ import type { StructureReport } from '@/markdown/structure'
3
+
4
+ export interface GateInput {
5
+ /** Every ban hit across every file measured, flattened. */
6
+ readonly bans: readonly BanFinding[]
7
+ /**
8
+ * Every structural measure the run made, read by nothing here.
9
+ *
10
+ * Naming it is what makes the split checkable: bullet, paragraph, and depth
11
+ * weight are judgments a reader settles, and a push failing on one teaches a
12
+ * contributor to route around the stage. A signature taking the ban list
13
+ * alone states the same rule and leaves no place to assert it.
14
+ */
15
+ readonly structure: readonly StructureReport[]
16
+ }
17
+
18
+ /**
19
+ * Whether the audit found something that should fail the caller.
20
+ *
21
+ * A banned character, word, or spelling is a fact rather than a threshold, so
22
+ * it gates unconditionally and there is no widened mode to reach for. The
23
+ * standards decide what counts as banned, which keeps this answering how many
24
+ * rather than which.
25
+ */
26
+ export function isGating({ bans }: GateInput): boolean {
27
+ return bans.length > 0
28
+ }
@@ -12,7 +12,7 @@ The astro stack covers Astro + TypeScript projects: content sites, marketing sit
12
12
  2. Add React integration: `bunx astro add react`. Do not use `astro add tailwind`. That command installs the v3 integration. Tailwind v4 arrives via the web manifest.
13
13
  3. Install web tooling: `aitk tooling sync web .`
14
14
  4. Install astro adapter: `aitk tooling sync astro .`
15
- 5. Extend `.claude/context/ci.md` and `.claude/context/development.md` per the web reference's extend sections plus the astro rows below.
15
+ 5. Extend the `ci` and `development` context entries under `.claude/context/` per the web reference's extend sections plus the astro rows below.
16
16
  6. Run `bun run lint:fix` then `bun run check`.
17
17
 
18
18
  ## What ships as golden configs
@@ -26,7 +26,7 @@ Seeds live in `tooling/base/seeds/`. Sync drops each once on first install and n
26
26
  - `.cspell/project-terms.txt` and `.cspell/tech-stack.txt`: one word per line, sorted alphabetically.
27
27
  - `.lintstagedrc`: the glob map below.
28
28
  - `.prettierignore`: created empty. Projects add their own entries.
29
- - `.claude/context/ci.md` and `.claude/context/development.md`: extend with project-specific commands, workflows, or deploy steps. Canonical rationale stays in this reference.
29
+ - `.claude/context/`: extend the `ci` and `development` entries with project-specific commands, workflows, or deploy steps. Canonical rationale stays in this reference.
30
30
 
31
31
  ## Tool pairing
32
32
 
@@ -32,7 +32,7 @@
32
32
 
33
33
  ## Commands
34
34
 
35
- - Run `bun run check` before committing. Full script reference in `.claude/context/development.md`.
35
+ - Run `bun run check` before committing. Full script reference in the development entry under `.claude/context/`.
36
36
 
37
37
  ## Output
38
38
 
@@ -11,7 +11,7 @@ The vite-react stack covers Vite + React + TypeScript projects: web apps and Chr
11
11
  1. Scaffold with `bunx create-vite@latest <name> --template react-ts` (web apps) or `bunx create-crxjs@latest` (Chrome extensions).
12
12
  2. Install base and web tooling: `aitk tooling sync web .`
13
13
  3. Install vite-react deps and configs: `aitk tooling sync vite-react .`
14
- 4. Extend `.claude/context/ci.md` and `.claude/context/development.md` per the web reference's extend sections plus the vite-react rows below.
14
+ 4. Extend the `ci` and `development` context entries under `.claude/context/` per the web reference's extend sections plus the vite-react rows below.
15
15
  5. Run `bun run lint:fix` then `bun run check`.
16
16
 
17
17
  ## What ships as golden configs
@@ -78,7 +78,7 @@ Under `## Running CI locally`, document that `bun run check:full` runs verify pl
78
78
 
79
79
  ## Development docs (extend)
80
80
 
81
- Extend `.claude/context/development.md` so the `## Scripts` table lists every web script. Stack adapters add their `dev`, `build`, `preview`, `typecheck` rows.
81
+ Extend the `development` context entry under `.claude/context/` so the `## Scripts` table lists every web script. Stack adapters add their `dev`, `build`, `preview`, `typecheck` rows.
82
82
 
83
83
  Append rows:
84
84