@erclx/aitk 3.12.0 → 3.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/claude/.claude-plugin/plugin.json +1 -1
  2. package/claude/skills/claude-groundwork/REQUIREMENT.md +1 -0
  3. package/claude/skills/claude-groundwork/SKILL.md +12 -12
  4. package/claude/skills/claude-intake/REQUIREMENT.md +1 -0
  5. package/claude/skills/claude-intake/SKILL.md +7 -7
  6. package/docs/agents/install-and-sync.md +55 -28
  7. package/docs/agents/intake.md +3 -1
  8. package/docs/ai-workflow.md +2 -2
  9. package/docs/target-projects.md +7 -5
  10. package/governance/rules/claude/556-groundwork.md +5 -1
  11. package/governance/rules/claude/557-intake.md +5 -1
  12. package/governance/rules/lang/120-bash.md +18 -0
  13. package/governance/rules/lib/305-e2e-reliability.md +34 -0
  14. package/governance/rules/lib/306-test-scope.md +25 -0
  15. package/governance/rules/lib/350-security-web.md +1 -0
  16. package/governance/rules/ui/450-link-behavior.md +19 -0
  17. package/governance/stacks/astro.toml +1 -1
  18. package/governance/stacks/base.toml +1 -1
  19. package/governance/stacks/react.toml +1 -1
  20. package/package.json +1 -1
  21. package/src/commands/gov.ts +10 -1
  22. package/src/commands/sync.ts +18 -5
  23. package/src/demo/compile.ts +1 -1
  24. package/src/gov/adapter.ts +29 -0
  25. package/src/gov/install.ts +22 -1
  26. package/src/gov/stacks.ts +33 -1
  27. package/src/intake/folder.ts +49 -9
  28. package/src/sync/check.ts +53 -15
  29. package/src/sync/engine.ts +25 -0
  30. package/src/sync/stamp.ts +73 -24
  31. package/src/tooling/stamp.ts +1 -1
  32. package/standards/groundwork.md +10 -3
  33. package/standards/index.md +2 -2
  34. package/standards/intake.md +10 -3
  35. package/tooling/web/manifest.toml +1 -0
  36. package/tooling/web/reference.md +1 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "aitk",
3
3
  "description": "Automated governance, versioning, and discovery tools for Claude Code.",
4
- "version": "3.12.0",
4
+ "version": "3.13.0",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -14,6 +14,7 @@ Two failure modes cost more than the rest. A track that fans out to subagents re
14
14
  ## Must
15
15
 
16
16
  - Detect open, resume, and close from the folder itself, matching the topic against tracks already there before deriving a slug
17
+ - Name a newly opened folder with a two-digit ordinal ahead of the slug, taken from the highest one already present across groundwork and intake
17
18
  - Apply the qualifying test in open mode alone, so a track already measured is not refused by the test that admitted it
18
19
  - Measure the current state now rather than carrying a figure from a previous session
19
20
  - Carry a lean and the finding that would overturn it on every open question, or admit that a measurement is missing
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: claude-groundwork
3
- description: Opens and runs a numbered groundwork folder under `.claude/groundwork/<slug>/` for a topic that has to be measured before it can be planned. Detects open, resume, and close from the folder itself. Use when asked to "research X", "dig into X", "work out what we should do about X", "measure this before we commit", or "open a groundwork folder". Do NOT use to write a feature plan or to implement. That is `claude-feature`.
3
+ description: Opens and runs a numbered groundwork folder under `.claude/groundwork/<nn>-<slug>/` for a topic that has to be measured before it can be planned. Detects open, resume, and close from the folder itself. Use when asked to "research X", "dig into X", "work out what we should do about X", "measure this before we commit", or "open a groundwork folder". Do NOT use to write a feature plan or to implement. That is `claude-feature`.
4
4
  ---
5
5
 
6
6
  # Claude groundwork
@@ -19,10 +19,10 @@ Read `${CLAUDE_SKILL_DIR}/../../standards/groundwork.md` before writing any file
19
19
 
20
20
  ## Write scope
21
21
 
22
- - Write only inside `.claude/groundwork/<slug>/`. A feature plan, source changes, a standard, a rule, and a reference doc all live outside that folder, so this one rule forbids every one of them.
22
+ - Write only inside `.claude/groundwork/<nn>-<slug>/`. A feature plan, source changes, a standard, a rule, and a reference doc all live outside that folder, so this one rule forbids every one of them.
23
23
  - One exception, at close only: write one task file recording what the track concluded.
24
24
  - A second exception, for what a spike reads: write an input under `.claude/.tmp/groundwork-fixtures/<slug>/`. Keep it out of `.claude/groundwork/` so mode detection never matches a fixture as a track. A fixture a headless run is pointed at goes outside the repository instead, per the rule in `## Running a spike`.
25
- - What a spike produces stays inside the track rather than joining the two exceptions above: write evidence a spike file cites under `.claude/groundwork/<slug>/evidence/`, which the first rule already permits. Mode detection matches entries at the top level of the tracks directory, so the sibling the fixtures rule guards against cannot be a folder nested inside a track, and the scratch path holds only what can be deleted without loss, which a recording a finding rests on is not.
25
+ - What a spike produces stays inside the track rather than joining the two exceptions above: write evidence a spike file cites under `.claude/groundwork/<nn>-<slug>/evidence/`, which the first rule already permits. Mode detection matches entries at the top level of the tracks directory, so the sibling the fixtures rule guards against cannot be a folder nested inside a track, and the scratch path holds only what can be deleted without loss, which a recording a finding rests on is not.
26
26
  - Reading is not restricted. External research is in scope, so read documentation, comparable projects, and papers whenever a live question needs them.
