@erclx/canon 5.23.0 → 5.25.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 (98) hide show
  1. package/claude/.claude-plugin/plugin.json +1 -1
  2. package/claude/skills/backlog-triage/REQUIREMENT.md +2 -2
  3. package/claude/skills/backlog-triage/SKILL.md +19 -6
  4. package/claude/skills/canon-operator/REQUIREMENT.md +1 -0
  5. package/claude/skills/canon-operator/SKILL.md +5 -1
  6. package/claude/skills/canvas/REQUIREMENT.md +49 -0
  7. package/claude/skills/canvas/SKILL.md +106 -0
  8. package/claude/skills/design-extract/REQUIREMENT.md +1 -1
  9. package/claude/skills/draft-doc/REQUIREMENT.md +2 -1
  10. package/claude/skills/draft-doc/references/readme/agent-facing.md +59 -0
  11. package/claude/skills/draft-doc/references/readme/application.md +66 -0
  12. package/claude/skills/draft-doc/references/readme/cli.md +64 -0
  13. package/claude/skills/draft-doc/references/readme/library.md +57 -0
  14. package/claude/skills/draft-doc/references/readme/plugin.md +66 -0
  15. package/claude/skills/draft-doc/references/readme.md +33 -4
  16. package/claude/skills/draft-figure/REQUIREMENT.md +14 -3
  17. package/claude/skills/draft-figure/SKILL.md +24 -51
  18. package/claude/skills/draft-figure/references/architecture-set.md +35 -0
  19. package/claude/skills/draft-figure/references/hand-drawn.md +31 -0
  20. package/claude/skills/draft-figure/references/mermaid.md +55 -0
  21. package/claude/skills/git-followup/SKILL.md +17 -6
  22. package/claude/skills/git-pr/references/evidence.md +4 -2
  23. package/claude/skills/git-split/SKILL.md +1 -1
  24. package/claude/skills/index-lookup/SKILL.md +2 -2
  25. package/claude/skills/review-address/SKILL.md +6 -13
  26. package/claude/skills/review-address/references/rest-reads.md +43 -0
  27. package/claude/skills/review-craft/references/ui.md +7 -1
  28. package/claude/skills/review-pr/REQUIREMENT.md +1 -0
  29. package/claude/skills/review-pr/SKILL.md +9 -3
  30. package/claude/skills/review-pr/references/close-out.md +2 -2
  31. package/claude/skills/review-pr/references/post.md +17 -5
  32. package/claude/skills/review-pr/references/scope.md +10 -8
  33. package/claude/skills/review-ui/SKILL.md +12 -4
  34. package/claude/skills/role-orchestrator/references/orchestrator-cloud-launch.md +30 -1
  35. package/claude/skills/role-orchestrator/references/orchestrator-launch.md +4 -0
  36. package/claude/skills/role-orchestrator/references/orchestrator-poll.md +4 -4
  37. package/claude/skills/role-orchestrator/references/orchestrator-review-dispatch.md +6 -0
  38. package/claude/skills/role-orchestrator/scripts/poll.ts +90 -27
  39. package/claude/skills/role-orchestrator/scripts/repo.ts +3 -5
  40. package/claude/skills/role-orchestrator/scripts/watch.ts +4 -7
  41. package/docs/agents/canvas.md +6 -2
  42. package/docs/agents/commands.md +45 -45
  43. package/docs/agents/context-audit-checks.md +2 -2
  44. package/docs/agents/index.md +0 -1
  45. package/docs/agents/key-changes.md +3 -3
  46. package/docs/agents/pr-evidence.md +11 -2
  47. package/docs/agents/pr-reads.md +5 -4
  48. package/docs/target/projects.md +4 -4
  49. package/docs/workflow/ai-workflow.md +0 -1
  50. package/docs/workflow/skill-map.md +3 -3
  51. package/docs/workflow/visual-design-workflow.md +2 -1
  52. package/governance/renames.toml +0 -4
  53. package/governance/rules/standards/markdown.md +1 -0
  54. package/package.json +1 -2
  55. package/scripts/core/install-check.sh +1 -1
  56. package/src/audits/catalog.ts +2 -2
  57. package/src/canvas/client/inspector/field.tsx +200 -0
  58. package/src/canvas/client/inspector/fields.css +151 -0
  59. package/src/canvas/client/inspector/section.tsx +27 -0
  60. package/src/canvas/client/inspector/values.ts +60 -0
  61. package/src/canvas/client/inspector.tsx +192 -111
  62. package/src/canvas/client/shell.css +0 -94
  63. package/src/canvas/tokens.ts +9 -1
  64. package/src/claude/cases/misc.ts +5 -0
  65. package/src/claude/cases/workflow.ts +1 -1
  66. package/src/claude/seeds.ts +0 -1
  67. package/src/commands/canvas.ts +29 -0
  68. package/src/commands/design.ts +2 -64
  69. package/src/commands/pr.ts +219 -202
  70. package/src/context/audit.ts +7 -6
  71. package/src/design/budget.ts +149 -0
  72. package/src/design/document.ts +22 -10
  73. package/src/design/render.ts +11 -1
  74. package/src/design/tokens.ts +40 -61
  75. package/src/gate/measures.ts +49 -0
  76. package/src/gate/stages.ts +5 -1
  77. package/src/help.ts +2 -2
  78. package/src/migrate/skill-names.ts +7 -1
  79. package/src/pr/evidence.ts +25 -5
  80. package/src/pr/rest.ts +137 -0
  81. package/standards/canvas.md +112 -0
  82. package/standards/context.md +2 -1
  83. package/standards/design.md +13 -3
  84. package/standards/figures.md +0 -1
  85. package/standards/index.md +1 -1
  86. package/standards/markdown.md +1 -0
  87. package/standards/mermaid.md +1 -7
  88. package/tooling/claude/reference.md +0 -5
  89. package/tooling/claude/seeds/.claude/hooks/scratch-guard.sh +1 -1
  90. package/claude/skills/draft-diagram/REQUIREMENT.md +0 -45
  91. package/claude/skills/draft-diagram/SKILL.md +0 -177
  92. package/docs/agents/design-board.md +0 -33
  93. package/governance/rules/standards/diagrams.md +0 -14
  94. package/governance/rules/standards/mermaid.md +0 -15
  95. package/scripts/core/check-gallery-exclusion.sh +0 -50
  96. package/src/design/board.ts +0 -576
  97. package/standards/diagrams.md +0 -132
  98. package/tooling/claude/seeds/.claude/diagrams/index.md +0 -8
