@tea-agent/loop-agent 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/AGENTS.md +62 -45
  2. package/CHANGELOG.md +60 -28
  3. package/README.md +160 -124
  4. package/bin/loop-agent.js +21 -21
  5. package/dist/adapters/index.js +3 -2
  6. package/dist/adapters/loop-agent.js +44 -2
  7. package/dist/application/dag/args.js +420 -0
  8. package/dist/application/dag/generate-task-dag.js +280 -0
  9. package/dist/application/dag/report-dag.js +14 -0
  10. package/dist/application/dag/run-dag.js +106 -0
  11. package/dist/application/dag/validate-dag.js +102 -0
  12. package/dist/application/loop/run-action.js +23 -0
  13. package/dist/cli/catalog.js +2 -237
  14. package/dist/cli/command-definitions.js +571 -0
  15. package/dist/cli/index.js +2 -0
  16. package/dist/cli/program.js +65 -1
  17. package/dist/cli/router.js +13 -0
  18. package/dist/cli-governance/active-residue-check.js +38 -0
  19. package/dist/commands/dag-report.js +6 -107
  20. package/dist/commands/dag-run-task.js +8 -466
  21. package/dist/commands/dag-validate.js +7 -179
  22. package/dist/commands/examples.js +90 -0
  23. package/dist/commands/init.js +1518 -0
  24. package/dist/commands/loop.js +57 -31
  25. package/dist/commands/pi-prompt.js +2 -9
  26. package/dist/commands/run-dag.js +7 -180
  27. package/dist/executors/cursor-executor-artifacts.js +3 -4
  28. package/dist/executors/cursor-worker-client.js +13 -3
  29. package/dist/executors/dag-cursor-executor.js +2 -3
  30. package/dist/executors/dag-pi-executor.js +3 -4
  31. package/dist/executors/dag-static-executor.js +2 -5
  32. package/dist/executors/pi-defaults.js +9 -0
  33. package/dist/executors/shell-executor.js +12 -20
  34. package/dist/governance/manifest-types.js +1 -0
  35. package/dist/infrastructure/harness/active-residue-policy.js +73 -0
  36. package/dist/infrastructure/harness/artifact-store.js +72 -0
  37. package/dist/infrastructure/harness/atomic-write.js +49 -0
  38. package/dist/infrastructure/harness/completed-facts-guard.js +40 -0
  39. package/dist/infrastructure/harness/loop-action-store.js +23 -0
  40. package/dist/infrastructure/harness/loop-store.js +41 -0
  41. package/dist/infrastructure/harness/one-shot-run-store.js +94 -0
  42. package/dist/infrastructure/harness/task-store.js +77 -0
  43. package/dist/records/one-shot-runs.js +26 -61
  44. package/dist/records/promotion.js +3 -4
  45. package/dist/shared/artifacts-core.js +5 -5
  46. package/dist/shared/logger.js +9 -15
  47. package/dist/task/delegate.js +4 -4
  48. package/dist/task/runtime.js +5 -7
  49. package/dist/task/state.js +6 -20
  50. package/dist/workflows/dag/convergence/controller.js +277 -0
  51. package/dist/workflows/dag/dynamic-runtime/condition.js +48 -0
  52. package/dist/workflows/dag/dynamic-runtime/loop-until.js +156 -0
  53. package/dist/workflows/dag/dynamic-runtime/map.js +185 -0
  54. package/dist/workflows/dag/dynamic-runtime/reduction.js +72 -0
  55. package/dist/workflows/dag/dynamic-runtime/shared.js +133 -0
  56. package/dist/workflows/dag/failure-routing.js +82 -0
  57. package/dist/workflows/dag/lifecycle.js +101 -8
  58. package/dist/workflows/dag/node-execution.js +262 -0
  59. package/dist/workflows/dag/report.js +73 -1
  60. package/dist/workflows/dag/run-store.js +36 -0
  61. package/dist/workflows/dag/runner.js +82 -1341
  62. package/dist/workflows/dag/scheduler.js +84 -0
  63. package/dist/workflows/dag/upstream-artifacts.js +20 -18
  64. package/dist/workflows/loop/actions/cursor-fix.js +191 -0
  65. package/dist/workflows/loop/actions/dag-action.js +130 -0
  66. package/dist/workflows/loop/actions/pi-review.js +267 -0
  67. package/dist/workflows/loop/actions/shared.js +157 -0
  68. package/dist/workflows/loop/actions/shell-verify.js +82 -0
  69. package/dist/workflows/loop/actions/types.js +1 -0
  70. package/dist/workflows/loop/actions/workflow-action.js +255 -0
  71. package/dist/workflows/loop/actions.js +55 -1212
  72. package/dist/workflows/loop/closeout.js +5 -4
  73. package/dist/workflows/loop/context.js +2 -3
  74. package/dist/workflows/loop/events.js +3 -2
  75. package/dist/workflows/loop/policy/auto-policy.js +104 -0
  76. package/dist/workflows/loop/policy/cursor-fix-policy.js +31 -0
  77. package/dist/workflows/loop/rounds.js +3 -3
  78. package/dist/workflows/loop/signals.js +4 -7
  79. package/dist/workflows/loop/state.js +11 -11
  80. package/docs/README.md +47 -44
  81. package/docs/agent-dag-recovery-playbook.md +32 -6
  82. package/docs/agent-dag-runner.md +17 -17
  83. package/docs/architecture/runtime-boundaries.md +147 -0
  84. package/docs/cursor-executor-usage.md +5 -5
  85. package/docs/decisions/README.md +2 -2
  86. package/docs/design/README.md +24 -24
  87. package/docs/development-principles.md +50 -50
  88. package/docs/dynamic-workflow-dag-engine-roadmap.md +6 -6
  89. package/docs/exec-plans/README.md +4 -4
  90. package/docs/exec-plans/active/README.md +10 -5
  91. package/docs/exec-plans/completed/README.md +9 -5
  92. package/docs/feature-workflow.md +111 -109
  93. package/docs/harness-methodology-verification.md +18 -18
  94. package/docs/loop-agent-harness.md +36 -36
  95. package/docs/production-readiness.md +96 -0
  96. package/docs/progress/README.md +2 -2
  97. package/docs/reports/README.md +4 -2
  98. package/docs/templates/agent-dag-decision-gate-dogfood-report.md +1 -1
  99. package/docs/templates/agent-dag-process-supervisor.prompt.md +2 -2
  100. package/docs/templates/agent-dag-report.schema.json +33 -2
  101. package/docs/templates/agent-dag-review-verdict.prompt.md +1 -1
  102. package/docs/templates/agent-dag.base.json +195 -195
  103. package/docs/templates/agent-dag.final-verification.json +190 -190
  104. package/docs/templates/agent-dag.schema.json +17 -17
  105. package/docs/templates/agent-dag.supervised-implementation.json +500 -500
  106. package/docs/templates/hybrid-dag.json +193 -193
  107. package/docs/templates/production-readiness-checklist.md +57 -0
  108. package/docs/templates/progress-log.md +7 -7
  109. package/docs/templates/project-start-checklist.md +8 -8
  110. package/docs/templates/qa-report.md +17 -11
  111. package/docs/templates/sprint-contract.md +19 -19
  112. package/docs/verification-matrix.md +37 -26
  113. package/examples/example-dag.json +51 -51
  114. package/examples/hybrid-loop-agent-dag.json +194 -194
  115. package/harness.json +5 -5
  116. package/package.json +62 -61
  117. package/skills/ai-engineering-context/SKILL.md +21 -21
  118. package/skills/loop-agent/SKILL.md +56 -171
  119. package/skills/loop-agent/references/README.md +6 -2
  120. package/skills/loop-agent/references/command-reference.md +107 -65
  121. package/skills/loop-agent/references/harness-policy.md +115 -115
  122. package/skills/loop-agent/references/hybrid-dag.md +30 -30
  123. package/skills/loop-agent/references/learned/README.md +13 -13
  124. package/skills/loop-agent/references/long-running-loop.md +59 -0
  125. package/skills/loop-agent/references/model-routing.md +1 -1
  126. package/skills/loop-agent/references/orchestrator-and-interventions.md +1 -1
  127. package/skills/loop-agent/references/pi-prompt.md +9 -9
  128. package/skills/loop-agent/references/pi-subagent-assisted-mode.md +0 -2
  129. package/skills/loop-agent/references/post-implementation-and-patterns.md +7 -7
  130. package/skills/loop-agent/references/task-workflow.md +19 -19
  131. package/skills/loop-agent/references/verification-and-failure-handling.md +54 -0
  132. package/skills/requesting-code-review/SKILL.md +40 -40
  133. package/skills/requesting-code-review/code-reviewer.md +4 -4
  134. package/skills/systematic-debugging/CREATION-LOG.md +43 -43
  135. package/skills/systematic-debugging/SKILL.md +113 -113
  136. package/skills/systematic-debugging/condition-based-waiting.md +20 -20
  137. package/skills/systematic-debugging/defense-in-depth.md +27 -27
  138. package/skills/systematic-debugging/root-cause-tracing.md +38 -38
  139. package/skills/systematic-debugging/test-academic.md +6 -6
  140. package/skills/systematic-debugging/test-pressure-1.md +6 -6
  141. package/skills/systematic-debugging/test-pressure-2.md +2 -2
  142. package/skills/systematic-debugging/test-pressure-3.md +6 -6
  143. package/skills/verification-before-completion/SKILL.md +37 -37
