agentplane 0.3.10 → 0.3.11

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 (228) hide show
  1. package/assets/policy/governance.md +3 -4
  2. package/assets/policy/incidents.md +19 -88
  3. package/assets/policy/workflow.branch_pr.md +1 -1
  4. package/assets/policy/workflow.direct.md +2 -2
  5. package/bin/agentplane.js +56 -1
  6. package/bin/runtime-watch.js +1 -0
  7. package/bin/stale-dist-policy.d.ts +1 -1
  8. package/bin/stale-dist-policy.js +13 -0
  9. package/dist/.build-manifest.json +219 -154
  10. package/dist/cli/bootstrap-guide.d.ts.map +1 -1
  11. package/dist/cli/bootstrap-guide.js +3 -2
  12. package/dist/cli/command-guide.d.ts.map +1 -1
  13. package/dist/cli/command-guide.js +2 -1
  14. package/dist/cli/command-invocations.d.ts.map +1 -1
  15. package/dist/cli/command-invocations.js +4 -1
  16. package/dist/cli/run-cli/command-catalog/project.d.ts +1 -1
  17. package/dist/cli/run-cli/command-catalog/project.d.ts.map +1 -1
  18. package/dist/cli/run-cli/command-catalog/project.js +3 -1
  19. package/dist/cli/run-cli/command-catalog/task.d.ts +1 -1
  20. package/dist/cli/run-cli/command-catalog/task.d.ts.map +1 -1
  21. package/dist/cli/run-cli/command-catalog/task.js +10 -0
  22. package/dist/cli/run-cli/command-catalog.d.ts +1 -1
  23. package/dist/cli/run-cli/command-catalog.d.ts.map +1 -1
  24. package/dist/cli/run-cli/commands/core/preflight.d.ts.map +1 -1
  25. package/dist/cli/run-cli/commands/core/preflight.js +44 -1
  26. package/dist/cli/run-cli.test-helpers.d.ts.map +1 -1
  27. package/dist/cli/run-cli.test-helpers.js +12 -0
  28. package/dist/commands/branch/cleanup-merged.d.ts +2 -0
  29. package/dist/commands/branch/cleanup-merged.d.ts.map +1 -1
  30. package/dist/commands/branch/cleanup-merged.js +132 -28
  31. package/dist/commands/branch/work-start.d.ts.map +1 -1
  32. package/dist/commands/branch/work-start.js +60 -1
  33. package/dist/commands/cleanup/merged.command.d.ts +2 -0
  34. package/dist/commands/cleanup/merged.command.d.ts.map +1 -1
  35. package/dist/commands/cleanup/merged.command.js +24 -0
  36. package/dist/commands/doctor/branch-pr.d.ts +4 -0
  37. package/dist/commands/doctor/branch-pr.d.ts.map +1 -0
  38. package/dist/commands/doctor/branch-pr.js +96 -0
  39. package/dist/commands/doctor/fixes.d.ts +5 -0
  40. package/dist/commands/doctor/fixes.d.ts.map +1 -1
  41. package/dist/commands/doctor/fixes.js +70 -0
  42. package/dist/commands/doctor.run.d.ts.map +1 -1
  43. package/dist/commands/doctor.run.js +6 -1
  44. package/dist/commands/finish.run.d.ts.map +1 -1
  45. package/dist/commands/finish.run.js +11 -0
  46. package/dist/commands/finish.spec.d.ts +11 -0
  47. package/dist/commands/finish.spec.d.ts.map +1 -1
  48. package/dist/commands/finish.spec.js +51 -0
  49. package/dist/commands/guard/impl/close-message.d.ts.map +1 -1
  50. package/dist/commands/guard/impl/close-message.js +23 -6
  51. package/dist/commands/guard/impl/commands.d.ts.map +1 -1
  52. package/dist/commands/guard/impl/commands.js +24 -2
  53. package/dist/commands/guard/impl/env.d.ts +1 -0
  54. package/dist/commands/guard/impl/env.d.ts.map +1 -1
  55. package/dist/commands/guard/impl/env.js +1 -0
  56. package/dist/commands/hooks/index.d.ts.map +1 -1
  57. package/dist/commands/hooks/index.js +98 -1
  58. package/dist/commands/incidents/collect.command.d.ts.map +1 -1
  59. package/dist/commands/incidents/collect.command.js +12 -7
  60. package/dist/commands/incidents/incidents.command.js +1 -1
  61. package/dist/commands/incidents/shared.d.ts +34 -0
  62. package/dist/commands/incidents/shared.d.ts.map +1 -1
  63. package/dist/commands/incidents/shared.js +166 -12
  64. package/dist/commands/pr/check.d.ts.map +1 -1
  65. package/dist/commands/pr/check.js +238 -135
  66. package/dist/commands/pr/close-superseded.d.ts +9 -0
  67. package/dist/commands/pr/close-superseded.d.ts.map +1 -0
  68. package/dist/commands/pr/close-superseded.js +129 -0
  69. package/dist/commands/pr/close.d.ts +11 -0
  70. package/dist/commands/pr/close.d.ts.map +1 -0
  71. package/dist/commands/pr/close.js +116 -0
  72. package/dist/commands/pr/index.d.ts +2 -0
  73. package/dist/commands/pr/index.d.ts.map +1 -1
  74. package/dist/commands/pr/index.js +2 -0
  75. package/dist/commands/pr/integrate/artifacts.d.ts +7 -0
  76. package/dist/commands/pr/integrate/artifacts.d.ts.map +1 -1
  77. package/dist/commands/pr/integrate/artifacts.js +66 -1
  78. package/dist/commands/pr/integrate/cmd.d.ts.map +1 -1
  79. package/dist/commands/pr/integrate/cmd.js +16 -0
  80. package/dist/commands/pr/integrate/internal/bootstrap-guidance.d.ts +8 -0
  81. package/dist/commands/pr/integrate/internal/bootstrap-guidance.d.ts.map +1 -0
  82. package/dist/commands/pr/integrate/internal/bootstrap-guidance.js +59 -0
  83. package/dist/commands/pr/integrate/internal/finalize.d.ts.map +1 -1
  84. package/dist/commands/pr/integrate/internal/finalize.js +40 -12
  85. package/dist/commands/pr/integrate/internal/merge.d.ts.map +1 -1
  86. package/dist/commands/pr/integrate/internal/merge.js +36 -13
  87. package/dist/commands/pr/integrate/internal/post-integrate-bootstrap.d.ts +13 -0
  88. package/dist/commands/pr/integrate/internal/post-integrate-bootstrap.d.ts.map +1 -0
  89. package/dist/commands/pr/integrate/internal/post-integrate-bootstrap.js +25 -0
  90. package/dist/commands/pr/integrate/internal/prepare.d.ts +3 -2
  91. package/dist/commands/pr/integrate/internal/prepare.d.ts.map +1 -1
  92. package/dist/commands/pr/integrate/internal/prepare.js +101 -38
  93. package/dist/commands/pr/internal/freshness.d.ts +20 -0
  94. package/dist/commands/pr/internal/freshness.d.ts.map +1 -0
  95. package/dist/commands/pr/internal/freshness.js +50 -0
  96. package/dist/commands/pr/internal/gh-api.d.ts +6 -0
  97. package/dist/commands/pr/internal/gh-api.d.ts.map +1 -0
  98. package/dist/commands/pr/internal/gh-api.js +80 -0
  99. package/dist/commands/pr/internal/pr-paths.d.ts +10 -0
  100. package/dist/commands/pr/internal/pr-paths.d.ts.map +1 -1
  101. package/dist/commands/pr/internal/pr-paths.js +10 -0
  102. package/dist/commands/pr/internal/review-template.d.ts.map +1 -1
  103. package/dist/commands/pr/internal/review-template.js +37 -4
  104. package/dist/commands/pr/internal/sync.d.ts +9 -0
  105. package/dist/commands/pr/internal/sync.d.ts.map +1 -1
  106. package/dist/commands/pr/internal/sync.js +462 -122
  107. package/dist/commands/pr/open.d.ts +1 -0
  108. package/dist/commands/pr/open.d.ts.map +1 -1
  109. package/dist/commands/pr/open.js +13 -2
  110. package/dist/commands/pr/pr.command.d.ts +15 -0
  111. package/dist/commands/pr/pr.command.d.ts.map +1 -1
  112. package/dist/commands/pr/pr.command.js +118 -2
  113. package/dist/commands/pr/update.d.ts.map +1 -1
  114. package/dist/commands/pr/update.js +59 -1
  115. package/dist/commands/release/apply.command.d.ts.map +1 -1
  116. package/dist/commands/release/apply.command.js +3 -17
  117. package/dist/commands/release/apply.preflight.d.ts.map +1 -1
  118. package/dist/commands/release/apply.preflight.js +1 -1
  119. package/dist/commands/shared/gh-transport.d.ts +16 -0
  120. package/dist/commands/shared/gh-transport.d.ts.map +1 -0
  121. package/dist/commands/shared/gh-transport.js +71 -0
  122. package/dist/commands/shared/git-diff.d.ts +3 -1
  123. package/dist/commands/shared/git-diff.d.ts.map +1 -1
  124. package/dist/commands/shared/git-diff.js +10 -2
  125. package/dist/commands/shared/git-ops.d.ts +1 -0
  126. package/dist/commands/shared/git-ops.d.ts.map +1 -1
  127. package/dist/commands/shared/git-ops.js +15 -0
  128. package/dist/commands/shared/git-worktree.d.ts +2 -0
  129. package/dist/commands/shared/git-worktree.d.ts.map +1 -1
  130. package/dist/commands/shared/git-worktree.js +22 -2
  131. package/dist/commands/shared/post-commit-pr-artifacts.d.ts +9 -0
  132. package/dist/commands/shared/post-commit-pr-artifacts.d.ts.map +1 -0
  133. package/dist/commands/shared/post-commit-pr-artifacts.js +22 -0
  134. package/dist/commands/shared/pr-meta.d.ts +20 -0
  135. package/dist/commands/shared/pr-meta.d.ts.map +1 -1
  136. package/dist/commands/shared/pr-meta.js +125 -0
  137. package/dist/commands/shared/task-backend.d.ts +7 -0
  138. package/dist/commands/shared/task-backend.d.ts.map +1 -1
  139. package/dist/commands/shared/task-backend.js +34 -22
  140. package/dist/commands/task/close-duplicate.d.ts.map +1 -1
  141. package/dist/commands/task/close-duplicate.js +34 -1
  142. package/dist/commands/task/derive.js +1 -1
  143. package/dist/commands/task/doc-template.d.ts.map +1 -1
  144. package/dist/commands/task/doc-template.js +7 -11
  145. package/dist/commands/task/findings-add.command.d.ts +20 -0
  146. package/dist/commands/task/findings-add.command.d.ts.map +1 -0
  147. package/dist/commands/task/findings-add.command.js +165 -0
  148. package/dist/commands/task/findings.command.d.ts +7 -0
  149. package/dist/commands/task/findings.command.d.ts.map +1 -0
  150. package/dist/commands/task/findings.command.js +20 -0
  151. package/dist/commands/task/findings.d.ts +63 -0
  152. package/dist/commands/task/findings.d.ts.map +1 -0
  153. package/dist/commands/task/findings.js +188 -0
  154. package/dist/commands/task/finish-shared.d.ts +1 -0
  155. package/dist/commands/task/finish-shared.d.ts.map +1 -1
  156. package/dist/commands/task/finish-shared.js +55 -1
  157. package/dist/commands/task/finish.d.ts +10 -0
  158. package/dist/commands/task/finish.d.ts.map +1 -1
  159. package/dist/commands/task/finish.js +125 -6
  160. package/dist/commands/task/hosted-close-pr.command.d.ts +11 -0
  161. package/dist/commands/task/hosted-close-pr.command.d.ts.map +1 -0
  162. package/dist/commands/task/hosted-close-pr.command.js +414 -0
  163. package/dist/commands/task/hosted-close.command.d.ts.map +1 -1
  164. package/dist/commands/task/hosted-close.command.js +49 -1
  165. package/dist/commands/task/hosted-merge-sync.d.ts +38 -0
  166. package/dist/commands/task/hosted-merge-sync.d.ts.map +1 -1
  167. package/dist/commands/task/hosted-merge-sync.js +249 -17
  168. package/dist/commands/task/index.d.ts +1 -0
  169. package/dist/commands/task/index.d.ts.map +1 -1
  170. package/dist/commands/task/index.js +1 -0
  171. package/dist/commands/task/new.d.ts +1 -0
  172. package/dist/commands/task/new.d.ts.map +1 -1
  173. package/dist/commands/task/new.js +71 -1
  174. package/dist/commands/task/new.spec.d.ts.map +1 -1
  175. package/dist/commands/task/new.spec.js +7 -0
  176. package/dist/commands/task/normalize.command.d.ts +2 -0
  177. package/dist/commands/task/normalize.command.d.ts.map +1 -1
  178. package/dist/commands/task/normalize.command.js +45 -0
  179. package/dist/commands/task/normalize.d.ts +2 -0
  180. package/dist/commands/task/normalize.d.ts.map +1 -1
  181. package/dist/commands/task/normalize.js +85 -8
  182. package/dist/commands/task/plan.d.ts.map +1 -1
  183. package/dist/commands/task/plan.js +7 -10
  184. package/dist/commands/task/shared/docs.d.ts +6 -0
  185. package/dist/commands/task/shared/docs.d.ts.map +1 -1
  186. package/dist/commands/task/shared/docs.js +14 -0
  187. package/dist/commands/task/shared/transitions.d.ts.map +1 -1
  188. package/dist/commands/task/shared/transitions.js +11 -1
  189. package/dist/commands/task/shared.d.ts +1 -1
  190. package/dist/commands/task/shared.d.ts.map +1 -1
  191. package/dist/commands/task/shared.js +1 -1
  192. package/dist/commands/task/start-ready.d.ts.map +1 -1
  193. package/dist/commands/task/start-ready.js +86 -0
  194. package/dist/commands/task/start.d.ts.map +1 -1
  195. package/dist/commands/task/start.js +7 -10
  196. package/dist/commands/task/task.command.d.ts.map +1 -1
  197. package/dist/commands/task/task.command.js +4 -0
  198. package/dist/commands/task/verify-command-shared.d.ts +19 -0
  199. package/dist/commands/task/verify-command-shared.d.ts.map +1 -1
  200. package/dist/commands/task/verify-command-shared.js +152 -1
  201. package/dist/commands/task/verify-ok.command.d.ts.map +1 -1
  202. package/dist/commands/task/verify-ok.command.js +15 -2
  203. package/dist/commands/task/verify-record.d.ts +36 -0
  204. package/dist/commands/task/verify-record.d.ts.map +1 -1
  205. package/dist/commands/task/verify-record.js +166 -11
  206. package/dist/commands/task/verify-rework.command.d.ts.map +1 -1
  207. package/dist/commands/task/verify-rework.command.js +15 -2
  208. package/dist/commands/task/verify-show.command.d.ts +1 -1
  209. package/dist/commands/task/verify-show.command.d.ts.map +1 -1
  210. package/dist/commands/task/verify-show.command.js +28 -1
  211. package/dist/commands/verify.run.d.ts.map +1 -1
  212. package/dist/commands/verify.run.js +12 -0
  213. package/dist/commands/verify.spec.d.ts +2 -6
  214. package/dist/commands/verify.spec.d.ts.map +1 -1
  215. package/dist/commands/verify.spec.js +30 -3
  216. package/dist/runtime/incidents/index.d.ts +1 -1
  217. package/dist/runtime/incidents/index.d.ts.map +1 -1
  218. package/dist/runtime/incidents/resolve.d.ts.map +1 -1
  219. package/dist/runtime/incidents/resolve.js +319 -73
  220. package/dist/runtime/incidents/types.d.ts +14 -2
  221. package/dist/runtime/incidents/types.d.ts.map +1 -1
  222. package/dist/shared/env.d.ts +1 -0
  223. package/dist/shared/env.d.ts.map +1 -1
  224. package/dist/shared/env.js +22 -1
  225. package/dist/shared/protected-paths.d.ts +1 -1
  226. package/dist/shared/protected-paths.d.ts.map +1 -1
  227. package/dist/shared/protected-paths.js +4 -0
  228. package/package.json +2 -2
