@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,629 @@
|
|
|
1
|
+
import { existsSync, readFileSync, statSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Three states rather than two. An arm with no declaration is `unchecked`: it
|
|
6
|
+
* cannot pass, since nothing was asserted, and it does not fail, since failing
|
|
7
|
+
* every undeclared arm would make the harness unusable while expectations roll
|
|
8
|
+
* out. `canon sandbox coverage` is what keeps the count from hiding.
|
|
9
|
+
*/
|
|
10
|
+
export type VerdictState = 'pass' | 'fail' | 'unchecked'
|
|
11
|
+
|
|
12
|
+
export interface ContentAssertion {
|
|
13
|
+
readonly path: string
|
|
14
|
+
readonly pattern: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface Expectation {
|
|
18
|
+
readonly paths: readonly string[]
|
|
19
|
+
readonly absent: readonly string[]
|
|
20
|
+
readonly content: readonly ContentAssertion[]
|
|
21
|
+
readonly writeScope: readonly string[]
|
|
22
|
+
/**
|
|
23
|
+
* Undefined means the arm makes no claim about escapes, which is every arm
|
|
24
|
+
* today. Present, even as `[]`, means the arm asserts a bound: the empty
|
|
25
|
+
* form declares that a correct run produces none, so `stringArray`'s
|
|
26
|
+
* collapse of "absent" and "empty" into one `[]` would erase that claim.
|
|
27
|
+
*/
|
|
28
|
+
readonly escapeScope?: readonly string[]
|
|
29
|
+
readonly reply: readonly string[]
|
|
30
|
+
readonly manual: readonly string[]
|
|
31
|
+
readonly maxTurns?: number
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface AssertionResult {
|
|
35
|
+
readonly ok: boolean
|
|
36
|
+
readonly message: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* `reply` is optional because an absent reply and an empty one mean different
|
|
41
|
+
* things. A run whose envelope was never supplied has nothing to assert against
|
|
42
|
+
* and skips. A run that genuinely returned no text carries the empty string and
|
|
43
|
+
* fails every reply assertion, which is the finding.
|
|
44
|
+
*/
|
|
45
|
+
export interface RunEnvelope {
|
|
46
|
+
readonly isError: boolean
|
|
47
|
+
readonly turns: number
|
|
48
|
+
readonly denials: number
|
|
49
|
+
readonly reply?: string
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface Verdict {
|
|
53
|
+
readonly state: VerdictState
|
|
54
|
+
readonly asserted: number
|
|
55
|
+
readonly failed: number
|
|
56
|
+
readonly unchecked: number
|
|
57
|
+
readonly results: readonly AssertionResult[]
|
|
58
|
+
readonly manual: readonly string[]
|
|
59
|
+
readonly skipped: readonly string[]
|
|
60
|
+
readonly note?: string
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* `writes`, `escapes`, and `envelope` are absent when the caller supplied no
|
|
65
|
+
* data for them, which is not the same as a run that wrote nothing, escaped
|
|
66
|
+
* nowhere, or reported nothing. The assertion kinds that depend on them report
|
|
67
|
+
* as skipped rather than silently dropping out of the count, so the cheap
|
|
68
|
+
* standalone path cannot claim more coverage than it had.
|
|
69
|
+
*/
|
|
70
|
+
export interface CheckInput {
|
|
71
|
+
readonly sandboxDir: string
|
|
72
|
+
readonly writes?: readonly string[]
|
|
73
|
+
readonly escapes?: readonly string[]
|
|
74
|
+
/**
|
|
75
|
+
* Whether any watched escape root held one of the four directories this run.
|
|
76
|
+
* Undefined when the caller supplied no escapes at all, which already skips.
|
|
77
|
+
* False is what separates a watch that ran and found nothing from one with
|
|
78
|
+
* nothing to watch, both of which produce the same empty `escapes` list.
|
|
79
|
+
*/
|
|
80
|
+
readonly escapesWatched?: boolean
|
|
81
|
+
readonly envelope?: RunEnvelope
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
interface KindOutcome {
|
|
85
|
+
readonly results: AssertionResult[]
|
|
86
|
+
readonly skipped: string[]
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const EXIT_CODE: Record<VerdictState, number> = {
|
|
90
|
+
pass: 0,
|
|
91
|
+
fail: 1,
|
|
92
|
+
unchecked: 0,
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* `unchecked` exits zero so an undeclared arm does not break a gate while
|
|
97
|
+
* expectations roll out. Pass `strict` to make it exit one, which is how a
|
|
98
|
+
* caller that has finished arming its scenarios keeps them armed.
|
|
99
|
+
*/
|
|
100
|
+
export function verdictExitCode(state: VerdictState, strict = false): number {
|
|
101
|
+
if (strict && state === 'unchecked') return 1
|
|
102
|
+
|
|
103
|
+
return EXIT_CODE[state]
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface ScenarioTarget {
|
|
107
|
+
readonly category: string
|
|
108
|
+
readonly command: string
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Splits `<category>:<command>`. Returns undefined rather than a partial target,
|
|
113
|
+
* so a typo cannot resolve to a declaration path that happens not to exist and
|
|
114
|
+
* report `unchecked` on what is really a caller error.
|
|
115
|
+
*/
|
|
116
|
+
export function parseTarget(target: string): ScenarioTarget | undefined {
|
|
117
|
+
const [category, command, ...rest] = target.split(':')
|
|
118
|
+
if (rest.length > 0) return undefined
|
|
119
|
+
if (category === undefined || category === '') return undefined
|
|
120
|
+
if (command === undefined || command === '') return undefined
|
|
121
|
+
|
|
122
|
+
return { category, command }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function expectFilePath(
|
|
126
|
+
root: string,
|
|
127
|
+
category: string,
|
|
128
|
+
command: string,
|
|
129
|
+
arm: string,
|
|
130
|
+
): string {
|
|
131
|
+
return join(
|
|
132
|
+
root,
|
|
133
|
+
'scripts',
|
|
134
|
+
'sandbox',
|
|
135
|
+
'fixtures',
|
|
136
|
+
category,
|
|
137
|
+
command,
|
|
138
|
+
arm,
|
|
139
|
+
'expect.toml',
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function stringArray(value: unknown): string[] {
|
|
144
|
+
if (!Array.isArray(value)) return []
|
|
145
|
+
|
|
146
|
+
return value.filter(
|
|
147
|
+
(entry): entry is string => typeof entry === 'string' && entry !== '',
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function contentArray(value: unknown): ContentAssertion[] {
|
|
152
|
+
if (!Array.isArray(value)) return []
|
|
153
|
+
|
|
154
|
+
const assertions: ContentAssertion[] = []
|
|
155
|
+
for (const entry of value) {
|
|
156
|
+
if (typeof entry !== 'object' || entry === null) continue
|
|
157
|
+
|
|
158
|
+
const record = entry as Record<string, unknown>
|
|
159
|
+
|
|
160
|
+
// Dropping a half-written entry rather than throwing is deliberate, and it
|
|
161
|
+
// reads as the opposite of the stray-key check below. An entry missing its
|
|
162
|
+
// path or pattern declares no assertion to lose, and `checkExpectation`
|
|
163
|
+
// fails an arm whose surviving declaration asserts nothing, so the vacuous
|
|
164
|
+
// pass is already closed one level up. A stray key is the reverse: the entry
|
|
165
|
+
// is well-formed and the declaration around it silently lost a key it
|
|
166
|
+
// appears to carry, which nothing downstream can see.
|
|
167
|
+
if (typeof record.path !== 'string' || record.path === '') continue
|
|
168
|
+
if (typeof record.pattern !== 'string' || record.pattern === '') continue
|
|
169
|
+
|
|
170
|
+
// A bare key written below a `[[content]]` header belongs to that table in
|
|
171
|
+
// TOML, not to the document, so a declaration listing `manual` or
|
|
172
|
+
// `max_turns` after its content blocks parses clean and silently asserts
|
|
173
|
+
// neither. The `claude/ui-test` arm shipped that way: a turn ceiling that
|
|
174
|
+
// never ran and five manual entries that never reached the unchecked count,
|
|
175
|
+
// while `canon sandbox coverage` counted the arm as armed. Nothing at the
|
|
176
|
+
// top level can see the difference, so the check belongs here.
|
|
177
|
+
const stray = Object.keys(record).filter(
|
|
178
|
+
(key) => key !== 'path' && key !== 'pattern',
|
|
179
|
+
)
|
|
180
|
+
if (stray.length > 0) {
|
|
181
|
+
throw new Error(
|
|
182
|
+
`content entry for ${record.path} carries ${stray.join(', ')}. Move top-level keys above the first [[content]] block.`,
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
assertions.push({ path: record.path, pattern: record.pattern })
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return assertions
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Throws on malformed TOML. `resolveVerdict` turns that into a failed verdict, so
|
|
194
|
+
* a typo in a declaration reads the same way a pattern that does not compile
|
|
195
|
+
* does, rather than surfacing as a stack trace.
|
|
196
|
+
*/
|
|
197
|
+
export function parseExpectation(source: string): Expectation {
|
|
198
|
+
const parsed = Bun.TOML.parse(source) as Record<string, unknown>
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
paths: stringArray(parsed.paths),
|
|
202
|
+
absent: stringArray(parsed.absent),
|
|
203
|
+
content: contentArray(parsed.content),
|
|
204
|
+
writeScope: stringArray(parsed.write_scope),
|
|
205
|
+
escapeScope:
|
|
206
|
+
parsed.escape_scope === undefined
|
|
207
|
+
? undefined
|
|
208
|
+
: stringArray(parsed.escape_scope),
|
|
209
|
+
reply: stringArray(parsed.reply),
|
|
210
|
+
manual: stringArray(parsed.manual),
|
|
211
|
+
maxTurns:
|
|
212
|
+
typeof parsed.max_turns === 'number' ? parsed.max_turns : undefined,
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* `manual` is deliberately excluded. Counting it would let an arm declare five
|
|
218
|
+
* prose lines, assert nothing, and still read green, which is the vacuous case
|
|
219
|
+
* `scripts/core/install-check.sh` warns about in its own comment.
|
|
220
|
+
*/
|
|
221
|
+
export function countMechanicalAssertions(expectation: Expectation): number {
|
|
222
|
+
return (
|
|
223
|
+
expectation.paths.length +
|
|
224
|
+
expectation.absent.length +
|
|
225
|
+
expectation.content.length +
|
|
226
|
+
expectation.writeScope.length +
|
|
227
|
+
(expectation.escapeScope === undefined ? 0 : 1) +
|
|
228
|
+
expectation.reply.length
|
|
229
|
+
)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* An entry carrying `*` is matched as a glob, so an arm can assert a file whose
|
|
234
|
+
* name a run derives. Reports the matched path rather than the pattern, since a
|
|
235
|
+
* pass on `lessons/0001-*.html` says nothing until the name it found is named.
|
|
236
|
+
*/
|
|
237
|
+
function checkPaths(
|
|
238
|
+
expectation: Expectation,
|
|
239
|
+
sandboxDir: string,
|
|
240
|
+
): AssertionResult[] {
|
|
241
|
+
return expectation.paths.map((path) => {
|
|
242
|
+
const written = writtenUnder(path, sandboxDir)
|
|
243
|
+
|
|
244
|
+
return written
|
|
245
|
+
? { ok: true, message: `exists: ${written}` }
|
|
246
|
+
: { ok: false, message: `missing: ${path}` }
|
|
247
|
+
})
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* The first file an entry matches, or undefined when it matches none. An entry
|
|
252
|
+
* carrying `*` is matched as a glob, which is what lets an arm name a file whose
|
|
253
|
+
* name a run derives rather than fixes. Pinning one spelling of a derived name
|
|
254
|
+
* passes vacuously against every other spelling, which reads as coverage the arm
|
|
255
|
+
* does not have.
|
|
256
|
+
*
|
|
257
|
+
* Returning the match rather than a boolean is what lets a result name the file
|
|
258
|
+
* the run wrote instead of the pattern that found it. A glob matching several
|
|
259
|
+
* files answers with one of them in no fixed order, so an arm asserting content
|
|
260
|
+
* through a glob seeds a folder holding one.
|
|
261
|
+
*/
|
|
262
|
+
function writtenUnder(pattern: string, sandboxDir: string): string | undefined {
|
|
263
|
+
if (!pattern.includes('*')) {
|
|
264
|
+
return existsSync(join(sandboxDir, pattern)) ? pattern : undefined
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
for (const match of new Bun.Glob(pattern).scanSync({
|
|
268
|
+
cwd: sandboxDir,
|
|
269
|
+
dot: true,
|
|
270
|
+
})) {
|
|
271
|
+
return match
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return undefined
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function checkAbsent(
|
|
278
|
+
expectation: Expectation,
|
|
279
|
+
sandboxDir: string,
|
|
280
|
+
): AssertionResult[] {
|
|
281
|
+
return expectation.absent.map((path) => {
|
|
282
|
+
const written = writtenUnder(path, sandboxDir)
|
|
283
|
+
|
|
284
|
+
return written
|
|
285
|
+
? { ok: false, message: `should not exist: ${written}` }
|
|
286
|
+
: { ok: true, message: `absent: ${path}` }
|
|
287
|
+
})
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* A pattern against a missing file reports as a content miss rather than
|
|
292
|
+
* throwing, since an arm may assert content without also listing the path. A
|
|
293
|
+
* pattern that does not compile is a defect in the declaration, so it fails the
|
|
294
|
+
* assertion it belongs to rather than aborting the whole verdict.
|
|
295
|
+
*
|
|
296
|
+
* A path carrying `*` resolves to the file it matched, and falls back to itself
|
|
297
|
+
* when it matched none so the miss is reported against the entry as written.
|
|
298
|
+
*/
|
|
299
|
+
function checkContent(
|
|
300
|
+
expectation: Expectation,
|
|
301
|
+
sandboxDir: string,
|
|
302
|
+
): AssertionResult[] {
|
|
303
|
+
return expectation.content.map(({ path, pattern }) => {
|
|
304
|
+
const matched = writtenUnder(path, sandboxDir) ?? path
|
|
305
|
+
const full = join(sandboxDir, matched)
|
|
306
|
+
const label = `${matched} =~ ${pattern}`
|
|
307
|
+
|
|
308
|
+
let matcher: RegExp
|
|
309
|
+
try {
|
|
310
|
+
matcher = new RegExp(pattern, 'm')
|
|
311
|
+
} catch {
|
|
312
|
+
return { ok: false, message: `invalid pattern for ${path}: ${pattern}` }
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (!existsSync(full) || !statSync(full).isFile()) {
|
|
316
|
+
return { ok: false, message: `no file to match: ${path}` }
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (matcher.test(readFileSync(full, 'utf8'))) {
|
|
320
|
+
return { ok: true, message: `matches: ${label}` }
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return { ok: false, message: `no match: ${label}` }
|
|
324
|
+
})
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* The probe on 2026-07-30 found no permission mode that both allows writes under
|
|
329
|
+
* `.claude/` and scopes them, so the run goes wide and this assertion carries the
|
|
330
|
+
* property the permission layer used to. An arm declaring no scope skips it.
|
|
331
|
+
*/
|
|
332
|
+
function checkWriteScope(
|
|
333
|
+
expectation: Expectation,
|
|
334
|
+
writes: readonly string[] | undefined,
|
|
335
|
+
): KindOutcome {
|
|
336
|
+
if (expectation.writeScope.length === 0) return { results: [], skipped: [] }
|
|
337
|
+
|
|
338
|
+
if (writes === undefined) {
|
|
339
|
+
return {
|
|
340
|
+
results: [],
|
|
341
|
+
skipped: ['write scope: no write data supplied, pass --writes'],
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// A scope produces one result per write, so a run that wrote nothing produces
|
|
346
|
+
// none, and without this the declaration vanishes from the verdict entirely:
|
|
347
|
+
// no result, no skipped entry, and no contribution to the unchecked count that
|
|
348
|
+
// exists to surface exactly this. The `undefined` branch above cannot stand in,
|
|
349
|
+
// since `run.sh` always passes `--writes` and `readPathList` returns `[]` for an
|
|
350
|
+
// empty file. An arm whose output escaped the snapshot reads as a clean run,
|
|
351
|
+
// which is the vacuous pass the harness exists to remove.
|
|
352
|
+
if (writes.length === 0) {
|
|
353
|
+
return {
|
|
354
|
+
results: [],
|
|
355
|
+
skipped: ['write scope: the run wrote nothing, so no path was checked'],
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const globs = expectation.writeScope.map((glob) => new Bun.Glob(glob))
|
|
360
|
+
|
|
361
|
+
return {
|
|
362
|
+
results: writes.map((path) =>
|
|
363
|
+
globs.some((glob) => glob.match(path))
|
|
364
|
+
? { ok: true, message: `in scope: ${path}` }
|
|
365
|
+
: { ok: false, message: `wrote outside declared scope: ${path}` },
|
|
366
|
+
),
|
|
367
|
+
skipped: [],
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* `run.sh` watches two toolkit roots for a write to shared session scratch
|
|
373
|
+
* during a run, and reports every one it finds as an unattributed escape with
|
|
374
|
+
* no arm able to fail on it. An arm whose skill legitimately reaches outside
|
|
375
|
+
* the sandbox tree declares the destinations here, and the harness asserts
|
|
376
|
+
* them instead of trusting the skill to bound itself.
|
|
377
|
+
*
|
|
378
|
+
* Declaring the scope inverts the empty case against `checkWriteScope`. A
|
|
379
|
+
* write-scope declaration exists to bound required output, so a run that wrote
|
|
380
|
+
* nothing skips rather than passing on a fabricated zero. An escape-scope
|
|
381
|
+
* declaration exists to bound a side effect nothing requires, so zero escapes
|
|
382
|
+
* is the outcome a correct run produces and reports as a pass outright,
|
|
383
|
+
* provided a watched root held something to watch. `run.sh`'s `snapshot_root`
|
|
384
|
+
* returns an empty manifest both when a watch ran clean and when none of the
|
|
385
|
+
* four watched directories existed under a root, and the two produce the same
|
|
386
|
+
* empty `escapes` list. `watched` is what tells them apart: a run that had
|
|
387
|
+
* nothing to watch reports unmeasured rather than passing on a diff it never
|
|
388
|
+
* had the target to take.
|
|
389
|
+
*/
|
|
390
|
+
function checkEscapeScope(
|
|
391
|
+
expectation: Expectation,
|
|
392
|
+
escapes: readonly string[] | undefined,
|
|
393
|
+
watched: boolean | undefined,
|
|
394
|
+
): KindOutcome {
|
|
395
|
+
if (expectation.escapeScope === undefined) return { results: [], skipped: [] }
|
|
396
|
+
|
|
397
|
+
if (escapes === undefined) {
|
|
398
|
+
return {
|
|
399
|
+
results: [],
|
|
400
|
+
skipped: ['escape scope: no escape data supplied, pass --escapes'],
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (escapes.length === 0) {
|
|
405
|
+
if (watched === false) {
|
|
406
|
+
return {
|
|
407
|
+
results: [],
|
|
408
|
+
skipped: ['escape scope: no watched root held a target, unmeasured'],
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
return {
|
|
413
|
+
results: [{ ok: true, message: 'no escape during this run' }],
|
|
414
|
+
skipped: [],
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const globs = expectation.escapeScope.map((glob) => new Bun.Glob(glob))
|
|
419
|
+
|
|
420
|
+
return {
|
|
421
|
+
results: escapes.map((path) =>
|
|
422
|
+
globs.some((glob) => glob.match(path))
|
|
423
|
+
? { ok: true, message: `declared escape: ${path}` }
|
|
424
|
+
: { ok: false, message: `unbounded escape: ${path}` },
|
|
425
|
+
),
|
|
426
|
+
skipped: [],
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Plain substrings, matched case-sensitively, against the text the run replied
|
|
432
|
+
* with. A substring rather than a regex because the pattern a reply assertion
|
|
433
|
+
* wants is a load-bearing token, a path or a command, and a regex invites the
|
|
434
|
+
* anchored sentence that goes red on any rewording.
|
|
435
|
+
*
|
|
436
|
+
* Declare only positives. A negative substring passes on every reply that
|
|
437
|
+
* phrases the thing differently, which is the vacuous pass
|
|
438
|
+
* `countMechanicalAssertions` excludes `manual` to prevent. An entry asserting
|
|
439
|
+
* what a run must not have said stays in `manual` with its reason.
|
|
440
|
+
*/
|
|
441
|
+
function checkReply(
|
|
442
|
+
expectation: Expectation,
|
|
443
|
+
envelope: RunEnvelope | undefined,
|
|
444
|
+
): KindOutcome {
|
|
445
|
+
if (expectation.reply.length === 0) return { results: [], skipped: [] }
|
|
446
|
+
|
|
447
|
+
if (envelope?.reply === undefined) {
|
|
448
|
+
return {
|
|
449
|
+
results: [],
|
|
450
|
+
skipped: ['reply: no reply text supplied, pass --envelope'],
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const reply = envelope.reply
|
|
455
|
+
|
|
456
|
+
return {
|
|
457
|
+
results: expectation.reply.map((fragment) =>
|
|
458
|
+
reply.includes(fragment)
|
|
459
|
+
? { ok: true, message: `reply says: ${fragment}` }
|
|
460
|
+
: { ok: false, message: `reply never says: ${fragment}` },
|
|
461
|
+
),
|
|
462
|
+
skipped: [],
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* The envelope never determines a pass. It can only fail a run the expectations
|
|
468
|
+
* would otherwise have passed. Under `bypassPermissions` the denial count is
|
|
469
|
+
* always zero, so the write-scope assertion is what replaced it.
|
|
470
|
+
*/
|
|
471
|
+
function checkEnvelope(
|
|
472
|
+
expectation: Expectation,
|
|
473
|
+
envelope: RunEnvelope | undefined,
|
|
474
|
+
): KindOutcome {
|
|
475
|
+
if (envelope === undefined) {
|
|
476
|
+
const skipped =
|
|
477
|
+
expectation.maxTurns === undefined
|
|
478
|
+
? []
|
|
479
|
+
: ['turn ceiling: no envelope supplied, pass --envelope']
|
|
480
|
+
|
|
481
|
+
return { results: [], skipped }
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const results: AssertionResult[] = []
|
|
485
|
+
|
|
486
|
+
if (envelope.isError) {
|
|
487
|
+
results.push({ ok: false, message: 'envelope: is_error true' })
|
|
488
|
+
}
|
|
489
|
+
if (envelope.denials > 0) {
|
|
490
|
+
results.push({
|
|
491
|
+
ok: false,
|
|
492
|
+
message: `envelope: ${envelope.denials} permission denials`,
|
|
493
|
+
})
|
|
494
|
+
}
|
|
495
|
+
if (
|
|
496
|
+
expectation.maxTurns !== undefined &&
|
|
497
|
+
envelope.turns > expectation.maxTurns
|
|
498
|
+
) {
|
|
499
|
+
results.push({
|
|
500
|
+
ok: false,
|
|
501
|
+
message: `envelope: ${envelope.turns} turns over ceiling of ${expectation.maxTurns}`,
|
|
502
|
+
})
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
return { results, skipped: [] }
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
export function checkExpectation(
|
|
509
|
+
expectation: Expectation,
|
|
510
|
+
input: CheckInput,
|
|
511
|
+
): Verdict {
|
|
512
|
+
const scope = checkWriteScope(expectation, input.writes)
|
|
513
|
+
const escapeScope = checkEscapeScope(
|
|
514
|
+
expectation,
|
|
515
|
+
input.escapes,
|
|
516
|
+
input.escapesWatched,
|
|
517
|
+
)
|
|
518
|
+
const reply = checkReply(expectation, input.envelope)
|
|
519
|
+
const envelope = checkEnvelope(expectation, input.envelope)
|
|
520
|
+
|
|
521
|
+
const results = [
|
|
522
|
+
...checkPaths(expectation, input.sandboxDir),
|
|
523
|
+
...checkAbsent(expectation, input.sandboxDir),
|
|
524
|
+
...checkContent(expectation, input.sandboxDir),
|
|
525
|
+
...reply.results,
|
|
526
|
+
...scope.results,
|
|
527
|
+
...escapeScope.results,
|
|
528
|
+
...envelope.results,
|
|
529
|
+
]
|
|
530
|
+
const skipped = [
|
|
531
|
+
...scope.skipped,
|
|
532
|
+
...escapeScope.skipped,
|
|
533
|
+
...reply.skipped,
|
|
534
|
+
...envelope.skipped,
|
|
535
|
+
]
|
|
536
|
+
|
|
537
|
+
const failed = results.filter((result) => !result.ok).length
|
|
538
|
+
|
|
539
|
+
// A declaration counts what it declares, this counts what ran, and the two
|
|
540
|
+
// diverge on `write_scope`, which produces one result per write rather than one
|
|
541
|
+
// per glob. An arm declaring only a write scope against a run that wrote
|
|
542
|
+
// nothing would otherwise report pass having asserted nothing, which is the
|
|
543
|
+
// vacuous pass the whole feature exists to remove.
|
|
544
|
+
if (results.length === 0) {
|
|
545
|
+
return {
|
|
546
|
+
state: 'fail',
|
|
547
|
+
asserted: 0,
|
|
548
|
+
failed: 1,
|
|
549
|
+
unchecked: expectation.manual.length + skipped.length,
|
|
550
|
+
results: [
|
|
551
|
+
{ ok: false, message: 'no assertion ran against this sandbox' },
|
|
552
|
+
...results,
|
|
553
|
+
],
|
|
554
|
+
manual: expectation.manual,
|
|
555
|
+
skipped,
|
|
556
|
+
note: 'The declaration asserts something, but nothing was checkable here. A pass with zero assertions is not a pass.',
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
return {
|
|
561
|
+
state: failed > 0 ? 'fail' : 'pass',
|
|
562
|
+
asserted: results.length,
|
|
563
|
+
failed,
|
|
564
|
+
unchecked: expectation.manual.length + skipped.length,
|
|
565
|
+
results,
|
|
566
|
+
manual: expectation.manual,
|
|
567
|
+
skipped,
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* A missing declaration and an empty one are different verdicts. Absence is the
|
|
573
|
+
* rollout state for most arms. A declaration that exists and asserts nothing is
|
|
574
|
+
* silent truncation, and it goes red.
|
|
575
|
+
*/
|
|
576
|
+
export function resolveVerdict(expectFile: string, input: CheckInput): Verdict {
|
|
577
|
+
if (!existsSync(expectFile)) {
|
|
578
|
+
return {
|
|
579
|
+
state: 'unchecked',
|
|
580
|
+
asserted: 0,
|
|
581
|
+
failed: 0,
|
|
582
|
+
unchecked: 0,
|
|
583
|
+
results: [],
|
|
584
|
+
manual: [],
|
|
585
|
+
skipped: [],
|
|
586
|
+
note: `No expect.toml at ${expectFile}. Nothing was asserted.`,
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
let expectation: Expectation
|
|
591
|
+
try {
|
|
592
|
+
expectation = parseExpectation(readFileSync(expectFile, 'utf8'))
|
|
593
|
+
} catch (error) {
|
|
594
|
+
const reason = error instanceof Error ? error.message : String(error)
|
|
595
|
+
|
|
596
|
+
return {
|
|
597
|
+
state: 'fail',
|
|
598
|
+
asserted: 0,
|
|
599
|
+
failed: 1,
|
|
600
|
+
unchecked: 0,
|
|
601
|
+
results: [
|
|
602
|
+
{ ok: false, message: `expect.toml does not parse: ${reason}` },
|
|
603
|
+
],
|
|
604
|
+
manual: [],
|
|
605
|
+
skipped: [],
|
|
606
|
+
note: `Fix the declaration at ${expectFile}.`,
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
if (countMechanicalAssertions(expectation) === 0) {
|
|
611
|
+
return {
|
|
612
|
+
state: 'fail',
|
|
613
|
+
asserted: 0,
|
|
614
|
+
failed: 1,
|
|
615
|
+
unchecked: expectation.manual.length,
|
|
616
|
+
results: [
|
|
617
|
+
{
|
|
618
|
+
ok: false,
|
|
619
|
+
message: `expect.toml declares no mechanical assertion: ${expectFile}`,
|
|
620
|
+
},
|
|
621
|
+
],
|
|
622
|
+
manual: expectation.manual,
|
|
623
|
+
skipped: [],
|
|
624
|
+
note: 'An expectation file that asserts nothing passes every run. Declare one or delete the file.',
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
return checkExpectation(expectation, input)
|
|
629
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto'
|
|
2
|
+
import { homedir } from 'node:os'
|
|
3
|
+
import { join } from 'node:path'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Mints a short per-run identifier the first time it is asked for and holds it
|
|
7
|
+
* in `CANON_SANDBOX_RUN_ID` for the rest of this process, so a script that
|
|
8
|
+
* spawns a child inheriting `process.env` — `run.sh` calling `manage-sandbox.sh`
|
|
9
|
+
* and then `canon sandbox check` — resolves the same tree in every one of them.
|
|
10
|
+
* A process that already carries the variable, inherited from such a parent,
|
|
11
|
+
* reuses it rather than minting a new one.
|
|
12
|
+
*
|
|
13
|
+
* Twin of `mint_sandbox_run_id` in `scripts/lib/sandbox-path.sh`.
|
|
14
|
+
*/
|
|
15
|
+
export function mintSandboxRunId(): string {
|
|
16
|
+
const existing = process.env.CANON_SANDBOX_RUN_ID
|
|
17
|
+
if (existing !== undefined && existing !== '') return existing
|
|
18
|
+
|
|
19
|
+
const id = randomBytes(4).toString('hex')
|
|
20
|
+
process.env.CANON_SANDBOX_RUN_ID = id
|
|
21
|
+
return id
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The provisioned tree's path, split out of `src/commands/sandbox.ts` so the
|
|
26
|
+
* per-run default is unit-testable on its own rather than only through the
|
|
27
|
+
* command's registration.
|
|
28
|
+
*
|
|
29
|
+
* Twin of `resolve_sandbox_dir` in `scripts/lib/sandbox-path.sh`. The exec
|
|
30
|
+
* boundary rules out a shared constant, so a change to the default lands on
|
|
31
|
+
* both sides. The fall-through used to be one path per machine, so two
|
|
32
|
+
* sessions each resolving the default at once provisioned over each other
|
|
33
|
+
* with neither told; `mintSandboxRunId` gives the path a per-run component
|
|
34
|
+
* instead, which is what makes two such sessions land on two different trees.
|
|
35
|
+
*/
|
|
36
|
+
export function sandboxTree(): string {
|
|
37
|
+
const override = process.env.CANON_SANDBOX_DIR
|
|
38
|
+
if (override !== undefined && override !== '') return override
|
|
39
|
+
|
|
40
|
+
const state = process.env.XDG_STATE_HOME
|
|
41
|
+
const base =
|
|
42
|
+
state !== undefined && state !== ''
|
|
43
|
+
? state
|
|
44
|
+
: join(homedir(), '.local', 'state')
|
|
45
|
+
|
|
46
|
+
return join(base, 'canon', `sandbox-${mintSandboxRunId()}`)
|
|
47
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { isMarked } from '@/exempt-marker'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The inline token exempting one line from the secret scan.
|
|
5
|
+
*
|
|
6
|
+
* Shaped on the `stub: true` precedent in `src/seed-marker.ts`, which answers
|
|
7
|
+
* a check whose own comment records a false-positive class. The exemption
|
|
8
|
+
* travels with the line it exempts rather than sitting in a path list away
|
|
9
|
+
* from it, so a reader meeting a muted match finds the reason on the spot.
|
|
10
|
+
*
|
|
11
|
+
* The set of files carrying one is empty today. What empties it is the keying
|
|
12
|
+
* rather than this mechanism: `patterns.ts` matches issued values and never
|
|
13
|
+
* the words around them, so nothing in the shipped tree matches on purpose.
|
|
14
|
+
* A path allow-list was declined for the same reason, since the noise it would
|
|
15
|
+
* target is word-keyed and spread past the fixture trees, so it would hide
|
|
16
|
+
* part of the noise and none of the risk.
|
|
17
|
+
*/
|
|
18
|
+
export const SECRET_MARKER = 'canon-allow-secret'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Whether the line at `index` is exempt, reading itself and the line above it.
|
|
22
|
+
*
|
|
23
|
+
* Where a marker may sit and what makes one count are `isMarked`, which the
|
|
24
|
+
* superseded-value sweep reads through the same call. Only the token differs
|
|
25
|
+
* between the two, so the placement rule has one answer rather than two that
|
|
26
|
+
* can drift.
|
|
27
|
+
*/
|
|
28
|
+
export function isExempt(lines: readonly string[], index: number): boolean {
|
|
29
|
+
return isMarked(lines, index, SECRET_MARKER)
|
|
30
|
+
}
|