@erclx/canon 5.25.0 → 5.26.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 (123) hide show
  1. package/README.md +2 -1
  2. package/claude/.claude-plugin/plugin.json +1 -1
  3. package/claude/skills/canvas/SKILL.md +1 -1
  4. package/claude/skills/context-fold/REQUIREMENT.md +6 -2
  5. package/claude/skills/context-fold/SKILL.md +8 -6
  6. package/claude/skills/context-fold/references/anchor-sweep.md +2 -2
  7. package/claude/skills/design-taste/REQUIREMENT.md +1 -1
  8. package/claude/skills/design-taste/SKILL.md +4 -2
  9. package/claude/skills/design-taste/references/adopted.md +40 -0
  10. package/claude/skills/design-taste/references/kinds.md +1 -1
  11. package/claude/skills/design-taste/references/systems.md +1 -7
  12. package/claude/skills/design-taste/references/tells.md +2 -24
  13. package/claude/skills/docs-sync/REQUIREMENT.md +2 -1
  14. package/claude/skills/document-health/REQUIREMENT.md +2 -0
  15. package/claude/skills/document-health/SKILL.md +21 -0
  16. package/claude/skills/document-health/references/axes.md +12 -0
  17. package/claude/skills/draft-and-pick/SKILL.md +1 -1
  18. package/claude/skills/draft-identity/SKILL.md +1 -1
  19. package/claude/skills/draft-screencast/SKILL.md +9 -7
  20. package/claude/skills/draft-slides/REQUIREMENT.md +17 -12
  21. package/claude/skills/draft-slides/SKILL.md +31 -20
  22. package/claude/skills/git-issue/references/issue.md +10 -0
  23. package/claude/skills/git-pr/references/evidence.md +8 -0
  24. package/claude/skills/git-pr/references/labels.md +12 -0
  25. package/claude/skills/git-split/SKILL.md +19 -8
  26. package/claude/skills/markdown-propose/references/format.md +9 -0
  27. package/claude/skills/memory-review/references/later-phases.md +7 -0
  28. package/claude/skills/review-ui/references/post.md +4 -2
  29. package/claude/skills/role-orchestrator/references/orchestrator-cloud-launch.md +8 -0
  30. package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +13 -0
  31. package/claude/skills/search-craft/REQUIREMENT.md +1 -1
  32. package/claude/skills/search-craft/SKILL.md +3 -1
  33. package/claude/skills/search-craft/references/adopted.md +30 -0
  34. package/claude/skills/target-setup/references/detect.md +8 -0
  35. package/claude/skills/target-setup/references/indexes.md +13 -0
  36. package/claude/skills/target-setup/references/verify.md +8 -0
  37. package/claude/skills/test-first/SKILL.md +2 -0
  38. package/claude/skills/test-first/references/adopted.md +36 -0
  39. package/claude/skills/write-human/SKILL.md +1 -1
  40. package/claude/skills/write-human/references/machine-tells.md +13 -0
  41. package/docs/agents/canvas.md +3 -1
  42. package/docs/agents/capture.md +1 -1
  43. package/docs/agents/commands.md +3 -3
  44. package/docs/agents/index.md +2 -2
  45. package/docs/agents/records-stale.md +22 -9
  46. package/docs/agents/records.md +1 -1
  47. package/docs/agents/sandbox.md +1 -1
  48. package/docs/agents/skills-audit.md +10 -3
  49. package/docs/agents/slides.md +15 -3
  50. package/docs/workflow/ai-workflow.md +1 -1
  51. package/docs/workflow/index.md +1 -0
  52. package/docs/workflow/parallel-features.md +1 -1
  53. package/docs/workflow/skill-map-on-request.md +42 -0
  54. package/docs/workflow/skill-map.md +1 -34
  55. package/package.json +1 -1
  56. package/scripts/lib/sandbox-dispatch.sh +1 -1
  57. package/scripts/lib/sandbox-git.sh +1 -9
  58. package/scripts/lib/sandbox-path.sh +1 -1
  59. package/scripts/sandbox-hook.sh +99 -0
  60. package/src/canvas/capture.ts +1 -9
  61. package/src/canvas/client/inspector/color-field.tsx +368 -0
  62. package/src/canvas/client/inspector/fields.css +163 -22
  63. package/src/canvas/client/inspector/values.ts +123 -0
  64. package/src/canvas/client/inspector.tsx +46 -69
  65. package/src/capture/render.ts +45 -7
  66. package/src/capture/sources.ts +43 -0
  67. package/src/census/count.ts +1 -1
  68. package/src/claude/cases/workflow.ts +5 -0
  69. package/src/claude/seeds.ts +1 -1
  70. package/src/claude/skills-audit.ts +68 -14
  71. package/src/client-commands.ts +1 -1
  72. package/src/commands/claude.ts +27 -2
  73. package/src/commands/feedback.ts +3 -3
  74. package/src/commands/records.ts +88 -6
  75. package/src/commands/sandbox.ts +50 -10
  76. package/src/commands/slides.ts +31 -77
  77. package/src/context/audit.ts +1 -1
  78. package/src/design/tokens.ts +2 -18
  79. package/src/docs/read.ts +1 -1
  80. package/src/{github.ts → feedback/github.ts} +1 -1
  81. package/src/gate/measures.ts +6 -4
  82. package/src/gov/citations.ts +1 -1
  83. package/src/gov/payload.ts +1 -1
  84. package/src/gov/superseded.ts +2 -2
  85. package/src/markdown/ceiling.ts +1 -1
  86. package/src/record-root.ts +1 -0
  87. package/src/records/backup.ts +1 -0
  88. package/src/records/push-guard.ts +1 -1
  89. package/src/records/stale-canonical.ts +146 -0
  90. package/src/records/stale.ts +1 -1
  91. package/src/sandbox/hooks.ts +128 -0
  92. package/src/sandbox/provision.ts +596 -0
  93. package/src/sandbox/tree.ts +103 -4
  94. package/src/secrets/marker.ts +2 -2
  95. package/src/secrets/scan.ts +1 -1
  96. package/src/shipped/references.ts +2 -2
  97. package/src/slides/convert/export.ts +2 -16
  98. package/src/slides/decks.ts +110 -0
  99. package/src/standards/read.ts +1 -1
  100. package/src/sync/seeds-report.ts +1 -1
  101. package/src/tasks/answers.ts +1 -1
  102. package/src/tasks/archive.ts +1 -1
  103. package/src/teach/nav.ts +1 -1
  104. package/src/tooling/inject.ts +1 -1
  105. package/src/web/readme-citations.ts +1 -1
  106. package/standards/architecture.md +1 -0
  107. package/standards/canvas.md +1 -1
  108. package/standards/requirements.md +2 -0
  109. package/standards/skill-practice.md +2 -1
  110. package/standards/skill.md +1 -0
  111. package/scripts/manage-sandbox.sh +0 -472
  112. package/src/slides/layouts.ts +0 -478
  113. package/src/slides/parse.ts +0 -84
  114. package/src/slides/render.ts +0 -110
  115. package/src/slides/styles.ts +0 -96
  116. /package/claude/skills/write-human/references/{source-material.md → adopted.md} +0 -0
  117. /package/src/{commands/feedback-format.ts → feedback/body.ts} +0 -0
  118. /package/src/{github-format.ts → feedback/github-format.ts} +0 -0
  119. /package/src/{frontmatter.ts → markdown/frontmatter.ts} +0 -0
  120. /package/src/{binary.ts → sweep/binary.ts} +0 -0
  121. /package/src/{exempt-marker.ts → sweep/exempt-marker.ts} +0 -0
  122. /package/src/{paths.ts → tasks/paths.ts} +0 -0
  123. /package/src/{seed-marker.ts → tooling/seed-marker.ts} +0 -0
