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
@@ -6,12 +6,12 @@ import { fileExists } from "../../cli/fs-utils.js";
6
6
  import { createCliEmitter, workflowModeMessage } from "../../cli/output.js";
7
7
  import { CliError } from "../../shared/errors.js";
8
8
  import { parsePrMeta } from "../shared/pr-meta.js";
9
- import { gitListTaskBranches, parseTaskIdFromBranch } from "../shared/git-worktree.js";
9
+ import { findWorktreeForBranch, gitListTaskBranches, parseTaskIdFromBranch, } from "../shared/git-worktree.js";
10
10
  import { gitRevParse } from "../shared/git-ops.js";
11
- import { isTaskLocalOnlyAdvance } from "../shared/task-local-freshness.js";
12
11
  import { loadBackendTask, loadCommandContext, } from "../shared/task-backend.js";
13
- import { readPrArtifact, resolvePrPaths } from "./internal/pr-paths.js";
12
+ import { readPrArtifactFromBranch, resolvePrPaths } from "./internal/pr-paths.js";
14
13
  import { validateGithubPrBodyContents, validateReviewContents, } from "./internal/review-template.js";
14
+ import { assessPrArtifactFreshness } from "./internal/freshness.js";
15
15
  function isUnknownRevisionError(err) {
16
16
  const message = err instanceof Error ? err.message : String(err);
17
17
  return /unknown revision or path not in the working tree/i.test(message);
@@ -31,26 +31,101 @@ async function resolveArtifactBranch(opts) {
31
31
  }
32
32
  return null;
33
33
  }
