agentplane 0.2.18 → 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.
Files changed (103) hide show
  1. package/assets/AGENTS.md +4 -3
  2. package/bin/agentplane.js +24 -0
  3. package/dist/backends/task-backend/load.d.ts +2 -0
  4. package/dist/backends/task-backend/load.d.ts.map +1 -1
  5. package/dist/backends/task-backend/load.js +38 -18
  6. package/dist/cli/command-guide.d.ts.map +1 -1
  7. package/dist/cli/command-guide.js +12 -11
  8. package/dist/cli/run-cli/command-catalog.d.ts +1 -1
  9. package/dist/cli/run-cli/command-catalog.d.ts.map +1 -1
  10. package/dist/cli/run-cli/command-catalog.js +12 -1
  11. package/dist/cli/run-cli/commands/core.d.ts +9 -1
  12. package/dist/cli/run-cli/commands/core.d.ts.map +1 -1
  13. package/dist/cli/run-cli/commands/core.js +268 -8
  14. package/dist/cli/run-cli/commands/init/write-config.d.ts.map +1 -1
  15. package/dist/cli/run-cli/commands/init/write-config.js +2 -0
  16. package/dist/cli/run-cli/commands/init/write-gitignore.d.ts.map +1 -1
  17. package/dist/cli/run-cli/commands/init/write-gitignore.js +3 -18
  18. package/dist/cli/run-cli/commands/init.d.ts +1 -0
  19. package/dist/cli/run-cli/commands/init.d.ts.map +1 -1
  20. package/dist/cli/run-cli/commands/init.js +55 -30
  21. package/dist/cli/run-cli.d.ts.map +1 -1
  22. package/dist/cli/run-cli.js +85 -14
  23. package/dist/commands/commit.command.d.ts.map +1 -1
  24. package/dist/commands/commit.command.js +2 -0
  25. package/dist/commands/commit.spec.d.ts +2 -0
  26. package/dist/commands/commit.spec.d.ts.map +1 -1
  27. package/dist/commands/commit.spec.js +26 -0
  28. package/dist/commands/doctor.run.d.ts.map +1 -1
  29. package/dist/commands/doctor.run.js +15 -6
  30. package/dist/commands/finish.run.d.ts.map +1 -1
  31. package/dist/commands/finish.run.js +2 -0
  32. package/dist/commands/finish.spec.d.ts +2 -0
  33. package/dist/commands/finish.spec.d.ts.map +1 -1
  34. package/dist/commands/finish.spec.js +36 -0
  35. package/dist/commands/guard/impl/allow.d.ts.map +1 -1
  36. package/dist/commands/guard/impl/allow.js +33 -7
  37. package/dist/commands/guard/impl/commands.d.ts +2 -0
  38. package/dist/commands/guard/impl/commands.d.ts.map +1 -1
  39. package/dist/commands/guard/impl/commands.js +24 -4
  40. package/dist/commands/guard/impl/comment-commit.d.ts +1 -0
  41. package/dist/commands/guard/impl/comment-commit.d.ts.map +1 -1
  42. package/dist/commands/guard/impl/comment-commit.js +16 -24
  43. package/dist/commands/release/apply.command.d.ts.map +1 -1
  44. package/dist/commands/release/apply.command.js +79 -3
  45. package/dist/commands/release/plan.command.d.ts.map +1 -1
  46. package/dist/commands/release/plan.command.js +25 -1
  47. package/dist/commands/shared/task-backend.d.ts +3 -0
  48. package/dist/commands/shared/task-backend.d.ts.map +1 -1
  49. package/dist/commands/shared/task-backend.js +4 -1
  50. package/dist/commands/task/block.d.ts.map +1 -1
  51. package/dist/commands/task/block.js +12 -9
  52. package/dist/commands/task/close-duplicate.command.d.ts +14 -0
  53. package/dist/commands/task/close-duplicate.command.d.ts.map +1 -0
  54. package/dist/commands/task/close-duplicate.command.js +102 -0
  55. package/dist/commands/task/close-duplicate.d.ts +14 -0
  56. package/dist/commands/task/close-duplicate.d.ts.map +1 -0
  57. package/dist/commands/task/close-duplicate.js +90 -0
  58. package/dist/commands/task/close-noop.command.d.ts +14 -0
  59. package/dist/commands/task/close-noop.command.d.ts.map +1 -0
  60. package/dist/commands/task/close-noop.command.js +77 -0
  61. package/dist/commands/task/close-noop.d.ts +13 -0
  62. package/dist/commands/task/close-noop.d.ts.map +1 -0
  63. package/dist/commands/task/close-noop.js +77 -0
  64. package/dist/commands/task/finish.d.ts +2 -0
  65. package/dist/commands/task/finish.d.ts.map +1 -1
  66. package/dist/commands/task/finish.js +52 -10
  67. package/dist/commands/task/index.d.ts +3 -0
  68. package/dist/commands/task/index.d.ts.map +1 -1
  69. package/dist/commands/task/index.js +3 -0
  70. package/dist/commands/task/new.d.ts.map +1 -1
  71. package/dist/commands/task/new.js +34 -6
  72. package/dist/commands/task/new.spec.js +1 -1
  73. package/dist/commands/task/plan.d.ts.map +1 -1
  74. package/dist/commands/task/plan.js +2 -3
  75. package/dist/commands/task/set-status.d.ts.map +1 -1
  76. package/dist/commands/task/set-status.js +12 -9
  77. package/dist/commands/task/shared.d.ts +19 -0
  78. package/dist/commands/task/shared.d.ts.map +1 -1
  79. package/dist/commands/task/shared.js +137 -0
  80. package/dist/commands/task/start-ready.command.d.ts +14 -0
  81. package/dist/commands/task/start-ready.command.d.ts.map +1 -0
  82. package/dist/commands/task/start-ready.command.js +77 -0
  83. package/dist/commands/task/start-ready.d.ts +13 -0
  84. package/dist/commands/task/start-ready.d.ts.map +1 -0
  85. package/dist/commands/task/start-ready.js +46 -0
  86. package/dist/commands/task/start.d.ts.map +1 -1
  87. package/dist/commands/task/start.js +13 -11
  88. package/dist/commands/task/update.command.d.ts +1 -0
  89. package/dist/commands/task/update.command.d.ts.map +1 -1
  90. package/dist/commands/task/update.command.js +8 -0
  91. package/dist/commands/task/update.d.ts +1 -0
  92. package/dist/commands/task/update.d.ts.map +1 -1
  93. package/dist/commands/task/update.js +19 -3
  94. package/dist/shared/errors.d.ts +9 -1
  95. package/dist/shared/errors.d.ts.map +1 -1
  96. package/dist/shared/errors.js +3 -1
  97. package/dist/shared/runtime-artifacts.d.ts +3 -0
  98. package/dist/shared/runtime-artifacts.d.ts.map +1 -0
  99. package/dist/shared/runtime-artifacts.js +18 -0
  100. package/dist/usecases/context/resolve-context.d.ts +3 -0
  101. package/dist/usecases/context/resolve-context.d.ts.map +1 -1
  102. package/dist/usecases/context/resolve-context.js +6 -1
  103. package/package.json +5 -4