@@ -1,11 +1,13 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
- import { normalizeTaskDocVersion } from "@agentplaneorg/core";
3
+ import { normalizeTaskDocVersion, resolveBaseBranch } from "@agentplaneorg/core";
4
4
  import { CliError } from "../../shared/errors.js";
5
5
  import { writeJsonStableIfChanged } from "../../shared/write-if-changed.js";
6
6
  import { execFileAsync } from "../shared/git.js";
7
+ import { withGhTransportRetry } from "../shared/gh-transport.js";
7
8
  import { parseTaskIdFromBranch } from "../shared/git-worktree.js";
8
9
  import { parsePrMeta } from "../shared/pr-meta.js";
10
+ import { ghEnv } from "../pr/internal/gh-api.js";
9
11
  import { appendTaskEvent } from "./shared.js";
10
12
  function normalizeMergedPr(value) {
11
13
  if (!value || typeof value !== "object" || Array.isArray(value))
@@ -78,6 +80,19 @@ function pickHostedMergedPr(records) {
78
80
  return right.localeCompare(left);
79
81
  })[0] ?? null);
80
82
  }
83
+ export function resolveLocalMergedPrMeta(meta) {
84
+ const branch = meta?.branch?.trim() ?? "";
85
+ const mergeCommit = meta?.merge_commit?.trim() ?? "";
86
+ if (meta?.status !== "MERGED" || !branch || !mergeCommit)
87
+ return null;
88
+ return {
89
+ branch,
90
+ base: meta.base ?? null,
91
+ mergedAt: meta.merged_at ?? null,
92
+ mergeCommit,
93
+ headSha: meta.head_sha ?? null,
94
+ };
95
+ }
81
96
  export function resolveHostedMergeTargetFromEvent(opts) {
82
97
  if (!opts.event || typeof opts.event !== "object" || Array.isArray(opts.event))
83
98
  return null;
@@ -94,23 +109,27 @@ export function resolveHostedMergeTargetFromEvent(opts) {
94
109
  mergedPr,
95
110
  };
96
111
  }
