@tea-agent/loop-agent 0.2.0 → 0.3.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/AGENTS.md +43 -24
- package/CHANGELOG.md +72 -44
- package/README.md +177 -164
- package/bin/loop-agent.js +21 -21
- package/dist/application/dag/args.js +6 -0
- package/dist/application/dag/generate-task-dag.js +5 -3
- package/dist/application/dag/run-dag.js +17 -1
- package/dist/application/dag/validate-dag.js +41 -0
- package/dist/cli/command-definitions.js +2 -2
- package/dist/cli/program.js +24 -4
- package/dist/commands/init.js +1036 -461
- package/dist/workflows/dag/dynamic-runtime/loop-until.js +2 -1
- package/dist/workflows/dag/dynamic-runtime/map.js +1 -0
- package/dist/workflows/dag/failure-routing.js +82 -0
- package/dist/workflows/dag/init-hybrid.js +3 -3
- package/dist/workflows/dag/lifecycle.js +95 -3
- package/dist/workflows/dag/report.js +73 -1
- package/dist/workflows/dag/skills.js +3 -3
- package/dist/workflows/dag/types.js +2 -0
- package/dist/workflows/dynamic/compile.js +11 -0
- package/dist/workflows/dynamic/spec.js +1 -0
- package/docs/README.md +50 -45
- package/docs/agent-dag-recovery-playbook.md +32 -6
- package/docs/agent-dag-runner.md +19 -17
- package/docs/architecture/runtime-boundaries.md +1 -1
- package/docs/cursor-executor-usage.md +5 -5
- package/docs/decisions/README.md +2 -2
- package/docs/design/README.md +24 -24
- package/docs/development-principles.md +50 -50
- package/docs/dynamic-workflow-dag-engine-roadmap.md +6 -6
- package/docs/exec-plans/README.md +4 -4
- package/docs/exec-plans/active/README.md +7 -9
- package/docs/exec-plans/completed/README.md +10 -8
- package/docs/feature-workflow.md +111 -109
- package/docs/harness-methodology-verification.md +18 -18
- package/docs/init-surface.manifest.json +175 -0
- package/docs/loop-agent-harness.md +36 -36
- package/docs/production-readiness.md +96 -0
- package/docs/progress/README.md +2 -2
- package/docs/reports/README.md +4 -2
- package/docs/skills/README.md +6 -0
- package/docs/skills/vetted-skill-registry.md +26 -0
- package/docs/templates/agent-dag-decision-gate-dogfood-report.md +1 -1
- package/docs/templates/agent-dag-process-supervisor.prompt.md +2 -2
- package/docs/templates/agent-dag-report.schema.json +33 -2
- package/docs/templates/agent-dag-review-verdict.prompt.md +1 -1
- package/docs/templates/agent-dag.base.json +195 -195
- package/docs/templates/agent-dag.final-verification.json +190 -190
- package/docs/templates/agent-dag.schema.json +17 -17
- package/docs/templates/agent-dag.supervised-implementation.json +500 -500
- package/docs/templates/hybrid-dag.json +193 -193
- package/docs/templates/init-evolution-review.md +33 -0
- package/docs/templates/production-readiness-checklist.md +57 -0
- package/docs/templates/progress-log.md +7 -7
- package/docs/templates/project-start-checklist.md +8 -8
- package/docs/templates/qa-report.md +17 -11
- package/docs/templates/sprint-contract.md +19 -19
- package/docs/verification-matrix.md +37 -26
- package/examples/example-dag.json +51 -51
- package/examples/hybrid-loop-agent-dag.json +194 -194
- package/harness.json +10 -8
- package/package.json +60 -58
- package/skills/code-review-core/SKILL.md +20 -0
- package/skills/codebase-scout/SKILL.md +19 -0
- package/skills/init-capability-evolution/SKILL.md +69 -0
- package/skills/loop-agent/SKILL.md +35 -35
- package/skills/loop-agent/references/command-reference.md +125 -65
- package/skills/loop-agent/references/harness-policy.md +30 -30
- package/skills/loop-agent/references/hybrid-dag.md +30 -30
- package/skills/loop-agent/references/model-routing.md +1 -1
- package/skills/loop-agent/references/orchestrator-and-interventions.md +1 -1
- package/skills/loop-agent/references/pi-prompt.md +9 -9
- package/skills/loop-agent/references/post-implementation-and-patterns.md +7 -7
- package/skills/loop-agent/references/task-workflow.md +19 -19
- package/skills/loop-agent/references/verification-and-failure-handling.md +36 -0
- package/skills/test-driven-development/SKILL.md +20 -0
- package/skills/webapp-testing/SKILL.md +19 -0
package/bin/loop-agent.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
3
|
-
import { dirname, join } from "node:path";
|
|
4
|
-
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
|
-
|
|
6
|
-
const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
7
|
-
const cliEntry = join(packageRoot, "dist", "cli.js");
|
|
8
|
-
|
|
9
|
-
if (!existsSync(cliEntry)) {
|
|
10
|
-
console.error(
|
|
11
|
-
`loop-agent: cannot find built CLI at ${cliEntry}. Run \`npm run build\` before using the package bin.`,
|
|
12
|
-
);
|
|
13
|
-
process.exit(1);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
try {
|
|
17
|
-
await import(pathToFileURL(cliEntry).href);
|
|
18
|
-
} catch (error) {
|
|
19
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
20
|
-
process.exit(1);
|
|
21
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
|
+
|
|
6
|
+
const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
7
|
+
const cliEntry = join(packageRoot, "dist", "cli.js");
|
|
8
|
+
|
|
9
|
+
if (!existsSync(cliEntry)) {
|
|
10
|
+
console.error(
|
|
11
|
+
`loop-agent: cannot find built CLI at ${cliEntry}. Run \`npm run build\` before using the package bin.`,
|
|
12
|
+
);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
await import(pathToFileURL(cliEntry).href);
|
|
18
|
+
} catch (error) {
|
|
19
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
@@ -159,6 +159,7 @@ export function parseDagValidateArgs(args) {
|
|
|
159
159
|
let dagPath;
|
|
160
160
|
let strictModelMatrix = false;
|
|
161
161
|
let strictGovernance = false;
|
|
162
|
+
let strictSkills = false;
|
|
162
163
|
const forbidExecutors = [];
|
|
163
164
|
let spineTaskId;
|
|
164
165
|
for (let i = 0; i < args.length; i += 1) {
|
|
@@ -185,6 +186,10 @@ export function parseDagValidateArgs(args) {
|
|
|
185
186
|
strictGovernance = true;
|
|
186
187
|
continue;
|
|
187
188
|
}
|
|
189
|
+
if (arg === "--strict-skills") {
|
|
190
|
+
strictSkills = true;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
188
193
|
if (arg === "--forbid-executor") {
|
|
189
194
|
const executor = args[++i];
|
|
190
195
|
if (!executor) {
|
|
@@ -230,6 +235,7 @@ export function parseDagValidateArgs(args) {
|
|
|
230
235
|
dagPath: path.resolve(dagPath),
|
|
231
236
|
strictModelMatrix,
|
|
232
237
|
strictGovernance,
|
|
238
|
+
strictSkills,
|
|
233
239
|
forbidExecutors,
|
|
234
240
|
spineTaskId,
|
|
235
241
|
};
|
|
@@ -58,9 +58,9 @@ export async function assertSafeForExecution(dagPath) {
|
|
|
58
58
|
function buildNextSteps(taskId, outputPath, cwd) {
|
|
59
59
|
return [
|
|
60
60
|
`Review ${outputPath}`,
|
|
61
|
-
`
|
|
62
|
-
`
|
|
63
|
-
`
|
|
61
|
+
`loop-agent dag validate --dag ${outputPath} --strict-models --strict-governance`,
|
|
62
|
+
`loop-agent run-dag --dag ${outputPath} --cwd ${cwd}`,
|
|
63
|
+
`loop-agent dag run-task ${taskId} --execute --cwd ${cwd}`,
|
|
64
64
|
];
|
|
65
65
|
}
|
|
66
66
|
function isBroadWriteSetEntryForPacket(entry) {
|
|
@@ -233,6 +233,7 @@ export async function generateTaskDagUseCase(input) {
|
|
|
233
233
|
if (!shouldRunExecution(parsed)) {
|
|
234
234
|
return {
|
|
235
235
|
mode: "generate+validate",
|
|
236
|
+
message: "DAG draft created",
|
|
236
237
|
ok: true,
|
|
237
238
|
taskId: initResult.taskId,
|
|
238
239
|
outputPath,
|
|
@@ -264,6 +265,7 @@ export async function generateTaskDagUseCase(input) {
|
|
|
264
265
|
});
|
|
265
266
|
return {
|
|
266
267
|
mode: resolveExecutionMode(parsed),
|
|
268
|
+
message: "DAG draft created",
|
|
267
269
|
ok: true,
|
|
268
270
|
taskId: initResult.taskId,
|
|
269
271
|
outputPath,
|
|
@@ -38,6 +38,14 @@ async function flushCanvasSafely(canvas) {
|
|
|
38
38
|
return getCanvasFlushErrorMessage(error);
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
|
+
function buildRunDagNextSteps(runId) {
|
|
42
|
+
return [
|
|
43
|
+
`loop-agent dag report --run-id ${runId} --markdown`,
|
|
44
|
+
`loop-agent dag doctor --run-id ${runId} --markdown`,
|
|
45
|
+
`loop-agent promote-run <task-id> --run-id ${runId}`,
|
|
46
|
+
`loop-agent dag closeout-draft --run-id ${runId}`,
|
|
47
|
+
];
|
|
48
|
+
}
|
|
41
49
|
export async function runDagUseCase(input) {
|
|
42
50
|
const spec = await loadDagSpecFromFile(input.dagPath);
|
|
43
51
|
if (input.noCursor) {
|
|
@@ -64,11 +72,13 @@ export async function runDagUseCase(input) {
|
|
|
64
72
|
const canvasError = await flushCanvasSafely(canvas);
|
|
65
73
|
return {
|
|
66
74
|
mode: "dry-run",
|
|
75
|
+
message: "DAG run prepared",
|
|
67
76
|
title: spec.title,
|
|
68
77
|
taskCount: spec.tasks.length,
|
|
69
78
|
ranks: summary.ranks,
|
|
70
79
|
runId: summary.runId,
|
|
71
80
|
runDir: summary.runDir,
|
|
81
|
+
next: buildRunDagNextSteps(summary.runId),
|
|
72
82
|
...(resolvedCanvasPath ? { canvasPath: resolvedCanvasPath } : {}),
|
|
73
83
|
...(canvasError ? { canvasError } : {}),
|
|
74
84
|
};
|
|
@@ -85,9 +95,15 @@ export async function runDagUseCase(input) {
|
|
|
85
95
|
if (resolvedCanvasPath) {
|
|
86
96
|
return {
|
|
87
97
|
...summary,
|
|
98
|
+
message: "DAG run finished",
|
|
99
|
+
next: buildRunDagNextSteps(summary.runId),
|
|
88
100
|
canvasPath: resolvedCanvasPath,
|
|
89
101
|
...(canvasError ? { canvasError } : {}),
|
|
90
102
|
};
|
|
91
103
|
}
|
|
92
|
-
return
|
|
104
|
+
return {
|
|
105
|
+
...summary,
|
|
106
|
+
message: "DAG run finished",
|
|
107
|
+
next: buildRunDagNextSteps(summary.runId),
|
|
108
|
+
};
|
|
93
109
|
}
|
|
@@ -4,6 +4,8 @@ import { assertValidDagSpec, collectExecutorModelWarnings, collectForbiddenExecu
|
|
|
4
4
|
import { inferGovernanceProfile } from "../../workflows/dag/governance-profile.js";
|
|
5
5
|
import { topoSortToRanks } from "../../workflows/dag/topo.js";
|
|
6
6
|
import { runSpineAudit } from "../../governance/spine-audit.js";
|
|
7
|
+
import { resolveDagNodeSkills } from "../../workflows/dag/skills.js";
|
|
8
|
+
import { resolveDagSkillInstructions } from "../../workflows/dag/skill-instructions.js";
|
|
7
9
|
function formatModelMatrixWarnings(warnings) {
|
|
8
10
|
return warnings.map((warning) => ({
|
|
9
11
|
executor: warning.executor,
|
|
@@ -55,6 +57,36 @@ function ensureNoForbiddenExecutors(spec, forbidExecutors, rawTaskExecutors) {
|
|
|
55
57
|
.join("; ");
|
|
56
58
|
throw new Error(`forbidden executor present: ${details}`);
|
|
57
59
|
}
|
|
60
|
+
async function ensureStrictSkillResolution(spec, repoRoot) {
|
|
61
|
+
const failures = [];
|
|
62
|
+
for (const task of spec.tasks) {
|
|
63
|
+
if (task.executor !== "pi" && task.executor !== "cursor")
|
|
64
|
+
continue;
|
|
65
|
+
const skills = resolveDagNodeSkills(spec, task);
|
|
66
|
+
if (skills.length === 0)
|
|
67
|
+
continue;
|
|
68
|
+
const resolved = await resolveDagSkillInstructions(skills, {
|
|
69
|
+
cwd: repoRoot,
|
|
70
|
+
perSkillMaxChars: 100_000,
|
|
71
|
+
totalMaxChars: 1_000_000,
|
|
72
|
+
});
|
|
73
|
+
for (const entry of resolved) {
|
|
74
|
+
if (entry.resolution !== "resolved") {
|
|
75
|
+
failures.push(`${task.id}:${entry.name}:${entry.resolution}${entry.error ? `:${entry.error}` : ""}`);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (entry.truncated) {
|
|
79
|
+
failures.push(`${task.id}:${entry.name}:truncated`);
|
|
80
|
+
}
|
|
81
|
+
for (const unresolved of entry.unresolvedReferences ?? []) {
|
|
82
|
+
failures.push(`${task.id}:${entry.name}:unresolved-reference:${unresolved.path}:${unresolved.reason}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (failures.length > 0) {
|
|
87
|
+
throw new Error(`strict skill audit failed: ${failures.join("; ")}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
58
90
|
export async function validateDagUseCase(input) {
|
|
59
91
|
const spec = await loadDagSpecFromFile(input.dagPath);
|
|
60
92
|
const rawTaskExecutors = await loadRawTaskExecutorMap(input.dagPath);
|
|
@@ -65,6 +97,9 @@ export async function validateDagUseCase(input) {
|
|
|
65
97
|
if (input.strictModelMatrix) {
|
|
66
98
|
ensureStrictModelMatrix(warnings);
|
|
67
99
|
}
|
|
100
|
+
if (input.strictSkills) {
|
|
101
|
+
await ensureStrictSkillResolution(spec, input.repoRoot);
|
|
102
|
+
}
|
|
68
103
|
const spineAudit = input.spineTaskId
|
|
69
104
|
? await runSpineAudit(input.repoRoot, input.spineTaskId)
|
|
70
105
|
: undefined;
|
|
@@ -81,9 +116,14 @@ export async function validateDagUseCase(input) {
|
|
|
81
116
|
const { ranks } = topoSortToRanks(spec);
|
|
82
117
|
const governanceProfile = inferGovernanceProfile(spec);
|
|
83
118
|
return {
|
|
119
|
+
message: "DAG validation passed",
|
|
84
120
|
title: spec.title,
|
|
85
121
|
taskCount: spec.tasks.length,
|
|
86
122
|
ranks,
|
|
123
|
+
checks: {
|
|
124
|
+
writeSets: "checked",
|
|
125
|
+
decisionGates: "checked",
|
|
126
|
+
},
|
|
87
127
|
governanceProfile,
|
|
88
128
|
spineAudit: spineAudit
|
|
89
129
|
? {
|
|
@@ -97,5 +137,6 @@ export async function validateDagUseCase(input) {
|
|
|
97
137
|
...formatModelMatrixWarnings(warnings),
|
|
98
138
|
...formatGovernanceWarnings(governanceWarnings),
|
|
99
139
|
],
|
|
140
|
+
next: [`loop-agent run-dag --dag ${input.dagPath} --cwd ${input.repoRoot}`],
|
|
100
141
|
};
|
|
101
142
|
}
|
|
@@ -50,7 +50,7 @@ export function formatSubcommandUsageError(name, subcommands) {
|
|
|
50
50
|
return `usage: ${formatSubcommandUsage(name, subcommands)}`;
|
|
51
51
|
}
|
|
52
52
|
const DOCS_SUBCOMMANDS = ["audit", "archive"];
|
|
53
|
-
const INIT_SUBCOMMANDS = ["instructions", "doctor"];
|
|
53
|
+
const INIT_SUBCOMMANDS = ["instructions", "doctor", "check-update", "update"];
|
|
54
54
|
const EXAMPLES_SUBCOMMANDS = ["list", "show", "copy"];
|
|
55
55
|
const CLOSEOUT_SUBCOMMANDS = ["task"];
|
|
56
56
|
const PLAN_SUBCOMMANDS = ["list"];
|
|
@@ -145,7 +145,7 @@ export const COMMAND_DEFINITIONS = [
|
|
|
145
145
|
adapter: "none",
|
|
146
146
|
tier: "primary",
|
|
147
147
|
intent: "Initialize a target repository with loop-agent harness capabilities.",
|
|
148
|
-
usage: "init [instructions|doctor] [--profile full|minimal] [--merge]",
|
|
148
|
+
usage: "init [instructions|doctor|check-update|update] [--profile full|minimal] [--merge] [--json|--markdown] [--bootstrap-surface|--apply-safe]",
|
|
149
149
|
subcommands: [...INIT_SUBCOMMANDS],
|
|
150
150
|
handler: async ({ repoRoot, subcommand, rest }) => {
|
|
151
151
|
await runInit(repoRoot, [subcommand, ...rest].filter(Boolean));
|
package/dist/cli/program.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
1
2
|
import path from "node:path";
|
|
2
3
|
import { Command } from "commander";
|
|
3
4
|
import { resolveAdapter } from "../adapters/index.js";
|
|
@@ -50,10 +51,22 @@ import { handleStandaloneOneShotCommandError } from "../shared/one-shot-prompt-a
|
|
|
50
51
|
import { runDagDecisionInspect, runDagDecisionValidate, } from "../workflows/dag/decision-envelope.js";
|
|
51
52
|
import { runDagDoctor, runDagStatus } from "../workflows/dag/lifecycle.js";
|
|
52
53
|
import { CODE_AGENT_COMMAND_MANIFEST } from "./catalog.js";
|
|
53
|
-
function
|
|
54
|
+
function readPackageVersion() {
|
|
55
|
+
try {
|
|
56
|
+
const raw = readFileSync(new URL("../../package.json", import.meta.url), "utf-8");
|
|
57
|
+
const parsed = JSON.parse(raw);
|
|
58
|
+
if (typeof parsed.version === "string")
|
|
59
|
+
return parsed.version;
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// Fall back only if the package metadata is unexpectedly unavailable.
|
|
63
|
+
}
|
|
64
|
+
return "0.0.0";
|
|
65
|
+
}
|
|
66
|
+
function resolveRepoRootOnly(command, defaultRepoRoot, behavior) {
|
|
54
67
|
const options = collectGlobalOptions(command);
|
|
55
68
|
const repoRoot = path.resolve(options.repoRoot ?? defaultRepoRoot);
|
|
56
|
-
if (options.repoRoot)
|
|
69
|
+
if (options.repoRoot && !behavior?.quiet)
|
|
57
70
|
console.log(`[loop-agent] repo root: ${repoRoot}`);
|
|
58
71
|
return repoRoot;
|
|
59
72
|
}
|
|
@@ -341,6 +354,7 @@ function configureDagValidate(command) {
|
|
|
341
354
|
.option("--dag <path>", "DAG spec path")
|
|
342
355
|
.option("--strict-models", "fail on model routing warnings")
|
|
343
356
|
.option("--strict-governance", "fail on governance warnings")
|
|
357
|
+
.option("--strict-skills", "fail on missing, errored, truncated, or unresolved skill instructions")
|
|
344
358
|
.option("--forbid-executor <executor>", "forbid executor", (value, previous = []) => [
|
|
345
359
|
...previous,
|
|
346
360
|
value,
|
|
@@ -474,7 +488,9 @@ async function runPiPromptCommand(args) {
|
|
|
474
488
|
}
|
|
475
489
|
}
|
|
476
490
|
async function runInitCommand(args, command, defaultRepoRoot) {
|
|
477
|
-
|
|
491
|
+
const initArgs = commandArgsWithOptions(args, command);
|
|
492
|
+
const quiet = initArgs.includes("--json") || initArgs.includes("--markdown");
|
|
493
|
+
await runInit(resolveRepoRootOnly(command, defaultRepoRoot, { quiet }), initArgs);
|
|
478
494
|
}
|
|
479
495
|
async function runExamplesCommand(args, command, defaultRepoRoot) {
|
|
480
496
|
await runExamples(resolveRepoRootOnly(command, defaultRepoRoot), commandArgsWithOptions(args, command));
|
|
@@ -484,6 +500,7 @@ export function buildLoopAgentProgram(options) {
|
|
|
484
500
|
program
|
|
485
501
|
.name("loop-agent")
|
|
486
502
|
.description("Loop Agent task and DAG automation CLI")
|
|
503
|
+
.version(readPackageVersion(), "-V, --version", "display version")
|
|
487
504
|
.showHelpAfterError()
|
|
488
505
|
.option("--repo-root <path>", "repository root")
|
|
489
506
|
.option("--adapter <name>", "harness adapter name")
|
|
@@ -526,7 +543,10 @@ export function buildLoopAgentProgram(options) {
|
|
|
526
543
|
.option("--no-merge", "skip existing files")
|
|
527
544
|
.option("--provider <provider>", "model provider")
|
|
528
545
|
.option("--model <model>", "model name")
|
|
529
|
-
.option("--json", "print JSON")
|
|
546
|
+
.option("--json", "print JSON")
|
|
547
|
+
.option("--markdown", "print Markdown")
|
|
548
|
+
.option("--bootstrap-surface", "write an inferred .harness/init-surface.json baseline")
|
|
549
|
+
.option("--apply-safe", "apply deterministic safe init updates");
|
|
530
550
|
command.action(async (args, _options, actionCommand) => runInitCommand(args, actionCommand, options.defaultRepoRoot));
|
|
531
551
|
addStandaloneSubcommands(command, entry.subcommands ?? [], (args, actionCommand) => runInitCommand(args, actionCommand, options.defaultRepoRoot));
|
|
532
552
|
program.addCommand(command);
|