@event4u/agent-config 16.1.0 → 16.2.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.
Files changed (217) hide show
  1. package/.augment-plugin/marketplace.json +3 -3
  2. package/.augment-plugin/plugin.json +1 -1
  3. package/.claude-plugin/marketplace.json +1 -1
  4. package/CHANGELOG.md +237 -248
  5. package/dist/agent-src/commands/roadmap/process-full.md +1 -1
  6. package/dist/agent-src/commands/work.md +4 -0
  7. package/dist/agent-src/contexts/execution/autonomy-mechanics.md +4 -8
  8. package/dist/agent-src/rules/ui-audit-gate.md +1 -2
  9. package/dist/agent-src/skills/accessibility-auditor/data/aria-patterns.csv +1 -1
  10. package/dist/agent-src/skills/accessibility-auditor/data/manifest.json +2 -2
  11. package/dist/agent-src/skills/design-review/SKILL.md +30 -4
  12. package/dist/agent-src/skills/design-review/references/verification-automation.md +32 -0
  13. package/dist/agent-src/skills/existing-ui-audit/SKILL.md +16 -1
  14. package/dist/agent-src/skills/existing-ui-audit/references/component-taxonomy-detection.md +56 -0
  15. package/dist/agent-src/skills/markitdown/SKILL.md +4 -0
  16. package/dist/agent-src/skills/playwright-testing/SKILL.md +30 -6
  17. package/dist/agent-src/skills/ui-component-architect/SKILL.md +23 -0
  18. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  19. package/dist/agent-src/templates/roadmaps.md +30 -0
  20. package/dist/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
  21. package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +260 -21
  22. package/dist/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
  23. package/dist/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
  24. package/dist/agent-src/templates/scripts/work_engine/state.ts +57 -7
  25. package/dist/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
  26. package/dist/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
  27. package/dist/cli/python/knowledge_ingest.js +1 -1
  28. package/dist/cli/python/workspace_drive.js +1 -1
  29. package/dist/cli-delegate/{chunk-S3VFXMTM.js → chunk-4XFP2Y3E.js} +1 -1
  30. package/dist/cli-delegate/{chunk-HDVVGPQW.js → chunk-7XGBX6ZH.js} +471 -387
  31. package/dist/cli-delegate/{chunk-GJCBB2UG.js → chunk-FP36SWJD.js} +432 -144
  32. package/dist/cli-delegate/{chunk-UNCRJBT3.js → chunk-IGD6ERX6.js} +31 -0
  33. package/dist/cli-delegate/{chunk-FXYUYU4N.js → chunk-JNMVLWLR.js} +42 -1
  34. package/dist/cli-delegate/{chunk-QJWAUMJW.js → chunk-YJ72UIAK.js} +730 -15
  35. package/dist/cli-delegate/cmd_conformance.js +902 -782
  36. package/dist/cli-delegate/cmd_doctor.js +2 -2
  37. package/dist/cli-delegate/cmd_export.js +2 -2
  38. package/dist/cli-delegate/cmd_handoff.js +4 -4
  39. package/dist/cli-delegate/cmd_memory_get.js +1 -1
  40. package/dist/cli-delegate/cmd_prune.js +2 -2
  41. package/dist/cli-delegate/cmd_route_audit.js +1 -1
  42. package/dist/cli-delegate/cmd_session_recycle.js +2 -2
  43. package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
  44. package/dist/cli-delegate/cmd_settings_set.js +1 -1
  45. package/dist/cli-delegate/cmd_sync.js +2 -2
  46. package/dist/cli-delegate/cmd_uninstall.js +2 -2
  47. package/dist/cli-delegate/cmd_validate.js +2 -2
  48. package/dist/discovery/deprecation-report.md +1 -1
  49. package/dist/discovery/discovery-manifest.json +10 -10
  50. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  51. package/dist/discovery/discovery-manifest.summary.md +1 -1
  52. package/dist/discovery/dormancy-report.md +1 -1
  53. package/dist/discovery/orphan-report.md +1 -1
  54. package/dist/discovery/packs.json +2 -2
  55. package/dist/discovery/trust-report.md +1 -1
  56. package/dist/discovery/workspaces.json +2 -2
  57. package/dist/hooks/dispatch.js +6666 -2581
  58. package/dist/hooks/dispatch.sha256 +1 -0
  59. package/dist/install/install.mjs +848 -733
  60. package/dist/mcp/registry-manifest.json +2 -2
  61. package/dist/mcp/server.json +2 -2
  62. package/dist/mcp/server.mjs +2747 -1510
  63. package/dist/server/schemas/settings.js +23 -0
  64. package/dist/ui/assets/index-Cwq4d0hX.js +23 -0
  65. package/dist/ui/assets/index-Cyjy8bWO.css +1 -0
  66. package/dist/ui/index.html +2 -2
  67. package/docs/contracts/settings-classes.md +6 -4
  68. package/package.json +4 -4
  69. package/src/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
  70. package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +260 -21
  71. package/src/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
  72. package/src/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
  73. package/src/agent-src/templates/scripts/work_engine/state.ts +57 -7
  74. package/src/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
  75. package/src/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
  76. package/src/config/agent-settings.template.yml +29 -0
  77. package/src/config/ci-local-parity.yml +18 -0
  78. package/src/config/continuity-surface.json +2 -2
  79. package/src/config/gate-coverage.yml +234 -3
  80. package/src/config/gate-violation-baselines.json +7 -3
  81. package/src/config/hook-latency-budget.json +21 -0
  82. package/src/config/hook-token-budget.json +2 -2
  83. package/src/config/metric-registry.yml +114 -0
  84. package/src/config/placeholder-drift-budget.json +10 -0
  85. package/src/config/routing-coverage-seed.json +18 -4
  86. package/src/scripts/_cli/cmd_doctor.ts +5 -19
  87. package/src/scripts/_cli/doctor_execution.ts +100 -17
  88. package/src/scripts/_cli/doctor_network_posture.ts +226 -0
  89. package/src/scripts/_lib/body_portable.ts +102 -0
  90. package/src/scripts/_lib/cc_transcript.ts +132 -1
  91. package/src/scripts/_lib/concern_sla_window.ts +200 -0
  92. package/src/scripts/_lib/forge_protection.ts +114 -0
  93. package/src/scripts/_lib/forge_reader.ts +558 -0
  94. package/src/scripts/_lib/host_env_write.ts +111 -0
  95. package/src/scripts/_lib/llm_proposer_transport.ts +14 -4
  96. package/src/scripts/_lib/obligations.ts +119 -8
  97. package/src/scripts/_lib/probe_inputs.ts +271 -0
  98. package/src/scripts/_lib/read_surface_scan.ts +322 -0
  99. package/src/scripts/_lib/release_highlights.ts +2 -1
  100. package/src/scripts/_lib/retrieval_sanitize.ts +77 -16
  101. package/src/scripts/_lib/shell_write_shapes.ts +108 -0
  102. package/src/scripts/_lib/structural_hiding.ts +622 -0
  103. package/src/scripts/_lib/turn_end_refusals.ts +525 -4
  104. package/src/scripts/_lib/turn_end_transcript.ts +216 -0
  105. package/src/scripts/_lib/verification_command.ts +324 -0
  106. package/src/scripts/_lib/verification_evidence.ts +647 -0
  107. package/src/scripts/_lib/verify_clause.ts +280 -0
  108. package/src/scripts/ai_team/team_dispatch.ts +4 -0
  109. package/src/scripts/before_complete_hook.ts +340 -23
  110. package/src/scripts/bench_hook_latency.ts +412 -5
  111. package/src/scripts/cache_realization_report.ts +61 -1
  112. package/src/scripts/check_argument_hint.ts +287 -0
  113. package/src/scripts/check_claims.ts +125 -1
  114. package/src/scripts/check_enforcement_coverage.ts +38 -9
  115. package/src/scripts/check_enforcement_matrix.ts +245 -3
  116. package/src/scripts/check_finding_dispositions.ts +22 -3
  117. package/src/scripts/check_generator_sync.ts +71 -0
  118. package/src/scripts/check_host_docs_digest.ts +786 -0
  119. package/src/scripts/check_host_format_column.ts +559 -0
  120. package/src/scripts/check_input_references.ts +228 -0
  121. package/src/scripts/check_inputs_parity.ts +235 -0
  122. package/src/scripts/check_kernel_edit_ratified.ts +47 -5
  123. package/src/scripts/check_kill_switch_table.ts +294 -0
  124. package/src/scripts/check_placeholder_drift.ts +221 -0
  125. package/src/scripts/check_read_surface_coverage.ts +515 -0
  126. package/src/scripts/check_release_holds.ts +2 -1
  127. package/src/scripts/check_routing_coverage.ts +378 -11
  128. package/src/scripts/check_static_parity.ts +17 -0
  129. package/src/scripts/ci_settle.ts +145 -19
  130. package/src/scripts/closure_scan.ts +115 -1
  131. package/src/scripts/compile_hook_manifest.ts +60 -16
  132. package/src/scripts/external_sources_denylist.json +29 -4
  133. package/src/scripts/hook_manifest.json +1 -1
  134. package/src/scripts/hook_manifest.yaml +42 -5
  135. package/src/scripts/hooks/block_config_weakening.ts +229 -4
  136. package/src/scripts/hooks/block_kernel_rule_writes.ts +9 -78
  137. package/src/scripts/hooks/block_plumbing_writes.ts +314 -0
  138. package/src/scripts/hooks/bundle_integrity.ts +291 -0
  139. package/src/scripts/hooks/chain_nudge_hook.ts +1 -2
  140. package/src/scripts/hooks/code_graph_context_hook.ts +1 -2
  141. package/src/scripts/hooks/comment_discipline_hook.ts +1 -3
  142. package/src/scripts/hooks/concern_registry.ts +2 -0
  143. package/src/scripts/hooks/concern_timings.ts +94 -0
  144. package/src/scripts/hooks/delegation_nudge_hook.ts +1 -2
  145. package/src/scripts/hooks/design_pass_hook.ts +100 -9
  146. package/src/scripts/hooks/design_slop_hook.ts +1 -2
  147. package/src/scripts/hooks/dispatch_hook.ts +25 -19
  148. package/src/scripts/hooks/edit_shape_hook.ts +1 -2
  149. package/src/scripts/hooks/end_review_nudge_hook.ts +1 -1
  150. package/src/scripts/hooks/evidence_independence.ts +1 -2
  151. package/src/scripts/hooks/exit_codes.ts +128 -0
  152. package/src/scripts/hooks/host_lowering.json +1 -0
  153. package/src/scripts/hooks/host_lowering.ts +128 -3
  154. package/src/scripts/hooks/host_lowering.yaml +262 -46
  155. package/src/scripts/hooks/host_semantics.ts +30 -2
  156. package/src/scripts/hooks/injection_budget.ts +9 -2
  157. package/src/scripts/hooks/interruption_ledger_hook.ts +1 -2
  158. package/src/scripts/hooks/journal_record_hook.ts +1 -2
  159. package/src/scripts/hooks/memory_recall_hook.ts +1 -2
  160. package/src/scripts/hooks/obligation_settle_hook.ts +151 -5
  161. package/src/scripts/hooks/one_question_per_ask_hook.ts +25 -4
  162. package/src/scripts/hooks/orchestration_record_hook.ts +1 -2
  163. package/src/scripts/hooks/permission_gate.ts +1 -2
  164. package/src/scripts/hooks/push_settle_hook.ts +1 -3
  165. package/src/scripts/hooks/replay_hook.ts +2 -1
  166. package/src/scripts/hooks/reread_guard_hook.ts +1 -2
  167. package/src/scripts/hooks/review_baseline_hook.ts +1 -2
  168. package/src/scripts/hooks/rtk_wrap_hook.ts +1 -2
  169. package/src/scripts/hooks/rule_inject_hook.ts +8 -5
  170. package/src/scripts/hooks/run_continuation_hook.ts +13 -15
  171. package/src/scripts/hooks/skill_route_hook.ts +1 -2
  172. package/src/scripts/hooks/source_first_gate_hook.ts +1 -2
  173. package/src/scripts/hooks/spawn_guard_shadow_hook.ts +1 -2
  174. package/src/scripts/hooks/subagent_ledger_hook.ts +1 -2
  175. package/src/scripts/hooks/suggestion_capture_hook.ts +1 -1
  176. package/src/scripts/hooks/table_fingerprint.ts +49 -0
  177. package/src/scripts/hooks/telemetry_flush_hook.ts +1 -2
  178. package/src/scripts/hooks/telemetry_usage_hook.ts +1 -2
  179. package/src/scripts/hooks/tool_result_bytes_hook.ts +93 -9
  180. package/src/scripts/hooks/turn_end_gate_hook.ts +337 -324
  181. package/src/scripts/hooks/ui_route_nudge_hook.ts +1 -2
  182. package/src/scripts/install.ts +3 -4
  183. package/src/scripts/lint_deny_text.ts +419 -0
  184. package/src/scripts/lint_exit_codes.ts +262 -0
  185. package/src/scripts/lint_hook_manifest.ts +55 -0
  186. package/src/scripts/lint_roadmap_blockers.ts +78 -12
  187. package/src/scripts/lint_skill_descriptions.ts +1 -1
  188. package/src/scripts/lint_skill_scripts_readonly.ts +1 -1
  189. package/src/scripts/mcp_server/prompts.ts +142 -1
  190. package/src/scripts/measure_skill_ranker_baseline.ts +305 -51
  191. package/src/scripts/measure_turn_end_gate.ts +193 -0
  192. package/src/scripts/memory_lookup.ts +1 -1
  193. package/src/scripts/prepack-check.mjs +53 -0
  194. package/src/scripts/release_publication.ts +13 -1
  195. package/src/scripts/report_invocation_surface.ts +383 -0
  196. package/src/scripts/report_obligation_carriers.ts +699 -0
  197. package/src/scripts/report_obligation_writer_split.ts +236 -0
  198. package/src/scripts/report_skill_menu_census.ts +208 -0
  199. package/src/scripts/report_standing_payload_by_host.ts +1 -1
  200. package/src/scripts/roadmap_verify_share.ts +213 -0
  201. package/src/scripts/schemas/command.schema.json +44 -0
  202. package/src/scripts/schemas/review-findings.schema.json +4 -0
  203. package/src/scripts/schemas/skill.schema.json +44 -0
  204. package/src/scripts/self_review_gate.ts +359 -10
  205. package/src/scripts/semantic_parity_census.ts +314 -0
  206. package/src/scripts/session_eol_report.ts +54 -1
  207. package/src/scripts/sweep_dead_scan_roots.ts +1 -1
  208. package/src/scripts/trigger_eval_grandfather.json +0 -2
  209. package/src/scripts/trigger_eval_rotation.ts +122 -27
  210. package/src/scripts/ui_conformance_probe.ts +156 -4
  211. package/src/scripts/update_prices.ts +85 -3
  212. package/src/scripts/write_bundle_digest.mjs +74 -0
  213. package/src/server/schemas/settings.ts +31 -0
  214. package/src/templates/consumer-settings/ONBOARDING.md +1 -0
  215. package/dist/ui/assets/index-DH1xc62v.js +0 -29
  216. package/dist/ui/assets/index-jSfMW8_t.css +0 -1
  217. /package/dist/cli-delegate/{chunk-YUM5KURG.js → chunk-7RQP4UDH.js} +0 -0
