@erclx/canon 5.33.0 → 5.35.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 (190) hide show
  1. package/README.md +3 -5
  2. package/claude/.claude-plugin/plugin.json +1 -1
  3. package/claude/skills/api-design/REQUIREMENT.md +2 -3
  4. package/claude/skills/api-design/SKILL.md +2 -3
  5. package/claude/skills/api-design/references/adopted.md +1 -1
  6. package/claude/skills/auto-ship/references/draft-mark.md +1 -1
  7. package/claude/skills/canon-cli/SKILL.md +10 -11
  8. package/claude/skills/canon-feedback/REQUIREMENT.md +1 -0
  9. package/claude/skills/canon-feedback/SKILL.md +10 -28
  10. package/claude/skills/canon-feedback/references/template.md +53 -0
  11. package/claude/skills/canvas-board/SKILL.md +4 -1
  12. package/claude/skills/canvas-board/references/canvas.md +26 -3
  13. package/claude/skills/code-craft/REQUIREMENT.md +1 -1
  14. package/claude/skills/code-craft/SKILL.md +0 -1
  15. package/claude/skills/create-rule/SKILL.md +1 -1
  16. package/claude/skills/create-rule/references/rule.md +3 -1
  17. package/claude/skills/create-skill/SKILL.md +2 -1
  18. package/claude/skills/create-skill/references/skill-paths.md +12 -15
  19. package/{standards/standard.md → claude/skills/create-skill/references/skill-reference.md} +50 -45
  20. package/claude/skills/create-skill/references/skill.md +10 -4
  21. package/claude/skills/draft-and-pick/SKILL.md +1 -1
  22. package/claude/skills/git-commit/references/versioning.md +13 -13
  23. package/claude/skills/git-issue/references/issue.md +4 -1
  24. package/claude/skills/git-pr/SKILL.md +1 -1
  25. package/claude/skills/git-pr/references/publish.md +1 -1
  26. package/claude/skills/review-address/REQUIREMENT.md +1 -1
  27. package/claude/skills/review-address/SKILL.md +2 -2
  28. package/claude/skills/review-craft/references/ui.md +1 -1
  29. package/claude/skills/review-pr/SKILL.md +3 -3
  30. package/claude/skills/review-pr/references/post.md +1 -1
  31. package/claude/skills/review-ui/REQUIREMENT.md +1 -1
  32. package/claude/skills/review-ui/SKILL.md +8 -6
  33. package/claude/skills/review-ui/references/driving.md +8 -8
  34. package/claude/skills/review-ui/references/post.md +52 -16
  35. package/claude/skills/role-orchestrator/REQUIREMENT.md +2 -2
  36. package/claude/skills/role-orchestrator/SKILL.md +11 -12
  37. package/claude/skills/role-orchestrator/references/orchestrator-cloud-launch.md +3 -1
  38. package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +11 -8
  39. package/claude/skills/role-orchestrator/references/orchestrator-handback.md +10 -4
  40. package/claude/skills/role-orchestrator/references/orchestrator-launch.md +11 -2
  41. package/claude/skills/role-orchestrator/references/orchestrator-poll.md +11 -12
  42. package/claude/skills/role-orchestrator/references/orchestrator-review-dispatch.md +12 -15
  43. package/claude/skills/role-orchestrator/references/orchestrator-reviewer-launch.md +6 -4
  44. package/claude/skills/role-orchestrator/scripts/poll.ts +50 -1
  45. package/claude/skills/role-reviewer/REQUIREMENT.md +17 -7
  46. package/claude/skills/role-reviewer/SKILL.md +17 -10
  47. package/claude/skills/role-worker/REQUIREMENT.md +1 -1
  48. package/claude/skills/role-worker/SKILL.md +6 -6
  49. package/claude/skills/session-compact/references/handoff-note.md +1 -1
  50. package/claude/skills/target-setup/SKILL.md +12 -6
  51. package/claude/skills/target-setup/references/detect.md +22 -5
  52. package/claude/skills/test-craft/REQUIREMENT.md +2 -3
  53. package/claude/skills/test-craft/SKILL.md +15 -3
  54. package/docs/agents/audits.md +1 -1
  55. package/docs/agents/canvas.md +5 -1
  56. package/docs/agents/commands.md +3 -5
  57. package/docs/agents/counts.md +5 -6
  58. package/docs/agents/drift-surfaces.md +15 -19
  59. package/docs/agents/gov-detect.md +47 -0
  60. package/docs/agents/index.md +6 -4
  61. package/docs/agents/install-and-sync.md +11 -31
  62. package/docs/agents/markdown-audit.md +10 -12
  63. package/docs/agents/migrate.md +1 -1
  64. package/docs/agents/pr-evidence-layout.md +70 -0
  65. package/docs/agents/pr-evidence.md +8 -9
  66. package/docs/agents/pr-frames.md +7 -2
  67. package/docs/agents/pr-lift.md +132 -0
  68. package/docs/agents/records-stale.md +1 -1
  69. package/docs/agents/records-validate.md +11 -23
  70. package/docs/agents/records.md +1 -1
  71. package/docs/agents/routing.md +1 -1
  72. package/docs/agents/rule-citations.md +11 -14
  73. package/docs/agents/scripting.md +4 -21
  74. package/docs/agents/state-scoped-risk.md +1 -1
  75. package/docs/agents/tasks-validate.md +1 -1
  76. package/docs/agents/teach.md +1 -1
  77. package/docs/target/index.md +1 -0
  78. package/docs/target/migrations.md +4 -0
  79. package/docs/target/projects.md +11 -11
  80. package/docs/target/scenarios.md +5 -6
  81. package/docs/target/standards-moved.md +32 -0
  82. package/docs/target/sync.md +5 -5
  83. package/docs/workflow/operating-model.md +16 -18
  84. package/docs/workflow/skill-map.md +1 -1
  85. package/governance/detect.toml +197 -0
  86. package/governance/renames.toml +0 -1
  87. package/governance/rules/code/000-code.md +1 -13
  88. package/governance/rules/code/010-testing.md +2 -30
  89. package/governance/rules/ui/400-ui.md +1 -1
  90. package/governance/rules/ui/480-canvas.md +13 -0
  91. package/governance/stacks/base.toml +15 -8
  92. package/governance/stacks/go.toml +2 -0
  93. package/governance/stacks/node.toml +1 -1
  94. package/governance/stacks/php.toml +2 -0
  95. package/governance/stacks/python.toml +1 -1
  96. package/package.json +1 -4
  97. package/scripts/core/install-check.sh +2 -3
  98. package/scripts/core/regen-hero.ts +0 -6
  99. package/scripts/lib/ui.sh +0 -9
  100. package/src/audits/catalog.ts +0 -9
  101. package/src/canvas/arrange.ts +66 -0
  102. package/src/canvas/capture.ts +20 -6
  103. package/src/canvas/content.ts +39 -0
  104. package/src/canvas/fit.ts +238 -0
  105. package/src/canvas/tokens.ts +28 -10
  106. package/src/capture/frame-values.ts +2 -32
  107. package/src/claude/routing.ts +1 -1
  108. package/src/claude/skills-reach.ts +4 -6
  109. package/src/cli/help.ts +3 -4
  110. package/src/cli.ts +0 -2
  111. package/src/commands/canvas.ts +179 -1
  112. package/src/commands/gov.ts +149 -10
  113. package/src/commands/init.ts +1 -1
  114. package/src/commands/labels.ts +4 -44
  115. package/src/commands/migrate.ts +1 -1
  116. package/src/commands/pr.ts +376 -1
  117. package/src/commands/records.ts +4 -20
  118. package/src/commands/tooling.ts +2 -4
  119. package/src/counts/catalogs.ts +0 -6
  120. package/src/feedback/body.ts +26 -5
  121. package/src/gate/boundaries.ts +2 -2
  122. package/src/gate/measures.ts +64 -72
  123. package/src/gate/sequencer.ts +51 -2
  124. package/src/gate/stages.ts +3 -22
  125. package/src/gov/citations.ts +11 -43
  126. package/src/gov/detect.ts +301 -0
  127. package/src/gov/list.ts +23 -2
  128. package/src/gov/stacks.ts +9 -0
  129. package/src/markdown/bans.ts +1 -1
  130. package/src/migrate/scratch-evidence.ts +2 -2
  131. package/src/pr/evidence.ts +218 -45
  132. package/src/pr/frames.ts +23 -3
  133. package/src/pr/lift.ts +418 -0
  134. package/src/pr/rest.ts +3 -0
  135. package/src/pr/review-scope.ts +76 -0
  136. package/src/records/validate.ts +6 -194
  137. package/src/sandbox/equivalence.ts +14 -7
  138. package/src/sandbox/provision.ts +0 -5
  139. package/src/secrets/shipped.ts +1 -2
  140. package/src/serve/static.ts +1 -2
  141. package/src/shipped/references.ts +29 -80
  142. package/src/sync/check.ts +18 -14
  143. package/src/tooling/inject.ts +6 -37
  144. package/src/tooling/read.ts +2 -3
  145. package/src/tooling/scan.ts +4 -17
  146. package/src/tooling/subfolder.ts +2 -66
  147. package/tooling/base/configs/scripts/verify.json +1 -1
  148. package/tooling/base/configs/scripts/verify.ts +1 -12
  149. package/tooling/base/manifest.toml +1 -2
  150. package/tooling/base/reference.md +10 -16
  151. package/tooling/base/seeds/.github/workflows/verify.yml +0 -3
  152. package/tooling/base/seeds/.lintstagedrc +1 -2
  153. package/tooling/base/seeds/canon/context/ci.md +0 -1
  154. package/tooling/base/seeds/canon/context/development.md +1 -1
  155. package/tooling/claude/reference.md +1 -1
  156. package/tooling/claude/seeds/.claude/hooks/review-post-scan.sh +13 -1
  157. package/tooling/go/reference.md +1 -1
  158. package/tooling/php/reference.md +1 -1
  159. package/tooling/python/reference.md +2 -2
  160. package/tooling/web/configs/.github/workflows/verify.yml +0 -3
  161. package/tooling/web/manifest.toml +0 -1
  162. package/tooling/web/reference.md +2 -4
  163. package/docs/agents/standards-audit.md +0 -40
  164. package/governance/rules/tooling/765-spelling.md +0 -19
  165. package/governance/stacks/astro.toml +0 -2
  166. package/governance/stacks/nextjs.toml +0 -2
  167. package/governance/stacks/node-server.toml +0 -2
  168. package/governance/stacks/python-fastapi.toml +0 -2
  169. package/governance/stacks/react.toml +0 -2
  170. package/src/commands/standards.ts +0 -278
  171. package/src/labels/spelling.ts +0 -120
  172. package/src/standards/audit.ts +0 -146
  173. package/src/standards/list.ts +0 -33
  174. package/src/standards/read.ts +0 -177
  175. package/src/standards/scope.ts +0 -56
  176. package/standards/index.md +0 -10
  177. package/tooling/base/configs/.vscode/extensions.json +0 -9
  178. package/tooling/base/configs/.vscode/settings.json +0 -3
  179. package/tooling/base/seeds/.cspell/project-terms.txt +0 -0
  180. package/tooling/base/seeds/.cspell/tech-stack.txt +0 -25
  181. package/tooling/base/seeds/cspell.json +0 -20
  182. package/tooling/go/seeds/.cspell/tech-stack.txt +0 -16
  183. package/tooling/nextjs/seeds/.cspell/tech-stack.txt +0 -3
  184. package/tooling/php/seeds/.cspell/tech-stack.txt +0 -5
  185. package/tooling/python/seeds/.cspell/tech-stack.txt +0 -18
  186. package/tooling/vite-react/seeds/.cspell/project-terms.txt +0 -1
  187. package/tooling/vite-react/seeds/.cspell/tech-stack.txt +0 -2
  188. package/tooling/web/configs/.vscode/extensions.json +0 -13
  189. package/tooling/web/configs/.vscode/settings.json +0 -10
  190. package/tooling/web/seeds/.cspell/tech-stack.txt +0 -21
