@tea-agent/loop-agent 0.1.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 (264) hide show
  1. package/AGENTS.md +121 -0
  2. package/CHANGELOG.md +37 -0
  3. package/README.md +144 -0
  4. package/bin/loop-agent.js +21 -0
  5. package/dist/adapters/aimax.js +91 -0
  6. package/dist/adapters/context.js +32 -0
  7. package/dist/adapters/index.js +28 -0
  8. package/dist/adapters/loop-agent.js +98 -0
  9. package/dist/adapters/types.js +1 -0
  10. package/dist/cli/catalog.js +259 -0
  11. package/dist/cli/help.js +55 -0
  12. package/dist/cli/index.js +3 -0
  13. package/dist/cli/program.js +505 -0
  14. package/dist/cli.js +12 -0
  15. package/dist/commands/closeout.js +13 -0
  16. package/dist/commands/coverage-audit.js +14 -0
  17. package/dist/commands/cursor-prompt.js +222 -0
  18. package/dist/commands/cursor-worker.js +43 -0
  19. package/dist/commands/dag-approve.js +102 -0
  20. package/dist/commands/dag-final-verification.js +76 -0
  21. package/dist/commands/dag-init-hybrid.js +56 -0
  22. package/dist/commands/dag-reconcile-tasks.js +51 -0
  23. package/dist/commands/dag-reject.js +91 -0
  24. package/dist/commands/dag-report.js +177 -0
  25. package/dist/commands/dag-resume.js +34 -0
  26. package/dist/commands/dag-run-task.js +470 -0
  27. package/dist/commands/dag-validate.js +186 -0
  28. package/dist/commands/dag-workflow-compile.js +91 -0
  29. package/dist/commands/dag-workflow-plan.js +130 -0
  30. package/dist/commands/dag-workflow-validate.js +66 -0
  31. package/dist/commands/delegate.js +132 -0
  32. package/dist/commands/docs-archive.js +5 -0
  33. package/dist/commands/docs-audit.js +5 -0
  34. package/dist/commands/doctor.js +50 -0
  35. package/dist/commands/goal.js +92 -0
  36. package/dist/commands/handoff-check.js +5 -0
  37. package/dist/commands/harvest.js +44 -0
  38. package/dist/commands/inspect.js +11 -0
  39. package/dist/commands/instructions.js +195 -0
  40. package/dist/commands/knowledge.js +64 -0
  41. package/dist/commands/loop-benchmark.js +72 -0
  42. package/dist/commands/loop.js +241 -0
  43. package/dist/commands/new-task.js +5 -0
  44. package/dist/commands/pi-prompt.js +181 -0
  45. package/dist/commands/pi-reuse-benchmark.js +153 -0
  46. package/dist/commands/plan-list.js +5 -0
  47. package/dist/commands/promote-run.js +29 -0
  48. package/dist/commands/reference-index.js +16 -0
  49. package/dist/commands/run-dag.js +184 -0
  50. package/dist/commands/spine.js +38 -0
  51. package/dist/commands/stats.js +84 -0
  52. package/dist/commands/status.js +56 -0
  53. package/dist/commands/study-init.js +192 -0
  54. package/dist/commands/workflow.js +259 -0
  55. package/dist/commands/worktree-create.js +31 -0
  56. package/dist/commands/worktree-list.js +5 -0
  57. package/dist/commands/worktree-remove.js +26 -0
  58. package/dist/cursor-worker-entry.js +8 -0
  59. package/dist/executors/config-core.js +55 -0
  60. package/dist/executors/config.js +2 -0
  61. package/dist/executors/cursor-artifacts.js +33 -0
  62. package/dist/executors/cursor-execution-log.js +81 -0
  63. package/dist/executors/cursor-executor-artifacts.js +135 -0
  64. package/dist/executors/cursor-executor.js +468 -0
  65. package/dist/executors/cursor-run.js +115 -0
  66. package/dist/executors/cursor-tool.js +94 -0
  67. package/dist/executors/cursor-worker-client.js +213 -0
  68. package/dist/executors/cursor-worker-protocol.js +18 -0
  69. package/dist/executors/cursor-worker-server.js +54 -0
  70. package/dist/executors/cursor-worker.js +3 -0
  71. package/dist/executors/cursor.js +6 -0
  72. package/dist/executors/dag-cursor-executor.js +88 -0
  73. package/dist/executors/dag-pi-executor.js +322 -0
  74. package/dist/executors/dag-static-executor.js +45 -0
  75. package/dist/executors/dag.js +4 -0
  76. package/dist/executors/index.js +8 -0
  77. package/dist/executors/model-routing.js +60 -0
  78. package/dist/executors/pi-event-serializer.js +43 -0
  79. package/dist/executors/pi-executor.js +606 -0
  80. package/dist/executors/pi-reuse-benchmark.js +316 -0
  81. package/dist/executors/pi-runtime-reuse.js +29 -0
  82. package/dist/executors/pi-sdk-executor.js +255 -0
  83. package/dist/executors/pi-sdk.js +1 -0
  84. package/dist/executors/pi.js +3 -0
  85. package/dist/executors/shell-executor.js +300 -0
  86. package/dist/executors/shell-presets.js +47 -0
  87. package/dist/executors/shell-verification.js +251 -0
  88. package/dist/executors/shell-write-guard.js +126 -0
  89. package/dist/executors/shell.js +3 -0
  90. package/dist/executors/static.js +1 -0
  91. package/dist/governance/checks.js +434 -0
  92. package/dist/governance/harness.js +9 -0
  93. package/dist/governance/index.js +3 -0
  94. package/dist/governance/manifest-types.js +128 -0
  95. package/dist/governance/manifest.js +2 -0
  96. package/dist/governance/path-guard.js +69 -0
  97. package/dist/governance/path-guards.js +2 -0
  98. package/dist/governance/profiles.js +3 -0
  99. package/dist/governance/requirement-coverage.js +425 -0
  100. package/dist/governance/skill-safety.js +135 -0
  101. package/dist/governance/spine-audit.js +152 -0
  102. package/dist/records/closeout.js +2 -0
  103. package/dist/records/harvest.js +236 -0
  104. package/dist/records/index.js +3 -0
  105. package/dist/records/one-shot-runs.js +421 -0
  106. package/dist/records/promotion.js +199 -0
  107. package/dist/shared/artifacts-core.js +88 -0
  108. package/dist/shared/artifacts.js +2 -0
  109. package/dist/shared/context-files.js +32 -0
  110. package/dist/shared/context.js +2 -0
  111. package/dist/shared/copy-dir.js +17 -0
  112. package/dist/shared/git-progress.js +165 -0
  113. package/dist/shared/index.js +5 -0
  114. package/dist/shared/logger.js +23 -0
  115. package/dist/shared/one-shot-prompt-args.js +98 -0
  116. package/dist/shared/path-refs.js +31 -0
  117. package/dist/shared/prompts.js +26 -0
  118. package/dist/shared/reference-context.js +238 -0
  119. package/dist/shared/timeout-policy.js +19 -0
  120. package/dist/shared/timeout.js +1 -0
  121. package/dist/shared/types.js +5 -0
  122. package/dist/task/config-types.js +97 -0
  123. package/dist/task/config.js +2 -0
  124. package/dist/task/delegate.js +220 -0
  125. package/dist/task/goal-audit.js +51 -0
  126. package/dist/task/goal-policy.js +8 -0
  127. package/dist/task/goal.js +3 -0
  128. package/dist/task/ids.js +1 -0
  129. package/dist/task/index.js +9 -0
  130. package/dist/task/lifecycle.js +1 -0
  131. package/dist/task/paths.js +1 -0
  132. package/dist/task/read-model.js +149 -0
  133. package/dist/task/runtime.js +699 -0
  134. package/dist/task/source-state.js +1 -0
  135. package/dist/task/state.js +55 -0
  136. package/dist/task/subagent-guidance.js +1 -0
  137. package/dist/task/workflow-state-types.js +92 -0
  138. package/dist/task/worktree-cleanup.js +140 -0
  139. package/dist/task/worktree.js +171 -0
  140. package/dist/workflows/dag/authoring.js +8 -0
  141. package/dist/workflows/dag/authority-surface.js +138 -0
  142. package/dist/workflows/dag/canvas-observer.js +474 -0
  143. package/dist/workflows/dag/decision-envelope.js +502 -0
  144. package/dist/workflows/dag/decision-evidence.js +153 -0
  145. package/dist/workflows/dag/decision-gates.js +1 -0
  146. package/dist/workflows/dag/executor-registry.js +25 -0
  147. package/dist/workflows/dag/facts.js +4 -0
  148. package/dist/workflows/dag/failure-category.js +111 -0
  149. package/dist/workflows/dag/final-verification.js +180 -0
  150. package/dist/workflows/dag/governance-constants.js +5 -0
  151. package/dist/workflows/dag/governance-profile.js +405 -0
  152. package/dist/workflows/dag/index.js +6 -0
  153. package/dist/workflows/dag/init-hybrid.js +855 -0
  154. package/dist/workflows/dag/knowledge-curator.js +162 -0
  155. package/dist/workflows/dag/lifecycle.js +484 -0
  156. package/dist/workflows/dag/prompt-source.js +88 -0
  157. package/dist/workflows/dag/prompt.js +130 -0
  158. package/dist/workflows/dag/reconcile-tasks.js +404 -0
  159. package/dist/workflows/dag/recovery-recommendation.js +226 -0
  160. package/dist/workflows/dag/repair-artifact.js +136 -0
  161. package/dist/workflows/dag/report.js +1019 -0
  162. package/dist/workflows/dag/runner.js +1677 -0
  163. package/dist/workflows/dag/runtime.js +5 -0
  164. package/dist/workflows/dag/skill-instructions.js +471 -0
  165. package/dist/workflows/dag/skills.js +41 -0
  166. package/dist/workflows/dag/spec.js +3 -0
  167. package/dist/workflows/dag/topo.js +30 -0
  168. package/dist/workflows/dag/types.js +275 -0
  169. package/dist/workflows/dag/upstream-artifacts.js +95 -0
  170. package/dist/workflows/dag/validate.js +527 -0
  171. package/dist/workflows/dynamic/artifacts.js +65 -0
  172. package/dist/workflows/dynamic/compile.js +360 -0
  173. package/dist/workflows/dynamic/compileTypes.js +1 -0
  174. package/dist/workflows/dynamic/errors.js +5 -0
  175. package/dist/workflows/dynamic/index.js +7 -0
  176. package/dist/workflows/dynamic/profiles.js +156 -0
  177. package/dist/workflows/dynamic/spec.js +114 -0
  178. package/dist/workflows/dynamic/validate.js +275 -0
  179. package/dist/workflows/loop/actions.js +1334 -0
  180. package/dist/workflows/loop/benchmark.js +510 -0
  181. package/dist/workflows/loop/closeout.js +134 -0
  182. package/dist/workflows/loop/context.js +48 -0
  183. package/dist/workflows/loop/events.js +25 -0
  184. package/dist/workflows/loop/hash.js +32 -0
  185. package/dist/workflows/loop/index.js +8 -0
  186. package/dist/workflows/loop/paths.js +17 -0
  187. package/dist/workflows/loop/rounds.js +81 -0
  188. package/dist/workflows/loop/signals.js +55 -0
  189. package/dist/workflows/loop/state.js +116 -0
  190. package/dist/workflows/loop/templates.js +54 -0
  191. package/dist/workflows/loop/types.js +28 -0
  192. package/docs/README.md +62 -0
  193. package/docs/agent-dag-recovery-playbook.md +158 -0
  194. package/docs/agent-dag-runner.md +40 -0
  195. package/docs/cursor-executor-usage.md +25 -0
  196. package/docs/decisions/README.md +3 -0
  197. package/docs/design/README.md +36 -0
  198. package/docs/development-principles.md +71 -0
  199. package/docs/dynamic-workflow-dag-engine-roadmap.md +1749 -0
  200. package/docs/exec-plans/README.md +6 -0
  201. package/docs/exec-plans/active/README.md +5 -0
  202. package/docs/exec-plans/completed/README.md +5 -0
  203. package/docs/feature-workflow.md +184 -0
  204. package/docs/harness-methodology-debugging.md +153 -0
  205. package/docs/harness-methodology-tdd.md +130 -0
  206. package/docs/harness-methodology-verification.md +27 -0
  207. package/docs/loop-agent-harness.md +42 -0
  208. package/docs/progress/README.md +3 -0
  209. package/docs/reports/README.md +3 -0
  210. package/docs/templates/adr.md +60 -0
  211. package/docs/templates/agent-dag-authority-surface-audit.prompt.md +94 -0
  212. package/docs/templates/agent-dag-decision-envelope.schema.json +213 -0
  213. package/docs/templates/agent-dag-decision-gate-dogfood-report.md +117 -0
  214. package/docs/templates/agent-dag-decision-gate.prompt.md +246 -0
  215. package/docs/templates/agent-dag-process-supervisor.prompt.md +98 -0
  216. package/docs/templates/agent-dag-report.schema.json +423 -0
  217. package/docs/templates/agent-dag-review-verdict.prompt.md +68 -0
  218. package/docs/templates/agent-dag.base.json +195 -0
  219. package/docs/templates/agent-dag.final-verification.json +190 -0
  220. package/docs/templates/agent-dag.schema.json +316 -0
  221. package/docs/templates/agent-dag.supervised-implementation.json +500 -0
  222. package/docs/templates/exec-plan.md +64 -0
  223. package/docs/templates/feature-spec.md +53 -0
  224. package/docs/templates/hybrid-dag.json +193 -0
  225. package/docs/templates/progress-log.md +17 -0
  226. package/docs/templates/project-start-checklist.md +9 -0
  227. package/docs/templates/qa-report.md +42 -0
  228. package/docs/templates/sprint-contract.md +29 -0
  229. package/docs/verification-matrix.md +30 -0
  230. package/examples/decision-gate-agent-dag.json +123 -0
  231. package/examples/example-dag.json +51 -0
  232. package/examples/hybrid-loop-agent-dag.json +194 -0
  233. package/harness.json +92 -0
  234. package/package.json +61 -0
  235. package/skills/ai-engineering-context/SKILL.md +48 -0
  236. package/skills/loop-agent/SKILL.md +260 -0
  237. package/skills/loop-agent/references/README.md +63 -0
  238. package/skills/loop-agent/references/command-reference.md +315 -0
  239. package/skills/loop-agent/references/harness-policy.md +258 -0
  240. package/skills/loop-agent/references/hybrid-dag.md +216 -0
  241. package/skills/loop-agent/references/learned/README.md +21 -0
  242. package/skills/loop-agent/references/model-routing.md +36 -0
  243. package/skills/loop-agent/references/multi-worktree.md +54 -0
  244. package/skills/loop-agent/references/one-shot-runs.md +85 -0
  245. package/skills/loop-agent/references/orchestrator-and-interventions.md +169 -0
  246. package/skills/loop-agent/references/pi-prompt.md +23 -0
  247. package/skills/loop-agent/references/pi-subagent-assisted-mode.md +83 -0
  248. package/skills/loop-agent/references/post-implementation-and-patterns.md +44 -0
  249. package/skills/loop-agent/references/task-workflow.md +84 -0
  250. package/skills/loop-agent/references/verification-and-failure-handling.md +74 -0
  251. package/skills/requesting-code-review/SKILL.md +101 -0
  252. package/skills/requesting-code-review/code-reviewer.md +168 -0
  253. package/skills/systematic-debugging/CREATION-LOG.md +119 -0
  254. package/skills/systematic-debugging/SKILL.md +296 -0
  255. package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
  256. package/skills/systematic-debugging/condition-based-waiting.md +115 -0
  257. package/skills/systematic-debugging/defense-in-depth.md +122 -0
  258. package/skills/systematic-debugging/find-polluter.sh +63 -0
  259. package/skills/systematic-debugging/root-cause-tracing.md +169 -0
  260. package/skills/systematic-debugging/test-academic.md +14 -0
  261. package/skills/systematic-debugging/test-pressure-1.md +58 -0
  262. package/skills/systematic-debugging/test-pressure-2.md +68 -0
  263. package/skills/systematic-debugging/test-pressure-3.md +69 -0
  264. package/skills/verification-before-completion/SKILL.md +154 -0
