@erclx/canon 4.0.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/LICENSE +21 -0
- package/README.md +129 -0
- package/claude/.claude-plugin/plugin.json +19 -0
- package/claude/skills/bash-cli-script/REQUIREMENT.md +42 -0
- package/claude/skills/bash-cli-script/SKILL.md +48 -0
- package/claude/skills/bash-cli-script/references/template.md +43 -0
- package/claude/skills/bash-script/REQUIREMENT.md +36 -0
- package/claude/skills/bash-script/SKILL.md +100 -0
- package/claude/skills/bash-script/references/patterns.md +349 -0
- package/claude/skills/canon-cli/REQUIREMENT.md +41 -0
- package/claude/skills/canon-cli/SKILL.md +103 -0
- package/claude/skills/canon-feedback-file/REQUIREMENT.md +40 -0
- package/claude/skills/canon-feedback-file/SKILL.md +80 -0
- package/claude/skills/canon-feedback-triage/REQUIREMENT.md +40 -0
- package/claude/skills/canon-feedback-triage/SKILL.md +63 -0
- package/claude/skills/canon-operator/REQUIREMENT.md +61 -0
- package/claude/skills/canon-operator/SKILL.md +108 -0
- package/claude/skills/canon-rollout/REQUIREMENT.md +59 -0
- package/claude/skills/canon-rollout/SKILL.md +147 -0
- package/claude/skills/canon-screencast/REQUIREMENT.md +39 -0
- package/claude/skills/canon-screencast/SKILL.md +167 -0
- package/claude/skills/canon-slides-draft/REQUIREMENT.md +39 -0
- package/claude/skills/canon-slides-draft/SKILL.md +62 -0
- package/claude/skills/ci-workflow/REQUIREMENT.md +40 -0
- package/claude/skills/ci-workflow/SKILL.md +65 -0
- package/claude/skills/ci-workflow/references/workflows.md +98 -0
- package/claude/skills/claude-address-review/REQUIREMENT.md +57 -0
- package/claude/skills/claude-address-review/SKILL.md +212 -0
- package/claude/skills/claude-address-review/references/rebase-conflicts.md +39 -0
- package/claude/skills/claude-autoship/REQUIREMENT.md +50 -0
- package/claude/skills/claude-autoship/SKILL.md +207 -0
- package/claude/skills/claude-design-extract/REQUIREMENT.md +42 -0
- package/claude/skills/claude-design-extract/SKILL.md +102 -0
- package/claude/skills/claude-diagram/REQUIREMENT.md +45 -0
- package/claude/skills/claude-diagram/SKILL.md +177 -0
- package/claude/skills/claude-docs/REQUIREMENT.md +60 -0
- package/claude/skills/claude-docs/SKILL.md +287 -0
- package/claude/skills/claude-docs/references/anchor-sweep.md +58 -0
- package/claude/skills/claude-docs/references/wireframe-sweep.md +45 -0
- package/claude/skills/claude-feature/REQUIREMENT.md +36 -0
- package/claude/skills/claude-feature/SKILL.md +115 -0
- package/claude/skills/claude-groundwork/REQUIREMENT.md +48 -0
- package/claude/skills/claude-groundwork/SKILL.md +142 -0
- package/claude/skills/claude-intake/REQUIREMENT.md +49 -0
- package/claude/skills/claude-intake/SKILL.md +114 -0
- package/claude/skills/claude-intake-answer/REQUIREMENT.md +48 -0
- package/claude/skills/claude-intake-answer/SKILL.md +90 -0
- package/claude/skills/claude-markdown-propose/REQUIREMENT.md +48 -0
- package/claude/skills/claude-markdown-propose/SKILL.md +118 -0
- package/claude/skills/claude-markdown-propose/references/format.md +107 -0
- package/claude/skills/claude-memory-capture/REQUIREMENT.md +50 -0
- package/claude/skills/claude-memory-capture/SKILL.md +101 -0
- package/claude/skills/claude-memory-review/REQUIREMENT.md +50 -0
- package/claude/skills/claude-memory-review/SKILL.md +210 -0
- package/claude/skills/claude-memory-review/references/receipt-format.md +48 -0
- package/claude/skills/claude-orchestrate/REQUIREMENT.md +121 -0
- package/claude/skills/claude-orchestrate/SKILL.md +241 -0
- package/claude/skills/claude-orchestrate/references/orchestrator-dispatch.md +132 -0
- package/claude/skills/claude-orchestrate/references/orchestrator-handoff.md +32 -0
- package/claude/skills/claude-orchestrate/references/orchestrator-parked.md +72 -0
- package/claude/skills/claude-orchestrate/references/orchestrator-poll.md +87 -0
- package/claude/skills/claude-orchestrate/references/orchestrator-resume.md +30 -0
- package/claude/skills/claude-orchestrate/references/orchestrator-sweep.md +21 -0
- package/claude/skills/claude-orchestrate/scripts/poll.sh +373 -0
- package/claude/skills/claude-orchestrate/scripts/watch.sh +181 -0
- package/claude/skills/claude-pr-review/REQUIREMENT.md +47 -0
- package/claude/skills/claude-pr-review/SKILL.md +295 -0
- package/claude/skills/claude-review/REQUIREMENT.md +39 -0
- package/claude/skills/claude-review/SKILL.md +142 -0
- package/claude/skills/claude-seed-sync/REQUIREMENT.md +45 -0
- package/claude/skills/claude-seed-sync/SKILL.md +156 -0
- package/claude/skills/claude-standards-audit/REQUIREMENT.md +33 -0
- package/claude/skills/claude-standards-audit/SKILL.md +99 -0
- package/claude/skills/claude-tasks/REQUIREMENT.md +43 -0
- package/claude/skills/claude-tasks/SKILL.md +161 -0
- package/claude/skills/claude-teach/REQUIREMENT.md +56 -0
- package/claude/skills/claude-teach/SKILL.md +196 -0
- package/claude/skills/claude-teach/references/lesson-craft.md +59 -0
- package/claude/skills/claude-teach/references/pedagogy.md +67 -0
- package/claude/skills/claude-teach/references/promotion.md +54 -0
- package/claude/skills/claude-ui-test/REQUIREMENT.md +40 -0
- package/claude/skills/claude-ui-test/SKILL.md +77 -0
- package/claude/skills/claude-ux-audit/REQUIREMENT.md +40 -0
- package/claude/skills/claude-ux-audit/SKILL.md +79 -0
- package/claude/skills/claude-ux-measure/REQUIREMENT.md +48 -0
- package/claude/skills/claude-ux-measure/SKILL.md +122 -0
- package/claude/skills/claude-worker/REQUIREMENT.md +54 -0
- package/claude/skills/claude-worker/SKILL.md +96 -0
- package/claude/skills/claude-worktree/REQUIREMENT.md +58 -0
- package/claude/skills/claude-worktree/SKILL.md +136 -0
- package/claude/skills/create-rule/REQUIREMENT.md +45 -0
- package/claude/skills/create-rule/SKILL.md +68 -0
- package/claude/skills/create-skill/REQUIREMENT.md +38 -0
- package/claude/skills/create-skill/SKILL.md +32 -0
- package/claude/skills/create-snippet/REQUIREMENT.md +39 -0
- package/claude/skills/create-snippet/SKILL.md +30 -0
- package/claude/skills/create-standard/REQUIREMENT.md +35 -0
- package/claude/skills/create-standard/SKILL.md +29 -0
- package/claude/skills/decision-escalate/REQUIREMENT.md +45 -0
- package/claude/skills/decision-escalate/SKILL.md +79 -0
- package/claude/skills/docs-sync/REQUIREMENT.md +41 -0
- package/claude/skills/docs-sync/SKILL.md +95 -0
- package/claude/skills/git-branch/REQUIREMENT.md +38 -0
- package/claude/skills/git-branch/SKILL.md +60 -0
- package/claude/skills/git-commit/REQUIREMENT.md +36 -0
- package/claude/skills/git-commit/SKILL.md +49 -0
- package/claude/skills/git-followup/REQUIREMENT.md +43 -0
- package/claude/skills/git-followup/SKILL.md +48 -0
- package/claude/skills/git-issue/REQUIREMENT.md +38 -0
- package/claude/skills/git-issue/SKILL.md +65 -0
- package/claude/skills/git-pr/REQUIREMENT.md +50 -0
- package/claude/skills/git-pr/SKILL.md +164 -0
- package/claude/skills/git-pr/references/labels.md +95 -0
- package/claude/skills/git-ship/REQUIREMENT.md +42 -0
- package/claude/skills/git-ship/SKILL.md +55 -0
- package/claude/skills/git-split/REQUIREMENT.md +39 -0
- package/claude/skills/git-split/SKILL.md +162 -0
- package/claude/skills/git-stage/REQUIREMENT.md +39 -0
- package/claude/skills/git-stage/SKILL.md +73 -0
- package/claude/skills/git-worktree/REQUIREMENT.md +38 -0
- package/claude/skills/git-worktree/SKILL.md +130 -0
- package/claude/skills/migration-claude-md/REQUIREMENT.md +40 -0
- package/claude/skills/migration-claude-md/SKILL.md +76 -0
- package/claude/skills/migration-context/REQUIREMENT.md +36 -0
- package/claude/skills/migration-context/SKILL.md +95 -0
- package/claude/skills/migration-standards-drop/REQUIREMENT.md +55 -0
- package/claude/skills/migration-standards-drop/SKILL.md +113 -0
- package/claude/skills/migration-superseded/REQUIREMENT.md +44 -0
- package/claude/skills/migration-superseded/SKILL.md +115 -0
- package/claude/skills/project-commands/REQUIREMENT.md +42 -0
- package/claude/skills/project-commands/SKILL.md +85 -0
- package/claude/skills/restate-plainly/REQUIREMENT.md +41 -0
- package/claude/skills/restate-plainly/SKILL.md +39 -0
- package/claude/skills/session-map/REQUIREMENT.md +57 -0
- package/claude/skills/session-map/SKILL.md +70 -0
- package/claude/skills/session-resume/REQUIREMENT.md +49 -0
- package/claude/skills/session-resume/SKILL.md +51 -0
- package/claude/skills/setup-gov/REQUIREMENT.md +37 -0
- package/claude/skills/setup-gov/SKILL.md +77 -0
- package/claude/skills/setup-indexes/REQUIREMENT.md +45 -0
- package/claude/skills/setup-indexes/SKILL.md +153 -0
- package/claude/skills/setup-init/REQUIREMENT.md +45 -0
- package/claude/skills/setup-init/SKILL.md +127 -0
- package/claude/skills/setup-plugins/REQUIREMENT.md +42 -0
- package/claude/skills/setup-plugins/SKILL.md +81 -0
- package/claude/skills/setup-plugins/references/plugin-catalog.md +53 -0
- package/claude/skills/setup-verify/REQUIREMENT.md +39 -0
- package/claude/skills/setup-verify/SKILL.md +51 -0
- package/claude/skills/systematic-debugging/REQUIREMENT.md +41 -0
- package/claude/skills/systematic-debugging/SKILL.md +70 -0
- package/claude/skills/write-human/REQUIREMENT.md +46 -0
- package/claude/skills/write-human/SKILL.md +68 -0
- package/claude/skills/write-human/references/density.md +38 -0
- package/claude/skills/write-human/references/machine-tells.md +107 -0
- package/claude/skills/write-human/references/source-material.md +37 -0
- package/claude/skills/youtube-transcripts/REQUIREMENT.md +38 -0
- package/claude/skills/youtube-transcripts/SKILL.md +34 -0
- package/docs/agents/audits.md +98 -0
- package/docs/agents/capture.md +37 -0
- package/docs/agents/census.md +23 -0
- package/docs/agents/commands.md +146 -0
- package/docs/agents/comments.md +34 -0
- package/docs/agents/context-audit-checks.md +120 -0
- package/docs/agents/context-audit.md +83 -0
- package/docs/agents/counts.md +76 -0
- package/docs/agents/demo.md +86 -0
- package/docs/agents/docs.md +17 -0
- package/docs/agents/gate.md +84 -0
- package/docs/agents/index.md +47 -0
- package/docs/agents/indexes.md +35 -0
- package/docs/agents/install-and-sync.md +385 -0
- package/docs/agents/intake.md +81 -0
- package/docs/agents/key-changes.md +103 -0
- package/docs/agents/label-coverage.md +73 -0
- package/docs/agents/markdown-audit.md +197 -0
- package/docs/agents/output-shape.md +70 -0
- package/docs/agents/overview.md +26 -0
- package/docs/agents/records.md +170 -0
- package/docs/agents/restated.md +81 -0
- package/docs/agents/review-classification.md +77 -0
- package/docs/agents/routing.md +61 -0
- package/docs/agents/rule-citations.md +98 -0
- package/docs/agents/sandbox.md +71 -0
- package/docs/agents/scripting.md +149 -0
- package/docs/agents/sessions.md +120 -0
- package/docs/agents/skills-audit.md +94 -0
- package/docs/agents/skills-reach.md +64 -0
- package/docs/agents/standards-audit.md +38 -0
- package/docs/agents/state-scoped-risk.md +105 -0
- package/docs/agents/superseded.md +85 -0
- package/docs/agents/targets.md +83 -0
- package/docs/agents/tasks.md +200 -0
- package/docs/agents/teach.md +158 -0
- package/docs/agents/test-order.md +56 -0
- package/docs/agents/worktrees.md +62 -0
- package/docs/ai-workflow.md +317 -0
- package/docs/index.md +23 -0
- package/docs/operating-model.md +223 -0
- package/docs/target-projects.md +258 -0
- package/docs/visual-design-workflow.md +151 -0
- package/docs/zshrc-aliases.md +65 -0
- package/governance/rules/ci/700-ci-workflow.md +44 -0
- package/governance/rules/claude/500-prose.md +15 -0
- package/governance/rules/claude/501-markdown.md +14 -0
- package/governance/rules/claude/510-context.md +28 -0
- package/governance/rules/claude/511-indexes.md +14 -0
- package/governance/rules/claude/520-wireframes.md +12 -0
- package/governance/rules/claude/530-requirements.md +11 -0
- package/governance/rules/claude/540-architecture.md +11 -0
- package/governance/rules/claude/550-design.md +11 -0
- package/governance/rules/claude/555-tasks.md +12 -0
- package/governance/rules/claude/556-groundwork.md +11 -0
- package/governance/rules/claude/557-intake.md +11 -0
- package/governance/rules/claude/558-plan.md +22 -0
- package/governance/rules/claude/559-memory.md +11 -0
- package/governance/rules/claude/560-diagrams.md +18 -0
- package/governance/rules/claude/561-teach.md +13 -0
- package/governance/rules/claude/562-session.md +15 -0
- package/governance/rules/claude/570-skill.md +24 -0
- package/governance/rules/claude/575-hooks.md +17 -0
- package/governance/rules/claude/576-settings.md +14 -0
- package/governance/rules/claude/580-readme.md +11 -0
- package/governance/rules/claude/590-rule-authoring.md +12 -0
- package/governance/rules/claude/591-standard-authoring.md +12 -0
- package/governance/rules/claude/592-claude-md.md +19 -0
- package/governance/rules/core/000-constitution.md +30 -0
- package/governance/rules/core/005-behavior.md +27 -0
- package/governance/rules/core/010-testing.md +35 -0
- package/governance/rules/core/015-output.md +20 -0
- package/governance/rules/core/020-concurrency.md +22 -0
- package/governance/rules/core/025-indexes.md +9 -0
- package/governance/rules/core/030-error-handling.md +31 -0
- package/governance/rules/core/035-tasks.md +13 -0
- package/governance/rules/core/040-performance.md +20 -0
- package/governance/rules/core/045-memory.md +12 -0
- package/governance/rules/core/050-logging.md +20 -0
- package/governance/rules/core/055-scratch.md +9 -0
- package/governance/rules/core/060-naming.md +19 -0
- package/governance/rules/core/065-spelling.md +19 -0
- package/governance/rules/core/070-planning.md +18 -0
- package/governance/rules/core/075-dependencies.md +25 -0
- package/governance/rules/core/080-config-comments.md +22 -0
- package/governance/rules/core/085-worktrees.md +17 -0
- package/governance/rules/core/087-git.md +11 -0
- package/governance/rules/core/090-code-comments.md +39 -0
- package/governance/rules/framework/200-react.md +51 -0
- package/governance/rules/framework/210-astro.md +41 -0
- package/governance/rules/framework/220-fastapi.md +43 -0
- package/governance/rules/framework/230-nextjs.md +48 -0
- package/governance/rules/framework/250-tailwind.md +32 -0
- package/governance/rules/framework/260-shadcn.md +34 -0
- package/governance/rules/lang/100-typescript.md +40 -0
- package/governance/rules/lang/110-python.md +42 -0
- package/governance/rules/lang/120-bash.md +19 -0
- package/governance/rules/lib/300-testing-ts.md +39 -0
- package/governance/rules/lib/305-e2e-reliability.md +34 -0
- package/governance/rules/lib/306-test-scope.md +25 -0
- package/governance/rules/lib/310-zod.md +25 -0
- package/governance/rules/lib/320-tanstack-query.md +32 -0
- package/governance/rules/lib/330-testing-py.md +44 -0
- package/governance/rules/lib/340-pydantic.md +38 -0
- package/governance/rules/lib/350-security-web.md +32 -0
- package/governance/rules/lib/360-security-server.md +39 -0
- package/governance/rules/lib/370-database.md +35 -0
- package/governance/rules/snippets/505-at-references.md +9 -0
- package/governance/rules/ui/400-ui.md +36 -0
- package/governance/rules/ui/410-a11y.md +48 -0
- package/governance/rules/ui/420-forms.md +36 -0
- package/governance/rules/ui/430-ux-completeness.md +65 -0
- package/governance/rules/ui/440-surface-capture.md +34 -0
- package/governance/rules/ui/450-link-behavior.md +19 -0
- package/governance/stacks/astro.toml +2 -0
- package/governance/stacks/base.toml +8 -0
- package/governance/stacks/node-server.toml +2 -0
- package/governance/stacks/node.toml +2 -0
- package/governance/stacks/python-fastapi.toml +2 -0
- package/governance/stacks/python.toml +2 -0
- package/governance/stacks/react.toml +2 -0
- package/package.json +69 -0
- package/scripts/config.sh +11 -0
- package/scripts/core/bootstrap.sh +81 -0
- package/scripts/core/check-color-source.sh +41 -0
- package/scripts/core/check-ignore-parity.sh +162 -0
- package/scripts/core/check-plugin-boundary.sh +45 -0
- package/scripts/core/check-seed-independence.sh +59 -0
- package/scripts/core/check-skill-paths.sh +24 -0
- package/scripts/core/clean.sh +36 -0
- package/scripts/core/install-check.sh +101 -0
- package/scripts/core/list-seed-roots.sh +18 -0
- package/scripts/core/regen-claude-copies.sh +10 -0
- package/scripts/core/regen-hero.sh +217 -0
- package/scripts/core/regen-indexes.sh +10 -0
- package/scripts/core/regen-tooling-paths.sh +61 -0
- package/scripts/core/repair-bare-flag.sh +19 -0
- package/scripts/core/snapshot.sh +134 -0
- package/scripts/core/update.sh +35 -0
- package/scripts/docs/list.sh +165 -0
- package/scripts/lib/frontmatter.sh +30 -0
- package/scripts/lib/gov.sh +14 -0
- package/scripts/lib/sandbox-fixtures.sh +191 -0
- package/scripts/lib/sandbox-git.sh +125 -0
- package/scripts/lib/sandbox-path.sh +206 -0
- package/scripts/lib/tooling.sh +35 -0
- package/scripts/lib/ui.sh +266 -0
- package/scripts/lib/worktree.sh +20 -0
- package/scripts/manage-sandbox.sh +466 -0
- package/scripts/snippets/create.sh +156 -0
- package/scripts/standards/list.sh +115 -0
- package/scripts/tooling/create.sh +109 -0
- package/scripts/tooling/verify.sh +179 -0
- package/snippets/align.md +12 -0
- package/snippets/claude/decision-memo.md +39 -0
- package/snippets/claude/feature-recap.md +19 -0
- package/snippets/claude/figma-steps.md +24 -0
- package/snippets/compact-summary.md +5 -0
- package/snippets/decision-help.md +6 -0
- package/snippets/meta-prompt.md +14 -0
- package/snippets/research-prompt.md +7 -0
- package/snippets/session-notes.md +11 -0
- package/snippets/snippets.toml +5 -0
- package/snippets/step-by-step.md +10 -0
- package/snippets/web-research.md +21 -0
- package/src/audits/baseline.ts +201 -0
- package/src/audits/catalog.ts +876 -0
- package/src/audits/run.ts +204 -0
- package/src/autoship/classify.ts +75 -0
- package/src/autoship/paths.ts +51 -0
- package/src/binary.ts +16 -0
- package/src/browser/engine.ts +40 -0
- package/src/census/count.ts +113 -0
- package/src/claude/cases/all.ts +24 -0
- package/src/claude/cases/authoring.ts +53 -0
- package/src/claude/cases/claude-workflow.ts +158 -0
- package/src/claude/cases/git.ts +44 -0
- package/src/claude/cases/misc.ts +27 -0
- package/src/claude/cases/setup.ts +94 -0
- package/src/claude/gitignore.ts +51 -0
- package/src/claude/routing.ts +283 -0
- package/src/claude/seeds-list.ts +47 -0
- package/src/claude/seeds.ts +150 -0
- package/src/claude/settings.ts +151 -0
- package/src/claude/skills-audit.ts +228 -0
- package/src/claude/skills-drift.ts +156 -0
- package/src/claude/skills-list.ts +99 -0
- package/src/claude/skills-rank.ts +320 -0
- package/src/claude/skills-reach.ts +227 -0
- package/src/cli-run.ts +43 -0
- package/src/cli.ts +200 -0
- package/src/commands/audits.ts +350 -0
- package/src/commands/autoship.ts +129 -0
- package/src/commands/capture.ts +133 -0
- package/src/commands/census.ts +105 -0
- package/src/commands/claude.ts +1286 -0
- package/src/commands/comments.ts +240 -0
- package/src/commands/context.ts +857 -0
- package/src/commands/demo.ts +389 -0
- package/src/commands/deps.ts +173 -0
- package/src/commands/design.ts +36 -0
- package/src/commands/docs.ts +60 -0
- package/src/commands/feedback-format.ts +23 -0
- package/src/commands/feedback.ts +112 -0
- package/src/commands/gate.ts +189 -0
- package/src/commands/gov.ts +1265 -0
- package/src/commands/indexes.ts +184 -0
- package/src/commands/init.ts +113 -0
- package/src/commands/intake.ts +406 -0
- package/src/commands/inventory.ts +256 -0
- package/src/commands/labels.ts +361 -0
- package/src/commands/markdown.ts +544 -0
- package/src/commands/migrate.ts +175 -0
- package/src/commands/pass-through.ts +39 -0
- package/src/commands/pr.ts +411 -0
- package/src/commands/records.ts +728 -0
- package/src/commands/sandbox.ts +468 -0
- package/src/commands/secrets.ts +132 -0
- package/src/commands/serve.ts +159 -0
- package/src/commands/sessions.ts +408 -0
- package/src/commands/slides.ts +126 -0
- package/src/commands/snippets.ts +84 -0
- package/src/commands/standards.ts +247 -0
- package/src/commands/sync.ts +428 -0
- package/src/commands/targets.ts +319 -0
- package/src/commands/tasks.ts +743 -0
- package/src/commands/teach.ts +786 -0
- package/src/commands/tooling.ts +573 -0
- package/src/commands/transcripts.ts +44 -0
- package/src/commands/upgrade.ts +231 -0
- package/src/commands/wiki.ts +100 -0
- package/src/commands/worktrees.ts +191 -0
- package/src/comments/scan.ts +338 -0
- package/src/comments/trend.ts +207 -0
- package/src/comments/vocabulary.ts +85 -0
- package/src/context/architecture.ts +364 -0
- package/src/context/audit.ts +790 -0
- package/src/context/citations.ts +196 -0
- package/src/context/folders.ts +186 -0
- package/src/context/gate.ts +57 -0
- package/src/context/index-drift.ts +64 -0
- package/src/context/narration.ts +99 -0
- package/src/copy.ts +30 -0
- package/src/counts/catalogs.ts +96 -0
- package/src/counts/numbers.ts +79 -0
- package/src/counts/scan.ts +314 -0
- package/src/demo/beats.ts +135 -0
- package/src/demo/compile.ts +326 -0
- package/src/demo/container.ts +63 -0
- package/src/demo/cursors.ts +55 -0
- package/src/demo/drive.ts +357 -0
- package/src/demo/pointer.ts +178 -0
- package/src/demo/theme.ts +112 -0
- package/src/deps/audit.ts +153 -0
- package/src/design/parse.ts +116 -0
- package/src/design/render.ts +249 -0
- package/src/docs/read.ts +77 -0
- package/src/exec.ts +16 -0
- package/src/exempt-marker.ts +43 -0
- package/src/frontmatter.ts +13 -0
- package/src/gate/measures.ts +682 -0
- package/src/gate/sequencer.ts +386 -0
- package/src/gate/stages.ts +412 -0
- package/src/git-env.ts +36 -0
- package/src/git-files.ts +105 -0
- package/src/git-ignore.ts +46 -0
- package/src/github-format.ts +13 -0
- package/src/github.ts +24 -0
- package/src/gov/adapter.ts +103 -0
- package/src/gov/citations.ts +514 -0
- package/src/gov/consumed.ts +129 -0
- package/src/gov/install.ts +132 -0
- package/src/gov/list.ts +106 -0
- package/src/gov/payload.ts +39 -0
- package/src/gov/restated.ts +814 -0
- package/src/gov/stacks.ts +205 -0
- package/src/gov/superseded.ts +415 -0
- package/src/gov/test-order.ts +407 -0
- package/src/indexes/frontmatter.ts +46 -0
- package/src/indexes/regen.ts +84 -0
- package/src/indexes/render.ts +201 -0
- package/src/indexes/walk.ts +83 -0
- package/src/init/flags.ts +60 -0
- package/src/init/plan.ts +114 -0
- package/src/init/run.ts +46 -0
- package/src/init/steps.ts +77 -0
- package/src/intake/folder.ts +320 -0
- package/src/intake/items.ts +174 -0
- package/src/inventory/config.ts +117 -0
- package/src/inventory/group.ts +76 -0
- package/src/inventory/subjects.ts +114 -0
- package/src/inventory/walk.ts +129 -0
- package/src/labels/audit.ts +82 -0
- package/src/labels/coverage.ts +79 -0
- package/src/labels/map.ts +101 -0
- package/src/labels/phase.ts +95 -0
- package/src/markdown/bans.ts +94 -0
- package/src/markdown/files.ts +102 -0
- package/src/markdown/gate.ts +28 -0
- package/src/markdown/scan.ts +295 -0
- package/src/markdown/structure.ts +730 -0
- package/src/migrate/apply.ts +115 -0
- package/src/migrate/plan.ts +103 -0
- package/src/migrate/rename.ts +183 -0
- package/src/pr/bijection.ts +145 -0
- package/src/pr/paths.ts +335 -0
- package/src/process/harness.ts +167 -0
- package/src/project-root.ts +19 -0
- package/src/records/backup.ts +455 -0
- package/src/records/migrate.ts +78 -0
- package/src/records/size.ts +260 -0
- package/src/records/validate.ts +1162 -0
- package/src/sandbox/census.ts +228 -0
- package/src/sandbox/coverage.ts +115 -0
- package/src/sandbox/expect.ts +629 -0
- package/src/sandbox/tree.ts +47 -0
- package/src/secrets/marker.ts +30 -0
- package/src/secrets/patterns.ts +142 -0
- package/src/secrets/scan.ts +123 -0
- package/src/secrets/shipped.ts +111 -0
- package/src/seed-marker.ts +74 -0
- package/src/serve/static.ts +322 -0
- package/src/sessions/claim.ts +85 -0
- package/src/sessions/live.ts +79 -0
- package/src/sessions/registry.ts +137 -0
- package/src/sessions/resolve.ts +333 -0
- package/src/slides/layouts.ts +391 -0
- package/src/slides/open.ts +18 -0
- package/src/slides/parse.ts +84 -0
- package/src/slides/render.ts +88 -0
- package/src/slides/styles.ts +44 -0
- package/src/snippets/categories.ts +66 -0
- package/src/snippets/list.ts +32 -0
- package/src/snippets/presets.ts +50 -0
- package/src/standards/audit.ts +132 -0
- package/src/standards/read.ts +100 -0
- package/src/sync/check.ts +692 -0
- package/src/sync/engine.ts +576 -0
- package/src/sync/git.ts +225 -0
- package/src/sync/history.ts +123 -0
- package/src/sync/layout.ts +140 -0
- package/src/sync/reverse.ts +268 -0
- package/src/sync/seeds-report.ts +126 -0
- package/src/sync/stamp.ts +358 -0
- package/src/sync/target.ts +70 -0
- package/src/sync/workflow.ts +200 -0
- package/src/target.ts +43 -0
- package/src/targets/pulls.ts +250 -0
- package/src/targets/registry.ts +203 -0
- package/src/targets/resolve.ts +145 -0
- package/src/targets/sweep.ts +246 -0
- package/src/tasks/archive.ts +506 -0
- package/src/tasks/record.ts +311 -0
- package/src/tasks/trunk.ts +89 -0
- package/src/tasks/validate.ts +1001 -0
- package/src/teach/lesson.ts +180 -0
- package/src/teach/workspace.ts +842 -0
- package/src/tooling/gitignore.ts +122 -0
- package/src/tooling/inject.ts +193 -0
- package/src/tooling/list.ts +39 -0
- package/src/tooling/manifest.ts +176 -0
- package/src/tooling/package.ts +166 -0
- package/src/tooling/read.ts +65 -0
- package/src/tooling/scan.ts +147 -0
- package/src/tooling/stamp.ts +44 -0
- package/src/transcripts/fetch.ts +156 -0
- package/src/transcripts/metadata.ts +54 -0
- package/src/transcripts/vtt.ts +114 -0
- package/src/ui.ts +266 -0
- package/src/version/compare.ts +53 -0
- package/src/version/installed.ts +40 -0
- package/src/version/manager.ts +67 -0
- package/src/version/skew.ts +192 -0
- package/src/wiki/init.ts +85 -0
- package/src/worktree.ts +143 -0
- package/src/worktrees/reclaim.ts +306 -0
- package/standards/architecture.md +72 -0
- package/standards/branch.md +59 -0
- package/standards/commit.md +72 -0
- package/standards/context.md +151 -0
- package/standards/design.md +93 -0
- package/standards/diagrams.md +152 -0
- package/standards/glossary.md +75 -0
- package/standards/groundwork.md +211 -0
- package/standards/index.md +36 -0
- package/standards/intake.md +192 -0
- package/standards/issue.md +94 -0
- package/standards/markdown.md +137 -0
- package/standards/memory.md +144 -0
- package/standards/plan.md +172 -0
- package/standards/pr.md +139 -0
- package/standards/publish.md +51 -0
- package/standards/readme.md +208 -0
- package/standards/requirements.md +70 -0
- package/standards/rule.md +118 -0
- package/standards/session.md +109 -0
- package/standards/skill.md +300 -0
- package/standards/slug.md +39 -0
- package/standards/snippets.md +76 -0
- package/standards/standard.md +170 -0
- package/standards/tasks.md +254 -0
- package/standards/teach.md +153 -0
- package/standards/versioning.md +71 -0
- package/standards/wireframes.md +113 -0
- package/tooling/astro/configs/astro.config.mjs +31 -0
- package/tooling/astro/configs/eslint.config.js +79 -0
- package/tooling/astro/configs/playwright.config.ts +26 -0
- package/tooling/astro/configs/tsconfig.json +12 -0
- package/tooling/astro/configs/vitest.config.ts +22 -0
- package/tooling/astro/manifest.toml +34 -0
- package/tooling/astro/reference.md +60 -0
- package/tooling/base/configs/.editorconfig +5 -0
- package/tooling/base/configs/.github/pull_request_template.md +18 -0
- package/tooling/base/configs/.github/workflows/verify.yml +35 -0
- package/tooling/base/configs/.husky/commit-msg +1 -0
- package/tooling/base/configs/.husky/post-merge +61 -0
- package/tooling/base/configs/.husky/post-rewrite +21 -0
- package/tooling/base/configs/.husky/pre-commit +1 -0
- package/tooling/base/configs/.husky/pre-push +1 -0
- package/tooling/base/configs/.prettierrc +12 -0
- package/tooling/base/configs/.shellcheckrc +1 -0
- package/tooling/base/configs/.vscode/extensions.json +9 -0
- package/tooling/base/configs/.vscode/settings.json +3 -0
- package/tooling/base/configs/commitlint.config.js +11 -0
- package/tooling/base/configs/scripts/verify.sh +64 -0
- package/tooling/base/manifest.toml +30 -0
- package/tooling/base/reference.md +98 -0
- package/tooling/base/seeds/.claude/context/ci.md +33 -0
- package/tooling/base/seeds/.claude/context/development.md +37 -0
- package/tooling/base/seeds/.claude/context/index.md +11 -0
- package/tooling/base/seeds/.cspell/project-terms.txt +0 -0
- package/tooling/base/seeds/.cspell/tech-stack.txt +19 -0
- package/tooling/base/seeds/.lintstagedrc +8 -0
- package/tooling/base/seeds/.prettierignore +0 -0
- package/tooling/base/seeds/cspell.json +20 -0
- package/tooling/claude/manifest.toml +14 -0
- package/tooling/claude/reference.md +79 -0
- package/tooling/claude/seeds/.claude/ARCHITECTURE.md +13 -0
- package/tooling/claude/seeds/.claude/DESIGN.md +62 -0
- package/tooling/claude/seeds/.claude/REQUIREMENTS.md +18 -0
- package/tooling/claude/seeds/.claude/diagrams/index.md +8 -0
- package/tooling/claude/seeds/.claude/hooks/index-reminder.sh +50 -0
- package/tooling/claude/seeds/.claude/hooks/memory-index.sh +68 -0
- package/tooling/claude/seeds/.claude/hooks/path-form.sh +57 -0
- package/tooling/claude/seeds/.claude/hooks/scratch-guard.sh +52 -0
- package/tooling/claude/seeds/.claude/hooks/standards-audit.sh +86 -0
- package/tooling/claude/seeds/.claude/hooks/tasks-index.sh +71 -0
- package/tooling/claude/seeds/.claude/memory/index.md +8 -0
- package/tooling/claude/seeds/.claude/settings.json +47 -0
- package/tooling/claude/seeds/.claude/tasks/index.md +8 -0
- package/tooling/claude/seeds/.claude/wireframes/index.md +8 -0
- package/tooling/claude/seeds/CLAUDE.md +29 -0
- package/tooling/claude/user/settings.template.json +10 -0
- package/tooling/claude/user/statusline-command.sh +53 -0
- package/tooling/python/configs/.coveragerc +14 -0
- package/tooling/python/configs/.python-version +1 -0
- package/tooling/python/configs/mypy.ini +6 -0
- package/tooling/python/configs/pytest.ini +4 -0
- package/tooling/python/configs/ruff.toml +15 -0
- package/tooling/python/configs/scripts/verify.sh +77 -0
- package/tooling/python/manifest.toml +16 -0
- package/tooling/python/reference.md +66 -0
- package/tooling/python/seeds/.cspell/tech-stack.txt +19 -0
- package/tooling/python/seeds/tests/test_smoke.py +2 -0
- package/tooling/vite-react/configs/playwright.config.ts +26 -0
- package/tooling/vite-react/configs/tsconfig.json +35 -0
- package/tooling/vite-react/configs/vite.config.ts +24 -0
- package/tooling/vite-react/configs/vitest.config.ts +26 -0
- package/tooling/vite-react/manifest.toml +23 -0
- package/tooling/vite-react/reference.md +55 -0
- package/tooling/vite-react/seeds/.cspell/project-terms.txt +1 -0
- package/tooling/vite-react/seeds/.cspell/tech-stack.txt +1 -0
- package/tooling/web/configs/.github/workflows/verify.yml +134 -0
- package/tooling/web/configs/.vscode/extensions.json +13 -0
- package/tooling/web/configs/.vscode/settings.json +10 -0
- package/tooling/web/configs/e2e/home.spec.ts +6 -0
- package/tooling/web/configs/e2e/screenshot.ts +53 -0
- package/tooling/web/configs/eslint.config.js +82 -0
- package/tooling/web/configs/scripts/screenshot.sh +28 -0
- package/tooling/web/configs/scripts/verify.sh +80 -0
- package/tooling/web/configs/scripts/worktree-port.sh +74 -0
- package/tooling/web/configs/src/test/setup.ts +8 -0
- package/tooling/web/manifest.toml +58 -0
- package/tooling/web/reference.md +115 -0
- package/tooling/web/seeds/.cspell/tech-stack.txt +18 -0
- package/tsconfig.json +14 -0
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
import type { Command } from 'commander'
|
|
4
|
+
import { execScript } from '@/exec'
|
|
5
|
+
import { PROJECT_ROOT } from '@/project-root'
|
|
6
|
+
import {
|
|
7
|
+
assertedPercent,
|
|
8
|
+
collectCensus,
|
|
9
|
+
type CensusReport,
|
|
10
|
+
} from '@/sandbox/census'
|
|
11
|
+
import {
|
|
12
|
+
collectCoverage,
|
|
13
|
+
coveragePercent,
|
|
14
|
+
type CoverageReport,
|
|
15
|
+
} from '@/sandbox/coverage'
|
|
16
|
+
import {
|
|
17
|
+
expectFilePath,
|
|
18
|
+
parseTarget,
|
|
19
|
+
resolveVerdict,
|
|
20
|
+
verdictExitCode,
|
|
21
|
+
type RunEnvelope,
|
|
22
|
+
type Verdict,
|
|
23
|
+
} from '@/sandbox/expect'
|
|
24
|
+
import { sandboxTree } from '@/sandbox/tree'
|
|
25
|
+
import {
|
|
26
|
+
frameError,
|
|
27
|
+
intro,
|
|
28
|
+
logError,
|
|
29
|
+
logInfo,
|
|
30
|
+
logRemove,
|
|
31
|
+
logStep,
|
|
32
|
+
logWarn,
|
|
33
|
+
outro,
|
|
34
|
+
select,
|
|
35
|
+
} from '@/ui'
|
|
36
|
+
|
|
37
|
+
const SANDBOX_DIR = join(PROJECT_ROOT, 'scripts', 'sandbox')
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Reports that the scenario tree does not ship, and answers whether it reported,
|
|
41
|
+
* so a caller that sees `true` returns without touching the tree.
|
|
42
|
+
*
|
|
43
|
+
* `scripts/sandbox` is excluded from the published package, so an installed
|
|
44
|
+
* `canon` resolves `SANDBOX_DIR` to a directory that is not there. Both entry
|
|
45
|
+
* points that walk the tree ask here rather than carrying a check each, because
|
|
46
|
+
* absence is a property of the install rather than of a verb, and a second copy
|
|
47
|
+
* of the question is a second message to keep true. `check` reads the tree as
|
|
48
|
+
* well, through `expectFilePath`, and needs no guard because its provisioned-tree
|
|
49
|
+
* check already stops an installed run before the read.
|
|
50
|
+
*
|
|
51
|
+
* The distinction it preserves is between an absent tree and an empty one. Only
|
|
52
|
+
* the second is a real zero, and a coverage percentage over a denominator nobody
|
|
53
|
+
* looked at reads as a suite that examined everything and found it clean.
|
|
54
|
+
*
|
|
55
|
+
* The frame opens and closes here, so this runs before `intro` rather than
|
|
56
|
+
* inside an open frame.
|
|
57
|
+
*/
|
|
58
|
+
function reportAbsentScenarioTree(): boolean {
|
|
59
|
+
if (existsSync(SANDBOX_DIR)) return false
|
|
60
|
+
|
|
61
|
+
frameError('sandbox is toolkit-only and is absent from an installed canon')
|
|
62
|
+
process.exitCode = 1
|
|
63
|
+
|
|
64
|
+
return true
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Holds fixture content for scenarios rather than scenarios of its own.
|
|
69
|
+
* Twin of the `-not -name fixtures` filter in `scripts/manage-sandbox.sh`.
|
|
70
|
+
*/
|
|
71
|
+
const FIXTURES_DIR = 'fixtures'
|
|
72
|
+
|
|
73
|
+
/** A run that reported nothing counts as clean, so only real signals fail. */
|
|
74
|
+
const CLEAN_ENVELOPE: RunEnvelope = { isError: false, turns: 0, denials: 0 }
|
|
75
|
+
|
|
76
|
+
interface CheckOptions {
|
|
77
|
+
readonly envelope?: string
|
|
78
|
+
readonly writes?: string
|
|
79
|
+
readonly escapes?: string
|
|
80
|
+
readonly escapesWatched?: boolean
|
|
81
|
+
readonly json?: boolean
|
|
82
|
+
readonly strict?: boolean
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
interface CoverageOptions {
|
|
86
|
+
readonly json?: boolean
|
|
87
|
+
readonly strict?: boolean
|
|
88
|
+
readonly skills?: boolean
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function getCategories(): string[] {
|
|
92
|
+
return readdirSync(SANDBOX_DIR, { withFileTypes: true })
|
|
93
|
+
.filter((d) => d.isDirectory() && d.name !== FIXTURES_DIR)
|
|
94
|
+
.map((d) => d.name)
|
|
95
|
+
.sort()
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function getCommands(category: string): string[] {
|
|
99
|
+
return readdirSync(join(SANDBOX_DIR, category), { withFileTypes: true })
|
|
100
|
+
.filter((f) => f.isFile() && f.name.endsWith('.sh'))
|
|
101
|
+
.map((f) => f.name.replace(/\.sh$/, ''))
|
|
102
|
+
.sort()
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function interactivePicker(): Promise<string> {
|
|
106
|
+
intro('canon sandbox')
|
|
107
|
+
|
|
108
|
+
const categories = getCategories()
|
|
109
|
+
const category = await select({
|
|
110
|
+
message: 'Select category:',
|
|
111
|
+
options: categories.map((c) => ({ value: c, label: c })),
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
const commands = getCommands(category)
|
|
115
|
+
const command = await select({
|
|
116
|
+
message: 'Select command:',
|
|
117
|
+
options: commands.map((c) => ({ value: c, label: c })),
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
return `${category}:${command}`
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Reads the `claude -p --output-format json` envelope. Returns undefined when no
|
|
125
|
+
* file was given, so the turn ceiling reports as skipped rather than passing on a
|
|
126
|
+
* fabricated zero. A file that exists but does not parse falls back to clean,
|
|
127
|
+
* since decision 8 lets the envelope fail a run but never pass one.
|
|
128
|
+
*
|
|
129
|
+
* `result` carries the reply text and is left undefined on both fallbacks, which
|
|
130
|
+
* routes a reply assertion to skipped. Fabricating an empty string there would
|
|
131
|
+
* fail every reply assertion on a run that supplied no envelope, turning a gap in
|
|
132
|
+
* the input into a red arm.
|
|
133
|
+
*/
|
|
134
|
+
function readEnvelope(path: string | undefined): RunEnvelope | undefined {
|
|
135
|
+
if (path === undefined) return undefined
|
|
136
|
+
if (!existsSync(path)) return CLEAN_ENVELOPE
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
const parsed = JSON.parse(readFileSync(path, 'utf8')) as Record<
|
|
140
|
+
string,
|
|
141
|
+
unknown
|
|
142
|
+
>
|
|
143
|
+
const denials = parsed.permission_denials
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
isError: parsed.is_error === true,
|
|
147
|
+
turns: typeof parsed.num_turns === 'number' ? parsed.num_turns : 0,
|
|
148
|
+
denials: Array.isArray(denials) ? denials.length : 0,
|
|
149
|
+
reply: typeof parsed.result === 'string' ? parsed.result : undefined,
|
|
150
|
+
}
|
|
151
|
+
} catch {
|
|
152
|
+
return CLEAN_ENVELOPE
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Shared by `--writes` and `--escapes`, which are both a newline-delimited
|
|
158
|
+
* path list written by `run.sh`. Undefined when no file was given, which is
|
|
159
|
+
* not the same as a run that produced no paths. Write scope and escape scope
|
|
160
|
+
* both need that distinction: an empty list is a finding, an absent list is a
|
|
161
|
+
* gap in what the caller supplied.
|
|
162
|
+
*/
|
|
163
|
+
function readPathList(path: string | undefined): string[] | undefined {
|
|
164
|
+
if (path === undefined) return undefined
|
|
165
|
+
if (!existsSync(path)) return []
|
|
166
|
+
|
|
167
|
+
return readFileSync(path, 'utf8')
|
|
168
|
+
.split('\n')
|
|
169
|
+
.map((line) => line.trim())
|
|
170
|
+
.filter((line) => line !== '')
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* The unchecked count rides the summary line in every verdict, including a pass.
|
|
175
|
+
* It is the number the rollout is measured by, and a count that surfaces only on
|
|
176
|
+
* arms that happen to carry prose is a count nobody drains.
|
|
177
|
+
*/
|
|
178
|
+
function reportVerdict(verdict: Verdict): void {
|
|
179
|
+
if (verdict.results.length > 0) {
|
|
180
|
+
logStep('Checking expectations')
|
|
181
|
+
for (const result of verdict.results) {
|
|
182
|
+
if (result.ok) logInfo(result.message)
|
|
183
|
+
else logRemove(result.message)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (verdict.manual.length > 0) {
|
|
188
|
+
logStep('Not checked (needs a reader)')
|
|
189
|
+
for (const line of verdict.manual) logWarn(line)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (verdict.skipped.length > 0) {
|
|
193
|
+
logStep('Not checked (no data supplied)')
|
|
194
|
+
for (const line of verdict.skipped) logWarn(line)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
logStep(`Verdict: ${verdict.state.toUpperCase()}`)
|
|
198
|
+
if (verdict.note !== undefined) logWarn(verdict.note)
|
|
199
|
+
|
|
200
|
+
const summary = `${verdict.asserted} asserted, ${verdict.failed} failed, ${verdict.unchecked} unchecked`
|
|
201
|
+
if (verdict.state === 'unchecked') logWarn(summary)
|
|
202
|
+
else logInfo(summary)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Lists the undeclared scenarios by name rather than counting them. A percentage
|
|
207
|
+
* alone is a number nobody acts on, while a name is the next thing to arm.
|
|
208
|
+
*/
|
|
209
|
+
function reportCoverage(report: CoverageReport): void {
|
|
210
|
+
const armed = report.scenarios.filter((s) => s.armed.length > 0)
|
|
211
|
+
const bare = report.scenarios.filter((s) => s.armed.length === 0)
|
|
212
|
+
|
|
213
|
+
if (armed.length > 0) {
|
|
214
|
+
logStep('Declares expectations')
|
|
215
|
+
for (const scenario of armed) {
|
|
216
|
+
logInfo(
|
|
217
|
+
`${scenario.category}:${scenario.command} (${scenario.armed.join(', ')})`,
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (bare.length > 0) {
|
|
223
|
+
logStep('Provisions only, asserts nothing')
|
|
224
|
+
for (const scenario of bare)
|
|
225
|
+
logWarn(`${scenario.category}:${scenario.command}`)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
logStep('Coverage')
|
|
229
|
+
const summary =
|
|
230
|
+
`${report.armedScenarios}/${report.totalScenarios} scenarios declared ` +
|
|
231
|
+
`(${coveragePercent(report)}%), ${report.armedArms} armed arms`
|
|
232
|
+
if (bare.length > 0) logWarn(summary)
|
|
233
|
+
else logInfo(summary)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Names the skills nothing can fail, which is the work queue the arm batches
|
|
238
|
+
* consume. Exempt entries print their reason, since an exemption with no reason
|
|
239
|
+
* beside it is indistinguishable from a skill nobody has got to yet.
|
|
240
|
+
*/
|
|
241
|
+
function reportCensus(report: CensusReport): void {
|
|
242
|
+
const shouldBe = report.skills.filter(
|
|
243
|
+
(s) => s.verdict === 'should-be-asserted',
|
|
244
|
+
)
|
|
245
|
+
const exempt = report.skills.filter((s) => s.verdict === 'exempt')
|
|
246
|
+
|
|
247
|
+
if (shouldBe.length > 0) {
|
|
248
|
+
logStep('No arm can fail these')
|
|
249
|
+
for (const entry of shouldBe)
|
|
250
|
+
logWarn(
|
|
251
|
+
`${entry.skill} (${entry.scenarios.length === 0 ? 'no scenario' : entry.scenarios.join(', ')})`,
|
|
252
|
+
)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (exempt.length > 0) {
|
|
256
|
+
logStep('Exempt')
|
|
257
|
+
for (const entry of exempt) logInfo(`${entry.skill}: ${entry.reason}`)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// A wrong exemption is the one way this report can overstate itself, so both
|
|
261
|
+
// kinds print as errors even on a run that is otherwise clean. They separate
|
|
262
|
+
// because the reader checks a different thing: whether the skill left the
|
|
263
|
+
// tree, or whether an arm landed and the entry outlived its reason.
|
|
264
|
+
if (report.staleExemptions.length > 0) {
|
|
265
|
+
logStep('Exemptions naming no shipped skill')
|
|
266
|
+
for (const skill of report.staleExemptions) logError(skill)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (report.supersededExemptions.length > 0) {
|
|
270
|
+
logStep('Exemptions an arm now asserts')
|
|
271
|
+
for (const skill of report.supersededExemptions)
|
|
272
|
+
logError(`${skill}, delete the entry`)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
logStep('Skills')
|
|
276
|
+
const summary =
|
|
277
|
+
`${report.asserted}/${report.totalSkills} skills asserted ` +
|
|
278
|
+
`(${assertedPercent(report)}%), ${report.shouldBeAsserted} should be, ` +
|
|
279
|
+
`${report.exempt} exempt`
|
|
280
|
+
if (report.shouldBeAsserted > 0) logWarn(summary)
|
|
281
|
+
else logInfo(summary)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function runCoverage(options: CoverageOptions): void {
|
|
285
|
+
if (reportAbsentScenarioTree()) return
|
|
286
|
+
|
|
287
|
+
intro('canon sandbox coverage')
|
|
288
|
+
|
|
289
|
+
const report = collectCoverage(PROJECT_ROOT)
|
|
290
|
+
|
|
291
|
+
// A broken `exempt.toml` reads as a caller error, not as a crash. The parser
|
|
292
|
+
// throws rather than returning a smaller set, since a dropped exemption is
|
|
293
|
+
// invisible in the counts, and the conversion belongs here for the reason
|
|
294
|
+
// `resolveVerdict` catches its own parse: a typo in a declaration should read
|
|
295
|
+
// the way a pattern that does not compile does rather than as a stack trace.
|
|
296
|
+
let census: CensusReport | undefined
|
|
297
|
+
if (options.skills === true) {
|
|
298
|
+
try {
|
|
299
|
+
census = collectCensus(PROJECT_ROOT, report)
|
|
300
|
+
} catch (error) {
|
|
301
|
+
const reason = error instanceof Error ? error.message : String(error)
|
|
302
|
+
reportCoverage(report)
|
|
303
|
+
logStep('Skills')
|
|
304
|
+
logError(`Cannot read the exemptions: ${reason}`)
|
|
305
|
+
outro()
|
|
306
|
+
process.exitCode = 1
|
|
307
|
+
|
|
308
|
+
return
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// The scenario view stays whichever way `--skills` is set. The two count
|
|
313
|
+
// different denominators, and replacing one with the other loses the scenario
|
|
314
|
+
// rollout the strict gate is written against.
|
|
315
|
+
reportCoverage(report)
|
|
316
|
+
if (census !== undefined) reportCensus(census)
|
|
317
|
+
|
|
318
|
+
if (options.json === true)
|
|
319
|
+
process.stdout.write(
|
|
320
|
+
`${JSON.stringify(census === undefined ? report : { ...report, ...census })}\n`,
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
outro()
|
|
324
|
+
|
|
325
|
+
// A wrong exemption exits non-zero without `--strict`. It is a wrong claim in
|
|
326
|
+
// committed data rather than a rollout still in progress, and the whole point
|
|
327
|
+
// of the verdict is that an exemption someone can no longer check is worse
|
|
328
|
+
// than no exemption at all.
|
|
329
|
+
const wrongExemption =
|
|
330
|
+
(census?.staleExemptions.length ?? 0) > 0 ||
|
|
331
|
+
(census?.supersededExemptions.length ?? 0) > 0
|
|
332
|
+
const rolloutIncomplete =
|
|
333
|
+
options.strict === true && report.armedScenarios < report.totalScenarios
|
|
334
|
+
process.exitCode = wrongExemption || rolloutIncomplete ? 1 : 0
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function runCheck(
|
|
338
|
+
target: string,
|
|
339
|
+
arm: string | undefined,
|
|
340
|
+
options: CheckOptions,
|
|
341
|
+
): void {
|
|
342
|
+
intro('canon sandbox check')
|
|
343
|
+
|
|
344
|
+
const parsed = parseTarget(target)
|
|
345
|
+
if (parsed === undefined) {
|
|
346
|
+
logError('Invalid target. Use <category>:<command>, e.g. claude:docs.')
|
|
347
|
+
outro()
|
|
348
|
+
process.exitCode = 1
|
|
349
|
+
return
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// A sandbox that was never provisioned fails every path assertion, reading as a
|
|
353
|
+
// skill that did nothing rather than a caller that ran check too early. The
|
|
354
|
+
// whole point of a verdict is that it means what it says.
|
|
355
|
+
const sandboxDir = sandboxTree()
|
|
356
|
+
if (!existsSync(sandboxDir)) {
|
|
357
|
+
logError(`No sandbox at ${sandboxDir}. Provision one with canon sandbox.`)
|
|
358
|
+
outro()
|
|
359
|
+
process.exitCode = 1
|
|
360
|
+
return
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const verdict = resolveVerdict(
|
|
364
|
+
expectFilePath(PROJECT_ROOT, parsed.category, parsed.command, arm ?? ''),
|
|
365
|
+
{
|
|
366
|
+
sandboxDir,
|
|
367
|
+
writes: readPathList(options.writes),
|
|
368
|
+
escapes: readPathList(options.escapes),
|
|
369
|
+
escapesWatched:
|
|
370
|
+
options.escapes === undefined
|
|
371
|
+
? undefined
|
|
372
|
+
: options.escapesWatched === true,
|
|
373
|
+
envelope: readEnvelope(options.envelope),
|
|
374
|
+
},
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
// The report always renders on stderr. `--json` adds the machine copy on
|
|
378
|
+
// stdout rather than replacing the frame, per the stream contract in
|
|
379
|
+
// `docs/agents/output-shape.md`, so one invocation serves a human and a caller at once.
|
|
380
|
+
reportVerdict(verdict)
|
|
381
|
+
if (options.json === true)
|
|
382
|
+
process.stdout.write(`${JSON.stringify(verdict)}\n`)
|
|
383
|
+
|
|
384
|
+
outro()
|
|
385
|
+
process.exitCode = verdictExitCode(verdict.state, options.strict === true)
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export function register(program: Command): void {
|
|
389
|
+
const sandbox = program
|
|
390
|
+
.command('sandbox')
|
|
391
|
+
.description('Provision and run sandbox scenarios')
|
|
392
|
+
.allowUnknownOption()
|
|
393
|
+
.allowExcessArguments(true)
|
|
394
|
+
.passThroughOptions()
|
|
395
|
+
.action(async (_opts: unknown, cmd: Command) => {
|
|
396
|
+
if (reportAbsentScenarioTree()) return
|
|
397
|
+
|
|
398
|
+
const args = cmd.args
|
|
399
|
+
|
|
400
|
+
if (args.length === 0) {
|
|
401
|
+
const resolved = await interactivePicker()
|
|
402
|
+
await execScript('manage-sandbox.sh', ['--no-header', resolved])
|
|
403
|
+
} else {
|
|
404
|
+
await execScript('manage-sandbox.sh', args)
|
|
405
|
+
}
|
|
406
|
+
})
|
|
407
|
+
|
|
408
|
+
sandbox
|
|
409
|
+
.command('check')
|
|
410
|
+
.description('Check a provisioned sandbox against a scenario expectation')
|
|
411
|
+
.argument('<target>', 'Scenario as <category>:<command>, e.g. claude:docs')
|
|
412
|
+
.argument('[arm]', 'Named scenario arm, e.g. drift')
|
|
413
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
414
|
+
.option('--envelope <file>', 'Run envelope JSON from claude -p')
|
|
415
|
+
.option('--writes <file>', 'Newline-delimited paths the session wrote')
|
|
416
|
+
.option(
|
|
417
|
+
'--escapes <file>',
|
|
418
|
+
'Newline-delimited paths written to a watched toolkit root, for escape scope',
|
|
419
|
+
)
|
|
420
|
+
.option(
|
|
421
|
+
'--escapes-watched',
|
|
422
|
+
'At least one watched root held a target this run, so a zero-escape file is a clean watch rather than one with nothing to watch',
|
|
423
|
+
)
|
|
424
|
+
.option('--json', 'Emit the verdict as JSON on stdout')
|
|
425
|
+
.option('--strict', 'Exit non-zero when the arm declares no expectation')
|
|
426
|
+
.addHelpText(
|
|
427
|
+
'after',
|
|
428
|
+
[
|
|
429
|
+
'',
|
|
430
|
+
'Examples:',
|
|
431
|
+
' canon sandbox check claude:docs drift',
|
|
432
|
+
' canon sandbox check claude:docs drift --envelope run.json --json',
|
|
433
|
+
'',
|
|
434
|
+
'Exit codes: 0 on pass or unchecked, 1 on failure.',
|
|
435
|
+
'With --strict, unchecked exits 1 as well.',
|
|
436
|
+
].join('\n'),
|
|
437
|
+
)
|
|
438
|
+
.action(
|
|
439
|
+
(target: string, arm: string | undefined, options: CheckOptions) => {
|
|
440
|
+
runCheck(target, arm, options)
|
|
441
|
+
},
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
sandbox
|
|
445
|
+
.command('coverage')
|
|
446
|
+
.description('Report which scenarios declare expectations')
|
|
447
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
448
|
+
.option('--json', 'Emit the report as JSON on stdout')
|
|
449
|
+
.option('--strict', 'Exit non-zero while any scenario declares nothing')
|
|
450
|
+
.option('--skills', 'Add a per-skill asserted, should-be, or exempt census')
|
|
451
|
+
.addHelpText(
|
|
452
|
+
'after',
|
|
453
|
+
[
|
|
454
|
+
'',
|
|
455
|
+
'Examples:',
|
|
456
|
+
' canon sandbox coverage',
|
|
457
|
+
' canon sandbox coverage --json',
|
|
458
|
+
' canon sandbox coverage --skills',
|
|
459
|
+
'',
|
|
460
|
+
'Exit codes: 0, unless --strict and a scenario declares nothing, or',
|
|
461
|
+
'--skills and an exemption names no shipped skill or one an arm asserts.',
|
|
462
|
+
'Where the scenario tree does not ship, exits 1 without a report.',
|
|
463
|
+
].join('\n'),
|
|
464
|
+
)
|
|
465
|
+
.action((options: CoverageOptions) => {
|
|
466
|
+
runCoverage(options)
|
|
467
|
+
})
|
|
468
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { resolve } from 'node:path'
|
|
2
|
+
import type { Command } from 'commander'
|
|
3
|
+
import { type ScanRefusal, scanShippedTree } from '@/secrets/scan'
|
|
4
|
+
import { intro, logError, logInfo, logStep, logWarn, outro, plural } from '@/ui'
|
|
5
|
+
|
|
6
|
+
interface ScanCommandOptions {
|
|
7
|
+
readonly json?: boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** What a reader does about each way the corpus fails to build. */
|
|
11
|
+
const REFUSALS: Record<ScanRefusal, string> = {
|
|
12
|
+
'no-manifest':
|
|
13
|
+
'No package.json here, so nothing is published from this tree.',
|
|
14
|
+
'no-publish':
|
|
15
|
+
'The manifest declares private, so this project publishes nothing.',
|
|
16
|
+
// Stated as an unread corpus rather than an absent one. A publish with no
|
|
17
|
+
// files field packs the whole tree, so this is the package that ships the
|
|
18
|
+
// most, and calling it nothing to read is the denial the reasoning in
|
|
19
|
+
// src/secrets/shipped.ts warns against.
|
|
20
|
+
'no-files-field':
|
|
21
|
+
'package.json declares no files field, so a publish would pack the whole tree. This check reads a declared corpus and left that one unread.',
|
|
22
|
+
'no-git': 'git could not list this tree, so the corpus is unknown.',
|
|
23
|
+
'no-shipped-files':
|
|
24
|
+
'The files field matched nothing git lists, so nothing would be scanned.',
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function register(program: Command): void {
|
|
28
|
+
const secrets = program
|
|
29
|
+
.command('secrets')
|
|
30
|
+
.description('Read committed state for credentials that ship to a target')
|
|
31
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
32
|
+
|
|
33
|
+
secrets
|
|
34
|
+
.command('scan')
|
|
35
|
+
.description('Report credential-shaped values in the tree this repo ships')
|
|
36
|
+
.argument(
|
|
37
|
+
'[path]',
|
|
38
|
+
'Repository to scan, defaulting to the current directory',
|
|
39
|
+
)
|
|
40
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
41
|
+
.option('--json', 'Add a machine-readable record on stdout')
|
|
42
|
+
.addHelpText(
|
|
43
|
+
'after',
|
|
44
|
+
[
|
|
45
|
+
'',
|
|
46
|
+
'Scope:',
|
|
47
|
+
" The package's own files field, so the corpus is what npm packs",
|
|
48
|
+
' and what the plugin ships. Nothing outside it is read.',
|
|
49
|
+
'',
|
|
50
|
+
'Exit codes:',
|
|
51
|
+
' 0 the shipped tree carries no credential-shaped value',
|
|
52
|
+
' 1 refused, with the reason on stderr',
|
|
53
|
+
' 2 at least one value was found',
|
|
54
|
+
'',
|
|
55
|
+
'Examples:',
|
|
56
|
+
' canon secrets scan',
|
|
57
|
+
' canon secrets scan --json',
|
|
58
|
+
'',
|
|
59
|
+
].join('\n'),
|
|
60
|
+
)
|
|
61
|
+
.action(async (path: string | undefined, opts: ScanCommandOptions) => {
|
|
62
|
+
process.exitCode = await runScan(path, opts)
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function runScan(
|
|
67
|
+
path: string | undefined,
|
|
68
|
+
opts: ScanCommandOptions,
|
|
69
|
+
): Promise<number> {
|
|
70
|
+
const root = resolve(path ?? process.cwd())
|
|
71
|
+
const emitJson = opts.json ?? false
|
|
72
|
+
|
|
73
|
+
intro('canon secrets scan')
|
|
74
|
+
|
|
75
|
+
const scan = await scanShippedTree(root)
|
|
76
|
+
|
|
77
|
+
if (scan.kind === 'refused') {
|
|
78
|
+
logStep('Refused')
|
|
79
|
+
logWarn(REFUSALS[scan.reason])
|
|
80
|
+
outro()
|
|
81
|
+
|
|
82
|
+
if (emitJson) {
|
|
83
|
+
process.stdout.write(
|
|
84
|
+
`${JSON.stringify({
|
|
85
|
+
root,
|
|
86
|
+
reason: scan.reason,
|
|
87
|
+
message: REFUSALS[scan.reason],
|
|
88
|
+
})}\n`,
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
return 1
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
logStep('Corpus')
|
|
95
|
+
logInfo(
|
|
96
|
+
`${plural(scan.files, 'file')} read, ${scan.skipped} skipped as binary or unreadable`,
|
|
97
|
+
)
|
|
98
|
+
// Stated on every run, including a clean one. The corpus answers what the
|
|
99
|
+
// package publishes, and a reader who sees only the passing count reads the
|
|
100
|
+
// verdict as covering the repository.
|
|
101
|
+
logInfo(
|
|
102
|
+
`${scan.listed - scan.files - scan.skipped} of ${scan.listed} listed files sit outside the published corpus and were not read`,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
logStep('Findings')
|
|
106
|
+
if (scan.findings.length === 0) {
|
|
107
|
+
logInfo('No credential-shaped value in the shipped tree.')
|
|
108
|
+
} else {
|
|
109
|
+
logError(plural(scan.findings.length, 'value'))
|
|
110
|
+
for (const finding of scan.findings) {
|
|
111
|
+
logWarn(
|
|
112
|
+
`${finding.file}:${finding.line}:${finding.column} ${finding.label} ${finding.preview}`,
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
outro()
|
|
118
|
+
|
|
119
|
+
if (emitJson) {
|
|
120
|
+
process.stdout.write(
|
|
121
|
+
`${JSON.stringify({
|
|
122
|
+
root,
|
|
123
|
+
files: scan.files,
|
|
124
|
+
skipped: scan.skipped,
|
|
125
|
+
listed: scan.listed,
|
|
126
|
+
findings: scan.findings,
|
|
127
|
+
})}\n`,
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return scan.findings.length === 0 ? 0 : 2
|
|
132
|
+
}
|