@erclx/canon 5.21.0 → 5.23.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 (88) hide show
  1. package/claude/.claude-plugin/plugin.json +1 -1
  2. package/claude/skills/auto-ship/SKILL.md +1 -1
  3. package/claude/skills/auto-ship/references/draft-mark.md +8 -6
  4. package/claude/skills/draft-doc/REQUIREMENT.md +45 -0
  5. package/claude/skills/draft-doc/SKILL.md +77 -0
  6. package/claude/skills/draft-doc/references/context.md +31 -0
  7. package/claude/skills/draft-doc/references/docs.md +33 -0
  8. package/claude/skills/draft-doc/references/readme.md +58 -0
  9. package/claude/skills/draft-doc/references/wiki.md +53 -0
  10. package/claude/skills/draft-doc/references/wireframes.md +48 -0
  11. package/claude/skills/draft-figure/REQUIREMENT.md +1 -1
  12. package/claude/skills/draft-figure/SKILL.md +1 -1
  13. package/claude/skills/git-pr/SKILL.md +5 -46
  14. package/claude/skills/git-pr/references/final-command.md +75 -0
  15. package/claude/skills/git-pr/references/labels.md +2 -2
  16. package/claude/skills/role-orchestrator/references/orchestrator-cloud-launch.md +76 -0
  17. package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +11 -1
  18. package/claude/skills/role-orchestrator/references/orchestrator-launch.md +5 -1
  19. package/claude/skills/search-craft/REQUIREMENT.md +4 -4
  20. package/claude/skills/search-craft/SKILL.md +1 -1
  21. package/claude/skills/target-check/SKILL.md +9 -9
  22. package/claude/skills/target-check/references/domains.md +11 -5
  23. package/docs/agents/architecture-record.md +2 -2
  24. package/docs/agents/commands.md +1 -1
  25. package/docs/agents/drift-surfaces.md +20 -9
  26. package/docs/agents/index.md +3 -3
  27. package/docs/agents/install-and-sync.md +3 -3
  28. package/docs/agents/migrate.md +1 -1
  29. package/docs/agents/rule-citations.md +1 -1
  30. package/docs/agents/slides.md +47 -3
  31. package/docs/agents/tasks-validate.md +4 -2
  32. package/docs/agents/tasks.md +2 -2
  33. package/docs/target/migrations.md +3 -1
  34. package/docs/target/projects.md +1 -1
  35. package/docs/target/sync.md +1 -1
  36. package/docs/workflow/skill-map.md +14 -18
  37. package/governance/rules/canon/604-scratch.md +0 -1
  38. package/governance/rules/claude/575-hooks.md +1 -2
  39. package/governance/rules/claude/576-settings.md +1 -2
  40. package/governance/rules/standards/architecture.md +0 -1
  41. package/governance/rules/standards/context.md +0 -1
  42. package/governance/rules/standards/design.md +0 -1
  43. package/governance/rules/standards/diagrams.md +0 -3
  44. package/governance/rules/standards/requirements.md +0 -1
  45. package/governance/rules/standards/wireframes.md +0 -1
  46. package/package.json +1 -1
  47. package/scripts/manage-sandbox.sh +7 -8
  48. package/src/claude/cases/authoring.ts +5 -5
  49. package/src/commands/slides.ts +14 -4
  50. package/src/commands/sync.ts +10 -0
  51. package/src/commands/tasks.ts +16 -0
  52. package/src/gov/citations.ts +1 -2
  53. package/src/migrate/skill-names.ts +12 -0
  54. package/src/slides/convert/deck.ts +83 -3
  55. package/src/slides/convert/export.ts +67 -19
  56. package/src/slides/convert/shapes.ts +43 -3
  57. package/src/slides/convert/walk.ts +43 -1
  58. package/src/slides/package/fonts.ts +297 -0
  59. package/src/slides/package/motion.ts +316 -0
  60. package/src/slides/package/test-face.ts +113 -0
  61. package/src/sync/check.ts +9 -0
  62. package/src/sync/layout.ts +27 -1
  63. package/src/tasks/validate.ts +65 -0
  64. package/standards/architecture.md +2 -3
  65. package/standards/board.md +2 -0
  66. package/standards/context.md +0 -1
  67. package/standards/design.md +0 -1
  68. package/standards/diagrams.md +0 -3
  69. package/standards/requirements.md +3 -2
  70. package/standards/rule.md +1 -0
  71. package/standards/wireframes.md +0 -1
  72. package/tooling/claude/seeds/.claude/hooks/path-form.sh +2 -2
  73. package/tooling/claude/seeds/.claude/hooks/pr-create-log.sh +10 -5
  74. package/tooling/claude/seeds/.claude/hooks/{tasks-index.sh → records-index.sh} +42 -35
  75. package/tooling/claude/seeds/.claude/settings.json +1 -5
  76. package/tooling/claude/seeds/canon/ARCHITECTURE.md +1 -1
  77. package/tooling/claude/seeds/canon/REQUIREMENTS.md +2 -0
  78. package/claude/skills/draft-context/REQUIREMENT.md +0 -37
  79. package/claude/skills/draft-context/SKILL.md +0 -61
  80. package/claude/skills/draft-docs/REQUIREMENT.md +0 -35
  81. package/claude/skills/draft-docs/SKILL.md +0 -59
  82. package/claude/skills/draft-readme/REQUIREMENT.md +0 -38
  83. package/claude/skills/draft-readme/SKILL.md +0 -73
  84. package/claude/skills/draft-wiki/REQUIREMENT.md +0 -42
  85. package/claude/skills/draft-wiki/SKILL.md +0 -83
  86. package/claude/skills/draft-wireframes/REQUIREMENT.md +0 -38
  87. package/claude/skills/draft-wireframes/SKILL.md +0 -69
  88. package/tooling/claude/seeds/.claude/hooks/memory-index.sh +0 -95