@@ -36,7 +36,8 @@ Each check traces to a line in the standard.
36
36
  - Folder name in kebab-case
37
37
  - Each `REQUIREMENT.md` declaring `Gap` and `Must`, matched at any heading level
38
38
  - No ISO date in `SKILL.md` or any file under `references/`, reported as `datedProvenance` in the JSON record's `findings`
39
- - Each listed practice skill closing with `## Excuses and rebuttals`, `## Red flags`, and `## Before handing over`, and carrying `references/adopted.md`, reported as `practiceShape`
39
+ - Each listed practice skill closing with `## Excuses and rebuttals`, `## Red flags`, and `## Before handing over`, and each skill on the wider ledger list carrying `references/adopted.md`, reported together as `practiceShape`
40
+ - Each file under `references/` running over 100 lines opening with a `## Contents` H2, reported as `referenceContents`
40
41
 
41
42
  A body whose frontmatter does not parse reports as declaring neither field rather than ending the run, so one malformed file cannot hide the corpus behind it. A key present with an empty value reads as absent, since a blank `name` would otherwise report as a name disagreeing with every folder. A folder carrying no requirement is reported once under presence rather than counted again for the sections it therefore lacks.
42
43
 
@@ -44,7 +45,11 @@ The date check reads prose alone. A date inside a fence or a code span is exampl
44
45
 
