@erclx/canon 5.33.0 → 5.35.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 (190) hide show
  1. package/README.md +3 -5
  2. package/claude/.claude-plugin/plugin.json +1 -1
  3. package/claude/skills/api-design/REQUIREMENT.md +2 -3
  4. package/claude/skills/api-design/SKILL.md +2 -3
  5. package/claude/skills/api-design/references/adopted.md +1 -1
  6. package/claude/skills/auto-ship/references/draft-mark.md +1 -1
  7. package/claude/skills/canon-cli/SKILL.md +10 -11
  8. package/claude/skills/canon-feedback/REQUIREMENT.md +1 -0
  9. package/claude/skills/canon-feedback/SKILL.md +10 -28
  10. package/claude/skills/canon-feedback/references/template.md +53 -0
  11. package/claude/skills/canvas-board/SKILL.md +4 -1
  12. package/claude/skills/canvas-board/references/canvas.md +26 -3
  13. package/claude/skills/code-craft/REQUIREMENT.md +1 -1
  14. package/claude/skills/code-craft/SKILL.md +0 -1
  15. package/claude/skills/create-rule/SKILL.md +1 -1
  16. package/claude/skills/create-rule/references/rule.md +3 -1
  17. package/claude/skills/create-skill/SKILL.md +2 -1
  18. package/claude/skills/create-skill/references/skill-paths.md +12 -15
  19. package/{standards/standard.md → claude/skills/create-skill/references/skill-reference.md} +50 -45
  20. package/claude/skills/create-skill/references/skill.md +10 -4
  21. package/claude/skills/draft-and-pick/SKILL.md +1 -1
  22. package/claude/skills/git-commit/references/versioning.md +13 -13
  23. package/claude/skills/git-issue/references/issue.md +4 -1
  24. package/claude/skills/git-pr/SKILL.md +1 -1
  25. package/claude/skills/git-pr/references/publish.md +1 -1
  26. package/claude/skills/review-address/REQUIREMENT.md +1 -1
  27. package/claude/skills/review-address/SKILL.md +2 -2
  28. package/claude/skills/review-craft/references/ui.md +1 -1
  29. package/claude/skills/review-pr/SKILL.md +3 -3
  30. package/claude/skills/review-pr/references/post.md +1 -1
  31. package/claude/skills/review-ui/REQUIREMENT.md +1 -1
  32. package/claude/skills/review-ui/SKILL.md +8 -6
  33. package/claude/skills/review-ui/references/driving.md +8 -8
  34. package/claude/skills/review-ui/references/post.md +52 -16
  35. package/claude/skills/role-orchestrator/REQUIREMENT.md +2 -2
  36. package/claude/skills/role-orchestrator/SKILL.md +11 -12
  37. package/claude/skills/role-orchestrator/references/orchestrator-cloud-launch.md +3 -1
  38. package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +11 -8
  39. package/claude/skills/role-orchestrator/references/orchestrator-handback.md +10 -4
  40. package/claude/skills/role-orchestrator/references/orchestrator-launch.md +11 -2
  41. package/claude/skills/role-orchestrator/references/orchestrator-poll.md +11 -12
  42. package/claude/skills/role-orchestrator/references/orchestrator-review-dispatch.md +12 -15
  43. package/claude/skills/role-orchestrator/references/orchestrator-reviewer-launch.md +6 -4
  44. package/claude/skills/role-orchestrator/scripts/poll.ts +50 -1
  45. package/claude/skills/role-reviewer/REQUIREMENT.md +17 -7
  46. package/claude/skills/role-reviewer/SKILL.md +17 -10
  47. package/claude/skills/role-worker/REQUIREMENT.md +1 -1
  48. package/claude/skills/role-worker/SKILL.md +6 -6
  49. package/claude/skills/session-compact/references/handoff-note.md +1 -1
  50. package/claude/skills/target-setup/SKILL.md +12 -6
  51. package/claude/skills/target-setup/references/detect.md +22 -5
  52. package/claude/skills/test-craft/REQUIREMENT.md +2 -3
  53. package/claude/skills/test-craft/SKILL.md +15 -3
  54. package/docs/agents/audits.md +1 -1
  55. package/docs/agents/canvas.md +5 -1
  56. package/docs/agents/commands.md +3 -5
  57. package/docs/agents/counts.md +5 -6
  58. package/docs/agents/drift-surfaces.md +15 -19
  59. package/docs/agents/gov-detect.md +47 -0
  60. package/docs/agents/index.md +6 -4
  61. package/docs/agents/install-and-sync.md +11 -31
  62. package/docs/agents/markdown-audit.md +10 -12
  63. package/docs/agents/migrate.md +1 -1
  64. package/docs/agents/pr-evidence-layout.md +70 -0
  65. package/docs/agents/pr-evidence.md +8 -9
  66. package/docs/agents/pr-frames.md +7 -2
  67. package/docs/agents/pr-lift.md +132 -0
  68. package/docs/agents/records-stale.md +1 -1
  69. package/docs/agents/records-validate.md +11 -23
  70. package/docs/agents/records.md +1 -1
  71. package/docs/agents/routing.md +1 -1
  72. package/docs/agents/rule-citations.md +11 -14
  73. package/docs/agents/scripting.md +4 -21
  74. package/docs/agents/state-scoped-risk.md +1 -1
  75. package/docs/agents/tasks-validate.md +1 -1
  76. package/docs/agents/teach.md +1 -1
  77. package/docs/target/index.md +1 -0
  78. package/docs/target/migrations.md +4 -0
  79. package/docs/target/projects.md +11 -11
  80. package/docs/target/scenarios.md +5 -6
  81. package/docs/target/standards-moved.md +32 -0
  82. package/docs/target/sync.md +5 -5
  83. package/docs/workflow/operating-model.md +16 -18
  84. package/docs/workflow/skill-map.md +1 -1
  85. package/governance/detect.toml +197 -0
  86. package/governance/renames.toml +0 -1
  87. package/governance/rules/code/000-code.md +1 -13
  88. package/governance/rules/code/010-testing.md +2 -30
  89. package/governance/rules/ui/400-ui.md +1 -1
  90. package/governance/rules/ui/480-canvas.md +13 -0
  91. package/governance/stacks/base.toml +15 -8
  92. package/governance/stacks/go.toml +2 -0
  93. package/governance/stacks/node.toml +1 -1
  94. package/governance/stacks/php.toml +2 -0
  95. package/governance/stacks/python.toml +1 -1
  96. package/package.json +1 -4
  97. package/scripts/core/install-check.sh +2 -3
  98. package/scripts/core/regen-hero.ts +0 -6
  99. package/scripts/lib/ui.sh +0 -9
  100. package/src/audits/catalog.ts +0 -9
  101. package/src/canvas/arrange.ts +66 -0
  102. package/src/canvas/capture.ts +20 -6
  103. package/src/canvas/content.ts +39 -0
  104. package/src/canvas/fit.ts +238 -0
  105. package/src/canvas/tokens.ts +28 -10
  106. package/src/capture/frame-values.ts +2 -32
  107. package/src/claude/routing.ts +1 -1
  108. package/src/claude/skills-reach.ts +4 -6
  109. package/src/cli/help.ts +3 -4
  110. package/src/cli.ts +0 -2
  111. package/src/commands/canvas.ts +179 -1
  112. package/src/commands/gov.ts +149 -10
  113. package/src/commands/init.ts +1 -1
  114. package/src/commands/labels.ts +4 -44
  115. package/src/commands/migrate.ts +1 -1
  116. package/src/commands/pr.ts +376 -1
  117. package/src/commands/records.ts +4 -20
  118. package/src/commands/tooling.ts +2 -4
  119. package/src/counts/catalogs.ts +0 -6
  120. package/src/feedback/body.ts +26 -5
  121. package/src/gate/boundaries.ts +2 -2
  122. package/src/gate/measures.ts +64 -72
  123. package/src/gate/sequencer.ts +51 -2
  124. package/src/gate/stages.ts +3 -22
  125. package/src/gov/citations.ts +11 -43
  126. package/src/gov/detect.ts +301 -0
  127. package/src/gov/list.ts +23 -2
  128. package/src/gov/stacks.ts +9 -0
  129. package/src/markdown/bans.ts +1 -1
  130. package/src/migrate/scratch-evidence.ts +2 -2
  131. package/src/pr/evidence.ts +218 -45
  132. package/src/pr/frames.ts +23 -3
  133. package/src/pr/lift.ts +418 -0
  134. package/src/pr/rest.ts +3 -0
  135. package/src/pr/review-scope.ts +76 -0
  136. package/src/records/validate.ts +6 -194
  137. package/src/sandbox/equivalence.ts +14 -7
  138. package/src/sandbox/provision.ts +0 -5
  139. package/src/secrets/shipped.ts +1 -2
  140. package/src/serve/static.ts +1 -2
  141. package/src/shipped/references.ts +29 -80
  142. package/src/sync/check.ts +18 -14
  143. package/src/tooling/inject.ts +6 -37
  144. package/src/tooling/read.ts +2 -3
  145. package/src/tooling/scan.ts +4 -17
  146. package/src/tooling/subfolder.ts +2 -66
  147. package/tooling/base/configs/scripts/verify.json +1 -1
  148. package/tooling/base/configs/scripts/verify.ts +1 -12
  149. package/tooling/base/manifest.toml +1 -2
  150. package/tooling/base/reference.md +10 -16
  151. package/tooling/base/seeds/.github/workflows/verify.yml +0 -3
  152. package/tooling/base/seeds/.lintstagedrc +1 -2
  153. package/tooling/base/seeds/canon/context/ci.md +0 -1
  154. package/tooling/base/seeds/canon/context/development.md +1 -1
  155. package/tooling/claude/reference.md +1 -1
  156. package/tooling/claude/seeds/.claude/hooks/review-post-scan.sh +13 -1
  157. package/tooling/go/reference.md +1 -1
  158. package/tooling/php/reference.md +1 -1
  159. package/tooling/python/reference.md +2 -2
  160. package/tooling/web/configs/.github/workflows/verify.yml +0 -3
  161. package/tooling/web/manifest.toml +0 -1
  162. package/tooling/web/reference.md +2 -4
  163. package/docs/agents/standards-audit.md +0 -40
  164. package/governance/rules/tooling/765-spelling.md +0 -19
  165. package/governance/stacks/astro.toml +0 -2
  166. package/governance/stacks/nextjs.toml +0 -2
  167. package/governance/stacks/node-server.toml +0 -2
  168. package/governance/stacks/python-fastapi.toml +0 -2
  169. package/governance/stacks/react.toml +0 -2
  170. package/src/commands/standards.ts +0 -278
  171. package/src/labels/spelling.ts +0 -120
  172. package/src/standards/audit.ts +0 -146
  173. package/src/standards/list.ts +0 -33
  174. package/src/standards/read.ts +0 -177
  175. package/src/standards/scope.ts +0 -56
  176. package/standards/index.md +0 -10
  177. package/tooling/base/configs/.vscode/extensions.json +0 -9
  178. package/tooling/base/configs/.vscode/settings.json +0 -3
  179. package/tooling/base/seeds/.cspell/project-terms.txt +0 -0
  180. package/tooling/base/seeds/.cspell/tech-stack.txt +0 -25
  181. package/tooling/base/seeds/cspell.json +0 -20
  182. package/tooling/go/seeds/.cspell/tech-stack.txt +0 -16
  183. package/tooling/nextjs/seeds/.cspell/tech-stack.txt +0 -3
  184. package/tooling/php/seeds/.cspell/tech-stack.txt +0 -5
  185. package/tooling/python/seeds/.cspell/tech-stack.txt +0 -18
  186. package/tooling/vite-react/seeds/.cspell/project-terms.txt +0 -1
  187. package/tooling/vite-react/seeds/.cspell/tech-stack.txt +0 -2
  188. package/tooling/web/configs/.vscode/extensions.json +0 -13
  189. package/tooling/web/configs/.vscode/settings.json +0 -10
  190. package/tooling/web/seeds/.cspell/tech-stack.txt +0 -21
