@erclx/canon 5.24.0 → 5.25.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/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/design-extract/REQUIREMENT.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/git-followup/SKILL.md +17 -6
- package/claude/skills/git-pr/references/evidence.md +4 -2
- package/claude/skills/git-split/SKILL.md +1 -1
- package/claude/skills/index-lookup/SKILL.md +2 -2
- 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/role-orchestrator/references/orchestrator-cloud-launch.md +30 -1
- 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/docs/agents/canvas.md +1 -1
- package/docs/agents/commands.md +45 -45
- package/docs/agents/context-audit-checks.md +2 -2
- package/docs/agents/index.md +0 -1
- 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/target/projects.md +4 -4
- package/docs/workflow/ai-workflow.md +0 -1
- package/docs/workflow/skill-map.md +3 -3
- 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/src/audits/catalog.ts +2 -2
- package/src/canvas/client/inspector/field.tsx +200 -0
- package/src/canvas/client/inspector/fields.css +151 -0
- package/src/canvas/client/inspector/section.tsx +27 -0
- package/src/canvas/client/inspector/values.ts +60 -0
- package/src/canvas/client/inspector.tsx +154 -115
- package/src/canvas/client/shell.css +0 -101
- package/src/claude/cases/misc.ts +5 -0
- package/src/claude/cases/workflow.ts +1 -1
- package/src/claude/seeds.ts +0 -1
- package/src/commands/design.ts +2 -64
- package/src/commands/pr.ts +219 -202
- package/src/context/audit.ts +7 -6
- package/src/help.ts +2 -2
- package/src/migrate/skill-names.ts +7 -1
- package/src/pr/evidence.ts +25 -5
- package/src/pr/rest.ts +137 -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/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/src/design/board.ts +0 -576
- package/standards/diagrams.md +0 -132
- package/tooling/claude/seeds/.claude/diagrams/index.md +0 -8
|
@@ -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.
|
|
@@ -53,9 +53,11 @@ Read `reason` on the record rather than the exit code.
|
|
|
53
53
|
- `ok`: write `body` to `.canon/tmp/pr/evidence/body-<number>.md` at the main worktree root (resolved the way `session-worktree` does), then post or update the comment:
|
|
54
54
|
|
|
55
55
|
```bash
|
|
56
|
-
gh
|
|
56
|
+
gh api -X POST repos/{owner}/{repo}/issues/<number>/comments -F body=@<main-root>/.canon/tmp/pr/evidence/body-<number>.md --silent
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
+
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.
|
|
60
|
+
|
|
59
61
|
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
62
|
|
|
61
63
|
```bash
|
|
@@ -71,7 +73,7 @@ Any other `reason` is one of the mirrored git refusals (`gh-missing`, `gh-failed
|
|
|
71
73
|
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
74
|
|
|
73
75
|
```bash
|
|
74
|
-
gh
|
|
76
|
+
gh api -X POST repos/{owner}/{repo}/issues/<number>/comments -F body=@<main-root>/.canon/tmp/handoff/ui-checklist/<slug>.md --silent
|
|
75
77
|
```
|
|
76
78
|
|
|
77
79
|
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.
|
|
@@ -153,7 +153,7 @@ When the user signals the previous stacked PR has merged, restack the next one.
|
|
|
153
153
|
|
|
154
154
|
1. Rebase and push. The own-commit-count comes from the original split table.
|
|
155
155
|
`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
|
|
156
|
+
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, `gh pr edit <num> --base main`.
|
|
157
157
|
3. Reply: `✅ <branch> rebased onto main. Ready for squash-merge.`
|
|
158
158
|
|
|
159
159
|
Edge cases:
|
|
@@ -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
|
|
@@ -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
|
|
|
@@ -26,15 +26,23 @@ rather than inside it, so the two passes finish on their own clocks.
|
|
|
26
26
|
|
|
27
27
|
## Step 1: resolve the pull request
|
|
28
28
|
|
|
29
|
-
Resolve the number
|
|
29
|
+
Resolve the number and the head through `canon pr head <number> --json` for the number the launch names, or `canon pr head --json` for the current branch when it names none. Take `<number>` off the record's `number` and `<head>` off its `tip`. The first seven characters are `<short-sha>`. Record `<pass>` once, as the instant this pass starts, with `date -u +%Y%m%dT%H%M%SZ`. Every frame Step 5 pushes files under it, so a later pass at the same head never replaces an image this one's comment shows.
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
Every read in this step 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. When the launch named no number, fall back there to the open pull requests on the current branch, encoding it so a `/` in its name reaches the query as `%2F`, and stop when that prints anything but one number. Two open pull requests on one head against different bases leave nothing to say which one the launch meant. A named number skips that lookup, since this checkout's branch need not be the pull request's. Either way, read the head off the pull request itself:
|
|
32
32
|
|
|
33
33
|
```bash
|
|
34
|
-
gh
|
|
34
|
+
gh api "repos/{owner}/{repo}/pulls?head={owner}:$(git branch --show-current | jq -Rr @uri)&state=open" --jq '.[].number'
|
|
35
|
+
gh api repos/{owner}/{repo}/pulls/<number> --jq .head.sha
|
|
35
36
|
```
|
|
36
37
|
|
|
37
|
-
|
|
38
|
+
Read the newest UI verdict for the head guard, and the newest reply:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
gh api --paginate 'repos/{owner}/{repo}/pulls/<number>/reviews?per_page=100' --jq '.[] | select(.body // "" | split("\n")[0] | rtrimstr("\r") | startswith("## UI review")) | {body, submitted_at} | @json' | tail -n 1
|
|
42
|
+
gh api --paginate 'repos/{owner}/{repo}/issues/<number>/comments?per_page=100' --jq '.[] | select(.body // "" | split("\n")[0] | rtrimstr("\r") | . == "## Review response" or . == "## Post-review findings") | .created_at' | tail -n 1
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The first read prints the verdict's body and when it was posted as one JSON line, and the second prints when the newest reply was. `--paginate` runs the filter once per page, so `tail` outside it keeps the newest row in the thread rather than the newest on each page. The verdict goes out as JSON because its body spans many lines, and `tail` over the raw body would keep only its last one. An empty first read is a pull request carrying no verdict yet. Take the sha off the body's last-line `<!-- review-ui: head=<sha> -->` marker. A body carrying none covers no head this pass can trust, so drive.
|
|
38
46
|
|
|
39
47
|
Read nothing else about the change. The diff, the plan, the task, and the description's Summary and Technical Context all carry the author's argument for it, and a pass that has heard it drives towards what it expects.
|
|
40
48
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Orchestrator cloud launch
|
|
3
|
-
description: The cloud build
|
|
3
|
+
description: The cloud build and review shapes, being the launch prompt that carries the plan as text and announces over GitHub, the reviewer prompt whose posted review is its announcement, the create under a pseudo-terminal with its trust keys, and the readout that fails loudly on a missing session id
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Orchestrator cloud launch
|
|
@@ -74,3 +74,32 @@ gh api 'repos/{owner}/{repo}/pulls/<number>/commits' --jq '.[].commit.message' |
|
|
|
74
74
|
```
|
|
75
75
|
|
|
76
76
|
A count above zero matches the row. A count of zero is someone else's pull request, whatever its title says. Once it matches, record the number on the row's task yourself with `canon tasks pull-request <number> <task>`, since `git-pr` on the VM has no task file to write, then review it the way step 5 reviews any worker's pull request.
|
|
77
|
+
|
|
78
|
+
## Dispatch a review to a cloud reviewer
|
|
79
|
+
|
|
80
|
+
Run this once `orchestrator-review-dispatch.md` has sent a code review to a dispatched reviewer and the operator picked cloud for it. The reviewer needs the same `SessionStart` hook as the build shape, and a `canon` on the VM whose `pr` reads run on REST, since the proxy refuses GraphQL. A UI review never takes this shape, because it drives a browser the VM does not have.
|
|
81
|
+
|
|
82
|
+
Confirm the VM's `canon` reads over REST before dispatching. In this repository the setup hook links the checkout's own CLI, so a pushed branch carries the reads. Any other repository's VM installs `canon` from npm, so its `pr` reads run on REST only once a release carrying them is published, and its copied skills carry the REST lookups only once that repository's skills are re-scaffolded from that release.
|
|
83
|
+
|
|
84
|
+
Read `npm view @erclx/canon version` against the first release whose changelog names the REST reads, and check the target's copied `review-pr` the same way, by running `grep -rnE 'gh pr (list|view|review|diff|comment)'` over it and expecting no output. A version short of that release, or a hit, dispatches a reviewer that fails on its first GraphQL call, so take the local shape instead.
|
|
85
|
+
|
|
86
|
+
Check this pass's launched record for a cloud reviewer already holding the number, and run `canon pr review-state <number> --json` for a pass already posted. A cloud reviewer never appears in `canon sessions list`, so the live-session check the local shape runs reads clear on a pull request one already holds.
|
|
87
|
+
|
|
88
|
+
The create targets whichever repository the checkout it runs from names as `origin`. For a pull request in this repository, write the prompt to `.canon/tmp/cloud-review-<number>/prompt.md` at the main worktree root and run the create there. For a pull request in another repository, run the create from a checkout of that repository and write the prompt under that checkout's own `.canon/tmp/cloud-review-<number>/`, which its `.gitignore` has to cover before the create runs. A fresh clone made for this is untrusted, so the create takes the trust-prompt key variant under `## The create`:
|
|
89
|
+
|
|
90
|
+
```plaintext
|
|
91
|
+
/role-reviewer
|
|
92
|
+
Then run /review-pr <number> on <owner>/<repo>.
|
|
93
|
+
Your controller cannot receive a message from this session. The review you post is the announcement, so post nothing else for it. If you stop on a question before the review posts, comment on the pull request starting with QUESTION: review <number>.
|
|
94
|
+
<the cross-branch facts>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The cross-branch facts are the ones `### What the brief may carry` in `orchestrator-launch.md` allows a reviewer brief, and nothing of this session's own read. Neither skill carries the invocation flag, so no position-zero expansion is at stake and the bare names reach the copied project skills.
|
|
98
|
+
|
|
99
|
+
Create the session with the wrapper under `## The create`, setting `dir` to that prompt's folder, and keep its readout rule: no `session_` id means nothing was dispatched. Record the session id beside the number in this pass's launched record, and name the number, the repository, and the session id in the dispatch report.
|
|
100
|
+
|
|
101
|
+
`poll.ts` lists every open pull request from this machine and reads each one's review state, so the posted `## Review` reaches the next poll like a local reviewer's. Start the poll when the dispatch goes out rather than at its fallback. A `QUESTION: review <number>` comment reaches the same poll as an unclassified reply, so answer it on the pull request.
|
|
102
|
+
|
|
103
|
+
The pull request is the record to act on, and the transcript is read back only to check for a refused call. Run `claude --teleport <session>` under `script` from a throwaway clone of the target outside this repository, answer its trust prompt with the same piped keys, and set the terminal to 500 rows first, since only the screen tail is captured otherwise. Search the capture for `HTTP 403` and `graphql`. A hit names a call still on GraphQL, so report it as a finding rather than reading the posted review as clean.
|
|
104
|
+
|
|
105
|
+
A cloud reviewer cannot be messaged after it posts. A re-review after a worker's address pass is a fresh dispatch, local or cloud by the same pick, rather than the message `orchestrator-handback.md` sends a live reviewer.
|
|
@@ -103,6 +103,10 @@ claude --bg --model <model> -n "reviewer-<project>-<number>" "Run /canon:role-re
|
|
|
103
103
|
|
|
104
104
|
Check `canon sessions list --json` for a live `reviewer-<project>-<number>` before launching, and message that session instead when one holds the pull request.
|
|
105
105
|
|
|
106
|
+
## Dispatch a review to a cloud reviewer
|
|
107
|
+
|
|
108
|
+
Read `${CLAUDE_SKILL_DIR}/references/orchestrator-cloud-launch.md` when the operator picked cloud for a code review. Its prompt opens with `/role-reviewer`, carries the cross-branch facts, and makes the posted review the announcement.
|
|
109
|
+
|
|
106
110
|
## Dispatch to drive a pull request's checklist
|
|
107
111
|
|
|
108
112
|
`review-ui` is the second pass `role-reviewer` may run, so this shape reaches the role first the way the code reviewer's does. `orchestrator-review-dispatch.md` decides whether a pull request takes it, and it runs beside the code reviewer rather than after it.
|
|
@@ -9,7 +9,7 @@ Run the orchestrator's review trigger. The poll reports pull request movement an
|
|
|
9
9
|
|
|
10
10
|
## When to run it
|
|
11
11
|
|
|
12
|
-
Start the poll on an open pull request and stop it when the last one merges with nothing else out. That is the whole condition and it resolves from
|
|
12
|
+
Start the poll on an open pull request and stop it when the last one merges with nothing else out. That is the whole condition and it resolves from the open list without asking the operator. A release pull request alone does not qualify, since its sweep carries no findings.
|
|
13
13
|
|
|
14
14
|
A dispatch no longer starts it. The script reads pull requests and a building worker has none, so every interval between the launch and the push is a fixed cost returning nothing, measured as five consecutive runs reporting no movement across roughly fifteen minutes while one worker built. What replaces it is the worker announcing its own pull request the moment it opens one, per the `role-worker` skill that session loads, which covers exactly the transition the poll cannot observe.
|
|
15
15
|
|
|
@@ -71,15 +71,15 @@ The state reaches every stalled dispatch, since one threshold governs the headin
|
|
|
71
71
|
|
|
72
72
|
### The count behind the review dispatch
|
|
73
73
|
|
|
74
|
-
The report is also where the load count in `orchestrator-review-dispatch.md` is legible, and that count decides where a first pass runs, here or in a dispatched reviewer. That threshold trips on open pull requests awaiting a first pass, which is what `OPENED` and a pull request with no prior pass name here and what `SEEN` excludes, so read the count off these lines rather than off
|
|
74
|
+
The report is also where the load count in `orchestrator-review-dispatch.md` is legible, and that count decides where a first pass runs, here or in a dispatched reviewer. That threshold trips on open pull requests awaiting a first pass, which is what `OPENED` and a pull request with no prior pass name here and what `SEEN` excludes, so read the count off these lines rather than off the open list, which counts a branch closed out and waiting on a merge the same as one nobody has read. It is a separate condition from the poll-start fallback above, which decides when this loop runs rather than where a review runs.
|
|
75
75
|
|
|
76
76
|
The count used to read low, and it erred in the direction that breaks the trigger. A review's `commit.oid` is stamped with the head at submission rather than with the commit the reviewer read, so an author pushing between the diff read and the post left the pass recorded against a commit it never saw, and `SEEN` then fired on a head still awaiting its first look at that delta.
|
|
77
77
|
|
|
78
78
|
The pass now carries its own record instead. `review-pr` writes the commit it read and the instant it read it as a marker on the last line of every body it posts, and both this script and that skill resolve the covered state through `canon pr review-state`, which is the one place the marker is parsed. A commit pushed inside a pass's compose window falls outside the marked range, so it reads as `MOVED` rather than as `SEEN`. A `SEEN` is a claim about what a session read rather than about what GitHub stamped, so it wants no second read to be believed.
|
|
79
79
|
|
|
80
|
-
Read `source` on the record before trusting a `SEEN` on a thread whose newest pass is old. `marker` is the read-time record. `fallback` is a pass posted before the mechanism shipped, or a target whose CLI predates the verb, and it carries the defect above unchanged, so a `SEEN` under it is worth one
|
|
80
|
+
Read `source` on the record before trusting a `SEEN` on a thread whose newest pass is old. `marker` is the read-time record. `fallback` is a pass posted before the mechanism shipped, or a target whose CLI predates the verb, and it carries the defect above unchanged, so a `SEEN` under it is worth one read of the pull's reviews before it is believed. `none` is a thread carrying no pass at all.
|
|
81
81
|
|
|
82
|
-
The other side of that comparison used to lag as well, which made the two errors compound rather than cancel.
|
|
82
|
+
The other side of that comparison used to lag as well, which made the two errors compound rather than cancel. The pull read's `head.sha` answers from the pull request object and that object trails the branch ref by up to a minute after a push, reporting nothing about the trail, so a pass stamped ahead of the commit it read was compared against a head stamped behind the commit that exists. The head now comes from `canon pr head`, which resolves the tip through `git ls-remote` and reports which commit each source names, and the object's head stays behind it as the fallback for a target whose CLI predates the verb. Both halves of the comparison now answer about a commit somebody named rather than one GitHub stamped, which is what lets a `SEEN` be read as covered.
|
|
83
83
|
|
|
84
84
|
## The watch beside it
|
|
85
85
|
|
|
@@ -33,3 +33,9 @@ Hand what it finds to each reviewer as facts in the brief, per `### What the bri
|
|
|
33
33
|
## Launching the reviewer
|
|
34
34
|
|
|
35
35
|
Take the reviewer shape in `orchestrator-launch.md`, or its UI reviewer shape for the UI trigger. Check `canon sessions list --json` for a live `reviewer-<project>-<number>`, or `reviewer-ui-<project>-<number>`, first and message it instead of launching a second, since two reviewers on one pull request post two verdicts. The re-review after a worker's address pass goes back the same way, per `orchestrator-handback.md`.
|
|
36
|
+
|
|
37
|
+
## Local or cloud
|
|
38
|
+
|
|
39
|
+
A code review goes to a cloud reviewer rather than `claude --bg` only on the operator's explicit pick for that dispatch, the same standing the build shape's pick holds in `orchestrator-dispatch.md`. The cloud session costs no local context and no machine slot, which matters when the load trigger fired. It also reads nothing this machine holds, so a review that needs the board, a local preview, or a sandbox stays local.
|
|
40
|
+
|
|
41
|
+
This session hears a cloud reviewer through `poll.ts` alone, which already lists every open pull request from this machine, so the posted review is the announcement and no message arrives. `orchestrator-cloud-launch.md` carries the shape under `## Dispatch a review to a cloud reviewer`. A UI review always stays local.
|