@erclx/canon 4.95.0 → 4.97.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 (30) hide show
  1. package/claude/.claude-plugin/plugin.json +1 -1
  2. package/claude/skills/codebase-layout/REQUIREMENT.md +46 -0
  3. package/claude/skills/codebase-layout/SKILL.md +57 -0
  4. package/claude/skills/codebase-layout/references/adopted.md +34 -0
  5. package/claude/skills/codebase-layout/references/frontend.md +43 -0
  6. package/claude/skills/codebase-layout/references/tests.md +28 -0
  7. package/claude/skills/git-pr/REQUIREMENT.md +3 -0
  8. package/claude/skills/git-pr/SKILL.md +25 -0
  9. package/claude/skills/plan-feature/REQUIREMENT.md +1 -0
  10. package/claude/skills/plan-feature/SKILL.md +2 -1
  11. package/claude/skills/test-craft/REQUIREMENT.md +9 -0
  12. package/claude/skills/test-craft/references/e2e.md +1 -1
  13. package/docs/agents/commands.md +2 -1
  14. package/docs/agents/index.md +1 -0
  15. package/docs/agents/pr-evidence.md +9 -1
  16. package/docs/agents/pr-preview.md +103 -0
  17. package/docs/workflow/ai-workflow.md +1 -0
  18. package/governance/rules/core/000-constitution.md +1 -1
  19. package/governance/rules/lib/300-testing-ts.md +1 -0
  20. package/package.json +1 -1
  21. package/src/claude/cases/misc.ts +5 -0
  22. package/src/commands/pr.ts +300 -13
  23. package/src/pr/evidence.ts +36 -1
  24. package/src/pr/preview.ts +178 -0
  25. package/tooling/astro/configs/playwright.config.ts +3 -1
  26. package/tooling/cloudflare/configs/.github/workflows/deploy.yml +64 -2
  27. package/tooling/cloudflare/reference.md +6 -3
  28. package/tooling/nextjs/configs/playwright.config.ts +3 -1
  29. package/tooling/vite-react/configs/playwright.config.ts +3 -1
  30. package/tooling/web/configs/e2e/home.spec.ts +8 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "canon",
3
3
  "description": "Automated governance, versioning, and discovery tools for Claude Code.",
