@journeykit/canon 0.18.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.
@@ -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>] [--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
 
@@ -117,10 +117,10 @@ from anywhere). Any other transition is rejected by failure class, exits
117
117
  1, and leaves the record UNCHANGED (fail closed).
118
118
 
119
119
  ```bash
120
- canon subject status subject-domain-loop specced
121
- canon subject status subject-domain-loop building
122
- canon subject status subject-domain-loop verifying
123
- 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
124
124
  ```
125
125
 
126
126
  On success the status updates in place; `--json` prints the updated
@@ -149,29 +149,34 @@ empty set; so is one whose `scenario` records route away from the
149
149
  gate's rung. Each refusal prints by failure class (`uncovered-cell`),
150
150
  exits 1, and the status stays `verifying`. `retired` is not gated.
151
151
 
152
- **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
153
153
  exists and is not approved, read exactly as `canon status` reads it (one
154
154
  shared check, so status says `ship` only when this passes): the subject's
155
155
  newest adopted change has a canon `.canon/changes/<change>/brief.md`
156
- with no `Approved:` line outside HTML comments and code fences, or has no
157
- canon `brief.md` yet but comes from an external openspec plan with a
158
- `proposal.md` under the plan sources. It runs with or without a policy.
159
- The refusal is an `unreviewed-promotion` naming the subject, and says
160
- what to do:
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:
161
166
 
162
167
  ```
163
- 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 as `Approved: <who> <when> — "<their words>"` in .canon/changes/c-demo/brief.md
164
- canon subject status: refused — get the human's answer to the brief and record it as its `Approved:` line, or pass --override-reason <text> to move anyway; the waiver is recorded on the subject
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
165
170
  ```
166
171
 
167
172
  For an external change with no canon brief yet, the reason reads
168
173
  ``.canon/changes/<c>/brief.md does not exist yet: change `<c>` comes from
169
174
  the external plan <proposal>, which canon reads and never edits, so its
170
175
  approval lives in that canon file`` and the agent will ``create
171
- .canon/changes/<c>/brief.md holding a `Source: <proposal>` line and
172
- record their words there as `Approved: <who> <when> — "<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>"` ``.
173
178
 
174
- Never write the line without the human's answer. When there is no brief to
179
+ Never record the approval without the human's answer. When there is no brief to
175
180
  answer (no adopted change, or a newest change with neither a canon
176
181
  `brief.md` nor an external `proposal.md`), the move is not refused: it
177
182
  prints ``canon subject status: advisory: <id>'s brief is not approved —
@@ -185,9 +190,10 @@ the transition, not the check.
185
190
 
186
191
  This refusal is a backstop, and so is the evidence one: shipping is not
187
192
  the first thing an unapproved brief stops. The agent writes the brief,
188
- then stops and reports it, and nothing is built or attested for the
189
- subject until the human answers. `canon evidence add` refuses (exit 1,
190
- nothing staged) a scenario whose `@subject` owner, or a task whose change
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
191
197
  an adopting subject, has an enforced, unapproved brief, through the same
192
198
  check; see `canon-gate`.
193
199
 
@@ -214,7 +220,7 @@ also refuses a `false-success` (a scenario whose latest audit is
214
220
  sampled audit; see `canon-gate`.
215
221
 
216
222
  ```bash
217
- canon subject status demo-subject verifying \
223
+ canon subject status demo-subject --to verifying \
218
224
  --override-reason "reviewer out until Monday" --actor-id lead
219
225
  ```
220
226
 
@@ -250,12 +256,12 @@ gate now misses) reopens it: reopen, add tasks to the approved change,
250
256
  scenario, evidence, review, ship.
251
257
 
252
258
  ```bash
253
- canon subject status demo-subject verifying \
259
+ canon subject status demo-subject --to verifying \
254
260
  --reason "follow-up: the retry path leaks a file handle" --actor-id impl
255
261
  ```
256
262
 
257
263
  ```
258
- canon subject status: reopened `demo-subject` from shipped, recorded by `impl`: follow-up: the retry path leaks a file handle; add the follow-up as tasks in its approved change `c-demo`, then scenario, evidence and review; shipping again runs every guard
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
259
265
  canon subject status: demo-subject → verifying — written directly; nothing to promote
260
266
  ```
261
267
 
