@tea-agent/loop-agent 0.12.0 → 0.13.0-beta.0

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 (284) hide show
  1. package/AGENTS.md +155 -153
  2. package/CHANGELOG.md +338 -265
  3. package/README.md +345 -298
  4. package/bin/agent-worker.js +22 -22
  5. package/bin/loop-agent.js +21 -21
  6. package/dist/application/dag/generate-task-dag.js +28 -28
  7. package/dist/application/evaluation/candidate-hash.js +75 -0
  8. package/dist/application/evaluation/candidate.js +52 -0
  9. package/dist/application/evaluation/replay.js +289 -0
  10. package/dist/application/evaluation/types.js +130 -0
  11. package/dist/cli/command-definitions.js +27 -7
  12. package/dist/cli/program.js +8 -4
  13. package/dist/commands/cursor-prompt.js +6 -6
  14. package/dist/commands/eval.js +235 -0
  15. package/dist/commands/init.js +544 -506
  16. package/dist/commands/knowledge.js +129 -31
  17. package/dist/commands/loop-benchmark.js +11 -11
  18. package/dist/commands/pi-reuse-benchmark.js +16 -16
  19. package/dist/executors/pi-sdk-executor.js +38 -24
  20. package/dist/executors/shell-executor.js +34 -2
  21. package/dist/executors/shell-presets.js +20 -0
  22. package/dist/executors/shell-verification.js +7 -0
  23. package/dist/governance/manifest-types.js +4 -0
  24. package/dist/infrastructure/evaluation/candidate-store.js +435 -0
  25. package/dist/infrastructure/evaluation/store.js +40 -0
  26. package/dist/sidecars/cursor-prompt/executor.js +1 -1
  27. package/dist/task/config-types.js +28 -1
  28. package/dist/task/runtime.js +27 -27
  29. package/dist/worker/cli.js +96 -1
  30. package/dist/worker/delivery/package.js +3 -3
  31. package/dist/worker/feature/decision-loader.js +37 -6
  32. package/dist/worker/feature/next-action.js +10 -2
  33. package/dist/worker/feature/ready-plan-projection.js +81 -0
  34. package/dist/worker/feature/reducer.js +2 -1
  35. package/dist/worker/feature/review.js +19 -2
  36. package/dist/worker/feature/run.js +27 -2
  37. package/dist/worker/follow-up/approve.js +5 -2
  38. package/dist/worker/follow-up/factory.js +1 -1
  39. package/dist/worker/observability/read-model.js +246 -41
  40. package/dist/worker/observe/routes.js +173 -15
  41. package/dist/worker/observe/spec-evidence.js +281 -0
  42. package/dist/worker/observe/static/api.js +46 -27
  43. package/dist/worker/observe/static/app.js +150 -150
  44. package/dist/worker/observe/static/constants.js +148 -148
  45. package/dist/worker/observe/static/copy.js +67 -67
  46. package/dist/worker/observe/static/dag-helpers.js +172 -172
  47. package/dist/worker/observe/static/dag-layout.d.ts +31 -31
  48. package/dist/worker/observe/static/dag-layout.js +83 -83
  49. package/dist/worker/observe/static/dag-model.js +72 -72
  50. package/dist/worker/observe/static/dom.js +61 -61
  51. package/dist/worker/observe/static/format-pool.js +67 -67
  52. package/dist/worker/observe/static/format.js +292 -292
  53. package/dist/worker/observe/static/index.html +308 -308
  54. package/dist/worker/observe/static/kpi.js +94 -94
  55. package/dist/worker/observe/static/relations.js +133 -128
  56. package/dist/worker/observe/static/router.js +93 -85
  57. package/dist/worker/observe/static/run-processing.js +148 -148
  58. package/dist/worker/observe/static/shell-chrome.js +68 -68
  59. package/dist/worker/observe/static/state.js +253 -253
  60. package/dist/worker/observe/static/styles.css +1902 -1890
  61. package/dist/worker/observe/static/views/batch.js +227 -226
  62. package/dist/worker/observe/static/views/dag-graph.js +172 -172
  63. package/dist/worker/observe/static/views/dag-inspector.js +607 -477
  64. package/dist/worker/observe/static/views/dag.js +362 -362
  65. package/dist/worker/observe/static/views/dashboard.js +445 -442
  66. package/dist/worker/observe/static/views/failures.js +143 -143
  67. package/dist/worker/observe/static/views/feature.js +492 -453
  68. package/dist/worker/observe/static/views/pool.js +350 -347
  69. package/dist/worker/observe/static/views/run.js +453 -453
  70. package/dist/worker/observe/static/views/session-timeline.js +205 -205
  71. package/dist/worker/observe/static/views/shell.js +7 -7
  72. package/dist/worker/observe/static/views/task.js +314 -260
  73. package/dist/worker/observe/static/views/timeline.js +163 -163
  74. package/dist/worker/pool/doctor.js +165 -0
  75. package/dist/worker/pool/migrate-state.js +303 -0
  76. package/dist/worker/pool/run-store.js +205 -17
  77. package/dist/worker/pool/types.js +17 -1
  78. package/dist/worker/pool/validation.js +100 -15
  79. package/dist/worker/report/morning-report.js +12 -2
  80. package/dist/worker/runner/run-ready.js +41 -26
  81. package/dist/worker/task-graph/ready-planner.js +136 -0
  82. package/dist/workflows/dag/backend-test-analysis-contract.js +120 -0
  83. package/dist/workflows/dag/canvas-observer.js +275 -275
  84. package/dist/workflows/dag/convergence/controller.js +16 -8
  85. package/dist/workflows/dag/dynamic-runtime/map.js +90 -2
  86. package/dist/workflows/dag/failure-routing.js +12 -1
  87. package/dist/workflows/dag/init-hybrid.js +2404 -360
  88. package/dist/workflows/dag/node-execution.js +9 -0
  89. package/dist/workflows/dag/prompt.js +9 -0
  90. package/dist/workflows/dag/report.js +35 -1
  91. package/dist/workflows/dag/runner.js +28 -2
  92. package/dist/workflows/dag/task-demand-routing.js +383 -0
  93. package/dist/workflows/dag/types.js +51 -13
  94. package/dist/workflows/dag/upstream-artifacts.js +1 -0
  95. package/dist/workflows/dag/validate.js +59 -1
  96. package/docs/README.md +106 -104
  97. package/docs/agent-dag-recovery-playbook.md +195 -184
  98. package/docs/agent-dag-runner.md +67 -67
  99. package/docs/architecture/README.md +26 -26
  100. package/docs/architecture/dag-execution.md +140 -140
  101. package/docs/architecture/evolution.md +54 -53
  102. package/docs/architecture/facts-and-state.md +71 -58
  103. package/docs/architecture/runtime-boundaries.md +191 -191
  104. package/docs/architecture/system-overview.md +93 -93
  105. package/docs/architecture/worker-and-feature.md +85 -81
  106. package/docs/cursor-prompt-sidecar.md +36 -36
  107. package/docs/decisions/README.md +18 -15
  108. package/docs/design/README.md +167 -77
  109. package/docs/development-principles.md +73 -73
  110. package/docs/exec-plans/README.md +6 -6
  111. package/docs/exec-plans/active/README.md +15 -9
  112. package/docs/exec-plans/completed/README.md +85 -73
  113. package/docs/feature-workflow.md +389 -261
  114. package/docs/harness-methodology-debugging.md +153 -153
  115. package/docs/harness-methodology-tdd.md +130 -130
  116. package/docs/harness-methodology-verification.md +27 -27
  117. package/docs/init-surface.manifest.json +289 -280
  118. package/docs/loop-agent-harness.md +142 -130
  119. package/docs/production-readiness.md +96 -96
  120. package/docs/progress/README.md +64 -54
  121. package/docs/reports/README.md +117 -94
  122. package/docs/skills/README.md +7 -7
  123. package/docs/skills/vetted-skill-registry.md +29 -27
  124. package/docs/templates/adr.md +60 -60
  125. package/docs/templates/agent-dag-authority-surface-audit.prompt.md +94 -94
  126. package/docs/templates/agent-dag-decision-envelope.schema.json +213 -213
  127. package/docs/templates/agent-dag-decision-gate-dogfood-report.md +117 -117
  128. package/docs/templates/agent-dag-decision-gate.prompt.md +246 -246
  129. package/docs/templates/agent-dag-process-supervisor.prompt.md +98 -98
  130. package/docs/templates/agent-dag-report.schema.json +473 -473
  131. package/docs/templates/agent-dag-review-verdict.prompt.md +68 -68
  132. package/docs/templates/agent-dag.base.json +190 -190
  133. package/docs/templates/agent-dag.final-verification.json +185 -185
  134. package/docs/templates/agent-dag.schema.json +411 -383
  135. package/docs/templates/agent-dag.supervised-implementation.json +501 -501
  136. package/docs/templates/backend-test-analysis.schema.json +44 -0
  137. package/docs/templates/backend-test-dag.generate-pytest.prompt.md +202 -139
  138. package/docs/templates/backend-test-dag.json +311 -276
  139. package/docs/templates/backend-test-dag.retrospect.prompt.md +125 -125
  140. package/docs/templates/backend-test-dag.review-cases.prompt.md +81 -81
  141. package/docs/templates/exec-plan.md +64 -64
  142. package/docs/templates/feature-spec.md +53 -53
  143. package/docs/templates/frontend-design-contract.md +42 -33
  144. package/docs/templates/frontend-task-constraints.md +35 -25
  145. package/docs/templates/frontend-task-requirement.md +70 -61
  146. package/docs/templates/frontend-test-dag.generate-cases.prompt.md +5 -0
  147. package/docs/templates/frontend-test-dag.json +23 -0
  148. package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +3 -0
  149. package/docs/templates/frontend-test-dag.retrospect.prompt.md +3 -0
  150. package/docs/templates/frontend-test-dag.review-cases.prompt.md +3 -0
  151. package/docs/templates/frontend-test-dag.review-execution.prompt.md +3 -0
  152. package/docs/templates/harness.schema.json +221 -221
  153. package/docs/templates/hybrid-dag.json +188 -188
  154. package/docs/templates/init-evolution-review.md +35 -35
  155. package/docs/templates/interactive-ui-round2-experiment.md +66 -66
  156. package/docs/templates/knowledge-graph-bootstrap-dag.json +118 -0
  157. package/docs/templates/knowledge-sync-dag.json +178 -0
  158. package/docs/templates/knowledge-sync-draft.schema.json +71 -0
  159. package/docs/templates/product-line/AGENTS.md +8 -8
  160. package/docs/templates/product-line/README.md +9 -9
  161. package/docs/templates/product-line/acceptance.yaml +14 -14
  162. package/docs/templates/product-line/closeout.yaml +9 -9
  163. package/docs/templates/product-line/design.md +13 -13
  164. package/docs/templates/product-line/links.md +10 -10
  165. package/docs/templates/product-line/requirement.md +17 -17
  166. package/docs/templates/product-line/task-graph.yaml +15 -15
  167. package/docs/templates/product-line/task.yaml +64 -64
  168. package/docs/templates/product-line/test-plan.md +7 -7
  169. package/docs/templates/production-readiness-checklist.md +57 -57
  170. package/docs/templates/progress-log.md +17 -17
  171. package/docs/templates/project-start-checklist.md +9 -9
  172. package/docs/templates/qa-report.md +48 -48
  173. package/docs/templates/sprint-contract.md +29 -29
  174. package/docs/templates/worker-dogfood-evidence.md +80 -80
  175. package/docs/templates/worker-dogfood-setup.md +68 -68
  176. package/docs/verification-matrix.md +70 -66
  177. package/examples/decision-gate-agent-dag.json +177 -177
  178. package/examples/example-dag.json +46 -46
  179. package/examples/hybrid-loop-agent-dag.json +189 -189
  180. package/harness.json +66 -66
  181. package/package.json +88 -46
  182. package/scripts/check-product-line-docs.sh +29 -29
  183. package/scripts/check-task-pool-root.sh +32 -32
  184. package/scripts/kb-bootstrap-init-skeleton.sh +240 -0
  185. package/scripts/kb-graph-incremental-prepare.mjs +386 -0
  186. package/scripts/kb-graph-incremental-prepare.sh +5 -0
  187. package/scripts/kb-graph-materialize.mjs +105 -0
  188. package/scripts/kb-graph-materialize.sh +4 -0
  189. package/scripts/kb-graph-promote.mjs +164 -0
  190. package/scripts/kb-graph-promote.sh +4 -0
  191. package/scripts/kb-query.mjs +554 -0
  192. package/scripts/kb-query.sh +5 -0
  193. package/skills/agent-worker/SKILL.md +39 -37
  194. package/skills/agent-worker/references/agent-worker-operator.md +60 -43
  195. package/skills/ai-engineering-context/SKILL.md +48 -48
  196. package/skills/analyze-product-dependencies/SKILL.md +67 -0
  197. package/skills/analyze-product-dependencies/agents/openai.yaml +4 -0
  198. package/skills/analyze-product-dependencies/references/api-documentation-schema.md +30 -0
  199. package/skills/analyze-product-dependencies/references/dependency-analysis-schema.md +28 -0
  200. package/skills/analyze-product-dependencies/references/example.md +76 -0
  201. package/skills/analyze-product-dependencies/references/forward-test-cases.md +35 -0
  202. package/skills/analyze-product-dependencies/references/input-contract.md +11 -0
  203. package/skills/analyze-product-dependencies/references/scouting-rules.md +61 -0
  204. package/skills/analyze-product-dependencies/scripts/test-validators.mjs +267 -0
  205. package/skills/analyze-product-dependencies/scripts/validate-api-documentation.mjs +101 -0
  206. package/skills/analyze-product-dependencies/scripts/validate-dependency-analysis.mjs +142 -0
  207. package/skills/analyze-product-dependencies/scripts/validate-product-requirement-input.mjs +76 -0
  208. package/skills/analyze-product-dependencies/scripts/validation-helpers.mjs +146 -0
  209. package/skills/analyze-product-requirements/SKILL.md +90 -0
  210. package/skills/analyze-product-requirements/agents/openai.yaml +4 -0
  211. package/skills/analyze-product-requirements/references/acceptance-criteria.md +91 -0
  212. package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +56 -0
  213. package/skills/analyze-product-requirements/references/example.md +86 -0
  214. package/skills/analyze-product-requirements/references/forward-test-cases.md +66 -0
  215. package/skills/analyze-product-requirements/references/product-analysis-schema.md +32 -0
  216. package/skills/analyze-product-requirements/references/product-requirement-schema.md +33 -0
  217. package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +35 -0
  218. package/skills/analyze-product-requirements/scripts/test-validators.mjs +193 -0
  219. package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +69 -0
  220. package/skills/analyze-product-requirements/scripts/validate-product-requirement.mjs +97 -0
  221. package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +98 -0
  222. package/skills/analyze-product-requirements/scripts/validation-helpers.mjs +156 -0
  223. package/skills/code-review-core/SKILL.md +20 -20
  224. package/skills/codebase-scout/SKILL.md +19 -19
  225. package/skills/frontend-design-review/SKILL.md +66 -59
  226. package/skills/frontend-design-review/references/review-checklist.md +58 -37
  227. package/skills/frontend-implementation/SKILL.md +47 -51
  228. package/skills/frontend-implementation/references/code-standards.md +32 -34
  229. package/skills/frontend-implementation/references/design-spec.md +46 -46
  230. package/skills/frontend-implementation/references/node-contracts.md +76 -32
  231. package/skills/frontend-review/SKILL.md +59 -53
  232. package/skills/frontend-review/references/review-findings.md +47 -42
  233. package/skills/frontend-verification/SKILL.md +53 -40
  234. package/skills/frontend-verification/references/verification-checklist.md +68 -56
  235. package/skills/grill-me/SKILL.md +10 -10
  236. package/skills/grill-with-docs/SKILL.md +88 -88
  237. package/skills/grill-with-docs/adr-format.md +47 -47
  238. package/skills/grill-with-docs/context-format.md +60 -60
  239. package/skills/init-capability-evolution/SKILL.md +70 -70
  240. package/skills/loop-agent/SKILL.md +151 -151
  241. package/skills/loop-agent/references/README.md +67 -67
  242. package/skills/loop-agent/references/command-reference.md +505 -452
  243. package/skills/loop-agent/references/docs-converge.md +126 -126
  244. package/skills/loop-agent/references/harness-policy.md +263 -263
  245. package/skills/loop-agent/references/hybrid-dag.md +238 -233
  246. package/skills/loop-agent/references/learned/README.md +21 -21
  247. package/skills/loop-agent/references/long-running-loop.md +57 -57
  248. package/skills/loop-agent/references/model-routing.md +36 -36
  249. package/skills/loop-agent/references/multi-worktree.md +54 -54
  250. package/skills/loop-agent/references/one-shot-runs.md +85 -85
  251. package/skills/loop-agent/references/orchestrator-and-interventions.md +169 -169
  252. package/skills/loop-agent/references/pi-prompt.md +23 -23
  253. package/skills/loop-agent/references/pi-subagent-assisted-mode.md +84 -84
  254. package/skills/loop-agent/references/post-implementation-and-patterns.md +44 -44
  255. package/skills/loop-agent/references/task-workflow.md +89 -89
  256. package/skills/loop-agent/references/verification-and-failure-handling.md +139 -139
  257. package/skills/playwright-cli/SKILL.md +420 -0
  258. package/skills/playwright-cli/references/element-attributes.md +23 -0
  259. package/skills/playwright-cli/references/playwright-tests.md +39 -0
  260. package/skills/playwright-cli/references/request-mocking.md +87 -0
  261. package/skills/playwright-cli/references/running-code.md +241 -0
  262. package/skills/playwright-cli/references/session-management.md +225 -0
  263. package/skills/playwright-cli/references/storage-state.md +275 -0
  264. package/skills/playwright-cli/references/test-generation.md +433 -0
  265. package/skills/playwright-cli/references/tracing.md +139 -0
  266. package/skills/playwright-cli/references/video-recording.md +143 -0
  267. package/skills/playwright-cli-case-generator/SKILL.md +74 -0
  268. package/skills/requesting-code-review/SKILL.md +101 -101
  269. package/skills/requesting-code-review/code-reviewer.md +168 -168
  270. package/skills/systematic-debugging/CREATION-LOG.md +119 -119
  271. package/skills/systematic-debugging/SKILL.md +296 -296
  272. package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -158
  273. package/skills/systematic-debugging/condition-based-waiting.md +115 -115
  274. package/skills/systematic-debugging/defense-in-depth.md +122 -122
  275. package/skills/systematic-debugging/find-polluter.sh +63 -63
  276. package/skills/systematic-debugging/root-cause-tracing.md +169 -169
  277. package/skills/systematic-debugging/test-academic.md +14 -14
  278. package/skills/systematic-debugging/test-pressure-1.md +58 -58
  279. package/skills/systematic-debugging/test-pressure-2.md +68 -68
  280. package/skills/systematic-debugging/test-pressure-3.md +69 -69
  281. package/skills/test-driven-development/SKILL.md +20 -20
  282. package/skills/using-git-worktrees/SKILL.md +215 -215
  283. package/skills/verification-before-completion/SKILL.md +154 -154
  284. package/skills/webapp-testing/SKILL.md +19 -19
