@erclx/canon 5.44.0 → 5.46.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 (253) hide show
  1. package/README.md +1 -1
  2. package/claude/.claude-plugin/plugin.json +1 -1
  3. package/claude/skills/api-design/REQUIREMENT.md +15 -31
  4. package/claude/skills/auto-ship/REQUIREMENT.md +14 -25
  5. package/claude/skills/backlog-triage/REQUIREMENT.md +12 -23
  6. package/claude/skills/build-in-slices/REQUIREMENT.md +15 -25
  7. package/claude/skills/canon-cli/REQUIREMENT.md +15 -27
  8. package/claude/skills/canon-cli/SKILL.md +0 -1
  9. package/claude/skills/canon-feedback/REQUIREMENT.md +10 -18
  10. package/claude/skills/canon-feedback-triage/REQUIREMENT.md +10 -15
  11. package/claude/skills/canvas-board/REQUIREMENT.md +14 -28
  12. package/claude/skills/ci-workflow/REQUIREMENT.md +12 -21
  13. package/claude/skills/code-craft/REQUIREMENT.md +14 -28
  14. package/claude/skills/codebase-layout/REQUIREMENT.md +14 -31
  15. package/claude/skills/context-fold/REQUIREMENT.md +15 -39
  16. package/claude/skills/context-fold/references/context.md +3 -3
  17. package/claude/skills/create-rule/REQUIREMENT.md +10 -24
  18. package/claude/skills/create-rule/SKILL.md +3 -0
  19. package/claude/skills/create-skill/REQUIREMENT.md +10 -21
  20. package/claude/skills/decision-escalate/REQUIREMENT.md +12 -24
  21. package/claude/skills/deploy-app/REQUIREMENT.md +12 -22
  22. package/claude/skills/deprecation-migration/REQUIREMENT.md +14 -42
  23. package/claude/skills/design-identity/REQUIREMENT.md +14 -20
  24. package/claude/skills/design-taste/REQUIREMENT.md +16 -57
  25. package/claude/skills/docs-sync/REQUIREMENT.md +14 -23
  26. package/claude/skills/document-health/REQUIREMENT.md +15 -29
  27. package/claude/skills/document-health/SKILL.md +2 -2
  28. package/claude/skills/document-repair/REQUIREMENT.md +32 -0
  29. package/claude/skills/document-repair/SKILL.md +76 -0
  30. package/claude/skills/document-repair/references/batches.md +44 -0
  31. package/claude/skills/draft-and-pick/REQUIREMENT.md +4 -3
  32. package/claude/skills/draft-doc/REQUIREMENT.md +4 -3
  33. package/claude/skills/draft-figure/REQUIREMENT.md +4 -3
  34. package/claude/skills/draft-identity/REQUIREMENT.md +4 -3
  35. package/claude/skills/draft-ready/REQUIREMENT.md +4 -3
  36. package/claude/skills/draft-screencast/REQUIREMENT.md +4 -3
  37. package/claude/skills/draft-slides/REQUIREMENT.md +4 -3
  38. package/claude/skills/draw-figure/REQUIREMENT.md +15 -39
  39. package/claude/skills/git-branch/REQUIREMENT.md +12 -26
  40. package/claude/skills/git-commit/REQUIREMENT.md +11 -15
  41. package/claude/skills/git-followup/REQUIREMENT.md +11 -22
  42. package/claude/skills/git-issue/REQUIREMENT.md +9 -15
  43. package/claude/skills/git-pr/REQUIREMENT.md +13 -32
  44. package/claude/skills/git-pr/SKILL.md +10 -2
  45. package/claude/skills/git-pr/references/closing-issues.md +47 -0
  46. package/claude/skills/git-pr/references/pr.md +4 -0
  47. package/claude/skills/git-ship/REQUIREMENT.md +13 -26
  48. package/claude/skills/git-split/REQUIREMENT.md +10 -17
  49. package/claude/skills/git-stage/REQUIREMENT.md +12 -18
  50. package/claude/skills/git-worktree/REQUIREMENT.md +15 -19
  51. package/claude/skills/make-deck/REQUIREMENT.md +13 -21
  52. package/claude/skills/markdown-craft/REQUIREMENT.md +9 -3
  53. package/claude/skills/pick-by-eye/REQUIREMENT.md +17 -67
  54. package/claude/skills/plan-feature/REQUIREMENT.md +15 -24
  55. package/claude/skills/plan-groundwork/REQUIREMENT.md +12 -28
  56. package/claude/skills/plan-groundwork/SKILL.md +5 -2
  57. package/claude/skills/plan-groundwork/references/output.md +47 -1
  58. package/claude/skills/plan-intake/REQUIREMENT.md +11 -26
  59. package/claude/skills/plan-intake/references/intake.md +2 -2
  60. package/claude/skills/plan-intake-answer/REQUIREMENT.md +14 -29
  61. package/claude/skills/plan-intake-answer/SKILL.md +7 -1
  62. package/claude/skills/plan-ready/REQUIREMENT.md +17 -36
  63. package/claude/skills/pr-evidence/REQUIREMENT.md +11 -17
  64. package/claude/skills/read-frames/REQUIREMENT.md +3 -8
  65. package/claude/skills/record-screencast/REQUIREMENT.md +14 -17
  66. package/claude/skills/review-address/REQUIREMENT.md +14 -38
  67. package/claude/skills/review-craft/REQUIREMENT.md +14 -32
  68. package/claude/skills/review-pr/REQUIREMENT.md +15 -34
  69. package/claude/skills/review-ui/REQUIREMENT.md +14 -39
  70. package/claude/skills/role-orchestrator/REQUIREMENT.md +16 -99
  71. package/claude/skills/role-orchestrator/references/orchestrator-refill.md +5 -0
  72. package/claude/skills/role-orchestrator/references/orchestrator-review-dispatch.md +9 -0
  73. package/claude/skills/role-planner/REQUIREMENT.md +15 -73
  74. package/claude/skills/role-reviewer/REQUIREMENT.md +15 -65
  75. package/claude/skills/role-reviewer/SKILL.md +5 -1
  76. package/claude/skills/role-worker/REQUIREMENT.md +13 -41
  77. package/claude/skills/role-worker/SKILL.md +2 -0
  78. package/claude/skills/script-screencast/REQUIREMENT.md +10 -16
  79. package/claude/skills/search-craft/REQUIREMENT.md +12 -36
  80. package/claude/skills/search-craft/SKILL.md +1 -1
  81. package/claude/skills/search-craft/references/adopted.md +2 -2
  82. package/claude/skills/session-compact/REQUIREMENT.md +13 -23
  83. package/claude/skills/session-map/REQUIREMENT.md +13 -29
  84. package/claude/skills/session-relay/REQUIREMENT.md +14 -58
  85. package/claude/skills/session-relay/SKILL.md +6 -0
  86. package/claude/skills/session-resume/REQUIREMENT.md +13 -27
  87. package/claude/skills/session-worktree/REQUIREMENT.md +14 -43
  88. package/claude/skills/systematic-debugging/REQUIREMENT.md +13 -34
  89. package/claude/skills/target-check/REQUIREMENT.md +16 -23
  90. package/claude/skills/target-setup/REQUIREMENT.md +11 -34
  91. package/claude/skills/target-setup/SKILL.md +2 -2
  92. package/claude/skills/target-setup/references/detect.md +0 -1
  93. package/claude/skills/target-setup/references/verify.md +2 -2
  94. package/claude/skills/task-board/REQUIREMENT.md +13 -22
  95. package/claude/skills/task-board/SKILL.md +7 -0
  96. package/claude/skills/task-board/references/tasks.md +1 -1
  97. package/claude/skills/task-complete/REQUIREMENT.md +11 -24
  98. package/claude/skills/task-complete/references/archive.md +1 -0
  99. package/claude/skills/teach-workspace/REQUIREMENT.md +14 -45
  100. package/claude/skills/teach-workspace/references/promotion.md +5 -5
  101. package/claude/skills/test-craft/REQUIREMENT.md +15 -46
  102. package/claude/skills/test-first/REQUIREMENT.md +14 -20
  103. package/claude/skills/ui-checklist/REQUIREMENT.md +14 -29
  104. package/claude/skills/ux-audit/REQUIREMENT.md +3 -7
  105. package/claude/skills/ux-measure/REQUIREMENT.md +14 -28
  106. package/claude/skills/video-craft/REQUIREMENT.md +14 -27
  107. package/claude/skills/write-doc/REQUIREMENT.md +14 -24
  108. package/claude/skills/write-human/REQUIREMENT.md +15 -27
  109. package/claude/skills/youtube-transcripts/REQUIREMENT.md +9 -15
  110. package/docs/agents/context-audit.md +19 -12
  111. package/docs/agents/install-and-sync.md +2 -2
  112. package/docs/agents/markdown-audit.md +15 -10
  113. package/docs/agents/output-shape.md +1 -2
  114. package/docs/target/migrations.md +18 -0
  115. package/docs/target/projects.md +3 -2
  116. package/docs/target/scenarios.md +4 -1
  117. package/docs/workflow/skill-map-on-request.md +1 -1
  118. package/docs/workflow/skill-map.md +6 -5
  119. package/governance/detect.toml +4 -0
  120. package/governance/ledger/lang/go.md +61 -0
  121. package/governance/ledger/lang/php.md +50 -0
  122. package/governance/ledger/lang/python.md +68 -0
  123. package/governance/ledger/lang/typescript.md +64 -0
  124. package/governance/ledger/lib/database.md +65 -0
  125. package/governance/ledger/lib/e2e-reliability.md +57 -0
  126. package/governance/ledger/lib/pydantic.md +61 -0
  127. package/governance/ledger/lib/security-server.md +62 -0
  128. package/governance/ledger/lib/security-web.md +51 -0
  129. package/governance/ledger/lib/tanstack-query.md +57 -0
  130. package/governance/ledger/lib/test-scope.md +43 -0
  131. package/governance/ledger/lib/testing-go.md +57 -0
  132. package/governance/ledger/lib/testing-php.md +51 -0
  133. package/governance/ledger/lib/testing-py.md +67 -0
  134. package/governance/ledger/lib/testing-ts.md +63 -0
  135. package/governance/ledger/lib/zod.md +47 -0
  136. package/governance/ledger/tooling/ci-workflow.md +33 -0
  137. package/governance/ledger/tooling/code-comments.md +48 -0
  138. package/governance/ledger/tooling/config-comments.md +27 -0
  139. package/governance/ledger/tooling/dependencies.md +38 -0
  140. package/governance/ledger/ui/a11y.md +55 -0
  141. package/governance/ledger/ui/copy.md +49 -0
  142. package/governance/ledger/ui/design-reference.md +42 -0
  143. package/governance/ledger/ui/design-taste.md +19 -0
  144. package/governance/ledger/ui/forms.md +71 -0
  145. package/governance/ledger/ui/interface-casing.md +47 -0
  146. package/governance/ledger/ui/keyboard.md +68 -0
  147. package/governance/ledger/ui/link-behavior.md +29 -0
  148. package/governance/ledger/ui/motion.md +33 -0
  149. package/governance/ledger/ui/surface-capture.md +69 -0
  150. package/governance/ledger/ui/ux-completeness.md +79 -0
  151. package/governance/renames.toml +0 -2
  152. package/governance/rules/claude/output.md +5 -1
  153. package/governance/rules/code/routing.md +5 -1
  154. package/governance/rules/lang/go.md +7 -16
  155. package/governance/rules/lang/php.md +3 -3
  156. package/governance/rules/lang/python.md +9 -18
  157. package/governance/rules/lang/typescript.md +9 -19
  158. package/governance/rules/lib/database.md +5 -5
  159. package/governance/rules/lib/e2e-reliability.md +4 -6
  160. package/governance/rules/lib/pydantic.md +14 -16
  161. package/governance/rules/lib/security-server.md +4 -4
  162. package/governance/rules/lib/security-web.md +6 -10
  163. package/governance/rules/lib/tanstack-query.md +10 -12
  164. package/governance/rules/lib/test-scope.md +4 -4
  165. package/governance/rules/lib/testing-go.md +1 -2
  166. package/governance/rules/lib/testing-php.md +4 -5
  167. package/governance/rules/lib/testing-py.md +11 -19
  168. package/governance/rules/lib/testing-ts.md +7 -16
  169. package/governance/rules/lib/zod.md +6 -8
  170. package/governance/rules/tooling/code-comments.md +2 -4
  171. package/governance/rules/tooling/config-comments.md +1 -4
  172. package/governance/rules/tooling/dependencies.md +1 -1
  173. package/governance/rules/ui/a11y.md +16 -45
  174. package/governance/rules/ui/copy.md +6 -9
  175. package/governance/rules/ui/forms.md +13 -19
  176. package/governance/rules/ui/interface-casing.md +5 -7
  177. package/governance/rules/ui/keyboard.md +38 -0
  178. package/governance/rules/ui/link-behavior.md +3 -3
  179. package/governance/rules/ui/motion.md +3 -3
  180. package/governance/rules/ui/surface-capture.md +15 -27
  181. package/governance/rules/ui/ux-completeness.md +16 -39
  182. package/governance/stacks/base.toml +0 -1
  183. package/package.json +1 -1
  184. package/scripts/core/install-check.sh +1 -1
  185. package/src/claude/cases/workflow.ts +12 -0
  186. package/src/cli/help.ts +0 -1
  187. package/src/cli.ts +0 -2
  188. package/src/commands/context.ts +57 -3
  189. package/src/commands/gov.ts +4 -0
  190. package/src/commands/init.ts +2 -2
  191. package/src/commands/labels.ts +83 -3
  192. package/src/commands/markdown.ts +188 -21
  193. package/src/commands/sessions.ts +36 -3
  194. package/src/commands/tasks.ts +180 -0
  195. package/src/commands/tooling.ts +18 -5
  196. package/src/commands/worktrees.ts +2 -0
  197. package/src/context/coverage.ts +210 -0
  198. package/src/context/gate.ts +12 -3
  199. package/src/design/components.ts +1 -1
  200. package/src/gate/stages.ts +1 -1
  201. package/src/gov/audit.ts +22 -2
  202. package/src/init/plan.ts +1 -8
  203. package/src/init/steps.ts +44 -7
  204. package/src/intake/items.ts +14 -0
  205. package/src/inventory/subjects.ts +1 -1
  206. package/src/labels/closing.ts +46 -0
  207. package/src/markdown/gated.ts +250 -0
  208. package/src/markdown/scope.ts +119 -2
  209. package/src/migrate/rename.ts +6 -0
  210. package/src/migrate/scratch-evidence.ts +12 -4
  211. package/src/records/backup.ts +3 -3
  212. package/src/records/validate.ts +3 -3
  213. package/src/roots/record.ts +17 -8
  214. package/src/sessions/claim.ts +7 -0
  215. package/src/sessions/live.ts +53 -6
  216. package/src/sessions/registry.ts +11 -0
  217. package/src/sessions/resolve.ts +32 -5
  218. package/src/tasks/closes.ts +136 -0
  219. package/src/tasks/record.ts +1 -1
  220. package/src/tooling/inject.ts +26 -7
  221. package/src/tooling/package.ts +32 -0
  222. package/src/tooling/read.ts +46 -3
  223. package/src/tooling/scan.ts +8 -2
  224. package/src/worktrees/reclaim.ts +16 -1
  225. package/tooling/astro/reference.md +64 -93
  226. package/tooling/base/configs/.github/workflows/phase-label-gate.yml +1 -1
  227. package/tooling/base/configs/scripts/verify.ts +2 -2
  228. package/tooling/base/reference.md +1 -9
  229. package/tooling/base/seeds/canon/context/{ci.md → development/ci.md} +1 -1
  230. package/tooling/base/seeds/canon/context/development/index.md +11 -0
  231. package/tooling/base/seeds/canon/context/{development.md → development/setup.md} +8 -12
  232. package/tooling/base/seeds/canon/context/index.md +1 -2
  233. package/tooling/claude/manifest.toml +1 -5
  234. package/tooling/claude/reference.md +36 -59
  235. package/tooling/claude/seeds/.claude/hooks/board-validate.sh +1 -1
  236. package/tooling/claude/seeds/.claude/hooks/index-reminder.sh +2 -7
  237. package/tooling/claude/seeds/.claude/hooks/pr-create-log.sh +2 -7
  238. package/tooling/claude/seeds/.claude/hooks/scratch-guard.sh +3 -10
  239. package/tooling/claude/seeds/.claude/hooks/search-reminder.sh +2 -7
  240. package/tooling/claude/seeds/.claude/hooks/standards-audit.sh +2 -4
  241. package/tooling/claude/seeds/CLAUDE.md +2 -2
  242. package/tooling/nextjs/reference.md +0 -6
  243. package/tooling/php/reference.md +0 -4
  244. package/tooling/web/configs/.github/workflows/verify.yml +28 -0
  245. package/tooling/web/configs/.oxlintrc.json +7 -0
  246. package/tooling/web/configs/e2e/capture-types.ts +35 -0
  247. package/tooling/web/configs/e2e/screenshot.ts +23 -72
  248. package/tooling/web/manifest.toml +1 -1
  249. package/tooling/web/reference.md +77 -124
  250. package/tooling/web/seeds/e2e/capture/cases.ts +47 -0
  251. package/governance/rules/canon/task-scope.md +0 -12
  252. package/src/commands/wiki.ts +0 -100
  253. package/src/wiki/init.ts +0 -85
