@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 +5 -1
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/claude-diagram/SKILL.md +19 -0
- package/claude/skills/claude-docs/SKILL.md +67 -9
- package/claude/skills/create-standard/SKILL.md +1 -1
- package/claude/skills/toolkit-cli/SKILL.md +1 -1
- package/docs/agents.md +12 -0
- package/docs/ai-workflow.md +2 -1
- package/docs/operating-model.md +1 -1
- package/docs/target-projects.md +2 -2
- package/docs/visual-design-workflow.md +1 -1
- package/governance/rules/claude/580-readme.md +1 -1
- package/governance/rules/claude/591-standard-authoring.md +13 -0
- package/governance/stacks/base.toml +1 -1
- package/package.json +1 -1
- package/scripts/core/regen-claude-copies.sh +10 -1
- package/scripts/core/verify.sh +1 -1
- package/scripts/docs/list.sh +32 -15
- package/src/commands/gov.ts +40 -0
- package/src/docs/read.ts +25 -7
- package/src/gov/consumed.ts +129 -0
- package/src/indexes/render.ts +26 -6
- package/standards/context.md +2 -0
- package/standards/diagrams.md +10 -2
- package/standards/index.md +1 -0
- package/standards/readme.md +21 -0
- package/{claude/skills/create-standard/references → standards}/standard.md +29 -10
- package/standards/bundled/standard.md +0 -52
package/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# aitk
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@erclx/aitk)
|
|
4
|
+
[](https://github.com/erclx/aitk/actions/workflows/verify.yml)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
3
7
|
One source for your repos' AI conventions. Install once, sync everywhere.
|
|
4
8
|
|
|
5
9
|

|
|
@@ -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
|
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
-
|
|
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
|
|
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.
|
package/docs/ai-workflow.md
CHANGED
|
@@ -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
|
|
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
|
|
package/docs/operating-model.md
CHANGED
|
@@ -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
|
package/docs/target-projects.md
CHANGED
|
@@ -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
|
@@ -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
|
-
|
|
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"
|
package/scripts/core/verify.sh
CHANGED
|
@@ -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/
|
|
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"
|
package/scripts/docs/list.sh
CHANGED
|
@@ -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
|
|
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 < <(
|
|
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
|
|
93
|
-
name
|
|
94
|
-
|
|
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
|
}
|
package/src/commands/gov.ts
CHANGED
|
@@ -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
|
|
25
|
-
|
|
26
|
-
|
|
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
|
|
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
|
-
|
|
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
|
+
}
|
package/src/indexes/render.ts
CHANGED
|
@@ -161,21 +161,41 @@ function renderIndex(
|
|
|
161
161
|
}
|
|
162
162
|
})
|
|
163
163
|
} else {
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
|
|
185
|
+
if (hasCategories && subCatalogs.length > 0) {
|
|
186
|
+
out += '\n## Sub-catalogs\n\n'
|
|
171
187
|
for (const catalog of subCatalogs) {
|
|
172
|
-
out +=
|
|
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
|
}
|
package/standards/context.md
CHANGED
|
@@ -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
|
|
package/standards/diagrams.md
CHANGED
|
@@ -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
|
-
|
|
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.
|
package/standards/index.md
CHANGED
|
@@ -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
|
package/standards/readme.md
CHANGED
|
@@ -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.
|
|
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.
|