@erclx/canon 5.39.0 → 5.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/README.md +39 -22
  2. package/claude/.claude-plugin/plugin.json +1 -1
  3. package/claude/skills/auto-ship/REQUIREMENT.md +1 -0
  4. package/claude/skills/auto-ship/SKILL.md +6 -0
  5. package/claude/skills/auto-ship/references/fan-in.md +59 -0
  6. package/claude/skills/canvas-board/SKILL.md +1 -1
  7. package/claude/skills/create-skill/references/skill.md +1 -1
  8. package/claude/skills/decision-escalate/REQUIREMENT.md +1 -1
  9. package/claude/skills/decision-escalate/SKILL.md +1 -2
  10. package/claude/skills/draft-slides/SKILL.md +2 -3
  11. package/claude/skills/draft-slides/references/deck.md +183 -0
  12. package/claude/skills/git-branch/REQUIREMENT.md +17 -6
  13. package/claude/skills/git-branch/SKILL.md +63 -7
  14. package/claude/skills/git-pr/references/evidence.md +2 -2
  15. package/claude/skills/git-pr/references/final-command.md +1 -1
  16. package/claude/skills/git-pr/references/labels.md +1 -1
  17. package/claude/skills/git-pr/references/publish.md +1 -1
  18. package/claude/skills/plan-feature/references/batches.md +48 -1
  19. package/claude/skills/plan-feature/references/plan.md +2 -0
  20. package/claude/skills/plan-intake-answer/REQUIREMENT.md +1 -1
  21. package/claude/skills/plan-intake-answer/SKILL.md +1 -2
  22. package/claude/skills/review-address/references/rebase-conflicts.md +7 -0
  23. package/claude/skills/review-ui/SKILL.md +3 -0
  24. package/claude/skills/review-ui/references/close-out.md +47 -0
  25. package/claude/skills/review-ui/references/post.md +3 -1
  26. package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +1 -0
  27. package/claude/skills/role-orchestrator/references/orchestrator-handback.md +1 -1
  28. package/claude/skills/role-worker/REQUIREMENT.md +1 -0
  29. package/claude/skills/role-worker/SKILL.md +9 -0
  30. package/claude/skills/task-board/references/board.md +1 -1
  31. package/claude/skills/task-board/references/tasks.md +1 -1
  32. package/claude/skills/teach-workspace/REQUIREMENT.md +1 -1
  33. package/claude/skills/teach-workspace/SKILL.md +1 -2
  34. package/claude/skills/ux-measure/SKILL.md +1 -1
  35. package/docs/agents/audits.md +61 -60
  36. package/docs/agents/context-audit.md +40 -58
  37. package/docs/agents/index.md +5 -51
  38. package/docs/agents/install-and-sync.md +1 -0
  39. package/docs/agents/markdown-audit.md +53 -99
  40. package/docs/agents/records.md +37 -79
  41. package/docs/agents/sessions-transfer.md +26 -70
  42. package/docs/target/index.md +0 -1
  43. package/docs/target/migrations.md +57 -66
  44. package/docs/target/projects.md +36 -78
  45. package/docs/target/scenarios.md +9 -19
  46. package/docs/target/sync.md +27 -68
  47. package/docs/workflow/ai-workflow.md +22 -68
  48. package/docs/workflow/operating-model.md +32 -186
  49. package/docs/workflow/parallel-features.md +30 -66
  50. package/docs/workflow/skill-map-on-request.md +22 -24
  51. package/docs/workflow/skill-map.md +72 -76
  52. package/docs/workflow/visual-design-workflow.md +25 -118
  53. package/docs/workflow/zshrc-aliases.md +18 -37
  54. package/package.json +1 -1
  55. package/src/canvas/client/inspector/fields.css +4 -0
  56. package/src/canvas/index.html +6 -0
  57. package/src/claude/cases/git.ts +4 -0
  58. package/src/claude/skills-reach.ts +2 -2
  59. package/src/cli/ui.ts +3 -1
  60. package/src/commands/canvas.ts +23 -0
  61. package/src/commands/capture.ts +14 -0
  62. package/src/commands/claude.ts +13 -0
  63. package/src/commands/demo.ts +22 -0
  64. package/src/commands/deps.ts +6 -0
  65. package/src/commands/driver.ts +28 -2
  66. package/src/commands/gov.ts +52 -18
  67. package/src/commands/intake.ts +17 -0
  68. package/src/commands/labels.ts +38 -5
  69. package/src/commands/pr.ts +192 -52
  70. package/src/commands/records.ts +19 -3
  71. package/src/commands/secrets.ts +6 -0
  72. package/src/commands/sessions.ts +8 -0
  73. package/src/commands/tasks.ts +52 -0
  74. package/src/commands/teach.ts +13 -0
  75. package/src/commands/upstream.ts +25 -1
  76. package/src/commands/worktrees.ts +5 -0
  77. package/src/context/audit.ts +22 -4
  78. package/src/context/folders.ts +1 -1
  79. package/src/context/shape-gated.ts +5 -2
  80. package/src/counts/scan.ts +1 -1
  81. package/src/design/components.ts +1 -1
  82. package/src/design/document.ts +1 -1
  83. package/src/design/tokens.ts +1 -1
  84. package/src/gate/boundaries.ts +4 -1
  85. package/src/gate/measures.ts +1 -1
  86. package/src/gate/stages.ts +7 -7
  87. package/src/git/env.ts +6 -0
  88. package/src/markdown/scope.ts +11 -19
  89. package/src/pr/frames.ts +53 -9
  90. package/src/pr/lift.ts +153 -19
  91. package/src/pr/painted.ts +87 -0
  92. package/src/records/validate.ts +1 -0
  93. package/src/roots/record.ts +18 -1
  94. package/src/sandbox/census.ts +1 -1
  95. package/src/sandbox/headless/run.ts +3 -2
  96. package/src/sandbox/headless/watch.ts +3 -4
  97. package/src/slides/convert/master.ts +3 -0
  98. package/src/sync/format.ts +46 -0
  99. package/src/sync/stamp.ts +6 -0
  100. package/src/tasks/answers.ts +79 -2
  101. package/src/tasks/fan-in.ts +176 -0
  102. package/src/tasks/stack.ts +1 -1
  103. package/tooling/astro/reference.md +80 -39
  104. package/tooling/base/reference.md +63 -32
  105. package/tooling/base/seeds/canon/context/ci.md +7 -15
  106. package/tooling/base/seeds/canon/context/development.md +14 -14
  107. package/tooling/claude/reference.md +40 -22
  108. package/tooling/cloudflare/reference.md +31 -12
  109. package/tooling/go/reference.md +47 -23
  110. package/tooling/nextjs/reference.md +52 -19
  111. package/tooling/php/reference.md +35 -16
  112. package/tooling/python/reference.md +47 -30
  113. package/tooling/shell/reference.md +6 -5
  114. package/tooling/vite-react/reference.md +49 -21
  115. package/tooling/web/reference.md +131 -71
  116. package/docs/agents/canvas.md +0 -86
  117. package/docs/agents/capture.md +0 -65
  118. package/docs/agents/census.md +0 -23
  119. package/docs/agents/comments.md +0 -34
  120. package/docs/agents/context-audit-checks.md +0 -108
  121. package/docs/agents/context-classify.md +0 -101
  122. package/docs/agents/counts.md +0 -74
  123. package/docs/agents/demo.md +0 -140
  124. package/docs/agents/driver.md +0 -103
  125. package/docs/agents/gate.md +0 -90
  126. package/docs/agents/hooks.md +0 -78
  127. package/docs/agents/intake.md +0 -81
  128. package/docs/agents/key-changes.md +0 -116
  129. package/docs/agents/label-coverage.md +0 -86
  130. package/docs/agents/markdown-audit-density.md +0 -39
  131. package/docs/agents/markdown-audit-weight.md +0 -83
  132. package/docs/agents/pr-evidence-layout.md +0 -70
  133. package/docs/agents/pr-evidence.md +0 -273
  134. package/docs/agents/pr-frames.md +0 -131
  135. package/docs/agents/pr-lift.md +0 -132
  136. package/docs/agents/pr-local.md +0 -111
  137. package/docs/agents/pr-preview.md +0 -155
  138. package/docs/agents/pr-reads.md +0 -111
  139. package/docs/agents/pr-tick.md +0 -72
  140. package/docs/agents/records-push.md +0 -61
  141. package/docs/agents/records-stale.md +0 -40
  142. package/docs/agents/records-validate.md +0 -57
  143. package/docs/agents/restated.md +0 -83
  144. package/docs/agents/routing.md +0 -61
  145. package/docs/agents/rule-citations.md +0 -90
  146. package/docs/agents/sandbox.md +0 -126
  147. package/docs/agents/sessions.md +0 -128
  148. package/docs/agents/skills-audit.md +0 -120
  149. package/docs/agents/skills-reach.md +0 -64
  150. package/docs/agents/slides.md +0 -176
  151. package/docs/agents/state-scoped-risk.md +0 -105
  152. package/docs/agents/superseded.md +0 -85
  153. package/docs/agents/tasks-archive.md +0 -83
  154. package/docs/agents/tasks-plans.md +0 -152
  155. package/docs/agents/tasks-validate.md +0 -133
  156. package/docs/agents/tasks.md +0 -123
  157. package/docs/agents/teach-authoring.md +0 -118
  158. package/docs/agents/teach.md +0 -157
  159. package/docs/agents/test-order.md +0 -58
  160. package/docs/agents/upstream.md +0 -94
  161. package/docs/agents/worktrees.md +0 -118
  162. package/docs/target/standards-moved.md +0 -34