@@ -1,3 +1,5 @@
1
+ import { createHash } from "node:crypto";
2
+ import { readFile } from "node:fs/promises";
1
3
  import path from "node:path";
2
4
  import { recordDecisionEnvelopeForNode, shouldPauseOnHumanEscalation, writeHumanEscalationArtifacts, } from "./decision-envelope.js";
3
5
  import { writeNodeRecord, writeNodeSkillArtifacts } from "./run-store.js";
@@ -405,6 +407,13 @@ export async function executeDagNode(input) {
405
407
  assistantText: node.assistantText,
406
408
  });
407
409
  Object.assign(node, artifactMeta);
410
+ if (task.shell?.jsonArtifactGate) {
411
+ const artifactPath = path.join(runDir, task.shell.jsonArtifactGate.outputDir, task.shell.jsonArtifactGate.artifactName);
412
+ const bytes = await readFile(artifactPath);
413
+ node.structuredArtifactPath = artifactPath;
414
+ node.structuredArtifactSha256 = createHash("sha256").update(bytes).digest("hex");
415
+ node.structuredArtifactSchemaId = task.shell.jsonArtifactGate.schemaId;
416
+ }
408
417
  }
409
418
  state.nodes[nodeId].nodeRecordPath = path.join(runDir, `${nodeId}.json`);
