@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.
Files changed (77) hide show
  1. package/AGENTS.md +43 -24
  2. package/CHANGELOG.md +72 -44
  3. package/README.md +177 -164
  4. package/bin/loop-agent.js +21 -21
  5. package/dist/application/dag/args.js +6 -0
  6. package/dist/application/dag/generate-task-dag.js +5 -3
  7. package/dist/application/dag/run-dag.js +17 -1
  8. package/dist/application/dag/validate-dag.js +41 -0
  9. package/dist/cli/command-definitions.js +2 -2
  10. package/dist/cli/program.js +24 -4
  11. package/dist/commands/init.js +1036 -461
  12. package/dist/workflows/dag/dynamic-runtime/loop-until.js +2 -1
  13. package/dist/workflows/dag/dynamic-runtime/map.js +1 -0
  14. package/dist/workflows/dag/failure-routing.js +82 -0
  15. package/dist/workflows/dag/init-hybrid.js +3 -3
  16. package/dist/workflows/dag/lifecycle.js +95 -3
  17. package/dist/workflows/dag/report.js +73 -1
  18. package/dist/workflows/dag/skills.js +3 -3
  19. package/dist/workflows/dag/types.js +2 -0
  20. package/dist/workflows/dynamic/compile.js +11 -0
  21. package/dist/workflows/dynamic/spec.js +1 -0
  22. package/docs/README.md +50 -45
  23. package/docs/agent-dag-recovery-playbook.md +32 -6
  24. package/docs/agent-dag-runner.md +19 -17
  25. package/docs/architecture/runtime-boundaries.md +1 -1
  26. package/docs/cursor-executor-usage.md +5 -5
  27. package/docs/decisions/README.md +2 -2
  28. package/docs/design/README.md +24 -24
  29. package/docs/development-principles.md +50 -50
  30. package/docs/dynamic-workflow-dag-engine-roadmap.md +6 -6
  31. package/docs/exec-plans/README.md +4 -4
  32. package/docs/exec-plans/active/README.md +7 -9
  33. package/docs/exec-plans/completed/README.md +10 -8
  34. package/docs/feature-workflow.md +111 -109
  35. package/docs/harness-methodology-verification.md +18 -18
  36. package/docs/init-surface.manifest.json +175 -0
  37. package/docs/loop-agent-harness.md +36 -36
  38. package/docs/production-readiness.md +96 -0
  39. package/docs/progress/README.md +2 -2
  40. package/docs/reports/README.md +4 -2
  41. package/docs/skills/README.md +6 -0
  42. package/docs/skills/vetted-skill-registry.md +26 -0
  43. package/docs/templates/agent-dag-decision-gate-dogfood-report.md +1 -1
  44. package/docs/templates/agent-dag-process-supervisor.prompt.md +2 -2
  45. package/docs/templates/agent-dag-report.schema.json +33 -2
  46. package/docs/templates/agent-dag-review-verdict.prompt.md +1 -1
  47. package/docs/templates/agent-dag.base.json +195 -195
  48. package/docs/templates/agent-dag.final-verification.json +190 -190
  49. package/docs/templates/agent-dag.schema.json +17 -17
  50. package/docs/templates/agent-dag.supervised-implementation.json +500 -500
  51. package/docs/templates/hybrid-dag.json +193 -193
  52. package/docs/templates/init-evolution-review.md +33 -0
  53. package/docs/templates/production-readiness-checklist.md +57 -0
  54. package/docs/templates/progress-log.md +7 -7
  55. package/docs/templates/project-start-checklist.md +8 -8
  56. package/docs/templates/qa-report.md +17 -11
  57. package/docs/templates/sprint-contract.md +19 -19
  58. package/docs/verification-matrix.md +37 -26
  59. package/examples/example-dag.json +51 -51
  60. package/examples/hybrid-loop-agent-dag.json +194 -194
  61. package/harness.json +10 -8
  62. package/package.json +60 -58
  63. package/skills/code-review-core/SKILL.md +20 -0
  64. package/skills/codebase-scout/SKILL.md +19 -0
  65. package/skills/init-capability-evolution/SKILL.md +69 -0
  66. package/skills/loop-agent/SKILL.md +35 -35
  67. package/skills/loop-agent/references/command-reference.md +125 -65
  68. package/skills/loop-agent/references/harness-policy.md +30 -30
  69. package/skills/loop-agent/references/hybrid-dag.md +30 -30
  70. package/skills/loop-agent/references/model-routing.md +1 -1
  71. package/skills/loop-agent/references/orchestrator-and-interventions.md +1 -1
  72. package/skills/loop-agent/references/pi-prompt.md +9 -9
  73. package/skills/loop-agent/references/post-implementation-and-patterns.md +7 -7
  74. package/skills/loop-agent/references/task-workflow.md +19 -19
  75. package/skills/loop-agent/references/verification-and-failure-handling.md +36 -0
  76. package/skills/test-driven-development/SKILL.md +20 -0
  77. 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
- `npm run dev -- dag validate --dag ${outputPath}`,
62
- `npm run dev -- run-dag --dag ${outputPath} --cwd ${cwd}`,
63
- `npm run dev -- dag run-task ${taskId} --execute --cwd ${cwd}`,
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 summary;
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));
@@ -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 resolveRepoRootOnly(command, defaultRepoRoot) {
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
- await runInit(resolveRepoRootOnly(command, defaultRepoRoot), commandArgsWithOptions(args, command));
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);