@journeykit/canon 0.30.0 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/dist/skills/SKILL.src.md +77 -32
  2. package/dist/skills/reference/brief-align.md +2 -2
  3. package/dist/skills/reference/brief-clarify.md +2 -2
  4. package/dist/skills/reference/brief-harden.md +2 -2
  5. package/dist/skills/reference/brief-polish.md +8 -8
  6. package/dist/skills/reference/brief-shape.md +40 -9
  7. package/dist/skills/reference/canon-authoring.md +6 -6
  8. package/dist/skills/reference/canon-gate.md +45 -36
  9. package/dist/skills/reference/canon-learn.md +2 -2
  10. package/dist/skills/reference/canon-plan-import.md +2 -2
  11. package/dist/skills/reference/canon-plugins.md +6 -6
  12. package/dist/skills/reference/canon-policy.md +7 -4
  13. package/dist/skills/reference/canon-report-dashboard.md +17 -10
  14. package/dist/skills/reference/canon-review.md +24 -24
  15. package/dist/skills/reference/canon-storage.md +2 -2
  16. package/dist/skills/reference/canon-subject.md +137 -103
  17. package/dist/skills/reference/role-implementer.md +52 -36
  18. package/dist/skills/reference/role-orchestrator.md +68 -38
  19. package/dist/skills/reference/role-planner.md +99 -45
  20. package/dist/skills/reference/role-reviewer.md +69 -43
  21. package/man/canon-adapter-authorize.1 +2 -2
  22. package/man/canon-adapter-validate.1 +2 -2
  23. package/man/canon-adapter.1 +2 -2
  24. package/man/canon-artifacts-move.1 +2 -2
  25. package/man/canon-artifacts-store.1 +2 -2
  26. package/man/canon-artifacts-verify.1 +2 -2
  27. package/man/canon-artifacts.1 +2 -2
  28. package/man/canon-brief-approve.1 +17 -5
  29. package/man/canon-brief-check.1 +2 -2
  30. package/man/canon-brief-show.1 +6 -3
  31. package/man/canon-brief.1 +8 -7
  32. package/man/canon-change-new.1 +4 -4
  33. package/man/canon-change.1 +3 -3
  34. package/man/canon-completion.1 +2 -2
  35. package/man/canon-context-pack-add.1 +2 -2
  36. package/man/canon-context-pack-show.1 +2 -2
  37. package/man/canon-context-pack-verify.1 +2 -2
  38. package/man/canon-context-pack.1 +2 -2
  39. package/man/canon-context.1 +2 -2
  40. package/man/canon-dashboard.1 +2 -2
  41. package/man/canon-demo-init.1 +2 -2
  42. package/man/canon-demo-review.1 +2 -2
  43. package/man/canon-demo.1 +2 -2
  44. package/man/canon-dispatch-begin.1 +10 -10
  45. package/man/canon-dispatch-diff.1 +2 -2
  46. package/man/canon-dispatch-end.1 +2 -2
  47. package/man/canon-dispatch.1 +2 -2
  48. package/man/canon-divergence-accept.1 +15 -6
  49. package/man/canon-divergence-add.1 +6 -6
  50. package/man/canon-divergence-defer.1 +6 -6
  51. package/man/canon-divergence-list.1 +2 -2
  52. package/man/canon-divergence-resolve.1 +6 -6
  53. package/man/canon-divergence.1 +2 -2
  54. package/man/canon-evidence-add.1 +16 -16
  55. package/man/canon-evidence-approval-payload.1 +10 -10
  56. package/man/canon-evidence-audit.1 +10 -10
  57. package/man/canon-evidence.1 +3 -3
  58. package/man/canon-export.1 +2 -2
  59. package/man/canon-feature-new.1 +6 -6
  60. package/man/canon-feature.1 +2 -2
  61. package/man/canon-finding-add.1 +11 -11
  62. package/man/canon-finding-close.1 +8 -8
  63. package/man/canon-finding-list.1 +4 -4
  64. package/man/canon-finding.1 +4 -4
  65. package/man/canon-format.1 +2 -2
  66. package/man/canon-gate-check.1 +2 -2
  67. package/man/canon-gate-install-hooks.1 +2 -2
  68. package/man/canon-gate-promote.1 +2 -2
  69. package/man/canon-gate.1 +2 -2
  70. package/man/canon-ingest-plans.1 +2 -2
  71. package/man/canon-ingest-records.1 +4 -4
  72. package/man/canon-ingest-sessions.1 +2 -2
  73. package/man/canon-ingest.1 +2 -2
  74. package/man/canon-init.1 +2 -2
  75. package/man/canon-inventory-sync.1 +2 -2
  76. package/man/canon-inventory.1 +2 -2
  77. package/man/canon-ledger-compact.1 +2 -2
  78. package/man/canon-ledger.1 +2 -2
  79. package/man/canon-migrate.1 +20 -3
  80. package/man/canon-plugin-sync.1 +2 -2
  81. package/man/canon-plugin.1 +2 -2
  82. package/man/canon-policy-approve.1 +9 -3
  83. package/man/canon-policy-show.1 +2 -2
  84. package/man/canon-policy.1 +2 -2
  85. package/man/canon-prompt-add.1 +2 -2
  86. package/man/canon-prompt-show.1 +2 -2
  87. package/man/canon-prompt.1 +2 -2
  88. package/man/canon-purge.1 +2 -2
  89. package/man/canon-query.1 +5 -5
  90. package/man/canon-regime-show.1 +2 -2
  91. package/man/canon-regime.1 +2 -2
  92. package/man/canon-report-build.1 +2 -2
  93. package/man/canon-report-check.1 +2 -2
  94. package/man/canon-report-snapshot.1 +2 -2
  95. package/man/canon-report.1 +2 -2
  96. package/man/canon-retrieve.1 +5 -5
  97. package/man/canon-review-change.1 +6 -6
  98. package/man/canon-review-scenario.1 +6 -6
  99. package/man/canon-review.1 +2 -2
  100. package/man/canon-scenario-new.1 +10 -10
  101. package/man/canon-scenario.1 +2 -2
  102. package/man/canon-schema.1 +2 -2
  103. package/man/canon-search.1 +104 -0
  104. package/man/canon-selftest.1 +2 -2
  105. package/man/canon-skills-check.1 +2 -2
  106. package/man/canon-skills-doctor.1 +2 -2
  107. package/man/canon-skills-install.1 +2 -2
  108. package/man/canon-skills-show.1 +2 -2
  109. package/man/canon-skills.1 +2 -2
  110. package/man/canon-status.1 +7 -2
  111. package/man/canon-strategy-approval-payload.1 +2 -2
  112. package/man/canon-strategy-promote.1 +2 -2
  113. package/man/canon-strategy-rollback.1 +5 -5
  114. package/man/canon-strategy.1 +2 -2
  115. package/man/canon-subject-adopt.1 +4 -4
  116. package/man/canon-subject-move.1 +5 -5
  117. package/man/canon-subject-new.1 +5 -5
  118. package/man/canon-subject-show.1 +2 -2
  119. package/man/canon-subject-unadopt.1 +5 -5
  120. package/man/canon-subject.1 +2 -2
  121. package/man/canon-task-done.1 +2 -2
  122. package/man/canon-task.1 +2 -2
  123. package/man/canon-tier-age.1 +2 -2
  124. package/man/canon-tier.1 +2 -2
  125. package/man/canon-trace.1 +56 -0
  126. package/man/canon.1 +8 -2
  127. package/package.json +4 -4
