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
@@ -12,6 +12,22 @@ export declare function normalizeDependsOnInput(value: string): string[];
12
12
  export declare function normalizeTaskStatus(value: string): string;
13
13
  export declare function toStringArray(value: unknown): string[];
14
14
  export declare function requiresVerify(tags: string[], requiredTags: string[]): boolean;
15
+ export type PrimaryTagResolution = {
16
+ primary: string;
17
+ matched: string[];
18
+ usedFallback: boolean;
19
+ };
20
+ export type TaskTagPolicy = {
21
+ primaryAllowlist: string[];
22
+ strictPrimary: boolean;
23
+ fallbackPrimary: string;
24
+ lockPrimaryOnUpdate: boolean;
25
+ };
26
+ export declare function readTaskTagPolicy(input: CommandContext | AgentplaneConfig): TaskTagPolicy;
27
+ export declare function resolvePrimaryTagFromConfig(tags: string[], config: AgentplaneConfig): PrimaryTagResolution;
28
+ export declare function requiresVerifyStepsByPrimary(tags: string[], config: AgentplaneConfig): boolean;
29
+ export declare function requiresVerificationByPrimary(tags: string[], config: AgentplaneConfig): boolean;
30
+ export declare function resolvePrimaryTag(tags: string[], ctx: CommandContext): PrimaryTagResolution;
15
31
  export declare function warnIfUnknownOwner(ctx: CommandContext, owner: string): Promise<void>;
16
32
  export declare function appendTaskEvent(task: TaskData, event: TaskEvent): TaskEvent[];
17
33
  export declare function ensurePlanApprovedIfRequired(task: TaskData, config: AgentplaneConfig): void;
@@ -34,7 +50,10 @@ export declare function enforceStatusCommitPolicy(opts: {
34
50
  action: string;
35
51
  confirmed: boolean;
36
52
  quiet: boolean;
53
+ statusFrom: string;
54
+ statusTo: string;
37
55
  }): void;