@@ -0,0 +1,197 @@
1
+ # Evidence that marks a project as wanting an opt-in rule. Each table is keyed by
2
+ # a rule basename, the same key `renames.toml` and the stacks use, so a rename
3
+ # sweep reaches this file too. `canon gov detect` reads it and proposes every
4
+ # rule whose evidence a target holds, and `target-setup` puts those proposals to
5
+ # the operator before anything installs.
6
+ #
7
+ # `deps` are package names read from `package.json` (`dependencies` and
8
+ # `devDependencies`), `pyproject.toml`, `go.mod`, and `composer.json`, at the
9
+ # root and in every folder to depth two. `files` are globs matched against the
10
+ # target's git-listed paths to depth two, and against each folder on those
11
+ # paths, so `.github/workflows` matches a folder holding a workflow. A glob with
12
+ # no wildcard is also tested on disk, which reaches a gitignored path such as
13
+ # `.canon/canvas`.
14
+ #
15
+ # Every rule no stack reaches needs a table here, which the `unreferenced-rules`
16
+ # gate stage checks, along with every key naming a live rule. A table for a rule
17
+ # a stack already reaches is harmless, since the verb leaves out every rule the
18
+ # caller's stack resolves.
19
+
20
+ ["skill"]
21
+ deps = []
22
+ files = [".claude/skills", "claude/skills"]
23
+
24
+ ["700-ci-workflow"]
25
+ deps = []
26
+ files = [".github/workflows"]
27
+
28
+ ["775-dependencies"]
29
+ deps = []
30
+ files = ["**/package.json", "**/pyproject.toml", "**/go.mod", "**/composer.json"]
31
+
32
+ ["780-config-comments"]
33
+ deps = []
34
+ files = [".editorconfig", "**/Dockerfile*", "**/*.config.*", "**/.env.example"]
35
+
36
+ ["790-code-comments"]
37
+ deps = []
38
+ files = ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx", "**/*.sh", "**/*.py"]
39
+
40
+ ["130-go"]
41
+ deps = []
42
+ files = ["**/go.mod"]
43
+
44
+ ["140-php"]
45
+ deps = []
46
+ files = ["**/composer.json"]
47
+
48
+ ["200-react"]
49
+ deps = ["react"]
50
+ files = []
51
+
52
+ ["210-astro"]
53
+ deps = ["astro"]
54
+ files = ["**/astro.config.*"]
55
+
56
+ ["220-fastapi"]
57
+ deps = ["fastapi"]
58
+ files = []
59
+
60
+ ["230-nextjs"]
61
+ deps = ["next"]
62
+ files = ["**/next.config.*"]
63
+
64
+ ["250-tailwind"]
65
+ deps = ["tailwindcss", "@tailwindcss/vite", "@tailwindcss/postcss"]
66
+ files = ["**/tailwind.config.*"]
67
+
68
+ ["260-shadcn"]
69
+ deps = []
70
+ files = ["**/components.json"]
71
+
72
+ ["305-e2e-reliability"]
73
+ deps = ["@playwright/test", "playwright", "cypress"]
74
+ files = ["**/e2e"]
75
+
76
+ ["306-test-scope"]
77
+ deps = ["@playwright/test", "playwright", "cypress"]
78
+ files = ["**/e2e"]
79
+
80
+ ["310-zod"]
81
+ deps = ["zod"]
82
+ files = []
83
+
84
+ ["320-tanstack-query"]
85
+ deps = [
86
+ "@tanstack/react-query",
87
+ "@tanstack/vue-query",
88
+ "@tanstack/solid-query",
89
+ "@tanstack/svelte-query",
90
+ "@tanstack/query-core",
91
+ ]
92
+ files = []
93
+
94
+ ["335-testing-go"]
95
+ deps = []
96
+ files = ["**/go.mod"]
97
+
98
+ ["336-testing-php"]
99
+ deps = ["phpunit/phpunit", "pestphp/pest"]
100
+ files = ["**/phpunit.xml", "**/phpunit.xml.dist"]
101
+
102
+ ["340-pydantic"]
103
+ deps = ["pydantic"]
104
+ files = []
105
+
106
+ ["350-security-web"]
107
+ deps = ["react", "vue", "svelte", "astro", "next", "solid-js", "preact"]
108
+ files = ["index.html", "*/index.html"]
109
+
110
+ ["360-security-server"]
111
+ deps = [
112
+ "express",
113
+ "fastify",
114
+ "hono",
115
+ "koa",
116
+ "elysia",
117
+ "@nestjs/core",
118
+ "fastapi",
119
+ "django",
120
+ "flask",
121
+ "starlette",
122
+ "laravel/framework",
123
+ "symfony/http-kernel",
124
+ "slim/slim",
125
+ "github.com/gin-gonic/gin",
126
+ "github.com/labstack/echo/v4",
127
+ "github.com/go-chi/chi/v5",
128
+ "github.com/gofiber/fiber/v2",
129
+ ]
130
+ files = []
131
+
132
+ ["370-database"]
133
+ deps = [
134
+ "prisma",
135
+ "@prisma/client",
136
+ "drizzle-orm",
137
+ "typeorm",
138
+ "sequelize",
139
+ "knex",
140
+ "kysely",
141
+ "mongoose",
142
+ "pg",
143
+ "mysql2",
144
+ "better-sqlite3",
145
+ "sqlalchemy",
146
+ "alembic",
147
+ "psycopg",
148
+ "psycopg2",
149
+ "psycopg2-binary",
150
+ "asyncpg",
151
+ "django",
152
+ "doctrine/orm",
153
+ "illuminate/database",
154
+ "gorm.io/gorm",
155
+ "github.com/jackc/pgx/v5",
156
+ ]
157
+ files = ["**/migrations", "**/schema.prisma", "**/*.sql"]
158
+
159
+ ["400-ui"]
160
+ deps = ["react", "vue", "svelte", "astro", "next", "solid-js", "preact"]
161
+ files = ["index.html", "*/index.html"]
162
+
163
+ ["410-a11y"]
164
+ deps = ["react", "vue", "svelte", "astro", "next", "solid-js", "preact"]
165
+ files = ["index.html", "*/index.html"]
166
+
167
+ ["420-forms"]
168
+ deps = ["react", "vue", "svelte", "astro", "next", "solid-js", "preact"]
169
+ files = ["index.html", "*/index.html"]
170
+
171
+ ["430-ux-completeness"]
172
+ deps = ["react", "vue", "svelte", "astro", "next", "solid-js", "preact"]
173
+ files = ["index.html", "*/index.html"]
174
+
175
+ ["440-surface-capture"]
176
+ deps = ["react", "vue", "svelte", "astro", "next", "solid-js", "preact"]
177
+ files = ["index.html", "*/index.html"]
178
+
179
+ ["450-link-behavior"]
180
+ deps = ["react", "vue", "svelte", "astro", "next", "solid-js", "preact"]
181
+ files = ["index.html", "*/index.html"]
182
+
183
+ ["460-design-taste"]
184
+ deps = ["react", "vue", "svelte", "astro", "next", "solid-js", "preact"]
185
+ files = ["canon/DESIGN.md", "index.html", "*/index.html"]
186
+
187
+ ["465-interface-casing"]
188
+ deps = ["react", "vue", "svelte", "astro", "next", "solid-js", "preact"]
189
+ files = ["index.html", "*/index.html"]
190
+
191
+ ["470-motion"]
192
+ deps = ["react", "vue", "svelte", "astro", "next", "solid-js", "preact"]
193
+ files = ["index.html", "*/index.html"]
194
+
195
+ ["480-canvas"]
196
+ deps = []
197
+ files = [".canon/canvas"]
@@ -24,7 +24,6 @@
24
24
  "555-tasks" = "655-tasks"
