@erclx/canon 4.63.0 → 4.65.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/claude/.claude-plugin/plugin.json +1 -1
  2. package/claude/skills/canon-record/REQUIREMENT.md +34 -0
  3. package/claude/skills/canon-record/SKILL.md +54 -0
  4. package/claude/skills/canon-screencast/REQUIREMENT.md +1 -1
  5. package/claude/skills/canon-screencast/SKILL.md +3 -3
  6. package/claude/skills/claude-autoship/REQUIREMENT.md +3 -1
  7. package/claude/skills/claude-autoship/SKILL.md +26 -12
  8. package/claude/skills/draft-readme/REQUIREMENT.md +36 -0
  9. package/claude/skills/draft-readme/SKILL.md +65 -0
  10. package/claude/skills/identity/REQUIREMENT.md +40 -0
  11. package/claude/skills/identity/SKILL.md +86 -0
  12. package/claude/skills/test-first/REQUIREMENT.md +34 -0
  13. package/claude/skills/test-first/SKILL.md +24 -0
  14. package/docs/agents/demo.md +2 -0
  15. package/docs/agents/review-classification.md +2 -2
  16. package/docs/workflow/ai-workflow.md +12 -8
  17. package/docs/workflow/visual-design-workflow.md +1 -0
  18. package/governance/rules/core/070-planning.md +1 -0
  19. package/governance/rules/ui/440-surface-capture.md +2 -0
  20. package/package.json +6 -3
  21. package/scripts/core/regen-agent-fixture.sh +1 -1
  22. package/scripts/core/regen-web-favicon.ts +67 -0
  23. package/src/claude/cases/authoring.ts +4 -0
  24. package/src/claude/cases/claude-workflow.ts +5 -0
  25. package/src/claude/cases/misc.ts +10 -0
  26. package/src/design/base.css +8 -3
  27. package/src/design/tokens.ts +23 -0
  28. package/src/gate/stages.ts +27 -4
  29. package/standards/readme.md +2 -0
  30. package/tooling/astro/configs/eslint.config.js +7 -1
  31. package/tooling/astro/reference.md +1 -1
  32. package/tooling/nextjs/configs/eslint.config.js +3 -2
  33. package/tooling/nextjs/reference.md +1 -1
  34. package/tooling/web/configs/eslint.config.js +1 -1
  35. package/tooling/web/reference.md +1 -1
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "canon",
3
3
  "description": "Automated governance, versioning, and discovery tools for Claude Code.",
4
- "version": "4.63.0",
4
+ "version": "4.65.0",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -0,0 +1,34 @@
1
+ ---
2
+ name: canon-record
3
+ description: Why compiling and running a screencast draft needs a routed skill rather than an operator typing two verbs by hand, and why the unresolved-field refusal cannot be a suggestion
4
+ ---
5
+
6
+ # Canon record requirement
7
+
8
+ ## Gap
9
+
10
+ Without this skill, `canon-screencast` writes a draft and names `canon demo compile` as the next step, and nothing after that routes a session there or to `canon demo run`. An operator has to know both verbs, type them in order, and read the compile record for unresolved fields by hand. A session that fills one in on its own reproduces the exact failure `canon demo run`'s `plan-unresolved` reason exists to catch, one layer up where nothing enforces it.
11
+
12
+ ## Must
13
+
14
+ - Resolve the default plan path the same way `canon demo compile` does, so a caller passing only the draft path reaches the plan without naming it
15
+ - Skip compiling when a plan already exists at that path, since its timing may be tuned by hand and a draft cannot reproduce that
16
+ - Report every unresolved field from the compile or run record and stop rather than filling one in
17
+ - Report every path a run wrote, video, mp4, gif, still, skipping any the record carries as null
18
+
19
+ ## Must not
20
+
21
+ - Pass `--force` to compile
22
+ - Guess or fill a target, a URL, or any other unresolved field
23
+ - Drive the application through anything other than `canon demo run`
24
+ - Assume this skill's own invocation frequency needs no check. `canon-screencast`'s closing block names it by hand, but whether anything else reaches for it beyond that pointer or an operator typing its name has no answer at creation time, so a review pass some months in should read that back rather than take it on faith.
25
+
26
+ ## Guards
27
+
28
+ - No draft path given: stop rather than guessing what to compile
29
+
30
+ ## Out of scope
31
+
32
+ - Drafting the beats, which `canon-screencast` owns
33
+ - Filling an existing plan's target or URL, which is the operator's own edit
34
+ - Verifying a recording beyond its own caption, which nothing in the toolkit does yet
@@ -0,0 +1,54 @@
1
+ ---
2
+ name: canon-record
3
+ description: Drives a screencast draft through to a recording. Compiles it with `canon demo compile` when no plan exists yet at the default path, skipping compile when one is already there, then runs `canon demo run` once nothing is unresolved. Reports every unresolved field from the compile or run record and stops rather than guessing one. Use when asked to "record the screencast", "run the demo", "compile and record this draft", or right after `canon-screencast` prints its next-step line. Do NOT use to draft the beats, which is `canon-screencast`, or to fill in a plan's target or URL, which is the operator's own edit.
4
+ ---
5
+
6
+ # Canon record
7
+
8
+ ## Guards
9
+
10
+ - If no draft path is given, stop: `❌ No draft path. Pass the path canon-screencast printed.`
11
+ - Never guess or fill an unresolved field, a target, a URL, or anything else the compile or run record names. Report it and stop. Filling one in reproduces the failure `canon demo run`'s `plan-unresolved` reason exists to catch, one layer up where nothing enforces it.
12
+ - Never pass `--force` to compile. A plan already at the default output path may carry timing tuned by hand, and the draft cannot reproduce that, so leave it untouched.
13
+ - Drive the application through `canon demo run` alone. Never open a browser, click through the app, or write to the output paths some other way.
14
+
15
+ ## Step 1: resolve the plan path
16
+
17
+ Derive the default plan path the same way `canon demo compile` does: `<out>/<slug>.json`, where `<out>` defaults to `demos` and `<slug>` defaults to the draft's filename with its extension stripped. `.canon/tmp/screencast/inline-edit.md` resolves to `demos/inline-edit.json`.
18
+
19
+ ## Step 2: compile only when no plan exists yet
20
+
21
+ Check whether the resolved plan path already exists.
22
+
23
+ - **It exists.** A person may have tuned it by hand since compiling. Skip compiling and go to Step 3 with this path.
24
+ - **It does not exist.** Run:
25
+
26
+ ```bash
27
+ canon demo compile <draft> --json
28
+ ```
29
+
30
+ Branch on the record rather than the exit code:
31
+ - `reason: draft-missing` or `reason: draft-unreadable`: report the reason, plus the record's `message` when it carries one, and stop.
32
+ - No `reason` key, meaning the plan was written: read `unresolved` off the record.
33
+ - Non-empty: report the plan path and every field the array names, one per line, and stop. Do not proceed to Step 3.
34
+ - Empty: continue to Step 3 with the record's `plan` path.
35
+
36
+ ## Step 3: run
37
+
38
+ Run:
39
+
40
+ ```bash
41
+ canon demo run <plan> --json
42
+ ```
43
+
44
+ Branch on the record's `reason`:
45
+
46
+ - `plan-unresolved`: report every field in `unresolved`, one per line, and stop. This is the path a pre-existing plan takes, since Step 2 skipped compiling and never read its fields.
47
+ - Any other reason (`plan-missing`, `plan-unreadable`, `no-output-requested`, `cursor-unreadable`, `engine-missing`, `browser-missing`): report the reason, plus the record's `message` or `install` line when it carries one, and stop.
48
+ - No `reason` key, meaning the run wrote its output: continue to Step 4.
49
+
50
+ ## Step 4: output
51
+
52
+ The record carries `video`, `mp4`, `gif`, and `still`, each a path or `null`. Report each one that is not `null`, one per line, skipping the rest.
53
+
54
+ Say so plainly if `canon demo compile` or `canon demo run` is not available, rather than driving the application some other way. Both ship with the CLI and this skill ships with the plugin, so a project carrying one and not the other is a real state.
@@ -33,7 +33,7 @@ A draft that names the recording software, the editing software, or the window m
33
33
 
34
34
  ## Out of scope
35
35
 
36
- - Producing the recording, which stops at the script by design
36
+ - Producing the recording, which `canon-record` owns
37
37
  - Refining an existing draft, which is a direct edit of the file
38
38
  - Slide decks, which `canon-slides-draft` owns
39
39
  - Where the recording ships, which the draft lists and the user decides
@@ -8,7 +8,7 @@ description: Drafts a screencast script with pre-seeded beats, defaults, and dec
8
8
  ## Guards
9
9
 
10
10
  - If no topic is provided, stop: `❌ No screencast topic. Describe what you are recording.`
11
- - Draft, then hand off. Do not edit video or generate captions, and do not drive the application. A recording is another command's job rather than something forbidden: write the draft, name `canon demo compile` as the next step, and stop.
11
+ - Draft, then hand off. Do not edit video or generate captions, and do not drive the application. A recording is another skill's job rather than something forbidden: write the draft, name `canon-record` as the next step, and stop.
12
12
  - Stack-agnostic in the draft. Never name a recording tool, an editing tool, a font, or a window manager. Keep selectors, URLs, wait conditions, and timings out of the beats too, since those four are exactly what the compiler adds in a plan of its own. A beat carrying them stops being a document a person can read and edit down.