@@ -4,6 +4,7 @@ import {
4
4
  } from "./chunk-F7DQWYR5.js";
5
5
  import {
6
6
  has_stable_session_id,
7
+ owns_session_state,
7
8
  prune_legacy_state_file,
8
9
  prune_stale_session_states,
9
10
  session_state_file,
@@ -563,6 +564,472 @@ function validateAssumption(input, label = "assumption") {
563
564
  import fs from "node:fs";
564
565
  import path from "node:path";
565
566
  import { fileURLToPath, pathToFileURL } from "node:url";
567
+
568
+ // src/scripts/_lib/verification_command.ts
569
+ var VERIFY_BINARIES = /* @__PURE__ */ new Set([
570
+ "vitest",
571
+ "jest",
572
+ "pytest",
573
+ "phpunit",
574
+ "pest",
575
+ "tsc",
576
+ "eslint",
577
+ "ruff",
578
+ "mypy",
579
+ "pyright",
580
+ "clippy",
581
+ "phpstan",
582
+ "psalm",
583
+ "ecs"
584
+ ]);
585
+ var DELEGATING_HEADS = /* @__PURE__ */ new Set([
586
+ "npx",
587
+ "bunx",
588
+ "sudo",
589
+ "time",
590
+ "command"
591
+ ]);
592
+ var PATH_PREFIXES = [
593
+ "./",
594
+ "vendor/bin/",
595
+ ".venv/bin/",
596
+ "node_modules/.bin/",
597
+ "bin/"
598
+ ];
599
+ var VERIFY_WORD = /(^|[-_:])(test|tests|check|checks|lint|typecheck|tsc|qa|preflight|smoke|validate)([-_:]|$)/i;
600
+ var NPM_SCRIPT_WORD = /(^|[-_:])(test|tests|check|checks|lint|typecheck|tsc|build)([-_:]|$)/i;
601
+ function stripComment(command) {
602
+ let inSingle = false;
603
+ let inDouble = false;
604
+ for (let i = 0; i < command.length; i += 1) {
605
+ const ch = command[i];
606
+ if (ch === "'" && !inDouble) inSingle = !inSingle;
607
+ else if (ch === '"' && !inSingle) inDouble = !inDouble;
608
+ else if (ch === "#" && !inSingle && !inDouble && (i === 0 || /\s/.test(command[i - 1] ?? ""))) {
609
+ return command.slice(0, i);
610
+ }
611
+ }
612
+ return command;
613
+ }
614
+ function splitSegments(command) {
615
+ const raw = [];
616
+ let buf = "";
617
+ let inSingle = false;
618
+ let inDouble = false;
619
+ for (let i = 0; i < command.length; i += 1) {
620
+ const ch = command[i];
621
+ const two = command.slice(i, i + 2);
622
+ if (ch === "'" && !inDouble) {
623
+ inSingle = !inSingle;
624
+ buf += ch;
625
+ continue;
626
+ }
627
+ if (ch === '"' && !inSingle) {
628
+ inDouble = !inDouble;
629
+ buf += ch;
630
+ continue;
631
+ }
632
+ if (!inSingle && !inDouble) {
633
+ if (two === "&&" || two === "||") {
634
+ raw.push({ text: buf, sep: two });
635
+ buf = "";
636
+ i += 1;
637
+ continue;
638
+ }
639
+ if (ch === ";" || ch === "|") {
640
+ raw.push({ text: buf, sep: ch });
641
+ buf = "";
642
+ continue;
643
+ }
644
+ }
645
+ buf += ch;
646
+ }
647
+ raw.push({ text: buf, sep: null });
648
+ return raw.map((entry, idx) => {
649
+ const next = raw[idx + 1];
650
+ const nextWords = next ? words(next.text) : [];
651
+ return {
652
+ words: words(entry.text),
653
+ nextSep: entry.sep,
654
+ nextHead: (nextWords[0] ?? null)?.toLowerCase() ?? null
655
+ };
656
+ });
657
+ }
658
+ function words(text) {
659
+ const tokens = text.trim().split(/\s+/).filter((w) => w.length > 0);
660
+ let i = 0;
661
+ while (i < tokens.length && /^[A-Za-z_][A-Za-z0-9_]*=/.test(tokens[i])) i += 1;
662
+ return tokens.slice(i);
663
+ }
664
+ function bareName(word) {
665
+ let w = word;
666
+ for (const prefix of PATH_PREFIXES) {
667
+ if (w.startsWith(prefix)) {
668
+ w = w.slice(prefix.length);
669
+ break;
670
+ }
671
+ }
672
+ const slash = w.lastIndexOf("/");
673
+ return (slash >= 0 ? w.slice(slash + 1) : w).toLowerCase();
674
+ }
675
+ function failureIsDiscarded(seg) {
676
+ if (seg.nextSep === "||") return seg.nextHead === "true" || seg.nextHead === ":";
677
+ if (seg.nextSep === ";") return seg.nextHead === "true" || seg.nextHead === ":";
678
+ return false;
679
+ }
680
+ function segmentVerifies(rawWords) {
681
+ const w = rawWords.filter((x) => x.length > 0);
682
+ let idx = 0;
683
+ while (idx < w.length && DELEGATING_HEADS.has(bareName(w[idx]))) idx += 1;
684
+ if (idx >= w.length) return false;
685
+ const head = bareName(w[idx]);
686
+ const rest = w.slice(idx + 1).map((x) => x.toLowerCase());
687
+ const arg1 = rest[0] ?? "";
688
+ if (VERIFY_BINARIES.has(head)) return true;
689
+ switch (head) {
690
+ case "npm":
691
+ case "pnpm":
692
+ case "yarn":
693
+ case "bun": {
694
+ if (arg1 === "dlx" || arg1 === "exec") return segmentVerifies(rest.slice(1));
695
+ const script = arg1 === "run" ? rest[1] ?? "" : arg1;
696
+ return NPM_SCRIPT_WORD.test(script);
697
+ }
698
+ case "task":
699
+ return VERIFY_WORD.test(arg1) || /^ci([-_:]|$)/i.test(arg1);
700
+ case "cargo":
701
+ return arg1 === "test" || arg1 === "check" || arg1 === "clippy";
702
+ case "go":
703
+ return arg1 === "test" || arg1 === "vet";
704
+ case "make":
705
+ return VERIFY_WORD.test(arg1) || /^ci([-_:]|$)/i.test(arg1);
706
+ case "composer":
707
+ return VERIFY_WORD.test(arg1) || arg1 === "phpstan";
708
+ case "php":
709
+ return arg1 === "artisan" && (rest[1] ?? "") === "test";
710
+ case "artisan":
711
+ return arg1 === "test";
712
+ case "python":
713
+ case "python3":
714
+ return arg1 === "-m" && VERIFY_BINARIES.has(rest[1] ?? "");
715
+ case "node":
716
+ case "deno":
717
+ return rest.some((a) => a === "test" || a.startsWith("--test"));
718
+ case "scripts-run": {
719
+ const script = bareName(arg1);
720
+ return /^(check|lint|verify|validate)[-_]/.test(script);
721
+ }
722
+ default:
723
+ return false;
724
+ }
725
+ }
726
+ function isVerificationCommand(command) {
727
+ if (!command) return false;
728
+ const segments = splitSegments(stripComment(command));
729
+ return segments.some((seg) => !failureIsDiscarded(seg) && segmentVerifies(seg.words));
730
+ }
731
+ var _WIDE_CANDIDATE_RE = /\b(test|tests|vitest|jest|pytest|phpunit|pest|tsc|eslint|ruff|mypy|pyright|clippy|phpstan|typecheck|lint[-_:a-z]*|build|ci|preflight|smoke[-_:a-z]*|check[-_:a-z]*|validate[-_:a-z]*)\b|\b(task|npm|pnpm|yarn|composer|cargo|go|make|php artisan|bun)\s+(run\s+)?\S*(test|check|lint|build|ci|typecheck)/i;
732
+ function mightBeVerification(command) {
733
+ return _WIDE_CANDIDATE_RE.test(command);
734
+ }
735
+
736
+ // src/scripts/_lib/verification_evidence.ts
737
+ var INSTRUMENT_GAP_REASONS = /* @__PURE__ */ new Set([
738
+ "exit_code_unavailable",
739
+ "unreadable_record"
740
+ ]);
741
+ function isInstrumentGap(verdict) {
742
+ return verdict.kind === "INVALID_RUN" && INSTRUMENT_GAP_REASONS.has(verdict.reason);
743
+ }
744
+ var NON_VERIFYING_EXECUTABLES = /* @__PURE__ */ new Set([
745
+ ":",
746
+ "awk",
747
+ "basename",
748
+ "cat",
749
+ "cd",
750
+ "chmod",
751
+ "cp",
752
+ "curl",
753
+ "cut",
754
+ "date",
755
+ "dirname",
756
+ "echo",
757
+ "exit",
758
+ "export",
759
+ "false",
760
+ "find",
761
+ "git",
762
+ "grep",
763
+ "head",
764
+ "less",
765
+ "ln",
766
+ "ls",
767
+ "mkdir",
768
+ "more",
769
+ "mv",
770
+ "open",
771
+ "printf",
772
+ "pwd",
773
+ "realpath",
774
+ "rg",
775
+ "rm",
776
+ "sed",
777
+ "sleep",
778
+ "sort",
779
+ "source",
780
+ "tail",
781
+ "tee",
782
+ "touch",
783
+ "tr",
784
+ "true",
785
+ "type",
786
+ "uniq",
787
+ "wc",
788
+ "wget",
789
+ "which",
790
+ "yes"
791
+ ]);
792
+ var COMMAND_PREFIXES = /* @__PURE__ */ new Set([
793
+ "bunx",
794
+ "command",
795
+ "env",
796
+ "exec",
797
+ "nice",
798
+ "nohup",
799
+ "npx",
800
+ "sudo",
801
+ "time"
802
+ ]);
803
+ function leadingExecutable(segment) {
804
+ for (const raw of segment.trim().split(/\s+/)) {
805
+ if (raw === "") continue;
806
+ if (/^[A-Za-z_][A-Za-z0-9_]*=/.test(raw)) continue;
807
+ const base = (raw.split("/").pop() ?? raw).replace(/\.exe$/i, "").toLowerCase();
808
+ if (base === "") continue;
809
+ if (COMMAND_PREFIXES.has(base)) continue;
810
+ return base;
811
+ }
812
+ return null;
813
+ }
814
+ function canVerify(command) {
815
+ for (const segment of command.split(/&&|\|\||[;|\n]/)) {
816
+ if (segment.trim() === "") continue;
817
+ const exe = leadingExecutable(segment);
818
+ if (exe !== null && NON_VERIFYING_EXECUTABLES.has(exe)) continue;
819
+ if (isVerificationCommand(segment)) return true;
820
+ }
821
+ return false;
822
+ }
823
+ var KILLED_EXIT_CODES = /* @__PURE__ */ new Set([137, 143]);
824
+ var LOAD_FAILURE_PATTERNS = [
825
+ /\bfailed to load\b/i,
826
+ /\berror(?:s)? collecting\b/i,
827
+ /\bcollection error\b/i,
828
+ /\bcannot find module\b/i,
829
+ /\bmodule not found\b/i,
830
+ /\bunable to load\b/i,
831
+ /\btransform failed\b/i,
832
+ /\bconftest\.py\b.*\berror\b/i,
833
+ /\bcould not open input file\b/i,
834
+ /\bfatal error\b/i
835
+ ];
836
+ function _num(m) {
837
+ return m?.[1] === void 0 ? 0 : Number(m[1]);
838
+ }
839
+ function parseVitestOrJest(text) {
840
+ const line = /^[ \t]*Tests:?[ \t]+(.+)$/m.exec(text);
841
+ if (line?.[1] === void 0) return null;
842
+ const body = line[1];
843
+ const failed = _num(/(\d+)\s+failed/i.exec(body));
844
+ const passed = _num(/(\d+)\s+passed/i.exec(body));
845
+ const totalMatch = /\((\d+)\)\s*$/.exec(body.trim()) ?? /(\d+)\s+total/i.exec(body);
846
+ const total = totalMatch?.[1] === void 0 ? null : Number(totalMatch[1]);
847
+ return { passed, failed, total };
848
+ }
849
+ function parsePytest(text) {
850
+ if (/\bno tests ran\b/i.test(text)) return { passed: 0, failed: 0, total: 0 };
851
+ const line = /^[= ]*((?:\d+\s+(?:passed|failed|error|errors|skipped|xfailed|xpassed|deselected)[,\s]*)+)(?:in\s+[\d.]+s)?[= ]*$/im.exec(
852
+ text
853
+ );
854
+ if (line?.[1] === void 0) return null;
855
+ const body = line[1];
856
+ const passed = _num(/(\d+)\s+passed/i.exec(body));
857
+ const failed = _num(/(\d+)\s+failed/i.exec(body)) + _num(/(\d+)\s+errors?\b/i.exec(body));
858
+ const skipped = _num(/(\d+)\s+skipped/i.exec(body));
859
+ return { passed, failed, total: passed + failed + skipped };
860
+ }
861
+ function parsePhpunit(text) {
862
+ if (/\bno tests executed\b/i.test(text)) return { passed: 0, failed: 0, total: 0 };
863
+ const ok = /\bOK\s*\((\d+)\s+tests?/i.exec(text);
864
+ if (ok?.[1] !== void 0) {
865
+ const n = Number(ok[1]);
866
+ return { passed: n, failed: 0, total: n };
867
+ }
868
+ const counted = /^[ \t]*Tests:\s*(\d+),\s*Assertions:\s*\d+(.*)$/m.exec(text);
869
+ if (counted?.[1] === void 0) return null;
870
+ const total = Number(counted[1]);
871
+ const rest = counted[2] ?? "";
872
+ const failed = _num(/Failures:\s*(\d+)/i.exec(rest)) + _num(/Errors:\s*(\d+)/i.exec(rest)) + _num(/Risky:\s*(\d+)/i.exec(rest));
873
+ return { passed: Math.max(total - failed, 0), failed, total };
874
+ }
875
+ function parseTap(text) {
876
+ const plan = /^1\.\.(\d+)\s*$/m.exec(text);
877
+ const okLines = text.match(/^ok\b/gim)?.length ?? 0;
878
+ const notOkLines = text.match(/^not ok\b/gim)?.length ?? 0;
879
+ const trailerFail = /^#\s*fail\s+(\d+)\s*$/im.exec(text);
880
+ if (plan?.[1] === void 0 && okLines === 0 && notOkLines === 0) return null;
881
+ const total = plan?.[1] === void 0 ? okLines + notOkLines : Number(plan[1]);
882
+ const failed = trailerFail?.[1] === void 0 ? notOkLines : Number(trailerFail[1]);
883
+ return { passed: okLines, failed, total };
884
+ }
885
+ function parseSummary(text) {
886
+ return parsePhpunit(text) ?? parseVitestOrJest(text) ?? parsePytest(text) ?? parseTap(text);
887
+ }
888
+ function output(record) {
889
+ return `${record.stdout_tail ?? ""}
890
+ ${record.stderr_tail ?? ""}`;
891
+ }
892
+ function classifyRun(record) {
893
+ if (typeof record !== "object" || record === null || Array.isArray(record)) {
894
+ return { kind: "INVALID_RUN", reason: "unreadable_record" };
895
+ }
896
+ const r = record;
897
+ if (typeof r.command !== "string" || r.command.trim() === "") {
898
+ return { kind: "INVALID_RUN", reason: "unreadable_record" };
899
+ }
900
+ if (!canVerify(r.command)) {
901
+ return { kind: "INVALID_RUN", reason: "not_a_verification_command" };
902
+ }
903
+ if (r.exit_code !== null && r.exit_code !== void 0 && (typeof r.exit_code !== "number" || !Number.isFinite(r.exit_code))) {
904
+ return { kind: "INVALID_RUN", reason: "unreadable_record" };
905
+ }
906
+ const text = output(r);
907
+ const summary = parseSummary(text);
908
+ if (r.interrupted === true || typeof r.exit_code === "number" && KILLED_EXIT_CODES.has(r.exit_code)) {
909
+ if (summary !== null && summary.failed > 0) return { kind: "FAIL_EVIDENCE" };
910
+ return { kind: "INVALID_RUN", reason: "timeout_or_killed" };
911
+ }
912
+ if (summary !== null && summary.failed > 0) return { kind: "FAIL_EVIDENCE" };
913
+ if (r.exit_code === null || r.exit_code === void 0) {
914
+ return { kind: "INVALID_RUN", reason: "exit_code_unavailable" };
915
+ }
916
+ if ((summary === null || summary.passed === 0) && LOAD_FAILURE_PATTERNS.some((re) => re.test(text))) {
917
+ return { kind: "INVALID_RUN", reason: "fixture_or_load_failure" };
918
+ }
919
+ if (summary !== null) {
920
+ if (summary.failed > 0) return { kind: "FAIL_EVIDENCE" };
921
+ if (summary.total === 0 || summary.total === null && summary.passed === 0) {
922
+ return { kind: "INVALID_RUN", reason: "zero_tests_discovered" };
923
+ }
924
+ if (summary.total !== null && summary.total > 0 && summary.passed === 0) {
925
+ return { kind: "INVALID_RUN", reason: "zero_tests_discovered" };
926
+ }
927
+ if (r.exit_code !== 0) {
928
+ return { kind: "INVALID_RUN", reason: "nonzero_exit_without_test_failure" };
929
+ }
930
+ return { kind: "PASS_EVIDENCE_OK" };
931
+ }
932
+ if (r.exit_code !== 0) {
933
+ return { kind: "INVALID_RUN", reason: "nonzero_exit_without_test_failure" };
934
+ }
935
+ return { kind: "PASS_EVIDENCE_OK" };
936
+ }
937
+ function runnerOf(command) {
938
+ for (const [name, re] of [
939
+ ["vitest", /\bvitest\b/i],
940
+ ["jest", /\bjest\b/i],
941
+ ["pytest", /\bpytest\b/i],
942
+ ["phpunit", /\bphpunit\b/i],
943
+ ["pest", /\bpest\b/i],
944
+ ["go", /\bgo\s+test\b/i],
945
+ ["cargo", /\bcargo\s+test\b/i]
946
+ ]) {
947
+ if (re.test(command)) return name;
948
+ }
949
+ return "other";
950
+ }
951
+ function placeRecord(raw) {
952
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return null;
953
+ const v = raw["after_edits"];
954
+ if (typeof v !== "number" || !Number.isFinite(v)) return null;
955
+ return v;
956
+ }
957
+ function readRunEvidence(state) {
958
+ const reasons = [];
959
+ let passed = false;
960
+ let failed = false;
961
+ let gap = false;
962
+ for (const raw of state.runs) {
963
+ const placed = placeRecord(raw);
964
+ if (placed === null) {
965
+ gap = true;
966
+ reasons.push("unreadable_record");
967
+ continue;
968
+ }
969
+ if (placed < state.edits_this_turn) continue;
970
+ const verdict = classifyRun(raw);
971
+ if (verdict.kind === "PASS_EVIDENCE_OK") {
972
+ passed = true;
973
+ continue;
974
+ }
975
+ if (verdict.kind === "FAIL_EVIDENCE") {
976
+ failed = true;
977
+ continue;
978
+ }
979
+ reasons.push(verdict.reason);
980
+ if (isInstrumentGap(verdict)) gap = true;
981
+ }
982
+ return { passed, failed, instrumentGap: gap && !passed && !failed, reasons };
983
+ }
984
+ function testTargetKey(command) {
985
+ const targets = /* @__PURE__ */ new Set();
986
+ for (const token of command.split(/\s+/)) {
987
+ const bare = token.replace(/^['"]|['"]$/g, "");
988
+ if (/[./]\w*(?:test|spec)\w*\.[a-z]+$/i.test(bare) || /(?:Test|Spec)\.php$/.test(bare)) {
989
+ targets.add(bare);
990
+ }
991
+ }
992
+ return [...targets].sort().join("|");
993
+ }
994
+ function hasRedThenGreen(state) {
995
+ let reddenedAnywhere = false;
996
+ const reddened = /* @__PURE__ */ new Set();
997
+ for (let i = 0; i < state.runs.length; i += 1) {
998
+ const raw = state.runs[i];
999
+ const verdict = classifyRun(raw);
1000
+ const key = testTargetKey(
1001
+ typeof raw === "object" && raw !== null && !Array.isArray(raw) ? String(raw["command"] ?? "") : ""
1002
+ );
1003
+ if (RED_VERDICTS(verdict)) {
1004
+ reddened.add(key);
1005
+ reddenedAnywhere = true;
1006
+ continue;
1007
+ }
1008
+ if (verdict.kind !== "PASS_EVIDENCE_OK") continue;
1009
+ const placed = placeRecord(raw);
1010
+ if (placed === null || placed < state.edits_this_turn) continue;
1011
+ if (key === "" ? reddenedAnywhere : reddened.has(key)) return true;
1012
+ }
1013
+ return false;
1014
+ }
1015
+ function RED_VERDICTS(verdict) {
1016
+ if (verdict.kind === "FAIL_EVIDENCE") return true;
1017
+ if (verdict.kind !== "INVALID_RUN") return false;
1018
+ return verdict.reason === "fixture_or_load_failure" || verdict.reason === "nonzero_exit_without_test_failure" || verdict.reason === "zero_tests_discovered";
1019
+ }
1020
+ function describeRecordFinding(failed, reasons) {
1021
+ const tail = " (verify-before-complete: no verification command run in this message \u2192 you cannot claim it passes)";
1022
+ if (failed) {
1023
+ return "this turn changed a file and its verification run FAILED \u2014 a recorded run after the last edit reports failing tests" + tail;
1024
+ }
1025
+ if (reasons.length === 0) {
1026
+ return "this turn changed a file and no verification run was recorded after the last edit" + tail;
1027
+ }
1028
+ return `this turn changed a file and no recorded run after the last edit proves anything \u2014 the runs seen classify as ${[...new Set(reasons)].join(", ")}` + tail;
1029
+ }
1030
+ var NO_RED_EVIDENCE_REASON = "a completion claim over production code this turn changed, with a test file touched but `no_red_evidence` \u2014 no recorded run shows that test target failing and then passing after the last edit. A test never seen red has unknown sensitivity: run it against the unfixed code, watch it fail for the intended reason, then fix and re-run";
1031
+
1032
+ // src/scripts/before_complete_hook.ts
566
1033
  var STATE_FILE = path.join("agents", "state", "verify-before-complete.json");
567
1034
  var STATE_DIR = path.join("agents", "state", "verify-before-complete");
568
1035
  var STATE_RETENTION_DAYS = 7;
@@ -585,10 +1052,6 @@ var COMMAND_TOOLS = /* @__PURE__ */ new Set([
585
1052
  "RunShellCommand"
586
1053
  // Cursor
587
1054
  ]);
588
- var _VERIFICATION_RE = new RegExp(
589
- "(?:^|[\\s;&|`(])(task\\s+(?:ci|test|tests|lint|check|qa|phpstan|rector|ecs|pest|pytest)|(?:\\./|\\.venv/bin/|vendor/bin/)?(?:pest|phpunit|phpstan|psalm|rector|ecs)\\b|(?:python3?|\\.venv/bin/python3?)\\s+-m\\s+pytest|pytest\\b|(?:npm|pnpm|yarn|bun)\\s+(?:run\\s+)?(?:test|check|lint|typecheck|tsc)|cargo\\s+(?:test|check|clippy)|go\\s+test|make\\s+(?:test|check|lint)|composer\\s+(?:test|check|lint|phpstan)|(?:php\\s+)?artisan\\s+test)",
590
- "i"
591
- );
592
1055
  function _now() {
593
1056
  return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "+00:00");
594
1057
  }
@@ -612,6 +1075,11 @@ function _empty_state() {
612
1075
  // has to survive it: the measured failure is a completion claim in a LATER
613
1076
  // turn than the poll it rests on.
614
1077
  ci_last: null,
1078
+ // Round 8 — see `_reset_turn`. Present in the empty state so a freshly
1079
+ // created file has the same shape as one that has seen a turn boundary;
1080
+ // a reader must never have to distinguish "no runs yet" from "key absent".
1081
+ verification_runs: [],
1082
+ edits_this_turn: 0,
615
1083
  checked_at: _now()
616
1084
  };
617
1085
  }
@@ -639,7 +1107,7 @@ function _extract_command(payload) {
639
1107
  return [tool, null];
640
1108
  }
641
1109
  function _is_verification(command) {
642
- return _VERIFICATION_RE.test(command) || isCiPoll(command);
1110
+ return isVerificationCommand(command) || isCiPoll(command);
643
1111
  }
644
1112
  var _VACUOUS_PATTERNS = [
645
1113
  // gh pr checks / gh run — no checks registered at all.
@@ -655,8 +1123,8 @@ var _VACUOUS_PATTERNS = [
655
1123
  /\b0\s+files?\s+(?:checked|scanned|linted)\b/i,
656
1124
  /\bnothing to check\b/i
657
1125
  ];
658
- function isVacuousOutput(output) {
659
- const text = output.trim();
1126
+ function isVacuousOutput(output2) {
1127
+ const text = output2.trim();
660
1128
  if (!text) {
661
1129
  return false;
662
1130
  }
@@ -667,19 +1135,19 @@ var _CI_POLL_RE = /\bgh\s+(?:pr\s+checks|run\s+(?:watch|list|view))\b/i;
667
1135
  function isCiPoll(command) {
668
1136
  return _CI_POLL_RE.test(command);
669
1137
  }
670
- function pendingCount(output) {
671
- const m = /(\d+)\s+(?:checks? )?pending\b/i.exec(output) ?? /\bpending[:=]\s*(\d+)/i.exec(output);
1138
+ function pendingCount(output2) {
1139
+ const m = /(\d+)\s+(?:checks? )?pending\b/i.exec(output2) ?? /\bpending[:=]\s*(\d+)/i.exec(output2);
672
1140
  if (m?.[1] !== void 0) {
673
1141
  return Number(m[1]);
674
1142
  }
675
- const rows = output.match(/^\S.*\b(pending|in_progress|queued)\b/gim);
1143
+ const rows = output2.match(/^\S.*\b(pending|in_progress|queued)\b/gim);
676
1144
  if (rows) {
677
1145
  return rows.length;
678
1146
  }
679
- if (/\bno checks reported\b/i.test(output)) {
1147
+ if (/\bno checks reported\b/i.test(output2)) {
680
1148
  return null;
681
1149
  }
682
- if (/^\S+\s+(pass|fail|skipping|successful|failing)\b/im.test(output)) {
1150
+ if (/^\S+\s+(pass|fail|skipping|successful|failing)\b/im.test(output2)) {
683
1151
  return 0;
684
1152
  }
685
1153
  return null;
@@ -696,11 +1164,101 @@ function _extract_output(payload) {
696
1164
  }
697
1165
  return null;
698
1166
  }
1167
+ var EDIT_TOOLS = /* @__PURE__ */ new Set([
1168
+ "Edit",
1169
+ "Write",
1170
+ "MultiEdit",
1171
+ "NotebookEdit",
1172
+ "str-replace-editor",
1173
+ "save-file",
1174
+ "str_replace_editor",
1175
+ "create_file",
1176
+ "write_to_file",
1177
+ "replace_in_file",
1178
+ "apply_diff",
1179
+ "edit_file"
1180
+ ]);
1181
+ var MAX_VERIFICATION_RUNS_PER_TURN = 24;
1182
+ var RUN_OUTPUT_TAIL_BYTES = 4096;
1183
+ function _cap_runs(runs) {
1184
+ if (runs.length <= MAX_VERIFICATION_RUNS_PER_TURN) return runs;
1185
+ const tail = runs.slice(-(MAX_VERIFICATION_RUNS_PER_TURN - 1));
1186
+ const head = runs.find((r) => {
1187
+ if (r === null || typeof r !== "object" || Array.isArray(r)) return false;
1188
+ const rec = r;
1189
+ const code = rec["exit_code"];
1190
+ return typeof code === "number" && code !== 0;
1191
+ });
1192
+ if (head === void 0 || tail.includes(head)) {
1193
+ return runs.slice(-MAX_VERIFICATION_RUNS_PER_TURN);
1194
+ }
1195
+ return [head, ...tail];
1196
+ }
1197
+ var _ERROR_EXIT_PREFIX = /^Error:\s*Exit code\s*(\d+)/i;
1198
+ var _NO_EXIT = { code: null, source: null, interrupted: false };
1199
+ function _numeric_exit_field(obj) {
1200
+ for (const key of ["exit_code", "exitCode", "returncode", "returnCode", "status_code"]) {
1201
+ const v = obj[key];
1202
+ if (typeof v === "number" && Number.isFinite(v)) return Math.trunc(v);
1203
+ if (typeof v === "string" && v.trim() !== "" && Number.isFinite(Number(v))) {
1204
+ return Math.trunc(Number(v));
1205
+ }
1206
+ }
1207
+ return null;
1208
+ }
1209
+ function _extract_exit_reading(payload) {
1210
+ const direct = _numeric_exit_field(payload);
1211
+ if (direct !== null) return { code: direct, source: "field", interrupted: false };
1212
+ for (const key of ["tool_response", "toolResponse", "result", "output"]) {
1213
+ const v = payload[key];
1214
+ if (typeof v === "string") {
1215
+ const m = _ERROR_EXIT_PREFIX.exec(v);
1216
+ if (m?.[1] !== void 0) {
1217
+ return { code: Number(m[1]), source: "error_prefix", interrupted: false };
1218
+ }
1219
+ continue;
1220
+ }
1221
+ if (v === null || typeof v !== "object" || Array.isArray(v)) continue;
1222
+ const obj = v;
1223
+ const nested = _numeric_exit_field(obj);
1224
+ if (nested !== null) return { code: nested, source: "field", interrupted: false };
1225
+ if (obj["interrupted"] === true) return { code: null, source: null, interrupted: true };
1226
+ const hasStream = typeof obj["stdout"] === "string" || typeof obj["stderr"] === "string";
1227
+ if (obj["interrupted"] === false && hasStream) {
1228
+ return { code: 0, source: "response_shape", interrupted: false };
1229
+ }
1230
+ }
1231
+ return _NO_EXIT;
1232
+ }
1233
+ function _extract_run_streams(payload) {
1234
+ for (const key of ["tool_response", "toolResponse", "result", "output"]) {
1235
+ const v = payload[key];
1236
+ if (typeof v === "string") return { stdout: v, stderr: "" };
1237
+ if (v === null || typeof v !== "object" || Array.isArray(v)) continue;
1238
+ const obj = v;
1239
+ const out = obj["stdout"] ?? obj["output"];
1240
+ const err = obj["stderr"];
1241
+ if (typeof out === "string" || typeof err === "string") {
1242
+ return {
1243
+ stdout: typeof out === "string" ? out : "",
1244
+ stderr: typeof err === "string" ? err : ""
1245
+ };
1246
+ }
1247
+ }
1248
+ const topOut = payload["stdout"] ?? payload["output"];
1249
+ const topErr = payload["stderr"];
1250
+ return {
1251
+ stdout: typeof topOut === "string" ? topOut : "",
1252
+ stderr: typeof topErr === "string" ? topErr : ""
1253
+ };
1254
+ }
699
1255
  function _reset_turn(state, session_id) {
700
1256
  state["session_id"] = session_id || state["session_id"] || "";
701
1257
  state["turn_started_at"] = _now();
702
1258
  state["verifications_this_turn"] = 0;
703
1259
  state["verified_this_turn"] = false;
1260
+ state["verification_runs"] = [];
1261
+ state["edits_this_turn"] = 0;
704
1262
  state["ci_saw_pending"] = false;
705
1263
  state["nonevidence_this_turn"] = 0;
706
1264
  return state;
@@ -732,12 +1290,15 @@ function _update(state, event, envelope) {
732
1290
  state = _reset_turn(state, session_id);
733
1291
  } else if (event === "post_tool_use") {
734
1292
  const [tool, cmd] = _extract_command(pl);
1293
+ if (tool !== null && EDIT_TOOLS.has(tool)) {
1294
+ state["edits_this_turn"] = _asInt(state["edits_this_turn"]) + 1;
1295
+ }
735
1296
  if (cmd && _is_verification(cmd)) {
736
- const output = _extract_output(pl);
737
- const vacuous = output !== null && isVacuousOutput(output);
1297
+ const output2 = _extract_output(pl);
1298
+ const vacuous = output2 !== null && isVacuousOutput(output2);
738
1299
  let counts = !vacuous;
739
1300
  if (isCiPoll(cmd)) {
740
- const pending = output === null ? null : pendingCount(output);
1301
+ const pending = output2 === null ? null : pendingCount(output2);
741
1302
  if (pending !== null && pending > 0) {
742
1303
  state["ci_saw_pending"] = true;
743
1304
  counts = false;
@@ -769,6 +1330,30 @@ function _update(state, event, envelope) {
769
1330
  state["nonevidence_this_turn"] = _asInt(state["nonevidence_this_turn"]) + 1;
770
1331
  }
771
1332
  }
1333
+ if (cmd && mightBeVerification(cmd)) {
1334
+ const streams = _extract_run_streams(pl);
1335
+ const exit = _extract_exit_reading(pl);
1336
+ const runs = Array.isArray(state["verification_runs"]) ? [...state["verification_runs"]] : [];
1337
+ runs.push({
1338
+ command: cmd.slice(0, 512),
1339
+ tool,
1340
+ exit_code: exit.code,
1341
+ // Provenance, not decoration: `response_shape` is a code this recorder
1342
+ // DERIVED from the host's success form, and a reader disputing a verdict
1343
+ // needs to know which of the three readings produced it.
1344
+ exit_source: exit.source,
1345
+ interrupted: exit.interrupted,
1346
+ stdout_tail: streams.stdout.slice(-RUN_OUTPUT_TAIL_BYTES),
1347
+ // Written, and previously not: `RunRecord.stderr_tail` was declared and
1348
+ // read by the classifier's `output()` while nothing populated it, so
1349
+ // stderr reached a parser only by accident inside a stringified blob.
1350
+ stderr_tail: streams.stderr.slice(-RUN_OUTPUT_TAIL_BYTES),
1351
+ runner: runnerOf(cmd),
1352
+ after_edits: _asInt(state["edits_this_turn"]),
1353
+ at: _now()
1354
+ });
1355
+ state["verification_runs"] = _cap_runs(runs);
1356
+ }
772
1357
  } else if (event === "stop") {
773
1358
  state["last_stop_at"] = _now();
774
1359
  }
@@ -859,6 +1444,23 @@ function main(argv) {
859
1444
  const args = parse_args(argv ?? process.argv.slice(2));
860
1445
  return run(_readStdin(), { consumer_root: process.cwd(), verbose: args.verbose });
861
1446
  }
1447
+ function readTurnRunState(workspaceRoot, session_id) {
1448
+ if (!has_stable_session_id(session_id)) return null;
1449
+ try {
1450
+ const raw = fs.readFileSync(path.join(workspaceRoot, statePathFor(session_id)), "utf-8");
1451
+ const decoded = JSON.parse(raw);
1452
+ if (!owns_session_state(decoded, session_id)) return null;
1453
+ if (typeof decoded !== "object" || decoded === null || Array.isArray(decoded)) return null;
1454
+ const state = decoded;
1455
+ const runs = state["verification_runs"];
1456
+ const edits = state["edits_this_turn"];
1457
+ if (!Array.isArray(runs)) return null;
1458
+ if (typeof edits !== "number" || !Number.isFinite(edits) || edits < 1) return null;
1459
+ return { runs, edits_this_turn: edits };
1460
+ } catch {
1461
+ return null;
1462
+ }
1463
+ }
862
1464
  function _isCliEntry() {
863
1465
  if (true) {
864
1466
  return false;
@@ -1009,6 +1611,79 @@ function readInstallBoundary() {
1009
1611
  return { version: null, installed_at: null };
1010
1612
  }
1011
1613
  }
1614
+ var SHADOW_LAYERS = ["stop_hook_active", "refused_turn"];
1615
+ function emptyRetryCounts() {
1616
+ const out = {};
1617
+ for (const l of SHADOW_LAYERS) out[l] = 0;
1618
+ return out;
1619
+ }
1620
+ var SHADOW_MAX_ROWS = 200;
1621
+ var SHADOW_SUFFIX = ".shadow.json";
1622
+ function sessionShadowFile(workspaceRoot, sessionKey) {
1623
+ return path2.join(refusalStateDir(workspaceRoot), `${sessionKey}${SHADOW_SUFFIX}`);
1624
+ }
1625
+ function parseShadowRecord(raw) {
1626
+ let decoded;
1627
+ try {
1628
+ decoded = JSON.parse(raw);
1629
+ } catch {
1630
+ return null;
1631
+ }
1632
+ if (typeof decoded !== "object" || decoded === null || Array.isArray(decoded)) return null;
1633
+ const o = decoded;
1634
+ const rowsRaw = o["would_refuse_again"];
1635
+ if (!Array.isArray(rowsRaw)) return null;
1636
+ const rows = [];
1637
+ for (const r of rowsRaw) {
1638
+ if (typeof r !== "object" || r === null || Array.isArray(r)) continue;
1639
+ const ro = r;
1640
+ const det = ro["detector"];
1641
+ const turn = ro["turn"];
1642
+ const at = ro["at"];
1643
+ const layer = ro["layer"];
1644
+ if (!isDetector(det)) continue;
1645
+ if (typeof turn !== "number" || !Number.isFinite(turn)) continue;
1646
+ if (typeof at !== "string") continue;
1647
+ if (layer !== "stop_hook_active" && layer !== "refused_turn") continue;
1648
+ rows.push({ detector: det, turn, at, layer });
1649
+ }
1650
+ const num = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : 0;
1651
+ const observed = o["retries_observed"];
1652
+ const byLayer = emptyRetryCounts();
1653
+ if (typeof observed === "object" && observed !== null && !Array.isArray(observed)) {
1654
+ for (const l of SHADOW_LAYERS) {
1655
+ byLayer[l] = num(observed[l]);
1656
+ }
1657
+ }
1658
+ return {
1659
+ would_refuse_again: rows,
1660
+ retries_observed: byLayer,
1661
+ dropped: num(o["dropped"]),
1662
+ first_at: typeof o["first_at"] === "string" ? o["first_at"] : "",
1663
+ last_at: typeof o["last_at"] === "string" ? o["last_at"] : ""
1664
+ };
1665
+ }
1666
+ function foldShadow(prev, input) {
1667
+ const rows = [...prev?.would_refuse_again ?? []];
1668
+ for (const d of input.detectors) {
1669
+ rows.push({ detector: d, turn: input.turnOrdinal, at: input.at, layer: input.layer });
1670
+ }
1671
+ let dropped = prev?.dropped ?? 0;
1672
+ if (rows.length > SHADOW_MAX_ROWS) {
1673
+ dropped += rows.length - SHADOW_MAX_ROWS;
1674
+ rows.splice(0, rows.length - SHADOW_MAX_ROWS);
1675
+ }
1676
+ const retries = emptyRetryCounts();
1677
+ for (const l of SHADOW_LAYERS) retries[l] = prev?.retries_observed[l] ?? 0;
1678
+ retries[input.layer] += 1;
1679
+ return {
1680
+ would_refuse_again: rows,
1681
+ retries_observed: retries,
1682
+ dropped,
1683
+ first_at: prev?.first_at !== void 0 && prev.first_at !== "" ? prev.first_at : input.at,
1684
+ last_at: input.at
1685
+ };
1686
+ }
1012
1687
  var REFUSAL_STATE_MAX_AGE_DAYS = 90;
1013
1688
  function pruneAgedRefusalState(workspaceRoot, opts = {}) {
1014
1689
  const maxAgeDays = opts.maxAgeDays ?? REFUSAL_STATE_MAX_AGE_DAYS;
@@ -1026,6 +1701,37 @@ function pruneAgedRefusalState(workspaceRoot, opts = {}) {
1026
1701
  if (!name.endsWith(".json")) continue;
1027
1702
  result.scanned += 1;
1028
1703
  const file = path2.join(dir, name);
1704
+ if (name.endsWith(SHADOW_SUFFIX)) {
1705
+ let shadow = null;
1706
+ try {
1707
+ shadow = parseShadowRecord(fs2.readFileSync(file, "utf-8"));
1708
+ } catch {
1709
+ shadow = null;
1710
+ }
1711
+ const shadowAt = shadow === null ? NaN : Date.parse(shadow.last_at);
1712
+ if (!Number.isFinite(shadowAt)) {
1713
+ let mtime = NaN;
1714
+ try {
1715
+ mtime = fs2.statSync(file).mtimeMs;
1716
+ } catch {
1717
+ mtime = NaN;
1718
+ }
1719
+ if (!Number.isFinite(mtime) || mtime >= cutoffMs) {
1720
+ result.kept += 1;
1721
+ continue;
1722
+ }
1723
+ } else if (shadowAt >= cutoffMs) {
1724
+ result.kept += 1;
1725
+ continue;
1726
+ }
1727
+ try {
1728
+ fs2.unlinkSync(file);
1729
+ result.pruned += 1;
1730
+ } catch {
1731
+ result.kept += 1;
1732
+ }
1733
+ continue;
1734
+ }
1029
1735
  let rec = null;
1030
1736
  try {
1031
1737
  rec = parseRecord(fs2.readFileSync(file, "utf-8"));
@@ -1052,10 +1758,16 @@ function pruneAgedRefusalState(workspaceRoot, opts = {}) {
1052
1758
  }
1053
1759
 
1054
1760
  export {
1761
+ isVerificationCommand,
1762
+ readRunEvidence,
1763
+ hasRedThenGreen,
1764
+ describeRecordFinding,
1765
+ NO_RED_EVIDENCE_REASON,
1055
1766
  statePathFor,
1056
1767
  isVacuousOutput,
1057
1768
  isCiPoll,
1058
1769
  pendingCount,
1770
+ readTurnRunState,
1059
1771
  CAPSULE_SCHEMA_VERSION,
1060
1772
  scanEnvelopeDirectives,
1061
1773
  wrapAsPriorSessionData,
@@ -1068,5 +1780,8 @@ export {
1068
1780
  parseRecord,
1069
1781
  foldRefusal,
1070
1782
  readInstallBoundary,
1783
+ sessionShadowFile,
1784
+ parseShadowRecord,
1785
+ foldShadow,
1071
1786
  pruneAgedRefusalState
1072
1787
  };