@erclx/canon 4.79.0 → 4.81.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/claude/.claude-plugin/plugin.json +1 -1
  2. package/claude/skills/canon-rollout/SKILL.md +1 -1
  3. package/claude/skills/design-extract/SKILL.md +5 -1
  4. package/claude/skills/git-pr/SKILL.md +3 -3
  5. package/claude/skills/git-pr/references/labels.md +1 -1
  6. package/claude/skills/sketch-design/REQUIREMENT.md +38 -0
  7. package/claude/skills/sketch-design/SKILL.md +92 -0
  8. package/claude/skills/teach-workspace/SKILL.md +23 -1
  9. package/claude/skills/teach-workspace/references/lesson-craft.md +11 -0
  10. package/docs/agents/audits.md +2 -2
  11. package/docs/agents/commands.md +6 -4
  12. package/docs/agents/design-board.md +13 -11
  13. package/docs/agents/index.md +1 -1
  14. package/docs/agents/install-and-sync.md +3 -3
  15. package/docs/agents/label-coverage.md +1 -1
  16. package/docs/agents/teach.md +16 -0
  17. package/docs/target-projects.md +1 -1
  18. package/docs/workflow/ai-workflow.md +1 -0
  19. package/docs/workflow/visual-design-workflow.md +1 -0
  20. package/package.json +1 -1
  21. package/src/audits/baseline.ts +17 -5
  22. package/src/claude/cases/misc.ts +5 -0
  23. package/src/commands/design.ts +24 -8
  24. package/src/commands/sync.ts +1 -1
  25. package/src/commands/teach.ts +118 -0
  26. package/src/design/board.ts +130 -47
  27. package/src/gate/measures.ts +4 -4
  28. package/src/labels/map.ts +19 -5
  29. package/src/legacy-path.ts +15 -0
  30. package/src/migrate/plan.ts +1 -0
  31. package/src/migrate/surface-roots.ts +5 -6
  32. package/src/project-root.ts +16 -0
  33. package/src/surface-root.ts +7 -14
  34. package/src/sync/stamp.ts +22 -17
  35. package/src/targets/sweep.ts +6 -2
  36. package/src/teach/render.ts +126 -0
@@ -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.79.0",
4
+ "version": "4.81.0",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -36,7 +36,7 @@ Every target in every wave takes the same branch and the same title, written fro
36
36
  - Title: `chore(agents): <what changed in that target>`.
37
37
  - Body: the sections `${CLAUDE_SKILL_DIR}/../../standards/pr.md` fixes, describing what that target received.
38
38
  - Body, one line: name this toolkit as the source of the changed files and where to change the content upstream. A reader in that repository months later has no other route to the fact that these files are toolkit-managed and that a local edit is lost at the next wave.
39
- - Body, no version number anywhere in it. `${CLAUDE_SKILL_DIR}/../../standards/versioning.md` permits a semver tag only where the pull request cuts a release, and `.claude/canon/config.json` in the target already records `syncedAt` and a sha256 per synced file. A version in the body is a second copy of a fact the target holds canonically, and it is the copy that goes stale while the pull request sits open.
39
+ - Body, no version number anywhere in it. `${CLAUDE_SKILL_DIR}/../../standards/versioning.md` permits a semver tag only where the pull request cuts a release, and `canon/config/config.json` in the target already records `syncedAt` and a sha256 per synced file. A version in the body is a second copy of a fact the target holds canonically, and it is the copy that goes stale while the pull request sits open.
40
40
 
41
41
  The title withholds the source and the body supplies it, because the two have different audiences. `agents` says what changed without saying where it came from, which is what a target's own history wants, and the source line is for a reader inside that repository rather than for whoever merges.
42
42
 
@@ -17,7 +17,7 @@ Step 1 carries two more stops that apply to one path only. Do not evaluate them
17
17
  Check the project root for UI surfaces: `src/ui.ts`, `src/ui.tsx`, `src/components/**`, `scripts/lib/ui.sh`, and any `*.css`, `tailwind.config.*`, or `theme.*`.
18
18
 
19
19
  - Any match takes the source path. Existing code defines the system, so every filled cell traces to a value already in the tree.
20
- - No match takes the greenfield path. Nothing anchors a value, so every filled cell is a proposal and carries the tag that says so.
20
+ - No match takes the greenfield path. Nothing anchors a value there unless a picked reference does, so name `sketch-design` as the first move: a picked reference anchors a cell where a paragraph alone cannot. A direct run of this skill still falls through to the proposal-from-personality rules in Step 4, tag included, so this skill stays runnable on its own with nothing rendered yet.
21
21
 
22
22
  The project decides this, never a user flag or an argument. Announce which path ran in one line before Step 2, since the two produce different-looking output from the same skill.
23
23
 
