@journeykit/canon 0.24.1 → 0.26.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.
@@ -23,7 +23,8 @@ a feature, a one-line request included. A human is needed once per scope:
23
23
  the one thing you never assume, even when the human said they cannot
24
24
  answer until you report: the brief is that report. The one answer
25
25
  approves the brief and the policy, `.canon/policy.yaml`, which is what
26
- the gate grades the work against. Record it only from the human's answer:
26
+ the gate grades the work against. The human may also approve it on the `canon dashboard` briefs page; you never use that form.
27
+ Record it only from the human's answer:
27
28
  `canon brief approve --change-id <slug> --by <the human> --note "<their words>"`.
28
29
  Never edit the policy to make the gate pass: a policy that differs from
29
30
  the approved one fails closed (`POLICY_CHANGED_SINCE_APPROVAL`, not
@@ -31,6 +32,16 @@ a feature, a one-line request included. A human is needed once per scope:
31
32
  approval is not approved (`BRIEF_CHANGED_SINCE_APPROVAL`): it needs a new
32
33
  answer. The ship guard and `canon evidence add` refuse an unapproved or
33
34
  edited brief, but those are backstops: the stop itself is at step 1.
35
+ - **Approval is scoped.** Evidence for a scenario needs the approval of the
36
+ changes its `@flow:<change>.<F-id>` tags name, so a new unapproved change
37
+ on a live subject waits on its own scenarios only; `change new` and
38
+ `subject adopt` warn when the subject is live. `canon subject unadopt
39
+ <change> --subject-id <id>` takes a draft change back off (refused once
40
+ approved). The subject's scope is its newest adopted change, walking back
41
+ past pending drafts (brief not approved, no scenario of the subject tagged
42
+ `@flow:<change>.*`): reopen, evidence and ship read the newest approved or
43
+ tagged change, and the transition prints one line naming every draft set
44
+ aside; a draft a scenario is tagged for blocks until it is approved. → `reference/canon-subject.md`
34
45
  - **Inside an approved scope, never stop for a human.** Fixes, follow-ups,
35
46
  review rounds and re-reviews stay in the approved change (never
36
47
  `canon change new`: a new change is a new scope) and go straight on: scenario,
@@ -50,7 +61,7 @@ a feature, a one-line request included. A human is needed once per scope:
50
61
  1. **Brief.** The planner writes the PRD brief (`.canon/changes/<slug>/brief.md`), checks it with `canon brief check`, and stops for the human. → `reference/role-planner.md`
51
62
  2. **Subject.** `canon subject new`, then `canon change new` (`--kind chore` for work that alters no behavior); the change writes the brief skeleton. → `reference/role-planner.md`
52
63
  3. **Scenarios.** `canon scenario new --flow <F-id> --case <case>` per behavior of a flow step, failure paths (`--case failure`) included. → `reference/role-implementer.md`
53
- 4. **Units.** Split the work; each unit has its own actor id and session id, passed on everything it records. A unit never attests another unit's work.
64
+ 4. **Units.** Split the work; one unit is one flow step's slice with a named verify command, and each unit has its own actor id and session id, passed on everything it records. A unit never attests another unit's work.
54
65
  5. **Implement.** Code with tests named by scenario id (`game.run.01: …`). → `reference/role-implementer.md`
55
66
  6. **Evidence.** `canon evidence add` binds the test run to its scenarios. → `reference/role-implementer.md`
56
67
  7. **Independent review.** A different session, a separate process with its own session id, that can run the checks it signs off, records `canon finding add` and `canon review add`. Every change. → `reference/role-reviewer.md`
@@ -135,10 +146,10 @@ Load only the reference the current step needs.
135
146
  ```bash
136
147
  canon init # canon.yaml, starter policy (--policy light|standard|strict, default strict; see canon-policy), AGENTS.md block, commented plans examples
137
148
  canon init --providers=claude,codex # harnesses for the canon skill; without it init asks on a terminal, else takes the detected .claude/.codex/.omp/.pi
138
- canon skills install # follows canon.yaml skills.providers (else the install lock's, else the detected ones; none detected installs nothing)
149
+ canon skills install # follows canon.yaml skills.providers (else the install lock's, else the detected ones; none detected installs nothing); prunes projected files the bundle no longer ships when their bytes match the lock, keeps a modified one and reports `kept:`
139
150
  canon skills install --providers=claude,codex,omp,pi # any subset or none; rewrites skills.providers
140
- canon skills check # read-only drift check
141
- canon skills doctor # diagnostics, incl. a stray canon projection for an unselected provider and a projection a formatter rewrote; never deletes user files
151
+ canon skills check # read-only drift check; exit 1 (SKILLS_DRIFT) names each orphan, a projected file the bundle no longer ships
152
+ canon skills doctor # diagnostics, incl. a stray canon projection for an unselected provider and a projection a formatter rewrote, and `orphan:` lines for projected files the bundle no longer ships; never deletes user files
142
153
  ```
143
154
 
144
155
  `install` and `doctor` end with `owned-paths:`, the globs of every path
@@ -155,6 +166,13 @@ never by assuming a default. The projection is a read-only copy: Claude
155
166
 
156
167
  ### Route by task
157
168
 
169
+ - **Fix a bug:** the approved change if there is one, else the planner (`canon change new`); first a failing run of the scenario (`canon scenario new --case failure`, then `canon evidence add`), then the fix. `reference/role-implementer.md`.
170
+ - **Add or change a feature:** `canon subject new`, `canon change new`, then the brief. `reference/role-planner.md`.
171
+ - **Pure refactor, dependency, build, CI or infra:** `canon change new <slug> --subject-id <id> --kind chore`; data or interface changes are not chores. `reference/role-planner.md`.
172
+ - **Review someone's change:** `CANON_ROLE=reviewer`, `canon brief check --change-id <slug>`. `reference/role-reviewer.md`.
173
+ - **Resume:** `canon status --json`, then follow `meta.next`. `reference/role-orchestrator.md`.
174
+ - **Plan more work on a live subject:** `canon change new <slug> --subject-id <id>` (it warns until the brief is approved; `canon subject unadopt` parks a draft). `reference/canon-subject.md`, `reference/role-planner.md`.
175
+ - **Upgrade canon in a repo:** `canon migrate --dry-run`. `reference/role-orchestrator.md`.
158
176
  - **Your role's steps:** `reference/role-planner.md`, `role-implementer.md`, `role-reviewer.md`, `role-orchestrator.md`.
159
177
  - **Where the repo stands, subjects and changes:** `reference/canon-subject.md` (`canon status`, `canon subject new|adopt|status`, `canon change new`).
160
178
  - **Write or reorganize `.feature` specs:** `reference/canon-authoring.md` before editing; use the scaffold.
@@ -39,4 +39,4 @@ Implement, evidence and review (loop steps 5 to 7). If the brief had to change,
39
39
  - Explaining writes nothing. Asked what a finding means or what a pass would do, answer in chat and leave the brief untouched.
40
40
  - No pass requires another as a prerequisite. Run the one the findings or the human point at, in any order.
41
41
  - A pass closes only the findings it rechecked: rerun `canon brief check` after the edit and report a finding as closed only when it is gone from that run. Never claim a rule you did not rerun.
42
- - Write in the brief's language (`language:` in `canon.yaml`). Edits touch `.canon/changes/<slug>/brief.md` only. A brief edited after approval is not approved: it needs a new human answer and `canon brief approve` again.
42
+ - Write in the brief's language (detected from the text; `language:` in `canon.yaml` overrides it). Edits touch `.canon/changes/<slug>/brief.md` only. A brief edited after approval is not approved: it needs a new human answer and `canon brief approve` again.
@@ -39,4 +39,4 @@ Every open line is a question with a proposed default or a stated assumption. `c
39
39
  - Explaining writes nothing. Asked what a finding means or what a pass would do, answer in chat and leave the brief untouched.
40
40
  - No pass requires another as a prerequisite. Run the one the findings or the human point at, in any order.
41
41
  - A pass closes only the findings it rechecked: rerun `canon brief check` after the edit and report a finding as closed only when it is gone from that run. Never claim a rule you did not rerun.
42
- - Write in the brief's language (`language:` in `canon.yaml`). Edits touch `.canon/changes/<slug>/brief.md` only. A brief edited after approval is not approved: it needs a new human answer and `canon brief approve` again.
42
+ - Write in the brief's language (detected from the text; `language:` in `canon.yaml` overrides it). Edits touch `.canon/changes/<slug>/brief.md` only. A brief edited after approval is not approved: it needs a new human answer and `canon brief approve` again.
@@ -15,6 +15,8 @@ brief has none of them: harden its Invariants and Checks instead.
15
15
  - Each step reads sees → does → becomes, and names who and what state it starts from.
16
16
  - Acceptance states, per step, what must be observable and which failure path is covered (invalid input, empty state, interrupted, repeated, denied).
17
17
  - Step detail holds rules, states and edge cases of that step only; a rule that spans steps is stated once, where it first applies.
18
+ - Intent states current behavior and Outcome the desired behavior; interfaces are described by behavior (inputs, outputs, errors); no file path, line number or function name stands in a section that decides the work (a `Source:` line may).
19
+ - A flow step is one unit an implementer can verify with one named command; split a step that needs two.
18
20
  - Acceptance writes no scenario ids: scenarios do not exist yet and point at steps.
19
21
 
20
22
  ## NEVER
@@ -38,4 +40,4 @@ brief has none of them: harden its Invariants and Checks instead.
38
40
  - Explaining writes nothing. Asked what a finding means or what a pass would do, answer in chat and leave the brief untouched.
39
41
  - No pass requires another as a prerequisite. Run the one the findings or the human point at, in any order.
40
42
  - A pass closes only the findings it rechecked: rerun `canon brief check` after the edit and report a finding as closed only when it is gone from that run. Never claim a rule you did not rerun.
41
- - Write in the brief's language (`language:` in `canon.yaml`). Edits touch `.canon/changes/<slug>/brief.md` only. A brief edited after approval is not approved: it needs a new human answer and `canon brief approve` again.
43
+ - Write in the brief's language (detected from the text; `language:` in `canon.yaml` overrides it). Edits touch `.canon/changes/<slug>/brief.md` only. A brief edited after approval is not approved: it needs a new human answer and `canon brief approve` again.
@@ -1,33 +1,236 @@
1
1
  # Brief pass: polish
2
2
 
3
- Tighten the prose of the normative sections so a reader can act on it. Run it
4
- last or whenever `canon brief` reports prose advisories; it never blocks
5
- approval.
3
+ Make the brief something a human reads once and understands. Run it whenever
4
+ `canon brief check` reports `prose.*` advisories; `canon status` and
5
+ `meta.next` name it before the human stop. It never blocks approval, and it
6
+ is not finished by silencing rules: it is finished when the brief reads
7
+ plainly and each advisory is gone or waived with a reason.
6
8
 
7
- ## Scope
9
+ ## Mode first, style second
8
10
 
9
- Sections with `role=normative`: Desired outcome, User flow, Step detail,
10
- Acceptance (feature) or Invariants and Checks (chore). Context and question
11
- sections are not judged. Prose rules ship for `language: en` and `ko`; any
12
- other language gets the structure rules only.
11
+ Fix what the document is before touching a word. Style rules cannot rescue a
12
+ brief that says the wrong thing.
13
+
14
+ - **Intent** states the current behavior: what a user sees and does today, and the gap. Not the history of how it got there.
15
+ - **Desired outcome** states what is true afterwards, as behavior a user can observe.
16
+ - **Interfaces** are described by behavior: inputs, outputs, errors, limits. Not by file, line or function.
17
+ - **Acceptance** gives, per step, a check a reader can run without asking the author, and one failure path naming the condition and what the user sees.
18
+ - **Provenance** goes on one line, `Source: <where this came from>`. That line is the only place a path or an identifier belongs.
19
+ - **No** paths, line numbers, identifiers, dates or decision history in the prose. A fact stated in one section is not restated in another; point to the section instead.
20
+
21
+ ## Scope and language
22
+
23
+ Rules read the brief by section role. Intent and Questions and assumptions
24
+ get the phrase rules (`evaluative`, `connective`, `contrast`, `colloquial`,
25
+ `degree`) and the five newer ones; `decision-history` reads normative text
26
+ and Intent, not Questions. `sentence-length`, `paragraph-length` and
27
+ `key-sentence` read the normative sections only (Desired outcome, User flow,
28
+ Step detail, Acceptance; for a chore, Invariants and Checks). Out of scope
29
+ is not judged. Headings, code spans, fenced code, URLs, code-like tokens and
30
+ the `Source:` and `Approved:` lines are not counted as prose.
31
+
32
+ `canon brief check` detects the language of the text from the script of its
33
+ prose letters: Hangul is `ko`, Latin is `en`, kana or Han text is named
34
+ (`ja`, `zh`) and gets the structure rules only. A section in another script
35
+ than the rest is judged in its own language, and a section with too few
36
+ letters to tell inherits the brief's. The output prints one line,
37
+ `language: ko (detected)`; the JSON carries `language` and `languageSource`
38
+ (`detected` or `configured`). `language:` in `canon.yaml` is an override: when
39
+ set it decides, and `brief check` adds `languageNote` (one line in the human
40
+ output) if the text looks like another language. Report that note to the
41
+ human; do not translate the brief to fit the setting.
42
+
43
+ ## The rules
44
+
45
+ Each finding carries the matched text or the measured value, and a hint.
46
+ Fix the sentence by saying the concrete thing; swapping in a near synonym only
47
+ moves the finding. The examples are invented.
48
+
49
+ ### `prose.evaluative`
50
+
51
+ Catches a verdict standing in for a fact (en "it is important", "it is
52
+ crucial", "it is essential"; ko "중요하", "핵심이", "필요가 있", "바람직하",
53
+ "의미가 있"). Fix: state the fact the verdict stood for.
54
+
55
+ - en. Before: "It is important that export keeps the old file." After: "A failed export keeps the previous file."
56
+ - ko. 전: "내보내기가 실패해도 이전 파일을 유지하는 것이 중요하다." 후: "내보내기가 실패하면 이전 파일을 그대로 둔다."
57
+
58
+ ### `prose.connective`
59
+
60
+ Catches a sentence that opens with a decorative connective (en "therefore",
61
+ "in conclusion"; ko "따라서", "결론적으로", "이처럼", "사실", "참고로"). Fix:
62
+ delete it; the order of the sentences already carries the link. Keep the
63
+ link only as a condition ("if", "unless").
64
+
65
+ - en. Before: "Therefore the list is sorted by date." After: "The list is sorted by date, newest first."
66
+ - ko. 전: "따라서 목록은 날짜순으로 정렬한다." 후: "목록은 날짜 내림차순으로 정렬한다."
67
+
68
+ ### `prose.contrast`
69
+
70
+ Catches the rhetorical "not X but Y" (ko "A가 아니라 B"). Fix: state Y alone.
71
+
72
+ - en. Before: "This is not a rename but a new way to find notes." After: "Renaming a note updates its search entry at once."
73
+ - ko. 전: "이것은 이름 변경이 아니라 노트를 찾는 새로운 방식이다." 후: "노트 이름을 바꾸면 검색 항목이 바로 바뀐다."
74
+
75
+ ### `prose.colloquial`
76
+
77
+ Korean only. Catches a spoken ending on a written sentence (~세요, ~어요,
78
+ ~아요, ~해요, ~에요, ~네요, ~죠, …). Fix: the plain written form (`~다`).
79
+
80
+ - ko. 전: "이름이 겹치면 뒤에 숫자를 붙여 주세요." 후: "이름이 겹치면 뒤에 숫자를 붙인다."
81
+
82
+ ### `prose.degree`
83
+
84
+ Catches a degree word nobody can check (en "very", "really", "extremely";
85
+ ko "매우", "겨우", "무려", "고작"). Fix: the number or the condition.
86
+
87
+ - en. Before: "The page loads very quickly." After: "The page accepts input within one second on the reference laptop."
88
+ - ko. 전: "화면이 매우 빠르게 열린다." 후: "화면은 기준 노트북에서 1초 안에 입력을 받는다."
89
+
90
+ ### `prose.sentence-length`
91
+
92
+ Normative sections. Catches a sentence over 30 words (en) or 80 characters
93
+ (ko). Fix: one statement per sentence, split at the condition, the action
94
+ and the result.
95
+
96
+ - en. Before: "When a user who has not signed in opens a shared link that has already expired, the page, which also logs the attempt, shows a notice and a sign-in button and offers a way back to the list." After: "A signed-out user opens an expired shared link. The page shows the notice 'Link expired' and a sign-in button. The attempt is logged."
97
+ - ko. 전: "로그인하지 않은 사용자가 만료된 공유 링크를 열면 시도를 기록하면서 안내 문구와 로그인 버튼을 함께 보여 주고 이후 목록 화면으로 돌아가는 경로도 제공한다." 후: "로그인하지 않은 사용자가 만료된 공유 링크를 연다. 화면에 '링크가 만료되었습니다' 문구와 로그인 버튼이 뜬다. 시도는 기록한다."
98
+
99
+ ### `prose.paragraph-length`
100
+
101
+ Normative sections. Catches a paragraph of more than five sentences. Fix: the
102
+ compression pass in `role-planner.md`: keep the sentences that state a
103
+ condition, an action, a result or a number; make three or more parallel
104
+ items a list; delete or move the rest.
105
+
106
+ ### `prose.key-sentence`
107
+
108
+ Normative sections. Each carries exactly one key sentence marked `==**…**==`:
109
+ the line a reader must not miss. Fix: mark the single rule or result the
110
+ section turns on, and nothing else.
111
+
112
+ - en: `==**A deleted note stays recoverable for 30 days.**==`
113
+ - ko: `==**삭제한 노트는 30일 동안 복구할 수 있다.**==`
114
+
115
+ ### `prose.code-citation`
116
+
117
+ Catches code references in sentences: paths with a known extension,
118
+ `path:line`, paths of two or more segments (`a/b/c`, `./x`), and backticked
119
+ code-shaped identifiers (`a::b`, `f()`, `snake_case`, `obj.member`,
120
+ `camelCase`). It does not catch UI labels, product names (`Next.js`, `iPhone`),
121
+ flags, commands, dates, routes or URLs, or the `Source:` line. Fix: say what the code does as behavior;
122
+ provenance goes on the one `Source:` line.
123
+
124
+ - en. Before: "`NoteStore.save` in `src/store/notes.ts:212` drops the tag list." After: "Saving a note drops its tags." and, once, `Source: the note store's save path.`
125
+ - ko. 전: "`src/store/notes.ts:212`의 `NoteStore.save`가 태그를 버린다." 후: "노트를 저장하면 태그가 사라진다." 그리고 한 번 `Source: 노트 저장소의 저장 경로.`
126
+
127
+ ### `prose.parenthetical`
128
+
129
+ Catches a section with three or more parenthetical asides at a density over
130
+ four per 1000 characters. Labels like `(F1)` or `(30초)` do not count. Fix:
131
+ if the aside is a condition or a result, make it its own sentence; if it is
132
+ background, delete it; if it is a doubt, move it to Questions and assumptions.
133
+
134
+ - en. Before: "Sync runs (every hour), on open (when online), and retries (at most twice)." After: "Sync runs every hour and when the app opens while online. A failed sync retries twice."
135
+ - ko. 전: "동기화는 (한 시간마다), 앱을 열 때 (온라인이면 언제나) 실행되고 (최대 두 번) 재시도한다." 후: "동기화는 매시간, 그리고 온라인 상태에서 앱을 열 때 실행한다. 실패한 동기화는 두 번 재시도한다."
136
+
137
+ ### `prose.decision-history`
138
+
139
+ Normative text, Intent and table cells. One finding per sentence that holds
140
+ a calendar date, in any language (`2026-10-21`, `2026년 10월`, `10월 21일`,
141
+ `Oct 21`, `21 October`, `October 2026`), or, in Korean text, one of
142
+ `결정했다`, `결정했고`, `결정하여`, `결정됐다`, `결정되었다`, `정해졌다`, `합의했다`,
143
+ `확정했다`, `~기로 했다`, `논의 결과`, `운영자 결정`, or, in English text, "we
144
+ decided", "we agreed", "was decided", "it was agreed". There is no gating: a
145
+ date next to 부터 or after is a finding too. Fix: delete the history and write
146
+ the decision as present behavior; a decision still open goes to Questions and
147
+ assumptions. When the date is part of the behavior (an effective date, a
148
+ deadline) or a quoted label, keep it and waive it with `match=` set to the
149
+ date (it compares the date or phrase the finding matched and the excerpt, nothing else), so the waiver clears that date only and every other note in the section
150
+ still draws a finding. Never waive the rule for the whole section.
151
+
152
+ - en. Before: "On 3 March the team agreed on a grid as the default view." After: "The default view is a grid."
153
+ - ko. 전: "3월 3일 회의에서 기본 보기를 격자로 하기로 했다." 후: "기본 보기는 격자다."
154
+ - waive one date: `<!-- canon:waive prose.decision-history detail "연체 요금은 2026-12-01부터 적용되는 동작이다" match="2026-12-01" -->`
155
+
156
+ What it misses: a decision written in other words ("회의에서 정한 대로" is not
157
+ listed on purpose, it reads as behavior). Over-reports: a date that is
158
+ behavior, and the closed-list phrases in behavior text; waive those with `match=`.
159
+
160
+ ### `prose.ai-vocabulary`
161
+
162
+ Catches a short closed list of stock words and metaphors (en "seamless",
163
+ "robust", "leverage", "streamline", "empower", "north star", "game-changer",
164
+ …; ko "원활", "견고", "시너지", "패러다임", "북극성", "토대", …). Fix: the plain
165
+ verb and its object.
166
+
167
+ - en. Before: "This leverages the sync engine for a seamless experience." After: "A note edited on one device appears on the others within a minute."
168
+ - ko. 전: "동기화 엔진으로 원활한 사용 경험을 제공한다." 후: "한 기기에서 고친 노트가 1분 안에 다른 기기에 나타난다."
169
+
170
+ ### `prose.ko-ending`
171
+
172
+ Korean only. Flagged everywhere, list items included: polite `~ㅂ니다`,
173
+ `~ㅂ니까`, `~ㅂ시다` (합니다, 입니다, 없습니다; a plain word like 아니다 is not
174
+ polite), `~십시오`, and a plain imperative: a terminated, unlabeled sentence
175
+ whose last word ends in 라 after a syllable with no final consonant (하라,
176
+ 해라, 두라, 오라, 넣으라, 남겨라, 줘라). A syllable with a final consonant
177
+ (달라, 갈라) never counts. The `~요` forms in `prose.colloquial`'s list
178
+ (`~세요`, `~해요`, …) belong to that rule. Outside list items any terminated
179
+ sentence not ending in `~다` is flagged, a plain hortative `~자` included. In a
180
+ list item the plain `~자` is not detected: it is the noun suffix in 참가자,
181
+ 후보자, 하자, 주자, so keep a hortative out of lists yourself. Not flagged:
182
+ questions, `없음.`, sentences ending in code or a quote, flow steps, list
183
+ fragments without a verb, a `label:` item, and an unterminated list item.
184
+
185
+ What it over-reports: a class of nouns, often loanwords, that end in an open
186
+ syllable plus 라 (보라 'purple', 카메라, 오페라, 인프라) as a terminated list
187
+ item; keep the sentence and waive with `match="<the noun>"`. What it misses: the
188
+ prohibitive `~말라` (`지우지 말라`: 말 has a final consonant), any consonant-stem
189
+ imperative written `~라` (달라 as a verb), and any hortative inside a list.
190
+ Fix: end the statement in `~다`.
191
+
192
+ - ko. 전: "목록이 비어 있으면 안내를 보여 줍니다." 후: "목록이 비어 있으면 안내를 보여 준다."
193
+ - ko. 전: "- 재고를 확인해라." 후: "- 재고를 확인한다."
194
+
195
+ ### `prose.length`
196
+
197
+ Catches a section or the whole brief over its character budget; the finding
198
+ states the measured value and the budget. Section budgets (ko / en
199
+ characters): Intent 400 / 700, Outcome 500 / 900, Flow 800 / 1500, Detail
200
+ 2500 / 3500, Acceptance 1000 / 1800, Questions 800 / 1200, Invariants and
201
+ Checks 800 / 1500 each (Out of scope has none); whole brief 4000 / 6000. Fix: the
202
+ compression pass in `role-planner.md`: delete sentences that restate another
203
+ section, turn parallel items into a list or table, move assumptions to
204
+ Questions and assumptions and a not-doing list to Out of scope. A brief this
205
+ long usually says several things twice. A waiver for the whole-brief budget
206
+ uses the section key `brief`.
13
207
 
14
208
  ## Inspect
15
209
 
16
- - `canon brief check --json`, the `prose.*` findings: `evaluative`, `connective`, `contrast`, `colloquial` (ko), `degree`, `sentence-length`, `paragraph-length`, `key-sentence`. Each carries the matched phrase or the threshold and the measured value.
17
- - Replace an evaluative or degree word with the observable fact it stood for. Split a long sentence or paragraph. Cut connectives that only decorate.
18
- - Each normative section carries exactly one key sentence marked `==**…**==`: the one line a reader must not miss.
19
- - A finding is a prompt to reread, not a verdict: if the phrase is the precise term (a state name, a quoted label), keep it and waive that rule in that section with `<!-- canon:waive <rule-id> <section-key> "<reason>" -->`.
210
+ - `canon brief check --json`: read the `prose.*` findings, then the sections they sit in. Fix by section, not by rule.
211
+ - A finding is a prompt to reread, not a verdict. When the flagged text is the precise term (a state name, a quoted label, a command), keep it and waive that rule in that section:
212
+ `<!-- canon:waive <rule-id> <section-key> "<reason>" -->`
213
+ To waive one finding and keep the rest of the rule live in that section, add the text the finding names: `<!-- canon:waive <rule-id> <section-key> "<reason>" match="<text>" -->` (after the reason's closing quote only a whole `match="<text>"` is allowed; `match =`, `MATCH=`, an unquoted or empty match or extra text is a blocker, and a reason holds no double quote).
214
+ A waiver is right when the text is a quotation or a fixed term, and wrong when it excuses a sentence you could simply rewrite. One rule, one section, a reason the human can read.
215
+
216
+ ## The loop
217
+
218
+ 1. Fix the mode problems above, then the findings, section by section.
219
+ 2. Run `canon brief check --change-id <slug> --json`.
220
+ 3. Repeat until no advisory remains, or each remaining one is waived with a reason.
221
+ 4. Stop and report to the human, in the brief's language. Do not loop further: a clean run says nothing about whether the brief is good, only that these counts are within budget.
20
222
 
21
223
  ## NEVER
22
224
 
23
225
  - Never change meaning: no flow step, rule, limit or failure path added, removed or softened while rewording.
24
- - Never rewrite to dodge a rule (swap a listed phrase for a near synonym); say the concrete thing.
25
- - Never polish sections that are not normative, or touch flow ids and subheadings.
26
- - Never treat a clean prose run as a claim about who wrote the brief or whether it is good.
226
+ - Never rewrite to dodge a rule (a near synonym, a list used to hide a long sentence); say the concrete thing.
227
+ - Never touch flow ids or subheadings, and never polish the markers.
228
+ - Never translate the brief to match a `language:` setting.
229
+ - Never treat a clean run as a claim about who wrote the brief.
27
230
 
28
231
  ## Verify
29
232
 
30
- `canon brief check` no longer lists the `prose.*` findings you rewrote (or lists them as waived with a reason); no blockers appeared.
233
+ `canon brief check` no longer lists the `prose.*` findings you rewrote, or lists them as waived with a reason; no blockers appeared.
31
234
 
32
235
  ## Next
33
236
 
@@ -39,4 +242,4 @@ Report to the human and `canon brief approve` from their answer.
39
242
  - Explaining writes nothing. Asked what a finding means or what a pass would do, answer in chat and leave the brief untouched.
40
243
  - No pass requires another as a prerequisite. Run the one the findings or the human point at, in any order.
41
244
  - A pass closes only the findings it rechecked: rerun `canon brief check` after the edit and report a finding as closed only when it is gone from that run. Never claim a rule you did not rerun.
42
- - Write in the brief's language (`language:` in `canon.yaml`). Edits touch `.canon/changes/<slug>/brief.md` only. A brief edited after approval is not approved: it needs a new human answer and `canon brief approve` again.
245
+ - Write in the brief's language (detected from the text, or `language:` in `canon.yaml` when set). Edits touch `.canon/changes/<slug>/brief.md` only. A brief edited after approval is not approved: it needs a new human answer and `canon brief approve` again.
@@ -20,8 +20,9 @@ marker, never by heading text.
20
20
  - `canon brief check --json`: each finding names the rule, section key, line and hint.
21
21
  - Every section present once, in order, with its marker; no `<!-- canon:hint … -->` comment left; no `TODO`/`TBD`; no empty section (write `None.` where nothing applies).
22
22
  - The flow is a numbered list with ids `F1`, `F2`, … each reading sees → does → becomes.
23
+ - Intent describes what the product does today and Outcome what is true after; leftover implementation paths belong in a `Source:` line or nowhere.
23
24
  - A chore's category is exactly one of `refactor`, `dependency`, `build`, `ci`, `infra`; its invariants and checks say something.
24
- - A `canon:waive <rule-id> <section-key> "<reason>"` comment is well formed with a real reason.
25
+ - A `canon:waive <rule-id> <section-key> "<reason>"` comment (optionally with `match="<text>"`) is well formed with a real reason.
25
26
 
26
27
  ## NEVER
27
28
 
@@ -43,4 +44,4 @@ marker, never by heading text.
43
44
  - Explaining writes nothing. Asked what a finding means or what a pass would do, answer in chat and leave the brief untouched.
44
45
  - No pass requires another as a prerequisite. Run the one the findings or the human point at, in any order.
45
46
  - A pass closes only the findings it rechecked: rerun `canon brief check` after the edit and report a finding as closed only when it is gone from that run. Never claim a rule you did not rerun.
46
- - Write in the brief's language (`language:` in `canon.yaml`). Edits touch `.canon/changes/<slug>/brief.md` only. A brief edited after approval is not approved: it needs a new human answer and `canon brief approve` again.
47
+ - Write in the brief's language (detected from the text; `language:` in `canon.yaml` overrides it). Edits touch `.canon/changes/<slug>/brief.md` only. A brief edited after approval is not approved: it needs a new human answer and `canon brief approve` again.
@@ -293,7 +293,7 @@ line, was edited after its approval (`BRIEF_CHANGED_SINCE_APPROVAL`), or
293
293
  there is no canon `brief.md` yet but the change comes from an
294
294
  external openspec plan with a `proposal.md` under the plan sources (the
295
295
  check `canon status` and the ship guard use; see
296
- `canon-subject`). The subjects are the scenario's `@subject` owner for
296
+ `canon-subject`; which changes a scenario answers to is scoped by its `@flow:<change>.<F-id>` tags, described there). The subjects are the scenario's `@subject` owner for
297
297
  `--scenario-id`, and every subject adopting the task's change for
298
298
  `--task`:
299
299
 
@@ -354,7 +354,8 @@ review waivers: 1 advisory(ies) — not failing the gate:
354
354
  The waiver lasts only until the subject's next status write.
355
355
 
356
356
  `canon subject status <id> --to shipped` also refuses where a brief exists and
357
- is not approved (the subject's newest adopted change has a canon
357
+ is not approved (the subject's scope, its newest adopted change walking back past pending drafts
358
+ no scenario is tagged `@flow:<change>.*` for, has a canon
358
359
  `brief.md` with no `Approved:` line or one the brief's text no longer
359
360
  matches, or an external `proposal.md` and no canon `brief.md` yet;
360
361
  see `canon-subject`), as an `unreviewed-promotion` naming
@@ -115,9 +115,6 @@ canon report --snapshot /tmp/canon-snap
115
115
  Serves the dashboard web app locally, pointed at a real snapshot:
116
116
 
117
117
  ```bash
118
- # build the app once
119
- bun install && bun run --cwd packages/dashboard build
120
-
121
118
  canon dashboard # regenerates a fresh snapshot every run, serves it
122
119
  canon dashboard --snapshot /tmp/canon-snap --port 8080
123
120
  ```
@@ -129,8 +126,32 @@ canon dashboard --snapshot /tmp/canon-snap --port 8080
129
126
  `manifest.json` (a separately generated snapshot, or a downloaded CI
130
127
  artifact) — never silently regenerated over it. If it has no
131
128
  `manifest.json`, one is generated there once.
132
- - Prints the URL to open, then serves until Ctrl-C. If the app is not
133
- built, `canon dashboard` fails with the exact build command to run.
129
+ - Runs in any repo: the released binary carries the app (in canon's own
130
+ checkout a built `packages/dashboard/dist` is used instead). It prints the
131
+ panels URL and a briefs URL (`/briefs.html`), then serves until Ctrl-C.
132
+ A taken port fails with `DASHBOARD_PORT_IN_USE`: pass `--port <n>` or
133
+ `--port 0`.
134
+ - Without the `duckdb` CLI on `PATH` the server still runs; the panels page
135
+ says why there is no snapshot and how to get one (it never shows demo
136
+ data), and the briefs page works regardless.
137
+
138
+ ### The briefs page (where a human approves briefs)
139
+
140
+ `/briefs.html` lists every live change whose brief awaits the human (no
141
+ `Approved:` line, or edited since) with its intent, outcome, flow steps,
142
+ acceptance, questions and assumptions, and its `canon brief check`
143
+ blockers and advisories. The human types their name and their answer and
144
+ clicks Approve: that records exactly what `canon brief approve --change-id
145
+ <id> --by <name> --note <text>` records, the `Approved:` line bound to the
146
+ brief digest and the policy approval. It is refused when the brief changed
147
+ after the page loaded (`BRIEF_CHANGED_SINCE_PAGE_LOAD`: reload) or while it
148
+ has blockers (`BRIEF_HAS_BLOCKERS`). The server is localhost only and
149
+ refuses requests from other origins (`DASHBOARD_ORIGIN_REFUSED`).
150
+
151
+ For an agent the human stop is unchanged: when clean briefs are waiting,
152
+ tell the human they can approve them in `canon dashboard` (briefs page) or
153
+ answer in chat. An agent never clicks Approve or fills the form, and runs
154
+ `canon brief approve` only from the human's own words.
134
155
 
135
156
  The dashboard renders SEVEN panels — trust matrix, session costs, role
136
157
  memory, flywheel funnel, review burn-down, review rounds, review totals.
@@ -73,8 +73,16 @@ canon finding add --change-id <c> --round <n> --seq <n> \
73
73
  [--reviewed-sha <sha>] [--file-ref path/to/file.rs:120-134] [--scenario-id <id> …] [--actor-id <id>] [--session-id <id>]
74
74
  canon finding close --change-id <c> --round <n> --seq <n> \
75
75
  --disposition fixed --resolution-sha <sha> [--session-id <id>] # or rejected / deferred / open
76
+ canon finding close --change-id <c> --round <n> --seq <n> \
77
+ --disposition rejected|deferred --reason "<why, one line>"
76
78
  ```
77
79
 
80
+ `--reason` is required for `rejected` and `deferred` (refused with
81
+ `FINDING_REASON_REQUIRED` otherwise) and optional for `fixed`. It is stored
82
+ on the closing version, shown by `finding list` as `[reason: …]` and carried
83
+ in the JSON. Give the reason when you reject or defer: it is the only record
84
+ of why the finding was not fixed.
85
+
78
86
  `--scenario-id` (repeatable, each a scenario the index knows; refused with
79
87
  `FINDING_UNKNOWN_SCENARIO` otherwise) names the scenarios the finding is
80
88
  about. After a `fixed` close, fresh evidence for those scenarios alone
@@ -122,14 +130,18 @@ found it: `--introduced-by self` is a usage error (exit 2) that points at
122
130
 
123
131
  Severity is the reviewer's own judgement, never a score canon derives.
124
132
 
133
+ Write a review's or finding's `--summary` as prose that also names the blast
134
+ radius: what else the change touches that the checks do not exercise. It is
135
+ text in the summary, not a field.
136
+
125
137
  ### Dispositions
126
138
 
127
139
  | Disposition | Meaning |
128
140
  |---|---|
129
141
  | `open` | Raised and not yet dealt with. The default on `finding add`; `finding close --disposition open` reopens. |
130
142
  | `fixed` | Fixed by a commit. Requires `--resolution-sha <sha>` naming a commit this repo holds; only `fixed` may carry one. |
131
- | `rejected` | The reviewer's point was considered and declined. |
132
- | `deferred` | Accepted, but postponed to later work. |
143
+ | `rejected` | The reviewer's point was considered and declined. Needs `--reason`. |
144
+ | `deferred` | Accepted, but postponed to later work. Needs `--reason`. |
133
145
 
134
146
  ### How findings fold
135
147
 
@@ -260,7 +260,7 @@ skills:
260
260
  `--providers a,b` (or `none`) answers without asking; with neither it takes
261
261
  the detected ones, and with none detected it writes only a commented example
262
262
  and installs nothing. `.agents` alone never selects Codex. `canon skills
263
- install|check|doctor` follow the section; `canon skills install
263
+ install|check|doctor` follow the section; `install` prunes projected files the bundle no longer ships when their bytes match the lock (a modified one stays, reported as `kept:`), `check` exits 1 (`SKILLS_DRIFT`) naming each orphan and `doctor` prints `orphan:` lines; `canon skills install
264
264
  --providers=<list>` rewrites it in place. Without the section they use the
265
265
  install lock's providers, then the detected ones; nothing undetected is ever
266
266
  added. A present section that is not this shape, or names another harness,
@@ -342,10 +342,17 @@ comment, only the missing ones):
342
342
  - `.canon/audit/` — the query/export/purge access log.
343
343
  - `.canon/ledger/kind=scenario/` — the scenario index; every reader
344
344
  rebuilds it.
345
+ - `.canon/archive/kind=scenario/` — superseded scenario index rows;
346
+ derived like the index.
345
347
  - `.canon/scenario-index.lock` — the lock serializing index rebuilds.
346
348
  - `.canon/artifact-store.lock` — the lock serializing artifact store
347
349
  writes.
350
+ - `.canon/dashboard-snapshot/` — the snapshot `canon dashboard`
351
+ regenerates on every run.
352
+ - `.canon/learn/` and `.canon/r2/` — local learn scratch and the local
353
+ object-store stand-in `canon report` writes.
348
354
 
349
355
  A rerun on a repo that already has `canon.yaml` only refreshes
350
- `AGENTS.md`, so an existing repo's `.gitignore` is unchanged; add the
351
- lines by hand to adopt them.
356
+ `AGENTS.md`; `canon migrate` adds the lines an existing `.gitignore`
357
+ lacks, and `canon skills doctor` and `canon init --check-config` name a
358
+ missing one.
@@ -159,7 +159,7 @@ exits 1, and the status stays `verifying`. `retired` is not gated.
159
159
  **The brief (0.16, 0.17, 0.19):** `verifying → shipped` also refuses where a brief
160
160
  exists and is not approved, read exactly as `canon status` reads it (one
161
161
  shared check, so status says `ship` only when this passes): the subject's
162
- newest adopted change has a canon `.canon/changes/<change>/brief.md`
162
+ scope (its newest adopted change, except pending drafts, see below) has a canon `.canon/changes/<change>/brief.md`
163
163
  with no `Approved:` line outside HTML comments and code fences, whose
164
164
  `Approved:` line carries a digest the brief's text no longer matches
165
165
  (`BRIEF_CHANGED_SINCE_APPROVAL`; the brief was edited after the human
@@ -204,6 +204,30 @@ scenario whose `@subject` owner, or a task whose change
204
204
  an adopting subject, has an enforced, unapproved brief, through the same
205
205
  check; see `canon-gate`.
206
206
 
207
+ **Approval is scoped to the scenarios a change names.** The evidence
208
+ refusal asks the approval of each change the scenario's `@flow:<change>.<F-id>`
209
+ tags name. An untagged scenario answers to the subject's newest adopted
210
+ change whose brief carries an `Approved:` line; if no adopted change ever
211
+ did, a scenario with evidence recorded before the subject first adopted a
212
+ change (and without a brief override) needs no brief, and one without
213
+ answers to the newest adopted change. A task answers to its own
214
+ change. So planning the next change for a `building`, `verifying` or
215
+ `shipped` subject does not freeze re-verification of its existing
216
+ scenarios; scenarios written for the new change (`canon scenario new
217
+ --flow <F-id>` tags them) wait for its approval. The ship guard above is
218
+ unchanged, except for a pending draft: the subject cannot ship until its
219
+ scope is approved, where the scope is found walking the adopted changes from
220
+ the newest back: a change whose brief was never approved and that no scenario
221
+ of the subject is tagged `@flow:<change>.*` for is a pending draft and is
222
+ skipped; the first approved or tagged change is the scope. A tagged unapproved
223
+ draft anywhere above the newest approved change therefore blocks. The reopen
224
+ and ship transitions print one line naming every change set aside (stderr, and
225
+ `meta.warnings` with `--json`): ``<change> is a pending draft (brief not
226
+ approved, no scenario tagged `@flow:<change>.*`): not part of this transition,
227
+ which reads the subject's newest approved or tagged change``, and `canon status`
228
+ marks them "not yet part of the subject's scope" (`pendingDrafts` in JSON)
229
+ while still asking the human to answer a draft's brief.
230
+
207
231
  ### The review guard (`spec_coverage.require_review`, opt-in)
208
232
 
209
233
  When `.canon/policy.yaml` sets `spec_coverage.require_review` (see
@@ -279,8 +303,10 @@ canon subject status: demo-subject → verifying — written directly; nothing t
279
303
  records the change the work goes into:
280
304
  `reopen: { reason: …, change: c-demo, actor: { agent_id: impl } }`. The
281
305
  next status write clears both, as it clears a waiver.
282
- - Inside the approved scope it needs no human: the subject's newest
283
- adopted change's brief is approved, or there is no brief to answer.
306
+ - Inside the approved scope it needs no human: the subject's scope (its
307
+ newest adopted change, walking back past pending drafts no scenario is
308
+ tagged for) has an approved brief, or there
309
+ is no brief to answer.
284
310
  Otherwise it refuses, exit 1, naming the brief to approve, and
285
311
  `--override-reason` does not waive it:
286
312
  ``unreviewed-promotion demo-subject — shipped → verifying: reopening needs the approved scope, and the brief is not approved — .canon/changes/c-next/brief.md has no `Approved:` line; …``.
@@ -345,6 +371,31 @@ counted. If the change write then fails, `adopt` exits `2`, names the
345
371
  subject that now lists the change, and prints `canon subject adopt
346
372
  <change_id> --subject-id <id>` to complete it.
347
373
 
374
+ `canon change new <slug> --subject-id <S>` and `canon subject adopt
375
+ <change> --subject-id <S>` print a warning on stderr, and in JSON
376
+ `meta.warnings`, when `<S>` is `building`, `verifying` or `shipped` and the
377
+ change's brief is not approved. The warning says the subject's existing
378
+ scenarios keep accepting evidence and the subject can still be reopened and
379
+ shipped for its approved scope while no scenario is tagged `@flow:<change>.*`;
380
+ once one is, the subject cannot ship until the brief is approved. The draft is
381
+ pending until then: the reopen and ship guards read the newest adopted change
382
+ with an `Approved:` line instead, so a shipped subject can reopen, re-verify
383
+ and ship again while the draft waits; `canon status` still lists it.
384
+
385
+ ### Taking a draft change back out: `canon subject unadopt`
386
+
387
+ ```bash
388
+ canon subject unadopt <change> --subject-id <S> --as planner --json
389
+ ```
390
+
391
+ Removes the link on both records and keeps the change and its brief, so the
392
+ draft can be adopted elsewhere (a `proposed` holding subject, say) while the
393
+ live subject keeps shipping. It is refused with `SUBJECT_UNADOPT_REFUSED`
394
+ for a change whose brief carries an `Approved:` line, and while a scenario
395
+ the subject owns carries a `@flow:<change>.*` tag (it would fall back to an
396
+ older approval; remove or retag those scenarios first, the hint names them);
397
+ an unlinked pair is a no-op.
398
+
348
399
  ### Starting a new change: `canon change new`
349
400
 
350
401
  For a change that does not exist yet, one command writes it and records
@@ -432,11 +483,15 @@ advisory, and is read as before.
432
483
  or later, a flow step no scenario points at (`brief.flow-uncovered`) or
433
484
  one with no failure scenario (`brief.flow-no-failure`): these two
434
485
  **gate shipping, never approval**.
435
- - **Advisories (prose, `prose.*`)**: phrase lists, sentence and
436
- paragraph length, and key-sentence marking in `role=normative`
437
- sections, for `language: en` and `ko`. Never an authorship verdict.
486
+ - **Advisories (prose, `prose.*`)**: judged in the language the text is
487
+ written in (`language:` overrides): phrase lists, code citations,
488
+ asides, dates and decision notes, stock vocabulary, Korean endings and
489
+ length budgets, in the normative sections plus Intent and Questions;
490
+ sentence, paragraph and key-sentence rules in normative sections.
491
+ Never an authorship verdict. `reference/brief-polish.md` has each rule.
438
492
  - **Waivers**: `<!-- canon:waive <rule-id> <section-key> "<reason>" -->`,
439
- one rule in one section, reason required; listed in the output.
493
+ one rule in one section, reason required; add `match="<text>"` to waive
494
+ only the findings whose matched text contains it. Listed in the output.
440
495
  - **`approve`** records the human's answer: it writes
441
496
  `Approved: <who> <when> — "<words>" (brief sha256:<digest>)` into the
442
497
  brief and records the policy approval in the same call. It refuses
@@ -101,6 +101,10 @@ NOT an attr value) and satisfies that directive's attrs:
101
101
  - "cargo test"
102
102
  ```
103
103
 
104
+ When the body carries a continuation for another session, `summary` holds
105
+ the next command and the claims already verified; reference artifacts and
106
+ record ids instead of pasting them, and redact secrets.
107
+
104
108
  It's validated and compiled to a canon handoff body. Each
105
109
  `handoff-<domain>` directive declares its own attrs (e.g. `design` /
106
110
  `content` domains may require `acceptance-criteria` instead of
@@ -45,12 +45,27 @@ After the first `status`, follow `meta.next` of each write; do not call
45
45
  root (default `.canon/specs`); `reference/canon-authoring.md` has the
46
46
  scaffold. When you fill in a scenario, keep its `# canon: {...}` provenance
47
47
  lines: dropping one aborts `inventory sync` on `missing-provenance`.
48
- 2. **Units.** Give each unit its own actor id and session id and pass them on
49
- everything it records. A delegated unit attests its own work; never attest
50
- another unit's work under your id.
48
+ 2. **Units.** One unit is one flow step's complete path (sees, does,
49
+ becomes) that can be verified on its own, sized to one session. Name the
50
+ command that proves it (a test filter, a script) before you start it, and
51
+ run that command before you call the unit done. Give each unit its own
52
+ actor id and session id and pass them on everything it records. A
53
+ delegated unit attests its own work; never attest another unit's work
54
+ under your id. Units stay inside the one approved change; `tasks.md` is a
55
+ note, not a record.
51
56
  3. **Code and tests.** Name tests by scenario id (`game.run.01: …`) so a test
52
57
  report binds to its scenario. Record a defect you find in your own work
53
58
  with `finding add` the moment you find it, before review.
59
+ **A fix fails first.** For a bug fix, write or extend the scenario's test
60
+ and run it red on the unfixed code, then bind that failing run before you
61
+ change the code (`evidence add --scenario-id <id> --report <fmt>:<path> …`
62
+ with `--verdict divergent`; a failed case never records `faithful`, and
63
+ `--all-scenarios` derives `divergent` from it).
64
+ Fix the code, run again and bind the passing run: the green record
65
+ supersedes the red one, and `canon query --kind evidence --history
66
+ --scenario-id <id>` shows the red-then-green pair to the reviewer. A red
67
+ run is never an accepted gap. When no executable reproduction is
68
+ possible, say in `--ref` what the closest check is and why.
54
69
  4. **Evidence.** One test run that proves several scenarios is bound once with
55
70
  `--all-scenarios`: one record per indexed scenario owning a case, each with
56
71
  the verdict its own cases show; any refusal writes none. For one scenario
@@ -99,6 +114,16 @@ After the first `status`, follow `meta.next` of each write; do not call
99
114
  was accepted is accepted again by the reviewer once new evidence says
100
115
  something different.
101
116
 
117
+ ## Excuses that fail
118
+
119
+ | The thought | The reality |
120
+ |---|---|
121
+ | "The suite is slow; record `faithful` to pass." | `faithful` over a failed case is refused; a limit outside this change is recorded `divergent` with the reason in `--ref`. |
122
+ | "I fixed it, the test would pass." | Run the test red first and bind it, then green. Evidence is a claim; the history is what the reviewer reads. |
123
+ | "The brief is as good as approved." | `evidence add` refuses an unapproved brief's scenarios. Say so and wait. |
124
+ | "I will attest the other unit's work under my id." | Each unit records under its own actor id and session id. |
125
+ | "I was told this twice already." | A correction repeated across runs belongs in a scenario, the policy or a gate; tell the orchestrator. |
126
+
102
127
  ## Retry safety
103
128
 
104
129
  Repeating `evidence add` with identical input returns the existing record
@@ -48,6 +48,36 @@ shell).
48
48
  Order: planner, then (after the human's answer) implementer, then reviewer.
49
49
  Give each a unit: its own `--actor-id` and a session id.
50
50
 
51
+ ### Spawn contract
52
+
53
+ Every spawn prompt names, in this order: the goal (the change slug and the
54
+ one flow step or review it serves); the scope it owns (the scenarios or
55
+ paths it may write); what it must not touch (the brief, the policy, other
56
+ units' scenarios, anything outside its scope); the command that proves it is
57
+ done (the unit's verify command, or `canon gate check` for the reviewer); and
58
+ its own `--actor-id` and `--session-id`. Two agents never write the same
59
+ paths: give parallel implementers disjoint scopes, and where the harness can
60
+ isolate a child in its own worktree, use it. The child reports what it
61
+ verified apart from what it only claims.
62
+
63
+ ### What each harness gives you
64
+
65
+ Last reviewed 2026-10-21. A cell marked `verify` is unconfirmed here: check
66
+ the harness before relying on it; a harness version moves faster than this
67
+ text.
68
+
69
+ | Harness | In-harness subagents | Isolation | Ask the human | Hooks | Skipped |
70
+ |---|---|---|---|---|---|
71
+ | claude (`.claude`) | yes, a subagent tool; a child shares your environment, so pass the role in the task | a subagent can run in its own git worktree | yes, a question tool | yes, in the harness settings; `scripts/canon-retrieve-pre-dispatch.sh` is a sidecar, not installed | none |
72
+ | codex (`.agents`) | `verify`: none when #39 was filed; start the reviewer as a new `codex exec` run | none from canon: use a separate worktree you create | no in `codex exec`; stop and report | `verify` | in-harness spawn, unless a newer version provides it |
73
+ | omp (`.omp`) | yes, the task tool | `verify` | yes, an ask tool | yes | none |
74
+ | pi (`.pi`) | `verify`: assume none; start a new `pi` process | none from canon | `verify` | `verify` | in-harness spawn, isolation |
75
+
76
+ Where a harness has no subagent tool, the reviewer is still a separate
77
+ process with its own session id: start it from the shell with its own
78
+ `CANON_ROLE=reviewer`. A harness that cannot ask the human means you stop
79
+ and report in your final message; you never assume the answer.
80
+
51
81
  The reviewer is a separate process with its own session id, never your
52
82
  session or the implementer's under another label. Start it as a new process
53
83
  (a new `codex exec` run, or the harness's subagent), pass nothing of the
@@ -59,7 +89,9 @@ not sign off.
59
89
 
60
90
  When `status` or `meta.next` shows a step with `actor: human` (the brief's
61
91
  approval), stop everything for that scope, report the brief to the human in
62
- its language, and wait. Do not start the implementer before the answer.
92
+ its language, and wait. Tell the human they can approve the clean briefs in
93
+ `canon dashboard` (the briefs page) or answer in chat; you never click
94
+ Approve or fill the form, and you run `brief approve` only from their words. Do not start the implementer before the answer.
63
95
  Record the answer with `brief approve`. Never approve it yourself and never
64
96
  edit the policy to pass the gate. A policy that no longer matches the
65
97
  approval fails closed (`POLICY_CHANGED_SINCE_APPROVAL`, or
@@ -70,6 +102,27 @@ the approved policy from git; never edit the policy yourself. Inside an
70
102
  approved scope never stop for a human: fixes, follow-ups and re-reviews go
71
103
  straight to the implementer and reviewer in the same change.
72
104
 
105
+ ## Hand off a note, not a copy
106
+
107
+ A note one session leaves for another (a resume note, a spawn prompt, a
108
+ `Handoff` body) points to artifacts and record ids instead of pasting them:
109
+ name the change slug, the scenario ids, the evidence or finding keys and the
110
+ files, and the next command. Say what was verified (a command that ran, with
111
+ its result) apart from what is only claimed. Redact secrets: tokens, keys,
112
+ passwords and private URLs never go into a note, a `--ref`, a `--summary`
113
+ or a brief. When a `Handoff` body carries a continuation, its `summary`
114
+ holds the next command and the verified claims (`reference/canon-vocab.md`).
115
+
116
+ ## Excuses that fail
117
+
118
+ | The thought | The reality |
119
+ |---|---|
120
+ | "Edit the policy so the gate passes." | A changed policy fails closed (`POLICY_CHANGED_SINCE_APPROVAL`); ask the human to re-approve or restore it from git. |
121
+ | "The reviewer can be me under another label." | The reviewer is a separate process with its own real session id (#39). |
122
+ | "Approve the brief; the human said they are busy." | Never approve for the human; report the brief and wait. |
123
+ | "Override the refusal to ship." | Fix the gap the refusal names; `--override-reason` does not waive an approval. |
124
+ | "I have corrected this agent before." | A correction repeated across runs belongs in a scenario, the policy or a gate, not more prose. Tell the human. |
125
+
73
126
  ## Move the subject
74
127
 
75
128
  Move along `proposed → specced → building → verifying → shipped` with
@@ -15,6 +15,7 @@ canon subject new <subject-id> --domain <domain> --title "<title>" --actor-id <u
15
15
  canon change new <slug> --subject-id <subject-id> --title "<title>" --as planner --json
16
16
  canon change new <slug> --subject-id <subject-id> --kind chore --title "<title>" --as planner --json
17
17
  canon subject adopt <slug> --subject-id <other-subject-id> --as planner --json
18
+ canon subject unadopt <slug> --subject-id <subject-id> --as planner --json # a draft change off a live subject; refused once its brief is approved
18
19
  canon brief check --change-id <slug> --as planner --json
19
20
  canon brief approve --change-id <slug> --by "<the human>" --note "<their words>" --as planner --json
20
21
  ```
@@ -23,8 +24,10 @@ After the first `status`, follow `meta.next` of each write. `brief check`
23
24
  lists blockers and advisories; bare `canon brief --as planner` names the next
24
25
  steps. While the brief has blockers, `status` and `change new`'s `meta.next`
25
26
  send you to write it (`write the brief: canon brief check --change-id <slug>
26
- lists N blockers`); the human step appears only once the brief is clean and
27
- unapproved. A policy that no longer matches its approval fails closed
27
+ lists N blockers`); then, while prose advisories remain, to the polish pass
28
+ (`polish the brief in <path> (reference/brief-polish.md), then run canon
29
+ brief check --change-id <slug>`, with the advisory count and the top rule
30
+ ids); the human step comes after it. A policy that no longer matches its approval fails closed
28
31
  (`POLICY_CHANGED_SINCE_APPROVAL`): never edit it; the human re-approves.
29
32
 
30
33
  ## Steps
@@ -34,28 +37,61 @@ unapproved. A policy that no longer matches its approval fails closed
34
37
  `tasks.md` (`tasks.md` is an optional work note nothing gates on). A
35
38
  change that also serves another subject is adopted there too
36
39
  (`subject adopt`): it then belongs to both,
37
- and its open blockers gate both. Inside an approved scope there is no new
40
+ and its open blockers gate both. Planning the next change for a
41
+ `building`, `verifying` or `shipped` subject warns (stderr and JSON
42
+ `meta.warnings`) until its brief is approved: evidence for its existing scenarios is not
43
+ frozen and the subject can still be reopened and shipped for its approved
44
+ scope while no scenario is tagged for the draft (a pending draft); once one
45
+ is, the subject cannot ship before the approval.
46
+ A scenario answers to the changes its `@flow:<change>.<F-id>`
47
+ tags name (`canon scenario new --flow <F-id>` tags one for this change).
48
+ To park a draft that should not gate a live subject, `subject unadopt`
49
+ it and adopt it into a `proposed` subject. Inside an approved scope there is no new
38
50
  subject or change: a fix or follow-up stays in the existing change.
39
- 2. **Write the brief.** A PRD: intent, desired outcome, the user flow as
51
+ 2. **Write the brief.** Briefs outlive the code that exists today and are
52
+ read by sessions that did not write them. Intent says what the product
53
+ does today, in behavior words, and why that is not enough; Desired outcome
54
+ says what is true afterwards. Describe an interface by its behavior
55
+ (inputs, outputs, errors), and keep file paths, line numbers and function
56
+ names out of the sections that decide the work; a `Source: <path>`
57
+ provenance line is the one exception. A PRD: intent, desired outcome, the user flow as
40
58
  numbered steps `F1`, `F2`, … (sees → does → becomes), step detail per
41
59
  step, acceptance per step with its failure path, questions and
42
- assumptions, out of scope. Write it in the `language:` of `canon.yaml`;
43
- unset, in the request's language, and add "which language?" to the
44
- questions. Anything that could change policy goes to the questions: never
45
- decide it yourself. A change that comes from an external plan the human
60
+ assumptions, out of scope. Write it in the language of the request, or
61
+ the `language:` of `canon.yaml` when it is set (`brief check` detects the
62
+ language of the text and reports whether it used the detected or the
63
+ configured one; a `languageNote` means they disagree: tell the human, and
64
+ ask "which language?" in the questions). Anything that could change
65
+ policy goes to the questions: never decide it yourself. A change that
66
+ comes from an external plan the human
46
67
  prepared (an openspec `proposal.md` read through `plans.sources`) gets a
47
68
  `brief.md` whose `Source: <path>` line points at the plan; canon never
48
69
  edits the plan.
49
70
  3. **Check it.** `brief check` (read-only) lists findings: fix every blocker
50
- (structure) before you stop; advisories (prose) are optional. Five optional
51
- refinement passes, each standing alone and none needing another first:
71
+ (structure) before you stop. Four optional refinement passes, each
72
+ standing alone and none needing another first:
52
73
  - `reference/brief-shape.md`: structure (sections, order, leftover template text).
53
74
  - `reference/brief-clarify.md`: open questions.
54
75
  - `reference/brief-harden.md`: flow and step detail agree.
55
76
  - `reference/brief-align.md`: scenarios cover the flow.
56
- - `reference/brief-polish.md`: prose.
77
+
78
+ **Compress, then polish.** Before the human reads it, shorten the brief.
79
+ A human reads it once, so every sentence has to earn its place:
80
+ - Per paragraph, keep only sentences that state a condition, an action, a result or a number. Delete the rest.
81
+ - Three or more parallel items become a list or a table.
82
+ - Delete a sentence that restates another section: each fact lives in one place (single source of truth).
83
+ - An assumption goes to Questions and assumptions. A "not doing" list goes to Out of scope.
84
+
85
+ Then, when `brief check` reports prose advisories (`prose.*`; `status` and
86
+ `meta.next` name the step), the polish pass is required before the human
87
+ stop: follow `reference/brief-polish.md` and rerun `brief check` until
88
+ the advisories are gone or each remaining one is waived with a reason.
89
+ It never blocks approval, but you do not hand the human a brief with
90
+ unexamined advisories.
57
91
  4. **Stop for the human.** Report the brief to the human, in the brief's
58
- language, asking once. Build nothing for that scope (no scenarios, code
92
+ language, asking once, and say they can approve it in `canon dashboard`
93
+ (the briefs page, `/briefs.html`) or answer in chat. You never click
94
+ Approve or fill that form yourself. Build nothing for that scope (no scenarios, code
59
95
  or evidence) until the human answers. The approval is never assumed, even
60
96
  when the human said they cannot answer until you report: the brief is
61
97
  that report. The one answer approves the brief and the policy
@@ -86,6 +122,28 @@ shipped subject `change new` prints the reopen command for the orchestrator
86
122
  ship again until the suite is evidenced after the chore's approval. Work that
87
123
  changes behavior is never a chore: it is a feature change.
88
124
 
125
+ ### A migration is cut in phases
126
+
127
+ A migration (a column, a format, a dependency swap) is cut as expand,
128
+ migrate, contract: one change per phase, each adopting only after the
129
+ previous one shipped. A phase that alters nothing a user or a caller can
130
+ observe (a behavior-preserving internal rename, a build step) is a chore. A
131
+ phase that changes data, a public interface or output is never a chore: it
132
+ is a feature change with a PRD brief, whose Acceptance names the down path
133
+ (how it is undone), the safety condition of any backfill (idempotent,
134
+ resumable, checked before and after), and the destructive cleanup (drop the
135
+ old column, delete the old format) in its own later change.
136
+
137
+ ## Excuses that fail
138
+
139
+ | The thought | The reality |
140
+ |---|---|
141
+ | "The human is unavailable; I will assume the approval." | Never. The brief is the report; stop, and put open questions into Questions and assumptions. |
142
+ | "This could change policy, but I will decide it." | Anything that could change policy goes to the questions. |
143
+ | "A small follow-up needs no new answer." | A follow-up that adds or changes a flow step is a scope change and needs a new `brief approve`. |
144
+ | "This migration is just a chore." | Data, interface or output changes are feature changes. |
145
+ | "Put the file and line in the brief so nobody has to look." | Paths rot; describe behavior. |
146
+
89
147
  ## Hand-off
90
148
 
91
149
  After the approval the implementer takes the scenarios, then the reviewer.
@@ -15,12 +15,14 @@ canon gate check --as reviewer --json # once, before review add
15
15
  canon finding add --change-id <slug> --round <n> --seq <n> --severity <blocker|should-fix|note> --reviewer <me> --actor-id <me> --session-id <session> --summary "<defect>" --scenario-id <id> --scenario-id <id> --as reviewer --json # one --scenario-id per scenario the defect is about
16
16
  canon finding list --change-id <slug> --open --as reviewer --json
17
17
  canon finding close --change-id <slug> --round <n> --seq <n> --disposition fixed --resolution-sha <sha> --as reviewer --json
18
+ canon finding close --change-id <slug> --round <n> --seq <n> --disposition rejected|deferred --reason "<why, one line>" --as reviewer --json # --reason is required here (FINDING_REASON_REQUIRED); optional for fixed
18
19
  canon review add --project-id <root-id> --scenario-id <id> --scenario-id <id> --reviewer <me> --actor-id <me> --role reviewer --session-id <session> --pin <sha> --upstream-ref <ref> --as reviewer --json
19
20
  canon review add --project-id <root-id> --scenario-ids-from - --reviewer <me> --actor-id <me> --role reviewer --session-id <session> --pin <sha> --upstream-ref <ref> --as reviewer --json # ids on stdin, one per line
20
21
  canon review add --project-id <root-id> --change-id <chore-slug> --reviewer <me> --actor-id <me> --role reviewer --session-id <session> --pin <sha> --upstream-ref <ref> --as reviewer --json # a chore: one change-level review of its invariants and checks against the diff
21
22
  canon divergence accept --project-id <root-id> --scenario-id <id> --reason "<why the gap stays>" --tracking <issue-url-or-ref> [--expiry YYYY-MM-DD] --reviewer <me> --actor-id <me> --session-id <session> --as reviewer --json
22
23
  canon artifacts verify --full --as reviewer --json # the files the evidence and reviews bind are in the artifact store; refuses when an object store is unreachable
23
24
  canon query --kind evidence --history --as reviewer --json # every version, not only the current record
25
+ canon query --kind evidence --history --scenario-id <id> --as reviewer --json # one scenario's red-then-green pair
24
26
  ```
25
27
 
26
28
  `review add` needs `--upstream-ref <ref>` or `--original-spec-ref <path>`;
@@ -70,10 +72,28 @@ does not sign off; record that instead (`--outcome indeterminate`, or
70
72
 
71
73
  ## Check the checks
72
74
 
73
- Passing is not enough: read each bound test against its scenario's Then
74
- steps. Record a finding (`finding add`) when a check pins behavior the
75
- scenario does not require, or skips a Then step the scenario states: such a
76
- test passes without proving the scenario. Do this before you sign.
75
+ Passing is not enough. Read each bound test against its scenario's Then
76
+ steps, in two separate passes, and say which pass a finding came from:
77
+
78
+ 1. **Spec fidelity.** The brief's step and the scenario against the test
79
+ (the PRD read above). Finding: the check pins behavior the scenario does
80
+ not require, or skips a Then step it states.
81
+ 2. **Standards.** The diff and the test against the code it claims to prove.
82
+ Finding: the test passes without proving anything. The usual ways:
83
+ - the expected value is computed by the code under test (tautology);
84
+ - it asserts an implementation detail (a call count, a private name), not behavior;
85
+ - it never reaches the real path or the real side effect (a file written, a record stored, a request sent);
86
+ - it asserts only that no error happened;
87
+ - it mocks the unit it is meant to test.
88
+
89
+ When a check looks weak, you may name the one-line change to the code that
90
+ should make it fail and run it if it is cheap; if the test stays green, record
91
+ the finding. For a fix, read `canon query --kind evidence --history
92
+ --scenario-id <id>`: expect an earlier non-faithful version, or a named
93
+ closest reproduction in `--ref`; record a finding when neither exists. Put
94
+ one blast-radius fact in the finding or review `--summary`: what else the
95
+ change touches that the checks do not exercise. Do all of this before you
96
+ sign.
77
97
 
78
98
  ## Accept a known gap
79
99
 
@@ -93,7 +113,10 @@ ship guard and stays listed as `accepted-divergence` in `status` and
93
113
 
94
114
  - Every defect is a finding; `finding list` shows round, seq, severity and
95
115
  disposition, so the next seq and the open blockers are one command away.
96
- Close a finding once it is fixed.
116
+ Close a finding once it is dealt with. When you close it `rejected` or
117
+ `deferred`, give the reason in one line with `--reason "<why>"`: the close
118
+ is refused without it (`FINDING_REASON_REQUIRED`), and `finding list` shows
119
+ it as `[reason: …]` so the next reader sees why it was not fixed.
97
120
  - Sign off in one batch with `review add`, all or nothing. It writes
98
121
  directly; nothing to promote. canon stores and re-checks attached bytes
99
122
  and never compares images.
@@ -114,4 +137,14 @@ ship guard and stays listed as `accepted-divergence` in `status` and
114
137
  brief stales it. The chore also needs evidence recorded after its approval
115
138
  (the implementer's `--all-scenarios` run) before it ships.
116
139
 
140
+ ## Excuses that fail
141
+
142
+ | The thought | The reality |
143
+ |---|---|
144
+ | "I will review this under a new session label." | A review is a separate process with its own real session id; a relabelled self-review is not independent (#39). |
145
+ | "The tests pass, so I can sign." | Read the test against the Then steps first; a green test can prove nothing. |
146
+ | "I cannot run the tests, but the evidence looks right." | Say so and record `indeterminate` or `unavailable`; never sign what you could not run. |
147
+ | "This is a minor defect; I will skip the finding." | Every defect is a finding; severity says how much it matters. |
148
+ | "I will edit the brief to fix it." | A brief defect is a finding, not an edit. |
149
+
117
150
  Reference: `reference/canon-review.md`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@journeykit/canon",
3
- "version": "0.24.1",
3
+ "version": "0.26.0",
4
4
  "description": "canon — harness knowledge substrate. JS launcher: platform detection + native binary resolution.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -30,9 +30,9 @@
30
30
  },
31
31
  "dependencies": {},
32
32
  "optionalDependencies": {
33
- "@journeykit/canon-core-darwin-arm64": "0.24.1",
34
- "@journeykit/canon-core-linux-x64": "0.24.1",
35
- "@journeykit/canon-core-win32-x64": "0.24.1"
33
+ "@journeykit/canon-core-darwin-arm64": "0.26.0",
34
+ "@journeykit/canon-core-linux-x64": "0.26.0",
35
+ "@journeykit/canon-core-win32-x64": "0.26.0"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/node": "^20.0.0",