25
25
  "558-plan" = "658-plan"
26
26
  "501-markdown" = "801-markdown"
27
- "065-spelling" = "765-spelling"
28
27
  "075-dependencies" = "775-dependencies"
29
28
  "080-config-comments" = "780-config-comments"
30
29
  "090-code-comments" = "790-code-comments"
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: Code placement, shape, dependency, error, and naming choices a model does not make by default
2
+ description: Routes code placement and shape decisions to the skills that carry them
3
3
  ---
4
4
 
5
5
  # Code standards
@@ -7,19 +7,7 @@ description: Code placement, shape, dependency, error, and naming choices a mode
7
7
  ## Placement
8
8
 
9
9
  - Load the `canon:codebase-layout` skill before placing a new file, and report it rather than proceeding silently when it does not resolve.
10
- - Prioritize native platform capabilities over third-party libraries.
11
- - Ensure data and configuration reside in designated Single Source of Truth locations.
12
10
 
13
11
  ## Shape
14
12
 
15
13
  - Load the `canon:code-craft` skill before deciding the shape of a new function, class, or module, and report it rather than proceeding silently when it does not resolve.
16
-
17
- ## Errors
18
-
19
- - Return structured error types for recoverable failures.
20
- - Never expose internal implementation details in error messages.
21
-
22
- ## Naming
23
-
24
- - Prefix booleans with `is`, `has`, `should`, or `can`: `isLoading`, `hasAccess`.
25
- - Prefix event handlers with `handle`: `handleClick`, `handleSubmit`.
@@ -1,37 +1,9 @@
1
1
  ---