27
27
  - Every claim about a source outside the project carries a link to it. A source found and not read is listed as a lead and is never cited.
28
28
  - Treat the folder as gitignored and unbacked. It dies with the machine, so `07-next-session.md` repeats what it needs instead of pointing at its siblings.
@@ -41,11 +41,11 @@ Record method, result, measured cost, and caveats in `08-spikes.md`. Put whateve
41
41
 
42
42
  ## Step 1: detect the mode
43
43
 
44
- List `.claude/groundwork/` from the project root and match the topic against the tracks already there before deriving anything. A resume pass rarely phrases the topic the way the folder was named, so a fresh slug derived from the wording would miss a live track and restart it.
44
+ List `.claude/groundwork/` from the project root and match the topic against the slug half of each `<nn>-<slug>` folder already there before deriving anything. A resume pass rarely phrases the topic the way the folder was named, so a fresh slug derived from the wording would miss a live track and restart it.
45
45
 
46
46
  Never match against `.claude/` itself. That directory holds every other workflow surface, so a topic matched there lands on a folder that was never a track.
47
47
 
48
- With no match, derive a kebab-case slug named for the subject rather than the activity. Prefer `ts-migration` over `migration-research`. Then route on `.claude/groundwork/<slug>/`:
48
+ With no match, derive a kebab-case slug named for the subject rather than the activity. Prefer `ts-migration` over `migration-research`. Also list `.claude/intake/` and take `<nn>` as the highest ordinal present across both listings, incremented, per `${CLAUDE_SKILL_DIR}/../../standards/groundwork.md`. Then route on `.claude/groundwork/<nn>-<slug>/`:
49
49
 
50
50
  - Folder absent: open
51
51
  - Folder present without `06-decision.md`: resume
@@ -72,7 +72,7 @@ The standard sets the open question format and requires it inside a topic file a
72
72
 
73
73
  ## Open mode
74
74
 
75
- 1. Create `.claude/groundwork/<slug>/`.
75
+ 1. Create `.claude/groundwork/<nn>-<slug>/`, with `<nn>` and `<slug>` as derived in Step 1.
76
76
  2. Write `README.md` first. Writing it first forces the question of what the track is for.
77
77
  3. Write `01-current-state.md` by measuring now. Never carry a figure from a previous session or from recall without re-measuring it. Measure only what an open question in the folder needs. A number with no question attached is how groundwork turns into the work.
78
78
  4. Write `00-scope.md` when the track is large enough to run away. Skip it on a small track.
@@ -103,12 +103,12 @@ Emit the full relative path from the project root for every file written or upda
103
103
  Open and resume:
104
104
 
105
105
  ```plaintext
106
- 📂 Opened .claude/groundwork/<slug>/
106
+ 📂 Opened .claude/groundwork/<nn>-<slug>/
107
107
 
108
108
  **Written:**
109
109
 
110
- - `.claude/groundwork/<slug>/README.md`
111
- - `.claude/groundwork/<slug>/01-current-state.md`
110
+ - `.claude/groundwork/<nn>-<slug>/README.md`
111
+ - `.claude/groundwork/<nn>-<slug>/01-current-state.md`
112
112
 
113
113
  **Open questions:**
114
114
 
@@ -122,12 +122,12 @@ Use `📂 Resumed` in place of `📂 Opened` on a resume pass.
122
122
  Close:
123
123
 
124
124
  ```plaintext
125
- ✅ Closed .claude/groundwork/<slug>/
125
+ ✅ Closed .claude/groundwork/<nn>-<slug>/
126
126
 
127
127
  **Written:**
128
128
 
129
- - `.claude/groundwork/<slug>/06-decision.md`
130
- - `.claude/groundwork/<slug>/07-next-session.md`
129
+ - `.claude/groundwork/<nn>-<slug>/06-decision.md`
130
+ - `.claude/groundwork/<nn>-<slug>/07-next-session.md`
131
131
 
132
132
  **Uncited external claims:** <count, or none>
133
133
 
@@ -22,6 +22,7 @@ A session with no numbering convention re-decides the folder shape per dump, so
22
22
  - Close every item with a verdict, and pair every open question with a suggested pick
23
23
  - Treat an empty operator slot as unread rather than as agreement
24
24
  - Reserve the index number and carry the domain in every other filename, leaving the rest of the numbering as read order
25
+ - Name a newly opened folder with a two-digit ordinal ahead of the slug, taken from the highest one already present across intake and groundwork
25
26
  - Keep answers on items alone, with the index pointing at them
26
27
  - Name the heading and the act beside every path the pass wrote
27
28
  - Confine writes to the intake folder
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: claude-intake
3
- description: Files a raw brain dump into a numbered intake folder under `.claude/intake/<slug>/`, one item per finding carrying a measured problem, a proposed fix, and a verdict. Use when asked to "file this dump", "triage my notes", "work through this list", "sort out this brain dump", or "run an intake pass". Do NOT use for one question that has to be measured before anyone can plan it. That is `claude-groundwork`.
3
+ description: Files a raw brain dump into a numbered intake folder under `.claude/intake/<nn>-<slug>/`, one item per finding carrying a measured problem, a proposed fix, and a verdict. Use when asked to "file this dump", "triage my notes", "work through this list", "sort out this brain dump", or "run an intake pass". Do NOT use for one question that has to be measured before anyone can plan it. That is `claude-groundwork`.
4
4
  ---
