@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.
- package/dist/skills/SKILL.src.md +23 -5
- 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 +3 -1
- package/dist/skills/reference/brief-polish.md +220 -17
- package/dist/skills/reference/brief-shape.md +3 -2
- package/dist/skills/reference/canon-gate.md +3 -2
- package/dist/skills/reference/canon-report-dashboard.md +26 -5
- package/dist/skills/reference/canon-review.md +14 -2
- package/dist/skills/reference/canon-storage.md +10 -3
- package/dist/skills/reference/canon-subject.md +62 -7
- package/dist/skills/reference/canon-vocab.md +4 -0
- package/dist/skills/reference/role-implementer.md +28 -3
- package/dist/skills/reference/role-orchestrator.md +54 -1
- package/dist/skills/reference/role-planner.md +70 -12
- package/dist/skills/reference/role-reviewer.md +38 -5
- package/package.json +4 -4
package/dist/skills/SKILL.src.md
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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.
|
|
@@ -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
|
|
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
|
|
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
|
-
-
|
|
133
|
-
built
|
|
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
|
|
351
|
-
|
|
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
|
|
283
|
-
adopted change
|
|
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.*`)**:
|
|
436
|
-
|
|
437
|
-
|
|
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;
|
|
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.**
|
|
49
|
-
|
|
50
|
-
|
|
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.
|
|
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`);
|
|
27
|
-
|
|
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.
|
|
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.**
|
|
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
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
|
51
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
74
|
-
steps
|
|
75
|
-
|
|
76
|
-
|
|
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
|
|
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.
|
|
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.
|
|
34
|
-
"@journeykit/canon-core-linux-x64": "0.
|
|
35
|
-
"@journeykit/canon-core-win32-x64": "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",
|