@event4u/agent-config 14.12.0 → 14.14.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 +118 -155
- package/CONTRIBUTING.md +38 -0
- package/CREDITS.md +135 -0
- package/NOTICE +13 -0
- package/README.md +8 -6
- package/dist/agent-src/commands/analyze/inbox.md +145 -2
- package/dist/agent-src/commands/feature/dev.md +7 -2
- package/dist/agent-src/commands/ghostwriter/write.md +5 -1
- package/dist/agent-src/commands/implement-ticket.md +1 -1
- package/dist/agent-src/commands/mission/upgrade.md +1 -1
- package/dist/agent-src/commands/orchestrate.md +2 -2
- package/dist/agent-src/commands/post-as/me.md +5 -1
- package/dist/agent-src/commands/pr/merge.md +19 -6
- package/dist/agent-src/commands/roadmap/process-full.md +19 -5
- package/dist/agent-src/commands/tdd/red.md +12 -2
- package/dist/agent-src/commands/work.md +1 -1
- package/dist/agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +40 -0
- package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +5 -2
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +21 -0
- package/dist/agent-src/contexts/execution/orchestration-telemetry.md +1 -0
- package/dist/agent-src/contexts/execution/subagent-response-contract.md +40 -0
- package/dist/agent-src/contexts/execution/subagent-steering.md +13 -1
- package/dist/agent-src/contexts/execution/terminal-states.md +96 -0
- package/dist/agent-src/rules/artifact-drafting-protocol.md +1 -1
- package/dist/agent-src/rules/broken-access-control.md +4 -0
- package/dist/agent-src/rules/code-comment-discipline.md +6 -0
- package/dist/agent-src/rules/code-provenance.md +4 -0
- package/dist/agent-src/rules/design-fidelity.md +1 -1
- package/dist/agent-src/rules/domain-safety-pii.md +4 -0
- package/dist/agent-src/rules/domain-safety-retention.md +4 -0
- package/dist/agent-src/rules/engineering-safety-floor.md +2 -0
- package/dist/agent-src/rules/external-code-graph-interop.md +14 -12
- package/dist/agent-src/rules/improve-before-implement.md +6 -8
- package/dist/agent-src/rules/laravel-routing.md +1 -1
- package/dist/agent-src/rules/lethal-trifecta-guard.md +4 -0
- package/dist/agent-src/rules/secret-vcs-guard.md +2 -0
- package/dist/agent-src/rules/security-sensitive-stop.md +2 -0
- package/dist/agent-src/rules/source-confidentiality.md +12 -34
- package/dist/agent-src/rules/symfony-routing.md +1 -1
- package/dist/agent-src/rules/token-efficiency.md +14 -0
- package/dist/agent-src/rules/tool-safety.md +4 -0
- package/dist/agent-src/rules/untrusted-input-defense.md +4 -0
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +252 -8
- package/dist/agent-src/scripts/guarded_baseline.ts +343 -0
- package/dist/agent-src/scripts/roadmap_set_step.ts +253 -0
- package/dist/agent-src/scripts/stub_queue.ts +82 -0
- package/dist/agent-src/scripts/stubs_due.ts +516 -0
- package/dist/agent-src/scripts/ticket_bundles.ts +84 -0
- package/dist/agent-src/scripts/update_roadmap_progress.ts +16 -12
- package/dist/agent-src/skills/brand-asset-generation/SKILL.md +17 -4
- package/dist/agent-src/skills/brand-audit/SKILL.md +33 -0
- package/dist/agent-src/skills/code-intelligence/SKILL.md +57 -0
- package/dist/agent-src/skills/database/SKILL.md +8 -0
- package/dist/agent-src/skills/dependency-upgrade/SKILL.md +27 -0
- package/dist/agent-src/skills/design-review/SKILL.md +1 -1
- package/dist/agent-src/skills/doc-coauthoring/SKILL.md +1 -1
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +17 -1
- package/dist/agent-src/skills/feature-planning/SKILL.md +9 -2
- package/dist/agent-src/skills/frontend-render-security/SKILL.md +44 -0
- package/dist/agent-src/skills/git-workflow/SKILL.md +1 -1
- package/dist/agent-src/skills/js-library-packaging/scripts/check_package_surface.ts +38 -4
- package/dist/agent-src/skills/judge-artifact-completeness/rubrics/ticket-quality-score.json +2 -2
- package/dist/agent-src/skills/laravel/SKILL.md +35 -0
- package/dist/agent-src/skills/laravel-migration/SKILL.md +57 -3
- package/dist/agent-src/skills/monorepo-workspace/SKILL.md +26 -0
- package/dist/agent-src/skills/playbook-authoring/SKILL.md +28 -1
- package/dist/agent-src/skills/react-shadcn-ui/SKILL.md +1 -1
- package/dist/agent-src/skills/skill-writing/references/procedure.md +5 -5
- package/dist/agent-src/skills/sql-writing/SKILL.md +7 -1
- package/dist/agent-src/skills/standards-from-config/SKILL.md +27 -4
- package/dist/agent-src/skills/supply-chain-intake/SKILL.md +11 -4
- package/dist/agent-src/skills/symfony-workflow/SKILL.md +35 -0
- package/dist/agent-src/skills/test-case-discovery/SKILL.md +85 -0
- package/dist/agent-src/skills/test-driven-development/SKILL.md +33 -9
- package/dist/agent-src/skills/testing-anti-patterns/SKILL.md +1 -1
- package/dist/agent-src/skills/testing-anti-patterns/process-anti-patterns.md +37 -0
- package/dist/agent-src/skills/ui-component-architect/SKILL.md +45 -2
- package/dist/agent-src/skills/upstream-contribute/SKILL.md +38 -1
- package/dist/agent-src/skills/verify-repair-loop/SKILL.md +9 -4
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +97 -3
- package/dist/agent-src/templates/scripts/check_memory.ts +258 -17
- package/dist/agent-src/templates/scripts/check_memory_proposal.ts +9 -3
- package/dist/agent-src/templates/scripts/memory_lookup.ts +86 -0
- package/dist/agent-src/templates/scripts/memory_signal.ts +44 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/implement.ts +90 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/index.ts +11 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/test.ts +23 -1
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +10 -1
- package/dist/agent-src/templates/scripts/work_engine/directives/mixed/stitch.ts +5 -1
- package/dist/agent-src/templates/scripts/work_engine/stack/runner.ts +102 -14
- package/dist/cli/commands/uiAudit.js +124 -5
- package/dist/cli/registry.js +2 -0
- package/dist/cli-delegate/{chunk-HYWSRNUX.js → chunk-2DK2VJWG.js} +3 -3
- package/dist/cli-delegate/{chunk-JCW7ZLHK.js → chunk-4FS4WRXP.js} +5 -0
- package/dist/cli-delegate/{chunk-OMIVVCG2.js → chunk-A4QJ6ENT.js} +24 -0
- package/dist/cli-delegate/{chunk-JJA2QEWA.js → chunk-AKJGUQNO.js} +466 -285
- package/dist/cli-delegate/{chunk-E46KPN6K.js → chunk-BTWUYDGN.js} +424 -363
- package/dist/cli-delegate/{chunk-K4FFODTS.js → chunk-ILMUPLGW.js} +1227 -316
- package/dist/cli-delegate/{chunk-FF7WAFMR.js → chunk-KPBWFOPZ.js} +5 -1
- package/dist/cli-delegate/cmd_adr_effective.js +102 -0
- package/dist/cli-delegate/cmd_conformance.js +80 -11
- package/dist/cli-delegate/cmd_doctor.js +3 -3
- package/dist/cli-delegate/cmd_export.js +3 -2
- package/dist/cli-delegate/cmd_fleet.js +2 -2
- 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 +3 -2
- package/dist/cli-delegate/cmd_settings_get.js +1 -1
- package/dist/cli-delegate/cmd_settings_migrate.js +3 -2
- package/dist/cli-delegate/cmd_settings_set.js +2 -2
- package/dist/cli-delegate/cmd_sync.js +3 -2
- package/dist/cli-delegate/cmd_uninstall.js +3 -2
- package/dist/cli-delegate/cmd_validate.js +3 -2
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +58 -58
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +2 -2
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +2 -2
- package/dist/hooks/dispatch.js +2900 -1049
- package/dist/install/claudeRuleRewrite.js +254 -0
- package/dist/install/detect.js +29 -16
- package/dist/install/detect_php_shape.js +159 -0
- package/dist/install/install.mjs +634 -428
- package/dist/mcp/awesome-mcp-servers.row.md +1 -1
- package/dist/mcp/mcp-cloudflare-catalogue.json +1 -1
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +3 -3
- package/dist/mcp/server.mjs +4895 -2627
- package/dist/server/schemas/settings.js +3 -0
- package/dist/ui/assets/index-D0jaCRPM.js +29 -0
- package/dist/ui/index.html +1 -1
- package/docs/MIGRATION.md +1 -1
- package/docs/THIRD-PARTY-NOTICES.md +25 -0
- package/docs/contracts/provider-lifecycle.md +1 -1
- package/docs/contracts/settings-classes.md +10 -4
- package/docs/guidelines/agent-infra/5w2h-analysis.md +3 -3
- package/docs/guidelines/agent-infra/agent-interaction-and-decision-quality.md +1 -0
- package/docs/guidelines/agent-infra/artifact-drafting-protocol-mechanics.md +58 -3
- package/docs/guidelines/agent-infra/ask-when-uncertain-demos.md +2 -2
- package/docs/guidelines/agent-infra/asking-and-brevity-examples.md +3 -3
- package/docs/guidelines/agent-infra/break-glass-usage.md +7 -7
- package/docs/guidelines/agent-infra/carve-out-predicates.md +3 -3
- package/docs/guidelines/agent-infra/comparison-matrix.md +1 -1
- package/docs/guidelines/agent-infra/critical-thinking.md +4 -4
- package/docs/guidelines/agent-infra/direct-answers-demos.md +2 -2
- package/docs/guidelines/agent-infra/domain-eval-anti-pattern.md +1 -1
- package/docs/guidelines/agent-infra/engineering-memory-data-format.md +3 -3
- package/docs/guidelines/agent-infra/existence-question-verdicts.md +80 -0
- package/docs/guidelines/agent-infra/first-principles.md +2 -2
- package/docs/guidelines/agent-infra/framework-neutrality-patterns.md +2 -2
- package/docs/guidelines/agent-infra/guarded-baseline.md +39 -0
- package/docs/guidelines/agent-infra/inversion-thinking.md +5 -5
- package/docs/guidelines/agent-infra/language-and-tone-examples.md +1 -1
- package/docs/guidelines/agent-infra/layered-settings.md +10 -10
- package/docs/guidelines/agent-infra/memory-access.md +1 -1
- package/docs/guidelines/agent-infra/mental-models.md +3 -3
- package/docs/guidelines/agent-infra/reuse-verdict-mechanics.md +71 -0
- package/docs/guidelines/agent-infra/review-routing-data-format.md +7 -7
- package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +13 -7
- package/docs/guidelines/agent-infra/role-contracts.md +2 -2
- package/docs/guidelines/agent-infra/role-mode-router.md +3 -3
- package/docs/guidelines/agent-infra/rule-type-governance.md +2 -2
- package/docs/guidelines/agent-infra/scqa-framework.md +5 -5
- package/docs/guidelines/agent-infra/self-improvement-pipeline.md +6 -6
- package/docs/guidelines/agent-infra/simplicity-and-goal-demos.md +2 -2
- package/docs/guidelines/agent-infra/six-hats.md +3 -3
- package/docs/guidelines/agent-infra/skill-quality-checklist.md +6 -6
- package/docs/guidelines/agent-infra/source-confidentiality-mechanics.md +109 -0
- package/docs/guidelines/agent-infra/systems-thinking.md +1 -1
- package/docs/guidelines/agent-infra/verify-before-complete-demos.md +2 -2
- package/docs/guidelines/augment-portability-patterns.md +6 -4
- package/docs/guidelines/code-clarity.md +18 -0
- package/docs/guidelines/component-oriented-and-oop-development.md +3 -2
- package/docs/guidelines/cross-role-handoff.md +2 -2
- package/docs/guidelines/design-fidelity-mechanics.md +1 -1
- package/docs/guidelines/docs/readme-size-and-splitting.md +3 -3
- package/docs/guidelines/php/database.md +90 -7
- package/docs/guidelines/prompt-templates.md +7 -7
- package/llms.txt +2 -2
- package/package.json +5 -2
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +252 -8
- package/src/agent-src/scripts/guarded_baseline.ts +343 -0
- package/src/agent-src/scripts/roadmap_set_step.ts +253 -0
- package/src/agent-src/scripts/stub_queue.ts +82 -0
- package/src/agent-src/scripts/stubs_due.ts +516 -0
- package/src/agent-src/scripts/ticket_bundles.ts +84 -0
- package/src/agent-src/scripts/update_roadmap_progress.ts +16 -12
- package/src/agent-src/templates/scripts/check_memory.ts +258 -17
- package/src/agent-src/templates/scripts/check_memory_proposal.ts +9 -3
- package/src/agent-src/templates/scripts/memory_lookup.ts +86 -0
- package/src/agent-src/templates/scripts/memory_signal.ts +44 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/implement.ts +90 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/index.ts +11 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/test.ts +23 -1
- package/src/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +10 -1
- package/src/agent-src/templates/scripts/work_engine/directives/mixed/stitch.ts +5 -1
- package/src/agent-src/templates/scripts/work_engine/stack/runner.ts +102 -14
- package/src/config/agent-settings.template.yml +32 -3
- package/src/config/assurance-capability-registry.json +21 -9
- package/src/config/canonical-terms.yml +119 -0
- package/src/config/capability-native-outcome-bars.json +81 -0
- package/src/config/ci-local-parity.yml +80 -0
- package/src/config/drafting-channels.yml +43 -0
- package/src/config/estate-count-budget.json +57 -36
- package/src/config/evaluator-budgets.json +14 -14
- package/src/config/gate-coverage.yml +729 -63
- package/src/config/gate-reachability-exemptions.json +21 -0
- package/src/config/gate-violation-baselines.json +42 -19
- package/src/config/harness-evolution-budget.json +25 -0
- package/src/config/lapsed-beta-baseline.json +96 -0
- package/src/config/memory-twin-verdicts.yml +129 -0
- package/src/config/metric-registry.yml +124 -0
- package/src/config/pack-size-budget.json +16 -15
- package/src/config/preamble-payload-budget.json +12 -4
- package/src/config/profiles/balanced.ini +27 -0
- package/src/config/profiles/full.ini +27 -0
- package/src/config/profiles/minimal.ini +27 -0
- package/src/config/publish-surface.json +9 -2
- package/src/config/routing-coverage-seed.json +16 -2
- package/src/config/rule-stub-ceilings.json +133 -112
- package/src/config/turnaround-budget.json +66 -0
- package/src/install/claudeRuleRewrite.ts +288 -0
- package/src/install/detect.ts +60 -17
- package/src/install/detect_php_shape.ts +191 -0
- package/src/scripts/_cli/cmd_doctor.ts +27 -112
- package/src/scripts/_dispatch.bash +23 -0
- package/src/scripts/_lib/activation_ladder.ts +255 -0
- package/src/scripts/_lib/activation_receipt_producer.ts +341 -0
- package/src/scripts/_lib/activation_states.ts +88 -0
- package/src/scripts/_lib/adherence_detectors.ts +143 -0
- package/src/scripts/_lib/adr_frontmatter.ts +26 -0
- package/src/scripts/_lib/arith_claims.ts +181 -0
- package/src/scripts/_lib/arm_ranking.ts +62 -0
- package/src/scripts/_lib/asset_delivery_ledger.ts +303 -0
- package/src/scripts/_lib/audit_field_provenance.ts +54 -0
- package/src/scripts/_lib/auto_dispatch.ts +32 -3
- package/src/scripts/_lib/bench_ab_size_claim.ts +98 -4
- package/src/scripts/_lib/benchmark_cache_fields.ts +158 -0
- package/src/scripts/_lib/candidate_pair_delta.ts +349 -0
- package/src/scripts/_lib/candidate_proposer.ts +402 -0
- package/src/scripts/_lib/candidate_record.ts +601 -0
- package/src/scripts/_lib/capture_rate.ts +319 -0
- package/src/scripts/_lib/carrier_status.ts +118 -0
- package/src/scripts/_lib/cascade_stage_enumeration.ts +92 -0
- package/src/scripts/_lib/collector_denominator.ts +771 -0
- package/src/scripts/_lib/collector_record.ts +340 -0
- package/src/scripts/_lib/collector_store.ts +664 -0
- package/src/scripts/_lib/collector_supervision.ts +827 -0
- package/src/scripts/_lib/concern_estate.ts +77 -0
- package/src/scripts/_lib/config_chain.ts +477 -0
- package/src/scripts/_lib/config_cost.ts +209 -0
- package/src/scripts/_lib/corpus_manifest.ts +726 -0
- package/src/scripts/_lib/council_settings_block.ts +6 -0
- package/src/scripts/_lib/curator_ops.ts +172 -0
- package/src/scripts/_lib/delivery_arm_experiment.ts +282 -0
- package/src/scripts/_lib/delivery_criticality.ts +126 -0
- package/src/scripts/_lib/doctor_runtime_checks.ts +70 -0
- package/src/scripts/_lib/empty_cycles.ts +88 -0
- package/src/scripts/_lib/estate_carrier_cases.ts +72 -0
- package/src/scripts/_lib/eval_publication.ts +354 -0
- package/src/scripts/_lib/evaluation_cascade.ts +452 -0
- package/src/scripts/_lib/evaluation_vector.ts +283 -0
- package/src/scripts/_lib/evaluator_promotion.ts +225 -0
- package/src/scripts/_lib/evidence_basis.ts +118 -0
- package/src/scripts/_lib/evolution_roi.ts +562 -0
- package/src/scripts/_lib/exec_evidence.ts +8 -0
- package/src/scripts/_lib/experience_card.ts +272 -0
- package/src/scripts/_lib/experience_report.ts +151 -0
- package/src/scripts/_lib/experiment_freeze.ts +112 -0
- package/src/scripts/_lib/generated_by.ts +66 -0
- package/src/scripts/_lib/harness_evolution_guards.ts +239 -0
- package/src/scripts/_lib/hook_effect_probe.ts +141 -0
- package/src/scripts/_lib/host_denominator.ts +687 -0
- package/src/scripts/_lib/host_projection_reach.ts +157 -0
- package/src/scripts/_lib/ignored_blocker.ts +148 -0
- package/src/scripts/_lib/install_reach_checks.ts +495 -0
- package/src/scripts/_lib/json_duplicate_keys.ts +150 -0
- package/src/scripts/_lib/judge_hygiene.ts +228 -0
- package/src/scripts/_lib/legacy_boundary_map.ts +228 -0
- package/src/scripts/_lib/lexical_shortlist.ts +215 -0
- package/src/scripts/_lib/llm_candidate_proposer.ts +461 -0
- package/src/scripts/_lib/llm_proposer_transport.ts +153 -0
- package/src/scripts/_lib/loop_guards.ts +299 -0
- package/src/scripts/_lib/loss_class.ts +129 -0
- package/src/scripts/_lib/machine_wake.ts +51 -0
- package/src/scripts/_lib/md_prose_lines.ts +177 -0
- package/src/scripts/_lib/minimality_tiebreak.ts +168 -0
- package/src/scripts/_lib/one_resolver_invariant.ts +449 -0
- package/src/scripts/_lib/orchestration_record.ts +253 -5
- package/src/scripts/_lib/outcome_envelope.ts +227 -0
- package/src/scripts/_lib/outcome_vocabularies.ts +125 -0
- package/src/scripts/_lib/paired_stats.ts +245 -0
- package/src/scripts/_lib/paired_verdict.ts +226 -0
- package/src/scripts/_lib/pathology_archive.ts +393 -0
- package/src/scripts/_lib/prefix_stable_surfaces.ts +123 -0
- package/src/scripts/_lib/privacy_class.ts +47 -0
- package/src/scripts/_lib/promotion_capability.ts +315 -0
- package/src/scripts/_lib/promotion_evidence.ts +414 -0
- package/src/scripts/_lib/promotion_review.ts +294 -0
- package/src/scripts/_lib/prompt_shape.ts +92 -0
- package/src/scripts/_lib/regression_neighbourhood.ts +259 -0
- package/src/scripts/_lib/release_highlights.ts +70 -1
- package/src/scripts/_lib/release_material.ts +125 -2
- package/src/scripts/_lib/repeated_failure.ts +105 -0
- package/src/scripts/_lib/repo_root.ts +111 -0
- package/src/scripts/_lib/review_skipped_record.ts +37 -0
- package/src/scripts/_lib/role_split.ts +338 -0
- package/src/scripts/_lib/routing_corpus.ts +307 -0
- package/src/scripts/_lib/routing_index_input.ts +104 -0
- package/src/scripts/_lib/rule_injection.ts +22 -1
- package/src/scripts/_lib/runtime_journal.ts +1499 -0
- package/src/scripts/_lib/runtime_wiring_checks.ts +430 -0
- package/src/scripts/_lib/semantic_noop.ts +167 -0
- package/src/scripts/_lib/skill_catalogue.ts +11 -1
- package/src/scripts/_lib/source_digest.ts +200 -0
- package/src/scripts/_lib/source_redact.ts +260 -0
- package/src/scripts/_lib/source_shape.ts +502 -0
- package/src/scripts/_lib/source_snapshot_dedup.ts +199 -0
- package/src/scripts/_lib/strip_comments.d.mts +8 -0
- package/src/scripts/_lib/strip_comments.mjs +65 -0
- package/src/scripts/_lib/test_red_state.ts +154 -0
- package/src/scripts/_lib/trigger_shift.ts +144 -0
- package/src/scripts/_lib/untrusted_content.ts +82 -7
- package/src/scripts/activation_receipt.ts +258 -0
- package/src/scripts/adoption_report.ts +1 -1
- package/src/scripts/adr/evidence_census.ts +1 -1
- package/src/scripts/adr/regenerate_index.ts +26 -2
- package/src/scripts/adr_cite_check.ts +164 -1
- package/src/scripts/ai_council/blind_review.ts +11 -1
- package/src/scripts/ai_council/clients.ts +4 -0
- package/src/scripts/ai_council/config.ts +3 -0
- package/src/scripts/ai_council/consensus.ts +28 -8
- package/src/scripts/ai_council/consensus_round.ts +226 -0
- package/src/scripts/ai_council/cross_exam.ts +128 -0
- package/src/scripts/ai_council/disagreement_signal.ts +338 -0
- package/src/scripts/ai_council/explain_route.ts +157 -0
- package/src/scripts/ai_council/information_gain.ts +293 -0
- package/src/scripts/ai_council/inline_findings.ts +340 -0
- package/src/scripts/ai_council/judge_position_bias.ts +197 -0
- package/src/scripts/ai_council/leakage_corpus.ts +385 -0
- package/src/scripts/ai_council/leakage_patterns.ts +453 -0
- package/src/scripts/ai_council/minority_retention.ts +171 -0
- package/src/scripts/ai_council/orchestrator.ts +16 -203
- package/src/scripts/ai_council/orchestrator_results.ts +23 -3
- package/src/scripts/ai_council/pricing.ts +106 -0
- package/src/scripts/ai_council/prompts.ts +74 -7
- package/src/scripts/ai_council/provider_leakage_bench.ts +327 -0
- package/src/scripts/ai_council/py_format.ts +14 -3
- package/src/scripts/ai_council/recouncil_guard.ts +293 -0
- package/src/scripts/ai_council/recouncil_savings.ts +299 -0
- package/src/scripts/ai_council/replay_route.ts +223 -0
- package/src/scripts/ai_council/response_record.ts +50 -0
- package/src/scripts/ai_council/reviewer_assignment.ts +279 -0
- package/src/scripts/ai_council/routing_training_row.ts +197 -0
- package/src/scripts/ai_council/synthesis_strategy.ts +159 -0
- package/src/scripts/ai_council/topology_bench_manifest.ts +860 -0
- package/src/scripts/ai_council/topology_vocabulary.ts +175 -0
- package/src/scripts/asset_delivery_ledger.ts +157 -0
- package/src/scripts/attest_artifact.ts +193 -0
- package/src/scripts/audit_auto_rules.ts +2 -1
- package/src/scripts/audit_command_surface.ts +1 -1
- package/src/scripts/audit_mcp_tools.ts +2 -2
- package/src/scripts/audit_skill_overlap_allowlist.json +6 -1
- package/src/scripts/audit_user_type_axis.ts +2 -1
- package/src/scripts/bench_ab_clone.ts +215 -10
- package/src/scripts/bench_ab_integrity.ts +119 -5
- package/src/scripts/bench_ab_v2_stats.ts +40 -213
- package/src/scripts/build_archive_index.ts +1 -1
- package/src/scripts/build_linear_digest.ts +1 -1
- package/src/scripts/build_proof.ts +11 -4
- package/src/scripts/build_rule_trigger_matrix.ts +1 -1
- package/src/scripts/build_source_digests.ts +157 -0
- package/src/scripts/cache_realization_report.ts +103 -0
- package/src/scripts/check_adr_frontmatter.ts +131 -10
- package/src/scripts/check_benchmark_report_fields.ts +207 -0
- package/src/scripts/check_beta_review_markers.ts +235 -6
- package/src/scripts/check_bundle_path_leakage.ts +34 -1
- package/src/scripts/check_claims.ts +576 -6
- package/src/scripts/check_composite_arming.ts +35 -0
- package/src/scripts/check_concern_admissions.ts +365 -0
- package/src/scripts/check_enforcement_coverage.ts +28 -0
- package/src/scripts/check_estate_count.ts +347 -25
- package/src/scripts/check_gate_reachability.ts +383 -0
- package/src/scripts/check_host_loadability.ts +104 -0
- package/src/scripts/check_loss_class_declared.ts +308 -0
- package/src/scripts/check_md_language.ts +15 -98
- package/src/scripts/check_memory_proposal.ts +18 -11
- package/src/scripts/check_no_currency_in_cost_surfaces.ts +225 -0
- package/src/scripts/check_no_external_sources.ts +281 -6
- package/src/scripts/check_pack_conformance_fixture.ts +11 -1
- package/src/scripts/check_pack_size.ts +59 -2
- package/src/scripts/check_preamble_payload_budget.ts +114 -3
- package/src/scripts/check_prefix_stable_mutation.ts +372 -0
- package/src/scripts/check_publish_surface.ts +10 -1
- package/src/scripts/check_references.ts +24 -1
- package/src/scripts/check_release_highlights.ts +96 -13
- package/src/scripts/check_roadmap_trackable.ts +17 -2
- package/src/scripts/check_rule_evidence_declaration.ts +316 -0
- package/src/scripts/check_rule_projection_integrity.ts +14 -4
- package/src/scripts/check_score_contract.ts +26 -3
- package/src/scripts/check_static_parity.ts +285 -0
- package/src/scripts/check_supervision_claim_atomicity.ts +498 -0
- package/src/scripts/check_suppression_hygiene.ts +15 -0
- package/src/scripts/ci_settle.ts +39 -2
- package/src/scripts/code_graph/build.ts +60 -3
- package/src/scripts/code_graph/extract.ts +44 -0
- package/src/scripts/code_graph/types.ts +24 -0
- package/src/scripts/collector_daemon.ts +714 -0
- package/src/scripts/config_cost_report.ts +302 -0
- package/src/scripts/consumer_matrix.ts +74 -1
- package/src/scripts/corpus_manifest.ts +235 -0
- package/src/scripts/cost/budget.mjs +10 -3
- package/src/scripts/cost/track.mjs +10 -3
- package/src/scripts/council_cli.ts +37 -54
- package/src/scripts/dispatch_r2_reviewer.ts +46 -5
- package/src/scripts/evolution_lab.ts +1153 -0
- package/src/scripts/external_sources_denylist.json +5 -18
- package/src/scripts/extract_audit_patterns.ts +21 -0
- package/src/scripts/fold_intake.ts +12 -1
- package/src/scripts/gen_discovery_baseline.ts +1 -1
- package/src/scripts/generate_capabilities_index.ts +12 -5
- package/src/scripts/generate_capability_matrix.ts +3 -3
- package/src/scripts/generate_command_flows.ts +2 -2
- package/src/scripts/generate_cookbook.ts +2 -2
- package/src/scripts/generate_index.ts +8 -1
- package/src/scripts/generate_knowledge_index.ts +2 -1
- package/src/scripts/generate_ownership_matrix.ts +2 -1
- package/src/scripts/generate_pack_manifests.ts +10 -2
- package/src/scripts/generate_role_experiences_catalog.ts +2 -2
- package/src/scripts/generate_settings_reference.ts +1 -1
- package/src/scripts/git_authorization_hook.ts +22 -1
- package/src/scripts/grade_target_readiness.ts +48 -5
- package/src/scripts/hook_effect_doctor.ts +261 -0
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +125 -16
- package/src/scripts/hooks/block_speaking_inbox_dir.ts +271 -0
- package/src/scripts/hooks/block_unauthorized_git.ts +18 -0
- package/src/scripts/hooks/comment_discipline_hook.ts +146 -0
- package/src/scripts/hooks/concern_registry.ts +6 -0
- package/src/scripts/hooks/dispatch_hook.ts +32 -0
- package/src/scripts/hooks/journal_record_hook.ts +345 -0
- package/src/scripts/hooks/prepush_metadata_sources.sh +110 -0
- package/src/scripts/hooks/run_continuation_hook.ts +44 -9
- package/src/scripts/hooks/shims/install.sh +61 -0
- package/src/scripts/hooks/shims/php +101 -0
- package/src/scripts/hooks/suggestion_capture_hook.ts +10 -0
- package/src/scripts/hooks/turn_end_gate_hook.ts +9 -1
- package/src/scripts/hot_context_hook.ts +16 -0
- package/src/scripts/install-hooks.sh +20 -5
- package/src/scripts/install.ts +17 -35
- package/src/scripts/inventory_frontmatter.ts +2 -1
- package/src/scripts/language_mirror_hook.ts +14 -2
- package/src/scripts/lint_adr_sweep_routing.ts +12 -0
- package/src/scripts/lint_canonical_terms.ts +604 -0
- package/src/scripts/lint_carrier_integrity.ts +820 -0
- package/src/scripts/lint_code_comments.ts +643 -0
- package/src/scripts/lint_composition_review.ts +619 -0
- package/src/scripts/lint_consolidation_lineage.ts +666 -0
- package/src/scripts/lint_eval_specs.ts +593 -0
- package/src/scripts/lint_hook_manifest.ts +19 -0
- package/src/scripts/lint_host_portability.ts +373 -0
- package/src/scripts/lint_judge_prompt_expectation.ts +27 -1
- package/src/scripts/lint_memory_twin_parity.ts +335 -0
- package/src/scripts/lint_metric_consumers.ts +355 -0
- package/src/scripts/lint_plan_risk_register.ts +39 -19
- package/src/scripts/lint_promotion_paths.ts +779 -0
- package/src/scripts/lint_skill_descriptions.ts +18 -1
- package/src/scripts/lint_skill_frontmatter_safety.ts +58 -0
- package/src/scripts/lint_skill_link_reach.ts +252 -0
- package/src/scripts/lint_skill_trigger_corpus.ts +426 -23
- package/src/scripts/lint_ticket_buildable.ts +17 -1
- package/src/scripts/llm_propose.ts +256 -0
- package/src/scripts/mcp_server/consumer_tool_catalog.json +1 -1
- package/src/scripts/mcp_server/tool_catalog_source.ts +1 -1
- package/src/scripts/measure_delivery_sets.ts +361 -0
- package/src/scripts/measure_docs_dead_links.ts +348 -0
- package/src/scripts/measure_host_capture.ts +461 -0
- package/src/scripts/measure_routing_signal.ts +307 -0
- package/src/scripts/migration_status.ts +3 -3
- package/src/scripts/model_rule_injection.ts +76 -0
- package/src/scripts/orchestration_record.ts +14 -0
- package/src/scripts/preamble_byte_census.ts +139 -10
- package/src/scripts/prepack-check.mjs +4 -1
- package/src/scripts/probe_council_retention.ts +495 -0
- package/src/scripts/probe_turnaround.ts +384 -0
- package/src/scripts/probe_unblocked_ask.ts +279 -0
- package/src/scripts/release.ts +21 -37
- package/src/scripts/release_drill.ts +143 -0
- package/src/scripts/release_env.ts +35 -0
- package/src/scripts/release_publication.ts +84 -0
- package/src/scripts/render_benchmark_md.ts +1 -1
- package/src/scripts/rule_activation_census.ts +70 -9
- package/src/scripts/rule_backlinks.ts +1 -1
- package/src/scripts/run_lifecycle_suite.ts +208 -0
- package/src/scripts/run_skill_evals.ts +40 -0
- package/src/scripts/schemas/delivery-manifest.schema.json +43 -0
- package/src/scripts/schemas/drafting-channels.schema.json +43 -0
- package/src/scripts/schemas/evals.schema.json +24 -1
- package/src/scripts/schemas/rule.schema.json +52 -1
- package/src/scripts/schemas/skill.schema.json +17 -2
- package/src/scripts/skill_overlap.ts +7 -1
- package/src/scripts/skill_tools/run_block_d_eval.ts +8 -1
- package/src/scripts/skill_tools/score_skill_relevance.ts +84 -3
- package/src/scripts/skill_tools/suggest_skill_for_task.ts +8 -1
- package/src/scripts/skill_usage_report.ts +2 -1
- package/src/scripts/stubs_due.ts +211 -0
- package/src/scripts/sweep_source_surfaces.ts +678 -0
- package/src/scripts/sync_pr_branch.ts +3 -3
- package/src/scripts/trigger_eval_grandfather.json +202 -222
- package/src/scripts/utilization_report.ts +22 -2
- package/src/server/schemas/settings.ts +5 -0
- package/src/templates/marketing-copy.yml +1 -1
- package/dist/ui/assets/index-Cxgy63kz.js +0 -26
|
@@ -219,17 +219,71 @@ export function payloadClassCounts(
|
|
|
219
219
|
}));
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
+
/**
|
|
223
|
+
* The recorded BUILT-surface figure, if the budget file carries one.
|
|
224
|
+
*
|
|
225
|
+
* Read by key SHAPE (`built_surface_measurement_<date>`) rather than by the one
|
|
226
|
+
* dated key that exists today, so re-measuring the built surface is an added
|
|
227
|
+
* record rather than an edit to this function. The newest key wins.
|
|
228
|
+
*/
|
|
229
|
+
export function recordedBuiltPackedMb(budget: PackSizeBudget): number | null {
|
|
230
|
+
const keys = Object.keys(budget)
|
|
231
|
+
.filter((k) => /^built_surface_measurement_/.test(k))
|
|
232
|
+
.sort();
|
|
233
|
+
const newest = keys[keys.length - 1];
|
|
234
|
+
if (newest === undefined) return null;
|
|
235
|
+
const block = (budget as unknown as Record<string, unknown>)[newest];
|
|
236
|
+
if (block === null || typeof block !== 'object') return null;
|
|
237
|
+
const built = (block as { built?: unknown }).built;
|
|
238
|
+
if (built === null || typeof built !== 'object') return null;
|
|
239
|
+
const value = (built as { packed_mb?: unknown }).packed_mb;
|
|
240
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : null;
|
|
241
|
+
}
|
|
242
|
+
|
|
222
243
|
export function evaluate(budget: PackSizeBudget, pack: PackResult): string[] {
|
|
223
244
|
const errors: string[] = [];
|
|
224
245
|
|
|
225
246
|
const packedMb = pack.size / 1e6;
|
|
226
247
|
const entry = budget.budgets['packed_size_mb'];
|
|
248
|
+
const built = payloadIsBuilt(pack.files);
|
|
249
|
+
const pct = budget.regression_pct;
|
|
227
250
|
if (entry === undefined) {
|
|
228
251
|
errors.push('packed_size_mb: missing from the budget file — gate would pass vacuously');
|
|
252
|
+
} else if (built) {
|
|
253
|
+
// A BUILT payload is not the surface `max` describes, and comparing
|
|
254
|
+
// them is the category error `pack-size-budget.json` warns about in its
|
|
255
|
+
// own `measurement_conditions`: every cap in that file, `max: 9.1`
|
|
256
|
+
// included, was measured with `--ignore-scripts` on a tree with no
|
|
257
|
+
// `dist/cli/**`. The built surface it records separately is ~2 MB
|
|
258
|
+
// larger *by construction*, so a job that happens to build first turns
|
|
259
|
+
// a green branch red for a reason that has nothing to do with its diff.
|
|
260
|
+
// Measured 2026-08-30: main 8.985 (2715 entries, unbuilt) against a
|
|
261
|
+
// branch adding six source files at 9.922 (2827 entries) — the 112-file
|
|
262
|
+
// difference is `dist/cli` + `dist/cli-delegate`, not the diff.
|
|
263
|
+
//
|
|
264
|
+
// So the built payload is judged against the recorded BUILT figure,
|
|
265
|
+
// with the same creep percentage. That keeps teeth on the surface a
|
|
266
|
+
// consumer installs instead of pretending the unbuilt cap covers it.
|
|
267
|
+
const baseline = recordedBuiltPackedMb(budget);
|
|
268
|
+
if (baseline === null) {
|
|
269
|
+
errors.push(
|
|
270
|
+
`packed_size_mb: measured ${packedMb.toFixed(3)} on a BUILT payload, and the budget `
|
|
271
|
+
+ 'file records no built-surface measurement to compare it against — the unbuilt '
|
|
272
|
+
+ 'cap does not describe this tree. Record one, or pack with --ignore-scripts.',
|
|
273
|
+
);
|
|
274
|
+
} else {
|
|
275
|
+
const ceiling = baseline * (1 + pct / 100);
|
|
276
|
+
if (packedMb > ceiling) {
|
|
277
|
+
errors.push(
|
|
278
|
+
`packed_size_mb (BUILT surface): measured ${packedMb.toFixed(3)} regressed `
|
|
279
|
+
+ `>${String(pct)}% vs the recorded built figure ${String(baseline)} `
|
|
280
|
+
+ `(ceiling ${ceiling.toFixed(3)})`,
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
229
284
|
} else if (packedMb > entry.max) {
|
|
230
285
|
errors.push(`packed_size_mb: measured ${packedMb.toFixed(3)} exceeds budget ${entry.max}`);
|
|
231
286
|
} else {
|
|
232
|
-
const pct = budget.regression_pct;
|
|
233
287
|
const ceiling = entry.last_measured * (1 + pct / 100);
|
|
234
288
|
if (entry.last_measured > 0 && packedMb > ceiling) {
|
|
235
289
|
errors.push(
|
|
@@ -436,7 +490,10 @@ export function main(argv: readonly string[] = process.argv.slice(2)): number {
|
|
|
436
490
|
return 1;
|
|
437
491
|
}
|
|
438
492
|
process.stdout.write(
|
|
439
|
-
|
|
493
|
+
// The SURFACE is named on the green path too. A figure with no surface
|
|
494
|
+
// label is how two numbers for one lever start circulating, which is
|
|
495
|
+
// the confusion this budget file opens by warning about.
|
|
496
|
+
`✅ pack size within budget (${payloadIsBuilt(pack.files) ? 'BUILT surface, vs the recorded built figure' : 'UNBUILT surface, vs max'}) — ${(pack.size / 1e6).toFixed(3)} MB packed, ` +
|
|
440
497
|
`${Object.keys(perSkill).length} skills, largest share ` +
|
|
441
498
|
`${Math.max(...Object.values(perSkill).map((b) => (b / total) * 100)).toFixed(2)}%\n`,
|
|
442
499
|
);
|
|
@@ -20,11 +20,15 @@
|
|
|
20
20
|
*
|
|
21
21
|
* Exit codes: 0 within budget · 1 over budget · 2 misuse / unreadable budget.
|
|
22
22
|
*/
|
|
23
|
+
import { execFileSync } from 'node:child_process';
|
|
23
24
|
import * as fs from 'node:fs';
|
|
25
|
+
import * as os from 'node:os';
|
|
24
26
|
import * as path from 'node:path';
|
|
25
27
|
|
|
26
28
|
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
27
29
|
import { censusClaudeMdHierarchy, censusRuleDir, censusSkillsCatalog } from './preamble_byte_census.js';
|
|
30
|
+
import { PREFIX_STABLE_SURFACES, prefixStableRoots } from './_lib/prefix_stable_surfaces.js';
|
|
31
|
+
import { attributeGrowth, buildLedger, renderAttribution } from './_lib/asset_delivery_ledger.js';
|
|
28
32
|
|
|
29
33
|
const HERE = path.dirname(new URL(import.meta.url).pathname);
|
|
30
34
|
const REPO_ROOT = path.resolve(HERE, '..', '..');
|
|
@@ -63,12 +67,30 @@ export function readBudget(file: string = BUDGET_FILE): Budget {
|
|
|
63
67
|
};
|
|
64
68
|
}
|
|
65
69
|
|
|
70
|
+
/** Look one surface root up by id. Throws on an unknown id — a renamed surface
|
|
71
|
+
* must fail loudly here rather than degrade this gate to measuring nothing. */
|
|
72
|
+
function surfaceRoot(id: string): string {
|
|
73
|
+
const s = PREFIX_STABLE_SURFACES.find((x) => x.id === id);
|
|
74
|
+
if (s === undefined) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
`prefix-stable surface '${id}' is not declared in _lib/prefix_stable_surfaces.ts — ` +
|
|
77
|
+
`the payload census cannot measure a bucket whose root it cannot resolve`,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
return s.root;
|
|
81
|
+
}
|
|
82
|
+
|
|
66
83
|
/** Measure only what the repo tree determines — no `~`, no transcripts, no network. */
|
|
67
84
|
export function measureDeterministicPayload(
|
|
68
85
|
repoRoot: string = REPO_ROOT,
|
|
69
86
|
): Array<{ name: string; tokens: number; files: number }> {
|
|
70
|
-
|
|
71
|
-
|
|
87
|
+
// Roots come from the canonical prefix-stable surface registry, never from a
|
|
88
|
+
// literal here: `check_prefix_stable_mutation` guards the same boundary, and
|
|
89
|
+
// two independent lists of one boundary is the drift shape this repository
|
|
90
|
+
// has already paid for. `surfaceRoot` throws rather than silently measuring
|
|
91
|
+
// nothing if an id is renamed out from under it.
|
|
92
|
+
const projectRules = censusRuleDir(path.join(repoRoot, surfaceRoot('project-scope-rules')));
|
|
93
|
+
const skills = censusSkillsCatalog(path.join(repoRoot, surfaceRoot('preloaded-skills-catalog')));
|
|
72
94
|
// Only the PROJECT half of the CLAUDE.md hierarchy is deterministic — the
|
|
73
95
|
// user file and its @-imports live on whatever machine runs this.
|
|
74
96
|
const claudeMd = censusClaudeMdHierarchy(repoRoot, path.join(repoRoot, '.no-such-home'));
|
|
@@ -120,6 +142,67 @@ export function evaluate(repoRoot?: string, budgetFile?: string, overrideCeiling
|
|
|
120
142
|
return { measured, baseline: budget.baseline_tokens, ceiling, withinBudget: measured <= ceiling, buckets };
|
|
121
143
|
}
|
|
122
144
|
|
|
145
|
+
/**
|
|
146
|
+
* Per-asset attribution for a refusal, against the merge-base tree.
|
|
147
|
+
*
|
|
148
|
+
* Reads the base tree through `git worktree`-free plumbing: the ledger is built
|
|
149
|
+
* over a temporary checkout of `git merge-base HEAD origin/main`. Every failure
|
|
150
|
+
* mode returns an empty list — the caller treats attribution as an explanation,
|
|
151
|
+
* never as a precondition for refusing.
|
|
152
|
+
*/
|
|
153
|
+
function attributeGrowthAgainstBase(): string[] | null {
|
|
154
|
+
try {
|
|
155
|
+
const base = execFileSync('git', ['merge-base', 'HEAD', 'origin/main'], {
|
|
156
|
+
cwd: REPO_ROOT,
|
|
157
|
+
encoding: 'utf-8',
|
|
158
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
159
|
+
}).trim();
|
|
160
|
+
if (base.length === 0) return null;
|
|
161
|
+
|
|
162
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'payload-base-'));
|
|
163
|
+
try {
|
|
164
|
+
for (const rel of prefixStableRoots()) {
|
|
165
|
+
// `git archive` of one subtree, extracted into the scratch root.
|
|
166
|
+
// A root absent at the base ref simply yields nothing.
|
|
167
|
+
try {
|
|
168
|
+
const tar = execFileSync('git', ['archive', base, '--', rel], {
|
|
169
|
+
cwd: REPO_ROOT,
|
|
170
|
+
maxBuffer: 256 * 1024 * 1024,
|
|
171
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
172
|
+
});
|
|
173
|
+
// `-f -` is not optional: without it BSD tar reads its
|
|
174
|
+
// default device rather than stdin, and the extraction
|
|
175
|
+
// silently produces nothing — which reads downstream as
|
|
176
|
+
// "the base tree was unavailable" rather than as a bug.
|
|
177
|
+
execFileSync('tar', ['-x', '-f', '-', '-C', tmp], {
|
|
178
|
+
input: tar,
|
|
179
|
+
stdio: ['pipe', 'ignore', 'ignore'],
|
|
180
|
+
});
|
|
181
|
+
} catch {
|
|
182
|
+
/* root absent at base — nothing to extract */
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const [rulesRel, skillsRel] = prefixStableRoots();
|
|
186
|
+
const before = buildLedger(
|
|
187
|
+
path.join(tmp, rulesRel ?? ''),
|
|
188
|
+
path.join(tmp, skillsRel ?? ''),
|
|
189
|
+
tmp,
|
|
190
|
+
);
|
|
191
|
+
const after = buildLedger(
|
|
192
|
+
path.join(REPO_ROOT, rulesRel ?? ''),
|
|
193
|
+
path.join(REPO_ROOT, skillsRel ?? ''),
|
|
194
|
+
REPO_ROOT,
|
|
195
|
+
);
|
|
196
|
+
if (before.rows.length === 0) return null;
|
|
197
|
+
return renderAttribution(attributeGrowth(before.rows, after.rows));
|
|
198
|
+
} finally {
|
|
199
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
200
|
+
}
|
|
201
|
+
} catch {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
123
206
|
export function main(argv: string[] = process.argv.slice(2)): number {
|
|
124
207
|
const json = argv.includes('--format=json') || argv.includes('--json');
|
|
125
208
|
// `--ceiling <n>`: the grace ceiling the CI step reads out of
|
|
@@ -145,7 +228,7 @@ export function main(argv: string[] = process.argv.slice(2)): number {
|
|
|
145
228
|
gate: 'check_preamble_payload_budget',
|
|
146
229
|
scanned: verdict.buckets.reduce((n, b) => n + b.files, 0),
|
|
147
230
|
units: 'payload source file(s)',
|
|
148
|
-
roots:
|
|
231
|
+
roots: prefixStableRoots(),
|
|
149
232
|
});
|
|
150
233
|
} catch (err) {
|
|
151
234
|
if (err instanceof DeadScopeError) {
|
|
@@ -170,12 +253,40 @@ export function main(argv: string[] = process.argv.slice(2)): number {
|
|
|
170
253
|
`(baseline ${verdict.baseline}, ${sign}${delta}; ceiling ${verdict.ceiling})\n`,
|
|
171
254
|
);
|
|
172
255
|
if (!verdict.withinBudget) {
|
|
256
|
+
// road-to-delivered-cost-truth 2.2 — a gate names its own "no". The
|
|
257
|
+
// ceiling message alone states a fact and leaves the reader to find the
|
|
258
|
+
// cause; naming the assets and their token deltas makes the refusal
|
|
259
|
+
// actionable, which is the difference between a refusal that gets fixed
|
|
260
|
+
// and one that gets suppressed.
|
|
261
|
+
//
|
|
262
|
+
// Attribution is BEST-EFFORT and never changes the verdict: the
|
|
263
|
+
// comparison needs the merge-base tree, and a shallow clone, a detached
|
|
264
|
+
// build or a first commit legitimately has none. A gate that failed to
|
|
265
|
+
// refuse because it could not explain itself would be strictly worse
|
|
266
|
+
// than one that refuses without the explanation.
|
|
267
|
+
const attribution = attributeGrowthAgainstBase();
|
|
173
268
|
process.stderr.write(
|
|
174
269
|
`❌ per-spawn preamble payload grew past the ratchet: ${verdict.measured} > ${verdict.ceiling} tok.\n` +
|
|
175
270
|
` Every rule and skill description here is re-written on EVERY subagent spawn, so growth\n` +
|
|
176
271
|
` is paid per spawn, not once. Either shrink the addition, or raise baseline_tokens in\n` +
|
|
177
272
|
` src/config/preamble-payload-budget.json with the reason in the same commit.\n`,
|
|
178
273
|
);
|
|
274
|
+
// Three distinct states, and conflating the last two is a diagnostic
|
|
275
|
+
// defect rather than a cosmetic one: "I could not look" and "I looked
|
|
276
|
+
// and nothing changed" send a reader to completely different places.
|
|
277
|
+
if (attribution === null) {
|
|
278
|
+
process.stderr.write(
|
|
279
|
+
'\n (no per-asset attribution: the merge-base tree could not be read here, so the\n' +
|
|
280
|
+
' growth cannot be traced to specific assets. The refusal stands regardless.)\n',
|
|
281
|
+
);
|
|
282
|
+
} else if (attribution.length === 0) {
|
|
283
|
+
process.stderr.write(
|
|
284
|
+
'\n Per-asset attribution: NO standing asset changed against the merge base — this\n' +
|
|
285
|
+
' diff did not cause the overage, it inherited it.\n',
|
|
286
|
+
);
|
|
287
|
+
} else {
|
|
288
|
+
process.stderr.write('\n' + attribution.join('\n') + '\n');
|
|
289
|
+
}
|
|
179
290
|
return 1;
|
|
180
291
|
}
|
|
181
292
|
process.stdout.write('✅ per-spawn preamble payload within the ratchet.\n');
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* A mid-session mutation of a declared prefix-stable surface is a violation.
|
|
4
|
+
*
|
|
5
|
+
* WHY
|
|
6
|
+
* ---
|
|
7
|
+
* `check_kernel_prefix_stability` guards the kernel prefix at AUTHORING time —
|
|
8
|
+
* it compares a committed snapshot and re-anchors in the same PR. `payload_hash_drift`
|
|
9
|
+
* measures prefix churn AFTER the fact, from an audit ledger. Neither can see the
|
|
10
|
+
* case this gate exists for: a hook, a script, or (once one exists) a resident
|
|
11
|
+
* process rewriting a prefix-stable surface DURING a session, which invalidates
|
|
12
|
+
* the prompt cache for the rest of it and makes the next call pay the cache-WRITE
|
|
13
|
+
* rate over the whole prefix.
|
|
14
|
+
*
|
|
15
|
+
* That is the runtime half, and until this gate it was prose.
|
|
16
|
+
*
|
|
17
|
+
* WHAT IT CHECKS
|
|
18
|
+
* --------------
|
|
19
|
+
* For every concern in `src/scripts/hook_manifest.yaml` bound on at least one
|
|
20
|
+
* MID-SESSION slot — any slot that is not a declared re-arm event — the gate reads
|
|
21
|
+
* the concern's script and looks for evidence that it writes inside a surface
|
|
22
|
+
* declared in `_lib/prefix_stable_surfaces.ts`. Evidence is a violation unless the
|
|
23
|
+
* concern declares `re_arm: <event>`, naming the boundary at which a rebuilt
|
|
24
|
+
* prefix is expected and paid for once.
|
|
25
|
+
*
|
|
26
|
+
* THE LIST IS LOADED, NEVER RESTATED. Both this gate and
|
|
27
|
+
* `check_preamble_payload_budget` import `PREFIX_STABLE_SURFACES`. Two lists
|
|
28
|
+
* describing one boundary is the drift shape this repository has already paid for.
|
|
29
|
+
*
|
|
30
|
+
* FAIL-CLOSED ON UNDECIDABLE (council 2026-08-28, 2/2)
|
|
31
|
+
* ----------------------------------------------------
|
|
32
|
+
* A write whose target is a runtime variable cannot be classified by reading the
|
|
33
|
+
* source. Classifying that as "fine" turns every dynamic path into an accidental
|
|
34
|
+
* exemption. So: a dynamic write target in a script that ALSO carries a literal
|
|
35
|
+
* resolving into a declared surface is reported as `undecidable` and fails. A
|
|
36
|
+
* dynamic write in a script with no such literal is not reported — the gate does
|
|
37
|
+
* not claim to know what it cannot see, and reporting every dynamic write in every
|
|
38
|
+
* hook would be a gate nobody can keep green.
|
|
39
|
+
*
|
|
40
|
+
* Exit codes: 0 clean · 1 violation(s) · 2 misuse / unreadable manifest.
|
|
41
|
+
*
|
|
42
|
+
* Usage:
|
|
43
|
+
* ./scripts-run src/scripts/check_prefix_stable_mutation [--root <dir>] [--json] [--quiet]
|
|
44
|
+
* ./scripts-run src/scripts/check_prefix_stable_mutation --self-test
|
|
45
|
+
*/
|
|
46
|
+
import { spawnSync } from 'node:child_process';
|
|
47
|
+
import * as fs from 'node:fs';
|
|
48
|
+
import * as os from 'node:os';
|
|
49
|
+
import * as path from 'node:path';
|
|
50
|
+
|
|
51
|
+
import { GateLedger } from './_lib/gate_ledger.js';
|
|
52
|
+
import { runGateCli, runSelfTest, type SelfTestCase } from './_lib/gate_self_test.js';
|
|
53
|
+
import { DeadScopeError, reportScanned } from './_lib/scan_scope.js';
|
|
54
|
+
import { PREFIX_STABLE_SURFACES, RE_ARM_EVENTS, prefixStableRoots, surfaceFor } from './_lib/prefix_stable_surfaces.js';
|
|
55
|
+
|
|
56
|
+
const HERE = path.dirname(new URL(import.meta.url).pathname);
|
|
57
|
+
const REPO_ROOT = path.resolve(HERE, '..', '..');
|
|
58
|
+
|
|
59
|
+
/** Write-shaped calls. A read never invalidates a cache; only a write does. */
|
|
60
|
+
const WRITE_CALLS = [
|
|
61
|
+
'writeFileSync',
|
|
62
|
+
'appendFileSync',
|
|
63
|
+
'createWriteStream',
|
|
64
|
+
'renameSync',
|
|
65
|
+
'unlinkSync',
|
|
66
|
+
'rmSync',
|
|
67
|
+
'rmdirSync',
|
|
68
|
+
'cpSync',
|
|
69
|
+
'copyFileSync',
|
|
70
|
+
'truncateSync',
|
|
71
|
+
'writeFile',
|
|
72
|
+
'appendFile',
|
|
73
|
+
'outputFileSync',
|
|
74
|
+
] as const;
|
|
75
|
+
|
|
76
|
+
export interface Finding {
|
|
77
|
+
concern: string;
|
|
78
|
+
script: string;
|
|
79
|
+
slots: string[];
|
|
80
|
+
kind: 'literal' | 'undecidable';
|
|
81
|
+
surface: string;
|
|
82
|
+
evidence: string;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface Verdict {
|
|
86
|
+
scanned: number;
|
|
87
|
+
findings: Finding[];
|
|
88
|
+
midSessionConcerns: number;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
interface ConcernSpec {
|
|
92
|
+
script?: unknown;
|
|
93
|
+
re_arm?: unknown;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Yaml is already a dependency of the manifest linter; reuse the same reader shape. */
|
|
97
|
+
function loadManifest(root: string): { concerns: Record<string, ConcernSpec>; platforms: Record<string, Record<string, string[]>> } {
|
|
98
|
+
const file = path.join(root, 'src', 'scripts', 'hook_manifest.yaml');
|
|
99
|
+
const raw = fs.readFileSync(file, 'utf-8');
|
|
100
|
+
// Minimal, dependency-free parse of the two shapes this gate needs. The full
|
|
101
|
+
// schema is `lint_hook_manifest`'s business; duplicating a YAML dependency
|
|
102
|
+
// here would add a second parser to disagree with.
|
|
103
|
+
const out = spawnSync(
|
|
104
|
+
process.execPath,
|
|
105
|
+
['-e', 'const y=require("js-yaml");let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{const d=y.load(s);process.stdout.write(JSON.stringify({concerns:d.concerns||{},platforms:d.platforms||{}}))})'],
|
|
106
|
+
{ input: raw, encoding: 'utf-8', cwd: REPO_ROOT, maxBuffer: 64 * 1024 * 1024 },
|
|
107
|
+
);
|
|
108
|
+
if (out.status !== 0 || !out.stdout) {
|
|
109
|
+
throw new Error(`hook_manifest.yaml could not be parsed: ${out.stderr || 'no output'}`);
|
|
110
|
+
}
|
|
111
|
+
return JSON.parse(out.stdout) as { concerns: Record<string, ConcernSpec>; platforms: Record<string, Record<string, string[]>> };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Every slot each concern is bound on, across every platform. */
|
|
115
|
+
export function slotsByConcern(platforms: Record<string, Record<string, string[]>>): Map<string, Set<string>> {
|
|
116
|
+
const m = new Map<string, Set<string>>();
|
|
117
|
+
for (const slots of Object.values(platforms)) {
|
|
118
|
+
if (slots === null || typeof slots !== 'object') continue;
|
|
119
|
+
for (const [slot, names] of Object.entries(slots)) {
|
|
120
|
+
if (!Array.isArray(names)) continue;
|
|
121
|
+
for (const n of names) {
|
|
122
|
+
const set = m.get(n) ?? new Set<string>();
|
|
123
|
+
set.add(slot);
|
|
124
|
+
m.set(n, set);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return m;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Slots that are NOT re-arm boundaries — i.e. fire inside a live session. */
|
|
132
|
+
export function midSessionSlots(slots: Iterable<string>): string[] {
|
|
133
|
+
return [...slots].filter((s) => !RE_ARM_EVENTS.includes(s)).sort();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** All string literals in a source fragment, in order. */
|
|
137
|
+
function literals(src: string): string[] {
|
|
138
|
+
const out: string[] = [];
|
|
139
|
+
const re = /'([^'\\\n]*)'|"([^"\\\n]*)"|`([^`$\\\n]*)`/g;
|
|
140
|
+
let m: RegExpExecArray | null;
|
|
141
|
+
while ((m = re.exec(src)) !== null) out.push(m[1] ?? m[2] ?? m[3] ?? '');
|
|
142
|
+
return out;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** The first argument expression of each write-shaped call in `src`. */
|
|
146
|
+
export function writeTargets(src: string): string[] {
|
|
147
|
+
const out: string[] = [];
|
|
148
|
+
for (const call of WRITE_CALLS) {
|
|
149
|
+
const re = new RegExp(`\\b${call}\\s*\\(`, 'g');
|
|
150
|
+
let m: RegExpExecArray | null;
|
|
151
|
+
while ((m = re.exec(src)) !== null) {
|
|
152
|
+
let depth = 1;
|
|
153
|
+
let i = m.index + m[0].length;
|
|
154
|
+
const start = i;
|
|
155
|
+
while (i < src.length && depth > 0) {
|
|
156
|
+
const ch = src[i];
|
|
157
|
+
if (ch === '(' || ch === '[' || ch === '{') depth += 1;
|
|
158
|
+
else if (ch === ')' || ch === ']' || ch === '}') depth -= 1;
|
|
159
|
+
else if (ch === ',' && depth === 1) break;
|
|
160
|
+
i += 1;
|
|
161
|
+
}
|
|
162
|
+
out.push(src.slice(start, i));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return out;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Classify one script. Returns findings for this concern.
|
|
170
|
+
*
|
|
171
|
+
* `literal` — a write whose own argument carries a literal resolving into a
|
|
172
|
+
* declared surface. Unambiguous.
|
|
173
|
+
* `undecidable` — a write with no classifiable literal, in a file that carries a
|
|
174
|
+
* surface literal somewhere. Fails closed.
|
|
175
|
+
*/
|
|
176
|
+
export function classifyScript(source: string): Array<{ kind: 'literal' | 'undecidable'; surface: string; evidence: string }> {
|
|
177
|
+
const found: Array<{ kind: 'literal' | 'undecidable'; surface: string; evidence: string }> = [];
|
|
178
|
+
const targets = writeTargets(source);
|
|
179
|
+
if (targets.length === 0) return found;
|
|
180
|
+
|
|
181
|
+
// A file-wide literal that resolves into a surface — the fail-closed trigger.
|
|
182
|
+
const fileSurfaces = literals(source)
|
|
183
|
+
.map((l) => ({ lit: l, s: surfaceFor(l) }))
|
|
184
|
+
.filter((x) => x.s !== null);
|
|
185
|
+
|
|
186
|
+
let sawDynamic = false;
|
|
187
|
+
for (const t of targets) {
|
|
188
|
+
const lits = literals(t);
|
|
189
|
+
// `path.join('dist','agent-src','rules', …)` — join the literal segments too.
|
|
190
|
+
const joined = lits.join('/');
|
|
191
|
+
const hit = lits.map((l) => surfaceFor(l)).find((s) => s !== null) ?? surfaceFor(joined);
|
|
192
|
+
if (hit) {
|
|
193
|
+
found.push({ kind: 'literal', surface: hit.id, evidence: t.trim().slice(0, 120) });
|
|
194
|
+
} else if (lits.length === 0) {
|
|
195
|
+
sawDynamic = true;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const firstSurface = fileSurfaces[0];
|
|
200
|
+
if (found.length === 0 && sawDynamic && firstSurface !== undefined && firstSurface.s !== null) {
|
|
201
|
+
found.push({
|
|
202
|
+
kind: 'undecidable',
|
|
203
|
+
surface: firstSurface.s.id,
|
|
204
|
+
evidence: `a write target is computed at runtime and this file carries the literal '${firstSurface.lit}'`,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
return found;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function evaluate(root: string = REPO_ROOT, ledger?: GateLedger): Verdict {
|
|
211
|
+
const { concerns, platforms } = loadManifest(root);
|
|
212
|
+
const bySlot = slotsByConcern(platforms);
|
|
213
|
+
const findings: Finding[] = [];
|
|
214
|
+
let scanned = 0;
|
|
215
|
+
let midSessionConcerns = 0;
|
|
216
|
+
|
|
217
|
+
ledger?.plan(Object.keys(concerns));
|
|
218
|
+
|
|
219
|
+
for (const [name, spec] of Object.entries(concerns)) {
|
|
220
|
+
const slots = midSessionSlots(bySlot.get(name) ?? []);
|
|
221
|
+
if (slots.length === 0) {
|
|
222
|
+
ledger?.outOfScope(name, 'not_applicable_kind');
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
midSessionConcerns += 1;
|
|
226
|
+
|
|
227
|
+
const rel = typeof spec?.script === 'string' ? spec.script : null;
|
|
228
|
+
if (rel === null) {
|
|
229
|
+
ledger?.skip(name, 'manifest_absent');
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
const abs = path.join(root, rel);
|
|
233
|
+
if (!fs.existsSync(abs)) {
|
|
234
|
+
ledger?.skip(name, 'no_applicable_files');
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
scanned += 1;
|
|
238
|
+
|
|
239
|
+
const declaredReArm = typeof spec?.re_arm === 'string' ? spec.re_arm : null;
|
|
240
|
+
if (declaredReArm !== null && RE_ARM_EVENTS.includes(declaredReArm)) {
|
|
241
|
+
ledger?.skip(name, 'declared_exemption');
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const hits = classifyScript(fs.readFileSync(abs, 'utf-8'));
|
|
246
|
+
for (const hit of hits) {
|
|
247
|
+
findings.push({ concern: name, script: rel, slots, kind: hit.kind, surface: hit.surface, evidence: hit.evidence });
|
|
248
|
+
}
|
|
249
|
+
if (hits.length > 0) ledger?.fail(name, `${String(hits.length)} mid-session write(s) into a prefix-stable surface`);
|
|
250
|
+
else ledger?.complete(name);
|
|
251
|
+
}
|
|
252
|
+
return { scanned, findings, midSessionConcerns };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// ---------------------------------------------------------------- self-test
|
|
256
|
+
|
|
257
|
+
function plant(dir: string, reArm: string | null, body: string): void {
|
|
258
|
+
fs.mkdirSync(path.join(dir, 'src', 'scripts'), { recursive: true });
|
|
259
|
+
fs.writeFileSync(path.join(dir, 'src', 'scripts', 'fixture_hook.ts'), body);
|
|
260
|
+
const arm = reArm === null ? '' : `\n re_arm: ${reArm}`;
|
|
261
|
+
fs.writeFileSync(
|
|
262
|
+
path.join(dir, 'src', 'scripts', 'hook_manifest.yaml'),
|
|
263
|
+
`schema_version: 1\nconcerns:\n fixture:\n script: src/scripts/fixture_hook.ts${arm}\nplatforms:\n claude:\n post_tool_use:\n - fixture\n`,
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const WRITES_SURFACE = `import * as fs from 'node:fs';\nfs.writeFileSync('dist/agent-src/rules/x.md', 'hi');\n`;
|
|
268
|
+
const WRITES_ELSEWHERE = `import * as fs from 'node:fs';\nfs.writeFileSync('agents/runtime/state/x.json', 'hi');\n`;
|
|
269
|
+
const DYNAMIC_WITH_SURFACE_LITERAL = `import * as fs from 'node:fs';\nconst root = 'dist/agent-src/rules';\nfs.writeFileSync(target, 'hi');\n`;
|
|
270
|
+
const DYNAMIC_NO_SURFACE = `import * as fs from 'node:fs';\nfs.writeFileSync(target, 'hi');\n`;
|
|
271
|
+
const READS_ONLY = `import * as fs from 'node:fs';\nfs.readFileSync('dist/agent-src/rules/x.md', 'utf-8');\n`;
|
|
272
|
+
|
|
273
|
+
function selfTestCases(): SelfTestCase[] {
|
|
274
|
+
const mk = (name: string, expect: 'reject' | 'accept', reArm: string | null, body: string): SelfTestCase => ({
|
|
275
|
+
name,
|
|
276
|
+
expect,
|
|
277
|
+
run: () => {
|
|
278
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'psm-'));
|
|
279
|
+
try {
|
|
280
|
+
plant(dir, reArm, body);
|
|
281
|
+
return runGateCli(REPO_ROOT, 'src/scripts/check_prefix_stable_mutation.ts', ['--root', dir, '--quiet'], REPO_ROOT);
|
|
282
|
+
} finally {
|
|
283
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
});
|
|
287
|
+
return [
|
|
288
|
+
mk('mid-session write to a declared surface, no re-arm → reject', 'reject', null, WRITES_SURFACE),
|
|
289
|
+
mk('same write with re_arm: pre_compact → accept', 'accept', 'pre_compact', WRITES_SURFACE),
|
|
290
|
+
mk('same write with re_arm: session_start → accept', 'accept', 'session_start', WRITES_SURFACE),
|
|
291
|
+
mk('write outside every declared surface → accept', 'accept', null, WRITES_ELSEWHERE),
|
|
292
|
+
mk('dynamic target + surface literal in file → reject (fail-closed)', 'reject', null, DYNAMIC_WITH_SURFACE_LITERAL),
|
|
293
|
+
mk('dynamic target, no surface literal → accept (not claimed)', 'accept', null, DYNAMIC_NO_SURFACE),
|
|
294
|
+
mk('reads a surface but never writes → accept', 'accept', null, READS_ONLY),
|
|
295
|
+
];
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ---------------------------------------------------------------------- CLI
|
|
299
|
+
|
|
300
|
+
export function main(argv: string[] = process.argv.slice(2)): number {
|
|
301
|
+
if (argv.includes('--self-test')) {
|
|
302
|
+
return runSelfTest({ gate: 'check_prefix_stable_mutation', cases: selfTestCases(), minCases: 6, minRejectCases: 2 });
|
|
303
|
+
}
|
|
304
|
+
const quiet = argv.includes('--quiet');
|
|
305
|
+
const json = argv.includes('--json');
|
|
306
|
+
const ri = argv.indexOf('--root');
|
|
307
|
+
const rootArg = ri !== -1 ? argv[ri + 1] : undefined;
|
|
308
|
+
const root = rootArg !== undefined ? path.resolve(rootArg) : REPO_ROOT;
|
|
309
|
+
|
|
310
|
+
const ledger = new GateLedger('check_prefix_stable_mutation');
|
|
311
|
+
let verdict: Verdict;
|
|
312
|
+
try {
|
|
313
|
+
verdict = evaluate(root, ledger);
|
|
314
|
+
} catch (err) {
|
|
315
|
+
process.stderr.write(`❌ check_prefix_stable_mutation: ${(err as Error).message}\n`);
|
|
316
|
+
return 2;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
try {
|
|
320
|
+
reportScanned({
|
|
321
|
+
gate: 'check_prefix_stable_mutation',
|
|
322
|
+
scanned: verdict.scanned,
|
|
323
|
+
units: 'mid-session hook script(s)',
|
|
324
|
+
roots: ['src/scripts/hook_manifest.yaml', ...prefixStableRoots()],
|
|
325
|
+
});
|
|
326
|
+
} catch (err) {
|
|
327
|
+
if (err instanceof DeadScopeError) {
|
|
328
|
+
process.stderr.write(`❌ check_prefix_stable_mutation: ${err.message}\n`);
|
|
329
|
+
return 2;
|
|
330
|
+
}
|
|
331
|
+
throw err;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
ledger.report();
|
|
335
|
+
|
|
336
|
+
if (json) {
|
|
337
|
+
process.stdout.write(JSON.stringify(verdict, null, 2) + '\n');
|
|
338
|
+
return verdict.findings.length === 0 ? 0 : 1;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (verdict.findings.length === 0) {
|
|
342
|
+
if (!quiet) {
|
|
343
|
+
process.stdout.write(
|
|
344
|
+
`✅ no mid-session mutation of a prefix-stable surface ` +
|
|
345
|
+
`(${String(verdict.scanned)} script(s) across ${String(verdict.midSessionConcerns)} mid-session concern(s), ` +
|
|
346
|
+
`${String(PREFIX_STABLE_SURFACES.length)} declared surface(s)).\n`,
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
return 0;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
for (const f of verdict.findings) {
|
|
353
|
+
process.stderr.write(
|
|
354
|
+
`❌ ${f.concern} (${f.script}) fires on ${f.slots.join(', ')} and ${f.kind === 'literal' ? 'writes' : 'may write'} ` +
|
|
355
|
+
`inside prefix-stable surface '${f.surface}':\n ${f.evidence}\n`,
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
process.stderr.write(
|
|
359
|
+
`\n A mid-session write to a prefix-stable surface invalidates the prompt cache for the\n` +
|
|
360
|
+
` rest of the session — the next call pays the cache-WRITE rate over the whole prefix.\n` +
|
|
361
|
+
` Either move the write to a re-arm boundary (${RE_ARM_EVENTS.join(' / ')}), or declare\n` +
|
|
362
|
+
` 're_arm: <event>' on the concern in src/scripts/hook_manifest.yaml naming the boundary\n` +
|
|
363
|
+
` at which the rebuilt prefix is expected and paid for once.\n`,
|
|
364
|
+
);
|
|
365
|
+
return 1;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (process.env['GATE_SELF_TEST_CHILD'] !== '1' || process.argv.includes('--root')) {
|
|
369
|
+
if (process.argv[1] !== undefined && import.meta.url === `file://${process.argv[1]}`) {
|
|
370
|
+
process.exit(main());
|
|
371
|
+
}
|
|
372
|
+
}
|
|
@@ -127,7 +127,7 @@ export function buildSurface(root: string, payload: readonly string[]): PublishS
|
|
|
127
127
|
const shadowed = npmignore.filter((pat) => payload.some((p) => npmignoreMatches(pat, p)));
|
|
128
128
|
return {
|
|
129
129
|
_comment:
|
|
130
|
-
'GENERATED by
|
|
130
|
+
'GENERATED by check_publish_surface --write. Do not hand-edit. ' +
|
|
131
131
|
'It records the resolved publish surface so a change to what ships is a diff a reviewer reads. ' +
|
|
132
132
|
'`files[]` is an ALLOWLIST and it overrides .npmignore for anything under an included root — ' +
|
|
133
133
|
'measured, after an attempt to strip compiled test artefacts via .npmignore had no effect because ' +
|
|
@@ -228,6 +228,15 @@ export function buildSurfaceFrom(npmignore: readonly string[], payload: readonly
|
|
|
228
228
|
return npmignore.filter((pat) => payload.some((p) => npmignoreMatches(pat, p)));
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
+
// ledger-exempt: this gate has ONE target, not a population. The verdict is a
|
|
232
|
+
// single comparison — `driftKey(committed)` against `driftKey(live)` — over the
|
|
233
|
+
// resolved publish surface as a whole. `payload` is the SCANNED corpus (guarded
|
|
234
|
+
// by `assertScanned` and published as `scanned:`), not a set of targets each
|
|
235
|
+
// reaching its own outcome: no file in it is ever individually skipped,
|
|
236
|
+
// completed or failed. A ledger here would plan one target and complete one
|
|
237
|
+
// target, which measures nothing and reads as adoption. The silent-skip class
|
|
238
|
+
// the ledger exists to catch is closed here by a different mechanism: the
|
|
239
|
+
// gate's own `--self-test` proves the comparison discriminates.
|
|
231
240
|
export function main(argv: readonly string[] = process.argv.slice(2)): number {
|
|
232
241
|
if (argv.includes('--self-test')) return selfTest();
|
|
233
242
|
const payload = packPayload(REPO_ROOT);
|