@@ -11,50 +11,50 @@ Full help: `canon <command> --help`. Bare `canon --help` lists every top-level c
11
11
 
12
12
  One row per top-level command, in the order `canon --help` prints them. A domain's subcommands are in `## Domain commands` below.
13
13
 
14
- | Command | Purpose |
15
- | --------------------------- | ------------------------------------------------------------------------------- |
16
- | `canon init [path]` | Bootstrap a project with selected toolkit domains |
17
- | `canon sync [path]` | Sync every installed domain, or report drift and version skew with `--check` |
18
- | `canon upgrade` | Reinstall the CLI globally with the package manager the install path names |
19
- | `canon migrate <verb>` | Move a project off a retired name or layout, reporting the plan until `--write` |
20
- | `canon targets <verb>` | Report the projects this toolkit installed into |
21
- | `canon gov <verb>` | Install and sync governance rules, and audit the tree against them |
22
- | `canon standards <verb>` | List, audit, or print a standard by name |
23
- | `canon tooling <verb>` | Sync, diff, and verify tooling stacks |
24
- | `canon claude <verb>` | Install the Claude workflow and audit both skill corpora |
25
- | `canon wiki <verb>` | Scaffold the wiki pages |
26
- | `canon indexes <verb>` | Regenerate `index.md` files from sibling frontmatter, or flatten them |
27
- | `canon docs [topic]` | Emit toolkit reference docs (`list`, or a topic by name) |
28
- | `canon design <verb>` | Render `canon/DESIGN.md` tokens, build the board, install the base stylesheet |
29
- | `canon slides <verb>` | Render a `.claude/SLIDES.md` deck, or list its layouts |
30
- | `canon capture [source]` | Render HTML or a URL to PNG and prove each declared font resolved |
31
- | `canon serve [dir]` | Serve a directory on loopback and print the link that opens it |
32
- | `canon demo <verb>` | Compile and record a running app |
33
- | `canon inventory [subject]` | Group every route's elements by one computed property, never gating |
34
- | `canon drive <url> <run>` | Walk a page through named interactions and measure each state |
35
- | `canon transcripts <url>` | Fetch a YouTube transcript with metadata frontmatter (needs `yt-dlp`) |
36
- | `canon teach <verb>` | Open and author learning workspaces |
37
- | `canon canvas <verb>` | Serve a local canvas of pages and HTML frames, arrange, edit, and capture them |
38
- | `canon sandbox [cat:cmd]` | Run sandbox scenarios, toolkit-only like the tree it reads |
39
- | `canon tasks <verb>` | Read and write the task board, the plans it cites, and its archive |
40
- | `canon intake <verb>` | Report intake folders and write answers into them |
41
- | `canon records <verb>` | Validate, size, prune, and back up the session records |
42
- | `canon sessions <verb>` | Resolve live sessions to worktree and branch, and move one between machines |
43
- | `canon worktrees <verb>` | Report and remove worktrees whose pull request merged |
44
- | `canon hooks <verb>` | Run the steps a git hook drives |
45
- | `canon autoship <verb>` | Decide whether a changed set needs the review pass |
46
- | `canon pr <verb>` | Read a pull request's diff, head, checks, and review, and post its evidence |
47
- | `canon feedback` | Write toolkit feedback from stdin, or open an issue with `--github` |
48
- | `canon audits <verb>` | Run every audit as one set under one verdict against the recorded baseline |
49
- | `canon gate <verb>` | Run every stage that guards a branch here |
50
- | `canon secrets <verb>` | Report credential-shaped values in the tree the package ships |
51
- | `canon deps <verb>` | Report published advisories against the resolved dependency set |
52
- | `canon labels <verb>` | Read a changed set against the label map, and scan pull request text |
53
- | `canon comments <verb>` | Measure comment density by language and kind, with a trend from git |
54
- | `canon context <verb>` | Report context folder health, and classify changed or stale sections |
55
- | `canon markdown <verb>` | Fail markdown on a banned character or a dead link, and report its structure |
56
- | `canon repo <verb>` | Propose and apply the remote's description, homepage, and topics |
57
- | `canon census [path]` | Report tracked file count, extensions, and a line total skipping binaries |
14
+ | Command | Purpose |
15
+ | --------------------------- | ------------------------------------------------------------------------------------------ |
16
+ | `canon init [path]` | Bootstrap a project with selected toolkit domains |
17
+ | `canon sync [path]` | Sync every installed domain, or report drift and version skew with `--check` |
18
+ | `canon upgrade` | Reinstall the CLI globally with the package manager the install path names |
19
+ | `canon migrate <verb>` | Move a project off a retired name or layout, reporting the plan until `--write` |
20
+ | `canon targets <verb>` | Report the projects this toolkit installed into |
21
+ | `canon gov <verb>` | Install and sync governance rules, and audit the tree against them |
22
+ | `canon standards <verb>` | List, audit, or print a standard by name |
23
+ | `canon tooling <verb>` | Sync, diff, and verify tooling stacks |
24
+ | `canon claude <verb>` | Install the Claude workflow and audit both skill corpora |
25
+ | `canon wiki <verb>` | Scaffold the wiki pages |
26
+ | `canon indexes <verb>` | Regenerate `index.md` files from sibling frontmatter, or flatten them |
27
+ | `canon docs [topic]` | Emit toolkit reference docs (`list`, or a topic by name) |
28
+ | `canon design <verb>` | Render or regenerate `canon/DESIGN.md` tokens, emit their CSS, install the base stylesheet |
29
+ | `canon slides <verb>` | Render a `.claude/SLIDES.md` deck, or list its layouts |
30
+ | `canon capture [source]` | Render HTML or a URL to PNG and prove each declared font resolved |
31
+ | `canon serve [dir]` | Serve a directory on loopback and print the link that opens it |
32
+ | `canon demo <verb>` | Compile and record a running app |
33
+ | `canon inventory [subject]` | Group every route's elements by one computed property, never gating |
34
+ | `canon drive <url> <run>` | Walk a page through named interactions and measure each state |
35
+ | `canon transcripts <url>` | Fetch a YouTube transcript with metadata frontmatter (needs `yt-dlp`) |
36
+ | `canon teach <verb>` | Open and author learning workspaces |
37
+ | `canon canvas <verb>` | Serve a local canvas of pages and HTML frames, arrange, edit, and capture them |
38
+ | `canon sandbox [cat:cmd]` | Run sandbox scenarios, toolkit-only like the tree it reads |
39
+ | `canon tasks <verb>` | Read and write the task board, the plans it cites, and its archive |
40
+ | `canon intake <verb>` | Report intake folders and write answers into them |
41
+ | `canon records <verb>` | Validate, size, prune, and back up the session records |
42
+ | `canon sessions <verb>` | Resolve live sessions to worktree and branch, and move one between machines |
43
+ | `canon worktrees <verb>` | Report and remove worktrees whose pull request merged |
44
+ | `canon hooks <verb>` | Run the steps a git hook drives |
45
+ | `canon autoship <verb>` | Decide whether a changed set needs the review pass |
46
+ | `canon pr <verb>` | Read a pull request's diff, head, checks, and review, and post its evidence |
47
+ | `canon feedback` | Write toolkit feedback from stdin, or open an issue with `--github` |
48
+ | `canon audits <verb>` | Run every audit as one set under one verdict against the recorded baseline |
49
+ | `canon gate <verb>` | Run every stage that guards a branch here |
50
+ | `canon secrets <verb>` | Report credential-shaped values in the tree the package ships |
51
+ | `canon deps <verb>` | Report published advisories against the resolved dependency set |
52
+ | `canon labels <verb>` | Read a changed set against the label map, and scan pull request text |
53
+ | `canon comments <verb>` | Measure comment density by language and kind, with a trend from git |
54
+ | `canon context <verb>` | Report context folder health, and classify changed or stale sections |
55
+ | `canon markdown <verb>` | Fail markdown on a banned character or a dead link, and report its structure |
56
+ | `canon repo <verb>` | Propose and apply the remote's description, homepage, and topics |
57
+ | `canon census [path]` | Report tracked file count, extensions, and a line total skipping binaries |
58
58
 
