@tea-agent/loop-agent 0.9.0 → 0.10.0-alpha.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 (183) hide show
  1. package/AGENTS.md +8 -0
  2. package/CHANGELOG.md +92 -11
  3. package/README.md +69 -5
  4. package/dist/application/dag/args.js +4 -14
  5. package/dist/application/dag/generate-task-dag.js +32 -2
  6. package/dist/application/dag/run-dag.js +1 -27
  7. package/dist/application/dag/validate-dag.js +2 -2
  8. package/dist/application/loop/run-action.js +0 -4
  9. package/dist/cli/command-definitions.js +7 -11
  10. package/dist/cli/program.js +9 -21
  11. package/dist/commands/cursor-prompt.js +42 -82
  12. package/dist/commands/dag-approve.js +36 -0
  13. package/dist/commands/dag-reconcile-run.js +118 -0
  14. package/dist/commands/delegate.js +75 -77
  15. package/dist/commands/doctor.js +0 -18
  16. package/dist/commands/init.js +60 -40
  17. package/dist/commands/instructions.js +7 -10
  18. package/dist/commands/loop.js +4 -20
  19. package/dist/executors/config-core.js +0 -51
  20. package/dist/executors/dag-pi-executor.js +1 -1
  21. package/dist/executors/dag.js +0 -1
  22. package/dist/executors/index.js +0 -2
  23. package/dist/executors/model-routing.js +9 -9
  24. package/dist/executors/shell-executor.js +1 -1
  25. package/dist/governance/checks.js +6 -3
  26. package/dist/governance/manifest-types.js +33 -2
  27. package/dist/infrastructure/harness/loop-action-store.js +0 -3
  28. package/dist/records/harvest.js +2 -23
  29. package/dist/records/one-shot-runs.js +1 -1
  30. package/dist/shared/artifacts-core.js +24 -5
  31. package/dist/shared/output-truncation.js +37 -0
  32. package/dist/shared/package-metadata.js +353 -0
  33. package/dist/{executors/cursor-executor.js → sidecars/cursor-prompt/executor.js} +2 -42
  34. package/dist/sidecars/cursor-prompt/index.js +3 -0
  35. package/dist/sidecars/cursor-prompt/stream.js +121 -0
  36. package/dist/task/config-types.js +29 -12
  37. package/dist/task/delegate.js +9 -21
  38. package/dist/task/runtime.js +1 -2
  39. package/dist/worker/cli.js +243 -0
  40. package/dist/worker/closeout/apply.js +73 -0
  41. package/dist/worker/closeout/preview.js +30 -0
  42. package/dist/worker/delivery/final-verification.js +194 -0
  43. package/dist/worker/delivery/git-transaction.js +354 -0
  44. package/dist/worker/delivery/package.js +502 -0
  45. package/dist/worker/feature/decision-loader.js +68 -0
  46. package/dist/worker/feature/discover.js +14 -0
  47. package/dist/worker/feature/next-action.js +74 -0
  48. package/dist/worker/feature/reducer.js +133 -0
  49. package/dist/worker/feature/review.js +502 -0
  50. package/dist/worker/feature/run.js +365 -0
  51. package/dist/worker/feature/types.js +1 -0
  52. package/dist/worker/follow-up/approve.js +270 -0
  53. package/dist/worker/follow-up/factory.js +234 -0
  54. package/dist/worker/follow-up/paths.js +25 -0
  55. package/dist/worker/follow-up/policy.js +26 -0
  56. package/dist/worker/follow-up/schema.js +93 -0
  57. package/dist/worker/follow-up/store.js +96 -0
  58. package/dist/worker/loop-agent/loop-agent-client.js +329 -126
  59. package/dist/worker/metrics/projector.js +139 -0
  60. package/dist/worker/observability/read-model.js +282 -15
  61. package/dist/worker/observe/paths.js +17 -5
  62. package/dist/worker/observe/static/app.js +443 -61
  63. package/dist/worker/observe/static/index.html +3 -1
  64. package/dist/worker/observe/static/styles.css +85 -18
  65. package/dist/worker/pool/run-store.js +14 -2
  66. package/dist/worker/pool/validation.js +59 -0
  67. package/dist/worker/preflight.js +49 -1
  68. package/dist/worker/report/morning-report.js +41 -6
  69. package/dist/worker/run-task/run-task.js +23 -13
  70. package/dist/worker/runner/run-ready.js +89 -11
  71. package/dist/worker/task-spec/schema.js +0 -1
  72. package/dist/workflows/dag/convergence/controller.js +1 -1
  73. package/dist/workflows/dag/executor-registry.js +0 -2
  74. package/dist/workflows/dag/governance-profile.js +10 -0
  75. package/dist/workflows/dag/init-hybrid.js +601 -26
  76. package/dist/workflows/dag/lifecycle.js +146 -0
  77. package/dist/workflows/dag/node-execution.js +64 -7
  78. package/dist/workflows/dag/prompt.js +16 -0
  79. package/dist/workflows/dag/report.js +2 -0
  80. package/dist/workflows/dag/runner.js +176 -119
  81. package/dist/workflows/dag/scheduler.js +7 -2
  82. package/dist/workflows/dag/skill-snapshot.js +527 -0
  83. package/dist/workflows/dag/types.js +45 -9
  84. package/dist/workflows/dag/validate.js +5 -8
  85. package/dist/workflows/loop/actions/dag-action.js +0 -2
  86. package/dist/workflows/loop/actions/shared.js +1 -1
  87. package/dist/workflows/loop/actions.js +14 -31
  88. package/dist/workflows/loop/benchmark.js +1 -1
  89. package/dist/workflows/loop/index.js +1 -1
  90. package/dist/workflows/loop/policy/auto-policy.js +22 -14
  91. package/dist/workflows/loop/policy/path-patterns.js +13 -0
  92. package/docs/README.md +35 -16
  93. package/docs/agent-dag-recovery-playbook.md +1 -1
  94. package/docs/architecture/README.md +26 -0
  95. package/docs/architecture/dag-execution.md +134 -0
  96. package/docs/architecture/evolution.md +52 -0
  97. package/docs/architecture/facts-and-state.md +58 -0
  98. package/docs/architecture/runtime-boundaries.md +41 -15
  99. package/docs/architecture/system-overview.md +93 -0
  100. package/docs/architecture/worker-and-feature.md +81 -0
  101. package/docs/cursor-prompt-sidecar.md +36 -0
  102. package/docs/decisions/README.md +13 -1
  103. package/docs/design/README.md +39 -13
  104. package/docs/development-principles.md +1 -1
  105. package/docs/exec-plans/active/README.md +2 -2
  106. package/docs/exec-plans/completed/README.md +14 -1
  107. package/docs/feature-workflow.md +44 -4
  108. package/docs/init-surface.manifest.json +63 -1
  109. package/docs/loop-agent-harness.md +65 -3
  110. package/docs/progress/README.md +27 -0
  111. package/docs/reports/README.md +74 -8
  112. package/docs/skills/README.md +2 -1
  113. package/docs/skills/vetted-skill-registry.md +2 -1
  114. package/docs/templates/agent-dag-report.schema.json +4 -2
  115. package/docs/templates/agent-dag.base.json +0 -5
  116. package/docs/templates/agent-dag.final-verification.json +0 -5
  117. package/docs/templates/agent-dag.schema.json +1 -2
  118. package/docs/templates/agent-dag.supervised-implementation.json +1 -6
  119. package/docs/templates/backend-test-dag.generate-pytest.prompt.md +131 -0
  120. package/docs/templates/backend-test-dag.json +213 -0
  121. package/docs/templates/backend-test-dag.retrospect.prompt.md +128 -0
  122. package/docs/templates/backend-test-dag.review-cases.prompt.md +85 -0
  123. package/docs/templates/frontend-design-contract.md +33 -0
  124. package/docs/templates/frontend-task-constraints.md +25 -0
  125. package/docs/templates/frontend-task-requirement.md +61 -0
  126. package/docs/templates/harness.schema.json +8 -5
  127. package/docs/templates/hybrid-dag.json +1 -6
  128. package/docs/templates/init-evolution-review.md +4 -2
  129. package/docs/templates/interactive-ui-round2-experiment.md +1 -1
  130. package/docs/templates/product-line/task.yaml +0 -1
  131. package/docs/templates/worker-dogfood-evidence.md +28 -0
  132. package/docs/templates/worker-dogfood-setup.md +20 -0
  133. package/docs/verification-matrix.md +17 -0
  134. package/examples/decision-gate-agent-dag.json +87 -33
  135. package/examples/example-dag.json +0 -5
  136. package/examples/hybrid-loop-agent-dag.json +0 -5
  137. package/harness.json +6 -11
  138. package/package.json +22 -44
  139. package/scripts/check-product-line-docs.sh +10 -3
  140. package/skills/agent-worker/SKILL.md +37 -0
  141. package/skills/agent-worker/references/agent-worker-operator.md +43 -0
  142. package/skills/frontend-design-review/SKILL.md +59 -0
  143. package/skills/frontend-design-review/references/review-checklist.md +37 -0
  144. package/skills/frontend-implementation/SKILL.md +48 -0
  145. package/skills/frontend-implementation/references/code-standards.md +34 -0
  146. package/skills/frontend-implementation/references/design-spec.md +46 -0
  147. package/skills/frontend-implementation/references/node-contracts.md +32 -0
  148. package/skills/frontend-review/SKILL.md +53 -0
  149. package/skills/frontend-review/references/review-findings.md +42 -0
  150. package/skills/frontend-verification/SKILL.md +40 -0
  151. package/skills/frontend-verification/references/verification-checklist.md +56 -0
  152. package/skills/grill-me/SKILL.md +10 -0
  153. package/skills/grill-with-docs/SKILL.md +88 -0
  154. package/skills/grill-with-docs/adr-format.md +47 -0
  155. package/skills/grill-with-docs/context-format.md +60 -0
  156. package/skills/init-capability-evolution/SKILL.md +1 -0
  157. package/skills/loop-agent/SKILL.md +11 -9
  158. package/skills/loop-agent/references/command-reference.md +28 -15
  159. package/skills/loop-agent/references/docs-converge.md +126 -0
  160. package/skills/loop-agent/references/harness-policy.md +7 -7
  161. package/skills/loop-agent/references/hybrid-dag.md +13 -15
  162. package/skills/loop-agent/references/long-running-loop.md +4 -6
  163. package/skills/loop-agent/references/multi-worktree.md +6 -6
  164. package/skills/loop-agent/references/orchestrator-and-interventions.md +3 -3
  165. package/skills/loop-agent/references/pi-subagent-assisted-mode.md +14 -11
  166. package/skills/using-git-worktrees/SKILL.md +215 -0
  167. package/dist/commands/cursor-worker.js +0 -43
  168. package/dist/cursor-worker-entry.js +0 -8
  169. package/dist/executors/cursor-artifacts.js +0 -33
  170. package/dist/executors/cursor-execution-log.js +0 -81
  171. package/dist/executors/cursor-executor-artifacts.js +0 -134
  172. package/dist/executors/cursor-run.js +0 -115
  173. package/dist/executors/cursor-tool.js +0 -94
  174. package/dist/executors/cursor-worker-client.js +0 -223
  175. package/dist/executors/cursor-worker-protocol.js +0 -18
  176. package/dist/executors/cursor-worker-server.js +0 -54
  177. package/dist/executors/cursor-worker.js +0 -3
  178. package/dist/executors/cursor.js +0 -6
  179. package/dist/executors/dag-cursor-executor.js +0 -87
  180. package/dist/workflows/loop/actions/cursor-fix.js +0 -191
  181. package/dist/workflows/loop/policy/cursor-fix-policy.js +0 -31
  182. package/docs/cursor-executor-usage.md +0 -25
  183. package/docs/dynamic-workflow-dag-engine-roadmap.md +0 -1749
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
2
2
  import { readFile, writeFile, mkdir } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import YAML from "yaml";
