@backburner/cli 0.1.0

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 (202) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +329 -0
  3. package/dist/src/agents/antigravity-launch.js +162 -0
  4. package/dist/src/agents/antigravity.js +251 -0
  5. package/dist/src/agents/claude-launch.js +117 -0
  6. package/dist/src/agents/claude.js +271 -0
  7. package/dist/src/agents/codex-launch.js +82 -0
  8. package/dist/src/agents/codex.js +261 -0
  9. package/dist/src/agents/composite-runner.js +13 -0
  10. package/dist/src/agents/extraction.js +442 -0
  11. package/dist/src/agents/failure-feedback.js +132 -0
  12. package/dist/src/agents/gemini-launch.js +96 -0
  13. package/dist/src/agents/gemini.js +392 -0
  14. package/dist/src/agents/job-agent-assignments.js +180 -0
  15. package/dist/src/agents/lifecycle-feedback.js +147 -0
  16. package/dist/src/agents/limit-detector.js +83 -0
  17. package/dist/src/agents/opencode-launch.js +138 -0
  18. package/dist/src/agents/opencode.js +156 -0
  19. package/dist/src/agents/plan-breakdown-feedback.js +97 -0
  20. package/dist/src/agents/pool.js +168 -0
  21. package/dist/src/agents/prompt.js +462 -0
  22. package/dist/src/agents/provider-sessions.js +105 -0
  23. package/dist/src/agents/review-feedback.js +255 -0
  24. package/dist/src/agents/selection.js +113 -0
  25. package/dist/src/agents/service.js +1087 -0
  26. package/dist/src/agents/types.js +1 -0
  27. package/dist/src/agents/wave-assessment-feedback.js +69 -0
  28. package/dist/src/attention/derive.js +337 -0
  29. package/dist/src/capabilities/index.js +2 -0
  30. package/dist/src/capabilities/projection.js +183 -0
  31. package/dist/src/capabilities/types.js +1 -0
  32. package/dist/src/capabilities/validator.js +87 -0
  33. package/dist/src/cli/commands/broker-smoke.js +96 -0
  34. package/dist/src/cli/commands/broker.js +56 -0
  35. package/dist/src/cli/commands/init.js +4 -0
  36. package/dist/src/cli/commands/journal.js +43 -0
  37. package/dist/src/cli/commands/run-loop.js +56 -0
  38. package/dist/src/cli/commands/run.js +1241 -0
  39. package/dist/src/cli/commands/tui.js +187 -0
  40. package/dist/src/cli/dispatcher.js +51 -0
  41. package/dist/src/cli/errors.js +2 -0
  42. package/dist/src/cli/init.js +92 -0
  43. package/dist/src/cli/options.js +86 -0
  44. package/dist/src/cli/output.js +231 -0
  45. package/dist/src/cli/run-journal.js +342 -0
  46. package/dist/src/cli/run-types.js +1 -0
  47. package/dist/src/cli/run.js +32 -0
  48. package/dist/src/cli/runtime/dispatch-lifecycle.js +772 -0
  49. package/dist/src/cli/runtime/provider-runtime-types.js +1 -0
  50. package/dist/src/cli/runtime/provider-runtime.js +152 -0
  51. package/dist/src/cli/runtime/provider-tools.js +73 -0
  52. package/dist/src/cli/runtime/providers/antigravity.js +26 -0
  53. package/dist/src/cli/runtime/providers/claude.js +20 -0
  54. package/dist/src/cli/runtime/providers/codex.js +20 -0
  55. package/dist/src/cli/runtime/providers/gemini.js +20 -0
  56. package/dist/src/cli/runtime/providers/opencode.js +20 -0
  57. package/dist/src/cli/runtime/run-context.js +131 -0
  58. package/dist/src/cli/tui/index.js +3 -0
  59. package/dist/src/cli/tui/screen.js +388 -0
  60. package/dist/src/cli/tui/tracker.js +558 -0
  61. package/dist/src/cli/tui/types.js +1 -0
  62. package/dist/src/cli/tui/views.js +1006 -0
  63. package/dist/src/config/loader.js +136 -0
  64. package/dist/src/config/schemas.js +403 -0
  65. package/dist/src/config/types.js +1 -0
  66. package/dist/src/context/types.js +331 -0
  67. package/dist/src/domain/entities.js +1 -0
  68. package/dist/src/git/backburner-git-tool-service.js +680 -0
  69. package/dist/src/git/gitops-lifecycle.js +56 -0
  70. package/dist/src/git/merge-prep-workspace.js +357 -0
  71. package/dist/src/git/types.js +484 -0
  72. package/dist/src/github/broker-model-content.js +215 -0
  73. package/dist/src/github/broker-server.js +237 -0
  74. package/dist/src/github/broker.js +638 -0
  75. package/dist/src/github/composed.js +53 -0
  76. package/dist/src/github/gateway.js +478 -0
  77. package/dist/src/github/normalize.js +222 -0
  78. package/dist/src/github/pr-classification.js +73 -0
  79. package/dist/src/github/scoped-broker.js +35 -0
  80. package/dist/src/github/security.js +126 -0
  81. package/dist/src/github/service.js +332 -0
  82. package/dist/src/github/types.js +1 -0
  83. package/dist/src/github/utils.js +16 -0
  84. package/dist/src/journal/index.js +4 -0
  85. package/dist/src/journal/reader.js +62 -0
  86. package/dist/src/journal/renderer.js +50 -0
  87. package/dist/src/journal/service.js +72 -0
  88. package/dist/src/journal/types.js +1 -0
  89. package/dist/src/journal/writer.js +19 -0
  90. package/dist/src/mcps/bridge.js +166 -0
  91. package/dist/src/mcps/index.js +5 -0
  92. package/dist/src/mcps/reconcile.js +113 -0
  93. package/dist/src/mcps/smithery-client.js +186 -0
  94. package/dist/src/mcps/smithery.js +164 -0
  95. package/dist/src/mcps/types.js +1 -0
  96. package/dist/src/memory/json-store.js +71 -0
  97. package/dist/src/memory/prompt.js +16 -0
  98. package/dist/src/memory/scope.js +31 -0
  99. package/dist/src/memory/store.js +1 -0
  100. package/dist/src/memory/tool-service.js +142 -0
  101. package/dist/src/memory/types.js +12 -0
  102. package/dist/src/onboarding/config-writer.js +96 -0
  103. package/dist/src/onboarding/engine.js +86 -0
  104. package/dist/src/onboarding/prompt.js +102 -0
  105. package/dist/src/onboarding/services/provider-discovery.js +58 -0
  106. package/dist/src/onboarding/services/repo-discovery.js +246 -0
  107. package/dist/src/onboarding/steps/check-github-auth.js +32 -0
  108. package/dist/src/onboarding/steps/check-requirements.js +34 -0
  109. package/dist/src/onboarding/steps/configure-models.js +105 -0
  110. package/dist/src/onboarding/steps/configure-paths.js +33 -0
  111. package/dist/src/onboarding/steps/discover-providers.js +38 -0
  112. package/dist/src/onboarding/steps/discover-repos.js +39 -0
  113. package/dist/src/onboarding/steps/generate-config.js +106 -0
  114. package/dist/src/onboarding/steps/readiness-validation.js +158 -0
  115. package/dist/src/onboarding/steps/select-repos.js +76 -0
  116. package/dist/src/onboarding/steps/show-summary.js +97 -0
  117. package/dist/src/onboarding/steps/welcome.js +27 -0
  118. package/dist/src/onboarding/types.js +1 -0
  119. package/dist/src/onboarding/validators/git-access.js +63 -0
  120. package/dist/src/onboarding/validators/github-auth.js +85 -0
  121. package/dist/src/onboarding/validators/tool-checker.js +153 -0
  122. package/dist/src/onboarding/validators/workspace.js +83 -0
  123. package/dist/src/prompts/resolver.js +106 -0
  124. package/dist/src/retrospectives/candidates.js +224 -0
  125. package/dist/src/retrospectives/derive.js +169 -0
  126. package/dist/src/retrospectives/ingest.js +321 -0
  127. package/dist/src/retrospectives/management-prs.js +380 -0
  128. package/dist/src/retrospectives/proposals.js +199 -0
  129. package/dist/src/retrospectives/scopes.js +272 -0
  130. package/dist/src/retrospectives/scoring.js +171 -0
  131. package/dist/src/retrospectives/types.js +1 -0
  132. package/dist/src/state/loader.js +473 -0
  133. package/dist/src/state/types.js +15 -0
  134. package/dist/src/tasks/derivation/builders.js +70 -0
  135. package/dist/src/tasks/derivation/handlers/comment-response.js +169 -0
  136. package/dist/src/tasks/derivation/handlers/implement-plan.js +103 -0
  137. package/dist/src/tasks/derivation/handlers/index.js +54 -0
  138. package/dist/src/tasks/derivation/handlers/packet-pr-control.js +92 -0
  139. package/dist/src/tasks/derivation/handlers/packet-worktree.js +29 -0
  140. package/dist/src/tasks/derivation/handlers/plan-breakdown.js +45 -0
  141. package/dist/src/tasks/derivation/handlers/prepare-for-merge.js +91 -0
  142. package/dist/src/tasks/derivation/handlers/product-discovery.js +36 -0
  143. package/dist/src/tasks/derivation/handlers/retry-failed-task.js +125 -0
  144. package/dist/src/tasks/derivation/handlers/review-pr.js +28 -0
  145. package/dist/src/tasks/derivation/handlers/shared.js +24 -0
  146. package/dist/src/tasks/derivation/handlers/sync-parent-branch.js +100 -0
  147. package/dist/src/tasks/derivation/handlers/wave-assessment.js +223 -0
  148. package/dist/src/tasks/derivation/handlers/write-plan.js +101 -0
  149. package/dist/src/tasks/derivation/handlers/write-product-spec.js +49 -0
  150. package/dist/src/tasks/derivation/runner.js +18 -0
  151. package/dist/src/tasks/derivation/types.js +1 -0
  152. package/dist/src/tasks/derive.js +810 -0
  153. package/dist/src/tasks/mcp-skip.js +10 -0
  154. package/dist/src/tasks/retry.js +10 -0
  155. package/dist/src/tasks/task.js +184 -0
  156. package/dist/src/tasks/types.js +1 -0
  157. package/dist/src/utils/command-runner.js +192 -0
  158. package/dist/src/utils/json.js +38 -0
  159. package/dist/src/utils/logger.js +43 -0
  160. package/dist/src/utils/paths.js +38 -0
  161. package/dist/src/utils/slug.js +10 -0
  162. package/dist/src/workflows/registry.js +174 -0
  163. package/dist/src/workflows/triage.js +90 -0
  164. package/dist/src/workflows/workstream-machine/derive-events.js +121 -0
  165. package/dist/src/workflows/workstream-machine/evaluate.js +24 -0
  166. package/dist/src/workflows/workstream-machine/events.js +1 -0
  167. package/dist/src/workflows/workstream-machine/machine.js +355 -0
  168. package/dist/src/workflows/workstream-machine/selectors.js +19 -0
  169. package/dist/src/workflows/workstream-machine/types.js +1 -0
  170. package/dist/src/workstreams/branch-sync.js +87 -0
  171. package/dist/src/workstreams/derive.js +629 -0
  172. package/dist/src/workstreams/mcp-blockers.js +420 -0
  173. package/dist/src/workstreams/packet-candidates.js +42 -0
  174. package/dist/src/workstreams/packet-lifecycle-feedback.js +60 -0
  175. package/dist/src/workstreams/packet-lifecycle-report.js +199 -0
  176. package/dist/src/workstreams/packet-plan.js +597 -0
  177. package/dist/src/workstreams/packet-projections.js +190 -0
  178. package/dist/src/workstreams/parent-branch-sync-action.js +312 -0
  179. package/dist/src/workstreams/parent-branch-sync.js +148 -0
  180. package/dist/src/workstreams/phases.js +4 -0
  181. package/dist/src/workstreams/plan-breakdown-generator/export.js +19 -0
  182. package/dist/src/workstreams/plan-breakdown-generator/index.js +6 -0
  183. package/dist/src/workstreams/plan-breakdown-generator/inspect.js +50 -0
  184. package/dist/src/workstreams/plan-breakdown-generator/patch.js +139 -0
  185. package/dist/src/workstreams/plan-breakdown-generator/session.js +19 -0
  186. package/dist/src/workstreams/plan-breakdown-generator/store.js +27 -0
  187. package/dist/src/workstreams/plan-breakdown-generator/tools.js +407 -0
  188. package/dist/src/workstreams/plan-breakdown-generator/types.js +6 -0
  189. package/dist/src/workstreams/plan-breakdown-generator/validate.js +212 -0
  190. package/dist/src/workstreams/plan-breakdown-schema.js +175 -0
  191. package/dist/src/workstreams/utils.js +15 -0
  192. package/dist/src/workstreams/wave-assessment/checkpoint.js +172 -0
  193. package/dist/src/workstreams/wave-assessment/index.js +3 -0
  194. package/dist/src/workstreams/wave-assessment/session.js +84 -0
  195. package/dist/src/workstreams/wave-assessment/store.js +51 -0
  196. package/dist/src/workstreams/wave-assessment/tools.js +419 -0
  197. package/dist/src/workstreams/wave-assessment/types.js +1 -0
  198. package/dist/src/workstreams/wave-assessment/validate.js +78 -0
  199. package/dist/src/worktrees/service.js +839 -0
  200. package/dist/src/worktrees/workspace-ownership.js +34 -0
  201. package/dist/src/worktrees/workspace-recovery.js +177 -0
  202. package/package.json +61 -0
