@erclx/canon 5.11.0 → 5.12.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/REQUIREMENT.md +1 -0
- package/claude/skills/auto-ship/SKILL.md +5 -8
- package/claude/skills/auto-ship/references/failure-recovery.md +0 -1
- package/claude/skills/auto-ship/references/ui-checklist.md +21 -0
- package/claude/skills/git-pr/references/evidence.md +7 -7
- package/claude/skills/git-ship/REQUIREMENT.md +1 -1
- package/claude/skills/git-ship/SKILL.md +1 -1
- package/claude/skills/review-craft/references/ui.md +1 -2
- package/claude/skills/review-pr/SKILL.md +1 -1
- package/claude/skills/ui-checklist/SKILL.md +1 -18
- package/docs/agents/gate.md +1 -1
- package/docs/agents/hooks.md +2 -0
- package/docs/agents/install-and-sync.md +2 -2
- package/docs/agents/pr-evidence.md +26 -25
- package/docs/agents/tasks-validate.md +3 -3
- package/docs/target/migrations.md +1 -1
- package/docs/workflow/ai-workflow.md +2 -1
- package/docs/workflow/parallel-features.md +3 -3
- package/package.json +3 -4
- package/scripts/lib/sandbox-path.sh +1 -1
- package/src/commands/pr.ts +16 -14
- package/src/gate/stages.ts +10 -2
- package/src/tasks/validate.ts +37 -27
- package/standards/tasks.md +1 -1
- package/tooling/base/configs/.husky/post-merge +30 -77
- package/tooling/base/configs/.husky/post-rewrite +3 -3
- package/tooling/base/reference.md +1 -1
- package/scripts/core/clean.sh +0 -36
- package/scripts/core/regen-agent-fixture.sh +0 -235
- package/scripts/core/regen-claude-copies.sh +0 -10
- package/scripts/core/regen-indexes.sh +0 -10
- package/scripts/core/snapshot.sh +0 -134
- package/scripts/core/update.sh +0 -35
|
@@ -21,6 +21,7 @@ Review is the step that varies most. It gets skipped on a diff that needed one,
|
|
|
21
21
|
- Own the review receipt's lifetime, since this chain writes it, cites it in its own closing block, and is the only body that can read whether the step keeping it is still using it
|
|
22
22
|
- Delegate the ship sequence to `git-ship` rather than restating it, and name only what this chain adds to it
|
|
23
23
|
- Open the pull request as a draft before the continuous integration watch begins, since a pull request marked after it is mergeable for the length of the run
|
|
24
|
+
- Continue past a produced UI checklist rather than stopping on it, and report its unchecked boxes in the closing block, since the draft mark already holds the merge and a stop there skipped review on the resumed path
|
|
24
25
|
- Prove the draft target's head is the current branch and its state is open before marking it, and stop with nothing marked on a mismatch, since the number is retyped between steps and a draft mark on a release pull request holds that release
|
|
25
26
|
- Name the recovery for the stop it took, since the value of stopping is that the user knows where to resume
|
|
26
27
|
|
|
@@ -6,7 +6,7 @@ disable-model-invocation: true
|
|
|
6
6
|
|
|
7
7
|
# Auto ship
|
|
8
8
|
|
|
9
|
-
Chain the post-plan pipeline in a single run. Every step has a stop condition. State is always recoverable on stop: code lives on the branch, review output on disk, plan still linked.
|
|
9
|
+
Chain the post-plan pipeline in a single run. Every step but the UI checklist has a stop condition. State is always recoverable on stop: code lives on the branch, review output on disk, plan still linked.
|
|
10
10
|
|
|
11
11
|
## Guards
|
|
12
12
|
|
|
@@ -95,11 +95,7 @@ Never read a missing subcommand as clean. Read `${CLAUDE_SKILL_DIR}/references/v
|
|
|
95
95
|
|
|
96
96
|
## Step 5: UI checklist (conditional)
|
|
97
97
|
|
|
98
|
-
If the diff touches UI files (JSX, TSX, Vue, Svelte, HTML, or CSS under `src/`), invoke `canon:ui-checklist
|
|
99
|
-
|
|
100
|
-
If `ui-checklist` produces a checklist, stop: `❌ UI requires visual verification. Checklist at .canon/tmp/handoff/ui-checklist/<slug>.md, which reaches the pull request once /git-ship runs. Verify manually, then run /git-ship.`
|
|
101
|
-
|
|
102
|
-
If there is nothing to verify visually and nothing shipping untested, continue.
|
|
98
|
+
If the diff touches UI files (JSX, TSX, Vue, Svelte, HTML, or CSS under `src/`), invoke `canon:ui-checklist`, then continue to Step 6 whatever it returns, in every run. The Step 8 draft mark holds the merge, so a stop here protects nothing. When it produces a checklist, read `${CLAUDE_SKILL_DIR}/references/ui-checklist.md` for the counts to hold before `git-pr` removes the file.
|
|
103
99
|
|
|
104
100
|
## Step 6: review
|
|
105
101
|
|
|
@@ -149,10 +145,11 @@ Emit the Output block on the wake after `git-ship`'s background CI watch exits o
|
|
|
149
145
|
|
|
150
146
|
## Output
|
|
151
147
|
|
|
152
|
-
Respond with up to
|
|
148
|
+
Respond with up to six lines:
|
|
153
149
|
|
|
154
150
|
```plaintext
|
|
155
151
|
✅ Autoshipped (<state>): <PR url>
|
|
152
|
+
🖼️ <N> visual boxes unchecked (<M> taste), owed on the evidence comment of <PR url> to the operator, or to the UI reviewer for the boxes a driver can run
|
|
156
153
|
<N minor findings kept in .canon/review/branch-<slug>.md>
|
|
157
154
|
<N facts routed to context entries>
|
|
158
155
|
<N memories captured in .canon/memory/>
|
|
@@ -161,7 +158,7 @@ Respond with up to five lines:
|
|
|
161
158
|
|
|
162
159
|
`<state>` is whatever the Step 8 read returned, being `draft` or `ready, unsupervised`, rather than the state the undo asked for.
|
|
163
160
|
|
|
164
|
-
|
|
161
|
+
Fill the second line from the counts Step 5 held, and omit it when no checklist was produced. Omit the third line if there were no minor findings, and the fourth if nothing routed. Omit the fifth if `memory-capture` wrote no memory file this session, and the sixth if it returned no review line. Pass that line through verbatim, since a dispatched worker's controller relays it to the operator.
|
|
165
162
|
|
|
166
163
|
This block replaces the one `git-ship` closes on rather than following it, since emitting both reports one run twice and buries the state under a `✅ Shipped` that does not name it.
|
|
167
164
|
|
|
@@ -20,7 +20,6 @@ Every stop point leaves recoverable state. The user resumes manually from the ap
|
|
|
20
20
|
| Empty changed-file list | Re-run once the plan produces tracked output. Ship gitignored output outside the chain, never by tracking it. |
|
|
21
21
|
| Branch collision on worktree entry | `session-worktree` Step 5 found `<slug>` already as a local branch. Resolve manually (rename or delete the stale branch), then re-run autoship. |
|
|
22
22
|
| Verify fails | Read logs, fix manually, run `/git-ship` |
|
|
23
|
-
| UI checklist | Verify visually, run `/git-ship` |
|
|
24
23
|
| Inherited review findings | Fix findings, run `/git-ship` |
|
|
25
24
|
| Self-introduced finding survived | Read the receipt for what the one repair pass left open, fix it, run `/git-ship` |
|
|
26
25
|
| git-ship fails | Inspect hook or remote error, run again |
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: UI checklist counts
|
|
3
|
+
description: What auto-ship Step 5 holds when ui-checklist produces a checklist, so the closing block can name the unchecked visual boxes after git-pr has removed the file
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Hold the checklist counts
|
|
7
|
+
|
|
8
|
+
Step 5 of `auto-ship`. The session reads this file when `ui-checklist` has produced a checklist.
|
|
9
|
+
|
|
10
|
+
The chain continues to Step 6 here in every run, dispatched or hand-run, since the skill cannot tell the two apart and the draft mark at Step 8 holds the merge either way. A stop here used to leave nothing committed, and resuming at `/git-ship` skipped the review at Step 6.
|
|
11
|
+
|
|
12
|
+
Count two things in `.canon/tmp/handoff/ui-checklist/<slug>.md` at the main worktree root:
|
|
13
|
+
|
|
14
|
+
- `<N>`, the `- [ ]` lines
|
|
15
|
+
- `<M>`, how many of those end in `(taste)`
|
|
16
|
+
|
|
17
|
+
Hold both until the Output block. `git-pr` removes the file once it posts the evidence comment, so a count taken after Step 8 reads nothing.
|
|
18
|
+
|
|
19
|
+
The Output block's second line takes both counts. When every box is a taste box, `<N>` and `<M>` match and the boxes are owed to the operator alone, since a driver reports a taste box as needing a person's eyes rather than passing it, so say the operator and drop the UI reviewer clause.
|
|
20
|
+
|
|
21
|
+
When `ui-checklist` reports nothing to verify visually, it writes no file, Step 5 holds no count, and the Output block carries no boxes line.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Rendered-surface evidence
|
|
3
|
-
description: How git-pr finds the UI checklist and the local server, posts the evidence comparison or the checklist alone, and adds the preview address, for a change that touches a rendered surface
|
|
3
|
+
description: How git-pr finds the UI checklist and the local server, posts the evidence comparison or the checklist alone as the fallback for a git refusal, and adds the preview address, for a change that touches a rendered surface
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Rendered-surface evidence
|
|
@@ -13,7 +13,7 @@ The rendered-surface step of `git-pr`, reached after the final command printed t
|
|
|
13
13
|
|
|
14
14
|
When it exists, scan it against `${CLAUDE_SKILL_DIR}/../../standards/publish.md` before either step posts it, the same as the pull request body in the skill's pre-publish scan.
|
|
15
15
|
|
|
16
|
-
Where it lands is decided by the evidence step below rather than here, since a checklist reads next to the screenshots it annotates and posting it on its own is the fallback for a
|
|
16
|
+
Where it lands is decided by the evidence step below rather than here, since a checklist reads next to the screenshots it annotates and posting it on its own is the fallback for a refused render.
|
|
17
17
|
|
|
18
18
|
## Find the local server
|
|
19
19
|
|
|
@@ -31,7 +31,7 @@ The verb reads the listening sockets on this machine, keeps those whose process
|
|
|
31
31
|
|
|
32
32
|
Pass `--local` to the evidence step only on an `ok` here. The binary answering `ok` is the one carrying the flag, so a binary lacking the verb never meets a flag it would reject.
|
|
33
33
|
|
|
34
|
-
With
|
|
34
|
+
With a checklist, a diff carrying no evidence image still renders `ok`, and a local link rides in the same comment above the checklist, so a reviewer gets both in one place. A local link with neither an evidence image nor a checklist still reports `no-evidence`, which keeps a branch that changed no rendered surface from getting a comment holding a link and nothing else.
|
|
35
35
|
|
|
36
36
|
A seeded workflow replaces the line with a note once the pull request closes, so the link does not outlive the branch it points at.
|
|
37
37
|
|
|
@@ -43,13 +43,13 @@ Run the verb once against the number the skill's final command printed. Pass `--
|
|
|
43
43
|
canon pr evidence <number> --checklist <main-root>/.canon/tmp/handoff/ui-checklist/<slug>.md --local <url> --json
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
One call answers both questions because a checklist
|
|
46
|
+
One call answers both questions because a checklist decides `no-evidence`. The verb renders `ok` on a diff carrying a checklist and no evidence image, with a marked body holding the checklist, so the checklist always lands in the one comment a later call finds and edits. Without a checklist, a diff carrying no evidence image reports `no-evidence`.
|
|
47
47
|
|
|
48
48
|
Pass `--checklist` only for a file that exists. The verb refuses as `unreadable-checklist` on a path it cannot read or one holding nothing, which is a caller bug rather than a transient failure, so stop and repair the path rather than posting a body with the checklist silently dropped.
|
|
49
49
|
|
|
50
50
|
Read `reason` on the record rather than the exit code.
|
|
51
51
|
|
|
52
|
-
- `no-evidence`: nothing changed under an `evidence/` segment
|
|
52
|
+
- `no-evidence`: nothing changed under an `evidence/` segment and no checklist was passed, so no body was rendered. Say nothing about the evidence and move on.
|
|
53
53
|
- `ok`: write `body` to `.canon/tmp/pr/evidence/body-<number>.md` at the main worktree root (resolved the way `session-worktree` does), then post or update the comment:
|
|
54
54
|
|
|
55
55
|
```bash
|
|
@@ -68,7 +68,7 @@ Any other `reason` is one of the mirrored git refusals (`gh-missing`, `gh-failed
|
|
|
68
68
|
|
|
69
69
|
## Post the UI checklist alone
|
|
70
70
|
|
|
71
|
-
Run this step only when the evidence step above
|
|
71
|
+
Run this step only as the fallback, when the evidence step above reported one of the git refusals and a checklist file exists, since every other branch with a checklist rendered `ok` and carried it. The comment this posts carries no marker, so a later `canon pr evidence` call reports no `checklist` for it. Post it as its own comment on `<number>`:
|
|
72
72
|
|
|
73
73
|
```bash
|
|
74
74
|
gh pr comment <number> --body-file <main-root>/.canon/tmp/handoff/ui-checklist/<slug>.md
|
|
@@ -92,7 +92,7 @@ Deleting the file is what makes the later re-render safe. `git-followup` re-runs
|
|
|
92
92
|
|
|
93
93
|
## Post the preview address
|
|
94
94
|
|
|
95
|
-
Run this step only when the evidence step above returned `ok` or a checklist was posted
|
|
95
|
+
Run this step only when the evidence step above returned `ok` or a checklist was posted by the fallback. Either one means the pull request changes a rendered surface, and a reviewer holding a checklist with no screenshots needs the live page most. Otherwise skip it silently.
|
|
96
96
|
|
|
97
97
|
The evidence comment is already posted, so the reviewer has the screenshots while the deploy runs. Mint the preview against the same `<number>`:
|
|
98
98
|
|
|
@@ -9,7 +9,7 @@ description: What the ship chain is for, the gaps it closes, and why it does not
|
|
|
9
9
|
|
|
10
10
|
Without this skill, the post-feature sequence runs from memory. Doc sync gets skipped, so the pull request ships with planning docs describing the previous scope, or it runs after staging has already closed and its output never reaches a commit. Chaining by hand is also where a session narrates between steps, which turns one flow into a conversation and invites a decision at every boundary.
|
|
11
11
|
|
|
12
|
-
The sequence also reaches the remote on work nothing re-checked. This skill is the resume point `auto-ship` names at
|
|
12
|
+
The sequence also reaches the remote on work nothing re-checked. This skill is the resume point `auto-ship` names at three of its stop points, including the one a failed verify takes, so the fix the user makes by hand after that stop is pushed with no suite run against it. Every branch shipped so far passed that verify on its first attempt, which is why the path has produced no instance rather than being closed.
|
|
13
13
|
|
|
14
14
|
## Must
|
|
15
15
|
|
|
@@ -15,7 +15,7 @@ Run the verify commands `CLAUDE.md` names (lint, typecheck, tests) before the se
|
|
|
15
15
|
|
|
16
16
|
When `CLAUDE.md` names no verify command, say so on one line and continue. A project with no suite is not a project with a failing one.
|
|
17
17
|
|
|
18
|
-
Verify runs ahead of the sync skills so a stop leaves the tree exactly as the user left it. Re-running a suite the caller already ran costs one command, and the path it closes is the one that has no other guard: `auto-ship` verifies at its own Step 3 and then hands
|
|
18
|
+
Verify runs ahead of the sync skills so a stop leaves the tree exactly as the user left it. Re-running a suite the caller already ran costs one command, and the path it closes is the one that has no other guard: `auto-ship` verifies at its own Step 3 and then hands three of its stop points straight back here, so a fix made by hand after one of those stops otherwise reaches the remote with nothing re-run.
|
|
19
19
|
|
|
20
20
|
## Pre-check
|
|
21
21
|
|
|
@@ -45,7 +45,7 @@ Take the sha from the row, so the base and head sides are exactly what the comme
|
|
|
45
45
|
|
|
46
46
|
Open at most 12 images per pass, filled in this order:
|
|
47
47
|
|
|
48
|
-
1. Every image a ticked checklist box
|
|
48
|
+
1. Every image a ticked checklist box names
|
|
49
49
|
2. The head side of each state at its narrowest and widest captured width, in one theme
|
|
50
50
|
3. The base side, only for a state whose head image looks wrong or whose diff was meant to change appearance
|
|
51
51
|
|
|
@@ -65,7 +65,6 @@ The `## What to look at` block between the `pr-checklist` markers is the author'
|
|
|
65
65
|
|
|
66
66
|
- Test each ticked box against an opened image showing the state and width it names. Flag a box whose image contradicts it, quoting the box.
|
|
67
67
|
- A ticked box no image covers is untested rather than confirmed, and the finding says so.
|
|
68
|
-
- Read the `**Widths:**` block's `Seen at:` line against the widths the images carry.
|
|
69
68
|
- Test only the rows the current comment shows. A box ticked on an earlier head can describe images the comment no longer carries.
|
|
70
69
|
- Leave unchecked boxes to the procedure's own read of the description.
|
|
71
70
|
|
|
@@ -73,7 +73,7 @@ Read each test file the branch added or changed against the final filter in `can
|
|
|
73
73
|
|
|
74
74
|
Then read the description's `## Testing` section, which is the one part of a pull request body this session is positioned to falsify. The branch author cannot see what a sibling branch drove the same day, and the arm list is what settles the claim rather than the diff.
|
|
75
75
|
|
|
76
|
-
Test every unchecked box against the testing discipline in `${CLAUDE_SKILL_DIR}/../../standards/pr.md`, which reserves an unchecked box for a capability the agent lacks. Raise the box when it names no human at all, when the human it names is a live agent session, or when it names a person for a step the repository ships a harness for,
|
|
76
|
+
Test every unchecked box against the testing discipline in `${CLAUDE_SKILL_DIR}/../../standards/pr.md`, which reserves an unchecked box for a capability the agent lacks. Raise the box when it names no human at all, when the human it names is a live agent session, or when it names a person for a step the repository ships a harness for, such as `scripts/sandbox/run.sh`. Cost alone is not an answer, since authorizing a spend is the operator's and performing the run is not. A refusal the author actually met is an answer, and it names which one.
|
|
77
77
|
|
|
78
78
|
Test every ticked box too, bounded to one naming a file or a command. Confirm what it names still exists at `<headRefOid>`, `git show <headRefOid>:<path>` for a file or `git grep <command> <headRefOid>` for a command, rather than re-running what it claims. A box naming neither stays untested, since nothing here can confirm a claim carrying no artifact. A box ticked before a later commit removed or replaced what it names is a `should-fix` finding on the body itself, filed under a `**PR body**` block ahead of the file blocks, since what it corrupts is the merge record rather than a file in the diff.
|
|
79
79
|
|
|
@@ -34,30 +34,13 @@ Group visual items by feature area. Use `- [ ]` checkbox syntax, and write each
|
|
|
34
34
|
Write each box so a session driving the running app can run it without guessing:
|
|
35
35
|
|
|
36
36
|
- **Route.** Name where the box is driven, as a path or the control that reaches it. A box naming no route is driven from the address the driver opens, so a single-page app with no routing needs no invented path. Write "from the page the app opens on" when a state is reached by a sequence rather than a URL.
|
|
37
|
-
- **Width.** Name the width a box is driven at only when the change can make it differ by width,
|
|
37
|
+
- **Width.** Name the width a box is driven at only when the change can make it differ by width, such as layout, spacing, or anything responsive. A box about copy, color, or a state that renders the same everywhere leaves the width off rather than inventing one.
|
|
38
38
|
- **Verb.** Open on an action a driver performs, such as "scroll to", "click", "resize to", or "tab to". Never open on "read" or "look at", which name no action.
|
|
39
39
|
- **Taste.** End the line with `(taste)` when the result is a judgment of look or feel rather than a fact a driver can observe. A driver reports such a box as needing a person's eyes instead of passing it.
|
|
40
40
|
- **Setup.** A box needing a state the page does not open in states the setup inline. When a hosted preview cannot reach that state, such as a stopped service or a seeded record, end the line with `(local only)` instead.
|
|
41
41
|
|
|
42
42
|
The checkbox stays `- [ ]` followed by the action and the result, since the evidence comment carries ticked boxes forward by that shape.
|
|
43
43
|
|
|
44
|
-
## The widths block
|
|
45
|
-
|
|
46
|
-
A screenshot shows one width, so a reviewer cannot tell from the picture alone whether any other width was looked at. When a change touches layout, spacing, or anything responsive, put this block above the visual items, since it frames what the screenshots beside it can support:
|
|
47
|
-
|
|
48
|
-
```markdown
|
|
49
|
-
**Widths:**
|
|
50
|
-
|
|
51
|
-
Seen at: <the widths the reviewer looked at>
|
|
52
|
-
Not seen at: <the widths nobody looked at, or "none">
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
Leave both lines for the reviewer to complete. This session cannot verify a width, so the block asks what was seen and never asserts coverage. Fix no width list in the block, since which widths matter belongs to the project.
|
|
56
|
-
|
|
57
|
-
Keep the `Not seen at:` line even when the answer is "none", so an omission reads as a claim rather than a gap.
|
|
58
|
-
|
|
59
|
-
Omit the block when the change cannot differ by width, such as copy, color, or a state that renders the same everywhere, the way the missing-test heading is omitted below.
|
|
60
|
-
|
|
61
44
|
## The missing-test list
|
|
62
45
|
|
|
63
46
|
Name every automatable change this branch ships with no test, one per line, with the layer `test-craft` would place it at. Do not write the test and do not read the project's test config to write one against.
|
package/docs/agents/gate.md
CHANGED
|
@@ -5,7 +5,7 @@ description: Running the gate this repository verifies a branch with, what the s
|
|
|
5
5
|
|
|
6
6
|
# Merge gate
|
|
7
7
|
|
|
8
|
-
`canon gate run` runs every stage that guards a branch here, in order, stopping at the first stage that finds a fact. `bun run check` and `bun run check:ci` resolve to it, and `
|
|
8
|
+
`canon gate run` runs every stage that guards a branch here, in order, stopping at the first stage that finds a fact. `bun run check` and `bun run check:ci` resolve to it, and the `update` package script runs `bun run check` after a dependency update.
|
|
9
9
|
|
|
10
10
|
```bash
|
|
11
11
|
canon gate run
|
package/docs/agents/hooks.md
CHANGED
|
@@ -16,6 +16,8 @@ canon hooks post-merge --root "$root"
|
|
|
16
16
|
| --------------- | ---------------------------------------------------------------------------------------------------- |
|
|
17
17
|
| `--root <path>` | Main worktree root holding the board and the records, resolved from `git worktree list` when omitted |
|
|
18
18
|
|
|
19
|
+
The base stack ships the same call as its `.husky/post-merge`, behind the same board guard and upgrade bootstrap, so a target that syncs it runs these five steps.
|
|
20
|
+
|
|
19
21
|
Exit codes: `0` on every path, since a hook failing aborts nothing useful. Lines go to stderr, and stdout stays empty. An ordinary merge prints nothing.
|
|
20
22
|
|
|
21
23
|
## The steps
|
|
@@ -139,8 +139,8 @@ since a target has no rule source of its own that ships nowhere.
|
|
|
139
139
|
|
|
140
140
|
It takes `--root <path>` and defaults to the toolkit root, prints nothing on success, and
|
|
141
141
|
reports the reason on stderr with exit 1 when the record names a stack or rule
|
|
142
|
-
that does not resolve.
|
|
143
|
-
|
|
142
|
+
that does not resolve. The Consumed copies stage of `bun run check` calls it and
|
|
143
|
+
asserts the result is committed.
|
|
144
144
|
|
|
145
145
|
## Whole-project sync
|
|
146
146
|
|
|
@@ -46,19 +46,19 @@ image or marks the case new.
|
|
|
46
46
|
|
|
47
47
|
`reason` on the record is what a caller branches on, not the exit code:
|
|
48
48
|
|
|
49
|
-
| Reason | What it means
|
|
50
|
-
| ---------------------- |
|
|
51
|
-
| `ok` | A body was rendered. `commentId` is set when a marked comment already exists.
|
|
52
|
-
| `no-evidence` |
|
|
53
|
-
| `unreadable-checklist` | `--checklist` named a path that could not be read, or one holding nothing.
|
|
54
|
-
| `gh-missing` | `gh` is not on the path, so no pull request could be resolved.
|
|
55
|
-
| `gh-failed` | `gh` could not answer for this repository or branch, or read its comments.
|
|
56
|
-
| `no-branch` | The pull request carries no head branch name.
|
|
57
|
-
| `no-object-head` | The pull request object reported no head commit.
|
|
58
|
-
| `no-base` | GitHub reported no merge base for the pull request.
|
|
59
|
-
| `unreadable-changes` | GitHub could not list what the pull request changed.
|
|
60
|
-
| `would-empty` | The render holds no cases and the marked comment holds some. No body.
|
|
61
|
-
| `check-writes` | `--check` was passed with `--preview`, `--local`, or `--checklist`.
|
|
49
|
+
| Reason | What it means |
|
|
50
|
+
| ---------------------- | --------------------------------------------------------------------------------------- |
|
|
51
|
+
| `ok` | A body was rendered. `commentId` is set when a marked comment already exists. |
|
|
52
|
+
| `no-evidence` | No `evidence/` segment in the diff and no `--checklist` or `--preview`. A silent no-op. |
|
|
53
|
+
| `unreadable-checklist` | `--checklist` named a path that could not be read, or one holding nothing. |
|
|
54
|
+
| `gh-missing` | `gh` is not on the path, so no pull request could be resolved. |
|
|
55
|
+
| `gh-failed` | `gh` could not answer for this repository or branch, or read its comments. |
|
|
56
|
+
| `no-branch` | The pull request carries no head branch name. |
|
|
57
|
+
| `no-object-head` | The pull request object reported no head commit. |
|
|
58
|
+
| `no-base` | GitHub reported no merge base for the pull request. |
|
|
59
|
+
| `unreadable-changes` | GitHub could not list what the pull request changed. |
|
|
60
|
+
| `would-empty` | The render holds no cases and the marked comment holds some. No body. |
|
|
61
|
+
| `check-writes` | `--check` was passed with `--preview`, `--local`, or `--checklist`. |
|
|
62
62
|
|
|
63
63
|
`no-evidence` is not a refusal a caller reports. A project on a stack that
|
|
64
64
|
carries no evidence path, such as `base` or `python`, hits this reason on
|
|
@@ -76,8 +76,9 @@ shows a reviewer, each field present only when that comment holds it:
|
|
|
76
76
|
|
|
77
77
|
These come from the comment already posted, never from the flags the call
|
|
78
78
|
passed, so a skill driving a review reads which address and which checklist a
|
|
79
|
-
reviewer has without parsing the comment itself. A checklist posted
|
|
80
|
-
|
|
79
|
+
reviewer has without parsing the comment itself. A checklist posted raw, which
|
|
80
|
+
only the fallback for a refused render does now, carries no marker, so the
|
|
81
|
+
record reports none of the three for it. Reading
|
|
81
82
|
them means the thread is read before the `no-evidence` answer too, so an
|
|
82
83
|
unreadable thread refuses as `gh-failed` on both reasons rather than
|
|
83
84
|
reporting the fields absent.
|
|
@@ -156,11 +157,11 @@ already holds, the same way the preview address is carried. That is what keeps
|
|
|
156
157
|
already ticked, since `git-pr` deletes the handoff once the first post reports
|
|
157
158
|
success and no later call has a file to pass.
|
|
158
159
|
|
|
159
|
-
The option
|
|
160
|
-
|
|
161
|
-
checklist
|
|
162
|
-
|
|
163
|
-
that cannot be read, or one holding nothing, refuses as
|
|
160
|
+
The option decides `no-evidence` on its own. A branch with a checklist and no
|
|
161
|
+
changed evidence image renders `ok` with a marked body holding the checklist,
|
|
162
|
+
so the checklist always lands in the one comment a later call finds and edits.
|
|
163
|
+
The raw post survives only as the caller's fallback when the render is refused.
|
|
164
|
+
A path that cannot be read, or one holding nothing, refuses as
|
|
164
165
|
`unreadable-checklist` rather than rendering without it: the caller deletes the
|
|
165
166
|
handoff once a post succeeds, so a silently dropped checklist is the only copy
|
|
166
167
|
gone.
|
|
@@ -175,8 +176,8 @@ the local address the marked comment already holds, the same as the other two.
|
|
|
175
176
|
|
|
176
177
|
The option does not turn `no-evidence` into `ok` on its own. A branch with a
|
|
177
178
|
dev server running and no rendered change would otherwise get a comment holding
|
|
178
|
-
a link and nothing else. Together with `--checklist`
|
|
179
|
-
|
|
179
|
+
a link and nothing else. Together with `--checklist` the link and the
|
|
180
|
+
checklist land in one comment.
|
|
180
181
|
`canon docs pr-local` covers where the address comes from and how the line is
|
|
181
182
|
removed at close.
|
|
182
183
|
|
|
@@ -214,9 +215,9 @@ those ask a read to write.
|
|
|
214
215
|
|
|
215
216
|
The thread cannot separate a skipped preview step from a failed one. A pull
|
|
216
217
|
request whose deploy run failed or timed out reads as owing a preview, and the
|
|
217
|
-
session that ran it holds the cause. A checklist
|
|
218
|
-
|
|
219
|
-
|
|
218
|
+
session that ran it holds the cause. A checklist-only branch posts a marked
|
|
219
|
+
comment, so it reads as owing a preview once that comment exists and a deploy
|
|
220
|
+
workflow resolves. The deploy
|
|
220
221
|
workflow is read from the checkout rather than the pull request's head, so a
|
|
221
222
|
pull request that adds or removes its own deploy workflow reads against trunk's.
|
|
222
223
|
|
|
@@ -17,7 +17,7 @@ Seven checks run. Collisions reaches one half of the `## Run now` test the board
|
|
|
17
17
|
| Check | What it reports |
|
|
18
18
|
| ---------- | ----------------------------------------------------------------------------------------------------------------------- |
|
|
19
19
|
| Shape | A row whose cell count disagrees with its table's header, or one stranded behind a table a blank line already closed |
|
|
20
|
-
| Plan | A row whose plan citation disagrees with its group or its task file, or a task whose `Plan:` line
|
|
20
|
+
| Plan | A row whose plan citation disagrees with its group or its task file, or a task whose `Plan:` line lists one plan twice |
|
|
21
21
|
| Mapping | A row or backlog line naming no task file |
|
|
22
22
|
| Grouping | A task carrying a row in more than one readiness group, or on both surfaces |
|
|
23
23
|
| Ordering | A `## Needs a plan` row whose stated position disagrees with where it actually sits, or which states no position at all |
|
|
@@ -26,11 +26,11 @@ Seven checks run. Collisions reaches one half of the `## Run now` test the board
|
|
|
26
26
|
|
|
27
27
|
Shape runs before any other check reads a row, since a row failing it carries no dependable fields for the rest to check. A blank or prose line closes the table above it, so the walk treats the next pipe line as a fresh header candidate rather than as a continuation. That candidate counts as a header only when the line behind it is a separator carrying the same cell count, and one that fails is `row-untabled`, stranded behind a table that already closed. Cell count still has to match the header on every row that clears that test, and a row whose count disagrees is `row-misshapen`, the shape a dropped pipe or a merged column produces.
|
|
28
28
|
|
|
29
|
-
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
|
|
29
|
+
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 where the row's plan is none of the task's links 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.
|
|
30
30
|
|
|
31
31
|
The other two groups have no Plan column, so the same check reads the task file's own `Plan:` line against what the group name claims. A `## Needs a plan` row whose task cites a plan that sits live on disk is `plan-parked`, since the group says no such plan exists and the row is either misplaced or its plan is stale and belongs in the archive. An `## Up next` row whose task cites no live plan is `plan-absent`, since that group says one is written. A plan already archived, or a cited path with no file behind it, reads as not live on both sides. A row whose task file is gone is left to `task-unresolved`. An `## Up next` row with nothing left to build, waiting on an act only a person can take, reports `plan-absent` once its plan archives while the task stays live. That report is expected, since the check reads the task file and not the `Waiting on` prose, so a row in that state moves to `## Needs a plan` or closes.
|
|
32
32
|
|
|
33
|
-
A
|
|
33
|
+
A `Plan:` line linking several distinct plans is the shape a sliced task carries, so the checks read the whole line rather than reporting it. A `## Run now` row passes when its plan is any one of the links, and a parked row is judged on whether any link is live: `## Up next` needs one, and `## Needs a plan` reports `plan-parked` when one is. The check adds no sliced marker, so an unsliced task given a second link by hand reads as sliced. `plan-several` now means a line listing one plan twice, compared by the file each link resolves to, so `../plans/x.md` beside `.canon/plans/x.md` counts. It is reported in every group and replaces the `plan-uncited` or `plan-absent` report the line would otherwise draw. The message lists the links, and the fix is to drop the repeat.
|
|
34
34
|
|
|
35
35
|
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.
|
|
36
36
|
|
|
@@ -62,7 +62,7 @@ The first line reports the plan and the second applies it, relocating each rule
|
|
|
62
62
|
|
|
63
63
|
Twenty-five plugin skills dropped their `claude-` prefix for two-word names, so `canon:claude-docs` answers as `canon:context-fold` and `canon:claude-tasks` as `canon:task-board`. A project that installed governance or tooling before that release holds files naming the old ones, and the plugin answers to none of them. A later release renamed `docs-fold` to `context-fold`, since the skill folds a session into the context entries and the task board rather than into `docs/`, and the same sweep carries a project across that rename too. <!-- canon-keep-retired -->
|
|
64
64
|
|
|
65
|
-
Two of those files run rather than sit there. `.husky/post-merge`
|
|
65
|
+
Two of those files run rather than sit there. `.husky/post-merge` runs the `canon hooks post-merge` verb, so a stale copy of the hook calls a binary that may lack it, and `.claude/hooks/pr-create-log.sh` hands a session a message naming a skill, so a stale copy tells someone to invoke something that no longer exists. A rule under `.claude/rules/canon/core/` names skills too, though a rule is read rather than run.
|
|
66
66
|
|
|
67
67
|
Resync what the toolkit owns, then sweep what the project wrote:
|
|
68
68
|
|
|
@@ -87,7 +87,8 @@ When features are independent, run them in parallel, one worktree and one sessio
|
|
|
87
87
|
For features on a mature stack, chain the post-plan pipeline in one session. Approve the plan, invoke `canon:auto-ship`, and the skill runs implement → verify → review → ship sequentially.
|
|
88
88
|
|
|
89
89
|
- Use when the plan is tight and the stack has real verify commands and test coverage
|
|
90
|
-
- Autoship stops on: verify failure after one fix attempt,
|
|
90
|
+
- Autoship stops on: verify failure after one fix attempt, an inherited review finding above minor, no diff baseline resolving against `main`, an empty changed-file list, or hook failure
|
|
91
|
+
- A produced UI checklist does not stop it. The checklist rides to the draft pull request's evidence comment, and the closing block names how many visual boxes are still unchecked and who owes them
|
|
91
92
|
- Every stop leaves recoverable state. Fix and resume with `/git-ship`
|
|
92
93
|
- Skip autoship for auth, migrations, security-sensitive changes, or work where the plan itself is uncertain
|
|
93
94
|
|
|
@@ -70,8 +70,8 @@ Past that shape, it checks what a surviving row claims against what the tree hol
|
|
|
70
70
|
|
|
71
71
|
Archiving a task carries its plan with it, in the same act. The merge is what settles a plan, and the hook below reaches the archive with nobody watching, so a second call after it would be a second failure point leaving the task archived and the plan live. A task whose plan a sibling still cites archives on its own and leaves the plan live, since moving it on the first task to close strands every other pointer at a path that has gone.
|
|
72
72
|
|
|
73
|
-
Nothing chained that archive until the `post-merge` git hook landed. Every earlier step fires from `canon:auto-ship` or `canon:git-ship`, both of which finish while the pull request is still open, so a task archived there would close for work that may be abandoned. The board is gitignored, which rules out reading it from anywhere but the machine that pulled. The hook
|
|
73
|
+
Nothing chained that archive until the `post-merge` git hook landed. Every earlier step fires from `canon:auto-ship` or `canon:git-ship`, both of which finish while the pull request is still open, so a task archived there would close for work that may be abandoned. The board is gitignored, which rules out reading it from anywhere but the machine that pulled. The hook archives the task a merged pull request closed, through the same verb the base stack ships to every target, and does nothing on a project with no board.
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
The archive gates on a pull request number the merge carried and a closed task, because outcomes are marked on the branch and a task can read all `[x]` while its pull request is still open, so `canon:task-board` still confirms the work reached `main` when a person archives by hand. A companion `post-rewrite` hook carries the same steps for anyone pulling with rebase, which fires that event instead of `post-merge`.
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
The hook archives through `canon tasks archive`, which carries the refusals above, so a task whose pull request has not merged stays where it is. Both hooks ship with the `base` tooling stack, so a target project running this workflow gets the same trigger, and `CANON_SKIP_RECLAIM`, `CANON_SKIP_UPGRADE`, and `CANON_SKIP_PLUGIN_UPDATE` turn off the three steps that act on the machine.
|
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.12.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": {
|
|
@@ -36,9 +36,8 @@
|
|
|
36
36
|
"prepare": "husky",
|
|
37
37
|
"check": "bun src/cli.ts gate run",
|
|
38
38
|
"check:ci": "bun src/cli.ts gate run --all --no-write",
|
|
39
|
-
"update": "
|
|
40
|
-
"clean": "
|
|
41
|
-
"snapshot": "./scripts/core/snapshot.sh",
|
|
39
|
+
"update": "bun update --interactive && bun run check",
|
|
40
|
+
"clean": "rm -rf node_modules && bun pm cache rm && bun install",
|
|
42
41
|
"bootstrap": "./scripts/core/bootstrap.sh",
|
|
43
42
|
"canon:sandbox": "canon sandbox",
|
|
44
43
|
"canon:sandbox:reset": "canon sandbox reset",
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
# both carry the rule sending shared session scratch to the main worktree root,
|
|
8
8
|
# and nothing decides which root wins. A session picking the toolkit writes its
|
|
9
9
|
# output where no manifest reads it, so the run reports success while the verdict
|
|
10
|
-
# reports no writes at all.
|
|
10
|
+
# reports no writes at all. The retired eval runner kept its fixture outside the
|
|
11
11
|
# repository for the same reason.
|
|
12
12
|
#
|
|
13
13
|
# Mints a short random per-run identifier the first time it is asked for, then
|
package/src/commands/pr.ts
CHANGED
|
@@ -466,17 +466,17 @@ export function register(program: Command): void {
|
|
|
466
466
|
'--checklist closes the body with a visual checklist, below the',
|
|
467
467
|
'comparison it annotates. A checklist the marked comment already',
|
|
468
468
|
'carries is read back and carried forward the same way the preview',
|
|
469
|
-
'address is, which keeps a re-render from wiping ticked boxes. It
|
|
470
|
-
'
|
|
471
|
-
'image
|
|
472
|
-
'
|
|
469
|
+
'address is, which keeps a re-render from wiping ticked boxes. It',
|
|
470
|
+
'turns no-evidence into ok: a branch with a checklist and no evidence',
|
|
471
|
+
'image renders a marked body holding the checklist, so a later call',
|
|
472
|
+
'finds and edits that comment.',
|
|
473
473
|
'',
|
|
474
474
|
'--local adds a **Local preview:** line under the hosted preview line,',
|
|
475
475
|
'or opens the body with it when there is none, and is carried forward',
|
|
476
476
|
'the same way. It does not by itself turn no-evidence into ok, so a',
|
|
477
477
|
'branch with a server running and nothing to show posts no link-only',
|
|
478
|
-
'comment. Together with --checklist
|
|
479
|
-
'
|
|
478
|
+
'comment. Together with --checklist the link and the checklist land in',
|
|
479
|
+
'one comment.',
|
|
480
480
|
'',
|
|
481
481
|
'Both reasons carry what the marked comment already posted, each field',
|
|
482
482
|
'present only when that comment holds it:',
|
|
@@ -484,9 +484,10 @@ export function register(program: Command): void {
|
|
|
484
484
|
' local the **Local preview:** address under it',
|
|
485
485
|
' checklist the checklist between its delimiters, ticks included',
|
|
486
486
|
'These come from the comment already posted, never from the flags this',
|
|
487
|
-
'call passed. A checklist
|
|
488
|
-
'record reports none of the three for it. An
|
|
489
|
-
'gh-failed on both reasons rather than
|
|
487
|
+
'call passed. A checklist the caller posted raw after a refused render',
|
|
488
|
+
'carries no marker, so the record reports none of the three for it. An',
|
|
489
|
+
'unread thread refuses as gh-failed on both reasons rather than',
|
|
490
|
+
'reporting the fields absent.',
|
|
490
491
|
'',
|
|
491
492
|
'--check reads the same changed set and thread, renders no body, and',
|
|
492
493
|
'reports what the ship chain still owes the pull request:',
|
|
@@ -1605,14 +1606,15 @@ async function runEvidence(
|
|
|
1605
1606
|
return refuseWith('no-base', EVIDENCE_REFUSALS['no-base'], emitJson, root)
|
|
1606
1607
|
}
|
|
1607
1608
|
|
|
1608
|
-
// A
|
|
1609
|
-
// a
|
|
1610
|
-
|
|
1611
|
-
|
|
1609
|
+
// A checklist opens a comment on its own, so it always lands in the marked
|
|
1610
|
+
// comment a later call edits. A local address rides on a checklist or a
|
|
1611
|
+
// hosted preview and never opens one alone, so a docs-only branch with a
|
|
1612
|
+
// server up posts nothing.
|
|
1613
|
+
const hasChecklist = suppliedChecklist !== undefined
|
|
1612
1614
|
if (
|
|
1613
1615
|
grouped.kind === 'refused' &&
|
|
1614
1616
|
opts.preview === undefined &&
|
|
1615
|
-
!
|
|
1617
|
+
!hasChecklist
|
|
1616
1618
|
) {
|
|
1617
1619
|
logStep('Skipped')
|
|
1618
1620
|
logInfo(
|
package/src/gate/stages.ts
CHANGED
|
@@ -163,7 +163,11 @@ export const STAGES: readonly Stage[] = [
|
|
|
163
163
|
id: 'indexes',
|
|
164
164
|
label: 'Indexes',
|
|
165
165
|
checks: [
|
|
166
|
-
|
|
166
|
+
{
|
|
167
|
+
kind: 'cli',
|
|
168
|
+
argv: ['indexes', 'regen'],
|
|
169
|
+
failure: 'Index regen failed',
|
|
170
|
+
},
|
|
167
171
|
{
|
|
168
172
|
kind: 'drift',
|
|
169
173
|
pathspec: '*index.md',
|
|
@@ -177,7 +181,11 @@ export const STAGES: readonly Stage[] = [
|
|
|
177
181
|
id: 'consumed-copies',
|
|
178
182
|
label: 'Consumed copies',
|
|
179
183
|
checks: [
|
|
180
|
-
|
|
184
|
+
{
|
|
185
|
+
kind: 'cli',
|
|
186
|
+
argv: ['gov', 'regen'],
|
|
187
|
+
failure: 'Consumed-copy regen failed',
|
|
188
|
+
},
|
|
181
189
|
{
|
|
182
190
|
kind: 'drift',
|
|
183
191
|
pathspec: '.claude/rules',
|