@tea-agent/loop-agent 0.39.0-beta.2 → 0.39.0-beta.3

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 (202) hide show
  1. package/AGENTS.md +4 -1
  2. package/CHANGELOG.md +187 -92
  3. package/README.md +9 -5
  4. package/bin/loop-agent.js +7 -3
  5. package/dist/application/task-lifecycle/recommendations.js +13 -2
  6. package/dist/build-stamp.json +3 -3
  7. package/dist/cli/command-definitions.js +16 -5
  8. package/dist/cli/program.js +16 -2
  9. package/dist/cli/update/notifier.js +2 -2
  10. package/dist/cli.js +2 -1
  11. package/dist/commands/dag-rerun.js +55 -1
  12. package/dist/commands/init-model-catalog.js +464 -0
  13. package/dist/commands/init-upgrade.js +265 -97
  14. package/dist/commands/init.js +429 -28
  15. package/dist/commands/inspect-next.js +8 -0
  16. package/dist/executors/dag-pi-executor.js +1645 -29
  17. package/dist/executors/pi-extension-resolver.js +233 -0
  18. package/dist/executors/pi-playwright-cli-tool.js +51 -12
  19. package/dist/executors/pi-sdk-executor.js +180 -33
  20. package/dist/executors/shell-executor.js +1150 -171
  21. package/dist/executors/shell-write-guard.js +14 -0
  22. package/dist/shared/dag-prompt-override.js +28 -0
  23. package/dist/shared/openspec-spec.js +70 -4
  24. package/dist/shared/operator/capabilities.js +32 -0
  25. package/dist/shared/playwright-cli-command-policy.js +15 -0
  26. package/dist/shared/update/console-notifier.js +100 -0
  27. package/dist/{cli → shared}/update/npm-client.js +41 -8
  28. package/dist/{cli → shared}/update/state.js +41 -13
  29. package/dist/task/config-types.js +19 -5
  30. package/dist/task/contract/project.js +3 -0
  31. package/dist/task/contract/schema.js +2 -1
  32. package/dist/task/frontend-project-capability.js +42 -16
  33. package/dist/task/source-prepare/parse-intent.js +24 -4
  34. package/dist/task/source-prepare/prepare.js +36 -0
  35. package/dist/worker/cli.js +31 -1
  36. package/dist/worker/console/chat/pi-console-config.js +81 -10
  37. package/dist/worker/console/chat/routes.js +1 -0
  38. package/dist/worker/console/dag-execution-receipt.js +33 -0
  39. package/dist/worker/console/frontend-human-decision-adapter.js +19 -0
  40. package/dist/worker/console/frontend-split-operation-adapter.js +20 -0
  41. package/dist/worker/console/index.js +3 -0
  42. package/dist/worker/console/operation-store.js +226 -6
  43. package/dist/worker/console/operator-actions.js +263 -0
  44. package/dist/worker/console/operator-user-error.js +2 -2
  45. package/dist/worker/console/routes.js +43 -2
  46. package/dist/worker/console/security.js +63 -9
  47. package/dist/worker/console/server.js +151 -6
  48. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C4kVYweA.js → abnfDiagram-N423BO3Z-zsNyvGaH.js} +1 -1
  49. package/dist/worker/console/static/assets/{arc-Bj2M8iO5.js → arc-BDjZ5kE1.js} +1 -1
  50. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CLlXe4-6.js → architectureDiagram-T3A2C74G-BkgaujnN.js} +1 -1
  51. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-psEp5xH0.js → blockDiagram-VBNYF7ZC-CqxOJi0j.js} +1 -1
  52. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DeKcOCGJ.js → c4Diagram-5PPSVZJV-C2JHLdRi.js} +1 -1
  53. package/dist/worker/console/static/assets/channel-BuqaGKAT.js +1 -0
  54. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-K0C744p1.js → chunk-2GRJ4B5K-DF_YSvOS.js} +1 -1
  55. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-xbNw4J9-.js → chunk-2Q5K7J3B-D3Ort5Vo.js} +1 -1
  56. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DjLaSDUO.js → chunk-5RXB4S5H-XfAfZeUC.js} +1 -1
  57. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CtiPlKel.js → chunk-5VM5RSS4-SLBibhf5.js} +1 -1
  58. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DZtolir7.js → chunk-6Q2QTUOP-Dz29Ye_f.js} +1 -1
  59. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CPcdNeew.js → chunk-GF5L2VYU-DB2G4H8V.js} +1 -1
  60. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-NgHc6V37.js → chunk-JWPE2WC7-CIoGkxdu.js} +1 -1
  61. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DQ_T-hg8.js → chunk-KBJHAD2P-CG78FGkn.js} +1 -1
  62. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DgLsxcVP.js → chunk-RYQCIY6F-ZDHAvXPd.js} +1 -1
  63. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-UrVoM9z1.js → chunk-XXDRQBXY-BKkkC4VA.js} +1 -1
  64. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DNdSHjH9.js +1 -0
  65. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DNdSHjH9.js +1 -0
  66. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CLkYvTb3.js → cose-bilkent-JH36ORCC-SposUZsO.js} +1 -1
  67. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BwT_xKrE.js → cynefin-VYW2F7L2-BSCtQpD7.js} +1 -1
  68. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Bi9tuzif.js → cynefinDiagram-MW4NZA55-DgFHrw9l.js} +1 -1
  69. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BCIqkWBV.js → dagre-VZM6K2ZE-BVBasYbj.js} +1 -1
  70. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Dip6l9_Z.js → diagram-7IWD3JNH-6r_4rN_c.js} +1 -1
  71. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B-xkh_wK.js → diagram-B4RE2ZJO-JmuaB41P.js} +1 -1
  72. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DZO0kTt_.js → diagram-LBJQPF4R-xXvAY3sk.js} +1 -1
  73. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-D6ZplNbZ.js → diagram-Q27KOJAE-D2B3lY2o.js} +1 -1
  74. package/dist/worker/console/static/assets/{diagram-UB23O5K3-BPekbhoS.js → diagram-UB23O5K3-tK54tMEX.js} +1 -1
  75. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BtmaJpK5.js → ebnfDiagram-BXEA7PRR-CNtELzV-.js} +1 -1
  76. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-6DS0Xc44.js → erDiagram-JOGREHBK-C1xKMKcW.js} +1 -1
  77. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CihnROSm.js → flowDiagram-UKHOOZJN-CZ7dlPMz.js} +1 -1
  78. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CZC4zOE2.js → ganttDiagram-PKOTCBZU-BqGb42Xe.js} +1 -1
  79. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Dq1fhzGN.js → gitGraphDiagram-DS77QQ5N-D_BlXEtt.js} +1 -1
  80. package/dist/worker/console/static/assets/{index-DTZOKgAn.js → index-D5zGX6fG.js} +68 -68
  81. package/dist/worker/console/static/assets/index-DBbhESQ_.css +1 -0
  82. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DlSl4BGx.js → infoDiagram-6WML65LV-Vce2vmIe.js} +1 -1
  83. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-snnzFl_U.js → ishikawaDiagram-WSZJBQD7-DDwVBAvI.js} +1 -1
  84. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-BDE7qpMx.js → journeyDiagram-NVQOT4AX-y_Ew-wv4.js} +1 -1
  85. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-CD2Ci04G.js → kanban-definition-27J2QSJJ-CjXJFp1K.js} +1 -1
  86. package/dist/worker/console/static/assets/{linear-DdnapKIH.js → linear-1itMst1Q.js} +1 -1
  87. package/dist/worker/console/static/assets/{mermaid.core-BVjAT9b8.js → mermaid.core-C-D3ZRUa.js} +5 -5
  88. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-D0yJk-zA.js → mindmap-definition-FAOFIHXS-BiYywmZc.js} +1 -1
  89. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BVo9tFP-.js → pegDiagram-VL7TDLO6-l2fydNWA.js} +1 -1
  90. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DnOV-mZ_.js → pieDiagram-7S7Q4E2Y-Dz0hNbYW.js} +1 -1
  91. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CzUJo58i.js → quadrantDiagram-CIZ2JOQS-BujI_mLZ.js} +1 -1
  92. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Bvikrolh.js → railroadDiagram-AXF67PYL-pWe84beb.js} +1 -1
  93. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-DtaSVCap.js → requirementDiagram-LRYGKXZP-ByYu2frg.js} +1 -1
  94. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-C2c9A0wW.js → sankeyDiagram-W5VNT64P-CoPnilFP.js} +1 -1
  95. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DmXJcU7r.js → sequenceDiagram-SI44F4Z6-1Md2LnS0.js} +1 -1
  96. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-B4GUFW92.js → sizeCapture-X5ZJPWSS-DqqqAoSH.js} +1 -1
  97. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Dsad2MXf.js → stateDiagram-OKZ733FA-C4sGkf--.js} +1 -1
  98. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B8rmop_9.js +1 -0
  99. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DGq48Fbi.js → swimlanes-SLNWSIFB-ByxJrUzW.js} +2 -2
  100. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-B0ky-kBM.js +8 -0
  101. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CSSi6OFf.js → timeline-definition-Z64GVDOM-D8MLgM0O.js} +1 -1
  102. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BqyzPrWv.js → vennDiagram-T6HMQDX7-DnBis-wZ.js} +1 -1
  103. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DlznVb6S.js → wardleyDiagram-T6FBY63Y-uuyYT7UO.js} +1 -1
  104. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CfBcgJ_K.js → xychartDiagram-ELKLHX3M-BjgabhwK.js} +1 -1
  105. package/dist/worker/console/static/index.html +2 -2
  106. package/dist/worker/console/static-src/active-run-badge.js +17 -0
  107. package/dist/worker/console/static-src/operator-chat/mutation-gate.js +1 -1
  108. package/dist/worker/loop-agent/loop-agent-client.js +7 -3
  109. package/dist/worker/materialize/frontend-split-task-materializer.js +72 -0
  110. package/dist/worker/observe/node-input.js +74 -3
  111. package/dist/worker/observe/node-process.js +377 -0
  112. package/dist/worker/observe/routes.js +36 -3
  113. package/dist/worker/observe/shell-handler-keys.js +34 -0
  114. package/dist/worker/observe/static/api.js +66 -0
  115. package/dist/worker/observe/static/constants.js +8 -1
  116. package/dist/worker/observe/static/dag-history-labels.js +4 -0
  117. package/dist/worker/observe/static/format.js +23 -0
  118. package/dist/worker/observe/static/markdown-render.js +20 -1
  119. package/dist/worker/observe/static/operator-chrome.js +4 -0
  120. package/dist/worker/observe/static/state.js +13 -2
  121. package/dist/worker/observe/static/styles.css +73 -0
  122. package/dist/worker/observe/static/views/dag-inspector.js +612 -8
  123. package/dist/worker/observe/static/views/dag.js +8 -1
  124. package/dist/worker/observe/static/views/failures.js +5 -2
  125. package/dist/worker/observe/static/views/session-timeline.js +78 -9
  126. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +20 -2
  127. package/dist/workflows/dag/contract-output-registry.js +15 -0
  128. package/dist/workflows/dag/contract-validator-registrations.js +1 -2
  129. package/dist/workflows/dag/failure-category.js +12 -0
  130. package/dist/workflows/dag/frontend-closeout.js +221 -0
  131. package/dist/workflows/dag/frontend-design-policy.js +347 -0
  132. package/dist/workflows/dag/frontend-human-decision.js +182 -0
  133. package/dist/workflows/dag/frontend-implementation-contract.js +378 -160
  134. package/dist/workflows/dag/frontend-prewrite-gate.js +258 -393
  135. package/dist/workflows/dag/frontend-provider-capability-matrix.js +159 -0
  136. package/dist/workflows/dag/frontend-recovery-capsule.js +455 -0
  137. package/dist/workflows/dag/frontend-recovery-controller.js +201 -0
  138. package/dist/workflows/dag/frontend-recovery-lineage.js +178 -0
  139. package/dist/workflows/dag/frontend-recovery-plan.js +17 -10
  140. package/dist/workflows/dag/frontend-recovery-run.js +33 -20
  141. package/dist/workflows/dag/frontend-repair.js +7 -424
  142. package/dist/workflows/dag/frontend-review-context.js +195 -8
  143. package/dist/workflows/dag/frontend-risk.js +15 -2
  144. package/dist/workflows/dag/frontend-shadow-dual-write.js +665 -0
  145. package/dist/workflows/dag/frontend-shape-capsule-store.js +191 -0
  146. package/dist/workflows/dag/frontend-shape-facts.js +409 -0
  147. package/dist/workflows/dag/frontend-shape.js +425 -0
  148. package/dist/workflows/dag/frontend-split-application-service.js +203 -0
  149. package/dist/workflows/dag/frontend-split-orchestrator.js +899 -0
  150. package/dist/workflows/dag/frontend-test-case-checklist.js +26 -2
  151. package/dist/workflows/dag/frontend-test-case-manifest.js +5 -0
  152. package/dist/workflows/dag/frontend-test-case-quality.js +5 -13
  153. package/dist/workflows/dag/frontend-test-environment-probe.js +227 -0
  154. package/dist/workflows/dag/frontend-test-markdown.js +61 -0
  155. package/dist/workflows/dag/frontend-test-result-contract.js +160 -28
  156. package/dist/workflows/dag/frontend-test-standard-scenarios.js +68 -0
  157. package/dist/workflows/dag/frontend-typed-event-store.js +438 -0
  158. package/dist/workflows/dag/frontend-typed-event-transaction.js +180 -0
  159. package/dist/workflows/dag/frontend-verification-trace.js +52 -17
  160. package/dist/workflows/dag/frontend-worktree-diff.js +97 -18
  161. package/dist/workflows/dag/frontend-writer-admission.js +260 -0
  162. package/dist/workflows/dag/frontend-writer-status.js +244 -0
  163. package/dist/workflows/dag/init-hybrid.js +572 -500
  164. package/dist/workflows/dag/node-execution.js +307 -70
  165. package/dist/workflows/dag/prompt.js +10 -18
  166. package/dist/workflows/dag/rerun-plan.js +144 -5
  167. package/dist/workflows/dag/rerun-run.js +58 -6
  168. package/dist/workflows/dag/retry-policy.js +228 -4
  169. package/dist/workflows/dag/runner.js +314 -119
  170. package/dist/workflows/dag/scheduler.js +114 -16
  171. package/dist/workflows/dag/structured-output-repair.js +712 -0
  172. package/dist/workflows/dag/types.js +235 -23
  173. package/dist/workflows/dag/upstream-artifacts.js +0 -4
  174. package/dist/workflows/dag/validate.js +60 -10
  175. package/docs/init-surface.manifest.json +5 -0
  176. package/docs/operations/README.md +1 -1
  177. package/docs/templates/agent-dag.schema.json +44 -0
  178. package/docs/templates/frontend-test-dag.json +8 -10
  179. package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +1 -1
  180. package/docs/templates/init-managed-agents.md +13 -3
  181. package/harness.json +3 -3
  182. package/package.json +5 -1
  183. package/scripts/next-info.mjs +356 -0
  184. package/scripts/next-publish-gate.mjs +238 -0
  185. package/scripts/release-source-binding.mjs +251 -0
  186. package/skills/codebase-scout/SKILL.md +1 -1
  187. package/skills/fe-test-ui-scout/SKILL.md +65 -0
  188. package/skills/fe-test-ui-scout/references/ledger-schema.md +62 -0
  189. package/skills/fe-test-ui-scout/references/recon-protocol.md +54 -0
  190. package/skills/frontend-implementation/SKILL.md +4 -4
  191. package/skills/frontend-implementation/references/design-spec.md +11 -7
  192. package/skills/frontend-implementation/references/node-contracts.md +20 -11
  193. package/skills/loop-agent/references/command-reference.md +11 -4
  194. package/skills/loop-agent/references/hybrid-dag.md +1 -1
  195. package/skills/playwright-cli/SKILL.md +22 -0
  196. package/skills/playwright-cli-case-generator/SKILL.md +20 -1
  197. package/dist/worker/console/static/assets/channel-CPF4N7pf.js +0 -1
  198. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C2DwA_Y1.js +0 -1
  199. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C2DwA_Y1.js +0 -1
  200. package/dist/worker/console/static/assets/index-Ya5FE7cD.css +0 -1
  201. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B8FB_cNk.js +0 -1
  202. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CrZYaWfx.js +0 -8
