agentplane 0.2.22 → 0.2.24

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 (33) hide show
  1. package/assets/AGENTS.md +44 -30
  2. package/assets/agents/CODER.json +1 -1
  3. package/assets/agents/DOCS.json +1 -1
  4. package/assets/agents/ORCHESTRATOR.json +10 -9
  5. package/assets/agents/PLANNER.json +9 -4
  6. package/assets/agents/TESTER.json +1 -1
  7. package/dist/cli/command-guide.js +7 -7
  8. package/dist/cli/run-cli/commands/init.d.ts +1 -1
  9. package/dist/cli/run-cli/commands/init.d.ts.map +1 -1
  10. package/dist/cli/run-cli/commands/init.js +80 -37
  11. package/dist/cli/run-cli.test-helpers.d.ts.map +1 -1
  12. package/dist/cli/run-cli.test-helpers.js +4 -5
  13. package/dist/commands/hooks/index.d.ts.map +1 -1
  14. package/dist/commands/hooks/index.js +20 -6
  15. package/dist/commands/release/apply.command.d.ts.map +1 -1
  16. package/dist/commands/release/apply.command.js +18 -3
  17. package/dist/commands/release/plan.command.js +1 -1
  18. package/dist/commands/scenario/impl/commands.d.ts +18 -0
  19. package/dist/commands/scenario/impl/commands.d.ts.map +1 -1
  20. package/dist/commands/scenario/impl/commands.js +37 -5
  21. package/dist/commands/shared/pr-meta.d.ts +5 -0
  22. package/dist/commands/shared/pr-meta.d.ts.map +1 -1
  23. package/dist/commands/shared/pr-meta.js +11 -1
  24. package/dist/commands/task/new.d.ts.map +1 -1
  25. package/dist/commands/task/new.js +110 -7
  26. package/dist/commands/task/new.spec.d.ts.map +1 -1
  27. package/dist/commands/task/new.spec.js +2 -1
  28. package/dist/commands/task/shared.d.ts +5 -0
  29. package/dist/commands/task/shared.d.ts.map +1 -1
  30. package/dist/commands/task/shared.js +68 -4
  31. package/dist/commands/task/update.d.ts.map +1 -1
  32. package/dist/commands/task/update.js +6 -1
  33. package/package.json +2 -2
@@ -53,9 +53,14 @@ function hookScriptText(hook) {
53
53
  "#!/usr/bin/env sh",
54
54
  `# ${HOOK_MARKER} (do not edit)`,
55
55
  "set -e",
56
+ 'REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"',
57
+ 'SHIM="$REPO_ROOT/.agentplane/bin/agentplane"',
58
+ 'if [ -x "$SHIM" ]; then',
59
+ ' exec "$SHIM" hooks run ' + hook + ' "$@"',
60
+ "fi",
56
61
  "if ! command -v agentplane >/dev/null 2>&1; then",
57
- ' echo "agentplane hooks: agentplane not found in PATH" >&2',
58
- " exit 1",
62
+ ' echo "agentplane hooks: runner not found (PATH missing and shim unavailable)." >&2',
63
+ " exit 127",
59
64
  "fi",
60
65
  "exec agentplane hooks run " + hook + ' "$@"',
61
66
  "",
@@ -66,11 +71,20 @@ function shimScriptText() {
66
71
  "#!/usr/bin/env sh",
67
72
  `# ${SHIM_MARKER} (do not edit)`,
68
73
  "set -e",
69
- "if ! command -v agentplane >/dev/null 2>&1; then",
70
- ' echo "agentplane shim: agentplane not found in PATH" >&2',
71
- " exit 1",
74
+ 'SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"',
75
+ 'REPO_ROOT="$(dirname "$(dirname "$SCRIPT_DIR")")"',
76
+ 'LOCAL_BIN="$REPO_ROOT/packages/agentplane/bin/agentplane.js"',
77
+ 'if command -v node >/dev/null 2>&1 && [ -f "$LOCAL_BIN" ]; then',
78
+ ' exec node "$LOCAL_BIN" "$@"',
79
+ "fi",
80
+ "if command -v npx >/dev/null 2>&1; then",
81
+ ' exec npx --yes agentplane "$@"',
82
+ "fi",
83
+ "if command -v agentplane >/dev/null 2>&1; then",
84
+ ' exec agentplane "$@"',
72
85
  "fi",
73
- 'exec agentplane "$@"',
86
+ 'echo "agentplane shim: runner not found (need node+npx or agentplane in PATH)." >&2',
87
+ " exit 127",
74
88
  "",
75
89
  ].join("\n");
76
90
  }
