@erclx/canon 4.97.0 → 4.99.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 (38) hide show
  1. package/README.md +29 -27
  2. package/claude/.claude-plugin/plugin.json +1 -1
  3. package/claude/skills/auto-ship/SKILL.md +6 -4
  4. package/claude/skills/draft-and-pick/REQUIREMENT.md +3 -1
  5. package/claude/skills/draft-and-pick/SKILL.md +3 -2
  6. package/claude/skills/draft-and-pick/references/frame.html +118 -0
  7. package/claude/skills/draft-ready/SKILL.md +3 -3
  8. package/claude/skills/git-pr/SKILL.md +41 -21
  9. package/claude/skills/test-craft/REQUIREMENT.md +1 -1
  10. package/claude/skills/test-first/REQUIREMENT.md +1 -1
  11. package/claude/skills/ui-checklist/REQUIREMENT.md +49 -0
  12. package/claude/skills/ui-checklist/SKILL.md +71 -0
  13. package/claude/skills/ux-audit/REQUIREMENT.md +1 -1
  14. package/claude/skills/ux-measure/REQUIREMENT.md +1 -1
  15. package/docs/agents/capture.md +2 -2
  16. package/docs/agents/demo.md +3 -1
  17. package/docs/agents/pr-evidence.md +34 -11
  18. package/docs/agents/pr-preview.md +4 -3
  19. package/docs/agents/tasks.md +3 -1
  20. package/docs/workflow/ai-workflow.md +4 -4
  21. package/docs/workflow/visual-design-workflow.md +2 -2
  22. package/package.json +1 -1
  23. package/scripts/core/regen-hero.sh +33 -1
  24. package/src/claude/cases/workflow.ts +3 -2
  25. package/src/commands/demo.ts +4 -3
  26. package/src/commands/pr.ts +53 -1
  27. package/src/commands/tasks.ts +16 -0
  28. package/src/demo/compile.ts +20 -0
  29. package/src/demo/drive.ts +1 -0
  30. package/src/migrate/skill-names.ts +14 -6
  31. package/src/pr/evidence.ts +36 -1
  32. package/src/sandbox/expect.ts +2 -1
  33. package/src/slides/styles.ts +28 -3
  34. package/src/tasks/archive.ts +115 -3
  35. package/standards/ready.md +2 -2
  36. package/standards/tasks.md +4 -2
  37. package/claude/skills/ui-test/REQUIREMENT.md +0 -48
  38. package/claude/skills/ui-test/SKILL.md +0 -87
package/README.md CHANGED
@@ -1,3 +1,7 @@
1
+ <div align="center">
2
+
3
+ <img src="assets/brand/mark-accent.svg" alt="canon" width="56" height="56">
4
+
1
5
  # canon
2
6
 