@@ -0,0 +1,855 @@
1
+ import { access, readFile, writeFile } from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { assertValidDagSpec } from "./validate.js";
5
+ import { parseDagSpec, } from "./types.js";
6
+ import { pathMatchesPattern } from "../../shared/git-progress.js";
7
+ import { BASELINE_FORBIDDEN_PATHS } from "./governance-constants.js";
8
+ import { resolveAdapter } from "../../adapters/index.js";
9
+ import { loadHarnessManifest } from "../../governance/harness.js";
10
+ import { buildAuthoritySurfaceAuditNode, buildAuthoritySurfaceGateNode, resolveAuthoritySurfaceAudit, } from "./authority-surface.js";
11
+ import { getTaskPaths, loadTaskConfig } from "../../task/runtime.js";
12
+ import { resolveVerifyPreset } from "../../executors/shell-verification.js";
13
+ const REQUIREMENT_FILE = "需求.md";
14
+ const CONSTRAINT_FILE = "执行约束.md";
15
+ const MAX_SOURCE_EXCERPT_CHARS = 2000;
16
+ const HYBRID_DEFAULTS = {
17
+ executor: "pi",
18
+ piBackend: "sdk-first",
19
+ contextProfile: "slim",
20
+ skills: ["ai-engineering-context"],
21
+ writePolicy: "read-only",
22
+ };
23
+ const HYBRID_SKILLS_BY_ROLE = {
24
+ planner: ["loop-agent"],
25
+ scout: ["ai-engineering-context"],
26
+ implementer: ["verification-before-completion"],
27
+ reviewer: ["requesting-code-review"],
28
+ verifier: ["verification-before-completion", "systematic-debugging"],
29
+ closeout: ["loop-agent", "verification-before-completion"],
30
+ };
31
+ const IMPLEMENT_WRITESET_PLACEHOLDER = "REPLACE/WITH/NARROW/IMPLEMENT/PATHS/**";
32
+ function resolveEnabledExecutors(manifestExecutors) {
33
+ return Object.entries(manifestExecutors)
34
+ .filter(([, config]) => config.enabled !== false)
35
+ .map(([name]) => name);
36
+ }
37
+ function resolveImplementationExecutor(sources) {
38
+ const enabled = sources.enabledExecutors
39
+ ? new Set(sources.enabledExecutors)
40
+ : undefined;
41
+ if (!enabled || enabled.has("pi"))
42
+ return "pi";
43
+ if (enabled.has("cursor"))
44
+ return "cursor";
45
+ return "pi";
46
+ }
47
+ function implementationNodeId(executor) {
48
+ return executor === "cursor" ? "implement-cursor" : "implement-pi";
49
+ }
50
+ function repairNodeId(executor) {
51
+ return executor === "cursor" ? "repair-cursor" : "repair-pi";
52
+ }
53
+ function probePathsForWriteSetEntry(entry) {
54
+ const normalized = entry.replace(/\\/g, "/").replace(/^\.\//, "");
55
+ if (!normalized.includes("*")) {
56
+ return [normalized];
57
+ }
58
+ const concrete = normalized
59
+ .replace(/\/\*\*$/, "/_probe_")
60
+ .replace(/\*\*$/, "_probe_")
61
+ .replace(/\*/g, "_");
62
+ return Array.from(new Set([normalized, concrete]));
63
+ }
64
+ function allowedPathHitsBaselineForbidden(allowedPath) {
65
+ const probes = probePathsForWriteSetEntry(allowedPath);
66
+ for (const forbidden of BASELINE_FORBIDDEN_PATHS) {
67
+ if (probes.some((probe) => pathMatchesPattern(probe, forbidden)) ||
68
+ pathMatchesPattern(allowedPath, forbidden) ||
69
+ pathMatchesPattern(forbidden, allowedPath)) {
70
+ return forbidden;
71
+ }
72
+ }
73
+ return undefined;
74
+ }
75
+ export function assertTaskAllowedPathsPreflight(taskConfig) {
76
+ for (const allowedPath of taskConfig.allowedPaths) {
77
+ const forbidden = allowedPathHitsBaselineForbidden(allowedPath);
78
+ if (forbidden) {
79
+ throw new Error(`task allowedPaths entry "${allowedPath}" overlaps baseline forbidden path "${forbidden}"; task source paths are main-session owned and must not be executor writeSet`);
80
+ }
81
+ }
82
+ }
83
+ const STANDARD_GLOBAL_CONSTRAINTS = [
84
+ "Do not commit runtime traces under .harness/dag-runs/",
85
+ "Do not enable cross-node Pi runtime reuse by default",
86
+ "Preserve CODE_AGENT_PI_BACKEND=cli-only rollback for Pi nodes",
87
+ "Same-rank exclusive writeSet entries must be disjoint",
88
+ "Every task must explicitly declare executor; defaults.executor is schema-only and not a runtime fallback",
89
+ "Prefer same-rank parallel read-only scouts over serial chains when outputs are independent",
90
+ "If the DAG is a single linear chain, challenge whether read-only work can run in parallel ranks",
91
+ "Read-only DAG nodes must not write repository files, including root artifacts/**; return findings in node output only",
92
+ "Root artifacts/ is reserved for explicit exclusive write nodes, not read-only scout/reviewer output",
93
+ "exclusive implementer nodes must use narrow, concrete writeSet paths; never keep ** or repo root",
94
+ `Replace ${IMPLEMENT_WRITESET_PLACEHOLDER} with concrete paths before executing the implementation writer`,
95
+ ];
96
+ function mapTaskComplexity(complexity) {
97
+ if (complexity === "small")
98
+ return "LOW";
99
+ if (complexity === "large")
100
+ return "HIGH";
101
+ return "MED";
102
+ }
103
+ function excerptMarkdown(content, maxChars = MAX_SOURCE_EXCERPT_CHARS) {
104
+ const trimmed = content.trim();
105
+ if (trimmed.length <= maxChars)
106
+ return trimmed;
107
+ return `${trimmed.slice(0, maxChars)}\n\n...[truncated for draft prompt]`;
108
+ }
109
+ function extractObjective(requirementMarkdown, title) {
110
+ const lines = requirementMarkdown.split("\n");
111
+ for (const line of lines) {
112
+ const trimmed = line.trim();
113
+ if (!trimmed || trimmed.startsWith("#"))
114
+ continue;
115
+ return trimmed.slice(0, 500);
116
+ }
117
+ return `Implement task: ${title}`;
118
+ }
119
+ function extractSuccessCriteria(requirementMarkdown, taskId) {
120
+ const criteria = [];
121
+ const sectionMatch = requirementMarkdown.match(/##\s*验收标准\s*\n([\s\S]*?)(?=\n##\s|\n#\s|$)/);
122
+ if (sectionMatch) {
123
+ for (const line of sectionMatch[1].split("\n")) {
124
+ const trimmed = line.replace(/^[-*]\s*\[[ xX]\]\s*/, "").trim();
125
+ if (trimmed.startsWith("-") || trimmed.startsWith("*")) {
126
+ criteria.push(trimmed.replace(/^[-*]\s*/, "").trim());
127
+ }
128
+ else if (trimmed.length > 0 && !trimmed.startsWith("#")) {
129
+ criteria.push(trimmed);
130
+ }
131
+ }
132
+ }
133
+ if (criteria.length > 0)
134
+ return criteria.slice(0, 8);
135
+ return [
136
+ `contract-pi produces an auditable read-only implementation contract for ${taskId}`,
137
+ "scout-src and scout-tests complete parallel read-only reconnaissance without file writes",
138
+ "plan-pi returns a read-only implementation plan aligned with constraints",
139
+ "the implementation writer changes only paths declared in writeSet",
140
+ "verify-pi returns read-only verification strategy and residual risks",
141
+ "closeout-pi returns read-only handoff summary without writing artifacts/docs",
142
+ ];
143
+ }
144
+ function mergeForbiddenPaths(taskConfig) {
145
+ const merged = new Set([
146
+ ".harness/**",
147
+ ".harness/dag-runs/**",
148
+ "artifacts/**",
149
+ ...taskConfig.forbiddenPaths,
150
+ ]);
151
+ return [...merged];
152
+ }
153
+ function resolveImplementPaths(taskConfig) {
154
+ if (taskConfig.allowedPaths.length > 0) {
155
+ const allowed = [...taskConfig.allowedPaths];
156
+ return {
157
+ allowedPaths: allowed,
158
+ writeSet: [...allowed],
159
+ };
160
+ }
161
+ return {
162
+ allowedPaths: ["**"],
163
+ writeSet: [IMPLEMENT_WRITESET_PLACEHOLDER],
164
+ };
165
+ }
166
+ function shellQuote(value) {
167
+ return `'${value.replace(/'/g, `'\\''`)}'`;
168
+ }
169
+ function verifyCommandToShell(repoRoot, command) {
170
+ const relativeCwd = path.relative(repoRoot, command.cwd);
171
+ const cwdPrefix = relativeCwd && !relativeCwd.startsWith("..") && !path.isAbsolute(relativeCwd)
172
+ ? `cd ${shellQuote(relativeCwd)} && `
173
+ : command.cwd !== repoRoot
174
+ ? `cd ${shellQuote(command.cwd)} && `
175
+ : "";
176
+ const envPrefix = [
177
+ ...(command.args.includes("scripts/check-repo.sh")
178
+ ? ["HARNESS_ALLOW_ACTIVE_DAG_RUNS=1"]
179
+ : []),
180
+ ...Object.entries(command.env ?? {}).map(([key, value]) => `${key}=${shellQuote(String(value))}`),
181
+ ].join(" ");
182
+ const commandText = command.args.map(shellQuote).join(" ");
183
+ return `${cwdPrefix}${envPrefix ? `${envPrefix} ` : ""}${commandText}`;
184
+ }
185
+ function buildVerifyShellCommands(input) {
186
+ if (!input.repoRoot || !input.commands || input.commands.length === 0) {
187
+ return input.fallbackCommands;
188
+ }
189
+ const repoRoot = input.repoRoot;
190
+ return input.commands.map((command) => verifyCommandToShell(repoRoot, command));
191
+ }
192
+ function resolveDagVerifyStrategy(taskConfig) {
193
+ return {
194
+ intermediateQuota: taskConfig.dagVerifyStrategy?.intermediateQuota ?? taskConfig.verifyQuota,
195
+ finalQuota: "full",
196
+ focusedCommandSource: taskConfig.dagVerifyStrategy?.focusedCommandSource ?? "adapter",
197
+ };
198
+ }
199
+ function buildVerifyEvidence(input) {
200
+ return {
201
+ phase: input.phase,
202
+ quota: input.quota,
203
+ commandSource: input.commandSource,
204
+ commandCount: input.commands?.length ?? input.fallbackCommands.length,
205
+ commandLabels: input.commands?.map((command) => command.label) ?? input.fallbackCommands,
206
+ finalFullRequired: input.finalFullRequired,
207
+ };
208
+ }
209
+ function deriveParallelScoutPaths(taskConfig) {
210
+ const allowed = taskConfig.allowedPaths;
211
+ if (allowed.length === 0) {
212
+ return { srcPaths: ["**"], testPaths: ["**"] };
213
+ }
214
+ const srcPaths = allowed.filter((entry) => !entry.includes("/test/"));
215
+ const explicitTestPaths = allowed.filter((entry) => entry.includes("/test/"));
216
+ const derivedTestPaths = allowed
217
+ .filter((entry) => entry.includes("/src/"))
218
+ .map((entry) => entry.replace("/src/", "/test/"));
219
+ return {
220
+ srcPaths: srcPaths.length > 0 ? srcPaths : allowed,
221
+ testPaths: explicitTestPaths.length > 0
222
+ ? explicitTestPaths
223
+ : derivedTestPaths.length > 0
224
+ ? derivedTestPaths
225
+ : allowed,
226
+ };
227
+ }
228
+ function buildSourceContextBlock(sources) {
229
+ const parts = [
230
+ "## Task source: 需求.md",
231
+ excerptMarkdown(sources.requirementMarkdown),
232
+ ];
233
+ if (sources.constraintMarkdown) {
234
+ parts.push("## Task source: 执行约束.md", excerptMarkdown(sources.constraintMarkdown));
235
+ }
236
+ parts.push("## Task config summary", `- taskId: ${sources.taskConfig.taskId}`, `- flow: ${sources.taskConfig.flow}`, `- complexity: ${sources.taskConfig.complexity}`, `- contextProfile: ${sources.taskConfig.contextProfile}`, `- allowedPaths: ${sources.taskConfig.allowedPaths.join(", ") || "(none — review before execute)"}`, `- forbiddenPaths: ${sources.taskConfig.forbiddenPaths.join(", ") || "(none)"}`, '- Pi DAG nodes are read-only unless toolProfile="write" is explicitly selected for a bounded writer node.', "- Agent DAG read-only nodes must not write root artifacts/**; root artifacts/ is not a per-node scratchpad.");
237
+ if (sources.taskConfig.hardConstraints.length > 0) {
238
+ parts.push("- hardConstraints:", ...sources.taskConfig.hardConstraints.map((c) => ` - ${c}`));
239
+ }
240
+ return parts.join("\n\n");
241
+ }
242
+ export async function loadTaskHybridSources(repoRoot, taskId) {
243
+ const paths = getTaskPaths(repoRoot, taskId);
244
+ const requirementPath = path.join(paths.sourceDir, REQUIREMENT_FILE);
245
+ const constraintPath = path.join(paths.sourceDir, CONSTRAINT_FILE);
246
+ try {
247
+ await access(paths.taskConfigPath);
248
+ }
249
+ catch {
250
+ throw new Error(`task.json not found for task "${taskId}": expected ${paths.taskConfigPath}`);
251
+ }
252
+ let requirementMarkdown;
253
+ try {
254
+ requirementMarkdown = await readFile(requirementPath, "utf-8");
255
+ }
256
+ catch {
257
+ throw new Error(`required source file missing for task "${taskId}": ${requirementPath}`);
258
+ }
259
+ let constraintMarkdown = null;
260
+ try {
261
+ constraintMarkdown = await readFile(constraintPath, "utf-8");
262
+ }
263
+ catch {
264
+ // optional
265
+ }
266
+ const taskConfig = await loadTaskConfig(repoRoot, taskId);
267
+ const manifest = await loadHarnessManifest(repoRoot);
268
+ const strategy = resolveDagVerifyStrategy(taskConfig);
269
+ let verifyCommands;
270
+ try {
271
+ const adapter = await resolveAdapter(repoRoot);
272
+ const preset = resolveVerifyPreset(taskConfig.verifyPreset, taskConfig);
273
+ verifyCommands = {
274
+ intermediate: adapter.getVerifyCommands(repoRoot, {
275
+ preset,
276
+ quota: strategy.intermediateQuota,
277
+ phase: "intermediate",
278
+ taskConfig,
279
+ }),
280
+ final: adapter.getVerifyCommands(repoRoot, {
281
+ preset,
282
+ quota: "full",
283
+ phase: "final",
284
+ taskConfig,
285
+ }),
286
+ };
287
+ if (verifyCommands.final.length === 0) {
288
+ throw new Error("adapter returned no final verification commands");
289
+ }
290
+ }
291
+ catch (error) {
292
+ throw new Error(`failed to load verification commands for task "${taskId}": ${error instanceof Error ? error.message : String(error)}`);
293
+ }
294
+ return {
295
+ taskId,
296
+ repoRoot,
297
+ taskDir: paths.taskDir,
298
+ requirementPath,
299
+ constraintPath,
300
+ requirementMarkdown,
301
+ constraintMarkdown,
302
+ taskConfig,
303
+ enabledExecutors: resolveEnabledExecutors(manifest.executors),
304
+ verifyCommands,
305
+ };
306
+ }
307
+ export function buildStandardHybridDagFromTask(sources) {
308
+ const { taskConfig } = sources;
309
+ const forbiddenPaths = mergeForbiddenPaths(taskConfig);
310
+ const implementPaths = resolveImplementPaths(taskConfig);
311
+ const scoutPaths = deriveParallelScoutPaths(taskConfig);
312
+ const scoutComplexity = mapTaskComplexity(taskConfig.complexity);
313
+ const implementComplexity = taskConfig.complexity === "small"
314
+ ? "MED"
315
+ : taskConfig.complexity === "large"
316
+ ? "HIGH"
317
+ : "MED";
318
+ const writerExecutor = resolveImplementationExecutor(sources);
319
+ const implementId = implementationNodeId(writerExecutor);
320
+ const sourceContext = buildSourceContextBlock(sources);
321
+ const globalConstraints = [
322
+ ...taskConfig.hardConstraints,
323
+ ...(sources.constraintMarkdown
324
+ ? [`See 执行约束.md in task source (${sources.taskId})`]
325
+ : []),
326
+ ...STANDARD_GLOBAL_CONSTRAINTS,
327
+ ];
328
+ const spec = {
329
+ version: 2,
330
+ title: `Hybrid DAG: ${taskConfig.title}`,
331
+ objective: extractObjective(sources.requirementMarkdown, taskConfig.title),
332
+ successCriteria: extractSuccessCriteria(sources.requirementMarkdown, sources.taskId),
333
+ globalConstraints,
334
+ defaults: {
335
+ ...HYBRID_DEFAULTS,
336
+ contextProfile: taskConfig.contextProfile,
337
+ },
338
+ skillsByRole: HYBRID_SKILLS_BY_ROLE,
339
+ executorModels: {
340
+ cursor: {
341
+ LOW: "composer-2.5",
342
+ MED: "composer-2.5",
343
+ HIGH: "composer-2.5",
344
+ },
345
+ pi: {
346
+ LOW: "gpt-5.3-codex-spark",
347
+ MED: "glm-5.2",
348
+ HIGH: "gpt-5.5",
349
+ },
350
+ },
351
+ tasks: [
352
+ {
353
+ id: "contract-pi",
354
+ depends_on: [],
355
+ role: "planner",
356
+ executor: "pi",
357
+ complexity: "MED",
358
+ writePolicy: "read-only",
359
+ allowedPaths: taskConfig.allowedPaths.length > 0 ? taskConfig.allowedPaths : ["**"],
360
+ forbiddenPaths,
361
+ outputContract: "Plain Markdown implementation contract (10 lines or fewer); no file writes.",
362
+ subtask_prompt: [
363
+ "Read the task source materials and return a concise implementation contract (10 lines or fewer).",
364
+ "Cover scope, risks, and verification expectations. Read-only: do not modify code, docs, artifacts, or repository files.",
365
+ sourceContext,
366
+ ].join("\n\n"),
367
+ },
368
+ {
369
+ id: "scout-src",
370
+ depends_on: ["contract-pi"],
371
+ role: "scout",
372
+ executor: "pi",
373
+ complexity: scoutComplexity,
374
+ writePolicy: "read-only",
375
+ allowedPaths: scoutPaths.srcPaths,
376
+ forbiddenPaths,
377
+ outputContract: "Plain Markdown source reconnaissance summary; no file writes.",
378
+ subtask_prompt: [
379
+ "Perform read-only source reconnaissance for the task scope.",
380
+ "List key source files, existing patterns, and integration points. Do not modify files.",
381
+ sourceContext,
382
+ ].join("\n\n"),
383
+ },
384
+ {
385
+ id: "scout-tests",
386
+ depends_on: ["contract-pi"],
387
+ role: "scout",
388
+ executor: "pi",
389
+ complexity: scoutComplexity,
390
+ writePolicy: "read-only",
391
+ allowedPaths: scoutPaths.testPaths,
392
+ forbiddenPaths,
393
+ outputContract: "Plain Markdown test coverage reconnaissance summary; no file writes.",
394
+ subtask_prompt: [
395
+ "Perform read-only test reconnaissance for the task scope.",
396
+ "List relevant tests, coverage gaps, and verification commands. Do not modify files.",
397
+ sourceContext,
398
+ ].join("\n\n"),
399
+ },
400
+ {
401
+ id: "plan-pi",
402
+ depends_on: ["scout-src", "scout-tests"],
403
+ role: "planner",
404
+ executor: "pi",
405
+ complexity: "MED",
406
+ writePolicy: "read-only",
407
+ allowedPaths: taskConfig.allowedPaths.length > 0 ? taskConfig.allowedPaths : ["**"],
408
+ forbiddenPaths,
409
+ outputContract: "Plain Markdown implementation plan; no file writes.",
410
+ subtask_prompt: [
411
+ "Based on contract-pi, scout-src, and scout-tests upstream output, return a minimal implementation plan.",
412
+ "Identify ordered steps, narrow writeSet boundaries, and verification commands. Read-only: do not modify code, docs, artifacts, or repository files.",
413
+ sourceContext,
414
+ ].join("\n\n"),
415
+ },
416
+ {
417
+ id: implementId,
418
+ depends_on: ["plan-pi"],
419
+ role: "implementer",
420
+ executor: writerExecutor,
421
+ toolProfile: "write",
422
+ complexity: implementComplexity,
423
+ writePolicy: "exclusive",
424
+ writeSet: implementPaths.writeSet,
425
+ allowedPaths: implementPaths.allowedPaths,
426
+ forbiddenPaths,
427
+ subtask_prompt: [
428
+ "Implement the approved plan with minimal focused changes.",
429
+ "Stay within writeSet. Do not write root artifacts/** unless artifacts paths are explicitly declared in writeSet.",
430
+ sourceContext,
431
+ ].join("\n\n"),
432
+ },
433
+ {
434
+ id: "verify-pi",
435
+ depends_on: [implementId],
436
+ role: "verifier",
437
+ executor: "pi",
438
+ complexity: "MED",
439
+ writePolicy: "read-only",
440
+ allowedPaths: taskConfig.allowedPaths.length > 0 ? taskConfig.allowedPaths : ["**"],
441
+ forbiddenPaths,
442
+ outputContract: "Plain Markdown verification matrix and residual risks; no file writes.",
443
+ subtask_prompt: [
444
+ "Review upstream implementation and return the verification matrix.",
445
+ `Task verifyMode=${taskConfig.verifyMode}, verifyPreset=${taskConfig.verifyPreset}.`,
446
+ "List commands to run, expected pass/fail evidence, and residual risks. Read-only: do not modify code, docs, artifacts, or repository files.",
447
+ sourceContext,
448
+ ].join("\n\n"),
449
+ },
450
+ {
451
+ id: "closeout-pi",
452
+ depends_on: ["verify-pi"],
453
+ role: "closeout",
454
+ executor: "pi",
455
+ complexity: "MED",
456
+ writePolicy: "read-only",
457
+ allowedPaths: taskConfig.allowedPaths.length > 0
458
+ ? [...taskConfig.allowedPaths, "docs/**"]
459
+ : ["**", "docs/**"],
460
+ forbiddenPaths,
461
+ outputContract: "Plain Markdown closeout summary; no file writes.",
462
+ subtask_prompt: [
463
+ "Return closeout summary: what changed, verification status, risks, and handoff notes.",
464
+ "Read-only: do not modify code, docs, artifacts, or .harness/dag-runs/.",
465
+ sourceContext,
466
+ ].join("\n\n"),
467
+ },
468
+ ],
469
+ };
470
+ parseDagSpec(spec);
471
+ assertValidDagSpec(spec);
472
+ return spec;
473
+ }
474
+ export function buildHybridDagFromTask(sources, options = {}) {
475
+ const standard = buildStandardHybridDagFromTask(sources);
476
+ const template = options.template ?? "standard-dag";
477
+ if (template === "standard-dag")
478
+ return standard;
479
+ if (template === "review-gated-dag")
480
+ return buildReviewGatedHybridDag(standard, sources);
481
+ return buildSupervisedHybridDag(standard, sources);
482
+ }
483
+ function cloneTask(task, patch = {}) {
484
+ return { ...task, ...patch };
485
+ }
486
+ function getTaskOrThrow(spec, id) {
487
+ const task = spec.tasks.find((item) => item.id === id);
488
+ if (!task)
489
+ throw new Error(`generated DAG missing expected node: ${id}`);
490
+ return task;
491
+ }
492
+ function replaceTask(spec, task) {
493
+ const index = spec.tasks.findIndex((item) => item.id === task.id);
494
+ if (index < 0)
495
+ throw new Error(`generated DAG missing expected node: ${task.id}`);
496
+ spec.tasks[index] = task;
497
+ }
498
+ function commonReadOnlyPaths(sources) {
499
+ return sources.taskConfig.allowedPaths.length > 0
500
+ ? sources.taskConfig.allowedPaths
501
+ : ["**"];
502
+ }
503
+ function commonForbiddenPaths(sources) {
504
+ return mergeForbiddenPaths(sources.taskConfig);
505
+ }
506
+ function buildReviewNode(sources) {
507
+ return {
508
+ id: "review-pi",
509
+ depends_on: ["verify-pi"],
510
+ role: "reviewer",
511
+ executor: "pi",
512
+ complexity: "HIGH",
513
+ writePolicy: "read-only",
514
+ allowedPaths: commonReadOnlyPaths(sources),
515
+ forbiddenPaths: commonForbiddenPaths(sources),
516
+ outputContract: "Plain Markdown whose first non-empty line is VERDICT: pass or VERDICT: request-revision; Critical/Important findings force request-revision. No file writes.",
517
+ subtask_prompt: [
518
+ "Review upstream implementation and verification evidence.",
519
+ "First non-empty line must be exactly VERDICT: pass or VERDICT: request-revision.",
520
+ "List Critical/Important findings when present; any Critical/Important finding must force request-revision. Read-only: do not modify files.",
521
+ buildSourceContextBlock(sources),
522
+ ].join("\n\n"),
523
+ };
524
+ }
525
+ function buildReviewGateNode(sources) {
526
+ return {
527
+ id: "review-gate-shell",
528
+ depends_on: ["review-pi"],
529
+ role: "verifier",
530
+ executor: "shell",
531
+ complexity: "LOW",
532
+ writePolicy: "read-only",
533
+ allowedPaths: commonReadOnlyPaths(sources),
534
+ forbiddenPaths: commonForbiddenPaths(sources),
535
+ outputContract: "Deterministic review verdict gate: exit 0 only when review-pi first non-empty assistant output line is pass.",
536
+ subtask_prompt: "Deterministic gate: block downstream closeout unless review-pi emitted VERDICT: pass.",
537
+ shell: {
538
+ commands: [],
539
+ verdictGate: {
540
+ fromNodeId: "review-pi",
541
+ accept: ["VERDICT: pass"],
542
+ label: "review",
543
+ lineMode: "first-verdict-line",
544
+ },
545
+ cwd: ".",
546
+ timeoutMs: 60000,
547
+ },
548
+ };
549
+ }
550
+ function buildReviewGatedHybridDag(standard, sources) {
551
+ const spec = {
552
+ ...standard,
553
+ title: `Review-gated ${standard.title}`,
554
+ objective: `${standard.objective ?? ""}\n\nRoute: review-gated DAG selected by workflowPolicy/governanceProfile.`.trim(),
555
+ globalConstraints: [
556
+ ...(standard.globalConstraints ?? []),
557
+ "Review-gated DAGs must block closeout unless review-pi emits first-line VERDICT: pass.",
558
+ ],
559
+ tasks: standard.tasks.map((task) => ({ ...task })),
560
+ };
561
+ const closeout = getTaskOrThrow(spec, "closeout-pi");
562
+ replaceTask(spec, cloneTask(closeout, { depends_on: ["review-gate-shell"] }));
563
+ spec.tasks.splice(spec.tasks.length - 1, 0, buildReviewNode(sources), buildReviewGateNode(sources));
564
+ parseDagSpec(spec);
565
+ assertValidDagSpec(spec);
566
+ return spec;
567
+ }
568
+ function buildWriteSetAuditNode(sources) {
569
+ return {
570
+ id: "write-set-audit-pi",
571
+ depends_on: ["plan-pi"],
572
+ role: "reviewer",
573
+ executor: "pi",
574
+ complexity: "MED",
575
+ writePolicy: "read-only",
576
+ allowedPaths: commonReadOnlyPaths(sources),
577
+ forbiddenPaths: commonForbiddenPaths(sources),
578
+ outputContract: "Plain Markdown whose first non-empty line is VERDICT: pass or VERDICT: request-revision; includes writeSet coverage findings. No file writes.",
579
+ subtask_prompt: [
580
+ "Audit plan-pi writeSet coverage against task constraints.",
581
+ "First non-empty line must be exactly VERDICT: pass or VERDICT: request-revision.",
582
+ "Request revision if required files lack a single exclusive owner, writeSet is broad/placeholder, or forbidden paths overlap. Do not edit files.",
583
+ buildSourceContextBlock(sources),
584
+ ].join("\n\n"),
585
+ };
586
+ }
587
+ function buildWriteSetGateNode(sources) {
588
+ return {
589
+ id: "write-set-gate-shell",
590
+ depends_on: ["write-set-audit-pi"],
591
+ role: "verifier",
592
+ executor: "shell",
593
+ complexity: "LOW",
594
+ writePolicy: "read-only",
595
+ allowedPaths: commonReadOnlyPaths(sources),
596
+ forbiddenPaths: commonForbiddenPaths(sources),
597
+ outputContract: "Deterministic write-set audit verdict gate: exit 0 only when write-set-audit-pi first non-empty assistant output line is pass.",
598
+ subtask_prompt: "Deterministic gate: block the implementation writer unless write-set-audit-pi emitted VERDICT: pass.",
599
+ shell: {
600
+ commands: [],
601
+ verdictGate: {
602
+ fromNodeId: "write-set-audit-pi",
603
+ accept: ["VERDICT: pass"],
604
+ label: "write-set audit",
605
+ lineMode: "first-verdict-line",
606
+ },
607
+ cwd: ".",
608
+ timeoutMs: 60000,
609
+ },
610
+ };
611
+ }
612
+ function buildSoftVerifyNode(sources) {
613
+ const implementId = implementationNodeId(resolveImplementationExecutor(sources));
614
+ const strategy = resolveDagVerifyStrategy(sources.taskConfig);
615
+ const fallbackCommands = ["npm run typecheck"];
616
+ const commands = buildVerifyShellCommands({
617
+ repoRoot: sources.repoRoot,
618
+ commands: sources.verifyCommands?.intermediate,
619
+ fallbackCommands,
620
+ });
621
+ return {
622
+ id: "soft-verify-shell",
623
+ depends_on: [implementId],
624
+ role: "verifier",
625
+ executor: "shell",
626
+ complexity: "LOW",
627
+ writePolicy: "read-only",
628
+ allowedPaths: commonReadOnlyPaths(sources),
629
+ forbiddenPaths: commonForbiddenPaths(sources),
630
+ outputContract: "Archived shell stdout/stderr with exit codes for focused verification; no worktree writes.",
631
+ subtask_prompt: "Run focused soft verification before process supervision.",
632
+ shell: {
633
+ commands,
634
+ verifyEvidence: buildVerifyEvidence({
635
+ phase: "intermediate",
636
+ quota: strategy.intermediateQuota ?? "full",
637
+ commandSource: sources.verifyCommands ? "adapter" : "inline",
638
+ commands: sources.verifyCommands?.intermediate,
639
+ fallbackCommands,
640
+ }),
641
+ cwd: ".",
642
+ timeoutMs: 300000,
643
+ },
644
+ };
645
+ }
646
+ function buildProcessSupervisorNode(sources) {
647
+ const implementId = implementationNodeId(resolveImplementationExecutor(sources));
648
+ const repairId = repairNodeId(resolveImplementationExecutor(sources));
649
+ return {
650
+ id: "process-supervisor-pi",
651
+ depends_on: ["soft-verify-shell", implementId],
652
+ role: "supervisor",
653
+ executor: "pi",
654
+ complexity: "HIGH",
655
+ writePolicy: "read-only",
656
+ allowedPaths: commonReadOnlyPaths(sources),
657
+ forbiddenPaths: commonForbiddenPaths(sources),
658
+ outputContract: "Plain Markdown whose first non-empty line is VERDICT: pass or VERDICT: request-revision, followed by a REPAIR_ARTIFACT_JSON fenced block. Audit writeSet coverage, boundary drift, verification gaps, repair scope. No file writes.",
659
+ subtask_prompt: [
660
+ "Supervise the implementation process after soft verification.",
661
+ "First non-empty line must be exactly VERDICT: pass or VERDICT: request-revision.",
662
+ "Immediately after the verdict, include one fenced block labelled REPAIR_ARTIFACT_JSON with this JSON shape:",
663
+ '{"schemaVersion":1,"verdict":"pass|request-revision","failureClass":"syntax|runtime|logic|boundary|environment|governance|unknown","rootCause":"one concise reason","fixScope":["path/or/component"],"invariant":"behavior or contract to preserve","evidenceRefs":["relative/path/or/node"],"rawLogFallbackAllowed":false}',
664
+ `For request-revision, fixScope must be inside ${repairId} allowedPaths/writeSet. For pass, use an empty fixScope array.`,
665
+ `Audit boundary drift, verification gaps, and whether ${repairId} should perform bounded fixes. Read-only: do not modify files.`,
666
+ buildSourceContextBlock(sources),
667
+ ].join("\n\n"),
668
+ };
669
+ }
670
+ function buildProcessGateNode(sources) {
671
+ return {
672
+ id: "process-gate-shell",
673
+ depends_on: ["process-supervisor-pi"],
674
+ role: "verifier",
675
+ executor: "shell",
676
+ complexity: "LOW",
677
+ writePolicy: "read-only",
678
+ allowedPaths: commonReadOnlyPaths(sources),
679
+ forbiddenPaths: commonForbiddenPaths(sources),
680
+ outputContract: "Deterministic process supervisor verdict gate: exit 0 when first non-empty assistant output line is pass or request-revision; exit non-zero otherwise.",
681
+ subtask_prompt: "Deterministic gate: validate process-supervisor-pi first-line VERDICT before repair/hard-verify.",
682
+ shell: {
683
+ commands: [],
684
+ verdictGate: {
685
+ fromNodeId: "process-supervisor-pi",
686
+ accept: ["VERDICT: pass", "VERDICT: request-revision"],
687
+ label: "process",
688
+ lineMode: "first-verdict-line",
689
+ },
690
+ repairArtifactGate: {
691
+ fromNodeId: "process-supervisor-pi",
692
+ },
693
+ cwd: ".",
694
+ timeoutMs: 60000,
695
+ },
696
+ };
697
+ }
698
+ function buildRepairNode(sources) {
699
+ const writerExecutor = resolveImplementationExecutor(sources);
700
+ const implement = resolveImplementPaths(sources.taskConfig);
701
+ return {
702
+ id: repairNodeId(writerExecutor),
703
+ depends_on: ["process-gate-shell", "process-supervisor-pi"],
704
+ role: "implementer",
705
+ executor: writerExecutor,
706
+ toolProfile: "write",
707
+ complexity: sources.taskConfig.complexity === "small" ? "MED" : "HIGH",
708
+ writePolicy: "exclusive",
709
+ writeSet: implement.writeSet,
710
+ allowedPaths: implement.allowedPaths,
711
+ forbiddenPaths: commonForbiddenPaths(sources),
712
+ outputContract: "Repair summary or explicit no-op when process supervisor passed; changed files and tests when revised. Must not ask main session to write artifacts.",
713
+ subtask_prompt: [
714
+ "If process-supervisor-pi returned VERDICT: request-revision, apply bounded fixes within writeSet addressing its REPAIR_ARTIFACT_JSON.fixScope and preserving REPAIR_ARTIFACT_JSON.invariant.",
715
+ "Use REPAIR_ARTIFACT_JSON.failureClass and rootCause to choose the smallest repair strategy. Raw log is fallback evidence only when rawLogFallbackAllowed is true.",
716
+ "If VERDICT: pass, return no-op with evidence. Re-run focused tests when you change code.",
717
+ buildSourceContextBlock(sources),
718
+ ].join("\n\n"),
719
+ };
720
+ }
721
+ function buildHardVerifyNode(sources) {
722
+ const repairId = repairNodeId(resolveImplementationExecutor(sources));
723
+ const fallbackCommands = [
724
+ "HARNESS_ALLOW_ACTIVE_DAG_RUNS=1 bash scripts/check-repo.sh",
725
+ ];
726
+ const commands = buildVerifyShellCommands({
727
+ repoRoot: sources.repoRoot,
728
+ commands: sources.verifyCommands?.final,
729
+ fallbackCommands,
730
+ });
731
+ return {
732
+ id: "hard-verify-shell",
733
+ depends_on: [repairId],
734
+ role: "verifier",
735
+ executor: "shell",
736
+ complexity: "LOW",
737
+ writePolicy: "read-only",
738
+ allowedPaths: ["./**", "docs/**", "scripts/**"],
739
+ forbiddenPaths: commonForbiddenPaths(sources),
740
+ outputContract: "Archived hard verification stdout/stderr with exit codes; no worktree writes.",
741
+ subtask_prompt: "Run hard verification after repair round.",
742
+ shell: {
743
+ commands,
744
+ verifyEvidence: buildVerifyEvidence({
745
+ phase: "final",
746
+ quota: "full",
747
+ commandSource: sources.verifyCommands ? "adapter" : "inline",
748
+ commands: sources.verifyCommands?.final,
749
+ fallbackCommands,
750
+ finalFullRequired: true,
751
+ }),
752
+ cwd: ".",
753
+ timeoutMs: 300000,
754
+ },
755
+ };
756
+ }
757
+ function buildDecisionNode(sources) {
758
+ return {
759
+ id: "decision-pi",
760
+ depends_on: ["review-gate-shell", "hard-verify-shell"],
761
+ role: "reviewer",
762
+ executor: "pi",
763
+ complexity: "HIGH",
764
+ writePolicy: "read-only",
765
+ allowedPaths: commonReadOnlyPaths(sources),
766
+ forbiddenPaths: commonForbiddenPaths(sources),
767
+ outputContract: "Markdown with exactly one DECISION_ENVELOPE_JSON fenced block plus evidence summary. No file writes.",
768
+ subtask_prompt: [
769
+ "Return an advisory Decision Gate envelope for the supervised DAG outcome.",
770
+ "Include exactly one DECISION_ENVELOPE_JSON fenced block and concise evidence. Read-only: do not modify files.",
771
+ buildSourceContextBlock(sources),
772
+ ].join("\n\n"),
773
+ decisionGate: { enabled: true, schemaVersion: 1, mode: "record-only" },
774
+ };
775
+ }
776
+ function buildSupervisedHybridDag(standard, sources) {
777
+ const contract = getTaskOrThrow(standard, "contract-pi");
778
+ const scoutSrc = getTaskOrThrow(standard, "scout-src");
779
+ const scoutTests = getTaskOrThrow(standard, "scout-tests");
780
+ const plan = getTaskOrThrow(standard, "plan-pi");
781
+ const implement = getTaskOrThrow(standard, implementationNodeId(resolveImplementationExecutor(sources)));
782
+ const closeout = getTaskOrThrow(standard, "closeout-pi");
783
+ const authorityDecision = resolveAuthoritySurfaceAudit(sources);
784
+ const reviewDependsOn = authorityDecision.enabled
785
+ ? ["authority-surface-gate-shell"]
786
+ : ["hard-verify-shell"];
787
+ const supervisedConstraints = [
788
+ ...(standard.globalConstraints ?? []),
789
+ "Supervised DAGs must use write-set, process, hard-verification, review, and decision gates before closeout.",
790
+ "Shell governance inside active DAG must use HARNESS_ALLOW_ACTIVE_DAG_RUNS=1 bash scripts/check-repo.sh.",
791
+ ];
792
+ if (authorityDecision.enabled) {
793
+ supervisedConstraints.push("Authority surface audit gate must fail closed without explicit VERDICT: pass from authority-surface-audit-pi.");
794
+ }
795
+ const spec = {
796
+ ...standard,
797
+ title: `Supervised ${standard.title}`,
798
+ objective: `${standard.objective ?? ""}\n\nRoute: supervised implementation DAG selected by workflowPolicy/governanceProfile or explicit CLI profile.`.trim(),
799
+ globalConstraints: supervisedConstraints,
800
+ convergence: sources.taskConfig.convergence,
801
+ verifyStrategy: resolveDagVerifyStrategy(sources.taskConfig),
802
+ tasks: [
803
+ cloneTask(contract),
804
+ cloneTask(scoutSrc),
805
+ cloneTask(scoutTests),
806
+ cloneTask(plan),
807
+ buildWriteSetAuditNode(sources),
808
+ buildWriteSetGateNode(sources),
809
+ cloneTask(implement, { depends_on: ["write-set-gate-shell"] }),
810
+ buildSoftVerifyNode(sources),
811
+ buildProcessSupervisorNode(sources),
812
+ buildProcessGateNode(sources),
813
+ buildRepairNode(sources),
814
+ buildHardVerifyNode(sources),
815
+ ...(authorityDecision.enabled
816
+ ? [
817
+ buildAuthoritySurfaceAuditNode(sources),
818
+ buildAuthoritySurfaceGateNode(sources),
819
+ ]
820
+ : []),
821
+ cloneTask(buildReviewNode(sources), {
822
+ depends_on: reviewDependsOn,
823
+ }),
824
+ buildReviewGateNode(sources),
825
+ buildDecisionNode(sources),
826
+ cloneTask(closeout, { depends_on: ["decision-pi"] }),
827
+ ],
828
+ };
829
+ parseDagSpec(spec);
830
+ assertValidDagSpec(spec);
831
+ return spec;
832
+ }
833
+ export function defaultHybridDagOutputPath(taskId) {
834
+ return path.join(os.tmpdir(), `${taskId}-hybrid-dag.json`);
835
+ }
836
+ export async function writeHybridDagDraft(sources, outputPath, options = {}) {
837
+ const template = options.template ?? "standard-dag";
838
+ const spec = buildHybridDagFromTask(sources, { template });
839
+ await writeFile(outputPath, `${JSON.stringify(spec, null, 2)}\n`, "utf-8");
840
+ return {
841
+ taskId: sources.taskId,
842
+ outputPath,
843
+ taskCount: spec.tasks.length,
844
+ nodeIds: spec.tasks.map((task) => task.id),
845
+ template,
846
+ };
847
+ }
848
+ export async function initHybridDagFromTask(repoRoot, taskId, options = {}) {
849
+ const sources = await loadTaskHybridSources(repoRoot, taskId);
850
+ assertTaskAllowedPathsPreflight(sources.taskConfig);
851
+ const outputPath = options.outputPath ?? defaultHybridDagOutputPath(taskId);
852
+ return writeHybridDagDraft(sources, outputPath, {
853
+ template: options.template,
854
+ });
855
+ }