5
5
 
6
6
  # Claude intake
@@ -29,7 +29,7 @@ Using the wrong one fails in two shapes. Intake on a question that needs measuri
29
29
 
30
30
  ## Write scope
31
31
 
32
- - Write only inside `.claude/intake/<slug>/`. A plan file, a task file, a source change, a standard, and a rule all live outside that folder, so this one rule forbids every one of them.
32
+ - Write only inside `.claude/intake/<nn>-<slug>/`. A plan file, a task file, a source change, a standard, and a rule all live outside that folder, so this one rule forbids every one of them.
33
33
  - There is no exception. Promoting an item onto the board runs through `claude-tasks` after the operator has answered, which is a separate invocation.
34
34
  - Reading is unrestricted inside the project. Measuring is the work.
35
35
  - Treat the folder as gitignored and unbacked. No check reaches its contents, so every rule stated here holds only while a session reads it.
@@ -38,11 +38,11 @@ Nothing outside this body carries the write-scope floor, and no path-scoped rule
38
38
 
39
39
  ## Step 1: detect open or resume
40
40
 
41
- List `.claude/intake/` from the project root and match the topic against the folders already there before deriving a slug. A second pass over the same subject rarely phrases the topic the way the folder was named, so a fresh slug would open a duplicate beside a live folder.
41
+ List `.claude/intake/` from the project root and match the topic against the slug half of each `<nn>-<slug>` folder already there before deriving a slug. A second pass over the same subject rarely phrases the topic the way the folder was named, so a fresh slug would open a duplicate beside a live folder.
42
42
 
43
43
  Never match against `.claude/` itself. That directory holds every other workflow surface, so a topic matched there lands on a folder that was never an intake.
44
44
 
45
- With no match, derive a kebab-case slug named for the subject rather than the activity. Prefer `toolkit-overview` over `august-triage`. An absent folder opens, and a present one resumes by appending items and revising verdicts the tree has moved under.
45
+ With no match, derive a kebab-case slug named for the subject rather than the activity. Prefer `toolkit-overview` over `august-triage`. Also list `.claude/groundwork/` and take `<nn>` as the highest ordinal present across both listings, incremented, per `${CLAUDE_SKILL_DIR}/../../standards/intake.md`. An absent folder opens, and a present one resumes by appending items and revising verdicts the tree has moved under.
46
46
 
47
47
  ## Step 2: orient
48
48
 
@@ -90,12 +90,12 @@ Emit the full relative path from the project root for every file written, and na
90
90
  A file the pass only read gets no line, which is what keeps the block short.
91
91
 
92
92
  ```plaintext
93
- 📂 Opened .claude/intake/<slug>/
93
+ 📂 Opened .claude/intake/<nn>-<slug>/
94
94
 
95
95
  **Filed:**
96
96
 
97
- - `.claude/intake/<slug>/05-coverage.md` gains items 6 to 8 under a new `## What the merge gate covers`
98
- - `.claude/intake/<slug>/00-overview.md` cluster rows and verdict counts updated
97
+ - `.claude/intake/<nn>-<slug>/05-coverage.md` gains items 6 to 8 under a new `## What the merge gate covers`
98
+ - `.claude/intake/<nn>-<slug>/00-overview.md` cluster rows and verdict counts updated
99
99
 
100
100
  **Routing:** <N> plan-ready, <N> groundwork candidates, <N> already settled
101
101
 
@@ -16,6 +16,15 @@ is left alone regardless of its name, since that subfolder is project-authored
16
16
  by location. It also removes a stale `.claude/GOV.md`
17
17
  from the retired build. Use `aitk gov install` to add rules.
18
18
 
19
+ When the target's install recorded a stack, `aitk gov sync` also reports a
20
+ rule that stack lists and `.claude/rules/` does not hold, as a `missing` entry
21
+ carrying no change. This is what makes a target whose recorded sync point
22
+ postdates a rule joining its stack still see that rule: the report reads the
23
+ target's current entitlement against its current tree rather than diffing
24
+ from an anchor a later sync could advance past the rule's own commit. A
25
+ target whose install predates the recorded chain falls back to the same
26
+ band-inference `newRules` uses in `aitk sync --check`.
27
+
19
28
  There is no `aitk standards sync` and no `aitk standards install`. The corpus
20
29
  installs into no project, so the domain has nothing in a target to reconcile.
21
30
  `aitk standards <name>` prints one, resolving `standards/` at the working root
@@ -94,16 +103,23 @@ It also refuses a target whose working tree is dirty, so commit or stash first.
94
103
  and is safe to run at any time. Each file is classified as `stale` when it still
95
104
  matches what the toolkit installed, `customized` when the project edited it,
96
105
  `stranded` when it sits at a path the toolkit no longer installs to, `orphaned`
97
- when the project authored it, or `drifted` when no stamp covers it.
106
+ when the project authored it, or `drifted` when no stamp covers it. Governance
107
+ also reports `missing`, for a rule the target's recorded stack lists that its
108
+ tree does not hold at all.
98
109
 
