@tea-agent/loop-agent 0.38.0 → 0.38.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 (147) hide show
  1. package/CHANGELOG.md +191 -0
  2. package/dist/application/dag/generate-task-dag.js +9 -0
  3. package/dist/build-stamp.json +3 -3
  4. package/dist/commands/init-upgrade.js +97 -19
  5. package/dist/executors/dag-pi-executor.js +42 -4
  6. package/dist/executors/shell-executor.js +269 -15
  7. package/dist/infrastructure/harness/atomic-write.js +2 -1
  8. package/dist/shared/openspec-spec.js +76 -0
  9. package/dist/shared/operator/capabilities.js +149 -27
  10. package/dist/task/config-types.js +43 -0
  11. package/dist/task/frontend-project-capability.js +292 -38
  12. package/dist/worker/console/chat/deferred-turn.js +91 -0
  13. package/dist/worker/console/chat/mcp-inventory.js +221 -0
  14. package/dist/worker/console/chat/pi-runtime.js +165 -83
  15. package/dist/worker/console/chat/repo-walk.js +10 -1
  16. package/dist/worker/console/chat/routes.js +477 -55
  17. package/dist/worker/console/chat/semantic-activity.js +20 -8
  18. package/dist/worker/console/chat/session-store.js +146 -2
  19. package/dist/worker/console/chat/turn-process.js +17 -30
  20. package/dist/worker/console/harness-pi-model-matrix.js +137 -0
  21. package/dist/worker/console/operator-actions.js +128 -4
  22. package/dist/worker/console/operator-user-error.js +19 -0
  23. package/dist/worker/console/pi-readiness.js +8 -1
  24. package/dist/worker/console/recovery-cta.js +4 -4
  25. package/dist/worker/console/recovery-error-copy.js +4 -4
  26. package/dist/worker/console/security.js +20 -1
  27. package/dist/worker/console/server.js +6 -1
  28. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-DSvF6RXQ.js → abnfDiagram-N423BO3Z-C4bmo3iY.js} +1 -1
  29. package/dist/worker/console/static/assets/{arc-DuI4ogtJ.js → arc-SAD7McOS.js} +1 -1
  30. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DdBbgD4y.js → architectureDiagram-T3A2C74G-DAVLUdK1.js} +1 -1
  31. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-xQC2HELm.js → blockDiagram-VBNYF7ZC-CPPdv4vj.js} +1 -1
  32. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DPMSgwIZ.js → c4Diagram-5PPSVZJV-D1GRGZax.js} +1 -1
  33. package/dist/worker/console/static/assets/channel-R6oQIDkw.js +1 -0
  34. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-WTEKTxin.js → chunk-2GRJ4B5K-D_Bq2ZqK.js} +1 -1
  35. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-B6vZ0Pv_.js → chunk-2Q5K7J3B-CPLVtamK.js} +1 -1
  36. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BrerNAix.js → chunk-5RXB4S5H-DnaZWcL3.js} +1 -1
  37. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-D_ch2aaI.js → chunk-5VM5RSS4-BRTmFgnq.js} +1 -1
  38. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-VvfacNrq.js → chunk-6Q2QTUOP-BRlfYZoH.js} +1 -1
  39. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-B-uGFIg2.js → chunk-GF5L2VYU-CjNybD4u.js} +1 -1
  40. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BlysCtod.js → chunk-JWPE2WC7-D3e-74fG.js} +1 -1
  41. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BjAUAfcT.js → chunk-KBJHAD2P-Br3ir6f2.js} +1 -1
  42. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-BeZnBmb-.js → chunk-RYQCIY6F-1NgVuRbh.js} +1 -1
  43. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-Bbfe3UJG.js → chunk-XXDRQBXY-BTYuvuWN.js} +1 -1
  44. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BPXlmPWI.js +1 -0
  45. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BPXlmPWI.js +1 -0
  46. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BKJQnYVM.js → cose-bilkent-JH36ORCC-BC6Bgosf.js} +1 -1
  47. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-B1wtI3hU.js → cynefin-VYW2F7L2-DV-HypfL.js} +1 -1
  48. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-DCF6DBSu.js → cynefinDiagram-MW4NZA55-DhTaddO-.js} +1 -1
  49. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BJyNGRlP.js → dagre-VZM6K2ZE-BnEcOmiI.js} +1 -1
  50. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-B7j1-VsZ.js → diagram-7IWD3JNH-xphGRVKp.js} +1 -1
  51. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BREJ140a.js → diagram-B4RE2ZJO-CrdD1mRG.js} +1 -1
  52. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-CDPHmHGH.js → diagram-LBJQPF4R-D4YdLsaF.js} +1 -1
  53. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-BtZLrez1.js → diagram-Q27KOJAE-LCKyGZ5U.js} +1 -1
  54. package/dist/worker/console/static/assets/{diagram-UB23O5K3-mzybu8oU.js → diagram-UB23O5K3-BeiTe7t0.js} +1 -1
  55. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-Dk3I2uO6.js → ebnfDiagram-BXEA7PRR-BO2cq8GS.js} +1 -1
  56. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-BT97u_tQ.js → erDiagram-JOGREHBK-CmWww3Nt.js} +1 -1
  57. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CMOtYzui.js → flowDiagram-UKHOOZJN-C2kkUYg5.js} +1 -1
  58. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DiNydkuY.js → ganttDiagram-PKOTCBZU-DY_JZ2iQ.js} +1 -1
  59. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Cwt6fLkl.js → gitGraphDiagram-DS77QQ5N-BizhCSw9.js} +1 -1
  60. package/dist/worker/console/static/assets/index-BXN89VPX.js +325 -0
  61. package/dist/worker/console/static/assets/index-CuH5CNFu.css +1 -0
  62. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-AsGIGKKB.js → infoDiagram-6WML65LV-Dlo2eK_d.js} +1 -1
  63. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-BL8ITZh3.js → ishikawaDiagram-WSZJBQD7-3EIlIIuZ.js} +1 -1
  64. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-VPKAUX8M.js → journeyDiagram-NVQOT4AX-DiMDe5Yh.js} +1 -1
  65. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-BL56hfGp.js → kanban-definition-27J2QSJJ-sN553oIN.js} +1 -1
  66. package/dist/worker/console/static/assets/{linear-BmSG3nIZ.js → linear-D9Py31Ld.js} +1 -1
  67. package/dist/worker/console/static/assets/{mermaid.core-DWGK9kwP.js → mermaid.core-D7Occzk_.js} +5 -5
  68. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS--vvKLjo0.js → mindmap-definition-FAOFIHXS-DjDRboiR.js} +1 -1
  69. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-B41mxb2-.js → pegDiagram-VL7TDLO6-Cgc4qlzp.js} +1 -1
  70. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DTlu77sw.js → pieDiagram-7S7Q4E2Y-DxZj8pKd.js} +1 -1
  71. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-Bk5A7alv.js → quadrantDiagram-CIZ2JOQS-CJ50MngR.js} +1 -1
  72. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Csf9jzQy.js → railroadDiagram-AXF67PYL-BY-OD8Tr.js} +1 -1
  73. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-CeptoSNx.js → requirementDiagram-LRYGKXZP-CQFButfK.js} +1 -1
  74. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-A-DLCLer.js → sankeyDiagram-W5VNT64P-BB_QUK_b.js} +1 -1
  75. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-22VbPdOI.js → sequenceDiagram-SI44F4Z6-CqhXZjyx.js} +1 -1
  76. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DWvAyBC9.js → sizeCapture-X5ZJPWSS-2l1U4rgJ.js} +1 -1
  77. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-qICZ75yT.js → stateDiagram-OKZ733FA-BX1FbrhJ.js} +1 -1
  78. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DJKi0Qel.js +1 -0
  79. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-xi7lcMyp.js → swimlanes-SLNWSIFB-DOSnT7hb.js} +2 -2
  80. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-YYe7EEm5.js +8 -0
  81. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Bu6Nl6O2.js → timeline-definition-Z64GVDOM-DVJQA7AN.js} +1 -1
  82. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BzMz4Bn9.js → vennDiagram-T6HMQDX7-74uxl9gx.js} +1 -1
  83. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CxaJnzc5.js → wardleyDiagram-T6FBY63Y-DsBxlcg_.js} +1 -1
  84. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-5JK-omQK.js → xychartDiagram-ELKLHX3M-snuW9Mjw.js} +1 -1
  85. package/dist/worker/console/static/index.html +2 -2
  86. package/dist/worker/console/static-src/app/console-types.js +39 -0
  87. package/dist/worker/console/static-src/app/useOperatorActions.js +1 -1
  88. package/dist/worker/console/static-src/app/useRecoveryActions.js +2 -2
  89. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +82 -22
  90. package/dist/worker/console/static-src/operator-chat/mutation-gate.js +45 -0
  91. package/dist/worker/console/static-src/operator-chat/resource-diagnostics.js +204 -0
  92. package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +8 -1
  93. package/dist/worker/console/static-src/operator-chat/turn-submission.js +42 -0
  94. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +18 -0
  95. package/dist/worker/console/static-src/operator-chat/useChatStream.js +216 -10
  96. package/dist/worker/console/static-src/operator-chat/useChatThread.js +50 -22
  97. package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +26 -1
  98. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +755 -51
  99. package/dist/workflows/dag/backend-test-case-manifest.js +4 -0
  100. package/dist/workflows/dag/backend-test-execution-contract.js +18 -9
  101. package/dist/workflows/dag/backend-test-gap-fill.js +205 -0
  102. package/dist/workflows/dag/backend-test-intake-context.js +32 -0
  103. package/dist/workflows/dag/backend-test-layout.js +133 -0
  104. package/dist/workflows/dag/backend-test-markdown-workflow.js +167 -32
  105. package/dist/workflows/dag/backend-test-module-stem.js +5 -0
  106. package/dist/workflows/dag/backend-test-pytest-collection.js +368 -33
  107. package/dist/workflows/dag/backend-test-result-contract.js +1 -1
  108. package/dist/workflows/dag/backend-test-scenario-param.js +884 -140
  109. package/dist/workflows/dag/backend-test-scenario-partitions.js +249 -0
  110. package/dist/workflows/dag/backend-test-writer-completeness.js +100 -56
  111. package/dist/workflows/dag/contract-validator-registrations.js +3 -1
  112. package/dist/workflows/dag/convergence/controller.js +141 -0
  113. package/dist/workflows/dag/dynamic-runtime/map.js +24 -8
  114. package/dist/workflows/dag/dynamic-runtime/shared.js +1 -1
  115. package/dist/workflows/dag/failure-routing.js +9 -4
  116. package/dist/workflows/dag/frontend-implementation-contract.js +386 -0
  117. package/dist/workflows/dag/frontend-plan-render.js +90 -0
  118. package/dist/workflows/dag/frontend-prewrite-gate.js +479 -28
  119. package/dist/workflows/dag/init-hybrid.js +920 -110
  120. package/dist/workflows/dag/lifecycle.js +4 -0
  121. package/dist/workflows/dag/node-execution.js +77 -8
  122. package/dist/workflows/dag/prompt.js +45 -0
  123. package/dist/workflows/dag/report.js +6 -0
  124. package/dist/workflows/dag/rerun-feedback.js +212 -0
  125. package/dist/workflows/dag/rerun-plan.js +93 -27
  126. package/dist/workflows/dag/rerun-run.js +3 -0
  127. package/dist/workflows/dag/rerun-task.js +61 -2
  128. package/dist/workflows/dag/retry-policy.js +33 -0
  129. package/dist/workflows/dag/types.js +148 -4
  130. package/dist/workflows/dag/validate.js +7 -2
  131. package/docs/templates/backend-test-dag.json +60 -35
  132. package/docs/templates/frontend-design-contract.md +40 -14
  133. package/docs/templates/frontend-implementation-contract.schema.json +9 -2
  134. package/docs/templates/frontend-task-constraints.md +40 -13
  135. package/docs/templates/frontend-task-requirement.md +54 -20
  136. package/harness.json +3 -1
  137. package/package.json +1 -1
  138. package/skills/frontend-design-review/references/review-checklist.md +2 -0
  139. package/skills/frontend-implementation/references/design-spec.md +16 -19
  140. package/skills/frontend-implementation/references/node-contracts.md +15 -11
  141. package/dist/worker/console/static/assets/channel-DLVz9TYW.js +0 -1
  142. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BeAuGcO9.js +0 -1
  143. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BeAuGcO9.js +0 -1
  144. package/dist/worker/console/static/assets/index-DQxiso1w.js +0 -325
  145. package/dist/worker/console/static/assets/index-_j0Hzo69.css +0 -1
  146. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DzKvfHTX.js +0 -1
  147. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Cxqr_qM1.js +0 -8
