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

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 (161) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +60 -2308
  3. package/README.md +7 -0
  4. package/dist/application/task-lifecycle/advance.js +38 -7
  5. package/dist/build-stamp.json +3 -3
  6. package/dist/commands/init.js +13 -9
  7. package/dist/commands/task-advance.js +32 -0
  8. package/dist/executors/dag-pi-executor.js +1615 -188
  9. package/dist/executors/pi-executor.js +85 -3
  10. package/dist/executors/pi-sdk-executor.js +18 -0
  11. package/dist/executors/shell-executor.js +92 -7
  12. package/dist/shared/backend-dogfood-preflight.js +47 -0
  13. package/dist/shared/dag-failure-category.js +3 -0
  14. package/dist/shared/operator/capabilities.js +180 -0
  15. package/dist/task/config-types.js +29 -0
  16. package/dist/task/contract/project.js +9 -0
  17. package/dist/task/contract/schema.js +17 -0
  18. package/dist/task/source-prepare/build-draft.js +60 -0
  19. package/dist/worker/console/chat/pi-mode-loop-isolation.js +155 -0
  20. package/dist/worker/console/chat/pi-runtime/custom-tools/operator-tools.js +24 -16
  21. package/dist/worker/console/chat/pi-runtime/custom-tools/scheduled-goal-tools.js +22 -0
  22. package/dist/worker/console/chat/pi-runtime/progressive-tools.js +195 -0
  23. package/dist/worker/console/chat/pi-runtime.js +86 -27
  24. package/dist/worker/console/chat/routes.js +19 -0
  25. package/dist/worker/console/chat/scheduled-goal-booking.js +59 -0
  26. package/dist/worker/console/chat/scheduled-goal-delivery.js +27 -0
  27. package/dist/worker/console/chat/scheduled-goal-request.js +190 -0
  28. package/dist/worker/console/chat/session-mode.js +7 -3
  29. package/dist/worker/console/chat/session-store.js +42 -9
  30. package/dist/worker/console/chat/tool-preview.js +115 -0
  31. package/dist/worker/console/chat/turn-order.js +13 -0
  32. package/dist/worker/console/chat/turn-process.js +32 -30
  33. package/dist/worker/console/operator-actions.js +50 -0
  34. package/dist/worker/console/prd-intake-bridge.js +54 -1
  35. package/dist/worker/console/scheduled-goal-host.js +98 -0
  36. package/dist/worker/console/scheduled-goal-operation-adapter.js +127 -0
  37. package/dist/worker/console/server.js +15 -0
  38. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-B_UDknhR.js → abnfDiagram-N423BO3Z-8-j6y-sd.js} +1 -1
  39. package/dist/worker/console/static/assets/{arc-D6hU0drN.js → arc-eoQiMvuk.js} +1 -1
  40. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DmVikdRK.js → architectureDiagram-T3A2C74G-C4A3uMcI.js} +1 -1
  41. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-DydABCRg.js → blockDiagram-VBNYF7ZC-D4zD2F-Q.js} +1 -1
  42. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DrXKC2Jl.js → c4Diagram-5PPSVZJV-j1RkJziL.js} +1 -1
  43. package/dist/worker/console/static/assets/channel-ChE7y-cx.js +1 -0
  44. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-D2-qFxr7.js → chunk-2GRJ4B5K-YBHmhik1.js} +1 -1
  45. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-ChfeF41P.js → chunk-2Q5K7J3B-COfWyo9P.js} +1 -1
  46. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-Drwrz55x.js → chunk-5RXB4S5H-I99OUkHY.js} +1 -1
  47. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CAQGErL7.js → chunk-5VM5RSS4-XKxoJNJ4.js} +1 -1
  48. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DHO38w5U.js → chunk-6Q2QTUOP-DLT_cYx1.js} +1 -1
  49. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-EYIN0EOI.js → chunk-GF5L2VYU-CxcKZ9mV.js} +1 -1
  50. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-CrFLKqkT.js → chunk-JWPE2WC7-V1EqXdjY.js} +1 -1
  51. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DMchffoo.js → chunk-KBJHAD2P-DebTtdFp.js} +1 -1
  52. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DaSvtKQa.js → chunk-RYQCIY6F-B-ivNRDf.js} +1 -1
  53. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-DcW8W9Qj.js → chunk-XXDRQBXY-DC_Ds11b.js} +1 -1
  54. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C01TCf2X.js +1 -0
  55. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C01TCf2X.js +1 -0
  56. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BUirrMEi.js → cose-bilkent-JH36ORCC-sWEqKwIb.js} +1 -1
  57. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BXzO8iXR.js → cynefin-VYW2F7L2-BXa_dcu4.js} +1 -1
  58. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-DAtgaxIn.js → cynefinDiagram-MW4NZA55-C-Mle84F.js} +1 -1
  59. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-C1re9Noh.js → dagre-VZM6K2ZE-CXPDBITe.js} +1 -1
  60. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-N8Kj08J9.js → diagram-7IWD3JNH-CC-WJQfa.js} +1 -1
  61. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-D6BpSLs3.js → diagram-B4RE2ZJO-CDAV5Vs4.js} +1 -1
  62. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-C2Vlgnl4.js → diagram-LBJQPF4R-CmFiAcNz.js} +1 -1
  63. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-CMbqtVLS.js → diagram-Q27KOJAE-DPBZHuyn.js} +1 -1
  64. package/dist/worker/console/static/assets/{diagram-UB23O5K3-A770Eb-0.js → diagram-UB23O5K3-jUlm_Ds3.js} +1 -1
  65. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-LV2w_2pT.js → ebnfDiagram-BXEA7PRR-DWhQ3mfY.js} +1 -1
  66. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-bwqf56ah.js → erDiagram-JOGREHBK-Tr2gMqet.js} +1 -1
  67. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-BnVtHhZh.js → flowDiagram-UKHOOZJN-DJjVQHPA.js} +1 -1
  68. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-BGVToEqc.js → ganttDiagram-PKOTCBZU-D74dQ4u0.js} +1 -1
  69. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-DL2l7Vne.js → gitGraphDiagram-DS77QQ5N-DwW0tZ0X.js} +1 -1
  70. package/dist/worker/console/static/assets/index-BWkIfcrK.css +1 -0
  71. package/dist/worker/console/static/assets/{index-B_V4wvXs.js → index-Cdkvw_H6.js} +97 -97
  72. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-BWIpubOx.js → infoDiagram-6WML65LV-ILCbxJyb.js} +1 -1
  73. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-DjYBD8vv.js → ishikawaDiagram-WSZJBQD7-DeuWBQ89.js} +1 -1
  74. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-C0_mBaHb.js → journeyDiagram-NVQOT4AX-CF5ih8Fk.js} +1 -1
  75. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-Cy71zbX6.js → kanban-definition-27J2QSJJ-C7yOSuRO.js} +1 -1
  76. package/dist/worker/console/static/assets/{linear-Dt3_w3Vn.js → linear-BDZ9riWi.js} +1 -1
  77. package/dist/worker/console/static/assets/{mermaid.core-DnNOlfEP.js → mermaid.core-7pKqYtpZ.js} +5 -5
  78. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-DAbTspwq.js → mindmap-definition-FAOFIHXS-LeJDybSU.js} +1 -1
  79. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-NNiM141p.js → pegDiagram-VL7TDLO6-BJvT3pMD.js} +1 -1
  80. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-CN80Z1Do.js → pieDiagram-7S7Q4E2Y-_rGqpMan.js} +1 -1
  81. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-Bxyfh0JU.js → quadrantDiagram-CIZ2JOQS-DPcSv5aA.js} +1 -1
  82. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BlWnC79M.js → railroadDiagram-AXF67PYL-Drxx4hkJ.js} +1 -1
  83. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-BrEu4P_e.js → requirementDiagram-LRYGKXZP-BCTUdU4z.js} +1 -1
  84. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Bt70igBr.js → sankeyDiagram-W5VNT64P-B6wzbZmB.js} +1 -1
  85. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-C8DQR-r9.js → sequenceDiagram-SI44F4Z6-BGt8d3QQ.js} +1 -1
  86. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DEMinJAT.js → sizeCapture-X5ZJPWSS-7nlhJKo7.js} +1 -1
  87. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-DDdDBzQc.js → stateDiagram-OKZ733FA-Cv2stqMA.js} +1 -1
  88. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DC7V5vcq.js +1 -0
  89. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-G2wcBs3T.js → swimlanes-SLNWSIFB-qDAo4Yc1.js} +2 -2
  90. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Bwy4QUTO.js +8 -0
  91. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Dzn4g2gJ.js → timeline-definition-Z64GVDOM-CKbDNKTr.js} +1 -1
  92. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-5v2rP9oO.js → vennDiagram-T6HMQDX7-DI-9EHic.js} +1 -1
  93. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-B4EOx7ew.js → wardleyDiagram-T6FBY63Y-BrzzRDpX.js} +1 -1
  94. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CQ1v8eYP.js → xychartDiagram-ELKLHX3M-BclH5hGh.js} +1 -1
  95. package/dist/worker/console/static/index.html +2 -2
  96. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +41 -11
  97. package/dist/worker/console/static-src/operator-chat/compaction-message.js +3 -17
  98. package/dist/worker/console/static-src/operator-chat/timeline-merge.js +29 -0
  99. package/dist/worker/console/static-src/operator-chat/useChatThread.js +2 -5
  100. package/dist/worker/console/workspace-context.js +11 -0
  101. package/dist/worker/observe/static/operator-chrome.js +3 -1
  102. package/dist/worker/scheduler/scheduled-goal-dispatch.js +117 -0
  103. package/dist/worker/scheduler/scheduled-goal-evidence.js +167 -0
  104. package/dist/worker/scheduler/scheduled-goal-recovery.js +62 -0
  105. package/dist/worker/scheduler/scheduled-goal-store.js +699 -0
  106. package/dist/worker/scheduler/scheduled-goal-supervisor.js +132 -0
  107. package/dist/worker/scheduler/scheduled-goal-time.js +102 -0
  108. package/dist/worker/scheduler/scheduled-goal-types.js +95 -0
  109. package/dist/workflows/dag/backend-test-markdown-workflow.js +6 -24
  110. package/dist/workflows/dag/backend-test-plan-protocol.js +82 -5
  111. package/dist/workflows/dag/dag-retry-schema.js +11 -0
  112. package/dist/workflows/dag/frontend-closeout.js +4 -2
  113. package/dist/workflows/dag/frontend-committed-facts.js +461 -0
  114. package/dist/workflows/dag/frontend-durable-tools.js +15 -3
  115. package/dist/workflows/dag/frontend-implementation-contract.js +365 -8
  116. package/dist/workflows/dag/frontend-plan-canary.js +53 -0
  117. package/dist/workflows/dag/frontend-plan-decision-contract.js +803 -0
  118. package/dist/workflows/dag/frontend-plan-render.js +0 -2
  119. package/dist/workflows/dag/frontend-prewrite-gate.js +3 -3
  120. package/dist/workflows/dag/frontend-provider-capability-matrix.js +8 -61
  121. package/dist/workflows/dag/frontend-recovery-run.js +57 -0
  122. package/dist/workflows/dag/frontend-review-context.js +43 -70
  123. package/dist/workflows/dag/frontend-risk.js +92 -10
  124. package/dist/workflows/dag/frontend-session-budget.js +117 -3
  125. package/dist/workflows/dag/frontend-shape.js +11 -55
  126. package/dist/workflows/dag/frontend-test-execution-evidence.js +35 -10
  127. package/dist/workflows/dag/frontend-typed-event-store.js +32 -25
  128. package/dist/workflows/dag/frontend-verification-trace.js +11 -2
  129. package/dist/workflows/dag/frontend-writer-admission.js +3 -47
  130. package/dist/workflows/dag/frontend-writer-status.js +0 -23
  131. package/dist/workflows/dag/init-hybrid.js +182 -185
  132. package/dist/workflows/dag/lifecycle.js +7 -2
  133. package/dist/workflows/dag/node-execution.js +48 -13
  134. package/dist/workflows/dag/rerun-plan.js +10 -0
  135. package/dist/workflows/dag/rerun-task.js +77 -1
  136. package/dist/workflows/dag/retry-policy.js +18 -0
  137. package/dist/workflows/dag/scheduler.js +2 -4
  138. package/dist/workflows/dag/types.js +23 -0
  139. package/docs/README.md +1 -0
  140. package/docs/init-surface.manifest.json +1 -0
  141. package/docs/operations/README.md +2 -0
  142. package/docs/templates/README.md +1 -1
  143. package/docs/templates/agent-dag.schema.json +2 -2
  144. package/docs/templates/backend-test-dag.json +14 -10
  145. package/docs/templates/frontend-implementation-contract.schema.json +0 -7
  146. package/docs/templates/frontend-implementation-dag.json +8 -9
  147. package/package.json +6 -3
  148. package/skills/frontend-bounded-implement/SKILL.md +3 -4
  149. package/skills/frontend-design-review/SKILL.md +7 -12
  150. package/skills/frontend-design-review/references/review-checklist.md +7 -7
  151. package/skills/frontend-plan/SKILL.md +8 -18
  152. package/skills/frontend-plan/references/decision-contract.md +19 -26
  153. package/skills/frontend-review/SKILL.md +21 -25
  154. package/skills/frontend-review/references/review-findings.md +48 -24
  155. package/dist/worker/console/static/assets/channel-Drecd94a.js +0 -1
  156. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-8Udu0t8-.js +0 -1
  157. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-8Udu0t8-.js +0 -1
  158. package/dist/worker/console/static/assets/index-DcudonhZ.css +0 -1
  159. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-CyCiS0Lt.js +0 -1
  160. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-U0lhi6N0.js +0 -8
  161. package/dist/workflows/dag/frontend-shadow-dual-write.js +0 -975
