@event4u/agent-config 14.10.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 +335 -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/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-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 +3 -0
- package/dist/agent-src/skills/context-authoring/SKILL.md +1 -1
- package/dist/agent-src/skills/conventional-commits-writing/SKILL.md +3 -0
- 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 +79 -1
- 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/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/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/laravel-migration/SKILL.md +3 -0
- package/dist/agent-src/skills/learning-to-rule-or-skill/SKILL.md +3 -0
- 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/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 +6 -0
- package/dist/agent-src/skills/multi-tenancy/SKILL.md +4 -0
- 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/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/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 +2 -2
- 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/scene-blueprint.schema.yaml +26 -0
- package/dist/agent-src/skills/secrets-management/SKILL.md +4 -0
- 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/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 +359 -124
- 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 +207 -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/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/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_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/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/rule.schema.json +24 -2
- package/src/scripts/schemas/skill.schema.json +39 -0
- 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/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
|
@@ -13,6 +13,15 @@
|
|
|
13
13
|
* .windsurf/mcp.json (in-project)
|
|
14
14
|
* ~/.config/claude-desktop/claude_desktop_config.json (user, opt-in)
|
|
15
15
|
*
|
|
16
|
+
* `.mcp.json` — Claude Code's project-scope config — is a CHECK-ONLY target
|
|
17
|
+
* (Phase 1.3 of `road-to-skill-delivery-over-mcp`). It is deliberately not
|
|
18
|
+
* rendered: the installer writes it with `merge_json_file`, which touches only
|
|
19
|
+
* this package's own server key and records it in `agents/installed-tools.lock`
|
|
20
|
+
* so uninstall subtracts exactly that key. Rendering it here would overwrite a
|
|
21
|
+
* file the consumer also edits and silently delete servers they added by hand.
|
|
22
|
+
* `--check` therefore asserts CONTAINMENT of our entry rather than equality with
|
|
23
|
+
* a rendering, and says so on the same line.
|
|
24
|
+
*
|
|
16
25
|
* All targets use the same `mcpServers` top-level key. The source file uses
|
|
17
26
|
* `servers` to keep our internal schema stable if a downstream format ever
|
|
18
27
|
* diverges.
|
|
@@ -40,6 +49,36 @@ export function default_project_root(): string {
|
|
|
40
49
|
return fs.realpathSync(process.cwd());
|
|
41
50
|
}
|
|
42
51
|
|
|
52
|
+
/** The one entry `.mcp.json` must contain. Mirrors `install.ts:MCP_SERVER_KEY`. */
|
|
53
|
+
export const MCP_JSON_SERVER_KEY = 'agent-config';
|
|
54
|
+
|
|
55
|
+
/** Claude Code's project-scope MCP config. Checked here, written by the installer. */
|
|
56
|
+
export function claude_code_mcp_target(projectRoot: string): string {
|
|
57
|
+
return path.join(projectRoot, '.mcp.json');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Does `.mcp.json` carry this package's server entry?
|
|
62
|
+
*
|
|
63
|
+
* `absent` is a distinct verdict from `missing-entry` on purpose: absent means
|
|
64
|
+
* the installer has not run for `claude-code` here, which is a hint; a file that
|
|
65
|
+
* exists WITHOUT our key means something removed it, which is a failure.
|
|
66
|
+
*/
|
|
67
|
+
export function check_mcp_json(projectRoot: string): 'absent' | 'ok' | 'missing-entry' | 'unreadable' {
|
|
68
|
+
const target = claude_code_mcp_target(projectRoot);
|
|
69
|
+
if (!fs.existsSync(target)) return 'absent';
|
|
70
|
+
let parsed: unknown;
|
|
71
|
+
try {
|
|
72
|
+
parsed = JSON.parse(fs.readFileSync(target, 'utf-8'));
|
|
73
|
+
} catch {
|
|
74
|
+
return 'unreadable';
|
|
75
|
+
}
|
|
76
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) return 'unreadable';
|
|
77
|
+
const servers = (parsed as Record<string, unknown>)['mcpServers'];
|
|
78
|
+
if (servers === null || typeof servers !== 'object' || Array.isArray(servers)) return 'missing-entry';
|
|
79
|
+
return MCP_JSON_SERVER_KEY in (servers as Record<string, unknown>) ? 'ok' : 'missing-entry';
|
|
80
|
+
}
|
|
81
|
+
|
|
43
82
|
export function in_project_targets(projectRoot: string): Record<string, string> {
|
|
44
83
|
return {
|
|
45
84
|
cursor: path.join(projectRoot, '.cursor', 'mcp.json'),
|
|
@@ -274,7 +313,33 @@ function cmd_check(args: ParsedArgs): number {
|
|
|
274
313
|
return 1;
|
|
275
314
|
}
|
|
276
315
|
process.stdout.write('✅ All MCP targets match source.\n');
|
|
277
|
-
return
|
|
316
|
+
return _report_mcp_json(projectRoot);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* The `.mcp.json` leg of `--check`. Additive: it can only turn a green run red
|
|
321
|
+
* when the file EXISTS and has lost our entry, never on a fresh tree where the
|
|
322
|
+
* installer has not run.
|
|
323
|
+
*/
|
|
324
|
+
function _report_mcp_json(projectRoot: string): number {
|
|
325
|
+
const verdict = check_mcp_json(projectRoot);
|
|
326
|
+
const rel = path.relative(projectRoot, claude_code_mcp_target(projectRoot)) || '.mcp.json';
|
|
327
|
+
if (verdict === 'ok') {
|
|
328
|
+
process.stdout.write(`✅ ${rel} carries the \`${MCP_JSON_SERVER_KEY}\` server (containment, not equality).\n`);
|
|
329
|
+
return 0;
|
|
330
|
+
}
|
|
331
|
+
if (verdict === 'absent') {
|
|
332
|
+
process.stdout.write(
|
|
333
|
+
`ℹ️ ${rel} not present — written by \`agent-config install\` for the ` +
|
|
334
|
+
'claude-code tool, never rendered from mcp.json.\n',
|
|
335
|
+
);
|
|
336
|
+
return 0;
|
|
337
|
+
}
|
|
338
|
+
process.stderr.write(
|
|
339
|
+
`❌ ${rel} exists but does not carry the \`${MCP_JSON_SERVER_KEY}\` server ` +
|
|
340
|
+
`(${verdict}). Re-run \`agent-config install\`.\n`,
|
|
341
|
+
);
|
|
342
|
+
return 1;
|
|
278
343
|
}
|
|
279
344
|
|
|
280
345
|
class ArgError extends Error {}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fingerprint store for third-party MCP tool definitions.
|
|
3
|
+
*
|
|
4
|
+
* Step 1.1 of `road-to-mcp-runtime-integrity`. The threat is a **rug-pull**: a
|
|
5
|
+
* third-party MCP server whose tool definition is benign at connection time and
|
|
6
|
+
* mutated afterwards, so the description the model reasons about is no longer the
|
|
7
|
+
* description that was reviewed. Nothing in this tree detected that:
|
|
8
|
+
* `lint_mcp_config_security.ts` reads shipped CONFIG for supply-chain smells and
|
|
9
|
+
* never sees a definition change after connection, and `audit_mcp_tools.ts` is an
|
|
10
|
+
* inventory generator for THIS package's own consumer catalog.
|
|
11
|
+
*
|
|
12
|
+
* WHAT THIS FILE IS NOT, and the distinction is load-bearing. It is a store and a
|
|
13
|
+
* comparison, bound to **no hook slot**. The roadmap's `## The no-silent-downgrade
|
|
14
|
+
* rule` forbids shipping a `post_tool_use` or session-start variant — a check that
|
|
15
|
+
* fires after first use is a post-mortem, not a control, for a tool with
|
|
16
|
+
* irreversible side effects — and the pre-use slot is gated on a per-turn
|
|
17
|
+
* composite ceiling that is not armed (`hook-latency-budget.json`,
|
|
18
|
+
* `observe_only: true`, `p50_ci: null`). So the placement decision is deliberately
|
|
19
|
+
* absent here rather than quietly resolved: **rug-pull protection remains absent
|
|
20
|
+
* and the protection level is zero** until an owner records which side of that
|
|
21
|
+
* trade-off they chose. Wiring this module into a slot without that record is the
|
|
22
|
+
* silent downgrade the roadmap binds itself against.
|
|
23
|
+
*
|
|
24
|
+
* THIRD-PARTY ONLY. This package's own consumer catalog is already covered by
|
|
25
|
+
* `audit_mcp_tools.ts`; fingerprinting it here would produce two sources of truth
|
|
26
|
+
* for the same tools.
|
|
27
|
+
*
|
|
28
|
+
* KEYED BY SERVER PLUS TOOL NAME, so a renamed tool reads as a NEW tool rather
|
|
29
|
+
* than as a mutation of the old one. That is the difference between "this server
|
|
30
|
+
* shipped a new tool" and "this server changed a tool you already approved", and
|
|
31
|
+
* collapsing them is how a mismatch surface becomes noise.
|
|
32
|
+
*
|
|
33
|
+
* A FIRST SIGHTING IS RECORDED, NOT REPORTED. Third-party servers legitimately
|
|
34
|
+
* add tools; reporting every first sighting with the same weight as a mutation
|
|
35
|
+
* trains the reader to ignore the surface, and noise on a security surface is
|
|
36
|
+
* worse than silence because it reads as coverage (Risk 4 in the roadmap).
|
|
37
|
+
*
|
|
38
|
+
* PII-exclusion-by-construction: a record holds a server id, a tool name, a hex
|
|
39
|
+
* digest and an ISO date. There is NO field capable of holding the description
|
|
40
|
+
* text, the schema, or any free-form content — the digest is one-way and the
|
|
41
|
+
* inputs are not retained. Never widen it.
|
|
42
|
+
*/
|
|
43
|
+
import crypto from 'node:crypto';
|
|
44
|
+
import fs from 'node:fs';
|
|
45
|
+
import path from 'node:path';
|
|
46
|
+
|
|
47
|
+
import { update_json_under_lock } from './hooks/state_io.js';
|
|
48
|
+
|
|
49
|
+
/** Where the store lives, relative to the workspace root. */
|
|
50
|
+
export const FINGERPRINT_STORE = path.join(
|
|
51
|
+
'agents',
|
|
52
|
+
'runtime',
|
|
53
|
+
'state',
|
|
54
|
+
'mcp-tool-fingerprints.json',
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
export const SCHEMA_VERSION = 1;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The three fields a rug-pull can mutate to change what the model reasons about.
|
|
61
|
+
*
|
|
62
|
+
* `name` is included even though it is also half the key: a tool whose key is
|
|
63
|
+
* `server/x` and whose declared `name` later reads `y` is a mismatch, not a
|
|
64
|
+
* rename, and leaving `name` out of the digest would let that pass.
|
|
65
|
+
*/
|
|
66
|
+
export interface McpToolDefinition {
|
|
67
|
+
readonly name: string;
|
|
68
|
+
readonly description?: string;
|
|
69
|
+
readonly inputSchema?: unknown;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface FingerprintRecord {
|
|
73
|
+
/** Hex sha256 of the canonical definition. */
|
|
74
|
+
readonly digest: string;
|
|
75
|
+
/** ISO date (day resolution — a timestamp would be a behavioural fingerprint). */
|
|
76
|
+
readonly first_seen: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export type FingerprintOutcome =
|
|
80
|
+
/** No record existed. Written silently; nothing to report. */
|
|
81
|
+
| { readonly kind: 'first-sighting'; readonly key: string; readonly digest: string }
|
|
82
|
+
/** A record existed and matches. Nothing to report. */
|
|
83
|
+
| { readonly kind: 'unchanged'; readonly key: string; readonly digest: string }
|
|
84
|
+
/** A record existed and does NOT match. This is the surface. */
|
|
85
|
+
| {
|
|
86
|
+
readonly kind: 'mismatch';
|
|
87
|
+
readonly key: string;
|
|
88
|
+
readonly digest: string;
|
|
89
|
+
readonly recorded: string;
|
|
90
|
+
readonly first_seen: string;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* The store key. Server id and tool name, separated by a character neither may
|
|
95
|
+
* contain unescaped, so two distinct pairs cannot collide onto one key — the
|
|
96
|
+
* same failure `session_state_file` exists to prevent for session ids.
|
|
97
|
+
*/
|
|
98
|
+
export function fingerprintKey(server: string, toolName: string): string {
|
|
99
|
+
const esc = (s: string): string => s.replace(/%/g, '%25').replace(/\//g, '%2F');
|
|
100
|
+
return `${esc(server)}/${esc(toolName)}`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Canonical JSON with object keys sorted at every depth.
|
|
105
|
+
*
|
|
106
|
+
* Without this the digest is a fingerprint of the SERIALISER's key order rather
|
|
107
|
+
* than of the definition, and a server that reorders its schema keys between two
|
|
108
|
+
* connections would read as a rug-pull. Arrays keep their order — order is
|
|
109
|
+
* meaningful in a schema's `required` list and reordering it IS a change.
|
|
110
|
+
*/
|
|
111
|
+
export function canonicalize(value: unknown): unknown {
|
|
112
|
+
if (Array.isArray(value)) return value.map(canonicalize);
|
|
113
|
+
if (value !== null && typeof value === 'object') {
|
|
114
|
+
const out: Record<string, unknown> = {};
|
|
115
|
+
for (const k of Object.keys(value as Record<string, unknown>).sort()) {
|
|
116
|
+
out[k] = canonicalize((value as Record<string, unknown>)[k]);
|
|
117
|
+
}
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
return value;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Hex sha256 over the canonical form of the three mutable fields.
|
|
125
|
+
*
|
|
126
|
+
* `description` and `inputSchema` are normalised to `null` when absent so that
|
|
127
|
+
* "absent" and "explicitly null" hash alike — a server toggling between the two
|
|
128
|
+
* has changed nothing the model can read, and reporting it would be noise.
|
|
129
|
+
*/
|
|
130
|
+
export function fingerprintDefinition(def: McpToolDefinition): string {
|
|
131
|
+
const canonical = canonicalize({
|
|
132
|
+
name: def.name,
|
|
133
|
+
description: def.description ?? null,
|
|
134
|
+
inputSchema: def.inputSchema ?? null,
|
|
135
|
+
});
|
|
136
|
+
return crypto.createHash('sha256').update(JSON.stringify(canonical), 'utf-8').digest('hex');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
interface StoreDoc {
|
|
140
|
+
schema_version: number;
|
|
141
|
+
tools: Record<string, FingerprintRecord>;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function emptyDoc(): StoreDoc {
|
|
145
|
+
return { schema_version: SCHEMA_VERSION, tools: {} };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Read the store. A missing, unreadable or malformed store reads as empty. */
|
|
149
|
+
export function readStore(workspaceRoot: string): StoreDoc {
|
|
150
|
+
try {
|
|
151
|
+
const decoded: unknown = JSON.parse(
|
|
152
|
+
fs.readFileSync(path.join(workspaceRoot, FINGERPRINT_STORE), 'utf-8'),
|
|
153
|
+
);
|
|
154
|
+
if (typeof decoded === 'object' && decoded !== null && !Array.isArray(decoded)) {
|
|
155
|
+
const tools = (decoded as Record<string, unknown>)['tools'];
|
|
156
|
+
return {
|
|
157
|
+
schema_version: SCHEMA_VERSION,
|
|
158
|
+
tools:
|
|
159
|
+
typeof tools === 'object' && tools !== null && !Array.isArray(tools)
|
|
160
|
+
? (tools as Record<string, FingerprintRecord>)
|
|
161
|
+
: {},
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
} catch {
|
|
165
|
+
// Absent or malformed both mean "nothing recorded", and the outcome that
|
|
166
|
+
// follows is `first-sighting` — which reports nothing. A corrupt store
|
|
167
|
+
// must never be read as a mismatch: that would turn one bad write into a
|
|
168
|
+
// rug-pull alert on every tool at once.
|
|
169
|
+
}
|
|
170
|
+
return emptyDoc();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Record `def` for `server`, and say what the record implies.
|
|
175
|
+
*
|
|
176
|
+
* Read-modify-write under ONE lock, not three steps: two hosts connected to the
|
|
177
|
+
* same workspace fingerprint concurrently, and `atomic_write_json` makes the
|
|
178
|
+
* publish atomic while leaving load -> merge -> publish racy — which loses a
|
|
179
|
+
* whole tool's record rather than merely an increment, because this mutator
|
|
180
|
+
* republishes the `tools` map. Same primitive and same reason as
|
|
181
|
+
* `rule-trips.json`.
|
|
182
|
+
*
|
|
183
|
+
* `today` is injected rather than read from the clock so a caller (and a test)
|
|
184
|
+
* can pin it; nothing in this module reads wall time on its own.
|
|
185
|
+
*/
|
|
186
|
+
export function recordFingerprint(
|
|
187
|
+
workspaceRoot: string,
|
|
188
|
+
server: string,
|
|
189
|
+
def: McpToolDefinition,
|
|
190
|
+
today: string,
|
|
191
|
+
): FingerprintOutcome {
|
|
192
|
+
const key = fingerprintKey(server, def.name);
|
|
193
|
+
const digest = fingerprintDefinition(def);
|
|
194
|
+
let outcome: FingerprintOutcome = { kind: 'first-sighting', key, digest };
|
|
195
|
+
|
|
196
|
+
update_json_under_lock<StoreDoc>(path.join(workspaceRoot, FINGERPRINT_STORE), (loaded) => {
|
|
197
|
+
const doc: StoreDoc = { ...emptyDoc(), ...((loaded ?? {}) as Partial<StoreDoc>) };
|
|
198
|
+
const tools: Record<string, FingerprintRecord> =
|
|
199
|
+
typeof doc.tools === 'object' && doc.tools !== null && !Array.isArray(doc.tools)
|
|
200
|
+
? { ...doc.tools }
|
|
201
|
+
: {};
|
|
202
|
+
const prior = tools[key];
|
|
203
|
+
if (prior === undefined) {
|
|
204
|
+
tools[key] = { digest, first_seen: today };
|
|
205
|
+
outcome = { kind: 'first-sighting', key, digest };
|
|
206
|
+
} else if (prior.digest === digest) {
|
|
207
|
+
outcome = { kind: 'unchanged', key, digest };
|
|
208
|
+
} else {
|
|
209
|
+
// The recorded digest is NOT overwritten. A mismatch is a surface for
|
|
210
|
+
// a human decision, not a verdict, and silently re-baselining would
|
|
211
|
+
// make the second read of a mutated tool report `unchanged` — which
|
|
212
|
+
// is the one outcome a rug-pull must never produce.
|
|
213
|
+
outcome = {
|
|
214
|
+
kind: 'mismatch',
|
|
215
|
+
key,
|
|
216
|
+
digest,
|
|
217
|
+
recorded: prior.digest,
|
|
218
|
+
first_seen: prior.first_seen,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
return { schema_version: SCHEMA_VERSION, tools };
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
return outcome;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Human-readable line for a mismatch, and nothing for the other two outcomes.
|
|
229
|
+
*
|
|
230
|
+
* Returning `null` rather than an empty string is deliberate: a caller that
|
|
231
|
+
* prints whatever it gets back cannot accidentally emit a blank line for a
|
|
232
|
+
* first sighting, which is the shape of "reported silently".
|
|
233
|
+
*/
|
|
234
|
+
export function describeOutcome(outcome: FingerprintOutcome): string | null {
|
|
235
|
+
if (outcome.kind !== 'mismatch') return null;
|
|
236
|
+
return (
|
|
237
|
+
`mcp tool definition changed since it was first recorded: ${outcome.key} ` +
|
|
238
|
+
`(recorded ${outcome.recorded.slice(0, 12)} on ${outcome.first_seen}, ` +
|
|
239
|
+
`now ${outcome.digest.slice(0, 12)}) — review the change before using this tool`
|
|
240
|
+
);
|
|
241
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Standing R3-rate metric over recently merged changes.
|
|
4
|
+
*
|
|
5
|
+
* Step 3.1 of `road-to-target-project-assurance-readiness`, and it exists because of
|
|
6
|
+
* Risk 2 rather than for its own sake: `classify_change_risk` resolves ties and
|
|
7
|
+
* unknowns **upward**, which is safe per change and corrosive in aggregate — once
|
|
8
|
+
* most changes read R3, the owed gate set is noise and gets worked around rather
|
|
9
|
+
* than met. The roadmap's mitigation is explicit about where the defect would live:
|
|
10
|
+
* **> 40 % of changes classifying R3 is a defect in the override list**, not in the
|
|
11
|
+
* people meeting the gates. This is the instrument that can say so.
|
|
12
|
+
*
|
|
13
|
+
* WHAT IT CANNOT REPORT, and the null is recorded per row rather than omitted.
|
|
14
|
+
* The step asks for `agreement` and `r3_recall` alongside the rate. Both are
|
|
15
|
+
* measured against a **human-labelled** corpus, and step 0.2 recorded that no human
|
|
16
|
+
* labeller is reachable — so those two fields are written as `null` with
|
|
17
|
+
* `null_reason` naming why. Writing them absent would make a later reader unable to
|
|
18
|
+
* distinguish "not measured" from "measured as zero", which on a recall metric is
|
|
19
|
+
* the difference between no data and total failure.
|
|
20
|
+
*
|
|
21
|
+
* NIGHTLY, NOT PER-CHANGE. The classifier runs in-session per change; the
|
|
22
|
+
* *measurement* runs on a schedule. A per-PR trigger would make the metric a gate,
|
|
23
|
+
* and Phase 2 — the phase that gives the class authority — is cancelled on the
|
|
24
|
+
* pre-registered null route precisely because the class is unvalidated.
|
|
25
|
+
*/
|
|
26
|
+
import { spawnSync } from 'node:child_process';
|
|
27
|
+
import * as fs from 'node:fs';
|
|
28
|
+
import * as path from 'node:path';
|
|
29
|
+
import process from 'node:process';
|
|
30
|
+
import { fileURLToPath } from 'node:url';
|
|
31
|
+
|
|
32
|
+
import { type RiskClass, classifyPaths } from './classify_change_risk.js';
|
|
33
|
+
|
|
34
|
+
const _HERE = fileURLToPath(import.meta.url);
|
|
35
|
+
const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
36
|
+
export const DRIFT_LEDGER = path.join('agents', 'evidence', 'risk-classifier-drift.jsonl');
|
|
37
|
+
|
|
38
|
+
/** The rate above which the override list — not the reviewer — is the defect. */
|
|
39
|
+
export const R3_RATE_DEFECT_THRESHOLD = 0.4;
|
|
40
|
+
|
|
41
|
+
export interface DriftRow {
|
|
42
|
+
measured_at: string;
|
|
43
|
+
window_days: number;
|
|
44
|
+
commits: number;
|
|
45
|
+
counts: Record<RiskClass, number>;
|
|
46
|
+
r3_rate: number | null;
|
|
47
|
+
r3_rate_over_threshold: boolean | null;
|
|
48
|
+
agreement: null;
|
|
49
|
+
r3_recall: null;
|
|
50
|
+
null_reason: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** One entry per merged commit in the window: its sha and its touched paths. */
|
|
54
|
+
export function commitsInWindow(days: number, cwd = REPO_ROOT): { sha: string; paths: string[] }[] {
|
|
55
|
+
const log = spawnSync(
|
|
56
|
+
'git',
|
|
57
|
+
['log', '--first-parent', `--since=${String(days)} days ago`, '--format=%H'],
|
|
58
|
+
{ cwd, encoding: 'utf-8', maxBuffer: 64 * 1024 * 1024 },
|
|
59
|
+
);
|
|
60
|
+
if (log.status !== 0) return [];
|
|
61
|
+
const shas = (log.stdout || '').split('\n').map((s) => s.trim()).filter(Boolean);
|
|
62
|
+
const out: { sha: string; paths: string[] }[] = [];
|
|
63
|
+
for (const sha of shas) {
|
|
64
|
+
// `-m --first-parent` so a merge commit reports the paths it BROUGHT IN
|
|
65
|
+
// rather than nothing at all — a merge-heavy history would otherwise read
|
|
66
|
+
// as a stream of empty diffs, and an empty diff classifies R3 by the
|
|
67
|
+
// upward rule, which would invent an R3 rate of 1.0 out of bookkeeping.
|
|
68
|
+
const d = spawnSync('git', ['show', '--pretty=', '--name-only', '-m', '--first-parent', sha], {
|
|
69
|
+
cwd,
|
|
70
|
+
encoding: 'utf-8',
|
|
71
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
72
|
+
});
|
|
73
|
+
const paths = (d.stdout || '').split('\n').map((s) => s.trim()).filter(Boolean);
|
|
74
|
+
if (paths.length > 0) out.push({ sha, paths });
|
|
75
|
+
}
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function buildRow(
|
|
80
|
+
commits: readonly { sha: string; paths: string[] }[],
|
|
81
|
+
measured_at: string,
|
|
82
|
+
window_days: number,
|
|
83
|
+
): DriftRow {
|
|
84
|
+
const counts: Record<RiskClass, number> = { R0: 0, R1: 0, R2: 0, R3: 0 };
|
|
85
|
+
for (const c of commits) counts[classifyPaths(c.paths).cls] += 1;
|
|
86
|
+
const n = commits.length;
|
|
87
|
+
const r3_rate = n === 0 ? null : counts.R3 / n;
|
|
88
|
+
return {
|
|
89
|
+
measured_at,
|
|
90
|
+
window_days,
|
|
91
|
+
commits: n,
|
|
92
|
+
counts,
|
|
93
|
+
r3_rate,
|
|
94
|
+
// `null` rather than `false` on an empty window: no commits is not evidence
|
|
95
|
+
// that the rate is acceptable.
|
|
96
|
+
r3_rate_over_threshold: r3_rate === null ? null : r3_rate > R3_RATE_DEFECT_THRESHOLD,
|
|
97
|
+
agreement: null,
|
|
98
|
+
r3_recall: null,
|
|
99
|
+
null_reason:
|
|
100
|
+
'no human-labelled corpus: step 0.2 recorded no reachable labeller, so ' +
|
|
101
|
+
'agreement and r3_recall have no reference standard to be measured against. ' +
|
|
102
|
+
'Reopens per agents/evidence/risk-classifier-prereg.md § Re-open threshold.',
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function main(argv: readonly string[]): number {
|
|
107
|
+
const wIdx = argv.indexOf('--window-days');
|
|
108
|
+
const window_days = wIdx >= 0 ? Number(argv[wIdx + 1] ?? '30') : 30;
|
|
109
|
+
const nowIdx = argv.indexOf('--now');
|
|
110
|
+
// Injected rather than read from the clock so a test can pin the row.
|
|
111
|
+
const measured_at = nowIdx >= 0 ? String(argv[nowIdx + 1]) : new Date().toISOString();
|
|
112
|
+
const dry = argv.includes('--dry-run');
|
|
113
|
+
|
|
114
|
+
const commits = commitsInWindow(window_days);
|
|
115
|
+
const row = buildRow(commits, measured_at, window_days);
|
|
116
|
+
const line = JSON.stringify(row);
|
|
117
|
+
|
|
118
|
+
if (dry) {
|
|
119
|
+
process.stdout.write(line + '\n');
|
|
120
|
+
return 0;
|
|
121
|
+
}
|
|
122
|
+
const target = path.join(REPO_ROOT, DRIFT_LEDGER);
|
|
123
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
124
|
+
fs.appendFileSync(target, line + '\n', 'utf-8');
|
|
125
|
+
process.stdout.write(
|
|
126
|
+
`risk-class drift: ${String(row.commits)} commit(s) over ${String(window_days)}d — ` +
|
|
127
|
+
`R3 ${String(row.counts.R3)} (rate ${row.r3_rate === null ? 'n/a' : row.r3_rate.toFixed(3)})` +
|
|
128
|
+
`${row.r3_rate_over_threshold === true ? ' ⚠️ OVER THRESHOLD — the override list is the defect' : ''}\n`,
|
|
129
|
+
);
|
|
130
|
+
return 0;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function _isCliEntry(): boolean {
|
|
134
|
+
const invoked = process.argv[1];
|
|
135
|
+
return invoked !== undefined && path.resolve(invoked) === path.resolve(_HERE);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (_isCliEntry()) {
|
|
139
|
+
process.exit(main(process.argv.slice(2)));
|
|
140
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* measure_skill_ranker_baseline — the number Phase 3 has to beat.
|
|
4
|
+
*
|
|
5
|
+
* `road-to-skill-delivery-over-mcp` Phase 0.3 asked for a top-1 / top-3 hit
|
|
6
|
+
* rate for the incumbent keyword ranker, "over the 496-line corpus … against
|
|
7
|
+
* the corpus's expected skill per line". Building it surfaced two defects in
|
|
8
|
+
* that premise, and this file is written around them rather than over them.
|
|
9
|
+
*
|
|
10
|
+
* DEFECT A — the 496-line corpus has no expected SKILL. `tests/eval/routing-matrix/`
|
|
11
|
+
* is keyed by `rule:` and every prompt is labelled with the RULE it should
|
|
12
|
+
* activate. Scoring a skill ranker against it would need a skill label that
|
|
13
|
+
* does not exist there, so a "hit rate" over it would be invented.
|
|
14
|
+
*
|
|
15
|
+
* DEFECT B — 496 is stale. The matrix holds 499 prompts today
|
|
16
|
+
* (`grep -h "^\s*- prompt:" tests/eval/routing-matrix/*.yaml | wc -l`), and 496
|
|
17
|
+
* is still published in six places including `hook-token-budget.json`.
|
|
18
|
+
*
|
|
19
|
+
* So the baseline is measured against the corpus that DOES carry expected
|
|
20
|
+
* skills — `tests/eval/corpus-{dev,non-dev}.yaml`, 26 labelled prompts — and
|
|
21
|
+
* the routing matrix is reported alongside it as what it can honestly be: a
|
|
22
|
+
* COVERAGE measurement (does the ranker return anything at all, and how
|
|
23
|
+
* confident) with no ground truth and therefore no accuracy.
|
|
24
|
+
*
|
|
25
|
+
* Both halves are emitted in one JSON object so a later ranker revision can be
|
|
26
|
+
* compared on the same two axes in one run:
|
|
27
|
+
*
|
|
28
|
+
* ./scripts-run src/scripts/measure_skill_ranker_baseline --json
|
|
29
|
+
* ./scripts-run src/scripts/measure_skill_ranker_baseline --ranker keyword-v2 --json
|
|
30
|
+
*
|
|
31
|
+
* A 26-prompt denominator is small and is stated as such in the output. It is
|
|
32
|
+
* the whole labelled ground truth this repository has.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import * as fs from 'node:fs';
|
|
36
|
+
import * as path from 'node:path';
|
|
37
|
+
import { fileURLToPath } from 'node:url';
|
|
38
|
+
|
|
39
|
+
import { rank } from './skill_tools/score_skill_relevance.js';
|
|
40
|
+
import type { RankOptions } from '../shared/skillRanking.js';
|
|
41
|
+
|
|
42
|
+
export const REPO = path.resolve(fileURLToPath(import.meta.url), '..', '..', '..');
|
|
43
|
+
const LABELLED_CORPORA = ['tests/eval/corpus-dev.yaml', 'tests/eval/corpus-non-dev.yaml'];
|
|
44
|
+
const MATRIX_DIR = 'tests/eval/routing-matrix';
|
|
45
|
+
/** Where the projected skills live in this checkout. */
|
|
46
|
+
export const SKILLS_DIR = path.join(REPO, 'src', 'skills');
|
|
47
|
+
|
|
48
|
+
export interface LabelledPrompt {
|
|
49
|
+
id: string;
|
|
50
|
+
corpus: string;
|
|
51
|
+
prompt: string;
|
|
52
|
+
expected: string[];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Minimal reader for the two eval corpora. They are hand-written YAML with a
|
|
57
|
+
* fixed two-space list shape; a full YAML parse is avoided so this script has
|
|
58
|
+
* the same zero-dependency profile as the ranker it measures.
|
|
59
|
+
*/
|
|
60
|
+
export function readLabelledPrompts(repo = REPO): LabelledPrompt[] {
|
|
61
|
+
const out: LabelledPrompt[] = [];
|
|
62
|
+
for (const rel of LABELLED_CORPORA) {
|
|
63
|
+
const file = path.join(repo, rel);
|
|
64
|
+
if (!fs.existsSync(file)) continue;
|
|
65
|
+
const corpus = path.basename(rel, '.yaml');
|
|
66
|
+
let id = '';
|
|
67
|
+
let prompt = '';
|
|
68
|
+
for (const raw of fs.readFileSync(file, 'utf8').split('\n')) {
|
|
69
|
+
const line = raw.replace(/\r$/, '');
|
|
70
|
+
if (/^\s*#/.test(line)) continue;
|
|
71
|
+
const mId = /^\s*-\s+id:\s*(\S+)\s*$/.exec(line);
|
|
72
|
+
if (mId) {
|
|
73
|
+
id = mId[1]!;
|
|
74
|
+
prompt = '';
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const mPrompt = /^\s*prompt:\s*"(.*)"\s*$/.exec(line);
|
|
78
|
+
if (mPrompt) {
|
|
79
|
+
prompt = mPrompt[1]!;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const mExp = /^\s*expected_skills:\s*\[(.*)\]\s*$/.exec(line);
|
|
83
|
+
if (mExp && id && prompt) {
|
|
84
|
+
const expected = mExp[1]!
|
|
85
|
+
.split(',')
|
|
86
|
+
.map((s) => s.trim())
|
|
87
|
+
.filter(Boolean);
|
|
88
|
+
out.push({ id, corpus, prompt, expected });
|
|
89
|
+
id = '';
|
|
90
|
+
prompt = '';
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return out;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Every `- prompt:` string in the rule routing matrix. No skill label exists. */
|
|
98
|
+
export function readMatrixPrompts(repo = REPO): string[] {
|
|
99
|
+
const dir = path.join(repo, MATRIX_DIR);
|
|
100
|
+
if (!fs.existsSync(dir)) return [];
|
|
101
|
+
const out: string[] = [];
|
|
102
|
+
for (const name of fs.readdirSync(dir).sort()) {
|
|
103
|
+
if (!name.endsWith('.yaml')) continue;
|
|
104
|
+
for (const raw of fs.readFileSync(path.join(dir, name), 'utf8').split('\n')) {
|
|
105
|
+
const m = /^\s*-?\s*prompt:\s*"(.*)"\s*$/.exec(raw.replace(/\r$/, ''));
|
|
106
|
+
if (m && m[1]) out.push(m[1]);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return out;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface AccuracyArm {
|
|
113
|
+
corpus_prompts: number;
|
|
114
|
+
top1: number;
|
|
115
|
+
top3: number;
|
|
116
|
+
misses: string[];
|
|
117
|
+
denominator_note: string;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export interface CoverageArm {
|
|
121
|
+
corpus_prompts: number;
|
|
122
|
+
prompts_with_any_result: number;
|
|
123
|
+
mean_top_score: number;
|
|
124
|
+
note: string;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface RankerBaseline {
|
|
128
|
+
schema: 1;
|
|
129
|
+
ranker: string;
|
|
130
|
+
commit: string;
|
|
131
|
+
skills_dir: string;
|
|
132
|
+
skills_indexed: number;
|
|
133
|
+
accuracy: AccuracyArm;
|
|
134
|
+
matrix_coverage: CoverageArm;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function hitAt(rows: readonly (readonly [string, number, string[]])[], n: number, expected: readonly string[]): boolean {
|
|
138
|
+
const top = rows.slice(0, n).map((r) => r[0]);
|
|
139
|
+
return expected.some((e) => top.includes(e));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function measure(opts: { ranker: string; commit: string; skillsDir?: string; repo?: string }): RankerBaseline {
|
|
143
|
+
const repo = opts.repo ?? REPO;
|
|
144
|
+
const skillsDir = opts.skillsDir ?? SKILLS_DIR;
|
|
145
|
+
// `keyword-v2` is Phase 3.1: the same formula with `triggers:` prose folded
|
|
146
|
+
// into each skill's term source. Any other label measures v1.
|
|
147
|
+
const rankOpts: RankOptions = opts.ranker === 'keyword-v2' ? { includeTriggers: true } : {};
|
|
148
|
+
const labelled = readLabelledPrompts(repo);
|
|
149
|
+
let top1 = 0;
|
|
150
|
+
let top3 = 0;
|
|
151
|
+
const misses: string[] = [];
|
|
152
|
+
for (const p of labelled) {
|
|
153
|
+
const rows = rank(p.prompt, skillsDir, rankOpts);
|
|
154
|
+
if (hitAt(rows, 1, p.expected)) top1++;
|
|
155
|
+
if (hitAt(rows, 3, p.expected)) top3++;
|
|
156
|
+
else misses.push(p.id);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const matrix = readMatrixPrompts(repo);
|
|
160
|
+
let withResult = 0;
|
|
161
|
+
let scoreSum = 0;
|
|
162
|
+
for (const prompt of matrix) {
|
|
163
|
+
const rows = rank(prompt, skillsDir, rankOpts);
|
|
164
|
+
if (rows.length > 0) {
|
|
165
|
+
withResult++;
|
|
166
|
+
scoreSum += rows[0]![1];
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const skillsIndexed = fs.existsSync(skillsDir)
|
|
171
|
+
? fs.readdirSync(skillsDir).filter((s) => fs.existsSync(path.join(skillsDir, s, 'SKILL.md'))).length
|
|
172
|
+
: 0;
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
schema: 1,
|
|
176
|
+
ranker: opts.ranker,
|
|
177
|
+
commit: opts.commit,
|
|
178
|
+
skills_dir: path.relative(repo, skillsDir),
|
|
179
|
+
skills_indexed: skillsIndexed,
|
|
180
|
+
accuracy: {
|
|
181
|
+
corpus_prompts: labelled.length,
|
|
182
|
+
top1: labelled.length ? Math.round((top1 / labelled.length) * 1000) / 1000 : 0,
|
|
183
|
+
top3: labelled.length ? Math.round((top3 / labelled.length) * 1000) / 1000 : 0,
|
|
184
|
+
misses,
|
|
185
|
+
denominator_note:
|
|
186
|
+
'tests/eval/corpus-dev.yaml + corpus-non-dev.yaml — the only expected-skill ground ' +
|
|
187
|
+
'truth in this tree. Small; a single prompt moves each rate by ~4 points.',
|
|
188
|
+
},
|
|
189
|
+
matrix_coverage: {
|
|
190
|
+
corpus_prompts: matrix.length,
|
|
191
|
+
prompts_with_any_result: withResult,
|
|
192
|
+
mean_top_score: withResult ? Math.round((scoreSum / withResult) * 100) / 100 : 0,
|
|
193
|
+
note:
|
|
194
|
+
'tests/eval/routing-matrix/ is labelled with the expected RULE, never a skill, so ' +
|
|
195
|
+
'this arm carries no accuracy — only whether the ranker answers at all and how ' +
|
|
196
|
+
'confident its top answer is. Its size is measured here, not assumed: the roadmap ' +
|
|
197
|
+
'and five other sites still publish 496.',
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function main(argv: readonly string[]): number {
|
|
203
|
+
const ranker = argv.includes('--ranker') ? (argv[argv.indexOf('--ranker') + 1] ?? 'keyword-v1') : 'keyword-v1';
|
|
204
|
+
const commit = argv.includes('--commit') ? (argv[argv.indexOf('--commit') + 1] ?? 'unknown') : 'unknown';
|
|
205
|
+
const out = measure({ ranker, commit });
|
|
206
|
+
process.stdout.write(`${JSON.stringify(out, null, 2)}\n`);
|
|
207
|
+
return 0;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1])) {
|
|
211
|
+
process.exit(main(process.argv.slice(2)));
|
|
212
|
+
}
|