@erclx/canon 5.25.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 +4 -3
- 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 +14 -1
- 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/REQUIREMENT.md +6 -2
- package/claude/skills/context-fold/SKILL.md +9 -21
- package/claude/skills/context-fold/references/anchor-sweep.md +2 -2
- 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/REQUIREMENT.md +1 -1
- package/claude/skills/design-taste/SKILL.md +6 -2
- package/claude/skills/design-taste/references/adopted.md +40 -0
- package/claude/skills/design-taste/references/kinds.md +1 -1
- package/claude/skills/design-taste/references/systems.md +1 -7
- package/claude/skills/design-taste/references/tells.md +2 -24
- package/claude/skills/docs-sync/REQUIREMENT.md +2 -1
- package/claude/skills/docs-sync/SKILL.md +2 -0
- package/claude/skills/document-health/REQUIREMENT.md +2 -0
- package/claude/skills/document-health/SKILL.md +23 -0
- package/claude/skills/document-health/references/axes.md +12 -0
- package/claude/skills/draft-and-pick/SKILL.md +3 -1
- 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 +3 -1
- package/claude/skills/draft-ready/SKILL.md +2 -0
- package/claude/skills/draft-screencast/SKILL.md +11 -7
- package/claude/skills/draft-slides/REQUIREMENT.md +17 -12
- package/claude/skills/draft-slides/SKILL.md +33 -20
- 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-issue/references/issue.md +10 -0
- package/claude/skills/git-pr/SKILL.md +2 -0
- package/claude/skills/git-pr/references/evidence.md +14 -6
- package/claude/skills/git-pr/references/labels.md +12 -0
- package/claude/skills/git-ship/SKILL.md +2 -0
- package/claude/skills/git-split/SKILL.md +21 -8
- 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/markdown-propose/references/format.md +9 -0
- package/claude/skills/memory-capture/SKILL.md +2 -0
- package/claude/skills/memory-review/SKILL.md +2 -0
- package/claude/skills/memory-review/references/later-phases.md +7 -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/review-ui/references/post.md +4 -2
- package/claude/skills/role-orchestrator/SKILL.md +5 -14
- package/claude/skills/role-orchestrator/references/orchestrator-cloud-launch.md +8 -0
- package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +13 -0
- 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/REQUIREMENT.md +1 -1
- package/claude/skills/search-craft/SKILL.md +5 -1
- package/claude/skills/search-craft/references/adopted.md +30 -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/target-setup/references/detect.md +8 -0
- package/claude/skills/target-setup/references/indexes.md +13 -0
- package/claude/skills/target-setup/references/verify.md +8 -0
- package/claude/skills/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 +4 -0
- package/claude/skills/test-first/references/adopted.md +36 -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 +3 -1
- package/claude/skills/write-human/references/machine-tells.md +13 -0
- package/claude/skills/youtube-transcripts/SKILL.md +2 -0
- package/docs/agents/canvas.md +24 -6
- package/docs/agents/capture.md +6 -6
- package/docs/agents/commands.md +5 -5
- package/docs/agents/index.md +5 -5
- 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/records-stale.md +22 -9
- package/docs/agents/records.md +1 -1
- package/docs/agents/sandbox.md +29 -5
- package/docs/agents/scripting.md +8 -1
- package/docs/agents/skills-audit.md +11 -3
- package/docs/agents/slides.md +15 -3
- package/docs/workflow/ai-workflow.md +1 -1
- package/docs/workflow/index.md +1 -0
- package/docs/workflow/operating-model.md +5 -6
- package/docs/workflow/parallel-features.md +23 -2
- package/docs/workflow/skill-map-on-request.md +42 -0
- package/docs/workflow/skill-map.md +3 -34
- 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-git.sh +1 -9
- package/scripts/lib/sandbox-path.sh +6 -162
- package/scripts/sandbox-hook.sh +99 -0
- package/src/audits/run.ts +1 -1
- package/src/canvas/address.ts +12 -3
- package/src/canvas/capture.ts +1 -9
- package/src/canvas/client/inspector/align-grid.tsx +162 -0
- package/src/canvas/client/inspector/color-field.tsx +398 -0
- 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 +547 -17
- package/src/canvas/client/inspector/size-field.tsx +69 -0
- package/src/canvas/client/inspector/values.ts +166 -1
- package/src/canvas/client/inspector.tsx +236 -73
- 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/capture/render.ts +45 -7
- package/src/capture/sources.ts +43 -0
- package/src/census/count.ts +2 -2
- package/src/claude/cases/workflow.ts +5 -0
- package/src/claude/seeds.ts +2 -2
- package/src/claude/skills-audit.ts +96 -14
- 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 +94 -11
- package/src/commands/design.ts +142 -2
- package/src/commands/feedback.ts +3 -3
- 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 +89 -7
- package/src/commands/repo.ts +1 -1
- package/src/commands/sandbox.ts +103 -11
- package/src/commands/sessions.ts +1 -1
- package/src/commands/slides.ts +31 -77
- 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/audit.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/design/tokens.ts +2 -18
- package/src/docs/read.ts +1 -1
- package/src/{github.ts → feedback/github.ts} +2 -2
- package/src/{client-commands.ts → gate/client-commands.ts} +1 -1
- package/src/gate/measures.ts +53 -13
- 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 +2 -2
- package/src/gov/install.ts +1 -1
- package/src/gov/payload.ts +1 -1
- package/src/gov/superseded.ts +3 -3
- 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/ceiling.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/record-root.ts +1 -0
- package/src/records/backup.ts +2 -1
- package/src/records/push-guard.ts +1 -1
- package/src/records/stale-canonical.ts +146 -0
- package/src/records/stale.ts +1 -1
- package/src/sandbox/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 +128 -0
- package/src/sandbox/provision.ts +607 -0
- package/src/sandbox/tree.ts +99 -4
- package/src/secrets/marker.ts +2 -2
- package/src/secrets/scan.ts +2 -2
- package/src/sessions/claim.ts +1 -1
- package/src/sessions/resolve.ts +1 -1
- package/src/sessions/transfer/bundle.ts +1 -1
- package/src/shipped/references.ts +2 -2
- package/src/slides/convert/export.ts +2 -16
- package/src/slides/decks.ts +110 -0
- package/src/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 +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/seeds-report.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/answers.ts +1 -1
- package/src/tasks/archive.ts +1 -1
- package/src/tasks/reach.ts +1 -1
- package/src/tasks/trunk.ts +1 -1
- package/src/teach/nav.ts +1 -1
- package/src/tooling/inject.ts +2 -2
- package/src/tooling/subfolder.ts +1 -1
- package/src/web/readme-citations.ts +2 -2
- package/src/worktrees/bare-flag.ts +1 -1
- package/src/worktrees/reclaim.ts +2 -2
- package/src/worktrees/remove.ts +1 -1
- package/standards/architecture.md +1 -0
- package/standards/canvas.md +3 -2
- package/standards/requirements.md +2 -0
- package/standards/skill-practice.md +2 -1
- package/standards/skill.md +2 -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/scripts/manage-sandbox.sh +0 -472
- package/src/slides/layouts.ts +0 -478
- package/src/slides/parse.ts +0 -84
- package/src/slides/render.ts +0 -110
- package/src/slides/styles.ts +0 -96
- /package/claude/skills/write-human/references/{source-material.md → adopted.md} +0 -0
- /package/src/{commands/feedback-format.ts → feedback/body.ts} +0 -0
- /package/src/{github-format.ts → feedback/github-format.ts} +0 -0
- /package/src/{git-base.ts → git/base.ts} +0 -0
- /package/src/{git-env.ts → git/env.ts} +0 -0
- /package/src/{frontmatter.ts → markdown/frontmatter.ts} +0 -0
- /package/src/{binary.ts → sweep/binary.ts} +0 -0
- /package/src/{exempt-marker.ts → sweep/exempt-marker.ts} +0 -0
- /package/src/{copy.ts → sync/copy.ts} +0 -0
- /package/src/{target.ts → targets/validate.ts} +0 -0
- /package/src/{paths.ts → tasks/paths.ts} +0 -0
- /package/src/{seed-marker.ts → tooling/seed-marker.ts} +0 -0
|
@@ -136,7 +136,7 @@ Before the first feature session on a UI-heavy project, pick a design tier. The
|
|
|
136
136
|
|
|
137
137
|
## Skills
|
|
138
138
|
|
|
139
|
-
The [skill map](skill-map.md)
|
|
139
|
+
The [skill map](skill-map.md) and its sibling [skill map on request](skill-map-on-request.md) give every skill the plugin ships one row saying when to reach for it. The first groups rows by the moment a project meets them, and the sibling holds the skills that answer a request.
|
|
140
140
|
|
|
141
141
|
## Feedback routing
|
|
142
142
|
|
package/docs/workflow/index.md
CHANGED
|
@@ -15,6 +15,7 @@ How a person and their agents run a feature, from the design tier through the pa
|
|
|
15
15
|
|
|
16
16
|
- [Operating model](operating-model.md): Orchestrator, planner, and worker roles for building across parallel sessions
|
|
17
17
|
- [Parallel features](parallel-features.md): Run independent features in parallel worktrees, with the records and board every session shares
|
|
18
|
+
- [Skill map on request](skill-map-on-request.md): When to reach for the plugin skills that serve a request rather than a moment in a project's life
|
|
18
19
|
- [Skill map](skill-map.md): When to reach for each plugin skill, grouped by the moment a project meets it
|
|
19
20
|
- [Visual design workflow](visual-design-workflow.md): Tiered guide for design and wireframe authoring with Claude Code
|
|
20
21
|
- [Zshrc aliases for Claude Code](zshrc-aliases.md): Shell aliases that shorten common Claude Code invocations
|
|
@@ -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
|
|
|
@@ -54,7 +54,28 @@ A plan that ships is archived, never deleted. `canon tasks archive` moves it to
|
|
|
54
54
|
|
|
55
55
|
A branch review report takes the other route and is swept rather than archived. `review-branch` writes it flat into `.canon/review/` as `branch-<slug>.md`, the session addressing it reads it once, and the durable record of what a review found is the comment `review-pr` posts on the pull request, so `context-fold` deletes any report whose branch is gone. The body that writes a report owns how long it lives, which leaves the shipping branch's own report on disk through the run that cites it and collects it a branch later. What that loses is a local-only review on a branch that never opened a pull request, which is why the report says so where a reader meets it.
|
|
56
56
|
|
|
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.
|
|
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
|
+
|
|
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.
|
|
58
79
|
|
|
59
80
|
## The task board
|
|
60
81
|
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Skill map on request
|
|
3
|
+
description: When to reach for the plugin skills that serve a request rather than a moment in a project's life
|
|
4
|
+
category: Workflow
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Skill map on request
|
|
8
|
+
|
|
9
|
+
These two groups hold the skills no single moment in a project's life calls for, so they sit apart from the moment-ordered groups in the [skill map](skill-map.md). The two pages together are the corpus the coverage claim is measured against, and a skill takes one row across both.
|
|
10
|
+
|
|
11
|
+
## Generate an artifact on demand
|
|
12
|
+
|
|
13
|
+
| Skill | When to use |
|
|
14
|
+
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
15
|
+
| `canon:create-rule` | For a project-specific governance rule the toolkit does not ship |
|
|
16
|
+
| `canon:create-skill` | For a new `SKILL.md` |
|
|
17
|
+
| `canon:create-standard` | For a new authoring convention |
|
|
18
|
+
| `canon:draft-doc` | For a brand-new `docs/` page, context entry, wireframe surface, wiki reference page, or `README.md`, drafted against the standard for its kind |
|
|
19
|
+
| `canon:draft-ready` | For finished files a worker should copy, written as a ready folder with its overview, thin plan, and task |
|
|
20
|
+
| `canon:draft-figure` | For a hand-drawn figure or an architecture view inside an existing doc, drafted in Mermaid or freehand SVG |
|
|
21
|
+
| `canon:bash-cli-script` | For a small non-interactive shell wrapper, stopping at 100 lines in favor of TypeScript on Bun |
|
|
22
|
+
| `canon:ci-workflow` | For a GitHub Actions workflow file |
|
|
23
|
+
| `canon:draft-slides` | For a deck, drawn as HTML slides in its own folder and rendered to editable PowerPoint |
|
|
24
|
+
| `canon:draft-screencast` | For a recording script with beats and defaults already seeded |
|
|
25
|
+
| `canon:record-screencast` | For recording a screencast draft, then composing the take into a finished mp4 when the draft asks for a wrap |
|
|
26
|
+
| `canon:draft-identity` | For a project's logo mark and its social card, drafted through `draft-and-pick`'s pick loop |
|
|
27
|
+
|
|
28
|
+
## Answer a question at any point
|
|
29
|
+
|
|
30
|
+
| Skill | When to use |
|
|
31
|
+
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
32
|
+
| `canon:canon-cli` | Before running an unfamiliar verb, a sync, or an install, to learn which command to run, which reference doc covers it, or what it overwrites, merges, or leaves alone |
|
|
33
|
+
| `canon:index-lookup` | To find where a topic is documented across the tracked `index.md` catalogs |
|
|
34
|
+
| `canon:youtube-transcripts` | When a video transcript is wanted in the repo as context |
|
|
35
|
+
| `canon:read-frames` | To read a recorded demo back frame by frame and report what each one shows, with no verdict on whether the recording looks right |
|
|
36
|
+
| `canon:teach-workspace` | To learn a subject across sessions, in a workspace that holds the progress |
|
|
37
|
+
| `canon:write-human` | Before drafting or revising prose, for voice, rhythm, and density |
|
|
38
|
+
| `canon:restate-plainly` | When an answer or a document has to be read again in plain words |
|
|
39
|
+
|
|
40
|
+
Every row answers a question rather than marking a point in a project's life, so filing one under a phase on the [skill map](skill-map.md) would send a reader to the wrong group.
|
|
41
|
+
|
|
42
|
+
A learning workspace produces two halves and only one of them leaves. A lesson is worked through once and stays in the workspace, and a reference page or a glossary carries no learner, so it belongs wherever the project already keeps prose on that subject. Asking `canon:teach-workspace` to promote sorts each durable page by who owns its subject, sending an Anthropic-owned subject to the wiki, an internal one to the matching context entry, and everything else, subject-neutral material included, to the public docs. It proposes and waits, because a promoted page is public prose that needs a line naming who owns the subject, and it writes nothing to a destination: each page the operator confirms goes to a handoff file that `canon:context-fold` folds in from a branch. A project with no wiki folder gets a refusal naming `canon wiki init` rather than a folder it never asked for.
|
|
@@ -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
|
|
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
|
|
|
@@ -115,36 +117,3 @@ This page is the corpus the coverage claim is measured against: every name `cano
|
|
|
115
117
|
| `canon:canon-feedback` | When something in the toolkit is broken, missing, or off, to open an issue on it |
|
|
116
118
|
| `canon:canon-feedback-triage` | In the toolkit repo, to work through the open feedback issues |
|
|
117
119
|
| `canon:canon-rollout` | In the toolkit repo, to take one change out to every consuming project at once |
|
|
118
|
-
|
|
119
|
-
## Generate an artifact on demand
|
|
120
|
-
|
|
121
|
-
| Skill | When to use |
|
|
122
|
-
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
123
|
-
| `canon:create-rule` | For a project-specific governance rule the toolkit does not ship |
|
|
124
|
-
| `canon:create-skill` | For a new `SKILL.md` |
|
|
125
|
-
| `canon:create-standard` | For a new authoring convention |
|
|
126
|
-
| `canon:draft-doc` | For a brand-new `docs/` page, context entry, wireframe surface, wiki reference page, or `README.md`, drafted against the standard for its kind |
|
|
127
|
-
| `canon:draft-ready` | For finished files a worker should copy, written as a ready folder with its overview, thin plan, and task |
|
|
128
|
-
| `canon:draft-figure` | For a hand-drawn figure or an architecture view inside an existing doc, drafted in Mermaid or freehand SVG |
|
|
129
|
-
| `canon:bash-cli-script` | For a small non-interactive shell wrapper, stopping at 100 lines in favor of TypeScript on Bun |
|
|
130
|
-
| `canon:ci-workflow` | For a GitHub Actions workflow file |
|
|
131
|
-
| `canon:draft-slides` | For a deck, drafted as `.claude/SLIDES.md` and rendered to PowerPoint |
|
|
132
|
-
| `canon:draft-screencast` | For a recording script with beats and defaults already seeded |
|
|
133
|
-
| `canon:record-screencast` | For recording a screencast draft, then composing the take into a finished mp4 when the draft asks for a wrap |
|
|
134
|
-
| `canon:draft-identity` | For a project's logo mark and its social card, drafted through `draft-and-pick`'s pick loop |
|
|
135
|
-
|
|
136
|
-
## Answer a question at any point
|
|
137
|
-
|
|
138
|
-
| Skill | When to use |
|
|
139
|
-
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
140
|
-
| `canon:canon-cli` | Before running an unfamiliar verb, a sync, or an install, to learn which command to run, which reference doc covers it, or what it overwrites, merges, or leaves alone |
|
|
141
|
-
| `canon:index-lookup` | To find where a topic is documented across the tracked `index.md` catalogs |
|
|
142
|
-
| `canon:youtube-transcripts` | When a video transcript is wanted in the repo as context |
|
|
143
|
-
| `canon:read-frames` | To read a recorded demo back frame by frame and report what each one shows, with no verdict on whether the recording looks right |
|
|
144
|
-
| `canon:teach-workspace` | To learn a subject across sessions, in a workspace that holds the progress |
|
|
145
|
-
| `canon:write-human` | Before drafting or revising prose, for voice, rhythm, and density |
|
|
146
|
-
| `canon:restate-plainly` | When an answer or a document has to be read again in plain words |
|
|
147
|
-
|
|
148
|
-
Every row answers a question rather than marking a point in a project's life, so a phase above would send a reader to the wrong group.
|
|
149
|
-
|
|
150
|
-
A learning workspace produces two halves and only one of them leaves. A lesson is worked through once and stays in the workspace, and a reference page or a glossary carries no learner, so it belongs wherever the project already keeps prose on that subject. Asking `canon:teach-workspace` to promote sorts each durable page by who owns its subject, sending an Anthropic-owned subject to the wiki, an internal one to the matching context entry, and everything else, subject-neutral material included, to the public docs. It proposes and waits, because a promoted page is public prose that needs a line naming who owns the subject, and it writes nothing to a destination: each page the operator confirms goes to a handoff file that `canon:context-fold` folds in from a branch. A project with no wiki folder gets a refusal naming `canon wiki init` rather than a folder it never asked for.
|
|
@@ -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
|
|
@@ -60,7 +60,7 @@ configure_sandbox_git_identity() {
|
|
|
60
60
|
SANDBOX_ANCHOR_REPO="canon-sandbox"
|
|
61
61
|
|
|
62
62
|
# A scenario calls this from its own use_anchor hook rather than this file
|
|
63
|
-
# defining the hook.
|
|
63
|
+
# defining the hook. sandbox-hook.sh keys off `type -t use_anchor`, so
|
|
64
64
|
# declaring it here would hand an anchor to the scenarios that source this file
|
|
65
65
|
# for the identity helpers alone.
|
|
66
66
|
use_sandbox_anchor() {
|
|
@@ -112,14 +112,6 @@ ensure_sandbox_anchor_repo() {
|
|
|
112
112
|
log_info "Created ${GITHUB_ORG}/${repo_name} as a private repository."
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
# Mirrors the merge-base resolution the diff-baseline port already carries
|
|
116
|
-
# across five skill bodies, so a checkout whose local main trails origin/main
|
|
117
|
-
# does not pull in skill bodies other merged branches changed.
|
|
118
|
-
resolve_sandbox_skill_diff_base() {
|
|
119
|
-
git -C "$PROJECT_ROOT" merge-base HEAD origin/main 2>/dev/null ||
|
|
120
|
-
git -C "$PROJECT_ROOT" merge-base HEAD main 2>/dev/null
|
|
121
|
-
}
|
|
122
|
-
|
|
123
115
|
# A remote is useless without an author, so the scenarios that reach one always
|
|
124
116
|
# configure both. configure_sandbox_git_identity stays callable on its own for
|
|
125
117
|
# the scenarios that never push. The probe runs first so an absent remote is
|
|
@@ -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
|
-
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# No `-u`. Scenarios ran under the retired dispatcher without it and read unset
|
|
3
|
+
# variables as empty, so turning it on here changes what they provision.
|
|
4
|
+
set -e
|
|
5
|
+
set -o pipefail
|
|
6
|
+
|
|
7
|
+
# Runs one scenario's hooks for the provisioning order in
|
|
8
|
+
# `src/sandbox/provision.ts`. Scenarios and the library they call stay bash, so
|
|
9
|
+
# the harness reaches them through this entry and nothing else.
|
|
10
|
+
#
|
|
11
|
+
# probe <scenario> runs `use_config` and `use_anchor` when declared
|
|
12
|
+
# and prints every export they changed, closed by an
|
|
13
|
+
# `@anchor=<0|1>` record
|
|
14
|
+
# stage <scenario> <file> re-runs both hooks, runs `stage_setup` inside the
|
|
15
|
+
# tree, and writes the exports it changed to <file>,
|
|
16
|
+
# or an `@exited` record when the shell exits first
|
|
17
|
+
#
|
|
18
|
+
# Pairs are NUL-separated `NAME=value`, since a value can hold a newline. The
|
|
19
|
+
# retired dispatcher ran `stage_setup` in its own shell, so an export, an `exit`,
|
|
20
|
+
# or an `exec` there reached the harness, and the report carries all three: an
|
|
21
|
+
# `exec` skips the EXIT trap and leaves no report at all. The stage re-runs the
|
|
22
|
+
# hooks rather than receiving the probe's pairs, since they only export.
|
|
23
|
+
|
|
24
|
+
SCRIPT_DIR="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd)"
|
|
25
|
+
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
|
26
|
+
export PROJECT_ROOT
|
|
27
|
+
|
|
28
|
+
# Globals carry a prefix, since the scenario sourced below shares this shell.
|
|
29
|
+
_hook_die() {
|
|
30
|
+
echo "sandbox-hook: $1" >&2
|
|
31
|
+
exit 2
|
|
32
|
+
}
|
|
33
|
+
_hook_mode="${1:-}"
|
|
34
|
+
_hook_scenario="${2:-}"
|
|
35
|
+
_hook_report="${3:-}"
|
|
36
|
+
|
|
37
|
+
[[ "$_hook_mode" == "probe" || "$_hook_mode" == "stage" ]] || _hook_die "mode must be probe or stage, got: ${_hook_mode:-<empty>}"
|
|
38
|
+
[ -f "$_hook_scenario" ] || _hook_die "no scenario file at: ${_hook_scenario:-<empty>}"
|
|
39
|
+
[ "$_hook_mode" = "probe" ] || [ -n "$_hook_report" ] || _hook_die "stage needs a file to report its exports to"
|
|
40
|
+
[ "$_hook_mode" = "probe" ] || trap 'printf "@exited\0" >"$_hook_report"' EXIT
|
|
41
|
+
|
|
42
|
+
declare -A _hook_before
|
|
43
|
+
_hook_snapshot() {
|
|
44
|
+
local name
|
|
45
|
+
_hook_before=()
|
|
46
|
+
for name in $(compgen -e); do
|
|
47
|
+
_hook_before[$name]="${!name}"
|
|
48
|
+
done
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# The shell moves PWD and OLDPWD itself on every `cd`, and neither is state.
|
|
52
|
+
_hook_changed() {
|
|
53
|
+
local name
|
|
54
|
+
for name in $(compgen -e); do
|
|
55
|
+
[[ "$name" == "PWD" || "$name" == "OLDPWD" ]] && continue
|
|
56
|
+
if [[ ! -v _hook_before[$name] ]] || [ "${_hook_before[$name]}" != "${!name}" ]; then
|
|
57
|
+
printf '%s=%s\0' "$name" "${!name}"
|
|
58
|
+
fi
|
|
59
|
+
done
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# Taken before `config.sh`, so a `GITHUB_ORG` it derives from the remote reaches
|
|
63
|
+
# the harness as an export like any other.
|
|
64
|
+
_hook_snapshot
|
|
65
|
+
|
|
66
|
+
source "$PROJECT_ROOT/scripts/config.sh"
|
|
67
|
+
source "$PROJECT_ROOT/scripts/lib/ui.sh"
|
|
68
|
+
source "$PROJECT_ROOT/scripts/lib/sandbox-path.sh"
|
|
69
|
+
source "$PROJECT_ROOT/scripts/lib/sandbox-git.sh"
|
|
70
|
+
source "$PROJECT_ROOT/scripts/lib/sandbox-fixtures.sh"
|
|
71
|
+
|
|
72
|
+
# Unexported, as the dispatcher held them, so a scenario reads both while a
|
|
73
|
+
# child it spawns sees neither. Shellcheck cannot see the scenario reading them.
|
|
74
|
+
# shellcheck disable=SC2034
|
|
75
|
+
SANDBOX="$(resolve_sandbox_dir)" SANDBOX_DIR="$PROJECT_ROOT/sandbox"
|
|
76
|
+
|
|
77
|
+
# shellcheck source=/dev/null
|
|
78
|
+
source "$_hook_scenario"
|
|
79
|
+
|
|
80
|
+
_hook_declared() { [[ "$(type -t "$1")" == "function" ]]; }
|
|
81
|
+
_hook_run() {
|
|
82
|
+
if _hook_declared use_config; then use_config; fi
|
|
83
|
+
if _hook_declared use_anchor; then use_anchor; fi
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if [ "$_hook_mode" = "probe" ]; then
|
|
87
|
+
# Stdout carries the pairs alone, so a hook that prints is moved off it.
|
|
88
|
+
_hook_run >&2
|
|
89
|
+
_hook_changed
|
|
90
|
+
if _hook_declared use_anchor; then printf '@anchor=1\0'; else printf '@anchor=0\0'; fi
|
|
91
|
+
exit 0
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
_hook_run
|
|
95
|
+
_hook_snapshot
|
|
96
|
+
cd "$SANDBOX"
|
|
97
|
+
stage_setup
|
|
98
|
+
trap - EXIT
|
|
99
|
+
_hook_changed >"$_hook_report"
|
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
|
|
package/src/canvas/capture.ts
CHANGED
|
@@ -196,14 +196,6 @@ async function serveFor<T>(
|
|
|
196
196
|
/** The element a composite is captured by, holding every frame and no chrome. */
|
|
197
197
|
export const COMPOSITE_SELECTOR = '#canvas-composite'
|
|
198
198
|
|
|
199
|
-
/**
|
|
200
|
-
* The container holds no text, so its family exists only for the engine's
|
|
201
|
-
* font probe, which rejects a family the machine lacks. The browser default is
|
|
202
|
-
* Times New Roman, which a Linux runner often lacks, and `system-ui` is the one
|
|
203
|
-
* every platform maps. Each frame keeps its own fonts inside its iframe.
|
|
204
|
-
*/
|
|
205
|
-
const COMPOSITE_FONT = 'system-ui'
|
|
206
|
-
|
|
207
199
|
export interface Placement {
|
|
208
200
|
readonly frame: string
|
|
209
201
|
readonly url: string
|
|
@@ -255,7 +247,7 @@ export function buildComposite(
|
|
|
255
247
|
const html = [
|
|
256
248
|
'<!doctype html>',
|
|
257
249
|
'<html><head><meta charset="utf-8">',
|
|
258
|
-
`<style>html,body{margin:0;background:transparent}#canvas-composite{position:relative
|
|
250
|
+
`<style>html,body{margin:0;background:transparent}#canvas-composite{position:relative}#canvas-composite iframe{position:absolute;display:block;border:0;background:transparent}</style>`,
|
|
259
251
|
'</head><body>',
|
|
260
252
|
`<div id="canvas-composite" style="width:${width}px;height:${height}px">`,
|
|
261
253
|
...iframes,
|