@@ -50,23 +50,19 @@ seed tree rather than from a fixed set of filenames, so a folder added later is
50
50
  covered without a code change. Only an exact stem matches, which leaves a
51
51
  suffixed variant such as `TASKS-ARCHIVE.md` unreported.
52
52
 
53
- No skill splits the file. The standard governing `replacedBy` is the one the
54
- `appliesTo` the standards catalog declares, and `canon standards <name>` reads
55
- its destination shape, so the split is the project's own to make.
53
+ No skill splits the file. The destination's shape lives in the skill that owns
54
+ it, so the split is the project's own to make.
56
55
 
57
56
  `unmigrated` names a domain sitting at the root layout an older toolkit installed
58
57
  to, with nothing at the path the current one reads. It carries `rootPath`,
59
- `installPath`, and a file count. Without it a project holding `standards/` at its
58
+ `installPath`, and a file count. Without it a project holding an older layout at its
60
59
  root reports zero entries for that domain and reads as clean, which is the most
61
60
  misleading state the report can produce.
62
61
 
63
- The field currently names no domain. Standards and snippets are the two the
64
- toolkit ever installed at a project root. Standards closed its install channel
65
- and snippets retired outright, so a project still holding a root `standards/`
66
- folder is carrying its own authoring surface rather than an unfinished install.
67
- Nothing proposes moving it, and no command relocates the content. Move it
68
- yourself. A leftover `snippets/` folder reports under the reverse walk instead,
69
- where `docs/target/sync.md` says what to do with it.
62
+ The field currently names no domain. Snippets was the one the toolkit ever
63
+ installed at a project root, and it retired outright. A leftover `snippets/`
64
+ folder reports under the reverse walk instead, where `docs/target/sync.md` says
65
+ what to do with it.
70
66
 
71
67
  `unmovedSurfaces` names a tracked surface the target still holds at its `.claude/`
72
68
  spelling with nothing at the `canon/` one, such as `.claude/REQUIREMENTS.md` or
@@ -123,14 +119,14 @@ that is not a full clone as unmeasured rather than clean. This gap does not
123
119
  reach the chain-based read above, since it consults no anchor at all.
124
120
 
125
121
  In the fallback, entitlement is filtered, because a stack does not receive