@@ -39,6 +39,8 @@ On the source path, also read the UI surfaces matched in Step 1 plus `canon docs
39
39
 
40
40
  On the greenfield path, also read `canon/ARCHITECTURE.md` for platform, tech stack, and surface type. Do not scan `src/`, stylesheets, or UI modules. Step 1 already established they hold nothing.
41
41
 
42
+ On the greenfield path, also check `.canon/review/evidence/*/design-handoff.md` for a file the `sketch-design` skill wrote, taking the most recently modified match when more than one exists. Read it when found. Its presence is what Step 4 traces cells from instead of proposing them.
43
+
42
44
  Run these reads in parallel. Do not speculatively recurse into every directory.
43
45
 
44
46
  ## Step 3: fetch the seed template
@@ -72,6 +74,8 @@ On the source path, the tag marks the exception. On the greenfield path it marks
72
74
 
73
75
  Anchor every proposal to a signal, never to a default. "Calm and dense" pins muted grays and tight spacing. A requirements non-goal of "no motion" makes Motion read `No animation.` with no tag. A CLI-only surface leans Typography monospaced and keeps Borders minimal.
74
76
 
77
+ - **Traced cells**: when Step 2 matched a `design-handoff.md` file, fill only the fields it actually states, no tag, the same traced-versus-proposed distinction the source path draws for a stylesheet value.
78
+ - The handoff carries hex and family and size, never Weight, Line height, or a color's Intent. Those cells, every field of a role the handoff never names, and Motion and Iconography regardless, still follow the proposal rules below, tag included.
75
79
  - **Personality**: transcribe the `## Personality` paragraph from `canon/REQUIREMENTS.md` verbatim. This is the one section that is not a proposal. No tag.
76
80
  - **Color**: one row per role. Rewrite the Intent cell in personality language, for example `warm off-white page canvas` instead of the seed default `page canvas`. Propose hex values matching the personality. Dense and calm gives low saturation and high text contrast. Playful gives saturated accents. Every Intent and Value cell gets `? verify`.
77
81
  - **Typography**: one row per role. Propose families fitting the platform, system UI for web, monospaced for CLI tools, serif for editorial, and a harmonious scale. Every cell gets `? verify`.
@@ -11,7 +11,7 @@ Read these files in parallel:
11
11
 
12
12
  - `${CLAUDE_SKILL_DIR}/../../standards/branch.md`: branch format, valid types, and constraints
13
13
  - `${CLAUDE_SKILL_DIR}/../../standards/pr.md`: structure, rules, and banned phrases
14
- - `${CLAUDE_SKILL_DIR}/references/labels.md`: label map format, matching, and the missing-label warning. Skip when the project has no `.claude/canon/pr-labels.toml`.
14
+ - `${CLAUDE_SKILL_DIR}/references/labels.md`: label map format, matching, and the missing-label warning. Skip when the project has no `canon/config/pr-labels.toml` and no `.claude/canon/pr-labels.toml`.
15
15
  - `${CLAUDE_SKILL_DIR}/../../standards/markdown.md`: banned words, punctuation, and formatting for all generated text
16
16
  - The `write-human` skill: voice, rhythm, and sentence construction for all generated text
17
17
  - `${CLAUDE_SKILL_DIR}/../../standards/versioning.md`: phase label vs semver discipline
@@ -119,7 +119,7 @@ A `reason` of `no-map` is the answer that the project declared no map, which ear
119
119
 
120
120
  Every other `reason` is a map or a range the verb could not read, which is `unreadable-map`, `no-domains`, `no-base`, and `unreadable-changes`, plus `bad-base` for a ref this skill resolved wrongly. Take the fallback below and warn beside the result line, naming the reason. A map with a typo in it still has rows a prefix match can reach, and reading the refusal as an absence would open the pull request with no labels and nothing said, which is the surface merging bare that the verb exists to name.
121
121
 
122
- The fallback is reading `.claude/canon/pr-labels.toml` and matching it against the name-only diff per `${CLAUDE_SKILL_DIR}/references/labels.md`. It also covers no record coming back at all, which is an installed `canon` predating the verb, since a skill reaches a target the moment it merges while the CLI reaches one only when a release publishes. The fallback labels correctly and reports no uncovered path, which is the half only the verb carries.
122
+ The fallback is reading `canon/config/pr-labels.toml`, or `.claude/canon/pr-labels.toml` when the project has not moved, and matching it against the name-only diff per `${CLAUDE_SKILL_DIR}/references/labels.md`. It also covers no record coming back at all, which is an installed `canon` predating the verb, since a skill reaches a target the moment it merges while the CLI reaches one only when a release publishes. Naming both spellings matters exactly here: the binary old enough to need this fallback is the same binary that may predate the move, so the project's map can still sit at the older path. The fallback labels correctly and reports no uncovered path, which is the half only the verb carries.
123
123
 
124
124
  Leave `pr_labels` empty when no map resolves or no prefix matches, which skips the labelling command rather than running it against nothing.
125
125
 
@@ -223,7 +223,7 @@ Respond with one line, using the `url` the final command printed:
223
223
 
224
224
  Add a line for each `uncovered` path the labels step reported, naming the path and the map it belongs in:
225
225
 
226
- `⚠️ No label covers <path>. Add a row to .claude/canon/pr-labels.toml or a [declined] entry.`
226
+ `⚠️ No label covers <path>. Add a row to canon/config/pr-labels.toml or a [declined] entry.`
227
227
 
228
228
  Add a further line only when the labelling command printed its warning, quoting the label `gh` refused:
229
229
 
@@ -17,7 +17,7 @@ Does not govern:
17
17
 
18
18
  ## Map format
19
19
 
20
- The map lives at `.claude/canon/pr-labels.toml` in the project root. Each key under `[domains]` is a label name and its value is the list of path prefixes that earn it.
20
+ The map lives at `canon/config/pr-labels.toml` in the project root, or `.claude/canon/pr-labels.toml` for a project that has not moved. Each key under `[domains]` is a label name and its value is the list of path prefixes that earn it.
21
21
 
22
22
  ```toml
23
23
  [domains]
@@ -0,0 +1,38 @@
1
+ ---
2
+ name: sketch-design
3
+ description: Why a greenfield design value needs a render behind it before it reaches DESIGN.md, and where the reference loop stops short of writing that file itself
4
+ ---
5
+
6
+ # Sketch design requirement
7
+
8
+ ## Gap
9
+
10
+ Without this skill, a session asked to match a look an operator has in mind either invents token values from a personality paragraph alone, which is what `design-extract`'s greenfield path already does and states plainly as a proposal rather than a fact, or builds a one-off render-and-pick comparison by hand for each request, restating `draft-and-pick`'s candidate page, hand-off, and pick mechanics from scratch with nothing comparing the copy to the shipped one. Neither path lets an operator point at a reference image or a live page and have the values that follow trace back to what was actually looked at and picked.
11
+
12
+ ## Must
13
+
14
+ - Take at least two operator-supplied references, each a path to an image on disk or a URL to render, and gather them through this skill's own Step 1 prompt rather than a caller-supplied argument
15
+ - Render every URL reference before drafting, and report a capture refusal naming an unrecognized source as a version gap rather than a broken reference
16
+ - Follow `draft-and-pick`'s candidate page, render, hand-off, structured pick, and archival capture mechanics rather than restating them
17
+ - Trace the picked reference's color, type, and spacing by reading its render, and carry the operator's own stated reason for the pick forward as written
18
+ - Write the handoff one field per line, grouped under the section names `standards/design.md` already fixes for `canon/DESIGN.md`, so `design-extract` can read it mechanically
19
+ - Resolve the handoff file and the archival capture against the main worktree root, never against a linked worktree this run happens to be building in
20
+
21
+ ## Must not
22
+
23
+ - Write to `canon/DESIGN.md`. Tracing values into the handoff is this skill's whole output. Filling the design document from them is `design-extract`'s Step 2, kept as the one place that file is written.
24
+ - Take the pick on the operator's behalf. That call is theirs on every arm that is defensible, the same boundary `draft-and-pick` states for itself.
25
+ - Restate `draft-and-pick`'s render, hand-off, pick, or loop mechanics, or `canon capture`'s render mechanics
26
+ - Assume this skill's own invocation frequency needs no check. `design-extract` Step 1 points to it as the first move on the greenfield path, and an operator can also type its name directly, so whether anything else reaches for it has an answer only after a review pass reads usage back, rather than at creation time.
27
+
28
+ ## Guards
29
+
30
+ - `canon` not on PATH: stop, since the render loop and the capture calls both need it
31
+ - Fires on a direct request or on `design-extract`'s own pointer, never on the model's own read that a project could use a design direction
32
+
33
+ ## Out of scope
34
+
35
+ - `design-extract` reads the handoff this skill writes and fills `canon/DESIGN.md` from it. That fill, and the seed template it starts from, stay entirely that skill's own.
36
+ - `draft-and-pick` owns the render, hand-off, pick, and loop mechanics this skill invokes rather than reimplements
37
+ - `canon capture` and `canon serve` own the render and the address mechanics
38
+ - Auditing an implemented UI against its tokens, which `ux-audit` owns
@@ -0,0 +1,92 @@
1
+ ---
2
+ name: sketch-design
3
+ description: Runs a reference-and-pick loop against operator-supplied images and URLs, then writes the picked reference's traced color, type, and spacing values to a handoff file `design-extract` reads. Use when asked to "sketch a design direction", "compare these reference sites", "pick a look before we build DESIGN.md", "show me a few design directions", or as the first move on `design-extract`'s greenfield path when nothing has been rendered yet. Do NOT use when a value is already anchored to running code or a stylesheet, which is `design-extract`'s source path, or to draft the design system document itself, which stays `design-extract`'s job.
4
+ ---
5
+
6
+ # Sketch design
7
+
8
+ `design-extract`'s greenfield path proposes color, type, and spacing from a personality paragraph alone, with no render behind any value. This skill puts operator-supplied references in front of a render instead, so the values `design-extract` fills are traced to a picked reference rather than invented from prose.
9
+
10
+ ## Guards
11
+
12
+ - If `canon` is not on PATH, stop: `❌ canon CLI not found.`
13
+ - Draft no comparison for a reference set the operator has not supplied. This skill fires on a direct request or on `design-extract` pointing at it, never on the model's own read that a project could use one.
14
+
15
+ ## Step 1: gather the references and render what needs it
16
+
17
+ Ask the operator, in this step's own prompt, for the references to compare: a path to an image already on disk for each one already rendered somewhere, and a full URL for each live page to render. Take at least two.
18
+
19
+ Follow `${CLAUDE_SKILL_DIR}/../draft-and-pick/SKILL.md` Step 1 items 2 through 5 to name the decision and derive `<dest>`, with the decision fixed rather than derived: "which of these references the project's design should draw from." Arm 0 is the current state when `canon/DESIGN.md` already carries filled Color, Typography, or Spacing cells. Arms start at 1 otherwise, since a greenfield project asking for this loop has nothing shipped yet to anchor a baseline arm against.
20
+
21
+ Render every URL reference before drafting, one call per reference since each targets its own file:
22
+
23
+ ```bash
24
+ canon capture <url> --selector body --out <dest>/refs/arm-<id>.png
25
+ ```
26
+
27
+ `--selector body` is the right choice here, unlike inside `draft-and-pick`'s own arms: the page belongs to someone else, so nothing on it is a wrapper this skill can name, and a full-page render is what a reference comparison needs. An operator-supplied image needs no render. Reference it directly from `<dest>/refs/` by copying it there under the same `arm-<id>` naming, so every arm resolves through one path shape regardless of source.
28
+
29
+ `canon capture <url>` reaches a target only once a release ships the URL-source capability. Report a refusal naming an unrecognized source as that gap rather than a broken reference, and name upgrading the installed `canon` as the repair.
30
+
31
+ ## Step 2: author the candidate set as one page
32
+
33
+ Follow `${CLAUDE_SKILL_DIR}/../draft-and-pick/SKILL.md` Step 2, with one substitution: each arm is an `<img>` of the file `<dest>/refs/arm-<id>.png` produced in Step 1, not markup drafted from scratch. Wrap each in the shared class that skill's Step 2 fixes, label it with its id and its cost, and inline everything else the page needs, per that step's rules.
34
+
35
+ ## Step 3: render, hand off, and take the pick
36
+
37
+ Follow `${CLAUDE_SKILL_DIR}/../draft-and-pick/SKILL.md` Steps 3 and 4 unchanged: render the combined page, hand over the address, and put the choice to the operator through the structured question surface. Take whatever reason the operator's answer already states, whether through the surface's own free-text escape or in the surrounding turn, as the reason Step 5 records. The surface takes the pick and the reason in one turn when the operator gives both together, rather than needing a second one.
38
+
39
+ ## Step 4: loop, when the operator asks for a change
40
+
41
+ The arms here are fixed references rather than drafted markup, so nothing about a picked arm's own content can be revised. A loop here narrows the set instead: drop an arm the operator rules out, or add a new reference by returning to Step 1 for it alone. Return to Step 2 with the adjusted set and repeat Step 3. Stop when the operator says the pick is right, the same rule `draft-and-pick` Step 5 states, never when the set stops changing.
42
+
43
+ ## Step 5: trace and write the handoff
44
+
45
+ 1. Carry forward the reason Step 3's pick already stated, as written rather than paraphrased. Ask the operator to state, in their own words, why the picked reference won only when the pick arrived with no reason attached.
46
+ 2. Read the picked arm's render at `<dest>/refs/arm-<id>.png` and trace its color, type, and spacing by looking, the same reading a person does against a screenshot. Name a role only where the reference actually shows it. Leave one out rather than guessing.
47
+ 3. Write `.canon/review/evidence/<slug>/design-handoff.md`, resolved against the main worktree root rather than the linked worktree this run may be building in, since shared session scratch always resolves there. One field per line, grouped under the section names `${CLAUDE_SKILL_DIR}/../../standards/design.md` fixes, so `design-extract` reads it mechanically:
48
+
49
+ ```markdown
50
+ # Design handoff
51
+
52
+ Picked: arm-<id> (<label>)
53
+ Reason: <the operator's own sentence from item 1>
54
+
55
+ ## Color
56
+
57
+ - <role>: <hex>
58
+
59
+ ## Typography
60
+
61
+ - <role>: <family>, <size>
62
+
63
+ ## Spacing
64
+
65
+ - base unit: <value>
66
+
67
+ ## Borders
68
+
69
+ - <role>: <radius>, <width>
70
+ ```
71
+
72
+ Write only the sections and roles the trace actually names. A role or a whole section with nothing traced is left out rather than written empty, since `design-extract` Step 2 treats an absent line as untraced and an empty one as a claim about the source.
73
+
74
+ 4. Batch-capture the final round's arm files, the same directory-batch convention `draft-and-pick` Step 6 uses: `canon capture <dest>/arms --selector <wrapper-class> --out .canon/review/evidence/<slug>/`, naming Step 2's chosen class, so every arm considered, not only the winner, survives past the pick as a revert record.
75
+ 5. Delete `<dest>` and everything in it, now that the handoff and the arms both sit at the durable path above. Report the path as still standing when the delete is refused, rather than closing on a report the tree contradicts.
76
+
77
+ ## Response format
78
+
79
+ ```plaintext
80
+ 📝 Wrote .canon/review/evidence/<slug>/design-handoff.md
81
+ 📝 Wrote .canon/review/evidence/<slug>/arm-<id>.png (and every other arm considered)
82
+
83
+ Picked arm-<id>: <label>. Run /canon:design-extract to fill canon/DESIGN.md from the trace.
84
+ ```
85
+
86
+ ## What this delegates
87
+
88
+ Cite these rather than restating them. A step reimplemented here rots against the skill that owns it.
89
+
90
+ - `draft-and-pick` owns the candidate page, the render and hand-off, the structured pick, and the archival capture convention, cited above
91
+ - `design-extract` reads the handoff file this skill writes and fills `canon/DESIGN.md` from it. This skill never writes that file itself.
92
+ - `canon capture` owns the render mechanics, including the URL source and its font refusal
@@ -113,7 +113,29 @@ Write each option as a `<label class="opt" data-k="<letter>">` holding a radio `
113
113
 
114
114
  Then carry a teach-back block beside the quiz. A quiz is recognition and the pedagogy prefers production, so a lesson offering only a quiz tests the weak form. Ask for an explanation to a named audience, and carry a `<details>` listing what a complete explanation covers, closed by default, so a learner reading with no session in the room can grade themselves. The reference states both shapes in full.
115
115
 
116
- Write the chrome as four empty marker pairs rather than composing it by hand: `<!-- canon:teach:style -->`/`<!-- /canon:teach:style -->` inside `<head>`, and `<!-- canon:teach:header -->`, `<!-- canon:teach:footnav -->`, and `<!-- canon:teach:scripts -->` each with its own close marker, in that order in `<body>`. Write the authored `<h1>`, lede, body, and quiz between the header's close marker and the footnav's open marker, and nothing else anywhere in the file. Then run:
116
+ Compose the quiz and the teach-back block as one string. Below, that string is the block list's trailing `raw` entry, rather than markup written into the file by hand.
117
+
118
+ ### Building the lesson body
119
+
120
+ Write the chrome as four empty marker pairs rather than composing it by hand: `<!-- canon:teach:style -->`/`<!-- /canon:teach:style -->` inside `<head>`, and `<!-- canon:teach:header -->`, `<!-- canon:teach:footnav -->`, and `<!-- canon:teach:scripts -->` each with its own close marker, in that order in `<body>`.
121
+
122
+ Build the authored `<h1>`, lede, body, and quiz as a JSON array of blocks rather than composing markup by hand, and render it through the verb rather than through a component import:
123
+
124
+ ```bash
125
+ echo '[
126
+ {"type":"heading","level":1,"text":"<title>"},
127
+ {"type":"paragraph","lede":true,"text":"<the dek>"},
128
+ {"type":"paragraph","text":"<a body paragraph>"},
129
+ {"type":"list","ordered":true,"items":["<step one>","<step two>"]},
130
+ {"type":"raw","html":"<the quiz and teach-back block composed above>"}
131
+ ]' | canon teach render --json
132
+ ```
133
+
134
+ A `heading`, a `paragraph` (`lede: true` for the dek), and a `list` cover the structural body. Reach for `raw` only where none of the three can carry the content, never as a shortcut around composing one, and give the quiz and teach-back block the array's trailing `raw` entry every time, since their fixed contract is not a components concern. Take the call's `html` field and write it between the header's close marker and the footnav's open marker, and nothing else anywhere in the file.
135
+
136
+ Report it rather than proceeding silently when the verb does not resolve, which is an installed CLI predating it, and never compose the lesson body by hand as a fallback. That is the state this section exists to end, and a target holds this skill body before it holds the verb, since a plugin skill reaches a target the moment it merges while the CLI reaches one only when a release publishes.
137
+
138
+ Then run:
117
139
 
118
140
  ```bash
119
141
  canon teach nav <topic> --json
@@ -71,6 +71,17 @@ The quiz shape is a contract rather than a convention. `canon teach nav` splices
71
71
 
72
72
  The stepper hides and shows and sets nothing else, so how a selected option looks, and whether a wrong one is marked as wrong, is the workspace stylesheet's to grow. Name the correct option in the feedback text for that reason: it is what a learner reads to find out whether they were right.
73
73
 
74
+ ## The block list
75
+
76
+ `canon teach render` takes a JSON array of blocks and renders it through the same components the fixture lesson is generated from. Four types, and every structural body composes from them:
77
+
78
+ - `{"type":"heading","level":1|2,"text":"<text>"}`: an `<h1>` or `<h2>`.
79
+ - `{"type":"paragraph","text":"<text>","lede":true}`: a `<p>`, marked `lede` for the dek that opens the lesson.
80
+ - `{"type":"list","items":["<text>", ...],"ordered":true}`: a `<ul>` or, with `ordered`, an `<ol>`.
81
+ - `{"type":"raw","html":"<markup>"}`: the escape hatch, passed through unescaped.
82
+
83
+ The quiz and the teach-back block above are the one case `raw` is always needed for, since their fixed contract is not a components concern. Give them the array's trailing entry rather than reaching for `raw` anywhere the other three could carry the content instead.
84
+
74
85
  ## Teach back
75
86
 
76
87
  A quiz is recognition and a teach-back is production, which is the form retrieval practice prefers. Carry both: the quiz places the learner against options someone could hold, and the teach-back is where the learner finds out what they cannot say without help.
@@ -19,7 +19,7 @@ canon audits list --json
19
19
  | ----------------- | ---------------------------------------------------------------------------------------------------------- |
20
20
  | `--json` | Add a machine-readable record on stdout, keeping the frame |
21
21
  | `--root <path>` | Measure this tree instead of the current worktree |
22
- | `--record` | Write this run's tracked counts to `.claude/canon/baseline.json` |
22
+ | `--record` | Write this run's tracked counts to `canon/config/baseline.json` |
23
23
  | `--corpus <name>` | Limit the run to one corpus (`tracked`, `per-machine`, `upstream`), repeatable, defaulting to every corpus |
24
24
 
25
25
  ## What it runs
@@ -75,7 +75,7 @@ Every run states how many corpora it measured against how many it skipped, inclu
75
75
 
76
76
  ## The retained baseline
77
77
 
78
- `.claude/canon/baseline.json` holds the counts from the last run recorded with `--record`, alongside the day it was taken and the commit it was read at. Each later run reports which counts moved and by how much.
78
+ `canon/config/baseline.json` holds the counts from the last run recorded with `--record`, alongside the day it was taken and the commit it was read at, falling back to `.claude/canon/baseline.json` for a project that has not moved. Each later run reports which counts moved and by how much.
79
79
 
80
80
  The file is committed. A per-machine record cannot answer the question this half exists for, which is whether a number grew since anyone last looked, and a fresh checkout has to inherit that answer rather than start over. The cost is that a branch moving a count either fixes it or re-records it and says why, which is the shape the consumed-copy assertion already carries.
81
81
 
@@ -24,7 +24,7 @@ Full help: `canon <command> --help`. Behavior notes for the install and sync ver
24
24
  | `canon docs [topic]` | Emit toolkit reference docs (`list`, or a topic by name) |
25
25
  | `canon design render` | Render `canon/DESIGN.md` tokens to HTML and CSS |
26
26
  | `canon design regen` | Rewrite this repository's `canon/DESIGN.md` and `src/design/base.css` from `src/design/tokens.ts` |
27
- | `canon design board` | Generate a static page set indexing tokens, surfaces, wireframes, past candidates, and components, against the toolkit checkout like `regen` (`--out`) |
27
+ | `canon design board` | Generate a static page set indexing tokens, surfaces, wireframes, past candidates, components, and references against a caller-resolved `--root` (`--out`), defaulting to the main worktree |
28
28
  | `canon design css` | Emit the design tokens and components as CSS on stdout (`--no-components` for properties alone) |
29
29
  | `canon design install` | Install the base stylesheet into a project at `.claude/design/base.css` |
30
30
  | `canon design sync` | Update a base stylesheet already installed under `.claude/design/` |
@@ -45,8 +45,10 @@ Full help: `canon <command> --help`. Behavior notes for the install and sync ver
45
45
  | `canon teach open` | Open a workspace at the next ordinal with its mission, resources, and glossary files (`--json`) |
46
46
  | `canon teach resource` | Record sources and leads in a workspace, repeating `--read` or `--lead` as `<title>=<url>` (`--json`) |
47
47
  | `canon teach glossary` | Add terms to a workspace glossary alphabetically, repeating `--term <term>=<definition>` (`--json`) |
48
+ | `canon teach lesson` | Resolve the numbered path, shared stylesheet, mission success lines, and one quiz option order per question for the next lesson, writing nothing (`--json`) |
48
49
  | `canon teach stylesheet` | Seed a workspace stylesheet from the design source, leaving an existing one alone without `--force` (`--json`) |
49
50
  | `canon teach nav` | Rewrite the teach-root listing, a workspace's contents page, and each lesson's chrome from its four marker regions (`--json`) |
51
+ | `canon teach render` | Render a JSON block list (heading, paragraph, list, raw) from stdin to HTML through the shared components, refusing `bad-input` by block index (`--json`) |
50
52
  | `canon records validate` | Report a session record or a standard against the standard governing it, per kind (`--json`) |
51
53
  | `canon records migrate` | Rewrite the records a validate finding names a recoverable transform for (`--write`, `--json`) |
52
54
  | `canon records ordinal` | Report or claim the next ordinal `intake` and `groundwork` share, creating the folder atomically (`--claim`, `--json`) |
@@ -56,7 +58,7 @@ Full help: `canon <command> --help`. Behavior notes for the install and sync ver
56
58
  | `canon migrate rename` | Rewrite every unprotected `aitk` token to `canon` and move the paths that carry the name, reporting the plan without `--write` (`--scope`, `--json`) |
57
59
  | `canon migrate skill-names` | Move the prefixed skill folders onto their two-word names and rewrite every citation, reporting the plan without `--write` (`--root`, `--json`) |
58
60
  | `canon migrate records` | Move the gitignored session records to `.canon/` and repoint every tracked citation, reporting the plan without `--write` (`--root`, `--json`) |
59
- | `canon migrate surface-roots` | Move the tracked surfaces (`context/`, `wireframes/`, and the three loose documents) from `.claude/` to `canon/` with `git mv` and repoint every tracked citation, reporting the plan without `--write` (`--root`, `--json`) |
61
+ | `canon migrate surface-roots` | Move the tracked surfaces (`context/`, `wireframes/`, the three loose documents, and the install stamp folder) from `.claude/` to `canon/` with `git mv` and repoint every tracked citation, reporting the plan without `--write` (`--root`, `--json`) |
60
62
  | `canon migrate record-tree` | Repoint the old-root citations inside the records themselves, scoped to the live folders and reporting every line without `--write` (`--root`, `--json`) |
61
63
  | `canon migrate rule-layout` | Move a target's installed rules from the flat `.claude/rules/<subdir>/` layout to `.claude/rules/canon/<subdir>/`, reporting the plan without `--write` (`--root`, `--json`) |
62
64
  | `canon migrate scratch-evidence` | Move a folder under `.tmp/` that a durable record cites as evidence to `.canon/review/evidence/`, repointing every citation live or archived, reporting the plan without `--write` (`--root`, `--json`) |
@@ -139,7 +141,7 @@ Each domain exposes a consistent shape where applicable: `list`, `install`, `syn
139
141
  | `slides` | `render`, `list` |
140
142
  | `tasks` | `archive`, `validate` |
141
143
  | `intake` | `list`, `answer` |
142
- | `teach` | `list`, `open`, `resource`, `glossary`, `lesson`, `stylesheet`, `nav` |
144
+ | `teach` | `list`, `open`, `resource`, `glossary`, `lesson`, `stylesheet`, `nav`, `render` |
143
145
  | `comments` | `scan` |
144
146
  | `context` | `audit` |
145
147
  | `markdown` | `audit` |
@@ -167,7 +169,7 @@ Common patterns:
167
169
 
168
170
  A record folder already present at the destination is a refusal rather than a merge, and a line carrying `canon-keep-record-root`, or the nearest non-blank line below it, keeps the old spelling for prose that dates a decision. The records themselves are never swept: everything under `.canon/` and every `.claude/` record folder is passed over and reported as a count on its own line, which is what keeps the run that follows the ignore collapse touching the same files as one before it. Running it twice rewrites nothing, which is the check that the exclusions, the markers, and that skip all fired.
169
171
 
170
- `migrate surface-roots` moves the tracked surfaces from `.claude/` to `canon/`: `context/`, `wireframes/`, `ARCHITECTURE.md`, `REQUIREMENTS.md`, and `DESIGN.md`. Every path is tracked, so each file moves with `git mv` and its history follows it, and every tracked citation of one is rewritten in the same run. `.claude/canon/`, `rules/`, `skills/`, `hooks/`, and `settings.json` stay where Claude Code reads them. It reports until `--write` is passed, and a destination already present under `canon/` refuses the whole plan rather than splitting a surface across both roots.
172
+ `migrate surface-roots` moves the tracked surfaces from `.claude/` to `canon/`: `context/`, `wireframes/`, `ARCHITECTURE.md`, `REQUIREMENTS.md`, `DESIGN.md`, and the install stamp folder, respelled `canon/config/` since a project under `canon/` reserves the bare `canon` name for the CLI's own install. Every path is tracked, so each file moves with `git mv` and its history follows it, and every tracked citation of one is rewritten in the same run. `rules/`, `skills/`, `hooks/`, and `settings.json` stay where Claude Code reads them. It reports until `--write` is passed, and a destination already present under `canon/` refuses the whole plan rather than splitting a surface across both roots.
171
173
 
172
174
  A line carrying `canon-keep-surface-root`, or the nearest non-blank line above it, keeps the old spelling. A rule's frontmatter `paths:` glob, a citation shaped like a path into another repository, and one inside a dated paragraph are reported by file and line and left alone. Running it twice rewrites nothing, which is the idempotence check. A project that never runs it keeps resolving each surface under `.claude/`, since every reader takes whichever root holds the entry.
173
175
 
@@ -1,30 +1,32 @@
1
1
  ---
2
2
  title: Design board
3
- description: Generating a static index over the toolkit checkout's own design surfaces, what each panel reads, and why it never runs against a target
3
+ description: Generating a static index over a project's design surfaces, what each panel reads, and which two stay toolkit-checkout-only
4
4
  ---
5
5
 
6
6
  # Design board
7
7
 
8
- `canon design board [--out <path>]` generates a static page set indexing five design surfaces and reports the path a reader opens with `canon serve`. Like `canon design regen`, it runs against the toolkit checkout the CLI resolves its own root from, never against a target project's own files: a target holds none of the sources below, since none of `canon/DESIGN.md`, `canon/wireframes/`, `web/`, or the gitignored `.canon/` records ship with the published package. Running the installed CLI from inside a checkout of this project prints a mismatch warning when the caller's working directory disagrees with the root it resolved.
8
+ `canon design board [--out <path>] [--root <path>]` generates a static page set indexing six design surfaces and reports the path a reader opens with `canon serve`. `--root` defaults to the main worktree the same way `canon teach`'s verbs resolve their own root. The surfaces panel's landing-page half and the whole components panel render only when that root is this toolkit's own checkout, since both read this repository's own build output rather than anything a target project produces. Running the installed CLI from inside a checkout of this project prints a mismatch warning when the caller's working directory disagrees with the root it resolved.
9
9
 
10
- Each panel reads what is already on disk and reports its own missing source rather than failing the run, so an absent build or an empty corpus renders an empty-state message instead of a broken frame.
10
+ Each panel reads what is already on disk and reports its own missing source rather than failing the run, so an absent build, an empty corpus, or a toolkit-only gate renders an empty-state message instead of a broken frame.
11
11
 
12
12
  ```bash
13
13
  canon design board
14
14
  canon serve .canon/review/board
15
15
  ```
16
16
 
17
- | Option | Default | Behavior |
18
- | ------------------ | --------------------- | ---------------- |
19
- | `-o, --out <path>` | `.canon/review/board` | Output directory |
17
+ | Option | Default | Behavior |
18
+ | ------------------ | --------------------- | --------------------------------------------- |
19
+ | `-o, --out <path>` | `.canon/review/board` | Output directory |
20
+ | `--root <path>` | The main worktree | Project root the board reads its sources from |
20
21
 
21
22
  ## Panels
22
23
 
23
- - **Tokens** renders `canon/DESIGN.md` through the same renderer `canon design render` uses, rather than a second one.
24
- - **Surfaces** iframes the built landing page from `web/dist/` and a teach workspace from `.canon/teach/`, copying each whole into the board's own tree. Either reports its own missing build or absent workspace rather than rendering a broken frame.
25
- - **Wireframes** renders each of the six files under `canon/wireframes/` as-is inside a `<pre>`, beside a line naming the surface it describes.
26
- - **Past candidates** lists an arm capture image per folder under `.canon/review/evidence/`, and states the corpus carries none rather than rendering an empty grid.
27
- - **Components** iframes the gallery built by `bun run web:gallery`, a second Astro config at `web/gallery.config.mjs` reading `web/gallery-src/` and writing `web/gallery-dist/`. That config's `srcDir` is never read by `web:build`'s own config, so the gallery never reaches the published `web/dist/`. The gallery page renders every component under `web/src/components/` except two whose props carry no defaults, which it names rather than filling with invented data. Reports a missing gallery build rather than rendering a broken frame.
24
+ - **Tokens** renders `DESIGN.md` through the same renderer `canon design render` uses, rather than a second one, reading `<root>/canon/DESIGN.md` or `<root>/.claude/DESIGN.md` for a target that has not run `canon migrate surface-roots`.
25
+ - **Surfaces** iframes the built landing page from `<root>/web/dist/` and a teach workspace from `<root>/.canon/teach/`, copying each whole into the board's own tree. The landing-page half reports a toolkit-only notice outside this toolkit's own checkout, and either half reports its own missing build or absent workspace rather than rendering a broken frame.
26
+ - **Wireframes** reads every `**/*.md` under `<root>/canon/wireframes/` or `<root>/.claude/wireframes/`, excluding `index.md` at any depth, and renders each file as-is inside a `<pre>`, labeled from its own `description` frontmatter field. Reports the whole panel empty rather than per file when the directory is absent or holds nothing to render.
27
+ - **Past candidates** lists an arm capture image per folder under `<root>/.canon/review/evidence/`, and states the corpus carries none rather than rendering an empty grid.
28
+ - **Components** iframes the gallery built by `bun run web:gallery`, a second Astro config at `web/gallery.config.mjs` reading `web/gallery-src/` and writing `web/gallery-dist/`. That config's `srcDir` is never read by `web:build`'s own config, so the gallery never reaches the published `web/dist/`. The gallery page renders every component under `web/src/components/` except two whose props carry no defaults, which it names rather than filling with invented data. Reports a toolkit-only notice outside this toolkit's own checkout, and a missing gallery build otherwise.
29
+ - **References** lists whatever image an operator has dropped flat under `<root>/.canon/review/references/`, with no fetching and no second token source: a reference is an image, nothing more. Reports the folder as absent or as present but carrying no image rather than rendering an empty grid.
28
30
 
29
31
  ## What it does not do
30
32
 
@@ -17,7 +17,7 @@ CLI catalog and invocation rules for agents, split by command domain. Start with
17
17
  - [Context audit](context-audit.md): Running the audit, its flags and folder scope, the exit codes, the citation gate, and the widened gate the seed stage runs
18
18
  - [Self-stated counts](counts.md): Reading a sentence that asserts a closed catalog's size, how a match is decided, the plausibility filter that keeps a generic word from matching a subset, and why the sweep reports rather than gates
19
19
  - [Demo](demo.md): Compiling a screencast draft into a runnable plan, driving a served application to a recording and a still, reading numbered frames back out of a recording, the pointer the recording paints, and what each refusal reports
20
- - [Design board](design-board.md): Generating a static index over the toolkit checkout's own design surfaces, what each panel reads, and why it never runs against a target
20
+ - [Design board](design-board.md): Generating a static index over a project's design surfaces, what each panel reads, and which two stay toolkit-checkout-only
21
21
  - [Docs](docs.md): How canon docs resolves the toolkit's own reference surface from an install root, and how a split domain is named
22
22
  - [Driver](driver.md): Walking a page through named interactions, the probe catalog and the false finding each one carries, why viewport heights are never defaulted, and what each refusal reports
23
23
  - [Merge gate](gate.md): Running the gate this repository verifies a branch with, what the stage table holds and what stays a script, how the changed set scopes three stages, and why a stage that cannot read its input reports rather than passing
@@ -158,9 +158,9 @@ Use `--json` for the machine-readable report and `--exit-code` to fail a CI job.
158
158
  and missing files are both excluded from that exit code: a project-authored
159
159
  rule never converges, and a sync that added a missing one silently changes
160
160
  what the project is governed by, which stays a separate command an operator
161
- chooses to run. Attribution reads `canon/config/config.json` first and falls
162
- back to `.claude/canon/config.json`, which every install and sync still writes
163
- in this release.
161
+ chooses to run. Attribution reads `canon/config/config.json` first, which every
162
+ install and sync now writes, and falls back to `.claude/canon/config.json` for
163
+ a target that has not moved.
164
164
 
165
165
  A target installed before stamping shipped has no such file, and neither does
166
166
  that fallback do anything to migrate it. A target stamped before the file
@@ -28,7 +28,7 @@ Under `--json` the record holds stdout alone and the frame still renders on stde
28
28
 
29
29
  ## The map it reads
30
30
 
31
- The map sits at `.claude/canon/pr-labels.toml` and carries two tables. `[domains]` keys a label name to the path prefixes that earn it, and `[declined]` keys a reason to the prefixes that earn no label on purpose.
31
+ The map sits at `canon/config/pr-labels.toml`, falling back to `.claude/canon/pr-labels.toml` for a project that has not moved, and carries two tables. `[domains]` keys a label name to the path prefixes that earn it, and `[declined]` keys a reason to the prefixes that earn no label on purpose.
32
32
 
33
33
  ```toml
