@journeykit/canon 0.7.0 → 0.8.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/dist/index.js +7 -2
- package/dist/skills/SKILL.src.md +46 -0
- package/dist/skills/reference/canon-artifact-ingest.md +72 -0
- package/dist/skills/reference/canon-authoring.md +155 -0
- package/dist/skills/reference/canon-context.md +137 -0
- package/dist/skills/reference/canon-fmt.md +43 -0
- package/dist/skills/reference/canon-gate.md +199 -0
- package/dist/skills/reference/canon-inventory.md +102 -0
- package/dist/skills/reference/canon-learn.md +245 -0
- package/dist/skills/reference/canon-plan-import.md +143 -0
- package/dist/skills/reference/canon-plugins.md +131 -0
- package/dist/skills/reference/canon-policy.md +119 -0
- package/dist/skills/reference/canon-report-dashboard.md +343 -0
- package/dist/skills/reference/canon-retrieve.md +123 -0
- package/dist/skills/reference/canon-session-ingest.md +214 -0
- package/dist/skills/reference/canon-storage.md +152 -0
- package/dist/skills/reference/canon-subject.md +161 -0
- package/dist/skills/reference/canon-vocab.md +115 -0
- package/dist/skills/scripts/canon-retrieve-pre-dispatch.sh +126 -0
- package/package.json +6 -5
package/dist/index.js
CHANGED
|
@@ -155,5 +155,10 @@ if (!binary) {
|
|
|
155
155
|
console.error(" build from source: cargo build --release -p canon-cli");
|
|
156
156
|
process.exit(1);
|
|
157
157
|
}
|
|
158
|
-
var
|
|
159
|
-
|
|
158
|
+
var bundledSkills = join(cliDir, "dist", "skills");
|
|
159
|
+
var checkoutSkills = join(workspaceRoot, "canon", "skills");
|
|
160
|
+
var skillsSource = existsSync(join(bundledSkills, "SKILL.src.md")) ? bundledSkills : checkoutSkills;
|
|
161
|
+
var result = spawnSync(binary, process.argv.slice(2), {
|
|
162
|
+
stdio: "inherit",
|
|
163
|
+
env: { ...process.env, CANON_SKILLS_SOURCE: skillsSource }
|
|
164
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: canon
|
|
3
|
+
description: The provider-neutral Canon companion skill for agents using the canon CLI. Routes setup, authoring, validation, storage, ingestion, learning, retrieval, reporting, and provider-specific lazy references.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Canon
|
|
7
|
+
|
|
8
|
+
Use this single skill when working with Canon in a consumer repository. Canon's CLI remains explicitly modular; choose the command that matches the operation rather than asking an implicit router to guess.
|
|
9
|
+
|
|
10
|
+
## Setup and provider projection
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
canon init
|
|
14
|
+
canon skills install # detects .claude/.codex/.omp/.pi; if none exists, installs Claude+Codex
|
|
15
|
+
canon skills install --providers=claude,codex,omp,pi
|
|
16
|
+
canon skills check # read-only drift check
|
|
17
|
+
canon skills doctor # diagnostics; never deletes user files
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The canonical bundle is read-only. Claude receives
|
|
21
|
+
`.claude/skills/canon/SKILL.md`; Codex receives the flattened
|
|
22
|
+
`.codex/skills/canon.md`; OMP receives `.omp/skills/canon/SKILL.md`; and Pi
|
|
23
|
+
receives `.pi/skills/canon/SKILL.md`. Every selected provider receives matching
|
|
24
|
+
`reference/**` and `scripts/**` sidecars under its `canon` bundle. OMP and Pi
|
|
25
|
+
projections are project-local passive skill bundles: the
|
|
26
|
+
`scripts/canon-retrieve-pre-dispatch.sh` file is available as a sidecar, not
|
|
27
|
+
installed as a native hook. Load only the reference needed for the current
|
|
28
|
+
task. `canon/skills-dev` is a separate developer-only legacy source and is
|
|
29
|
+
installed explicitly with `--source canon/skills-dev`.
|
|
30
|
+
|
|
31
|
+
## Route by task
|
|
32
|
+
|
|
33
|
+
- **Start product work / pin a durable unit:** read `reference/canon-subject.md`, then run `canon context` and `canon subject`.
|
|
34
|
+
- **Write or reorganize `.feature` specs:** read `reference/canon-authoring.md` before editing; use the scaffold.
|
|
35
|
+
- **Understand available fields/enums/policy bindings:** read `reference/canon-context.md` and `reference/canon-vocab.md`.
|
|
36
|
+
- **Capture replayable run inputs / version prompts:** read `reference/canon-context.md`, then use `canon context-pack create|show|verify` and `canon prompt register|show` with explicit repository-relative JSON manifests.
|
|
37
|
+
- **Validate or index specs:** read `reference/canon-fmt.md` and `reference/canon-inventory.md`.
|
|
38
|
+
- **Run evidence gates, flip tasks, install hooks:** read `reference/canon-gate.md`.
|
|
39
|
+
- **Ingest artifacts or sessions:** read `reference/canon-artifact-ingest.md`, `reference/canon-session-ingest.md`, or `reference/canon-plan-import.md`.
|
|
40
|
+
- **Configure storage, policy, plugins, or strategy learning:** read `reference/canon-storage.md`, `reference/canon-policy.md`, `reference/canon-plugins.md`, or `reference/canon-learn.md`.
|
|
41
|
+
- **Retrieve dispatch guidance:** read `reference/canon-retrieve.md` and load `scripts/canon-retrieve-pre-dispatch.sh` when wiring hooks.
|
|
42
|
+
- **Read generated reports and dashboards:** read `reference/canon-report-dashboard.md`.
|
|
43
|
+
|
|
44
|
+
## Safety contract
|
|
45
|
+
|
|
46
|
+
`canon format --check`, `canon skills check`, and `canon gate check` are read-only. Do not hand-edit generated reports, provenance lines, locks, or task evidence. Inspect `canon context` before authoring typed artifacts, and treat every failure class as actionable evidence rather than bypassing the gate.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# canon-artifact-ingest
|
|
2
|
+
|
|
3
|
+
`canon ingest artifacts` turns a repo's canon-native ARTIFACTS — the
|
|
4
|
+
outputs of review/divergence/task/handoff work — into the regime-keyed
|
|
5
|
+
verdict trajectories that strategy memory and the reward gate consume
|
|
6
|
+
(see `canon-learn`). One pass runs the four adapters, normalizes, derives
|
|
7
|
+
verdicts, and writes idempotently.
|
|
8
|
+
|
|
9
|
+
## `canon ingest artifacts [--repo <dir>] [--watch]`
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
canon ingest artifacts # one pass over the repo's artifacts
|
|
13
|
+
canon ingest artifacts --repo ../svc # a specific repo
|
|
14
|
+
canon ingest artifacts --watch # poll loop
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Source roots resolve from `canon.yaml`'s `artifacts.sources` config
|
|
18
|
+
(`openspec_root`, ledger/divergence/handoff roots) — generic, never a
|
|
19
|
+
hardcoded path; ordinarily the consumer repo's own roots. Unconfigured =
|
|
20
|
+
no scan for that family.
|
|
21
|
+
|
|
22
|
+
## The four adapters
|
|
23
|
+
|
|
24
|
+
Each reads one artifact family and emits a normalized event keyed by that
|
|
25
|
+
family's join key:
|
|
26
|
+
|
|
27
|
+
- **`divergence`** — parity/divergence reports → verdict keyed by the
|
|
28
|
+
lane derived from the report's on-disk path.
|
|
29
|
+
- **`ledger`** — review ledger records → verdict; the record's
|
|
30
|
+
trust-level tag (`@reviewed`/`@ratified`) rides through as a
|
|
31
|
+
passthrough `trust_level` field, not collapsed into one "success"
|
|
32
|
+
bucket. (The other three adapters carry no trust-level concept.)
|
|
33
|
+
- **`openspec_task`** — `tasks.md` checkbox rows: a `- [ ]` → `- [x] … —
|
|
34
|
+
✅ <evidence>` flip, or a `**DEFERRED**`/`**DROPPED**` rewrite, keyed by
|
|
35
|
+
`task_id` (`<change_id>#<n>`). The evidence string is parsed for a
|
|
36
|
+
mergeable PR / CI reference where present (CI-first).
|
|
37
|
+
- **`handoff`** — handoff transitions, keyed by `handoff_id`.
|
|
38
|
+
|
|
39
|
+
## Verdict derivation
|
|
40
|
+
|
|
41
|
+
Verdict derivation is a pure function from a normalized event to an
|
|
42
|
+
optional verdict `{role, polarity, becomes}`:
|
|
43
|
+
|
|
44
|
+
- A matched mapping row → a verdict for that role.
|
|
45
|
+
- No mapped row → NO verdict. A prose-only task flip, a deferred/dropped
|
|
46
|
+
task, or a handoff transition alone produces none — deliberate, not a
|
|
47
|
+
gap.
|
|
48
|
+
|
|
49
|
+
## Idempotence — a re-ingest is a no-op
|
|
50
|
+
|
|
51
|
+
Write-identity is content-digest based: the digest derives both the
|
|
52
|
+
`regime_key` `<hash>` segment and the trajectory write-identity. Before
|
|
53
|
+
writing, the driver does a regime-key existence check — a second ingest
|
|
54
|
+
over an unchanged corpus re-derives the identical id and persists zero
|
|
55
|
+
new trajectories (reported as `trajectories_skipped_duplicate`).
|
|
56
|
+
|
|
57
|
+
## Reading a role's own verdict stream
|
|
58
|
+
|
|
59
|
+
Verdicts persist regime-keyed (`<role>/<repo>/<area>/<hash>`), so a
|
|
60
|
+
role-scoped agent reads only its own lane — the same regime namespacing
|
|
61
|
+
`canon retrieve --role <r> --regime <k>` and `canon learn` query. This
|
|
62
|
+
verdict stream is the evidence the reward gate scores when deciding
|
|
63
|
+
promotion/demotion.
|
|
64
|
+
|
|
65
|
+
## What this skill does NOT cover
|
|
66
|
+
|
|
67
|
+
- Session/cost ingestion (`canon ingest sessions`) — a different
|
|
68
|
+
pipeline; see the `canon-session-ingest` skill.
|
|
69
|
+
- Strategy promotion/demotion mechanics — see the `canon-learn` skill;
|
|
70
|
+
this skill only produces the verdict evidence promotion reads.
|
|
71
|
+
- The `regime_key` grammar itself — see `canon-retrieve`, which derives
|
|
72
|
+
the same key via the `canon regime-key` serializer.
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# canon-authoring
|
|
2
|
+
|
|
3
|
+
A `.feature` corpus is the join spine everything else in canon hangs
|
|
4
|
+
off: `canon inventory sync` indexes it, `canon review`/`canon evidence`
|
|
5
|
+
attest against it, `canon gate check` measures coverage over it. A
|
|
6
|
+
corpus that is organized badly does not fail `canon format` — it
|
|
7
|
+
produces a green gate that measures the wrong thing. This skill is the
|
|
8
|
+
organizing rule.
|
|
9
|
+
|
|
10
|
+
## Vocabulary
|
|
11
|
+
|
|
12
|
+
| Term | Is | Is NOT |
|
|
13
|
+
|---|---|---|
|
|
14
|
+
| **area** | One bounded context of the product, a kebab noun (`checkout`, `world-map`, `dm`). The directory `area=<area>/`. | A sprint, a team, a theme ("experience", "quality"). |
|
|
15
|
+
| **surface** | One screen, flow, or API a user or caller actually meets (`cart`, `place-lock`, `session-create`). Exactly ONE `.feature` file per surface. | A grab-bag ("misc", "experience", "review") — see anti-patterns. |
|
|
16
|
+
| **scenario** | One observable behavior of that surface: a refusal, a transition, a boundary, a derived value, an idempotence property. Readable by someone who has never seen the code. | A function name, an implementation step, a rule about a document or a report. |
|
|
17
|
+
| **`<area>.<surface>.<nn>`** | The scenario's durable id. Assigned once, never renumbered; gaps are fine. | A sort key you tidy up. |
|
|
18
|
+
|
|
19
|
+
A `Feature:` title names the surface. If you cannot name the surface,
|
|
20
|
+
you do not have one yet.
|
|
21
|
+
|
|
22
|
+
## Lanes: what kind of requirement is this?
|
|
23
|
+
|
|
24
|
+
Every scenario is one of a small set of lanes, tagged `@lane:<value>`:
|
|
25
|
+
|
|
26
|
+
| Lane | The scenario is about | Attested by |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| `behavior` | What the product does — state, input, data, navigation, errors. The default. | A test or an observed run. |
|
|
29
|
+
| `design` | How it looks and moves — tokens, spacing, motion, easing. | A design review. |
|
|
30
|
+
| `architecture` | How the code is shaped — layering, ownership, error surfacing. | An architecture review. |
|
|
31
|
+
| `process` | How the TEAM works — what a review report may claim, how acceptance is run, what a build must check. | Nothing in the product. |
|
|
32
|
+
|
|
33
|
+
`process` rules are real, but they have no code surface and can never
|
|
34
|
+
be `faithful` from a test. Keep them out of the coverage number:
|
|
35
|
+
|
|
36
|
+
```yaml
|
|
37
|
+
# .canon/policy.yaml
|
|
38
|
+
spec_coverage:
|
|
39
|
+
require_evidence: true
|
|
40
|
+
scope: [building, verifying]
|
|
41
|
+
exclude_lanes: [process]
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The base set comes from `canon.core`; a repo declares its own `lane`
|
|
45
|
+
enum in `.canon/vocab/<id>/enums.yaml` to replace it (see
|
|
46
|
+
`canon-vocab`). `canon context` shows the set in force. A value outside
|
|
47
|
+
a declared set is refused at the scaffold and dropped, with a counted
|
|
48
|
+
diagnostic, at `canon inventory sync`.
|
|
49
|
+
|
|
50
|
+
**The test for a `behavior` scenario:** name the test or run that would
|
|
51
|
+
attest it. If you can't, it is a `process` rule, or it is a gap — record
|
|
52
|
+
the gap, do not write the scenario.
|
|
53
|
+
|
|
54
|
+
## Subject pinning
|
|
55
|
+
|
|
56
|
+
Every scenario belongs to a Subject (the durable product unit, see
|
|
57
|
+
`canon-subject`). Write the Subject first, then pin with `--subject`;
|
|
58
|
+
`canon inventory sync` carries the tag onto the index record and
|
|
59
|
+
`spec_coverage.scope` filters on it. An unpinned scenario is invisible to
|
|
60
|
+
a scoped gate.
|
|
61
|
+
|
|
62
|
+
## The commands — and the one thing you never type
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
canon feature new checkout.cart --title "Cart" # once per surface
|
|
66
|
+
canon scenario new checkout.cart.01 --title "Adding an item raises the count" \
|
|
67
|
+
--subject checkout-core --lane behavior # once per behavior
|
|
68
|
+
canon format specs # clean?
|
|
69
|
+
canon inventory sync # index it
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
- `--actor <id>` (or `CANON_ACTOR` in the environment) puts YOUR id on the
|
|
73
|
+
provenance line. Default is `canon-scaffold`.
|
|
74
|
+
- **Never hand-type, move, or edit a `# canon:` line.** The scaffold
|
|
75
|
+
writes it directly above the scenario's tags:
|
|
76
|
+
|
|
77
|
+
```gherkin
|
|
78
|
+
# canon: {"schema":1,"at":"2026-09-16T10:02:11Z","actor":{"agent_id":"Main"}}
|
|
79
|
+
@subject:checkout-core
|
|
80
|
+
@lane:behavior
|
|
81
|
+
@checkout.cart.01
|
|
82
|
+
Scenario: Adding an item raises the count
|
|
83
|
+
Given an empty cart
|
|
84
|
+
When one item is added
|
|
85
|
+
Then the count reads 1
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
A line directly UNDER the header (older corpora) is also accepted.
|
|
89
|
+
Anywhere else — separated by a blank line, above `Feature:` with
|
|
90
|
+
scenario tags in between — is `missing-provenance`, and the whole
|
|
91
|
+
root's sync aborts.
|
|
92
|
+
- Steps are yours to write; the scaffold's `Given a step` is a
|
|
93
|
+
placeholder, not a spec.
|
|
94
|
+
|
|
95
|
+
## Anti-patterns, by name
|
|
96
|
+
|
|
97
|
+
- **The grab-bag surface.** One `experience.feature` holding pointer
|
|
98
|
+
capture, i18n fallback, a compiler tag rule, and "the review report
|
|
99
|
+
must not claim video quality". Four surfaces and a process rule. Split
|
|
100
|
+
by what the user meets; move the rule to `lane:process`.
|
|
101
|
+
- **A Then about a document.** "Then the report distinguishes X from Y."
|
|
102
|
+
The product has no report. `lane:process`, or `policy.yaml`.
|
|
103
|
+
- **A Feature title that is a theme.** "Fidelity of the original
|
|
104
|
+
experience" names no surface. Name the screen.
|
|
105
|
+
- **Hand-stamped provenance.** Eighty lines with one timestamp and the
|
|
106
|
+
same `agent_id`. Use `canon scenario new --actor`.
|
|
107
|
+
- **Coverage by splitting.** One behavior as two scenarios so two rows go
|
|
108
|
+
green. One behavior, one id.
|
|
109
|
+
- **Runner tags as classification.** `@design`, `@p2`, `@wip` are
|
|
110
|
+
invisible to canon — they index nothing. Only `@lane:` and `@subject:`
|
|
111
|
+
are read; any other `@name:value` namespace is counted as a diagnostic
|
|
112
|
+
at sync so you can see it was dropped.
|
|
113
|
+
|
|
114
|
+
## Worked example
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
specs/features/kind=feature/area=checkout/
|
|
118
|
+
cart.feature # surface: the cart screen
|
|
119
|
+
payment.feature # surface: the payment step
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
```gherkin
|
|
123
|
+
Feature: Cart
|
|
124
|
+
# canon: {"schema":1,"at":"2026-09-16T10:02:11Z","actor":{"agent_id":"Main"}}
|
|
125
|
+
|
|
126
|
+
# canon: {"schema":1,"at":"2026-09-16T10:02:11Z","actor":{"agent_id":"Main"}}
|
|
127
|
+
@subject:checkout-core
|
|
128
|
+
@lane:behavior
|
|
129
|
+
@checkout.cart.01
|
|
130
|
+
Scenario: Adding an item raises the count
|
|
131
|
+
Given an empty cart
|
|
132
|
+
When one item is added
|
|
133
|
+
Then the count reads 1
|
|
134
|
+
|
|
135
|
+
# canon: {"schema":1,"at":"2026-09-16T10:02:40Z","actor":{"agent_id":"Main"}}
|
|
136
|
+
@subject:checkout-core
|
|
137
|
+
@lane:design
|
|
138
|
+
@checkout.cart.02
|
|
139
|
+
Scenario: The count badge uses the accent token and a 120ms ease-out
|
|
140
|
+
Given a cart with one item
|
|
141
|
+
When a second item is added
|
|
142
|
+
Then the badge animates over 120ms with ease-out in the accent color
|
|
143
|
+
|
|
144
|
+
# canon: {"schema":1,"at":"2026-09-16T10:03:02Z","actor":{"agent_id":"Main"}}
|
|
145
|
+
@subject:checkout-core
|
|
146
|
+
@lane:process
|
|
147
|
+
@checkout.cart.03
|
|
148
|
+
Scenario: A cart review names the device it was run on
|
|
149
|
+
Given a cart design review
|
|
150
|
+
When the report is written
|
|
151
|
+
Then it names the device and OS the review ran on
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
`.03` is excluded from coverage by `exclude_lanes: [process]`; `.01` is
|
|
155
|
+
attested by a test, `.02` by a design review.
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# canon-context
|
|
2
|
+
|
|
3
|
+
`canon context` is a capability QUERY over the same schema/policy/
|
|
4
|
+
vocabulary registry `canon format`/`canon gate` validate against — never
|
|
5
|
+
validation itself. It answers "what CAN I author in this repo, and how?":
|
|
6
|
+
record kinds + their envelope fields, enum domains, join-key grammars,
|
|
7
|
+
partition layout, policy-derived evidence requirements, the typed
|
|
8
|
+
authoring vocabulary, and the CEL binding surface — folded into one
|
|
9
|
+
deterministic authoring surface.
|
|
10
|
+
|
|
11
|
+
## `canon context [--repo <dir>] [--json]`
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
canon context # compact human outline (prompt-injectable)
|
|
15
|
+
canon context --json # the full machine-readable surface
|
|
16
|
+
canon context --repo ../other # resolve a specific repo's surface
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Reproducible context packs and prompt bundles
|
|
20
|
+
|
|
21
|
+
Use an explicit, repository-relative JSON manifest when a run needs a
|
|
22
|
+
replayable snapshot. The manifest selects files; Canon copies their bytes into
|
|
23
|
+
an immutable content-addressed registry and records only metadata and digests
|
|
24
|
+
in the pack manifest.
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
canon context-pack create --manifest .canon/context-manifest.json
|
|
28
|
+
canon context-pack create --manifest .canon/context-manifest.json --repo ../my-repo --json
|
|
29
|
+
canon context-pack show sha256:<pack-digest> --repo . --json
|
|
30
|
+
canon context-pack verify sha256:<pack-digest>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`create` prints only the pack id by default, so captured content is never
|
|
34
|
+
written to concise terminal output. `--json` prints the pack manifest,
|
|
35
|
+
including selected-path metadata and content digests. `show` verifies the
|
|
36
|
+
manifest and immutable content objects before returning it; `verify` exits
|
|
37
|
+
nonzero when the pack is missing, stale, or tampered. Manifest paths must be
|
|
38
|
+
relative to the resolved repo, remain inside it, and contain no symlink;
|
|
39
|
+
missing or unsafe paths are rejected by the context-pack module.
|
|
40
|
+
|
|
41
|
+
## Provider-neutral adapter responses
|
|
42
|
+
|
|
43
|
+
An execution provider may return a versioned response envelope for Canon to
|
|
44
|
+
validate. Validation is read-only: `canon` never starts a provider, interprets
|
|
45
|
+
provider-specific extension payloads, or enforces declared capabilities.
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
canon adapter validate --response response.json
|
|
49
|
+
canon adapter validate --response response.json --repo . --json
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The envelope has `protocol_version: 1`, required `run_id`, `provider`,
|
|
53
|
+
`model`, and `context_pack_id`, a closed status vocabulary
|
|
54
|
+
(`succeeded`/`failed`/`aborted`), and strict filesystem/network/secrets
|
|
55
|
+
capability vocabularies. Evidence references are safe, relative references
|
|
56
|
+
with `sha256:<64-hex>` digests. Telemetry timestamps must be RFC3339 with
|
|
57
|
+
`ended_at >= started_at`, and counts/cost are nonnegative.
|
|
58
|
+
|
|
59
|
+
Core fields are closed and unknown fields are rejected. Provider-specific JSON
|
|
60
|
+
belongs only under opaque `extensions`; successful JSON output contains the
|
|
61
|
+
normalized core summary and extension key names, never extension payloads.
|
|
62
|
+
`--repo` can verify the referenced context pack; file-only validation reports
|
|
63
|
+
`context_join_verified: null`. A valid declaration is not a sandbox:
|
|
64
|
+
providers MUST enforce filesystem, network, and secret capabilities before
|
|
65
|
+
execution.
|
|
66
|
+
|
|
67
|
+
Version prompt inputs separately when several runs should share the same
|
|
68
|
+
prompt files:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
canon prompt register --name reviewer --version v1 \
|
|
72
|
+
--manifest .canon/reviewer-prompt.json
|
|
73
|
+
canon prompt show --name reviewer --version v1 --json
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Prompt registration uses the same immutable content registry. `prompt show`
|
|
77
|
+
verifies the selected bundle and returns its metadata/digests; neither concise
|
|
78
|
+
output form includes captured prompt bytes.
|
|
79
|
+
|
|
80
|
+
- **A capability query, never validation:** `canon context` ALWAYS exits
|
|
81
|
+
`0` with the full surface — even when `canon format --check` or
|
|
82
|
+
`canon gate` would report diagnostics against the same repo. It reads
|
|
83
|
+
only the schema/policy/vocabulary registry, never the corpus or the
|
|
84
|
+
evidence ledger.
|
|
85
|
+
- `--repo` resolves through the same nearest-`canon.yaml`-ancestor walk
|
|
86
|
+
`canon format`/`canon gate`/`canon retrieve` use: omitted or `.` walks up
|
|
87
|
+
from cwd to the project root; any other explicit dir is used as-is.
|
|
88
|
+
- `--json` renders the full surface; the default renders a compact per-
|
|
89
|
+
section outline. Both project from the identical resolution (byte-stable
|
|
90
|
+
across runs).
|
|
91
|
+
|
|
92
|
+
## The surface sections
|
|
93
|
+
|
|
94
|
+
- **`kinds`** — each record kind's `{schema_version, envelope_fields,
|
|
95
|
+
partition}`.
|
|
96
|
+
- **`enums`** — each enum domain's members (verdicts, statuses, lanes,
|
|
97
|
+
roles, polarity, …).
|
|
98
|
+
- **`joinKeys`** — the join-spine grammar string per key (`regime_key`,
|
|
99
|
+
`session_id`, `task_id`, …).
|
|
100
|
+
- **`policy`** — the resolved `policy.yaml`-derived evidence/trust
|
|
101
|
+
requirements per kind.
|
|
102
|
+
- **`vocab`** — the typed authoring vocabulary (directive/enum/evidence-
|
|
103
|
+
kind index) + its content-hash version.
|
|
104
|
+
- **`cel`** — the per-kind CEL binding surface: the bindable
|
|
105
|
+
`record.<field>` names + types and the callable function allowlist a
|
|
106
|
+
`policy.yaml` `applies_when:`/predicate may reference. The agent-facing
|
|
107
|
+
answer to "what can a CEL predicate for this kind reference?"
|
|
108
|
+
|
|
109
|
+
## Single source of truth
|
|
110
|
+
|
|
111
|
+
`canon context` never re-derives a schema/policy/enum list of its own — it
|
|
112
|
+
resolves through the same shared schema, policy, and vocabulary loaders
|
|
113
|
+
the validators use. A schema edit propagates to `canon context`'s output
|
|
114
|
+
AND `canon format`'s diagnostics from one edit site, never two.
|
|
115
|
+
|
|
116
|
+
## Reading an "expected one of: …" validator error
|
|
117
|
+
|
|
118
|
+
When `canon format --check` rejects an out-of-domain enum value it emits:
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
`not-a-real-kind` is not a valid value for `kind` of `run` (expected one of: run, review, divergence, …)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The `expected one of:` member list is the SAME enum domain `canon context
|
|
125
|
+
--json`'s `enums.<field>` reports for that kind — so to fix the artifact,
|
|
126
|
+
pick a member `canon context` lists. Both read the member list off the
|
|
127
|
+
same schema, no hand-maintained second copy.
|
|
128
|
+
|
|
129
|
+
## What this skill does NOT cover
|
|
130
|
+
|
|
131
|
+
- `canon format --check`'s full violation taxonomy — see `canon-fmt`;
|
|
132
|
+
this skill only covers reading its enum diagnostic against the surface.
|
|
133
|
+
- Authoring a typed task atom / handoff body — see
|
|
134
|
+
`canon-vocab`; `canon context`'s `vocab`/`cel` sections
|
|
135
|
+
tell you what's declarable, not how to declare it.
|
|
136
|
+
- `policy.yaml` CEL authoring semantics — see `canon-policy`; this skill
|
|
137
|
+
only surfaces the bindable identifier/function set per kind.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# canon-fmt
|
|
2
|
+
|
|
3
|
+
`canon format --check` is canon's read-only format validator over a
|
|
4
|
+
consumer-repo artifact corpus: ledger records (run/drill/review/clear/
|
|
5
|
+
code-review/design-review), divergence events (manifest/review/
|
|
6
|
+
remediation), `features/`, `inventory/`, and `policy.yaml`. It never
|
|
7
|
+
rewrites or mutates the corpus — it only validates and reports.
|
|
8
|
+
|
|
9
|
+
## Running
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
canon format --check <corpus-root> # e.g. a consumer repo's spec/
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Read-only, always. Exits nonzero if any violation is found, and prints
|
|
16
|
+
a report grouped by failure class in this fixed order:
|
|
17
|
+
|
|
18
|
+
| Failure class | Meaning | Fix |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| `layout-grammar` | Wrong Hive path shape — a `features/`/`inventory/` file not under a `kind=<kind>/area=<area>/` prefix, a wrong leaf filename, or `assets.lock`'s ad-hoc format. | Move the file under the correct `kind=…/area=…/` prefix with the expected leaf name. |
|
|
21
|
+
| `missing-envelope` | A YAML file (`inventory/`, `policy.yaml`) has no top-level `schema`/`kind`/`at`/`actor` keys. | Add the four envelope keys at the top level. |
|
|
22
|
+
| `missing-provenance` | A `.feature` file's `Feature:`/`Scenario:` header has no `# canon: {...}` comment either directly above it (with its tags, no blank line) or directly below it. | Use `canon scenario new` / `canon feature new`, which stamp it; never hand-type the line. See `canon-authoring`. |
|
|
23
|
+
| `missing-actor` | No structured `actor` object — a bare `by` string, or absent. | Replace `by: <name>` with a structured `actor` object. |
|
|
24
|
+
| `unspecified-evidence` | A ledger `run`/`drill` record's `evidence` field is absent or an empty, untyped array. | Add a typed `evidence` array. |
|
|
25
|
+
| `free-text-ref` | `upstream_ref`/`port_ref` has a segment not matching `<file>#<symbol>[:<a>-<b>]`. | Rewrite the ref into the `<file>#<symbol>[:<a>-<b>]` grammar. |
|
|
26
|
+
| `joined-ref` | `upstream_ref`/`port_ref` is `;`- or `,`-joined but not split into a structured `refs` array. | Split into a `refs:` array, one entry per ref. |
|
|
27
|
+
| `abbreviated-sha` | `app_sha`/`harness_sha` is present but shorter than the full 40-hex form. | Use the full 40-character SHA. |
|
|
28
|
+
| `one-way-backref` | A divergence review event's `ledger_ref` has no reciprocal `divergence_refs` entry on the ledger record it names. | Add the matching `divergence_refs` entry to that ledger record. |
|
|
29
|
+
| `missing-join-identity` | Corpus-wide, one line: no ledger record carries `change_id`/`task_id`. | Standing note only — new records carry these going forward; historical backfill is out of scope. |
|
|
30
|
+
|
|
31
|
+
Every class is a stable string, safe to grep/diff across runs.
|
|
32
|
+
|
|
33
|
+
## What it does NOT do
|
|
34
|
+
|
|
35
|
+
- **Rewrite anything.** It only validates and reports; a one-shot
|
|
36
|
+
migration of an existing corpus is a separate, per-repo concern.
|
|
37
|
+
- **Backfill `change_id`/`task_id` on historical records.** Both stay
|
|
38
|
+
absent on records that predate the schema; `missing-join-identity` is
|
|
39
|
+
a standing corpus-level note, not a per-record defect it fixes.
|
|
40
|
+
- **Guess a `{file, symbol}` from free prose.** A non-conforming ref is
|
|
41
|
+
reported as `free-text-ref` exactly as found, never fabricated.
|
|
42
|
+
- **Touch a consumer repo's CI gates.** Conforming a corpus and
|
|
43
|
+
reconciling its own tooling is that repo's own operational step.
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# canon-gate
|
|
2
|
+
|
|
3
|
+
`canon gate` is canon's trust spine: a two-layer evidence gate over a
|
|
4
|
+
repo's artifact corpus. It answers two DIFFERENT questions per artifact —
|
|
5
|
+
"does required evidence exist" (coverage) and "did the evidence pass, by
|
|
6
|
+
whom, how stale" (verdict-ledger) — never collapsing them into one
|
|
7
|
+
"done" boolean.
|
|
8
|
+
|
|
9
|
+
## The trust ladder
|
|
10
|
+
|
|
11
|
+
Each artifact climbs a trust ladder as evidence accrues: an unreviewed
|
|
12
|
+
record is weaker than a reviewed one, which is weaker than a ratified
|
|
13
|
+
one. `policy.yaml` sets a `trust_required` level per artifact class; the
|
|
14
|
+
release-scoped check (below) enforces it. A human-only `flagged` overlay
|
|
15
|
+
overrides the ladder — a flagged artifact is never green regardless of
|
|
16
|
+
evidence. (Author `policy.yaml`'s trust/staleness fields via `canon-policy`.)
|
|
17
|
+
|
|
18
|
+
## The eight failure classes
|
|
19
|
+
|
|
20
|
+
Every violation carries one of these stable, grep-able strings:
|
|
21
|
+
|
|
22
|
+
| Class | Meaning |
|
|
23
|
+
|---|---|
|
|
24
|
+
| `uncovered-cell` | Either a policy-required evidence cell (role × artifact) with no matching record, or — with `spec_coverage` enabled — a spec scenario that is unimplemented or mismatched. The detail string distinguishes them. Coverage means "a test exists", not "a test passed": even a `Divergent` verdict satisfies the role-cell form. |
|
|
25
|
+
| `unreviewed-promotion` | An artifact tagged `reviewed` has no matching ledger review record. |
|
|
26
|
+
| `trust-below-required` | Achieved trust level is below `policy.yaml`'s `trust_required` for its class — RELEASE-scoped only (`canon gate check --release`); never fires on an ordinary run. |
|
|
27
|
+
| `stale-evidence` | A passing record degraded to stale: its declared surface changed since its `evidence_sha`, or HEAD moved past `staleness.max_commits_behind`. Only degrades an already-green record. |
|
|
28
|
+
| `malformed-evidence` | A candidate record doesn't parse, is misfiled, or carries an unparseable interim tag. Malformed evidence is no evidence. |
|
|
29
|
+
| `flagged` | The human-only `flagged` overlay is set — never green regardless of passing evidence. |
|
|
30
|
+
| `unevidenced-flip` | `canon gate task <task_id>` was asked to flip a checkbox with no matching, non-`Divergent` evidence record. |
|
|
31
|
+
| `fabricated-evidence` | An evidence note contains a blocklisted marker (`"would pass"`, `"TBD"`, `"n/a"`) or a bare `verified` claim with no attached command result. |
|
|
32
|
+
|
|
33
|
+
## `canon gate check [--repo <dir>] [--release]`
|
|
34
|
+
|
|
35
|
+
Assembles coverage + ledger + staleness + the always-on trust-ladder
|
|
36
|
+
check, plus the opt-in spec-coverage check, over the resolved repo's
|
|
37
|
+
corpus and runs them, printing every violation grouped by failure class.
|
|
38
|
+
`--release` additionally engages the release-scoped
|
|
39
|
+
`trust-below-required` check; the trust-ladder check is never dropped
|
|
40
|
+
when `--release` is passed.
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
canon gate check --repo . # ordinary evaluation
|
|
44
|
+
canon gate check --repo . --release # + trust-below-required
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Exit `0` clean, `1` gate-red (any violation), `2` usage/load failure
|
|
48
|
+
(unreadable ledger, corrupt `canon.yaml`). `--repo` (or its omission)
|
|
49
|
+
resolves through the nearest-ancestor `canon.yaml` walk, so it reads the
|
|
50
|
+
repo ROOT's `.canon/policy.yaml` and `.canon/ledger` from any subdirectory.
|
|
51
|
+
|
|
52
|
+
### The spec-derived worklist (`spec_coverage`, opt-in)
|
|
53
|
+
|
|
54
|
+
Coverage groups the EVIDENCE corpus, so it can only ever enumerate
|
|
55
|
+
artifacts that already have evidence. A `.feature` scenario nobody has
|
|
56
|
+
attested to appears in no group and is invisible to it. `spec_coverage`
|
|
57
|
+
adds the other direction: it starts from the Scenario corpus
|
|
58
|
+
`canon inventory sync` materializes and left-joins evidence onto it, so
|
|
59
|
+
an unimplemented or mismatched spec becomes a reported violation.
|
|
60
|
+
|
|
61
|
+
```yaml
|
|
62
|
+
# .canon/policy.yaml — ABSENT by default; absent means zero violations
|
|
63
|
+
spec_coverage:
|
|
64
|
+
require_evidence: true
|
|
65
|
+
scope: [building, verifying] # optional; omit for the whole corpus
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
- **Unimplemented** — no `EvidenceRecord` carries the scenario's
|
|
69
|
+
`(project_id, scenario_id)`. Author one with
|
|
70
|
+
`canon evidence add --scenario-id <id> --project-id <root-id>`; both
|
|
71
|
+
flags are required together, because two spec roots may carry the same
|
|
72
|
+
scenario id.
|
|
73
|
+
- **Mismatched** — the folded divergence state is `open`,
|
|
74
|
+
`still-divergent`, or `resolved-invalid` (a resolution whose app sha
|
|
75
|
+
has moved), or the latest ledger verdict is `divergent`.
|
|
76
|
+
- **`scope`** narrows blocking to scenarios whose `@subject:<id>` tag
|
|
77
|
+
links them to a Subject in one of the named statuses. With a `scope`
|
|
78
|
+
set, an untagged scenario is out of scope; with `scope` omitted, every
|
|
79
|
+
scenario is in scope. A tag naming no Subject record is reported as a
|
|
80
|
+
dangling link, never silently skipped.
|
|
81
|
+
|
|
82
|
+
Two refusals rather than a silent pass, both surfacing as
|
|
83
|
+
`uncovered-cell` on the subject `spec_coverage`:
|
|
84
|
+
|
|
85
|
+
- A **malformed section** (unknown key, unknown `scope` status) refuses
|
|
86
|
+
at check time. It is never treated as absent — a typo must not be
|
|
87
|
+
silently equivalent to not opting in.
|
|
88
|
+
- A **corpus kind routed off the gate's rung** (`scenario`,
|
|
89
|
+
`divergence`, or `subject` sent anywhere but `local`) refuses, because
|
|
90
|
+
the gate reads one tier and would otherwise pass by seeing nothing.
|
|
91
|
+
|
|
92
|
+
Malformed rows in those three kinds do NOT surface as
|
|
93
|
+
`malformed-evidence`; they are kept off the evidence violation set on
|
|
94
|
+
purpose, so enabling nothing changes no existing verdict.
|
|
95
|
+
|
|
96
|
+
## Effect-aware risk approvals (`risk_tiers`)
|
|
97
|
+
|
|
98
|
+
`risk_tiers` is absent by default and remains a no-op when absent. When it
|
|
99
|
+
is present, `canon gate check` selects the highest-ranked configured tier
|
|
100
|
+
whose current evidence binding matches a path or exact `effect:<kebab-slug>`.
|
|
101
|
+
The current generation is selected deterministically; approvals from an
|
|
102
|
+
older `evidence_sha`, a non-`faithful` verdict, or an unverified attestation
|
|
103
|
+
never count. A required tier also fails closed when the current record has
|
|
104
|
+
no usable `surface_ref`/effect binding, so a caller cannot suppress a
|
|
105
|
+
configured rule by declaring a safe path.
|
|
106
|
+
|
|
107
|
+
`canon evidence add --surface-ref src/auth/login.rs` (repeatable) or
|
|
108
|
+
`--surface-ref effect:secret-access` persists an explicitly validated
|
|
109
|
+
binding. This is still attestation input, not proof of the changed paths;
|
|
110
|
+
run/diff metadata is authoritative when available, and an unavailable
|
|
111
|
+
binding is a gate violation rather than a clean result.
|
|
112
|
+
|
|
113
|
+
Approval identity is separate from attestation text. `--approval-by` is
|
|
114
|
+
accepted as a verified approval only when it equals a non-empty
|
|
115
|
+
`CANON_ACTOR` environment identity and `--approval-role human` is supplied.
|
|
116
|
+
The record persists that verification bit for replay; arbitrary caller
|
|
117
|
+
strings, empty identities, and `agent` roles never satisfy a risk tier.
|
|
118
|
+
Malformed `risk_tiers` sections produce a stable `uncovered-cell` policy
|
|
119
|
+
violation (`risk_tiers policy-invalid: ...`) instead of degrading to an
|
|
120
|
+
empty clean map.
|
|
121
|
+
|
|
122
|
+
## `canon gate task <task_id> [--repo <dir>]`
|
|
123
|
+
|
|
124
|
+
The evidence-gated task checkbox flip. Resolves `<task_id>`
|
|
125
|
+
(`<change_id>#<n>`) through `canon.yaml`'s `plans:` sources — the first
|
|
126
|
+
configured dialect whose `PlanWriteBack` locates the task's document
|
|
127
|
+
wins, so the flip is plan-dialect agnostic (`openspec` change dirs,
|
|
128
|
+
`superpowers` plan docs). A repo with no `plans:` section falls back to
|
|
129
|
+
the compat default `[{ dialect: openspec, root: <repo> }]`; no source
|
|
130
|
+
locating the task at all is a loud failure naming every source consulted.
|
|
131
|
+
It requires
|
|
132
|
+
a matching non-`Divergent` evidence record, and flips `- [ ]` → `- [x]`
|
|
133
|
+
with an appended evidence note ONLY on a clean check. Every other path —
|
|
134
|
+
missing evidence, a `Divergent` verdict, a fabricated note — leaves the
|
|
135
|
+
row byte-unchanged and exits `1` with the blocking violation on stderr.
|
|
136
|
+
An already-`[x]` row is an idempotent no-op (exit `0`). An unknown
|
|
137
|
+
`task_id` is reported (exit `1`).
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
canon gate task my-change#5.2 --repo .
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
This is canon's own authority for the checkbox grammar — always flip
|
|
144
|
+
through `canon gate task`, never by hand.
|
|
145
|
+
|
|
146
|
+
## `canon gate promote [--repo <dir>] [--dry-run]`
|
|
147
|
+
|
|
148
|
+
Staging → committed: every well-formed record under
|
|
149
|
+
`.canon/ledger/_staging/` is re-validated with the SAME checks the gate
|
|
150
|
+
applies, assigned a monotonic per-(role, surface) `run_seq` (gap-free
|
|
151
|
+
within one invocation, continuing from the committed max), and moved into
|
|
152
|
+
the committed ledger. A malformed or unpartitionable candidate is refused
|
|
153
|
+
— exit `1`, no `run_seq` consumed, the file left in place. `--dry-run`
|
|
154
|
+
prints the plan (target path + assigned `run_seq` per candidate) without
|
|
155
|
+
writing or deleting.
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
canon gate promote --repo . # land every staged candidate
|
|
159
|
+
canon gate promote --repo . --dry-run # preview only
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## `canon gate install-hooks [--repo] [--event] [--matcher] [--command] [--timeout]`
|
|
163
|
+
|
|
164
|
+
Idempotent, diff-only hook-seam installation: merges one
|
|
165
|
+
`{matcher?, hooks: [{type: "command", command, timeout}]}` entry into
|
|
166
|
+
BOTH `<repo>/.claude/settings.json` and `<repo>/.codex/hooks.json` —
|
|
167
|
+
additive only, never touching an existing entry in the same matcher
|
|
168
|
+
group. Running it twice with no manual edits between reports "no diff"
|
|
169
|
+
and writes nothing. When neither file already carries a `canon
|
|
170
|
+
gate`-invoking command, it ALSO emits
|
|
171
|
+
`<repo>/.canon/scripts/canon-gate-pre-commit.sh` (advisory by default — set
|
|
172
|
+
`CANON_GATE_ADVISORY=0` to make it block the commit on a failing gate).
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
canon gate install-hooks --repo .
|
|
176
|
+
# non-default wiring:
|
|
177
|
+
canon gate install-hooks --repo . --event PreToolUse --matcher Edit --command "canon gate check" --timeout 30
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Prefer this over hand-editing `settings.json`/`hooks.json`.
|
|
181
|
+
|
|
182
|
+
## `canon gate selftest`
|
|
183
|
+
|
|
184
|
+
Runs the shipped fixture corpus — one fixture per failure class, each a
|
|
185
|
+
deliberately broken corpus proving that class fires and ONLY that class
|
|
186
|
+
(both under-detection and over-triggering fail the run). Takes no
|
|
187
|
+
`--repo`; self-contained. Run it before trusting any other `canon gate
|
|
188
|
+
check` run's green.
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
canon gate selftest
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Reading a violation
|
|
195
|
+
|
|
196
|
+
Every printed line is `<failure-class> <subject> — <detail>`. `<subject>`
|
|
197
|
+
is the artifact's own join identity (`task_id` preferred, then
|
|
198
|
+
`scenario_id`, then `run_id`) — grep the ledger or `tasks.md` for it
|
|
199
|
+
directly, never guess.
|