59
59
  `canon serve` drives no browser, which is what separates it from the four that do. All four ship now that `capture` does, so the line between them is the engine rather than the package. A generated page loses its script to an editor preview and to a `file://` open, so the link is the delivery rather than a convenience, and every generated surface here reaches a reader through one. A teach lesson's stylesheet is embedded rather than linked, so only its script still needs a server. It binds `127.0.0.1` and never a wildcard, because what it is pointed at is routinely a gitignored record tree. It sends `cache-control: no-store`, since a preview exists to be edited and reloaded and a cached stylesheet reads as a fix that did not work.
60
60
 
@@ -91,7 +91,7 @@ Each domain exposes a consistent shape where applicable: `list`, `install`, `syn
91
91
  - `wiki`, in `scripting.md`: `init`
92
92
  - `indexes`, in `indexes.md`: `regen`, `list`
93
93
  - `docs`, in `docs.md`: `list`, `<topic>`
94
- - `design`, in `design-board.md`: `render`, `regen`, `board`, `css`, `install`, `sync`
94
+ - `design`, in `install-and-sync.md` for `install` and `sync`, and described below for the rest: `render`, `regen`, `css`, `install`, `sync`
95
95
  - `slides`, described below: `render`, `list`
96
96
  - `demo`, in `demo.md`: `compile`, `run`, `frames`
97
97
  - `inventory`, described above: `run`
@@ -85,9 +85,9 @@ All three wireframe findings are printed under a bare run. The states-mismatch f
85
85
 
86
86
  The provenance check covers `canon/context/` alone, the reference-form check covers the split folders inside it, the required-section check covers `canon/context/` and `canon/wireframes/`, length and the table finding reach every audited folder, and the wireframe states check covers `canon/wireframes/` alone.
87
87
 
88
- What narrows the provenance check is stated in `standards/context.md`, which opens its scope by handing diagrams and wireframes to `diagrams.md` and `wireframes.md`, and the sibling standards do not restate it. A marker reported in a diagram entry would cite a rule that entry's own standard routes elsewhere. The split is between kinds of rule rather than kinds of folder, and what decides it is which tier states the rule rather than what the check measures.
88
+ What narrows the provenance check is stated in `standards/context.md`, which opens its scope by handing figures to the `draft-figure` skill and wireframes to `wireframes.md`, and the sibling standards do not restate it. A marker reported in a diagram or wireframe entry would cite a rule that entry's own standard routes elsewhere. The split is between kinds of rule rather than kinds of folder, and what decides it is which tier states the rule rather than what the check measures.
89
89
 
90
- Length and the table finding generalize as judgments about how far a reader travels, so both reach wherever the audit is pointed. Required sections narrow for a plainer reason: each list is one standard's own, so it reaches the folder that standard governs. The context and wireframe standards each state a set, and the diagram standard states none.
90
+ Length and the table finding generalize as judgments about how far a reader travels, so both reach wherever the audit is pointed. Required sections narrow for a plainer reason: each list is one standard's own, so it reaches the folder that standard governs. The context and wireframe standards each state a set, and an older install's diagrams folder has no standard to state one.
91
91
 
92
92
  The same test is what moved depth and bullet weight out of this command entirely. A rule stated at the attribute tier reaches every markdown file, and a check reaching every markdown file has no reason to require a folder that resolves.
93
93
 
@@ -20,7 +20,6 @@ CLI catalog and invocation rules for agents, split by command domain. Start with
20
20
  - [Doc classifier](context-classify.md): Classifying canonical-doc content as keep, replace/rewrite, history, or move in diff and sweep mode, the classifier setting pair, the record shape, and the exit codes
21
21
  - [Self-stated counts](counts.md): Reading a sentence that asserts a closed catalog's size, how a match is decided, the plausibility filter that keeps a generic word from matching a subset, and why the sweep reports rather than gates
22
22
  - [Demo](demo.md): Compiling a screencast draft into a runnable plan, driving a served application to a recording and a still, reading numbered frames back out of a recording, the pointer the recording paints, and what each refusal reports
23
- - [Design board](design-board.md): Generating a static index over a project's design surfaces, what each panel reads, and which two stay toolkit-checkout-only
24
23
  - [Docs](docs.md): How canon docs resolves the toolkit's own reference surface from an install root, and how a split domain is named
25
24
  - [Drift surfaces](drift-surfaces.md): The seven sections canon sync --check reports beside the per-domain scan, being seeds, superseded artifacts, unmigrated domains, surfaces at the old root, new rules and skills, and the reverse walk, with the managed-target gate and what counts toward the exit code
26
25
  - [Driver](driver.md): Walking a page through named interactions, the probe catalog and the false finding each one carries, why viewport heights are never defaulted, and what each refusal reports
@@ -26,9 +26,9 @@ The positional is the pull request to read, defaulting to the one open on this b
26
26
 
27
27
  `--base` names the far side of that range and never the commit the comparison runs against directly. The reader resolves the merge base between `HEAD` and the ref, so `--base origin/main` measures what the branch wrote rather than what the trunk gained while the branch was open. Passing the trunk as it stands today is therefore safe on a branch of any age, and a ref sharing no history with `HEAD` refuses as `bad-base`.
28
28
 
29
- Without `--body` the body, the file list, and the head commit come back from one `gh pr view` call, because the three have to describe the same commit and reading them apart leaves a window where a push between them compares a body against another head's files.
29
+ Without `--body` the body and the head commit come off the REST pull read and the file list off the paginated files endpoint. Every read runs on REST, since a cloud session's GitHub proxy refuses the GraphQL that `gh pr view` runs on. A push landing between the two reads compares a body against the next head's files, a window the verb accepts for the cloud reach.
30
30
 
31
- That view caps its file list at 100 rows and says nothing about having done so, measured against a pull request carrying 101 files where the view reported 100. A pull request at the cap therefore takes a second read through the paginated endpoint, and a failure there refuses with `gh-truncated` rather than comparing against a set known to be short. A set silently one file short is the worst input this takes, since the missing file is exactly what a correct bullet would then be accused of inventing.
31
+ A failed page refuses as `gh-failed` rather than comparing against a set known to be short. A set silently one file short is the worst input this takes, since the missing file is exactly what a correct bullet would then be accused of inventing. With no number named, the verb reads the one pull request open on the checkout's branch, and refuses as `ambiguous-pull` when two are open against different bases.
32
32
 
33
33
  ## The two directions
34
34
 
@@ -91,7 +91,7 @@ Three refusals separate a clean pass from a read that produced nothing:
91
91
 
92
92
  An empty extraction read as a clean pass is the failure shape this repository has recorded twice, which is why the middle one is its own reason rather than a zero count.
93
93
 
94
- `gh-missing`, `gh-failed`, `gh-truncated`, `unreadable-body`, `unreadable-tree`, `no-base`, `bad-base`, and `unreadable-changes` cover the reads that never reached a comparison.
94
+ `gh-missing`, `gh-failed`, `ambiguous-pull`, `unreadable-body`, `unreadable-tree`, `no-base`, `bad-base`, and `unreadable-changes` cover the reads that never reached a comparison.
95
95
 
96
96
  ## What it was measured against
97
97
 
@@ -138,7 +138,7 @@ call, is what keeps two close-out comments from landing beside each other the
138
138
  way `review-pr` once posted, before that skill's own guard existed. It is
139
139
  also why this verb renders the whole comment body rather than handing each
140
140
  skill a record to format on its own: a fix to the table shape or the
141
- collapsed-details wrapper lands once, not twice.
141
+ details wrapper lands once, not twice.
142
142
 
143
143
  ## The preview line
144
144
 
@@ -232,7 +232,16 @@ render needs, so it never refuses `no-base`. The orchestrator's draft lift is
232
232
  the caller, and lifts only on `settled`, so a refusal holds it the same as
233
233
  `owed` does.
234
234
 
235
- ## What a collapsed comment still leaves to GitHub
235
+ ## When the comment opens
236
+
237
+ The comment opens every state while it carries six images or fewer, counting one
238
+ per row for the head plus one per row that is not new, and closes every state
239
+ past that, so the whole comment is open or closed together. A line under
240
+ `## Evidence` names the compared base and head as short shas, which tells a
241
+ reader whether the images match the latest push. The base is the merge base the
242
+ compare endpoint returns, not trunk's tip.
243
+
244
+ ## What the comment still leaves to GitHub
236
245
 
