@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
@@ -0,0 +1,392 @@
1
+ import {
2
+ appendFileSync,
3
+ copyFileSync,
4
+ existsSync,
5
+ mkdirSync,
6
+ readdirSync,
7
+ readFileSync,
8
+ realpathSync,
9
+ statSync,
10
+ } from 'node:fs'
11
+ import {
12
+ basename,
13
+ dirname,
14
+ extname,
15
+ isAbsolute,
16
+ join,
17
+ relative,
18
+ resolve,
19
+ } from 'node:path'
20
+ import { primaryFontFamily } from '@/capture/sources'
21
+ import { DESIGN_INSTALL_DIR, DESIGN_PROJECT_SUBDIR } from '@/design/adapter'
22
+
23
+ /**
24
+ * A project's own faces, declared as plain `@font-face` rules in its override
25
+ * sheets and stored beside them under `project/`, which sync treats as
26
+ * project-authored by location and never writes. CSS is the declaration
27
+ * format because every consumer already reads it, and reading descriptors
28
+ * out of the font's own tables cannot open WOFF2 without a Brotli parser.
29
+ *
30
+ * A rule's `url()` is read from inside `project/` only, after `realpath`, so
31
+ * an override cannot carry a file from elsewhere on the machine into a frame.
32
+ * A hand-edited sheet reaches this module as often as `addProjectFace` does,
33
+ * so the check sits on the read rather than on the write.
34
+ */
35
+
36
+ export const PROJECT_FONTS_DIR = 'fonts'
37
+ export const PROJECT_FONTS_SHEET = 'fonts.css'
38
+
39
+ export type FaceProblem = 'missing' | 'outside-folder'
40
+
41
+ export interface ProjectFace {
42
+ readonly family: string
43
+ /** The `font-weight` descriptor verbatim, `400` where the rule omits it. */
44
+ readonly weight: string
45
+ readonly style: string
46
+ /** Relative to the root. */
47
+ readonly sheet: string
48
+ /** The first local `url()` as the rule writes it. */
49
+ readonly file: string
50
+ readonly present: boolean
51
+ readonly problem?: FaceProblem
52
+ }
53
+
54
+ export interface DroppedFace {
55
+ readonly sheet: string
56
+ readonly file: string
57
+ readonly reason: FaceProblem
58
+ }
59
+
60
+ export interface InlinedSheet {
61
+ readonly css: string
62
+ readonly dropped: readonly DroppedFace[]
63
+ /** The families of the rules that survived, in sheet order. */
64
+ readonly declared: readonly string[]
65
+ }
66
+
67
+ const FORMATS = {
68
+ '.woff2': { mime: 'font/woff2', format: 'woff2', magic: ['wOF2'] },
69
+ '.woff': { mime: 'font/woff', format: 'woff', magic: ['wOFF'] },
70
+ '.ttf': {
71
+ mime: 'font/ttf',
72
+ format: 'truetype',
73
+ magic: ['\u0000\u0001\u0000\u0000', 'true'],
74
+ },
75
+ '.otf': { mime: 'font/otf', format: 'opentype', magic: ['OTTO'] },
76
+ } as const
77
+
78
+ type FontExtension = keyof typeof FORMATS
79
+
80
+ function isFontExtension(ext: string): ext is FontExtension {
81
+ return Object.hasOwn(FORMATS, ext)
82
+ }
83
+
84
+ const FACE_RULE = /@font-face\s*\{[^}]*\}/g
85
+ const URL = /url\(\s*(['"]?)([^'")]+)\1\s*\)/g
86
+ const SCHEME = /^[a-z][a-z0-9+.-]*:/i
87
+ const DECLARATION = /([A-Za-z-]+)\s*:\s*([^;}]*)/g
88
+
89
+ /** Comments blanked to equal length, so an index into the result fits the source. */
90
+ function masked(css: string): string {
91
+ return css.replace(/\/\*[\s\S]*?\*\//g, (comment) =>
92
+ ' '.repeat(comment.length),
93
+ )
94
+ }
95
+
96
+ function descriptor(rule: string, name: string): string | undefined {
97
+ const match = rule.match(new RegExp(`(?:^|[;{\\s])${name}\\s*:\\s*([^;}]*)`))
98
+ return match?.[1]?.trim()
99
+ }
100
+
101
+ function familyOf(rule: string): string {
102
+ return primaryFontFamily(descriptor(rule, 'font-family') ?? '')
103
+ }
104
+
105
+ /**
106
+ * Every family a declaration value lists, quoted or not, across the sheet
107
+ * outside its `@font-face` rules, which declare a face rather than use one.
108
+ * Reads every property rather than only `font-family`, since a token such as
109
+ * `--figure-hand` names its stack under a name that ends in neither.
110
+ */
111
+ export function namedFamilies(css: string): Set<string> {
112
+ const uses = masked(css).replace(FACE_RULE, '')
113
+ const named = new Set<string>()
114
+ for (const [, , value] of uses.matchAll(DECLARATION)) {
115
+ for (const item of (value ?? '').split(',')) {
116
+ const family = primaryFontFamily(item)
117
+ if (family) named.add(family)
118
+ }
119
+ }
120
+ return named
121
+ }
122
+
123
+ function projectDir(root: string): string {
124
+ return join(root, DESIGN_INSTALL_DIR, DESIGN_PROJECT_SUBDIR)
125
+ }
126
+
127
+ function isWithin(folder: string, path: string): boolean {
128
+ const inside = relative(folder, path)
129
+ return inside !== '' && !inside.startsWith('..') && !isAbsolute(inside)
130
+ }
131
+
132
+ type Located =
133
+ | { readonly ok: true; readonly path: string }
134
+ | { readonly ok: false; readonly reason: FaceProblem }
135
+
136
+ /** A URL path as the name on disk, or undefined for a malformed escape. */
137
+ function decoded(url: string): string | undefined {
138
+ try {
139
+ return decodeURIComponent(url.replace(/[?#].*$/, ''))
140
+ } catch {
141
+ return undefined
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Confined lexically before any read, then again after resolving links. The
147
+ * check runs on the decoded path, so an escaped `..` cannot pass it.
148
+ */
149
+ function locate(root: string, sheet: string, url: string): Located {
150
+ const folder = projectDir(root)
151
+ const clean = decoded(url)
152
+ if (clean === undefined) return { ok: false, reason: 'missing' }
153
+ if (isAbsolute(clean)) return { ok: false, reason: 'outside-folder' }
154
+ const path = resolve(join(root, dirname(sheet)), clean)
155
+ if (!isWithin(folder, path)) return { ok: false, reason: 'outside-folder' }
156
+ if (!existsSync(path) || !statSync(path).isFile()) {
157
+ return { ok: false, reason: 'missing' }
158
+ }
159
+ if (!isWithin(realpathSync(folder), realpathSync(path))) {
160
+ return { ok: false, reason: 'outside-folder' }
161
+ }
162
+ return { ok: true, path }
163
+ }
164
+
165
+ function localUrls(rule: string): string[] {
166
+ return [...rule.matchAll(URL)]
167
+ .map((match) => match[2]?.trim() ?? '')
168
+ .filter((url) => url !== '' && !SCHEME.test(url))
169
+ }
170
+
171
+ function mimeOf(path: string): string {
172
+ const ext = extname(path).toLowerCase()
173
+ return isFontExtension(ext) ? FORMATS[ext].mime : 'application/octet-stream'
174
+ }
175
+
176
+ /**
177
+ * Rewrites each local `url()` in a sheet's `@font-face` rules to a `data:`
178
+ * URI, so a frame carries the face rather than a path the frame's origin
179
+ * cannot serve. A rule naming a file that is missing or leads out of
180
+ * `project/` is dropped whole and reported, and the sheet otherwise renders.
181
+ * A `data:` or remote URL is left as written.
182
+ */
183
+ export function inlineProjectFaces(
184
+ root: string,
185
+ sheet: string,
186
+ css: string,
187
+ ): InlinedSheet {
188
+ const dropped: DroppedFace[] = []
189
+ const declared: string[] = []
190
+ let out = ''
191
+ let cursor = 0
192
+ for (const match of masked(css).matchAll(FACE_RULE)) {
193
+ const start = match.index
194
+ const end = start + match[0].length
195
+ const rule = css.slice(start, end)
196
+ out += css.slice(cursor, start)
197
+ cursor = end
198
+
199
+ const urls = localUrls(match[0])
200
+ const located = urls.map((url) => ({ url, at: locate(root, sheet, url) }))
201
+ const failed = located.find(({ at }) => !at.ok)
202
+ if (failed && !failed.at.ok) {
203
+ dropped.push({ sheet, file: failed.url, reason: failed.at.reason })
204
+ continue
205
+ }
206
+ let inlined = rule
207
+ for (const { url, at } of located) {
208
+ if (!at.ok) continue
209
+ const data = `data:${mimeOf(at.path)};base64,${readFileSync(at.path).toString('base64')}`
210
+ inlined = inlined.replace(url, data)
211
+ }
212
+ out += inlined
213
+ declared.push(familyOf(match[0]))
214
+ }
215
+ return { css: out + css.slice(cursor), dropped, declared }
216
+ }
217
+
218
+ /** The project's override sheets, root-relative, in the order a frame cascades them. */
219
+ export function projectSheets(root: string): string[] {
220
+ const folder = projectDir(root)
221
+ if (!existsSync(folder)) return []
222
+ return readdirSync(folder)
223
+ .filter((name) => name.endsWith('.css'))
224
+ .sort((a, b) => a.localeCompare(b))
225
+ .map((name) => join(DESIGN_INSTALL_DIR, DESIGN_PROJECT_SUBDIR, name))
226
+ }
227
+
228
+ /** Every face the override sheets declare, without reading a font file. */
229
+ export function listProjectFaces(root: string): ProjectFace[] {
230
+ return projectSheets(root).flatMap((sheet) => {
231
+ const css = masked(readFileSync(join(root, sheet), 'utf8'))
232
+ return [...css.matchAll(FACE_RULE)].map((match): ProjectFace => {
233
+ const rule = match[0]
234
+ const local = localUrls(rule)[0]
235
+ const external = [...rule.matchAll(URL)][0]?.[2]?.trim() ?? ''
236
+ /*
237
+ * A rule carrying its face inline or remotely has no file to read, and
238
+ * the frame keeps it as written, so it lists as present. A `data:` URI
239
+ * is shortened to its scheme rather than echoing the whole face.
240
+ */
241
+ const file = local ?? (external.startsWith('data:') ? 'data:' : external)
242
+ const at: Located = local
243
+ ? locate(root, sheet, local)
244
+ : external
245
+ ? { ok: true, path: external }
246
+ : { ok: false, reason: 'missing' }
247
+ return {
248
+ family: familyOf(rule),
249
+ weight: descriptor(rule, 'font-weight') ?? '400',
250
+ style: descriptor(rule, 'font-style') ?? 'normal',
251
+ sheet,
252
+ file,
253
+ present: at.ok,
254
+ ...(!at.ok && { problem: at.reason }),
255
+ }
256
+ })
257
+ })
258
+ }
259
+
260
+ export interface FaceSpec {
261
+ readonly family: string
262
+ readonly weight?: string
263
+ readonly style?: string
264
+ }
265
+
266
+ export type AddOutcome =
267
+ | {
268
+ readonly ok: true
269
+ readonly family: string
270
+ readonly weight: string
271
+ readonly style: string
272
+ /** Relative to the sheet's folder, as the rule writes it. */
273
+ readonly file: string
274
+ readonly sheet: string
275
+ }
276
+ | {
277
+ readonly ok: false
278
+ readonly reason: 'invalid-font' | 'invalid-face' | 'exists'
279
+ readonly detail: string
280
+ }
281
+
282
+ /** A name CSS takes inside single quotes with no escaping. */
283
+ const SAFE_FAMILY = /^[A-Za-z0-9][A-Za-z0-9 _-]*$/
284
+ /** A file name a single-quoted `url()` takes with no escaping. */
285
+ const SAFE_FILE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/
286
+ const WEIGHT = /^\d{1,4}( \d{1,4})?$/
287
+
288
+ function weightInRange(weight: string): boolean {
289
+ return weight.split(' ').every((n) => Number(n) >= 1 && Number(n) <= 1000)
290
+ }
291
+
292
+ /**
293
+ * Copies a face into `project/fonts/` and appends one rule for it to
294
+ * `project/fonts.css`. Refuses before writing anything when the extension is
295
+ * not a font, the bytes disagree with it, a descriptor would need escaping,
296
+ * or the folder already holds a file of that name.
297
+ */
298
+ export function addProjectFace(
299
+ root: string,
300
+ source: string,
301
+ spec: FaceSpec,
302
+ ): AddOutcome {
303
+ const name = basename(source)
304
+ const ext = extname(name).toLowerCase()
305
+ if (!isFontExtension(ext)) {
306
+ return {
307
+ ok: false,
308
+ reason: 'invalid-font',
309
+ detail: `${name} is not a font. Use woff2, woff, ttf, or otf`,
310
+ }
311
+ }
312
+ if (!SAFE_FILE.test(name)) {
313
+ return {
314
+ ok: false,
315
+ reason: 'invalid-font',
316
+ detail: `${name} must be letters, digits, dots, dashes, or underscores to sit in a url(). Rename it`,
317
+ }
318
+ }
319
+ if (!existsSync(source) || !statSync(source).isFile()) {
320
+ return { ok: false, reason: 'invalid-font', detail: `${source} not found` }
321
+ }
322
+ const head = readFileSync(source).subarray(0, 4).toString('binary')
323
+ if (!FORMATS[ext].magic.some((magic) => head === magic)) {
324
+ return {
325
+ ok: false,
326
+ reason: 'invalid-font',
327
+ detail: `${name} does not read as ${ext.slice(1)} from its first bytes`,
328
+ }
329
+ }
330
+
331
+ const weight = spec.weight ?? '400'
332
+ const style = spec.style ?? 'normal'
333
+ if (!SAFE_FAMILY.test(spec.family)) {
334
+ return {
335
+ ok: false,
336
+ reason: 'invalid-face',
337
+ detail: `family ${spec.family} must be letters, digits, spaces, dashes, or underscores`,
338
+ }
339
+ }
340
+ if (!WEIGHT.test(weight) || !weightInRange(weight)) {
341
+ return {
342
+ ok: false,
343
+ reason: 'invalid-face',
344
+ detail: `weight ${weight} must be one number or a range of two, from 1 to 1000`,
345
+ }
346
+ }
347
+ if (style !== 'normal' && style !== 'italic') {
348
+ return {
349
+ ok: false,
350
+ reason: 'invalid-face',
351
+ detail: `style ${style} must be normal or italic`,
352
+ }
353
+ }
354
+
355
+ const folder = join(projectDir(root), PROJECT_FONTS_DIR)
356
+ const dest = join(folder, name)
357
+ const file = `${PROJECT_FONTS_DIR}/${name}`
358
+ if (existsSync(dest)) {
359
+ return {
360
+ ok: false,
361
+ reason: 'exists',
362
+ detail: `${relative(root, dest)} already exists. Remove it or rename the source`,
363
+ }
364
+ }
365
+
366
+ mkdirSync(folder, { recursive: true })
367
+ copyFileSync(source, dest)
368
+ const sheetPath = join(projectDir(root), PROJECT_FONTS_SHEET)
369
+ const lead =
370
+ existsSync(sheetPath) && readFileSync(sheetPath, 'utf8').trim() !== ''
371
+ ? '\n'
372
+ : ''
373
+ appendFileSync(
374
+ sheetPath,
375
+ `${lead}@font-face {
376
+ font-family: '${spec.family}';
377
+ font-weight: ${weight};
378
+ font-style: ${style};
379
+ font-display: swap;
380
+ src: url('${file}') format('${FORMATS[ext].format}');
381
+ }
382
+ `,
383
+ )
384
+ return {
385
+ ok: true,
386
+ family: spec.family,
387
+ weight,
388
+ style,
389
+ file,
390
+ sheet: relative(root, sheetPath),
391
+ }
392
+ }
@@ -2,11 +2,8 @@
2
2
  * The design system's one source of values.
3
3
  *
4
4
  * `canon/DESIGN.md` is rendered from this module rather than read by it, so
5
- * the document a person opens is a view and this file is the fact. Every
6
- * rendering surface reads from here in the form it can take: a CSS surface
7
- * takes custom properties through `@/design/css`, and the slide renderer takes
8
- * bare hex through `bareHex` below, because PowerPoint has no concept of a
9
- * custom property.
5
+ * the document a person opens is a view and this file is the fact. A CSS
6
+ * surface reads it as custom properties through `@/design/css`.
10
7
  *
11
8
  * The two artifacts can disagree, which is what the `design` gate stage exists
12
9
  * to catch. Nothing else compares them.
@@ -371,16 +368,3 @@ export function colorValue(
371
368
  ): string | undefined {
372
369
  return tokens.color.find((token) => token.role === role)?.value
373
370
  }
374
-
375
- /**
376
- * The adapter the slide renderer takes. `PptxGenJS` receives color as
377
- * `{ color: theme.background }` and wants six hex digits with no leading `#`,
378
- * so the shared thing is the value and this is the per-consumer form.
379
- *
380
- * The hex is raised to upper case because that is the spelling
381
- * `src/slides/styles.ts` has always written, and the only one a diff of a
382
- * rendered deck reads cleanly against.
383
- */
384
- export function bareHex(value: string): string {
385
- return value.replace(/^#/, '').toUpperCase()
386
- }
package/src/docs/read.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { existsSync, readFileSync, readdirSync } 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 { SURFACE_ROOTS } from '@/surface-root'
5
5
 
6
6
  const INDEX_TOPIC = 'index'
@@ -1,12 +1,12 @@
1
1
  import { execa } from 'execa'
2
- import { gitEnv } from '@/git-env'
2
+ import { gitEnv } from '@/git/env'
3
3
  import { PROJECT_ROOT } from '@/project-root'
4
4
  import {
5
5
  buildIssueArgs,
6
6
  failureDetail,
7
7
  type CreateIssueOptions,
8
8
  type CreateIssueResult,
9
- } from '@/github-format'
9
+ } from '@/feedback/github-format'
10
10
 
11
11
  const GH_TIMEOUT_MS = 30_000
12
12
 
@@ -1,4 +1,4 @@
1
- import { escapeForPattern, isMarked } from '@/exempt-marker'
1
+ import { escapeForPattern, isMarked } from '@/sweep/exempt-marker'
2
2
 
3
3
  /**
4
4
  * A client command this repository quotes, with the argument spelling every
@@ -6,7 +6,7 @@ import {
6
6
  CLIENT_COMMANDS,
7
7
  type ClientCommand,
8
8
  clientCommandCitationsIn,
9
- } from '@/client-commands'
9
+ } from '@/gate/client-commands'
10
10
  import {
11
11
  isOverCount,
12
12
  isOverLength,
@@ -18,13 +18,15 @@ import {
18
18
  import { isOverWordCap, measureRequirements } from '@/context/requirements'
19
19
  import {
20
20
  auditSkills,
21
+ LEDGER_SKILLS,
21
22
  PRACTICE_LEDGER,
22
23
  PRACTICE_SKILLS,
23
24
  SHIPPED_CORPUS,
24
25
  } from '@/claude/skills-audit'
26
+ import { familyKeys } from '@/claude/skills-families'
25
27
  import { measureDesignProse } from '@/design/budget'
26
28
  import { DESIGN_DOCUMENT } from '@/design/regen'
27
- import { listRepositoryFiles } from '@/git-files'
29
+ import { listRepositoryFiles } from '@/git/files'
28
30
  import { ceilingFindings } from '@/markdown/ceiling'
29
31
  import { resolveMarkdown } from '@/markdown/files'
30
32
  import { CHECKPOINTS } from '@/markdown/structure'
@@ -165,7 +167,7 @@ export function auditsBaselineRel(root: string): string {
165
167
  }
166
168
 
167
169
  export const CAPTURE_STAMP_FAILURE =
168
- 'A capture set disagrees with the stamp written when its image was captured. Run canon capture assets/captures --selector .window --out assets/evidence and commit each frame with its image and its stamp.'
170
+ 'A capture set disagrees with the stamp written when its image was captured. Run canon capture assets/captures --selector .window --out assets/frames and commit each frame with its image and its stamp.'
169
171
 
170
172
  function parseJson(payload: string): unknown {
171
173
  try {
@@ -411,8 +413,9 @@ export const skillProvenance: Measure = async (ctx) => {
411
413
  }
412
414
 
413
415
  /**
414
- * Every listed practice skill against the closing sections and the ledger
415
- * `standards/skill-practice.md` requires of one.
416
+ * Every listed practice skill against the closing sections, and every skill on
417
+ * the wider ledger list against the ledger, both of which
418
+ * `standards/skill-practice.md` requires.
416
419
  *
417
420
  * Read in-process for the reason `skillProvenance` is, so the verb keeps its
418
421
  * missing-requirement-only exit. The list names shipped folders alone, so a
@@ -434,19 +437,55 @@ export const skillPracticeShape: Measure = async (ctx) => {
434
437
  .join(', ')
435
438
  return {
436
439
  emissions: [],
437
- failure: `${plural(found.length, 'practice shape finding')}: ${where}. Add the closing H2 exactly as named, or the ledger at ${PRACTICE_LEDGER}.`,
440
+ failure: `${plural(found.length, 'practice shape finding')}: ${where}. Add the closing H2 exactly as named to a practice skill, or the ledger at ${PRACTICE_LEDGER} to any skill on the ledger list.`,
438
441
  }
439
442
  }
440
443
 
441
444
  return {
442
445
  emissions: [
443
446
  info(
444
- `${plural(PRACTICE_SKILLS.length, 'practice skill')} carry every closing section and a ledger`,
447
+ `${plural(PRACTICE_SKILLS.length, 'practice skill')} carry every closing section, and ${plural(LEDGER_SKILLS.length, 'listed skill')} carry a ledger`,
445
448
  ),
446
449
  ],
447
450
  }
448
451
  }
449
452
 
453
+ /**
454
+ * Every shipped skill against the family vocabulary, since the landing page
455
+ * groups the skill map by the field and a skill without one has no group.
456
+ *
457
+ * Read in-process for the reason `skillProvenance` is. Only the shipped corpus
458
+ * takes a family, so a tree without `claude/skills/` reports unmeasured rather
459
+ * than a vacuous pass.
460
+ */
461
+ export const skillFamily: Measure = async (ctx) => {
462
+ const report = await auditSkills(ctx.root)
463
+ const shipped = report.corpora.find((corpus) => corpus.rel === SHIPPED_CORPUS)
464
+ if (shipped === undefined) {
465
+ return {
466
+ emissions: [],
467
+ unmeasured: `no skill corpus at ${SHIPPED_CORPUS} resolved, so no family was read.`,
468
+ }
469
+ }
470
+
471
+ const found = report.family
472
+ if (found.length > 0) {
473
+ const where = found
474
+ .map((finding) => `${finding.rel} ${finding.detail}`)
475
+ .join(', ')
476
+ return {
477
+ emissions: [],
478
+ failure: `${plural(found.length, 'skill family finding')}: ${where}. Add metadata.family to the frontmatter, one of ${familyKeys().join(', ')}, matching the skill map group its row sits under.`,
479
+ }
480
+ }
481
+
482
+ return {
483
+ emissions: [
484
+ info(`${plural(shipped.skills, 'shipped skill')} declare a known family`),
485
+ ],
486
+ }
487
+ }
488
+
450
489
  /**
451
490
  * Every tracked markdown document against the whole-document ceiling.
452
491
  *
@@ -980,7 +1019,7 @@ export const clientCommandCitations = async (
980
1019
  return {
981
1020
  emissions: [],
982
1021
  failure:
983
- 'The client command table is empty, so the corpus was walked and nothing was looked for. Check src/client-commands.ts.',
1022
+ 'The client command table is empty, so the corpus was walked and nothing was looked for. Check src/gate/client-commands.ts.',
984
1023
  }
985
1024
  }
986
1025
 
@@ -1021,8 +1060,8 @@ export const clientCommandCitations = async (
1021
1060
  ),
1022
1061
  failure:
1023
1062
  found.length === 1
1024
- ? `One tracked citation quotes a client command with the wrong argument. Match it against the canonical form in src/client-commands.ts, or mark the line ${CLIENT_COMMAND_MARKER}: <reason> where the argument differs on purpose.`
1025
- : `${found.length} tracked citations quote a client command with the wrong argument. Match each against the canonical form in src/client-commands.ts, or mark the line ${CLIENT_COMMAND_MARKER}: <reason> where the argument differs on purpose.`,
1063
+ ? `One tracked citation quotes a client command with the wrong argument. Match it against the canonical form in src/gate/client-commands.ts, or mark the line ${CLIENT_COMMAND_MARKER}: <reason> where the argument differs on purpose.`
1064
+ : `${found.length} tracked citations quote a client command with the wrong argument. Match each against the canonical form in src/gate/client-commands.ts, or mark the line ${CLIENT_COMMAND_MARKER}: <reason> where the argument differs on purpose.`,
1026
1065
  }
1027
1066
  }
1028
1067
 
@@ -1476,11 +1515,12 @@ async function collectPluginManifests(ctx: MeasureContext): Promise<string[]> {
1476
1515
  * A capture set spans two folders. The markup and the template beside it are
1477
1516
  * the authored half and sit under `assets/captures/`, while the image a
1478
1517
  * document points at and the stamp answering for it sit under
1479
- * `assets/evidence/`, which is where `--out assets/evidence` sends them and where
1480
- * `canon pr evidence` looks for a changed image.
1518
+ * `assets/frames/`, which is where `--out assets/frames` sends them. They sit
1519
+ * outside `assets/evidence/` so a changed marketing image is not read as dev
1520
+ * evidence.
1481
1521
  */
1482
1522
  const CAPTURE_MARKUP_DIR = 'assets/captures'
1483
- const CAPTURE_OUTPUT_DIR = 'assets/evidence'
1523
+ const CAPTURE_OUTPUT_DIR = 'assets/frames'
1484
1524
 
1485
1525
  /**
1486
1526
  * Every capture under `assets/captures/`, named by the base its three files
@@ -7,7 +7,7 @@ import type {
7
7
  RunCommand,
8
8
  } from '@/gate/measures'
9
9
  import type { Check, Stage } from '@/gate/stages'
10
- import { gitEnv } from '@/git-env'
10
+ import { gitEnv } from '@/git/env'
11
11
  import { PROJECT_ROOT } from '@/project-root'
12
12
 
13
13
  export type StageStatus =
@@ -22,6 +22,7 @@ import {
22
22
  seedStandards,
23
23
  rawFieldFileReference,
24
24
  shippedReferences,
25
+ skillFamily,
25
26
  skillPracticeShape,
26
27
  skillProvenance,
27
28
  standardCriteria,
@@ -496,6 +497,14 @@ export const STAGES: readonly Stage[] = [
496
497
  label: 'Skill practice shape',
497
498
  checks: [{ kind: 'measure', measure: skillPracticeShape }],
498
499
  },
500
+ {
501
+ // The audit verb only reports a shipped skill with a missing or unknown
502
+ // family, so a target is told without failing. The landing page groups the
503
+ // skill map by the field, so the stage holds this corpus at zero.
504
+ id: 'skill-family',
505
+ label: 'Skill family',
506
+ checks: [{ kind: 'measure', measure: skillFamily }],
507
+ },
499
508
  {
500
509
  id: 'standard-criteria',
501
510
  label: 'Standard success criteria',
@@ -1,6 +1,6 @@
1
1
  import { $ } from 'bun'
2
- import { baseCandidates, isMergeBase } from '@/git-base'
3
- import { gitEnv } from '@/git-env'
2
+ import { baseCandidates, isMergeBase } from '@/git/base'
3
+ import { gitEnv } from '@/git/env'
4
4
 
5
5
  /** Runs git under `root` with the resolution variables a hook exports stripped. */
6
6
  async function git(
@@ -1,6 +1,6 @@
1
1
  import { resolve } from 'node:path'
2
2
  import { $ } from 'bun'
3
- import { gitEnv } from '@/git-env'
3
+ import { gitEnv } from '@/git/env'
4
4
 
5
5
  /**
6
6
  * Reports which of `candidates` git ignores under `root`.
@@ -1,5 +1,5 @@
1
1
  import { $ } from 'bun'
2
- import { gitEnv } from '@/git-env'
2
+ import { gitEnv } from '@/git/env'
3
3
 
4
4
  /**
5
5
  * Resolves the root of the checkout the caller is standing in, which is the
@@ -2,8 +2,8 @@ import { existsSync } from 'node:fs'
2
2
  import { readFile } from 'node:fs/promises'
3
3
  import { dirname, join, resolve } from 'node:path'
4
4
  import { $ } from 'bun'
5
- import { isMarked } from '@/exempt-marker'
6
- import { gitEnv } from '@/git-env'
5
+ import { isMarked } from '@/sweep/exempt-marker'
6
+ import { gitEnv } from '@/git/env'
7
7
  import { listRuleFiles } from '@/gov/payload'
8
8
  import { parseFrontmatter } from '@/indexes/frontmatter'
9
9
  import { findBacktickSpans } from '@/markdown/backticks'
@@ -1,6 +1,6 @@
1
1
  import { existsSync } from 'node:fs'
2
2
  import { basename, dirname, join, relative } from 'node:path'
3
- import { copyPreservingMode } from '@/copy'
3
+ import { copyPreservingMode } from '@/sync/copy'
4
4
 
5
5
  export interface RuleSource {
6
6
  readonly rule: string
@@ -1,6 +1,6 @@
1
1
  import { readFileSync } from 'node:fs'
2
2
  import { basename, resolve } from 'node:path'
3
- import { stripFrontmatter } from '@/frontmatter'
3
+ import { stripFrontmatter } from '@/markdown/frontmatter'
4
4
 
5
5
  /**
6
6
  * Lists installed rules in the order the payload concatenates them. Sorting by