410
419
  await writeNodeRecord(runDir, nodeId, state.nodes[nodeId]);
@@ -73,6 +73,15 @@ export function buildUpstreamContext(task, upstream, maxChars = MAX_UPSTREAM_CHA
73
73
  : record.assistantArtifactPath;
74
74
  const { preview, truncated } = formatStdoutPreview(upstreamText, maxChars);
75
75
  let section = `## Upstream output: ${depId}\n${preview}`;
76
+ if (record.structuredArtifactPath) {
77
+ section += [
78
+ "",
79
+ "Validated structured artifact (read-only runner evidence — use read tool to fetch; do not edit):",
80
+ `- path: ${record.structuredArtifactPath}`,
81
+ `- schema: ${record.structuredArtifactSchemaId ?? "unknown"}`,
82
+ `- sha256: ${record.structuredArtifactSha256 ?? "unknown"}`,
83
+ ].join("\n");
84
+ }
76
85
  if (truncated && artifactPath) {
77
86
  section += formatUpstreamArtifactPointerMap(record, artifactKind);
78
87
  }
@@ -186,6 +186,20 @@ const dagRunReportEntrySchema = z
186
186
  runtimeContractReason: z.string().optional(),
187
187
  })
188
188
  .optional(),
189
+ evaluationAssociation: z.discriminatedUnion("status", [
190
+ z.object({ status: z.literal("absent-legacy") }).strict(),
191
+ z
192
+ .object({
193
+ status: z.literal("present"),
194
+ campaignId: z.string().min(1),
195
+ candidateId: z.string().min(1),
196
+ bundleHash: z.string().regex(/^sha256:[a-f0-9]{64}$/),
197
+ seed: z.number().int().nonnegative(),
198
+ split: z.enum(["public", "private", "held_out"]).optional(),
199
+ taskRef: z.string().min(1).optional(),
200
+ })
201
+ .strict(),
202
+ ]),
189
203
  executorJsonl: dagArtifactRefSchema,