237
246
  GitHub already draws its own before-and-after comparison on the Files Changed
238
247
  tab for any tracked image that changed. This comment does not make that view
@@ -18,7 +18,7 @@ canon pr review-state 1341 --json
18
18
 
19
19
  ## The pull request object is not the authority for a head
20
20
 
21
- `gh pr view --json headRefOid` reads a field on the pull request object. That object trails the branch ref by up to a minute after a push and carries nothing saying how far behind it is, so a session that keys a read on it is describing whichever commit GitHub last folded into the object.
21
+ The pull request's head sha, `head.sha` on the REST pull read and `headRefOid` in `gh pr view`, is a field on the pull request object. That object trails the branch ref by up to a minute after a push and carries nothing saying how far behind it is, so a session that keys a read on it is describing whichever commit GitHub last folded into the object.
22
22
 
23
23
  Two failures on 2026-09-01 came off that one field. A reviewing session posted a finding calling a pushed commit unpushed. A worker fired a green claim and retracted it on its own thread minutes later. Neither session did anything wrong with the value it was handed. The value was stale and said so nowhere.
24
24
 
@@ -74,7 +74,7 @@ Both empty cases report `pending` instead: a tip carrying no run yet, and a list
74
74
 
75
75
  The verb keeps the newest run per check name rather than rolling every matched run into the state. A workflow that fires twice on one commit used to leave its first, stale run beside the rerun, where a failing one could outvote a passing rerun. Grouping the matched set by name and keeping the highest `id` per group removes that stale run before the rollup runs. `collapsed` on the record counts what the fold discards, so a caller reading it against `matched` sees the dedupe directly, instead of reading a `matched` above the check count as the verb disagreeing with `gh pr checks`.
76
76
 
