@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,177 @@
1
+ import { mkdir, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { buildDagCloseoutDraft, buildDagReport, DAG_PAUSED_LATEST_REPORT_PRESET, DAG_RECOVERY_ACTIONS, formatDagReportHandoffMarkdown, formatDagReportMarkdown, serializeDagReportJson, } from "../workflows/dag/report.js";
4
+ const RECOVERY_ACTIONS = new Set(DAG_RECOVERY_ACTIONS);
5
+ const LIFECYCLE_FILTERS = new Set([
6
+ "active",
7
+ "paused",
8
+ "completed",
9
+ "all",
10
+ ]);
11
+ export function parseDagReportArgs(args) {
12
+ let runId;
13
+ let lifecycle = "all";
14
+ let json = false;
15
+ let markdown = false;
16
+ let failedOnly = false;
17
+ let latest = false;
18
+ let pausedLatest = false;
19
+ let action;
20
+ for (let i = 0; i < args.length; i += 1) {
21
+ const arg = args[i];
22
+ if (arg === "--run-id") {
23
+ runId = args[++i];
24
+ continue;
25
+ }
26
+ if (arg.startsWith("--run-id=")) {
27
+ runId = arg.slice("--run-id=".length);
28
+ continue;
29
+ }
30
+ if (arg === "--lifecycle") {
31
+ const value = args[++i];
32
+ if (!LIFECYCLE_FILTERS.has(value)) {
33
+ throw new Error(`invalid --lifecycle value: ${value} (expected active|paused|completed|all)`);
34
+ }
35
+ lifecycle = value;
36
+ continue;
37
+ }
38
+ if (arg.startsWith("--lifecycle=")) {
39
+ const value = arg.slice("--lifecycle=".length);
40
+ if (!LIFECYCLE_FILTERS.has(value)) {
41
+ throw new Error(`invalid --lifecycle value: ${value} (expected active|paused|completed|all)`);
42
+ }
43
+ lifecycle = value;
44
+ continue;
45
+ }
46
+ if (arg === "--json") {
47
+ json = true;
48
+ continue;
49
+ }
50
+ if (arg === "--markdown") {
51
+ markdown = true;
52
+ continue;
53
+ }
54
+ if (arg === "--failed-only") {
55
+ failedOnly = true;
56
+ continue;
57
+ }
58
+ if (arg === "--latest") {
59
+ latest = true;
60
+ continue;
61
+ }
62
+ if (arg === "--paused-latest") {
63
+ pausedLatest = true;
64
+ continue;
65
+ }
66
+ if (arg === "--action") {
67
+ const value = args[++i];
68
+ if (!RECOVERY_ACTIONS.has(value)) {
69
+ throw new Error(`invalid --action value: ${value} (expected ${[...DAG_RECOVERY_ACTIONS].join("|")})`);
70
+ }
71
+ action = value;
72
+ continue;
73
+ }
74
+ if (arg.startsWith("--action=")) {
75
+ const value = arg.slice("--action=".length);
76
+ if (!RECOVERY_ACTIONS.has(value)) {
77
+ throw new Error(`invalid --action value: ${value} (expected ${[...DAG_RECOVERY_ACTIONS].join("|")})`);
78
+ }
79
+ action = value;
80
+ continue;
81
+ }
82
+ if (arg.startsWith("-")) {
83
+ throw new Error(`unknown dag report flag: ${arg}`);
84
+ }
85
+ throw new Error(`unexpected positional argument: ${arg}`);
86
+ }
87
+ if (json && markdown) {
88
+ throw new Error("cannot use --json and --markdown together");
89
+ }
90
+ if (pausedLatest) {
91
+ if (lifecycle !== "all") {
92
+ throw new Error("cannot combine --paused-latest with an explicit --lifecycle filter");
93
+ }
94
+ lifecycle = DAG_PAUSED_LATEST_REPORT_PRESET.lifecycle;
95
+ latest = DAG_PAUSED_LATEST_REPORT_PRESET.latest;
96
+ }
97
+ return {
98
+ runId,
99
+ lifecycle,
100
+ json,
101
+ markdown,
102
+ failedOnly,
103
+ latest,
104
+ pausedLatest,
105
+ action,
106
+ };
107
+ }
108
+ export async function runDagReport(repoRoot, rawArgs) {
109
+ const parsed = parseDagReportArgs(rawArgs);
110
+ const report = await buildDagReport({
111
+ repoRoot,
112
+ runId: parsed.runId,
113
+ lifecycle: parsed.lifecycle,
114
+ failedOnly: parsed.failedOnly,
115
+ latest: parsed.latest,
116
+ action: parsed.action,
117
+ });
118
+ if (parsed.json) {
119
+ console.log(JSON.stringify(serializeDagReportJson(report), null, 2));
120
+ return;
121
+ }
122
+ if (parsed.markdown) {
123
+ console.log(formatDagReportHandoffMarkdown(report));
124
+ return;
125
+ }
126
+ console.log(formatDagReportMarkdown(report));
127
+ }
128
+ export function parseDagCloseoutDraftArgs(args) {
129
+ let runId;
130
+ let outputPath;
131
+ for (let i = 0; i < args.length; i += 1) {
132
+ const arg = args[i];
133
+ const requireValue = (flag) => {
134
+ const value = args[++i];
135
+ if (!value || value.startsWith("-")) {
136
+ throw new Error(`missing value for ${flag}`);
137
+ }
138
+ return value;
139
+ };
140
+ if (arg === "--run-id") {
141
+ runId = requireValue("--run-id");
142
+ continue;
143
+ }
144
+ if (arg.startsWith("--run-id=")) {
145
+ runId = arg.slice("--run-id=".length);
146
+ if (!runId)
147
+ throw new Error("missing value for --run-id");
148
+ continue;
149
+ }
150
+ if (arg === "--output") {
151
+ outputPath = requireValue("--output");
152
+ continue;
153
+ }
154
+ if (arg.startsWith("--output=")) {
155
+ outputPath = arg.slice("--output=".length);
156
+ if (!outputPath)
157
+ throw new Error("missing value for --output");
158
+ continue;
159
+ }
160
+ if (arg.startsWith("-")) {
161
+ throw new Error(`unknown dag closeout-draft flag: ${arg}`);
162
+ }
163
+ throw new Error(`unexpected positional argument: ${arg}`);
164
+ }
165
+ return { runId, outputPath };
166
+ }
167
+ export async function runDagCloseoutDraft(repoRoot, rawArgs) {
168
+ const parsed = parseDagCloseoutDraftArgs(rawArgs);
169
+ const draft = await buildDagCloseoutDraft({
170
+ repoRoot,
171
+ runId: parsed.runId,
172
+ outputPath: parsed.outputPath,
173
+ });
174
+ await mkdir(path.dirname(draft.outputPath), { recursive: true });
175
+ await writeFile(draft.outputPath, draft.markdown, "utf-8");
176
+ console.log(`Wrote closeout draft to ${draft.outputPath}`);
177
+ }
@@ -0,0 +1,34 @@
1
+ import { resumeDagRun } from "../workflows/dag/runner.js";
2
+ export function parseDagResumeArgs(args) {
3
+ if (args.length === 0) {
4
+ throw new Error("usage: dag resume --run-id <id>");
5
+ }
6
+ let runId;
7
+ for (let i = 0; i < args.length; i += 1) {
8
+ const arg = args[i];
9
+ if (arg === "--run-id") {
10
+ runId = args[++i];
11
+ continue;
12
+ }
13
+ if (arg.startsWith("--run-id=")) {
14
+ runId = arg.slice("--run-id=".length);
15
+ continue;
16
+ }
17
+ if (arg.startsWith("-")) {
18
+ throw new Error(`unknown dag resume flag: ${arg}`);
19
+ }
20
+ throw new Error(`unexpected positional argument: ${arg}`);
21
+ }
22
+ if (!runId) {
23
+ throw new Error("dag resume requires --run-id <id>");
24
+ }
25
+ return { runId };
26
+ }
27
+ export async function runDagResume(repoRoot, rawArgs) {
28
+ const parsed = parseDagResumeArgs(rawArgs);
29
+ const summary = await resumeDagRun({
30
+ cwd: repoRoot,
31
+ runId: parsed.runId,
32
+ });
33
+ console.log(JSON.stringify(summary, null, 2));
34
+ }
@@ -0,0 +1,470 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { resolveAutoRoutingProfile, requiresSupervisedQualityGate, } from "../workflows/dag/governance-profile.js";
4
+ import { resolveShellCommands } from "../executors/shell-executor.js";
5
+ import { parseDagSpec } from "../workflows/dag/types.js";
6
+ import { pathMatchesPattern } from "../shared/git-progress.js";
7
+ import { loadHarnessManifest } from "../governance/harness.js";
8
+ import { defaultHybridDagOutputPath, initHybridDagFromTask, } from "../workflows/dag/init-hybrid.js";
9
+ import { runDagValidate } from "./dag-validate.js";
10
+ import { runRunDag } from "./run-dag.js";
11
+ const PLACEHOLDER_WRITESET_MARKER = "REPLACE/WITH";
12
+ export function parseDagRunTaskArgs(args, defaultCwd) {
13
+ if (args.length === 0) {
14
+ throw new Error("usage: dag run-task <task-id> [--output <path>] [--profile auto|minimal|standard|reviewed|supervised] [--strict-models] [--no-cursor] [--execute] [--init-only] [--dry-run] [--cwd <dir>] [--max-concurrent N] [--run-id id] [--canvas-path <abs-path> | --canvas <name> [--canvases-dir <dir>]]");
15
+ }
16
+ let taskId;
17
+ let outputPath;
18
+ let strictModels = false;
19
+ let noCursor = false;
20
+ let execute = false;
21
+ let initOnly = false;
22
+ let dryRun = false;
23
+ let cwd;
24
+ let maxConcurrent;
25
+ let runId;
26
+ let canvasPath;
27
+ let canvasName;
28
+ let canvasesDir;
29
+ let profile = "standard";
30
+ let profileExplicit = false;
31
+ for (let i = 0; i < args.length; i += 1) {
32
+ const arg = args[i];
33
+ if (arg === "--output" || arg === "-o") {
34
+ outputPath = args[++i];
35
+ if (!outputPath) {
36
+ throw new Error("dag run-task --output requires a path");
37
+ }
38
+ continue;
39
+ }
40
+ if (arg.startsWith("--output=")) {
41
+ outputPath = arg.slice("--output=".length);
42
+ continue;
43
+ }
44
+ if (arg === "--strict-models") {
45
+ strictModels = true;
46
+ continue;
47
+ }
48
+ if (arg === "--no-cursor") {
49
+ noCursor = true;
50
+ continue;
51
+ }
52
+ if (arg === "--execute") {
53
+ execute = true;
54
+ continue;
55
+ }
56
+ if (arg === "--init-only") {
57
+ initOnly = true;
58
+ continue;
59
+ }
60
+ if (arg === "--dry-run") {
61
+ dryRun = true;
62
+ continue;
63
+ }
64
+ if (arg === "--cwd" || arg === "-C") {
65
+ cwd = args[++i];
66
+ continue;
67
+ }
68
+ if (arg.startsWith("--cwd=")) {
69
+ cwd = arg.slice(6);
70
+ continue;
71
+ }
72
+ if (arg === "--max-concurrent") {
73
+ maxConcurrent = Number(args[++i]);
74
+ continue;
75
+ }
76
+ if (arg === "--run-id") {
77
+ runId = args[++i];
78
+ continue;
79
+ }
80
+ if (arg === "--canvas-path") {
81
+ canvasPath = args[++i];
82
+ continue;
83
+ }
84
+ if (arg.startsWith("--canvas-path=")) {
85
+ canvasPath = arg.slice(14);
86
+ continue;
87
+ }
88
+ if (arg === "--canvas") {
89
+ canvasName = args[++i];
90
+ continue;
91
+ }
92
+ if (arg.startsWith("--canvas=")) {
93
+ canvasName = arg.slice(9);
94
+ continue;
95
+ }
96
+ if (arg === "--canvases-dir") {
97
+ canvasesDir = args[++i];
98
+ continue;
99
+ }
100
+ if (arg.startsWith("--canvases-dir=")) {
101
+ canvasesDir = arg.slice(15);
102
+ continue;
103
+ }
104
+ if (arg === "--profile") {
105
+ const value = args[++i];
106
+ if (!value) {
107
+ throw new Error("dag run-task --profile requires a value");
108
+ }
109
+ profile = parseDagRunTaskProfile(value);
110
+ profileExplicit = true;
111
+ continue;
112
+ }
113
+ if (arg.startsWith("--profile=")) {
114
+ profile = parseDagRunTaskProfile(arg.slice("--profile=".length));
115
+ profileExplicit = true;
116
+ continue;
117
+ }
118
+ if (arg.startsWith("-")) {
119
+ throw new Error(`unknown dag run-task flag: ${arg}`);
120
+ }
121
+ if (taskId) {
122
+ throw new Error(`unexpected positional argument: ${arg}`);
123
+ }
124
+ taskId = arg;
125
+ }
126
+ if (!taskId) {
127
+ throw new Error("dag run-task requires <task-id>");
128
+ }
129
+ return {
130
+ taskId,
131
+ outputPath: outputPath ? path.resolve(outputPath) : undefined,
132
+ strictModels,
133
+ noCursor,
134
+ execute,
135
+ initOnly,
136
+ dryRun,
137
+ cwd: path.resolve(cwd ?? defaultCwd ?? process.cwd()),
138
+ maxConcurrent,
139
+ runId,
140
+ canvasPath,
141
+ canvasName,
142
+ canvasesDir,
143
+ profile,
144
+ profileExplicit,
145
+ };
146
+ }
147
+ function parseDagRunTaskProfile(value) {
148
+ if (value === "auto" ||
149
+ value === "minimal" ||
150
+ value === "standard" ||
151
+ value === "reviewed" ||
152
+ value === "supervised") {
153
+ return value;
154
+ }
155
+ throw new Error(`dag run-task --profile must be one of: auto, minimal, standard, reviewed, supervised; got ${value}`);
156
+ }
157
+ function buildValidateArgs(dagPath, parsed) {
158
+ const validateArgs = ["--dag", dagPath];
159
+ if (parsed.strictModels)
160
+ validateArgs.push("--strict-models");
161
+ if (parsed.noCursor)
162
+ validateArgs.push("--forbid-executor", "cursor");
163
+ return validateArgs;
164
+ }
165
+ function buildRunDagArgs(dagPath, parsed) {
166
+ const runArgs = ["--dag", dagPath, "--cwd", parsed.cwd];
167
+ if (parsed.dryRun)
168
+ runArgs.push("--dry-run");
169
+ else if (parsed.initOnly)
170
+ runArgs.push("--init-only");
171
+ if (parsed.noCursor)
172
+ runArgs.push("--no-cursor");
173
+ if (parsed.maxConcurrent !== undefined) {
174
+ runArgs.push("--max-concurrent", String(parsed.maxConcurrent));
175
+ }
176
+ if (parsed.runId)
177
+ runArgs.push("--run-id", parsed.runId);
178
+ if (parsed.canvasPath)
179
+ runArgs.push("--canvas-path", parsed.canvasPath);
180
+ if (parsed.canvasName)
181
+ runArgs.push("--canvas", parsed.canvasName);
182
+ if (parsed.canvasesDir)
183
+ runArgs.push("--canvases-dir", parsed.canvasesDir);
184
+ return runArgs;
185
+ }
186
+ function shouldRunExecution(parsed) {
187
+ return parsed.execute || parsed.initOnly || parsed.dryRun;
188
+ }
189
+ function resolveExecutionMode(parsed) {
190
+ if (parsed.dryRun)
191
+ return "dry-run";
192
+ if (parsed.initOnly)
193
+ return "init-only";
194
+ return "execute";
195
+ }
196
+ function isUnsafeWriteSetEntry(entry) {
197
+ const normalized = entry.trim();
198
+ if (!normalized || normalized === "." || normalized === "./")
199
+ return true;
200
+ if (normalized === "**")
201
+ return true;
202
+ return normalized.includes(PLACEHOLDER_WRITESET_MARKER);
203
+ }
204
+ export async function assertSafeForExecution(dagPath) {
205
+ const raw = JSON.parse(await readFile(dagPath, "utf-8"));
206
+ const unsafe = [];
207
+ for (const task of raw.tasks ?? []) {
208
+ if (task.writePolicy !== "exclusive")
209
+ continue;
210
+ const writeSet = Array.isArray(task.writeSet) ? task.writeSet : [];
211
+ for (const entry of writeSet) {
212
+ if (typeof entry !== "string")
213
+ continue;
214
+ if (isUnsafeWriteSetEntry(entry)) {
215
+ const taskId = typeof task.id === "string" ? task.id : "unknown";
216
+ unsafe.push(`task=${taskId}, writeSet=${entry}`);
217
+ }
218
+ }
219
+ }
220
+ if (unsafe.length === 0)
221
+ return;
222
+ throw new Error(`refusing execution: narrow implement writeSet before --execute/init-only/dry-run: ${unsafe.join("; ")}`);
223
+ }
224
+ async function captureValidateSummary(repoRoot, validateArgs) {
225
+ let captured;
226
+ const originalLog = console.log;
227
+ console.log = (message) => {
228
+ if (typeof message === "string") {
229
+ captured = JSON.parse(message);
230
+ }
231
+ };
232
+ try {
233
+ await runDagValidate(repoRoot, validateArgs);
234
+ }
235
+ finally {
236
+ console.log = originalLog;
237
+ }
238
+ if (!captured) {
239
+ throw new Error("dag run-task validate produced no JSON summary");
240
+ }
241
+ return captured;
242
+ }
243
+ function buildNextSteps(taskId, outputPath, cwd) {
244
+ return [
245
+ `Review ${outputPath}`,
246
+ `npm run dev -- dag validate --dag ${outputPath}`,
247
+ `npm run dev -- run-dag --dag ${outputPath} --cwd ${cwd}`,
248
+ `npm run dev -- dag run-task ${taskId} --execute --cwd ${cwd}`,
249
+ ];
250
+ }
251
+ function isBroadWriteSetEntryForPacket(entry) {
252
+ const normalized = entry.trim().replace(/\\/g, "/").replace(/^\.\//, "");
253
+ if (!normalized || normalized === "." || normalized === "./")
254
+ return true;
255
+ if (normalized === "**")
256
+ return true;
257
+ return normalized.includes(PLACEHOLDER_WRITESET_MARKER);
258
+ }
259
+ function resolveWritePolicyForPacket(task, spec) {
260
+ return task.writePolicy ?? spec.defaults?.writePolicy ?? "none";
261
+ }
262
+ function collectWriterTasksForPacket(spec) {
263
+ return spec.tasks.filter((task) => {
264
+ const writePolicy = resolveWritePolicyForPacket(task, spec);
265
+ return writePolicy === "exclusive" && (task.writeSet?.length ?? 0) > 0;
266
+ });
267
+ }
268
+ function findForbiddenOverlapsForPacket(task) {
269
+ const overlaps = [];
270
+ for (const writeSetEntry of task.writeSet ?? []) {
271
+ for (const forbiddenPath of task.forbiddenPaths ?? []) {
272
+ if (pathMatchesPattern(writeSetEntry, forbiddenPath) ||
273
+ pathMatchesPattern(forbiddenPath, writeSetEntry)) {
274
+ overlaps.push({ writeSetEntry, forbiddenPath });
275
+ }
276
+ }
277
+ }
278
+ return overlaps;
279
+ }
280
+ function isVerificationShellTask(task) {
281
+ if (task.executor !== "shell" || !task.shell)
282
+ return false;
283
+ if (/verify|verification/i.test(task.id))
284
+ return true;
285
+ const commands = resolveShellCommands(task.shell);
286
+ return commands.some((command) => /(vitest|npm run (lint|typecheck|test)|check-repo\.sh|loop-agent-standard-verify)/.test(command));
287
+ }
288
+ async function buildReviewPacket(input) {
289
+ const spec = parseDagSpec(JSON.parse(await readFile(input.dagPath, "utf-8")));
290
+ const writers = collectWriterTasksForPacket(spec).map((task) => {
291
+ const writeSet = task.writeSet ?? [];
292
+ return {
293
+ nodeId: task.id,
294
+ role: task.role,
295
+ writePolicy: resolveWritePolicyForPacket(task, spec),
296
+ writeSet,
297
+ broadEntries: writeSet.filter(isBroadWriteSetEntryForPacket),
298
+ forbiddenOverlaps: findForbiddenOverlapsForPacket(task),
299
+ };
300
+ });
301
+ const shellTasks = spec.tasks.filter((task) => task.executor === "shell" && task.shell);
302
+ const shellVerification = shellTasks
303
+ .filter(isVerificationShellTask)
304
+ .map((task) => ({
305
+ nodeId: task.id,
306
+ commands: resolveShellCommands(task.shell),
307
+ }));
308
+ return {
309
+ profileRouting: {
310
+ requestedProfile: input.profileRouting.requestedProfile,
311
+ selectedByProfile: input.profileRouting.selectedByProfile,
312
+ selectedTemplate: input.profileRouting.selectedTemplate,
313
+ source: input.profileRouting.source,
314
+ ...(input.profileRouting.routingReasons
315
+ ? { routingReasons: input.profileRouting.routingReasons }
316
+ : {}),
317
+ },
318
+ governanceProfile: input.governanceProfile,
319
+ writers,
320
+ broadWriteSetRisk: writers.some((writer) => writer.broadEntries.length > 0),
321
+ forbiddenOverlapRisk: writers.some((writer) => writer.forbiddenOverlaps.length > 0),
322
+ shellGates: shellTasks
323
+ .filter((task) => task.shell?.verdictGate)
324
+ .map((task) => ({
325
+ nodeId: task.id,
326
+ fromNodeId: task.shell.verdictGate.fromNodeId,
327
+ accept: task.shell.verdictGate.accept,
328
+ lineMode: task.shell.verdictGate.lineMode ?? "first-non-empty",
329
+ label: task.shell.verdictGate.label,
330
+ commands: resolveShellCommands(task.shell),
331
+ })),
332
+ shellVerification,
333
+ decisionGates: spec.tasks.map((task) => ({
334
+ nodeId: task.id,
335
+ enabled: task.decisionGate?.enabled ?? false,
336
+ mode: task.decisionGate?.enabled
337
+ ? (task.decisionGate.mode ?? "record-only")
338
+ : "disabled",
339
+ })),
340
+ expectedVerification: shellVerification.flatMap((entry) => entry.commands),
341
+ };
342
+ }
343
+ function templateForProfile(policy, profile) {
344
+ return policy.dag.profileRouting[profile];
345
+ }
346
+ async function resolveProfileRouting(repoRoot, parsed, candidateProfile) {
347
+ if (!parsed.profileExplicit) {
348
+ return {
349
+ requestedProfile: parsed.profile,
350
+ selectedByProfile: "standard",
351
+ selectedTemplate: "standard-dag",
352
+ source: "default",
353
+ candidateProfile,
354
+ };
355
+ }
356
+ const manifest = await loadHarnessManifest(repoRoot);
357
+ if (parsed.profile === "auto") {
358
+ const supervisedGate = requiresSupervisedQualityGate(candidateProfile);
359
+ const selectedByProfile = resolveAutoRoutingProfile(candidateProfile);
360
+ const escalatedToSupervised = supervisedGate.required && selectedByProfile === "supervised";
361
+ return {
362
+ requestedProfile: "auto",
363
+ selectedByProfile,
364
+ selectedTemplate: templateForProfile(manifest.workflowPolicy, selectedByProfile),
365
+ source: escalatedToSupervised
366
+ ? "supervised-quality-gate"
367
+ : "workflowPolicy",
368
+ candidateProfile,
369
+ routingReasons: supervisedGate.required
370
+ ? supervisedGate.reasons
371
+ : undefined,
372
+ };
373
+ }
374
+ return {
375
+ requestedProfile: parsed.profile,
376
+ selectedByProfile: parsed.profile,
377
+ selectedTemplate: templateForProfile(manifest.workflowPolicy, parsed.profile),
378
+ source: "cli",
379
+ candidateProfile,
380
+ };
381
+ }
382
+ export async function runDagRunTask(repoRoot, rawArgs) {
383
+ const parsed = parseDagRunTaskArgs(rawArgs, repoRoot);
384
+ const candidateResult = await initHybridDagFromTask(repoRoot, parsed.taskId, {
385
+ outputPath: parsed.outputPath,
386
+ template: "standard-dag",
387
+ });
388
+ const candidateValidateArgs = buildValidateArgs(candidateResult.outputPath, parsed);
389
+ const candidateValidateSummary = await captureValidateSummary(repoRoot, candidateValidateArgs);
390
+ const profileRouting = await resolveProfileRouting(repoRoot, parsed, candidateValidateSummary.governanceProfile ?? {
391
+ profile: "standard",
392
+ process: [],
393
+ delivery: [],
394
+ codeChange: [],
395
+ reasons: ["dag run-task validate did not report governanceProfile"],
396
+ });
397
+ const initResult = profileRouting.selectedTemplate === "standard-dag"
398
+ ? candidateResult
399
+ : await initHybridDagFromTask(repoRoot, parsed.taskId, {
400
+ outputPath: parsed.outputPath,
401
+ template: profileRouting.selectedTemplate,
402
+ });
403
+ const outputPath = initResult.outputPath;
404
+ const validateArgs = buildValidateArgs(outputPath, parsed);
405
+ const validateSummary = profileRouting.selectedTemplate === "standard-dag"
406
+ ? candidateValidateSummary
407
+ : await captureValidateSummary(repoRoot, validateArgs);
408
+ const governanceProfile = validateSummary.governanceProfile ?? {
409
+ profile: "standard",
410
+ process: [],
411
+ delivery: [],
412
+ codeChange: [],
413
+ reasons: ["dag run-task validate did not report governanceProfile"],
414
+ };
415
+ const reviewPacket = await buildReviewPacket({
416
+ dagPath: outputPath,
417
+ profileRouting,
418
+ governanceProfile,
419
+ });
420
+ if (!shouldRunExecution(parsed)) {
421
+ console.log(JSON.stringify({
422
+ mode: "generate+validate",
423
+ ok: true,
424
+ taskId: initResult.taskId,
425
+ outputPath,
426
+ defaultOutputPath: defaultHybridDagOutputPath(parsed.taskId),
427
+ taskCount: initResult.taskCount,
428
+ nodeIds: initResult.nodeIds,
429
+ title: validateSummary.title,
430
+ ranks: validateSummary.ranks,
431
+ governanceProfile,
432
+ profileRouting,
433
+ reviewPacket,
434
+ warnings: validateSummary.warnings,
435
+ next: buildNextSteps(parsed.taskId, outputPath, parsed.cwd),
436
+ }, null, 2));
437
+ return;
438
+ }
439
+ await assertSafeForExecution(outputPath);
440
+ const runArgs = buildRunDagArgs(outputPath, parsed);
441
+ const executionMode = resolveExecutionMode(parsed);
442
+ let runSummary;
443
+ const originalLog = console.log;
444
+ console.log = (message) => {
445
+ if (typeof message === "string") {
446
+ runSummary = JSON.parse(message);
447
+ }
448
+ };
449
+ try {
450
+ await runRunDag(repoRoot, runArgs);
451
+ }
452
+ finally {
453
+ console.log = originalLog;
454
+ }
455
+ console.log(JSON.stringify({
456
+ mode: executionMode,
457
+ ok: true,
458
+ taskId: initResult.taskId,
459
+ outputPath,
460
+ taskCount: initResult.taskCount,
461
+ nodeIds: initResult.nodeIds,
462
+ title: validateSummary.title,
463
+ ranks: validateSummary.ranks,
464
+ governanceProfile,
465
+ profileRouting,
466
+ reviewPacket,
467
+ warnings: validateSummary.warnings,
468
+ run: runSummary,
469
+ }, null, 2));
470
+ }