@@ -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
+ }
@@ -0,0 +1,14 @@
1
+ import type { CommandCtx, CommandSpec } from "../../cli/spec/spec.js";
2
+ import type { CommandContext } from "../shared/task-backend.js";
3
+ type TaskCloseNoopParsed = {
4
+ taskId: string;
5
+ author: string;
6
+ note?: string;
7
+ force: boolean;
8
+ yes: boolean;
9
+ quiet: boolean;
10
+ };
11
+ export declare const taskCloseNoopSpec: CommandSpec<TaskCloseNoopParsed>;
12
+ export declare function makeRunTaskCloseNoopHandler(getCtx: (cmd: string) => Promise<CommandContext>): (ctx: CommandCtx, p: TaskCloseNoopParsed) => Promise<number>;
13
+ export {};
14
+ //# sourceMappingURL=close-noop.command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"close-noop.command.d.ts","sourceRoot":"","sources":["../../../src/commands/task/close-noop.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,KAAK,mBAAmB,GAAG;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,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,iBAAiB,EAAE,WAAW,CAAC,mBAAmB,CA2D9D,CAAC;AAEF,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,IAC5E,KAAK,UAAU,EAAE,GAAG,mBAAmB,KAAG,OAAO,CAAC,MAAM,CAAC,CAaxE"}
@@ -0,0 +1,77 @@
1
+ import { usageError } from "../../cli/spec/errors.js";
2
+ import { cmdTaskCloseNoop } from "./close-noop.js";
3
+ export const taskCloseNoopSpec = {
4
+ id: ["task", "close-noop"],
5
+ group: "Task",
6
+ summary: "Close a task as a verified no-op in one command.",
7
+ args: [{ name: "task-id", required: true, valueHint: "<task-id>" }],
8
+ options: [
9
+ {
10
+ kind: "string",
11
+ name: "author",
12
+ valueHint: "<id>",
13
+ required: true,
14
+ description: "Comment author id (e.g. ORCHESTRATOR).",
15
+ },
16
+ {
17
+ kind: "string",
18
+ name: "note",
19
+ valueHint: "<text>",
20
+ description: "Optional closure note.",
21
+ },
22
+ {
23
+ kind: "boolean",
24
+ name: "force",
25
+ default: false,
26
+ description: "Override status/verification gates.",
27
+ },
28
+ {
29
+ kind: "boolean",
30
+ name: "yes",
31
+ default: false,
32
+ description: "Auto-approve force-action checks when required.",
33
+ },
34
+ { kind: "boolean", name: "quiet", default: false, description: "Suppress output." },
35
+ ],
36
+ examples: [
37
+ {
38
+ cmd: 'agentplane task close-noop 202602030608-F1Q8AB --author ORCHESTRATOR --note "Duplicate tracking artifact"',
39
+ why: "Close bookkeeping-only tasks without manual verify+finish choreography.",
40
+ },
41
+ ],
42
+ validateRaw: (raw) => {
43
+ const taskId = typeof raw.args["task-id"] === "string" ? raw.args["task-id"].trim() : "";
44
+ const author = typeof raw.opts.author === "string" ? raw.opts.author.trim() : "";
45
+ const note = raw.opts.note;
46
+ if (!taskId)
47
+ throw usageError({ spec: taskCloseNoopSpec, message: "Invalid value for task-id: empty." });
48
+ if (!author)
49
+ throw usageError({ spec: taskCloseNoopSpec, message: "Invalid value for --author: empty." });
50
+ if (typeof note === "string" && note.trim().length === 0) {
51
+ throw usageError({ spec: taskCloseNoopSpec, message: "Invalid value for --note: empty." });
52
+ }
53
+ },
54
+ parse: (raw) => ({
55
+ taskId: String(raw.args["task-id"]),
56
+ author: String(raw.opts.author),
57
+ note: typeof raw.opts.note === "string" ? raw.opts.note : undefined,
58
+ force: raw.opts.force === true,
59
+ yes: raw.opts.yes === true,
60
+ quiet: raw.opts.quiet === true,
61
+ }),
62
+ };
63
+ export function makeRunTaskCloseNoopHandler(getCtx) {
64
+ return async (ctx, p) => {
65
+ return await cmdTaskCloseNoop({
66
+ ctx: await getCtx("task close-noop"),
67
+ cwd: ctx.cwd,
68
+ rootOverride: ctx.rootOverride,
69
+ taskId: p.taskId,
70
+ author: p.author,
71
+ note: p.note,
72
+ force: p.force,
73
+ yes: p.yes,
74
+ quiet: p.quiet,
75
+ });
76
+ };
77
+ }
@@ -0,0 +1,13 @@
1
+ import { type CommandContext } from "../shared/task-backend.js";
2
+ export declare function cmdTaskCloseNoop(opts: {
3
+ ctx?: CommandContext;
4
+ cwd: string;
5
+ rootOverride?: string;
6
+ taskId: string;
7
+ author: string;
8
+ note?: string;
9
+ force: boolean;
10
+ yes: boolean;
11
+ quiet: boolean;
12
+ }): Promise<number>;
13
+ //# sourceMappingURL=close-noop.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"close-noop.d.ts","sourceRoot":"","sources":["../../../src/commands/task/close-noop.ts"],"names":[],"mappings":"AAIA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAGpF,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,MAAM,CAAC;IACf,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,CAsElB"}
@@ -0,0 +1,77 @@
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 { backendIsLocalFileBackend, getTaskStore } from "../shared/task-store.js";
5
+ import { loadCommandContext } from "../shared/task-backend.js";
6
+ import { appendTaskEvent, nowIso, requireStructuredComment } from "./shared.js";
7
+ export async function cmdTaskCloseNoop(opts) {
8
+ try {
9
+ const ctx = opts.ctx ??
10
+ (await loadCommandContext({ cwd: opts.cwd, rootOverride: opts.rootOverride ?? null }));
11
+ if (opts.force) {
12
+ await ensureActionApproved({
13
+ action: "force_action",
14
+ config: ctx.config,
15
+ yes: opts.yes,
16
+ reason: "task close-noop --force",
17
+ });
18
+ }
19
+ const useStore = backendIsLocalFileBackend(ctx);
20
+ const store = useStore ? getTaskStore(ctx) : null;
21
+ const task = useStore
22
+ ? await store.get(opts.taskId)
23
+ : await ctx.taskBackend.getTask(opts.taskId);
24
+ if (!task) {
25
+ throw new CliError({
26
+ exitCode: 4,
27
+ code: "E_IO",
28
+ message: `Task not found: ${opts.taskId}`,
29
+ });
30
+ }
31
+ if (!opts.force && String(task.status || "TODO").toUpperCase() === "DONE") {
32
+ throw new CliError({
33
+ exitCode: 2,
34
+ code: "E_USAGE",
35
+ message: `Task is already DONE: ${opts.taskId} (use --force to override)`,
36
+ });
37
+ }
38
+ const normalizedNote = opts.note?.trim();
39
+ const baseBody = "Verified: no implementation changes were required; closure is recorded as no-op bookkeeping.";
40
+ const body = normalizedNote ? `${baseBody}\n\nNote: ${normalizedNote}` : baseBody;
41
+ const verifiedCfg = ctx.config.tasks.comments.verified;
42
+ requireStructuredComment(body, verifiedCfg.prefix, verifiedCfg.min_chars);
43
+ const at = nowIso();
44
+ const next = {
45
+ ...task,
46
+ status: "DONE",
47
+ comments: [
48
+ ...(Array.isArray(task.comments) ? task.comments : []),
49
+ { author: opts.author, body },
50
+ ],
51
+ events: appendTaskEvent(task, {
52
+ type: "status",
53
+ at,
54
+ author: opts.author,
55
+ from: String(task.status || "TODO").toUpperCase(),
56
+ to: "DONE",
57
+ note: body,
58
+ }),
59
+ result_summary: "No-op closure recorded.",
60
+ risk_level: "low",
61
+ breaking: false,
62
+ doc_version: 2,
63
+ doc_updated_at: at,
64
+ doc_updated_by: opts.author,
65
+ };
66
+ await (useStore ? store.update(opts.taskId, () => next) : ctx.taskBackend.writeTask(next));
67
+ if (!opts.quiet) {
68
+ process.stdout.write(`task.done: ${opts.taskId} (no-op)\n`);
69
+ }
70
+ return 0;
71
+ }
72
+ catch (err) {
73
+ if (err instanceof CliError)
74
+ throw err;
75
+ throw mapBackendError(err, { command: "task close-noop", root: opts.rootOverride ?? null });
76
+ }
77
+ }
@@ -24,6 +24,8 @@ export declare function cmdFinish(opts: {
24
24
  statusCommitAutoAllow: boolean;
25
25
  statusCommitRequireClean: boolean;
26
26
  confirmStatusCommit: boolean;
27
+ closeCommit?: boolean;
28
+ closeUnstageOthers?: boolean;
27
29
  quiet: boolean;
28
30
  }): Promise<number>;