@@ -0,0 +1,13 @@
1
+ import { buildUnsupportedCommandMessage } from "./help.js";
2
+ import { COMMAND_DEFINITIONS, } from "./command-definitions.js";
3
+ export function resolveCliCommandHandler(command) {
4
+ return COMMAND_DEFINITIONS.find((definition) => definition.name === command)
5
+ ?.handler;
6
+ }
7
+ export async function dispatchCliCommand(ctx) {
8
+ const handler = resolveCliCommandHandler(ctx.command);
9
+ if (!handler) {
10
+ throw new Error(buildUnsupportedCommandMessage(ctx.command, ctx.subcommand));
11
+ }
12
+ await handler(ctx);
13
+ }
@@ -0,0 +1,38 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { evaluateActiveResidue, formatActiveResidueFinding, shouldFailActiveResidue, } from "../infrastructure/harness/active-residue-policy.js";
3
+ function readEntriesFile(filePath) {
4
+ const raw = readFileSync(filePath, "utf-8");
5
+ return raw
6
+ .split(/\r?\n/)
7
+ .map((line) => line.trim())
8
+ .filter(Boolean);
9
+ }
10
+ function getRequiredFlag(args, flag) {
11
+ const index = args.indexOf(flag);
12
+ const value = index >= 0 ? args[index + 1] : undefined;
13
+ if (!value) {
14
+ throw new Error(`missing required flag: ${flag}`);
15
+ }
16
+ return value;
17
+ }
18
+ function main(rawArgs, env) {
19
+ const dagEntriesFile = getRequiredFlag(rawArgs, "--dag-entries-file");
20
+ const toolEntriesFile = getRequiredFlag(rawArgs, "--tool-entries-file");
21
+ const findings = evaluateActiveResidue({
22
+ dagActiveEntries: readEntriesFile(dagEntriesFile),
23
+ toolActiveEntries: readEntriesFile(toolEntriesFile),
24
+ });
25
+ for (const finding of findings) {
26
+ if (env[finding.allowEnv] === "1")
27
+ continue;
28
+ process.stderr.write(formatActiveResidueFinding(finding));
29
+ }
30
+ return shouldFailActiveResidue(findings, env).fail ? 1 : 0;
31
+ }
32
+ try {
33
+ process.exitCode = main(process.argv.slice(2), process.env);
34
+ }
35
+ catch (error) {
36
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
37
+ process.exitCode = 1;
38
+ }
@@ -1,113 +1,12 @@
1
1
  import { mkdir, writeFile } from "node:fs/promises";
