agentplane 0.3.11 → 0.3.12

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 (100) hide show
  1. package/assets/AGENTS.md +2 -2
  2. package/assets/agents/CODER.json +4 -0
  3. package/assets/agents/CREATOR.json +1 -0
  4. package/assets/agents/DOCS.json +2 -1
  5. package/assets/agents/INTEGRATOR.json +2 -1
  6. package/assets/agents/ORCHESTRATOR.json +2 -0
  7. package/assets/agents/PLANNER.json +3 -1
  8. package/assets/agents/REVIEWER.json +1 -0
  9. package/assets/agents/TESTER.json +2 -2
  10. package/assets/agents/UPDATER.json +1 -0
  11. package/assets/agents/UPGRADER.json +1 -1
  12. package/assets/policy/incidents.md +1 -0
  13. package/bin/agentplane.js +58 -3
  14. package/bin/stale-dist-policy.js +6 -1
  15. package/dist/.build-manifest.json +88 -68
  16. package/dist/cli/run-cli/command-catalog/core.d.ts +1 -1
  17. package/dist/cli/run-cli/command-catalog/core.d.ts.map +1 -1
  18. package/dist/cli/run-cli/command-catalog/core.js +6 -1
  19. package/dist/cli/run-cli/command-catalog.d.ts +1 -1
  20. package/dist/cli/run-cli/command-catalog.d.ts.map +1 -1
  21. package/dist/cli/run-cli.test-helpers.d.ts +1 -0
  22. package/dist/cli/run-cli.test-helpers.d.ts.map +1 -1
  23. package/dist/cli/run-cli.test-helpers.js +14 -0
  24. package/dist/commands/branch/cleanup-merged.d.ts +1 -0
  25. package/dist/commands/branch/cleanup-merged.d.ts.map +1 -1
  26. package/dist/commands/branch/cleanup-merged.js +18 -9
  27. package/dist/commands/branch/work-start.d.ts.map +1 -1
  28. package/dist/commands/branch/work-start.js +82 -5
  29. package/dist/commands/doctor/branch-pr.js +2 -2
  30. package/dist/commands/guard/impl/commands.d.ts +1 -0
  31. package/dist/commands/guard/impl/commands.d.ts.map +1 -1
  32. package/dist/commands/guard/impl/commands.js +78 -8
  33. package/dist/commands/hooks/index.d.ts +1 -1
  34. package/dist/commands/hooks/index.d.ts.map +1 -1
  35. package/dist/commands/hooks/index.js +48 -12
  36. package/dist/commands/pr/check.d.ts.map +1 -1
  37. package/dist/commands/pr/check.js +3 -0
  38. package/dist/commands/pr/integrate/cmd.d.ts.map +1 -1
  39. package/dist/commands/pr/integrate/cmd.js +27 -2
  40. package/dist/commands/pr/integrate/internal/cleanup.d.ts +1 -11
  41. package/dist/commands/pr/integrate/internal/cleanup.d.ts.map +1 -1
  42. package/dist/commands/pr/integrate/internal/cleanup.js +1 -46
  43. package/dist/commands/pr/integrate/internal/finalize.d.ts.map +1 -1
  44. package/dist/commands/pr/integrate/internal/finalize.js +3 -0
  45. package/dist/commands/pr/integrate/internal/github-protection.d.ts +5 -0
  46. package/dist/commands/pr/integrate/internal/github-protection.d.ts.map +1 -0
  47. package/dist/commands/pr/integrate/internal/github-protection.js +13 -0
  48. package/dist/commands/pr/integrate/internal/pre-integrate-bootstrap.d.ts +15 -0
  49. package/dist/commands/pr/integrate/internal/pre-integrate-bootstrap.d.ts.map +1 -0
  50. package/dist/commands/pr/integrate/internal/pre-integrate-bootstrap.js +35 -0
  51. package/dist/commands/pr/integrate/internal/prepare.d.ts +1 -0
  52. package/dist/commands/pr/integrate/internal/prepare.d.ts.map +1 -1
  53. package/dist/commands/pr/integrate/internal/prepare.js +8 -0
  54. package/dist/commands/pr/internal/auto-commit.d.ts +7 -0
  55. package/dist/commands/pr/internal/auto-commit.d.ts.map +1 -0
  56. package/dist/commands/pr/internal/auto-commit.js +64 -0
  57. package/dist/commands/pr/internal/freshness.d.ts +1 -0
  58. package/dist/commands/pr/internal/freshness.d.ts.map +1 -1
  59. package/dist/commands/pr/internal/freshness.js +2 -0
  60. package/dist/commands/pr/internal/sync.d.ts.map +1 -1
  61. package/dist/commands/pr/internal/sync.js +93 -26
  62. package/dist/commands/pr/open.d.ts.map +1 -1
  63. package/dist/commands/pr/open.js +11 -0
  64. package/dist/commands/pr/update.d.ts.map +1 -1
  65. package/dist/commands/pr/update.js +13 -2
  66. package/dist/commands/release/apply.command.d.ts +3 -1
  67. package/dist/commands/release/apply.command.d.ts.map +1 -1
  68. package/dist/commands/release/apply.command.js +354 -18
  69. package/dist/commands/release/apply.mutation.d.ts.map +1 -1
  70. package/dist/commands/release/apply.mutation.js +1 -0
  71. package/dist/commands/release/apply.reporting.d.ts +1 -0
  72. package/dist/commands/release/apply.reporting.d.ts.map +1 -1
  73. package/dist/commands/release/apply.reporting.js +12 -8
  74. package/dist/commands/release/apply.types.d.ts +13 -0
  75. package/dist/commands/release/apply.types.d.ts.map +1 -1
  76. package/dist/commands/release/plan.command.d.ts.map +1 -1
  77. package/dist/commands/release/plan.command.js +48 -0
  78. package/dist/commands/shared/merged-branch-cleanup.d.ts +12 -0
  79. package/dist/commands/shared/merged-branch-cleanup.d.ts.map +1 -0
  80. package/dist/commands/shared/merged-branch-cleanup.js +46 -0
  81. package/dist/commands/shared/post-commit-pr-artifacts.d.ts.map +1 -1
  82. package/dist/commands/shared/post-commit-pr-artifacts.js +35 -0
  83. package/dist/commands/shared/task-backend.d.ts.map +1 -1
  84. package/dist/commands/shared/task-backend.js +37 -5
  85. package/dist/commands/shared/task-local-freshness.d.ts +2 -0
  86. package/dist/commands/shared/task-local-freshness.d.ts.map +1 -1
  87. package/dist/commands/shared/task-local-freshness.js +7 -1
  88. package/dist/commands/task/finish-shared.d.ts +1 -0
  89. package/dist/commands/task/finish-shared.d.ts.map +1 -1
  90. package/dist/commands/task/finish-shared.js +1 -0
  91. package/dist/commands/task/hosted-close-pr.command.d.ts.map +1 -1
  92. package/dist/commands/task/hosted-close-pr.command.js +35 -0
  93. package/dist/commands/task/hosted-close.command.d.ts.map +1 -1
  94. package/dist/commands/task/hosted-close.command.js +185 -18
  95. package/dist/commands/task/hosted-merge-sync.d.ts +4 -1
  96. package/dist/commands/task/hosted-merge-sync.d.ts.map +1 -1
  97. package/dist/commands/task/hosted-merge-sync.js +52 -10
  98. package/dist/commands/task/start-ready.d.ts.map +1 -1
  99. package/dist/commands/task/start-ready.js +0 -86
  100. package/package.json +2 -2
