@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
|
@@ -0,0 +1,663 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A `stable` adapter tier must resolve to evidence a reviewer can reach.
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS EXISTS
|
|
5
|
+
* ---------------
|
|
6
|
+
* Seven media adapters carry `# Lifecycle: stable — promoted 2026-06-10
|
|
7
|
+
* (maintainer-authorized)`. `docs/contracts/provider-lifecycle.md` says
|
|
8
|
+
* promotion "requires a maintainer-captured real-API smoke trace under
|
|
9
|
+
* `agents/reference/ai-video/smoke-traces/`" — and `git ls-files` over that
|
|
10
|
+
* path returns **0**. The traces were captured and then deliberately withheld
|
|
11
|
+
* (`d7f5d5d3c`, 2026-06-10, "smoke-traces fully local-only"), a decision
|
|
12
|
+
* reaffirmed by AI council on 2026-08-23. So the evidence is real and the
|
|
13
|
+
* CLAIM was uncheckable: a reviewer holding a clone had nothing to look at,
|
|
14
|
+
* and nothing stopped the next adapter from writing `stable` with no evidence
|
|
15
|
+
* at all.
|
|
16
|
+
*
|
|
17
|
+
* `agents/evidence/ai-video/trace-index.json` (written by
|
|
18
|
+
* `smoke-trace.sh index`) is the five-field projection that makes the claim
|
|
19
|
+
* checkable without publishing one request body. This gate is what makes the
|
|
20
|
+
* index load-bearing rather than decorative.
|
|
21
|
+
*
|
|
22
|
+
* cache-invalidation: the index carries no `v<N>` path segment on purpose. It
|
|
23
|
+
* is regenerated WHOLESALE from the local trace directory on every
|
|
24
|
+
* `smoke-trace.sh index` run — never merged into, never appended to — so there
|
|
25
|
+
* is no window in which a reader can hold a file written under an older shape.
|
|
26
|
+
* A version namespace exists to stop a stale cache being read after its
|
|
27
|
+
* producer changed; here the producer rewrites the whole file, this gate reads
|
|
28
|
+
* it in the same commit, and the five-field shape is asserted directly by
|
|
29
|
+
* `tests/scripts/ai_video_trace_index.test.ts`. Adding `v1/` would create a
|
|
30
|
+
* second path to keep in sync and buy nothing.
|
|
31
|
+
*
|
|
32
|
+
* WHAT IT CHECKS — and the limit, stated rather than implied
|
|
33
|
+
* ---------------------------------------------------------
|
|
34
|
+
* 1. Every adapter header reading `stable` has at least one index row for its
|
|
35
|
+
* provider whose `captured_at` is within {@link RECHECK_WINDOW_DAYS}.
|
|
36
|
+
* 2. Every `smoke_trace` id referenced by a model-capability manifest resolves
|
|
37
|
+
* to a row. A dangling reference is a claim pointing at nothing.
|
|
38
|
+
*
|
|
39
|
+
* **It cannot check that the trace was a LIVE, SUCCESSFUL round-trip.** The
|
|
40
|
+
* index carries five fields by design — provider, trace_id, captured_at,
|
|
41
|
+
* model, sha256 — and `mode` and `success` are not among them, because adding
|
|
42
|
+
* fields to reach a stronger check is how a five-field allowlist becomes a
|
|
43
|
+
* body copy. So a dry-run trace satisfies rule 1. That is a real weakness and
|
|
44
|
+
* it is written here instead of behind a stronger-sounding success line: the
|
|
45
|
+
* per-model `verified: true` + `smoke_trace` pair in the manifests is the
|
|
46
|
+
* stronger signal, and rule 2 is what keeps it honest.
|
|
47
|
+
*
|
|
48
|
+
* The staleness window is ONE constant, shared with the `recheck_by` stamp
|
|
49
|
+
* `smoke-trace.sh` writes into every trace, so the two cannot drift apart.
|
|
50
|
+
*
|
|
51
|
+
* COST CALIBRATION (roadmap step 2.3) — warn, never fail
|
|
52
|
+
* -----------------------------------------------------
|
|
53
|
+
* `--cost-diff <base-ref>` warns when a manifest's `cost_per_second_usd`
|
|
54
|
+
* changed without a citation of the cost ledger in the same diff. A WARNING,
|
|
55
|
+
* because a modeled estimate is allowed to be re-modeled by a human; what is
|
|
56
|
+
* not allowed is doing it silently after money was already spent measuring the
|
|
57
|
+
* real figure.
|
|
58
|
+
*
|
|
59
|
+
* Modes:
|
|
60
|
+
* (bare) gate over the real tree
|
|
61
|
+
* --quiet verdict line only
|
|
62
|
+
* --table emit the generated tier table for the contract
|
|
63
|
+
* --cost-diff <base-ref> additionally warn on uncited manifest cost changes
|
|
64
|
+
* --root <dir> drive a fixture tree (tests)
|
|
65
|
+
*
|
|
66
|
+
* Exit 0 = every stable claim resolves. 1 = a claim does not.
|
|
67
|
+
* 2 = the gate could not run (no index, empty corpus).
|
|
68
|
+
*/
|
|
69
|
+
import { execFileSync } from 'node:child_process';
|
|
70
|
+
import * as fs from 'node:fs';
|
|
71
|
+
import * as path from 'node:path';
|
|
72
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
73
|
+
|
|
74
|
+
import * as os from 'node:os';
|
|
75
|
+
|
|
76
|
+
import { asOf } from './_lib/as_of.js';
|
|
77
|
+
import { GateLedger, LedgerUsageError } from './_lib/gate_ledger.js';
|
|
78
|
+
import { runGateCli, runSelfTest, type SelfTestCase } from './_lib/gate_self_test.js';
|
|
79
|
+
import { DeadScopeError, reportScanned } from './_lib/scan_scope.js';
|
|
80
|
+
|
|
81
|
+
const _HERE = fileURLToPath(import.meta.url);
|
|
82
|
+
const GATE = 'lint_adapter_tier';
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* How long a smoke trace stands as evidence.
|
|
86
|
+
*
|
|
87
|
+
* 180 days is a STATED DEFAULT, not a measured optimum, and the honest reason
|
|
88
|
+
* to have any window at all is documented in the roadmap's own risk register:
|
|
89
|
+
* vendor endpoints have gained and lost frame conditioning inside weeks, so an
|
|
90
|
+
* undated claim is a claim about the past presented as the present.
|
|
91
|
+
* `AIV_TRACE_RECHECK_DAYS` overrides it in `smoke-trace.sh`; both read the
|
|
92
|
+
* same number so a change moves both sides.
|
|
93
|
+
*
|
|
94
|
+
* Revisit-if: a provider is observed changing a capability inside the window,
|
|
95
|
+
* or two consecutive re-captures find nothing changed.
|
|
96
|
+
*/
|
|
97
|
+
export const RECHECK_WINDOW_DAYS = 180;
|
|
98
|
+
|
|
99
|
+
export const ADAPTER_DIRS = [
|
|
100
|
+
path.join('src', 'scripts', 'ai-video', 'adapters'),
|
|
101
|
+
path.join('src', 'scripts', 'ai-image', 'adapters'),
|
|
102
|
+
] as const;
|
|
103
|
+
export const MANIFEST_DIR = path.join('src', 'scripts', 'ai-video', 'lib', 'model-capabilities');
|
|
104
|
+
// cache-invalidation: regenerated wholesale by `smoke-trace.sh index` on every
|
|
105
|
+
// run, so no version namespace is needed — see the module docstring for why.
|
|
106
|
+
export const INDEX_PATH = path.join('agents', 'evidence', 'ai-video', 'trace-index.json');
|
|
107
|
+
|
|
108
|
+
export interface IndexRow {
|
|
109
|
+
provider: string;
|
|
110
|
+
trace_id: string;
|
|
111
|
+
captured_at: string;
|
|
112
|
+
model: string | null;
|
|
113
|
+
sha256: string;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface Finding {
|
|
117
|
+
adapter: string;
|
|
118
|
+
reason: string;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface AdapterTier {
|
|
122
|
+
adapter: string;
|
|
123
|
+
domain: string;
|
|
124
|
+
tier: string;
|
|
125
|
+
file: string;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Parse `# Lifecycle: <tier>` out of an adapter header.
|
|
130
|
+
*
|
|
131
|
+
* Returns `null` when the header is absent — which the caller treats as a
|
|
132
|
+
* finding for a would-be `stable` claim, never as a pass.
|
|
133
|
+
*/
|
|
134
|
+
export function parseTier(source: string): string | null {
|
|
135
|
+
const m = /^#\s*Lifecycle:\s*([a-z]+)/m.exec(source);
|
|
136
|
+
return m?.[1] ?? null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* A capture stamp is filesystem-safe (`2026-06-10T12-36-49Z`), so the two
|
|
141
|
+
* time separators have to come back before `Date` will read it. An
|
|
142
|
+
* unparseable stamp returns `null` and is reported — never silently treated
|
|
143
|
+
* as fresh, which would make a malformed row the easiest way to pass.
|
|
144
|
+
*/
|
|
145
|
+
export function parseStamp(stamp: string): Date | null {
|
|
146
|
+
const iso = stamp.replace(/T(\d{2})-(\d{2})-(\d{2})Z$/, 'T$1:$2:$3Z');
|
|
147
|
+
const d = new Date(iso);
|
|
148
|
+
return Number.isNaN(d.getTime()) ? null : d;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function ageInDays(stamp: string, now = asOf()): number | null {
|
|
152
|
+
const d = parseStamp(stamp);
|
|
153
|
+
if (d === null) return null;
|
|
154
|
+
return (now.getTime() - d.getTime()) / 86_400_000;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Every adapter in the tree with its parsed tier. */
|
|
158
|
+
export function collectAdapters(root: string): AdapterTier[] {
|
|
159
|
+
const out: AdapterTier[] = [];
|
|
160
|
+
for (const rel of ADAPTER_DIRS) {
|
|
161
|
+
const dir = path.join(root, rel);
|
|
162
|
+
if (!fs.existsSync(dir)) continue;
|
|
163
|
+
for (const name of fs.readdirSync(dir).sort()) {
|
|
164
|
+
if (!name.endsWith('.sh')) continue;
|
|
165
|
+
const file = path.join(rel, name);
|
|
166
|
+
const tier = parseTier(fs.readFileSync(path.join(root, file), 'utf-8'));
|
|
167
|
+
out.push({
|
|
168
|
+
adapter: name.replace(/\.sh$/, ''),
|
|
169
|
+
domain: path.basename(path.dirname(rel)),
|
|
170
|
+
tier: tier ?? 'unknown',
|
|
171
|
+
file,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return out;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function check(
|
|
179
|
+
root: string,
|
|
180
|
+
opts: { now?: Date; quiet?: boolean } = {},
|
|
181
|
+
): { code: number; findings: Finding[]; scanned: number; adapters: AdapterTier[] } {
|
|
182
|
+
const now = opts.now ?? asOf();
|
|
183
|
+
const findings: Finding[] = [];
|
|
184
|
+
const adapters = collectAdapters(root);
|
|
185
|
+
|
|
186
|
+
const indexAbs = path.join(root, INDEX_PATH);
|
|
187
|
+
if (!fs.existsSync(indexAbs)) {
|
|
188
|
+
process.stderr.write(
|
|
189
|
+
`❌ ${GATE}: trace index not found at ${INDEX_PATH}.\n` +
|
|
190
|
+
` Regenerate it with:\n` +
|
|
191
|
+
` bash src/scripts/ai-video/smoke-trace.sh index\n` +
|
|
192
|
+
` Exit 2, not 0: with no index every stable claim is unverifiable, which is\n` +
|
|
193
|
+
` the condition this gate exists to refuse — not a clean tree.\n`,
|
|
194
|
+
);
|
|
195
|
+
return { code: 2, findings, scanned: adapters.length, adapters };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
let rows: IndexRow[];
|
|
199
|
+
try {
|
|
200
|
+
rows = JSON.parse(fs.readFileSync(indexAbs, 'utf-8')) as IndexRow[];
|
|
201
|
+
if (!Array.isArray(rows)) throw new Error('not an array');
|
|
202
|
+
} catch (err) {
|
|
203
|
+
process.stderr.write(`❌ ${GATE}: ${INDEX_PATH} is not a readable row array (${String(err)}).\n`);
|
|
204
|
+
return { code: 2, findings, scanned: adapters.length, adapters };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
reportScanned({
|
|
209
|
+
gate: GATE,
|
|
210
|
+
scanned: adapters.length,
|
|
211
|
+
units: 'adapter header(s)',
|
|
212
|
+
roots: [...ADAPTER_DIRS],
|
|
213
|
+
});
|
|
214
|
+
} catch (err) {
|
|
215
|
+
if (err instanceof DeadScopeError) {
|
|
216
|
+
process.stderr.write(`❌ ${err.message}\n`);
|
|
217
|
+
return { code: 2, findings, scanned: adapters.length, adapters };
|
|
218
|
+
}
|
|
219
|
+
throw err;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const byProvider = new Map<string, IndexRow[]>();
|
|
223
|
+
for (const r of rows) {
|
|
224
|
+
const list = byProvider.get(r.provider) ?? [];
|
|
225
|
+
list.push(r);
|
|
226
|
+
byProvider.set(r.provider, list);
|
|
227
|
+
}
|
|
228
|
+
const traceIds = new Set(rows.map((r) => r.trace_id));
|
|
229
|
+
|
|
230
|
+
const ledger = new GateLedger(GATE);
|
|
231
|
+
try {
|
|
232
|
+
// One planned unit per adapter header, plus one per manifest reference:
|
|
233
|
+
// both are claims, and a claim that is never adjudicated is what the
|
|
234
|
+
// ledger exists to surface.
|
|
235
|
+
const refs = manifestTraceRefs(root);
|
|
236
|
+
ledger.plan([
|
|
237
|
+
...adapters.map((a) => `adapter:${a.file}`),
|
|
238
|
+
...refs.map((r) => `ref:${r.manifest}:${r.model}`),
|
|
239
|
+
]);
|
|
240
|
+
|
|
241
|
+
for (const a of adapters) {
|
|
242
|
+
const id = `adapter:${a.file}`;
|
|
243
|
+
if (a.tier !== 'stable') {
|
|
244
|
+
ledger.complete(id);
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
const candidates = byProvider.get(a.adapter) ?? [];
|
|
248
|
+
if (candidates.length === 0) {
|
|
249
|
+
const reason =
|
|
250
|
+
`header claims \`stable\` but the trace index holds no row for provider ` +
|
|
251
|
+
`\`${a.adapter}\` — the promotion evidence is unreachable from a clone`;
|
|
252
|
+
findings.push({ adapter: a.adapter, reason });
|
|
253
|
+
ledger.fail(id, reason);
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
let freshest: { age: number; id: string } | null = null;
|
|
257
|
+
let unparseable = 0;
|
|
258
|
+
for (const c of candidates) {
|
|
259
|
+
const age = ageInDays(c.captured_at, now);
|
|
260
|
+
if (age === null) {
|
|
261
|
+
unparseable += 1;
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
if (freshest === null || age < freshest.age) freshest = { age, id: c.trace_id };
|
|
265
|
+
}
|
|
266
|
+
if (freshest === null) {
|
|
267
|
+
const reason =
|
|
268
|
+
`header claims \`stable\` and all ${String(unparseable)} index row(s) for ` +
|
|
269
|
+
`\`${a.adapter}\` carry an unparseable \`captured_at\` — an undatable trace ` +
|
|
270
|
+
`is not dated evidence`;
|
|
271
|
+
findings.push({ adapter: a.adapter, reason });
|
|
272
|
+
ledger.fail(id, reason);
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
if (freshest.age > RECHECK_WINDOW_DAYS) {
|
|
276
|
+
const reason =
|
|
277
|
+
`header claims \`stable\` but its freshest trace \`${freshest.id}\` is ` +
|
|
278
|
+
`${freshest.age.toFixed(0)} days old, past the ${String(RECHECK_WINDOW_DAYS)}-day ` +
|
|
279
|
+
`window — stale, so re-capture or demote the header; nothing is demoted for you`;
|
|
280
|
+
findings.push({ adapter: a.adapter, reason });
|
|
281
|
+
ledger.fail(id, reason);
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
ledger.complete(id);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
for (const r of refs) {
|
|
288
|
+
const id = `ref:${r.manifest}:${r.model}`;
|
|
289
|
+
if (traceIds.has(r.traceId)) {
|
|
290
|
+
ledger.complete(id);
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
const reason =
|
|
294
|
+
`manifest \`${r.manifest}\` model \`${r.model}\` cites smoke_trace ` +
|
|
295
|
+
`\`${r.traceId}\`, which resolves to no index row — a dangling evidence pointer`;
|
|
296
|
+
findings.push({ adapter: r.manifest, reason });
|
|
297
|
+
ledger.fail(id, reason);
|
|
298
|
+
}
|
|
299
|
+
} catch (err) {
|
|
300
|
+
if (err instanceof LedgerUsageError) {
|
|
301
|
+
process.stderr.write(`❌ ${err.message}\n`);
|
|
302
|
+
return { code: 2, findings, scanned: adapters.length, adapters };
|
|
303
|
+
}
|
|
304
|
+
throw err;
|
|
305
|
+
}
|
|
306
|
+
ledger.report();
|
|
307
|
+
|
|
308
|
+
return { code: findings.length > 0 ? 1 : 0, findings, scanned: adapters.length, adapters };
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export interface TraceRef {
|
|
312
|
+
manifest: string;
|
|
313
|
+
model: string;
|
|
314
|
+
traceId: string;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/** Every `smoke_trace` reference across the model-capability manifests. */
|
|
318
|
+
export function manifestTraceRefs(root: string): TraceRef[] {
|
|
319
|
+
const dir = path.join(root, MANIFEST_DIR);
|
|
320
|
+
if (!fs.existsSync(dir)) return [];
|
|
321
|
+
const out: TraceRef[] = [];
|
|
322
|
+
for (const name of fs.readdirSync(dir).sort()) {
|
|
323
|
+
if (!name.endsWith('.json')) continue;
|
|
324
|
+
let doc: { models?: Record<string, { smoke_trace?: unknown }> };
|
|
325
|
+
try {
|
|
326
|
+
doc = JSON.parse(fs.readFileSync(path.join(dir, name), 'utf-8')) as typeof doc;
|
|
327
|
+
} catch {
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
for (const [model, entry] of Object.entries(doc.models ?? {})) {
|
|
331
|
+
const t = entry.smoke_trace;
|
|
332
|
+
if (typeof t === 'string' && t !== '') {
|
|
333
|
+
out.push({ manifest: name.replace(/\.json$/, ''), model, traceId: t });
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return out;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* The generated tier table for `docs/contracts/provider-lifecycle.md` § 5.
|
|
342
|
+
*
|
|
343
|
+
* §5 was a hand-written day-one snapshot that said all five adapters were
|
|
344
|
+
* `experimental` while seven headers read `stable`. The contract self-scoped
|
|
345
|
+
* that table to its landing day, so it was stale rather than wrong — but a
|
|
346
|
+
* stale snapshot in a contract is still something a reader trips over.
|
|
347
|
+
* Generating it means the snapshot cannot drift again.
|
|
348
|
+
*/
|
|
349
|
+
export function tierTable(root: string, now = asOf()): string {
|
|
350
|
+
const adapters = collectAdapters(root);
|
|
351
|
+
const indexAbs = path.join(root, INDEX_PATH);
|
|
352
|
+
let rows: IndexRow[] = [];
|
|
353
|
+
if (fs.existsSync(indexAbs)) {
|
|
354
|
+
try {
|
|
355
|
+
rows = JSON.parse(fs.readFileSync(indexAbs, 'utf-8')) as IndexRow[];
|
|
356
|
+
} catch {
|
|
357
|
+
rows = [];
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
const lines = [
|
|
361
|
+
'| Adapter | Domain | Tier | Freshest reachable trace |',
|
|
362
|
+
'|---|---|---|---|',
|
|
363
|
+
];
|
|
364
|
+
for (const a of adapters) {
|
|
365
|
+
const mine = rows.filter((r) => r.provider === a.adapter);
|
|
366
|
+
let evidence = '— (none in the index)';
|
|
367
|
+
if (mine.length > 0) {
|
|
368
|
+
let best: IndexRow | null = null;
|
|
369
|
+
let bestAge = Number.POSITIVE_INFINITY;
|
|
370
|
+
for (const r of mine) {
|
|
371
|
+
const age = ageInDays(r.captured_at, now);
|
|
372
|
+
if (age !== null && age < bestAge) {
|
|
373
|
+
bestAge = age;
|
|
374
|
+
best = r;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
evidence =
|
|
378
|
+
best === null
|
|
379
|
+
? `${String(mine.length)} row(s), none datable`
|
|
380
|
+
: `\`${best.captured_at}\` (${String(mine.length)} row(s))`;
|
|
381
|
+
}
|
|
382
|
+
lines.push(`| \`${a.adapter}\` | ${a.domain} | \`${a.tier}\` | ${evidence} |`);
|
|
383
|
+
}
|
|
384
|
+
return lines.join('\n');
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Step 2.3 — warn when a manifest cost changed with no ledger citation.
|
|
389
|
+
*
|
|
390
|
+
* Deliberately a WARNING and never a failure: re-modeling an estimate is a
|
|
391
|
+
* legitimate human act. Doing it silently *after* a live run measured the real
|
|
392
|
+
* charge is the thing worth noticing, because that is the moment the modeled
|
|
393
|
+
* number stops being a guess and starts being a contradicted measurement.
|
|
394
|
+
*/
|
|
395
|
+
export function costDiffWarnings(root: string, baseRef: string): string[] {
|
|
396
|
+
const warnings: string[] = [];
|
|
397
|
+
let diff = '';
|
|
398
|
+
try {
|
|
399
|
+
// GIT_DIR / GIT_WORK_TREE are scrubbed, not inherited. A gate invoked
|
|
400
|
+
// from a hook or a worktree runs with those set to somebody else's
|
|
401
|
+
// repository, and `cwd` is then silently ignored — the diff comes back
|
|
402
|
+
// from the wrong tree and the warning is computed about the wrong
|
|
403
|
+
// change. `cwd` must be the only thing that decides which repo this is.
|
|
404
|
+
const env = { ...process.env };
|
|
405
|
+
delete env.GIT_DIR;
|
|
406
|
+
delete env.GIT_WORK_TREE;
|
|
407
|
+
delete env.GIT_INDEX_FILE;
|
|
408
|
+
diff = execFileSync('git', ['diff', '--unified=0', baseRef, '--', MANIFEST_DIR, LEDGER_PATH], {
|
|
409
|
+
cwd: root,
|
|
410
|
+
encoding: 'utf-8',
|
|
411
|
+
env,
|
|
412
|
+
maxBuffer: 32 * 1024 * 1024,
|
|
413
|
+
});
|
|
414
|
+
} catch {
|
|
415
|
+
return [`${GATE}: could not diff against \`${baseRef}\` — cost-change warning skipped, not passed`];
|
|
416
|
+
}
|
|
417
|
+
const costChanged = /^[+-].*"cost_per_second_usd"/m.test(diff);
|
|
418
|
+
const ledgerCited = diff.split('\n').some((l) => l.startsWith('+') && l.includes(LEDGER_PATH));
|
|
419
|
+
const ledgerTouched = diff.includes(LEDGER_PATH);
|
|
420
|
+
if (costChanged && !ledgerCited && !ledgerTouched) {
|
|
421
|
+
warnings.push(
|
|
422
|
+
`${GATE}: a manifest \`cost_per_second_usd\` changed in this diff with no row added to ` +
|
|
423
|
+
`\`${LEDGER_PATH}\`. A modeled cost may only be re-modeled from measured charges — ` +
|
|
424
|
+
`cite the ledger rows the new figure averages, or say in the commit why none exist.`,
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
return warnings;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
export const LEDGER_PATH = path.join('agents', 'evidence', 'ai-video', 'cost-ledger.jsonl');
|
|
431
|
+
|
|
432
|
+
export const CONTRACT_PATH = path.join('docs', 'contracts', 'provider-lifecycle.md');
|
|
433
|
+
export const BEGIN_MARKER = '<!-- BEGIN GENERATED: adapter-tier-table -->';
|
|
434
|
+
export const END_MARKER = '<!-- END GENERATED: adapter-tier-table -->';
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Splice the generated table into the contract between its markers.
|
|
438
|
+
*
|
|
439
|
+
* Returns the new file text. Idempotent by construction: the block between the
|
|
440
|
+
* markers is REPLACED, never appended to, so running the writer twice produces
|
|
441
|
+
* the same bytes.
|
|
442
|
+
*/
|
|
443
|
+
export function spliceContract(contractText: string, table: string): string {
|
|
444
|
+
const b = contractText.indexOf(BEGIN_MARKER);
|
|
445
|
+
const e = contractText.indexOf(END_MARKER);
|
|
446
|
+
if (b === -1 || e === -1 || e < b) {
|
|
447
|
+
throw new Error(
|
|
448
|
+
`${GATE}: ${CONTRACT_PATH} is missing the generated-table markers ` +
|
|
449
|
+
`(${BEGIN_MARKER} … ${END_MARKER}). Restore them rather than hand-writing the table.`,
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
const head = contractText.slice(0, b + BEGIN_MARKER.length);
|
|
453
|
+
const tail = contractText.slice(e);
|
|
454
|
+
return `${head}\n${table}\n${tail}`;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Does the contract's generated block match the tree?
|
|
459
|
+
*
|
|
460
|
+
* This is the half that makes "generated" a fact rather than a comment. A
|
|
461
|
+
* `DO NOT EDIT BY HAND` marker with nothing checking it is a request, and the
|
|
462
|
+
* defect this replaced was precisely a table nobody re-derived for ten weeks.
|
|
463
|
+
*/
|
|
464
|
+
export function contractDrift(root: string, now = asOf()): string | null {
|
|
465
|
+
const abs = path.join(root, CONTRACT_PATH);
|
|
466
|
+
if (!fs.existsSync(abs)) return `${CONTRACT_PATH} not found`;
|
|
467
|
+
const text = fs.readFileSync(abs, 'utf-8');
|
|
468
|
+
const want = spliceContract(text, tierTable(root, now));
|
|
469
|
+
return want === text ? null : `${CONTRACT_PATH} § 5 table is stale — regenerate with \`--write-contract\``;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
const SELF = 'src/scripts/lint_adapter_tier.ts';
|
|
474
|
+
const SELF_TEST_MIN_CASES = 6;
|
|
475
|
+
const SELF_TEST_MIN_REJECT = 3;
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* A minimal tree with the same shape as the real one, so the self-test drives
|
|
479
|
+
* the REAL binary over a fixture rather than re-asserting the pure functions
|
|
480
|
+
* a unit test already covers.
|
|
481
|
+
*/
|
|
482
|
+
function _fixtureTree(opts: { tier: string; rows: unknown[]; withContract?: boolean }): string {
|
|
483
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'adapter-tier-st-'));
|
|
484
|
+
for (const rel of ADAPTER_DIRS) fs.mkdirSync(path.join(root, rel), { recursive: true });
|
|
485
|
+
fs.mkdirSync(path.join(root, MANIFEST_DIR), { recursive: true });
|
|
486
|
+
fs.mkdirSync(path.join(root, path.dirname(INDEX_PATH)), { recursive: true });
|
|
487
|
+
fs.writeFileSync(
|
|
488
|
+
path.join(root, ADAPTER_DIRS[0], 'zz-fixture.sh'),
|
|
489
|
+
`#!/usr/bin/env bash\n# Lifecycle: ${opts.tier} — fixture\nexit 0\n`,
|
|
490
|
+
);
|
|
491
|
+
fs.writeFileSync(path.join(root, INDEX_PATH), JSON.stringify(opts.rows));
|
|
492
|
+
if (opts.withContract !== false) {
|
|
493
|
+
fs.mkdirSync(path.join(root, path.dirname(CONTRACT_PATH)), { recursive: true });
|
|
494
|
+
const seed = `# fixture\n\n${BEGIN_MARKER}\n${END_MARKER}\n`;
|
|
495
|
+
fs.writeFileSync(path.join(root, CONTRACT_PATH), spliceContract(seed, tierTable(root)));
|
|
496
|
+
}
|
|
497
|
+
return root;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function _freshRow(provider: string, daysAgo: number, traceId = `${provider}-t`) {
|
|
501
|
+
const d = new Date(asOf().getTime() - daysAgo * 86_400_000);
|
|
502
|
+
return {
|
|
503
|
+
provider,
|
|
504
|
+
trace_id: traceId,
|
|
505
|
+
captured_at: d.toISOString().replace(/:/g, '-').replace(/\.\d+Z$/, 'Z'),
|
|
506
|
+
model: null,
|
|
507
|
+
sha256: 'x'.repeat(64),
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function selfTest(): number {
|
|
512
|
+
const repoRoot = path.resolve(_HERE, '..', '..', '..');
|
|
513
|
+
const drive = (root: string) => runGateCli(repoRoot, SELF, ['--quiet', '--root', root], repoRoot);
|
|
514
|
+
const withTree = (opts: Parameters<typeof _fixtureTree>[0], f: (root: string) => number): number => {
|
|
515
|
+
const root = _fixtureTree(opts);
|
|
516
|
+
try {
|
|
517
|
+
return f(root);
|
|
518
|
+
} finally {
|
|
519
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
520
|
+
}
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
const cases: SelfTestCase[] = [
|
|
524
|
+
{
|
|
525
|
+
name: 'rejects a stable header with no index row at all',
|
|
526
|
+
expect: 'reject',
|
|
527
|
+
run: () => withTree({ tier: 'stable', rows: [] }, drive),
|
|
528
|
+
},
|
|
529
|
+
{
|
|
530
|
+
name: 'rejects a stable header whose only row is past the 180-day window',
|
|
531
|
+
expect: 'reject',
|
|
532
|
+
run: () => withTree({ tier: 'stable', rows: [_freshRow('zz-fixture', 400)] }, drive),
|
|
533
|
+
},
|
|
534
|
+
{
|
|
535
|
+
name: 'rejects a stable header whose row carries an unparseable captured_at',
|
|
536
|
+
expect: 'reject',
|
|
537
|
+
run: () =>
|
|
538
|
+
withTree(
|
|
539
|
+
{
|
|
540
|
+
tier: 'stable',
|
|
541
|
+
rows: [{ provider: 'zz-fixture', trace_id: 'z', captured_at: 'not-a-date', model: null, sha256: 'x' }],
|
|
542
|
+
},
|
|
543
|
+
drive,
|
|
544
|
+
),
|
|
545
|
+
},
|
|
546
|
+
{
|
|
547
|
+
name: 'accepts a stable header with a fresh row',
|
|
548
|
+
expect: 'accept',
|
|
549
|
+
run: () => withTree({ tier: 'stable', rows: [_freshRow('zz-fixture', 3)] }, drive),
|
|
550
|
+
},
|
|
551
|
+
{
|
|
552
|
+
name: 'accepts an experimental header with no evidence — the tier claims nothing',
|
|
553
|
+
expect: 'accept',
|
|
554
|
+
run: () => withTree({ tier: 'experimental', rows: [] }, drive),
|
|
555
|
+
},
|
|
556
|
+
{
|
|
557
|
+
name: 'the real tree is green, and its contract block matches',
|
|
558
|
+
expect: 'accept',
|
|
559
|
+
run: () => (contractDrift(repoRoot) === null && check(repoRoot).code === 0 ? 0 : 1),
|
|
560
|
+
},
|
|
561
|
+
{
|
|
562
|
+
name: 'a hand-edited contract table is detected as stale',
|
|
563
|
+
expect: 'reject',
|
|
564
|
+
run: () => {
|
|
565
|
+
const text = fs.readFileSync(path.join(repoRoot, CONTRACT_PATH), 'utf-8');
|
|
566
|
+
const hacked = text.replace(BEGIN_MARKER, `${BEGIN_MARKER}\n| hand | edited | row | here |`);
|
|
567
|
+
return hacked === text || spliceContract(hacked, tierTable(repoRoot)) !== hacked ? 1 : 0;
|
|
568
|
+
},
|
|
569
|
+
},
|
|
570
|
+
];
|
|
571
|
+
return runSelfTest({
|
|
572
|
+
gate: GATE,
|
|
573
|
+
cases,
|
|
574
|
+
minCases: SELF_TEST_MIN_CASES,
|
|
575
|
+
minRejectCases: SELF_TEST_MIN_REJECT,
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
export function main(argv = process.argv.slice(2)): number {
|
|
580
|
+
if (argv.includes('--self-test') && process.env.GATE_SELF_TEST_CHILD !== '1') return selfTest();
|
|
581
|
+
const quiet = argv.includes('--quiet');
|
|
582
|
+
let root = path.resolve(_HERE, '..', '..', '..');
|
|
583
|
+
const rootIdx = argv.indexOf('--root');
|
|
584
|
+
const rootArg = rootIdx === -1 ? undefined : argv[rootIdx + 1];
|
|
585
|
+
if (rootArg !== undefined) root = path.resolve(rootArg);
|
|
586
|
+
|
|
587
|
+
if (argv.includes('--table')) {
|
|
588
|
+
process.stdout.write(`${tierTable(root)}\n`);
|
|
589
|
+
return 0;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
if (argv.includes('--write-contract')) {
|
|
593
|
+
const abs = path.join(root, CONTRACT_PATH);
|
|
594
|
+
const before = fs.readFileSync(abs, 'utf-8');
|
|
595
|
+
const after = spliceContract(before, tierTable(root));
|
|
596
|
+
if (after !== before) fs.writeFileSync(abs, after);
|
|
597
|
+
process.stdout.write(
|
|
598
|
+
after === before
|
|
599
|
+
? `✅ ${CONTRACT_PATH} § 5 table already current.\n`
|
|
600
|
+
: `✅ ${CONTRACT_PATH} § 5 table regenerated.\n`,
|
|
601
|
+
);
|
|
602
|
+
return 0;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
const res = check(root, { quiet });
|
|
606
|
+
|
|
607
|
+
const costIdx = argv.indexOf('--cost-diff');
|
|
608
|
+
const baseRef = costIdx === -1 ? undefined : argv[costIdx + 1];
|
|
609
|
+
if (baseRef !== undefined) {
|
|
610
|
+
for (const w of costDiffWarnings(root, baseRef)) {
|
|
611
|
+
process.stdout.write(`⚠️ ${w}\n`);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
if (res.code === 2) return 2;
|
|
616
|
+
|
|
617
|
+
// The contract's generated block is checked on every run, so `DO NOT EDIT
|
|
618
|
+
// BY HAND` is enforced rather than requested.
|
|
619
|
+
const drift = contractDrift(root);
|
|
620
|
+
if (drift !== null) {
|
|
621
|
+
process.stdout.write(`❌ ${drift}\n`);
|
|
622
|
+
process.stdout.write(` npx tsx src/scripts/lint_adapter_tier.ts --write-contract\n`);
|
|
623
|
+
return 1;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
if (res.findings.length > 0) {
|
|
627
|
+
process.stdout.write('❌ adapter tier claim without reachable evidence:\n\n');
|
|
628
|
+
for (const f of res.findings) {
|
|
629
|
+
process.stdout.write(` ${f.adapter} — ${f.reason}\n`);
|
|
630
|
+
}
|
|
631
|
+
process.stdout.write(
|
|
632
|
+
`\nThe index is the reviewer-facing evidence surface; the raw traces stay local-only\n` +
|
|
633
|
+
`(council 2026-08-23). Regenerate the index with\n` +
|
|
634
|
+
` bash src/scripts/ai-video/smoke-trace.sh index\n` +
|
|
635
|
+
`or demote the header. This gate never demotes an adapter for you — a tier is a\n` +
|
|
636
|
+
`maintainer claim, and silently rewriting one would be the drift it exists to stop.\n`,
|
|
637
|
+
);
|
|
638
|
+
return 1;
|
|
639
|
+
}
|
|
640
|
+
if (!quiet) {
|
|
641
|
+
const stable = res.adapters.filter((a) => a.tier === 'stable').length;
|
|
642
|
+
process.stdout.write(
|
|
643
|
+
`✅ ${String(stable)} stable claim(s) resolve to a trace inside ${String(RECHECK_WINDOW_DAYS)} days ` +
|
|
644
|
+
`(${String(res.scanned)} adapter header(s) scanned).\n`,
|
|
645
|
+
);
|
|
646
|
+
}
|
|
647
|
+
return 0;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function _isCliEntry(): boolean {
|
|
651
|
+
if (process.argv[1] === undefined) return false;
|
|
652
|
+
const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
|
|
653
|
+
if (import.meta.url === argvUrl) return true;
|
|
654
|
+
try {
|
|
655
|
+
return fs.realpathSync(fileURLToPath(import.meta.url)) === fs.realpathSync(path.resolve(process.argv[1]));
|
|
656
|
+
} catch {
|
|
657
|
+
return false;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
if (_isCliEntry() || process.argv[1] === _HERE) {
|
|
662
|
+
process.exit(main());
|
|
663
|
+
}
|