@@ -289,7 +295,8 @@ canon subject adopt subject-domain-loop-plan \
289
295
  ```
290
296
 
291
297
  A change made with `canon change new` (below) is recorded and adopted
292
- already, and editing its `tasks.md` needs no import either:
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:
293
300
  `canon evidence add --task` and `canon gate task` read the file as it is,
294
301
  and a flip re-imports that change itself. Run `canon ingest plans` for a
295
302
  plan written outside canon, or when `canon query --kind task` must list
@@ -334,16 +341,21 @@ canon change new add-login --subject auth --title "Add login"
334
341
 
335
342
  ```
336
343
  canon change new: wrote .canon/changes/add-login/brief.md
337
- canon change new: wrote .canon/changes/add-login/tasks.md
338
344
  canon change new: recorded change `add-login` adopted into subject `auth` — written directly; nothing to promote
339
345
  ```
340
346
 
341
- - **Writes** canon's own change dir, `.canon/changes/<slug>/`:
342
- `brief.md` (`# <title>`, `## Why` holding the title, which imports as
343
- the change summary, `## What Changes` with a comment saying what goes
344
- there, and a comment naming where the human's `Approved:` line goes,
345
- so the file starts unapproved) and `tasks.md` (no rows yet, so the
346
- change imports as `proposed`; the openspec checkbox row grammar).
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.
347
359
  `.canon/changes/` is canon's native plan source: every plan reader
348
360
  reads it with no `plans:` entry, so `canon.yaml` needs no edit and no
349
361
  external plans source is needed (see `canon-plan-import`).
@@ -366,9 +378,10 @@ canon change new: recorded change `add-login` adopted into subject `auth` — wr
366
378
  exist, or when the slug already has a `.canon/changes/<slug>` dir
367
379
  (active or under `.canon/changes/archive/`) or a `change` record.
368
380
 
369
- Add task rows to `tasks.md` as `- [ ] <n> <title>`, and flip each with
370
- `canon gate task <slug>#<n>` once evidence exists. `canon evidence add
371
- --task` and the flip read `tasks.md` as it is, so a new row needs no
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
372
385
  `canon ingest plans` run.
373
386
 
374
387
  A change from an external plan (an openspec change listed under
@@ -376,10 +389,59 @@ A change from an external plan (an openspec change listed under
376
389
  `canon subject adopt`) gets its brief as a canon file too:
377
390
  `.canon/changes/<change>/brief.md` holding a `Source: <path>` line that
378
391
  points at the external document and, once the human answers, the
379
- `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
380
393
  pointer `brief.md` is not a change of its own: the native reader skips
381
394
  it, so the external source keeps the change, its summary and its tasks.
382
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
+
383
445
  ## Where are we: `canon status`
384
446
 
385
447
  ```bash
@@ -413,7 +475,7 @@ uses, so status and the gate cannot disagree about a gap:
413
475
  `require_review` on, a `warning:` line counts the in-effect reviews that
414
476
  predate review pinning (0.15), checked against the commit each pinned:
415
477
  current, stale (named) and undecidable.
416
- - **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`
417
479
  subject, and each `proposed` subject whose newest adopted change has a
418
480
  brief (a canon `brief.md`, or an external `proposal.md`), a `brief:`
419
481
  line under it says whether the brief is approved:
@@ -423,10 +485,16 @@ uses, so status and the gate cannot disagree about a gap:
423
485
  change (the last one adopted; a live dir before an archived one), and
424
486
  only there: a line that starts with `Approved:` (indent allowed), says
425
487
  something after it, and sits outside HTML comments (`<!-- … -->`,
426
- multi-line included) and fenced code blocks (```` ``` ```` or `~~~`),
427
- written by the agent once the human answers, as
428
- `Approved: <who> <when> — "<the human's words>"`. Fixes and follow-ups
429
- live as tasks in the approved change, so a newly adopted change is a
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
430
498
  new scope: an older change's approval does not cover it. A change from
431
499
  an external plan has its brief in the same canon file, with a
432
500
  `Source: <path>` line pointing at the external document; an `Approved:`
@@ -440,13 +508,19 @@ uses, so status and the gate cannot disagree about a gap:
440
508
  and never edits, so its approval lives in that canon file)``. With a
441
509
  canon brief and no line it reads
442
510
  ``brief: not approved (<brief> has no `Approved:` line)``.
443
- 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.
444
518
  - **Red shipped subject** (0.18) — a `shipped` subject that the
445
519
  `verifying → shipped` guard would now refuse, by the guard's own checks