@@ -64,7 +64,7 @@ canon subject new subject-domain-loop \
64
64
  `domain` enum has no constraint and accepts any slug. Run
65
65
  `canon context` to see the set in force.
66
66
  - `--summary` is optional; `--owner-role` defaults to `implementer`;
67
- `--actor-id` defaults to `canon`. A freshly-authored Subject with no
67
+ `--actor` defaults to `canon`. A freshly-authored Subject with no
68
68
  adopted changes and no tagged scenarios is a valid minimal record and
69
69
  starts at status `proposed`. `--json` prints the written record. Several
70
70
  subjects at once: `--input` takes inline JSON, a file or `-`; an array (or
@@ -76,12 +76,12 @@ canon subject new subject-domain-loop \
76
76
  ### 2. Retrieve subject-scoped strategy memory BEFORE working
77
77
 
78
78
  ```bash
79
- canon retrieve --for-role dev --domain dev --subject-id subject-domain-loop --limit 5
79
+ canon retrieve --for-role dev --domain dev --subject subject-domain-loop --limit 5
80
80
  ```
81
81
 
82
- - `--regime <key>` XOR the derived pair `--domain <d> [--subject-id <id>]`:
82
+ - `--regime <key>` XOR the derived pair `--domain <d> [--subject <id>]`:
83
83
  give the full regime key directly, OR let retrieval derive it. Never both.
84
- - With `--domain`/`--subject-id`, retrieval tries `<domain>-<subject_id>`
84
+ - With `--domain`/`--subject`, retrieval tries `<domain>-<subject_id>`
85
85
  first, then `<domain>` — a subject's own lessons win, falling back to
86
86
  the domain's shared memory when the subject is new.
87
87
  - Fail-soft: an empty/missing store degrades to an empty guidance list,
@@ -89,7 +89,7 @@ canon retrieve --for-role dev --domain dev --subject-id subject-domain-loop --li
89
89
 
90
90
  ### 3. Work → pin every artifact to `subject_id`
91
91
 
92
- - **Scenarios**: `canon scenario new <tag> --subject-id <subject-id>` emits
92
+ - **Scenarios**: `canon scenario new <tag> --subject <subject-id>` emits
93
93
  the `@subject:<subject-id>` tag; the scenario index maps the tag onto
94
94
  the scenario's `subject_id` the next time a command reads it (no sync
95
95
  step; see `canon-inventory`). A malformed/absent tag leaves it unset