@@ -445,6 +445,9 @@ async function stageContinuationRun(input) {
445
445
  ranks,
446
446
  nodes,
447
447
  continuation,
448
+ ...(input.parentState.convergence
449
+ ? { convergence: structuredClone(input.parentState.convergence) }
450
+ : {}),
448
451
  ...(input.parentState.evaluation
449
452
  ? { evaluation: structuredClone(input.parentState.evaluation) }
450
453
  : {}),
@@ -3,7 +3,8 @@ import path from "node:path";
3
3
  import { generateTaskDagUseCase } from "../../application/dag/generate-task-dag.js";
4
4
  import { writeJsonAtomic } from "../../infrastructure/harness/atomic-write.js";
5
5
  import { DAG_RUNS_DIR, isTerminalDagRunStatus, locateDagRun, readDagRunSpec, readDagRunState, } from "./lifecycle.js";
6
- import { parseDagSpec } from "./types.js";
6
+ import { deriveDagRerunFeedback } from "./rerun-feedback.js";
7
+ import { parseDagSpec, } from "./types.js";
7
8
  export const RERUN_TASK_LINEAGE_REL_PATH = ".runtime/rerun-task-lineage.json";
8
9
  export function parseDagRerunTaskArgs(args) {
9
10
  if (args.length === 0) {
@@ -218,6 +219,7 @@ async function writeRerunTaskLineage(cwd, newRunId, input) {
218
219
  parentRunId: input.parentRunId,
219
220
  reason: input.reason,
220
221
  requestId: input.requestId,
222
+ feedback: input.feedback,
221
223
  createdAt: new Date().toISOString(),
222
224
  }, { repoRoot: cwd, allowCompletedFactsWrite: true });
223
225
  }
@@ -228,12 +230,30 @@ function blockedResult(input) {
228
230
  reason: input.reason,
229
231
  requestId: input.requestId,
230
232
  mode: "blocked",
233
+ feedback: input.feedback ?? {
234
+ inherited: false,
235
+ reason: "no-unresolved-verify-review-feedback",
236
+ },
231
237
  blockedReason: input.blockedReason,
232
238
  ...(input.note ? { note: input.note } : {}),
233
239
  ...(input.taskId ? { taskId: input.taskId } : {}),
234
240
  ...(input.conflictPaths ? { conflictPaths: input.conflictPaths } : {}),
235
241
  };
236
242
  }