446
520
  (owned scenarios, verdicts, required cases, audits, and, where
447
521
  `require_review` covers `shipped`, reviews and open blockers under
448
522
  `block_on_findings`; a gap its waiver recorded is not counted), prints
449
- ``gate: red — <n> <class> (first: <subject>), …; reopen with `canon subject status <id> verifying --reason "<why it reopens>" --actor-id <unit>` ``,
523
+ ``gate: red — <n> <class> (first: <subject>), …; reopen with `canon subject status <id> --to verifying --reason "<why it reopens>" --actor-id <unit>` ``,
450
524
  carries `red: [...]` in `--json`, and that reopen is its only `next:`
451
525
  step. Open blockers count against every subject a change belongs to.
452
526
  - **Unowned** — scenarios with no `@subject:` tag.
@@ -481,7 +555,7 @@ uses, so status and the gate cannot disagree about a gap:
481
555
  …` (its reason adds "then `canon gate promote`" only under
482
556
  `write.promote: manual`); an open blocker → `canon finding close …`; a
483
557
  due, unreviewed, evidenced scenario → `canon review add …` from another
484
- session; none of these → `canon subject status <id> <next status>`.
558
+ session; none of these → `canon subject status <id> --to <next status>`.
485
559
  Last, unowned scenarios → `canon status`, after tagging them in their
486
560
  `.feature` files (status re-indexes the edit; no sync step). With more
487
561
  than one `specs.roots[]` entry, every suggested `canon scenario new`
@@ -497,15 +571,17 @@ uses, so status and the gate cannot disagree about a gap:
497
571
  unanswered> (an older change's approval does not cover a new scope),
498
572
  and the approval is never assumed: `canon evidence add` refuses <id>'s
499
573
  scenarios and tasks until it is recorded; once the human answers,
500
- <record> and run `canon policy approve --by <the human> --note "<their
501
- words>"` ``, where `<record>` is ``record their words as `Approved: <who>
502
- <when> — "<their words>"` in <brief>``, or, for an external change,
503
- ``create <brief> holding a `Source: <proposal>` line and record their
504
- words there as `Approved: <who> <when> — "<their 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 …` ``.
505
581
  It is the subject's only step, whatever its status: nothing suggests
506
582
  `canon feature new`, `canon scenario new`, `canon evidence add`, a
507
583
  review or a move-on for it before the human answers, and
508
- `canon subject status <id> shipped` refuses on the same check. The
584
+ `canon subject status <id> --to shipped` refuses on the same check. The
509
585
  approval is never assumed. A subject with no brief to answer gets no human step;
510
586
  its `brief:` line says
511
587
  `not approved (no adopted change; advisory, nothing to approve yet)` or
@@ -513,7 +589,7 @@ uses, so status and the gate cannot disagree about a gap:
513
589
  approve yet)`` and nothing is withheld (a `proposed` subject with no
514
590
  brief, and a `specced` subject, show no `brief:` line at all). Once nothing blocks, a
515
591
  ship-ready verifying subject gets the agent's own
516
- `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.
517
593
 
518
594
  Status fails closed rather than suggest progress the gate would refuse:
519
595
 
@@ -545,7 +621,7 @@ Status fails closed rather than suggest progress the gate would refuse:
545
621
  ],
546
622
  "unowned": ["cart.promo.01"],
547
623
  "next": [
548
- { "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 and record their words there as `Approved: <who> <when> — \"<their words>\"` and run `canon policy approve --by <the human> --note \"<their words>\"`", "actor": "human" },
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" },
549
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" },
550
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" },
551
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" }
@@ -569,7 +645,12 @@ plan document the change comes from (the canon brief's `Source:` line,
569
645
  or, before that file exists, the external `proposal.md`; omitted for a
570
646
  canon-written change); `approval` the text after `Approved:`; and
