@tea-agent/loop-agent 0.41.1-beta.0 → 0.41.2

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 (209) hide show
  1. package/CHANGELOG.md +61 -267
  2. package/dist/application/dag/generate-task-dag.js +19 -75
  3. package/dist/application/task-lifecycle/advance.js +5 -24
  4. package/dist/application/task-lifecycle/observe.js +17 -171
  5. package/dist/application/task-lifecycle/plan-transitions.js +7 -42
  6. package/dist/application/task-lifecycle/recommendations.js +2 -13
  7. package/dist/build-stamp.json +3 -3
  8. package/dist/cli/command-definitions.js +0 -7
  9. package/dist/cli/program.js +0 -5
  10. package/dist/commands/client-recovery.js +0 -3
  11. package/dist/commands/init.js +1 -27
  12. package/dist/commands/task-advance.js +0 -19
  13. package/dist/executors/dag-pi-executor.js +231 -3784
  14. package/dist/executors/pi-executor.js +4 -15
  15. package/dist/executors/pi-extension-resolver.js +14 -2
  16. package/dist/executors/shell-executor.js +217 -945
  17. package/dist/executors/shell-write-guard.js +0 -7
  18. package/dist/shared/dag-failure-category.js +0 -12
  19. package/dist/shared/openspec-spec.js +4 -70
  20. package/dist/shared/operator/capabilities.js +0 -7
  21. package/dist/task/config-types.js +7 -34
  22. package/dist/task/contract/adopt.js +0 -4
  23. package/dist/task/contract/import-revision.js +0 -4
  24. package/dist/task/contract/project.js +0 -3
  25. package/dist/task/contract/schema.js +1 -2
  26. package/dist/task/frontend-project-capability.js +20 -203
  27. package/dist/task/runtime.js +2 -5
  28. package/dist/task/source-prepare/build-draft.js +3 -3
  29. package/dist/task/source-prepare/fragment-inventory.js +9 -15
  30. package/dist/task/source-prepare/prepare.js +1 -89
  31. package/dist/task/source-prepare/semantic-intake.js +2 -6
  32. package/dist/task/source-references.js +1 -22
  33. package/dist/worker/console/chat/chat-ui-policy.js +4 -2
  34. package/dist/worker/console/chat/pi-runtime.js +235 -13
  35. package/dist/worker/console/chat/repo-browser.js +10 -3
  36. package/dist/worker/console/chat/routes.js +86 -44
  37. package/dist/worker/console/chat/semantic-activity.js +12 -11
  38. package/dist/worker/console/chat/session-stats.js +96 -0
  39. package/dist/worker/console/chat/session-store.js +133 -70
  40. package/dist/worker/console/chat/shortcuts.js +1 -1
  41. package/dist/worker/console/chat/turn-process.js +139 -60
  42. package/dist/worker/console/index.js +0 -3
  43. package/dist/worker/console/operation-store.js +6 -226
  44. package/dist/worker/console/operator-actions.js +7 -169
  45. package/dist/worker/console/pi-plugins.js +3 -1
  46. package/dist/worker/console/server.js +19 -4
  47. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-Bho4we4c.js → abnfDiagram-N423BO3Z-DQ5jlYVU.js} +1 -1
  48. package/dist/worker/console/static/assets/{arc-TBDkTeK0.js → arc-BeTVCzH9.js} +1 -1
  49. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-BF_fZrn2.js → architectureDiagram-T3A2C74G-DassREBi.js} +1 -1
  50. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-CCnPgUe2.js → blockDiagram-VBNYF7ZC-DyeNX8Ek.js} +1 -1
  51. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BCYZJLAT.js → c4Diagram-5PPSVZJV-BXb_zxGx.js} +1 -1
  52. package/dist/worker/console/static/assets/channel-B_lofstT.js +1 -0
  53. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-D8K6adhU.js → chunk-2GRJ4B5K-B9F9gxGC.js} +1 -1
  54. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DkT1Q78b.js → chunk-2Q5K7J3B-Byd5zHEQ.js} +1 -1
  55. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DuhvpIr7.js → chunk-5RXB4S5H-BqoxMJk9.js} +1 -1
  56. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BnK6dC49.js → chunk-5VM5RSS4-CT7d6YZw.js} +1 -1
  57. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DPz__z7r.js → chunk-6Q2QTUOP-CdiNyN0N.js} +1 -1
  58. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-0kkppM2j.js → chunk-GF5L2VYU-BGw9bpVc.js} +1 -1
  59. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BB41iK5t.js → chunk-JWPE2WC7-DiDVlVB1.js} +1 -1
  60. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-CiVrxKHU.js → chunk-KBJHAD2P-BhHf-4yK.js} +1 -1
  61. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-C2h9Xje1.js → chunk-RYQCIY6F-B79pEgwv.js} +1 -1
  62. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-3iOSHdbM.js → chunk-XXDRQBXY-Cb70EcUV.js} +1 -1
  63. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-ieUI0-eY.js +1 -0
  64. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-ieUI0-eY.js +1 -0
  65. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BN3A7tkk.js → cose-bilkent-JH36ORCC-DBHLnAf1.js} +1 -1
  66. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-CTuBy0Or.js → cynefin-VYW2F7L2-BT_JRsOH.js} +1 -1
  67. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-wb6tUlMN.js → cynefinDiagram-MW4NZA55-Bss-msa3.js} +1 -1
  68. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-GP1Ivx8B.js → dagre-VZM6K2ZE-B8Gr24pt.js} +1 -1
  69. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-ejrcK6L0.js → diagram-7IWD3JNH-BrjU70Jw.js} +1 -1
  70. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-YFkCCvUU.js → diagram-B4RE2ZJO-Cp_JpPGd.js} +1 -1
  71. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-B4rrvIaL.js → diagram-LBJQPF4R-BWKICEP-.js} +1 -1
  72. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-C-YIZcUf.js → diagram-Q27KOJAE-D3z_UNR_.js} +1 -1
  73. package/dist/worker/console/static/assets/{diagram-UB23O5K3-BPF5wcL1.js → diagram-UB23O5K3-_Ywm390x.js} +1 -1
  74. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-CU5SzXjO.js → ebnfDiagram-BXEA7PRR-D9QYQQMB.js} +1 -1
  75. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-CpL414DE.js → erDiagram-JOGREHBK-BNgXNKAr.js} +1 -1
  76. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-MrhLzNau.js → flowDiagram-UKHOOZJN-QRLnkPCg.js} +1 -1
  77. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-Bg4qFuUo.js → ganttDiagram-PKOTCBZU-B9fOduNi.js} +1 -1
  78. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-ColxSXLR.js → gitGraphDiagram-DS77QQ5N-FSEdtPN2.js} +1 -1
  79. package/dist/worker/console/static/assets/index-Cs9JUSPs.css +1 -0
  80. package/dist/worker/console/static/assets/index-DtPrwu7M.js +451 -0
  81. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-Czvtm6gc.js → infoDiagram-6WML65LV-D5Bjg9dm.js} +1 -1
  82. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-B7Xp8HyJ.js → ishikawaDiagram-WSZJBQD7-BlNRkWvf.js} +1 -1
  83. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DWYZcvph.js → journeyDiagram-NVQOT4AX-BdJiw3SL.js} +1 -1
  84. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-KTLZRUjx.js → kanban-definition-27J2QSJJ-CsDNlYht.js} +1 -1
  85. package/dist/worker/console/static/assets/{linear-_Exn7bfl.js → linear-8g9dbEmX.js} +1 -1
  86. package/dist/worker/console/static/assets/{mermaid.core-Cxg8kX8V.js → mermaid.core-ChyQH_ut.js} +5 -5
  87. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-oKrlo2Bt.js → mindmap-definition-FAOFIHXS-DwYoCTYt.js} +1 -1
  88. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-XAeteiNd.js → pegDiagram-VL7TDLO6-T_b2rS17.js} +1 -1
  89. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-BTf1K-lp.js → pieDiagram-7S7Q4E2Y-1Cr7WMhH.js} +1 -1
  90. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-C0IlY0Jf.js → quadrantDiagram-CIZ2JOQS-Bq4qM59o.js} +1 -1
  91. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BVbKtZhW.js → railroadDiagram-AXF67PYL-ytFgR1RL.js} +1 -1
  92. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-C5e4JFcr.js → requirementDiagram-LRYGKXZP-CnUdUVYX.js} +1 -1
  93. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Cz30hc29.js → sankeyDiagram-W5VNT64P-_5rJqG6R.js} +1 -1
  94. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DS33n6d0.js → sequenceDiagram-SI44F4Z6-D5imyGbt.js} +1 -1
  95. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-46w7v-9T.js → sizeCapture-X5ZJPWSS-DvgDn3wL.js} +1 -1
  96. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-pbcD5SsZ.js → stateDiagram-OKZ733FA-BViu7duc.js} +1 -1
  97. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-kNxnG4_w.js +1 -0
  98. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DuivYBSj.js → swimlanes-SLNWSIFB-BcULKg2P.js} +2 -2
  99. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DyTzlcWH.js +8 -0
  100. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Cb9VZVIl.js → timeline-definition-Z64GVDOM-DmPveIjt.js} +1 -1
  101. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-D2-mfOgR.js → vennDiagram-T6HMQDX7-9r-iFy0R.js} +1 -1
  102. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DeS2aC76.js → wardleyDiagram-T6FBY63Y-D3fIXdvM.js} +1 -1
  103. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-WGsrFjxH.js → xychartDiagram-ELKLHX3M-BCSS9SBg.js} +1 -1
  104. package/dist/worker/console/static/index.html +2 -2
  105. package/dist/worker/console/static-src/app/useRunProgress.js +9 -2
  106. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +19 -8
  107. package/dist/worker/console/static-src/operator-chat/dag-progress-link.js +22 -0
  108. package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +15 -7
  109. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +2 -0
  110. package/dist/worker/console/static-src/operator-chat/useChatThread.js +6 -0
  111. package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +28 -3
  112. package/dist/worker/console/static-src/pages/tasks/run-panel-progress.js +106 -0
  113. package/dist/worker/console/static-src/shell/workspace-route.js +4 -0
  114. package/dist/worker/console/workspace-context.js +1 -111
  115. package/dist/worker/observability/read-model.js +4 -0
  116. package/dist/worker/observe/routes.js +4 -0
  117. package/dist/worker/observe/static/dag-node-purpose.js +0 -5
  118. package/dist/worker/observe/static/views/dag-inspector.js +2 -7
  119. package/dist/worker/observe/static/views/session-timeline.js +46 -15
  120. package/dist/workflows/dag/backend-test-result-contract.js +1 -1
  121. package/dist/workflows/dag/contract-validator-registrations.js +2 -1
  122. package/dist/workflows/dag/frontend-implementation-contract.js +162 -932
  123. package/dist/workflows/dag/frontend-plan-render.js +1 -2
  124. package/dist/workflows/dag/frontend-prewrite-gate.js +349 -255
  125. package/dist/workflows/dag/frontend-recovery-plan.js +10 -17
  126. package/dist/workflows/dag/frontend-recovery-run.js +20 -33
  127. package/dist/workflows/dag/frontend-repair.js +432 -1
  128. package/dist/workflows/dag/frontend-review-context.js +15 -261
  129. package/dist/workflows/dag/frontend-verification-trace.js +24 -249
  130. package/dist/workflows/dag/frontend-worktree-diff.js +17 -250
  131. package/dist/workflows/dag/init-hybrid.js +494 -685
  132. package/dist/workflows/dag/interrupt-request.js +0 -7
  133. package/dist/workflows/dag/node-execution.js +4 -732
  134. package/dist/workflows/dag/prompt.js +1 -34
  135. package/dist/workflows/dag/report.js +1 -37
  136. package/dist/workflows/dag/rerun-feedback.js +1 -256
  137. package/dist/workflows/dag/rerun-plan.js +2 -10
  138. package/dist/workflows/dag/rerun-run.js +0 -23
  139. package/dist/workflows/dag/rerun-task.js +0 -29
  140. package/dist/workflows/dag/retry-policy.js +104 -214
  141. package/dist/workflows/dag/runner.js +122 -429
  142. package/dist/workflows/dag/scheduler.js +16 -114
  143. package/dist/workflows/dag/types.js +12 -239
  144. package/dist/workflows/dag/validate.js +12 -28
  145. package/docs/init-surface.manifest.json +12 -30
  146. package/docs/skills/vetted-skill-registry.md +2 -4
  147. package/docs/templates/README.md +0 -2
  148. package/docs/templates/agent-dag-report.schema.json +2 -8
  149. package/docs/templates/agent-dag.schema.json +1 -1
  150. package/docs/templates/frontend-implementation-contract.schema.json +1 -4
  151. package/harness.json +3 -3
  152. package/package.json +1 -1
  153. package/skills/frontend-bounded-implement/SKILL.md +14 -15
  154. package/skills/frontend-design-review/SKILL.md +41 -22
  155. package/skills/frontend-implementation/SKILL.md +52 -0
  156. package/skills/frontend-implementation/references/code-standards.md +33 -0
  157. package/skills/frontend-implementation/references/design-spec.md +56 -0
  158. package/skills/frontend-implementation/references/node-contracts.md +31 -0
  159. package/skills/frontend-review/SKILL.md +15 -20
  160. package/skills/frontend-review/references/review-findings.md +7 -6
  161. package/skills/frontend-verification/SKILL.md +1 -1
  162. package/skills/loop-agent/references/command-reference.md +0 -1
  163. package/skills/loop-agent/references/hybrid-dag.md +2 -2
  164. package/dist/commands/dag-follow-up.js +0 -138
  165. package/dist/executors/pi-read-budget-policy.js +0 -239
  166. package/dist/worker/console/frontend-human-decision-adapter.js +0 -19
  167. package/dist/worker/console/frontend-split-operation-adapter.js +0 -20
  168. package/dist/worker/console/static/assets/channel-bnXW0U3C.js +0 -1
  169. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-rZpq_twR.js +0 -1
  170. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-rZpq_twR.js +0 -1
  171. package/dist/worker/console/static/assets/index-D3CC4eUz.css +0 -1
  172. package/dist/worker/console/static/assets/index-DLt4ZFvD.js +0 -437
  173. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-zBMYiUyi.js +0 -1
  174. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-d_rms-5i.js +0 -8
  175. package/dist/worker/console/static-src/active-run-badge.js +0 -17
  176. package/dist/worker/materialize/frontend-split-task-materializer.js +0 -72
  177. package/dist/workflows/dag/dag-retry-schema.js +0 -138
  178. package/dist/workflows/dag/frontend-closeout.js +0 -221
  179. package/dist/workflows/dag/frontend-design-policy.js +0 -400
  180. package/dist/workflows/dag/frontend-human-decision.js +0 -182
  181. package/dist/workflows/dag/frontend-provider-capability-matrix.js +0 -159
  182. package/dist/workflows/dag/frontend-read-budgets.js +0 -12
  183. package/dist/workflows/dag/frontend-recovery-capsule.js +0 -455
  184. package/dist/workflows/dag/frontend-recovery-controller.js +0 -202
  185. package/dist/workflows/dag/frontend-recovery-lineage.js +0 -178
  186. package/dist/workflows/dag/frontend-review-findings.js +0 -270
  187. package/dist/workflows/dag/frontend-shadow-dual-write.js +0 -914
  188. package/dist/workflows/dag/frontend-shape-capsule-store.js +0 -191
  189. package/dist/workflows/dag/frontend-shape-facts.js +0 -409
  190. package/dist/workflows/dag/frontend-shape.js +0 -427
  191. package/dist/workflows/dag/frontend-source-fidelity-ledger.js +0 -108
  192. package/dist/workflows/dag/frontend-split-application-service.js +0 -203
  193. package/dist/workflows/dag/frontend-split-orchestrator.js +0 -899
  194. package/dist/workflows/dag/frontend-typed-event-store.js +0 -452
  195. package/dist/workflows/dag/frontend-typed-event-transaction.js +0 -180
  196. package/dist/workflows/dag/frontend-writer-admission.js +0 -285
  197. package/dist/workflows/dag/frontend-writer-status.js +0 -256
  198. package/dist/workflows/dag/recovery-lease.js +0 -80
  199. package/docs/templates/frontend-implementation-dag.json +0 -89
  200. package/docs/templates/spec-registry.schema.json +0 -45
  201. package/skills/frontend-bounded-implement/references/code-standards.md +0 -19
  202. package/skills/frontend-contract/SKILL.md +0 -23
  203. package/skills/frontend-contract/references/contract-protocol.md +0 -34
  204. package/skills/frontend-plan/SKILL.md +0 -26
  205. package/skills/frontend-plan/references/decision-contract.md +0 -37
  206. package/skills/frontend-plan/references/design-decisions.md +0 -17
  207. package/skills/frontend-scout/SKILL.md +0 -25
  208. package/skills/frontend-scout/references/design-evidence.md +0 -16
  209. package/skills/frontend-scout/references/scout-evidence.md +0 -23
