@tea-agent/loop-agent 0.43.0-next.16 → 0.43.0-next.18

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 (154) hide show
  1. package/CHANGELOG.md +8 -1
  2. package/dist/application/dag/args.js +8 -0
  3. package/dist/build-stamp.json +3 -3
  4. package/dist/cli/command-definitions.js +2 -0
  5. package/dist/cli/program.js +3 -1
  6. package/dist/cli/update/init-surface-notifier.js +2 -0
  7. package/dist/cli/update/policy.js +2 -0
  8. package/dist/commands/cursor-prompt.js +2 -0
  9. package/dist/commands/dag-approve.js +2 -0
  10. package/dist/commands/dag-final-verification.js +2 -0
  11. package/dist/commands/dag-init-hybrid.js +2 -0
  12. package/dist/commands/dag-reconcile-tasks.js +2 -0
  13. package/dist/commands/dag-reject.js +2 -0
  14. package/dist/commands/dag-report.js +2 -0
  15. package/dist/commands/dag-rerun.js +2 -0
  16. package/dist/commands/dag-resume.js +2 -0
  17. package/dist/commands/dag-workflow-compile.js +2 -0
  18. package/dist/commands/dag-workflow-plan.js +2 -0
  19. package/dist/commands/dag-workflow-validate.js +2 -0
  20. package/dist/commands/delegate.js +2 -0
  21. package/dist/commands/examples.js +2 -0
  22. package/dist/commands/import-prd.js +2 -0
  23. package/dist/commands/init-model-catalog.js +2 -0
  24. package/dist/commands/init.js +6 -4
  25. package/dist/commands/instructions.js +2 -0
  26. package/dist/commands/knowledge.js +2 -0
  27. package/dist/commands/loop-benchmark.js +2 -0
  28. package/dist/commands/pi-prompt.js +2 -0
  29. package/dist/commands/pi-reuse-benchmark.js +2 -0
  30. package/dist/commands/promote-run.js +2 -0
  31. package/dist/commands/stats.js +1 -1
  32. package/dist/commands/study-init.js +2 -0
  33. package/dist/commands/task-advance.js +2 -0
  34. package/dist/commands/task-source-prepare.js +2 -0
  35. package/dist/commands/worktree-create.js +2 -0
  36. package/dist/commands/worktree-remove.js +2 -0
  37. package/dist/executors/dag-pi-executor.js +25 -6
  38. package/dist/executors/shell-verification.js +3 -0
  39. package/dist/governance/exec-plans.js +6 -3
  40. package/dist/infrastructure/console/app-data.js +6 -0
  41. package/dist/shared/artifacts-core.js +2 -0
  42. package/dist/shared/git-progress.js +2 -0
  43. package/dist/shared/one-shot-prompt-args.js +2 -0
  44. package/dist/shared/package-metadata.js +6 -4
  45. package/dist/shared/pi-context-pressure/sift-bridge.js +1 -1
  46. package/dist/shared/reference-context.js +2 -0
  47. package/dist/task/contract/recover.js +6 -0
  48. package/dist/task/source-prepare/artifact-meta.js +9 -2
  49. package/dist/task/source-prepare/fragment-inventory.js +4 -1
  50. package/dist/task/source-prepare/parse-intent.js +36 -11
  51. package/dist/task/task-demand-routing.js +5 -2
  52. package/dist/worker/console/chat/browser-automation.js +468 -308
  53. package/dist/worker/console/chat/browser-routes.js +175 -57
  54. package/dist/worker/console/chat/explore-tools.js +5 -2
  55. package/dist/worker/console/chat/pi-runtime/custom-tools/browser-tools.js +115 -20
  56. package/dist/worker/console/chat/pi-runtime.js +9 -1
  57. package/dist/worker/console/chat/routes.js +1 -1
  58. package/dist/worker/console/chat/sdd-data-alignment.js +1 -1
  59. package/dist/worker/console/chat/session-mode.js +1 -1
  60. package/dist/worker/console/chat/tools.js +7 -1
  61. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C4NXyedy.js → abnfDiagram-N423BO3Z-B_UDknhR.js} +1 -1
  62. package/dist/worker/console/static/assets/{arc-NZ3qXvBI.js → arc-D6hU0drN.js} +1 -1
  63. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-COZSRvml.js → architectureDiagram-T3A2C74G-DmVikdRK.js} +1 -1
  64. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-Ck0Gxi9C.js → blockDiagram-VBNYF7ZC-DydABCRg.js} +1 -1
  65. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BX5HQ4Ox.js → c4Diagram-5PPSVZJV-DrXKC2Jl.js} +1 -1
  66. package/dist/worker/console/static/assets/channel-Drecd94a.js +1 -0
  67. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-CoviemSL.js → chunk-2GRJ4B5K-D2-qFxr7.js} +1 -1
  68. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DbOdOkbZ.js → chunk-2Q5K7J3B-ChfeF41P.js} +1 -1
  69. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BsG2pmdQ.js → chunk-5RXB4S5H-Drwrz55x.js} +1 -1
  70. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-DGdvrNm8.js → chunk-5VM5RSS4-CAQGErL7.js} +1 -1
  71. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BAy7Yd6y.js → chunk-6Q2QTUOP-DHO38w5U.js} +1 -1
  72. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-D9UA7hUb.js → chunk-GF5L2VYU-EYIN0EOI.js} +1 -1
  73. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-DPz7SZ8V.js → chunk-JWPE2WC7-CrFLKqkT.js} +1 -1
  74. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BmuEOIh7.js → chunk-KBJHAD2P-DMchffoo.js} +1 -1
  75. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CIhBWnd8.js → chunk-RYQCIY6F-DaSvtKQa.js} +1 -1
  76. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CScR6mDD.js → chunk-XXDRQBXY-DcW8W9Qj.js} +1 -1
  77. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-8Udu0t8-.js +1 -0
  78. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-8Udu0t8-.js +1 -0
  79. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BjpfWaUr.js → cose-bilkent-JH36ORCC-BUirrMEi.js} +1 -1
  80. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BFQohJgT.js → cynefin-VYW2F7L2-BXzO8iXR.js} +1 -1
  81. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-D3gzRJnS.js → cynefinDiagram-MW4NZA55-DAtgaxIn.js} +1 -1
  82. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-5eojkhoA.js → dagre-VZM6K2ZE-C1re9Noh.js} +1 -1
  83. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-CpD8LWu_.js → diagram-7IWD3JNH-N8Kj08J9.js} +1 -1
  84. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B8_4qJ2a.js → diagram-B4RE2ZJO-D6BpSLs3.js} +1 -1
  85. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-HOsXf0eu.js → diagram-LBJQPF4R-C2Vlgnl4.js} +1 -1
  86. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-BqwRwiWP.js → diagram-Q27KOJAE-CMbqtVLS.js} +1 -1
  87. package/dist/worker/console/static/assets/{diagram-UB23O5K3-BRVCNZe1.js → diagram-UB23O5K3-A770Eb-0.js} +1 -1
  88. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-Cvb1TgGh.js → ebnfDiagram-BXEA7PRR-LV2w_2pT.js} +1 -1
  89. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DS0O0qG9.js → erDiagram-JOGREHBK-bwqf56ah.js} +1 -1
  90. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-BHIRvRr3.js → flowDiagram-UKHOOZJN-BnVtHhZh.js} +1 -1
  91. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DcCJ1JWx.js → ganttDiagram-PKOTCBZU-BGVToEqc.js} +1 -1
  92. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-B9ia5ony.js → gitGraphDiagram-DS77QQ5N-DL2l7Vne.js} +1 -1
  93. package/dist/worker/console/static/assets/{index-wXlQohNs.js → index-B_V4wvXs.js} +44 -44
  94. package/dist/worker/console/static/assets/{index-B9goN3S5.css → index-DcudonhZ.css} +1 -1
  95. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-3ogpgegn.js → infoDiagram-6WML65LV-BWIpubOx.js} +1 -1
  96. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-4tgpQ5SL.js → ishikawaDiagram-WSZJBQD7-DjYBD8vv.js} +1 -1
  97. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DNfgHqFx.js → journeyDiagram-NVQOT4AX-C0_mBaHb.js} +1 -1
  98. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-Dz7ngC8z.js → kanban-definition-27J2QSJJ-Cy71zbX6.js} +1 -1
  99. package/dist/worker/console/static/assets/{linear-DI7Jv62E.js → linear-Dt3_w3Vn.js} +1 -1
  100. package/dist/worker/console/static/assets/{mermaid.core-CQA4Nzmo.js → mermaid.core-DnNOlfEP.js} +5 -5
  101. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-CtsMc8Mf.js → mindmap-definition-FAOFIHXS-DAbTspwq.js} +1 -1
  102. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-ByppE9yH.js → pegDiagram-VL7TDLO6-NNiM141p.js} +1 -1
  103. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-B5PtgFgt.js → pieDiagram-7S7Q4E2Y-CN80Z1Do.js} +1 -1
  104. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-Cq4p0k2K.js → quadrantDiagram-CIZ2JOQS-Bxyfh0JU.js} +1 -1
  105. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-DjfkyljN.js → railroadDiagram-AXF67PYL-BlWnC79M.js} +1 -1
  106. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-DzynNRSl.js → requirementDiagram-LRYGKXZP-BrEu4P_e.js} +1 -1
  107. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Cj7TCpTJ.js → sankeyDiagram-W5VNT64P-Bt70igBr.js} +1 -1
  108. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-C73C6ykA.js → sequenceDiagram-SI44F4Z6-C8DQR-r9.js} +1 -1
  109. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-BiNLgN2x.js → sizeCapture-X5ZJPWSS-DEMinJAT.js} +1 -1
  110. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-UwvjS64-.js → stateDiagram-OKZ733FA-DDdDBzQc.js} +1 -1
  111. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-CyCiS0Lt.js +1 -0
  112. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-CC7DH98r.js → swimlanes-SLNWSIFB-G2wcBs3T.js} +2 -2
  113. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-U0lhi6N0.js +8 -0
  114. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-LSBXTR70.js → timeline-definition-Z64GVDOM-Dzn4g2gJ.js} +1 -1
  115. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BAuxWwGP.js → vennDiagram-T6HMQDX7-5v2rP9oO.js} +1 -1
  116. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y--E5_gjZx.js → wardleyDiagram-T6FBY63Y-B4EOx7ew.js} +1 -1
  117. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-dJWNmyzF.js → xychartDiagram-ELKLHX3M-CQ1v8eYP.js} +1 -1
  118. package/dist/worker/console/static/index.html +2 -2
  119. package/dist/worker/console/static-src/operator-chat/slash-palette-nav.js +15 -5
  120. package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +2 -0
  121. package/dist/worker/console/static-src/prd-file-import.js +3 -1
  122. package/dist/worker/console/workspace-context.js +6 -1
  123. package/dist/worker/delivery/git-transaction.js +2 -0
  124. package/dist/worker/feature/fullstack-validate.js +2 -0
  125. package/dist/worker/loop-agent/loop-agent-client.js +8 -2
  126. package/dist/worker/materialize/harness-task-materializer.js +4 -1
  127. package/dist/worker/metrics/projector.js +1 -1
  128. package/dist/worker/observability/event-store.js +4 -1
  129. package/dist/worker/observability/read-model.js +4 -1
  130. package/dist/worker/observe/node-transparency.js +23 -1
  131. package/dist/worker/observe/routes.js +4 -0
  132. package/dist/worker/outcomes/declared-artifacts.js +2 -0
  133. package/dist/worker/preflight.js +3 -0
  134. package/dist/worker/task-spec/validate.js +4 -1
  135. package/dist/workflows/dag/decision-envelope.js +2 -0
  136. package/dist/workflows/dag/dynamic-runtime/shared.js +2 -2
  137. package/dist/workflows/dag/frontend-verification-trace.js +14 -10
  138. package/dist/workflows/dag/init-hybrid.js +105 -11
  139. package/dist/workflows/dag/interrupt-request.js +2 -0
  140. package/dist/workflows/dag/lifecycle.js +4 -0
  141. package/dist/workflows/dag/node-execution.js +2 -0
  142. package/dist/workflows/dag/reconcile-run.js +2 -0
  143. package/dist/workflows/dag/repair-artifact.js +3 -1
  144. package/dist/workflows/dag/report.js +2 -0
  145. package/dist/workflows/dag/rerun-task.js +2 -0
  146. package/dist/workflows/dag/types.js +21 -6
  147. package/dist/workflows/dag/validate.js +4 -0
  148. package/docs/governance/README.md +1 -0
  149. package/package.json +3 -1
  150. package/dist/worker/console/static/assets/channel-BxdTlb_5.js +0 -1
  151. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-_ByA1Nf8.js +0 -1
  152. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-_ByA1Nf8.js +0 -1
  153. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-nLgMZ0D3.js +0 -1
  154. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BcYqTady.js +0 -8