99
110
  Use `--json` for the machine-readable report and `--exit-code` to fail a CI job. Orphaned
100
- files are excluded from that exit code, since a project-authored rule never
101
- converges. Attribution reads `.claude/aitk/config.json`, which every install and
102
- sync writes.
103
-
104
- A target installed before stamping shipped has no such file, and neither does one
105
- stamped before the file moved into `.claude/aitk/`, which is read as absent
106
- rather than migrated. Both fall back to the toolkit's own git history.
111
+ and missing files are both excluded from that exit code: a project-authored
112
+ rule never converges, and a sync that added a missing one silently changes
113
+ what the project is governed by, which stays a separate command an operator
114
+ chooses to run. Attribution reads `.claude/aitk/config.json`, which every
115
+ install and sync writes.
116
+
117
+ A target installed before stamping shipped has no such file, and neither does
118
+ that fallback do anything to migrate it. A target stamped before the file
119
+ moved into `.claude/aitk/` still carries it at the retired
120
+ `.claude/aitk.json`, and `aitk sync --check` reads that path when the current
121
+ one is absent, reporting it rather than moving it. Only a target carrying
122
+ neither path falls back to the toolkit's own git history.
107
123
  Installed content matching any
108
124
  version that history ever published proves the file is untouched, so it reports
109
125
  `stale` naming the commit it came from, and content matching no published
@@ -207,10 +223,10 @@ file and proposes relocating the lot.
207
223
 
208
224
  #### Rules the target never received
209
225
 
210
- `newRules` names a rule the toolkit authored after this target last synced
211
- governance. A sync refreshes the files a target already holds and adds none, so
212
- without this section a project's rule set freezes at its install date while every
213
- file it does hold reports as current. That is the report's most confident wrong
226
+ `newRules` names a rule the target could receive and its tree does not hold. A
227
+ sync refreshes the files a target already holds and adds none, so without this
228
+ section a project's rule set freezes at its install date while every file it
229
+ does hold reports as current. That is the report's most confident wrong
214
230
  answer, since a clean result reads as a target holding everything the toolkit
215
231
  publishes.
216
232
 
@@ -225,11 +241,22 @@ installs, nothing counts toward `--exit-code`, and a target can read the list an
225
241
  act on none of it. The value stops at an operator reading it, which is the same
226
242
  contract the skills list already sets.
227
243
 
228
- The measurement anchors on governance's own stamp rather than on the oldest
229
- anchor across domains, since rules are domain-scoped and a shared anchor would
230
- let a snippets sync move the revision rules are measured from. A target carrying
231
- no governance anchor reports nothing at all: it has no date to measure against,
232
- and diffing from the start of history would read the whole catalog as new.
244
+ Since `aitk gov install` records the stack it resolved, a target carrying that
245
+ record answers this by comparing its current entitlement against its current
246
+ tree, with no anchor and no git diff involved. That is what lets the section
247
+ name a rule that shipped before the target's last sync: the anchor a sync
248
+ advances plays no part in the read, where an anchor-bound diff can never see a
249
+ rule on the far side of a window a later sync moved past it. The per-file
250
+ `missing` state `aitk gov sync` reports comes from the same comparison, so a
251
+ rule the chain lists reaches both surfaces the same way.
252
+
253
+ A target stamped before governance recorded a chain falls back to the older
254
+ band-inference read below. The measurement there anchors on governance's own
255
+ stamp rather than on the oldest anchor across domains, since rules are
256
+ domain-scoped and a shared anchor would let a snippets sync move the revision
257
+ rules are measured from. A target carrying no chain and no governance anchor
258
+ reports nothing at all: it has no date to measure against, and diffing from the
259
+ start of history would read the whole catalog as new.
233
260
 
234
261
  An anchor this toolkit cannot resolve reports nothing by the same route, and that
235
262
  one is not visible. A stamp naming a revision the running clone has never seen,
@@ -238,22 +265,22 @@ fails the read and yields an empty list rather than a stated absence. It looks
238
265
  identical to a target holding every rule the toolkit publishes. `newSkills`
239
266
  behaves the same way, and neither carries the `historyUnavailable` flag the
240
267
  per-domain scan uses to separate the two. Treat an empty section on a toolkit
241
- that is not a full clone as unmeasured rather than clean.
268
+ that is not a full clone as unmeasured rather than clean. This gap does not
269
+ reach the chain-based read above, since it consults no anchor at all.
242
270
 
243
- Entitlement is filtered, because a stack does not receive every rule. The base
244
- stack takes the `core` and `claude` folders whole and every other stack extends
245
- it with individually named rules, so an unfiltered list would tell a base
246
- consumer about rules it can never receive and train the reader to skip the
247
- section.
271
+ In the fallback, entitlement is filtered, because a stack does not receive
272
+ every rule. The base stack takes the `core` and `claude` folders whole and
273
+ every other stack extends it with individually named rules, so an unfiltered
274
+ list would tell a base consumer about rules it can never receive and train the
275
+ reader to skip the section.
248
276
 
249
277
  The filter accepts a band on either of two grounds. A folder the base stack takes
250
278
  whole is entitled to every target, read from the stack file so a folder added to
251
279
  base later needs no code change. Every other band is read off the folders the
