agentplane 0.2.19 → 0.2.21
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 +4 -3
- package/bin/agentplane.js +24 -0
- package/dist/backends/task-backend/load.d.ts +2 -0
- package/dist/backends/task-backend/load.d.ts.map +1 -1
- package/dist/backends/task-backend/load.js +38 -18
- package/dist/cli/command-guide.d.ts.map +1 -1
- package/dist/cli/command-guide.js +12 -11
- package/dist/cli/run-cli/command-catalog.d.ts +1 -1
- package/dist/cli/run-cli/command-catalog.d.ts.map +1 -1
- package/dist/cli/run-cli/command-catalog.js +12 -1
- package/dist/cli/run-cli/commands/core.d.ts +9 -1
- package/dist/cli/run-cli/commands/core.d.ts.map +1 -1
- package/dist/cli/run-cli/commands/core.js +268 -8
- package/dist/cli/run-cli/commands/init/write-config.d.ts.map +1 -1
- package/dist/cli/run-cli/commands/init/write-config.js +2 -0
- package/dist/cli/run-cli/commands/init/write-gitignore.d.ts.map +1 -1
- package/dist/cli/run-cli/commands/init/write-gitignore.js +3 -18
- package/dist/cli/run-cli/commands/init.d.ts +1 -0
- package/dist/cli/run-cli/commands/init.d.ts.map +1 -1
- package/dist/cli/run-cli/commands/init.js +55 -30
- package/dist/cli/run-cli.d.ts.map +1 -1
- package/dist/cli/run-cli.js +85 -14
- package/dist/commands/commit.command.d.ts.map +1 -1
- package/dist/commands/commit.command.js +2 -0
- package/dist/commands/commit.spec.d.ts +2 -0
- package/dist/commands/commit.spec.d.ts.map +1 -1
- package/dist/commands/commit.spec.js +26 -0
- package/dist/commands/doctor.run.d.ts.map +1 -1
- package/dist/commands/doctor.run.js +15 -6
- package/dist/commands/finish.run.d.ts.map +1 -1
- package/dist/commands/finish.run.js +2 -0
- package/dist/commands/finish.spec.d.ts +2 -0
- package/dist/commands/finish.spec.d.ts.map +1 -1
- package/dist/commands/finish.spec.js +36 -0
- package/dist/commands/guard/impl/allow.d.ts.map +1 -1
- package/dist/commands/guard/impl/allow.js +33 -7
- package/dist/commands/guard/impl/commands.d.ts +2 -0
- package/dist/commands/guard/impl/commands.d.ts.map +1 -1
- package/dist/commands/guard/impl/commands.js +24 -4
- package/dist/commands/guard/impl/comment-commit.d.ts +1 -0
- package/dist/commands/guard/impl/comment-commit.d.ts.map +1 -1
- package/dist/commands/guard/impl/comment-commit.js +16 -24
- package/dist/commands/release/apply.command.d.ts.map +1 -1
- package/dist/commands/release/apply.command.js +51 -3
- package/dist/commands/release/plan.command.d.ts.map +1 -1
- package/dist/commands/release/plan.command.js +25 -1
- package/dist/commands/shared/task-backend.d.ts +3 -0
- package/dist/commands/shared/task-backend.d.ts.map +1 -1
- package/dist/commands/shared/task-backend.js +4 -1
- package/dist/commands/task/block.d.ts.map +1 -1
- package/dist/commands/task/block.js +12 -9
- package/dist/commands/task/close-duplicate.command.d.ts +14 -0
- package/dist/commands/task/close-duplicate.command.d.ts.map +1 -0
- package/dist/commands/task/close-duplicate.command.js +102 -0
- package/dist/commands/task/close-duplicate.d.ts +14 -0
- package/dist/commands/task/close-duplicate.d.ts.map +1 -0
- package/dist/commands/task/close-duplicate.js +90 -0
- package/dist/commands/task/close-noop.command.d.ts +14 -0
- package/dist/commands/task/close-noop.command.d.ts.map +1 -0
- package/dist/commands/task/close-noop.command.js +77 -0
- package/dist/commands/task/close-noop.d.ts +13 -0
- package/dist/commands/task/close-noop.d.ts.map +1 -0
- package/dist/commands/task/close-noop.js +77 -0
- package/dist/commands/task/finish.d.ts +2 -0
- package/dist/commands/task/finish.d.ts.map +1 -1
- package/dist/commands/task/finish.js +52 -10
- package/dist/commands/task/index.d.ts +3 -0
- package/dist/commands/task/index.d.ts.map +1 -1
- package/dist/commands/task/index.js +3 -0
- package/dist/commands/task/new.d.ts.map +1 -1
- package/dist/commands/task/new.js +34 -6
- package/dist/commands/task/new.spec.js +1 -1
- package/dist/commands/task/plan.d.ts.map +1 -1
- package/dist/commands/task/plan.js +2 -3
- package/dist/commands/task/set-status.d.ts.map +1 -1
- package/dist/commands/task/set-status.js +12 -9
- package/dist/commands/task/shared.d.ts +19 -0
- package/dist/commands/task/shared.d.ts.map +1 -1
- package/dist/commands/task/shared.js +137 -0
- package/dist/commands/task/start-ready.command.d.ts +14 -0
- package/dist/commands/task/start-ready.command.d.ts.map +1 -0
- package/dist/commands/task/start-ready.command.js +77 -0
- package/dist/commands/task/start-ready.d.ts +13 -0
- package/dist/commands/task/start-ready.d.ts.map +1 -0
- package/dist/commands/task/start-ready.js +46 -0
- package/dist/commands/task/start.d.ts.map +1 -1
- package/dist/commands/task/start.js +13 -11
- package/dist/commands/task/update.command.d.ts +1 -0
- package/dist/commands/task/update.command.d.ts.map +1 -1
- package/dist/commands/task/update.command.js +8 -0
- package/dist/commands/task/update.d.ts +1 -0
- package/dist/commands/task/update.d.ts.map +1 -1
- package/dist/commands/task/update.js +19 -3
- package/dist/shared/errors.d.ts +9 -1
- package/dist/shared/errors.d.ts.map +1 -1
- package/dist/shared/errors.js +3 -1
- package/dist/shared/runtime-artifacts.d.ts +3 -0
- package/dist/shared/runtime-artifacts.d.ts.map +1 -0
- package/dist/shared/runtime-artifacts.js +18 -0
- package/dist/usecases/context/resolve-context.d.ts +3 -0
- package/dist/usecases/context/resolve-context.d.ts.map +1 -1
- package/dist/usecases/context/resolve-context.js +6 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../../../src/commands/guard/impl/commands.ts"],"names":[],"mappings":"AAGA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../../../src/commands/guard/impl/commands.ts"],"names":[],"mappings":"AAGA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAOvF,OAAO,EAAoB,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAyDxE,wBAAsB,aAAa,CAAC,IAAI,EAAE;IACxC,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,OAAO,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CAsBlB;AAED,wBAAsB,oBAAoB,CAAC,IAAI,EAAE;IAC/C,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC;CAC1B,GAAG,OAAO,CAAC,MAAM,CAAC,CAyBlB;AAED,wBAAsB,cAAc,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,CAS9E;AAED,wBAAsB,SAAS,CAAC,IAAI,EAAE;IACpC,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,OAAO,CAAC;IACtB,KAAK,EAAE,OAAO,CAAC;IACf,kBAAkB,EAAE,OAAO,CAAC;IAC5B,cAAc,EAAE,OAAO,CAAC;CACzB,GAAG,OAAO,CAAC,MAAM,CAAC,CAyJlB"}
|
|
@@ -3,6 +3,7 @@ import { successMessage } from "../../../cli/output.js";
|
|
|
3
3
|
import { CliError } from "../../../shared/errors.js";
|
|
4
4
|
import { loadCommandContext } from "../../shared/task-backend.js";
|
|
5
5
|
import { loadTaskFromContext } from "../../shared/task-backend.js";
|
|
6
|
+
import { execFileAsync, gitEnv } from "../../shared/git.js";
|
|
6
7
|
import { suggestAllowPrefixes } from "./allow.js";
|
|
7
8
|
import { buildCloseCommitMessage, taskReadmePathForTask } from "./close-message.js";
|
|
8
9
|
import { buildGitCommitEnv } from "./env.js";
|
|
@@ -126,13 +127,22 @@ export async function cmdCommit(opts) {
|
|
|
126
127
|
if (opts.close) {
|
|
127
128
|
const ctx = opts.ctx ??
|
|
128
129
|
(await loadCommandContext({ cwd: opts.cwd, rootOverride: opts.rootOverride ?? null }));
|
|
129
|
-
// Make the close commit deterministic:
|
|
130
|
-
|
|
131
|
-
if (staged.length > 0) {
|
|
130
|
+
// Make the close commit deterministic: start from a clean index unless --unstage-others is used.
|
|
131
|
+
let staged = await ctx.git.statusStagedPaths();
|
|
132
|
+
if (staged.length > 0 && opts.closeUnstageOthers) {
|
|
133
|
+
if (!opts.closeCheckOnly) {
|
|
134
|
+
await execFileAsync("git", ["restore", "--staged", "--", "."], {
|
|
135
|
+
cwd: ctx.resolvedProject.gitRoot,
|
|
136
|
+
env: gitEnv(),
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
staged = opts.closeCheckOnly ? staged : await ctx.git.statusStagedPaths();
|
|
140
|
+
}
|
|
141
|
+
if (staged.length > 0 && !opts.closeUnstageOthers) {
|
|
132
142
|
throw new CliError({
|
|
133
143
|
exitCode: 5,
|
|
134
144
|
code: "E_GIT",
|
|
135
|
-
message: "Staged files exist (close commit requires an empty index)",
|
|
145
|
+
message: "Staged files exist (close commit requires an empty index; rerun with --unstage-others to auto-unstage).",
|
|
136
146
|
});
|
|
137
147
|
}
|
|
138
148
|
const task = await loadTaskFromContext({ ctx, taskId: opts.taskId });
|
|
@@ -145,6 +155,16 @@ export async function cmdCommit(opts) {
|
|
|
145
155
|
const readmeRel = readmeAbs.startsWith(ctx.resolvedProject.gitRoot)
|
|
146
156
|
? readmeAbs.slice(ctx.resolvedProject.gitRoot.length + 1)
|
|
147
157
|
: readmeAbs;
|
|
158
|
+
if (opts.closeCheckOnly) {
|
|
159
|
+
if (!opts.quiet) {
|
|
160
|
+
const stagedCount = staged.length;
|
|
161
|
+
const suffix = stagedCount > 0 && opts.closeUnstageOthers
|
|
162
|
+
? `; would unstage ${stagedCount} path(s)`
|
|
163
|
+
: "";
|
|
164
|
+
process.stdout.write(`${successMessage("close preflight", opts.taskId, `subject=${msg.subject}${suffix}`)}\n`);
|
|
165
|
+
}
|
|
166
|
+
return 0;
|
|
167
|
+
}
|
|
148
168
|
await ctx.git.stage([readmeRel]);
|
|
149
169
|
// Close commits should not require manual --allow flags:
|
|
150
170
|
// the command stages exactly one task README under workflow_dir.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"comment-commit.d.ts","sourceRoot":"","sources":["../../../../src/commands/guard/impl/comment-commit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAS/E,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"comment-commit.d.ts","sourceRoot":"","sources":["../../../../src/commands/guard/impl/comment-commit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAS/E,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAiEvF,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAC5C,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB,YAAY,EAAE,OAAO,CAAC;IACtB,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,gBAAgB,CAAC;CAC1B,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CA+F/D"}
|
|
@@ -8,24 +8,6 @@ import { buildGitCommitEnv } from "./env.js";
|
|
|
8
8
|
import { stageAllowlist, suggestAllowPrefixes } from "./allow.js";
|
|
9
9
|
import { guardCommitCheck } from "./policy.js";
|
|
10
10
|
function deriveCommitMessageFromComment(opts) {
|
|
11
|
-
const raw = (opts.formattedComment ?? formatCommentBodyForCommit(opts.body, opts.config))
|
|
12
|
-
.trim()
|
|
13
|
-
.replaceAll(/\s+/g, " ");
|
|
14
|
-
if (!raw) {
|
|
15
|
-
throw new CliError({
|
|
16
|
-
exitCode: 2,
|
|
17
|
-
code: "E_USAGE",
|
|
18
|
-
message: "Comment body is required to build a commit message from the task comment",
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
const summary = raw.replace(/^(start|blocked|verified):\s*/i, "").trim();
|
|
22
|
-
if (!summary) {
|
|
23
|
-
throw new CliError({
|
|
24
|
-
exitCode: 2,
|
|
25
|
-
code: "E_USAGE",
|
|
26
|
-
message: "Comment body is required to build a commit message from the task comment",
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
11
|
const prefix = opts.emoji.trim();
|
|
30
12
|
if (!prefix) {
|
|
31
13
|
throw new CliError({
|
|
@@ -42,15 +24,25 @@ function deriveCommitMessageFromComment(opts) {
|
|
|
42
24
|
message: invalidValueMessage("task id", opts.taskId, "valid task id"),
|
|
43
25
|
});
|
|
44
26
|
}
|
|
45
|
-
|
|
27
|
+
const primary = opts.primaryTag.trim().toLowerCase();
|
|
28
|
+
if (!primary) {
|
|
29
|
+
throw new CliError({
|
|
30
|
+
exitCode: 2,
|
|
31
|
+
code: "E_USAGE",
|
|
32
|
+
message: "Primary tag is required when deriving commit messages from task comments",
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
const status = (opts.statusTo?.trim().toLowerCase() ?? "status-transition").replaceAll(/\s+/g, "-");
|
|
36
|
+
return `${prefix} ${suffix} ${primary}: ${status}`;
|
|
46
37
|
}
|
|
47
38
|
function deriveCommitBodyFromComment(opts) {
|
|
48
39
|
const lines = [
|
|
49
40
|
`Task: ${opts.taskId}`,
|
|
41
|
+
`Primary: ${opts.primaryTag}`,
|
|
50
42
|
...(opts.executorAgent ? [`Agent: ${opts.executorAgent}`] : []),
|
|
51
43
|
...(opts.author ? [`Author: ${opts.author}`] : []),
|
|
52
|
-
...(opts.
|
|
53
|
-
`Comment: ${normalizeCommentBodyForCommit(opts.formattedComment
|
|
44
|
+
...(opts.statusTo ? [`Status: ${opts.statusTo}`] : []),
|
|
45
|
+
`Comment: ${normalizeCommentBodyForCommit(opts.formattedComment ?? opts.commentBody)}`,
|
|
54
46
|
];
|
|
55
47
|
return lines.join("\n").trimEnd();
|
|
56
48
|
}
|
|
@@ -88,14 +80,14 @@ export async function commitFromComment(opts) {
|
|
|
88
80
|
});
|
|
89
81
|
const message = deriveCommitMessageFromComment({
|
|
90
82
|
taskId: opts.taskId,
|
|
91
|
-
|
|
83
|
+
primaryTag: opts.primaryTag,
|
|
84
|
+
statusTo: opts.statusTo,
|
|
92
85
|
emoji: opts.emoji,
|
|
93
|
-
formattedComment: opts.formattedComment,
|
|
94
|
-
config: opts.config,
|
|
95
86
|
});
|
|
96
87
|
const formattedComment = opts.formattedComment ?? formatCommentBodyForCommit(opts.commentBody, opts.config);
|
|
97
88
|
const body = deriveCommitBodyFromComment({
|
|
98
89
|
taskId: opts.taskId,
|
|
90
|
+
primaryTag: opts.primaryTag,
|
|
99
91
|
executorAgent: opts.executorAgent,
|
|
100
92
|
author: opts.author,
|
|
101
93
|
statusFrom: opts.statusFrom,
|
|
@@ -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;
|
|
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"}
|
|
@@ -82,6 +82,20 @@ async function readPackageVersion(pkgJsonPath) {
|
|
|
82
82
|
}
|
|
83
83
|
return version;
|
|
84
84
|
}
|
|
85
|
+
async function readCoreDependencyVersion(pkgJsonPath) {
|
|
86
|
+
const raw = JSON.parse(await readFile(pkgJsonPath, "utf8"));
|
|
87
|
+
const value = raw.dependencies?.["@agentplaneorg/core"];
|
|
88
|
+
const version = typeof value === "string" ? value.trim() : "";
|
|
89
|
+
if (!version) {
|
|
90
|
+
throw new CliError({
|
|
91
|
+
exitCode: exitCodeForError("E_VALIDATION"),
|
|
92
|
+
code: "E_VALIDATION",
|
|
93
|
+
message: `Missing dependency @agentplaneorg/core in ${pkgJsonPath}. ` +
|
|
94
|
+
"Release parity requires packages/agentplane to pin @agentplaneorg/core to the same version.",
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
return version;
|
|
98
|
+
}
|
|
85
99
|
async function replacePackageVersionInFile(pkgJsonPath, nextVersion) {
|
|
86
100
|
const text = await readFile(pkgJsonPath, "utf8");
|
|
87
101
|
const replaced = text.replace(/"version"\s*:\s*"[^"]*"/u, `"version": "${nextVersion}"`);
|
|
@@ -94,6 +108,27 @@ async function replacePackageVersionInFile(pkgJsonPath, nextVersion) {
|
|
|
94
108
|
}
|
|
95
109
|
await writeFile(pkgJsonPath, replaced, "utf8");
|
|
96
110
|
}
|
|
111
|
+
async function replaceAgentplanePackageMetadata(pkgJsonPath, nextVersion) {
|
|
112
|
+
const text = await readFile(pkgJsonPath, "utf8");
|
|
113
|
+
const withVersion = text.replace(/"version"\s*:\s*"[^"]*"/u, `"version": "${nextVersion}"`);
|
|
114
|
+
if (withVersion === text) {
|
|
115
|
+
throw new CliError({
|
|
116
|
+
exitCode: exitCodeForError("E_VALIDATION"),
|
|
117
|
+
code: "E_VALIDATION",
|
|
118
|
+
message: `Failed to update version in ${pkgJsonPath} (missing "version" field).`,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
const withDependency = withVersion.replace(/("@agentplaneorg\/core"\s*:\s*")[^"]*(")/u, `$1${nextVersion}$2`);
|
|
122
|
+
if (withDependency === withVersion) {
|
|
123
|
+
throw new CliError({
|
|
124
|
+
exitCode: exitCodeForError("E_VALIDATION"),
|
|
125
|
+
code: "E_VALIDATION",
|
|
126
|
+
message: `Failed to update @agentplaneorg/core dependency in ${pkgJsonPath}. ` +
|
|
127
|
+
"Ensure packages/agentplane/package.json declares this dependency.",
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
await writeFile(pkgJsonPath, withDependency, "utf8");
|
|
131
|
+
}
|
|
97
132
|
function cleanHookEnv() {
|
|
98
133
|
const env = { ...gitEnv() };
|
|
99
134
|
delete env.AGENTPLANE_TASK_ID;
|
|
@@ -359,9 +394,10 @@ export const runReleaseApply = async (ctx, flags) => {
|
|
|
359
394
|
await validateReleaseNotes(notesPath);
|
|
360
395
|
const corePkgPath = path.join(gitRoot, "packages", "core", "package.json");
|
|
361
396
|
const agentplanePkgPath = path.join(gitRoot, "packages", "agentplane", "package.json");
|
|
362
|
-
const [coreVersion, agentplaneVersion] = await Promise.all([
|
|
397
|
+
const [coreVersion, agentplaneVersion, coreDependencyVersion] = await Promise.all([
|
|
363
398
|
readPackageVersion(corePkgPath),
|
|
364
399
|
readPackageVersion(agentplanePkgPath),
|
|
400
|
+
readCoreDependencyVersion(agentplanePkgPath),
|
|
365
401
|
]);
|
|
366
402
|
if (coreVersion !== agentplaneVersion) {
|
|
367
403
|
throw new CliError({
|
|
@@ -371,6 +407,15 @@ export const runReleaseApply = async (ctx, flags) => {
|
|
|
371
407
|
`packages/core=${coreVersion} packages/agentplane=${agentplaneVersion}`,
|
|
372
408
|
});
|
|
373
409
|
}
|
|
410
|
+
if (coreDependencyVersion !== coreVersion) {
|
|
411
|
+
throw new CliError({
|
|
412
|
+
exitCode: exitCodeForError("E_VALIDATION"),
|
|
413
|
+
code: "E_VALIDATION",
|
|
414
|
+
message: "Release dependency parity check failed before apply. " +
|
|
415
|
+
`packages/agentplane dependency @agentplaneorg/core=${coreDependencyVersion} ` +
|
|
416
|
+
`must match packages/core version ${coreVersion}.`,
|
|
417
|
+
});
|
|
418
|
+
}
|
|
374
419
|
const git = new GitContext({ gitRoot });
|
|
375
420
|
await ensureCleanTrackedTree(gitRoot);
|
|
376
421
|
await ensureTagDoesNotExist(gitRoot, plan.nextTag);
|
|
@@ -391,10 +436,13 @@ export const runReleaseApply = async (ctx, flags) => {
|
|
|
391
436
|
if (coreVersion === plan.prevVersion) {
|
|
392
437
|
await Promise.all([
|
|
393
438
|
replacePackageVersionInFile(corePkgPath, plan.nextVersion),
|
|
394
|
-
|
|
439
|
+
replaceAgentplanePackageMetadata(agentplanePkgPath, plan.nextVersion),
|
|
395
440
|
]);
|
|
396
441
|
}
|
|
397
|
-
else if (coreVersion
|
|
442
|
+
else if (coreVersion === plan.nextVersion) {
|
|
443
|
+
await replaceAgentplanePackageMetadata(agentplanePkgPath, plan.nextVersion);
|
|
444
|
+
}
|
|
445
|
+
else {
|
|
398
446
|
throw new CliError({
|
|
399
447
|
exitCode: exitCodeForError("E_VALIDATION"),
|
|
400
448
|
code: "E_VALIDATION",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plan.command.d.ts","sourceRoot":"","sources":["../../../src/commands/release/plan.command.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAM1E,KAAK,QAAQ,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;AAE5C,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,OAAO,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"plan.command.d.ts","sourceRoot":"","sources":["../../../src/commands/release/plan.command.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAM1E,KAAK,QAAQ,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;AAE5C,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,OAAO,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG,gBAAgB,CAAC;AAqIjD,eAAO,MAAM,eAAe,EAAE,WAAW,CAAC,iBAAiB,CA4E1D,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,cAAc,CAAC,iBAAiB,CAoE5D,CAAC"}
|
|
@@ -43,6 +43,20 @@ async function readPackageVersion(pkgJsonPath) {
|
|
|
43
43
|
}
|
|
44
44
|
return version;
|
|
45
45
|
}
|
|
46
|
+
async function readCoreDependencyVersion(pkgJsonPath) {
|
|
47
|
+
const raw = JSON.parse(await readFile(pkgJsonPath, "utf8"));
|
|
48
|
+
const value = raw.dependencies?.["@agentplaneorg/core"];
|
|
49
|
+
const version = typeof value === "string" ? value.trim() : "";
|
|
50
|
+
if (!version) {
|
|
51
|
+
throw new CliError({
|
|
52
|
+
exitCode: exitCodeForError("E_VALIDATION"),
|
|
53
|
+
code: "E_VALIDATION",
|
|
54
|
+
message: `Missing dependency @agentplaneorg/core in ${pkgJsonPath}. ` +
|
|
55
|
+
"Release planning requires packages/agentplane to pin @agentplaneorg/core to the same version.",
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return version;
|
|
59
|
+
}
|
|
46
60
|
async function getLatestSemverTag(gitRoot) {
|
|
47
61
|
try {
|
|
48
62
|
const { stdout } = await execFileAsync("git", ["describe", "--tags", "--abbrev=0", "--match", "v[0-9]*.[0-9]*.[0-9]*"], { cwd: gitRoot, env: gitEnv() });
|
|
@@ -176,9 +190,10 @@ export const runReleasePlan = async (ctx, flags) => {
|
|
|
176
190
|
const gitRoot = resolved.gitRoot;
|
|
177
191
|
const corePkgPath = path.join(gitRoot, "packages", "core", "package.json");
|
|
178
192
|
const agentplanePkgPath = path.join(gitRoot, "packages", "agentplane", "package.json");
|
|
179
|
-
const [coreVersion, agentplaneVersion] = await Promise.all([
|
|
193
|
+
const [coreVersion, agentplaneVersion, coreDependencyVersion] = await Promise.all([
|
|
180
194
|
readPackageVersion(corePkgPath),
|
|
181
195
|
readPackageVersion(agentplanePkgPath),
|
|
196
|
+
readCoreDependencyVersion(agentplanePkgPath),
|
|
182
197
|
]);
|
|
183
198
|
if (coreVersion !== agentplaneVersion) {
|
|
184
199
|
throw new CliError({
|
|
@@ -188,6 +203,15 @@ export const runReleasePlan = async (ctx, flags) => {
|
|
|
188
203
|
`packages/core=${coreVersion} packages/agentplane=${agentplaneVersion}`,
|
|
189
204
|
});
|
|
190
205
|
}
|
|
206
|
+
if (coreDependencyVersion !== coreVersion) {
|
|
207
|
+
throw new CliError({
|
|
208
|
+
exitCode: exitCodeForError("E_VALIDATION"),
|
|
209
|
+
code: "E_VALIDATION",
|
|
210
|
+
message: "Release dependency parity check failed before planning. " +
|
|
211
|
+
`packages/agentplane dependency @agentplaneorg/core=${coreDependencyVersion} ` +
|
|
212
|
+
`must match packages/core version ${coreVersion}.`,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
191
215
|
const prevTag = flags.since ?? (await getLatestSemverTag(gitRoot));
|
|
192
216
|
const nextVersion = bumpVersion(coreVersion, flags.bump);
|
|
193
217
|
const nextTag = `v${nextVersion}`;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AgentplaneConfig, ResolvedProject } from "@agentplaneorg/core";
|
|
1
2
|
import { loadTaskBackend, type TaskData } from "../../backends/task-backend.js";
|
|
2
3
|
import { GitContext } from "./git-context.js";
|
|
3
4
|
export type CommandMemo = {
|
|
@@ -22,6 +23,8 @@ export declare function taskDataToFrontmatter(task: TaskData): Record<string, un
|
|
|
22
23
|
export declare function loadCommandContext(opts: {
|
|
23
24
|
cwd: string;
|
|
24
25
|
rootOverride?: string | null;
|
|
26
|
+
resolvedProject?: ResolvedProject;
|
|
27
|
+
config?: AgentplaneConfig;
|
|
25
28
|
}): Promise<CommandContext>;
|
|
26
29
|
export declare function loadTaskFromContext(opts: {
|
|
27
30
|
ctx: CommandContext;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-backend.d.ts","sourceRoot":"","sources":["../../../src/commands/shared/task-backend.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"task-backend.d.ts","sourceRoot":"","sources":["../../../src/commands/shared/task-backend.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAG7E,OAAO,EAAE,eAAe,EAAE,KAAK,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AAChF,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC/B,YAAY,CAAC,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACjC,UAAU,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,eAAe,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;IACzE,MAAM,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;IAC9D,WAAW,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IACpE,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,GAAG,EAAE,UAAU,CAAC;IAEhB,IAAI,EAAE,WAAW,CAAC;IAGlB,QAAQ,EAAE,cAAc,CAAC,iBAAiB,CAAC,CAAC;IAC5C,OAAO,EAAE,cAAc,CAAC,aAAa,CAAC,CAAC;CACxC,CAAC;AASF,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAQ3E;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA+B7E;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE;IAC7C,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,MAAM,CAAC,EAAE,gBAAgB,CAAC;CAC3B,GAAG,OAAO,CAAC,cAAc,CAAC,CAmB1B;AAED,wBAAsB,mBAAmB,CAAC,IAAI,EAAE;IAC9C,GAAG,EAAE,cAAc,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAepB;AAED,wBAAsB,eAAe,CAAC,IAAI,EAAE;IAC1C,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC;IACV,OAAO,EAAE,cAAc,CAAC,aAAa,CAAC,CAAC;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,QAAQ,EAAE,cAAc,CAAC,iBAAiB,CAAC,CAAC;IAC5C,MAAM,EAAE,cAAc,CAAC,QAAQ,CAAC,CAAC;IACjC,IAAI,EAAE,QAAQ,CAAC;CAChB,CAAC,CAaD;AAED,wBAAsB,aAAa,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAG5E"}
|
|
@@ -50,10 +50,13 @@ export function taskDataToFrontmatter(task) {
|
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
52
|
export async function loadCommandContext(opts) {
|
|
53
|
-
const
|
|
53
|
+
const backendLoaded = await loadTaskBackend({
|
|
54
54
|
cwd: opts.cwd,
|
|
55
55
|
rootOverride: opts.rootOverride ?? null,
|
|
56
|
+
resolvedProject: opts.resolvedProject,
|
|
57
|
+
config: opts.config,
|
|
56
58
|
});
|
|
59
|
+
const { backend, backendId, backendConfigPath, resolved, config } = backendLoaded;
|
|
57
60
|
return {
|
|
58
61
|
resolvedProject: resolved,
|
|
59
62
|
config,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"block.d.ts","sourceRoot":"","sources":["../../../src/commands/task/block.ts"],"names":[],"mappings":"AAQA,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"block.d.ts","sourceRoot":"","sources":["../../../src/commands/task/block.ts"],"names":[],"mappings":"AAQA,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,2BAA2B,CAAC;AAgBnC,wBAAsB,QAAQ,CAAC,IAAI,EAAE;IACnC,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,iBAAiB,EAAE,OAAO,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,eAAe,EAAE,OAAO,CAAC;IACzB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,kBAAkB,EAAE,OAAO,CAAC;IAC5B,mBAAmB,EAAE,OAAO,CAAC;IAC7B,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CAmIlB"}
|
|
@@ -7,7 +7,7 @@ import { ensureActionApproved } from "../shared/approval-requirements.js";
|
|
|
7
7
|
import { loadCommandContext, loadTaskFromContext, } from "../shared/task-backend.js";
|
|
8
8
|
import { backendIsLocalFileBackend, getTaskStore } from "../shared/task-store.js";
|
|
9
9
|
import { readDirectWorkLock } from "../../shared/direct-work-lock.js";
|
|
10
|
-
import { appendTaskEvent, defaultCommitEmojiForAgentId, enforceStatusCommitPolicy, isTransitionAllowed, nowIso, requireStructuredComment, } from "./shared.js";
|
|
10
|
+
import { appendTaskEvent, defaultCommitEmojiForAgentId, enforceStatusCommitPolicy, isTransitionAllowed, nowIso, requireStructuredComment, resolvePrimaryTag, toStringArray, } from "./shared.js";
|
|
11
11
|
export async function cmdBlock(opts) {
|
|
12
12
|
try {
|
|
13
13
|
const ctx = opts.ctx ??
|
|
@@ -20,14 +20,6 @@ export async function cmdBlock(opts) {
|
|
|
20
20
|
reason: "block --force",
|
|
21
21
|
});
|
|
22
22
|
}
|
|
23
|
-
if (opts.commitFromComment) {
|
|
24
|
-
enforceStatusCommitPolicy({
|
|
25
|
-
policy: ctx.config.status_commit_policy,
|
|
26
|
-
action: "block",
|
|
27
|
-
confirmed: opts.confirmStatusCommit,
|
|
28
|
-
quiet: opts.quiet,
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
23
|
const { prefix, min_chars: minChars } = ctx.config.tasks.comments.blocked;
|
|
32
24
|
requireStructuredComment(opts.body, prefix, minChars);
|
|
33
25
|
const useStore = backendIsLocalFileBackend(ctx);
|
|
@@ -43,6 +35,16 @@ export async function cmdBlock(opts) {
|
|
|
43
35
|
message: `Refusing status transition ${currentStatus} -> BLOCKED (use --force to override)`,
|
|
44
36
|
});
|
|
45
37
|
}
|
|
38
|
+
if (opts.commitFromComment) {
|
|
39
|
+
enforceStatusCommitPolicy({
|
|
40
|
+
policy: ctx.config.status_commit_policy,
|
|
41
|
+
action: "block",
|
|
42
|
+
confirmed: opts.confirmStatusCommit,
|
|
43
|
+
quiet: opts.quiet,
|
|
44
|
+
statusFrom: currentStatus,
|
|
45
|
+
statusTo: "BLOCKED",
|
|
46
|
+
});
|
|
47
|
+
}
|
|
46
48
|
const formattedComment = opts.commitFromComment
|
|
47
49
|
? formatCommentBodyForCommit(opts.body, ctx.config)
|
|
48
50
|
: null;
|
|
@@ -94,6 +96,7 @@ export async function cmdBlock(opts) {
|
|
|
94
96
|
cwd: opts.cwd,
|
|
95
97
|
rootOverride: opts.rootOverride,
|
|
96
98
|
taskId: opts.taskId,
|
|
99
|
+
primaryTag: resolvePrimaryTag(toStringArray(task.tags), ctx).primary,
|
|
97
100
|
executorAgent,
|
|
98
101
|
author: opts.author,
|
|
99
102
|
statusFrom: currentStatus,
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CommandCtx, CommandSpec } from "../../cli/spec/spec.js";
|
|
2
|
+
import type { CommandContext } from "../shared/task-backend.js";
|
|
3
|
+
export type TaskCloseDuplicateParsed = {
|
|
4
|
+
taskId: string;
|
|
5
|
+
duplicateOf: string;
|
|
6
|
+
author: string;
|
|
7
|
+
note?: string;
|
|
8
|
+
force: boolean;
|
|
9
|
+
yes: boolean;
|
|
10
|
+
quiet: boolean;
|
|
11
|
+
};
|
|
12
|
+
export declare const taskCloseDuplicateSpec: CommandSpec<TaskCloseDuplicateParsed>;
|
|
13
|
+
export declare function makeRunTaskCloseDuplicateHandler(getCtx: (cmd: string) => Promise<CommandContext>): (ctx: CommandCtx, p: TaskCloseDuplicateParsed) => Promise<number>;
|
|
14
|
+
//# sourceMappingURL=close-duplicate.command.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"close-duplicate.command.d.ts","sourceRoot":"","sources":["../../../src/commands/task/close-duplicate.command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAEtE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAGhE,MAAM,MAAM,wBAAwB,GAAG;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,EAAE,OAAO,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,WAAW,CAAC,wBAAwB,CAmFxE,CAAC;AAEF,wBAAgB,gCAAgC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,IACjF,KAAK,UAAU,EAAE,GAAG,wBAAwB,KAAG,OAAO,CAAC,MAAM,CAAC,CAc7E"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { usageError } from "../../cli/spec/errors.js";
|
|
2
|
+
import { cmdTaskCloseDuplicate } from "./close-duplicate.js";
|
|
3
|
+
export const taskCloseDuplicateSpec = {
|
|
4
|
+
id: ["task", "close-duplicate"],
|
|
5
|
+
group: "Task",
|
|
6
|
+
summary: "Close a task as a duplicate of another task with no-op bookkeeping metadata.",
|
|
7
|
+
args: [{ name: "task-id", required: true, valueHint: "<task-id>" }],
|
|
8
|
+
options: [
|
|
9
|
+
{
|
|
10
|
+
kind: "string",
|
|
11
|
+
name: "of",
|
|
12
|
+
valueHint: "<task-id>",
|
|
13
|
+
required: true,
|
|
14
|
+
description: "Canonical task id that this task duplicates.",
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
kind: "string",
|
|
18
|
+
name: "author",
|
|
19
|
+
valueHint: "<id>",
|
|
20
|
+
required: true,
|
|
21
|
+
description: "Comment author id (e.g. ORCHESTRATOR).",
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
kind: "string",
|
|
25
|
+
name: "note",
|
|
26
|
+
valueHint: "<text>",
|
|
27
|
+
description: "Optional short reason appended to the closure comment.",
|
|
28
|
+
},
|
|
29
|
+
{ kind: "boolean", name: "force", default: false, description: "Force closure despite gates." },
|
|
30
|
+
{
|
|
31
|
+
kind: "boolean",
|
|
32
|
+
name: "yes",
|
|
33
|
+
default: false,
|
|
34
|
+
description: "Auto-approve force-action approval checks when required.",
|
|
35
|
+
},
|
|
36
|
+
{ kind: "boolean", name: "quiet", default: false, description: "Suppress output." },
|
|
37
|
+
],
|
|
38
|
+
examples: [
|
|
39
|
+
{
|
|
40
|
+
cmd: "agentplane task close-duplicate 202602120845-VKGC27 --of 202602120845-RWJ8K3 --author ORCHESTRATOR",
|
|
41
|
+
why: "Close accidental duplicate task in one command.",
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
validateRaw: (raw) => {
|
|
45
|
+
const taskId = typeof raw.args["task-id"] === "string" ? raw.args["task-id"].trim() : "";
|
|
46
|
+
const of = typeof raw.opts.of === "string" ? raw.opts.of.trim() : "";
|
|
47
|
+
const author = typeof raw.opts.author === "string" ? raw.opts.author.trim() : "";
|
|
48
|
+
const note = raw.opts.note;
|
|
49
|
+
if (!taskId) {
|
|
50
|
+
throw usageError({
|
|
51
|
+
spec: taskCloseDuplicateSpec,
|
|
52
|
+
message: "Invalid value for task-id: empty.",
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
if (!of) {
|
|
56
|
+
throw usageError({ spec: taskCloseDuplicateSpec, message: "Invalid value for --of: empty." });
|
|
57
|
+
}
|
|
58
|
+
if (!author) {
|
|
59
|
+
throw usageError({
|
|
60
|
+
spec: taskCloseDuplicateSpec,
|
|
61
|
+
message: "Invalid value for --author: empty.",
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
if (typeof note === "string" && note.trim().length === 0) {
|
|
65
|
+
throw usageError({
|
|
66
|
+
spec: taskCloseDuplicateSpec,
|
|
67
|
+
message: "Invalid value for --note: empty.",
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
if (taskId === of) {
|
|
71
|
+
throw usageError({
|
|
72
|
+
spec: taskCloseDuplicateSpec,
|
|
73
|
+
message: "Duplicate target must differ from task-id.",
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
parse: (raw) => ({
|
|
78
|
+
taskId: String(raw.args["task-id"]),
|
|
79
|
+
duplicateOf: String(raw.opts.of),
|
|
80
|
+
author: String(raw.opts.author),
|
|
81
|
+
note: typeof raw.opts.note === "string" ? raw.opts.note : undefined,
|
|
82
|
+
force: raw.opts.force === true,
|
|
83
|
+
yes: raw.opts.yes === true,
|
|
84
|
+
quiet: raw.opts.quiet === true,
|
|
85
|
+
}),
|
|
86
|
+
};
|
|
87
|
+
export function makeRunTaskCloseDuplicateHandler(getCtx) {
|
|
88
|
+
return async (ctx, p) => {
|
|
89
|
+
return await cmdTaskCloseDuplicate({
|
|
90
|
+
ctx: await getCtx("task close-duplicate"),
|
|
91
|
+
cwd: ctx.cwd,
|
|
92
|
+
rootOverride: ctx.rootOverride,
|
|
93
|
+
taskId: p.taskId,
|
|
94
|
+
duplicateOf: p.duplicateOf,
|
|
95
|
+
author: p.author,
|
|
96
|
+
note: p.note,
|
|
97
|
+
force: p.force,
|
|
98
|
+
yes: p.yes,
|
|
99
|
+
quiet: p.quiet,
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type CommandContext } from "../shared/task-backend.js";
|
|
2
|
+
export declare function cmdTaskCloseDuplicate(opts: {
|
|
3
|
+
ctx: CommandContext;
|
|
4
|
+
cwd: string;
|
|
5
|
+
rootOverride?: string;
|
|
6
|
+
taskId: string;
|
|
7
|
+
duplicateOf: string;
|
|
8
|
+
author: string;
|
|
9
|
+
note?: string;
|
|
10
|
+
force: boolean;
|
|
11
|
+
yes: boolean;
|
|
12
|
+
quiet: boolean;
|
|
13
|
+
}): Promise<number>;
|
|
14
|
+
//# sourceMappingURL=close-duplicate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"close-duplicate.d.ts","sourceRoot":"","sources":["../../../src/commands/task/close-duplicate.ts"],"names":[],"mappings":"AAIA,OAAO,EAAuB,KAAK,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAIrF,wBAAsB,qBAAqB,CAAC,IAAI,EAAE;IAChD,GAAG,EAAE,cAAc,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,EAAE,OAAO,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CAuFlB"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { mapBackendError } from "../../cli/error-map.js";
|
|
2
|
+
import { CliError } from "../../shared/errors.js";
|
|
3
|
+
import { ensureActionApproved } from "../shared/approval-requirements.js";
|
|
4
|
+
import { loadTaskFromContext } from "../shared/task-backend.js";
|
|
5
|
+
import { backendIsLocalFileBackend, getTaskStore } from "../shared/task-store.js";
|
|
6
|
+
import { appendTaskEvent, nowIso, requireStructuredComment } from "./shared.js";
|
|
7
|
+
export async function cmdTaskCloseDuplicate(opts) {
|
|
8
|
+
try {
|
|
9
|
+
const sourceId = opts.taskId.trim();
|
|
10
|
+
const duplicateOf = opts.duplicateOf.trim();
|
|
11
|
+
if (!sourceId || !duplicateOf) {
|
|
12
|
+
throw new CliError({
|
|
13
|
+
exitCode: 2,
|
|
14
|
+
code: "E_USAGE",
|
|
15
|
+
message: "Both task id and --of must be non-empty.",
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
if (sourceId === duplicateOf) {
|
|
19
|
+
throw new CliError({
|
|
20
|
+
exitCode: 2,
|
|
21
|
+
code: "E_USAGE",
|
|
22
|
+
message: `Duplicate target must differ from task id (${sourceId}).`,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
if (opts.force) {
|
|
26
|
+
await ensureActionApproved({
|
|
27
|
+
action: "force_action",
|
|
28
|
+
config: opts.ctx.config,
|
|
29
|
+
yes: opts.yes,
|
|
30
|
+
reason: "task close-duplicate --force",
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
const canonical = await loadTaskFromContext({ ctx: opts.ctx, taskId: duplicateOf });
|
|
34
|
+
const useStore = backendIsLocalFileBackend(opts.ctx);
|
|
35
|
+
const store = useStore ? getTaskStore(opts.ctx) : null;
|
|
36
|
+
const task = useStore
|
|
37
|
+
? await store.get(sourceId)
|
|
38
|
+
: await loadTaskFromContext({ ctx: opts.ctx, taskId: sourceId });
|
|
39
|
+
if (!opts.force && String(task.status || "TODO").toUpperCase() === "DONE") {
|
|
40
|
+
throw new CliError({
|
|
41
|
+
exitCode: 2,
|
|
42
|
+
code: "E_USAGE",
|
|
43
|
+
message: `Task is already DONE: ${sourceId} (use --force to override)`,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
const reason = opts.note?.trim();
|
|
47
|
+
const canonicalTitle = canonical.title?.trim() ? ` (${canonical.title.trim()})` : "";
|
|
48
|
+
const baseBody = `Verified: ${sourceId} is a bookkeeping duplicate of ${duplicateOf}${canonicalTitle}; ` +
|
|
49
|
+
"no code/config changes are expected in this task and closure is recorded as no-op.";
|
|
50
|
+
const body = reason ? `${baseBody}\n\nReason: ${reason}` : baseBody;
|
|
51
|
+
const verifiedCfg = opts.ctx.config.tasks.comments.verified;
|
|
52
|
+
requireStructuredComment(body, verifiedCfg.prefix, verifiedCfg.min_chars);
|
|
53
|
+
const at = nowIso();
|
|
54
|
+
const next = {
|
|
55
|
+
...task,
|
|
56
|
+
status: "DONE",
|
|
57
|
+
comments: [
|
|
58
|
+
...(Array.isArray(task.comments) ? task.comments : []),
|
|
59
|
+
{ author: opts.author, body },
|
|
60
|
+
],
|
|
61
|
+
events: appendTaskEvent(task, {
|
|
62
|
+
type: "status",
|
|
63
|
+
at,
|
|
64
|
+
author: opts.author,
|
|
65
|
+
from: String(task.status || "TODO").toUpperCase(),
|
|
66
|
+
to: "DONE",
|
|
67
|
+
note: body,
|
|
68
|
+
}),
|
|
69
|
+
result_summary: `Closed as duplicate of ${duplicateOf}.`,
|
|
70
|
+
risk_level: "low",
|
|
71
|
+
breaking: false,
|
|
72
|
+
doc_version: 2,
|
|
73
|
+
doc_updated_at: at,
|
|
74
|
+
doc_updated_by: opts.author,
|
|
75
|
+
};
|
|
76
|
+
await (useStore ? store.update(sourceId, () => next) : opts.ctx.taskBackend.writeTask(next));
|
|
77
|
+
if (!opts.quiet) {
|
|
78
|
+
process.stdout.write(`task.done: ${sourceId} (duplicate of ${duplicateOf})\n`);
|
|
79
|
+
}
|
|
80
|
+
return 0;
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
if (err instanceof CliError)
|
|
84
|
+
throw err;
|
|
85
|
+
throw mapBackendError(err, {
|
|
86
|
+
command: "task close-duplicate",
|
|
87
|
+
root: opts.rootOverride ?? null,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|