@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,169 @@
1
+ import { classifyManagementPullRequest } from "../../../github/pr-classification.js";
2
+ import { workflowRegistry } from "../../../workflows/registry.js";
3
+ import { isRetryLinkCommand } from "../../retry.js";
4
+ import { buildCommentReason, buildTaskId, isAgentAuthoredComment } from "../builders.js";
5
+ export const commentResponseHandler = {
6
+ name: "comment_response",
7
+ derive(context, env) {
8
+ return deriveCommentTasks(context, env);
9
+ }
10
+ };
11
+ export function deriveManagementPullRequestCommentTasks(context, env, _sink) {
12
+ return deriveCommentTasks(context, env).filter((task) => task.kind !== "respond_pr_comment");
13
+ }
14
+ function deriveCommentTasks(context, env) {
15
+ const attention = context.attention;
16
+ if (!attention?.commentIds?.length) {
17
+ return [];
18
+ }
19
+ const tasks = [];
20
+ for (const commentId of [...attention.commentIds].sort((left, right) => left.localeCompare(right))) {
21
+ const comment = env.currentCommentsById.get(commentId);
22
+ if (!comment || isAgentAuthoredComment(comment)) {
23
+ continue;
24
+ }
25
+ if (attention.reasons.includes("retry_requested") && isRetryLinkCommand(comment.body)) {
26
+ continue;
27
+ }
28
+ const previousComment = env.previousComments.get(comment.id);
29
+ const isNewOrEdited = !previousComment ||
30
+ previousComment.updatedAt !== comment.updatedAt ||
31
+ previousComment.body !== comment.body;
32
+ if (!isNewOrEdited) {
33
+ continue;
34
+ }
35
+ if (comment.parentType === "issue") {
36
+ const issue = context.issue;
37
+ if (!issue || issue.number !== comment.parentNumber) {
38
+ continue;
39
+ }
40
+ if (!env.previousIssues.has(issue.id) && context.attention?.reasons.includes("new_issue")) {
41
+ continue;
42
+ }
43
+ tasks.push(createCommentTask("respond_issue_comment", comment, issue, {
44
+ localRepoPath: context.repoSyncRecord?.localPath
45
+ }, {
46
+ reason: buildCommentReason(comment, previousComment),
47
+ summary: `Respond to issue comment on #${issue.number}: ${issue.title}`,
48
+ contextBuilder: env.input.contextBuilder,
49
+ now: env.input.now
50
+ }, undefined, context.workstream.id, context.workstream.phase));
51
+ continue;
52
+ }
53
+ const prTask = derivePullRequestCommentTask(comment, previousComment, context, env);
54
+ if (prTask) {
55
+ tasks.push(prTask);
56
+ }
57
+ }
58
+ return tasks;
59
+ }
60
+ function derivePullRequestCommentTask(comment, previousComment, context, env) {
61
+ let parentPullRequest = resolveCommentPullRequest(comment, context);
62
+ let isPacketPR = false;
63
+ let worktreeContext = {
64
+ localRepoPath: context.activeWorktree?.path ?? context.repoSyncRecord?.localPath,
65
+ branchName: context.activeWorktree?.branchName,
66
+ planDocumentPath: context.activeWorktree?.planDocumentPath,
67
+ productSpecDocumentPath: context.activeWorktree?.productSpecDocumentPath,
68
+ logDocumentPath: context.activeWorktree?.logDocumentPath,
69
+ packetId: context.activeWorktree?.packet?.packetId
70
+ };
71
+ if (!parentPullRequest) {
72
+ const packetProjection = env.packetProjectionByRepoAndPrNumber.get(`${comment.repoId}:${comment.parentNumber}`);
73
+ if (packetProjection &&
74
+ packetProjection.parentWorkstream.id === context.workstream.id &&
75
+ packetProjection.pullRequest.number === comment.parentNumber) {
76
+ parentPullRequest = packetProjection.pullRequest;
77
+ isPacketPR = true;
78
+ worktreeContext = {
79
+ localRepoPath: packetProjection.packetWorktree?.path ?? context.repoSyncRecord?.localPath,
80
+ branchName: packetProjection.packetWorktree?.branchName,
81
+ planDocumentPath: packetProjection.packetWorktree?.planDocumentPath,
82
+ productSpecDocumentPath: packetProjection.packetWorktree?.productSpecDocumentPath,
83
+ logDocumentPath: packetProjection.packetWorktree?.logDocumentPath,
84
+ packetId: packetProjection.packetWorktree?.packet?.packetId ?? packetProjection.packet.id
85
+ };
86
+ }
87
+ }
88
+ if (!parentPullRequest) {
89
+ return undefined;
90
+ }
91
+ const isManagementPR = classifyManagementPullRequest({
92
+ repoId: parentPullRequest.repoId,
93
+ repoSlug: parentPullRequest.repoSlug,
94
+ pullRequestNumber: parentPullRequest.number,
95
+ headBranch: parentPullRequest.headBranch,
96
+ managementPrs: env.input.previousState.managementPrs,
97
+ ...(env.input.managementRepo ? { managementRepo: env.input.managementRepo } : {})
98
+ }).kind === "management";
99
+ if (isManagementPR) {
100
+ return undefined;
101
+ }
102
+ if (!isPacketPR &&
103
+ !env.previousPullRequests.has(parentPullRequest.id) &&
104
+ parentPullRequest.number === comment.parentNumber) {
105
+ return undefined;
106
+ }
107
+ return createCommentTask("respond_pr_comment", comment, parentPullRequest, worktreeContext, {
108
+ reason: buildCommentReason(comment, previousComment),
109
+ summary: `Respond to PR comment on #${parentPullRequest.number}: ${parentPullRequest.title}`,
110
+ contextBuilder: env.input.contextBuilder,
111
+ now: env.input.now
112
+ }, comment.pullRequestReviewLocation, context.workstream.id, context.workstream.phase);
113
+ }
114
+ function resolveCommentPullRequest(comment, context) {
115
+ if (context.activePullRequest && context.activePullRequest.number === comment.parentNumber) {
116
+ return context.activePullRequest;
117
+ }
118
+ return undefined;
119
+ }
120
+ function createCommentTask(kind, comment, parent, worktreeContext, input, pullRequestReviewLocation, workstreamId, workflowPhase) {
121
+ const executionPolicy = workflowRegistry.resolveTaskExecutionPolicy(kind);
122
+ return {
123
+ id: buildTaskId(kind, comment.id),
124
+ kind,
125
+ status: "pending",
126
+ executionPermission: executionPolicy.executionPermission,
127
+ handoffPolicy: executionPolicy.handoffPolicy,
128
+ repoId: comment.repoId,
129
+ repoName: comment.repoName,
130
+ repoSlug: comment.repoSlug,
131
+ sourceKind: "comment",
132
+ sourceId: comment.id,
133
+ sourceUrl: comment.url,
134
+ sourceReactionTargetId: comment.reactionTargetId,
135
+ parentSourceKind: comment.parentType === "issue" ? "issue" : "pull_request",
136
+ parentSourceId: parent.id,
137
+ relevant: true,
138
+ reason: input.reason,
139
+ summary: input.summary,
140
+ ...(workstreamId ? { workstreamId } : {}),
141
+ ...(workflowPhase ? { workflowPhase } : {}),
142
+ context: input.contextBuilder.build({
143
+ taskKind: kind,
144
+ ...(workflowPhase ? { workflowPhase } : {}),
145
+ repoIdentifier: comment.repoSlug,
146
+ sourceKind: "comment",
147
+ sourceUrl: comment.url,
148
+ labels: parent.labels,
149
+ parentUrl: parent.url,
150
+ ...(parent.kind === "issue"
151
+ ? {
152
+ issueTitle: parent.title,
153
+ issueBody: parent.body
154
+ }
155
+ : { headSha: parent.headSha }),
156
+ commentUrl: comment.url,
157
+ commentText: comment.body,
158
+ ...(worktreeContext.localRepoPath ? { localRepoPath: worktreeContext.localRepoPath } : {}),
159
+ ...(worktreeContext.branchName ? { branchName: worktreeContext.branchName } : {}),
160
+ ...(worktreeContext.planDocumentPath ? { planDocumentPath: worktreeContext.planDocumentPath } : {}),
161
+ ...(worktreeContext.productSpecDocumentPath ? { productSpecDocumentPath: worktreeContext.productSpecDocumentPath } : {}),
162
+ ...(worktreeContext.logDocumentPath ? { logDocumentPath: worktreeContext.logDocumentPath } : {}),
163
+ ...(worktreeContext.packetId ? { packetId: worktreeContext.packetId } : {}),
164
+ ...(pullRequestReviewLocation ? { pullRequestReviewLocation } : {})
165
+ }),
166
+ createdAt: input.now,
167
+ updatedAt: input.now
168
+ };
169
+ }
@@ -0,0 +1,103 @@
1
+ import { buildTaskRecord, createPullRequestTask } from "../builders.js";
2
+ export const implementPlanHandler = {
3
+ name: "implement_plan",
4
+ derive(context, env) {
5
+ const task = deriveImplementTask(context, env);
6
+ return task ? [task] : [];
7
+ }
8
+ };
9
+ export function derivePacketImplementTask(context, env) {
10
+ const { workstream, issue, activePullRequest, activeWorktree, packetPullRequest } = context;
11
+ if (workstream.status !== "open" ||
12
+ workstream.phase === "abandoned" ||
13
+ workstream.phase === "resolved") {
14
+ return undefined;
15
+ }
16
+ if (packetPullRequest && packetPullRequest.state !== "open") {
17
+ return undefined;
18
+ }
19
+ if (!activeWorktree?.packet || activeWorktree.status !== "implementation_ready") {
20
+ return undefined;
21
+ }
22
+ const parentSource = issue ?? activePullRequest;
23
+ const source = packetPullRequest ?? parentSource;
24
+ if (!source) {
25
+ return undefined;
26
+ }
27
+ return createWorktreeImplementTask(source, activeWorktree, {
28
+ contextBuilder: env.input.contextBuilder,
29
+ now: env.input.now
30
+ }, parentSource, workstream.id);
31
+ }
32
+ function deriveImplementTask(context, env) {
33
+ const { workstream, attention, activePullRequest, activeWorktree, repoConfig, repoSyncRecord } = context;
34
+ if (!attention || !activePullRequest || !repoConfig) {
35
+ return undefined;
36
+ }
37
+ if (activeWorktree?.status === "implementation_done" || workstream.phase === "implementation_done") {
38
+ return undefined;
39
+ }
40
+ if (activeWorktree &&
41
+ activeWorktree.status !== "implementation_ready" &&
42
+ activeWorktree.status !== "awaiting_plan_approval" &&
43
+ activeWorktree.status !== "implementing") {
44
+ return undefined;
45
+ }
46
+ if (workstream.phase === "abandoned" || workstream.phase === "resolved") {
47
+ return undefined;
48
+ }
49
+ if (activePullRequest.state !== "open") {
50
+ return undefined;
51
+ }
52
+ if (attention.reasons.includes("pr_review_requested") ||
53
+ (!attention.reasons.includes("plan_approved") &&
54
+ workstream.phase !== "implementation_ready" &&
55
+ workstream.phase !== "implementing" &&
56
+ workstream.phase !== "implementation_feedback")) {
57
+ return undefined;
58
+ }
59
+ return createPullRequestTask("implement_plan", activePullRequest, repoSyncRecord, {
60
+ reason: `Pull request carries the "${repoConfig.labels.agentPlanApproved}" label and is ready for implementation.`,
61
+ summary: `Implement approved plan for PR #${activePullRequest.number}: ${activePullRequest.title}`,
62
+ contextBuilder: env.input.contextBuilder,
63
+ now: env.input.now
64
+ }, activeWorktree, workstream.id);
65
+ }
66
+ function createWorktreeImplementTask(source, worktree, input, parentSource, workstreamId) {
67
+ const reason = worktree.packet
68
+ ? `Implementation packet "${worktree.packet.packetId}" is approved in wave ${worktree.packet.waveIndex} and ready for implementation.`
69
+ : `Implementation ready for ${source.kind === "issue" ? "issue" : "PR"} #${source.number}: ${source.title}`;
70
+ const summary = worktree.packet
71
+ ? `Implement approved packet "${worktree.packet.packetId}" in wave ${worktree.packet.waveIndex}`
72
+ : `Implement approved plan for ${source.kind === "issue" ? "issue" : "PR"} #${source.number}: ${source.title}`;
73
+ return buildTaskRecord(`task:implement_plan:worktree:${worktree.id}`, "implement_plan", source, undefined, {
74
+ reason,
75
+ summary,
76
+ contextBuilder: input.contextBuilder,
77
+ now: input.now
78
+ }, input.contextBuilder.build({
79
+ taskKind: "implement_plan",
80
+ repoIdentifier: source.repoSlug,
81
+ sourceKind: worktree.prUrl ? "pull_request" : source.kind,
82
+ sourceUrl: worktree.prUrl || source.url,
83
+ ...(worktree.prUrl ? { parentUrl: parentSource?.url ?? source.url } : {}),
84
+ labels: source.labels,
85
+ localRepoPath: worktree.path,
86
+ branchName: worktree.branchName,
87
+ ...(worktree.packet?.packetId ? { packetId: worktree.packet.packetId } : {}),
88
+ planDocumentPath: worktree.planDocumentPath,
89
+ ...(worktree.productSpecDocumentPath ? { productSpecDocumentPath: worktree.productSpecDocumentPath } : {}),
90
+ logDocumentPath: worktree.logDocumentPath,
91
+ ...(parentSource && parentSource.kind === "issue"
92
+ ? {
93
+ issueTitle: parentSource.title,
94
+ issueBody: parentSource.body
95
+ }
96
+ : source.kind === "issue"
97
+ ? {
98
+ issueTitle: source.title,
99
+ issueBody: source.body
100
+ }
101
+ : {})
102
+ }), workstreamId);
103
+ }
@@ -0,0 +1,54 @@
1
+ import { commentResponseHandler } from "./comment-response.js";
2
+ import { implementPlanHandler } from "./implement-plan.js";
3
+ import { packetPullRequestControlHandler } from "./packet-pr-control.js";
4
+ import { packetWorktreeHandler } from "./packet-worktree.js";
5
+ import { preparePullRequestForMergeHandler } from "./prepare-for-merge.js";
6
+ import { productDiscoveryHandler } from "./product-discovery.js";
7
+ import { retryFailedTaskHandler } from "./retry-failed-task.js";
8
+ import { reviewPullRequestHandler } from "./review-pr.js";
9
+ import { syncParentBranchHandler, syncParentBranchPreflightHandler } from "./sync-parent-branch.js";
10
+ import { hasOpenWaveAssessmentParentPullRequest, isWaveAssessmentCheckpointAllowed, waveAssessmentHandler } from "./wave-assessment.js";
11
+ import { writePlanHandler } from "./write-plan.js";
12
+ import { writeProductSpecHandler } from "./write-product-spec.js";
13
+ export { deriveManagementPullRequestCommentTasks } from "./comment-response.js";
14
+ export { isActiveParentWorkstream } from "./shared.js";
15
+ export { planBreakdownHandler } from "./plan-breakdown.js";
16
+ export { waveAssessmentHandler } from "./wave-assessment.js";
17
+ const gatedSyncParentBranchPreflightHandler = {
18
+ name: syncParentBranchPreflightHandler.name,
19
+ derive(context, env, sink) {
20
+ if (hasOpenWaveAssessmentParentPullRequest(context, env) &&
21
+ !isWaveAssessmentCheckpointAllowed(context, env)) {
22
+ return [];
23
+ }
24
+ return syncParentBranchPreflightHandler.derive(context, env, sink);
25
+ }
26
+ };
27
+ const gatedSyncParentBranchHandler = {
28
+ name: syncParentBranchHandler.name,
29
+ derive(context, env, sink) {
30
+ if (!isWaveAssessmentCheckpointAllowed(context, env)) {
31
+ return [];
32
+ }
33
+ return syncParentBranchHandler.derive(context, env, sink);
34
+ }
35
+ };
36
+ export const parentWorkstreamHandlers = [
37
+ waveAssessmentHandler,
38
+ gatedSyncParentBranchPreflightHandler,
39
+ gatedSyncParentBranchHandler,
40
+ reviewPullRequestHandler,
41
+ preparePullRequestForMergeHandler,
42
+ packetPullRequestControlHandler,
43
+ writeProductSpecHandler,
44
+ writePlanHandler
45
+ ];
46
+ export const activeParentWorkstreamHandlers = [
47
+ packetWorktreeHandler
48
+ ];
49
+ export const finalWorkstreamHandlers = [
50
+ implementPlanHandler,
51
+ productDiscoveryHandler,
52
+ retryFailedTaskHandler,
53
+ commentResponseHandler
54
+ ];
@@ -0,0 +1,92 @@
1
+ import { classifyManagementPullRequest } from "../../../github/pr-classification.js";
2
+ import { buildTaskId, createPullRequestTask } from "../builders.js";
3
+ import { createPrepareForMergeTask } from "./prepare-for-merge.js";
4
+ import { isActiveParentWorkstream } from "./shared.js";
5
+ export const packetPullRequestControlHandler = {
6
+ name: "packet_pr_control",
7
+ derive(context, env, sink) {
8
+ return derivePacketPullRequestTasks(context, env, sink);
9
+ }
10
+ };
11
+ function derivePacketPullRequestTasks(context, env, sink) {
12
+ if (!isActiveParentWorkstream(context.workstream)) {
13
+ return [];
14
+ }
15
+ const tasks = [];
16
+ const projectionsForWorkstream = env.packetPullRequestProjections.filter((projection) => projection.parentWorkstream.id === context.workstream.id);
17
+ for (const projection of projectionsForWorkstream) {
18
+ const isManagementPR = classifyManagementPullRequest({
19
+ repoId: projection.pullRequest.repoId,
20
+ repoSlug: projection.pullRequest.repoSlug,
21
+ pullRequestNumber: projection.pullRequest.number,
22
+ headBranch: projection.pullRequest.headBranch,
23
+ managementPrs: env.input.previousState.managementPrs,
24
+ ...(env.input.managementRepo ? { managementRepo: env.input.managementRepo } : {})
25
+ }).kind === "management";
26
+ if (isManagementPR) {
27
+ continue;
28
+ }
29
+ const repoConfig = env.repoConfigs.get(projection.pullRequest.repoId);
30
+ if (!repoConfig || projection.pullRequest.state !== "open") {
31
+ continue;
32
+ }
33
+ if (projection.pullRequest.labels.includes(repoConfig.labels.agentReview)) {
34
+ const packetReviewTask = derivePacketPullRequestReviewTask(context, projection, repoConfig, env, sink);
35
+ if (packetReviewTask) {
36
+ tasks.push(packetReviewTask);
37
+ }
38
+ }
39
+ if (projection.pullRequest.labels.includes(repoConfig.labels.prepareForMerge)) {
40
+ const packetPrepareForMergeTask = createPrepareForMergeTask({
41
+ pullRequest: projection.pullRequest,
42
+ repoSyncRecord: env.repoSyncRecords.get(projection.pullRequest.repoId),
43
+ repoConfig,
44
+ contextBuilder: env.input.contextBuilder,
45
+ now: env.input.now,
46
+ workstreamId: context.workstream.id,
47
+ worktree: projection.packetWorktree,
48
+ packetProjection: projection
49
+ });
50
+ tasks.push(packetPrepareForMergeTask);
51
+ }
52
+ }
53
+ return tasks;
54
+ }
55
+ function derivePacketPullRequestReviewTask(context, projection, repoConfig, env, sink) {
56
+ const handledHeadSha = findHandledReviewHeadShaForPullRequest({
57
+ pullRequest: projection.pullRequest,
58
+ previousPullRequests: env.input.previousState.prs,
59
+ currentPullRequests: env.currentPullRequests,
60
+ tasks: env.input.previousState.tasks
61
+ });
62
+ if (handledHeadSha && handledHeadSha === projection.pullRequest.headSha) {
63
+ const taskId = buildTaskId("review_pr", projection.pullRequest.id);
64
+ sink.preserveExisting(taskId);
65
+ return undefined;
66
+ }
67
+ return createPullRequestTask("review_pr", projection.pullRequest, env.repoSyncRecords.get(projection.pullRequest.repoId), {
68
+ reason: `Packet pull request has the "${repoConfig.labels.agentReview}" label and code changed (head: ${projection.pullRequest.headSha || "unknown"}).`,
69
+ summary: `Review packet PR #${projection.pullRequest.number}: ${projection.pullRequest.title}`,
70
+ contextBuilder: env.input.contextBuilder,
71
+ now: env.input.now
72
+ }, projection.packetWorktree, context.workstream.id);
73
+ }
74
+ function findHandledReviewHeadShaForPullRequest(input) {
75
+ const latestCompletedTask = input.tasks
76
+ .filter((task) => task.kind === "review_pr" &&
77
+ task.status === "completed" &&
78
+ task.sourceId === input.pullRequest.id)
79
+ .sort((a, b) => b.updatedAt.localeCompare(a.updatedAt))[0];
80
+ if (!latestCompletedTask) {
81
+ return undefined;
82
+ }
83
+ const previousPr = input.previousPullRequests.find((pr) => pr.id === input.pullRequest.id);
84
+ if (previousPr) {
85
+ return previousPr.headSha;
86
+ }
87
+ const currentPr = input.currentPullRequests.find((pr) => pr.id === input.pullRequest.id);
88
+ if (currentPr) {
89
+ return currentPr.headSha;
90
+ }
91
+ return undefined;
92
+ }
@@ -0,0 +1,29 @@
1
+ import { derivePacketImplementTask } from "./implement-plan.js";
2
+ import { resolvePacketPullRequest } from "./shared.js";
3
+ import { deriveWritePlanTask } from "./write-plan.js";
4
+ export const packetWorktreeHandler = {
5
+ name: "packet_worktree_tasks",
6
+ derive(context, env) {
7
+ return derivePacketWorktreeTasks(context, env);
8
+ }
9
+ };
10
+ function derivePacketWorktreeTasks(context, env) {
11
+ const tasks = [];
12
+ for (const packetWorktree of env.packetWorktreesByWorkstreamId.get(context.workstream.id) ?? []) {
13
+ const packetPullRequest = resolvePacketPullRequest(packetWorktree, env.currentPrsByRepoAndNumber);
14
+ const packetContext = {
15
+ ...context,
16
+ activeWorktree: packetWorktree,
17
+ packetPullRequest
18
+ };
19
+ const writePlanTask = deriveWritePlanTask(packetContext, env);
20
+ if (writePlanTask) {
21
+ tasks.push(writePlanTask);
22
+ }
23
+ const implementTask = derivePacketImplementTask(packetContext, env);
24
+ if (implementTask) {
25
+ tasks.push(implementTask);
26
+ }
27
+ }
28
+ return tasks;
29
+ }
@@ -0,0 +1,45 @@
1
+ import { hasGeneratedPlanBreakdown } from "../../../workstreams/packet-plan.js";
2
+ import { buildTaskId, buildTaskRecord } from "../builders.js";
3
+ export const planBreakdownHandler = {
4
+ name: "plan_breakdown",
5
+ derive(context, env) {
6
+ const task = derivePlanBreakdownTask(context, env);
7
+ return task ? [task] : [];
8
+ },
9
+ shouldBlockRemainingHandlers(task) {
10
+ return task.status !== "completed" && task.status !== "failed";
11
+ }
12
+ };
13
+ function derivePlanBreakdownTask(context, env) {
14
+ const { workstream, attention, issue, activePullRequest, repoConfig, repoSyncRecord } = context;
15
+ if (hasGeneratedPlanBreakdown(workstream)) {
16
+ return undefined;
17
+ }
18
+ if (!attention?.reasons.includes("plan_breakdown_requested") || !repoConfig) {
19
+ return undefined;
20
+ }
21
+ const source = (issue?.labels.includes(repoConfig.labels.planBreakdownNeeded) ? issue : undefined)
22
+ ?? (activePullRequest?.labels.includes(repoConfig.labels.planBreakdownNeeded) ? activePullRequest : undefined);
23
+ if (!source) {
24
+ return undefined;
25
+ }
26
+ return buildTaskRecord(buildTaskId("plan_breakdown", source.id), "plan_breakdown", source, repoSyncRecord, {
27
+ reason: `The "${repoConfig.labels.planBreakdownNeeded}" label is present, requesting a technical breakdown before implementation.`,
28
+ summary: `Break down implementation for ${source.kind.replace("_", " ")} #${source.number}: ${source.title}`,
29
+ contextBuilder: env.input.contextBuilder,
30
+ now: env.input.now
31
+ }, env.input.contextBuilder.build({
32
+ taskKind: "plan_breakdown",
33
+ repoIdentifier: source.repoSlug,
34
+ sourceKind: source.kind,
35
+ sourceUrl: source.url,
36
+ labels: source.labels,
37
+ ...(source.kind === "issue"
38
+ ? {
39
+ issueTitle: source.title,
40
+ issueBody: source.body
41
+ }
42
+ : {}),
43
+ ...(repoSyncRecord?.localPath ? { localRepoPath: repoSyncRecord.localPath } : {})
44
+ }), workstream.id);
45
+ }
@@ -0,0 +1,91 @@
1
+ import { buildTaskId, buildTaskRecord } from "../builders.js";
2
+ export const preparePullRequestForMergeHandler = {
3
+ name: "prepare_pr_for_merge",
4
+ derive(context, env) {
5
+ const task = derivePrepareForMergeTask(context, env);
6
+ return task ? [task] : [];
7
+ }
8
+ };
9
+ function derivePrepareForMergeTask(context, env) {
10
+ const { workstream, attention, activePullRequest, activeWorktree, repoConfig, repoSyncRecord } = context;
11
+ if (!attention ||
12
+ !activePullRequest ||
13
+ !repoConfig ||
14
+ activePullRequest.state !== "open" ||
15
+ !attention.reasons.includes("prepare_for_merge_requested") ||
16
+ !activePullRequest.labels.includes(repoConfig.labels.prepareForMerge)) {
17
+ return undefined;
18
+ }
19
+ const worktree = activeWorktree?.branchName === activePullRequest.headBranch ? activeWorktree : undefined;
20
+ return createPrepareForMergeTask({
21
+ pullRequest: activePullRequest,
22
+ repoSyncRecord,
23
+ repoConfig,
24
+ contextBuilder: env.input.contextBuilder,
25
+ now: env.input.now,
26
+ workstreamId: workstream.id,
27
+ worktree
28
+ });
29
+ }
30
+ export function createPrepareForMergeTask(input) {
31
+ const { pullRequest, repoSyncRecord, repoConfig, contextBuilder, now, workstreamId, worktree, packetProjection } = input;
32
+ const preflight = {
33
+ prNumber: pullRequest.number,
34
+ headBranch: pullRequest.headBranch,
35
+ headSha: pullRequest.headSha,
36
+ baseBranch: pullRequest.baseBranch,
37
+ prTitle: pullRequest.title,
38
+ // Capture durable worktree paths at derivation time for audit/log use after clone cleanup
39
+ persistentWorktreePath: worktree?.path,
40
+ persistentLogDocumentPath: worktree?.logDocumentPath,
41
+ persistentPlanDocumentPath: worktree?.planDocumentPath
42
+ };
43
+ const waveIndex = packetProjection?.packetWorktree?.packet?.waveIndex ??
44
+ findWaveIndexForPacket(packetProjection?.packet.id, packetProjection?.parentWorkstream.packetPlan);
45
+ const reason = packetProjection
46
+ ? `Packet pull request #${pullRequest.number} (packet: ${packetProjection.packet.id}, wave: ${waveIndex ?? "unknown"}) has the "${repoConfig.labels.prepareForMerge}" label, requesting merge preparation into "${pullRequest.baseBranch}".`
47
+ : `Pull request has the "${repoConfig.labels.prepareForMerge}" label, requesting merge preparation into "${pullRequest.baseBranch}".`;
48
+ const summary = packetProjection
49
+ ? `Prepare packet PR #${pullRequest.number} for merge into ${pullRequest.baseBranch} (Workstream: ${workstreamId})`
50
+ : `Prepare PR #${pullRequest.number} for merge into ${pullRequest.baseBranch}: ${pullRequest.title}`;
51
+ return buildTaskRecord(buildTaskId("prepare_pr_for_merge", pullRequest.id), "prepare_pr_for_merge", pullRequest, repoSyncRecord, {
52
+ reason,
53
+ summary,
54
+ contextBuilder,
55
+ now
56
+ }, contextBuilder.build({
57
+ taskKind: "prepare_pr_for_merge",
58
+ repoIdentifier: pullRequest.repoSlug,
59
+ sourceKind: "pull_request",
60
+ sourceUrl: pullRequest.url,
61
+ labels: pullRequest.labels,
62
+ prepareForMerge: preflight,
63
+ ...(worktree?.path
64
+ ? {
65
+ localRepoPath: worktree.path,
66
+ branchName: worktree.branchName,
67
+ planDocumentPath: worktree.planDocumentPath,
68
+ logDocumentPath: worktree.logDocumentPath
69
+ }
70
+ : repoSyncRecord?.localPath
71
+ ? { localRepoPath: repoSyncRecord.localPath }
72
+ : {}),
73
+ ...(worktree?.packet?.packetId
74
+ ? { packetId: worktree.packet.packetId }
75
+ : packetProjection?.packet.id
76
+ ? { packetId: packetProjection.packet.id }
77
+ : {})
78
+ }), workstreamId);
79
+ }
80
+ function findWaveIndexForPacket(packetId, packetPlan) {
81
+ if (!packetId || !packetPlan) {
82
+ return undefined;
83
+ }
84
+ for (let i = 0; i < packetPlan.waves.length; i++) {
85
+ const wave = packetPlan.waves[i];
86
+ if (wave && wave.includes(packetId)) {
87
+ return i;
88
+ }
89
+ }
90
+ return undefined;
91
+ }
@@ -0,0 +1,36 @@
1
+ import { createIssueTask } from "../builders.js";
2
+ export const productDiscoveryHandler = {
3
+ name: "product_discovery",
4
+ derive(context, env) {
5
+ const task = deriveProductDiscoveryTask(context, env);
6
+ return task ? [task] : [];
7
+ }
8
+ };
9
+ function deriveProductDiscoveryTask(context, env) {
10
+ const { workstream, attention, issue, repoConfig, repoSyncRecord } = context;
11
+ if (!issue || workstream.origin.type !== "issue") {
12
+ return undefined;
13
+ }
14
+ if (workstream.phase !== "product_discovery") {
15
+ return undefined;
16
+ }
17
+ const hasExistingProductDiscoveryTask = env.existingTasks.some((task) => task.kind === "product_discovery" && task.workstreamId === workstream.id);
18
+ const previousWorkstream = env.input.previousState.workstreams.find((record) => record.id === workstream.id);
19
+ const issueCommentsUnavailable = repoSyncRecord?.githubScan?.failures.some((failure) => failure.step === "issue_comments" && failure.scope === `issue#${issue.number}`) ?? false;
20
+ const isNewIssueProductDiscovery = Boolean(attention?.reasons.includes("new_issue") && !env.previousIssues.has(issue.id));
21
+ const isMigratedProductDiscovery = Boolean(!hasExistingProductDiscoveryTask &&
22
+ !issueCommentsUnavailable &&
23
+ (previousWorkstream?.phase === "issue_triage" || previousWorkstream?.phase === "issue_waiting"));
24
+ if (!isNewIssueProductDiscovery && !isMigratedProductDiscovery) {
25
+ return undefined;
26
+ }
27
+ if (repoConfig && issue.labels.includes(repoConfig.labels.agentProductApproved)) {
28
+ return undefined;
29
+ }
30
+ return createIssueTask("product_discovery", issue, repoSyncRecord, {
31
+ reason: "Issue is newly seen and needs Product Discovery before implementation approval.",
32
+ summary: `Product Discovery for issue #${issue.number}: ${issue.title}`,
33
+ contextBuilder: env.input.contextBuilder,
34
+ now: env.input.now
35
+ }, workstream.id);
36
+ }