45
46
  Each finding names the file and the line, such as `claude/skills/<name>/references/<file>.md line 12: <date>`. A date after "measured" or "verified" reports like any other, unlike the context audit, because a skill body states the rule a session follows today rather than a reading of the tree.
46
47
 
47
- The practice check reads only the skills the audit lists, which the JSON record carries as `checkpoints.practiceSkills`. The list names folders under the toolkit's shipped `claude/skills/` corpus alone, so a project's own skill is never read as a practice skill and a same-named folder under `.claude/skills/` produces no finding. Where `claude/skills/` resolves, a listed folder it does not hold reports as a missing skill, so a renamed or misspelled entry cannot pass unread. Each closing section matches only as an exact H2 outside a fence, so an H3 or a heading carrying more words reports as missing.
48
+ The practice check reads only the skills the audit lists, which the JSON record carries as `checkpoints.practiceSkills` for the closing sections and `checkpoints.ledgerSkills` for the ledger. The ledger list holds every practice skill plus the skills that cite outside sources without a practice's closing shape, so a skill on it alone owes the ledger and none of the sections.
49
+
50
+ Both lists name folders under the toolkit's shipped `claude/skills/` corpus alone, so a project's own skill is never read as a practice skill and a same-named folder under `.claude/skills/` produces no finding. Where `claude/skills/` resolves, a listed folder it does not hold reports as a missing skill, so a renamed or misspelled entry cannot pass unread. Each closing section matches only as an exact H2 outside a fence, so an H3 or a heading carrying more words reports as missing.
51
+
52
+ The contents check counts every line of a reference, frontmatter included, the way `wc -l` does, so a file of exactly 100 lines passes and one of 101 needs the list. `## Contents` matches the same way the closing sections do, as an exact H2 outside a fence. `SKILL.md` is never read for it, and the threshold travels in the JSON record as `checkpoints.contentsThreshold`.
48
53
 
49
54
  ## What it leaves alone
50
55
 
@@ -56,7 +61,9 @@ A check with no rule behind it prints an opinion as a defect, which is where the
56
61
 
57
62
  ## Exit codes
58
63
 
59
- Exit codes are `0` for a clean run, `1` for a refusal, and `2` for a skill folder carrying no `REQUIREMENT.md`. Only presence sets a failing code. Name, description, folder, requirement-section, dated-provenance, and practice shape findings print and return `0`, because each is a judgment a reader settles and failing a push on one would make the check something to route around. In the toolkit, two `bun run check` stages read the same report in-process and fail a push on dated provenance and on practice shape, since its own corpus is held at zero on both.
64
+ Exit codes are `0` for a clean run, `1` for a refusal, and `2` for a skill folder carrying no `REQUIREMENT.md`. Only presence sets a failing code. Name, description, folder, requirement-section, dated-provenance, practice shape, and reference contents findings print and return `0`, because each is a judgment a reader settles and failing a push on one would make the check something to route around. In the toolkit, two `bun run check` stages read the same report in-process and fail a push on dated provenance and on practice shape, since its own corpus is held at zero on both.
65
+
66
+ No stage reads reference contents, so a long reference without a list stays a report.
60
67
 
61
68
  ## The requirement gate
62
69
 
@@ -1,16 +1,28 @@
1
1
  ---
2
2
  title: Slides
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
3
+ description: 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
4
4
  ---
5
5
 
6
6
  # Slides
7
7
 
8
- `canon slides render --source <folder>` turns a folder of `.html` slides into an editable PowerPoint deck. Each slide describes its own shapes. What belongs to the whole deck, being the bands, the numbers, and the mark, sits in one file beside them, and the master that carries it takes the project's own colors and face.
8
+ `canon slides render <deck>` turns a folder of `.html` slides into an editable PowerPoint deck. Each deck lives in its own folder under `.canon/slides/`, which `canon records push` backs and git ignores, and `canon slides list --json` lists them. Each slide describes its own shapes. What belongs to the whole deck, being the bands, the numbers, and the mark, sits in one file beside them, and the master that carries it takes the project's own colors and face.
9
9
 
