@journeykit/canon 0.24.1 → 0.25.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/skills/SKILL.src.md +23 -5
- package/dist/skills/reference/brief-harden.md +2 -0
- package/dist/skills/reference/brief-shape.md +1 -0
- package/dist/skills/reference/canon-gate.md +3 -2
- package/dist/skills/reference/canon-report-dashboard.md +26 -5
- package/dist/skills/reference/canon-review.md +14 -2
- package/dist/skills/reference/canon-storage.md +1 -1
- package/dist/skills/reference/canon-subject.md +54 -3
- package/dist/skills/reference/canon-vocab.md +4 -0
- package/dist/skills/reference/role-implementer.md +28 -3
- package/dist/skills/reference/role-orchestrator.md +54 -1
- package/dist/skills/reference/role-planner.md +43 -3
- package/dist/skills/reference/role-reviewer.md +38 -5
- package/package.json +4 -4
package/dist/skills/SKILL.src.md
CHANGED
|
@@ -23,7 +23,8 @@ a feature, a one-line request included. A human is needed once per scope:
|
|
|
23
23
|
the one thing you never assume, even when the human said they cannot
|
|
24
24
|
answer until you report: the brief is that report. The one answer
|
|
25
25
|
approves the brief and the policy, `.canon/policy.yaml`, which is what
|
|
26
|
-
the gate grades the work against.
|
|
26
|
+
the gate grades the work against. The human may also approve it on the `canon dashboard` briefs page; you never use that form.
|
|
27
|
+
Record it only from the human's answer:
|
|
27
28
|
`canon brief approve --change-id <slug> --by <the human> --note "<their words>"`.
|
|
28
29
|
Never edit the policy to make the gate pass: a policy that differs from
|
|
29
30
|
the approved one fails closed (`POLICY_CHANGED_SINCE_APPROVAL`, not
|
|
@@ -31,6 +32,16 @@ a feature, a one-line request included. A human is needed once per scope:
|
|
|
31
32
|
approval is not approved (`BRIEF_CHANGED_SINCE_APPROVAL`): it needs a new
|
|
32
33
|
answer. The ship guard and `canon evidence add` refuse an unapproved or
|
|
33
34
|
edited brief, but those are backstops: the stop itself is at step 1.
|
|
35
|
+
- **Approval is scoped.** Evidence for a scenario needs the approval of the
|
|
36
|
+
changes its `@flow:<change>.<F-id>` tags name, so a new unapproved change
|
|
37
|
+
on a live subject waits on its own scenarios only; `change new` and
|
|
38
|
+
`subject adopt` warn when the subject is live. `canon subject unadopt
|
|
39
|
+
<change> --subject-id <id>` takes a draft change back off (refused once
|
|
40
|
+
approved). The subject's scope is its newest adopted change, walking back
|
|
41
|
+
past pending drafts (brief not approved, no scenario of the subject tagged
|
|
42
|
+
`@flow:<change>.*`): reopen, evidence and ship read the newest approved or
|
|
43
|
+
tagged change, and the transition prints one line naming every draft set
|
|
44
|
+
aside; a draft a scenario is tagged for blocks until it is approved. → `reference/canon-subject.md`
|
|
34
45
|
- **Inside an approved scope, never stop for a human.** Fixes, follow-ups,
|
|
35
46
|
review rounds and re-reviews stay in the approved change (never
|
|
36
47
|
`canon change new`: a new change is a new scope) and go straight on: scenario,
|
|
@@ -50,7 +61,7 @@ a feature, a one-line request included. A human is needed once per scope:
|
|
|
50
61
|
1. **Brief.** The planner writes the PRD brief (`.canon/changes/<slug>/brief.md`), checks it with `canon brief check`, and stops for the human. → `reference/role-planner.md`
|
|
51
62
|
2. **Subject.** `canon subject new`, then `canon change new` (`--kind chore` for work that alters no behavior); the change writes the brief skeleton. → `reference/role-planner.md`
|
|
52
63
|
3. **Scenarios.** `canon scenario new --flow <F-id> --case <case>` per behavior of a flow step, failure paths (`--case failure`) included. → `reference/role-implementer.md`
|
|
53
|
-
4. **Units.** Split the work; each unit has its own actor id and session id, passed on everything it records. A unit never attests another unit's work.
|
|
64
|
+
4. **Units.** Split the work; one unit is one flow step's slice with a named verify command, and each unit has its own actor id and session id, passed on everything it records. A unit never attests another unit's work.
|
|
54
65
|
5. **Implement.** Code with tests named by scenario id (`game.run.01: …`). → `reference/role-implementer.md`
|
|
55
66
|
6. **Evidence.** `canon evidence add` binds the test run to its scenarios. → `reference/role-implementer.md`
|
|
56
67
|
7. **Independent review.** A different session, a separate process with its own session id, that can run the checks it signs off, records `canon finding add` and `canon review add`. Every change. → `reference/role-reviewer.md`
|
|
@@ -135,10 +146,10 @@ Load only the reference the current step needs.
|
|
|
135
146
|
```bash
|
|
136
147
|
canon init # canon.yaml, starter policy (--policy light|standard|strict, default strict; see canon-policy), AGENTS.md block, commented plans examples
|
|
137
148
|
canon init --providers=claude,codex # harnesses for the canon skill; without it init asks on a terminal, else takes the detected .claude/.codex/.omp/.pi
|
|
138
|
-
canon skills install # follows canon.yaml skills.providers (else the install lock's, else the detected ones; none detected installs nothing)
|
|
149
|
+
canon skills install # follows canon.yaml skills.providers (else the install lock's, else the detected ones; none detected installs nothing); prunes projected files the bundle no longer ships when their bytes match the lock, keeps a modified one and reports `kept:`
|
|
139
150
|
canon skills install --providers=claude,codex,omp,pi # any subset or none; rewrites skills.providers
|
|
140
|
-
canon skills check # read-only drift check
|
|
141
|
-
canon skills doctor # diagnostics, incl. a stray canon projection for an unselected provider and a projection a formatter rewrote; never deletes user files
|
|
151
|
+
canon skills check # read-only drift check; exit 1 (SKILLS_DRIFT) names each orphan, a projected file the bundle no longer ships
|
|
152
|
+
canon skills doctor # diagnostics, incl. a stray canon projection for an unselected provider and a projection a formatter rewrote, and `orphan:` lines for projected files the bundle no longer ships; never deletes user files
|
|
142
153
|
```
|
|
143
154
|
|
|
144
155
|
`install` and `doctor` end with `owned-paths:`, the globs of every path
|
|
@@ -155,6 +166,13 @@ never by assuming a default. The projection is a read-only copy: Claude
|
|
|
155
166
|
|
|
156
167
|
### Route by task
|
|
157
168
|
|
|
169
|
+
- **Fix a bug:** the approved change if there is one, else the planner (`canon change new`); first a failing run of the scenario (`canon scenario new --case failure`, then `canon evidence add`), then the fix. `reference/role-implementer.md`.
|
|
170
|
+
- **Add or change a feature:** `canon subject new`, `canon change new`, then the brief. `reference/role-planner.md`.
|
|
171
|
+
- **Pure refactor, dependency, build, CI or infra:** `canon change new <slug> --subject-id <id> --kind chore`; data or interface changes are not chores. `reference/role-planner.md`.
|
|
172
|
+
- **Review someone's change:** `CANON_ROLE=reviewer`, `canon brief check --change-id <slug>`. `reference/role-reviewer.md`.
|
|
173
|
+
- **Resume:** `canon status --json`, then follow `meta.next`. `reference/role-orchestrator.md`.
|
|
174
|
+
- **Plan more work on a live subject:** `canon change new <slug> --subject-id <id>` (it warns until the brief is approved; `canon subject unadopt` parks a draft). `reference/canon-subject.md`, `reference/role-planner.md`.
|
|
175
|
+
- **Upgrade canon in a repo:** `canon migrate --dry-run`. `reference/role-orchestrator.md`.
|
|
158
176
|
- **Your role's steps:** `reference/role-planner.md`, `role-implementer.md`, `role-reviewer.md`, `role-orchestrator.md`.
|
|
159
177
|
- **Where the repo stands, subjects and changes:** `reference/canon-subject.md` (`canon status`, `canon subject new|adopt|status`, `canon change new`).
|
|
160
178
|
- **Write or reorganize `.feature` specs:** `reference/canon-authoring.md` before editing; use the scaffold.
|
|
@@ -15,6 +15,8 @@ brief has none of them: harden its Invariants and Checks instead.
|
|
|
15
15
|
- Each step reads sees → does → becomes, and names who and what state it starts from.
|
|
16
16
|
- Acceptance states, per step, what must be observable and which failure path is covered (invalid input, empty state, interrupted, repeated, denied).
|
|
17
17
|
- Step detail holds rules, states and edge cases of that step only; a rule that spans steps is stated once, where it first applies.
|
|
18
|
+
- Intent states current behavior and Outcome the desired behavior; interfaces are described by behavior (inputs, outputs, errors); no file path, line number or function name stands in a section that decides the work (a `Source:` line may).
|
|
19
|
+
- A flow step is one unit an implementer can verify with one named command; split a step that needs two.
|
|
18
20
|
- Acceptance writes no scenario ids: scenarios do not exist yet and point at steps.
|
|
19
21
|
|
|
20
22
|
## NEVER
|
|
@@ -20,6 +20,7 @@ marker, never by heading text.
|
|
|
20
20
|
- `canon brief check --json`: each finding names the rule, section key, line and hint.
|
|
21
21
|
- Every section present once, in order, with its marker; no `<!-- canon:hint … -->` comment left; no `TODO`/`TBD`; no empty section (write `None.` where nothing applies).
|
|
22
22
|
- The flow is a numbered list with ids `F1`, `F2`, … each reading sees → does → becomes.
|
|
23
|
+
- Intent describes what the product does today and Outcome what is true after; leftover implementation paths belong in a `Source:` line or nowhere.
|
|
23
24
|
- A chore's category is exactly one of `refactor`, `dependency`, `build`, `ci`, `infra`; its invariants and checks say something.
|
|
24
25
|
- A `canon:waive <rule-id> <section-key> "<reason>"` comment is well formed with a real reason.
|
|
25
26
|
|
|
@@ -293,7 +293,7 @@ line, was edited after its approval (`BRIEF_CHANGED_SINCE_APPROVAL`), or
|
|
|
293
293
|
there is no canon `brief.md` yet but the change comes from an
|
|
294
294
|
external openspec plan with a `proposal.md` under the plan sources (the
|
|
295
295
|
check `canon status` and the ship guard use; see
|
|
296
|
-
`canon-subject
|
|
296
|
+
`canon-subject`; which changes a scenario answers to is scoped by its `@flow:<change>.<F-id>` tags, described there). The subjects are the scenario's `@subject` owner for
|
|
297
297
|
`--scenario-id`, and every subject adopting the task's change for
|
|
298
298
|
`--task`:
|
|
299
299
|
|
|
@@ -354,7 +354,8 @@ review waivers: 1 advisory(ies) — not failing the gate:
|
|
|
354
354
|
The waiver lasts only until the subject's next status write.
|
|
355
355
|
|
|
356
356
|
`canon subject status <id> --to shipped` also refuses where a brief exists and
|
|
357
|
-
is not approved (the subject's newest adopted change
|
|
357
|
+
is not approved (the subject's scope, its newest adopted change walking back past pending drafts
|
|
358
|
+
no scenario is tagged `@flow:<change>.*` for, has a canon
|
|
358
359
|
`brief.md` with no `Approved:` line or one the brief's text no longer
|
|
359
360
|
matches, or an external `proposal.md` and no canon `brief.md` yet;
|
|
360
361
|
see `canon-subject`), as an `unreviewed-promotion` naming
|
|
@@ -115,9 +115,6 @@ canon report --snapshot /tmp/canon-snap
|
|
|
115
115
|
Serves the dashboard web app locally, pointed at a real snapshot:
|
|
116
116
|
|
|
117
117
|
```bash
|
|
118
|
-
# build the app once
|
|
119
|
-
bun install && bun run --cwd packages/dashboard build
|
|
120
|
-
|
|
121
118
|
canon dashboard # regenerates a fresh snapshot every run, serves it
|
|
122
119
|
canon dashboard --snapshot /tmp/canon-snap --port 8080
|
|
123
120
|
```
|
|
@@ -129,8 +126,32 @@ canon dashboard --snapshot /tmp/canon-snap --port 8080
|
|
|
129
126
|
`manifest.json` (a separately generated snapshot, or a downloaded CI
|
|
130
127
|
artifact) — never silently regenerated over it. If it has no
|
|
131
128
|
`manifest.json`, one is generated there once.
|
|
132
|
-
-
|
|
133
|
-
built
|
|
129
|
+
- Runs in any repo: the released binary carries the app (in canon's own
|
|
130
|
+
checkout a built `packages/dashboard/dist` is used instead). It prints the
|
|
131
|
+
panels URL and a briefs URL (`/briefs.html`), then serves until Ctrl-C.
|
|
132
|
+
A taken port fails with `DASHBOARD_PORT_IN_USE`: pass `--port <n>` or
|
|
133
|
+
`--port 0`.
|
|
134
|
+
- Without the `duckdb` CLI on `PATH` the server still runs; the panels page
|
|
135
|
+
says why there is no snapshot and how to get one (it never shows demo
|
|
136
|
+
data), and the briefs page works regardless.
|
|
137
|
+
|
|
138
|
+
### The briefs page (where a human approves briefs)
|
|
139
|
+
|
|
140
|
+
`/briefs.html` lists every live change whose brief awaits the human (no
|
|
141
|
+
`Approved:` line, or edited since) with its intent, outcome, flow steps,
|
|
142
|
+
acceptance, questions and assumptions, and its `canon brief check`
|
|
143
|
+
blockers and advisories. The human types their name and their answer and
|
|
144
|
+
clicks Approve: that records exactly what `canon brief approve --change-id
|
|
145
|
+
<id> --by <name> --note <text>` records, the `Approved:` line bound to the
|
|
146
|
+
brief digest and the policy approval. It is refused when the brief changed
|
|
147
|
+
after the page loaded (`BRIEF_CHANGED_SINCE_PAGE_LOAD`: reload) or while it
|
|
148
|
+
has blockers (`BRIEF_HAS_BLOCKERS`). The server is localhost only and
|
|
149
|
+
refuses requests from other origins (`DASHBOARD_ORIGIN_REFUSED`).
|
|
150
|
+
|
|
151
|
+
For an agent the human stop is unchanged: when clean briefs are waiting,
|
|
152
|
+
tell the human they can approve them in `canon dashboard` (briefs page) or
|
|
153
|
+
answer in chat. An agent never clicks Approve or fills the form, and runs
|
|
154
|
+
`canon brief approve` only from the human's own words.
|
|
134
155
|
|
|
135
156
|
The dashboard renders SEVEN panels — trust matrix, session costs, role
|
|
136
157
|
memory, flywheel funnel, review burn-down, review rounds, review totals.
|
|
@@ -73,8 +73,16 @@ canon finding add --change-id <c> --round <n> --seq <n> \
|
|
|
73
73
|
[--reviewed-sha <sha>] [--file-ref path/to/file.rs:120-134] [--scenario-id <id> …] [--actor-id <id>] [--session-id <id>]
|
|
74
74
|
canon finding close --change-id <c> --round <n> --seq <n> \
|
|
75
75
|
--disposition fixed --resolution-sha <sha> [--session-id <id>] # or rejected / deferred / open
|
|
76
|
+
canon finding close --change-id <c> --round <n> --seq <n> \
|
|
77
|
+
--disposition rejected|deferred --reason "<why, one line>"
|
|
76
78
|
```
|
|
77
79
|
|
|
80
|
+
`--reason` is required for `rejected` and `deferred` (refused with
|
|
81
|
+
`FINDING_REASON_REQUIRED` otherwise) and optional for `fixed`. It is stored
|
|
82
|
+
on the closing version, shown by `finding list` as `[reason: …]` and carried
|
|
83
|
+
in the JSON. Give the reason when you reject or defer: it is the only record
|
|
84
|
+
of why the finding was not fixed.
|
|
85
|
+
|
|
78
86
|
`--scenario-id` (repeatable, each a scenario the index knows; refused with
|
|
79
87
|
`FINDING_UNKNOWN_SCENARIO` otherwise) names the scenarios the finding is
|
|
80
88
|
about. After a `fixed` close, fresh evidence for those scenarios alone
|
|
@@ -122,14 +130,18 @@ found it: `--introduced-by self` is a usage error (exit 2) that points at
|
|
|
122
130
|
|
|
123
131
|
Severity is the reviewer's own judgement, never a score canon derives.
|
|
124
132
|
|
|
133
|
+
Write a review's or finding's `--summary` as prose that also names the blast
|
|
134
|
+
radius: what else the change touches that the checks do not exercise. It is
|
|
135
|
+
text in the summary, not a field.
|
|
136
|
+
|
|
125
137
|
### Dispositions
|
|
126
138
|
|
|
127
139
|
| Disposition | Meaning |
|
|
128
140
|
|---|---|
|
|
129
141
|
| `open` | Raised and not yet dealt with. The default on `finding add`; `finding close --disposition open` reopens. |
|
|
130
142
|
| `fixed` | Fixed by a commit. Requires `--resolution-sha <sha>` naming a commit this repo holds; only `fixed` may carry one. |
|
|
131
|
-
| `rejected` | The reviewer's point was considered and declined. |
|
|
132
|
-
| `deferred` | Accepted, but postponed to later work. |
|
|
143
|
+
| `rejected` | The reviewer's point was considered and declined. Needs `--reason`. |
|
|
144
|
+
| `deferred` | Accepted, but postponed to later work. Needs `--reason`. |
|
|
133
145
|
|
|
134
146
|
### How findings fold
|
|
135
147
|
|
|
@@ -260,7 +260,7 @@ skills:
|
|
|
260
260
|
`--providers a,b` (or `none`) answers without asking; with neither it takes
|
|
261
261
|
the detected ones, and with none detected it writes only a commented example
|
|
262
262
|
and installs nothing. `.agents` alone never selects Codex. `canon skills
|
|
263
|
-
install|check|doctor` follow the section; `canon skills install
|
|
263
|
+
install|check|doctor` follow the section; `install` prunes projected files the bundle no longer ships when their bytes match the lock (a modified one stays, reported as `kept:`), `check` exits 1 (`SKILLS_DRIFT`) naming each orphan and `doctor` prints `orphan:` lines; `canon skills install
|
|
264
264
|
--providers=<list>` rewrites it in place. Without the section they use the
|
|
265
265
|
install lock's providers, then the detected ones; nothing undetected is ever
|
|
266
266
|
added. A present section that is not this shape, or names another harness,
|
|
@@ -159,7 +159,7 @@ exits 1, and the status stays `verifying`. `retired` is not gated.
|
|
|
159
159
|
**The brief (0.16, 0.17, 0.19):** `verifying → shipped` also refuses where a brief
|
|
160
160
|
exists and is not approved, read exactly as `canon status` reads it (one
|
|
161
161
|
shared check, so status says `ship` only when this passes): the subject's
|
|
162
|
-
newest adopted change has a canon `.canon/changes/<change>/brief.md`
|
|
162
|
+
scope (its newest adopted change, except pending drafts, see below) has a canon `.canon/changes/<change>/brief.md`
|
|
163
163
|
with no `Approved:` line outside HTML comments and code fences, whose
|
|
164
164
|
`Approved:` line carries a digest the brief's text no longer matches
|
|
165
165
|
(`BRIEF_CHANGED_SINCE_APPROVAL`; the brief was edited after the human
|
|
@@ -204,6 +204,30 @@ scenario whose `@subject` owner, or a task whose change
|
|
|
204
204
|
an adopting subject, has an enforced, unapproved brief, through the same
|
|
205
205
|
check; see `canon-gate`.
|
|
206
206
|
|
|
207
|
+
**Approval is scoped to the scenarios a change names.** The evidence
|
|
208
|
+
refusal asks the approval of each change the scenario's `@flow:<change>.<F-id>`
|
|
209
|
+
tags name. An untagged scenario answers to the subject's newest adopted
|
|
210
|
+
change whose brief carries an `Approved:` line; if no adopted change ever
|
|
211
|
+
did, a scenario with evidence recorded before the subject first adopted a
|
|
212
|
+
change (and without a brief override) needs no brief, and one without
|
|
213
|
+
answers to the newest adopted change. A task answers to its own
|
|
214
|
+
change. So planning the next change for a `building`, `verifying` or
|
|
215
|
+
`shipped` subject does not freeze re-verification of its existing
|
|
216
|
+
scenarios; scenarios written for the new change (`canon scenario new
|
|
217
|
+
--flow <F-id>` tags them) wait for its approval. The ship guard above is
|
|
218
|
+
unchanged, except for a pending draft: the subject cannot ship until its
|
|
219
|
+
scope is approved, where the scope is found walking the adopted changes from
|
|
220
|
+
the newest back: a change whose brief was never approved and that no scenario
|
|
221
|
+
of the subject is tagged `@flow:<change>.*` for is a pending draft and is
|
|
222
|
+
skipped; the first approved or tagged change is the scope. A tagged unapproved
|
|
223
|
+
draft anywhere above the newest approved change therefore blocks. The reopen
|
|
224
|
+
and ship transitions print one line naming every change set aside (stderr, and
|
|
225
|
+
`meta.warnings` with `--json`): ``<change> is a pending draft (brief not
|
|
226
|
+
approved, no scenario tagged `@flow:<change>.*`): not part of this transition,
|
|
227
|
+
which reads the subject's newest approved or tagged change``, and `canon status`
|
|
228
|
+
marks them "not yet part of the subject's scope" (`pendingDrafts` in JSON)
|
|
229
|
+
while still asking the human to answer a draft's brief.
|
|
230
|
+
|
|
207
231
|
### The review guard (`spec_coverage.require_review`, opt-in)
|
|
208
232
|
|
|
209
233
|
When `.canon/policy.yaml` sets `spec_coverage.require_review` (see
|
|
@@ -279,8 +303,10 @@ canon subject status: demo-subject → verifying — written directly; nothing t
|
|
|
279
303
|
records the change the work goes into:
|
|
280
304
|
`reopen: { reason: …, change: c-demo, actor: { agent_id: impl } }`. The
|
|
281
305
|
next status write clears both, as it clears a waiver.
|
|
282
|
-
- Inside the approved scope it needs no human: the subject's
|
|
283
|
-
adopted change
|
|
306
|
+
- Inside the approved scope it needs no human: the subject's scope (its
|
|
307
|
+
newest adopted change, walking back past pending drafts no scenario is
|
|
308
|
+
tagged for) has an approved brief, or there
|
|
309
|
+
is no brief to answer.
|
|
284
310
|
Otherwise it refuses, exit 1, naming the brief to approve, and
|
|
285
311
|
`--override-reason` does not waive it:
|
|
286
312
|
``unreviewed-promotion demo-subject — shipped → verifying: reopening needs the approved scope, and the brief is not approved — .canon/changes/c-next/brief.md has no `Approved:` line; …``.
|
|
@@ -345,6 +371,31 @@ counted. If the change write then fails, `adopt` exits `2`, names the
|
|
|
345
371
|
subject that now lists the change, and prints `canon subject adopt
|
|
346
372
|
<change_id> --subject-id <id>` to complete it.
|
|
347
373
|
|
|
374
|
+
`canon change new <slug> --subject-id <S>` and `canon subject adopt
|
|
375
|
+
<change> --subject-id <S>` print a warning on stderr, and in JSON
|
|
376
|
+
`meta.warnings`, when `<S>` is `building`, `verifying` or `shipped` and the
|
|
377
|
+
change's brief is not approved. The warning says the subject's existing
|
|
378
|
+
scenarios keep accepting evidence and the subject can still be reopened and
|
|
379
|
+
shipped for its approved scope while no scenario is tagged `@flow:<change>.*`;
|
|
380
|
+
once one is, the subject cannot ship until the brief is approved. The draft is
|
|
381
|
+
pending until then: the reopen and ship guards read the newest adopted change
|
|
382
|
+
with an `Approved:` line instead, so a shipped subject can reopen, re-verify
|
|
383
|
+
and ship again while the draft waits; `canon status` still lists it.
|
|
384
|
+
|
|
385
|
+
### Taking a draft change back out: `canon subject unadopt`
|
|
386
|
+
|
|
387
|
+
```bash
|
|
388
|
+
canon subject unadopt <change> --subject-id <S> --as planner --json
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
Removes the link on both records and keeps the change and its brief, so the
|
|
392
|
+
draft can be adopted elsewhere (a `proposed` holding subject, say) while the
|
|
393
|
+
live subject keeps shipping. It is refused with `SUBJECT_UNADOPT_REFUSED`
|
|
394
|
+
for a change whose brief carries an `Approved:` line, and while a scenario
|
|
395
|
+
the subject owns carries a `@flow:<change>.*` tag (it would fall back to an
|
|
396
|
+
older approval; remove or retag those scenarios first, the hint names them);
|
|
397
|
+
an unlinked pair is a no-op.
|
|
398
|
+
|
|
348
399
|
### Starting a new change: `canon change new`
|
|
349
400
|
|
|
350
401
|
For a change that does not exist yet, one command writes it and records
|
|
@@ -101,6 +101,10 @@ NOT an attr value) and satisfies that directive's attrs:
|
|
|
101
101
|
- "cargo test"
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
+
When the body carries a continuation for another session, `summary` holds
|
|
105
|
+
the next command and the claims already verified; reference artifacts and
|
|
106
|
+
record ids instead of pasting them, and redact secrets.
|
|
107
|
+
|
|
104
108
|
It's validated and compiled to a canon handoff body. Each
|
|
105
109
|
`handoff-<domain>` directive declares its own attrs (e.g. `design` /
|
|
106
110
|
`content` domains may require `acceptance-criteria` instead of
|
|
@@ -45,12 +45,27 @@ After the first `status`, follow `meta.next` of each write; do not call
|
|
|
45
45
|
root (default `.canon/specs`); `reference/canon-authoring.md` has the
|
|
46
46
|
scaffold. When you fill in a scenario, keep its `# canon: {...}` provenance
|
|
47
47
|
lines: dropping one aborts `inventory sync` on `missing-provenance`.
|
|
48
|
-
2. **Units.**
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
2. **Units.** One unit is one flow step's complete path (sees, does,
|
|
49
|
+
becomes) that can be verified on its own, sized to one session. Name the
|
|
50
|
+
command that proves it (a test filter, a script) before you start it, and
|
|
51
|
+
run that command before you call the unit done. Give each unit its own
|
|
52
|
+
actor id and session id and pass them on everything it records. A
|
|
53
|
+
delegated unit attests its own work; never attest another unit's work
|
|
54
|
+
under your id. Units stay inside the one approved change; `tasks.md` is a
|
|
55
|
+
note, not a record.
|
|
51
56
|
3. **Code and tests.** Name tests by scenario id (`game.run.01: …`) so a test
|
|
52
57
|
report binds to its scenario. Record a defect you find in your own work
|
|
53
58
|
with `finding add` the moment you find it, before review.
|
|
59
|
+
**A fix fails first.** For a bug fix, write or extend the scenario's test
|
|
60
|
+
and run it red on the unfixed code, then bind that failing run before you
|
|
61
|
+
change the code (`evidence add --scenario-id <id> --report <fmt>:<path> …`
|
|
62
|
+
with `--verdict divergent`; a failed case never records `faithful`, and
|
|
63
|
+
`--all-scenarios` derives `divergent` from it).
|
|
64
|
+
Fix the code, run again and bind the passing run: the green record
|
|
65
|
+
supersedes the red one, and `canon query --kind evidence --history
|
|
66
|
+
--scenario-id <id>` shows the red-then-green pair to the reviewer. A red
|
|
67
|
+
run is never an accepted gap. When no executable reproduction is
|
|
68
|
+
possible, say in `--ref` what the closest check is and why.
|
|
54
69
|
4. **Evidence.** One test run that proves several scenarios is bound once with
|
|
55
70
|
`--all-scenarios`: one record per indexed scenario owning a case, each with
|
|
56
71
|
the verdict its own cases show; any refusal writes none. For one scenario
|
|
@@ -99,6 +114,16 @@ After the first `status`, follow `meta.next` of each write; do not call
|
|
|
99
114
|
was accepted is accepted again by the reviewer once new evidence says
|
|
100
115
|
something different.
|
|
101
116
|
|
|
117
|
+
## Excuses that fail
|
|
118
|
+
|
|
119
|
+
| The thought | The reality |
|
|
120
|
+
|---|---|
|
|
121
|
+
| "The suite is slow; record `faithful` to pass." | `faithful` over a failed case is refused; a limit outside this change is recorded `divergent` with the reason in `--ref`. |
|
|
122
|
+
| "I fixed it, the test would pass." | Run the test red first and bind it, then green. Evidence is a claim; the history is what the reviewer reads. |
|
|
123
|
+
| "The brief is as good as approved." | `evidence add` refuses an unapproved brief's scenarios. Say so and wait. |
|
|
124
|
+
| "I will attest the other unit's work under my id." | Each unit records under its own actor id and session id. |
|
|
125
|
+
| "I was told this twice already." | A correction repeated across runs belongs in a scenario, the policy or a gate; tell the orchestrator. |
|
|
126
|
+
|
|
102
127
|
## Retry safety
|
|
103
128
|
|
|
104
129
|
Repeating `evidence add` with identical input returns the existing record
|
|
@@ -48,6 +48,36 @@ shell).
|
|
|
48
48
|
Order: planner, then (after the human's answer) implementer, then reviewer.
|
|
49
49
|
Give each a unit: its own `--actor-id` and a session id.
|
|
50
50
|
|
|
51
|
+
### Spawn contract
|
|
52
|
+
|
|
53
|
+
Every spawn prompt names, in this order: the goal (the change slug and the
|
|
54
|
+
one flow step or review it serves); the scope it owns (the scenarios or
|
|
55
|
+
paths it may write); what it must not touch (the brief, the policy, other
|
|
56
|
+
units' scenarios, anything outside its scope); the command that proves it is
|
|
57
|
+
done (the unit's verify command, or `canon gate check` for the reviewer); and
|
|
58
|
+
its own `--actor-id` and `--session-id`. Two agents never write the same
|
|
59
|
+
paths: give parallel implementers disjoint scopes, and where the harness can
|
|
60
|
+
isolate a child in its own worktree, use it. The child reports what it
|
|
61
|
+
verified apart from what it only claims.
|
|
62
|
+
|
|
63
|
+
### What each harness gives you
|
|
64
|
+
|
|
65
|
+
Last reviewed 2026-10-21. A cell marked `verify` is unconfirmed here: check
|
|
66
|
+
the harness before relying on it; a harness version moves faster than this
|
|
67
|
+
text.
|
|
68
|
+
|
|
69
|
+
| Harness | In-harness subagents | Isolation | Ask the human | Hooks | Skipped |
|
|
70
|
+
|---|---|---|---|---|---|
|
|
71
|
+
| claude (`.claude`) | yes, a subagent tool; a child shares your environment, so pass the role in the task | a subagent can run in its own git worktree | yes, a question tool | yes, in the harness settings; `scripts/canon-retrieve-pre-dispatch.sh` is a sidecar, not installed | none |
|
|
72
|
+
| codex (`.agents`) | `verify`: none when #39 was filed; start the reviewer as a new `codex exec` run | none from canon: use a separate worktree you create | no in `codex exec`; stop and report | `verify` | in-harness spawn, unless a newer version provides it |
|
|
73
|
+
| omp (`.omp`) | yes, the task tool | `verify` | yes, an ask tool | yes | none |
|
|
74
|
+
| pi (`.pi`) | `verify`: assume none; start a new `pi` process | none from canon | `verify` | `verify` | in-harness spawn, isolation |
|
|
75
|
+
|
|
76
|
+
Where a harness has no subagent tool, the reviewer is still a separate
|
|
77
|
+
process with its own session id: start it from the shell with its own
|
|
78
|
+
`CANON_ROLE=reviewer`. A harness that cannot ask the human means you stop
|
|
79
|
+
and report in your final message; you never assume the answer.
|
|
80
|
+
|
|
51
81
|
The reviewer is a separate process with its own session id, never your
|
|
52
82
|
session or the implementer's under another label. Start it as a new process
|
|
53
83
|
(a new `codex exec` run, or the harness's subagent), pass nothing of the
|
|
@@ -59,7 +89,9 @@ not sign off.
|
|
|
59
89
|
|
|
60
90
|
When `status` or `meta.next` shows a step with `actor: human` (the brief's
|
|
61
91
|
approval), stop everything for that scope, report the brief to the human in
|
|
62
|
-
its language, and wait.
|
|
92
|
+
its language, and wait. Tell the human they can approve the clean briefs in
|
|
93
|
+
`canon dashboard` (the briefs page) or answer in chat; you never click
|
|
94
|
+
Approve or fill the form, and you run `brief approve` only from their words. Do not start the implementer before the answer.
|
|
63
95
|
Record the answer with `brief approve`. Never approve it yourself and never
|
|
64
96
|
edit the policy to pass the gate. A policy that no longer matches the
|
|
65
97
|
approval fails closed (`POLICY_CHANGED_SINCE_APPROVAL`, or
|
|
@@ -70,6 +102,27 @@ the approved policy from git; never edit the policy yourself. Inside an
|
|
|
70
102
|
approved scope never stop for a human: fixes, follow-ups and re-reviews go
|
|
71
103
|
straight to the implementer and reviewer in the same change.
|
|
72
104
|
|
|
105
|
+
## Hand off a note, not a copy
|
|
106
|
+
|
|
107
|
+
A note one session leaves for another (a resume note, a spawn prompt, a
|
|
108
|
+
`Handoff` body) points to artifacts and record ids instead of pasting them:
|
|
109
|
+
name the change slug, the scenario ids, the evidence or finding keys and the
|
|
110
|
+
files, and the next command. Say what was verified (a command that ran, with
|
|
111
|
+
its result) apart from what is only claimed. Redact secrets: tokens, keys,
|
|
112
|
+
passwords and private URLs never go into a note, a `--ref`, a `--summary`
|
|
113
|
+
or a brief. When a `Handoff` body carries a continuation, its `summary`
|
|
114
|
+
holds the next command and the verified claims (`reference/canon-vocab.md`).
|
|
115
|
+
|
|
116
|
+
## Excuses that fail
|
|
117
|
+
|
|
118
|
+
| The thought | The reality |
|
|
119
|
+
|---|---|
|
|
120
|
+
| "Edit the policy so the gate passes." | A changed policy fails closed (`POLICY_CHANGED_SINCE_APPROVAL`); ask the human to re-approve or restore it from git. |
|
|
121
|
+
| "The reviewer can be me under another label." | The reviewer is a separate process with its own real session id (#39). |
|
|
122
|
+
| "Approve the brief; the human said they are busy." | Never approve for the human; report the brief and wait. |
|
|
123
|
+
| "Override the refusal to ship." | Fix the gap the refusal names; `--override-reason` does not waive an approval. |
|
|
124
|
+
| "I have corrected this agent before." | A correction repeated across runs belongs in a scenario, the policy or a gate, not more prose. Tell the human. |
|
|
125
|
+
|
|
73
126
|
## Move the subject
|
|
74
127
|
|
|
75
128
|
Move along `proposed → specced → building → verifying → shipped` with
|
|
@@ -15,6 +15,7 @@ canon subject new <subject-id> --domain <domain> --title "<title>" --actor-id <u
|
|
|
15
15
|
canon change new <slug> --subject-id <subject-id> --title "<title>" --as planner --json
|
|
16
16
|
canon change new <slug> --subject-id <subject-id> --kind chore --title "<title>" --as planner --json
|
|
17
17
|
canon subject adopt <slug> --subject-id <other-subject-id> --as planner --json
|
|
18
|
+
canon subject unadopt <slug> --subject-id <subject-id> --as planner --json # a draft change off a live subject; refused once its brief is approved
|
|
18
19
|
canon brief check --change-id <slug> --as planner --json
|
|
19
20
|
canon brief approve --change-id <slug> --by "<the human>" --note "<their words>" --as planner --json
|
|
20
21
|
```
|
|
@@ -34,9 +35,24 @@ unapproved. A policy that no longer matches its approval fails closed
|
|
|
34
35
|
`tasks.md` (`tasks.md` is an optional work note nothing gates on). A
|
|
35
36
|
change that also serves another subject is adopted there too
|
|
36
37
|
(`subject adopt`): it then belongs to both,
|
|
37
|
-
and its open blockers gate both.
|
|
38
|
+
and its open blockers gate both. Planning the next change for a
|
|
39
|
+
`building`, `verifying` or `shipped` subject warns (stderr and JSON
|
|
40
|
+
`meta.warnings`) until its brief is approved: evidence for its existing scenarios is not
|
|
41
|
+
frozen and the subject can still be reopened and shipped for its approved
|
|
42
|
+
scope while no scenario is tagged for the draft (a pending draft); once one
|
|
43
|
+
is, the subject cannot ship before the approval.
|
|
44
|
+
A scenario answers to the changes its `@flow:<change>.<F-id>`
|
|
45
|
+
tags name (`canon scenario new --flow <F-id>` tags one for this change).
|
|
46
|
+
To park a draft that should not gate a live subject, `subject unadopt`
|
|
47
|
+
it and adopt it into a `proposed` subject. Inside an approved scope there is no new
|
|
38
48
|
subject or change: a fix or follow-up stays in the existing change.
|
|
39
|
-
2. **Write the brief.**
|
|
49
|
+
2. **Write the brief.** Briefs outlive the code that exists today and are
|
|
50
|
+
read by sessions that did not write them. Intent says what the product
|
|
51
|
+
does today, in behavior words, and why that is not enough; Desired outcome
|
|
52
|
+
says what is true afterwards. Describe an interface by its behavior
|
|
53
|
+
(inputs, outputs, errors), and keep file paths, line numbers and function
|
|
54
|
+
names out of the sections that decide the work; a `Source: <path>`
|
|
55
|
+
provenance line is the one exception. A PRD: intent, desired outcome, the user flow as
|
|
40
56
|
numbered steps `F1`, `F2`, … (sees → does → becomes), step detail per
|
|
41
57
|
step, acceptance per step with its failure path, questions and
|
|
42
58
|
assumptions, out of scope. Write it in the `language:` of `canon.yaml`;
|
|
@@ -55,7 +71,9 @@ unapproved. A policy that no longer matches its approval fails closed
|
|
|
55
71
|
- `reference/brief-align.md`: scenarios cover the flow.
|
|
56
72
|
- `reference/brief-polish.md`: prose.
|
|
57
73
|
4. **Stop for the human.** Report the brief to the human, in the brief's
|
|
58
|
-
language, asking once
|
|
74
|
+
language, asking once, and say they can approve it in `canon dashboard`
|
|
75
|
+
(the briefs page, `/briefs.html`) or answer in chat. You never click
|
|
76
|
+
Approve or fill that form yourself. Build nothing for that scope (no scenarios, code
|
|
59
77
|
or evidence) until the human answers. The approval is never assumed, even
|
|
60
78
|
when the human said they cannot answer until you report: the brief is
|
|
61
79
|
that report. The one answer approves the brief and the policy
|
|
@@ -86,6 +104,28 @@ shipped subject `change new` prints the reopen command for the orchestrator
|
|
|
86
104
|
ship again until the suite is evidenced after the chore's approval. Work that
|
|
87
105
|
changes behavior is never a chore: it is a feature change.
|
|
88
106
|
|
|
107
|
+
### A migration is cut in phases
|
|
108
|
+
|
|
109
|
+
A migration (a column, a format, a dependency swap) is cut as expand,
|
|
110
|
+
migrate, contract: one change per phase, each adopting only after the
|
|
111
|
+
previous one shipped. A phase that alters nothing a user or a caller can
|
|
112
|
+
observe (a behavior-preserving internal rename, a build step) is a chore. A
|
|
113
|
+
phase that changes data, a public interface or output is never a chore: it
|
|
114
|
+
is a feature change with a PRD brief, whose Acceptance names the down path
|
|
115
|
+
(how it is undone), the safety condition of any backfill (idempotent,
|
|
116
|
+
resumable, checked before and after), and the destructive cleanup (drop the
|
|
117
|
+
old column, delete the old format) in its own later change.
|
|
118
|
+
|
|
119
|
+
## Excuses that fail
|
|
120
|
+
|
|
121
|
+
| The thought | The reality |
|
|
122
|
+
|---|---|
|
|
123
|
+
| "The human is unavailable; I will assume the approval." | Never. The brief is the report; stop, and put open questions into Questions and assumptions. |
|
|
124
|
+
| "This could change policy, but I will decide it." | Anything that could change policy goes to the questions. |
|
|
125
|
+
| "A small follow-up needs no new answer." | A follow-up that adds or changes a flow step is a scope change and needs a new `brief approve`. |
|
|
126
|
+
| "This migration is just a chore." | Data, interface or output changes are feature changes. |
|
|
127
|
+
| "Put the file and line in the brief so nobody has to look." | Paths rot; describe behavior. |
|
|
128
|
+
|
|
89
129
|
## Hand-off
|
|
90
130
|
|
|
91
131
|
After the approval the implementer takes the scenarios, then the reviewer.
|
|
@@ -15,12 +15,14 @@ canon gate check --as reviewer --json # once, before review add
|
|
|
15
15
|
canon finding add --change-id <slug> --round <n> --seq <n> --severity <blocker|should-fix|note> --reviewer <me> --actor-id <me> --session-id <session> --summary "<defect>" --scenario-id <id> --scenario-id <id> --as reviewer --json # one --scenario-id per scenario the defect is about
|
|
16
16
|
canon finding list --change-id <slug> --open --as reviewer --json
|
|
17
17
|
canon finding close --change-id <slug> --round <n> --seq <n> --disposition fixed --resolution-sha <sha> --as reviewer --json
|
|
18
|
+
canon finding close --change-id <slug> --round <n> --seq <n> --disposition rejected|deferred --reason "<why, one line>" --as reviewer --json # --reason is required here (FINDING_REASON_REQUIRED); optional for fixed
|
|
18
19
|
canon review add --project-id <root-id> --scenario-id <id> --scenario-id <id> --reviewer <me> --actor-id <me> --role reviewer --session-id <session> --pin <sha> --upstream-ref <ref> --as reviewer --json
|
|
19
20
|
canon review add --project-id <root-id> --scenario-ids-from - --reviewer <me> --actor-id <me> --role reviewer --session-id <session> --pin <sha> --upstream-ref <ref> --as reviewer --json # ids on stdin, one per line
|
|
20
21
|
canon review add --project-id <root-id> --change-id <chore-slug> --reviewer <me> --actor-id <me> --role reviewer --session-id <session> --pin <sha> --upstream-ref <ref> --as reviewer --json # a chore: one change-level review of its invariants and checks against the diff
|
|
21
22
|
canon divergence accept --project-id <root-id> --scenario-id <id> --reason "<why the gap stays>" --tracking <issue-url-or-ref> [--expiry YYYY-MM-DD] --reviewer <me> --actor-id <me> --session-id <session> --as reviewer --json
|
|
22
23
|
canon artifacts verify --full --as reviewer --json # the files the evidence and reviews bind are in the artifact store; refuses when an object store is unreachable
|
|
23
24
|
canon query --kind evidence --history --as reviewer --json # every version, not only the current record
|
|
25
|
+
canon query --kind evidence --history --scenario-id <id> --as reviewer --json # one scenario's red-then-green pair
|
|
24
26
|
```
|
|
25
27
|
|
|
26
28
|
`review add` needs `--upstream-ref <ref>` or `--original-spec-ref <path>`;
|
|
@@ -70,10 +72,28 @@ does not sign off; record that instead (`--outcome indeterminate`, or
|
|
|
70
72
|
|
|
71
73
|
## Check the checks
|
|
72
74
|
|
|
73
|
-
Passing is not enough
|
|
74
|
-
steps
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
Passing is not enough. Read each bound test against its scenario's Then
|
|
76
|
+
steps, in two separate passes, and say which pass a finding came from:
|
|
77
|
+
|
|
78
|
+
1. **Spec fidelity.** The brief's step and the scenario against the test
|
|
79
|
+
(the PRD read above). Finding: the check pins behavior the scenario does
|
|
80
|
+
not require, or skips a Then step it states.
|
|
81
|
+
2. **Standards.** The diff and the test against the code it claims to prove.
|
|
82
|
+
Finding: the test passes without proving anything. The usual ways:
|
|
83
|
+
- the expected value is computed by the code under test (tautology);
|
|
84
|
+
- it asserts an implementation detail (a call count, a private name), not behavior;
|
|
85
|
+
- it never reaches the real path or the real side effect (a file written, a record stored, a request sent);
|
|
86
|
+
- it asserts only that no error happened;
|
|
87
|
+
- it mocks the unit it is meant to test.
|
|
88
|
+
|
|
89
|
+
When a check looks weak, you may name the one-line change to the code that
|
|
90
|
+
should make it fail and run it if it is cheap; if the test stays green, record
|
|
91
|
+
the finding. For a fix, read `canon query --kind evidence --history
|
|
92
|
+
--scenario-id <id>`: expect an earlier non-faithful version, or a named
|
|
93
|
+
closest reproduction in `--ref`; record a finding when neither exists. Put
|
|
94
|
+
one blast-radius fact in the finding or review `--summary`: what else the
|
|
95
|
+
change touches that the checks do not exercise. Do all of this before you
|
|
96
|
+
sign.
|
|
77
97
|
|
|
78
98
|
## Accept a known gap
|
|
79
99
|
|
|
@@ -93,7 +113,10 @@ ship guard and stays listed as `accepted-divergence` in `status` and
|
|
|
93
113
|
|
|
94
114
|
- Every defect is a finding; `finding list` shows round, seq, severity and
|
|
95
115
|
disposition, so the next seq and the open blockers are one command away.
|
|
96
|
-
Close a finding once it is
|
|
116
|
+
Close a finding once it is dealt with. When you close it `rejected` or
|
|
117
|
+
`deferred`, give the reason in one line with `--reason "<why>"`: the close
|
|
118
|
+
is refused without it (`FINDING_REASON_REQUIRED`), and `finding list` shows
|
|
119
|
+
it as `[reason: …]` so the next reader sees why it was not fixed.
|
|
97
120
|
- Sign off in one batch with `review add`, all or nothing. It writes
|
|
98
121
|
directly; nothing to promote. canon stores and re-checks attached bytes
|
|
99
122
|
and never compares images.
|
|
@@ -114,4 +137,14 @@ ship guard and stays listed as `accepted-divergence` in `status` and
|
|
|
114
137
|
brief stales it. The chore also needs evidence recorded after its approval
|
|
115
138
|
(the implementer's `--all-scenarios` run) before it ships.
|
|
116
139
|
|
|
140
|
+
## Excuses that fail
|
|
141
|
+
|
|
142
|
+
| The thought | The reality |
|
|
143
|
+
|---|---|
|
|
144
|
+
| "I will review this under a new session label." | A review is a separate process with its own real session id; a relabelled self-review is not independent (#39). |
|
|
145
|
+
| "The tests pass, so I can sign." | Read the test against the Then steps first; a green test can prove nothing. |
|
|
146
|
+
| "I cannot run the tests, but the evidence looks right." | Say so and record `indeterminate` or `unavailable`; never sign what you could not run. |
|
|
147
|
+
| "This is a minor defect; I will skip the finding." | Every defect is a finding; severity says how much it matters. |
|
|
148
|
+
| "I will edit the brief to fix it." | A brief defect is a finding, not an edit. |
|
|
149
|
+
|
|
117
150
|
Reference: `reference/canon-review.md`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@journeykit/canon",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
4
4
|
"description": "canon — harness knowledge substrate. JS launcher: platform detection + native binary resolution.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {},
|
|
32
32
|
"optionalDependencies": {
|
|
33
|
-
"@journeykit/canon-core-darwin-arm64": "0.
|
|
34
|
-
"@journeykit/canon-core-linux-x64": "0.
|
|
35
|
-
"@journeykit/canon-core-win32-x64": "0.
|
|
33
|
+
"@journeykit/canon-core-darwin-arm64": "0.25.0",
|
|
34
|
+
"@journeykit/canon-core-linux-x64": "0.25.0",
|
|
35
|
+
"@journeykit/canon-core-win32-x64": "0.25.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/node": "^20.0.0",
|