13
13
 
14
14
  ## Step 1: read the project context
@@ -159,9 +159,9 @@ Print the file path on its own line and a one-line summary. Do not paraphrase th
159
159
  Draft has 5 beats and pre-seeded defaults. Edit the beats and the resolved decisions.
160
160
 
161
161
  To record it rather than shoot it by hand:
162
- canon demo compile .canon/tmp/screencast/<slug>.md
162
+ canon-record .canon/tmp/screencast/<slug>.md
163
163
  ```
164
164
 
165
- Name the command and stop there. Do not compile the draft, do not run it, and do not generate captions. The operator edits the beats first, and the compiler reports which selectors and URLs they still owe it.
165
+ Name the skill and stop there. Do not compile the draft, do not run it, and do not generate captions. The operator edits the beats first, and the compiler reports which selectors and URLs they still owe it.
166
166
 
167
167
  Say so plainly if `canon demo compile` is not available, rather than driving the application some other way. The command ships with the CLI and the skills ship with the plugin, so a project carrying one and not the other is a real state.
@@ -16,6 +16,7 @@ Review is the step that varies most. It gets skipped on a diff that needed one,
16
16
  - Take the approved plan for the branch as the scope, and implement only what it describes
17
17
  - Give every step a stop condition, and leave the code on the branch and the receipts on disk at each one
18
18
  - Classify the changed-file list by path as well as by extension, so informational prose skips a code review with no signal on it and executable prose still reaches one
19
+ - Check the branch's committed history for a test-order violation between verify and review, and report any finding without gating on it, matching the verb's own contract
19
20
  - Split findings by origin, stopping on a critical or should-fix one the branch inherited and repairing one this run caused
20
21
  - Own the review receipt's lifetime, since this chain writes it, cites it in its own closing block, and is the only body that can read whether the step keeping it is still using it
21
22
  - Delegate the ship sequence to `git-ship` rather than restating it, and name only what this chain adds to it
@@ -34,6 +35,7 @@ Review is the step that varies most. It gets skipped on a diff that needed one,
34
35
  - Run the memory Apply phase. Promoting an entry changes how the agent operates and ships as its own change.
35
36
  - Read an empty changed-file list as prose-only. It satisfies that test vacuously and would route the branch past review instead of through it.
36
37
  - Read a markdown extension as evidence the change only informs. A skill body, a governance rule, and a standard are behavior written in prose.
38
+ - Stop the chain or rewrite a commit over a test-order finding. The verb reports and never gates, and a commit already in history is a different act from the work this run is building.
37
39
 
38
40
  ## Guards
39
41
 
@@ -47,4 +49,4 @@ Review is the step that varies most. It gets skipped on a diff that needed one,
47
49
 
48
50
  - Writing the plan, which `claude-feature` owns. This chain starts from one already approved.
49
51
  - The behavior of each step, owned by the skill invoked. This skill owns the order and the stop conditions.
50
- - The ship sequence and the resume path after a stop, both of which `git-ship` owns. That skill is the tail of this chain, invoked at Step 7 rather than copied into it, so the overlap is one body reached two ways rather than two bodies stating one order.
52
+ - The ship sequence and the resume path after a stop, both of which `git-ship` owns. That skill is the tail of this chain, invoked at Step 8 rather than copied into it, so the overlap is one body reached two ways rather than two bodies stating one order.
@@ -17,7 +17,7 @@ Chain the post-plan pipeline in a single run. Every step has a stop condition. S
17
17
 
18
18
  ## Diff baseline
19
19
 
20
- Step 5 classifies the changed-file list to decide whether review runs. Resolve the base ref once:
20
+ Step 6 classifies the changed-file list to decide whether review runs. Resolve the base ref once:
21
21
 
22
22
  ```bash
23
23
  git merge-base HEAD origin/main 2>/dev/null || git merge-base HEAD main 2>/dev/null
@@ -27,7 +27,7 @@ Prefer `origin/main` over local `main`. A local `main` trailing the remote pulls
27
27
 
28
28
  The baseline is unusable when no merge base resolves against either ref. Stop: `❌ No diff baseline against main. Fetch origin, then re-run autoship.`
29
29
 
30
- The base equalling HEAD stays usable here, unlike in the four read-only siblings carrying this section. Step 5 runs before anything is committed, since `git-stage` commits at Step 7, so the base equals HEAD on every ordinary run. The classifier diffs the base against the working tree rather than against HEAD, which keeps the uncommitted work in the set at correct scope. Do not port the sibling `base == HEAD` stop into this skill.
30
+ The base equalling HEAD stays usable here, unlike in the four read-only siblings carrying this section. Step 6 runs before anything is committed, since `git-stage` commits at Step 8, so the base equals HEAD on every ordinary run. The classifier diffs the base against the working tree rather than against HEAD, which keeps the uncommitted work in the set at correct scope. Do not port the sibling `base == HEAD` stop into this skill.
31
31
 
32
32
  ## Step 0: take the role, then enter a worktree
33
33
 
@@ -103,7 +103,21 @@ Run the verify commands defined in `CLAUDE.md` (lint, typecheck, tests). On fail
103
103
 
104
104
  Do not loop. Do not bypass hooks.
105
105
 
106
- ## Step 4: UI test (conditional)
106
+ ## Step 4: test order
107
+
108
+ Run `canon gov test-order --json` from the worktree this chain is building in, which is the one tree holding the branch's own commits. The Guards send a `.canon/plans/` and a `.canon/review/` read to the main worktree root and this is not one of those: that checkout sits on the trunk on an ordinary run, so the range closes on itself there and every finding the branch carries reads as clean.
109
+
110
+ The verb reads git history rather than the working tree, so a branch with nothing committed yet reads as clean, and what it catches is a violation that already reached this branch's history in an earlier session or an earlier round of this chain. It reports and never gates, since nothing wires its exit into a push, so branch on the record's `kind` and its `findings` array rather than on the exit, which reads 2 on a finding and 1 on a refusal and which a shell function wrapping `canon` can flatten to zero either way.
111
+
112
+ - `kind: 'measured'`, `findings` empty. Nothing on the branch reached history ahead of its test. Continue to Step 5.
113
+ - `kind: 'measured'`, `findings` non-empty. Report each pair's `subject` and `reason` to the user and continue to Step 5. Do not stop the chain and do not attempt a fix: the commit already reached history, and rewriting it here rewrites what an earlier run shipped rather than what this run is building.
114
+ - `kind: 'unreadable'`. Report the `message` and continue to Step 5. A shallow clone or a repository with no trunk is an ordinary state this check cannot read, not a reason to stop shipping.
115
+
116
+ ### When the verb is absent
117
+
118
+ The verb ships with the CLI and this body ships with the plugin, matching Step 6's own fallback for the classify verb. Report that the check did not run rather than reading a missing subcommand as clean, and continue to Step 5.
119
+
120
+ ## Step 5: UI test (conditional)
107
121
 
108
122
  If the diff touches UI files (JSX, TSX, Vue, Svelte, HTML, or CSS under `src/`), invoke `canon:claude-ui-test`.
109
123
 
