@journeykit/canon 0.25.0 → 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.
@@ -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.
@@ -40,4 +40,4 @@ brief has none of them: harden its Invariants and Checks instead.
40
40
  - Explaining writes nothing. Asked what a finding means or what a pass would do, answer in chat and leave the brief untouched.
41
41
  - No pass requires another as a prerequisite. Run the one the findings or the human point at, in any order.
42
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.
43
- - 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.
@@ -22,7 +22,7 @@ marker, never by heading text.
22
22
  - The flow is a numbered list with ids `F1`, `F2`, … each reading sees → does → becomes.
23
23
  - Intent describes what the product does today and Outcome what is true after; leftover implementation paths belong in a `Source:` line or nowhere.
24
24
  - A chore's category is exactly one of `refactor`, `dependency`, `build`, `ci`, `infra`; its invariants and checks say something.
25
- - 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.
26
26
 
27
27
  ## NEVER
28
28
 
@@ -44,4 +44,4 @@ marker, never by heading text.
44
44
  - Explaining writes nothing. Asked what a finding means or what a pass would do, answer in chat and leave the brief untouched.
45
45
  - No pass requires another as a prerequisite. Run the one the findings or the human point at, in any order.
46
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.
47
- - 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.
@@ -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.
@@ -483,11 +483,15 @@ advisory, and is read as before.
483
483
  or later, a flow step no scenario points at (`brief.flow-uncovered`) or
484
484
  one with no failure scenario (`brief.flow-no-failure`): these two
485
485
  **gate shipping, never approval**.
486
- - **Advisories (prose, `prose.*`)**: phrase lists, sentence and
487
- paragraph length, and key-sentence marking in `role=normative`
488
- 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.
489
492
  - **Waivers**: `<!-- canon:waive <rule-id> <section-key> "<reason>" -->`,
490
- 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.
491
495
  - **`approve`** records the human's answer: it writes
492
496
  `Approved: <who> <when> — "<words>" (brief sha256:<digest>)` into the
493
497
  brief and records the policy approval in the same call. It refuses
@@ -24,8 +24,10 @@ After the first `status`, follow `meta.next` of each write. `brief check`
24
24
  lists blockers and advisories; bare `canon brief --as planner` names the next
25
25
  steps. While the brief has blockers, `status` and `change new`'s `meta.next`
26
26
  send you to write it (`write the brief: canon brief check --change-id <slug>
27
- lists N blockers`); the human step appears only once the brief is clean and
28
- 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
29
31
  (`POLICY_CHANGED_SINCE_APPROVAL`): never edit it; the human re-approves.
30
32
 
31
33
  ## Steps
@@ -55,21 +57,37 @@ unapproved. A policy that no longer matches its approval fails closed
55
57
  provenance line is the one exception. A PRD: intent, desired outcome, the user flow as
56
58
  numbered steps `F1`, `F2`, … (sees → does → becomes), step detail per
57
59
  step, acceptance per step with its failure path, questions and
58
- assumptions, out of scope. Write it in the `language:` of `canon.yaml`;
59
- unset, in the request's language, and add "which language?" to the
60
- questions. Anything that could change policy goes to the questions: never
61
- 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
62
67
  prepared (an openspec `proposal.md` read through `plans.sources`) gets a
63
68
  `brief.md` whose `Source: <path>` line points at the plan; canon never
64
69
  edits the plan.
65
70
  3. **Check it.** `brief check` (read-only) lists findings: fix every blocker
66
- (structure) before you stop; advisories (prose) are optional. Five optional
67
- 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:
68
73
  - `reference/brief-shape.md`: structure (sections, order, leftover template text).
69
74
  - `reference/brief-clarify.md`: open questions.
70
75
  - `reference/brief-harden.md`: flow and step detail agree.
71
76
  - `reference/brief-align.md`: scenarios cover the flow.
72
- - `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.
73
91
  4. **Stop for the human.** Report the brief to the human, in the brief's
74
92
  language, asking once, and say they can approve it in `canon dashboard`
75
93
  (the briefs page, `/briefs.html`) or answer in chat. You never click
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@journeykit/canon",
3
- "version": "0.25.0",
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.25.0",
34
- "@journeykit/canon-core-linux-x64": "0.25.0",
35
- "@journeykit/canon-core-win32-x64": "0.25.0"
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",