243
+ async function readGeneratedRerunFeedback(outputPath) {
244
+ const raw = JSON.parse(await readFile(outputPath, "utf8"));
245
+ return parseDagSpec(raw).rerunFeedback;
246
+ }
247
+ function summarizeFeedback(feedback, fallbackReason) {
248
+ return feedback
249
+ ? {
250
+ inherited: true,
251
+ sourceNodeId: feedback.sourceNodeId,
252
+ digest: feedback.digest,
253
+ bindingStatus: feedback.binding.status,
254
+ }
255
+ : { inherited: false, reason: fallbackReason };
256
+ }
237
257
  function extractRunSummary(generateResult) {
238
258
  if (generateResult.mode === "generate+basic-validate")
239
259
  return undefined;
@@ -277,7 +297,26 @@ export async function rerunStandaloneTask(input) {
277
297
  note: eligibility.note,
278
298
  });
279
299
  }
300
+ let parentFeedback;
301
+ let missingFeedbackReason = "no-unresolved-verify-review-feedback";
302
+ try {
303
+ parentFeedback = await deriveDagRerunFeedback({
304
+ parentRunId: input.parentRunId,
305
+ taskId: taskResolution.taskId,
306
+ runDir: located.runDir,
307
+ spec,
308
+ state,
309
+ });
310
+ }
311
+ catch {
312
+ // Feedback is additive recovery context. A corrupt/missing historical
313
+ // artifact must be reported honestly but must not block an otherwise
314
+ // eligible full rerun.
315
+ missingFeedbackReason = "feedback-unavailable";
316
+ }
280
317
  const execute = input.execute ?? true;