@@ -1,285 +0,0 @@
1
- import { deterministicSha256, frontendImplementationContractSchema, } from "./frontend-implementation-contract.js";
2
- import { expandFrontendGlobPaths } from "./frontend-worktree-diff.js";
3
- import { z } from "zod";
4
- /** Runtime writer admission evidence. This is the sole production authorization type. */
5
- export const frontendWriterAdmissionSchema = z
6
- .object({
7
- schemaVersion: z.literal(1),
8
- writerNodeId: z.literal("frontend-implement-pi"),
9
- decision: z.enum(["authorized", "denied"]),
10
- sourceArtifact: z.string().min(1),
11
- artifactHash: z.string().regex(/^[a-f0-9]{64}$/),
12
- checkedAt: z.string().min(1),
13
- reason: z.string().nullable(),
14
- })
15
- .strict();
16
- /** A+B (AC-006): four-state writer admission classification. `accepted` is the
17
- * only authorized state; `requires-human-approval` is the High-topology
18
- * high-admission-receipt gate; `blocked` is deterministic denial; `stale` is
19
- * digest drift (source refresh / re-contract). */
20
- export const frontendWriterAdmissionClassificationSchema = z.enum([
21
- "accepted",
22
- "requires-human-approval",
23
- "blocked",
24
- "stale",
25
- // Legacy fixture alias: writeSet-external test helpers still emit `admitted`.
26
- // Canonical producers always emit one of the four values above.
27
- "admitted",
28
- ]);
29
- /** A+B (AC-006): Micro topology uses `requirement-to-target-and-evidence`
30
- * coverage; Small/Standard/High use `requirement-to-decision`. The two schemas
31
- * are deliberately separated so a Micro evidence coverage can never be read as
32
- * a decision coverage. */
33
- export const frontendAdmissionCoverageSchema = z.discriminatedUnion("coverageId", [
34
- z
35
- .object({
36
- coverageId: z.literal("requirement-to-target-and-evidence"),
37
- requirementId: z.string().min(1),
38
- targetPaths: z.array(z.string().min(1)),
39
- evidenceRefs: z.array(z.string().min(1)),
40
- })
41
- .strict(),
42
- z
43
- .object({
44
- coverageId: z.literal("requirement-to-decision"),
45
- requirementId: z.string().min(1),
46
- decisionRefs: z.array(z.string().min(1)),
47
- })
48
- .strict(),
49
- ]);
50
- /** Failure-source values shared with frontend recovery planning (M8). */
51
- export const frontendAdmissionFailureSourceSchema = z.enum([
52
- "frontend-plan-pi",
53
- "frontend-design-policy-shell",
54
- "frontend-writer-admission-shell",
55
- "frontend-implement-pi",
56
- "frontend-verify-shell",
57
- ]);
58
- /**
59
- * Deterministic artifact schema for `contracts/frontend-writer-admission-result.json`.
60
- * Scheduler admission gate and frontend recovery planning read this artifact
61
- * (fail-closed); it replaces the legacy frontend-prewrite-result-v1 as the
62
- * write authorization source.
63
- */
64
- export const frontendWriterAdmissionResultV1Schema = z
65
- .object({
66
- schemaVersion: z.literal(1),
67
- schemaId: z.literal("frontend-writer-admission-shell-v1"),
68
- classification: frontendWriterAdmissionClassificationSchema,
69
- writeSet: z.array(z.string().min(1)),
70
- writeSetDigest: z.string().regex(/^[a-f0-9]{64}$/),
71
- baselineDigest: z.string().regex(/^[a-f0-9]{64}$/),
72
- admissionDigest: z.string().regex(/^[a-f0-9]{64}$/),
73
- baselineDebtPolicy: z.string().min(1).optional(),
74
- findings: z.array(z
75
- .object({
76
- code: z.string().min(1),
77
- message: z.string().min(1),
78
- path: z.string().optional(),
79
- })
80
- .strict()),
81
- // r12 policy change: a request_design_changes verdict no longer blocks
82
- // writer admission. The verdict and its findings ride along as
83
- // advisory context for the implement node; blocking is owned by the
84
- // deterministic gates (prewrite, write guard) and the final code
85
- // review.
86
- designReviewAdvisory: z
87
- .object({
88
- verdict: z.enum(["approve_design", "request_design_changes"]),
89
- restartPhase: z.string().min(1).optional(),
90
- findingCount: z.number().int().nonnegative(),
91
- })
92
- .strict()
93
- .optional(),
94
- failureSource: frontendAdmissionFailureSourceSchema.optional(),
95
- })
96
- .strict();
97
- /** Artifact relative path written by the frontend-writer-admission-shell. */
98
- export const FRONTEND_WRITER_ADMISSION_RESULT_ARTIFACT = "contracts/frontend-writer-admission-result.json";
99
- function sortFindings(findings) {
100
- return [...findings].sort((a, b) => {
101
- if (a.code !== b.code)
102
- return a.code < b.code ? -1 : 1;
103
- if (a.message !== b.message)
104
- return a.message < b.message ? -1 : 1;
105
- const pathA = a.path ?? "";
106
- const pathB = b.path ?? "";
107
- if (pathA !== pathB)
108
- return pathA < pathB ? -1 : 1;
109
- return 0;
110
- });
111
- }
112
- /** Reject non-concrete writeSet entries: absolute paths, traversal, globstar,
113
- * repo-root, and empty/duplicate-slash segments. */
114
- export function isConcreteWriteSetPath(value) {
115
- const path = value.trim();
116
- if (path === "" || path === "." || path === "./")
117
- return false;
118
- if (path.startsWith("/") || path.includes("\\"))
119
- return false;
120
- const segments = path.split("/");
121
- if (segments.some((segment) => segment === ".." || segment === "**" || segment === "")) {
122
- return false;
123
- }
124
- return true;
125
- }
126
- /** Derive the concrete writeSet from implementation targets ∪ non-static
127
- * verification target files. Entries are de-duplicated and lexicographically
128
- * sorted (frozen). */
129
- export function deriveWriteSet(contract, options) {
130
- const findings = [];
131
- const candidates = [
132
- ...contract.targets.files,
133
- ...contract.verificationTargets
134
- .filter((target) => target.type !== "static")
135
- .map((target) => target.file),
136
- ];
137
- // Expand glob entries (e.g. `src/**` inherited from the skeleton) against
138
- // the enumerated workspace files so the derived writeSet contains only
139
- // concrete paths. Without a file list, globs are treated as concrete and
140
- // rejected by isConcreteWriteSetPath below (fail-closed).
141
- const expanded = options?.workspaceFiles
142
- ? expandFrontendGlobPaths({
143
- candidates,
144
- workspaceFiles: options.workspaceFiles,
145
- })
146
- : candidates;
147
- const writeSet = new Set();
148
- for (const candidate of expanded) {
149
- if (!isConcreteWriteSetPath(candidate)) {
150
- findings.push({
151
- code: "write-set-path-not-concrete",
152
- message: `writeSet entry is not a concrete path: ${candidate}`,
153
- path: candidate,
154
- });
155
- continue;
156
- }
157
- writeSet.add(candidate);
158
- }
159
- return { writeSet: [...writeSet].sort(), findings };
160
- }
161
- /** Sort and freeze the injected baseline; duplicate paths and empty baselines
162
- * are blocking (collection itself is production wiring, not this shell). */
163
- export function freezeBaseline(baseline) {
164
- const findings = [];
165
- if (baseline.length === 0) {
166
- findings.push({
167
- code: "baseline-empty",
168
- message: "writer admission baseline is empty",
169
- });
170
- }
171
- const seen = new Set();
172
- for (const entry of baseline) {
173
- if (seen.has(entry.path)) {
174
- findings.push({
175
- code: "baseline-duplicate-path",
176
- message: `baseline contains duplicate path: ${entry.path}`,
177
- path: entry.path,
178
- });
179
- }
180
- seen.add(entry.path);
181
- }
182
- const sorted = [...baseline].sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
183
- return { baselineDigest: deterministicSha256(sorted), findings };
184
- }
185
- /** Combine mock strategy, allowed strategies digest, frozen command digest,
186
- * production-default-off flag and real-integration expectation into one
187
- * deterministic admission digest. */
188
- export function deriveAdmissionDigest(contract, allowedMockStrategies, frozenCommandLabels, baselineDebtPolicy) {
189
- return deterministicSha256({
190
- mockApiStrategy: contract.mockApi.strategy,
191
- allowedMockStrategiesDigest: deterministicSha256([...allowedMockStrategies].sort()),
192
- frozenCommandLabelsDigest: deterministicSha256([...frozenCommandLabels].sort()),
193
- productionDefaultOff: contract.mockApi.productionDefaultOff === true,
194
- hasRealIntegrationGap: Boolean(contract.realIntegrationGap),
195
- baselineDebtPolicy: baselineDebtPolicy ?? "",
196
- });
197
- }
198
- /** Shape-aware deterministic admission: re-validate the typed contract, honor
199
- * the design-policy result, derive/freeze the writeSet, digest the baseline,
200
- * and assemble the admission digest. */
201
- export function deriveFrontendWriterAdmissionShell(input) {
202
- const findings = [];
203
- const shape = frontendImplementationContractSchema.safeParse(input.contract);
204
- if (!shape.success) {
205
- findings.push({
206
- code: "contract-shape-invalid",
207
- message: `contract fails strict shape validation: ${shape.error.issues[0]?.message ?? "unknown issue"}`,
208
- path: shape.error.issues[0]?.path.join(".") || undefined,
209
- });
210
- }
211
- if (input.designPolicyResult.classification === "blocked") {
212
- findings.push({
213
- code: "design-policy-blocked",
214
- message: `design policy is blocked with ${input.designPolicyResult.findings.length} finding(s)`,
215
- });
216
- }
217
- const derived = deriveWriteSet(input.contract, {
218
- workspaceFiles: input.workspaceFiles,
219
- });
220
- findings.push(...derived.findings);
221
- const baseline = freezeBaseline(input.baseline);
222
- findings.push(...baseline.findings);
223
- const sortedFindings = sortFindings(findings);
224
- // Deterministic admission: any finding blocks. High-risk contracts keep the
225
- // full policy/writeSet/baseline gates but do NOT stop for a human approval
226
- // step — the DAG runs unattended, so `requires-human-approval` would strand
227
- // every high-risk writer. The receipt is still materialized for audit; the
228
- // scheduler authorizes `accepted` directly.
229
- const classification = sortedFindings.length > 0 ? "blocked" : "accepted";
230
- const result = {
231
- schemaId: "frontend-writer-admission-shell-v1",
232
- classification,
233
- writeSet: derived.writeSet,
234
- writeSetDigest: deterministicSha256(derived.writeSet),
235
- baselineDigest: baseline.baselineDigest,
236
- admissionDigest: deriveAdmissionDigest(input.contract, input.allowedMockStrategies, input.frozenCommandLabels, input.baselineDebtPolicy),
237
- ...(input.baselineDebtPolicy !== undefined
238
- ? { baselineDebtPolicy: input.baselineDebtPolicy }
239
- : {}),
240
- findings: sortedFindings,
241
- };
242
- return result;
243
- }
244
- function normalizeNewVerdict(classification) {
245
- return classification === "accepted" ||
246
- classification === "requires-human-approval" ||
247
- classification === "admitted"
248
- ? "accepted"
249
- : "blocked";
250
- }
251
- function normalizeLegacyVerdict(classification) {
252
- return classification === "accepted" || classification === "accepted-normalized"
253
- ? "accepted"
254
- : "blocked";
255
- }
256
- export function compareAdmissionToLegacyGate(input) {
257
- const newVerdict = normalizeNewVerdict(input.newClassification);
258
- const legacyVerdict = normalizeLegacyVerdict(input.legacyClassification);
259
- return {
260
- newClassification: input.newClassification,
261
- legacyClassification: input.legacyClassification,
262
- newVerdict,
263
- legacyVerdict,
264
- match: newVerdict === legacyVerdict,
265
- };
266
- }
267
- /** Render a stable, deterministically ordered mismatch list for the evaluation
268
- * set. Input order is irrelevant: rows are sorted by a canonical key. */
269
- export function renderAdmissionShadowDiff(results) {
270
- const indexed = results.map((result, index) => ({ ...result, index }));
271
- const sorted = indexed.sort((a, b) => {
272
- const keyA = `${a.legacyClassification}|${a.newClassification}`;
273
- const keyB = `${b.legacyClassification}|${b.newClassification}`;
274
- if (keyA !== keyB)
275
- return keyA < keyB ? -1 : 1;
276
- return a.index - b.index;
277
- });
278
- const mismatches = sorted.filter((row) => !row.match);
279
- const lines = [
280
- "## Admission Shadow Diff",
281
- `${sorted.length} comparisons, ${mismatches.length} mismatches`,
282
- ...sorted.map((row) => `- ${row.legacyClassification} -> ${row.newClassification}: legacy=${row.legacyVerdict} new=${row.newVerdict}${row.match ? " match" : " MISMATCH"}`),
283
- ];
284
- return lines.join("\n");
285
- }
@@ -1,256 +0,0 @@
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. focused-check failure → needs-repair (including trace preflight gaps)
31
- * 3. non-empty diff → changed (diff is the mechanical fact source)
32
- * 4. empty diff + runtime already-satisfied evidence → already-satisfied
33
- * 5. empty diff, zero writes, zero diff → blocked (no progress)
34
- * 6. otherwise → empty-diff (fail-closed)
35
- */
36
- export function deriveFrontendWriterStatus(facts) {
37
- if (!facts.writeGuardOk) {
38
- return {
39
- status: "blocked",
40
- reason: `write guard violations: ${facts.writeGuardViolations.join(", ") || "unknown"}`,
41
- };
42
- }
43
- if (facts.focusedCheckFailures.length > 0) {
44
- const failures = facts.focusedCheckFailures
45
- .map((failure) => failure.targetId
46
- ? `${failure.targetId}: ${failure.detail}`
47
- : failure.detail)
48
- .join("; ");
49
- return {
50
- status: "needs-repair",
51
- reason: `focused checks require repair: ${failures}`,
52
- };
53
- }
54
- if (facts.changedFiles.length > 0) {
55
- return {
56
- status: "changed",
57
- reason: `non-empty run-attributed diff (${facts.changedFiles.length} file${facts.changedFiles.length === 1 ? "" : "s"})`,
58
- };
59
- }
60
- if (facts.alreadySatisfiedEvidence) {
61
- return {
62
- status: "already-satisfied",
63
- reason: `empty diff with runtime already-satisfied evidence (${facts.alreadySatisfiedEvidence.requirementTargets.length} target${facts.alreadySatisfiedEvidence.requirementTargets.length === 1 ? "" : "s"})`,
64
- };
65
- }
66
- if (facts.writeToolEvents === 0 && facts.changedFiles.length === 0) {
67
- return {
68
- status: "blocked",
69
- reason: "zero write-tool events and zero diff: no implementation progress",
70
- };
71
- }
72
- return {
73
- status: "empty-diff",
74
- reason: "empty diff without runtime already-satisfied evidence (fail-closed)",
75
- };
76
- }
77
- /**
78
- * Deterministic failure fingerprint over the normalized failure signature:
79
- * category + focused-check failures (target + detail) + guard status. Used for
80
- * repeated-failure deduplication inside the bounded micro-loop.
81
- */
82
- export function computeFailureFingerprint(facts) {
83
- const signature = normalizeFailureSignature(facts);
84
- return createHash("sha256").update(signature).digest("hex");
85
- }
86
- function normalizeFailureSignature(facts) {
87
- const parts = [];
88
- if (!facts.writeGuardOk) {
89
- parts.push("write-guard");
90
- }
91
- for (const failure of facts.focusedCheckFailures) {
92
- parts.push(`${failure.category}:${failure.targetId ?? ""}:${failure.detail}`);
93
- }
94
- if (facts.changedFiles.length === 0 && facts.writeToolEvents === 0) {
95
- parts.push("no-progress");
96
- }
97
- return parts.sort().join("\n");
98
- }
99
- export const DEFAULT_FRONTEND_MICRO_LOOP_BUDGET = {
100
- maxFocusedCheckRounds: 3,
101
- maxWriteAttempts: 5,
102
- maxWallTimeMs: 15 * 60 * 1000,
103
- maxTokens: 200_000,
104
- maxRepeatedFingerprints: 2,
105
- };
106
- /**
107
- * Pure micro-loop continuation gate. Deterministic: same budget + history +
108
- * timestamps always yield the same decision. The caller (executor) drives the
109
- * loop by re-checking this function after each focused-check round.
110
- */
111
- export function evaluateMicroLoopContinuation(budget, history, startWallTimeMs, nowWallTimeMs) {
112
- const rounds = history.length;
113
- const last = history[rounds - 1];
114
- if (last) {
115
- const resolved = (last.status === "changed" &&
116
- last.writeGuardOk &&
117
- last.focusedCheckFailures.length === 0) ||
118
- last.status === "already-satisfied" ||
119
- last.status === "blocked";
120
- if (resolved) {
121
- return { continue: false, reason: "status-resolved" };
122
- }
123
- if (last.writeAttempts >= budget.maxWriteAttempts) {
124
- return { continue: false, reason: "budget-write-attempts" };
125
- }
126
- if (last.tokensUsed >= budget.maxTokens) {
127
- return { continue: false, reason: "budget-tokens" };
128
- }
129
- if (nowWallTimeMs - startWallTimeMs >= budget.maxWallTimeMs) {
130
- return { continue: false, reason: "budget-wall-time" };
131
- }
132
- const repeatCount = history.filter((round) => round.fingerprint === last.fingerprint).length;
133
- if (repeatCount >= budget.maxRepeatedFingerprints) {
134
- return { continue: false, reason: "budget-repeated-fingerprint" };
135
- }
136
- }
137
- if (rounds >= budget.maxFocusedCheckRounds) {
138
- return { continue: false, reason: "budget-focused-check-rounds" };
139
- }
140
- return { continue: true };
141
- }
142
- /**
143
- * Build a deterministic per-round checkpoint payload. Persistence stays with the
144
- * caller (executor writes `runDir/<nodeId>/` only, never root `artifacts/**`).
145
- */
146
- export function buildFrontendMicroLoopCheckpoint(input) {
147
- return {
148
- schemaVersion: 1,
149
- nodeId: input.nodeId,
150
- round: input.round,
151
- status: deriveFrontendWriterStatus(input.facts).status,
152
- fingerprint: computeFailureFingerprint(input.facts),
153
- changedFiles: [...input.facts.changedFiles].sort(),
154
- focusedCheckFailures: input.facts.focusedCheckFailures.map((failure) => ({
155
- ...failure,
156
- })),
157
- writeGuardOk: input.facts.writeGuardOk,
158
- recordedAt: input.recordedAt,
159
- wallTimeMs: input.facts.wallTimeMs,
160
- tokensUsed: input.facts.tokensUsed,
161
- writeAttempts: input.facts.writeAttempts,
162
- };
163
- }
164
- /**
165
- * Shadow comparison between the fact-derived status and the legacy
166
- * `IMPLEMENTATION_OUTCOME:` first-line parse. Never used to decide ok/fail —
167
- * only persisted for the M4 shadow evaluation.
168
- */
169
- export function compareFactStatusToLegacyOutcome(factStatus, legacyOutcome) {
170
- const factVerdict = factStatus === "changed" || factStatus === "already-satisfied"
171
- ? "ok"
172
- : "fail";
173
- const legacyVerdict = legacyOutcome === "changed" || legacyOutcome === "already-satisfied"
174
- ? "ok"
175
- : "fail";
176
- return {
177
- factStatus,
178
- legacyOutcome,
179
- factVerdict,
180
- legacyVerdict,
181
- match: factVerdict === legacyVerdict,
182
- };
183
- }
184
- /**
185
- * Pure session-event scanner: count write-tool events from the persisted
186
- * session-events.jsonl content. Fail-closed: an unparseable line never counts
187
- * as a write, so a corrupt/empty log cannot inflate progress.
188
- */
189
- export function countWriteToolEventsFromSessionEvents(content) {
190
- let count = 0;
191
- for (const line of content.split("\n")) {
192
- const trimmed = line.trim();
193
- if (!trimmed)
194
- continue;
195
- let event;
196
- try {
197
- event = JSON.parse(trimmed);
198
- }
199
- catch {
200
- continue;
201
- }
202
- if (event !== null &&
203
- typeof event === "object" &&
204
- event.type === "tool_execution_start" &&
205
- typeof event.toolName === "string" &&
206
- FRONTEND_WRITE_TOOL_NAMES.has(event.toolName)) {
207
- count += 1;
208
- }
209
- }
210
- return count;
211
- }
212
- /**
213
- * IO collector for frontend facts. Reads only the persisted session-events
214
- * file; every other fact is passed in by the executor so this stays narrow and
215
- * deterministic under test (tests inject session content by writing to a temp
216
- * path or call the pure scanner directly).
217
- */
218
- export async function collectFrontendWriterFacts(input) {
219
- let writeToolEvents = 0;
220
- try {
221
- const content = await readFile(input.sessionEventsPath, "utf8");
222
- writeToolEvents = countWriteToolEventsFromSessionEvents(content);
223
- }
224
- catch {
225
- // Missing/unreadable session log → fail-closed at zero, never fail-open.
226
- writeToolEvents = 0;
227
- }
228
- return {
229
- changedFiles: [...input.changedFiles].sort(),
230
- writeToolEvents,
231
- writeGuardOk: input.writeGuard.ok,
232
- writeGuardViolations: [...input.writeGuard.violations],
233
- requirementTargets: [...new Set(input.requirementTargets)].sort(),
234
- coveredRequirementIds: [...new Set(input.coveredRequirementIds)].sort(),
235
- focusedCheckFailures: input.focusedCheckFailures.map((failure) => ({
236
- ...failure,
237
- })),
238
- ...(input.alreadySatisfiedEvidence
239
- ? {
240
- alreadySatisfiedEvidence: {
241
- requirementTargets: [
242
- ...input.alreadySatisfiedEvidence.requirementTargets,
243
- ].sort(),
244
- proofSha256: input.alreadySatisfiedEvidence.proofSha256,
245
- },
246
- }
247
- : {}),
248
- tokensUsed: input.tokensUsed ?? 0,
249
- wallTimeMs: input.wallTimeMs ?? 0,
250
- rounds: input.rounds ?? 1,
251
- writeAttempts: input.writeAttempts ?? 1,
252
- ...(input.firstLineText !== undefined
253
- ? { firstLineText: input.firstLineText }
254
- : {}),
255
- };
256
- }
@@ -1,80 +0,0 @@
1
- import { createHash, randomBytes } from "node:crypto";
2
- import { mkdir, readFile, writeFile } from "node:fs/promises";
3
- import path from "node:path";
4
- import { writeJsonAtomic } from "../../infrastructure/harness/atomic-write.js";
5
- import { DAG_RUNS_DIR } from "./lifecycle.js";
6
- function recoveryLeasePath(cwd, parentRunId) {
7
- // Never derive a filesystem name directly from an externally supplied legacy
8
- // run id. The content remains auditable while the filename is traversal-safe.
9
- const key = createHash("sha256").update(parentRunId).digest("hex");
10
- return path.join(cwd, DAG_RUNS_DIR, ".recovery-leases", `${key}.json`);
11
- }
12
- function parseLease(raw, parentRunId) {
13
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
14
- throw new Error("invalid dag recovery lease: expected object");
15
- }
16
- const record = raw;
17
- if (record.schemaVersion !== 1 ||
18
- record.parentRunId !== parentRunId ||
19
- typeof record.requestId !== "string" ||
20
- record.requestId.length === 0 ||
21
- (record.kind !== "auto-frontend-recovery" &&
22
- record.kind !== "rerun-from-node" &&
23
- record.kind !== "standalone-task-rerun") ||
24
- typeof record.leaseToken !== "string" ||
25
- record.leaseToken.length === 0 ||
26
- typeof record.acquiredAt !== "string" ||
27
- typeof record.executionRef !== "undefined" &&
28
- typeof record.executionRef !== "string") {
29
- throw new Error("invalid dag recovery lease: schema mismatch");
30
- }
31
- return record;
32
- }
33
- export async function acquireDagRecoveryLease(input) {
34
- const leasePath = recoveryLeasePath(input.cwd, input.parentRunId);
35
- const lease = {
36
- schemaVersion: 1,
37
- parentRunId: input.parentRunId,
38
- requestId: input.requestId,
39
- kind: input.kind,
40
- leaseToken: randomBytes(16).toString("hex"),
41
- acquiredAt: (input.now ?? (() => new Date()))().toISOString(),
42
- };
43
- await mkdir(path.dirname(leasePath), { recursive: true });
44
- try {
45
- // `wx` is the cross-process compare-and-create primitive. The lease is
46
- // deliberately immutable for a parent run: a later recovery must follow
47
- // the committed child lineage rather than fork another DAG.
48
- await writeFile(leasePath, `${JSON.stringify(lease, null, 2)}\n`, {
49
- encoding: "utf8",
50
- flag: "wx",
51
- });
52
- return { kind: "acquired", leasePath, lease };
53
- }
54
- catch (error) {
55
- if (!(error && typeof error === "object" && "code" in error && error.code === "EEXIST")) {
56
- throw error;
57
- }
58
- const existing = parseLease(JSON.parse(await readFile(leasePath, "utf8")), input.parentRunId);
59
- return { kind: "held", leasePath, lease: existing };
60
- }
61
- }
62
- /** Persist the operation/run identity before the caller is allowed to execute it. */
63
- export async function bindDagRecoveryLeaseExecution(input) {
64
- if (!input.executionRef.trim()) {
65
- throw new Error("dag recovery lease executionRef is required");
66
- }
67
- const existing = parseLease(JSON.parse(await readFile(input.leasePath, "utf8")), input.lease.parentRunId);
68
- if (existing.leaseToken !== input.lease.leaseToken) {
69
- throw new Error("dag recovery lease ownership drift");
70
- }
71
- if (existing.executionRef && existing.executionRef !== input.executionRef) {
72
- throw new Error("dag recovery lease execution identity drift");
73
- }
74
- const bound = {
75
- ...existing,
76
- executionRef: input.executionRef,
77
- };
78
- await writeJsonAtomic(input.leasePath, bound);
79
- return bound;
80
- }