@@ -0,0 +1,76 @@
1
+ ---
2
+ title: Orchestrator cloud launch
3
+ description: The cloud build shape, being the launch prompt that carries the plan as text and announces over GitHub, the create under a pseudo-terminal with its trust keys, and the readout that fails loudly on a missing session id
4
+ ---
5
+
6
+ # Orchestrator cloud launch
7
+
8
+ Run this once `orchestrator-dispatch.md` has cleared a row and picked cloud for it. The worker runs on a Claude Code cloud VM that clones the repository from GitHub, loads no plugin, and cannot see this machine's `.canon/`.
9
+
10
+ The shape needs a `SessionStart` hook in the repository that, on the VM alone, copies the plugin skills into `.claude/skills/` and puts `canon` on the path. A repository without one cannot take this shape, so fall back to a local worker there. Where the hook exists, the launch prompt calls `/auto-ship` with no `canon:` prefix, since the copied skills list as project skills.
11
+
12
+ ## Check the row is unclaimed
13
+
14
+ A cloud worker never appears in `canon sessions list` and never pushes the derived branch, since the cloud assigns it a `claude/` branch of its own. So the roster and the refs read clear on a row a cloud worker is already building. Run the local check in `orchestrator-dispatch.md` anyway, since a local worker on the same row still collides, then read the two places a cloud worker does show:
15
+
16
+ ```bash
17
+ prs=$(gh api 'repos/{owner}/{repo}/pulls?state=open&per_page=100' --jq '.[] | select(.head.ref | startswith("claude/")) | .number') || { echo 'unreadable: the open pull request list'; exit 1; }
18
+ found=$(printf '%s\n' "$prs" | while IFS= read -r n; do
19
+ [ -n "$n" ] || continue
20
+ bodies=$(gh api "repos/{owner}/{repo}/issues/$n/comments" --jq '.[].body') || { echo "unreadable: the comments on #$n"; exit 1; }
21
+ if printf '%s\n' "$bodies" | grep -qE '^(ANNOUNCE|QUESTION): plan <slug>([^a-z0-9-]|$)'; then echo "claimed: #$n"; fi
22
+ done) || { printf '%s\n' "$found" | grep '^unreadable'; exit 1; }
23
+ printf '%s\n' "${found:-clear}"
24
+ ```
25
+
26
+ The prompt below has the worker open its `ANNOUNCE:` and `QUESTION:` comments with `plan <slug>`, so the comment is where a cloud pull request names its row. A title or body cannot, since `git-pr` writes both from the diff. Ending the match at a character no slug carries keeps `log` from claiming `log-entry`.
27
+
28
+ Branch on the line it prints rather than on the exit, which a shell function wrapping a command can flatten to zero:
29
+
30
+ - `claimed: #<n>`: a cloud worker's pull request names the plan slug. Treat the row as claimed and report each number.
31
+ - `clear`, and this pass's launched record holds no cloud session for the row: the row is clear of cloud workers.
32
+ - `unreadable: <what>`: the list or a comment read failed, so nothing was checked. Treat the candidate as unverified and fall back to the human-launch line, the same as a refused local check in `orchestrator-dispatch.md`. Never read it as clear, since an empty answer from a failed read is the collision this check exists to catch.
33
+
34
+ The launched record covers the minutes before the pull request opens, when nothing on GitHub names the row yet. It is this session's alone, so a second dispatcher sees only the pull request list, which is the window `orchestrator-dispatch.md` already states for local workers under `## Hold what this pass already launched`.
35
+
36
+ ## The prompt
37
+
38
+ Write the prompt to `.canon/tmp/cloud-<slug>/prompt.md` at the main worktree root, with the command at position zero, per `### Position zero` in `orchestrator-launch.md`:
39
+
40
+ ```plaintext
41
+ /auto-ship .canon/plans/feature-<slug>.md
42
+ Before Step 1 reads it, write the plan below verbatim to .canon/plans/feature-<slug>.md, creating the folder. That folder is gitignored, so it stays out of every commit. Stay on the branch this session was given, since a cloud session may push only to its own claude/ branch, so skip the worktree entry and the branch rename, and open the pull request from that branch although its name does not follow the branch convention git-pr checks.
43
+ Your controller cannot receive a message from this session. Announce on GitHub instead, naming the repository <owner>/<repo> in every comment. When the pull request opens, comment on it starting with ANNOUNCE: plan <slug>, then the number, the branch, the head sha, the CI state, and every point you departed from the plan on. If the chain stops after the pull request opens, post that comment anyway and name the stop. If you stop on a question before a pull request exists, push what you have, open a draft pull request through the REST create with draft=true, since the draft mark cannot run here, and comment on it starting with QUESTION: plan <slug>.
44
+ Before you finish, read the pull request's live body and delete every line carrying claude.ai/code/session_, since the label scan fails on one.
45
+ <the plan file, verbatim>
46
+ ```
47
+
48
+ The plan travels as text because the VM has no `.canon/` and the orchestrator alone writes the board, so nothing syncs the record across. `auto-ship` Step 1 then reads it at the path the command names, the same as on a local worker.
49
+
50
+ ## The create
51
+
52
+ `claude --cloud` refuses without a terminal, so the create runs under `script`, which supplies one:
53
+
54
+ ```bash
55
+ dir=.canon/tmp/cloud-<slug>
56
+ timeout 150 script -qec "claude --cloud \"\$(cat $dir/prompt.md)\"" "$dir/create.typescript" < <(sleep 30)
57
+ session=$(grep -A 2 'Created cloud session:' "$dir/create.typescript" | grep -o 'session_[A-Za-z0-9]*' | head -n 1)
58
+ [ -n "$session" ] || { printf 'No cloud session id in %s. Nothing was dispatched.\n' "$dir/create.typescript" >&2; exit 1; }
59
+ printf 'session=%s\n' "$session"
60
+ ```
61
+
62
+ Run it from the main worktree root, since the session targets whichever repository that checkout's `origin` names. That folder is already trusted, so the create screen shows no trust prompt. From a checkout this machine has not trusted, such as a fresh clone of another repository, the screen asks first, and piped keys answer it: replace `<(sleep 30)` with `<(sleep 4; printf '\033[B'; sleep 1; printf '\r'; sleep 30)`. Either way stdin stays open for the create, which is the shape the wrapper was measured in, since an early end of input can reach the terminal before the create screen settles.
63
+
64
+ The readout is the text `Created cloud session: <title>`, unanchored since a terminal capture can open a line with escape codes, followed by a `View:` address carrying the `session_...` id. Anything else is a failed create, including a run that exits zero. Report the typescript's last lines and fall back to the human-launch line in `orchestrator-dispatch.md` rather than recording a session that does not exist. The wrapper is a stopgap: any change to the interactive create screen breaks it, which is why the readout fails loudly instead of guessing.
65
+
66
+ Record `session` beside the row's branch in this pass's launched record. A cloud worker never registers in `canon sessions list`, so that record and the pull request list are the only two readings that know it exists. Report the dispatch the way the local shape does, naming the row, the plan, the repository, and the session id in place of a session name.
67
+
68
+ ## Recognize the pull request
69
+
70
+ A cloud worker cannot message this session and never appears in the roster `watch.ts` reads, so its pull request and its `ANNOUNCE:` comment are the announcement. Start the poll in `orchestrator-poll.md` when the cloud dispatch goes out, rather than at that runbook's thirty-minute fallback. A pull request on a `claude/` head is this row's when one of its commits carries a `Claude-Session:` trailer naming the session id the create recorded:
71
+
72
+ ```bash
73
+ gh api 'repos/{owner}/{repo}/pulls/<number>/commits' --jq '.[].commit.message' | grep -c 'Claude-Session:.*<session>'
74
+ ```
75
+
76
+ A count above zero matches the row. A count of zero is someone else's pull request, whatever its title says. Once it matches, record the number on the row's task yourself with `canon tasks pull-request <number> <task>`, since `git-pr` on the VM has no task file to write, then review it the way step 5 reviews any worker's pull request.
@@ -54,6 +54,10 @@ Reading `claimed` off the record is what keeps this a check rather than a rule a
54
54
 
55
55
  What the ref read cannot see is a branch pushed from another machine since the last fetch, because it reads the remote-tracking ref rather than the remote. Nobody has hit that, and a `git ls-remote` per dispatch costs 0.438s against 0.001s, so the gap is recorded rather than closed.
56
56
 
57
+ ### A cloud worker's claim
58
+
59
+ A cloud worker never appears in the roster and never pushes the derived branch, so run the local check above and then the cloud claim in `${CLAUDE_SKILL_DIR}/references/orchestrator-cloud-launch.md` before a cloud dispatch.
60
+
57
61
  ## Hold what this pass already launched
58
62
 
59
63
  A worker registers with `branch: main` and the main worktree as its `cwd` until `auto-ship` Step 0 moves it, which took several seconds on both measured runs. Neither the roster nor the refs name the candidate during that window, so a second check inside it reads clear.
@@ -82,9 +86,15 @@ A `claude --bg` session inherits the model of whatever launched it rather than r
82
86
 
83
87
  Name `<model>` on the launch, and pick it against the task rather than copying whatever this session happens to run. Sizing the model to the row is the dispatcher's call, the same call it already makes on the branch. A mechanical row moving files under a written plan is not the row that needs the largest model, and one whose plan carries an open judgment is.
84
88
 
89
+ ## Pick local or cloud
90
+
91
+ Dispatch to a cloud worker only on the operator's explicit pick for that dispatch, until three cloud rows have merged. Whether a cloud session is worth its cost is the operator's account to judge, and the review load a cloud pull request adds is unmeasured, so the pick stays a person's until that count gives a reading.
92
+
93
+ A row qualifies for that pick when its plan carries no open question and touches no surface needing local state, such as the sandbox, the records remote, or a browser. A cloud worker cannot reach any of the three, and it cannot ask the operator mid-build except through a draft pull request.
94
+
85
95
  ## Dispatch
86
96
 
87
- Read `${CLAUDE_SKILL_DIR}/references/orchestrator-launch.md` once every check above clears, and launch from its build template. It also holds the review-address and planning shapes, which skip the checks above.
97
+ Read `${CLAUDE_SKILL_DIR}/references/orchestrator-launch.md` once every check above clears, and launch from its build template. That runbook also holds the review-address and planning shapes, which skip the checks above. A row the operator picked for cloud launches from `${CLAUDE_SKILL_DIR}/references/orchestrator-cloud-launch.md` instead, since the cloud VM shares neither the plugin nor the board with this machine.
88
98
 
89
99
  ## Fall back to the human
90
100
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Orchestrator launch runbook
3
- description: The launch every dispatch kind shares, being the build, review-address, planning, reviewer, and UI reviewer templates, the session name and controller id each carries, why the command sits at position zero, and what the brief may carry
3
+ description: The launch every dispatch kind shares, being the build, cloud build, review-address, planning, reviewer, and UI reviewer templates, the session name and controller id each carries, why the command sits at position zero, and what the brief may carry
4
4
  ---
5
5
 
6
6
  Run this once `orchestrator-dispatch.md` has cleared a row, or from its planning, review-address, or reviewer shape below when that runbook's checks do not bind. The model each template names comes from `## Pick the model` in that runbook.
@@ -49,6 +49,10 @@ The prompt carries pointers and standing context, and stops there. The branch an
49
49
 
50
50
  Report the dispatch as loudly as the human-launch line it replaces: name the branch, the model, the task, and the session name, so a person reading the transcript can follow what fired without watching it happen.
51
51
 
52
+ ## Dispatch to a cloud worker
53
+
54
+ Read `${CLAUDE_SKILL_DIR}/references/orchestrator-cloud-launch.md` when `orchestrator-dispatch.md` picked cloud for the row. It holds the prompt that carries the plan as text and announces over GitHub, the create under `script`, and the readout that refuses to record a missing session id.
55
+
52
56
  ## Dispatch to address a review
53
57
 
54
58
  `review-address` is a single pass, not a chain, so a launch naming it alone reaches no `role-worker` and takes no role, which owes no message either. Reach the role directly on this launch instead of wrapping a second chain around one skill that has none of its own.
@@ -7,7 +7,7 @@ description: Why a session needs a thinking step before an outside search, deriv
7
7
 
8
8
  ## Gap
9
9
 
10
- Without this skill, a session reaching outside the project runs one generic `WebSearch` and takes what ranks highest. Nothing in the toolkit says how to search, and the two procedures that require an outside source, `plan-groundwork` and `draft-wiki`, give no route to one.
10
+ Without this skill, a session reaching outside the project runs one generic `WebSearch` and takes what ranks highest. Nothing in the toolkit says how to search, and the two procedures that require an outside source, `plan-groundwork` and `draft-doc`'s wiki kind, give no route to one.
11
11
 
12
12
  The miss is measured. On 2026-10-02 two queries ran twice each with identical text, once open and once with `allowed_domains` set to authorities the session derived itself with no list to consult.
13
13
 
@@ -30,7 +30,7 @@ Access is a second gap a session cannot derive. Probed on 2026-10-02, Reddit ref
30
30
  - Prefer a public API over a fetched and summarized page where one exists, since the API returns the dates, authors, and identifiers a citation needs
31
31
  - Carry a dated access note grown only from a failure a session hit, and stop to ask the operator before any paid or logged-in route
32
32
  - Close with the practice skill's excuses, red flags, and closing checklist, answering "the model already knows the good sites" and "one search was enough"
33
- - Be loaded from the sourcing steps of `plan-groundwork` and `draft-wiki`, each reporting when the skill does not resolve
33
+ - Be loaded from the sourcing steps of `plan-groundwork` and `draft-doc`'s wiki kind, each reporting when the skill does not resolve
34
34
 
35
35
  ## Must not
36
36
 
@@ -42,7 +42,7 @@ Access is a second gap a session cannot derive. Probed on 2026-10-02, Reddit ref
42
42
  ## Out of scope
43
43
 
44
44
  - The form a citation takes: the procedure or standard writing it
45
- - A Claude Code subject: the `claude-code-guide` agent, which `draft-wiki` already routes to
45
+ - A Claude Code subject: the `claude-code-guide` agent, which `draft-doc`'s wiki sourcing step already routes to
46
46
  - Image generation, which makes a picture rather than finding a source
47
47
  - Loading on every web search rather than on a research-shaped request: a hook matching `WebSearch`, planned on its own
48
48
  - Whether a session already scopes searches without the skill, which only a run on a fresh research request without it would show
@@ -54,4 +54,4 @@ Access is a second gap a session cannot derive. Probed on 2026-10-02, Reddit ref
54
54
  - No `Skill` call fired, so the description did not route a plain research request to the body.
55
55
  - The run searched four times with no domain filter, naming authors and journals from recall, then fetched the papers' own pages. It never named the field, so the scenario's field pattern failed and the arm went red on one of its assertions.
56
56
 
57
- The arm shows the trigger is the gap rather than the body: a session that never loads the skill cannot follow it. The pointers from `plan-groundwork` and `draft-wiki` reach only those procedures, and the hook matching `WebSearch` is the route for every other search. One run on one model shows the miss exists and does not size it.
57
+ The arm shows the trigger is the gap rather than the body: a session that never loads the skill cannot follow it. The pointers from `plan-groundwork` and `draft-doc`'s wiki kind reach only those procedures, and the hook matching `WebSearch` is the route for every other search. One run on one model shows the miss exists and does not size it.
@@ -78,6 +78,6 @@ Each line is a claim measured once, and `${CLAUDE_SKILL_DIR}/REQUIREMENT.md` rec
78
78
  ## What this delegates
79
79
 
80
80
  - The form a citation takes: the surface writing it, such as the standard the procedure writes to
81
- - A Claude Code subject: the `claude-code-guide` agent, which `draft-wiki` routes to
81
+ - A Claude Code subject: the `claude-code-guide` agent, which `draft-doc`'s wiki sourcing step routes to
82
82
  - Generating an image: whatever draws the surface, since making one is not search
83
83
  - Access code a session needs repeatedly: a `canon` verb, never a script in this skill
@@ -26,7 +26,7 @@ CANON_NON_INTERACTIVE=1 canon sync --check . --json
26
26
  CANON_NON_INTERACTIVE=1 canon context audit --json
27
27
  ```
28
28
 
29
- The first answers five domains from one read, being `skew`, `domains`, `seeds`, `tooling`, and the layout fields `unmigrated`, `superseded`, and `reverse`. The second deepens a domain that first read only counts.
29
+ The first answers five domains from one read, being `skew`, `domains`, `seeds`, `tooling`, and the layout fields `unmigrated`, `superseded`, `unmovedSurfaces`, and `reverse`. The second deepens a domain that first read only counts.
30
30
 
31
31
  Then run `CANON_NON_INTERACTIVE=1 canon records validate <kind> --json` once per record folder the target carries, taking `<kind>` from what is present under the record root rather than from a list held here.
32
32
 
@@ -76,14 +76,14 @@ Lead with the table, then one detail block per domain that is not current:
76
76
  ```markdown
77
77
  Target check: `<path>`, against canon `<version>`.
78
78
 
79
- | Domain | State | Evidence |
80
- | ---------------- | ------------------------- | --------------------------------- |
81
- | Toolkit version | current / behind / unread | `skew.installed` against `latest` |
82
- | `canon/` folder | current / behind / unread | what the layout fields named |
83
- | Governance rules | current / behind / unread | the per-state counts |
84
- | Tooling | current / behind / unread | what a sync would change |
85
- | Seeds | current / behind / unread | the per-state counts |
86
- | Claude harness | unchecked | no verb reads it |
79
+ | Domain | State | Evidence |
80
+ | ---------------- | ------------------------- | ---------------------------------------------- |
81
+ | Toolkit version | current / behind / unread | `skew.installed` against `latest` |
82
+ | `canon/` folder | current / behind / unread | the layout fields, including `unmovedSurfaces` |
83
+ | Governance rules | current / behind / unread | the per-state counts |
84
+ | Tooling | current / behind / unread | what a sync would change |
85
+ | Seeds | current / behind / unread | the per-state counts |
86
+ | Claude harness | unchecked | no verb reads it |
87
87
 
88
88
  No overall verdict. The harness row is unchecked rather than passing.
89
89
  ```
@@ -21,19 +21,25 @@ This domain is first because it conditions the other five. A target a release be
21
21
 
22
22
  ## 2. The `canon/` folder
23
23
 
24
- Read three fields off the same report, then two verbs:
24
+ Read four fields off the same report, then two verbs:
25
25
 
26
26
  - `unmigrated`: a domain still at the root layout with nothing under `.claude/`. The most urgent finding, because such a domain reports no drift of its own while being entirely behind.
27
27
  - `superseded`: a file a newer seed folder replaced. No command moves it, since the content is the project's own.
28
+ - `unmovedSurfaces`: a surface held only at its `.claude/` spelling, each entry carrying `rel` and `movesTo`. Every other field reads clean for such a target, and the report's drift status ignores it by design, so read the array itself rather than an exit status.
28
29
  - `reverse`: `unclaimed` names folders at roots the toolkit stopped shipping, each with an `attribution` of `dropped`, `unattributed`, or `project`.
29
30
  - `canon context audit --json`: required sections, entry length, citations, and index drift across the context folder.
30
31
  - `canon records validate <kind> --json`, once per record folder the target carries.
31
32
 
32
- - Current: `unmigrated` and `superseded` both empty, `reverse.unclaimed` carrying nothing outside `project` attribution, and both verbs finding nothing.
33
- - Behind: any of those carrying an entry. Name the entry and its attribution.
34
- - Unread: `historyUnavailable` set on `reverse`, a `reverse` key the report does not carry at all, or either verb refusing. A missing `reverse` key is a binary predating the field rather than a target with nothing unclaimed.
33
+ - Current: `unmigrated`, `superseded`, and `unmovedSurfaces` all empty, `reverse.unclaimed` carrying nothing outside `project` attribution, and both verbs finding nothing.
34
+ - Behind: any of those carrying an entry. Name the entry and its attribution, and for `unmovedSurfaces` each `rel` with its `movesTo`.
35
+ - Unread: `historyUnavailable` set on `reverse`, a `reverse` or `unmovedSurfaces` key the report does not carry at all, or either verb refusing. A missing key is a binary predating the field rather than a target with nothing to report, so name the installed version beside it.
35
36
 
36
- Routes by finding. `unmigrated` routes to a relocation the user runs, since no command moves it. `superseded` routes to a split the user makes, since no command moves the content and no skill proposes it. An `unclaimed` folder carrying `project` attribution routes nowhere, since history proved the project owns it.
37
+ Routes by finding.
38
+
39
+ - `unmovedSurfaces` routes to `canon migrate surface-roots`, which only reports its plan until `--write` is passed. Name the verb with each entry's `rel` and `movesTo`, and run neither form.
40
+ - `unmigrated` routes to a relocation the user runs, since no command moves it.
41
+ - `superseded` routes to a split the user makes, since no command moves the content and no skill proposes it.
42
+ - An `unclaimed` folder carrying `project` attribution routes nowhere, since history proved the project owns it.
37
43
 
38
44
  ## 3. Governance rules
39
45
 
@@ -19,7 +19,7 @@ What the derivation costs where it does apply is that the ceiling rises when a d
19
19
 
20
20
  The decision word check counts each decision's prose against the cap the record states in a clause of the form `at most 150 words a decision`. The count runs below the `###` heading with fenced lines dropped, so neither the heading nor a template block inside a decision counts, and the report and the gate read the same number. The JSON record carries the clause as `architecture.wordCap`, and the audit catalog counts the decisions past it as `recordOverWords`, absent where the record states no clause.
21
21
 
22
- The risk bullet check counts every list item under `## Risks / open questions`, nested ones included, against a clause of the form `at most 6 risk bullets`, since a nested bullet is weight the reader pays for like any other. Both clauses can share the entry cap's sentence, and each gates only a record stating it, for the reason the cap does. A record stating a risk cap and carrying no Risks section is reported as absent rather than passed as zero bullets. The JSON record carries the clause as `architecture.riskCap` and the count as `architecture.risksBullets`, and the audit catalog counts a section past its cap as `recordOverRisks`, absent where the record states no clause.
22
+ The risk bullet check counts every list item under `## Risks / open questions`, nested ones included, against a clause of the form `at most 6 risk bullets`, since a nested bullet is weight the reader pays for like any other. Both clauses can share the entry cap's sentence, and each gates only a record stating it, for the reason the cap does. The record `canon claude init` seeds into a new project states all three caps in that one sentence, so a new project gates on them from its first decision, and a project that installed earlier keeps whatever its own record states. A record stating a risk cap and carrying no Risks section is reported as absent rather than passed as zero bullets. The JSON record carries the clause as `architecture.riskCap` and the count as `architecture.risksBullets`, and the audit catalog counts a section past its cap as `recordOverRisks`, absent where the record states no clause.
23
23
 
24
24
  The coverage report classifies each decision as carrying a countable claim, a structural invariant, or neither, then reports each testable entry against whether it names a check that exists. A countable claim carries a figure a run could recompute, and an invariant quantifies over a named tree closely enough that a walk could falsify it. A check is a `scripts/**.sh` path the entry spells that is on disk, or a `canon` invocation matching a registered audit, so coverage reads the entry rather than the tree and a claim some check happens to cover without the entry saying so reads as unchecked.
25
25
 
@@ -33,4 +33,4 @@ The `## Risks / open questions` section is weighed the same way, reported apart
33
33
 
34
34
  ## The requirements record
35
35
 
36
- The audit reads `canon/REQUIREMENTS.md` under its own step and counts every word below the frontmatter against the cap the record states in a clause of the form `This record holds at most 600 words.` The cap is the record's own, so a record stating none is measured and never gated, and a project with no record is reported as out of scope. The JSON record carries the reading as `requirements`, null where the project carries no record and absent under `--citations-only`, and the audit catalog counts a record past its cap as `requirementsOverWords`.
36
+ The audit reads `canon/REQUIREMENTS.md` under its own step and counts every word below the frontmatter against the cap the record states in a clause of the form `This record holds at most 600 words.` The cap is the record's own, so a record stating none is measured and never gated, and a project with no record is reported as out of scope. The seeded record states the 600-word cap. The JSON record carries the reading as `requirements`, null where the project carries no record and absent under `--citations-only`, and the audit catalog counts a record past its cap as `requirementsOverWords`.
@@ -130,7 +130,7 @@ Subcommands no other page describes:
130
130
  - `design regen` rewrites this repository's `canon/DESIGN.md` from `src/design/tokens.ts` and `src/design/base.css` from `src/design/neutral.ts`. `design css` emits the tokens and components as CSS on stdout, with `--no-components` for properties alone, and `design render` declares each vendored face the typography table names under `--embed-fonts`.
131
131
  - `slides render` renders a `.claude/SLIDES.md` source into a PowerPoint deck and reports any unrecognized layout name on stderr, and `slides list --json` emits the layout catalog.
132
132
  - `slides render --source <folder>` lays out each `.html` file in filename order in Chromium at 1280 by 720 and rebuilds it as editable shapes. Each element drawn as a picture prints one `✗` line on stderr naming the slide, the selector, and the properties that forced it. A link left out, being a `#slide-N` past the deck's end or any target that is neither that nor an absolute URL, prints one too, and the exit stays 0.
133
- - A deck folder's `deck.json`, its per-slide `<body>` attributes, speaker notes, and charts are described in `slides.md`.
133
+ - A deck folder's `deck.json` and the faces it embeds, its per-slide `<body>` attributes, transitions and entrances, speaker notes, and charts are described in `slides.md`.
134
134
  - `canvas`, with its verbs, content format, and write routes, is described in `canvas.md`.
135
135
  - `labels scan` fails a pull request or a posted review whose title, body, or review comment carries a phase label, a label a code span quotes, a gitignored record path, a session link, a title word no dictionary holds, or a title breaking `standards/pr.md`'s format, casing, or length rule (`--event`, `--body-file`, `--json`).
136
136
  - `repo metadata propose` compares a description, homepage, and topic set computed from the README and `package.json` against what the remote carries, writing nothing, and `repo metadata apply` writes an explicitly supplied set through `gh repo edit` (`--description`, `--homepage`, `--topics`).
@@ -1,19 +1,19 @@
1
1
  ---
2
2
  title: Drift surfaces
3
- description: The six sections canon sync --check reports beside the per-domain scan, being seeds, superseded artifacts, unmigrated domains, new rules and skills, and the reverse walk, with the managed-target gate and what counts toward the exit code
3
+ description: 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
4
4
  ---
5
5
 
6
6
  # Drift surfaces
7
7
 
8
- `canon sync --check` reports six sections outside the per-domain scan that
8
+ `canon sync --check` reports seven sections outside the per-domain scan that
9
9
  `install-and-sync.md` describes, because each names something
10
- that walk cannot see: `seeds`, `superseded`, `unmigrated`, `newSkills`,
11
- `newRules`, and `reverse`. None of them produces a change, and no sync command
12
- applies any of them. The headings below group the first three together and give
13
- `newSkills` its description under `newRules`, which answers the same question
14
- about a different corpus.
10
+ that walk cannot see: `seeds`, `superseded`, `unmigrated`, `unmovedSurfaces`,
11
+ `newSkills`, `newRules`, and `reverse`. None of them produces a change, and no
12
+ sync command applies any of them. The headings below group the first four
13
+ together and give `newSkills` its description under `newRules`, which answers the
14
+ same question about a different corpus.
15
15
 
16
- All six report only against a toolkit-managed target, which is one carrying a
16
+ All seven report only against a toolkit-managed target, which is one carrying a
17
17
  `.claude/` directory, a `CLAUDE.md`, or a domain still at the root layout. The
18
18
  report says so through `managed` in the JSON and routes an unmanaged directory to
19
19
  `canon init`. Seeds are why the gate exists, since they enumerate from the toolkit
@@ -26,7 +26,7 @@ toolkit installed. When `managed` is false every section comes back empty rather
26
26
  than the render alone going quiet, so a consumer reading `--json` never acts on a
27
27
  finding the rendered half withheld.
28
28
 
29
- ## Seeds, superseded artifacts, and unmigrated domains
29
+ ## Seeds, superseded artifacts, unmigrated domains, and old-root surfaces
30
30
 
31
31
  `seeds` classifies every seed the toolkit ships against the target's copy, as
32
32
  `matching`, `stale`, `drifted`, or `missing`. `missing` has no per-domain
@@ -68,6 +68,13 @@ Nothing proposes moving it, and no command relocates the content. Move it
68
68
  yourself. A leftover `snippets/` folder reports under the reverse walk instead,
69
69
  where `docs/target/sync.md` says what to do with it.
70
70
 
71
+ `unmovedSurfaces` names a tracked surface the target still holds at its `.claude/`
72
+ spelling with nothing at the `canon/` one, such as `.claude/REQUIREMENTS.md` or
73
+ `.claude/context/`. Each entry carries `rel` and `movesTo`, and the render names
74
+ `canon migrate surface-roots`, the verb that moves them. Rules glob the `canon/`
75
+ spelling alone, so a target on the old root stops loading the rules scoped to
76
+ those files until it moves. <!-- canon-keep-surface-root -->
77
+
71
78
  ## Rules the target never received
72
79
 
73
80
  `newRules` names a rule the target could receive and its tree does not hold. A
@@ -182,6 +189,10 @@ toolkit shipped and the target renamed goes unmatched, the same limit the
182
189
  is: only the user can move content they wrote, so failing a job on it leaves the
183
190
  job red with no mechanical remedy.
184
191
 
192
+ `unmovedSurfaces` is excluded although `canon migrate surface-roots` closes it.
193
+ The move rewrites citations across the project, so it is a change the project
194
+ schedules, and a job counting it would go red the day a target updated the CLI.
195
+
185
196
  `newRules` is excluded on a different ground, since a command does close it. What
186
197
  excludes it is that installing a rule changes what a project is governed by, so
187
198
  gating on the count would pressure a target into adopting rules nobody chose.
@@ -22,7 +22,7 @@ CLI catalog and invocation rules for agents, split by command domain. Start with
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
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
24
  - [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
- - [Drift surfaces](drift-surfaces.md): The six sections canon sync --check reports beside the per-domain scan, being seeds, superseded artifacts, unmigrated domains, new rules and skills, and the reverse walk, with the managed-target gate and what counts toward the exit code
25
+ - [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
26
  - [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
27
27
  - [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
28
28
  - [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
@@ -55,14 +55,14 @@ CLI catalog and invocation rules for agents, split by command domain. Start with
55
55
  - [Sessions](sessions.md): Resolving live peer sessions to the worktree and branch each holds, reading which row is the caller, the liveness confidence field, what each session surface can see, and where moving a session to another machine is described
56
56
  - [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
57
57
  - [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
58
- - [Slides](slides.md): What a folder of HTML slides declares for the whole deck in deck.json, what each slide declares on its own body, the master built from the project's tokens, speaker notes, charts, and what each refusal reports
58
+ - [Slides](slides.md): What a folder of HTML slides 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
59
59
  - [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
60
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
61
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
62
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
63
63
  - [Tasks archive](tasks-archive.md): Archiving a shipped task by stem or pull request, declining one decided against, the refusal reasons each carries, and how the plan and ready folder move with the task
64
64
  - [Tasks plans](tasks-plans.md): The five verbs that read or write a task's plan, being where it sits and who cites it, whether it waits on the operator, the branch it derives, what a branch reached against what it declared, and writing its Plan line
65
- - [Tasks validate](tasks-validate.md): Validating the board against the tree, the seven checks, the untested, claims, and unplaced arrays, the exit codes, and the refusal reasons
65
+ - [Tasks validate](tasks-validate.md): Validating the board against the tree, the seven checks, the untested, claims, wide, and unplaced arrays, the exit codes, and the refusal reasons
66
66
  - [Tasks](tasks.md): Claiming the next phase label, recording a pull request number and closing an outcome, listing each task's readiness, and where archiving, the plan verbs, and board validation are described
67
67
  - [Teach authoring](teach-authoring.md): Resolving what the next lesson needs before it is written, rendering a lesson body's blocks to HTML, and rewriting the root listing, a contents page, and each lesson's chrome and quiz stepper from its marker regions
68
68
  - [Teach](teach.md): Listing learning workspaces with what their records schedule next, opening one with its required files, recording sources and glossary terms, viewing one with its chrome refreshed, where the lesson authoring verbs are described, the refusal reasons, and why every write here runs through a verb
@@ -233,9 +233,9 @@ edit and a job counting those stays red with no remedy.
233
233
 
234
234
  ### Surfaces reported beside the domains
235
235
 
236
- Six sections sit outside the per-domain scan, because each names something
237
- that walk cannot see: `seeds`, `superseded`, `unmigrated`, `newSkills`,
238
- `newRules`, and `reverse`. None of them produces a change, and no sync command
236
+ Seven sections sit outside the per-domain scan, because each names something
237
+ that walk cannot see: `seeds`, `superseded`, `unmigrated`, `unmovedSurfaces`,
238
+ `newSkills`, `newRules`, and `reverse`. None of them produces a change, and no sync command
239
239
  applies any of them. What each reports, the managed-target gate they share, and
240
240
  which count toward `--exit-code` are in `drift-surfaces.md`.
241
241
 
@@ -9,7 +9,7 @@ description: What each canon migrate verb moves and rewrites, what it refuses, t
9
9
 
10
10
  `migrate rename` moves a project off the retired `aitk` name. It reports until `--write` is passed, and `--scope target` rewrites the toolkit-owned folders alone, reporting every other citation as one the project owns rather than editing prose somebody wrote. A project installing `canon` fresh never needs it.
11
11
 
12
- `migrate skill-names` moves the plugin skills that carried a `claude-` prefix onto their two-word names and rewrites every citation of one. It carries two plain renames as well, where a skill carried no prefix and changed its name because the old one described a job it no longer does, landing on `ui-checklist` and `context-fold`, and it leaves the pointer answering under the fold skill's old name in place. It shares its engine with `migrate rename` and differs in what it is scanning for: a skill name is a whole name rather than a word stem, so it matches only where the word ends, which is what keeps a longer word that merely opens with a skill name, such as `claude-worktrees`, out of it. It takes no `--scope`, since the skill folders it moves are authored in the toolkit and no target holds a copy of that catalog, which leaves a target's own citations of a renamed skill as the whole of what it rewrites there. The changelog and the eval transcripts are passed over on the argument the other sweep already carries, that each records what shipped or what a session ran under whatever name was current then.
12
+ `migrate skill-names` moves the plugin skills that carried a `claude-` prefix onto their two-word names and rewrites every citation of one. It carries two plain renames as well, where a skill carried no prefix and changed its name because the old one described a job it no longer does, landing on `ui-checklist` and `context-fold`, and one merge, where the five per-kind document drafters land on `draft-doc`. It leaves the pointer answering under the fold skill's old name in place. It shares its engine with `migrate rename` and differs in what it is scanning for: a skill name is a whole name rather than a word stem, so it matches only where the word ends, which is what keeps a longer word that merely opens with a skill name, such as `claude-worktrees`, out of it. It takes no `--scope`, since the skill folders it moves are authored in the toolkit and no target holds a copy of that catalog, which leaves a target's own citations of a renamed skill as the whole of what it rewrites there. The changelog and the eval transcripts are passed over on the argument the other sweep already carries, that each records what shipped or what a session ran under whatever name was current then.
13
13
 
14
14
  `migrate records` moves a project's session records from `.claude/` to `.canon/` and rewrites every tracked file that cites one. It reports until `--write` is passed, and refuses outright when the project does not already ignore `.canon/`, since every folder it relocates is ignored where it stands and landing one under a tracked root commits the memory pen. Take the ignore entry with `canon tooling sync --write` first.
15
15
 
@@ -59,7 +59,7 @@ The extension test is what carries most of the separation, and it costs one real
59
59
 
60
60
  Two classes resolve to nothing and are right to. Both are reported by name rather than dropped, so a reader can see what the verdict declined to judge.
61
61
 
62
- **Governed.** A rule spelling a path in its own frontmatter `paths:` is naming an artifact a target holds rather than a file here. `governance/rules/standards/diagrams.md` declares `.claude/DIAGRAMS.md` and then tells its reader to convert one an older install left behind, so the file is correctly absent from this tree and correctly named in the rule.
62
+ **Governed.** A rule spelling a path in its own frontmatter `paths:` is naming an artifact a target holds rather than a file here, so the file is correctly absent from this tree and correctly named in the rule. No shipped rule declares one today, and the stage's test fixtures carry the instance that keeps the class exercised.
63
63
 
64
64
  Only an exact declaration exempts, never a glob match against one. A glob declares a shape, so a body path sitting inside it is still a citation and a stale one is still a defect. A rule scoped at `docs/**` citing a `docs/agents/renamed.md` that moved is exactly the class this stage exists to catch, and matching the glob would excuse it.
65
65
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Slides
3
- description: What a folder of HTML slides declares for the whole deck in deck.json, what each slide declares on its own body, the master built from the project's tokens, speaker notes, charts, and what each refusal reports
3
+ description: What a folder of HTML slides 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
4
4
  ---
5
5
 
6
6
  # Slides
@@ -22,13 +22,18 @@ canon slides render --source slides/q3-review
22
22
  | `footer` | on, with the title on the left | a band at the bottom, with the same three slots |
23
23
  | `slideNumbers` | `true` | the number at the right of the footer |
24
24
  | `mark` | none | an image path, relative to the folder, top right |
25
+ | `fonts` | none | faces to embed in the file, described below |
25
26
 
26
27
  ```json
27
28
  {
28
29
  "title": "Q3 review",
29
30
  "header": { "left": "Acme" },
30
31
  "footer": { "center": "Internal" },
31
- "mark": "assets/mark.png"
32
+ "mark": "assets/mark.png",
33
+ "fonts": [
34
+ { "family": "Inter", "path": "fonts/Inter-Regular.ttf" },
35
+ { "family": "Inter", "weight": 700, "path": "fonts/Inter-Bold.ttf" }
36
+ ]
32
37
  }
33
38
  ```
34
39
 
@@ -38,6 +43,17 @@ Slide numbers belong to the footer and hold its right slot. They show only where
38
43
 
39
44
  Prefer a raster mark. An SVG mark lands as a vector, and its fallback for a viewer that cannot draw SVG comes out broken.
40
45
 
46
+ ### Embedded faces
47
+
48
+ Each entry in `fonts` names one TrueType or OpenType file inside the deck folder, and the deck carries it so it renders in its own face on a machine without the font installed.
49
+
50
+ - `family` is required and should match the family the slides set, since that is the name PowerPoint looks the face up by.
51
+ - `weight` defaults to `400`. PowerPoint holds four faces a family, so `600` and above fills the bold slot and anything lighter fills regular.
52
+ - `style` is `normal` or `italic`, defaulting to `normal`.
53
+ - `path` is relative to the folder and has to stay inside it.
54
+
55
+ A face is refused, and the deck still written without it, when its license forbids embedding or allows a bitmap only, when it is a variable font, when it is a `woff` or `woff2` file, or when a second face lands in a slot one already fills. Supply static instances, one per weight, in place of a variable face. For `woff2`, supply the TrueType or OpenType file it was compressed from, which most font downloads ship beside it.
56
+
41
57
  ## What each slide declares
42
58
 
43
59
  A slide sets its own place in the deck through attributes on `<body>`.
@@ -55,6 +71,30 @@ Once any slide opens a section, slides ahead of the first one sit in a section n
55
71
 
56
72
  A slide dropping one band keeps the other from the master. A slide replacing a slot draws its band as its own text, so editing that band once in PowerPoint's master view reaches every slide except the ones that replaced it.
57
73
 
74
+ ## Motion
75
+
76
+ A slide declares how it arrives on `<body>`, and an element declares how it comes in on its own tag.
77
+
78
+ | Attribute | Takes | Effect |
79
+ | -------------------------- | ------------------------------- | -------------------------------------- |
80
+ | `data-transition` | `fade`, `push`, `wipe`, `cover` | the transition into this slide |
81
+ | `data-transition-duration` | `500`, `500ms`, or `0.5s` | rounded to 0.5, 0.75, or 1 second |
82
+ | `data-enter` | `fade`, `fly`, `wipe`, `zoom` | an entrance played on a click |
83
+ | `data-enter-order` | a number | the click it plays on, lowest first |
84
+ | `data-enter-duration` | `500`, `500ms`, or `0.5s` | the entrance length, 500 ms by default |
85
+
86
+ ```html
87
+ <body data-transition="fade">
88
+ <h1>Results</h1>
89
+ <div class="card" data-enter="fly" data-enter-order="1">…</div>
90
+ <div class="card" data-enter="fly" data-enter-order="2">…</div>
91
+ </body>
92
+ ```
93
+
94
+ Each entrance is its own click. Entrances without an order play after the ordered ones, in page order. An entrance brings in every shape drawn from its element and the elements inside it together, so a card's box and its text arrive on one click, and an element drawn as a picture brings in that picture. An element inside it with its own `data-enter` waits for its own click. Fly comes in from the bottom and wipe from the left.
95
+
96
+ A transition length rounds to the nearest of the three speeds PowerPoint's file format names, since a length in milliseconds needs an extension the deck does not write.
97
+
58
98
  ## The master
59
99
 
60
100
  The master takes its colors from the `<html>` element of the first slide as Chromium laid it out with the project's token stylesheet injected, so a theme switched on one slide's `<body>`, such as a dark cover, stays on that slide:
@@ -112,9 +152,13 @@ A `<figure data-chart="...">` holding a `<table>` becomes a native, editable cha
112
152
  | -------------------------------------- | ---------------------------------------------- | ---- |
113
153
  | `deck.json` field malformed or unknown | `✗` naming the file and the field | 1 |
114
154
  | `mark` path missing | `✗` naming the resolved path | 1 |
155
+ | `fonts` path missing or outside folder | `✗` naming the field and the path | 1 |
156
+ | face not embeddable | `✗` naming the path and the reason | 0 |
157
+ | unknown transition or entrance name | `✗` naming the slide, the element, the effect | 0 |
158
+ | entrance on an element that draws none | `✗` naming the slide and the element | 0 |
115
159
  | chart cell not a number | `✗` naming the slide, the figure, and the cell | 0 |
116
160
  | `data-chart` outside the five types | `✗` naming the value | 0 |
117
161
  | token role not declared | `✗` naming the role | 0 |
118
162
  | slide body sets its own `--color-text` | `✗` naming the slides | 0 |
119
163
 
120
- A refused chart is left out and the rest of the deck is written.
164
+ A refused chart, effect, or face is left out and the rest of the deck is written.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Tasks validate
3
- description: Validating the board against the tree, the seven checks, the untested, claims, and unplaced arrays, the exit codes, and the refusal reasons
3
+ description: Validating the board against the tree, the seven checks, the untested, claims, wide, and unplaced arrays, the exit codes, and the refusal reasons
4
4
  ---
5
5
 
6
6
  # Tasks validate
@@ -103,7 +103,9 @@ That claim collides with every row a later session writes under the folder, and
103
103
 
104
104
  The scan reaches `## Run now` and stops, where the collision check stops. A cell in another group describes work nobody has planned, written as a sentence and rewritten once a plan exists, so a claim read off one reports on prose rather than on a file set. A parked folder claim surfaces when its row is promoted, which is when the cell becomes something a dispatcher can act on.
105
105
 
106
- A task file neither surface names lands in a fourth array, on the same reasoning:
106
+ A code span in any cell that the preview cannot break lands in a fourth array, `wide`, with the same `group`, `subject`, and `message` fields. The scan measures a span's longest whitespace-free run and flags one past 60 characters, or a brace-glob at any width. A 57-character span fit the pane and a 78-character one did not, so the limit is a heuristic and the array moves no exit code. A link target is skipped, since only its label renders, and the scan reaches every group, since the overflow it exists for sat under `## Up next`.
107
+
108
+ A task file neither surface names lands in a fifth array, on the same reasoning:
107
109
 
108
110
  ```json
109
111
  {
@@ -64,7 +64,7 @@ Run from a branch other than `main` or `master`, the verb also drops that branch
64
64
 
65
65
  Exit codes: `0` recorded, `1` refused. The `reason` field carries `no-board`, `no-match`, or `ambiguous`. `git-pr` skips silently on those three, because each is a case where a guessed write would archive the wrong task once the branch merges.
66
66
 
67
- A malformed argument refuses as `bad-input` instead, which sits outside that set on purpose. `git-pr` derives the number by slicing whatever `gh pr create` printed, so a non-numeric value is reachable, and folding it into the swallowed set would lose the number with nothing reporting it.
67
+ A malformed argument refuses as `bad-input` instead, which sits outside that set on purpose. `git-pr` derives the number by slicing the address the create call returned, so a non-numeric value is reachable, and folding it into the swallowed set would lose the number with nothing reporting it.
68
68
 
69
69
  ## Outcome
70
70
 
@@ -118,6 +118,6 @@ canon tasks list --json | jq -r '.tasks[] | select(.readiness == "backlog") | .s
118
118
 
119
119
  ## Validate
120
120
 
121
- `canon tasks validate` reports what each row of `priority.md` and each line of `backlog.md` claims against what the tree holds. Its seven checks, the three arrays that move no exit code, and the refusals are in `tasks-validate.md`.
121
+ `canon tasks validate` reports what each row of `priority.md` and each line of `backlog.md` claims against what the tree holds. Its seven checks, the four arrays that move no exit code, and the refusals are in `tasks-validate.md`.
122
122
 
123
123
  For the board format, see `standards/board.md`. For the `Pull request:` line and the archive rules, see `standards/tasks.md`.
@@ -62,7 +62,9 @@ The first line reports the plan and the second applies it, relocating each rule
62
62
 
63
63
  Twenty-five plugin skills dropped their `claude-` prefix for two-word names, so `canon:claude-docs` answers as `canon:context-fold` and `canon:claude-tasks` as `canon:task-board`. A project that installed governance or tooling before that release holds files naming the old ones, and the plugin answers to none of them. A later release renamed `docs-fold` to `context-fold`, since the skill folds a session into the context entries and the task board rather than into `docs/`, and the same sweep carries a project across that rename too. The pointer that answered under the old name no longer ships, so a citation the sweep has not reached names nothing. <!-- canon-keep-retired -->
64
64
 
65
- Two of those files run rather than sit there. `.husky/post-merge` runs the `canon hooks post-merge` verb, so a stale copy of the hook calls a binary that may lack it, and `.claude/hooks/pr-create-log.sh` hands a session a message naming a skill, so a stale copy tells someone to invoke something that no longer exists. A rule under `.claude/rules/canon/core/` names skills too, though a rule is read rather than run.
65
+ The same sweep carries the merge of the five per-kind document drafters into `draft-doc`, so a citation of any one of them lands on `canon:draft-doc`.
66
+
67
+ Two of those files run rather than sit there. `.husky/post-merge` runs the `canon hooks post-merge` verb, so a stale copy of the hook calls a binary that may lack it, and `.claude/hooks/pr-create-log.sh` hands a session a message naming a skill, so a stale copy tells someone to invoke something that no longer exists. A copy older than `git-pr`'s move to REST also matches only `gh pr create`, so it stays silent on every pull request `git-pr` opens. A rule under `.claude/rules/canon/core/` names skills too, though a rule is read rather than run.
66
68
 
67
69
  Resync what the toolkit owns, then sweep what the project wrote:
68
70
 
@@ -71,7 +71,7 @@ Keep the `## Scripts` table in `canon/context/development.md` current as scripts
71
71
 
72
72
  Scaffold installs tooling and seeds. It does not fill the planning docs or the design system. Complete those before the first feature session:
73
73
 
74
- 1. Fill `canon/REQUIREMENTS.md` and `canon/ARCHITECTURE.md`. The seed provides the files, the scope and decisions are yours to write.
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
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.
77
77
  4. Start the feature loop. See [AI workflow](../workflow/ai-workflow.md) for the per-feature sequence.
@@ -28,7 +28,7 @@ Each domain holds its own toolkit commit, so syncing governance today does not m
28
28
 
29
29
  A project that has never synced under a toolkit new enough to write a stamp falls back to the toolkit's own git history. Installed content matching any version that history published proves the file untouched, so it reports `stale` naming the commit it came from, and content matching no published version stays `drifted`. That fallback needs the toolkit as a git checkout. Installed from the registry it ships source without history, and the report says attribution was unavailable rather than reading every file as a local edit.
30
30
 
31
- Further causes sit outside the per-domain scan, each naming something that walk cannot see. A seed the project edited is reported under `seeds` and reconciled with `canon:seed-sync`, since no sync command touches a seed. A file a newer seed folder replaced is reported under `superseded`, such as `.claude/TASKS.md` against the `.canon/tasks/` that now ships, and nothing moves it because the content is the project's own. A domain sitting at the root layout with nothing under `.claude/` is reported under `unmigrated`, and no command moves it either. The project moves the content itself.
31
+ Further causes sit outside the per-domain scan, each naming something that walk cannot see. A seed the project edited is reported under `seeds` and reconciled with `canon:seed-sync`, since no sync command touches a seed. A file a newer seed folder replaced is reported under `superseded`, such as `.claude/TASKS.md` against the `.canon/tasks/` that now ships, and nothing moves it because the content is the project's own. A domain sitting at the root layout with nothing under `.claude/` is reported under `unmigrated`, and no command moves it either. The project moves the content itself. A surface still at its `.claude/` spelling with nothing under `canon/`, such as `.claude/REQUIREMENTS.md`, is reported under `unmovedSurfaces`, since rules glob the `canon/` spelling alone, and `canon migrate surface-roots` moves it. <!-- canon-keep-surface-root -->
32
32
 
33
33
  `unmigrated` currently names no domain, since standards and snippets are the two the toolkit ever installed at the project root, standards closed its install channel, and snippets retired outright. A target still holding a root `standards/` folder from an older toolkit is carrying its own authoring surface now, not an unfinished install, and nothing proposes moving it. A leftover `.claude/snippets/` reports under `reverse` below instead.
34
34