@@ -0,0 +1,244 @@
1
+ import { createHash } from "node:crypto";
2
+ import { readFile } from "node:fs/promises";
3
+ /**
4
+ * M4: fact-derived frontend writer status + bounded in-node micro-loop.
5
+ *
6
+ * `frontend-implement-pi` derives its implementation status from mechanical
7
+ * facts (write-tool events, run delta, cumulative diff, write guard, requirement
8
+ * coverage, focused-check failures) instead of parsing the
9
+ * `IMPLEMENTATION_OUTCOME:` first line. The legacy `implementation-outcome-v1`
10
+ * protocol (backend/README/module writers + `frontend-repair-pi`) is untouched.
11
+ *
12
+ * Everything here is either a pure function (deterministically testable) or an
13
+ * IO collector that is only reached through a dynamic import from the Pi
14
+ * executor, avoiding an executor↔workflows static cycle.
15
+ */
16
+ /**
17
+ * Write/edit-style tool names emitted by DAG writer customTools. Kept in sync
18
+ * with the `SDK_WRITE_TOOL_NAMES` set in pi-sdk-executor.ts so the persisted
19
+ * session-event count and the in-memory writeToolCallCount agree.
20
+ */
21
+ export const FRONTEND_WRITE_TOOL_NAMES = new Set([
22
+ "edit",
23
+ "write",
24
+ "str_replace_editor",
25
+ ]);
26
+ /**
27
+ * AC-001/AC-003: derive the writer status from mechanical facts only. The
28
+ * first-line text is never consulted. Ordering:
29
+ * 1. write-guard violation → blocked
30
+ * 2. non-empty diff → changed (diff is the mechanical fact source)
31
+ * 3. empty diff + runtime already-satisfied evidence → already-satisfied
32
+ * 4. empty diff, zero writes, zero diff → blocked (no progress)
33
+ * 5. otherwise → empty-diff (fail-closed)
34
+ */
35
+ export function deriveFrontendWriterStatus(facts) {
36
+ if (!facts.writeGuardOk) {
37
+ return {
38
+ status: "blocked",
39
+ reason: `write guard violations: ${facts.writeGuardViolations.join(", ") || "unknown"}`,
40
+ };
41
+ }
42
+ if (facts.changedFiles.length > 0) {
43
+ return {
44
+ status: "changed",
45
+ reason: `non-empty run-attributed diff (${facts.changedFiles.length} file${facts.changedFiles.length === 1 ? "" : "s"})`,
46
+ };
47
+ }
48
+ if (facts.alreadySatisfiedEvidence) {
49
+ return {
50
+ status: "already-satisfied",
51
+ reason: `empty diff with runtime already-satisfied evidence (${facts.alreadySatisfiedEvidence.requirementTargets.length} target${facts.alreadySatisfiedEvidence.requirementTargets.length === 1 ? "" : "s"})`,
52
+ };
53
+ }
54
+ if (facts.writeToolEvents === 0 && facts.changedFiles.length === 0) {
55
+ return {
56
+ status: "blocked",
57
+ reason: "zero write-tool events and zero diff: no implementation progress",
58
+ };
59
+ }
60
+ return {
61
+ status: "empty-diff",
62
+ reason: "empty diff without runtime already-satisfied evidence (fail-closed)",
63
+ };
64
+ }
65
+ /**
66
+ * Deterministic failure fingerprint over the normalized failure signature:
67
+ * category + focused-check failures (target + detail) + guard status. Used for
68
+ * repeated-failure deduplication inside the bounded micro-loop.
69
+ */
70
+ export function computeFailureFingerprint(facts) {
71
+ const signature = normalizeFailureSignature(facts);
72
+ return createHash("sha256").update(signature).digest("hex");
73
+ }
74
+ function normalizeFailureSignature(facts) {
75
+ const parts = [];
76
+ if (!facts.writeGuardOk) {
77
+ parts.push("write-guard");
78
+ }
79
+ for (const failure of facts.focusedCheckFailures) {
80
+ parts.push(`${failure.category}:${failure.targetId ?? ""}:${failure.detail}`);
81
+ }
82
+ if (facts.changedFiles.length === 0 && facts.writeToolEvents === 0) {
83
+ parts.push("no-progress");
84
+ }
85
+ return parts.sort().join("\n");
86
+ }
87
+ export const DEFAULT_FRONTEND_MICRO_LOOP_BUDGET = {
88
+ maxFocusedCheckRounds: 3,
89
+ maxWriteAttempts: 5,
90
+ maxWallTimeMs: 15 * 60 * 1000,
91
+ maxTokens: 200_000,
92
+ maxRepeatedFingerprints: 2,
93
+ };
94
+ /**
95
+ * Pure micro-loop continuation gate. Deterministic: same budget + history +
96
+ * timestamps always yield the same decision. The caller (executor) drives the
97
+ * loop by re-checking this function after each focused-check round.
98
+ */
99
+ export function evaluateMicroLoopContinuation(budget, history, startWallTimeMs, nowWallTimeMs) {
100
+ const rounds = history.length;
101
+ const last = history[rounds - 1];
102
+ if (last) {
103
+ const resolved = (last.status === "changed" &&
104
+ last.writeGuardOk &&
105
+ last.focusedCheckFailures.length === 0) ||
106
+ last.status === "already-satisfied" ||
107
+ last.status === "blocked";
108
+ if (resolved) {
109
+ return { continue: false, reason: "status-resolved" };
110
+ }
111
+ if (last.writeAttempts >= budget.maxWriteAttempts) {
112
+ return { continue: false, reason: "budget-write-attempts" };
113
+ }
114
+ if (last.tokensUsed >= budget.maxTokens) {
115
+ return { continue: false, reason: "budget-tokens" };
116
+ }
117
+ if (nowWallTimeMs - startWallTimeMs >= budget.maxWallTimeMs) {
118
+ return { continue: false, reason: "budget-wall-time" };
119
+ }
120
+ const repeatCount = history.filter((round) => round.fingerprint === last.fingerprint).length;
121
+ if (repeatCount >= budget.maxRepeatedFingerprints) {
122
+ return { continue: false, reason: "budget-repeated-fingerprint" };
123
+ }
124
+ }
125
+ if (rounds >= budget.maxFocusedCheckRounds) {
126
+ return { continue: false, reason: "budget-focused-check-rounds" };
127
+ }
128
+ return { continue: true };
129
+ }
130
+ /**
131
+ * Build a deterministic per-round checkpoint payload. Persistence stays with the
132
+ * caller (executor writes `runDir/<nodeId>/` only, never root `artifacts/**`).
133
+ */
134
+ export function buildFrontendMicroLoopCheckpoint(input) {
135
+ return {
136
+ schemaVersion: 1,
137
+ nodeId: input.nodeId,
138
+ round: input.round,
139
+ status: deriveFrontendWriterStatus(input.facts).status,
140
+ fingerprint: computeFailureFingerprint(input.facts),
141
+ changedFiles: [...input.facts.changedFiles].sort(),
142
+ focusedCheckFailures: input.facts.focusedCheckFailures.map((failure) => ({
143
+ ...failure,
144
+ })),
145
+ writeGuardOk: input.facts.writeGuardOk,
146
+ recordedAt: input.recordedAt,
147
+ wallTimeMs: input.facts.wallTimeMs,
148
+ tokensUsed: input.facts.tokensUsed,
149
+ writeAttempts: input.facts.writeAttempts,
150
+ };
151
+ }
152
+ /**
153
+ * Shadow comparison between the fact-derived status and the legacy
154
+ * `IMPLEMENTATION_OUTCOME:` first-line parse. Never used to decide ok/fail —
155
+ * only persisted for the M4 shadow evaluation.
156
+ */
157
+ export function compareFactStatusToLegacyOutcome(factStatus, legacyOutcome) {
158
+ const factVerdict = factStatus === "changed" || factStatus === "already-satisfied"
159
+ ? "ok"
160
+ : "fail";
161
+ const legacyVerdict = legacyOutcome === "changed" || legacyOutcome === "already-satisfied"
162
+ ? "ok"
163
+ : "fail";
164
+ return {
165
+ factStatus,
166
+ legacyOutcome,
167
+ factVerdict,
168
+ legacyVerdict,
169
+ match: factVerdict === legacyVerdict,
170
+ };
171
+ }
172
+ /**
173
+ * Pure session-event scanner: count write-tool events from the persisted
174
+ * session-events.jsonl content. Fail-closed: an unparseable line never counts
175
+ * as a write, so a corrupt/empty log cannot inflate progress.
176
+ */
177
+ export function countWriteToolEventsFromSessionEvents(content) {
178
+ let count = 0;
179
+ for (const line of content.split("\n")) {
180
+ const trimmed = line.trim();
181
+ if (!trimmed)
182
+ continue;
183
+ let event;
184
+ try {
185
+ event = JSON.parse(trimmed);
186
+ }
187
+ catch {
188
+ continue;
189
+ }
190
+ if (event !== null &&
191
+ typeof event === "object" &&
192
+ event.type === "tool_execution_start" &&
193
+ typeof event.toolName === "string" &&
194
+ FRONTEND_WRITE_TOOL_NAMES.has(event.toolName)) {
195
+ count += 1;
196
+ }
197
+ }
198
+ return count;
199
+ }
200
+ /**
201
+ * IO collector for frontend facts. Reads only the persisted session-events
202
+ * file; every other fact is passed in by the executor so this stays narrow and
203
+ * deterministic under test (tests inject session content by writing to a temp
204
+ * path or call the pure scanner directly).
205
+ */
206
+ export async function collectFrontendWriterFacts(input) {
207
+ let writeToolEvents = 0;
208
+ try {
209
+ const content = await readFile(input.sessionEventsPath, "utf8");
210
+ writeToolEvents = countWriteToolEventsFromSessionEvents(content);
211
+ }
212
+ catch {
213
+ // Missing/unreadable session log → fail-closed at zero, never fail-open.
214
+ writeToolEvents = 0;
215
+ }
216
+ return {
217
+ changedFiles: [...input.changedFiles].sort(),
218
+ writeToolEvents,
219
+ writeGuardOk: input.writeGuard.ok,
220
+ writeGuardViolations: [...input.writeGuard.violations],
221
+ requirementTargets: [...new Set(input.requirementTargets)].sort(),
222
+ coveredRequirementIds: [...new Set(input.coveredRequirementIds)].sort(),
223
+ focusedCheckFailures: input.focusedCheckFailures.map((failure) => ({
224
+ ...failure,
225
+ })),
226
+ ...(input.alreadySatisfiedEvidence
227
+ ? {
228
+ alreadySatisfiedEvidence: {
229
+ requirementTargets: [
230
+ ...input.alreadySatisfiedEvidence.requirementTargets,
231
+ ].sort(),
232
+ proofSha256: input.alreadySatisfiedEvidence.proofSha256,
233
+ },
234
+ }
235
+ : {}),
236
+ tokensUsed: input.tokensUsed ?? 0,
237
+ wallTimeMs: input.wallTimeMs ?? 0,
238
+ rounds: input.rounds ?? 1,
239
+ writeAttempts: input.writeAttempts ?? 1,
240
+ ...(input.firstLineText !== undefined
241
+ ? { firstLineText: input.firstLineText }
242
+ : {}),
243
+ };
244
+ }