97
- async function resolveHostedMergedPr(opts) {
98
- const { stdout } = await execFileAsync("gh", [
99
- "pr",
100
- "list",
101
- "--state",
102
- "merged",
103
- "--head",
104
- opts.branch,
105
- "--json",
106
- "number,title,url,mergedAt,baseRefName,headRefName,headRefOid,mergeCommit",
107
- ], {
108
- cwd: opts.cwd,
109
- env: process.env,
110
- maxBuffer: 10 * 1024 * 1024,
112
+ export async function resolveHostedMergedPr(opts) {
113
+ return await withGhTransportRetry(async () => {
114
+ const { stdout } = await execFileAsync("gh", [
115
+ "pr",
116
+ "list",
117
+ "--state",
118
+ "merged",
119
+ "--head",
120
+ opts.branch,
121
+ "--json",
122
+ "number,title,url,mergedAt,baseRefName,headRefName,headRefOid,mergeCommit",
123
+ ], {
124
+ cwd: opts.cwd,
125
+ env: ghEnv(),
126
+ maxBuffer: 10 * 1024 * 1024,
127
+ });
128
+ const parsed = JSON.parse(stdout);
129
+ return Array.isArray(parsed) ? pickHostedMergedPr(parsed) : null;
130
+ }, {
131
+ label: `looking up merged PR metadata for ${opts.branch}`,
111
132
  });
112
- const parsed = JSON.parse(stdout);
113
- return Array.isArray(parsed) ? pickHostedMergedPr(parsed) : null;
114
133
  }
115
134
  function buildSyncedPrMeta(opts) {
116
135
  const at = opts.mergedPr.mergedAt ?? new Date().toISOString();
@@ -160,6 +179,119 @@ function buildSyncedTask(opts) {
160
179
  doc_updated_by: "INTEGRATOR",
161
180
  };
162
181
  }
182
+ function needsHostedMergeSyncFromLocalMeta(opts) {
183
+ const currentStatus = String(opts.task.status || "TODO").toUpperCase();
184
+ const expectedCommit = opts.meta.mergeCommit;
185
+ if (currentStatus !== "DONE")
186
+ return true;
187
+ if ((opts.task.commit?.hash ?? "") !== expectedCommit)
188
+ return true;
189
+ return false;
190
+ }
191
+ function buildLocallyMergedSyncedTask(opts) {
192
+ const at = opts.meta.mergedAt ?? new Date().toISOString();
193
+ const currentStatus = String(opts.task.status || "TODO").toUpperCase();
194
+ const note = "Local PR metadata already marks the task branch as MERGED; " +
195
+ "task projection reconciled without an additional GitHub lookup.";
196
+ const statusEvent = {
197
+ type: "status",
198
+ at,
199
+ author: "INTEGRATOR",
200
+ from: currentStatus,
201
+ to: "DONE",
202
+ note,
203
+ };
204
+ return {
205
+ ...opts.task,
206
+ status: "DONE",
207
+ result_summary: opts.task.result_summary ?? "Merged and reconciled from local PR metadata.",
208
+ commit: opts.task.commit?.hash?.trim()
209
+ ? opts.task.commit
210
+ : {
211
+ hash: opts.meta.mergeCommit,
212
+ message: "Merged branch_pr task reconciled from local PR metadata",
213
+ },
214
+ events: appendTaskEvent(opts.task, statusEvent),
215
+ doc_version: normalizeTaskDocVersion(opts.task.doc_version),
216
+ doc_updated_at: at,
217
+ doc_updated_by: "INTEGRATOR",
218
+ };
219
+ }
220
+ function hasTaskVerificationForLocalSync(opts) {
221
+ if (opts.task.verification?.state === "ok")
222
+ return "task";
223
+ if (opts.meta?.verify?.status === "pass")
224
+ return "pr";
225
+ return null;
226
+ }
227
+ async function gitRefExists(opts) {
228
+ try {
229
+ await execFileAsync("git", ["rev-parse", "--verify", "--quiet", opts.ref], {
230
+ cwd: opts.cwd,
231
+ env: process.env,
232
+ });
233
+ return true;
234
+ }
235
+ catch {
236
+ return false;
237
+ }
238
+ }
239
+ async function gitIsAncestor(opts) {
240
+ try {
241
+ await execFileAsync("git", ["merge-base", "--is-ancestor", opts.ancestor, opts.descendant], {
242
+ cwd: opts.cwd,
243
+ env: process.env,
244
+ });
245
+ return true;
246
+ }
247
+ catch (err) {
248
+ const code = err?.code;
249
+ if (code === 1)
250
+ return false;
251
+ throw err;
252
+ }
253
+ }
254
+ async function resolveSyncBaseBranch(opts) {
255
+ const fromMeta = opts.meta?.base?.trim() ?? "";
256
+ if (fromMeta.length > 0)
257
+ return fromMeta;
258
+ return await resolveBaseBranch({
259
+ cwd: opts.ctx.resolvedProject.gitRoot,
260
+ rootOverride: opts.ctx.resolvedProject.gitRoot,
261
+ cliBaseOpt: null,
262
+ mode: opts.ctx.config.workflow_mode,
263
+ });
264
+ }
265
+ function buildLocallySyncedTask(opts) {
266
+ const at = new Date().toISOString();
267
+ const currentStatus = String(opts.task.status || "TODO").toUpperCase();
268
+ const note = `Local branch_pr reconciliation detected task commit ${opts.candidate.commitHash.slice(0, 12)} ` +
269
+ `on base ${opts.candidate.base}; canonical task state normalized after shipment.`;
270
+ const statusEvent = {
271
+ type: "status",
272
+ at,
273
+ author: "INTEGRATOR",
274
+ from: currentStatus,
275
+ to: "DONE",
276
+ note,
277
+ };
278
+ return {
279
+ ...opts.task,
280
+ status: "DONE",
281
+ result_summary: opts.task.result_summary ??
282
+ `Shipped on ${opts.candidate.base} and reconciled from local branch_pr state.`,
283
+ commit: opts.task.commit?.hash?.trim()
284
+ ? opts.task.commit
285
+ : {
286
+ hash: opts.candidate.commitHash,
287
+ message: `Shipped on ${opts.candidate.base} before canonical task closure`,
288
+ },
289
+ events: appendTaskEvent(opts.task, statusEvent),
290
+ doc_version: normalizeTaskDocVersion(opts.task.doc_version),
291
+ doc_updated_at: at,
292
+ doc_updated_by: "INTEGRATOR",
293
+ };
294
+ }
163
295
  async function readPrMetaIfPresent(opts) {
164
296
  const metaPath = path.join(opts.ctx.resolvedProject.gitRoot, opts.ctx.config.paths.workflow_dir, opts.taskId, "pr", "meta.json");
165
297
  try {
@@ -238,6 +370,97 @@ export async function syncHostedMergedTask(opts) {
238
370
  synced: 1,
239
371
  };
240
372
  }
373
+ export async function findLocallyShippedBranchPrTasks(opts) {
374
+ if (opts.ctx.backendId !== "local" || opts.ctx.config.workflow_mode !== "branch_pr") {
375
+ return [];
376
+ }
377
+ const matches = [];
378
+ for (const task of opts.tasks) {
379
+ const currentStatus = String(task.status || "TODO").toUpperCase();
380
+ if (currentStatus === "DONE")
381
+ continue;
382
+ const prMetaRecord = await readPrMetaIfPresent({ ctx: opts.ctx, taskId: task.id });
383
+ const verificationSource = hasTaskVerificationForLocalSync({
384
+ task,
385
+ meta: prMetaRecord?.meta ?? null,
386
+ });
387
+ if (!verificationSource)
388
+ continue;
389
+ const commitHash = task.commit?.hash?.trim() ?? "";
390
+ if (!commitHash)
391
+ continue;
392
+ const base = await resolveSyncBaseBranch({
393
+ ctx: opts.ctx,
394
+ meta: prMetaRecord?.meta ?? null,
395
+ });
396
+ if (!base)
397
+ continue;
398
+ if (!(await gitRefExists({ cwd: opts.ctx.resolvedProject.gitRoot, ref: base })))
399
+ continue;
400
+ if (!(await gitIsAncestor({
401
+ cwd: opts.ctx.resolvedProject.gitRoot,
402
+ ancestor: commitHash,
403
+ descendant: base,
404
+ }))) {
405
+ continue;
406
+ }
407
+ matches.push({
408
+ taskId: task.id,
409
+ branch: prMetaRecord?.meta.branch?.trim() ?? "",
410
+ base,
411
+ commitHash,
412
+ verificationSource,
413
+ });
414
+ }
415
+ return matches;
416
+ }
417
+ export async function findDoneBranchPrTasksWithOpenPrArtifacts(opts) {
418
+ if (opts.ctx.backendId !== "local" || opts.ctx.config.workflow_mode !== "branch_pr") {
419
+ return [];
420
+ }
421
+ const matches = [];
422
+ for (const task of opts.tasks) {
423
+ const currentStatus = String(task.status || "TODO").toUpperCase();
424
+ if (currentStatus !== "DONE")
425
+ continue;
426
+ const prMetaRecord = await readPrMetaIfPresent({ ctx: opts.ctx, taskId: task.id });
427
+ const meta = prMetaRecord?.meta ?? null;
428
+ if (!meta || meta.status === "MERGED")
429
+ continue;
430
+ const branch = meta.branch?.trim() ?? "";
431
+ if (!branch)
432
+ continue;
433
+ const commitHash = task.commit?.hash?.trim() ?? meta.head_sha?.trim() ?? "";
434
+ if (!commitHash)
435
+ continue;
436
+ const base = await resolveSyncBaseBranch({
437
+ ctx: opts.ctx,
438
+ meta,
439
+ });
440
+ if (!base)
441
+ continue;
442
+ matches.push({
443
+ taskId: task.id,
444
+ branch,
445
+ base,
446
+ commitHash,
447
+ });
448
+ }
449
+ return matches;
450
+ }
451
+ export async function syncLocallyShippedBranchPrTasks(opts) {
452
+ const matches = await findLocallyShippedBranchPrTasks(opts);
453
+ if (matches.length === 0)
454
+ return { tasks: opts.tasks, synced: 0 };
455
+ const byTaskId = new Map(matches.map((entry) => [entry.taskId, entry]));
456
+ return {
457
+ tasks: opts.tasks.map((task) => {
458
+ const candidate = byTaskId.get(task.id);
459
+ return candidate ? buildLocallySyncedTask({ task, candidate }) : task;
460
+ }),
461
+ synced: matches.length,
462
+ };
463
+ }
241
464
  export async function syncHostedMergedTasks(opts) {
242
465
  if (opts.ctx.backendId !== "local" || opts.ctx.config.workflow_mode !== "branch_pr") {
243
466
  return { tasks: opts.tasks, synced: 0 };
@@ -255,6 +478,15 @@ export async function syncHostedMergedTasks(opts) {
255
478
  nextTasks.push(task);
256
479
  continue;
257
480
  }
481
+ const localMergedMeta = resolveLocalMergedPrMeta(prMetaRecord.meta);
482
+ if (localMergedMeta) {
483
+ nextTasks.push(needsHostedMergeSyncFromLocalMeta({ task, meta: localMergedMeta })
484
+ ? buildLocallyMergedSyncedTask({ task, meta: localMergedMeta })
485
+ : task);
486
+ if (needsHostedMergeSyncFromLocalMeta({ task, meta: localMergedMeta }))
487
+ synced += 1;
488
+ continue;
489
+ }
258
490
  const mergedPr = await resolveHostedMergedPr({
259
491
  cwd: opts.ctx.resolvedProject.gitRoot,
260
492
  branch,
@@ -18,6 +18,7 @@ export { cmdTaskDerive } from "./derive.js";
18
18
  export { cmdTaskCloseDuplicate } from "./close-duplicate.js";
19
19
  export { cmdTaskStartReady } from "./start-ready.js";
20
20
  export { cmdTaskCloseNoop } from "./close-noop.js";
21
+ export { makeRunTaskHostedClosePrHandler, taskHostedClosePrSpec, } from "./hosted-close-pr.command.js";
21
22
  export { cmdTaskExport } from "./export.js";
22
23
  export { cmdTaskLint } from "./lint.js";
23
24
  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;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"}
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;AACnD,OAAO,EACL,+BAA+B,EAC/B,qBAAqB,GACtB,MAAM,8BAA8B,CAAC;AAEtC,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"}
@@ -17,6 +17,7 @@ export { cmdTaskDerive } from "./derive.js";
17
17
  export { cmdTaskCloseDuplicate } from "./close-duplicate.js";
18
18
  export { cmdTaskStartReady } from "./start-ready.js";
19
19
  export { cmdTaskCloseNoop } from "./close-noop.js";
20
+ export { makeRunTaskHostedClosePrHandler, taskHostedClosePrSpec, } from "./hosted-close-pr.command.js";
20
21
  export { cmdTaskExport } from "./export.js";
21
22
  export { cmdTaskLint } from "./lint.js";
22
23
  export { cmdTaskPlanSet, cmdTaskPlanApprove, cmdTaskPlanReject } from "./plan.js";
@@ -7,6 +7,7 @@ export type TaskNewParsed = {
7
7
  tags: string[];
8
8
  dependsOn: string[];
9
9
  verify: string[];
10
+ allowDuplicate: boolean;
10
11
  };
11
12
  export declare function runTaskNewParsed(opts: {
12
13
  ctx?: CommandContext;
@@ -1 +1 @@
1
- {"version":3,"file":"new.d.ts","sourceRoot":"","sources":["../../../src/commands/task/new.ts"],"names":[],"mappings":"AAaA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAcpF,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;AAmDF,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,CA4IlB"}
1
+ {"version":3,"file":"new.d.ts","sourceRoot":"","sources":["../../../src/commands/task/new.ts"],"names":[],"mappings":"AAaA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAepF,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;IACjB,cAAc,EAAE,OAAO,CAAC;CACzB,CAAC;AAiIF,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,CAuJlB"}
@@ -8,6 +8,22 @@ import { makeReadOnlyUsecaseContext } from "../../usecases/context/resolve-conte
8
8
  import { loadCommandContext } from "../shared/task-backend.js";
9
9
  import { ensureTaskDependsOnGraphIsAcyclic, nowIso, requiresVerifyStepsByPrimary, resolvePrimaryTag, warnIfUnknownOwner, } from "./shared.js";
10
10
  import { buildDefaultVerifyStepsSection, defaultTaskDocV3, TASK_DOC_VERSION_V3, } from "./doc-template.js";
11
+ const TASK_NEW_DUPLICATE_THRESHOLD = 0.75;
12
+ const TASK_NEW_DUPLICATE_STOPWORDS = new Set([
13
+ "a",
14
+ "an",
15
+ "and",
16
+ "for",
17
+ "from",
18
+ "in",
19
+ "is",
20
+ "of",
21
+ "on",
22
+ "the",
23
+ "to",
24
+ "when",
25
+ "with",
26
+ ]);
11
27
  function dedupeTrimmed(values) {
12
28
  const seen = new Set();
13
29
  const out = [];
@@ -55,11 +71,65 @@ function sanitizeTaskNewParsed(p) {
55
71
  const verify = dedupeTrimmed(p.verify);
56
72
  return { ...p, title, description, owner, tags, dependsOn, verify };
57
73
  }
74
+ function normalizeDuplicateTitleTokens(value) {
75
+ return value
76
+ .trim()
77
+ .toLowerCase()
78
+ .replaceAll(/[^a-z0-9]+/g, " ")
79
+ .split(/\s+/)
80
+ .map((token) => token.trim())
81
+ .filter((token) => token.length > 0 &&
82
+ (token.length > 2 || /\d/.test(token)) &&
83
+ !TASK_NEW_DUPLICATE_STOPWORDS.has(token));
84
+ }
85
+ function duplicateSimilarity(left, right) {
86
+ const leftTokens = normalizeDuplicateTitleTokens(left);
87
+ const rightTokens = normalizeDuplicateTitleTokens(right);
88
+ if (leftTokens.length === 0 || rightTokens.length === 0) {
89
+ return left.trim().toLowerCase() === right.trim().toLowerCase() ? 1 : 0;
90
+ }
91
+ const leftSet = new Set(leftTokens);
92
+ const rightSet = new Set(rightTokens);
93
+ const intersection = [...leftSet].filter((token) => rightSet.has(token)).length;
94
+ const union = new Set([...leftSet, ...rightSet]).size;
95
+ return union === 0 ? 0 : intersection / union;
96
+ }
97
+ function listOpenTaskDuplicates(tasks, title) {
98
+ return tasks
99
+ .filter((task) => String(task.status ?? "").toUpperCase() !== "DONE")
100
+ .map((task) => ({
101
+ task,
102
+ score: duplicateSimilarity(task.title ?? "", title),
103
+ }))
104
+ .filter(({ score }) => score >= TASK_NEW_DUPLICATE_THRESHOLD)
105
+ .toSorted((left, right) => right.score - left.score || left.task.id.localeCompare(right.task.id));
106
+ }
107
+ function formatDuplicateTaskMessage(duplicates, allowDuplicate) {
108
+ const summary = duplicates
109
+ .slice(0, 3)
110
+ .map(({ task, score }) => `${task.id} (${Math.round(score * 100)}% title overlap, status=${String(task.status || "TODO").toUpperCase()}): ${task.title}`)
111
+ .join("; ");
112
+ const tail = allowDuplicate
113
+ ? "creating a duplicate because --allow-duplicate was passed"
114
+ : "rerun with --allow-duplicate only when intentional or close the extra task with `agentplane task close-duplicate`";
115
+ return `potential duplicate open task detected: ${summary}; ${tail}.`;
116
+ }
58
117
  export async function runTaskNewParsed(opts) {
59
118
  const p = sanitizeTaskNewParsed(opts.parsed);
60
119
  try {
61
120
  const ctx = opts.ctx ??
62
121
  (await loadCommandContext({ cwd: opts.cwd, rootOverride: opts.rootOverride ?? null }));
122
+ const duplicateTasks = listOpenTaskDuplicates(await ctx.taskBackend.listTasks(), p.title);
123
+ if (duplicateTasks.length > 0 && !p.allowDuplicate) {
124
+ throw new CliError({
125
+ exitCode: 3,
126
+ code: "E_VALIDATION",
127
+ message: formatDuplicateTaskMessage(duplicateTasks, false),
128
+ });
129
+ }
130
+ if (duplicateTasks.length > 0 && p.allowDuplicate) {
131
+ process.stderr.write(`${warnMessage(formatDuplicateTaskMessage(duplicateTasks, true))}\n`);
132
+ }
63
133
  const suffixLength = ctx.config.tasks.id_suffix_length_default;
64
134
  if (!ctx.taskBackend.generateTaskId) {
65
135
  throw new CliError({
@@ -96,7 +166,7 @@ export async function runTaskNewParsed(opts) {
96
166
  primary: primary.primary,
97
167
  verifyCommands: p.verify,
98
168
  }));
99
- 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`);
169
+ process.stderr.write(`${warnMessage("task requires Verify Steps by primary tag; seeded a concrete ## Verify Steps section in README (refine it only if the task needs stricter acceptance coverage)")}\n`);
100
170
  }
101
171
  const hasSpike = p.tags.some((tag) => tag.trim().toLowerCase() === spikeTag);
102
172
  const hasImplementationTags = requiresVerifyStepsByPrimary(p.tags, ctx.config);
@@ -1 +1 @@
1
- {"version":3,"file":"new.spec.d.ts","sourceRoot":"","sources":["../../../src/commands/task/new.spec.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAE1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAG9C,eAAO,MAAM,WAAW,EAAE,WAAW,CAAC,aAAa,CA+ElD,CAAC"}
1
+ {"version":3,"file":"new.spec.d.ts","sourceRoot":"","sources":["../../../src/commands/task/new.spec.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAE1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAG9C,eAAO,MAAM,WAAW,EAAE,WAAW,CAAC,aAAa,CAuFlD,CAAC"}
@@ -57,6 +57,12 @@ export const taskNewSpec = {
57
57
  repeatable: true,
58
58
  description: "Repeatable. Verification commands/checks to run for this task.",
59
59
  },
60
+ {
61
+ kind: "boolean",
62
+ name: "allow-duplicate",
63
+ default: false,
64
+ description: "Allow creating a task even when an open task with a highly similar title already exists.",
65
+ },
60
66
  ],
61
67
  examples: [
62
68
  {
@@ -76,5 +82,6 @@ export const taskNewSpec = {
76
82
  tags: (raw.opts.tag ?? []),
77
83
  dependsOn: (raw.opts["depends-on"] ?? []),
78
84
  verify: (raw.opts.verify ?? []),
85
+ allowDuplicate: raw.opts["allow-duplicate"] === true,
79
86
  }),
80
87
  };
@@ -5,6 +5,8 @@ export type TaskNormalizeParsed = {
5
5
  force: boolean;
6
6
  yes: boolean;
7
7
  syncHostedMerges: boolean;
8
+ syncBranchPrState: boolean;
9
+ taskIds: string[];
8
10
  };
9
11
  export declare const taskNormalizeSpec: CommandSpec<TaskNormalizeParsed>;
10
12
  export declare function makeRunTaskNormalizeHandler(getCtx: (cmd: string) => Promise<CommandContext>): (ctx: CommandCtx, p: TaskNormalizeParsed) => Promise<number>;
@@ -1 +1 @@
1
- {"version":3,"file":"normalize.command.d.ts","sourceRoot":"","sources":["../../../src/commands/task/normalize.command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACtE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAIhE,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,EAAE,OAAO,CAAC;IACb,gBAAgB,EAAE,OAAO,CAAC;CAC3B,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,WAAW,CAAC,mBAAmB,CA6C9D,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,CAWxE"}
1
+ {"version":3,"file":"normalize.command.d.ts","sourceRoot":"","sources":["../../../src/commands/task/normalize.command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAEtE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAIhE,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,EAAE,OAAO,CAAC;IACb,gBAAgB,EAAE,OAAO,CAAC;IAC1B,iBAAiB,EAAE,OAAO,CAAC;IAC3B,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,WAAW,CAAC,mBAAmB,CA2F9D,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"}
@@ -1,3 +1,4 @@
1
+ import { usageError } from "../../cli/spec/errors.js";
1
2
  import { cmdTaskNormalize } from "./normalize.js";
2
3
  export const taskNormalizeSpec = {
3
4
  id: ["task", "normalize"],
@@ -28,6 +29,19 @@ export const taskNormalizeSpec = {
28
29
  default: false,
29
30
  description: "Query GitHub for merged hosted PRs referenced by local PR artifacts and reconcile stale branch_pr task state before normalization.",
30
31
  },
32
+ {
33
+ kind: "boolean",
34
+ name: "sync-branch-pr-state",
35
+ default: false,
36
+ description: "Reconcile stale local branch_pr task state when verified task commits already landed on the base branch.",
37
+ },
38
+ {
39
+ kind: "string",
40
+ name: "task-id",
41
+ valueHint: "<task-id>",
42
+ repeatable: true,
43
+ description: "Repeatable. Limit reconcile modes to explicit task ids instead of scanning every task.",
44
+ },
31
45
  ],
32
46
  examples: [
33
47
  { cmd: "agentplane task normalize", why: "Normalize tasks and print a short summary." },
@@ -36,12 +50,41 @@ export const taskNormalizeSpec = {
36
50
  cmd: "agentplane task normalize --sync-hosted-merges",
37
51
  why: "Reconcile stale branch_pr task state from hosted PR merges, then normalize the local projection.",
38
52
  },
53
+ {
54
+ cmd: "agentplane task normalize --sync-branch-pr-state",
55
+ why: "Reconcile locally shipped branch_pr task state when task commits already reached the base branch.",
56
+ },
57
+ {
58
+ cmd: "agentplane task normalize --sync-hosted-merges --task-id 202604071853-XGX2YJ",
59
+ why: "Reconcile only known stale tasks instead of scanning unrelated historical PR metadata.",
60
+ },
39
61
  ],
62
+ validateRaw: (raw) => {
63
+ const taskIds = raw.opts["task-id"] ?? [];
64
+ if (taskIds.length > 0 &&
65
+ raw.opts["sync-hosted-merges"] !== true &&
66
+ raw.opts["sync-branch-pr-state"] !== true) {
67
+ throw usageError({
68
+ spec: taskNormalizeSpec,
69
+ message: "--task-id requires --sync-hosted-merges and/or --sync-branch-pr-state.",
70
+ });
71
+ }
72
+ for (const taskId of taskIds) {
73
+ if (typeof taskId !== "string" || taskId.trim().length === 0) {
74
+ throw usageError({
75
+ spec: taskNormalizeSpec,
76
+ message: "Invalid value for --task-id: empty.",
77
+ });
78
+ }
79
+ }
80
+ },
40
81
  parse: (raw) => ({
41
82
  quiet: raw.opts.quiet === true,
42
83
  force: raw.opts.force === true,
43
84
  yes: raw.opts.yes === true,
44
85
  syncHostedMerges: raw.opts["sync-hosted-merges"] === true,
86
+ syncBranchPrState: raw.opts["sync-branch-pr-state"] === true,
87
+ taskIds: (raw.opts["task-id"] ?? []).map((taskId) => taskId.trim()),
45
88
  }),
46
89
  };
47
90
  export function makeRunTaskNormalizeHandler(getCtx) {
@@ -54,6 +97,8 @@ export function makeRunTaskNormalizeHandler(getCtx) {
54
97
  force: p.force,
55
98
  yes: p.yes,
56
99
  syncHostedMerges: p.syncHostedMerges,
100
+ syncBranchPrState: p.syncBranchPrState,
101
+ taskIds: p.taskIds,
57
102
  });
58
103
  };
59
104
  }
@@ -7,5 +7,7 @@ export declare function cmdTaskNormalize(opts: {
7
7
  force: boolean;
8
8
  yes?: boolean;
9
9
  syncHostedMerges?: boolean;
10
+ syncBranchPrState?: boolean;
11
+ taskIds?: string[];
10
12
  }): Promise<number>;
11
13
  //# sourceMappingURL=normalize.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"normalize.d.ts","sourceRoot":"","sources":["../../../src/commands/task/normalize.ts"],"names":[],"mappings":"AAGA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAIpF,wBAAsB,gBAAgB,CAAC,IAAI,EAAE;IAC3C,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,GAAG,OAAO,CAAC,MAAM,CAAC,CAqDlB"}
1
+ {"version":3,"file":"normalize.d.ts","sourceRoot":"","sources":["../../../src/commands/task/normalize.ts"],"names":[],"mappings":"AAKA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAgDpF,wBAAsB,gBAAgB,CAAC,IAAI,EAAE;IAC3C,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB,GAAG,OAAO,CAAC,MAAM,CAAC,CAqGlB"}