34
34
  [domains]
@@ -137,6 +137,22 @@ Each `quiz` entry carries `order`, the authored option indices in presentation o
137
137
 
138
138
  The order is drawn here rather than instructed, and that is the point of the verb. An author told to vary the position still varies it by judgment, and the judgment settles on the first slot, which is the defect this design departs from. The draw is uniform over the options, so the position carries no information about which answer is correct.
139
139
 
140
+ ## Render
141
+
142
+ `canon teach render` renders a lesson body's structural blocks to HTML, through the same components the fixture lesson under `examples/teach/00-fixture/` is generated from. It takes no topic and no `--root`, since the verb is a stateless transform reading nothing off a workspace on disk.
143
+
144
+ ```bash
145
+ echo '[{"type":"heading","level":1,"text":"Compass bearings"}]' | canon teach render --json
146
+ ```
147
+
148
+ | Option | Behavior |
149
+ | -------- | ---------------------------------------- |
150
+ | `--json` | Emit a machine-readable record on stdout |
151
+
152
+ It reads a JSON array of blocks from stdin, each a `heading`, `paragraph`, `list`, or `raw` block, and reports `{ ok: true, html }` on `--json` or the bare rendered HTML on stdout otherwise. Content none of the three components can express takes a `raw` block, carrying its own HTML verbatim and unescaped, which is the shape the quiz and the teach-back block travel in.
153
+
154
+ It refuses `bad-input` on empty stdin, on malformed JSON, on stdin that does not parse to an array, and on a block carrying an unrecognized `type` or a field of the wrong shape for its type, naming the block's index in the message.
155
+
140
156
  ## Nav
