@erclx/canon 5.25.0 → 5.27.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 (315) hide show
  1. package/README.md +4 -3
  2. package/claude/.claude-plugin/plugin.json +1 -1
  3. package/claude/skills/api-design/SKILL.md +2 -0
  4. package/claude/skills/auto-ship/SKILL.md +3 -11
  5. package/claude/skills/auto-ship/references/review-findings.md +20 -0
  6. package/claude/skills/backlog-triage/SKILL.md +2 -0
  7. package/claude/skills/bash-cli-script/SKILL.md +2 -0
  8. package/claude/skills/build-in-slices/SKILL.md +2 -0
  9. package/claude/skills/canon-cli/SKILL.md +2 -0
  10. package/claude/skills/canon-feedback/SKILL.md +2 -0
  11. package/claude/skills/canon-feedback-triage/SKILL.md +2 -0
  12. package/claude/skills/canon-operator/SKILL.md +2 -0
  13. package/claude/skills/canon-rollout/SKILL.md +3 -11
  14. package/claude/skills/canon-rollout/references/failure-recovery.md +20 -0
  15. package/claude/skills/canvas/SKILL.md +14 -1
  16. package/claude/skills/ci-workflow/SKILL.md +2 -0
  17. package/claude/skills/code-craft/SKILL.md +2 -0
  18. package/claude/skills/codebase-layout/SKILL.md +2 -0
  19. package/claude/skills/context-fold/REQUIREMENT.md +6 -2
  20. package/claude/skills/context-fold/SKILL.md +9 -21
  21. package/claude/skills/context-fold/references/anchor-sweep.md +2 -2
  22. package/claude/skills/context-fold/references/completion-report.md +26 -0
  23. package/claude/skills/create-rule/SKILL.md +2 -0
  24. package/claude/skills/create-skill/SKILL.md +2 -0
  25. package/claude/skills/create-standard/SKILL.md +2 -0
  26. package/claude/skills/decision-escalate/SKILL.md +2 -0
  27. package/claude/skills/deploy-app/SKILL.md +2 -0
  28. package/claude/skills/deprecation-migration/SKILL.md +2 -0
  29. package/claude/skills/design-extract/SKILL.md +2 -0
  30. package/claude/skills/design-taste/REQUIREMENT.md +1 -1
  31. package/claude/skills/design-taste/SKILL.md +6 -2
  32. package/claude/skills/design-taste/references/adopted.md +40 -0
  33. package/claude/skills/design-taste/references/kinds.md +1 -1
  34. package/claude/skills/design-taste/references/systems.md +1 -7
  35. package/claude/skills/design-taste/references/tells.md +2 -24
  36. package/claude/skills/docs-sync/REQUIREMENT.md +2 -1
  37. package/claude/skills/docs-sync/SKILL.md +2 -0
  38. package/claude/skills/document-health/REQUIREMENT.md +2 -0
  39. package/claude/skills/document-health/SKILL.md +23 -0
  40. package/claude/skills/document-health/references/axes.md +12 -0
  41. package/claude/skills/draft-and-pick/SKILL.md +3 -1
  42. package/claude/skills/draft-doc/SKILL.md +2 -0
  43. package/claude/skills/draft-doc/references/wiki.md +1 -1
  44. package/claude/skills/draft-figure/SKILL.md +2 -0
  45. package/claude/skills/draft-identity/SKILL.md +3 -1
  46. package/claude/skills/draft-ready/SKILL.md +2 -0
  47. package/claude/skills/draft-screencast/SKILL.md +11 -7
  48. package/claude/skills/draft-slides/REQUIREMENT.md +17 -12
  49. package/claude/skills/draft-slides/SKILL.md +33 -20
  50. package/claude/skills/git-branch/SKILL.md +2 -0
  51. package/claude/skills/git-commit/SKILL.md +2 -0
  52. package/claude/skills/git-followup/SKILL.md +2 -0
  53. package/claude/skills/git-issue/SKILL.md +2 -0
  54. package/claude/skills/git-issue/references/issue.md +10 -0
  55. package/claude/skills/git-pr/SKILL.md +2 -0
  56. package/claude/skills/git-pr/references/evidence.md +14 -6
  57. package/claude/skills/git-pr/references/labels.md +12 -0
  58. package/claude/skills/git-ship/SKILL.md +2 -0
  59. package/claude/skills/git-split/SKILL.md +21 -8
  60. package/claude/skills/git-stage/SKILL.md +2 -0
  61. package/claude/skills/git-worktree/SKILL.md +2 -0
  62. package/claude/skills/index-lookup/SKILL.md +2 -0
  63. package/claude/skills/markdown-propose/SKILL.md +2 -0
  64. package/claude/skills/markdown-propose/references/format.md +9 -0
  65. package/claude/skills/memory-capture/SKILL.md +2 -0
  66. package/claude/skills/memory-review/SKILL.md +2 -0
  67. package/claude/skills/memory-review/references/later-phases.md +7 -0
  68. package/claude/skills/plan-feature/SKILL.md +2 -0
  69. package/claude/skills/plan-groundwork/SKILL.md +2 -0
  70. package/claude/skills/plan-intake/SKILL.md +2 -0
  71. package/claude/skills/plan-intake-answer/SKILL.md +2 -0
  72. package/claude/skills/project-commands/SKILL.md +2 -0
  73. package/claude/skills/read-frames/SKILL.md +2 -0
  74. package/claude/skills/record-screencast/SKILL.md +2 -0
  75. package/claude/skills/repo-metadata/SKILL.md +2 -0
  76. package/claude/skills/restate-plainly/SKILL.md +2 -0
  77. package/claude/skills/review-address/SKILL.md +3 -7
  78. package/claude/skills/review-address/references/post-review-findings.md +16 -0
  79. package/claude/skills/review-branch/SKILL.md +2 -0
  80. package/claude/skills/review-craft/SKILL.md +2 -0
  81. package/claude/skills/review-craft/references/ui.md +2 -2
  82. package/claude/skills/review-pr/SKILL.md +4 -8
  83. package/claude/skills/review-pr/references/body-variants.md +52 -0
  84. package/claude/skills/review-pr/references/key-changes.md +16 -0
  85. package/claude/skills/review-pr/references/post.md +15 -43
  86. package/claude/skills/review-ui/SKILL.md +3 -1
  87. package/claude/skills/review-ui/references/post.md +4 -2
  88. package/claude/skills/role-orchestrator/SKILL.md +5 -14
  89. package/claude/skills/role-orchestrator/references/orchestrator-cloud-launch.md +8 -0
  90. package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +13 -0
  91. package/claude/skills/role-orchestrator/references/orchestrator-handback.md +1 -1
  92. package/claude/skills/role-orchestrator/references/orchestrator-inbound.md +23 -0
  93. package/claude/skills/role-orchestrator/references/orchestrator-launch.md +5 -33
  94. package/claude/skills/role-orchestrator/references/orchestrator-poll.md +1 -21
  95. package/claude/skills/role-orchestrator/references/orchestrator-review-dispatch.md +1 -1
  96. package/claude/skills/role-orchestrator/references/orchestrator-reviewer-launch.md +38 -0
  97. package/claude/skills/role-orchestrator/references/orchestrator-watch.md +26 -0
  98. package/claude/skills/role-planner/SKILL.md +2 -0
  99. package/claude/skills/role-reviewer/REQUIREMENT.md +1 -1
  100. package/claude/skills/role-reviewer/SKILL.md +2 -0
  101. package/claude/skills/role-worker/SKILL.md +2 -0
  102. package/claude/skills/search-craft/REQUIREMENT.md +1 -1
  103. package/claude/skills/search-craft/SKILL.md +5 -1
  104. package/claude/skills/search-craft/references/adopted.md +30 -0
  105. package/claude/skills/seed-sync/SKILL.md +2 -0
  106. package/claude/skills/session-compact/SKILL.md +2 -0
  107. package/claude/skills/session-map/SKILL.md +2 -0
  108. package/claude/skills/session-relay/SKILL.md +2 -0
  109. package/claude/skills/session-resume/SKILL.md +2 -0
  110. package/claude/skills/session-worktree/SKILL.md +3 -11
  111. package/claude/skills/session-worktree/references/install-command.md +20 -0
  112. package/claude/skills/sketch-design/SKILL.md +2 -0
  113. package/claude/skills/systematic-debugging/SKILL.md +2 -0
  114. package/claude/skills/target-check/SKILL.md +2 -0
  115. package/claude/skills/target-setup/SKILL.md +2 -0
  116. package/claude/skills/target-setup/references/detect.md +8 -0
  117. package/claude/skills/target-setup/references/indexes.md +13 -0
  118. package/claude/skills/target-setup/references/verify.md +8 -0
  119. package/claude/skills/task-board/SKILL.md +2 -0
  120. package/claude/skills/teach-workspace/SKILL.md +5 -15
  121. package/claude/skills/teach-workspace/references/lesson-build.md +75 -0
  122. package/claude/skills/teach-workspace/references/lesson-craft.md +13 -69
  123. package/claude/skills/teach-workspace/references/revisit.md +22 -0
  124. package/claude/skills/test-craft/SKILL.md +2 -0
  125. package/claude/skills/test-first/SKILL.md +4 -0
  126. package/claude/skills/test-first/references/adopted.md +36 -0
  127. package/claude/skills/ui-checklist/SKILL.md +2 -0
  128. package/claude/skills/ux-audit/SKILL.md +2 -0
  129. package/claude/skills/ux-measure/SKILL.md +2 -0
  130. package/claude/skills/ux-walkthrough/SKILL.md +2 -0
  131. package/claude/skills/video-craft/SKILL.md +2 -0
  132. package/claude/skills/write-human/SKILL.md +3 -1
  133. package/claude/skills/write-human/references/machine-tells.md +13 -0
  134. package/claude/skills/youtube-transcripts/SKILL.md +2 -0
  135. package/docs/agents/canvas.md +24 -6
  136. package/docs/agents/capture.md +6 -6
  137. package/docs/agents/commands.md +5 -5
  138. package/docs/agents/index.md +5 -5
  139. package/docs/agents/install-and-sync.md +7 -0
  140. package/docs/agents/key-changes.md +1 -1
  141. package/docs/agents/pr-evidence.md +38 -15
  142. package/docs/agents/pr-local.md +19 -13
  143. package/docs/agents/pr-preview.md +44 -21
  144. package/docs/agents/records-stale.md +22 -9
  145. package/docs/agents/records.md +1 -1
  146. package/docs/agents/sandbox.md +29 -5
  147. package/docs/agents/scripting.md +8 -1
  148. package/docs/agents/skills-audit.md +11 -3
  149. package/docs/agents/slides.md +15 -3
  150. package/docs/workflow/ai-workflow.md +1 -1
  151. package/docs/workflow/index.md +1 -0
  152. package/docs/workflow/operating-model.md +5 -6
  153. package/docs/workflow/parallel-features.md +23 -2
  154. package/docs/workflow/skill-map-on-request.md +42 -0
  155. package/docs/workflow/skill-map.md +3 -34
  156. package/docs/workflow/zshrc-aliases.md +1 -1
  157. package/package.json +1 -1
  158. package/scripts/core/regen-hero.ts +1 -1
  159. package/scripts/lib/sandbox-git.sh +1 -9
  160. package/scripts/lib/sandbox-path.sh +6 -162
  161. package/scripts/sandbox-hook.sh +99 -0
  162. package/src/audits/run.ts +1 -1
  163. package/src/canvas/address.ts +12 -3
  164. package/src/canvas/capture.ts +1 -9
  165. package/src/canvas/client/inspector/align-grid.tsx +162 -0
  166. package/src/canvas/client/inspector/color-field.tsx +398 -0
  167. package/src/canvas/client/inspector/color-inputs.tsx +139 -0
  168. package/src/canvas/client/inspector/color-picker.tsx +668 -0
  169. package/src/canvas/client/inspector/color-space.ts +240 -0
  170. package/src/canvas/client/inspector/field.tsx +5 -1
  171. package/src/canvas/client/inspector/fields.css +547 -17
  172. package/src/canvas/client/inspector/size-field.tsx +69 -0
  173. package/src/canvas/client/inspector/values.ts +166 -1
  174. package/src/canvas/client/inspector.tsx +236 -73
  175. package/src/canvas/client/selection.tsx +197 -0
  176. package/src/canvas/client/shell.css +104 -12
  177. package/src/canvas/client/state.ts +69 -9
  178. package/src/canvas/client/surface.tsx +147 -22
  179. package/src/canvas/content.ts +50 -1
  180. package/src/canvas/edit.ts +37 -4
  181. package/src/canvas/editing.ts +203 -0
  182. package/src/canvas/global.d.ts +15 -0
  183. package/src/canvas/server.ts +149 -12
  184. package/src/canvas/tokens.ts +59 -17
  185. package/src/capture/render.ts +45 -7
  186. package/src/capture/sources.ts +43 -0
  187. package/src/census/count.ts +2 -2
  188. package/src/claude/cases/workflow.ts +5 -0
  189. package/src/claude/seeds.ts +2 -2
  190. package/src/claude/skills-audit.ts +96 -14
  191. package/src/claude/skills-drift.ts +1 -1
  192. package/src/claude/skills-families.ts +36 -0
  193. package/src/claude/skills-list.ts +76 -15
  194. package/src/commands/audits.ts +2 -2
  195. package/src/commands/canvas.ts +179 -2
  196. package/src/commands/claude.ts +94 -11
  197. package/src/commands/design.ts +142 -2
  198. package/src/commands/feedback.ts +3 -3
  199. package/src/commands/gov.ts +1 -1
  200. package/src/commands/hooks.ts +1 -1
  201. package/src/commands/indexes.ts +1 -1
  202. package/src/commands/init.ts +1 -1
  203. package/src/commands/intake.ts +1 -1
  204. package/src/commands/migrate.ts +1 -1
  205. package/src/commands/pr.ts +105 -58
  206. package/src/commands/records.ts +89 -7
  207. package/src/commands/repo.ts +1 -1
  208. package/src/commands/sandbox.ts +103 -11
  209. package/src/commands/sessions.ts +1 -1
  210. package/src/commands/slides.ts +31 -77
  211. package/src/commands/sync.ts +1 -1
  212. package/src/commands/tasks.ts +2 -2
  213. package/src/commands/teach.ts +1 -1
  214. package/src/commands/transcripts.ts +1 -1
  215. package/src/commands/worktrees.ts +1 -1
  216. package/src/comments/scan.ts +1 -1
  217. package/src/comments/trend.ts +1 -1
  218. package/src/context/audit.ts +1 -1
  219. package/src/context/citations.ts +1 -1
  220. package/src/context/classify/extract.ts +2 -2
  221. package/src/design/project-fonts.ts +392 -0
  222. package/src/design/tokens.ts +2 -18
  223. package/src/docs/read.ts +1 -1
  224. package/src/{github.ts → feedback/github.ts} +2 -2
  225. package/src/{client-commands.ts → gate/client-commands.ts} +1 -1
  226. package/src/gate/measures.ts +53 -13
  227. package/src/gate/sequencer.ts +1 -1
  228. package/src/gate/stages.ts +9 -0
  229. package/src/{git-files.ts → git/files.ts} +2 -2
  230. package/src/{git-ignore.ts → git/ignore.ts} +1 -1
  231. package/src/{worktree.ts → git/worktree.ts} +1 -1
  232. package/src/gov/citations.ts +2 -2
  233. package/src/gov/install.ts +1 -1
  234. package/src/gov/payload.ts +1 -1
  235. package/src/gov/superseded.ts +3 -3
  236. package/src/gov/test-order.ts +2 -2
  237. package/src/help.ts +1 -1
  238. package/src/hooks/post-merge.ts +1 -1
  239. package/src/indexes/walk.ts +1 -1
  240. package/src/labels/audit.ts +1 -1
  241. package/src/markdown/ceiling.ts +1 -1
  242. package/src/markdown/files.ts +1 -1
  243. package/src/migrate/apply.ts +1 -1
  244. package/src/pr/bijection.ts +1 -1
  245. package/src/pr/evidence.ts +96 -43
  246. package/src/pr/local.ts +16 -10
  247. package/src/pr/preview.ts +78 -2
  248. package/src/record-root.ts +1 -0
  249. package/src/records/backup.ts +2 -1
  250. package/src/records/push-guard.ts +1 -1
  251. package/src/records/stale-canonical.ts +146 -0
  252. package/src/records/stale.ts +1 -1
  253. package/src/sandbox/headless/dispatch.ts +264 -0
  254. package/src/sandbox/headless/run.ts +653 -0
  255. package/src/sandbox/headless/watch.ts +145 -0
  256. package/src/sandbox/hooks.ts +128 -0
  257. package/src/sandbox/provision.ts +607 -0
  258. package/src/sandbox/tree.ts +99 -4
  259. package/src/secrets/marker.ts +2 -2
  260. package/src/secrets/scan.ts +2 -2
  261. package/src/sessions/claim.ts +1 -1
  262. package/src/sessions/resolve.ts +1 -1
  263. package/src/sessions/transfer/bundle.ts +1 -1
  264. package/src/shipped/references.ts +2 -2
  265. package/src/slides/convert/export.ts +2 -16
  266. package/src/slides/decks.ts +110 -0
  267. package/src/slides/package/fonts.ts +6 -2
  268. package/src/standards/audit.ts +2 -2
  269. package/src/standards/read.ts +1 -1
  270. package/src/sync/check.ts +2 -2
  271. package/src/sync/engine.ts +2 -2
  272. package/src/sync/git.ts +1 -1
  273. package/src/sync/history.ts +1 -1
  274. package/src/sync/reverse.ts +1 -1
  275. package/src/sync/seeds-report.ts +1 -1
  276. package/src/sync/target.ts +1 -1
  277. package/src/sync/workflow.ts +1 -1
  278. package/src/targets/pulls.ts +2 -2
  279. package/src/targets/sweep.ts +2 -2
  280. package/src/tasks/answers.ts +1 -1
  281. package/src/tasks/archive.ts +1 -1
  282. package/src/tasks/reach.ts +1 -1
  283. package/src/tasks/trunk.ts +1 -1
  284. package/src/teach/nav.ts +1 -1
  285. package/src/tooling/inject.ts +2 -2
  286. package/src/tooling/subfolder.ts +1 -1
  287. package/src/web/readme-citations.ts +2 -2
  288. package/src/worktrees/bare-flag.ts +1 -1
  289. package/src/worktrees/reclaim.ts +2 -2
  290. package/src/worktrees/remove.ts +1 -1
  291. package/standards/architecture.md +1 -0
  292. package/standards/canvas.md +3 -2
  293. package/standards/requirements.md +2 -0
  294. package/standards/skill-practice.md +2 -1
  295. package/standards/skill.md +2 -0
  296. package/standards/wiki.md +12 -3
  297. package/tooling/base/configs/.github/workflows/pr-local-preview.yml +2 -2
  298. package/scripts/lib/sandbox-dispatch.sh +0 -190
  299. package/scripts/manage-sandbox.sh +0 -472
  300. package/src/slides/layouts.ts +0 -478
  301. package/src/slides/parse.ts +0 -84
  302. package/src/slides/render.ts +0 -110
  303. package/src/slides/styles.ts +0 -96
  304. /package/claude/skills/write-human/references/{source-material.md → adopted.md} +0 -0
  305. /package/src/{commands/feedback-format.ts → feedback/body.ts} +0 -0
  306. /package/src/{github-format.ts → feedback/github-format.ts} +0 -0
  307. /package/src/{git-base.ts → git/base.ts} +0 -0
  308. /package/src/{git-env.ts → git/env.ts} +0 -0
  309. /package/src/{frontmatter.ts → markdown/frontmatter.ts} +0 -0
  310. /package/src/{binary.ts → sweep/binary.ts} +0 -0
  311. /package/src/{exempt-marker.ts → sweep/exempt-marker.ts} +0 -0
  312. /package/src/{copy.ts → sync/copy.ts} +0 -0
  313. /package/src/{target.ts → targets/validate.ts} +0 -0
  314. /package/src/{paths.ts → tasks/paths.ts} +0 -0
  315. /package/src/{seed-marker.ts → tooling/seed-marker.ts} +0 -0