@@ -1 +1 @@
1
- {"version":3,"file":"apply.command.d.ts","sourceRoot":"","sources":["../../../src/commands/release/apply.command.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAU1E,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,OAAO,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,iBAAiB,CAAC;AAsVnD,eAAO,MAAM,gBAAgB,EAAE,WAAW,CAAC,kBAAkB,CAwE5D,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,cAAc,CAAC,kBAAkB,CAuK9D,CAAC"}
1
+ {"version":3,"file":"apply.command.d.ts","sourceRoot":"","sources":["../../../src/commands/release/apply.command.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAU1E,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,OAAO,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,iBAAiB,CAAC;AAsVnD,eAAO,MAAM,gBAAgB,EAAE,WAAW,CAAC,kBAAkB,CAiF5D,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,cAAc,CAAC,kBAAkB,CA+K9D,CAAC"}
@@ -146,11 +146,11 @@ async function validateReleaseNotes(notesPath) {
146
146
  });
147
147
  }
148
148
  const bulletCount = content.split(/\r?\n/u).filter((line) => /^\s*[-*]\s+\S+/u.test(line)).length;
149
- if (bulletCount < 3) {
149
+ if (bulletCount < 1) {
150
150
  throw new CliError({
151
151
  exitCode: exitCodeForError("E_VALIDATION"),
152
152
  code: "E_VALIDATION",
153
- message: `Release notes must include at least 3 bullet points in ${notesPath}.`,
153
+ message: `Release notes must include at least one bullet point in ${notesPath}.`,
154
154
  });
155
155
  }
156
156
  if (/[\u0400-\u04FF]/u.test(content)) {
@@ -301,7 +301,8 @@ export const releaseApplySpec = {
301
301
  kind: "boolean",
302
302
  name: "push",
303
303
  default: false,
304
- description: "Push the release commit and tag to the remote (requires --yes).",
304
+ description: "Mandatory for real releases: push commit and tag so GitHub publish workflow can publish to npm " +
305
+ "(requires --yes). Local tests can skip with AGENTPLANE_RELEASE_DRY_RUN=1.",
305
306
  },
306
307
  {
307
308
  kind: "string",
@@ -325,6 +326,13 @@ export const releaseApplySpec = {
325
326
  };
326
327
  },
327
328
  validate: (p) => {
329
+ if (!p.push && process.env.AGENTPLANE_RELEASE_DRY_RUN !== "1") {
330
+ throw usageError({
331
+ spec: releaseApplySpec,
332
+ command: "release apply",
333
+ message: "Release publish is mandatory. Run `agentplane release apply --push --yes`.",
334
+ });
335
+ }
328
336
  if (p.push && p.yes !== true) {
329
337
  throw usageError({
330
338
  spec: releaseApplySpec,
@@ -356,6 +364,13 @@ export const releaseApplySpec = {
356
364
  ],
357
365
  };
358
366
  export const runReleaseApply = async (ctx, flags) => {
367
+ if (!flags.push && process.env.AGENTPLANE_RELEASE_DRY_RUN !== "1") {
368
+ throw usageError({
369
+ spec: releaseApplySpec,
370
+ command: "release apply",
371
+ message: "Release publish is mandatory. Run `agentplane release apply --push --yes`.",
372
+ });
373
+ }
359
374
  const resolved = await resolveProject({ cwd: ctx.cwd, rootOverride: ctx.rootOverride ?? null });
360
375
  const gitRoot = resolved.gitRoot;
361
376
  const planDir = flags.plan ? path.resolve(gitRoot, flags.plan) : await findLatestPlanDir(gitRoot);
@@ -104,7 +104,7 @@ function releaseInstructions(opts) {
104
104
  `Write English release notes as \`docs/releases/${opts.nextTag}.md\`.\n\n` +
105
105
  `Rules:\n` +
106
106
  `- Use human-readable bullets focused on outcomes and user-facing improvements.\n` +
107
- `- Minimum: 3 bullet points.\n` +
107
+ `- Include as many bullets as needed; do not enforce a fixed bullet count.\n` +
108
108
  `- Do not include Cyrillic.\n` +
109
109
  `- Use \`docs/releases/TEMPLATE.md\` as the structure.\n\n` +
110
110
  `Inputs:\n` +
@@ -1,4 +1,10 @@
1
1
  import { resolveProject } from "@agentplaneorg/core";
2
+ type RecipeToolRuntime = "node" | "bash";
3
+ type RecipeToolInvocation = {
4
+ command: string;
5
+ args: string[];
6
+ };
7
+ export declare function resolveRecipeToolInvocation(runtime: RecipeToolRuntime, entrypoint: string, args: string[]): RecipeToolInvocation;
2
8
  export declare function cmdScenarioListParsed(opts: {
3
9
  cwd: string;
4
10
  rootOverride?: string;
@@ -9,6 +15,17 @@ export declare function cmdScenarioInfoParsed(opts: {
9
15
  recipeId: string;
10
16
  scenarioId: string;
11
17
  }): Promise<number>;
18
+ export declare function executeRecipeTool(opts: {
19
+ runtime: RecipeToolRuntime;
20
+ entrypoint: string;
21
+ args: string[];
22
+ cwd: string;
23
+ env: Record<string, string>;
24
+ }): Promise<{
25
+ exitCode: number;
26
+ stdout: string;
27
+ stderr: string;
28
+ }>;
12
29
  export declare function cmdScenarioRunParsed(opts: {
13
30
  cwd: string;
14
31
  rootOverride?: string;
@@ -16,4 +33,5 @@ export declare function cmdScenarioRunParsed(opts: {
16
33
  scenarioId: string;
17
34
  resolved?: Awaited<ReturnType<typeof resolveProject>>;
18
35
  }): Promise<number>;
36
+ export {};
19
37
  //# sourceMappingURL=commands.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../../../src/commands/scenario/impl/commands.ts"],"names":[],"mappings":"AAKA,OAAO,EAAmB,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAgCtE,wBAAsB,qBAAqB,CAAC,IAAI,EAAE;IAChD,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,GAAG,OAAO,CAAC,MAAM,CAAC,CAoDlB;AAED,wBAAsB,qBAAqB,CAAC,IAAI,EAAE;IAChD,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,MAAM,CAAC,CAoElB;AAkCD,wBAAsB,oBAAoB,CAAC,IAAI,EAAE;IAC/C,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC,CAAC;CACvD,GAAG,OAAO,CAAC,MAAM,CAAC,CAqNlB"}
1
+ {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../../../src/commands/scenario/impl/commands.ts"],"names":[],"mappings":"AAKA,OAAO,EAAmB,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAgCtE,KAAK,iBAAiB,GAAG,MAAM,GAAG,MAAM,CAAC;AAEzC,KAAK,oBAAoB,GAAG;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB,CAAC;AAEF,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,iBAAiB,EAC1B,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EAAE,GACb,oBAAoB,CAQtB;AAED,wBAAsB,qBAAqB,CAAC,IAAI,EAAE;IAChD,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,GAAG,OAAO,CAAC,MAAM,CAAC,CAoDlB;AAED,wBAAsB,qBAAqB,CAAC,IAAI,EAAE;IAChD,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,MAAM,CAAC,CAoElB;AAED,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAC5C,OAAO,EAAE,iBAAiB,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC7B,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CA2ChE;AAMD,wBAAsB,oBAAoB,CAAC,IAAI,EAAE;IAC/C,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC,CAAC;CACvD,GAAG,OAAO,CAAC,MAAM,CAAC,CAuNlB"}
@@ -11,6 +11,15 @@ import { CliError } from "../../../shared/errors.js";
11
11
  import { RECIPES_DIR_NAME, RECIPES_SCENARIOS_DIR_NAME, RECIPES_SCENARIOS_INDEX_NAME, normalizeScenarioToolStep, readInstalledRecipesFile, readRecipeManifest, readScenarioDefinition, readScenarioIndex, resolveInstalledRecipeDir, resolveInstalledRecipesPath, resolveProjectRecipesCacheDir, } from "../../recipes.js";
12
12
  import { collectScenarioEnvKeys, getGitDiffSummary, redactArgs, writeScenarioReport, } from "./report.js";
13
13
  const execFileAsync = promisify(execFile);
14
+ export function resolveRecipeToolInvocation(runtime, entrypoint, args) {
15
+ if (runtime === "node") {
16
+ return { command: "node", args: [entrypoint, ...args] };
17
+ }
18
+ return {
19
+ command: "bash",
20
+ args: [entrypoint, ...args],
21
+ };
22
+ }
14
23
  export async function cmdScenarioListParsed(opts) {
15
24
  try {
16
25
  const installed = await readInstalledRecipesFile(resolveInstalledRecipesPath());
@@ -130,25 +139,46 @@ export async function cmdScenarioInfoParsed(opts) {
130
139
  throw mapCoreError(err, { command: "scenario info", root: opts.rootOverride ?? null });
131
140
  }
132
141
  }
133
- async function executeRecipeTool(opts) {
142
+ export async function executeRecipeTool(opts) {
143
+ const { command, args } = resolveRecipeToolInvocation(opts.runtime, opts.entrypoint, opts.args);
134
144
  try {
135
- const command = opts.runtime === "node" ? "node" : "bash";
136
- const { stdout, stderr } = await execFileAsync(command, [opts.entrypoint, ...opts.args], {
145
+ const { stdout, stderr } = await execFileAsync(command, args, {
137
146
  cwd: opts.cwd,
138
147
  env: opts.env,
139
148
  });
140
149
  return { exitCode: 0, stdout: String(stdout), stderr: String(stderr) };
141
150
  }
142
151
  catch (err) {
152
+ const rawCode = err && typeof err === "object" && "code" in err
153
+ ? err.code
154
+ : undefined;
155
+ const code = typeof rawCode === "number" ? rawCode : undefined;
156
+ const isCommandNotFound = rawCode === "ENOENT" || code === 127;
143
157
  let execErr = null;
144
158
  if (err && typeof err === "object") {
145
159
  execErr = err;
146
160
  }
147
161
  const exitCode = typeof execErr?.code === "number" ? execErr.code : 1;
162
+ let stderrText = String(execErr?.stderr ?? "");
163
+ const isMissingNodeEntrypoint = command === "node" &&
164
+ /Cannot find module/i.test(stderrText) &&
165
+ (stderrText.includes(opts.entrypoint) || stderrText.includes(`${opts.entrypoint}.js`));
166
+ if (isMissingNodeEntrypoint && !isCommandNotFound) {
167
+ const runtimeLabel = opts.entrypoint.replace(/\.(js|mjs|cjs)$/, "");
168
+ stderrText = `Runtime command not found: ${runtimeLabel}`;
169
+ return {
170
+ exitCode: 1,
171
+ stdout: "",
172
+ stderr: stderrText,
173
+ };
174
+ }
175
+ if (isCommandNotFound && !stderrText) {
176
+ stderrText = `Runtime command not found: ${command}`;
177
+ }
148
178
  return {
149
179
  exitCode,
150
180
  stdout: String(execErr?.stdout ?? ""),
151
- stderr: String(execErr?.stderr ?? ""),
181
+ stderr: stderrText,
152
182
  };
153
183
  }
154
184
  }
@@ -223,7 +253,9 @@ export async function cmdScenarioRunParsed(opts) {
223
253
  message: `Tool not found in recipe manifest: ${step.tool}`,
224
254
  });
225
255
  }
226
- const runtime = toolEntry.runtime === "node" || toolEntry.runtime === "bash" ? toolEntry.runtime : "";
256
+ const runtime = toolEntry.runtime === "node" || toolEntry.runtime === "bash"
257
+ ? toolEntry.runtime
258
+ : "";
227
259
  const entrypoint = typeof toolEntry.entrypoint === "string" ? toolEntry.entrypoint : "";
228
260
  if (!runtime || !entrypoint) {
229
261
  throw new CliError({
@@ -11,6 +11,11 @@ export type PrMeta = {
11
11
  command?: string;
12
12
  };
13
13
  };
14
+ export type ShellInvocation = {
15
+ command: string;
16
+ args: string[];
17
+ };
18
+ export declare function resolveShellInvocation(command: string): ShellInvocation;
14
19
  export declare function parsePrMeta(raw: string, taskId: string): PrMeta;
15
20
  export declare function extractLastVerifiedSha(logText: string): string | null;
16
21
  export declare function appendVerifyLog(logPath: string, header: string, content: string): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"pr-meta.d.ts","sourceRoot":"","sources":["../../../src/commands/shared/pr-meta.ts"],"names":[],"mappings":"AAaA,MAAM,MAAM,MAAM,GAAG;IACnB,cAAc,EAAE,CAAC,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,MAAM,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACrE,CAAC;AAEF,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAc/D;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQrE;AAED,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CAMf;AAED,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,GACV,OAAO,CAAC;IACT,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC,CAoBD"}
1
+ {"version":3,"file":"pr-meta.d.ts","sourceRoot":"","sources":["../../../src/commands/shared/pr-meta.ts"],"names":[],"mappings":"AAcA,MAAM,MAAM,MAAM,GAAG;IACnB,cAAc,EAAE,CAAC,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,MAAM,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACrE,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB,CAAC;AAEF,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe,CAQvE;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAc/D;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQrE;AAED,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CAMf;AAED,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,GACV,OAAO,CAAC;IACT,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC,CAqBD"}
@@ -1,4 +1,5 @@
1
1
  import { mkdir, writeFile } from "node:fs/promises";
2
+ import os from "node:os";
2
3
  import path from "node:path";
3
4
  import { execFileAsync } from "./git.js";
4
5
  function isRecord(value) {
@@ -7,6 +8,14 @@ function isRecord(value) {
7
8
  function isIsoDate(value) {
8
9
  return typeof value === "string" && !Number.isNaN(Date.parse(value));
9
10
  }
11
+ export function resolveShellInvocation(command) {
12
+ if (os.platform() === "win32") {
13
+ const rawComspec = process.env.ComSpec ?? process.env.COMSPEC;
14
+ const shellCommand = rawComspec && rawComspec !== "undefined" && rawComspec !== "null" ? rawComspec : "cmd.exe";
15
+ return { command: shellCommand, args: ["/d", "/s", "/c", command] };
16
+ }
17
+ return { command: "sh", args: ["-lc", command] };
18
+ }
10
19
  export function parsePrMeta(raw, taskId) {
11
20
  let parsed;
12
21
  try {
@@ -46,8 +55,9 @@ export async function appendVerifyLog(logPath, header, content) {
46
55
  await writeFile(logPath, `${lines.join("\n")}\n`, { flag: "a" });
47
56
  }
48
57
  export async function runShellCommand(command, cwd) {
58
+ const invocation = resolveShellInvocation(command);
49
59
  try {
50
- const { stdout, stderr } = await execFileAsync("sh", ["-lc", command], {
60
+ const { stdout, stderr } = await execFileAsync(invocation.command, invocation.args, {
51
61
  cwd,
52
62
  env: process.env,
53
63
  maxBuffer: 10 * 1024 * 1024,
@@ -1 +1 @@
1
- {"version":3,"file":"new.d.ts","sourceRoot":"","sources":["../../../src/commands/task/new.ts"],"names":[],"mappings":"AAIA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAQpF,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,KAAK,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;IAC5C,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC;AAyBF,wBAAsB,gBAAgB,CAAC,IAAI,EAAE;IAC3C,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,aAAa,CAAC;CACvB,GAAG,OAAO,CAAC,MAAM,CAAC,CAsElB"}
1
+ {"version":3,"file":"new.d.ts","sourceRoot":"","sources":["../../../src/commands/task/new.ts"],"names":[],"mappings":"AAMA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,2BAA2B,CAAC;AASpF,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,KAAK,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;IAC5C,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC;AAuIF,wBAAsB,gBAAgB,CAAC,IAAI,EAAE;IAC3C,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,aAAa,CAAC;CACvB,GAAG,OAAO,CAAC,MAAM,CAAC,CAgFlB"}
@@ -1,15 +1,112 @@
1
+ import { ensureDocSections, setMarkdownSection } from "@agentplaneorg/core";
1
2
  import { mapBackendError } from "../../cli/error-map.js";
2
3
  import { backendNotSupportedMessage, warnMessage } from "../../cli/output.js";
3
4
  import { CliError } from "../../shared/errors.js";
4
5
  import { loadCommandContext } from "../shared/task-backend.js";
5
- import { nowIso, requiresVerifyStepsByPrimary, resolvePrimaryTag, warnIfUnknownOwner, } from "./shared.js";
6
- function buildDefaultVerifyStepsDoc(opts) {
6
+ import { ensureTaskDependsOnGraphIsAcyclic, nowIso, requiresVerifyStepsByPrimary, resolvePrimaryTag, warnIfUnknownOwner, } from "./shared.js";
7
+ function dedupeTrimmed(values) {
8
+ const seen = new Set();
9
+ const out = [];
10
+ for (const raw of values) {
11
+ const value = String(raw ?? "").trim();
12
+ if (!value || seen.has(value))
13
+ continue;
14
+ seen.add(value);
15
+ out.push(value);
16
+ }
17
+ return out;
18
+ }
19
+ function sanitizeTaskNewParsed(p) {
20
+ const title = p.title.trim();
21
+ if (!title)
22
+ throw new CliError({
23
+ exitCode: 2,
24
+ code: "E_USAGE",
25
+ message: "Invalid value for --title: empty.",
26
+ });
27
+ const description = p.description.trim();
28
+ if (!description) {
29
+ throw new CliError({
30
+ exitCode: 2,
31
+ code: "E_USAGE",
32
+ message: "Invalid value for --description: empty.",
33
+ });
34
+ }
35
+ const owner = p.owner.trim();
36
+ if (!owner)
37
+ throw new CliError({
38
+ exitCode: 2,
39
+ code: "E_USAGE",
40
+ message: "Invalid value for --owner: empty.",
41
+ });
42
+ const tags = dedupeTrimmed(p.tags);
43
+ if (tags.length === 0) {
44
+ throw new CliError({
45
+ exitCode: 2,
46
+ code: "E_USAGE",
47
+ message: "Invalid value for --tag: provide at least one non-empty tag.",
48
+ });
49
+ }
50
+ const dependsOn = dedupeTrimmed(p.dependsOn);
51
+ const verify = dedupeTrimmed(p.verify);
52
+ return { ...p, title, description, owner, tags, dependsOn, verify };
53
+ }
54
+ function insertMarkdownSectionBefore(opts) {
55
+ const normalized = opts.body.replaceAll("\r\n", "\n");
56
+ if (normalized.includes(`## ${opts.section}`)) {
57
+ return setMarkdownSection(normalized, opts.section, opts.text);
58
+ }
59
+ const lines = normalized.split("\n");
60
+ const beforeHeading = `## ${opts.beforeSection}`;
61
+ const beforeIdx = lines.findIndex((line) => line.trim() === beforeHeading);
62
+ if (beforeIdx === -1)
63
+ return setMarkdownSection(normalized, opts.section, opts.text);
64
+ const textLines = opts.text.replaceAll("\r\n", "\n").split("\n");
65
+ const sectionLines = [`## ${opts.section}`, "", ...textLines, "", ""];
66
+ const out = [...lines.slice(0, beforeIdx), ...sectionLines, ...lines.slice(beforeIdx)];
67
+ return `${out.join("\n").trimEnd()}\n`;
68
+ }
69
+ function defaultTaskDoc(requiredSections) {
70
+ const verifyStepsTemplate = [
71
+ "<!-- TODO: FILL VERIFY STEPS -->",
72
+ "",
73
+ "### Scope",
74
+ "",
75
+ "",
76
+ "### Checks",
77
+ "",
78
+ "",
79
+ "### Evidence / Commands",
80
+ "",
81
+ "",
82
+ "### Pass criteria",
83
+ "",
84
+ "",
85
+ ].join("\n");
86
+ const verificationTemplate = [
87
+ "### Plan",
88
+ "",
89
+ "",
90
+ "### Results",
91
+ "",
92
+ "",
93
+ "<!-- BEGIN VERIFICATION RESULTS -->",
94
+ "<!-- END VERIFICATION RESULTS -->",
95
+ ].join("\n");
96
+ const baseDoc = ensureDocSections("", requiredSections);
97
+ const withVerifySteps = insertMarkdownSectionBefore({
98
+ body: baseDoc,
99
+ section: "Verify Steps",
100
+ text: verifyStepsTemplate,
101
+ beforeSection: "Verification",
102
+ });
103
+ return setMarkdownSection(withVerifySteps, "Verification", verificationTemplate);
104
+ }
105
+ function buildDefaultVerifyStepsSection(opts) {
7
106
  const checks = opts.verifyCommands.length > 0
8
107
  ? opts.verifyCommands.map((command) => `- \`${command}\``).join("\n")
9
108
  : "- Add explicit checks/commands for this task before approval.";
10
109
  return [
11
- "## Verify Steps",
12
- "",
13
110
  "### Scope",
14
111
  `- Primary tag: \`${opts.primary}\``,
15
112
  "",
@@ -25,7 +122,7 @@ function buildDefaultVerifyStepsDoc(opts) {
25
122
  ].join("\n");
26
123
  }
27
124
  export async function runTaskNewParsed(opts) {
28
- const p = opts.parsed;
125
+ const p = sanitizeTaskNewParsed(opts.parsed);
29
126
  try {
30
127
  const ctx = opts.ctx ??
31
128
  (await loadCommandContext({ cwd: opts.cwd, rootOverride: opts.rootOverride ?? null }));
@@ -53,6 +150,7 @@ export async function runTaskNewParsed(opts) {
53
150
  doc_updated_at: nowIso(),
54
151
  doc_updated_by: p.owner,
55
152
  id_source: "generated",
153
+ doc: defaultTaskDoc(ctx.config.tasks.doc.required_sections),
56
154
  };
57
155
  const spikeTag = (ctx.config.tasks.verify.spike_tag ?? "spike").trim().toLowerCase();
58
156
  const primary = resolvePrimaryTag(p.tags, ctx);
@@ -61,11 +159,16 @@ export async function runTaskNewParsed(opts) {
61
159
  }
62
160
  const requiresVerifySteps = requiresVerifyStepsByPrimary(p.tags, ctx.config);
63
161
  await warnIfUnknownOwner(ctx, p.owner);
162
+ await ensureTaskDependsOnGraphIsAcyclic({
163
+ backend: ctx.taskBackend,
164
+ taskId,
165
+ dependsOn: p.dependsOn,
166
+ });
64
167
  if (requiresVerifySteps) {
65
- task.doc = buildDefaultVerifyStepsDoc({
168
+ task.doc = setMarkdownSection(task.doc ?? "", "Verify Steps", buildDefaultVerifyStepsSection({
66
169
  primary: primary.primary,
67
170
  verifyCommands: p.verify,
68
- });
171
+ }));
69
172
  process.stderr.write(`${warnMessage("task requires Verify Steps by primary tag; seeded a default ## Verify Steps section in README (review and refine before approval/start)")}\n`);
70
173
  }
71
174
  const hasSpike = p.tags.some((tag) => tag.trim().toLowerCase() === spikeTag);
@@ -1 +1 @@
1
- {"version":3,"file":"new.spec.d.ts","sourceRoot":"","sources":["../../../src/commands/task/new.spec.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAE1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAG9C,eAAO,MAAM,WAAW,EAAE,WAAW,CAAC,aAAa,CA8ElD,CAAC"}
1
+ {"version":3,"file":"new.spec.d.ts","sourceRoot":"","sources":["../../../src/commands/task/new.spec.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAE1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAG9C,eAAO,MAAM,WAAW,EAAE,WAAW,CAAC,aAAa,CA+ElD,CAAC"}
@@ -3,7 +3,7 @@ export const taskNewSpec = {
3
3
  id: ["task", "new"],
4
4
  group: "Task",
5
5
  summary: "Create a new task (prints the generated task id).",
6
- description: "Creates a TODO task with doc_version=2 and writes it via the configured task backend.",
6
+ description: "Creates a TODO task with doc_version=2, seeds standard README sections, and writes it via the configured task backend.",
7
7
  options: [
8
8
  {
9
9
  kind: "string",
@@ -65,6 +65,7 @@ export const taskNewSpec = {
65
65
  },
66
66
  ],
67
67
  notes: [
68
+ "Task README scaffolding is applied automatically during creation.",
68
69
  "For verify-required primary tags, this command seeds a default ## Verify Steps section in README.",
69
70
  ],
70
71
  parse: (raw) => ({
@@ -37,6 +37,11 @@ export type DependencyState = {
37
37
  missing: string[];
38
38
  incomplete: string[];
39
39
  };
40
+ export declare function ensureTaskDependsOnGraphIsAcyclic(opts: {
41
+ backend: Pick<TaskBackend, "listTasks">;
42
+ taskId: string;
43
+ dependsOn: string[];
44
+ }): Promise<void>;
40
45
  export declare function resolveTaskDependencyState(task: TaskData, backend: Pick<TaskBackend, "getTask" | "getTasks">): Promise<DependencyState>;
41
46
  export declare function buildDependencyState(tasks: TaskData[]): Map<string, DependencyState>;
42
47
  export declare function formatTaskLine(task: TaskData, depState?: DependencyState): string;
@@ -1 +1 @@
1
- {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../../src/commands/task/shared.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAK9C,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAW5D,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,QAAQ,EAAE,KAAK,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAKjG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAEhE,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAExD,eAAO,MAAM,aAAa,+BAAsB,CAAC;AAiBjD,wBAAgB,MAAM,IAAI,MAAM,CAE/B;AAED,eAAO,MAAM,wBAAwB,qCAAqC,CAAC;AAE3E,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAiBjF;AAED,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAKvE;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAI/D;AAID,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAczD;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,CAKtD;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,OAAO,CAI9E;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,YAAY,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,aAAa,EAAE,OAAO,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,mBAAmB,EAAE,OAAO,CAAC;CAC9B,CAAC;AAsCF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,cAAc,GAAG,gBAAgB,GAAG,aAAa,CAkBzF;AAoCD,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,MAAM,EAAE,EACd,MAAM,EAAE,gBAAgB,GACvB,oBAAoB,CAgDtB;AAED,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAG9F;AAED,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAG/F;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,cAAc,GAAG,oBAAoB,CAE3F;AAED,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAe1F;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,GAAG,SAAS,EAAE,CAW7E;AAED,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAc3F;AAED,wBAAgB,qCAAqC,CACnD,IAAI,EAAE,QAAQ,EACd,MAAM,EAAE,gBAAgB,GACvB,IAAI,CAkBN;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB,CAAC;AAEF,wBAAsB,0BAA0B,CAC9C,IAAI,EAAE,QAAQ,EACd,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,SAAS,GAAG,UAAU,CAAC,GACjD,OAAO,CAAC,eAAe,CAAC,CAuB1B;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAqBpF;AAgBD,wBAAgB,cAAc,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC,EAAE,eAAe,GAAG,MAAM,CAgBjF;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAO1E;AAED,wBAAgB,6BAA6B,CAAC,IAAI,EAAE;IAClD,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;CAChB,GAAG,IAAI,CAUP;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE;IAC/C,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,gBAAgB,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,IAAI,CAmBP;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAgB7F;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAI5F;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE;IAC9C,MAAM,EAAE,gBAAgB,CAAC,sBAAsB,CAAC,CAAC;IACjD,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,IAAI,CA8BP;AAUD,wBAAgB,6BAA6B,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAK3F;AAED,wBAAsB,cAAc,CAClC,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,GACV,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAI5C;AAED,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAMlE;AAED,wBAAsB,4BAA4B,CAChD,GAAG,EAAE,cAAc,EACnB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,CAAC,CAGjB;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EAAE,EACd,IAAI,CAAC,EAAE;IAAE,UAAU,CAAC,EAAE,OAAO,CAAA;CAAE,GAC9B,eAAe,CA8EjB;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAiBnD"}
1
+ {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../../src/commands/task/shared.ts"],"names":[],"mappings":"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"}
@@ -204,10 +204,14 @@ export async function warnIfUnknownOwner(ctx, owner) {
204
204
  const ids = await listAgentIdsMemo(ctx);
205
205
  if (ids.length === 0)
206
206
  return;
207
- if (!ids.includes(trimmed)) {
208
- process.stderr.write(`${warnMessage(`unknown task owner id: ${trimmed} (not found under ${ctx.config.paths.agents_dir}; ` +
209
- `pick an existing agent id or create ${ctx.config.paths.agents_dir}/${trimmed}.json)`)}\n`);
210
- }
207
+ if (ids.includes(trimmed))
208
+ return;
209
+ throw new CliError({
210
+ exitCode: 3,
211
+ code: "E_VALIDATION",
212
+ message: `unknown task owner id: ${trimmed} (not found under ${ctx.config.paths.agents_dir}; ` +
213
+ `pick an existing agent id or create ${ctx.config.paths.agents_dir}/${trimmed}.json)`,
214
+ });
211
215
  }
212
216
  export function appendTaskEvent(task, event) {
213
217
  const existing = Array.isArray(task.events)
@@ -249,6 +253,66 @@ export function ensureVerificationSatisfiedIfRequired(task, config) {
249
253
  `(verification.state=${JSON.stringify(state)}; ${hint} or set agents.approvals.require_verify=false).`,
250
254
  });
251
255
  }
256
+ function hasDependsOnCycle(dependsOnMap) {
257
+ const visiting = new Set();
258
+ const visited = new Set();
259
+ const stack = [];
260
+ function dfs(taskId) {
261
+ if (visited.has(taskId))
262
+ return null;
263
+ if (visiting.has(taskId)) {
264
+ const start = stack.indexOf(taskId);
265
+ return start === -1 ? [taskId] : [...stack.slice(start), taskId];
266
+ }
267
+ visiting.add(taskId);
268
+ stack.push(taskId);
269
+ const deps = dependsOnMap.get(taskId) ?? [];
270
+ for (const depId of deps) {
271
+ const cycle = dfs(depId);
272
+ if (cycle)
273
+ return cycle;
274
+ }
275
+ stack.pop();
276
+ visiting.delete(taskId);
277
+ visited.add(taskId);
278
+ return null;
279
+ }
280
+ for (const taskId of dependsOnMap.keys()) {
281
+ const cycle = dfs(taskId);
282
+ if (cycle)
283
+ return cycle;
284
+ }
285
+ return null;
286
+ }
287
+ export async function ensureTaskDependsOnGraphIsAcyclic(opts) {
288
+ const nextDepends = dedupeStrings(opts.dependsOn);
289
+ if (nextDepends.includes(opts.taskId)) {
290
+ throw new CliError({
291
+ exitCode: 2,
292
+ code: "E_USAGE",
293
+ message: `depends_on cannot include task itself (${opts.taskId})`,
294
+ });
295
+ }
296
+ const allTasks = await opts.backend.listTasks();
297
+ const depMap = new Map();
298
+ for (const task of allTasks) {
299
+ const taskId = String(task.id || "").trim();
300
+ if (!taskId)
301
+ continue;
302
+ if (taskId === opts.taskId)
303
+ continue;
304
+ depMap.set(taskId, dedupeStrings(toStringArray(task.depends_on)));
305
+ }
306
+ depMap.set(opts.taskId, nextDepends);
307
+ const cycle = hasDependsOnCycle(depMap);
308
+ if (!cycle)
309
+ return;
310
+ throw new CliError({
311
+ exitCode: 2,
312
+ code: "E_USAGE",
313
+ message: `depends_on cycle detected: ${cycle.join(" -> ")}`,
314
+ });
315
+ }
252
316
  export async function resolveTaskDependencyState(task, backend) {
253
317
  const dependsOn = dedupeStrings(toStringArray(task.depends_on));
254
318
  if (dependsOn.length === 0) {
@@ -1 +1 @@
1
- {"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../../src/commands/task/update.ts"],"names":[],"mappings":"AAIA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAUpF,wBAAsB,aAAa,CAAC,IAAI,EAAE;IACxC,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,aAAa,EAAE,OAAO,CAAC;IACvB,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B,GAAG,OAAO,CAAC,MAAM,CAAC,CAwElB"}
1
+ {"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../../src/commands/task/update.ts"],"names":[],"mappings":"AAIA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAWpF,wBAAsB,aAAa,CAAC,IAAI,EAAE;IACxC,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,aAAa,EAAE,OAAO,CAAC;IACvB,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B,GAAG,OAAO,CAAC,MAAM,CAAC,CA6ElB"}
@@ -2,7 +2,7 @@ import { mapBackendError } from "../../cli/error-map.js";
2
2
  import { successMessage, unknownEntityMessage, warnMessage } from "../../cli/output.js";
3
3
  import { CliError } from "../../shared/errors.js";
4
4
  import { loadCommandContext } from "../shared/task-backend.js";
5
- import { dedupeStrings, requiresVerifyStepsByPrimary, readTaskTagPolicy, resolvePrimaryTag, toStringArray, warnIfUnknownOwner, } from "./shared.js";
5
+ import { dedupeStrings, ensureTaskDependsOnGraphIsAcyclic, requiresVerifyStepsByPrimary, readTaskTagPolicy, resolvePrimaryTag, toStringArray, warnIfUnknownOwner, } from "./shared.js";
6
6
  export async function cmdTaskUpdate(opts) {
7
7
  try {
8
8
  const ctx = opts.ctx ??
@@ -52,6 +52,11 @@ export async function cmdTaskUpdate(opts) {
52
52
  ? []
53
53
  : dedupeStrings(toStringArray(next.depends_on));
54
54
  const mergedDepends = dedupeStrings([...existingDepends, ...opts.dependsOn]);
55
+ await ensureTaskDependsOnGraphIsAcyclic({
56
+ backend: ctx.taskBackend,
57
+ taskId: opts.taskId,
58
+ dependsOn: mergedDepends,
59
+ });
55
60
  next.depends_on = mergedDepends;
56
61
  const existingVerify = opts.replaceVerify ? [] : dedupeStrings(toStringArray(next.verify));
57
62
  const mergedVerify = dedupeStrings([...existingVerify, ...opts.verify]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentplane",
3
- "version": "0.2.22",
3
+ "version": "0.2.24",
4
4
  "description": "Agent Plane CLI for task workflows, recipes, and project automation.",
5
5
  "keywords": [
6
6
  "agentplane",
@@ -55,7 +55,7 @@
55
55
  "prepublishOnly": "node ../../scripts/enforce-github-publish.mjs && npm run prepack"
56
56
  },
57
57
  "dependencies": {
58
- "@agentplaneorg/core": "0.2.22",
58
+ "@agentplaneorg/core": "0.2.24",
59
59
  "yauzl": "^2.10.0"
60
60
  },
61
61
  "devDependencies": {