@erclx/canon 4.74.0 → 4.75.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/create-skill/SKILL.md +0 -1
- package/claude/skills/docs-fold/SKILL.md +1 -1
- package/claude/skills/git-followup/SKILL.md +9 -5
- package/claude/skills/git-pr/SKILL.md +21 -0
- package/claude/skills/markdown-propose/REQUIREMENT.md +1 -1
- package/claude/skills/markdown-propose/SKILL.md +9 -7
- package/claude/skills/markdown-propose/references/format.md +5 -3
- package/claude/skills/plan-feature/SKILL.md +1 -1
- package/claude/skills/review-pr/SKILL.md +2 -2
- package/claude/skills/role-orchestrator/scripts/poll.sh +16 -10
- package/claude/skills/teach-workspace/references/lesson-craft.md +1 -6
- package/docs/agents/commands.md +1 -0
- package/docs/agents/index.md +1 -0
- package/docs/agents/pr-evidence.md +108 -0
- package/governance/rules/ui/440-surface-capture.md +1 -0
- package/package.json +1 -1
- package/src/commands/design.ts +10 -2
- package/src/commands/pr.ts +220 -0
- package/src/design/base.css +59 -0
- package/src/design/components.ts +80 -50
- package/src/design/fonts.ts +21 -0
- package/src/pr/evidence.ts +171 -0
- package/src/teach/fonts.ts +9 -11
- package/standards/architecture.md +1 -0
- package/standards/figures.md +51 -0
- package/standards/groundwork.md +1 -0
- package/standards/index.md +1 -0
- package/standards/intake.md +1 -0
- package/standards/skill.md +1 -1
|
@@ -173,7 +173,7 @@ Grep the tree for the name that went, rather than for the paths the diff carries
|
|
|
173
173
|
|
|
174
174
|
Report each hit as an ordinary rewrite.
|
|
175
175
|
|
|
176
|
-
|
|
176
|
+
Create a new entry only for a domain `.claude/context/index.md` already lists but carries no file for, following `${CLAUDE_SKILL_DIR}/../../standards/context.md` for its shape. Before treating a domain as carrying no file, confirm it holds no entry under either spelling, `.claude/context/<domain>.md` or `.claude/context/<domain>/index.md`, since a domain already split into a folder still passes a check that only looked for the flat file. A row in the catalog is the deliberate decision, taken by whoever added it. This step only fills in what that decision left open, and only until the next `canon indexes regen` pass, which rebuilds the catalog from each entry's own frontmatter plus every sibling's and drops a row whose file still does not exist. Create the file before that regen runs, or the row this bar exists to fill in is gone. A domain the catalog does not list at all is a different case: report it and stop, rather than creating an entry or a catalog row for it.
|
|
177
177
|
|
|
178
178
|
Write each updated entry immediately. Output one line per file, naming the path this run actually wrote rather than always the flat template:
|
|
179
179
|
|
|
@@ -29,15 +29,19 @@ A missing tracking ref is no longer a guard. An open pull request proves the bra
|
|
|
29
29
|
|
|
30
30
|
The lease is what stops the force from overwriting a commit this session never read. Run the ancestry test only where an upstream resolves, since it reads `@{u}`.
|
|
31
31
|
|
|
32
|
-
5.
|
|
33
|
-
|
|
32
|
+
5. Post the evidence comparison, on every invocation including `reply-owned`, since this comment is not the reply step 8 owns. Run `canon pr evidence <number> --json`, resolving `<number>` from `gh pr view --json number`, and read `reason` on the record rather than the exit code.
|
|
33
|
+
- `no-evidence`: nothing changed under an `evidence/` segment this push. Say nothing and move on.
|
|
34
|
+
- `ok`: write `body` to `.canon/tmp/pr-evidence/body-<number>.md` at the main worktree root (resolved the way `session-worktree` does), then post the comment with `gh pr comment <number> --body-file <main-root>/.canon/tmp/pr-evidence/body-<number>.md` when the record carries no `commentId`, or edit the existing one in place with `gh api -X PATCH repos/{owner}/{repo}/issues/comments/<commentId> -f body=@<main-root>/.canon/tmp/pr-evidence/body-<number>.md` when it does. Clean up the tmp file only after the call reports success.
|
|
35
|
+
- Any other reason is one of the mirrored git refusals (`gh-missing`, `gh-failed`, `no-base`, `unreadable-tree`, `unreadable-changes`). Report it and continue without stopping the chain.
|
|
36
|
+
6. Check for existing review comments: `gh api 'repos/{owner}/{repo}/pulls/<number>/comments' --jq 'length'`, resolving `<number>` from `gh pr view --json number`.
|
|
37
|
+
7. Sync the body and title on every invocation, before the routing below decides on the reply.
|
|
34
38
|
- Run `gh pr view --json url,title,body` and update the body with `gh pr edit --body` when the new commit changes scope, and the title with `gh pr edit --title` when the scope shifted enough to make it inaccurate.
|
|
35
39
|
- A fix commit answering a review changes what shipped exactly as much as an ordinary followup does, so the sync cannot wait on the invocation or the comment count below.
|
|
36
40
|
- A body a person edited by hand between rounds gets no special handling: judge it against the tree the same way regardless of who wrote it last, since a hand-edit the fix commit has made stale is the exact drift this sync exists to close.
|
|
37
|
-
|
|
41
|
+
8. Route on the invocation and the comment count for the reply alone.
|
|
38
42
|
- When invoked with `reply-owned`, skip this step: the caller posts its own reply.
|
|
39
|
-
- Otherwise, if the count is above zero, the followup addresses review feedback: post a one-line summary of the fix with `gh pr comment --body`, first running the scan in `${CLAUDE_SKILL_DIR}/../../standards/publish.md` against it, since the hook does not see an inline comment body. The `pull_request` check the git-pr surface carries triggers on a push or an open rather than a plain edit, so neither this comment nor the title and body step
|
|
40
|
-
- If it is zero, nothing further runs. The sync in step
|
|
43
|
+
- Otherwise, if the count is above zero, the followup addresses review feedback: post a one-line summary of the fix with `gh pr comment --body`, first running the scan in `${CLAUDE_SKILL_DIR}/../../standards/publish.md` against it, since the hook does not see an inline comment body. The `pull_request` check the git-pr surface carries triggers on a push or an open rather than a plain edit, so neither this comment nor the title and body step 7 synced reaches it.
|
|
44
|
+
- If it is zero, nothing further runs. The sync in step 7 already did this branch's job.
|
|
41
45
|
|
|
42
46
|
## After completion
|
|
43
47
|
|
|
@@ -176,6 +176,27 @@ rmdir <main-root>/.canon/tmp/ui-checklist 2>/dev/null || true
|
|
|
176
176
|
|
|
177
177
|
The `rmdir` is a no-op when another branch's pending checklist still sits in the folder, which keeps this step from deleting a handoff that is not its own.
|
|
178
178
|
|
|
179
|
+
### Post the evidence comparison
|
|
180
|
+
|
|
181
|
+
Run `canon pr evidence <number> --json` against the number the pull request step above resolved. Read `reason` on the record rather than the exit code.
|
|
182
|
+
|
|
183
|
+
- `no-evidence`: nothing changed under an `evidence/` segment. Say nothing and move on.
|
|
184
|
+
- `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:
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
gh pr comment <number> --body-file <main-root>/.canon/tmp/pr-evidence/body-<number>.md
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
When the record carries a `commentId`, edit that comment in place instead of posting a second one, reading the body field from the tmp file with `@`:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
gh api -X PATCH repos/{owner}/{repo}/issues/comments/<commentId> -f body=@<main-root>/.canon/tmp/pr-evidence/body-<number>.md
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Clean up the tmp file the way the UI-checklist step does, only after the call reports success.
|
|
197
|
+
|
|
198
|
+
Any other `reason` is one of the mirrored git refusals (`gh-missing`, `gh-failed`, `no-base`, `unreadable-tree`, `unreadable-changes`). Report it and move on without stopping the chain: a branch that carries no evidence images most of the time should not fail here on a transient git or `gh` read.
|
|
199
|
+
|
|
179
200
|
### Record the number on the task
|
|
180
201
|
|
|
181
202
|
Write the `number` the final command printed onto the task the branch is closing. Do not resolve it again. `${CLAUDE_SKILL_DIR}/REQUIREMENT.md` states why: a lookup that resolves by branch alone can return a closed pull request sharing that head, so the number is resolved once and reused rather than re-derived.
|
|
@@ -21,7 +21,7 @@ A second failure compounds the first. A claim copied across several files is cor
|
|
|
21
21
|
- Grep the named surface for the concern before proposing, so a defect's site count is measured rather than assumed
|
|
22
22
|
- Draft the replacement text, not only the finding
|
|
23
23
|
- Carry three labelled variants on a change whose replacement was invented, and one on a change that corrects text to a recorded fact
|
|
24
|
-
- Write one proposal file per source file under `.canon/proposals/<slug>/` and stop, leaving `You:` empty on every change
|
|
24
|
+
- Write one proposal file per source file under `.canon/proposals/<nn>-<slug>/` and stop, leaving `You:` empty on every change
|
|
25
25
|
- Apply only a change carrying an answer, one file at a time, and re-grep its anchor before applying it
|
|
26
26
|
- Report what each file leaves alone, so a proposal cannot be read as finding everything wanting
|
|
27
27
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: markdown-propose
|
|
3
|
-
description: Reviews a named markdown surface against a named concern, drafts a per-file proposal under `.canon/proposals/<slug>/` carrying a diff and a reason for each change, and stops without editing a source file. Takes the concern and the surface as inputs, such as a claim stated stronger than the record, a fact gone stale, two files disagreeing, or a passage duplicated without derivation. A later invocation applies what the operator answered. Use when asked to "propose a change to CLAUDE.md", "draft a rewrite of this standard", "propose fixes to this doc", "draft alternatives for this passage", or "apply the answered proposals". Do NOT use to report without drafting a replacement (`standards-audit` or `canon markdown audit`), to review a diff already made (`review-branch`), or to file a raw brain dump as findings (`plan-intake`).
|
|
3
|
+
description: Reviews a named markdown surface against a named concern, drafts a per-file proposal under `.canon/proposals/<nn>-<slug>/` carrying a diff and a reason for each change, and stops without editing a source file. Takes the concern and the surface as inputs, such as a claim stated stronger than the record, a fact gone stale, two files disagreeing, or a passage duplicated without derivation. A later invocation applies what the operator answered. Use when asked to "propose a change to CLAUDE.md", "draft a rewrite of this standard", "propose fixes to this doc", "draft alternatives for this passage", or "apply the answered proposals". Do NOT use to report without drafting a replacement (`standards-audit` or `canon markdown audit`), to review a diff already made (`review-branch`), or to file a raw brain dump as findings (`plan-intake`).
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Markdown propose
|
|
@@ -20,7 +20,9 @@ The value is the gate. A rewrite delivered in chat gets applied from memory acro
|
|
|
20
20
|
|
|
21
21
|
Two phases share this body, picked by whether a proposal folder already exists for the request's slug.
|
|
22
22
|
|
|
23
|
-
Derive `<slug>` from the concern and the surface, kebab-case, naming the subject rather than the activity. List `.canon/proposals/` at the main worktree root and match the topic against the
|
|
23
|
+
Derive `<slug>` from the concern and the surface, kebab-case, naming the subject rather than the activity. List `.canon/proposals/` at the main worktree root and match the topic against the slug half of each `<nn>-<slug>` folder already there before deriving a fresh one, the same way `plan-intake` matches its own folder. Never match against `.claude/` itself.
|
|
24
|
+
|
|
25
|
+
With no match, a fresh folder takes the next ordinal in `.canon/proposals/`'s own sequence: list the folders present, take the highest `<nn>`, and increment it, starting at `01` when none exist. That sequence is independent of the one `intake` and `groundwork` share, so a proposals folder never reads or claims from theirs.
|
|
24
26
|
|
|
25
27
|
- No matching folder, or the operator names a concern and a surface: **Propose**.
|
|
26
28
|
- A matching folder exists and the operator says apply, ship, or commit the answers: **Apply**.
|
|
@@ -29,7 +31,7 @@ All `.canon/proposals/` reads and writes resolve at the main worktree root, not
|
|
|
29
31
|
|
|
30
32
|
## Write scope
|
|
31
33
|
|
|
32
|
-
Write only inside `.canon/proposals/<slug>/`. A source file, a standard, a rule, and a plan all live outside that folder, so this one rule forbids every one of them during the Propose phase. The Apply phase is the one exception, and only for a change carrying a `You:` answer.
|
|
34
|
+
Write only inside `.canon/proposals/<nn>-<slug>/`. A source file, a standard, a rule, and a plan all live outside that folder, so this one rule forbids every one of them during the Propose phase. The Apply phase is the one exception, and only for a change carrying a `You:` answer.
|
|
33
35
|
|
|
34
36
|
## Concerns
|
|
35
37
|
|
|
@@ -66,7 +68,7 @@ A matched string means different things in different registers. A word describin
|
|
|
66
68
|
|
|
67
69
|
### 4. Write the proposals
|
|
68
70
|
|
|
69
|
-
One file per source file under `.canon/proposals/<slug>/`, per `${CLAUDE_SKILL_DIR}/references/format.md`.
|
|
71
|
+
One file per source file under `.canon/proposals/<nn>-<slug>/`, per `${CLAUDE_SKILL_DIR}/references/format.md`.
|
|
70
72
|
|
|
71
73
|
Draft the replacement text. A proposal reporting a problem without a replacement hands the work back rather than doing it.
|
|
72
74
|
|
|
@@ -97,20 +99,20 @@ Report the counts once the pass stops: files committed, changes applied, changes
|
|
|
97
99
|
Chat output is the report. This skill persists only the proposal folder itself.
|
|
98
100
|
|
|
99
101
|
```plaintext
|
|
100
|
-
📂 Opened .canon/proposals/<slug>/
|
|
102
|
+
📂 Opened .canon/proposals/<nn>-<slug>/
|
|
101
103
|
|
|
102
104
|
**Screened:** <N> files, <N> changes proposed, <N> carrying three variants
|
|
103
105
|
|
|
104
106
|
**Highest value:** <the single strongest change, one line>
|
|
105
107
|
|
|
106
|
-
Next: answer the `You:` slots in the files under .canon/proposals/<slug>/,
|
|
108
|
+
Next: answer the `You:` slots in the files under .canon/proposals/<nn>-<slug>/,
|
|
107
109
|
then re-invoke this skill to apply what you answered.
|
|
108
110
|
```
|
|
109
111
|
|
|
110
112
|
Use `📂 Resumed` in place of `📂 Opened` on a resume pass. The Apply phase reports instead:
|
|
111
113
|
|
|
112
114
|
```plaintext
|
|
113
|
-
✅ Applied .canon/proposals/<slug>/
|
|
115
|
+
✅ Applied .canon/proposals/<nn>-<slug>/
|
|
114
116
|
|
|
115
117
|
**Committed:** <N> files, <N> changes
|
|
116
118
|
|
|
@@ -4,11 +4,13 @@ Governs the proposal `markdown-propose` writes before it edits anything. The con
|
|
|
4
4
|
|
|
5
5
|
## Folder
|
|
6
6
|
|
|
7
|
-
- One folder per screening pass at `.canon/proposals/<slug>/`, where the slug names the subject rather than the activity
|
|
7
|
+
- One folder per screening pass at `.canon/proposals/<nn>-<slug>/`, where the slug names the subject rather than the activity
|
|
8
8
|
- One proposal file per source file, named `<nn>-<source filename, its own extension dropped>.md`, so a source already named `CLAUDE.md` becomes `01-CLAUDE.md` rather than `01-CLAUDE.md.md`
|
|
9
9
|
- `00-overview.md` when the pass spans more than two source files, holding the cross-file pattern, the change counts, and the settle order
|
|
10
10
|
- `applied.md` once the first change lands, holding every applied change with its reason and the answer it carried
|
|
11
11
|
|
|
12
|
+
The folder's own `<nn>` and a proposal file's own `<nn>` are two independent counters. The folder's ordinal records when the pass was opened, counted within `.canon/proposals/` alone rather than shared with `plan-groundwork` or `plan-intake`'s sequence, and a proposal file's ordinal inside it records settle order, the dependency order changes apply in. Reading one as the other gets a pass dated wrong or an apply ordered wrong.
|
|
13
|
+
|
|
12
14
|
The name carries both structures the folder has. The basename pairs a proposal to its source so a reader opens the two side by side, and the number is the settle order, which is a dependency rather than a preference: a file other files quote is settled first. Do not group proposals by theme.
|
|
13
15
|
|
|
14
16
|
A proposal opened after the initial pass takes the next free number, and the folder is never renumbered. Its number records when the site was found rather than where it belongs in the dependency order. Renumbering to restore the order would rewrite the names of files already carrying answers, which costs more than the ordering is worth once applying has started.
|
|
@@ -95,13 +97,13 @@ The folder is gitignored, and backed wherever a records remote is configured. It
|
|
|
95
97
|
Count the unread changes per file:
|
|
96
98
|
|
|
97
99
|
```bash
|
|
98
|
-
grep -c '^- \*\*You:\*\*$' .canon/proposals/<slug>/*.md
|
|
100
|
+
grep -c '^- \*\*You:\*\*$' .canon/proposals/<nn>-<slug>/*.md
|
|
99
101
|
```
|
|
100
102
|
|
|
101
103
|
Report every answer given, against the change it sits under:
|
|
102
104
|
|
|
103
105
|
```bash
|
|
104
|
-
awk '/^### /{h=FILENAME": "$0} /^- \*\*You:\*\*./{print h; print " "$0}' .canon/proposals/<slug>/*.md
|
|
106
|
+
awk '/^### /{h=FILENAME": "$0} /^- \*\*You:\*\*./{print h; print " "$0}' .canon/proposals/<nn>-<slug>/*.md
|
|
105
107
|
```
|
|
106
108
|
|
|
107
109
|
Both walk `###` headings, which is the mechanical reason an answer typed anywhere else is lost.
|
|
@@ -31,7 +31,7 @@ Coding standards live in `.claude/rules/`. Claude Code loads them automatically.
|
|
|
31
31
|
|
|
32
32
|
## Step 2: scan relevant source files
|
|
33
33
|
|
|
34
|
-
Based on the feature description, identify and read source files that are directly relevant. Do not read entire directories speculatively.
|
|
34
|
+
Based on the feature description, identify and read source files that are directly relevant. Do not read entire directories speculatively. Where a folder carries an `index.md`, read it first and load only the files it points at.
|
|
35
35
|
|
|
36
36
|
Measure against the tree rather than recall. Grep for each construct the plan will name and count the sites, so the plan carries the count the tree holds today. Confirm any work the plan sequences behind is still open, so it does not lead with an item that already shipped.
|
|
37
37
|
|
|
@@ -242,9 +242,9 @@ A minor the dispatched worker declines is what needs a surface that survives the
|
|
|
242
242
|
|
|
243
243
|
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.
|
|
244
244
|
|
|
245
|
-
Both of this skill's headings anchor as a section distinct from human threads, and neither invents beyond what the whole set already states. That set is
|
|
245
|
+
Both of this skill's headings anchor as a section distinct from human threads, and neither invents beyond what the whole set already states. That set is six headings across three families, stated here once so `role-orchestrator`'s poll and every reply-posting skill cite it rather than carry a copy. The review family, `## Review` and `## Review closed`, belongs to this skill alone. The reply family, `## Review response`, `## Rebase`, and `## Post-review findings`, belongs to `review-address`. The third family is a single heading, `## Evidence`, which `canon pr evidence` posts through `git-pr` and `git-followup` and which answers no comment already on the thread, so the poll excludes it from the unclassified count without reading it as a reply that owes a re-review.
|
|
246
246
|
|
|
247
|
-
The first reply heading answers a finding this skill posted, the second reports a stale branch resolved without one, and the third carries a finding a worker produces after a close-out rather than in answer to one already on the thread, since a finding produced late is still a finding. A comment posted under a heading outside these
|
|
247
|
+
The first reply heading answers a finding this skill posted, the second reports a stale branch resolved without one, and the third carries a finding a worker produces after a close-out rather than in answer to one already on the thread, since a finding produced late is still a finding. A comment posted under a heading outside these six reaches the poll as unclassified rather than as silence, so an invented seventh heading is a gap the next run reports instead of one it repeats. Do not append the PR number, which GitHub already renders above the comment.
|
|
248
248
|
|
|
249
249
|
Name the scope in every summary line after the first pass, since a reader cannot otherwise tell a narrow read from a full one. When the fallback in Step 2 fired, replace the commit count with `Re-reviewed the full change, the prior pass's commit is no longer on the branch`.
|
|
250
250
|
|
|
@@ -32,19 +32,25 @@ if [ -z "$BASE_REF" ]; then
|
|
|
32
32
|
fi
|
|
33
33
|
BASE_BRANCH="${BASE_REF#origin/}"
|
|
34
34
|
|
|
35
|
-
# These
|
|
36
|
-
# writes `## Review` and `## Review closed`, and states the full
|
|
35
|
+
# These six strings are owned elsewhere and pinned here. `review-pr`
|
|
36
|
+
# writes `## Review` and `## Review closed`, and states the full six-heading
|
|
37
37
|
# set once, beside the threshold it already states once. `review-address`
|
|
38
38
|
# writes `## Review response`, `## Rebase`, and `## Post-review findings`, the
|
|
39
39
|
# last for a finding a worker produces after a close-out rather than in answer
|
|
40
|
-
# to one already on the thread.
|
|
41
|
-
#
|
|
40
|
+
# to one already on the thread. `canon pr evidence`, run through `git-pr` and
|
|
41
|
+
# `git-followup`, writes `## Evidence`. All these surfaces ship separately, so
|
|
42
|
+
# a heading added in any of them breaks a test here that no check reaches
|
|
43
|
+
# across.
|
|
42
44
|
#
|
|
43
|
-
#
|
|
44
|
-
# reply family carries `## Rebase` and `## Post-review
|
|
45
|
-
# `## Review response` because neither answers a comment
|
|
46
|
-
# which is why both were kept outside the `## Review`
|
|
47
|
-
# into it.
|
|
45
|
+
# All three families match on the first line alone so the tests stay
|
|
46
|
+
# symmetric. The reply family carries `## Rebase` and `## Post-review
|
|
47
|
+
# findings` beside `## Review response` because neither answers a comment
|
|
48
|
+
# already on the thread, which is why both were kept outside the `## Review`
|
|
49
|
+
# family rather than folded into it. `## Evidence` answers no comment either,
|
|
50
|
+
# and it stays out of the reply family rather than joining it: an evidence
|
|
51
|
+
# comment is not a response to a review, so counting it as one would send the
|
|
52
|
+
# poll back for a re-review nothing asked for. It is excluded from the
|
|
53
|
+
# unmatched filter below instead, on its own.
|
|
48
54
|
#
|
|
49
55
|
# The review family reaches this file through `canon pr review-state` rather
|
|
50
56
|
# than through a filter of its own, so the two headings are pinned here only in
|
|
@@ -81,7 +87,7 @@ JQ_UNMATCHED_STATE='
|
|
|
81
87
|
| select(startswith("## "))
|
|
82
88
|
| select(. != "## Review" and . != "## Review closed"
|
|
83
89
|
and . != "## Review response" and . != "## Rebase"
|
|
84
|
-
and . != "## Post-review findings")
|
|
90
|
+
and . != "## Post-review findings" and . != "## Evidence")
|
|
85
91
|
] as $unclassified
|
|
86
92
|
| ($unclassified | length | tostring)
|
|
87
93
|
+ " "
|
|
@@ -35,12 +35,7 @@ Every element competes with the material for attention.
|
|
|
35
35
|
|
|
36
36
|
## Diagrams
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
- Wrap it in a `<figure>` holding an inline `<svg>` and a `<figcaption>` that names what to take from the diagram rather than what it shows. Let the figure run wider than the prose column, since a diagram cramped to the reading measure loses the labels it needs.
|
|
41
|
-
- Draw it by hand, as plain shapes and lines authored directly in the markup, not exported from a diagramming tool.
|
|
42
|
-
- Color every fill and stroke through a custom property the workspace's own stylesheet defines, never a literal hex value, which is the same rule that stylesheet's opening comment already states for every rule added under it. The diagram then re-colors itself on the same switch that re-colors the page.
|
|
43
|
-
- Give the `<svg>` an accessible name: `role="img"` with `aria-label` for one short line, or `aria-labelledby` pointing at a `<title>` element inside it for a longer one.
|
|
38
|
+
Follow `${CLAUDE_SKILL_DIR}/../../standards/figures.md` for when a diagram earns its place, the render-first policy between Mermaid and freehand SVG, and the wrapping, color, and accessibility rules every figure carries. A lesson reaching for a diagram on every section is the failure the neighboring `## Restraint` section already guards against.
|
|
44
39
|
|
|
45
40
|
## Quiz construction
|
|
46
41
|
|
package/docs/agents/commands.md
CHANGED
|
@@ -84,6 +84,7 @@ Full help: `canon <command> --help`. Behavior notes for the install and sync ver
|
|
|
84
84
|
| `canon pr head` | Compare the head a pull request object reports against the branch tip the remote carries, naming both shas (`--root`, `--json`) |
|
|
85
85
|
| `canon pr checks` | Report the check runs belonging to the branch tip, reading pending for a tip carrying none rather than clean (`--root`, `--json`) |
|
|
86
86
|
| `canon pr review-state` | Report the commit and instant the last review pass covered, off the pass's own marker rather than the fields GitHub stamps at submission (`--root`, `--json`) |
|
|
87
|
+
| `canon pr evidence` | Render a before-and-after comment comparing changed `evidence/` images against the merge base, editing its own prior comment in place rather than posting a second (`--root`, `--json`) |
|
|
87
88
|
| `canon repo metadata propose` | Compare a description, homepage, and topic set computed from the README and `package.json` against what the remote carries, writing nothing (`--root`, `--json`) |
|
|
88
89
|
| `canon repo metadata apply` | Write an explicitly supplied description, homepage, or topic set to the remote through `gh repo edit` (`--description`, `--homepage`, `--topics`, `--root`, `--json`) |
|
|
89
90
|
| `canon census [path]` | Report tracked file count, a breakdown by extension, and a line total that skips whatever reads as binary (`--json`) |
|
package/docs/agents/index.md
CHANGED
|
@@ -28,6 +28,7 @@ CLI catalog and invocation rules for agents, split by command domain. Start with
|
|
|
28
28
|
- [Markdown audit](markdown-audit.md): Running the audit over any markdown path, where its bans and checkpoints are read from, what each check reports, and why the ban half gates while the structural half reports
|
|
29
29
|
- [Output shape](output-shape.md): Two framed shapes every command renders into, how JSON and --names modes keep stdout clean, and the exit discipline that lets piped output drain
|
|
30
30
|
- [Overview](overview.md): What this folder covers, the invocation rules every command inherits, and where domain behavior is documented instead
|
|
31
|
+
- [The pull request evidence comparison](pr-evidence.md): What canon pr evidence compares, the marker that lets it edit its own comment rather than duplicate it, the refusal reasons it names, and why the comparison anchors at the merge base rather than the previous push
|
|
31
32
|
- [Head-sensitive pull request reads](pr-reads.md): Resolving a branch tip from the remote rather than from the pull request object, reading what a review pass covered off its own marker rather than off GitHub's submission stamps, why an empty run list is not a pass, the refusal reasons each verb names, and what the remote read costs
|
|
32
33
|
- [Records](records.md): The two roots a record folder resolves at, validating the session records and the standards corpus, the per-kind checks, the refusal reasons, migrating a record a frontmatter change orphaned, claiming the ordinal intake and groundwork share, reading each folder's size and growth, backing the folders to a private remote, and which root each kind defaults to
|
|
33
34
|
- [Restated instructions](restated.md): Counting the instructions the always-loaded file and every path-scoped rule share with the seed, the shipped skill bodies, and each other, how a match is decided, the three classes, which surface a later edit starts from, and why the sweep reports rather than gates
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: The pull request evidence comparison
|
|
3
|
+
description: What canon pr evidence compares, the marker that lets it edit its own comment rather than duplicate it, the refusal reasons it names, and why the comparison anchors at the merge base rather than the previous push
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# The pull request evidence comparison
|
|
7
|
+
|
|
8
|
+
`canon pr evidence` renders one comment naming every changed image under an
|
|
9
|
+
`evidence/` path segment, comparing each against the pull request's merge base
|
|
10
|
+
with the trunk. `git-pr` posts it when a pull request opens or is edited, and
|
|
11
|
+
`git-followup` posts it again after every later push, so a reviewer never has
|
|
12
|
+
to open Files Changed to see what a case looked like before and after.
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
canon pr evidence
|
|
16
|
+
canon pr evidence 1341 --json
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Why the comparison anchors at the merge base
|
|
20
|
+
|
|
21
|
+
The comparison is always base against the current head, never the previous
|
|
22
|
+
push against the new one. A follow-up only ever moves the after side of the
|
|
23
|
+
comment, so what the comment claims can only grow to match what the branch
|
|
24
|
+
actually shows, and a reviewer who opens it midway through a review round
|
|
25
|
+
never reads a stale before image describing an intermediate commit nobody is
|
|
26
|
+
looking at anymore.
|
|
27
|
+
|
|
28
|
+
## What counts as evidence
|
|
29
|
+
|
|
30
|
+
A changed path qualifies when one of its segments is literally `evidence` and
|
|
31
|
+
its filename carries an image extension (`png`, `jpg`, `jpeg`, `gif`, `webp`,
|
|
32
|
+
`avif`, `svg`). The second half of that test exists because an `evidence/`
|
|
33
|
+
folder holds whatever else a project keeps beside its captures. This
|
|
34
|
+
repository's own tree carries eight `.md` files, two `.sh` scripts, a `.tsv`,
|
|
35
|
+
a `.json`, and an `.html` file against a single `.png`, and every one of them
|
|
36
|
+
would render as a broken `![]()` embed without the extension filter.
|
|
37
|
+
|
|
38
|
+
## What the record carries
|
|
39
|
+
|
|
40
|
+
The record groups every evidence path by state, the remainder of its
|
|
41
|
+
directory under the `evidence/` segment, and within a state by filename stem,
|
|
42
|
+
so a `before/hero.png` and an `after/hero.png` naming the same case group as
|
|
43
|
+
one entry rather than two unrelated files. Each entry carries whether it
|
|
44
|
+
existed at the base commit, which decides whether the comment shows a base
|
|
45
|
+
image or marks the case new.
|
|
46
|
+
|
|
47
|
+
`reason` on the record is what a caller branches on, not the exit code:
|
|
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` | Nothing in the diff carries an `evidence/` segment. An ordinary silent no-op. |
|
|
53
|
+
| `gh-missing` | `gh` is not on the path, so no pull request could be resolved. |
|
|
54
|
+
| `gh-failed` | `gh` could not answer for this repository or branch. |
|
|
55
|
+
| `no-branch` | The pull request carries no head branch name. |
|
|
56
|
+
| `no-object-head` | The pull request object reported no head commit. |
|
|
57
|
+
| `no-base` | No base resolves against the trunk. |
|
|
58
|
+
| `unreadable-tree` | git could not read the tree at the base commit. |
|
|
59
|
+
| `unreadable-changes` | git could not list what this branch changed. |
|
|
60
|
+
|
|
61
|
+
`no-evidence` is not a refusal a caller reports. A project on a stack that
|
|
62
|
+
carries no evidence path, such as `base` or `python`, hits this reason on
|
|
63
|
+
every pull request and posts nothing, which is the correct behavior rather
|
|
64
|
+
than a gap.
|
|
65
|
+
|
|
66
|
+
## What `[number]` selects, and what it does not
|
|
67
|
+
|
|
68
|
+
Naming a number picks which pull request the rendered body claims to
|
|
69
|
+
describe: its head commit and, when one already exists, the marked comment to
|
|
70
|
+
edit in place. The base and the changed set are always read from the local
|
|
71
|
+
checkout's own history, `git diff` and `git ls-tree` against the merge base
|
|
72
|
+
with the trunk, rather than fetched for the named pull request over the API.
|
|
73
|
+
That is correct for `git-pr` and `git-followup`, which never pass a number
|
|
74
|
+
and always run from the worktree building the branch, so the local checkout
|
|
75
|
+
and the named pull request describe the same branch. Naming a number for a
|
|
76
|
+
pull request built somewhere else compares this checkout's own diff against a
|
|
77
|
+
head commit that describes a different one.
|
|
78
|
+
|
|
79
|
+
## One comment, found by its own marker
|
|
80
|
+
|
|
81
|
+
Every body this verb renders ends with a trailing marker naming the head it
|
|
82
|
+
describes:
|
|
83
|
+
|
|
84
|
+
```markdown
|
|
85
|
+
<!-- pr-evidence: head=<sha> -->
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`canon pr evidence` reads that marker back off every comment on the pull
|
|
89
|
+
request, the same way `canon pr review-state` reads its own marker off a
|
|
90
|
+
review body, and reports the REST id of whichever comment carries it as
|
|
91
|
+
`commentId`. A caller with no `commentId` posts a new comment. A caller
|
|
92
|
+
holding one edits that comment in place with a `PATCH` rather than posting a
|
|
93
|
+
second one.
|
|
94
|
+
|
|
95
|
+
Putting the lookup here, in the one place both `git-pr` and `git-followup`
|
|
96
|
+
call, is what keeps two close-out comments from landing beside each other the
|
|
97
|
+
way `review-pr` once posted, before that skill's own guard existed. It is
|
|
98
|
+
also why this verb renders the whole comment body rather than handing each
|
|
99
|
+
skill a record to format on its own: a fix to the table shape or the
|
|
100
|
+
collapsed-details wrapper lands once, not twice.
|
|
101
|
+
|
|
102
|
+
## What a collapsed comment still leaves to GitHub
|
|
103
|
+
|
|
104
|
+
GitHub already draws its own before-and-after comparison on the Files Changed
|
|
105
|
+
tab for any tracked image that changed. This comment does not make that view
|
|
106
|
+
redundant. The diff view shows what moved between two commits a reader has
|
|
107
|
+
already opened, and the comment is what lets a reader see the same comparison
|
|
108
|
+
without opening it at all.
|
|
@@ -37,3 +37,4 @@ paths:
|
|
|
37
37
|
- A project whose own established convention already commits that same comparison may route a flagged case's evidence there instead of a separate evidence folder. Only an established convention already committing the same comparison qualifies, not a preference for skipping a second folder.
|
|
38
38
|
- Commit an evidence case for the first time only after running the capture twice with no code change between the runs and confirming the two outputs are byte-identical.
|
|
39
39
|
- Recommitting an unbounded sweep on every run reaches a gigabyte of repository history inside a hundred merges. A small, committed evidence set is what a reviewer needs and what lets GitHub draw its own before-and-after comparison on the pull request.
|
|
40
|
+
- `git-pr` and `git-followup` maintain a comparison comment on the pull request automatically once a case is committed, so a reviewer never has to open Files Changed to see it.
|
package/package.json
CHANGED
package/src/commands/design.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
DESIGN_PROJECT_SUBDIR,
|
|
8
8
|
} from '@/design/adapter'
|
|
9
9
|
import { buildDesignCss } from '@/design/css'
|
|
10
|
+
import { HAND_DRAWN_FONT_FACES } from '@/design/fonts'
|
|
10
11
|
import { renderDesignDoc } from '@/design/render'
|
|
11
12
|
import { DESIGN_BASE_CSS, DESIGN_DOCUMENT, regenDesign } from '@/design/regen'
|
|
12
13
|
import { checkoutMismatchWarning, PROJECT_ROOT } from '@/project-root'
|
|
@@ -80,9 +81,16 @@ export function register(program: Command): void {
|
|
|
80
81
|
'--no-components',
|
|
81
82
|
'Custom properties only, without the component rules',
|
|
82
83
|
)
|
|
83
|
-
.
|
|
84
|
+
.option(
|
|
85
|
+
'--figures',
|
|
86
|
+
'Embed the hand-drawn figure fonts (Virgil, Excalifont), which the unconditional component styling never carries on its own',
|
|
87
|
+
)
|
|
88
|
+
.action((opts: { components: boolean; figures?: boolean }) => {
|
|
84
89
|
process.stdout.write(
|
|
85
|
-
buildDesignCss(undefined, {
|
|
90
|
+
buildDesignCss(undefined, {
|
|
91
|
+
components: opts.components,
|
|
92
|
+
embedFonts: opts.figures ? HAND_DRAWN_FONT_FACES : undefined,
|
|
93
|
+
}),
|
|
86
94
|
)
|
|
87
95
|
})
|
|
88
96
|
|