@erclx/canon 4.58.0 → 4.60.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/claude-groundwork/REQUIREMENT.md +1 -0
- package/claude/skills/claude-groundwork/SKILL.md +1 -1
- package/claude/skills/claude-memory-review/SKILL.md +2 -2
- package/claude/skills/claude-pr-review/SKILL.md +1 -1
- package/claude/skills/claude-tasks/REQUIREMENT.md +4 -0
- package/claude/skills/claude-tasks/SKILL.md +8 -2
- package/claude/skills/setup-init/REQUIREMENT.md +5 -4
- package/claude/skills/setup-init/SKILL.md +8 -3
- package/docs/agents/tasks.md +18 -3
- package/docs/target-projects.md +2 -1
- package/governance/rules/core/055-scratch.md +1 -0
- package/governance/rules/core/085-worktrees.md +1 -0
- package/package.json +1 -1
- package/src/commands/labels.ts +7 -9
- package/src/commands/tasks.ts +17 -0
- package/src/tasks/validate.ts +46 -19
- package/standards/index.md +1 -0
- package/standards/wiki.md +65 -0
- package/tooling/nextjs/manifest.toml +3 -0
- package/tooling/nextjs/reference.md +6 -0
|
@@ -22,6 +22,7 @@ A track that closes with several separable findings writes only one task, and th
|
|
|
22
22
|
- Carry a lean and the finding that would overturn it on every open question, or admit that a measurement is missing
|
|
23
23
|
- Confine writes to the track folder, with the close-time task file, the experiment fixture, and the intake routing below as the only exceptions
|
|
24
24
|
- Route a closing-track finding the required task does not cover through `claude-intake`, rather than leaving it to be asked about. The route runs in the same session, so it is a write outside the folder rather than a handoff to a later one.
|
|
25
|
+
- Place the closing task's row through `claude-tasks` Step 4 rather than writing `priority.md` or `backlog.md` directly
|
|
25
26
|
- Link every claim about a source outside the project, and list an unread source as a lead rather than citing it
|
|
26
27
|
- Put a fixture a headless run is pointed at outside the repository
|
|
27
28
|
- Write the next-session file self-contained, since the folder is unbacked and dies with the machine
|
|
@@ -92,7 +92,7 @@ The standard sets the open question format and requires it inside a topic file a
|
|
|
92
92
|
1. Write `06-decision.md`. It states the problem once, names the goal, lists what to do, and lists what was considered and dropped.
|
|
93
93
|
2. Write `07-next-session.md` self-contained, so it survives a compaction that loses the conversation.
|
|
94
94
|
3. Update the file map in `README.md`.
|
|
95
|
-
4. Write one task file in `.canon/tasks/` recording what the track concluded, even when the conclusion is to do nothing. Follow `${CLAUDE_SKILL_DIR}/../../standards/tasks.md` for the filename and frontmatter. Aside from an experiment fixture, this and the routing in Step 5 are the only ways close mode reaches outside the folder.
|
|
95
|
+
4. Write one task file in `.canon/tasks/` recording what the track concluded, even when the conclusion is to do nothing. Follow `${CLAUDE_SKILL_DIR}/../../standards/tasks.md` for the filename and frontmatter. Place the row through `claude-tasks` Step 4, which checks the roster for a live orchestrator before writing `priority.md` or `backlog.md` directly. Aside from an experiment fixture, this and the routing in Step 5 are the only ways close mode reaches outside the folder.
|
|
96
96
|
5. When the task written in Step 4 does not cover every finding the track surfaced, route what it leaves out through `claude-intake`. Skip this step when it does.
|
|
97
97
|
6. Report uncited external claims. Closing already reads every file in the folder, so list any statement about a source outside the project that carries neither a link nor a lead entry. Report and do not block, because judging whether a sentence makes an external claim is the call a checker gets wrong.
|
|
98
98
|
|
|
@@ -65,7 +65,7 @@ For each in-scope entry (see Scope), pick one action:
|
|
|
65
65
|
- **Promote to `CLAUDE.md`**: the rule is cross-domain behavior or a design principle applied across the whole project.
|
|
66
66
|
- **Promote to a skill body**: the rule fires only when editing a specific path-scoped domain. Name the target skill.
|
|
67
67
|
- **Promote to a standards file**: the rule is an authoring reference that belongs in the project's own standards folder as `<domain>.md`.
|
|
68
|
-
- **Promote to a context entry**: the entry states a fact about a domain carrying an entry in `.claude/context/index.md`. Append it to `.canon/tmp/memory-routing/<slug>.md` in the format `claude-memory-capture` writes, and tell the user to run `/claude-docs` from a branch. Do not edit the context entry here.
|
|
68
|
+
- **Promote to a context entry**: the entry states a fact about a domain carrying an entry in `.claude/context/index.md`. Append it to `.canon/tmp/memory-routing/<slug>.md` at the main worktree root, in the format `claude-memory-capture` writes, and tell the user to run `/claude-docs` from a branch. Do not edit the context entry here.
|
|
69
69
|
- **Hand off to governance**: the rule is coding-standards class (typescript, testing, naming, error-handling, performance, logging, concurrency, planning). Do not author the rule file inline. Never edit the synced `.claude/rules/` copies of toolkit rules, because `canon gov sync` overwrites them. Stop at handoff.
|
|
70
70
|
- In the toolkit repo, point the user at `internal-governance` and `${CLAUDE_SKILL_DIR}/../../standards/rule.md`, which own the source-of-truth rules under `governance/rules/`.
|
|
71
71
|
- In a target project, point the user at the `create-rule` skill, which scaffolds a project-local rule under `.claude/rules/`.
|
|
@@ -150,7 +150,7 @@ Free-form text after the verb is a reason. Capture it in the receipt but do not
|
|
|
150
150
|
Action by action type:
|
|
151
151
|
|
|
152
152
|
- **Promote**: use `Edit` to insert the rewritten rule into the target surface, then archive the memory file.
|
|
153
|
-
- **Promote to a context entry**: append the fact to `.canon/tmp/memory-routing/<slug>.md
|
|
153
|
+
- **Promote to a context entry**: append the fact to `.canon/tmp/memory-routing/<slug>.md` at the main worktree root, then archive the memory file. `claude-docs` folds it in on its next run from a branch, which is what keeps one skill writing context entries.
|
|
154
154
|
- **Hand off**: do not edit governance. Archive the memory file only if the user confirmed the handoff explicitly. Otherwise leave it in place.
|
|
155
155
|
- **Retire**: archive the memory file.
|
|
156
156
|
|
|
@@ -141,7 +141,7 @@ Use severity: `critical` (blocks merge), `should-fix` (fix before merge), `minor
|
|
|
141
141
|
|
|
142
142
|
## Step 4: post to the PR
|
|
143
143
|
|
|
144
|
-
Write the comment to `.canon/tmp/pr-review/body-<number>-<short-sha>.md
|
|
144
|
+
Write the comment to `.canon/tmp/pr-review/body-<number>-<short-sha>.md` at the main worktree root, not the current worktree, which the rest of this step calls `<body-file>`. Resolve that root the way `claude-worktree` does, and send the write as a plain single `Bash` command carrying a heredoc from a linked worktree, since `Edit` and `Write` refuse a main-root path there. The PR number stops two sessions reviewing different pull requests from overwriting each other between the write and the post, and the head commit stops a second pass overwriting the first one's body, leaving the folder a record of which commit each review covered.
|
|
145
145
|
|
|
146
146
|
Derive both segments from Step 1. Never pick a suffix by hand, and never reuse a name the folder already holds.
|
|
147
147
|
|
|
@@ -11,12 +11,15 @@ Without this skill, a task file gets a filename and frontmatter invented on the
|
|
|
11
11
|
|
|
12
12
|
Archiving fails in two ways that both lose work. Moving the file, editing the ordering file, and regenerating the index as three separate acts drifts from the one command the merge hook calls, so the attended and unattended paths stop agreeing. And an all-`[x]` task gets archived while its pull request is still open, because marking outcomes happens on the branch as the first step of shipping. The board is gitignored, so nothing restores a task archived early.
|
|
13
13
|
|
|
14
|
+
Placing a row without checking for another writer collides the same way. Two sessions filing work at once can claim one phase label twice or land two rows beside each other unread, since neither reads the board before writing it.
|
|
15
|
+
|
|
14
16
|
## Must
|
|
15
17
|
|
|
16
18
|
- Resolve the board at the main worktree root, since a linked worktree writing to `pwd` creates a second board nothing reads
|
|
17
19
|
- Read the tasks standard before writing, rather than working the filename and frontmatter from memory
|
|
18
20
|
- Require an origin at creation, because that is the only moment the invariant is enforceable
|
|
19
21
|
- Propose the phase label from what is already on the board and pad it to two digits
|
|
22
|
+
- Check the roster for a live orchestrator before writing a row, and hand off rather than write when one is found
|
|
20
23
|
- Confirm the work reached the default branch before archiving
|
|
21
24
|
- Run the archive command and route on the reason it refuses, since each reason has one resolution
|
|
22
25
|
- Report an origin that carries no task, as a list rather than a prompt
|
|
@@ -28,6 +31,7 @@ Archiving fails in two ways that both lose work. Moving the file, editing the or
|
|
|
28
31
|
- Write a pull request line at creation, when any number is a guess at someone else's work
|
|
29
32
|
- Archive around a refusal
|
|
30
33
|
- Split the legacy single-file archive into per-task files nobody wrote
|
|
34
|
+
- Fall through to a direct write when no orchestrator is found and this session's own name starts with `worker-` or `planner-`, since those role bodies ban the write with no exception
|
|
31
35
|
|
|
32
36
|
## Guards
|
|
33
37
|
|
|
@@ -47,9 +47,15 @@ Write it immediately. Claude Code's tool permission dialog is the confirmation g
|
|
|
47
47
|
|
|
48
48
|
A task file with no row is a dropped task, so name the surface it lands on in the same pass that creates it. A task that would plausibly be planned within the next few waves takes a row under `## Needs a plan` in `.canon/tasks/priority.md`, positioned by where it sits against the rows already there, with the reason for that position in its `Waiting on` cell. Anything else takes a line in `.canon/tasks/backlog.md`, which is unordered and where in the file it goes means nothing.
|
|
49
49
|
|
|
50
|
-
The test and both file shapes are in `${CLAUDE_SKILL_DIR}/../../standards/tasks.md`.
|
|
50
|
+
The test and both file shapes are in `${CLAUDE_SKILL_DIR}/../../standards/tasks.md`.
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
Check the roster for a live orchestrator before writing either file. Read `canon sessions list --self --json` for this session's own `sessionId` and `name`, then read `canon sessions list --json` and match a row whose `repository` matches this session's own, whose `sessionId` differs from it, and whose `name` starts with `orchestrator-`. That is the convention every hand-launched controller on this machine currently uses, and it excludes every `worker-` and `planner-` session cleanly. Treat a refusal from either call the same as a roster read that failed.
|
|
53
|
+
|
|
54
|
+
- **Found.** Do not write `priority.md` or `backlog.md`. Report the surface the row would take and the reason worked out above, then hand off by messaging that session with the same information so it places the row itself rather than two sessions writing the board at once.
|
|
55
|
+
- **Not found, and this session's own name (already read above) starts with `worker-` or `planner-`.** Do not write either file. Those two role bodies ban a board write with no exception, and an orchestrator absent from the roster is not consent to break it. Report the row and the surface it would take, so the operator or a later orchestrator places it.
|
|
56
|
+
- **Not found otherwise, or the roster read fails.** Write the row directly, as today. A failed read is indistinguishable from a solo project holding no orchestrator, and stopping would strand the row on the one path this section otherwise keeps unconditional. From a linked worktree the file-editing tools refuse the main root, so the row lands through the same `Bash` route the file itself took.
|
|
57
|
+
|
|
58
|
+
Say which branch fired and why in the report. The call is a judgment restated on every sweep rather than a property of the task, and a placement with no stated reason is one the next sweep re-derives from nothing.
|
|
53
59
|
|
|
54
60
|
### Step 5: report unlinked origins
|
|
55
61
|
|
|
@@ -9,7 +9,7 @@ description: Scope boundary for the one-shot onboarding chain and the setup step
|
|
|
9
9
|
|
|
10
10
|
Without this skill, a session installs a stack whose name it recalled rather than read, hand-writes configs the tooling sync already ships as golden files, and re-runs the chain over a project that owns its configs already.
|
|
11
11
|
|
|
12
|
-
Two failures are the expensive ones because both return success. A monorepo run drops husky into every subtree, git honors one `core.hooksPath`, and the extra hook directories go quiet with nothing reporting it. A caller reads the chain as onboarding complete when
|
|
12
|
+
Two failures are the expensive ones because both return success. A monorepo run drops husky into every subtree, git honors one `core.hooksPath`, and the extra hook directories go quiet with nothing reporting it. A caller reads the chain as onboarding complete when plugin provisioning never runs, since no step states that it sits outside the chain.
|
|
13
13
|
|
|
14
14
|
Two more start at the resolve step. A project whose language the toolkit carries no stack for resolves to `base`, and the preview reports the resolved name without marking it as a fallback, so `base` development dependencies, scripts, and hooks land on a project that will not use them and nobody had the moment to decline.
|
|
15
15
|
|
|
@@ -20,7 +20,7 @@ The other is the refusal that ends rather than routes. An existing project, an i
|
|
|
20
20
|
- Resolve every domain argument against a catalog read at run time, so a stack, rule, snippet, or tooling name the catalog does not carry cannot reach the command line
|
|
21
21
|
- Preview the resolved chain before the first command runs, naming the evidence file behind each detected technology
|
|
22
22
|
- Pass `--skip base` on every subtree past the first in a monorepo, so the shared hook layer lands once
|
|
23
|
-
- State which onboarding steps the chain does not run, so a caller knows what is left rather than inferring completeness from a clean report
|
|
23
|
+
- State which onboarding steps the chain does not run and why, so a caller knows what is left and where the boundary sits rather than inferring completeness from a clean report
|
|
24
24
|
- Mark a stack reached by fallback as a fallback in the preview, and name what `base` carries, so the write can be declined at the one point it is still declinable
|
|
25
25
|
- Name a destination for every state the chain does not serve, in this body rather than only in a reference doc the person would have to already know to open, and say for each whether the chain stops or runs on a default
|
|
26
26
|
|
|
@@ -37,9 +37,10 @@ The other is the refusal that ends rather than routes. An existing project, an i
|
|
|
37
37
|
## Out of scope
|
|
38
38
|
|
|
39
39
|
- Installing governance rules alone: `setup-gov`. This skill installs governance beside tooling and the seeds.
|
|
40
|
-
- Bootstrapping the `index.md` system: `setup-indexes`. Neither `canon init` nor this chain performs it.
|
|
41
40
|
- Provisioning Claude Code plugins: `setup-plugins`. Those install once per machine rather than into a project, so no project-scoped chain can carry them.
|
|
42
|
-
- Running the verification scripts, which `setup-verify` owns and this chain invokes as
|
|
41
|
+
- Running the verification scripts, which `setup-verify` owns and this chain invokes as step 4
|
|
42
|
+
- Publishing repository metadata: `repo-metadata`. It reaches a remote, which is past the project edge this chain stops at.
|
|
43
|
+
- Making the first commit: `git-commit`. It reaches the project's history, which is past the project edge this chain stops at.
|
|
43
44
|
- Re-installing into a project that already has the files, which the per-domain `canon <domain> install` and `canon sync` commands own
|
|
44
45
|
- Deciding which of those per-domain commands an existing project needs: `canon-operator`. That skill reads the target before naming one, which this chain never does, so it is the destination rather than a guess made here.
|
|
45
46
|
- Seeding the `.claude/` folder without the tooling chain, which `canon claude init` does on its own and `setup-indexes` finishes
|
|
@@ -10,7 +10,7 @@ Orchestrates the onboarding chain. Detects project type, resolves per-domain arg
|
|
|
10
10
|
## Scope
|
|
11
11
|
|
|
12
12
|
- This skill and `canon init` run once on a fresh scaffold, never on an existing project. They do not guard against clobbering existing configs. When tempted to add guards, mode switches, or an existing-project branch, stop. Extend the per-domain `canon <domain> install` or `canon sync` paths instead.
|
|
13
|
-
- The chain
|
|
13
|
+
- The chain folds `setup-indexes` in as its own final step, once the project-scoped work is written. It still does not provision Claude Code plugins: `setup-plugins` owns that, installing once per machine rather than into a project, so no project-scoped chain can carry it. Name it in the report so a clean result does not read as onboarding complete.
|
|
14
14
|
|
|
15
15
|
## Declined states
|
|
16
16
|
|
|
@@ -75,7 +75,7 @@ A resolved name and a fallback read alike once written, so mark the fallback her
|
|
|
75
75
|
|
|
76
76
|
## Execute
|
|
77
77
|
|
|
78
|
-
Run the chain in order, starting immediately after the preview. Each step's permission dialog is the confirmation gate. Do not pause for additional confirmation. Run from the target project's current directory.
|
|
78
|
+
Run the chain in order, starting immediately after the preview. Each step's permission dialog is the confirmation gate, except step 5, which hands off to a skill that confirms its own folder list with the operator. Do not pause for additional confirmation elsewhere. Run from the target project's current directory.
|
|
79
79
|
|
|
80
80
|
Step 1: `canon init` installs base tooling, claude seeds, governance rules, and wiki.
|
|
81
81
|
|
|
@@ -115,6 +115,8 @@ Do not generate ESLint, Vitest, or Playwright configs. They ship as golden files
|
|
|
115
115
|
|
|
116
116
|
Step 4: invoke `setup-verify`. Runs the `package.json` scripts and reports pass/fail.
|
|
117
117
|
|
|
118
|
+
Step 5: hand off to `setup-indexes` to bootstrap the `index.md` system over the project's own documentation folders. The skill confirms candidate folders with the operator rather than running unattended, which is the one step in this chain that pauses for a conversation. A fresh scaffold usually has no markdown-heavy folder yet, so this step frequently hands off with nothing for `setup-indexes` to present, which is that skill's own outcome to define rather than a claim this chain makes for it.
|
|
119
|
+
|
|
118
120
|
## Report
|
|
119
121
|
|
|
120
122
|
After the chain, report:
|
|
@@ -123,6 +125,9 @@ After the chain, report:
|
|
|
123
125
|
- Tooling stack synced (or skipped). Name the layers pulled via the extends chain.
|
|
124
126
|
- Any post-sync fixups applied (ESLint pin, filename renames)
|
|
125
127
|
- `setup-verify` outcome
|
|
128
|
+
- `setup-indexes` outcome (folders bootstrapped, or none found)
|
|
126
129
|
- Any domains or scripts that failed
|
|
127
130
|
- Any detection gaps surfaced during resolve
|
|
128
|
-
- Onboarding steps left to the caller: `setup-
|
|
131
|
+
- Onboarding steps left to the caller: `setup-plugins` for Claude Code plugins
|
|
132
|
+
|
|
133
|
+
The chain stops at the project edge. `repo-metadata` and `git-commit` also ship, reaching a remote and the project's history respectively, and neither runs here.
|
package/docs/agents/tasks.md
CHANGED
|
@@ -214,7 +214,7 @@ Seven checks run. Plan and Collisions reach one half each of the `## Run now` te
|
|
|
214
214
|
| ---------- | ----------------------------------------------------------------------------------------------------------------------- |
|
|
215
215
|
| Shape | A row whose cell count disagrees with its table's header, or one stranded behind a table a blank line already closed |
|
|
216
216
|
| Plan | A `## Run now` row whose Plan column carries no link, resolves to no file, or disagrees with the task's own line |
|
|
217
|
-
| Mapping | A row or backlog line naming no task file
|
|
217
|
+
| Mapping | A row or backlog line naming no task file |
|
|
218
218
|
| Grouping | A task carrying a row in more than one readiness group, or on both surfaces |
|
|
219
219
|
| Ordering | A `## Needs a plan` row whose stated position disagrees with where it actually sits, or which states no position at all |
|
|
220
220
|
| Collisions | Two `## Run now` rows whose Touches columns name a path in common |
|
|
@@ -224,9 +224,9 @@ Shape runs before any other check reads a row, since a row failing it carries no
|
|
|
224
224
|
|
|
225
225
|
The Plan check reads the row and the task file both, because the two are written by different hands and only the task's own `Plan:` line reaches the archive. A row carrying a plan whose task states none is `plan-uncited`, and a pair naming two different plans is `plan-mismatched`. Both sides resolve against the board and against the project root before they compare, so a row writing `../plans/x.md` and a task writing `.canon/plans/x.md` name one file rather than two.
|
|
226
226
|
|
|
227
|
-
Mapping spans two surfaces, because a task sits on `priority.md` when it would plausibly be planned soon and on `backlog.md` otherwise. A
|
|
227
|
+
Mapping spans two surfaces, because a task sits on `priority.md` when it would plausibly be planned soon and on `backlog.md` otherwise. A row or a backlog line naming no task file is `task-unresolved`, and a file both surfaces name is `row-duplicated` for the reason a task in two groups is: it claims two things about itself and only one can hold. A task file neither surface names is `unplaced` rather than a finding, since that is the normal state between a session filing it and a live orchestrator placing it on the board. One check across both is what lets a task move between them without the move reading as a dropped file.
|
|
228
228
|
|
|
229
|
-
A backlog line is a bullet carrying a link to a sibling task, since the backlog is a flat unordered list rather than a table. A bullet holding prose is skipped rather than reported, which keeps the file's own intro out of the findings, and the task that bullet meant to name
|
|
229
|
+
A backlog line is a bullet carrying a link to a sibling task, since the backlog is a flat unordered list rather than a table. A bullet holding prose is skipped rather than reported, which keeps the file's own intro out of the findings, and the task that bullet meant to name still lands in the `unplaced` array below rather than the findings. A project carrying no `backlog.md` reads as an empty backlog rather than a refusal, which leaves the one-to-one mapping this check ran before the second surface existed.
|
|
230
230
|
|
|
231
231
|
Ordering reads a `## Needs a plan` row's `Waiting on` cell for the position it claims, and reports two failures off one walk. A row stating an ordinal is checked against where it actually sits, which is `row-misordered`. A row claiming no position in either form it may take is `row-unranked`, since its cell argues the task matters and ranks it against nothing, which leaves the order recording when each row was filed.
|
|
232
232
|
|
|
@@ -282,6 +282,21 @@ That claim collides with every row a later session writes under the folder, and
|
|
|
282
282
|
|
|
283
283
|
The scan reaches `## Run now` and stops, where the collision check stops. A cell in another group describes work nobody has planned, written as a sentence and rewritten once a plan exists, so a claim read off one reports on prose rather than on a file set. A parked folder claim surfaces when its row is promoted, which is when the cell becomes something a dispatcher can act on.
|
|
284
284
|
|
|
285
|
+
A task file neither surface names lands in a fourth array, on the same reasoning:
|
|
286
|
+
|
|
287
|
+
```json
|
|
288
|
+
{
|
|
289
|
+
"unplaced": [
|
|
290
|
+
{
|
|
291
|
+
"subject": "v50.6-a-standard-no-skill-reads", // canon-allow-reference: shows the subject field's real vXX.Y-slug shape, not a citation of a real task
|
|
292
|
+
"message": "is a task file with no row on the board and no line on the backlog."
|
|
293
|
+
}
|
|
294
|
+
]
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
Under the roster-checked hand-off `claude-tasks` and `claude-groundwork` state, filing a task and placing its row are two acts a different session each may take, so a task caught between the two is ordinary rather than a finding and this array moves no exit code. It still reports, since it is the only local detector for a row a hand-edit dropped, a handoff message that never arrived, or an orchestrator that ended before placing it.
|
|
299
|
+
|
|
285
300
|
Exit codes: `0` every check passed, `1` refused, `2` at least one finding. The `reason` field carries which gate refused: `no-board`, `no-ordering`, or `no-groups`. A board grouping under headings of its own trips `no-groups` rather than being read against columns it never declared.
|
|
286
301
|
|
|
287
302
|
Columns are read from each table's own header rather than by position, so a project whose board differs from this one is reported for what it lacks. The `index`, `priority`, and `backlog` siblings are skipped, along with every pre-compaction handoff, which takes one file per session under a `session-` prefix. None of them is a task, and a handoff counted as one would be reported as a task carrying no row on every session that wrote one.
|
package/docs/target-projects.md
CHANGED
|
@@ -59,8 +59,9 @@ The chain is:
|
|
|
59
59
|
- `canon tooling sync <stack> --write` adds stack-specific deps, scripts, and gitignore entries
|
|
60
60
|
- The agent reads `canon tooling reference <stack>` (plus parents) as its audit context, follows it to generate eslint, vitest, playwright configs and the stack's setup script, and extends `.claude/context/ci.md` and `.claude/context/development.md` per the reference's extend sections <!-- audit-ignore-citations: .claude/context/development.md -->
|
|
61
61
|
- `setup-verify` runs the installed `package.json` scripts (lint, typecheck, check, test, build) and reports pass or fail
|
|
62
|
+
- `setup-indexes` bootstraps the `index.md` system over the project's own documentation folders, confirming candidate folders with the operator rather than running unattended
|
|
62
63
|
|
|
63
|
-
The chain serves a fresh scaffold and names a destination for the three states it does not. An existing project goes to `canon:canon-operator`, which reads what the project already carries before naming a per-domain command. An install wanting the Claude layer without the tooling chain runs `canon claude init` for the seed docs and then `canon:setup-indexes` for the index system. A language the toolkit ships no stack for is the one of the three the chain still runs for, on `base`, with the fallback marked in the preview so it can be declined there.
|
|
64
|
+
The chain stops at the project edge. `repo-metadata` and `git-commit` also ship, reaching a remote and the project's history respectively, and neither runs as part of it. The chain serves a fresh scaffold and names a destination for the three states it does not. An existing project goes to `canon:canon-operator`, which reads what the project already carries before naming a per-domain command. An install wanting the Claude layer without the tooling chain runs `canon claude init` for the seed docs and then `canon:setup-indexes` for the index system. A language the toolkit ships no stack for is the one of the three the chain still runs for, on `base`, with the fallback marked in the preview so it can be declined there.
|
|
64
65
|
|
|
65
66
|
Keep the `## Scripts` table in `.claude/context/development.md` current as scripts are added. Base tooling seeds that entry with the commands it installs, and each stack reference extends the table. `project-commands` reads it to start the app or run a check on request, so a command missing from the table cannot be run that way. A project whose entry outgrew one file and split into `.claude/context/development/` keeps the table in `overview.md`, which is where the skill looks next. <!-- audit-ignore-citations: .claude/context/development.md -->
|
|
66
67
|
|
|
@@ -8,3 +8,4 @@ description: Write temporary files to the scratch folder structure
|
|
|
8
8
|
|
|
9
9
|
- Write temporary files to `.canon/tmp/<slug>/<file>.md` in the project root, a nested `<slug>/` folder with a kebab-slug tied to the topic, not a flat `<slug>-<file>.md`. The scratch-guard hook enforces the location.
|
|
10
10
|
- Write to `.claude/.tmp/<slug>/` instead in a project that carries no `.canon/` root, which is one the record move has not reached. The hook accepts either, but the ignore file is the source: `.gitignore` and its manifest counterpart are what keep the fallback spelling out of `git status`, and the hook and this rule both defer to it. `canon migrate records` is what moves a project from this second spelling to the first. <!-- canon-keep-record-root -->
|
|
11
|
+
- "The project root" above means the current worktree by default. `085-worktrees.md` states the same mixed default for `.canon/tmp/` and defers to each skill's own body for which write resolves at the main root instead.
|
|
@@ -11,6 +11,7 @@ description: Route tracked-file writes and shared session scratch correctly from
|
|
|
11
11
|
## Shared session scratch
|
|
12
12
|
|
|
13
13
|
- Shared session scratch (`.canon/plans/`, `.canon/review/`, `.canon/memory/`, `.canon/tasks/`) lives at the main worktree root, not inside a linked worktree. From a linked worktree, resolve these paths against the main root via `git worktree list --porcelain | grep -m 1 '^worktree ' | cut -d' ' -f2-`. Fall back to `pwd` if not a git repo.
|
|
14
|
+
- `.canon/tmp/` is mixed rather than main-root by default: it stays worktree-local per `055-scratch.md`, and a skill that resolves a write there at the main root instead states so in its own body.
|
|
14
15
|
- From a linked worktree, every `Edit` or `Write` to a tracked file (source, docs) must use a path starting with `pwd`.
|
|
15
16
|
- From a linked worktree, `Edit` and `Write` are refused for every main-root path, session scratch included. The refusal names session isolation and points at the worktree copy, which is a second gitignored file no later session reads, so never take that redirect.
|
|
16
17
|
- `Read` resolves against the main root normally from a linked worktree. A main-root write reaches it only through `Bash`, as one plain command rather than a compound one, which is refused for complexity.
|
package/package.json
CHANGED
package/src/commands/labels.ts
CHANGED
|
@@ -155,9 +155,9 @@ export function register(program: Command): void {
|
|
|
155
155
|
"It also grades the title alone against standards/pr.md's ## Title",
|
|
156
156
|
'section: the `<type>(<scope>): <subject>` structure, lowercase casing',
|
|
157
157
|
'for the type, the scope, and the first subject word, and a 72-',
|
|
158
|
-
'character length cap. A
|
|
159
|
-
'
|
|
160
|
-
'string.',
|
|
158
|
+
'character length cap. A scan given no title, whether from a review',
|
|
159
|
+
'or from a bare --body or --body-file invocation, skips this check',
|
|
160
|
+
'rather than grading an empty string.',
|
|
161
161
|
'',
|
|
162
162
|
'Exit codes:',
|
|
163
163
|
' 0 none of the five found',
|
|
@@ -287,12 +287,10 @@ async function runScan(opts: ScanOptions): Promise<number> {
|
|
|
287
287
|
|
|
288
288
|
const result = scanPhaseLabels(resolved)
|
|
289
289
|
const spelling = await scanTitleSpelling(resolved.title, process.cwd())
|
|
290
|
-
// A
|
|
291
|
-
//
|
|
290
|
+
// A scan given no title, whatever produced that absence, has nothing to
|
|
291
|
+
// grade and would otherwise fail as `structure` for the wrong reason.
|
|
292
292
|
const titleFormat =
|
|
293
|
-
resolved.
|
|
294
|
-
? checkTitleFormat(resolved.title)
|
|
295
|
-
: undefined
|
|
293
|
+
resolved.title !== '' ? checkTitleFormat(resolved.title) : undefined
|
|
296
294
|
|
|
297
295
|
logStep(resolved.source === 'review' ? 'Review comment' : 'Pull request')
|
|
298
296
|
logInfo(
|
|
@@ -373,7 +371,7 @@ async function runScan(opts: ScanOptions): Promise<number> {
|
|
|
373
371
|
: 'Title format issue found',
|
|
374
372
|
)
|
|
375
373
|
if (titleFormat === undefined) {
|
|
376
|
-
logInfo('
|
|
374
|
+
logInfo('the scan carries no title, so there is no format to grade')
|
|
377
375
|
} else if (titleFormat.conforms) {
|
|
378
376
|
logInfo(
|
|
379
377
|
'the title matches <type>(<scope>): <subject> and its casing and length rules',
|
package/src/commands/tasks.ts
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
import {
|
|
23
23
|
type Finding,
|
|
24
24
|
type FolderClaim,
|
|
25
|
+
type Unplaced,
|
|
25
26
|
type Untested,
|
|
26
27
|
type ValidateOutcome,
|
|
27
28
|
validateBoard,
|
|
@@ -887,6 +888,17 @@ function reportValidation(
|
|
|
887
888
|
} else {
|
|
888
889
|
for (const claim of outcome.claims) logWarn(describeClaim(claim))
|
|
889
890
|
}
|
|
891
|
+
|
|
892
|
+
// Unplaced is the normal state between a session filing a task and an
|
|
893
|
+
// orchestrator placing its row, so it moves no exit code. It still reports,
|
|
894
|
+
// since it is the only local detector for a row a hand-edit dropped or a
|
|
895
|
+
// handoff that never arrived.
|
|
896
|
+
logStep('Unplaced')
|
|
897
|
+
if (outcome.unplaced.length === 0) {
|
|
898
|
+
logInfo('every task file sits on the board or the backlog')
|
|
899
|
+
} else {
|
|
900
|
+
for (const task of outcome.unplaced) logWarn(describeUnplaced(task))
|
|
901
|
+
}
|
|
890
902
|
outro()
|
|
891
903
|
}
|
|
892
904
|
|
|
@@ -901,6 +913,7 @@ function reportValidation(
|
|
|
901
913
|
findings: outcome.findings,
|
|
902
914
|
untested: outcome.untested,
|
|
903
915
|
claims: outcome.claims,
|
|
916
|
+
unplaced: outcome.unplaced,
|
|
904
917
|
})}\n`,
|
|
905
918
|
)
|
|
906
919
|
}
|
|
@@ -921,6 +934,10 @@ function describeClaim(claim: FolderClaim): string {
|
|
|
921
934
|
return `${claim.group}: ${claim.subject} ${claim.message}`
|
|
922
935
|
}
|
|
923
936
|
|
|
937
|
+
function describeUnplaced(task: Unplaced): string {
|
|
938
|
+
return `${task.subject} ${task.message}`
|
|
939
|
+
}
|
|
940
|
+
|
|
924
941
|
async function runArchive(
|
|
925
942
|
task: string | undefined,
|
|
926
943
|
opts: ArchiveCommandOptions,
|
package/src/tasks/validate.ts
CHANGED
|
@@ -38,7 +38,6 @@ export const FINDING_KINDS = [
|
|
|
38
38
|
'plan-uncited',
|
|
39
39
|
'plan-mismatched',
|
|
40
40
|
'task-unresolved',
|
|
41
|
-
'row-missing',
|
|
42
41
|
'row-duplicated',
|
|
43
42
|
'row-misshapen',
|
|
44
43
|
'row-untabled',
|
|
@@ -83,6 +82,18 @@ export interface FolderClaim {
|
|
|
83
82
|
readonly message: string
|
|
84
83
|
}
|
|
85
84
|
|
|
85
|
+
/**
|
|
86
|
+
* A task file neither surface names. That is the normal state between a
|
|
87
|
+
* session filing it and a live orchestrator placing it, so it reports beside
|
|
88
|
+
* the findings and moves no exit code. It is still the only local detector for
|
|
89
|
+
* a row lost to a hand-edit, a handoff message that never arrived, or an
|
|
90
|
+
* orchestrator that ended before placing it.
|
|
91
|
+
*/
|
|
92
|
+
export interface Unplaced {
|
|
93
|
+
readonly subject: string
|
|
94
|
+
readonly message: string
|
|
95
|
+
}
|
|
96
|
+
|
|
86
97
|
/**
|
|
87
98
|
* A backlog line, which carries a pointer and nothing else. The backlog is
|
|
88
99
|
* explicitly unordered, so a line has no position to read and no columns to
|
|
@@ -116,6 +127,7 @@ export interface ValidateReport {
|
|
|
116
127
|
readonly findings: readonly Finding[]
|
|
117
128
|
readonly untested: readonly Untested[]
|
|
118
129
|
readonly claims: readonly FolderClaim[]
|
|
130
|
+
readonly unplaced: readonly Unplaced[]
|
|
119
131
|
}
|
|
120
132
|
|
|
121
133
|
export interface ValidateRefused {
|
|
@@ -556,15 +568,15 @@ function resolves(target: string, dir: string, root: string): boolean {
|
|
|
556
568
|
}
|
|
557
569
|
|
|
558
570
|
/**
|
|
559
|
-
*
|
|
560
|
-
*
|
|
561
|
-
*
|
|
562
|
-
*
|
|
571
|
+
* Reports a task cited from two places on the board, which claims two
|
|
572
|
+
* contradictory things about itself: a task belongs to exactly one group,
|
|
573
|
+
* board or backlog. A task file neither surface names is no longer reported
|
|
574
|
+
* here, since that is the normal state between a session filing it and a live
|
|
575
|
+
* orchestrator placing it.
|
|
563
576
|
*/
|
|
564
577
|
function checkMapping(
|
|
565
578
|
rows: readonly BoardRow[],
|
|
566
579
|
backlog: readonly BacklogRow[],
|
|
567
|
-
stems: readonly string[],
|
|
568
580
|
dir: string,
|
|
569
581
|
): Finding[] {
|
|
570
582
|
const findings: Finding[] = []
|
|
@@ -638,18 +650,6 @@ function checkMapping(
|
|
|
638
650
|
}
|
|
639
651
|
}
|
|
640
652
|
|
|
641
|
-
for (const stem of stems) {
|
|
642
|
-
if (!seen.has(stem) && !listed.has(stem)) {
|
|
643
|
-
findings.push({
|
|
644
|
-
kind: 'row-missing',
|
|
645
|
-
group: undefined,
|
|
646
|
-
subject: stem,
|
|
647
|
-
message:
|
|
648
|
-
'is a task file with no row on the board and no line on the backlog.',
|
|
649
|
-
})
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
|
|
653
653
|
return findings
|
|
654
654
|
}
|
|
655
655
|
|
|
@@ -878,6 +878,32 @@ function checkFolderClaims(
|
|
|
878
878
|
return claims
|
|
879
879
|
}
|
|
880
880
|
|
|
881
|
+
/**
|
|
882
|
+
* Reports a task file neither the board nor the backlog names. Under the
|
|
883
|
+
* roster-checked hand-off, filing a task and placing its row are two acts a
|
|
884
|
+
* different session each may perform, so this state is ordinary rather than
|
|
885
|
+
* an error, and it moves no exit code. It still surfaces here rather than
|
|
886
|
+
* nowhere, since it is the only local detector for a row a hand-edit dropped,
|
|
887
|
+
* a handoff message that never arrived, or an orchestrator that ended before
|
|
888
|
+
* placing it.
|
|
889
|
+
*/
|
|
890
|
+
function checkUnplaced(
|
|
891
|
+
rows: readonly BoardRow[],
|
|
892
|
+
backlog: readonly BacklogRow[],
|
|
893
|
+
stems: readonly string[],
|
|
894
|
+
): Unplaced[] {
|
|
895
|
+
const seen = new Set(rows.flatMap((row) => (row.stem ? [row.stem] : [])))
|
|
896
|
+
const listed = new Set(backlog.flatMap((row) => (row.stem ? [row.stem] : [])))
|
|
897
|
+
|
|
898
|
+
return stems
|
|
899
|
+
.filter((stem) => !seen.has(stem) && !listed.has(stem))
|
|
900
|
+
.map((stem) => ({
|
|
901
|
+
subject: stem,
|
|
902
|
+
message:
|
|
903
|
+
'is a task file with no row on the board and no line on the backlog.',
|
|
904
|
+
}))
|
|
905
|
+
}
|
|
906
|
+
|
|
881
907
|
/**
|
|
882
908
|
* Reads the task a blocker cell cites. A task pointer is a bare sibling
|
|
883
909
|
* filename, the way every `Task` column spells one, so a target carrying a
|
|
@@ -1136,7 +1162,7 @@ export async function validateBoard(
|
|
|
1136
1162
|
|
|
1137
1163
|
const findings = [
|
|
1138
1164
|
...shapeFindings,
|
|
1139
|
-
...checkMapping(rows, backlog,
|
|
1165
|
+
...checkMapping(rows, backlog, dir),
|
|
1140
1166
|
...checkPlans(rows, dir, root),
|
|
1141
1167
|
...(await checkPlanAgreement(rows, dir, root)),
|
|
1142
1168
|
...checkCollisions(rows),
|
|
@@ -1152,5 +1178,6 @@ export async function validateBoard(
|
|
|
1152
1178
|
findings,
|
|
1153
1179
|
untested: parked.untested,
|
|
1154
1180
|
claims: checkFolderClaims(rows, root),
|
|
1181
|
+
unplaced: checkUnplaced(rows, backlog, stems),
|
|
1155
1182
|
}
|
|
1156
1183
|
}
|
package/standards/index.md
CHANGED
|
@@ -35,4 +35,5 @@ Reference docs for consistent authoring across the toolkit and target projects.
|
|
|
35
35
|
- [Tasks reference](tasks.md): Folder layout, filename convention, readiness groups, and content rules for .canon/tasks/
|
|
36
36
|
- [Teach reference](teach.md): Workspace layout, ordinal naming, frontmatter, and the mission and learning-record formats for a learning workspace
|
|
37
37
|
- [Versioning reference](versioning.md): Phase label vs semver discipline across tasks, PRs, reviews, issues, commits, and tags
|
|
38
|
+
- [Wiki reference](wiki.md): Folder split, frontmatter, naming convention, and sourcing for a wiki reference page
|
|
38
39
|
- [Wireframe reference](wireframes.md): Shape and content rules for .claude/wireframes/<surface>.md files
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Wiki reference
|
|
3
|
+
description: Folder split, frontmatter, naming convention, and sourcing for a wiki reference page
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Wiki reference
|
|
7
|
+
|
|
8
|
+
Applies to each authored page under `wiki/`. Skip for `index.md` at any depth, which is a hand-maintained catalog rather than a reference page, and carries `subtitle` rather than `description`.
|
|
9
|
+
|
|
10
|
+
## Scope
|
|
11
|
+
|
|
12
|
+
Governs each authored page under `wiki/`: which folder it belongs in, its frontmatter, its filename, and how it cites the subject it documents.
|
|
13
|
+
|
|
14
|
+
Does not govern:
|
|
15
|
+
|
|
16
|
+
- Voice, rhythm, sentence construction, and information density: the `write-human` skill
|
|
17
|
+
- Headings, punctuation, word choice, and file references: `markdown.md`
|
|
18
|
+
|
|
19
|
+
## What a working wiki page looks like
|
|
20
|
+
|
|
21
|
+
A page works when a reader who has never opened it settles two things without asking anyone:
|
|
22
|
+
|
|
23
|
+
- Which folder holds it, decided from the subject alone rather than from where it happened to get written
|
|
24
|
+
- Where the content came from, so a claim can be checked against its owner rather than against this repository
|
|
25
|
+
|
|
26
|
+
A page failing either is non-conforming even when it satisfies every shape rule below.
|
|
27
|
+
|
|
28
|
+
## Placement
|
|
29
|
+
|
|
30
|
+
- Write a page here only when its subject is owned outside this repository. Route anything about how this repository works to `docs/`, `.claude/context/`, or a skill body instead.
|
|
31
|
+
- File the page by owner: `wiki/claude/` for Anthropic, `wiki/tools/` for another vendor, `wiki/concepts/` where no single vendor owns the subject.
|
|
32
|
+
- Treat `wiki/concepts/rule-writing-vocabulary.md` as the one recorded exception to the owner test, stated in the page itself.
|
|
33
|
+
|
|
34
|
+
## Frontmatter
|
|
35
|
+
|
|
36
|
+
- `title` (required): sentence case, naming the subject
|
|
37
|
+
- `description` (required): one line naming what the page covers
|
|
38
|
+
|
|
39
|
+
## Naming
|
|
40
|
+
|
|
41
|
+
- Name a `wiki/claude/` page with a `claude-` prefix before the kebab subject.
|
|
42
|
+
- Name a `wiki/tools/` or `wiki/concepts/` page as a bare kebab subject, with no prefix.
|
|
43
|
+
|
|
44
|
+
## Sourcing
|
|
45
|
+
|
|
46
|
+
- Close the intro paragraph with a `Source:` sentence naming the owner. Link the canonical page where one exists, and name the owner alone where the subject has no single URL.
|
|
47
|
+
- Fetch current information through the `claude-code-guide` agent when the subject is Claude Code. Do not work from training knowledge.
|
|
48
|
+
- Propose an addition or correction and wait for confirmation. Do not write to a wiki file unasked.
|
|
49
|
+
|
|
50
|
+
## Template
|
|
51
|
+
|
|
52
|
+
```markdown
|
|
53
|
+
---
|
|
54
|
+
title: <Subject>
|
|
55
|
+
description: <one line naming what this page covers>
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
# <Subject>
|
|
59
|
+
|
|
60
|
+
<What the subject is and why it matters.> Source: <owner, with a link to the canonical page where one exists>.
|
|
61
|
+
|
|
62
|
+
## <Section>
|
|
63
|
+
|
|
64
|
+
<Reference content.>
|
|
65
|
+
```
|
|
@@ -17,3 +17,6 @@ packages = [
|
|
|
17
17
|
[scripts.override]
|
|
18
18
|
"dev" = "WORKTREE_PORT_OFFSET=$(bash scripts/worktree-port.sh) && export WORKTREE_PORT_OFFSET && next dev --port $((3000 + WORKTREE_PORT_OFFSET))"
|
|
19
19
|
"screenshot" = "PREVIEW_PORT=$(bash scripts/worktree-port.sh 3000) && export PREVIEW_PORT && bash scripts/screenshot.sh"
|
|
20
|
+
|
|
21
|
+
[gitignore]
|
|
22
|
+
"# Next" = [".next/"]
|
|
@@ -33,6 +33,12 @@ Next has no config-file port hook, unlike `astro.config.mjs`'s `server.port` or
|
|
|
33
33
|
|
|
34
34
|
`create-next-app`'s own default `tsconfig.json` needs no changes beyond project-specific path aliases, so a golden copy here would ship nothing the scaffold does not already write.
|
|
35
35
|
|
|
36
|
+
## Gitignore (extend)
|
|
37
|
+
|
|
38
|
+
`[gitignore]` groups this stack edits, restated here per the manifest-to-reference symmetry:
|
|
39
|
+
|
|
40
|
+
- `"# Next" = [".next/"]`
|
|
41
|
+
|
|
36
42
|
## Development docs (extend)
|
|
37
43
|
|
|
38
44
|
Append to the `## Scripts` table:
|