@erclx/canon 5.26.0 → 5.28.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 +5 -1
- 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 +7 -7
- 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/REQUIREMENT.md +4 -2
- package/claude/skills/review-address/SKILL.md +19 -14
- package/claude/skills/review-address/references/post-review-findings.md +16 -0
- package/claude/skills/review-address/references/rebase-conflicts.md +4 -2
- 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 +6 -6
- 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/REQUIREMENT.md +2 -2
- package/claude/skills/review-ui/SKILL.md +16 -12
- package/claude/skills/review-ui/references/driving.md +8 -8
- package/claude/skills/review-ui/references/post.md +13 -3
- package/claude/skills/review-ui/references/tick.md +22 -0
- package/claude/skills/role-orchestrator/REQUIREMENT.md +15 -15
- package/claude/skills/role-orchestrator/SKILL.md +31 -42
- package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +18 -16
- package/claude/skills/role-orchestrator/references/orchestrator-handback.md +13 -3
- package/claude/skills/role-orchestrator/references/orchestrator-inbound.md +23 -0
- package/claude/skills/role-orchestrator/references/orchestrator-launch.md +6 -34
- package/claude/skills/role-orchestrator/references/orchestrator-output.md +4 -4
- package/claude/skills/role-orchestrator/references/orchestrator-parked.md +5 -5
- package/claude/skills/role-orchestrator/references/orchestrator-poll.md +12 -23
- package/claude/skills/role-orchestrator/references/orchestrator-refill.md +6 -6
- 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-sweep.md +2 -2
- package/claude/skills/role-orchestrator/references/orchestrator-watch.md +26 -0
- package/claude/skills/role-orchestrator/scripts/poll.ts +304 -10
- package/claude/skills/role-planner/REQUIREMENT.md +1 -1
- package/claude/skills/role-planner/SKILL.md +4 -2
- package/claude/skills/role-reviewer/REQUIREMENT.md +1 -1
- package/claude/skills/role-reviewer/SKILL.md +3 -1
- package/claude/skills/role-worker/SKILL.md +5 -2
- 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 +3 -1
- 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 +24 -8
- package/docs/agents/capture.md +5 -5
- package/docs/agents/commands.md +2 -2
- package/docs/agents/gate.md +3 -1
- package/docs/agents/index.md +5 -4
- package/docs/agents/install-and-sync.md +7 -0
- package/docs/agents/key-changes.md +1 -1
- package/docs/agents/pr-evidence.md +47 -23
- package/docs/agents/pr-local.md +19 -13
- package/docs/agents/pr-preview.md +44 -21
- package/docs/agents/pr-tick.md +72 -0
- package/docs/agents/sandbox.md +30 -6
- package/docs/agents/scripting.md +8 -1
- package/docs/agents/skills-audit.md +2 -1
- package/docs/agents/tasks-plans.md +1 -1
- package/docs/agents/tasks-validate.md +18 -18
- package/docs/agents/tasks.md +1 -1
- package/docs/workflow/operating-model.md +15 -21
- package/docs/workflow/parallel-features.md +24 -3
- 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/baseline.ts +1 -1
- package/src/audits/run.ts +2 -2
- package/src/canvas/address.ts +12 -3
- package/src/canvas/capture.ts +1 -1
- 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 +66 -3
- package/src/canvas/client/inspector/fields.css +473 -6
- package/src/canvas/client/inspector/size-field.tsx +69 -0
- package/src/canvas/client/inspector/token-select.tsx +109 -0
- package/src/canvas/client/inspector/values.ts +117 -3
- package/src/canvas/client/inspector.tsx +311 -15
- package/src/canvas/client/keys.ts +54 -0
- package/src/canvas/client/selection.tsx +199 -0
- package/src/canvas/client/shell.css +210 -12
- package/src/canvas/client/state.ts +165 -14
- package/src/canvas/client/surface.tsx +341 -39
- package/src/canvas/client/tools.tsx +64 -0
- package/src/canvas/content.ts +51 -2
- package/src/canvas/edit.ts +49 -6
- 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 +60 -18
- package/src/census/count.ts +1 -1
- package/src/claude/seeds.ts +3 -3
- 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/claude/skills-reach.ts +1 -1
- package/src/commands/audits.ts +2 -2
- package/src/commands/canvas.ts +180 -3
- package/src/commands/claude.ts +70 -12
- package/src/commands/design.ts +145 -5
- package/src/commands/docs.ts +1 -1
- package/src/commands/feedback.ts +2 -2
- package/src/commands/gate.ts +9 -2
- package/src/commands/gov.ts +3 -3
- package/src/commands/hooks.ts +1 -1
- package/src/commands/indexes.ts +1 -1
- package/src/commands/init.ts +2 -2
- package/src/commands/intake.ts +1 -1
- package/src/commands/migrate.ts +2 -2
- package/src/commands/pr.ts +288 -65
- package/src/commands/records.ts +1 -1
- package/src/commands/repo.ts +1 -1
- package/src/commands/sandbox.ts +64 -15
- package/src/commands/sessions.ts +1 -1
- package/src/commands/slides.ts +1 -1
- package/src/commands/sync.ts +2 -2
- package/src/commands/tasks.ts +21 -4
- package/src/commands/teach.ts +1 -1
- package/src/commands/tooling.ts +1 -1
- package/src/commands/transcripts.ts +2 -2
- package/src/commands/upgrade.ts +1 -1
- package/src/commands/wiki.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/architecture.ts +1 -1
- package/src/context/citations.ts +3 -3
- package/src/context/classify/extract.ts +3 -3
- package/src/context/folders.ts +2 -2
- package/src/context/requirements.ts +1 -1
- package/src/design/project-fonts.ts +392 -0
- package/src/docs/read.ts +1 -1
- package/src/exec.ts +1 -1
- package/src/feedback/github.ts +2 -2
- package/src/gate/measures.ts +48 -10
- package/src/gate/sequencer.ts +56 -4
- package/src/gate/stages.ts +17 -0
- package/src/gate/suite-lock.ts +214 -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/intake/folder.ts +1 -1
- package/src/labels/audit.ts +1 -1
- package/src/labels/map.ts +1 -1
- package/src/labels/phase.ts +3 -3
- package/src/markdown/files.ts +1 -1
- package/src/migrate/apply.ts +1 -1
- package/src/migrate/record-layout.ts +1 -1
- package/src/migrate/records.ts +11 -11
- package/src/migrate/scratch-evidence.ts +1 -1
- package/src/migrate/surface-roots.ts +6 -6
- package/src/pr/bijection.ts +1 -1
- package/src/pr/evidence.ts +250 -59
- package/src/pr/local.ts +16 -10
- package/src/pr/preview.ts +78 -2
- package/src/pr/tick.ts +56 -0
- package/src/process/harness.ts +1 -1
- package/src/records/backup.ts +2 -2
- package/src/records/ordinal.ts +1 -1
- package/src/records/prune.ts +1 -1
- package/src/records/size.ts +1 -1
- package/src/records/stale-canonical.ts +2 -2
- package/src/records/stale.ts +1 -1
- package/src/records/validate.ts +2 -2
- package/src/{project-root.ts → roots/project.ts} +1 -1
- package/src/{record-root.ts → roots/record.ts} +1 -1
- package/src/sandbox/coverage.ts +11 -12
- package/src/sandbox/equivalence.ts +57 -26
- 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/hooks.ts +1 -1
- package/src/sandbox/provision.ts +90 -29
- package/src/sandbox/scenario.ts +588 -0
- package/src/sandbox/tree.ts +4 -8
- package/src/secrets/scan.ts +1 -1
- package/src/sessions/claim.ts +1 -1
- package/src/sessions/live.ts +4 -1
- package/src/sessions/resolve.ts +1 -1
- package/src/sessions/transfer/bundle.ts +2 -2
- package/src/slides/decks.ts +1 -1
- package/src/slides/package/fonts.ts +6 -2
- package/src/standards/audit.ts +2 -2
- package/src/standards/read.ts +1 -1
- package/src/sync/check.ts +2 -2
- package/src/sync/engine.ts +3 -3
- package/src/sync/git.ts +1 -1
- package/src/sync/history.ts +1 -1
- package/src/sync/layout.ts +2 -2
- package/src/sync/reverse.ts +1 -1
- package/src/sync/stamp.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/{target.ts → targets/validate.ts} +1 -1
- package/src/tasks/answers.ts +1 -1
- package/src/tasks/archive.ts +1 -1
- package/src/tasks/label.ts +1 -1
- package/src/tasks/reach.ts +2 -2
- package/src/tasks/trunk.ts +1 -1
- package/src/tasks/validate.ts +38 -14
- package/src/teach/nav.ts +1 -1
- package/src/teach/workspace.ts +1 -1
- package/src/tooling/inject.ts +2 -2
- package/src/tooling/read.ts +1 -1
- package/src/tooling/scan.ts +1 -1
- package/src/tooling/subfolder.ts +1 -1
- package/src/version/installed.ts +1 -1
- package/src/version/skew.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/board.md +3 -3
- package/standards/canvas.md +2 -1
- package/standards/plan.md +13 -7
- 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/tsconfig.json +1 -1
- 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/{legacy-path.ts → roots/legacy.ts} +0 -0
- /package/src/{surface-root.ts → roots/surface.ts} +0 -0
- /package/src/{copy.ts → sync/copy.ts} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Tasks validate
|
|
3
|
-
description: Validating the board against the tree, the seven checks, the
|
|
3
|
+
description: Validating the board against the tree, the seven checks, the five report arrays, the exit codes, and the refusal reasons
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Tasks validate
|
|
@@ -12,17 +12,17 @@ canon tasks validate
|
|
|
12
12
|
canon tasks validate --json
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
Seven checks run.
|
|
15
|
+
Seven checks run. Touches reaches only the `## Run now` rows, asking each to name its files, and Plan reaches every group, reading the Plan column under `## Run now` and the task file's own line under the other two. Mapping and Grouping test the folder contract and hold for every group, and Shape holds for every group too, ahead of the four. Ordering reaches only the `## Needs a plan` rows, and Blockers reaches every row outside `## Run now`:
|
|
16
16
|
|
|
17
|
-
| Check
|
|
18
|
-
|
|
|
19
|
-
| Shape
|
|
20
|
-
| Plan
|
|
21
|
-
| Mapping
|
|
22
|
-
| Grouping
|
|
23
|
-
| Ordering
|
|
24
|
-
|
|
|
25
|
-
| Blockers
|
|
17
|
+
| Check | What it reports |
|
|
18
|
+
| -------- | ----------------------------------------------------------------------------------------------------------------------- |
|
|
19
|
+
| Shape | A row whose cell count disagrees with its table's header, or one stranded behind a table a blank line already closed |
|
|
20
|
+
| Plan | A row whose plan citation disagrees with its group or its task file, or a task whose `Plan:` line lists one plan twice |
|
|
21
|
+
| Mapping | A row or backlog line naming no task file |
|
|
22
|
+
| Grouping | A task carrying a row in more than one readiness group, or on both surfaces |
|
|
23
|
+
| Ordering | A `## Needs a plan` row whose stated position disagrees with where it actually sits, or which states no position at all |
|
|
24
|
+
| Touches | A `## Run now` row whose Touches column names no file |
|
|
25
|
+
| Blockers | A parked row whose blocker has stopped holding, whose cited task resolves nowhere, or whose cited task was declined |
|
|
26
26
|
|
|
27
27
|
Shape runs before any other check reads a row, since a row failing it carries no dependable fields for the rest to check. A blank or prose line closes the table above it, so the walk treats the next pipe line as a fresh header candidate rather than as a continuation. That candidate counts as a header only when the line behind it is a separator carrying the same cell count, and one that fails is `row-untabled`, stranded behind a table that already closed. Cell count still has to match the header on every row that clears that test, and a row whose count disagrees is `row-misshapen`, the shape a dropped pipe or a merged column produces.
|
|
28
28
|
|
|
@@ -42,9 +42,9 @@ The ordinal phrase is prose rather than data, searched for anywhere in the cell
|
|
|
42
42
|
|
|
43
43
|
The comparative phrase is bounded the same way and for the same reason. A closed verb list of `leads`, `heads`, `opens`, `closes`, `trails`, `precedes`, `follows`, `outranks`, and `sits under`, `above`, or `below` has to sit in one clause with a positional object, which is a `vNN.N` phase label, the word `group`, or `row` or `rows`. Both halves are needed, since a cell reading `it closes a gap the reference gate leaves open` carries the verb and claims no position, and the clause bound is what stops a verb in one half of the cell pairing with an object in the other. An ordinal exempts the row, being a comparative claim already. An unusual comparative phrasing reports as unranked, the safe direction for a check over prose, and a vocabulary verb reaching a positional object non-positionally reports as ranked, which only grading prose could separate.
|
|
44
44
|
|
|
45
|
-
The
|
|
45
|
+
The Touches check is the one a person cannot run by eye. Paths come from the backticked spans in the Touches column, a span naming no file is dropped, and a directory shares any file beneath it. A `## Run now` row whose column parses to nothing is `touches-unstated`, since a row stating no file set makes a claim nothing can check.
|
|
46
46
|
|
|
47
|
-
Where a directory holds the other row's file, the
|
|
47
|
+
Two `## Run now` rows naming a path in common land in a second array, `shared`, which moves no exit code since the branch merging second rebases. Merge order reads it, as does a dispatcher weighing a contract, relocation, or sweep hold. Where a directory holds the other row's file, the entry names the row that claimed it, reading `both touch src/tasks, which v2.0-second claims as a folder.` The shared strings alone leave an over-broad cell and a genuine overlap identical. <!-- canon-allow-reference: illustrates the entry's own sentence shape, not a citation of a real task -->
|
|
48
48
|
|
|
49
49
|
The blocker check re-takes a measurement the board records once and never repeats. Two of the five blocker kinds put a fact on disk: a dependency is settled by the cited task being archived or by its work reaching the trunk, and a collision is settled by nothing under `## Run now` still holding the file the cell cites. A cited task resolving under `.canon/tasks/declined/` instead reports separately as `blocker-declined`, since a decided-against task is neither the shipped work `blocker-settled` reports nor the dangling pointer `blocker-unresolved` reports.
|
|
50
50
|
|
|
@@ -69,7 +69,7 @@ Both halves gate on a citation inside the `Waiting on` cell, never on the column
|
|
|
69
69
|
|
|
70
70
|
A citation resolving in none of the board, the archive, or the declined folder is `blocker-unresolved` rather than a settled row. Reading an absent file as archived or declined states a specific fact about a file nobody ever wrote, which is what a renamed task or a typo produces, and only a task that genuinely closed or was genuinely declined releases the row waiting on it.
|
|
71
71
|
|
|
72
|
-
The other three kinds rest on a person's judgment, so a row neither half reached lands in a
|
|
72
|
+
The other three kinds rest on a person's judgment, so a row neither half reached lands in a third array rather than in the findings:
|
|
73
73
|
|
|
74
74
|
```json
|
|
75
75
|
{
|
|
@@ -85,7 +85,7 @@ The other three kinds rest on a person's judgment, so a row neither half reached
|
|
|
85
85
|
|
|
86
86
|
An untested row is not a finding and moves no exit code. Reading a clean findings list as a clean board is the failure the array exists to prevent, and `orchestrator-parked.md` is the pass that takes those rows by hand.
|
|
87
87
|
|
|
88
|
-
A `## Run now` row whose Touches column names a bare folder lands in a
|
|
88
|
+
A `## Run now` row whose Touches column names a bare folder lands in a fourth array on the same reasoning:
|
|
89
89
|
|
|
90
90
|
```json
|
|
91
91
|
{
|
|
@@ -101,11 +101,11 @@ A `## Run now` row whose Touches column names a bare folder lands in a third arr
|
|
|
101
101
|
|
|
102
102
|
That claim collides with every row a later session writes under the folder, and it is legitimate whenever the row does rewrite the directory, so the array states the reach and moves no exit code. A measure failing on a cell that is right teaches a reader to skip it. Folder against file is decided by asking the tree for a path that resolves, and by the extension only for a path the row has yet to create, since the name alone reads a file carrying no extension as a folder.
|
|
103
103
|
|
|
104
|
-
The scan reaches `## Run now` and stops, where the
|
|
104
|
+
The scan reaches `## Run now` and stops, where the Touches check stops. A cell in another group describes work nobody has planned, written as a sentence and rewritten once a plan exists, so a claim read off one reports on prose rather than on a file set. A parked folder claim surfaces when its row is promoted, which is when the cell becomes something a dispatcher can act on.
|
|
105
105
|
|
|
106
|
-
A code span in any cell that the preview cannot break lands in a
|
|
106
|
+
A code span in any cell that the preview cannot break lands in a fifth array, `wide`, with the same `group`, `subject`, and `message` fields. The scan measures a span's longest whitespace-free run and flags one past 60 characters, or a brace-glob at any width. A 57-character span fit the pane and a 78-character one did not, so the limit is a heuristic and the array moves no exit code. A link target is skipped, since only its label renders, and the scan reaches every group, since the overflow it exists for sat under `## Up next`.
|
|
107
107
|
|
|
108
|
-
A task file neither surface names lands in a
|
|
108
|
+
A task file neither surface names lands in a sixth array, on the same reasoning:
|
|
109
109
|
|
|
110
110
|
```json
|
|
111
111
|
{
|
package/docs/agents/tasks.md
CHANGED
|
@@ -118,6 +118,6 @@ canon tasks list --json | jq -r '.tasks[] | select(.readiness == "backlog") | .s
|
|
|
118
118
|
|
|
119
119
|
## Validate
|
|
120
120
|
|
|
121
|
-
`canon tasks validate` reports what each row of `priority.md` and each line of `backlog.md` claims against what the tree holds. Its seven checks, the
|
|
121
|
+
`canon tasks validate` reports what each row of `priority.md` and each line of `backlog.md` claims against what the tree holds. Its seven checks, the five arrays that move no exit code, and the refusals are in `tasks-validate.md`.
|
|
122
122
|
|
|
123
123
|
For the board format, see `standards/board.md`. For the `Pull request:` line and the archive rules, see `standards/tasks.md`.
|
|
@@ -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
|
|
|
@@ -34,7 +34,7 @@ it. All four are framing and boundaries rather than logic.
|
|
|
34
34
|
|
|
35
35
|
The planner is the one role the orchestrator also performs. Per-row planning
|
|
36
36
|
runs warm inside the orchestrator's own session or cold in a dispatched one, and
|
|
37
|
-
the boundary between them is the cross-feature call: which rows
|
|
37
|
+
the boundary between them is the cross-feature call: which rows conflict, what
|
|
38
38
|
merges before what, and whether a row should run at all stay with the
|
|
39
39
|
orchestrator, because a session reading the board sees blockers and file sets
|
|
40
40
|
and can write a confident merge order off a partial picture. The per-row
|
|
@@ -62,20 +62,20 @@ Filing that row stops at the defect and the surface it was seen on. How far the
|
|
|
62
62
|
defect reaches and what causes it belong to whoever plans the row, so it arrives
|
|
63
63
|
carrying neither a count nor an asserted mechanism, and the seat that found it
|
|
64
64
|
says as much in a plain sentence rather than supplying either. What separates the
|
|
65
|
-
two is what a measurement decides: the blocker re-test, the
|
|
66
|
-
the
|
|
65
|
+
two is what a measurement decides: the blocker re-test, the branch check, and
|
|
66
|
+
the conflict check against every track in flight all decide whether a row can
|
|
67
67
|
start, and only a count of the defect's extent decides how big it is.
|
|
68
68
|
|
|
69
69
|
## The loop
|
|
70
70
|
|
|
71
71
|
One feature travels this path end to end.
|
|
72
72
|
|
|
73
|
-
1. The next feature is planned with `plan-feature`, writing a plan to `.canon/plans/`. The orchestrator runs it warm when the row turns on a contract other features consume or a
|
|
74
|
-
2. Orchestrator checks the plan waits on nobody, checks the branch is unclaimed, and checks
|
|
73
|
+
1. The next feature is planned with `plan-feature`, writing a plan to `.canon/plans/`. The orchestrator runs it warm when the row turns on a contract other features consume or a dependency on another row, and dispatches a planner under `role-planner` otherwise. A cold planner measures the row against the tree rather than trusting what the row claims, and it reads what is in flight by composing the live session roster with open pull requests. A bare branch or worktree is not evidence on its own, since this repository leaves both behind after a squash merge.
|
|
74
|
+
2. Orchestrator checks the plan waits on nobody, checks the branch is unclaimed, and checks no conflict holds the plan behind a track in flight, then dispatches a background worker with `claude --bg` against the plan, naming the branch and the model on the launch rather than leaving the worker to derive either. No count caps how many run at once. The branch travels as the argument to the worker's own worktree call, which is the one place the name is read rather than inferred. It falls back to naming the invocation for a human to run through `session-worktree` and `auto-ship` when the plan still waits on an answer only the operator can give, the branch check refuses, or a conflict holds the plan behind a track already in flight. Either way, the worker enters its own worktree, builds, self-checks, opens a PR, and stops at the PR boundary.
|
|
75
75
|
3. A reviewer under `role-reviewer`, or the orchestrator, posts `review-pr` findings, and `review-ui` drives any visual checklist beside it.
|
|
76
|
-
4. Orchestrator tells the session holding that branch to run `review-address` once the pass posted a finding at any severity, resolving the target then with `canon sessions list --branch` and reporting the invocation for the human when no live session holds it. The worker addresses the findings, rebases onto `origin/main` when a sibling landed first
|
|
76
|
+
4. Orchestrator tells the session holding that branch to run `review-address` once the pass posted a finding at any severity, resolving the target then with `canon sessions list --branch` and reporting the invocation for the human when no live session holds it. The worker addresses the findings, rebases onto `origin/main` when a sibling landed first on a file it writes, then pushes a follow-up. A pass carrying only minor findings dispatches too, since the grade runs low often enough that a floor at should-fix loses fixes a worker would have made. `review-pr` states that threshold and the heading follows it, so an open heading is itself the signal to send.
|
|
77
77
|
5. Orchestrator closes the review out with `review-pr` again. The second pass reads only the commits the follow-up added, or the worker's response alone when the follow-up added none, and posts under `## Review` when it finds anything and under `## Review closed` when it finds nothing, so a reader learns from the heading whether work is still owed and takes the merge decision from the counts on the line under it. A pass finding nothing where a close-out already stands rewrites that comment to cover what it read rather than posting a second one, so the thread carries one live verdict. Repeat from step 4 until a pass closes the review.
|
|
78
|
-
6. The human reads the result and merges.
|
|
78
|
+
6. The human reads the result and merges. After each merge, the orchestrator tells any trailing worker whose files the merged diff touches to run `review-address`, which rebases whether or not the review left anything open.
|
|
79
79
|
|
|
80
80
|
There is no loop construct here. Each worker is a single build that halts at the
|
|
81
81
|
PR. The merge stays a manual human gate. Reliability comes from the plan being
|
|
@@ -148,8 +148,8 @@ whether the branch is blocked, so the pass reads the worker's response rather th
|
|
|
148
148
|
a delta and names its body from that response instead of from a tree that did not
|
|
149
149
|
change.
|
|
150
150
|
|
|
151
|
-
A branch
|
|
152
|
-
to close. `review-address` rebases onto `origin/main` between the fixes
|
|
151
|
+
A branch `main` moved under during review, conflicting or not, is the worker's
|
|
152
|
+
problem to close. `review-address` rebases onto `origin/main` between the fixes
|
|
153
153
|
and the push, so one force-push carries both and the reviewer reads one delta.
|
|
154
154
|
The staleness test sits ahead of the no-findings guard, so a branch whose review
|
|
155
155
|
closed clean and then went stale still rebases when the skill is invoked. The
|
|
@@ -200,12 +200,7 @@ before another is carried nowhere at all.
|
|
|
200
200
|
|
|
201
201
|
## Parallelism
|
|
202
202
|
|
|
203
|
-
|
|
204
|
-
No number caps worker tracks. Open one whenever its file set is disjoint from
|
|
205
|
-
every track already in flight, compared at the file path rather than at a folder
|
|
206
|
-
above it, and stop adding once you can no longer review every output properly.
|
|
207
|
-
Serialize a track sharing a wiring seam with another, and serialize one whose
|
|
208
|
-
sets are disjoint when a stated reason still puts it behind another.
|
|
203
|
+
Review attention binds, and no number caps worker tracks, so stop adding once you cannot review every output. A row waits only on a conflict a merge cannot settle, which the `role-orchestrator` dispatch runbook states once. A shared file is not one: both rows build, and the branch merging second resolves any conflict in its own session after the first lands, since no practice source serializes on a file. [Fowler](https://martinfowler.com/articles/branching-patterns.html) has whoever integrates second check health "even if it's a clean merge", and [DORA](https://dora.dev/capabilities/trunk-based-development/) keeps branches to hours with no integration phase.
|
|
209
204
|
|
|
210
205
|
Review splits by vantage. A reviewer under `role-reviewer` takes a pull
|
|
211
206
|
request's read, first pass included, when three await one (a number set by
|
|
@@ -225,12 +220,11 @@ Unit checks run freely in many worktrees at once.
|
|
|
225
220
|
A dev server, an end-to-end run, and a screenshot run alongside each other on a
|
|
226
221
|
web stack, since every worktree derives its own port. Singleton resources (one
|
|
227
222
|
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.
|
|
223
|
+
hand. See [Parallel features](parallel-features.md) for merge order and
|
|
224
|
+
[Claude Code and git worktrees](../../wiki/claude/worktrees.md) for port collisions.
|
|
231
225
|
|
|
232
226
|
## Related
|
|
233
227
|
|
|
234
|
-
- [Claude Code and git worktrees](../../wiki/claude/worktrees.md) for the
|
|
228
|
+
- [Claude Code and git worktrees](../../wiki/claude/worktrees.md) for the worktree mechanism and its gotchas
|
|
235
229
|
- [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
230
|
- `canon/context/claude-plugin/skill-strategy/overview.md` for how the skills in the loop are categorized
|
|
@@ -24,7 +24,7 @@ Execution order comes off `.canon/tasks/priority.md` and nothing sequences work
|
|
|
24
24
|
|
|
25
25
|
Run one orchestrator at a time. The board is gitignored, so a second session reads none of the first one's writes and the two collide on labels and archives.
|
|
26
26
|
|
|
27
|
-
See [operating model](operating-model.md) for what caps the worker tracks under it and
|
|
27
|
+
See [operating model](operating-model.md) for what caps the worker tracks under it and what conflict holds one behind another. An operator caps a session's workers by saying so, and that cap binds for the session rather than standing as a number in a file.
|
|
28
28
|
|
|
29
29
|
Before a handoff, the orchestrator checks the plan against the tree rather than reading it: grep each construct it names and count the sites, confirm every phase label it cites is still open, and open each file it describes. A plan goes stale from whatever merged after it was written, and reading cannot catch that.
|
|
30
30
|
|
|
@@ -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
|
+
**Rebase 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 touches a sibling's files. A shared file does not hold a branch back, per the conflict test in [operating model](operating-model.md). These paths are shared hotspots, the ones a second branch should expect to rebase over and resolve:
|
|
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. Resolve its conflict by running the regen over the rebased tree rather than by hand.
|
|
69
|
+
|
|
70
|
+
Land any in-flight edit to a hotspot on `main` before fanning out where you can. 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. The branch merging second resolves, in the session that built it and after the first lands, never against a sibling still building: run `/review-address` there, which trial-merges against `origin/main` and rebases a branch that no longer merges or whose files `main` changed. 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.** Sharing a file was never the reason. If every rebase needs a decision the tree does not carry, because one branch depended on another, changed a contract the other consumed, moved a path the other wrote, or swept a folder the other wrote inside, 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.
|
|
@@ -64,7 +85,7 @@ A branch review report takes the other route and is swept rather than archived.
|
|
|
64
85
|
|
|
65
86
|
`canon tasks validate` reads a row against its own table before it reads anything the row claims. A blank or prose line closes the table above it, so a row stranded there is checked against the line behind it rather than parsed as a continuation, and a row that clears that test still has its cell count checked against its header. A `## Needs a plan` row that states its own position, searched for `<ordinal> here` or the bare word `last` anywhere in the cell rather than at its start, is checked against where it actually sits, which is what catches a gap, a duplicate, and a sequence starting somewhere other than first alike.
|
|
66
87
|
|
|
67
|
-
Past that shape, it checks what a surviving row claims against what the tree holds: every plan pointer resolves, every task file is named by a board row or a backlog line and never by both, no task sits in two groups, and
|
|
88
|
+
Past that shape, it checks what a surviving row claims against what the tree holds: every plan pointer resolves, every task file is named by a board row or a backlog line and never by both, no task sits in two groups, and every row marked ready names its files. One check across both surfaces is what lets a task move between the board and the backlog without the move reading as a dropped file. Paths two ready rows share go to a `shared` array moving no exit code, the candidate list the orchestrator's conflict check reads. Blockers re-takes what a parked row waits on, reporting one whose cited task reached the trunk and one whose cited file nothing running still holds. A cited task settles the row by being archived, or by closing every outcome and naming a pull request the trunk carries, since the checkbox alone is marked while the branch is still in review. Both halves read a citation out of the blocker cell, so a row citing neither is reported as untested rather than counted clean, and so is a cited task the trunk could not answer for. It reports and never writes, because a row is the orchestrator's claim and a validator repairing one would assert the claim it exists to test. Nothing fires it automatically, since the board is gitignored per-machine scratch with no shared moment to hang a hook on, so the orchestrator's sweep calls it at the point the readiness claim is made and follows it with the parked re-test.
|
|
68
89
|
|
|
69
90
|
`canon:task-board` owns the two operations that bracket a task's life. It creates the file, holding the filename convention and the frontmatter contract so a malformed write cannot break the index for every sibling, and it moves a shipped task to `.canon/tasks/archive/`. Creation is where the origin invariant is enforced: every task names a plan, a groundwork folder, an intake folder, or an issue, since a task with no origin is either lost context or work nobody decided to do.
|
|
70
91
|
|
|
@@ -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/baseline.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { mkdir, readFile, writeFile } from 'node:fs/promises'
|
|
2
2
|
import { dirname, join, relative } from 'node:path'
|
|
3
3
|
import type { AuditResult } from '@/audits/catalog'
|
|
4
|
-
import { resolveExisting } from '@/legacy
|
|
4
|
+
import { resolveExisting } from '@/roots/legacy'
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Where the retained counts live, relative to the project root.
|
package/src/audits/run.ts
CHANGED
|
@@ -7,8 +7,8 @@ import {
|
|
|
7
7
|
classify,
|
|
8
8
|
type Corpus,
|
|
9
9
|
} from '@/audits/catalog'
|
|
10
|
-
import { gitEnv } from '@/git
|
|
11
|
-
import { PROJECT_ROOT } from '@/project
|
|
10
|
+
import { gitEnv } from '@/git/env'
|
|
11
|
+
import { PROJECT_ROOT } from '@/roots/project'
|
|
12
12
|
|
|
13
13
|
/** Every value a corpus filter accepts, matching the `Corpus` union. */
|
|
14
14
|
export const CORPORA: readonly Corpus[] = ['tracked', 'per-machine', 'upstream']
|
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
|
|
package/src/canvas/capture.ts
CHANGED