29
31
  //# sourceMappingURL=finish.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"finish.d.ts","sourceRoot":"","sources":["../../../src/commands/task/finish.ts"],"names":[],"mappings":"AAUA,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,2BAA2B,CAAC;AAiCnC,wBAAsB,SAAS,CAAC,IAAI,EAAE;IACpC,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;IAC9B,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,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,YAAY,EAAE,OAAO,CAAC;IACtB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,qBAAqB,EAAE,OAAO,CAAC;IAC/B,wBAAwB,EAAE,OAAO,CAAC;IAClC,mBAAmB,EAAE,OAAO,CAAC;IAC7B,KAAK,EAAE,OAAO,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CAiRlB"}
1
+ {"version":3,"file":"finish.d.ts","sourceRoot":"","sources":["../../../src/commands/task/finish.ts"],"names":[],"mappings":"AAUA,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,2BAA2B,CAAC;AAmCnC,wBAAsB,SAAS,CAAC,IAAI,EAAE;IACpC,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;IAC9B,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,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,YAAY,EAAE,OAAO,CAAC;IACtB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,qBAAqB,EAAE,OAAO,CAAC;IAC/B,wBAAwB,EAAE,OAAO,CAAC;IAClC,mBAAmB,EAAE,OAAO,CAAC;IAC7B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,KAAK,EAAE,OAAO,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CA6TlB"}
@@ -4,12 +4,12 @@ import { formatCommentBodyForCommit } from "../../shared/comment-format.js";
4
4
  import { CliError } from "../../shared/errors.js";