@@ -0,0 +1,810 @@
1
+ import { deriveWorkstreams } from "../workstreams/derive.js";
2
+ import { deriveAttentionRecords } from "../attention/derive.js";
3
+ import { buildParentBranchSyncPreflight, hasGeneratedPlanBreakdown } from "../workstreams/packet-plan.js";
4
+ import { buildWaveAssessmentCheckpointIdentity, waveAssessmentCheckpointIdentityMatches } from "../workstreams/wave-assessment/checkpoint.js";
5
+ import { collectPacketPullRequestProjections } from "../workstreams/packet-projections.js";
6
+ import { classifyManagementPullRequest } from "../github/pr-classification.js";
7
+ import { runBlockingHandler, runHandlers } from "./derivation/runner.js";
8
+ import { activeParentWorkstreamHandlers, deriveManagementPullRequestCommentTasks, finalWorkstreamHandlers, isActiveParentWorkstream, parentWorkstreamHandlers, planBreakdownHandler } from "./derivation/handlers/index.js";
9
+ export function deriveTasks(input) {
10
+ const repoConfigs = new Map(input.repos.map((repo) => [repo.id, repo]));
11
+ const repoSyncRecords = new Map(input.repoSyncRecords.map((record) => [record.repoId, record]));
12
+ const previousIssues = new Map(input.previousState.issues.map((issue) => [issue.id, issue]));
13
+ const previousPullRequests = new Map(input.previousState.prs.map((pr) => [pr.id, pr]));
14
+ const previousComments = new Map(input.previousState.comments.map((comment) => [comment.id, comment]));
15
+ const currentIssues = [...input.currentIssues].sort(compareIssueLikeEntities);
16
+ const currentPullRequests = [...input.currentPullRequests].sort(compareIssueLikeEntities);
17
+ const currentComments = [...input.currentComments].sort(compareComments);
18
+ const worktrees = [...(input.worktrees ?? input.previousState.worktrees)].sort(compareWorktrees);
19
+ const currentIssuesById = new Map(currentIssues.map((issue) => [issue.id, issue]));
20
+ const currentPullRequestsById = new Map(currentPullRequests.map((pullRequest) => [pullRequest.id, pullRequest]));
21
+ const currentCommentsById = new Map(currentComments.map((comment) => [comment.id, comment]));
22
+ const currentWorktreePaths = new Set(worktrees.map((worktree) => worktree.path));
23
+ const provisionalWorkstreams = input.workstreams ??
24
+ deriveWorkstreams({
25
+ repos: input.repos,
26
+ currentIssues,
27
+ currentPullRequests,
28
+ previousPullRequests: input.previousState.prs,
29
+ worktrees,
30
+ previousWorkstreams: input.previousState.workstreams
31
+ });
32
+ const attentionRecords = input.attention ??
33
+ deriveAttentionRecords({
34
+ repos: input.repos,
35
+ workstreams: provisionalWorkstreams,
36
+ previousState: input.previousState,
37
+ currentIssues,
38
+ currentPullRequests,
39
+ currentComments,
40
+ tasks: input.previousState.tasks,
41
+ worktrees,
42
+ now: input.now
43
+ });
44
+ const workstreams = input.workstreams ??
45
+ deriveWorkstreams({
46
+ repos: input.repos,
47
+ currentIssues,
48
+ currentPullRequests,
49
+ previousPullRequests: input.previousState.prs,
50
+ worktrees,
51
+ previousWorkstreams: input.previousState.workstreams,
52
+ attention: attentionRecords
53
+ });
54
+ const existingTasks = input.previousState.tasks;
55
+ const existingTasksById = new Map(existingTasks.map((task) => [task.id, task]));
56
+ const desiredTasks = new Map();
57
+ const attentionByWorkstreamId = new Map(attentionRecords.map((record) => [record.workstreamId, record]));
58
+ const currentIssuesByRepoAndNumber = new Map(currentIssues.map((issue) => [`${issue.repoKey}:${issue.number}`, issue]));
59
+ const currentPrsByRepoAndNumber = new Map(currentPullRequests.map((pr) => [`${pr.repoKey}:${pr.number}`, pr]));
60
+ const worktreesById = new Map(worktrees.map((worktree) => [worktree.id, worktree]));
61
+ const worktreesByRepoAndBranch = new Map(worktrees.map((worktree) => [`${worktree.repoId}:${worktree.branchName}`, worktree]));
62
+ const worktreesByPath = new Map(worktrees.map((worktree) => [worktree.path, worktree]));
63
+ const currentPrsByRepoAndBranch = new Map(currentPullRequests.map((pr) => [`${pr.repoId}:${pr.headBranch}`, pr]));
64
+ const workstreamsById = new Map(workstreams.map((w) => [w.id, w]));
65
+ const packetPullRequestProjections = collectPacketPullRequestProjections({
66
+ pullRequests: currentPullRequests,
67
+ workstreams,
68
+ worktrees
69
+ });
70
+ const packetProjectionByRepoAndPrNumber = new Map();
71
+ for (const projection of packetPullRequestProjections) {
72
+ const key = `${projection.pullRequest.repoId}:${projection.pullRequest.number}`;
73
+ if (!packetProjectionByRepoAndPrNumber.has(key)) {
74
+ packetProjectionByRepoAndPrNumber.set(key, projection);
75
+ }
76
+ }
77
+ const packetWorktreesByWorkstreamId = new Map();
78
+ for (const worktree of worktrees) {
79
+ if (worktree.packet && isPacketWorktreeInCurrentPlan(worktree, workstreamsById)) {
80
+ const list = packetWorktreesByWorkstreamId.get(worktree.packet.parentWorkstreamId) ?? [];
81
+ list.push(worktree);
82
+ packetWorktreesByWorkstreamId.set(worktree.packet.parentWorkstreamId, list);
83
+ }
84
+ }
85
+ const env = {
86
+ input,
87
+ repoConfigs,
88
+ repoSyncRecords,
89
+ previousIssues,
90
+ previousPullRequests,
91
+ previousComments,
92
+ currentIssues,
93
+ currentPullRequests,
94
+ currentCommentsById,
95
+ currentIssuesByRepoAndNumber,
96
+ currentPrsByRepoAndNumber,
97
+ packetPullRequestProjections,
98
+ packetProjectionByRepoAndPrNumber,
99
+ packetWorktreesByWorkstreamId,
100
+ existingTasks,
101
+ existingTasksById
102
+ };
103
+ const taskSink = {
104
+ add: (task) => addTask(task, env, desiredTasks),
105
+ replace: (task) => {
106
+ desiredTasks.set(task.id, task);
107
+ return task;
108
+ },
109
+ get: (taskId) => desiredTasks.get(taskId),
110
+ preserveExisting: (taskId) => {
111
+ const existingTask = existingTasksById.get(taskId);
112
+ if (existingTask) {
113
+ desiredTasks.set(taskId, existingTask);
114
+ }
115
+ return existingTask;
116
+ }
117
+ };
118
+ for (const workstream of [...workstreams].sort(compareWorkstreams)) {
119
+ const context = resolveWorkstreamContext(workstream, attentionByWorkstreamId.get(workstream.id), currentIssuesByRepoAndNumber, currentPrsByRepoAndNumber, worktreesById, worktreesByRepoAndBranch, repoConfigs, repoSyncRecords, input.managementRepo, input.previousState.managementPrs);
120
+ if (context.isManagementPullRequest) {
121
+ for (const task of deriveManagementPullRequestCommentTasks(context, env, taskSink)) {
122
+ taskSink.add(task);
123
+ }
124
+ continue;
125
+ }
126
+ if (runBlockingHandler(planBreakdownHandler, context, env, taskSink)) {
127
+ continue;
128
+ }
129
+ runHandlers(parentWorkstreamHandlers, context, env, taskSink);
130
+ if (isActiveParentWorkstream(workstream)) {
131
+ runHandlers(activeParentWorkstreamHandlers, context, env, taskSink);
132
+ }
133
+ runHandlers(finalWorkstreamHandlers, context, env, taskSink);
134
+ }
135
+ for (const existingTask of existingTasks) {
136
+ if (desiredTasks.has(existingTask.id)) {
137
+ continue;
138
+ }
139
+ if (!isUnresolvedStatus(existingTask.status) && existingTask.status !== "failed") {
140
+ continue;
141
+ }
142
+ if (!shouldCarryForwardUnresolvedTask(existingTask, {
143
+ repoConfigs,
144
+ currentIssuesById,
145
+ currentIssuesByRepoAndNumber,
146
+ currentPullRequestsById,
147
+ currentCommentsById,
148
+ currentWorktreePaths,
149
+ worktreesByPath,
150
+ currentPullRequestsByRepoAndBranch: currentPrsByRepoAndBranch,
151
+ attentionByWorkstreamId,
152
+ workstreamsById,
153
+ repoSyncRecords,
154
+ previousTasks: input.previousState.tasks,
155
+ previousExecutions: input.previousState.executions,
156
+ ...(input.managementRepo ? { managementRepo: input.managementRepo } : {}),
157
+ managementPrs: input.previousState.managementPrs
158
+ })) {
159
+ continue;
160
+ }
161
+ const refreshedTask = refreshCarriedForwardTask(existingTask, workstreamsById);
162
+ if (!refreshedTask) {
163
+ continue;
164
+ }
165
+ desiredTasks.set(existingTask.id, mergeTask(refreshedTask, {
166
+ ...refreshedTask,
167
+ status: normalizeTaskStatusForNewCycle(refreshedTask.status)
168
+ }, input.now));
169
+ }
170
+ return [...desiredTasks.values()].sort(compareTasks);
171
+ }
172
+ function refreshCarriedForwardTask(task, workstreamsById) {
173
+ if (task.kind !== "respond_issue_comment" || task.workstreamId === undefined) {
174
+ return task;
175
+ }
176
+ const currentWorkstream = workstreamsById.get(task.workstreamId);
177
+ if (!currentWorkstream) {
178
+ return task;
179
+ }
180
+ if ((task.workflowPhase === "product_discovery" || task.workflowPhase === "product_specification") &&
181
+ currentWorkstream.phase !== "product_discovery" &&
182
+ currentWorkstream.phase !== "product_specification") {
183
+ return undefined;
184
+ }
185
+ if (task.workflowPhase === currentWorkstream.phase) {
186
+ return task;
187
+ }
188
+ return {
189
+ ...task,
190
+ workflowPhase: currentWorkstream.phase
191
+ };
192
+ }
193
+ function isPacketWorktreeInCurrentPlan(worktree, workstreamsById) {
194
+ if (!worktree.packet) {
195
+ return false;
196
+ }
197
+ const parentWorkstream = workstreamsById.get(worktree.packet.parentWorkstreamId);
198
+ if (!parentWorkstream?.packetPlan) {
199
+ return true;
200
+ }
201
+ return Boolean(parentWorkstream.packetPlan.packets[worktree.packet.packetId]);
202
+ }
203
+ function addTask(task, env, desiredTasks) {
204
+ const currentDesiredTask = desiredTasks.get(task.id);
205
+ if (currentDesiredTask) {
206
+ const mergedCycleTask = mergeSameCycleTask(currentDesiredTask, task);
207
+ desiredTasks.set(task.id, mergedCycleTask);
208
+ return mergedCycleTask;
209
+ }
210
+ const mergedTask = mergeTask(env.existingTasksById.get(task.id), task, env.input.now);
211
+ desiredTasks.set(task.id, mergedTask);
212
+ return mergedTask;
213
+ }
214
+ function mergeSameCycleTask(current, candidate) {
215
+ if (current.kind === "prepare_pr_for_merge" &&
216
+ candidate.kind === "prepare_pr_for_merge" &&
217
+ candidate.status === "pending" &&
218
+ current.status === "failed") {
219
+ return mergeSameCycleReplacement(current, candidate);
220
+ }
221
+ if (candidate.context.localRepoPath && !current.context.localRepoPath) {
222
+ return mergeSameCycleReplacement(current, candidate);
223
+ }
224
+ return current;
225
+ }
226
+ function mergeSameCycleReplacement(current, candidate) {
227
+ return {
228
+ ...candidate,
229
+ createdAt: current.createdAt,
230
+ ...(current.lastExecutionId ? { lastExecutionId: current.lastExecutionId } : {}),
231
+ ...(current.lastFailureCommentId ? { lastFailureCommentId: current.lastFailureCommentId } : {}),
232
+ context: {
233
+ ...candidate.context,
234
+ ...(current.context.recoveryNeeded !== undefined ? { recoveryNeeded: current.context.recoveryNeeded } : {})
235
+ }
236
+ };
237
+ }
238
+ function resolveWorkstreamContext(workstream, attention, currentIssuesByRepoAndNumber, currentPrsByRepoAndNumber, worktreesById, worktreesByRepoAndBranch, repoConfigs, repoSyncRecords, managementRepo, managementPrs) {
239
+ const issueNumber = workstream.linkedIssueNumber ?? (workstream.origin.type === "issue" ? workstream.origin.number : undefined);
240
+ const issue = issueNumber !== undefined
241
+ ? currentIssuesByRepoAndNumber.get(`${workstream.repoKey}:${issueNumber}`)
242
+ : undefined;
243
+ const activePullRequest = resolveActivePullRequest(workstream, currentPrsByRepoAndNumber);
244
+ const activeWorktree = (workstream.activeWorktreeId ? worktreesById.get(workstream.activeWorktreeId) : undefined) ??
245
+ (activePullRequest
246
+ ? worktreesByRepoAndBranch.get(`${activePullRequest.repoId}:${activePullRequest.headBranch}`)
247
+ : undefined);
248
+ const repoId = issue?.repoId ?? activePullRequest?.repoId ?? activeWorktree?.repoId;
249
+ const repoSlug = issue?.repoSlug ?? activePullRequest?.repoSlug ?? activeWorktree?.repoSlug;
250
+ const isManagementPullRequest = activePullRequest
251
+ ? classifyManagementPullRequest({
252
+ repoId: activePullRequest.repoId,
253
+ repoSlug: activePullRequest.repoSlug,
254
+ pullRequestNumber: activePullRequest.number,
255
+ headBranch: activePullRequest.headBranch,
256
+ managementPrs,
257
+ ...(managementRepo ? { managementRepo } : {})
258
+ }).kind === "management"
259
+ : false;
260
+ return {
261
+ workstream,
262
+ attention,
263
+ issue,
264
+ activePullRequest,
265
+ activeWorktree,
266
+ repoConfig: repoId ? repoConfigs.get(repoId) : undefined,
267
+ repoSyncRecord: repoId ? repoSyncRecords.get(repoId) : undefined,
268
+ isManagementPullRequest
269
+ };
270
+ }
271
+ function resolveActivePullRequest(workstream, currentPrsByRepoAndNumber) {
272
+ const candidateNumbers = [
273
+ ...(workstream.activePrNumber !== undefined ? [workstream.activePrNumber] : []),
274
+ ...workstream.linkedPrs.map((pr) => pr.number),
275
+ ...(workstream.origin.type === "pr" ? [workstream.origin.number] : [])
276
+ ];
277
+ for (const number of candidateNumbers) {
278
+ const pr = currentPrsByRepoAndNumber.get(`${workstream.repoKey}:${number}`);
279
+ if (pr) {
280
+ return pr;
281
+ }
282
+ }
283
+ return undefined;
284
+ }
285
+ function resolveCommentPullRequest(comment, context) {
286
+ if (context.activePullRequest && context.activePullRequest.number === comment.parentNumber) {
287
+ return context.activePullRequest;
288
+ }
289
+ return undefined;
290
+ }
291
+ function resolvePacketPullRequest(worktree, currentPrsByRepoAndNumber) {
292
+ if (worktree.prNumber !== undefined) {
293
+ const pr = currentPrsByRepoAndNumber.get(`${worktree.repoSlug}:${worktree.prNumber}`);
294
+ if (pr) {
295
+ return pr;
296
+ }
297
+ }
298
+ if (worktree.prUrl) {
299
+ const match = worktree.prUrl.match(/\/pull\/(\d+)$/);
300
+ if (match) {
301
+ const prNumber = Number(match[1]);
302
+ const pr = currentPrsByRepoAndNumber.get(`${worktree.repoSlug}:${prNumber}`);
303
+ if (pr) {
304
+ return pr;
305
+ }
306
+ }
307
+ }
308
+ return undefined;
309
+ }
310
+ function mergeTask(existing, desired, now) {
311
+ if (!existing) {
312
+ return desired;
313
+ }
314
+ const mergedBase = {
315
+ ...desired,
316
+ createdAt: existing.createdAt,
317
+ ...(existing.lastExecutionId ? { lastExecutionId: existing.lastExecutionId } : {}),
318
+ ...(existing.lastFailureCommentId ? { lastFailureCommentId: existing.lastFailureCommentId } : {})
319
+ };
320
+ if (tasksEquivalent(existing, mergedBase)) {
321
+ return existing;
322
+ }
323
+ const shouldResetStatus = isTerminalStatus(existing.status) &&
324
+ existing.status !== "failed" &&
325
+ (existing.reason !== desired.reason ||
326
+ existing.summary !== desired.summary ||
327
+ !contextsEquivalentForRerun(existing.context, desired.context));
328
+ let status = normalizeTaskStatusForNewCycle(existing.status);
329
+ let recoveryNeeded = existing.context?.recoveryNeeded;
330
+ if (shouldResetStatus) {
331
+ status = "pending";
332
+ }
333
+ if (desired.status === "failed") {
334
+ status = "failed";
335
+ recoveryNeeded = true;
336
+ }
337
+ return {
338
+ ...mergedBase,
339
+ status,
340
+ context: {
341
+ ...mergedBase.context,
342
+ recoveryNeeded
343
+ },
344
+ updatedAt: now
345
+ };
346
+ }
347
+ function tasksEquivalent(left, right) {
348
+ return (left.id === right.id &&
349
+ left.kind === right.kind &&
350
+ left.status === right.status &&
351
+ left.executionPermission === right.executionPermission &&
352
+ handoffPoliciesEquivalent(left.handoffPolicy, right.handoffPolicy) &&
353
+ left.repoId === right.repoId &&
354
+ left.repoName === right.repoName &&
355
+ left.repoSlug === right.repoSlug &&
356
+ left.sourceKind === right.sourceKind &&
357
+ left.sourceId === right.sourceId &&
358
+ left.sourceUrl === right.sourceUrl &&
359
+ left.parentSourceKind === right.parentSourceKind &&
360
+ left.parentSourceId === right.parentSourceId &&
361
+ left.relevant === right.relevant &&
362
+ left.reason === right.reason &&
363
+ left.summary === right.summary &&
364
+ left.workstreamId === right.workstreamId &&
365
+ left.workflowPhase === right.workflowPhase &&
366
+ left.createdAt === right.createdAt &&
367
+ left.sourceReactionTargetId === right.sourceReactionTargetId &&
368
+ contextsEquivalent(left.context, right.context));
369
+ }
370
+ function handoffPoliciesEquivalent(left, right) {
371
+ return (left.expectsPushableHandoff === right.expectsPushableHandoff &&
372
+ left.orchestratorOwnsPush === right.orchestratorOwnsPush &&
373
+ left.remotePublishOwner === right.remotePublishOwner);
374
+ }
375
+ function contextsEquivalent(left, right) {
376
+ return (contextsEquivalentForRerun(left, right) &&
377
+ arraysEqual(left.labels, right.labels));
378
+ }
379
+ function contextsEquivalentForRerun(left, right) {
380
+ return (left.repoIdentifier === right.repoIdentifier &&
381
+ left.localRepoPath === right.localRepoPath &&
382
+ left.productSpecDocumentPath === right.productSpecDocumentPath &&
383
+ left.issueUrl === right.issueUrl &&
384
+ left.issueTitle === right.issueTitle &&
385
+ left.issueBody === right.issueBody &&
386
+ left.pullRequestUrl === right.pullRequestUrl &&
387
+ left.commentUrl === right.commentUrl &&
388
+ left.relevantCommentText === right.relevantCommentText &&
389
+ left.branchName === right.branchName &&
390
+ left.planDocumentPath === right.planDocumentPath &&
391
+ left.logDocumentPath === right.logDocumentPath &&
392
+ left.instruction === right.instruction &&
393
+ preflightsEquivalent(left.preflight, right.preflight) &&
394
+ prepareForMergePreflightsEquivalent(left.prepareForMerge, right.prepareForMerge) &&
395
+ waveAssessmentCheckpointsEquivalent(left.waveAssessmentCheckpoint, right.waveAssessmentCheckpoint) &&
396
+ waveAssessmentResultsEquivalent(left.finalizedWaveAssessment, right.finalizedWaveAssessment));
397
+ }
398
+ function waveAssessmentCheckpointsEquivalent(left, right) {
399
+ if (!left && !right) {
400
+ return true;
401
+ }
402
+ if (!left || !right) {
403
+ return false;
404
+ }
405
+ return (left.id === right.id &&
406
+ left.sessionId === right.sessionId &&
407
+ waveAssessmentCheckpointIdentityMatches(left.identity, right.identity));
408
+ }
409
+ function waveAssessmentResultsEquivalent(left, right) {
410
+ if (!left && !right) {
411
+ return true;
412
+ }
413
+ if (!left || !right) {
414
+ return false;
415
+ }
416
+ return (left.id === right.id &&
417
+ left.sessionId === right.sessionId &&
418
+ left.decision === right.decision &&
419
+ left.summary === right.summary &&
420
+ left.status === right.status &&
421
+ left.postedCommentId === right.postedCommentId &&
422
+ left.createdAt === right.createdAt &&
423
+ left.finalizedAt === right.finalizedAt &&
424
+ left.updatedAt === right.updatedAt &&
425
+ waveAssessmentCheckpointsEquivalent(left.checkpoint, right.checkpoint) &&
426
+ assessmentFindingsEquivalent(left.findings, right.findings));
427
+ }
428
+ function assessmentFindingsEquivalent(left, right) {
429
+ if (left.length !== right.length) {
430
+ return false;
431
+ }
432
+ return left.every((leftFinding, index) => {
433
+ const rightFinding = right[index];
434
+ return Boolean(rightFinding &&
435
+ leftFinding.id === rightFinding.id &&
436
+ leftFinding.category === rightFinding.category &&
437
+ leftFinding.summary === rightFinding.summary &&
438
+ leftFinding.details === rightFinding.details &&
439
+ leftFinding.createdAt === rightFinding.createdAt &&
440
+ arraysEqual(leftFinding.affectedPlanNodeIds, rightFinding.affectedPlanNodeIds));
441
+ });
442
+ }
443
+ function preflightsEquivalent(left, right) {
444
+ if (!left && !right) {
445
+ return true;
446
+ }
447
+ if (!left || !right) {
448
+ return false;
449
+ }
450
+ return (left.workstreamId === right.workstreamId &&
451
+ left.repoKey === right.repoKey &&
452
+ left.parentIntegrationBranch === right.parentIntegrationBranch &&
453
+ left.parentPrNumber === right.parentPrNumber &&
454
+ left.defaultBranch === right.defaultBranch &&
455
+ left.currentWaveIndex === right.currentWaveIndex &&
456
+ left.nextWaveIndex === right.nextWaveIndex &&
457
+ arraysEqual(left.completedPacketIds, right.completedPacketIds) &&
458
+ arraysEqual(left.pendingFuturePacketIds, right.pendingFuturePacketIds));
459
+ }
460
+ function prepareForMergePreflightsEquivalent(left, right) {
461
+ if (!left && !right) {
462
+ return true;
463
+ }
464
+ if (!left || !right) {
465
+ return false;
466
+ }
467
+ return (left.prNumber === right.prNumber &&
468
+ left.headBranch === right.headBranch &&
469
+ left.headSha === right.headSha &&
470
+ left.baseBranch === right.baseBranch &&
471
+ left.prTitle === right.prTitle);
472
+ }
473
+ function arraysEqual(left, right) {
474
+ return left.length === right.length && left.every((value, index) => value === right[index]);
475
+ }
476
+ function compareIssueLikeEntities(left, right) {
477
+ const repoComparison = left.repoSlug.localeCompare(right.repoSlug);
478
+ if (repoComparison !== 0) {
479
+ return repoComparison;
480
+ }
481
+ if (left.number !== right.number) {
482
+ return left.number - right.number;
483
+ }
484
+ return left.id.localeCompare(right.id);
485
+ }
486
+ function compareComments(left, right) {
487
+ const repoComparison = left.repoSlug.localeCompare(right.repoSlug);
488
+ if (repoComparison !== 0) {
489
+ return repoComparison;
490
+ }
491
+ if (left.parentNumber !== right.parentNumber) {
492
+ return left.parentNumber - right.parentNumber;
493
+ }
494
+ return left.id.localeCompare(right.id);
495
+ }
496
+ function compareWorktrees(left, right) {
497
+ const repoComparison = left.repoSlug.localeCompare(right.repoSlug);
498
+ if (repoComparison !== 0) {
499
+ return repoComparison;
500
+ }
501
+ return left.id.localeCompare(right.id);
502
+ }
503
+ function compareWorkstreams(left, right) {
504
+ const repoComparison = left.repoKey.localeCompare(right.repoKey);
505
+ if (repoComparison !== 0) {
506
+ return repoComparison;
507
+ }
508
+ return left.id.localeCompare(right.id);
509
+ }
510
+ function compareTasks(left, right) {
511
+ const repoComparison = left.repoSlug.localeCompare(right.repoSlug);
512
+ if (repoComparison !== 0) {
513
+ return repoComparison;
514
+ }
515
+ const kindComparison = left.kind.localeCompare(right.kind);
516
+ if (kindComparison !== 0) {
517
+ return kindComparison;
518
+ }
519
+ return left.id.localeCompare(right.id);
520
+ }
521
+ function isTerminalStatus(status) {
522
+ return status === "completed" || status === "failed";
523
+ }
524
+ function normalizeTaskStatusForNewCycle(status) {
525
+ return status === "dispatched" ? "pending" : status;
526
+ }
527
+ function isUnresolvedStatus(status) {
528
+ return status === "pending" || status === "queued" || status === "dispatched";
529
+ }
530
+ function shouldCarryForwardUnresolvedTask(task, input) {
531
+ const isManagement = (pr) => {
532
+ if (!pr || !input.managementPrs)
533
+ return false;
534
+ return classifyManagementPullRequest({
535
+ repoId: pr.repoId,
536
+ repoSlug: pr.repoSlug,
537
+ pullRequestNumber: pr.number,
538
+ headBranch: pr.headBranch,
539
+ managementPrs: input.managementPrs,
540
+ ...(input.managementRepo ? { managementRepo: input.managementRepo } : {})
541
+ }).kind === "management";
542
+ };
543
+ if (task.workstreamId) {
544
+ const workstream = input.workstreamsById.get(task.workstreamId);
545
+ const isParentActive = workstream &&
546
+ workstream.status === "open" &&
547
+ workstream.phase !== "abandoned" &&
548
+ workstream.phase !== "resolved";
549
+ if (!isParentActive) {
550
+ return false;
551
+ }
552
+ }
553
+ switch (task.kind) {
554
+ case "product_discovery": {
555
+ const issue = input.currentIssuesById.get(task.sourceId);
556
+ const repoConfig = input.repoConfigs.get(task.repoId);
557
+ return Boolean(issue &&
558
+ issue.state === "open" &&
559
+ repoConfig &&
560
+ !issue.labels.includes(repoConfig.labels.agentProductApproved));
561
+ }
562
+ case "write_product_spec": {
563
+ if (task.context.localRepoPath && input.currentWorktreePaths.has(task.context.localRepoPath)) {
564
+ const worktree = input.worktreesByPath.get(task.context.localRepoPath);
565
+ return Boolean(worktree && worktree.status !== "closed" && !worktree.productSpecWrittenAt);
566
+ }
567
+ const issue = input.currentIssuesById.get(task.sourceId);
568
+ const repoConfig = input.repoConfigs.get(task.repoId);
569
+ return Boolean(issue &&
570
+ issue.state === "open" &&
571
+ repoConfig &&
572
+ issue.labels.includes(repoConfig.labels.agentProductApproved));
573
+ }
574
+ case "triage_issue": {
575
+ return false;
576
+ }
577
+ case "respond_issue_comment": {
578
+ const comment = input.currentCommentsById.get(task.sourceId);
579
+ const issue = task.parentSourceId ? input.currentIssuesById.get(task.parentSourceId) : undefined;
580
+ return Boolean(comment && comment.parentType === "issue" && issue?.state === "open");
581
+ }
582
+ case "respond_pr_comment": {
583
+ const comment = input.currentCommentsById.get(task.sourceId);
584
+ const pullRequest = task.parentSourceId ? input.currentPullRequestsById.get(task.parentSourceId) : undefined;
585
+ if (isManagement(pullRequest))
586
+ return false;
587
+ return Boolean(comment && comment.parentType === "pull_request" && pullRequest?.state === "open");
588
+ }
589
+ case "review_pr": {
590
+ const pullRequest = input.currentPullRequestsById.get(task.sourceId);
591
+ const repoConfig = input.repoConfigs.get(task.repoId);
592
+ if (isManagement(pullRequest))
593
+ return false;
594
+ return Boolean(pullRequest &&
595
+ pullRequest.state === "open" &&
596
+ repoConfig &&
597
+ pullRequest.labels.includes(repoConfig.labels.agentReview));
598
+ }
599
+ case "write_plan": {
600
+ if (task.context.localRepoPath && input.currentWorktreePaths.has(task.context.localRepoPath)) {
601
+ const worktree = input.worktreesByPath.get(task.context.localRepoPath);
602
+ return worktree?.status !== "closed";
603
+ }
604
+ const pullRequest = input.currentPullRequestsById.get(task.sourceId);
605
+ const repoConfig = input.repoConfigs.get(task.repoId);
606
+ return Boolean(pullRequest &&
607
+ pullRequest.state === "open" &&
608
+ repoConfig &&
609
+ (pullRequest.labels.includes(repoConfig.labels.agentInProgress) ||
610
+ pullRequest.labels.includes(repoConfig.labels.agentPlanApproved)));
611
+ }
612
+ case "implement_plan": {
613
+ if (task.context.localRepoPath && input.currentWorktreePaths.has(task.context.localRepoPath)) {
614
+ const worktree = input.worktreesByPath.get(task.context.localRepoPath);
615
+ if (worktree?.status === "closed") {
616
+ return false;
617
+ }
618
+ const pullRequest = worktree
619
+ ? input.currentPullRequestsByRepoAndBranch.get(`${worktree.repoId}:${worktree.branchName}`)
620
+ : undefined;
621
+ const repoConfig = input.repoConfigs.get(task.repoId);
622
+ return Boolean(pullRequest &&
623
+ pullRequest.state === "open" &&
624
+ repoConfig &&
625
+ pullRequest.labels.includes(repoConfig.labels.agentPlanApproved));
626
+ }
627
+ const pullRequest = input.currentPullRequestsById.get(task.sourceId);
628
+ const repoConfig = input.repoConfigs.get(task.repoId);
629
+ return Boolean(pullRequest &&
630
+ pullRequest.state === "open" &&
631
+ repoConfig &&
632
+ pullRequest.labels.includes(repoConfig.labels.agentPlanApproved));
633
+ }
634
+ case "plan_breakdown": {
635
+ const source = input.currentIssuesById.get(task.sourceId) ?? input.currentPullRequestsById.get(task.sourceId);
636
+ const repoConfig = input.repoConfigs.get(task.repoId);
637
+ const workstream = task.workstreamId ? input.workstreamsById.get(task.workstreamId) : undefined;
638
+ if (task.workstreamId !== undefined && workstream === undefined) {
639
+ return false; // workstream was deleted or not found — don't carry forward
640
+ }
641
+ return Boolean(source &&
642
+ source.state === "open" &&
643
+ repoConfig &&
644
+ !hasGeneratedPlanBreakdown(workstream) &&
645
+ source.labels.includes(repoConfig.labels.planBreakdownNeeded));
646
+ }
647
+ case "sync_parent_branch_preflight": {
648
+ const issue = input.currentIssuesById.get(task.sourceId);
649
+ if (issue?.state !== "open") {
650
+ return false;
651
+ }
652
+ const workstreamId = task.context.preflight?.workstreamId;
653
+ if (!workstreamId) {
654
+ return false;
655
+ }
656
+ const workstream = input.workstreamsById.get(workstreamId);
657
+ if (!workstream) {
658
+ return false;
659
+ }
660
+ const attention = input.attentionByWorkstreamId.get(workstreamId);
661
+ if (!attention?.reasons.includes("packet_wave_complete_sync_needed")) {
662
+ return false;
663
+ }
664
+ const repoConfig = input.repoConfigs.get(task.repoId);
665
+ const repoSyncRecord = input.repoSyncRecords.get(task.repoId);
666
+ const defaultBranch = repoConfig?.defaultBranch ?? repoSyncRecord?.defaultBranch ?? undefined;
667
+ const preflight = buildParentBranchSyncPreflight({ workstream, defaultBranch });
668
+ if (!preflight) {
669
+ return false;
670
+ }
671
+ // Check if the preflight has changed. If the preflight would be different, we should NOT carry forward
672
+ // the old task because it would be stale.
673
+ return preflightsEquivalent(task.context.preflight, preflight);
674
+ }
675
+ case "sync_parent_branch": {
676
+ const parentPr = input.currentPullRequestsById.get(task.sourceId);
677
+ const repoConfig = input.repoConfigs.get(task.repoId);
678
+ if (!parentPr || parentPr.state !== "open" || !repoConfig || !parentPr.labels.includes(repoConfig.labels.agentSyncParentBranch)) {
679
+ return false;
680
+ }
681
+ const workstreamId = task.context.preflight?.workstreamId;
682
+ if (!workstreamId) {
683
+ return false;
684
+ }
685
+ const workstream = input.workstreamsById.get(workstreamId);
686
+ if (!workstream) {
687
+ return false;
688
+ }
689
+ const issueNumber = workstream.linkedIssueNumber ?? (workstream.origin.type === "issue" ? workstream.origin.number : undefined);
690
+ if (issueNumber === undefined) {
691
+ return false;
692
+ }
693
+ const issue = input.currentIssuesByRepoAndNumber.get(`${workstream.repoKey}:${issueNumber}`);
694
+ if (issue?.state !== "open") {
695
+ return false;
696
+ }
697
+ const attention = input.attentionByWorkstreamId.get(workstreamId);
698
+ if (!attention?.reasons.includes("packet_wave_complete_sync_needed")) {
699
+ return false;
700
+ }
701
+ const repoSyncRecord = input.repoSyncRecords.get(task.repoId);
702
+ const defaultBranch = repoConfig.defaultBranch ?? repoSyncRecord?.defaultBranch ?? undefined;
703
+ const preflight = buildParentBranchSyncPreflight({ workstream, defaultBranch });
704
+ if (!preflight) {
705
+ return false;
706
+ }
707
+ return preflightsEquivalent(task.context.preflight, preflight);
708
+ }
709
+ case "wave_plan_assessment":
710
+ case "wave_plan_replanning": {
711
+ const parentPr = input.currentPullRequestsById.get(task.sourceId);
712
+ if (!parentPr || parentPr.state !== "open") {
713
+ return false;
714
+ }
715
+ const workstreamId = task.workstreamId ?? task.context.waveAssessmentCheckpoint?.identity.workstreamId;
716
+ if (!workstreamId) {
717
+ return false;
718
+ }
719
+ const workstream = input.workstreamsById.get(workstreamId);
720
+ if (!workstream) {
721
+ return false;
722
+ }
723
+ const issueNumber = workstream.linkedIssueNumber ?? (workstream.origin.type === "issue" ? workstream.origin.number : undefined);
724
+ if (issueNumber === undefined) {
725
+ return false;
726
+ }
727
+ const issue = input.currentIssuesByRepoAndNumber.get(`${workstream.repoKey}:${issueNumber}`);
728
+ if (issue?.state !== "open") {
729
+ return false;
730
+ }
731
+ const attention = input.attentionByWorkstreamId.get(workstreamId);
732
+ if (!attention?.reasons.includes("packet_wave_complete_sync_needed")) {
733
+ return false;
734
+ }
735
+ const repoConfig = input.repoConfigs.get(task.repoId);
736
+ const repoSyncRecord = input.repoSyncRecords.get(task.repoId);
737
+ return isCurrentWaveAssessmentTaskCheckpoint({
738
+ task,
739
+ parentPr,
740
+ workstream,
741
+ repoConfig,
742
+ repoSyncRecord,
743
+ previousTasks: input.previousTasks,
744
+ previousExecutions: input.previousExecutions
745
+ });
746
+ }
747
+ case "prepare_pr_for_merge": {
748
+ const pullRequest = input.currentPullRequestsById.get(task.sourceId);
749
+ const repoConfig = input.repoConfigs.get(task.repoId);
750
+ if (isManagement(pullRequest))
751
+ return false;
752
+ if (!pullRequest ||
753
+ pullRequest.state !== "open" ||
754
+ !repoConfig ||
755
+ !pullRequest.labels.includes(repoConfig.labels.prepareForMerge)) {
756
+ return false;
757
+ }
758
+ const currentPreflight = {
759
+ prNumber: pullRequest.number,
760
+ headBranch: pullRequest.headBranch,
761
+ headSha: pullRequest.headSha,
762
+ baseBranch: pullRequest.baseBranch,
763
+ prTitle: pullRequest.title
764
+ };
765
+ return prepareForMergePreflightsEquivalent(task.context.prepareForMerge, currentPreflight);
766
+ }
767
+ default:
768
+ return false;
769
+ }
770
+ }
771
+ function isCurrentWaveAssessmentTaskCheckpoint(input) {
772
+ const taskCheckpoint = input.task.context.waveAssessmentCheckpoint;
773
+ if (!taskCheckpoint || !input.parentPr.headSha) {
774
+ return false;
775
+ }
776
+ const defaultBranch = input.repoConfig?.defaultBranch ?? input.repoSyncRecord?.defaultBranch ?? undefined;
777
+ const currentPreflight = buildParentBranchSyncPreflight({
778
+ workstream: input.workstream,
779
+ defaultBranch
780
+ });
781
+ if (!currentPreflight || !currentPreflight.parentPrNumber) {
782
+ return false;
783
+ }
784
+ const currentIdentity = buildWaveAssessmentCheckpointIdentity(currentPreflight, {
785
+ parentHeadSha: input.parentPr.headSha,
786
+ planRevision: resolveCurrentPacketPlanRevision(input.workstream),
787
+ originalPlanningSessionId: resolveCurrentOriginalPlanningSessionId({
788
+ workstream: input.workstream,
789
+ previousTasks: input.previousTasks,
790
+ previousExecutions: input.previousExecutions
791
+ }),
792
+ planNodeIds: input.workstream.packetPlan
793
+ ? Object.keys(input.workstream.packetPlan.packets).sort()
794
+ : []
795
+ });
796
+ return waveAssessmentCheckpointIdentityMatches(taskCheckpoint.identity, currentIdentity);
797
+ }
798
+ function resolveCurrentPacketPlanRevision(workstream) {
799
+ return (workstream.packetPlan?.source?.generatedAt ??
800
+ workstream.packetPlan?.updatedAt ??
801
+ `${workstream.packetPlan?.currentWaveIndex ?? 0}:${Object.keys(workstream.packetPlan?.packets ?? {}).sort().join(",")}`);
802
+ }
803
+ function resolveCurrentOriginalPlanningSessionId(input) {
804
+ const previousTasksById = new Map(input.previousTasks.map((task) => [task.id, task]));
805
+ const planBreakdownExecutions = input.previousExecutions
806
+ .filter((execution) => execution.taskKind === "plan_breakdown" && execution.taskStatus === "completed")
807
+ .filter((execution) => previousTasksById.get(execution.taskId)?.workstreamId === input.workstream.id)
808
+ .sort((left, right) => right.endedAt.localeCompare(left.endedAt));
809
+ return planBreakdownExecutions[0]?.result?.planBreakdownSessionId ?? "unknown";
810
+ }