@event4u/agent-config 14.9.0 → 14.11.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/.augment-plugin/marketplace.json +3 -3
- package/.augment-plugin/plugin.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +384 -0
- package/README.md +1 -1
- package/dist/agent-src/commands/design.md +88 -0
- package/dist/agent-src/commands/project/analyze.md +47 -0
- package/dist/agent-src/commands/review/changes.md +67 -6
- package/dist/agent-src/commands/roadmap/create.md +49 -0
- package/dist/agent-src/commands/roadmap/next.md +27 -11
- package/dist/agent-src/commands/roadmap/process-full.md +14 -0
- package/dist/agent-src/commands/video/from-script.md +60 -3
- package/dist/agent-src/commands/video/stitch.md +14 -0
- package/dist/agent-src/contexts/communication/rules-auto/guidelines-mechanics.md +1 -0
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +154 -2
- package/dist/agent-src/rules/active-remediation.md +3 -3
- package/dist/agent-src/rules/code-provenance.md +2 -2
- package/dist/agent-src/rules/context-hygiene.md +13 -33
- package/dist/agent-src/rules/council-availability.md +2 -2
- package/dist/agent-src/rules/decision-revisit-gate.md +3 -1
- package/dist/agent-src/rules/design-fidelity.md +18 -0
- package/dist/agent-src/rules/design-review-after-ui-write.md +2 -2
- package/dist/agent-src/rules/evaluator-independence.md +56 -44
- package/dist/agent-src/rules/fix-what-you-see.md +9 -1
- package/dist/agent-src/rules/framework-neutrality-in-generic-skills.md +1 -1
- package/dist/agent-src/rules/minimal-safe-diff.md +14 -20
- package/dist/agent-src/rules/missing-skill-recovery.md +13 -1
- package/dist/agent-src/rules/playbook-precedence.md +105 -0
- package/dist/agent-src/rules/recurring-criticism.md +3 -1
- package/dist/agent-src/rules/roadmap-progress-sync.md +14 -13
- package/dist/agent-src/rules/scale-discipline.md +24 -0
- package/dist/agent-src/rules/security-sensitive-stop.md +2 -2
- package/dist/agent-src/rules/settings-ask-protocol.md +2 -2
- package/dist/agent-src/rules/ui-audit-gate.md +27 -15
- package/dist/agent-src/rules/untrusted-input-defense.md +2 -2
- package/dist/agent-src/skills/accessibility-auditor/SKILL.md +19 -0
- package/dist/agent-src/skills/adr-create/SKILL.md +7 -0
- package/dist/agent-src/skills/agents-md-thin-root/SKILL.md +33 -0
- package/dist/agent-src/skills/ai-council/references/advanced-modes.md +17 -3
- package/dist/agent-src/skills/ai-council/references/output-and-synthesis.md +31 -0
- package/dist/agent-src/skills/ai-council/references/procedure.md +19 -0
- package/dist/agent-src/skills/alerting-doctrine/SKILL.md +225 -0
- package/dist/agent-src/skills/alerting-doctrine/evals/triggers.json +17 -0
- package/dist/agent-src/skills/async-python-patterns/SKILL.md +1 -1
- package/dist/agent-src/skills/authz-review/SKILL.md +0 -1
- package/dist/agent-src/skills/blast-radius-analyzer/SKILL.md +32 -0
- package/dist/agent-src/skills/brand/SKILL.md +3 -0
- package/dist/agent-src/skills/brand-asset-generation/SKILL.md +3 -0
- package/dist/agent-src/skills/brand-audit/SKILL.md +3 -0
- package/dist/agent-src/skills/brand-identity/SKILL.md +3 -0
- package/dist/agent-src/skills/brand-strategy/SKILL.md +3 -0
- package/dist/agent-src/skills/brand-to-tokens/SKILL.md +3 -0
- package/dist/agent-src/skills/check-refs/SKILL.md +3 -0
- package/dist/agent-src/skills/code-refactoring/SKILL.md +1 -1
- package/dist/agent-src/skills/code-review/SKILL.md +11 -0
- package/dist/agent-src/skills/command-routing/SKILL.md +6 -0
- package/dist/agent-src/skills/condense-memory/SKILL.md +4 -1
- package/dist/agent-src/skills/context-authoring/SKILL.md +1 -1
- package/dist/agent-src/skills/conventional-commits-writing/SKILL.md +4 -1
- package/dist/agent-src/skills/copilot-agents-optimization/SKILL.md +10 -0
- package/dist/agent-src/skills/corpus-grounding/SKILL.md +3 -0
- package/dist/agent-src/skills/design-intelligence/SKILL.md +3 -0
- package/dist/agent-src/skills/design-intelligence/data/landing.csv +36 -35
- package/dist/agent-src/skills/design-intelligence/data/stacks/threejs.csv +1 -0
- package/dist/agent-src/skills/design-review/SKILL.md +80 -2
- package/dist/agent-src/skills/design-review/references/verification-automation.md +41 -0
- package/dist/agent-src/skills/design-system-capture/SKILL.md +35 -40
- package/dist/agent-src/skills/design-system-capture/references/import-procedure.md +50 -0
- package/dist/agent-src/skills/design-tokens/SKILL.md +3 -0
- package/dist/agent-src/skills/design-variations/SKILL.md +3 -0
- package/dist/agent-src/skills/developer-like-execution/SKILL.md +3 -0
- package/dist/agent-src/skills/discovery-interview/SKILL.md +1 -1
- package/dist/agent-src/skills/doc-coauthoring/SKILL.md +1 -1
- package/dist/agent-src/skills/docx-authoring/SKILL.md +1 -1
- package/dist/agent-src/skills/editorial-calendar/SKILL.md +1 -1
- package/dist/agent-src/skills/estimate-ticket/SKILL.md +3 -0
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +20 -3
- package/dist/agent-src/skills/fe-design/SKILL.md +74 -41
- package/dist/agent-src/skills/fe-design/evals/triggers.json +65 -53
- package/dist/agent-src/skills/fe-design/references/craft-floor.md +52 -0
- package/dist/agent-src/skills/fe-design/references/source-led-port.md +36 -0
- package/dist/agent-src/skills/file-editor/SKILL.md +5 -0
- package/dist/agent-src/skills/finishing-a-development-branch/SKILL.md +2 -2
- package/dist/agent-src/skills/git-workflow/SKILL.md +3 -0
- package/dist/agent-src/skills/github-ci/SKILL.md +1 -1
- package/dist/agent-src/skills/html-deck/SKILL.md +3 -0
- package/dist/agent-src/skills/iconography/SKILL.md +3 -0
- package/dist/agent-src/skills/image-editing/SKILL.md +3 -0
- package/dist/agent-src/skills/image-generation/SKILL.md +3 -0
- package/dist/agent-src/skills/image-provider-routing/SKILL.md +3 -0
- package/dist/agent-src/skills/incident-commander/SKILL.md +10 -0
- package/dist/agent-src/skills/js-library-packaging/SKILL.md +173 -0
- package/dist/agent-src/skills/js-library-packaging/evals/triggers.json +17 -0
- package/dist/agent-src/skills/js-library-packaging/scripts/check_package_surface.ts +270 -0
- package/dist/agent-src/skills/judge-artifact-completeness/SKILL.md +1 -1
- package/dist/agent-src/skills/judge-injection-defense/SKILL.md +1 -1
- package/dist/agent-src/skills/laravel-migration/SKILL.md +3 -0
- package/dist/agent-src/skills/laravel-validation/SKILL.md +1 -1
- package/dist/agent-src/skills/learning-to-rule-or-skill/SKILL.md +3 -0
- package/dist/agent-src/skills/legal-intake-triage/SKILL.md +1 -1
- package/dist/agent-src/skills/legal-practice-profile/SKILL.md +1 -1
- package/dist/agent-src/skills/lint-skills/SKILL.md +3 -0
- package/dist/agent-src/skills/llm-provider-knowledge/SKILL.md +3 -0
- package/dist/agent-src/skills/logging-monitoring/SKILL.md +234 -51
- package/dist/agent-src/skills/logo-generation/SKILL.md +3 -0
- package/dist/agent-src/skills/md-language-check/SKILL.md +3 -0
- package/dist/agent-src/skills/messaging-architecture/SKILL.md +1 -1
- package/dist/agent-src/skills/migration-architect/SKILL.md +1 -1
- package/dist/agent-src/skills/module-detect-on-the-fly/SKILL.md +1 -1
- package/dist/agent-src/skills/module-management/SKILL.md +1 -1
- package/dist/agent-src/skills/monorepo-workspace/SKILL.md +149 -0
- package/dist/agent-src/skills/monorepo-workspace/evals/triggers.json +17 -0
- package/dist/agent-src/skills/motion-choreographer/SKILL.md +7 -1
- package/dist/agent-src/skills/multi-tenancy/SKILL.md +4 -0
- package/dist/agent-src/skills/onboarding-program/SKILL.md +1 -1
- package/dist/agent-src/skills/operational-readiness/SKILL.md +184 -0
- package/dist/agent-src/skills/operational-readiness/evals/triggers.json +17 -0
- package/dist/agent-src/skills/override-management/SKILL.md +3 -0
- package/dist/agent-src/skills/perf-feedback-craft/SKILL.md +1 -1
- package/dist/agent-src/skills/persona-improvement/SKILL.md +3 -0
- package/dist/agent-src/skills/pest-testing/SKILL.md +3 -0
- package/dist/agent-src/skills/playbook-authoring/SKILL.md +162 -0
- package/dist/agent-src/skills/playwright-testing/SKILL.md +3 -0
- package/dist/agent-src/skills/privacy-review/SKILL.md +3 -0
- package/dist/agent-src/skills/prompt-engineering-image/SKILL.md +3 -0
- package/dist/agent-src/skills/prompt-optimizer/SKILL.md +1 -1
- package/dist/agent-src/skills/quality-tools/SKILL.md +5 -0
- package/dist/agent-src/skills/react-shadcn-ui/SKILL.md +52 -21
- package/dist/agent-src/skills/readme-reviewer/SKILL.md +3 -0
- package/dist/agent-src/skills/readme-writing/SKILL.md +3 -0
- package/dist/agent-src/skills/readme-writing-package/SKILL.md +3 -0
- package/dist/agent-src/skills/receiving-code-review/SKILL.md +3 -3
- package/dist/agent-src/skills/refine-prompt/SKILL.md +3 -0
- package/dist/agent-src/skills/refine-ticket/SKILL.md +3 -0
- package/dist/agent-src/skills/requesting-code-review/SKILL.md +2 -2
- package/dist/agent-src/skills/review-routing/SKILL.md +2 -2
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +23 -41
- package/dist/agent-src/skills/roadmap-writing/references/follow-up-roadmap-shape.md +47 -0
- package/dist/agent-src/skills/rtk-output-filtering/SKILL.md +22 -6
- package/dist/agent-src/skills/runway-cognition/SKILL.md +3 -0
- package/dist/agent-src/skills/scene-expander/SKILL.md +1 -1
- package/dist/agent-src/skills/scene-expander/scene-blueprint.schema.yaml +26 -0
- package/dist/agent-src/skills/secrets-management/SKILL.md +5 -1
- package/dist/agent-src/skills/server-hardening/SKILL.md +159 -0
- package/dist/agent-src/skills/server-hardening/evals/triggers.json +17 -0
- package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +3 -0
- package/dist/agent-src/skills/skill-management/SKILL.md +3 -0
- package/dist/agent-src/skills/skill-reviewer/SKILL.md +3 -0
- package/dist/agent-src/skills/skill-writing/SKILL.md +6 -0
- package/dist/agent-src/skills/spreadsheet-authoring/SKILL.md +3 -0
- package/dist/agent-src/skills/storybook-workshop/SKILL.md +194 -0
- package/dist/agent-src/skills/storybook-workshop/evals/triggers.json +17 -0
- package/dist/agent-src/skills/storybook-workshop/scripts/story_contrast_floor.ts +134 -0
- package/dist/agent-src/skills/subagent-orchestration/SKILL.md +1 -1
- package/dist/agent-src/skills/subagent-orchestration/prompts/README.md +25 -0
- package/dist/agent-src/skills/supply-chain-intake/SKILL.md +12 -0
- package/dist/agent-src/skills/systematic-debugging/SKILL.md +1 -1
- package/dist/agent-src/skills/terraform/SKILL.md +3 -0
- package/dist/agent-src/skills/test-driven-development/SKILL.md +1 -1
- package/dist/agent-src/skills/threat-modeling/SKILL.md +0 -3
- package/dist/agent-src/skills/token-optimizer/SKILL.md +3 -0
- package/dist/agent-src/skills/typography-system/SKILL.md +3 -0
- package/dist/agent-src/skills/ui-component-architect/SKILL.md +9 -1
- package/dist/agent-src/skills/upstream-contribute/SKILL.md +3 -0
- package/dist/agent-src/skills/using-git-worktrees/SKILL.md +1 -1
- package/dist/agent-src/skills/validate-feature-fit/SKILL.md +3 -0
- package/dist/agent-src/skills/verify-completion-evidence/SKILL.md +2 -2
- package/dist/agent-src/skills/wireframe/SKILL.md +3 -0
- package/dist/agent-src/skills/wireframe/references/story-beat-ledger.schema.json +115 -0
- package/dist/agent-src/skills/workspace-link/SKILL.md +131 -0
- package/dist/agent-src/skills/workspace-link/evals/triggers.json +17 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/playbook.md +75 -0
- package/dist/agent-src/templates/roadmaps.md +37 -0
- package/dist/agent-src/templates/scripts/check_playbook_invokes.ts +231 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +24 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/polish.ts +2 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/review.ts +216 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +159 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/stack_bundles.ts +73 -4
- package/dist/agent-src/templates/scripts/work_engine/stack/detect.ts +325 -40
- package/dist/cli/commands/uiAudit.js +221 -0
- package/dist/cli/commands/uiAudit.js.map +1 -0
- package/dist/cli/commands/uiRender.js +206 -0
- package/dist/cli/commands/uiRender.js.map +1 -0
- package/dist/cli/main.js +22 -1
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/mcp/content.js +64 -1
- package/dist/cli/mcp/content.js.map +1 -1
- package/dist/cli/mcp/dispatch.js +175 -10
- package/dist/cli/mcp/dispatch.js.map +1 -1
- package/dist/cli/registry.js +3 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/{chunk-H4VIVD4C.js → chunk-236PNQZK.js} +413 -391
- package/dist/cli-delegate/{chunk-G23AT6GD.js → chunk-DMSZHXZS.js} +57 -1
- package/dist/cli-delegate/{chunk-77GR3VD4.js → chunk-FF7WAFMR.js} +58 -3
- package/dist/cli-delegate/{chunk-PLSG6UL5.js → chunk-K4FFODTS.js} +341 -204
- package/dist/cli-delegate/{chunk-YM4GZDJG.js → chunk-L43NCYPB.js} +5 -0
- package/dist/cli-delegate/{chunk-B2ERU2VA.js → chunk-WOKWX5PL.js} +1 -1
- package/dist/cli-delegate/cmd_brand_status.js +8 -6
- package/dist/cli-delegate/cmd_conformance.js +3 -3
- package/dist/cli-delegate/cmd_doctor.js +3 -3
- package/dist/cli-delegate/cmd_export.js +4 -4
- package/dist/cli-delegate/cmd_handoff.js +1 -1
- package/dist/cli-delegate/cmd_preflight.js +2 -2
- package/dist/cli-delegate/cmd_prune.js +4 -4
- package/dist/cli-delegate/cmd_session_recycle.js +70 -9
- package/dist/cli-delegate/cmd_settings_migrate.js +4 -4
- package/dist/cli-delegate/cmd_settings_set.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +4 -4
- package/dist/cli-delegate/cmd_uninstall.js +4 -4
- package/dist/cli-delegate/cmd_validate.js +4 -4
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +380 -145
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +4 -4
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +18 -8
- package/dist/discovery/trust-report.md +3 -3
- package/dist/discovery/workspaces.json +16 -6
- package/dist/hooks/dispatch.js +2337 -1436
- package/dist/install/install.mjs +795 -708
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +1685 -1250
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/lean_projection_mode.js +40 -0
- package/dist/scripts/_lib/lean_projection_mode.js.map +1 -0
- package/dist/scripts/_lib/scoped_projection.js +84 -0
- package/dist/scripts/_lib/scoped_projection.js.map +1 -1
- package/dist/scripts/_lib/ui_surface.js +89 -0
- package/dist/scripts/_lib/ui_surface.js.map +1 -0
- package/dist/scripts/condense.js +6 -10
- package/dist/scripts/condense.js.map +1 -1
- package/dist/scripts/project_thin_rules.js +40 -2
- package/dist/scripts/project_thin_rules.js.map +1 -1
- package/dist/server/schemas/settings.js +3 -0
- package/dist/server/schemas/settings.js.map +1 -1
- package/dist/shared/skillRanking.js +159 -0
- package/dist/shared/skillRanking.js.map +1 -0
- package/docs/contracts/provider-lifecycle.md +47 -18
- package/docs/contracts/settings-classes.md +14 -4
- package/docs/guidelines/agent-infra/context-hygiene-mechanics.md +49 -0
- package/docs/guidelines/agent-infra/evaluator-independence-mechanics.md +92 -0
- package/docs/guidelines/agent-infra/minimal-safe-diff-mechanics.md +45 -1
- package/docs/guidelines/agent-infra/roadmap-deferred-resolution-provenance.md +54 -0
- package/docs/guidelines/design-antipatterns.md +4 -2
- package/docs/guidelines/design-asset-discipline.md +38 -0
- package/docs/guidelines/design-fidelity-mechanics.md +45 -29
- package/docs/guidelines/design-handover-extraction.md +35 -5
- package/docs/guidelines/design-modes.md +41 -1
- package/docs/guidelines/monorepo-antipatterns.md +141 -0
- package/llms.txt +50 -39
- package/package.json +1 -1
- package/src/agent-src/templates/scripts/check_playbook_invokes.ts +231 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +24 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/polish.ts +2 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/review.ts +216 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +159 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/stack_bundles.ts +73 -4
- package/src/agent-src/templates/scripts/work_engine/stack/detect.ts +325 -40
- package/src/config/agent-settings.template.yml +28 -0
- package/src/config/assurance-capability-registry.json +223 -0
- package/src/config/assurance-threshold-budget.json +65 -0
- package/src/config/ci-local-parity.yml +14 -0
- package/src/config/discovery/unassigned-artefacts.yml +2 -0
- package/src/config/dispatch-economy-metrics.json +4 -2
- package/src/config/evaluator-budgets.json +14 -14
- package/src/config/gate-coverage.yml +227 -8
- package/src/config/gate-violation-baselines.json +76 -72
- package/src/config/hook-latency-budget.json +1 -0
- package/src/config/hook-token-budget.json +137 -125
- package/src/config/pack-size-budget.json +4 -2
- package/src/config/preamble-payload-budget.json +71 -67
- package/src/config/quorum-attendance-budget.json +12 -4
- package/src/config/rule-activation-census.json +7 -3
- package/src/config/rule-enforcement-baseline.json +0 -1
- package/src/config/rule-stub-ceilings.json +112 -91
- package/src/scripts/_cli/cmd_doctor.ts +15 -16
- package/src/scripts/_cli/doctor_install_mode.ts +49 -0
- package/src/scripts/_cli/doctor_overrides.ts +154 -0
- package/src/scripts/_dispatch.bash +17 -0
- package/src/scripts/_lib/as_of.ts +412 -0
- package/src/scripts/_lib/exec_evidence.ts +7 -0
- package/src/scripts/_lib/hook_settings.ts +33 -0
- package/src/scripts/_lib/host_listing_model.ts +192 -0
- package/src/scripts/_lib/lean_projection_mode.ts +44 -0
- package/src/scripts/_lib/mcp_bridge.ts +199 -0
- package/src/scripts/_lib/review_independence.ts +62 -10
- package/src/scripts/_lib/rule_injection.ts +239 -0
- package/src/scripts/_lib/run_checkpoint.ts +54 -1
- package/src/scripts/_lib/scoped_projection.ts +96 -0
- package/src/scripts/_lib/session_register.ts +44 -2
- package/src/scripts/_lib/skill_catalogue.ts +38 -2
- package/src/scripts/_lib/transcript_reads.ts +207 -0
- package/src/scripts/_lib/ui_authority.test.ts +251 -0
- package/src/scripts/_lib/ui_authority.ts +343 -0
- package/src/scripts/ai-video/adapters/fal.sh +8 -3
- package/src/scripts/ai-video/adapters/gemini-veo.sh +10 -0
- package/src/scripts/ai-video/adapters/higgsfield.sh +10 -2
- package/src/scripts/ai-video/adapters/kling.sh +10 -0
- package/src/scripts/ai-video/adapters/sora.sh +10 -0
- package/src/scripts/ai-video/lib/calibrate-cost.sh +145 -0
- package/src/scripts/ai-video/lib/model-capabilities/README.md +53 -7
- package/src/scripts/ai-video/lib/model-capabilities/comfyui.json +15 -3
- package/src/scripts/ai-video/lib/model-capabilities/fal.json +36 -6
- package/src/scripts/ai-video/lib/model-capabilities/gemini-veo.json +23 -0
- package/src/scripts/ai-video/lib/model-capabilities/higgsfield.json +23 -0
- package/src/scripts/ai-video/lib/model-capabilities/kling.json +23 -0
- package/src/scripts/ai-video/lib/model-capabilities/musetalk.json +8 -2
- package/src/scripts/ai-video/lib/model-capabilities/replicate.json +36 -6
- package/src/scripts/ai-video/lib/model-capabilities/sora.json +45 -0
- package/src/scripts/ai-video/lib/model-capabilities/syncso.json +8 -2
- package/src/scripts/ai-video/lib/parse-blueprint.sh +52 -5
- package/src/scripts/ai-video/lib/resume-scan.sh +92 -3
- package/src/scripts/ai-video/lib/seam-plan.sh +212 -0
- package/src/scripts/ai-video/smoke-trace.sh +450 -2
- package/src/scripts/ai-video/stitch.sh +191 -10
- package/src/scripts/ai-video/test-pipeline.sh +84 -6
- package/src/scripts/ai_council/cli_agency_bounds.ts +139 -0
- package/src/scripts/ai_council/cli_least_agency_canary.ts +347 -0
- package/src/scripts/ai_council/clients.ts +3 -2
- package/src/scripts/ai_council/consensus.ts +63 -6
- package/src/scripts/ai_council/events_log.ts +46 -1
- package/src/scripts/ai_council/orchestrator.ts +103 -108
- package/src/scripts/ai_council/orchestrator_results.ts +166 -0
- package/src/scripts/ai_council/prompts.ts +12 -5
- package/src/scripts/ai_council/quorum.ts +82 -1
- package/src/scripts/ai_council/quorum_wiring.ts +82 -5
- package/src/scripts/ai_council/seating.ts +211 -0
- package/src/scripts/ai_council/stance_tally.ts +22 -0
- package/src/scripts/bench_hook_latency.ts +62 -0
- package/src/scripts/build_proof.ts +12 -1
- package/src/scripts/capture_skill_catalogue.ts +21 -0
- package/src/scripts/check_always_budget.ts +2 -1
- package/src/scripts/check_augmentignore.ts +2 -1
- package/src/scripts/check_beta_review_markers.ts +2 -1
- package/src/scripts/check_composite_arming.ts +364 -0
- package/src/scripts/check_corpus_staleness.ts +2 -1
- package/src/scripts/check_council_pin_staleness.ts +3 -1
- package/src/scripts/check_enforcement_coverage.ts +116 -3
- package/src/scripts/check_enforcement_denominator.ts +365 -0
- package/src/scripts/check_gate_coverage.ts +2 -1
- package/src/scripts/check_hook_bundle_freshness.ts +13 -1
- package/src/scripts/check_knowledge_cards.ts +2 -1
- package/src/scripts/check_knowledge_pages.ts +3 -2
- package/src/scripts/check_memory.ts +2 -1
- package/src/scripts/check_no_automerge_key.ts +287 -0
- package/src/scripts/check_pack_conformance_fixture.ts +565 -0
- package/src/scripts/check_pack_passport_reconciliation.ts +274 -0
- package/src/scripts/check_proposal.ts +2 -1
- package/src/scripts/check_reach_staleness.ts +2 -1
- package/src/scripts/check_release_adjacent_health.ts +2 -1
- package/src/scripts/check_release_pr_shape.ts +5 -0
- package/src/scripts/check_review_schema.ts +34 -1
- package/src/scripts/check_roadmap_trackable.ts +48 -3
- package/src/scripts/check_source_size_budget.ts +2 -1
- package/src/scripts/check_standing_payload_delta.ts +505 -0
- package/src/scripts/check_trigger_evals.ts +2 -1
- package/src/scripts/classify_change_risk.ts +281 -0
- package/src/scripts/compute_skill_tiers.ts +186 -0
- package/src/scripts/condense.ts +8 -13
- package/src/scripts/council_attendance_metrics.ts +183 -0
- package/src/scripts/council_cli.ts +10 -8
- package/src/scripts/council_parse_rate.ts +116 -0
- package/src/scripts/derive_playbooks.ts +406 -0
- package/src/scripts/design_slop_rules.test.ts +7 -0
- package/src/scripts/design_slop_rules.ts +56 -2
- package/src/scripts/dispatch_r2_reviewer.ts +16 -0
- package/src/scripts/frontend_corpus_hash.ts +116 -0
- package/src/scripts/generate_pack_manifests.ts +78 -0
- package/src/scripts/grade_target_readiness.ts +478 -0
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +97 -16
- package/src/scripts/hooks/concern_registry.ts +4 -0
- package/src/scripts/hooks/design_pass_hook.ts +356 -0
- package/src/scripts/hooks/rtk_wrap_hook.ts +30 -7
- package/src/scripts/hooks/rule_inject_hook.ts +318 -0
- package/src/scripts/hot_context_hook.ts +74 -7
- package/src/scripts/install.ts +40 -89
- package/src/scripts/lint_adapter_tier.ts +663 -0
- package/src/scripts/lint_archived_skills.ts +93 -0
- package/src/scripts/lint_budget_ownership.ts +2 -1
- package/src/scripts/lint_deterministic_time.ts +470 -0
- package/src/scripts/lint_framework_leakage.ts +5 -0
- package/src/scripts/lint_handoffs.ts +525 -1
- package/src/scripts/lint_judge_prompt_expectation.ts +155 -0
- package/src/scripts/lint_marketplace.ts +88 -0
- package/src/scripts/lint_media_policy_linkage.ts +140 -1
- package/src/scripts/lint_one_off_age.ts +6 -1
- package/src/scripts/lint_override_kernel_guard.ts +87 -5
- package/src/scripts/lint_roadmap_complexity.ts +56 -0
- package/src/scripts/lint_rule_enforcement_declaration.ts +24 -7
- package/src/scripts/lint_rule_norm_pin.ts +321 -0
- package/src/scripts/lint_spawn_payload.ts +42 -5
- package/src/scripts/lint_symptom_intake.ts +2 -1
- package/src/scripts/mcp_parity_smoke.ts +30 -4
- package/src/scripts/mcp_render.ts +66 -1
- package/src/scripts/mcp_tool_fingerprint.ts +241 -0
- package/src/scripts/measure_risk_class_drift.ts +140 -0
- package/src/scripts/measure_skill_ranker_baseline.ts +212 -0
- package/src/scripts/media/lib/adapter-common.sh +211 -1
- package/src/scripts/media/lib/adapter-contract.md +74 -6
- package/src/scripts/model_rule_injection.ts +907 -0
- package/src/scripts/project_thin_rules.ts +45 -2
- package/src/scripts/release.ts +66 -860
- package/src/scripts/release_drill.ts +59 -3
- package/src/scripts/release_env.ts +298 -0
- package/src/scripts/release_publication.ts +720 -0
- package/src/scripts/roadmap_context.ts +778 -0
- package/src/scripts/schemas/fidelity-measurement-sheet.schema.json +169 -0
- package/src/scripts/schemas/pack.schema.json +62 -4
- package/src/scripts/schemas/rule.schema.json +24 -2
- package/src/scripts/schemas/skill.schema.json +41 -2
- package/src/scripts/schemas/ui-authority.schema.json +114 -0
- package/src/scripts/session_register_hook.ts +29 -3
- package/src/scripts/sessions_cli.ts +67 -3
- package/src/scripts/skill_tools/score_skill_relevance.ts +36 -53
- package/src/scripts/tighten_skill_descriptions.ts +264 -0
- package/src/scripts/token_sink_report.ts +239 -0
- package/src/scripts/trigger_coverage.ts +122 -3
- package/src/scripts/update_counts.ts +7 -3
- package/src/scripts/validate_frontmatter.ts +66 -0
- package/src/server/schemas/settings.ts +5 -0
- package/src/shared/skillRanking.ts +183 -0
|
@@ -48,6 +48,17 @@ type Dict = Record<string, unknown>;
|
|
|
48
48
|
|
|
49
49
|
interface Artefact {
|
|
50
50
|
path: string;
|
|
51
|
+
/**
|
|
52
|
+
* On-disk byte size, captured where the ABSOLUTE path is still known.
|
|
53
|
+
*
|
|
54
|
+
* `path` above is a LOGICAL relative path whose root differs per collection
|
|
55
|
+
* mode, so it cannot be stat'd later. A first version of the token passport
|
|
56
|
+
* did exactly that, and every `statSync` threw into a catch that returned 0
|
|
57
|
+
* — 17 packs generated an all-zero passport that would then have
|
|
58
|
+
* "reconciled" against nothing. Carrying the number from the one place it
|
|
59
|
+
* is knowable removes the failure mode rather than handling it.
|
|
60
|
+
*/
|
|
61
|
+
bytes: number;
|
|
51
62
|
name: string;
|
|
52
63
|
description: string;
|
|
53
64
|
category: string;
|
|
@@ -143,8 +154,15 @@ function _artefact_record(p: string, logicalRel: string, fm: Dict): Artefact {
|
|
|
143
154
|
: path.basename(p) === 'SKILL.md'
|
|
144
155
|
? path.basename(path.dirname(p))
|
|
145
156
|
: _stem(p);
|
|
157
|
+
let bytes = 0;
|
|
158
|
+
try {
|
|
159
|
+
bytes = fs.statSync(p).size;
|
|
160
|
+
} catch {
|
|
161
|
+
bytes = 0;
|
|
162
|
+
}
|
|
146
163
|
return {
|
|
147
164
|
path: logicalRel,
|
|
165
|
+
bytes,
|
|
148
166
|
name,
|
|
149
167
|
description: _pyStrip(_pyTruthy(fm.description) ? String(fm.description) : ''),
|
|
150
168
|
category: _category_from_logical(logicalRel),
|
|
@@ -247,6 +265,65 @@ function _category_from_logical(logicalRel: string): string {
|
|
|
247
265
|
* Build the pack.yaml meta dict. Mirrors `_build_pack_yaml`. Insertion
|
|
248
266
|
* order here is irrelevant — `_py_safe_dump` sorts keys (sort_keys=True).
|
|
249
267
|
*/
|
|
268
|
+
/**
|
|
269
|
+
* What this pack contributes to the STANDING session payload.
|
|
270
|
+
*
|
|
271
|
+
* Derivation, tokenizer and reconciliation target are fixed by
|
|
272
|
+
* `docs/contracts/pack-token-passport.md`, written before the first passport
|
|
273
|
+
* was generated — a number whose derivation is undocumented becomes a figure
|
|
274
|
+
* people quote without being able to reproduce.
|
|
275
|
+
*
|
|
276
|
+
* `chars/4`, deliberately, because that is the basis
|
|
277
|
+
* `preamble-payload-budget.json` declares and `preamble_byte_census.ts` uses. A
|
|
278
|
+
* passport measured with an exact tokenizer could not be reconciled against a
|
|
279
|
+
* census measured with a proxy without the gap between the two METHODS reading
|
|
280
|
+
* as a discrepancy in the packs.
|
|
281
|
+
*
|
|
282
|
+
* The catalog line is built the same way `censusSkillsCatalog` builds it —
|
|
283
|
+
* `- <name>: <description>\n` — rather than approximated, so the two sides of
|
|
284
|
+
* the reconciliation are counting the same string.
|
|
285
|
+
*
|
|
286
|
+
* It measures the STANDING contribution only. A skill body loaded on invocation
|
|
287
|
+
* is a runtime cost and is not here; the residual bucket (tool definitions +
|
|
288
|
+
* dispatch prompt) is not attributable to a pack at all. Both are stated in the
|
|
289
|
+
* contract, which is where the number lives.
|
|
290
|
+
*/
|
|
291
|
+
function _token_passport(artefacts: readonly Artefact[]): Dict {
|
|
292
|
+
const tok = (chars: number): number => Math.round(chars / 4);
|
|
293
|
+
let ruleChars = 0;
|
|
294
|
+
let catalogChars = 0;
|
|
295
|
+
let commandChars = 0;
|
|
296
|
+
let otherChars = 0;
|
|
297
|
+
for (const a of artefacts) {
|
|
298
|
+
if (a.category === 'skill') {
|
|
299
|
+
catalogChars += `- ${a.name}: ${a.description}\n`.length;
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
if (a.category === 'rule') ruleChars += a.bytes;
|
|
303
|
+
else if (a.category === 'command') commandChars += a.bytes;
|
|
304
|
+
// Everything else a pack CLAIMS — contexts, personas, presets,
|
|
305
|
+
// profiles, user-types. Counted, because a pack that claims 47
|
|
306
|
+
// artefacts and reports 0 tokens is worse than no passport: it reads as
|
|
307
|
+
// "this pack is free" when it means "the three named buckets do not
|
|
308
|
+
// cover what this pack holds". Measured: `core` claims 47 artefacts and
|
|
309
|
+
// none of them is a rule, a skill or a command — `src/agent-src/`
|
|
310
|
+
// carries no `rules/` or `skills/` subtree at all.
|
|
311
|
+
else otherChars += a.bytes;
|
|
312
|
+
}
|
|
313
|
+
const rules_tokens = tok(ruleChars);
|
|
314
|
+
const catalog_tokens = tok(catalogChars);
|
|
315
|
+
const commands_tokens = tok(commandChars);
|
|
316
|
+
const other_tokens = tok(otherChars);
|
|
317
|
+
return {
|
|
318
|
+
basis: 'chars/4',
|
|
319
|
+
rules_tokens,
|
|
320
|
+
catalog_tokens,
|
|
321
|
+
commands_tokens,
|
|
322
|
+
other_tokens,
|
|
323
|
+
total_tokens: rules_tokens + catalog_tokens + commands_tokens + other_tokens,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
250
327
|
function _build_pack_yaml(pid: string, vocab: Record<string, Dict>, artefacts: Artefact[]): Dict {
|
|
251
328
|
const meta = vocab[pid] ?? {};
|
|
252
329
|
const label = _pyTruthy(meta.label)
|
|
@@ -276,6 +353,7 @@ function _build_pack_yaml(pid: string, vocab: Record<string, Dict>, artefacts: A
|
|
|
276
353
|
? (meta.trust_level_default as unknown)
|
|
277
354
|
: 'core',
|
|
278
355
|
artefact_count: artefacts.length,
|
|
356
|
+
token_passport: _token_passport(artefacts),
|
|
279
357
|
};
|
|
280
358
|
if (_isPlainObject(meta.onboarding)) {
|
|
281
359
|
out.onboarding = meta.onboarding;
|
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Grade a TARGET repository's assurance readiness — the matrix `/project:analyze` prints.
|
|
4
|
+
*
|
|
5
|
+
* WHY A SCRIPT AND NOT PROSE. `road-to-target-project-assurance-readiness`
|
|
6
|
+
* Phase 1 asks for detection asserted "by a vitest spec", and a gather list
|
|
7
|
+
* written in a command's markdown cannot be asserted by anything. So the
|
|
8
|
+
* detection is here and the command invokes it; the command keeps the narrative.
|
|
9
|
+
*
|
|
10
|
+
* THE ANTI-VANITY RULE IS STRUCTURAL, NOT A STYLE NOTE. Ten dimensions are
|
|
11
|
+
* graded 0 Absent / 1 Present / 2 Enforced-in-CI / 3 Independent-and-diff-scoped,
|
|
12
|
+
* and the verdict is `min` over the FOUR knockout dimensions, reported as
|
|
13
|
+
* `L<n> — bound by <dimension>`. **No aggregate number is ever emitted** — no
|
|
14
|
+
* percentage, no x/100, no mean. A single score is the vanity metric the source
|
|
15
|
+
* report's own anti-vanity rule forbids, and `renderMatrix` carries a test that
|
|
16
|
+
* greps its own output for `%` and `/100`.
|
|
17
|
+
*
|
|
18
|
+
* NOT-DETECTABLE IS NOT ZERO, AND ON A KNOCKOUT IT BINDS. A `0` claims the target
|
|
19
|
+
* lacks something. `not detectable` says this tool cannot tell — a different fact,
|
|
20
|
+
* and the honest one for a Python target, because `quality-tools` routes PHP and
|
|
21
|
+
* JS/TS only. An undetectable KNOCKOUT binds at L0 with its reason printed, so the
|
|
22
|
+
* gap is visible rather than flattering.
|
|
23
|
+
*
|
|
24
|
+
* SCOPE — this grades, it does not install. Whether a missing gate then gets
|
|
25
|
+
* created in the target is a successor roadmap's problem
|
|
26
|
+
* (`stubs/road-to-target-project-bootstrap-enforce.md`).
|
|
27
|
+
*
|
|
28
|
+
* Exit codes:
|
|
29
|
+
* 0 — a matrix was produced (any level, including L0 — a low grade is a
|
|
30
|
+
* finding about the target, never a failure of this gate)
|
|
31
|
+
* 1 — nothing was gradeable (no target root)
|
|
32
|
+
* 2 — the gate could not run (bad args)
|
|
33
|
+
*/
|
|
34
|
+
import * as fs from 'node:fs';
|
|
35
|
+
import * as os from 'node:os';
|
|
36
|
+
import * as path from 'node:path';
|
|
37
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
38
|
+
|
|
39
|
+
import { GateLedger } from './_lib/gate_ledger.js';
|
|
40
|
+
import { runGateCli, runSelfTest, type SelfTestCase } from './_lib/gate_self_test.js';
|
|
41
|
+
import { DeadScopeError, reportScanned } from './_lib/scan_scope.js';
|
|
42
|
+
|
|
43
|
+
const _HERE = fileURLToPath(import.meta.url);
|
|
44
|
+
const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
45
|
+
const SELF = 'src/scripts/grade_target_readiness.ts';
|
|
46
|
+
|
|
47
|
+
const SELF_TEST_MIN_CASES = 6;
|
|
48
|
+
const SELF_TEST_MIN_REJECT = 2;
|
|
49
|
+
|
|
50
|
+
/** 0 Absent · 1 Present · 2 Enforced-in-CI · 3 Independent-and-diff-scoped. */
|
|
51
|
+
export type Grade = 0 | 1 | 2 | 3;
|
|
52
|
+
|
|
53
|
+
/** `null` means "this tool cannot tell", which is NOT a zero. */
|
|
54
|
+
export interface DimensionResult {
|
|
55
|
+
id: string;
|
|
56
|
+
label: string;
|
|
57
|
+
knockout: boolean;
|
|
58
|
+
grade: Grade | null;
|
|
59
|
+
/** Required when `grade` is null: why detection is impossible, not merely absent. */
|
|
60
|
+
notDetectable?: string;
|
|
61
|
+
evidence: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface Matrix {
|
|
65
|
+
level: Grade | 0;
|
|
66
|
+
boundBy: string;
|
|
67
|
+
boundReason?: string;
|
|
68
|
+
dimensions: DimensionResult[];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ── primitive probes ───────────────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
const _exists = (root: string, ...rel: string[]): boolean =>
|
|
74
|
+
rel.some((r) => fs.existsSync(path.join(root, r)));
|
|
75
|
+
|
|
76
|
+
function _glob1(root: string, dir: string, re: RegExp): boolean {
|
|
77
|
+
const d = path.join(root, dir);
|
|
78
|
+
try {
|
|
79
|
+
return fs.readdirSync(d).some((f) => re.test(f));
|
|
80
|
+
} catch {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function _readIfAny(root: string, ...rel: string[]): string {
|
|
86
|
+
for (const r of rel) {
|
|
87
|
+
try {
|
|
88
|
+
return fs.readFileSync(path.join(root, r), 'utf-8');
|
|
89
|
+
} catch {
|
|
90
|
+
/* next */
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return '';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Every workflow file's text, concatenated. Empty when there is no CI at all. */
|
|
97
|
+
function _ciText(root: string): string {
|
|
98
|
+
const d = path.join(root, '.github', 'workflows');
|
|
99
|
+
let out = '';
|
|
100
|
+
try {
|
|
101
|
+
for (const f of fs.readdirSync(d)) {
|
|
102
|
+
if (/\.ya?ml$/.test(f)) out += `${fs.readFileSync(path.join(d, f), 'utf-8')}\n`;
|
|
103
|
+
}
|
|
104
|
+
} catch {
|
|
105
|
+
/* no CI */
|
|
106
|
+
}
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Is this a Python-primary target?
|
|
112
|
+
*
|
|
113
|
+
* Load-bearing for dimension 4: `quality-tools` routes PHP and JS/TS only, so on
|
|
114
|
+
* a Python target the static-analysis dimension is NOT DETECTABLE rather than 0.
|
|
115
|
+
*/
|
|
116
|
+
export function isPythonTarget(root: string): boolean {
|
|
117
|
+
const py = _exists(root, 'pyproject.toml', 'setup.cfg', 'requirements.txt', 'setup.py');
|
|
118
|
+
const other = _exists(root, 'composer.json', 'package.json');
|
|
119
|
+
return py && !other;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ── the ten dimensions ─────────────────────────────────────────────────────
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Grade one dimension. `ci` decides 1 → 2: a thing that exists is `Present`; a
|
|
126
|
+
* thing CI runs in a job that is not `continue-on-error` is `Enforced-in-CI`.
|
|
127
|
+
*/
|
|
128
|
+
function _graded(present: boolean, enforced: boolean, diffScoped = false): Grade {
|
|
129
|
+
if (!present) return 0;
|
|
130
|
+
if (diffScoped && enforced) return 3;
|
|
131
|
+
return enforced ? 2 : 1;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** A CI job that cannot fail the build enforces nothing. */
|
|
135
|
+
function _ciBlocks(ci: string, ...needles: string[]): boolean {
|
|
136
|
+
if (ci === '') return false;
|
|
137
|
+
const mentions = needles.some((n) => ci.toLowerCase().includes(n.toLowerCase()));
|
|
138
|
+
if (!mentions) return false;
|
|
139
|
+
// Coarse but honest: a workflow that marks EVERY job continue-on-error
|
|
140
|
+
// enforces nothing. Per-job attribution needs a YAML parse and is the
|
|
141
|
+
// successor roadmap's problem; stated rather than silently approximated.
|
|
142
|
+
const allSoft = /continue-on-error:\s*true/.test(ci) && !/continue-on-error:\s*false/.test(ci);
|
|
143
|
+
return !allSoft;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function grade(root: string): Matrix {
|
|
147
|
+
const ci = _ciText(root);
|
|
148
|
+
const hasCi = ci !== '';
|
|
149
|
+
const py = isPythonTarget(root);
|
|
150
|
+
const pyproject = _readIfAny(root, 'pyproject.toml');
|
|
151
|
+
|
|
152
|
+
const dims: DimensionResult[] = [];
|
|
153
|
+
|
|
154
|
+
// 1. behaviour contract
|
|
155
|
+
const contract = _exists(root, 'AGENTS.md', 'openapi.yaml', 'openapi.yml', 'CONTRACT.md');
|
|
156
|
+
dims.push({
|
|
157
|
+
id: 'behaviour-contract',
|
|
158
|
+
label: 'behaviour contract',
|
|
159
|
+
knockout: false,
|
|
160
|
+
grade: _graded(contract, false),
|
|
161
|
+
evidence: contract ? 'AGENTS.md / OpenAPI / CONTRACT.md present' : 'none found',
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// 2. test presence & types — KNOCKOUT
|
|
165
|
+
const testCfg =
|
|
166
|
+
_exists(root, 'vitest.config.ts', 'vitest.config.js', 'jest.config.js', 'jest.config.ts',
|
|
167
|
+
'phpunit.xml', 'phpunit.xml.dist', 'pest.php', 'pytest.ini', 'tox.ini') ||
|
|
168
|
+
/\[tool\.pytest/.test(pyproject);
|
|
169
|
+
dims.push({
|
|
170
|
+
id: 'test-presence',
|
|
171
|
+
label: 'test presence & types',
|
|
172
|
+
knockout: true,
|
|
173
|
+
grade: _graded(testCfg, _ciBlocks(ci, 'vitest', 'jest', 'phpunit', 'pest', 'pytest', 'npm test', 'task test')),
|
|
174
|
+
evidence: testCfg ? 'a test-runner config is present' : 'no test-runner config',
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// 3. test strength — mutation or property-based
|
|
178
|
+
const mutation =
|
|
179
|
+
_glob1(root, '.', /^stryker\.conf\./) || _exists(root, 'infection.json', 'infection.json.dist') ||
|
|
180
|
+
/\[tool\.mutmut\]/.test(pyproject) || /\[mutmut\]/.test(_readIfAny(root, 'setup.cfg'));
|
|
181
|
+
const property =
|
|
182
|
+
/fast-check/.test(_readIfAny(root, 'package.json')) || /hypothesis/.test(pyproject + _readIfAny(root, 'requirements.txt'));
|
|
183
|
+
dims.push({
|
|
184
|
+
id: 'test-strength',
|
|
185
|
+
label: 'test strength',
|
|
186
|
+
knockout: false,
|
|
187
|
+
grade: _graded(mutation || property, _ciBlocks(ci, 'stryker', 'infection', 'mutmut')),
|
|
188
|
+
evidence: mutation ? 'mutation config present' : property ? 'property-based library present' : 'neither mutation nor property testing',
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// 4. static analysis & types — KNOCKOUT, and the not-detectable case
|
|
192
|
+
if (py) {
|
|
193
|
+
dims.push({
|
|
194
|
+
id: 'static-analysis',
|
|
195
|
+
label: 'static analysis & types',
|
|
196
|
+
knockout: true,
|
|
197
|
+
grade: null,
|
|
198
|
+
notDetectable: 'quality-tools has no Python mode',
|
|
199
|
+
evidence: 'Python-primary target',
|
|
200
|
+
});
|
|
201
|
+
} else {
|
|
202
|
+
const sa = _exists(root, 'phpstan.neon', 'phpstan.neon.dist', 'psalm.xml', 'tsconfig.json');
|
|
203
|
+
dims.push({
|
|
204
|
+
id: 'static-analysis',
|
|
205
|
+
label: 'static analysis & types',
|
|
206
|
+
knockout: true,
|
|
207
|
+
grade: _graded(sa, _ciBlocks(ci, 'phpstan', 'psalm', 'tsc', 'typecheck')),
|
|
208
|
+
evidence: sa ? 'a static-analysis config is present' : 'no static-analysis config',
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// 5. architecture gates
|
|
213
|
+
const arch = _glob1(root, '.', /^\.dependency-cruiser\./) || _exists(root, 'deptrac.yaml', 'deptrac.yml');
|
|
214
|
+
dims.push({
|
|
215
|
+
id: 'architecture-gates',
|
|
216
|
+
label: 'architecture gates',
|
|
217
|
+
knockout: false,
|
|
218
|
+
grade: _graded(arch, _ciBlocks(ci, 'dependency-cruiser', 'deptrac')),
|
|
219
|
+
evidence: arch ? 'a boundary-gate config is present' : 'no architecture gate',
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// 6. security & supply chain — KNOCKOUT
|
|
223
|
+
const lock = _exists(root, 'package-lock.json', 'pnpm-lock.yaml', 'yarn.lock', 'composer.lock', 'poetry.lock', 'uv.lock');
|
|
224
|
+
const sast = _glob1(root, '.', /^\.semgrep/) || _exists(root, 'bandit.yaml', '.bandit');
|
|
225
|
+
const audit = _ciBlocks(ci, 'npm audit', 'pnpm audit', 'composer audit', 'pip-audit', 'semgrep', 'bandit');
|
|
226
|
+
dims.push({
|
|
227
|
+
id: 'security-supply-chain',
|
|
228
|
+
label: 'security & supply chain',
|
|
229
|
+
knockout: true,
|
|
230
|
+
grade: _graded(lock || sast, audit),
|
|
231
|
+
evidence: `${lock ? 'lockfile' : 'no lockfile'}; ${sast ? 'SAST config' : 'no SAST config'}; ${audit ? 'audit runs in CI' : 'no blocking audit step'}`,
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// 7. CI enforcement — KNOCKOUT
|
|
235
|
+
const blockingJob = hasCi && !(/continue-on-error:\s*true/.test(ci) && !/continue-on-error:\s*false/.test(ci));
|
|
236
|
+
dims.push({
|
|
237
|
+
id: 'ci-enforcement',
|
|
238
|
+
label: 'CI enforcement',
|
|
239
|
+
knockout: true,
|
|
240
|
+
grade: hasCi ? (blockingJob ? 2 : 1) : 0,
|
|
241
|
+
evidence: !hasCi ? 'no workflows' : blockingJob ? 'at least one job can fail the build' : 'every job is continue-on-error',
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// 8. independent verification
|
|
245
|
+
const owners = _exists(root, 'CODEOWNERS', '.github/CODEOWNERS', 'docs/CODEOWNERS');
|
|
246
|
+
dims.push({
|
|
247
|
+
id: 'independent-verification',
|
|
248
|
+
label: 'independent verification',
|
|
249
|
+
knockout: false,
|
|
250
|
+
grade: _graded(owners, false),
|
|
251
|
+
evidence: owners ? 'CODEOWNERS present' : 'no CODEOWNERS',
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// 9. evidence & traceability
|
|
255
|
+
const adr = _exists(root, 'docs/adr', 'docs/decisions', 'adr');
|
|
256
|
+
dims.push({
|
|
257
|
+
id: 'evidence-traceability',
|
|
258
|
+
label: 'evidence & traceability',
|
|
259
|
+
knockout: false,
|
|
260
|
+
grade: _graded(adr, false),
|
|
261
|
+
evidence: adr ? 'a decision record directory exists' : 'no decision records',
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// 10. runtime verification — never detectable, and deliberately NOT a knockout
|
|
265
|
+
dims.push({
|
|
266
|
+
id: 'runtime-verification',
|
|
267
|
+
label: 'runtime verification',
|
|
268
|
+
knockout: false,
|
|
269
|
+
grade: null,
|
|
270
|
+
notDetectable: 'needs a deploy platform this tool does not own',
|
|
271
|
+
evidence: 'out of scope by design (see the roadmap exclusion table)',
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// ── the verdict: min over knockouts, undetectable binds at L0 ──────────
|
|
275
|
+
const knockouts = dims.filter((d) => d.knockout);
|
|
276
|
+
const undetectable = knockouts.find((d) => d.grade === null);
|
|
277
|
+
if (undetectable !== undefined) {
|
|
278
|
+
// `boundReason` is spread in conditionally rather than assigned
|
|
279
|
+
// `undefined`: under `exactOptionalPropertyTypes` an optional property
|
|
280
|
+
// and a property explicitly set to `undefined` are different types, and
|
|
281
|
+
// the second does not satisfy `boundReason?: string`.
|
|
282
|
+
return {
|
|
283
|
+
level: 0,
|
|
284
|
+
boundBy: undetectable.label,
|
|
285
|
+
...(undetectable.notDetectable === undefined ? {} : { boundReason: undetectable.notDetectable }),
|
|
286
|
+
dimensions: dims,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
let level: Grade = 3;
|
|
290
|
+
let boundBy = knockouts[0]?.label ?? 'unknown';
|
|
291
|
+
for (const k of knockouts) {
|
|
292
|
+
const g = k.grade as Grade;
|
|
293
|
+
if (g < level) {
|
|
294
|
+
level = g;
|
|
295
|
+
boundBy = k.label;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return { level, boundBy, dimensions: dims };
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// ── rendering ──────────────────────────────────────────────────────────────
|
|
302
|
+
|
|
303
|
+
const GRADE_WORD: Record<number, string> = { 0: 'Absent', 1: 'Present', 2: 'Enforced-in-CI', 3: 'Independent+diff-scoped' };
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Render the matrix. Emits NO aggregate figure — no `%`, no `/100`, no mean.
|
|
307
|
+
* A test greps this function's own output for both, because the pressure to
|
|
308
|
+
* print one number is exactly what turns readiness into a vanity metric.
|
|
309
|
+
*/
|
|
310
|
+
export function renderMatrix(m: Matrix): string {
|
|
311
|
+
const L: string[] = [];
|
|
312
|
+
L.push('READINESS');
|
|
313
|
+
L.push(
|
|
314
|
+
m.boundReason !== undefined
|
|
315
|
+
? `L${String(m.level)} — bound by ${m.boundBy} (not detectable — ${m.boundReason})`
|
|
316
|
+
: `L${String(m.level)} — bound by ${m.boundBy}`,
|
|
317
|
+
);
|
|
318
|
+
L.push('');
|
|
319
|
+
for (const d of m.dimensions) {
|
|
320
|
+
const val = d.grade === null ? `not detectable — ${d.notDetectable ?? ''}` : `${String(d.grade)} ${GRADE_WORD[d.grade] ?? ''}`;
|
|
321
|
+
L.push(` ${d.knockout ? '!' : ' '} ${d.label.padEnd(28)} ${val}`);
|
|
322
|
+
}
|
|
323
|
+
L.push('');
|
|
324
|
+
L.push(' ! = knockout dimension. The level is the MINIMUM over knockouts;');
|
|
325
|
+
L.push(' an undetectable knockout binds at L0 with its reason shown.');
|
|
326
|
+
L.push(' No aggregate score is reported, by design.');
|
|
327
|
+
return L.join('\n');
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// ── self-test ──────────────────────────────────────────────────────────────
|
|
331
|
+
|
|
332
|
+
function _mk(files: Record<string, string>): string {
|
|
333
|
+
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'target-readiness-'));
|
|
334
|
+
for (const [rel, body] of Object.entries(files)) {
|
|
335
|
+
const p = path.join(d, rel);
|
|
336
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
337
|
+
fs.writeFileSync(p, body);
|
|
338
|
+
}
|
|
339
|
+
return d;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const BLOCKING_CI = 'jobs:\n t:\n steps:\n - run: npx vitest run\n - run: npx tsc --noEmit\n - run: npm audit\n';
|
|
343
|
+
|
|
344
|
+
export function selfTest(): number {
|
|
345
|
+
const made: string[] = [];
|
|
346
|
+
const mk = (f: Record<string, string>): string => {
|
|
347
|
+
const d = _mk(f);
|
|
348
|
+
made.push(d);
|
|
349
|
+
return d;
|
|
350
|
+
};
|
|
351
|
+
const cases: SelfTestCase[] = [
|
|
352
|
+
{
|
|
353
|
+
name: 'a bare directory grades L0 bound by a knockout, and does not crash',
|
|
354
|
+
expect: 'accept',
|
|
355
|
+
run: () => (grade(mk({ 'README.md': '#\n' })).level === 0 ? 0 : 1),
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
name: 'a Python target reports static analysis NOT DETECTABLE, never 0',
|
|
359
|
+
expect: 'accept',
|
|
360
|
+
run: () => {
|
|
361
|
+
const m = grade(mk({ 'pyproject.toml': '[tool.pytest.ini_options]\n' }));
|
|
362
|
+
const sa = m.dimensions.find((d) => d.id === 'static-analysis');
|
|
363
|
+
return sa?.grade === null && sa.notDetectable === 'quality-tools has no Python mode' ? 0 : 1;
|
|
364
|
+
},
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
name: 'an undetectable knockout BINDS at L0 with its reason',
|
|
368
|
+
expect: 'accept',
|
|
369
|
+
run: () => {
|
|
370
|
+
const m = grade(mk({ 'pyproject.toml': '[tool.pytest.ini_options]\n' }));
|
|
371
|
+
return m.level === 0 && m.boundBy === 'static analysis & types' && m.boundReason !== undefined ? 0 : 1;
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
name: 'CI enforcement at 0 binds even when everything else is enforced',
|
|
376
|
+
expect: 'accept',
|
|
377
|
+
run: () => {
|
|
378
|
+
const m = grade(mk({
|
|
379
|
+
'package.json': '{"devDependencies":{"fast-check":"1"}}',
|
|
380
|
+
'tsconfig.json': '{}',
|
|
381
|
+
'vitest.config.ts': '',
|
|
382
|
+
'package-lock.json': '{}',
|
|
383
|
+
'.semgrep.yml': '',
|
|
384
|
+
'deptrac.yaml': '',
|
|
385
|
+
'AGENTS.md': '#\n',
|
|
386
|
+
'CODEOWNERS': '* @o\n',
|
|
387
|
+
'docs/adr/ADR-1.md': '#\n',
|
|
388
|
+
}));
|
|
389
|
+
return m.level === 0 && m.boundBy === 'CI enforcement' ? 0 : 1;
|
|
390
|
+
},
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
name: 'the rendered matrix carries NO aggregate figure',
|
|
394
|
+
expect: 'accept',
|
|
395
|
+
run: () => {
|
|
396
|
+
const out = renderMatrix(grade(mk({ 'package.json': '{}', '.github/workflows/ci.yml': BLOCKING_CI })));
|
|
397
|
+
return /%|\/100/.test(out) ? 1 : 0;
|
|
398
|
+
},
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
name: 'rejects a missing target root rather than grading an empty tree',
|
|
402
|
+
expect: 'reject',
|
|
403
|
+
run: () => runGateCli(REPO_ROOT, SELF, ['--quiet', '--target', path.join(REPO_ROOT, 'no-such-target')], REPO_ROOT),
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
name: 'rejects a target argument that is a file, not a directory',
|
|
407
|
+
expect: 'reject',
|
|
408
|
+
run: () => runGateCli(REPO_ROOT, SELF, ['--quiet', '--target', path.join(REPO_ROOT, 'package.json')], REPO_ROOT),
|
|
409
|
+
},
|
|
410
|
+
];
|
|
411
|
+
try {
|
|
412
|
+
return runSelfTest({ gate: 'grade_target_readiness', cases, minCases: SELF_TEST_MIN_CASES, minRejectCases: SELF_TEST_MIN_REJECT });
|
|
413
|
+
} finally {
|
|
414
|
+
for (const d of made) fs.rmSync(d, { recursive: true, force: true });
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function main(argv?: readonly string[]): number {
|
|
419
|
+
const args = argv ?? process.argv.slice(2);
|
|
420
|
+
if (args.includes('--self-test')) return selfTest();
|
|
421
|
+
const quiet = args.includes('--quiet');
|
|
422
|
+
const i = args.indexOf('--target');
|
|
423
|
+
const target = i >= 0 ? args[i + 1] : process.cwd();
|
|
424
|
+
if (target === undefined) {
|
|
425
|
+
process.stderr.write('grade_target_readiness: --target requires a directory\n');
|
|
426
|
+
return 2;
|
|
427
|
+
}
|
|
428
|
+
let stat: fs.Stats;
|
|
429
|
+
try {
|
|
430
|
+
stat = fs.statSync(target);
|
|
431
|
+
} catch {
|
|
432
|
+
process.stderr.write(`❌ target not found: ${target}\n`);
|
|
433
|
+
return 1;
|
|
434
|
+
}
|
|
435
|
+
if (!stat.isDirectory()) {
|
|
436
|
+
process.stderr.write(`❌ target is not a directory: ${target}\n`);
|
|
437
|
+
return 1;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const m = grade(target);
|
|
441
|
+
const ledger = new GateLedger('grade_target_readiness');
|
|
442
|
+
ledger.plan(m.dimensions.map((d) => d.id));
|
|
443
|
+
for (const d of m.dimensions) {
|
|
444
|
+
if (d.grade === null) ledger.outOfScope(d.id, 'not_applicable_kind');
|
|
445
|
+
else ledger.complete(d.id);
|
|
446
|
+
}
|
|
447
|
+
ledger.report();
|
|
448
|
+
try {
|
|
449
|
+
reportScanned({
|
|
450
|
+
gate: 'grade_target_readiness',
|
|
451
|
+
scanned: m.dimensions.length,
|
|
452
|
+
units: 'readiness dimension(s)',
|
|
453
|
+
roots: [target],
|
|
454
|
+
});
|
|
455
|
+
} catch (err) {
|
|
456
|
+
if (err instanceof DeadScopeError) {
|
|
457
|
+
process.stderr.write(`❌ ${err.message}\n`);
|
|
458
|
+
return 2;
|
|
459
|
+
}
|
|
460
|
+
throw err;
|
|
461
|
+
}
|
|
462
|
+
if (!quiet) process.stdout.write(`${renderMatrix(m)}\n`);
|
|
463
|
+
return 0;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function _isCliEntry(): boolean {
|
|
467
|
+
if (process.argv[1] === undefined) return false;
|
|
468
|
+
if (import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) return true;
|
|
469
|
+
try {
|
|
470
|
+
return fs.realpathSync(fileURLToPath(import.meta.url)) === fs.realpathSync(path.resolve(process.argv[1]));
|
|
471
|
+
} catch {
|
|
472
|
+
return false;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (_isCliEntry() || process.argv[1] === _HERE) process.exit(main());
|
|
477
|
+
|
|
478
|
+
export { main };
|