5
5
  import { readFile, rm } from "node:fs/promises";
6
6
  import path from "node:path";
7
- import { buildGitCommitEnv, commitFromComment } from "../guard/index.js";
7
+ import { buildGitCommitEnv, cmdCommit, commitFromComment } from "../guard/index.js";
8
8
  import { ensureActionApproved } from "../shared/approval-requirements.js";
9
9
  import { loadCommandContext, loadTaskFromContext, } from "../shared/task-backend.js";
10
10
  import { backendIsLocalFileBackend, getTaskStore } from "../shared/task-store.js";
11
11
  import { readDirectWorkLock } from "../../shared/direct-work-lock.js";
12
- import { appendTaskEvent, defaultCommitEmojiForStatus, enforceStatusCommitPolicy, ensureVerificationSatisfiedIfRequired, nowIso, readCommitInfo, readHeadCommit, requireStructuredComment, } from "./shared.js";
12
+ import { appendTaskEvent, defaultCommitEmojiForStatus, enforceStatusCommitPolicy, ensureVerificationSatisfiedIfRequired, nowIso, readCommitInfo, readHeadCommit, requireStructuredComment, resolvePrimaryTag, toStringArray, } from "./shared.js";
13
13
  async function clearDirectWorkLockIfMatches(opts) {
14
14
  const lockPath = path.join(opts.agentplaneDir, "cache", "direct-work.json");
15
15
  try {
@@ -40,14 +40,6 @@ export async function cmdFinish(opts) {
40
40
  }
41
41
  const { prefix, min_chars: minChars } = ctx.config.tasks.comments.verified;
42
42
  requireStructuredComment(opts.body, prefix, minChars);
43
- if (opts.commitFromComment || opts.statusCommit) {
44
- enforceStatusCommitPolicy({
45
- policy: ctx.config.status_commit_policy,
46
- action: "finish",
47
- confirmed: opts.confirmStatusCommit,
48
- quiet: opts.quiet,
49
- });
50
- }
51
43
  if ((opts.commitFromComment || opts.statusCommit) && opts.taskIds.length !== 1) {
52
44
  throw new CliError({
53
45
  exitCode: 2,
@@ -55,6 +47,20 @@ export async function cmdFinish(opts) {
55
47
  message: "--commit-from-comment/--status-commit requires exactly one task id",
56
48
  });
57
49
  }
50
+ if (opts.closeCommit && opts.taskIds.length !== 1) {
51
+ throw new CliError({
52
+ exitCode: 2,
53
+ code: "E_USAGE",
54
+ message: "--close-commit requires exactly one task id",
55
+ });
56
+ }
57
+ if (opts.closeCommit && (opts.commitFromComment || opts.statusCommit)) {
58
+ throw new CliError({
59
+ exitCode: 2,
60
+ code: "E_USAGE",
61
+ message: "--close-commit cannot be combined with --commit-from-comment/--status-commit",
62
+ });
63
+ }
58
64
  const primaryTaskId = opts.taskIds[0] ?? "";
59
65
  if ((opts.commitFromComment || opts.statusCommit) && !primaryTaskId) {
60
66
  throw new CliError({
@@ -82,6 +88,7 @@ export async function cmdFinish(opts) {
82
88
  const riskLevel = opts.risk;
83
89
  const breaking = opts.breaking === true;
84
90
  let primaryStatusFrom = null;
91
+ let primaryTag = null;
85
92
  for (const taskId of opts.taskIds) {
86
93
  const task = useStore ? await store.get(taskId) : await loadTaskFromContext({ ctx, taskId });
87
94
  if (!opts.force) {
@@ -98,6 +105,7 @@ export async function cmdFinish(opts) {
98
105
  (opts.commitFromComment || opts.statusCommit) &&
99
106
  primaryStatusFrom === null) {
100
107
  primaryStatusFrom = String(task.status || "TODO").toUpperCase();
108
+ primaryTag = resolvePrimaryTag(toStringArray(task.tags), ctx).primary;
101
109
  }
102
110
  ensureVerificationSatisfiedIfRequired(task, ctx.config);
103
111
  if (taskId === metaTaskId) {
@@ -149,6 +157,16 @@ export async function cmdFinish(opts) {
149
157
  ? store.update(taskId, () => nextTask)
150
158
  : ctx.taskBackend.writeTask(nextTask));
151
159
  }
160
+ if (opts.commitFromComment || opts.statusCommit) {
161
+ enforceStatusCommitPolicy({
162
+ policy: ctx.config.status_commit_policy,
163
+ action: "finish",
164
+ confirmed: opts.confirmStatusCommit,
165
+ quiet: opts.quiet,
166
+ statusFrom: primaryStatusFrom ?? "UNKNOWN",
167
+ statusTo: "DONE",
168
+ });
169
+ }
152
170
  // tasks.json is export-only; generated via `agentplane task export`.
153
171
  let executorAgent = null;
154
172
  if (opts.commitFromComment || opts.statusCommit) {
@@ -174,6 +192,7 @@ export async function cmdFinish(opts) {
174
192
  cwd: opts.cwd,
175
193
  rootOverride: opts.rootOverride,
176
194
  taskId: primaryTaskId,
195
+ primaryTag: primaryTag ?? "meta",
177
196
  executorAgent: executorAgent ?? undefined,
178
197
  author: opts.author,
179
198
  statusFrom: primaryStatusFrom ?? undefined,
@@ -234,6 +253,7 @@ export async function cmdFinish(opts) {
234
253
  cwd: opts.cwd,
235
254
  rootOverride: opts.rootOverride,
236
255
  taskId: primaryTaskId,
256
+ primaryTag: primaryTag ?? "meta",
237
257
  executorAgent: executorAgent ?? undefined,
238
258
  author: opts.author,
239
259
  statusFrom: primaryStatusFrom ?? undefined,
@@ -249,6 +269,28 @@ export async function cmdFinish(opts) {
249
269
  config: ctx.config,
250
270
  });
251
271
  }
272
+ if (opts.closeCommit && primaryTaskId) {
273
+ await cmdCommit({
274
+ ctx,
275
+ cwd: opts.cwd,
276
+ rootOverride: opts.rootOverride,
277
+ taskId: primaryTaskId,
278
+ message: "",
279
+ close: true,
280
+ allow: [],
281
+ autoAllow: false,
282
+ allowTasks: true,
283
+ allowBase: false,
284
+ allowPolicy: false,
285
+ allowConfig: false,
286
+ allowHooks: false,
287
+ allowCI: false,
288
+ requireClean: true,
289
+ quiet: opts.quiet,
290
+ closeUnstageOthers: opts.closeUnstageOthers === true,
291
+ closeCheckOnly: false,
292
+ });
293
+ }
252
294
  if (ctx.config.workflow_mode === "direct") {
253
295
  await clearDirectWorkLockIfMatches({
254
296
  agentplaneDir: ctx.resolvedProject.agentplaneDir,
@@ -15,6 +15,9 @@ export { cmdTaskComment } from "./comment.js";
15
15
  export { cmdTaskSetStatus } from "./set-status.js";
16
16
  export { cmdTaskShow } from "./show.js";
17
17
  export { cmdTaskDerive } from "./derive.js";
18
+ export { cmdTaskCloseDuplicate } from "./close-duplicate.js";
19
+ export { cmdTaskStartReady } from "./start-ready.js";
20
+ export { cmdTaskCloseNoop } from "./close-noop.js";
18
21
  export { cmdTaskExport } from "./export.js";
19
22
  export { cmdTaskLint } from "./lint.js";
20
23
  export { cmdTaskPlanSet, cmdTaskPlanApprove, cmdTaskPlanReject } from "./plan.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/task/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,OAAO,EAAE,sBAAsB,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAChE,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAErD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExC,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAElF,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE1E,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAEzD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/task/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,OAAO,EAAE,sBAAsB,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAChE,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAErD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEnD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExC,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAElF,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE1E,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAEzD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"}
@@ -14,6 +14,9 @@ export { cmdTaskComment } from "./comment.js";
14
14
  export { cmdTaskSetStatus } from "./set-status.js";
15
15
  export { cmdTaskShow } from "./show.js";
16
16
  export { cmdTaskDerive } from "./derive.js";
17
+ export { cmdTaskCloseDuplicate } from "./close-duplicate.js";
18
+ export { cmdTaskStartReady } from "./start-ready.js";
19
+ export { cmdTaskCloseNoop } from "./close-noop.js";
17
20
  export { cmdTaskExport } from "./export.js";
18
21
  export { cmdTaskLint } from "./lint.js";
19
22
  export { cmdTaskPlanSet, cmdTaskPlanApprove, cmdTaskPlanReject } from "./plan.js";
@@ -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;AAGpF,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;AAEF,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,CA4DlB"}
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"}
@@ -2,7 +2,28 @@ import { mapBackendError } from "../../cli/error-map.js";
2
2
  import { backendNotSupportedMessage, warnMessage } from "../../cli/output.js";
3
3
  import { CliError } from "../../shared/errors.js";
4
4
  import { loadCommandContext } from "../shared/task-backend.js";
5
- import { nowIso, requiresVerify, warnIfUnknownOwner } from "./shared.js";
5
+ import { nowIso, requiresVerifyStepsByPrimary, resolvePrimaryTag, warnIfUnknownOwner, } from "./shared.js";
6
+ function buildDefaultVerifyStepsDoc(opts) {
7
+ const checks = opts.verifyCommands.length > 0
8
+ ? opts.verifyCommands.map((command) => `- \`${command}\``).join("\n")
9
+ : "- Add explicit checks/commands for this task before approval.";
10
+ return [
11
+ "## Verify Steps",
12
+ "",
13
+ "### Scope",
14
+ `- Primary tag: \`${opts.primary}\``,
15
+ "",
16
+ "### Checks",
17
+ checks,
18
+ "",
19
+ "### Evidence / Commands",
20
+ "- Record executed commands and key outputs.",
21
+ "",
22
+ "### Pass criteria",
23
+ "- Steps are reproducible and produce expected results.",
24
+ "",
25
+ ].join("\n");
26
+ }
6
27
  export async function runTaskNewParsed(opts) {
7
28
  const p = opts.parsed;
8
29
  try {
@@ -33,17 +54,24 @@ export async function runTaskNewParsed(opts) {
33
54
  doc_updated_by: p.owner,
34
55
  id_source: "generated",
35
56
  };
36
- const requireStepsTags = ctx.config.tasks.verify.require_steps_for_tags ?? ctx.config.tasks.verify.required_tags;
37
57
  const spikeTag = (ctx.config.tasks.verify.spike_tag ?? "spike").trim().toLowerCase();
38
- const requiresVerifySteps = requiresVerify(p.tags, requireStepsTags);
58
+ const primary = resolvePrimaryTag(p.tags, ctx);
59
+ if (primary.usedFallback) {
60
+ process.stderr.write(`${warnMessage(`primary tag not found in task tags; using fallback primary=${primary.primary}`)}\n`);
61
+ }
62
+ const requiresVerifySteps = requiresVerifyStepsByPrimary(p.tags, ctx.config);
39
63
  await warnIfUnknownOwner(ctx, p.owner);
40
64
  if (requiresVerifySteps) {
41
- process.stderr.write(`${warnMessage(`task requires ## Verify Steps in README; run \`agentplane task scaffold ${taskId}\` and fill it before approving the plan`)}\n`);
65
+ task.doc = buildDefaultVerifyStepsDoc({
66
+ primary: primary.primary,
67
+ verifyCommands: p.verify,
68
+ });
69
+ 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`);
42
70
  }
43
71
  const hasSpike = p.tags.some((tag) => tag.trim().toLowerCase() === spikeTag);
44
- const hasImplementationTags = requiresVerify(p.tags, ctx.config.tasks.verify.required_tags);
72
+ const hasImplementationTags = requiresVerifyStepsByPrimary(p.tags, ctx.config);
45
73
  if (hasSpike && hasImplementationTags) {
46
- process.stderr.write(`${warnMessage("spike is combined with code/backend/frontend tags; consider splitting spike vs implementation tasks")}\n`);
74
+ process.stderr.write(`${warnMessage("spike is combined with a primary tag that requires verify steps; consider splitting spike vs implementation tasks")}\n`);
47
75
  }
48
76
  await ctx.taskBackend.writeTask(task);
49
77
  process.stdout.write(`${taskId}\n`);
@@ -65,7 +65,7 @@ export const taskNewSpec = {
65
65
  },
66
66
  ],
67
67
  notes: [
68
- "This command may emit warnings for tasks that require Verify Steps in the README (config-dependent).",
68
+ "For verify-required primary tags, this command seeds a default ## Verify Steps section in README.",
69
69
  ],
70
70
  parse: (raw) => ({
71
71
  title: raw.opts.title,
@@ -1 +1 @@
1
- {"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../../src/commands/task/plan.ts"],"names":[],"mappings":"AASA,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,2BAA2B,CAAC;AA+CnC,wBAAsB,cAAc,CAAC,IAAI,EAAE;IACzC,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,MAAM,CAAC,CAmElB;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE;IAC7C,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC,MAAM,CAAC,CAkFlB;AAED,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,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,OAAO,CAAC,MAAM,CAAC,CAuDlB"}
1
+ {"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../../src/commands/task/plan.ts"],"names":[],"mappings":"AASA,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,2BAA2B,CAAC;AA+CnC,wBAAsB,cAAc,CAAC,IAAI,EAAE;IACzC,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,MAAM,CAAC,CAmElB;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE;IAC7C,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC,MAAM,CAAC,CAgFlB;AAED,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,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,OAAO,CAAC,MAAM,CAAC,CAuDlB"}
@@ -6,7 +6,7 @@ import { backendNotSupportedMessage } from "../../cli/output.js";
6
6
  import { CliError } from "../../shared/errors.js";
7
7
  import { loadCommandContext, loadTaskFromContext, } from "../shared/task-backend.js";
8
8
  import { backendIsLocalFileBackend, getTaskStore } from "../shared/task-store.js";
9
- import { extractDocSection, isVerifyStepsFilled, nowIso, requiresVerify, toStringArray, } from "./shared.js";
9
+ import { extractDocSection, isVerifyStepsFilled, nowIso, requiresVerifyStepsByPrimary, toStringArray, } from "./shared.js";
10
10
  async function loadPlanTask(opts) {
11
11
  const ctx = opts.ctx ??
12
12
  (await loadCommandContext({ cwd: opts.cwd, rootOverride: opts.rootOverride ?? null }));
@@ -120,9 +120,8 @@ export async function cmdTaskPlanApprove(opts) {
120
120
  const enforceVerifySteps = config.tasks.verify.enforce_on_plan_approve !== false;
121
121
  if (enforceVerifySteps) {
122
122
  const tags = toStringArray(task.tags);
123
- const requireStepsTags = config.tasks.verify.require_steps_for_tags ?? config.tasks.verify.required_tags;
124
123
  const spikeTag = (config.tasks.verify.spike_tag ?? "spike").trim().toLowerCase();
125
- const verifyRequired = requiresVerify(tags, requireStepsTags);
124
+ const verifyRequired = requiresVerifyStepsByPrimary(tags, config);
126
125
  const isSpike = tags.some((tag) => tag.trim().toLowerCase() === spikeTag);
127
126
  if (verifyRequired || isSpike) {
128
127
  const verifySteps = extractDocSection(baseDoc, "Verify Steps");
@@ -1 +1 @@
1
- {"version":3,"file":"set-status.d.ts","sourceRoot":"","sources":["../../../src/commands/task/set-status.ts"],"names":[],"mappings":"AAQA,OAAO,EAKL,KAAK,cAAc,EACpB,MAAM,2BAA2B,CAAC;AAcnC,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,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,EAAE,OAAO,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;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CA0JlB"}
1
+ {"version":3,"file":"set-status.d.ts","sourceRoot":"","sources":["../../../src/commands/task/set-status.ts"],"names":[],"mappings":"AAQA,OAAO,EAKL,KAAK,cAAc,EACpB,MAAM,2BAA2B,CAAC;AAgBnC,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,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,EAAE,OAAO,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;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CA6JlB"}
@@ -6,7 +6,7 @@ import { commitFromComment } from "../guard/index.js";
6
6
  import { ensureActionApproved } from "../shared/approval-requirements.js";
7
7
  import { listTasksMemo, loadCommandContext, loadTaskFromContext, resolveDocUpdatedBy, } from "../shared/task-backend.js";
8
8
  import { backendIsLocalFileBackend, getTaskStore } from "../shared/task-store.js";
9
- import { appendTaskEvent, buildDependencyState, defaultCommitEmojiForStatus, enforceStatusCommitPolicy, isTransitionAllowed, normalizeTaskStatus, nowIso, readCommitInfo, } from "./shared.js";
9
+ import { appendTaskEvent, buildDependencyState, defaultCommitEmojiForStatus, enforceStatusCommitPolicy, isTransitionAllowed, normalizeTaskStatus, nowIso, readCommitInfo, resolvePrimaryTag, toStringArray, } from "./shared.js";
10
10
  export async function cmdTaskSetStatus(opts) {
11
11
  const nextStatus = normalizeTaskStatus(opts.status);
12
12
  if (nextStatus === "DONE" && !opts.force) {
@@ -41,14 +41,6 @@ export async function cmdTaskSetStatus(opts) {
41
41
  const task = useStore
42
42
  ? await store.get(opts.taskId)
43
43
  : await loadTaskFromContext({ ctx, taskId: opts.taskId });
44
- if (opts.commitFromComment) {
45
- enforceStatusCommitPolicy({
46
- policy: config.status_commit_policy,
47
- action: "task set-status",
48
- confirmed: opts.confirmStatusCommit,
49
- quiet: opts.quiet,
50
- });
51
- }
52
44
  const currentStatus = String(task.status || "TODO").toUpperCase();
53
45
  if (!opts.force && !isTransitionAllowed(currentStatus, nextStatus)) {
54
46
  throw new CliError({
@@ -77,6 +69,16 @@ export async function cmdTaskSetStatus(opts) {
77
69
  });
78
70
  }
79
71
  }
72
+ if (opts.commitFromComment) {
73
+ enforceStatusCommitPolicy({
74
+ policy: config.status_commit_policy,
75
+ action: "task set-status",
76
+ confirmed: opts.confirmStatusCommit,
77
+ quiet: opts.quiet,
78
+ statusFrom: currentStatus,
79
+ statusTo: nextStatus,
80
+ });
81
+ }
80
82
  const existingComments = Array.isArray(task.comments)
81
83
  ? task.comments.filter((item) => !!item && typeof item.author === "string" && typeof item.body === "string")
82
84
  : [];
@@ -125,6 +127,7 @@ export async function cmdTaskSetStatus(opts) {
125
127
  cwd: opts.cwd,
126
128
  rootOverride: opts.rootOverride,
127
129
  taskId: opts.taskId,
130
+ primaryTag: resolvePrimaryTag(toStringArray(task.tags), ctx).primary,
128
131
  author: opts.author,
129
132
  statusFrom: currentStatus,
130
133
  statusTo: nextStatus,