@erclx/canon 5.74.0 → 5.75.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/CHANGELOG.md +18 -0
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/create-rule/SKILL.md +5 -1
- package/claude/skills/create-rule/references/ledger.md +73 -0
- package/claude/skills/create-rule/references/rule.md +1 -1
- package/claude/skills/create-skill/references/skill-practice.md +1 -1
- package/claude/skills/pr-evidence/references/reel.md +9 -9
- package/claude/skills/proposal-critique/references/adopted.md +44 -0
- package/claude/skills/ui-film/SKILL.md +8 -7
- package/docs/agents/audits.md +24 -24
- package/docs/agents/migrate.md +10 -10
- package/docs/migrations/by-verb.md +2 -0
- package/governance/detect.toml +4 -0
- package/governance/ledger/claude/rule.md +16 -0
- package/governance/rules/claude/rule.md +13 -0
- package/package.json +1 -1
- package/scripts/ci/refresh-frames-pr.sh +11 -12
- package/src/audits/catalog.ts +7 -2
- package/src/capture/stamp.ts +3 -5
- package/src/claude/skills-audit.ts +65 -0
- package/src/cli/help.ts +1 -1
- package/src/commands/claude.ts +6 -2
- package/src/commands/gov.ts +12 -7
- package/src/commands/migrate.ts +6 -2
- package/src/commands/take.ts +36 -7
- package/src/counts/catalogs.ts +3 -5
- package/src/gate/measures.ts +1 -144
- package/src/gate/stages.ts +0 -23
- package/src/gov/audit.ts +59 -13
- package/src/markdown/scope.ts +2 -0
- package/src/migrate/session-defaults.ts +114 -4
- package/src/take/encode.ts +9 -1
- package/src/take/film.ts +17 -2
- package/src/take/fixtures/busy.html +107 -0
- package/src/take/perform.ts +99 -25
- package/src/take/screencast.ts +4 -3
- package/src/take/stepper.ts +33 -20
- package/tooling/claude/reference.md +4 -2
- package/tooling/claude/seeds/.claude/hooks/plan-guard.sh +239 -0
- package/tooling/claude/seeds/.claude/settings.json +8 -0
- package/scripts/core/regen-hero.ts +0 -113
- package/src/capture/frame-values.ts +0 -326
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [5.75.0](https://github.com/erclx/canon/compare/v5.74.0...v5.75.0) (2026-10-29)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **claude:** audit proposal-critique against its ledger ([#2845](https://github.com/erclx/canon/issues/2845)) ([397699b](https://github.com/erclx/canon/commit/397699bdeb7da47a39fb210d2850e8502d503a1f))
|
|
9
|
+
* **claude:** seed the plan guard and wire installed projects ([#2851](https://github.com/erclx/canon/issues/2851)) ([e312aab](https://github.com/erclx/canon/commit/e312aabaa69941ce01f656a635b98ef9e01680f1))
|
|
10
|
+
* **gov:** check a project's ledgers under canon/ledger/ ([#2848](https://github.com/erclx/canon/issues/2848)) ([65fb0e6](https://github.com/erclx/canon/commit/65fb0e6e60ef22c16a7743964865fc62d6b55ed8))
|
|
11
|
+
* **governance:** route a rule edit to create-rule ([#2847](https://github.com/erclx/canon/issues/2847)) ([010b639](https://github.com/erclx/canon/commit/010b63970a874905ec9ee3db520b7cb5f96db778))
|
|
12
|
+
* **take:** refuse a take past --max-seconds as over-cap ([#2855](https://github.com/erclx/canon/issues/2855)) ([d9d6424](https://github.com/erclx/canon/commit/d9d642434d082f9f7d4b1508723576044e55f6a4))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* **take:** cut a screencast clip at the stage clock ([#2853](https://github.com/erclx/canon/issues/2853)) ([3588832](https://github.com/erclx/canon/commit/3588832a8263079ff8340b2cf7af3c492bf4cc66))
|
|
18
|
+
* **take:** hold canon take steady on a page whose frames run slow ([#2852](https://github.com/erclx/canon/issues/2852)) ([f2b55f9](https://github.com/erclx/canon/commit/f2b55f9a2f948c46fb4c84720da7596df31107a5))
|
|
19
|
+
* **take:** scroll a target taller than the window ([#2854](https://github.com/erclx/canon/issues/2854)) ([976b85d](https://github.com/erclx/canon/commit/976b85d27391106d2cb7e03072451de5a128d2fd))
|
|
20
|
+
|
|
3
21
|
## [5.74.0](https://github.com/erclx/canon/compare/v5.73.0...v5.74.0) (2026-10-28)
|
|
4
22
|
|
|
5
23
|
|
|
@@ -86,6 +86,10 @@ Read `${CLAUDE_SKILL_DIR}/references/checklist.md` and run every bullet of the w
|
|
|
86
86
|
|
|
87
87
|
## After writing
|
|
88
88
|
|
|
89
|
-
Emit the full path on its own line: `.claude/rules/project/<subdir>/<slug>.md`. Remind the user that Claude Code loads path-scoped rules when it reads or writes a matching file, and always-on rules every session. When a bullet claims outside practice, tell them to cite its source in the commit that adds the rule.
|
|
89
|
+
Emit the full path on its own line: `.claude/rules/project/<subdir>/<slug>.md`. Remind the user that Claude Code loads path-scoped rules when it reads or writes a matching file, and always-on rules every session. When a bullet claims outside practice, tell them to cite its source, in a ledger or in the commit that adds the rule.
|
|
90
90
|
|
|
91
91
|
Show the new rule as a diff in chat and commit nothing until the operator says OK.
|
|
92
|
+
|
|
93
|
+
### When a bullet cites outside practice
|
|
94
|
+
|
|
95
|
+
Offer to record each source in a ledger at `canon/ledger/rules/<subdir>/<slug>.md`, which `canon gov audit` checks row for bullet. Read `${CLAUDE_SKILL_DIR}/references/ledger.md` for the layout and the row shape before writing one.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Project ledger reference
|
|
3
|
+
description: Where a project's rule and skill ledgers live under canon/ledger/, and the row shape both audits check, one table per H2 and one row per bullet
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Project ledger reference
|
|
7
|
+
|
|
8
|
+
## Contents
|
|
9
|
+
|
|
10
|
+
- [Overview](#overview)
|
|
11
|
+
- [Where it lives](#where-it-lives)
|
|
12
|
+
- [Shape](#shape)
|
|
13
|
+
- [Rows](#rows)
|
|
14
|
+
- [Example](#example)
|
|
15
|
+
|
|
16
|
+
## Overview
|
|
17
|
+
|
|
18
|
+
A ledger names what stands behind each bullet of a rule or a skill file, kept beside the file rather than inside it, so the reasoning costs no load. A bullet claiming outside practice names its source, and a bullet recording a project decision names what it prevents.
|
|
19
|
+
|
|
20
|
+
A project writes a ledger where it wants one and owes none. `canon gov audit` and `canon claude skills audit` check each ledger that exists against its file and report a ledger whose file is gone.
|
|
21
|
+
|
|
22
|
+
## Where it lives
|
|
23
|
+
|
|
24
|
+
Every ledger sits under `canon/ledger/`, which Claude Code never loads. A ledger under `.claude/rules/` would load as a rule on every matching read.
|
|
25
|
+
|
|
26
|
+
- A rule at `.claude/rules/project/<subdir>/<slug>.md` takes `canon/ledger/rules/<subdir>/<slug>.md`
|
|
27
|
+
- A skill body at `.claude/skills/<skill>/SKILL.md` takes `canon/ledger/skills/<skill>/SKILL.md`
|
|
28
|
+
- A skill reference at `.claude/skills/<skill>/references/<file>.md` takes `canon/ledger/skills/<skill>/<file>.md`, dropping the `references/` segment
|
|
29
|
+
- Move or delete a ledger in the change that moves or deletes its file, since the audits report one left behind
|
|
30
|
+
|
|
31
|
+
## Shape
|
|
32
|
+
|
|
33
|
+
The audits match rows to bullets by count under a heading of the same name, so the shape is fixed and whether a row supports its bullet is a review judgment.
|
|
34
|
+
|
|
35
|
+
- Write one `## <heading>` per H2 in the file, spelled as the file spells it
|
|
36
|
+
- Put one table under each heading, with one row per bullet in the file's order
|
|
37
|
+
- Give a file with no H2 one table under the title
|
|
38
|
+
- Write no other table in the ledger, since every table row under a heading counts. A sample table inside a code fence is not counted.
|
|
39
|
+
|
|
40
|
+
A skill file counts a few lines differently from a rule.
|
|
41
|
+
|
|
42
|
+
- Give each top-level ordered step a row, the way a bullet takes one
|
|
43
|
+
- Give `## Contents` no table, since it repeats the headings
|
|
44
|
+
- Stop a `SKILL.md` ledger's tables at the body's first closing section, such as `## Excuses and rebuttals` or `## What this delegates`, since a table under one reads as rows with no bullets
|
|
45
|
+
|
|
46
|
+
## Rows
|
|
47
|
+
|
|
48
|
+
Each row names the bullet and the one thing behind it.
|
|
49
|
+
|
|
50
|
+
- **Sourced:** a link to the primary source, with the line it says, such as the framework's own docs or the language spec
|
|
51
|
+
- **House choice:** a cell opening `House choice:` and naming the bug class the bullet prevents, when no outside source takes a side
|
|
52
|
+
- **Decision:** a house choice naming what the bullet prevents and the option it beat, with the date it was decided
|
|
53
|
+
- Cut a bullet that has neither a source nor a bug class behind it, rather than writing a row that says so
|
|
54
|
+
|
|
55
|
+
## Example
|
|
56
|
+
|
|
57
|
+
A project React rule in the `framework/` subdir, with two bullets under `## Component patterns`, takes this ledger at `canon/ledger/rules/framework/react.md`:
|
|
58
|
+
|
|
59
|
+
```markdown
|
|
60
|
+
---
|
|
61
|
+
title: React rule ledger
|
|
62
|
+
description: Sources for the project React rule, one row per bullet
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
# React rule ledger
|
|
66
|
+
|
|
67
|
+
## Component patterns
|
|
68
|
+
|
|
69
|
+
| Bullet | Source |
|
|
70
|
+
| ---------------------------- | --------------------------------------------------------------------------------------------- |
|
|
71
|
+
| Stable keys over array index | [Rendering lists](https://react.dev/learn/rendering-lists): index keys cause subtle bugs |
|
|
72
|
+
| Named exports | House choice: a default export renamed at import drifts from the component's name in a search |
|
|
73
|
+
```
|
|
@@ -101,7 +101,7 @@ A bullet claiming outside practice, such as a language idiom or a library's corr
|
|
|
101
101
|
|
|
102
102
|
- Cite the source in the commit that adds the rule, naming a primary page or the bug class the bullet prevents
|
|
103
103
|
- Cut a bullet that has neither a source nor a bug class behind it
|
|
104
|
-
-
|
|
104
|
+
- A project can hold its rules to a source ledger kept outside the installed tree, so the citation costs no load, laid out in `${CLAUDE_SKILL_DIR}/references/ledger.md`
|
|
105
105
|
- A bullet resting on how Claude Code behaves ends with a `Revisit when` sentence, per `### Phrasing`
|
|
106
106
|
|
|
107
107
|
## Location
|
|
@@ -73,4 +73,4 @@ The audit counts a few lines in a ledgered file apart from a rule's.
|
|
|
73
73
|
- It keeps a wider ledger list beside it, adding the skills that teach a craft drawn from outside sources without a practice's closing shape
|
|
74
74
|
- A skill on the wider list alone owes the ledger and none of the three sections
|
|
75
75
|
- Never mark a skill as a practice skill in its own frontmatter, since a self-declared kind lets a skill exempt itself.
|
|
76
|
-
- Both lists name this toolkit's shipped skills alone
|
|
76
|
+
- Both lists name this toolkit's shipped skills alone. A project's own skill owes no ledger, and one it writes is checked per `${CLAUDE_SKILL_DIR}/../create-rule/references/ledger.md`.
|
|
@@ -62,23 +62,23 @@ Write one plan entry for each beat chosen, then skip when none was.
|
|
|
62
62
|
|
|
63
63
|
The negative test is a judgment over the box's text. A misread box costs a weaker reel and never a wrong pass, since the still check below guards the footage and the UI reviewer drives every box regardless.
|
|
64
64
|
|
|
65
|
-
Keep the whole reel at or under 12 seconds. Each beat opens and closes on a 0.6 s hold, so give each action's `wait` only what its result needs to settle.
|
|
65
|
+
Keep the whole reel at or under 12 seconds, which the take below enforces. Each beat opens and closes on a 0.6 s hold, so give each action's `wait` only what its result needs to settle.
|
|
66
66
|
|
|
67
67
|
## Film the reel
|
|
68
68
|
|
|
69
69
|
```bash
|
|
70
|
-
canon take <url> .canon/tmp/pr/reel/<number>/plan.json --out .canon/tmp/pr/reel/<number>/takes --reel .canon/tmp/pr/reel/<number>/reel.webp --json
|
|
70
|
+
canon take <url> .canon/tmp/pr/reel/<number>/plan.json --out .canon/tmp/pr/reel/<number>/takes --reel .canon/tmp/pr/reel/<number>/reel.webp --max-seconds 12 --json
|
|
71
71
|
```
|
|
72
72
|
|
|
73
73
|
Branch on the record's `reason`, never on the exit code.
|
|
74
74
|
|
|
75
|
-
| Record
|
|
76
|
-
|
|
|
77
|
-
| No `reason`
|
|
78
|
-
| `reel-failed`
|
|
79
|
-
|
|
|
80
|
-
|
|
81
|
-
|
|
75
|
+
| Record | Route |
|
|
76
|
+
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
77
|
+
| No `reason` | Continue to the stills |
|
|
78
|
+
| `reel-failed` | Skip as `reel-failed` with the record's `message` |
|
|
79
|
+
| `target-offscreen` | Add a setup step that brings the target into the window, being a scroll over an element already inside it, such as a fixed header, with `dy` covering the distance, or a click on a link to it, and film once more. Skip as `target-offscreen` on a second refusal |
|
|
80
|
+
| `over-cap` | Drop `beat` and each beat after it, then film once more. Skip as `over-cap` if `beat` is first or it refuses again |
|
|
81
|
+
| Any other value | Skip under that `reason`, such as `target-missing` with its `beat` |
|
|
82
82
|
|
|
83
83
|
## Check each still
|
|
84
84
|
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Adopted and declined
|
|
3
|
+
description: Which designs this skill considered and dropped, and what would revive each, so a later session extends the position instead of re-deriving it
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Adopted and declined
|
|
7
|
+
|
|
8
|
+
The skill came out of a groundwork track that measured two shipped fixes against the questions a critique would have asked. This file records the options the track dropped. A later session extending the skill adds to it rather than re-arguing an item settled here.
|
|
9
|
+
|
|
10
|
+
A bullet's source is linked in its row of the toolkit's per-bullet ledger at `governance/ledger/skills/proposal-critique/`, outside the plugin, and this file links no source a row cites.
|
|
11
|
+
|
|
12
|
+
## Declined
|
|
13
|
+
|
|
14
|
+
Each item names the design that stays out, the reason, and what would bring it back.
|
|
15
|
+
|
|
16
|
+
### A default call from every planning skill
|
|
17
|
+
|
|
18
|
+
An authoring session questioning its own plan is the weak form Kahneman, Lovallo, and Sibony warn against when they ask for reviewers independent of the team that made the recommendation. The planning skills name the critique in one line for a one-way change or a fix with an unverified cause, and never call it.
|
|
19
|
+
|
|
20
|
+
Revisit when a replay finds a case where the on-demand form would not have been invoked in time.
|
|
21
|
+
|
|
22
|
+
### Folding it into `decision-escalate`
|
|
23
|
+
|
|
24
|
+
That skill batches preference questions. Most critique questions are answered from evidence, and only the remainder goes to the operator.
|
|
25
|
+
|
|
26
|
+
Revisit when critique passes show every question turning out to be preference.
|
|
27
|
+
|
|
28
|
+
### Running it as a forked subagent
|
|
29
|
+
|
|
30
|
+
A fork reads cold, which is the independence Kahneman, Lovallo, and Sibony ask for. It also loses the conversation, and a background fork loses the tools the operator questions need.
|
|
31
|
+
|
|
32
|
+
Revisit when a critique keeps agreeing with the session that wrote the proposal.
|
|
33
|
+
|
|
34
|
+
### A written critique section in the plan
|
|
35
|
+
|
|
36
|
+
A plan already carries `## Risks` and `## Questions`, and its validator owns the sections. The verdict goes to chat, and the operator decides what moves into a record.
|
|
37
|
+
|
|
38
|
+
Revisit when critiques are lost to compaction before anyone acts on them.
|
|
39
|
+
|
|
40
|
+
## Revisit the whole design when
|
|
41
|
+
|
|
42
|
+
- Replaying the chain that shipped two fixes and two or three past plans finds the on-demand form would not have been invoked in time
|
|
43
|
+
- One body cannot hold questions that differ so much per record, whether plan, fix, or triage
|
|
44
|
+
- A replayed case shows a cheap, reversible change still doing real harm
|
|
@@ -70,13 +70,14 @@ canon take <url> .canon/video/<slug>/plan.json --out .canon/video/<slug>/takes -
|
|
|
70
70
|
|
|
71
71
|
Branch on the record's `reason`, never on the exit code, which a shell function wrapping `canon` can flatten to zero.
|
|
72
72
|
|
|
73
|
-
| Record | Route
|
|
74
|
-
| -------------------------------- |
|
|
75
|
-
| No `reason` | Continue to the stills
|
|
76
|
-
| `target-missing` with `fallback` | Check the access before the selector, per the note below the table
|
|
77
|
-
| `target-missing` | Open the record's `still`, find the target the record's `beat` names on the page, fix the plan, and film once more
|
|
78
|
-
| `
|
|
79
|
-
|
|
|
73
|
+
| Record | Route |
|
|
74
|
+
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
75
|
+
| No `reason` | Continue to the stills |
|
|
76
|
+
| `target-missing` with `fallback` | Check the access before the selector, per the note below the table |
|
|
77
|
+
| `target-missing` | Open the record's `still`, find the target the record's `beat` names on the page, fix the plan, and film once more |
|
|
78
|
+
| `target-offscreen` | Add a setup step that brings the target into the window, being a scroll over an element already inside it, such as a fixed header, with `dy` covering the distance, or a click on a link to it, and film once more |
|
|
79
|
+
| `server-unreachable` | Restart the server from Step 1 and film once more |
|
|
80
|
+
| Any other `reason` | Report the `reason` and `message` to the operator and stop, since each is a setup step |
|
|
80
81
|
|
|
81
82
|
A refusal carrying `fallback` means the screencast opened the address a second time, so a single-use link was spent before the step ran. Read `${CLAUDE_SKILL_DIR}/references/config.md` for the access the take opens with, and fix that before touching the target.
|
|
82
83
|
|
package/docs/agents/audits.md
CHANGED
|
@@ -63,30 +63,30 @@ An audit that finds no corpus reports `absent`, which moves neither the verdict
|
|
|
63
63
|
|
|
64
64
|
`canon audits list` reports the first 18 rows. The rows marked `outside` run by hand or from a skill and sit beside the aggregate.
|
|
65
65
|
|
|
66
|
-
| Id | Verb | Measures
|
|
67
|
-
| -------------------- | ----------------------------------- |
|
|
68
|
-
| `context` | `canon context audit` | Context folder structure, citations, and the requirements record's word cap
|
|
69
|
-
| `markdown` | `canon markdown audit` | Bans, dead links, density, and reference shape
|
|
70
|
-
| `skills` | `canon claude skills audit` | Both skill corpora against the skill standards
|
|
71
|
-
| `skills-reach` | `canon claude skills reach` | Skill bodies citing a path no target receives
|
|
72
|
-
| `skills-rank` | `canon claude skills rank` | Whether skill descriptions separate against a routing case corpus
|
|
73
|
-
| `tasks` | `canon tasks validate` | Board rows nothing resolves
|
|
74
|
-
| `records-plans` | `canon records validate plans` | Plan records against their shape
|
|
75
|
-
| `records-groundwork` | `canon records validate groundwork` | Groundwork folders against their shape
|
|
76
|
-
| `records-intake` | `canon records validate intake` | Intake folders against their shape
|
|
77
|
-
| `comments` | `canon comments scan` | Comment density by language and kind, and `TODO`, `FIXME`, `HACK`, `XXX`
|
|
78
|
-
| `test-order` | `canon gov test-order` | Implementations that reached branch history before their test
|
|
79
|
-
| `secrets` | `canon secrets scan` | Credential-shaped values in the shipped tree
|
|
80
|
-
| `labels` | `canon labels audit` | Changed paths no row of the label map reaches
|
|
81
|
-
| `restated` | `canon gov restated` | Instructions `CLAUDE.md` states that a second surface states too
|
|
82
|
-
| `rules` | `canon gov audit` | Rules past a shape limit
|
|
83
|
-
| `counts` | `canon gov counts` | Sentences stating a closed catalog's size that disagree with the tree
|
|
84
|
-
| `census` | `canon census` | File count, extension breakdown, and line total
|
|
85
|
-
| `deps` | `canon deps audit` | Advisories against the resolved dependencies
|
|
86
|
-
| `outside` | `canon gov citations` | Rule citations and internal frontmatter globs reaching nothing
|
|
87
|
-
| `outside` | `canon gov superseded <old> <new>` | Where the tree still asserts a value a changed convention no longer produces
|
|
88
|
-
| `outside` | `canon claude skills drift <ref>` | Shipped skill bodies rewritten since a ref
|
|
89
|
-
| `outside` | `canon claude routing` | Per `CLAUDE.md` section, how many bullets name a path and whether a rule covers it
|
|
66
|
+
| Id | Verb | Measures | Finding |
|
|
67
|
+
| -------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------- | -------------- |
|
|
68
|
+
| `context` | `canon context audit` | Context folder structure, citations, and the requirements record's word cap | Gates |
|
|
69
|
+
| `markdown` | `canon markdown audit` | Bans, dead links, density, and reference shape | Gates |
|
|
70
|
+
| `skills` | `canon claude skills audit` | Both skill corpora against the skill standards, and any ledger under `canon/ledger/skills/` | Gates |
|
|
71
|
+
| `skills-reach` | `canon claude skills reach` | Skill bodies citing a path no target receives | Reports |
|
|
72
|
+
| `skills-rank` | `canon claude skills rank` | Whether skill descriptions separate against a routing case corpus | Reports |
|
|
73
|
+
| `tasks` | `canon tasks validate` | Board rows nothing resolves | Reports |
|
|
74
|
+
| `records-plans` | `canon records validate plans` | Plan records against their shape | Reports |
|
|
75
|
+
| `records-groundwork` | `canon records validate groundwork` | Groundwork folders against their shape | Reports |
|
|
76
|
+
| `records-intake` | `canon records validate intake` | Intake folders against their shape | Reports |
|
|
77
|
+
| `comments` | `canon comments scan` | Comment density by language and kind, and `TODO`, `FIXME`, `HACK`, `XXX` | Reports |
|
|
78
|
+
| `test-order` | `canon gov test-order` | Implementations that reached branch history before their test | Reports |
|
|
79
|
+
| `secrets` | `canon secrets scan` | Credential-shaped values in the shipped tree | Gates |
|
|
80
|
+
| `labels` | `canon labels audit` | Changed paths no row of the label map reaches | Reports |
|
|
81
|
+
| `restated` | `canon gov restated` | Instructions `CLAUDE.md` states that a second surface states too | Reports |
|
|
82
|
+
| `rules` | `canon gov audit` | Rules past a shape limit or out of step with a ledger, and here rules short of one | Reports |
|
|
83
|
+
| `counts` | `canon gov counts` | Sentences stating a closed catalog's size that disagree with the tree | Reports |
|
|
84
|
+
| `census` | `canon census` | File count, extension breakdown, and line total | Reports |
|
|
85
|
+
| `deps` | `canon deps audit` | Advisories against the resolved dependencies | Reports |
|
|
86
|
+
| `outside` | `canon gov citations` | Rule citations and internal frontmatter globs reaching nothing | Gates in check |
|
|
87
|
+
| `outside` | `canon gov superseded <old> <new>` | Where the tree still asserts a value a changed convention no longer produces | Reports |
|
|
88
|
+
| `outside` | `canon claude skills drift <ref>` | Shipped skill bodies rewritten since a ref | Reports |
|
|
89
|
+
| `outside` | `canon claude routing` | Per `CLAUDE.md` section, how many bullets name a path and whether a rule covers it | Reports |
|
|
90
90
|
|
|
91
91
|
A deliberate secret takes a `canon-allow-secret: <reason>` marker. A deliberate citation takes `canon-allow-citation: <reason>` and a deliberate superseded value takes `canon-allow-superseded: <reason>`, each on its own line or the one above. Run any verb with `--help` for its flags and refusal reasons.
|
|
92
92
|
|
package/docs/agents/migrate.md
CHANGED
|
@@ -21,7 +21,7 @@ kind: reference
|
|
|
21
21
|
| `record-layout` | Review receipts, memory archives, feedback, renders, picks, evidence, and the design board into their current folders |
|
|
22
22
|
| `session-defaults` | The seeded session defaults into an existing `.claude/settings.json`, listed below the table |
|
|
23
23
|
|
|
24
|
-
`record-tree` follows citations in `diagrams`, `memory`, `plans`, `proposals`, `review`, `tasks`, and `teach`. `session-defaults` denies `Agent`, `Task`, and both merge commands,
|
|
24
|
+
`record-tree` follows citations in `diagrams`, `memory`, `plans`, `proposals`, `review`, `tasks`, and `teach`. `session-defaults` denies `Agent`, `Task`, and both merge commands, turns auto memory off, and wires an installed `plan-guard.sh` into the PreToolUse hooks. The bullets below add detail per verb.
|
|
25
25
|
|
|
26
26
|
- `skill-names` takes no `--scope`, and skips the changelog, eval transcripts, replay fixtures, and the pointer each renamed `draft-` skill leaves at its old name
|
|
27
27
|
- `surface-roots` moves `context/`, `wireframes/`, `ARCHITECTURE.md`, `REQUIREMENTS.md`, `DESIGN.md`, and the stamp folder, and leaves `rules/`, `skills/`, `hooks/`, and `settings.json` in place
|
|
@@ -56,15 +56,15 @@ Three markers keep an old spelling on a line. Each also covers the nearest non-b
|
|
|
56
56
|
|
|
57
57
|
## Refusals
|
|
58
58
|
|
|
59
|
-
| Verb | Refuses when
|
|
60
|
-
| ------------------ |
|
|
61
|
-
| `records` | The project does not ignore `.canon/`. Run `canon tooling sync --write` first.
|
|
62
|
-
| `records` | A record folder already exists at the destination
|
|
63
|
-
| `surface-roots` | Any destination already exists under `canon/`, refusing the whole plan
|
|
64
|
-
| `scratch-evidence` | A destination is occupied
|
|
65
|
-
| `record-layout` | A destination already exists, each move refused on its own
|
|
66
|
-
| `rule-layout` | A destination holds different bytes. Neither file is touched and other moves apply.
|
|
67
|
-
| `session-defaults` | The settings file is not valid JSON, or `permissions` or
|
|
59
|
+
| Verb | Refuses when |
|
|
60
|
+
| ------------------ | --------------------------------------------------------------------------------------------------------- |
|
|
61
|
+
| `records` | The project does not ignore `.canon/`. Run `canon tooling sync --write` first. |
|
|
62
|
+
| `records` | A record folder already exists at the destination |
|
|
63
|
+
| `surface-roots` | Any destination already exists under `canon/`, refusing the whole plan |
|
|
64
|
+
| `scratch-evidence` | A destination is occupied |
|
|
65
|
+
| `record-layout` | A destination already exists, each move refused on its own |
|
|
66
|
+
| `rule-layout` | A destination holds different bytes. Neither file is touched and other moves apply. |
|
|
67
|
+
| `session-defaults` | The settings file is not valid JSON, or `permissions`, `deny`, `hooks`, or `PreToolUse` has another shape |
|
|
68
68
|
|
|
69
69
|
A `rule-layout` destination holding the same bytes marks a duplicate, which `--write` deletes.
|
|
70
70
|
|
|
@@ -108,6 +108,8 @@ It moves each surface with `git mv` and repoints every tracked citation. No othe
|
|
|
108
108
|
|
|
109
109
|
The seeded `.claude/settings.json` now denies the `Agent` and `Task` tools and both merge commands, and sets `autoMemoryEnabled` to `false`. `canon claude sync` never rewrites a settings file you hold, so it warns while the file holds no default or lacks a merge entry. Removing `Agent` and `Task` later draws no warning.
|
|
110
110
|
|
|
111
|
+
The plan guard takes two steps. `canon claude init` adds the absent `.claude/hooks/plan-guard.sh`, and the verb below then wires it into `hooks.PreToolUse` for `Bash` and `Write|Edit`. It skips a tool whose matcher already runs the guard, and wires nothing while the script is absent.
|
|
112
|
+
|
|
111
113
|
```bash
|
|
112
114
|
canon migrate session-defaults --json
|
|
113
115
|
canon migrate session-defaults --write --json
|
package/governance/detect.toml
CHANGED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Rule rule ledger
|
|
3
|
+
description: Decisions behind governance/rules/claude/rule.md, one row per bullet
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Rule rule ledger
|
|
7
|
+
|
|
8
|
+
The rule is a core rule, so every row is a house choice rather than a citation. Each names what goes wrong without its bullet and the option the bullet beat, with the date it was decided.
|
|
9
|
+
|
|
10
|
+
## Rule files
|
|
11
|
+
|
|
12
|
+
| Bullet | Source | Read |
|
|
13
|
+
| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- |
|
|
14
|
+
| Load create-rule before an edit | House choice: a session hand-editing rules without the skill shipped defects `canon gov audit` passed (issue 2842). It beat relying on the audit alone | 2026-10-29 |
|
|
15
|
+
| Report an unresolved skill | House choice: a project that installed governance without the plugin has no `create-rule`, and a silent skip hides that. It beat omitting the line | 2026-10-29 |
|
|
16
|
+
| Audit each touched rule | House choice: the audit catches the shape and ledger defects the skill's checklist names, so running it before the commit closes the loop. It beat leaving the audit to the pre-push gate alone | 2026-10-29 |
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Route edits to a rule file to the create-rule skill for its shape and checklist
|
|
3
|
+
paths:
|
|
4
|
+
- '.claude/rules/project/**/*.md'
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Rule standards
|
|
8
|
+
|
|
9
|
+
## Rule files
|
|
10
|
+
|
|
11
|
+
- Load the `canon:create-rule` skill before a hand edit to a rule, since it carries the frontmatter, the shape limits, and the bullet checklist.
|
|
12
|
+
- Report it rather than proceeding silently when `canon:create-rule` does not resolve, as in a project that installed governance without the plugin.
|
|
13
|
+
- Run `canon gov audit` over each rule the edit touched before committing it.
|
package/package.json
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# Commits the
|
|
2
|
+
# Commits the recaptured home baseline onto the refresh branch and opens or
|
|
3
3
|
# updates its pull request, called by refresh-capture-frames.yml after the
|
|
4
|
-
# capture
|
|
4
|
+
# capture step. The step holds GH_TOKEN and HUSKY, since the token scope is
|
|
5
5
|
# read off the workflow.
|
|
6
6
|
#
|
|
7
7
|
# Hooks stay off, since pre-push runs a check whose format step needs shfmt,
|
|
8
8
|
# which the runner lacks. verify.yml checks the pull request.
|
|
9
9
|
#
|
|
10
|
-
# The
|
|
10
|
+
# The baseline commits where they rendered, then only the files that commit wrote
|
|
11
11
|
# or dropped carry onto main fetched now, so a merge landing mid-run neither
|
|
12
|
-
# leaves the branch on an older base nor loses a frame it moved. The pull
|
|
12
|
+
# leaves the branch on an older base nor loses a baseline frame it moved. The pull
|
|
13
13
|
# request resolves by number, since every merged refresh leaves a closed one on
|
|
14
14
|
# the same head.
|
|
15
15
|
set -e
|
|
16
16
|
|
|
17
17
|
branch=chore/refresh-capture-frames
|
|
18
|
-
title="chore(web): refresh
|
|
18
|
+
title="chore(web): refresh home baseline"
|
|
19
19
|
|
|
20
|
-
if [ -z "$(git status --porcelain assets/
|
|
21
|
-
echo "
|
|
20
|
+
if [ -z "$(git status --porcelain assets/evidence)" ]; then
|
|
21
|
+
echo "The home baseline is current, nothing to refresh."
|
|
22
22
|
exit 0
|
|
23
23
|
fi
|
|
24
24
|
git config user.name "github-actions[bot]"
|
|
25
25
|
git config user.email "41898282+github-actions[bot]@users.noreply.github.com" # canon-allow-reference: the bot account id, not a commit
|
|
26
|
-
git add assets/
|
|
26
|
+
git add assets/evidence
|
|
27
27
|
git commit -m "$title"
|
|
28
28
|
rendered=$(git rev-parse HEAD)
|
|
29
29
|
written=$(git diff --name-only --no-renames --diff-filter=d HEAD~1 HEAD)
|
|
@@ -36,8 +36,8 @@ fi
|
|
|
36
36
|
if [ -n "$dropped" ]; then
|
|
37
37
|
printf '%s\n' "$dropped" | xargs -d '\n' git rm -q --ignore-unmatch --
|
|
38
38
|
fi
|
|
39
|
-
if git diff --cached --quiet -- assets/
|
|
40
|
-
echo "Main already carries
|
|
39
|
+
if git diff --cached --quiet -- assets/evidence; then
|
|
40
|
+
echo "Main already carries this baseline, nothing to refresh."
|
|
41
41
|
exit 0
|
|
42
42
|
fi
|
|
43
43
|
git commit -m "$title"
|
|
@@ -50,11 +50,10 @@ if [ -z "$number" ]; then
|
|
|
50
50
|
--title "$title" \
|
|
51
51
|
--body "## Summary
|
|
52
52
|
|
|
53
|
-
- Refreshes the
|
|
53
|
+
- Refreshes the home page baseline from main so canon pr evidence compares against a current render.
|
|
54
54
|
|
|
55
55
|
## Key changes
|
|
56
56
|
|
|
57
|
-
- Regenerates the count and token bearing frames, their images, and their stamps from main.
|
|
58
57
|
- Recaptures the home page baseline under assets/evidence/home.
|
|
59
58
|
- Merges itself by squash once its required check passes."
|
|
60
59
|
number=$(gh pr list --head "$branch" --state open --json number --jq '.[0].number')
|
package/src/audits/catalog.ts
CHANGED
|
@@ -379,7 +379,8 @@ function restatedCounts(record: unknown): Record<string, number> | undefined {
|
|
|
379
379
|
* a short one are fixed by different passes.
|
|
380
380
|
*/
|
|
381
381
|
function ruleCounts(record: unknown): Record<string, number> | undefined {
|
|
382
|
-
const
|
|
382
|
+
const root = asObject(record)
|
|
383
|
+
const rules = root?.rules
|
|
383
384
|
if (!Array.isArray(rules)) return undefined
|
|
384
385
|
|
|
385
386
|
let shapeRules = 0
|
|
@@ -397,7 +398,11 @@ function ruleCounts(record: unknown): Record<string, number> | undefined {
|
|
|
397
398
|
if ((lengthOf(ledger.mismatches) ?? 0) > 0) ledgerMismatches += 1
|
|
398
399
|
}
|
|
399
400
|
|
|
400
|
-
|
|
401
|
+
// A target ledger with no rule sits beside `rules` rather than in it. A
|
|
402
|
+
// binary predating it carries no `orphans` key, which reads as none.
|
|
403
|
+
const orphanLedgers = lengthOf(root?.orphans) ?? 0
|
|
404
|
+
|
|
405
|
+
return { shapeRules, missingLedgers, ledgerMismatches, orphanLedgers }
|
|
401
406
|
}
|
|
402
407
|
|
|
403
408
|
function findingsOnly(record: unknown): Record<string, number> | undefined {
|
package/src/capture/stamp.ts
CHANGED
|
@@ -6,13 +6,11 @@ const STAMP_EXTENSION = '.stamp'
|
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Provenance for one rendered PNG: the markup it came from and a digest over
|
|
9
|
-
* each side of the pair.
|
|
10
|
-
*
|
|
11
|
-
* colon, and the space after it are a contract between the writer and the gate
|
|
12
|
-
* rather than formatting.
|
|
9
|
+
* each side of the pair. Nothing reads the digests back today, so no check
|
|
10
|
+
* fails when either side moves.
|
|
13
11
|
*
|
|
14
12
|
* Both sides are recorded because either can move alone. The markup digest
|
|
15
|
-
*
|
|
13
|
+
* would show an edit committed with no capture, and the image digest a PNG
|
|
16
14
|
* replaced under markup that never changed.
|
|
17
15
|
*/
|
|
18
16
|
export interface CaptureStamp {
|
|
@@ -82,6 +82,7 @@ export const LEDGER_SKILLS: readonly string[] = [
|
|
|
82
82
|
join(SHIPPED_CORPUS, 'write-human'),
|
|
83
83
|
join(SHIPPED_CORPUS, 'test-first'),
|
|
84
84
|
join(SHIPPED_CORPUS, 'ui-craft'),
|
|
85
|
+
join(SHIPPED_CORPUS, 'proposal-critique'),
|
|
85
86
|
]
|
|
86
87
|
|
|
87
88
|
function references(...names: string[]): string[] {
|
|
@@ -125,6 +126,7 @@ export const BULLET_LEDGER_SKILLS: Readonly<Record<string, BulletLedgerScope>> =
|
|
|
125
126
|
[join(SHIPPED_CORPUS, 'write-human')]: { exempt: [], body: true },
|
|
126
127
|
[join(SHIPPED_CORPUS, 'systematic-debugging')]: { exempt: [], body: true },
|
|
127
128
|
[join(SHIPPED_CORPUS, 'build-in-slices')]: { exempt: [], body: true },
|
|
129
|
+
[join(SHIPPED_CORPUS, 'proposal-critique')]: { exempt: [], body: true },
|
|
128
130
|
}
|
|
129
131
|
|
|
130
132
|
/** Where a per-bullet ledger lives, as `<skill>/<file>.md` below it. */
|
|
@@ -139,6 +141,15 @@ export function skillLedgerPath(folder: string, file: string): string {
|
|
|
139
141
|
return join(SKILL_LEDGERS, folder, inside.startsWith('..') ? file : inside)
|
|
140
142
|
}
|
|
141
143
|
|
|
144
|
+
/**
|
|
145
|
+
* Where a target keeps the ledgers behind its own skills, as `<skill>/<file>.md`
|
|
146
|
+
* below it. A tree holding `governance/rules/` is the toolkit, which keeps its
|
|
147
|
+
* ledgers at `SKILL_LEDGERS`, so this folder is never read there.
|
|
148
|
+
*/
|
|
149
|
+
export const PROJECT_SKILL_LEDGERS = join('canon', 'ledger', 'skills')
|
|
150
|
+
|
|
151
|
+
const TOOLKIT_MARKER = join('governance', 'rules')
|
|
152
|
+
|
|
142
153
|
/** The closing H2s a practice skill carries, matched exactly outside fences. */
|
|
143
154
|
export const PRACTICE_SECTIONS: readonly string[] = [
|
|
144
155
|
'Excuses and rebuttals',
|
|
@@ -312,6 +323,9 @@ export async function auditSkills(root: string): Promise<SkillsAudit> {
|
|
|
312
323
|
present.map((corpus) => readCorpus(root, corpus.rel, corpus.dir)),
|
|
313
324
|
)
|
|
314
325
|
const sources = perCorpus.flat()
|
|
326
|
+
const projectLedgers = existsSync(join(root, TOOLKIT_MARKER))
|
|
327
|
+
? []
|
|
328
|
+
: await readProjectLedgers(root, sources)
|
|
315
329
|
|
|
316
330
|
return {
|
|
317
331
|
corpora: present.map((corpus, index) => ({
|
|
@@ -344,6 +358,7 @@ export async function auditSkills(root: string): Promise<SkillsAudit> {
|
|
|
344
358
|
sources,
|
|
345
359
|
),
|
|
346
360
|
...sources.flatMap(practiceFindings),
|
|
361
|
+
...projectLedgers,
|
|
347
362
|
],
|
|
348
363
|
referenceContents: sources.flatMap((source) =>
|
|
349
364
|
source.bodies.flatMap(contentsFindings),
|
|
@@ -483,6 +498,56 @@ async function readReferenceLedgers(
|
|
|
483
498
|
)
|
|
484
499
|
}
|
|
485
500
|
|
|
501
|
+
/**
|
|
502
|
+
* Pairs each file a target wrote under `PROJECT_SKILL_LEDGERS` with the skill
|
|
503
|
+
* file of that name, `SKILL.md` with the body and any other with the reference
|
|
504
|
+
* at `references/<file>`. Nothing is owed, since a project has no list to owe a
|
|
505
|
+
* ledger against, so only a ledger that exists is read, and one naming a skill
|
|
506
|
+
* or a file that is gone is reported rather than skipped.
|
|
507
|
+
*/
|
|
508
|
+
async function readProjectLedgers(
|
|
509
|
+
root: string,
|
|
510
|
+
sources: readonly SkillSource[],
|
|
511
|
+
): Promise<SkillFinding[]> {
|
|
512
|
+
const dir = join(root, PROJECT_SKILL_LEDGERS)
|
|
513
|
+
if (!existsSync(dir)) return []
|
|
514
|
+
const paths = [
|
|
515
|
+
...new Bun.Glob('**/*.md').scanSync({ cwd: dir, onlyFiles: true }),
|
|
516
|
+
].sort()
|
|
517
|
+
|
|
518
|
+
const perLedger = await Promise.all(
|
|
519
|
+
paths.map(async (path): Promise<SkillFinding[]> => {
|
|
520
|
+
const ledgerRel = join(PROJECT_SKILL_LEDGERS, path)
|
|
521
|
+
const [folder, ...rest] = path.split('/')
|
|
522
|
+
const owners = sources.filter((source) => source.folder === folder)
|
|
523
|
+
if (rest.length === 0 || owners.length === 0) {
|
|
524
|
+
return [{ rel: ledgerRel, detail: `missing skill: ${folder}` }]
|
|
525
|
+
}
|
|
526
|
+
const file = rest.join('/')
|
|
527
|
+
const target = file === 'SKILL.md' ? file : join('references', file)
|
|
528
|
+
const rows = countLedgerRows(await Bun.file(join(dir, path)).text())
|
|
529
|
+
|
|
530
|
+
return owners.flatMap((owner) => {
|
|
531
|
+
const rel = join(owner.rel, target)
|
|
532
|
+
const body = owner.bodies.find((entry) => entry.rel === rel)
|
|
533
|
+
if (body === undefined) {
|
|
534
|
+
return [{ rel: ledgerRel, detail: `missing file: ${rel}` }]
|
|
535
|
+
}
|
|
536
|
+
return referenceLedgerFindings({
|
|
537
|
+
rel,
|
|
538
|
+
ledgerRel,
|
|
539
|
+
mismatches: compareLedger(
|
|
540
|
+
ledgerGroups(body.text, target === 'SKILL.md'),
|
|
541
|
+
rows,
|
|
542
|
+
),
|
|
543
|
+
sources: [],
|
|
544
|
+
})
|
|
545
|
+
})
|
|
546
|
+
}),
|
|
547
|
+
)
|
|
548
|
+
return perLedger.flat()
|
|
549
|
+
}
|
|
550
|
+
|
|
486
551
|
/**
|
|
487
552
|
* Directives per H2, `null` for any above the first. A contents list repeats
|
|
488
553
|
* the headings rather than stating a rule, so `## Contents` takes no rows. A
|