@@ -1,3 +1,4 @@
1
+ import { spawnSync } from 'node:child_process'
1
2
  import { randomBytes } from 'node:crypto'
2
3
  import { homedir } from 'node:os'
3
4
  import { join } from 'node:path'
@@ -5,10 +6,11 @@ import { join } from 'node:path'
5
6
  /**
6
7
  * Mints a short per-run identifier the first time it is asked for and holds it
7
8
  * in `CANON_SANDBOX_RUN_ID` for the rest of this process, so a script that
8
- * spawns a child inheriting `process.env` — `run.sh` calling `manage-sandbox.sh`
9
- * and then `canon sandbox check` — resolves the same tree in every one of them.
10
- * A process that already carries the variable, inherited from such a parent,
11
- * reuses it rather than minting a new one.
9
+ * spawns a child inheriting `process.env` — `canon sandbox run` calling
10
+ * `canon sandbox` to provision and then `canon sandbox check` — resolves the same tree in every one
11
+ * of them, as do the hook and installer children provisioning spawns. A process
12
+ * that already carries the variable, inherited from such a parent, reuses it
13
+ * rather than minting a new one.
12
14
  *
13
15
  * Twin of `mint_sandbox_run_id` in `scripts/lib/sandbox-path.sh`.
14
16
  */
