@erclx/canon 4.96.0 → 4.98.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 +29 -27
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/codebase-layout/REQUIREMENT.md +46 -0
- package/claude/skills/codebase-layout/SKILL.md +57 -0
- package/claude/skills/codebase-layout/references/adopted.md +34 -0
- package/claude/skills/codebase-layout/references/frontend.md +43 -0
- package/claude/skills/codebase-layout/references/tests.md +28 -0
- package/claude/skills/draft-and-pick/REQUIREMENT.md +2 -0
- package/claude/skills/draft-and-pick/SKILL.md +2 -1
- package/claude/skills/draft-and-pick/references/frame.html +118 -0
- package/claude/skills/git-pr/REQUIREMENT.md +3 -0
- package/claude/skills/git-pr/SKILL.md +25 -0
- package/claude/skills/plan-feature/REQUIREMENT.md +1 -0
- package/claude/skills/plan-feature/SKILL.md +2 -1
- package/claude/skills/test-craft/REQUIREMENT.md +9 -0
- package/claude/skills/test-craft/references/e2e.md +1 -1
- package/docs/agents/capture.md +2 -2
- package/docs/agents/commands.md +2 -1
- package/docs/agents/index.md +1 -0
- package/docs/agents/pr-evidence.md +9 -1
- package/docs/agents/pr-preview.md +103 -0
- package/docs/workflow/ai-workflow.md +1 -0
- package/governance/rules/core/000-constitution.md +1 -1
- package/governance/rules/lib/300-testing-ts.md +1 -0
- package/package.json +1 -1
- package/scripts/core/regen-hero.sh +33 -1
- package/src/claude/cases/misc.ts +5 -0
- package/src/commands/pr.ts +300 -13
- package/src/pr/evidence.ts +36 -1
- package/src/pr/preview.ts +178 -0
- package/src/slides/styles.ts +28 -3
- package/tooling/astro/configs/playwright.config.ts +3 -1
- package/tooling/cloudflare/configs/.github/workflows/deploy.yml +64 -2
- package/tooling/cloudflare/reference.md +6 -3
- package/tooling/nextjs/configs/playwright.config.ts +3 -1
- package/tooling/vite-react/configs/playwright.config.ts +3 -1
- package/tooling/web/configs/e2e/home.spec.ts +8 -0
package/docs/agents/capture.md
CHANGED
|
@@ -18,9 +18,9 @@ canon capture https://example.com --selector .window --out preview.png
|
|
|
18
18
|
|
|
19
19
|
## What this repository captures
|
|
20
20
|
|
|
21
|
-
A capture set here spans two folders. `assets/captures/` holds
|
|
21
|
+
A capture set here spans two folders. `assets/captures/` holds three sources and the template each is written from, and `assets/evidence/` holds their images and the built-page capture a document points at with the stamp answering for each, under the segment `canon pr evidence` compares. One run over the source folder rebuilds every image, and it takes `--out assets/evidence` to put each PNG where its document points rather than beside the markup it rendered. None of the three sources is edited by hand. `scripts/core/regen-hero.sh` writes each `.html` from the template beside it, filling one shared value map into all of them, and `bun run check` regenerates them and fails on the difference.
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
Two of the three take catalog data, so a stack gaining a rule moves the frame on the next run. `install.html` is the exception, since it holds terminal text from a real run in its template rather than derived from a live catalog at build time. `assets/evidence/arrival.png` is the one image with no source here: it is the built page's `.fold` element captured in dark mode against a local `web/` preview, since `canon capture` takes no color scheme and the page follows the browser's default of light, so it is retaken by hand when the page's first screen changes.
|
|
24
24
|
|
|
25
25
|
`assets/captures/` is read flat and never descends, by the regeneration script, by this command, and by the drift stage alike. A source nested a further folder down is skipped by all three with nothing reported, so a new frame is a template dropped directly in rather than a folder of its own. The split is what retired the name prefix the flat layout used to need: a frame is named for itself now, since `assets/captures/` no longer mixes markup in with the images.
|
|
26
26
|
|
package/docs/agents/commands.md
CHANGED
|
@@ -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`) |
|
package/docs/agents/index.md
CHANGED
|
@@ -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
|
@@ -77,8 +77,11 @@ GOV_JSON="$(catalog gov list)"
|
|
|
77
77
|
STANDARDS_JSON="$(catalog standards list)"
|
|
78
78
|
SNIPPETS_JSON="$(catalog snippets list)"
|
|
79
79
|
TOOLING_JSON="$(catalog tooling list)"
|
|
80
|
+
# The catalog ledger names three commands, and the registered set is only
|
|
81
|
+
# readable from the root help, which lists one per line under `Commands:`.
|
|
82
|
+
COMMANDS_HELP="$(cd "$PROJECT_ROOT" && CANON_NON_INTERACTIVE=1 bun src/cli.ts --help 2>/dev/null)"
|
|
80
83
|
|
|
81
|
-
for payload in "$COUNTS_JSON" "$SKILLS_JSON" "$GOV_JSON" "$STANDARDS_JSON" "$SNIPPETS_JSON" "$TOOLING_JSON"; do
|
|
84
|
+
for payload in "$COUNTS_JSON" "$SKILLS_JSON" "$GOV_JSON" "$STANDARDS_JSON" "$SNIPPETS_JSON" "$TOOLING_JSON" "$COMMANDS_HELP"; do
|
|
82
85
|
if [ -z "$payload" ]; then
|
|
83
86
|
echo "regen-hero: a catalog returned nothing, refusing to write a zeroed hero" >&2
|
|
84
87
|
exit 1
|
|
@@ -98,6 +101,7 @@ printf '%s' "$GOV_JSON" >"$PAYLOAD_DIR/gov.json"
|
|
|
98
101
|
printf '%s' "$STANDARDS_JSON" >"$PAYLOAD_DIR/standards.json"
|
|
99
102
|
printf '%s' "$SNIPPETS_JSON" >"$PAYLOAD_DIR/snippets.json"
|
|
100
103
|
printf '%s' "$TOOLING_JSON" >"$PAYLOAD_DIR/tooling.json"
|
|
104
|
+
printf '%s' "$COMMANDS_HELP" >"$PAYLOAD_DIR/commands-help.txt"
|
|
101
105
|
printf '%s' "$TOKEN_CSS" >"$PAYLOAD_DIR/tokens.css"
|
|
102
106
|
|
|
103
107
|
export PAYLOAD_DIR
|
|
@@ -326,8 +330,36 @@ for (const [label, rows] of [
|
|
|
326
330
|
}
|
|
327
331
|
}
|
|
328
332
|
|
|
333
|
+
// The ledger names a few real entries per domain. Chosen by hand for the same
|
|
334
|
+
// reason FEATURED_SKILLS is, and asserted against the catalog so a rename fails
|
|
335
|
+
// the run rather than leaving a name the repository no longer ships.
|
|
336
|
+
const commandNames = readFileSync(PAYLOAD_DIR + "/commands-help.txt", "utf8")
|
|
337
|
+
.split("\n")
|
|
338
|
+
.map((line) => line.match(/^[│\s]{2,}(?<name>[a-z][a-z-]*)\s/)?.groups?.name)
|
|
339
|
+
.filter(Boolean)
|
|
340
|
+
const LEDGER = [
|
|
341
|
+
["skills", skills.length, ["plan-feature", "role-orchestrator", "review-pr"], skills],
|
|
342
|
+
["rules", rules.length, ["behavior", "memory", "testing"], rules],
|
|
343
|
+
["standards", standards.length, ["markdown", "plan", "teach"], standards],
|
|
344
|
+
["commands", commandCount, ["gov", "tasks", "design"], commandNames],
|
|
345
|
+
["stacks", toolingStacks.length, ["astro", "python", "web"], toolingStacks.map((stack) => stack.name)],
|
|
346
|
+
]
|
|
347
|
+
for (const [domain, , chosen, names] of LEDGER) {
|
|
348
|
+
const known = new Set(names)
|
|
349
|
+
const missing = chosen.filter((name) => !known.has(name))
|
|
350
|
+
if (missing.length > 0) {
|
|
351
|
+
console.error(`regen-hero: ledger ${domain} names missing from the catalog: ${missing.join(", ")}`)
|
|
352
|
+
process.exit(1)
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
const ledgerRows = LEDGER.map(
|
|
356
|
+
([domain, count, chosen]) =>
|
|
357
|
+
` <div class="row"><span class="count">${count}</span><span class="domain">${domain}</span><span class="names">${escape(chosen.join(", "))}</span></div>`,
|
|
358
|
+
).join("\n")
|
|
359
|
+
|
|
329
360
|
const values = {
|
|
330
361
|
TOKENS: tokenCss,
|
|
362
|
+
LEDGER_ROWS: ledgerRows,
|
|
331
363
|
GOV_STACK_ROWS: govStackRows,
|
|
332
364
|
GOV_RULE_ROWS: govRuleRows,
|
|
333
365
|
STANDARD_ROWS: standardRows,
|
package/src/claude/cases/misc.ts
CHANGED
|
@@ -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',
|