@erclx/canon 5.26.0 → 5.27.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 -2
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/api-design/SKILL.md +2 -0
- package/claude/skills/auto-ship/SKILL.md +3 -11
- package/claude/skills/auto-ship/references/review-findings.md +20 -0
- package/claude/skills/backlog-triage/SKILL.md +2 -0
- package/claude/skills/bash-cli-script/SKILL.md +2 -0
- package/claude/skills/build-in-slices/SKILL.md +2 -0
- package/claude/skills/canon-cli/SKILL.md +2 -0
- package/claude/skills/canon-feedback/SKILL.md +2 -0
- package/claude/skills/canon-feedback-triage/SKILL.md +2 -0
- package/claude/skills/canon-operator/SKILL.md +2 -0
- package/claude/skills/canon-rollout/SKILL.md +3 -11
- package/claude/skills/canon-rollout/references/failure-recovery.md +20 -0
- package/claude/skills/canvas/SKILL.md +13 -0
- package/claude/skills/ci-workflow/SKILL.md +2 -0
- package/claude/skills/code-craft/SKILL.md +2 -0
- package/claude/skills/codebase-layout/SKILL.md +2 -0
- package/claude/skills/context-fold/SKILL.md +3 -17
- package/claude/skills/context-fold/references/completion-report.md +26 -0
- package/claude/skills/create-rule/SKILL.md +2 -0
- package/claude/skills/create-skill/SKILL.md +2 -0
- package/claude/skills/create-standard/SKILL.md +2 -0
- package/claude/skills/decision-escalate/SKILL.md +2 -0
- package/claude/skills/deploy-app/SKILL.md +2 -0
- package/claude/skills/deprecation-migration/SKILL.md +2 -0
- package/claude/skills/design-extract/SKILL.md +2 -0
- package/claude/skills/design-taste/SKILL.md +2 -0
- package/claude/skills/docs-sync/SKILL.md +2 -0
- package/claude/skills/document-health/SKILL.md +2 -0
- package/claude/skills/draft-and-pick/SKILL.md +2 -0
- package/claude/skills/draft-doc/SKILL.md +2 -0
- package/claude/skills/draft-doc/references/wiki.md +1 -1
- package/claude/skills/draft-figure/SKILL.md +2 -0
- package/claude/skills/draft-identity/SKILL.md +2 -0
- package/claude/skills/draft-ready/SKILL.md +2 -0
- package/claude/skills/draft-screencast/SKILL.md +2 -0
- package/claude/skills/draft-slides/SKILL.md +2 -0
- package/claude/skills/git-branch/SKILL.md +2 -0
- package/claude/skills/git-commit/SKILL.md +2 -0
- package/claude/skills/git-followup/SKILL.md +2 -0
- package/claude/skills/git-issue/SKILL.md +2 -0
- package/claude/skills/git-pr/SKILL.md +2 -0
- package/claude/skills/git-pr/references/evidence.md +6 -6
- package/claude/skills/git-ship/SKILL.md +2 -0
- package/claude/skills/git-split/SKILL.md +2 -0
- package/claude/skills/git-stage/SKILL.md +2 -0
- package/claude/skills/git-worktree/SKILL.md +2 -0
- package/claude/skills/index-lookup/SKILL.md +2 -0
- package/claude/skills/markdown-propose/SKILL.md +2 -0
- package/claude/skills/memory-capture/SKILL.md +2 -0
- package/claude/skills/memory-review/SKILL.md +2 -0
- package/claude/skills/plan-feature/SKILL.md +2 -0
- package/claude/skills/plan-groundwork/SKILL.md +2 -0
- package/claude/skills/plan-intake/SKILL.md +2 -0
- package/claude/skills/plan-intake-answer/SKILL.md +2 -0
- package/claude/skills/project-commands/SKILL.md +2 -0
- package/claude/skills/read-frames/SKILL.md +2 -0
- package/claude/skills/record-screencast/SKILL.md +2 -0
- package/claude/skills/repo-metadata/SKILL.md +2 -0
- package/claude/skills/restate-plainly/SKILL.md +2 -0
- package/claude/skills/review-address/SKILL.md +3 -7
- package/claude/skills/review-address/references/post-review-findings.md +16 -0
- package/claude/skills/review-branch/SKILL.md +2 -0
- package/claude/skills/review-craft/SKILL.md +2 -0
- package/claude/skills/review-craft/references/ui.md +2 -2
- package/claude/skills/review-pr/SKILL.md +4 -8
- package/claude/skills/review-pr/references/body-variants.md +52 -0
- package/claude/skills/review-pr/references/key-changes.md +16 -0
- package/claude/skills/review-pr/references/post.md +15 -43
- package/claude/skills/review-ui/SKILL.md +3 -1
- package/claude/skills/role-orchestrator/SKILL.md +5 -14
- package/claude/skills/role-orchestrator/references/orchestrator-handback.md +1 -1
- package/claude/skills/role-orchestrator/references/orchestrator-inbound.md +23 -0
- package/claude/skills/role-orchestrator/references/orchestrator-launch.md +5 -33
- package/claude/skills/role-orchestrator/references/orchestrator-poll.md +1 -21
- package/claude/skills/role-orchestrator/references/orchestrator-review-dispatch.md +1 -1
- package/claude/skills/role-orchestrator/references/orchestrator-reviewer-launch.md +38 -0
- package/claude/skills/role-orchestrator/references/orchestrator-watch.md +26 -0
- package/claude/skills/role-planner/SKILL.md +2 -0
- package/claude/skills/role-reviewer/REQUIREMENT.md +1 -1
- package/claude/skills/role-reviewer/SKILL.md +2 -0
- package/claude/skills/role-worker/SKILL.md +2 -0
- package/claude/skills/search-craft/SKILL.md +2 -0
- package/claude/skills/seed-sync/SKILL.md +2 -0
- package/claude/skills/session-compact/SKILL.md +2 -0
- package/claude/skills/session-map/SKILL.md +2 -0
- package/claude/skills/session-relay/SKILL.md +2 -0
- package/claude/skills/session-resume/SKILL.md +2 -0
- package/claude/skills/session-worktree/SKILL.md +3 -11
- package/claude/skills/session-worktree/references/install-command.md +20 -0
- package/claude/skills/sketch-design/SKILL.md +2 -0
- package/claude/skills/systematic-debugging/SKILL.md +2 -0
- package/claude/skills/target-check/SKILL.md +2 -0
- package/claude/skills/target-setup/SKILL.md +2 -0
- package/claude/skills/task-board/SKILL.md +2 -0
- package/claude/skills/teach-workspace/SKILL.md +5 -15
- package/claude/skills/teach-workspace/references/lesson-build.md +75 -0
- package/claude/skills/teach-workspace/references/lesson-craft.md +13 -69
- package/claude/skills/teach-workspace/references/revisit.md +22 -0
- package/claude/skills/test-craft/SKILL.md +2 -0
- package/claude/skills/test-first/SKILL.md +2 -0
- package/claude/skills/ui-checklist/SKILL.md +2 -0
- package/claude/skills/ux-audit/SKILL.md +2 -0
- package/claude/skills/ux-measure/SKILL.md +2 -0
- package/claude/skills/ux-walkthrough/SKILL.md +2 -0
- package/claude/skills/video-craft/SKILL.md +2 -0
- package/claude/skills/write-human/SKILL.md +2 -0
- package/claude/skills/youtube-transcripts/SKILL.md +2 -0
- package/docs/agents/canvas.md +23 -7
- package/docs/agents/capture.md +5 -5
- package/docs/agents/commands.md +2 -2
- package/docs/agents/index.md +3 -3
- package/docs/agents/install-and-sync.md +7 -0
- package/docs/agents/key-changes.md +1 -1
- package/docs/agents/pr-evidence.md +38 -15
- package/docs/agents/pr-local.md +19 -13
- package/docs/agents/pr-preview.md +44 -21
- package/docs/agents/sandbox.md +28 -4
- package/docs/agents/scripting.md +8 -1
- package/docs/agents/skills-audit.md +2 -1
- package/docs/workflow/operating-model.md +5 -6
- package/docs/workflow/parallel-features.md +22 -1
- package/docs/workflow/skill-map.md +3 -1
- package/docs/workflow/zshrc-aliases.md +1 -1
- package/package.json +1 -1
- package/scripts/core/regen-hero.ts +1 -1
- package/scripts/lib/sandbox-path.sh +6 -162
- package/src/audits/run.ts +1 -1
- package/src/canvas/address.ts +12 -3
- package/src/canvas/client/inspector/align-grid.tsx +162 -0
- package/src/canvas/client/inspector/color-field.tsx +201 -171
- package/src/canvas/client/inspector/color-inputs.tsx +139 -0
- package/src/canvas/client/inspector/color-picker.tsx +668 -0
- package/src/canvas/client/inspector/color-space.ts +240 -0
- package/src/canvas/client/inspector/field.tsx +5 -1
- package/src/canvas/client/inspector/fields.css +395 -6
- package/src/canvas/client/inspector/size-field.tsx +69 -0
- package/src/canvas/client/inspector/values.ts +43 -1
- package/src/canvas/client/inspector.tsx +190 -4
- package/src/canvas/client/selection.tsx +197 -0
- package/src/canvas/client/shell.css +104 -12
- package/src/canvas/client/state.ts +69 -9
- package/src/canvas/client/surface.tsx +147 -22
- package/src/canvas/content.ts +50 -1
- package/src/canvas/edit.ts +37 -4
- package/src/canvas/editing.ts +203 -0
- package/src/canvas/global.d.ts +15 -0
- package/src/canvas/server.ts +149 -12
- package/src/canvas/tokens.ts +59 -17
- package/src/census/count.ts +1 -1
- package/src/claude/seeds.ts +1 -1
- package/src/claude/skills-audit.ts +28 -0
- package/src/claude/skills-drift.ts +1 -1
- package/src/claude/skills-families.ts +36 -0
- package/src/claude/skills-list.ts +76 -15
- package/src/commands/audits.ts +2 -2
- package/src/commands/canvas.ts +179 -2
- package/src/commands/claude.ts +67 -9
- package/src/commands/design.ts +142 -2
- package/src/commands/gov.ts +1 -1
- package/src/commands/hooks.ts +1 -1
- package/src/commands/indexes.ts +1 -1
- package/src/commands/init.ts +1 -1
- package/src/commands/intake.ts +1 -1
- package/src/commands/migrate.ts +1 -1
- package/src/commands/pr.ts +105 -58
- package/src/commands/records.ts +1 -1
- package/src/commands/repo.ts +1 -1
- package/src/commands/sandbox.ts +54 -2
- package/src/commands/sessions.ts +1 -1
- package/src/commands/sync.ts +1 -1
- package/src/commands/tasks.ts +2 -2
- package/src/commands/teach.ts +1 -1
- package/src/commands/transcripts.ts +1 -1
- package/src/commands/worktrees.ts +1 -1
- package/src/comments/scan.ts +1 -1
- package/src/comments/trend.ts +1 -1
- package/src/context/citations.ts +1 -1
- package/src/context/classify/extract.ts +2 -2
- package/src/design/project-fonts.ts +392 -0
- package/src/feedback/github.ts +1 -1
- package/src/gate/measures.ts +47 -9
- package/src/gate/sequencer.ts +1 -1
- package/src/gate/stages.ts +9 -0
- package/src/{git-files.ts → git/files.ts} +2 -2
- package/src/{git-ignore.ts → git/ignore.ts} +1 -1
- package/src/{worktree.ts → git/worktree.ts} +1 -1
- package/src/gov/citations.ts +1 -1
- package/src/gov/install.ts +1 -1
- package/src/gov/superseded.ts +1 -1
- package/src/gov/test-order.ts +2 -2
- package/src/help.ts +1 -1
- package/src/hooks/post-merge.ts +1 -1
- package/src/indexes/walk.ts +1 -1
- package/src/labels/audit.ts +1 -1
- package/src/markdown/files.ts +1 -1
- package/src/migrate/apply.ts +1 -1
- package/src/pr/bijection.ts +1 -1
- package/src/pr/evidence.ts +96 -43
- package/src/pr/local.ts +16 -10
- package/src/pr/preview.ts +78 -2
- package/src/records/backup.ts +1 -1
- package/src/records/stale-canonical.ts +1 -1
- package/src/sandbox/headless/dispatch.ts +264 -0
- package/src/sandbox/headless/run.ts +653 -0
- package/src/sandbox/headless/watch.ts +145 -0
- package/src/sandbox/provision.ts +12 -1
- package/src/sandbox/tree.ts +4 -8
- package/src/secrets/scan.ts +1 -1
- package/src/sessions/claim.ts +1 -1
- package/src/sessions/resolve.ts +1 -1
- package/src/sessions/transfer/bundle.ts +1 -1
- package/src/slides/package/fonts.ts +6 -2
- package/src/standards/audit.ts +2 -2
- package/src/sync/check.ts +2 -2
- package/src/sync/engine.ts +2 -2
- package/src/sync/git.ts +1 -1
- package/src/sync/history.ts +1 -1
- package/src/sync/reverse.ts +1 -1
- package/src/sync/target.ts +1 -1
- package/src/sync/workflow.ts +1 -1
- package/src/targets/pulls.ts +2 -2
- package/src/targets/sweep.ts +2 -2
- package/src/tasks/reach.ts +1 -1
- package/src/tasks/trunk.ts +1 -1
- package/src/tooling/inject.ts +1 -1
- package/src/tooling/subfolder.ts +1 -1
- package/src/web/readme-citations.ts +1 -1
- package/src/worktrees/bare-flag.ts +1 -1
- package/src/worktrees/reclaim.ts +2 -2
- package/src/worktrees/remove.ts +1 -1
- package/standards/canvas.md +2 -1
- package/standards/skill.md +1 -0
- package/standards/wiki.md +12 -3
- package/tooling/base/configs/.github/workflows/pr-local-preview.yml +2 -2
- package/scripts/lib/sandbox-dispatch.sh +0 -190
- /package/src/{client-commands.ts → gate/client-commands.ts} +0 -0
- /package/src/{git-base.ts → git/base.ts} +0 -0
- /package/src/{git-env.ts → git/env.ts} +0 -0
- /package/src/{copy.ts → sync/copy.ts} +0 -0
- /package/src/{target.ts → targets/validate.ts} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: The pull request preview deploy
|
|
3
|
-
description: How canon pr preview finds a fenced Cloudflare Pages deploy, dispatches it on a pull request's branch, reads the alias the run prints, the refusal reasons it names, and why an unfenced deploy is refused before anything runs
|
|
3
|
+
description: How canon pr preview finds a fenced Cloudflare Pages deploy, refuses a branch its push filter does not serve, dispatches it on a pull request's branch, reads the alias the run prints, the refusal reasons it names, and why an unfenced deploy is refused before anything runs
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# The pull request preview deploy
|
|
@@ -8,8 +8,8 @@ description: How canon pr preview finds a fenced Cloudflare Pages deploy, dispat
|
|
|
8
8
|
`canon pr preview` publishes a pull request's branch to a Cloudflare Pages
|
|
9
9
|
preview and reports the address. `git-pr` calls it after the evidence
|
|
10
10
|
comparison, then re-renders that comment through `canon pr evidence --preview`
|
|
11
|
-
so the link
|
|
12
|
-
rather than screenshots and a checklist alone.
|
|
11
|
+
so the link sits on the address line under its heading. A reviewer gets a page
|
|
12
|
+
to click into before merging rather than screenshots and a checklist alone.
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
15
|
canon pr preview --json
|
|
@@ -31,6 +31,24 @@ the first file, in path order, that meets four tests:
|
|
|
31
31
|
The workflow is read before the pull request, so a project with no fenced
|
|
32
32
|
deploy is refused without a network call.
|
|
33
33
|
|
|
34
|
+
## Which branches it serves
|
|
35
|
+
|
|
36
|
+
A dispatch runs the workflow whatever its `on.push.paths` filter says, so a
|
|
37
|
+
branch that touched nothing the site builds from would get a preview showing
|
|
38
|
+
none of its change. The verb reads the pull request's changed paths and matches
|
|
39
|
+
them against the picked workflow's own `on.push.paths` or `on.push.paths-ignore`
|
|
40
|
+
list, applying `!` patterns in order the way GitHub does. When nothing matches,
|
|
41
|
+
it refuses as `unserved` and dispatches nothing. `--check` answers `unserved`
|
|
42
|
+
the same way.
|
|
43
|
+
|
|
44
|
+
The push filter is the one statement of what the site builds from, since it
|
|
45
|
+
already decides when production deploys. A workflow with neither list, with no
|
|
46
|
+
push trigger, or that does not parse as YAML serves every change, so a project
|
|
47
|
+
whose every change ships keeps minting for every branch.
|
|
48
|
+
|
|
49
|
+
A removed path counts, the same as GitHub's own push filter counts it, so a
|
|
50
|
+
branch whose only site change deletes a file still gets a preview.
|
|
51
|
+
|
|
34
52
|
## Why an unfenced deploy is refused
|
|
35
53
|
|
|
36
54
|
Cloudflare Pages publishes a deploy that names no branch to production. A
|
|
@@ -62,19 +80,21 @@ keeps the match independent of the local clock.
|
|
|
62
80
|
|
|
63
81
|
`reason` on the record is what a caller branches on, not the exit code:
|
|
64
82
|
|
|
65
|
-
| Reason
|
|
66
|
-
|
|
|
67
|
-
| `ok`
|
|
68
|
-
| `no-deploy`
|
|
69
|
-
| `
|
|
70
|
-
| `
|
|
71
|
-
| `
|
|
72
|
-
| `
|
|
73
|
-
| `
|
|
74
|
-
| `
|
|
75
|
-
| `
|
|
76
|
-
| `
|
|
77
|
-
| `
|
|
83
|
+
| Reason | What it means |
|
|
84
|
+
| -------------------- | -------------------------------------------------------------------------- |
|
|
85
|
+
| `ok` | The preview was published. `url` carries the address and `runId` the run. |
|
|
86
|
+
| `no-deploy` | No dispatchable workflow runs `pages deploy`. |
|
|
87
|
+
| `unserved` | The push path filter matches no changed path, so nothing was dispatched. |
|
|
88
|
+
| `unreadable-changes` | The pull request's changed paths could not be read. |
|
|
89
|
+
| `unfenced` | The deploy passes no `--branch`, so nothing was dispatched. |
|
|
90
|
+
| `no-alias` | The workflow prints no alias line, or the finished run's log carried none. |
|
|
91
|
+
| `run-failed` | The deploy run finished without succeeding. `runId` names it. |
|
|
92
|
+
| `timeout` | The run did not finish inside `--timeout` minutes, 15 by default. |
|
|
93
|
+
| `bad-timeout` | `--timeout` was not a positive number. |
|
|
94
|
+
| `gh-missing` | `gh` is not on the path. |
|
|
95
|
+
| `gh-failed` | `gh` could not read the pull request, or list or dispatch the workflow. |
|
|
96
|
+
| `no-branch` | The pull request carries no head branch name. |
|
|
97
|
+
| `check-timeout` | `--check` was combined with `--timeout`. |
|
|
78
98
|
|
|
79
99
|
The exit is 0 on `ok` and 1 on every refusal. A `timeout` leaves the run
|
|
80
100
|
going, so the preview may still land after the verb has given up on it.
|
|
@@ -107,10 +127,11 @@ reason but `fresh`.
|
|
|
107
127
|
|
|
108
128
|
## How the address reaches the pull request
|
|
109
129
|
|
|
110
|
-
`canon pr evidence --preview <url>` puts `**Preview:** <url>` on the
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
change still gets one comment a later
|
|
130
|
+
`canon pr evidence --preview <url>` puts `**Preview:** <url>` on the address
|
|
131
|
+
line under the evidence body's `## Evidence` heading. With no evidence image in
|
|
132
|
+
the diff, the body is the heading, that line, and the trailing marker alone, so
|
|
133
|
+
a pull request whose screenshots did not change still gets one comment a later
|
|
134
|
+
call can find and edit.
|
|
114
135
|
|
|
115
136
|
A later `canon pr evidence` run without `--preview` reads the address off the
|
|
116
137
|
marked comment and carries it into the new body. `git-followup` re-renders the
|
|
@@ -123,7 +144,9 @@ way, which is what keeps that push from wiping boxes a reviewer already ticked.
|
|
|
123
144
|
A `pull_request` trigger on `closed` in the same workflow runs a cleanup job
|
|
124
145
|
that deletes every preview deployment Cloudflare holds for the branch. The
|
|
125
146
|
deploy job carries `if: github.event_name != 'pull_request'`, so a closed pull
|
|
126
|
-
request never deploys.
|
|
147
|
+
request never deploys. The same close runs `canon pr local --remove`, which
|
|
148
|
+
drops the address line from the evidence comment, hosted link included, so the
|
|
149
|
+
comment never links a deployment that no longer exists.
|
|
127
150
|
|
|
128
151
|
## What this does not cover
|
|
129
152
|
|
package/docs/agents/sandbox.md
CHANGED
|
@@ -39,15 +39,39 @@ The verdict `state` is `pass`, `fail`, or `unchecked`. An arm with no `expect.to
|
|
|
39
39
|
|
|
40
40
|
Omitting `--writes`, `--escapes`, or `--envelope` does not silently drop the assertion kinds that need them. Write scope, escape scope, the turn ceiling, and the reply assertion report as unchecked and appear in the count, so the standalone command cannot claim more coverage than it had. Supplying `--escapes` without `--escapes-watched` reports the same way: a zero-escape result with no root confirmed watched is unmeasured rather than a pass. A verdict never reports `pass` with zero assertions.
|
|
41
41
|
|
|
42
|
-
An arm declaring `escape_scope` asserts a bound on
|
|
42
|
+
An arm declaring `escape_scope` asserts a bound on the headless runner's own escape watch rather than on the sandbox tree. `write_scope` skips when the run wrote nothing, since a required output missing is itself a finding, but `escape_scope` passes on zero escapes outright, since a clean run producing none is the expected outcome for a destination nothing requires a skill to touch, provided `--escapes-watched` confirms a root held something to watch. Declaring `escape_scope = []` asserts that a correct run reaches none of the watched destinations at all. `canon/context/sandbox/isolation.md` names what the watch reaches and what it cannot.
|
|
43
43
|
|
|
44
44
|
An envelope that parses but carries no `result` field skips the reply assertion the same way an absent file does. An envelope carrying an empty `result` fails it, since a run that returned no text is a finding rather than a gap in the input.
|
|
45
45
|
|
|
46
46
|
Exit 0 means `pass` or `unchecked`. Exit 1 means `fail`, or a caller error: a malformed target, or a sandbox that was never provisioned. A missing sandbox reports as an error rather than a failed verdict, because failing every path assertion would read as a skill that did nothing. `--strict` moves `unchecked` to exit 1 for a caller that has finished arming its scenarios.
|
|
47
47
|
|
|
48
|
+
## Headless runs
|
|
49
|
+
|
|
50
|
+
`canon sandbox run` provisions a scenario, drives one `claude -p` session over it, and scores the result with `canon sandbox check`. `sandbox/run.sh` is a wrapper that passes its arguments to the verb, so either spelling runs the same code.
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
canon sandbox run <category>:<command> "<prompt>" [arm]
|
|
54
|
+
canon sandbox run claude:plan-feature "/canon:plan-feature add a widget" small
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Every run bills a real session through the `claude` binary on `PATH`. The session runs with `--plugin-dir` pointed at this checkout's `claude/`, so it loads the branch's skills rather than an installed copy, and provisioning injects no skill into the tree for the same reason. Four variables override the flags it hands to `claude -p`:
|
|
58
|
+
|
|
59
|
+
| Variable | Default |
|
|
60
|
+
| ---------------------------------- | -------------------------------- |
|
|
61
|
+
| `CANON_SKILL_TEST_MODEL` | `sonnet` |
|
|
62
|
+
| `CANON_SKILL_TEST_TOOLS` | `Bash,Read,Glob,Grep,Edit,Write` |
|
|
63
|
+
| `CANON_SKILL_TEST_MAX_TURNS` | `30` |
|
|
64
|
+
| `CANON_SKILL_TEST_PERMISSION_MODE` | `bypassPermissions` |
|
|
65
|
+
|
|
66
|
+
The max-turns value is the only budget a run has. An arm's `max_turns` is asserted after the run and cannot raise it.
|
|
67
|
+
|
|
68
|
+
Stdout carries one JSON record: the session's envelope with `verdict`, `escapes`, and `sessions` merged in, described under the skills census below. Stderr carries the framed timeline, the provisioning output, the session's own stderr, the verdict report, and a warning for each escape, new session record, or reaped process. An envelope that does not parse still prints its verdict, under `is_error: true`.
|
|
69
|
+
|
|
70
|
+
Exit codes follow whichever step stopped the run. Provisioning's code ends a run before the session starts, and a session that exits non-zero ends it with that code after writing a dead-run record, `{is_error, exit_code, raw_output}`, to the same runs folder the merged record uses. Otherwise the verdict's code stands, except that an envelope that does not parse forces 1. An interrupt reaps the session's process group before the runner exits.
|
|
71
|
+
|
|
48
72
|
## Provisioning equivalence
|
|
49
73
|
|
|
50
|
-
`canon sandbox equivalence` proves a change moved nothing in what scenarios provision. It provisions every arm of the named targets on a base ref and on the working checkout, builds a manifest of each tree, and names what differs. It runs offline and never calls `sandbox
|
|
74
|
+
`canon sandbox equivalence` proves a change moved nothing in what scenarios provision. It provisions every arm of the named targets on a base ref and on the working checkout, builds a manifest of each tree, and names what differs. It runs offline and never calls `canon sandbox run` or a `claude` binary, so no arm it runs bills a model.
|
|
51
75
|
|
|
52
76
|
```bash
|
|
53
77
|
canon sandbox equivalence claude --base origin/main --json
|
|
@@ -95,8 +119,8 @@ A skill pairs to a scenario by filename, `<category>-<command>` first and bare `
|
|
|
95
119
|
|
|
96
120
|
`exempt` means no arm should be written and holds only with a reason, declared in `sandbox/exempt.toml` and limited to a harness limit the checker cannot reach past or a skill that writes no artifact. An armed arm outranks an exemption. An exemption naming no shipped skill, or naming one an arm now asserts, exits 1 without `--strict`. Each armed arm reports as `<category>:<command>/<arm>`, so two same-named arms under different scenarios stay distinct.
|
|
97
121
|
|
|
98
|
-
`sandbox
|
|
122
|
+
`canon sandbox run` calls this after a headless run and merges the verdict into the envelope it prints. It also writes that merged record to `.canon/tmp/runs/sandbox/<target>-<arm>-<timestamp>.json` with a `writes` array appended, and logs the path on stderr. Both fields are what a later re-score needs, since `--envelope` and `--writes` read files the run deletes on exit.
|
|
99
123
|
|
|
100
124
|
Two more fields ride alongside the verdict rather than inside it. `escapes` lists what the run wrote under a watched toolkit root, which the verdict cannot assert over because those files sit outside the sandbox tree. `sessions` reports the nested-dispatch bound, carrying `watched` for whether the client's session registry was there to read, `new` for the records that appeared while the run was in flight, `concurrent` for the records present both before and after, and `reap` for what the run found in the session's process group afterwards. Neither field fails a run on its own.
|
|
101
125
|
|
|
102
|
-
`run
|
|
126
|
+
`canon sandbox run` passes `concurrent` through `--concurrent-sessions` to `sandbox check`, and `checkEscapeScope` appends a witness count to an `unbounded escape:` message rather than lets it soften the verdict.
|
package/docs/agents/scripting.md
CHANGED
|
@@ -17,7 +17,7 @@ Use these to discover what's available instead of hardcoding names.
|
|
|
17
17
|
| `canon standards list --json` | Standards docs and the paths each governs |
|
|
18
18
|
| `canon gov list --json` | Governance stacks, rule sets, and unreferenced rules |
|
|
19
19
|
| `canon claude seeds list --json` | Seed doc sources with content |
|
|
20
|
-
| `canon claude skills list --json` | Plugin skills, descriptions, requirement flag
|
|
20
|
+
| `canon claude skills list --json` | Plugin skills, descriptions, requirement flag, family vocabulary |
|
|
21
21
|
| `canon docs list --json` | Consumer docs plus per-domain context |
|
|
22
22
|
| `canon audits list --json` | Audits the set runs, the corpus each reads, and whether it gates |
|
|
23
23
|
|
|
@@ -68,6 +68,13 @@ or unparseable returns an empty description rather than failing the listing, so
|
|
|
68
68
|
one malformed file cannot hide the rest of the catalog. `--names` emits skill
|
|
69
69
|
names one per line.
|
|
70
70
|
|
|
71
|
+
Each skill in `--json` carries `family`, the `metadata.family` its frontmatter
|
|
72
|
+
declares, or `null` where it declares none. A top-level `families` array holds
|
|
73
|
+
the vocabulary as `{ key, group }` entries in skill map order, where `group` is
|
|
74
|
+
the map heading the family's rows sit under. Read a new skill's family from it
|
|
75
|
+
rather than from the map. `--by-family` prints the human listing as one step per
|
|
76
|
+
family, with a trailing step for any skill whose family is missing or unknown.
|
|
77
|
+
|
|
71
78
|
Each entry also carries `requirement`, whether the folder holds a sibling
|
|
72
79
|
`REQUIREMENT.md`. Every skill is meant to carry one, so a `false` is a gap to
|
|
73
80
|
close rather than a recorded exemption, and the flag answers which skills are
|
|
@@ -38,6 +38,7 @@ Each check traces to a line in the standard.
|
|
|
38
38
|
- No ISO date in `SKILL.md` or any file under `references/`, reported as `datedProvenance` in the JSON record's `findings`
|
|
39
39
|
- Each listed practice skill closing with `## Excuses and rebuttals`, `## Red flags`, and `## Before handing over`, and each skill on the wider ledger list carrying `references/adopted.md`, reported together as `practiceShape`
|
|
40
40
|
- Each file under `references/` running over 100 lines opening with a `## Contents` H2, reported as `referenceContents`
|
|
41
|
+
- Each skill under `claude/skills/` declaring a `metadata.family` that names a key in `checkpoints.families`, reported as `family`
|
|
41
42
|
|
|
42
43
|
A body whose frontmatter does not parse reports as declaring neither field rather than ending the run, so one malformed file cannot hide the corpus behind it. A key present with an empty value reads as absent, since a blank `name` would otherwise report as a name disagreeing with every folder. A folder carrying no requirement is reported once under presence rather than counted again for the sections it therefore lacks.
|
|
43
44
|
|
|
@@ -61,7 +62,7 @@ A check with no rule behind it prints an opinion as a defect, which is where the
|
|
|
61
62
|
|
|
62
63
|
## Exit codes
|
|
63
64
|
|
|
64
|
-
Exit codes are `0` for a clean run, `1` for a refusal, and `2` for a skill folder carrying no `REQUIREMENT.md`. Only presence sets a failing code. Name, description, folder, requirement-section, dated-provenance, practice shape,
|
|
65
|
+
Exit codes are `0` for a clean run, `1` for a refusal, and `2` for a skill folder carrying no `REQUIREMENT.md`. Only presence sets a failing code. Name, description, folder, requirement-section, dated-provenance, practice shape, reference contents, and family findings print and return `0`, because each is a judgment a reader settles and failing a push on one would make the check something to route around. In the toolkit, three `bun run check` stages read the same report in-process and fail a push on dated provenance, on practice shape, and on family, since its own corpus is held at zero on all three.
|
|
65
66
|
|
|
66
67
|
No stage reads reference contents, so a long reference without a list stays a report.
|
|
67
68
|
|
|
@@ -11,8 +11,8 @@ loop and without losing the human review gate. One warm session holds the
|
|
|
11
11
|
cross-feature call and reviews. Planning runs there or in a session of its own.
|
|
12
12
|
Cold worker sessions build. The human launches workers and merges.
|
|
13
13
|
|
|
14
|
-
This page covers the roles and the loop. For
|
|
15
|
-
|
|
14
|
+
This page covers the roles and the loop. For merge order and fan-out rules, see
|
|
15
|
+
[Parallel features](parallel-features.md), and for worktree gotchas, [Claude Code and git worktrees](../../wiki/claude/worktrees.md).
|
|
16
16
|
|
|
17
17
|
## Four roles
|
|
18
18
|
|
|
@@ -225,12 +225,11 @@ Unit checks run freely in many worktrees at once.
|
|
|
225
225
|
A dev server, an end-to-end run, and a screenshot run alongside each other on a
|
|
226
226
|
web stack, since every worktree derives its own port. Singleton resources (one
|
|
227
227
|
local model server, one GPU) still serialize, as does any port a stack fixes by
|
|
228
|
-
hand. See
|
|
229
|
-
[Claude Code and git worktrees](../../wiki/claude/worktrees.md) for
|
|
230
|
-
port-collision detail.
|
|
228
|
+
hand. See [Parallel features](parallel-features.md) for merge order and
|
|
229
|
+
[Claude Code and git worktrees](../../wiki/claude/worktrees.md) for port collisions.
|
|
231
230
|
|
|
232
231
|
## Related
|
|
233
232
|
|
|
234
|
-
- [Claude Code and git worktrees](../../wiki/claude/worktrees.md) for the
|
|
233
|
+
- [Claude Code and git worktrees](../../wiki/claude/worktrees.md) for the worktree mechanism and its gotchas
|
|
235
234
|
- [Claude Code subagents](../../wiki/claude/subagents.md) for in-session parallelism without worktrees, and [blocking subagents](../../wiki/claude/permissions.md#blocking-subagents) for the `permissions.deny` rule that stops every spawn
|
|
236
235
|
- `canon/context/claude-plugin/skill-strategy/overview.md` for how the skills in the loop are categorized
|
|
@@ -32,7 +32,7 @@ A constraint naming a track in flight carries the same problem past the handoff,
|
|
|
32
32
|
|
|
33
33
|
## Records at the main root
|
|
34
34
|
|
|
35
|
-
`.canon/plans/`, `.canon/review/`, and `.canon/memory/` all resolve at the main worktree root, so artifacts created in any session are visible from any sibling worktree. A session inside a worktree reads them directly, since the file-editing tools refuse a main-root path but `Read` resolves normally. It writes a whole file through the shell and makes a change inside an existing file through a `canon` verb, which resolves the main root in-process. See [Claude Code and git worktrees](../../wiki/claude/worktrees.md) for the
|
|
35
|
+
`.canon/plans/`, `.canon/review/`, and `.canon/memory/` all resolve at the main worktree root, so artifacts created in any session are visible from any sibling worktree. A session inside a worktree reads them directly, since the file-editing tools refuse a main-root path but `Read` resolves normally. It writes a whole file through the shell and makes a change inside an existing file through a `canon` verb, which resolves the main root in-process. See [Claude Code and git worktrees](../../wiki/claude/worktrees.md) for why the tool refuses the write, and the fan-out section below for the domain-level guidance.
|
|
36
36
|
|
|
37
37
|
The plan's shape is fixed by `standards/plan.md`: the section list, the filename, and the contract its questions keep. Its lifecycle, from the session that writes it to the archive, is `standards/plan-lifecycle.md`. Every question carries a `- Suggested:` line and an empty `- Answer:` slot, and a blank answer accepts the suggestion at execution time. That default is what makes a plan decision-ready in one pass, and it is the opposite of the contract an intake folder keeps, where an empty slot means nobody reached the item.
|
|
38
38
|
|
|
@@ -56,6 +56,27 @@ A branch review report takes the other route and is swept rather than archived.
|
|
|
56
56
|
|
|
57
57
|
`canon:context-fold` decides which task closed by reading the diff rather than the conversation. It resolves a merge base against `origin/main`, unions the committed diff with the working tree and untracked files, then matches unchecked outcomes on the board against what shipped. A task that shipped without ever being discussed still gets marked. Architecture and design stay session-sourced, because a diff cannot carry a judgment, and an architecture write further needs a folded plan naming the slot decision. The requirements record is never folded. A direction change gets one report line naming `canon:document-health`, which reviews that record only when asked.
|
|
58
58
|
|
|
59
|
+
## Shipping a fan-out
|
|
60
|
+
|
|
61
|
+
A fan-out of N worktrees produces N pull requests. The order they merge in matters only when two branches touch the same file.
|
|
62
|
+
|
|
63
|
+
**Collision profile.** Work confined to one domain directory, such as a new skill under its own folder or independent fixes in different subtrees of `scripts/`, rarely collides. These paths are shared hotspots, so a branch touching one serializes rather than fans out, because parallel edits almost always produce a conflict or a stale regenerated file:
|
|
64
|
+
|
|
65
|
+
- `CLAUDE.md`, since cross-domain behavior lives there
|
|
66
|
+
- `tooling/**`, since stack manifests, golden configs, and seeds are tightly coupled
|
|
67
|
+
- `canon/context/context-model/overview.md` and `docs/agents/`, which several domains cross-reference
|
|
68
|
+
- Any folder's `index.md`, since `canon indexes regen` rewrites it and two worktrees adding files to a regen-covered folder race on it
|
|
69
|
+
|
|
70
|
+
Land any in-flight edit to a hotspot on `main` before fanning out. A worktree on a stale `CLAUDE.md` costs more to rebase than it saved.
|
|
71
|
+
|
|
72
|
+
**Merge order.** Merge the branch with the smallest hotspot footprint first. A branch touching `CLAUDE.md`, a context entry, or a regenerated `index.md` merges last, and its siblings rebase on the new `main` once it lands.
|
|
73
|
+
|
|
74
|
+
**Rebase before the next merge.** After one squash-merge, each sibling is behind `main` and may carry a stale copy of a shared file. Run `git fetch origin && git rebase origin/main` in the sibling, resolve any conflict in the worktree, push, then merge. Never force-merge a stale branch.
|
|
75
|
+
|
|
76
|
+
**Clean up after merge.** Run `/git-worktree cleanup` to remove worktrees whose branches merged on GitHub and prune the local branches. The skill reads merge state through `gh pr view`. To start a fresh feature from a stale worktree, `ExitWorktree(action: "keep")` back to main, then `/session-worktree <new-name>`.
|
|
77
|
+
|
|
78
|
+
**When fan-out was the wrong call.** If every sibling needs a rebase and every rebase conflicts on the same file, the branches should have serialized. Land one, wait, then start the next. The cleanup skill only tidies a flow that already worked.
|
|
79
|
+
|
|
59
80
|
## The task board
|
|
60
81
|
|
|
61
82
|
`.canon/tasks/` is gitignored and resolves at the main worktree root, so every session shares one board. One file per task is what keeps concurrent sessions from overwriting each other, since a gitignored board has no history to recover a lost write from. Its `index.md` is generated by a hook rather than by `bun run check`, because the whole-repo index walk skips gitignored folders.
|
|
@@ -8,7 +8,9 @@ category: Workflow
|
|
|
8
8
|
|
|
9
9
|
Groups run in the order a project meets them. They reconcile the scenarios in [AI workflow](ai-workflow.md) with the lifecycle [target projects](../target/projects.md) describes, so every moment either one names has a group. Each row says when to reach for a skill, and its description says what it does.
|
|
10
10
|
|
|
11
|
-
This page and [skill map on request](skill-map-on-request.md) are the corpus the coverage claim is measured against: every name `canon claude skills list --names` reports takes exactly one row across the two. A skill serving two moments sits at the earlier one, this page counting as earlier than the sibling, and a mention on any other page is prose rather than routing. The sibling holds the groups that answer a request rather than mark a moment.
|
|
11
|
+
This page and [skill map on request](skill-map-on-request.md) are the corpus the coverage claim is measured against: every name `canon claude skills list --names` reports takes exactly one row across the two. A skill serving two moments sits at the earlier one, this page counting as earlier than the sibling, and a mention on any other page is prose rather than routing. The sibling holds the groups that answer a request rather than mark a moment.
|
|
12
|
+
|
|
13
|
+
Each row sits under the group its skill's `metadata.family` names. The landing page's skills field groups by that field and reads each row's text from both pages, so its build refuses a skill with no row, a row naming no skill, a row filed under another group, and a heading on either page naming no family group, even one with no rows under it. A new group takes its vocabulary entry in `src/claude/skills-families.ts` before its heading lands here.
|
|
12
14
|
|
|
13
15
|
## Set up a project
|
|
14
16
|
|
|
@@ -68,7 +68,7 @@ Use `clp` in any other repository where you want the toolkit skills available. W
|
|
|
68
68
|
|
|
69
69
|
Use `cls` or `clps` to save Opus usage on routine sessions. Switch mid-session with `/model` to avoid restarting.
|
|
70
70
|
|
|
71
|
-
Use `clw <name>` for features that will take more than one session. A worktree isolates the branch, the transcripts, and the `/resume` history. See [Claude Code and git worktrees](../../wiki/claude/worktrees.md) for fan-out rules.
|
|
71
|
+
Use `clw <name>` for features that will take more than one session. A worktree isolates the branch, the transcripts, and the `/resume` history. See [Claude Code and git worktrees](../../wiki/claude/worktrees.md) for the worktree mechanism, and [Parallel features](parallel-features.md) for fan-out rules.
|
|
72
72
|
|
|
73
73
|
Use `clc` to resume the last session without a picker. Use `clr` when you have several sessions and need to pick by name or recency. Reach for `clpc` over `clc` wherever the resumed session needs the plugin, inside the toolkit repository as well as outside it, since neither resume re-runs discovery.
|
|
74
74
|
|
package/package.json
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
*
|
|
21
21
|
* Only the HTML regenerates here. The PNG beside it is a chromium render whose
|
|
22
22
|
* bytes move with the browser version. Rebuild the images with
|
|
23
|
-
* `canon capture assets/captures --selector .window --out assets/
|
|
23
|
+
* `canon capture assets/captures --selector .window --out assets/frames`
|
|
24
24
|
* after this script reports a change. That capture writes a .stamp beside each
|
|
25
25
|
* PNG, which records the digest of the markup it rendered and is what the Hero
|
|
26
26
|
* stage compares, so a frame's three files commit together. The frame carries
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
|
|
3
|
-
# The sandbox tree lives outside the toolkit worktree. `sandbox
|
|
3
|
+
# The sandbox tree lives outside the toolkit worktree. `canon sandbox run`
|
|
4
4
|
# sets cwd to it for `claude -p`, and every `CLAUDE.md` between that cwd and the
|
|
5
5
|
# filesystem root loads into the session. Under the repository the toolkit's own
|
|
6
6
|
# instructions join that chain beside the seeded copy the scenario installed,
|
|
@@ -12,12 +12,11 @@
|
|
|
12
12
|
#
|
|
13
13
|
# Mints a short random per-run identifier the first time it is asked for, then
|
|
14
14
|
# holds it in CANON_SANDBOX_RUN_ID for the rest of this process. A direct call
|
|
15
|
-
# (not `$(...)`) exports it into the caller's own shell
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
# variable, inherited from such a parent, reuses it rather than minting a new one.
|
|
15
|
+
# (not `$(...)`) exports it into the caller's own shell. A call already carrying
|
|
16
|
+
# the variable, inherited from a parent such as `canon sandbox run`, reuses it
|
|
17
|
+
# rather than minting a new one, which is what keeps a hook on the parent's tree.
|
|
19
18
|
#
|
|
20
|
-
# Twin of `mintSandboxRunId` in `src/
|
|
19
|
+
# Twin of `mintSandboxRunId` in `src/sandbox/tree.ts`.
|
|
21
20
|
mint_sandbox_run_id() {
|
|
22
21
|
if [ -z "${CANON_SANDBOX_RUN_ID:-}" ]; then
|
|
23
22
|
CANON_SANDBOX_RUN_ID="$(date +%s)-$$-$RANDOM"
|
|
@@ -33,7 +32,7 @@ sandbox_dir_prefix() {
|
|
|
33
32
|
printf '%s/canon/sandbox\n' "${XDG_STATE_HOME:-$HOME/.local/state}"
|
|
34
33
|
}
|
|
35
34
|
|
|
36
|
-
# Twin of `sandboxTree` in `src/
|
|
35
|
+
# Twin of `sandboxTree` in `src/sandbox/tree.ts`. The exec boundary rules out
|
|
37
36
|
# a shared constant, so a change to the default lands on both sides.
|
|
38
37
|
#
|
|
39
38
|
# A bare fall-through with no per-run component is one path per machine, so
|
|
@@ -49,158 +48,3 @@ resolve_sandbox_dir() {
|
|
|
49
48
|
mint_sandbox_run_id
|
|
50
49
|
printf '%s-%s\n' "$(sandbox_dir_prefix)" "$CANON_SANDBOX_RUN_ID"
|
|
51
50
|
}
|
|
52
|
-
|
|
53
|
-
# Collapses repeated separators, folds `.` and `..` segments, and strips every
|
|
54
|
-
# trailing separator, leaving a bare root as `/`. Every comparison below is a
|
|
55
|
-
# string test, so `//`, `$HOME//`, and `$HOME/../../usr` each read as a path no
|
|
56
|
-
# rule names until this runs, and an unresolved `..` defeats the allowlist and
|
|
57
|
-
# both directions of the repository test at once.
|
|
58
|
-
#
|
|
59
|
-
# The fold is lexical because the guard runs before provisioning creates the
|
|
60
|
-
# tree, which rules out `cd` with `pwd -P` and any resolution needing the path
|
|
61
|
-
# to exist. Nothing here follows a symlink, so a `..` below one resolves against
|
|
62
|
-
# the link's own path rather than its target. A `..` climbing past the root
|
|
63
|
-
# clamps to `/`, matching the kernel, which leaves the allowlist to refuse it
|
|
64
|
-
# under the rule that already covers every root path.
|
|
65
|
-
normalize_sandbox_path() {
|
|
66
|
-
local path="$1"
|
|
67
|
-
|
|
68
|
-
[ -n "$path" ] || return 0
|
|
69
|
-
|
|
70
|
-
while [ "$path" != "${path//\/\//\/}" ]; do
|
|
71
|
-
path="${path//\/\//\/}"
|
|
72
|
-
done
|
|
73
|
-
|
|
74
|
-
local root=""
|
|
75
|
-
if [ "${path#/}" != "$path" ]; then
|
|
76
|
-
root="/"
|
|
77
|
-
path="${path#/}"
|
|
78
|
-
fi
|
|
79
|
-
|
|
80
|
-
# `resolved` carries each kept segment behind its own separator, so a pop is
|
|
81
|
-
# one suffix removal and popping an empty stack is the no-op that clamps at
|
|
82
|
-
# the root. `climbed` holds the leading `..` a relative path has no segment to
|
|
83
|
-
# pop against, which dropping would change the directory it names.
|
|
84
|
-
local resolved="" climbed="" segment
|
|
85
|
-
local remaining=4096
|
|
86
|
-
|
|
87
|
-
while [ -n "$path" ] && [ "$remaining" -gt 0 ]; do
|
|
88
|
-
remaining=$((remaining - 1))
|
|
89
|
-
|
|
90
|
-
segment="${path%%/*}"
|
|
91
|
-
if [ "$segment" = "$path" ]; then
|
|
92
|
-
path=""
|
|
93
|
-
else
|
|
94
|
-
path="${path#*/}"
|
|
95
|
-
fi
|
|
96
|
-
|
|
97
|
-
case "$segment" in
|
|
98
|
-
"" | .) ;;
|
|
99
|
-
..)
|
|
100
|
-
if [ -n "$resolved" ]; then
|
|
101
|
-
resolved="${resolved%/*}"
|
|
102
|
-
elif [ -z "$root" ]; then
|
|
103
|
-
climbed="$climbed../"
|
|
104
|
-
fi
|
|
105
|
-
;;
|
|
106
|
-
*) resolved="$resolved/$segment" ;;
|
|
107
|
-
esac
|
|
108
|
-
done
|
|
109
|
-
|
|
110
|
-
if [ -n "$root" ]; then
|
|
111
|
-
path="${resolved:-/}"
|
|
112
|
-
else
|
|
113
|
-
path="${climbed}${resolved#/}"
|
|
114
|
-
path="${path:-.}"
|
|
115
|
-
fi
|
|
116
|
-
|
|
117
|
-
while [ "${#path}" -gt 1 ] && [ "${path%/}" != "$path" ]; do
|
|
118
|
-
path="${path%/}"
|
|
119
|
-
done
|
|
120
|
-
|
|
121
|
-
printf '%s' "$path"
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
# Whether `candidate` is `target` or a directory containing it. Removing the
|
|
125
|
-
# former removes the latter, which is what makes an ancestor as dangerous as an
|
|
126
|
-
# exact match.
|
|
127
|
-
is_at_or_above() {
|
|
128
|
-
local candidate="$1"
|
|
129
|
-
local target="$2"
|
|
130
|
-
|
|
131
|
-
[ "$candidate" = "$target" ] || [ "${target#"$candidate"/}" != "$target" ]
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
# Prints the reason a location is unusable and returns non-zero, so the caller
|
|
135
|
-
# reports one message naming the path rather than a bare refusal.
|
|
136
|
-
#
|
|
137
|
-
# Provisioning runs `rm -rf` on this path at three sites before staging, so the
|
|
138
|
-
# test is an allowlist rather than a list of paths to refuse. A blocklist has to
|
|
139
|
-
# name every system directory to be right once and stays wrong as soon as one is
|
|
140
|
-
# missed, while requiring a strict descendant of the home directory or of the
|
|
141
|
-
# temp root admits the default and every reasonable override and refuses `/`,
|
|
142
|
-
# `/usr`, `/etc`, and `$HOME` itself without naming any of them.
|
|
143
|
-
#
|
|
144
|
-
# The repository test is separate and runs both ways. A path under the worktree
|
|
145
|
-
# restores the ancestor chain the relocation removed, and a path above it is one
|
|
146
|
-
# `rm -rf` away from deleting the repository. It resolves the main worktree root
|
|
147
|
-
# rather than trusting `$PROJECT_ROOT`, since a linked worktree is itself inside
|
|
148
|
-
# that root.
|
|
149
|
-
assert_sandbox_dir_safe() {
|
|
150
|
-
local raw="$1"
|
|
151
|
-
local root="${2:-${PROJECT_ROOT:-$PWD}}"
|
|
152
|
-
|
|
153
|
-
if [ -z "$raw" ] || [ "${raw#/}" = "$raw" ]; then
|
|
154
|
-
printf 'CANON_SANDBOX_DIR must be an absolute path, got: %s\n' "${raw:-<empty>}"
|
|
155
|
-
return 1
|
|
156
|
-
fi
|
|
157
|
-
|
|
158
|
-
# `normalize_sandbox_path` stops at 4096 segments and drops the remainder,
|
|
159
|
-
# which would hand the tests below an ancestor of the path provisioning
|
|
160
|
-
# removes. Refusing past `PATH_MAX` keeps that bound out of reach, since a
|
|
161
|
-
# segment costs at least two bytes and no syscall accepts the string anyway.
|
|
162
|
-
if [ "${#raw}" -gt 4096 ]; then
|
|
163
|
-
printf 'Refusing the sandbox path. It is %s characters, past the longest path any filesystem here accepts.\n' "${#raw}"
|
|
164
|
-
return 1
|
|
165
|
-
fi
|
|
166
|
-
|
|
167
|
-
local dir home temp
|
|
168
|
-
dir="$(normalize_sandbox_path "$raw")"
|
|
169
|
-
home="$(normalize_sandbox_path "${HOME:-/root}")"
|
|
170
|
-
temp="$(normalize_sandbox_path "${TMPDIR:-/tmp}")"
|
|
171
|
-
|
|
172
|
-
# Every message below names `$raw`, which is what the operator set. The tests
|
|
173
|
-
# compare `$dir`, so a path carrying `..` is refused for a location its own
|
|
174
|
-
# spelling does not show.
|
|
175
|
-
local resolution=""
|
|
176
|
-
[ "$dir" = "$raw" ] || resolution=" It resolves to $dir."
|
|
177
|
-
|
|
178
|
-
if ! is_at_or_above "$home" "$dir" && ! is_at_or_above "$temp" "$dir"; then
|
|
179
|
-
printf 'Refusing %s as the sandbox. Provisioning removes the tree first, so the path has to sit under %s or %s.%s\n' "$raw" "$home" "$temp" "$resolution"
|
|
180
|
-
return 1
|
|
181
|
-
fi
|
|
182
|
-
|
|
183
|
-
if [ "$dir" = "$home" ] || [ "$dir" = "$temp" ]; then
|
|
184
|
-
printf 'Refusing %s as the sandbox. Provisioning removes the tree first, so the path has to sit under %s rather than be it.\n' "$raw" "$dir"
|
|
185
|
-
return 1
|
|
186
|
-
fi
|
|
187
|
-
|
|
188
|
-
local main_root
|
|
189
|
-
main_root="$(git -C "$root" worktree list --porcelain 2>/dev/null |
|
|
190
|
-
grep -m 1 '^worktree ' | cut -d' ' -f2-)"
|
|
191
|
-
main_root="$(normalize_sandbox_path "${main_root:-$root}")"
|
|
192
|
-
|
|
193
|
-
if is_at_or_above "$dir" "$main_root"; then
|
|
194
|
-
printf 'Refusing %s as the sandbox. Provisioning removes the tree first, and that path contains %s.%s\n' "$raw" "$main_root" "$resolution"
|
|
195
|
-
return 1
|
|
196
|
-
fi
|
|
197
|
-
|
|
198
|
-
case "$dir" in
|
|
199
|
-
"$main_root"/*)
|
|
200
|
-
printf 'Sandbox at %s sits inside %s, which puts the toolkit CLAUDE.md back on the session ancestor chain.%s Point CANON_SANDBOX_DIR outside the repository.\n' "$raw" "$main_root" "$resolution"
|
|
201
|
-
return 1
|
|
202
|
-
;;
|
|
203
|
-
esac
|
|
204
|
-
|
|
205
|
-
return 0
|
|
206
|
-
}
|
package/src/audits/run.ts
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
classify,
|
|
8
8
|
type Corpus,
|
|
9
9
|
} from '@/audits/catalog'
|
|
10
|
-
import { gitEnv } from '@/git
|
|
10
|
+
import { gitEnv } from '@/git/env'
|
|
11
11
|
import { PROJECT_ROOT } from '@/project-root'
|
|
12
12
|
|
|
13
13
|
/** Every value a corpus filter accepts, matching the `Corpus` union. */
|
package/src/canvas/address.ts
CHANGED
|
@@ -9,6 +9,12 @@
|
|
|
9
9
|
/** Marks the token stylesheet the server injects, which the file never holds. */
|
|
10
10
|
export const TOKENS_ATTRIBUTE = 'data-canvas-tokens'
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Marks a document wrapper the server writes because the file leaves it to the
|
|
14
|
+
* browser to imply, so the shell counts only what the file states.
|
|
15
|
+
*/
|
|
16
|
+
export const IMPLIED_ATTRIBUTE = 'data-canvas-implied'
|
|
17
|
+
|
|
12
18
|
/** On that same element, the hash of the file the frame was served from. */
|
|
13
19
|
export const HASH_ATTRIBUTE = 'data-canvas-hash'
|
|
14
20
|
|
|
@@ -22,8 +28,9 @@ export interface ElementAddress {
|
|
|
22
28
|
readonly tag: string
|
|
23
29
|
/**
|
|
24
30
|
* Every element the shell counted. A browser implies elements the file never
|
|
25
|
-
* states, such as a `tbody
|
|
26
|
-
*
|
|
31
|
+
* states, such as a `tbody`, and each one shifts the count, so a total that
|
|
32
|
+
* differs from the server's is what exposes it. The server marks an implied
|
|
33
|
+
* document wrapper itself, so only the marked ones leave the count.
|
|
27
34
|
*/
|
|
28
35
|
readonly count: number
|
|
29
36
|
/**
|
|
@@ -53,7 +60,9 @@ export function excerpt(text: string): string {
|
|
|
53
60
|
|
|
54
61
|
export function documentElements(doc: Document): Element[] {
|
|
55
62
|
return [...doc.querySelectorAll('*')].filter(
|
|
56
|
-
(element) =>
|
|
63
|
+
(element) =>
|
|
64
|
+
!element.hasAttribute(TOKENS_ATTRIBUTE) &&
|
|
65
|
+
!element.hasAttribute(IMPLIED_ATTRIBUTE),
|
|
57
66
|
)
|
|
58
67
|
}
|
|
59
68
|
|