141
157
 
142
158
  `canon teach nav` rewrites the teach-root listing, a workspace's contents page, and each of its lessons' chrome, from what the workspace holds on disk.
@@ -208,7 +208,7 @@ The report opens by naming the binary running it. The installed version reads ag
208
208
 
209
209
  A `stale` file still matches what the toolkit installed, so the update is mechanical. A `customized` file carries local edits, so taking the upstream version is a decision and `canon:seed-sync` is the tool for it. A `stranded` file sits where an older toolkit installed it and the toolkit has since moved, which is a relocation the report names but no command runs.
210
210
 
211
- That attribution comes from `canon/config/config.json` when a project carries it, falling back to `.claude/canon/config.json`, a stamp every install and sync still writes in this release. A target stamped before that path shipped is read from the retired `.claude/canon.json` instead, reported rather than migrated. Governance records a hash per installed file, plus the stack `canon gov install` was given, and tooling records the stack chain it resolved instead of any file hash, since its install runs no per-file walk to attribute.
211
+ That attribution comes from `canon/config/config.json`, which every install and sync now writes, falling back to `.claude/canon/config.json` for a target stamped before that move. A target stamped before that path shipped is read from the retired `.claude/canon.json` instead, reported rather than migrated. Governance records a hash per installed file, plus the stack `canon gov install` was given, and tooling records the stack chain it resolved instead of any file hash, since its install runs no per-file walk to attribute.
212
212
 