@@ -97,7 +97,7 @@ canon retrieve --for-role dev --domain dev --subject-id subject-domain-loop --li
97
97
  - **Changes**: adopt imported plan changes under the Subject (see
98
98
  [Adopt flow](#adopt-flow)).
99
99
  - **Reviews / evidence / divergences**: authored as usual (`canon review
100
- add`, `canon finding add`, `canon gate`, `canon divergence …`) against
100
+ scenario`, `canon finding add`, `canon gate check`, the `canon divergence` leaves) against
101
101
  the Subject's scenarios and adopted changes — the join spine carries
102
102
  them back through `scenario_id` and `change_id`. See `canon-review`
103
103
  for the finish-a-subject order.
@@ -113,7 +113,7 @@ this subject and domain — the exact memory step 2 retrieves next time.
113
113
 
114
114
  ## Status lifecycle + the shipped evidence gate
115
115
 
116
- `canon subject move <id> --to <state> [--override-reason <text>] [--reason <text>] [--actor-id <id>]`
116
+ `canon subject move <id> --to <state> [--override-reason <text>] [--reason <text>] [--actor <id>]`
117
117
  performs a policy-gated transition.
118
118
  The legal chain:
119
119
 
@@ -169,7 +169,7 @@ exits 1, and the status stays `verifying`. `retired` is not gated.
169
169
  **The brief (0.16, 0.17, 0.19):** `verifying → shipped` also refuses where a brief
170
170
  exists and is not approved, read exactly as `canon status` reads it (one
171
171
  shared check, so status says `ship` only when this passes): the subject's
172
- scope (its newest adopted change, except pending drafts, see below) has a canon `.canon/changes/<change>/brief.md`
172
+ scope (every change the subject answers for, see below) has a canon `.canon/changes/<change>/brief.md`
173
173
  with no `Approved:` line outside HTML comments and code fences, whose
174
174
  `Approved:` line carries a digest the brief's text no longer matches
175
175
  (`BRIEF_CHANGED_SINCE_APPROVAL`; the brief was edited after the human
@@ -207,36 +207,50 @@ the transition, not the check.
207
207
 
208
208
  This refusal is a backstop, and so is the evidence one: shipping is not
209
209
  the first thing an unapproved brief stops. The agent writes the brief,
210
- runs `canon brief check` until it has no blockers, then stops and reports
211
- it, and nothing is built or attested for the subject until the human
210
+ runs `canon brief check` until it has no blockers, then shows it to the
211
+ human and asks, and nothing is built or attested for the subject until the human
212
212
  answers. `canon evidence add` refuses (exit 1, nothing staged) a
213
213
  scenario whose `@subject` owner, or a task whose change
214
214
  an adopting subject, has an enforced, unapproved brief, through the same
215
215
  check; see `canon-gate`.
216
216
 
217
- **Approval is scoped to the scenarios a change names.** The evidence
218
- refusal asks the approval of each change the scenario's `@flow:<change>.<F-id>`
219
- tags name. An untagged scenario answers to the subject's newest adopted
220
- change whose brief carries an `Approved:` line; if no adopted change ever
221
- did, a scenario with evidence recorded before the subject first adopted a
222
- change (and without a brief override) needs no brief, and one without
223
- answers to the newest adopted change. A task answers to its own
224
- change. So planning the next change for a `building`, `verifying` or
225
- `shipped` subject does not freeze re-verification of its existing
226
- scenarios; scenarios written for the new change (`canon scenario new
227
- --flow <F-id>` tags them) wait for its approval. The ship guard above is
228
- unchanged, except for a pending draft: the subject cannot ship until its
229
- scope is approved, where the scope is found walking the adopted changes from
230
- the newest back: a change whose brief was never approved and that no scenario
231
- of the subject is tagged `@flow:<change>.*` for is a pending draft and is
232
- skipped; the first approved or tagged change is the scope. A tagged unapproved
233
- draft anywhere above the newest approved change therefore blocks. The reopen
234
- and ship transitions print one line naming every change set aside (stderr, and
235
- `meta.warnings` with `--json`): ``<change> is a pending draft (brief not
236
- approved, no scenario tagged `@flow:<change>.*`): not part of this transition,
237
- which reads the subject's newest approved or tagged change``, and `canon status`
238
- marks them "not yet part of the subject's scope" (`pendingDrafts` in JSON)
239
- while still asking the human to answer a draft's brief.
217
+ **A subject answers for the changes it tags, approved or adopted as chores
218
+ (0.31).** Reopen, ship and `canon status` judge every adopted change that is
219
+ (1) *tagged*: a scenario the subject owns is tagged `@flow:<change>.*`;
220
+ (2) *approved*: its brief has an `Approved:` line, even if the brief was
221
+ edited since, and even if the tags were removed; or (3) a *chore*, which has
222
+ no scenario to tag and so governs from the moment it is adopted. They pass
223
+ only when every one of them reads as approved now; an unapproved one and one
224
+ edited after its approval (`BRIEF_CHANGED_SINCE_APPROVAL`) are named for
225
+ different reasons, and the answer does not depend on the order the changes
226
+ were adopted or approved. The ship guard's brief-structure check runs over
227
+ all of them. When several block, the hint is one
228
+ ``canon brief approve <id>+ --approver <the human> --note "<their words>"``
229
+ line naming every blocking change; a reopen refusal cannot be passed with
230
+ `--override-reason`, a ship brief refusal can. A change never approved, not a
231
+ chore and tagged by no scenario is a *pending draft* and blocks nothing: the
232
+ reopen and ship transitions print one line naming every pending draft
233
+ (stderr, and `meta.warnings` with `--json`): ``<change> is a pending draft
234
+ (never approved, not a chore, no scenario tagged `@flow:<change>.*`): not part
235
+ of this transition, which judges the subject's tagged, approved and chore
236
+ changes``, and `canon status` marks them "not yet part of the subject's
237
+ scope" (`pendingDrafts` in JSON) while still asking the human to answer a
238
+ draft's brief. A subject with no such change at all keeps the older rule: its
239
+ newest adopted change awaits its first approval.
240
+
241
+ **Evidence follows the scenario.** A scenario tagged `@flow:<change>.<F-id>`
242
+ answers only to the changes its tags name (counting only changes the subject
243
+ adopted); another change's edit after its approval never blocks it. An untagged
244
+ scenario (one whose tags were removed reads the same) answers to every adopted
245
+ change with an `Approved:` line, a chore included, so an approved change edited
246
+ since refuses it (`BRIEF_CHANGED_SINCE_APPROVAL`); an unapproved chore blocks
247
+ reopen and ship, never evidence. When no adopted change has an approved line,
248
+ a scenario with evidence recorded before the subject first adopted a change
249
+ (and without a brief override) needs no brief, and one without answers to the
250
+ newest adopted change. A task answers to its own change. So planning the next
251
+ change for a `building`, `verifying` or `shipped` subject does not freeze
252
+ re-verification of its existing scenarios; scenarios written for the new
253
+ change (`canon scenario new --flow <F-id>` tags them) wait for its approval.
240
254
 
241
255
  ### The review guard (`spec_coverage.require_review`, opt-in)
242
256
 
@@ -262,7 +276,7 @@ sampled audit; see `canon-gate`.
262
276
 
263
277
  ```bash
264
278
  canon subject move demo-subject --to verifying \
265
- --override-reason "reviewer out until Monday" --actor-id lead
279
+ --override-reason "reviewer out until Monday" --actor lead
266
280
  ```
267
281
 
268
282
  `--override-reason "<one line>"` waives ONLY `unreviewed-promotion` (the
@@ -279,7 +293,7 @@ status_override:
279
293
  reason: reviewer out until Monday
280
294
  waived:
281
295
  - { class: unreviewed-promotion, subject: world.demo.01 }
282
- actor: { agent_id: lead } # from --actor-id, default `canon`
296
+ actor: { agent_id: lead } # from --actor, default `canon`
283
297
  ```
284
298
 
285
299
  A blank or multi-line reason exits 2. The waiver is cleared on the
@@ -298,7 +312,7 @@ scenario, evidence, review, ship.
298
312
 
299
313
  ```bash
300
314
  canon subject move demo-subject --to verifying \
301
- --reason "follow-up: the retry path leaks a file handle" --actor-id impl
315
+ --reason "follow-up: the retry path leaks a file handle" --actor impl
302
316
  ```
303
317
 
304
318
  ```output
@@ -313,9 +327,8 @@ canon subject move: demo-subject → verifying — written directly; nothing to
313
327
  records the change the work goes into:
314
328
  `reopen: { reason: …, change: c-demo, actor: { agent_id: impl } }`. The
315
329
  next status write clears both, as it clears a waiver.
316
- - Inside the approved scope it needs no human: the subject's scope (its
317
- newest adopted change, walking back past pending drafts no scenario is
318
- tagged for) has an approved brief, or there
330
+ - Inside the approved scope it needs no human: every change the subject
331
+ answers for (tagged, approved or chore) has an approved brief, or there
319
332
  is no brief to answer.
320
333
  Otherwise it refuses, exit 1, naming the brief to approve, and
321
334
  `--override-reason` does not waive it:
@@ -342,19 +355,19 @@ Planning docs written outside canon become change/task records via
342
355
  ```bash
343
356
  canon ingest plans --repo . # import → change/task rows
344
357
  canon subject adopt subject-domain-loop \
345
- --change-id subject-domain-loop-plan # link change → subject
358
+ --change subject-domain-loop-plan # link change → subject
346
359
  ```
347
360
 
348
361
  A change made with `canon change new` (below) is recorded and adopted
349
362
  already. A `tasks.md` in it is an optional work note nothing gates on
350
363
  (`canon change new` no longer writes one); an existing one is still read:
351
- `canon evidence add --task-id` and `canon task done` read the file as it is,
364
+ `canon evidence add --task` and `canon task done` read the file as it is,
352
365
  and a flip re-imports that change itself. Run `canon ingest plans` for a
353
366
  plan written outside canon, or when `canon query --kind task` must list
354
367
  rows nobody has flipped yet.
355
368
 
356
369
  `adopt` adds the subject to the change's `subject_ids` and the change to
357
- the Subject's `change_ids`, so `canon query --kind change --change-id <id>`
370
+ the Subject's `change_ids`, so `canon query --kind change --change <id>`
358
371
  and `canon query --kind subject` agree on the link from both ends. It
359
372
  writes only the side that lacks the link, so rerunning it on a linked
360
373
  pair writes nothing and exits `0`.
@@ -379,27 +392,28 @@ The two records cannot be written atomically, so the subject is written
379
392
  first: a half-done adoption still has that change's open blockers
380
393
  counted. If the change write then fails, `adopt` exits `2`, names the
381
394
  subject that now lists the change, and prints `canon subject adopt
382
- <subject_id> --change-id <change_id>` to complete it.
395
+ <subject_id> --change <change_id>` to complete it.
383
396
 
384
- `canon change new <slug> --subject-id <S>` and `canon subject adopt
385
- <S> --change-id <change>` print a warning on stderr, and in JSON
397
+ `canon change new <slug> --subject <S>` and `canon subject adopt
398
+ <S> --change <change>` print a warning on stderr, and in JSON
386
399
  `meta.warnings`, when `<S>` is `building`, `verifying` or `shipped` and the
387
400
  change's brief is not approved. The warning says the subject's existing
388
401
  scenarios keep accepting evidence and the subject can still be reopened and
389
402
  shipped for its approved scope while no scenario is tagged `@flow:<change>.*`;
390
403
  once one is, the subject cannot ship until the brief is approved. The draft is
391
- pending until then: the reopen and ship guards read the newest adopted change
392
- with an `Approved:` line instead, so a shipped subject can reopen, re-verify
393
- and ship again while the draft waits; `canon status` still lists it.
404
+ pending until then: the reopen and ship guards judge only the changes the
405
+ subject answers for (tagged, approved or chore), so a shipped subject can
406
+ reopen, re-verify and ship again while the draft waits; `canon status` still
407
+ lists it.
394
408
 
395
409
  **Done when:** `canon subject show <id>` lists the change in the subject's
396
- `change_ids` and `canon query --kind change --change-id <change>` lists the
410
+ `change_ids` and `canon query --kind change --change <change>` lists the
397
411
  subject in the change's `subject_ids`.
398
412
 
399
413
  ### Taking a draft change back out: `canon subject unadopt`
400
414
 
401
415
  ```text
402
- canon subject unadopt <S> --change-id <change> --json
416
+ canon subject unadopt <S> --change <change> --json
403
417
  ```
404
418
 
405
419
  Removes the link on both records and keeps the change and its brief, so the
@@ -416,7 +430,7 @@ For a change that does not exist yet, one command writes it and records
416
430
  it adopted:
417
431
 
418
432
  ```bash
419
- canon change new add-login --subject-id auth --title "Add login"
433
+ canon change new add-login --subject auth --title "Add login"
420
434
  ```
421
435
 
422
436
  ```output
@@ -427,11 +441,10 @@ canon change new: recorded change `add-login` adopted into subject `auth` — wr
427
441
  - **Writes** canon's own change dir, `.canon/changes/<slug>/`, holding
428
442
  `brief.md` only: the PRD skeleton (`# <title>`, then the sections intent,
429
443
  desired outcome, user flow, step detail, acceptance, questions and
430
- assumptions, out of scope, each opened by a
431
- `<!-- canon:section <key> role=… -->` marker and a hint comment, with
432
- the headings in the repo's `language:`), and a comment naming
433
- `canon brief approve` as where the human's answer is recorded, so the
434
- file starts unapproved. The intent section's first paragraph holds the
444
+ assumptions, out of scope, each an empty `##` section under its English
445
+ heading; no comment, no hint: `canon brief show` and the brief-shape skill
446
+ carry each section's guidance, in the repo's `language:`), so the file
447
+ starts unapproved. The intent section's first paragraph holds the
435
448
  title, which imports as the change summary. `--change-kind chore` writes the
436
449
  short chore brief instead (intent, category, invariants, checks, out of
437
450
  scope; see `canon brief` below). No `tasks.md` is written:
@@ -452,7 +465,7 @@ canon change new: recorded change `add-login` adopted into subject `auth` — wr
452
465
  cannot be written atomically. If the change record fails after the
453
466
  subject record was written, the command exits `2`, keeps the change
454
467
  dir and that one subject record, and prints the repair, `canon ingest
455
- plans && canon subject adopt <id> --change-id <slug>`, which completes
468
+ plans && canon subject adopt <id> --change <slug>`, which completes
456
469
  the link and is safe to rerun.
457
470
  - **Refuses** with exit `2`, writing nothing, when the subject does not
458
471
  exist, or when the slug already has a `.canon/changes/<slug>` dir
@@ -461,7 +474,7 @@ canon change new: recorded change `add-login` adopted into subject `auth` — wr
461
474
  A `tasks.md` is an optional work note canon never writes and nothing
462
475
  gates on. If one exists, rows added as `- [ ] <n> <title>` are flipped with
463
476
  `canon task done <slug>#<n>` once evidence exists; `canon evidence add
464
- --task-id` and the flip read the file as it is, so a new row needs no
477
+ --task` and the flip read the file as it is, so a new row needs no
465
478
  `canon ingest plans` run.
466
479
 
467
480
  A change from an external plan (an openspec change listed under
@@ -482,19 +495,35 @@ brief (`data.next`, `meta.next`).
482
495
  ```text
483
496
  canon brief show [<slug>] # state and the next one to three steps; never edits
484
497
  canon brief check [<slug>] [--json] # findings; exit 0 clean, 2 blockers, 1 error
485
- canon brief approve <slug> --approver <the human> --note "<their words>"
498
+ canon brief approve <slug> <slug> --approver <the human> --note "<their words>"
486
499
  ```
487
500
 
501
+ `canon brief approve` takes one or more slugs and records the same name, the
502
+ same answer and one timestamp on every brief and on the policy approval
503
+ (0.31). Every slug is checked before anything is written (a brief that
504
+ exists and reads, no blocker, a one-line name and answer, a body that does not
505
+ end inside an open code fence); one refused slug refuses the call, exit 2
506
+ `BRIEF_APPROVE_REFUSED` naming each refused slug with its own code, and
507
+ nothing is written. A repeated slug counts once. With one slug the output is as
508
+ before; with several, `--json` carries one entry per slug (`written`,
509
+ `unchanged`, `failed`, `skipped`). A disk error while writing exits 1 and
510
+ leaves the slugs already written approved. The call is human-only for every
511
+ slug; the `HUMAN_ONLY` hint gives the human one line with all the slugs.
512
+
488
513
  Without the change operand, the repo's only live change is read. The brief is the
489
- PRD `canon change new` writes; sections are found by their
490
- `<!-- canon:section <key> role=… -->` marker, never by heading text. A
491
- brief without markers (written before 0.19) checks as `brief.legacy`, an
492
- advisory, and is read as before.
514
+ PRD `canon change new` writes; sections are found by their English `##`
515
+ heading, never by a comment. A brief with fewer than two known headings
516
+ (written before 0.19) checks as `brief.legacy`, an advisory, and is read as
517
+ before. A brief with `<!-- canon:… -->` comments or Korean headings is in the
518
+ 0.30 format: `brief check` gives it one blocker (`brief.old-format`) and
519
+ `canon migrate` rewrites it; `status`, `gate check`, `context` and `brief show`
520
+ warn, and a command that writes and judges it is refused
521
+ (`OLD_BRIEF_FORMAT`).
493
522
 
494
523
  - **Blockers (structure, `brief.*`)**: a missing, duplicated or
495
524
  out-of-order section; a flow with no steps or a duplicate step id;
496
525
  step detail that does not match the flow ids in order; leftover
497
- placeholders (a hint comment still there, an empty section,
526
+ placeholders (an empty section,
498
527
  `TODO`/`TBD`); a malformed waiver; for a chore, a flow/detail/acceptance
499
528
  section, a category outside `refactor|dependency|build|ci|infra`, or
500
529
  invariants/checks that say only "none". Once the subject is `building`
@@ -507,8 +536,8 @@ advisory, and is read as before.
507
536
  length budgets, in the normative sections plus Intent and Questions;
508
537
  sentence, paragraph and key-sentence rules in normative sections.
509
538
  Never an authorship verdict. `canon skills show brief-polish` has each rule.
510
- - **Waivers**: `<!-- canon:waive <rule-id> <section-key> "<reason>" -->`,
511
- one rule in one section, reason required; add `match="<text>"` to waive
539
+ - **Waivers**: entries of the frontmatter `waive` list (`rule`, `section`,
540
+ `reason`), one rule in one section, reason required; add `match` to waive
512
541
  only the findings whose matched text contains it. Listed in the output.
513
542
  - **`approve`** records the human's answer: it writes
514
543
  `Approved: <who> <when> — "<words>" (brief sha256:<digest>)` into the
@@ -518,7 +547,7 @@ advisory, and is read as before.
518
547
  human answers again and `approve` runs again. Run it only from the
519
548
  human's answer, with their words as `--note`.
520
549
 
521
- The optional refinement passes that fix findings before the human stop are
550
+ The optional refinement passes that fix findings before the approval step are
522
551
  listed in `role-planner.md` in this directory.
523
552
 
524
553
  A **chore** (`canon change new --change-kind chore`) changes how the subject is
@@ -568,14 +597,14 @@ uses, so status and the gate cannot disagree about a gap:
568
597
  `require_review` on, a `warning:` line counts the in-effect reviews that
569
598
  predate 0.23 review pinning and wait for `canon migrate`.
570
599
  - **Brief** (0.16, 0.17, 0.19) — for each `building`, `verifying` or `shipped`
571
- subject, and each `proposed` subject whose newest adopted change has a
600
+ subject, and each `proposed` subject whose adopted change has a
572
601
  brief (a canon `brief.md`, or an external `proposal.md`), a `brief:`
573
- line under it says whether the brief is approved:
602
+ line per change the subject answers for (tagged, approved or chore) says
603
+ whether that brief is approved:
574
604
  `brief: approved in <brief> — <the recorded words>`, or
575
605
  `brief: not approved (…)`. Approval is an `Approved:` line in
576
- `.canon/changes/<change>/brief.md` for the subject's newest adopted
577
- change (the last one adopted; a live dir before an archived one), and
578
- only there: a line that starts with `Approved:` (indent allowed), says
606
+ `.canon/changes/<change>/brief.md` for the change (a live dir before an
607
+ archived one), and only there: a line that starts with `Approved:` (indent allowed), says
579
608
  something after it, and sits outside HTML comments (`<!-- … -->`,
580
609
  multi-line included) and fenced code blocks (```` ``` ```` or `~~~`).
581
610
  `canon brief approve` writes it from the human's answer, as
@@ -612,7 +641,7 @@ uses, so status and the gate cannot disagree about a gap:
612
641
  (owned scenarios, verdicts, required cases, audits, and, where
613
642
  `require_review` covers `shipped`, reviews and open blockers under
614
643
  `block_on_findings`; a gap its waiver recorded is not counted), prints
615
- ``gate: red — <n> <class> (first: <subject>), …; reopen with `canon subject move <id> --to verifying --reason "<why it reopens>" --actor-id <unit>` ``,
644
+ ``gate: red — <n> <class> (first: <subject>), …; reopen with `canon subject move <id> --to verifying --reason "<why it reopens>" --actor <unit>` ``,
616
645
  carries `red: [...]` in `--json`, and that reopen is its only `next:`
617
646
  step. Open blockers count against every subject a change belongs to.
618
647
  - **Unowned** — scenarios with no `@subject:` tag.
@@ -637,19 +666,19 @@ uses, so status and the gate cannot disagree about a gap:
637
666
  first, then building, specced, proposed, shipped: an unapproved brief
638
667
  (building and later, and proposed once its change has a brief)
639
668
  → the human step below, which is then the subject's
640
- only step; no scenario → `canon scenario new --subject-id <id>` (and, when no
669
+ only step; no scenario → `canon scenario new --subject <id>` (and, when no
641
670
  `.feature` file holds a scenario yet, `canon feature new` first, unless
642
671
  every subject waits on its brief); a missing case → `canon scenario new
643
- <surface>.<next nn> … --case <case>`; (building and later) an
644
- unevidenced or divergent scenario → `canon evidence add --scenario-id
645
- …` (its reason adds "then `canon gate promote`" only under
672
+ <surface>.<nn> --title "<what happens on this path>" --subject <id> --case <case>`; (building and later) an
673
+ unevidenced or divergent scenario → `canon evidence add --scenario <id>
674
+ --project <root> --class test-run --role implementer --ref "<test command>"` (its reason adds "then `canon gate promote`" only under
646
675
  `write.promote: manual`); an open blocker → `canon finding close <change>`; a
647
676
  due, unreviewed, evidenced scenario → `canon review scenario <id>` from another
648
677
  session; none of these → `canon subject move <id> --to <next status>`.
649
678
  Last, unowned scenarios → `canon status`, after tagging them in their
650
679
  `.feature` files (status re-indexes the edit; no sync step). With more
651
680
  than one `specs.roots[]` entry, every suggested `canon scenario new`
652
- carries `--project-id`: the owning root for a missing case, `<root-id>`
681
+ carries `--project`: the owning root for a missing case, `<root-id>`
653
682
  (the reason lists the roots) for a subject that owns no scenario yet.
654
683
  - **The one human step.** Every step is the agent's to run except one,
655
684
  and it comes only once the brief is clean: while `canon brief check
@@ -659,7 +688,7 @@ uses, so status and the gate cannot disagree about a gap:
659
688
  newest change's canon
660
689
  `brief.md` has no `Approved:` line, or it has no canon `brief.md` yet
661
690
  and comes from an external openspec `proposal.md`) and clean gets
662
- `human: stop and report the brief in <brief>; build and attest nothing for <id> until the human answers (one answer approves brief and policy)`,
691
+ `human: show the brief in <brief> to the human and ask; build and attest nothing for <id> until the human answers (one answer approves brief and policy)`,
663
692
  where `<brief>` is the canon `brief.md`, or the external `proposal.md`
664
693
  when no canon brief exists yet. Its reason reads ``<id>: <why
665
694
  unanswered> (an older change's approval does not cover a new scope),
@@ -701,17 +730,17 @@ Status fails closed rather than suggest progress the gate would refuse:
701
730
 
702
731
  ```json
703
732
  {
704
- "statusVersion": 3,
733
+ "statusVersion": 4,
705
734
  "canonVersion": "0.29.0",
706
735
  "policy": { "present": true, "spec_coverage": { "require_evidence": true, "scope": [], "exclude_lanes": [], "require_cases": ["failure"], "require_review": { "scope": ["verifying", "shipped"], "distinct_actor": true, "block_on_findings": true } } },
707
736
  "warnings": [],
708
737
  "subjects": [
709
738
  { "id": "cart", "title": "Cart", "status": "building", "scenarios": 2, "excluded": 0, "evidenced": 2, "divergent": 0,
710
739
  "reviewed": 1, "staleReviews": 0, "unsignedReviews": 0, "reviewDue": true, "openBlockers": 1, "missingCases": [],
711
- "brief": { "approved": true, "enforced": false, "change": "c-cart", "brief": ".canon/changes/c-cart/brief.md", "briefExists": true, "approval": "Journey 2026-10-11 — \"approved\"" } },
740
+ "briefs": [{ "status": "approved", "reason": "tagged", "enforced": false, "change": "c-cart", "brief": ".canon/changes/c-cart/brief.md", "briefExists": true, "approval": "Journey 2026-10-11 — \"approved\"" }] },
712
741
  { "id": "promo", "title": "Promo", "status": "verifying", "scenarios": 1, "excluded": 0, "evidenced": 1, "divergent": 0,
713
742
  "reviewed": 1, "staleReviews": 0, "unsignedReviews": 0, "reviewDue": true, "openBlockers": 0, "missingCases": [],
714
- "brief": { "approved": false, "enforced": true, "change": "c-promo", "brief": ".canon/changes/c-promo/brief.md", "briefExists": false, "source": "openspec/changes/c-promo/proposal.md" } }
743
+ "briefs": [{ "status": "unapproved", "reason": "tagged", "enforced": true, "change": "c-promo", "brief": ".canon/changes/c-promo/brief.md", "briefExists": false, "source": "openspec/changes/c-promo/proposal.md" }] }
715
744
  ],
716
745
  "unowned": ["cart.promo.01"]
717
746
  }
@@ -723,8 +752,8 @@ first, with `meta.nextOmitted` counting what was cut and `meta.waitingOn`
723
752
 
724
753
  ```json
725
754
  "next": [
726
- { "command": "stop and report the brief in openspec/changes/c-promo/proposal.md; build and attest nothing for promo until the human answers; then record their words with `canon brief approve c-promo --approver <the human> --note \"<their words>\"`", "argv": [], "why": "promo: the brief is not approved", "actor": "human", "role": null },
727
- { "command": "canon finding close c-cart --round 1 --seq 1 --disposition fixed --actor-id <unit> --session-id <session>", "argv": [], "why": "cart: 1 open blocker finding(s); fix it, commit, then close it", "actor": "agent", "role": "implementer" },
755
+ { "command": "show the brief in openspec/changes/c-promo/proposal.md; build and attest nothing for promo until the human answers; then record their words with `canon brief approve c-promo --approver <the human> --note \"<their words>\"`", "argv": [], "why": "promo: the brief is not approved", "actor": "human", "role": null },
756
+ { "command": "canon finding close c-cart --round 1 --seq 1 --disposition fixed --actor <unit> --session <session>", "argv": [], "why": "cart: 1 open blocker finding(s); fix it, commit, then close it", "actor": "agent", "role": "implementer" },
728
757
  { "command": "canon status", "argv": ["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", "role": "planner" }
729
758
  ]
730
759
  ```
@@ -740,24 +769,28 @@ skills install`.
740
769
  Each `missingCases` entry is `{ "projectId", "surface", "case" }`: the
741
770
  spec root, the `<area>.<surface>`, and the required case it lacks.
742
771
 
743
- `brief` is present for `building`, `verifying` and `shipped` subjects,
744
- and for a `proposed` subject whose newest adopted change has a brief (a
745
- canon `brief.md` or an external `proposal.md`). `change` is the newest
746
- adopted change (omitted when the subject adopts none); `brief` its canon
747
- `.canon/changes/<change>/brief.md`, the one file the approval is read
748
- from (omitted when the change has no brief to answer); `briefExists`
749
- (always present) whether that file exists (`false` for an external
750
- change whose canon brief is still to be created); `source` the external
751
- plan document the change comes from (the canon brief's `Source:` line,
752
- or, before that file exists, the external `proposal.md`; omitted for a
753
- canon-written change); `approval` the text after `Approved:`; and
754
- `enforced` whether the missing approval blocks (a brief exists to
755
- answer). The 0.16 `proposal` field is gone. `changed` (omitted when
756
- false) says the brief was edited after its approval, `legacyApproval`
757
- that the `Approved:` line carries no digest, `findings` is
758
- `{ "blockers", "advisories" }` from `canon brief check` (PRD briefs
759
- only), and `flow` lists each flow step as `{ "id", "change",
760
- "scenarios": [{ id, title, failure, evidenced, review }], "gaps" }`.
772
+ `briefs` is present for `building`, `verifying` and `shipped` subjects,
773
+ and for a `proposed` subject whose adopted change has a brief (a
774
+ canon `brief.md` or an external `proposal.md`); it is a list with one entry
775
+ per change the subject answers for, in adoption order (0.31). Each entry has
776
+ `change`; `brief`, its canon `.canon/changes/<change>/brief.md`, the one file
777
+ the approval is read from (omitted when the change has no brief to answer);
778
+ `status`: `approved`, `unapproved` or `changed` (edited after its approval);
779
+ `reason` why the subject answers for it: `tagged` (a scenario it owns is
780
+ tagged `@flow:<change>.*`), `approved` (its brief has an `Approved:` line) or
781
+ `chore`; `briefExists` (always present) whether that file exists (`false` for
782
+ an external change whose canon brief is still to be created); `source` the
783
+ external plan document the change comes from (the canon brief's `Source:`
784
+ line, or, before that file exists, the external `proposal.md`; omitted for a
785
+ canon-written change); `approval` the text after `Approved:`; `enforced`
786
+ whether the missing approval blocks (a brief exists to answer);
787
+ `legacyApproval` that the `Approved:` line carries no digest; and `findings`,
788
+ `{ "blockers", "advisories" }` from `canon brief check` (PRD briefs only).
789
+ A subject with no such change has one entry, its newest adopted change, with no
790
+ `reason` (or, adopting none, an entry with no `change`). `pendingDrafts` lists
791
+ only the pending drafts, and `flow` lists each flow step of every tagged
792
+ change as `{ "id", "change", "scenarios": [{ id, title, failure, evidenced,
793
+ review }], "gaps" }`.
761
794
 
762
795
  Each `meta.next` entry's `actor` is `agent` (its `command` is a canon command
763
796
  line) or `human` (its `command` says what to get from the human).
@@ -768,7 +801,8 @@ changes only when a field changes meaning or is removed. It became `2`
768
801
  in 0.16, when `next[].command` stopped always being a command line
769
802
  (`actor` tells which), and `brief` was added alongside. It is `3` since
770
803
  0.17, when `brief.proposal` was removed: the canon `brief.md` is
771
- `brief.brief`, with `briefExists` and `source`.
804
+ `brief.brief`, with `briefExists` and `source`. It is `4` since 0.31, when the
805
+ `brief` object became the `briefs` list.
772
806
 
773
807
  `subject new`, `adopt`, `unadopt` and `move` write their records directly; the
774
808
  human-readable success line ends `— written directly; nothing to