@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.
- package/README.md +3 -5
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/api-design/REQUIREMENT.md +2 -3
- package/claude/skills/api-design/SKILL.md +2 -3
- package/claude/skills/api-design/references/adopted.md +1 -1
- package/claude/skills/auto-ship/references/draft-mark.md +1 -1
- package/claude/skills/canon-cli/SKILL.md +10 -11
- package/claude/skills/canon-feedback/REQUIREMENT.md +1 -0
- package/claude/skills/canon-feedback/SKILL.md +10 -28
- package/claude/skills/canon-feedback/references/template.md +53 -0
- package/claude/skills/canvas-board/SKILL.md +4 -1
- package/claude/skills/canvas-board/references/canvas.md +26 -3
- package/claude/skills/code-craft/REQUIREMENT.md +1 -1
- package/claude/skills/code-craft/SKILL.md +0 -1
- package/claude/skills/create-rule/SKILL.md +1 -1
- package/claude/skills/create-rule/references/rule.md +3 -1
- package/claude/skills/create-skill/SKILL.md +2 -1
- package/claude/skills/create-skill/references/skill-paths.md +12 -15
- package/{standards/standard.md → claude/skills/create-skill/references/skill-reference.md} +50 -45
- package/claude/skills/create-skill/references/skill.md +10 -4
- package/claude/skills/draft-and-pick/SKILL.md +1 -1
- package/claude/skills/git-commit/references/versioning.md +13 -13
- package/claude/skills/git-issue/references/issue.md +4 -1
- package/claude/skills/git-pr/SKILL.md +1 -1
- package/claude/skills/git-pr/references/publish.md +1 -1
- package/claude/skills/review-address/REQUIREMENT.md +1 -1
- package/claude/skills/review-address/SKILL.md +2 -2
- package/claude/skills/review-craft/references/ui.md +1 -1
- package/claude/skills/review-pr/SKILL.md +3 -3
- package/claude/skills/review-pr/references/post.md +1 -1
- package/claude/skills/review-ui/REQUIREMENT.md +1 -1
- package/claude/skills/review-ui/SKILL.md +8 -6
- package/claude/skills/review-ui/references/driving.md +8 -8
- package/claude/skills/review-ui/references/post.md +52 -16
- package/claude/skills/role-orchestrator/REQUIREMENT.md +2 -2
- package/claude/skills/role-orchestrator/SKILL.md +11 -12
- package/claude/skills/role-orchestrator/references/orchestrator-cloud-launch.md +3 -1
- package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +11 -8
- package/claude/skills/role-orchestrator/references/orchestrator-handback.md +10 -4
- package/claude/skills/role-orchestrator/references/orchestrator-launch.md +11 -2
- package/claude/skills/role-orchestrator/references/orchestrator-poll.md +11 -12
- package/claude/skills/role-orchestrator/references/orchestrator-review-dispatch.md +12 -15
- package/claude/skills/role-orchestrator/references/orchestrator-reviewer-launch.md +6 -4
- package/claude/skills/role-orchestrator/scripts/poll.ts +50 -1
- package/claude/skills/role-reviewer/REQUIREMENT.md +17 -7
- package/claude/skills/role-reviewer/SKILL.md +17 -10
- package/claude/skills/role-worker/REQUIREMENT.md +1 -1
- package/claude/skills/role-worker/SKILL.md +6 -6
- package/claude/skills/session-compact/references/handoff-note.md +1 -1
- package/claude/skills/target-setup/SKILL.md +12 -6
- package/claude/skills/target-setup/references/detect.md +22 -5
- package/claude/skills/test-craft/REQUIREMENT.md +2 -3
- package/claude/skills/test-craft/SKILL.md +15 -3
- package/docs/agents/audits.md +1 -1
- package/docs/agents/canvas.md +5 -1
- package/docs/agents/commands.md +3 -5
- package/docs/agents/counts.md +5 -6
- package/docs/agents/drift-surfaces.md +15 -19
- package/docs/agents/gov-detect.md +47 -0
- package/docs/agents/index.md +6 -4
- package/docs/agents/install-and-sync.md +11 -31
- package/docs/agents/markdown-audit.md +10 -12
- package/docs/agents/migrate.md +1 -1
- package/docs/agents/pr-evidence-layout.md +70 -0
- package/docs/agents/pr-evidence.md +8 -9
- package/docs/agents/pr-frames.md +7 -2
- package/docs/agents/pr-lift.md +132 -0
- package/docs/agents/records-stale.md +1 -1
- package/docs/agents/records-validate.md +11 -23
- package/docs/agents/records.md +1 -1
- package/docs/agents/routing.md +1 -1
- package/docs/agents/rule-citations.md +11 -14
- package/docs/agents/scripting.md +4 -21
- package/docs/agents/state-scoped-risk.md +1 -1
- package/docs/agents/tasks-validate.md +1 -1
- package/docs/agents/teach.md +1 -1
- package/docs/target/index.md +1 -0
- package/docs/target/migrations.md +4 -0
- package/docs/target/projects.md +11 -11
- package/docs/target/scenarios.md +5 -6
- package/docs/target/standards-moved.md +32 -0
- package/docs/target/sync.md +5 -5
- package/docs/workflow/operating-model.md +16 -18
- package/docs/workflow/skill-map.md +1 -1
- package/governance/detect.toml +197 -0
- package/governance/renames.toml +0 -1
- package/governance/rules/code/000-code.md +1 -13
- package/governance/rules/code/010-testing.md +2 -30
- package/governance/rules/ui/400-ui.md +1 -1
- package/governance/rules/ui/480-canvas.md +13 -0
- package/governance/stacks/base.toml +15 -8
- package/governance/stacks/go.toml +2 -0
- package/governance/stacks/node.toml +1 -1
- package/governance/stacks/php.toml +2 -0
- package/governance/stacks/python.toml +1 -1
- package/package.json +1 -4
- package/scripts/core/install-check.sh +2 -3
- package/scripts/core/regen-hero.ts +0 -6
- package/scripts/lib/ui.sh +0 -9
- package/src/audits/catalog.ts +0 -9
- package/src/canvas/arrange.ts +66 -0
- package/src/canvas/capture.ts +20 -6
- package/src/canvas/content.ts +39 -0
- package/src/canvas/fit.ts +238 -0
- package/src/canvas/tokens.ts +28 -10
- package/src/capture/frame-values.ts +2 -32
- package/src/claude/routing.ts +1 -1
- package/src/claude/skills-reach.ts +4 -6
- package/src/cli/help.ts +3 -4
- package/src/cli.ts +0 -2
- package/src/commands/canvas.ts +179 -1
- package/src/commands/gov.ts +149 -10
- package/src/commands/init.ts +1 -1
- package/src/commands/labels.ts +4 -44
- package/src/commands/migrate.ts +1 -1
- package/src/commands/pr.ts +376 -1
- package/src/commands/records.ts +4 -20
- package/src/commands/tooling.ts +2 -4
- package/src/counts/catalogs.ts +0 -6
- package/src/feedback/body.ts +26 -5
- package/src/gate/boundaries.ts +2 -2
- package/src/gate/measures.ts +64 -72
- package/src/gate/sequencer.ts +51 -2
- package/src/gate/stages.ts +3 -22
- package/src/gov/citations.ts +11 -43
- package/src/gov/detect.ts +301 -0
- package/src/gov/list.ts +23 -2
- package/src/gov/stacks.ts +9 -0
- package/src/markdown/bans.ts +1 -1
- package/src/migrate/scratch-evidence.ts +2 -2
- package/src/pr/evidence.ts +218 -45
- package/src/pr/frames.ts +23 -3
- package/src/pr/lift.ts +418 -0
- package/src/pr/rest.ts +3 -0
- package/src/pr/review-scope.ts +76 -0
- package/src/records/validate.ts +6 -194
- package/src/sandbox/equivalence.ts +14 -7
- package/src/sandbox/provision.ts +0 -5
- package/src/secrets/shipped.ts +1 -2
- package/src/serve/static.ts +1 -2
- package/src/shipped/references.ts +29 -80
- package/src/sync/check.ts +18 -14
- package/src/tooling/inject.ts +6 -37
- package/src/tooling/read.ts +2 -3
- package/src/tooling/scan.ts +4 -17
- package/src/tooling/subfolder.ts +2 -66
- package/tooling/base/configs/scripts/verify.json +1 -1
- package/tooling/base/configs/scripts/verify.ts +1 -12
- package/tooling/base/manifest.toml +1 -2
- package/tooling/base/reference.md +10 -16
- package/tooling/base/seeds/.github/workflows/verify.yml +0 -3
- package/tooling/base/seeds/.lintstagedrc +1 -2
- package/tooling/base/seeds/canon/context/ci.md +0 -1
- package/tooling/base/seeds/canon/context/development.md +1 -1
- package/tooling/claude/reference.md +1 -1
- package/tooling/claude/seeds/.claude/hooks/review-post-scan.sh +13 -1
- package/tooling/go/reference.md +1 -1
- package/tooling/php/reference.md +1 -1
- package/tooling/python/reference.md +2 -2
- package/tooling/web/configs/.github/workflows/verify.yml +0 -3
- package/tooling/web/manifest.toml +0 -1
- package/tooling/web/reference.md +2 -4
- package/docs/agents/standards-audit.md +0 -40
- package/governance/rules/tooling/765-spelling.md +0 -19
- package/governance/stacks/astro.toml +0 -2
- package/governance/stacks/nextjs.toml +0 -2
- package/governance/stacks/node-server.toml +0 -2
- package/governance/stacks/python-fastapi.toml +0 -2
- package/governance/stacks/react.toml +0 -2
- package/src/commands/standards.ts +0 -278
- package/src/labels/spelling.ts +0 -120
- package/src/standards/audit.ts +0 -146
- package/src/standards/list.ts +0 -33
- package/src/standards/read.ts +0 -177
- package/src/standards/scope.ts +0 -56
- package/standards/index.md +0 -10
- package/tooling/base/configs/.vscode/extensions.json +0 -9
- package/tooling/base/configs/.vscode/settings.json +0 -3
- package/tooling/base/seeds/.cspell/project-terms.txt +0 -0
- package/tooling/base/seeds/.cspell/tech-stack.txt +0 -25
- package/tooling/base/seeds/cspell.json +0 -20
- package/tooling/go/seeds/.cspell/tech-stack.txt +0 -16
- package/tooling/nextjs/seeds/.cspell/tech-stack.txt +0 -3
- package/tooling/php/seeds/.cspell/tech-stack.txt +0 -5
- package/tooling/python/seeds/.cspell/tech-stack.txt +0 -18
- package/tooling/vite-react/seeds/.cspell/project-terms.txt +0 -1
- package/tooling/vite-react/seeds/.cspell/tech-stack.txt +0 -2
- package/tooling/web/configs/.vscode/extensions.json +0 -13
- package/tooling/web/configs/.vscode/settings.json +0 -10
- 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"]
|
package/governance/renames.toml
CHANGED
|
@@ -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:
|
|
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:
|
|
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
|
|
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
|
-
#
|
|
3
|
-
#
|
|
4
|
-
# `canon
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
+
]
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
extends = "base"
|
|
2
|
-
rules = ["code", "100-typescript"]
|
|
2
|
+
rules = ["code", "100-typescript", "300-testing-ts"]
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
extends = "base"
|
|
2
|
-
rules = ["code", "110-python", "
|
|
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.
|
|
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
|
-
#
|
|
101
|
-
#
|
|
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
|
-
}
|
package/src/audits/catalog.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/canvas/capture.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
193
|
+
detail: `${server.detail}, so run canon canvas serve and try again`,
|
|
178
194
|
}
|
|
179
195
|
}
|
|
180
196
|
|
|
181
197
|
try {
|
|
182
|
-
|
|
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,
|
package/src/canvas/content.ts
CHANGED
|
@@ -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
|