5
+ import { controllerIdentitiesMatch, controllerIdentityExpectationFailure, resolveControllerIdentity, } from "../loop-agent/loop-agent-client.js";
5
6
  import { deriveFailureRoute, deriveFailureRouteFromError, } from "../pool/failure-routing.js";
6
7
  import { findRunByWorkerRunId, getTaskPoolRoot, readAllTaskPoolStates, recordTaskPoolRun, writeTaskPoolState, } from "../pool/run-store.js";
7
8
  import { runTaskSpec, } from "../run-task/run-task.js";
@@ -9,7 +10,26 @@ import { formatDuration, noopProgressReporter, } from "../progress-reporter.js";
9
10
  import { computeReadyQueue } from "../task-graph/ready-queue.js";
10
11
  import { taskGraphSpecSchema } from "../task-graph/task-graph-schema.js";
11
12
  import { taskSpecSchema } from "../task-spec/schema.js";
13
+ import { preflightTargetRepo } from "../preflight.js";
12
14
  export async function runReadyTasks(options) {
15
+ let controllerIdentity = resolveControllerIdentity(options.client, options.controllerIdentity);
16
+ const identityFailure = controllerIdentityExpectationFailure(controllerIdentity, options.controllerExpectation);
17
+ if (identityFailure) {
18
+ throw new Error(`${identityFailure.code}: ${identityFailure.message}`);
19
+ }
20
+ if (!options.skipTaskPreflight && !options.runTask) {
21
+ const preflight = await preflightTargetRepo({
22
+ repoRoot: options.repoRoot,
23
+ client: options.client,
24
+ runCheckRepo: options.runCheckRepo,
25
+ ...(options.checkRepoCommand ? { checkRepoCommand: options.checkRepoCommand } : {}),
26
+ ...(options.controllerExpectation ? { expectation: options.controllerExpectation } : {}),
27
+ });
28
+ if (!preflight.ok) {
29
+ throw new Error(`target repo preflight failed: ${preflight.code}: ${preflight.message}`);
30
+ }
31
+ controllerIdentity = preflight.controllerIdentity ?? controllerIdentity;
32
+ }
13
33
  const now = options.now ?? new Date();
14
34
  const startedAt = now.toISOString();
15
35
  const batchRunId = options.batchRunId ?? buildBatchRunId(now);
@@ -43,12 +63,19 @@ export async function runReadyTasks(options) {
43
63
  const taskSpecPath = path.join(options.featureDir, "tasks", node?.task ?? `${taskId}.yaml`);
44
64
  const taskSpec = await loadTaskSpec(taskSpecPath);
45
65
  const retryOfWorkerRunId = states[taskId]?.retryOfWorkerRunId;
46
- const workerRunId = retryOfWorkerRunId
66
+ let workerRunId = retryOfWorkerRunId
47
67
  ? buildRetryWorkerRunId(taskId, retryOfWorkerRunId, now)
48
68
  : options.workerRunIdForTask?.(taskId, taskSpec) ??
49
- buildStableWorkerRunId(taskId, taskSpec, now);
69
+ buildStableWorkerRunId(taskId, taskSpec, now, controllerIdentity);
50
70
  if (workerRunId) {
51
- const existing = await findRunByWorkerRunId(options.repoRoot, workerRunId);
71
+ let existing = await findRunByWorkerRunId(options.repoRoot, workerRunId);
72
+ if (existing && !controllerRunCanBeReused(existing, controllerIdentity)) {
73
+ workerRunId = buildControllerScopedWorkerRunId(workerRunId, controllerIdentity);
74
+ existing = await findRunByWorkerRunId(options.repoRoot, workerRunId);
75
+ if (existing && !controllerRunCanBeReused(existing, controllerIdentity)) {
76
+ throw new Error(`workerRunId collision has incompatible controller identity: ${workerRunId}`);
77
+ }
78
+ }
52
79
  if (existing) {
53
80
  progress.task(`task ${index}/${total} ${taskId}: reuse existing run ${workerRunId}`);
54
81
  emit(progress, {
@@ -66,6 +93,7 @@ export async function runReadyTasks(options) {
66
93
  status: "reused",
67
94
  runRecordPath: existing.runRecordPath,
68
95
  });
96
+ await options.onTaskFinalized?.({ status: "reused", taskSpec, workerRunId, ...(existing.runRecordPath ? { runRecordPath: existing.runRecordPath } : {}) });
69
97
  continue;
70
98
  }
71
99
  }
@@ -80,7 +108,7 @@ export async function runReadyTasks(options) {
80
108
  workerRunId,
81
109
  status: "running",
82
110
  });
83
- progress.step(`preflight → materialize → dag-run-task → validate → run-dag → report`);
111
+ progress.step(`materialize → dag-run-task → validate → run-dag → report`);
84
112
  let result;
85
113
  try {
86
114
  await writeTaskPoolState(options.repoRoot, {
@@ -106,12 +134,11 @@ export async function runReadyTasks(options) {
106
134
  client: options.client,
107
135
  now,
108
136
  workerRunId,
109
- preflight: {
110
- runCheckRepo: options.runCheckRepo,
111
- ...(options.checkRepoCommand ? { checkRepoCommand: options.checkRepoCommand } : {}),
112
- },
137
+ preflight: false,
113
138
  progress,
114
139
  ...(options.piModel ? { piModel: options.piModel } : {}),
140
+ ...(controllerIdentity ? { controllerIdentity } : {}),
141
+ ...(options.controllerExpectation ? { controllerExpectation: options.controllerExpectation } : {}),
115
142
  });
116
143
  }
117
144
  catch (error) {
@@ -128,6 +155,7 @@ export async function runReadyTasks(options) {
128
155
  status: "failed",
129
156
  message,
130
157
  });
158
+ let recorded = false;
131
159
  try {
132
160
  await recordTaskPoolRun({
133
161
  repoRoot: options.repoRoot,
@@ -142,18 +170,23 @@ export async function runReadyTasks(options) {
142
170
  error: message,
143
171
  failure,
144
172
  ...(retryOfWorkerRunId ? { retryOfWorkerRunId } : {}),
173
+ ...(controllerIdentity ? { controllerIdentity } : {}),
145
174
  },
146
175
  });
176
+ recorded = true;
147
177
  }
148
178
  catch (recordError) {
179
+ await options.onTaskFinalized?.({ status: "run-error", taskSpec, workerRunId });
149
180
  tasks.push({
150
181
  taskId,
151
182
  workerRunId,
152
183
  status: "record-error",
153
184
  error: errorMessage(recordError),
154
185
  });
155
- continue;
186
+ break;
156
187
  }
188
+ if (recorded)
189
+ await options.onTaskFinalized?.({ status: "run-error", taskSpec, workerRunId });
157
190
  tasks.push({
158
191
  taskId,
159
192
  workerRunId,
@@ -206,9 +239,12 @@ export async function runReadyTasks(options) {
206
239
  ...(failure ? { failure } : {}),
207
240
  ...(retryOfWorkerRunId ? { retryOfWorkerRunId } : {}),
208
241
  ...(result.failureArtifacts ? { failureArtifacts: result.failureArtifacts } : {}),
242
+ ...(controllerIdentity ? { controllerIdentity } : {}),
209
243
  };
244
+ let recorded = false;
210
245
  try {
211
246
  await recordTaskPoolRun({ repoRoot: options.repoRoot, run });
247
+ recorded = true;
212
248
  tasks.push({
213
249
  taskId,
214
250
  workerRunId: result.workerRunId,
@@ -217,6 +253,7 @@ export async function runReadyTasks(options) {
217
253
  });
218
254
  }
219
255
  catch (error) {
256
+ await options.onTaskFinalized?.({ status: result.status, taskSpec, workerRunId: result.workerRunId, runRecordPath: result.runRecordPath });
220
257
  tasks.push({
221
258
  taskId,
222
259
  workerRunId: result.workerRunId,
@@ -224,7 +261,10 @@ export async function runReadyTasks(options) {
224
261
  error: errorMessage(error),
225
262
  runRecordPath: result.runRecordPath,
226
263
  });
264
+ break;
227
265
  }
266
+ if (recorded)
267
+ await options.onTaskFinalized?.({ status: result.status, taskSpec, workerRunId: result.workerRunId, runRecordPath: result.runRecordPath });
228
268
  }
229
269
  const summary = summarize(tasks);
230
270
  const batchRunPath = path.join(getTaskPoolRoot(options.repoRoot), "artifacts", batchRunId, "batch-run.json");
@@ -237,6 +277,7 @@ export async function runReadyTasks(options) {
237
277
  summary,
238
278
  tasks,
239
279
  batchRunPath,
280
+ ...(controllerIdentity ? { controllerIdentity } : {}),
240
281
  };
241
282
  await mkdir(path.dirname(batchRunPath), { recursive: true });
242
283
  await writeFile(batchRunPath, `${JSON.stringify(output, null, 2)}\n`, "utf-8");
@@ -260,18 +301,55 @@ function emit(progress, input) {
260
301
  export function buildBatchRunId(now) {
261
302
  return `batch-${now.toISOString().replace(/[-:.]/g, "").slice(0, 15)}`;
262
303
  }
263
- export function buildStableWorkerRunId(taskId, taskSpec, now) {
304
+ export function buildStableWorkerRunId(taskId, taskSpec, now, controllerIdentity) {
264
305
  const date = now.toISOString().slice(0, 10).replace(/-/g, "");
265
306
  const slug = taskId
266
307
  .toLowerCase()
267
308
  .replace(/[^a-z0-9]+/g, "-")
268
309
  .replace(/^-+|-+$/g, "");
269
310
  const hash = createHash("sha256")
270
- .update(`${taskSpec.feature_id}:${taskSpec.id}:${taskSpec.title}`)
311
+ .update([
312
+ taskSpec.feature_id,
313
+ taskSpec.id,
314
+ taskSpec.title,
315
+ ...(controllerIdentity
316
+ ? [
317
+ controllerIdentity.packageName,
318
+ controllerIdentity.packageVersion,
319
+ controllerIdentity.binarySha256,
320
+ controllerIdentity.packageFingerprint.value,
321
+ ]
322
+ : []),
323
+ ].join(":"))
271
324
  .digest("hex")
272
325
  .slice(0, 10);
273
326
  return `wr-${date}-${slug}-${hash}`;
274
327
  }
328
+ function buildControllerScopedWorkerRunId(baseWorkerRunId, controllerIdentity) {
329
+ const hash = createHash("sha256")
330
+ .update(controllerIdentity
331
+ ? JSON.stringify({
332
+ packageName: controllerIdentity.packageName,
333
+ binName: controllerIdentity.binName,
334
+ packageVersion: controllerIdentity.packageVersion,
335
+ binarySha256: controllerIdentity.binarySha256,
336
+ packageFingerprint: controllerIdentity.packageFingerprint.value,
337
+ requested: controllerIdentity.requested,
338
+ entry: controllerIdentity.entry,
339
+ realEntry: controllerIdentity.realEntry,
340
+ launch: controllerIdentity.launch,
341
+ packageRoot: controllerIdentity.packageRoot,
342
+ })
343
+ : "legacy-unpinned-controller")
344
+ .digest("hex")
345
+ .slice(0, 10);
346
+ return `${baseWorkerRunId}-controller-${hash}`;
347
+ }
348
+ function controllerRunCanBeReused(existing, controllerIdentity) {
349
+ if (!existing.controllerIdentity && !controllerIdentity)
350
+ return true;
351
+ return controllerIdentitiesMatch(existing.controllerIdentity, controllerIdentity);
352
+ }
275
353
  /** A retry is a distinct execution attempt and must not collide with failed evidence. */
276
354
  export function buildRetryWorkerRunId(taskId, previousWorkerRunId, now) {
277
355
  const date = now.toISOString().slice(0, 10).replace(/-/g, "");
@@ -97,7 +97,6 @@ export const taskSpecSchema = z
97
97
  profile_policy: z.literal("mapped"),
98
98
  strict_models: z.boolean().optional().default(true),
99
99
  strict_governance: z.boolean().optional().default(true),
100
- no_cursor: z.boolean().optional().default(false),
101
100
  max_concurrent: z.number().int().positive().optional().default(1),
102
101
  })
103
102
  .strict(),
@@ -5,7 +5,7 @@ import { freshNodeRecord } from "../dynamic-runtime/shared.js";
5
5
  const CONVERGENCE_CHAIN_NODE_IDS = [
6
6
  "process-supervisor-pi",
7
7
  "process-gate-shell",
8
- "repair-cursor",
8
+ "repair-pi",
9
9
  "hard-verify-shell",
10
10
  ];
11
11
  const CONVERGENCE_NON_RETRY_FAILURES = new Set([
@@ -1,4 +1,3 @@
1
- import { executeDagCursorNode } from "../../executors/dag-cursor-executor.js";
2
1
  import { executeDagPiNode } from "../../executors/dag-pi-executor.js";
3
2
  import { executeDagShellNode } from "../../executors/shell-executor.js";
4
3
  import { executeDagStaticNode } from "../../executors/dag-static-executor.js";
@@ -9,7 +8,6 @@ function requireModel(input) {
9
8
  return input;
10
9
  }
11
10
  export const DEFAULT_DAG_EXECUTOR_REGISTRY = {
12
- cursor: (input, meta) => executeDagCursorNode(requireModel(input), meta),
13
11
  pi: (input, meta) => executeDagPiNode(requireModel(input), meta),
14
12
  shell: executeDagShellNode,
15
13
  static: executeDagStaticNode,
@@ -147,6 +147,9 @@ function hasPostImplementationShellVerification(spec, ranks) {
147
147
  }
148
148
  return false;
149
149
  }
150
+ function hasBackendTestTopology(spec) {
151
+ return spec.tasks.some((task) => /execute-backend-pytest|generate-backend-functional-cases|review-backend-cases|generate-backend-pytest|test-retrospect/i.test(task.id));
152
+ }
150
153
  function hasSupervisedTopology(spec) {
151
154
  const hasSupervisor = spec.tasks.some((task) => task.role === "supervisor");
152
155
  const hasRepairWriter = spec.tasks.some((task) => isRepairNode(task) && isExclusiveWriter(task, spec));
@@ -302,6 +305,10 @@ function buildProfileReasons(profile, process, delivery, codeChange, supervisedT
302
305
  reasons.push("supervised topology detected (supervisor + repair + write-set/process/review gates)");
303
306
  return reasons;
304
307
  }
308
+ if (profile === "backend-test") {
309
+ reasons.push("backend-test topology detected (analyze → functional cases → review → pytest → execute → retrospect)");
310
+ return reasons;
311
+ }
305
312
  if (supervisedTopology) {
306
313
  reasons.push("partial supervised markers present but topology incomplete; profile downgraded");
307
314
  }
@@ -337,6 +344,9 @@ function buildProfileReasons(profile, process, delivery, codeChange, supervisedT
337
344
  return reasons;
338
345
  }
339
346
  function inferProfileName(spec, process, delivery, codeChange, ranks) {
347
+ if (hasBackendTestTopology(spec)) {
348
+ return "backend-test";
349
+ }
340
350
  if (hasSupervisedTopology(spec)) {
341
351
  return "supervised";
342
352
  }