@@ -0,0 +1,250 @@
1
+ import {
2
+ chmodSync,
3
+ existsSync,
4
+ mkdirSync,
5
+ readFileSync,
6
+ statSync,
7
+ writeFileSync,
8
+ } from 'node:fs'
9
+ import { readFile } from 'node:fs/promises'
10
+ import { dirname, join } from 'node:path'
11
+ import { listRepositoryFiles } from '@/git/files'
12
+ import { densityFindings, type DensityKind } from '@/markdown/density'
13
+ import { referenceShapeGates } from '@/markdown/gate'
14
+ import {
15
+ type ReferenceShapeKind,
16
+ referenceShapeFindings,
17
+ } from '@/markdown/reference-shape'
18
+ import { bodyLines } from '@/markdown/scan'
19
+ import {
20
+ densityGates,
21
+ GATED_PATHS,
22
+ GATED_SCOPE_ROOTS,
23
+ inDensityScope,
24
+ } from '@/markdown/scope'
25
+ import { isOwnCheckout } from '@/roots/project'
26
+
27
+ /** Where a target keeps the prefixes its density findings gate under. */
28
+ export const GATED_CONFIG_REL = join('canon', 'config', 'markdown.toml')
29
+
30
+ /**
31
+ * An absent file is today's target default and gates nothing. A malformed one
32
+ * refuses rather than reading as empty, since an empty list would silently
33
+ * drop every gate a project had turned on.
34
+ */
35
+ export type GatedConfig =
36
+ | { readonly kind: 'absent' }
37
+ | { readonly kind: 'listed'; readonly prefixes: readonly string[] }
38
+ | { readonly kind: 'malformed'; readonly message: string }
39
+
40
+ export function readGatedConfig(root: string): GatedConfig {
41
+ const path = join(root, GATED_CONFIG_REL)
42
+ if (!existsSync(path)) return { kind: 'absent' }
43
+
44
+ let parsed: Record<string, unknown>
45
+ try {
46
+ parsed = Bun.TOML.parse(readFileSync(path, 'utf8')) as Record<
47
+ string,
48
+ unknown
49
+ >
50
+ } catch (error) {
51
+ return {
52
+ kind: 'malformed',
53
+ message: `${GATED_CONFIG_REL} is not valid TOML: ${error instanceof Error ? error.message : String(error)}`,
54
+ }
55
+ }
56
+
57
+ const table = parsed.markdown
58
+ if (table === undefined) return { kind: 'listed', prefixes: [] }
59
+ if (typeof table !== 'object' || table === null || Array.isArray(table)) {
60
+ return {
61
+ kind: 'malformed',
62
+ message: `${GATED_CONFIG_REL}: [markdown] is not a table`,
63
+ }
64
+ }
65
+
66
+ const gated = (table as Record<string, unknown>).gated
67
+ if (gated === undefined) return { kind: 'listed', prefixes: [] }
68
+ if (
69
+ !Array.isArray(gated) ||
70
+ !gated.every((each): each is string => typeof each === 'string')
71
+ ) {
72
+ return {
73
+ kind: 'malformed',
74
+ message: `${GATED_CONFIG_REL}: markdown.gated is not a list of strings`,
75
+ }
76
+ }
77
+
78
+ return { kind: 'listed', prefixes: gated }
79
+ }
80
+
81
+ /**
82
+ * Whether `prefix` can be listed. A folder ends in a slash, since a bare
83
+ * `docs` would also gate `docs-old/`. A path without one has to name an
84
+ * existing file, which is how a requirement gates by its own path.
85
+ */
86
+ export function prefixShapeError(
87
+ root: string,
88
+ prefix: string,
89
+ ): string | undefined {
90
+ if (prefix === '' || prefix.startsWith('/') || prefix.includes('..')) {
91
+ return `${prefix || '(empty)'} is not a repository-relative path`
92
+ }
93
+ // A listed prefix is a `startsWith` match against repository paths, so either
94
+ // shape below matches no file and would gate nothing while reading as clean.
95
+ if (prefix.includes('//')) {
96
+ return `${prefix} carries a doubled slash, so it matches no file`
97
+ }
98
+ const path = join(root, prefix.replace(/\/$/, ''))
99
+ const isFile = existsSync(path) && !statSync(path).isDirectory()
100
+ if (prefix.endsWith('/')) {
101
+ return isFile
102
+ ? `${prefix} names a file, so drop the trailing slash`
103
+ : undefined
104
+ }
105
+ if (isFile) return undefined
106
+ return `${prefix} names no file. End a folder prefix with a slash.`
107
+ }
108
+
109
+ /**
110
+ * Appends `prefix` to the target's list, creating the file when absent. A
111
+ * prefix already listed writes nothing. Preserves an existing file's mode.
112
+ */
113
+ export function appendGatedPrefix(
114
+ root: string,
115
+ current: readonly string[],
116
+ prefix: string,
117
+ ): 'appended' | 'present' {
118
+ if (current.includes(prefix)) return 'present'
119
+ writeGatedList(root, [...current, prefix])
120
+ return 'appended'
121
+ }
122
+
123
+ /** Written by hand rather than serialized, the way `classifier.toml` is. */
124
+ function writeGatedList(root: string, prefixes: readonly string[]): void {
125
+ const path = join(root, GATED_CONFIG_REL)
126
+ const mode = existsSync(path) ? statSync(path).mode : undefined
127
+ const lines = prefixes.map((each) => ` ${JSON.stringify(each)},`)
128
+
129
+ mkdirSync(dirname(path), { recursive: true })
130
+ writeFileSync(path, `[markdown]\ngated = [\n${lines.join('\n')}\n]\n`)
131
+ if (mode !== undefined) chmodSync(path, mode)
132
+ }
133
+
134
+ /** The prefixes a root gates under, and whether a bullet run gates unlisted. */
135
+ export interface GatedList {
136
+ readonly prefixes: readonly string[]
137
+ readonly alwaysGated: boolean
138
+ }
139
+
140
+ export type GatedResolution =
141
+ | {
142
+ readonly kind: 'resolved'
143
+ readonly source: 'canon' | 'config' | 'absent'
144
+ readonly list: GatedList
145
+ }
146
+ | { readonly kind: 'malformed'; readonly message: string }
147
+
148
+ /**
149
+ * The list a root gates under: `GATED_PATHS` in canon's own checkout, which
150
+ * tracks its rewrite progress, and the target's config everywhere else.
151
+ */
152
+ export function resolveGatedList(
153
+ root: string,
154
+ own: boolean = isOwnCheckout(root),
155
+ ): GatedResolution {
156
+ if (own) {
157
+ return {
158
+ kind: 'resolved',
159
+ source: 'canon',
160
+ list: { prefixes: GATED_PATHS, alwaysGated: true },
161
+ }
162
+ }
163
+
164
+ const config = readGatedConfig(root)
165
+ if (config.kind === 'malformed') return config
166
+ return {
167
+ kind: 'resolved',
168
+ source: config.kind === 'absent' ? 'absent' : 'config',
169
+ list: {
170
+ prefixes: config.kind === 'listed' ? config.prefixes : [],
171
+ alwaysGated: false,
172
+ },
173
+ }
174
+ }
175
+
176
+ export interface BlockingFinding {
177
+ readonly path: string
178
+ readonly kind: DensityKind | ReferenceShapeKind
179
+ readonly line: number
180
+ }
181
+
182
+ /**
183
+ * Every density and reference shape finding that would gate once `prefix` is
184
+ * listed, read from the files the audit reads. An absent folder carries none.
185
+ * Undefined when the tree cannot be listed, since an empty answer there would
186
+ * gate a corpus nobody measured.
187
+ */
188
+ export async function findingsUnder(
189
+ root: string,
190
+ prefix: string,
191
+ ): Promise<BlockingFinding[] | undefined> {
192
+ const listed = await listRepositoryFiles(root)
193
+ if (!listed) return undefined
194
+
195
+ const under = listed.filter(
196
+ (rel) => rel.endsWith('.md') && rel.startsWith(prefix),
197
+ )
198
+ const found = await Promise.all(
199
+ under.map(async (rel) => {
200
+ const source = await readFile(join(root, rel), 'utf8')
201
+ const density = inDensityScope(rel)
202
+ ? densityFindings(bodyLines(source)).filter((finding) =>
203
+ densityGates(rel, finding.kind, [prefix], false),
204
+ )
205
+ : []
206
+ const shape = referenceShapeGates(rel, [prefix])
207
+ ? referenceShapeFindings(source)
208
+ : []
209
+ return [...density, ...shape].map((finding) => ({
210
+ path: rel,
211
+ kind: finding.kind,
212
+ line: finding.line,
213
+ }))
214
+ }),
215
+ )
216
+
217
+ return found.flat()
218
+ }
219
+
220
+ export type ScopeRootsResult =
221
+ | { readonly kind: 'exists' }
222
+ | { readonly kind: 'unavailable' }
223
+ | {
224
+ readonly kind: 'gated'
225
+ readonly gated: readonly string[]
226
+ readonly left: readonly { prefix: string; findings: number }[]
227
+ }
228
+
229
+ /**
230
+ * Gates each of `GATED_SCOPE_ROOTS` that carries no finding, for a new
231
+ * project. A root with one is left out and named, since gating it would fail
232
+ * the first push on prose a scaffolder wrote. An existing config is a list
233
+ * someone chose, so it is never touched.
234
+ */
235
+ export async function gateScopeRoots(root: string): Promise<ScopeRootsResult> {
236
+ if (existsSync(join(root, GATED_CONFIG_REL))) return { kind: 'exists' }
237
+
238
+ const gated: string[] = []
239
+ const left: { prefix: string; findings: number }[] = []
240
+
241
+ for (const prefix of GATED_SCOPE_ROOTS) {
242
+ const findings = await findingsUnder(root, prefix)
243
+ if (!findings) return { kind: 'unavailable' }
244
+ if (findings.length === 0) gated.push(prefix)
245
+ else left.push({ prefix, findings: findings.length })
246
+ }
247
+
248
+ writeGatedList(root, gated)
249
+ return { kind: 'gated', gated, left }
250
+ }
@@ -36,12 +36,27 @@ const DENSITY_SCOPE = [
36
36
  /^(?:README|CONTRIBUTING)\.md$/,
37
37
  ]
