@tea-agent/loop-agent 0.10.0 → 0.11.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 (166) hide show
  1. package/AGENTS.md +10 -2
  2. package/CHANGELOG.md +67 -25
  3. package/README.md +82 -11
  4. package/dist/application/dag/args.js +1 -12
  5. package/dist/application/dag/generate-task-dag.js +23 -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 +44 -16
  10. package/dist/cli/program.js +40 -23
  11. package/dist/cli/update/notifier.js +117 -0
  12. package/dist/cli/update/npm-client.js +151 -0
  13. package/dist/cli/update/policy.js +58 -0
  14. package/dist/cli/update/state.js +68 -0
  15. package/dist/cli.js +33 -0
  16. package/dist/commands/cursor-prompt.js +42 -82
  17. package/dist/commands/dag-approve.js +36 -0
  18. package/dist/commands/delegate.js +75 -77
  19. package/dist/commands/doctor.js +0 -18
  20. package/dist/commands/init.js +476 -91
  21. package/dist/commands/instructions.js +7 -10
  22. package/dist/commands/loop.js +4 -20
  23. package/dist/commands/plan.js +50 -0
  24. package/dist/executors/config-core.js +0 -51
  25. package/dist/executors/dag-pi-executor.js +1 -1
  26. package/dist/executors/dag.js +0 -1
  27. package/dist/executors/index.js +0 -2
  28. package/dist/executors/model-routing.js +9 -9
  29. package/dist/executors/shell-executor.js +1 -1
  30. package/dist/governance/checks.js +6 -3
  31. package/dist/governance/exec-plans.js +545 -0
  32. package/dist/governance/manifest-types.js +24 -2
  33. package/dist/infrastructure/harness/loop-action-store.js +0 -3
  34. package/dist/records/harvest.js +2 -23
  35. package/dist/records/one-shot-runs.js +1 -1
  36. package/dist/shared/artifacts-core.js +24 -5
  37. package/dist/shared/output-truncation.js +37 -0
  38. package/dist/shared/package-metadata.js +353 -0
  39. package/dist/{executors/cursor-executor.js → sidecars/cursor-prompt/executor.js} +2 -42
  40. package/dist/sidecars/cursor-prompt/index.js +3 -0
  41. package/dist/sidecars/cursor-prompt/stream.js +121 -0
  42. package/dist/task/config-types.js +28 -12
  43. package/dist/task/delegate.js +9 -21
  44. package/dist/task/runtime.js +1 -2
  45. package/dist/worker/cli.js +29 -2
  46. package/dist/worker/delivery/final-verification.js +47 -11
  47. package/dist/worker/delivery/package.js +63 -10
  48. package/dist/worker/feature/run.js +60 -8
  49. package/dist/worker/loop-agent/loop-agent-client.js +329 -126
  50. package/dist/worker/observability/read-model.js +27 -1
  51. package/dist/worker/observe/static/app.js +326 -45
  52. package/dist/worker/observe/static/index.html +1 -1
  53. package/dist/worker/observe/static/styles.css +5 -4
  54. package/dist/worker/preflight.js +49 -1
  55. package/dist/worker/run-task/run-task.js +22 -12
  56. package/dist/worker/runner/run-ready.js +76 -12
  57. package/dist/worker/task-spec/schema.js +0 -1
  58. package/dist/workflows/dag/convergence/controller.js +1 -1
  59. package/dist/workflows/dag/executor-registry.js +0 -2
  60. package/dist/workflows/dag/init-hybrid.js +402 -25
  61. package/dist/workflows/dag/node-execution.js +61 -7
  62. package/dist/workflows/dag/runner.js +45 -17
  63. package/dist/workflows/dag/scheduler.js +7 -2
  64. package/dist/workflows/dag/sdd-embedded.js +128 -0
  65. package/dist/workflows/dag/skill-instructions.js +5 -4
  66. package/dist/workflows/dag/skill-snapshot.js +527 -0
  67. package/dist/workflows/dag/types.js +42 -9
  68. package/dist/workflows/dag/validate.js +5 -8
  69. package/dist/workflows/loop/actions/dag-action.js +0 -2
  70. package/dist/workflows/loop/actions/shared.js +1 -1
  71. package/dist/workflows/loop/actions.js +14 -31
  72. package/dist/workflows/loop/benchmark.js +1 -1
  73. package/dist/workflows/loop/index.js +1 -1
  74. package/dist/workflows/loop/policy/auto-policy.js +22 -14
  75. package/dist/workflows/loop/policy/path-patterns.js +13 -0
  76. package/docs/README.md +36 -33
  77. package/docs/agent-dag-recovery-playbook.md +1 -1
  78. package/docs/agent-dag-runner.md +2 -2
  79. package/docs/architecture/README.md +26 -0
  80. package/docs/architecture/dag-execution.md +134 -0
  81. package/docs/architecture/evolution.md +52 -0
  82. package/docs/architecture/facts-and-state.md +58 -0
  83. package/docs/architecture/runtime-boundaries.md +45 -17
  84. package/docs/architecture/system-overview.md +93 -0
  85. package/docs/architecture/worker-and-feature.md +81 -0
  86. package/docs/cursor-prompt-sidecar.md +36 -0
  87. package/docs/decisions/README.md +13 -1
  88. package/docs/design/README.md +42 -21
  89. package/docs/development-principles.md +2 -2
  90. package/docs/exec-plans/active/README.md +2 -2
  91. package/docs/exec-plans/completed/README.md +12 -0
  92. package/docs/feature-workflow.md +50 -4
  93. package/docs/harness-methodology-debugging.md +1 -1
  94. package/docs/harness-methodology-tdd.md +3 -3
  95. package/docs/init-surface.manifest.json +60 -25
  96. package/docs/loop-agent-harness.md +28 -4
  97. package/docs/progress/README.md +32 -1
  98. package/docs/reports/README.md +84 -18
  99. package/docs/skills/README.md +2 -1
  100. package/docs/skills/vetted-skill-registry.md +2 -1
  101. package/docs/templates/agent-dag-report.schema.json +6 -6
  102. package/docs/templates/agent-dag.base.json +0 -5
  103. package/docs/templates/agent-dag.final-verification.json +0 -5
  104. package/docs/templates/agent-dag.schema.json +1 -2
  105. package/docs/templates/agent-dag.supervised-implementation.json +1 -6
  106. package/docs/templates/frontend-design-contract.md +33 -0
  107. package/docs/templates/frontend-task-constraints.md +25 -0
  108. package/docs/templates/frontend-task-requirement.md +61 -0
  109. package/docs/templates/harness.schema.json +10 -12
  110. package/docs/templates/hybrid-dag.json +1 -6
  111. package/docs/templates/interactive-ui-round2-experiment.md +1 -1
  112. package/docs/templates/product-line/task.yaml +0 -1
  113. package/docs/templates/project-start-checklist.md +2 -2
  114. package/docs/templates/worker-dogfood-evidence.md +28 -0
  115. package/docs/templates/worker-dogfood-setup.md +20 -0
  116. package/docs/verification-matrix.md +10 -0
  117. package/examples/decision-gate-agent-dag.json +87 -33
  118. package/examples/example-dag.json +0 -5
  119. package/examples/hybrid-loop-agent-dag.json +0 -5
  120. package/harness.json +7 -15
  121. package/package.json +22 -46
  122. package/scripts/check-product-line-docs.sh +10 -7
  123. package/skills/agent-worker/SKILL.md +37 -0
  124. package/skills/agent-worker/references/agent-worker-operator.md +43 -0
  125. package/skills/frontend-design-review/SKILL.md +59 -0
  126. package/skills/frontend-design-review/references/review-checklist.md +37 -0
  127. package/skills/frontend-implementation/SKILL.md +51 -0
  128. package/skills/frontend-implementation/references/code-standards.md +34 -0
  129. package/skills/frontend-implementation/references/design-spec.md +46 -0
  130. package/skills/frontend-implementation/references/node-contracts.md +32 -0
  131. package/skills/frontend-review/SKILL.md +53 -0
  132. package/skills/frontend-review/references/review-findings.md +42 -0
  133. package/skills/frontend-verification/SKILL.md +40 -0
  134. package/skills/frontend-verification/references/verification-checklist.md +56 -0
  135. package/skills/grill-me/SKILL.md +10 -0
  136. package/skills/grill-with-docs/SKILL.md +88 -0
  137. package/skills/grill-with-docs/adr-format.md +47 -0
  138. package/skills/grill-with-docs/context-format.md +60 -0
  139. package/skills/loop-agent/SKILL.md +11 -9
  140. package/skills/loop-agent/references/command-reference.md +13 -15
  141. package/skills/loop-agent/references/docs-converge.md +126 -0
  142. package/skills/loop-agent/references/harness-policy.md +7 -7
  143. package/skills/loop-agent/references/hybrid-dag.md +15 -18
  144. package/skills/loop-agent/references/long-running-loop.md +4 -6
  145. package/skills/loop-agent/references/multi-worktree.md +6 -6
  146. package/skills/loop-agent/references/orchestrator-and-interventions.md +3 -3
  147. package/skills/loop-agent/references/pi-subagent-assisted-mode.md +14 -11
  148. package/skills/loop-agent/references/task-workflow.md +1 -1
  149. package/skills/using-git-worktrees/SKILL.md +215 -0
  150. package/dist/commands/cursor-worker.js +0 -43
  151. package/dist/cursor-worker-entry.js +0 -8
  152. package/dist/executors/cursor-artifacts.js +0 -33
  153. package/dist/executors/cursor-execution-log.js +0 -81
  154. package/dist/executors/cursor-executor-artifacts.js +0 -134
  155. package/dist/executors/cursor-run.js +0 -115
  156. package/dist/executors/cursor-tool.js +0 -94
  157. package/dist/executors/cursor-worker-client.js +0 -223
  158. package/dist/executors/cursor-worker-protocol.js +0 -18
  159. package/dist/executors/cursor-worker-server.js +0 -54
  160. package/dist/executors/cursor-worker.js +0 -3
  161. package/dist/executors/cursor.js +0 -6
  162. package/dist/executors/dag-cursor-executor.js +0 -87
  163. package/dist/workflows/loop/actions/cursor-fix.js +0 -191
  164. package/dist/workflows/loop/policy/cursor-fix-policy.js +0 -31
  165. package/docs/cursor-executor-usage.md +0 -25
  166. package/docs/dynamic-workflow-dag-engine-roadmap.md +0 -1749