252
- target already carries, since `aitk gov install` records file hashes and never
253
- the stack it resolved, which leaves the installed tree as the only evidence of
254
- what a chain reached. One band can be reached by more than one stack, so the test
255
- over-reports inside a folder the target holds, which costs a line where
256
- under-reporting would cost the section its point.
280
+ target already carries, which is the fallback's only evidence of what a target
281
+ was entitled to before it recorded a chain. One band can be reached by more than
282
+ one stack, so the test over-reports inside a folder the target holds, which
283
+ costs a line where under-reporting would cost the section its point.
257
284
 
258
285
  A rule the target already holds is dropped by name. That is what keeps a rule the
259
286
  toolkit moved between band folders out of the list, since a rename reaches this
@@ -9,6 +9,8 @@ description: Reading intake folder counts and items, the three read states an it
9
9
 
10
10
  `aitk intake list` reports the intake folders under `.claude/intake/`, or the items one folder holds. It reads and never writes, because an answer belongs to the operator and a verb that filled one would decide what the folder exists to ask.
11
11
 
12
+ A folder carries a two-digit ordinal ahead of its slug, as in `21-toolkit-overview`, so a listing sorts by when each opened. A bare slug still resolves: passing `toolkit-overview` matches the one folder whose name is an ordinal ahead of it, and the folder's real name is what every command reports back.
13
+
12
14
  ```bash
13
15
  aitk intake list
14
16
  aitk intake list toolkit-overview --unread --json
@@ -58,7 +60,7 @@ One call writes one cluster. A call per selection is the alternative, and severa
58
60
 
59
61
  An item already carrying an answer refuses rather than being overwritten, and one filled item refuses the whole batch, so a partly applied write never lands. Drop the named item and send the rest.
60
62
 
61
- Exit codes: `0` every named item now carries its answer, `1` refused. The `reason` field carries `no-intake`, `no-folder`, `no-cluster`, `no-item`, `answered`, or `bad-input`.
63
+ Exit codes: `0` every named item now carries its answer, `1` refused. The `reason` field carries `no-intake`, `no-folder`, `ambiguous-slug`, `no-cluster`, `no-item`, `answered`, or `bad-input`. A bare slug matching more than one ordinal-prefixed folder refuses as `ambiguous-slug` rather than `no-folder`, naming every match in `detail`.
62
64
 
63
65
  An exit code says nothing about a call made from a session, since a shell profile may wrap the binary in a function taking its status from a later command. Read the record's `reason` rather than the exit when a skill consumes this, which matters most here because the verb writes.
64
66
 
@@ -44,8 +44,8 @@ One session works for most features. Prefer splitting across two sessions only w
44
44
 
45
45
  Work in Claude Code directly. It reads `CLAUDE.md` automatically and has full file access, no pasting needed.
46
46
 
47
- - When the input is a pile of findings rather than one feature, invoke `aitk:claude-intake` first. It files the dump into `.claude/intake/<slug>/`, one item per finding carrying a problem measured against the tree, a proposed fix, and a verdict, then names which items are plan-ready, which need measuring, and which are already settled.
48
- - When the current state is unmeasured and more than one approach is live, invoke `aitk:claude-groundwork` first. It opens a track folder under `.claude/groundwork/<slug>/` and ends in a decision, which may be to do nothing. Skip it when the approach is already settled.
47
+ - When the input is a pile of findings rather than one feature, invoke `aitk:claude-intake` first. It files the dump into `.claude/intake/<nn>-<slug>/`, one item per finding carrying a problem measured against the tree, a proposed fix, and a verdict, then names which items are plan-ready, which need measuring, and which are already settled.
48
+ - When the current state is unmeasured and more than one approach is live, invoke `aitk:claude-groundwork` first. It opens a track folder under `.claude/groundwork/<nn>-<slug>/` and ends in a decision, which may be to do nothing. Skip it when the approach is already settled.
49
49
  - Invoke `aitk:claude-feature` to scan for code-level conflicts and ambiguities, confirm approach before proceeding
50
50
  - Implement the feature, then Claude Code runs the commands defined in `CLAUDE.md`, fixes failures, and iterates until all pass
51
51
  - For UI changes, invoke `aitk:claude-ui-test` to generate and run Playwright e2e tests
@@ -124,7 +124,7 @@ The report opens by naming the binary running it. The installed version reads ag
124
124
 
125
125
  A `stale` file still matches what the toolkit installed, so the update is mechanical. A `customized` file carries local edits, so taking the upstream version is a decision and `aitk:claude-seed-sync` is the tool for it. A `stranded` file sits where an older toolkit installed it and the toolkit has since moved, which is what `aitk:migration-standards` handles.
126
126
 
127
- That attribution comes from `.claude/aitk/config.json`, a stamp every install and sync writes. Snippets and governance record a hash per installed file. Tooling records the stack chain it resolved instead, since its install runs no per-file walk to attribute.
127
+ That attribution comes from `.claude/aitk/config.json`, a stamp every install and sync writes. A target stamped before that path shipped is read from the retired `.claude/aitk.json` instead, reported rather than migrated. Snippets and governance record a hash per installed file. Governance also records the stack `aitk gov install` was given, and tooling records the stack chain it resolved instead of any file hash, since its install runs no per-file walk to attribute.
128
128
 
129
129
  Each domain holds its own toolkit commit, so syncing governance today does not move the revision snippets measures against, and each domain reports the upstream commits touching its own source path. Running any sync stamps that domain, and the report names the ones still unstamped.
130
130
 
@@ -136,13 +136,15 @@ That third one matters most on an older project. Before it existed, a target hol
136
136
 
137
137
  #### Rules you never received
138
138
 
139
- A sync refreshes the files you already hold and adds none, so your rule set is frozen at the date you installed governance while every file in it reports as current. `newRules` names the rules the toolkit has authored since then, which is the one section that reads your absence rather than your contents.
139
+ A sync refreshes the files you already hold and adds none, so your rule set is frozen at the date you installed governance while every file in it reports as current. `newRules` names a rule you could receive that your tree does not hold, which is the one section that reads your absence rather than your contents. `aitk gov sync` reports the same rules per file, marked `missing`, so you see them either way you check.
140
140
 
141
141
  Take a clean section as reporting rather than as delivering. Nothing here installs, and it counts toward nothing, so pick the rules up with `aitk gov install <stack>` or take one with `--add <rule>`. That separation is deliberate: adopting a rule changes what your project is governed by, which is a choice a sync has no business making for you.
142
142
 
143
- The list is filtered to what your stack can receive, read off the rule folders you already carry plus the folders the base stack takes whole. A rule under `lang/` or `ui/` belongs to some stacks and not others, so an unfiltered list would name rules you can never install. A target that has never stamped governance reports nothing at all, since there is no date to measure against.
143
+ Since `aitk gov install` records the stack you gave it, the list is read by comparing that stack's current rules against what you hold right now, with no date involved at all. That is what lets it name a rule the toolkit shipped before you last synced: nothing here depends on when you installed. A target stamped before this recording shipped falls back to the older read below.
144
144
 
145
- An empty section is not proof either way when the toolkit running the check is not a full git clone. The read needs history to reach your anchor, and an install from the registry ships none, so the section goes quiet rather than saying it could not measure. Run the check from a clone before reading a clean result as a complete one.
145
+ The fallback filters to what your stack can receive, read off the rule folders you already carry plus the folders the base stack takes whole. A rule under `lang/` or `ui/` belongs to some stacks and not others, so an unfiltered list would name rules you can never install. A target with neither a recorded stack nor a governance anchor reports nothing at all, since there is no date to measure against either.
146
+
147
+ In the fallback, an empty section is not proof either way when the toolkit running the check is not a full git clone. The read needs history to reach your anchor, and an install from the registry ships none, so the section goes quiet rather than saying it could not measure. Run the check from a clone before reading a clean result as a complete one. The stack-based read above is untouched by this, since it consults no history.
146
148
 
147
149
  This also closes the case where a rule arrives citing a sibling you do not have. A sync refreshing a rule can land a version pointing at a file it never installs, and the section names the missing sibling rather than the broken citation, which is enough to act on.
148
150
 
@@ -177,7 +179,7 @@ Standards take no part in that run. Nothing installed them, so there is no copy
177
179
  ### Targeted
178
180
 
179
181
  - Claude seed docs such as `CLAUDE.md` and `.claude/REQUIREMENTS.md`: invoke `aitk:claude-seed-sync`. The skill splits each file into a preamble (between the H1 and the first H2) plus one part per `##` section, then diffs part by part and proposes per-part edits. User customizations are preserved.