77
- A conflicted branch produces no `pull_request` check runs rather than a failing one. GitHub builds no merge ref for it, so no `pull_request` workflow starts and a repository running only that trigger leaves the listing empty, which reads as `pending` and never leaves it. A workflow triggered by a push to the branch still runs on the tip, so `matched` rises above zero, `conflicted` stays false, and the wait ends on those runs rather than on the reading. `mergeStateStatus` is the field that separates the two, and the record carries `conflicted: true` and `mergeState` when it reads `DIRTY` with no run belonging to the tip. A `DIRTY` reading beside runs on the tip leaves the state as those runs say, since a conflict arising after they completed does not unsay them. `UNKNOWN` is transient while GitHub computes the merge state, right after a push, so it reads as not conflicted and costs one more poll. A binary predating the field keeps answering `pending`.
77
+ A conflicted branch produces no `pull_request` check runs rather than a failing one. GitHub builds no merge ref for it, so no `pull_request` workflow starts and a repository running only that trigger leaves the listing empty, which reads as `pending` and never leaves it. A workflow triggered by a push to the branch still runs on the tip, so `matched` rises above zero, `conflicted` stays false, and the wait ends on those runs rather than on the reading. The pull request's `mergeable_state` is the field that separates the two. The verb raises it to upper case, matching the GraphQL `mergeStateStatus` it replaced, and the record carries `conflicted: true` and `mergeState` when it reads `DIRTY` with no run belonging to the tip. A `DIRTY` reading beside runs on the tip leaves the state as those runs say, since a conflict arising after they completed does not unsay them. `UNKNOWN` is transient while GitHub computes the merge state, right after a push, so it reads as not conflicted and costs one more poll. A binary predating the field keeps answering `pending`.
78
78
 
79
79
  A listing carrying even one foreign run reports `pending` whatever the matching half says. A set that describes another commit says nothing about this one, so answering off the rows that happen to match would put a verdict on a set already known to be incomplete.
80
80
 
@@ -90,6 +90,7 @@ Each refusal names a different repair:
90
90
  | -------------------- | ------------------------------------------------------------------------------------------ |
91
91
  | `gh-missing` | `gh` is not on the path, so no pull request resolved |
92
92
  | `gh-failed` | `gh` could not answer for this branch, so name a number instead |
93
+ | `ambiguous-pull` | Two open pull requests share the branch against different bases, so name a number |
93
94
  | `no-branch` | The pull request carries no head branch name, so no ref could be read |
94
95
  | `unresolvable-ref` | The remote read failed, which is not the same answer as an absent branch |
95
96
  | `no-remote-branch` | The remote carries no branch by that name, so there is no tip to compare against |
@@ -105,6 +106,6 @@ One remote round trip per call, sampled here at 0.41s, 0.55s, and 0.67s on 2026-
105
106
 
106
107
  One head-sensitive read is left on the object deliberately. `canon targets pulls` reads `statusCheckRollup` for every open pull request across every target, where resolving a tip per row would cost one remote read per pull request across a dozen projects, against a surface that reports a listing rather than gating a push.
107
108
 
108
- The orchestrator poll spends that same read per open pull request and repeats it on a three-minute timer, which makes it the heaviest caller here rather than an exception to the paragraph above. Six open pull requests is around 120 remote reads an hour. It now spends a third read per pull request per tick besides, since `canon pr review-state` makes its own `gh pr view` call beside the payload the poll already fetched. That one is an API round trip rather than a remote ref read, and it buys a coverage test that answers about what a session read. What separates the two cases is what each reading decides rather than what it costs. The poll's head fires the review trigger, so a stale one sends a pass at a commit nobody read or withholds one that is owed. The listing decides nothing, so the same spend buys a fresher column in a report and no correctness at all.
109
+ The orchestrator poll spends that same read per open pull request and repeats it on a three-minute timer, which makes it the heaviest caller here rather than an exception to the paragraph above. Six open pull requests is around 120 remote reads an hour. It reads over REST alone, one reviews read and one issue-comments read per pull request, plus a pull read only when `canon pr head` returns no tip. It spends a further read per pull request per tick besides, since `canon pr review-state` makes its own REST reviews read beside the payload the poll already fetched. That one is an API round trip rather than a remote ref read, and it buys a coverage test that answers about what a session read. What separates the two cases is what each reading decides rather than what it costs. The poll's head fires the review trigger, so a stale one sends a pass at a commit nobody read or withholds one that is owed. The listing decides nothing, so the same spend buys a fresher column in a report and no correctness at all.
109
110
 
110
- `canon pr key-changes` also stays on the object, for a different reason. It reads the body, the file list, and `headRefOid` in one call on purpose, so the three describe one commit. That is a consistency requirement rather than a freshness one, and keying its head elsewhere would break it.
111
+ `canon pr key-changes` also stays on the object, for a different reason. It reads the body and the head off one pull read, so the two describe one commit. That is a consistency requirement rather than a freshness one, and keying its head elsewhere would break it. The file list comes off a second read, since only GraphQL returns it beside the body and a cloud session's proxy refuses GraphQL.
@@ -73,14 +73,14 @@ Scaffold installs tooling and seeds. It does not fill the planning docs or the d
73
73
 
74
74
  1. Fill `canon/REQUIREMENTS.md` and `canon/ARCHITECTURE.md`. The seed provides the files, the scope and decisions are yours to write. Each seed states its own caps, 12 decisions of at most 150 words and 6 risk bullets for the architecture record and 600 words for the requirements, and `canon context audit` gates on them. Edit a number in the record to loosen it.
75
75
  2. For a UI project, invoke `canon:design-extract` to draft `canon/DESIGN.md`. With no UI code yet it takes the greenfield path and proposes tokens from the requirements and a `## Personality` section. Skip for non-UI projects.
76
- 3. Optionally invoke `canon:draft-diagram` to draft entries under `.canon/diagrams/` from the architecture and the requirements. One file per diagram kind, so a later refresh of one kind leaves the others untouched. It renders each diagram it writes to verify the layout, which downloads the Mermaid CLI on first use and takes about 15 seconds.
76
+ 3. Optionally invoke `canon:draft-figure` naming an architecture view, such as system context or components, and the document it belongs in. It draws the view from the architecture and the requirements and renders it to verify the layout, which downloads the Mermaid CLI on first use and takes about 15 seconds.
77
77
  4. Start the feature loop. See [AI workflow](../workflow/ai-workflow.md) for the per-feature sequence.
78
78
 
79
- A machine without a renderer still gets the diagrams and is told which check was skipped.
79
+ A machine without a renderer still gets the figure and is told which check was skipped.
80
80
 