318
+ let effectiveFeedback;
319
+ let expectedRerunFeedbackDigest;
281
320
  // AC-001 generated-output-conflict preflight: when a real execution is
282
321
  // requested, first generate the DAG draft (execute:false) so we can inspect
283
322
  // the regenerated spec's requireChangedFiles writer write sets against
@@ -296,15 +335,23 @@ export async function rerunStandaloneTask(input) {
296
335
  dryRun: false,
297
336
  profile: input.profile ?? "auto",
298
337
  profileExplicit: input.profileExplicit ?? false,
338
+ rerunFeedback: parentFeedback,
299
339
  });
300
340
  if (preflight.mode === "generate+basic-validate") {
301
341
  let conflictPaths = [];
302
342
  try {
303
343
  const raw = await readFile(preflight.outputPath, "utf8");
304
344
  const preflightSpec = parseDagSpec(JSON.parse(raw));
345
+ effectiveFeedback = preflightSpec.rerunFeedback;
346
+ expectedRerunFeedbackDigest = effectiveFeedback?.digest;
347
+ if (parentFeedback && !effectiveFeedback) {
348
+ throw new Error("rerun feedback missing from generated preflight DAG");
349
+ }
305
350
  conflictPaths = await assessGeneratedOutputConflict(preflightSpec, input.cwd);
306
351
  }
307
- catch {
352
+ catch (error) {
353
+ if (parentFeedback)
354
+ throw error;
308
355
  // If the draft cannot be parsed, fall through to the real execution
309
356
  // path: execution's own validation will surface the structural defect.
310
357
  conflictPaths = [];
@@ -332,7 +379,17 @@ export async function rerunStandaloneTask(input) {
332
379
  dryRun: false,
333
380
  profile: input.profile ?? "auto",
334
381
  profileExplicit: input.profileExplicit ?? false,
382
+ rerunFeedback: parentFeedback,
383
+ expectedRerunFeedbackDigest,
335
384
  });
385
+ if (parentFeedback && !effectiveFeedback) {
386
+ effectiveFeedback = await readGeneratedRerunFeedback(generateResult.outputPath);
387
+ }
388
+ if (expectedRerunFeedbackDigest &&
389
+ effectiveFeedback?.digest !== expectedRerunFeedbackDigest) {
390
+ throw new Error(`rerun feedback drift after generation: expected ${expectedRerunFeedbackDigest}, got ${effectiveFeedback?.digest ?? "missing"}`);
391
+ }
392
+ const feedbackSummary = summarizeFeedback(effectiveFeedback, missingFeedbackReason);
336
393
  const newRunId = extractNewRunId(generateResult);
337
394
  const runSummary = extractRunSummary(generateResult);
338
395
  const completedSuccessfully = !execute ||
@@ -342,6 +399,7 @@ export async function rerunStandaloneTask(input) {
342
399
  parentRunId: input.parentRunId,
343
400
  reason: input.reason,
344
401
  requestId: input.requestId,
402
+ feedback: feedbackSummary,
345
403
  });
346
404
  }
