@event4u/agent-config 16.1.0 → 16.3.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 +98 -257
- package/README.md +14 -12
- package/dist/agent-src/commands/analyze/inbox.md +1 -1
- package/dist/agent-src/commands/analyze/roadmap-repos.md +1 -1
- package/dist/agent-src/commands/bug/fix.md +17 -2
- package/dist/agent-src/commands/feature/plan.md +1 -1
- package/dist/agent-src/commands/feature/roadmap.md +1 -1
- package/dist/agent-src/commands/fix/ci.md +1 -1
- package/dist/agent-src/commands/implement-ticket.md +1 -1
- package/dist/agent-src/commands/jira-ticket.md +1 -1
- package/dist/agent-src/commands/roadmap/create.md +1 -1
- package/dist/agent-src/commands/roadmap/materialize.md +1 -1
- package/dist/agent-src/commands/roadmap/process-full.md +5 -5
- package/dist/agent-src/commands/roadmap/resolve-blockers.md +361 -0
- package/dist/agent-src/commands/roadmap/triage-parked.md +325 -0
- package/dist/agent-src/commands/roadmap.md +18 -4
- package/dist/agent-src/commands/tests/create.md +17 -3
- package/dist/agent-src/commands/tests/execute.md +22 -5
- package/dist/agent-src/commands/work.md +4 -0
- package/dist/agent-src/contexts/authority/decision-revisit-gate-enforcement.md +43 -0
- package/dist/agent-src/contexts/communication/rules-auto/design-fidelity-enforcement.md +24 -0
- package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +6 -6
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +4 -8
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +32 -2
- package/dist/agent-src/contexts/execution/session-canary-enforcement-history.md +67 -0
- package/dist/agent-src/contexts/execution/toolchain-resolver.md +101 -7
- package/dist/agent-src/rules/active-remediation.md +1 -1
- package/dist/agent-src/rules/autonomous-execution.md +1 -0
- package/dist/agent-src/rules/broken-access-control.md +1 -0
- package/dist/agent-src/rules/command-suggestion-policy.md +1 -0
- package/dist/agent-src/rules/context-hygiene.md +1 -1
- package/dist/agent-src/rules/decision-revisit-gate.md +16 -33
- package/dist/agent-src/rules/design-fidelity.md +13 -16
- package/dist/agent-src/rules/doc-screenshot-hygiene.md +1 -0
- package/dist/agent-src/rules/domain-safety-disclaimer.md +1 -0
- package/dist/agent-src/rules/domain-safety-pii.md +1 -0
- package/dist/agent-src/rules/domain-safety-retention.md +1 -0
- package/dist/agent-src/rules/engineering-safety-floor.md +1 -0
- package/dist/agent-src/rules/evaluator-independence.md +3 -2
- package/dist/agent-src/rules/fast-path-marker-visibility.md +1 -0
- package/dist/agent-src/rules/finance-safety-floor.md +1 -0
- package/dist/agent-src/rules/git-history-discipline.md +1 -0
- package/dist/agent-src/rules/image-likeness-and-rights.md +1 -0
- package/dist/agent-src/rules/legal-safety-floor.md +3 -2
- package/dist/agent-src/rules/lethal-trifecta-guard.md +1 -0
- package/dist/agent-src/rules/low-impact-corpus-privacy-floor.md +1 -0
- package/dist/agent-src/rules/media-governance-routing.md +14 -17
- package/dist/agent-src/rules/persona-governance.md +2 -2
- package/dist/agent-src/rules/provider-lifecycle-discipline.md +1 -1
- package/dist/agent-src/rules/question-not-instruction.md +1 -0
- package/dist/agent-src/rules/roadmap-progress-sync.md +1 -1
- package/dist/agent-src/rules/runtime-safety.md +1 -0
- package/dist/agent-src/rules/secret-vcs-guard.md +1 -0
- package/dist/agent-src/rules/security-sensitive-stop.md +3 -2
- package/dist/agent-src/rules/session-canary.md +8 -62
- package/dist/agent-src/rules/settings-ask-protocol.md +1 -0
- package/dist/agent-src/rules/source-confidentiality.md +1 -0
- package/dist/agent-src/rules/strategy-safety-floor.md +1 -0
- package/dist/agent-src/rules/tool-safety.md +1 -0
- package/dist/agent-src/rules/ui-audit-gate.md +1 -2
- package/dist/agent-src/rules/untrusted-input-defense.md +1 -0
- package/dist/agent-src/rules/user-interrupt-priority.md +1 -0
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +115 -20
- package/dist/agent-src/scripts/deferral_carry.ts +339 -0
- package/dist/agent-src/scripts/update_roadmap_progress.ts +4 -1
- package/dist/agent-src/skills/accessibility-auditor/data/aria-patterns.csv +1 -1
- package/dist/agent-src/skills/accessibility-auditor/data/manifest.json +2 -2
- package/dist/agent-src/skills/api-design/data/manifest.json +2 -2
- package/dist/agent-src/skills/api-testing/SKILL.md +17 -0
- package/dist/agent-src/skills/code-intelligence/SKILL.md +14 -2
- package/dist/agent-src/skills/design-review/SKILL.md +30 -4
- package/dist/agent-src/skills/design-review/references/verification-automation.md +32 -0
- package/dist/agent-src/skills/design-system-capture/references/import-procedure.md +1 -1
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +18 -3
- package/dist/agent-src/skills/existing-ui-audit/references/component-taxonomy-detection.md +56 -0
- package/dist/agent-src/skills/fe-design/SKILL.md +1 -1
- package/dist/agent-src/skills/iconography/SKILL.md +1 -1
- package/dist/agent-src/skills/js-library-packaging/SKILL.md +2 -2
- package/dist/agent-src/skills/markitdown/SKILL.md +4 -0
- package/dist/agent-src/skills/monorepo-workspace/SKILL.md +1 -1
- package/dist/agent-src/skills/playwright-testing/SKILL.md +30 -6
- package/dist/agent-src/skills/quality-tools/SKILL.md +11 -1
- package/dist/agent-src/skills/quality-tools/references/go-tools.md +76 -0
- package/dist/agent-src/skills/quality-tools/references/python-tools.md +104 -0
- package/dist/agent-src/skills/review-routing/SKILL.md +2 -2
- package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +1 -1
- package/dist/agent-src/skills/storybook-workshop/SKILL.md +3 -3
- package/dist/agent-src/skills/tailwind-engineer/SKILL.md +1 -1
- package/dist/agent-src/skills/test-case-discovery/SKILL.md +7 -0
- package/dist/agent-src/skills/test-driven-development/SKILL.md +17 -0
- package/dist/agent-src/skills/test-performance/SKILL.md +19 -1
- package/dist/agent-src/skills/ui-component-architect/SKILL.md +25 -2
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +30 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +302 -21
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
- package/dist/agent-src/templates/scripts/work_engine/stack/runner.ts +774 -109
- package/dist/agent-src/templates/scripts/work_engine/state.ts +57 -7
- package/dist/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
- package/dist/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
- package/dist/cli/python/knowledge_ingest.js +1 -1
- package/dist/cli/python/workspace_drive.js +1 -1
- package/dist/cli/registry.js +1 -1
- package/dist/cli-delegate/archive_completed_roadmaps.js +92 -12
- package/dist/cli-delegate/check_update_banner.js +12 -1
- package/dist/cli-delegate/chunk-523HCFYO.js +90 -0
- package/dist/cli-delegate/{chunk-M2DPGFZ4.js → chunk-6V6TA25H.js} +22 -152
- package/dist/cli-delegate/chunk-7IQE3D5S.js +191 -0
- package/dist/cli-delegate/{chunk-6KFEC3G7.js → chunk-CQWX336L.js} +1 -1
- package/dist/cli-delegate/chunk-DEIV7XP7.js +108 -0
- package/dist/cli-delegate/chunk-EEHDXSYD.js +2040 -0
- package/dist/cli-delegate/{chunk-P6ZHP5W2.js → chunk-GKE7ZVFA.js} +4 -4
- package/dist/cli-delegate/chunk-JQOM7TTM.js +1201 -0
- package/dist/cli-delegate/{chunk-UNCRJBT3.js → chunk-JQVYKIZA.js} +41 -0
- package/dist/cli-delegate/{chunk-FC75PGJE.js → chunk-JY2I2DXV.js} +12 -1
- package/dist/cli-delegate/chunk-KB5FZ655.js +208 -0
- package/dist/cli-delegate/{chunk-YUM5KURG.js → chunk-KR6EM23A.js} +235 -1455
- package/dist/cli-delegate/{chunk-LTYVIY76.js → chunk-LIYTLH6O.js} +1 -1
- package/dist/cli-delegate/chunk-NQ7WT43P.js +2729 -0
- package/dist/cli-delegate/chunk-NXEILF2K.js +2160 -0
- package/dist/cli-delegate/{chunk-5R7HOLAF.js → chunk-NYX3L2PZ.js} +1 -1
- package/dist/cli-delegate/{chunk-4E2W63DA.js → chunk-OKJUIAPS.js} +62 -1
- package/dist/cli-delegate/{chunk-QAJS5YWY.js → chunk-PHD7OOLE.js} +2 -1
- package/dist/cli-delegate/{chunk-RGNIWTEA.js → chunk-QLDHIPWR.js} +1 -1
- package/dist/cli-delegate/{chunk-HDVVGPQW.js → chunk-RQNNW7IF.js} +1405 -567
- package/dist/cli-delegate/{chunk-S3VFXMTM.js → chunk-S4UMZNES.js} +1 -1
- package/dist/cli-delegate/{chunk-6KM6ORRY.js → chunk-S4YWNRKO.js} +24 -8
- package/dist/cli-delegate/{chunk-GJCBB2UG.js → chunk-TRI6DQZ6.js} +3054 -325
- package/dist/cli-delegate/chunk-ULQRUR23.js +208 -0
- package/dist/cli-delegate/{chunk-NUHLI2RX.js → chunk-YCU3TIXH.js} +2 -0
- package/dist/cli-delegate/chunk-ZBZV2G5R.js +210 -0
- package/dist/cli-delegate/{chunk-WGXTVKCQ.js → chunk-ZUZKZYCN.js} +5 -3
- package/dist/cli-delegate/cmd_analyze_session.js +1 -1
- package/dist/cli-delegate/cmd_brand_status.js +1 -1
- package/dist/cli-delegate/cmd_conformance.js +1862 -809
- package/dist/cli-delegate/cmd_doctor.js +13 -6
- package/dist/cli-delegate/cmd_doctor_neighbours.js +23 -0
- package/dist/cli-delegate/cmd_explain.js +5 -5
- package/dist/cli-delegate/cmd_export.js +11 -8
- package/dist/cli-delegate/cmd_fleet.js +3 -3
- package/dist/cli-delegate/cmd_handoff.js +10 -7
- package/dist/cli-delegate/cmd_mcp_available.js +1 -1
- package/dist/cli-delegate/cmd_memory_get.js +3 -1
- package/dist/cli-delegate/cmd_migrate.js +2 -2
- package/dist/cli-delegate/cmd_packs_active.js +3 -3
- package/dist/cli-delegate/cmd_preflight.js +6 -6
- package/dist/cli-delegate/cmd_prune.js +11 -8
- package/dist/cli-delegate/cmd_refresh.js +2 -1
- package/dist/cli-delegate/cmd_route_audit.js +8 -6
- package/dist/cli-delegate/cmd_route_explain.js +4 -4
- package/dist/cli-delegate/cmd_session_recycle.js +4 -3
- package/dist/cli-delegate/cmd_settings_get.js +1 -1
- package/dist/cli-delegate/cmd_settings_migrate.js +11 -8
- package/dist/cli-delegate/cmd_settings_set.js +4 -4
- package/dist/cli-delegate/cmd_sync.js +11 -8
- package/dist/cli-delegate/cmd_uninstall.js +11 -8
- package/dist/cli-delegate/cmd_update.js +1 -1
- package/dist/cli-delegate/cmd_upgrade.js +3 -2
- package/dist/cli-delegate/cmd_validate.js +11 -8
- package/dist/cli-delegate/cmd_versions.js +1 -1
- package/dist/cli-delegate/deferral_carry.js +16 -0
- package/dist/cli-delegate/gate_execute.js +2 -2
- package/dist/cli-delegate/roadmap_gates.js +5 -5
- package/dist/cli-delegate/update_roadmap_progress.js +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +132 -80
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +3 -3
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +7 -5
- package/dist/discovery/trust-report.md +2 -2
- package/dist/discovery/workspaces.json +6 -4
- package/dist/hooks/dispatch.js +7972 -5186
- package/dist/hooks/dispatch.sha256 +1 -0
- package/dist/install/claudePathsPlan.js +47 -6
- package/dist/install/claudeRuleRewrite.js +7 -0
- package/dist/install/emit_host_rules_cli.js +39 -36
- package/dist/install/globalRuleScope.js +87 -0
- package/dist/install/host_rule_ownership.js +160 -0
- package/dist/install/install.mjs +2226 -1055
- package/dist/install/installThinLayer.js +261 -0
- package/dist/install/installedRuleLinks.js +176 -0
- package/dist/install/wizard-plan.js +24 -0
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +40913 -34865
- package/dist/scripts/_lib/agent_settings.js +11 -0
- package/dist/scripts/_lib/claude_settings_hooks.js +10 -8
- package/dist/scripts/_lib/installed_layer.js +378 -0
- package/dist/scripts/_lib/lean_projection_mode.js +218 -15
- package/dist/scripts/_lib/rule_consequence_class.js +45 -0
- package/dist/scripts/_lib/rule_injection.js +67 -10
- package/dist/scripts/_lib/rule_law_section.js +122 -0
- package/dist/scripts/_lib/thin_rules.js +458 -0
- package/dist/scripts/_lib/windsurf_render.js +60 -0
- package/dist/scripts/condense.js +14 -26
- package/dist/scripts/project_thin_rules.js +14 -294
- package/dist/server/schemas/settings.js +29 -0
- package/dist/shared/skillRanking.js +74 -13
- package/dist/ui/assets/index-Cwq4d0hX.js +23 -0
- package/dist/ui/assets/index-Cyjy8bWO.css +1 -0
- package/dist/ui/index.html +2 -2
- package/docs/contracts/settings-classes.md +10 -6
- package/package.json +4 -4
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +115 -20
- package/src/agent-src/scripts/deferral_carry.ts +339 -0
- package/src/agent-src/scripts/update_roadmap_progress.ts +4 -1
- package/src/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
- package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +302 -21
- package/src/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
- package/src/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
- package/src/agent-src/templates/scripts/work_engine/stack/runner.ts +774 -109
- package/src/agent-src/templates/scripts/work_engine/state.ts +57 -7
- package/src/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
- package/src/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
- package/src/config/agent-settings.template.yml +91 -22
- package/src/config/ci-local-parity.yml +18 -0
- package/src/config/continuity-surface.json +9 -2
- package/src/config/evaluator-budgets.json +4 -4
- package/src/config/gate-coverage.yml +327 -3
- package/src/config/gate-violation-baselines.json +21 -9
- package/src/config/hook-bundle-budget.json +25 -0
- package/src/config/hook-latency-budget.json +21 -0
- package/src/config/hook-token-budget.json +8 -3
- package/src/config/host-instruction-limits.json +33 -0
- package/src/config/metric-registry.yml +114 -0
- package/src/config/placeholder-drift-budget.json +10 -0
- package/src/config/routing-coverage-seed.json +38 -3
- package/src/config/rule-activation-census.json +7 -3
- package/src/config/rule-consequence-class.json +161 -0
- package/src/config/rule-law-ceilings.json +99 -0
- package/src/install/claudePathsPlan.ts +46 -4
- package/src/install/claudeRuleRewrite.ts +10 -1
- package/src/install/emit_host_rules_cli.ts +43 -34
- package/src/install/globalRuleScope.ts +109 -0
- package/src/install/host_rule_ownership.ts +170 -0
- package/src/install/installThinLayer.ts +312 -0
- package/src/install/installedRuleLinks.ts +220 -0
- package/src/install/wizard-plan.ts +24 -0
- package/src/scripts/_cli/cmd_doctor.ts +28 -90
- package/src/scripts/_cli/cmd_doctor_neighbours.ts +293 -0
- package/src/scripts/_cli/doctor_execution.ts +100 -17
- package/src/scripts/_cli/doctor_network_posture.ts +226 -0
- package/src/scripts/_dispatch.bash +16 -2
- package/src/scripts/_lib/agent_settings.ts +11 -0
- package/src/scripts/_lib/body_portable.ts +102 -0
- package/src/scripts/_lib/cc_transcript.ts +132 -1
- package/src/scripts/_lib/claude_settings_hooks.ts +10 -8
- package/src/scripts/_lib/concern_sla_window.ts +200 -0
- package/src/scripts/_lib/estate_offsets.ts +241 -0
- package/src/scripts/_lib/exemption_shape.ts +143 -0
- package/src/scripts/_lib/fnv.ts +23 -0
- package/src/scripts/_lib/foreign_scan.ts +106 -0
- package/src/scripts/_lib/forge_protection.ts +114 -0
- package/src/scripts/_lib/forge_reader.ts +558 -0
- package/src/scripts/_lib/gitignore_block.ts +314 -0
- package/src/scripts/_lib/global_deploy_inventory.ts +32 -0
- package/src/scripts/_lib/graph_feeder_record.ts +214 -0
- package/src/scripts/_lib/hook_settings.ts +73 -0
- package/src/scripts/_lib/host_capability.ts +40 -19
- package/src/scripts/_lib/host_env_write.ts +111 -0
- package/src/scripts/_lib/host_hook_merge.ts +165 -0
- package/src/scripts/_lib/installed_layer.ts +538 -0
- package/src/scripts/_lib/json_merge.ts +82 -0
- package/src/scripts/_lib/json_pointers.ts +93 -11
- package/src/scripts/_lib/lean_projection_mode.ts +275 -15
- package/src/scripts/_lib/llm_proposer_transport.ts +14 -4
- package/src/scripts/_lib/machine_wake.ts +16 -3
- package/src/scripts/_lib/neighbour_census.ts +760 -0
- package/src/scripts/_lib/neighbour_scan.ts +122 -0
- package/src/scripts/_lib/neighbour_tool_use.ts +189 -0
- package/src/scripts/_lib/obligations.ts +119 -8
- package/src/scripts/_lib/probe_inputs.ts +271 -0
- package/src/scripts/_lib/prompt_shape.ts +46 -0
- package/src/scripts/_lib/read_surface_scan.ts +322 -0
- package/src/scripts/_lib/release_highlights.ts +2 -1
- package/src/scripts/_lib/release_migration_gate.ts +19 -3
- package/src/scripts/_lib/reserved_name_sweep.ts +73 -0
- package/src/scripts/_lib/retrieval_sanitize.ts +77 -16
- package/src/scripts/_lib/review_coverage.ts +134 -0
- package/src/scripts/_lib/rule_consequence_class.ts +71 -0
- package/src/scripts/_lib/rule_injection.ts +130 -10
- package/src/scripts/_lib/rule_law_section.ts +147 -0
- package/src/scripts/_lib/rule_layer_overlap.ts +58 -0
- package/src/scripts/_lib/shell_write_shapes.ts +108 -0
- package/src/scripts/_lib/skill_origin.ts +254 -0
- package/src/scripts/_lib/structural_hiding.ts +622 -0
- package/src/scripts/_lib/structured_ask.ts +64 -21
- package/src/scripts/_lib/thin_rules.ts +540 -0
- package/src/scripts/_lib/touched_file_quality.ts +434 -0
- package/src/scripts/_lib/turn_end_refusals.ts +600 -4
- package/src/scripts/_lib/turn_end_transcript.ts +216 -0
- package/src/scripts/_lib/verification_command.ts +324 -0
- package/src/scripts/_lib/verification_evidence.ts +647 -0
- package/src/scripts/_lib/verify_clause.ts +280 -0
- package/src/scripts/_lib/windsurf_render.ts +63 -0
- package/src/scripts/ai_council/cli_call_budget.ts +13 -5
- package/src/scripts/ai_council/clients.ts +26 -57
- package/src/scripts/ai_council/config.ts +1 -1
- package/src/scripts/ai_council/vendor_defaults.ts +45 -0
- package/src/scripts/ai_team/team_dispatch.ts +4 -0
- package/src/scripts/audit_skill_overlap.ts +261 -0
- package/src/scripts/before_complete_hook.ts +573 -25
- package/src/scripts/bench_hook_latency.ts +412 -5
- package/src/scripts/bench_touched_file_quality.ts +238 -0
- package/src/scripts/cache_realization_report.ts +61 -1
- package/src/scripts/check_argument_hint.ts +287 -0
- package/src/scripts/check_claims.ts +125 -1
- package/src/scripts/check_enforcement_coverage.ts +38 -9
- package/src/scripts/check_enforcement_matrix.ts +621 -22
- package/src/scripts/check_estate_count.ts +130 -153
- package/src/scripts/check_finding_dispositions.ts +138 -7
- package/src/scripts/check_generator_sync.ts +71 -0
- package/src/scripts/check_hook_bundle_composition.ts +370 -0
- package/src/scripts/check_host_docs_digest.ts +786 -0
- package/src/scripts/check_host_format_column.ts +559 -0
- package/src/scripts/check_input_references.ts +228 -0
- package/src/scripts/check_inputs_parity.ts +235 -0
- package/src/scripts/check_kernel_edit_ratified.ts +89 -9
- package/src/scripts/check_kill_switch_table.ts +294 -0
- package/src/scripts/check_memory.ts +7 -1
- package/src/scripts/check_placeholder_drift.ts +221 -0
- package/src/scripts/check_read_surface_coverage.ts +515 -0
- package/src/scripts/check_release_highlights.ts +10 -0
- package/src/scripts/check_release_holds.ts +2 -1
- package/src/scripts/check_release_pr_shape.ts +38 -15
- package/src/scripts/check_routing_coverage.ts +378 -11
- package/src/scripts/check_standing_rule_delivery.ts +33 -13
- package/src/scripts/check_static_parity.ts +17 -0
- package/src/scripts/check_verify_expectation_delta.ts +521 -0
- package/src/scripts/ci_settle.ts +145 -19
- package/src/scripts/closure_scan.ts +115 -1
- package/src/scripts/code_graph/detect.ts +127 -7
- package/src/scripts/code_graph/foreign.ts +194 -0
- package/src/scripts/code_graph/query.ts +217 -4
- package/src/scripts/code_graph/types.ts +26 -2
- package/src/scripts/code_graph/validate.ts +3 -1
- package/src/scripts/code_graph/verbs.ts +103 -0
- package/src/scripts/compile_hook_manifest.ts +90 -14
- package/src/scripts/condense.ts +14 -27
- package/src/scripts/estate_exemption_shape.ts +202 -0
- package/src/scripts/external_sources_denylist.json +29 -4
- package/src/scripts/generate_host_cost_table.ts +1 -1
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +192 -14
- package/src/scripts/hooks/block_config_weakening.ts +294 -4
- package/src/scripts/hooks/block_kernel_rule_writes.ts +9 -78
- package/src/scripts/hooks/block_plumbing_writes.ts +342 -0
- package/src/scripts/hooks/bundle_integrity.ts +291 -0
- package/src/scripts/hooks/chain_nudge_hook.ts +1 -2
- package/src/scripts/hooks/code_graph_context_hook.ts +253 -25
- package/src/scripts/hooks/comment_discipline_hook.ts +1 -3
- package/src/scripts/hooks/concern_failure_policy.ts +280 -0
- package/src/scripts/hooks/concern_registry.ts +2 -0
- package/src/scripts/hooks/concern_timings.ts +94 -0
- package/src/scripts/hooks/delegation_nudge_hook.ts +1 -2
- package/src/scripts/hooks/design_pass_hook.ts +100 -9
- package/src/scripts/hooks/design_slop_hook.ts +1 -2
- package/src/scripts/hooks/dispatch_hook.ts +77 -71
- package/src/scripts/hooks/edit_shape_hook.ts +1 -2
- package/src/scripts/hooks/end_review_nudge_hook.ts +1 -1
- package/src/scripts/hooks/evidence_independence.ts +1 -2
- package/src/scripts/hooks/exit_codes.ts +129 -0
- package/src/scripts/hooks/host_lowering.json +1 -0
- package/src/scripts/hooks/host_lowering.ts +146 -3
- package/src/scripts/hooks/host_lowering.yaml +262 -46
- package/src/scripts/hooks/host_semantics.ts +30 -2
- package/src/scripts/hooks/injection_budget.ts +9 -2
- package/src/scripts/hooks/interruption_ledger_hook.ts +1 -2
- package/src/scripts/hooks/journal_record_hook.ts +1 -2
- package/src/scripts/hooks/memory_recall_hook.ts +1 -2
- package/src/scripts/hooks/obligation_settle_hook.ts +151 -5
- package/src/scripts/hooks/one_question_per_ask_hook.ts +43 -6
- package/src/scripts/hooks/orchestration_record_hook.ts +1 -2
- package/src/scripts/hooks/payload_stub.ts +10 -1
- package/src/scripts/hooks/permission_gate.ts +1 -2
- package/src/scripts/hooks/push_settle_hook.ts +1 -3
- package/src/scripts/hooks/replay_hook.ts +2 -1
- package/src/scripts/hooks/reread_guard_hook.ts +1 -2
- package/src/scripts/hooks/review_baseline_hook.ts +1 -2
- package/src/scripts/hooks/rtk_wrap_hook.ts +1 -2
- package/src/scripts/hooks/rule_inject_hook.ts +765 -68
- package/src/scripts/hooks/run_continuation_hook.ts +13 -15
- package/src/scripts/hooks/skill_route_hook.ts +1 -2
- package/src/scripts/hooks/source_first_gate_hook.ts +1 -2
- package/src/scripts/hooks/spawn_guard_shadow_hook.ts +1 -2
- package/src/scripts/hooks/subagent_ledger_hook.ts +1 -2
- package/src/scripts/hooks/suggestion_capture_hook.ts +1 -1
- package/src/scripts/hooks/table_fingerprint.ts +70 -0
- package/src/scripts/hooks/telemetry_flush_hook.ts +1 -2
- package/src/scripts/hooks/telemetry_usage_hook.ts +65 -10
- package/src/scripts/hooks/tool_result_bytes_hook.ts +93 -9
- package/src/scripts/hooks/turn_end_gate_hook.ts +421 -324
- package/src/scripts/hooks/ui_route_nudge_hook.ts +1 -2
- package/src/scripts/hooks_status.ts +112 -0
- package/src/scripts/injection_scan_hook.ts +7 -0
- package/src/scripts/install.ts +75 -189
- package/src/scripts/installed_layer_report.ts +103 -0
- package/src/scripts/language_mirror_hook.ts +15 -6
- package/src/scripts/learning_sidecar.ts +119 -8
- package/src/scripts/lint_deny_text.ts +419 -0
- package/src/scripts/lint_exit_codes.ts +262 -0
- package/src/scripts/lint_hook_manifest.ts +108 -0
- package/src/scripts/lint_major_migration_sections.ts +156 -26
- package/src/scripts/lint_pack_boundaries.ts +63 -5
- package/src/scripts/lint_roadmap_blockers.ts +78 -12
- package/src/scripts/lint_rule_law_section.ts +517 -0
- package/src/scripts/lint_skill_descriptions.ts +1 -1
- package/src/scripts/lint_skill_scripts_readonly.ts +1 -1
- package/src/scripts/lint_workflow_security.ts +161 -5
- package/src/scripts/mcp_server/consumer_tool_catalog.json +53 -0
- package/src/scripts/mcp_server/graph_tools.ts +72 -3
- package/src/scripts/mcp_server/prompts.ts +142 -1
- package/src/scripts/measure_routing_signal.ts +54 -16
- package/src/scripts/measure_skill_ranker_baseline.ts +515 -53
- package/src/scripts/measure_turn_end_gate.ts +193 -0
- package/src/scripts/memory_learn_hook.ts +91 -6
- package/src/scripts/memory_lookup.ts +1 -1
- package/src/scripts/model_rule_injection.ts +52 -5
- package/src/scripts/prepack-check.mjs +53 -0
- package/src/scripts/project_thin_rules.ts +34 -327
- package/src/scripts/release_publication.ts +13 -1
- package/src/scripts/report_evidence_temperature.ts +657 -0
- package/src/scripts/report_installed_rule_links.ts +157 -0
- package/src/scripts/report_invocation_surface.ts +383 -0
- package/src/scripts/report_label_agreement.ts +233 -0
- package/src/scripts/report_obligation_carriers.ts +699 -0
- package/src/scripts/report_obligation_mechanism.ts +230 -0
- package/src/scripts/report_obligation_writer_split.ts +236 -0
- package/src/scripts/report_single_token_triggers.ts +277 -0
- package/src/scripts/report_skill_menu_census.ts +208 -0
- package/src/scripts/report_skill_ranker_confusion.ts +442 -0
- package/src/scripts/report_standing_payload_by_host.ts +188 -15
- package/src/scripts/report_verification_record_coverage.ts +306 -0
- package/src/scripts/roadmap_verify_share.ts +213 -0
- package/src/scripts/rule_activation_census.ts +5 -2
- package/src/scripts/rule_trigger_eval.ts +9 -9
- package/src/scripts/schemas/command.schema.json +44 -0
- package/src/scripts/schemas/review-findings.schema.json +7 -2
- package/src/scripts/schemas/rule.schema.json +5 -0
- package/src/scripts/schemas/skill.schema.json +44 -0
- package/src/scripts/self_review_gate.ts +421 -17
- package/src/scripts/semantic_parity_census.ts +314 -0
- package/src/scripts/session_eol_report.ts +54 -1
- package/src/scripts/skill_tools/score_skill_relevance.ts +290 -25
- package/src/scripts/sweep_dead_scan_roots.ts +1 -1
- package/src/scripts/sweep_skill_ranker_signals.ts +129 -0
- package/src/scripts/sync_gitignore.ts +29 -235
- package/src/scripts/trigger_eval_grandfather.json +0 -7
- package/src/scripts/trigger_eval_rotation.ts +122 -27
- package/src/scripts/ui_conformance_probe.ts +156 -4
- package/src/scripts/update_prices.ts +85 -3
- package/src/scripts/write_bundle_digest.mjs +74 -0
- package/src/server/schemas/settings.ts +41 -0
- package/src/shared/skillRanking.ts +117 -11
- package/src/templates/consumer-settings/ONBOARDING.md +1 -0
- package/dist/cli-delegate/chunk-FXYUYU4N.js +0 -4416
- package/dist/cli-delegate/chunk-QJWAUMJW.js +0 -1072
- package/dist/cli-delegate/chunk-YDPILCOL.js +0 -152
- package/dist/ui/assets/index-DH1xc62v.js +0 -29
- package/dist/ui/assets/index-jSfMW8_t.css +0 -1
|
@@ -0,0 +1,647 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verification evidence — what a run RECORD says, never what a command SAYS.
|
|
3
|
+
*
|
|
4
|
+
* The defect this module exists to close: the turn-end gate's detector C asks
|
|
5
|
+
* whether a verification command appeared in the transcript after the last
|
|
6
|
+
* edit, and answers that by matching the command TEXT against a regex. `echo
|
|
7
|
+
* test` matches it — the word `test` is in the pattern — so a turn can change
|
|
8
|
+
* production code, echo a word, and end. Nothing in the estate read whether the
|
|
9
|
+
* command ran, what it exited with, or whether it discovered a single test.
|
|
10
|
+
*
|
|
11
|
+
* So the unit here is a RECORD, not a string: a command, an exit code, and the
|
|
12
|
+
* tails of what it printed. The classifier is pure — no fs, no spawn, no clock,
|
|
13
|
+
* no process state — because its verdict is quoted into a refusal and a refusal
|
|
14
|
+
* that depends on the machine it ran on cannot be reproduced by the person it
|
|
15
|
+
* refused.
|
|
16
|
+
*
|
|
17
|
+
* WHY `INVALID_RUN` CARRIES A REASON AND NOT A BOOLEAN.
|
|
18
|
+
* Two of the reasons say the INSTRUMENT failed (`exit_code_unavailable`,
|
|
19
|
+
* `unreadable_record`) and five say the RUN failed. Only the second kind may
|
|
20
|
+
* ever refuse a turn: a host that omits an exit code has told us nothing about
|
|
21
|
+
* the operator's work, and refusing on it would punish the turn for the
|
|
22
|
+
* recorder's gap. `INSTRUMENT_GAP_REASONS` below is that split, exported so the
|
|
23
|
+
* consumer reads the distinction rather than re-deriving it from a comment.
|
|
24
|
+
*
|
|
25
|
+
* WHY EXIT 0 WITH NO PARSABLE SUMMARY IS A PASS.
|
|
26
|
+
* `tsc --noEmit`, `eslint` and `phpstan` print nothing on success — silence is
|
|
27
|
+
* the Unix convention for it. A classifier demanding a test summary would
|
|
28
|
+
* reclassify the most common green signal in this repository as invalid, and
|
|
29
|
+
* would refuse every consumer whose runner this module has never heard of.
|
|
30
|
+
* Exit 0 from a command the verification set admits IS the machine-readable
|
|
31
|
+
* signal; the parsers below only ever make the verdict SHARPER (a summary
|
|
32
|
+
* reporting failures overrides a zero exit, and a summary reporting zero tests
|
|
33
|
+
* overrides it too).
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import { isVerificationCommand } from './verification_command.js';
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
export type InvalidRunReason =
|
|
40
|
+
| 'zero_tests_discovered'
|
|
41
|
+
| 'nonzero_exit_without_test_failure'
|
|
42
|
+
| 'fixture_or_load_failure'
|
|
43
|
+
| 'unreadable_record'
|
|
44
|
+
| 'not_a_verification_command'
|
|
45
|
+
| 'exit_code_unavailable'
|
|
46
|
+
| 'timeout_or_killed';
|
|
47
|
+
|
|
48
|
+
export type VerificationVerdict =
|
|
49
|
+
| { readonly kind: 'PASS_EVIDENCE_OK' }
|
|
50
|
+
| { readonly kind: 'FAIL_EVIDENCE' }
|
|
51
|
+
| { readonly kind: 'INVALID_RUN'; readonly reason: InvalidRunReason };
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* One observed run of one command.
|
|
55
|
+
*
|
|
56
|
+
* `exit_code: null` is a real and distinct value from `0`: it means the host
|
|
57
|
+
* did not surface one, which is a fact about the host. Conflating it with
|
|
58
|
+
* success is the single most dangerous shortcut available here, so the type
|
|
59
|
+
* makes it impossible to write by accident.
|
|
60
|
+
*/
|
|
61
|
+
export interface RunRecord {
|
|
62
|
+
readonly command: string;
|
|
63
|
+
readonly exit_code: number | null;
|
|
64
|
+
readonly stdout_tail?: string;
|
|
65
|
+
readonly stderr_tail?: string;
|
|
66
|
+
readonly runner?: string;
|
|
67
|
+
/** The host reported the call interrupted — a kill, never a verdict on the work. */
|
|
68
|
+
readonly interrupted?: boolean;
|
|
69
|
+
/** How the recorder obtained `exit_code`. Advisory; nothing branches on it. */
|
|
70
|
+
readonly exit_source?: 'field' | 'error_prefix' | 'response_shape' | null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Reasons that describe a gap in the INSTRUMENT rather than a failure of the run.
|
|
75
|
+
*
|
|
76
|
+
* A consumer deciding whether to refuse reads this set; it never decides from
|
|
77
|
+
* the reason string directly. Risk 1 of the parent roadmap is exactly this:
|
|
78
|
+
* "a passing run classifies INVALID_RUN" when the parser has not met the
|
|
79
|
+
* consumer's runner, and the mitigation is that an instrument gap is logged as
|
|
80
|
+
* a finding and never refused on.
|
|
81
|
+
*/
|
|
82
|
+
export const INSTRUMENT_GAP_REASONS: ReadonlySet<InvalidRunReason> = new Set<InvalidRunReason>([
|
|
83
|
+
'exit_code_unavailable',
|
|
84
|
+
'unreadable_record',
|
|
85
|
+
]);
|
|
86
|
+
|
|
87
|
+
/** Whether this verdict leaves the turn unrefusable — a pass, or a gap in the instrument. */
|
|
88
|
+
export function isInstrumentGap(verdict: VerificationVerdict): boolean {
|
|
89
|
+
return verdict.kind === 'INVALID_RUN' && INSTRUMENT_GAP_REASONS.has(verdict.reason);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Executables that cannot verify anything, whatever words follow them.
|
|
94
|
+
*
|
|
95
|
+
* A BLOCK list and not an allow list, and the direction is the whole argument.
|
|
96
|
+
* `not_a_verification_command` is a REFUSING reason: a command wrongly landing
|
|
97
|
+
* in it refuses honest work, which is the failure that teaches an operator to
|
|
98
|
+
* switch the gate off. An allow list would put every runner this module has
|
|
99
|
+
* never met — `bash scripts/test.sh`, `./run-tests.sh`, a consumer's in-house
|
|
100
|
+
* wrapper — into that refusing bucket on day one. So the rejection is narrow:
|
|
101
|
+
* only executables that demonstrably assert nothing are named, and everything
|
|
102
|
+
* unknown is given the benefit of its exit code.
|
|
103
|
+
*
|
|
104
|
+
* `git` is here because `git log`, `git show` and `git diff` read history; the
|
|
105
|
+
* one git subcommand anybody could call verification is `git bisect run`, which
|
|
106
|
+
* verifies by invoking a different command that gets its own record.
|
|
107
|
+
*/
|
|
108
|
+
const NON_VERIFYING_EXECUTABLES: ReadonlySet<string> = new Set([
|
|
109
|
+
':', 'awk', 'basename', 'cat', 'cd', 'chmod', 'cp', 'curl', 'cut', 'date', 'dirname',
|
|
110
|
+
'echo', 'exit', 'export', 'false', 'find', 'git', 'grep', 'head', 'less', 'ln', 'ls',
|
|
111
|
+
'mkdir', 'more', 'mv', 'open', 'printf', 'pwd', 'realpath', 'rg', 'rm', 'sed', 'sleep',
|
|
112
|
+
'sort', 'source', 'tail', 'tee', 'touch', 'tr', 'true', 'type', 'uniq', 'wc', 'wget',
|
|
113
|
+
'which', 'yes',
|
|
114
|
+
]);
|
|
115
|
+
|
|
116
|
+
/** Wrappers that prefix a real command without being one. */
|
|
117
|
+
const COMMAND_PREFIXES: ReadonlySet<string> = new Set([
|
|
118
|
+
'bunx', 'command', 'env', 'exec', 'nice', 'nohup', 'npx', 'sudo', 'time',
|
|
119
|
+
]);
|
|
120
|
+
|
|
121
|
+
/** The executable a single command segment actually runs, lower-cased and unpathed. */
|
|
122
|
+
function leadingExecutable(segment: string): string | null {
|
|
123
|
+
for (const raw of segment.trim().split(/\s+/)) {
|
|
124
|
+
if (raw === '') continue;
|
|
125
|
+
if (/^[A-Za-z_][A-Za-z0-9_]*=/.test(raw)) continue; // FOO=bar prefix
|
|
126
|
+
const base = (raw.split('/').pop() ?? raw).replace(/\.exe$/i, '').toLowerCase();
|
|
127
|
+
if (base === '') continue;
|
|
128
|
+
if (COMMAND_PREFIXES.has(base)) continue;
|
|
129
|
+
return base;
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Whether any segment of this command line could have verified something.
|
|
136
|
+
*
|
|
137
|
+
* The selector in `verification_command.ts` answers "is this worth recording",
|
|
138
|
+
* and it matches `echo test` because the word `test` is in its pattern. That is
|
|
139
|
+
* the defect the record path exists to close, so this cannot delegate to it
|
|
140
|
+
* alone: a segment whose executable cannot assert anything is skipped BEFORE
|
|
141
|
+
* the selector is consulted, and only the surviving segments may qualify.
|
|
142
|
+
*/
|
|
143
|
+
function canVerify(command: string): boolean {
|
|
144
|
+
for (const segment of command.split(/&&|\|\||[;|\n]/)) {
|
|
145
|
+
if (segment.trim() === '') continue;
|
|
146
|
+
const exe = leadingExecutable(segment);
|
|
147
|
+
if (exe !== null && NON_VERIFYING_EXECUTABLES.has(exe)) continue;
|
|
148
|
+
if (isVerificationCommand(segment)) return true;
|
|
149
|
+
}
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Exit codes a shell reports for a process the kernel killed.
|
|
155
|
+
*
|
|
156
|
+
* 128 + SIGKILL(9) and 128 + SIGTERM(15). A test runner the CI timeout reaped
|
|
157
|
+
* printed whatever it had got through; its partial output can easily contain a
|
|
158
|
+
* passing summary for the files it reached, which is why a kill is checked
|
|
159
|
+
* BEFORE the summary is trusted and never after.
|
|
160
|
+
*/
|
|
161
|
+
const KILLED_EXIT_CODES: ReadonlySet<number> = new Set([137, 143]);
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Output shapes that mean the suite never ran, as distinct from running clean.
|
|
165
|
+
*
|
|
166
|
+
* Deliberately separate from `isVacuousOutput` in the recorder: that one asks
|
|
167
|
+
* whether a whole output blob proves nothing, line by line, and answers `false`
|
|
168
|
+
* for empty output. This one asks whether a specific "the loader died" marker
|
|
169
|
+
* is present anywhere, which is a different question with a different default.
|
|
170
|
+
*/
|
|
171
|
+
const LOAD_FAILURE_PATTERNS: readonly RegExp[] = [
|
|
172
|
+
/\bfailed to load\b/i,
|
|
173
|
+
/\berror(?:s)? collecting\b/i,
|
|
174
|
+
/\bcollection error\b/i,
|
|
175
|
+
/\bcannot find module\b/i,
|
|
176
|
+
/\bmodule not found\b/i,
|
|
177
|
+
/\bunable to load\b/i,
|
|
178
|
+
/\btransform failed\b/i,
|
|
179
|
+
/\bconftest\.py\b.*\berror\b/i,
|
|
180
|
+
/\bcould not open input file\b/i,
|
|
181
|
+
/\bfatal error\b/i,
|
|
182
|
+
];
|
|
183
|
+
|
|
184
|
+
/** A parsed test summary. `total === null` means the runner did not state one. */
|
|
185
|
+
interface Summary {
|
|
186
|
+
readonly passed: number;
|
|
187
|
+
readonly failed: number;
|
|
188
|
+
readonly total: number | null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function _num(m: RegExpMatchArray | null): number {
|
|
192
|
+
return m?.[1] === undefined ? 0 : Number(m[1]);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* vitest: `Tests 3 failed | 812 passed (815)`; jest: `Tests: 1 failed, 2 passed, 3 total`.
|
|
197
|
+
*
|
|
198
|
+
* One parser for both because both put the counts on a line whose label is
|
|
199
|
+
* `Tests`, and splitting them would mean two places to keep a count regex
|
|
200
|
+
* correct. The separators differ (`|` vs `,`) and neither parser cares.
|
|
201
|
+
*/
|
|
202
|
+
function parseVitestOrJest(text: string): Summary | null {
|
|
203
|
+
const line = /^[ \t]*Tests:?[ \t]+(.+)$/m.exec(text);
|
|
204
|
+
if (line?.[1] === undefined) return null;
|
|
205
|
+
const body = line[1];
|
|
206
|
+
const failed = _num(/(\d+)\s+failed/i.exec(body));
|
|
207
|
+
const passed = _num(/(\d+)\s+passed/i.exec(body));
|
|
208
|
+
const totalMatch = /\((\d+)\)\s*$/.exec(body.trim()) ?? /(\d+)\s+total/i.exec(body);
|
|
209
|
+
const total = totalMatch?.[1] === undefined ? null : Number(totalMatch[1]);
|
|
210
|
+
return { passed, failed, total };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* pytest's `-q` tail: `3 passed, 1 failed in 0.12s`, or `no tests ran in 0.01s`.
|
|
215
|
+
*
|
|
216
|
+
* `error` is counted as a failure rather than as its own axis: a collection
|
|
217
|
+
* error and an assertion failure are the same answer to "did this run prove the
|
|
218
|
+
* change works", and the separate `fixture_or_load_failure` reason already
|
|
219
|
+
* carries the case where the distinction matters.
|
|
220
|
+
*/
|
|
221
|
+
function parsePytest(text: string): Summary | null {
|
|
222
|
+
if (/\bno tests ran\b/i.test(text)) return { passed: 0, failed: 0, total: 0 };
|
|
223
|
+
const line = /^[= ]*((?:\d+\s+(?:passed|failed|error|errors|skipped|xfailed|xpassed|deselected)[,\s]*)+)(?:in\s+[\d.]+s)?[= ]*$/im.exec(
|
|
224
|
+
text,
|
|
225
|
+
);
|
|
226
|
+
if (line?.[1] === undefined) return null;
|
|
227
|
+
const body = line[1];
|
|
228
|
+
const passed = _num(/(\d+)\s+passed/i.exec(body));
|
|
229
|
+
const failed =
|
|
230
|
+
_num(/(\d+)\s+failed/i.exec(body)) + _num(/(\d+)\s+errors?\b/i.exec(body));
|
|
231
|
+
const skipped = _num(/(\d+)\s+skipped/i.exec(body));
|
|
232
|
+
return { passed, failed, total: passed + failed + skipped };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* phpunit (`OK (15 tests, 30 assertions)`, `Tests: 15, Assertions: 30, Failures: 1.`)
|
|
237
|
+
* and pest (`Tests: 2 failed, 10 passed (24 assertions)`).
|
|
238
|
+
*
|
|
239
|
+
* Pest's line is handled by `parseVitestOrJest` above — it wears the same
|
|
240
|
+
* `Tests:` label with `failed`/`passed` words — so only the two phpunit shapes
|
|
241
|
+
* need their own reading here.
|
|
242
|
+
*/
|
|
243
|
+
function parsePhpunit(text: string): Summary | null {
|
|
244
|
+
if (/\bno tests executed\b/i.test(text)) return { passed: 0, failed: 0, total: 0 };
|
|
245
|
+
const ok = /\bOK\s*\((\d+)\s+tests?/i.exec(text);
|
|
246
|
+
if (ok?.[1] !== undefined) {
|
|
247
|
+
const n = Number(ok[1]);
|
|
248
|
+
return { passed: n, failed: 0, total: n };
|
|
249
|
+
}
|
|
250
|
+
const counted = /^[ \t]*Tests:\s*(\d+),\s*Assertions:\s*\d+(.*)$/m.exec(text);
|
|
251
|
+
if (counted?.[1] === undefined) return null;
|
|
252
|
+
const total = Number(counted[1]);
|
|
253
|
+
const rest = counted[2] ?? '';
|
|
254
|
+
const failed =
|
|
255
|
+
_num(/Failures:\s*(\d+)/i.exec(rest)) +
|
|
256
|
+
_num(/Errors:\s*(\d+)/i.exec(rest)) +
|
|
257
|
+
_num(/Risky:\s*(\d+)/i.exec(rest));
|
|
258
|
+
return { passed: Math.max(total - failed, 0), failed, total };
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* TAP: a `1..N` plan with `ok` / `not ok` lines, or a `# fail N` trailer.
|
|
263
|
+
*
|
|
264
|
+
* The plan is authoritative for the total because a truncated stream can carry
|
|
265
|
+
* fewer result lines than it promised — and a stream that promised five results
|
|
266
|
+
* and delivered two is not a clean run, which the total/seen mismatch below
|
|
267
|
+
* lets the caller see as a non-pass.
|
|
268
|
+
*/
|
|
269
|
+
function parseTap(text: string): Summary | null {
|
|
270
|
+
const plan = /^1\.\.(\d+)\s*$/m.exec(text);
|
|
271
|
+
const okLines = text.match(/^ok\b/gim)?.length ?? 0;
|
|
272
|
+
const notOkLines = text.match(/^not ok\b/gim)?.length ?? 0;
|
|
273
|
+
const trailerFail = /^#\s*fail\s+(\d+)\s*$/im.exec(text);
|
|
274
|
+
if (plan?.[1] === undefined && okLines === 0 && notOkLines === 0) return null;
|
|
275
|
+
const total = plan?.[1] === undefined ? okLines + notOkLines : Number(plan[1]);
|
|
276
|
+
const failed = trailerFail?.[1] === undefined ? notOkLines : Number(trailerFail[1]);
|
|
277
|
+
return { passed: okLines, failed, total };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Every parser, tried in turn; the first that recognises the text wins.
|
|
282
|
+
*
|
|
283
|
+
* Order matters only between phpunit and vitest/jest, and only because pest
|
|
284
|
+
* writes a `Tests:` line that the vitest parser reads correctly — so phpunit's
|
|
285
|
+
* stricter `Tests: <n>, Assertions:` shape is tried first and declines when the
|
|
286
|
+
* line is pest's.
|
|
287
|
+
*/
|
|
288
|
+
export function parseSummary(text: string): Summary | null {
|
|
289
|
+
return parsePhpunit(text) ?? parseVitestOrJest(text) ?? parsePytest(text) ?? parseTap(text);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function output(record: RunRecord): string {
|
|
293
|
+
return `${record.stdout_tail ?? ''}\n${record.stderr_tail ?? ''}`;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Classify one run record.
|
|
298
|
+
*
|
|
299
|
+
* The order of the checks IS the policy and is not interchangeable:
|
|
300
|
+
*
|
|
301
|
+
* 1. an unreadable record says nothing about the run;
|
|
302
|
+
* 2. a command that cannot verify anything is never evidence, whatever it
|
|
303
|
+
* exited with — this is the `echo test` case the regex path admits;
|
|
304
|
+
* 3. a missing exit code is the host's gap, reported as one;
|
|
305
|
+
* 4. a killed process is checked BEFORE its output, because partial output
|
|
306
|
+
* from a reaped runner can carry a passing summary for the files it
|
|
307
|
+
* reached;
|
|
308
|
+
* 5. a load failure is checked before the summary, because a suite that never
|
|
309
|
+
* loaded can still print a summary of the zero tests it ran;
|
|
310
|
+
* 6. the summary then sharpens or overrides the exit code;
|
|
311
|
+
* 7. and only then does a bare exit code decide.
|
|
312
|
+
*/
|
|
313
|
+
export function classifyRun(record: unknown): VerificationVerdict {
|
|
314
|
+
if (typeof record !== 'object' || record === null || Array.isArray(record)) {
|
|
315
|
+
return { kind: 'INVALID_RUN', reason: 'unreadable_record' };
|
|
316
|
+
}
|
|
317
|
+
const r = record as Partial<RunRecord>;
|
|
318
|
+
if (typeof r.command !== 'string' || r.command.trim() === '') {
|
|
319
|
+
return { kind: 'INVALID_RUN', reason: 'unreadable_record' };
|
|
320
|
+
}
|
|
321
|
+
if (!canVerify(r.command)) {
|
|
322
|
+
return { kind: 'INVALID_RUN', reason: 'not_a_verification_command' };
|
|
323
|
+
}
|
|
324
|
+
if (
|
|
325
|
+
r.exit_code !== null
|
|
326
|
+
&& r.exit_code !== undefined
|
|
327
|
+
&& (typeof r.exit_code !== 'number' || !Number.isFinite(r.exit_code))
|
|
328
|
+
) {
|
|
329
|
+
return { kind: 'INVALID_RUN', reason: 'unreadable_record' };
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const text = output(r as RunRecord);
|
|
333
|
+
const summary = parseSummary(text);
|
|
334
|
+
|
|
335
|
+
// A KILL is read before anything the process printed: a reaped runner's
|
|
336
|
+
// partial output can carry a passing summary for the files it reached. A
|
|
337
|
+
// summary that reports FAILURES still wins, because those failures happened.
|
|
338
|
+
if (r.interrupted === true || (typeof r.exit_code === 'number' && KILLED_EXIT_CODES.has(r.exit_code))) {
|
|
339
|
+
if (summary !== null && summary.failed > 0) return { kind: 'FAIL_EVIDENCE' };
|
|
340
|
+
return { kind: 'INVALID_RUN', reason: 'timeout_or_killed' };
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// A RUN THAT PRINTED ITS OWN FAILURES IS A FAILURE, exit code or not. This
|
|
344
|
+
// sits above the missing-exit-code gap deliberately, and the order was the
|
|
345
|
+
// other way round until an independent review measured what it cost: on
|
|
346
|
+
// Claude Code the success shape carries no exit field at all, so a failing
|
|
347
|
+
// run whose summary said `2 failed` classified `exit_code_unavailable`,
|
|
348
|
+
// became an instrument gap, and ended the turn normally. The evidence of
|
|
349
|
+
// failure was in the record, was parseable, and was thrown away. A host's
|
|
350
|
+
// silence about an exit status tells us nothing about the operator's work;
|
|
351
|
+
// a runner's own failure count tells us plenty.
|
|
352
|
+
if (summary !== null && summary.failed > 0) return { kind: 'FAIL_EVIDENCE' };
|
|
353
|
+
|
|
354
|
+
if (r.exit_code === null || r.exit_code === undefined) {
|
|
355
|
+
return { kind: 'INVALID_RUN', reason: 'exit_code_unavailable' };
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// A LOAD FAILURE, and only where it is not contradicted by a clean summary.
|
|
359
|
+
// The patterns are broad on purpose — `fatal error`, `cannot find module` —
|
|
360
|
+
// and a passing run that merely PRINTS one is common: vitest echoes a
|
|
361
|
+
// `stderr |` block verbatim, so any test exercising an error path can carry
|
|
362
|
+
// the words. Checking this above a summary reporting passes refused honest
|
|
363
|
+
// work, which is the expensive direction for a gate that can block a turn.
|
|
364
|
+
// The case the original ordering was defending — "a suite that never loaded
|
|
365
|
+
// can still print a summary of the zero tests it ran" — is already covered
|
|
366
|
+
// by `zero_tests_discovered` below, and by the `passed === 0` clause here.
|
|
367
|
+
if (
|
|
368
|
+
(summary === null || summary.passed === 0)
|
|
369
|
+
&& LOAD_FAILURE_PATTERNS.some((re) => re.test(text))
|
|
370
|
+
) {
|
|
371
|
+
return { kind: 'INVALID_RUN', reason: 'fixture_or_load_failure' };
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (summary !== null) {
|
|
375
|
+
if (summary.failed > 0) return { kind: 'FAIL_EVIDENCE' };
|
|
376
|
+
if (summary.total === 0 || (summary.total === null && summary.passed === 0)) {
|
|
377
|
+
return { kind: 'INVALID_RUN', reason: 'zero_tests_discovered' };
|
|
378
|
+
}
|
|
379
|
+
if (summary.total !== null && summary.total > 0 && summary.passed === 0) {
|
|
380
|
+
return { kind: 'INVALID_RUN', reason: 'zero_tests_discovered' };
|
|
381
|
+
}
|
|
382
|
+
if (r.exit_code !== 0) {
|
|
383
|
+
return { kind: 'INVALID_RUN', reason: 'nonzero_exit_without_test_failure' };
|
|
384
|
+
}
|
|
385
|
+
return { kind: 'PASS_EVIDENCE_OK' };
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (r.exit_code !== 0) {
|
|
389
|
+
return { kind: 'INVALID_RUN', reason: 'nonzero_exit_without_test_failure' };
|
|
390
|
+
}
|
|
391
|
+
return { kind: 'PASS_EVIDENCE_OK' };
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* The runner a command names, or `other`.
|
|
396
|
+
*
|
|
397
|
+
* Advisory only — nothing in `classifyRun` branches on it. It exists so a
|
|
398
|
+
* finding can say which parser was in play when a verdict is disputed, which is
|
|
399
|
+
* the difference between "the classifier is wrong" and "the classifier has
|
|
400
|
+
* never met this runner".
|
|
401
|
+
*/
|
|
402
|
+
export function runnerOf(command: string): string {
|
|
403
|
+
for (const [name, re] of [
|
|
404
|
+
['vitest', /\bvitest\b/i],
|
|
405
|
+
['jest', /\bjest\b/i],
|
|
406
|
+
['pytest', /\bpytest\b/i],
|
|
407
|
+
['phpunit', /\bphpunit\b/i],
|
|
408
|
+
['pest', /\bpest\b/i],
|
|
409
|
+
['go', /\bgo\s+test\b/i],
|
|
410
|
+
['cargo', /\bcargo\s+test\b/i],
|
|
411
|
+
] as const) {
|
|
412
|
+
if (re.test(command)) return name;
|
|
413
|
+
}
|
|
414
|
+
return 'other';
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* One turn's recorded runs, as the recorder left them.
|
|
419
|
+
*
|
|
420
|
+
* `edits_this_turn` is the recorder's OWN total, not a count taken from a
|
|
421
|
+
* transcript. Both numbers in the placement comparison below therefore come
|
|
422
|
+
* from the same counter in the same file, so the comparison cannot be thrown
|
|
423
|
+
* off by a transcript reader and the recorder disagreeing about what an edit is.
|
|
424
|
+
*/
|
|
425
|
+
export interface TurnRunState {
|
|
426
|
+
readonly runs: readonly unknown[];
|
|
427
|
+
readonly edits_this_turn: number;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* What the records say about the turn, and whether they may be refused on.
|
|
432
|
+
*
|
|
433
|
+
* Three fields rather than a boolean because the three answers are genuinely
|
|
434
|
+
* different: a pass allows, a failure refuses, and a gap in the INSTRUMENT
|
|
435
|
+
* allows while saying so. Collapsing the third into either of the others is
|
|
436
|
+
* Risk 1 of the plan realised — "a passing run classifies INVALID_RUN" and the
|
|
437
|
+
* turn is refused for the recorder's shortcoming.
|
|
438
|
+
*/
|
|
439
|
+
export interface RecordReading {
|
|
440
|
+
/** A `PASS_EVIDENCE_OK` record placed after the turn's last edit. */
|
|
441
|
+
readonly passed: boolean;
|
|
442
|
+
/** A `FAIL_EVIDENCE` record placed after the turn's last edit. */
|
|
443
|
+
readonly failed: boolean;
|
|
444
|
+
/** Nothing judgeable, and the reason is the recorder's — never refuse on this. */
|
|
445
|
+
readonly instrumentGap: boolean;
|
|
446
|
+
/** Every non-passing verdict in the window, for the finding text. Advisory. */
|
|
447
|
+
readonly reasons: readonly InvalidRunReason[];
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Where in the turn's edit sequence a record sits, or `null` when unplaceable.
|
|
452
|
+
*
|
|
453
|
+
* `after_edits` is the recorder's edit counter as it stood when the command ran.
|
|
454
|
+
* A record carrying no number cannot be placed at all, and an unplaceable record
|
|
455
|
+
* is treated below as an instrument gap rather than as either evidence — which
|
|
456
|
+
* is the direction that under-refuses.
|
|
457
|
+
*/
|
|
458
|
+
function placeRecord(raw: unknown): number | null {
|
|
459
|
+
if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) return null;
|
|
460
|
+
const v = (raw as Record<string, unknown>)['after_edits'];
|
|
461
|
+
if (typeof v !== 'number' || !Number.isFinite(v)) return null;
|
|
462
|
+
return v;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Read the turn's records for evidence that its LAST edit was verified.
|
|
467
|
+
*
|
|
468
|
+
* A record qualifies only when `after_edits >= edits_this_turn` — nothing was
|
|
469
|
+
* edited after the command ran. That is the same freshness clause the transcript
|
|
470
|
+
* path applies by scanning only after the last edit, and keeping it is the
|
|
471
|
+
* difference between this being a stricter reading of the same rule and being a
|
|
472
|
+
* different, weaker rule wearing the same name.
|
|
473
|
+
*/
|
|
474
|
+
export function readRunEvidence(state: TurnRunState): RecordReading {
|
|
475
|
+
const reasons: InvalidRunReason[] = [];
|
|
476
|
+
let passed = false;
|
|
477
|
+
let failed = false;
|
|
478
|
+
let gap = false;
|
|
479
|
+
|
|
480
|
+
for (const raw of state.runs) {
|
|
481
|
+
const placed = placeRecord(raw);
|
|
482
|
+
if (placed === null) {
|
|
483
|
+
gap = true;
|
|
484
|
+
reasons.push('unreadable_record');
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
if (placed < state.edits_this_turn) continue; // an edit followed this run
|
|
488
|
+
const verdict = classifyRun(raw);
|
|
489
|
+
if (verdict.kind === 'PASS_EVIDENCE_OK') {
|
|
490
|
+
passed = true;
|
|
491
|
+
continue;
|
|
492
|
+
}
|
|
493
|
+
if (verdict.kind === 'FAIL_EVIDENCE') {
|
|
494
|
+
failed = true;
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
497
|
+
reasons.push(verdict.reason);
|
|
498
|
+
if (isInstrumentGap(verdict)) gap = true;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// A pass or a failure is a reading of the RUN, and either one settles the
|
|
502
|
+
// question; the gap only matters when nothing judgeable was found at all.
|
|
503
|
+
return { passed, failed, instrumentGap: gap && !passed && !failed, reasons };
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* The test files a command names, or the empty string when it names none.
|
|
508
|
+
*
|
|
509
|
+
* The key groups records so that a red and a green about the SAME target can be
|
|
510
|
+
* paired. A command with no explicit target — `npx vitest run`, `task test` —
|
|
511
|
+
* gets the empty key, which is its own group: a whole-suite red followed by a
|
|
512
|
+
* whole-suite green is a legitimate red-then-green, and pairing it with a
|
|
513
|
+
* single-file run would claim a sequence nobody observed.
|
|
514
|
+
*
|
|
515
|
+
* Sorted and joined so argument ORDER cannot split one target set into two
|
|
516
|
+
* groups.
|
|
517
|
+
*/
|
|
518
|
+
export function testTargetKey(command: string): string {
|
|
519
|
+
const targets = new Set<string>();
|
|
520
|
+
for (const token of command.split(/\s+/)) {
|
|
521
|
+
const bare = token.replace(/^['"]|['"]$/g, '');
|
|
522
|
+
if (/[./]\w*(?:test|spec)\w*\.[a-z]+$/i.test(bare) || /(?:Test|Spec)\.php$/.test(bare)) {
|
|
523
|
+
targets.add(bare);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
return [...targets].sort().join('|');
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Whether the turn's records show a red run turning green over the same target.
|
|
531
|
+
*
|
|
532
|
+
* WHY THIS IS EVIDENCE AND A NEW TEST FILE ALONE IS NOT. Detector F's escape
|
|
533
|
+
* hatch is "a test file was touched somewhere in the turn", which a file
|
|
534
|
+
* containing `it('works', () => expect(true).toBe(true))` satisfies. A test
|
|
535
|
+
* never seen red has unknown sensitivity: it may assert nothing the change
|
|
536
|
+
* could break. A red-then-green pair over one target is the cheapest observable
|
|
537
|
+
* proof that the assertion discriminates — it failed, the code changed, it
|
|
538
|
+
* passed.
|
|
539
|
+
*
|
|
540
|
+
* ORDER COMES FROM THE ARRAY, not from a timestamp. The recorder appends, so
|
|
541
|
+
* index order IS run order, and it survives a host whose clock is coarse or
|
|
542
|
+
* whose records share a second. Only records at or after the turn's edit total
|
|
543
|
+
* count for the GREEN half, for the same freshness reason `readRunEvidence`
|
|
544
|
+
* applies; the RED half is deliberately unbounded, because a red necessarily
|
|
545
|
+
* predates the edit that fixed it.
|
|
546
|
+
*/
|
|
547
|
+
export function hasRedThenGreen(state: TurnRunState): boolean {
|
|
548
|
+
let reddenedAnywhere = false;
|
|
549
|
+
const reddened = new Set<string>();
|
|
550
|
+
for (let i = 0; i < state.runs.length; i += 1) {
|
|
551
|
+
const raw = state.runs[i];
|
|
552
|
+
const verdict = classifyRun(raw);
|
|
553
|
+
const key = testTargetKey(
|
|
554
|
+
typeof raw === 'object' && raw !== null && !Array.isArray(raw)
|
|
555
|
+
? String((raw as Record<string, unknown>)['command'] ?? '')
|
|
556
|
+
: '',
|
|
557
|
+
);
|
|
558
|
+
if (RED_VERDICTS(verdict)) {
|
|
559
|
+
reddened.add(key);
|
|
560
|
+
reddenedAnywhere = true;
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
563
|
+
if (verdict.kind !== 'PASS_EVIDENCE_OK') continue;
|
|
564
|
+
const placed = placeRecord(raw);
|
|
565
|
+
if (placed === null || placed < state.edits_this_turn) continue;
|
|
566
|
+
// A WHOLE-SUITE green (the empty key) settles any reddened target: it is
|
|
567
|
+
// strictly stronger evidence than a single-file green, and "red on the
|
|
568
|
+
// file, then the whole suite" is the common honest sequence. A targeted
|
|
569
|
+
// green still only settles its own target.
|
|
570
|
+
if (key === '' ? reddenedAnywhere : reddened.has(key)) return true;
|
|
571
|
+
}
|
|
572
|
+
return false;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Which verdicts count as the RED half of a red-then-green pair.
|
|
577
|
+
*
|
|
578
|
+
* Deliberately wider than `FAIL_EVIDENCE`, and the canonical TDD first red is
|
|
579
|
+
* why: a new test importing a module that does not exist yet prints `Cannot
|
|
580
|
+
* find module`, which classifies `fixture_or_load_failure`. Requiring a parsed
|
|
581
|
+
* failure COUNT for the red half would refuse exactly the discipline this
|
|
582
|
+
* detector exists to encourage — including the shape of work that introduced
|
|
583
|
+
* this module.
|
|
584
|
+
*
|
|
585
|
+
* The red half needs to show the target NOT PASSING, which is a weaker bar than
|
|
586
|
+
* showing how many assertions failed. The green half carries the placement and
|
|
587
|
+
* freshness checks, so the pair is still anchored to the turn's last edit.
|
|
588
|
+
* `exit_code_unavailable` and `unreadable_record` are excluded: those say the
|
|
589
|
+
* instrument failed, not that the target did.
|
|
590
|
+
*/
|
|
591
|
+
function RED_VERDICTS(verdict: VerificationVerdict): boolean {
|
|
592
|
+
if (verdict.kind === 'FAIL_EVIDENCE') return true;
|
|
593
|
+
if (verdict.kind !== 'INVALID_RUN') return false;
|
|
594
|
+
return (
|
|
595
|
+
verdict.reason === 'fixture_or_load_failure'
|
|
596
|
+
|| verdict.reason === 'nonzero_exit_without_test_failure'
|
|
597
|
+
|| verdict.reason === 'zero_tests_discovered'
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* The refusal text for a record-path finding.
|
|
603
|
+
*
|
|
604
|
+
* It lives HERE, beside the verdicts it names, rather than in the detector that
|
|
605
|
+
* emits it: this module owns the vocabulary (`not_a_verification_command`,
|
|
606
|
+
* `zero_tests_discovered`, …), so a sentence quoting one of those words has to
|
|
607
|
+
* change whenever the vocabulary does. Splitting the two is how a refusal comes
|
|
608
|
+
* to name a reason the classifier stopped producing.
|
|
609
|
+
*
|
|
610
|
+
* It names the VERDICT rather than restating the rule, because the operator's
|
|
611
|
+
* next action differs per reason: a failed run means fix the code, a zero-test
|
|
612
|
+
* run means the filter matched nothing, and `not_a_verification_command` means
|
|
613
|
+
* the thing that ran could not have checked anything.
|
|
614
|
+
*/
|
|
615
|
+
export function describeRecordFinding(
|
|
616
|
+
failed: boolean,
|
|
617
|
+
reasons: readonly InvalidRunReason[],
|
|
618
|
+
): string {
|
|
619
|
+
const tail =
|
|
620
|
+
' (verify-before-complete: no verification command run in this message → '
|
|
621
|
+
+ 'you cannot claim it passes)';
|
|
622
|
+
if (failed) {
|
|
623
|
+
return (
|
|
624
|
+
'this turn changed a file and its verification run FAILED — a recorded '
|
|
625
|
+
+ 'run after the last edit reports failing tests' + tail
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
if (reasons.length === 0) {
|
|
629
|
+
return (
|
|
630
|
+
'this turn changed a file and no verification run was recorded after '
|
|
631
|
+
+ 'the last edit' + tail
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
return (
|
|
635
|
+
'this turn changed a file and no recorded run after the last edit proves '
|
|
636
|
+
+ `anything — the runs seen classify as ${[...new Set(reasons)].join(', ')}`
|
|
637
|
+
+ tail
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/** The refusal text when a new test file arrived with no red it passed. */
|
|
642
|
+
export const NO_RED_EVIDENCE_REASON =
|
|
643
|
+
'a completion claim over production code this turn changed, with a test '
|
|
644
|
+
+ 'file touched but `no_red_evidence` — no recorded run shows that test '
|
|
645
|
+
+ 'target failing and then passing after the last edit. A test never seen '
|
|
646
|
+
+ 'red has unknown sensitivity: run it against the unfixed code, watch it '
|
|
647
|
+
+ 'fail for the intended reason, then fix and re-run';
|