81
- Each diagram entry records the commit and date it was last verified against, and nothing maintains that record for you. The folder is redrawn on demand rather than swept on every ship, so `verified` carries the whole signal: an entry whose date sits far behind your branch is due a read, and no pass will name which one. Run `canon:draft-diagram` again when the code a kind is drawn from moves.
81
+ Nothing watches a figure once it is drawn. Run `canon:draft-figure` again when the code a view is drawn from moves.
82
82
 
83
- `canon/ARCHITECTURE.md` carries the same mechanism on the same ship. `canon:context-fold` anchors a decision it amends to the paths that decision cites, and reports an anchored decision whose cited path the branch touched.
83
+ `canon/ARCHITECTURE.md` is watched where a figure is not. `canon:context-fold` anchors a decision it amends to the paths that decision cites, and reports an anchored decision whose cited path the branch touched.
84
84
 
85
85
  ### Stack decision
86
86
 
@@ -26,7 +26,6 @@ canon/
26
26
  └── rules/ ← path-scoped governance rules, written by canon gov install
27
27
 
28
28
  .canon/
29
- ├── diagrams/ ← one Mermaid entry per diagram kind with a generated index.md, redrawn on demand
30
29
  ├── tasks/ ← one file per task with a generated index.md, local scratch
31
30
  ├── plans/ ← one plan per feature, archived inside itself once it ships
32
31
  ├── memory/ ← durable session facts no context entry owns
@@ -6,7 +6,7 @@ category: Workflow
6
6
 
7
7
  # Skill map
8
8
 
9
- Groups run in the order a project meets them, so a reader at a known point scans to that group and reads across. The set reconciles the scenarios in [AI workflow](ai-workflow.md) with the lifecycle [target projects](../target/projects.md) describes, rather than inventing a third vocabulary beside those two, so a group name matches neither source exactly and every moment either one names has a group. Each row says when to reach for the skill. What it does is the skill's own description.
9
+ Groups run in the order a project meets them. They reconcile the scenarios in [AI workflow](ai-workflow.md) with the lifecycle [target projects](../target/projects.md) describes, so every moment either one names has a group. Each row says when to reach for a skill, and its description says what it does.
10
10
 
11
11
  This page is the corpus the coverage claim is measured against: every name `canon claude skills list --names` reports takes exactly one row here. A skill serving two moments sits at the earlier one, and a mention on any other page is prose rather than routing. The landing page's skills field renders from this page, so its build refuses a skill with no row and a row naming no skill.
12
12
 
@@ -18,7 +18,6 @@ This page is the corpus the coverage claim is measured against: every name `cano
18
18
  | `canon:canon-operator` | On a project that already exists, to read what it carries before an install is picked |
19
19
  | `canon:sketch-design` | Before `design-extract`'s greenfield path, to trace a design direction from reference images or URLs |
20
20
  | `canon:design-extract` | Before the first UI feature, to draft `canon/DESIGN.md` |
21
- | `canon:draft-diagram` | Once the architecture is written, to draft per-kind entries under `.canon/diagrams/` |
22
21
  | `canon:deploy-app` | Once a project is ready to publish, to set up its Cloudflare or Vercel deploy and stop for token and domain |
23
22
  | `canon:repo-metadata` | When the GitHub About text, homepage, or topics may have drifted, to reconcile them against the README |
24
23
 
@@ -32,6 +31,7 @@ This page is the corpus the coverage claim is measured against: every name `cano
32
31
  | `canon:plan-groundwork` | When the state is unmeasured and more than one approach is live |
33
32
  | `canon:decision-escalate` | When open decisions turn on your preference and want batching into one set |
34
33
  | `canon:draft-and-pick` | When the call is taste and wants several candidates rendered side by side |
34
+ | `canon:canvas` | When a direction should be drawn as frames the operator can see, select, and edit |
35
35
  | `canon:task-board` | When a decided item needs a file on the board, or a shipped one needs archiving |
36
36
  | `canon:plan-feature` | When the approach is settled and the next step is a plan |
37
37
  | `canon:codebase-layout` | When a plan or change adds a file and you need to decide which folder holds it |