@@ -9,6 +9,7 @@
9
9
  */
10
10
  import { createHash } from "node:crypto";
11
11
  import { z } from "zod";
12
+ import { collectFrontendSharedSignals, isNarrowProductSurface } from "./frontend-risk.js";
12
13
  /** Fixture-only node counts. They are NOT the authoritative 15/13/11
13
14
  * topology: split-required has no count. */
14
15
  export const FRONTEND_SHAPE_NODE_COUNTS = {
@@ -17,16 +18,6 @@ export const FRONTEND_SHAPE_NODE_COUNTS = {
17
18
  standard: 11,
18
19
  high: 11,
19
20
  };
20
- const SHAPE_ESCALATION_PATTERNS = [
21
- { id: "auth-permission", re: /\bauth\b|\bpermission\b|\brbac\b|登录|权限|\bjwt\b|\bsession\b/i },
22
- { id: "sensitive-action", re: /\bpayment\b|delete\s+account|支付|删除用户|\bdestructive\b/i },
23
- { id: "ssr-boundary", re: /\bssr\b|\brsc\b|server\s+component|\bhydration\b|server-client/i },
24
- { id: "new-route", re: /new\s+route|新增路由|createBrowserRouter|add\s+route/i },
25
- { id: "shared-public-api", re: /public\s+api|shared\s+component|公共组件|破坏性|breaking\s+change/i },
26
- { id: "schema-api-change", re: /\bopenapi\b|graphql\s+schema|api\s+contract|接口变更|schema\s+change/i },
27
- { id: "new-dependency", re: /new\s+dependency|add\s+dependency|npm\s+install|新增依赖/i },
28
- { id: "cross-domain-paths", re: /多个领域|cross-module|multiple\s+packages/i },
29
- ];
30
21
  const SHAPE_SMALL_PATTERNS = [
31
22
  { id: "single-component", re: /single\s+component|单一组件|局部样式|style\s+tweak|css\s+only|文案|copy\s+change/i },
32
23
  { id: "no-remote", re: /no\s+api|无\s*api|无\s*接口|本地状态|local\s+state\s+only|pure\s+ui/i },
@@ -59,48 +50,13 @@ function pathSpreadScore(paths) {
59
50
  return tops.size;
60
51
  }
61
52
  function collectEscalationSignals(blob, supervised) {
62
- const signals = [];
63
- for (const pattern of SHAPE_ESCALATION_PATTERNS) {
64
- if (hasAffirmativeShapeSignal(blob, pattern))
65
- signals.push(pattern.id);
66
- }
67
- if (supervised)
68
- signals.push("supervised-profile");
53
+ const signals = collectFrontendSharedSignals(blob, supervised);
69
54
  if (SHAPE_SPLIT_REQUIRED_PATTERN.test(blob) &&
70
55
  !SHAPE_SPLIT_REQUIRED_NEGATION.test(blob)) {
71
56
  signals.push("large-requirement-split");
72
57
  }
73
58
  return [...new Set(signals)];
74
59
  }
75
- function isExplicitShapeExclusion(clause, pattern) {
76
- const match = new RegExp(pattern.re.source, "i").exec(clause);
77
- if (!match)
78
- return false;
79
- const prefix = clause.slice(0, match.index).trim();
80
- const suffix = clause.slice(match.index + match[0].length).trim();
81
- const actionInSignal = ["new-route", "new-dependency"].includes(pattern.id);
82
- if (suffix && !(actionInSignal && /^no$/i.test(prefix) && /^is\s+(?:required|needed)$/i.test(suffix)))
83
- return false;
84
- if (/^(?:不涉及|不(?:修改|变更|调整|改变|新增|添加)|无需(?:修改|变更|调整|改变|新增|添加)|no\s+changes?\s+to|(?:do\s+not|don't)\s+(?:change|modify))$/i.test(prefix))
85
- return true;
86
- return actionInSignal && /^(?:不|禁止|无需|无须|不需要|no|without|do\s+not|don't)$/i.test(prefix);
87
- }
88
- /** Only a complete, independent scope exclusion can suppress a risk signal.
89
- * Nested negation, conditions and trailing consequences remain risk evidence;
90
- * an excluded occurrence never cancels another occurrence elsewhere. */
91
- function hasAffirmativeShapeSignal(blob, pattern) {
92
- for (const sentence of blob.split(/[\n。.;;!?!?]/)) {
93
- if (!pattern.re.test(sentence))
94
- continue;
95
- // Commas can attach a condition to an exclusion. A comma-separated list
96
- // is independent only when every item is itself an explicit exclusion.
97
- const clauses = sentence.split(/[,,、]/).map(part => part.trim().replace(/^(?:[-*+]|\d+\))\s+/, ""));
98
- if (clauses.every(clause => SHAPE_ESCALATION_PATTERNS.some(candidate => isExplicitShapeExclusion(clause, candidate))))
99
- continue;
100
- return true;
101
- }
102
- return false;
103
- }
104
60
  /** Negation applies to the API mention itself, never to unrelated Mock policy. */
105
61
  function hasFrontendRemoteSignal(blob) {
106
62
  const remaining = blob
@@ -126,7 +82,7 @@ function resolveBaselineFrontendTaskShape(input) {
126
82
  }
127
83
  const paths = input.allowedPaths ?? [];
128
84
  const spread = pathSpreadScore(paths);
129
- const concentrated = spread <= 1 && paths.length <= 4;
85
+ const concentrated = paths.length > 0 && spread <= 1 && paths.length <= 4;
130
86
  const hasApiSignal = hasFrontendRemoteSignal(blob);
131
87
  const complexity = (input.complexity ?? "").toLowerCase();
132
88
  // split-required wins over everything: it is a termination/escalation
@@ -172,17 +128,17 @@ function resolveBaselineFrontendTaskShape(input) {
172
128
  reason: "micro scope: trivial single-file change without remote API",
173
129
  };
174
130
  }
175
- // small: no escalation, no supervised, small keywords, concentrated paths,
176
- // no remote API signal.
177
- if (smallHits.length > 0 &&
178
- concentrated &&
179
- !hasApiSignal &&
180
- (complexity === "small" || complexity === "low")) {
131
+ // small: no escalation, no supervised, narrow single-domain delivery
132
+ // surface, no remote API signal, and not an explicitly large task. Keyword
133
+ // and explicit "small/low" complexity hints are no longer required — they
134
+ // were a fragile proxy for "this is genuinely a small change" and blocked
135
+ // genuinely-small tasks whose authors used different wording.
136
+ if (isNarrowProductSurface(paths) && !hasApiSignal && complexity !== "large") {
181
137
  return {
182
138
  shape: "small",
183
- signals: smallHits,
139
+ signals: smallHits.length > 0 ? smallHits : ["structured-small"],
184
140
  rejectedSignals: [],
185
- reason: `small signals without escalation: ${smallHits.join(", ")}`,
141
+ reason: "narrow single-domain delivery surface without remote API or escalation; small topology",
186
142
  };
187
143
  }
188
144
  const rejected = smallHits.map((id) => `small:${id}:blocked-by-${hasApiSignal ? "api-signal" : "path-spread"}`);
@@ -1,21 +1,18 @@
1
1
  import path from "node:path";
2
2
  import { readFile } from "node:fs/promises";
3
3
  import { z } from "zod";
4
- /** Observe supported frozen Vitest commands without changing their selection. */
5
- export async function withFrontendTestReporter(input) {
6
- // Compound commands/wrappers need their own adapter: never append flags to
7
- // a different shell command, change selection, or override a user reporter.
8
- if (/[;&|<>`\n\r$]/.test(input.command) || /--(?:reporter|outputFile)/.test(input.command))
9
- return undefined;
10
- const direct = /^(?:(?:npx|pnpm exec|yarn exec)\s+)?vitest\s+run(?:\s|$)/.test(input.command.trim());
11
- const npm = /^npm\s+(?:run\s+([\w:-]+)|(test))(?=\s|$)(.*)$/.exec(input.command.trim());
4
+ /** Decide whether a bare vitest/npm-test command is observable and, if so,
5
+ * return the command with the separator (" --") a reporter would need. */
6
+ async function adaptPlainTestCommand(command, cwd) {
7
+ const direct = /^(?:(?:npx|pnpm exec|yarn exec)\s+)?vitest\s+run(?:\s|$)/.test(command.trim());
8
+ const npm = /^npm\s+(?:run\s+([\w:-]+)|(test))(?=\s|$)(.*)$/.exec(command.trim());
12
9
  let separator = "";
13
10
  if (!direct) {
14
11
  if (!npm)
15
12
  return undefined;
16
13
  let manifest;
17
14
  try {
18
- manifest = JSON.parse(await readFile(path.join(input.cwd, "package.json"), "utf8"));
15
+ manifest = JSON.parse(await readFile(path.join(cwd, "package.json"), "utf8"));
19
16
  }
20
17
  catch {
21
18
  return undefined;
@@ -27,8 +24,36 @@ export async function withFrontendTestReporter(input) {
27
24
  return undefined;
28
25
  separator = npm[3]?.trim() ? "" : " --";
29
26
  }
27
+ return `${command}${separator}`;
28
+ }
29
+ /** Observe supported frozen Vitest commands without changing their selection. */
30
+ export async function withFrontendTestReporter(input) {
31
+ // Compound commands/wrappers need their own adapter: never append flags to
32
+ // a different shell command, change selection, or override a user reporter.
33
+ if (/[;&|<>`\n\r$]/.test(input.command) || /--(?:reporter|outputFile)/.test(input.command))
34
+ return undefined;
35
+ // Frozen verify commands are env-prefixed bash wrappers —
36
+ // `HARNESS_ALLOW_ACTIVE_DAG_RUNS=1 'bash' '-lc' 'npm test'` — so the JSON
37
+ // reporter must be injected INSIDE the quoted payload; appending it to the
38
+ // wrapper would target bash's own CLI and produce no test report at all
39
+ // (smoke r54: every behavior verification target then failed the trace
40
+ // gate with "test execution report unavailable").
41
+ const bashWrapped = /^((?:[A-Za-z_][A-Za-z0-9_]*=(?:'[^']*'|[^\s'"]+)\s+)*)'bash'\s+'-lc'\s+'([^']*)'\s*$/.exec(input.command);
42
+ if (bashWrapped) {
43
+ const inner = bashWrapped[2];
44
+ if (/[;&|<>`\n\r$]/.test(inner) || /--(?:reporter|outputFile)/.test(inner))
45
+ return undefined;
46
+ const adaptedInner = await adaptPlainTestCommand(inner, input.cwd);
47
+ if (!adaptedInner)
48
+ return undefined;
49
+ const quote = (value) => `'${value.replace(/'/g, `'"'"'`)}'`;
50
+ return input.command.replace(`'${inner}'`, `'${adaptedInner} --reporter=json --outputFile=${quote(input.reportPath)}'`);
51
+ }
52
+ const adapted = await adaptPlainTestCommand(input.command, input.cwd);
53
+ if (!adapted)
54
+ return undefined;
30
55
  const quote = (value) => `'${value.replace(/'/g, `'"'"'`)}'`;
31
- return `${input.command}${separator} --reporter=json --outputFile=${quote(input.reportPath)}`;
56
+ return `${adapted} --reporter=json --outputFile=${quote(input.reportPath)}`;
32
57
  }
33
58
  const resultSchema = z.object({
34
59
  testResults: z.array(z.object({
@@ -31,18 +31,11 @@ export const TYPED_EVENT_FACT_KINDS = [
31
31
  "provider-ended-without-terminal",
32
32
  "read-only-fact-channel",
33
33
  ];
34
- /**
35
- * M2 shadow-only fact catalog (AC-001 shadow dual write). These facts are
36
- * audit-only observations derived from the authoritative text/JSON outputs of
37
- * the frontend-contract-pi / frontend-scout-pi / frontend-plan-pi nodes. They
38
- * are never branch-control signals and never participate in writer admission.
39
- *
40
- * Kept deliberately separate from `TYPED_EVENT_FACT_KINDS`: widening the shadow
41
- * catalog must never widen the provider capability matrix (which stays frozen
42
- * at its seven kinds). `target-surface` is shared by the scout and plan
43
- * origins, so the union of the two catalogs is 12 unique kinds.
44
- */
45
- export const SHADOW_FACT_KINDS = [
34
+ /** Frontend DAG decision facts. These are committed runtime facts, not a
35
+ * secondary text channel; Policy, Admission and Plan compilation read them
36
+ * directly. Kept separate from provider capability facts so the provider
37
+ * matrix remains frozen. */
38
+ export const FRONTEND_DAG_FACT_KINDS = [
46
39
  "requirement",
47
40
  "constraint",
48
41
  "open-question",
@@ -56,11 +49,26 @@ export const SHADOW_FACT_KINDS = [
56
49
  "design-deviation",
57
50
  "dependency",
58
51
  ];
52
+ /** Experimental decision Plan fact catalog. The decision tools emit semantic
53
+ * decisions (module placement, reuse, state ownership, Mock boundary, behavior
54
+ * verification focus); `data-flow` and `dependency` reuse the DAG catalog above.
55
+ * Kept as a dedicated catalog so widening the decision surface never widens the
56
+ * frozen provider / event / review / design catalogs. */
57
+ export const FRONTEND_DECISION_FACT_KINDS = [
58
+ "module-placement",
59
+ "reuse-decision",
60
+ "state-ownership",
61
+ "api-mock-boundary",
62
+ "verification-focus",
63
+ ];
64
+ /** Terminal fact for the experimental decision Plan path. Kept separate from the
65
+ * decision fact catalog so it never gets collected as a decision entry. */
66
+ export const DECISION_TERMINAL_FACT_KINDS = ["finalize_decision"];
59
67
  /**
60
68
  * M5 review terminal fact catalog (AC-001). These two committed typed tools are
61
69
  * the authoritative terminal verdict of `frontend-review-pi`. They are kept in
62
70
  * a dedicated catalog so widening the review terminal surface never widens the
63
- * frozen 7/7/12 provider/shadow/event catalogs.
71
+ * frozen provider/event catalogs.
64
72
  */
65
73
  export const REVIEW_TERMINAL_FACT_KINDS = [
66
74
  "approve_review",
@@ -72,7 +80,7 @@ export const REVIEW_TERMINAL_FACT_KINDS = [
72
80
  * `approve_design` / `request_design_changes` instead of the legacy first-line
73
81
  * `VERDICT: pass|request-revision` text protocol. Kept in a dedicated catalog
74
82
  * so widening the design terminal surface never widens the frozen provider /
75
- * shadow / event / review catalogs.
83
+ * event / review catalogs.
76
84
  */
77
85
  export const DESIGN_TERMINAL_FACT_KINDS = [
78
86
  "approve_design",
@@ -84,7 +92,7 @@ export const DESIGN_TERMINAL_FACT_KINDS = [
84
92
  * open-question/split-proposal facts plus the `contract-finalized` terminal
85
93
  * fact carrying the terminal disposition (`ready | ready-with-assumptions |
86
94
  * blocked` + optional `blockingOwner`). Kept in a dedicated catalog so
87
- * widening the contract surface never widens the frozen provider / shadow /
95
+ * widening the contract surface never widens the frozen provider /
88
96
  * event / review / design catalogs.
89
97
  */
90
98
  export const CONTRACT_FACT_KINDS = [
@@ -142,8 +150,8 @@ export const FRONTEND_ISSUE_CATEGORY_VALUES = [
142
150
  ];
143
151
  export const frontendIssueCategorySchema = z.enum(FRONTEND_ISSUE_CATEGORY_VALUES);
144
152
  /** Runtime control facts committed at frontend shape boundaries. Kept outside
145
- * the frozen provider/shadow catalogs so adding control authority cannot widen
146
- * either legacy capability surface. */
153
+ * the frozen provider catalog so adding control authority cannot widen that
154
+ * capability surface. */
147
155
  export const FRONTEND_SHAPE_FACT_KINDS = ["frontend-shape"];
148
156
  export const typedEventPhaseSchema = z.enum([
149
157
  "staged",
@@ -153,7 +161,9 @@ export const typedEventPhaseSchema = z.enum([
153
161
  const ALL_TYPED_EVENT_FACT_KIND_VALUES = [
154
162
  ...new Set([
155
163
  ...TYPED_EVENT_FACT_KINDS,
156
- ...SHADOW_FACT_KINDS,
164
+ ...FRONTEND_DAG_FACT_KINDS,
165
+ ...FRONTEND_DECISION_FACT_KINDS,
166
+ ...DECISION_TERMINAL_FACT_KINDS,
157
167
  ...REVIEW_TERMINAL_FACT_KINDS,
158
168
  ...DESIGN_TERMINAL_FACT_KINDS,
159
169
  ...FRONTEND_SHAPE_FACT_KINDS,
@@ -166,7 +176,7 @@ const ALL_TYPED_EVENT_FACT_KIND_VALUES = [
166
176
  export const typedEventFactKindSchema = z.enum(ALL_TYPED_EVENT_FACT_KIND_VALUES);
167
177
  /** A typed event fact is a `kind`-discriminated record. The schema stays
168
178
  * permissive beyond `kind` (arbitrary JSON-safe payload) so provider/model
169
- * shadow facts can carry their own fields, but `kind` is always validated. */
179
+ * provider facts can carry their own fields, but `kind` is always validated. */
170
180
  export const typedEventFactSchema = z
171
181
  .record(z.unknown())
172
182
  .superRefine((value, ctx) => {
@@ -178,8 +188,7 @@ export const typedEventFactSchema = z
178
188
  });
179
189
  }
180
190
  });
181
- /** A single review finding shared by both typed terminal verdicts. Mirrors the
182
- * legacy JSON review finding shape so typed↔JSON equivalence stays total. */
191
+ /** A single review finding shared by both typed terminal verdicts. */
183
192
  export const reviewFindingSchema = z
184
193
  .object({
185
194
  severity: z.enum(["Critical", "Important", "Minor", "Info"]),
@@ -190,8 +199,7 @@ export const reviewFindingSchema = z
190
199
  })
191
200
  .strict();
192
201
  /** `approve_review` terminal fact payload. Approval may carry informational
193
- * findings (Minor/Info) but never Critical/Important — the same rule the legacy
194
- * JSON verdict applies to `verdict: "pass"`. */
202
+ * findings (Minor/Info) but never Critical/Important. */
195
203
  export const approveReviewFactSchema = z
196
204
  .object({
197
205
  kind: z.literal("approve_review"),
@@ -221,8 +229,7 @@ export const requestReviewChangesFactSchema = z
221
229
  })
222
230
  .strict();
223
231
  /** `approve_design` terminal fact payload. Approval may carry informational
224
- * findings (Minor/Info) but never Critical/Important — the same rule the legacy
225
- * text protocol applies to `VERDICT: pass`. */
232
+ * findings (Minor/Info) but never Critical/Important. */
226
233
  export const approveDesignFactSchema = z
227
234
  .object({
228
235
  kind: z.literal("approve_design"),
@@ -260,6 +260,8 @@ export async function evaluateFrontendWriterTracePreflight(input) {
260
260
  file: target.file,
261
261
  ...(target.symbol ? { symbol: target.symbol } : {}),
262
262
  status: fileTrace.issues.length ? "failed" : "ok",
263
+ evidence: fileTrace.issues.length ? "unavailable" : "file-exists",
264
+ behaviorCoverage: "unconfirmed",
263
265
  matchedNodeIds: [],
264
266
  issues: fileTrace.issues,
265
267
  });
@@ -296,7 +298,10 @@ export async function evaluateVerificationTargets(input) {
296
298
  });
297
299
  issues.push(...fileTrace.issues);
298
300
  const executed = input.executedTests?.get(target.commandLabel);
299
- if (target.mode !== "static" && executed !== undefined) {
301
+ if (target.mode !== "static" && executed === undefined) {
302
+ issues.push(`test execution report unavailable for behavior target: ${target.id}`);
303
+ }
304
+ else if (target.mode !== "static" && executed !== undefined) {
300
305
  // safePath permits "./" prefixes; reporter files are cwd-relative.
301
306
  const targetFile = path.posix.normalize(target.file.replace(/\\/g, "/"));
302
307
  const matching = executed.filter((test) => test.file === targetFile);
@@ -314,6 +319,10 @@ export async function evaluateVerificationTargets(input) {
314
319
  file: target.file,
315
320
  symbol: target.symbol,
316
321
  status,
322
+ evidence: issues.length
323
+ ? "unavailable"
324
+ : target.mode === "static" ? "command-execution" : "test-file-execution",
325
+ behaviorCoverage: "unconfirmed",
317
326
  matchedNodeIds: owners ?? [],
318
327
  issues,
319
328
  });
@@ -520,7 +529,7 @@ export function formatFrontendVerificationTraceStdout(result) {
520
529
  `Browser: ${result.browserStatus}`,
521
530
  `Visual: ${result.visualStatus}`,
522
531
  `Targets: ${result.targets.length}`,
523
- ...result.targets.map((target) => `- ${target.id}: ${target.status} label=${JSON.stringify(target.commandLabel)} file=${target.file}`),
532
+ ...result.targets.map((target) => `- ${target.id}: execution=${target.status} evidence=${target.evidence} behavior-coverage=${target.behaviorCoverage} label=${JSON.stringify(target.commandLabel)} file=${target.file}`),
524
533
  ];
525
534
  if (result.artifactPath) {
526
535
  lines.push(`Artifact: ${result.artifactPath}`);
@@ -22,9 +22,8 @@ export const frontendWriterAdmissionClassificationSchema = z.enum([
22
22
  "requires-human-approval",
23
23
  "blocked",
24
24
  "stale",
25
- // Legacy artifacts remain parseable for diagnostics, but `admitted` is never
26
- // accepted by the runtime authorization gate. Canonical producers always
27
- // emit one of the four values above.
25
+ // `admitted` is retained only as an invalidated input value; the runtime
26
+ // authorization gate accepts only the four canonical classifications above.
28
27
  "admitted",
29
28
  ]);
30
29
  /** A+B (AC-006): Micro topology uses `requirement-to-target-and-evidence`
@@ -59,8 +58,7 @@ export const frontendAdmissionFailureSourceSchema = z.enum([
59
58
  /**
60
59
  * Deterministic artifact schema for `contracts/frontend-writer-admission-result.json`.
61
60
  * Scheduler admission gate and frontend recovery planning read this artifact
62
- * (fail-closed); it replaces the legacy frontend-prewrite-result-v1 as the
63
- * write authorization source.
61
+ * (fail-closed); it is the write authorization source for new runs.
64
62
  */
65
63
  export const frontendWriterAdmissionResultV1Schema = z
66
64
  .object({
@@ -275,45 +273,3 @@ export function deriveFrontendWriterAdmissionShell(input) {
275
273
  };
276
274
  return result;
277
275
  }
278
- function normalizeNewVerdict(classification) {
279
- return classification === "accepted" ||
280
- classification === "requires-human-approval" ||
281
- classification === "admitted"
282
- ? "accepted"
283
- : "blocked";
284
- }
285
- function normalizeLegacyVerdict(classification) {
286
- return classification === "accepted" || classification === "accepted-normalized"
287
- ? "accepted"
288
- : "blocked";
289
- }
290
- export function compareAdmissionToLegacyGate(input) {
291
- const newVerdict = normalizeNewVerdict(input.newClassification);
292
- const legacyVerdict = normalizeLegacyVerdict(input.legacyClassification);
293
- return {
294
- newClassification: input.newClassification,
295
- legacyClassification: input.legacyClassification,
296
- newVerdict,
297
- legacyVerdict,
298
- match: newVerdict === legacyVerdict,
299
- };
300
- }
301
- /** Render a stable, deterministically ordered mismatch list for the evaluation
302
- * set. Input order is irrelevant: rows are sorted by a canonical key. */
303
- export function renderAdmissionShadowDiff(results) {
304
- const indexed = results.map((result, index) => ({ ...result, index }));
305
- const sorted = indexed.sort((a, b) => {
306
- const keyA = `${a.legacyClassification}|${a.newClassification}`;
307
- const keyB = `${b.legacyClassification}|${b.newClassification}`;
308
- if (keyA !== keyB)
309
- return keyA < keyB ? -1 : 1;
310
- return a.index - b.index;
311
- });
312
- const mismatches = sorted.filter((row) => !row.match);
313
- const lines = [
314
- "## Admission Shadow Diff",
315
- `${sorted.length} comparisons, ${mismatches.length} mismatches`,
316
- ...sorted.map((row) => `- ${row.legacyClassification} -> ${row.newClassification}: legacy=${row.legacyVerdict} new=${row.newVerdict}${row.match ? " match" : " MISMATCH"}`),
317
- ];
318
- return lines.join("\n");
319
- }
@@ -161,26 +161,6 @@ export function buildFrontendMicroLoopCheckpoint(input) {
161
161
  writeAttempts: input.facts.writeAttempts,
162
162
  };
163
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
164
  /**
185
165
  * Pure session-event scanner: count write-tool events from the persisted
186
166
  * session-events.jsonl content. Fail-closed: an unparseable line never counts
@@ -249,8 +229,5 @@ export async function collectFrontendWriterFacts(input) {
249
229
  wallTimeMs: input.wallTimeMs ?? 0,
250
230
  rounds: input.rounds ?? 1,
251
231
  writeAttempts: input.writeAttempts ?? 1,
252
- ...(input.firstLineText !== undefined
253
- ? { firstLineText: input.firstLineText }
254
- : {}),
255
232
  };
256
233
  }