2
- description: Enforce universal testing standards and best practices
2
+ description: Routes a test edit to the skill that carries the testing floors
3
3
  ---
4
4
 
5
5
  # Testing standards
6
6
 
7
7
  ## Before writing a test
8
8
 
9
- - Load the `canon:test-craft` skill before choosing the layer for a new test, and report it rather than proceeding silently when the skill does not resolve.
10
-
11
- ## Test structure
12
-
13
- - Structure tests using the Arrange, Act, Assert (AAA) pattern.
14
- - Each test should verify a single behavior.
15
- - Do not use conditional logic or loops within test bodies.
16
-
17
- ## Test focus
18
-
19
- - Do not test private functions or internal state directly.
20
- - Do not target arbitrary coverage percentages.
21
-
22
- ## Organization and isolation
23
-
24
- - Group related tests using the framework's nesting mechanism.
25
- - Keep nesting shallow (max 2 levels).
26
- - Ensure tests are independent with no shared side effects.
27
- - Clean up side effects and restore state after each test.
28
-
29
- ## Test data and async
30
-
31
- - Use factory functions or builders for test data over inline object literals.
32
- - Always await async operations.
33
- - Do not fire-and-forget promises in tests.
34
-
35
- ## Verification
36
-
37
- - Do not use snapshot testing for verification.
9
+ - Load the `canon:test-craft` skill before writing or changing a test, and report it rather than proceeding silently when the skill does not resolve.
@@ -11,7 +11,7 @@ paths:
11
11
 
