@erclx/canon 5.24.0 → 5.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/backlog-triage/REQUIREMENT.md +2 -2
- package/claude/skills/backlog-triage/SKILL.md +19 -6
- package/claude/skills/canvas/REQUIREMENT.md +49 -0
- package/claude/skills/canvas/SKILL.md +106 -0
- package/claude/skills/context-fold/REQUIREMENT.md +6 -2
- package/claude/skills/context-fold/SKILL.md +8 -6
- package/claude/skills/context-fold/references/anchor-sweep.md +2 -2
- package/claude/skills/design-extract/REQUIREMENT.md +1 -1
- package/claude/skills/design-taste/REQUIREMENT.md +1 -1
- package/claude/skills/design-taste/SKILL.md +4 -2
- package/claude/skills/design-taste/references/adopted.md +40 -0
- package/claude/skills/design-taste/references/kinds.md +1 -1
- package/claude/skills/design-taste/references/systems.md +1 -7
- package/claude/skills/design-taste/references/tells.md +2 -24
- package/claude/skills/docs-sync/REQUIREMENT.md +2 -1
- package/claude/skills/document-health/REQUIREMENT.md +2 -0
- package/claude/skills/document-health/SKILL.md +21 -0
- package/claude/skills/document-health/references/axes.md +12 -0
- package/claude/skills/draft-and-pick/SKILL.md +1 -1
- package/claude/skills/draft-doc/REQUIREMENT.md +1 -1
- package/claude/skills/draft-figure/REQUIREMENT.md +14 -3
- package/claude/skills/draft-figure/SKILL.md +24 -51
- package/claude/skills/draft-figure/references/architecture-set.md +35 -0
- package/claude/skills/draft-figure/references/hand-drawn.md +31 -0
- package/claude/skills/draft-figure/references/mermaid.md +55 -0
- package/claude/skills/draft-identity/SKILL.md +1 -1
- package/claude/skills/draft-screencast/SKILL.md +9 -7
- package/claude/skills/draft-slides/REQUIREMENT.md +17 -12
- package/claude/skills/draft-slides/SKILL.md +31 -20
- package/claude/skills/git-followup/SKILL.md +17 -6
- package/claude/skills/git-issue/references/issue.md +10 -0
- package/claude/skills/git-pr/references/evidence.md +12 -2
- package/claude/skills/git-pr/references/labels.md +12 -0
- package/claude/skills/git-split/SKILL.md +19 -8
- package/claude/skills/index-lookup/SKILL.md +2 -2
- package/claude/skills/markdown-propose/references/format.md +9 -0
- package/claude/skills/memory-review/references/later-phases.md +7 -0
- package/claude/skills/review-address/SKILL.md +6 -13
- package/claude/skills/review-address/references/rest-reads.md +43 -0
- package/claude/skills/review-craft/references/ui.md +7 -1
- package/claude/skills/review-pr/REQUIREMENT.md +1 -0
- package/claude/skills/review-pr/SKILL.md +9 -3
- package/claude/skills/review-pr/references/close-out.md +2 -2
- package/claude/skills/review-pr/references/post.md +17 -5
- package/claude/skills/review-pr/references/scope.md +10 -8
- package/claude/skills/review-ui/SKILL.md +12 -4
- package/claude/skills/review-ui/references/post.md +4 -2
- package/claude/skills/role-orchestrator/references/orchestrator-cloud-launch.md +38 -1
- package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +13 -0
- package/claude/skills/role-orchestrator/references/orchestrator-launch.md +4 -0
- package/claude/skills/role-orchestrator/references/orchestrator-poll.md +4 -4
- package/claude/skills/role-orchestrator/references/orchestrator-review-dispatch.md +6 -0
- package/claude/skills/role-orchestrator/scripts/poll.ts +90 -27
- package/claude/skills/role-orchestrator/scripts/repo.ts +3 -5
- package/claude/skills/role-orchestrator/scripts/watch.ts +4 -7
- package/claude/skills/search-craft/REQUIREMENT.md +1 -1
- package/claude/skills/search-craft/SKILL.md +3 -1
- package/claude/skills/search-craft/references/adopted.md +30 -0
- package/claude/skills/target-setup/references/detect.md +8 -0
- package/claude/skills/target-setup/references/indexes.md +13 -0
- package/claude/skills/target-setup/references/verify.md +8 -0
- package/claude/skills/test-first/SKILL.md +2 -0
- package/claude/skills/test-first/references/adopted.md +36 -0
- package/claude/skills/write-human/SKILL.md +1 -1
- package/claude/skills/write-human/references/machine-tells.md +13 -0
- package/docs/agents/canvas.md +4 -2
- package/docs/agents/capture.md +1 -1
- package/docs/agents/commands.md +47 -47
- package/docs/agents/context-audit-checks.md +2 -2
- package/docs/agents/index.md +2 -3
- package/docs/agents/key-changes.md +3 -3
- package/docs/agents/pr-evidence.md +11 -2
- package/docs/agents/pr-reads.md +5 -4
- package/docs/agents/records-stale.md +22 -9
- package/docs/agents/records.md +1 -1
- package/docs/agents/sandbox.md +1 -1
- package/docs/agents/skills-audit.md +10 -3
- package/docs/agents/slides.md +15 -3
- package/docs/target/projects.md +4 -4
- package/docs/workflow/ai-workflow.md +1 -2
- package/docs/workflow/index.md +1 -0
- package/docs/workflow/parallel-features.md +1 -1
- package/docs/workflow/skill-map-on-request.md +42 -0
- package/docs/workflow/skill-map.md +3 -36
- package/docs/workflow/visual-design-workflow.md +1 -0
- package/governance/renames.toml +0 -4
- package/governance/rules/standards/markdown.md +1 -0
- package/package.json +1 -2
- package/scripts/core/install-check.sh +1 -1
- package/scripts/lib/sandbox-dispatch.sh +1 -1
- package/scripts/lib/sandbox-git.sh +1 -9
- package/scripts/lib/sandbox-path.sh +1 -1
- package/scripts/sandbox-hook.sh +99 -0
- package/src/audits/catalog.ts +2 -2
- package/src/canvas/capture.ts +1 -9
- package/src/canvas/client/inspector/color-field.tsx +368 -0
- package/src/canvas/client/inspector/field.tsx +200 -0
- package/src/canvas/client/inspector/fields.css +292 -0
- package/src/canvas/client/inspector/section.tsx +27 -0
- package/src/canvas/client/inspector/values.ts +183 -0
- package/src/canvas/client/inspector.tsx +176 -160
- package/src/canvas/client/shell.css +0 -101
- package/src/capture/render.ts +45 -7
- package/src/capture/sources.ts +43 -0
- package/src/census/count.ts +1 -1
- package/src/claude/cases/misc.ts +5 -0
- package/src/claude/cases/workflow.ts +6 -1
- package/src/claude/seeds.ts +1 -2
- package/src/claude/skills-audit.ts +68 -14
- package/src/client-commands.ts +1 -1
- package/src/commands/claude.ts +27 -2
- package/src/commands/design.ts +2 -64
- package/src/commands/feedback.ts +3 -3
- package/src/commands/pr.ts +219 -202
- package/src/commands/records.ts +88 -6
- package/src/commands/sandbox.ts +50 -10
- package/src/commands/slides.ts +31 -77
- package/src/context/audit.ts +8 -7
- package/src/design/tokens.ts +2 -18
- package/src/docs/read.ts +1 -1
- package/src/{github.ts → feedback/github.ts} +1 -1
- package/src/gate/measures.ts +6 -4
- package/src/gov/citations.ts +1 -1
- package/src/gov/payload.ts +1 -1
- package/src/gov/superseded.ts +2 -2
- package/src/help.ts +2 -2
- package/src/markdown/ceiling.ts +1 -1
- package/src/migrate/skill-names.ts +7 -1
- package/src/pr/evidence.ts +25 -5
- package/src/pr/rest.ts +137 -0
- package/src/record-root.ts +1 -0
- package/src/records/backup.ts +1 -0
- package/src/records/push-guard.ts +1 -1
- package/src/records/stale-canonical.ts +146 -0
- package/src/records/stale.ts +1 -1
- package/src/sandbox/hooks.ts +128 -0
- package/src/sandbox/provision.ts +596 -0
- package/src/sandbox/tree.ts +103 -4
- package/src/secrets/marker.ts +2 -2
- package/src/secrets/scan.ts +1 -1
- package/src/shipped/references.ts +2 -2
- package/src/slides/convert/export.ts +2 -16
- package/src/slides/decks.ts +110 -0
- package/src/standards/read.ts +1 -1
- package/src/sync/seeds-report.ts +1 -1
- package/src/tasks/answers.ts +1 -1
- package/src/tasks/archive.ts +1 -1
- package/src/teach/nav.ts +1 -1
- package/src/tooling/inject.ts +1 -1
- package/src/web/readme-citations.ts +1 -1
- package/standards/architecture.md +1 -0
- package/standards/canvas.md +112 -0
- package/standards/context.md +2 -1
- package/standards/figures.md +0 -1
- package/standards/index.md +1 -1
- package/standards/markdown.md +1 -0
- package/standards/mermaid.md +1 -7
- package/standards/requirements.md +2 -0
- package/standards/skill-practice.md +2 -1
- package/standards/skill.md +1 -0
- package/tooling/claude/reference.md +0 -5
- package/claude/skills/draft-diagram/REQUIREMENT.md +0 -45
- package/claude/skills/draft-diagram/SKILL.md +0 -177
- package/docs/agents/design-board.md +0 -33
- package/governance/rules/standards/diagrams.md +0 -14
- package/governance/rules/standards/mermaid.md +0 -15
- package/scripts/core/check-gallery-exclusion.sh +0 -50
- package/scripts/manage-sandbox.sh +0 -472
- package/src/design/board.ts +0 -576
- package/src/slides/layouts.ts +0 -478
- package/src/slides/parse.ts +0 -84
- package/src/slides/render.ts +0 -110
- package/src/slides/styles.ts +0 -96
- package/standards/diagrams.md +0 -132
- package/tooling/claude/seeds/.claude/diagrams/index.md +0 -8
- /package/claude/skills/write-human/references/{source-material.md → adopted.md} +0 -0
- /package/src/{commands/feedback-format.ts → feedback/body.ts} +0 -0
- /package/src/{github-format.ts → feedback/github-format.ts} +0 -0
- /package/src/{frontmatter.ts → markdown/frontmatter.ts} +0 -0
- /package/src/{binary.ts → sweep/binary.ts} +0 -0
- /package/src/{exempt-marker.ts → sweep/exempt-marker.ts} +0 -0
- /package/src/{paths.ts → tasks/paths.ts} +0 -0
- /package/src/{seed-marker.ts → tooling/seed-marker.ts} +0 -0
|
@@ -14,7 +14,16 @@ its own reply, so skip the comment in step 7. The push and body sync still run.
|
|
|
14
14
|
|
|
15
15
|
- If `git branch --show-current` returns `main`, stop: `❌ On main. Switch to a PR branch first.`
|
|
16
16
|
- If `git status --porcelain` is empty, stop: `❌ No changes to ship.`
|
|
17
|
-
-
|
|
17
|
+
- Resolve `<number>` once, here, through `canon pr head --json`, reading the record's `number`. Every later step names this `<number>` rather than resolving it again. A record refusing with `ambiguous-pull` stops: `❌ Two open PRs on this branch. Name the one to update.`
|
|
18
|
+
- When no record comes back, or it carries no `number`, read the open pull requests on the branch over REST instead. The branch is encoded so a `/` in its name reaches the query as `%2F`:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
gh api "repos/{owner}/{repo}/pulls?head={owner}:$(git branch --show-current | jq -Rr @uri)&state=open" --jq '.[].number'
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Nothing printed stops: `❌ No open PR for this branch. Use git-ship to open one.` More than one number takes the `ambiguous-pull` stop above rather than the first, since two open pull requests on one head against different bases leave nothing to say which one this push updates.
|
|
25
|
+
|
|
26
|
+
Every read and write in this skill runs on REST, since the `gh pr` subcommands run on GraphQL and a cloud session's GitHub proxy refuses it. A cloud worker answering a review reaches this skill through `review-address`, so one GraphQL call here breaks that whole leg.
|
|
18
27
|
|
|
19
28
|
A missing tracking ref is no longer a guard. An open pull request proves the branch reached the remote, and a branch created by worktree entry has been measured carrying an open pull request with no tracking ref, where the old guard reported that the branch still needed pushing. That message describes a state the branch is not in, so the run stopped on a diagnosis nobody could act on. Step 4 sets the ref instead.
|
|
20
29
|
|
|
@@ -29,21 +38,21 @@ A missing tracking ref is no longer a guard. An open pull request proves the bra
|
|
|
29
38
|
|
|
30
39
|
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
40
|
|
|
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
|
|
41
|
+
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` and read `reason` on the record rather than the exit code.
|
|
33
42
|
- `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
|
|
43
|
+
- `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 api -X POST repos/{owner}/{repo}/issues/<number>/comments -F body=@<main-root>/.canon/tmp/pr/evidence/body-<number>.md --silent` 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
44
|
- Any other reason is one of the mirrored git refusals (`gh-missing`, `gh-failed`, `no-base`, `unreadable-changes`, `would-empty`). Report it and continue without stopping the chain.
|
|
36
45
|
|
|
37
46
|
After an `ok` post, read what the pull request is still owed with `canon pr evidence <number> --check --json`, branching on `reason` and `owed` rather than the exit. When `owed` lists `preview`, mint it through the preview section of `canon:git-pr`'s `references/evidence.md`, then re-read once. Report anything but `settled` after that re-read and continue, since a refused deploy would otherwise loop. A binary lacking `--check` reports the thread unread rather than settled. A preview already on the comment counts as settled whatever head built it, so a later push does not re-mint one.
|
|
38
47
|
|
|
39
|
-
6. Check for existing review comments: `gh api 'repos/{owner}/{repo}/pulls/<number>/comments' --jq 'length'
|
|
48
|
+
6. Check for existing review comments: `gh api 'repos/{owner}/{repo}/pulls/<number>/comments' --jq 'length'`.
|
|
40
49
|
7. Sync the body and title on every invocation, before the routing below decides on the reply.
|
|
41
|
-
-
|
|
50
|
+
- Read the live title and body with `gh api repos/{owner}/{repo}/pulls/<number> --jq '{html_url, title, body}'`. Update the body with `gh api -X PATCH repos/{owner}/{repo}/pulls/<number> -F body=@<file> --silent` when the new commit changes scope, writing the new body to `<file>` first, and the title with `gh api -X PATCH repos/{owner}/{repo}/pulls/<number> -f title="<title>" --silent` when the scope shifted enough to make it inaccurate.
|
|
42
51
|
- 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.
|
|
43
52
|
- 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.
|
|
44
53
|
8. Route on the invocation and the comment count for the reply alone.
|
|
45
54
|
- When invoked with `reply-owned`, skip this step: the caller posts its own reply.
|
|
46
|
-
- Otherwise, if the count is above zero, the followup addresses review feedback: post a one-line summary of the fix with `gh
|
|
55
|
+
- Otherwise, if the count is above zero, the followup addresses review feedback: post a one-line summary of the fix with `gh api -X POST repos/{owner}/{repo}/issues/<number>/comments -f body="<summary>" --silent`, 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.
|
|
47
56
|
- If it is zero, nothing further runs. The sync in step 7 already did this branch's job.
|
|
48
57
|
|
|
49
58
|
## After completion
|
|
@@ -53,3 +62,5 @@ Output one line:
|
|
|
53
62
|
```plaintext
|
|
54
63
|
✅ Followup shipped: <pr-url>
|
|
55
64
|
```
|
|
65
|
+
|
|
66
|
+
Take `<pr-url>` off the `html_url` step 7 read.
|
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Issue reference
|
|
2
2
|
|
|
3
|
+
## Contents
|
|
4
|
+
|
|
5
|
+
- [Scope](#scope)
|
|
6
|
+
- [Title](#title)
|
|
7
|
+
- [Labels](#labels)
|
|
8
|
+
- [Content](#content)
|
|
9
|
+
- [Sections](#sections)
|
|
10
|
+
- [Formatting](#formatting)
|
|
11
|
+
- [Examples](#examples)
|
|
12
|
+
|
|
3
13
|
## Scope
|
|
4
14
|
|
|
5
15
|
Governs a tracker issue: its title, its labels, and the sections its body carries.
|
|
@@ -7,6 +7,14 @@ description: How git-pr finds the UI checklist and the local server, posts the e
|
|
|
7
7
|
|
|
8
8
|
The rendered-surface step of `git-pr`, reached after the final command printed the pull request number. The step runs on every pull request, with no test on the diff ahead of it. `no-evidence` is the silent branch, so a pull request that changes no rendered surface posts nothing.
|
|
9
9
|
|
|
10
|
+
## Contents
|
|
11
|
+
|
|
12
|
+
- [Find the UI checklist](#find-the-ui-checklist)
|
|
13
|
+
- [Find the local server](#find-the-local-server)
|
|
14
|
+
- [Post the evidence comparison](#post-the-evidence-comparison)
|
|
15
|
+
- [Post the UI checklist alone](#post-the-ui-checklist-alone)
|
|
16
|
+
- [Post the preview address](#post-the-preview-address)
|
|
17
|
+
|
|
10
18
|
## Find the UI checklist
|
|
11
19
|
|
|
12
20
|
`ui-checklist` writes a visual checklist to `.canon/tmp/handoff/ui-checklist/<slug>.md` at the main worktree root when a change needs visual verification, with `<slug>` derived per `${CLAUDE_SKILL_DIR}/../../standards/slug.md`. This skill is the file's sole consumer. Resolve the main root the way `session-worktree` does (`git worktree list --porcelain | grep -m 1 '^worktree ' | cut -d' ' -f2-`, falling back to `pwd`) and check for the file there. A missing file means no checklist was produced, and the two steps below each skip their checklist half.
|
|
@@ -53,9 +61,11 @@ Read `reason` on the record rather than the exit code.
|
|
|
53
61
|
- `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
62
|
|
|
55
63
|
```bash
|
|
56
|
-
gh
|
|
64
|
+
gh api -X POST repos/{owner}/{repo}/issues/<number>/comments -F body=@<main-root>/.canon/tmp/pr/evidence/body-<number>.md --silent
|
|
57
65
|
```
|
|
58
66
|
|
|
67
|
+
Both posts in this file go through the REST issue-comments endpoint, since the `gh pr` subcommands run on GraphQL and a cloud session's GitHub proxy refuses it. `git-followup` mints a preview through this file, so a GraphQL post here would break the cloud review-address leg.
|
|
68
|
+
|
|
59
69
|
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 `@`, which needs the typed-field flag `-F` because the raw-string flag `-f` posts the path itself as the body:
|
|
60
70
|
|
|
61
71
|
```bash
|
|
@@ -71,7 +81,7 @@ Any other `reason` is one of the mirrored git refusals (`gh-missing`, `gh-failed
|
|
|
71
81
|
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
82
|
|
|
73
83
|
```bash
|
|
74
|
-
gh
|
|
84
|
+
gh api -X POST repos/{owner}/{repo}/issues/<number>/comments -F body=@<main-root>/.canon/tmp/handoff/ui-checklist/<slug>.md --silent
|
|
75
85
|
```
|
|
76
86
|
|
|
77
87
|
Run the cleanup below only once the call that carried the checklist reports success, whichever of the two steps that was. On a failure, stop and leave the file in place: a retry needs the checklist to still be there, and deleting it on a failed post loses the only copy with nothing landed on the pull request.
|
|
@@ -5,6 +5,18 @@ description: Path-to-label map format, prefix matching against the changed set,
|
|
|
5
5
|
|
|
6
6
|
# Label reference
|
|
7
7
|
|
|
8
|
+
## Contents
|
|
9
|
+
|
|
10
|
+
- [Scope](#scope)
|
|
11
|
+
- [Map format](#map-format)
|
|
12
|
+
- [Paths a map declines to label](#paths-a-map-declines-to-label)
|
|
13
|
+
- [Reporting a surface no row reaches](#reporting-a-surface-no-row-reaches)
|
|
14
|
+
- [Matching](#matching)
|
|
15
|
+
- [Applying](#applying)
|
|
16
|
+
- [A label the remote does not carry](#a-label-the-remote-does-not-carry)
|
|
17
|
+
- [Release pull requests](#release-pull-requests)
|
|
18
|
+
- [Labels at run time](#labels-at-run-time)
|
|
19
|
+
|
|
8
20
|
## Scope
|
|
9
21
|
|
|
10
22
|
Governs the labels a pull request carries: the map a project declares, how a changed path resolves to a label, and what a label missing from the remote costs.
|
|
@@ -86,7 +86,8 @@ git switch -c <branch> main && git cherry-pick <sha> <sha> \
|
|
|
86
86
|
&& (cat <<'BODY' > .canon/tmp/pr/split/<branch>.md
|
|
87
87
|
<body following pr.md template, written from the cherry-picked commits>
|
|
88
88
|
BODY
|
|
89
|
-
) && gh
|
|
89
|
+
) && gh api -X POST 'repos/{owner}/{repo}/pulls' -f title="<title>" -f head=<branch> -f base=main -F body=@.canon/tmp/pr/split/<branch>.md --jq .html_url \
|
|
90
|
+
&& printf 'opened=true\n' \
|
|
90
91
|
&& rm .canon/tmp/pr/split/<branch>.md
|
|
91
92
|
|
|
92
93
|
# Return to primary branch, push, and open its PR
|
|
@@ -94,13 +95,18 @@ git checkout <new_name> && git push -u origin <new_name> \
|
|
|
94
95
|
&& (cat <<'BODY' > .canon/tmp/pr/split/<new_name>.md
|
|
95
96
|
<body following pr.md template, written from the primary's commits>
|
|
96
97
|
BODY
|
|
97
|
-
) && gh
|
|
98
|
+
) && gh api -X POST 'repos/{owner}/{repo}/pulls' -f title="<title>" -f head=<new_name> -f base=main -F body=@.canon/tmp/pr/split/<new_name>.md --jq .html_url \
|
|
99
|
+
&& printf 'opened=true\n' \
|
|
98
100
|
&& rm .canon/tmp/pr/split/<new_name>.md
|
|
99
101
|
|
|
100
102
|
# Clean up the body-file dir if all PRs succeeded (no-op when non-empty)
|
|
101
103
|
rmdir .canon/tmp/pr/split 2>/dev/null || true
|
|
102
104
|
```
|
|
103
105
|
|
|
106
|
+
Every pull request opens through the REST pulls endpoint rather than the `gh pr` create subcommand, which runs on GraphQL and fails where a cloud session's GitHub proxy refuses it. The endpoint requires `base`, so each call names the branch its layer sits on. `-F` reads the body file, where `-f` would post the path itself.
|
|
107
|
+
|
|
108
|
+
A create the endpoint refuses, such as one for a head that already has an open pull request, exits non-zero and stops the chain before `opened=true` prints and before the body file is removed. The `opened=true` line is what a pull request creation log reads to count the opening, since the REST call prints a URL and never names itself.
|
|
109
|
+
|
|
104
110
|
For stacked mode, base each branch on the previous and cherry-pick only that group's commits:
|
|
105
111
|
|
|
106
112
|
```bash
|
|
@@ -115,7 +121,8 @@ git switch -c <branch-1> main && git cherry-pick <g1-sha> <g1-sha> \
|
|
|
115
121
|
&& (cat <<'BODY' > .canon/tmp/pr/split/<branch-1>.md
|
|
116
122
|
<body following pr.md template, written from the cherry-picked commits>
|
|
117
123
|
BODY
|
|
118
|
-
) && gh
|
|
124
|
+
) && gh api -X POST 'repos/{owner}/{repo}/pulls' -f title="<title>" -f head=<branch-1> -f base=main -F body=@.canon/tmp/pr/split/<branch-1>.md --jq .html_url \
|
|
125
|
+
&& printf 'opened=true\n' \
|
|
119
126
|
&& rm .canon/tmp/pr/split/<branch-1>.md
|
|
120
127
|
|
|
121
128
|
# Group 2: based on <branch-1>, this group's commits only
|
|
@@ -124,15 +131,17 @@ git checkout -b <branch-2> && git cherry-pick <g2-sha> <g2-sha> \
|
|
|
124
131
|
&& (cat <<'BODY' > .canon/tmp/pr/split/<branch-2>.md
|
|
125
132
|
<body following pr.md template, written from the cherry-picked commits>
|
|
126
133
|
BODY
|
|
127
|
-
) && gh
|
|
134
|
+
) && gh api -X POST 'repos/{owner}/{repo}/pulls' -f title="<title>" -f head=<branch-2> -f base=<branch-1> -F body=@.canon/tmp/pr/split/<branch-2>.md --jq .html_url \
|
|
135
|
+
&& printf 'opened=true\n' \
|
|
128
136
|
&& rm .canon/tmp/pr/split/<branch-2>.md
|
|
129
137
|
|
|
130
|
-
# Return to primary branch, push, and open its PR
|
|
138
|
+
# Return to primary branch, push, and open its PR on the last group's branch
|
|
131
139
|
git checkout <new_name> && git push -u origin <new_name> \
|
|
132
140
|
&& (cat <<'BODY' > .canon/tmp/pr/split/<new_name>.md
|
|
133
141
|
<body following pr.md template, written from the primary's commits>
|
|
134
142
|
BODY
|
|
135
|
-
) && gh
|
|
143
|
+
) && gh api -X POST 'repos/{owner}/{repo}/pulls' -f title="<title>" -f head=<new_name> -f base=<branch-2> -F body=@.canon/tmp/pr/split/<new_name>.md --jq .html_url \
|
|
144
|
+
&& printf 'opened=true\n' \
|
|
136
145
|
&& rm .canon/tmp/pr/split/<new_name>.md
|
|
137
146
|
|
|
138
147
|
# Clean up the body-file dir if all PRs succeeded (no-op when non-empty)
|
|
@@ -145,6 +154,8 @@ Respond with exactly one line:
|
|
|
145
154
|
|
|
146
155
|
`✅ Renamed: <old> → <new> | PRs: <primary-url>, <url1>, <url2>`
|
|
147
156
|
|
|
157
|
+
Read each URL off the line its create printed through `--jq .html_url`.
|
|
158
|
+
|
|
148
159
|
Do not add any other text.
|
|
149
160
|
|
|
150
161
|
## Stacked merge loop
|
|
@@ -153,10 +164,10 @@ When the user signals the previous stacked PR has merged, restack the next one.
|
|
|
153
164
|
|
|
154
165
|
1. Rebase and push. The own-commit-count comes from the original split table.
|
|
155
166
|
`git fetch origin main && git checkout <branch> && git rebase --onto origin/main HEAD~<own-commit-count> && git push --force-with-lease`
|
|
156
|
-
2. Verify the PR's base auto-retargeted to main with `gh
|
|
167
|
+
2. Verify the PR's base auto-retargeted to main with `gh api repos/{owner}/{repo}/pulls/<num> --jq .base.ref`, a REST read a cloud session's GitHub proxy serves where it refuses GraphQL. If not, retarget it with `gh api -X PATCH repos/{owner}/{repo}/pulls/<num> -f base=main --silent`.
|
|
157
168
|
3. Reply: `✅ <branch> rebased onto main. Ready for squash-merge.`
|
|
158
169
|
|
|
159
170
|
Edge cases:
|
|
160
171
|
|
|
161
172
|
- If this branch's commits modify a file added by an unmerged upstream PR, wait for that PR to merge before rebasing.
|
|
162
|
-
- If the PR was orphaned by base-branch deletion, recreate it with
|
|
173
|
+
- If the PR was orphaned by base-branch deletion, recreate it with the same create the final commands run, passing `-f head=<branch> -f base=main` and a regenerated body, and print `opened=true` after it.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: index-lookup
|
|
3
|
-
description: Answers a topic search over every tracked index.md catalog in the project by running `canon indexes list --json` and matching the topic against each entry's title, description, and path, then reports the hits or names why there are none. Names the gitignored task
|
|
3
|
+
description: Answers a topic search over every tracked index.md catalog in the project by running `canon indexes list --json` and matching the topic against each entry's title, description, and path, then reports the hits or names why there are none. Names the gitignored task and memory catalogs and the groundwork and intake record folders as pointers outside its walked corpus, so it never claims a topic is undocumented when the topic only sits there. Use when asked "where is X documented", "is there a page about X", "search the docs for X", "find the index entry for X", or "what covers X". Do NOT use to grep source code, browse a file whose path is already known, or regenerate an index, which is `canon indexes regen`.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Index lookup
|
|
@@ -13,7 +13,7 @@ Answers a topic search over the project's tracked `index.md` catalogs, not a tex
|
|
|
13
13
|
2. Run `canon indexes list --json` from that root.
|
|
14
14
|
3. Read `errors` first. A folder failing frontmatter validation drops out of `entries` and lands its message in `errors` instead, per the verb's own per-folder isolation, so report an error alongside the match rather than reading it as a reason to stop.
|
|
15
15
|
4. Match the topic case-insensitively as a substring against each entry's `title`, `description`, and `path`. Report every match. A lookup with no ranking is honest about what it found, and a single best guess is not.
|
|
16
|
-
5. Check whether the project carries a gitignored indexed folder or a README-based record catalog outside `list`'s walk: `.canon/tasks/`, `.canon/memory/`, `.canon/
|
|
16
|
+
5. Check whether the project carries a gitignored indexed folder or a README-based record catalog outside `list`'s walk: `.canon/tasks/`, `.canon/memory/`, `.canon/groundwork/`, `.canon/intake/`. Test each for existence and at least one file inside it before naming it. A folder absent from this project names nothing.
|
|
17
17
|
6. Report the hits, plus a pointer to any outside-the-walk folder found present, per Output below.
|
|
18
18
|
|
|
19
19
|
## Rules
|
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
Governs the proposal `markdown-propose` writes before it edits anything. The concern being screened is an input and does not change the format.
|
|
4
4
|
|
|
5
|
+
## Contents
|
|
6
|
+
|
|
7
|
+
- [Folder](#folder)
|
|
8
|
+
- [Change format](#change-format)
|
|
9
|
+
- [The answer contract](#the-answer-contract)
|
|
10
|
+
- [Applying](#applying)
|
|
11
|
+
- [After](#after)
|
|
12
|
+
- [Retrieval](#retrieval)
|
|
13
|
+
|
|
5
14
|
## Folder
|
|
6
15
|
|
|
7
16
|
- One folder per screening pass at `.canon/proposals/<nn>-<slug>/`, where the slug names the subject rather than the activity
|
|
@@ -7,6 +7,13 @@ description: The Challenge, Discuss, Apply, and Cleanup phases of memory-review,
|
|
|
7
7
|
|
|
8
8
|
The phases of `memory-review` after Propose. The session reads this file when the phase table in the skill picks Challenge, Discuss, Apply, or Cleanup, and reads only the section for that phase. The skill's Guards still hold for every phase here.
|
|
9
9
|
|
|
10
|
+
## Contents
|
|
11
|
+
|
|
12
|
+
- [Challenge phase](#challenge-phase)
|
|
13
|
+
- [Discuss phase](#discuss-phase)
|
|
14
|
+
- [Apply phase](#apply-phase)
|
|
15
|
+
- [Cleanup phase](#cleanup-phase)
|
|
16
|
+
|
|
10
17
|
## Challenge phase
|
|
11
18
|
|
|
12
19
|
Trigger: user says "challenge the promotes", "challenge before apply", or asks for a high-bar pass. Run before Apply. No mutations to memory files or promotion targets. Review file only.
|
|
@@ -10,19 +10,13 @@ the PR from an independent session. This skill consumes them: fix, reply, push.
|
|
|
10
10
|
|
|
11
11
|
## Guards
|
|
12
12
|
|
|
13
|
-
-
|
|
13
|
+
- Resolve `<number>` and `<tip>` off `canon pr head --json`. An `ambiguous-pull` refusal stops: `❌ Two open PRs on this branch. Name the one to address.` A record with no `number` takes the lookup in `${CLAUDE_SKILL_DIR}/references/rest-reads.md`. Nothing resolving stops: `❌ No open PR. Nothing to address.`
|
|
14
14
|
- If the PR has no review comments or threads, run step 5's staleness test before deciding. A branch that still merges stops here: `✅ No review findings to address.` One that does not skips steps 1 through 4 and runs step 5 onward, since a branch goes stale from `main` moving and a closed review says nothing about whether it still merges.
|
|
15
15
|
- Fix findings. Do not merge.
|
|
16
16
|
|
|
17
17
|
## Step 1: pull the review findings and CI status
|
|
18
18
|
|
|
19
|
-
Read the
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
gh pr view --json number,reviews,comments
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
For inline review comments, read them via `gh api` on the PR's review comments. Collect each finding with its file, location, and body.
|
|
19
|
+
Read the reviews, the thread's comments, and the inline review comments through the three reads in `${CLAUDE_SKILL_DIR}/references/rest-reads.md`. Collect each finding with its file, location, and body.
|
|
26
20
|
|
|
27
21
|
Also read the CI check status so the fixes cover failing checks, not only review comments:
|
|
28
22
|
|
|
@@ -30,7 +24,7 @@ Also read the CI check status so the fixes cover failing checks, not only review
|
|
|
30
24
|
canon pr checks <number> --json
|
|
31
25
|
```
|
|
32
26
|
|
|
33
|
-
Read the verdict off the record's `state` rather than off the exit. Treat `failing` as a finding to resolve alongside the review comments. A `pending` covers a tip whose runs have yet to conclude and a tip carrying no run at all, which the record separates on `matched`, and neither is a green to continue on. A record carrying `conflicted: true` is a third case: the branch conflicts with its base, so no run will start and the wait never ends. Stop and report it, since a rebase is the repair.
|
|
27
|
+
Read the verdict off the record's `state` rather than off the exit. Treat `failing` as a finding to resolve alongside the review comments. A `pending` covers a tip whose runs have yet to conclude and a tip carrying no run at all, which the record separates on `matched`, and neither is a green to continue on. A record carrying `conflicted: true` is a third case: the branch conflicts with its base, so no run will start and the wait never ends. Stop and report it, since a rebase is the repair. When no record comes back at all, read the check runs on the tip through `${CLAUDE_SKILL_DIR}/references/rest-reads.md`.
|
|
34
28
|
|
|
35
29
|
## Step 2: address each finding
|
|
36
30
|
|
|
@@ -160,12 +154,11 @@ canon labels scan --body-file .canon/tmp/pr/reply/reply-<number>.md
|
|
|
160
154
|
|
|
161
155
|
The hook skips `.canon/tmp/`, so this scan is the only gate on the published
|
|
162
156
|
reply. Fix any hit the standard names, then post the reply to the PR and
|
|
163
|
-
|
|
157
|
+
keep the `id` the POST answers with, since Step 7 edits this exact comment rather
|
|
164
158
|
than trusting whichever one `gh` considers last:
|
|
165
159
|
|
|
166
160
|
```bash
|
|
167
|
-
|
|
168
|
-
echo "${comment_url##*issuecomment-}" > .canon/tmp/pr/reply/reply-<number>.id
|
|
161
|
+
gh api -X POST 'repos/{owner}/{repo}/issues/<number>/comments' -F body=@.canon/tmp/pr/reply/reply-<number>.md --jq .id > .canon/tmp/pr/reply/reply-<number>.id
|
|
169
162
|
```
|
|
170
163
|
|
|
171
164
|
## Step 7: confirm resolution
|
|
@@ -252,5 +245,5 @@ Not everything worth reaching the reviewing session surfaces inside the numbered
|
|
|
252
245
|
Open with `## Post-review findings` rather than `## Review response`, since nothing on the thread is being answered. `review-pr` states the full heading set this belongs to and routes it the same as a response: `role-orchestrator`'s poll picks it up and sends the reviewing session back for a pass. Close the body with `🤖 Addressed by Claude Code` on its own line, matching the reply's footer.
|
|
253
246
|
|
|
254
247
|
```bash
|
|
255
|
-
gh
|
|
248
|
+
gh api -X POST 'repos/{owner}/{repo}/issues/<number>/comments' -F body=@.canon/tmp/pr/reply/reply-<number>.md --silent
|
|
256
249
|
```
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: REST reads
|
|
3
|
+
description: The reads review-address runs over REST, being the findings on the pull request, plus the pull request lookup and the check runs it falls back to when the installed canon predates pr head or pr checks
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# REST reads
|
|
7
|
+
|
|
8
|
+
The guard and Step 1 of `review-address`. The session reads this file on reaching Step 1, and from the guard when `canon pr head --json` hands back no `number`. Every read runs on REST, since the `gh pr` subcommands run on GraphQL and a cloud session's GitHub proxy refuses it. A cloud worker answering a review runs this skill, so one GraphQL call breaks the whole leg.
|
|
9
|
+
|
|
10
|
+
## The findings
|
|
11
|
+
|
|
12
|
+
Read the reviews, the thread's comments, and the inline review comments:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
gh api --paginate 'repos/{owner}/{repo}/pulls/<number>/reviews?per_page=100' --jq '.[] | {id, user: .user.login, state, commit_id, submitted_at, body} | @json'
|
|
16
|
+
gh api --paginate 'repos/{owner}/{repo}/issues/<number>/comments?per_page=100' --jq '.[] | {id, user: .user.login, created_at, body} | @json'
|
|
17
|
+
gh api --paginate 'repos/{owner}/{repo}/pulls/<number>/comments?per_page=100' --jq '.[] | {id, user: .user.login, path, line, body} | @json'
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Each read prints one JSON line per row, oldest first across every page. A multi-line body stays inside its own row, and the newest review is the last line of the first read rather than the last row of its first page. `--paginate` runs the filter once per page, so never reduce inside it with `last` or `max`.
|
|
21
|
+
|
|
22
|
+
## The pull request on the branch
|
|
23
|
+
|
|
24
|
+
The guard's fallback, for a target whose CLI predates `canon pr head`. Read the open pull requests on the branch and the head of the one it finds:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
gh api "repos/{owner}/{repo}/pulls?head={owner}:$(git branch --show-current | jq -Rr @uri)&state=open" --jq '.[].number'
|
|
28
|
+
gh api repos/{owner}/{repo}/pulls/<number> --jq .head.sha
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The branch is encoded so a `/` in its name reaches the query as `%2F`. Written bare, it resolves nothing and reads as no open pull request.
|
|
32
|
+
|
|
33
|
+
Nothing printed takes the guard's no-PR stop. More than one number takes its `ambiguous-pull` stop rather than the first, since two open pull requests on one head against different bases leave nothing to say which one the review is on.
|
|
34
|
+
|
|
35
|
+
## The check runs on the tip
|
|
36
|
+
|
|
37
|
+
Step 1's fallback, for a target whose CLI predates `canon pr checks`:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
gh api --paginate 'repos/{owner}/{repo}/commits/<tip>/check-runs?per_page=100' --jq '.check_runs[] | {name, status, conclusion} | @json'
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
A run whose `status` is not `completed` is pending. A tip listing no run at all is pending too rather than green, the same reading `canon pr checks` gives it.
|
|
@@ -12,7 +12,13 @@ The diff touches a file that paints: markup, a stylesheet, a component or templa
|
|
|
12
12
|
|
|
13
13
|
Run this before any other section, since a pass that finds nothing to open otherwise reads the stylesheet instead and reports as if it had looked. Read its answer before composing any report or body, never in the same batch as the write, since what it decides is a finding the report has to carry.
|
|
14
14
|
|
|
15
|
-
Read
|
|
15
|
+
Read the thread over REST, which a cloud session's GitHub proxy serves where it refuses GraphQL, and take the head off `canon pr head <number> --json`, from the record's `tip`:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
gh api --paginate 'repos/{owner}/{repo}/issues/<number>/comments?per_page=100' --jq '.[] | select((.body // "") | contains("<!-- pr-evidence: head=")) | .body'
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Take the comment whose body carries a `<!-- pr-evidence: head=<sha> -->` marker. Match the marker, never the first line, since the body opens with a `**Preview:**` line when the project has a preview address.
|
|
16
22
|
|
|
17
23
|
Flag the change as shipping no screenshot when it paints and the pull request carries no marked comment, carries one with no `## Evidence` section, or carries only a checklist posted alone. Then decide how to raise it by whether the project keeps captures, which is the same test `canon pr evidence` applies:
|
|
18
24
|
|
|
@@ -27,6 +27,7 @@ Scoping a later pass off GitHub's review stamp skipped real work. On 2026-09-07
|
|
|
27
27
|
- Post the closing pass even with nothing to report, since a review left unanswered reads as one nobody closed
|
|
28
28
|
- State a withdrawal or a regrade on the thread with the fact that settled it, since a finding dropped in silence reads the same as one nobody answered
|
|
29
29
|
- Key the body file on the pull request number and the head commit, and on the response it answers once the head repeats, so no two passes overwrite each other
|
|
30
|
+
- Link a file-bound finding to its line at the reviewed head, since a bare file block leaves a reader hunting the diff for the spot
|
|
30
31
|
- Scan the comment for banned characters and internal phase labels before posting, since a finding phrased against a phase label reaches a reader with no task board
|
|
31
32
|
- Answer a `## For the reviewer` bullet in the body, bounded to the section itself rather than the Summary or the Technical Context around it, so the independent vantage stays clear of the author's framing
|
|
32
33
|
- Confirm each `**Review focus:**` item the plan names on a first pass, and turn one the pass cannot confirm into a finding, since the planning session named the breaking inputs before the diff existed and no other reader holds that list
|
|
@@ -30,7 +30,7 @@ reader scanning the thread finds the current verdict where the last one sat.
|
|
|
30
30
|
|
|
31
31
|
## Guards
|
|
32
32
|
|
|
33
|
-
- If no open PR resolves for the target branch via `
|
|
33
|
+
- If no open PR resolves for the target branch via `canon pr head --json`, stop: `❌ No open PR to review. Open one first, or use /review-branch for local changes.`
|
|
34
34
|
- Review and post. Do not merge. Merging is the human's gate.
|
|
35
35
|
|
|
36
36
|
## Step 1: resolve the PR and read context
|
|
@@ -43,9 +43,15 @@ date -u +%Y-%m-%dT%H:%M:%SZ
|
|
|
43
43
|
|
|
44
44
|
Everything from that line to the post is the compose window, and a commit pushed inside it is one this pass never saw. Stamping the body with the instant the window opened is what leaves that commit outside the covered range, so the next pass reads it rather than assuming it covered. Taking the stamp later, at the head resolution below or at Step 4 where the body is composed, claims a stretch this pass had already stopped reading through.
|
|
45
45
|
|
|
46
|
-
Then resolve the PR
|
|
46
|
+
Then resolve the PR through `canon pr head --json` for the current branch, or `canon pr head <number> --json` for a number the user names. Take `<number>` off the record's `number` and `<headRefOid>` off its `tip`. The first seven characters are `<short-sha>`, which names the body file in Step 4. Read the title and body over REST:
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
```bash
|
|
49
|
+
gh api repos/{owner}/{repo}/pulls/<number> --jq '{title, body, head: .head.sha, repo: .base.repo.html_url}'
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Every read in this skill runs on REST, since the `gh pr` lookups run on GraphQL and a cloud session's GitHub proxy refuses it. A target whose CLI predates the verb gets no record back. Fall back there to the `head` field above for `<headRefOid>`, and to `gh api "repos/{owner}/{repo}/pulls?head={owner}:<branch>&state=open" --jq '.[].number'` for a branch with no number named, stopping on the guard above when that prints anything but one number.
|
|
53
|
+
|
|
54
|
+
`<headRefOid>` and `<read-at>` travel into Step 4's marker, and `<headRefOid>` with the `repo` field as `<repo-url>` into its finding links. None is re-derived after this point. Re-reading the head later in the pass would name a commit this pass did not review, which is the defect the marker exists against, reached from the inside.
|
|
49
55
|
|
|
50
56
|
Read these in parallel from the project root, skipping any that do not exist:
|
|
51
57
|
|
|
@@ -9,10 +9,10 @@ Part of Step 4 of `review-pr`, reached only on the branch `post.md` names. Every
|
|
|
9
9
|
|
|
10
10
|
When `<prior-heading>` from Step 2 reads `## Review closed` and this pass carries nothing owed, the thread already holds this verdict. Replace the standing close-out rather than posting a second one beside it.
|
|
11
11
|
|
|
12
|
-
Resolve its numeric id
|
|
12
|
+
Resolve its numeric id off the REST listing, keeping the newest match across every page:
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
|
-
gh api repos/{owner}/{repo}/pulls/<number>/reviews --jq '
|
|
15
|
+
gh api --paginate 'repos/{owner}/{repo}/pulls/<number>/reviews?per_page=100' --jq '.[] | select((.body // "") | split("\n")[0] | rtrimstr("\r") == "## Review closed") | .id' | tail -n 1
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
Write the replacement body to `<body-file>`, the name `post.md` already derived at the top of Step 4, keeping the heading and the footer and naming what this pass covered on the scope line in place of what the old one covered. Derive that name the same way whichever path reached here, since the guard reads `<prior-heading>` alone and a repeated head resolves the third segment as usual. The folder then gains a record of every covered head rather than losing the one the standing comment named. Then replace the comment:
|
|
@@ -13,6 +13,16 @@ Derive both segments from Step 1. Never pick a suffix by hand, and never reuse a
|
|
|
13
13
|
|
|
14
14
|
When `<prior-oid>` from Step 2 equals `headRefOid`, the head repeats and the folder already holds `body-<number>-<short-sha>.md`. Add a third segment taking the id of the reply Step 2 resolved, giving `body-<number>-<short-sha>-r<comment-id>.md`, which is `<body-file>` on that path. That satisfies both prohibitions above rather than carving an exception into either. Step 2 already stopped the pass when that resolution came back empty, so reaching this line means the comment id is in hand.
|
|
15
15
|
|
|
16
|
+
A finding bound to a line links to it, so a reader skips the hunt:
|
|
17
|
+
|
|
18
|
+
```markdown
|
|
19
|
+
- **should-fix** ([line 12](<repo-url>/blob/<headRefOid>/<path>#L12)): what breaks and the fix
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
A finding that spans lines anchors to the span as `#L12-L14`, with no spaces and no en dash, and a single-line finding anchors as `#L12`. Take the line number from the file read at `<headRefOid>`, `git show <headRefOid>:<path>`, never from another checkout. The URL carries the full 40-character `<headRefOid>`, never `<short-sha>`, `HEAD`, or the branch name, so the link survives a push. A later pass restating an open finding links it again at its own `<headRefOid>` rather than copying the old bullet.
|
|
23
|
+
|
|
24
|
+
Four findings take the plain bullet with no link: a `**PR body**` entry, a finding on a file the head deleted, a finding on a line the diff removed from a file that survives, since the head holds no number for it, and a finding about a whole file with no one line. A path holding a character a URL needs encoded, such as a space or `#`, is percent-encoded in the link or keeps the plain bullet. Keep the file block header a bare backticked path, so the section-marker reading in `${CLAUDE_SKILL_DIR}/../../standards/markdown.md` does not change.
|
|
25
|
+
|
|
16
26
|
The comment is a rendered-for-human GitHub surface, so load the `write-human` skill for voice and word choice and follow `${CLAUDE_SKILL_DIR}/../../standards/markdown.md` for punctuation: cut editorializing, and keep every sentence load-bearing. Match this shape on a first pass:
|
|
17
27
|
|
|
18
28
|
```markdown
|
|
@@ -22,8 +32,8 @@ X critical, Y should-fix, Z minor. Reviewed against project docs and the board.
|
|
|
22
32
|
|
|
23
33
|
**`path/to/file.ext`**
|
|
24
34
|
|
|
25
|
-
- **should-fix
|
|
26
|
-
- **minor
|
|
35
|
+
- **should-fix** ([line 12](<repo-url>/blob/<headRefOid>/path/to/file.ext#L12)): what breaks and the fix, in two or three sentences.
|
|
36
|
+
- **minor** ([lines 30-34](<repo-url>/blob/<headRefOid>/path/to/file.ext#L30-L34)): finding.
|
|
27
37
|
|
|
28
38
|
**What is right**
|
|
29
39
|
|
|
@@ -47,7 +57,7 @@ Re-reviewed `<short-sha>`, N commits since the prior pass. X critical, Y should-
|
|
|
47
57
|
|
|
48
58
|
**`path/to/file.ext`**
|
|
49
59
|
|
|
50
|
-
- **should-fix
|
|
60
|
+
- **should-fix** ([line 12](<repo-url>/blob/<headRefOid>/path/to/file.ext#L12)): what breaks and the fix, in two or three sentences.
|
|
51
61
|
|
|
52
62
|
🤖 Reviewed by Claude Code
|
|
53
63
|
|
|
@@ -117,9 +127,11 @@ Before posting, run the scan in `${CLAUDE_SKILL_DIR}/../../standards/publish.md`
|
|
|
117
127
|
Do not run the command below when `<prior-heading>` from Step 2 reads `## Review closed` and this pass carries nothing owed. That pass replaces the standing comment rather than adding one, so read `${CLAUDE_SKILL_DIR}/references/close-out.md` instead of posting. Posting first and reaching that section afterward leaves two close-outs both naming the new head, which is worse than the pair the guard exists against.
|
|
118
128
|
|
|
119
129
|
```bash
|
|
120
|
-
gh
|
|
130
|
+
gh api -X POST 'repos/{owner}/{repo}/pulls/<number>/reviews' -f event=COMMENT -F body=@.canon/tmp/pr/review/body-<number>-<short-sha>.md --jq .html_url
|
|
121
131
|
```
|
|
122
132
|
|
|
133
|
+
The post goes through the REST reviews endpoint rather than the `gh pr` review subcommand, which runs on GraphQL and fails where a cloud session's GitHub proxy refuses it. `event=COMMENT` submits the review as a comment, the same state the old `--comment` flag produced.
|
|
134
|
+
|
|
123
135
|
A pass carrying nothing at all takes `## Review closed` and a short body, with the footer line included either way. On a first pass, post `✅ No findings. Reviewed against project docs and the board.` On a later pass, post `✅ Prior findings addressed. Re-reviewed <short-sha>, N commits since the prior pass.`
|
|
124
136
|
|
|
125
137
|
A pass carrying only minors is an ordinary finding-carrying pass, so it takes the open heading and the full shape rather than either short line, since the minors have to be readable and neither line reports them. A pass carrying only Testing questions, or only an unanswered reviewer request, takes the same route for the same reason. Keep whichever scope sentence the pass owes on the summary line:
|
|
@@ -131,7 +143,7 @@ A pass carrying only minors is an ordinary finding-carrying pass, so it takes th
|
|
|
131
143
|
|
|
132
144
|
**`path/to/file.ext`**
|
|
133
145
|
|
|
134
|
-
- **minor
|
|
146
|
+
- **minor** ([line 12](<repo-url>/blob/<headRefOid>/path/to/file.ext#L12)): finding, and the fix it wants.
|
|
135
147
|
|
|
136
148
|
🤖 Reviewed by Claude Code
|
|
137
149
|
|
|
@@ -17,28 +17,30 @@ The fields are `<prior-oid>` off `commit`, `<prior-heading>` off `heading`, and
|
|
|
17
17
|
|
|
18
18
|
`source` says which stamp answered. `marker` is the pass's own read-time record and is the authority. `fallback` is a pass posted before this mechanism shipped, so its commit is whatever the head was when GitHub recorded the review rather than what that session read, and a push inside its compose window is invisible. `none` is a thread carrying no pass at all.
|
|
19
19
|
|
|
20
|
-
Do not read `
|
|
20
|
+
Do not read `commit_id` or `submitted_at` off the reviews listing here. Both are stamped at submission, so a push landing between a pass's read and its post moves them onto a commit that pass never saw, and this step then scopes the delta past it and reports it covered.
|
|
21
21
|
|
|
22
|
-
A target whose CLI predates the verb meets a missing subcommand rather than a record. Fall back there to the
|
|
22
|
+
A target whose CLI predates the verb meets a missing subcommand rather than a record. Fall back there to the REST read below, which reads the stamps and carries the defect above, and say the fallback answered so a reader can tell a marker read from a stamped one:
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
|
-
gh
|
|
25
|
+
gh api --paginate 'repos/{owner}/{repo}/pulls/<number>/reviews?per_page=100' --jq '.[] | select(.body // "" | split("\n")[0] | rtrimstr("\r") | . == "## Review" or . == "## Review closed") | ((.commit_id // "") + "\t" + (.body | split("\n")[0] | rtrimstr("\r")) + "\t" + (.submitted_at // ""))' | tail -n 1
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
The three fields are `<prior-oid>`, `<prior-heading>`, and `<prior-at>`.
|
|
28
|
+
The three fields are `<prior-oid>`, `<prior-heading>`, and `<prior-at>`. The filter emits one line per matching review and `tail` keeps the newest, since `--paginate` runs the filter once per page and a `last` inside it would answer for each page rather than for the thread.
|
|
29
29
|
|
|
30
30
|
Match the first line for equality against the two headings this skill posts. A prefix test also matches `## Review response` and any heading merely starting with those words, which would scope the pass to whatever commit that comment carried. The `\r` trim covers a body composed in the GitHub web editor, which stores CRLF.
|
|
31
31
|
|
|
32
32
|
A `source` of `none`, or an empty result from the fallback, is a first pass. Read the whole change:
|
|
33
33
|
|
|
34
34
|
```bash
|
|
35
|
-
gh
|
|
35
|
+
gh api 'repos/{owner}/{repo}/pulls/<number>' -H 'Accept: application/vnd.github.diff'
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
```bash
|
|
39
|
-
gh
|
|
39
|
+
gh api --paginate 'repos/{owner}/{repo}/pulls/<number>/files?per_page=100' --jq '.[].filename'
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
+
Both reads go through REST, since the `gh pr` diff subcommand runs on GraphQL and a cloud session's GitHub proxy refuses it. The diff media type returns the same unified diff, and the paginated files listing returns every path rather than the first page.
|
|
43
|
+
|
|
42
44
|
A commit is a later pass. Fetch the pull request head so both commits are local:
|
|
43
45
|
|
|
44
46
|
```bash
|
|
@@ -58,14 +60,14 @@ On exit zero, review `<prior-oid>..<headRefOid>` and nothing else. `git diff` an
|
|
|
58
60
|
A commit is its own ancestor, so an unchanged head passes that test too, with an empty range. When `<prior-oid>` equals `<headRefOid>`, decide whether this pass has anything to add before reading anything else, since the empty range itself cannot answer that:
|
|
59
61
|
|
|
60
62
|
```bash
|
|
61
|
-
gh
|
|
63
|
+
gh api --paginate 'repos/{owner}/{repo}/issues/<number>/comments?per_page=100' --jq '.[] | select(.body // "" | split("\n")[0] | rtrimstr("\r") | . == "## Review response" or . == "## Rebase" or . == "## Post-review findings") | select(.created_at > "<prior-at>") | .id' | tail -n 1
|
|
62
64
|
```
|
|
63
65
|
|
|
64
66
|
`<prior-at>` is the instant Step 2 resolved above, which is the prior pass's `readAt` where it wrote one. Reading `submittedAt` off the thread here instead is the same submission-time defect on the time axis: a reply posted inside that pass's compose window sorts before the stamp and reads as already answered, when in fact the pass had stopped reading before it landed.
|
|
65
67
|
|
|
66
68
|
Scope the replies to those newer than the prior pass, never to every reply the thread carries. A pass answering the newest reply and a pass answering an older one derive the same third segment (Step 4), so an unscoped read hands a re-run after a close-out the name its own prior pass already wrote. That is the collision this case exists to prevent, reached without a rebase or an error.
|
|
67
69
|
|
|
68
|
-
|
|
70
|
+
The REST `id` is the number the thread displays in each comment's anchor. Keep `tail` outside the filter for the same per-page reason as the reviews read above.
|
|
69
71
|
|
|
70
72
|
An empty result means no reply arrived since the prior pass, so the head is unchanged and this pass has nothing new to add. Stop here, before Step 3 or Step 4 run: `❌ The head is unchanged since the prior pass on <short-sha>. Nothing new to review.` This is the earliest point every path crosses, which is why the check sits here rather than inside Step 4's filename derivation. A path that decides there is nothing to add never reaches a step reached only when composing a body, so a stop written there is a stop a shortcut path can route around.
|
|
71
73
|
|