@@ -111,7 +125,7 @@ If `claude-ui-test` produces a manual checklist, stop: `❌ UI requires visual v
111
125
 
112
126
  If all UI changes are covered by e2e tests, continue.
113
127
 
114
- ## Step 5: review
128
+ ## Step 6: review
115
129
 
116
130
  Classify the diff first. Take the union of `git diff --name-only <base>` and `git ls-files --others --exclude-standard`, resolving `<base>` per Diff baseline, then hand that set to the verb rather than reading it against the list below yourself:
117
131
 
@@ -121,7 +135,7 @@ canon autoship classify --json <path>...
121
135
 
122
136
  The verb reads names only and touches git not at all, so the set stays the one this step already computed and no second baseline resolves to disagree with the first. Branch on the record's `decision` rather than on the exit code, which a shell function wrapping `canon` can flatten to zero.
123
137
 
124
- - `skip`. Every path reads as prose and none states agent behavior. Skip review entirely and continue to Step 7.
138
+ - `skip`. Every path reads as prose and none states agent behavior. Skip review entirely and continue to Step 8.
125
139
  - `review`. Invoke `canon:claude-review`. The record names the `file` that decided it and the `test` it failed, `extension` for a path that is not prose and `behavior-path` for prose that states what an agent does.
126
140
  - `refused`, carrying reason `no-changes`. The changed set was empty, so take the stop below.
127
141
 
@@ -137,7 +151,7 @@ The verb ships with the CLI and this body ships with the plugin, so a target hol
137
151
 
138
152
  Never read an absent subcommand as a skip. Failing open is the exact defect the verb closes, and a shell that answers `command not found` reaching a body that skips on anything other than a `skip` record would ship every branch unreviewed.
139
153
 
140
- The skip needs both tests to pass: every changed file matches `*.md` or `*.txt`, and no changed file sits under a behavior path. On a pass, skip review entirely and continue to Step 7. Otherwise invoke `canon:claude-review`.
154
+ The skip needs both tests to pass: every changed file matches `*.md` or `*.txt`, and no changed file sits under a behavior path. On a pass, skip review entirely and continue to Step 8. Otherwise invoke `canon:claude-review`.
141
155
 
142
156
  Behavior paths carry two spellings, the one a surface authors at and the one it reaches a session at, so the rule reads the same in a toolkit and in a project that consumed one:
143
157
 
@@ -156,9 +170,9 @@ The list covers this toolkit's authoring layout and the layout it installs, whic
156
170
 
157
171
  The verb reads the same set from `src/autoship/paths.ts`, so a path added here belongs there too and a path added there belongs here. Two copies is what the fallback costs, and it stands until a release retires the written half.
158
172
 
159
- ## Step 6: evaluate findings
173
+ ## Step 7: evaluate findings
160
174
 
161
- Skip this step when Step 5 skipped review. Otherwise read `.canon/review/branch/review-<slug>.md` at the main worktree root. Split every finding by origin before parsing the summary line (`X critical, Y should-fix, Z minor`), since the stop exists for a defect the branch inherited rather than for one this run introduced.
175
+ Skip this step when Step 6 skipped review. Otherwise read `.canon/review/branch/review-<slug>.md` at the main worktree root. Split every finding by origin before parsing the summary line (`X critical, Y should-fix, Z minor`), since the stop exists for a defect the branch inherited rather than for one this run introduced.
162
176
 
163
177
  - **This run caused it, at any severity.** Fix it, re-run the Step 3 verify commands, re-read the fixed file against what the finding claimed, and continue. Do not report it as a stop and do not offer the fix as a choice, which is the same stop wearing a proposal.
164
178
  - **It predates this run, critical or should-fix.** Stop: `❌ Review found non-minor issues that predate this run. See .canon/review/branch/review-<slug>.md. Fix and run /git-ship.`
@@ -168,9 +182,9 @@ Read origin as causation rather than authorship. Staleness this run induced in a
168
182
 
169
183
  Bound the repair at one pass, the way Step 3 bounds verify. When that re-read shows the finding still standing, stop: `❌ A self-introduced finding survived one fix pass. See .canon/review/branch/review-<slug>.md. Fix and run /git-ship.`
170
184
 
171
- This chain owns the receipt's lifetime, which is what makes the Output block's citation resolve on a run that reaches it. `claude-docs` used to delete the current slug's receipt while running under Step 7 below, so the closing line named a file the same run had already removed. That sweep now reaches only reports whose branch is gone, which collects this one a branch later rather than during the run that wrote it. The cost is one receipt per live branch left in `.canon/review/branch/`, bounded by the branch count rather than by the lifetime of the checkout.
185
+ This chain owns the receipt's lifetime, which is what makes the Output block's citation resolve on a run that reaches it. `claude-docs` used to delete the current slug's receipt while running under Step 8 below, so the closing line named a file the same run had already removed. That sweep now reaches only reports whose branch is gone, which collects this one a branch later rather than during the run that wrote it. The cost is one receipt per live branch left in `.canon/review/branch/`, bounded by the branch count rather than by the lifetime of the checkout.
172
186
 
173
- ## Step 7: ship
187
+ ## Step 8: ship
174
188
 
175
189
  Invoke `canon:git-ship`. That body owns the sequence, being the verify gate, memory capture, both doc syncs, staging, the commit grouping, the branch rename, the pull request, the CI watch, and the scoped memory review, along with the reason each step sits where it does. This step used to restate that list and the two drifted apart with nothing comparing them, so read the order there and never here.
176
190
 
@@ -201,11 +215,11 @@ Respond with up to five lines:
201
215
  <Memory proposal at .canon/review/memory/memory-review-<slug>.md>
202
216
  ```
203
217
 
204
- `<state>` is whatever the Step 7 read returned, being `draft` or `ready, unsupervised`, rather than the state the undo asked for. Writing the word `draft` there unconditionally is what this line used to do, and it named a state no step had read.
218
+ `<state>` is whatever the Step 8 read returned, being `draft` or `ready, unsupervised`, rather than the state the undo asked for. Writing the word `draft` there unconditionally is what this line used to do, and it named a state no step had read.
205
219
 
206
220
  Omit the second line if there were no minor findings, and the third if nothing routed. Omit the fourth and fifth if `claude-memory-capture` wrote no memory file this session, since an empty pen means no scoped review and no proposal. A run that routes every fact and writes none is the shape to expect, and it reports three lines.
207
221
 
208
- This block replaces the one `git-ship` closes on rather than following it. The two carry the same three trailing lines and differ on the two above them, since the first names the state the read returned and the second reports the minor findings Step 6 kept, neither of which that body has a counterpart for. Emitting both reports one run twice and buries the state under a `✅ Shipped` that does not name it.
222
+ This block replaces the one `git-ship` closes on rather than following it. The two carry the same three trailing lines and differ on the two above them, since the first names the state the read returned and the second reports the minor findings Step 7 kept, neither of which that body has a counterpart for. Emitting both reports one run twice and buries the state under a `✅ Shipped` that does not name it.
209
223
 
210
224
  ## Failure recovery
211
225
 
