@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
|
@@ -0,0 +1,779 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* The promotion-path structural invariant — discharge route 1 of the carried
|
|
4
|
+
* blocking condition on `road-to-harness-promotion-bridge`.
|
|
5
|
+
*
|
|
6
|
+
* WHY THIS GATE EXISTS
|
|
7
|
+
* --------------------
|
|
8
|
+
* The carried condition covers *"a verb, a state transition into `promoted`, or
|
|
9
|
+
* any write into `src/` derived from a candidate"*. Before this gate, TWO of
|
|
10
|
+
* those three limbs were enforced — `verbPromote` refuses unconditionally
|
|
11
|
+
* (`src/scripts/evolution_lab.ts`), and `assertTransition(_, 'promoted')`
|
|
12
|
+
* refuses without a named approver (`src/scripts/_lib/candidate_record.ts`) —
|
|
13
|
+
* and the third was enforced by nothing. The AI council of 2026-08-31 ruled the
|
|
14
|
+
* condition NOT DISCHARGED on exactly that gap, and named the discharging
|
|
15
|
+
* mechanism: an invariant that *inventories* every lifecycle-record write site
|
|
16
|
+
* and every `src/` write site and *fails* when a promotion-capable write does
|
|
17
|
+
* not pass through ONE guarded capability, where the capability is unobtainable
|
|
18
|
+
* while `blocker: merge-authority` is open.
|
|
19
|
+
*
|
|
20
|
+
* `src/scripts/_lib/promotion_capability.ts` is that capability. This is that
|
|
21
|
+
* invariant.
|
|
22
|
+
*
|
|
23
|
+
* THE FOUR RULES
|
|
24
|
+
* --------------
|
|
25
|
+
* R0 the capability is UNOBTAINABLE while the blocker is open. Probed by
|
|
26
|
+
* calling `acquirePromotionCapability` and requiring it to throw. This is
|
|
27
|
+
* executed, not read: a text match would pass over a module whose refusal
|
|
28
|
+
* had been edited into a no-op.
|
|
29
|
+
* R1 no approval synthesis outside {@link R1_ALLOWLIST}. Any construction of
|
|
30
|
+
* a `HumanApproval`, any reference to the type, and any three-argument
|
|
31
|
+
* `assertTransition(...)` call — the argument that supplies the approval.
|
|
32
|
+
* R2 no promoted-state record write outside {@link R2_ALLOWLIST}. A record
|
|
33
|
+
* literal carrying `lifecycle: 'promoted'` (or `ACCEPTED_STATE`) bypasses
|
|
34
|
+
* the transition gate entirely, which is the second bypass vector both
|
|
35
|
+
* council seats named.
|
|
36
|
+
* R3 no candidate-derived write into the repository source tree. A module
|
|
37
|
+
* that reads candidate data may not target `src/` with a filesystem
|
|
38
|
+
* write. This is the THIRD LIMB, the one nothing gated before.
|
|
39
|
+
*
|
|
40
|
+
* ANTI-VACUITY — this gate fails when it scans nothing
|
|
41
|
+
* ----------------------------------------------------
|
|
42
|
+
* Risk 2 of the roadmap's register is that a check over a population of zero
|
|
43
|
+
* discharges the condition while scanning nothing, and the condition's own text
|
|
44
|
+
* forbids exactly that. Three floors, all fail-closed:
|
|
45
|
+
* {@link MIN_FILES} source files, {@link MIN_CANDIDATE_MODULES} modules that
|
|
46
|
+
* actually read candidate data, and {@link MIN_WRITE_SITES} filesystem-write
|
|
47
|
+
* sites inside them. Below any of them the gate exits 2 — the population moved
|
|
48
|
+
* or the scanner broke, and either way its green would be false.
|
|
49
|
+
*
|
|
50
|
+
* **Gaming risk.** The cheapest degenerate pass is prose relocation: this gate
|
|
51
|
+
* strips block comments and whole-line `//` comments before matching, so a
|
|
52
|
+
* bypass written inside a block comment is invisible to it. That is deliberate
|
|
53
|
+
* and the direction is safe — this module and its neighbours DOCUMENT the
|
|
54
|
+
* constructs they refuse, and a gate that failed on its own documentation would
|
|
55
|
+
* be a reason to stop documenting it. A trailing `//` comment is NOT stripped,
|
|
56
|
+
* so prose there trips the gate rather than hiding a construct. Second
|
|
57
|
+
* degenerate pass: adding a file to {@link R1_ALLOWLIST} / {@link R2_ALLOWLIST}.
|
|
58
|
+
* Mitigation — the allowlists are literals in this file, so growing one is a
|
|
59
|
+
* visible diff, and `tests/scripts/lint_promotion_paths.test.ts` pins their exact
|
|
60
|
+
* membership so a silent addition fails a test rather than passing review.
|
|
61
|
+
* **Residual, stated rather than implied:** R3 resolves `const` bindings up to
|
|
62
|
+
* {@link CONST_HOPS} hops, so a destination assembled at runtime from a value
|
|
63
|
+
* carrying no `src` literal anywhere in its chain is not detectable textually.
|
|
64
|
+
* That residual is bounded rather than closed — such a write is a source-tree
|
|
65
|
+
* write, but it is only a PROMOTION when it also carries an approval or a
|
|
66
|
+
* promoted record, which R1 and R2 catch independently.
|
|
67
|
+
*
|
|
68
|
+
* ADVISORY STAGE — satisfied by measurement, not by absence. Run against the
|
|
69
|
+
* real corpus at authoring time this gate produced ZERO findings over 2812
|
|
70
|
+
* source and test files, 16 candidate-derived modules and 66 write sites, and
|
|
71
|
+
* it found and fixed two of its own defects on the way (a substitution that
|
|
72
|
+
* rewrote identifiers inside string literals, and an expansion that destroyed
|
|
73
|
+
* the `REPO_ROOT` token it keys on). It therefore lands at
|
|
74
|
+
* error with an empty baseline rather than as a wall over a frozen corpus.
|
|
75
|
+
* There is no `gate-violation-baselines.json` entry to promote FROM: the
|
|
76
|
+
* promotion condition would be "promote to error when the baseline reaches 0",
|
|
77
|
+
* and it is 0 on arrival.
|
|
78
|
+
*
|
|
79
|
+
* Exit codes: 0 clean · 1 a promotion-capable bypass · 2 misuse or dead scope.
|
|
80
|
+
*
|
|
81
|
+
* Usage:
|
|
82
|
+
* ./scripts-run src/scripts/lint_promotion_paths [--root <dir>] [--quiet]
|
|
83
|
+
* ./scripts-run src/scripts/lint_promotion_paths --self-test
|
|
84
|
+
*/
|
|
85
|
+
import * as fs from 'node:fs';
|
|
86
|
+
import * as os from 'node:os';
|
|
87
|
+
import * as path from 'node:path';
|
|
88
|
+
import process from 'node:process';
|
|
89
|
+
|
|
90
|
+
import { GateLedger } from './_lib/gate_ledger.js';
|
|
91
|
+
import { runGateCli, runSelfTest, type SelfTestCase } from './_lib/gate_self_test.js';
|
|
92
|
+
import { assertScanned, DeadScopeError, reportScanned } from './_lib/scan_scope.js';
|
|
93
|
+
import {
|
|
94
|
+
PromotionCapabilityUnobtainableError,
|
|
95
|
+
REPO_SOURCE_ROOT,
|
|
96
|
+
acquirePromotionCapability,
|
|
97
|
+
isRefusingStatus,
|
|
98
|
+
readMergeAuthorityStatus,
|
|
99
|
+
} from './_lib/promotion_capability.js';
|
|
100
|
+
|
|
101
|
+
const HERE = path.dirname(new URL(import.meta.url).pathname);
|
|
102
|
+
const REPO_ROOT = path.resolve(HERE, '..', '..');
|
|
103
|
+
|
|
104
|
+
export const GATE = 'lint_promotion_paths';
|
|
105
|
+
|
|
106
|
+
/** Roots walked for R1 and R2 — the authored source tree. */
|
|
107
|
+
export const SOURCE_ROOTS: readonly string[] = ['src'];
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Roots walked additionally for R3.
|
|
111
|
+
*
|
|
112
|
+
* Tests are OUT of R1/R2 scope and IN R3 scope, and the asymmetry is deliberate:
|
|
113
|
+
* a test must be able to construct the refused shape in order to test the
|
|
114
|
+
* refusal, and no test has any business writing into the source tree.
|
|
115
|
+
*/
|
|
116
|
+
export const R3_EXTRA_ROOTS: readonly string[] = ['tests'];
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* This gate's own file.
|
|
120
|
+
*
|
|
121
|
+
* A detector has to NAME what it detects — the patterns below and the self-test
|
|
122
|
+
* fixtures beside them contain every construct this gate refuses. A gate that
|
|
123
|
+
* failed on its own detector is the false-positive shape
|
|
124
|
+
* `check_no_currency_in_cost_surfaces` already solved the same way, and its
|
|
125
|
+
* discrimination is proven by `--self-test` against planted fixtures rather
|
|
126
|
+
* than by scanning itself.
|
|
127
|
+
*/
|
|
128
|
+
export const GATE_SELF = 'src/scripts/lint_promotion_paths.ts';
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* This gate's paired fixture file, for the same reason and with the same bound.
|
|
132
|
+
*
|
|
133
|
+
* `tests/scripts/lint_promotion_paths.test.ts` carries every construct this gate
|
|
134
|
+
* refuses, as string fixtures fed to the detectors — including a literal
|
|
135
|
+
* `import ... candidate_record.js`, which makes `isCandidateDerived` read the
|
|
136
|
+
* test itself as a candidate module. It ships nothing and can promote nothing;
|
|
137
|
+
* its membership here is pinned by an assertion inside that very file, so
|
|
138
|
+
* widening the allowlist is a visible diff in two places rather than one.
|
|
139
|
+
*/
|
|
140
|
+
export const GATE_TEST = 'tests/scripts/lint_promotion_paths.test.ts';
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* R1 allowlist — the files that may name a human approval.
|
|
144
|
+
*
|
|
145
|
+
* `candidate_record.ts` DEFINES `HumanApproval` and the transition gate;
|
|
146
|
+
* `promotion_capability.ts` is the one guarded mint. Anywhere else, naming the
|
|
147
|
+
* type is doing approval work outside the capability.
|
|
148
|
+
*/
|
|
149
|
+
export const R1_ALLOWLIST: readonly string[] = [
|
|
150
|
+
'src/scripts/_lib/candidate_record.ts',
|
|
151
|
+
'src/scripts/_lib/promotion_capability.ts',
|
|
152
|
+
GATE_SELF,
|
|
153
|
+
GATE_TEST,
|
|
154
|
+
];
|
|
155
|
+
|
|
156
|
+
/** R2 allowlist — narrower on purpose. Only the capability may express a promotion. */
|
|
157
|
+
export const R2_ALLOWLIST: readonly string[] = ['src/scripts/_lib/promotion_capability.ts', GATE_SELF, GATE_TEST];
|
|
158
|
+
|
|
159
|
+
/** R3 allowlist — the capability performs no write at all, and is listed for symmetry. */
|
|
160
|
+
export const R3_ALLOWLIST: readonly string[] = ['src/scripts/_lib/promotion_capability.ts', GATE_SELF, GATE_TEST];
|
|
161
|
+
|
|
162
|
+
/** Modules whose import marks a file as reading candidate data. */
|
|
163
|
+
export const CANDIDATE_DATA_MODULES: readonly string[] = [
|
|
164
|
+
'candidate_record',
|
|
165
|
+
'candidate_proposer',
|
|
166
|
+
'curator_ops',
|
|
167
|
+
'bench_ab_clone',
|
|
168
|
+
'promotion_evidence',
|
|
169
|
+
'promotion_review',
|
|
170
|
+
];
|
|
171
|
+
|
|
172
|
+
/** Anti-vacuity floors. Below any of these the population moved or the scanner broke. */
|
|
173
|
+
export const MIN_FILES = 400;
|
|
174
|
+
export const MIN_CANDIDATE_MODULES = 4;
|
|
175
|
+
export const MIN_WRITE_SITES = 10;
|
|
176
|
+
|
|
177
|
+
/** How far R3 follows a `const` binding when resolving a write destination. */
|
|
178
|
+
export const CONST_HOPS = 3;
|
|
179
|
+
|
|
180
|
+
const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'clones', '__pycache__']);
|
|
181
|
+
|
|
182
|
+
export interface Finding {
|
|
183
|
+
rule: 'R0' | 'R1' | 'R2' | 'R3';
|
|
184
|
+
file: string;
|
|
185
|
+
line: number;
|
|
186
|
+
what: string;
|
|
187
|
+
text: string;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ------------------------------------------------------------------ scanning
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Remove block comments and WHOLE-LINE `//` comments, preserving line count.
|
|
194
|
+
*
|
|
195
|
+
* A trailing `//` comment is left in place on purpose — stripping it would let a
|
|
196
|
+
* construct hide behind a preceding string containing `//`, and that is a
|
|
197
|
+
* cheaper bypass than the false positive it would prevent.
|
|
198
|
+
*/
|
|
199
|
+
export function stripComments(source: string): string {
|
|
200
|
+
const noBlocks = source.replace(/\/\*[\s\S]*?\*\//g, (m) => m.replace(/[^\n]/g, ' '));
|
|
201
|
+
return noBlocks
|
|
202
|
+
.split('\n')
|
|
203
|
+
.map((l) => (/^\s*\/\//.test(l) ? '' : l))
|
|
204
|
+
.join('\n');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Does this file import anything that reads candidate data? */
|
|
208
|
+
export function isCandidateDerived(source: string): boolean {
|
|
209
|
+
const body = stripComments(source);
|
|
210
|
+
return CANDIDATE_DATA_MODULES.some((m) =>
|
|
211
|
+
new RegExp(`from\\s+['"][^'"]*${m}(\\.js)?['"]`).test(body),
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function lineOf(body: string, index: number): number {
|
|
216
|
+
let n = 1;
|
|
217
|
+
for (let i = 0; i < index && i < body.length; i += 1) {
|
|
218
|
+
if (body[i] === '\n') n += 1;
|
|
219
|
+
}
|
|
220
|
+
return n;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Split a call's argument list into top-level arguments.
|
|
225
|
+
*
|
|
226
|
+
* `open` is the index of the `(`. Returns `null` when the parentheses do not
|
|
227
|
+
* balance within the file, which is a parse failure rather than a pass.
|
|
228
|
+
*/
|
|
229
|
+
export function callArguments(body: string, open: number): string[] | null {
|
|
230
|
+
let depth = 0;
|
|
231
|
+
let quote: string | null = null;
|
|
232
|
+
const args: string[] = [];
|
|
233
|
+
let current = '';
|
|
234
|
+
for (let i = open; i < body.length; i += 1) {
|
|
235
|
+
const c = body[i] as string;
|
|
236
|
+
if (quote !== null) {
|
|
237
|
+
current += c;
|
|
238
|
+
if (c === '\\') {
|
|
239
|
+
current += body[i + 1] ?? '';
|
|
240
|
+
i += 1;
|
|
241
|
+
} else if (c === quote) {
|
|
242
|
+
quote = null;
|
|
243
|
+
}
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
if (c === "'" || c === '"' || c === '`') {
|
|
247
|
+
quote = c;
|
|
248
|
+
current += c;
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
if (c === '(' || c === '[' || c === '{') {
|
|
252
|
+
depth += 1;
|
|
253
|
+
if (depth === 1 && c === '(') continue;
|
|
254
|
+
current += c;
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
if (c === ')' || c === ']' || c === '}') {
|
|
258
|
+
depth -= 1;
|
|
259
|
+
if (depth === 0 && c === ')') {
|
|
260
|
+
args.push(current);
|
|
261
|
+
return args;
|
|
262
|
+
}
|
|
263
|
+
current += c;
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
if (c === ',' && depth === 1) {
|
|
267
|
+
args.push(current);
|
|
268
|
+
current = '';
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
current += c;
|
|
272
|
+
}
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// --------------------------------------------------------------------- R1
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Approval synthesis: constructing, typing, or supplying a human approval.
|
|
280
|
+
*
|
|
281
|
+
* The `approver:` / `approvedAt:` detectors are CONSTRUCTION-specific — an
|
|
282
|
+
* interface field declaration (`readonly approver: string;`) is a type position
|
|
283
|
+
* and is excluded by the negative lookahead, so the definition site does not
|
|
284
|
+
* have to be allowlisted for the wrong reason.
|
|
285
|
+
*/
|
|
286
|
+
export function findApprovalSites(source: string): Finding[] {
|
|
287
|
+
const body = stripComments(source);
|
|
288
|
+
const out: Finding[] = [];
|
|
289
|
+
const patterns: Array<[string, RegExp]> = [
|
|
290
|
+
['approver-construction', /\bapprover\s*:\s*(?!(?:string|number|boolean|unknown|any)\b)\S/g],
|
|
291
|
+
['approvedAt-construction', /\bapprovedAt\s*:\s*(?!(?:string|number|boolean|unknown|any)\b)\S/g],
|
|
292
|
+
['HumanApproval-reference', /\bHumanApproval\b/g],
|
|
293
|
+
['assertHumanApproval-reference', /\bassertHumanApproval\b/g],
|
|
294
|
+
];
|
|
295
|
+
for (const [what, re] of patterns) {
|
|
296
|
+
re.lastIndex = 0;
|
|
297
|
+
let m: RegExpExecArray | null;
|
|
298
|
+
while ((m = re.exec(body)) !== null) {
|
|
299
|
+
out.push({
|
|
300
|
+
rule: 'R1',
|
|
301
|
+
file: '',
|
|
302
|
+
line: lineOf(body, m.index),
|
|
303
|
+
what,
|
|
304
|
+
text: (body.split('\n')[lineOf(body, m.index) - 1] ?? '').trim().slice(0, 120),
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
// A three-argument `assertTransition(from, to, approval)` supplies the
|
|
309
|
+
// approval the two-argument form cannot. The DECLARATION is excluded by the
|
|
310
|
+
// preceding-`function` test — its parameter list also carries two commas.
|
|
311
|
+
const callRe = /\bassertTransition\s*\(/g;
|
|
312
|
+
let c: RegExpExecArray | null;
|
|
313
|
+
while ((c = callRe.exec(body)) !== null) {
|
|
314
|
+
const before = body.slice(Math.max(0, c.index - 40), c.index);
|
|
315
|
+
if (/\bfunction\s*$/.test(before)) continue;
|
|
316
|
+
const args = callArguments(body, body.indexOf('(', c.index));
|
|
317
|
+
if (args !== null && args.length >= 3) {
|
|
318
|
+
out.push({
|
|
319
|
+
rule: 'R1',
|
|
320
|
+
file: '',
|
|
321
|
+
line: lineOf(body, c.index),
|
|
322
|
+
what: 'assertTransition-with-approval',
|
|
323
|
+
text: args.map((a) => a.trim()).join(', ').slice(0, 120),
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return out;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// --------------------------------------------------------------------- R2
|
|
331
|
+
|
|
332
|
+
/** A record literal that names the accepted state directly, bypassing the transition. */
|
|
333
|
+
export function findPromotedWriteSites(source: string): Finding[] {
|
|
334
|
+
const body = stripComments(source);
|
|
335
|
+
const out: Finding[] = [];
|
|
336
|
+
const patterns: Array<[string, RegExp]> = [
|
|
337
|
+
['promoted-literal', /\blifecycle\s*:\s*['"`]promoted['"`]/g],
|
|
338
|
+
['accepted-state-write', /\blifecycle\s*:\s*ACCEPTED_STATE\b/g],
|
|
339
|
+
];
|
|
340
|
+
for (const [what, re] of patterns) {
|
|
341
|
+
re.lastIndex = 0;
|
|
342
|
+
let m: RegExpExecArray | null;
|
|
343
|
+
while ((m = re.exec(body)) !== null) {
|
|
344
|
+
out.push({
|
|
345
|
+
rule: 'R2',
|
|
346
|
+
file: '',
|
|
347
|
+
line: lineOf(body, m.index),
|
|
348
|
+
what,
|
|
349
|
+
text: (body.split('\n')[lineOf(body, m.index) - 1] ?? '').trim().slice(0, 120),
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return out;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// --------------------------------------------------------------------- R3
|
|
357
|
+
|
|
358
|
+
/** Write calls, and which argument carries the DESTINATION. */
|
|
359
|
+
export const WRITE_CALLS: Readonly<Record<string, number>> = {
|
|
360
|
+
writeFileSync: 0,
|
|
361
|
+
writeFile: 0,
|
|
362
|
+
appendFileSync: 0,
|
|
363
|
+
appendFile: 0,
|
|
364
|
+
mkdirSync: 0,
|
|
365
|
+
rmSync: 0,
|
|
366
|
+
rmdirSync: 0,
|
|
367
|
+
unlinkSync: 0,
|
|
368
|
+
createWriteStream: 0,
|
|
369
|
+
outputFileSync: 0,
|
|
370
|
+
copyFileSync: 1,
|
|
371
|
+
cpSync: 1,
|
|
372
|
+
renameSync: 1,
|
|
373
|
+
linkSync: 1,
|
|
374
|
+
symlinkSync: 1,
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Tokens that root a path at the REPOSITORY, as opposed to a clone or a tmpdir.
|
|
379
|
+
*
|
|
380
|
+
* The discriminator matters: `join(victim, 'src', 'smuggled.ts')` in
|
|
381
|
+
* `tests/scripts/bench_ab_candidate.test.ts:384` writes into a CLONE's `src/`,
|
|
382
|
+
* which is a candidate's own sandbox and is already gated — by
|
|
383
|
+
* `bench_ab_integrity`'s allowed-delta-path check, whose sensitivity that very
|
|
384
|
+
* test proves. Promotion is a candidate reaching CANONICAL `agent-config`, so
|
|
385
|
+
* this rule is anchored at the repository root and says so.
|
|
386
|
+
*/
|
|
387
|
+
export const REPO_ROOT_TOKENS = /\b(?:REPO_ROOT|CAPABILITY_REPO_ROOT|repoRoot|repo_root|ROOT|root|cwd)\b/;
|
|
388
|
+
|
|
389
|
+
/** A string literal naming `src` as a path segment. */
|
|
390
|
+
const SRC_SEGMENT = new RegExp(`['"\`]${REPO_SOURCE_ROOT}['"\`]`);
|
|
391
|
+
|
|
392
|
+
/** A destination given as a bare repo-relative literal — `'src/...'`, `"src"`. */
|
|
393
|
+
const SRC_FIRST_LITERAL = new RegExp(`^\\s*['"\`]${REPO_SOURCE_ROOT}(?:/|['"\`])`);
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Does this write destination resolve to the canonical source tree?
|
|
397
|
+
*
|
|
398
|
+
* Two accepted shapes, and nothing else: a repo-root token joined with a `src`
|
|
399
|
+
* segment, or a destination whose FIRST component is a `src`-rooted literal.
|
|
400
|
+
* `original` is the unexpanded expression, because the first-component test is
|
|
401
|
+
* about how the destination was written, not about what it expands to; the
|
|
402
|
+
* two-shape test below reads the UNION of both, for the reason stated in the
|
|
403
|
+
* body.
|
|
404
|
+
*/
|
|
405
|
+
export function targetsCanonicalSource(original: string, expanded: string): boolean {
|
|
406
|
+
if (SRC_FIRST_LITERAL.test(original)) {
|
|
407
|
+
return true;
|
|
408
|
+
}
|
|
409
|
+
// Both texts, unioned. Expansion can DESTROY the signal as easily as reveal
|
|
410
|
+
// it: `REPO_ROOT` is itself a `const`, so substituting it replaces the very
|
|
411
|
+
// token this rule keys on — measured, on the first sensitivity probe, which
|
|
412
|
+
// fired R1 and R2 on a planted bypass and missed its R3 half entirely.
|
|
413
|
+
const combined = `${original} ${expanded}`;
|
|
414
|
+
return REPO_ROOT_TOKENS.test(combined) && SRC_SEGMENT.test(combined);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/** Module- and function-level `const NAME = <expr>;` bindings, for one-name resolution. */
|
|
418
|
+
export function constBindings(body: string): Map<string, string> {
|
|
419
|
+
const out = new Map<string, string>();
|
|
420
|
+
const re = /\b(?:const|let)\s+([A-Za-z_$][\w$]*)\s*(?::[^=]*)?=\s*([^;\n]*(?:\n[^;]*)?);/g;
|
|
421
|
+
let m: RegExpExecArray | null;
|
|
422
|
+
while ((m = re.exec(body)) !== null) {
|
|
423
|
+
const name = m[1] as string;
|
|
424
|
+
if (!out.has(name)) out.set(name, (m[2] as string).replace(/\s+/g, ' '));
|
|
425
|
+
}
|
|
426
|
+
return out;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Substitute bound identifiers into `expr`, up to {@link CONST_HOPS} hops.
|
|
431
|
+
*
|
|
432
|
+
* Substitution IN PLACE rather than accumulation: an earlier draft appended each
|
|
433
|
+
* binding's value to a growing string and then matched over the whole thing, so
|
|
434
|
+
* one unrelated binding anywhere in the chain contaminated the verdict — it
|
|
435
|
+
* reported `rmSync(scratch)` in a test as a write into `src/`. Callees are not
|
|
436
|
+
* expanded (`join(...)` is a function, not a path), and a binding longer than
|
|
437
|
+
* {@link MAX_BINDING_CHARS} is skipped because an object or an array literal is
|
|
438
|
+
* not a path expression.
|
|
439
|
+
*/
|
|
440
|
+
export const MAX_BINDING_CHARS = 200;
|
|
441
|
+
|
|
442
|
+
export function expandExpression(expr: string, bindings: Map<string, string>): string {
|
|
443
|
+
let current = expr;
|
|
444
|
+
for (let hop = 0; hop < CONST_HOPS; hop += 1) {
|
|
445
|
+
const next = substituteOutsideStrings(current, (name, followedByCall) => {
|
|
446
|
+
if (followedByCall) return name;
|
|
447
|
+
const value = bindings.get(name);
|
|
448
|
+
if (value === undefined || value === name || value.length > MAX_BINDING_CHARS) return name;
|
|
449
|
+
return `(${value})`;
|
|
450
|
+
});
|
|
451
|
+
if (next === current) break;
|
|
452
|
+
current = next;
|
|
453
|
+
}
|
|
454
|
+
return current;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Apply `replace` to identifiers in CODE positions only, never inside a string
|
|
459
|
+
* literal.
|
|
460
|
+
*
|
|
461
|
+
* The second defect this function exists for, and it was found by running the
|
|
462
|
+
* gate rather than by reading it: substituting over the raw text rewrote the
|
|
463
|
+
* identifier `cand` INSIDE the literal `'ac-cand-'`, because `-` is a word
|
|
464
|
+
* boundary. The injected value happened to carry `join(REPO_ROOT, 'src', ...)`,
|
|
465
|
+
* so a `rmSync(scratch)` on a temp directory was reported as a write into the
|
|
466
|
+
* source tree. A substitution that can edit string contents is not resolving a
|
|
467
|
+
* path expression, it is corrupting one.
|
|
468
|
+
*/
|
|
469
|
+
export function substituteOutsideStrings(
|
|
470
|
+
source: string,
|
|
471
|
+
replace: (name: string, followedByCall: boolean) => string,
|
|
472
|
+
): string {
|
|
473
|
+
let out = '';
|
|
474
|
+
let code = '';
|
|
475
|
+
let quote: string | null = null;
|
|
476
|
+
const flush = (): void => {
|
|
477
|
+
out += code.replace(/\b[A-Za-z_$][\w$]*\b/g, (name, offset: number, whole: string) =>
|
|
478
|
+
replace(name, whole[offset + name.length] === '('),
|
|
479
|
+
);
|
|
480
|
+
code = '';
|
|
481
|
+
};
|
|
482
|
+
for (let i = 0; i < source.length; i += 1) {
|
|
483
|
+
const c = source[i] as string;
|
|
484
|
+
if (quote !== null) {
|
|
485
|
+
out += c;
|
|
486
|
+
if (c === '\\') {
|
|
487
|
+
out += source[i + 1] ?? '';
|
|
488
|
+
i += 1;
|
|
489
|
+
} else if (c === quote) {
|
|
490
|
+
quote = null;
|
|
491
|
+
}
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
if (c === "'" || c === '"' || c === '`') {
|
|
495
|
+
flush();
|
|
496
|
+
quote = c;
|
|
497
|
+
out += c;
|
|
498
|
+
continue;
|
|
499
|
+
}
|
|
500
|
+
code += c;
|
|
501
|
+
}
|
|
502
|
+
flush();
|
|
503
|
+
return out;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
export interface WriteSite {
|
|
507
|
+
line: number;
|
|
508
|
+
fn: string;
|
|
509
|
+
dest: string;
|
|
510
|
+
targetsSource: boolean;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/** Every filesystem-write site in a module, with its destination classified. */
|
|
514
|
+
export function findWriteSites(source: string): WriteSite[] {
|
|
515
|
+
const body = stripComments(source);
|
|
516
|
+
const bindings = constBindings(body);
|
|
517
|
+
const out: WriteSite[] = [];
|
|
518
|
+
const re = new RegExp(`\\b(${Object.keys(WRITE_CALLS).join('|')})\\s*\\(`, 'g');
|
|
519
|
+
let m: RegExpExecArray | null;
|
|
520
|
+
while ((m = re.exec(body)) !== null) {
|
|
521
|
+
const fn = m[1] as string;
|
|
522
|
+
const before = body.slice(Math.max(0, m.index - 40), m.index);
|
|
523
|
+
if (/\bfunction\s*$/.test(before)) continue;
|
|
524
|
+
const args = callArguments(body, body.indexOf('(', m.index));
|
|
525
|
+
if (args === null) continue;
|
|
526
|
+
const destIndex = WRITE_CALLS[fn] as number;
|
|
527
|
+
const dest = (args[destIndex] ?? '').trim();
|
|
528
|
+
if (dest === '') continue;
|
|
529
|
+
out.push({
|
|
530
|
+
line: lineOf(body, m.index),
|
|
531
|
+
fn,
|
|
532
|
+
dest: dest.slice(0, 120),
|
|
533
|
+
targetsSource: targetsCanonicalSource(dest, expandExpression(dest, bindings)),
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
return out;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// -------------------------------------------------------------------- walk
|
|
540
|
+
|
|
541
|
+
export function listTypeScript(root: string, rel = ''): string[] {
|
|
542
|
+
const abs = path.join(root, rel);
|
|
543
|
+
let entries: fs.Dirent[];
|
|
544
|
+
try {
|
|
545
|
+
entries = fs.readdirSync(abs, { withFileTypes: true });
|
|
546
|
+
} catch {
|
|
547
|
+
return [];
|
|
548
|
+
}
|
|
549
|
+
const out: string[] = [];
|
|
550
|
+
for (const e of entries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))) {
|
|
551
|
+
if (SKIP_DIRS.has(e.name)) continue;
|
|
552
|
+
const child = rel === '' ? e.name : `${rel}/${e.name}`;
|
|
553
|
+
if (e.isDirectory()) out.push(...listTypeScript(root, child));
|
|
554
|
+
else if (e.name.endsWith('.ts') && !e.name.endsWith('.d.ts')) out.push(child);
|
|
555
|
+
}
|
|
556
|
+
return out;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
export interface Evaluation {
|
|
560
|
+
scanned: number;
|
|
561
|
+
candidateModules: string[];
|
|
562
|
+
writeSites: number;
|
|
563
|
+
findings: Finding[];
|
|
564
|
+
capabilityStatus: string;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
export function evaluate(root: string = REPO_ROOT, ledger?: GateLedger): Evaluation {
|
|
568
|
+
const r12Files = SOURCE_ROOTS.flatMap((r) => listTypeScript(path.join(root, r)).map((f) => `${r}/${f}`));
|
|
569
|
+
const r3Only = R3_EXTRA_ROOTS.flatMap((r) => listTypeScript(path.join(root, r)).map((f) => `${r}/${f}`));
|
|
570
|
+
const all = [...r12Files, ...r3Only];
|
|
571
|
+
ledger?.plan(all);
|
|
572
|
+
|
|
573
|
+
const findings: Finding[] = [];
|
|
574
|
+
const candidateModules: string[] = [];
|
|
575
|
+
let writeSites = 0;
|
|
576
|
+
|
|
577
|
+
for (const rel of all) {
|
|
578
|
+
const source = fs.readFileSync(path.join(root, rel), 'utf-8');
|
|
579
|
+
const hits: Finding[] = [];
|
|
580
|
+
const inR12 = r12Files.includes(rel);
|
|
581
|
+
|
|
582
|
+
if (inR12 && !R1_ALLOWLIST.includes(rel)) {
|
|
583
|
+
hits.push(...findApprovalSites(source).map((f) => ({ ...f, file: rel })));
|
|
584
|
+
}
|
|
585
|
+
if (inR12 && !R2_ALLOWLIST.includes(rel)) {
|
|
586
|
+
hits.push(...findPromotedWriteSites(source).map((f) => ({ ...f, file: rel })));
|
|
587
|
+
}
|
|
588
|
+
if (isCandidateDerived(source)) {
|
|
589
|
+
candidateModules.push(rel);
|
|
590
|
+
const sites = findWriteSites(source);
|
|
591
|
+
writeSites += sites.length;
|
|
592
|
+
if (!R3_ALLOWLIST.includes(rel)) {
|
|
593
|
+
for (const s of sites.filter((x) => x.targetsSource)) {
|
|
594
|
+
hits.push({
|
|
595
|
+
rule: 'R3',
|
|
596
|
+
file: rel,
|
|
597
|
+
line: s.line,
|
|
598
|
+
what: `candidate-derived ${s.fn} into ${REPO_SOURCE_ROOT}/`,
|
|
599
|
+
text: s.dest,
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
findings.push(...hits);
|
|
606
|
+
if (hits.length > 0) ledger?.fail(rel, `${String(hits.length)} promotion-path finding(s)`);
|
|
607
|
+
else ledger?.complete(rel);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// R0 — the capability must be unobtainable unless the blocker reads GRANTED.
|
|
611
|
+
// Called, not read: a text match would pass over a refusal edited into a
|
|
612
|
+
// no-op. `resolved` now means `Status: resolved` AND `Disposition: granted`;
|
|
613
|
+
// a blocker closed as `refused`, or closed without saying which, is a
|
|
614
|
+
// refusing status here exactly like an open one.
|
|
615
|
+
const status = readMergeAuthorityStatus(root);
|
|
616
|
+
if (isRefusingStatus(status)) {
|
|
617
|
+
let refused = false;
|
|
618
|
+
try {
|
|
619
|
+
acquirePromotionCapability({ approver: 'gate probe', approvedAt: '1970-01-01' }, root);
|
|
620
|
+
} catch (e) {
|
|
621
|
+
refused = e instanceof PromotionCapabilityUnobtainableError;
|
|
622
|
+
}
|
|
623
|
+
if (!refused) {
|
|
624
|
+
findings.push({
|
|
625
|
+
rule: 'R0',
|
|
626
|
+
file: 'src/scripts/_lib/promotion_capability.ts',
|
|
627
|
+
line: 1,
|
|
628
|
+
what: 'capability obtainable while the blocker does not read granted',
|
|
629
|
+
text: `blocker status ${status}, yet acquirePromotionCapability() returned a token`,
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
return { scanned: all.length, candidateModules, writeSites, findings, capabilityStatus: status };
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// ---------------------------------------------------------------- self-test
|
|
638
|
+
|
|
639
|
+
function selfTestCases(): SelfTestCase[] {
|
|
640
|
+
const plant = (name: string, expect: 'reject' | 'accept', rel: string, body: string): SelfTestCase => ({
|
|
641
|
+
name,
|
|
642
|
+
expect,
|
|
643
|
+
run: () => {
|
|
644
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'promo-'));
|
|
645
|
+
try {
|
|
646
|
+
// A minimal but ABOVE-FLOOR tree: the anti-vacuity floors must not
|
|
647
|
+
// be what decides these cases, so the fixture carries enough files,
|
|
648
|
+
// candidate modules and write sites to clear them.
|
|
649
|
+
fs.mkdirSync(path.join(dir, 'src', 'scripts', '_lib'), { recursive: true });
|
|
650
|
+
fs.mkdirSync(path.join(dir, 'tests'), { recursive: true });
|
|
651
|
+
fs.mkdirSync(path.join(dir, 'agents', 'roadmaps'), { recursive: true });
|
|
652
|
+
fs.writeFileSync(
|
|
653
|
+
path.join(dir, 'agents', 'roadmaps', 'road-to-harness-promotion-bridge.md'),
|
|
654
|
+
'### blocker: merge-authority\n\n- **Status:** open\n',
|
|
655
|
+
);
|
|
656
|
+
for (let i = 0; i < MIN_FILES + 5; i += 1) {
|
|
657
|
+
fs.writeFileSync(path.join(dir, 'src', 'scripts', `filler_${String(i)}.ts`), 'export const x = 1;\n');
|
|
658
|
+
}
|
|
659
|
+
for (let i = 0; i < MIN_CANDIDATE_MODULES + 1; i += 1) {
|
|
660
|
+
fs.writeFileSync(
|
|
661
|
+
path.join(dir, 'src', 'scripts', `cand_${String(i)}.ts`),
|
|
662
|
+
"import { parseCandidateRecord } from './_lib/candidate_record.js';\n" +
|
|
663
|
+
'const out = "clones/x";\n' +
|
|
664
|
+
'fs.writeFileSync(out, parseCandidateRecord);\n' +
|
|
665
|
+
'fs.mkdirSync(out);\nfs.rmSync(out);\n',
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
fs.writeFileSync(path.join(dir, rel), body);
|
|
669
|
+
return runGateCli(REPO_ROOT, 'src/scripts/lint_promotion_paths.ts', ['--root', dir, '--quiet'], REPO_ROOT);
|
|
670
|
+
} finally {
|
|
671
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
672
|
+
}
|
|
673
|
+
},
|
|
674
|
+
});
|
|
675
|
+
const CAND = "import { parseCandidateRecord } from './_lib/candidate_record.js';\n";
|
|
676
|
+
return [
|
|
677
|
+
plant('a synthesised approver → reject', 'reject', 'src/scripts/rogue.ts',
|
|
678
|
+
"assertTransition(from, 'promoted', { approver: 'ci', approvedAt: '2026-01-01' });\n"),
|
|
679
|
+
plant('a HumanApproval reference outside the allowlist → reject', 'reject', 'src/scripts/rogue.ts',
|
|
680
|
+
'const a: HumanApproval = load();\n'),
|
|
681
|
+
plant('a three-argument assertTransition → reject', 'reject', 'src/scripts/rogue.ts',
|
|
682
|
+
'assertTransition(a, b, c);\n'),
|
|
683
|
+
plant('a record written straight into the promoted state → reject', 'reject', 'src/scripts/rogue.ts',
|
|
684
|
+
"const r = { kind: 'candidate', lifecycle: 'promoted' };\n"),
|
|
685
|
+
plant('a promoted state via the accepted-state constant → reject', 'reject', 'src/scripts/rogue.ts',
|
|
686
|
+
'const r = { lifecycle: ACCEPTED_STATE };\n'),
|
|
687
|
+
plant('a candidate-derived write into src/ → reject', 'reject', 'src/scripts/rogue.ts',
|
|
688
|
+
`${CAND}fs.writeFileSync(path.join(root, 'src', 'rules', 'x.md'), parseCandidateRecord);\n`),
|
|
689
|
+
plant('a candidate-derived copy INTO src/, second argument → reject', 'reject', 'src/scripts/rogue.ts',
|
|
690
|
+
`${CAND}fs.copyFileSync(from, 'src/skills/x/SKILL.md');\n`),
|
|
691
|
+
plant('a candidate-derived write behind a const hop → reject', 'reject', 'src/scripts/rogue.ts',
|
|
692
|
+
`${CAND}const dest = path.join(root, 'src', 'rules');\nfs.writeFileSync(dest, body);\n`),
|
|
693
|
+
plant('a TEST writing into src/ from candidate data → reject', 'reject', 'tests/rogue.test.ts',
|
|
694
|
+
`${CAND}fs.writeFileSync('src/rules/x.md', body);\n`),
|
|
695
|
+
// Accepting poles — each pins a direction a widened pattern would break.
|
|
696
|
+
plant('the interface field declaration is not a construction → accept', 'accept', 'src/scripts/ok.ts',
|
|
697
|
+
'interface A { readonly approver: string; readonly approvedAt: string; }\n'),
|
|
698
|
+
plant('a two-argument assertTransition → accept', 'accept', 'src/scripts/ok.ts',
|
|
699
|
+
"assertTransition(from, 'promoted');\n"),
|
|
700
|
+
plant('a candidate-derived write to a clone directory → accept', 'accept', 'src/scripts/ok.ts',
|
|
701
|
+
`${CAND}const dest = path.join(CLONES, 'candidate-1');\nfs.writeFileSync(dest, body);\n`),
|
|
702
|
+
plant('a candidate-derived READ of src/ → accept', 'accept', 'src/scripts/ok.ts',
|
|
703
|
+
`${CAND}const t = fs.readFileSync(path.join(root, 'src', 'x.ts'));\n`),
|
|
704
|
+
plant('prose in a block comment naming the constructs → accept', 'accept', 'src/scripts/ok.ts',
|
|
705
|
+
'/* never write approver: or lifecycle: "promoted" or HumanApproval here */\nexport const x = 1;\n'),
|
|
706
|
+
plant('a non-candidate module writing into src/ → accept', 'accept', 'src/scripts/ok.ts',
|
|
707
|
+
"fs.writeFileSync(path.join(root, 'src', 'generated.ts'), body);\n"),
|
|
708
|
+
];
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// --------------------------------------------------------------------- CLI
|
|
712
|
+
|
|
713
|
+
export function main(argv: string[] = process.argv.slice(2)): number {
|
|
714
|
+
if (argv.includes('--self-test')) {
|
|
715
|
+
return runSelfTest({ gate: GATE, cases: selfTestCases(), minCases: 14, minRejectCases: 9 });
|
|
716
|
+
}
|
|
717
|
+
const quiet = argv.includes('--quiet');
|
|
718
|
+
const ri = argv.indexOf('--root');
|
|
719
|
+
const rootArg = ri !== -1 ? argv[ri + 1] : undefined;
|
|
720
|
+
const root = rootArg !== undefined ? path.resolve(rootArg) : REPO_ROOT;
|
|
721
|
+
|
|
722
|
+
const ledger = new GateLedger(GATE);
|
|
723
|
+
let v: Evaluation;
|
|
724
|
+
try {
|
|
725
|
+
v = evaluate(root, ledger);
|
|
726
|
+
} catch (err) {
|
|
727
|
+
process.stderr.write(`❌ ${GATE}: ${(err as Error).message}\n`);
|
|
728
|
+
return 2;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
try {
|
|
732
|
+
reportScanned({
|
|
733
|
+
gate: GATE,
|
|
734
|
+
scanned: v.scanned,
|
|
735
|
+
units: 'TypeScript file(s)',
|
|
736
|
+
roots: [...SOURCE_ROOTS, ...R3_EXTRA_ROOTS],
|
|
737
|
+
});
|
|
738
|
+
// The anti-vacuity floors. The condition this gate discharges says in as
|
|
739
|
+
// many words that a check over a population of zero discharges nothing,
|
|
740
|
+
// so a collapsed population is a dead scope rather than a clean run.
|
|
741
|
+
assertScanned({ gate: `${GATE}/files`, scanned: v.scanned >= MIN_FILES ? v.scanned : 0, units: `TypeScript file(s) (floor ${String(MIN_FILES)})`, roots: [...SOURCE_ROOTS, ...R3_EXTRA_ROOTS] });
|
|
742
|
+
assertScanned({ gate: `${GATE}/candidate-modules`, scanned: v.candidateModules.length >= MIN_CANDIDATE_MODULES ? v.candidateModules.length : 0, units: `candidate-derived module(s) (floor ${String(MIN_CANDIDATE_MODULES)})`, roots: [...SOURCE_ROOTS, ...R3_EXTRA_ROOTS] });
|
|
743
|
+
assertScanned({ gate: `${GATE}/write-sites`, scanned: v.writeSites >= MIN_WRITE_SITES ? v.writeSites : 0, units: `filesystem-write site(s) in candidate-derived modules (floor ${String(MIN_WRITE_SITES)})`, roots: v.candidateModules });
|
|
744
|
+
} catch (err) {
|
|
745
|
+
if (err instanceof DeadScopeError) {
|
|
746
|
+
process.stderr.write(`❌ ${GATE}: ${err.message}\n`);
|
|
747
|
+
return 2;
|
|
748
|
+
}
|
|
749
|
+
throw err;
|
|
750
|
+
}
|
|
751
|
+
ledger.report();
|
|
752
|
+
|
|
753
|
+
if (v.findings.length > 0) {
|
|
754
|
+
for (const f of v.findings) {
|
|
755
|
+
process.stderr.write(`❌ ${f.file}:${String(f.line)} ${f.rule} ${f.what}\n ${f.text}\n`);
|
|
756
|
+
}
|
|
757
|
+
process.stderr.write(
|
|
758
|
+
`\n ${String(v.findings.length)} promotion-capable write(s) bypass the guarded capability\n` +
|
|
759
|
+
' (src/scripts/_lib/promotion_capability.ts). Promotion into canonical agent-config\n' +
|
|
760
|
+
' is gated on blocker merge-authority (ADR-239 Decision 3), which is owner-reserved.\n' +
|
|
761
|
+
' Route the write through acquirePromotionCapability, or do not perform it.\n',
|
|
762
|
+
);
|
|
763
|
+
return 1;
|
|
764
|
+
}
|
|
765
|
+
if (!quiet) {
|
|
766
|
+
process.stdout.write(
|
|
767
|
+
`✅ ${GATE}: no promotion path bypasses the guarded capability ` +
|
|
768
|
+
`(${String(v.scanned)} file(s), ${String(v.candidateModules.length)} candidate-derived ` +
|
|
769
|
+
`module(s), ${String(v.writeSites)} write site(s); blocker status: ${v.capabilityStatus}).\n`,
|
|
770
|
+
);
|
|
771
|
+
}
|
|
772
|
+
return 0;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
if (process.env['GATE_SELF_TEST_CHILD'] !== '1' || process.argv.includes('--root')) {
|
|
776
|
+
if (process.argv[1] !== undefined && import.meta.url === `file://${process.argv[1]}`) {
|
|
777
|
+
process.exit(main());
|
|
778
|
+
}
|
|
779
|
+
}
|