@@ -8,6 +8,7 @@ import { BASELINE_FORBIDDEN_PATHS } from "./governance-constants.js";
8
8
  import { resolveAdapter } from "../../adapters/index.js";
9
9
  import { loadHarnessManifest } from "../../governance/harness.js";
10
10
  import { buildAuthoritySurfaceAuditNode, buildAuthoritySurfaceGateNode, resolveAuthoritySurfaceAudit, } from "./authority-surface.js";
11
+ import { applySddEmbeddedEnhancements, probeRepoLocalSddSkills, } from "./sdd-embedded.js";
11
12
  import { getTaskPaths, loadTaskConfig } from "../../task/runtime.js";
12
13
  import { materializeTaskReferenceDocs } from "../../task/source-references.js";
13
14
  import { resolveVerifyPreset } from "../../executors/shell-verification.js";
@@ -58,27 +59,38 @@ const HYBRID_SKILLS_BY_ROLE = {
58
59
  verifier: ["verification-before-completion", "systematic-debugging"],
59
60
  closeout: ["loop-agent", "verification-before-completion"],
60
61
  };
62
+ const FRONTEND_DEFAULTS = {
63
+ ...HYBRID_DEFAULTS,
64
+ skills: [],
65
+ };
66
+ const FRONTEND_SKILLS_BY_ROLE = {
67
+ planner: [],
68
+ scout: [],
69
+ implementer: [],
70
+ reviewer: [],
71
+ supervisor: [],
72
+ verifier: [],
73
+ closeout: [],
74
+ };
75
+ const FRONTEND_IMPLEMENTATION_SKILLS = ["frontend-implementation"];
76
+ const FRONTEND_DESIGN_REVIEW_SKILLS = ["frontend-design-review"];
77
+ const FRONTEND_REVIEW_SKILLS = ["frontend-review"];
78
+ const FRONTEND_VERIFICATION_SKILLS = ["frontend-verification"];
61
79
  const IMPLEMENT_WRITESET_PLACEHOLDER = "REPLACE/WITH/NARROW/IMPLEMENT/PATHS/**";
