agentplane 0.1.6 → 0.1.7

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 (111) hide show
  1. package/assets/AGENTS.md +1 -1
  2. package/assets/agents/ORCHESTRATOR.json +1 -1
  3. package/assets/agents/UPGRADER.json +1 -1
  4. package/dist/cli/run-cli.d.ts.map +1 -1
  5. package/dist/cli/run-cli.js +22 -7
  6. package/dist/commands/branch/index.d.ts +60 -0
  7. package/dist/commands/branch/index.d.ts.map +1 -0
  8. package/dist/commands/branch/index.js +511 -0
  9. package/dist/commands/guard/index.d.ts +67 -0
  10. package/dist/commands/guard/index.d.ts.map +1 -0
  11. package/dist/commands/guard/index.js +367 -0
  12. package/dist/commands/hooks/index.d.ts +18 -0
  13. package/dist/commands/hooks/index.d.ts.map +1 -0
  14. package/dist/commands/hooks/index.js +290 -0
  15. package/dist/commands/pr/index.d.ts +46 -0
  16. package/dist/commands/pr/index.d.ts.map +1 -0
  17. package/dist/commands/pr/index.js +854 -0
  18. package/dist/commands/shared/git-diff.d.ts +9 -0
  19. package/dist/commands/shared/git-diff.d.ts.map +1 -0
  20. package/dist/commands/shared/git-diff.js +41 -0
  21. package/dist/commands/shared/git-ops.d.ts +24 -0
  22. package/dist/commands/shared/git-ops.d.ts.map +1 -0
  23. package/dist/commands/shared/git-ops.js +181 -0
  24. package/dist/commands/shared/git-worktree.d.ts +8 -0
  25. package/dist/commands/shared/git-worktree.d.ts.map +1 -0
  26. package/dist/commands/shared/git-worktree.js +48 -0
  27. package/dist/commands/shared/git.d.ts +4 -0
  28. package/dist/commands/shared/git.d.ts.map +1 -0
  29. package/dist/commands/shared/git.js +14 -0
  30. package/dist/commands/shared/path.d.ts +3 -0
  31. package/dist/commands/shared/path.d.ts.map +1 -0
  32. package/dist/commands/shared/path.js +14 -0
  33. package/dist/commands/shared/pr-meta.d.ts +21 -0
  34. package/dist/commands/shared/pr-meta.d.ts.map +1 -0
  35. package/dist/commands/shared/pr-meta.js +72 -0
  36. package/dist/commands/shared/task-backend.d.ts +15 -0
  37. package/dist/commands/shared/task-backend.d.ts.map +1 -0
  38. package/dist/commands/shared/task-backend.js +55 -0
  39. package/dist/commands/task/add.d.ts +8 -0
  40. package/dist/commands/task/add.d.ts.map +1 -0
  41. package/dist/commands/task/add.js +164 -0
  42. package/dist/commands/task/block.d.ts +19 -0
  43. package/dist/commands/task/block.d.ts.map +1 -0
  44. package/dist/commands/task/block.js +86 -0
  45. package/dist/commands/task/comment.d.ts +8 -0
  46. package/dist/commands/task/comment.d.ts.map +1 -0
  47. package/dist/commands/task/comment.js +29 -0
  48. package/dist/commands/task/doc.d.ts +17 -0
  49. package/dist/commands/task/doc.d.ts.map +1 -0
  50. package/dist/commands/task/doc.js +220 -0
  51. package/dist/commands/task/export.d.ts +5 -0
  52. package/dist/commands/task/export.d.ts.map +1 -0
  53. package/dist/commands/task/export.js +27 -0
  54. package/dist/commands/task/finish.d.ts +27 -0
  55. package/dist/commands/task/finish.d.ts.map +1 -0
  56. package/dist/commands/task/finish.js +131 -0
  57. package/dist/commands/task/index.d.ts +23 -0
  58. package/dist/commands/task/index.d.ts.map +1 -0
  59. package/dist/commands/task/index.js +22 -0
  60. package/dist/commands/task/lint.d.ts +5 -0
  61. package/dist/commands/task/lint.d.ts.map +1 -0
  62. package/dist/commands/task/lint.js +22 -0
  63. package/dist/commands/task/list.d.ts +11 -0
  64. package/dist/commands/task/list.d.ts.map +1 -0
  65. package/dist/commands/task/list.js +54 -0
  66. package/dist/commands/task/migrate.d.ts +6 -0
  67. package/dist/commands/task/migrate.d.ts.map +1 -0
  68. package/dist/commands/task/migrate.js +70 -0
  69. package/dist/commands/task/new.d.ts +8 -0
  70. package/dist/commands/task/new.d.ts.map +1 -0
  71. package/dist/commands/task/new.js +117 -0
  72. package/dist/commands/task/next.d.ts +6 -0
  73. package/dist/commands/task/next.d.ts.map +1 -0
  74. package/dist/commands/task/next.js +45 -0
  75. package/dist/commands/task/normalize.d.ts +6 -0
  76. package/dist/commands/task/normalize.d.ts.map +1 -0
  77. package/dist/commands/task/normalize.js +46 -0
  78. package/dist/commands/task/ready.d.ts +6 -0
  79. package/dist/commands/task/ready.d.ts.map +1 -0
  80. package/dist/commands/task/ready.js +57 -0
  81. package/dist/commands/task/scaffold.d.ts +8 -0
  82. package/dist/commands/task/scaffold.d.ts.map +1 -0
  83. package/dist/commands/task/scaffold.js +131 -0
  84. package/dist/commands/task/scrub.d.ts +8 -0
  85. package/dist/commands/task/scrub.d.ts.map +1 -0
  86. package/dist/commands/task/scrub.js +121 -0
  87. package/dist/commands/task/search.d.ts +7 -0
  88. package/dist/commands/task/search.d.ts.map +1 -0
  89. package/dist/commands/task/search.js +79 -0
  90. package/dist/commands/task/set-status.d.ts +19 -0
  91. package/dist/commands/task/set-status.d.ts.map +1 -0
  92. package/dist/commands/task/set-status.js +123 -0
  93. package/dist/commands/task/shared.d.ts +46 -0
  94. package/dist/commands/task/shared.d.ts.map +1 -0
  95. package/dist/commands/task/shared.js +283 -0
  96. package/dist/commands/task/show.d.ts +6 -0
  97. package/dist/commands/task/show.d.ts.map +1 -0
  98. package/dist/commands/task/show.js +35 -0
  99. package/dist/commands/task/start.d.ts +19 -0
  100. package/dist/commands/task/start.d.ts.map +1 -0
  101. package/dist/commands/task/start.js +109 -0
  102. package/dist/commands/task/update.d.ts +8 -0
  103. package/dist/commands/task/update.d.ts.map +1 -0
  104. package/dist/commands/task/update.js +144 -0
  105. package/dist/commands/task/verify.d.ts +14 -0
  106. package/dist/commands/task/verify.d.ts.map +1 -0
  107. package/dist/commands/task/verify.js +362 -0
  108. package/dist/commands/workflow.d.ts +5 -364
  109. package/dist/commands/workflow.d.ts.map +1 -1
  110. package/dist/commands/workflow.js +6 -4617
  111. package/package.json +2 -2