2
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
- }
3
+ import { buildDagCloseoutDraft, formatDagReportHandoffMarkdown, formatDagReportMarkdown, } from "../workflows/dag/report.js";
4
+ import { parseDagReportArgs, } from "../application/dag/args.js";
5
+ import { reportDagUseCase, serializeReportDagJson, } from "../application/dag/report-dag.js";
6
+ export { parseDagReportArgs };
108
7
  export async function runDagReport(repoRoot, rawArgs) {
109
8
  const parsed = parseDagReportArgs(rawArgs);
110
- const report = await buildDagReport({
9
+ const report = await reportDagUseCase({
111
10
  repoRoot,
112
11
  runId: parsed.runId,
113
12
  lifecycle: parsed.lifecycle,
@@ -116,7 +15,7 @@ export async function runDagReport(repoRoot, rawArgs) {
116
15
  action: parsed.action,
117
16
  });
118
17
  if (parsed.json) {
119
- console.log(JSON.stringify(serializeDagReportJson(report), null, 2));
18
+ console.log(JSON.stringify(serializeReportDagJson(report), null, 2));
120
19
  return;
121
20
  }
122
21
  if (parsed.markdown) {
@@ -1,470 +1,12 @@
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
- }
1
+ import { assertSafeForExecution, generateTaskDagUseCase, } from "../application/dag/generate-task-dag.js";
2
+ import { parseDagRunTaskArgs, } from "../application/dag/args.js";
3
+ export { assertSafeForExecution };
4
+ export { parseDagRunTaskArgs };
382
5
  export async function runDagRunTask(repoRoot, rawArgs) {
383
6
  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,
7
+ const result = await generateTaskDagUseCase({
8
+ repoRoot,
9
+ ...parsed,
419
10
  });
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));
11
+ console.log(JSON.stringify(result, null, 2));
470
12
  }