@@ -0,0 +1,36 @@
1
+ ---
2
+ name: draft-readme
3
+ description: Why a README needs a project-type read and a confirm step, not the rewrite path docs-sync already owns
4
+ ---
5
+
6
+ # Readme draft requirement
7
+
8
+ ## Gap
9
+
10
+ Without this skill, a session drafting a README either copies the generic template from memory or reaches for `docs-sync`, which has nothing to diff a nonexistent page against and reports the topic as unrelated to any change. A scaffold-written stub meets the same dead end, since no diff touches it either. Either way the page ships with no read of `standards/readme.md`, no read of what the project actually is, and no badge chosen against its rendered value rather than its service name.
11
+
12
+ A README whose real documentation lives in `.claude/context/` or `docs/` was a second gap the standard itself carried until this build: nothing named a pointer page as complete, so a session drafting one either padded the page or read the required list as unsatisfied.
13
+
14
+ ## Must
15
+
16
+ - Read `standards/readme.md` before drafting, since its `## Voice` section is scoped to a repository-root README and yields to `write-human`'s reference voice everywhere else
17
+ - Detect every project type the standard's `## Content` list applies to, from the manifest and the tree, rather than drafting against the closest single type
18
+ - Treat an existing README with no H1, or with headings that name only the scaffold that wrote it, as unedited generator output to draft over rather than a page to sync section by section
19
+ - Verify a candidate badge by the value it would render rather than by a fetch's status code, since a badge service answers 200 for a query it cannot satisfy
20
+ - Confirm the resolved path, the detected types, the badge candidates, and the full content with the user before writing, since project-type detection and badge selection are judgment calls with no diff to preview them against
21
+
22
+ ## Must not
23
+
24
+ - Rewrite an authored README. One carrying an H1 that names the project refuses toward `docs-sync`.
25
+ - Offer to overwrite an authored README on the refusal path, or propose conforming it to what this skill would have drafted. A target project's own citations into its README are invisible from here, so the refusal reports rather than proposes.
26
+ - Assume this skill's own invocation frequency needs no check. Whether anything reaches for it beyond an author typing its name has no answer at creation time, so a review pass some months in should read that back rather than take the assumption on faith.
27
+
28
+ ## Guards
29
+
30
+ - The target already exists and is authored, carrying an H1 that names the project: stop and point at `docs-sync` instead.
31
+
32
+ ## Out of scope
33
+
34
+ - Rewriting or syncing an existing authored `README.md` against a diff since main: `docs-sync`
35
+ - The consumer-facing reference under `docs/`: `draft-docs`
36
+ - Product scope and goals, and every other surface `standards/readme.md` already scopes out of its own governance
@@ -0,0 +1,65 @@
1
+ ---
2
+ name: draft-readme
3
+ description: Drafts a project's README.md against the readme standard, detecting project type and badge candidates, and confirming with the user before write. Use when asked to "write a README", "draft a README for this project", "add a README", or "create a README.md" where none exists yet or the existing one is unedited scaffold output. Do NOT use to rewrite or resync an existing authored README against a diff, which is `docs-sync`.
4
+ ---
5
+
6
+ # Readme draft
7
+
8
+ Drafts a project's `README.md` end to end: read the standard, detect the project's shape, confirm the draft with the user, then write.
9
+
10
+ Read these files in parallel:
11
+
12
+ - `${CLAUDE_SKILL_DIR}/../../standards/readme.md`: voice, structure, required and optional sections, badge selection, and what to link out to rather than carry. Voice is claimed here for a repository-root README, and `write-human` yields it there while keeping rhythm, density, and the machine-tell catalog everywhere, root README included.
13
+ - `${CLAUDE_SKILL_DIR}/../../standards/markdown.md`: banned words, punctuation, and formatting for all generated text
14
+ - The `write-human` skill: rhythm, density, and sentence construction for all generated text
15
+
16
+ ## Guards
17
+
18
+ - Default the target to `README.md` at the repository root when no path is given. A caller naming a path under a folder, a harness, or an internal tool is drafting a nested README instead, which keeps the reference voice per the standard's `## Voice` section rather than the root voice below.
19
+ - Read the target if it exists.
20
+ - It carries an H1 naming the project: it is authored. Stop: `❌ <path> already exists and covers the project. Run canon:docs-sync instead.`
21
+ - It carries no H1, or its only headings restate the tool that scaffolded it rather than the project: it is unedited generator output. Continue, drafting over it rather than syncing its sections.
22
+ - It does not exist: continue.
23
+ - A hand-authored README opening with a badge block, or a title in some other form the H1 test misses, falls into the no-H1 branch the same as a scaffold page. The Confirm step below is what catches that case before the write happens, so treat it as load-bearing rather than a courtesy: never skip it on the reasoning that the guard already decided.
24
+
25
+ ## Detect
26
+
27
+ - Read the project's manifest (`package.json`, `pyproject.toml`, `Cargo.toml`, or equivalent) for a `bin` field or CLI entry point, an installable package name, and its declared dependencies.
28
+ - Check for a `claude/skills/` or `.claude/skills/` folder, a `plugin.json`, or a marketplace manifest, each naming an agent-facing or marketplace-distributed surface.
29
+ - A project is often several of these at once. Note every type that applies rather than stopping at the first match, since the Draft step covers each one the project actually is.
30
+
31
+ ## Draft
32
+
33
+ - Draft the page against `${CLAUDE_SKILL_DIR}/../../standards/readme.md`: H1, a 2-3 sentence description in plain text, then the required sections, then whichever optional sections and per-type content the Detect step found.
34
+ - Cover every applicable project type from the standard's `## Content` list rather than picking the closest one.
35
+ - Candidate badges: check for a published package (a registry field in the manifest), a CI workflow, and a `LICENSE` file.
36
+ - State each candidate's rendered value in the preview rather than trusting a fetch's status code, since a badge service answers 200 for a query it cannot satisfy.
37
+ - Pin a status badge to the branch the standard names and confirm the workflow actually triggers on that branch before offering it. Zero badges is a correct answer when nothing passes the test.
38
+
39
+ ## Confirm
40
+
41
+ - Show the resolved path, the detected project types, the badge candidates and what backs each one, and the full drafted content before writing.
42
+ - Confirm with the user. This skill waits for that answer rather than treating the tool permission dialog as the gate, since project-type detection and badge selection are judgment calls with no diff to preview them against.
43
+
44
+ ## Write
45
+
46
+ - Write the file at the confirmed path, creating the folder when it is absent.
47
+ - Run `canon markdown audit <path>`.
48
+
49
+ ## Response format
50
+
51
+ ### Preview
52
+
53
+ **Target:** `<path>` (root | nested)
54
+ **Detected:** `<project types>`
55
+ **Badges:** `<candidates, or none>`
56
+
57
+ ```markdown
58
+ <drafted H1 and body>
59
+ ```
60
+
61
+ ### After confirmation
62
+
63
+ ```plaintext
64
+ ✅ Drafted: <path>
65
+ ```
@@ -0,0 +1,40 @@
1
+ ---
2
+ name: identity
3
+ description: Why the mark and the card are one pick rather than two, and where the size sequence and the write folder come from before either is drafted
4
+ ---
5
+
6
+ # Identity requirement
7
+
8
+ ## Gap
9
+
10
+ Without this skill, a session asked for a logo and a social card either invents shapes with nothing behind them, or drafts the mark and the card as two separate decisions that can land on shapes that do not compose, which is the failure the task filing this skill measured by hand on every project it touched. A search across every shipped skill and standard found zero hits for `logo`, `og:image`, `og-image`, or Open Graph, so nothing in the catalog reached either output.
11
+
12
+ A session building this without a shared loop restates `draft-and-pick`'s render, hand-off, pick, and loop mechanics from scratch, which drifts from the shipped one with nothing comparing the two copies. It also guesses at the icon size sequence and the output folder rather than reading what the project already declares, and it produces one raster per size through a separate render call per size, which multiplies capture invocations for no reason `canon capture` cannot already batch.
13
+
14
+ ## Must
15
+
16
+ - Detect the icon size sequence from the project's own HTML head or manifest before falling back to the stated default set
17
+ - Detect the write folder from the project's own static-asset convention before falling back to the project root, and announce which one decided it
18
+ - Draft every arm already composed inside the card frame, so one pick settles the mark's shape and its composition together
19
+ - Follow `draft-and-pick`'s Steps 2 through 5 for the render, hand-off, pick, and loop, rather than restating them
20
+ - Write the final mark as its own SVG source, not baked into a raster only
21
+ - Render every final size and the card in one capture call, by sharing one selector class across pages, each declared at half its target dimension to land on the literal size once the render engine's fixed 2x scale factor is applied
22
+
23
+ ## Must not
24
+
25
+ - Restate `draft-and-pick`'s render, hand-off, pick, or loop mechanics
26
+ - Add a `## Logo` section to `standards/design.md`'s fixed section set. `src/design/parse.ts` reads a fixed key set, and no outcome behind this skill asks for a schema change.
27
+ - Modify `draft-and-pick`, `claude-design-extract`, `canon capture`, or `canon design render`. This skill composes all four and extending any of them is a separate change.
28
+ - Assume this skill's own invocation frequency needs no check. Whether anything reaches for it beyond an operator typing its name has no answer at creation time, so a review pass some months in should read that back rather than take it on faith.
29
+
30
+ ## Guards
31
+
32
+ - `canon` not on PATH: stop, since the render loop and the finalize step both need it
33
+ - Fires on a direct request only. A run offering a logo to a project that never asked for one spends a render nobody wanted.
34
+
35
+ ## Out of scope
36
+
37
+ - Wiring the produced files into a project's own HTML head or manifest, which is a separate edit this skill leaves for the operator to make against their own markup
38
+ - Recording the mark's construction rules in `standards/design.md`, which the task's own constraint keeps out of the fixed section set
39
+ - Mutating an existing logo file directly, which is a direct edit rather than a skill
40
+ - Auditing an implemented UI against its tokens, which `claude-ux-audit` owns
@@ -0,0 +1,86 @@
1
+ ---
2
+ name: identity
3
+ description: Drafts a project's logo mark through draft-and-pick's render-and-pick loop, then composes the picked mark into an icon sequence and a 1200x630 social card. Use when asked to "make a logo", "design a logo mark", "create a favicon", "build the icon set", "generate a social card", "make an og:image", or "draft the logo and social card together". Do NOT use to mutate an existing logo file directly, which is a plain edit, or to record the mark's construction rules in DESIGN.md, which is out of scope.
4
+ ---
5
+
6
+ # Identity
7
+
8
+ One identity rendered twice: the same mark sized down to an icon sequence and composed with type into the social card sized up. Drafting both from one pick keeps the mark and its card composition from settling on shapes that do not match.
9
+
10
+ ## Guards
11
+
12
+ - If `canon` is not on PATH, stop: `❌ canon CLI not found.`
13
+ - Draft no candidate for a mark the operator has not asked to make. This skill fires on a direct request, never on the model's own read that a project could use one.
14
+
15
+ ## Step 1: read signal
16
+
17
+ Read these in parallel, skipping any that do not exist:
18
+
19
+ - `.claude/DESIGN.md`: the `## Personality`, `## Color`, and `## Typography` sections, the same three cells `claude-design-extract` Step 2 sources from
20
+ - `.claude/REQUIREMENTS.md`: the `## Personality` paragraph, when `.claude/DESIGN.md` carries none
21
+ - `CLAUDE.md`: the project's stated voice, when neither file above carries a personality signal
22
+
23
+ No signal from any of the three is not a stop. Draft against a neutral default and tag the color and type choices `? verify` per `${CLAUDE_SKILL_DIR}/../../standards/design.md`'s uncertainty tag.
24
+
25
+ ## Step 2: detect the icon size sequence
26
+
27
+ Check the project's HTML entry points (`index.html`, `public/index.html`, `src/index.html`) for a `<link rel="icon">` or `<link rel="apple-touch-icon">` tag carrying a `sizes` attribute, and any `manifest.json` or `site.webmanifest` for an `icons` array. Take the union of every size found.
28
+
29
+ Fall back to the stated default when nothing is detected, since no `tooling/` stack scaffolds a `public/` folder, a favicon reference, or an `og:image` meta tag:
30
+
31
+ - `16x16`, `32x32`, `48x48`
32
+ - `180x180`
33
+ - `192x192`, `512x512`
34
+
35
+ This default set is raster-only. Step 6 already writes the vector source at `favicon.svg` regardless of which branch decided the sequence, so a project willing to reference an SVG favicon directly is covered either way.
36
+
37
+ Announce which of the two decided the sequence.
38
+
39
+ ## Step 3: detect the write folder
40
+
41
+ Check for `public/`, `static/`, or a stack-declared asset folder, taking the first that exists. Fall back to the project root when none is detected, mirroring `claude-design-extract`'s own source-versus-greenfield split. Announce the folder so the operator can move the files if the project's own convention differs.
42
+
43
+ ## Step 4: name the decision and the arms
44
+
45
+ Follow `${CLAUDE_SKILL_DIR}/../draft-and-pick/SKILL.md` Step 1, with the decision fixed rather than derived: "the project's logo mark and its composition into the social card." Vary the mark's shape or style across arms, keeping the card's type and layout fixed, per that skill's one-property rule. Draft each arm already inside the full 1200x630 card frame, mark and type together, so the pick settles the shape and the composition in one choice. Arm 0 is the current mark when the folder from Step 3 already holds a logo file (`favicon.svg`, `favicon.ico`, `logo.svg`, or similar). Arms start at 1 otherwise.
46
+
47
+ Structure every arm as a `.mark` element, the inline SVG alone, nested inside a `.card` element, the full composition, so Step 6 can address either without re-deriving them.
48
+
49
+ ## Step 5: draft, render, pick, and loop
50
+
51
+ Follow `${CLAUDE_SKILL_DIR}/../draft-and-pick/SKILL.md` Steps 2 through 5 against the arms from Step 4: author the page, render and hand off, take the pick, and loop on it.
52
+
53
+ ## Step 6: finalize
54
+
55
+ This step replaces `draft-and-pick`'s own Step 6, since the pick here produces several final files rather than one applied surface.
56
+
57
+ 1. Extract the picked arm's `.mark` markup as the final vector source. Write it to `<write-folder>/favicon.svg`.
58
+ 2. Under `<dest>/render/`, write one page per Step 2 size plus one for the card. Give every page's captured element the shared class `.render`, so one capture call renders the whole batch regardless of the size spread. `canon capture` opens every page at a fixed 2x device scale factor and screenshots the element at that scale, so declare each `.render` element at half its target dimension, `<w>/2` by `<h>/2`, to land the captured PNG on the literal target size rather than double it. Declare a machine-resolved font stack (`system-ui` behind a generic fallback) on each, since `canon capture` refuses a page naming no font at all.
59
+ - `icon-<w>x<h>.html`: the picked `.mark` markup, its `.render` wrapper sized `<w>/2` by `<h>/2`
60
+ - `og-image.html`: the picked `.card` markup, its `.render` wrapper sized 600x315 to capture at the native 1200x630
61
+ 3. Render the batch:
62
+
63
+ ```bash
64
+ canon capture <dest>/render --selector .render --out <write-folder>
65
+ ```
66
+
67
+ 4. Report each written file's path and the dimensions `canon capture` printed for it, confirming each equals its Step 2 target rather than half of it.
68
+ 5. Delete `<dest>`, per `draft-and-pick`'s own scratch-folder rule.
69
+
70
+ ## Response format
71
+
72
+ ```plaintext
73
+ 📝 Wrote <write-folder>/favicon.svg
74
+ 📝 Wrote <write-folder>/icon-16x16.png (16x16)
75
+ 📝 Wrote <write-folder>/og-image.png (1200x630)
76
+
77
+ Write folder: <detected <path>|defaulted to project root>. Move the files if this project's own convention differs.
78
+ ```
79
+
80
+ ## What this delegates
81
+
82
+ Cite these rather than restating them.
83
+
84
+ - `draft-and-pick` owns Steps 1 through 5 of the render-and-pick loop, cited above
85
+ - `claude-design-extract` owns building `.claude/DESIGN.md`. This skill only reads it.
86
+ - `canon capture` owns the render mechanics, its font refusal, and its reported dimensions
@@ -0,0 +1,34 @@
1
+ ---
2
+ name: test-first
3
+ description: Why the loop exists and where it stops short of a debugging session or a visual check
4
+ ---
5
+
6
+ # Test first requirement
7
+
8
+ ## Gap
9
+
10
+ Without this skill, a session implementing a planned change writes the test after the code, or writes both in one motion and never runs the test against the code as it stood before. Either way, nothing showed that the test would have caught the defect it exists to catch, so a test added after the fact protects nothing beyond what the author was already confident about.
11
+
12
+ ## Must
13
+
14
+ - Write or extend the test before the implementation it covers, for any behavior whose shape is already known
15
+ - Run the test before implementing and confirm it fails for the missing behavior, not for an unrelated mistake in the test itself
16
+ - Implement the minimum the current test demands, and start a new test before extending past it
17
+ - Re-run the test after implementing and confirm the pass is the one the test was written to prove
18
+
19
+ ## Must not
20
+
21
+ - Write the implementation and the test together with the test never run red first
22
+ - Treat a test that already passes before any code change as evidence for the new behavior
23
+ - Restate the reproducing-test step `canon:systematic-debugging` already owns for a failure with no known cause
24
+ - Decide whether a change looks right visually, which runs after implementation as a separate check
25
+
26
+ ## Guards
27
+
28
+ - A test failing for a reason other than the missing behavior blocks moving to implementation. Fix the test first.
29
+
30
+ ## Out of scope
31
+
32
+ - Finding the cause of an unexplained failure: `canon:systematic-debugging`
33
+ - Confirming visual output after a change lands: `070-planning.md` states the order and `claude-ui-test` covers it
34
+ - The mechanical audit of whether an implementation reached history ahead of its test: `canon gov test-order`, invoked from `claude-autoship`
@@ -0,0 +1,24 @@
1
+ ---
2
+ name: test-first
3
+ description: Write the failing test for a behavior before writing the code that satisfies it, confirm it fails for the right reason, then implement the smallest change that turns it green. Auto-triggers before implementing a planned feature, adding a function or an endpoint, or extending existing behavior whose new shape is already decided. Do NOT use for a failure with no known cause, which is canon:systematic-debugging, or when the test for the behavior already exists and already passes.
4
+ ---
5
+
6
+ # Test first
7
+
8
+ ## The loop
9
+
10
+ 1. Find or write the test for the behavior before touching the code it covers. Follow the pairing convention the project already uses, which is commonly a test sitting beside its subject under one name, so that `foo.ts` takes `foo.test.ts` beside it.
11
+ 2. Write the test against the behavior as it should exist once the change lands, naming the case for what it proves rather than for the function it calls.
12
+ 3. Run only that test and read the failure. Confirm it fails because the behavior is missing, not because of a typo, a missing import, or a signature the test itself got wrong. A test failing for the wrong reason still reads green the moment any code exists to satisfy that wrong reason, so treat any other failure as a defect in the test and fix the test before moving on.
13
+ 4. Implement the smallest change that turns the failure into a pass. Do not add behavior the test does not ask for. A second behavior wants its own test written first, not a shortcut folded into the change already open.
14
+ 5. Run the test again and confirm it passes for the reason it was written for, not by accident. Then run the surrounding suite so a change made to satisfy this test has not broken another.
15
+
16
+ ## Extending existing behavior
17
+
18
+ - Changing what a function already does starts with changing what its test asserts. Run that changed test once before touching the implementation, and confirm it fails against the code as it stands today.
19
+ - A test that already passes before any code changes proves nothing about the new behavior. Widen the assertion or add a case until the suite fails against the current implementation.
20
+
21
+ ## What this skill does not cover
22
+
23
+ - A failure with no known cause. Route through `canon:systematic-debugging` first, whose own fix phase already writes the reproducing test as part of finding the cause. This skill starts once the shape of the fix or the feature is already decided.
24
+ - Confirming a change looks right once it has landed, which is a separate concern from whether the test passed and runs after implementation rather than before it.
@@ -23,6 +23,8 @@ So `canon demo compile` writes a second artifact rather than adding fields to a
23
23
 
