@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
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
import { spawnSync, type StdioOptions } from 'node:child_process'
|
|
2
|
+
import {
|
|
3
|
+
appendFileSync,
|
|
4
|
+
copyFileSync,
|
|
5
|
+
existsSync,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
readdirSync,
|
|
8
|
+
rmSync,
|
|
9
|
+
statSync,
|
|
10
|
+
writeFileSync,
|
|
11
|
+
} from 'node:fs'
|
|
12
|
+
import { basename, dirname, join, relative } from 'node:path'
|
|
13
|
+
import { probeScenario, stageScenario } from '@/sandbox/hooks'
|
|
14
|
+
import { assertSandboxDirSafe, sandboxRoots, sandboxTree } from '@/sandbox/tree'
|
|
15
|
+
import {
|
|
16
|
+
intro,
|
|
17
|
+
logError,
|
|
18
|
+
logInfo,
|
|
19
|
+
logRemove,
|
|
20
|
+
logStep,
|
|
21
|
+
logWarn,
|
|
22
|
+
outro,
|
|
23
|
+
palette,
|
|
24
|
+
select,
|
|
25
|
+
} from '@/ui'
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Provisions one scenario into the sandbox tree, and resets or cleans that tree.
|
|
29
|
+
* Each scenario's hooks run in bash through `sandbox-hook.sh`, and everything
|
|
30
|
+
* between them runs here, in the order the retired bash dispatcher held.
|
|
31
|
+
*
|
|
32
|
+
* Narration is part of the contract. `canon sandbox equivalence` compares the
|
|
33
|
+
* log line for line against the bash harness, so every message, its frame
|
|
34
|
+
* glyph, and its position in the order stays as that harness wrote it.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
const ANCHOR_FIXTURE_PARTS = ['sandbox', 'fixtures', 'anchor', 'create']
|
|
38
|
+
const FIXTURE_SUFFIX = '.fixture'
|
|
39
|
+
const SKILL_PATHSPEC = 'claude/skills/**/SKILL.md'
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Ends the run with a status. The frame closes once, at the top, for every
|
|
43
|
+
* stop, which is what the bash harness's `trap close_timeline EXIT` did.
|
|
44
|
+
*/
|
|
45
|
+
class Halt extends Error {
|
|
46
|
+
constructor(
|
|
47
|
+
readonly status: number,
|
|
48
|
+
readonly isFrameOpen = true,
|
|
49
|
+
) {
|
|
50
|
+
super(`halted with ${status}`)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function fail(message: string): never {
|
|
55
|
+
logError(message)
|
|
56
|
+
throw new Halt(1)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function must(status: number): void {
|
|
60
|
+
if (status !== 0) throw new Halt(status)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface RunOptions {
|
|
64
|
+
readonly cwd?: string
|
|
65
|
+
readonly env?: NodeJS.ProcessEnv
|
|
66
|
+
readonly stdout?: 'ignore' | 'inherit'
|
|
67
|
+
readonly stderr?: 'ignore' | 'inherit'
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function run(command: string, args: string[], options: RunOptions): number {
|
|
71
|
+
const stdio: StdioOptions = [
|
|
72
|
+
'ignore',
|
|
73
|
+
options.stdout ?? 'inherit',
|
|
74
|
+
options.stderr ?? 'inherit',
|
|
75
|
+
]
|
|
76
|
+
const result = spawnSync(command, args, {
|
|
77
|
+
cwd: options.cwd,
|
|
78
|
+
env: options.env,
|
|
79
|
+
stdio,
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
return result.status ?? 1
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function readGit(cwd: string, args: string[]): string | undefined {
|
|
86
|
+
const result = spawnSync('git', args, {
|
|
87
|
+
cwd,
|
|
88
|
+
encoding: 'utf8',
|
|
89
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
return result.status === 0 ? result.stdout : undefined
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function finish(message: string): void {
|
|
96
|
+
const { GREEN, NC } = palette(process.stderr)
|
|
97
|
+
outro()
|
|
98
|
+
process.stderr.write(`\n${GREEN}${message}${NC}\n`)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function listFixtureFiles(dir: string): string[] {
|
|
102
|
+
if (!existsSync(dir)) return []
|
|
103
|
+
|
|
104
|
+
return (readdirSync(dir, { recursive: true, encoding: 'utf8' }) as string[])
|
|
105
|
+
.map((path) => join(dir, path))
|
|
106
|
+
.filter((path) => statSync(path).isFile())
|
|
107
|
+
.sort()
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function stageAnchorTree(
|
|
111
|
+
root: string,
|
|
112
|
+
sandbox: string,
|
|
113
|
+
env: NodeJS.ProcessEnv,
|
|
114
|
+
): void {
|
|
115
|
+
// The anchor scenarios build their remote URL inside a command substitution,
|
|
116
|
+
// where a failure would leave an empty remote, so the guard fires here first.
|
|
117
|
+
if ((env.GITHUB_ORG ?? '') === '')
|
|
118
|
+
fail(
|
|
119
|
+
'GITHUB_ORG is empty. Export GITHUB_ORG=<org>, or run from a clone whose remote.origin.url points at GitHub.',
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
logStep('Staging anchor tree from fixture')
|
|
123
|
+
|
|
124
|
+
const fixtureDir = join(root, ...ANCHOR_FIXTURE_PARTS)
|
|
125
|
+
const files = listFixtureFiles(fixtureDir)
|
|
126
|
+
const stray = files.filter((path) => !path.endsWith(FIXTURE_SUFFIX))
|
|
127
|
+
if (stray.length > 0)
|
|
128
|
+
fail(`Fixture missing the ${FIXTURE_SUFFIX} suffix: ${stray.join(' ')} `)
|
|
129
|
+
if (files.length === 0)
|
|
130
|
+
fail(`Anchor fixture provisions nothing: ${fixtureDir}`)
|
|
131
|
+
|
|
132
|
+
rmSync(sandbox, { recursive: true, force: true })
|
|
133
|
+
mkdirSync(sandbox, { recursive: true })
|
|
134
|
+
for (const file of files) {
|
|
135
|
+
const target = join(
|
|
136
|
+
sandbox,
|
|
137
|
+
relative(fixtureDir, file).slice(0, -FIXTURE_SUFFIX.length),
|
|
138
|
+
)
|
|
139
|
+
mkdirSync(dirname(target), { recursive: true })
|
|
140
|
+
copyFileSync(file, target)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
must(run('git', ['init'], { cwd: sandbox, env, stdout: 'ignore' }))
|
|
144
|
+
must(run('git', ['add', '.'], { cwd: sandbox, env }))
|
|
145
|
+
must(
|
|
146
|
+
run(
|
|
147
|
+
'git',
|
|
148
|
+
[
|
|
149
|
+
'commit',
|
|
150
|
+
'-m',
|
|
151
|
+
'feat(sandbox): initial sandbox setup from anchor',
|
|
152
|
+
'--no-verify',
|
|
153
|
+
],
|
|
154
|
+
{ cwd: sandbox, env, stdout: 'ignore' },
|
|
155
|
+
),
|
|
156
|
+
)
|
|
157
|
+
logInfo(`Anchor tree staged from fixture, remote stays ${env.ANCHOR_REPO}`)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function initEmptyTree(sandbox: string, env: NodeJS.ProcessEnv): void {
|
|
161
|
+
rmSync(sandbox, { recursive: true, force: true })
|
|
162
|
+
mkdirSync(sandbox, { recursive: true })
|
|
163
|
+
writeFileSync(join(sandbox, '.gitignore'), '.canon/tmp/\nnode_modules\n')
|
|
164
|
+
|
|
165
|
+
must(run('git', ['init'], { cwd: sandbox, env, stdout: 'ignore' }))
|
|
166
|
+
must(
|
|
167
|
+
run('git', ['add', '.gitignore'], {
|
|
168
|
+
cwd: sandbox,
|
|
169
|
+
env,
|
|
170
|
+
stdout: 'ignore',
|
|
171
|
+
stderr: 'ignore',
|
|
172
|
+
}),
|
|
173
|
+
)
|
|
174
|
+
must(
|
|
175
|
+
run(
|
|
176
|
+
'git',
|
|
177
|
+
[
|
|
178
|
+
'commit',
|
|
179
|
+
'-m',
|
|
180
|
+
'feat(sandbox): initial empty sandbox setup',
|
|
181
|
+
'--no-verify',
|
|
182
|
+
],
|
|
183
|
+
{ cwd: sandbox, env, stdout: 'ignore' },
|
|
184
|
+
),
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Scoped to the throwaway tree so the operator's global config is never
|
|
190
|
+
* written. The empty value resets the helper list git concatenates across
|
|
191
|
+
* scopes, so an operator's stale helper cannot win ahead of `gh`.
|
|
192
|
+
*/
|
|
193
|
+
function configureCredentials(sandbox: string, env: NodeJS.ProcessEnv): void {
|
|
194
|
+
must(run('git', ['config', 'credential.helper', ''], { cwd: sandbox, env }))
|
|
195
|
+
must(
|
|
196
|
+
run(
|
|
197
|
+
'git',
|
|
198
|
+
['config', '--add', 'credential.helper', '!gh auth git-credential'],
|
|
199
|
+
{ cwd: sandbox, env },
|
|
200
|
+
),
|
|
201
|
+
)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Runs the real installer rather than copying the source tree, so a change to
|
|
206
|
+
* what install produces reaches the sandbox. Its own frame stays captured
|
|
207
|
+
* unless it fails, where it is the only thing naming the cause.
|
|
208
|
+
*/
|
|
209
|
+
function install(
|
|
210
|
+
root: string,
|
|
211
|
+
label: string,
|
|
212
|
+
args: string[],
|
|
213
|
+
env: NodeJS.ProcessEnv,
|
|
214
|
+
): void {
|
|
215
|
+
const result = spawnSync('bun', [join(root, 'src', 'cli.ts'), ...args], {
|
|
216
|
+
env: { ...env, CANON_NON_INTERACTIVE: '1' },
|
|
217
|
+
encoding: 'utf8',
|
|
218
|
+
stdio: ['ignore', 'ignore', 'pipe'],
|
|
219
|
+
})
|
|
220
|
+
if (result.status === 0) return
|
|
221
|
+
|
|
222
|
+
process.stderr.write(result.stderr ?? '')
|
|
223
|
+
fail(`Could not install ${label} into the sandbox.`)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* No standards install. The corpus installs into no target, so a scenario
|
|
228
|
+
* driving a skill that reads one takes the `canon standards <name>` path a real
|
|
229
|
+
* project takes.
|
|
230
|
+
*/
|
|
231
|
+
function setupAssets(
|
|
232
|
+
root: string,
|
|
233
|
+
sandbox: string,
|
|
234
|
+
env: NodeJS.ProcessEnv,
|
|
235
|
+
): void {
|
|
236
|
+
if (
|
|
237
|
+
(env.SANDBOX_INJECT_SEEDS ?? '') !== '' &&
|
|
238
|
+
existsSync(join(root, 'tooling', 'claude', 'seeds'))
|
|
239
|
+
)
|
|
240
|
+
install(root, 'seeds', ['claude', 'init', sandbox], env)
|
|
241
|
+
|
|
242
|
+
if (
|
|
243
|
+
(env.SANDBOX_INJECT_GOV ?? '') !== '' &&
|
|
244
|
+
existsSync(join(root, 'governance', 'rules'))
|
|
245
|
+
)
|
|
246
|
+
install(
|
|
247
|
+
root,
|
|
248
|
+
'gov rules',
|
|
249
|
+
['gov', 'install', env.SANDBOX_GOV_STACK || 'base', sandbox],
|
|
250
|
+
env,
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
must(run('git', ['add', '.'], { cwd: sandbox, env }))
|
|
254
|
+
const isStaged =
|
|
255
|
+
run('git', ['diff', '--cached', '--quiet'], { cwd: sandbox, env }) !== 0
|
|
256
|
+
if (isStaged)
|
|
257
|
+
must(
|
|
258
|
+
run(
|
|
259
|
+
'git',
|
|
260
|
+
[
|
|
261
|
+
'commit',
|
|
262
|
+
'-m',
|
|
263
|
+
'chore(sandbox): initial environment setup',
|
|
264
|
+
'--no-verify',
|
|
265
|
+
],
|
|
266
|
+
{ cwd: sandbox, env, stdout: 'ignore' },
|
|
267
|
+
),
|
|
268
|
+
)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Copies every skill body this checkout changed against the merge base into the
|
|
273
|
+
* tree, so a scenario exercises the branch's skill rather than the installed
|
|
274
|
+
* plugin's. The merge base against `origin/main` comes first, so a local `main`
|
|
275
|
+
* trailing the remote does not pull in skills other merged branches changed.
|
|
276
|
+
* A headless run skips it, since a copied body alone shadows the plugin's whole
|
|
277
|
+
* skill folder and its relative citations then resolve to nothing.
|
|
278
|
+
*/
|
|
279
|
+
export function injectChangedSkills(
|
|
280
|
+
root: string,
|
|
281
|
+
sandbox: string,
|
|
282
|
+
isSkipAutoCommit: boolean,
|
|
283
|
+
isSkipInject = false,
|
|
284
|
+
): string[] {
|
|
285
|
+
if (isSkipInject) {
|
|
286
|
+
logInfo(
|
|
287
|
+
'Skipped dev skill injection: the run resolves skills through --plugin-dir',
|
|
288
|
+
)
|
|
289
|
+
return []
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const base = (
|
|
293
|
+
readGit(root, ['merge-base', 'HEAD', 'origin/main']) ??
|
|
294
|
+
readGit(root, ['merge-base', 'HEAD', 'main']) ??
|
|
295
|
+
'main'
|
|
296
|
+
).trim()
|
|
297
|
+
|
|
298
|
+
const changed =
|
|
299
|
+
readGit(root, ['diff', base, '--name-only', '--', SKILL_PATHSPEC]) ?? ''
|
|
300
|
+
const untracked =
|
|
301
|
+
readGit(root, [
|
|
302
|
+
'ls-files',
|
|
303
|
+
'--others',
|
|
304
|
+
'--exclude-standard',
|
|
305
|
+
'--',
|
|
306
|
+
SKILL_PATHSPEC,
|
|
307
|
+
]) ?? ''
|
|
308
|
+
const paths = [
|
|
309
|
+
...new Set(
|
|
310
|
+
`${changed}\n${untracked}`.split('\n').filter((p) => p.trim() !== ''),
|
|
311
|
+
),
|
|
312
|
+
]
|
|
313
|
+
|
|
314
|
+
const injected: string[] = []
|
|
315
|
+
for (const path of paths) {
|
|
316
|
+
// A deleted skill lists beside a changed one, and nothing is left to inject.
|
|
317
|
+
if (!existsSync(join(root, path))) continue
|
|
318
|
+
|
|
319
|
+
const name = basename(dirname(path))
|
|
320
|
+
const target = join(sandbox, '.claude', 'skills', name)
|
|
321
|
+
mkdirSync(target, { recursive: true })
|
|
322
|
+
copyFileSync(join(root, path), join(target, 'SKILL.md'))
|
|
323
|
+
if (isSkipAutoCommit)
|
|
324
|
+
appendFileSync(
|
|
325
|
+
join(sandbox, '.git', 'info', 'exclude'),
|
|
326
|
+
`.claude/skills/${name}/SKILL.md\n`,
|
|
327
|
+
)
|
|
328
|
+
logInfo(`Injected dev skill: ${name}`)
|
|
329
|
+
injected.push(name)
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return injected
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Commits what the scenario staged, unless it asked to stay uncommitted. A
|
|
337
|
+
* scenario that staged nothing fails the commit, and the status passes through
|
|
338
|
+
* as the bash harness's did.
|
|
339
|
+
*/
|
|
340
|
+
export function commitScenarioChanges(
|
|
341
|
+
sandbox: string,
|
|
342
|
+
isSkipAutoCommit: boolean,
|
|
343
|
+
env: NodeJS.ProcessEnv,
|
|
344
|
+
): number {
|
|
345
|
+
if (isSkipAutoCommit) {
|
|
346
|
+
logInfo('Skipping auto-commit')
|
|
347
|
+
return 0
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
logStep('Staging environment changes')
|
|
351
|
+
const quiet = {
|
|
352
|
+
cwd: sandbox,
|
|
353
|
+
env,
|
|
354
|
+
stdout: 'ignore',
|
|
355
|
+
stderr: 'ignore',
|
|
356
|
+
} as const
|
|
357
|
+
const added = run('git', ['add', '.'], quiet)
|
|
358
|
+
if (added !== 0) return added
|
|
359
|
+
const committed = run(
|
|
360
|
+
'git',
|
|
361
|
+
[
|
|
362
|
+
'commit',
|
|
363
|
+
'-m',
|
|
364
|
+
'chore(sandbox): apply scenario specific setup',
|
|
365
|
+
'--no-verify',
|
|
366
|
+
],
|
|
367
|
+
{ cwd: sandbox, env, stdout: 'ignore' },
|
|
368
|
+
)
|
|
369
|
+
if (committed !== 0) return committed
|
|
370
|
+
logInfo('Git state clean after setup')
|
|
371
|
+
|
|
372
|
+
return 0
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function tagBaseline(sandbox: string, env: NodeJS.ProcessEnv): void {
|
|
376
|
+
const quiet = {
|
|
377
|
+
cwd: sandbox,
|
|
378
|
+
env,
|
|
379
|
+
stdout: 'ignore',
|
|
380
|
+
stderr: 'ignore',
|
|
381
|
+
} as const
|
|
382
|
+
must(run('git', ['update-ref', 'refs/sandbox/baseline', 'HEAD'], quiet))
|
|
383
|
+
const tree = spawnSync('git', ['write-tree'], {
|
|
384
|
+
cwd: sandbox,
|
|
385
|
+
env,
|
|
386
|
+
encoding: 'utf8',
|
|
387
|
+
stdio: ['ignore', 'pipe', 'inherit'],
|
|
388
|
+
})
|
|
389
|
+
must(tree.status ?? 1)
|
|
390
|
+
must(
|
|
391
|
+
run(
|
|
392
|
+
'git',
|
|
393
|
+
['update-ref', 'refs/sandbox/baseline-index', tree.stdout.trim()],
|
|
394
|
+
quiet,
|
|
395
|
+
),
|
|
396
|
+
)
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
interface Scenario {
|
|
400
|
+
readonly category: string
|
|
401
|
+
readonly command: string
|
|
402
|
+
readonly arm: string | undefined
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function provisionScenario(
|
|
406
|
+
root: string,
|
|
407
|
+
sandbox: string,
|
|
408
|
+
scenario: Scenario,
|
|
409
|
+
baseEnv: NodeJS.ProcessEnv,
|
|
410
|
+
): void {
|
|
411
|
+
const env: NodeJS.ProcessEnv = { ...baseEnv }
|
|
412
|
+
if (scenario.arm !== undefined) {
|
|
413
|
+
env.SANDBOX_SCENARIO = scenario.arm
|
|
414
|
+
env.CANON_NON_INTERACTIVE = '1'
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const sandboxDir = join(root, 'sandbox')
|
|
418
|
+
const file = join(sandboxDir, scenario.category, `${scenario.command}.sh`)
|
|
419
|
+
if (!existsSync(file) || !statSync(file).isFile())
|
|
420
|
+
fail(`Sandbox script not found: ${scenario.category}/${scenario.command}`)
|
|
421
|
+
|
|
422
|
+
const probe = probeScenario(file, env)
|
|
423
|
+
if (!probe.ok) throw new Halt(probe.status)
|
|
424
|
+
Object.assign(env, probe.exports)
|
|
425
|
+
|
|
426
|
+
if (!existsSync(sandboxDir))
|
|
427
|
+
fail(`Sandbox directory not found at: ${sandboxDir}`)
|
|
428
|
+
|
|
429
|
+
const cwd = process.cwd()
|
|
430
|
+
if (cwd === sandbox || cwd.startsWith(`${sandbox}/`)) {
|
|
431
|
+
logWarn(
|
|
432
|
+
'Detected execution inside the sandbox. Switching to project root...',
|
|
433
|
+
)
|
|
434
|
+
process.chdir(root)
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
logStep(`Provisioning ${scenario.category}:${scenario.command}`)
|
|
438
|
+
if (probe.isAnchor) stageAnchorTree(root, sandbox, env)
|
|
439
|
+
else initEmptyTree(sandbox, env)
|
|
440
|
+
configureCredentials(sandbox, env)
|
|
441
|
+
setupAssets(root, sandbox, env)
|
|
442
|
+
|
|
443
|
+
// A scenario that execs a verb as its last step hands the run to that verb,
|
|
444
|
+
// whose own frame is the last one written, so nothing follows it here.
|
|
445
|
+
const staged = stageScenario(file, env)
|
|
446
|
+
if (staged.ending === 'replaced') throw new Halt(staged.status, false)
|
|
447
|
+
if (staged.ending === 'exited') throw new Halt(staged.status)
|
|
448
|
+
Object.assign(env, staged.exports)
|
|
449
|
+
|
|
450
|
+
const isSkipAutoCommit = (env.SANDBOX_SKIP_AUTO_COMMIT ?? '') !== ''
|
|
451
|
+
const isSkipInject = (env.SANDBOX_SKIP_SKILL_INJECT ?? '') !== ''
|
|
452
|
+
injectChangedSkills(root, sandbox, isSkipAutoCommit, isSkipInject)
|
|
453
|
+
must(commitScenarioChanges(sandbox, isSkipAutoCommit, env))
|
|
454
|
+
tagBaseline(sandbox, env)
|
|
455
|
+
|
|
456
|
+
finish('✓ Sandbox Ready')
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function cleanSandbox(sandbox: string): void {
|
|
460
|
+
logStep('Removing sandbox')
|
|
461
|
+
rmSync(sandbox, { recursive: true, force: true })
|
|
462
|
+
logRemove(sandbox)
|
|
463
|
+
finish('✓ Sandbox clean')
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function isAtBaseline(sandbox: string): boolean {
|
|
467
|
+
const head = readGit(sandbox, ['rev-parse', 'HEAD'])
|
|
468
|
+
const baseline = readGit(sandbox, ['rev-parse', 'refs/sandbox/baseline'])
|
|
469
|
+
if (head === undefined || head !== baseline) return false
|
|
470
|
+
|
|
471
|
+
const baselineIndex = readGit(sandbox, [
|
|
472
|
+
'rev-parse',
|
|
473
|
+
'refs/sandbox/baseline-index',
|
|
474
|
+
])
|
|
475
|
+
const index = readGit(sandbox, ['write-tree'])
|
|
476
|
+
if (baselineIndex !== undefined && index !== baselineIndex) return false
|
|
477
|
+
|
|
478
|
+
const quiet = { cwd: sandbox, stdout: 'ignore', stderr: 'ignore' } as const
|
|
479
|
+
if (run('git', ['diff', '--quiet'], quiet) !== 0) return false
|
|
480
|
+
|
|
481
|
+
const untracked = readGit(sandbox, [
|
|
482
|
+
'ls-files',
|
|
483
|
+
'--others',
|
|
484
|
+
'--exclude-standard',
|
|
485
|
+
])
|
|
486
|
+
|
|
487
|
+
return untracked === undefined || untracked === ''
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
async function resetSandbox(sandbox: string): Promise<void> {
|
|
491
|
+
if (!existsSync(join(sandbox, '.git')))
|
|
492
|
+
fail('No sandbox found. Run `canon sandbox` first.')
|
|
493
|
+
|
|
494
|
+
logStep('Sandbox state')
|
|
495
|
+
|
|
496
|
+
if (readGit(sandbox, ['rev-parse', 'refs/sandbox/baseline']) === undefined)
|
|
497
|
+
fail('No baseline found. Re-provision with `canon sandbox <cat>:<cmd>`.')
|
|
498
|
+
|
|
499
|
+
if (isAtBaseline(sandbox)) {
|
|
500
|
+
logInfo('At baseline')
|
|
501
|
+
finish('✓ Sandbox at baseline')
|
|
502
|
+
return
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
logWarn('Uncommitted changes detected')
|
|
506
|
+
|
|
507
|
+
const isConfirmed = await select({
|
|
508
|
+
message: 'Reset sandbox to initial state?',
|
|
509
|
+
options: [
|
|
510
|
+
{ value: true, label: 'Yes' },
|
|
511
|
+
{ value: false, label: 'No' },
|
|
512
|
+
],
|
|
513
|
+
nonInteractiveDefault: true,
|
|
514
|
+
})
|
|
515
|
+
if (!isConfirmed) {
|
|
516
|
+
logWarn('Reset cancelled')
|
|
517
|
+
outro()
|
|
518
|
+
return
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
logStep('Resetting sandbox')
|
|
522
|
+
const quiet = { cwd: sandbox } as const
|
|
523
|
+
must(
|
|
524
|
+
run('git', ['reset', '--hard', 'refs/sandbox/baseline', '--quiet'], quiet),
|
|
525
|
+
)
|
|
526
|
+
must(run('git', ['clean', '-fd', '--quiet'], quiet))
|
|
527
|
+
const baselineIndex = readGit(sandbox, [
|
|
528
|
+
'rev-parse',
|
|
529
|
+
'refs/sandbox/baseline-index',
|
|
530
|
+
])?.trim()
|
|
531
|
+
const index = readGit(sandbox, ['write-tree'])?.trim()
|
|
532
|
+
if (baselineIndex !== undefined && baselineIndex !== index) {
|
|
533
|
+
must(run('git', ['read-tree', baselineIndex], quiet))
|
|
534
|
+
must(run('git', ['checkout-index', '-a', '-f'], quiet))
|
|
535
|
+
}
|
|
536
|
+
logInfo('Sandbox reset to baseline')
|
|
537
|
+
finish('✓ Sandbox reset complete')
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Splits `<category>:<command>` at the first colon, as `IFS=: read` did, so a
|
|
542
|
+
* command name holding a colon keeps the rest.
|
|
543
|
+
*/
|
|
544
|
+
function parseTarget(target: string): { category: string; command: string } {
|
|
545
|
+
const split = target.indexOf(':')
|
|
546
|
+
if (split < 0)
|
|
547
|
+
fail(
|
|
548
|
+
"Invalid format. Use <category>:<command>, 'reset', 'clean', or --help",
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
return { category: target.slice(0, split), command: target.slice(split + 1) }
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
export interface SandboxRequest {
|
|
555
|
+
/** False where a caller already opened the frame, as `run.sh` and the picker do. */
|
|
556
|
+
readonly isHeader: boolean
|
|
557
|
+
/** `<category>:<command>`, `reset`, or `clean`. */
|
|
558
|
+
readonly target: string
|
|
559
|
+
readonly arm: string | undefined
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Runs one `canon sandbox` request and returns its exit status. Every stop
|
|
564
|
+
* closes the frame on the way out, success or not.
|
|
565
|
+
*/
|
|
566
|
+
export async function runSandbox(
|
|
567
|
+
root: string,
|
|
568
|
+
request: SandboxRequest,
|
|
569
|
+
): Promise<number> {
|
|
570
|
+
if (request.isHeader) intro('canon sandbox')
|
|
571
|
+
|
|
572
|
+
try {
|
|
573
|
+
if (!process.cwd().startsWith(root))
|
|
574
|
+
fail(
|
|
575
|
+
'Context error: you must run this command from inside the toolkit repository.',
|
|
576
|
+
)
|
|
577
|
+
|
|
578
|
+
const sandbox = sandboxTree()
|
|
579
|
+
const unsafe = assertSandboxDirSafe(sandbox, sandboxRoots(root))
|
|
580
|
+
if (unsafe !== undefined) fail(unsafe)
|
|
581
|
+
|
|
582
|
+
if (request.target === 'reset') {
|
|
583
|
+
await resetSandbox(sandbox)
|
|
584
|
+
return 0
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
if (request.target === 'clean') {
|
|
588
|
+
cleanSandbox(sandbox)
|
|
589
|
+
return 0
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
const { category, command } = parseTarget(request.target)
|
|
593
|
+
provisionScenario(
|
|
594
|
+
root,
|
|
595
|
+
sandbox,
|
|
596
|
+
{ category, command, arm: request.arm },
|
|
597
|
+
{ ...process.env, PROJECT_ROOT: root, GIT_TERMINAL_PROMPT: '0' },
|
|
598
|
+
)
|
|
599
|
+
|
|
600
|
+
return 0
|
|
601
|
+
} catch (error) {
|
|
602
|
+
if (!(error instanceof Halt)) throw error
|
|
603
|
+
if (error.isFrameOpen) outro()
|
|
604
|
+
|
|
605
|
+
return error.status
|
|
606
|
+
}
|
|
607
|
+
}
|