4
- "version": "4.95.0",
4
+ "version": "4.97.0",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -0,0 +1,46 @@
1
+ ---
2
+ name: codebase-layout
3
+ description: Why the session writing a new path into a plan needs placement guidance at that moment, and where that guidance stops short of test layers and module interfaces
4
+ ---
5
+
6
+ # Codebase layout requirement
7
+
8
+ ## Gap
9
+
10
+ Without this skill, the session that decides where a new file goes has nothing about layout in front of it when it decides. The planner writes each new path into the plan, the worker copies it, and the planner's only instruction on the tree is to measure against it, which for a new path reads as copying the flat folder beside it.
11
+
12
+ The failure is measured on a real target. Two source folders grew from 11 files to 46 and 36 flat files in six days, with the constitution's directory line and a stack rule mandating feature folders both installed throughout. The split that followed touched 98 files, and the end to end folder is flat again, four specs beside five helpers. Archived plans from the same project name every new file as a literal flat path.
13
+
14
+ Sharpening the rules does not reach the decision. Both were loaded and neither was applied, since an always-loaded line competes with everything else a session holds and a path-scoped rule fires on a read of an existing file rather than on a path written into a plan.
15
+
16
+ ## Must
17
+
18
+ - Be loaded by `plan-feature` whenever a plan names a path that does not exist yet, so the guidance is in front of the planner at the moment it writes the path
19
+ - Tell the session to name and keep to the project's existing layout strategy before placing a file, since two strategies in one tree cost every reader a guess
20
+ - State the split trigger as a second role entering a folder, with a file count only as a prompt to look
21
+ - State colocation with the one consumer and promotion on the second, and imports flowing from shared toward features toward the app entry
22
+ - Require a one-clause placement reason on every new path the plan names
23
+ - Hold the stack-neutral core in the body and defer category specifics to a reference loaded only when a new path falls in that category
24
+ - Record the external layouts adopted and declined with the reason for each, so a later session extends the position instead of re-deriving it
25
+ - Fire on a direct question about placement as well, such as where a file goes or whether a flat folder should split, since a session outside any plan still places files
26
+
27
+ ## Must not
28
+
29
+ - Name a framework or runner in the body. Stack specifics live in the references.
30
+ - Carry a hard file-count threshold. A command folder holding sixty files of one role reads fine flat, and a number would fail it.
31
+ - Restate the layer rule, the layer table, or the final filter `test-craft` owns
32
+ - Mandate one layout across projects, such as a `features/` tree, over the project's own strategy
33
+ - Restate the constitution's directory line. The rule points here and this body carries the depth.
34
+
35
+ ## Guards
36
+
37
+ - A plan that only edits existing files does not load the skill, so the cost lands only where a placement is decided
38
+ - A strategy change is proposed as its own decision rather than made while placing one file
39
+
40
+ ## Out of scope
41
+
42
+ - Which layer a test belongs at: `test-craft`
43
+ - Interface depth, seams, and where a module boundary sits in code, which govern a module's shape rather than its path on disk
44
+ - Per-language placement conventions such as test suffixes and the top-level test folder: the language testing rules
45
+ - Backend, asset, and script layouts, which wait for a driving project before a reference is written
46
+ - Whether a plan that loaded the skill actually places files differently, which needs a measured run on a target rather than a rule here
@@ -0,0 +1,57 @@
1
+ ---
2
+ name: codebase-layout
3
+ description: Carries the rules that decide where a new file goes, when a folder splits, and when a file moves from its one consumer to shared code, so a plan places each new path by role and feature with a one-clause reason rather than beside its nearest neighbor. Use when a plan names a file or folder that does not exist yet, when `plan-feature` reaches its placement step, or when asked "where should this file go", "this folder is flat", "restructure this folder", or "should this be its own folder". Do NOT use to choose which layer a test belongs at, which is `test-craft`, or to shape a module's interface and seams.
4
+ ---
5
+
6
+ # Codebase layout
7
+
8
+ A session placing a new file copies the folder next to it, so a flat folder stays flat until a refactor pays to split it. In one target, two source folders grew to 46 and 36 flat files under installed rules against it, and the split cost a pull request touching 98 files. This skill carries the placement call to the moment the path is written, which is the one moment it is cheap.
9
+
10
+ Load it before writing a new path into a plan, not after. A worker copies the path the plan gives it, so the placement is settled once the plan is.
11
+
12
+ Skip it when every path the work names already exists.
13
+
14
+ ## Read the project's strategy first
15
+
16
+ - Name the layout the project already uses before placing anything: by feature, by route, by role, or flat. Look at the folders a new file would sit beside and at any stated rule on layout.
17
+ - Keep to that strategy. A second strategy beside the first costs every later reader a guess about which one a file follows.
18
+ - Propose a strategy change as its own decision, never as a side effect of placing one file.
19
+
20
+ ## Group what changes together
21
+
22
+ - Put files that change for the same reason in one folder, and separate files that change for different reasons.
23
+ - Name a folder for what it holds for a reader, being a feature, a route, or a role, over its technical kind alone. `checkout/` tells a reader what is inside where `helpers/` does not.
24
+ - Never create a bucket named `misc`, `common`, `utils`, or `helpers` on its own. Qualify it by what it serves, or place the file with its consumer.
25
+
26
+ ## Split before the file lands
27
+
28
+ - Split a folder when the new file brings a second role into it, before the file lands rather than in a later refactor. The split that touched 98 files would have touched a handful when the folder held eleven.
29
+ - Treat roughly ten files as a prompt to look, not a gate. A folder holding one role can stay flat at any size, and a folder mixing two roles is worth splitting at six.
30
+ - Give a new role a named folder in the plan, rather than dropping its first file beside the nearest neighbor.
31
+
32
+ ## Place a file with its consumer
33
+
34
+ - Colocate a new file with the one module that uses it.
35
+ - Promote it to shared code when a second consumer arrives, and move it in the change that adds that consumer.
36
+ - Keep imports flowing one way, from shared code toward features toward the app entry. A shared module importing from a feature, or one feature importing another, names a file in the wrong place.
37
+
38
+ ## State the reason on every new path
39
+
40
+ - Give each new path in a plan a one-clause reason for where it sits: the role it holds, the consumer it serves, or the strategy it follows.
41
+ - Name a new folder in the plan entry that creates it, so the worker creates the folder rather than inferring it.
42
+
43
+ ## Read the reference for the category
44
+
45
+ Read the reference matching what the new paths hold, and skip the rest.
46
+
47
+ - Application source for an interface: `${CLAUDE_SKILL_DIR}/references/frontend.md`
48
+ - Test files of any layer: `${CLAUDE_SKILL_DIR}/references/tests.md`
49
+
50
+ Read `${CLAUDE_SKILL_DIR}/references/adopted.md` only when extending this guidance or arguing against a rule in it. It records which external layouts were adopted, which were declined, and why.
51
+
52
+ ## What this delegates
53
+
54
+ - Which layer a test belongs at: `test-craft`. This skill decides where the test file sits once its layer is chosen.
55
+ - Where a new path gets written into a plan, and the plan's shape: `plan-feature` and the plan standard
56
+ - The directory principle every session loads: `000-constitution`
57
+ - Runner, suffix, and top-level test folder conventions per language: `300-testing-ts` and `330-testing-py`
@@ -0,0 +1,34 @@
1
+ ---
2
+ title: Adopted and declined
3
+ description: Which external layout patterns this skill adopted, which it declined, and why, so a later session extends the position instead of re-deriving it
4
+ ---
5
+
6
+ # Adopted and declined
7
+
8
+ External sources were read and filtered rather than imported. This file is the record. A later session extending the skill adds to it rather than re-arguing an item already settled here.
9
+
10
+ ## Adopted
11
+
12
+ **Group what changes together.** Robert Martin's restatement of the single responsibility principle: "Gather together the things that change for the same reasons. Separate those things that change for different reasons" ([The Single Responsibility Principle](https://blog.cleancoder.com/uncle-bob/2014/05/08/SingleReponsibilityPrinciple.html)). It gives "split by role" a test a planner can apply to a folder: would these files change together?
13
+
14
+ **Name folders for the domain rather than the framework.** Martin's Screaming Architecture argues the top level should tell readers "about the system, not about the frameworks you used in your system" ([Screaming Architecture](https://blog.cleancoder.com/uncle-bob/2011/09/30/Screaming-Architecture.html)). It is the root of naming a folder for what it holds rather than its technical kind.
15
+
16
+ **Feature folders, no cross-feature imports, one-way flow.** bulletproof-react puts most code under per-feature folders holding only the subfolders each needs, forbids one feature importing another, and enforces a shared, then features, then app direction with an import-path lint rule ([project structure](https://github.com/alan2207/bulletproof-react/blob/master/docs/project-structure.md)). Adopted as the direction rule in the body and the feature-folder guidance in `frontend.md`, without the mandate.
17
+
18
+ **Grow in levels and promote on the second consumer.** Robin Wieruch grows a project from one file to feature folders, each level triggered by a named event rather than a count, and moves a helper to the shared layer once two or more features need it ([React folder structure](https://www.robinwieruch.de/react-folder-structure/)). Adopted as the promotion rule in the body and the levels in `frontend.md`.
19
+
20
+ **Consistency over any one strategy.** The Next.js docs call the framework unopinionated about organization, list three strategies, and ask a project to "choose a strategy that works for you and your team and be consistent across the project" ([project structure](https://nextjs.org/docs/app/getting-started/project-structure)). Adopted as the body's first section, reading and keeping the project's own strategy.
21
+
22
+ **Fixtures in modules, page objects on their own.** Playwright defines fixtures in dedicated modules that extend the base test, merges them into one module the specs import, and keeps page objects as their own modules ([fixtures](https://playwright.dev/docs/test-fixtures)). Adopted in `tests.md` as the split between specs and support.
23
+
24
+ ## Declined
25
+
26
+ **Deep modules and seams.** Matt Pocock's codebase-design skill covers a lot of behavior behind a small interface placed at a clean seam ([codebase-design](https://github.com/mattpocock/skills/blob/main/skills/engineering/codebase-design/SKILL.md)). It answers where a boundary goes in code, not where a file goes on disk, and absorbing it doubles the skill's scope before the placement problem is shown solved. It becomes a reference only when a plan places files well and still draws a boundary badly.
27
+
28
+ **A mandated `features/` tree.** bulletproof-react's layout, and a stack rule some projects install, put domain code under `src/features/`. Declined as a mandate because it conflicts with keeping the project's own strategy, and a project that never adopted feature folders gains a second strategy rather than a better one. A stack rule mandating the tree still loads beside this skill in a project that installs it, and the two give different answers there until that rule is softened on its own.
29
+
30
+ **A numeric file ceiling.** None of the sources gives a reliable number, and a count fails a folder holding sixty files of one role that reads fine flat. The body triggers on a second role and treats roughly ten files as a prompt to look.
31
+
32
+ **A required placement field in the plan format.** It would touch every plan, including the ones that create nothing. The body asks for a one-clause reason on each new path instead, and the field is worth revisiting only if plans load the skill and still list flat paths.
33
+
34
+ **Loading the skill in the worker.** The worker copies the path the plan gives it, so placement is already decided by the time a worker would read this.
@@ -0,0 +1,43 @@
1
+ ---
2
+ title: Frontend layout
3
+ description: Where interface source goes, being feature folders against shared UI, routes and their private folders, hooks and helpers split by consumer, fixtures by feature, and the promotion rule, with one worked failure
4
+ ---
5
+
6
+ # Frontend layout
7
+
8
+ Read this before placing a new component, hook, helper, route, or fixture in an interface project. The body's rules hold here unchanged, and this file says how they land on the folders an interface project grows.
9
+
10
+ ## Feature folders against shared UI
11
+
12
+ - Put code that serves one feature under that feature's folder, holding only the subfolders it needs: its components, its hooks, its data access, its types ([bulletproof-react](https://github.com/alan2207/bulletproof-react/blob/master/docs/project-structure.md)).
13
+ - Keep the shared `components/` folder for pieces two or more features render, such as a button, a dialog, or a layout shell. A component only one feature renders is feature code, even when it looks generic.
14
+ - Never import one feature from another. Lift the piece both need to shared code, or compose the two features at the app or route level ([bulletproof-react](https://github.com/alan2207/bulletproof-react/blob/master/docs/project-structure.md)).
15
+ - Enforce the flow from shared toward features toward the app where the project already lints imports. An import-path restriction turns the direction into a check rather than a habit.
16
+
17
+ ## Grow in levels
18
+
19
+ - Start flat. A small project with one screen needs no feature folders, and adding them early is structure nobody reads.
20
+ - Move to the next level on a named event rather than a count: a second feature, a second role in `components/`, a helper only one feature uses ([Robin Wieruch](https://www.robinwieruch.de/react-folder-structure/)).
21
+ - Take one level per change. A plan that jumps from flat to a full feature tree rewrites every import in the project.
22
+
23
+ ## Routes
24
+
25
+ - Follow the router's own conventions where the framework ships file-based routing, and treat everything outside them as project strategy.
26
+ - A framework calling itself unopinionated about organization leaves the strategy to the project. Pick one of keeping code outside the routes folder, top-level folders inside it, or splitting by feature or route, and "be consistent across the project" ([Next.js project structure](https://nextjs.org/docs/app/getting-started/project-structure)).
27
+ - Colocate a component only one route renders beside that route, in a folder the router excludes from routing, such as a private `_components` folder in a router that supports one.
28
+ - Treat `components` and `lib` as placeholder names with no framework meaning. Their contents still follow the body's rules.
29
+
30
+ ## Hooks and helpers
31
+
32
+ - Put a hook or helper one feature uses inside that feature. Promote it to a shared `hooks/` or `lib/` folder when a second feature needs it, and move it in the change that adds the second consumer ([Robin Wieruch](https://www.robinwieruch.de/react-folder-structure/)).
33
+ - Split a shared `lib/` by what each file serves once it holds two roles, such as data access beside formatting beside browser APIs. A flat `lib/` is the second folder to overflow after `components/`.
34
+ - Name a helper file for what it does rather than `utils` or `helpers`, so the folder listing reads as an index.
35
+
36
+ ## Fixtures and sample data
37
+
38
+ - Keep fixtures and sample data beside the feature they describe, and name the file for that feature.
39
+ - Promote a fixture to a shared folder only when two features' tests build from it.
40
+
41
+ ## A worked failure
42
+
43
+ An interface project's `components/` folder held 11 files when its first plan landed and 46 six days later, with its `lib/` at 36, all flat. Every plan in between named each new file as a sibling of the last, since the folder it was measured against was flat. The installed rules said to split by role and to keep domain logic in feature folders, and neither was applied. The split, when it came, touched 98 files, and the project's end to end folder has since grown flat again, four specs beside five helpers. Placing the twelfth component in a named subfolder would have cost that plan one folder.
@@ -0,0 +1,28 @@
1
+ ---
2
+ title: Test layout
3
+ description: Where test files live once their layer is chosen, being unit tests beside their module and an end to end folder split into specs, a fixtures module, page objects, and support, with one worked failure
4
+ ---
5
+
6
+ # Test layout
7
+
8
+ Read this before placing a new test file or a file a test imports. Which layer the test belongs at is settled first, by `test-craft`. This file starts from that answer and says where the file goes.
9
+
10
+ ## Unit and component tests
11
+
12
+ - Put a unit or component test beside the module it tests, named after it, so a reader opening the module finds its test in the same listing.
13
+ - Move the test with the module. A test left behind in a folder its module has left is the first sign the folder no longer holds one role.
14
+ - Keep a factory or builder one test file uses inside that file. Move it beside the tests that share it once a second file needs it, and name it for what it builds.
15
+
16
+ ## End to end tests
17
+
18
+ A browser suite starts as a folder of specs, and every helper written after the first lands beside them unless something says otherwise. Split the folder by role before the first support file lands.
19
+
20
+ - Keep specs in the folder the runner reads, and nothing there but specs once support code exists.
21
+ - Put fixtures in a dedicated module that extends the runner's base test, and merge several into one fixtures module the specs import ([Playwright fixtures](https://playwright.dev/docs/test-fixtures)). A spec importing its setup from one place is how a new spec finds what exists.
22
+ - Give each page object its own module, named for the page or region it drives ([Playwright fixtures](https://playwright.dev/docs/test-fixtures)).
23
+ - Put helpers, stubs, and seed data in a support folder beside the specs rather than among them. Name each file for what it serves, never a single `helpers` file every spec imports.
24
+ - Group specs by journey once they mix, such as checkout specs in one folder and account specs in another. A folder of a handful of specs covering one journey stays flat.
25
+
26
+ ## A worked failure
27
+
28
+ An interface project's end to end folder held four specs beside five helper files, all flat, after an earlier refactor had split its source folders by role. The project's plans named every new file as a literal flat path, so each helper landed as a sibling of the specs it served. A new spec then had no single place to find its setup, and the next helper went beside the others. One plan naming a support folder when the first helper arrived would have kept the specs as the folder's only role.
@@ -26,6 +26,8 @@ A lookup that resolves by head branch alone carries its own failure. A branch na
26
26
  - Label from the paths the branch changed, against a map the project declares, so the label set belongs to the project rather than to the skill
27
27
  - Apply labels after the pull request exists, so a label the remote does not carry costs a warning rather than the pull request
28
28
  - Report a refused label, since a warning nothing surfaces leaves the run indistinguishable from one that labelled
29
+ - Put a preview address on a pull request that changes a rendered surface when the project's deploy can mint one, since screenshots and a checklist leave a reviewer nothing to click into before merging
30
+ - Bound the wait on that preview and finish the chain without the link on a timeout, since a slow deploy must not hold the ship
29
31
 
30
32
  ## Must not
31
33
 
@@ -35,6 +37,7 @@ A lookup that resolves by head branch alone carries its own failure. A branch na
35
37
  - Edit a pull request that is not open, or record its number on a task
36
38
  - Name a domain of any one project in the skill body or its references
37
39
  - Create a label the map names and the remote lacks
40
+ - Dispatch a deploy whose command names no branch, since Cloudflare Pages publishes such a deploy to production
38
41
  - Emit anything after the result line
39
42
 
40
43
  ## Guards
@@ -197,6 +197,27 @@ Clean up the tmp file the way the UI-checklist step does, only after the call re
197
197
 
198
198
  Any other `reason` is one of the mirrored git refusals (`gh-missing`, `gh-failed`, `no-base`, `unreadable-tree`, `unreadable-changes`). Report it and move on without stopping the chain: a branch that carries no evidence images most of the time should not fail here on a transient git or `gh` read.
199
199
 
200
+ ### Post the preview address
201
+
202
+ Run this step only when the evidence step above returned `ok` or the UI checklist step posted a comment. Either one means the pull request changes a rendered surface, and a reviewer holding a checklist with no screenshots needs the live page most. Otherwise skip it silently.
203
+
204
+ The evidence comment is already posted, so the reviewer has the screenshots while the deploy runs. Mint the preview against the same `<number>`:
205
+
206
+ ```bash
207
+ canon pr preview <number> --json
208
+ ```
209
+
210
+ The verb dispatches the project's Pages deploy on the pull request's branch and waits on the run for up to 15 minutes. It refuses before dispatching anything when the deploy command passes no `--branch`, since that deploy would publish the branch to production. `canon docs pr-preview` states the contract. Read `reason` on the record rather than the exit code.
211
+
212
+ - `ok`: re-render the evidence body with the address on its first line, then post or update it exactly as the evidence step does, through the same tmp file and the `commentId` the record carries:
213
+
214
+ ```bash
215
+ canon pr evidence <number> --preview <url> --json
216
+ ```
217
+
218
+ - `no-deploy`: the project deploys nothing a dispatch can start. Say nothing and move on.
219
+ - Any other `reason`, being `unfenced`, `no-alias`, `run-failed`, `timeout`, or a mirrored `gh` refusal: report it with the record's `message` and move on without stopping the chain. A missing link costs the reviewer a click, and a held ship costs the whole chain.
220
+
200
221
  ### Record the number on the task
201
222
 
202
223
  Write the `number` the final command printed onto the task the branch is closing. Do not resolve it again. `${CLAUDE_SKILL_DIR}/REQUIREMENT.md` states why: a lookup that resolves by branch alone can return a closed pull request sharing that head, so the number is resolved once and reused rather than re-derived.
@@ -233,4 +254,8 @@ Add a line when the UI checklist step posted a comment, naming the pull request
233
254
 
234
255
  `📋 Posted the UI checklist to <number>.`
235
256
 
257
+ Add a line when the preview step returned an address:
258
+
259
+ `🔗 Preview: <url>`
260
+
236
261
  Do not add any other text.
@@ -13,6 +13,7 @@ Without this skill, implementation starts before anyone knows what it touches. A
13
13
 
14
14
  - Read the project's own Claude setup before scanning source, so the plan inherits decisions already made instead of reopening them
15
15
  - Name every file the work touches with the reason it is touched
16
+ - Place each new path against the layout guidance with a one-clause reason, loading that guidance only when the plan creates a file or folder, since the planner decides placement and the worker copies whatever path it is given
16
17
  - Surface each unresolved ambiguity as a numbered question carrying a suggested answer and an empty answer slot, so the plan is decision-ready in one pass
17
18
  - State which act a constraint forbids when it names a surface to leave alone, since conforming that surface and retargeting a pointer into it are different acts and only one is out of scope
18
19
  - Scale the output to the work. A two-file change with nothing to decide should not produce a plan file.
@@ -33,7 +33,7 @@ Coding standards live in `.claude/rules/`. Claude Code loads them automatically.
33
33
 
34
34
  Based on the feature description, identify and read source files that are directly relevant. Do not read entire directories speculatively. Where a folder carries an `index.md`, read it first and load only the files it points at.
35
35
 
36
- Measure against the tree rather than recall. Grep for each construct the plan will name and count the sites, so the plan carries the count the tree holds today. Confirm any work the plan sequences behind is still open, so it does not lead with an item that already shipped.
36
+ Measure against the tree rather than recall. Grep for each construct the plan will name and count the sites, so the plan carries the count the tree holds today. The tree tells a new path what exists beside it, not where it belongs, so a flat folder is a reading to place against rather than a pattern to copy. Confirm any work the plan sequences behind is still open, so it does not lead with an item that already shipped.
37
37
 
38
38
  Open each file before describing what is in it. A count or a claim carried from an earlier session, a summary, or another document is the most common way a plan ships the wrong scope.
39
39
 
@@ -45,6 +45,7 @@ What this skill adds on top of the standard:
45
45
 
46
46
  - Apply senior judgment to every `- Suggested:` line. Pick the best option and state it in one line with its reason or main tradeoff. No padding, no alternatives unless they change the pick.
47
47
  - Suggest a real default when best practice, the codebase, or prior context points to one.
48
+ - Load the `canon:codebase-layout` skill before writing the `**Files to touch:**` entries when any of them names a file or folder that does not exist yet, and give each new path its placement reason in its entry. Skip the load when every entry edits an existing file. Report it rather than proceeding silently when the skill does not resolve.
48
49
  - Prefer `None identified.` over low-signal fillers. A small feature should produce a short plan, not a padded one.
49
50
  - When three or more questions remain, keep chat output to the file pointer plus a short summary. Inline chat is fine when two or fewer remain.
50
51
 
@@ -52,3 +52,12 @@ Moving the guidance into a path-scoped rule does not reach the session that need
52
52
  - Writing a UI change's tests and its visual checklist after implementation: `ui-test`
53
53
  - Grading the tests a pull request adds: `review-pr`
54
54
  - Whether the guidance moves where a session puts a test, which needs a measured with-and-without run rather than a rule here
55
+
56
+ ## Measured
57
+
58
+ 2026-09-19, one Sonnet run per arm over the `pull` arm of the toolkit's own sandbox scenario for this skill, a page with an end to end spec already beside it and a prompt adding a loading state.
59
+
60
+ - With the skill: the loading state landed in a new component test and the existing end to end spec stayed unchanged.
61
+ - Without it, on a plugin dir lacking the skill: no `Skill` call fired, and the run made the same placement, one component test with three cases and the spec unchanged.
62
+
63
+ The arm did not discriminate, so the run shows no movement from the skill. It also shows that the pull toward extending an existing spec is too weak to move an unaided session, which is a fixture finding and not evidence about the body. A stronger pull, such as a prompt naming the spec, is the next arm to try before reading this as a rework signal on the skill. One run per arm on one model cannot separate a difference of one test from noise.
@@ -28,7 +28,7 @@ Suites dominated by end to end tests inflate both runtime and flake ([Google Tes
28
28
  ## Motion off by default
29
29
 
30
30
  - Run with motion reduced unless the test asserts motion, and opt back in per test with `page.emulateMedia({ reducedMotion: 'no-preference' })`.
31
- - Set it once in the config's `use` block as `reducedMotion: 'reduce'` rather than per test, since the option defaults to `'no-preference'` ([TestOptions](https://playwright.dev/docs/api/class-testoptions)). A site honoring the reduced preference then has nothing to wait out in any test that does not opt back in.
31
+ - Set it once in the config's `use` block as `contextOptions: { reducedMotion: 'reduce' }` rather than per test, since the option defaults to `'no-preference'` ([BrowserContext options](https://playwright.dev/docs/api/class-browser#browser-new-context)). A top-level `use.reducedMotion` reaches no page and type-checks nowhere, so a spec asserting the media query is what catches it. A site honoring the reduced preference then has nothing to wait out in any test that does not opt back in.
32
32
 
33
33
  ## Isolation and speed
34
34
 
@@ -94,7 +94,8 @@ Full help: `canon <command> --help`. Behavior notes for the install and sync ver
94
94
  | `canon pr head` | Compare the head a pull request object reports against the branch tip the remote carries, naming both shas (`--root`, `--json`) |
95
95
  | `canon pr checks` | Report the check runs belonging to the branch tip, reading pending for a tip carrying none rather than clean (`--root`, `--json`) |
96
96
  | `canon pr review-state` | Report the commit and instant the last review pass covered, off the pass's own marker rather than the fields GitHub stamps at submission (`--root`, `--json`) |
97
- | `canon pr evidence` | Render a before-and-after comment comparing changed `evidence/` images against the merge base, editing its own prior comment in place rather than posting a second (`--root`, `--json`) |
97
+ | `canon pr evidence` | Render a before-and-after comment comparing changed `evidence/` images against the merge base, editing its own prior comment in place rather than posting a second (`--root`, `--json`, `--preview`) |
98
+ | `canon pr preview` | Dispatch the Pages deploy on a pull request's branch and report the preview address the run prints, refusing a deploy that passes no `--branch` (`--root`, `--json`, `--timeout`) |
98
99
  | `canon repo metadata propose` | Compare a description, homepage, and topic set computed from the README and `package.json` against what the remote carries, writing nothing (`--root`, `--json`) |
99
100
  | `canon repo metadata apply` | Write an explicitly supplied description, homepage, or topic set to the remote through `gh repo edit` (`--description`, `--homepage`, `--topics`, `--root`, `--json`) |
100
101
  | `canon census [path]` | Report tracked file count, a breakdown by extension, and a line total that skips whatever reads as binary (`--json`) |
@@ -31,6 +31,7 @@ CLI catalog and invocation rules for agents, split by command domain. Start with
31
31
  - [Output shape](output-shape.md): Two framed shapes every command renders into, how JSON and --names modes keep stdout clean, and the exit discipline that lets piped output drain
32
32
  - [Overview](overview.md): What this folder covers, the invocation rules every command inherits, and where domain behavior is documented instead
33
33
  - [The pull request evidence comparison](pr-evidence.md): What canon pr evidence compares, the marker that lets it edit its own comment rather than duplicate it, the refusal reasons it names, and why the comparison anchors at the merge base rather than the previous push
34
+ - [The pull request preview deploy](pr-preview.md): How canon pr preview finds a fenced Cloudflare Pages deploy, dispatches it on a pull request's branch, reads the alias the run prints, the refusal reasons it names, and why an unfenced deploy is refused before anything runs
34
35
  - [Head-sensitive pull request reads](pr-reads.md): Resolving a branch tip from the remote rather than from the pull request object, reading what a review pass covered off its own marker rather than off GitHub's submission stamps, why an empty run list is not a pass, the refusal reasons each verb names, and what the remote read costs
35
36
  - [Records](records.md): The two roots a record folder resolves at, validating the session records and the standards corpus, the per-kind checks, the refusal reasons, migrating a record a frontmatter change orphaned, claiming the ordinal intake and groundwork share, reading each folder's size and growth, backing the folders to a private remote, and which root each kind defaults to
36
37
  - [Restated instructions](restated.md): Counting the instructions the always-loaded file and every path-scoped rule share with the seed, the shipped skill bodies, and each other, how a match is decided, the three classes, which surface a later edit starts from, and why the sweep reports rather than gates
@@ -51,7 +51,7 @@ image or marks the case new.
51
51
  | `ok` | A body was rendered. `commentId` is set when a marked comment already exists. |
52
52
  | `no-evidence` | Nothing in the diff carries an `evidence/` segment. An ordinary silent no-op. |
53
53
  | `gh-missing` | `gh` is not on the path, so no pull request could be resolved. |
54
- | `gh-failed` | `gh` could not answer for this repository or branch. |
54
+ | `gh-failed` | `gh` could not answer for this repository or branch, or read its comments. |
55
55
  | `no-branch` | The pull request carries no head branch name. |
56
56
  | `no-object-head` | The pull request object reported no head commit. |
57
57
  | `no-base` | No base resolves against the trunk. |
@@ -99,6 +99,14 @@ also why this verb renders the whole comment body rather than handing each
99
99
  skill a record to format on its own: a fix to the table shape or the
100
100
  collapsed-details wrapper lands once, not twice.
101
101
 
102
+ ## The preview line
103
+
104
+ `--preview <url>` opens the body with the branch's preview address, and a run
105
+ without it carries forward the address the marked comment already holds. With
106
+ no evidence in the diff and an address in hand, the body is the address and the
107
+ marker alone, reported as `ok`. `canon docs pr-preview` covers where the
108
+ address comes from.
109
+
102
110
  ## What a collapsed comment still leaves to GitHub
103
111
 
104
112
  GitHub already draws its own before-and-after comparison on the Files Changed
@@ -0,0 +1,103 @@
1
+ ---
2
+ title: The pull request preview deploy
3
+ description: How canon pr preview finds a fenced Cloudflare Pages deploy, dispatches it on a pull request's branch, reads the alias the run prints, the refusal reasons it names, and why an unfenced deploy is refused before anything runs
4
+ ---
5
+
6
+ # The pull request preview deploy
7
+
8
+ `canon pr preview` publishes a pull request's branch to a Cloudflare Pages
9
+ preview and reports the address. `git-pr` calls it after the evidence
10
+ comparison, then re-renders that comment through `canon pr evidence --preview`
11
+ so the link opens it. A reviewer gets a page to click into before merging
12
+ rather than screenshots and a checklist alone.
13
+
14
+ ```bash
15
+ canon pr preview --json
16
+ canon pr preview 1341 --json --timeout 20
17
+ ```
18
+
19
+ ## Which workflow it dispatches
20
+
21
+ The verb reads `.github/workflows/*.yml` and `*.yaml` in the checkout and picks
22
+ the first file, in path order, that meets four tests:
23
+
24
+ - It runs `pages deploy`.
25
+ - It carries a `workflow_dispatch` trigger, since nothing else lets a caller
26
+ start it on a branch.
27
+ - Its deploy passes `--branch=${{ github.ref_name }}`.
28
+ - It prints a `canon-preview-alias:` line.
29
+
30
+ The workflow is read before the pull request, so a project with no fenced
31
+ deploy is refused without a network call.
32
+
33
+ ## Why an unfenced deploy is refused
34
+
35
+ Cloudflare Pages publishes a deploy that names no branch to production. A
36
+ dispatch from an unmerged branch through such a workflow ships that branch to
37
+ the live site, so the verb refuses as `unfenced` and dispatches nothing. The
38
+ fix is one flag on the deploy command, and the cloudflare stack's
39
+ `deploy.yml` already carries it.
40
+
41
+ ## Where the address comes from
42
+
43
+ The workflow prints the alias Cloudflare returned for the deploy, read from
44
+ `wrangler-action`'s `pages-deployment-alias-url` output, on a fixed line:
45
+
46
+ ```plaintext
47
+ canon-preview-alias: https://<alias>.<project>.pages.dev
48
+ ```
49
+
50
+ The verb reads that line out of the finished run's log rather than computing
51
+ the address from the branch name. Cloudflare flattens a branch into its alias
52
+ and may truncate it, and a computed address that is wrong is worse than none.
53
+ The output needs wrangler 3.78 or later, so the workflow pins
54
+ `wranglerVersion`.
55
+
56
+ `gh workflow run` reports no run id. The verb lists the branch's dispatched
57
+ runs before dispatching and takes the one id that appears afterwards, which
58
+ keeps the match independent of the local clock.
59
+
60
+ ## What the record carries
61
+
62
+ `reason` on the record is what a caller branches on, not the exit code:
63
+
64
+ | Reason | What it means |
65
+ | ------------- | -------------------------------------------------------------------------- |
66
+ | `ok` | The preview was published. `url` carries the address and `runId` the run. |
67
+ | `no-deploy` | No dispatchable workflow runs `pages deploy`. |
68
+ | `unfenced` | The deploy passes no `--branch`, so nothing was dispatched. |
69
+ | `no-alias` | The workflow prints no alias line, or the finished run's log carried none. |
70
+ | `run-failed` | The deploy run finished without succeeding. `runId` names it. |
71
+ | `timeout` | The run did not finish inside `--timeout` minutes, 15 by default. |
72
+ | `bad-timeout` | `--timeout` was not a positive number. |
73
+ | `gh-missing` | `gh` is not on the path. |
74
+ | `gh-failed` | `gh` could not read the pull request, or list or dispatch the workflow. |
75
+ | `no-branch` | The pull request carries no head branch name. |
76
+
77
+ The exit is 0 on `ok` and 1 on every refusal. A `timeout` leaves the run
78
+ going, so the preview may still land after the verb has given up on it.
79
+
80
+ ## How the address reaches the pull request
81
+
82
+ `canon pr evidence --preview <url>` puts `**Preview:** <url>` on the first line
83
+ of the evidence body. With no evidence image in the diff, the body is that line
84
+ and the trailing marker alone, so a pull request with a checklist and no
85
+ screenshots still gets one comment a later call can find and edit.
86
+
87
+ A later `canon pr evidence` run without `--preview` reads the address off the
88
+ marked comment and carries it into the new body. `git-followup` re-renders the
89
+ comment after every push, and without that carry the first push would delete
90
+ the link.
91
+
92
+ ## Removing a preview
93
+
94
+ A `pull_request` trigger on `closed` in the same workflow runs a cleanup job
95
+ that deletes every preview deployment Cloudflare holds for the branch. The
96
+ deploy job carries `if: github.event_name != 'pull_request'`, so a closed pull
97
+ request never deploys.
98
+
99
+ ## What this does not cover
100
+
101
+ The verb builds only the deployed-and-public case. A private repository whose
102
+ previews need access protection, and a project with no deploy at all where a
103
+ local server would stand in, both wait for a project that needs them.
@@ -223,6 +223,7 @@ This section is the corpus the coverage claim is measured against: every name `c
223
223
  | `canon:draft-and-pick` | When the call is taste and wants several candidates rendered side by side |
224
224
  | `canon:task-board` | When a decided item needs a file on the board, or a shipped one needs archiving |
225
225
  | `canon:plan-feature` | When the approach is settled and the next step is a plan |
226
+ | `canon:codebase-layout` | When a plan or change adds a file and you need to decide which folder holds it |
226
227
 
227
228
  ### Build the feature
228
229
 
@@ -19,7 +19,7 @@ Your primary directive is to maintain long-term system health over short-term co
19
19
  - Implement only the functionality required for the immediate task (YAGNI).
20
20
  - Extract shared logic into single-purpose utilities. Never duplicate behavior across modules (DRY).
21
21
  - Each function, module, and component should have a single reason to change (SRP).
22
- - Apply SRP to directories: once a folder mixes distinct roles and grows past a handful of files, split it into subfolders by role.
22
+ - Apply SRP to directories: once a folder mixes distinct roles and grows past a handful of files, split it into subfolders by role. Load the `canon:codebase-layout` skill before placing a new file, and report it rather than proceeding silently when it does not resolve.
23
23
  - Favor composition over inheritance.
24
24
  - Prioritize native platform capabilities over third-party libraries.
25
25
 
@@ -27,6 +27,7 @@ paths:
27
27
  - Place all Playwright tests within the `e2e/` directory.
28
28
  - Never place Playwright tests inside `src/`.
29
29
  - Scope the three rules above to tests the Playwright runner executes. A Vitest test importing a browser driver to exercise project code stays beside its module, where Vitest looks for it.
30
+ - Keep specs apart from their fixtures, page objects, and helpers inside `e2e/`. Load the `canon:codebase-layout` skill for the folder shape.
30
31
 
31
32
  ## Timers and async
32
33
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/canon",
3
3
  "type": "module",
4
- "version": "4.95.0",
4
+ "version": "4.97.0",
5
5
  "description": "Infrastructure and quality tooling for developer workflows",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -59,6 +59,11 @@ export const MISC_CASES: readonly SkillCase[] = [
59
59
  'The loading state already shipped. Should its test be an e2e test or a component one, and is the one I wrote any good?',
60
60
  expect: 'test-craft',
61
61
  },
62
+ {
63
+ prompt:
64
+ "The e2e folder has specs and helpers mixed together and I'm adding two more, where should they go?",
65
+ expect: 'codebase-layout',
66
+ },
62
67
  {
63
68
  prompt: 'Does our github about text still match what the readme says?',
64
69
  expect: 'repo-metadata',