@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.
- package/README.md +29 -27
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/auto-ship/SKILL.md +6 -4
- package/claude/skills/draft-and-pick/REQUIREMENT.md +3 -1
- package/claude/skills/draft-and-pick/SKILL.md +3 -2
- package/claude/skills/draft-and-pick/references/frame.html +118 -0
- package/claude/skills/draft-ready/SKILL.md +3 -3
- package/claude/skills/git-pr/SKILL.md +41 -21
- package/claude/skills/test-craft/REQUIREMENT.md +1 -1
- package/claude/skills/test-first/REQUIREMENT.md +1 -1
- package/claude/skills/ui-checklist/REQUIREMENT.md +49 -0
- package/claude/skills/ui-checklist/SKILL.md +71 -0
- package/claude/skills/ux-audit/REQUIREMENT.md +1 -1
- package/claude/skills/ux-measure/REQUIREMENT.md +1 -1
- package/docs/agents/capture.md +2 -2
- package/docs/agents/demo.md +3 -1
- package/docs/agents/pr-evidence.md +34 -11
- package/docs/agents/pr-preview.md +4 -3
- package/docs/agents/tasks.md +3 -1
- package/docs/workflow/ai-workflow.md +4 -4
- package/docs/workflow/visual-design-workflow.md +2 -2
- package/package.json +1 -1
- package/scripts/core/regen-hero.sh +33 -1
- package/src/claude/cases/workflow.ts +3 -2
- package/src/commands/demo.ts +4 -3
- package/src/commands/pr.ts +53 -1
- package/src/commands/tasks.ts +16 -0
- package/src/demo/compile.ts +20 -0
- package/src/demo/drive.ts +1 -0
- package/src/migrate/skill-names.ts +14 -6
- package/src/pr/evidence.ts +36 -1
- package/src/sandbox/expect.ts +2 -1
- package/src/slides/styles.ts +28 -3
- package/src/tasks/archive.ts +115 -3
- package/standards/ready.md +2 -2
- package/standards/tasks.md +4 -2
- package/claude/skills/ui-test/REQUIREMENT.md +0 -48
- 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
|
[](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
|
-
|
|
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
|
-
|
|
15
|
+
</div>
|
|
16
16
|
|
|
17
|
-

|
|
18
18
|
|
|
19
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
70
|
+

|
|
69
71
|
|
|
70
|
-
|
|
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
|
-
|
|
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
|
-
|
|
76
|
+

|
|
75
77
|
|
|
76
|
-

|
|
77
79
|
|
|
78
|
-
|
|
80
|
+
## It runs on itself
|
|
79
81
|
|
|
80
|
-
The
|
|
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
|
-

|
|
83
85
|
|
|
84
|
-
|
|
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
|
|
|
@@ -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
|
|
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-
|
|
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-
|
|
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
|
-
|
|
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-
|
|
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
|
|
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:`
|
|
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:
|
|
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
|
|
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
|
-
###
|
|
155
|
+
### Find the UI checklist
|
|
156
156
|
|
|
157
|
-
`ui-
|
|
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
|
|
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
|
-
|
|
168
|
+
canon pr evidence <number> --checklist <main-root>/.canon/tmp/handoff/ui-checklist/<slug>.md --json
|
|
163
169
|
```
|
|
164
170
|
|
|
165
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
194
|
+
### Post the UI checklist alone
|
|
182
195
|
|
|
183
|
-
|
|
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/
|
|
199
|
+
gh pr comment <number> --body-file <main-root>/.canon/tmp/handoff/ui-checklist/<slug>.md
|
|
188
200
|
```
|
|
189
201
|
|
|
190
|
-
|
|
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
|
-
|
|
207
|
+
rm <main-root>/.canon/tmp/handoff/ui-checklist/<slug>.md
|
|
194
208
|
```
|
|
195
209
|
|
|
196
|
-
|
|
210
|
+
```bash
|
|
211
|
+
rmdir <main-root>/.canon/tmp/handoff/ui-checklist 2>/dev/null || true
|
|
212
|
+
```
|
|
197
213
|
|
|
198
|
-
|
|
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
|
|
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
|
|
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
|
|
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-
|
|
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-
|
|
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 `
|
|
47
|
+
- Writing behavioral tests against the interface, which `test-first` and `test-craft` own
|
|
48
48
|
- Fixing what the reading found
|