180
- - Governance rules already installed: `aitk gov sync <path>` diffs and applies, and never adds new rules
182
+ - Governance rules already installed: `aitk gov sync <path>` diffs and applies, and never adds new rules. A rule your recorded stack lists reports as `missing` instead.
181
183
  - Tooling configs and seeds: `aitk tooling <stack> <path>` overwrites golden configs and merges seeds
182
184
  - Reference docs for a stack: `aitk tooling ref <stack> <path>`
183
185
  - Index regeneration after markdown edits: `aitk indexes regen`
@@ -1,11 +1,15 @@
1
1
  ---
2
- description: Route .claude/groundwork/ edits to the groundwork standard for numbering, frontmatter, and required file contents
2
+ description: Route .claude/groundwork/ edits to the groundwork standard for ordinal naming, numbering, frontmatter, and required file contents
3
3
  paths:
4
4
  - '.claude/groundwork/**'
5
5
  ---
6
6
 
7
7
  # Groundwork standards
8
8
 
9
+ ## Folder name
10
+
11
+ - Name a new track `.claude/groundwork/<nn>-<slug>/`, a two-digit zero-padded ordinal followed by a kebab-case slug. Take the ordinal from the highest one already present across both `.claude/groundwork/` and `.claude/intake/`, incremented, or `01` when neither folder holds an entry. Never renumber an existing folder.
12
+
9
13
  ## Measuring and closing
10
14
 
11
15
  - Never carry a figure from an earlier session without re-measuring it. Mark an inference as an inference.
@@ -1,11 +1,15 @@
1
1
  ---
2
- description: Route .claude/intake/ edits to the intake standard for the item format, the answer contract, and numbering
2
+ description: Route .claude/intake/ edits to the intake standard for the item format, the answer contract, and ordinal naming
3
3
  paths:
4
4
  - '.claude/intake/**'
5
5
  ---
6
6
 
7
7
  # Intake standards
8
8
 
9
+ ## Folder name
10
+
11
+ - Name a new dump `.claude/intake/<nn>-<slug>/`, a two-digit zero-padded ordinal followed by a kebab-case slug. Take the ordinal from the highest one already present across both `.claude/intake/` and `.claude/groundwork/`, incremented, or `01` when neither folder holds an entry. Never renumber an existing folder.
12
+
9
13
  ## The answer contract