24
24
  The plan is committed, not scratch. Its timing is a starting point tuned by watching a recording, and the draft cannot reproduce a tuned value, so a recompile over an existing plan refuses and names `--force`.
25
25
 
26
+ `canon-record` is the routed way to run both verbs from a draft path: it compiles only when no plan exists yet, then runs, stopping to report any field still unresolved rather than guessing one.
27
+
26
28
  | Option | Behavior |
27
29
  | --------------- | ----------------------------------------------------------------- |
28
30
  | `--out <dir>` | Directory the plan and its output paths point at, default `demos` |
@@ -64,13 +64,13 @@ An empty set refuses with `no-changes` rather than skipping. Both tests are univ
64
64
 
65
65
  ## Why it is a verb
66
66
 
67
- The decision was three sentences in `claude-autoship` Step 5, applied by a session reading a bulleted list of paths. It failed three times. A driven arm on 2026-08-30 staged `.claude/skills/deploy-check/SKILL.md`, which that list names, and the chain skipped review and opened a draft pull request anyway. The rule was correct and the session did not apply it, and the two fixes before that one were each a rewrite of the same prose.
67
+ The decision was three sentences in `claude-autoship`'s review step, applied by a session reading a bulleted list of paths. It failed three times. A driven arm on 2026-08-30 staged `.claude/skills/deploy-check/SKILL.md`, which that list names, and the chain skipped review and opened a draft pull request anyway. The rule was correct and the session did not apply it, and the two fixes before that one were each a rewrite of the same prose.
68
68
 
69
69
  A rule a session can talk itself out of moves into a verb. That is the same argument the quiz-order draw in `canon teach lesson` was decided on: an instruction is a hope where a verb is a check.
70
70
 
71
71
  ## The written fallback
72
72
 
73
- The verb ships with the CLI and Step 5 ships with the plugin, so a target holding an older binary meets a missing subcommand. The skill body keeps the written list and applies it by hand there, naming which of the two decided the run.
73
+ The verb ships with the CLI and Step 6 ships with the plugin, so a target holding an older binary meets a missing subcommand. The skill body keeps the written list and applies it by hand there, naming which of the two decided the run.
74
74
 
75
75
  The fallback is never a skip. Failing open is the exact defect the verb closes, so a body that skips on anything other than a `skip` record would ship every branch unreviewed the moment the subcommand went absent.
76
76
 
@@ -157,7 +157,7 @@ The list stays written in the skill body as the fallback for a target whose inst
157
157
 
158
158
  #### Memory in the chain
159
159
 