@@ -245,7 +245,10 @@ function checkPathBoundaries(taskSpec, errors, warnings) {
245
245
  const pathErrors = errors.slice(start).filter((error) => error.layer === "path-boundary");
246
246
  if (pathErrors.length === 0)
247
247
  return "ok";
248
- return toFailure(pathErrors[0]);
248
+ const [firstPathError] = pathErrors;
249
+ if (firstPathError === undefined)
250
+ return "ok";
251
+ return toFailure(firstPathError);
249
252
  }
250
253
  function checkVerifyCommands(taskSpec, errors) {
251
254
  if (!VERIFY_OPTIONAL_TYPES.has(taskSpec.type) &&
@@ -386,6 +386,8 @@ export function parseDagDecisionCommandArgs(mode, args) {
386
386
  let nodeId;
387
387
  for (let i = 0; i < args.length; i += 1) {
388
388
  const arg = args[i];
389
+ if (arg === undefined)
390
+ break;
389
391
  if (arg === "--run-id") {
390
392
  runId = args[++i];
391
393
  continue;
@@ -31,13 +31,13 @@ export function getNodeOutputAsJson(state, nodeId) {
31
31
  export function normalizeVerdictCandidateLine(value) {
32
32
  const trimmed = String(value).trim();
33
33
  const emphasized = trimmed.match(/^(?:`{1,3}|\*{1,3})\s*(VERDICT:[^`*]+?)\s*(?:`{1,3}|\*{1,3})$/);
34
- const line = (emphasized ? emphasized[1] : trimmed).trim();
34
+ const line = (emphasized?.[1] ?? trimmed).trim();
35
35
  // Canonicalize the colon separator and trailing sentence punctuation so
36
36
  // "VERDICT:pass", "VERDICT: pass", or "VERDICT: pass." match the declared
37
37
  // enum lines without accepting free-text verdicts. Conflicting verdicts
38
38
  // still canonicalize to distinct enum lines and remain fail-closed.
39
39
  const verdictMatch = line.match(/^VERDICT:\s*(pass|request-revision)\s*[.!?。!?]*$/i);
40
- if (verdictMatch)
40
+ if (verdictMatch?.[1])
41
41
  return `VERDICT: ${verdictMatch[1].toLowerCase()}`;
42
42
  return line;
43
43
  }
@@ -170,6 +170,8 @@ export function extractFrontendTestTitles(content) {
170
170
  }
171
171
  for (let index = 0; index < tokens.length; index += 1) {
172
172
  const token = tokens[index];
173
+ if (token === undefined)
174
+ continue;
173
175
  if (token?.type !== "identifier" ||
174
176
  !["describe", "it", "test"].includes(token.value) ||
175
177
  (tokens[index - 1]?.type === "punctuation" &&
@@ -183,15 +185,16 @@ export function extractFrontendTestTitles(content) {
183
185
  while (tokens[cursor]?.type === "punctuation" &&
184
186
  tokens[cursor]?.value === "." &&
185
187
  tokens[cursor + 1]?.type === "identifier") {
186
- if (tokens[cursor + 1]?.type === "identifier" &&
187
- configurationModifiers.has(tokens[cursor + 1].value)) {
188
+ const modifier = tokens[cursor + 1];
189
+ if (modifier === undefined)
190
+ break;
191
+ if (configurationModifiers.has(modifier.value)) {
188
192
  hasConfigurationModifier = true;
189
- if (["runIf", "skipIf"].includes(tokens[cursor + 1].value)) {
190
- conditionalModifier = tokens[cursor + 1].value;
193
+ if (["runIf", "skipIf"].includes(modifier.value)) {
194
+ conditionalModifier = modifier.value;
191
195
  }
192
196
  }
193
- if (tokens[cursor + 1]?.type === "identifier" &&
194
- nonExecutingModifiers.has(tokens[cursor + 1].value)) {
197
+ if (nonExecutingModifiers.has(modifier.value)) {
195
198
  hasNonExecutingModifier = true;
196
199
  }
197
200
  cursor += 2;
@@ -221,9 +224,10 @@ export function extractFrontendTestTitles(content) {
221
224
  index = closingParenIndex(cursor) ?? tokens.length;
222
225
  continue;
223
226
  }
224
- if (tokens[cursor + 1]?.type !== "string")
227
+ const titleToken = tokens[cursor + 1];
228
+ if (titleToken?.type !== "string")
225
229
  continue;
226
- titles.push(tokens[cursor + 1].value);
230
+ titles.push(titleToken.value);
227
231
  }
228
232
  return titles;
229
233
  }
@@ -326,9 +330,9 @@ export async function evaluateVerificationTargets(input) {
326
330
  export function verificationSymbolMatchesContent(symbol, content) {
327
331
  if (symbol.trim().toLowerCase() === "all describe blocks")
328
332
  return content.includes("describe(");
333
+ const separatorHead = symbol.split(/[((]/, 1)[0];
329
334
  const symbols = symbol.includes(" / ")
330
- ? symbol
331
- .split(/[((]/, 1)[0]
335
+ ? (separatorHead ?? symbol)
332
336
  .split("/")
333
337
  .map((item) => item.trim())
334
338
  .filter(Boolean)
@@ -814,7 +814,7 @@ export function extractSuccessCriteria(requirementMarkdown, taskId) {
814
814
  const criteria = [];
815
815
  const sectionMatch = requirementMarkdown.match(SUCCESS_CRITERIA_SECTION);
816
816
  if (sectionMatch) {
817
- for (const line of sectionMatch[2].split("\n")) {
817
+ for (const line of (sectionMatch[2] ?? "").split("\n")) {
818
818
  const trimmed = line.replace(/^[-*]\s*\[[ xX]\]\s*/, "").trim();
819
819
  if (trimmed.startsWith("-") || trimmed.startsWith("*")) {
820
820
  criteria.push(trimmed.replace(/^[-*]\s*/, "").trim());
@@ -1020,6 +1020,82 @@ function extractFrontendVerifyCommandsFromMarkdown(input) {
1020
1020
  }
1021
1021
  return { staticCommands, behaviorCommands };
1022
1022
  }
1023
+ /**
1024
+ * Resolve the static verification commands the frontend DAG will freeze.
1025
+ *
1026
+ * The frontend contract is "the implementation must be statically verifiable -
1027
+ * never a no-op". It used to mean exactly `npm run build`, which made the whole
1028
+ * run fail at the verify node (8 of 11) for any project that legitimately has no
1029
+ * build step: a zero-dependency Node app, a static-site repo, a plain-ESM
1030
+ * browser module. The failure landed *after* `frontend-implement-pi` had already
1031
+ * delivered correct, test-passing work, so the run reported failure for a project
1032
+ * shape that was never going to pass. That is a meaningless failure and a
1033
+ * gratuitous loss of trust, not a safety property.
1034
+ *
1035
+ * Order, keeping every choice a real check:
1036
+ * 1. `npm run build` when the project defines it - unchanged for every project
1037
+ * that already worked.
1038
+ * 2. static commands the task declared via `--verify`. These were already being
1039
+ * classified onto the static lane and then discarded, so an operator who
1040
+ * declared `--verify typecheck:npm run typecheck` was silently ignored.
1041
+ * 3. the project's own `typecheck` / `check` script.
1042
+ * 4. otherwise the historical `npm run build` is kept. That still fails at the
1043
+ * verify node, exactly as before this change - it is deliberately NOT a new
1044
+ * generation-time error. Introducing one would turn a late failure into an
1045
+ * early one for projects that never declared static verification, which is
1046
+ * more failure, not less; the operator can still declare one via `--verify`.
1047
+ *
1048
+ * Lint is deliberately never chosen: the frontend topology hard-codes
1049
+ * `lintShellCommands = []` so existing lint debt stays outside this workflow, and
1050
+ * auto-selecting a lint script would turn that debt into a fresh failure.
1051
+ */
1052
+ const FRONTEND_STATIC_SCRIPT_PREFERENCE = ["typecheck", "check"];
1053
+ async function resolveFrontendStaticCommands(input) {
1054
+ let scripts = {};
1055
+ try {
1056
+ const manifest = JSON.parse(await readFile(path.join(input.repoRoot, "package.json"), "utf8"));
1057
+ scripts = manifest.scripts ?? {};
1058
+ }
1059
+ catch (error) {
1060
+ if (error.code !== "ENOENT")
1061
+ throw error;
1062
+ }
1063
+ const hasScript = (name) => {
1064
+ const script = scripts[name];
1065
+ return typeof script === "string" && script.trim() !== "";
1066
+ };
1067
+ if (hasScript("build")) {
1068
+ return [
1069
+ {
1070
+ label: "build",
1071
+ args: ["bash", "-lc", "npm run build"],
1072
+ cwd: input.repoRoot,
1073
+ },
1074
+ ];
1075
+ }
1076
+ if (input.explicitStatic.length > 0)
1077
+ return input.explicitStatic;
1078
+ for (const name of FRONTEND_STATIC_SCRIPT_PREFERENCE) {
1079
+ if (!hasScript(name))
1080
+ continue;
1081
+ return [
1082
+ {
1083
+ label: name,
1084
+ args: ["bash", "-lc", `npm run ${name}`],
1085
+ cwd: input.repoRoot,
1086
+ },
1087
+ ];
1088
+ }
1089
+ // Nothing static is discoverable: keep the historical command rather than
1090
+ // inventing a new failure. See the doc comment above for why.
1091
+ return [
1092
+ {
1093
+ label: "build",
1094
+ args: ["bash", "-lc", "npm run build"],
1095
+ cwd: input.repoRoot,
1096
+ },
1097
+ ];
1098
+ }
1023
1099
  /** Reuse the project's test entrypoint; never install a runner for an untested project. */
1024
1100
  async function resolveFrontendExistingTests(sources) {
1025
1101
  const repoRoot = sources.repoRoot ?? process.cwd();
@@ -1425,7 +1501,7 @@ function isFullSuiteVerifyCommand(command) {
1425
1501
  (rest.length === 2 && rest[0] === "run" && rest[1] === "test"));
1426
1502
  }
1427
1503
  if (["bash", "sh"].includes(executable) && rest.length === 1) {
1428
- return /(?:^|\/)scripts\/(?:ci|check-repo)\.sh$/i.test(rest[0].replace(/\\/g, "/"));
1504
+ return /(?:^|\/)scripts\/(?:ci|check-repo)\.sh$/i.test((rest[0] ?? "").replace(/\\/g, "/"));
1429
1505
  }
1430
1506
  return (rest.length === 0 &&
1431
1507
  /(?:^|\/)scripts\/(?:ci|check-repo)\.sh$/i.test((args[index] ?? "").replace(/\\/g, "/")));
@@ -2597,11 +2673,27 @@ function resolveFrontendMockContextBlock(sources) {
2597
2673
  parts.push("Implement the selected Mock strategy within authorized paths and keep production-default-off isolation. Mock behavior verification is deferred to frontend-test; it is not an implementation gate.");
2598
2674
  }
2599
2675
  if (mode === "not-required") {
2600
- parts.push("Generation-time evidence does not require Mock. The assessment must still use contract/scout evidence: select not-needed when Mock is intentionally skipped, or select a safe Mock strategy if project evidence supports one.");
2676
+ // The assessment sentence is deliberately split: every branch keeps the
2677
+ // "select not-needed when Mock is intentionally skipped" guidance, because
2678
+ // that is the correct action in both cases, and only the permissive
2679
+ // "or select a safe Mock strategy if project evidence supports one" tail is
2680
+ // withheld when the allow-list is frozen.
2681
+ //
2682
+ // Emitting both was a direct instruction conflict, and it cost a whole
2683
+ // frontend run (dogfood R2): the model resolved it the permissive way, the
2684
+ // typed tool accepted ten `native` records with ok:true, and the
2685
+ // design-policy shell rejected the contract ~90s later, killing 8
2686
+ // downstream nodes. The fix belongs here - stop contradicting ourselves -
2687
+ // rather than in extra enforcement that would leave the model guessing.
2688
+ const mockAssessment = "Generation-time evidence does not require Mock. The assessment must still use contract/scout evidence: select not-needed when Mock is intentionally skipped";
2601
2689
  if (frontendMockStrategyMustBeNotNeeded(sources)) {
2690
+ parts.push(`${mockAssessment}.`);
2602
2691
  parts.push('Auto mode has no confirmed project Mock capability. The structured contract must set mockApi.strategy to "not-needed". Keep the real request path as the default, record any unproved backend behavior as Real Integration Gap, and do not add Mock files or dependencies within this run.');
2603
2692
  parts.push('HARD CONSTRAINT (frozen at generation time): this DAG allows only mockApi.strategy "not-needed"; the prewrite gate rejects any other strategy. If project governance (openspec / ai_workspace / decision records, e.g. a DEC rule requiring native) demands Mock-backed verification, that is a generation-time contract gap, not a plan-revision defect: confirm the project Mock capability and policy: "required" in task.json and regenerate the DAG.');
2604
2693
  }
2694
+ else {
2695
+ parts.push(`${mockAssessment}, or select a safe Mock strategy if project evidence supports one.`);
2696
+ }
2605
2697
  }
2606
2698
  if (mode === "blocked") {
2607
2699
  parts.push("Mock contract is blocked. The DAG must stop before any write node executes.");
@@ -3125,7 +3217,7 @@ async function buildFrontendHybridDagFromTask(sources) {
3125
3217
  "Frontend planning must consume the read-only Mock assessment strategy produced after scouting; MOCK_STRATEGY: blocked must not pass the deterministic Mock contract gate.",
3126
3218
  "Mock implementations must preserve the real request path as the default, require explicit test/dev activation, and never rely on commenting out the real request.",
3127
3219
  "Mock-backed behavior evidence proves only the documented frontend contract, never real API integration.",
3128
- "frontend-implementation requires npm run build and existing affected unit tests. Update affected tests with the implementation; do not install a test framework when absent. Broader browser/visual testing may be deferred; build success is never test success.",
3220
+ "frontend-implementation requires the frozen static verification command and existing affected unit tests. Update affected tests with the implementation; do not install a test framework when absent. Broader browser/visual testing may be deferred; static-verification success is never test success.",
3129
3221
  "Frontend closeout renders only from committed facts; a weak status (failed / not-run / baseline-debt / mock-backed / pending) can never be rewritten into a stronger one (passed / real-integrated).",
3130
3222
  `Frontend risk classification: ${frontendRisk.selectedRisk} — ${frontendRisk.reason}`,
3131
3223
  frontendRisk.forceFullGates
@@ -3141,13 +3233,15 @@ async function buildFrontendHybridDagFromTask(sources) {
3141
3233
  : "Mock contract is blocked by deterministic generation-time Mock safety constraints.";
3142
3234
  return buildBlockedFrontendMockDag(frontendSources, readOnlyPaths, forbiddenPaths, globalConstraints, blockedReason);
3143
3235
  }
3144
- // Build is mandatory; existing or explicitly declared tests remain required.
3145
- // Keep a real failing command when build is unavailable; never use a no-op.
3146
- const buildCommands = [{
3147
- label: "build",
3148
- args: ["bash", "-lc", "npm run build"],
3149
- cwd: sources.repoRoot ?? process.cwd(),
3150
- }];
3236
+ // Static verification is mandatory; existing or explicitly declared tests remain
3237
+ // required. Never use a no-op - but never freeze a command the project cannot run
3238
+ // either. See resolveFrontendStaticCommands for why `npm run build` is not the
3239
+ // only acceptable answer.
3240
+ const explicitFrontendCommands = buildExplicitFrontendVerifyCommands(sources.taskConfig, sources.repoRoot ?? process.cwd());
3241
+ const buildCommands = await resolveFrontendStaticCommands({
3242
+ repoRoot: sources.repoRoot ?? process.cwd(),
3243
+ explicitStatic: explicitFrontendCommands.staticCommands,
3244
+ });
3151
3245
  const staticShellCommands = buildVerifyShellCommands({
3152
3246
  repoRoot: sources.repoRoot ?? process.cwd(), commands: buildCommands, fallbackCommands: [],
3153
3247
  });
@@ -454,6 +454,8 @@ export function parseDagRequestInterruptArgs(args) {
454
454
  for (let i = 0; i < args.length; i += 1) {
455
455
  const cursor = { value: i };
456
456
  const arg = args[i];
457
+ if (arg === undefined)
458
+ break;
457
459
  const runIdValue = readFlag(args, cursor, "run-id");
458
460
  if (runIdValue !== undefined) {
459
461
  runId = runIdValue;
@@ -684,6 +684,8 @@ export function parseDagStatusArgs(args) {
684
684
  let runId;
685
685
  for (let i = 0; i < args.length; i += 1) {
686
686
  const arg = args[i];
687
+ if (arg === undefined)
688
+ break;
687
689
  if (arg === "--run-id") {
688
690
  runId = args[++i];
689
691
  continue;
@@ -712,6 +714,8 @@ export function parseDagDoctorArgs(args) {
712
714
  let markdown = false;
713
715
  for (let i = 0; i < args.length; i += 1) {
714
716
  const arg = args[i];
717
+ if (arg === undefined)
718
+ break;
715
719
  if (arg === "--run-id") {
716
720
  runId = args[++i];
717
721
  if (!runId || runId.startsWith("-")) {
@@ -1075,6 +1075,8 @@ function validateRepairArtifactGateBeforeShell(input) {
1075
1075
  if (!gate)
1076
1076
  return;
1077
1077
  const upstream = input.state.nodes[gate.fromNodeId];
1078
+ if (!upstream)
1079
+ return;
1078
1080
  const parsed = parseSupervisorRepairArtifact(upstream);
1079
1081
  if (!parsed.ok) {
1080
1082
  throw new Error(`repair artifact gate failed: ${parsed.reason}`);
@@ -7,6 +7,8 @@ export function parseDagReconcileRunArgs(args) {
7
7
  let reason;
8
8
  for (let index = 0; index < args.length; index += 1) {
9
9
  const arg = args[index];
10
+ if (arg === undefined)
11
+ break;
10
12
  if (arg === "--run-id")
11
13
  runId = args[++index];
12
14
  else if (arg.startsWith("--run-id="))
@@ -330,7 +330,9 @@ export function resolveRepairTaskForGate(input) {
330
330
  }
331
331
  const candidates = directDownstream.filter(isGovernedRepairWriter);
332
332
  if (candidates.length === 1) {
333
- return { ok: true, repairTask: candidates[0] };
333
+ const [onlyCandidate] = candidates;
334
+ if (onlyCandidate)
335
+ return { ok: true, repairTask: onlyCandidate };
334
336
  }
335
337
  const candidateIds = directDownstream.map((task) => task.id).join(", ") || "none";
336
338
  if (candidates.length === 0) {
@@ -449,6 +449,8 @@ export async function buildDagRunReportEntry(input) {
449
449
  const rankNodes = input.state.ranks[rank] ?? [];
450
450
  for (let order = 0; order < rankNodes.length; order += 1) {
451
451
  const nodeId = rankNodes[order];
452
+ if (nodeId === undefined)
453
+ continue;
452
454
  const node = input.state.nodes[nodeId];
453
455
  if (!node)
454
456
  continue;
@@ -26,6 +26,8 @@ export function parseDagRerunTaskArgs(args) {
26
26
  let recoveryOperationId;
27
27
  for (let index = 0; index < args.length; index += 1) {
28
28
  const arg = args[index];
29
+ if (arg === undefined)
30
+ break;
29
31
  if (arg === "--run-id")
30
32
  parentRunId = args[++index];
31
33
  else if (arg.startsWith("--run-id="))
@@ -192,6 +192,25 @@ export const dagJsonArtifactGateSchema = z.object({
192
192
  const dagFrontendNodeIdSchema = z
193
193
  .string()
194
194
  .regex(/^[a-z][a-z0-9-]*$/, "frontend node id must be kebab-case");
195
+ /**
196
+ * Requirement id accepted in a generated DAG's `requiredRequirementIds`.
197
+ *
198
+ * These ids are lifted verbatim from the task requirement ledger, which mirrors
199
+ * whatever the PRD wrote. A PRD that writes `AC1` (no hyphen) therefore yields an
200
+ * id this schema rejects, and `dagSpecSchema.parse` surfaces zod's raw issue array
201
+ * - so the operator saw only `"message": "Invalid"` plus a JSON pointer and had no
202
+ * way to learn the constraint. The message below carries it into that error.
203
+ *
204
+ * This is deliberately a fail-closed check, not a normalizer: `AC1` is
205
+ * canonicalized to `AC-1` upstream by `normalizeAcceptanceId` in the semantic
206
+ * intake, so every workflow reads the same ids. What remains here is the guard
207
+ * for genuinely malformed input - and because `dagSpecSchema.parse` surfaces
208
+ * zod's raw issue array, the message below is what makes that guard
209
+ * self-explanatory instead of `"message": "Invalid"` plus a JSON pointer.
210
+ */
211
+ const dagRequirementIdSchema = z
212
+ .string()
213
+ .regex(/^(?:REQ|BR|AC)-[A-Z0-9]+(?:-[A-Z0-9]+)*$/, 'requirement id must be "<REQ|BR|AC>-<SEGMENT>" with a hyphen after the prefix (e.g. AC-1, AC-FE-001, REQ-SRC-ABC123); ids come verbatim from the task requirement ledger, so a PRD writing "AC1" must write "AC-1"');
195
214
  const openspecCandidatePathSchema = z
196
215
  .string()
197
216
  .refine((candidate) => !candidate.includes("\\") &&
@@ -210,9 +229,7 @@ export const dagFrontendPrewriteGateSchema = z.object({
210
229
  planFallbackFromNodeIds: z.array(dagFrontendNodeIdSchema).default([]),
211
230
  reviewFromNodeId: dagFrontendNodeIdSchema,
212
231
  reviewFallbackFromNodeIds: z.array(dagFrontendNodeIdSchema).default([]),
213
- requiredRequirementIds: z
214
- .array(z.string().regex(/^(?:REQ|BR|AC)-[A-Z0-9]+(?:-[A-Z0-9]+)*$/))
215
- .default([]),
232
+ requiredRequirementIds: z.array(dagRequirementIdSchema).default([]),
216
233
  allowedMockStrategies: z
217
234
  .array(z.enum(["native", "browser-intercept", "request-adapter", "not-needed"]))
218
235
  .min(1),
@@ -422,9 +439,7 @@ export const dagFrontendDesignPolicySchema = z
422
439
  .object({
423
440
  schemaVersion: z.literal(1),
424
441
  planFromNodeId: dagFrontendNodeIdSchema,
425
- requiredRequirementIds: z
426
- .array(z.string().regex(/^(?:REQ|BR|AC)-[A-Z0-9]+(?:-[A-Z0-9]+)*$/))
427
- .default([]),
442
+ requiredRequirementIds: z.array(dagRequirementIdSchema).default([]),
428
443
  allowedMockStrategies: z
429
444
  .array(z.enum(["native", "browser-intercept", "request-adapter", "not-needed"]))
430
445
  .min(1),
@@ -222,6 +222,8 @@ function validateRepairArtifactGateConfig(task, spec, issues) {
222
222
  return;
223
223
  }
224
224
  const hardVerify = hardVerifyCandidates[0];
225
+ if (!hardVerify)
226
+ return;
225
227
  const descendantIds = new Set();
226
228
  const queue = [hardVerify.id];
227
229
  while (queue.length > 0) {
@@ -411,6 +413,8 @@ function validateSameRankWriteSetConflicts(spec, ranks, issues) {
411
413
  for (let j = i + 1; j < exclusiveWriters.length; j += 1) {
412
414
  const left = exclusiveWriters[i];
413
415
  const right = exclusiveWriters[j];
416
+ if (!left || !right)
417
+ continue;
414
418
  const leftSet = left.writeSet ?? [];
415
419
  const rightSet = right.writeSet ?? [];
416
420
  for (const leftEntry of leftSet) {
@@ -10,6 +10,7 @@
10
10
  - [`harness-methodology-tdd.md`](harness-methodology-tdd.md):测试先行的方法与边界。
11
11
  - [`harness-methodology-verification.md`](harness-methodology-verification.md):如何用新鲜命令结果支持完成声明。
12
12
  - [`harness-methodology-debugging.md`](harness-methodology-debugging.md):系统化定位故障,不用猜测代替证据。
13
+ - [`defect-ledger.md`](defect-ledger.md):真实缺陷台账与优先级规则——治理目标先从真故障选,再看代理指标排名。
13
14
  - [`document-review-policy.md`](document-review-policy.md):文档 freshness、`reviewTier` 与 `lastReviewed` 更新边界。
14
15
  - [`lightweight-github-collaboration.md`](lightweight-github-collaboration.md):短分支、简短 PR、CI 与 Squash Merge 的轻量协作规则。
15
16
  - [`研发模式.md`](研发模式.md):Feature 团队推进、交付和知识回写方法论。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tea-agent/loop-agent",
3
- "version": "0.43.0-next.16",
3
+ "version": "0.43.0-next.18",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "loop-agent": "bin/loop-agent.js",
@@ -84,6 +84,7 @@
84
84
  "katex": "^0.16.47",
85
85
  "mermaid": "^11.16.1",
86
86
  "node-pty": "^1.2.0-beta.15",
87
+ "playwright-core": "^1.63.0",
87
88
  "rehype-highlight": "^7.0.2",
88
89
  "rehype-katex": "^7.0.1",
89
90
  "remark-math": "^6.0.0",
@@ -103,6 +104,7 @@
103
104
  "@types/react-dom": "^19.1.6",
104
105
  "@types/semver": "^7.7.1",
105
106
  "@vitest/coverage-v8": "^4.1.11",
107
+ "fast-check": "^4.10.0",
106
108
  "react": "^19.1.0",
107
109
  "react-dom": "^19.1.0",
108
110
  "react-markdown": "^10.1.0",
@@ -1 +0,0 @@
1
- import{ag as o,ah as n}from"./mermaid.core-CQA4Nzmo.js";const t=(a,r)=>o.lang.round(n.parse(a)[r]);export{t as c};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as e,C as t}from"./chunk-GF5L2VYU-D9UA7hUb.js";import{_ as i}from"./mermaid.core-CQA4Nzmo.js";import"./chunk-5VM5RSS4-DGdvrNm8.js";import"./chunk-XXDRQBXY-CScR6mDD.js";import"./chunk-KBJHAD2P-BmuEOIh7.js";import"./chunk-2GRJ4B5K-CoviemSL.js";import"./index-wXlQohNs.js";var n={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{n as diagram};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as e,C as t}from"./chunk-GF5L2VYU-D9UA7hUb.js";import{_ as i}from"./mermaid.core-CQA4Nzmo.js";import"./chunk-5VM5RSS4-DGdvrNm8.js";import"./chunk-XXDRQBXY-CScR6mDD.js";import"./chunk-KBJHAD2P-BmuEOIh7.js";import"./chunk-2GRJ4B5K-CoviemSL.js";import"./index-wXlQohNs.js";var n={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{n as diagram};
@@ -1 +0,0 @@
1
- import{s as r,b as e,a,S as s}from"./chunk-5RXB4S5H-BsG2pmdQ.js";import{_ as i}from"./mermaid.core-CQA4Nzmo.js";import"./chunk-XXDRQBXY-CScR6mDD.js";import"./chunk-KBJHAD2P-BmuEOIh7.js";import"./chunk-2GRJ4B5K-CoviemSL.js";import"./index-wXlQohNs.js";var u={parser:a,get db(){return new s(2)},renderer:e,styles:r,init:i(t=>{t.state||(t.state={}),t.state.arrowMarkerAbsolute=t.arrowMarkerAbsolute},"init")};export{u as diagram};
@@ -1,8 +0,0 @@
1
- import{c as r,s as e}from"./flowDiagram-UKHOOZJN-BHIRvRr3.js";import{_ as a}from"./mermaid.core-CQA4Nzmo.js";import"./chunk-5VM5RSS4-DGdvrNm8.js";import"./chunk-XXDRQBXY-CScR6mDD.js";import"./chunk-KBJHAD2P-BmuEOIh7.js";import"./chunk-2GRJ4B5K-CoviemSL.js";import"./channel-BxdTlb_5.js";import"./index-wXlQohNs.js";var o=a(t=>`${e(t)}
2
- .swimlane.cluster rect {
3
- stroke: ${t.clusterBorder} !important;
4
- }
5
- [data-look="neo"].cluster rect {
6
- filter: none;
7
- }
8
- `,"getStyles"),s=o,n=r({defaultLayout:"swimlane",styles:s});export{n as diagram};