56
+ export declare function isMajorStatusCommitTransition(statusFrom: string, statusTo: string): boolean;
38
57
  export declare function readCommitInfo(cwd: string, rev: string): Promise<{
39
58
  hash: string;
40
59
  message: string;
@@ -1 +1 @@
1
- {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../../src/commands/task/shared.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../../src/commands/task/shared.ts"],"names":[],"mappings":"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"}
@@ -9,6 +9,7 @@ import { exitCodeForError } from "../../cli/exit-codes.js";
9
9
  import { CliError } from "../../shared/errors.js";
10
10
  import { dedupeStrings } from "../../shared/strings.js";
11
11
  import { parseGitLogHashSubject } from "../../shared/git-log.js";
12
+ import { isRecord } from "../../shared/guards.js";
12
13
  export { dedupeStrings } from "../../shared/strings.js";
13
14
  export const execFileAsync = promisify(execFile);
14
15
  async function listAgentIdsMemo(ctx) {
@@ -87,6 +88,115 @@ export function requiresVerify(tags, requiredTags) {
87
88
  return false;
88
89
  return tags.some((tag) => required.has(tag.trim().toLowerCase()));
89
90
  }
91
+ function readStringArray(value) {
92
+ if (!Array.isArray(value))
93
+ return [];
94
+ return value
95
+ .filter((item) => typeof item === "string")
96
+ .map((item) => item.trim())
97
+ .filter((item) => item.length > 0);
98
+ }
99
+ function readBoolean(value, fallback) {
100
+ return typeof value === "boolean" ? value : fallback;
101
+ }
102
+ function readString(value, fallback) {
103
+ if (typeof value !== "string")
104
+ return fallback;
105
+ const trimmed = value.trim();
106
+ return trimmed.length > 0 ? trimmed : fallback;
107
+ }
108
+ function configFromInput(input) {
109
+ return "config" in input ? input.config : input;
110
+ }
111
+ export function readTaskTagPolicy(input) {
112
+ const config = configFromInput(input);
113
+ const tasks = isRecord(config.tasks) ? config.tasks : {};
114
+ const rawTagsCandidate = tasks.tags;
115
+ const rawTags = isRecord(rawTagsCandidate) ? rawTagsCandidate : {};
116
+ const fallbackAllowlist = ["code", "data", "research", "docs", "ops", "product", "meta"];
117
+ const normalizedAllowlist = dedupeStrings(readStringArray(rawTags.primary_allowlist)
118
+ .map((tag) => tag.toLowerCase())
119
+ .filter(Boolean));
120
+ return {
121
+ primaryAllowlist: normalizedAllowlist.length > 0 ? normalizedAllowlist : fallbackAllowlist,
122
+ strictPrimary: readBoolean(rawTags.strict_primary, false),
123
+ fallbackPrimary: readString(rawTags.fallback_primary, "meta").toLowerCase(),
124
+ lockPrimaryOnUpdate: readBoolean(rawTags.lock_primary_on_update, true),
125
+ };
126
+ }
127
+ function readVerifyPrimaryPolicy(config) {
128
+ const tasks = isRecord(config.tasks) ? config.tasks : {};
129
+ const rawVerifyCandidate = tasks.verify;
130
+ const rawVerify = isRecord(rawVerifyCandidate)
131
+ ? rawVerifyCandidate
132
+ : {};
133
+ const explicitSteps = dedupeStrings(readStringArray(rawVerify.require_steps_for_primary)
134
+ .map((tag) => tag.toLowerCase())
135
+ .filter(Boolean));
136
+ const legacySteps = dedupeStrings(readStringArray(rawVerify.require_steps_for_tags ?? rawVerify.required_tags)
137
+ .map((tag) => tag.toLowerCase())
138
+ .filter(Boolean));
139
+ const requireStepsForPrimary = new Set(explicitSteps.length > 0 ? explicitSteps : legacySteps);
140
+ const explicitVerification = dedupeStrings(readStringArray(rawVerify.require_verification_for_primary)
141
+ .map((tag) => tag.toLowerCase())
142
+ .filter(Boolean));
143
+ const requireVerificationForPrimary = new Set(explicitVerification.length > 0 ? explicitVerification : [...requireStepsForPrimary.values()]);
144
+ return { requireStepsForPrimary, requireVerificationForPrimary };
145
+ }
146
+ export function resolvePrimaryTagFromConfig(tags, config) {
147
+ const policy = readTaskTagPolicy(config);
148
+ const allowlist = policy.primaryAllowlist;
149
+ if (allowlist.length === 0) {
150
+ throw new CliError({
151
+ exitCode: 3,
152
+ code: "E_VALIDATION",
153
+ message: "tasks.tags.primary_allowlist must contain at least one tag.",
154
+ });
155
+ }
156
+ const normalizedTags = dedupeStrings(tags.map((t) => t.trim().toLowerCase()).filter(Boolean));
157
+ const matched = normalizedTags.filter((tag) => allowlist.includes(tag));
158
+ if (matched.length > 1) {
159
+ throw new CliError({
160
+ exitCode: exitCodeForError("E_USAGE"),
161
+ code: "E_USAGE",
162
+ message: `Task must include exactly one primary tag from allowlist (${allowlist.join(", ")}); ` +
163
+ `found multiple: ${matched.join(", ")}`,
164
+ });
165
+ }
166
+ if (matched.length === 1) {
167
+ return { primary: matched[0], matched, usedFallback: false };
168
+ }
169
+ const strict = policy.strictPrimary;
170
+ if (strict) {
171
+ throw new CliError({
172
+ exitCode: exitCodeForError("E_USAGE"),
173
+ code: "E_USAGE",
174
+ message: `Task must include exactly one primary tag from allowlist (${allowlist.join(", ")}); ` +
175
+ "none found and strict_primary=true",
176
+ });
177
+ }
178
+ const fallback = policy.fallbackPrimary;
179
+ if (!fallback || !allowlist.includes(fallback)) {
180
+ throw new CliError({
181
+ exitCode: 3,
182
+ code: "E_VALIDATION",
183
+ message: `tasks.tags.fallback_primary=${JSON.stringify(policy.fallbackPrimary)} ` +
184
+ `must be present in tasks.tags.primary_allowlist (${allowlist.join(", ")}).`,
185
+ });
186
+ }
187
+ return { primary: fallback, matched, usedFallback: true };
188
+ }
189
+ export function requiresVerifyStepsByPrimary(tags, config) {
190
+ const primary = resolvePrimaryTagFromConfig(tags, config).primary;
191
+ return readVerifyPrimaryPolicy(config).requireStepsForPrimary.has(primary);
192
+ }
193
+ export function requiresVerificationByPrimary(tags, config) {
194
+ const primary = resolvePrimaryTagFromConfig(tags, config).primary;
195
+ return readVerifyPrimaryPolicy(config).requireVerificationForPrimary.has(primary);
196
+ }
197
+ export function resolvePrimaryTag(tags, ctx) {
198
+ return resolvePrimaryTagFromConfig(tags, ctx.config);
199
+ }
90
200
  export async function warnIfUnknownOwner(ctx, owner) {
91
201
  const trimmed = owner.trim();
92
202
  if (!trimmed)
@@ -125,6 +235,8 @@ export function ensurePlanApprovedIfRequired(task, config) {
125
235
  export function ensureVerificationSatisfiedIfRequired(task, config) {
126
236
  if (config.agents?.approvals?.require_verify !== true)
127
237
  return;
238
+ if (!requiresVerificationByPrimary(toStringArray(task.tags), config))
239
+ return;
128
240
  const state = task.verification?.state ?? "missing";
129
241
  if (state === "ok")
130
242
  return;
@@ -232,6 +344,14 @@ export async function readHeadCommit(cwd) {
232
344
  return { hash, message: subject };
233
345
  }
234
346
  export function enforceStatusCommitPolicy(opts) {
347
+ if (!isMajorStatusCommitTransition(opts.statusFrom, opts.statusTo)) {
348
+ throw new CliError({
349
+ exitCode: 2,
350
+ code: "E_USAGE",
351
+ message: `${opts.action}: status/comment-driven commit is allowed only for major transitions ` +
352
+ `(got ${opts.statusFrom.toUpperCase()} -> ${opts.statusTo.toUpperCase()})`,
353
+ });
354
+ }
235
355
  if (opts.policy === "off")
236
356
  return;
237
357
  if (opts.policy === "warn") {
@@ -249,6 +369,23 @@ export function enforceStatusCommitPolicy(opts) {
249
369
  });
250
370
  }
251
371
  }
372
+ const MAJOR_STATUS_COMMIT_TRANSITIONS = new Set([
373
+ "READY->DOING",
374
+ "TODO->DOING",
375
+ "DOING->BLOCKED",
376
+ "BLOCKED->DOING",
377
+ "DOING->DONE",
378
+ "DONE->VERIFIED",
379
+ "VERIFIED->FINISHED",
380
+ "VERIFIED->EXPORTED",
381
+ ]);
382
+ export function isMajorStatusCommitTransition(statusFrom, statusTo) {
383
+ const from = statusFrom.trim().toUpperCase();
384
+ const to = statusTo.trim().toUpperCase();
385
+ if (!from || !to)
386
+ return false;
387
+ return MAJOR_STATUS_COMMIT_TRANSITIONS.has(`${from}->${to}`);
388
+ }
252
389
  export async function readCommitInfo(cwd, rev) {
253
390
  const { stdout } = await execFileAsync("git", ["log", "-1", "--pretty=%H%x00%s", rev], { cwd });
254
391
  const { hash, subject } = parseGitLogHashSubject(stdout);
@@ -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 TaskStartReadyParsed = {
4
+ taskId: string;
5
+ author: string;
6
+ body: string;
7
+ force: boolean;
8
+ yes: boolean;
9
+ quiet: boolean;
10
+ };
11
+ export declare const taskStartReadySpec: CommandSpec<TaskStartReadyParsed>;
12
+ export declare function makeRunTaskStartReadyHandler(getCtx: (cmd: string) => Promise<CommandContext>): (ctx: CommandCtx, p: TaskStartReadyParsed) => Promise<number>;
13
+ export {};
14
+ //# sourceMappingURL=start-ready.command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"start-ready.command.d.ts","sourceRoot":"","sources":["../../../src/commands/task/start-ready.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,oBAAoB,GAAG;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,EAAE,OAAO,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,eAAO,MAAM,kBAAkB,EAAE,WAAW,CAAC,oBAAoB,CA2DhE,CAAC;AAEF,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,IAC7E,KAAK,UAAU,EAAE,GAAG,oBAAoB,KAAG,OAAO,CAAC,MAAM,CAAC,CAazE"}
@@ -0,0 +1,77 @@
1
+ import { usageError } from "../../cli/spec/errors.js";
2
+ import { cmdTaskStartReady } from "./start-ready.js";
3
+ export const taskStartReadySpec = {
4
+ id: ["task", "start-ready"],
5
+ group: "Task",
6
+ summary: "Run readiness checks and start a task in one deterministic 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. CODER).",
15
+ },
16
+ {
17
+ kind: "string",
18
+ name: "body",
19
+ valueHint: "<text>",
20
+ required: true,
21
+ description: "Structured Start comment body.",
22
+ },
23
+ {
24
+ kind: "boolean",
25
+ name: "force",
26
+ default: false,
27
+ description: "Override readiness and transition checks.",
28
+ },
29
+ {
30
+ kind: "boolean",
31
+ name: "yes",
32
+ default: false,
33
+ description: "Auto-approve force-action checks when required.",
34
+ },
35
+ { kind: "boolean", name: "quiet", default: false, description: "Suppress output." },
36
+ ],
37
+ examples: [
38
+ {
39
+ cmd: 'agentplane task start-ready 202602030608-F1Q8AB --author CODER --body "Start: implement parser hardening"',
40
+ why: "Check readiness and transition to DOING without command chaining.",
41
+ },
42
+ ],
43
+ validateRaw: (raw) => {
44
+ const taskId = typeof raw.args["task-id"] === "string" ? raw.args["task-id"].trim() : "";
45
+ const author = typeof raw.opts.author === "string" ? raw.opts.author.trim() : "";
46
+ const body = typeof raw.opts.body === "string" ? raw.opts.body.trim() : "";
47
+ if (!taskId)
48
+ throw usageError({ spec: taskStartReadySpec, message: "Invalid value for task-id: empty." });
49
+ if (!author)
50
+ throw usageError({ spec: taskStartReadySpec, message: "Invalid value for --author: empty." });
51
+ if (!body)
52
+ throw usageError({ spec: taskStartReadySpec, message: "Invalid value for --body: empty." });
53
+ },
54
+ parse: (raw) => ({
55
+ taskId: String(raw.args["task-id"]),
56
+ author: String(raw.opts.author),
57
+ body: String(raw.opts.body),
58
+ force: raw.opts.force === true,
59
+ yes: raw.opts.yes === true,
60
+ quiet: raw.opts.quiet === true,
61
+ }),
62
+ };
63
+ export function makeRunTaskStartReadyHandler(getCtx) {
64
+ return async (ctx, p) => {
65
+ return await cmdTaskStartReady({
66
+ ctx: await getCtx("task start-ready"),
67
+ cwd: ctx.cwd,
68
+ rootOverride: ctx.rootOverride,
69
+ taskId: p.taskId,
70
+ author: p.author,
71
+ body: p.body,
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 cmdTaskStartReady(opts: {
3
+ ctx?: CommandContext;
4
+ cwd: string;
5
+ rootOverride?: string;
6
+ taskId: string;
7
+ author: string;
8
+ body: string;
9
+ force: boolean;
10
+ yes: boolean;
11
+ quiet: boolean;
12
+ }): Promise<number>;
13
+ //# sourceMappingURL=start-ready.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"start-ready.d.ts","sourceRoot":"","sources":["../../../src/commands/task/start-ready.ts"],"names":[],"mappings":"AAEA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAKpF,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,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,EAAE,OAAO,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CAuClB"}
@@ -0,0 +1,46 @@
1
+ import { mapBackendError } from "../../cli/error-map.js";
2
+ import { CliError } from "../../shared/errors.js";
3
+ import { loadCommandContext } from "../shared/task-backend.js";
4
+ import { cmdReady } from "./ready.js";
5
+ import { cmdStart } from "./start.js";
6
+ export async function cmdTaskStartReady(opts) {
7
+ try {
8
+ const ctx = opts.ctx ??
9
+ (await loadCommandContext({ cwd: opts.cwd, rootOverride: opts.rootOverride ?? null }));
10
+ const readyCode = await cmdReady({
11
+ ctx,
12
+ cwd: opts.cwd,
13
+ rootOverride: opts.rootOverride,
14
+ taskId: opts.taskId,
15
+ });
16
+ if (readyCode !== 0 && !opts.force) {
17
+ throw new CliError({
18
+ exitCode: 2,
19
+ code: "E_USAGE",
20
+ message: `Task is not ready: ${opts.taskId} (resolve dependencies or use --force)`,
21
+ });
22
+ }
23
+ return await cmdStart({
24
+ ctx,
25
+ cwd: opts.cwd,
26
+ rootOverride: opts.rootOverride,
27
+ taskId: opts.taskId,
28
+ author: opts.author,
29
+ body: opts.body,
30
+ commitFromComment: false,
31
+ commitAllow: [],
32
+ commitAutoAllow: false,
33
+ commitAllowTasks: true,
34
+ commitRequireClean: false,
35
+ confirmStatusCommit: false,
36
+ force: opts.force,
37
+ yes: opts.yes,
38
+ quiet: opts.quiet,
39
+ });
40
+ }
41
+ catch (err) {
42
+ if (err instanceof CliError)
43
+ throw err;
44
+ throw mapBackendError(err, { command: "task start-ready", root: opts.rootOverride ?? null });
45
+ }
46
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../../src/commands/task/start.ts"],"names":[],"mappings":"AAQA,OAAO,EAIL,KAAK,cAAc,EACpB,MAAM,2BAA2B,CAAC;AAoBnC,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,CAoMlB"}
1
+ {"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../../src/commands/task/start.ts"],"names":[],"mappings":"AAQA,OAAO,EAIL,KAAK,cAAc,EACpB,MAAM,2BAA2B,CAAC;AAqBnC,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,CAqMlB"}
@@ -7,7 +7,7 @@ import { ensureActionApproved } from "../shared/approval-requirements.js";
7
7
  import { listTasksMemo, 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, buildDependencyState, ensurePlanApprovedIfRequired, enforceStatusCommitPolicy, defaultCommitEmojiForAgentId, extractDocSection, isVerifyStepsFilled, isTransitionAllowed, nowIso, requiresVerify, requireStructuredComment, toStringArray, } from "./shared.js";
10
+ import { appendTaskEvent, buildDependencyState, ensurePlanApprovedIfRequired, enforceStatusCommitPolicy, defaultCommitEmojiForAgentId, extractDocSection, isVerifyStepsFilled, isTransitionAllowed, nowIso, requiresVerifyStepsByPrimary, requireStructuredComment, resolvePrimaryTag, toStringArray, } from "./shared.js";
11
11
  export async function cmdStart(opts) {
12
12
  try {
13
13
  const ctx = opts.ctx ??
@@ -20,14 +20,6 @@ export async function cmdStart(opts) {
20
20
  reason: "start --force",
21
21
  });
22
22
  }
23
- if (opts.commitFromComment) {
24
- enforceStatusCommitPolicy({
25
- policy: ctx.config.status_commit_policy,
26
- action: "start",
27
- confirmed: opts.confirmStatusCommit,
28
- quiet: opts.quiet,
29
- });
30
- }
31
23
  const { prefix, min_chars: minChars } = ctx.config.tasks.comments.start;
32
24
  requireStructuredComment(opts.body, prefix, minChars);
33
25
  const useStore = backendIsLocalFileBackend(ctx);
@@ -39,9 +31,8 @@ export async function cmdStart(opts) {
39
31
  const enforce = ctx.config.tasks.verify.enforce_on_start_when_no_plan !== false;
40
32
  if (enforce) {
41
33
  const tags = toStringArray(task.tags);
42
- const requireStepsTags = ctx.config.tasks.verify.require_steps_for_tags ?? ctx.config.tasks.verify.required_tags;
43
34
  const spikeTag = (ctx.config.tasks.verify.spike_tag ?? "spike").trim().toLowerCase();
44
- const verifyRequired = requiresVerify(tags, requireStepsTags);
35
+ const verifyRequired = requiresVerifyStepsByPrimary(tags, ctx.config);
45
36
  const isSpike = tags.some((tag) => tag.trim().toLowerCase() === spikeTag);
46
37
  const doc = typeof task.doc === "string" ? task.doc : "";
47
38
  if (verifyRequired || isSpike) {
@@ -77,6 +68,16 @@ export async function cmdStart(opts) {
77
68
  message: `Refusing status transition ${currentStatus} -> DOING (use --force to override)`,
78
69
  });
79
70
  }
71
+ if (opts.commitFromComment) {
72
+ enforceStatusCommitPolicy({
73
+ policy: ctx.config.status_commit_policy,
74
+ action: "start",
75
+ confirmed: opts.confirmStatusCommit,
76
+ quiet: opts.quiet,
77
+ statusFrom: currentStatus,
78
+ statusTo: "DOING",
79
+ });
80
+ }
80
81
  if (!opts.force) {
81
82
  const allTasks = await listTasksMemo(ctx);
82
83
  const depState = buildDependencyState(allTasks);
@@ -151,6 +152,7 @@ export async function cmdStart(opts) {
151
152
  cwd: opts.cwd,
152
153
  rootOverride: opts.rootOverride,
153
154
  taskId: opts.taskId,
155
+ primaryTag: resolvePrimaryTag(toStringArray(task.tags), ctx).primary,
154
156
  executorAgent,
155
157
  author: opts.author,
156
158
  statusFrom: currentStatus,
@@ -12,6 +12,7 @@ export type TaskUpdateParsed = {
12
12
  replaceDependsOn: boolean;
13
13
  verify: string[];
14
14
  replaceVerify: boolean;
15
+ allowPrimaryChange: boolean;
15
16
  };
16
17
  export declare const taskUpdateSpec: CommandSpec<TaskUpdateParsed>;
17
18
  export declare function makeRunTaskUpdateHandler(getCtx: (cmd: string) => Promise<CommandContext>): (ctx: CommandCtx, p: TaskUpdateParsed) => Promise<number>;
@@ -1 +1 @@
1
- {"version":3,"file":"update.command.d.ts","sourceRoot":"","sources":["../../../src/commands/task/update.command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAGtE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAKhE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;IAC7C,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;CACxB,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,WAAW,CAAC,gBAAgB,CAiHxD,CAAC;AAEF,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,IACzE,KAAK,UAAU,EAAE,GAAG,gBAAgB,KAAG,OAAO,CAAC,MAAM,CAAC,CAkBrE"}
1
+ {"version":3,"file":"update.command.d.ts","sourceRoot":"","sources":["../../../src/commands/task/update.command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAGtE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAKhE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;IAC7C,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,EAAE,OAAO,CAAC;CAC7B,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,WAAW,CAAC,gBAAgB,CAwHxD,CAAC;AAEF,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,IACzE,KAAK,UAAU,EAAE,GAAG,gBAAgB,KAAG,OAAO,CAAC,MAAM,CAAC,CAmBrE"}
@@ -63,6 +63,12 @@ export const taskUpdateSpec = {
63
63
  default: false,
64
64
  description: "Replace existing verify commands (instead of merging).",
65
65
  },
66
+ {
67
+ kind: "boolean",
68
+ name: "allow-primary-change",
69
+ default: false,
70
+ description: "Explicitly allow changing the task primary tag during update.",
71
+ },
66
72
  ],
67
73
  examples: [
68
74
  {
@@ -110,6 +116,7 @@ export const taskUpdateSpec = {
110
116
  replaceDependsOn: raw.opts["replace-depends-on"] === true,
111
117
  verify,
112
118
  replaceVerify: raw.opts["replace-verify"] === true,
119
+ allowPrimaryChange: raw.opts["allow-primary-change"] === true,
113
120
  };
114
121
  },
115
122
  };
@@ -130,6 +137,7 @@ export function makeRunTaskUpdateHandler(getCtx) {
130
137
  replaceDependsOn: p.replaceDependsOn,
131
138
  verify: p.verify,
132
139
  replaceVerify: p.replaceVerify,
140
+ allowPrimaryChange: p.allowPrimaryChange,
133
141
  });
134
142
  };
135
143
  }
@@ -14,5 +14,6 @@ export declare function cmdTaskUpdate(opts: {
14
14
  replaceDependsOn: boolean;
15
15
  verify: string[];
16
16
  replaceVerify: boolean;
17
+ allowPrimaryChange?: boolean;
17
18
  }): Promise<number>;
18
19
  //# sourceMappingURL=update.d.ts.map
@@ -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;AAGpF,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;CACxB,GAAG,OAAO,CAAC,MAAM,CAAC,CAqDlB"}
1
+ {"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../../src/commands/task/update.ts"],"names":[],"mappings":"AAIA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAUpF,wBAAsB,aAAa,CAAC,IAAI,EAAE;IACxC,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,aAAa,EAAE,OAAO,CAAC;IACvB,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B,GAAG,OAAO,CAAC,MAAM,CAAC,CAwElB"}
@@ -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, requiresVerify, toStringArray, warnIfUnknownOwner } from "./shared.js";
5
+ import { dedupeStrings, requiresVerifyStepsByPrimary, readTaskTagPolicy, resolvePrimaryTag, toStringArray, warnIfUnknownOwner, } from "./shared.js";
6
6
  export async function cmdTaskUpdate(opts) {
7
7
  try {
8
8
  const ctx = opts.ctx ??
@@ -26,14 +26,27 @@ export async function cmdTaskUpdate(opts) {
26
26
  next.owner = opts.owner;
27
27
  await warnIfUnknownOwner(ctx, opts.owner);
28
28
  }
29
+ const currentPrimary = resolvePrimaryTag(toStringArray(next.tags), ctx).primary;
29
30
  const existingTags = opts.replaceTags ? [] : dedupeStrings(toStringArray(next.tags));
30
31
  const mergedTags = dedupeStrings([...existingTags, ...opts.tags]);
32
+ const nextPrimary = resolvePrimaryTag(mergedTags, ctx).primary;
33
+ const tagPolicy = readTaskTagPolicy(ctx);
34
+ if (currentPrimary !== nextPrimary &&
35
+ tagPolicy.lockPrimaryOnUpdate === true &&
36
+ opts.allowPrimaryChange !== true) {
37
+ throw new CliError({
38
+ exitCode: 2,
39
+ code: "E_USAGE",
40
+ message: `Primary tag change is locked (${currentPrimary} -> ${nextPrimary}). ` +
41
+ "Use --allow-primary-change to override explicitly.",
42
+ });
43
+ }
31
44
  next.tags = mergedTags;
32
45
  const spikeTag = (ctx.config.tasks.verify.spike_tag ?? "spike").trim().toLowerCase();
33
46
  const hasSpike = mergedTags.some((tag) => tag.trim().toLowerCase() === spikeTag);
34
- const hasImplementationTags = requiresVerify(mergedTags, ctx.config.tasks.verify.required_tags);
47
+ const hasImplementationTags = requiresVerifyStepsByPrimary(mergedTags, ctx.config);
35
48
  if (hasSpike && hasImplementationTags) {
36
- process.stderr.write(`${warnMessage("spike is combined with code/backend/frontend tags; consider splitting spike vs implementation tasks")}\n`);
49
+ process.stderr.write(`${warnMessage("spike is combined with a primary tag that requires verify steps; consider splitting spike vs implementation tasks")}\n`);
37
50
  }
38
51
  const existingDepends = opts.replaceDependsOn
39
52
  ? []
@@ -48,6 +61,9 @@ export async function cmdTaskUpdate(opts) {
48
61
  return 0;
49
62
  }
50
63
  catch (err) {
64
+ if (err instanceof CliError) {
65
+ throw err;
66
+ }
51
67
  throw mapBackendError(err, { command: "task update", root: opts.rootOverride ?? null });
52
68
  }
53
69
  }
@@ -10,5 +10,13 @@ export declare class CliError extends Error {
10
10
  context?: Record<string, unknown>;
11
11
  });
12
12
  }
13
- export declare function formatJsonError(err: CliError): string;
13
+ export type JsonErrorGuidance = {
14
+ hint?: string;
15
+ nextAction?: {
16
+ command: string;
17
+ reason: string;
18
+ reasonCode?: string;
19
+ };
20
+ };
21
+ export declare function formatJsonError(err: CliError, guidance?: JsonErrorGuidance): string;
14
22
  //# sourceMappingURL=errors.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/shared/errors.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GACjB,SAAS,GACT,cAAc,GACd,MAAM,GACN,OAAO,GACP,WAAW,GACX,WAAW,GACX,YAAY,CAAC;AAEjB,qBAAa,QAAS,SAAQ,KAAK;IACjC,SAAgB,QAAQ,EAAE,MAAM,CAAC;IACjC,SAAgB,IAAI,EAAE,SAAS,CAAC;IAChC,SAAgB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;gBAEtC,IAAI,EAAE;QAChB,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,SAAS,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACnC;CAMF;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,QAAQ,GAAG,MAAM,CAYrD"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/shared/errors.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GACjB,SAAS,GACT,cAAc,GACd,MAAM,GACN,OAAO,GACP,WAAW,GACX,WAAW,GACX,YAAY,CAAC;AAEjB,qBAAa,QAAS,SAAQ,KAAK;IACjC,SAAgB,QAAQ,EAAE,MAAM,CAAC;IACjC,SAAgB,IAAI,EAAE,SAAS,CAAC;IAChC,SAAgB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;gBAEtC,IAAI,EAAE;QAChB,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,SAAS,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACnC;CAMF;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE;QACX,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;CACH,CAAC;AAEF,wBAAgB,eAAe,CAAC,GAAG,EAAE,QAAQ,EAAE,QAAQ,CAAC,EAAE,iBAAiB,GAAG,MAAM,CAcnF"}
@@ -9,12 +9,14 @@ export class CliError extends Error {
9
9
  this.context = opts.context;
10
10
  }
11
11
  }
12
- export function formatJsonError(err) {
12
+ export function formatJsonError(err, guidance) {
13
13
  return JSON.stringify({
14
14
  error: {
15
15
  code: err.code,
16
16
  message: err.message,
17
17
  context: err.context ?? undefined,
18
+ hint: guidance?.hint,
19
+ next_action: guidance?.nextAction,
18
20
  },
19
21
  }, null, 2);
20
22
  }
@@ -0,0 +1,3 @@
1
+ export declare const RUNTIME_GITIGNORE_LINES: readonly ["# agentplane: ignore runtime/transient workspace artifacts", ".env", ".agentplane/worktrees", ".agentplane/cache", ".agentplane/recipes-cache", ".agentplane/.upgrade", ".agentplane/.release", ".agentplane/upgrade", ".agentplane/tasks.json", "AGENTS.md.bak-*", ".agentplane/agents/*.bak-*"];
2
+ export declare const AGENT_PROMPT_GITIGNORE_LINES: readonly ["# agentplane: ignore local agent prompts/templates", "AGENTS.md", ".agentplane/agents/"];
3
+ //# sourceMappingURL=runtime-artifacts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-artifacts.d.ts","sourceRoot":"","sources":["../../src/shared/runtime-artifacts.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,uBAAuB,8SAY1B,CAAC;AAEX,eAAO,MAAM,4BAA4B,qGAI/B,CAAC"}
@@ -0,0 +1,18 @@
1
+ export const RUNTIME_GITIGNORE_LINES = [
2
+ "# agentplane: ignore runtime/transient workspace artifacts",
3
+ ".env",
4
+ ".agentplane/worktrees",
5
+ ".agentplane/cache",
6
+ ".agentplane/recipes-cache",
7
+ ".agentplane/.upgrade",
8
+ ".agentplane/.release",
9
+ ".agentplane/upgrade",
10
+ ".agentplane/tasks.json",
11
+ "AGENTS.md.bak-*",
12
+ ".agentplane/agents/*.bak-*",
13
+ ];
14
+ export const AGENT_PROMPT_GITIGNORE_LINES = [
15
+ "# agentplane: ignore local agent prompts/templates",
16
+ "AGENTS.md",
17
+ ".agentplane/agents/",
18
+ ];
@@ -1,3 +1,4 @@
1
+ import type { AgentplaneConfig, ResolvedProject } from "@agentplaneorg/core";
1
2
  import type { Adapters } from "../../adapters/index.js";
2
3
  import { type CommandContext } from "../../commands/shared/task-backend.js";
3
4
  import { PolicyEngine } from "../../policy/engine.js";
@@ -9,6 +10,8 @@ export type UsecaseContext = {
9
10
  export declare function resolveContext(opts: {
10
11
  cwd: string;
11
12
  rootOverride?: string | null;
13
+ resolvedProject?: ResolvedProject;
14
+ config?: AgentplaneConfig;
12
15
  }): Promise<CommandContext>;
13
16
  export declare function makeUsecaseContext(command: CommandContext): UsecaseContext;
14
17
  //# sourceMappingURL=resolve-context.d.ts.map