160
- `git-ship` runs its verify gate and then opens on `claude-memory-capture`, which sends what the session learned to the surface that owns it. `autoship` reaches the same step by invoking that skill at its Step 7 rather than restating the order. A fact about a domain carrying an entry in `.claude/context/index.md` is routed to that entry, and `claude-docs` folds it in on the next step, so it ships in the same pull request. Anything no entry owns stays a file in `.canon/memory/`.
160
+ `git-ship` runs its verify gate and then opens on `claude-memory-capture`, which sends what the session learned to the surface that owns it. `autoship` reaches the same step by invoking that skill at its Step 8 rather than restating the order. A fact about a domain carrying an entry in `.claude/context/index.md` is routed to that entry, and `claude-docs` folds it in on the next step, so it ships in the same pull request. Anything no entry owns stays a file in `.canon/memory/`.
161
161
 
162
162
  Capture leads rather than trails because a routed fact edits a tracked file, which has to reach the branch before the commit steps run.
163
163
 
@@ -219,13 +219,14 @@ This section is the corpus the coverage claim is measured against: every name `c
219
219
 
220
220
  ### Build the feature
221
221
 
222
- | Skill | When to use |
223
- | ---------------------------- | ------------------------------------------------------------------- |
224
- | `canon:claude-worktree` | At the plan-to-execute boundary, to get an isolated tree and branch |
225
- | `canon:claude-autoship` | After plan approval, to chain implement, verify, review, draft PR |
226
- | `canon:project-commands` | When the project's own command needs running |
227
- | `canon:systematic-debugging` | When a test fails or a bug surfaces, to force root cause first |
228
- | `canon:claude-ui-test` | After a UI change, to generate e2e tests and a visual checklist |
222
+ | Skill | When to use |
223
+ | ---------------------------- | ------------------------------------------------------------------------- |
224
+ | `canon:claude-worktree` | At the plan-to-execute boundary, to get an isolated tree and branch |
225
+ | `canon:claude-autoship` | After plan approval, to chain implement, verify, review, draft PR |
226
+ | `canon:project-commands` | When the project's own command needs running |
227
+ | `canon:test-first` | Before implementing a planned change, to write and run its test red first |
228
+ | `canon:systematic-debugging` | When a test fails or a bug surfaces, to force root cause first |
229
+ | `canon:claude-ui-test` | After a UI change, to generate e2e tests and a visual checklist |
229
230
 
230
231
  ### Check the work before it leaves the branch
231
232
 
@@ -294,11 +295,14 @@ This section is the corpus the coverage claim is measured against: every name `c
294
295
  | `canon:draft-docs` | For a brand-new `docs/*.md` page, drafted against `standards/docs.md` |
295
296
  | `canon:draft-context` | For a brand-new `.claude/context/<domain>.md` entry, drafted against `standards/context.md` |
296
297
  | `canon:draft-wireframes` | For a brand-new `.claude/wireframes/<surface>.md` file, drafted against `standards/wireframes.md` |
298
+ | `canon:draft-readme` | For a project's `README.md`, drafted against `standards/readme.md` |
297
299
  | `canon:bash-script` | For an interactive, human-facing shell tool |
298
300
  | `canon:bash-cli-script` | For a non-interactive automation, CI, or pipeline script |
299
301
  | `canon:ci-workflow` | For a GitHub Actions workflow file |
300
302
  | `canon:canon-slides-draft` | For a deck, drafted as `.claude/SLIDES.md` and rendered to PowerPoint |
301
303
  | `canon:canon-screencast` | For a recording script with beats and defaults already seeded |
304
+ | `canon:canon-record` | For compiling and running a screencast draft into a recording and a still |
305
+ | `canon:identity` | For a project's logo mark and its social card, drafted through `draft-and-pick`'s pick loop |
302
306
 
303
307
  ### Answer a question at any point
304
308
 
@@ -40,6 +40,7 @@ A cell no source anchors ends in `? verify`, and the preview shows that marker b
40
40
  - `canon:claude-ux-audit` for UX gap detection on existing surfaces
41
41
  - `canon:claude-ux-measure` for what a running surface costs to paint, read against published thresholds
42
42
  - `canon:draft-and-pick` for a call settled by looking, drafting several candidates onto one page and taking your pick
43
+ - `canon:identity` to draft a project's logo mark and compose it into an icon sequence and a social card, through `draft-and-pick`'s own render-and-pick loop
43
44
  - Anthropic's `frontend-design` plugin optional for light visual steering
44
45
 
45
46
  ### When to pick
@@ -13,6 +13,7 @@ description: Enforce planning standards before implementation
13
13
  - Propose the simplest solution that satisfies the requirement before implementing complex patterns.
14
14
  - Write or update tests as part of every implementation plan.
15
15
  - Write the test for a behavior before the code that implements it. Confirm visual output after implementing it, not before.
16
+ - Load the `canon:test-first` skill before writing the implementation for a behavior whose test does not exist yet, and report it rather than proceeding silently when the skill does not resolve.
16
17
  - Run `canon gov test-order` before shipping a branch. Fix what it names as reaching history ahead of its test.
17
18
  - Load the `canon:systematic-debugging` skill before proposing a fix for a failing test, a surfaced bug, or behavior nobody has explained yet, and report it rather than proceeding silently when the skill does not resolve.
18
19
  - Do not modify code without a confirmed plan.
@@ -4,6 +4,7 @@ paths:
4
4
  - '**/routes/**/*.{tsx,jsx,vue,svelte,astro}'
5
5
  - '**/pages/**/*.{tsx,jsx,vue,svelte,astro}'
6
6
  - '**/app/**/page.{tsx,jsx}'
7
+ - '**/components/**/*.{tsx,jsx,vue,svelte,astro}'
7
8
  - '**/*.html'
8
9
  ---
9
10
 
@@ -13,6 +14,7 @@ paths:
13
14
 
14
15
  - A surface is anything the project renders for a person to look at. A framework route is one. A page the project generates is another, whoever generates it.
15
16
  - Judge a generated page by the same rule as a route. Reading its markup reports nothing about how it composes.
17
+ - A component the production build strips out is not a surface, such as a dev-only scenario switcher gated behind a build flag. The build removes it before anything renders, so no comparison exists to capture.
16
18
 
17
19
  ## When to capture
18
20
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/canon",
3
3
  "type": "module",
4
- "version": "4.63.0",
4
+ "version": "4.65.0",
5
5
  "description": "Infrastructure and quality tooling for developer workflows",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -41,9 +41,10 @@
41
41
  "bootstrap": "./scripts/core/bootstrap.sh",
42
42
  "canon:sandbox": "canon sandbox",
43
43
  "canon:sandbox:reset": "canon sandbox reset",
44
- "web:tokens": "canon design css --no-components > web/src/styles/tokens.css.new && (echo '/* Generated by `canon design css --no-components`. Regenerate with `bun run web:tokens`. Do not hand-edit. */'; cat web/src/styles/tokens.css.new) > web/src/styles/tokens.css && rm web/src/styles/tokens.css.new",
44
+ "web:tokens": "bun src/cli.ts design css --no-components > web/src/styles/tokens.css.new && (echo '/* Generated by `canon design css --no-components`. Regenerate with `bun run web:tokens`. Do not hand-edit. */'; cat web/src/styles/tokens.css.new) > web/src/styles/tokens.css && rm web/src/styles/tokens.css.new",
45
45
  "web:dev": "cd web && astro dev",
46
- "web:build": "bun run web:tokens && cd web && astro check && astro build",
46
+ "web:favicon": "bun scripts/core/regen-web-favicon.ts",
47
+ "web:build": "bun run web:tokens && bun run web:favicon && cd web && astro check && astro build",
47
48
  "web:preview": "cd web && astro preview",
48
49
  "web:e2e": "cd web && playwright test"
49
50
  },
@@ -62,9 +63,11 @@
62
63
  "@tailwindcss/vite": "^4.2.4",
63
64
  "@testing-library/jest-dom": "^6.9.1",
64
65
  "@types/bun": "^1.2.10",
66
+ "@types/micromatch": "^4.0.10",
65
67
  "astro": "^6.1.9",
66
68
  "cspell": "^8.17.1",
67
69
  "husky": "^9.1.7",
70
+ "micromatch": "^4.0.8",
68
71
  "prettier": "^3.8.1",
69
72
  "prettier-plugin-astro": "^0.14.1",
70
73
  "prettier-plugin-tailwindcss": "^0.7.2",
@@ -202,7 +202,7 @@ const summary = {
202
202
  }
203
203
 