10
10
  ```bash
11
- canon slides render --source slides/q3-review
11
+ canon slides list --json
12
+ canon slides render q3-review
12
13
  ```
13
14
 
15
+ ## The deck folder
16
+
17
+ | Path | Holds |
18
+ | -------------------------------- | ---------------------------------------------------- |
19
+ | `.canon/slides/<deck>/*.html` | the slides, rendered in filename order |
20
+ | `.canon/slides/<deck>/deck.json` | what belongs to the whole deck, described below |
21
+ | `.canon/slides/<deck>/assets/` | images the slides reference by a relative path |
22
+ | `.canon/slides/layouts/` | the project's shared layouts, never listed as a deck |
23
+
24
+ A folder holding no `.html` file is not a deck. `render` with no name takes the only deck there is, and refuses with the list when there are several. It also takes a folder path outside `.canon/slides/`.
25
+
14
26
  ## The deck file
15
27
 
16
28
  `deck.json` beside the slides is optional. Without it the deck gets its defaults.
@@ -136,7 +136,7 @@ Before the first feature session on a UI-heavy project, pick a design tier. The
136
136
 
137
137
  ## Skills
138
138
 
139
- The [skill map](skill-map.md) gives every skill the plugin ships one row saying when to reach for it, grouped by the moment a project meets it.
139
+ The [skill map](skill-map.md) and its sibling [skill map on request](skill-map-on-request.md) give every skill the plugin ships one row saying when to reach for it. The first groups rows by the moment a project meets them, and the sibling holds the skills that answer a request.
140
140
 
141
141
  ## Feedback routing
142
142
 
@@ -15,6 +15,7 @@ How a person and their agents run a feature, from the design tier through the pa
15
15
 
16
16
  - [Operating model](operating-model.md): Orchestrator, planner, and worker roles for building across parallel sessions
17
17
  - [Parallel features](parallel-features.md): Run independent features in parallel worktrees, with the records and board every session shares
18
+ - [Skill map on request](skill-map-on-request.md): When to reach for the plugin skills that serve a request rather than a moment in a project's life
18
19
  - [Skill map](skill-map.md): When to reach for each plugin skill, grouped by the moment a project meets it
19
20
  - [Visual design workflow](visual-design-workflow.md): Tiered guide for design and wireframe authoring with Claude Code
20
21
  - [Zshrc aliases for Claude Code](zshrc-aliases.md): Shell aliases that shorten common Claude Code invocations
@@ -54,7 +54,7 @@ A plan that ships is archived, never deleted. `canon tasks archive` moves it to
54
54
 
55
55
  A branch review report takes the other route and is swept rather than archived. `review-branch` writes it flat into `.canon/review/` as `branch-<slug>.md`, the session addressing it reads it once, and the durable record of what a review found is the comment `review-pr` posts on the pull request, so `context-fold` deletes any report whose branch is gone. The body that writes a report owns how long it lives, which leaves the shipping branch's own report on disk through the run that cites it and collects it a branch later. What that loses is a local-only review on a branch that never opened a pull request, which is why the report says so where a reader meets it.
56
56
 
57
- `canon:context-fold` decides which task closed by reading the diff rather than the conversation. It resolves a merge base against `origin/main`, unions the committed diff with the working tree and untracked files, then matches unchecked outcomes on the board against what shipped. A task that shipped without ever being discussed still gets marked. Requirements, architecture, and design stay session-sourced, because a diff cannot carry a judgment.
57
+ `canon:context-fold` decides which task closed by reading the diff rather than the conversation. It resolves a merge base against `origin/main`, unions the committed diff with the working tree and untracked files, then matches unchecked outcomes on the board against what shipped. A task that shipped without ever being discussed still gets marked. Architecture and design stay session-sourced, because a diff cannot carry a judgment, and an architecture write further needs a folded plan naming the slot decision. The requirements record is never folded. A direction change gets one report line naming `canon:document-health`, which reviews that record only when asked.
58
58
 
59
59
  ## The task board
60
60
 