@@ -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.
@@ -1,176 +0,0 @@
1
- ---
2
- title: Slides
3
- description: Where a deck folder lives and what it holds, what it declares for the whole deck in deck.json, the faces it embeds, what each slide declares on its own body, transitions and entrances, the master built from the project's tokens, speaker notes, charts, and what each refusal reports
4
- ---
5
-
6
- # Slides
7
-
8
- `canon slides render <deck>` turns a folder of `.html` slides into an editable PowerPoint deck. Each deck lives in its own folder under `.canon/slides/`, which `canon records push` backs and git ignores, and `canon slides list --json` lists them. Each slide describes its own shapes. What belongs to the whole deck, being the bands, the numbers, and the mark, sits in one file beside them, and the master that carries it takes the project's own colors and face.
9
-
10
- ```bash
11
- canon slides list --json
12
- canon slides render q3-review
13
- ```
14
-
15
- ## The deck folder
16
-
17
- | Path | Holds |
18
- | -------------------------------- | ---------------------------------------------------- |
19
- | `.canon/slides/<deck>/*.html` | the slides, rendered in filename order |
20
- | `.canon/slides/<deck>/deck.json` | what belongs to the whole deck, described below |
21
- | `.canon/slides/<deck>/assets/` | images the slides reference by a relative path |
22
- | `.canon/slides/layouts/` | the project's shared layouts, never listed as a deck |
23
-
24
- A folder holding no `.html` file is not a deck. `render` with no name takes the only deck there is, and refuses with the list when there are several. It also takes a folder path outside `.canon/slides/`.
25
-
26
- ## The deck file
27
-
28
- `deck.json` beside the slides is optional. Without it the deck gets its defaults.
29
-
30
- | Field | Default | What it sets |
31
- | -------------- | ------------------------------ | ------------------------------------------------- |
32
- | `title` | the folder name | the file's title and the default footer text |
33
- | `header` | off | a band at the top, with `left`, `center`, `right` |
34
- | `footer` | on, with the title on the left | a band at the bottom, with the same three slots |
35
- | `slideNumbers` | `true` | the number at the right of the footer |
36
- | `mark` | none | an image path, relative to the folder, top right |
37
- | `fonts` | none | faces to embed in the file, described below |
38
-
39
- ```json
40
- {
41
- "title": "Q3 review",
42
- "header": { "left": "Acme" },
43
- "footer": { "center": "Internal" },
44
- "mark": "assets/mark.png",
45
- "fonts": [
46
- { "family": "Inter", "path": "fonts/Inter-Regular.ttf" },
47
- { "family": "Inter", "weight": 700, "path": "fonts/Inter-Bold.ttf" }
48
- ]
49
- }
50
- ```
51
-
52
- A band takes `show` and the three slots. A declared band merges over its default, so `"footer": { "center": "Internal" }` keeps the title on the left. Declaring any slot turns a band on unless it also says `"show": false`. Set a slot to `""` to empty it.
53
-
54
- Slide numbers belong to the footer and hold its right slot. They show only where the footer does, and `footer.right` needs `slideNumbers` set to `false`.
55
-
56
- Prefer a raster mark. An SVG mark lands as a vector, and its fallback for a viewer that cannot draw SVG comes out broken.
57
-
58
- ### Embedded faces
59
-
60
- Each entry in `fonts` names one TrueType or OpenType file inside the deck folder, and the deck carries it so it renders in its own face on a machine without the font installed.
61
-
62
- - `family` is required and should match the family the slides set, since that is the name PowerPoint looks the face up by.
63
- - `weight` defaults to `400`. PowerPoint holds four faces a family, so `600` and above fills the bold slot and anything lighter fills regular.
64
- - `style` is `normal` or `italic`, defaulting to `normal`.
65
- - `path` is relative to the folder and has to stay inside it.
66
-
67
- A face is refused, and the deck still written without it, when its license forbids embedding or allows a bitmap only, when it is a variable font, when it is a `woff` or `woff2` file, or when a second face lands in a slot one already fills. Supply static instances, one per weight, in place of a variable face. For `woff2`, supply the TrueType or OpenType file it was compressed from, which most font downloads ship beside it.
68
-
69
- ## What each slide declares
70
-
71
- A slide sets its own place in the deck through attributes on `<body>`.
72
-
73
- | Attribute | Effect |
74
- | --------------------------------------- | --------------------------------------------- |
75
- | `data-section="Results"` | opens a section the slides after it stay in |
76
- | `data-hidden` | hides the slide in the show |
77
- | `data-header="off"` | drops the header on this slide |
78
- | `data-footer="off"` | drops the footer and its number on this slide |
79
- | `data-footer-left`, `-center`, `-right` | replaces that footer slot on this slide alone |
80
- | `data-header-left`, `-center`, `-right` | replaces that header slot on this slide alone |
81
-
82
- Once any slide opens a section, slides ahead of the first one sit in a section named after the deck, since PowerPoint wants every slide in one. A title the deck returns to after another section opens a new section numbered `Intro (2)`, so each section stays one run of slides.
83
-
84
- A slide dropping one band keeps the other from the master. A slide replacing a slot draws its band as its own text, so editing that band once in PowerPoint's master view reaches every slide except the ones that replaced it.
85
-
86
- ## Motion
87
-
88
- A slide declares how it arrives on `<body>`, and an element declares how it comes in on its own tag.
89
-
90
- | Attribute | Takes | Effect |
91
- | -------------------------- | ------------------------------- | -------------------------------------- |
92
- | `data-transition` | `fade`, `push`, `wipe`, `cover` | the transition into this slide |
93
- | `data-transition-duration` | `500`, `500ms`, or `0.5s` | rounded to 0.5, 0.75, or 1 second |
94
- | `data-enter` | `fade`, `fly`, `wipe`, `zoom` | an entrance played on a click |
95
- | `data-enter-order` | a number | the click it plays on, lowest first |
96
- | `data-enter-duration` | `500`, `500ms`, or `0.5s` | the entrance length, 500 ms by default |
97
-
98
- ```html
99
- <body data-transition="fade">
100
- <h1>Results</h1>
101
- <div class="card" data-enter="fly" data-enter-order="1">…</div>
102
- <div class="card" data-enter="fly" data-enter-order="2">…</div>
103
- </body>
104
- ```
105
-
106
- Each entrance is its own click. Entrances without an order play after the ordered ones, in page order. An entrance brings in every shape drawn from its element and the elements inside it together, so a card's box and its text arrive on one click, and an element drawn as a picture brings in that picture. An element inside it with its own `data-enter` waits for its own click. Fly comes in from the bottom and wipe from the left.
107
-
108
- A transition length rounds to the nearest of the three speeds PowerPoint's file format names, since a length in milliseconds needs an extension the deck does not write.
109
-
110
- ## The master
111
-
112
- The master takes its colors from the `<html>` element of the first slide as Chromium laid it out with the project's token stylesheet injected, so a theme switched on one slide's `<body>`, such as a dark cover, stays on that slide:
113
-
114
- - `--color-background`: the slide ground
115
- - `--color-text`: the header text
116
- - `--color-muted`: the footer text
117
- - `--color-accent`: the slide number
118
- - the `<body>` font family, first name with a ` Variable` suffix dropped: the theme face
119
-
120
- A role the project declares no token for falls back to the slide body's own color and prints one `✗` line naming it. It never falls back to the toolkit's palette.
121
-
122
- A slide whose `<body>` background differs from the master's keeps its own. A slide whose `<body>` computes a `--color-text` apart from the master's prints one `✗` line naming it, since its bands keep the master's colors and may not read against its ground.
123
-
124
- ## Speaker notes
125
-
126
- `<aside class="notes">` becomes the slide's notes and is never drawn. Each paragraph or list item becomes its own line, and markup is dropped.
127
-
128
- ## Charts
129
-
130
- A `<figure data-chart="...">` holding a `<table>` becomes a native, editable chart placed where the figure was laid out. The table stays the page's accessible source and is not drawn as well.
131
-
132
- ```html
133
- <figure data-chart="bar" data-labels>
134
- <figcaption>Revenue by region</figcaption>
135
- <table>
136
- <tr>
137
- <th>Region</th>
138
- <th>Q1</th>
139
- <th>Q2</th>
140
- </tr>
141
- <tr>
142
- <td>North</td>
143
- <td>12</td>
144
- <td>16</td>
145
- </tr>
146
- <tr>
147
- <td>South</td>
148
- <td></td>
149
- <td>11</td>
150
- </tr>
151
- </table>
152
- </figure>
153
- ```
154
-
155
- - `data-chart` takes `bar`, `line`, `area`, `pie`, or `doughnut`.
156
- - The header row names the series and the first column names the categories.
157
- - An empty cell is a gap, not a zero. A comma is a thousands separator only where it groups in threes, as in `12,345.5`, so a comma-decimal `3,5` refuses rather than reading as 35.
158
- - `data-labels` shows each value. A `<figcaption>` becomes the chart title.
159
- - Series take the accent first, then every other declared `--color-*` role in sheet order except the background and surface, repeating past the last.
160
-
161
- ## Refusals
162
-
163
- | Case | Report | Exit |
164
- | -------------------------------------- | ---------------------------------------------- | ---- |
165
- | `deck.json` field malformed or unknown | `✗` naming the file and the field | 1 |
166
- | `mark` path missing | `✗` naming the resolved path | 1 |
167
- | `fonts` path missing or outside folder | `✗` naming the field and the path | 1 |
168
- | face not embeddable | `✗` naming the path and the reason | 0 |
169
- | unknown transition or entrance name | `✗` naming the slide, the element, the effect | 0 |
170
- | entrance on an element that draws none | `✗` naming the slide and the element | 0 |
171
- | chart cell not a number | `✗` naming the slide, the figure, and the cell | 0 |
172
- | `data-chart` outside the five types | `✗` naming the value | 0 |
173
- | token role not declared | `✗` naming the role | 0 |
174
- | slide body sets its own `--color-text` | `✗` naming the slides | 0 |
175
-
176
- A refused chart, effect, or face is left out and the rest of the deck is written.
@@ -1,105 +0,0 @@
1
- ---
2
- title: State-scoped risk
3
- description: Reading committed state rather than an arriving change, the shipped-tree corpus the secret scan reads, what it keys on and how a deliberate value is exempted, the advisory check and its network failure mode, and why one gates while the other reports
4
- ---
5
-
6
- # State-scoped risk
7
-
8
- Every review surface a session can reach is scoped to a change. `review-pr` reads a pull request, and `code-review` takes a diff, a branch, or a path. A risk that arrived before the range under review is invisible to both by construction, which is what these two commands answer.
9
-
10
- | Question | Command |
11
- | -------------------------------------------------------- | -------------------- |
12
- | Does a credential sit in the tree this repository ships? | `canon secrets scan` |
13
- | Does a resolved dependency carry a published advisory? | `canon deps audit` |
14
-
15
- Neither reads a range. That boundary is deliberate: change-scoped correctness review already exists, and a state-scoped bug scan would be a different product competing with it. Both register in `canon audits run`, so a reader who runs the aggregate gets them without knowing they exist.
16
-
17
- ## Secrets in the shipped tree
18
-
19
- ```bash
20
- canon secrets scan
21
- canon secrets scan --json
22
- canon secrets scan ../my-app
23
- ```
24
-
25
- The corpus is the package's own `files` field rather than a list the check keeps. That field is the single statement of which trees leave this repository, so a second list beside it would answer the same question and drift. It also carries the negations the publish already makes, which is what puts the eval tree and every test file out of scope by the rule that keeps them out of the tarball rather than by an exclusion this check invented. The sandbox tree sits under no `files` entry at all, so the same field leaves it out without a negation.
26
-
27
- The plugin reaches a target by a different route, loading live from `claude/` rather than from a tarball. That folder is a `files` entry too, so both routes land inside the same corpus.
28
-
29
- Three root files are read whether or not the field names them, being `package.json`, the readme, and the license, since npm packs those on every publish. A negation still removes one, because a field that excludes a file outranks the default that included it.
30
-
31
- ### What the corpus leaves out
32
-
33
- The corpus answers what the package publishes, which is narrower than what the repository holds. Measured against this repository on 2026-08-21, the scan read 544 files and left 593 of the 1139 git lists unread. Those include everything under `.claude/`, `wiki/`, and `internal/`, the workflow definitions under `.github/`, and the trees the publish negations remove, being `scripts/eval/` and every test file. The scenario tree was a third negation at that reading and now sits at `sandbox/` under no `files` entry, so it stays unread. `src/capture/` was a fourth negation when that reading was taken and is not one now. The folder holds 5 tracked files, 2 of them tests the publish still excludes, so the corpus is 3 wider than the numbers above describe and the unread count is 3 smaller.
34
-
35
- A public repository makes that gap readable by anyone, so a clean run means no credential in the published tree rather than none in the repository. The run states the number on every pass, including a clean one, so the bound travels with the verdict. Widening the corpus to every tracked file is a separate decision, since the row this implements puts the shipped tree first on the toolkit's rule that content leaving the repository gates harder than content that stays.
36
-
37
- ### What it keys on
38
-
39
- Every pattern matches an issued value and none of them matches a word. A scan keyed on `password`, `secret`, or `token` fires on the environment reads, the workflow inputs, and the prose that name those things, and this repository ships all three. Keying on values instead is what makes the exclusion set empty: measured across the shipped tree on 2026-08-21, 544 files produced zero findings with nothing exempted.
40
-
41
- The set covers issuer-stamped shapes, being AWS access key ids, GitHub tokens in both forms, Google API keys, Slack tokens and webhooks, Stripe live keys, Anthropic and OpenAI keys, npm tokens, and private key block headers. What it does not reach is a credential no issuer stamps recognizably, which no exclusion policy would have helped with either.
42
-
43
- A reported value is redacted to its two ends. Those are what a reader needs to find it in the file and to tell one match from another, and the middle is the part no report should carry.
44
-
45
- ### Exempting a deliberate value
46
-
47
- A line carrying a credential-shaped value on purpose takes an inline marker, either on the line itself or on the line directly above it:
48
-
49
- ```bash
50
- # canon-allow-secret: documented sample from the vendor's own reference
51
- AWS_KEY="<the sample value>"
52
- ```
53
-
54
- Only a marker naming a reason counts. A bare token reads as a line that meant to say something and did not, which is the rule `stub: true` already applies to a seed field set to anything but `true`, and honoring it would let a typo mute a finding.
55
-
56
- The exemption travels with the line rather than sitting in a path list away from it, so a reader meeting a muted match finds the reason on the spot. A path allow-list was weighed and declined: the noise it would target is word-keyed and spread past the fixture trees, so it would hide part of the noise and none of the risk.
57
-
58
- ### Exit codes and gating
59
-
60
- Exit codes are `0` when the shipped tree carries no credential-shaped value, `1` for a refusal, and `2` for at least one value found.
61
-
62
- This is the one entry in `canon audits run` that gates without a `canon gate run` stage behind it. A credential in the published tree is a fact rather than a judgment, which is the test the catalog asks any gating addition to pass, and content leaving the repository ranks above content that stays.
63
-
64
- A refusal is never a clean tree. Five reasons produce one, and each exits `1`, because zero findings over zero files reads in the report exactly like zero findings over the whole shipped tree.
65
-
66
- | Reason | What it means | In the aggregate |
67
- | ------------------ | ------------------------------------------------------------ | ---------------- |
68
- | `no-manifest` | No `package.json`, so nothing is published from this tree | absent |
69
- | `no-publish` | The manifest declares `private`, so it publishes nothing | absent |
70
- | `no-shipped-files` | The `files` field matched nothing git lists | absent |
71
- | `no-files-field` | A publish would pack the whole tree, and none of it was read | unmeasured |
72
- | `no-git` | git could not list the tree, so the corpus is unknown | unmeasured |
73
-
74
- The split turns on whether a corpus exists. The first three mean this project publishes nothing, which is where most targets installing this CLI sit, so reporting them as unmeasured would pin the aggregate at `incomplete` there forever.
75
-
76
- The last two mean a corpus exists and went unread, so neither is softened. `private` is what separates them, since a manifest with no `files` field publishes everything rather than nothing and the field alone cannot tell those apart. Declaring `private: true` is the way a project that never publishes says so.
77
-
78
- ## Advisories against the resolved dependencies
79
-
80
- ```bash
81
- canon deps audit
82
- canon deps audit --json
83
- ```
84
-
85
- The check shells the runtime's own advisory command rather than carrying an index. A vendored advisory database is a second corpus to keep current, and what this is worth is the report rather than the data.
86
-
87
- It refuses for three reasons, and each is an absence rather than a break: no `package.json`, no lockfile beside it, and no record back from the lookup. The lockfile is checked here rather than left to the underlying command, because a project whose dependencies were never resolved needs an install and not a retry, and one message naming the network would send half the readers at the wrong cause.
88
-
89
- That buys the one failure mode no other audit here carries. The command reaches a network, so an unreachable index has to be told from a tree with nothing against it. The exit code cannot separate them, since the underlying command exits non-zero both on advisories found and on a lookup that failed, so the record on stdout is what decides: output that parses is a measurement, and output that does not is a refusal under `no-record`.
90
-
91
- Exit codes are `0` when nothing is published against the resolved set, `1` for a refusal, and `2` for at least one advisory.
92
-
93
- ### Why it reports rather than gates
94
-
95
- A published advisory is a fact about the index and a judgment about this tree, since the upgrade closing it may not exist yet. A push failing on one would teach a contributor to route around the stage while nothing about the dependency has changed, which is the split every other reporting measure here rests on.
96
-
97
- The corpus is recorded as `upstream`, a third value beside `tracked` and `per-machine`. Its count moves when someone publishes rather than when someone edits here, so a retained baseline would report growth against a tree nobody touched, and the aggregate keeps it out of the record for the mirror image of the reason it keeps gitignored scratch out.
98
-
99
- The same value decides what an offline run reports. An index this machine could not reach is an ordinary absence rather than a broken checkout, so it lands as `absent` and moves no verdict. Reading it as unmeasured would pin the aggregate at `incomplete` on every machine without a network, which is a signal nobody reads after the second time they see it.
100
-
101
- ## Reading either from a skill
102
-
103
- 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 instead.
104
-
105
- The secret scan publishes `findings`, `files`, `skipped`, and `listed`, where `listed` is everything git reports so a consumer can state the bound alongside the verdict. The advisory check publishes `advisories` and a `severities` object. A refusal from either publishes `reason` and `message` and no measurement keys at all, which is what separates it from a clean run.