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.
- package/assets/AGENTS.md +44 -30
- package/assets/agents/CODER.json +1 -1
- package/assets/agents/DOCS.json +1 -1
- package/assets/agents/ORCHESTRATOR.json +10 -9
- package/assets/agents/PLANNER.json +9 -4
- package/assets/agents/TESTER.json +1 -1
- package/dist/cli/command-guide.js +7 -7
- package/dist/cli/run-cli/commands/init.d.ts +1 -1
- package/dist/cli/run-cli/commands/init.d.ts.map +1 -1
- package/dist/cli/run-cli/commands/init.js +80 -37
- package/dist/cli/run-cli.test-helpers.d.ts.map +1 -1
- package/dist/cli/run-cli.test-helpers.js +4 -5
- package/dist/commands/hooks/index.d.ts.map +1 -1
- package/dist/commands/hooks/index.js +20 -6
- package/dist/commands/release/apply.command.d.ts.map +1 -1
- package/dist/commands/release/apply.command.js +18 -3
- package/dist/commands/release/plan.command.js +1 -1
- package/dist/commands/scenario/impl/commands.d.ts +18 -0
- package/dist/commands/scenario/impl/commands.d.ts.map +1 -1
- package/dist/commands/scenario/impl/commands.js +37 -5
- package/dist/commands/shared/pr-meta.d.ts +5 -0
- package/dist/commands/shared/pr-meta.d.ts.map +1 -1
- package/dist/commands/shared/pr-meta.js +11 -1
- package/dist/commands/task/new.d.ts.map +1 -1
- package/dist/commands/task/new.js +110 -7
- package/dist/commands/task/new.spec.d.ts.map +1 -1
- package/dist/commands/task/new.spec.js +2 -1
- package/dist/commands/task/shared.d.ts +5 -0
- package/dist/commands/task/shared.d.ts.map +1 -1
- package/dist/commands/task/shared.js +68 -4
- package/dist/commands/task/update.d.ts.map +1 -1
- package/dist/commands/task/update.js +6 -1
- 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:
|
|
58
|
-
" exit
|
|
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
|
-
"
|
|
70
|
-
'
|
|
71
|
-
"
|
|
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
|
-
'
|
|
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,
|
|
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 <
|
|
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
|
|
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: "
|
|
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
|
-
`-
|
|
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;
|
|
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
|
|
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:
|
|
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"
|
|
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":"
|
|
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(
|
|
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":"
|
|
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
|
|
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 =
|
|
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,
|
|
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,
|
|
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,CAgB1F;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;AAkCF,wBAAsB,iCAAiC,CAAC,IAAI,EAAE;IAC5D,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB,GAAG,OAAO,CAAC,IAAI,CAAC,CA2BhB;AAED,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"}
|
|
@@ -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 (
|
|
208
|
-
|
|
209
|
-
|
|
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;
|
|
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.
|
|
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.
|
|
58
|
+
"@agentplaneorg/core": "0.2.24",
|
|
59
59
|
"yauzl": "^2.10.0"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|