10
14
 
11
15
  - Never fill a `You:` slot and never infer a disposition from an empty one. Empty means unread, never agreement.
@@ -0,0 +1,18 @@
1
+ ---
2
+ description: Route bash script authoring to the interactive or non-interactive skill, and name the lint gate
3
+ paths:
4
+ - '**/*.sh'
5
+ ---
6
+
7
+ # Bash standards
8
+
9
+ ## Skill routing
10
+
11
+ - Use `bash-script` for an interactive or human-facing script: prompts, a visual timeline UI, framed terminal output.
12
+ - Use `cli-script` for a non-interactive script: automation, CI, cron, a pipeline helper, or anything run by an agent rather than watched by a person.
13
+ - Load the matched skill's own reference templates rather than hand-rolling interactivity or logging patterns outside them.
14
+
15
+ ## Lint gate
16
+
17
+ - Format with `shfmt --write --indent 2` and lint with `shellcheck --severity=warning` before committing a script.
18
+ - Fix a shellcheck finding at the source. Suppress one with a directive comment only for a genuine false positive, and state why beside the suppression.
@@ -0,0 +1,34 @@
1
+ ---
2
+ description: Enforce settled waits and falsifiable guards in end-to-end tests
3
+ paths:
4
+ - 'e2e/*.ts'
5
+ - 'e2e/**/*.ts'
6
+ ---
7
+
8
+ # End-to-end reliability standards
9
+
10
+ ## Waiting
11
+
12
+ - Settle on the condition a step waits for. Use `expect.poll` or a web-first assertion ahead of any read.
13
+ - Reserve a fixed duration for an assertion that nothing happened across a window.
14
+ - Bound every settle with an explicit timeout.
15
+ - Do not raise a timeout to clear a failure that reproduces under load. Replace the wait with a settle.
16
+ - Do not read a value once after a pause. Poll it.
17
+
18
+ ## Falsifiable guards
19
+
20
+ - Assert the set under test is non-empty before asserting a property over its members.
21
+ - Raise from an instrument that was refused rather than returning a value a passing assertion accepts.
22
+ - Run a new guard against the defect it was written for, and see it fail, before trusting it.
23
+ - Do not weaken an assertion to clear a failure. Narrow the wait instead.
24
+
25
+ ## Reproducing a failure that only appears in CI
26
+
27
+ - Reproduce under `Emulation.setCPUThrottlingRate` rather than by rerunning the gate.
28
+ - Read the state the assertion does not: which markers were set, which listeners fired, how far a transition ran.
29
+ - Vary the condition under suspicion deliberately. Do not compare counts across runs that differed in something uncontrolled.
30
+ - Read the check conclusion as its own act. A green diff review reports nothing about the gate.
31
+
32
+ ## Authority
33
+
34
+ - Follow `.claude/rules/lib/300-testing-ts.md` for framework choice, file placement, and test naming.
@@ -0,0 +1,25 @@
1
+ ---
2
+ description: Enforce which specs and which engines an end-to-end run covers at each point in the loop
3
+ paths:
4
+ - 'e2e/*.ts'
5
+ - 'e2e/**/*.ts'
6
+ ---
7
+
8
+ # Test scope standards
9
+
10
+ ## Selecting a run
11
+
12
+ - Narrow an end-to-end run by spec path or by test name. Never narrow it by engine.
13
+ - Run one named test while iterating on a behavior: `bun run test:e2e -- -g '<name>'`.
14
+ - Run one surface while iterating on that surface: `bun run test:e2e -- e2e/<area>.spec.ts`.
15
+ - Run `bun run test:e2e:changed` to select specs from the import graph.
16
+ - Run the whole suite before pushing.
17
+ - Pass `--project` in a local run only to reproduce a failure that engine has already reported. The CI matrix passes it on every job, one engine per leg, which is the gate rather than a narrowed run.
18
+ - Do not add a script that pins a default run to one engine.
19
+
20
+ ## Instruments
21
+
22
+ - Answer a question about the running page with a script against the dev server rather than with the suite.
23
+ - Do not enable `fullyParallel` in `playwright.config.ts`.
24
+ - Follow `.claude/rules/ui/440-surface-capture.md` for capture scope.
25
+ - Follow `.claude/rules/lib/305-e2e-reliability.md` for waits and guards.
@@ -4,6 +4,7 @@ paths:
4
4
  - '**/*.tsx'
5
5
  - '**/*.jsx'
6
6
  - '**/*.html'
7
+ - '**/*.astro'
7
8
  ---
8
9
 
9
10
  # Web security standards
@@ -0,0 +1,19 @@
1
+ ---
2
+ description: Enforce the same-tab default for an outbound link and for a mail link
3
+ paths:
4
+ - '**/*.tsx'
5
+ - '**/*.jsx'
6
+ - '**/*.astro'
7
+ - '**/*.html'
8
+ ---
9
+
10
+ # Link behavior standards
11
+
12
+ ## Default target
13
+
14
+ - Open a link in the same tab. Reserve `target="_blank"` for a destination that would discard in-progress work if it replaced the current page, such as a document a user is midway through elsewhere.
15
+ - Do not add `target="_blank"` as a default for every external link.
16
+
17
+ ## mailto: links
18
+
19
+ - Open a `mailto:` link in the same tab. It hands off to the mail client rather than replacing page content, so a new tab leaves an empty tab behind.
@@ -1,2 +1,2 @@
1
1
  extends = "node"