12
12
  ## Punctuation
13
13
 
14
- - Do not use em dashes (`—`). Use a comma or period, or restructure the sentence.
14
+ - Do not use em dashes (`—`) or semicolons (`;`). Use a comma or period, or restructure the sentence.
15
15
  - Do not add punctuation to button labels or short action strings.
16
16
 
17
17
  ## Button copy
@@ -0,0 +1,13 @@
1
+ ---
2
+ description: Route edits to canvas frames under .canon/canvas/ to the canvas-board skill for the house style, and keep frame text and box height readable
3
+ paths:
4
+ - '.canon/canvas/**'
5
+ ---
6
+
7
+ # Canvas standards
8
+
9
+ ## Frames
10
+
11
+ - Load the `canon:canvas-board` skill before drawing or editing a frame. It carries the house style for an explanation frame and the content format. Report it rather than proceeding silently when the skill does not resolve.
12
+ - Write explanation frame text in sentence case. A mockup of the project's interface follows its design document instead.
13
+ - Size each explanation frame's box to show all its content with no scrollbar.
@@ -1,9 +1,16 @@
1
1
  extends = ""
2
- # Four audience groups whole. Every rule authored under them ships to a base
3
- # consumer, so adding one is a single edit rather than a file and a name here.
4
- # `canon` carries the toolkit workflow and canonical-doc rules, `claude` the
5
- # session conduct and `.claude/` authoring rules, `tooling` the dev setup and
6
- # CI workflow rules, and `writing` the prose and document-shape rules. The
7
- # `code` group is left to the stacks that write code, so a docs or writing
8
- # project loads no rule it can never apply.
9
- rules = ["canon", "claude", "tooling", "writing"]
2
+ # Named rule by rule rather than by folder, so a rule authored later under
3
+ # `canon` or `claude` reaches no project until it is added here or proposed by
4
+ # `canon gov detect`. Every other rule is opt-in.
5
+ rules = [
6
+ "566-output",
7
+ "602-tasks",
8
+ "604-scratch",
9
+ "605-worktrees",
10
+ "606-git",
11
+ "611-indexes",
12
+ "context",
13
+ "tasks",
14
+ "plan",
15
+ "markdown",
16
+ ]
@@ -0,0 +1,2 @@
1
+ extends = "base"
2
+ rules = ["code", "130-go", "335-testing-go"]
@@ -1,2 +1,2 @@
1
1
  extends = "base"