3
7
  [![npm version](https://img.shields.io/npm/v/@erclx/canon)](https://www.npmjs.com/package/@erclx/canon)
@@ -6,17 +10,13 @@
6
10
 
7
11
  canon is a CLI and Claude Code plugin that stops your AI conventions drifting apart across repositories. It keeps one authoritative copy and installs it into each project on demand.
8
12
 
9
- ![The canon catalog, listing skills, governance rules, and standards with the count each ships, the workflow skills named, and a sample of the rule and standard names](assets/evidence/hero.png)
10
-
11
- The counts above are read from the catalogs when the image is built, so they're what the repo actually ships today. See it live at [canon.erclx.dev](https://canon.erclx.dev).
12
-
13
- ## It runs on itself
13
+ **[See it run at canon.erclx.dev](https://canon.erclx.dev)**
14
14
 
15
- The workflow this toolkit ships is the workflow that built it. Several Claude Code sessions run at once, each in its own git worktree on its own branch, and each opens its own pull request.
15
+ </div>
16
16
 
17
- ![The canon landing page's session list, with the orchestrator pinned above bands of working and completed sessions, workers and planners in both, each naming what it is doing and the pull request it opened, and one row moving from Working into Completed](demos/agent-view.gif)
17
+ ![The first screen of the canon landing page, a project before and after one canon init, with the file counts each step lays down](assets/evidence/arrival.png)
18
18
 
19
- That is the landing page's own agent view, recorded from a local build by `canon demo run`. The session rows behind it are a real `canon sessions list --json` read. The activity text and the pull request number are transcribed, because no verb here reports either.
19
+ The page is one real session the toolkit ran on itself, and every count on it is read from the repo when the page builds.
20
20
 
21
21
  ## Why
22
22
 
@@ -53,35 +53,37 @@ bun install --global @erclx/canon
53
53
 
54
54
  ## What is inside
55
55
 
56
- Each domain has a canonical source in this repo and a thin install or sync CLI on your side.
56
+ Each domain has a canonical source in this repo and a thin install or sync CLI on your side. The domains split on one line: some are copied into your project and become yours to edit, and some are never copied at all.
57
57
 
58
- - Claude Code plugin: skills that plan a feature, review a diff, sync the planning docs, and run the ship chain from branch through pull request
59
- - Governance rules: coding and authoring rules that load into a Claude session when a matching path is edited, installed per project and refreshed by sync
60
- - Standards: authoring conventions for commits, branches, plans, tasks, and markdown, read by name with `canon standards <name>` rather than copied into your project
61
- - Snippets: reusable prompts fired by `@` reference in a Claude Code session, resolved live from the plugin with no install step
62
- - Tooling stacks: golden configs, seeds, and a reference per framework, laid down by `canon init` and reconciled by `canon tooling sync`
63
- - Design system: a `DESIGN.md` token format, a skill that drafts one from an existing project or from scratch, and a render command
64
- - Slides: a `SLIDES.md` source format with a layout catalog, rendered to PowerPoint by `canon slides render`
65
- - Transcripts: `canon transcripts` pulls a YouTube transcript with metadata frontmatter into any repo
66
- - Sandbox: scenario-based scaffolds that provision an isolated project state for verifying each domain flow
58
+ | Domain | What it is | How it reaches you |
59
+ | -------------- | ------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------- |
60
+ | Plugin skills | Skills that plan a feature, review a diff, sync the planning docs, and run the ship chain | Loaded live from the plugin, never copied |
61
+ | Governance | Coding and authoring rules that load into a Claude session when a matching path is edited | Installed per project by `canon gov install`, refreshed by sync |
62
+ | Standards | Authoring conventions for commits, branches, plans, tasks, and markdown | Opened by name, read by name with `canon standards <name>` |
63
+ | Snippets | Reusable prompts fired by `@` reference | Resolved live from the plugin |
64
+ | Tooling stacks | Golden configs, seeds, and a reference per framework | Laid down by `canon init`, reconciled by `canon tooling sync` |
65
+ | Design system | Ships a `DESIGN.md` token format, a skill that drafts one from an existing project or from scratch, and a render command | `canon design render` |
66
+ | Slides | A `SLIDES.md` source format with a layout catalog | `canon slides render` writes PowerPoint |
67
+ | Transcripts | A YouTube transcript with metadata frontmatter | `canon transcripts <url>` writes it into any repo |
68
+ | Sandbox | Scenario scaffolds that provision an isolated project state for verifying each domain flow | `canon sandbox` |
67
69
 
68
- Those domains split on one line: some are copied into your project and become yours to edit, and some are never copied at all.
70
+ ![The canon catalog as a ledger of five rows, with the count each ships and a few real names: skills, rules, standards, commands, and stacks](assets/evidence/catalog.png)
69
71
 
70
- ![canon tooling list and canon standards list side by side, the first showing five stacks with their dependency and script counts, the second showing standards against the artifact each governs](assets/evidence/install-surface.png)
72
+ Every count in that image is read from the catalogs when it is built, so they're what the repo ships today. A tooling stack lands as real files under version control, because a config is something your build reads and your project owns. A standard stays here and is opened by name, so there is no copy in your repo to drift from this one. Governance is the third shape. A rule with a path glob loads only when a matching path is edited, and a rule with none loads every session. Run `canon gov list` to see the glob beside each rule.
71
73
 
72
- A tooling stack lands as real files under version control, because a config is something your build reads and your project owns. A standard stays here and is opened by name, so there is no copy in your repo to drift from this one.
74
+ Two of the outputs are documents you open rather than files you run. A `SLIDES.md` renders to a deck, and a learning workspace renders to a small site.
73
75
 
74
- Governance is the third shape, and it is worth seeing rather than reading about, because the glob beside each rule is what decides whether it reaches a session at all.
76
+ ![A slide from the showcase deck rendered by canon slides render, a bulleted list under a heading on a dark warm background](examples/slides/evidence/showcase-dark-04.png)
75
77
 
76
- ![canon gov list, showing seven stacks with the rules each carries and a sample of rules beside the path glob that loads each one](assets/evidence/governance.png)
78
+ ![The learning workspace listing rendered by canon teach, three workspaces with their lesson and term counts, one still a stub](examples/teach/evidence/root-listing.png)
77
79
 
78
- Stacks compose, so a react project inherits node and node inherits base. A rule with a glob loads only when a matching path is edited, and a rule with none loads every session. Both counts and every row above are read from the catalogs when the image is built.
80
+ ## It runs on itself
79
81
 
80
- The toolkit tracks its own work the same way, in a task board no catalog can read back.
82
+ The workflow this toolkit ships is the workflow that built it. Several Claude Code sessions run at once, each in its own git worktree on its own branch, and each opens its own pull request.
81
83
 
82
- ![the task board's Run now table, one row per task in flight, each naming the files it touches and the plan it runs under](assets/evidence/task-board.png)
84
+ ![The canon landing page scrolling from the dispatch section to the branch graph, three workers and a planner on disjoint file sets with the pull request each opened](demos/agent-view.gif)
83
85
 
84
- `.canon/tasks/` is gitignored session scratch, so unlike the two frames above, this one is a hand-taken snapshot rather than something the build reads live, and it goes stale the moment the board moves.
86
+ That recording is the landing page's own `dispatch` and `workers` sections, driven by `canon demo run` against a local build. The branch graph beside the pull request numbers is authored rather than read, because nothing on a build machine records which files four sessions held.
85
87
 
86
88
  ## Documentation
87
89
 
@@ -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.97.0",
4
+ "version": "4.99.0",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -117,13 +117,15 @@ The verb reads git history rather than the working tree, so a branch with nothin
117
117
 
118
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
119
 
120
- ## Step 5: UI test (conditional)
120
+ ## Step 5: UI checklist (conditional)
121
121
 
122
- If the diff touches UI files (JSX, TSX, Vue, Svelte, HTML, or CSS under `src/`), invoke `canon:ui-test`.
122
+ If the diff touches UI files (JSX, TSX, Vue, Svelte, HTML, or CSS under `src/`), invoke `canon:ui-checklist`.
123
123
 
124
- If `ui-test` produces a manual checklist, stop: `❌ UI requires visual verification. Checklist at .canon/tmp/handoff/ui-checklist/<slug>.md, which reaches the pull request once /git-ship runs. Verify manually, then run /git-ship.`
124
+ If `ui-checklist` produces a checklist, stop: `❌ UI requires visual verification. Checklist at .canon/tmp/handoff/ui-checklist/<slug>.md, which reaches the pull request once /git-ship runs. Verify manually, then run /git-ship.`
125
125
 
126
- If all UI changes are covered by tests, continue.
126
+ The stop is unchanged by that skill's shrink. The checklist is now the step's whole output rather than the visual remainder beside the tests it wrote, so the stop is the only thing that makes the operator look before `git-ship`.
127
+
128
+ If there is nothing to verify visually and nothing shipping untested, continue.
127
129
 
128
130
  ## Step 6: review
129
131
 
@@ -28,6 +28,7 @@ Without this skill, a session facing a decision nobody can settle from a diff:
28
28
  - Names no layer at all, since the instruction to name one carries no catalog to name from and nothing reports the omission. Four consecutive rounds against one page each varied a property at the top layer while the defect sat two below it, and three of the four changed nothing the operator could see.
29
29
  - Judges a lower layer through a finished higher one, so an operator asked about composition looks at the palette instead and answers about that.
30
30
  - Hands over the combined page alone for a decision about layout, where every arm sits in a column while the media queries answer to the whole window, so no arm is ever seen at its own viewport and a fault that appears only narrower survives the pick.
31
+ - Rebuilds a navigation harness by hand on a run with several rounds, since each round writes its own folder and nothing reaches all of them from one address. Two design tracks hand-wrote the same page picker and theme toggle.
31
32
  - Sets the theme on an arm that persists its own and reads it back on load, so the control reports one theme while the arm renders the other and the set is compared across two.
32
33
 
33
34
  ## Must
@@ -38,6 +39,7 @@ Without this skill, a session facing a decision nobody can settle from a diff:
38
39
  - Grey-box the set when the declared layer sits below typography, and say the set is grey-boxed when handing it over, so the operator reads a flat page as the question rather than as unfinished work.
39
40
  - Render every arm in one theme at a time, with a control that sets the whole set, so the comparison holds still while the operator reads it, and set the theme in a way that survives an arm reading its own stored preference back on load.
40
41
  - Hand over the per-arm file addresses beside the combined page where the declared layer is composition, layout or space, since only a whole-page file at a real viewport answers how an arm behaves at a width, and the browser's own device toolbar is the control.
42
+ - Write a frame from `references/frame.html` where the run has several rounds, so a picker reaches every round from one address. The frame carries no width control of its own.
41
43
  - Judge each render against a stated bar before handing it over, name the weakest thing on the page, and fix it where that sentence would embarrass the work.
42
44
  - Write each iteration to its own folder rather than narrowing the previous one in place, so every earlier round stays openable.
43
45
  - Write the run's renders inside the record that cites them wherever one exists, and reserve the scratch path for inputs that are re-runnable and cited by nothing.
@@ -66,7 +68,7 @@ The refusal strings sit in the body, since the runtime loads that file and ignor
66
68
  - `plan-feature` plans the work once the answer is settled, and declares the pull request boundary its plan carries. This produces the answer to pick from and stops before the plan.
67
69
  - `ux-audit` reads source to find roughness and reports it. This takes its input from the operator and changes nothing until they pick.
68
70
  - `ux-measure` measures what a running interface costs to paint. This measures whatever a visual claim depends on, which is usually geometry or contrast rather than cost.
69
- - `ui-test` writes tests for a change already made. This runs before there is a change to test.
71
+ - `ui-checklist` writes what to look at on a change already made. This runs before there is a change to look at.
70
72
  - `draft-screencast` scripts a recording of something already built. This has nothing built yet.
71
73
  - `canon capture`, `canon serve`, and `canon drive` own the render, the address, and the probes, and are invoked rather than reimplemented.
72
74
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: draft-and-pick
3
- description: Drafts several candidates for a decision judged by looking, renders them side by side on one page, hands the operator the addresses, takes the pick through the structured question surface, and loops on the pick until they stop. Use when asked to "draft some options", "show me a few versions", "try a few variations", "mock up alternatives", "give me candidates for X", or when a choice is taste rather than correctness. Do NOT use when the request already names the answer and asks for it to be built, which is `plan-feature`. Do NOT use to read source for roughness, which is `ux-audit`, to measure what a running interface costs to paint, which is `ux-measure`, to write tests for a change already made, which is `ui-test`, to script a recording, which is `draft-screencast`, or to inspect a running app across many findings, which is `ux-walkthrough`.
3
+ description: Drafts several candidates for a decision judged by looking, renders them side by side on one page, hands the operator the addresses, takes the pick through the structured question surface, and loops on the pick until they stop. Use when asked to "draft some options", "show me a few versions", "try a few variations", "mock up alternatives", "give me candidates for X", or when a choice is taste rather than correctness. Do NOT use when the request already names the answer and asks for it to be built, which is `plan-feature`. Do NOT use to read source for roughness, which is `ux-audit`, to measure what a running interface costs to paint, which is `ux-measure`, to write what to look at on a change already made, which is `ui-checklist`, to script a recording, which is `draft-screencast`, or to inspect a running app across many findings, which is `ux-walkthrough`.
4
4
  ---
5
5
 
6
6
  # Draft and pick
@@ -31,6 +31,7 @@ Write every arm side by side on one self-contained HTML page at `<dest>/candidat
31
31
  - Label each arm on the page with its id and its cost, so the render carries what the question will ask about.
32
32
  - Give the combined page one control that sets every arm's theme at once, beside whatever per-arm control the arms carry. A set spanning both themes cannot be compared, since the operator has to toggle each arm and hold the earlier ones in memory, which is the failure the single-page rule exists to prevent.
33
33
  - Set that theme so it survives the arm reading its own preference back. An arm that stores a theme and applies it on load will overwrite whatever the control set, which shows the control in one state and the arm in the other, and it passes a fresh browser profile because the stored value is not there yet.
34
+ - Write `<dest>/frame.html` from `${CLAUDE_SKILL_DIR}/references/frame.html` where the run has more than one round, and hand over its address beside the combined page. Replace the template's one data token with the JSON object its comment describes, listing the combined page and every per-arm file of this round and each earlier one, with each `src` relative to the frame and `themeKey` set to the key the arms read their theme from. The frame is the route to every round from one address, with a page picker and the global theme toggle. Earlier rounds are sibling folders, so serve the parent folder with `canon serve <parent> --entry <dest-name>/frame.html` rather than `<dest>`, and keep the per-arm files as the fallback where nothing is served.
34
35
  - Hand over the per-arm file addresses beside the combined page where the declared layer is composition, layout or space, and say that an arm is judged at a width by opening its own file in the browser's device toolbar. The combined page cannot answer that question at all: every arm sits in a column there while the media queries answer to the whole window, so each one renders its widest layout in a narrow space no matter how the window is sized. Build no width control of your own, since the browser already has one.
35
36
  - Take the live-app branch instead when the surface under decision is a running app: lift the rendered markup and link a copy of the built stylesheet rather than inlining, per `${CLAUDE_SKILL_DIR}/references/live-arms.md`.
36
37
  - On the default path, inline every style, script, and asset the page needs. The render reads the file off disk, so a page reaching for a build step or a network font renders without it and the arms differ by something nobody chose.
@@ -70,7 +71,7 @@ Put the choice to the operator through the structured question surface, since a
70
71
  ## Step 5: loop on the pick
71
72
 
72
73
  1. Write each iteration to its own `<dest>` rather than narrowing the previous one in place, suffixing the slug so the folders sort. An iteration overwritten is one a later pass cannot open, and the losing round is what stops a correction re-proposing something already rejected.
73
- 2. Write fresh arms off the pick and return to Step 2 where the correction opens a new question. Revise the one arm where it does not.
74
+ 2. Write fresh arms off the pick and return to Step 2 where the correction opens a new question. Revise the one arm where it does not. Each pass through Step 2 rewrites the frame with every earlier round's arms, since the list is baked in when the frame is written.
74
75
  3. Re-render, hand off again, and take the next answer.
75
76
  4. Repeat until the operator says it is right. The loop stops on their word and on nothing else, so a run stopping because the arms stopped differing has stopped early.
76
77
  5. Hold the real surface untouched across every iteration. Nothing outside the run's own folders changes until the pick is final.
@@ -0,0 +1,118 @@
1
+ <!doctype html>
2
+ <html lang="en" data-theme="light">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>Draft and pick frame</title>
6
+ <style>
7
+ :root {
8
+ --bg: #f4f4f5;
9
+ --fg: #18181b;
10
+ --bar: #ffffff;
11
+ --line: #d4d4d8;
12
+ }
13
+ :root[data-theme='dark'] {
14
+ --bg: #18181b;
15
+ --fg: #f4f4f5;
16
+ --bar: #27272a;
17
+ --line: #3f3f46;
18
+ }
19
+ * {
20
+ box-sizing: border-box;
21
+ }
22
+ body {
23
+ margin: 0;
24
+ height: 100vh;
25
+ display: flex;
26
+ flex-direction: column;
27
+ background: var(--bg);
28
+ color: var(--fg);
29
+ font-family: system-ui, sans-serif;
30
+ font-size: 14px;
31
+ }
32
+ header {
33
+ display: flex;
34
+ gap: 12px;
35
+ align-items: center;
36
+ padding: 8px 12px;
37
+ background: var(--bar);
38
+ border-bottom: 1px solid var(--line);
39
+ }
40
+ select,
41
+ button {
42
+ font: inherit;
43
+ color: inherit;
44
+ background: var(--bg);
45
+ border: 1px solid var(--line);
46
+ border-radius: 4px;
47
+ padding: 4px 8px;
48
+ }
49
+ main {
50
+ flex: 1;
51
+ overflow: auto;
52
+ display: flex;
53
+ justify-content: center;
54
+ padding: 12px;
55
+ }
56
+ iframe {
57
+ width: 100%;
58
+ height: 100%;
59
+ border: 1px solid var(--line);
60
+ background: #fff;
61
+ }
62
+ </style>
63
+ </head>
64
+ <body>
65
+ <header>
66
+ <label
67
+ >Page
68
+ <select id="picker"></select
69
+ ></label>
70
+ <button id="theme" type="button">Theme: light</button>
71
+ <span>F12 device toolbar to resize</span>
72
+ </header>
73
+ <main><iframe id="page" title="Candidate page"></iframe></main>
74
+ <!-- The skill replaces the token below with one JSON object: {"themeKey": "<key the arms read>", "rounds": [{"label": "...", "pages": [{"label": "...", "src": "<path relative to this file>"}]}]} -->
75
+ <script type="application/json" id="frame-data">
76
+ __FRAME_DATA__
77
+ </script>
78
+ <script>
79
+ const data = JSON.parse(document.getElementById('frame-data').textContent)
80
+ const picker = document.getElementById('picker')
81
+ const themeButton = document.getElementById('theme')
82
+ const frame = document.getElementById('page')
83
+ let theme = 'light'
84
+
85
+ for (const round of data.rounds) {
86
+ for (const page of round.pages) {
87
+ const option = document.createElement('option')
88
+ option.value = page.src
89
+ option.textContent = round.label + ' · ' + page.label
90
+ picker.append(option)
91
+ }
92
+ }
93
+
94
+ // A page that stores its own theme and reads it back on load overwrites the
95
+ // attribute alone, so write the stored key as well as the attribute.
96
+ function applyTheme() {
97
+ document.documentElement.dataset.theme = theme
98
+ themeButton.textContent = 'Theme: ' + theme
99
+ try {
100
+ const win = frame.contentWindow
101
+ win.document.documentElement.setAttribute('data-theme', theme)
102
+ win.localStorage.setItem(data.themeKey, theme)
103
+ } catch {}
104
+ }
105
+
106
+ picker.addEventListener('change', () => {
107
+ frame.src = picker.value
108
+ })
109
+ themeButton.addEventListener('click', () => {
110
+ theme = theme === 'light' ? 'dark' : 'light'
111
+ applyTheme()
112
+ })
113
+ frame.addEventListener('load', applyTheme)
114
+
115
+ if (picker.options.length > 0) frame.src = picker.value
116
+ </script>
117
+ </body>
118
+ </html>
@@ -73,7 +73,7 @@ Write `<main-root>/.canon/tasks/<label>-<slug>.md` by heredoc, from the label St
73
73
  canon tasks plan-link <task-stem> .canon/plans/feature-<slug>.md --json
74
74
  ```
75
75
 
76
- Branch on the record rather than on the exit. `Ready:` is defined nowhere yet, so the line orients a worker reading the task and no check reads it.
76
+ Branch on the record rather than on the exit. The `Ready:` line orients a worker reading the task, and `canon tasks archive` reads it to move the folder on ship.
77
77
 
78
78
  A task file with no row is a dropped task, so place the row in the same pass. The plan exists, so the row takes `## Run now` when `canon tasks plan-reach` reports nothing claimed and `## Up next` otherwise, with the held path in its `Waiting on` cell, per the tests in `${CLAUDE_SKILL_DIR}/../../standards/tasks.md`. Check the roster the way `canon:task-board` Step 4 does, reading `canon sessions list --self --json` for this session and `canon sessions list --json` for a row from the same repository, under another `sessionId`, whose `name` starts with `orchestrator-`. Treat a refused read as a roster read that failed.
79
79
 
@@ -106,10 +106,10 @@ Fix what the records name in the files this run wrote and re-run once. Report a
106
106
  Plan: .canon/plans/feature-<slug>.md
107
107
  Task: .canon/tasks/<label>-<slug>.md
108
108
  Row: <label> <title>, <written under ## Run now | reported to <orchestrator name> | left for the operator to place>, <reason>
109
- Archive on ship: move the folder to .canon/ready/archive/<nn>-<slug>/ by hand, then retarget the task's Ready: line
109
+ Archive on ship: canon tasks archive moves the folder to .canon/ready/archive/<nn>-<slug>/
110
110
  ```
111
111
 
112
- Name the archive move every time. `canon tasks archive` moves the task and its plan and leaves the folder live, so the move is the one step this run leaves for whoever ships.
112
+ Name the archive move every time, so whoever ships knows the verb carries the folder and needs no step of their own.
113
113
 
114
114
  ## Rules
115
115
 
@@ -152,54 +152,72 @@ rm -rf .canon/tmp/pr/body
152
152
  printf 'number=%s\nurl=%s\n' "$pr_number" "$pr_url"
153
153
  ```
154
154
 
155
- ### Post the UI checklist
155
+ ### Find the UI checklist
156
156
 
157
- `ui-test` writes a manual checklist to `.canon/tmp/handoff/ui-checklist/<slug>.md` at the main worktree root when a change needs visual verification, with `<slug>` derived per `${CLAUDE_SKILL_DIR}/../../standards/slug.md`. This step is the file's sole consumer. Resolve the main root the way `session-worktree` does (`git worktree list --porcelain | grep -m 1 '^worktree ' | cut -d' ' -f2-`, falling back to `pwd`) and check for the file there. A missing file means no checklist was produced, and there is nothing to post.
157
+ `ui-checklist` writes a visual checklist to `.canon/tmp/handoff/ui-checklist/<slug>.md` at the main worktree root when a change needs visual verification, with `<slug>` derived per `${CLAUDE_SKILL_DIR}/../../standards/slug.md`. This skill is the file's sole consumer. Resolve the main root the way `session-worktree` does (`git worktree list --porcelain | grep -m 1 '^worktree ' | cut -d' ' -f2-`, falling back to `pwd`) and check for the file there. A missing file means no checklist was produced, and the two steps below each skip their checklist half.
158
158
 
159
- When it exists, scan it against `${CLAUDE_SKILL_DIR}/../../standards/publish.md` before posting, the same as the pull request body above. Post it as its own comment on `<number>`, the number the final command above resolved, rather than folding it into the body, since a later push editing the body would overwrite checkboxes a reviewer already ticked:
159
+ When it exists, scan it against `${CLAUDE_SKILL_DIR}/../../standards/publish.md` before either step posts it, the same as the pull request body above.
160
+
161
+ Where it lands is decided by the evidence step below rather than here, since a checklist reads next to the screenshots it annotates and posting it on its own is the fallback for a branch that changed no screenshot.
162
+
163
+ ### Post the evidence comparison
164
+
165
+ Run the verb once against the number the pull request step above resolved, passing `--checklist` when the step above found a file and leaving it off when it did not:
160
166
 
161
167
  ```bash
162
- gh pr comment <number> --body-file <main-root>/.canon/tmp/handoff/ui-checklist/<slug>.md
168
+ canon pr evidence <number> --checklist <main-root>/.canon/tmp/handoff/ui-checklist/<slug>.md --json
163
169
  ```
164
170
 
165
- Run the cleanup below only once that call reports success. On a failure, stop and leave the file in place: a retry needs the checklist to still be there, and deleting it on a failed post loses the only copy with nothing landed on the pull request.
171
+ One call answers both questions because a checklist does not decide `no-evidence`. The verb reports `no-evidence` on a diff carrying no evidence image whether or not a checklist came with it, so the branch below reads the same `reason` it would have read without the flag, and the checklist is folded in only on the path that has a comparison to fold it into.
166
172
 
167
- From a linked worktree the file-editing tools refuse a main-root path, so the cleanup goes out through `Bash` as two plain commands, the file and then the folder, rather than joined by `&&`, which is refused as compound:
173
+ Pass `--checklist` only for a file that exists. The verb refuses as `unreadable-checklist` on a path it cannot read or one holding nothing, which is a caller bug rather than a transient failure, so stop and repair the path rather than posting a body with the checklist silently dropped.
174
+
175
+ Read `reason` on the record rather than the exit code.
176
+
177
+ - `no-evidence`: nothing changed under an `evidence/` segment, so there is no comparison to post and no body was rendered. Say nothing about the evidence and fall through to the checklist step below.
178
+ - `ok`: write `body` to `.canon/tmp/pr/evidence/body-<number>.md` at the main worktree root (resolved the way `session-worktree` does), then post or update the comment:
168
179
 
169
180
  ```bash
170
- rm <main-root>/.canon/tmp/handoff/ui-checklist/<slug>.md
181
+ gh pr comment <number> --body-file <main-root>/.canon/tmp/pr/evidence/body-<number>.md
171
182
  ```
172
183
 
184
+ When the record carries a `commentId`, edit that comment in place instead of posting a second one, reading the body field from the tmp file with `@`:
185
+
173
186
  ```bash
174
- rmdir <main-root>/.canon/tmp/handoff/ui-checklist 2>/dev/null || true
187
+ gh api -X PATCH repos/{owner}/{repo}/issues/comments/<commentId> -f body=@<main-root>/.canon/tmp/pr/evidence/body-<number>.md
175
188
  ```
176
189
 
177
- The `rmdir` is a no-op when another branch's pending checklist still sits in the folder, which keeps this step from deleting a handoff that is not its own.
190
+ Delete the handoff file once that call reports success, per the cleanup below, since the checklist now lives on the pull request. Clean up the tmp body file the same way.
178
191
 
179
- ### Post the evidence comparison
192
+ Any other `reason` is one of the mirrored git refusals (`gh-missing`, `gh-failed`, `no-base`, `unreadable-tree`, `unreadable-changes`). Report it and move on without stopping the chain: a branch that carries no evidence images most of the time should not fail here on a transient git or `gh` read. Fall through to the checklist step, which posts the checklist alone rather than losing it to a transient read.
180
193
 
181
- Run `canon pr evidence <number> --json` against the number the pull request step above resolved. Read `reason` on the record rather than the exit code.
194
+ ### Post the UI checklist alone
182
195
 
183
- - `no-evidence`: nothing changed under an `evidence/` segment. Say nothing and move on.
184
- - `ok`: write `body` to `.canon/tmp/pr/evidence/body-<number>.md` at the main worktree root (resolved the way `session-worktree` does), then post or update the comment:
196
+ Run this step only when the evidence step above did not carry the checklist, meaning it reported `no-evidence` or one of the git refusals, and a checklist file exists. Post it as its own comment on `<number>`:
185
197
 
186
198
  ```bash
187
- gh pr comment <number> --body-file <main-root>/.canon/tmp/pr/evidence/body-<number>.md
199
+ gh pr comment <number> --body-file <main-root>/.canon/tmp/handoff/ui-checklist/<slug>.md
188
200
  ```
189
201
 
190
- When the record carries a `commentId`, edit that comment in place instead of posting a second one, reading the body field from the tmp file with `@`:
202
+ Run the cleanup below only once the call that carried the checklist reports success, whichever of the two steps that was. On a failure, stop and leave the file in place: a retry needs the checklist to still be there, and deleting it on a failed post loses the only copy with nothing landed on the pull request.
203
+
204
+ From a linked worktree the file-editing tools refuse a main-root path, so the cleanup goes out through `Bash` as two plain commands, the file and then the folder, rather than joined by `&&`, which is refused as compound:
191
205
 
192
206
  ```bash
193
- gh api -X PATCH repos/{owner}/{repo}/issues/comments/<commentId> -f body=@<main-root>/.canon/tmp/pr/evidence/body-<number>.md
207
+ rm <main-root>/.canon/tmp/handoff/ui-checklist/<slug>.md
194
208
  ```
195
209
 
196
- Clean up the tmp file the way the UI-checklist step does, only after the call reports success.
210
+ ```bash
211
+ rmdir <main-root>/.canon/tmp/handoff/ui-checklist 2>/dev/null || true
212
+ ```
197
213
 
198
- Any other `reason` is one of the mirrored git refusals (`gh-missing`, `gh-failed`, `no-base`, `unreadable-tree`, `unreadable-changes`). Report it and move on without stopping the chain: a branch that carries no evidence images most of the time should not fail here on a transient git or `gh` read.
214
+ The `rmdir` is a no-op when another branch's pending checklist still sits in the folder, which keeps this step from deleting a handoff that is not its own.
215
+
216
+ Deleting the file is what makes the later re-render safe. `git-followup` re-runs `canon pr evidence` with no `--checklist`, and the verb carries the checklist forward out of the comment it is editing, so the boxes a reviewer already ticked survive the push.
199
217
 
200
218
  ### Post the preview address
201
219
 
202
- Run this step only when the evidence step above returned `ok` or the UI checklist step posted a comment. Either one means the pull request changes a rendered surface, and a reviewer holding a checklist with no screenshots needs the live page most. Otherwise skip it silently.
220
+ Run this step only when the evidence step above returned `ok` or a checklist was posted, by either step. Either one means the pull request changes a rendered surface, and a reviewer holding a checklist with no screenshots needs the live page most. Otherwise skip it silently.
203
221
 
204
222
  The evidence comment is already posted, so the reviewer has the screenshots while the deploy runs. Mint the preview against the same `<number>`:
205
223
 
@@ -250,9 +268,11 @@ Add a further line only when the labelling command printed its warning, quoting
250
268
 
251
269
  `⚠️ Labels not applied: <what gh reported>`
252
270
 
253
- Add a line when the UI checklist step posted a comment, naming the pull request it landed on:
271
+ Add a line when a checklist was posted, naming the pull request it landed on and which comment carries it:
272
+
273
+ `📋 Posted the UI checklist to <number>, in the evidence comment.`
254
274
 
255
- `📋 Posted the UI checklist to <number>.`
275
+ `📋 Posted the UI checklist to <number>, on its own.`
256
276
 
257
277
  Add a line when the preview step returned an address:
258
278
 
@@ -49,7 +49,7 @@ Moving the guidance into a path-scoped rule does not reach the session that need
49
49
  - Structure floors for every test: the core testing rule
50
50
  - Runner choice, file suffix, placement, and query conventions: the TypeScript and Python testing rules
51
51
  - Settle, guard, and run-scope rules inside an end to end file: the end to end reliability and test scope rules
52
- - Writing a UI change's tests and its visual checklist after implementation: `ui-test`
52
+ - Writing a UI change's visual checklist, and naming the behaviors it ships untested for this skill to place: `ui-checklist`
53
53
  - Grading the tests a pull request adds: `review-pr`
54
54
  - Whether the guidance moves where a session puts a test, which needs a measured with-and-without run rather than a rule here
55
55
 
@@ -32,5 +32,5 @@ Without this skill, a session implementing a planned change writes the test afte
32
32
 
33
33
  - Choosing the layer a test belongs at and judging whether it is a good test: `canon:test-craft`, loaded at step 1 rather than restated here
34
34
  - Finding the cause of an unexplained failure: `canon:systematic-debugging`
35
- - Confirming visual output after a change lands: `070-planning.md` states the order and `ui-test` covers it
35
+ - Confirming visual output after a change lands: `070-planning.md` states the order and `ui-checklist` writes the list to confirm against
36
36
  - The mechanical audit of whether an implementation reached history ahead of its test: `canon gov test-order`, invoked from `auto-ship`
@@ -0,0 +1,49 @@
1
+ ---
2
+ name: ui-checklist
3
+ description: Why the visual half of a UI change needs a written record a reviewer can open, why that record travels with the screenshots it annotates, and why writing the tests belongs elsewhere now
4
+ ---
5
+
6
+ # UI checklist requirement
7
+
8
+ ## Gap
9
+
10
+ Without this skill, UI work ships on the confidence of the session that wrote it. Nobody states which part of the change an eye has to judge, so a reviewer opens the diff and reads the automatable half, which the tests already cover, and never looks at the half that only rendering shows.
11
+
12
+ The visual half is where the record is lost. A checklist printed into chat scrolls away before anyone verifies it, so the ship step has nothing to gate on and the work reads as verified because a list was produced. Written from a linked worktree against `pwd`, the file lands where the caller does not look.
13
+
14
+ Writing the checklist correctly to disk does not close the gap either. The file sits under a gitignored folder on the machine that authored it, so a pull request reviewer, on that machine or another, never sees it. A record that only the author can open reads as verified for the same reason a record that never existed does.
15
+
16
+ Posting it as a comment of its own is the next failure. The screenshots the checklist annotates sit in a second comment, so a reviewer reads "the hero settles without a jump" with no picture of the hero beside it and scrolls looking for one. Two comments describing one change also drift: a later push re-renders the screenshots and leaves the checklist claiming what an older head showed.
17
+
18
+ The automatable half has its own gap, and it is no longer this skill's to close by writing the tests. `test-first` writes a behavior's test while the code is written, and `test-craft` owns which layer it belongs at. A skill writing tests after implementation duplicates both, and what it produces is the test the session already skipped rather than the test the behavior needed. What stays open is that nobody names the behaviors shipping untested, which is a list rather than a test.
19
+
20
+ ## Must
21
+
22
+ - Split every change into visual-only and automatable before writing anything
23
+ - Write the checklist for the visual-only half, each item an action and the result a person is looking for
24
+ - Name every automatable change shipping with no test, with the layer `test-craft` would place it at, as a section of the same file
25
+ - Write the file to the `.canon/tmp/handoff/ui-checklist/<slug>.md` handoff at the main worktree root, overwriting
26
+ - Report that there is nothing to verify rather than manufacturing a checklist to show work
27
+
28
+ ## Must not
29
+
30
+ - Write a test at any layer, which is `test-first` while the code is written and `test-craft` for where it goes
31
+ - Read a project's test config or runner in order to write against it
32
+ - Try to assert a visual property programmatically
33
+ - Repeat the full checklist in chat, which is what made it evaporate
34
+ - Stage or commit the checklist, which is gitignored scratch
35
+ - Talk to GitHub directly. Posting the checklist belongs to `git-pr`, the sole consumer of the handoff file
36
+ - Move the handoff path to match this skill's name, which strands a checklist an older binary wrote
37
+
38
+ ## Guards
39
+
40
+ - No implementation context in the session: stop, since there is nothing to split
41
+ - Nothing visual and nothing untested: skip the file write and report the coverage
42
+
43
+ ## Out of scope
44
+
45
+ - Judging whether the UI is any good, which `ux-audit` owns
46
+ - The rules for choosing a layer, which `test-craft` owns
47
+ - Writing the test itself, which is `test-first`
48
+ - Deciding whether an outstanding checklist blocks the ship, which the calling pipeline gates on
49
+ - Performing the visual verification, which needs a person
@@ -0,0 +1,71 @@
1
+ ---
2
+ name: ui-checklist
3
+ description: Writes the visual checklist a reviewer reads beside the evidence screenshots, and names each changed behavior shipping with no test so `test-craft` can place one. Use after implementing UI changes, or when asked "what should I look at", "what do I verify", or "give me a visual checklist". Do NOT use in empty sessions with no implementation context, and do NOT use to write the tests themselves.
4
+ ---
5
+
6
+ # UI checklist
7
+
8
+ ## Guards
9
+
10
+ - If no implementation context exists in the session, stop: `❌ No implementation context. Describe what you built first.`
11
+ - This skill writes no test. When a behavior needs one, name it and let `canon:test-craft` place it.
12
+
13
+ ## Analysis
14
+
15
+ Review the session to identify what was built or changed. Split each change two ways:
16
+
17
+ - **Visual-only:** spacing, alignment, color, typography, layout proportions, animation timing. These are what the checklist exists for, since nothing asserts them programmatically.
18
+ - **Automatable:** interactions, state transitions, form submissions, keyboard handling, conditional rendering, loading, empty, and error states, plus journeys across routes and behavior only a real browser renders.
19
+
20
+ An automatable change already covered by a test written during implementation is done. What is left is the missing-test list below.
21
+
22
+ ## The checklist
23
+
24
+ Group visual items by feature area. Use `- [ ]` checkbox syntax, and write each item as the action and the result a person is looking for, so a reviewer who did not build the change can run it.
25
+
26
+ ```markdown
27
+ **What to verify visually:**
28
+
29
+ **<Feature area>**
30
+
31
+ - [ ] <action> → <expected visual result>
32
+ ```
33
+
34
+ ## The missing-test list
35
+
36
+ Name every automatable change this branch ships with no test, one per line, with the layer `test-craft` would place it at. Do not write the test and do not read the project's test config to write one against.
37
+
38
+ Route each through the `test-craft` layer table: a journey or a browser-only behavior to end to end, a rendered state to a component test, and logic that renders nothing to a unit test. When the project has no runner at that layer, say so on the line rather than moving the item to a layer that does.
39
+
40
+ This list ships inside the checklist file, below the visual items, so a reviewer reads what is untested next to what to look at:
41
+
42
+ ```markdown
43
+ **Shipping without a test:**
44
+
45
+ - <behavior> → <layer> (<runner, or the runner that is missing>)
46
+ ```
47
+
48
+ Omit the heading when every automatable change is covered.
49
+
50
+ ## Persist the checklist
51
+
52
+ Derive `<slug>` per `${CLAUDE_SKILL_DIR}/../../standards/slug.md`. Fall back to `latest` on an empty result.
53
+
54
+ When a checklist is produced, write it directly to `.canon/tmp/handoff/ui-checklist/<slug>.md` at the main worktree root, not the current worktree. Resolve that root the way `session-worktree` does. Create the directory if it does not exist. Always overwrite. This is a handoff file rather than a deliverable: `git-pr` renders it into the evidence comment once a pull request opens, then removes it, and nothing here talks to `gh` directly.
55
+
56
+ The path names the artifact rather than this skill, so it does not move when the skill is renamed. A checklist written by an older binary still lands where the consumer looks.
57
+
58
+ From a linked worktree the file-editing tools refuse that path, so the checklist goes out through `Bash`. Send the `mkdir -p` and the heredoc as two plain commands rather than joining them with `&&`, which is refused as compound.
59
+
60
+ Skip the file write when there is nothing to verify visually and nothing shipping untested.
61
+
62
+ The `.canon/tmp/` directory is gitignored. Do not stage or commit the file.
63
+
64
+ ## Output order
65
+
66
+ 1. If a checklist was produced, write it to file, then output only the file path in chat:
67
+ `📝 Wrote .canon/tmp/handoff/ui-checklist/<slug>.md`
68
+ 2. Name the count of visual items and the count of behaviors shipping without a test, on one line each
69
+ 3. If nothing was produced: `✅ Nothing to verify visually and nothing shipping untested.`
70
+
71
+ Do not repeat the full checklist in chat.
@@ -35,6 +35,6 @@ The pass slides into fixing what it finds, and then the audit and the change lan
35
35
 
36
36
  - Fixing what it found, which is a separate change with its own review
37
37
  - Feature planning, which `plan-feature` owns
38
- - Verifying one specific change, which `ui-test` owns
38
+ - Verifying one specific change, which `ui-checklist` owns
39
39
  - Defining the intent it audits against, which `design-extract` and the wireframes own
40
40
  - Measuring what a running interface costs to paint, block, or shift, which `ux-measure` owns. Contrast stays here rather than going with it, being computable from two color values this skill already reads off the token table.
@@ -44,5 +44,5 @@ A session that does start one picks a runner on the spot. The reading then comes
44
44
  - Judging the interface against stated intent, which `ux-audit` owns
45
45
  - Contrast, which is computable from two color values in the token table `ux-audit` already reads. A contrast failure from a color computed at runtime stays invisible to that reader, and the cost is accepted rather than overlooked.
46
46
  - Network waterfall, bundle size, and accessibility, which no version of this measures
47
- - Writing behavioral tests against the interface, which `ui-test` owns
47
+ - Writing behavioral tests against the interface, which `test-first` and `test-craft` own
48
48
  - Fixing what the reading found