@erclx/canon 5.39.0 → 5.40.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 +5 -5
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/canvas-board/SKILL.md +1 -1
- package/claude/skills/draft-slides/SKILL.md +2 -3
- package/claude/skills/draft-slides/references/deck.md +183 -0
- package/claude/skills/git-pr/references/evidence.md +2 -2
- package/claude/skills/git-pr/references/final-command.md +1 -1
- package/claude/skills/git-pr/references/labels.md +1 -1
- package/claude/skills/git-pr/references/publish.md +1 -1
- package/claude/skills/review-ui/SKILL.md +1 -0
- package/claude/skills/role-orchestrator/references/orchestrator-handback.md +1 -1
- package/claude/skills/task-board/references/board.md +1 -1
- package/claude/skills/task-board/references/tasks.md +1 -1
- package/claude/skills/ux-measure/SKILL.md +1 -1
- package/docs/agents/audits.md +61 -60
- package/docs/agents/context-audit.md +40 -58
- package/docs/agents/index.md +5 -51
- package/docs/agents/install-and-sync.md +1 -0
- package/docs/agents/markdown-audit.md +53 -99
- package/docs/agents/records.md +37 -79
- package/docs/agents/sessions-transfer.md +26 -70
- package/docs/target/index.md +0 -1
- package/docs/target/migrations.md +57 -66
- package/docs/target/projects.md +36 -78
- package/docs/target/scenarios.md +9 -19
- package/docs/target/sync.md +27 -68
- package/docs/workflow/ai-workflow.md +22 -68
- package/docs/workflow/operating-model.md +32 -186
- package/docs/workflow/parallel-features.md +29 -66
- package/docs/workflow/skill-map-on-request.md +22 -24
- package/docs/workflow/skill-map.md +72 -76
- package/docs/workflow/visual-design-workflow.md +25 -118
- package/docs/workflow/zshrc-aliases.md +18 -37
- package/package.json +1 -1
- package/src/canvas/client/inspector/fields.css +4 -0
- package/src/canvas/index.html +6 -0
- package/src/claude/skills-reach.ts +2 -2
- package/src/cli/ui.ts +3 -1
- package/src/commands/canvas.ts +23 -0
- package/src/commands/capture.ts +14 -0
- package/src/commands/claude.ts +13 -0
- package/src/commands/demo.ts +22 -0
- package/src/commands/deps.ts +6 -0
- package/src/commands/driver.ts +28 -2
- package/src/commands/gov.ts +52 -18
- package/src/commands/intake.ts +17 -0
- package/src/commands/labels.ts +38 -5
- package/src/commands/pr.ts +160 -43
- package/src/commands/records.ts +8 -0
- package/src/commands/secrets.ts +6 -0
- package/src/commands/sessions.ts +8 -0
- package/src/commands/tasks.ts +52 -0
- package/src/commands/teach.ts +13 -0
- package/src/commands/upstream.ts +25 -1
- package/src/commands/worktrees.ts +5 -0
- package/src/context/audit.ts +22 -4
- package/src/context/folders.ts +1 -1
- package/src/context/shape-gated.ts +4 -2
- package/src/counts/scan.ts +1 -1
- package/src/design/components.ts +1 -1
- package/src/design/document.ts +1 -1
- package/src/design/tokens.ts +1 -1
- package/src/gate/boundaries.ts +4 -1
- package/src/gate/measures.ts +1 -1
- package/src/gate/stages.ts +7 -7
- package/src/git/env.ts +6 -0
- package/src/markdown/scope.ts +5 -17
- package/src/pr/lift.ts +153 -19
- package/src/pr/painted.ts +87 -0
- package/src/roots/record.ts +18 -1
- package/src/sandbox/census.ts +1 -1
- package/src/sandbox/headless/run.ts +3 -2
- package/src/sandbox/headless/watch.ts +3 -4
- package/src/slides/convert/master.ts +3 -0
- package/src/sync/format.ts +46 -0
- package/src/sync/stamp.ts +6 -0
- package/tooling/astro/reference.md +2 -2
- package/tooling/base/reference.md +1 -1
- package/tooling/go/reference.md +1 -1
- package/tooling/nextjs/reference.md +1 -1
- package/tooling/php/reference.md +1 -1
- package/tooling/python/reference.md +1 -1
- package/tooling/vite-react/reference.md +1 -1
- package/tooling/web/reference.md +1 -1
- package/docs/agents/canvas.md +0 -86
- package/docs/agents/capture.md +0 -65
- package/docs/agents/census.md +0 -23
- package/docs/agents/comments.md +0 -34
- package/docs/agents/context-audit-checks.md +0 -108
- package/docs/agents/context-classify.md +0 -101
- package/docs/agents/counts.md +0 -74
- package/docs/agents/demo.md +0 -140
- package/docs/agents/driver.md +0 -103
- package/docs/agents/gate.md +0 -90
- package/docs/agents/hooks.md +0 -78
- package/docs/agents/intake.md +0 -81
- package/docs/agents/key-changes.md +0 -116
- package/docs/agents/label-coverage.md +0 -86
- package/docs/agents/markdown-audit-density.md +0 -39
- package/docs/agents/markdown-audit-weight.md +0 -83
- package/docs/agents/pr-evidence-layout.md +0 -70
- package/docs/agents/pr-evidence.md +0 -273
- package/docs/agents/pr-frames.md +0 -131
- package/docs/agents/pr-lift.md +0 -132
- package/docs/agents/pr-local.md +0 -111
- package/docs/agents/pr-preview.md +0 -155
- package/docs/agents/pr-reads.md +0 -111
- package/docs/agents/pr-tick.md +0 -72
- package/docs/agents/records-push.md +0 -61
- package/docs/agents/records-stale.md +0 -40
- package/docs/agents/records-validate.md +0 -57
- package/docs/agents/restated.md +0 -83
- package/docs/agents/routing.md +0 -61
- package/docs/agents/rule-citations.md +0 -90
- package/docs/agents/sandbox.md +0 -126
- package/docs/agents/sessions.md +0 -128
- package/docs/agents/skills-audit.md +0 -120
- package/docs/agents/skills-reach.md +0 -64
- package/docs/agents/slides.md +0 -176
- package/docs/agents/state-scoped-risk.md +0 -105
- package/docs/agents/superseded.md +0 -85
- package/docs/agents/tasks-archive.md +0 -83
- package/docs/agents/tasks-plans.md +0 -152
- package/docs/agents/tasks-validate.md +0 -133
- package/docs/agents/tasks.md +0 -123
- package/docs/agents/teach-authoring.md +0 -118
- package/docs/agents/teach.md +0 -157
- package/docs/agents/test-order.md +0 -58
- package/docs/agents/upstream.md +0 -94
- package/docs/agents/worktrees.md +0 -118
- package/docs/target/standards-moved.md +0 -34
package/docs/agents/routing.md
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Routing report
|
|
3
|
-
description: Reading per CLAUDE.md section how many bullets name a path, what counts as naming one, when a rule counts as covering it, the two refusals, and why the verb reports rather than gates
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Routing report
|
|
7
|
-
|
|
8
|
-
`canon claude routing` reports, per `CLAUDE.md` section, how many top-level bullets name a path and how many of those a path-scoped rule already covers. It answers the firing axis of the tier test in `canon/context/context-model.md`, whether a fact applies every session or fires on one path, and which until this verb existed was a judgment nothing counted. The tier test names two further axes, conditional presence and updatability, that this verb does not check.
|
|
9
|
-
|
|
10
|
-
```bash
|
|
11
|
-
canon claude routing
|
|
12
|
-
canon claude routing --json
|
|
13
|
-
canon claude routing ../my-app
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
| Option | Behavior |
|
|
17
|
-
| -------- | ------------------------------------------------------------ |
|
|
18
|
-
| `[path]` | Repository root to read, defaulting to the current directory |
|
|
19
|
-
| `--json` | Add a machine-readable record on stdout, keeping the frame |
|
|
20
|
-
|
|
21
|
-
Under `--json` the record holds stdout alone and the frame still renders on stderr, refusals included, which is the split `output-shape.md` fixes for every mode.
|
|
22
|
-
|
|
23
|
-
The root is the argument rather than the toolkit, matching the reach and drift verbs, so a linked worktree reads its own branch and a target reads its own file.
|
|
24
|
-
|
|
25
|
-
## What it reads
|
|
26
|
-
|
|
27
|
-
Every H2 and H3 owning at least one top-level bullet. An H3 is reported under the H2 containing it, as `Behavior / Scope discipline`, so a section and its subsections are counted apart rather than summed.
|
|
28
|
-
|
|
29
|
-
Three things are read past. A nested bullet belongs to the one above it rather than to the section. A bullet inside a fenced block is example text rather than instruction. A heading carrying no bullet answers nothing the report asks, so it is dropped rather than listed at zero.
|
|
30
|
-
|
|
31
|
-
## What counts as naming a path
|
|
32
|
-
|
|
33
|
-
A bullet is path-scoped here when it names a path. That is evidence for the tier judgment rather than the judgment itself, and the gap runs both ways: a bullet naming a folder can still apply every session, and one firing on a path it never spells is invisible to the count. Read a section's number as a place to look rather than as a verdict on it.
|
|
34
|
-
|
|
35
|
-
A backticked token counts when it carries a separator or an alphabetic extension, which admits `src/cli.ts` and `tsconfig.json` while leaving a flag, a bare word, and a version string out.
|
|
36
|
-
|
|
37
|
-
A shape counts as the folder above its placeholder. `canon/context/<domain>.md` names `canon/context/` and nothing narrower, so dropping the token whole would report the section carrying it as naming no path at all. A placeholder opening the first segment has no openable prefix and is dropped.
|
|
38
|
-
|
|
39
|
-
## When a rule counts as covering it
|
|
40
|
-
|
|
41
|
-
A rule covers a named path only when its glob anchors to a location. A glob opening `**` reaches every folder in the tree, so it answers that a file type is governed and never that a named path is.
|
|
42
|
-
|
|
43
|
-
The corpus-wide markdown rules are what force that. Counting one reports every markdown path as covered, which collapses the column to a constant and tells a reader nothing about which folder somebody actually scoped a rule to.
|
|
44
|
-
|
|
45
|
-
A folder is probed with a handful of extensions rather than matched literally, because a glob narrowed by file type reaches under a folder without ever matching the folder's own name. Globs are read from each rule's frontmatter block alone, so a rule quoting a path in its body does not register a scope it never declared.
|
|
46
|
-
|
|
47
|
-
The paths a section names that no rule reaches are listed beside it, which is where a reader looks first when deciding what a new rule would cover.
|
|
48
|
-
|
|
49
|
-
## Refusals
|
|
50
|
-
|
|
51
|
-
Two, each naming what a reader does about it. `no-claude-md` is a tree with no always-loaded file. `no-rules` is a tree carrying no path-scoped rule under `.claude/rules/`, where every path would report as uncovered and the column would say nothing.
|
|
52
|
-
|
|
53
|
-
An always-on rule declaring no `paths` is skipped rather than refused. It applies at the same priority as the always-loaded file, so it covers no path in particular.
|
|
54
|
-
|
|
55
|
-
## Exit codes
|
|
56
|
-
|
|
57
|
-
Exit codes are `0` when the file was read and `1` for a refusal. No finding moves the exit code.
|
|
58
|
-
|
|
59
|
-
Nothing wires this into `bun run check` or into a hook. Whether a bullet belongs in a rule is a judgment, and gating a measure that counts a judgment forces an escape hatch for every deliberate case.
|
|
60
|
-
|
|
61
|
-
An exit code says nothing about a call made from a session, since a shell profile may wrap the binary in a function taking its status from a later command. Read the JSON record's `sections` array rather than the exit when a skill consumes this.
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Rule citations
|
|
3
|
-
description: Resolving every path a governance rule cites and every frontmatter glob the internal corpus declares, the one form a citation is written in, the shapes that look like citations and are not, the two classes where an absent path is correct, why the glob half reads one corpus, and why this one gates
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Rule citations
|
|
7
|
-
|
|
8
|
-
`canon gov citations` resolves every path a rule cites and every frontmatter glob the internal corpus declares, naming the ones reaching nothing. It answers a failure no other stage sees: a rule points a reader at a file, the file moves, and nothing reports it until a session opens the path and finds an absence.
|
|
9
|
-
|
|
10
|
-
A glob fails the same way and more quietly. A rule scoped at a directory that moved stops matching, so it never loads again, and a rule that never fires looks exactly like a rule nobody violated.
|
|
11
|
-
|
|
12
|
-
```bash
|
|
13
|
-
canon gov citations
|
|
14
|
-
canon gov citations --json
|
|
15
|
-
canon gov citations --root ../my-app
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
| Option | Behavior |
|
|
19
|
-
| --------------- | ---------------------------------------------------------- |
|
|
20
|
-
| `--root <path>` | Tree to read, defaulting to the current directory |
|
|
21
|
-
| `--json` | Add a machine-readable record on stdout, keeping the frame |
|
|
22
|
-
|
|
23
|
-
Under `--json` the record holds stdout alone and the frame still renders on stderr, refusals included, which is the split `output-shape.md` fixes for every mode.
|
|
24
|
-
|
|
25
|
-
## What the drift check cannot see
|
|
26
|
-
|
|
27
|
-
`bun run check` asserts that an authored rule and its consumed copy agree, and a dead citation passes that assertion because both copies carry it. The teach rule told its reader to open a `references/glossary.md` inside the teach skill that had never existed at that path. The rule shipped, survived a release, and fired during a real teach run before anyone noticed.
|
|
28
|
-
|
|
29
|
-
Two files agreeing is what a drift check measures. Whether either one is right about the tree is a different question, and this is the stage that asks it.
|
|
30
|
-
|
|
31
|
-
## The one form
|
|
32
|
-
|
|
33
|
-
A citation reaches a reader as a backticked path, reported under the form `path`, and it resolves against the root. A bare filename is never a citation, since with no folder around it nothing tells a rule from a convention such as `index.md`. A rule pointing at another rule writes its repository path.
|
|
34
|
-
|
|
35
|
-
A call to the retired standards verb is no form here. A rule holding one fails the shipped-references stage of `canon gate run`, which reads every shipped file for it.
|
|
36
|
-
|
|
37
|
-
A `path` is anchored on the whole backticked span rather than on a trailing pattern inside it. Cutting `tooling-reference.md` out of `.claude/skills/internal-tooling/references/tooling-reference.md` and resolving that against the root alone reports a file that exists as missing, which is a mistake made by hand while measuring this corpus before the stage was written.
|
|
38
|
-
|
|
39
|
-
## What is not a citation
|
|
40
|
-
|
|
41
|
-
A rule body is full of backticked spans carrying a slash, and almost none of them names a file in this tree. The stage declines four shapes outright.
|
|
42
|
-
|
|
43
|
-
- A placeholder or glob segment describes a shape rather than naming a file: `canon/context/<domain>.md`, `app/**/route.ts`, `${CLAUDE_SKILL_DIR}/../references/<name>.md`.
|
|
44
|
-
- A bare filename names a convention: `route.ts`, `manifest.toml`, `components.json`, `playwright.config.ts`.
|
|
45
|
-
- A span carrying no file extension is a folder or a module specifier: `src/pages/`, `next/font`, `try/except`, `react-hooks/set-state-in-effect`, `oven-sh/setup-bun@v2`, `@/lib/utils`.
|
|
46
|
-
- A fenced block displays a path rather than pointing at one.
|
|
47
|
-
|
|
48
|
-
The extension test is what carries most of the separation, and it costs the real paths that name a folder or a symlink, which this stage declines to check because nothing in the span says it is a file. Declining is the right direction for a gate, where a guess that goes wrong fails a push over prose.
|
|
49
|
-
|
|
50
|
-
## Where an absent path is correct
|
|
51
|
-
|
|
52
|
-
Two classes resolve to nothing and are right to. Both are reported by name rather than dropped, so a reader can see what the verdict declined to judge.
|
|
53
|
-
|
|
54
|
-
**Governed.** A rule spelling a path in its own frontmatter `paths:` is naming an artifact a target holds rather than a file here, so the file is correctly absent from this tree and correctly named in the rule. No shipped rule declares one today, and the stage's test fixtures carry the instance that keeps the class exercised.
|
|
55
|
-
|
|
56
|
-
Only an exact declaration exempts, never a glob match against one. A glob declares a shape, so a body path sitting inside it is still a citation and a stale one is still a defect. A rule scoped at `docs/**` citing a `docs/agents/renamed.md` that moved is exactly the class this stage exists to catch, and matching the glob would excuse it.
|
|
57
|
-
|
|
58
|
-
**Ignored.** A path git ignores is session scratch no clone is expected to hold. `governance/rules/canon/tasks.md` cites `.canon/tasks/index.md`, which is real in a live project and absent from a fresh clone and from every linked worktree. Resolving against the filesystem alone would make the verdict depend on which tree the stage ran in, so the unresolved paths go to one batched `git check-ignore` and an ignored one is excused. A read git cannot answer refuses rather than reporting those paths dead.
|
|
59
|
-
|
|
60
|
-
## Why the glob half reads one corpus
|
|
61
|
-
|
|
62
|
-
Bodies are read across both corpora and frontmatter globs across `internal/rules/` alone.
|
|
63
|
-
|
|
64
|
-
A rule under `governance/rules/` installs into a target, and its `paths:` entries name the shape that project holds rather than anything here. Measured over that corpus, 32 of its 72 globs match nothing in this tree and every one of them is correct. `src/pages/**` in the Astro rule cannot be told by pattern from a path this repository might hold, so a check cannot separate the two, and gating them would ship an exemption list the length of the corpus. Nothing reads the shipped globs, and the measurement is the reason rather than a gap left for someone to close.
|
|
65
|
-
|
|
66
|
-
`internal/rules/` ships nowhere. The tree it governs is the tree present, which makes the question answerable, and all 14 of its globs across 7 rules match at the commit this shipped on.
|
|
67
|
-
|
|
68
|
-
What that leaves unreached is a glob that matches real files and still reaches none of the work it was scoped at. `governance/rules/lib/e2e-reliability.md` scoped itself at `e2e/*.ts` and `e2e/**/*.ts` and now reads `**/e2e/**/*.ts`. No probe in this repository is written under `e2e/`, so the rule asking a session to watch a new guard fail never fired for the session writing guards. Resolution is mechanical and reach is a judgment about where the work happens, so only the first is here.
|
|
69
|
-
|
|
70
|
-
## The exemption marker
|
|
71
|
-
|
|
72
|
-
Everything the classifier can separate mechanically is separated there. For the residue, a line carries `canon-allow-citation: <reason>` on itself or the one directly above, which moves it into the report's `Exempt` section. Only a marker naming a reason counts, since a bare token is a line that meant to say something and did not. This is the `canon-allow-superseded` shape, and both read the same placement rule through one helper.
|
|
73
|
-
|
|
74
|
-
## The blind spots
|
|
75
|
-
|
|
76
|
-
A citation that resolves and points at the wrong file passes. Resolution is mechanical and correctness is a reading, and only the first is a gate's business.
|
|
77
|
-
|
|
78
|
-
A path written into running prose without backticks is not read at all. Matching one would report every sentence that happens to name a file, and the backticks are what separate a reference a reader follows from a name in a sentence.
|
|
79
|
-
|
|
80
|
-
A folder or a path carrying no extension is declined rather than guessed at, which is stated above as the cost of the test that carries the separation.
|
|
81
|
-
|
|
82
|
-
A glob under `governance/rules/` is not read, and a glob that resolves while reaching none of the work it was scoped at is a reading rather than a resolution. Both are stated above.
|
|
83
|
-
|
|
84
|
-
## Exit codes
|
|
85
|
-
|
|
86
|
-
Exit codes are `0` when every citation resolves or is excused and every glob read matches, `1` for a refusal, and `2` for at least one cited path reaching nothing or one glob matching nothing. It refuses a tree holding neither rule corpus, since a tree with no rules passes each of its zero rules, and a `git check-ignore` read that fails.
|
|
87
|
-
|
|
88
|
-
This gates, and `bun run check` runs it as the `Rule citations` stage. The sibling sweeps report rather than gate because a value appears for reasons unrelated to the convention, so their output is a reading. A path resolving to nothing carries no judgment: either the file is there or the citation is stale, and the two classes where absence is correct are separated before the verdict rather than left for a reader to settle. The corpus is clean at the commit this shipped on, so the gate starts green and stays that way until something breaks.
|
|
89
|
-
|
|
90
|
-
An exit code says nothing about a call made from a session, since a shell profile may wrap the binary in a function taking its status from a later command. Read the JSON record's `citations` array and filter on `status` rather than the exit when a skill consumes this.
|
package/docs/agents/sandbox.md
DELETED
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Sandbox
|
|
3
|
-
description: Scenario routing, the expectation scoring surface, and the coverage census over scenarios and skills
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Sandbox
|
|
7
|
-
|
|
8
|
-
`canon sandbox` provisions isolated project states, scores a provisioned one against a declared expectation, and reports which scenarios declare an expectation at all.
|
|
9
|
-
|
|
10
|
-
## Scenarios
|
|
11
|
-
|
|
12
|
-
Scenarios live under `sandbox/`, one folder per category and one file per command, each a TypeScript module. `sandbox/fixtures/` is the exception, holding file content that scenarios stage rather than scenarios of its own, so the picker and the coverage report both filter it out. That tree sits outside `files` in `package.json`, so an installed `canon` carries the command, reports it as toolkit-only on one line, and exits 1 rather than failing on the missing directory. Route non-interactively with `SANDBOX_SCENARIO`:
|
|
13
|
-
|
|
14
|
-
```bash
|
|
15
|
-
SANDBOX_SCENARIO=sync canon sandbox infra:tooling
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
Scenario categories: `infra:*` (domain flows), `git:*`, `scaffold:*`. `create` scenarios require interactive input and loop on empty input, so skip them in automated runs.
|
|
19
|
-
|
|
20
|
-
## Scenario expectations
|
|
21
|
-
|
|
22
|
-
`canon sandbox check <category>:<command> [arm]` scores a provisioned sandbox against the arm's `expect.toml`, printing a verdict on stderr and, with `--json`, the same verdict as a record on stdout.
|
|
23
|
-
|
|
24
|
-
```bash
|
|
25
|
-
canon sandbox check claude:context-fold drift --json
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
| Flag | Effect |
|
|
29
|
-
| ------------------------------ | ----------------------------------------------------------------------------------------------------------------- |
|
|
30
|
-
| `--envelope <file>` | Read `is_error`, `num_turns`, denials, and the reply text |
|
|
31
|
-
| `--writes <file>` | Newline-delimited paths the session wrote, for write scope |
|
|
32
|
-
| `--escapes <file>` | Newline-delimited paths written to a watched toolkit root, for escape scope |
|
|
33
|
-
| `--escapes-watched` | At least one watched root held a target this run |
|
|
34
|
-
| `--concurrent-sessions <file>` | Newline-delimited sessions live in the registry both before and after this run, a witness for an unbounded escape |
|
|
35
|
-
| `--json` | Emit the verdict record on stdout |
|
|
36
|
-
| `--strict` | Exit 1 on `unchecked` instead of 0 |
|
|
37
|
-
|
|
38
|
-
The verdict `state` is `pass`, `fail`, or `unchecked`. An arm with no `expect.toml` is `unchecked` and exits 0, so the harness stays usable while expectations roll out. A declaration that exists but asserts nothing is a failure, since an expectation file that asserts nothing passes every run.
|
|
39
|
-
|
|
40
|
-
Omitting `--writes`, `--escapes`, or `--envelope` does not silently drop the assertion kinds that need them. Write scope, escape scope, the turn ceiling, and the reply assertion report as unchecked and appear in the count, so the standalone command cannot claim more coverage than it had. Supplying `--escapes` without `--escapes-watched` reports the same way: a zero-escape result with no root confirmed watched is unmeasured rather than a pass. A verdict never reports `pass` with zero assertions.
|
|
41
|
-
|
|
42
|
-
An arm declaring `escape_scope` asserts a bound on the headless runner's own escape watch rather than on the sandbox tree. `write_scope` skips when the run wrote nothing, since a required output missing is itself a finding, but `escape_scope` passes on zero escapes outright, since a clean run producing none is the expected outcome for a destination nothing requires a skill to touch, provided `--escapes-watched` confirms a root held something to watch. Declaring `escape_scope = []` asserts that a correct run reaches none of the watched destinations at all. `canon/context/sandbox/isolation.md` names what the watch reaches and what it cannot.
|
|
43
|
-
|
|
44
|
-
An envelope that parses but carries no `result` field skips the reply assertion the same way an absent file does. An envelope carrying an empty `result` fails it, since a run that returned no text is a finding rather than a gap in the input.
|
|
45
|
-
|
|
46
|
-
Exit 0 means `pass` or `unchecked`. Exit 1 means `fail`, or a caller error: a malformed target, or a sandbox that was never provisioned. A missing sandbox reports as an error rather than a failed verdict, because failing every path assertion would read as a skill that did nothing. `--strict` moves `unchecked` to exit 1 for a caller that has finished arming its scenarios.
|
|
47
|
-
|
|
48
|
-
## Headless runs
|
|
49
|
-
|
|
50
|
-
`canon sandbox run` provisions a scenario, drives one `claude -p` session over it, and scores the result with `canon sandbox check`.
|
|
51
|
-
|
|
52
|
-
```bash
|
|
53
|
-
canon sandbox run <category>:<command> "<prompt>" [arm]
|
|
54
|
-
canon sandbox run claude:plan-feature "/canon:plan-feature add a widget" small
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
Every run bills a real session through the `claude` binary on `PATH`. The session runs with `--plugin-dir` pointed at this checkout's `claude/`, so it loads the branch's skills rather than an installed copy, and provisioning injects no skill into the tree for the same reason. Four variables override the flags it hands to `claude -p`:
|
|
58
|
-
|
|
59
|
-
| Variable | Default |
|
|
60
|
-
| ---------------------------------- | -------------------------------- |
|
|
61
|
-
| `CANON_SKILL_TEST_MODEL` | `sonnet` |
|
|
62
|
-
| `CANON_SKILL_TEST_TOOLS` | `Bash,Read,Glob,Grep,Edit,Write` |
|
|
63
|
-
| `CANON_SKILL_TEST_MAX_TURNS` | `30` |
|
|
64
|
-
| `CANON_SKILL_TEST_PERMISSION_MODE` | `bypassPermissions` |
|
|
65
|
-
|
|
66
|
-
The max-turns value is the only budget a run has. An arm's `max_turns` is asserted after the run and cannot raise it.
|
|
67
|
-
|
|
68
|
-
Stdout carries one JSON record: the session's envelope with `verdict`, `escapes`, and `sessions` merged in, described under the skills census below. Stderr carries the framed timeline, the provisioning output, the session's own stderr, the verdict report, and a warning for each escape, new session record, or reaped process. An envelope that does not parse still prints its verdict, under `is_error: true`.
|
|
69
|
-
|
|
70
|
-
Exit codes follow whichever step stopped the run. Provisioning's code ends a run before the session starts, and a session that exits non-zero ends it with that code after writing a dead-run record, `{is_error, exit_code, raw_output}`, to the same runs folder the merged record uses. Otherwise the verdict's code stands, except that an envelope that does not parse forces 1. An interrupt reaps the session's process group before the runner exits.
|
|
71
|
-
|
|
72
|
-
## Provisioning equivalence
|
|
73
|
-
|
|
74
|
-
`canon sandbox equivalence` proves a change moved nothing in what scenarios provision. It provisions every arm of the named targets on a base ref and on the working checkout, builds a manifest of each tree, and names what differs. It runs offline and never calls `canon sandbox run` or a `claude` binary, so no arm it runs bills a model.
|
|
75
|
-
|
|
76
|
-
```bash
|
|
77
|
-
canon sandbox equivalence claude --base origin/main --json
|
|
78
|
-
canon sandbox equivalence claude:plan-feature/full --base HEAD
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
A target is `<category>`, `<category>:<command>`, or `<category>:<command>/<arm>`, defaulting to every category.
|
|
82
|
-
|
|
83
|
-
| Flag | Effect |
|
|
84
|
-
| ------------------ | -------------------------------------------------------------------------------------------------- |
|
|
85
|
-
| `--base <ref>` | Ref the baseline is provisioned from, `main` by default |
|
|
86
|
-
| `--include-anchor` | Also run the arms that push to the shared GitHub anchor, one at a time, for an attended run |
|
|
87
|
-
| `--stub-remote` | Run the anchor arms against a local bare repository and a stub `gh`, writing nothing to the anchor |
|
|
88
|
-
| `--out <dir>` | Keep the provisioned trees, logs, and per-side manifests in this folder |
|
|
89
|
-
| `--no-masks` | Compare every path, which lists what each mask would hide |
|
|
90
|
-
| `--json` | Emit the record on stdout |
|
|
91
|
-
|
|
92
|
-
The baseline is a detached worktree with full history, removed on exit including after `SIGINT`. Each arm provisions on the base side, is read into a manifest, is deleted, then provisions on the head side at the same path with the run id pinned. A manifest holds every file's mode and hash, every symlink's target, the exit status, the narration, and for the outer repository and each nested one the commit subjects, the staged index, and the branch.
|
|
93
|
-
|
|
94
|
-
The record lists per arm `identical`, `differs` with the manifest keys that differ, `red-on-base` when both sides fail the same way, or `skipped-anchor`. It also carries `canonVersion`, `baseCommit`, `masksApplied`, `errors`, and `counts`. A declared arm adds a `check` entry from `canon sandbox check` on the head tree, whose `asserted` count shows its `expect.toml` resolved from `sandbox/fixtures/`.
|
|
95
|
-
|
|
96
|
-
Exit 0 means every compared arm is identical or red the same way on both sides. Exit 1 means any `differs`, a base and head exit that disagree, an armed arm that `canon sandbox coverage` lists and enumeration lacks, or a `.ts` scenario that fails to load.
|
|
97
|
-
|
|
98
|
-
## Scenario coverage
|
|
99
|
-
|
|
100
|
-
`canon sandbox coverage` reports which scenarios declare expectations and which only provision a state. It reads the fixture tree, so it needs no provisioned sandbox and runs nothing. Where that tree does not ship it exits 1 and prints no percentage, since a denominator nobody looked at is not a coverage result. A tree that is present and holds no scenarios is a real zero and still reports one.
|
|
101
|
-
|
|
102
|
-
```bash
|
|
103
|
-
canon sandbox coverage --json
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
| Flag | Effect |
|
|
107
|
-
| ---------- | ----------------------------------------------------------- |
|
|
108
|
-
| `--json` | Emit the coverage record on stdout |
|
|
109
|
-
| `--strict` | Exit 1 while any scenario declares no expectation |
|
|
110
|
-
| `--skills` | Add a per-skill asserted, should-be-asserted, exempt census |
|
|
111
|
-
|
|
112
|
-
The record carries every scenario with the arms that declare, plus `totalScenarios`, `armedScenarios`, and `armedArms`. Scenarios and arms count separately, since several arms can share one scenario and dividing one by the other overstates the rollout.
|
|
113
|
-
|
|
114
|
-
### The skills census
|
|
115
|
-
|
|
116
|
-
`--skills` answers what the scenario count cannot, which is whether anything can fail a given skill. It adds `skills`, `totalSkills`, `asserted`, `shouldBeAsserted`, `exempt`, `staleExemptions`, and `supersededExemptions` to the record, and keeps the scenario view rather than replacing it. The two denominators disagree on purpose: an armed scenario under `infra/` or `tooling/` exercises a CLI domain and pairs with no skill at all.
|
|
117
|
-
|
|
118
|
-
A skill pairs to a scenario by filename, `<category>-<command>` first and bare `<command>` second, so `claude/target-setup.ts` reaches the `target-setup` skill. `should-be-asserted` is the default rather than a queue to drain, and which of those skills earns an arm is a project decision the census does not make.
|
|
119
|
-
|
|
120
|
-
`exempt` means no arm should be written and holds only with a reason, declared in `sandbox/exempt.toml` and limited to a harness limit the checker cannot reach past or a skill that writes no artifact. An armed arm outranks an exemption. An exemption naming no shipped skill, or naming one an arm now asserts, exits 1 without `--strict`. Each armed arm reports as `<category>:<command>/<arm>`, so two same-named arms under different scenarios stay distinct.
|
|
121
|
-
|
|
122
|
-
`canon sandbox run` calls this after a headless run and merges the verdict into the envelope it prints. It also writes that merged record to `.canon/tmp/runs/sandbox/<target>-<arm>-<timestamp>.json` with a `writes` array appended, and logs the path on stderr. Both fields are what a later re-score needs, since `--envelope` and `--writes` read files the run deletes on exit.
|
|
123
|
-
|
|
124
|
-
Two more fields ride alongside the verdict rather than inside it. `escapes` lists what the run wrote under a watched toolkit root, which the verdict cannot assert over because those files sit outside the sandbox tree. `sessions` reports the nested-dispatch bound, carrying `watched` for whether the client's session registry was there to read, `new` for the records that appeared while the run was in flight, `concurrent` for the records present both before and after, and `reap` for what the run found in the session's process group afterwards. Neither field fails a run on its own.
|
|
125
|
-
|
|
126
|
-
`canon sandbox run` passes `concurrent` through `--concurrent-sessions` to `sandbox check`, and `checkEscapeScope` appends a witness count to an `unbounded escape:` message rather than lets it soften the verdict.
|
package/docs/agents/sessions.md
DELETED
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Sessions
|
|
3
|
-
description: Resolving live peer sessions to the worktree and branch each holds, reading which row is the caller, the liveness confidence field, what each session surface can see, and where moving a session to another machine is described
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Sessions
|
|
7
|
-
|
|
8
|
-
## List
|
|
9
|
-
|
|
10
|
-
`canon sessions list` reports every live Claude Code session on the machine with the working directory and branch it holds.
|
|
11
|
-
|
|
12
|
-
```bash
|
|
13
|
-
canon sessions list
|
|
14
|
-
canon sessions list --json
|
|
15
|
-
canon sessions list --branch feat/parser --json
|
|
16
|
-
canon sessions list --branch chore/agents --repository ../caret --json
|
|
17
|
-
canon sessions list --self --json
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
| Option | Behavior |
|
|
21
|
-
| --------------------- | ----------------------------------------------------- |
|
|
22
|
-
| `--json` | Add a machine-readable record on stdout |
|
|
23
|
-
| `--branch <name>` | Report the sessions holding this branch |
|
|
24
|
-
| `--repository <path>` | Answer about this project rather than the working one |
|
|
25
|
-
| `--self` | Report the caller's own row |
|
|
26
|
-
|
|
27
|
-
It reads and never writes. The question it answers is which session to address when work has to reach the one holding a given branch, which a session listing cannot answer on its own.
|
|
28
|
-
|
|
29
|
-
Exit codes: `0` the roster was read, `1` refused. The refusal carries a `reason` of `no-registry`, `no-repository`, `no-self-identity`, or `no-self-row`.
|
|
30
|
-
|
|
31
|
-
An exit code says nothing about a call made from a session, since a shell profile may wrap the binary in a function taking its status from a later command. Read the record's `reason` rather than the exit when a skill consumes this.
|
|
32
|
-
|
|
33
|
-
## Scope and count
|
|
34
|
-
|
|
35
|
-
`--branch` scopes the match to one repository, and refuses when none resolves. A branch name identifies a branch inside a repository and nothing across a machine, so an unscoped match reaches a session working in a different project, and `main` collides on every machine running two of them.
|
|
36
|
-
|
|
37
|
-
Which repository that is defaults to the one the command runs in, and `--repository <path>` names another. Every reading moves with it, the session match and the worktree and ref reads alike, because the answer is about that project rather than about where the caller stands. The roster underneath is machine-wide already, so the flag removes a filter rather than widening a search.
|
|
38
|
-
|
|
39
|
-
That is what a dispatcher reaching outside its own project needs. Without it a check run from the toolkit against a branch held by a live session in a consuming project answered unclaimed, and two sessions were sent onto branches other sessions were holding. One refused on the worktree lock and one cut a second worktree on the same branch, which would have put two sessions pushing to one ref.
|
|
40
|
-
|
|
41
|
-
A bare run reports every repository and carries a `repository` field on each row, holding the shared git directory that a main checkout and all its linked worktrees agree on. That is what a caller filters on when it wants a scope of its own.
|
|
42
|
-
|
|
43
|
-
The match can return more than one session. Read the count rather than the first row, since nothing stops two sessions holding one branch, and a caller that treats the result as singular picks among candidates without knowing it.
|
|
44
|
-
|
|
45
|
-
Resolution reads a session's registered `cwd`, taken at whatever `locate()` finds there rather than wherever it has since worked. That registration updates whenever the harness enters a worktree: a worker dispatched onto `main` re-registers at its own branch once `auto-ship` Step 0 runs. What stays fixed is a session moved by shell command, `cd` or `git -C` against another root, with no worktree entry behind it, so it answers only for the branch its last registered `cwd` sits in.
|
|
46
|
-
|
|
47
|
-
A session whose `cwd` reflects a harness worktree entry, at launch or mid-session, resolves fully, covering every dispatched worker once Step 0 runs. A zero for a different branch it is genuinely working on is not evidence the branch is unclaimed, only that a shell-moved registration cannot see it.
|
|
48
|
-
|
|
49
|
-
## Whether a branch is already claimed
|
|
50
|
-
|
|
51
|
-
With `--branch`, the JSON record also carries `worktree` (the path of any worktree already checked out to it, or `null`), `refs` (the refs that already name the branch, the local head and the `origin` remote-tracking ref alike), and `claimed` (`true` when any of the three holds it).
|
|
52
|
-
|
|
53
|
-
Read `claimed` rather than composing the three fields by hand. A worktree can outlive the session that made it, and a session can hold a branch before any worktree exists for it. A branch sitting behind a merged pull request has neither and is taken all the same, which is the reading that was missing when a dispatcher cleared a merged branch and told a worker to build on it.
|
|
54
|
-
|
|
55
|
-
The ref read covers the local head and the remote-tracking ref, which means it sees the remote at whatever the last fetch left behind. A branch pushed from another machine since then reads absent. Closing that gap needs `git ls-remote`, and it is left open deliberately: the remote read costs 0.438s against 0.001s for the local one, on a check that runs before every dispatch.
|
|
56
|
-
|
|
57
|
-
`worktree`, `refs`, and `claimed` are `null` on a bare run with no `--branch`, since none of the three questions has a branch to answer about. A refusal (`no-registry` or `no-repository`) carries none of the keys at all, which a caller should read as unverified rather than as clear.
|
|
58
|
-
|
|
59
|
-
Two flags say which reading came up short. `sessionsReadable` is `false` when the session roster could not be read, and `refsReadable` is `false` when the ref read failed. Either one leaves `claimed` covering the readings around it alone, so a `false` there is a report that ran short of evidence rather than a report that the branch is clear. They stay separate fields because a caller told the roster failed goes and looks at the roster, and folding both into one flag would send it to the wrong place.
|
|
60
|
-
|
|
61
|
-
## Which row is the caller
|
|
62
|
-
|
|
63
|
-
`--self` narrows the report to the row belonging to the session making the call. A dispatcher reads it to learn the `sessionId` it carries into a launch, because the roster returns every field on every row and marks none of them as the caller.
|
|
64
|
-
|
|
65
|
-
Three identifier namespaces reach a session and two of them join to a row. Four variables spell them:
|
|
66
|
-
|
|
67
|
-
- `CLAUDE_CODE_SESSION_ID` holds the roster's own `sessionId`, and is read first because it survives a rename.
|
|
68
|
-
- `CLAUDE_PID` holds the caller's process id, which every row already carries as `pid`.
|
|
69
|
-
- `CLAUDE_CODE_MESSAGING_SOCKET` spells that same pid in its basename. It is read last, since the spelling is a client convention rather than a published interface and a client that moves the socket drops this rung alone.
|
|
70
|
-
- `CLAUDE_CODE_HOST_SESSION_ID` is never read. It carries a `local_`-prefixed value from the harness namespace that matches no row, and it is the variable a reader searching the environment for a session id finds first.
|
|
71
|
-
|
|
72
|
-
The read refuses rather than returning an empty roster, on the same ground as `no-registry`. A `no-self-identity` refusal means the environment stated none of the three, which is a client identifying its sessions some other way. A `no-self-row` refusal means it stated one and no live row carries it, which is what a session running outside a local process gets.
|
|
73
|
-
|
|
74
|
-
## What each surface can see
|
|
75
|
-
|
|
76
|
-
The roster this verb reads and the channel a session sends messages on enumerate different populations, and neither contains the other. Read at one moment, `canon sessions list` returned 6 rows against the 13 peers plus caller the agent listing reported. The roster held a background session at confirmed confidence on two reads a minute apart that the listing never carried, so a live session sat here with no way to reach it. Nine Remote Control sessions ran the other way, reachable there with no row here at all, since they leave no local process for the registry to record.
|
|
77
|
-
|
|
78
|
-
That bounds what a carried identifier buys. Resolving a `sessionId` to a name here can produce a name the send channel rejects, and `--self` answers nothing for a controller driving from Remote Control, which is the operator working from their phone rather than an edge case. A caller that cannot resolve an addressee reports the failed resolution instead of falling back to a guess.
|
|
79
|
-
|
|
80
|
-
The `name` field is what both surfaces agree on, and that agreement is what makes the resolution work. `SendMessage` takes a name and carries no identifier parameter, so the carry runs id to name to send rather than sending an id anywhere.
|
|
81
|
-
|
|
82
|
-
Agreement is not uniqueness, and the last hop is where that bites. Two live sessions can hold one name, the roster carries no field that separates them, and a send to the bare name reaches whichever the channel resolves first. The agent listing prints a ref beside each row and the roster does not, so a name resolving to more than one live row is completed from that listing rather than from anything this verb returns.
|
|
83
|
-
|
|
84
|
-
## Why the verb exists
|
|
85
|
-
|
|
86
|
-
A session listing reports a name, a kind, a status, and how long each session has been running. None of those names a branch. Resolving a branch to a session therefore meant ordering the roster by start time and matching it against the order the worktrees were created, which is an inference that fails whenever two sessions start inside the same minute.
|
|
87
|
-
|
|
88
|
-
Each session writes its own record on disk carrying its working directory beside its own name. This verb reads those records, so a name joins to a branch by an exact match on one file rather than by a guess across two orderings.
|
|
89
|
-
|
|
90
|
-
## What a row carries
|
|
91
|
-
|
|
92
|
-
Every row names the session, the process holding it, its working directory, and the branch checked out there. The status field repeats what the session listing reports, so a caller picking a target reads one output instead of two.
|
|
93
|
-
|
|
94
|
-
A field the record did not carry is reported as null rather than as a value, so an absent start time never reads as a session launched in 1970 and an absent identifier never reads as an empty one.
|
|
95
|
-
|
|
96
|
-
A row whose branch cannot be read is kept and marked rather than dropped. A caller has to be able to tell a session holding no branch from one the resolver never saw, and the second is the failure the verb replaces. The `unresolved` field carries the reason:
|
|
97
|
-
|
|
98
|
-
- `detached-head`: the session holds a worktree with no branch name
|
|
99
|
-
- `not-a-repository`: the session is working outside any git repository
|
|
100
|
-
- `git-unavailable`: git is not on the path, so nothing could be read
|
|
101
|
-
|
|
102
|
-
## The confidence field
|
|
103
|
-
|
|
104
|
-
Every report states how liveness was decided, on a pass as well as a failure.
|
|
105
|
-
|
|
106
|
-
`confirmed` means each row's process was matched against the start time its own record stamped at launch, so the process holding the pid is the session that wrote the file. A caller can address a row of that kind directly.
|
|
107
|
-
|
|
108
|
-
`unverified` means only that the pid answers a probe. That cannot separate the original session from an unrelated process that inherited its pid after the session ended, so the roster is a candidate list rather than an identity. Treat the mapping as inferred and open the message by naming the branch the reader is believed to hold, asking to be corrected.
|
|
109
|
-
|
|
110
|
-
The registry holds one record per session and is never pruned, so it accumulates thousands of entries. On the `unverified` path a stale record whose pid has been reused reads as live, which is why the field is reported rather than assumed.
|
|
111
|
-
|
|
112
|
-
## The status dwell
|
|
113
|
-
|
|
114
|
-
Every row carries `statusUpdatedAt`, the stamp a client writes beside `status` at the moment it last changed, and `statusDwellMs`, the elapsed milliseconds since that stamp. Measured over the live registry, 23 of 341 usable records carry `statusUpdatedAt`, so `null` is the ordinary answer rather than an edge case, and the absence tracks a client version rather than a record's age alone: the one record ever measured carrying `status: "waiting"` is among the 318 without it.
|
|
115
|
-
|
|
116
|
-
`statusDwellMs` falls back to the coarser `updatedAt` stamp when `statusUpdatedAt` is absent, so it is `null` only where a record carries neither. `statusUpdatedAt` itself is never backfilled from the fallback and stays `null` in that case, since it names the exact stamp rather than an estimate. A stamp ahead of the reading clock clamps the dwell to zero rather than reporting a negative one.
|
|
117
|
-
|
|
118
|
-
The dwell is what separates a status that resolves on its own from one that does not. `busy` and `idle` transition without help, so a long dwell there is ordinary. `waiting` does not: a session in that state is blocked on something outside itself, and a dwell that keeps growing past the ordinary span of a prompt is a session stalled rather than paused. `canon sessions list` renders the dwell beside the status at the coarsest unit that keeps it a whole number, and the JSON record carries both fields on every row.
|
|
119
|
-
|
|
120
|
-
## Export and import
|
|
121
|
-
|
|
122
|
-
`canon sessions export <id>` packs one session into a bundle and `canon sessions import <bundle>` places it on another machine. Both are in `sessions-transfer.md`.
|
|
123
|
-
|
|
124
|
-
## What the read depends on
|
|
125
|
-
|
|
126
|
-
The records live under the Claude Code configuration directory, which the verb resolves from `CLAUDE_CONFIG_DIR` and falls back to `~/.claude`. Their location, their filenames, and the fields inside them are a client implementation detail rather than a published interface, so a client change can move them. The verb reports an absent registry as a refusal rather than as a machine running no sessions, which is what surfaces the move instead of burying it in an empty roster.
|
|
127
|
-
|
|
128
|
-
The start-time comparison reads the process filesystem and exists only on some platforms. Where it does not, the verb still answers and marks the confidence, so a target without it keeps a working roster rather than losing the command.
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Skill audit
|
|
3
|
-
description: Measuring both skill corpora against the skill standards, the checks it reads, the requirement gate that is the only failing one, and the drift verb that names bodies rewritten since a ref
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Skill audit
|
|
7
|
-
|
|
8
|
-
`canon claude skills audit [path]` reports both skill corpora against the rules `claude/skills/create-skill/references/skill.md`, `claude/skills/create-skill/references/skill-requirement.md`, and `claude/skills/create-skill/references/skill-practice.md` state mechanically. It reads and reports. Fixing what it finds is separate work.
|
|
9
|
-
|
|
10
|
-
```bash
|
|
11
|
-
canon claude skills audit
|
|
12
|
-
canon claude skills audit --json
|
|
13
|
-
canon claude skills audit --requirements-only
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
| Option | Behavior |
|
|
17
|
-
| --------------------- | -------------------------------------------------------------------- |
|
|
18
|
-
| `--json` | Add a machine-readable record on stdout, keeping the frame |
|
|
19
|
-
| `--requirements-only` | Run the gating presence check alone, printing nothing when it passes |
|
|
20
|
-
|
|
21
|
-
## Corpus scope
|
|
22
|
-
|
|
23
|
-
Both trees are measured. `claude/skills/` installs into a target and `.claude/skills/` stays in the toolkit, the standard governs each, so a run reading one reports a pass over half its subject. A tree the project does not carry is skipped, which puts a target holding `.claude/skills/` alone in scope. A run where neither resolves refuses, since a clean exit over nothing measured is the outcome the command exists to prevent.
|
|
24
|
-
|
|
25
|
-
The audit reads the directory it is pointed at, defaulting to the cwd. `canon claude skills list` resolves the shipped corpus from its own install root instead, so a dev-linked binary reports `main` no matter which worktree runs it. Auditing a branch needs the cwd reading, which is why the two commands resolve their root differently.
|
|
26
|
-
|
|
27
|
-
## What it measures
|
|
28
|
-
|
|
29
|
-
Each check traces to a line in the standard.
|
|
30
|
-
|
|
31
|
-
### Folder and frontmatter
|
|
32
|
-
|
|
33
|
-
- Frontmatter `name` matching the folder name
|
|
34
|
-
- Frontmatter `description` present
|
|
35
|
-
- `description` under 1024 characters
|
|
36
|
-
- No `README.md` inside a skill folder
|
|
37
|
-
- Folder name in kebab-case
|
|
38
|
-
|
|
39
|
-
### Requirement and content
|
|
40
|
-
|
|
41
|
-
- `REQUIREMENT.md` present in every skill folder
|
|
42
|
-
- Each `REQUIREMENT.md` declaring `Gap` and `Must`, matched at any heading level
|
|
43
|
-
- No ISO date in `SKILL.md` or any file under `references/`, reported as `datedProvenance` in the JSON record's `findings`
|
|
44
|
-
|
|
45
|
-
### Practice, references, and family
|
|
46
|
-
|
|
47
|
-
- Each listed practice skill closing with `## Excuses and rebuttals`, `## Red flags`, and `## Before handing over`, and each skill on the wider ledger list carrying `references/adopted.md`, reported together as `practiceShape`
|
|
48
|
-
- Each file under `references/` running over 100 lines opening with a `## Contents` H2, reported as `referenceContents`
|
|
49
|
-
- Each skill under `claude/skills/` declaring a `metadata.family` that names a key in `checkpoints.families`, reported as `family`
|
|
50
|
-
|
|
51
|
-
A body whose frontmatter does not parse reports as declaring neither field rather than ending the run, so one malformed file cannot hide the corpus behind it. A key present with an empty value reads as absent, since a blank `name` would otherwise report as a name disagreeing with every folder. A folder carrying no requirement is reported once under presence rather than counted again for the sections it therefore lacks.
|
|
52
|
-
|
|
53
|
-
The date check reads prose alone. A date inside a fence or a code span is example data, such as a sample frontmatter block, and a date in the body's own frontmatter is metadata, so neither reports. `REQUIREMENT.md` and `EVAL.md` are not read, since the requirement's `Gap` is where the standard sends the incident and the date that earned a rule.
|
|
54
|
-
|
|
55
|
-
Each finding names the file and the line, such as `claude/skills/<name>/references/<file>.md line 12: <date>`. A date after "measured" or "verified" reports like any other, unlike the context audit, because a skill body states the rule a session follows today rather than a reading of the tree.
|
|
56
|
-
|
|
57
|
-
The practice check reads only the skills the audit lists, which the JSON record carries as `checkpoints.practiceSkills` for the closing sections and `checkpoints.ledgerSkills` for the ledger. The ledger list holds every practice skill plus the skills that cite outside sources without a practice's closing shape, so a skill on it alone owes the ledger and none of the sections.
|
|
58
|
-
|
|
59
|
-
Both lists name folders under the toolkit's shipped `claude/skills/` corpus alone, so a project's own skill is never read as a practice skill and a same-named folder under `.claude/skills/` produces no finding. Where `claude/skills/` resolves, a listed folder it does not hold reports as a missing skill, so a renamed or misspelled entry cannot pass unread. Each closing section matches only as an exact H2 outside a fence, so an H3 or a heading carrying more words reports as missing.
|
|
60
|
-
|
|
61
|
-
The contents check counts every line of a reference, frontmatter included, the way `wc -l` does, so a file of exactly 100 lines passes and one of 101 needs the list. `## Contents` matches the same way the closing sections do, as an exact H2 outside a fence. `SKILL.md` is never read for it, and the threshold travels in the JSON record as `checkpoints.contentsThreshold`.
|
|
62
|
-
|
|
63
|
-
## What it leaves alone
|
|
64
|
-
|
|
65
|
-
The report names its own blind spots on every run, including the run where everything passed. Whether each `Must` traces to a stated gap is the rule in the requirement standard worth the most and no parser reads it. Whether a gap states an observed failure rather than an intent, and whether a description routes, are the same kind of judgment. The 150-line body checkpoint is the one mechanical rule still absent here, and adding it would print a count rather than a defect, since the standard makes it a prompt to look with nothing enforcing it.
|
|
66
|
-
|
|
67
|
-
The date check reads ISO dates alone. A date written in words, a month with no day, and provenance carrying no date at all pass it.
|
|
68
|
-
|
|
69
|
-
A check with no rule behind it prints an opinion as a defect, which is where the list stops.
|
|
70
|
-
|
|
71
|
-
## Exit codes
|
|
72
|
-
|
|
73
|
-
Exit codes are `0` for a clean run, `1` for a refusal, and `2` for a skill folder carrying no `REQUIREMENT.md`. Only presence sets a failing code. Name, description, folder, requirement-section, dated-provenance, practice shape, reference contents, and family findings print and return `0`, because each is a judgment a reader settles and failing a push on one would make the check something to route around. In the toolkit, three `bun run check` stages read the same report in-process and fail a push on dated provenance, on practice shape, and on family, since its own corpus is held at zero on all three.
|
|
74
|
-
|
|
75
|
-
No stage reads reference contents, so a long reference without a list stays a report.
|
|
76
|
-
|
|
77
|
-
## The requirement gate
|
|
78
|
-
|
|
79
|
-
`--requirements-only` is the half wired into `bun run check`. Presence of a required file is a fact with no false positives, and the rule had nothing reading it, so a skill shipped without the sibling passed every stage while the standard required it.
|
|
80
|
-
|
|
81
|
-
The check is preventive rather than diagnostic. Every mechanical rule passed across both corpora the day it shipped, so what it buys is the regression it stops rather than a backlog it surfaces.
|
|
82
|
-
|
|
83
|
-
## Drift since a ref
|
|
84
|
-
|
|
85
|
-
`canon claude skills drift <ref>` names the shipped skill bodies rewritten between that commit and the current `HEAD`.
|
|
86
|
-
|
|
87
|
-
```bash
|
|
88
|
-
canon claude skills drift HEAD~20
|
|
89
|
-
canon claude skills drift 02d7b265 --json # canon-allow-reference: a copyable command takes a literal git ref, which no qualified form is
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
| Option | Behavior |
|
|
93
|
-
| -------- | ---------------------------------------------------------- |
|
|
94
|
-
| `--json` | Add a machine-readable record on stdout, keeping the frame |
|
|
95
|
-
|
|
96
|
-
A skill body enters a session once and stays. Re-invoking the skill does not re-read the file, so an edit made this session is not picked up and nothing compares the two. A session that outlives a merge touching a body it loaded keeps applying what it loaded, and a compaction carries the held copy forward with the summary it writes, which makes that exposure the age of the oldest load rather than the age of the session.
|
|
97
|
-
|
|
98
|
-
Age is one route and not the only one. A session that edits a body and then invokes that same skill later in the same run replays the pre-edit text, which is what a ship chain does whenever a branch touches a body the chain runs at the end. This verb does not reach that route, because it reads history and the edit is still uncommitted when the replay happens. Re-read a body after editing it, and do not wait for a report to name it.
|
|
99
|
-
|
|
100
|
-
Which shape the staleness takes decides whether anyone notices. A held body naming a file the branch deleted fails loudly by having nothing to read. One naming a file that still exists while saying something different resolves and reads current, so the session applies the stale rule and reports success.
|
|
101
|
-
|
|
102
|
-
The ref is required and carries no default. `HEAD` is the only value the command could supply for itself, and it answers every run with nothing moved, which is the silence this verb exists to break. A session passes the commit it started from.
|
|
103
|
-
|
|
104
|
-
Nothing on the machine records that commit, so a session recovers it from its own elapsed run time with `git log -1 --format=%H --before='<duration> ago'` and rounds the duration up. Over-reporting costs one read of a body while under-reporting costs the answer, so the generous end of the estimate is the correct one and no session needs to pin the exact commit.
|
|
105
|
-
|
|
106
|
-
### What drift reads
|
|
107
|
-
|
|
108
|
-
Bodies alone, one `SKILL.md` per folder, across `claude/skills/` and not the internal tree. A reference or a requirement beside the body is read by whoever opens it and holds no copy that outlives the read, and the internal corpus never loads into a target session at all.
|
|
109
|
-
|
|
110
|
-
The report names the newest commit that rewrote each body rather than the first, so a body rewritten twice in the range points at the version on disk now.
|
|
111
|
-
|
|
112
|
-
### What drift cannot answer
|
|
113
|
-
|
|
114
|
-
The report says a file moved, not that a session holds a stale copy. A session cannot read its own loaded body as bytes, so the comparison runs against history and a ref older than the oldest load over-reports. That is the safe direction, since confirming a name costs one read of the body while the failure being answered is silence. Every run states the bound, including the run that names nothing.
|
|
115
|
-
|
|
116
|
-
The verb answers only when someone runs it, and a report read is not a report acted on. A session that sees a moved body in the result and does not re-read that body stands where it started. One session reached that outcome after running the verb at session start, then followed the held copy for the rest of the day.
|
|
117
|
-
|
|
118
|
-
Two cases fall outside the range. An uncommitted edit in the working tree is not history yet, so a body changed and left unstaged reports as unmoved. A target project loads the plugin from a marketplace cache with no repository behind it, where the verb refuses and names the absent history rather than reporting a clean tree.
|
|
119
|
-
|
|
120
|
-
Exit codes are `0` when history was read, whether or not a body moved, and `1` when the question could not be answered. An absent shipped corpus, an absent repository, and a ref matching no commit each refuse with their own reason on stderr. A command exiting zero on a question it never answered is a failure already recorded against two skills, which is why no path here reports an empty result in place of a refusal.
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Citation reach
|
|
3
|
-
description: Reporting the skill bodies that cite a path no target project receives, which corpus the verb reads, the ownership key that decides what counts, the one-word qualifier that marks a citation as decided, and why the verb reports instead of gating
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Citation reach
|
|
7
|
-
|
|
8
|
-
`canon claude skills reach [path]` reports every skill body citing a path that exists in the project and reaches no reader elsewhere. It reads and reports. Repairing what it finds is separate work.
|
|
9
|
-
|
|
10
|
-
```bash
|
|
11
|
-
canon claude skills reach
|
|
12
|
-
canon claude skills reach --json
|
|
13
|
-
canon claude skills reach ~/repos/my-project
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
| Option | Behavior |
|
|
17
|
-
| -------- | ---------------------------------------------------------- |
|
|
18
|
-
| `--json` | Add a machine-readable record on stdout, keeping the frame |
|
|
19
|
-
|
|
20
|
-
## Which corpus it reads
|
|
21
|
-
|
|
22
|
-
`claude/skills/` when the project holds it, and `.claude/skills/` otherwise. The shipped tree wins where both are present, so a toolkit reads what it ships and a project holding only its own skills is measured rather than refused. Every report names the corpus it read on its `Corpus` line and carries it as `corpus` in the `--json` record.
|
|
23
|
-
|
|
24
|
-
## The defect it reads for
|
|
25
|
-
|
|
26
|
-
A plugin skill installs into a project and the toolkit's own tree is not there. A body naming `canon/context/features/transcripts.md` resolves for a session running in this repository and sends every other reader to nothing, and no stage asked the question until this one. The shape is wider than one folder: a seed naming a standard with no route and a machine-readable field naming a toolkit-only path are the same claim, true here and false in a target.
|
|
27
|
-
|
|
28
|
-
## What counts as a citation
|
|
29
|
-
|
|
30
|
-
A backticked token carrying a separator and an extension, which is how every body spells a path it means a reader to open. Three forms are skipped by construction.
|
|
31
|
-
|
|
32
|
-
- A placeholder such as `canon/context/<domain>.md`, which names a shape rather than a file
|
|
33
|
-
- A path resolved through `${CLAUDE_SKILL_DIR}`, which is self-contained wherever the plugin loads
|
|
34
|
-
- A sibling named relatively, such as `references/labels.md`, which matches no authoring root and travels with the body
|
|
35
|
-
|
|
36
|
-
The same sibling named from the repository root as `claude/skills/<name>/references/<file>.md` is reported rather than skipped. The plugin loads from a cache rather than from the project tree, so that spelling resolves for nobody and the report is correct.
|
|
37
|
-
|
|
38
|
-
A path the toolkit does not hold is dropped rather than reported. The measure asks whether a claim true here is false in a target, and a path true in neither is a different defect that `canon context audit` already reports against its own corpus.
|
|
39
|
-
|
|
40
|
-
## The ownership key
|
|
41
|
-
|
|
42
|
-
A cited path counts when it sits under an authoring root no install channel delivers. Standards install nowhere and are read through the plugin corpus, governance rules install under `.claude/rules/`, and the rest is this repository's own source, catalogs, and contract pages.
|
|
43
|
-
|
|
44
|
-
`src/`, `scripts/`, and bare `docs/` are deliberately outside the list. A body naming one of those is describing the reader's own tree, so listing them reports a correct citation on every run and buries the finding under the pass.
|
|
45
|
-
|
|
46
|
-
`canon/context/` joins them when the corpus read is a project's own. A seed put those entries there and the project owns them afterward, so a body under `.claude/skills/` naming one points at a file its reader holds. The seed disowning below cannot answer that in a project, since it reads a `tooling/` tree only the toolkit carries.
|
|
47
|
-
|
|
48
|
-
A path a seed installs is disowned twice, under its own name and under the folder spelling it takes once a project splits the entry. A domain that outgrows one file becomes `<domain>/`, which is still the entry the seed delivered, so reporting the split form would fail a project for growing.
|
|
49
|
-
|
|
50
|
-
## The qualifier
|
|
51
|
-
|
|
52
|
-
A correct citation and a defective one are the same string, and the sentence around it is the difference. A citation counts as decided when its line names the toolkit as the owner, matching the bodies that already spell it that way. The repair for a finding is to say whose copy the path is, never to delete the citation, since the paths name real documents a reader wants.
|
|
53
|
-
|
|
54
|
-
```markdown
|
|
55
|
-
Read `canon/context/context-model.md` from the toolkit if context on the system is needed.
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
A line mentioning the toolkit for an unrelated reason exempts a citation on it. That is the accepted cost of a word over a notation every future body would have to learn.
|
|
59
|
-
|
|
60
|
-
## Exit codes
|
|
61
|
-
|
|
62
|
-
Exit codes are `0` when every citation names its owner, `1` for a refusal, and `2` when at least one is unqualified. The refusal is a tree carrying neither `claude/skills/` nor `.claude/skills/`, which holds no skill body to measure, and it reports the reason rather than a clean count over nothing.
|
|
63
|
-
|
|
64
|
-
The verb reports rather than gates. A toolkit-scoped instruction is sometimes meant for a session in this repository, so failing a push on one would make the check something to route around. `canon audits run` registers it with no gating exit for the same reason, and carries `unqualifiedCitations` as its retained count.
|