@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
|
@@ -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
|
|
|
@@ -68,7 +68,9 @@ Record the instant just before the post, in UTC, as `<posted-at>`. Step 8 keeps
|
|
|
68
68
|
|
|
69
69
|
```bash
|
|
70
70
|
date -u +%Y-%m-%dT%H:%M:%SZ
|
|
71
|
-
gh
|
|
71
|
+
gh api -X POST 'repos/{owner}/{repo}/pulls/<number>/reviews' -f event=COMMENT -F body=@.canon/tmp/pr/review-ui/body-<number>-<short-sha>.md --jq .html_url
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
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. `-F` reads the file into the body, where `-f` would post the path itself.
|
|
75
|
+
|
|
76
|
+
Never pass `event=APPROVE` or `event=REQUEST_CHANGES`, which carry a state the poll does not read and a weight this pass has not earned.
|
|
@@ -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
|
|
@@ -9,6 +9,14 @@ Run this once `orchestrator-dispatch.md` has cleared a row and picked cloud for
|
|
|
9
9
|
|
|
10
10
|
The shape needs a `SessionStart` hook in the repository that, on the VM alone, copies the plugin skills into `.claude/skills/` and puts `canon` on the path. A repository without one cannot take this shape, so fall back to a local worker there. Where the hook exists, the launch prompt calls `/auto-ship` with no `canon:` prefix, since the copied skills list as project skills.
|
|
11
11
|
|
|
12
|
+
## Contents
|
|
13
|
+
|
|
14
|
+
- [Check the row is unclaimed](#check-the-row-is-unclaimed)
|
|
15
|
+
- [The prompt](#the-prompt)
|
|
16
|
+
- [The create](#the-create)
|
|
17
|
+
- [Recognize the pull request](#recognize-the-pull-request)
|
|
18
|
+
- [Dispatch a review to a cloud reviewer](#dispatch-a-review-to-a-cloud-reviewer)
|
|
19
|
+
|
|
12
20
|
## Check the row is unclaimed
|
|
13
21
|
|
|
14
22
|
A cloud worker never appears in `canon sessions list` and never pushes the derived branch, since the cloud assigns it a `claude/` branch of its own. So the roster and the refs read clear on a row a cloud worker is already building. Run the local check in `orchestrator-dispatch.md` anyway, since a local worker on the same row still collides, then read the two places a cloud worker does show:
|
|
@@ -74,3 +82,32 @@ gh api 'repos/{owner}/{repo}/pulls/<number>/commits' --jq '.[].commit.message' |
|
|
|
74
82
|
```
|
|
75
83
|
|
|
76
84
|
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.
|
|
85
|
+
|
|
86
|
+
## Dispatch a review to a cloud reviewer
|
|
87
|
+
|
|
88
|
+
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.
|
|
89
|
+
|
|
90
|
+
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.
|
|
91
|
+
|
|
92
|
+
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.
|
|
93
|
+
|
|
94
|
+
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.
|
|
95
|
+
|
|
96
|
+
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`:
|
|
97
|
+
|
|
98
|
+
```plaintext
|
|
99
|
+
/role-reviewer
|
|
100
|
+
Then run /review-pr <number> on <owner>/<repo>.
|
|
101
|
+
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>.
|
|
102
|
+
<the cross-branch facts>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
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.
|
|
106
|
+
|
|
107
|
+
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.
|
|
108
|
+
|
|
109
|
+
`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.
|
|
110
|
+
|
|
111
|
+
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.
|
|
112
|
+
|
|
113
|
+
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.
|
|
@@ -5,6 +5,19 @@ description: The plan-answer gate, the collision check before a self-dispatch, t
|
|
|
5
5
|
|
|
6
6
|
Run this at loop step 4, for a `## Run now` row whose plan is verified, in place of handing the worktree to a human. The disjointness gate below is where that row's file set is tested against every track in flight.
|
|
7
7
|
|
|
8
|
+
## Contents
|
|
9
|
+
|
|
10
|
+
- [Derive the candidate](#derive-the-candidate)
|
|
11
|
+
- [Check the plan waits on nobody](#check-the-plan-waits-on-nobody)
|
|
12
|
+
- [Check the branch is unclaimed](#check-the-branch-is-unclaimed)
|
|
13
|
+
- [Hold what this pass already launched](#hold-what-this-pass-already-launched)
|
|
14
|
+
- [Check the file sets are disjoint](#check-the-file-sets-are-disjoint)
|
|
15
|
+
- [Pick the model](#pick-the-model)
|
|
16
|
+
- [Pick local or cloud](#pick-local-or-cloud)
|
|
17
|
+
- [Dispatch](#dispatch)
|
|
18
|
+
- [Fall back to the human](#fall-back-to-the-human)
|
|
19
|
+
- [Stop the loop](#stop-the-loop)
|
|
20
|
+
|
|
8
21
|
## Derive the candidate
|
|
9
22
|
|
|
10
23
|
Run `canon tasks plan-branch <plan> --json` against the row's plan file and read `branch`, `type`, `slug`, and `conforms` off the record.
|
|
@@ -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.
|
|
@@ -20,7 +20,6 @@ interface Review {
|
|
|
20
20
|
|
|
21
21
|
export interface Payload {
|
|
22
22
|
comments?: Comment[]
|
|
23
|
-
headRefOid?: string
|
|
24
23
|
reviews?: Review[]
|
|
25
24
|
}
|
|
26
25
|
|
|
@@ -416,11 +415,80 @@ const parseJson = (text: string): unknown => {
|
|
|
416
415
|
}
|
|
417
416
|
}
|
|
418
417
|
|
|
418
|
+
/**
|
|
419
|
+
* One JSON object per line, which is what `.[] | <projection> | @json` prints
|
|
420
|
+
* across every page. The projection keeps stdout to the fields the mappers
|
|
421
|
+
* read, since a whole REST row carries user objects and links and a long thread
|
|
422
|
+
* could overrun the spawn buffer and read as unreadable. A failed read and a
|
|
423
|
+
* line that does not parse both return null, so neither reads as a thread with
|
|
424
|
+
* nothing on it.
|
|
425
|
+
*/
|
|
426
|
+
const readRows = (
|
|
427
|
+
path: string,
|
|
428
|
+
fields: string,
|
|
429
|
+
): null | Record<string, unknown>[] => {
|
|
430
|
+
const read = run('gh', [
|
|
431
|
+
'api',
|
|
432
|
+
'--paginate',
|
|
433
|
+
`repos/{owner}/{repo}/${path}`,
|
|
434
|
+
'--jq',
|
|
435
|
+
`.[] | {${fields}} | @json`,
|
|
436
|
+
])
|
|
437
|
+
if (!read.isOk) return null
|
|
438
|
+
|
|
439
|
+
const rows: Record<string, unknown>[] = []
|
|
440
|
+
for (const line of read.stdout.split('\n')) {
|
|
441
|
+
if (line.trim() === '') continue
|
|
442
|
+
const row = parseJson(line)
|
|
443
|
+
if (typeof row !== 'object' || row === null) return null
|
|
444
|
+
rows.push(row as Record<string, unknown>)
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
return rows
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const text = (value: unknown): null | string =>
|
|
451
|
+
typeof value === 'string' ? value : null
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* The REST rows carry snake_case names, mapped onto the camelCase shape the
|
|
455
|
+
* classifiers read. `commit_id` becomes `commit.oid`. A pending review has no
|
|
456
|
+
* `submitted_at`, which maps to null.
|
|
457
|
+
*/
|
|
458
|
+
export const toReview = (row: Record<string, unknown>): Review => ({
|
|
459
|
+
body: text(row.body),
|
|
460
|
+
commit: { oid: text(row.commit_id) },
|
|
461
|
+
submittedAt: text(row.submitted_at),
|
|
462
|
+
})
|
|
463
|
+
|
|
464
|
+
export const toComment = (row: Record<string, unknown>): Comment => ({
|
|
465
|
+
body: text(row.body),
|
|
466
|
+
createdAt: text(row.created_at),
|
|
467
|
+
})
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Two reads feed one payload, so either failing makes the pull request
|
|
471
|
+
* unreadable rather than a thread missing half its rows.
|
|
472
|
+
*/
|
|
473
|
+
const readPayload = (number: string): null | Payload => {
|
|
474
|
+
const reviews = readRows(
|
|
475
|
+
`pulls/${number}/reviews?per_page=100`,
|
|
476
|
+
'body, commit_id, submitted_at',
|
|
477
|
+
)
|
|
478
|
+
const comments = readRows(
|
|
479
|
+
`issues/${number}/comments?per_page=100`,
|
|
480
|
+
'body, created_at',
|
|
481
|
+
)
|
|
482
|
+
if (reviews === null || comments === null) return null
|
|
483
|
+
|
|
484
|
+
return { comments: comments.map(toComment), reviews: reviews.map(toReview) }
|
|
485
|
+
}
|
|
486
|
+
|
|
419
487
|
type Snapshot = { baseline: Baseline } | { row: Row }
|
|
420
488
|
|
|
421
489
|
const nowSeconds = (): number => Math.floor(Date.now() / 1000)
|
|
422
490
|
|
|
423
|
-
const readHead = (number: string
|
|
491
|
+
const readHead = (number: string): string => {
|
|
424
492
|
// The tip is the authority for the head, not the pull request object, which
|
|
425
493
|
// lags the ref by up to a minute after a push. The object's head is the
|
|
426
494
|
// fallback for a target on an older binary with no `pr head` verb.
|
|
@@ -432,13 +500,20 @@ const readHead = (number: string, payload: Payload): string => {
|
|
|
432
500
|
? (record as Record<string, unknown>).tip
|
|
433
501
|
: undefined
|
|
434
502
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
503
|
+
if (typeof tip === 'string' && tip !== '') return tip
|
|
504
|
+
|
|
505
|
+
const pull = run('gh', [
|
|
506
|
+
'api',
|
|
507
|
+
`repos/{owner}/{repo}/pulls/${number}`,
|
|
508
|
+
'--jq',
|
|
509
|
+
'.head.sha',
|
|
510
|
+
])
|
|
511
|
+
|
|
512
|
+
return pull.isOk ? pull.stdout.trim() : ''
|
|
438
513
|
}
|
|
439
514
|
|
|
440
515
|
/**
|
|
441
|
-
*
|
|
516
|
+
* The pull read's `mergeable_state` reports unknown until GitHub finishes
|
|
442
517
|
* computing it, which is exactly when a poll asks. merge-tree answers locally
|
|
443
518
|
* against the base this machine has. It exits non-zero on a ref it cannot
|
|
444
519
|
* resolve as well as on a real conflict, so both sides are checked first.
|
|
@@ -530,12 +605,9 @@ const main = (): number => {
|
|
|
530
605
|
// one as GONE. That is a louder wrong answer than the one this script was
|
|
531
606
|
// fixed for, so the run aborts rather than classify on it.
|
|
532
607
|
const listed = run('gh', [
|
|
533
|
-
'
|
|
534
|
-
'
|
|
535
|
-
'
|
|
536
|
-
'open',
|
|
537
|
-
'--json',
|
|
538
|
-
'number',
|
|
608
|
+
'api',
|
|
609
|
+
'--paginate',
|
|
610
|
+
'repos/{owner}/{repo}/pulls?state=open&per_page=100',
|
|
539
611
|
'--jq',
|
|
540
612
|
'.[].number',
|
|
541
613
|
])
|
|
@@ -550,19 +622,10 @@ const main = (): number => {
|
|
|
550
622
|
|
|
551
623
|
const snapshots: Snapshot[] = []
|
|
552
624
|
for (const number of listed.stdout.split(/\s+/).filter((n) => n !== '')) {
|
|
553
|
-
// A
|
|
554
|
-
//
|
|
555
|
-
// single place to surface.
|
|
556
|
-
const
|
|
557
|
-
'pr',
|
|
558
|
-
'view',
|
|
559
|
-
number,
|
|
560
|
-
'--json',
|
|
561
|
-
'headRefOid,reviews,comments',
|
|
562
|
-
])
|
|
563
|
-
const payload = view.isOk
|
|
564
|
-
? (parseJson(view.stdout) as Payload | null)
|
|
565
|
-
: null
|
|
625
|
+
// A read that failed and a pull request with no reviews both arrive as an
|
|
626
|
+
// empty result, so each read returns null on failure and the payload gives
|
|
627
|
+
// the failure a single place to surface.
|
|
628
|
+
const payload = readPayload(number)
|
|
566
629
|
const old = known.get(number)
|
|
567
630
|
const unreadable = (reason: string): void => {
|
|
568
631
|
if (old === undefined) {
|
|
@@ -578,12 +641,12 @@ const main = (): number => {
|
|
|
578
641
|
snapshots.push({ baseline: carryForward(old) })
|
|
579
642
|
}
|
|
580
643
|
|
|
581
|
-
if (payload === null
|
|
644
|
+
if (payload === null) {
|
|
582
645
|
unreadable('could not be read')
|
|
583
646
|
continue
|
|
584
647
|
}
|
|
585
648
|
|
|
586
|
-
const head = readHead(number
|
|
649
|
+
const head = readHead(number)
|
|
587
650
|
if (head === '') {
|
|
588
651
|
unreadable('returned no head')
|
|
589
652
|
continue
|
|
@@ -53,12 +53,10 @@ export const baseRef = (): string => {
|
|
|
53
53
|
if (symbolic.isOk && ref !== '') return ref
|
|
54
54
|
|
|
55
55
|
const repo = run('gh', [
|
|
56
|
-
'
|
|
57
|
-
'
|
|
58
|
-
'--json',
|
|
59
|
-
'defaultBranchRef',
|
|
56
|
+
'api',
|
|
57
|
+
'repos/{owner}/{repo}',
|
|
60
58
|
'--jq',
|
|
61
|
-
'.
|
|
59
|
+
'.default_branch',
|
|
62
60
|
])
|
|
63
61
|
const name = repo.isOk ? repo.stdout.trim() : ''
|
|
64
62
|
|
|
@@ -175,14 +175,11 @@ export const pass = (
|
|
|
175
175
|
|
|
176
176
|
const readPulls = (): Read<string[]> => {
|
|
177
177
|
const listed = run('gh', [
|
|
178
|
-
'
|
|
179
|
-
'
|
|
180
|
-
'
|
|
181
|
-
'open',
|
|
182
|
-
'--json',
|
|
183
|
-
'number,headRefName,title',
|
|
178
|
+
'api',
|
|
179
|
+
'--paginate',
|
|
180
|
+
'repos/{owner}/{repo}/pulls?state=open&per_page=100',
|
|
184
181
|
'--jq',
|
|
185
|
-
'.[] | "PR-OPEN #\\(.number) \\(.
|
|
182
|
+
'.[] | "PR-OPEN #\\(.number) \\(.head.ref) \\(.title)"',
|
|
186
183
|
])
|
|
187
184
|
if (!listed.isOk) return { isOk: false }
|
|
188
185
|
|
|
@@ -18,7 +18,7 @@ The miss is measured. On 2026-10-02 two queries ran twice each with identical te
|
|
|
18
18
|
|
|
19
19
|
The authorities in both pairs were named by the session unprompted once it was asked to, which is the argument for a method over a list. Two queries at one run each show the effect exists and do not size it.
|
|
20
20
|
|
|
21
|
-
Access is a second gap a session cannot derive. Probed on 2026-10-02, Reddit refused its JSON search with a 403 and refused `WebFetch`, while its RSS feed answered 200. X redirected `curl` to a login page and returned 402 to `WebFetch`, so no free route reaches it.
|
|
21
|
+
Access is a second gap a session cannot derive. Probed on 2026-10-02, Reddit refused its JSON search with a 403 and refused `WebFetch`, while its RSS feed answered 200. X redirected `curl` to a login page and returned 402 to `WebFetch`, so no free route reaches it. Re-probed on 2026-10-04, Reddit's RSS feed, its search feed, and its JSON listing and search all answered 403, so the feed that answered two days earlier no longer does.
|
|
22
22
|
|
|
23
23
|
## Must
|
|
24
24
|
|
|
@@ -44,10 +44,12 @@ The procedure that loaded this skill owns what the result is for and where it is
|
|
|
44
44
|
|
|
45
45
|
Each line is a claim measured once, and `${CLAUDE_SKILL_DIR}/REQUIREMENT.md` records the date under its Gap. Sites change their blocking without notice, so re-check a line before relying on it. Add a line only for a failure a session actually hit, and record the date it hit in the requirement beside it.
|
|
46
46
|
|
|
47
|
-
- Reddit
|
|
47
|
+
- Reddit refuses its RSS feed and its JSON search alike with a 403. `WebFetch` is refused for the site, so no keyless route reaches it.
|
|
48
48
|
- X answers nothing without paying or a login. `curl` redirects to a login page and `WebFetch` returns 402.
|
|
49
49
|
- Stop and ask the operator before any route that pays, logs in, or works around a refusal. Never suggest scraping behind a login.
|
|
50
50
|
|
|
51
|
+
Read `${CLAUDE_SKILL_DIR}/references/adopted.md` only when extending this guidance or arguing against a rule in it. It records which comparable skills were read, what was adopted and declined, and why.
|
|
52
|
+
|
|
51
53
|
## Excuses and rebuttals
|
|
52
54
|
|
|
53
55
|
| Excuse | Rebuttal |
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Adopted and declined
|
|
3
|
+
description: Which comparable research skills this skill was read against, what it took from them, what it declined, and which of its rules rest on measurement rather than a source
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Adopted and declined
|
|
7
|
+
|
|
8
|
+
Three third-party research skills were read before this one was written. None of them was imported, and most of the body rests on paired searches measured in this skill's `REQUIREMENT.md` rather than on any of them. A later session extending the skill adds to this file rather than re-arguing an item settled here.
|
|
9
|
+
|
|
10
|
+
## Adopted
|
|
11
|
+
|
|
12
|
+
**Recording each route's failure.** [aghori3004/claude-research-stack](https://github.com/aghori3004/claude-research-stack) keeps one `research-sources` skill that orders sources cheapest first and writes down how each one fails. Adopted as the access note, which records a refusal a session hit rather than leaving the next session to learn it by failing.
|
|
13
|
+
|
|
14
|
+
**Calling the tools directly.** The same skill ships no wrapper scripts, so it calls the command-line tools and servers it names. Adopted as the rule to reach a keyless API with a plain request rather than writing a wrapper for one search.
|
|
15
|
+
|
|
16
|
+
**Treating a paid route as the operator's call.** [rohunvora/x-research-skill](https://github.com/rohunvora/x-research-skill) reaches X through the official API at a cost per search page. Adopted as the stop before any route that pays or logs in, since that cost is one the operator decides rather than one a session discovers.
|
|
17
|
+
|
|
18
|
+
## Declined
|
|
19
|
+
|
|
20
|
+
**A directory of sites per category.** None of the three is one, and each earns its keep on access and cost instead. A site list also rots with nobody noticing, so the body derives a field's authorities per question rather than reading them off a list.
|
|
21
|
+
|
|
22
|
+
**Reaching walled sites through a logged-in browser.** `claude-research-stack` reaches Reddit and X through a desktop Chrome session holding the operator's login. Declined because the body never suggests working around a login.
|
|
23
|
+
|
|
24
|
+
**A bundled engine.** [mvanhorn/last30days-skill](https://github.com/mvanhorn/last30days-skill) is a Python engine with its own test suite and paid aggregators behind it. Declined because a skill here ships no code, and access a session needs repeatedly becomes a `canon` verb.
|
|
25
|
+
|
|
26
|
+
## Stated by no source
|
|
27
|
+
|
|
28
|
+
**Naming the field and deriving its authorities.** No comparable skill takes a thinking step before searching. The step rests on the paired open and scoped searches recorded in `REQUIREMENT.md` under `Gap`, where the scoped run returned primary sources and caught a wrong version number the open run carried.
|
|
29
|
+
|
|
30
|
+
**Preferring a public API over a fetched page.** It rests on the same probes, where the scholarly and reference APIs returned the dates, authors, and identifiers a citation needs and a fetched page returned a summary.
|
|
@@ -9,6 +9,14 @@ What the `install` and `gov` phases both do before they write anything. Stated
|
|
|
9
9
|
once here rather than twice in the body, since the two phases differ in what
|
|
10
10
|
they install rather than in how they read the project.
|
|
11
11
|
|
|
12
|
+
## Contents
|
|
13
|
+
|
|
14
|
+
- [Read the catalogs](#read-the-catalogs)
|
|
15
|
+
- [Detect](#detect)
|
|
16
|
+
- [Resolve arguments](#resolve-arguments)
|
|
17
|
+
- [Gap handling](#gap-handling)
|
|
18
|
+
- [Preview](#preview)
|
|
19
|
+
|
|
12
20
|
## Read the catalogs
|
|
13
21
|
|
|
14
22
|
Run in parallel. Never hardcode stack, rule, or standards names. The
|
|
@@ -12,6 +12,19 @@ user, and writes. The CLI handles validation and regeneration only.
|
|
|
12
12
|
Read `canon docs indexes` from the toolkit if context on the system is needed
|
|
13
13
|
before scanning.
|
|
14
14
|
|
|
15
|
+
## Contents
|
|
16
|
+
|
|
17
|
+
- [Scope](#scope)
|
|
18
|
+
- [Scan](#scan)
|
|
19
|
+
- [Present candidates](#present-candidates)
|
|
20
|
+
- [Draft frontmatter](#draft-frontmatter)
|
|
21
|
+
- [Scaffold the folder index](#scaffold-the-folder-index)
|
|
22
|
+
- [Inject](#inject)
|
|
23
|
+
- [Validate](#validate)
|
|
24
|
+
- [Offer the convention seed](#offer-the-convention-seed)
|
|
25
|
+
- [Closeout](#closeout)
|
|
26
|
+
- [Reference](#reference)
|
|
27
|
+
|
|
15
28
|
## Scope
|
|
16
29
|
|
|
17
30
|
- Bootstrap un-indexed folders only. Skip folders that already contain an `index.md`.
|
|
@@ -12,6 +12,14 @@ Two depths over one contract. Both read the `scripts` block from the project's
|
|
|
12
12
|
own `package.json` and run what is declared there, so neither hardcodes a script
|
|
13
13
|
name and a stack exposing a different set is covered without an edit here.
|
|
14
14
|
|
|
15
|
+
## Contents
|
|
16
|
+
|
|
17
|
+
- [Guards](#guards)
|
|
18
|
+
- [Step 1: read scripts](#step-1-read-scripts)
|
|
19
|
+
- [Step 2: run the chain](#step-2-run-the-chain)
|
|
20
|
+
- [Step 3: report](#step-3-report)
|
|
21
|
+
- [Out of scope](#out-of-scope)
|
|
22
|
+
|
|
15
23
|
## Guards
|
|
16
24
|
|
|
17
25
|
A monorepo syncs each language root into its own subfolder, so the project root
|
|
@@ -19,6 +19,8 @@ description: Write the failing test for a behavior before writing the code that
|
|
|
19
19
|
- Changing what a function already does starts with changing what its test asserts. Run that changed test once before touching the implementation, and confirm it fails against the code as it stands today.
|
|
20
20
|
- A test that already passes before any code changes proves nothing about the new behavior. Widen the assertion or add a case until the suite fails against the current implementation.
|
|
21
21
|
|
|
22
|
+
Read `${CLAUDE_SKILL_DIR}/references/adopted.md` only when extending this guidance or arguing against a rule in it. It records which external sources the loop matches, which parts were declined, and which rules no source states.
|
|
23
|
+
|
|
22
24
|
## What this skill does not cover
|
|
23
25
|
|
|
24
26
|
- Which layer a test belongs at and what makes it a good one. `canon:test-craft` owns that judgment, and step 1 loads it.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Adopted and declined
|
|
3
|
+
description: Which external sources the test-first loop draws on, which parts it declined, and which of its rules no source states, so a later session extends the position instead of re-deriving it
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Adopted and declined
|
|
7
|
+
|
|
8
|
+
The loop predates this ledger, so this file records the sources it matches rather than the ones it was drafted from. Each was opened before it was cited. A later session extending the skill adds to this file rather than re-arguing an item settled here.
|
|
9
|
+
|
|
10
|
+
## Adopted
|
|
11
|
+
|
|
12
|
+
**The loop itself.** Kent Beck's [Canon TDD](https://newsletter.kentbeck.com/p/canon-tdd) states it as turning one item into "an actual, concrete, runnable test", changing the code "to make the test (& all previous tests) pass", then refactoring. Adopted as steps 2, 4, 5, and 6, with the whole suite run after the pass rather than the new test alone.
|
|
13
|
+
|
|
14
|
+
**Making it pass without designing.** The same post warns against "mixing refactoring into making the test pass", and keeps implementation design decisions for the refactor. Adopted as step 4's smallest change and step 6's cleanup of the shortcuts that change left.
|
|
15
|
+
|
|
16
|
+
**Never faking the pass.** The same post lists deleting assertions "so the test pretends to pass" and "copying actual, computed values & pasting them into the expected values" as mistakes. Adopted as step 5's rule that a pass must be the one the test was written to prove.
|
|
17
|
+
|
|
18
|
+
**Refactoring is a step, not an option.** Martin Fowler's [TestDrivenDevelopment](https://martinfowler.com/bliki/TestDrivenDevelopment.html) names "neglecting the third step" as the most common way to get the practice wrong. Adopted as step 6 being mandatory where Canon TDD writes "optionally refactor".
|
|
19
|
+
|
|
20
|
+
## Declined
|
|
21
|
+
|
|
22
|
+
**The test list.** Canon TDD opens by listing every expected variant of the new behavior before writing a test. Declined because a plan already names the behaviors a build covers, and `canon:build-in-slices` owns the order they land in, so a second list here would hold a second copy of the scope.
|
|
23
|
+
|
|
24
|
+
**Refactoring only as far as the session needs.** Canon TDD warns against refactoring further than necessary and calls duplication "a hint, not a command". Declined as a separate rule, since `canon:code-craft` owns when an abstraction earns its place and step 6 already limits refactoring to what step 4 left.
|
|
25
|
+
|
|
26
|
+
## Checked and not a source
|
|
27
|
+
|
|
28
|
+
**Software Engineering at Google, [Testing Overview](https://abseil.io/resources/swe-book/html/ch11.html).** The default shelf's testing chapter covers why tests matter and how a suite is structured, and never addresses writing a test before its code or watching it fail. It informs `canon:test-craft`, which step 1 loads, rather than this loop.
|
|
29
|
+
|
|
30
|
+
**Kent Beck's _Test-Driven Development: By Example_.** The book is the loop's origin, and no openable copy was available to cite a version from. Canon TDD is Beck's own later restatement and stands in for it here.
|
|
31
|
+
|
|
32
|
+
## Stated by no source
|
|
33
|
+
|
|
34
|
+
**Confirming the failure is the right one.** Step 3's rule, that a test failing for a typo or a wrong signature is a defect in the test, comes from the failure recorded in this skill's `REQUIREMENT.md` under `Gap` rather than from any source above.
|
|
35
|
+
|
|
36
|
+
**Extending existing behavior.** The rule that a changed assertion must fail against the code as it stands today is the same failure applied to an edit, and carries no external source.
|
|
@@ -67,4 +67,4 @@ Report each file written or updated by its full path from the project root.
|
|
|
67
67
|
|
|
68
68
|
## Source material
|
|
69
69
|
|
|
70
|
-
The position on external writing guidance sits in `${CLAUDE_SKILL_DIR}/references/
|
|
70
|
+
The position on external writing guidance sits in `${CLAUDE_SKILL_DIR}/references/adopted.md`, which states what this skill adopted, what it declined, and the measurement behind each. Read it before importing a rule from outside this corpus. Do not re-derive that position, since a second answer will disagree with the recorded one.
|
|
@@ -9,6 +9,19 @@ Each pattern below is a shape rather than a token, so no closed word set matches
|
|
|
9
9
|
|
|
10
10
|
Work top down. The first three account for most of what a reader calls machine-written.
|
|
11
11
|
|
|
12
|
+
## Contents
|
|
13
|
+
|
|
14
|
+
- [Rule of three](#rule-of-three)
|
|
15
|
+
- [Synonym cycling](#synonym-cycling)
|
|
16
|
+
- [False ranges](#false-ranges)
|
|
17
|
+
- [Inline-header lists](#inline-header-lists)
|
|
18
|
+
- [Adverb propping](#adverb-propping)
|
|
19
|
+
- [Passive voice with an unnamed actor](#passive-voice-with-an-unnamed-actor)
|
|
20
|
+
- [Closing restatement](#closing-restatement)
|
|
21
|
+
- [Question as transition](#question-as-transition)
|
|
22
|
+
- [Escalating parallel clauses](#escalating-parallel-clauses)
|
|
23
|
+
- [Empty superlative frames](#empty-superlative-frames)
|
|
24
|
+
|
|
12
25
|
## Rule of three
|
|
13
26
|
|
|
14
27
|
A triad wherever a list appears, whatever the subject actually holds. Three adjectives, three clauses, three examples, over and over, until the count is the only thing a reader can predict.
|
package/docs/agents/canvas.md
CHANGED
|
@@ -5,7 +5,7 @@ description: The canvas server, its content format, and the verbs that list, arr
|
|
|
5
5
|
|
|
6
6
|
# Canvas
|
|
7
7
|
|
|
8
|
-
`canon canvas` serves a local canvas of pages and HTML frames and gives an agent the verbs the operator's mouse has. A page is a folder under `.canon/canvas/`, a frame is one HTML file in it, and `layout.json` beside them holds each frame's box.
|
|
8
|
+
`canon canvas` serves a local canvas of pages and HTML frames and gives an agent the verbs the operator's mouse has. A page is a folder under `.canon/canvas/`, a frame is one HTML file in it, and `layout.json` beside them holds each frame's box. `standards/canvas.md` fixes that content format, and the `canvas` skill carries the procedure a session follows to drive the canvas.
|
|
9
9
|
|
|
10
10
|
## Serve
|
|
11
11
|
|
|
@@ -39,7 +39,9 @@ A pick is refused as `address-mismatch` when the browser builds elements the fil
|
|
|
39
39
|
|
|
40
40
|
`text` replaces the text of an element holding text alone and refuses one holding other elements as `not-text-only`. Every byte outside the element stays as it was. `--json` emits the page, frame, file, path, and the file's new `hash`.
|
|
41
41
|
|
|
42
|
-
The operator edits the same set from the inspector, which posts to the same writer with the hash the frame was served with. An edit made against a version of the file Claude has since rewritten is refused as `stale-address` and the shell reloads the frame, so nothing lands on a shifted element.
|
|
42
|
+
The operator edits the same set from the inspector, which posts to the same writer with the hash the frame was served with. An edit made against a version of the file Claude has since rewritten is refused as `stale-address` and the shell reloads the frame, so nothing lands on a shifted element. Both writers take the frame file's lock, and an edit that keeps the selected element where it was keeps the selection fresh.
|
|
43
|
+
|
|
44
|
+
A color picked from the project's tokens is written as `var(--<name>)`, and below full opacity as a `color-mix()` of that `var()` with `transparent` at the opacity percent, so it keeps following the theme. A browser older than 2023 drops a `color-mix()` value, which leaves the property unset there. A hex is written as six digits, or eight below full opacity.
|
|
43
45
|
|
|
44
46
|
Inline style beats a class, so an operator's edit masks a class change Claude makes later. Read a frame's inline styles before restyling it, and remove one the change should replace. The inspector marks a color or background set inline with the word `raw` when its value names no `var()` and is not `currentColor` or a CSS-wide keyword, since that value stays fixed when the theme changes. A property outside the set, or a value carrying `;`, `{`, `}`, `<`, `>`, or a line break, is refused as `invalid-edit`.
|
|
45
47
|
|
package/docs/agents/capture.md
CHANGED
|
@@ -44,7 +44,7 @@ Neither digest is ever written by hand. A digest is what the gate compares, so a
|
|
|
44
44
|
|
|
45
45
|
Each source renders at `deviceScaleFactor` 2 with a transparent background, and the success line reports the pixel dimensions the element wrapped to. Size is reported and never asserted. The height of a terminal frame is whatever its text wrapped to at a fixed width, so pinning that number would harden an accident.
|
|
46
46
|
|
|
47
|
-
What is asserted is the font. The command reads the first family the captured element
|
|
47
|
+
What is asserted is the font. The command reads the first family of each rendered element holding text inside the captured element, and fails when the browser did not resolve one of them, because a fallback face rewraps the block and silently changes the output. A generic keyword as the first family, such as `monospace` or `system-ui`, also fails with its own reason, since it resolves to a different font on each machine, though a quoted `"monospace"` is a family name and goes through the probe. An element holding no text skips the check, since there is nothing to rewrap. Sources therefore name a real font rather than relying on `monospace`. A source that cannot render reports its own line and exits 1 without dropping the rest of the batch.
|
|
48
48
|
|
|
49
49
|
A run refuses before it reads anything else when `--selector` is absent, naming the flag and pointing at `--help`. Ordering it first is what keeps the message about the invocation: the source defaults to `assets`, so checking that first would answer `assets not found` from whatever directory the caller happened to be in and say nothing about the flag that was actually missing. That source default is a deliberate choice rather than one inherited from some general convention: a missing folder still refuses loud, naming the argument, once the flag check passes, where the selector's dropped default cropped the wrong region in silence.
|
|
50
50
|
|