2
- rules = ["210-astro", "350-security-web", "400-ui", "410-a11y", "430-ux-completeness", "440-surface-capture"]
2
+ rules = ["210-astro", "350-security-web", "400-ui", "410-a11y", "430-ux-completeness", "440-surface-capture", "450-link-behavior"]
@@ -1,4 +1,4 @@
1
1
  extends = ""
2
2
  # Both folders whole. Every rule authored under them ships to a base consumer,
3
3
  # so adding one is a single edit rather than a file and a name here.
4
- rules = ["core", "claude"]
4
+ rules = ["core", "claude", "120-bash"]
@@ -1,2 +1,2 @@
1
1
  extends = "node"
2
- rules = ["200-react", "230-nextjs", "250-tailwind", "300-testing-ts", "310-zod", "350-security-web", "400-ui", "410-a11y", "420-forms", "430-ux-completeness", "440-surface-capture"]
2
+ rules = ["200-react", "230-nextjs", "250-tailwind", "300-testing-ts", "305-e2e-reliability", "306-test-scope", "310-zod", "350-security-web", "400-ui", "410-a11y", "420-forms", "430-ux-completeness", "440-surface-capture", "450-link-behavior"]
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/aitk",
3
3
  "type": "module",
4
- "version": "3.12.0",
4
+ "version": "3.13.0",
5
5
  "description": "Infrastructure and quality tooling for developer workflows",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -35,6 +35,7 @@ import {
35
35
  type TestOrderReport,
36
36
  } from '@/gov/test-order'
37
37
  import { recordStamp, runDomainSync } from '@/sync/engine'
38
+ import { writeChainStamp } from '@/sync/stamp'
38
39
  import { resolveTarget } from '@/target'
39
40
  import {
40
41
  intro,
@@ -757,7 +758,15 @@ async function runInstall(
757
758
 
758
759
  logStep('Installing rules')
759
760
  for (const rel of await installRules(found, resolved)) logAdd(rel)
760
- await recordStamp(createGovAdapter(PROJECT_ROOT), resolved, new Date())
761
+
762
+ const now = new Date()
763
+ await recordStamp(createGovAdapter(PROJECT_ROOT), resolved, now)
764
+ await writeChainStamp(
765
+ resolved,
766
+ { domain: 'governance', toolkitRoot: PROJECT_ROOT },
767
+ [selected],
768
+ now,
769
+ )
761
770
 
762
771
  const { GREEN, NC } = palette(process.stderr)
763
772
  outro()
@@ -99,6 +99,12 @@ function renderCheck(report: CheckReport): void {
99
99
 
100
100
  renderSkew(report)
101
101
 
102
+ if (report.stampAtLegacyPath) {
103
+ logWarn(
104
+ 'Stamp found at the retired .claude/aitk.json. Move it to .claude/aitk/config.json.',
105
+ )
106
+ }
107
+
102
108
  if (!report.managed) {
103
109
  logStep('Not a toolkit project')
104
110
  logWarn('No .claude/ directory and no CLAUDE.md at the target.')
@@ -124,13 +130,20 @@ function renderCheck(report: CheckReport): void {
124
130
 
125
131
  for (const entry of domain.entries) {
126
132
  if (entry.state === 'matching' || entry.state === 'orphaned') continue
127
- logWarn(`${entry.rel} (${entry.state})`)
133
+ logWarn(entry.notice ?? `${entry.rel} (${entry.state})`)
128
134
  }
129
135
 
130
- const { stale, customized, drifted, stranded, orphaned } = domain.counts
136
+ const { stale, customized, drifted, stranded, orphaned, missing } =
137
+ domain.counts
131
138
  if (stale + customized + drifted + stranded === 0) {
132
139
  logInfo(orphaned === 0 ? 'up to date' : `up to date (${orphaned} local)`)
133
140
  }
141
+ // A local file is not a deficiency and a missing rule is, so this never
142
+ // shares the up-to-date line's parenthetical. It prints on its own,
143
+ // regardless of whether anything above needs a sync.
144
+ if (missing > 0) {
145
+ logWarn(`${missing} listed by the stack, not installed`)
146
+ }
134
147
 
135
148
  for (const commit of domain.upstream) {
136
149
  logInfo(`${commit.sha} ${commit.subject}`)
@@ -168,10 +181,10 @@ function renderCheck(report: CheckReport): void {
168
181
  if (report.newRules.length > 0) {
169
182
  logStep('New rules, never installed')
170
183
  for (const name of report.newRules) logWarn(name)
171
- // Names the stack as the reader's to supply, since no target records one
172
- // and `--add` layers onto a resolved stack rather than standing in for it.
184
+ // `install` re-resolves the whole stack rather than adding one rule, so
185
+ // the remedy names both routes rather than assuming the reader wants all.
173
186
  logInfo(
174
- 'Run `aitk gov install <stack>`, naming the stack yourself since no target records it. Add `--add <rule>` to take one.',
187
+ 'Run `aitk gov install <stack>` to take the whole stack again, or `--add <rule>` to take one.',
175
188
  )
176
189
  }
177
190