34
- async function readPrArtifactWithOptionalBranch(opts) {
35
- const localPath = path.join(opts.prDir, opts.fileName);
36
- if (await fileExists(localPath)) {
37
- return await readFile(localPath, "utf8");
34
+ async function readLocalPrArtifactText(prDir, fileName) {
35
+ const localPath = path.join(prDir, fileName);
36
+ if (!(await fileExists(localPath)))
37
+ return null;
38
+ return await readFile(localPath, "utf8");
39
+ }
40
+ function validateSnapshotContents(opts) {
41
+ const errors = [];
42
+ let meta = null;
43
+ if (opts.texts.metaText) {
44
+ try {
45
+ meta = parsePrMeta(opts.texts.metaText, opts.taskId);
46
+ }
47
+ catch (err) {
48
+ const message = err instanceof Error ? err.message : String(err);
49
+ errors.push(message);
50
+ }
38
51
  }
39
- if (opts.branchCache.value === undefined) {
40
- opts.branchCache.value = await resolveArtifactBranch({
41
- ctx: opts.ctx,
42
- resolved: opts.resolved,
43
- taskId: opts.taskId,
44
- });
52
+ else {
53
+ errors.push(`Missing PR directory: ${opts.relPrDir}`, `Missing ${opts.relMetaPath}`);
45
54
  }
46
- if (!opts.branchCache.value)
47
- return null;
48
- return await readPrArtifact({
49
- resolved: opts.resolved,
50
- prDir: opts.prDir,
51
- fileName: opts.fileName,
52
- branch: opts.branchCache.value,
55
+ if (opts.texts.diffstatText === null)
56
+ errors.push(`Missing ${opts.relDiffstatPath}`);
57
+ if (opts.texts.verifyLogText === null)
58
+ errors.push(`Missing ${opts.relVerifyLogPath}`);
59
+ if (opts.texts.reviewText) {
60
+ validateReviewContents(opts.texts.reviewText, errors);
61
+ }
62
+ else {
63
+ errors.push(`Missing ${opts.relReviewPath}`);
64
+ }
65
+ if (!opts.texts.githubTitleText?.trim()) {
66
+ errors.push(`Missing ${opts.relGithubTitlePath}`);
67
+ }
68
+ if (opts.texts.githubBodyText) {
69
+ validateGithubPrBodyContents(opts.texts.githubBodyText, errors);
70
+ }
71
+ else {
72
+ errors.push(`Missing ${opts.relGithubBodyPath}`);
73
+ }
74
+ return { meta, errors };
75
+ }
76
+ async function evaluateSnapshotFreshness(opts) {
77
+ if (!opts.snapshot.meta || !opts.branchHeadSha)
78
+ return;
79
+ const freshness = await assessPrArtifactFreshness({
80
+ gitRoot: opts.gitRoot,
81
+ workflowDir: opts.workflowDir,
82
+ taskId: opts.taskId,
83
+ branchHeadSha: opts.branchHeadSha,
84
+ metaHeadSha: opts.snapshot.meta.head_sha ?? null,
85
+ metaLastVerifiedSha: opts.snapshot.meta.last_verified_sha ?? null,
86
+ metaVerifyStatus: opts.snapshot.meta.verify?.status ?? null,
87
+ taskVerificationState: opts.taskVerificationState,
88
+ verifyLogText: opts.snapshot.texts.verifyLogText,
89
+ requiresVerify: opts.requiresVerify,
53
90
  });
91
+ opts.snapshot.freshnessEvaluated = true;
92
+ opts.snapshot.freshnessReviewFresh = freshness.reviewFresh;
93
+ opts.snapshot.freshnessVerifySatisfied = freshness.verifySatisfied;
94
+ opts.snapshot.freshnessVerifyFresh = freshness.verifyFresh;
95
+ opts.snapshot.freshnessVerifyLogSha = freshness.verifyLogSha;
96
+ }
97
+ function finalizeSnapshotErrors(opts) {
98
+ const errors = [...opts.snapshot.errors];
99
+ const meta = opts.snapshot.meta;
100
+ if (!meta)
101
+ return errors;
102
+ if (opts.branchHeadSha && opts.snapshot.freshnessEvaluated) {
103
+ if (!opts.snapshot.freshnessReviewFresh) {
104
+ errors.push(`PR artifacts stale: head_sha=${meta.head_sha ?? "<missing>"} current_head=${opts.branchHeadSha}`);
105
+ }
106
+ if (opts.requiresVerify && !opts.snapshot.freshnessVerifySatisfied) {
107
+ if (meta.verify?.status !== "pass") {
108
+ errors.push("Verify requirements not satisfied (meta.verify.status != pass)");
109
+ }
110
+ if ((!meta.last_verified_sha || !meta.last_verified_at) &&
111
+ !opts.snapshot.freshnessVerifyLogSha) {
112
+ errors.push("Verify metadata missing (last_verified_sha/last_verified_at)");
113
+ }
114
+ }
115
+ if (opts.requiresVerify && meta.last_verified_sha && !opts.snapshot.freshnessVerifyFresh) {
116
+ errors.push(`Verify state stale: last_verified_sha=${meta.last_verified_sha} current_head=${opts.branchHeadSha}`);
117
+ }
118
+ return errors;
119
+ }
120
+ if (opts.requiresVerify) {
121
+ if (meta.verify?.status !== "pass") {
122
+ errors.push("Verify requirements not satisfied (meta.verify.status != pass)");
123
+ }
124
+ if (!meta.last_verified_sha || !meta.last_verified_at) {
125
+ errors.push("Verify metadata missing (last_verified_sha/last_verified_at)");
126
+ }
127
+ }
128
+ return errors;
54
129
  }
55
130
  export async function cmdPrCheck(opts) {
56
131
  try {
@@ -80,99 +155,51 @@ export async function cmdPrCheck(opts) {
80
155
  const relGithubTitlePath = path.relative(resolved.gitRoot, githubTitlePath);
81
156
  const relGithubBodyPath = path.relative(resolved.gitRoot, githubBodyPath);
82
157
  const branchCache = {};
83
- let meta = null;
84
- const metaText = await readPrArtifactWithOptionalBranch({
85
- ctx,
86
- resolved,
87
- prDir,
88
- fileName: "meta.json",
158
+ const requiresVerify = Boolean(task.verify && task.verify.length > 0);
159
+ const localTexts = {
160
+ metaText: await readLocalPrArtifactText(prDir, "meta.json"),
161
+ diffstatText: await readLocalPrArtifactText(prDir, "diffstat.txt"),
162
+ verifyLogText: await readLocalPrArtifactText(prDir, "verify.log"),
163
+ reviewText: await readLocalPrArtifactText(prDir, "review.md"),
164
+ githubTitleText: await readLocalPrArtifactText(prDir, "github-title.txt"),
165
+ githubBodyText: await readLocalPrArtifactText(prDir, "github-body.md"),
166
+ };
167
+ const localParsed = validateSnapshotContents({
168
+ texts: localTexts,
169
+ relPrDir,
170
+ relMetaPath,
171
+ relDiffstatPath,
172
+ relVerifyLogPath,
173
+ relReviewPath,
174
+ relGithubTitlePath,
175
+ relGithubBodyPath,
89
176
  taskId: task.id,
90
- branchCache,
91
177
  });
92
- if (metaText) {
93
- try {
94
- meta = parsePrMeta(metaText, task.id);
95
- }
96
- catch (err) {
97
- const message = err instanceof Error ? err.message : String(err);
98
- errors.push(message);
99
- }
178
+ const localSnapshot = {
179
+ source: "local",
180
+ texts: localTexts,
181
+ meta: localParsed.meta,
182
+ errors: localParsed.errors,
183
+ freshnessEvaluated: false,
184
+ freshnessReviewFresh: false,
185
+ freshnessVerifySatisfied: false,
186
+ freshnessVerifyFresh: false,
187
+ freshnessVerifyLogSha: null,
188
+ };
189
+ const localBranch = localSnapshot.meta?.branch?.trim() ?? "";
190
+ if (localBranch) {
191
+ branchCache.value = localBranch;
100
192
  }
101
- else {
102
- errors.push(`Missing PR directory: ${relPrDir}`, `Missing ${relMetaPath}`);
103
- }
104
- const diffstatText = await readPrArtifactWithOptionalBranch({
105
- ctx,
106
- resolved,
107
- prDir,
108
- fileName: "diffstat.txt",
109
- taskId: task.id,
110
- branchCache,
111
- });
112
- if (diffstatText === null) {
113
- errors.push(`Missing ${relDiffstatPath}`);
114
- }
115
- const verifyLogText = await readPrArtifactWithOptionalBranch({
116
- ctx,
117
- resolved,
118
- prDir,
119
- fileName: "verify.log",
120
- taskId: task.id,
121
- branchCache,
122
- });
123
- if (verifyLogText === null) {
124
- errors.push(`Missing ${relVerifyLogPath}`);
125
- }
126
- const reviewText = await readPrArtifactWithOptionalBranch({
127
- ctx,
128
- resolved,
129
- prDir,
130
- fileName: "review.md",
131
- taskId: task.id,
132
- branchCache,
133
- });
134
- if (reviewText) {
135
- validateReviewContents(reviewText, errors);
136
- }
137
- else {
138
- errors.push(`Missing ${relReviewPath}`);
139
- }
140
- const githubTitleText = await readPrArtifactWithOptionalBranch({
141
- ctx,
142
- resolved,
143
- prDir,
144
- fileName: "github-title.txt",
145
- taskId: task.id,
146
- branchCache,
147
- });
148
- if (!githubTitleText?.trim()) {
149
- errors.push(`Missing ${relGithubTitlePath}`);
150
- }
151
- const githubBodyText = await readPrArtifactWithOptionalBranch({
152
- ctx,
153
- resolved,
154
- prDir,
155
- fileName: "github-body.md",
156
- taskId: task.id,
157
- branchCache,
158
- });
159
- if (githubBodyText) {
160
- validateGithubPrBodyContents(githubBodyText, errors);
161
- }
162
- else {
163
- errors.push(`Missing ${relGithubBodyPath}`);
164
- }
165
- if (task.verify && task.verify.length > 0) {
166
- if (meta?.verify?.status !== "pass") {
167
- errors.push("Verify requirements not satisfied (meta.verify.status != pass)");
168
- }
169
- if (!meta?.last_verified_sha || !meta.last_verified_at) {
170
- errors.push("Verify metadata missing (last_verified_sha/last_verified_at)");
171
- }
193
+ else if (branchCache.value === undefined) {
194
+ branchCache.value = await resolveArtifactBranch({
195
+ ctx,
196
+ resolved,
197
+ taskId: task.id,
198
+ });
172
199
  }
173
- const branchForFreshness = branchCache.value ?? (typeof meta?.branch === "string" ? meta.branch.trim() : null);
174
- if (meta && branchForFreshness) {
175
- let branchHeadSha = null;
200
+ const branchForFreshness = branchCache.value ?? null;
201
+ let branchHeadSha = null;
202
+ if (branchForFreshness) {
176
203
  try {
177
204
  branchHeadSha = await gitRevParse(resolved.gitRoot, [branchForFreshness]);
178
205
  }
@@ -180,34 +207,110 @@ export async function cmdPrCheck(opts) {
180
207
  if (!isUnknownRevisionError(err))
181
208
  throw err;
182
209
  }
183
- if (branchHeadSha) {
184
- const reviewFresh = (meta.head_sha ?? null) === branchHeadSha ||
185
- (await isTaskLocalOnlyAdvance({
186
- gitRoot: resolved.gitRoot,
187
- workflowDir: config.paths.workflow_dir,
188
- taskId: task.id,
189
- fromRef: meta.head_sha ?? null,
190
- toRef: branchHeadSha,
191
- }));
192
- if (!reviewFresh) {
193
- errors.push(`PR artifacts stale: head_sha=${meta.head_sha ?? "<missing>"} current_head=${branchHeadSha}`);
194
- }
195
- const verifyFresh = !task.verify ||
196
- task.verify.length === 0 ||
197
- !meta.last_verified_sha ||
198
- meta.last_verified_sha === branchHeadSha ||
199
- (await isTaskLocalOnlyAdvance({
200
- gitRoot: resolved.gitRoot,
201
- workflowDir: config.paths.workflow_dir,
202
- taskId: task.id,
203
- fromRef: meta.last_verified_sha,
204
- toRef: branchHeadSha,
205
- }));
206
- if (task.verify && task.verify.length > 0 && meta.last_verified_sha && !verifyFresh) {
207
- errors.push(`Verify state stale: last_verified_sha=${meta.last_verified_sha} current_head=${branchHeadSha}`);
208
- }
210
+ }
211
+ await evaluateSnapshotFreshness({
212
+ snapshot: localSnapshot,
213
+ gitRoot: resolved.gitRoot,
214
+ workflowDir: config.paths.workflow_dir,
215
+ taskId: task.id,
216
+ branchHeadSha,
217
+ taskVerificationState: task.verification?.state ?? null,
218
+ requiresVerify,
219
+ });
220
+ let selectedSnapshot = localSnapshot;
221
+ if (branchForFreshness &&
222
+ branchHeadSha &&
223
+ (!localSnapshot.meta ||
224
+ !localSnapshot.freshnessReviewFresh ||
225
+ (requiresVerify && !localSnapshot.freshnessVerifySatisfied))) {
226
+ const worktreePath = await findWorktreeForBranch(resolved.gitRoot, branchForFreshness);
227
+ const branchTexts = {
228
+ metaText: await readPrArtifactFromBranch({
229
+ resolved,
230
+ prDir,
231
+ fileName: "meta.json",
232
+ branch: branchForFreshness,
233
+ worktreePath,
234
+ }),
235
+ diffstatText: await readPrArtifactFromBranch({
236
+ resolved,
237
+ prDir,
238
+ fileName: "diffstat.txt",
239
+ branch: branchForFreshness,
240
+ worktreePath,
241
+ }),
242
+ verifyLogText: await readPrArtifactFromBranch({
243
+ resolved,
244
+ prDir,
245
+ fileName: "verify.log",
246
+ branch: branchForFreshness,
247
+ worktreePath,
248
+ }),
249
+ reviewText: await readPrArtifactFromBranch({
250
+ resolved,
251
+ prDir,
252
+ fileName: "review.md",
253
+ branch: branchForFreshness,
254
+ worktreePath,
255
+ }),
256
+ githubTitleText: await readPrArtifactFromBranch({
257
+ resolved,
258
+ prDir,
259
+ fileName: "github-title.txt",
260
+ branch: branchForFreshness,
261
+ worktreePath,
262
+ }),
263
+ githubBodyText: await readPrArtifactFromBranch({
264
+ resolved,
265
+ prDir,
266
+ fileName: "github-body.md",
267
+ branch: branchForFreshness,
268
+ worktreePath,
269
+ }),
270
+ };
271
+ const branchParsed = validateSnapshotContents({
272
+ texts: branchTexts,
273
+ relPrDir,
274
+ relMetaPath,
275
+ relDiffstatPath,
276
+ relVerifyLogPath,
277
+ relReviewPath,
278
+ relGithubTitlePath,
279
+ relGithubBodyPath,
280
+ taskId: task.id,
281
+ });
282
+ const branchSnapshot = {
283
+ source: "branch",
284
+ texts: branchTexts,
285
+ meta: branchParsed.meta,
286
+ errors: branchParsed.errors,
287
+ freshnessEvaluated: false,
288
+ freshnessReviewFresh: false,
289
+ freshnessVerifySatisfied: false,
290
+ freshnessVerifyFresh: false,
291
+ freshnessVerifyLogSha: null,
292
+ };
293
+ await evaluateSnapshotFreshness({
294
+ snapshot: branchSnapshot,
295
+ gitRoot: resolved.gitRoot,
296
+ workflowDir: config.paths.workflow_dir,
297
+ taskId: task.id,
298
+ branchHeadSha,
299
+ taskVerificationState: task.verification?.state ?? null,
300
+ requiresVerify,
301
+ });
302
+ if (branchSnapshot.errors.length === 0 &&
303
+ branchSnapshot.meta &&
304
+ branchSnapshot.freshnessReviewFresh &&
305
+ (!requiresVerify || branchSnapshot.freshnessVerifySatisfied)) {
306
+ selectedSnapshot = branchSnapshot;
209
307
  }
210
308
  }
309
+ errors.push(...finalizeSnapshotErrors({
310
+ snapshot: selectedSnapshot,
311
+ branchHeadSha,
312
+ requiresVerify,
313
+ }));
211
314
  if (errors.length > 0) {
212
315
  throw new CliError({
213
316
  exitCode: exitCodeForError("E_VALIDATION"),
@@ -0,0 +1,9 @@
1
+ import { type CommandContext } from "../shared/task-backend.js";
2
+ export declare function cmdPrCloseSuperseded(opts: {
3
+ ctx?: CommandContext;
4
+ cwd: string;
5
+ rootOverride?: string;
6
+ taskId: string;
7
+ deleteRemoteBranch: boolean;
8
+ }): Promise<number>;
9
+ //# sourceMappingURL=close-superseded.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"close-superseded.d.ts","sourceRoot":"","sources":["../../../src/commands/pr/close-superseded.ts"],"names":[],"mappings":"AAOA,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,2BAA2B,CAAC;AAiCnC,wBAAsB,oBAAoB,CAAC,IAAI,EAAE;IAC/C,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,kBAAkB,EAAE,OAAO,CAAC;CAC7B,GAAG,OAAO,CAAC,MAAM,CAAC,CAsHlB"}
@@ -0,0 +1,129 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { mapCoreError } from "../../cli/error-map.js";
3
+ import { exitCodeForError } from "../../cli/exit-codes.js";
4
+ import { createCliEmitter, infoMessage, workflowModeMessage } from "../../cli/output.js";
5
+ import { fileExists } from "../../cli/fs-utils.js";
6
+ import { CliError } from "../../shared/errors.js";
7
+ import { loadBackendTask, loadCommandContext, } from "../shared/task-backend.js";
8
+ import { parsePrMetaForwardCompatible } from "../shared/pr-meta.js";
9
+ import { cmdPrClose } from "./close.js";
10
+ import { isGhNotFound, resolveDefaultGithubRepo, runGhApiJson, runGhApiNoOutput, } from "./internal/gh-api.js";
11
+ import { resolvePrPaths } from "./internal/pr-paths.js";
12
+ async function deleteRemoteBranch(opts) {
13
+ const endpoint = `repos/${opts.repo}/git/refs/heads/${encodeURIComponent(opts.branch)}`;
14
+ try {
15
+ await runGhApiNoOutput(opts.cwd, [endpoint, "-X", "DELETE"]);
16
+ return "deleted";
17
+ }
18
+ catch (err) {
19
+ if (isGhNotFound(err))
20
+ return "already-absent";
21
+ throw err;
22
+ }
23
+ }
24
+ export async function cmdPrCloseSuperseded(opts) {
25
+ try {
26
+ const output = createCliEmitter();
27
+ const ctx = opts.ctx ??
28
+ (await loadCommandContext({ cwd: opts.cwd, rootOverride: opts.rootOverride ?? null }));
29
+ const { task } = await loadBackendTask({
30
+ ctx,
31
+ cwd: opts.cwd,
32
+ rootOverride: opts.rootOverride,
33
+ taskId: opts.taskId,
34
+ });
35
+ if (String(task.status ?? "")
36
+ .trim()
37
+ .toUpperCase() !== "DONE") {
38
+ throw new CliError({
39
+ exitCode: exitCodeForError("E_VALIDATION"),
40
+ code: "E_VALIDATION",
41
+ message: `Task ${opts.taskId} must be DONE before closing superseded PRs.`,
42
+ });
43
+ }
44
+ const { config, metaPath, resolved } = await resolvePrPaths({ ...opts, ctx });
45
+ if (config.workflow_mode !== "branch_pr") {
46
+ throw new CliError({
47
+ exitCode: exitCodeForError("E_USAGE"),
48
+ code: "E_USAGE",
49
+ message: workflowModeMessage(config.workflow_mode, "branch_pr"),
50
+ });
51
+ }
52
+ if (!(await fileExists(metaPath))) {
53
+ throw new CliError({
54
+ exitCode: exitCodeForError("E_VALIDATION"),
55
+ code: "E_VALIDATION",
56
+ message: `Missing PR metadata: ${metaPath}`,
57
+ });
58
+ }
59
+ const meta = parsePrMetaForwardCompatible(await readFile(metaPath, "utf8"), opts.taskId);
60
+ const branch = meta.branch?.trim() ?? "";
61
+ if (!branch) {
62
+ throw new CliError({
63
+ exitCode: exitCodeForError("E_VALIDATION"),
64
+ code: "E_VALIDATION",
65
+ message: `Missing PR branch for ${opts.taskId}`,
66
+ });
67
+ }
68
+ const repo = await resolveDefaultGithubRepo(resolved.gitRoot);
69
+ const owner = repo.split("/", 1)[0]?.trim() ?? "";
70
+ if (!owner) {
71
+ throw new CliError({
72
+ exitCode: exitCodeForError("E_VALIDATION"),
73
+ code: "E_VALIDATION",
74
+ message: "Could not derive GitHub owner from remote origin.",
75
+ });
76
+ }
77
+ const openPrs = await runGhApiJson(resolved.gitRoot, [
78
+ `repos/${repo}/pulls?head=${encodeURIComponent(`${owner}:${branch}`)}&state=open&per_page=100`,
79
+ ]);
80
+ if (openPrs.length === 0) {
81
+ const remoteBranchAction = opts.deleteRemoteBranch
82
+ ? await deleteRemoteBranch({ cwd: resolved.gitRoot, repo, branch })
83
+ : "skipped";
84
+ output.report([
85
+ { label: "task", value: opts.taskId },
86
+ { label: "branch", value: branch },
87
+ { label: "state", value: "skipped" },
88
+ { label: "reason", value: "no open task PR found" },
89
+ { label: "remote_branch_action", value: remoteBranchAction },
90
+ ], { header: infoMessage(`pr close-superseded ${opts.taskId}`) });
91
+ return 0;
92
+ }
93
+ if (openPrs.length > 1) {
94
+ throw new CliError({
95
+ exitCode: exitCodeForError("E_VALIDATION"),
96
+ code: "E_VALIDATION",
97
+ message: `Multiple open PRs match task ${opts.taskId} branch ${branch}: ${openPrs
98
+ .map((pr) => pr.number)
99
+ .filter((value) => typeof value === "number" && Number.isInteger(value) && value > 0)
100
+ .map((value) => `#${value}`)
101
+ .join(", ")}`,
102
+ });
103
+ }
104
+ const prNumber = Number(openPrs[0]?.number ?? 0);
105
+ if (!Number.isInteger(prNumber) || prNumber <= 0) {
106
+ throw new CliError({
107
+ exitCode: exitCodeForError("E_VALIDATION"),
108
+ code: "E_VALIDATION",
109
+ message: `Could not determine open PR number for task ${opts.taskId}.`,
110
+ });
111
+ }
112
+ return await cmdPrClose({
113
+ ctx,
114
+ cwd: opts.cwd,
115
+ rootOverride: opts.rootOverride,
116
+ prNumber,
117
+ comment: `Superseded by protected-main closure of task ${opts.taskId}.`,
118
+ deleteRemoteBranch: opts.deleteRemoteBranch,
119
+ });
120
+ }
121
+ catch (err) {
122
+ if (err instanceof CliError)
123
+ throw err;
124
+ throw mapCoreError(err, {
125
+ command: "pr close-superseded",
126
+ root: opts.rootOverride ?? null,
127
+ });
128
+ }
129
+ }
@@ -0,0 +1,11 @@
1
+ import { type CommandContext } from "../shared/task-backend.js";
2
+ export declare function cmdPrClose(opts: {
3
+ ctx?: CommandContext;
4
+ cwd: string;
5
+ rootOverride?: string;
6
+ prNumber: number;
7
+ repo?: string;
8
+ comment?: string;
9
+ deleteRemoteBranch: boolean;
10
+ }): Promise<number>;
11
+ //# sourceMappingURL=close.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"close.d.ts","sourceRoot":"","sources":["../../../src/commands/pr/close.ts"],"names":[],"mappings":"AAIA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,2BAA2B,CAAC;AA+CpF,wBAAsB,UAAU,CAAC,IAAI,EAAE;IACrC,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,OAAO,CAAC;CAC7B,GAAG,OAAO,CAAC,MAAM,CAAC,CAuGlB"}