204
204
  const fixture = {
205
- // Read by web/src/components/AgentView.astro. Every field below is either a
205
+ // Read by web/src/components/agent-view.astro. Every field below is either a
206
206
  // live read or a hand transcription, and this header is what says which.
207
207
  generatedBy: "scripts/core/regen-agent-fixture.sh",
208
208
  readAt: READ_AT,
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Writes web/public/favicon.svg from assets/brand/mark.svg and the design
3
+ * source's two accent values.
4
+ *
5
+ * The page is the fourth surface to carry the mark as a favicon and the only
6
+ * one that can answer for itself. `regen-hero.sh` and `src/design/render.ts`
7
+ * each bake a single literal, because both embed the mark as a data URI and a
8
+ * data URI has no CSS context. A file served at its own URL does have one, so
9
+ * this copy carries a `prefers-color-scheme` branch and tracks the reader's
10
+ * theme rather than picking one accent for everybody.
11
+ *
12
+ * `web/public/favicon.svg` was a symlink to the source before this, and the
13
+ * source fills `currentColor`, which resolves to black with no CSS context.
14
+ * That is what painted the tab icon black on every surface the page reaches.
15
+ * The source keeps `currentColor`, since the hero topbar embeds the same file
16
+ * inline and wants it to inherit.
17
+ */
18
+ import { writeFileSync } from 'node:fs'
19
+
20
+ const root = new URL('../..', import.meta.url).pathname
21
+
22
+ const mark = (await Bun.file(`${root}assets/brand/mark.svg`).text()).trim()
23
+ if (!mark) {
24
+ console.error('regen-web-favicon: assets/brand/mark.svg read empty')
25
+ process.exit(1)
26
+ }
27
+
28
+ const tokenCss = await Bun.file(`${root}web/src/styles/tokens.css`).text()
29
+ const read = (name: string): string => {
30
+ const match = tokenCss.match(
31
+ new RegExp(`--color-${name}:\\s*(#[0-9a-fA-F]{3,8})`),
32
+ )
33
+ if (!match) {
34
+ console.error(
35
+ `regen-web-favicon: tokens.css carries no --color-${name}, refusing to write a colorless favicon`,
36
+ )
37
+ process.exit(1)
38
+ }
39
+ return match[1] as string
40
+ }
41
+
42
+ const dark = read('accent')
43
+ const light = read('light-accent')
44
+
45
+ // The shapes only, with the source's authoring comment dropped. Both fills are
46
+ // replaced by a rule rather than an attribute so one branch can flip both.
47
+ const shapes = mark
48
+ .replace(/<!--[\s\S]*?-->/, '')
49
+ .trim()
50
+ .replace(/^<svg[^>]*>/, '')
51
+ .replace(/<\/svg>$/, '')
52
+ .replaceAll(' fill="currentColor"', '')
53
+ .trim()
54
+
55
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="10 10 80 80">
56
+ <style>
57
+ path, rect { fill: ${light}; }
58
+ @media (prefers-color-scheme: dark) { path, rect { fill: ${dark}; } }
59
+ </style>
60
+ ${shapes.replace(/\n\s*/g, '\n ')}
61
+ </svg>
62
+ `
63
+
64
+ writeFileSync(`${root}web/public/favicon.svg`, svg)
65
+ console.log(
66
+ `regen-web-favicon: wrote web/public/favicon.svg (${light} / ${dark})`,
67
+ )
@@ -42,6 +42,10 @@ export const AUTHORING_CASES: readonly SkillCase[] = [
42
42
  'Write a brand-new docs page for the capture command, nothing under docs/ covers it yet.',
43
43
  expect: 'draft-docs',
44
44
  },
45
+ {
46
+ prompt: 'This project has no README.md at all, write one from scratch.',
47
+ expect: 'draft-readme',
48
+ },
45
49
  {
46
50
  prompt:
47
51
  'Write a context entry for the payments domain, there is no .claude/context page for it yet.',
@@ -79,6 +79,11 @@ export const CLAUDE_WORKFLOW_CASES: readonly SkillCase[] = [
79
79
  prompt: 'Post a formal review with findings on that open pull request.',
80
80
  expect: 'claude-pr-review',
81
81
  },
82
+ {
83
+ prompt:
84
+ 'The screencast draft is finished. Turn it into an actual video now.',
85
+ expect: 'canon-record',
86
+ },
82
87
  {
83
88
  prompt:
84
89
  'Look over everything that changed on this branch for bugs and edge cases.',
@@ -12,6 +12,11 @@ export const MISC_CASES: readonly SkillCase[] = [
12
12
  'Show me a few different treatments for this callout so I can pick one by looking.',
13
13
  expect: 'draft-and-pick',
14
14
  },
15
+ {
16
+ prompt:
17
+ 'This project has no logo yet. Draft one and give me a social card to go with it.',
18
+ expect: 'identity',
19
+ },
15
20
  {
16
21
  prompt: 'Fire up the dev server the way this project documents it.',
17
22
  expect: 'project-commands',
@@ -29,6 +34,11 @@ export const MISC_CASES: readonly SkillCase[] = [
29
34
  prompt: "This test just started failing and I don't know why yet.",
30
35
  expect: 'systematic-debugging',
31
36
  },
37
+ {
38
+ prompt:
39
+ "I'm about to write the retry helper we settled on and nothing covers it yet, so get the failing case in first.",
40
+ expect: 'test-first',
41
+ },
32
42
  {
33
43
  prompt: 'Does our github about text still match what the readme says?',
34
44
  expect: 'repo-metadata',
@@ -16,9 +16,13 @@
16
16
  --color-success: #61c454;
17
17
  --color-light-background: #faf7f2;
18
18
  --color-light-surface: #f4efe6;
19
+ --color-light-chrome: #ede4d6;
19
20
  --color-light-text: #1a1815;
21
+ --color-light-text-body: #3d3630;
22
+ --color-light-text-secondary: #5c544b;
20
23
  --color-light-muted: #726b62;
21
24
  --color-light-accent: #a4471c;
25
+ --color-light-success: #2d6b22;
22
26
  --color-light-border: #e4dcd0;
23
27
  --space-xs: 6px;
24
28
  --space-sm: 12px;
@@ -53,15 +57,16 @@
53
57
  --radius-marker: 999px;
54
58
  }
55
59
 
56
- /* The record declares no light counterpart for chrome, text-body, text-secondary, success, so
57
- a light-ground surface using one is reading a dark value. Declare the
58
- counterpart in src/design/tokens.ts rather than overriding it here. */
59
60
  [data-theme='light'] {
60
61
  --color-background: var(--color-light-background);
61
62
  --color-surface: var(--color-light-surface);
63
+ --color-chrome: var(--color-light-chrome);
62
64
  --color-text: var(--color-light-text);
65
+ --color-text-body: var(--color-light-text-body);
66
+ --color-text-secondary: var(--color-light-text-secondary);
63
67
  --color-muted: var(--color-light-muted);
64
68
  --color-accent: var(--color-light-accent);
69
+ --color-success: var(--color-light-success);
65
70
  --color-border: var(--color-light-border);
66
71
  }
67
72
 
@@ -180,12 +180,29 @@ export const TOKENS: DesignTokens = {
180
180
  intent: 'cards and panels on a light ground',
181
181
  value: '#f4efe6',
182
182
  },
183
+ {
184
+ role: 'light-chrome',
185
+ intent: 'the window titlebar, one step above the canvas',
186
+ value: '#ede4d6',
187
+ },
183
188
  {
184
189
  role: 'light-text',
185
190
  intent: 'primary text on a light ground',
186
191
  value: '#1a1815',
187
192
  grounds: LIGHT_GROUNDS,
188
193
  },
194
+ {
195
+ role: 'light-text-body',
196
+ intent: 'default body copy on a light ground',
197
+ value: '#3d3630',
198
+ grounds: LIGHT_GROUNDS,
199
+ },
200
+ {
201
+ role: 'light-text-secondary',
202
+ intent: 'labels, captions, supporting copy on a light ground',
203
+ value: '#5c544b',
204
+ grounds: LIGHT_GROUNDS,
205
+ },
189
206
  {
190
207
  role: 'light-muted',
191
208
  intent: 'secondary text on a light ground',
@@ -198,6 +215,12 @@ export const TOKENS: DesignTokens = {
198
215
  value: '#a4471c',
199
216
  grounds: LIGHT_GROUNDS,
200
217
  },
218
+ {
219
+ role: 'light-success',
220
+ intent: 'confirmations, rendered and in the terminal, on light',
221
+ value: '#2d6b22',
222
+ grounds: ['light-background'],
223
+ },
201
224
  {
202
225
  role: 'light-border',
203
226
  intent: 'rules and panel edges on light',
@@ -220,10 +220,11 @@ export const STAGES: readonly Stage[] = [
220
220
  success: 'Tooling paths clean',
221
221
  },
222
222
  {
223
- // `.claude/DESIGN.md` and the base stylesheet are both written from
224
- // `src/design/tokens.ts` and neither is edited by hand. Two artifacts from
225
- // one source is the cost of the token move, and a render step that has to
226
- // run is only safe while something fails when it did not, which is this.
223
+ // `.claude/DESIGN.md`, the base stylesheet, the web stylesheet, and the
224
+ // tab icon are all written from `src/design/tokens.ts` and none is edited
225
+ // by hand. Four artifacts from one source is the cost of the token move,
226
+ // and a render step that has to run is only safe while something fails
227
+ // when it did not, which is this.
227
228
  id: 'design',
228
229
  label: 'Design',
229
230
  checks: [
@@ -244,6 +245,28 @@ export const STAGES: readonly Stage[] = [
244
245
  failure:
245
246
  'The base stylesheet drifted from the token source. Run bun run check and commit src/design/base.css.',
246
247
  },
248
+ {
249
+ kind: 'command',
250
+ argv: ['bun', 'run', 'web:tokens'],
251
+ failure: 'Web token regen failed',
252
+ },
253
+ {
254
+ kind: 'drift',
255
+ pathspec: 'web/src/styles/tokens.css',
256
+ failure:
257
+ 'The web stylesheet drifted from the token source. Run bun run check and commit web/src/styles/tokens.css.',
258
+ },
259
+ {
260
+ kind: 'command',
261
+ argv: ['bun', 'run', 'web:favicon'],
262
+ failure: 'Web favicon regen failed',
263
+ },
264
+ {
265
+ kind: 'drift',
266
+ pathspec: 'web/public/favicon.svg',
267
+ failure:
268
+ 'The tab icon drifted from the token source. Run bun run check and commit web/public/favicon.svg.',
269
+ },
247
270
  ],
248
271
  success: 'Design source clean',
249
272
  },
@@ -46,6 +46,8 @@ Scoped to the README at a repository root. A nested README documenting a folder,
46
46
  - Do not include license text. Reference the `LICENSE` file.
47
47
  - Do not include detailed contribution guidelines. Reference `CONTRIBUTING.md`.
48
48
  - Do not include extensive troubleshooting guides. Use a wiki or separate documentation.
49
+ - A README that points to the project's own context entries instead of restating them satisfies the required list by routing rather than carrying. A repository documented in `docs/` or `.claude/context/` names the entry point and the listing command, then stops. The reader is one hop away, the same way `## Badges` treats zero as complete.
50
+ - A README missing an H1, or whose headings restate the tool that scaffolded it rather than the project, is unedited generator output rather than a page anyone wrote. Draft over it. Nothing here asks a session to preserve a section a scaffold wrote and nobody replaced.
49
51
 
50
52
  ### Badges
51
53
 
@@ -50,10 +50,16 @@ export default defineConfig([
50
50
  ],
51
51
  'check-file/folder-naming-convention': [
52
52
  'error',
53
- { 'src/**/!(__tests__|pages)': 'KEBAB_CASE' },
53
+ { 'src/**/!(__tests__)/': 'KEBAB_CASE' },
54
54
  ],
55
55
  },
56
56
  },