190
204
  nodes: z.array(dagNodeReportRowSchema),
191
205
  })
@@ -502,6 +516,9 @@ export async function buildDagRunReportEntry(input) {
502
516
  pausedByNodeId: input.state.pausedByNodeId,
503
517
  pauseReason: input.state.pauseReason,
504
518
  ...summarizeControllerIdentityForReport(input.state.controllerIdentityRef, input.spec),
519
+ evaluationAssociation: input.state.evaluation
520
+ ? { status: "present", ...input.state.evaluation }
521
+ : { status: "absent-legacy" },
505
522
  nodes,
506
523
  };
507
524
  const primaryNode = resolvePrimaryFailureNode(partialEntry);
@@ -757,7 +774,9 @@ function formatConvergenceSummary(run) {
757
774
  `- convergence: enabled=${convergence.enabled}, pass=${convergence.currentPass}/${convergence.maxPasses}, terminal=${convergence.terminalReason ?? "-"}`,
758
775
  ];
759
776
  for (const pass of convergence.passHistory) {
760
- const verifySummary = pass.verifyPhase || pass.verifyQuota || pass.verifyCommandCount !== undefined
777
+ const verifySummary = pass.verifyPhase ||
778
+ pass.verifyQuota ||
779
+ pass.verifyCommandCount !== undefined
761
780
  ? `, verify=${pass.verifyPhase ?? "-"} quota=${pass.verifyQuota ?? "-"} commands=${pass.verifyCommandCount ?? "-"}`
762
781
  : "";
763
782
  lines.push(`- pass ${pass.pass}: ${pass.status}, reason=${pass.reason}, hardVerify=${pass.hardVerifyStatus ?? "-"}(${pass.hardVerifyFailureCategory ?? "-"})${verifySummary}, artifacts=${pass.artifactRefs.length}`);
@@ -809,6 +828,21 @@ export function formatDagReportMarkdown(report) {
809
828
  `- runDir: ${run.runDir}`,
810
829
  `- startedAt: ${run.startedAt}`,
811
830
  `- finishedAt: ${run.finishedAt ?? "-"}`,
831
+ ...(run.evaluationAssociation.status === "present"
832
+ ? [
833
+ `- evaluationAssociation: present`,
834
+ `- campaignId: ${run.evaluationAssociation.campaignId}`,
835
+ `- candidateId: ${run.evaluationAssociation.candidateId}`,
836
+ `- bundleHash: ${run.evaluationAssociation.bundleHash}`,
837
+ `- seed: ${run.evaluationAssociation.seed}`,
838
+ ...(run.evaluationAssociation.split
839
+ ? [`- split: ${run.evaluationAssociation.split}`]
840
+ : []),
841
+ ...(run.evaluationAssociation.taskRef
842
+ ? [`- taskRef: ${run.evaluationAssociation.taskRef}`]
843
+ : []),
844
+ ]
845
+ : ["- evaluationAssociation: absent/legacy"]),
812
846
  `- executorJsonl: ${run.executorJsonl.path} (${run.executorJsonl.exists ? "exists" : "missing"})`,
813
847
  ...formatConvergenceSummary(run),
814
848
  "",
@@ -1,6 +1,7 @@
1
1
  import { readdir, readFile } from "node:fs/promises";
2
2
  import { hostname } from "node:os";
3
3
  import path from "node:path";
4
+ import { readCandidateRecord } from "../../infrastructure/evaluation/candidate-store.js";
4
5
  import { CANONICAL_TASK_ID_PATTERN, formatLocalCompactDate, } from "../../task/runtime.js";
5
6
  import { getDagRunDir, isTerminalDagRunStatus, locateDagRun, readHumanApprovalArtifact, requireActiveDagRun, } from "./lifecycle.js";
6
7
  import { moveToCompletedRunDir, moveToPausedRunDir, prepareActiveRunDir, writeRunSpec, writeRunState, } from "./run-store.js";
@@ -15,7 +16,7 @@ import { relocateConvergenceArtifactPaths, relocateRunArtifactPaths, } from "./u
15
16
  import { createSkillSnapshot, prepareSkillSnapshotForContinuation, writeSkillSnapshot, } from "./skill-snapshot.js";
16
17
  import { buildNodePrompt, buildNodePromptWithResolvedSkillInstructions, executeDagNode, } from "./node-execution.js";
17
18
  import { runConvergencePassController, shouldEnableDagConvergence, } from "./convergence/controller.js";
18
- import { executeDagRanksOnce, isConditionSkippedReason, } from "./scheduler.js";
19
+ import { executeDagRanksOnce, isConditionSkippedReason } from "./scheduler.js";
19
20
  import { topoSortToRanks } from "./topo.js";
20
21
  import { parseDagSpec, resolveModelForTask, } from "./types.js";
21
22
  import { executeDynamicCondition } from "./dynamic-runtime/condition.js";
@@ -134,6 +135,7 @@ export function createInitialRunState(spec, opts, ranks, runId = opts.runId ?? "
134
135
  status: opts.initOnly || opts.dryRun ? "pending" : "running",
135
136
  ranks,
136
137
  nodes,
138
+ ...(spec.evaluation ? { evaluation: { ...spec.evaluation } } : {}),
137
139
  ...(shouldEnableDagConvergence(spec)
138
140
  ? {
139
141
  convergence: {
@@ -146,9 +148,32 @@ export function createInitialRunState(spec, opts, ranks, runId = opts.runId ?? "
146
148
  : {}),
147
149
  };
148
150
  }
151
+ export function assertFrozenEvaluationBinding(spec, state) {
152
+ const declared = spec.evaluation;
153
+ const frozen = state.evaluation;
154
+ if (!declared && !frozen)
155
+ return;
156
+ if (!declared ||
157
+ !frozen ||
158
+ JSON.stringify(declared) !== JSON.stringify(frozen)) {
159
+ throw new Error(`dag run ${state.runId} evaluation binding drifted between run.json and state.json; refuse to continue`);
160
+ }
161
+ }
162
+ export async function assertEvaluationBindingPreflight(cwd, binding) {
163
+ if (!binding)
164
+ return;
165
+ const record = await readCandidateRecord(cwd, binding.candidateId);
166
+ if (record.manifest.bundleHash !== binding.bundleHash) {
167
+ throw new Error(`evaluation bundleHash mismatch for ${binding.candidateId}: declared ${binding.bundleHash}, registry ${record.manifest.bundleHash}`);
168
+ }
169
+ if (!["eligible", "experimenting", "accepted"].includes(record.status)) {
170
+ throw new Error(`evaluation candidate ${binding.candidateId} lifecycle status ${record.status} is not executable`);
171
+ }
172
+ }
149
173
  export async function runDag(spec, opts) {
150
174
  assertValidDagSpec(spec);
151
- // Capability preflight must fail closed before any run directory or node work.
175
+ // Candidate identity is validated before run-id allocation or directory creation.
176
+ await assertEvaluationBindingPreflight(opts.cwd, spec.evaluation);
152
177
  const runningIdentity = resolveRunningControllerIdentity();
153
178
  if (!runningIdentity) {
154
179
  throw new Error("running controller identity could not be resolved; refuse to create an unpinned DAG run");
@@ -242,6 +267,7 @@ export async function resumeDagRun(opts) {
242
267
  if (isTerminalDagRunStatus(state.status)) {
243
268
  throw new Error(`dag run ${opts.runId} is already terminal (status=${state.status})`);
244
269
  }
270
+ assertFrozenEvaluationBinding(spec, state);
245
271
  // Runtime contract + controller identity must be re-verified before executing
246
272
  // any remaining node on resume; drift fails closed.
247
273
  const runningIdentity = resolveRunningControllerIdentity();
@@ -0,0 +1,383 @@
1
+ const OUT_OF_SCOPE_HEADING = /^(?:非目标|不包含|范围外|不在范围|out\s+of\s+scope|non[-\s]?goals?)(?:\s*(?:[((].*[))]|[::].*|[/—–-]\s*.*))?$/i;
2
+ const FRONTEND_TASK_TYPE = /^\s*(?:[-*]\s*)?task\s+type\s*:\s*frontend-feature\s*$/im;
3
+ const BACKEND_TASK_TYPE = /^\s*(?:[-*]\s*)?task\s+type\s*:\s*backend-feature\s*$/im;
4
+ const FRONTEND_NEGATION_PATTERNS = [
5
+ /(?:不涉及|无需|不需要|不包含|不修改|不实现|不交付|不要|禁止|不得|排除).{0,16}(?:前端(?:页面|组件|界面|功能|交互)?|页面|组件|界面|用户界面|浏览器交互)/,
6
+ /(?:前端(?:页面|组件|界面|功能|交互)?|页面|组件|界面|用户界面).{0,12}(?:不在(?:本次)?范围|无需|不需要|不修改|不实现|不交付|不要|禁止|不得)/,
7
+ /(?:不涉及|无需|不需要|不包含|不修改|不实现|不交付|不要|禁止|不得|排除).{0,16}(?<![A-Za-z0-9_])UI(?![A-Za-z0-9_])/i,
8
+ /(?<![A-Za-z0-9_])UI(?![A-Za-z0-9_]).{0,12}(?:不在(?:本次)?范围|无需|不需要|不修改|不实现|不交付|不要|禁止|不得)/i,
9
+ /\b(?:backend|server[-\s]?side)\s+only\b/i,
10
+ /\b(?:no|without)\s+(?:frontend|front[-\s]?end|ui|user\s+interface)\b/i,
11
+ /\b(?:do\s+not|don't|must\s+not|should\s+not)\s+(?:(?:add|implement|build|create|update|modify|fix|refactor|render|style|change|touch)\s+)?(?:(?:the|any|a|an)\s+)?(?:frontend|front[-\s]?end|ui|user\s+interface|page|component|form|modal|dialog)\b/i,
12
+ /\bwithout\s+(?:(?:adding|implementing|building|creating|updating|modifying|fixing|refactoring|rendering|styling|changing|touching)\s+)?(?:(?:the|any|a|an)\s+)?(?:frontend|front[-\s]?end|ui|user\s+interface|page|component|form|modal|dialog)\b/i,
13
+ /\b(?:frontend|front[-\s]?end|ui|user\s+interface|page|component|form|modal|dialog)\b[^.?!;\n]{0,20}\b(?:is|are)?\s*(?:out\s+of\s+scope|excluded|not\s+(?:required|needed)|unchanged)\b/i,
14
+ ];
15
+ const FRONTEND_DELIVERY_PATTERNS = [
16
+ /(?:新增|实现|开发|修改|重构|修复|优化|创建|渲染|搭建|改造)[^。!?;,,\n]{0,24}(?:前端(?:页面|组件|界面|功能|交互)|页面|组件|界面|表单|弹窗|对话框|用户界面|浏览器(?:页面|交互|行为))/,
17
+ /(?:前端(?:页面|组件|界面|功能|交互)|页面|组件|界面|表单|弹窗|对话框|用户界面|浏览器交互)[^。!?;,,\n]{0,16}(?:新增|实现|开发|修改|重构|修复|优化|创建|渲染|搭建|改造)/,
18
+ /\b(?:add|implement|build|create|update|modify|fix|refactor|render|style)\b[^.?!;\n]{0,48}\b(?:front[-\s]?end\s+(?:page|component|view|form|modal|interaction|ui)|ui|form|modal|dialog|dashboard|navigation|toast|button|dropdown|user\s+interface|browser\s+interaction)\b/i,
19
+ ];
20
+ const PATH_SUPPORTED_FRONTEND_DELIVERY_PATTERNS = [
21
+ /(?:新增|实现|开发|修改|重构|修复|优化|创建|渲染|搭建|改造)[^。!?;,,\n]{0,24}(?:页(?!\p{Script=Han})|表格|列表|按钮|导航|菜单|标签页|下拉框|选择器|提示|通知|加载状态|空状态|错误状态|交互|样式|布局)/u,
22
+ /(?:新增|实现|开发|修改|重构|修复|优化|创建|渲染|搭建|改造)[^。!?;,,\n]{0,24}(?<![A-Za-z0-9_])(?:DOM|UI|React|Vue|Svelte|Angular)(?![A-Za-z0-9_])/i,
23
+ /\b(?:add|implement|build|create|update|modify|fix|refactor|render|style)\b[^.?!;\n]{0,48}\b(?:page|component|screen|layout|dom|react|vue|svelte|angular)\b/i,
24
+ ];
25
+ const BACKEND_DELIVERY_PATTERNS = [
26
+ /(?:新增|实现|开发|修改|重构|修复|提供|创建|调整|优化|迁移|扩展)[^。!?;,,\n]{0,24}(?:后端(?:服务|接口|逻辑|API)?|服务端(?:服务|接口|逻辑)?|接口|数据库|数据表(?!格)|数据库索引|索引|控制器|服务层|仓储层)/i,
27
+ /(?:后端(?:服务|接口|逻辑|API)?|服务端(?:服务|接口|逻辑)?|接口|数据库|数据表(?!格)|数据库索引|控制器|服务层|仓储层)[^。!?;,,\n]{0,16}(?:新增|实现|开发|修改|重构|修复|提供|创建|调整|优化|迁移|扩展)/i,
28
+ /(?:新增|实现|开发|修改|重构|修复|提供|创建|调整|优化|迁移|扩展)[^。!?;,,\n]{0,24}(?<![A-Za-z0-9_])API(?![A-Za-z0-9_])/i,
29
+ /(?<![A-Za-z0-9_])API(?![A-Za-z0-9_])[^。!?;,,\n]{0,16}(?:新增|实现|开发|修改|重构|修复|提供|创建|调整|优化|迁移|扩展)/i,
30
+ /\b(?:add|implement|build|create|update|modify|fix|refactor|migrate|extend)\b[^.?!;\n]{0,48}\b(?:backend|server[-\s]?side|api|endpoint|database|migration|controller|service|repository)\b/i,
31
+ ];
32
+ const BACKEND_NEGATION_PATTERNS = [
33
+ /(?:不涉及|无需|不需要|不包含|不修改|不实现|不交付|不要|禁止|不得|排除).{0,16}(?:后端|服务端|接口|数据库|数据表(?!格)|索引)/,
34
+ /(?:不涉及|无需|不需要|不包含|不修改|不实现|不交付|不要|禁止|不得|排除).{0,16}(?<![A-Za-z0-9_])API(?![A-Za-z0-9_])/i,
35
+ /\b(?:no|without)\s+(?:backend|server[-\s]?side|api|endpoint|database)\b/i,
36
+ /\b(?:do\s+not|don't|must\s+not|should\s+not)\s+(?:(?:add|implement|build|create|update|modify|fix|refactor|migrate|change|touch)\s+)?(?:(?:the|any|a|an)\s+)?(?:backend|server[-\s]?side|api|endpoint|database)\b/i,
37
+ ];
38
+ const FRONTEND_PATH_PATTERNS = [
39
+ /(?:^|\/)apps\/web\/src(?:\/|$)/i,
40
+ /(?:^|\/)(?:frontend|client)(?:\/|$)/i,
41
+ /(?:^|\/)src\/(?:pages?|components?|views?)(?:\/|$)/i,
42
+ /\.(?:tsx|jsx|vue|svelte|css|scss|less)(?:$|[*?])/i,
43
+ ];
44
+ const BACKEND_PATH_PATTERNS = [
45
+ /(?:^|\/)(?:backend|server|api|controllers?|services?|repositories?|db|database|migrations?)(?:\/|$)/i,
46
+ /\.(?:sql)(?:$|[*?])/i,
47
+ ];
48
+ const NON_PRODUCT_PATH_SEGMENT = /(?:^|\/)(?:docs?|skills?|tests?|__tests__|e2e|cypress)(?:\/|$)/i;
49
+ const FRONTEND_PATH_EXCLUSIONS = NON_PRODUCT_PATH_SEGMENT;
50
+ const DOCUMENTATION_ONLY_MARKERS = /(?:文档|说明|指南|规范|测试用例|测试计划|评审|审查|\b(?:docs?|documentation|guide|specification|test\s+cases?|test\s+plan|review)\b)/i;
51
+ const DOCUMENTATION_ONLY_ACTIONS = /(?:新增|实现|开发|创建|编写|更新|修改|重构|修复|优化|渲染|搭建|改造|补充|生成|评审|审查|\b(?:add|implement|build|create|write|update|modify|refactor|fix|optimize|render|style|document|review|generate)\b)/i;
52
+ const PRODUCT_AND_DOCUMENT_DELIVERY = /(?:新增|实现|开发|修改|重构|修复|优化|创建|渲染|搭建|改造)[^。!?;,,\n]{0,24}(?:前端|页面|组件|界面|表单|交互|样式|布局|DOM|UI|React|Vue|Svelte|Angular|源码|代码)[^。!?;,,\n]{0,16}(?:并|同时|以及|和|与|\band\b)[^。!?;,,\n]{0,16}(?:文档|说明|指南|规范|\b(?:docs?|documentation|guide)\b)/i;
53
+ const TEST_ONLY_MARKERS = /(?:测试|用例|\b(?:e2e|unit|integration|component)\s+tests?\b|\btests?\b)/i;
54
+ const PRODUCT_AND_TEST_DELIVERY = /(?:并|同时|以及|和|与|\band\b).{0,12}(?:添加|新增|编写|补充|测试|\b(?:add|write|test)\b)/i;
55
+ const NON_PRODUCT_ALLOWED_PATH_PATTERNS = [
56
+ NON_PRODUCT_PATH_SEGMENT,
57
+ /\.(?:test|spec)\.[^/]+$/i,
58
+ /\.cy\.[^/]+$/i,
59
+ /\.mdx?(?:$|[*?])/i,
60
+ ];
61
+ const SPECIAL_TEMPLATE_BY_TASK_KIND = {
62
+ "frontend-implementation": "frontend-implementation",
63
+ "frontend-test": "frontend-test-dag",
64
+ "backend-test": "backend-test-dag",
65
+ "knowledge-sync": "knowledge-sync-dag",
66
+ "knowledge-graph-bootstrap": "knowledge-graph-bootstrap-dag",
67
+ };
68
+ const TASK_KIND_ROUTING_REASON = {
69
+ "frontend-implementation": 'taskKind "frontend-implementation" selects the dedicated frontend DAG template',
70
+ "frontend-test": 'taskKind "frontend-test" selects the dedicated frontend test DAG template',
71
+ "backend-test": 'taskKind "backend-test" selects the dedicated backend test DAG template',
72
+ "knowledge-sync": 'taskKind "knowledge-sync" selects the dedicated knowledge-sync DAG template',
73
+ "knowledge-graph-bootstrap": 'taskKind "knowledge-graph-bootstrap" selects the dedicated knowledge-graph bootstrap DAG template',
74
+ };
75
+ const SPECIAL_REQUESTED_TEMPLATES = new Set([
76
+ "frontend-implementation",
77
+ "frontend-test-dag",
78
+ "backend-test-dag",
79
+ "knowledge-sync-dag",
80
+ "knowledge-graph-bootstrap-dag",
81
+ ]);
82
+ function stripOutOfScopeSections(markdown) {
83
+ const output = [];
84
+ let skippedHeadingLevel = null;
85
+ for (const line of markdown.split(/\r?\n/)) {
86
+ const heading = /^(#{1,6})\s+(.+?)\s*$/.exec(line.trim());
87
+ if (heading) {
88
+ const level = heading[1].length;
89
+ if (skippedHeadingLevel !== null && level <= skippedHeadingLevel) {
90
+ skippedHeadingLevel = null;
91
+ }
92
+ if (OUT_OF_SCOPE_HEADING.test(heading[2].trim())) {
93
+ skippedHeadingLevel = level;
94
+ continue;
95
+ }
96
+ }
97
+ if (skippedHeadingLevel === null)
98
+ output.push(line);
99
+ }
100
+ return output.join("\n");
101
+ }
102
+ function stripFencedCodeBlocks(markdown) {
103
+ const output = [];
104
+ let fenceCharacter = null;
105
+ for (const line of markdown.split(/\r?\n/)) {
106
+ const fence = /^\s*(`{3,}|~{3,})/.exec(line);
107
+ if (fence) {
108
+ const character = fence[1][0];
109
+ if (fenceCharacter === null)
110
+ fenceCharacter = character;
111
+ else if (fenceCharacter === character)
112
+ fenceCharacter = null;
113
+ continue;
114
+ }
115
+ if (fenceCharacter === null)
116
+ output.push(line);
117
+ }
118
+ return output.join("\n");
119
+ }
120
+ /**
121
+ * Normalize task prose before deterministic demand/capability signal checks.
122
+ * Examples and explicit non-goals must not become positive routing evidence.
123
+ */
124
+ export function normalizeTaskRequirementText(markdown) {
125
+ return stripOutOfScopeSections(stripFencedCodeBlocks(markdown));
126
+ }
127
+ function splitClauses(text) {
128
+ return text
129
+ .split(/[。!?!?;;,,\r\n]+/)
130
+ .map((clause) => clause.trim())
131
+ .filter(Boolean);
132
+ }
133
+ function matchesAny(value, patterns) {
134
+ return patterns.some((pattern) => pattern.test(value));
135
+ }
136
+ function isFrontendNegated(clause) {
137
+ return matchesAny(clause, FRONTEND_NEGATION_PATTERNS);
138
+ }
139
+ function isDocumentationOnlyClause(clause) {
140
+ if (!DOCUMENTATION_ONLY_ACTIONS.test(clause))
141
+ return false;
142
+ if (DOCUMENTATION_ONLY_MARKERS.test(clause)) {
143
+ return !PRODUCT_AND_DOCUMENT_DELIVERY.test(clause);
144
+ }
145
+ return TEST_ONLY_MARKERS.test(clause) && !PRODUCT_AND_TEST_DELIVERY.test(clause);
146
+ }
147
+ function masksExistingBackendDependency(clause) {
148
+ return clause
149
+ .replace(/(?:调用|使用|消费|对接|依赖)(?:现有|已有)?[^。!?;,,\n]{0,12}(?:后端|服务端|API|接口)/gi, " ")
150
+ .replace(/\b(?:call|consume|use|integrate\s+with|depend\s+on)\b[^.?!;\n]{0,24}\b(?:existing|current)?\s*(?:backend|server[-\s]?side|api|endpoint)\b/gi, " ");
151
+ }
152
+ function addSignal(signals, signal) {
153
+ if (signals.some((current) => current.id === signal.id))
154
+ return;
155
+ signals.push(signal);
156
+ }
157
+ function collectTextSignals(input) {
158
+ let frontendDelivery = false;
159
+ let pathSupportedFrontendDelivery = false;
160
+ let backendDelivery = false;
161
+ let frontendNegated = false;
162
+ for (const clause of splitClauses(input.text)) {
163
+ const negated = isFrontendNegated(clause);
164
+ frontendNegated ||= negated;
165
+ const documentationOnly = isDocumentationOnlyClause(clause);
166
+ if (!negated &&
167
+ !documentationOnly &&
168
+ matchesAny(clause, FRONTEND_DELIVERY_PATTERNS)) {
169
+ frontendDelivery = true;
170
+ addSignal(input.signals, {
171
+ id: `frontend-delivery-${input.source}`,
172
+ source: input.source,
173
+ message: `${input.source} contains an implementation action and a frontend UI deliverable`,
174
+ });
175
+ }
176
+ if (!negated &&
177
+ !documentationOnly &&
178
+ matchesAny(clause, PATH_SUPPORTED_FRONTEND_DELIVERY_PATTERNS)) {
179
+ pathSupportedFrontendDelivery = true;
180
+ }
181
+ const backendClause = masksExistingBackendDependency(clause);
182
+ if (!matchesAny(clause, BACKEND_NEGATION_PATTERNS) &&
183
+ matchesAny(backendClause, BACKEND_DELIVERY_PATTERNS)) {
184
+ backendDelivery = true;
185
+ addSignal(input.signals, {
186
+ id: `backend-delivery-${input.source}`,
187
+ source: input.source,
188
+ message: `${input.source} contains an implementation action and a backend deliverable`,
189
+ });
190
+ }
191
+ }
192
+ return {
193
+ frontendDelivery,
194
+ pathSupportedFrontendDelivery,
195
+ backendDelivery,
196
+ frontendNegated,
197
+ };
198
+ }
199
+ function normalizedAllowedPath(value) {
200
+ return value.trim().replace(/\\/g, "/").replace(/^\.\//, "");
201
+ }
202
+ function reasonsForClassification(input) {
203
+ const signalIds = input.signals.map((signal) => signal.id);
204
+ const reasons = [`task demand classified as ${input.kind}`];
205
+ if (signalIds.length > 0)
206
+ reasons.push(`signals: ${signalIds.join(", ")}`);
207
+ if (input.blockers.length > 0)
208
+ reasons.push(`blockers: ${input.blockers.join(", ")}`);
209
+ return reasons;
210
+ }
211
+ export function classifyTaskDemand(input) {
212
+ const signals = [];
213
+ const blockers = [];
214
+ const requirement = normalizeTaskRequirementText(input.requirementMarkdown);
215
+ const hasFrontendTaskType = FRONTEND_TASK_TYPE.test(requirement);
216
+ const hasBackendTaskType = BACKEND_TASK_TYPE.test(requirement);
217
+ if (hasFrontendTaskType) {
218
+ addSignal(signals, {
219
+ id: "task-type-frontend-feature",
220
+ source: "requirement",
221
+ message: "requirement contains the materialized frontend-feature task type",
222
+ });
223
+ }
224
+ if (hasBackendTaskType) {
225
+ addSignal(signals, {
226
+ id: "task-type-backend-feature",
227
+ source: "requirement",
228
+ message: "requirement contains the materialized backend-feature task type",
229
+ });
230
+ }
231
+ const titleSignals = collectTextSignals({
232
+ text: input.title,
233
+ source: "title",
234
+ signals,
235
+ });
236
+ const requirementSignals = collectTextSignals({
237
+ text: requirement,
238
+ source: "requirement",
239
+ signals,
240
+ });
241
+ const frontendNegated = titleSignals.frontendNegated || requirementSignals.frontendNegated;
242
+ if (frontendNegated)
243
+ blockers.push("frontend-negated");
244
+ let frontendPath = false;
245
+ let backendPath = false;
246
+ for (const rawPath of input.allowedPaths) {
247
+ const allowedPath = normalizedAllowedPath(rawPath);
248
+ if (!FRONTEND_PATH_EXCLUSIONS.test(allowedPath) &&
249
+ matchesAny(allowedPath, FRONTEND_PATH_PATTERNS)) {
250
+ frontendPath = true;
251
+ }
252
+ if (matchesAny(allowedPath, BACKEND_PATH_PATTERNS))
253
+ backendPath = true;
254
+ }
255
+ if (frontendPath) {
256
+ addSignal(signals, {
257
+ id: "frontend-path",
258
+ source: "allowed-path",
259
+ message: "allowedPaths include a frontend-specific source path",
260
+ });
261
+ }
262
+ if (backendPath) {
263
+ addSignal(signals, {
264
+ id: "backend-path",
265
+ source: "allowed-path",
266
+ message: "allowedPaths include a backend-specific source path",
267
+ });
268
+ }
269
+ const pathSupportedFrontendDelivery = frontendPath &&
270
+ (titleSignals.pathSupportedFrontendDelivery ||
271
+ requirementSignals.pathSupportedFrontendDelivery);
272
+ if (pathSupportedFrontendDelivery) {
273
+ addSignal(signals, {
274
+ id: requirementSignals.pathSupportedFrontendDelivery
275
+ ? "frontend-path-supported-delivery-requirement"
276
+ : "frontend-path-supported-delivery-title",
277
+ source: requirementSignals.pathSupportedFrontendDelivery
278
+ ? "requirement"
279
+ : "title",
280
+ message: "task text contains a frontend UI artifact and allowedPaths provide frontend source-path evidence",
281
+ });
282
+ }
283
+ const frontendDelivery = titleSignals.frontendDelivery ||
284
+ requirementSignals.frontendDelivery ||
285
+ pathSupportedFrontendDelivery;
286
+ const allowedPathsOnlyCoverNonProductArtifacts = input.allowedPaths.length > 0 &&
287
+ input.allowedPaths.every((rawPath) => {
288
+ const allowedPath = normalizedAllowedPath(rawPath);
289
+ return matchesAny(allowedPath, NON_PRODUCT_ALLOWED_PATH_PATTERNS);
290
+ });
291
+ if (allowedPathsOnlyCoverNonProductArtifacts && frontendDelivery) {
292
+ blockers.push("non-product-allowed-paths");
293
+ }
294
+ const eligibleFrontendDelivery = frontendDelivery && !allowedPathsOnlyCoverNonProductArtifacts;
295
+ const backendDelivery = titleSignals.backendDelivery || requirementSignals.backendDelivery;
296
+ const taskTypeConflict = (hasFrontendTaskType && (backendDelivery || frontendNegated)) ||
297
+ (hasBackendTaskType && eligibleFrontendDelivery);
298
+ if (taskTypeConflict)
299
+ blockers.push("task-type-conflict");
300
+ let kind;
301
+ if ((hasFrontendTaskType && hasBackendTaskType) || taskTypeConflict) {
302
+ kind = "mixed";
303
+ }
304
+ else if (hasFrontendTaskType) {
305
+ kind = "frontend";
306
+ }
307
+ else if (hasBackendTaskType) {
308
+ kind = "backend";
309
+ }
310
+ else if (eligibleFrontendDelivery && backendDelivery) {
311
+ kind = "mixed";
312
+ }
313
+ else if (eligibleFrontendDelivery && frontendNegated) {
314
+ kind = "mixed";
315
+ }
316
+ else if (eligibleFrontendDelivery) {
317
+ kind = "frontend";
318
+ }
319
+ else if (backendDelivery) {
320
+ kind = "backend";
321
+ }
322
+ else {
323
+ kind = "unknown";
324
+ }
325
+ return {
326
+ kind,
327
+ signals,
328
+ blockers,
329
+ reasons: reasonsForClassification({ kind, signals, blockers }),
330
+ };
331
+ }
332
+ export function resolveTaskDagTemplateSelection(input) {
333
+ const requestedTemplate = input.requestedTemplate ?? "standard-dag";
334
+ const taskKindTemplate = SPECIAL_TEMPLATE_BY_TASK_KIND[input.taskKind];
335
+ if (taskKindTemplate) {
336
+ return {
337
+ template: taskKindTemplate,
338
+ source: "taskKind",
339
+ reasons: [TASK_KIND_ROUTING_REASON[input.taskKind]],
340
+ };
341
+ }
342
+ if (SPECIAL_REQUESTED_TEMPLATES.has(requestedTemplate)) {
343
+ return {
344
+ template: requestedTemplate,
345
+ source: "requested-template",
346
+ reasons: [],
347
+ };
348
+ }
349
+ if (input.taskKind !== "standard") {
350
+ return {
351
+ template: requestedTemplate,
352
+ source: "requested-template",
353
+ reasons: [],
354
+ };
355
+ }
356
+ if (requestedTemplate !== "standard-dag") {
357
+ return {
358
+ template: requestedTemplate,
359
+ source: "requested-template",
360
+ reasons: [],
361
+ };
362
+ }
363
+ const classification = classifyTaskDemand(input);
364
+ if (classification.kind === "frontend") {
365
+ const frontendSignals = classification.signals
366
+ .map((signal) => signal.id)
367
+ .filter((id) => id === "task-type-frontend-feature" || id.startsWith("frontend-"));
368
+ return {
369
+ template: "frontend-implementation",
370
+ source: "task-source",
371
+ reasons: [
372
+ `task source classified as frontend from signals: ${frontendSignals.join(", ")}`,
373
+ ],
374
+ classification,
375
+ };
376
+ }
377
+ return {
378
+ template: requestedTemplate,
379
+ source: "requested-template",
380
+ reasons: [],
381
+ classification,
382
+ };
383
+ }