@@ -0,0 +1,362 @@
1
+ import { readFile, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { ensureDocSections, normalizeDocSectionName, setMarkdownSection, } from "@agentplaneorg/core";
4
+ import { mapBackendError } from "../../cli/error-map.js";
5
+ import { fileExists } from "../../cli/fs-utils.js";
6
+ import { promptYesNo } from "../../cli/prompts.js";
7
+ import { backendNotSupportedMessage, infoMessage, successMessage, warnMessage, } from "../../cli/output.js";
8
+ import { CliError } from "../../shared/errors.js";
9
+ import { gitStatusChangedPaths } from "../guard/index.js";
10
+ import { gitRevParse } from "../shared/git-ops.js";
11
+ import { appendVerifyLog, extractLastVerifiedSha, parsePrMeta, runShellCommand, } from "../shared/pr-meta.js";
12
+ import { loadBackendTask } from "../shared/task-backend.js";
13
+ import { isPathWithin } from "../shared/path.js";
14
+ import { nowIso } from "./shared.js";
15
+ export const VERIFY_USAGE = "Usage: agentplane verify <task-id> [--cwd <path>] [--log <path>] [--skip-if-unchanged] [--quiet] [--require] [--yes]";
16
+ export const VERIFY_USAGE_EXAMPLE = "agentplane verify 202602030608-F1Q8AB";
17
+ function extractDocSection(doc, sectionName) {
18
+ const target = normalizeDocSectionName(sectionName);
19
+ if (!target)
20
+ return null;
21
+ const lines = doc.replaceAll("\r\n", "\n").split("\n");
22
+ let capturing = false;
23
+ const out = [];
24
+ for (const line of lines) {
25
+ const match = /^##\s+(.*)$/.exec(line.trim());
26
+ if (match) {
27
+ const key = normalizeDocSectionName(match[1] ?? "");
28
+ if (capturing)
29
+ break;
30
+ capturing = key === target;
31
+ continue;
32
+ }
33
+ if (capturing)
34
+ out.push(line);
35
+ }
36
+ if (!capturing)
37
+ return null;
38
+ return out.join("\n").trimEnd();
39
+ }
40
+ function stripListMarker(line) {
41
+ return line.replace(/^(?:[-*]|\d+\.)\s+/, "");
42
+ }
43
+ function parseVerifyStepsFromDoc(doc) {
44
+ const section = extractDocSection(doc, "Verify Steps");
45
+ if (!section)
46
+ return { commands: [], steps: [] };
47
+ const commands = [];
48
+ const steps = [];
49
+ const lines = section.split("\n");
50
+ let inFence = false;
51
+ for (const line of lines) {
52
+ const trimmed = line.trim();
53
+ if (!trimmed)
54
+ continue;
55
+ if (trimmed.startsWith("```")) {
56
+ inFence = !inFence;
57
+ continue;
58
+ }
59
+ if (inFence)
60
+ continue;
61
+ const normalized = stripListMarker(trimmed);
62
+ const lower = normalized.toLowerCase();
63
+ if (lower.startsWith("cmd:")) {
64
+ const command = normalized.slice(4).trim();
65
+ if (command)
66
+ commands.push(command);
67
+ continue;
68
+ }
69
+ steps.push(normalized);
70
+ }
71
+ return { commands, steps };
72
+ }
73
+ function renderVerificationSection(opts) {
74
+ const lines = [
75
+ `Status: ${opts.status}`,
76
+ `Verified at: ${opts.verifiedAt}`,
77
+ ...(opts.verifiedSha ? [`Verified sha: ${opts.verifiedSha}`] : []),
78
+ ...(opts.commands.length > 0
79
+ ? ["", "Commands:", ...opts.commands.map((command) => `- ${command}`)]
80
+ : []),
81
+ ...(opts.steps.length > 0
82
+ ? ["", "Manual steps:", ...opts.steps.map((step) => `- ${step}`)]
83
+ : []),
84
+ ...(opts.details ? ["", `Details: ${opts.details}`] : []),
85
+ ];
86
+ return lines.join("\n");
87
+ }
88
+ async function writeVerificationSection(opts) {
89
+ if (!opts.backend.getTaskDoc || !opts.backend.setTaskDoc) {
90
+ throw new CliError({
91
+ exitCode: 2,
92
+ code: "E_USAGE",
93
+ message: backendNotSupportedMessage("task docs"),
94
+ });
95
+ }
96
+ const baseDoc = ensureDocSections(opts.baseDoc ?? "", opts.config.tasks.doc.required_sections);
97
+ const nextDoc = setMarkdownSection(baseDoc, "Verification", opts.content);
98
+ const normalized = ensureDocSections(nextDoc, opts.config.tasks.doc.required_sections);
99
+ await opts.backend.setTaskDoc(opts.taskId, normalized, opts.updatedBy);
100
+ }
101
+ export async function cmdVerify(opts) {
102
+ try {
103
+ const { task, backend, config, resolved } = await loadBackendTask({
104
+ cwd: opts.cwd,
105
+ rootOverride: opts.rootOverride,
106
+ taskId: opts.taskId,
107
+ });
108
+ const docText = typeof task.doc === "string" ? task.doc : "";
109
+ const { commands: docCommands, steps: docSteps } = parseVerifyStepsFromDoc(docText);
110
+ const rawVerify = task.verify;
111
+ if (rawVerify !== undefined && rawVerify !== null && !Array.isArray(rawVerify)) {
112
+ throw new CliError({
113
+ exitCode: 2,
114
+ code: "E_USAGE",
115
+ message: `${task.id}: verify must be a list of strings`,
116
+ });
117
+ }
118
+ const taskCommands = Array.isArray(rawVerify)
119
+ ? rawVerify
120
+ .filter((item) => typeof item === "string")
121
+ .map((item) => item.trim())
122
+ .filter(Boolean)
123
+ : [];
124
+ const commands = docCommands.length > 0 ? docCommands : taskCommands;
125
+ let baseDoc = typeof task.doc === "string" ? task.doc : "";
126
+ if (docSteps.length > 0 && !opts.quiet) {
127
+ process.stdout.write(`${infoMessage(`${task.id}: manual verify steps:`)}\n`);
128
+ for (const step of docSteps) {
129
+ process.stdout.write(`- ${step}\n`);
130
+ }
131
+ }
132
+ if (commands.length === 0) {
133
+ if (opts.require) {
134
+ throw new CliError({
135
+ exitCode: 2,
136
+ code: "E_USAGE",
137
+ message: `${task.id}: no verify commands configured`,
138
+ });
139
+ }
140
+ if (!opts.quiet) {
141
+ process.stdout.write(`${infoMessage(`${task.id}: no verify commands configured`)}\n`);
142
+ }
143
+ return 0;
144
+ }
145
+ const requireVerifyApproval = config.agents?.approvals?.require_verify === true;
146
+ if (requireVerifyApproval && !opts.yes) {
147
+ if (!process.stdin.isTTY || opts.quiet) {
148
+ throw new CliError({
149
+ exitCode: 2,
150
+ code: "E_USAGE",
151
+ message: "Verification requires explicit approval (use --yes in non-interactive mode or set agents.approvals.require_verify=false).",
152
+ });
153
+ }
154
+ const approved = await promptYesNo("Require explicit approval for verification. Proceed?", false);
155
+ if (!approved) {
156
+ throw new CliError({
157
+ exitCode: 2,
158
+ code: "E_USAGE",
159
+ message: "Verification cancelled by user.",
160
+ });
161
+ }
162
+ }
163
+ if (!backend.getTaskDoc || !backend.setTaskDoc) {
164
+ throw new CliError({
165
+ exitCode: 2,
166
+ code: "E_USAGE",
167
+ message: backendNotSupportedMessage("task docs"),
168
+ });
169
+ }
170
+ if (!baseDoc) {
171
+ const fetched = await backend.getTaskDoc(task.id);
172
+ if (typeof fetched === "string")
173
+ baseDoc = fetched;
174
+ }
175
+ const execCwd = opts.execCwd ? path.resolve(opts.cwd, opts.execCwd) : resolved.gitRoot;
176
+ if (!isPathWithin(resolved.gitRoot, execCwd)) {
177
+ throw new CliError({
178
+ exitCode: 2,
179
+ code: "E_USAGE",
180
+ message: `--cwd must stay under repo root: ${execCwd}`,
181
+ });
182
+ }
183
+ const taskDir = path.join(resolved.gitRoot, config.paths.workflow_dir, opts.taskId);
184
+ const prDir = path.join(taskDir, "pr");
185
+ const metaPath = path.join(prDir, "meta.json");
186
+ let logPath = null;
187
+ if (opts.logPath) {
188
+ logPath = path.resolve(opts.cwd, opts.logPath);
189
+ if (!isPathWithin(resolved.gitRoot, logPath)) {
190
+ throw new CliError({
191
+ exitCode: 2,
192
+ code: "E_USAGE",
193
+ message: `--log must stay under repo root: ${logPath}`,
194
+ });
195
+ }
196
+ }
197
+ else if (await fileExists(prDir)) {
198
+ logPath = path.join(prDir, "verify.log");
199
+ }
200
+ let meta = null;
201
+ if (await fileExists(metaPath)) {
202
+ const rawMeta = await readFile(metaPath, "utf8");
203
+ meta = parsePrMeta(rawMeta, opts.taskId);
204
+ }
205
+ const headSha = await gitRevParse(execCwd, ["HEAD"]);
206
+ const currentSha = headSha;
207
+ if (opts.skipIfUnchanged) {
208
+ const changed = await gitStatusChangedPaths({
209
+ cwd: execCwd,
210
+ rootOverride: opts.rootOverride,
211
+ });
212
+ if (changed.length > 0) {
213
+ if (!opts.quiet) {
214
+ process.stdout.write(`${warnMessage(`${task.id}: working tree is dirty; ignoring --skip-if-unchanged`)}\n`);
215
+ }
216
+ }
217
+ else {
218
+ let lastVerifiedSha = meta?.last_verified_sha ?? null;
219
+ if (!lastVerifiedSha && logPath && (await fileExists(logPath))) {
220
+ const logText = await readFile(logPath, "utf8");
221
+ lastVerifiedSha = extractLastVerifiedSha(logText);
222
+ }
223
+ if (lastVerifiedSha && lastVerifiedSha === currentSha) {
224
+ const header = `[${nowIso()}] ℹ️ skipped (unchanged verified_sha=${currentSha})`;
225
+ if (logPath) {
226
+ await appendVerifyLog(logPath, header, "");
227
+ }
228
+ if (!opts.quiet) {
229
+ process.stdout.write(`${infoMessage(`${task.id}: verify skipped (unchanged sha ${currentSha.slice(0, 12)})`)}\n`);
230
+ }
231
+ if (meta) {
232
+ const nextMeta = {
233
+ ...meta,
234
+ last_verified_sha: currentSha,
235
+ last_verified_at: nowIso(),
236
+ verify: meta.verify ? { ...meta.verify, status: "pass" } : { status: "pass" },
237
+ };
238
+ await writeFile(metaPath, `${JSON.stringify(nextMeta, null, 2)}\n`, "utf8");
239
+ }
240
+ return 0;
241
+ }
242
+ }
243
+ }
244
+ let verifyError = null;
245
+ let failedCommand = null;
246
+ for (const command of commands) {
247
+ try {
248
+ if (!opts.quiet) {
249
+ process.stdout.write(`$ ${command}\n`);
250
+ }
251
+ const timestamp = nowIso();
252
+ const result = await runShellCommand(command, execCwd);
253
+ const shaPrefix = currentSha ? `sha=${currentSha} ` : "";
254
+ const header = `[${timestamp}] ${shaPrefix}$ ${command}`.trimEnd();
255
+ if (logPath) {
256
+ await appendVerifyLog(logPath, header, result.output);
257
+ }
258
+ if (result.code !== 0) {
259
+ throw new CliError({
260
+ exitCode: result.code || 1,
261
+ code: "E_IO",
262
+ message: `Verify command failed: ${command}`,
263
+ });
264
+ }
265
+ }
266
+ catch (err) {
267
+ verifyError = err instanceof Error ? err : new Error(String(err));
268
+ failedCommand = command;
269
+ break;
270
+ }
271
+ }
272
+ if (verifyError) {
273
+ const details = verifyError.message;
274
+ const failureAt = nowIso();
275
+ const content = renderVerificationSection({
276
+ status: "fail",
277
+ verifiedAt: failureAt,
278
+ verifiedSha: null,
279
+ commands,
280
+ steps: docSteps,
281
+ details: failedCommand ? `${details} (command: ${failedCommand})` : details,
282
+ });
283
+ await writeVerificationSection({
284
+ backend,
285
+ taskId: task.id,
286
+ config,
287
+ baseDoc,
288
+ content,
289
+ updatedBy: "VERIFY",
290
+ });
291
+ if (meta) {
292
+ const nextMeta = {
293
+ ...meta,
294
+ last_verified_at: failureAt,
295
+ verify: meta.verify
296
+ ? { ...meta.verify, status: "fail", command: commands.join(" && ") }
297
+ : { status: "fail", command: commands.join(" && ") },
298
+ };
299
+ await writeFile(metaPath, `${JSON.stringify(nextMeta, null, 2)}\n`, "utf8");
300
+ }
301
+ const existingComments = Array.isArray(task.comments)
302
+ ? task.comments.filter((item) => !!item && typeof item.author === "string" && typeof item.body === "string")
303
+ : [];
304
+ const failureBody = failedCommand
305
+ ? `Verify failed: ${details} (command: ${failedCommand})`
306
+ : `Verify failed: ${details}`;
307
+ const nextTask = {
308
+ ...task,
309
+ status: "DOING",
310
+ comments: [...existingComments, { author: "VERIFY", body: failureBody }],
311
+ doc_version: 2,
312
+ doc_updated_at: failureAt,
313
+ doc_updated_by: "VERIFY",
314
+ };
315
+ await backend.writeTask(nextTask);
316
+ throw verifyError;
317
+ }
318
+ if (currentSha) {
319
+ const header = `[${nowIso()}] ✅ verified_sha=${currentSha}`;
320
+ if (logPath) {
321
+ await appendVerifyLog(logPath, header, "");
322
+ }
323
+ }
324
+ if (!opts.quiet) {
325
+ process.stdout.write(`${successMessage("verify passed", task.id)}\n`);
326
+ }
327
+ const successAt = nowIso();
328
+ const successContent = renderVerificationSection({
329
+ status: "pass",
330
+ verifiedAt: successAt,
331
+ verifiedSha: currentSha,
332
+ commands,
333
+ steps: docSteps,
334
+ details: null,
335
+ });
336
+ await writeVerificationSection({
337
+ backend,
338
+ taskId: task.id,
339
+ config,
340
+ baseDoc,
341
+ content: successContent,
342
+ updatedBy: "VERIFY",
343
+ });
344
+ if (meta) {
345
+ const nextMeta = {
346
+ ...meta,
347
+ last_verified_sha: currentSha,
348
+ last_verified_at: successAt,
349
+ verify: meta.verify
350
+ ? { ...meta.verify, status: "pass", command: commands.join(" && ") }
351
+ : { status: "pass", command: commands.join(" && ") },
352
+ };
353
+ await writeFile(metaPath, `${JSON.stringify(nextMeta, null, 2)}\n`, "utf8");
354
+ }
355
+ return 0;
356
+ }
357
+ catch (err) {
358
+ if (err instanceof CliError)
359
+ throw err;
360
+ throw mapBackendError(err, { command: "verify", root: opts.rootOverride ?? null });
361
+ }
362
+ }