@@ -0,0 +1,42 @@
1
+ ---
2
+ title: Skill map on request
3
+ description: When to reach for the plugin skills that serve a request rather than a moment in a project's life
4
+ category: Workflow
5
+ ---
6
+
7
+ # Skill map on request
8
+
9
+ These two groups hold the skills no single moment in a project's life calls for, so they sit apart from the moment-ordered groups in the [skill map](skill-map.md). The two pages together are the corpus the coverage claim is measured against, and a skill takes one row across both.
10
+
11
+ ## Generate an artifact on demand
12
+
13
+ | Skill | When to use |
14
+ | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
15
+ | `canon:create-rule` | For a project-specific governance rule the toolkit does not ship |
16
+ | `canon:create-skill` | For a new `SKILL.md` |
17
+ | `canon:create-standard` | For a new authoring convention |
18
+ | `canon:draft-doc` | For a brand-new `docs/` page, context entry, wireframe surface, wiki reference page, or `README.md`, drafted against the standard for its kind |
19
+ | `canon:draft-ready` | For finished files a worker should copy, written as a ready folder with its overview, thin plan, and task |
20
+ | `canon:draft-figure` | For a hand-drawn figure or an architecture view inside an existing doc, drafted in Mermaid or freehand SVG |
21
+ | `canon:bash-cli-script` | For a small non-interactive shell wrapper, stopping at 100 lines in favor of TypeScript on Bun |
22
+ | `canon:ci-workflow` | For a GitHub Actions workflow file |
23
+ | `canon:draft-slides` | For a deck, drawn as HTML slides in its own folder and rendered to editable PowerPoint |
24
+ | `canon:draft-screencast` | For a recording script with beats and defaults already seeded |
25
+ | `canon:record-screencast` | For recording a screencast draft, then composing the take into a finished mp4 when the draft asks for a wrap |
26
+ | `canon:draft-identity` | For a project's logo mark and its social card, drafted through `draft-and-pick`'s pick loop |
27
+
28
+ ## Answer a question at any point
29
+
30
+ | Skill | When to use |
31
+ | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
32
+ | `canon:canon-cli` | Before running an unfamiliar verb, a sync, or an install, to learn which command to run, which reference doc covers it, or what it overwrites, merges, or leaves alone |
33
+ | `canon:index-lookup` | To find where a topic is documented across the tracked `index.md` catalogs |
34
+ | `canon:youtube-transcripts` | When a video transcript is wanted in the repo as context |
35
+ | `canon:read-frames` | To read a recorded demo back frame by frame and report what each one shows, with no verdict on whether the recording looks right |
36
+ | `canon:teach-workspace` | To learn a subject across sessions, in a workspace that holds the progress |
37
+ | `canon:write-human` | Before drafting or revising prose, for voice, rhythm, and density |
38
+ | `canon:restate-plainly` | When an answer or a document has to be read again in plain words |
39
+
40
+ Every row answers a question rather than marking a point in a project's life, so filing one under a phase on the [skill map](skill-map.md) would send a reader to the wrong group.
41
+
42
+ A learning workspace produces two halves and only one of them leaves. A lesson is worked through once and stays in the workspace, and a reference page or a glossary carries no learner, so it belongs wherever the project already keeps prose on that subject. Asking `canon:teach-workspace` to promote sorts each durable page by who owns its subject, sending an Anthropic-owned subject to the wiki, an internal one to the matching context entry, and everything else, subject-neutral material included, to the public docs. It proposes and waits, because a promoted page is public prose that needs a line naming who owns the subject, and it writes nothing to a destination: each page the operator confirms goes to a handoff file that `canon:context-fold` folds in from a branch. A project with no wiki folder gets a refusal naming `canon wiki init` rather than a folder it never asked for.
@@ -8,7 +8,7 @@ category: Workflow
8
8
 
9
9
  Groups run in the order a project meets them. They reconcile the scenarios in [AI workflow](ai-workflow.md) with the lifecycle [target projects](../target/projects.md) describes, so every moment either one names has a group. Each row says when to reach for a skill, and its description says what it does.
10
10
 
11
- This page is the corpus the coverage claim is measured against: every name `canon claude skills list --names` reports takes exactly one row here. A skill serving two moments sits at the earlier one, and a mention on any other page is prose rather than routing. The landing page's skills field renders from this page, so its build refuses a skill with no row and a row naming no skill.
11
+ This page and [skill map on request](skill-map-on-request.md) are the corpus the coverage claim is measured against: every name `canon claude skills list --names` reports takes exactly one row across the two. A skill serving two moments sits at the earlier one, this page counting as earlier than the sibling, and a mention on any other page is prose rather than routing. The sibling holds the groups that answer a request rather than mark a moment. The landing page's skills field renders from both pages, so its build refuses a skill with no row and a row naming no skill.
12
12
 