126
- every rule. The base stack takes four folders whole, the code stacks add `code`, and
127
- every other stack extends them with individually named rules, so an unfiltered
128
- list would tell a base consumer about rules it can never receive and train the
129
- reader to skip the section.
130
-
131
- The filter accepts a band on either of two grounds. A folder the base stack takes
132
- whole is entitled to every target, read from the stack file so a folder added to
133
- base later needs no code change. Every other band is read off the folders the
122
+ every rule. The base stack names ten rules, the language stacks add `code` and
123
+ their own language and testing rules, and every other rule is opt-in, so an
124
+ unfiltered list would tell a base consumer about rules it can never receive and
125
+ train the reader to skip the section.
126
+
127
+ The filter accepts a band on either of two grounds. A folder holding a rule the
128
+ base stack resolves to is entitled to every target, read through the stack file so
129
+ a rule added to base later needs no code change. Every other band is read off the folders the
134
130
  target already carries, which is the fallback's only evidence of what a target
135
131
  was entitled to before it recorded a chain. One band can be reached by more than
136
132
  one stack, so the test over-reports inside a folder the target holds, which
@@ -0,0 +1,47 @@
1
+ ---
2
+ title: Rule detection
3
+ description: Proposing opt-in governance rules from a target's dependencies and paths, the evidence file the verb reads, the record it emits, and its exit codes
4
+ ---
5
+
6
+ # Rule detection
7
+
8
+ `canon gov detect` proposes the opt-in governance rules a target's evidence points at, and writes nothing. A rule no stack names reaches a project only through this verb, which `canon:target-setup` calls before it previews an install.
9
+
10
+ ```bash
11
+ canon gov detect
12
+ canon gov detect ../my-app --stack node --json
13
+ ```
14
+
15
+ | Option | Behavior |
16
+ | ----------------- | ------------------------------------------------ |
17
+ | `--stack <stack>` | Leave out every rule this stack already resolves |
18
+ | `--json` | Emit the record on stdout instead of the frame |
19
+
20
+ ## Evidence
21
+
22
+ Each opt-in rule keeps a table in the toolkit's `governance/detect.toml`, keyed by rule basename. Its `deps` are package names, read from `package.json` (`dependencies` and `devDependencies`), `pyproject.toml`, `go.mod`, and `composer.json`. Its `files` are globs matched against the target's git-listed paths and against each folder on those paths, so `.github/workflows` matches a folder holding a workflow. A glob with no wildcard is also tested on disk, which is how `.canon/canvas` matches in a target that gitignores `.canon/`.
23
+
24
+ Both reads stop at depth two, which reaches a service folder and a workspace package under `packages/<name>/`. Outside git the verb walks the directory to the same depth and skips `.git`, `node_modules`, `vendor`, and `.venv`. A manifest that does not parse contributes nothing.
25
+
26
+ ## Record
27
+
28
+ ```json
29
+ {
30
+ "target": "/abs/path/my-app",
31
+ "stack": "node",
32
+ "proposals": [
33
+ {
34
+ "rule": "200-react",
35
+ "evidence": [{ "kind": "dep", "match": "react", "path": "package.json" }]
36
+ }
37
+ ]
38
+ }
39
+ ```
40
+
41
+ `kind` is `dep` for a dependency, with `path` naming the manifest that declares it, or `file` for a glob, with `path` naming what it matched. `stack` is null when no `--stack` was passed.
42
+
43
+ ## Exit codes
44
+
45
+ The verb exits 0 on every readable target, including one with no evidence, which returns an empty `proposals` array. A target that does not exist and a `--stack` that does not resolve each exit 1 with the reason on stderr.
46
+
47
+ Install what the operator kept with `canon gov install --add <rules> <target>`, which needs no stack.
@@ -9,7 +9,7 @@ category: Agent surface
9
9
  CLI catalog and invocation rules for agents, split by command domain. Start with overview.
10
10
 
11
11
  - [Audits](audits.md): Running every health check as one set, what the single verdict means, the exit code each outcome takes, the retained baseline and the delta it reports, and which corpora are kept out of the record
12
- - [Canvas](canvas.md): The canvas server, its content format, and the verbs that list, arrange, select, edit, mark, and capture frames
12
+ - [Canvas](canvas.md): The canvas server, its content format, and the verbs that list, arrange, fit, select, edit, mark, and capture frames
13
13
  - [Capture](capture.md): Rendering HTML sources to PNG, what the command asserts about fonts, and why the selector has no default
14
14
  - [Census](census.md): Tracked-plus-untracked file count, a breakdown by extension, and a line total that skips whatever reads as binary
15
15
  - [Command catalog](commands.md): Every top-level command with its purpose, each domain's subcommands and the page describing them, the browser commands, and the version skew report