57
+ {
58
+ files: ['src/pages/**/*.{ts,tsx,astro}'],
59
+ rules: {
60
+ 'check-file/folder-naming-convention': 'off',
61
+ },
62
+ },
57
63
  {
58
64
  files: ['**/*.{jsx,tsx}'],
59
65
  plugins: {
@@ -21,7 +21,7 @@ The astro stack covers Astro + TypeScript projects: content sites, marketing sit
21
21
  - `vitest.config.ts`: uses `getViteConfig` from `astro/config` (not `mergeConfig`). jsdom, globals, setup file, `passWithNoTests: true`, v8 coverage, `**/*.astro` in coverage excludes.
22
22
  - `playwright.config.ts`: all browsers, `webServer` runs `bun run build && bun run preview` on port `4321` plus `WORKTREE_PORT_OFFSET`, `reuseExistingServer: false`. Astro's dev/prod gap is wide (MDX, island hydration, asset optimization), so E2E always tests the built `dist/`. `DIST_PREBUILT` set in the environment drops the `build` half, running `bun run preview` alone against a `dist/` a prior CI job already produced.
23
23
  - `tsconfig.json`: extends `astro/tsconfigs/strict`, adds `skipLibCheck`, `vitest/globals` and `@testing-library/jest-dom` in types, `@/` paths.
24
- - `eslint.config.js`: overrides the web layer. Adds `eslint-plugin-astro` (`.astro` parser via `astro-eslint-parser`). React-hooks scoped to `.jsx`/`.tsx` only (`.astro` is not React). The shared block's `files` selector includes `.astro`, so `check-file/filename-naming-convention` reaches `.ts`, `.tsx`, and `.astro` under `KEBAB_CASE`, overriding Astro's own PascalCase component convention deliberately, on the ground that a component's name in markup comes from the import binding rather than the filename. `.js` and `.jsx` stay out of the rule's own pattern, matching `web` and `nextjs`. `check-file/folder-naming-convention`'s `src/**/!(__tests__|pages)` pattern reaches no folder under a real ESLint run, `.astro`-only or otherwise. See `.claude/context/tooling.md` for the measurement.
24
+ - `eslint.config.js`: overrides the web layer. Adds `eslint-plugin-astro` (`.astro` parser via `astro-eslint-parser`). React-hooks scoped to `.jsx`/`.tsx` only (`.astro` is not React). The shared block's `files` selector includes `.astro`, so `check-file/filename-naming-convention` reaches `.ts`, `.tsx`, and `.astro` under `KEBAB_CASE`, overriding Astro's own PascalCase component convention deliberately, on the ground that a component's name in markup comes from the import binding rather than the filename. `.js` and `.jsx` stay out of the rule's own pattern, matching `web` and `nextjs`. `check-file/folder-naming-convention` reaches every `src/**` folder except `__tests__` and the whole `pages/` subtree, which carries its own off-block for the bracket-named dynamic routes and nested slug folders Astro's file-based routing produces. See `.claude/context/tooling.md` for the measurement.
25
25
 
26
26
  ## Typecheck
27
27
 
@@ -59,15 +59,16 @@ export default defineConfig([
59
59
  ],
60
60
  'check-file/folder-naming-convention': [
61
61
  'error',
62
- { 'src/**/!(__tests__)': 'KEBAB_CASE' },
62
+ { 'src/**/!(__tests__)/': 'KEBAB_CASE' },
63
63
  ],
64
64
  },
65
65
  },
66
66
  {
67
- // App Router route and layout files export non-component values (metadata, route handlers), which this rule flags as violations.
67
+ // App Router route and layout files export non-component values (metadata, route handlers) and use bracket, paren, and at-sign folder syntax (dynamic segments, route groups, parallel slots), which these rules flag as violations.
68
68
  files: ['src/app/**/*.{ts,tsx}'],
69
69
  rules: {
70
70
  'react-refresh/only-export-components': 'off',
71
+ 'check-file/folder-naming-convention': 'off',
71
72
  },
72
73
  },
73
74
  {
@@ -19,7 +19,7 @@ The nextjs stack covers Next.js + TypeScript projects using the App Router. It s
19
19
  - `next.config.ts`: `agentRules: false` stops Next from regenerating `AGENTS.md`/`CLAUDE.md` on every run, which would otherwise compete with the root `CLAUDE.md`. `turbopack.root: import.meta.dirname` pins the workspace root, silencing Next's multi-lockfile inference warning in any checkout carrying more than one lockfile above the project, a worktree included.
20
20
  - `vitest.config.ts`: plain `defineConfig`, no `mergeConfig` or `getViteConfig` since Next has no Vite config to merge from. jsdom, globals, setup file, `passWithNoTests: true`, v8 coverage, `.next/**` in test excludes.
21
21
  - `playwright.config.ts`: all browsers, `webServer` runs `bun run build && bun run preview` on port `3000` plus `WORKTREE_PORT_OFFSET`, `reuseExistingServer: false`. `DIST_PREBUILT` drops the `build` half, matching the astro stack's flag.
22
- - `eslint.config.js`: overrides the web layer with `.next` and `next-env.d.ts` added to `globalIgnores`, keeping `react-hooks`/`react-refresh` since this stack still ships React components. `react-refresh/only-export-components` is `off` for `src/app/**`, since route and layout files export non-component values the rule would otherwise flag.
22
+ - `eslint.config.js`: overrides the web layer with `.next` and `next-env.d.ts` added to `globalIgnores`, keeping `react-hooks`/`react-refresh` since this stack still ships React components. `react-refresh/only-export-components` is `off` for `src/app/**`, since route and layout files export non-component values the rule would otherwise flag. `check-file/folder-naming-convention` is `off` for the same subtree, since App Router's bracket, paren, and at-sign folder syntax (dynamic segments, route groups, parallel slots) fails `KEBAB_CASE`.
23
23
 
24
24
  ## Port
25
25
 
@@ -57,7 +57,7 @@ export default defineConfig([
57
57
  ],
58
58
  'check-file/folder-naming-convention': [
59
59
  'error',
60
- { 'src/**/!(__tests__)': 'KEBAB_CASE' },
60
+ { 'src/**/!(__tests__)/': 'KEBAB_CASE' },
61
61
  ],
62
62
  },
63
63
  },
@@ -110,7 +110,7 @@ Append rows:
110
110
 
111
111
  `canon tooling verify <stack>` is the only automated caller of `bun run screenshot`, running it for any stack whose `package.json` declares the script and asserting that PNG files land under `screenshots/`. It counts them with a recursive find carrying no depth limit, so the section folders the seed writes satisfy the assertion without a change to it. Do not flatten the layout to protect that check. No ship chain captures a screenshot, so the output path the seed writes is a contract that one verifier reads rather than a default a ship step depends on.
112
112
 
113
- `governance/rules/ui/440-surface-capture.md` is what asks a session to run the capture after a route changes. It fires on route and page files rather than on every component, so a shared component changing every screen fires nothing and the operator runs the capture by hand.
113
+ `governance/rules/ui/440-surface-capture.md` is what asks a session to run the capture after a surface changes. It now fires on every component file too, reversing the route-and-page-only scope this reference once described, after a shipped batch of components carried no capture and a defect went unseen. The rule body states its own exemption for a component the production build strips out, so a reader chasing that case reads it there rather than here.
114
114
 
115
115
  The sweep under `screenshots/` is ignored again, and only a flagged case's `evidence/` output tracks in git, so the first capture a scaffolded target runs after this change is the baseline it commits there.
116
116