571
647
  `enforced` whether the missing approval blocks (a brief exists to
572
- 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" }`.
573
654
 
574
655
  Each `next` entry's `actor` is `agent` (its `command` is a canon command
575
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`.
@@ -0,0 +1,84 @@
1
+ # Role: planner
2
+
3
+ You turn a request into an approved scope: a subject, a change and a PRD
4
+ brief the human answers once. You build nothing. Declare the role with
5
+ `CANON_ROLE=planner` (or `--as planner`); `canon status` and `meta.next`
6
+ then show your steps and count the rest under `waitingOn`.
7
+
8
+ ## Commands
9
+
10
+ Every line is complete; fill the `<placeholders>`.
11
+
12
+ ```bash
13
+ canon status --as planner --json # once, at the start
14
+ canon subject new <subject-id> --domain <domain> --title "<title>" --actor-id <unit> --as planner --json
15
+ canon change new <slug> --subject <subject-id> --title "<title>" --as planner --json
16
+ canon change new <slug> --subject <subject-id> --kind chore --title "<title>" --as planner --json
17
+ canon subject adopt <slug> --subject <other-subject-id> --as planner --json
18
+ canon brief check --change <slug> --as planner --json
19
+ canon brief approve --change <slug> --by "<the human>" --note "<their words>" --as planner --json
20
+ ```
21
+
22
+ After the first `status`, follow `meta.next` of each write. `brief check`
23
+ lists blockers and advisories; bare `canon brief --as planner` names the next
24
+ steps.
25
+
26
+ ## Steps
27
+
28
+ 1. **Subject and change.** `subject new`, then `change new`. The change
29
+ writes the brief skeleton `.canon/changes/<slug>/brief.md` and no
30
+ `tasks.md` (`tasks.md` is an optional work note nothing gates on). A
31
+ change that also serves another subject is adopted there too
32
+ (`subject adopt`): it then belongs to both,
33
+ and its open blockers gate both. Inside an approved scope there is no new
34
+ subject or change: a fix or follow-up stays in the existing change.
35
+ 2. **Write the brief.** A PRD: intent, desired outcome, the user flow as
36
+ numbered steps `F1`, `F2`, … (sees → does → becomes), step detail per
37
+ step, acceptance per step with its failure path, questions and
38
+ assumptions, out of scope. Write it in the `language:` of `canon.yaml`;
39
+ unset, in the request's language, and add "which language?" to the
40
+ questions. Anything that could change policy goes to the questions: never
41
+ decide it yourself. A change that comes from an external plan the human
42
+ prepared (an openspec `proposal.md` read through `plans.sources`) gets a
43
+ `brief.md` whose `Source: <path>` line points at the plan; canon never
44
+ edits the plan.
45
+ 3. **Check it.** `brief check` (read-only) lists findings: fix every blocker
46
+ (structure) before you stop; advisories (prose) are optional. Five optional
47
+ refinement passes, each standing alone and none needing another first:
48
+ - `reference/brief-shape.md`: structure (sections, order, leftover template text).
49
+ - `reference/brief-clarify.md`: open questions.
50
+ - `reference/brief-harden.md`: flow and step detail agree.
51
+ - `reference/brief-align.md`: scenarios cover the flow.
52
+ - `reference/brief-polish.md`: prose.
53
+ 4. **Stop for the human.** Report the brief to the human, in the brief's
54
+ language, asking once. Build nothing for that scope (no scenarios, code
55
+ or evidence) until the human answers. The approval is never assumed, even
56
+ when the human said they cannot answer until you report: the brief is
57
+ that report. The one answer approves the brief and the policy
58
+ (`.canon/policy.yaml`, written by `canon init`), which is what the gate
59
+ grades the work against.
60
+ 5. **Record the answer** with `brief approve`. Never approve without a human answer, and never edit the policy to make
61
+ the gate pass. A brief edited after its approval is not approved
62
+ (`BRIEF_CHANGED_SINCE_APPROVAL`): it needs a new answer and a new
63
+ `canon brief approve`. A follow-up that adds or changes a step of the
64
+ brief is a scope change and needs a new answer.
65
+
66
+ When no human can answer one of the brief's questions, write the question
67
+ into Questions and assumptions, state the assumption you will build on, and
68
+ say so in your report. That applies to the brief's questions, never to the
69
+ approval.
70
+
71
+ ## Non-behavior work is a chore
72
+
73
+ A refactor, dependency, build, CI or infra change that alters nothing a user
74
+ can observe: `change new … --kind chore`. Its brief is short (intent,
75
+ category, invariants, checks, out of scope) and approved like any brief, one
76
+ human stop. A chore adds no scenarios: the
77
+ implementer runs the subject's suite again and binds it with
78
+ `canon evidence add --all-scenarios …`, then a re-review follows. Work that
79
+ changes behavior is never a chore: it is a feature change.
80
+
81
+ ## Hand-off
82
+
83
+ After the approval the implementer takes the scenarios, then the reviewer.
84
+ Do not start them in your session; the orchestrator (or the human) does.