@journeykit/canon 0.17.0 → 0.21.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 +105 -150
- package/dist/skills/reference/brief-align.md +42 -0
- package/dist/skills/reference/brief-clarify.md +42 -0
- package/dist/skills/reference/brief-harden.md +41 -0
- package/dist/skills/reference/brief-polish.md +42 -0
- package/dist/skills/reference/brief-shape.md +46 -0
- package/dist/skills/reference/canon-authoring.md +1 -1
- package/dist/skills/reference/canon-context.md +3 -3
- package/dist/skills/reference/canon-gate.md +133 -44
- package/dist/skills/reference/canon-inventory.md +2 -2
- package/dist/skills/reference/canon-learn.md +4 -6
- package/dist/skills/reference/canon-plan-import.md +10 -7
- package/dist/skills/reference/canon-policy.md +5 -3
- package/dist/skills/reference/canon-review.md +31 -12
- package/dist/skills/reference/canon-storage.md +22 -7
- package/dist/skills/reference/canon-subject.md +199 -62
- package/dist/skills/reference/role-implementer.md +80 -0
- package/dist/skills/reference/role-orchestrator.md +76 -0
- package/dist/skills/reference/role-planner.md +84 -0
- package/dist/skills/reference/role-reviewer.md +70 -0
- package/package.json +4 -4
|
@@ -103,7 +103,7 @@ this subject and domain — the exact memory step 2 retrieves next time.
|
|
|
103
103
|
|
|
104
104
|
## Status lifecycle + the shipped evidence gate
|
|
105
105
|
|
|
106
|
-
`canon subject status <id> <state> [--override-reason <text>] [--actor-id <id>]`
|
|
106
|
+
`canon subject status <id> --to <state> [--override-reason <text>] [--reason <text>] [--actor-id <id>]`
|
|
107
107
|
performs a policy-gated transition.
|
|
108
108
|
The legal chain:
|
|
109
109
|
|
|
@@ -111,15 +111,16 @@ The legal chain:
|
|
|
111
111
|
proposed → specced → building → verifying → shipped
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
-
plus **
|
|
115
|
-
|
|
116
|
-
|
|
114
|
+
plus **shipped → verifying** to reopen a shipped subject (with
|
|
115
|
+
`--reason`, below) and **any state → retired** (a subject can be retired
|
|
116
|
+
from anywhere). Any other transition is rejected by failure class, exits
|
|
117
|
+
1, and leaves the record UNCHANGED (fail closed).
|
|
117
118
|
|
|
118
119
|
```bash
|
|
119
|
-
canon subject status subject-domain-loop specced
|
|
120
|
-
canon subject status subject-domain-loop building
|
|
121
|
-
canon subject status subject-domain-loop verifying
|
|
122
|
-
canon subject status subject-domain-loop shipped # gated — see below
|
|
120
|
+
canon subject status subject-domain-loop --to specced
|
|
121
|
+
canon subject status subject-domain-loop --to building
|
|
122
|
+
canon subject status subject-domain-loop --to verifying
|
|
123
|
+
canon subject status subject-domain-loop --to shipped # gated — see below
|
|
123
124
|
```
|
|
124
125
|
|
|
125
126
|
On success the status updates in place; `--json` prints the updated
|
|
@@ -148,29 +149,34 @@ empty set; so is one whose `scenario` records route away from the
|
|
|
148
149
|
gate's rung. Each refusal prints by failure class (`uncovered-cell`),
|
|
149
150
|
exits 1, and the status stays `verifying`. `retired` is not gated.
|
|
150
151
|
|
|
151
|
-
**The brief (0.16, 0.17):** `verifying → shipped` also refuses where a brief
|
|
152
|
+
**The brief (0.16, 0.17, 0.19):** `verifying → shipped` also refuses where a brief
|
|
152
153
|
exists and is not approved, read exactly as `canon status` reads it (one
|
|
153
154
|
shared check, so status says `ship` only when this passes): the subject's
|
|
154
155
|
newest adopted change has a canon `.canon/changes/<change>/brief.md`
|
|
155
|
-
with no `Approved:` line outside HTML comments and code fences,
|
|
156
|
-
|
|
157
|
-
`
|
|
158
|
-
|
|
159
|
-
|
|
156
|
+
with no `Approved:` line outside HTML comments and code fences, whose
|
|
157
|
+
`Approved:` line carries a digest the brief's text no longer matches
|
|
158
|
+
(`BRIEF_CHANGED_SINCE_APPROVAL`; the brief was edited after the human
|
|
159
|
+
answered), or has no canon `brief.md` yet but comes from an external
|
|
160
|
+
openspec plan with a `proposal.md` under the plan sources. It also
|
|
161
|
+
refuses while `canon brief check` has blocker findings for that change,
|
|
162
|
+
which include a flow step with no scenario or no failure scenario (the
|
|
163
|
+
coverage rules gate shipping, never approval). It runs with or without
|
|
164
|
+
a policy. The refusal is an `unreviewed-promotion` naming the subject, and
|
|
165
|
+
says what to do:
|
|
160
166
|
|
|
161
167
|
```
|
|
162
|
-
canon subject status: unreviewed-promotion demo-subject — verifying → shipped: brief not approved — .canon/changes/c-demo/brief.md has no `Approved:` line; a human must answer the brief, and only from that answer the agent will record their words
|
|
163
|
-
canon subject status: refused — get the human's answer to the brief and record it
|
|
168
|
+
canon subject status: unreviewed-promotion demo-subject — verifying → shipped: brief not approved — .canon/changes/c-demo/brief.md has no `Approved:` line; a human must answer the brief, and only from that answer the agent will record their words with `canon brief approve --change c-demo --by <the human> --note "<their words>"`
|
|
169
|
+
canon subject status: refused — get the human's answer to the brief and record it with `canon brief approve`, or pass --override-reason <text> to move anyway; the waiver is recorded on the subject
|
|
164
170
|
```
|
|
165
171
|
|
|
166
172
|
For an external change with no canon brief yet, the reason reads
|
|
167
173
|
``.canon/changes/<c>/brief.md does not exist yet: change `<c>` comes from
|
|
168
174
|
the external plan <proposal>, which canon reads and never edits, so its
|
|
169
175
|
approval lives in that canon file`` and the agent will ``create
|
|
170
|
-
.canon/changes/<c>/brief.md holding a `Source: <proposal>` line
|
|
171
|
-
record their words
|
|
176
|
+
.canon/changes/<c>/brief.md holding a `Source: <proposal>` line, then
|
|
177
|
+
record their words with `canon brief approve --change <c> --by <the human> --note "<their words>"` ``.
|
|
172
178
|
|
|
173
|
-
Never
|
|
179
|
+
Never record the approval without the human's answer. When there is no brief to
|
|
174
180
|
answer (no adopted change, or a newest change with neither a canon
|
|
175
181
|
`brief.md` nor an external `proposal.md`), the move is not refused: it
|
|
176
182
|
prints ``canon subject status: advisory: <id>'s brief is not approved —
|
|
@@ -184,9 +190,10 @@ the transition, not the check.
|
|
|
184
190
|
|
|
185
191
|
This refusal is a backstop, and so is the evidence one: shipping is not
|
|
186
192
|
the first thing an unapproved brief stops. The agent writes the brief,
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
193
|
+
runs `canon brief check` until it has no blockers, then stops and reports
|
|
194
|
+
it, and nothing is built or attested for the subject until the human
|
|
195
|
+
answers. `canon evidence add` refuses (exit 1, nothing staged) a
|
|
196
|
+
scenario whose `@subject` owner, or a task whose change
|
|
190
197
|
an adopting subject, has an enforced, unapproved brief, through the same
|
|
191
198
|
check; see `canon-gate`.
|
|
192
199
|
|
|
@@ -213,7 +220,7 @@ also refuses a `false-success` (a scenario whose latest audit is
|
|
|
213
220
|
sampled audit; see `canon-gate`.
|
|
214
221
|
|
|
215
222
|
```bash
|
|
216
|
-
canon subject status demo-subject verifying \
|
|
223
|
+
canon subject status demo-subject --to verifying \
|
|
217
224
|
--override-reason "reviewer out until Monday" --actor-id lead
|
|
218
225
|
```
|
|
219
226
|
|
|
@@ -242,6 +249,39 @@ raised later, a scenario tagged later) still fails the gate. Without
|
|
|
242
249
|
`require_review`, nothing is printed and nothing changes, except that
|
|
243
250
|
`shipped` still runs the brief check above.
|
|
244
251
|
|
|
252
|
+
### Reopening a shipped subject (0.18)
|
|
253
|
+
|
|
254
|
+
A follow-up on a shipped subject (a fix, a review round, a scenario its
|
|
255
|
+
gate now misses) reopens it: reopen, add tasks to the approved change,
|
|
256
|
+
scenario, evidence, review, ship.
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
canon subject status demo-subject --to verifying \
|
|
260
|
+
--reason "follow-up: the retry path leaks a file handle" --actor-id impl
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
```
|
|
264
|
+
canon subject status: reopened `demo-subject` from shipped, recorded by `impl`: follow-up: the retry path leaks a file handle; keep the follow-up in its approved change `c-demo`, then scenario, evidence and review; shipping again runs every guard
|
|
265
|
+
canon subject status: demo-subject → verifying — written directly; nothing to promote
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
- `--reason "<one line>"` is required for `shipped → verifying` and
|
|
269
|
+
refused on any other transition (exit 2, as is a blank one). The new
|
|
270
|
+
record carries it with the actor and the change the work goes into:
|
|
271
|
+
`reopen: { reason: …, change: c-demo, actor: { agent_id: impl } }`. The
|
|
272
|
+
next status write clears it, as it clears a waiver.
|
|
273
|
+
- Inside the approved scope it needs no human: the subject's newest
|
|
274
|
+
adopted change's brief is approved, or there is no brief to answer.
|
|
275
|
+
Otherwise it refuses, exit 1, naming the brief to approve, and
|
|
276
|
+
`--override-reason` does not waive it:
|
|
277
|
+
``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; …``.
|
|
278
|
+
- The review guard does not run on a reopen (it prints `skipped … — a
|
|
279
|
+
reopen moves the subject back to fix its gaps; shipping again runs
|
|
280
|
+
it`). `verifying → shipped` afterwards runs every guard above again.
|
|
281
|
+
- `canon status` flags a shipped subject whose gate went red (whatever
|
|
282
|
+
`verifying → shipped` would now refuse it on) and its only step is
|
|
283
|
+
this reopen.
|
|
284
|
+
|
|
245
285
|
## Adopt flow
|
|
246
286
|
|
|
247
287
|
Planning docs written outside canon become change/task records via
|
|
@@ -255,25 +295,40 @@ canon subject adopt subject-domain-loop-plan \
|
|
|
255
295
|
```
|
|
256
296
|
|
|
257
297
|
A change made with `canon change new` (below) is recorded and adopted
|
|
258
|
-
already
|
|
298
|
+
already. A `tasks.md` in it is an optional work note nothing gates on
|
|
299
|
+
(`canon change new` no longer writes one); an existing one is still read:
|
|
259
300
|
`canon evidence add --task` and `canon gate task` read the file as it is,
|
|
260
301
|
and a flip re-imports that change itself. Run `canon ingest plans` for a
|
|
261
302
|
plan written outside canon, or when `canon query --kind task` must list
|
|
262
303
|
rows nobody has flipped yet.
|
|
263
304
|
|
|
264
|
-
`adopt`
|
|
265
|
-
`change_ids`, so `canon query --kind change --change-id …`
|
|
266
|
-
query --kind subject` agree on the link from both ends. It
|
|
267
|
-
the side that lacks the link, so rerunning it on a linked
|
|
268
|
-
nothing and exits `0`.
|
|
305
|
+
`adopt` adds the subject to the change's `subject_ids` and the change to
|
|
306
|
+
the Subject's `change_ids`, so `canon query --kind change --change-id …`
|
|
307
|
+
and `canon query --kind subject` agree on the link from both ends. It
|
|
308
|
+
writes only the side that lacks the link, so rerunning it on a linked
|
|
309
|
+
pair writes nothing and exits `0`.
|
|
310
|
+
|
|
311
|
+
A change may belong to several subjects (0.18). Adopting it under a
|
|
312
|
+
second subject adds that subject, never re-points the change, and says
|
|
313
|
+
so:
|
|
314
|
+
|
|
315
|
+
```
|
|
316
|
+
canon subject adopt: linked change `refactor-core` to subject `editing` as well; the change now belongs to `perception` and `editing` — written directly; nothing to promote
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Every reader joins a subject to its changes from both sides (the
|
|
320
|
+
subject's `change_ids`, and every change whose `subject_ids` names it):
|
|
321
|
+
the open-blocker rule, the status guard, `canon status`, the brief and
|
|
322
|
+
the evidence refusal. An open blocker on a shared change gates each
|
|
323
|
+
subject it belongs to. A change written before 0.18 names one subject as
|
|
324
|
+
`subject_id`; it reads, and `canon query --kind change --json` shows it,
|
|
325
|
+
as a one-element `subject_ids`.
|
|
269
326
|
|
|
270
327
|
The two records cannot be written atomically, so the subject is written
|
|
271
|
-
first:
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
and prints `canon subject adopt <change_id> --subject <id>` to complete
|
|
276
|
-
it.
|
|
328
|
+
first: a half-done adoption still has that change's open blockers
|
|
329
|
+
counted. If the change write then fails, `adopt` exits `2`, names the
|
|
330
|
+
subject that now lists the change, and prints `canon subject adopt
|
|
331
|
+
<change_id> --subject <id>` to complete it.
|
|
277
332
|
|
|
278
333
|
### Starting a new change: `canon change new`
|
|
279
334
|
|
|
@@ -286,16 +341,21 @@ canon change new add-login --subject auth --title "Add login"
|
|
|
286
341
|
|
|
287
342
|
```
|
|
288
343
|
canon change new: wrote .canon/changes/add-login/brief.md
|
|
289
|
-
canon change new: wrote .canon/changes/add-login/tasks.md
|
|
290
344
|
canon change new: recorded change `add-login` adopted into subject `auth` — written directly; nothing to promote
|
|
291
345
|
```
|
|
292
346
|
|
|
293
|
-
- **Writes** canon's own change dir, `.canon/changes/<slug
|
|
294
|
-
`brief.md` (`# <title>`,
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
347
|
+
- **Writes** canon's own change dir, `.canon/changes/<slug>/`, holding
|
|
348
|
+
`brief.md` only: the PRD skeleton (`# <title>`, then the sections intent,
|
|
349
|
+
desired outcome, user flow, step detail, acceptance, questions and
|
|
350
|
+
assumptions, out of scope, each opened by a
|
|
351
|
+
`<!-- canon:section <key> role=… -->` marker and a hint comment, with
|
|
352
|
+
the headings in the repo's `language:`), and a comment naming
|
|
353
|
+
`canon brief approve` as where the human's answer is recorded, so the
|
|
354
|
+
file starts unapproved. The intent section's first paragraph holds the
|
|
355
|
+
title, which imports as the change summary. `--kind chore` writes the
|
|
356
|
+
short chore brief instead (intent, category, invariants, checks, out of
|
|
357
|
+
scope; see `canon brief` below). No `tasks.md` is written:
|
|
358
|
+
`canon status` lists each flow step's scenarios instead.
|
|
299
359
|
`.canon/changes/` is canon's native plan source: every plan reader
|
|
300
360
|
reads it with no `plans:` entry, so `canon.yaml` needs no edit and no
|
|
301
361
|
external plans source is needed (see `canon-plan-import`).
|
|
@@ -318,9 +378,10 @@ canon change new: recorded change `add-login` adopted into subject `auth` — wr
|
|
|
318
378
|
exist, or when the slug already has a `.canon/changes/<slug>` dir
|
|
319
379
|
(active or under `.canon/changes/archive/`) or a `change` record.
|
|
320
380
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
381
|
+
A `tasks.md` is an optional work note canon never writes and nothing
|
|
382
|
+
gates on. If one exists, rows added as `- [ ] <n> <title>` are flipped with
|
|
383
|
+
`canon gate task <slug>#<n>` once evidence exists; `canon evidence add
|
|
384
|
+
--task` and the flip read the file as it is, so a new row needs no
|
|
324
385
|
`canon ingest plans` run.
|
|
325
386
|
|
|
326
387
|
A change from an external plan (an openspec change listed under
|
|
@@ -328,10 +389,59 @@ A change from an external plan (an openspec change listed under
|
|
|
328
389
|
`canon subject adopt`) gets its brief as a canon file too:
|
|
329
390
|
`.canon/changes/<change>/brief.md` holding a `Source: <path>` line that
|
|
330
391
|
points at the external document and, once the human answers, the
|
|
331
|
-
`Approved:` line. Canon never edits the external document. Such a
|
|
392
|
+
`Approved:` line that `canon brief approve` writes. Canon never edits the external document. Such a
|
|
332
393
|
pointer `brief.md` is not a change of its own: the native reader skips
|
|
333
394
|
it, so the external source keeps the change, its summary and its tasks.
|
|
334
395
|
|
|
396
|
+
### The brief: `canon brief`, `canon brief check`, `canon brief approve`
|
|
397
|
+
|
|
398
|
+
```bash
|
|
399
|
+
canon brief [--change <slug>] # state and the next one to three steps; never edits
|
|
400
|
+
canon brief check [--change <slug>] [--json] # findings; exit 0 clean, 2 blockers, 1 error
|
|
401
|
+
canon brief approve --change <slug> --by <the human> --note "<their words>"
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
Without `--change`, the repo's only live change is read. The brief is the
|
|
405
|
+
PRD `canon change new` writes; sections are found by their
|
|
406
|
+
`<!-- canon:section <key> role=… -->` marker, never by heading text. A
|
|
407
|
+
brief without markers (written before 0.19) checks as `brief.legacy`, an
|
|
408
|
+
advisory, and is read as before.
|
|
409
|
+
|
|
410
|
+
- **Blockers (structure, `brief.*`)**: a missing, duplicated or
|
|
411
|
+
out-of-order section; a flow with no steps or a duplicate step id;
|
|
412
|
+
step detail that does not match the flow ids in order; leftover
|
|
413
|
+
placeholders (a hint comment still there, an empty section,
|
|
414
|
+
`TODO`/`TBD`); a malformed waiver; for a chore, a flow/detail/acceptance
|
|
415
|
+
section, a category outside `refactor|dependency|build|ci|infra`, or
|
|
416
|
+
invariants/checks that say only "none". Once the subject is `building`
|
|
417
|
+
or later, a flow step no scenario points at (`brief.flow-uncovered`) or
|
|
418
|
+
one with no failure scenario (`brief.flow-no-failure`): these two
|
|
419
|
+
**gate shipping, never approval**.
|
|
420
|
+
- **Advisories (prose, `prose.*`)**: phrase lists, sentence and
|
|
421
|
+
paragraph length, and key-sentence marking in `role=normative`
|
|
422
|
+
sections, for `language: en` and `ko`. Never an authorship verdict.
|
|
423
|
+
- **Waivers**: `<!-- canon:waive <rule-id> <section-key> "<reason>" -->`,
|
|
424
|
+
one rule in one section, reason required; listed in the output.
|
|
425
|
+
- **`approve`** records the human's answer: it writes
|
|
426
|
+
`Approved: <who> <when> — "<words>" (brief sha256:<digest>)` into the
|
|
427
|
+
brief and records the policy approval in the same call. It refuses
|
|
428
|
+
(exit 2) while the check has structure blockers. Edit the brief
|
|
429
|
+
afterwards and every reader says `BRIEF_CHANGED_SINCE_APPROVAL` until a
|
|
430
|
+
human answers again and `approve` runs again. Run it only from the
|
|
431
|
+
human's answer, with their words as `--note`.
|
|
432
|
+
|
|
433
|
+
The optional refinement passes that fix findings before the human stop are
|
|
434
|
+
listed in `role-planner.md` in this directory.
|
|
435
|
+
|
|
436
|
+
A **chore** (`canon change new --kind chore`) changes how the subject is
|
|
437
|
+
built or kept, never what it does. Its brief is intent, category,
|
|
438
|
+
invariants, checks, out of scope; it adds no scenarios, and its evidence
|
|
439
|
+
is the subject's suite run again at the chore commit with
|
|
440
|
+
`canon evidence add --all-scenarios --report <kind>:<path> …`. That
|
|
441
|
+
supersedes the old evidence, which makes the pinned reviews stale, so an
|
|
442
|
+
independent re-review follows. Work that changes behavior is a feature
|
|
443
|
+
change, never a chore.
|
|
444
|
+
|
|
335
445
|
## Where are we: `canon status`
|
|
336
446
|
|
|
337
447
|
```bash
|
|
@@ -365,7 +475,7 @@ uses, so status and the gate cannot disagree about a gap:
|
|
|
365
475
|
`require_review` on, a `warning:` line counts the in-effect reviews that
|
|
366
476
|
predate review pinning (0.15), checked against the commit each pinned:
|
|
367
477
|
current, stale (named) and undecidable.
|
|
368
|
-
- **Brief** (0.16, 0.17) — for each `building`, `verifying` or `shipped`
|
|
478
|
+
- **Brief** (0.16, 0.17, 0.19) — for each `building`, `verifying` or `shipped`
|
|
369
479
|
subject, and each `proposed` subject whose newest adopted change has a
|
|
370
480
|
brief (a canon `brief.md`, or an external `proposal.md`), a `brief:`
|
|
371
481
|
line under it says whether the brief is approved:
|
|
@@ -375,10 +485,16 @@ uses, so status and the gate cannot disagree about a gap:
|
|
|
375
485
|
change (the last one adopted; a live dir before an archived one), and
|
|
376
486
|
only there: a line that starts with `Approved:` (indent allowed), says
|
|
377
487
|
something after it, and sits outside HTML comments (`<!-- … -->`,
|
|
378
|
-
multi-line included) and fenced code blocks (```` ``` ```` or `~~~`)
|
|
379
|
-
|
|
380
|
-
`Approved: <who> <when> — "<the human's words>"
|
|
381
|
-
|
|
488
|
+
multi-line included) and fenced code blocks (```` ``` ```` or `~~~`).
|
|
489
|
+
`canon brief approve` writes it from the human's answer, as
|
|
490
|
+
`Approved: <who> <when> — "<the human's words>" (brief sha256:<digest>)`,
|
|
491
|
+
the digest covering the brief without its `Approved:` lines. A brief
|
|
492
|
+
edited after that reads `brief: not approved (<brief> changed after its
|
|
493
|
+
approval (BRIEF_CHANGED_SINCE_APPROVAL))` and names the re-approval
|
|
494
|
+
command: the changed brief needs a new human answer. A line with no
|
|
495
|
+
digest (written before 0.19) stays approved, with an `advisory:` to
|
|
496
|
+
re-approve so it binds to the text. Fixes and follow-ups
|
|
497
|
+
stay in the approved change, so a newly adopted change is a
|
|
382
498
|
new scope: an older change's approval does not cover it. A change from
|
|
383
499
|
an external plan has its brief in the same canon file, with a
|
|
384
500
|
`Source: <path>` line pointing at the external document; an `Approved:`
|
|
@@ -392,7 +508,21 @@ uses, so status and the gate cannot disagree about a gap:
|
|
|
392
508
|
and never edits, so its approval lives in that canon file)``. With a
|
|
393
509
|
canon brief and no line it reads
|
|
394
510
|
``brief: not approved (<brief> has no `Approved:` line)``.
|
|
395
|
-
There is no record kind for it; the file is the record.
|
|
511
|
+
There is no record kind for it; the file is the record. For a brief in
|
|
512
|
+
the PRD format, `brief check: <n> blocker(s), <n> advisory(ies)` follows
|
|
513
|
+
(`canon brief` names the next step), then, per flow step of the
|
|
514
|
+
change, `flow F1` (with ` — <gaps>` when it has no scenario or no
|
|
515
|
+
failure scenario) and under it each scenario that carries
|
|
516
|
+
`@flow:<change>.F1` with its title, `[failure]` when it is, its
|
|
517
|
+
evidence (`yes`/`none`) and its review state.
|
|
518
|
+
- **Red shipped subject** (0.18) — a `shipped` subject that the
|
|
519
|
+
`verifying → shipped` guard would now refuse, by the guard's own checks
|
|
520
|
+
(owned scenarios, verdicts, required cases, audits, and, where
|
|
521
|
+
`require_review` covers `shipped`, reviews and open blockers under
|
|
522
|
+
`block_on_findings`; a gap its waiver recorded is not counted), prints
|
|
523
|
+
``gate: red — <n> <class> (first: <subject>), …; reopen with `canon subject status <id> --to verifying --reason "<why it reopens>" --actor-id <unit>` ``,
|
|
524
|
+
carries `red: [...]` in `--json`, and that reopen is its only `next:`
|
|
525
|
+
step. Open blockers count against every subject a change belongs to.
|
|
396
526
|
- **Unowned** — scenarios with no `@subject:` tag.
|
|
397
527
|
- **Audits** — how many scenarios are sampled for
|
|
398
528
|
`spec_coverage.require_audit`, confirmed, owed (no audit or
|
|
@@ -425,7 +555,7 @@ uses, so status and the gate cannot disagree about a gap:
|
|
|
425
555
|
…` (its reason adds "then `canon gate promote`" only under
|
|
426
556
|
`write.promote: manual`); an open blocker → `canon finding close …`; a
|
|
427
557
|
due, unreviewed, evidenced scenario → `canon review add …` from another
|
|
428
|
-
session; none of these → `canon subject status <id> <next status>`.
|
|
558
|
+
session; none of these → `canon subject status <id> --to <next status>`.
|
|
429
559
|
Last, unowned scenarios → `canon status`, after tagging them in their
|
|
430
560
|
`.feature` files (status re-indexes the edit; no sync step). With more
|
|
431
561
|
than one `specs.roots[]` entry, every suggested `canon scenario new`
|
|
@@ -441,15 +571,17 @@ uses, so status and the gate cannot disagree about a gap:
|
|
|
441
571
|
unanswered> (an older change's approval does not cover a new scope),
|
|
442
572
|
and the approval is never assumed: `canon evidence add` refuses <id>'s
|
|
443
573
|
scenarios and tasks until it is recorded; once the human answers,
|
|
444
|
-
<record>
|
|
445
|
-
|
|
446
|
-
<
|
|
447
|
-
``create <brief> holding a `Source: <proposal>` line
|
|
448
|
-
words
|
|
574
|
+
<record> (one call records the brief and the policy approval)``, where
|
|
575
|
+
`<record>` is ``record their words with `canon brief approve --change
|
|
576
|
+
<c> --by <the human> --note "<their words>"` ``, or, for an external
|
|
577
|
+
change, ``create <brief> holding a `Source: <proposal>` line, then
|
|
578
|
+
record their words with `canon brief approve --change <c> --by <the
|
|
579
|
+
human> --note "<their words>"` ``; for a brief edited after its
|
|
580
|
+
approval, ``re-approve with `canon brief approve …` ``.
|
|
449
581
|
It is the subject's only step, whatever its status: nothing suggests
|
|
450
582
|
`canon feature new`, `canon scenario new`, `canon evidence add`, a
|
|
451
583
|
review or a move-on for it before the human answers, and
|
|
452
|
-
`canon subject status <id> shipped` refuses on the same check. The
|
|
584
|
+
`canon subject status <id> --to shipped` refuses on the same check. The
|
|
453
585
|
approval is never assumed. A subject with no brief to answer gets no human step;
|
|
454
586
|
its `brief:` line says
|
|
455
587
|
`not approved (no adopted change; advisory, nothing to approve yet)` or
|
|
@@ -457,7 +589,7 @@ uses, so status and the gate cannot disagree about a gap:
|
|
|
457
589
|
approve yet)`` and nothing is withheld (a `proposed` subject with no
|
|
458
590
|
brief, and a `specced` subject, show no `brief:` line at all). Once nothing blocks, a
|
|
459
591
|
ship-ready verifying subject gets the agent's own
|
|
460
|
-
`canon subject status <id> shipped`; status never asks a human to ship.
|
|
592
|
+
`canon subject status <id> --to shipped`; status never asks a human to ship.
|
|
461
593
|
|
|
462
594
|
Status fails closed rather than suggest progress the gate would refuse:
|
|
463
595
|
|
|
@@ -489,7 +621,7 @@ Status fails closed rather than suggest progress the gate would refuse:
|
|
|
489
621
|
],
|
|
490
622
|
"unowned": ["cart.promo.01"],
|
|
491
623
|
"next": [
|
|
492
|
-
{ "command": "stop and report the brief in openspec/changes/c-promo/proposal.md; build and attest nothing for promo until the human answers (one answer approves brief and policy)", "why": "promo: .canon/changes/c-promo/brief.md does not exist yet: change `c-promo` comes from the external plan openspec/changes/c-promo/proposal.md, which canon reads and never edits, so its approval lives in that canon file (an older change's approval does not cover a new scope), and the approval is never assumed: … once the human answers, create .canon/changes/c-promo/brief.md holding a `Source: openspec/changes/c-promo/proposal.md` line
|
|
624
|
+
{ "command": "stop and report the brief in openspec/changes/c-promo/proposal.md; build and attest nothing for promo until the human answers (one answer approves brief and policy)", "why": "promo: .canon/changes/c-promo/brief.md does not exist yet: change `c-promo` comes from the external plan openspec/changes/c-promo/proposal.md, which canon reads and never edits, so its approval lives in that canon file (an older change's approval does not cover a new scope), and the approval is never assumed: … once the human answers, create .canon/changes/c-promo/brief.md holding a `Source: openspec/changes/c-promo/proposal.md` line, then record their words with `canon brief approve --change c-promo --by <the human> --note \"<their words>\"` (one call records the brief and the policy approval)", "actor": "human" },
|
|
493
625
|
{ "command": "canon finding close --change-id c-cart --round 1 --seq 1 --disposition fixed --resolution-sha <sha> --actor-id <unit> --session-id <session>", "why": "cart: 1 open blocker finding(s) (first: …); fix it, commit, then close it", "actor": "agent" },
|
|
494
626
|
{ "command": "canon review add --project-id root --scenario-id cart.add.02 --reviewer <reviewer> --actor-id <reviewer> --session-id <review-session> --role reviewer --pin <sha> --original-spec-ref \"<feature file>\"", "why": "cart: 1 of 2 scenario(s) lack a qualifying review; …", "actor": "agent" },
|
|
495
627
|
{ "command": "canon status", "why": "1 scenario(s) carry no @subject tag (first: cart.promo.01); add `@subject:<id>` above each in its .feature file, then check again: …", "actor": "agent" }
|
|
@@ -513,7 +645,12 @@ plan document the change comes from (the canon brief's `Source:` line,
|
|
|
513
645
|
or, before that file exists, the external `proposal.md`; omitted for a
|
|
514
646
|
canon-written change); `approval` the text after `Approved:`; and
|
|
515
647
|
`enforced` whether the missing approval blocks (a brief exists to
|
|
516
|
-
answer). The 0.16 `proposal` field is gone.
|
|
648
|
+
answer). The 0.16 `proposal` field is gone. `changed` (omitted when
|
|
649
|
+
false) says the brief was edited after its approval, `legacyApproval`
|
|
650
|
+
that the `Approved:` line carries no digest, `findings` is
|
|
651
|
+
`{ "blockers", "advisories" }` from `canon brief check` (PRD briefs
|
|
652
|
+
only), and `flow` lists each flow step as `{ "id", "change",
|
|
653
|
+
"scenarios": [{ id, title, failure, evidenced, review }], "gaps" }`.
|
|
517
654
|
|
|
518
655
|
Each `next` entry's `actor` is `agent` (its `command` is a canon command
|
|
519
656
|
line) or `human` (its `command` says what to get from the human).
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Role: implementer
|
|
2
|
+
|
|
3
|
+
You build inside an approved scope: scenarios, code, tests and evidence.
|
|
4
|
+
Declare the role with `CANON_ROLE=implementer` (or `--as implementer`).
|
|
5
|
+
Build nothing before the brief is approved: `canon evidence add` refuses an
|
|
6
|
+
unapproved brief's scenarios. If it is not approved, say so and wait; the
|
|
7
|
+
planner and the human own that.
|
|
8
|
+
|
|
9
|
+
## Commands
|
|
10
|
+
|
|
11
|
+
Every line is complete; fill the `<placeholders>`. `<root-id>` is the `id` of
|
|
12
|
+
a `specs.roots[]` entry in `canon.yaml` (`root` by default).
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
canon status --as implementer --json # once, at the start
|
|
16
|
+
canon feature new <area>.<surface> --title "<title>" --as implementer --json
|
|
17
|
+
canon scenario new <area>.<surface>.<nn> --subject <subject-id> --flow <F-id> --case <happy|edge|failure> --title "<title>" --as implementer --json
|
|
18
|
+
canon scenario new --from - --as implementer --json # many at once: NDJSON on stdin, one {"id","title","subject","flow","case",…} per line
|
|
19
|
+
canon evidence add --project-id <root-id> --all-scenarios --report <junit|cucumber|swift-testing>:<path> --kind test-run --role implementer --ref "<test command>" --actor-id <unit> --session-id <session> --as implementer --json
|
|
20
|
+
canon evidence add --project-id <root-id> --scenario-id <id> --artifact <path> --kind test-run --role implementer --ref "<what it shows>" --actor-id <unit> --session-id <session> --as implementer --json
|
|
21
|
+
canon finding add --change-id <slug> --round <n> --seq <n> --severity <blocker|should-fix|note> --reviewer <unit> --actor-id <unit> --session-id <session> --summary "<defect>" --as implementer --json
|
|
22
|
+
canon inventory sync --as implementer --json # only after a write printed NOT refreshed
|
|
23
|
+
canon gate promote --as implementer --json # only with write.promote: manual
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
After the first `status`, follow `meta.next` of each write; do not call
|
|
27
|
+
`canon status` again to learn the next step.
|
|
28
|
+
|
|
29
|
+
## Steps
|
|
30
|
+
|
|
31
|
+
1. **Scenarios.** One `feature new` per feature surface, then one
|
|
32
|
+
`scenario new` per behavior (several at once with `scenario new --from -`:
|
|
33
|
+
one JSON object per line, validated all-or-nothing), each pointing at the brief's flow step it
|
|
34
|
+
proves, with failure paths (`--case failure`) as well as `happy` and
|
|
35
|
+
`edge`. When the gate asks for a missing case (`require_cases`), add a new
|
|
36
|
+
scenario; never retag an existing one. Every bug fix adds or extends a
|
|
37
|
+
scenario. Canon indexes added, edited or deleted `.feature` files itself;
|
|
38
|
+
run `inventory sync` only when a command prints `NOT refreshed` (a format
|
|
39
|
+
violation or a duplicate id: it names each problem). Specs live at
|
|
40
|
+
`<root>/kind=feature/area=<area>/<surface>.feature` under a `specs.roots[]`
|
|
41
|
+
root (default `.canon/specs`); `reference/canon-authoring.md` has the
|
|
42
|
+
scaffold.
|
|
43
|
+
2. **Units.** Give each unit its own actor id and session id and pass them on
|
|
44
|
+
everything it records. A delegated unit attests its own work; never attest
|
|
45
|
+
another unit's work under your id.
|
|
46
|
+
3. **Code and tests.** Name tests by scenario id (`game.run.01: …`) so a test
|
|
47
|
+
report binds to its scenario. Record a defect you find in your own work
|
|
48
|
+
with `finding add` the moment you find it, before review.
|
|
49
|
+
4. **Evidence.** One test run that proves several scenarios is bound once with
|
|
50
|
+
`--all-scenarios`: one record per indexed scenario owning a case, each with
|
|
51
|
+
the verdict its own cases show; any refusal writes none. For one scenario
|
|
52
|
+
use `--scenario-id <id>` (repeatable) with `--report` or `--artifact`.
|
|
53
|
+
canon stores the bytes in `.canon/artifacts/sha256/` (commit it with the
|
|
54
|
+
ledger) and `canon gate check` re-checks them, so the working-tree report
|
|
55
|
+
may be gitignored. For a scenario about what users see, bind the screenshot
|
|
56
|
+
or recording (`--artifact <path>`). An external evaluator's audit is
|
|
57
|
+
`--kind audit`; canon never generates one. Evidence for a task of an
|
|
58
|
+
imported plan (`--task <id>`) must bind the artifact or report that proves
|
|
59
|
+
the task; a canon command is never that proof.
|
|
60
|
+
5. **Promote.** Under `write: { promote: auto }` (what `canon init` writes)
|
|
61
|
+
the record is final when `evidence add` returns. Only with
|
|
62
|
+
`write.promote: manual` (or no `write:` key) follow it with
|
|
63
|
+
`gate promote`; `meta.next` names it.
|
|
64
|
+
6. **A new record makes a signed review stale.** The add prints the reviewer
|
|
65
|
+
and the `canon review add … --pin <sha>` that re-pins it; that is the
|
|
66
|
+
reviewer's call. Do not run it yourself.
|
|
67
|
+
|
|
68
|
+
## Retry safety
|
|
69
|
+
|
|
70
|
+
Repeating `evidence add` with identical input returns the existing record
|
|
71
|
+
with `meta.unchanged: true` instead of a duplicate. After a timeout or a lost
|
|
72
|
+
response, run it again.
|
|
73
|
+
|
|
74
|
+
## Next
|
|
75
|
+
|
|
76
|
+
When every scenario has evidence, the review is the reviewer's session, in a
|
|
77
|
+
separate process: do not review your own change. A chore adds no scenarios:
|
|
78
|
+
run the suite again and bind it with `--all-scenarios`, which supersedes the
|
|
79
|
+
old evidence and makes pinned reviews stale. Reference:
|
|
80
|
+
`reference/canon-gate.md`.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Role: orchestrator
|
|
2
|
+
|
|
3
|
+
You run the loop across agents and move the subject. You write no brief,
|
|
4
|
+
code or review yourself. Declare the role with `CANON_ROLE=orchestrator` (or
|
|
5
|
+
`--as orchestrator`); your `canon status` also lists the human's steps.
|
|
6
|
+
|
|
7
|
+
## Commands
|
|
8
|
+
|
|
9
|
+
Every line is complete; fill the `<placeholders>`.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
canon status --as orchestrator --json --fields next,waitingOn # at the start and when resuming
|
|
13
|
+
canon brief approve --change <slug> --by "<the human>" --note "<their words>" --as orchestrator --json
|
|
14
|
+
canon subject status <subject-id> --to <proposed|specced|building|verifying|shipped> --actor-id <unit> --as orchestrator --json
|
|
15
|
+
canon subject status <subject-id> --to verifying --reason "<why>" --actor-id <unit> --as orchestrator --json # follow-up on a shipped subject
|
|
16
|
+
canon gate check --as orchestrator --json
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Read the state
|
|
20
|
+
|
|
21
|
+
`canon status` lists the next steps, each with the `role` that takes it
|
|
22
|
+
(`null` for the human), and `waitingOn` counts the steps held by each other
|
|
23
|
+
owner, `human` included. Call it once at the start and when resuming, not
|
|
24
|
+
after a write: after `subject status --to` or `brief approve`, the write's
|
|
25
|
+
`meta.next` and `meta.waitingOn` are the status for what it touched.
|
|
26
|
+
|
|
27
|
+
## Spawn each role
|
|
28
|
+
|
|
29
|
+
Start one agent per role with `CANON_ROLE` set in its environment, and tell
|
|
30
|
+
it to load only `reference/role-<role>.md`:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
CANON_ROLE=planner <harness> "<task: the request, the subject, the brief to write>"
|
|
34
|
+
CANON_ROLE=implementer <harness> "<task: change <slug>, scenarios and evidence>"
|
|
35
|
+
CANON_ROLE=reviewer <harness> "<task: change <slug>; review it>"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
A subagent tool that cannot set its child's environment (most in-harness
|
|
39
|
+
subagents share yours) passes the role in the task instead: tell the child
|
|
40
|
+
its role, to load only `reference/role-<role>.md`, and to pass `--as <role>`
|
|
41
|
+
on every canon call (or run `export CANON_ROLE=<role>` first in its own
|
|
42
|
+
shell).
|
|
43
|
+
|
|
44
|
+
Order: planner, then (after the human's answer) implementer, then reviewer.
|
|
45
|
+
Give each a unit: its own `--actor-id` and a session id.
|
|
46
|
+
|
|
47
|
+
The reviewer is a separate process with its own session id, never your
|
|
48
|
+
session or the implementer's under another label. Start it as a new process
|
|
49
|
+
(a new `codex exec` run, or the harness's subagent), pass nothing of the
|
|
50
|
+
implementer's context, and have it record its real session id as
|
|
51
|
+
`--session-id` (#39). A reviewer that cannot run the tests says so and does
|
|
52
|
+
not sign off.
|
|
53
|
+
|
|
54
|
+
## Relay the human stop
|
|
55
|
+
|
|
56
|
+
When `status` or `meta.next` shows a step with `actor: human` (the brief's
|
|
57
|
+
approval), stop everything for that scope, report the brief to the human in
|
|
58
|
+
its language, and wait. Do not start the implementer before the answer.
|
|
59
|
+
Record the answer with `brief approve`. Never approve it yourself and never
|
|
60
|
+
edit the policy to pass the gate. Inside an approved scope never stop for a
|
|
61
|
+
human: fixes, follow-ups and re-reviews go straight to the implementer and
|
|
62
|
+
reviewer in the same change.
|
|
63
|
+
|
|
64
|
+
## Move the subject
|
|
65
|
+
|
|
66
|
+
Move along `proposed → specced → building → verifying → shipped` with
|
|
67
|
+
`subject status --to`. It refuses a move the policy does not allow (missing
|
|
68
|
+
evidence or reviews, open blockers, an unapproved brief); fix the gap rather
|
|
69
|
+
than overriding. Move to `shipped` yourself once its guard passes, unless the
|
|
70
|
+
brief says the human ships. A follow-up on a shipped subject goes back to
|
|
71
|
+
`verifying` with `--reason`, then the implementer and reviewer in the same
|
|
72
|
+
change, then `--to shipped` again. `canon status` flags a shipped subject
|
|
73
|
+
whose gate went red. A bad `--to` fails with `error.field` `--to` and
|
|
74
|
+
`error.expected` listing the states.
|
|
75
|
+
|
|
76
|
+
Reference: `reference/canon-subject.md`.
|