38
38
 
39
+ /**
40
+ * The roots `canon init` gates in a new project when each passes at init time:
41
+ * every density surface a target authors itself, leaving out the toolkit's own
42
+ * `claude/skills/` and `tooling/` trees.
43
+ */
44
+ export const GATED_SCOPE_ROOTS: readonly string[] = [
45
+ 'canon/context/',
46
+ 'docs/',
47
+ '.claude/skills/',
48
+ 'wiki/',
49
+ 'README.md',
50
+ 'CONTRIBUTING.md',
51
+ ]
52
+
39
53
  /**
40
54
  * The path prefixes whose density findings fail a run, each added by the
41
55
  * rewrite batch that brought its folders to zero.
42
56
  *
43
57
  * It describes this repository's rewrite progress and nothing about a target's
44
- * corpus, which is why the audit reads it only inside a canon checkout.
58
+ * corpus, which is why the audit reads it only inside a canon checkout. A
59
+ * target keeps its own list in the file `src/markdown/gated.ts` reads.
45
60
  */
46
61
  export const GATED_PATHS: readonly string[] = [
47
62
  'canon/context/surfaces/',
@@ -50,8 +65,10 @@ export const GATED_PATHS: readonly string[] = [
50
65
  'canon/context/features/',
51
66
  'canon/context/context-model.md',
52
67
  'canon/context/index.md',
68
+ 'claude/skills/api-design/REQUIREMENT.md',
53
69
  'claude/skills/api-design/SKILL.md',
54
70
  'claude/skills/api-design/references/adopted.md',
71
+ 'claude/skills/auto-ship/REQUIREMENT.md',
55
72
  'claude/skills/auto-ship/SKILL.md',
56
73
  'claude/skills/auto-ship/references/draft-mark.md',
57
74
  'claude/skills/auto-ship/references/failure-recovery.md',
@@ -60,33 +77,45 @@ export const GATED_PATHS: readonly string[] = [
60
77
  'claude/skills/auto-ship/references/ui-checklist.md',
61
78
  'claude/skills/auto-ship/references/verb-absent.md',
62
79
  'claude/skills/auto-ship/references/worktree-name.md',
80
+ 'claude/skills/backlog-triage/REQUIREMENT.md',
63
81
  'claude/skills/backlog-triage/SKILL.md',
64
82
  'claude/skills/backlog-triage/references/apply.md',
83
+ 'claude/skills/build-in-slices/REQUIREMENT.md',
65
84
  'claude/skills/build-in-slices/SKILL.md',
66
85
  'claude/skills/build-in-slices/references/adopted.md',
86
+ 'claude/skills/canon-cli/REQUIREMENT.md',
67
87
  'claude/skills/canon-cli/SKILL.md',
88
+ 'claude/skills/canon-feedback-triage/REQUIREMENT.md',
68
89
  'claude/skills/canon-feedback-triage/SKILL.md',
90
+ 'claude/skills/canon-feedback/REQUIREMENT.md',
69
91
  'claude/skills/canon-feedback/SKILL.md',
70
92
  'claude/skills/canon-feedback/references/template.md',
93
+ 'claude/skills/canvas-board/REQUIREMENT.md',
71
94
  'claude/skills/canvas-board/SKILL.md',
72
95
  'claude/skills/canvas-board/references/canvas.md',
73
96
  'claude/skills/canvas-board/references/design.md',
97
+ 'claude/skills/ci-workflow/REQUIREMENT.md',
74
98
  'claude/skills/ci-workflow/SKILL.md',
75
99
  'claude/skills/ci-workflow/references/workflows.md',
100
+ 'claude/skills/code-craft/REQUIREMENT.md',
76
101
  'claude/skills/code-craft/SKILL.md',
77
102
  'claude/skills/code-craft/references/adopted.md',
103
+ 'claude/skills/codebase-layout/REQUIREMENT.md',
78
104
  'claude/skills/codebase-layout/SKILL.md',
79
105
  'claude/skills/codebase-layout/references/adopted.md',
80
106
  'claude/skills/codebase-layout/references/frontend.md',
81
107
  'claude/skills/codebase-layout/references/tests.md',
108
+ 'claude/skills/context-fold/REQUIREMENT.md',
82
109
  'claude/skills/context-fold/SKILL.md',
83
110
  'claude/skills/context-fold/references/classify.md',
84
111
  'claude/skills/context-fold/references/completion-report.md',
85
112
  'claude/skills/context-fold/references/context-refresh.md',
86
113
  'claude/skills/context-fold/references/context.md',
87
114
  'claude/skills/context-fold/references/unusable-baseline.md',
115
+ 'claude/skills/create-rule/REQUIREMENT.md',
88
116
  'claude/skills/create-rule/SKILL.md',
89
117
  'claude/skills/create-rule/references/rule.md',
118
+ 'claude/skills/create-skill/REQUIREMENT.md',
90
119
  'claude/skills/create-skill/SKILL.md',
91
120
  'claude/skills/create-skill/references/skill-disclosure.md',
92
121
  'claude/skills/create-skill/references/skill-frontmatter.md',
@@ -95,12 +124,16 @@ export const GATED_PATHS: readonly string[] = [
95
124
  'claude/skills/create-skill/references/skill-reference.md',
96
125
  'claude/skills/create-skill/references/skill-requirement.md',
97
126
  'claude/skills/create-skill/references/skill.md',
127
+ 'claude/skills/decision-escalate/REQUIREMENT.md',
98
128
  'claude/skills/decision-escalate/SKILL.md',
129
+ 'claude/skills/deploy-app/REQUIREMENT.md',
99
130
  'claude/skills/deploy-app/SKILL.md',
100
131
  'claude/skills/deploy-app/references/cloudflare.md',
101
132
  'claude/skills/deploy-app/references/vercel.md',
133
+ 'claude/skills/deprecation-migration/REQUIREMENT.md',
102
134
  'claude/skills/deprecation-migration/SKILL.md',
103
135
  'claude/skills/deprecation-migration/references/adopted.md',
136
+ 'claude/skills/design-taste/REQUIREMENT.md',
104
137
  'claude/skills/design-taste/SKILL.md',
105
138
  'claude/skills/design-taste/references/adopted.md',
106
139
  'claude/skills/design-taste/references/craft.md',
@@ -109,13 +142,27 @@ export const GATED_PATHS: readonly string[] = [
109
142
  'claude/skills/design-taste/references/systems.md',
110
143
  'claude/skills/design-taste/references/tells.md',
111
144
  'claude/skills/design-taste/references/vocabulary.md',
145
+ 'claude/skills/docs-sync/REQUIREMENT.md',
112
146
  'claude/skills/docs-sync/SKILL.md',
113
147
  'claude/skills/docs-sync/references/docs.md',
114
148
  'claude/skills/docs-sync/references/readme.md',
149
+ 'claude/skills/document-health/REQUIREMENT.md',
115
150
  'claude/skills/document-health/SKILL.md',
116
151
  'claude/skills/document-health/references/axes.md',
152
+ 'claude/skills/document-repair/REQUIREMENT.md',
153
+ 'claude/skills/document-repair/SKILL.md',
154
+ 'claude/skills/document-repair/references/batches.md',
155
+ 'claude/skills/draft-and-pick/REQUIREMENT.md',
156
+ 'claude/skills/draft-doc/REQUIREMENT.md',
157
+ 'claude/skills/draft-figure/REQUIREMENT.md',
158
+ 'claude/skills/draft-identity/REQUIREMENT.md',
159
+ 'claude/skills/draft-ready/REQUIREMENT.md',
160
+ 'claude/skills/draft-screencast/REQUIREMENT.md',
161
+ 'claude/skills/draft-slides/REQUIREMENT.md',
162
+ 'claude/skills/pick-by-eye/REQUIREMENT.md',
117
163
  'claude/skills/pick-by-eye/SKILL.md',
118
164
  'claude/skills/pick-by-eye/references/live-arms.md',
165
+ 'claude/skills/write-doc/REQUIREMENT.md',
119
166
  'claude/skills/write-doc/SKILL.md',
120
167
  'claude/skills/write-doc/references/context.md',
121
168
  'claude/skills/write-doc/references/docs.md',
@@ -128,82 +175,109 @@ export const GATED_PATHS: readonly string[] = [
128
175
  'claude/skills/write-doc/references/requirements.md',
129
176
  'claude/skills/write-doc/references/wiki-kind.md',
130
177
  'claude/skills/write-doc/references/wiki.md',
178
+ 'claude/skills/draw-figure/REQUIREMENT.md',
131
179
  'claude/skills/draw-figure/SKILL.md',
132
180
  'claude/skills/draw-figure/references/architecture-set.md',
133
181
  'claude/skills/draw-figure/references/figures.md',
134
182
  'claude/skills/draw-figure/references/hand-drawn.md',
135
183
  'claude/skills/draw-figure/references/mermaid-path.md',
136
184
  'claude/skills/draw-figure/references/mermaid.md',
185
+ 'claude/skills/design-identity/REQUIREMENT.md',
137
186
  'claude/skills/design-identity/SKILL.md',
138
187
  'claude/skills/design-identity/references/card-route.md',
188
+ 'claude/skills/plan-ready/REQUIREMENT.md',
139
189
  'claude/skills/plan-ready/SKILL.md',
140
190
  'claude/skills/plan-ready/references/assembly.md',
141
191
  'claude/skills/plan-ready/references/ready.md',
192
+ 'claude/skills/script-screencast/REQUIREMENT.md',
142
193
  'claude/skills/script-screencast/SKILL.md',
143
194
  'claude/skills/script-screencast/references/beats-template.md',
195
+ 'claude/skills/make-deck/REQUIREMENT.md',
144
196
  'claude/skills/make-deck/SKILL.md',
145
197
  'claude/skills/make-deck/references/deck.md',
198
+ 'claude/skills/git-branch/REQUIREMENT.md',
146
199
  'claude/skills/git-branch/SKILL.md',
147
200
  'claude/skills/git-branch/references/branch.md',
148
201
  'claude/skills/git-branch/references/slug.md',
202
+ 'claude/skills/git-commit/REQUIREMENT.md',
149
203
  'claude/skills/git-commit/SKILL.md',
150
204
  'claude/skills/git-commit/references/commit.md',
151
205
  'claude/skills/git-commit/references/versioning.md',
206
+ 'claude/skills/git-followup/REQUIREMENT.md',
152
207
  'claude/skills/git-followup/SKILL.md',
208
+ 'claude/skills/git-issue/REQUIREMENT.md',
153
209
  'claude/skills/git-issue/SKILL.md',
154
210
  'claude/skills/git-issue/references/issue.md',
211
+ 'claude/skills/git-pr/REQUIREMENT.md',
155
212
  'claude/skills/git-pr/SKILL.md',
156
213
  'claude/skills/git-pr/references/final-command.md',
157
214
  'claude/skills/git-pr/references/labels.md',
158
215
  'claude/skills/git-pr/references/pr.md',
159
216
  'claude/skills/git-pr/references/publish.md',
160
217
  'claude/skills/git-pr/references/task-number.md',
218
+ 'claude/skills/git-ship/REQUIREMENT.md',
161
219
  'claude/skills/git-ship/SKILL.md',
162
220
  'claude/skills/git-ship/references/ci-watch.md',
221
+ 'claude/skills/git-split/REQUIREMENT.md',
163
222
  'claude/skills/git-split/SKILL.md',
164
223
  'claude/skills/git-split/references/independent.md',
165
224
  'claude/skills/git-split/references/stacked.md',
225
+ 'claude/skills/git-stage/REQUIREMENT.md',
166
226
  'claude/skills/git-stage/SKILL.md',
227
+ 'claude/skills/git-worktree/REQUIREMENT.md',
167
228
  'claude/skills/git-worktree/SKILL.md',
168
229
  'claude/skills/git-worktree/references/cleanup.md',
230
+ 'claude/skills/markdown-craft/REQUIREMENT.md',
169
231
  'claude/skills/markdown-craft/SKILL.md',
170
232
  'claude/skills/markdown-craft/references/markdown.md',
233
+ 'claude/skills/plan-feature/REQUIREMENT.md',
171
234
  'claude/skills/plan-feature/SKILL.md',
172
235
  'claude/skills/plan-feature/references/batches.md',
173
236
  'claude/skills/plan-feature/references/plan-lifecycle.md',
174
237
  'claude/skills/plan-feature/references/plan.md',
238
+ 'claude/skills/plan-groundwork/REQUIREMENT.md',
175
239
  'claude/skills/plan-groundwork/SKILL.md',
176
240
  'claude/skills/plan-groundwork/references/groundwork-spikes.md',
177
241
  'claude/skills/plan-groundwork/references/groundwork.md',
178
242
  'claude/skills/plan-groundwork/references/output.md',
243
+ 'claude/skills/plan-intake-answer/REQUIREMENT.md',
179
244
  'claude/skills/plan-intake-answer/SKILL.md',
245
+ 'claude/skills/plan-intake/REQUIREMENT.md',
180
246
  'claude/skills/plan-intake/SKILL.md',
181
247
  'claude/skills/plan-intake/references/intake.md',
248
+ 'claude/skills/pr-evidence/REQUIREMENT.md',
182
249
  'claude/skills/pr-evidence/SKILL.md',
183
250
  'claude/skills/pr-evidence/references/evidence.md',
251
+ 'claude/skills/read-frames/REQUIREMENT.md',
184
252
  'claude/skills/read-frames/SKILL.md',
253
+ 'claude/skills/record-screencast/REQUIREMENT.md',
185
254
  'claude/skills/record-screencast/SKILL.md',
255
+ 'claude/skills/review-address/REQUIREMENT.md',
186
256
  'claude/skills/review-address/SKILL.md',
187
257
  'claude/skills/review-address/references/post-review-findings.md',
188
258
  'claude/skills/review-address/references/rebase-conflicts.md',
189
259
  'claude/skills/review-address/references/rebase-only.md',
190
260
  'claude/skills/review-address/references/rest-reads.md',
261
+ 'claude/skills/review-craft/REQUIREMENT.md',
191
262
  'claude/skills/review-craft/SKILL.md',
192
263
  'claude/skills/review-craft/references/adopted.md',
193
264
  'claude/skills/review-craft/references/security.md',
194
265
  'claude/skills/review-craft/references/ui.md',
266
+ 'claude/skills/review-pr/REQUIREMENT.md',
195
267
  'claude/skills/review-pr/SKILL.md',
196
268
  'claude/skills/review-pr/references/body-variants.md',
197
269
  'claude/skills/review-pr/references/close-out.md',
198
270
  'claude/skills/review-pr/references/key-changes.md',
199
271
  'claude/skills/review-pr/references/post.md',
200
272
  'claude/skills/review-pr/references/scope.md',
273
+ 'claude/skills/review-ui/REQUIREMENT.md',
201
274
  'claude/skills/review-ui/SKILL.md',
202
275
  'claude/skills/review-ui/references/close-out.md',
203
276
  'claude/skills/review-ui/references/driving.md',
204
277
  'claude/skills/review-ui/references/hosted-preview.md',
205
278
  'claude/skills/review-ui/references/post.md',
206
279
  'claude/skills/review-ui/references/tick.md',
280
+ 'claude/skills/role-orchestrator/REQUIREMENT.md',
207
281
  'claude/skills/role-orchestrator/SKILL.md',
208
282
  'claude/skills/role-orchestrator/references/orchestrator-cloud-launch.md',
209
283
  'claude/skills/role-orchestrator/references/orchestrator-dispatch.md',
@@ -221,38 +295,53 @@ export const GATED_PATHS: readonly string[] = [
221
295
  'claude/skills/role-orchestrator/references/orchestrator-reviewer-launch.md',
222
296
  'claude/skills/role-orchestrator/references/orchestrator-sweep.md',
223
297
  'claude/skills/role-orchestrator/references/orchestrator-watch.md',
298
+ 'claude/skills/role-planner/REQUIREMENT.md',
224
299
  'claude/skills/role-planner/SKILL.md',
300
+ 'claude/skills/role-reviewer/REQUIREMENT.md',
225
301
  'claude/skills/role-reviewer/SKILL.md',
302
+ 'claude/skills/role-worker/REQUIREMENT.md',
226
303
  'claude/skills/role-worker/SKILL.md',
304
+ 'claude/skills/search-craft/REQUIREMENT.md',
227
305
  'claude/skills/search-craft/SKILL.md',
228
306
  'claude/skills/search-craft/references/adopted.md',
307
+ 'claude/skills/session-compact/REQUIREMENT.md',
229
308
  'claude/skills/session-compact/SKILL.md',
230
309
  'claude/skills/session-compact/references/handoff-note.md',
310
+ 'claude/skills/session-map/REQUIREMENT.md',
231
311
  'claude/skills/session-map/SKILL.md',
232
312
  'claude/skills/session-map/references/session.md',
313
+ 'claude/skills/session-relay/REQUIREMENT.md',
233
314
  'claude/skills/session-relay/SKILL.md',
315
+ 'claude/skills/session-resume/REQUIREMENT.md',
234
316
  'claude/skills/session-resume/SKILL.md',
317
+ 'claude/skills/session-worktree/REQUIREMENT.md',
235
318
  'claude/skills/session-worktree/SKILL.md',
236
319
  'claude/skills/session-worktree/references/install-command.md',
237
320
  'claude/skills/session-worktree/references/main-root-writes.md',
321
+ 'claude/skills/systematic-debugging/REQUIREMENT.md',
238
322
  'claude/skills/systematic-debugging/SKILL.md',
239
323
  'claude/skills/systematic-debugging/references/adopted.md',
324
+ 'claude/skills/target-check/REQUIREMENT.md',
240
325
  'claude/skills/target-check/SKILL.md',
241
326
  'claude/skills/target-check/references/domains.md',
327
+ 'claude/skills/target-setup/REQUIREMENT.md',
242
328
  'claude/skills/target-setup/SKILL.md',
243
329
  'claude/skills/target-setup/references/detect.md',
244
330
  'claude/skills/target-setup/references/indexes.md',
245
331
  'claude/skills/target-setup/references/verify.md',
332
+ 'claude/skills/task-board/REQUIREMENT.md',
246
333
  'claude/skills/task-board/SKILL.md',
247
334
  'claude/skills/task-board/references/board.md',
248
335
  'claude/skills/task-board/references/create-report.md',
249
336
  'claude/skills/task-board/references/decline.md',
250
337
  'claude/skills/task-board/references/tasks-decline.md',
251
338
  'claude/skills/task-board/references/tasks.md',
339
+ 'claude/skills/task-complete/REQUIREMENT.md',
252
340
  'claude/skills/task-complete/SKILL.md',
253
341
  'claude/skills/task-complete/references/archive.md',
254
342
  'claude/skills/task-complete/references/receipt-sweep.md',
255
343
  'claude/skills/task-complete/references/unusable-baseline.md',
344
+ 'claude/skills/teach-workspace/REQUIREMENT.md',
256
345
  'claude/skills/teach-workspace/SKILL.md',
257
346
  'claude/skills/teach-workspace/references/glossary.md',
258
347
  'claude/skills/teach-workspace/references/lesson-build.md',
@@ -262,23 +351,43 @@ export const GATED_PATHS: readonly string[] = [
262
351
  'claude/skills/teach-workspace/references/promotion.md',
263
352
  'claude/skills/teach-workspace/references/revisit.md',
264
353
  'claude/skills/teach-workspace/references/teach.md',
354
+ 'claude/skills/test-craft/REQUIREMENT.md',
265
355
  'claude/skills/test-craft/SKILL.md',
266
356
  'claude/skills/test-craft/references/adopted.md',
267
357
  'claude/skills/test-craft/references/component.md',
268
358
  'claude/skills/test-craft/references/e2e.md',
269
359
  'claude/skills/test-craft/references/unit.md',
360
+ 'claude/skills/test-first/REQUIREMENT.md',
270
361
  'claude/skills/test-first/SKILL.md',
271
362
  'claude/skills/test-first/references/adopted.md',
363
+ 'claude/skills/ui-checklist/REQUIREMENT.md',
272
364
  'claude/skills/ui-checklist/SKILL.md',
365
+ 'claude/skills/ux-audit/REQUIREMENT.md',
273
366
  'claude/skills/ux-audit/SKILL.md',
367
+ 'claude/skills/ux-measure/REQUIREMENT.md',
274
368
  'claude/skills/ux-measure/SKILL.md',
369
+ 'claude/skills/video-craft/REQUIREMENT.md',
275
370
  'claude/skills/video-craft/SKILL.md',
276
371
  'claude/skills/video-craft/references/adopted.md',
372
+ 'claude/skills/write-human/REQUIREMENT.md',
277
373
  'claude/skills/write-human/SKILL.md',
278
374
  'claude/skills/write-human/references/adopted.md',
279
375
  'claude/skills/write-human/references/density.md',
280
376
  'claude/skills/write-human/references/machine-tells.md',
377
+ 'claude/skills/youtube-transcripts/REQUIREMENT.md',
281
378
  'claude/skills/youtube-transcripts/SKILL.md',
379
+ '.claude/skills/internal-canvas/REQUIREMENT.md',
380
+ '.claude/skills/internal-claude/REQUIREMENT.md',
381
+ '.claude/skills/internal-docs/REQUIREMENT.md',
382
+ '.claude/skills/internal-governance/REQUIREMENT.md',
383
+ '.claude/skills/internal-rule-audit/REQUIREMENT.md',
384
+ '.claude/skills/internal-sandbox-check/REQUIREMENT.md',
385
+ '.claude/skills/internal-scripts/REQUIREMENT.md',
386
+ '.claude/skills/internal-shipped-reference/REQUIREMENT.md',
387
+ '.claude/skills/internal-teach/REQUIREMENT.md',
388
+ '.claude/skills/internal-tooling/REQUIREMENT.md',
389
+ '.claude/skills/internal-upstream-digest/REQUIREMENT.md',
390
+ '.claude/skills/internal-web/REQUIREMENT.md',
282
391
  '.claude/skills/internal-',
283
392
  'docs/',
284
393
  'tooling/',
@@ -303,15 +412,23 @@ export function inDensityScope(rel: string): boolean {
303
412
  return DENSITY_SCOPE.some((pattern) => pattern.test(rel))
304
413
  }
305
414
 
306
- /** Whether a density finding of this kind on this path fails the run. */
415
+ /**
416
+ * Whether a density finding of this kind on this path fails the run.
417
+ *
418
+ * `alwaysGated` is off for a target's list, since no stage of a target's ever
419
+ * failed a push on a bullet run, and gating one unlisted would fail an older
420
+ * project's push on prose it has not touched since the limit landed.
421
+ */
307
422
  export function densityGates(
308
423
  rel: string,
309
424
  kind: DensityKind,
310
425
  gated: readonly string[] = GATED_PATHS,
426
+ alwaysGated = true,
311
427
  ): boolean {
312
428
  if (!inDensityScope(rel)) return false
313
429
  if (REQUIREMENT_SCOPE.test(rel)) return gated.includes(rel)
314
430
  if (
431
+ alwaysGated &&
315
432
  ALWAYS_GATED.includes(kind) &&
316
433
  BULLET_RUN_SCOPE.some((pattern) => pattern.test(rel))
317
434
  )
@@ -12,6 +12,12 @@
12
12
  * sweeps sharing a module constant would have to agree on a single map, and
13
13
  * the second rename this repository needed shares nothing with the first
14
14
  * except the scanning discipline above.
15
+ *
16
+ * Two misses survive a clean run. The sweep reads spelled text only, so a path
17
+ * assembled as `join('.claude', ...)` is never joined back into a token and
18
+ * keeps its old spelling. And rewriting a test's fixtures can collapse a
19
+ * fallback test into a copy of its neighbor, which keeps passing while it
20
+ * covers nothing, so read the rewritten fixtures rather than trusting the run.
15
21
  */
16
22
 
17
23
  /**
@@ -34,7 +34,7 @@ import {
34
34
  slugOf,
35
35
  } from '@/migrate/evidence-ordinal'
36
36
  import { presentFolders } from '@/records/backup'
37
- import { recordDir, SCRATCH } from '@/roots/record'
37
+ import { LEGACY_SCRATCH_REL, recordDir, SCRATCH } from '@/roots/record'
38
38
 
39
39
  /**
40
40
  * Every folder this promotion moves, at the name it carries under scratch.
@@ -64,9 +64,17 @@ export const PROMOTED_FOLDERS: readonly string[] = [
64
64
  'target-survey',
65
65
  ]
66
66
 
67
- /** Where a promoted folder sits before the move. */
67
+ /**
68
+ * Where a promoted folder sits before the move: `.canon/tmp` when it holds the
69
+ * folder, so a target part-way through a move promotes the newer copy, and the
70
+ * retired scratch folder otherwise. The verb is a migration, so it reads an
71
+ * unmigrated target where it is.
72
+ */
68
73
  export function sourcePath(root: string, folder: string): string {
69
- return recordDir(root, SCRATCH, folder)
74
+ const current = recordDir(root, SCRATCH, folder)
75
+ const legacy = join(root, LEGACY_SCRATCH_REL, folder)
76
+
77
+ return !existsSync(current) && existsSync(legacy) ? legacy : current
70
78
  }
71
79
 
72
80
  /** The folder a promoted one lands inside, under whichever root carries it. */
@@ -85,7 +93,7 @@ function escape(value: string): string {
85
93
  * swallowing a sibling folder whose name extends it.
86
94
  */
87
95
  const OLD_PREFIXES = [
88
- '.claude/.tmp/',
96
+ '.claude/.tmp/', // canon-keep-record-root
89
97
  '.canon/tmp/',
90
98
  '../.tmp/',
91
99
  '../tmp/',