13
13
  ## Set up a project
14
14
 
@@ -115,36 +115,3 @@ This page is the corpus the coverage claim is measured against: every name `cano
115
115
  | `canon:canon-feedback` | When something in the toolkit is broken, missing, or off, to open an issue on it |
116
116
  | `canon:canon-feedback-triage` | In the toolkit repo, to work through the open feedback issues |
117
117
  | `canon:canon-rollout` | In the toolkit repo, to take one change out to every consuming project at once |
118
-
119
- ## Generate an artifact on demand
120
-
121
- | Skill | When to use |
122
- | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
123
- | `canon:create-rule` | For a project-specific governance rule the toolkit does not ship |
124
- | `canon:create-skill` | For a new `SKILL.md` |
125
- | `canon:create-standard` | For a new authoring convention |
126
- | `canon:draft-doc` | For a brand-new `docs/` page, context entry, wireframe surface, wiki reference page, or `README.md`, drafted against the standard for its kind |
127
- | `canon:draft-ready` | For finished files a worker should copy, written as a ready folder with its overview, thin plan, and task |
128
- | `canon:draft-figure` | For a hand-drawn figure or an architecture view inside an existing doc, drafted in Mermaid or freehand SVG |
129
- | `canon:bash-cli-script` | For a small non-interactive shell wrapper, stopping at 100 lines in favor of TypeScript on Bun |
130
- | `canon:ci-workflow` | For a GitHub Actions workflow file |
131
- | `canon:draft-slides` | For a deck, drafted as `.claude/SLIDES.md` and rendered to PowerPoint |
132
- | `canon:draft-screencast` | For a recording script with beats and defaults already seeded |
133
- | `canon:record-screencast` | For recording a screencast draft, then composing the take into a finished mp4 when the draft asks for a wrap |
134
- | `canon:draft-identity` | For a project's logo mark and its social card, drafted through `draft-and-pick`'s pick loop |
135
-
136
- ## Answer a question at any point
137
-
138
- | Skill | When to use |
139
- | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
140
- | `canon:canon-cli` | Before running an unfamiliar verb, a sync, or an install, to learn which command to run, which reference doc covers it, or what it overwrites, merges, or leaves alone |
141
- | `canon:index-lookup` | To find where a topic is documented across the tracked `index.md` catalogs |
142
- | `canon:youtube-transcripts` | When a video transcript is wanted in the repo as context |
143
- | `canon:read-frames` | To read a recorded demo back frame by frame and report what each one shows, with no verdict on whether the recording looks right |
144
- | `canon:teach-workspace` | To learn a subject across sessions, in a workspace that holds the progress |
145
- | `canon:write-human` | Before drafting or revising prose, for voice, rhythm, and density |
146
- | `canon:restate-plainly` | When an answer or a document has to be read again in plain words |
147
-
148
- Every row answers a question rather than marking a point in a project's life, so a phase above would send a reader to the wrong group.
149
-
150
- A learning workspace produces two halves and only one of them leaves. A lesson is worked through once and stays in the workspace, and a reference page or a glossary carries no learner, so it belongs wherever the project already keeps prose on that subject. Asking `canon:teach-workspace` to promote sorts each durable page by who owns its subject, sending an Anthropic-owned subject to the wiki, an internal one to the matching context entry, and everything else, subject-neutral material included, to the public docs. It proposes and waits, because a promoted page is public prose that needs a line naming who owns the subject, and it writes nothing to a destination: each page the operator confirms goes to a handoff file that `canon:context-fold` folds in from a branch. A project with no wiki folder gets a refusal naming `canon wiki init` rather than a folder it never asked for.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/canon",
3
3
  "type": "module",
4
- "version": "5.25.0",
4
+ "version": "5.26.0",
5
5
  "description": "Runs your agent's work and keeps its code good, on one setup for every repo.",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -11,7 +11,7 @@
11
11
  #
12
12
  # Three mechanisms sit here rather than in `sandbox/run.sh`, which is the
13
13
  # only caller. Each is reachable from a test that never launches a session:
14
- # `src/sandbox-dispatch.test.ts` drives the shim against a stub binary and the
14
+ # `src/sandbox/dispatch.test.ts` drives the shim against a stub binary and the
15
15
  # reap against a `sleep`, where proving either through `run.sh` would mean
16
16
  # spawning the thing the bound exists to prevent.
17
17
 
@@ -60,7 +60,7 @@ configure_sandbox_git_identity() {
60
60
  SANDBOX_ANCHOR_REPO="canon-sandbox"
61
61
 
62
62
  # A scenario calls this from its own use_anchor hook rather than this file
63
- # defining the hook. manage-sandbox.sh keys off `type -t use_anchor`, so
63
+ # defining the hook. sandbox-hook.sh keys off `type -t use_anchor`, so
64
64
  # declaring it here would hand an anchor to the scenarios that source this file
65
65
  # for the identity helpers alone.
66
66
  use_sandbox_anchor() {
@@ -112,14 +112,6 @@ ensure_sandbox_anchor_repo() {
112
112
  log_info "Created ${GITHUB_ORG}/${repo_name} as a private repository."
113
113
  }
114
114
 
115
- # Mirrors the merge-base resolution the diff-baseline port already carries
116
- # across five skill bodies, so a checkout whose local main trails origin/main
117
- # does not pull in skill bodies other merged branches changed.
118
- resolve_sandbox_skill_diff_base() {
119
- git -C "$PROJECT_ROOT" merge-base HEAD origin/main 2>/dev/null ||
120
- git -C "$PROJECT_ROOT" merge-base HEAD main 2>/dev/null
121
- }
122
-
123
115
  # A remote is useless without an author, so the scenarios that reach one always
124
116
  # configure both. configure_sandbox_git_identity stays callable on its own for
125
117
  # the scenarios that never push. The probe runs first so an absent remote is
@@ -13,7 +13,7 @@
13
13
  # Mints a short random per-run identifier the first time it is asked for, then
14
14
  # holds it in CANON_SANDBOX_RUN_ID for the rest of this process. A direct call
15
15
  # (not `$(...)`) exports it into the caller's own shell, which is what lets
16
- # `run.sh` mint once and have manage-sandbox.sh and the check it shells out to
16
+ # `run.sh` mint once and have the provisioning and the check it shells out to
17
17
  # both inherit the same id as ordinary children. A call already carrying the
18
18
  # variable, inherited from such a parent, reuses it rather than minting a new one.
19
19
  #
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env bash
2
+ # No `-u`. Scenarios ran under the retired dispatcher without it and read unset
3
+ # variables as empty, so turning it on here changes what they provision.
4
+ set -e
5
+ set -o pipefail
6
+
7
+ # Runs one scenario's hooks for the provisioning order in
8
+ # `src/sandbox/provision.ts`. Scenarios and the library they call stay bash, so
9
+ # the harness reaches them through this entry and nothing else.
10
+ #
11
+ # probe <scenario> runs `use_config` and `use_anchor` when declared
12
+ # and prints every export they changed, closed by an
13
+ # `@anchor=<0|1>` record
14
+ # stage <scenario> <file> re-runs both hooks, runs `stage_setup` inside the
15
+ # tree, and writes the exports it changed to <file>,
16
+ # or an `@exited` record when the shell exits first
17
+ #
18
+ # Pairs are NUL-separated `NAME=value`, since a value can hold a newline. The
19
+ # retired dispatcher ran `stage_setup` in its own shell, so an export, an `exit`,
20
+ # or an `exec` there reached the harness, and the report carries all three: an
21
+ # `exec` skips the EXIT trap and leaves no report at all. The stage re-runs the
22
+ # hooks rather than receiving the probe's pairs, since they only export.
23
+
24
+ SCRIPT_DIR="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd)"
25
+ PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
26
+ export PROJECT_ROOT
27
+
28
+ # Globals carry a prefix, since the scenario sourced below shares this shell.
29
+ _hook_die() {
30
+ echo "sandbox-hook: $1" >&2
31
+ exit 2
32
+ }
33
+ _hook_mode="${1:-}"
34
+ _hook_scenario="${2:-}"
35
+ _hook_report="${3:-}"
36
+
37
+ [[ "$_hook_mode" == "probe" || "$_hook_mode" == "stage" ]] || _hook_die "mode must be probe or stage, got: ${_hook_mode:-<empty>}"
38
+ [ -f "$_hook_scenario" ] || _hook_die "no scenario file at: ${_hook_scenario:-<empty>}"
39
+ [ "$_hook_mode" = "probe" ] || [ -n "$_hook_report" ] || _hook_die "stage needs a file to report its exports to"
40
+ [ "$_hook_mode" = "probe" ] || trap 'printf "@exited\0" >"$_hook_report"' EXIT
41
+
42
+ declare -A _hook_before
43
+ _hook_snapshot() {
44
+ local name
45
+ _hook_before=()
46
+ for name in $(compgen -e); do
47
+ _hook_before[$name]="${!name}"
48
+ done
49
+ }
50
+
51
+ # The shell moves PWD and OLDPWD itself on every `cd`, and neither is state.
52
+ _hook_changed() {
53
+ local name
54
+ for name in $(compgen -e); do
55
+ [[ "$name" == "PWD" || "$name" == "OLDPWD" ]] && continue
56
+ if [[ ! -v _hook_before[$name] ]] || [ "${_hook_before[$name]}" != "${!name}" ]; then
57
+ printf '%s=%s\0' "$name" "${!name}"
58
+ fi
59
+ done
60
+ }
61
+
62
+ # Taken before `config.sh`, so a `GITHUB_ORG` it derives from the remote reaches
63
+ # the harness as an export like any other.
64
+ _hook_snapshot
65
+
66
+ source "$PROJECT_ROOT/scripts/config.sh"
67
+ source "$PROJECT_ROOT/scripts/lib/ui.sh"
68
+ source "$PROJECT_ROOT/scripts/lib/sandbox-path.sh"
69
+ source "$PROJECT_ROOT/scripts/lib/sandbox-git.sh"
70
+ source "$PROJECT_ROOT/scripts/lib/sandbox-fixtures.sh"
71
+
72
+ # Unexported, as the dispatcher held them, so a scenario reads both while a
73
+ # child it spawns sees neither. Shellcheck cannot see the scenario reading them.
74
+ # shellcheck disable=SC2034
75
+ SANDBOX="$(resolve_sandbox_dir)" SANDBOX_DIR="$PROJECT_ROOT/sandbox"
76
+
77
+ # shellcheck source=/dev/null
78
+ source "$_hook_scenario"
79
+
80
+ _hook_declared() { [[ "$(type -t "$1")" == "function" ]]; }
81
+ _hook_run() {
82
+ if _hook_declared use_config; then use_config; fi
83
+ if _hook_declared use_anchor; then use_anchor; fi
84
+ }
85
+
86
+ if [ "$_hook_mode" = "probe" ]; then
87
+ # Stdout carries the pairs alone, so a hook that prints is moved off it.
88
+ _hook_run >&2
89
+ _hook_changed
90
+ if _hook_declared use_anchor; then printf '@anchor=1\0'; else printf '@anchor=0\0'; fi
91
+ exit 0
92
+ fi
93
+
94
+ _hook_run
95
+ _hook_snapshot
96
+ cd "$SANDBOX"
97
+ stage_setup
98
+ trap - EXIT
99
+ _hook_changed >"$_hook_report"
@@ -196,14 +196,6 @@ async function serveFor<T>(
196
196
  /** The element a composite is captured by, holding every frame and no chrome. */
197
197
  export const COMPOSITE_SELECTOR = '#canvas-composite'
198
198
 
199
- /**
200
- * The container holds no text, so its family exists only for the engine's
201
- * font probe, which rejects a family the machine lacks. The browser default is
202
- * Times New Roman, which a Linux runner often lacks, and `system-ui` is the one
203
- * every platform maps. Each frame keeps its own fonts inside its iframe.
204
- */
205
- const COMPOSITE_FONT = 'system-ui'
206
-
207
199
  export interface Placement {
208
200
  readonly frame: string
209
201
  readonly url: string
@@ -255,7 +247,7 @@ export function buildComposite(
255
247
  const html = [
256
248
  '<!doctype html>',
257
249
  '<html><head><meta charset="utf-8">',
258
- `<style>html,body{margin:0;background:transparent}#canvas-composite{position:relative;font-family:${COMPOSITE_FONT}}#canvas-composite iframe{position:absolute;display:block;border:0;background:transparent}</style>`,
250
+ `<style>html,body{margin:0;background:transparent}#canvas-composite{position:relative}#canvas-composite iframe{position:absolute;display:block;border:0;background:transparent}</style>`,
259
251
  '</head><body>',
260
252
  `<div id="canvas-composite" style="width:${width}px;height:${height}px">`,
261
253
  ...iframes,