@@ -45,3 +47,96 @@ export function sandboxTree(): string {
45
47
 
46
48
  return join(base, 'canon', `sandbox-${mintSandboxRunId()}`)
47
49
  }
50
+
51
+ /**
52
+ * Lexical rather than `realpath`, since the guard runs before the tree exists,
53
+ * so nothing here follows a symlink.
54
+ */
55
+ export function normalizeSandboxPath(path: string): string {
56
+ if (path === '') return ''
57
+
58
+ const isAbsolute = path.startsWith('/')
59
+ const resolved: string[] = []
60
+ let climbed = ''
61
+
62
+ for (const segment of path.split('/')) {
63
+ if (segment === '' || segment === '.') continue
64
+ if (segment !== '..') resolved.push(segment)
65
+ else if (resolved.length > 0) resolved.pop()
66
+ else if (!isAbsolute) climbed += '../'
67
+ }
68
+
69
+ if (isAbsolute) return `/${resolved.join('/')}`
70
+
71
+ const relative = `${climbed}${resolved.join('/')}`.replace(/\/+$/, '')
72
+
73
+ return relative === '' ? '.' : relative
74
+ }
75
+
76
+ /** Whether `candidate` is `target` or a directory containing it. */
77
+ function isAtOrAbove(candidate: string, target: string): boolean {
78
+ return candidate === target || target.startsWith(`${candidate}/`)
79
+ }
80
+
81
+ export interface SandboxRoots {
82
+ readonly home: string
83
+ readonly temp: string
84
+ readonly mainRoot: string
85
+ }
86
+
87
+ /**
88
+ * The roots the guard measures against. The repository test reads the main
89
+ * worktree rather than `PROJECT_ROOT`, since a linked worktree sits inside it.
90
+ */
91
+ export function sandboxRoots(projectRoot: string): SandboxRoots {
92
+ const listing = spawnSync('git', ['worktree', 'list', '--porcelain'], {
93
+ cwd: projectRoot,
94
+ encoding: 'utf8',
95
+ })
96
+ const line = (listing.stdout ?? '')
97
+ .split('\n')
98
+ .find((l) => l.startsWith('worktree '))
99
+
100
+ return {
101
+ home: process.env.HOME || '/root',
102
+ temp: process.env.TMPDIR || '/tmp',
103
+ mainRoot: line === undefined ? projectRoot : line.slice('worktree '.length),
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Returns the reason a location cannot hold the tree, or undefined when it can.
109
+ * Provisioning removes the tree before staging, so the test is an allowlist: a
110
+ * strict descendant of the home folder or the temp root, outside the main
111
+ * worktree in both directions.
112
+ */
113
+ export function assertSandboxDirSafe(
114
+ raw: string,
115
+ roots: SandboxRoots,
116
+ ): string | undefined {
117
+ if (!raw.startsWith('/'))
118
+ return `CANON_SANDBOX_DIR must be an absolute path, got: ${raw || '<empty>'}`
119
+
120
+ if (raw.length > 4096)
121
+ return `Refusing the sandbox path. It is ${raw.length} characters, past the longest path any filesystem here accepts.`
122
+
123
+ const dir = normalizeSandboxPath(raw)
124
+ const home = normalizeSandboxPath(roots.home)
125
+ const temp = normalizeSandboxPath(roots.temp)
126
+ const mainRoot = normalizeSandboxPath(roots.mainRoot)
127
+ const resolution = dir === raw ? '' : ` It resolves to ${dir}.`
128
+
129
+ if (!isAtOrAbove(home, dir) && !isAtOrAbove(temp, dir))
130
+ return `Refusing ${raw} as the sandbox. Provisioning removes the tree first, so the path has to sit under ${home} or ${temp}.${resolution}`
131
+
132
+ if (dir === home || dir === temp)
133
+ return `Refusing ${raw} as the sandbox. Provisioning removes the tree first, so the path has to sit under ${dir} rather than be it.`
134
+
135
+ if (isAtOrAbove(dir, mainRoot))
136
+ return `Refusing ${raw} as the sandbox. Provisioning removes the tree first, and that path contains ${mainRoot}.${resolution}`
137
+
138
+ if (dir.startsWith(`${mainRoot}/`))
139
+ return `Sandbox at ${raw} sits inside ${mainRoot}, which puts the toolkit CLAUDE.md back on the session ancestor chain.${resolution} Point CANON_SANDBOX_DIR outside the repository.`
140
+
141
+ return undefined
142
+ }
@@ -1,9 +1,9 @@
1
- import { isMarked } from '@/exempt-marker'
1
+ import { isMarked } from '@/sweep/exempt-marker'
2
2
 
3
3
  /**
4
4
  * The inline token exempting one line from the secret scan.
5
5
  *
6
- * Shaped on the `stub: true` precedent in `src/seed-marker.ts`, which answers
6
+ * Shaped on the `stub: true` precedent in `src/tooling/seed-marker.ts`, which answers
7
7
  * a check whose own comment records a false-positive class. The exemption
8
8
  * travels with the line it exempts rather than sitting in a path list away
9
9
  * from it, so a reader meeting a muted match finds the reason on the spot.
@@ -1,7 +1,7 @@
1
1
  import { readFile } from 'node:fs/promises'
2
2
  import { join } from 'node:path'
3
- import { isBinary } from '@/binary'
4
- import { listRepositoryFiles } from '@/git-files'
3
+ import { isBinary } from '@/sweep/binary'
4
+ import { listRepositoryFiles } from '@/git/files'
5
5
  import { isExempt } from '@/secrets/marker'
6
6
  import { matchLine } from '@/secrets/patterns'
7
7
  import { readShipEntries, selectShipped } from '@/secrets/shipped'
@@ -9,7 +9,7 @@ import {
9
9
  listWorktrees,
10
10
  type RefReport,
11
11
  type WorktreeEntry,
12
- } from '@/worktree'
12
+ } from '@/git/worktree'
13
13
 
14
14
  export interface ClaimReport {
15
15
  readonly claimed: boolean
@@ -1,6 +1,6 @@
1
1
  import { basename } from 'node:path'
2
2
  import { $ } from 'bun'
3
- import { gitEnv } from '@/git-env'
3
+ import { gitEnv } from '@/git/env'
4
4
  import {
5
5
  type Confidence,
6
6
  liveness,
@@ -10,7 +10,7 @@ import {
10
10
  } from 'node:fs'
11
11
  import { basename, dirname, isAbsolute, join, relative } from 'node:path'
12
12
  import { $ } from 'bun'
13
- import { gitEnv } from '@/git-env'
13
+ import { gitEnv } from '@/git/env'
14
14
  import { recordDir, recordRoot, SCRATCH } from '@/record-root'
15
15
  import { remoteIdentity } from '@/records/backup'
16
16
  import {
@@ -1,6 +1,6 @@
1
1
  import { existsSync } from 'node:fs'
2
2
  import { join } from 'node:path'
3
- import { isMarked } from '@/exempt-marker'
3
+ import { isMarked } from '@/sweep/exempt-marker'
4
4
 
5
5
  /**
6
6
  * The corpora a target reader reaches, which is the `files` field less `src`.
@@ -113,7 +113,7 @@ const COMMIT_SHA = /(?<![0-9A-Za-z_@/#])([0-9a-f]{7,40})(?![0-9A-Za-z_])/g
113
113
  * one.
114
114
  *
115
115
  * The literal is hardcoded rather than read from `package.json` or
116
- * `git remote`, matching `src/github-format.ts` and `src/commands/repo.ts`,
116
+ * `git remote`, matching `src/feedback/github-format.ts` and `src/commands/repo.ts`,
117
117
  * which already hardcode it.
118
118
  *
119
119
  * Same word-boundary discipline as the two patterns above: a word character
@@ -1,10 +1,4 @@
1
- import {
2
- copyFileSync,
3
- mkdirSync,
4
- readdirSync,
5
- readFileSync,
6
- writeFileSync,
7
- } from 'node:fs'
1
+ import { copyFileSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
8
2
  import { basename, join } from 'node:path'
9
3
  import { fileURLToPath, pathToFileURL } from 'node:url'
10
4
  import JSZip from 'jszip'
@@ -42,6 +36,7 @@ import {
42
36
  type WalkedSlide,
43
37
  walkSlide,
44
38
  } from '@/slides/convert/walk'
39
+ import { slideFiles } from '@/slides/decks'
45
40
  import { embedFonts, type FontNotice } from '@/slides/package/fonts'
46
41
  import {
47
42
  type EntranceSpec,
@@ -127,15 +122,6 @@ function refused(reason: ExportRefusal, message: string): ExportResult {
127
122
  return { status: 'refused', reason, message }
128
123
  }
129
124
 
130
- /** Every `.html` file directly in the folder, in filename order. */
131
- export function slideFiles(sourceDir: string): string[] {
132
- return readdirSync(sourceDir, { withFileTypes: true })
133
- .filter((entry) => entry.isFile() && entry.name.endsWith('.html'))
134
- .map((entry) => entry.name)
135
- .sort((a, b) => a.localeCompare(b, undefined, { numeric: true }))
136
- .map((name) => join(sourceDir, name))
137
- }
138
-
139
125
  /** A picture whose bytes the post-write pass replaces or whose corners it rounds. */
140
126
  interface Patch {
141
127
  readonly slide: number
@@ -0,0 +1,110 @@
1
+ import { existsSync, readdirSync, statSync } from 'node:fs'
2
+ import { join, relative, resolve } from 'node:path'
3
+ import { creationRel, recordDir } from '@/record-root'
4
+ import { readDeck } from '@/slides/convert/deck'
5
+
6
+ /**
7
+ * A deck is a folder under the record root's `slides/`, holding its `.html`
8
+ * slides, an optional `deck.json`, and its assets. `layouts/` beside the decks
9
+ * holds the project's shared layouts and is never a deck itself.
10
+ */
11
+ export const SLIDES_FOLDER = 'slides'
12
+
13
+ export const LAYOUTS_FOLDER = 'layouts'
14
+
15
+ export interface Deck {
16
+ readonly name: string
17
+ /** From `deck.json`, falling back to the folder name as the export does. */
18
+ readonly title: string
19
+ readonly slides: number
20
+ /** Relative to the project root. */
21
+ readonly path: string
22
+ }
23
+
24
+ export type DeckRefusal = 'no-decks' | 'ambiguous' | 'no-deck'
25
+
26
+ export type DeckResolution =
27
+ | { readonly status: 'resolved'; readonly dir: string }
28
+ | {
29
+ readonly status: 'refused'
30
+ readonly reason: DeckRefusal
31
+ readonly message: string
32
+ }
33
+
34
+ /** Every `.html` file directly in the folder, in filename order. */
35
+ export function slideFiles(dir: string): string[] {
36
+ return readdirSync(dir, { withFileTypes: true })
37
+ .filter((entry) => entry.isFile() && entry.name.endsWith('.html'))
38
+ .map((entry) => entry.name)
39
+ .sort((a, b) => a.localeCompare(b, undefined, { numeric: true }))
40
+ .map((name) => join(dir, name))
41
+ }
42
+
43
+ export function listDecks(root: string): Deck[] {
44
+ const dir = recordDir(root, SLIDES_FOLDER)
45
+ if (!isDirectory(dir)) return []
46
+
47
+ return readdirSync(dir, { withFileTypes: true })
48
+ .filter((entry) => entry.isDirectory() && entry.name !== LAYOUTS_FOLDER)
49
+ .map((entry) => entry.name)
50
+ .sort((a, b) => a.localeCompare(b))
51
+ .flatMap((name) => {
52
+ const deckDir = join(dir, name)
53
+ const slides = slideFiles(deckDir).length
54
+ if (slides === 0) return []
55
+ const read = readDeck(deckDir)
56
+ const title = read.status === 'read' ? read.deck.title : name
57
+ return [{ name, title, slides, path: relative(root, deckDir) }]
58
+ })
59
+ }
60
+
61
+ /**
62
+ * The folder `render` reads. A deck name wins over a folder of the same name
63
+ * relative to the root, and no name at all takes the only deck there is.
64
+ */
65
+ export function resolveDeck(
66
+ root: string,
67
+ target: string | undefined,
68
+ ): DeckResolution {
69
+ const decks = listDecks(root)
70
+ const at = creationRel(root, SLIDES_FOLDER)
71
+
72
+ if (target === undefined) {
73
+ const [only, ...rest] = decks
74
+ if (only === undefined) {
75
+ return refuse('no-decks', `No deck under ${at}/. Draw one there first.`)
76
+ }
77
+ if (rest.length > 0) {
78
+ return refuse(
79
+ 'ambiguous',
80
+ `${decks.length} decks under ${at}/, name one: ${names(decks)}`,
81
+ )
82
+ }
83
+ return { status: 'resolved', dir: join(root, only.path) }
84
+ }
85
+
86
+ const named = decks.find((deck) => deck.name === target)
87
+ if (named) return { status: 'resolved', dir: join(root, named.path) }
88
+
89
+ const folder = resolve(root, target)
90
+ if (isDirectory(folder)) return { status: 'resolved', dir: folder }
91
+
92
+ return refuse(
93
+ 'no-deck',
94
+ decks.length === 0
95
+ ? `${target} is neither a deck under ${at}/ nor a folder`
96
+ : `${target} is neither a deck nor a folder. Decks: ${names(decks)}`,
97
+ )
98
+ }
99
+
100
+ function names(decks: readonly Deck[]): string {
101
+ return decks.map((deck) => deck.name).join(', ')
102
+ }
103
+
104
+ function refuse(reason: DeckRefusal, message: string): DeckResolution {
105
+ return { status: 'refused', reason, message }
106
+ }
107
+
108
+ function isDirectory(path: string): boolean {
109
+ return existsSync(path) && statSync(path).isDirectory()
110
+ }
@@ -152,9 +152,13 @@ export function eotOf(bytes: Uint8Array): EotResult {
152
152
  }
153
153
  const strings = names(font, name)
154
154
  const padding = Buffer.alloc(2)
155
- // RootStringCheckSum, EUDCCodePage, Padding6, SignatureSize, EUDCFlags,
156
- // and EUDCFontSize, all zero for a face with no root string or EUDC data.
155
+ // RootStringCheckSum, then EUDCCodePage, Padding6, SignatureSize,
156
+ // EUDCFlags, and EUDCFontSize, zero for a face with no EUDC data. The
157
+ // checksum is the root string's byte sum XOR 0x50475342, so an empty root
158
+ // string still writes the constant, and PowerPoint refuses to install a
159
+ // face whose checksum is zero as tampered with.
157
160
  const tail = Buffer.alloc(20)
161
+ tail.writeUInt32LE(0x50475342, 0)
158
162
  const eot = Buffer.concat([
159
163
  header(font, os2.offset, head.offset),
160
164
  padding,
@@ -1,8 +1,8 @@
1
1
  import { existsSync, readFileSync, readdirSync } from 'node:fs'
2
2
  import { basename, join } from 'node:path'
3
3
  import { $ } from 'bun'
4
- import { gitEnv } from '@/git-env'
5
- import { resolveBaseRef } from '@/git-files'
4
+ import { gitEnv } from '@/git/env'
5
+ import { resolveBaseRef } from '@/git/files'
6
6
  import { INDEX_FILE, standardsSourceDir } from '@/standards/read'
7
7
 
8
8
  /** Returned when a standard new to this branch states no success criterion under either heading `CRITERION_HEADING` accepts, the gating check. */
@@ -1,6 +1,6 @@
1
1
  import { existsSync, readdirSync, readFileSync } from 'node:fs'
2
2
  import { basename, join } from 'node:path'
3
- import { stripFrontmatter } from '@/frontmatter'
3
+ import { stripFrontmatter } from '@/markdown/frontmatter'
4
4
  import { PROJECT_ROOT } from '@/project-root'
5
5
 
6
6
  export const INDEX_FILE = 'index.md'
package/src/sync/check.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { existsSync } from 'node:fs'
2
2
  import { basename, join, sep } from 'node:path'
3
3
  import { execa } from 'execa'
4
- import { gitEnv } from '@/git-env'
4
+ import { gitEnv } from '@/git/env'
5
5
  import { createDesignAdapter, DESIGN_INSTALL_DIR } from '@/design/adapter'
6
6
  import { createGovAdapter, rulesSourceDir } from '@/gov/adapter'
7
7
  import { successorResolver } from '@/gov/renames'
@@ -29,7 +29,7 @@ import {
29
29
  stampedCommit,
30
30
  type StampDomain,
31
31
  } from '@/sync/stamp'
32
- import { isDirectory } from '@/target'
32
+ import { isDirectory } from '@/targets/validate'
33
33
  import {
34
34
  isWholeStack,
35
35
  loadManifest,
@@ -1,7 +1,7 @@
1
1
  import { existsSync, readFileSync } from 'node:fs'
2
2
  import { rm } from 'node:fs/promises'
3
3
  import { isAbsolute, relative, resolve } from 'node:path'
4
- import { copyPreservingMode } from '@/copy'
4
+ import { copyPreservingMode } from '@/sync/copy'
5
5
  import { checkoutMismatchWarning } from '@/project-root'
6
6
  import { findInstalledOrigin, readHistoryIndex } from '@/sync/history'
7
7
  import {
@@ -16,7 +16,7 @@ import {
16
16
  toStampKey,
17
17
  writeStamp,
18
18
  } from '@/sync/stamp'
19
- import { isDirectory } from '@/target'
19
+ import { isDirectory } from '@/targets/validate'
20
20
  import {
21
21
  intro,
22
22
  isNonInteractive,
package/src/sync/git.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { basename } from 'node:path'
2
2
  import { execa } from 'execa'
3
- import { gitEnv } from '@/git-env'
3
+ import { gitEnv } from '@/git/env'
4
4
  import type { SyncDomain } from '@/sync/target'
5
5
  import { pipeOutput } from '@/ui'
6
6
 
@@ -2,7 +2,7 @@ import { createHash } from 'node:crypto'
2
2
  import { readFileSync } from 'node:fs'
3
3
  import { sep } from 'node:path'
4
4
  import { execaSync } from 'execa'
5
- import { gitEnv } from '@/git-env'
5
+ import { gitEnv } from '@/git/env'
6
6
 
7
7
  /** Blob SHA to the newest commit whose post-image for that path held it. */
8
8
  export type PathHistory = ReadonlyMap<string, string>
@@ -1,7 +1,7 @@
1
1
  import { existsSync, statSync } from 'node:fs'
2
2
  import { join } from 'node:path'
3
3
  import { execaSync } from 'execa'
4
- import { gitEnv } from '@/git-env'
4
+ import { gitEnv } from '@/git/env'
5
5
  import {
6
6
  findInstalledOrigin,
7
7
  type HistoryIndex,
@@ -1,7 +1,7 @@
1
1
  import { readFileSync } from 'node:fs'
2
2
  import { relative } from 'node:path'
3
3
  import { planSeeds, type Seed } from '@/claude/seeds'
4
- import { rewritesOnInstall, stripSeedMarker } from '@/seed-marker'
4
+ import { rewritesOnInstall, stripSeedMarker } from '@/tooling/seed-marker'
5
5
  import { findInstalledOrigin, readHistoryIndex } from '@/sync/history'
6
6
 
7
7
  /**
@@ -1,7 +1,7 @@
1
1
  import { existsSync } from 'node:fs'
2
2
  import { join } from 'node:path'
3
3
  import { DESIGN_INSTALL_DIR } from '@/design/adapter'
4
- import { isDirectory } from '@/target'
4
+ import { isDirectory } from '@/targets/validate'
5
5
 
6
6
  export const SYNC_DOMAINS = ['governance', 'design', 'claude'] as const
7
7
 
@@ -11,7 +11,7 @@ import {
11
11
  syncBranchName,
12
12
  } from '@/sync/git'
13
13
  import { domainPaths, SYNC_DOMAINS } from '@/sync/target'
14
- import { isDirectory } from '@/target'
14
+ import { isDirectory } from '@/targets/validate'
15
15
  import {
16
16
  intro,
17
17
  logError,
@@ -1,6 +1,6 @@
1
1
  import { execa } from 'execa'
2
- import { gitEnv } from '@/git-env'
3
- import { isDirectory } from '@/target'
2
+ import { gitEnv } from '@/git/env'
3
+ import { isDirectory } from '@/targets/validate'
4
4
 
5
5
  const GH_TIMEOUT_MS = 30_000
6
6
 
@@ -1,7 +1,7 @@
1
1
  import { readdirSync } from 'node:fs'
2
2
  import { join, resolve } from 'node:path'
3
3
  import { $ } from 'bun'
4
- import { gitEnv } from '@/git-env'
4
+ import { gitEnv } from '@/git/env'
5
5
  import {
6
6
  claudeCanonStampPath,
7
7
  isLegacyStamped,
@@ -9,7 +9,7 @@ import {
9
9
  retiredNameStampPath,
10
10
  stampPath,
11
11
  } from '@/sync/stamp'
12
- import { isDirectory } from '@/target'
12
+ import { isDirectory } from '@/targets/validate'
13
13
 
14
14
  /**
15
15
  * Folders a walk never descends into.
@@ -1,7 +1,7 @@
1
1
  import { existsSync } from 'node:fs'
2
2
  import { readFile } from 'node:fs/promises'
3
3
  import { isAbsolute, relative, resolve } from 'node:path'
4
- import { isUnder } from '@/paths'
4
+ import { isUnder } from '@/tasks/paths'
5
5
  import { recordDir, recordDirs } from '@/record-root'
6
6
  import {
7
7
  hasStagedBatches,
@@ -2,7 +2,7 @@ import { existsSync } from 'node:fs'
2
2
  import { mkdir, readdir, readFile, rename, writeFile } from 'node:fs/promises'
3
3
  import { basename, dirname, join, relative, resolve, sep } from 'node:path'
4
4
  import { regenOne } from '@/indexes/regen'
5
- import { isUnder } from '@/paths'
5
+ import { isUnder } from '@/tasks/paths'
6
6
  import { recordDir, recordDirs } from '@/record-root'
7
7
 
8
8
  const TASKS = 'tasks'
@@ -1,7 +1,7 @@
1
1
  import { existsSync } from 'node:fs'
2
2
  import { readdir, readFile } from 'node:fs/promises'
3
3
  import { basename, join } from 'node:path'
4
- import { listChangedFiles, resolveBaseRef } from '@/git-files'
4
+ import { listChangedFiles, resolveBaseRef } from '@/git/files'
5
5
  import { recordDir } from '@/record-root'
6
6
  import { splitPlanSections } from '@/records/validate'
7
7
  import { type AnswersRefused, resolvePlanReference } from '@/tasks/answers'
@@ -1,5 +1,5 @@
1
1
  import { execa } from 'execa'
2
- import { gitEnv } from '@/git-env'
2
+ import { gitEnv } from '@/git/env'
3
3
 
4
4
  const GIT_TIMEOUT_MS = 10_000
5
5
 
package/src/teach/nav.ts CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  } from '@/design/components'
8
8
  import { buildDesignCss } from '@/design/css'
9
9
  import { FAVICON_COLORS, faviconLink, renderFavicon } from '@/design/favicon'
10
- import { stripFrontmatter } from '@/frontmatter'
10
+ import { stripFrontmatter } from '@/markdown/frontmatter'
11
11
  import { parseFrontmatter, readField } from '@/indexes/frontmatter'
12
12
  import { PROJECT_ROOT } from '@/project-root'
13
13
  import { compileScript } from '@/teach/browser/compile'
@@ -2,8 +2,8 @@ import { existsSync } from 'node:fs'
2
2
  import { copyFile, mkdir, readFile, writeFile } from 'node:fs/promises'
3
3
  import { dirname, join, relative } from 'node:path'
4
4
  import { $ } from 'bun'
5
- import { copyPreservingMode } from '@/copy'
6
- import { rewritesOnInstall, stripSeedMarker } from '@/seed-marker'
5
+ import { copyPreservingMode } from '@/sync/copy'
6
+ import { rewritesOnInstall, stripSeedMarker } from '@/tooling/seed-marker'
7
7
  import { resolveSurfacePath } from '@/surface-root'
8
8
  import { mergeSections, pruneSections } from '@/tooling/gitignore'
9
9
  import {
@@ -1,6 +1,6 @@
1
1
  import { existsSync, readdirSync, realpathSync } from 'node:fs'
2
2
  import { basename, join, relative } from 'node:path'
3
- import { gitEnv } from '@/git-env'
3
+ import { gitEnv } from '@/git/env'
4
4
  import { isWholeStack, type Manifest, seedFiles } from '@/tooling/manifest'
5
5
 
6
6
  /**
@@ -1,4 +1,4 @@
1
- import { isMarked } from '@/exempt-marker'
1
+ import { isMarked } from '@/sweep/exempt-marker'
2
2
 
3
3
  export const README_PARAPHRASE_MARKER = 'canon-allow-readme-paraphrase'
4
4
 
@@ -34,7 +34,7 @@ const QUOTED_PHRASE = /"([^"]+)"/g
34
34
  * retired `README.md:<n>` form, reported rather than accepted so the fragile
35
35
  * convention this replaces cannot come back on a later edit.
36
36
  *
37
- * Modeled on `clientCommandCitationsIn` in `src/client-commands.ts`, including
37
+ * Modeled on `clientCommandCitationsIn` in `src/gate/client-commands.ts`, including
38
38
  * its use of `isMarked` for the exemption.
39
39
  */
40
40
  export function readmeCitationsIn(
@@ -1,6 +1,6 @@
1
1
  import { basename } from 'node:path'
2
2
  import { execa } from 'execa'
3
- import { gitEnv } from '@/git-env'
3
+ import { gitEnv } from '@/git/env'
4
4
 
5
5
  /** Why a run wrote nothing. The three are different states a caller can tell apart. */
6
6
  export type BareFlagSkip =
@@ -1,7 +1,7 @@
1
1
  import { $ } from 'bun'
2
2
  import { execa } from 'execa'
3
3
  import { existsSync } from 'node:fs'
4
- import { gitEnv } from '@/git-env'
4
+ import { gitEnv } from '@/git/env'
5
5
  import {
6
6
  repositoryOf,
7
7
  type ResolvedSession,
@@ -12,7 +12,7 @@ import {
12
12
  currentWorktreeRoot,
13
13
  listWorktrees,
14
14
  type WorktreeEntry,
15
- } from '@/worktree'
15
+ } from '@/git/worktree'
16
16
 
17
17
  const GH_TIMEOUT_MS = 30_000
18
18
 
@@ -1,5 +1,5 @@
1
1
  import { $ } from 'bun'
2
- import { gitEnv } from '@/git-env'
2
+ import { gitEnv } from '@/git/env'
3
3
  import type { WorktreeVerdict } from '@/worktrees/reclaim'
4
4
 
5
5
  /** Which half of the removal a failure landed on, so a reader repairs the right one. */
@@ -54,6 +54,7 @@ Use `## Overview`, `## Key technical decisions` with one named H3 per decision,
54
54
 
55
55
  - Rewrite a decision a later one changed rather than appending the change beside it. A reader should find the design that stands in one place, with the alternative that lost stated once.
56
56
  - Hold only what is open under `## Risks / open questions`. An entry leaves the section in the change that settles it, becoming a decision here when it fills a slot and moving to the domain context entry it constrains when it does not.
57
+ - Carry an optional `reviewed: YYYY-MM-DD` frontmatter field naming the day someone last read the record whole for drift. Whoever finishes that review sets it as the review's last edit, and `canon records stale canonical` reads it to count the releases shipped since. A record with no field reads as never reviewed.
57
58
 
58
59
  ## Verification anchors
59
60