@erclx/aitk 0.10.0 → 0.12.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.
package/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # aitk
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/@erclx/aitk)](https://www.npmjs.com/package/@erclx/aitk)
4
+ [![CI status](https://img.shields.io/github/actions/workflow/status/erclx/aitk/verify.yml?branch=main)](https://github.com/erclx/aitk/actions/workflows/verify.yml)
5
+ [![License MIT](https://img.shields.io/npm/l/@erclx/aitk)](LICENSE)
6
+
3
7
  One source for your repos' AI conventions. Install once, sync everywhere.
4
8
 
5
9
  ![Adding the aitk marketplace and installing the plugin in Claude Code](assets/install.png)
@@ -39,7 +43,7 @@ Two limits worth knowing before you install. Claude Code is the only agent runti
39
43
 
40
44
  Each domain has a canonical source in this repo and a thin install or sync CLI on your side. The links run to internal narrative, written for someone maintaining the toolkit rather than installing it, so skip them on a first pass.
41
45
 
42
- - [Claude Code plugin](.claude/context/claude-plugin.md): skills for planning, review, docs sync, and the git ship chain
46
+ - [Claude Code plugin](.claude/context/claude-plugin/index.md): skills for planning, review, docs sync, and the git ship chain
43
47
  - [Governance rules](.claude/context/governance.md): Cursor rules and stacks, installable per project
44
48
  - [Standards](.claude/context/standards.md): shared authoring conventions, synced to projects
45
49
  - [Snippets](.claude/context/snippets.md): reusable prompts for Claude and Gemini chat
@@ -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.10.0",
4
+ "version": "0.12.0",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -55,6 +55,7 @@ One file per kind at `.claude/diagrams/<kind>.md`. Write only the files Step 2 s
55
55
  title: <what the entry answers, sentence case>
56
56
  description: <the question it settles and the signal that drives it>
57
57
  category: <the kind, matching the standard>
58
+ verified: 'TODO: never verified'
58
59
  ---
59
60
 
60
61
  # <Same as title>
@@ -71,8 +72,12 @@ category: <the kind, matching the standard>
71
72
  <one to three explanation paragraphs>
72
73
  ````
73
74
 
75
+ Write `verified` as the literal `TODO: never verified` here, quoted so the colon parses. Step 6 replaces it once a render has been read back, so a SHA written beside the diagram would assert a check that has not run yet. The placeholder is what keeps a render-less project conforming, since Step 5 sends a failed render past Step 6 and nothing stamps the entry. Carry an existing entry's current value across untouched when this pass is rewriting one, and let Step 6 replace that instead.
76
+
74
77
  When sources came from a code scan rather than planning prose, lead the explanation with `Source: code.` and add `Fidelity is lower than prose-driven diagrams. Verify against the project's intent.`
75
78
 
79
+ The code paths the explanation cites are what the `claude-docs` sweep watches for staleness, so cite paths that exist and spell them exactly.
80
+
76
81
  Quote node labels containing spaces or special characters with double quotes (`A["Web shell"]`). Avoid parentheses inside labels, they break some renderers. Use `<br/>` for line breaks inside labels.
77
82
 
78
83
  Run the banned-character scan in `.claude/standards/prose.md` against the whole file, including inside mermaid `subgraph` labels and node text, or `${CLAUDE_SKILL_DIR}/../../standards/prose.md` when the project does not have it. A colon also works as a replacement inside a label. The pedagogical voice the diagram standard asks for is a yield on voice alone and buys no exemption from these bans.
@@ -123,6 +128,20 @@ Fix the source and re-render. Stop after two correction passes on an entry.
123
128
 
124
129
  When a defect survives, keep the entry and name the defect in the chat output. A diagram whose author states the flaw is recoverable. A wrong diagram reported as verified is not.
125
130
 
131
+ ### Stamp the marker
132
+
133
+ Stamp every entry that passed. Set `verified` to the short SHA of `HEAD` and today's ISO date, and delete any `stale` key the `claude-docs` sweep left on it:
134
+
135
+ ```bash
136
+ git rev-parse --short HEAD
137
+ ```
138
+
139
+ The result is one line, `<sha> <date>`, as in `73e9a3f8 2026-08-02`.
140
+
141
+ Stamp nothing else. An entry Step 2 left alone was never re-checked, an entry whose defect survived is known wrong, and an entry whose render was skipped was never seen. All three keep the marker they already carry, and Step 7 names the last two. The field means a render was read back and judged correct, so stamping any of these spends the only signal a reader has on a diagram nobody confirmed.
142
+
143
+ A migration pass stamps on the same rule. Conversion writes entries, Step 5 renders them, and this step reads them back, so the marker records the commit the split was checked against rather than the commit the flat file was written at.
144
+
126
145
  ## Step 7: chat output
127
146
 
128
147
  ```plaintext
@@ -9,11 +9,11 @@ description: Updates `.claude/` planning docs to reflect decisions made during t
9
9
 
10
10
  - If no `.claude/` directory exists, stop: `❌ No .claude/ directory found. Run aitk claude init to set up the workflow.`
11
11
 
12
- The bail on a session that changed nothing lives at the end of Step 2, because it needs the diff to decide.
12
+ The skip for a session that changed nothing lives at the end of Step 2, because it needs the diff to decide. It drops the doc rewrite alone. The diff-driven sweeps in Steps 4 and 5 still run.
13
13
 
14
14
  ## Diff baseline
15
15
 
16
- Steps 2, 4, and 7 share one diff on the usable path. An unusable baseline splits them, per the rule below. Resolve the base ref once and reuse it:
16
+ Steps 2, 4, 5, and 7 share one diff on the usable path. An unusable baseline splits them, per the rule below. Resolve the base ref once and reuse it:
17
17
 
18
18
  ```bash
19
19
  git merge-base HEAD origin/main 2>/dev/null || git merge-base HEAD main 2>/dev/null
@@ -32,9 +32,9 @@ An unusable baseline costs only the committed half. `git diff <base> HEAD` is em
32
32
 
33
33
  **Step 2 recovers the committed half.** Read `git log -p -1`, widening to `git log -p -<n>` when the session spans several commits, and read the candidate task files against the working tree. That yields names and content both, which is what lets Step 2 decide on behavior rather than on filenames. A fresh `git init` on `main` with no remote is the ordinary shape of a scaffolded project, so this path carries the evidence rather than covering an edge case.
34
34
 
35
- **Steps 4 and 7 keep the scoped set.** Run them on the working tree and untracked files alone, and skip only when that set comes out empty, each reporting the warning its own step names.
35
+ **Steps 4, 5, and 7 keep the scoped set.** Run them on the working tree and untracked files alone, and skip only when that set comes out empty, each reporting the warning its own step names.
36
36
 
37
- Never substitute the whole tree for a missing baseline, and do not reuse Step 2's commit read in these two for consistency. On a fresh `git init` project the last commit is the scaffold commit, so `git log -p -1` is the whole tree by another route. Step 2 tolerates that because it only reads, and it matches conservatively against outcomes already on the board. These two write, so the same set stubs a wireframe for every uncovered surface in the repository and rewrites every context entry that tree touches.
37
+ Never substitute the whole tree for a missing baseline, and do not reuse Step 2's commit read in these three for consistency. On a fresh `git init` project the last commit is the scaffold commit, so `git log -p -1` is the whole tree by another route. Step 2 tolerates that because it only reads, and it matches conservatively against outcomes already on the board. These three write, so the same set stubs a wireframe for every uncovered surface in the repository, stubs a diagram for every source signal the scaffold introduced, and rewrites every context entry that tree touches.
38
38
 
39
39
  Widening what a step reads is safe. Widening what a step writes is not.
40
40
 
@@ -70,7 +70,17 @@ Keep the match conservative:
70
70
  - Match on the behavior an outcome describes, not on filenames or commit subjects. The path match above only narrowed which task files to open.
71
71
  - Leave an outcome `[ ]` when the diff is ambiguous. An unmarked shipped outcome costs one manual edit, while a wrongly marked one hides work that never happened.
72
72
 
73
- Stop here when the session shows no divergence **and** the diff matches no queued outcome: `✅ No doc updates needed. Session matched the original plan.` Both conditions have to hold. Shipping a queued task exactly as planned is the ordinary case and it reads as no divergence, so a session-only bail would stop the skill before it reaches the marking step.
73
+ Skip Step 3 when the session shows no divergence **and** the diff matches no queued outcome, reporting `✅ No doc updates needed. Session matched the original plan.` Both conditions have to hold. Shipping a queued task exactly as planned is the ordinary case and it reads as no divergence, so a session-only skip would drop the marking step with it.
74
+
75
+ Then run Steps 4 through 8. Step 3 is the only one this skips, because it is the only one driven by the session rather than by the diff or the board. A project with an empty task board making a mechanical change satisfies both conditions above, and stopping here would put an uncovered surface and an uncovered diagram kind out of reach in every such project.
76
+
77
+ Three of the steps that follow write, so each earns the reach separately:
78
+
79
+ - Steps 4 and 5 stub against the diff. These are why the skip is not a stop. A session that changed no docs is exactly when an uncovered surface or diagram kind goes unnoticed.
80
+ - Step 7 rewrites context entries against the diff. The Diff baseline section above already groups it with Steps 4 and 5 as a scoped-set step, so a quiet session is no different from any other for it.
81
+ - Step 8 reads the board rather than the session. Its board-wide scan exists to clear a plan an earlier run stranded, and a run that stops at Step 2 can never reach one.
82
+
83
+ This changes which steps the skill reaches. It does not widen what any of them reads. Steps 4, 5, and 7 still take the same scoped set the Diff baseline section defines, and that section's rule is about the input a write is handed rather than about which writes run.
74
84
 
75
85
  ## Step 3: update
76
86
 
@@ -127,13 +137,59 @@ Output one line per finding:
127
137
 
128
138
  If the sweep finds nothing, skip silently.
129
139
 
130
- ## Step 5: flag diagram staleness
140
+ ## Step 5: diagram staleness sweep
141
+
142
+ Skip this step silently when `.claude/diagrams/` does not exist at `pwd` or holds no entry. An entry is any `*.md` other than `index.md`, so a folder carrying the catalog alone is an empty set. A project that has never run `claude-diagram` is not told on every ship that it has holes. When the baseline is unusable, scope the sweep to the working tree and untracked files, and skip it only when that set is empty, reporting `⚠ No diff to scope against. Skipped the diagram sweep.`
143
+
144
+ This step writes frontmatter and never content. Mermaid bodies and explanation paragraphs are off limits to it. A change that removes a module does not carry the new correct shape of the picture, so rewriting a diagram from it produces a confident wrong diagram, which is worse than the stale one it replaced. The author redraws by running `claude-diagram`.
145
+
146
+ Follow `.claude/standards/diagrams.md` for the marker fields this step writes, or `${CLAUDE_SKILL_DIR}/../../standards/diagrams.md` when the project does not have it.
147
+
148
+ Both findings key on something literally entering or leaving the tree. Anything looser fires on ordinary feature work and rebuilds the ignored warning this sweep replaced.
149
+
150
+ **Contradicted entries.** For each entry, collect the backticked code paths its explanation cites. When a cited path is in the diff as a delete or a rename and no longer exists in the tree, append a `stale` key to that entry's frontmatter naming the path:
151
+
152
+ ```yaml
153
+ stale: 'src/gov/install.ts no longer exists'
154
+ ```
155
+
156
+ Append that key alone. Never edit `verified`, `title`, `description`, or `category`, and never touch the body. When the entry already carries `stale`, extend the existing line rather than adding a second key.
157
+
158
+ **Uncovered kinds.** The standard fixes one source signal per kind. Stub a kind when the diff adds its signal file and no entry covers that kind. The trigger is the signal appearing, never a file under it changing, so a branch editing a component folder that `components.md` already covers produces nothing here.
131
159
 
132
- If `.claude/DIAGRAMS.md` exists at `pwd` and this session edited any source `claude-diagram` reads (planning docs, deploy or infrastructure config, top-level component folders), surface a one-line warning:
160
+ | Signal added by the diff | Kind stubbed when absent |
161
+ | ------------------------------------------------------------------------------------------------------------------ | ------------------------ |
162
+ | `.claude/REQUIREMENTS.md` | `system-context.md` |
163
+ | `.claude/ARCHITECTURE.md` | `components.md` |
164
+ | A deploy or infrastructure config (`Dockerfile`, `.github/workflows/*`, `vercel.json`, `fly.toml`, `compose.yaml`) | `deployment.md` |
133
165
 
134
- `⚠ DIAGRAMS.md may be stale. Run /claude-diagram`
166
+ Leave `request-flow.md` and `data-pipeline.md` out. Neither has a source signal a diff can point at, so a rule covering them would guess at when they went stale.
135
167
 
136
- Do not regenerate inline. The author decides when to re-run the diagram skill. Skip the step silently when `.claude/DIAGRAMS.md` does not exist.
168
+ Write the stub at `.claude/diagrams/<kind>.md`:
169
+
170
+ ```markdown
171
+ ---
172
+ title: <Kind as title case>
173
+ description: 'TODO: name the question this entry settles.'
174
+ category: <the category the standard fixes for this kind>
175
+ verified: 'TODO: never verified'
176
+ ---
177
+
178
+ # <Kind as title case>
179
+
180
+ TODO: draw this. `<signal path>` entered the tree with no entry covering this kind.
181
+
182
+ Run `/claude-diagram <kind>` to replace the stub.
183
+ ```
184
+
185
+ No mermaid fence. An empty stub is visible debt that reaches review through the branch diff, while a generated diagram nobody rendered is invisible debt that reads as verified. A fence here invites the next session to fill it in without a render.
186
+
187
+ Output one line per finding:
188
+
189
+ - `⚠ Diagram stale: .claude/diagrams/<kind>.md cites <path>, which left the tree`
190
+ - `📝 Stubbed: .claude/diagrams/<kind>.md`
191
+
192
+ If the sweep finds nothing, skip silently. An ordinary change that adds no signal and deletes no cited path produces no output at all.
137
193
 
138
194
  ## Step 6: flag CLAUDE.md drift
139
195
 
@@ -202,3 +258,5 @@ Output one line per file updated:
202
258
  If no files were updated and nothing was swept, output:
203
259
 
204
260
  `✅ No changes needed.`
261
+
262
+ Suppress that line when Step 2 already reported no doc updates. It closes the run on its own, and emitting both leaves a quiet session reporting success twice for one outcome.
@@ -7,7 +7,7 @@ description: Creates a new standard file in `standards/` (toolkit repo) or `.cla
7
7
 
8
8
  Read these files in parallel:
9
9
 
10
- - `${CLAUDE_SKILL_DIR}/references/standard.md`: the meta-standard for a standard's shape, frontmatter, and structure
10
+ - `.claude/standards/standard.md` from the project root: the meta-standard for a standard's shape, frontmatter, and structure
11
11
  - `.claude/standards/prose.md` from the project root: prose conventions for all generated text
12
12
 
13
13
  Read a standard from `${CLAUDE_SKILL_DIR}/../../standards/` instead when the project does not have it.
@@ -35,4 +35,4 @@ What each `aitk` sync or install command does to existing files in a target proj
35
35
 
36
36
  ## Source of truth
37
37
 
38
- - Full semantics live in the toolkit's `.claude/context/tooling.md`, `standards.md`, and `claude-plugin.md`. This skill is the target-session summary. When they disagree, the context docs win.
38
+ - Full semantics live in the toolkit's `.claude/context/tooling.md`, `standards.md`, and `claude-plugin/`. This skill is the target-session summary. When they disagree, the context docs win.
package/docs/agents.md CHANGED
@@ -136,6 +136,16 @@ headlessly, so a call that names its stack or category is unchanged.
136
136
  `aitk snippets install`. Both resolve the target before anything else, so a path
137
137
  that does not exist fails rather than being scaffolded.
138
138
 
139
+ `aitk gov regen` is the one governance verb that runs against the toolkit root,
140
+ because the `.claude/rules/` it writes there is produced output rather than an
141
+ operator's working copy. It reads the stack recorded in `internal/governance.toml`,
142
+ installs it alongside anything under `internal/rules/`, and clears the
143
+ destination first so a rule the record stopped naming disappears. It takes
144
+ `--root <path>` and defaults to the toolkit root, prints nothing on success, and
145
+ reports the reason on stderr with exit 1 when the record names a stack or rule
146
+ that does not resolve. `scripts/core/regen-claude-copies.sh` calls it, and the
147
+ Consumed copies stage of `bun run check` asserts the result is committed.
148
+
139
149
  `aitk sync` runs every installed domain sync, then offers to commit the result
140
150
  and open a pull request. Under `AITK_NON_INTERACTIVE=1` it applies the domain
141
151
  syncs and then refuses the git workflow, reporting the branch and commit it
@@ -229,6 +239,8 @@ The record carries every scenario with the arms that declare, plus `totalScenari
229
239
  - `aitk docs list [--json]` lists the downstream catalog: the consumer-facing `docs/` surface plus per-domain narrative from `.claude/context/` when that root is present. Toolkit-internal context entries (`ci`, `development`, `extensions`, `sandbox`) are dropped. From a registry install the context section is absent rather than empty.
230
240
  - `aitk docs <topic>` prints one doc to stdout, resolved by exact name from `docs/` first, then `.claude/context/`. Any doc the install carries is reachable by name, including the toolkit-internal topics the list omits.
231
241
 
242
+ A domain too large for one file splits into `<domain>/` with a generated `index.md`, and both verbs name it by the folder. `aitk docs <domain>` prints that index, which is the catalog routing to the sub-area files, and the listing describes it from the index's `subtitle` where a sibling file supplies `description`. A sibling file wins over a folder of the same name. A folder carrying no `index.md` is absent from both, since a catalog is what makes the sub-areas reachable.
243
+
232
244
  Data prints to stdout and the frame to stderr, so `aitk docs <topic> > out.md` captures clean markdown. With no topic and no verb, `aitk docs` runs `list`. An unknown topic names the available topics on stderr and exits 1.
233
245
 
234
246
  Only a `---` block opening on the first line counts as frontmatter, so a document body carrying horizontal rules emits whole.
@@ -20,12 +20,13 @@ Project docs live in `.claude/` at the project root.
20
20
  ├── ARCHITECTURE.md ← technical design decisions
21
21
  ├── DESIGN.md ← visual intent and token decisions (UI projects)
22
22
  ├── WIREFRAMES.md ← ASCII wireframes: layout, UI copy, and interaction rules (UI projects)
23
+ ├── diagrams/ ← one Mermaid entry per diagram kind with a generated index.md
23
24
  ├── tasks/ ← one file per task with a generated index.md, gitignored local scratch
24
25
  ├── context/ ← per-domain narrative loaded on demand via index.md
25
26
  └── rules/ ← path-scoped governance rules, written by aitk gov install
26
27
  ```
27
28
 
28
- Three tiers of context load with different cost: always-loaded (root `CLAUDE.md`, `.claude/REQUIREMENTS.md`, `.claude/ARCHITECTURE.md`), path-scoped lazy (`.claude/rules/<scope>.md` with `paths:` glob), and on-demand lookup (`.claude/context/<domain>.md` discovered via `.claude/context/index.md`). See [the context model](../.claude/context/context-model.md) for the full picture.
29
+ Three tiers of context load with different cost: always-loaded (root `CLAUDE.md`, `.claude/REQUIREMENTS.md`, `.claude/ARCHITECTURE.md`), path-scoped lazy (`.claude/rules/<scope>.md` with `paths:` glob), and on-demand lookup (`.claude/context/<domain>.md`, or `.claude/context/<domain>/` once a domain outgrows one file, discovered via `.claude/context/index.md`). See [the context model](../.claude/context/context-model.md) for the full picture.
29
30
 
30
31
  Run `aitk init` to seed the `.claude/` directory, a root `CLAUDE.md` file, and `.claude/rules/` in one pass. `aitk init` chains claude init and governance install. Claude Code auto-loads every file in `.claude/rules/` at session start, applying always-on rules unconditionally and path-scoped rules to files matching their `paths:` glob.
31
32
 
@@ -103,4 +103,4 @@ port-collision detail.
103
103
 
104
104
  - [Claude Code and git worktrees](../wiki/claude-worktrees.md) for the isolation and fan-out mechanics
105
105
  - [Claude Code subagents](../wiki/claude-subagents.md) for in-session parallelism without worktrees
106
- - `.claude/context/claude-plugin.md` for how the skills in the loop are categorized
106
+ - `.claude/context/claude-plugin/skills.md` for how the skills in the loop are categorized
@@ -57,7 +57,7 @@ Scaffold installs tooling and seeds. It does not fill the planning docs or the d
57
57
 
58
58
  1. Fill `.claude/REQUIREMENTS.md` and `.claude/ARCHITECTURE.md`. The seed provides the files, the scope and decisions are yours to write.
59
59
  2. For a UI project, invoke `aitk:claude-design-propose` to draft `.claude/DESIGN.md` from the requirements and a `## Personality` section. Skip for non-UI projects.
60
- 3. Optionally invoke `aitk:claude-diagram` to draft entries under `.claude/diagrams/` from the architecture and the requirements. One file per diagram kind, so a later refresh of one kind leaves the others untouched. It renders each diagram it writes to verify the layout, which downloads the Mermaid CLI on first use and takes about 15 seconds. A machine without a renderer still gets the diagrams and is told which check was skipped.
60
+ 3. Optionally invoke `aitk:claude-diagram` to draft entries under `.claude/diagrams/` from the architecture and the requirements. One file per diagram kind, so a later refresh of one kind leaves the others untouched. It renders each diagram it writes to verify the layout, which downloads the Mermaid CLI on first use and takes about 15 seconds. A machine without a renderer still gets the diagrams and is told which check was skipped. Each entry records the commit and date it was last verified against, and `aitk:claude-docs` maintains that record on every ship: it annotates an entry whose cited code path left the tree and stubs a kind whose source signal arrived uncovered. The sweep writes frontmatter only, so a diagram's picture and prose change when you redraw them and at no other time.
61
61
  4. Start the feature loop. See [AI workflow](ai-workflow.md) for the per-feature sequence.
62
62
 
63
63
  ### Stack decision
@@ -179,5 +179,5 @@ Sync also refuses a target whose working tree is dirty, so commit or stash befor
179
179
 
180
180
  - [agents](agents.md): CLI flags, exit codes, and JSON output shapes
181
181
  - [AI workflow](ai-workflow.md): feature-development loop inside a toolkit-managed project
182
- - [tooling](../.claude/context/tooling.md), [governance](../.claude/context/governance.md), [claude plugin](../.claude/context/claude-plugin.md), [indexes](../.claude/context/indexes.md), [snippets](../.claude/context/snippets.md), [standards](../.claude/context/standards.md): per-domain mechanics
182
+ - [tooling](../.claude/context/tooling.md), [governance](../.claude/context/governance.md), [claude plugin](../.claude/context/claude-plugin/index.md), [indexes](../.claude/context/indexes.md), [snippets](../.claude/context/snippets.md), [standards](../.claude/context/standards.md): per-domain mechanics
183
183
  - [sandbox](../.claude/context/sandbox.md): scenario catalog for verifying domain flows
@@ -130,7 +130,7 @@ Zero or one yes: tier 0. Two or three: tier 1. Four: tier 2. Resist over-tiering
130
130
 
131
131
  - [Stitch](../wiki/stitch.md): Gemini-powered design product with the MCP server that anchors tier 1
132
132
  - [Claude Design](../wiki/claude-design.md): first-party hosted design product and handoff bundle
133
- - `.claude/context/claude-plugin.md`: how to decide between workflow and domain-knowledge skills
133
+ - `.claude/context/claude-plugin/skills.md`: how to decide between workflow and domain-knowledge skills
134
134
  - [Visual wireframes](../wiki/visual-wireframes.md): Excalidraw research and setup for the tier 1 wireframe companion
135
135
  - [Community skills and plugins](../wiki/community-skills.md): catalog of frontend design skills and integrations
136
136
  - [Community MCP servers](../wiki/community-mcp-servers.md): catalog of MCPs referenced across all tiers
@@ -8,4 +8,4 @@ paths:
8
8
 
9
9
  ## Authority
10
10
 
11
- - Follow `.claude/standards/readme.md` for structure, required and optional sections, heading hierarchy, and what to link out rather than inline. It is the single source.
11
+ - Follow `.claude/standards/readme.md` for voice, structure, required and optional sections, heading hierarchy, badge classes, per-project-type content, and what to link out rather than inline. It is the single source.
@@ -0,0 +1,13 @@
1
+ ---
2
+ description: Enforce shape, scope, and success criterion for authoring convention files
3
+ paths:
4
+ - '.claude/standards/**/*.md'
5
+ - 'standards/**/*.md'
6
+ ---
7
+
8
+ # Standard authoring standards
9
+
10
+ ## Authority
11
+
12
+ - Follow `.claude/standards/standard.md` for a standard's frontmatter, shape, scope, and success criterion. It is the single source.
13
+ - Read it before writing or editing a standard. Do not work the shape from memory.
@@ -1,2 +1,2 @@
1
1
  extends = ""
2
- rules = ["000-constitution", "010-testing", "020-concurrency", "030-error-handling", "040-performance", "050-logging", "060-naming", "070-planning", "080-config-comments", "500-prose", "510-context", "520-wireframes", "530-requirements", "540-architecture", "550-design", "555-tasks", "560-diagrams", "570-skill", "580-readme", "590-rule-authoring"]
2
+ rules = ["000-constitution", "010-testing", "020-concurrency", "030-error-handling", "040-performance", "050-logging", "060-naming", "070-planning", "080-config-comments", "500-prose", "510-context", "520-wireframes", "530-requirements", "540-architecture", "550-design", "555-tasks", "560-diagrams", "570-skill", "580-readme", "590-rule-authoring", "591-standard-authoring"]
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/aitk",
3
3
  "type": "module",
4
- "version": "0.10.0",
4
+ "version": "0.12.0",
5
5
  "description": "Infrastructure and quality tooling for developer workflows",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -23,4 +23,13 @@ mirror_dir "$PROJECT_ROOT/snippets" "$PROJECT_ROOT/.claude/snippets" -name "*.md
23
23
 
24
24
  # `internal/` is the surface the plugin does not symlink. Mirrored on its own so
25
25
  # toolkit sessions read it at a `.claude/` path like every other consumed copy.
26
- mirror_dir "$PROJECT_ROOT/internal" "$PROJECT_ROOT/.claude/internal" -name "*.md"
26
+ # `internal/rules/` is excluded because it lands in `.claude/rules/` below, and
27
+ # mirroring it here too would publish each rule at a second inert path. The
28
+ # exclusion is anchored to that one folder, since an unanchored `*/rules/*` would
29
+ # also drop a later `internal/standards/rules/` and report nothing for it.
30
+ mirror_dir "$PROJECT_ROOT/internal" "$PROJECT_ROOT/.claude/internal" -name "*.md" -not -path "$PROJECT_ROOT/internal/rules/*"
31
+
32
+ # `.claude/rules/` is a subset rather than a mirror, so it resolves through the
33
+ # stack machinery instead of a fourth `mirror_dir` call. The record naming the
34
+ # subset is `internal/governance.toml`.
35
+ bun "$PROJECT_ROOT/src/cli.ts" gov regen --root "$PROJECT_ROOT"
@@ -132,7 +132,7 @@ main() {
132
132
 
133
133
  log_step "Consumed copies"
134
134
  run_check "bash $PROJECT_ROOT/scripts/core/regen-claude-copies.sh" "Consumed-copy regen failed"
135
- assert_no_drift ".claude/standards .claude/snippets .claude/internal" "Consumed copies drifted. Run bun run check and commit .claude/standards, .claude/snippets, and .claude/internal."
135
+ assert_no_drift ".claude/standards .claude/snippets .claude/internal .claude/rules" "Consumed copies drifted. Run bun run check and commit .claude/standards, .claude/snippets, .claude/internal, and .claude/rules."
136
136
  log_info "Consumed copies clean"
137
137
 
138
138
  log_step "Skill references"
@@ -38,8 +38,33 @@ is_internal_topic() {
38
38
  esac
39
39
  }
40
40
 
41
+ # Emits `name<TAB>description<TAB>target` per context entry, sorted so a domain
42
+ # split into a folder lands in its alphabetical place rather than after every
43
+ # file. Matches listTopics in src/docs/read.ts, which sorts both together.
44
+ collect_context() {
45
+ local file name description
46
+ {
47
+ while IFS= read -r file; do
48
+ name=$(basename "$file" .md)
49
+ [ "$name" = "index" ] && continue
50
+ is_internal_topic "$name" && continue
51
+ description=$(read_frontmatter_field "$file" "description")
52
+ printf '%s\t%s\t%s\n' "$name" "$description" ".claude/context/$name.md"
53
+ done < <(find "$CONTEXT_DIR" -maxdepth 1 -type f -name "*.md")
54
+
55
+ # A split domain is named by its folder and described by its generated
56
+ # index, which carries subtitle where a sibling file carries description
57
+ while IFS= read -r file; do
58
+ name=$(basename "$(dirname "$file")")
59
+ is_internal_topic "$name" && continue
60
+ description=$(read_frontmatter_field "$file" "subtitle")
61
+ printf '%s\t%s\t%s\n' "$name" "$description" ".claude/context/$name/index.md"
62
+ done < <(find "$CONTEXT_DIR" -mindepth 2 -maxdepth 2 -type f -name "index.md")
63
+ } | sort
64
+ }
65
+
41
66
  list_text() {
42
- local file name description category
67
+ local file name description category target
43
68
  log_step "Docs"
44
69
  while IFS= read -r file; do
45
70
  name=$(basename "$file" .md)
@@ -53,13 +78,9 @@ list_text() {
53
78
  # Absent in a registry install, which ships docs/ without .claude/
54
79
  if [ -d "$CONTEXT_DIR" ]; then
55
80
  log_step "Domain context"
56
- while IFS= read -r file; do
57
- name=$(basename "$file" .md)
58
- [ "$name" = "index" ] && continue
59
- is_internal_topic "$name" && continue
60
- description=$(read_frontmatter_field "$file" "description")
81
+ while IFS=$'\t' read -r name description target; do
61
82
  log_info "$name : $description"
62
- done < <(find "$CONTEXT_DIR" -maxdepth 1 -type f -name "*.md" | sort)
83
+ done < <(collect_context)
63
84
  fi
64
85
  }
65
86
 
@@ -76,7 +97,7 @@ emit_json_entry() {
76
97
  }
77
98
 
78
99
  list_json() {
79
- local file name description category
100
+ local file name description category target
80
101
  JSON_FIRST=1
81
102
  printf '['
82
103
  while IFS= read -r file; do
@@ -89,13 +110,9 @@ list_json() {
89
110
  done < <(find "$DOCS_DIR" -maxdepth 1 -type f -name "*.md" | sort)
90
111
 
91
112
  if [ -d "$CONTEXT_DIR" ]; then
92
- while IFS= read -r file; do
93
- name=$(basename "$file" .md)
94
- [ "$name" = "index" ] && continue
95
- is_internal_topic "$name" && continue
96
- description=$(read_frontmatter_field "$file" "description")
97
- emit_json_entry "$name" "$description" "" ".claude/context/$(basename "$file")"
98
- done < <(find "$CONTEXT_DIR" -maxdepth 1 -type f -name "*.md" | sort)
113
+ while IFS=$'\t' read -r name description target; do
114
+ emit_json_entry "$name" "$description" "" "$target"
115
+ done < <(collect_context)
99
116
  fi
100
117
  printf ']'
101
118
  }
@@ -5,6 +5,7 @@ import type { Command } from 'commander'
5
5
  import { registerPassThroughVerbs } from '@/commands/pass-through'
6
6
  import { PROJECT_ROOT } from '@/exec'
7
7
  import { createGovAdapter } from '@/gov/adapter'
8
+ import { regenConsumedRules } from '@/gov/consumed'
8
9
  import { hasStandards, installRules, lookupRules } from '@/gov/install'
9
10
  import { buildRulesPayload, listRuleFiles } from '@/gov/payload'
10
11
  import {
@@ -39,6 +40,10 @@ interface InstallOptions {
39
40
  readonly add?: string
40
41
  }
41
42
 
43
+ interface RegenOptions {
44
+ readonly root?: string
45
+ }
46
+
42
47
  export function register(program: Command): void {
43
48
  const gov = program
44
49
  .command('gov')
@@ -95,9 +100,44 @@ export function register(program: Command): void {
95
100
  process.exitCode = await runBuild(target)
96
101
  })
97
102
 
103
+ gov
104
+ .command('regen')
105
+ .description("Rebuild a repository's own .claude/rules/ from its record")
106
+ .helpOption('-h, --help', 'Show this help message')
107
+ .option('--root <path>', 'Repository root to regenerate', PROJECT_ROOT)
108
+ .addHelpText(
109
+ 'after',
110
+ [
111
+ '',
112
+ 'Reads internal/governance.toml and installs the stack it names, plus',
113
+ 'any rules under internal/rules/. Unlike install and sync, this runs',
114
+ 'against the toolkit root, whose .claude/rules/ is produced output.',
115
+ '',
116
+ ].join('\n'),
117
+ )
118
+ .action(async (opts: RegenOptions) => {
119
+ process.exitCode = await runRegen(opts)
120
+ })
121
+
98
122
  registerPassThroughVerbs(gov, 'gov', PASS_THROUGH_VERBS)
99
123
  }
100
124
 
125
+ /**
126
+ * Silent on success so the consumed-copy stage that calls it stays as quiet as
127
+ * the three `mirror_dir` lines it sits beside. The installed set is readable on
128
+ * disk, so printing it would only add noise to every `bun run check`.
129
+ */
130
+ async function runRegen(opts: RegenOptions): Promise<number> {
131
+ const result = await regenConsumedRules(resolve(opts.root ?? PROJECT_ROOT))
132
+
133
+ if (!result.ok) {
134
+ process.stderr.write(`Consumed-rules regen failed: ${result.reason}\n`)
135
+ return 1
136
+ }
137
+
138
+ return 0
139
+ }
140
+
101
141
  /**
102
142
  * Renders the target-relative path the prompt quotes, keeping the argument the
103
143
  * caller typed rather than the absolute path it resolves to.
package/src/docs/read.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { existsSync, readFileSync } from 'node:fs'
2
- import { basename, join } from 'node:path'
2
+ import { basename, dirname, join } from 'node:path'
3
3
  import { stripFrontmatter } from '@/frontmatter'
4
4
 
5
5
  const INDEX_TOPIC = 'index'
@@ -16,14 +16,25 @@ export interface ResolvedTopic {
16
16
  readonly rel: string
17
17
  }
18
18
 
19
+ /**
20
+ * A domain too large for one file splits into `<domain>/` with a generated
21
+ * `index.md`, so a topic names either a sibling file or such a folder. Both
22
+ * spellings resolve to the same name a caller types.
23
+ */
19
24
  export function resolveTopic(
20
25
  root: string,
21
26
  topic: string,
22
27
  ): ResolvedTopic | undefined {
23
28
  for (const dir of ROOTS) {
24
- const rel = join(dir, `${topic}.md`)
25
- const path = join(root, rel)
26
- if (existsSync(path)) return { path, rel }
29
+ const candidates = [
30
+ join(dir, `${topic}.md`),
31
+ join(dir, topic, `${INDEX_TOPIC}.md`),
32
+ ]
33
+
34
+ for (const rel of candidates) {
35
+ const path = join(root, rel)
36
+ if (existsSync(path)) return { path, rel }
37
+ }
27
38
  }
28
39
 
29
40
  return undefined
@@ -41,14 +52,21 @@ export function listTopics(root: string): string[] {
41
52
  const cwd = join(root, dir)
42
53
  if (!existsSync(cwd)) continue
43
54
 
44
- const names = [
55
+ const files = [
45
56
  ...new Bun.Glob('*.md').scanSync({ cwd, onlyFiles: true, dot: true }),
46
57
  ]
47
58
  .map((name) => basename(name, '.md'))
48
59
  .filter((name) => name !== INDEX_TOPIC)
49
- .sort()
50
60
 
51
- topics.push(...names)
61
+ const folders = [
62
+ ...new Bun.Glob(`*/${INDEX_TOPIC}.md`).scanSync({
63
+ cwd,
64
+ onlyFiles: true,
65
+ dot: true,
66
+ }),
67
+ ].map((name) => dirname(name))
68
+
69
+ topics.push(...[...files, ...folders].sort())
52
70
  }
53
71
 
54
72
  return topics
@@ -0,0 +1,129 @@
1
+ import { existsSync, readFileSync } from 'node:fs'
2
+ import { rm } from 'node:fs/promises'
3
+ import { basename, join } from 'node:path'
4
+ import {
5
+ installRules,
6
+ installedRulesDir,
7
+ lookupRules,
8
+ type RuleSource,
9
+ ruleSubdir,
10
+ } from '@/gov/install'
11
+ import { mergeExtraRules, resolveRules } from '@/gov/stacks'
12
+
13
+ export const RECORD_REL = join('internal', 'governance.toml')
14
+
15
+ const INTERNAL_RULES_REL = join('internal', 'rules')
16
+
17
+ /** The stack a repository installs into its own `.claude/rules/`. */
18
+ export interface ConsumedRecord {
19
+ readonly stack: string
20
+ readonly add: readonly string[]
21
+ }
22
+
23
+ export type ConsumedResult =
24
+ | { readonly ok: true; readonly installed: readonly string[] }
25
+ | { readonly ok: false; readonly reason: string }
26
+
27
+ export function consumedRecordPath(root: string): string {
28
+ return join(root, RECORD_REL)
29
+ }
30
+
31
+ export function internalRulesDir(root: string): string {
32
+ return join(root, INTERNAL_RULES_REL)
33
+ }
34
+
35
+ /**
36
+ * Reads the record naming the consumed stack. A file with no `stack` reads the
37
+ * same as no file at all, since neither tells the producer what to install and
38
+ * the caller's message covers both.
39
+ */
40
+ export function readConsumedRecord(root: string): ConsumedRecord | undefined {
41
+ const path = consumedRecordPath(root)
42
+ if (!existsSync(path)) return undefined
43
+
44
+ const parsed = Bun.TOML.parse(readFileSync(path, 'utf8')) as Record<
45
+ string,
46
+ unknown
47
+ >
48
+ const stack = typeof parsed.stack === 'string' ? parsed.stack : ''
49
+ if (stack === '') return undefined
50
+
51
+ return {
52
+ stack,
53
+ add: Array.isArray(parsed.add)
54
+ ? parsed.add.filter(
55
+ (rule): rule is string => typeof rule === 'string' && rule !== '',
56
+ )
57
+ : [],
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Lists rules authored under `internal/rules/`. These govern toolkit authoring
63
+ * against paths only this repository has, so they install into the consumed
64
+ * copy without ever entering `governance/rules/`, which ships to targets.
65
+ */
66
+ export function listInternalRules(root: string): RuleSource[] {
67
+ const dir = internalRulesDir(root)
68
+ if (!existsSync(dir)) return []
69
+
70
+ return [...new Bun.Glob('**/*.md').scanSync({ cwd: dir, onlyFiles: true })]
71
+ .sort()
72
+ .map((rel) => {
73
+ const src = join(dir, rel)
74
+ return { rule: basename(rel, '.md'), src, subdir: ruleSubdir(src, dir) }
75
+ })
76
+ }
77
+
78
+ /**
79
+ * Rebuilds a repository's own `.claude/rules/` from its record. Unlike
80
+ * `gov install` and `gov sync`, this runs against the toolkit root on purpose:
81
+ * those two refuse it because a target's rules are the operator's to edit,
82
+ * while this destination is produced output that happens to live beside its
83
+ * source.
84
+ */
85
+ export async function regenConsumedRules(
86
+ root: string,
87
+ ): Promise<ConsumedResult> {
88
+ const record = readConsumedRecord(root)
89
+ if (record === undefined) {
90
+ return { ok: false, reason: `No stack recorded at ${RECORD_REL}` }
91
+ }
92
+
93
+ const resolution = resolveRules(root, record.stack)
94
+ if (!resolution.ok) {
95
+ return { ok: false, reason: `Stack not found: ${resolution.missingStack}` }
96
+ }
97
+
98
+ const { found, missing } = lookupRules(
99
+ root,
100
+ mergeExtraRules(resolution.rules, record.add.join(',')),
101
+ )
102
+ if (missing.length > 0) {
103
+ return { ok: false, reason: `No source for: ${missing.join(', ')}` }
104
+ }
105
+
106
+ const internal = listInternalRules(root)
107
+ const stackRules = new Set(found.map((entry) => entry.rule))
108
+ const shadowed = internal
109
+ .filter((entry) => stackRules.has(entry.rule))
110
+ .map((entry) => entry.rule)
111
+ if (shadowed.length > 0) {
112
+ return {
113
+ ok: false,
114
+ reason: `Internal rules shadow stack rules: ${shadowed.join(', ')}`,
115
+ }
116
+ }
117
+
118
+ // Clearing first is what makes a rule the record stopped naming disappear.
119
+ // Copying over the destination would leave it behind as an unsourced file,
120
+ // which is the state this producer exists to end.
121
+ await rm(installedRulesDir(root), { recursive: true, force: true })
122
+
123
+ const installed = [
124
+ ...(await installRules(found, root)),
125
+ ...(await installRules(internal, root)),
126
+ ]
127
+
128
+ return { ok: true, installed: installed.sort() }
129
+ }
@@ -161,21 +161,41 @@ function renderIndex(
161
161
  }
162
162
  })
163
163
  } else {
164
- for (const entry of entries) {
165
- out += formatEntry(entry)
166
- }
164
+ /**
165
+ * Flat mode sorts sub-catalogs among the files rather than after them. A
166
+ * folder and a file are both one domain to a reader scanning the catalog,
167
+ * so a trailing folder reads as absent from the alphabetical run it belongs
168
+ * in. Grouped mode keeps its own heading below, where a category is the
169
+ * organizing key and alphabetical position carries no meaning.
170
+ */
171
+ const lines = [
172
+ ...entries.map((entry) => ({
173
+ key: entry.name,
174
+ text: formatEntry(entry),
175
+ })),
176
+ ...subCatalogs.map((catalog) => ({
177
+ key: catalog.name,
178
+ text: formatSubCatalog(catalog),
179
+ })),
180
+ ].sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0))
181
+
182
+ for (const line of lines) out += line.text
167
183
  }
168
184
 
169
- if (subCatalogs.length > 0) {
170
- if (hasCategories) out += '\n## Sub-catalogs\n\n'
185
+ if (hasCategories && subCatalogs.length > 0) {
186
+ out += '\n## Sub-catalogs\n\n'
171
187
  for (const catalog of subCatalogs) {
172
- out += `- [${catalog.title}](${catalog.name}/${INDEX_FILE}): ${catalog.subtitle}\n`
188
+ out += formatSubCatalog(catalog)
173
189
  }
174
190
  }
175
191
 
176
192
  return out
177
193
  }
178
194
 
195
+ function formatSubCatalog(catalog: SubCatalog): string {
196
+ return `- [${catalog.title}](${catalog.name}/${INDEX_FILE}): ${catalog.subtitle}\n`
197
+ }
198
+
179
199
  function formatEntry(entry: IndexEntry): string {
180
200
  return `- [${entry.title}](${entry.name}): ${entry.description}\n`
181
201
  }
@@ -90,6 +90,8 @@ Only the `development` entry carries this section. It is not a general-purpose h
90
90
  - Past roughly 150 lines, check two things before adding more: whether the entry still covers a single domain, and whether it has filled with content `ls` or `--help` reproduces. Fix whichever is true rather than trimming to hit a number.
91
91
  - Never cut a `## Decisions` or `## Gotchas` entry to shorten a file. Cut a `## Layout` or `## CLI` section instead.
92
92
  - Split into a folder (`.claude/context/<domain>/<sub-area>.md`) when a domain has three or more sub-areas that do not fit cleanly in one file. That split is the natural ceiling.
93
+ - Keep a split domain's `index.md` generated. The catalog body is rewritten on every regen, so the domain's own overview and layout belong in a sibling file rather than in it, and the `subtitle` is what names the file to start with.
94
+ - Author a catalog that grows a row per shipped thing as a bullet list, never a markdown table. A table pads its columns to a shared width, so one row outgrowing its column reflows every row and turns a one-line edit into a whole-file rewrite that conflicts with any sibling branch. This is what forced the split above.
93
95
 
94
96
  ## Examples
95
97
 
@@ -24,8 +24,14 @@ An entry that fails these is non-conforming regardless of whether it satisfies e
24
24
  - `title` (required): sentence case, names what the entry answers (`System context`, `Request flow`), not what it draws.
25
25
  - `description` (required): one line on which question the entry settles and which source signal drives it.
26
26
  - `category` (required): the diagram kind, one of the five in Entry kinds. It is the grouping field `aitk indexes regen` renders headings from.
27
+ - `verified` (required): the short commit SHA an entry was last checked against and the ISO date of that check, separated by a space (`73e9a3f8 2026-08-02`). A stub nobody has drawn yet carries the literal `TODO: never verified` instead, which is the one other accepted value.
28
+ - `stale` (optional): one line naming what changed under the entry since that check. Absent on an entry nothing has flagged.
27
29
 
28
- All three feed `.claude/diagrams/index.md` when regenerated. The catalog sorts categories alphabetically rather than in narrative order, so an entry cannot rely on its position to introduce another. Each entry names its own starting point, and the catalog's subtitle routes a first-time reader to the system context entry.
30
+ The first three feed `.claude/diagrams/index.md` when regenerated. The catalog sorts categories alphabetically rather than in narrative order, so an entry cannot rely on its position to introduce another. Each entry names its own starting point, and the catalog's subtitle routes a first-time reader to the system context entry.
31
+
32
+ The marker fields reach the catalog through neither route. `aitk indexes regen` reads `title`, `description`, and `category` and ignores every other key, so a marker changes no generated file. A reader picks it up by opening the entry, which is where it sits above the diagram and where anyone deciding whether to trust the picture is already standing.
33
+
34
+ Two writers share the marker and neither touches the other's field. A pass that renders an entry and reads the picture back sets `verified` and clears `stale`. The `claude-docs` sweep appends `stale` and never edits `verified`. Keeping them separate is what lets a reader tell a diagram nobody has checked since the code moved from one that was checked and found correct.
29
35
 
30
36
  ## Entry kinds
31
37
 
@@ -72,7 +78,7 @@ A second entry for one kind takes a suffixed name (`request-flow-admin.md`) and
72
78
 
73
79
  - One to three short paragraphs below each diagram. Plain English and pedagogical.
74
80
  - Lead with what the diagram shows. Follow with why this shape was chosen and what alternative was rejected, when the choice was non-obvious.
75
- - Reference one or two specific code paths the reader can open. Do not enumerate every file.
81
+ - Reference one or two specific code paths the reader can open. Do not enumerate every file. Backticked paths here are also the set the `claude-docs` sweep watches, so a path cited in this section is one a later session gets told about when it leaves the tree.
76
82
  - Do not duplicate prose across entries. An entry that restates its neighbor has taken the neighbor's job.
77
83
  - The audience is mixed, so vocabulary runs as a gradient across the set. `System context` assumes no knowledge of the repository. `Deployment` may assume the reader has read the others.
78
84
 
@@ -101,5 +107,7 @@ Reference the context entry by path when a reader needs the mechanism. The diagr
101
107
  - When the system changes, update the entries whose source signal changed and leave the rest alone. Rewriting the folder wholesale reproduces the defect the per-kind split exists to end.
102
108
  - A diagram showing a defunct host or library is worse than no diagram. Audit the affected entry in the same PR.
103
109
  - `System context` has no named source signal beyond `.claude/REQUIREMENTS.md`, so nothing tells a session it went stale. Re-read it when the boundary or the set of external dependencies moves.
110
+ - The `claude-docs` sweep watches two things and writes frontmatter only. It appends `stale` when a path an entry cites leaves the tree, and it stubs a kind when a diff adds the source signal that kind is drawn from. Diagram bodies and explanation paragraphs are off limits to it, because a change that removes a module does not carry the new correct shape of the picture.
111
+ - That watch samples thinly. It sees the one or two paths an entry happened to cite and nothing else, so a change elsewhere leaves the entry looking current. `verified` is what covers the gap, and an entry whose date sits far behind the branch is due a read whether or not anything flagged it.
104
112
  - Mermaid blocks are inside fenced code, so the prose-standards hook ignores them. The explanation paragraphs around them are still prose. Follow `standards/prose.md`.
105
113
  - The punctuation bans still apply to node and subgraph labels, and nothing checks them there. An em dash in a label passes every gate the repository has, so read the labels before shipping the entry.
@@ -16,6 +16,7 @@ Reference docs for consistent authoring across the toolkit and target projects.
16
16
  - [Requirements reference](requirements.md): Shape and content rules for .claude/REQUIREMENTS.md
17
17
  - [Governance rule reference](rule.md): Rule frontmatter, body shape, and voice for .claude/rules files
18
18
  - [Claude skill reference](skill.md): Claude skill structure and authoring rules
19
+ - [Standard reference](standard.md): Shape and content rules for authoring a standard
19
20
  - [Tasks reference](tasks.md): Folder layout, filename convention, and content rules for .claude/tasks/
20
21
  - [Versioning reference](versioning.md): Phase label vs semver discipline across tasks, PRs, commits, and tags
21
22
  - [Wireframe reference](wireframes.md): Shape and content rules for .claude/wireframes/<surface>.md files
@@ -36,6 +36,23 @@ Scoped to the README at a repository root. A nested README documenting a folder,
36
36
  - Do not include detailed contribution guidelines. Reference `CONTRIBUTING.md`.
37
37
  - Do not include extensive troubleshooting guides. Use a wiki or separate documentation.
38
38
 
39
+ ### Badges
40
+
41
+ A badge earns its place by carrying a fact the reader cannot get from the page they are already on. Three classes pass that test.
42
+
43
+ - Package version, which reports what the registry actually published rather than what the working tree claims
44
+ - Build or CI status, which reports whether the checks are currently passing
45
+ - License, which passes because a README rendered on a registry page has no host sidebar stating it
46
+
47
+ Stars, forks, download counts, language chips, and "made with" badges fail. The first three restate what a repository host already renders above the README, and the rest decorate.
48
+
49
+ - Cap the block at four. A fifth costs more scanning than any badge repays.
50
+ - Give each badge alt text naming what it reports rather than the service that renders it. Write `npm version`, not `shields.io`.
51
+ - Link each badge to the page a doubting reader would check next, which is the registry listing for a version badge and the run history for a status badge. A red badge with no route to the failing run reports a problem and withholds the only thing the reader wants.
52
+ - Pin a status badge to the branch whose health it claims to report, and confirm the workflow actually runs on that branch. A workflow triggered by pull requests alone never runs on the default branch, so an unpinned badge reports whichever branch ran last and a pinned one reports nothing at all. Fix the trigger, or drop the badge under the rule below.
53
+ - Verify a badge by the value it renders, not by its status code. A badge service answers 200 for a query it cannot satisfy, so a blank badge and a working one look identical to a status check.
54
+ - Zero badges is a correct answer. A private project with no registry presence and no CI has nothing that passes the test, and an empty block beats a padded one.
55
+
39
56
  ## Content
40
57
 
41
58
  - Open public-facing READMEs with universal problems any reader recognizes, not repo-specific artifact names. Save artifact names for feature or "What is inside" sections.
@@ -44,6 +61,10 @@ Scoped to the README at a repository root. A nested README documenting a folder,
44
61
  - For libraries/tools: include API quickstart
45
62
  - For applications/products: include usage instructions and configuration options
46
63
  - For CLI tools: include command examples with flags
64
+ - For agent-facing repositories: name the file an agent loads first and the command that lists what the project exposes
65
+ - For marketplace-distributed plugins: give the install command for every channel the project publishes to
66
+
67
+ A project is often several of these at once. Cover every type that applies rather than picking the closest one. A repository that ships a CLI, distributes a plugin, and holds agent conventions owes its reader all three.
47
68
 
48
69
  ## Examples
49
70
 
@@ -1,33 +1,52 @@
1
1
  ---
2
2
  title: Standard reference
3
3
  description: Shape and content rules for authoring a standard
4
- consumers: create-standard
5
4
  ---
6
5
 
7
6
  # Standard reference
8
7
 
8
+ Applies to each authored standard in the folder. Skip for `index.md`, which is generated rather than authored.
9
+
9
10
  ## Overview
10
11
 
11
- A standard is a target-facing authoring convention for one document type. It installs into a project under `.claude/standards/` and is consumed by skills and developers alike. One standard governs one doc type. Split unrelated conventions into separate files.
12
+ A standard is a target-facing authoring convention for one document type. It installs into a project under `.claude/standards/` and is consumed by skills and developers alike. This file governs itself, so every rule below applies to it.
13
+
14
+ ## What a working standard looks like
15
+
16
+ A standard answers these questions. Each can be answered wrong, which is what makes them a test rather than a preamble.
17
+
18
+ - Which single document type does this govern, and where does that document live?
19
+ - Can an author who has seen no example produce a conforming document from this file alone?
20
+ - Does every rule state a shape the document must have, rather than a fact about the repository that happens to store it?
21
+ - What does a conforming document achieve, stated so a reviewer can call one non-conforming without appealing to taste?
22
+
23
+ A standard failing these questions is non-conforming even when it satisfies every shape rule below.
24
+
25
+ ## Scope
26
+
27
+ - Govern one document type per standard. Split unrelated conventions into separate files.
28
+ - Name no path, filename, or folder outside the document type the standard governs. A standard reaches projects whose layout is their own, so a path borrowed from the authoring repository is wrong in a target and nothing reports it.
29
+ - State the rule, never the mechanism enforcing it. Hooks, scripts, checks, and skill catalogs are facts about one repository. Name the condition the document must meet and let the enforcing surface name its own case.
30
+ - Invent inline examples rather than citing a real file elsewhere in the project. A cited file moves or is deleted and the standard goes stale in silence.
12
31
 
13
32
  ## Frontmatter
14
33
 
15
- - Start the file with a frontmatter block carrying `title` and `description`.
16
- - `title`: names the doc type in sentence case, suffixed `reference` (`Commit reference`, `Branch reference`).
34
+ - Start the file with a frontmatter block carrying at least `title` and `description`. A consuming surface may require more.
35
+ - `title`: names the doc type in sentence case, suffixed `reference` (`Commit reference`, `Branch reference`)
17
36
  - `description`: one line naming what the standard covers. It becomes the index link label on install.
18
37
 
19
38
  ## Structure
20
39
 
21
- - Use sentence case for every heading.
40
+ - Use sentence case for every heading
22
41
  - Flat `##` rule groups for a single-topic standard. Group `##` headers by concern for a multi-topic one.
23
42
  - When one `##` section covers more than one sub-concern, split its bullets into `###` subgroups, one subgroup per sub-concern. A flat bullet list under an `##` covers a single sub-concern. Roughly seven bullets is a signal to split, not a hard cap.
24
- - Order groups from the most-used rule down to the edge case.
43
+ - Order groups from the most-used rule down to the edge case
25
44
 
26
45
  ## Rules
27
46
 
28
- - Write rules as imperative bullets: one rule per bullet, one concern per group.
29
- - State the forbidden shape rather than enumerating allowed options, so a rule survives new categories.
30
- - Cut any rule that resists a crisp one-line phrasing.
47
+ - Write rules as imperative bullets: one rule per bullet, one concern per group
48
+ - State the forbidden shape rather than enumerating allowed options, so a rule survives new categories
49
+ - Cut any rule that resists a crisp one-line phrasing
31
50
  - Do not pad with filler prose. Every line earns its place as a usable reference entry.
32
51
 
33
52
  ## Success criterion
@@ -47,6 +66,6 @@ A standard is a target-facing authoring convention for one document type. It ins
47
66
  ## Examples
48
67
 
49
68
  - Include examples only where a rule is non-obvious. A self-evident rule needs none.
50
- - When shown, label them `### Correct` and `### Incorrect` with an inline `# reason` on each entry.
69
+ - When shown, label them `### Correct` and `### Incorrect` with an inline `# reason` on each entry
51
70
  - Keep to two or three entries. Show the pattern, not a catalog.
52
71
  - Keep each entry a short one-liner or command. Do not write multi-line correct and incorrect function blocks.
@@ -1,52 +0,0 @@
1
- ---
2
- title: Standard reference
3
- description: Shape and content rules for authoring a standard
4
- consumers: create-standard
5
- ---
6
-
7
- # Standard reference
8
-
9
- ## Overview
10
-
11
- A standard is a target-facing authoring convention for one document type. It installs into a project under `.claude/standards/` and is consumed by skills and developers alike. One standard governs one doc type. Split unrelated conventions into separate files.
12
-
13
- ## Frontmatter
14
-
15
- - Start the file with a frontmatter block carrying `title` and `description`.
16
- - `title`: names the doc type in sentence case, suffixed `reference` (`Commit reference`, `Branch reference`).
17
- - `description`: one line naming what the standard covers. It becomes the index link label on install.
18
-
19
- ## Structure
20
-
21
- - Use sentence case for every heading.
22
- - Flat `##` rule groups for a single-topic standard. Group `##` headers by concern for a multi-topic one.
23
- - When one `##` section covers more than one sub-concern, split its bullets into `###` subgroups, one subgroup per sub-concern. A flat bullet list under an `##` covers a single sub-concern. Roughly seven bullets is a signal to split, not a hard cap.
24
- - Order groups from the most-used rule down to the edge case.
25
-
26
- ## Rules
27
-
28
- - Write rules as imperative bullets: one rule per bullet, one concern per group.
29
- - State the forbidden shape rather than enumerating allowed options, so a rule survives new categories.
30
- - Cut any rule that resists a crisp one-line phrasing.
31
- - Do not pad with filler prose. Every line earns its place as a usable reference entry.
32
-
33
- ## Success criterion
34
-
35
- - State what a conforming artifact achieves, not only what shape it takes. A standard that specifies structure exhaustively and success nowhere cannot be argued against, only edited on taste.
36
- - Write the criterion as a small set of questions the artifact must answer, or a task a reader must be able to complete from it. Keep it checkable by a person in one sitting.
37
- - Place it near the top, above the shape rules it governs. The shape rules are the means and the criterion is the test.
38
- - Say that an artifact failing the criterion is non-conforming even when it satisfies every shape rule. Without that line the criterion reads as advice.
39
- - Add a criterion to an existing standard when that standard is next exercised, not in a sweep. A criterion written without a failure to point at is the taste-based edit this section exists to prevent, so a standard with no criterion yet is a known gap rather than a violation.
40
-
41
- ## Changing a standard
42
-
43
- - Change a standard on a failure, not on a finding. A finding is that the docs say X or a paper suggests Y. A failure is a conforming artifact that satisfied every shape rule and still missed the success criterion.
44
- - Park findings wherever the project tracks pending work, or in the standard's own backlog section when it tracks none. They are hypotheses to test, not instructions to apply.
45
- - Cite the failing artifact in the change that fixes it, so the next reader can tell which rules were paid for by evidence.
46
-
47
- ## Examples
48
-
49
- - Include examples only where a rule is non-obvious. A self-evident rule needs none.
50
- - When shown, label them `### Correct` and `### Incorrect` with an inline `# reason` on each entry.
51
- - Keep to two or three entries. Show the pattern, not a catalog.
52
- - Keep each entry a short one-liner or command. Do not write multi-line correct and incorrect function blocks.