213
213
  Each domain holds its own toolkit commit, so syncing governance today does not move the revision tooling measures against, and each domain reports the upstream commits touching its own source path. Running any sync stamps that domain, and the report names the ones still unstamped.
214
214
 
@@ -204,6 +204,7 @@ This section is the corpus the coverage claim is measured against: every name `c
204
204
  | `canon:setup-plugins` | On a new machine, to install the community and official plugins user-scoped |
205
205
  | `canon:setup-verify` | After the agent generates configs, to run the installed scripts and report pass or fail |
206
206
  | `canon:setup-smoke` | After `setup-verify` passes, to check the dev and preview servers, end-to-end tests, and the screenshot harness |
207
+ | `canon:sketch-design` | Before `design-extract`'s greenfield path, to trace a design direction from reference images or URLs |
207
208
  | `canon:design-extract` | Before the first UI feature, to draft `canon/DESIGN.md` |
208
209
  | `canon:draft-diagram` | Once the architecture is written, to draft per-kind entries under `.canon/diagrams/` |
209
210
  | `canon:repo-metadata` | When the GitHub About text, homepage, or topics may have drifted, to reconcile them against the README |
@@ -35,6 +35,7 @@ A cell no source anchors ends in `? verify`, and the preview shows that marker b
35
35
 
36
36
  ### Skills
37
37
 
38
+ - `canon:sketch-design` to trace a design direction from reference images or URLs, before `design-extract`'s greenfield path proposes from a personality paragraph alone
38
39
  - `canon:design-extract` to draft `canon/DESIGN.md`, from existing project signals or from requirements alone on day one
39
40
  - `canon:ui-test` for e2e test generation after UI changes
40
41
  - `canon:ux-audit` for UX gap detection on existing surfaces
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/canon",
3
3
  "type": "module",
4
- "version": "4.79.0",
4
+ "version": "4.81.0",
5
5
  "description": "Infrastructure and quality tooling for developer workflows",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -1,6 +1,7 @@
1
1
  import { mkdir, readFile, writeFile } from 'node:fs/promises'
2
- import { dirname, join } from 'node:path'
2
+ import { dirname, join, relative } from 'node:path'
3
3
  import type { AuditResult } from '@/audits/catalog'
4
+ import { resolveExisting } from '@/legacy-path'
4
5
 
5
6
  /**
6
7
  * Where the retained counts live, relative to the project root.
@@ -15,7 +16,14 @@ import type { AuditResult } from '@/audits/catalog'
15
16
  * that installs the CLI. A baseline in the package would hand a target this
16
17
  * repository's counts to measure its own tree against.
17
18
  */
18
- export const BASELINE_REL = join('.claude', 'canon', 'baseline.json')
19
+ export const BASELINE_REL = join('canon', 'config', 'baseline.json')
20
+
21
+ /**
22
+ * Where the baseline wrote before this move, `.claude/canon/baseline.json`.
23
+ * `readBaseline` falls back to it so a target that has not moved still reads
24
+ * its recorded floor, and the write no longer lands there.
25
+ */
26
+ const LEGACY_BASELINE_REL = join('.claude', 'canon', 'baseline.json')
19
27
 
20
28
  export interface Baseline {
21
29
  /** The day the record was taken, as `YYYY-MM-DD`. */
@@ -165,7 +173,11 @@ function isBaseline(value: unknown): value is Baseline {
165
173
  export async function readBaseline(
166
174
  root: string,
167
175
  ): Promise<Baseline | undefined> {
168
- const path = join(root, BASELINE_REL)
176
+ const path = resolveExisting([
177
+ join(root, BASELINE_REL),
178
+ join(root, LEGACY_BASELINE_REL),
179
+ ])
180
+ const rel = relative(root, path)
169
181
 
170
182
  let raw: string
171
183
  try {
@@ -178,12 +190,12 @@ export async function readBaseline(
178
190
  try {
179
191
  parsed = JSON.parse(raw)
180
192
  } catch {
181
- throw new Error(`${BASELINE_REL} does not parse as JSON. Fix or delete it.`)
193
+ throw new Error(`${rel} does not parse as JSON. Fix or delete it.`)
182
194
  }
183
195
 
184
196
  if (!isBaseline(parsed)) {
185
197
  throw new Error(
186
- `${BASELINE_REL} carries no recordedAt, commit, and checks. Fix or delete it.`,
198
+ `${rel} carries no recordedAt, commit, and checks. Fix or delete it.`,
187
199
  )
188
200
  }
189
201
 
@@ -17,6 +17,11 @@ export const MISC_CASES: readonly SkillCase[] = [
17
17
  'This project has no logo yet. Draft one and give me a social card to go with it.',
18
18
  expect: 'draft-identity',
19
19
  },
20
+ {
21
+ prompt:
22
+ "I found two sites whose look I want us to draw from. Put them side by side and let's settle which one the design should follow before anyone writes a token.",
23
+ expect: 'sketch-design',
24
+ },
20
25
  {
21
26
  prompt: 'Fire up the dev server the way this project documents it.',
22
27
  expect: 'project-commands',