@journeykit/canon 0.25.0 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/skills/reference/brief-align.md +1 -1
- package/dist/skills/reference/brief-clarify.md +1 -1
- package/dist/skills/reference/brief-harden.md +1 -1
- package/dist/skills/reference/brief-polish.md +220 -17
- package/dist/skills/reference/brief-shape.md +2 -2
- package/dist/skills/reference/canon-gate.md +13 -15
- package/dist/skills/reference/canon-inventory.md +13 -9
- package/dist/skills/reference/canon-storage.md +30 -18
- package/dist/skills/reference/canon-subject.md +15 -9
- package/dist/skills/reference/role-planner.md +27 -9
- package/package.json +4 -4
|
@@ -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
|
-
|
|
4
|
-
|
|
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
|
-
##
|
|
9
|
+
## Mode first, style second
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
|
17
|
-
-
|
|
18
|
-
|
|
19
|
-
|
|
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 (
|
|
25
|
-
- Never
|
|
26
|
-
- Never
|
|
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
|
|
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.
|
|
@@ -58,21 +58,19 @@ bytes no longer match the approved digest, or the approval cannot be read,
|
|
|
58
58
|
the gate fails closed: the violation is reported and the exit is `1` (see
|
|
59
59
|
`canon-policy`).
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
`<root>` is stale and could not be refreshed (…); `canon inventory sync`
|
|
75
|
-
lists the problems to fix` and exits 1, since the index it would grade
|
|
61
|
+
The gate is a read command: it writes nothing. A scenario index that is
|
|
62
|
+
stale (a `.feature` file added, edited or deleted since the last sync) is
|
|
63
|
+
graded as the refresh would leave it, built in memory from the changed
|
|
64
|
+
files with the code `canon inventory sync` runs; the index on disk is
|
|
65
|
+
brought up to date by the next command that writes (`canon evidence add`,
|
|
66
|
+
`canon review add`, `canon subject status`) or by `canon inventory sync`.
|
|
67
|
+
It prints no refresh line.
|
|
68
|
+
|
|
69
|
+
A root that fails to sync (format violations, a duplicate id) cannot be
|
|
70
|
+
graded in memory either, and the gate fails closed: it adds
|
|
71
|
+
`uncovered-cell <root> — the scenario index for spec root `<root>` is
|
|
72
|
+
stale and could not be refreshed (…); `canon inventory sync` lists the
|
|
73
|
+
problems to fix` and exits 1, since the index it would grade
|
|
76
74
|
does not hold the edits. The `verifying → shipped` guard refuses with
|
|
77
75
|
the same violation, and `canon status` lists the sync as its only step.
|
|
78
76
|
Fix what the sync lists and run the gate again. See `canon-storage`
|
|
@@ -6,21 +6,25 @@ records every downstream stage joins against.
|
|
|
6
6
|
|
|
7
7
|
## You rarely run it yourself
|
|
8
8
|
|
|
9
|
-
Since 0.16 the commands that read the index
|
|
10
|
-
`
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
Since 0.16 the commands that read the index see added, edited and deleted
|
|
10
|
+
`.feature` files without a sync step. `canon evidence add`, `canon review
|
|
11
|
+
add` and `canon subject status` refresh the index on disk first, with the
|
|
12
|
+
same code this command runs, scoped to the changed files, and say so in one
|
|
13
|
+
stderr line:
|
|
14
14
|
|
|
15
15
|
```
|
|
16
|
-
canon
|
|
16
|
+
canon evidence add: refreshed the scenario index from 1 changed feature file(s) (.canon/specs/kind=feature/area=cart/add.feature): 1 record(s) written
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
+
`canon status` and `canon gate check` are read commands and write nothing:
|
|
20
|
+
they grade the index as that refresh would leave it, built in memory, and
|
|
21
|
+
print no line.
|
|
22
|
+
|
|
19
23
|
So the working loop has no sync step and no `canon format` step: write the
|
|
20
24
|
scenario (`canon scenario new` writes it format-clean) and run the next
|
|
21
25
|
command. Run `canon inventory sync` when:
|
|
22
26
|
|
|
23
|
-
- a
|
|
27
|
+
- a command prints `NOT refreshed` — a root has a format violation (a
|
|
24
28
|
hand-written `Feature:`/`Scenario:` header with no `# canon:` provenance
|
|
25
29
|
comment, an emptied file, …) or a duplicate id, so its files were not
|
|
26
30
|
indexed. `canon gate check` then fails (`uncovered-cell <root>`),
|
|
@@ -57,7 +61,7 @@ and the openspec source `canon init` wrote is removed only after every
|
|
|
57
61
|
pointer brief is written and nothing unread is left under
|
|
58
62
|
`openspec/changes/` (each unread directory is named).
|
|
59
63
|
|
|
60
|
-
The
|
|
64
|
+
The refresh treats a file as stale when its latest index
|
|
61
65
|
generation has another `source_digest` or `scenario_digest`. A
|
|
62
66
|
generation written before 0.15 has no `scenario_digest`, so a repo that
|
|
63
67
|
kept an old index gets it rewritten on the first read.
|
|
@@ -68,7 +72,7 @@ kept an old index gets it rewritten on the first read.
|
|
|
68
72
|
author .feature corpus (canon scenario new)
|
|
69
73
|
│
|
|
70
74
|
▼
|
|
71
|
-
scenario index # refreshed by
|
|
75
|
+
scenario index # refreshed by the writers; `canon inventory sync` rebuilds it
|
|
72
76
|
│
|
|
73
77
|
▼
|
|
74
78
|
canon review add # attests a review — see canon-inventory downstream / canon-gate
|
|
@@ -272,21 +272,27 @@ fails those commands (exit `1`), and `canon init --check-config` reports
|
|
|
272
272
|
|
|
273
273
|
`canon inventory sync` materializes one `Scenario` record per
|
|
274
274
|
`(project_id, scenario_id)` under `.canon/ledger/kind=scenario/`. The
|
|
275
|
-
index is derived — a pure function of the `.feature` files
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
`canon
|
|
283
|
-
|
|
275
|
+
index is derived — a pure function of the `.feature` files. The index is
|
|
276
|
+
stale when a `.feature` file holds a scenario with no live index record,
|
|
277
|
+
or one whose recorded source digest differs from the file's current bytes,
|
|
278
|
+
or when the index holds a scenario of a spec root that no `.feature` file
|
|
279
|
+
of it declares any more.
|
|
280
|
+
|
|
281
|
+
The commands that write records (`canon subject status`, `canon review
|
|
282
|
+
add` and `canon evidence add`) rebuild a stale index on disk first: they
|
|
283
|
+
run `canon inventory sync`'s code on the changed files (validation still
|
|
284
|
+
covers the whole root) and print one stderr line:
|
|
284
285
|
|
|
285
286
|
```
|
|
286
|
-
canon
|
|
287
|
+
canon evidence add: refreshed the scenario index from 1 changed feature file(s) (.canon/specs/kind=feature/area=cart/add.feature): 1 record(s) written
|
|
287
288
|
```
|
|
288
289
|
|
|
289
|
-
|
|
290
|
+
The commands that only read (`canon gate check`, `canon status`) write
|
|
291
|
+
nothing: they grade a stale index as the refresh would leave it, built in
|
|
292
|
+
memory, and print no refresh line. `canon query --kind scenario` and the
|
|
293
|
+
report panels read the index on disk as it is.
|
|
294
|
+
|
|
295
|
+
A deleted scenario is not erased: `canon inventory sync` — and a writer's
|
|
290
296
|
refresh, identically — appends a tombstone, a new generation of its
|
|
291
297
|
record with `removed: true`. The gate, `canon status`, `canon query` and
|
|
292
298
|
the report panels then treat it as absent; re-adding the scenario writes
|
|
@@ -294,7 +300,7 @@ a live generation that supersedes the tombstone. A root that fails to
|
|
|
294
300
|
sync writes no tombstone either.
|
|
295
301
|
|
|
296
302
|
```
|
|
297
|
-
canon
|
|
303
|
+
canon evidence add: refreshed the scenario index from 1 scenario(s) no longer in any feature file (cart.pay.01): 1 record(s) written
|
|
298
304
|
```
|
|
299
305
|
|
|
300
306
|
`canon inventory sync` and every refresh hold an OS file lock on
|
|
@@ -303,17 +309,16 @@ index one change twice; a refresh that waited checks staleness again and
|
|
|
303
309
|
is silent if another run already did the work. The lock is released when
|
|
304
310
|
its holder exits, however it exits, so it never goes stale; a writer
|
|
305
311
|
gives up after 30 s with a message naming the file (`canon inventory
|
|
306
|
-
sync` exits `2`; a
|
|
312
|
+
sync` exits `2`; a writer's refresh is skipped and the command still runs).
|
|
307
313
|
|
|
308
314
|
A fresh index prints nothing. A root that fails to sync (format
|
|
309
315
|
violations, a duplicate id) is named and left as it was, and the command
|
|
310
316
|
still runs; `canon gate check` then fails with `uncovered-cell <root>`,
|
|
311
317
|
the `verifying → shipped` guard refuses, and `canon status` lists only
|
|
312
|
-
`canon inventory sync
|
|
313
|
-
keep only this line:
|
|
318
|
+
`canon inventory sync`:
|
|
314
319
|
|
|
315
320
|
```
|
|
316
|
-
canon
|
|
321
|
+
canon evidence add: found the scenario index stale for 1 changed feature file(s) (…): 0 record(s) written; NOT refreshed: root `root` (2 format violation(s), 0 sync error(s)) — `canon inventory sync` lists them
|
|
317
322
|
```
|
|
318
323
|
|
|
319
324
|
Nothing is written in a directory without `canon.yaml`. `canon inventory
|
|
@@ -342,10 +347,17 @@ comment, only the missing ones):
|
|
|
342
347
|
- `.canon/audit/` — the query/export/purge access log.
|
|
343
348
|
- `.canon/ledger/kind=scenario/` — the scenario index; every reader
|
|
344
349
|
rebuilds it.
|
|
350
|
+
- `.canon/archive/kind=scenario/` — superseded scenario index rows;
|
|
351
|
+
derived like the index.
|
|
345
352
|
- `.canon/scenario-index.lock` — the lock serializing index rebuilds.
|
|
346
353
|
- `.canon/artifact-store.lock` — the lock serializing artifact store
|
|
347
354
|
writes.
|
|
355
|
+
- `.canon/dashboard-snapshot/` — the snapshot `canon dashboard`
|
|
356
|
+
regenerates on every run.
|
|
357
|
+
- `.canon/learn/` and `.canon/r2/` — local learn scratch and the local
|
|
358
|
+
object-store stand-in `canon report` writes.
|
|
348
359
|
|
|
349
360
|
A rerun on a repo that already has `canon.yaml` only refreshes
|
|
350
|
-
`AGENTS.md
|
|
351
|
-
|
|
361
|
+
`AGENTS.md`; `canon migrate` adds the lines an existing `.gitignore`
|
|
362
|
+
lacks, and `canon skills doctor` and `canon init --check-config` name a
|
|
363
|
+
missing one.
|
|
@@ -21,11 +21,13 @@ authored in one place:
|
|
|
21
21
|
adopt`.
|
|
22
22
|
- **Scenarios**: the `@subject:<id>` tag on each Gherkin scenario,
|
|
23
23
|
indexed onto `Scenario.subject_id` by the scenario index (rebuilt by
|
|
24
|
-
`canon inventory sync`, and refreshed by
|
|
24
|
+
`canon inventory sync`, and refreshed on disk by the commands that write
|
|
25
|
+
records; `canon status` and `canon gate check` read it as the refresh
|
|
26
|
+
would leave it, without writing).
|
|
25
27
|
The Subject record carries no scenario list — the spec corpus is the
|
|
26
28
|
only source, and the Subjects report panel, `spec_coverage.scope`, and
|
|
27
29
|
the shipped gate all read it from there. To link a scenario, tag it;
|
|
28
|
-
the next `canon status` or `canon gate check`
|
|
30
|
+
the next `canon status` or `canon gate check` reads the tag. There is
|
|
29
31
|
no subject-side command for it.
|
|
30
32
|
|
|
31
33
|
## The 4-step loop
|
|
@@ -483,11 +485,15 @@ advisory, and is read as before.
|
|
|
483
485
|
or later, a flow step no scenario points at (`brief.flow-uncovered`) or
|
|
484
486
|
one with no failure scenario (`brief.flow-no-failure`): these two
|
|
485
487
|
**gate shipping, never approval**.
|
|
486
|
-
- **Advisories (prose, `prose.*`)**:
|
|
487
|
-
|
|
488
|
-
|
|
488
|
+
- **Advisories (prose, `prose.*`)**: judged in the language the text is
|
|
489
|
+
written in (`language:` overrides): phrase lists, code citations,
|
|
490
|
+
asides, dates and decision notes, stock vocabulary, Korean endings and
|
|
491
|
+
length budgets, in the normative sections plus Intent and Questions;
|
|
492
|
+
sentence, paragraph and key-sentence rules in normative sections.
|
|
493
|
+
Never an authorship verdict. `reference/brief-polish.md` has each rule.
|
|
489
494
|
- **Waivers**: `<!-- canon:waive <rule-id> <section-key> "<reason>" -->`,
|
|
490
|
-
one rule in one section, reason required;
|
|
495
|
+
one rule in one section, reason required; add `match="<text>"` to waive
|
|
496
|
+
only the findings whose matched text contains it. Listed in the output.
|
|
491
497
|
- **`approve`** records the human's answer: it writes
|
|
492
498
|
`Approved: <who> <when> — "<words>" (brief sha256:<digest>)` into the
|
|
493
499
|
brief and records the policy approval in the same call. It refuses
|
|
@@ -605,9 +611,9 @@ uses, so status and the gate cannot disagree about a gap:
|
|
|
605
611
|
unreadable one → `canon init --check-config`; a scenario tagged with a
|
|
606
612
|
subject that has no record → `canon subject new <that id>`; no subject
|
|
607
613
|
at all → `canon subject new`; subjects and `.feature` scenarios but no
|
|
608
|
-
scenario records → `canon inventory sync` (status
|
|
609
|
-
|
|
610
|
-
or a duplicate id, which the sync names). Then per subject, verifying
|
|
614
|
+
scenario records → `canon inventory sync` (status reads the index as a
|
|
615
|
+
refresh would leave it, so this means the refresh would be refused on a
|
|
616
|
+
format violation or a duplicate id, which the sync names). Then per subject, verifying
|
|
611
617
|
first, then building, specced, proposed, shipped: an unapproved brief
|
|
612
618
|
(building and later, and proposed once its change has a brief)
|
|
613
619
|
→ the human step below, which is then the subject's
|
|
@@ -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`);
|
|
28
|
-
|
|
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
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
|
67
|
-
|
|
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
|
-
|
|
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.
|
|
3
|
+
"version": "0.27.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.
|
|
34
|
-
"@journeykit/canon-core-linux-x64": "0.
|
|
35
|
-
"@journeykit/canon-core-win32-x64": "0.
|
|
33
|
+
"@journeykit/canon-core-darwin-arm64": "0.27.0",
|
|
34
|
+
"@journeykit/canon-core-linux-x64": "0.27.0",
|
|
35
|
+
"@journeykit/canon-core-win32-x64": "0.27.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/node": "^20.0.0",
|