@@ -23,6 +23,7 @@ CLI catalog and invocation rules for agents, split by command domain. Start with
23
23
  - [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
24
24
  - [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
25
25
  - [Merge gate](gate.md): Running the gate this repository verifies a branch with, what the stage table holds and what stays a script, how the changed set scopes three stages, and why a stage that cannot read its input reports rather than passing
26
+ - [Rule detection](gov-detect.md): Proposing opt-in governance rules from a target's dependencies and paths, the evidence file the verb reads, the record it emits, and its exit codes
26
27
  - [Hooks](hooks.md): Running the post-merge steps through one verb, the order and the child call behind each, the root and working directory each step reads, the skip switches, and the line each record maps to
27
28
  - [Indexes](indexes.md): Flags, exit codes, and JSON shape for canon indexes regen and canon indexes list, plus when regen auto-stages what it rewrote
28
29
  - [Install and sync](install-and-sync.md): What each install and sync verb writes, refuses, or leaves alone, and how drift is attributed in a target project
@@ -34,19 +35,21 @@ CLI catalog and invocation rules for agents, split by command domain. Start with
34
35
  - [Migrate](migrate.md): What each canon migrate verb moves and rewrites, what it refuses, the markers that keep an old spelling, and the idempotence check a second run gives
35
36
  - [Output shape](output-shape.md): Two framed shapes every command renders into, how JSON and --names modes keep stdout clean, and the exit discipline that lets piped output drain
36
37
  - [Overview](overview.md): What this folder covers, the invocation rules every command inherits, and where domain behavior is documented instead
38
+ - [The pull request evidence layout](pr-evidence-layout.md): How canon pr evidence groups screenshots into one block per view with a row per width and a column per theme, when a base column appears, where a name outside the form lands, how the commit line links, and when the blocks open
37
39
  - [The pull request evidence comparison](pr-evidence.md): What canon pr evidence compares, the marker that lets it edit its own comment rather than duplicate it, the refusal reasons it names, what the --check read reports as owed, and why the comparison anchors at the merge base rather than the previous push
38
40
  - [The pull request frame store](pr-frames.md): How canon pr frames pushes a UI review frame to a never-merged branch through the GitHub API and returns a branch-path link, which ref it writes, the pass segment that keeps an earlier image fixed, the refusal reasons it names, and how --drop and --prune rewrite the branch so a dropped frame leaves history
41
+ - [The pull request draft lift](pr-lift.md): How canon pr lift removes the draft mark once the reviews, evidence, standing, and CI all read clear at the head, the gate order and what each reads, the wait on a pending run, the re-read before the write, the refusal reasons it names, and what --check reports
39
42
  - [The pull request local preview](pr-local.md): How canon pr local finds the server a worktree is running, why a server in another worktree is never reported, the refusal reasons it names, and how a close workflow drops the address line with --remove
40
43
  - [The pull request preview deploy](pr-preview.md): How canon pr preview finds a fenced Cloudflare Pages deploy, refuses a branch its push filter does not serve, dispatches it on a pull request's branch, reads the alias the run prints, the refusal reasons it names, and why an unfenced deploy is refused before anything runs
41
44
  - [Head-sensitive pull request reads](pr-reads.md): Resolving a branch tip from the remote rather than from the pull request object, reading what a review pass covered off its own marker rather than off GitHub's submission stamps, why an empty run list is not a pass, the refusal reasons each verb names, and what the remote read costs
42
45
  - [The pull request checklist tick](pr-tick.md): How canon pr tick marks the checklist boxes a UI pass drove, the stamp that names the commit each passed at, why a render at a newer head clears a tick, the refusal reasons it names, and why a taste box is never ticked
43
46
  - [Records push](records-push.md): Backing the record folders to a private remote and restoring them, which folders the payload carries, the one-time setup, the refusal reasons, and the hook that runs the push
44
47
  - [Records stale](records-stale.md): Counting the releases since the requirements record was last reviewed, where the review date is read from, and the exit codes and refusals
45
- - [Records validate](records-validate.md): Validating the session records and the standards corpus, the per-kind checks, which root each kind defaults to, the exit codes, and the refusal reasons
48
+ - [Records validate](records-validate.md): Validating the session records, the per-kind checks, which root they default to, the exit codes, and the refusal reasons
46
49
  - [Records](records.md): The two roots a record folder resolves at, claiming the ordinal intake and groundwork share, reading each folder's size and growth, pruning stale scratch, and where validation, the canonical review count, and the private-remote backup are described
47
50
  - [Restated instructions](restated.md): Counting the instructions the always-loaded file and every path-scoped rule share with the seed, the skill bodies, and each other, how a match is decided, the three classes, which surface a later edit starts from, and why the sweep reports rather than gates
48
51
  - [Routing report](routing.md): Reading per CLAUDE.md section how many bullets name a path, what counts as naming one, when a rule counts as covering it, the two refusals, and why the verb reports rather than gates
49
- - [Rule citations](rule-citations.md): Resolving every path a governance rule cites and every frontmatter glob the internal corpus declares, the three forms a citation is written in, the shapes that look like citations and are not, the two classes where an absent path is correct, why the glob half reads one corpus, and why this one gates
52
+ - [Rule citations](rule-citations.md): Resolving every path a governance rule cites and every frontmatter glob the internal corpus declares, the two forms a citation is written in, the shapes that look like citations and are not, the two classes where an absent path is correct, why the glob half reads one corpus, and why this one gates
50
53
  - [Sandbox](sandbox.md): Scenario routing, the expectation scoring surface, and the coverage census over scenarios and skills
51
54
  - [Scripting](scripting.md): The runtime catalogs that replace hardcoded names, what each carries, and a headless invocation per domain
52
55
  - [Sessions transfer](sessions-transfer.md): Moving a session to another machine, the export bundle and its manifest, placing it with import, the refusal reasons, and what a resume on the target depends on
@@ -54,7 +57,6 @@ CLI catalog and invocation rules for agents, split by command domain. Start with
54
57
  - [Skill audit](skills-audit.md): Measuring both skill corpora against the skill standards, the checks it reads, the requirement gate that is the only failing one, and the drift verb that names bodies rewritten since a ref
55
58
  - [Citation reach](skills-reach.md): Reporting the skill bodies that cite a path no target project receives, which corpus the verb reads, the ownership key that decides what counts, the one-word qualifier that marks a citation as decided, and why the verb reports instead of gating
56
59
  - [Slides](slides.md): Where a deck folder lives and what it holds, what it declares for the whole deck in deck.json, the faces it embeds, what each slide declares on its own body, transitions and entrances, the master built from the project's tokens, speaker notes, charts, and what each refusal reports
57
- - [Standard success criteria](standards-audit.md): Reading the corpus against the success-criterion gate, the two headings it accepts, why the check scopes to arrival rather than the whole corpus, and the exit codes it sets
58
60
  - [State-scoped risk](state-scoped-risk.md): Reading committed state rather than an arriving change, the shipped-tree corpus the secret scan reads, what it keys on and how a deliberate value is exempted, the advisory check and its network failure mode, and why one gates while the other reports
59
61
  - [Superseded values](superseded.md): Reading where the tree still asserts a value a changed convention no longer produces, why the sweep keys on the value rather than the file, the family stem behind a templated citation, the exemption marker, the blind spots it cannot reach, and why it reports rather than gates
60
62
  - [Targets](targets.md): The projects this toolkit installed into, the record the install writes against the sweep that backs it, what bounds each answer, and the cross-target pull request read
@@ -27,15 +27,15 @@ carried, and the sync says so. The sync lists each change by path before it
27
27
  applies, and the target's git diff is the record afterwards. `canon sync
28
28
  --check` lists the same file as `retired` or `renamed` before any sync runs.
29
29
 
30
- A rule that only routed a path to a standard ships under that standard's name,
31
- but the tasks, plan, context, and skill rules are hand-written at `canon/` and
32
- `claude/skill.md`. A held copy of each syncs in place, and the groundwork,
33
- intake, session, design, readme, and rule copies are deleted as retired. The
34
- markdown rule is hand-written at `writing/markdown.md`, and a target holding
35
- `standards/markdown.md` keeps it there as `800-prose` retires.
30
+ A rule that only routed a path to a standard now names the skill that owns
31
+ the guidance. The tasks, plan, context, and skill rules are hand-written at
32
+ `canon/` and `claude/skill.md`. A held copy of each syncs in place, and the
33
+ groundwork, intake, session, design, readme, and rule copies are deleted as
34
+ retired. The markdown rule is hand-written at `writing/markdown.md` and names
35
+ the `markdown-craft` skill, and `800-prose` retires.
36
36
 
37
37
  A project's own rules belong under `.claude/rules/project/`, where
38
- `canon standards rule` reserves `900-999` for them, and a rule placed under
38
+ the `create-rule` skill's reference reserves `900-999` for them, and a rule placed under
39
39
  `canon/` instead is lost on the next sync.
40
40
 
41
41
  A sync never deletes a rule that a newer canon release installed. The stamp
@@ -86,12 +86,6 @@ from an anchor a later sync could advance past the rule's own commit. A
86
86
  target whose install predates the recorded chain falls back to the same
87
87
  band-inference `newRules` uses in `canon sync --check`.
88
88
 
89
- There is no `canon standards sync` and no `canon standards install`. The corpus
90
- installs into no project, so the domain has nothing in a target to reconcile.
91
- `canon standards <name>` prints one, resolving `standards/` at the working root
92
- and then the corpus inside the package, and `canon standards list --json` carries
93
- the catalog, each entry labeled by the root it was read from.
94
-
95
89
  ## Tooling diff and sync
96
90
 
97
91
  `canon tooling diff <stack> [target]` is the read-only comparison, and a session asking what differs reaches for it by default. It writes nothing and exits 1 when anything differs and 0 when nothing does, so it gates CI. `--json` adds the scan record on stdout with `ok: true`, and the frame goes to stderr. A refusal such as an unknown stack, an excluded stack, or a toolkit-root target still exits 1 and answers `{ ok: false, reason, message }`, so a caller branches on the record and reads a typo'd stack name as a refusal rather than as drift.
@@ -112,6 +106,10 @@ Every documented agent path already passes the argument, including
112
106
  `canon init`. The confirm-then-apply prompt after it still resolves to `Yes`
113
107
  headlessly, so a call that names its stack is unchanged.
114
108
 
109
+ `--add` with no stack installs the named rules into the one positional, or the
110
+ current directory, and keeps the recorded chain. Pass `./node` to force a target
111
+ named like a stack. [Rule detection](gov-detect.md) proposes the names.
112
+
115
113
  `canon gov install` also refuses the toolkit root as a target. It resolves the
116
114
  target before anything else, so a path that does not exist fails rather than
117
115
  being scaffolded.
@@ -125,23 +123,6 @@ Claude Code loads both copies of an edited rule at session start. Run `canon
125
123
  migrate rule-layout` first to move a target off the flat layout, which reports
126
124
  what it would move and applies it under `--write`.
127
125
 
128
- ## Standards resolution
129
-
130
- `canon standards <name>` writes the document to stdout and the root it answered
131
- from to stderr, so a caller capturing with `$(...)` receives the document alone.
132
- A name resolves with or without its `.md` extension, and one that matches no
133
- standard exits 1 after listing the catalog on stderr.
134
-
135
- Two roots answer, in order: `standards/` at the working directory, then the
136
- corpus inside the package. A project that authors standards of its own uses the
137
- first, and this repository's own authoring root is the same path. `.claude/standards/`
138
- is not among them, and no repository writes one any more. A copy an older
139
- toolkit installed into a target resolves nothing.
140
-
141
- There is no citation closure to compute, since nothing is copied. A standard
142
- that hands a concern to a sibling names it in `Does not govern:` and a reader
143
- runs the verb again for that name.
144
-
145
126
  ## Governance regen
146
127
 
147
128
  `canon gov regen` is the one governance verb that runs against the toolkit root,
@@ -254,7 +235,6 @@ still prompts. `--skip` takes `wiki`, `governance`, and `records`, and warns
254
235
  without aborting on any other value. Records has nothing to run non-interactively,
255
236
  since the private backup repository does not exist yet at scaffold time, so its
256
237
  step only prints the one-time setup reminder and `--skip records` silences it.
257
- There is no `--standards`, since no run writes that corpus into the target.
258
238
 
259
239
  ## Unguarded tooling primitives
260
240
 
@@ -5,14 +5,14 @@ description: Running the audit over any markdown path, where its bans and checkp
5
5
 
6
6
  # Markdown audit
7
7
 
8
- `canon markdown audit [path...]` reports any markdown file against the attribute standard `markdown.md`. An attribute standard governs a file rather than a folder, so this resolves no folder and requires no `index.md`, which is what puts `.claude/rules/` and `governance/` in reach. Folder-shaped findings stay in `canon context audit`, described in `context-audit.md`.
8
+ `canon markdown audit [path...]` reports any markdown file against the `markdown-craft` skill's `markdown.md` reference. That reference governs a file rather than a folder, so this resolves no folder and requires no `index.md`, which is what puts `.claude/rules/` and `governance/` in reach. Folder-shaped findings stay in `canon context audit`, described in `context-audit.md`.
9
9
 
10
10
  ```bash
11
11
  canon markdown audit
12
12
  canon markdown audit --json
13
13
  canon markdown audit .claude/rules governance
14
14
  canon markdown audit docs/agents/commands.md
15
- canon markdown audit 'standards/**/*.md'
15
+ canon markdown audit 'docs/**/*.md'
16
16
  ```
17
17
 
18
18
  | Option | Behavior |
@@ -27,29 +27,27 @@ A bare run measures every markdown file git lists, tracked plus untracked-and-no
27
27
 
28
28
  ## Where the rules come from
29
29
 
30
- The ban set and all ten checkpoints ship with the `canon` package as data, in `src/markdown/bans.ts` and `src/markdown/structure.ts`. Every project is measured against the same set whether or not it installed any standards, and no file has to resolve for a run to mean something.
30
+ The ban set and all ten checkpoints ship with the `canon` package as data, in `src/markdown/bans.ts` and `src/markdown/structure.ts`. Every project is measured against the same set whether or not it installed the plugin, and no file has to resolve for a run to mean something.
31
31
 
32
32
  Seven of the ten are stated in `markdown.md` and the three cadence numbers are stated in the `write-human` skill. That split is the content boundary rather than an accident: `markdown.md` carries the enforced rules a scan can decide, and the skill carries the rhythm rules a ban list cannot express. A cadence number moved in the skill and left in the code drifts the same way, so move both.
33
33
 
34
- Reading them out of the standards per run was the original design. It put a parser contract on a document authored for people, and the standard had to carry a paragraph of its own warning an author that a one-word backticked example in a `- Do not use ` bullet would be lifted into a literal ban set and ban that word everywhere. A rule existing to protect a parser from the prose it parses is the argument for separating them.
34
+ Reading them out of the reference per run was the original design. It put a parser contract on a document authored for people, and the reference had to carry a paragraph of its own warning an author that a one-word backticked example in a `- Do not use ` bullet would be lifted into a literal ban set and ban that word everywhere. A rule existing to protect a parser from the prose it parses is the argument for separating them.
35
35
 
36
36
  `markdown.md` still states every ban and every checkpoint, and a reader follows it rather than the code. Nothing compares the two, so a number moved in one place and left in the other drifts silently. Move both in the same change.
37
37
 
38
- The set is closed rather than extensible, so a project cannot add a term by editing a file. It holds the two characters alone, since a character is the one class a literal match settles. The set once carried 13 words and 6 British spellings as well. The words moved to the `write-human` skill as guidance, because a word ban catches the token and misses the habit, and `just`, `allows`, and `very` carry honest uses no literal match separates. Spelling went to a project's spell checker, which already reads every word.
38
+ The set is closed rather than extensible, so a project cannot add a term by editing a file. It holds the two characters alone, since a character is the one class a literal match settles. The set once carried 13 words and 6 British spellings as well. The words moved to the `write-human` skill as guidance, because a word ban catches the token and misses the habit, and `just`, `allows`, and `very` carry honest uses no literal match separates. Spelling is left to the author, with American spelling carried as guidance in `markdown-craft`.
39
39
 
40
40
  A set shipped empty is reported rather than passed. It finds nothing and would exit clean, which reports a corpus nobody checked as a corpus carrying no violation, so the run names the empty set and exits `3`. The set ships with the package, so a defect in the build is the only cause left.
41
41
 
42
- `canon standards <name>` still resolves a standard at the authoring root, then the package corpus, and prints it, so the human catalog reads without a project copy on disk.
43
-
44
42
  ## What each check reports
45
43
 
46
44
  ### Bans
47
45
 
48
46
  One closed set reports a hit: the characters `markdown.md` bans under `## Punctuation`.
49
47
 
50
- Frontmatter, fenced blocks, inline code spans, and link destinations are excluded. Without the code-span exclusion each standard would report its own backticked examples, and without the link exclusion a semicolon in a query string would report as prose no rewrite can fix.
48
+ Frontmatter, fenced blocks, inline code spans, and link destinations are excluded. Without the code-span exclusion each reference would report its own backticked examples, and without the link exclusion a semicolon in a query string would report as prose no rewrite can fix.
51
49
 
52
- Word choice stays unmeasured and the report says so on every run. Single words travel in the `write-human` skill as guidance a reader applies. A multi-word ban escapes the harvest by width, whether it carries a placeholder standing in for the rest of the sentence or spells the phrase out in full, and every rule under `## Voice` is a judgment. The bans `## Language` states over what a sentence may claim are patterns of the multi-word kind by construction, since a literal match over a pattern reports the compliant text and reaches none of the violations. A report listing hits without naming those would read as a verdict on the whole standard.
50
+ Word choice stays unmeasured and the report says so on every run. Single words travel in the `write-human` skill as guidance a reader applies. A multi-word ban escapes the harvest by width, whether it carries a placeholder standing in for the rest of the sentence or spells the phrase out in full, and every rule under `## Voice` is a judgment. The bans `## Language` states over what a sentence may claim are patterns of the multi-word kind by construction, since a literal match over a pattern reports the compliant text and reaches none of the violations. A report listing hits without naming those would read as a verdict on the whole reference.
53
51
 
54
52
  ### Links
55
53
 
@@ -103,14 +101,14 @@ Both hooks answer an absent record as well. A completed run always writes the re
103
101
 
104
102
  A machine with neither runner still blocks no edit, and it says so rather than exiting clean. The push stage holds either way. An edit nobody checked and an edit carrying no violation are one silence to a reader, so the enforcement a machine lacks is reported rather than inferred.
105
103
 
106
- The stage measures the whole corpus rather than the changed files. A `Do not use` bullet added to a standard bans a token retroactively, and no file in the push that adds the bullet was edited.
104
+ The stage measures the whole corpus rather than the changed files. A `Do not use` bullet added to a reference bans a token retroactively, and no file in the push that adds the bullet was edited.
107
105
 
108
106
  ## What it does not cover
109
107
 
110
- The verb reads the two attribute standards and nothing else. The five standards declaring `appliesTo: ["*"]` also include `publish.md`, `slug.md`, and `versioning.md`, none of which this implements.
108
+ The verb reads the `markdown-craft` skill's reference and the `write-human` cadence numbers and nothing else. The references the `git-pr`, `git-branch`, and `git-commit` skills own, `publish.md`, `slug.md`, and `versioning.md`, govern an attribute of other artifacts, and none of them is implemented here.
111
109
 
112
110
  `publish.md` describes a scan applying the same punctuation bans to finished text on its way out. No code implemented that scan before this command, so nothing is duplicated, and a later surface should call this verb rather than build a second one.
113
111
 
114
- That standard's cross-reference rule is out of reach rather than merely unimplemented. It resolves by destination, bare where the destination auto-links a pull request or issue number and backticked where it does not, so this verb sees only the half where the backticked form is correct. A check here would report every compliant reference in the tree and reach none of the published text that goes wrong, which is why the rule holds on reading alone.
112
+ That reference's cross-reference rule is out of reach rather than merely unimplemented. It resolves by destination, bare where the destination auto-links a pull request or issue number and backticked where it does not, so this verb sees only the half where the backticked form is correct. A check here would report every compliant reference in the tree and reach none of the published text that goes wrong, which is why the rule holds on reading alone.
115
113
 
116
114
  The list-density rule at `claude/skills/markdown-craft/references/markdown.md` is out of scope on purpose, since it carries no number and what a density figure should measure is still open.
@@ -25,7 +25,7 @@ Every citation in scope is reported with its file, its line number, and the line
25
25
 
26
26
  `migrate rule-layout` moves a target still on the flat installed-rule layout onto the `canon/`-wrapped one. It classifies each flat file against the target's own recorded stamp: an unchanged hash is clean, a changed one is edited, and a name neither the stamp nor the current rule catalog can vouch for is unclaimed and left in place. Clean and edited files both relocate with their content untouched, since moving a rule is a different decision from re-syncing it. The one renumbered rule reads its destination from a table rather than from its own current name, so it never lands as a second copy under the retired number.
27
27
 
28
- `migrate scratch-evidence` moves a folder out of `.tmp/` for good rather than repointing a citation in place. A promoted folder is one a durable record, live or archived, cites as its evidence, and no source file under `claude/`, `scripts/`, `src/`, `governance/`, or `standards/` names by path, which is what a script still writing into it fails. It walks every `canon records push` folder rather than the live ones alone, since most of what it repoints sits in `tasks/archive/`, `plans/archive/`, and `groundwork/`. A destination already occupied is a refusal, and a second run rewriting nothing is the same idempotence check `migrate record-tree` uses.
28
+ `migrate scratch-evidence` moves a folder out of `.tmp/` for good rather than repointing a citation in place. A promoted folder is one a durable record, live or archived, cites as its evidence, and no source file under `claude/`, `scripts/`, `src/`, or `governance/` names by path, which is what a script still writing into it fails. It walks every `canon records push` folder rather than the live ones alone, since most of what it repoints sits in `tasks/archive/`, `plans/archive/`, and `groundwork/`. A destination already occupied is a refusal, and a second run rewriting nothing is the same idempotence check `migrate record-tree` uses.
29
29
 
30
30
  A destination that already holds the same bytes marks the flat file a duplicate, which `--write` deletes rather than moves. One holding different bytes is a real collision: neither file is touched, it is reported by name, and every other planned move still applies independently. Run this before `canon gov install` or `canon gov sync` against a target still on the flat layout, since neither bootstrap verb detects or clears it on its own.
31
31
 
@@ -0,0 +1,70 @@
1
+ ---
2
+ title: The pull request evidence layout
3
+ description: How canon pr evidence groups screenshots into one block per view with a row per width and a column per theme, when a base column appears, where a name outside the form lands, how the commit line links, and when the blocks open
4
+ ---
5
+
6
+ # The pull request evidence layout
7
+
8
+ `canon pr evidence` renders the screenshots of a pull request as blocks a
9
+ reviewer reads by view. The record behind them, and the reasons a run can
10
+ refuse, are in `canon docs pr-evidence`.
11
+
12
+ ## One block per view
13
+
14
+ A case name that ends in `-dark-<width>` or `-light-<width>` belongs to the
15
+ view named by everything before that suffix, so `menu-open-light-1440` is the
16
+ `menu-open` view. The name is split from the right because a view may carry
17
+ hyphens of its own.
18
+
19
+ Each view is one `<details>` block. Its summary carries the view and its case
20
+ count, a row holds one width in ascending number order, and a column holds one
21
+ theme:
22
+
23
+ ```markdown
24
+ | Width | Dark | Light |
25
+ ```
26
+
27
+ A view with any case already at the base takes a base and a head column per
28
+ theme, and a case the pull request added shows `*(new)*` in its base cell:
29
+
30
+ ```markdown
31
+ | Width | Dark base | Dark head | Light base | Light head |
32
+ ```
33
+
34
+ A cell with no case in it stays empty. A theme no case of the view carries has
35
+ no column. A state under a named directory titles its blocks
36
+ `<state> / <view>`, so two states holding one view never repeat a name.
37
+
38
+ ## Names outside the form
39
+
40
+ A case name that does not split into a view, a theme, and a width lands in a
41
+ flat `Case | Base | Head` block for its state, or `Case | Head` when every case
42
+ in it is new. Nothing is dropped, so `board.png` or a name with no width still
43
+ shows.
44
+
45
+ ## The commit line
46
+
47
+ Under `## Evidence`, the Base and Head line names each commit as a short sha
48
+ that links to the full commit:
49
+
50
+ ```markdown
51
+ **Base:** [`<base>`](https://github.com/<repo>/commit/<base-sha>) · **Head:** [`<head>`](https://github.com/<repo>/commit/<head-sha>)
52
+ ```
53
+
54
+ A reader opens what the screenshots were taken against in one click. The base
55
+ is the merge base the compare endpoint returns, not trunk's tip.
56
+
57
+ ## When the blocks open
58
+
59
+ Every block opens while the comment carries six images or fewer, counting one
60
+ per case for the head plus one per case that is not new. Past that every block
61
+ closes, so the whole comment is open or closed together.
62
+
63
+ ## What reads the body back
64
+
65
+ The case count that `would-empty` compares is the number of head images in the
66
+ body. It holds for the grouped rows, where one row carries up to two cases, and
67
+ for every layout posted before, so a comment rendered under an older shape
68
+ still counts. The bold `**Base:**` label and the `<details>` blocks are
69
+ unchanged, which keeps the address reader and the review skills working on both
70
+ layouts. An older comment keeps its shape until the next run edits it in place.
@@ -41,8 +41,9 @@ The record groups every evidence path by state, the remainder of its
41
41
  directory under the `evidence/` segment, and within a state by filename stem,
42
42
  so a `before/hero.png` and an `after/hero.png` naming the same case group as
43
43
  one entry, not two files. Each entry carries whether it existed at the base
44
- commit, so the comment shows a base image or marks it new. All-new states drop
45
- the Base column and each `<img>` takes its PNG width, at most 1000.
44
+ commit, so the comment shows a base image or marks it new. Each `<img>` takes
45
+ its PNG width, at most 1000. `canon docs pr-evidence-layout` covers how the body
46
+ groups those entries by view.
46
47
 
47
48
  `reason` on the record is what a caller branches on, not the exit code:
48
49
 
@@ -152,7 +153,7 @@ whichever previews apply, joined by a middle dot:
152
153
 
153
154
  **Preview:** <url> · **Local preview:** <url>
154
155
 
155
- **Base:** `<base>` · **Head:** `<head>`
156
+ **Base:** [`<base>`](<base-commit-url>) · **Head:** [`<head>`](<head-commit-url>)
156
157
  ```
157
158
 
158
159
  The Base and Head line follows only when the comment carries screenshots.
@@ -258,12 +259,10 @@ the caller, and lifts only on `settled`, so a refusal holds it the same as
258
259
 
259
260
  ## When the comment opens
260
261
 
261
- The comment opens every state while it carries six images or fewer, counting one
262
- per row for the head plus one per row that is not new, and closes every state
263
- past that, so the whole comment is open or closed together. A line under
264
- `## Evidence` names the compared base and head as short shas, which tells a
265
- reader whether the images match the latest push. The base is the merge base the
266
- compare endpoint returns, not trunk's tip.
262
+ The comment opens every block while it carries six images or fewer and closes
263
+ every block past that. A line under `## Evidence` names the compared base and
264
+ head as linked short shas, which tells a reader whether the images match the
265
+ latest push. `canon docs pr-evidence-layout` covers the count and the blocks.
267
266
 
268
267
  ## What the comment still leaves to GitHub
269
268
 
@@ -12,6 +12,7 @@ the `canon-frames` branch and returns a link to its path there.
12
12
 
13
13
  ```bash
14
14
  canon pr frames 1341 --add frames/3.png --box 3 --head <sha> --pass 20261002T154450Z --json
15
+ canon pr frames 1341 --add frames/3-dark.png --box 3 --theme dark --head <sha> --pass 20261002T154450Z --json
15
16
  canon pr frames 1341 --drop --json
16
17
  canon pr frames --prune 30 --json
17
18
  ```
@@ -23,7 +24,10 @@ through the GitHub git data API, being blobs, trees, commits, and that one ref,
23
24
  so nothing is checked out, no worktree is entered, and neither `main` nor any
24
25
  branch a pull request holds is touched. The branch never merges.
25
26
 
26
- A frame lands at `pr-<number>/<short-head>/<pass>/box-<n>.png`. `--head` names
27
+ A frame lands at `pr-<number>/<short-head>/<pass>/box-<n>.png`, or at
28
+ `box-<n>-<theme>.png` when `--theme` names `dark` or `light`, so two shots of
29
+ one box in one pass never take the same path. A frame without `--theme` keeps
30
+ the plain form. `--head` names
27
31
  the commit the pass drove, 7 to 40 lowercase hex characters, and defaults to
28
32
  the head the pull request object reports. `--pass` is the instant the pass
29
33
  started, in compact UTC as `date -u +%Y%m%dT%H%M%SZ` prints it, which
@@ -59,6 +63,7 @@ be published with it.
59
63
  | `bad-box` | `--add` carried no positive `--box`. |
60
64
  | `bad-head` | `--head`, or the head the pull request reported, is not a 7 to 40 character sha. |
61
65
  | `bad-pass` | `--add` carried no `--pass` stamp in compact UTC. |
66
+ | `bad-theme` | `--theme` is neither `dark` nor `light`, or came without `--add`. |
62
67
  | `bad-days` | `--prune` carried no positive number of days. |
63
68
  | `unreadable-frame` | The `--add` file is missing, empty, or not a PNG. Nothing is called. |
64
69
  | `read-only` | GitHub refused a write with 403 or 404, which is what a fork's token gets. |
@@ -69,7 +74,7 @@ be published with it.
69
74
  | `gh-missing` | `gh` is not on the path. |
70
75
  | `gh-failed` | `gh` could not name the repository or read the pull request. |
71
76
 
72
- An add's `ok` record carries `link`, `commit`, `path`, `box`, `head`, `pass`,
77
+ An add's `ok` record carries `link`, `commit`, `path`, `box`, `theme` when one was passed, `head`, `pass`,
73
78
  and `created`, which is true when the add made the branch. A drop's carries
74
79
  `removed`, the pull requests whose folders went, plus `commit` for the rewrite
75
80
  or `deleted` when it emptied the branch. A prune adds `kept` and `unread`.
@@ -0,0 +1,132 @@
1
+ ---
2
+ title: The pull request draft lift
3
+ description: How canon pr lift removes the draft mark once the reviews, evidence, standing, and CI all read clear at the head, the gate order and what each reads, the wait on a pending run, the re-read before the write, the refusal reasons it names, and what --check reports
4
+ ---
5
+
6
+ # The pull request draft lift
7
+
8
+ `canon pr lift` takes a pull request out of draft. It reads the gate itself, so
9
+ any session that closes a review can run it, and nobody has to judge whether a
10
+ message asking for the lift came from someone entitled to ask.
11
+
12
+ ```bash
13
+ canon pr lift 1341 --json
14
+ canon pr lift 1341 --check --json
15
+ ```
16
+
17
+ With no number it resolves the one open pull request on the checked-out branch.
18
+ The ship chain marks every pull request a draft, and this is the verb that
19
+ undoes it.
20
+
21
+ ## What it reads
22
+
23
+ Five gates, read in this order. The first four need no wait, so they all run
24
+ before the check runs are read.
25
+
26
+ | Gate | Clear when |
27
+ | ---------- | --------------------------------------------------------------------------------------------- |
28
+ | `review` | The newest `## Review` pass is closed, and its marker names the head. |
29
+ | `ui` | The newest `## UI review` verdict is closed at the head. Binds only where a checklist exists. |
30
+ | `evidence` | `canon pr evidence --check` reads `settled`, meaning no post-pull-request step is owed. |
31
+ | `standing` | The head merges into the freshly fetched base, and the base moved no path the branch writes. |
32
+ | `ci` | Every check run on the branch tip concluded and none failed. |
33
+
34
+ The head is the branch tip read from the remote, not the pull request object's
35
+ head, which trails a push by up to a minute. A review marker names the commit
36
+ the reviewer read, and it covers the head when it is that commit or a prefix of
37
+ it.
38
+
39
+ The UI gate binds only when `canon pr evidence --check --json` carries a
40
+ `checklist` field. The verb branches on that field and not on the exit, so a
41
+ branch with no rendered surface owes no UI review.
42
+
43
+ A review pass with no read-time marker refuses as `review-unmarked`. Its covered
44
+ commit would be the stamp GitHub wrote at submission, which the marker exists to
45
+ replace, and lifting on it reintroduces the defect `canon pr review-state`
46
+ documents.
47
+
48
+ ## The wait
49
+
50
+ A pending CI run is waited on rather than refused. `--wait <seconds>` bounds it,
51
+ defaulting to 1200, and the check runs are read every 30 seconds until one of
52
+ four things happens:
53
+
54
+ - every run passed, and the verb goes on to the write
55
+ - a run failed, which refuses `ci-failing` at once
56
+ - the branch conflicts with its base, which refuses `ci-conflicted` at once,
57
+ since no run starts for a conflicted branch and the wait could not end
58
+ - the deadline passed, which refuses `ci-timeout`
59
+
60
+ A refusal on any of the first four gates comes before the wait starts. A code
61
+ reviewer whose UI partner is still driving refuses `ui-open` immediately
62
+ instead of holding a turn through a CI run.
63
+
64
+ ## The re-read
65
+
66
+ A push during the wait moves the head off the commit every earlier gate
67
+ cleared. After the wait, and immediately before the write, the verb reads the
68
+ pull request, the branch tip, and the reviews again. A tip that moved refuses
69
+ `head-moved`, and a review that opened refuses with its own reason. The
70
+ standing is read again as well, so a sibling merging during the wait refuses
71
+ `standing-stale` instead of lifting a branch that read fresh before it. A pull
72
+ request already out of draft by then reports `already-ready`.
73
+
74
+ Two reviewers closing within seconds of each other can each read the other's
75
+ review as still open and both refuse. The re-read narrows that window and does
76
+ not close it, so a draft whose reviews both read closed is the controlling
77
+ session's to lift.
78
+
79
+ ## The write
80
+
81
+ The lift is the `markPullRequestReadyForReview` GraphQL mutation, because the
82
+ REST pull request update carries no draft field. It is the one GraphQL call the
83
+ verb makes, and every read stays on REST. The verb reads `isDraft` back from the
84
+ mutation's answer, and a response that does not say `false` refuses `gh-failed`
85
+ rather than reporting a lift.
86
+
87
+ A cloud session's proxy refuses GraphQL, so a reviewer running there reaches
88
+ `gh-failed` on the write and hands the lift to the controlling session.
89
+
90
+ ## What it reports
91
+
92
+ `reason` on the record is what a caller branches on. Exit 0 covers `lifted` and
93
+ `already-ready`, so the second of two reviewers to close owes nobody a message.
94
+ Every other reason exits 1.
95
+
96
+ | Reason | What it means |
97
+ | ------------------- | ---------------------------------------------------------------------- |
98
+ | `lifted` | The mark came off. |
99
+ | `already-ready` | The pull request was not a draft. Nothing is written. |
100
+ | `clear` | Under `--check`, every gate reads clear. |
101
+ | `pull-closed` | The pull request is closed. |
102
+ | `review-missing` | The thread carries no code review. |
103
+ | `review-open` | The newest code review is open. |
104
+ | `review-unmarked` | The newest code review carries no marker. |
105
+ | `review-behind` | The newest code review closed on an earlier commit than the head. |
106
+ | `ui-missing` | A checklist exists and no UI review has been posted. |
107
+ | `ui-open` | The newest UI review is open. |
108
+ | `ui-unmarked` | The newest UI review carries no head marker. |
109
+ | `ui-behind` | The newest UI review closed on an earlier commit than the head. |
110
+ | `evidence-owed` | The evidence check reads `owed`. |
111
+ | `standing-stale` | The base moved under a path the branch writes. |
112
+ | `standing-conflict` | The head does not merge cleanly into the base. |
113
+ | `standing-unknown` | The head or base did not resolve, which a fork or deleted head leaves. |
114
+ | `ci-pending` | Under `--check`, a run has yet to conclude. |
115
+ | `ci-failing` | A check run failed. |
116
+ | `ci-conflicted` | The branch conflicts, so no run will start. |
117
+ | `ci-timeout` | A run was still pending when `--wait` ran out. |
118
+ | `head-moved` | A push landed during the wait. |
119
+ | `gh-failed` | A read or the write was refused, so nothing is reported as lifted. |
120
+
121
+ `standing-unknown` waits on a person, matching the orchestrator poll's own rule
122
+ for a head it cannot resolve.
123
+
124
+ ## `--check`
125
+
126
+ `--check` reports every gate and writes nothing. It reads the check runs once
127
+ and does not wait, so a pending run reports `ci-pending`. The record carries a
128
+ `gates` array with each gate's name, whether it is clear, and its reason, so a
129
+ caller sees every gate that blocks and not the first alone.
130
+
131
+ The orchestrator's poll and a session deciding whether to run the verb read the
132
+ same answer this way, rather than each restating the gate.
@@ -25,7 +25,7 @@ It reports and never writes, matching `validate` and `size`.
25
25
 
26
26
  `canonical` reads `REQUIREMENTS.md` at whichever surface root carries it, `canon/` ahead of `.claude/`, and omits it when the project does not hold one. It is tracked, so `--root` defaults to the worktree the caller stands in rather than the main one, and a stamp a branch just wrote reads back from that branch.
27
27
 
28
- The review point is an optional `reviewed: YYYY-MM-DD` frontmatter field, set by whoever finishes a review, per `canon standards requirements`. It is a date rather than a commit, since a commit written on a feature branch never reaches a trunk that squash-merges. The field is read off the raw frontmatter block rather than a parsed value, since a YAML parser resolves a bare date to a date object on one schema and a string on another. A value that is not a calendar date reads as never reviewed, and the record carries the raw value as `invalidReviewed` so a review can see it rather than the verb throwing over one hand edit.
28
+ The review point is an optional `reviewed: YYYY-MM-DD` frontmatter field, set by whoever finishes a review, per `claude/skills/draft-doc/references/requirements.md`. It is a date rather than a commit, since a commit written on a feature branch never reaches a trunk that squash-merges. The field is read off the raw frontmatter block rather than a parsed value, since a YAML parser resolves a bare date to a date object on one schema and a string on another. A value that is not a calendar date reads as never reviewed, and the record carries the raw value as `invalidReviewed` so a review can see it rather than the verb throwing over one hand edit.
29
29
 
30
30
  The count is every release tag merged into `HEAD` whose creator date falls after the reviewed day. A release tag ends in a dotted version, bare as `v5.25.0` or behind a component prefix as `canon-v2.1.0`, so an eval, deploy, or snapshot tag on the trunk never inflates it. A lightweight tag's creator date is its commit's date and an annotated tag's is the tagging date, so a project mixing both counts by two clocks.
31
31