2
- rules = ["code", "100-typescript"]
2
+ rules = ["code", "100-typescript", "300-testing-ts"]
@@ -0,0 +1,2 @@
1
+ extends = "base"
2
+ rules = ["code", "140-php", "336-testing-php"]
@@ -1,2 +1,2 @@
1
1
  extends = "base"
2
- rules = ["code", "110-python", "340-pydantic", "330-testing-py", "360-security-server", "370-database"]
2
+ rules = ["code", "110-python", "330-testing-py"]
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/canon",
3
3
  "type": "module",
4
- "version": "5.33.0",
4
+ "version": "5.35.0",
5
5
  "description": "Runs your agent's work and keeps its code good, on one setup for every repo.",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -10,7 +10,6 @@
10
10
  "files": [
11
11
  "src",
12
12
  "scripts",
13
- "standards",
14
13
  "governance",
15
14
  "tooling",
16
15
  "claude",
@@ -25,7 +24,6 @@
25
24
  "access": "public"
26
25
  },
27
26
  "scripts": {
28
- "check:spell": "cspell '**' '.*/**' '.*' --no-progress --color --show-context",
29
27
  "format": "prettier --write --log-level warn --ignore-path .gitignore --ignore-path .prettierignore . && shfmt --write --indent 2 scripts/ tooling/ claude/",
30
28
  "check:format": "prettier --check --log-level warn --ignore-path .gitignore --ignore-path .prettierignore . && shfmt --diff --indent 2 scripts/ tooling/ claude/",
31
29
  "check:shell": "find scripts tooling claude .claude/hooks -name '*.sh' -exec shellcheck --severity=warning {} + && find .husky -maxdepth 1 -type f -exec shellcheck --severity=warning --shell=sh {} +",
@@ -71,7 +69,6 @@
71
69
  "@types/bun": "^1.2.10",
72
70
  "@types/micromatch": "^4.0.10",
73
71
  "astro": "^6.1.9",
74
- "cspell": "^8.17.1",
75
72
  "happy-dom": "^20.14.5",
76
73
  "husky": "^9.1.7",
77
74
  "micromatch": "^4.0.8",
@@ -97,9 +97,8 @@ log_step "Assert scaffold"
97
97
  # catches a failed domain, and the gate stays green while the target is
98
98
  # missing everything that domain provides.
99
99
  #
100
- # Standards name no path because that corpus never installs into a target. A
101
- # scaffold reads a standard through `canon standards <name>`, resolving against
102
- # the toolkit rather than a copy this gate could assert on. The two
100
+ # Authoring references name no path because they ship inside the plugin's
101
+ # skills rather than installing into a target. The two
103
102
  # rule paths stand for the `claude` and `canon` groups `base` takes whole.
104
103
  for path in "CLAUDE.md" ".claude/wiki/index.md" ".claude" "canon/context/index.md" \
105
104
  ".prettierrc" ".editorconfig" ".lintstagedrc" ".husky/pre-commit" ".github/workflows/verify.yml" "scripts/verify.sh" \
@@ -50,7 +50,6 @@ import { listSkills } from '../../src/claude/skills-list'
50
50
  import { countCommands } from '../../src/counts/catalogs'
51
51
  import { buildDesignCss } from '../../src/design/css'
52
52
  import { buildGovCatalog } from '../../src/gov/list'
53
- import { listStandardEntries } from '../../src/standards/list'
54
53
  import { buildStackSummaries } from '../../src/tooling/list'
55
54
 
56
55
  const root = new URL('../..', import.meta.url).pathname
@@ -87,11 +86,6 @@ function run(): void {
87
86
  const { values, frames } = buildFrameValues({
88
87
  skills: listSkills(root).map((entry) => entry.name),
89
88
  gov: buildGovCatalog(root),
90
- // The toolkit root only: a project-local standard would put a row in the
91
- // hero the toolkit does not ship.
92
- standards: listStandardEntries(root).filter((entry) =>
93
- entry.source.startsWith('standards/'),
94
- ),
95
89
  toolingStacks: buildStackSummaries(root),
96
90
  commandCount: countCommands(root) ?? 0,
97
91
  tokenCss,
package/scripts/lib/ui.sh CHANGED
@@ -96,12 +96,3 @@ close_timeline() {
96
96
  set_palette 2
97
97
  echo -e "${GREY}└${NC}" >&2
98
98
  }
99
-
100
- guard_root() {
101
- local target="$1"
102
- local target_abs
103
- target_abs=$(cd "$target" && pwd)
104
- if [ "$target_abs" = "$PROJECT_ROOT" ]; then
105
- log_error "Cannot run against toolkit root. Files here are the source of truth."
106
- fi
107
- }
@@ -468,7 +468,6 @@ const RECORD_KINDS: readonly (readonly [string, Corpus])[] = [
468
468
  ['plans', 'per-machine'],
469
469
  ['groundwork', 'per-machine'],
470
470
  ['intake', 'per-machine'],
471
- ['standards', 'tracked'],
472
471
  ['teach', 'per-machine'],
473
472
  ]
474
473
 
@@ -588,14 +587,6 @@ export const AUDITS: readonly AuditSpec[] = [
588
587
  argv: ['records', 'validate', kind, '--json'],
589
588
  gatingExits: [],
590
589
  corpus,
591
- // `standards` is the one tracked kind here, so it takes none of the
592
- // per-machine default the other five inherit from their corpus. A target
593
- // reads standards through `canon standards` rather than a copy in its own
594
- // tree, so carrying no standards folder at all is the
595
- // ordinary state of every project but this repository.
596
- ...(kind === 'standards' && {
597
- absentReasons: ['no-folder'] satisfies RecordRefusal[],
598
- }),
599
590
  counts: findingsOnly,
600
591
  })),
601
592
  {
@@ -0,0 +1,66 @@
1
+ import { type Box, FRAME_GAP, type Frame } from '@/canvas/content'
2
+
3
+ export interface Placement {
4
+ readonly name: string
5
+ readonly from: Box
6
+ readonly to: Box
7
+ }
8
+
9
+ function boxOf(frame: Frame): Box {
10
+ return {
11
+ x: frame.x,
12
+ y: frame.y,
13
+ width: frame.width,
14
+ height: frame.height,
15
+ }
16
+ }
17
+
18
+ /**
19
+ * Boxes whose vertical spans overlap share a row, so a row the operator dragged
20
+ * stays a row. Reading by overlap rather than by an equal `y` lets a frame a
21
+ * few pixels off its neighbor still join it.
22
+ */
23
+ function rowsOf(frames: readonly Frame[]): Frame[][] {
24
+ const sorted = [...frames].sort((a, b) => a.y - b.y || a.x - b.x)
25
+ const rows: Frame[][] = []
26
+ let bottom = Number.NEGATIVE_INFINITY
27
+ for (const frame of sorted) {
28
+ const row = rows.at(-1)
29
+ if (row && frame.y < bottom) {
30
+ row.push(frame)
31
+ bottom = Math.max(bottom, frame.y + frame.height)
32
+ } else {
33
+ rows.push([frame])
34
+ bottom = frame.y + frame.height
35
+ }
36
+ }
37
+ return rows.map((row) =>
38
+ row.sort((a, b) => a.x - b.x || a.name.localeCompare(b.name)),
39
+ )
40
+ }
41
+
42
+ /**
43
+ * Re-lays a page's frames: each row left to right on `FRAME_GAP` from the
44
+ * page's leftmost edge, rows stacked on their tallest box plus the gap from
45
+ * the page's top edge. Returns only the frames whose position changes.
46
+ */
47
+ export function planArrange(frames: readonly Frame[]): Placement[] {
48
+ if (frames.length === 0) return []
49
+ const left = Math.min(...frames.map((frame) => frame.x))
50
+ let top = Math.min(...frames.map((frame) => frame.y))
51
+
52
+ const moves: Placement[] = []
53
+ for (const row of rowsOf(frames)) {
54
+ let x = left
55
+ for (const frame of row) {
56
+ const from = boxOf(frame)
57
+ const to: Box = { ...from, x, y: top }
58
+ if (from.x !== to.x || from.y !== to.y) {
59
+ moves.push({ name: frame.name, from, to })
60
+ }
61
+ x += frame.width + FRAME_GAP
62
+ }
63
+ top += Math.max(...row.map((frame) => frame.height)) + FRAME_GAP
64
+ }
65
+ return moves
66
+ }
@@ -157,7 +157,7 @@ export async function captureCanvas(
157
157
  })
158
158
  }
159
159
 
160
- type ServeRefused = {
160
+ export type ServeRefused = {
161
161
  readonly ok: false
162
162
  readonly reason: 'no-server' | 'capture-failed'
163
163
  readonly detail: string
@@ -168,20 +168,34 @@ async function serveFor<T>(
168
168
  deps: CaptureDeps,
169
169
  run: (base: string, capture: CaptureEngine) => Promise<T>,
170
170
  ): Promise<T | ServeRefused> {
171
- const start = deps.start ?? startCanvas
171
+ return serveCanvas(root, deps.start, async (base) => {
172
+ const capture =
173
+ deps.capture ?? (await import('@/capture/render')).captureSources
174
+ return run(base, capture)
175
+ })
176
+ }
177
+
178
+ /**
179
+ * Runs a call against a canvas served on a free port for its length, so a verb
180
+ * that renders frames reads the same address the board shows.
181
+ */
182
+ export async function serveCanvas<T>(
183
+ root: string,
184
+ startOverride: CaptureDeps['start'],
185
+ run: (base: string) => Promise<T>,
186
+ ): Promise<T | ServeRefused> {
187
+ const start = startOverride ?? startCanvas
172
188
  const server = start(root, { port: 0, shell: new Response('') })
173
189
  if (!server.ok) {
174
190
  return {
175
191
  ok: false,
176
192
  reason: 'no-server',
177
- detail: `${server.detail}, so run canon canvas serve and capture again`,
193
+ detail: `${server.detail}, so run canon canvas serve and try again`,
178
194
  }
179
195
  }
180
196
 
181
197
  try {
182
- const capture =
183
- deps.capture ?? (await import('@/capture/render')).captureSources
184
- return await run(server.url, capture)
198
+ return await run(server.url)
185
199
  } catch (error) {
186
200
  return {
187
201
  ok: false,
@@ -578,6 +578,45 @@ export function restoreBox(
578
578
  })
579
579
  }
580
580
 
581
+ export type PruneOutcome =
582
+ | { readonly ok: true; readonly dropped: readonly string[] }
583
+ | ContentRefused
584
+
585
+ /**
586
+ * Lists the layout entries whose frame file is gone, and removes them under
587
+ * the layout lock unless `write` is false. `readPage` never reports them, so
588
+ * this is the one place an entry outliving its frame is seen.
589
+ */
590
+ export function pruneLayout(
591
+ root: string,
592
+ page: string,
593
+ write: boolean,
594
+ ): PruneOutcome {
595
+ if (!isValidName(page)) {
596
+ return refuse('invalid-name', `${page} is not a valid page name`)
597
+ }
598
+ const dir = pagePath(root, page)
599
+ if (!isDirectory(dir)) return refuse('no-page', `page ${page} does not exist`)
600
+
601
+ const prune = (): PruneOutcome => {
602
+ const { boxes, malformed } = readLayout(dir)
603
+ if (malformed) {
604
+ return refuse('malformed-layout', `${page}/${LAYOUT_FILE} does not parse`)
605
+ }
606
+ const present = new Set(frameNames(dir))
607
+ const dropped = [...boxes.keys()].filter((name) => !present.has(name))
608
+ if (write && dropped.length > 0) {
609
+ writeLayout(
610
+ dir,
611
+ new Map([...boxes].filter(([name]) => present.has(name))),
612
+ )
613
+ }
614
+ return { ok: true, dropped }
615
+ }
616
+
617
+ return write ? withFileLock(join(dir, LAYOUT_FILE), prune) : prune()
618
+ }
619
+
581
620
  export interface Selection {
582
621
  readonly page: string
583
622
  readonly frame: string