@@ -125,7 +125,7 @@ This page is the corpus the coverage claim is measured against: every name `cano
125
125
  | `canon:create-standard` | For a new authoring convention |
126
126
  | `canon:draft-doc` | For a brand-new `docs/` page, context entry, wireframe surface, wiki reference page, or `README.md`, drafted against the standard for its kind |
127
127
  | `canon:draft-ready` | For finished files a worker should copy, written as a ready folder with its overview, thin plan, and task |
128
- | `canon:draft-figure` | For a hand-drawn figure inside an existing doc, drafted against `standards/figures.md` in Mermaid or freehand SVG |
128
+ | `canon:draft-figure` | For a hand-drawn figure or an architecture view inside an existing doc, drafted in Mermaid or freehand SVG |
129
129
  | `canon:bash-cli-script` | For a small non-interactive shell wrapper, stopping at 100 lines in favor of TypeScript on Bun |
130
130
  | `canon:ci-workflow` | For a GitHub Actions workflow file |
131
131
  | `canon:draft-slides` | For a deck, drafted as `.claude/SLIDES.md` and rendered to PowerPoint |
@@ -14,7 +14,7 @@ The tier framework sits alongside [Claude Design](../../wiki/claude/design.md),
14
14
 
15
15
  ## Tier 0: prose only
16
16
 
17
- The default. `canon/DESIGN.md` holds visual intent as prose plus token tables for color, typography, spacing, borders, motion, and iconography. `.claude/WIREFRAMES.md` holds each surface's regions and states.
17
+ The default. `canon/DESIGN.md` holds visual intent as token tables for color, typography, spacing, and borders, with one Personality paragraph and at most three one-line rules a section, motion and iconography included. `.claude/WIREFRAMES.md` holds each surface's regions and states.
18
18
 
19
19
  Claude Code reads both and writes the implementation. Works for CLI tools, internal dashboards, admin panels, and backend-focused projects.
20
20
 
@@ -41,6 +41,7 @@ A cell no source anchors ends in `? verify`, and the preview shows that marker b
41
41
  - `canon:ux-audit` for UX gap detection on existing surfaces
42
42
  - `canon:ux-measure` for what a running surface costs to paint, read against published thresholds
43
43
  - `canon:draft-and-pick` for a call settled by looking, drafting several candidates onto one page and taking your pick
44
+ - `canon:canvas` for a direction worked out over several turns, drawn as HTML frames you drag, select, and restyle in the browser while the session reads your changes back. Only the pick moves into `canon/DESIGN.md`, since the canvas folder is gitignored
44
45
  - `canon:ux-walkthrough` for a multi-finding inspection pass over a running app with the operator, drafting arms from the app's own markup and recording each pick for a builder
45
46
  - `canon:draft-identity` to draft a project's logo mark and compose it into an icon sequence and a social card, through `draft-and-pick`'s own render-and-pick loop. On an Astro project holding the card route scaffold, the card lands as a route the project keeps and is captured through a running server, so it reads the project's real stylesheet rather than typed copies of every color. Everywhere else it ships as a PNG and the run says so.
46
47
  - Anthropic's `frontend-design` plugin optional for light visual steering
@@ -38,13 +38,11 @@
38
38
  "557-intake" = "657-intake"
39
39
  "558-plan" = "658-plan"
40
40
  "559-memory" = "659-memory"
41
- "560-diagrams" = "660-diagrams"
42
41
  "561-teach" = "661-teach"
43
42
  "562-session" = "662-session"
44
43
  "563-ready" = "663-ready"
45
44
  "500-prose" = "800-prose"
46
45
  "501-markdown" = "801-markdown"
47
- "502-mermaid" = "802-mermaid"
48
46
  "580-readme" = "880-readme"
49
47
  "591-standard-authoring" = "891-standard-authoring"
50
48
  "065-spelling" = "765-spelling"
@@ -61,13 +59,11 @@
61
59
  "656-groundwork" = "groundwork"
62
60
  "657-intake" = "intake"
63
61
  "659-memory" = "memory"
64
- "660-diagrams" = "diagrams"
65
62
  "661-teach" = "teach"
66
63
  "662-session" = "session"
67
64
  "663-ready" = "ready"
68
65
  "570-skill" = "skill"
69
66
  "590-rule-authoring" = "rule"
70
67
  "801-markdown" = "markdown"
71
- "802-mermaid" = "mermaid"
72
68
  "880-readme" = "readme"
73
69
  "891-standard-authoring" = "standard"
@@ -13,3 +13,4 @@ paths:
13
13
  - Read it before a substantial markdown edit. Do not work the banned characters from memory.
14
14
  - Run `canon markdown audit <path>` after the edit, and rewrite the sentence carrying a hit rather than swapping the token for a near-synonym.
15
15
  - Voice, rhythm, and sentence construction are a separate topic. `800-prose` routes them.
16
+ - Load the `canon:draft-figure` skill before drafting or revising a Mermaid fence or a figure, and report it rather than proceeding silently when it does not resolve.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/canon",
3
3
  "type": "module",
4
- "version": "5.23.0",
4
+ "version": "5.25.0",
5
5
  "description": "Runs your agent's work and keeps its code good, on one setup for every repo.",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -45,7 +45,6 @@
45
45
  "web:favicon": "bun scripts/core/regen-web-favicon.ts",
46
46
  "web:previews": "bun scripts/core/regen-web-previews.ts",
47
47
  "web:build": "bun run web:tokens && bun run web:favicon && bun run web:previews && cd web && astro check && astro build",
48
- "web:gallery": "cd web && astro build --config gallery.config.mjs",
49
48
  "web:card": "cd web && astro dev --config card.config.mjs",
50
49
  "web:card:check": "PROJECT_ROOT=web bash tooling/astro/configs/scripts/check-card-exclusion.sh",
51
50
  "web:preview": "cd web && astro preview",
@@ -101,7 +101,7 @@ log_step "Assert scaffold"
101
101
  # scaffold reads a standard through `canon standards <name>`, resolving against
102
102
  # the toolkit rather than a copy this gate could assert on. The two
103
103
  # rule paths stand for the `claude` and `canon` groups `base` takes whole.
104
- for path in "CLAUDE.md" ".claude/wiki/index.md" ".claude" "canon/context/index.md" "canon/wireframes/index.md" ".canon/diagrams/index.md" \
104
+ for path in "CLAUDE.md" ".claude/wiki/index.md" ".claude" "canon/context/index.md" "canon/wireframes/index.md" \
105
105
  ".prettierrc" ".editorconfig" ".lintstagedrc" ".husky/pre-commit" ".github/workflows/verify.yml" "scripts/verify.sh" \
106
106
  ".claude/rules/canon/claude/565-behavior.md" ".claude/rules/canon/canon/604-scratch.md"; do
107
107
  if [ ! -e "$TARGET_DIR/$path" ]; then
@@ -587,8 +587,8 @@ export const AUDITS: readonly AuditSpec[] = [
587
587
  corpus: 'tracked',
588
588
  // The one reason this verb refuses for that is an absence rather than a
589
589
  // break, on the same test the reach check and the skill audit take: no
590
- // target adopts `canon/context/`, `.canon/diagrams/`, and
591
- // `canon/wireframes/` all at once, so without the allowance every such
590
+ // target adopts `canon/context/`, an older install's `.canon/diagrams/`,
591
+ // and `canon/wireframes/` all at once, so without the allowance every such
592
592
  // project reports the verb unmeasured on every run and never changes.
593
593
  absentReasons: ['no-folders'] satisfies ContextAuditRefusal[],
594
594
  counts: contextCounts,
@@ -0,0 +1,200 @@
1
+ /** @jsxImportSource preact */
2
+ import type { JSX } from 'preact'
3
+ import { useRef } from 'preact/hooks'
4
+ import { scrubStep } from '@/canvas/client/inspector/values'
5
+
6
+ /**
7
+ * One drag of a number field's glyph. Each call takes the value as the field
8
+ * shows it. Only `commit` writes, so a drag posts one edit at most.
9
+ */
10
+ export interface ScrubSession {
11
+ readonly preview: (value: string) => void
12
+ readonly restore: () => void
13
+ readonly commit: (value: string) => void
14
+ }
15
+
16
+ /**
17
+ * Opens a session at press, so what `restore` returns to is read before any
18
+ * preview, however often the panel renders during the drag.
19
+ */
20
+ export interface Scrub {
21
+ readonly begin: () => ScrubSession
22
+ /** Holds a scrubbed number inside what the property accepts. */
23
+ readonly clamp: (value: number) => number
24
+ }
25
+
26
+ interface FieldProps {
27
+ /** The full word, which names the input for assistive technology. */
28
+ readonly label: string
29
+ /** The short mark drawn inside the field, hidden from assistive technology. */
30
+ readonly glyph: string
31
+ readonly initial: string
32
+ readonly isBusy: boolean
33
+ readonly isWide?: boolean
34
+ readonly placeholder?: string
35
+ readonly onCommit: (value: string) => void
36
+ /** Makes the glyph a drag handle. Typing stays the keyboard path. */
37
+ readonly scrub?: Scrub
38
+ }
39
+
40
+ const NUMBER = /^-?\d*\.?\d+$/
41
+
42
+ interface Drag {
43
+ readonly pointerId: number
44
+ /** What the input held at press, which a cancel puts back. */
45
+ readonly typed: string
46
+ /** The number the drag started from, an empty field's placeholder included. */
47
+ readonly start: string
48
+ readonly session: ScrubSession
49
+ lastX: number
50
+ value: number
51
+ }
52
+
53
+ function fieldClass(isWide: boolean | undefined, modifier?: string): string {
54
+ return ['glyph-field', isWide ? 'is-wide' : '', modifier ?? '']
55
+ .filter(Boolean)
56
+ .join(' ')
57
+ }
58
+
59
+ /**
60
+ * Commits on change, which a text input fires on Enter or on leaving it, and
61
+ * sends nothing when the value is what it started at. Escape puts it back.
62
+ */
63
+ export function Field({
64
+ label,
65
+ glyph,
66
+ initial,
67
+ isBusy,
68
+ isWide,
69
+ placeholder,
70
+ onCommit,
71
+ scrub,
72
+ }: FieldProps): JSX.Element {
73
+ const input = useRef<HTMLInputElement>(null)
74
+ const handle = useRef<HTMLSpanElement>(null)
75
+ const drag = useRef<Drag | null>(null)
76
+
77
+ const end = (pointerId: number): Drag | undefined => {
78
+ const held = drag.current
79
+ if (!held || held.pointerId !== pointerId) return undefined
80
+ drag.current = null
81
+ return held
82
+ }
83
+
84
+ /* An empty field scrubs from the number its placeholder shows. */
85
+ const scrubFrom = (value: string): string =>
86
+ value.trim() || placeholder?.trim() || ''
87
+ const isScrubbable = scrub !== undefined && NUMBER.test(scrubFrom(initial))
88
+
89
+ const handlers = isScrubbable
90
+ ? {
91
+ onPointerDown: (event: PointerEvent) => {
92
+ const typed = input.current?.value ?? ''
93
+ const start = scrubFrom(typed)
94
+ if (isBusy || !NUMBER.test(start)) return
95
+ event.preventDefault()
96
+ /*
97
+ * Capture keeps the release coming here when the pointer leaves the
98
+ * glyph, the panel, or crosses into a frame's own document.
99
+ */
100
+ try {
101
+ handle.current?.setPointerCapture(event.pointerId)
102
+ } catch {
103
+ /* A pointer the browser no longer tracks ends on cancel instead. */
104
+ }
105
+ drag.current = {
106
+ pointerId: event.pointerId,
107
+ typed,
108
+ start,
109
+ session: scrub.begin(),
110
+ lastX: event.clientX,
111
+ value: Number(start),
112
+ }
113
+ },
114
+ onPointerMove: (event: PointerEvent) => {
115
+ const held = drag.current
116
+ if (!held || held.pointerId !== event.pointerId) return
117
+ const moved = event.clientX - held.lastX
118
+ held.lastX = event.clientX
119
+ held.value = scrub.clamp(
120
+ Math.round((held.value + moved * scrubStep(event.shiftKey)) * 10) /
121
+ 10,
122
+ )
123
+ const shown = String(held.value)
124
+ if (input.current) input.current.value = shown
125
+ held.session.preview(shown)
126
+ },
127
+ onPointerUp: (event: PointerEvent) => {
128
+ const held = end(event.pointerId)
129
+ if (!held) return
130
+ const shown = String(held.value)
131
+ if (shown === held.start) held.session.restore()
132
+ else held.session.commit(shown)
133
+ },
134
+ onPointerCancel: (event: PointerEvent) => {
135
+ const held = end(event.pointerId)
136
+ if (!held) return
137
+ if (input.current) input.current.value = held.typed
138
+ held.session.restore()
139
+ },
140
+ }
141
+ : {}
142
+
143
+ return (
144
+ <div class={fieldClass(isWide)}>
145
+ <span
146
+ ref={handle}
147
+ class={isScrubbable ? 'glyph is-scrub' : 'glyph'}
148
+ aria-hidden="true"
149
+ title={label}
150
+ {...handlers}
151
+ >
152
+ {glyph}
153
+ </span>
154
+ <input
155
+ ref={input}
156
+ class="glyph-field-input"
157
+ type="text"
158
+ aria-label={label}
159
+ title={initial}
160
+ defaultValue={initial}
161
+ placeholder={placeholder}
162
+ disabled={isBusy}
163
+ spellcheck={false}
164
+ onChange={(event) => {
165
+ const value = event.currentTarget.value.trim()
166
+ if (value !== initial.trim()) onCommit(value)
167
+ }}
168
+ onKeyDown={(event) => {
169
+ if (event.key === 'Escape') event.currentTarget.value = initial
170
+ }}
171
+ />
172
+ </div>
173
+ )
174
+ }
175
+
176
+ interface ReadOnlyFieldProps {
177
+ readonly label: string
178
+ readonly glyph: string
179
+ readonly value: string
180
+ readonly isWide?: boolean
181
+ }
182
+
183
+ /** A value the panel shows and nothing here writes, drawn like a field. */
184
+ export function ReadOnlyField({
185
+ label,
186
+ glyph,
187
+ value,
188
+ isWide,
189
+ }: ReadOnlyFieldProps): JSX.Element {
190
+ return (
191
+ <div class={fieldClass(isWide, 'is-readonly')}>
192
+ <span class="glyph" aria-hidden="true" title={label}>
193
+ {glyph}
194
+ </span>
195
+ <output class="glyph-field-input" aria-label={label} title={value}>
196
+ {value}
197
+ </output>
198
+ </div>
199
+ )
200
+ }