@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,876 @@
|
|
|
1
|
+
import type { SkillsAuditRefusal } from '@/claude/skills-audit'
|
|
2
|
+
import type { RankRefusal } from '@/claude/skills-rank'
|
|
3
|
+
import type { ReachRefusal } from '@/claude/skills-reach'
|
|
4
|
+
import type { ContextAuditRefusal } from '@/context/audit'
|
|
5
|
+
import type { AuditRefusal } from '@/deps/audit'
|
|
6
|
+
import type { RestatedRefusal } from '@/gov/restated'
|
|
7
|
+
import type { LabelAuditRefusal } from '@/labels/audit'
|
|
8
|
+
import type { ValidateRefusal as RecordRefusal } from '@/records/validate'
|
|
9
|
+
import type { ScanRefusal } from '@/secrets/scan'
|
|
10
|
+
import type { ValidateRefusal as BoardRefusal } from '@/tasks/validate'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The audits this repository already owns, and how to read each one's record.
|
|
14
|
+
*
|
|
15
|
+
* Every verb here ships its own `--json` shape with its own keys, and this
|
|
16
|
+
* module reads each shape rather than forcing a common envelope on them. Each
|
|
17
|
+
* record already has consumers, so an envelope would be a breaking change to
|
|
18
|
+
* every one of them bought for tidiness.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/** How far a finding from this audit reaches. */
|
|
22
|
+
export type Corpus =
|
|
23
|
+
/** Committed, so every clone measures the same files and a delta is shared. */
|
|
24
|
+
| 'tracked'
|
|
25
|
+
/** Gitignored session scratch, so the numbers are one machine's alone. */
|
|
26
|
+
| 'per-machine'
|
|
27
|
+
/**
|
|
28
|
+
* Read from an index off this machine, so the count moves when someone
|
|
29
|
+
* publishes rather than when someone edits here.
|
|
30
|
+
*
|
|
31
|
+
* Its own member rather than either of the two above. A baseline would
|
|
32
|
+
* record growth nobody caused, which is what keeps it out of the retained
|
|
33
|
+
* set alongside per-machine scratch, and an index this run could not reach
|
|
34
|
+
* is an ordinary absence rather than the broken checkout a missing tracked
|
|
35
|
+
* tree would be.
|
|
36
|
+
*/
|
|
37
|
+
| 'upstream'
|
|
38
|
+
|
|
39
|
+
export type AuditStatus =
|
|
40
|
+
/** The audit reported and every count it produced is zero. */
|
|
41
|
+
| 'clean'
|
|
42
|
+
/** The audit reported findings it does not gate on. */
|
|
43
|
+
| 'reported'
|
|
44
|
+
/** The audit reported a finding that is a fact, which fails the aggregate. */
|
|
45
|
+
| 'finding'
|
|
46
|
+
/**
|
|
47
|
+
* The per-machine corpus this audit reads is not on this disk, which is the
|
|
48
|
+
* ordinary state of a gitignored folder on a fresh clone and in CI.
|
|
49
|
+
*/
|
|
50
|
+
| 'absent'
|
|
51
|
+
/** The audit did not report, so the aggregate measured less than the set. */
|
|
52
|
+
| 'unmeasured'
|
|
53
|
+
|
|
54
|
+
export interface AuditSpec {
|
|
55
|
+
readonly id: string
|
|
56
|
+
readonly label: string
|
|
57
|
+
/** Arguments after `canon`, always ending in `--json`. */
|
|
58
|
+
readonly argv: readonly string[]
|
|
59
|
+
/**
|
|
60
|
+
* Exit codes this verb sets on a finding that is a fact.
|
|
61
|
+
*
|
|
62
|
+
* Empty for every verb whose findings are judgments. The catalog decides
|
|
63
|
+
* this rather than the exit code, because a reporting verb sets 2 on
|
|
64
|
+
* findings it deliberately does not gate on, so the code alone cannot say
|
|
65
|
+
* whether a finding is a fact.
|
|
66
|
+
*/
|
|
67
|
+
readonly gatingExits: readonly number[]
|
|
68
|
+
readonly corpus: Corpus
|
|
69
|
+
/**
|
|
70
|
+
* Refusal reasons that mean this audit has no corpus here rather than that
|
|
71
|
+
* it broke, overriding what the corpus alone would allow.
|
|
72
|
+
*
|
|
73
|
+
* Present only where the corpus answers wrongly. A tracked corpus normally
|
|
74
|
+
* allows nothing, since a tree that ships to targets and cannot be found is
|
|
75
|
+
* a broken checkout, and the secret scan is the exception: a project that
|
|
76
|
+
* publishes nothing has no shipped tree to read, which is an ordinary state
|
|
77
|
+
* rather than a defect.
|
|
78
|
+
*/
|
|
79
|
+
readonly absentReasons?: readonly string[]
|
|
80
|
+
/**
|
|
81
|
+
* Pulls the counts worth retaining out of this verb's record.
|
|
82
|
+
*
|
|
83
|
+
* Returns `undefined` when the record does not carry the keys it reads,
|
|
84
|
+
* which is a measure that did not run rather than a corpus with nothing in
|
|
85
|
+
* it. Zero there would read as clean, which is the two-states-look-alike
|
|
86
|
+
* defect this repository has already fixed elsewhere.
|
|
87
|
+
*/
|
|
88
|
+
readonly counts: (record: unknown) => Record<string, number> | undefined
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface AuditResult {
|
|
92
|
+
readonly id: string
|
|
93
|
+
readonly label: string
|
|
94
|
+
readonly status: AuditStatus
|
|
95
|
+
/** Retained by the baseline, which is `tracked` alone. */
|
|
96
|
+
readonly tracked: boolean
|
|
97
|
+
/** Why it is or is not retained, which `tracked` alone cannot say. */
|
|
98
|
+
readonly corpus: Corpus
|
|
99
|
+
readonly exitCode: number
|
|
100
|
+
readonly counts?: Record<string, number>
|
|
101
|
+
/** Why the audit did not report, present only on `unmeasured`. */
|
|
102
|
+
readonly reason?: string
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** The exit code every command here sets when it refuses. */
|
|
106
|
+
const EXIT_REFUSED = 1
|
|
107
|
+
|
|
108
|
+
/** The exit code every command here sets when it carries findings. */
|
|
109
|
+
const EXIT_FINDINGS = 2
|
|
110
|
+
|
|
111
|
+
function asObject(value: unknown): Record<string, unknown> | undefined {
|
|
112
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
113
|
+
return undefined
|
|
114
|
+
}
|
|
115
|
+
return value as Record<string, unknown>
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function lengthOf(value: unknown): number | undefined {
|
|
119
|
+
return Array.isArray(value) ? value.length : undefined
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Folds a set of per-key readings into one record, or `undefined` when any of
|
|
124
|
+
* them could not be read. One unreadable key means the record is not the shape
|
|
125
|
+
* the extractor was written against, and reporting the rest would publish a
|
|
126
|
+
* partial count under a name that claims to be whole.
|
|
127
|
+
*/
|
|
128
|
+
function allOf(
|
|
129
|
+
readings: Record<string, number | undefined>,
|
|
130
|
+
): Record<string, number> | undefined {
|
|
131
|
+
const counts: Record<string, number> = {}
|
|
132
|
+
for (const [key, value] of Object.entries(readings)) {
|
|
133
|
+
if (value === undefined) return undefined
|
|
134
|
+
counts[key] = value
|
|
135
|
+
}
|
|
136
|
+
return counts
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Reads the architecture record's three measures, or nothing when the project
|
|
141
|
+
* carries no record.
|
|
142
|
+
*
|
|
143
|
+
* Three states rather than two, matching what the verb publishes. The key
|
|
144
|
+
* absent is a run that never opened the record, which the aggregate never asks
|
|
145
|
+
* for and so reads as a shape that moved. Null is a project entitled to carry
|
|
146
|
+
* no record, whose other context counts still stand, so it contributes no key
|
|
147
|
+
* rather than a zero that would read as a conforming record.
|
|
148
|
+
*/
|
|
149
|
+
function architectureCounts(
|
|
150
|
+
root: Record<string, unknown>,
|
|
151
|
+
): Record<string, number> | undefined | 'absent' {
|
|
152
|
+
if (!('architecture' in root)) return undefined
|
|
153
|
+
if (root.architecture === null) return 'absent'
|
|
154
|
+
|
|
155
|
+
const record = asObject(root.architecture)
|
|
156
|
+
const decisions = record?.decisions
|
|
157
|
+
if (
|
|
158
|
+
record === undefined ||
|
|
159
|
+
!Array.isArray(decisions) ||
|
|
160
|
+
typeof record.lines !== 'number'
|
|
161
|
+
) {
|
|
162
|
+
return undefined
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// A record stating no length rule has no ceiling to be past, and reporting
|
|
166
|
+
// zero there would read as one measured and found conforming.
|
|
167
|
+
const ceiling =
|
|
168
|
+
typeof record.ceiling === 'number' ? record.ceiling : undefined
|
|
169
|
+
|
|
170
|
+
let unverifiable = 0
|
|
171
|
+
let unchecked = 0
|
|
172
|
+
for (const raw of decisions) {
|
|
173
|
+
const entry = asObject(raw)
|
|
174
|
+
const claim = entry?.claim
|
|
175
|
+
const checks = lengthOf(entry?.checks)
|
|
176
|
+
if (typeof claim !== 'string' || checks === undefined) return undefined
|
|
177
|
+
|
|
178
|
+
if (claim === 'neither') unverifiable += 1
|
|
179
|
+
else if (checks === 0) unchecked += 1
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
// A boolean, counted so the aggregate reads it the way it reads every
|
|
184
|
+
// other measure. The verb gates on it separately, and the key is absent
|
|
185
|
+
// rather than zero on a record that declared no ceiling.
|
|
186
|
+
...(ceiling !== undefined && {
|
|
187
|
+
recordOverLength: record.lines > ceiling ? 1 : 0,
|
|
188
|
+
}),
|
|
189
|
+
recordUnverifiable: unverifiable,
|
|
190
|
+
recordUnchecked: unchecked,
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function contextCounts(record: unknown): Record<string, number> | undefined {
|
|
195
|
+
const root = asObject(record)
|
|
196
|
+
if (root === undefined) return undefined
|
|
197
|
+
|
|
198
|
+
const entries = root.entries
|
|
199
|
+
if (!Array.isArray(entries)) return undefined
|
|
200
|
+
|
|
201
|
+
let bareReferences = 0
|
|
202
|
+
for (const entry of entries) {
|
|
203
|
+
const bare = lengthOf(asObject(entry)?.bareReferences)
|
|
204
|
+
if (bare === undefined) return undefined
|
|
205
|
+
bareReferences += bare
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const architecture = architectureCounts(root)
|
|
209
|
+
if (architecture === undefined) return undefined
|
|
210
|
+
|
|
211
|
+
const counts = allOf({
|
|
212
|
+
unresolvedCitations: lengthOf(asObject(root.citations)?.unresolved),
|
|
213
|
+
longEntries: lengthOf(root.length),
|
|
214
|
+
missingSections: lengthOf(root.missingSections),
|
|
215
|
+
indexDrift: lengthOf(root.indexDrift),
|
|
216
|
+
bareReferences,
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
if (counts === undefined) return undefined
|
|
220
|
+
return architecture === 'absent' ? counts : { ...counts, ...architecture }
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function markdownCounts(record: unknown): Record<string, number> | undefined {
|
|
224
|
+
const root = asObject(record)
|
|
225
|
+
if (root === undefined) return undefined
|
|
226
|
+
|
|
227
|
+
const entries = root.entries
|
|
228
|
+
if (!Array.isArray(entries)) return undefined
|
|
229
|
+
|
|
230
|
+
const depth = asObject(root.checkpoints)?.run
|
|
231
|
+
// A depth reading needs the checkpoint it is measured against. Counting zero
|
|
232
|
+
// without it reports a corpus nobody measured for depth as one with no file
|
|
233
|
+
// past the line.
|
|
234
|
+
if (entries.length > 0 && typeof depth !== 'number') return undefined
|
|
235
|
+
|
|
236
|
+
let bans = 0
|
|
237
|
+
let heavyBullets = 0
|
|
238
|
+
let heavyParagraphs = 0
|
|
239
|
+
let filesPastDepth = 0
|
|
240
|
+
let flatParagraphs = 0
|
|
241
|
+
|
|
242
|
+
for (const raw of entries) {
|
|
243
|
+
const entry = asObject(raw)
|
|
244
|
+
if (entry === undefined) return undefined
|
|
245
|
+
|
|
246
|
+
const entryBans = lengthOf(entry.bans)
|
|
247
|
+
const bullets = lengthOf(entry.heavyBullets)
|
|
248
|
+
const paragraphs = lengthOf(entry.heavyParagraphs)
|
|
249
|
+
const run = entry.longestRun
|
|
250
|
+
if (
|
|
251
|
+
entryBans === undefined ||
|
|
252
|
+
bullets === undefined ||
|
|
253
|
+
paragraphs === undefined ||
|
|
254
|
+
typeof run !== 'number'
|
|
255
|
+
) {
|
|
256
|
+
return undefined
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
bans += entryBans
|
|
260
|
+
heavyBullets += bullets
|
|
261
|
+
heavyParagraphs += paragraphs
|
|
262
|
+
if (typeof depth === 'number' && run > depth) filesPastDepth += 1
|
|
263
|
+
|
|
264
|
+
// A file below the measuring floor carries no cadence key at all, which is
|
|
265
|
+
// a file that was not measured rather than one with no flat paragraph.
|
|
266
|
+
const flat = asObject(entry.cadence)?.flat
|
|
267
|
+
if (typeof flat === 'number') flatParagraphs += flat
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return { bans, heavyBullets, heavyParagraphs, filesPastDepth, flatParagraphs }
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/** The finding arrays `canon claude skills audit` publishes, in its own order. */
|
|
274
|
+
const SKILL_FINDINGS = [
|
|
275
|
+
'missingRequirement',
|
|
276
|
+
'nameMismatch',
|
|
277
|
+
'missingDescription',
|
|
278
|
+
'longDescription',
|
|
279
|
+
'readme',
|
|
280
|
+
'folderName',
|
|
281
|
+
'requirementSections',
|
|
282
|
+
] as const
|
|
283
|
+
|
|
284
|
+
function skillCounts(record: unknown): Record<string, number> | undefined {
|
|
285
|
+
const findings = asObject(asObject(record)?.findings)
|
|
286
|
+
if (findings === undefined) return undefined
|
|
287
|
+
|
|
288
|
+
return allOf(
|
|
289
|
+
Object.fromEntries(
|
|
290
|
+
SKILL_FINDINGS.map((key) => [key, lengthOf(findings[key])]),
|
|
291
|
+
),
|
|
292
|
+
)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Reads the unqualified citations alone, leaving the qualified ones out.
|
|
297
|
+
*
|
|
298
|
+
* A qualified citation is a repair that already landed, so folding the two
|
|
299
|
+
* together would report a corpus getting worse every time one is fixed. The
|
|
300
|
+
* key is still read rather than assumed present, since a record carrying
|
|
301
|
+
* neither array is a shape that moved rather than a catalog with nothing in it.
|
|
302
|
+
*/
|
|
303
|
+
function reachCounts(record: unknown): Record<string, number> | undefined {
|
|
304
|
+
const root = asObject(record)
|
|
305
|
+
if (root === undefined || !Array.isArray(root.qualified)) return undefined
|
|
306
|
+
|
|
307
|
+
return allOf({ unqualifiedCitations: lengthOf(root.unqualified) })
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Reads the routing measure's miss and unmeasurable counts, leaving `rank1`
|
|
312
|
+
* and `top3` out. Both move in lockstep with `misses` over a fixed-size
|
|
313
|
+
* corpus, so carrying them would report one movement three times, the same
|
|
314
|
+
* reasoning `restatedCounts` already takes over its own pair of derived
|
|
315
|
+
* totals. `unmeasurable` is retained separately, since it counts a defect in
|
|
316
|
+
* the instrument rather than a collision the catalog itself carries.
|
|
317
|
+
*/
|
|
318
|
+
function rankCounts(record: unknown): Record<string, number> | undefined {
|
|
319
|
+
const root = asObject(record)
|
|
320
|
+
if (root === undefined) return undefined
|
|
321
|
+
|
|
322
|
+
return allOf({
|
|
323
|
+
misses: lengthOf(root.misses),
|
|
324
|
+
unmeasurable: lengthOf(root.unmeasurable),
|
|
325
|
+
})
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function boardCounts(record: unknown): Record<string, number> | undefined {
|
|
329
|
+
const root = asObject(record)
|
|
330
|
+
if (root === undefined) return undefined
|
|
331
|
+
|
|
332
|
+
// The board keeps its untested rows apart from its findings on purpose, so
|
|
333
|
+
// they move no exit code. Folding them together here would undo that.
|
|
334
|
+
return allOf({
|
|
335
|
+
findings: lengthOf(root.findings),
|
|
336
|
+
untested: lengthOf(root.untested),
|
|
337
|
+
})
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Reads the advisory verb's per-severity object rather than a total.
|
|
342
|
+
*
|
|
343
|
+
* A single count folds a critical advisory into a low one, and the response to
|
|
344
|
+
* the two differs. The keys are read off the record rather than listed here,
|
|
345
|
+
* so a severity the index adds is carried instead of silently dropped.
|
|
346
|
+
*/
|
|
347
|
+
function advisoryCounts(record: unknown): Record<string, number> | undefined {
|
|
348
|
+
const severities = asObject(asObject(record)?.severities)
|
|
349
|
+
if (severities === undefined) return undefined
|
|
350
|
+
|
|
351
|
+
const counts: Record<string, number> = {}
|
|
352
|
+
for (const [severity, value] of Object.entries(severities)) {
|
|
353
|
+
if (typeof value !== 'number') return undefined
|
|
354
|
+
counts[`advisories-${severity}`] = value
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return counts
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Reads the uncovered paths alone, which is the half that is a finding.
|
|
362
|
+
*
|
|
363
|
+
* The declined paths are deliberately not counted. They are whichever declined
|
|
364
|
+
* rows this branch happened to touch rather than a measure of the map, so a
|
|
365
|
+
* clean trunk reads zero while the map declares eight, and the number would
|
|
366
|
+
* describe the branch rather than the decision.
|
|
367
|
+
*
|
|
368
|
+
* Retaining it also broke the verdict. `classify` reads a clean run as quiet
|
|
369
|
+
* only when every count is zero, so a branch touching one declined path exited
|
|
370
|
+
* 0 and still reported as carrying findings.
|
|
371
|
+
*/
|
|
372
|
+
function labelCoverageCounts(
|
|
373
|
+
record: unknown,
|
|
374
|
+
): Record<string, number> | undefined {
|
|
375
|
+
const root = asObject(record)
|
|
376
|
+
if (root === undefined) return undefined
|
|
377
|
+
|
|
378
|
+
return allOf({ uncovered: lengthOf(root.uncovered) })
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Reads the two classes that are findings, leaving the declared mirrors out.
|
|
383
|
+
*
|
|
384
|
+
* A mirror is an authoring root and its consumed copy, which duplicate on
|
|
385
|
+
* purpose, so folding them in would report a corpus getting worse every time a
|
|
386
|
+
* seed is kept in step with the file it is authored from. Same reasoning the
|
|
387
|
+
* label audit drops its declined rows on.
|
|
388
|
+
*
|
|
389
|
+
* The reach count is left out for a different reason. It counts how many
|
|
390
|
+
* surfaces an instruction reached rather than whether anything is wrong, and it
|
|
391
|
+
* moves in lockstep with the two retained here, so a baseline carrying it would
|
|
392
|
+
* report one movement twice.
|
|
393
|
+
*/
|
|
394
|
+
function restatedCounts(record: unknown): Record<string, number> | undefined {
|
|
395
|
+
const counts = asObject(asObject(record)?.counts)
|
|
396
|
+
if (counts === undefined) return undefined
|
|
397
|
+
|
|
398
|
+
return allOf({
|
|
399
|
+
contradictions:
|
|
400
|
+
typeof counts.contradictions === 'number'
|
|
401
|
+
? counts.contradictions
|
|
402
|
+
: undefined,
|
|
403
|
+
repetitions:
|
|
404
|
+
typeof counts.repetitions === 'number' ? counts.repetitions : undefined,
|
|
405
|
+
})
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function findingsOnly(record: unknown): Record<string, number> | undefined {
|
|
409
|
+
const root = asObject(record)
|
|
410
|
+
if (root === undefined) return undefined
|
|
411
|
+
|
|
412
|
+
return allOf({ findings: lengthOf(root.findings) })
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function commentCounts(record: unknown): Record<string, number> | undefined {
|
|
416
|
+
const snapshot = asObject(record)?.snapshot
|
|
417
|
+
if (!Array.isArray(snapshot)) return undefined
|
|
418
|
+
|
|
419
|
+
let degradationHits = 0
|
|
420
|
+
for (const language of snapshot) {
|
|
421
|
+
const hits = lengthOf(asObject(language)?.degradationHits)
|
|
422
|
+
if (hits === undefined) return undefined
|
|
423
|
+
degradationHits += hits
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
return { degradationHits }
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Reads the census's three headline totals, leaving the extension breakdown
|
|
431
|
+
* out. The breakdown is what a caller reads for its own sake, and folding
|
|
432
|
+
* every extension into the retained baseline would grow the recorded key set
|
|
433
|
+
* with every language the tree ever picks up.
|
|
434
|
+
*/
|
|
435
|
+
function censusCounts(record: unknown): Record<string, number> | undefined {
|
|
436
|
+
const root = asObject(record)
|
|
437
|
+
if (root === undefined || !Array.isArray(root.byExtension)) return undefined
|
|
438
|
+
|
|
439
|
+
return allOf({
|
|
440
|
+
files: typeof root.files === 'number' ? root.files : undefined,
|
|
441
|
+
skipped: typeof root.skipped === 'number' ? root.skipped : undefined,
|
|
442
|
+
lines: typeof root.lines === 'number' ? root.lines : undefined,
|
|
443
|
+
})
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function testOrderCounts(record: unknown): Record<string, number> | undefined {
|
|
447
|
+
const root = asObject(record)
|
|
448
|
+
if (root === undefined) return undefined
|
|
449
|
+
|
|
450
|
+
// The unclassified count travels with the findings because it is the honest
|
|
451
|
+
// shape of this measure: a refactor and a new behavior cannot be told apart
|
|
452
|
+
// from history, and a findings count alone claims coverage it does not have.
|
|
453
|
+
return allOf({
|
|
454
|
+
findings: lengthOf(root.findings),
|
|
455
|
+
unclassified: lengthOf(root.unclassified),
|
|
456
|
+
})
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/** The record kinds `canon records validate` takes, and the corpus each reads. */
|
|
460
|
+
const RECORD_KINDS: readonly (readonly [string, Corpus])[] = [
|
|
461
|
+
['plans', 'per-machine'],
|
|
462
|
+
['groundwork', 'per-machine'],
|
|
463
|
+
['intake', 'per-machine'],
|
|
464
|
+
['memory', 'per-machine'],
|
|
465
|
+
['standards', 'tracked'],
|
|
466
|
+
['teach', 'per-machine'],
|
|
467
|
+
]
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Every audit the aggregate runs.
|
|
471
|
+
*
|
|
472
|
+
* `context`, `markdown`, and `skills` gate because the merge gate in
|
|
473
|
+
* `src/gate/stages.ts` already fails a push on each. `secrets` is the one entry that gates without
|
|
474
|
+
* a stage behind it, added deliberately rather than as a side effect, since a
|
|
475
|
+
* credential in the published tree is a fact and the split this repository
|
|
476
|
+
* records gates a fact and reports a judgment. Weigh any further addition
|
|
477
|
+
* against that test rather than against the count.
|
|
478
|
+
*
|
|
479
|
+
* Each verb runs once in its fullest form. Running the gating half separately
|
|
480
|
+
* would walk the same tree twice for a number the full record already carries.
|
|
481
|
+
*/
|
|
482
|
+
export const AUDITS: readonly AuditSpec[] = [
|
|
483
|
+
{
|
|
484
|
+
id: 'context',
|
|
485
|
+
label: 'Context folders',
|
|
486
|
+
argv: ['context', 'audit', '--json'],
|
|
487
|
+
gatingExits: [EXIT_FINDINGS],
|
|
488
|
+
corpus: 'tracked',
|
|
489
|
+
// The one reason this verb refuses for that is an absence rather than a
|
|
490
|
+
// break, on the same test the reach check and the skill audit take: no
|
|
491
|
+
// target adopts `.claude/context/`, `.claude/diagrams/`, and
|
|
492
|
+
// `.claude/wireframes/` all at once, so without the allowance every such
|
|
493
|
+
// project reports the verb unmeasured on every run and never changes.
|
|
494
|
+
absentReasons: ['no-folders'] satisfies ContextAuditRefusal[],
|
|
495
|
+
counts: contextCounts,
|
|
496
|
+
},
|
|
497
|
+
{
|
|
498
|
+
id: 'markdown',
|
|
499
|
+
label: 'Markdown corpus',
|
|
500
|
+
argv: ['markdown', 'audit', '--json'],
|
|
501
|
+
// 3 is the empty ban set, which is the corpus walked with nothing looked
|
|
502
|
+
// for. That is a broken check rather than a clean tree, so it fails here
|
|
503
|
+
// the way it already fails the push.
|
|
504
|
+
gatingExits: [EXIT_FINDINGS, 3],
|
|
505
|
+
corpus: 'tracked',
|
|
506
|
+
counts: markdownCounts,
|
|
507
|
+
},
|
|
508
|
+
{
|
|
509
|
+
id: 'skills',
|
|
510
|
+
label: 'Skill corpora',
|
|
511
|
+
argv: ['claude', 'skills', 'audit', '--json'],
|
|
512
|
+
gatingExits: [EXIT_FINDINGS],
|
|
513
|
+
corpus: 'tracked',
|
|
514
|
+
// The one reason this verb refuses for, and it is an absence for the same
|
|
515
|
+
// reason the reach check's is: a project carrying neither `claude/skills/`
|
|
516
|
+
// nor `.claude/skills/` has adopted no skill convention this audit reads.
|
|
517
|
+
absentReasons: ['no-corpus'] satisfies SkillsAuditRefusal[],
|
|
518
|
+
counts: skillCounts,
|
|
519
|
+
},
|
|
520
|
+
{
|
|
521
|
+
id: 'skills-reach',
|
|
522
|
+
label: 'Shipped citation reach',
|
|
523
|
+
argv: ['claude', 'skills', 'reach', '--json'],
|
|
524
|
+
// Reports rather than gates, on the split this file already draws. A body
|
|
525
|
+
// naming a toolkit path is sometimes correct, since the instruction may be
|
|
526
|
+
// meant for a session in this repository, so the verdict is a judgment and
|
|
527
|
+
// a push failing on one teaches a contributor to route around the stage.
|
|
528
|
+
gatingExits: [],
|
|
529
|
+
corpus: 'tracked',
|
|
530
|
+
// The one reason this verb refuses for, and it is an absence rather than a
|
|
531
|
+
// break. A tracked corpus normally allows nothing, since a tree that ships
|
|
532
|
+
// to targets and cannot be found is a broken checkout, and this is the
|
|
533
|
+
// second exception on the same test the secret scan takes: a project
|
|
534
|
+
// carrying neither `claude/skills/` nor `.claude/skills/` has adopted no
|
|
535
|
+
// skill convention, so without the allowance it reports the verb
|
|
536
|
+
// unmeasured on every run and never changes, which is the permanent signal
|
|
537
|
+
// the per-machine allowance exists against.
|
|
538
|
+
absentReasons: ['no-skills'] satisfies ReachRefusal[],
|
|
539
|
+
counts: reachCounts,
|
|
540
|
+
},
|
|
541
|
+
{
|
|
542
|
+
id: 'skills-rank',
|
|
543
|
+
label: 'Skill routing measure',
|
|
544
|
+
argv: ['claude', 'skills', 'rank', '--json'],
|
|
545
|
+
// Reports rather than gates. The corpus is a first run with no baseline to
|
|
546
|
+
// fail a push against, and a lexical ranker bounds a necessary condition
|
|
547
|
+
// rather than reporting real routing behavior, so a push failing on a
|
|
548
|
+
// moved rank would teach a contributor to route around the stage.
|
|
549
|
+
gatingExits: [],
|
|
550
|
+
corpus: 'tracked',
|
|
551
|
+
// The one reason this run refuses for, and it is an absence for the same
|
|
552
|
+
// reason the reach check's is: a project carrying neither `claude/skills/`
|
|
553
|
+
// nor `.claude/skills/` has adopted no skill convention this audit reads.
|
|
554
|
+
// The two case-corpus refusals never reach here, since they need `--cases`
|
|
555
|
+
// and this argv passes none.
|
|
556
|
+
absentReasons: ['no-skills'] satisfies RankRefusal[],
|
|
557
|
+
counts: rankCounts,
|
|
558
|
+
},
|
|
559
|
+
{
|
|
560
|
+
id: 'tasks',
|
|
561
|
+
label: 'Task board',
|
|
562
|
+
argv: ['tasks', 'validate', '--json'],
|
|
563
|
+
gatingExits: [],
|
|
564
|
+
corpus: 'per-machine',
|
|
565
|
+
counts: boardCounts,
|
|
566
|
+
},
|
|
567
|
+
...RECORD_KINDS.map(([kind, corpus]) => ({
|
|
568
|
+
id: `records-${kind}`,
|
|
569
|
+
label: `Records: ${kind}`,
|
|
570
|
+
argv: ['records', 'validate', kind, '--json'],
|
|
571
|
+
gatingExits: [],
|
|
572
|
+
corpus,
|
|
573
|
+
// `standards` is the one tracked kind here, so it takes none of the
|
|
574
|
+
// per-machine default the other five inherit from their corpus. A target
|
|
575
|
+
// reads standards through `canon standards` rather than a copy in its own
|
|
576
|
+
// tree, so carrying no standards folder at all is the
|
|
577
|
+
// ordinary state of every project but this repository.
|
|
578
|
+
...(kind === 'standards' && {
|
|
579
|
+
absentReasons: ['no-folder'] satisfies RecordRefusal[],
|
|
580
|
+
}),
|
|
581
|
+
counts: findingsOnly,
|
|
582
|
+
})),
|
|
583
|
+
{
|
|
584
|
+
id: 'comments',
|
|
585
|
+
label: 'Comment census',
|
|
586
|
+
argv: ['comments', 'scan', '--json'],
|
|
587
|
+
gatingExits: [],
|
|
588
|
+
corpus: 'tracked',
|
|
589
|
+
counts: commentCounts,
|
|
590
|
+
},
|
|
591
|
+
{
|
|
592
|
+
id: 'test-order',
|
|
593
|
+
label: 'Test order',
|
|
594
|
+
argv: ['gov', 'test-order', '--json'],
|
|
595
|
+
gatingExits: [],
|
|
596
|
+
corpus: 'tracked',
|
|
597
|
+
counts: testOrderCounts,
|
|
598
|
+
},
|
|
599
|
+
{
|
|
600
|
+
id: 'secrets',
|
|
601
|
+
label: 'Shipped tree secrets',
|
|
602
|
+
argv: ['secrets', 'scan', '--json'],
|
|
603
|
+
// The fourth gate, and the first added since the note above was written.
|
|
604
|
+
// A credential-shaped value sitting in the tree this repository publishes
|
|
605
|
+
// is a fact rather than a judgment, which is the test that note asks any
|
|
606
|
+
// addition to pass. It is decided here and stated in the context entry
|
|
607
|
+
// rather than arriving as a side effect of registering a measure.
|
|
608
|
+
gatingExits: [EXIT_FINDINGS],
|
|
609
|
+
corpus: 'tracked',
|
|
610
|
+
// The three reasons that mean this tree publishes nothing, which is where
|
|
611
|
+
// most targets installing this CLI sit. Without the allowance the
|
|
612
|
+
// aggregate reports `incomplete` on every run in every such project and
|
|
613
|
+
// never changes, which is the permanent signal the per-machine allowance
|
|
614
|
+
// exists against.
|
|
615
|
+
//
|
|
616
|
+
// Two reasons are deliberately left out, and both are a corpus that exists
|
|
617
|
+
// and went unread. `no-git` is a broken checkout, and `no-files-field` is a
|
|
618
|
+
// publish that would pack the whole tree, so calling either an absence
|
|
619
|
+
// would report a pass over a shipped tree nobody measured.
|
|
620
|
+
absentReasons: [
|
|
621
|
+
'no-manifest',
|
|
622
|
+
'no-publish',
|
|
623
|
+
'no-shipped-files',
|
|
624
|
+
] satisfies ScanRefusal[],
|
|
625
|
+
counts: findingsOnly,
|
|
626
|
+
},
|
|
627
|
+
{
|
|
628
|
+
id: 'labels',
|
|
629
|
+
label: 'Pull request label coverage',
|
|
630
|
+
argv: ['labels', 'audit', '--json'],
|
|
631
|
+
// Reports rather than gates, on the split this file already draws. Whether
|
|
632
|
+
// an uncovered surface deserves a label is a judgment, and a push failing
|
|
633
|
+
// on one would ask a contributor to answer a question only the person who
|
|
634
|
+
// owns the surface can.
|
|
635
|
+
gatingExits: [],
|
|
636
|
+
// The map is committed, so every clone reads the same rows and a delta is
|
|
637
|
+
// shared. The changed set the rows are read against is the branch's, which
|
|
638
|
+
// is what makes a clean trunk report zero rather than nothing.
|
|
639
|
+
corpus: 'tracked',
|
|
640
|
+
// Only `uncovered` is retained, for the reason its extractor states.
|
|
641
|
+
//
|
|
642
|
+
// The one reason that means this project declares no surfaces to cover,
|
|
643
|
+
// which is the recorded decision that a project without a map is labelled
|
|
644
|
+
// silently. A tracked corpus normally allows nothing absent, and this is
|
|
645
|
+
// the second exception beside the secret scan rather than a default.
|
|
646
|
+
//
|
|
647
|
+
// The other three are deliberately left out. A map that will not parse, a
|
|
648
|
+
// map with no usable row, and a range git could not answer are each a
|
|
649
|
+
// corpus that exists and went unread, so calling any of them an absence
|
|
650
|
+
// would report a pass over a branch nobody measured.
|
|
651
|
+
absentReasons: ['no-map'] satisfies LabelAuditRefusal[],
|
|
652
|
+
counts: labelCoverageCounts,
|
|
653
|
+
},
|
|
654
|
+
{
|
|
655
|
+
id: 'restated',
|
|
656
|
+
label: 'Restated instructions',
|
|
657
|
+
argv: ['gov', 'restated', '--json'],
|
|
658
|
+
// Reports rather than gates, on the split this file already draws. Whether
|
|
659
|
+
// a rule stated on two surfaces should be stated on one is a judgment the
|
|
660
|
+
// person owning the surface takes, and most restatements here are correct,
|
|
661
|
+
// so a push failing on one would fail on the ordinary case.
|
|
662
|
+
gatingExits: [],
|
|
663
|
+
corpus: 'tracked',
|
|
664
|
+
// Both reasons the sweep refuses for, and each is an absence rather than a
|
|
665
|
+
// break. A target holds neither the seed nor a shipped skills tree, so
|
|
666
|
+
// without the allowance every project installing this CLI reports the verb
|
|
667
|
+
// unmeasured on every run and never changes, which is the permanent signal
|
|
668
|
+
// the per-machine allowance exists against. Same shape as the reach verb.
|
|
669
|
+
absentReasons: [
|
|
670
|
+
'no-instructions',
|
|
671
|
+
'no-surfaces',
|
|
672
|
+
] satisfies RestatedRefusal[],
|
|
673
|
+
counts: restatedCounts,
|
|
674
|
+
},
|
|
675
|
+
{
|
|
676
|
+
id: 'counts',
|
|
677
|
+
label: 'Self-stated catalog counts',
|
|
678
|
+
argv: ['gov', 'counts', '--json'],
|
|
679
|
+
// Reports rather than gates, on the split this file already draws. The
|
|
680
|
+
// false-positive rate is read off the first real run rather than assumed
|
|
681
|
+
// ahead of it, and gating a measure with an unmeasured rate is what teaches
|
|
682
|
+
// a contributor to route around the stage.
|
|
683
|
+
gatingExits: [],
|
|
684
|
+
corpus: 'tracked',
|
|
685
|
+
counts: findingsOnly,
|
|
686
|
+
},
|
|
687
|
+
{
|
|
688
|
+
id: 'census',
|
|
689
|
+
label: 'Codebase census',
|
|
690
|
+
argv: ['census', '--json'],
|
|
691
|
+
// Reports rather than gates. A file count is a measure, not a judgment, so
|
|
692
|
+
// registering it here exists to retain growth against the baseline rather
|
|
693
|
+
// than to fail a push on a number moving.
|
|
694
|
+
gatingExits: [],
|
|
695
|
+
corpus: 'tracked',
|
|
696
|
+
counts: censusCounts,
|
|
697
|
+
},
|
|
698
|
+
{
|
|
699
|
+
id: 'deps',
|
|
700
|
+
label: 'Dependency advisories',
|
|
701
|
+
argv: ['deps', 'audit', '--json'],
|
|
702
|
+
// Reports rather than gates, on the same split. A published advisory is a
|
|
703
|
+
// fact about the index and a judgment about this tree, since the upgrade
|
|
704
|
+
// may not exist yet, and a push failing on one teaches a contributor to
|
|
705
|
+
// route around the stage while nothing about the dependency has changed.
|
|
706
|
+
gatingExits: [],
|
|
707
|
+
corpus: 'upstream',
|
|
708
|
+
counts: advisoryCounts,
|
|
709
|
+
},
|
|
710
|
+
]
|
|
711
|
+
|
|
712
|
+
export function auditFor(id: string): AuditSpec | undefined {
|
|
713
|
+
return AUDITS.find((audit) => audit.id === id)
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
export function isTracked(spec: AuditSpec): boolean {
|
|
717
|
+
return spec.corpus === 'tracked'
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
export function countsFor(
|
|
721
|
+
spec: AuditSpec,
|
|
722
|
+
record: unknown,
|
|
723
|
+
): Record<string, number> | undefined {
|
|
724
|
+
return spec.counts(record)
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* Reads the reason a refusing verb published, so the aggregate names the cause
|
|
729
|
+
* rather than only the exit. Every command here puts it in `reason`, and the
|
|
730
|
+
* ones that also carry a sentence put that in `message`.
|
|
731
|
+
*/
|
|
732
|
+
function refusalReason(record: unknown, exitCode: number): string {
|
|
733
|
+
const root = asObject(record)
|
|
734
|
+
const reason = typeof root?.reason === 'string' ? root.reason : undefined
|
|
735
|
+
const message = typeof root?.message === 'string' ? root.message : undefined
|
|
736
|
+
|
|
737
|
+
if (reason !== undefined && message !== undefined) {
|
|
738
|
+
return `${reason}: ${message}`
|
|
739
|
+
}
|
|
740
|
+
return reason ?? message ?? `exited ${exitCode} with no reason on stdout`
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* The reasons a verb publishes when the folder it reads is simply not there.
|
|
745
|
+
*
|
|
746
|
+
* Typed against the unions those two verbs export rather than spelled as bare
|
|
747
|
+
* strings, so renaming a reason at its source fails this build. A literal here
|
|
748
|
+
* would go on matching nothing and quietly turn every expected absence back
|
|
749
|
+
* into an unmeasured audit.
|
|
750
|
+
*/
|
|
751
|
+
const ABSENT_REASONS: readonly (RecordRefusal | BoardRefusal)[] = [
|
|
752
|
+
'no-folder',
|
|
753
|
+
'no-board',
|
|
754
|
+
]
|
|
755
|
+
|
|
756
|
+
/**
|
|
757
|
+
* Every reason the advisory verb refuses for, all of which are an absence.
|
|
758
|
+
*
|
|
759
|
+
* An index it could not reach, a project that is not JavaScript, and one whose
|
|
760
|
+
* dependencies were never resolved are three states in which there is nothing
|
|
761
|
+
* to measure rather than something broken. The verb has no fourth reason, so
|
|
762
|
+
* this is its whole union rather than a chosen subset, and typing it that way
|
|
763
|
+
* fails the build if a later reason arrives without this decision being made.
|
|
764
|
+
*
|
|
765
|
+
* Typed against that module's own union for the reason the record reasons are:
|
|
766
|
+
* a literal here would go on matching nothing after a rename and turn every
|
|
767
|
+
* offline run back into an unmeasured audit.
|
|
768
|
+
*/
|
|
769
|
+
const ADVISORY_ABSENT_REASONS: readonly AuditRefusal[] = [
|
|
770
|
+
'no-record',
|
|
771
|
+
'no-lockfile',
|
|
772
|
+
'no-manifest',
|
|
773
|
+
]
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* Whether a refusal is a folder this machine never created rather than a break.
|
|
777
|
+
*
|
|
778
|
+
* Every gitignored record folder is absent on a fresh clone and in CI, so six
|
|
779
|
+
* of the twelve refuse there on every run. Counting those as unmeasured pins
|
|
780
|
+
* the verdict at `incomplete` forever, and a signal that never changes is one
|
|
781
|
+
* nobody reads after the second time they see it.
|
|
782
|
+
*
|
|
783
|
+
* A tracked corpus gets no such allowance. That tree ships to targets, so a
|
|
784
|
+
* checkout that cannot find it is broken, and reading the absence as ordinary
|
|
785
|
+
* would report a pass over a corpus nobody measured.
|
|
786
|
+
*
|
|
787
|
+
* An upstream corpus takes the allowance for a different reason. Its index is
|
|
788
|
+
* off this machine, so an offline run reaches nothing through no fault of the
|
|
789
|
+
* tree, and pinning the verdict at incomplete every time the network is down
|
|
790
|
+
* is the same signal-nobody-reads failure the per-machine case already names.
|
|
791
|
+
*/
|
|
792
|
+
function absentReasonsFor(spec: AuditSpec): readonly string[] {
|
|
793
|
+
if (spec.absentReasons !== undefined) return spec.absentReasons
|
|
794
|
+
if (spec.corpus === 'per-machine') return ABSENT_REASONS
|
|
795
|
+
if (spec.corpus === 'upstream') return ADVISORY_ABSENT_REASONS
|
|
796
|
+
return []
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
function isExpectedAbsence(spec: AuditSpec, record: unknown): boolean {
|
|
800
|
+
const allowed = absentReasonsFor(spec)
|
|
801
|
+
if (allowed.length === 0) return false
|
|
802
|
+
|
|
803
|
+
const reason = asObject(record)?.reason
|
|
804
|
+
return typeof reason === 'string' && allowed.includes(reason)
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
/**
|
|
808
|
+
* Turns one verb's exit code and stdout into a result the aggregate can report.
|
|
809
|
+
*
|
|
810
|
+
* Unparseable output is `unmeasured` rather than clean, for the reason the
|
|
811
|
+
* verify pipeline already states about its own skipped stages: a run that does
|
|
812
|
+
* not report is a broken command, and skipping would report the pass the check
|
|
813
|
+
* exists to withhold.
|
|
814
|
+
*/
|
|
815
|
+
export function classify(
|
|
816
|
+
spec: AuditSpec,
|
|
817
|
+
exitCode: number,
|
|
818
|
+
stdout: string,
|
|
819
|
+
): AuditResult {
|
|
820
|
+
const base = {
|
|
821
|
+
id: spec.id,
|
|
822
|
+
label: spec.label,
|
|
823
|
+
tracked: isTracked(spec),
|
|
824
|
+
corpus: spec.corpus,
|
|
825
|
+
exitCode,
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
let record: unknown
|
|
829
|
+
let parsed = true
|
|
830
|
+
try {
|
|
831
|
+
record = JSON.parse(stdout)
|
|
832
|
+
} catch {
|
|
833
|
+
parsed = false
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
if (exitCode === EXIT_REFUSED) {
|
|
837
|
+
return {
|
|
838
|
+
...base,
|
|
839
|
+
status: isExpectedAbsence(spec, record) ? 'absent' : 'unmeasured',
|
|
840
|
+
reason: parsed
|
|
841
|
+
? refusalReason(record, exitCode)
|
|
842
|
+
: `refused with no record on stdout`,
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
const counts = parsed ? spec.counts(record) : undefined
|
|
847
|
+
|
|
848
|
+
if (spec.gatingExits.includes(exitCode)) {
|
|
849
|
+
return { ...base, status: 'finding', ...(counts && { counts }) }
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
if (exitCode !== 0 && exitCode !== EXIT_FINDINGS) {
|
|
853
|
+
return {
|
|
854
|
+
...base,
|
|
855
|
+
status: 'unmeasured',
|
|
856
|
+
reason: `exited ${exitCode}, which this verb does not document`,
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
if (counts === undefined) {
|
|
861
|
+
return {
|
|
862
|
+
...base,
|
|
863
|
+
status: 'unmeasured',
|
|
864
|
+
reason: parsed
|
|
865
|
+
? 'the record did not carry the keys this audit reads'
|
|
866
|
+
: 'stdout carried no JSON record',
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
if (exitCode === EXIT_FINDINGS) {
|
|
871
|
+
return { ...base, status: 'reported', counts }
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
const quiet = Object.values(counts).every((value) => value === 0)
|
|
875
|
+
return { ...base, status: quiet ? 'clean' : 'reported', counts }
|
|
876
|
+
}
|