62
80
  function resolveEnabledExecutors(manifestExecutors) {
63
81
  return Object.entries(manifestExecutors)
64
82
  .filter(([, config]) => config.enabled !== false)
65
- .map(([name]) => name);
83
+ .map(([name]) => name)
84
+ .filter((name) => name === "pi");
66
85
  }
67
- function resolveImplementationExecutor(sources) {
68
- const enabled = sources.enabledExecutors
69
- ? new Set(sources.enabledExecutors)
70
- : undefined;
71
- if (!enabled || enabled.has("pi"))
72
- return "pi";
73
- if (enabled.has("cursor"))
74
- return "cursor";
75
- return "pi";
86
+ function implementationNodeId() {
87
+ return "implement-pi";
76
88
  }
77
- function implementationNodeId(executor) {
78
- return executor === "cursor" ? "implement-cursor" : "implement-pi";
89
+ function repairNodeId() {
90
+ return "repair-pi";
79
91
  }
80
- function repairNodeId(executor) {
81
- return executor === "cursor" ? "repair-cursor" : "repair-pi";
92
+ function frontendImplementationNodeId() {
93
+ return "frontend-implement-pi";
82
94
  }
83
95
  function probePathsForWriteSetEntry(entry) {
84
96
  const normalized = entry.replace(/\\/g, "/").replace(/^\.\//, "");
@@ -275,6 +287,65 @@ function buildVerifyShellCommands(input) {
275
287
  const repoRoot = input.repoRoot;
276
288
  return input.commands.map((command) => verifyCommandToShell(repoRoot, command));
277
289
  }
290
+ function markdownVerifyCommand(repoRoot, command) {
291
+ const args = command.split(/\s+/).filter(Boolean);
292
+ if (args.length === 0 ||
293
+ args.some((arg) => !/^[a-z0-9@._/:=,+-]+$/i.test(arg))) {
294
+ return null;
295
+ }
296
+ return {
297
+ args,
298
+ cwd: repoRoot,
299
+ label: command,
300
+ };
301
+ }
302
+ function extractFrontendVerifyCommandsFromMarkdown(input) {
303
+ if (!input.repoRoot)
304
+ return { staticCommands: [], behaviorCommands: [] };
305
+ const markdown = [
306
+ input.requirementMarkdown,
307
+ input.constraintMarkdown ?? "",
308
+ ].join("\n");
309
+ const commands = new Set();
310
+ for (const line of markdown.split(/\r?\n/)) {
311
+ const bulletless = line
312
+ .trim()
313
+ .replace(/^[-*]\s*(?:\[[ xX]\]\s*)?/, "")
314
+ .trim();
315
+ const codeSpanCommands = Array.from(bulletless.matchAll(/`([^`]+)`/g), (match) => match[1].trim());
316
+ const candidates = codeSpanCommands.length > 0 ? codeSpanCommands : [bulletless];
317
+ for (const candidate of candidates) {
318
+ if (/^(npm|pnpm|yarn|bun)\s+(run\s+)?[a-z0-9:_-]+(?:\s.*)?$/i.test(candidate) ||
319
+ /^(npx|pnpm\s+exec|yarn\s+exec|bunx)\s+(vitest|jest|playwright|cypress|tsc|eslint)(?:\s.*)?$/i.test(candidate)) {
320
+ commands.add(candidate);
321
+ }
322
+ }
323
+ }
324
+ const staticCommands = [];
325
+ const behaviorCommands = [];
326
+ for (const command of commands) {
327
+ const verifyCommand = markdownVerifyCommand(input.repoRoot, command);
328
+ if (!verifyCommand)
329
+ continue;
330
+ if (/\b(typecheck|lint|eslint|tsc|build)\b/i.test(command)) {
331
+ staticCommands.push(verifyCommand);
332
+ continue;
333
+ }
334
+ if (/\b(test|vitest|jest|playwright|cypress|e2e)\b/i.test(command)) {
335
+ behaviorCommands.push(verifyCommand);
336
+ }
337
+ }
338
+ return { staticCommands, behaviorCommands };
339
+ }
340
+ function chooseFrontendVerifyCommands(input) {
341
+ if (input.parsedCommands.length > 0) {
342
+ return { commands: input.parsedCommands, commandSource: "inline" };
343
+ }
344
+ if (input.adapterCommands && input.adapterCommands.length > 0) {
345
+ return { commands: input.adapterCommands, commandSource: "adapter" };
346
+ }
347
+ return { commandSource: "inline" };
348
+ }
278
349
  function resolveDagVerifyStrategy(taskConfig) {
279
350
  return {
280
351
  intermediateQuota: taskConfig.dagVerifyStrategy?.intermediateQuota ?? taskConfig.verifyQuota,
@@ -311,6 +382,17 @@ function deriveParallelScoutPaths(taskConfig) {
311
382
  : allowed,
312
383
  };
313
384
  }
385
+ function deriveFrontendBehaviorPaths(taskConfig) {
386
+ if (taskConfig.allowedPaths.length === 0)
387
+ return ["**"];
388
+ const explicitTestPaths = taskConfig.allowedPaths.filter((entry) => entry.includes("/test/") ||
389
+ entry.includes("/tests/") ||
390
+ entry.includes("/e2e/") ||
391
+ entry.includes("/spec/"));
392
+ if (explicitTestPaths.length > 0)
393
+ return explicitTestPaths;
394
+ return taskConfig.allowedPaths;
395
+ }
314
396
  function toTaskRelativeSourcePath(sources, absolutePath) {
315
397
  return path
316
398
  .relative(sources.taskDir, absolutePath)
@@ -454,6 +536,7 @@ export async function loadTaskHybridSources(repoRoot, taskId) {
454
536
  enabledExecutors: resolveEnabledExecutors(manifest.executors),
455
537
  executorModelMatrix: resolveExecutorModelMatrices(manifest),
456
538
  verifyCommands,
539
+ sddEmbeddedSkills: await probeRepoLocalSddSkills(repoRoot),
457
540
  };
458
541
  }
459
542
  function mergeFinalVerifyCommands(repoRoot, taskConfig, adapterCommands) {
@@ -479,8 +562,7 @@ export function buildStandardHybridDagFromTask(sources) {
479
562
  const scoutPaths = deriveParallelScoutPaths(taskConfig);
480
563
  const scoutComplexity = mapTaskComplexity(taskConfig.complexity);
481
564
  const implementComplexity = resolveWriterComplexity(taskConfig);
482
- const writerExecutor = resolveImplementationExecutor(sources);
483
- const implementId = implementationNodeId(writerExecutor);
565
+ const implementId = implementationNodeId();
484
566
  const finalVerifyCommands = sources.verifyCommands?.final ?? [];
485
567
  const verifyShellCommands = buildVerifyShellCommands({
486
568
  repoRoot: sources.repoRoot,
@@ -604,7 +686,7 @@ export function buildStandardHybridDagFromTask(sources) {
604
686
  id: implementId,
605
687
  depends_on: ["plan-pi"],
606
688
  role: "implementer",
607
- executor: writerExecutor,
689
+ executor: "pi",
608
690
  toolProfile: "write",
609
691
  complexity: implementComplexity,
610
692
  writePolicy: "exclusive",
@@ -656,11 +738,305 @@ export function buildStandardHybridDagFromTask(sources) {
656
738
  },
657
739
  ],
658
740
  };
741
+ applySddEmbeddedEnhancements(spec, sources.sddEmbeddedSkills ?? new Set());
742
+ parseDagSpec(spec);
743
+ assertValidDagSpec(spec);
744
+ return spec;
745
+ }
746
+ function buildFrontendHybridDagFromTask(sources) {
747
+ const { taskConfig } = sources;
748
+ const forbiddenPaths = mergeForbiddenPaths(taskConfig);
749
+ const implementPaths = resolveImplementPaths(taskConfig);
750
+ const implementId = frontendImplementationNodeId();
751
+ const sourceContext = buildSourceContextBlock(sources);
752
+ const strategy = resolveDagVerifyStrategy(taskConfig);
753
+ const readOnlyPaths = taskConfig.allowedPaths.length > 0 ? taskConfig.allowedPaths : ["**"];
754
+ const behaviorPaths = deriveFrontendBehaviorPaths(taskConfig);
755
+ const globalConstraints = [
756
+ ...taskConfig.hardConstraints,
757
+ ...(sources.constraintMarkdown
758
+ ? [`See 执行约束.md in task source (${sources.taskId})`]
759
+ : []),
760
+ ...STANDARD_GLOBAL_CONSTRAINTS,
761
+ "frontend-implementation DAGs must pass the design verdict gate before any write node executes.",
762
+ "frontend-implementation DAGs must complete deterministic static verification and behavior verification before final review.",
763
+ "frontend review must block closeout unless review verdict is exactly VERDICT: pass.",
764
+ ];
765
+ const staticFallbackCommands = ["npm run typecheck", "npm run build"];
766
+ const behaviorFallbackCommands = ["npm test"];
767
+ const parsedFrontendVerifyCommands = extractFrontendVerifyCommandsFromMarkdown({
768
+ repoRoot: sources.repoRoot,
769
+ requirementMarkdown: sources.requirementMarkdown,
770
+ constraintMarkdown: sources.constraintMarkdown,
771
+ });
772
+ const staticVerifyCommands = chooseFrontendVerifyCommands({
773
+ parsedCommands: parsedFrontendVerifyCommands.staticCommands,
774
+ adapterCommands: sources.verifyCommands?.intermediate,
775
+ });
776
+ const behaviorVerifyCommands = chooseFrontendVerifyCommands({
777
+ parsedCommands: parsedFrontendVerifyCommands.behaviorCommands,
778
+ adapterCommands: sources.verifyCommands?.final,
779
+ });
780
+ const spec = {
781
+ version: 2,
782
+ title: `Frontend implementation DAG: ${taskConfig.title}`,
783
+ objective: extractObjective(sources.requirementMarkdown, taskConfig.title),
784
+ successCriteria: extractSuccessCriteria(sources.requirementMarkdown, sources.taskId),
785
+ globalConstraints,
786
+ defaults: {
787
+ ...FRONTEND_DEFAULTS,
788
+ contextProfile: taskConfig.contextProfile,
789
+ },
790
+ skillsByRole: FRONTEND_SKILLS_BY_ROLE,
791
+ executorModels: sources.executorModelMatrix ?? DEFAULT_DAG_EXECUTOR_MODELS,
792
+ verifyStrategy: resolveDagVerifyStrategy(taskConfig),
793
+ tasks: [
794
+ {
795
+ id: "frontend-contract-pi",
796
+ depends_on: [],
797
+ role: "planner",
798
+ executor: "pi",
799
+ complexity: "MED",
800
+ writePolicy: "read-only",
801
+ allowedPaths: readOnlyPaths,
802
+ forbiddenPaths,
803
+ skills: FRONTEND_IMPLEMENTATION_SKILLS,
804
+ outputContract: "Markdown contract with Scope, Non-goals, Acceptance Criteria, UI States, Target Runtime Environment, Risks, and Verification Expectations. No file writes.",
805
+ subtask_prompt: [
806
+ "Read task source and produce a concise frontend implementation contract.",
807
+ "Cover scope, non-goals, acceptance criteria, UI states, target runtime environment, risks, and verification expectations.",
808
+ "Read-only: do not modify code, docs, artifacts, or repository files.",
809
+ sourceContext,
810
+ ].join("\n\n"),
811
+ },
812
+ {
813
+ id: "frontend-scout-pi",
814
+ depends_on: ["frontend-contract-pi"],
815
+ role: "scout",
816
+ executor: "pi",
817
+ complexity: mapTaskComplexity(taskConfig.complexity),
818
+ writePolicy: "read-only",
819
+ allowedPaths: readOnlyPaths,
820
+ forbiddenPaths,
821
+ skills: FRONTEND_IMPLEMENTATION_SKILLS,
822
+ outputContract: "Markdown scout report covering frontend stack, routes, components, styling system, existing design conventions, state/data flow, test entry points, reuse opportunities, and risks. No file writes.",
823
+ subtask_prompt: [
824
+ "Inspect frontend code, routing, components, styles, package scripts, and tests.",
825
+ "Return code and design observations, existing reuse opportunities, and verification entry points.",
826
+ "Read-only: do not modify repository files.",
827
+ sourceContext,
828
+ ].join("\n\n"),
829
+ },
830
+ {
831
+ id: "frontend-plan-pi",
832
+ depends_on: ["frontend-scout-pi"],
833
+ role: "planner",
834
+ executor: "pi",
835
+ complexity: "MED",
836
+ writePolicy: "read-only",
837
+ allowedPaths: readOnlyPaths,
838
+ forbiddenPaths,
839
+ skills: FRONTEND_IMPLEMENTATION_SKILLS,
840
+ outputContract: "Markdown implementation plan with Implementation Steps, Target Files, UI State Handling, Styling / Component Strategy, Interaction Notes, Dependency Policy, Verification Plan, and Residual Risks. No file writes.",
841
+ subtask_prompt: [
842
+ "Based on frontend-contract-pi and frontend-scout-pi, return a minimal frontend implementation plan.",
843
+ "Include ordered steps, target files, UI state handling, styling/component strategy, interaction notes, dependency policy, and deterministic verification commands.",
844
+ "Read-only: do not modify code, docs, artifacts, or repository files.",
845
+ sourceContext,
846
+ ].join("\n\n"),
847
+ },
848
+ {
849
+ id: "frontend-design-gate-pi",
850
+ depends_on: ["frontend-plan-pi"],
851
+ role: "reviewer",
852
+ executor: "pi",
853
+ complexity: "MED",
854
+ writePolicy: "read-only",
855
+ allowedPaths: readOnlyPaths,
856
+ forbiddenPaths,
857
+ skills: FRONTEND_DESIGN_REVIEW_SKILLS,
858
+ outputContract: "Plain Markdown whose first non-empty line is VERDICT: pass or VERDICT: request-revision, followed by Findings, Required Plan Corrections, and Checked Items. No file writes.",
859
+ subtask_prompt: [
860
+ "Audit the frontend plan before implementation.",
861
+ "First non-empty line must be exactly VERDICT: pass or VERDICT: request-revision.",
862
+ "Request revision for missing applicable UI states, unsupported dependency additions, design-system drift without reason, weak interaction coverage, broad scope, or missing deterministic verification commands.",
863
+ "Read-only: do not modify repository files.",
864
+ sourceContext,
865
+ ].join("\n\n"),
866
+ },
867
+ {
868
+ id: "frontend-design-gate-shell",
869
+ depends_on: ["frontend-design-gate-pi"],
870
+ role: "verifier",
871
+ executor: "shell",
872
+ complexity: "LOW",
873
+ writePolicy: "read-only",
874
+ allowedPaths: readOnlyPaths,
875
+ forbiddenPaths,
876
+ outputContract: "Deterministic frontend design verdict gate: exit 0 only when frontend-design-gate-pi emits VERDICT: pass.",
877
+ subtask_prompt: "Deterministic gate: block frontend implementation unless frontend-design-gate-pi emitted VERDICT: pass.",
878
+ shell: {
879
+ commands: [],
880
+ verdictGate: {
881
+ fromNodeId: "frontend-design-gate-pi",
882
+ accept: ["VERDICT: pass"],
883
+ label: "frontend design gate",
884
+ lineMode: "first-verdict-line",
885
+ },
886
+ cwd: ".",
887
+ timeoutMs: 60000,
888
+ },
889
+ },
890
+ {
891
+ id: implementId,
892
+ depends_on: ["frontend-design-gate-shell"],
893
+ role: "implementer",
894
+ executor: "pi",
895
+ toolProfile: "write",
896
+ complexity: taskConfig.complexity === "large"
897
+ ? "HIGH"
898
+ : "MED",
899
+ writePolicy: "exclusive",
900
+ writeSet: implementPaths.writeSet,
901
+ allowedPaths: implementPaths.allowedPaths,
902
+ forbiddenPaths,
903
+ skills: FRONTEND_IMPLEMENTATION_SKILLS,
904
+ outputContract: "Markdown summary with Changed Files, Implemented Behavior, UI States Covered, Styling / Component Notes, Verification Attempted, and Residual Risks.",
905
+ subtask_prompt: [
906
+ "Implement the approved frontend plan with minimal focused changes.",
907
+ "Stay within writeSet and preserve unrelated files. Do not write root artifacts/** unless explicitly included in writeSet.",
908
+ sourceContext,
909
+ ].join("\n\n"),
910
+ },
911
+ {
912
+ id: "frontend-static-verify-shell",
913
+ depends_on: [implementId],
914
+ role: "verifier",
915
+ executor: "shell",
916
+ complexity: "LOW",
917
+ writePolicy: "read-only",
918
+ allowedPaths: readOnlyPaths,
919
+ forbiddenPaths,
920
+ outputContract: "Archived shell stdout/stderr with exit codes for deterministic static verification; no worktree writes.",
921
+ subtask_prompt: "Run deterministic static verification for the frontend implementation.",
922
+ shell: {
923
+ commands: buildVerifyShellCommands({
924
+ repoRoot: sources.repoRoot,
925
+ commands: staticVerifyCommands.commands,
926
+ fallbackCommands: staticFallbackCommands,
927
+ }),
928
+ verifyEvidence: buildVerifyEvidence({
929
+ phase: "intermediate",
930
+ quota: strategy.intermediateQuota ?? "full",
931
+ commandSource: staticVerifyCommands.commandSource,
932
+ commands: staticVerifyCommands.commands,
933
+ fallbackCommands: staticFallbackCommands,
934
+ }),
935
+ cwd: ".",
936
+ timeoutMs: 300000,
937
+ },
938
+ },
939
+ {
940
+ id: "frontend-behavior-verify-shell",
941
+ depends_on: ["frontend-static-verify-shell"],
942
+ role: "verifier",
943
+ executor: "shell",
944
+ complexity: "LOW",
945
+ writePolicy: "read-only",
946
+ allowedPaths: behaviorPaths,
947
+ forbiddenPaths,
948
+ outputContract: "Archived shell stdout/stderr with exit codes for deterministic behavior verification; no worktree writes.",
949
+ subtask_prompt: "Run deterministic behavior verification for frontend flows, states, and integration points.",
950
+ shell: {
951
+ commands: buildVerifyShellCommands({
952
+ repoRoot: sources.repoRoot,
953
+ commands: behaviorVerifyCommands.commands,
954
+ fallbackCommands: behaviorFallbackCommands,
955
+ }),
956
+ verifyEvidence: buildVerifyEvidence({
957
+ phase: "final",
958
+ quota: "full",
959
+ commandSource: behaviorVerifyCommands.commandSource,
960
+ commands: behaviorVerifyCommands.commands,
961
+ fallbackCommands: behaviorFallbackCommands,
962
+ finalFullRequired: true,
963
+ }),
964
+ cwd: ".",
965
+ timeoutMs: 300000,
966
+ },
967
+ },
968
+ {
969
+ id: "frontend-review-pi",
970
+ depends_on: ["frontend-behavior-verify-shell"],
971
+ role: "reviewer",
972
+ executor: "pi",
973
+ complexity: "HIGH",
974
+ writePolicy: "read-only",
975
+ allowedPaths: readOnlyPaths,
976
+ forbiddenPaths,
977
+ skills: FRONTEND_REVIEW_SKILLS,
978
+ outputContract: "Plain Markdown whose first non-empty line is VERDICT: pass or VERDICT: request-revision, followed by Findings, Verification Assessment, UX Assessment, and Residual Risks. No file writes.",
979
+ subtask_prompt: [
980
+ "Review the frontend implementation and verification evidence.",
981
+ "First non-empty line must be exactly VERDICT: pass or VERDICT: request-revision.",
982
+ "Any Critical or Important finding must force VERDICT: request-revision.",
983
+ "Review implementation quality, behavior/state coverage, verification evidence, and maintainability. Read-only: do not modify files.",
984
+ sourceContext,
985
+ ].join("\n\n"),
986
+ },
987
+ {
988
+ id: "frontend-review-gate-shell",
989
+ depends_on: ["frontend-review-pi"],
990
+ role: "verifier",
991
+ executor: "shell",
992
+ complexity: "LOW",
993
+ writePolicy: "read-only",
994
+ allowedPaths: readOnlyPaths,
995
+ forbiddenPaths,
996
+ outputContract: "Deterministic frontend review verdict gate: exit 0 only when frontend-review-pi emits VERDICT: pass.",
997
+ subtask_prompt: "Deterministic gate: block downstream closeout unless frontend-review-pi emitted VERDICT: pass.",
998
+ shell: {
999
+ commands: [],
1000
+ verdictGate: {
1001
+ fromNodeId: "frontend-review-pi",
1002
+ accept: ["VERDICT: pass"],
1003
+ label: "frontend review",
1004
+ lineMode: "first-verdict-line",
1005
+ },
1006
+ cwd: ".",
1007
+ timeoutMs: 60000,
1008
+ },
1009
+ },
1010
+ {
1011
+ id: "frontend-closeout-pi",
1012
+ depends_on: ["frontend-review-gate-shell"],
1013
+ role: "closeout",
1014
+ executor: "pi",
1015
+ complexity: "MED",
1016
+ writePolicy: "read-only",
1017
+ allowedPaths: taskConfig.allowedPaths.length > 0
1018
+ ? [...taskConfig.allowedPaths, "docs/**"]
1019
+ : ["**", "docs/**"],
1020
+ forbiddenPaths,
1021
+ skills: FRONTEND_VERIFICATION_SKILLS,
1022
+ outputContract: "Markdown closeout summary with Changes, Verification Evidence, Review Result, Known Risks, and Follow-up. No file writes.",
1023
+ subtask_prompt: [
1024
+ "Return a frontend closeout summary covering changes, verification evidence, review result, known risks, and follow-up.",
1025
+ "Read-only: do not modify code, docs, artifacts, or .harness/dag-runs/.",
1026
+ sourceContext,
1027
+ ].join("\n\n"),
1028
+ },
1029
+ ],
1030
+ };
659
1031
  parseDagSpec(spec);
660
1032
  assertValidDagSpec(spec);
661
1033
  return spec;
662
1034
  }
663
1035
  export function buildHybridDagFromTask(sources, options = {}) {
1036
+ if (sources.taskConfig.taskKind === "frontend-implementation" ||
1037
+ options.template === "frontend-implementation") {
1038
+ return buildFrontendHybridDagFromTask(sources);
1039
+ }
664
1040
  const standard = buildStandardHybridDagFromTask(sources);
665
1041
  const template = options.template ?? "standard-dag";
666
1042
  if (template === "standard-dag")
@@ -758,6 +1134,7 @@ function buildReviewGatedHybridDag(standard, sources) {
758
1134
  const closeout = getTaskOrThrow(spec, "closeout-pi");
759
1135
  replaceTask(spec, cloneTask(closeout, { depends_on: ["review-gate-shell"] }));
760
1136
  spec.tasks.splice(spec.tasks.length - 1, 0, buildReviewNode(sources), buildReviewGateNode(sources));
1137
+ applySddEmbeddedEnhancements(spec, sources.sddEmbeddedSkills ?? new Set());
761
1138
  parseDagSpec(spec);
762
1139
  assertValidDagSpec(spec);
763
1140
  return spec;
@@ -807,7 +1184,7 @@ function buildWriteSetGateNode(sources) {
807
1184
  };
808
1185
  }
809
1186
  function buildSoftVerifyNode(sources) {
810
- const implementId = implementationNodeId(resolveImplementationExecutor(sources));
1187
+ const implementId = implementationNodeId();
811
1188
  const strategy = resolveDagVerifyStrategy(sources.taskConfig);
812
1189
  const fallbackCommands = ["npm run typecheck"];
813
1190
  const commands = buildVerifyShellCommands({
@@ -841,8 +1218,8 @@ function buildSoftVerifyNode(sources) {
841
1218
  };
842
1219
  }
843
1220
  function buildProcessSupervisorNode(sources) {
844
- const implementId = implementationNodeId(resolveImplementationExecutor(sources));
845
- const repairId = repairNodeId(resolveImplementationExecutor(sources));
1221
+ const implementId = implementationNodeId();
1222
+ const repairId = repairNodeId();
846
1223
  return {
847
1224
  id: "process-supervisor-pi",
848
1225
  depends_on: ["soft-verify-shell", implementId],
@@ -893,13 +1270,12 @@ function buildProcessGateNode(sources) {
893
1270
  };
894
1271
  }
895
1272
  function buildRepairNode(sources) {
896
- const writerExecutor = resolveImplementationExecutor(sources);
897
1273
  const implement = resolveImplementPaths(sources.taskConfig);
898
1274
  return {
899
- id: repairNodeId(writerExecutor),
1275
+ id: repairNodeId(),
900
1276
  depends_on: ["process-gate-shell", "process-supervisor-pi"],
901
1277
  role: "implementer",
902
- executor: writerExecutor,
1278
+ executor: "pi",
903
1279
  toolProfile: "write",
904
1280
  complexity: isInteractiveUiTask(sources.taskConfig)
905
1281
  ? "HIGH"
@@ -921,7 +1297,7 @@ function buildRepairNode(sources) {
921
1297
  };
922
1298
  }
923
1299
  function buildHardVerifyNode(sources) {
924
- const repairId = repairNodeId(resolveImplementationExecutor(sources));
1300
+ const repairId = repairNodeId();
925
1301
  const fallbackCommands = [
926
1302
  "HARNESS_ALLOW_ACTIVE_DAG_RUNS=1 bash scripts/check-repo.sh",
927
1303
  ];
@@ -980,7 +1356,7 @@ function buildSupervisedHybridDag(standard, sources) {
980
1356
  const scoutSrc = getTaskOrThrow(standard, "scout-src");
981
1357
  const scoutTests = getTaskOrThrow(standard, "scout-tests");
982
1358
  const plan = getTaskOrThrow(standard, "plan-pi");
983
- const implement = getTaskOrThrow(standard, implementationNodeId(resolveImplementationExecutor(sources)));
1359
+ const implement = getTaskOrThrow(standard, implementationNodeId());
984
1360
  const closeout = getTaskOrThrow(standard, "closeout-pi");
985
1361
  const authorityDecision = resolveAuthoritySurfaceAudit(sources);
986
1362
  const reviewDependsOn = authorityDecision.enabled
@@ -1028,6 +1404,7 @@ function buildSupervisedHybridDag(standard, sources) {
1028
1404
  cloneTask(closeout, { depends_on: ["decision-pi"] }),
1029
1405
  ],
1030
1406
  };
1407
+ applySddEmbeddedEnhancements(spec, sources.sddEmbeddedSkills ?? new Set());
1031
1408
  parseDagSpec(spec);
1032
1409
  assertValidDagSpec(spec);
1033
1410
  return spec;
@@ -3,6 +3,7 @@ import { recordDecisionEnvelopeForNode, shouldPauseOnHumanEscalation, writeHuman
3
3
  import { writeNodeRecord, writeNodeSkillArtifacts } from "./run-store.js";
4
4
  import { buildDagNodePromptEnvelope } from "./prompt.js";
5
5
  import { persistLongNodeOutputArtifacts } from "./upstream-artifacts.js";
6
+ import { assertSkillSnapshotCoversSpec, buildNodePromptFromSnapshot, isDagSkillSnapshotIntegrityError, readSkillSnapshot, } from "./skill-snapshot.js";
6
7
  import { resolveDagSkillInstructions, skillInstructionMetadata, } from "./skill-instructions.js";
7
8
  import { resolveDagNodeSkills } from "./skills.js";
8
9
  import { parseRepairArtifactFromText, validateRepairArtifactScope, } from "./repair-artifact.js";
@@ -17,7 +18,7 @@ export function buildNodePrompt(spec, task, upstream) {
17
18
  }
18
19
  export async function buildNodePromptWithResolvedSkillInstructions(spec, task, upstream, cwd) {
19
20
  const skillNames = resolveDagNodeSkills(spec, task);
20
- const resolvedSkillInstructions = task.executor === "cursor" || task.executor === "pi"
21
+ const resolvedSkillInstructions = task.executor === "pi"
21
22
  ? await resolveDagSkillInstructions(skillNames, {
22
23
  cwd,
23
24
  includeLearnedPatterns: task.role === "implementer",
@@ -55,7 +56,7 @@ function assertRepairArtifactVerdictMatchesSupervisor(input) {
55
56
  }
56
57
  function findRepairTaskForGate(input) {
57
58
  return Array.from(input.tasksById.values()).find((candidate) => candidate.depends_on.includes(input.gateTask.id) &&
58
- candidate.id === "repair-cursor");
59
+ candidate.id === "repair-pi");
59
60
  }
60
61
  function parseSupervisorRepairArtifact(node) {
61
62
  const text = `${node?.assistantText ?? ""}\n${node?.stdout ?? ""}`;
@@ -111,6 +112,45 @@ export async function executeDagNode(input) {
111
112
  const { nodeId, tasksById, state, spec, cwd, runDir, executeNode } = input;
112
113
  const task = tasksById.get(nodeId);
113
114
  const node = state.nodes[nodeId];
115
+ const failSkillSnapshot = async (error) => {
116
+ const failedAt = new Date().toISOString();
117
+ node.startedAt ??= failedAt;
118
+ node.status = "ERROR";
119
+ node.stderr = error instanceof Error ? error.message : String(error);
120
+ node.failureCategory = "skill-snapshot-integrity";
121
+ node.finishedAt = failedAt;
122
+ node.lastActivityAt = node.finishedAt;
123
+ node.durationMs = Math.max(0, Date.now() - new Date(node.startedAt).getTime());
124
+ state.nodes[nodeId].nodeRecordPath = path.join(runDir, `${nodeId}.json`);
125
+ await writeNodeRecord(runDir, nodeId, state.nodes[nodeId]);
126
+ await input.persistState();
127
+ await notifyNodeObserver(input.observer, "onNodeFinish", nodeId, state);
128
+ };
129
+ const isDynamicTask = Boolean(task.dynamicExpansion
130
+ || task.dynamicReduction
131
+ || task.dynamicCondition
132
+ || task.dynamicLoopUntil);
133
+ let snapshotPrompt;
134
+ if (state.skillSnapshotRef) {
135
+ try {
136
+ const skillSnapshot = await readSkillSnapshot(runDir, state.skillSnapshotRef, {
137
+ expectedRunId: state.runId,
138
+ });
139
+ assertSkillSnapshotCoversSpec(skillSnapshot, spec, state);
140
+ if (!isDynamicTask) {
141
+ snapshotPrompt = buildNodePromptFromSnapshot({
142
+ spec,
143
+ task,
144
+ upstream: state.nodes,
145
+ snapshot: skillSnapshot,
146
+ });
147
+ }
148
+ }
149
+ catch (error) {
150
+ await failSkillSnapshot(error);
151
+ return;
152
+ }
153
+ }
114
154
  node.status = "RUNNING";
115
155
  node.startedAt = new Date().toISOString();
116
156
  node.lastActivityAt = node.startedAt;
@@ -119,10 +159,7 @@ export async function executeDagNode(input) {
119
159
  }
120
160
  await input.persistState();
121
161
  await notifyNodeObserver(input.observer, "onNodeStart", nodeId, state);
122
- if (task.dynamicExpansion ||
123
- task.dynamicReduction ||
124
- task.dynamicCondition ||
125
- task.dynamicLoopUntil) {
162
+ if (isDynamicTask) {
126
163
  const started = Date.now();
127
164
  try {
128
165
  const result = await input.executeDynamicNode({
@@ -165,7 +202,24 @@ export async function executeDagNode(input) {
165
202
  await notifyNodeObserver(input.observer, "onNodeFinish", nodeId, state);
166
203
  return;
167
204
  }
168
- const { prompt, resolvedSkills } = await buildNodePromptWithResolvedSkillInstructions(spec, task, state.nodes, cwd);
205
+ let prompt;
206
+ let resolvedSkills;
207
+ try {
208
+ if (snapshotPrompt) {
209
+ prompt = snapshotPrompt.prompt;
210
+ resolvedSkills = snapshotPrompt.resolvedSkills;
211
+ }
212
+ else {
213
+ ({ prompt, resolvedSkills } =
214
+ await buildNodePromptWithResolvedSkillInstructions(spec, task, state.nodes, cwd));
215
+ }
216
+ }
217
+ catch (error) {
218
+ if (!isDagSkillSnapshotIntegrityError(error))
219
+ throw error;
220
+ await failSkillSnapshot(error);
221
+ return;
222
+ }
169
223
  node.resolvedSkills = resolvedSkills;
170
224
  await writeNodeSkillArtifacts(runDir, nodeId, resolvedSkills);
171
225
  const model = resolveModelForTask(task, spec.executorModels);