@tea-agent/loop-agent 0.1.0 → 0.2.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.
- package/CHANGELOG.md +17 -1
- package/README.md +127 -92
- package/dist/adapters/index.js +3 -2
- package/dist/adapters/loop-agent.js +44 -2
- package/dist/application/dag/args.js +420 -0
- package/dist/application/dag/generate-task-dag.js +280 -0
- package/dist/application/dag/report-dag.js +14 -0
- package/dist/application/dag/run-dag.js +93 -0
- package/dist/application/dag/validate-dag.js +101 -0
- package/dist/application/loop/run-action.js +23 -0
- package/dist/cli/catalog.js +2 -237
- package/dist/cli/command-definitions.js +571 -0
- package/dist/cli/index.js +2 -0
- package/dist/cli/program.js +65 -1
- package/dist/cli/router.js +13 -0
- package/dist/cli-governance/active-residue-check.js +38 -0
- package/dist/commands/dag-report.js +6 -107
- package/dist/commands/dag-run-task.js +8 -466
- package/dist/commands/dag-validate.js +7 -179
- package/dist/commands/examples.js +90 -0
- package/dist/commands/init.js +1495 -0
- package/dist/commands/loop.js +57 -31
- package/dist/commands/pi-prompt.js +2 -9
- package/dist/commands/run-dag.js +7 -180
- package/dist/executors/cursor-executor-artifacts.js +3 -4
- package/dist/executors/cursor-worker-client.js +13 -3
- package/dist/executors/dag-cursor-executor.js +2 -3
- package/dist/executors/dag-pi-executor.js +3 -4
- package/dist/executors/dag-static-executor.js +2 -5
- package/dist/executors/pi-defaults.js +9 -0
- package/dist/executors/shell-executor.js +12 -20
- package/dist/governance/manifest-types.js +1 -0
- package/dist/infrastructure/harness/active-residue-policy.js +73 -0
- package/dist/infrastructure/harness/artifact-store.js +72 -0
- package/dist/infrastructure/harness/atomic-write.js +49 -0
- package/dist/infrastructure/harness/completed-facts-guard.js +40 -0
- package/dist/infrastructure/harness/loop-action-store.js +23 -0
- package/dist/infrastructure/harness/loop-store.js +41 -0
- package/dist/infrastructure/harness/one-shot-run-store.js +94 -0
- package/dist/infrastructure/harness/task-store.js +77 -0
- package/dist/records/one-shot-runs.js +26 -61
- package/dist/records/promotion.js +3 -4
- package/dist/shared/artifacts-core.js +5 -5
- package/dist/shared/logger.js +9 -15
- package/dist/task/delegate.js +4 -4
- package/dist/task/runtime.js +5 -7
- package/dist/task/state.js +6 -20
- package/dist/workflows/dag/convergence/controller.js +277 -0
- package/dist/workflows/dag/dynamic-runtime/condition.js +48 -0
- package/dist/workflows/dag/dynamic-runtime/loop-until.js +156 -0
- package/dist/workflows/dag/dynamic-runtime/map.js +185 -0
- package/dist/workflows/dag/dynamic-runtime/reduction.js +72 -0
- package/dist/workflows/dag/dynamic-runtime/shared.js +133 -0
- package/dist/workflows/dag/lifecycle.js +6 -5
- package/dist/workflows/dag/node-execution.js +262 -0
- package/dist/workflows/dag/run-store.js +36 -0
- package/dist/workflows/dag/runner.js +82 -1341
- package/dist/workflows/dag/scheduler.js +84 -0
- package/dist/workflows/dag/upstream-artifacts.js +20 -18
- package/dist/workflows/loop/actions/cursor-fix.js +191 -0
- package/dist/workflows/loop/actions/dag-action.js +130 -0
- package/dist/workflows/loop/actions/pi-review.js +267 -0
- package/dist/workflows/loop/actions/shared.js +157 -0
- package/dist/workflows/loop/actions/shell-verify.js +82 -0
- package/dist/workflows/loop/actions/types.js +1 -0
- package/dist/workflows/loop/actions/workflow-action.js +255 -0
- package/dist/workflows/loop/actions.js +55 -1212
- package/dist/workflows/loop/closeout.js +5 -4
- package/dist/workflows/loop/context.js +2 -3
- package/dist/workflows/loop/events.js +3 -2
- package/dist/workflows/loop/policy/auto-policy.js +104 -0
- package/dist/workflows/loop/policy/cursor-fix-policy.js +31 -0
- package/dist/workflows/loop/rounds.js +3 -3
- package/dist/workflows/loop/signals.js +4 -7
- package/dist/workflows/loop/state.js +11 -11
- package/docs/README.md +3 -2
- package/docs/architecture/runtime-boundaries.md +147 -0
- package/docs/exec-plans/active/README.md +4 -0
- package/docs/exec-plans/completed/README.md +6 -2
- package/package.json +2 -1
- package/skills/ai-engineering-context/SKILL.md +21 -21
- package/skills/loop-agent/SKILL.md +73 -188
- package/skills/loop-agent/references/README.md +6 -2
- package/skills/loop-agent/references/harness-policy.md +113 -113
- package/skills/loop-agent/references/learned/README.md +13 -13
- package/skills/loop-agent/references/long-running-loop.md +59 -0
- package/skills/loop-agent/references/pi-subagent-assisted-mode.md +0 -2
- package/skills/loop-agent/references/verification-and-failure-handling.md +18 -0
- package/skills/requesting-code-review/SKILL.md +40 -40
- package/skills/requesting-code-review/code-reviewer.md +4 -4
- package/skills/systematic-debugging/CREATION-LOG.md +43 -43
- package/skills/systematic-debugging/SKILL.md +113 -113
- package/skills/systematic-debugging/condition-based-waiting.md +20 -20
- package/skills/systematic-debugging/defense-in-depth.md +27 -27
- package/skills/systematic-debugging/root-cause-tracing.md +38 -38
- package/skills/systematic-debugging/test-academic.md +6 -6
- package/skills/systematic-debugging/test-pressure-1.md +6 -6
- package/skills/systematic-debugging/test-pressure-2.md +2 -2
- package/skills/systematic-debugging/test-pressure-3.md +6 -6
- 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,
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
|
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(
|
|
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 {
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
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
|
|
385
|
-
|
|
386
|
-
|
|
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
|
-
|
|
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
|
}
|