@tea-agent/loop-agent 0.23.1 → 0.24.1

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 (71) hide show
  1. package/CHANGELOG.md +61 -1
  2. package/README.md +1 -1
  3. package/bin/agent-worker.js +0 -0
  4. package/dist/commands/init.js +2 -2
  5. package/dist/executors/shell-executor.js +20 -7
  6. package/dist/shared/operator/capabilities.js +475 -2
  7. package/dist/task/dag-source-paths.js +50 -0
  8. package/dist/task/frontend-project-capability.js +316 -0
  9. package/dist/task/task-demand-routing.js +432 -0
  10. package/dist/worker/console/app-data.js +2 -0
  11. package/dist/worker/console/chat/artifact-card.js +23 -0
  12. package/dist/worker/console/chat/chat-event-store.js +495 -0
  13. package/dist/worker/console/chat/chat-ui-policy.js +25 -0
  14. package/dist/worker/console/chat/compaction-errors.js +64 -0
  15. package/dist/worker/console/chat/composer-draft-store.js +45 -0
  16. package/dist/worker/console/chat/context-panel.js +54 -0
  17. package/dist/worker/console/chat/contract-apply-receipt-store.js +174 -0
  18. package/dist/worker/console/chat/explore-tools.js +299 -0
  19. package/dist/worker/console/chat/human-gate-card.js +37 -0
  20. package/dist/worker/console/chat/interview-adapter.js +149 -0
  21. package/dist/worker/console/chat/operation-card.js +23 -0
  22. package/dist/worker/console/chat/pi-console-config.js +158 -0
  23. package/dist/worker/console/chat/pi-runtime.js +581 -43
  24. package/dist/worker/console/chat/repo-browser.js +140 -0
  25. package/dist/worker/console/chat/repo-walk.js +116 -0
  26. package/dist/worker/console/chat/resource-loader.js +18 -17
  27. package/dist/worker/console/chat/routes.js +1359 -65
  28. package/dist/worker/console/chat/runtime-context.js +24 -0
  29. package/dist/worker/console/chat/runtime-selection.js +37 -0
  30. package/dist/worker/console/chat/session-store.js +210 -11
  31. package/dist/worker/console/chat/shortcuts.js +15 -0
  32. package/dist/worker/console/chat/tool-adapter.js +81 -194
  33. package/dist/worker/console/chat/tools.js +72 -48
  34. package/dist/worker/console/chat/usage.js +37 -0
  35. package/dist/worker/console/chat/workspace-landing.js +56 -0
  36. package/dist/worker/console/dag-confirmation.js +42 -8
  37. package/dist/worker/console/human-gate-token.js +130 -0
  38. package/dist/worker/console/interview/grill-me.js +28 -5
  39. package/dist/worker/console/mutation-gate-receipt-store.js +184 -0
  40. package/dist/worker/console/operation-runner.js +6 -2
  41. package/dist/worker/console/operation-sse.js +26 -0
  42. package/dist/worker/console/operator-actions.js +420 -7
  43. package/dist/worker/console/server.js +14 -2
  44. package/dist/worker/console/static/assets/index-Cv68Ge5n.js +27 -0
  45. package/dist/worker/console/static/assets/index-Dp_2sKGk.css +1 -0
  46. package/dist/worker/console/static/index.html +2 -2
  47. package/dist/worker/observe/routes.js +8 -4
  48. package/dist/workflows/dag/backend-test-intake-context.js +10 -12
  49. package/dist/workflows/dag/backend-test-markdown-workflow.js +232 -47
  50. package/dist/workflows/dag/backend-test-result-contract.js +233 -0
  51. package/dist/workflows/dag/frontend-implementation-contract.js +23 -17
  52. package/dist/workflows/dag/frontend-lint-baseline.js +4 -4
  53. package/dist/workflows/dag/frontend-project-capability.js +1 -316
  54. package/dist/workflows/dag/frontend-worktree-diff.js +4 -1
  55. package/dist/workflows/dag/init-hybrid.js +46 -43
  56. package/dist/workflows/dag/rerun-plan.js +6 -10
  57. package/dist/workflows/dag/task-demand-routing.js +1 -383
  58. package/docs/README.md +1 -1
  59. package/docs/architecture/README.md +5 -5
  60. package/docs/architecture/evolution.md +4 -4
  61. package/docs/architecture/worker-and-feature.md +1 -1
  62. package/docs/templates/backend-test-dag.json +2 -2
  63. package/docs/templates/frontend-implementation-contract.schema.json +1 -1
  64. package/harness.json +1 -1
  65. package/package.json +1 -1
  66. package/skills/frontend-implementation/SKILL.md +7 -0
  67. package/skills/frontend-implementation/references/node-contracts.md +1 -1
  68. package/skills/frontend-verification/SKILL.md +4 -3
  69. package/skills/loop-agent/references/hybrid-dag.md +1 -1
  70. package/dist/worker/console/static/assets/index-DVl7Jxt5.js +0 -25
  71. package/dist/worker/console/static/assets/index-lVcIr9Ju.css +0 -1
