@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.
- package/LICENSE +21 -0
- package/README.md +329 -0
- package/dist/src/agents/antigravity-launch.js +162 -0
- package/dist/src/agents/antigravity.js +251 -0
- package/dist/src/agents/claude-launch.js +117 -0
- package/dist/src/agents/claude.js +271 -0
- package/dist/src/agents/codex-launch.js +82 -0
- package/dist/src/agents/codex.js +261 -0
- package/dist/src/agents/composite-runner.js +13 -0
- package/dist/src/agents/extraction.js +442 -0
- package/dist/src/agents/failure-feedback.js +132 -0
- package/dist/src/agents/gemini-launch.js +96 -0
- package/dist/src/agents/gemini.js +392 -0
- package/dist/src/agents/job-agent-assignments.js +180 -0
- package/dist/src/agents/lifecycle-feedback.js +147 -0
- package/dist/src/agents/limit-detector.js +83 -0
- package/dist/src/agents/opencode-launch.js +138 -0
- package/dist/src/agents/opencode.js +156 -0
- package/dist/src/agents/plan-breakdown-feedback.js +97 -0
- package/dist/src/agents/pool.js +168 -0
- package/dist/src/agents/prompt.js +462 -0
- package/dist/src/agents/provider-sessions.js +105 -0
- package/dist/src/agents/review-feedback.js +255 -0
- package/dist/src/agents/selection.js +113 -0
- package/dist/src/agents/service.js +1087 -0
- package/dist/src/agents/types.js +1 -0
- package/dist/src/agents/wave-assessment-feedback.js +69 -0
- package/dist/src/attention/derive.js +337 -0
- package/dist/src/capabilities/index.js +2 -0
- package/dist/src/capabilities/projection.js +183 -0
- package/dist/src/capabilities/types.js +1 -0
- package/dist/src/capabilities/validator.js +87 -0
- package/dist/src/cli/commands/broker-smoke.js +96 -0
- package/dist/src/cli/commands/broker.js +56 -0
- package/dist/src/cli/commands/init.js +4 -0
- package/dist/src/cli/commands/journal.js +43 -0
- package/dist/src/cli/commands/run-loop.js +56 -0
- package/dist/src/cli/commands/run.js +1241 -0
- package/dist/src/cli/commands/tui.js +187 -0
- package/dist/src/cli/dispatcher.js +51 -0
- package/dist/src/cli/errors.js +2 -0
- package/dist/src/cli/init.js +92 -0
- package/dist/src/cli/options.js +86 -0
- package/dist/src/cli/output.js +231 -0
- package/dist/src/cli/run-journal.js +342 -0
- package/dist/src/cli/run-types.js +1 -0
- package/dist/src/cli/run.js +32 -0
- package/dist/src/cli/runtime/dispatch-lifecycle.js +772 -0
- package/dist/src/cli/runtime/provider-runtime-types.js +1 -0
- package/dist/src/cli/runtime/provider-runtime.js +152 -0
- package/dist/src/cli/runtime/provider-tools.js +73 -0
- package/dist/src/cli/runtime/providers/antigravity.js +26 -0
- package/dist/src/cli/runtime/providers/claude.js +20 -0
- package/dist/src/cli/runtime/providers/codex.js +20 -0
- package/dist/src/cli/runtime/providers/gemini.js +20 -0
- package/dist/src/cli/runtime/providers/opencode.js +20 -0
- package/dist/src/cli/runtime/run-context.js +131 -0
- package/dist/src/cli/tui/index.js +3 -0
- package/dist/src/cli/tui/screen.js +388 -0
- package/dist/src/cli/tui/tracker.js +558 -0
- package/dist/src/cli/tui/types.js +1 -0
- package/dist/src/cli/tui/views.js +1006 -0
- package/dist/src/config/loader.js +136 -0
- package/dist/src/config/schemas.js +403 -0
- package/dist/src/config/types.js +1 -0
- package/dist/src/context/types.js +331 -0
- package/dist/src/domain/entities.js +1 -0
- package/dist/src/git/backburner-git-tool-service.js +680 -0
- package/dist/src/git/gitops-lifecycle.js +56 -0
- package/dist/src/git/merge-prep-workspace.js +357 -0
- package/dist/src/git/types.js +484 -0
- package/dist/src/github/broker-model-content.js +215 -0
- package/dist/src/github/broker-server.js +237 -0
- package/dist/src/github/broker.js +638 -0
- package/dist/src/github/composed.js +53 -0
- package/dist/src/github/gateway.js +478 -0
- package/dist/src/github/normalize.js +222 -0
- package/dist/src/github/pr-classification.js +73 -0
- package/dist/src/github/scoped-broker.js +35 -0
- package/dist/src/github/security.js +126 -0
- package/dist/src/github/service.js +332 -0
- package/dist/src/github/types.js +1 -0
- package/dist/src/github/utils.js +16 -0
- package/dist/src/journal/index.js +4 -0
- package/dist/src/journal/reader.js +62 -0
- package/dist/src/journal/renderer.js +50 -0
- package/dist/src/journal/service.js +72 -0
- package/dist/src/journal/types.js +1 -0
- package/dist/src/journal/writer.js +19 -0
- package/dist/src/mcps/bridge.js +166 -0
- package/dist/src/mcps/index.js +5 -0
- package/dist/src/mcps/reconcile.js +113 -0
- package/dist/src/mcps/smithery-client.js +186 -0
- package/dist/src/mcps/smithery.js +164 -0
- package/dist/src/mcps/types.js +1 -0
- package/dist/src/memory/json-store.js +71 -0
- package/dist/src/memory/prompt.js +16 -0
- package/dist/src/memory/scope.js +31 -0
- package/dist/src/memory/store.js +1 -0
- package/dist/src/memory/tool-service.js +142 -0
- package/dist/src/memory/types.js +12 -0
- package/dist/src/onboarding/config-writer.js +96 -0
- package/dist/src/onboarding/engine.js +86 -0
- package/dist/src/onboarding/prompt.js +102 -0
- package/dist/src/onboarding/services/provider-discovery.js +58 -0
- package/dist/src/onboarding/services/repo-discovery.js +246 -0
- package/dist/src/onboarding/steps/check-github-auth.js +32 -0
- package/dist/src/onboarding/steps/check-requirements.js +34 -0
- package/dist/src/onboarding/steps/configure-models.js +105 -0
- package/dist/src/onboarding/steps/configure-paths.js +33 -0
- package/dist/src/onboarding/steps/discover-providers.js +38 -0
- package/dist/src/onboarding/steps/discover-repos.js +39 -0
- package/dist/src/onboarding/steps/generate-config.js +106 -0
- package/dist/src/onboarding/steps/readiness-validation.js +158 -0
- package/dist/src/onboarding/steps/select-repos.js +76 -0
- package/dist/src/onboarding/steps/show-summary.js +97 -0
- package/dist/src/onboarding/steps/welcome.js +27 -0
- package/dist/src/onboarding/types.js +1 -0
- package/dist/src/onboarding/validators/git-access.js +63 -0
- package/dist/src/onboarding/validators/github-auth.js +85 -0
- package/dist/src/onboarding/validators/tool-checker.js +153 -0
- package/dist/src/onboarding/validators/workspace.js +83 -0
- package/dist/src/prompts/resolver.js +106 -0
- package/dist/src/retrospectives/candidates.js +224 -0
- package/dist/src/retrospectives/derive.js +169 -0
- package/dist/src/retrospectives/ingest.js +321 -0
- package/dist/src/retrospectives/management-prs.js +380 -0
- package/dist/src/retrospectives/proposals.js +199 -0
- package/dist/src/retrospectives/scopes.js +272 -0
- package/dist/src/retrospectives/scoring.js +171 -0
- package/dist/src/retrospectives/types.js +1 -0
- package/dist/src/state/loader.js +473 -0
- package/dist/src/state/types.js +15 -0
- package/dist/src/tasks/derivation/builders.js +70 -0
- package/dist/src/tasks/derivation/handlers/comment-response.js +169 -0
- package/dist/src/tasks/derivation/handlers/implement-plan.js +103 -0
- package/dist/src/tasks/derivation/handlers/index.js +54 -0
- package/dist/src/tasks/derivation/handlers/packet-pr-control.js +92 -0
- package/dist/src/tasks/derivation/handlers/packet-worktree.js +29 -0
- package/dist/src/tasks/derivation/handlers/plan-breakdown.js +45 -0
- package/dist/src/tasks/derivation/handlers/prepare-for-merge.js +91 -0
- package/dist/src/tasks/derivation/handlers/product-discovery.js +36 -0
- package/dist/src/tasks/derivation/handlers/retry-failed-task.js +125 -0
- package/dist/src/tasks/derivation/handlers/review-pr.js +28 -0
- package/dist/src/tasks/derivation/handlers/shared.js +24 -0
- package/dist/src/tasks/derivation/handlers/sync-parent-branch.js +100 -0
- package/dist/src/tasks/derivation/handlers/wave-assessment.js +223 -0
- package/dist/src/tasks/derivation/handlers/write-plan.js +101 -0
- package/dist/src/tasks/derivation/handlers/write-product-spec.js +49 -0
- package/dist/src/tasks/derivation/runner.js +18 -0
- package/dist/src/tasks/derivation/types.js +1 -0
- package/dist/src/tasks/derive.js +810 -0
- package/dist/src/tasks/mcp-skip.js +10 -0
- package/dist/src/tasks/retry.js +10 -0
- package/dist/src/tasks/task.js +184 -0
- package/dist/src/tasks/types.js +1 -0
- package/dist/src/utils/command-runner.js +192 -0
- package/dist/src/utils/json.js +38 -0
- package/dist/src/utils/logger.js +43 -0
- package/dist/src/utils/paths.js +38 -0
- package/dist/src/utils/slug.js +10 -0
- package/dist/src/workflows/registry.js +174 -0
- package/dist/src/workflows/triage.js +90 -0
- package/dist/src/workflows/workstream-machine/derive-events.js +121 -0
- package/dist/src/workflows/workstream-machine/evaluate.js +24 -0
- package/dist/src/workflows/workstream-machine/events.js +1 -0
- package/dist/src/workflows/workstream-machine/machine.js +355 -0
- package/dist/src/workflows/workstream-machine/selectors.js +19 -0
- package/dist/src/workflows/workstream-machine/types.js +1 -0
- package/dist/src/workstreams/branch-sync.js +87 -0
- package/dist/src/workstreams/derive.js +629 -0
- package/dist/src/workstreams/mcp-blockers.js +420 -0
- package/dist/src/workstreams/packet-candidates.js +42 -0
- package/dist/src/workstreams/packet-lifecycle-feedback.js +60 -0
- package/dist/src/workstreams/packet-lifecycle-report.js +199 -0
- package/dist/src/workstreams/packet-plan.js +597 -0
- package/dist/src/workstreams/packet-projections.js +190 -0
- package/dist/src/workstreams/parent-branch-sync-action.js +312 -0
- package/dist/src/workstreams/parent-branch-sync.js +148 -0
- package/dist/src/workstreams/phases.js +4 -0
- package/dist/src/workstreams/plan-breakdown-generator/export.js +19 -0
- package/dist/src/workstreams/plan-breakdown-generator/index.js +6 -0
- package/dist/src/workstreams/plan-breakdown-generator/inspect.js +50 -0
- package/dist/src/workstreams/plan-breakdown-generator/patch.js +139 -0
- package/dist/src/workstreams/plan-breakdown-generator/session.js +19 -0
- package/dist/src/workstreams/plan-breakdown-generator/store.js +27 -0
- package/dist/src/workstreams/plan-breakdown-generator/tools.js +407 -0
- package/dist/src/workstreams/plan-breakdown-generator/types.js +6 -0
- package/dist/src/workstreams/plan-breakdown-generator/validate.js +212 -0
- package/dist/src/workstreams/plan-breakdown-schema.js +175 -0
- package/dist/src/workstreams/utils.js +15 -0
- package/dist/src/workstreams/wave-assessment/checkpoint.js +172 -0
- package/dist/src/workstreams/wave-assessment/index.js +3 -0
- package/dist/src/workstreams/wave-assessment/session.js +84 -0
- package/dist/src/workstreams/wave-assessment/store.js +51 -0
- package/dist/src/workstreams/wave-assessment/tools.js +419 -0
- package/dist/src/workstreams/wave-assessment/types.js +1 -0
- package/dist/src/workstreams/wave-assessment/validate.js +78 -0
- package/dist/src/worktrees/service.js +839 -0
- package/dist/src/worktrees/workspace-ownership.js +34 -0
- package/dist/src/worktrees/workspace-recovery.js +177 -0
- 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
|
+
}
|