347
405
  const result = {
@@ -350,6 +408,7 @@ export async function rerunStandaloneTask(input) {
350
408
  reason: input.reason,
351
409
  requestId: input.requestId,
352
410
  mode: "generate+validate+execute",
411
+ feedback: feedbackSummary,
353
412
  taskId: taskResolution.taskId,
354
413
  ...(newRunId ? { newRunId } : {}),
355
414
  ...(runSummary && !completedSuccessfully && "status" in runSummary
@@ -21,6 +21,8 @@ export const STRUCTURED_OUTPUT_RETRY_CATEGORY = "output-too-large";
21
21
  export const PROTOCOL_INVALID_RETRY_CATEGORY = "protocol-invalid";
22
22
  /** Recoverable model artifact/schema formatting failure on read-only structured nodes. */
23
23
  export const STRUCTURED_ARTIFACT_INVALID_RETRY_CATEGORY = "invalid-output";
24
+ /** Provider stopReason=length truncated the response before the JSON contract completed. */
25
+ export const STRUCTURED_OUTPUT_TRUNCATED_RETRY_CATEGORY = "structured-output-truncated";
24
26
  /** Retry only a proven no-op from an explicitly opt-in bounded Pi writer. */
25
27
  export const WRITER_EMPTY_DIFF_RETRY_CATEGORY = "writer-empty-diff";
26
28
  /** Retry when a backend-test writer finished but Completeness Gate found missing/broken targets. */
@@ -36,6 +38,7 @@ export const STRUCTURED_REQUIRED_DAG_RETRY_CATEGORIES = [
36
38
  ...DEFAULT_DAG_RETRY_CATEGORIES,
37
39
  STRUCTURED_OUTPUT_RETRY_CATEGORY,
38
40
  STRUCTURED_ARTIFACT_INVALID_RETRY_CATEGORY,
41
+ STRUCTURED_OUTPUT_TRUNCATED_RETRY_CATEGORY,
39
42
  ];
40
43
  /** Categories allowed on nodes that declare a machine-readable outputProtocol. */
41
44
  export const PROTOCOL_AWARE_DAG_RETRY_CATEGORIES = [
@@ -48,6 +51,7 @@ export const ALL_DAG_RETRY_CATEGORIES = [
48
51
  STRUCTURED_OUTPUT_RETRY_CATEGORY,
49
52
  PROTOCOL_INVALID_RETRY_CATEGORY,
50
53
  STRUCTURED_ARTIFACT_INVALID_RETRY_CATEGORY,
54
+ STRUCTURED_OUTPUT_TRUNCATED_RETRY_CATEGORY,
51
55
  WRITER_EMPTY_DIFF_RETRY_CATEGORY,
52
56
  INCOMPLETE_WRITE_SET_RETRY_CATEGORY,
53
57
  WRITER_CLEAN_TIMEOUT_RETRY_CATEGORY,
@@ -118,6 +122,17 @@ export const STRUCTURED_REQUIRED_PI_RETRY_POLICY = {
118
122
  ...DEFAULT_READ_ONLY_PI_RETRY_POLICY,
119
123
  retryCategories: [...STRUCTURED_REQUIRED_DAG_RETRY_CATEGORIES],
120
124
  };
125
+ /**
126
+ * Planner read-only nodes (standard DAG contract-pi / plan-pi) may retry with
127
+ * a compact-output instruction when the model produced an oversized assistant
128
+ * response. Only `output-too-large` is added on top of the default set;
129
+ * report/review and other read-only roles keep the default set so they do not
130
+ * silently learn new output semantics.
131
+ */
132
+ export const PLANNER_OUTPUT_LIMIT_RETRY_POLICY = {
133
+ ...DEFAULT_READ_ONLY_PI_RETRY_POLICY,
134
+ retryCategories: [...DEFAULT_DAG_RETRY_CATEGORIES, STRUCTURED_OUTPUT_RETRY_CATEGORY],
135
+ };
121
136
  /**
122
137
  * Default retry for reviewer / recovery nodes that declare outputProtocol.
123
138
  * Includes protocol-invalid so missing VERDICT lines are corrected in-node.
@@ -186,6 +201,24 @@ function isExclusivePiImplementer(task) {
186
201
  !task.dynamicCondition &&
187
202
  !task.dynamicLoopUntil);
188
203
  }
204
+ /**
205
+ * Provider/transport failure categories that can crash a writer executor
206
+ * after some workspace writes already landed on disk. A continuation may
207
+ * restart that writer itself; it must never import or normalize the ERROR fact.
208
+ */
209
+ export const WRITER_TRANSPORT_FAILURE_CATEGORIES = new Set([
210
+ "unavailable",
211
+ "network",
212
+ "rate-limit",
213
+ "timeout",
214
+ ]);
215
+ export function isTransportCrashedExclusiveWriter(task, failureCategory) {
216
+ if (!task)
217
+ return false;
218
+ return (isExclusivePiImplementer(task) &&
219
+ failureCategory !== undefined &&
220
+ WRITER_TRANSPORT_FAILURE_CATEGORIES.has(failureCategory));
221
+ }
189
222
  /**
190
223
  * Static eligibility for the requireChangedFiles writer-empty-diff class.
191
224
  * The Pi executor assigns writer-empty-diff only after post-write-guard
@@ -171,6 +171,9 @@ export const dagJsonArtifactGateSchema = z.object({
171
171
  const dagFrontendNodeIdSchema = z
172
172
  .string()
173
173
  .regex(/^[a-z][a-z0-9-]*$/, "frontend node id must be kebab-case");
174
+ const openspecCandidatePathSchema = z
175
+ .string()
176
+ .refine((candidate) => !candidate.includes("\\") && isOpenspecSpecFilePath(candidate), "openspec candidate must be a repo-relative supported file under openspec/schemas/, openspec/project-specs/, or ai_workspace/");
174
177
  export const dagFrontendPrewriteGateSchema = z.object({
175
178
  schemaVersion: z.literal(1),
176
179
  planFromNodeId: dagFrontendNodeIdSchema,
@@ -189,11 +192,51 @@ export const dagFrontendPrewriteGateSchema = z.object({
189
192
  outputDir: z.string().regex(/^[a-z0-9][a-z0-9._-]*$/),
190
193
  requireSourceFreshness: z.literal(true),
191
194
  implementationWriteSet: z.array(z.string().min(1)).min(1).optional(),
192
- openspecCandidatePaths: z
193
- .array(z
195
+ openspecCandidatePaths: z.array(openspecCandidatePathSchema).default([]),
196
+ /**
197
+ * Generation-frozen component/theme specification bucket. Derived from
198
+ * `designEvidence.classified.component ∪ theme ∪ rule.components` at DAG
199
+ * generation. When non-empty and the canonical contract has UI-visible
200
+ * work, the prewrite gate requires a non-empty `uiComponentChoices` and
201
+ * cross-checks each `specReference.path` against this set + the effective
202
+ * plan openspec-citations block. Legacy DAGs without this field keep the
203
+ * field-absent semantics (component-choices-missing is not enforced).
204
+ */
205
+ componentSpecCandidatePaths: z.array(openspecCandidatePathSchema).optional(),
206
+ /**
207
+ * OpenSpec gate policy. Omitted on legacy DAGs: the runtime treats
208
+ * undefined as scan-strict so pre-existing full-read semantics are
209
+ * preserved. Generation always writes it explicitly for new DAGs.
210
+ */
211
+ openspecPolicy: z.enum(["cited", "scan-strict"]).optional(),
212
+ /**
213
+ * When true, the effective plan node (planFromNodeId when it is selected
214
+ * over the fallback) emits an RFC 7386 merge-patch delta instead of a full
215
+ * contract. The gate applies it on the original contract node
216
+ * (planFallbackFromNodeIds[0]) before analysis/materialization. Legacy DAGs
217
+ * without this field keep full-contract semantics.
218
+ */
219
+ revisionPatch: z.boolean().optional(),
220
+ /**
221
+ * Deterministically rendered plan.md artifact name (same outputDir as the
222
+ * contract). Defaults to frontend-plan.md at runtime.
223
+ */
224
+ planMdArtifactName: z
194
225
  .string()
195
- .refine((candidate) => !candidate.includes("\\") && isOpenspecSpecFilePath(candidate), "openspec candidate must be a repo-relative supported file under openspec/schemas/ or openspec/project-specs/"))
196
- .default([]),
226
+ .regex(/^[a-z0-9][a-z0-9._-]*\.md$/)
227
+ .optional(),
228
+ /**
229
+ * Generation-frozen candidate source metadata (diagnostic). cited mode
230
+ * candidates are declared ∪ taskSourceCited; scan-strict candidates are
231
+ * scanStrict (auto-discovered designEvidence.normativePaths).
232
+ */
233
+ openspecCandidateSources: z
234
+ .object({
235
+ declared: z.array(openspecCandidatePathSchema).default([]),
236
+ taskSourceCited: z.array(openspecCandidatePathSchema).default([]),
237
+ scanStrict: z.array(openspecCandidatePathSchema).default([]),
238
+ })
239
+ .optional(),
197
240
  });
198
241
  export const dagFrontendLintBaselineSchema = z
199
242
  .object({
@@ -380,6 +423,8 @@ export const dagBackendTestPipelineSchema = z.enum([
380
423
  "markdown-scenario-param",
381
424
  "markdown-execute-html",
382
425
  "markdown-manifest",
426
+ "markdown-report",
427
+ "ingest-backend-test-gap",
383
428
  ]);
384
429
  export const dagFrontendBrowserToolPreflightSchema = z.object({}).strict();
385
430
  export const dagFrontendTestEvidenceValidationSchema = z.object({}).strict();
@@ -557,6 +602,13 @@ export const dagDynamicExpansionSchema = z.object({
557
602
  maxTotalTokens: z.number().int().positive().optional(),
558
603
  })
559
604
  .optional(),
605
+ /**
606
+ * When true, any child blocked by the aggregate token budget fails the map
607
+ * barrier instead of being retained as a case-level blocked outcome.
608
+ * Backend-test enables this because partial Markdown/pytest module sets are
609
+ * invalid generation assets; frontend-test keeps the default false.
610
+ */
611
+ failOnTokenBudgetExhaustion: z.boolean().optional(),
560
612
  /**
561
613
  * When true, map child ERROR/auth/timeout is recorded as case-level
562
614
  * failed/blocked evidence and the map barrier still succeeds (frontend-test).
@@ -661,6 +713,8 @@ export const dagConvergenceSpecSchema = z
661
713
  */
662
714
  export const structuredContractOutputSchemaIds = [
663
715
  "frontend-implementation-contract-v1",
716
+ "frontend-implementation-contract-plan-patch-v1",
717
+ "frontend-implementation-contract-revision-patch-v1",
664
718
  ];
665
719
  export const dagTaskSchema = z.object({ id: z.string().regex(/^[a-z][a-z0-9-]*$/, "task id must be kebab-case"),
666
720
  depends_on: z.array(z.string()).default([]),
@@ -722,8 +776,25 @@ export const dagTaskSchema = z.object({ id: z.string().regex(/^[a-z][a-z0-9-]*$/
722
776
  .object({
723
777
  schemaId: z.enum(structuredContractOutputSchemaIds),
724
778
  retryOnInvalid: z.boolean().default(true),
779
+ /**
780
+ * Optional deterministic base object for patch-producing structured
781
+ * nodes. The frontend plan patch validator applies the model delta to
782
+ * this run-owned skeleton before strict contract validation.
783
+ */
784
+ skeleton: z.record(z.string(), z.unknown()).optional(),
725
785
  })
726
786
  .strict()
787
+ .superRefine((value, ctx) => {
788
+ if (value.schemaId ===
789
+ "frontend-implementation-contract-plan-patch-v1" &&
790
+ !value.skeleton) {
791
+ ctx.addIssue({
792
+ code: "custom",
793
+ message: "frontend plan patch output requires a runtime skeleton",
794
+ path: ["skeleton"],
795
+ });
796
+ }
797
+ })
727
798
  .optional(),
728
799
  /**
729
800
  * Fail-closed outcome/diff consistency contract for bounded Pi writers.
@@ -802,6 +873,55 @@ export const dagSourceBindingSchema = z.object({
802
873
  .min(1),
803
874
  requirementIds: z.array(z.string().regex(/^(?:REQ|BR|AC)-[A-Z0-9]+(?:-[A-Z0-9]+)*$/)),
804
875
  });
876
+ const dagRerunFeedbackEvidencePathSchema = z
877
+ .string()
878
+ .min(1)
879
+ .refine((value) => !/^(?:[A-Za-z]:[\\/]|[\\/]{2}|\/)/.test(value), "rerun feedback evidence paths must be repository-relative");
880
+ /**
881
+ * Bounded unresolved verify/review feedback carried by a full task rerun.
882
+ * This is recovery context, not a mutation of the original task contract.
883
+ */
884
+ export const dagRerunFeedbackSchema = z
885
+ .object({
886
+ schemaVersion: z.literal(1),
887
+ kind: z.literal("unresolved-terminal-feedback"),
888
+ parentRunId: z.string().min(1),
889
+ taskId: z.string().min(1),
890
+ sourceNodeId: z.string().min(1),
891
+ verdict: z.enum(["request-revision", "fail"]).optional(),
892
+ failureCategory: z.string().min(1).optional(),
893
+ feedbackText: z.string().min(1).max(12_000),
894
+ evidenceRefs: z
895
+ .array(z
896
+ .object({
897
+ nodeId: z.string().min(1),
898
+ preservedNodeRecordPath: dagRerunFeedbackEvidencePathSchema,
899
+ })
900
+ .strict())
901
+ .max(8),
902
+ attemptSummary: z
903
+ .object({
904
+ completedPasses: z.number().int().positive(),
905
+ maxPasses: z.number().int().positive(),
906
+ terminalReason: z.string().min(1).optional(),
907
+ })
908
+ .strict(),
909
+ binding: z
910
+ .object({
911
+ sourceCanonicalHash: z
912
+ .string()
913
+ .regex(/^[a-f0-9]{64}$/)
914
+ .optional(),
915
+ taskContractCanonicalHash: z
916
+ .string()
917
+ .regex(/^[a-f0-9]{64}$/)
918
+ .optional(),
919
+ status: z.enum(["exact", "changed", "unknown"]),
920
+ })
921
+ .strict(),
922
+ digest: z.string().regex(/^[a-f0-9]{64}$/),
923
+ })
924
+ .strict();
805
925
  export const dagSpecSchema = z
806
926
  .object({
807
927
  version: dagVersionSchema,
@@ -813,6 +933,28 @@ export const dagSpecSchema = z
813
933
  sourceBinding: dagSourceBindingSchema.optional(),
814
934
  /** v4 managed Task Contract binding; only valid on version 4. */
815
935
  taskContractBinding: dagTaskContractBindingSchema.optional(),
936
+ /** Optional unresolved parent-run feedback for a full task rerun. */
937
+ rerunFeedback: dagRerunFeedbackSchema.optional(),
938
+ /** Frozen backend-test artifact layout (plan A); absent = historical default testcase/ layout. */
939
+ backendTestLayout: z
940
+ .object({
941
+ testRoot: z.string(),
942
+ markdownRel: z.string(),
943
+ scriptRel: z.string(),
944
+ markdownDir: z.string(),
945
+ scriptDir: z.string(),
946
+ readmePath: z.string(),
947
+ isDefault: z.boolean(),
948
+ })
949
+ .optional(),
950
+ /** Bound user shared-setup document (plan B); absent = no shared setup references expected. */
951
+ backendTestSharedSetup: z
952
+ .object({
953
+ path: z.string(),
954
+ readPath: z.string(),
955
+ sha256: z.string(),
956
+ })
957
+ .optional(),
816
958
  outputLanguage: dagOutputLanguageSchema.optional(),
817
959
  objective: z.string().optional(),
818
960
  successCriteria: z.array(z.string()).optional(),
@@ -822,6 +964,8 @@ export const dagSpecSchema = z
822
964
  defaults: dagDefaultsSchema,
823
965
  skillsByRole: z.record(z.string(), z.array(z.string())).optional(),
824
966
  executorModels: dagExecutorModelsSchema.optional(),
967
+ /** Generation-time advisories surfaced as machine-visible warnings. */
968
+ advisories: z.array(z.string()).optional(),
825
969
  tasks: z.array(dagTaskSchema).min(1),
826
970
  })
827
971
  .superRefine((spec, ctx) => {
@@ -14,6 +14,7 @@ const GOVERNANCE_WARNING_TYPES = new Set([
14
14
  "shell-verdict-gate-multi-command-state",
15
15
  "shell-verdict-gate-handwritten-inline",
16
16
  "orphan-writer-source-binding",
17
+ "advisory",
17
18
  ]);
18
19
  const ROOT_ARTIFACT_PATH_PROBES = [
19
20
  "artifacts/修改记录.md",
@@ -32,10 +33,11 @@ export function collectGovernanceWarnings(issues) {
32
33
  return issues.filter(isGovernanceWarning);
33
34
  }
34
35
  function collectBlockingIssues(issues, options = {}) {
36
+ const nonAdvisory = issues.filter((issue) => issue.type !== "advisory");
35
37
  if (options.strictGovernance) {
36
- return [...issues];
38
+ return nonAdvisory;
37
39
  }
38
- return issues.filter((issue) => !isGovernanceWarning(issue));
40
+ return nonAdvisory.filter((issue) => !isGovernanceWarning(issue));
39
41
  }
40
42
  function normalizePath(value) {
41
43
  return value.replace(/\\/g, "/").replace(/^\.\//, "");
@@ -956,6 +958,9 @@ export function validateDagSpec(spec) {
956
958
  validateSameRankWriteSetConflicts(spec, ranks, issues);
957
959
  validateSameRankAgentAttributionRisks(spec, ranks, issues);
958
960
  validateWriterSourceBinding(spec, issues);
961
+ for (const advisory of spec.advisories ?? []) {
962
+ issues.push({ type: "advisory", message: advisory });
963
+ }
959
964
  return issues;
960
965
  }
961
966
  function validateProjectGovernanceTaskConfig(task, spec, issues) {