@erclx/canon 5.37.0 → 5.38.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/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/auto-ship/SKILL.md +1 -1
- package/claude/skills/backlog-triage/SKILL.md +1 -1
- package/claude/skills/build-in-slices/SKILL.md +1 -1
- package/claude/skills/git-issue/references/issue.md +11 -10
- package/claude/skills/markdown-craft/references/markdown.md +3 -2
- package/claude/skills/plan-groundwork/references/groundwork.md +53 -24
- package/claude/skills/review-pr/references/post.md +1 -1
- package/claude/skills/role-orchestrator/SKILL.md +1 -1
- package/claude/skills/role-orchestrator/references/orchestrator-parked.md +1 -1
- package/claude/skills/role-planner/SKILL.md +1 -1
- package/claude/skills/role-worker/SKILL.md +1 -1
- package/claude/skills/target-setup/SKILL.md +3 -4
- package/claude/skills/target-setup/references/verify.md +6 -5
- package/claude/skills/task-board/references/tasks.md +3 -1
- package/docs/target/projects.md +1 -1
- package/docs/target/scenarios.md +1 -1
- package/governance/detect.toml +6 -1
- package/package.json +6 -3
- package/scripts/core/install-check.sh +1 -1
- package/src/commands/claude.ts +0 -1
- package/src/commands/pr.ts +1 -1
- package/src/commands/records.ts +0 -1
- package/src/docs/read.ts +15 -21
- package/src/gate/stages.ts +14 -0
- package/src/markdown/structure.ts +1 -1
- package/src/sandbox/equivalence.ts +7 -9
- package/src/teach/browser/head.ts +2 -2
- package/src/teach/browser/sidebar.ts +3 -3
- package/src/teach/browser/theme.ts +2 -2
- package/src/tooling/inject.ts +2 -2
- package/src/tooling/package.ts +15 -18
- package/src/tooling/verify-stack.ts +6 -3
- package/tooling/astro/configs/.oxfmtrc.json +8 -0
- package/tooling/astro/configs/.prettierrc +13 -0
- package/tooling/astro/manifest.toml +13 -0
- package/tooling/astro/reference.md +15 -5
- package/tooling/base/configs/.oxfmtrc.json +7 -0
- package/tooling/base/configs/scripts/verify.ts +1 -1
- package/tooling/base/manifest.toml +6 -6
- package/tooling/base/reference.md +12 -9
- package/tooling/base/seeds/.lintstagedrc +1 -3
- package/tooling/base/seeds/canon/context/ci.md +3 -3
- package/tooling/base/seeds/canon/context/development.md +2 -2
- package/tooling/claude/manifest.toml +5 -1
- package/tooling/go/reference.md +2 -2
- package/tooling/nextjs/configs/.oxlintrc.json +223 -0
- package/tooling/nextjs/manifest.toml +2 -5
- package/tooling/nextjs/reference.md +4 -4
- package/tooling/php/manifest.toml +6 -1
- package/tooling/php/reference.md +2 -2
- package/tooling/python/manifest.toml +6 -1
- package/tooling/python/reference.md +4 -4
- package/tooling/vite-react/manifest.toml +1 -4
- package/tooling/vite-react/reference.md +3 -3
- package/tooling/web/configs/.oxlintrc.json +213 -0
- package/tooling/web/manifest.toml +9 -13
- package/tooling/web/reference.md +14 -14
- package/tooling/base/configs/.prettierrc +0 -12
- package/tooling/base/seeds/.prettierignore +0 -0
- package/tooling/nextjs/configs/eslint.config.js +0 -91
- package/tooling/web/configs/eslint.config.js +0 -82
|
@@ -50,7 +50,7 @@ Check for a `**Files to touch:**` or `## Files to touch` marker per `${CLAUDE_SK
|
|
|
50
50
|
|
|
51
51
|
Read `<plan>` at the main worktree root. This file is the scope for this run.
|
|
52
52
|
|
|
53
|
-
Its sections and its answer contract are fixed by `${CLAUDE_SKILL_DIR}/../plan-feature/references/plan.md`. A blank `- Answer:` accepts the `- Suggested:` line above it, so an unanswered question is a decision this run executes rather than a reason to stop. When this run decides against that suggestion, rewrite the `- Suggested:` line as `overridden at execution to <pick>,` plus the measurement, leaving the `- Answer:` slot blank, and put the same deviation in one line under the open task's `## Findings`, per `${CLAUDE_SKILL_DIR}/../plan-feature/references/plan.md
|
|
53
|
+
Its sections and its answer contract are fixed by `${CLAUDE_SKILL_DIR}/../plan-feature/references/plan.md`. A blank `- Answer:` accepts the `- Suggested:` line above it, so an unanswered question is a decision this run executes rather than a reason to stop. When this run decides against that suggestion, rewrite the `- Suggested:` line as `overridden at execution to <pick>,` plus the measurement, leaving the `- Answer:` slot blank, and put the same deviation in one line under the open task's `## Findings`, per `${CLAUDE_SKILL_DIR}/../plan-feature/references/plan.md`, opening with the bold source-and-date label that `${CLAUDE_SKILL_DIR}/../task-board/references/tasks.md` fixes.
|
|
54
54
|
|
|
55
55
|
## Step 2: implement
|
|
56
56
|
|
|
@@ -112,7 +112,7 @@ Take the task stem from the item heading. Every verdict leaves the folder slug o
|
|
|
112
112
|
- **keep**: append the `## Findings` line naming what reproduced. The backlog line stays.
|
|
113
113
|
- **promote**: move the backlog line to the bottom of `## Needs a plan` in `.canon/tasks/priority.md`, with a `Waiting on` cell stating what cleared and ending in `last`, then append the `## Findings` line.
|
|
114
114
|
|
|
115
|
-
Write the `## Findings` line as `-
|
|
115
|
+
Write the `## Findings` line as `- **<YYYY-MM-DD>, backlog triage <slug>:** <verdict>, <evidence>`, the bold label form `${CLAUDE_SKILL_DIR}/../task-board/references/tasks.md` fixes. It is the last write on every verdict with a task file behind it, since Step 1 reads it as applied and a line written ahead of a refused move would mark a row done that never moved.
|
|
116
116
|
|
|
117
117
|
Before any promote, check the roster the way `task-board` step 4 does: a live session in this repository whose name starts with `orchestrator-` and whose `sessionId` is not this one. When one is found, write the `## Findings` line but not `priority.md` or `backlog.md`, and message that session with the row and its cell so it places the row itself.
|
|
118
118
|
|
|
@@ -40,7 +40,7 @@ Leave the build passing at every commit. A slice that cannot pass on its own is
|
|
|
40
40
|
|
|
41
41
|
A defect outside the plan that turns up mid-build is a finding, not a slice. Fixing it on this branch widens a diff the plan scoped and hides the fix inside an unrelated review.
|
|
42
42
|
|
|
43
|
-
- Write it in one line under the open task's `## Findings`, naming the file and what is wrong, when the build is closing a task.
|
|
43
|
+
- Write it in one line under the open task's `## Findings`, naming the file and what is wrong, when the build is closing a task. Open the line with the bold source-and-date label from `${CLAUDE_SKILL_DIR}/../task-board/references/tasks.md`.
|
|
44
44
|
- Carry it in the handover message instead when no task exists, and never create a file to hold it.
|
|
45
45
|
- Leave the file as it is, even when the fix is one line.
|
|
46
46
|
- Fix it here only when the plan's own change cannot pass without it, and then say so in the commit that does.
|
|
@@ -53,17 +53,18 @@ Does not govern:
|
|
|
53
53
|
### Feedback
|
|
54
54
|
|
|
55
55
|
- One `## Toolkit feedback` wrapper holds every field as a nested `###` heading. A flat `##` field is not recognized.
|
|
56
|
-
|
|
57
|
-
|
|
|
58
|
-
|
|
|
59
|
-
| `###
|
|
60
|
-
| `###
|
|
61
|
-
| `###
|
|
62
|
-
| `###
|
|
63
|
-
| `###
|
|
56
|
+
|
|
57
|
+
| Field | Holds |
|
|
58
|
+
| -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
|
59
|
+
| `### From project` (optional) | the reporting project's name or kind. |
|
|
60
|
+
| `### Surface` (required) | the toolkit surface and its type, and the specific file or name when known. |
|
|
61
|
+
| `### Observed` (required) | the observed behavior. |
|
|
62
|
+
| `### Expected` (optional) | the expected behavior, or `unclear`. |
|
|
63
|
+
| `### Repro` (optional) | repro details, or `none`. |
|
|
64
|
+
| `### Proposed fix` (required) | a proposed fix, or `open` when the session settled on no direction. |
|
|
64
65
|
| `### Suggested shape` (optional) | the template, output, or field list the change should take, inside a `<details>` block. Omit the heading when there is none. |
|
|
65
|
-
| `### Why` (optional)
|
|
66
|
-
| `### Links` (optional)
|
|
66
|
+
| `### Why` (optional) | the reasons for the fix, one bullet each. Omit the heading when there are none. |
|
|
67
|
+
| `### Links` (optional) | the issues, pull requests, or documents the report cites, one per line. Omit the heading when there are none. |
|
|
67
68
|
|
|
68
69
|
- A required field is refused when present but empty. Write the fallback literal for From project, Expected, and Repro rather than leaving one blank.
|
|
69
70
|
|
|
@@ -42,6 +42,7 @@ Does not govern:
|
|
|
42
42
|
- H1 for document title, H2 for main sections, H3 for subsections
|
|
43
43
|
- Use sentence case for all headings (H1, H2, H3). The audit reports a heading or a frontmatter `title` with a capitalized word after the first, and a proper noun it reports stays as it is.
|
|
44
44
|
- Proper nouns and product names retain their casing in headings
|
|
45
|
+
- Shape a record as H2 per section and H3 per item a reader looks up by name, with a few bullets or prose under each H3. Use a table only for a fixed-shape set.
|
|
45
46
|
|
|
46
47
|
### Seams
|
|
47
48
|
|
|
@@ -67,11 +68,11 @@ Does not govern:
|
|
|
67
68
|
|
|
68
69
|
- Use prose by default. Reserve bullets for discrete, unrelated items.
|
|
69
70
|
- Keep paragraphs to four sentences or fewer. Split longer blocks at the next logical boundary.
|
|
70
|
-
- Past roughly 700 characters in one paragraph, folding in the lines that wrap it, split at the next logical boundary as well.
|
|
71
|
+
- Past roughly 700 characters in one paragraph, folding in the lines that wrap it, split at the next logical boundary as well. Two long sentences satisfy the sentence cap and still overload the reader. This number sits well above the bullet checkpoint because a paragraph is read straight through and a bullet is scanned. A paragraph a branch adds or grows past it fails the push in a context entry through the `paragraph-growth` gate stage, and an unchanged long paragraph passes. Elsewhere it is the audit's warning and no more.
|
|
71
72
|
|
|
72
73
|
### Bullets
|
|
73
74
|
|
|
74
|
-
- Keep bullets tight. Past roughly 250 characters in one top-level bullet, counting the lines that continue it and excluding any bullet nested under it, the overflow belongs in prose. A bullet a branch adds or grows past it fails the push in a context entry through the `paragraph-growth` gate stage, and an unchanged one passes until edited. Elsewhere it is the audit's warning and no more. The number is a checkpoint rather than a cap
|
|
75
|
+
- Keep bullets tight. Past roughly 250 characters in one top-level bullet, counting the lines that continue it and excluding any bullet nested under it, the overflow belongs in prose. A bullet a branch adds or grows past it fails the push in a context entry through the `paragraph-growth` gate stage, and an unchanged one passes until edited. Elsewhere it is the audit's warning and no more. The number is a checkpoint rather than a cap.
|
|
75
76
|
- Keep a run of consecutive top-level bullets to 5 or fewer under one heading, and fold a longer one into prose, an H3 per item, or a split at the seam. A blank line, a continuation, and a nested item do not break the run, and a paragraph or heading does. The audit reports a longer run and the `bullet-stacks` gate stage fails it.
|
|
76
77
|
- Leave a run whose every item opens with a bold label or a link as it is, since each item names its own subject. A task box (`[ ]`) is not a link, so a checklist counts as plain bullets. Numbered items do not count toward the cap. Governance rules and `REQUIREMENT.md` sit outside it, since a rule is one imperative per bullet.
|
|
77
78
|
- Collapse a stack of bullets narrating one subsystem into a single `###` subsection carrying one narrative. Splitting a heavy bullet into three light ones satisfies the checkpoint above and leaves the reader no better off, and subdividing a block does not lighten the bullets inside it, so the two rules answer different defects.
|
|
@@ -33,8 +33,8 @@ Governs a groundwork track under `.canon/groundwork/<nn>-<slug>/`: folder layout
|
|
|
33
33
|
Does not govern:
|
|
34
34
|
|
|
35
35
|
- **`groundwork-spikes.md`**: `08-spikes.md`, what each spike records, and the folders holding what a spike produced
|
|
36
|
-
- **`intake.md`**: A dump of many findings filed by domain, each
|
|
37
|
-
- **`plan.md`**: the feature plan a closed track feeds, and
|
|
36
|
+
- **`intake.md`**: A dump of many findings filed by domain, each with a verdict
|
|
37
|
+
- **`plan.md`**: the feature plan a closed track feeds, and its answer contract
|
|
38
38
|
- **`tasks.md`**: the task file a closing track writes, and the origin line pointing back at the folder
|
|
39
39
|
- **the `write-human` skill**: voice, rhythm, and sentence construction
|
|
40
40
|
- **`markdown.md`**: headings, punctuation, word choice, and file references
|
|
@@ -42,9 +42,9 @@ Does not govern:
|
|
|
42
42
|
|
|
43
43
|
## Folder name
|
|
44
44
|
|
|
45
|
-
- Name the folder `<nn>-<slug>`, a two-digit
|
|
46
|
-
- The ordinal marks a folder per track, opened once and worked over time,
|
|
47
|
-
- Claim the ordinal through `canon records ordinal groundwork <slug> --claim`, which reads the highest one across both `.canon/groundwork/` and `.canon/intake/` and creates the folder in one atomic act, so two sessions opening at once never share a number. The two kinds share one
|
|
45
|
+
- Name the folder `<nn>-<slug>`, a two-digit ordinal and a kebab-case slug.
|
|
46
|
+
- The ordinal marks a folder per track, opened once and worked over time, unlike a file addressed by name, one with its own sorting prefix, or one overwritten on purpose.
|
|
47
|
+
- Claim the ordinal through `canon records ordinal groundwork <slug> --claim`, which reads the highest one across both `.canon/groundwork/` and `.canon/intake/` and creates the folder in one atomic act, so two sessions opening at once never share a number. The two kinds share one order.
|
|
48
48
|
- With neither folder holding an entry, the first one opened takes `01`, whatever number a track's own files start at.
|
|
49
49
|
- Never renumber an existing folder. The ordinal is the order it opened, and a later reader cites it by that name.
|
|
50
50
|
|
|
@@ -53,17 +53,17 @@ Does not govern:
|
|
|
53
53
|
A track works when a session that has never seen it re-enters from the folder alone and can answer each of these:
|
|
54
54
|
|
|
55
55
|
- Which single question is being measured, and why is it running now?
|
|
56
|
-
- What is the current state, measured
|
|
57
|
-
- Which questions are
|
|
56
|
+
- What is the current state, measured this pass rather than carried in?
|
|
57
|
+
- Which questions are open, where is the evidence, and what overturns it?
|
|
58
58
|
- What was decided, and what was considered and dropped?
|
|
59
59
|
|
|
60
|
-
A track failing these is non-conforming even when it
|
|
60
|
+
A track failing these is non-conforming even when it meets every shape rule.
|
|
61
61
|
|
|
62
62
|
## Frontmatter and dating
|
|
63
63
|
|
|
64
64
|
- `title` (required): the track subject in sentence case
|
|
65
65
|
- `description` (required): one line naming what the track measures
|
|
66
|
-
- `date` (required, `README.md` only): the day
|
|
66
|
+
- `date` (required, `README.md` only): the day it opened, as `YYYY-MM-DD`
|
|
67
67
|
|
|
68
68
|
Carry the opening date as a frontmatter field rather than a sentence in the body, since nothing that walks the folder reads prose. Date the folder once rather than every file, because a per-file date goes stale on the first edit. The checkable half is the commit each measurement was taken against, which the file holding that measurement names.
|
|
69
69
|
|
|
@@ -90,13 +90,13 @@ Orients. Holds no findings.
|
|
|
90
90
|
|
|
91
91
|
- A one-line definition of the investigation
|
|
92
92
|
- A `## Why` section stating why the track is running now
|
|
93
|
-
- A file-map table of filename and what it holds, kept current
|
|
93
|
+
- A file-map table of filename and what it holds, kept current
|
|
94
94
|
- A `## Method` section splitting internal sources from external ones, naming which were used and which were not yet done, and listing under a leads heading any external source found but not opened
|
|
95
95
|
|
|
96
96
|
### Closing sections and phase
|
|
97
97
|
|
|
98
98
|
- A `## Prior art` section
|
|
99
|
-
- A `## Source citation` section stating the rule below, so a returning session
|
|
99
|
+
- A `## Source citation` section stating the rule below, so a returning session finds it in the folder
|
|
100
100
|
- The phase stated out loud in the first three lines, in the form `Groundwork phase. Nothing here is a feature plan.`
|
|
101
101
|
|
|
102
102
|
Every claim about a source outside the project carries a link to it, wherever the claim appears in the folder. Without one, a later reader cannot tell a fetched page from recall. A source found and not read is listed as a lead and is never cited, since a link to a page nobody opened is worse than no link. Listing it stops a later pass re-searching for it.
|
|
@@ -109,23 +109,19 @@ Facts before opinion. Verified measurement only, taken during this pass.
|
|
|
109
109
|
|
|
110
110
|
- Never carry a figure from a previous session without re-measuring, since every number built on a stale one is quietly wrong.
|
|
111
111
|
- Mark an inference as an inference where one is unavoidable.
|
|
112
|
-
- Measure only what an open question needs. A number with no question attached is
|
|
112
|
+
- Measure only what an open question needs. A number with no question attached is how the groundwork becomes the work.
|
|
113
113
|
|
|
114
114
|
## 00-scope.md
|
|
115
115
|
|
|
116
|
-
Written when the subject is large enough to run away. Holds constraints, risks, the open
|
|
116
|
+
Written when the subject is large enough to run away. Holds constraints, risks, the open questions, and what a decision would touch. A small track skips it.
|
|
117
117
|
|
|
118
118
|
## 06-decision.md
|
|
119
119
|
|
|
120
|
-
Closes the folder. Everything above it is input.
|
|
120
|
+
Closes the folder. Everything above it is input. Write it from the skeleton under `## Template`, with an H3 per dropped alternative.
|
|
121
121
|
|
|
122
|
-
|
|
123
|
-
- The goal
|
|
124
|
-
- The items to do
|
|
125
|
-
- What was considered and dropped
|
|
126
|
-
- `## Revisit when`: the "Overturned by" line of each lean that became the decision, carried over as written. A lean the decision did not take carries nothing, and a track whose deciding leans carried no overturn line omits the section.
|
|
122
|
+
`## Revisit when` carries the "Overturned by" line of each lean that became the decision, as written. A lean the decision did not take carries nothing, and a track whose deciding leans carried no overturn line omits the section.
|
|
127
123
|
|
|
128
|
-
The dropped list
|
|
124
|
+
The dropped list stops a future session re-proposing a rejected idea.
|
|
129
125
|
|
|
130
126
|
## 07-next-session.md
|
|
131
127
|
|
|
@@ -141,8 +137,8 @@ Every open question carries a lean, wherever it appears. A bare numbered list ha
|
|
|
141
137
|
- Overturned by: <the finding that would change it>
|
|
142
138
|
```
|
|
143
139
|
|
|
144
|
-
- Pair every lean with what would overturn it. A lean with no falsifier is
|
|
145
|
-
- On a measurement rather than a judgment, write `- Leaning: none, needs measuring` and drop the overturn line. A guess at a number is worse
|
|
140
|
+
- Pair every lean with what would overturn it. A lean with no falsifier is opinion.
|
|
141
|
+
- On a measurement rather than a judgment, write `- Leaning: none, needs measuring` and drop the overturn line. A guess at a number is worse.
|
|
146
142
|
- Mark each question open or answered, and repeat the open ones at the end of the file they belong to. That gives the decision file its agenda for free.
|
|
147
143
|
|
|
148
144
|
A lean is weaker than a plan's suggestion: it records the current read on an open question, not a decision to accept at execution time.
|
|
@@ -155,13 +151,15 @@ A lean is weaker than a plan's suggestion: it records the current read on an ope
|
|
|
155
151
|
|
|
156
152
|
## Anti-patterns
|
|
157
153
|
|
|
158
|
-
- **The groundwork becomes the work.** Gathering expands until the measuring costs more than the change it justifies. Cap it, and drop any thread with no question
|
|
154
|
+
- **The groundwork becomes the work.** Gathering expands until the measuring costs more than the change it justifies. Cap it, and drop any thread with no question.
|
|
159
155
|
- **Deciding by omission.** Closing a track while an unresolved question quietly fails an outcome. Resolve it or record it as knowingly accepted.
|
|
160
156
|
- **Recording a constraint discovered while defending a decision.** Check it against the alternative design first, or a fact about the current shape reads as inherent to the problem.
|
|
161
157
|
- **A plan written before the groundwork.** Every track that has done this had to supersede the plan it wrote.
|
|
162
158
|
|
|
163
159
|
## Template
|
|
164
160
|
|
|
161
|
+
### README.md
|
|
162
|
+
|
|
165
163
|
```markdown
|
|
166
164
|
---
|
|
167
165
|
title: <Track subject>
|
|
@@ -195,10 +193,41 @@ Groundwork phase. Nothing here is a feature plan.
|
|
|
195
193
|
|
|
196
194
|
## Prior art
|
|
197
195
|
|
|
198
|
-
<
|
|
196
|
+
### <earlier plan or folder>
|
|
197
|
+
|
|
198
|
+
<What it decided and how this track supersedes or builds on it. Say not to go looking for it again.>
|
|
199
199
|
|
|
200
200
|
## Source citation
|
|
201
201
|
|
|
202
202
|
Every claim about a source outside the project carries a link. A source found
|
|
203
203
|
and not read is listed as a lead and is never cited.
|
|
204
204
|
```
|
|
205
|
+
|
|
206
|
+
### 06-decision.md
|
|
207
|
+
|
|
208
|
+
```markdown
|
|
209
|
+
---
|
|
210
|
+
title: <Track subject> decision
|
|
211
|
+
description: <one line naming what the track decided>
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
# <Track subject> decision
|
|
215
|
+
|
|
216
|
+
## Problem and goal
|
|
217
|
+
|
|
218
|
+
<The problem stated once, then what done looks like.>
|
|
219
|
+
|
|
220
|
+
## What to do
|
|
221
|
+
|
|
222
|
+
1. <item>
|
|
223
|
+
|
|
224
|
+
## Considered and dropped
|
|
225
|
+
|
|
226
|
+
### <alternative>
|
|
227
|
+
|
|
228
|
+
<Why it was dropped, and what would revive it.>
|
|
229
|
+
|
|
230
|
+
## Revisit when
|
|
231
|
+
|
|
232
|
+
- <an "Overturned by" line, as written>
|
|
233
|
+
```
|
|
@@ -80,7 +80,7 @@ Keying either half of the opening's threshold on the grade was measured wrong: a
|
|
|
80
80
|
|
|
81
81
|
The cost is that the merge decision no longer reads off the heading alone, since an open heading covers a minor as well as a critical. Take it from the counts on the summary line, which is where they already sit. No summary line reports the merge as unblocked under either heading, since a thread reading open cannot also report that nothing blocks it.
|
|
82
82
|
|
|
83
|
-
A minor the dispatched worker declines is what needs a surface that survives the merge, rather than every minor, since one that gets fixed on the branch needs no durable record. Write a declined minor into the `## Findings` section of the task the branch closes, which is where the queue-refill sweep already routes a finding that changes another task. A declined finding left on the thread alone is lost the moment the pull request merges.
|
|
83
|
+
A minor the dispatched worker declines is what needs a surface that survives the merge, rather than every minor, since one that gets fixed on the branch needs no durable record. Write a declined minor into the `## Findings` section of the task the branch closes, as a line opening with the bold source-and-date label from `${CLAUDE_SKILL_DIR}/../task-board/references/tasks.md`, which is where the queue-refill sweep already routes a finding that changes another task. A declined finding left on the thread alone is lost the moment the pull request merges.
|
|
84
84
|
|
|
85
85
|
Read the state off the most recent review comment rather than off the presence of a closed one. A close-out does not close the pull request, so a commit pushed after it gets its own pass, and that pass reopens the review under `## Review` when it raises a finding of any grade.
|
|
86
86
|
|
|
@@ -43,7 +43,7 @@ No surface carries cross-version sequencing, so report none. A row's `Waiting on
|
|
|
43
43
|
|
|
44
44
|
A compaction is a moment this skill cannot detect, so the human asks for each side of it and this skill reads the matching runbook when they do.
|
|
45
45
|
|
|
46
|
-
On a request to write the handoff or save the session, read `${CLAUDE_SKILL_DIR}/references/orchestrator-handoff.md` and follow it. It sends the generic half to `canon:session-map`, which writes a session map per `${CLAUDE_SKILL_DIR}/../session-map/references/session.md`, then adds the decisions taken under delegated authority as this role's extension. A lesson about how to work goes to the owning task's Findings. Write nothing to the handoff that the board, a task file, or a groundwork folder already carries.
|
|
46
|
+
On a request to write the handoff or save the session, read `${CLAUDE_SKILL_DIR}/references/orchestrator-handoff.md` and follow it. It sends the generic half to `canon:session-map`, which writes a session map per `${CLAUDE_SKILL_DIR}/../session-map/references/session.md`, then adds the decisions taken under delegated authority as this role's extension. A lesson about how to work goes to the owning task's Findings, as a bold-labeled line per `${CLAUDE_SKILL_DIR}/../task-board/references/tasks.md`. Write nothing to the handoff that the board, a task file, or a groundwork folder already carries.
|
|
47
47
|
|
|
48
48
|
On a request to resume after a compaction, read `${CLAUDE_SKILL_DIR}/references/orchestrator-resume.md`, which reads that file back with the board and the groundwork behind the live work.
|
|
49
49
|
|
|
@@ -31,7 +31,7 @@ The blocker cell states what the row waits on, and each kind is tested different
|
|
|
31
31
|
|
|
32
32
|
Every measurement this pass takes is a blocker re-test on a row already filed, and it decides whether that row can start rather than how big it is. Sizing a row is the filing boundary `role-orchestrator` states under `## Boundaries`, which leaves the extent and the cause to whoever plans the row.
|
|
33
33
|
|
|
34
|
-
Write the result into the row. A re-test reported in chat is lost at the next compaction and the next pass measures the same thing again. Rewrite a blocker cell whose test no longer holds, move the row to the group its new state puts it in, and record the measurement in that task's `## Findings` with the date it was taken. `### Writing the board` in `role-orchestrator` owns the method, and `canon tasks validate` runs once the board is rewritten and before the report.
|
|
34
|
+
Write the result into the row. A re-test reported in chat is lost at the next compaction and the next pass measures the same thing again. Rewrite a blocker cell whose test no longer holds, move the row to the group its new state puts it in, and record the measurement in that task's `## Findings` as a line labeled with this session and the date it was taken, per `${CLAUDE_SKILL_DIR}/../task-board/references/tasks.md`. `### Writing the board` in `role-orchestrator` owns the method, and `canon tasks validate` runs once the board is rewritten and before the report.
|
|
35
35
|
|
|
36
36
|
## Re-testing a backlog row
|
|
37
37
|
|
|
@@ -69,7 +69,7 @@ it.
|
|
|
69
69
|
|
|
70
70
|
- Never write `.canon/tasks/priority.md` or `.canon/tasks/backlog.md`. Both are gitignored, so an overwrite drops a row with no history to recover it from.
|
|
71
71
|
- Never write the task file. The record is the whole output, and a row edited from here changes what the controller reads back as the state of the board.
|
|
72
|
-
- One exception, and it belongs to `plan-groundwork` alone: that skill writes a single task file at close, recording what the track concluded. Its own `## Write scope` states the exception, and this line exists so the rule above does not forbid what that skill requires.
|
|
72
|
+
- One exception, and it belongs to `plan-groundwork` alone: that skill writes a single task file at close, recording what the track concluded. Each finding in it opens with the bold source-and-date label that `${CLAUDE_SKILL_DIR}/../task-board/references/tasks.md` fixes. Its own `## Write scope` states the exception, and this line exists so the rule above does not forbid what that skill requires.
|
|
73
73
|
- Report what the row got wrong rather than repairing it. A stale count, a moved line, or a path that no longer resolves goes into the record and into the handback, and the controller decides which of the two carries the correction.
|
|
74
74
|
- Leave the cross-feature call alone. Which rows collide, what merges before what, and whether a row should run at all stay with the controller. `priority.md` shows blockers and file sets and is enough to write a confident merge order off a partial picture, which is the failure this boundary exists to prevent.
|
|
75
75
|
|
|
@@ -37,7 +37,7 @@ which `auto-ship` Step 2 loads.
|
|
|
37
37
|
- Copy a ready folder's files verbatim to the paths its plan's constraints name, when the plan's `**Constraints:**` block names one, and edit only what the gate or the overview's own list requires. A rewrite discards the text the handoff exists to carry.
|
|
38
38
|
- Report a draft flag reading ready once, and leave it cleared. The ship chain marks the pull request a draft, and only `canon pr lift` or the operator un-marks it, so a flag reading ready afterwards passed every gate the verb reads or was readied by a person, which GitHub requires before a merge. Re-drafting fights them, and no surface in the tree states a reason to.
|
|
39
39
|
- Leave the lift to the reviewers, who run `canon pr lift` on posting their close, and to the controlling session as the fallback. Refuse an instruction to ready the pull request directly, whoever sends it, since a direct ready skips every gate the verb reads.
|
|
40
|
-
- Rewrite a plan question's `- Suggested:` line as `overridden at execution to <pick>,` plus the measurement when this build decides against an unanswered one, leaving the `- Answer:` slot blank, and put the same deviation in one line under the open task's `## Findings`, per `${CLAUDE_SKILL_DIR}/../plan-feature/references/plan.md
|
|
40
|
+
- Rewrite a plan question's `- Suggested:` line as `overridden at execution to <pick>,` plus the measurement when this build decides against an unanswered one, leaving the `- Answer:` slot blank, and put the same deviation in one line under the open task's `## Findings`, per `${CLAUDE_SKILL_DIR}/../plan-feature/references/plan.md`, opening with the bold source-and-date label that `${CLAUDE_SKILL_DIR}/../task-board/references/tasks.md` fixes.
|
|
41
41
|
|
|
42
42
|
## The board is read-only
|
|
43
43
|
|
|
@@ -99,7 +99,7 @@ Step 2: `canon tooling sync <tooling-stack> --write` installs stack deps, script
|
|
|
99
99
|
CANON_NON_INTERACTIVE=1 canon tooling sync <tooling-stack> <target> --write
|
|
100
100
|
```
|
|
101
101
|
|
|
102
|
-
Monorepo with multiple language roots: run `canon init` once at the repo root so `base` (husky,
|
|
102
|
+
Monorepo with multiple language roots: run `canon init` once at the repo root so `base` (husky, oxfmt, commitlint, CI) lands single, then sync each subtree with `--skip base` so the shared layer is not re-dropped. Take the subtree commands from the per-root rows the preview printed rather than picking folders by hand. Detection found each root and its stack, and the two commands below show the shape.
|
|
103
103
|
|
|
104
104
|
```bash
|
|
105
105
|
CANON_NON_INTERACTIVE=1 canon tooling sync vite-react ./frontend --skip base --write
|
|
@@ -112,11 +112,10 @@ A subtree sync withholds `.github/`, since GitHub reads workflows only at the re
|
|
|
112
112
|
|
|
113
113
|
Step 3: post-sync fixups. Golden configs arrive from sync, so no config generation is required. But a few items may need a one-time touch:
|
|
114
114
|
|
|
115
|
-
- **ESLint version pin.** If `bun create vite` installed `eslint@^10` and the manifest pins `eslint@^9`, sync does not override a present dep. Run `bun add -d eslint@^9` if `bun run lint:fix` fails with `Class extends value undefined`.
|
|
116
115
|
- **File naming.** `bun create vite`'s `App.tsx` violates the `KEBAB_CASE` rule. Rename to `app.tsx` and update the import in `main.tsx`.
|
|
117
116
|
- **Docs.** Run `canon tooling reference <tooling-stack>` and `canon tooling reference web` for any stack-specific follow-ups (Chrome extension overrides, setup script details).
|
|
118
117
|
|
|
119
|
-
Do not generate ESLint, Vitest, or Playwright configs. They ship as golden files. Generating from prose duplicates what sync already installed.
|
|
118
|
+
Do not generate oxlint, ESLint, Vitest, or Playwright configs. They ship as golden files. Generating from prose duplicates what sync already installed.
|
|
120
119
|
|
|
121
120
|
## Phase: gov
|
|
122
121
|
|
|
@@ -161,7 +160,7 @@ After the chain, report:
|
|
|
161
160
|
|
|
162
161
|
- Domains installed, with a check per domain
|
|
163
162
|
- Tooling stack synced, or skipped. Name the layers pulled via the extends chain.
|
|
164
|
-
- Any post-sync fixups applied (
|
|
163
|
+
- Any post-sync fixups applied (filename renames)
|
|
165
164
|
- `verify` outcome, naming the depth it ran at
|
|
166
165
|
|
|
167
166
|
### Outcomes and gaps
|
|
@@ -58,10 +58,11 @@ exit code is the whole verdict.
|
|
|
58
58
|
| Order | Script | Skip if |
|
|
59
59
|
| ----- | ----------- | --------------------------- |
|
|
60
60
|
| 1 | `lint:fix` | absent |
|
|
61
|
-
| 2 | `
|
|
62
|
-
| 3 | `
|
|
63
|
-
| 4 | `
|
|
64
|
-
| 5 | `
|
|
61
|
+
| 2 | `format` | absent |
|
|
62
|
+
| 3 | `typecheck` | absent |
|
|
63
|
+
| 4 | `check` | absent |
|
|
64
|
+
| 5 | `test:run` | absent, fall back to `test` |
|
|
65
|
+
| 6 | `build` | absent |
|
|
65
66
|
|
|
66
67
|
Do not run composite scripts like `check:full`. Do not run `dev`, `preview`,
|
|
67
68
|
`test:e2e`, `test:ui`, or `screenshot` at this depth. They belong to `deep`
|
|
@@ -80,7 +81,7 @@ server, and whether the screenshot harness produces output.
|
|
|
80
81
|
| 3 | `test:e2e` | absent | leaf script: exit code |
|
|
81
82
|
| 4 | `screenshot` | absent | leaf script: exit code |
|
|
82
83
|
|
|
83
|
-
`deep` runs the four above and not the
|
|
84
|
+
`deep` runs the four above and not the six leaf scripts, which the default
|
|
84
85
|
depth already covers. The two sets are disjoint, so running both depths runs
|
|
85
86
|
every declared script exactly once.
|
|
86
87
|
|
|
@@ -82,6 +82,8 @@ Two headings, `## Outcomes` and `## Findings`. Outcomes are future and checkable
|
|
|
82
82
|
|
|
83
83
|
Add no third heading. Status stays inline on an outcome rather than becoming an "In progress" section.
|
|
84
84
|
|
|
85
|
+
Open each finding with a bold label naming its source and date, as in `- **planner, 2026-10-11:** <finding>`, and keep it to a few sentences. Anything longer moves into the plan or the groundwork folder. Outcomes stay a checklist, since the board tools read them by their `- [ ]` line.
|
|
86
|
+
|
|
85
87
|
Size the outcomes so one pull request closes all of them, and split the task before handing it off rather than after. A task closes whole or not at all, so outcomes spanning two pull requests leave the board showing in-progress work no branch carries.
|
|
86
88
|
|
|
87
89
|
A sliced task is the one allowed exception: a task the operator decided to ship across several pull requests on purpose, one slice each. Its `Pull request:` line lists every slice, so the board records which merged work covered which outcomes, and it closes whole when the last slice's merge finds every outcome ticked and no slice left pending.
|
|
@@ -115,7 +117,7 @@ Why this task exists and what it depends on.
|
|
|
115
117
|
|
|
116
118
|
## Findings
|
|
117
119
|
|
|
118
|
-
- What constrains the task
|
|
120
|
+
- **<source>, <YYYY-MM-DD>:** What constrains the task.
|
|
119
121
|
|
|
120
122
|
> Test strategy: <unit | component | e2e | visual | manual>, what is being verified
|
|
121
123
|
```
|
package/docs/target/projects.md
CHANGED
|
@@ -57,7 +57,7 @@ The chain is:
|
|
|
57
57
|
|
|
58
58
|
- `canon init` installs base tooling, Claude seeds, and governance rules into `.claude/rules/` in the same pass
|
|
59
59
|
- `canon tooling sync <stack> --write` adds stack-specific deps, scripts, and gitignore entries
|
|
60
|
-
- The agent reads `canon tooling reference <stack>` (plus parents) as its audit context, follows it to generate
|
|
60
|
+
- The agent reads `canon tooling reference <stack>` (plus parents) as its audit context, follows it to generate oxlint, vitest, playwright configs and the stack's setup script, and extends `canon/context/ci.md` and `canon/context/development.md` per the reference's extend sections <!-- audit-ignore-citations: canon/context/ci.md, canon/context/development.md -->
|
|
61
61
|
- The `verify` phase runs the installed `package.json` scripts (lint, typecheck, check, test, build) and reports pass or fail
|
|
62
62
|
- The `indexes` phase bootstraps the `index.md` system over the project's own documentation folders, confirming candidate folders with the operator rather than running unattended
|
|
63
63
|
|
package/docs/target/scenarios.md
CHANGED
|
@@ -46,7 +46,7 @@ canon tooling sync vite-react ./frontend --skip base --write
|
|
|
46
46
|
canon tooling sync python ./backend --skip base --write
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
`--skip base` drops the `base` layer from each subtree sync, so husky,
|
|
49
|
+
`--skip base` drops the `base` layer from each subtree sync, so husky, oxfmt, commitlint, and CI stay single at the repo root. The one exception is base's verify runner, `scripts/verify.ts` and `scripts/verify.json`, which every subtree still receives so it can verify on its own. Without it, every subtree re-drops husky, and since git honors only one `core.hooksPath` the extra hook dirs silently break. Each subtree still gets its own framework configs (oxlint, vitest, tsconfig, vite), and its own stack reference reads through `canon tooling reference <stack>`.
|
|
50
50
|
|
|
51
51
|
A sync whose target sits below the git root knows it is writing into a subfolder:
|
|
52
52
|
|
package/governance/detect.toml
CHANGED
|
@@ -27,7 +27,12 @@ files = [".github/workflows"]
|
|
|
27
27
|
|
|
28
28
|
["dependencies"]
|
|
29
29
|
deps = []
|
|
30
|
-
files = [
|
|
30
|
+
files = [
|
|
31
|
+
"**/package.json",
|
|
32
|
+
"**/pyproject.toml",
|
|
33
|
+
"**/go.mod",
|
|
34
|
+
"**/composer.json",
|
|
35
|
+
]
|
|
31
36
|
|
|
32
37
|
["config-comments"]
|
|
33
38
|
deps = []
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@erclx/canon",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "5.
|
|
4
|
+
"version": "5.38.0",
|
|
5
5
|
"description": "Runs your agent's work and keeps its code good, on one setup for every repo.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"bin": {
|
|
@@ -24,9 +24,10 @@
|
|
|
24
24
|
"access": "public"
|
|
25
25
|
},
|
|
26
26
|
"scripts": {
|
|
27
|
-
"format": "prettier --write --log-level warn
|
|
28
|
-
"check:format": "prettier --check --log-level warn
|
|
27
|
+
"format": "oxfmt . && prettier --write --log-level warn 'web/**/*.astro' && shfmt --write --indent 2 scripts/ tooling/ claude/",
|
|
28
|
+
"check:format": "oxfmt --check . && prettier --check --log-level warn 'web/**/*.astro' && shfmt --diff --indent 2 scripts/ tooling/ claude/",
|
|
29
29
|
"check:shell": "find scripts tooling claude .claude/hooks -name '*.sh' -exec shellcheck --severity=warning {} + && find .husky -maxdepth 1 -type f -exec shellcheck --severity=warning --shell=sh {} +",
|
|
30
|
+
"check:lint": "oxlint --disable-nested-config --max-warnings 0",
|
|
30
31
|
"check:types": "tsc --noEmit",
|
|
31
32
|
"check:install": "./scripts/core/install-check.sh",
|
|
32
33
|
"test": "bun --bun vitest run",
|
|
@@ -72,6 +73,8 @@
|
|
|
72
73
|
"happy-dom": "^20.14.5",
|
|
73
74
|
"husky": "^9.1.7",
|
|
74
75
|
"micromatch": "^4.0.8",
|
|
76
|
+
"oxfmt": "0.72.0",
|
|
77
|
+
"oxlint": "1.87.0",
|
|
75
78
|
"prettier": "^3.8.1",
|
|
76
79
|
"prettier-plugin-astro": "^0.14.1",
|
|
77
80
|
"prettier-plugin-tailwindcss": "^0.7.2",
|
|
@@ -101,7 +101,7 @@ log_step "Assert scaffold"
|
|
|
101
101
|
# skills rather than installing into a target. The two
|
|
102
102
|
# rule paths stand for the `claude` and `canon` groups `base` takes whole.
|
|
103
103
|
for path in "CLAUDE.md" ".claude/wiki/index.md" ".claude" "canon/context/index.md" \
|
|
104
|
-
".
|
|
104
|
+
".oxfmtrc.json" ".lintstagedrc" ".husky/pre-commit" ".github/workflows/verify.yml" "scripts/verify.ts" \
|
|
105
105
|
".claude/rules/canon/claude/output.md" ".claude/rules/canon/canon/scratch.md"; do
|
|
106
106
|
if [ ! -e "$TARGET_DIR/$path" ]; then
|
|
107
107
|
log_error "Missing after canon init: $path"
|
package/src/commands/claude.ts
CHANGED
|
@@ -70,7 +70,6 @@ import {
|
|
|
70
70
|
writeSettings,
|
|
71
71
|
} from '@/claude/settings'
|
|
72
72
|
import { copyPreservingMode } from '@/sync/copy'
|
|
73
|
-
import { execScript } from '@/cli/exec'
|
|
74
73
|
import { checkoutMismatchWarning, PROJECT_ROOT } from '@/roots/project'
|
|
75
74
|
import { recordDir } from '@/roots/record'
|
|
76
75
|
import { SURFACE_ENTRIES, surfaceDir } from '@/roots/surface'
|
package/src/commands/pr.ts
CHANGED
|
@@ -1891,7 +1891,7 @@ async function mapPool<T, R>(
|
|
|
1891
1891
|
limit: number,
|
|
1892
1892
|
task: (input: T) => Promise<R>,
|
|
1893
1893
|
): Promise<R[]> {
|
|
1894
|
-
const results: R[] =
|
|
1894
|
+
const results: R[] = Array.from({ length: inputs.length })
|
|
1895
1895
|
let next = 0
|
|
1896
1896
|
const lane = async (): Promise<void> => {
|
|
1897
1897
|
while (next < inputs.length) {
|
package/src/commands/records.ts
CHANGED
package/src/docs/read.ts
CHANGED
|
@@ -149,29 +149,23 @@ export function walkEntries(root: string, dir: string): WalkedEntry[] {
|
|
|
149
149
|
const cwd = join(root, dir)
|
|
150
150
|
if (!existsSync(cwd)) return []
|
|
151
151
|
|
|
152
|
-
const files = markdownNames(cwd).map(
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}),
|
|
158
|
-
)
|
|
152
|
+
const files = markdownNames(cwd).map((name): WalkedEntry => ({
|
|
153
|
+
name,
|
|
154
|
+
kind: 'file',
|
|
155
|
+
rel: join(dir, `${name}.md`),
|
|
156
|
+
}))
|
|
159
157
|
const folders = catalogedFolders(cwd)
|
|
160
|
-
const split = folders.map(
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}),
|
|
166
|
-
)
|
|
158
|
+
const split = folders.map((folder): WalkedEntry => ({
|
|
159
|
+
name: folder,
|
|
160
|
+
kind: 'folder',
|
|
161
|
+
rel: join(dir, folder, `${INDEX_TOPIC}.md`),
|
|
162
|
+
}))
|
|
167
163
|
const leaves = folders.flatMap((folder) =>
|
|
168
|
-
markdownNames(join(cwd, folder)).map(
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}),
|
|
174
|
-
),
|
|
164
|
+
markdownNames(join(cwd, folder)).map((name): WalkedEntry => ({
|
|
165
|
+
name,
|
|
166
|
+
kind: 'leaf',
|
|
167
|
+
rel: join(dir, folder, `${name}.md`),
|
|
168
|
+
})),
|
|
175
169
|
)
|
|
176
170
|
|
|
177
171
|
return [...files, ...split, ...leaves]
|
package/src/gate/stages.ts
CHANGED
|
@@ -544,6 +544,20 @@ export const STAGES: readonly Stage[] = [
|
|
|
544
544
|
],
|
|
545
545
|
success: 'Shell check passed',
|
|
546
546
|
},
|
|
547
|
+
{
|
|
548
|
+
id: 'lint',
|
|
549
|
+
label: 'Lint',
|
|
550
|
+
// Unscoped, since a full run costs less than spawning the process and a
|
|
551
|
+
// scope is one more pattern to fall behind the extensions oxlint reads.
|
|
552
|
+
checks: [
|
|
553
|
+
{
|
|
554
|
+
kind: 'command',
|
|
555
|
+
argv: ['bun', 'run', 'check:lint'],
|
|
556
|
+
failure: 'Lint failed',
|
|
557
|
+
},
|
|
558
|
+
],
|
|
559
|
+
success: 'Lint passed',
|
|
560
|
+
},
|
|
547
561
|
{
|
|
548
562
|
id: 'types',
|
|
549
563
|
label: 'Types',
|
|
@@ -86,7 +86,7 @@ const BLOCKQUOTE = /^\s*>/
|
|
|
86
86
|
* follows, and the price is that a span opening a fragment mid-paragraph reads
|
|
87
87
|
* as a sentence start.
|
|
88
88
|
*/
|
|
89
|
-
const SENTENCE_END = /[.!?]["'’”)\]]*(?=\s+(?:["'“(
|
|
89
|
+
const SENTENCE_END = /[.!?]["'’”)\]]*(?=\s+(?:["'“([]*[A-Z]|`)|\s*$)/g
|
|
90
90
|
|
|
91
91
|
/**
|
|
92
92
|
* Checkpoints the structural measures run against.
|
|
@@ -674,7 +674,7 @@ async function pool<T, R>(
|
|
|
674
674
|
size: number,
|
|
675
675
|
work: (item: T, index: number) => Promise<R>,
|
|
676
676
|
): Promise<R[]> {
|
|
677
|
-
const results: R[] =
|
|
677
|
+
const results: R[] = Array.from({ length: items.length })
|
|
678
678
|
let next = 0
|
|
679
679
|
|
|
680
680
|
async function lane(): Promise<void> {
|
|
@@ -827,14 +827,12 @@ export async function runEquivalence(
|
|
|
827
827
|
const records: ArmRecord[] = [
|
|
828
828
|
...offline,
|
|
829
829
|
...anchored,
|
|
830
|
-
...skipped.map(
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
}),
|
|
837
|
-
),
|
|
830
|
+
...skipped.map((arm): ArmRecord => ({
|
|
831
|
+
arm: armKey(arm),
|
|
832
|
+
state: 'skipped-anchor',
|
|
833
|
+
differs: [],
|
|
834
|
+
masked: [],
|
|
835
|
+
})),
|
|
838
836
|
].sort((x, y) => x.arm.localeCompare(y.arm))
|
|
839
837
|
|
|
840
838
|
const counts: Record<ArmState, number> = {
|