@@ -29,6 +29,7 @@ import { buildFrontendCaseEvidenceValidateShellSnippet, buildFrontendTestOutcome
29
29
  import { classifyFrontendRisk, } from "./frontend-risk.js";
30
30
  import { discoverFrontendProjectCapability, } from "./frontend-project-capability.js";
31
31
  import { FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID, loadFrontendImplementationContractJsonSchema, } from "./frontend-implementation-contract.js";
32
+ import { serializeDagTaskSourcePath } from "../../task/dag-source-paths.js";
32
33
  const REQUIREMENT_FILE = "需求.md";
33
34
  const CONSTRAINT_FILE = "执行约束.md";
34
35
  const REFERENCE_DIRECTORY = "references";
@@ -1062,8 +1063,12 @@ function deriveFrontendBehaviorPaths(taskConfig) {
1062
1063
  return explicitTestPaths;
1063
1064
  return taskConfig.allowedPaths;
1064
1065
  }
1065
- function toTaskRelativeSourcePath(sources, absolutePath) {
1066
- return path.relative(sources.taskDir, absolutePath).replaceAll(path.sep, "/");
1066
+ function toDagSourcePath(sources, absolutePath) {
1067
+ return serializeDagTaskSourcePath({
1068
+ repoRoot: sources.repoRoot,
1069
+ taskDir: sources.taskDir,
1070
+ absolutePath,
1071
+ });
1067
1072
  }
1068
1073
  function extractExplicitRequirementIds(...markdownInputs) {
1069
1074
  const ids = [];
@@ -1124,7 +1129,7 @@ function buildDagSourceBinding(sources) {
1124
1129
  taskId: sources.taskId,
1125
1130
  sources: sourceEntries.map((source) => ({
1126
1131
  kind: source.kind,
1127
- path: toTaskRelativeSourcePath(sources, source.path),
1132
+ path: toDagSourcePath(sources, source.path),
1128
1133
  sha256: createHash("sha256")
1129
1134
  .update(source.markdown, "utf8")
1130
1135
  .digest("hex"),
@@ -1149,13 +1154,13 @@ function buildBackendTestAnalysisSourceBindingContract(sources) {
1149
1154
  };
1150
1155
  }
1151
1156
  function buildSourceContextBlock(sources) {
1152
- const requirementRef = toTaskRelativeSourcePath(sources, sources.requirementPath);
1157
+ const requirementRef = toDagSourcePath(sources, sources.requirementPath);
1153
1158
  const requirementExcerpt = excerptMarkdown(sources.requirementMarkdown, {
1154
1159
  sourceRef: requirementRef,
1155
1160
  });
1156
1161
  const parts = ["## Task source: 需求.md", requirementExcerpt.text];
1157
1162
  if (sources.constraintMarkdown) {
1158
- const constraintRef = toTaskRelativeSourcePath(sources, sources.constraintPath);
1163
+ const constraintRef = toDagSourcePath(sources, sources.constraintPath);
1159
1164
  const constraintExcerpt = excerptMarkdown(sources.constraintMarkdown, {
1160
1165
  sourceRef: constraintRef,
1161
1166
  });
@@ -1165,7 +1170,7 @@ function buildSourceContextBlock(sources) {
1165
1170
  const relativePath = path
1166
1171
  .relative(path.join(sources.taskDir, "source"), reference.path)
1167
1172
  .replaceAll(path.sep, "/");
1168
- const referenceRef = toTaskRelativeSourcePath(sources, reference.path);
1173
+ const referenceRef = toDagSourcePath(sources, reference.path);
1169
1174
  const referenceExcerpt = excerptMarkdown(reference.markdown, {
1170
1175
  sourceRef: referenceRef,
1171
1176
  });
@@ -1289,7 +1294,7 @@ export async function loadTaskHybridSources(repoRoot, taskId) {
1289
1294
  };
1290
1295
  return sources;
1291
1296
  }
1292
- async function prepareFrontendMockSources(sources) {
1297
+ async function prepareFrontendMockSources(sources, discoveredProjectCapability) {
1293
1298
  const repoRoot = sources.repoRoot ?? process.cwd();
1294
1299
  const capability = await discoverFrontendMockCapability(repoRoot, sources.taskConfig);
1295
1300
  const sourceMockVerifyCommands = extractFrontendMockVerifyCommandsFromMarkdown({
@@ -1302,7 +1307,8 @@ async function prepareFrontendMockSources(sources) {
1302
1307
  capability.verifyCommands.push(command);
1303
1308
  }
1304
1309
  }
1305
- const projectCapability = await discoverFrontendProjectCapability(repoRoot);
1310
+ const projectCapability = discoveredProjectCapability ??
1311
+ (await discoverFrontendProjectCapability(repoRoot));
1306
1312
  const frontendRisk = classifyFrontendRisk({
1307
1313
  title: sources.taskConfig.title,
1308
1314
  requirementMarkdown: sources.requirementMarkdown,
@@ -1936,6 +1942,8 @@ async function buildFrontendHybridDagFromTask(sources) {
1936
1942
  "## Critical rules",
1937
1943
  "- verificationTargets is a TOP-LEVEL required array",
1938
1944
  "- uiStates items use name/applicable/expectedBehavior/implementationTargets/verificationTargetIds/notApplicableReason",
1945
+ "- Use uiStates: [] for frontend logic changes with no user-visible UI state. Do not invent UI states.",
1946
+ "- For applicable=true, provide non-empty expectedBehavior plus non-empty implementationTargets and verificationTargetIds. For applicable=false, provide non-empty notApplicableReason and omit expectedBehavior instead of emitting an empty string.",
1939
1947
  "- mockApi.productionDefaultOff must always be true (including strategy: not-needed)",
1940
1948
  "- All implementation files, verification files, symbols, and commands must be discovered from the current target workspace and current task. Never copy paths, symbols, or commands from the loop-agent repository, an example task, or prior run output.",
1941
1949
  "- Use relative POSIX paths rooted at the target workspace. Do not assume a particular src/test directory layout; preserve the target project's actual app/, packages/, spec/, __tests__, or other layout.",
@@ -2299,11 +2307,7 @@ async function buildFrontendHybridDagFromTask(sources) {
2299
2307
  ...(lintShellCommands.length > 0
2300
2308
  ? ["frontend-lint-baseline-shell"]
2301
2309
  : []),
2302
- "frontend-plan-revision-pi",
2303
- "frontend-final-design-review-pi",
2304
- "frontend-plan-pi",
2305
2310
  ],
2306
- dependsPolicy: "all-or-condition-skip",
2307
2311
  role: "implementer",
2308
2312
  executor: "pi",
2309
2313
  toolProfile: "write",
@@ -2316,6 +2320,7 @@ async function buildFrontendHybridDagFromTask(sources) {
2316
2320
  outputContract: "Markdown delivery summary with Contract Ref (path/schema/hash), Changed Files, Requirements Implemented, UI States, Tests Changed, Verification Attempts, Deviations, and Residual Risks. Follow fixed stages: contract confirm → tests → component/state → API/Mock → focused checks → diff cleanup.",
2317
2321
  subtask_prompt: [
2318
2322
  "Implement against the validated run-owned Frontend Implementation Contract from frontend-prewrite-gate-shell (path/schema/hash). Do not rebuild the contract from Markdown alone.",
2323
+ "The canonical contract already contains the approved requirement, target-file, UI-state, verification, design, and Mock/API decisions. Do not re-open task sources, OpenSpec, AI workspace, plan/revision, or design-review prose, and do not repeat broad repository research. Inspect only contract target files and directly related local code needed to implement them.",
2319
2324
  "Execute in fixed stages and report each in the delivery summary: (1) Contract confirm, (2) Tests sync, (3) Component/UI state implementation, (4) API/Mock wiring per contract.mockApi, (5) Focused checks behind frozen entrypoints only, (6) Diff cleanup.",
2320
2325
  "Map every requirement id and applicable UI state from the contract to concrete files. Do not invent shell verification commands; only frozen static/behavior entrypoints will run.",
2321
2326
  "Implement only the approved Mock strategy carried by the validated contract. Preserve the real request path as the default, require explicit test/dev activation, and never comment out or replace the real request with inline data.",
@@ -2323,8 +2328,6 @@ async function buildFrontendHybridDagFromTask(sources) {
2323
2328
  "For native, browser-intercept, or request-adapter, implement contract-aligned fixtures/states and a dev/test-only activation boundary in this same writer. For not-needed, do not add Mock files or a framework and state the positive reason.",
2324
2329
  "Do not write root artifacts/** unless explicitly included in writeSet. Do not claim Browser/visual verification.",
2325
2330
  writerDeliveryContract(taskConfig),
2326
- sourceContext,
2327
- mockContextBlock,
2328
2331
  ]
2329
2332
  .filter((value) => Boolean(value))
2330
2333
  .join("\n\n"),
@@ -2379,11 +2382,10 @@ async function buildFrontendHybridDagFromTask(sources) {
2379
2382
  subtask_prompt: [
2380
2383
  "Read contracts/frontend-repair-assessment.json and the validated frontend implementation contract.",
2381
2384
  "This node runs only for eligible=true. Apply the smallest fix for the classified repairable failure inside the original implement writeSet only.",
2385
+ "The repair assessment and canonical implementation contract are complete inputs for this phase. Do not re-open task sources, OpenSpec, AI workspace, plan/revision, or design-review prose, and do not repeat repository-wide discovery.",
2382
2386
  "Do not change lint/type/test config, do not add .skip/.only, do not comment out real requests, do not default-enable Mock, do not add dependencies.",
2383
2387
  "Do not re-plan requirements or expand allowed paths. Browser/visual remain not-run.",
2384
2388
  writerDeliveryContract(taskConfig),
2385
- sourceContext,
2386
- mockContextBlock,
2387
2389
  ]
2388
2390
  .filter((value) => Boolean(value))
2389
2391
  .join("\n\n"),
@@ -2449,19 +2451,7 @@ async function buildFrontendHybridDagFromTask(sources) {
2449
2451
  },
2450
2452
  {
2451
2453
  id: "frontend-review-pi",
2452
- depends_on: [
2453
- "frontend-review-context-shell",
2454
- "frontend-reverify-shell",
2455
- "frontend-repair-pi",
2456
- "frontend-verify-assess-shell",
2457
- implementId,
2458
- "frontend-prewrite-gate-shell",
2459
- "frontend-plan-pi",
2460
- "frontend-design-review-pi",
2461
- "frontend-plan-revision-pi",
2462
- "frontend-final-design-review-pi",
2463
- ],
2464
- dependsPolicy: "all-or-condition-skip",
2454
+ depends_on: ["frontend-review-context-shell"],
2465
2455
  role: "reviewer",
2466
2456
  executor: "pi",
2467
2457
  complexity: "HIGH",
@@ -2477,13 +2467,11 @@ async function buildFrontendHybridDagFromTask(sources) {
2477
2467
  "Read contracts/frontend-review-context.json from frontend-review-context-shell. It binds the validated implementation contract, frontend lint assessment when lint is configured, effective initial-or-post-repair verification trace, repair assessment, and the run-owned actual diff (contracts/frontend-worktree-diff.json + artifacts/diff_patch.patch). Do not claim actual diff is missing when those artifacts exist; do not invent a diff from the implementation summary alone. Trace proves command/file/symbol binding only—not semantic correctness.",
2478
2468
  "Treat lint status exactly as passed | baseline-debt | failed | unavailable. baseline-debt may continue only with intact evidence and zero diagnostics on writer-changed files; report the tolerated debt count and never rewrite it as lint passed. Typecheck, build, and test still require successful final exits.",
2479
2469
  "Flag .skip/.only, deleted or weakened tests, unauthorized config changes, Mock-only evidence claimed as real integration, and Browser/visual claims (always not-run in this workflow).",
2480
- "Use the direct contract and the effective plan/design branch: original plan plus initial pass when revision was skipped, or revised plan plus final design review when revision ran. Do not infer them from the implementation summary.",
2470
+ "The contract embedded in frontend-review-context.json is the effective reviewed plan materialized by the prewrite gate. Do not re-open task sources, OpenSpec, AI workspace, plan/revision, design-review, writer summary, or verification node prose. Inspect only the canonical review context, its bound diff, and diff-referenced files when semantic review requires source code.",
2481
2471
  "Treat a commented-out real request, default-enabled Mock, production entrypoint importing test mocks, API/fixture contract drift, unauthorized Mock dependency/path, or missing behavior evidence for the selected strategy as at least Important. Mock strategies require Mock-backed evidence. not-needed requires applicable real/no-remote behavior evidence unless auto mode explicitly skipped Mock because no project Mock capability exists; in that case verify that the real request remains the default and the Real Integration Gap is preserved.",
2482
2472
  "Inspect the frontend-verify-assess-shell or selected frontend-reverify-shell evidence in the review context directly, including the production/default-real-path static check, and require Mock activation to be off for that check.",
2483
2473
  "Distinguish Mock-backed evidence from real API integration evidence and preserve the Real Integration Gap when the backend was not exercised.",
2484
2474
  "Review implementation quality, behavior/state coverage, verification evidence, and maintainability. Read-only: do not modify files.",
2485
- sourceContext,
2486
- mockContextBlock,
2487
2475
  ].join("\n\n"),
2488
2476
  },
2489
2477
  {
@@ -2512,15 +2500,9 @@ async function buildFrontendHybridDagFromTask(sources) {
2512
2500
  {
2513
2501
  id: "frontend-closeout-pi",
2514
2502
  depends_on: [
2515
- "frontend-review-gate-shell",
2516
- "frontend-review-pi",
2517
2503
  "frontend-review-context-shell",
2518
- "frontend-reverify-shell",
2519
- "frontend-repair-pi",
2520
- "frontend-verify-assess-shell",
2521
- "frontend-prewrite-gate-shell",
2504
+ "frontend-review-gate-shell",
2522
2505
  ],
2523
- dependsPolicy: "all-or-condition-skip",
2524
2506
  role: "closeout",
2525
2507
  executor: "pi",
2526
2508
  complexity: "MED",
@@ -2533,12 +2515,11 @@ async function buildFrontendHybridDagFromTask(sources) {
2533
2515
  outputContract: "Markdown closeout summary with Changes, Mock Decision / Strategy / Files / Verification / Production Boundary, Verification Evidence, Review Result, Frontend Status, Real Integration Status, Known Risks, and Follow-up. No file writes.",
2534
2516
  subtask_prompt: [
2535
2517
  "Return a frontend closeout summary covering Mock decision/strategy/files/verification/production boundary, changes, verification evidence, review result, known risks, and follow-up.",
2518
+ "Use only the canonical frontend-review-context.json plus the final frontend review gate verdict. Do not re-open task sources, OpenSpec, AI workspace, plan/revision, design-review, writer, repair, or verification prose, and do not perform new repository research during closeout.",
2536
2519
  "Include a coverage matrix for each requirement id, applicable UI state, and verification target/check with status passed|failed|not-run|blocked|unavailable. Report lint separately as passed|baseline-debt|failed|unavailable; baseline-debt is explicit debt, not passed. Always state Browser accessibility verification: not-run and Visual regression: not-run. Use contracts/frontend-review-context.json and the effective frontend-verify-assess-shell or frontend-reverify-shell facts; do not invent Browser evidence from component tests.",
2537
2520
  `When only Mock-backed evidence passed, state exactly Frontend status: mock-validated and Real integration: pending, summarize the Real Integration Gap, and name ${taskConfig.taskId}-real-api-integration-verify as the explicit follow-up task to create/run after backend readiness. This follow-up is not auto-created or auto-executed. Never describe Mock evidence as real API integration.`,
2538
2521
  `When Mock was skipped in auto mode and no real API evidence passed, state exactly Frontend status: locally-validated and Real integration: pending, summarize the Real Integration Gap, and name ${taskConfig.taskId}-real-api-integration-verify as the explicit follow-up task when backend readiness matters.`,
2539
2522
  "Read-only: do not modify code, docs, artifacts, or .harness/dag-runs/.",
2540
- sourceContext,
2541
- mockContextBlock,
2542
2523
  ].join("\n\n"),
2543
2524
  },
2544
2525
  ],
@@ -3349,10 +3330,11 @@ async function buildBackendTestHybridDag(sources) {
3349
3330
  executor: "pi", toolProfile: "write", complexity: "HIGH", writePolicy: "exclusive",
3350
3331
  writeSet: ["testcase/**/test_*.py", "testcase/**/helpers/**", "testcase/**/factories/**"],
3351
3332
  allowedPaths: Array.from(new Set([...ro, "testcase/**"])), forbiddenPaths: forbidden,
3352
- outputContract: "Convert every final automatable Markdown case into pytest assets whose actual test function region contains the exact Case ID, preferably in the function name or docstring; no JSON and no pytest execution.",
3333
+ outputContract: "Convert every final automatable Markdown case into pytest assets whose actual test function region contains the exact Case ID, preferably in the function name or docstring. Each testcase/md/<module>.md (excluding README.md) maps one-to-one to testcase/test_<module>.py; never merge or split modules. No JSON and no pytest execution.",
3353
3334
  subtask_prompt: [
3354
3335
  "Convert testcase/md/** to pytest using upstream environment and advisory validation evidence plus only bounded pytest config/conftest. A FAIL advisory report does not authorize inventing missing behavior; use the final Markdown facts that are present.",
3355
3336
  "Ensure every final Markdown Case ID appears in at least one real pytest test function or pytest test class method region, preferably as `test_BE_<MODULE>_<NNN>_<description>` and in that function/method docstring. Module-level functions and class-based pytest methods are both supported. Multiple test functions may cover one Case ID; assertions come only from 预期结果/Expected Results and setup comes only from 前置条件 plus any optional 测试数据/自动化映射 or their legacy English aliases.",
3337
+ "Name each generated pytest file so it corresponds one-to-one with its source Markdown module file: for each `testcase/md/<module>.md` (excluding README.md), emit exactly one `testcase/test_<module>.py`. The <module> stem is the Markdown filename without the `.md` extension, lowercased and with non-alphanumeric characters replaced by underscores. For example, `testcase/md/resource_notes.md` maps to `testcase/test_resource_notes.py`, and `testcase/md/order-api.md` maps to `testcase/test_order_api.py`. Never merge multiple Markdown modules into one pytest file, never split one module across several files, and never invent pytest filenames unrelated to the Markdown modules.",
3356
3338
  "Generate a reusable HTTP logging helper (or equivalent client wrapper) and call it for every interface request. The request log must include method, URL/path, and request parameters (query plus JSON/body/payload summary). The response log must include status code and response result (JSON/text/body summary), and both records must be visible in pytest stdout/stderr without changing assertions.",
3357
3339
  "Compare timestamps and other semantically equivalent protocol values by parsed meaning, not byte-for-byte serialization. In particular, normalize valid ISO-8601 instants before equality/order assertions so differences such as omitted trailing fractional seconds do not create TestBug failures; preserve exact-string assertions only when the Markdown explicitly requires representation equality.",
3358
3340
  "Before logging, recursively redact sensitive keys and header values including authorization, proxy-authorization, cookie, set-cookie, token, password, secret, api key and credentials. Never print full Authorization/Cookie values. Apply bounded truncation to serialized request and response bodies (with an explicit truncation marker) so large payloads cannot flood pytest or report artifacts.",
@@ -4939,11 +4921,22 @@ function assertNoGovernanceFlagOnDisallowedTemplate(spec, template) {
4939
4921
  }
4940
4922
  }
4941
4923
  export async function buildHybridDagFromTask(sources, options = {}) {
4924
+ const routingProjectCapability = sources.taskConfig.taskKind === "standard" &&
4925
+ (options.template === undefined || options.template === "standard-dag") &&
4926
+ sources.repoRoot
4927
+ ? await discoverFrontendProjectCapability(sources.repoRoot)
4928
+ : sources.frontendProjectCapability;
4942
4929
  const selection = resolveTaskDagTemplateSelection({
4943
4930
  taskKind: sources.taskConfig.taskKind,
4944
4931
  title: sources.taskConfig.title,
4945
4932
  requirementMarkdown: sources.requirementMarkdown,
4946
4933
  allowedPaths: sources.taskConfig.allowedPaths,
4934
+ frontendProject: routingProjectCapability
4935
+ ? {
4936
+ framework: routingProjectCapability.framework,
4937
+ evidencePaths: routingProjectCapability.evidencePaths,
4938
+ }
4939
+ : undefined,
4947
4940
  requestedTemplate: options.template,
4948
4941
  });
4949
4942
  return buildHybridDagForTemplate(sources, selection.template);
@@ -5565,17 +5558,27 @@ export function defaultHybridDagOutputPath(taskId) {
5565
5558
  return path.join(os.tmpdir(), `${taskId}-hybrid-dag.json`);
5566
5559
  }
5567
5560
  export async function writeHybridDagDraft(sources, outputPath, options = {}) {
5561
+ const routingProjectCapability = sources.taskConfig.taskKind === "standard" &&
5562
+ (options.template === undefined || options.template === "standard-dag")
5563
+ ? await discoverFrontendProjectCapability(sources.repoRoot ?? process.cwd())
5564
+ : sources.frontendProjectCapability;
5568
5565
  const templateSelection = resolveTaskDagTemplateSelection({
5569
5566
  taskKind: sources.taskConfig.taskKind,
5570
5567
  title: sources.taskConfig.title,
5571
5568
  requirementMarkdown: sources.requirementMarkdown,
5572
5569
  allowedPaths: sources.taskConfig.allowedPaths,
5570
+ frontendProject: routingProjectCapability
5571
+ ? {
5572
+ framework: routingProjectCapability.framework,
5573
+ evidencePaths: routingProjectCapability.evidencePaths,
5574
+ }
5575
+ : undefined,
5573
5576
  requestedTemplate: options.template,
5574
5577
  });
5575
5578
  const template = templateSelection.template;
5576
5579
  assertTaskAllowedPathsPreflight(sources.taskConfig);
5577
5580
  const preparedSources = template === "frontend-implementation"
5578
- ? await prepareFrontendMockSources(sources)
5581
+ ? await prepareFrontendMockSources(sources, routingProjectCapability)
5579
5582
  : sources;
5580
5583
  const spec = await buildHybridDagForTemplate(preparedSources, template);
5581
5584
  if (preparedSources.repoRoot && dagHasWriterExecution(spec)) {
@@ -1,9 +1,8 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { readFile } from "node:fs/promises";
3
- import path from "node:path";
4
- import { getTaskPaths } from "../../task/runtime.js";
5
3
  import { assertTaskContractBindingFresh } from "./task-contract-binding.js";
6
4
  import { isSafeReadOnlyPiRetryCandidate } from "./retry-policy.js";
5
+ import { resolveDagTaskSourcePath } from "../../task/dag-source-paths.js";
7
6
  const REVIEW_VERDICT_RECOVERY_NODE_ID = "review-verdict-recovery-pi";
8
7
  const SAFE_SHELL_GATE_KEYS = [
9
8
  "verdictGate",
@@ -403,15 +402,12 @@ export async function evaluateLiveDagRerunBindings(input) {
403
402
  let sourceBindingMatched = true;
404
403
  if (sourceBinding) {
405
404
  try {
406
- const taskPaths = getTaskPaths(input.cwd, sourceBinding.taskId);
407
405
  for (const source of sourceBinding.sources) {
408
- const sourcePath = path.resolve(taskPaths.taskDir, source.path);
409
- const sourceRoot = path.resolve(taskPaths.sourceDir);
410
- if (sourcePath !== sourceRoot &&
411
- !sourcePath.startsWith(`${sourceRoot}${path.sep}`)) {
412
- sourceBindingMatched = false;
413
- break;
414
- }
406
+ const sourcePath = resolveDagTaskSourcePath({
407
+ workspaceRoot: input.cwd,
408
+ taskId: sourceBinding.taskId,
409
+ sourcePath: source.path,
410
+ });
415
411
  const bytes = await readFile(sourcePath);
416
412
  if (createHash("sha256").update(bytes).digest("hex") !== source.sha256) {
417
413
  sourceBindingMatched = false;