@@ -82,6 +82,154 @@ async function hasTaskArtifactChanges(opts) {
82
82
  });
83
83
  return stdout.trim().length > 0;
84
84
  }
85
+ function buildFallbackPrMeta(opts) {
86
+ const at = opts.mergedPr.mergedAt ?? new Date().toISOString();
87
+ const headShaRaw = opts.mergedPr.headRefOid?.trim();
88
+ const headSha = headShaRaw && headShaRaw.length > 0 ? headShaRaw : undefined;
89
+ const prUrlRaw = opts.mergedPr.url?.trim();
90
+ const prUrl = prUrlRaw && prUrlRaw.length > 0 ? prUrlRaw : undefined;
91
+ return {
92
+ schema_version: 1,
93
+ task_id: opts.taskId,
94
+ branch: opts.branch,
95
+ ...(opts.mergedPr.baseRefName ? { base: opts.mergedPr.baseRefName } : {}),
96
+ pr_number: opts.mergedPr.number,
97
+ ...(prUrl ? { pr_url: prUrl } : {}),
98
+ created_at: at,
99
+ updated_at: at,
100
+ status: "OPEN",
101
+ ...(headSha ? { head_sha: headSha } : {}),
102
+ last_verified_sha: null,
103
+ last_verified_at: null,
104
+ verify: { status: "skipped" },
105
+ };
106
+ }
107
+ function escapeRegExp(value) {
108
+ return value.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw `\$&`);
109
+ }
110
+ function extractMarkdownSection(markdown, heading) {
111
+ const pattern = new RegExp(String.raw `^## ${escapeRegExp(heading)}\n\n([\s\S]*?)(?=\n## [^\n]+\n|$)`, "m");
112
+ const match = pattern.exec(markdown);
113
+ return match?.[1]?.trim() ?? "";
114
+ }
115
+ function fallbackTaskTitleFromPrTitle(taskId, prTitle) {
116
+ const trimmed = prTitle?.trim() ?? "";
117
+ if (!trimmed)
118
+ return `Hosted close recovery for ${taskId}`;
119
+ const suffix = taskId.split("-").at(-1)?.trim();
120
+ if (suffix) {
121
+ const stripped = trimmed.replace(new RegExp(String.raw `\s*\(${escapeRegExp(suffix)}\)\s*$`), "");
122
+ if (stripped.trim().length > 0)
123
+ return stripped.trim();
124
+ }
125
+ return trimmed;
126
+ }
127
+ function isMissingTaskReadmeError(err, readmePath) {
128
+ if (!(err instanceof CliError))
129
+ return false;
130
+ return err.code === "E_IO" && err.message.includes(readmePath);
131
+ }
132
+ async function buildHostedTaskFromTrackedPrArtifacts(opts) {
133
+ const bodyPath = path.join(opts.gitRoot, opts.taskDirRelative, "pr", "github-body.md");
134
+ let bodyText = "";
135
+ try {
136
+ bodyText = await readFile(bodyPath, "utf8");
137
+ }
138
+ catch (err) {
139
+ const code = err?.code;
140
+ if (code === "ENOENT")
141
+ return null;
142
+ throw err;
143
+ }
144
+ const summary = extractMarkdownSection(bodyText, "Summary");
145
+ const scope = extractMarkdownSection(bodyText, "Scope");
146
+ const verification = extractMarkdownSection(bodyText, "Verification");
147
+ const handoff = extractMarkdownSection(bodyText, "Handoff Notes");
148
+ const summaryLines = summary
149
+ .split("\n")
150
+ .map((line) => line.trim())
151
+ .filter((line) => line.length > 0);
152
+ const title = summaryLines[0] || fallbackTaskTitleFromPrTitle(opts.taskId, opts.mergedPr.title);
153
+ const description = summaryLines.slice(1).join("\n").trim() ||
154
+ `Recovered hosted-close state from tracked PR artifacts for merged PR #${opts.mergedPr.number}.`;
155
+ const scopeText = scope || `- In scope: record canonical task closure for merged PR #${opts.mergedPr.number}.`;
156
+ const verificationText = verification || "- State: pending\n- Note: Recovered during hosted-close.";
157
+ const handoffText = handoff || "- No handoff notes recorded.";
158
+ const planText = `Recovered hosted-close state from tracked PR artifacts for merged PR #${opts.mergedPr.number}.`;
159
+ const rollbackText = [
160
+ "- Revert the hosted closure commit if the merged PR metadata was recorded incorrectly.",
161
+ "- Re-run the required checks after rollback.",
162
+ ].join("\n");
163
+ const doc = [
164
+ "## Summary",
165
+ "",
166
+ title,
167
+ "",
168
+ description,
169
+ "",
170
+ "## Scope",
171
+ "",
172
+ scopeText,
173
+ "",
174
+ "## Plan",
175
+ "",
176
+ planText,
177
+ "",
178
+ "## Verification",
179
+ "",
180
+ verificationText,
181
+ "",
182
+ "## Rollback Plan",
183
+ "",
184
+ rollbackText,
185
+ "",
186
+ "## Handoff Notes",
187
+ "",
188
+ handoffText,
189
+ "",
190
+ "## Findings",
191
+ "",
192
+ ].join("\n");
193
+ return {
194
+ id: opts.taskId,
195
+ title,
196
+ description,
197
+ status: "DOING",
198
+ priority: "med",
199
+ owner: "INTEGRATOR",
200
+ revision: 1,
201
+ origin: { system: "manual" },
202
+ depends_on: [],
203
+ tags: [],
204
+ verify: [],
205
+ plan_approval: { state: "pending", updated_at: null, updated_by: null, note: null },
206
+ verification: { state: "pending", updated_at: null, updated_by: null, note: null },
207
+ commit: null,
208
+ doc,
209
+ doc_version: 3,
210
+ doc_updated_at: opts.mergedPr.mergedAt ?? new Date().toISOString(),
211
+ doc_updated_by: "INTEGRATOR",
212
+ id_source: "generated",
213
+ };
214
+ }
215
+ async function readHostedPrMetaOrFallback(opts) {
216
+ const metaPath = path.join(opts.gitRoot, opts.taskDirRelative, "pr", "meta.json");
217
+ if (!(await fileExists(metaPath))) {
218
+ return {
219
+ metaPath,
220
+ meta: buildFallbackPrMeta({
221
+ taskId: opts.target.taskId,
222
+ branch: opts.target.branch,
223
+ mergedPr: opts.target.mergedPr,
224
+ }),
225
+ };
226
+ }
227
+ const rawMeta = await readFile(metaPath, "utf8");
228
+ return {
229
+ metaPath,
230
+ meta: parsePrMeta(rawMeta, opts.target.taskId),
231
+ };
232
+ }
85
233
  async function closeHostedTask(opts) {
86
234
  const rawEvent = await readFile(opts.eventJson, "utf8");
87
235
  const parsedEvent = JSON.parse(rawEvent);
@@ -94,24 +242,43 @@ async function closeHostedTask(opts) {
94
242
  }
95
243
  const gitRoot = opts.ctx.resolvedProject.gitRoot;
96
244
  const taskDirRelative = path.join(opts.ctx.config.paths.workflow_dir, target.taskId);
97
- const task = await loadTaskFromContext({ ctx: opts.ctx, taskId: target.taskId });
98
- const metaPath = path.join(gitRoot, taskDirRelative, "pr", "meta.json");
99
- if (!(await fileExists(metaPath))) {
100
- throw new CliError({
101
- exitCode: 3,
102
- code: "E_VALIDATION",
103
- message: `Hosted task closure could not find pr/meta.json for ${target.taskId}`,
245
+ const taskReadmePath = path.join(gitRoot, taskDirRelative, "README.md");
246
+ let task;
247
+ try {
248
+ task = await loadTaskFromContext({
249
+ ctx: opts.ctx,
250
+ taskId: target.taskId,
251
+ preferBranchSnapshot: true,
252
+ branchSnapshotBranch: target.branch,
104
253
  });
105
254
  }
106
- const rawMeta = await readFile(metaPath, "utf8");
107
- const meta = parsePrMeta(rawMeta, target.taskId);
108
- const alreadyClosed = String(task.status || "TODO").toUpperCase() === "DONE" &&
109
- (task.commit?.hash ?? "") === target.mergedPr.mergeCommit.oid &&
110
- meta.status === "MERGED" &&
111
- (meta.merge_commit ?? "") === target.mergedPr.mergeCommit.oid;
112
- if (String(task.status || "TODO").toUpperCase() === "DONE" &&
113
- (task.commit?.hash ?? "") !== "" &&
114
- (task.commit?.hash ?? "") !== target.mergedPr.mergeCommit.oid) {
255
+ catch (err) {
256
+ if (!isMissingTaskReadmeError(err, taskReadmePath))
257
+ throw err;
258
+ const recovered = await buildHostedTaskFromTrackedPrArtifacts({
259
+ gitRoot,
260
+ taskDirRelative,
261
+ taskId: target.taskId,
262
+ mergedPr: target.mergedPr,
263
+ });
264
+ if (!recovered)
265
+ throw err;
266
+ task = recovered;
267
+ }
268
+ if (!(await fileExists(taskReadmePath))) {
269
+ await opts.ctx.taskBackend.writeTask(task);
270
+ }
271
+ const { metaPath, meta } = await readHostedPrMetaOrFallback({
272
+ gitRoot,
273
+ taskDirRelative,
274
+ target,
275
+ });
276
+ const taskStatus = String(task.status || "TODO").toUpperCase();
277
+ const taskCommitHash = task.commit?.hash ?? "";
278
+ const alreadyClosed = taskStatus === "DONE" && taskCommitHash === target.mergedPr.mergeCommit.oid;
279
+ if (taskStatus === "DONE" &&
280
+ taskCommitHash !== "" &&
281
+ taskCommitHash !== target.mergedPr.mergeCommit.oid) {
115
282
  throw new CliError({
116
283
  exitCode: 3,
117
284
  code: "E_VALIDATION",
@@ -144,7 +311,7 @@ async function closeHostedTask(opts) {
144
311
  cwd: opts.cwd,
145
312
  rootOverride: opts.rootOverride,
146
313
  taskId: target.taskId,
147
- baseBranchOverride: target.mergedPr.baseRefName ?? meta.base ?? "main",
314
+ baseBranchOverride: nextMeta.base ?? "main",
148
315
  quiet: opts.quiet,
149
316
  });
150
317
  return {
@@ -197,7 +364,7 @@ async function closeHostedTask(opts) {
197
364
  cwd: opts.cwd,
198
365
  rootOverride: opts.rootOverride,
199
366
  taskId: target.taskId,
200
- baseBranchOverride: target.mergedPr.baseRefName ?? meta.base ?? "main",
367
+ baseBranchOverride: nextMeta.base ?? "main",
201
368
  quiet: opts.quiet,
202
369
  allowPolicy: collectedIncidents.wrote,
203
370
  });
@@ -27,7 +27,10 @@ export type LocalBranchPrSyncCandidate = {
27
27
  branch: string;
28
28
  base: string;
29
29
  commitHash: string;
30
- verificationSource: "task" | "pr";
30
+ verificationSource: "task" | "pr" | null;
31
+ metaPath: string | null;
32
+ meta: TaskPrMeta | null;
33
+ taskStatus: string;
31
34
  };
32
35
  export type LocalDoneBranchPrDrift = {
33
36
  taskId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"hosted-merge-sync.d.ts","sourceRoot":"","sources":["../../../src/commands/task/hosted-merge-sync.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"hosted-merge-sync.d.ts","sourceRoot":"","sources":["../../../src/commands/task/hosted-merge-sync.ts"],"names":[],"mappings":"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"}
@@ -292,6 +292,28 @@ function buildLocallySyncedTask(opts) {
292
292
  doc_updated_by: "INTEGRATOR",
293
293
  };
294
294
  }
295
+ function buildLocallySyncedPrMeta(opts) {
296
+ const at = new Date().toISOString();
297
+ return {
298
+ ...opts.meta,
299
+ branch: opts.candidate.branch,
300
+ base: opts.candidate.base,
301
+ status: "MERGED",
302
+ merged_at: opts.meta.merged_at ?? at,
303
+ merge_commit: opts.meta.merge_commit ?? opts.candidate.commitHash,
304
+ head_sha: opts.meta.head_sha ?? opts.candidate.commitHash,
305
+ updated_at: at,
306
+ };
307
+ }
308
+ function isStackedBranchAliasDoneTask(opts) {
309
+ const branchTaskId = parseTaskIdFromBranch("task", opts.branch);
310
+ if (!branchTaskId || branchTaskId === opts.task.id)
311
+ return false;
312
+ const summary = opts.task.result_summary?.trim().toLowerCase() ?? "";
313
+ if (!summary.includes("stacked branch_pr merge rooted at"))
314
+ return false;
315
+ return summary.includes(branchTaskId.toLowerCase());
316
+ }
295
317
  async function readPrMetaIfPresent(opts) {
296
318
  const metaPath = path.join(opts.ctx.resolvedProject.gitRoot, opts.ctx.config.paths.workflow_dir, opts.taskId, "pr", "meta.json");
297
319
  try {
@@ -377,21 +399,21 @@ export async function findLocallyShippedBranchPrTasks(opts) {
377
399
  const matches = [];
378
400
  for (const task of opts.tasks) {
379
401
  const currentStatus = String(task.status || "TODO").toUpperCase();
380
- if (currentStatus === "DONE")
381
- continue;
382
402
  const prMetaRecord = await readPrMetaIfPresent({ ctx: opts.ctx, taskId: task.id });
403
+ const meta = prMetaRecord?.meta ?? null;
404
+ const branch = meta?.branch?.trim() ?? "";
405
+ if (!branch)
406
+ continue;
383
407
  const verificationSource = hasTaskVerificationForLocalSync({
384
408
  task,
385
- meta: prMetaRecord?.meta ?? null,
409
+ meta,
386
410
  });
387
- if (!verificationSource)
388
- continue;
389
- const commitHash = task.commit?.hash?.trim() ?? "";
411
+ const commitHash = task.commit?.hash?.trim() ?? meta?.merge_commit?.trim() ?? meta?.head_sha?.trim() ?? "";
390
412
  if (!commitHash)
391
413
  continue;
392
414
  const base = await resolveSyncBaseBranch({
393
415
  ctx: opts.ctx,
394
- meta: prMetaRecord?.meta ?? null,
416
+ meta,
395
417
  });
396
418
  if (!base)
397
419
  continue;
@@ -404,12 +426,21 @@ export async function findLocallyShippedBranchPrTasks(opts) {
404
426
  }))) {
405
427
  continue;
406
428
  }
429
+ const metaNeedsSync = Boolean(meta && meta.status !== "MERGED");
430
+ const taskNeedsSync = currentStatus !== "DONE" && verificationSource !== null;
431
+ if (!taskNeedsSync && !metaNeedsSync)
432
+ continue;
433
+ if (!taskNeedsSync && currentStatus !== "DONE")
434
+ continue;
407
435
  matches.push({
408
436
  taskId: task.id,
409
- branch: prMetaRecord?.meta.branch?.trim() ?? "",
437
+ branch,
410
438
  base,
411
439
  commitHash,
412
440
  verificationSource,
441
+ metaPath: prMetaRecord?.metaPath ?? null,
442
+ meta,
443
+ taskStatus: currentStatus,
413
444
  });
414
445
  }
415
446
  return matches;
@@ -430,7 +461,11 @@ export async function findDoneBranchPrTasksWithOpenPrArtifacts(opts) {
430
461
  const branch = meta.branch?.trim() ?? "";
431
462
  if (!branch)
432
463
  continue;
433
- const commitHash = task.commit?.hash?.trim() ?? meta.head_sha?.trim() ?? "";
464
+ if (isStackedBranchAliasDoneTask({ task, branch }))
465
+ continue;
466
+ // Missing implementation commits are handled by a dedicated doctor check, and duplicate/no-op
467
+ // tasks should not also surface as stale mergeable PR drift just because meta.head_sha exists.
468
+ const commitHash = task.commit?.hash?.trim() ?? "";
434
469
  if (!commitHash)
435
470
  continue;
436
471
  const base = await resolveSyncBaseBranch({
@@ -452,11 +487,18 @@ export async function syncLocallyShippedBranchPrTasks(opts) {
452
487
  const matches = await findLocallyShippedBranchPrTasks(opts);
453
488
  if (matches.length === 0)
454
489
  return { tasks: opts.tasks, synced: 0 };
490
+ for (const candidate of matches) {
491
+ if (candidate.meta && candidate.metaPath && candidate.meta.status !== "MERGED") {
492
+ await writeJsonStableIfChanged(candidate.metaPath, buildLocallySyncedPrMeta({ meta: candidate.meta, candidate }));
493
+ }
494
+ }
455
495
  const byTaskId = new Map(matches.map((entry) => [entry.taskId, entry]));
456
496
  return {
457
497
  tasks: opts.tasks.map((task) => {
458
498
  const candidate = byTaskId.get(task.id);
459
- return candidate ? buildLocallySyncedTask({ task, candidate }) : task;
499
+ if (!candidate)
500
+ return task;
501
+ return candidate.taskStatus === "DONE" ? task : buildLocallySyncedTask({ task, candidate });
460
502
  }),
461
503
  synced: matches.length,
462
504
  };
@@ -1 +1 @@
1
- {"version":3,"file":"start-ready.d.ts","sourceRoot":"","sources":["../../../src/commands/task/start-ready.ts"],"names":[],"mappings":"AAUA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAkGpF,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,CA6ClB"}
1
+ {"version":3,"file":"start-ready.d.ts","sourceRoot":"","sources":["../../../src/commands/task/start-ready.ts"],"names":[],"mappings":"AAIA,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"}
@@ -1,90 +1,10 @@
1
- import { mkdir, readFile, realpath } from "node:fs/promises";
2
- import path from "node:path";
3
- import { resolveBaseBranch } from "@agentplaneorg/core";
4
1
  import { mapBackendError } from "../../cli/error-map.js";
5
2
  import { infoMessage, successMessage } from "../../cli/output.js";
6
3
  import { CliError } from "../../shared/errors.js";
7
4
  import { renderIncidentAdvice } from "../../runtime/incidents/index.js";
8
- import { writeTextIfChanged } from "../../shared/write-if-changed.js";
9
5
  import { loadCommandContext } from "../shared/task-backend.js";
10
6
  import { adviseTaskIncidents } from "../incidents/shared.js";
11
- import { findWorktreeForBranch, listWorktrees, parseTaskIdFromBranch, parseTaskIdFromCloseBranch, } from "../shared/git-worktree.js";
12
7
  import { cmdStart } from "./start.js";
13
- async function syncTaskReadmeAcrossRelevantWorktrees(opts) {
14
- const canonicalizeWorktreePath = async (value) => {
15
- try {
16
- return await realpath(value);
17
- }
18
- catch {
19
- return path.resolve(value);
20
- }
21
- };
22
- const currentRoot = path.resolve(opts.rootOverride ?? opts.ctx.resolvedProject.gitRoot);
23
- const currentCanonicalRoot = await canonicalizeWorktreePath(currentRoot);
24
- const workflowDir = opts.ctx.config.paths.workflow_dir;
25
- const sourceReadmePath = path.join(currentRoot, workflowDir, opts.taskId, "README.md");
26
- let sourceText = "";
27
- try {
28
- sourceText = await readFile(sourceReadmePath, "utf8");
29
- }
30
- catch {
31
- return;
32
- }
33
- const worktrees = await listWorktrees(currentRoot).catch(() => []);
34
- const normalizedWorktrees = await Promise.all(worktrees.map(async (entry) => ({
35
- ...entry,
36
- canonicalPath: await canonicalizeWorktreePath(entry.path),
37
- })));
38
- const targetRoots = new Set();
39
- const baseBranch = await resolveBaseBranch({
40
- cwd: opts.cwd,
41
- rootOverride: opts.rootOverride ?? null,
42
- cliBaseOpt: null,
43
- mode: opts.ctx.config.workflow_mode,
44
- });
45
- if (baseBranch) {
46
- const baseWorktree = await findWorktreeForBranch(currentRoot, baseBranch);
47
- if (baseWorktree) {
48
- const canonicalBaseWorktree = await canonicalizeWorktreePath(baseWorktree);
49
- if (canonicalBaseWorktree !== currentCanonicalRoot) {
50
- targetRoots.add(canonicalBaseWorktree);
51
- }
52
- }
53
- }
54
- if (targetRoots.size === 0) {
55
- const mainWorktree = normalizedWorktrees.find((entry) => entry.branch === "main" || entry.branch === "refs/heads/main");
56
- if (mainWorktree && mainWorktree.canonicalPath !== currentCanonicalRoot) {
57
- targetRoots.add(mainWorktree.canonicalPath);
58
- }
59
- }
60
- if (targetRoots.size === 0) {
61
- const nonTaskWorktrees = normalizedWorktrees.filter((entry) => {
62
- if (!entry.branch)
63
- return false;
64
- if (entry.canonicalPath === currentCanonicalRoot)
65
- return false;
66
- if (parseTaskIdFromBranch(opts.ctx.config.branch.task_prefix, entry.branch))
67
- return false;
68
- if (parseTaskIdFromCloseBranch(entry.branch))
69
- return false;
70
- return true;
71
- });
72
- if (nonTaskWorktrees.length === 1) {
73
- targetRoots.add(nonTaskWorktrees[0].canonicalPath);
74
- }
75
- }
76
- const matchingTaskWorktrees = normalizedWorktrees.filter((entry) => typeof entry.branch === "string" &&
77
- parseTaskIdFromBranch(opts.ctx.config.branch.task_prefix, entry.branch) === opts.taskId &&
78
- entry.canonicalPath !== currentCanonicalRoot);
79
- if (matchingTaskWorktrees.length === 1) {
80
- targetRoots.add(matchingTaskWorktrees[0].canonicalPath);
81
- }
82
- for (const targetRoot of targetRoots) {
83
- const targetReadmePath = path.join(targetRoot, workflowDir, opts.taskId, "README.md");
84
- await mkdir(path.dirname(targetReadmePath), { recursive: true });
85
- await writeTextIfChanged(targetReadmePath, sourceText);
86
- }
87
- }
88
8
  export async function cmdTaskStartReady(opts) {
89
9
  try {
90
10
  const ctx = opts.ctx ??
@@ -106,12 +26,6 @@ export async function cmdTaskStartReady(opts) {
106
26
  yes: opts.yes,
107
27
  quiet: true,
108
28
  });
109
- await syncTaskReadmeAcrossRelevantWorktrees({
110
- ctx,
111
- cwd: opts.cwd,
112
- rootOverride: opts.rootOverride,
113
- taskId: opts.taskId,
114
- });
115
29
  if (!opts.quiet) {
116
30
  process.stdout.write(`${successMessage("ready", opts.taskId)}\n`);
117
31
  const advice = await adviseTaskIncidents({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentplane",
3
- "version": "0.3.11",
3
+ "version": "0.3.12",
4
4
  "description": "Agent Plane CLI for task workflows, recipes, and project automation.",
5
5
  "keywords": [
6
6
  "agentplane",
@@ -55,7 +55,7 @@
55
55
  "prepublishOnly": "node ../../scripts/enforce-github-publish.mjs && npm run prepack"
56
56
  },
57
57
  "dependencies": {
58
- "@agentplaneorg/core": "0.3.11",
58
+ "@agentplaneorg/core": "0.3.12",
59
59
  "yauzl": "^2.10.0"
60
60
  },
61
61
  "devDependencies": {