@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,629 @@
|
|
|
1
|
+
import { reduceWorkstreamPhase } from "./phases.js";
|
|
2
|
+
import { markMergedPacketPullRequests } from "./packet-plan.js";
|
|
3
|
+
function deriveProductState(input) {
|
|
4
|
+
const approvalLabel = input.repo?.labels.agentProductApproved;
|
|
5
|
+
const issueApproved = approvalLabel !== undefined && input.issue?.labels.includes(approvalLabel);
|
|
6
|
+
const previous = input.previous?.product;
|
|
7
|
+
if (input.activeWorktree?.productSpecWrittenAt || previous?.status === "spec_written") {
|
|
8
|
+
return {
|
|
9
|
+
status: "spec_written",
|
|
10
|
+
...(approvalLabel ? { approvalLabel } : {}),
|
|
11
|
+
...(previous?.approvedAt ? { approvedAt: previous.approvedAt } : issueApproved && input.now ? { approvedAt: input.now } : {})
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
if (input.nextPhase === "product_specification" || issueApproved) {
|
|
15
|
+
return {
|
|
16
|
+
status: "approved_for_spec",
|
|
17
|
+
...(approvalLabel ? { approvalLabel } : {}),
|
|
18
|
+
...(previous?.approvedAt ? { approvedAt: previous.approvedAt } : input.now ? { approvedAt: input.now } : {})
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
if (input.nextPhase === "product_discovery" || previous?.status === "in_discovery") {
|
|
22
|
+
return {
|
|
23
|
+
status: "in_discovery",
|
|
24
|
+
...(approvalLabel ? { approvalLabel } : {})
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
return previous;
|
|
28
|
+
}
|
|
29
|
+
export function deriveWorkstreams(input) {
|
|
30
|
+
const reposById = new Map(input.repos.map((repo) => [repo.id, repo]));
|
|
31
|
+
const reposBySlug = new Map(input.repos.map((repo) => [`${repo.owner}/${repo.name}`, repo]));
|
|
32
|
+
const previousPullRequestIds = new Set((input.previousPullRequests ?? []).map((pr) => pr.id));
|
|
33
|
+
const currentIssues = [...input.currentIssues].sort(compareIssues);
|
|
34
|
+
const currentPullRequests = [...input.currentPullRequests].sort(comparePullRequests);
|
|
35
|
+
const previousWorkstreams = [...input.previousWorkstreams].sort(compareWorkstreams);
|
|
36
|
+
const worktrees = [...input.worktrees].sort(compareWorktrees);
|
|
37
|
+
const currentPrsByRepoAndNumber = new Map(currentPullRequests.map((pr) => [`${pr.repoId}:${pr.number}`, pr]));
|
|
38
|
+
const currentPrsByRepoAndBranch = new Map(currentPullRequests.map((pr) => [`${pr.repoId}:${pr.headBranch}`, pr]));
|
|
39
|
+
const attentionByWorkstreamId = new Map((input.attention ?? []).map((record) => [record.workstreamId, record]));
|
|
40
|
+
const worktreesByParentId = new Map();
|
|
41
|
+
for (const worktree of worktrees) {
|
|
42
|
+
let parentId = getIssueWorkstreamIdForWorktree(worktree);
|
|
43
|
+
if (!parentId && worktree.packet?.parentWorkstreamId) {
|
|
44
|
+
parentId = worktree.packet.parentWorkstreamId;
|
|
45
|
+
}
|
|
46
|
+
if (parentId) {
|
|
47
|
+
if (!worktreesByParentId.has(parentId)) {
|
|
48
|
+
worktreesByParentId.set(parentId, []);
|
|
49
|
+
}
|
|
50
|
+
worktreesByParentId.get(parentId).push(worktree);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const issueBuilders = new Map();
|
|
54
|
+
for (const previous of previousWorkstreams) {
|
|
55
|
+
if (previous.origin.type !== "issue") {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const packetPrNumbers = new Set(Object.values(previous.packetPlan?.packets ?? {})
|
|
59
|
+
.map((p) => p.prNumber)
|
|
60
|
+
.filter(isDefined));
|
|
61
|
+
issueBuilders.set(previous.id, {
|
|
62
|
+
id: previous.id,
|
|
63
|
+
repoId: undefined,
|
|
64
|
+
repoKey: previous.repoKey,
|
|
65
|
+
originIssueNumber: previous.origin.number,
|
|
66
|
+
previous,
|
|
67
|
+
currentIssue: undefined,
|
|
68
|
+
worktrees: [],
|
|
69
|
+
linkedPrNumbers: new Set(previous.linkedPrs
|
|
70
|
+
.map((pr) => pr.number)
|
|
71
|
+
.filter((num) => !packetPrNumbers.has(num)))
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
for (const issue of currentIssues) {
|
|
75
|
+
const id = buildIssueWorkstreamId(issue.repoOwner, issue.repoName, issue.number);
|
|
76
|
+
const existing = issueBuilders.get(id);
|
|
77
|
+
issueBuilders.set(id, {
|
|
78
|
+
id,
|
|
79
|
+
repoId: issue.repoId,
|
|
80
|
+
repoKey: issue.repoKey,
|
|
81
|
+
originIssueNumber: issue.number,
|
|
82
|
+
previous: existing?.previous ?? undefined,
|
|
83
|
+
currentIssue: issue,
|
|
84
|
+
worktrees: existing?.worktrees ?? [],
|
|
85
|
+
linkedPrNumbers: existing?.linkedPrNumbers ?? new Set()
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
for (const [parentId, relatedWorktrees] of worktreesByParentId) {
|
|
89
|
+
const issueNumber = extractIssueNumberFromWorkstreamId(parentId);
|
|
90
|
+
if (issueNumber === undefined) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const existing = issueBuilders.get(parentId);
|
|
94
|
+
const builder = existing ?? {
|
|
95
|
+
id: parentId,
|
|
96
|
+
repoId: relatedWorktrees[0]?.repoId,
|
|
97
|
+
repoKey: relatedWorktrees[0].repoSlug,
|
|
98
|
+
originIssueNumber: issueNumber,
|
|
99
|
+
previous: undefined,
|
|
100
|
+
currentIssue: undefined,
|
|
101
|
+
worktrees: [],
|
|
102
|
+
linkedPrNumbers: new Set()
|
|
103
|
+
};
|
|
104
|
+
for (const worktree of relatedWorktrees) {
|
|
105
|
+
builder.worktrees.push(worktree);
|
|
106
|
+
if (worktree.packet) {
|
|
107
|
+
if (worktree.prNumber !== undefined) {
|
|
108
|
+
builder.linkedPrNumbers.delete(worktree.prNumber);
|
|
109
|
+
}
|
|
110
|
+
const pr = currentPrsByRepoAndBranch.get(`${worktree.repoId}:${worktree.branchName}`);
|
|
111
|
+
if (pr) {
|
|
112
|
+
builder.linkedPrNumbers.delete(pr.number);
|
|
113
|
+
}
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (worktree.prNumber !== undefined) {
|
|
117
|
+
builder.linkedPrNumbers.add(worktree.prNumber);
|
|
118
|
+
}
|
|
119
|
+
const pr = currentPrsByRepoAndBranch.get(`${worktree.repoId}:${worktree.branchName}`);
|
|
120
|
+
if (pr) {
|
|
121
|
+
builder.linkedPrNumbers.add(pr.number);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
issueBuilders.set(parentId, builder);
|
|
125
|
+
}
|
|
126
|
+
const issueWorkstreams = [...issueBuilders.values()]
|
|
127
|
+
.map((builder) => {
|
|
128
|
+
const sortedWorktrees = [...builder.worktrees]
|
|
129
|
+
.filter((wt) => !wt.packet)
|
|
130
|
+
.sort(compareWorktrees);
|
|
131
|
+
const linkedPrs = [...builder.linkedPrNumbers]
|
|
132
|
+
.sort((left, right) => left - right)
|
|
133
|
+
.map((prNumber) => buildLinkedPullRequest(builder.previous, currentPrsByRepoAndNumber.get(`${builder.repoId ?? builder.currentIssue?.repoId}:${prNumber}`), prNumber, "implementation"));
|
|
134
|
+
const activeWorktree = pickActiveWorktree(sortedWorktrees);
|
|
135
|
+
const repoId = builder.repoId ?? builder.currentIssue?.repoId;
|
|
136
|
+
const activePrNumber = activeWorktree?.prNumber ??
|
|
137
|
+
(activeWorktree
|
|
138
|
+
? currentPrsByRepoAndBranch.get(`${activeWorktree.repoId}:${activeWorktree.branchName}`)?.number
|
|
139
|
+
: undefined) ??
|
|
140
|
+
pickActivePrNumber(linkedPrs, currentPrsByRepoAndNumber, repoId);
|
|
141
|
+
const activePullRequest = activePrNumber !== undefined && repoId !== undefined
|
|
142
|
+
? currentPrsByRepoAndNumber.get(`${repoId}:${activePrNumber}`)
|
|
143
|
+
: undefined;
|
|
144
|
+
const repo = reposById.get(repoId ?? "");
|
|
145
|
+
const status = deriveIssueStatus(builder.currentIssue, linkedPrs, currentPrsByRepoAndNumber, repoId, builder.previous?.status);
|
|
146
|
+
const resolution = deriveResolution(linkedPrs, currentPrsByRepoAndNumber, repoId, builder.previous?.resolution);
|
|
147
|
+
const integration = deriveIntegrationState(activePullRequest, activePrNumber, activeWorktree, builder.previous, repo?.defaultBranch);
|
|
148
|
+
const previousPacketPlan = builder.previous?.packetPlan;
|
|
149
|
+
let packetPlan = previousPacketPlan && repoId
|
|
150
|
+
? markMergedPacketPullRequests({
|
|
151
|
+
packetPlan: previousPacketPlan,
|
|
152
|
+
parentWorkstreamId: builder.id,
|
|
153
|
+
parentIntegrationBranch: integration?.branchName ?? builder.previous?.integration?.branchName,
|
|
154
|
+
repoId,
|
|
155
|
+
packetWorktrees: builder.worktrees,
|
|
156
|
+
pullRequests: currentPullRequests
|
|
157
|
+
})
|
|
158
|
+
: previousPacketPlan;
|
|
159
|
+
const nextPhase = reduceWorkstreamPhase({
|
|
160
|
+
previousPhase: builder.previous?.phase,
|
|
161
|
+
originType: "issue",
|
|
162
|
+
issuePresent: builder.currentIssue !== undefined,
|
|
163
|
+
activePullRequestState: activePullRequest?.state,
|
|
164
|
+
activePullRequestLabels: activePullRequest?.labels ?? [],
|
|
165
|
+
attentionReasons: attentionByWorkstreamId.get(builder.id)?.reasons ?? [],
|
|
166
|
+
inProgressLabel: repo?.labels.agentInProgress,
|
|
167
|
+
reviewLabel: repo?.labels.agentReview,
|
|
168
|
+
approvalLabel: repo?.labels.agentPlanApproved,
|
|
169
|
+
activeWorktreeStatus: activeWorktree?.status,
|
|
170
|
+
status,
|
|
171
|
+
resolution
|
|
172
|
+
});
|
|
173
|
+
const product = deriveProductState({
|
|
174
|
+
repo,
|
|
175
|
+
issue: builder.currentIssue,
|
|
176
|
+
previous: builder.previous,
|
|
177
|
+
activeWorktree,
|
|
178
|
+
nextPhase,
|
|
179
|
+
now: input.attention?.find((record) => record.workstreamId === builder.id)?.lastObservedAt
|
|
180
|
+
});
|
|
181
|
+
if (packetPlan) {
|
|
182
|
+
const packets = Object.values(packetPlan.packets);
|
|
183
|
+
const allMerged = packets.length > 0 && packets.every((p) => p.status === "merged_to_parent");
|
|
184
|
+
const anyStarted = packets.some((p) => p.status !== "planned");
|
|
185
|
+
const reasons = attentionByWorkstreamId.get(builder.id)?.reasons ?? [];
|
|
186
|
+
// Treat nextPhase === "implementation_ready", "implementing", or "implementation_done" as a plan approval signal.
|
|
187
|
+
// This is safe because a workstream cannot transition into or remain in these implementation phases
|
|
188
|
+
// without the plan first being approved. Checking this early nextPhase value helps eliminate
|
|
189
|
+
// the one-cycle lag that would occur if we only relied on the previous cycle's phase.
|
|
190
|
+
const hasApproval = reasons.includes("plan_approved") ||
|
|
191
|
+
nextPhase === "implementation_ready" ||
|
|
192
|
+
nextPhase === "implementing" ||
|
|
193
|
+
nextPhase === "implementation_done";
|
|
194
|
+
let newStatus = packetPlan.status;
|
|
195
|
+
if (allMerged) {
|
|
196
|
+
newStatus = "complete";
|
|
197
|
+
}
|
|
198
|
+
else if (anyStarted || nextPhase === "implementing") {
|
|
199
|
+
newStatus = "in_progress";
|
|
200
|
+
}
|
|
201
|
+
else if (hasApproval) {
|
|
202
|
+
newStatus = "approved";
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
const wasApproved = packetPlan.status === "approved";
|
|
206
|
+
const prevPhase = builder.previous?.phase;
|
|
207
|
+
// If the plan was previously approved and we were already in an implementation phase,
|
|
208
|
+
// but we no longer have approval (e.g. the plan approval label was explicitly removed),
|
|
209
|
+
// we transition back to "proposed".
|
|
210
|
+
// If the previous phase was something else (e.g. "planning" or "plan_feedback") and we
|
|
211
|
+
// don't have approval now, we preserve the "approved" status. This protects the approved status
|
|
212
|
+
// from being lost during transient cycle run failures or when the plan breakdown is re-evaluating.
|
|
213
|
+
const lostApproval = wasApproved &&
|
|
214
|
+
(prevPhase === "implementation_ready" ||
|
|
215
|
+
prevPhase === "implementing" ||
|
|
216
|
+
prevPhase === "implementation_done") &&
|
|
217
|
+
!hasApproval;
|
|
218
|
+
if (lostApproval) {
|
|
219
|
+
newStatus = "proposed";
|
|
220
|
+
}
|
|
221
|
+
else if (!wasApproved) {
|
|
222
|
+
newStatus = "proposed";
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
packetPlan = {
|
|
226
|
+
...packetPlan,
|
|
227
|
+
status: newStatus
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
const workstream = {
|
|
231
|
+
id: builder.id,
|
|
232
|
+
repoKey: builder.repoKey,
|
|
233
|
+
origin: {
|
|
234
|
+
type: "issue",
|
|
235
|
+
number: builder.originIssueNumber
|
|
236
|
+
},
|
|
237
|
+
linkedIssueNumber: builder.originIssueNumber,
|
|
238
|
+
linkedPrs,
|
|
239
|
+
...(activePrNumber !== undefined ? { activePrNumber } : {}),
|
|
240
|
+
...(activeWorktree ? { activeWorktreeId: activeWorktree.id } : {}),
|
|
241
|
+
phase: nextPhase,
|
|
242
|
+
attention: deriveAttention(linkedPrs, currentPrsByRepoAndNumber, repoId, repo, builder.previous?.attention),
|
|
243
|
+
status,
|
|
244
|
+
resolution,
|
|
245
|
+
...(product ? { product } : {}),
|
|
246
|
+
...(integration ? { integration } : {}),
|
|
247
|
+
...(packetPlan ? { packetPlan } : {}),
|
|
248
|
+
...(builder.previous?.mcpBlockers ? { mcpBlockers: builder.previous.mcpBlockers } : {}),
|
|
249
|
+
...(builder.previous?.mcpSkipExclusions ? { mcpSkipExclusions: builder.previous.mcpSkipExclusions } : {})
|
|
250
|
+
};
|
|
251
|
+
if (activePrNumber !== undefined) {
|
|
252
|
+
workstream.activePrNumber = activePrNumber;
|
|
253
|
+
}
|
|
254
|
+
if (activeWorktree) {
|
|
255
|
+
workstream.activeWorktreeId = activeWorktree.id;
|
|
256
|
+
}
|
|
257
|
+
else if (builder.previous?.activeWorktreeId) {
|
|
258
|
+
workstream.activeWorktreeId = builder.previous.activeWorktreeId;
|
|
259
|
+
}
|
|
260
|
+
return workstream;
|
|
261
|
+
})
|
|
262
|
+
.sort(compareWorkstreams);
|
|
263
|
+
const matchedPacketPrKeys = collectMatchedPacketPrKeys({
|
|
264
|
+
pullRequests: currentPullRequests,
|
|
265
|
+
issueWorkstreams,
|
|
266
|
+
reposById,
|
|
267
|
+
worktrees,
|
|
268
|
+
previousPullRequestIds
|
|
269
|
+
});
|
|
270
|
+
// Filter out matched packet PRs from issue workstream linkedPrs.
|
|
271
|
+
// These PRs are now managed as part of the packet plan and should not be treated
|
|
272
|
+
// as primary implementation PRs of the issue workstream itself.
|
|
273
|
+
for (const workstream of issueWorkstreams) {
|
|
274
|
+
workstream.linkedPrs = workstream.linkedPrs.filter((pr) => !matchedPacketPrKeys.has(buildRepoPrKey(workstream.repoKey, pr.number)));
|
|
275
|
+
}
|
|
276
|
+
const linkedIssuePrKeys = new Set(issueWorkstreams.flatMap((workstream) => workstream.linkedPrs.map((pr) => `${workstream.repoKey}:${pr.number}`)));
|
|
277
|
+
const previousPrWorkstreams = previousWorkstreams.filter((workstream) => workstream.origin.type === "pr");
|
|
278
|
+
const currentPrsByWorkstreamId = new Map(currentPullRequests.map((pr) => [buildPrWorkstreamId(pr.repoOwner, pr.repoName, pr.number), pr]));
|
|
279
|
+
const prWorkstreamIds = new Set([
|
|
280
|
+
...previousPrWorkstreams
|
|
281
|
+
.filter((workstream) => {
|
|
282
|
+
const currentPr = currentPrsByWorkstreamId.get(workstream.id);
|
|
283
|
+
if (!currentPr) {
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
return !matchedPacketPrKeys.has(buildRepoPrKey(currentPr.repoKey, currentPr.number));
|
|
287
|
+
})
|
|
288
|
+
.map((workstream) => workstream.id),
|
|
289
|
+
...currentPullRequests
|
|
290
|
+
.filter((pr) => !matchedPacketPrKeys.has(buildRepoPrKey(pr.repoKey, pr.number)))
|
|
291
|
+
.filter((pr) => isRelevantStandalonePr(pr, reposById.get(pr.repoId), worktrees, previousPullRequestIds))
|
|
292
|
+
.map((pr) => buildPrWorkstreamId(pr.repoOwner, pr.repoName, pr.number))
|
|
293
|
+
]);
|
|
294
|
+
const prWorkstreams = [...prWorkstreamIds]
|
|
295
|
+
.sort((left, right) => left.localeCompare(right))
|
|
296
|
+
.map((id) => {
|
|
297
|
+
const previous = previousPrWorkstreams.find((workstream) => workstream.id === id);
|
|
298
|
+
const currentPr = currentPullRequests.find((pr) => buildPrWorkstreamId(pr.repoOwner, pr.repoName, pr.number) === id);
|
|
299
|
+
const repoKey = currentPr?.repoKey ?? previous?.repoKey;
|
|
300
|
+
const prNumber = currentPr?.number ?? previous?.origin.number;
|
|
301
|
+
if (!repoKey || prNumber === undefined) {
|
|
302
|
+
return undefined;
|
|
303
|
+
}
|
|
304
|
+
if (linkedIssuePrKeys.has(`${repoKey}:${prNumber}`)) {
|
|
305
|
+
return undefined;
|
|
306
|
+
}
|
|
307
|
+
const repo = reposBySlug.get(repoKey);
|
|
308
|
+
const repoId = repo?.id;
|
|
309
|
+
const relatedWorktrees = worktreesByParentId.get(id) ?? [];
|
|
310
|
+
const primaryWorktree = currentPr && repoId
|
|
311
|
+
? worktrees.find((candidate) => candidate.repoId === repoId &&
|
|
312
|
+
candidate.branchName === currentPr.headBranch &&
|
|
313
|
+
!candidate.issueId &&
|
|
314
|
+
!candidate.packet)
|
|
315
|
+
: undefined;
|
|
316
|
+
const allWorktrees = primaryWorktree ? [primaryWorktree, ...relatedWorktrees] : relatedWorktrees;
|
|
317
|
+
const activeWorktree = pickActiveWorktree(allWorktrees.filter((wt) => !wt.packet));
|
|
318
|
+
const linkedPrNumbers = new Set();
|
|
319
|
+
linkedPrNumbers.add(prNumber);
|
|
320
|
+
for (const wt of allWorktrees) {
|
|
321
|
+
if (wt.packet) {
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
if (wt.prNumber !== undefined) {
|
|
325
|
+
linkedPrNumbers.add(wt.prNumber);
|
|
326
|
+
}
|
|
327
|
+
const packetPr = currentPrsByRepoAndBranch.get(`${wt.repoId}:${wt.branchName}`);
|
|
328
|
+
if (packetPr) {
|
|
329
|
+
linkedPrNumbers.add(packetPr.number);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
const linkedPrs = [...linkedPrNumbers]
|
|
333
|
+
.sort((left, right) => left - right)
|
|
334
|
+
.map((num) => buildLinkedPullRequest(previous, currentPrsByRepoAndNumber.get(`${repoId}:${num}`), num, num === prNumber ? undefined : "implementation"));
|
|
335
|
+
const activePrNumber = activeWorktree?.prNumber ??
|
|
336
|
+
(activeWorktree
|
|
337
|
+
? currentPrsByRepoAndBranch.get(`${activeWorktree.repoId}:${activeWorktree.branchName}`)?.number
|
|
338
|
+
: undefined) ??
|
|
339
|
+
pickActivePrNumber(linkedPrs, currentPrsByRepoAndNumber, repoId);
|
|
340
|
+
const activePullRequest = activePrNumber !== undefined && repoId !== undefined
|
|
341
|
+
? currentPrsByRepoAndNumber.get(`${repoId}:${activePrNumber}`)
|
|
342
|
+
: undefined;
|
|
343
|
+
const integration = deriveIntegrationState(activePullRequest, activePrNumber, activeWorktree, previous, repo?.defaultBranch);
|
|
344
|
+
const workstream = {
|
|
345
|
+
id,
|
|
346
|
+
repoKey,
|
|
347
|
+
origin: {
|
|
348
|
+
type: "pr",
|
|
349
|
+
number: prNumber
|
|
350
|
+
},
|
|
351
|
+
linkedPrs,
|
|
352
|
+
...(activePrNumber !== undefined ? { activePrNumber } : {}),
|
|
353
|
+
phase: reduceWorkstreamPhase({
|
|
354
|
+
previousPhase: previous?.phase,
|
|
355
|
+
originType: "pr",
|
|
356
|
+
issuePresent: false,
|
|
357
|
+
activePullRequestState: activePullRequest?.state,
|
|
358
|
+
activePullRequestLabels: activePullRequest?.labels ?? [],
|
|
359
|
+
attentionReasons: attentionByWorkstreamId.get(id)?.reasons ?? [],
|
|
360
|
+
inProgressLabel: repo?.labels.agentInProgress,
|
|
361
|
+
reviewLabel: repo?.labels.agentReview,
|
|
362
|
+
approvalLabel: repo?.labels.agentPlanApproved,
|
|
363
|
+
activeWorktreeStatus: activeWorktree?.status,
|
|
364
|
+
status: activePullRequest ? derivePrStatus(activePullRequest) : previous?.status ?? "open",
|
|
365
|
+
resolution: activePullRequest ? derivePrResolution(activePullRequest) : previous?.resolution ?? "unresolved"
|
|
366
|
+
}),
|
|
367
|
+
attention: activePullRequest
|
|
368
|
+
? deriveAttentionForPr(activePullRequest, repo, previous?.attention)
|
|
369
|
+
: previous?.attention ?? "idle",
|
|
370
|
+
status: activePullRequest ? derivePrStatus(activePullRequest) : previous?.status ?? "open",
|
|
371
|
+
resolution: activePullRequest ? derivePrResolution(activePullRequest) : previous?.resolution ?? "unresolved",
|
|
372
|
+
...(integration ? { integration } : {}),
|
|
373
|
+
...(previous?.packetPlan ? { packetPlan: previous.packetPlan } : {}),
|
|
374
|
+
...(previous?.mcpBlockers ? { mcpBlockers: previous.mcpBlockers } : {}),
|
|
375
|
+
...(previous?.mcpSkipExclusions ? { mcpSkipExclusions: previous.mcpSkipExclusions } : {})
|
|
376
|
+
};
|
|
377
|
+
if (activeWorktree) {
|
|
378
|
+
workstream.activeWorktreeId = activeWorktree.id;
|
|
379
|
+
}
|
|
380
|
+
else if (previous?.activeWorktreeId) {
|
|
381
|
+
workstream.activeWorktreeId = previous.activeWorktreeId;
|
|
382
|
+
}
|
|
383
|
+
return workstream;
|
|
384
|
+
})
|
|
385
|
+
.filter(isDefined);
|
|
386
|
+
return [...issueWorkstreams, ...prWorkstreams].sort(compareWorkstreams);
|
|
387
|
+
}
|
|
388
|
+
function buildLinkedPullRequest(previous, currentPr, prNumber, defaultRole) {
|
|
389
|
+
const previousPr = previous?.linkedPrs.find((pr) => pr.number === prNumber);
|
|
390
|
+
const linkedPr = {
|
|
391
|
+
number: prNumber
|
|
392
|
+
};
|
|
393
|
+
if (currentPr?.state) {
|
|
394
|
+
linkedPr.status = currentPr.state;
|
|
395
|
+
}
|
|
396
|
+
else if (previousPr?.status) {
|
|
397
|
+
linkedPr.status = previousPr.status;
|
|
398
|
+
}
|
|
399
|
+
const role = defaultRole ?? previousPr?.role;
|
|
400
|
+
if (role !== undefined) {
|
|
401
|
+
linkedPr.role = role;
|
|
402
|
+
}
|
|
403
|
+
return linkedPr;
|
|
404
|
+
}
|
|
405
|
+
function deriveAttention(linkedPrs, currentPrsByRepoAndNumber, repoId, repo, previousAttention) {
|
|
406
|
+
if (repoId !== undefined && repo) {
|
|
407
|
+
for (const linkedPr of linkedPrs) {
|
|
408
|
+
const pr = currentPrsByRepoAndNumber.get(`${repoId}:${linkedPr.number}`);
|
|
409
|
+
if (pr?.labels.includes(repo.labels.agentReview)) {
|
|
410
|
+
return "review_requested";
|
|
411
|
+
}
|
|
412
|
+
if (pr?.labels.includes(repo.labels.prepareForMerge)) {
|
|
413
|
+
return "prepare_for_merge_requested";
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return previousAttention ?? "idle";
|
|
418
|
+
}
|
|
419
|
+
function deriveAttentionForPr(pr, repo, previousAttention) {
|
|
420
|
+
if (repo) {
|
|
421
|
+
if (pr.labels.includes(repo.labels.agentReview)) {
|
|
422
|
+
return "review_requested";
|
|
423
|
+
}
|
|
424
|
+
if (pr.labels.includes(repo.labels.prepareForMerge)) {
|
|
425
|
+
return "prepare_for_merge_requested";
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
return previousAttention ?? "idle";
|
|
429
|
+
}
|
|
430
|
+
function deriveIssueStatus(issue, linkedPrs, currentPrsByRepoAndNumber, repoId, previousStatus) {
|
|
431
|
+
if (repoId !== undefined) {
|
|
432
|
+
for (const linkedPr of linkedPrs) {
|
|
433
|
+
const pr = currentPrsByRepoAndNumber.get(`${repoId}:${linkedPr.number}`);
|
|
434
|
+
if (pr?.state === "merged") {
|
|
435
|
+
return "merged";
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
if (issue?.state === "closed") {
|
|
440
|
+
return "closed";
|
|
441
|
+
}
|
|
442
|
+
// listOpenIssues only returns open issues. If an issue was previously tracked
|
|
443
|
+
// as open but is absent from the current scan, it has been closed on GitHub.
|
|
444
|
+
if (issue === undefined && previousStatus === "open") {
|
|
445
|
+
return "closed";
|
|
446
|
+
}
|
|
447
|
+
return previousStatus ?? "open";
|
|
448
|
+
}
|
|
449
|
+
function derivePrStatus(pr) {
|
|
450
|
+
if (pr.state === "merged") {
|
|
451
|
+
return "merged";
|
|
452
|
+
}
|
|
453
|
+
if (pr.state === "closed") {
|
|
454
|
+
return "closed";
|
|
455
|
+
}
|
|
456
|
+
return "open";
|
|
457
|
+
}
|
|
458
|
+
function deriveResolution(linkedPrs, currentPrsByRepoAndNumber, repoId, previousResolution) {
|
|
459
|
+
if (repoId !== undefined) {
|
|
460
|
+
for (const linkedPr of linkedPrs) {
|
|
461
|
+
const pr = currentPrsByRepoAndNumber.get(`${repoId}:${linkedPr.number}`);
|
|
462
|
+
if (pr?.state === "merged") {
|
|
463
|
+
return "merged";
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
return previousResolution ?? "unresolved";
|
|
468
|
+
}
|
|
469
|
+
function derivePrResolution(pr) {
|
|
470
|
+
if (pr.state === "merged") {
|
|
471
|
+
return "merged";
|
|
472
|
+
}
|
|
473
|
+
if (pr.state === "closed") {
|
|
474
|
+
return "closed";
|
|
475
|
+
}
|
|
476
|
+
return "unresolved";
|
|
477
|
+
}
|
|
478
|
+
function pickActiveWorktree(worktrees) {
|
|
479
|
+
return [...worktrees].sort((left, right) => {
|
|
480
|
+
const updatedComparison = right.updatedAt.localeCompare(left.updatedAt);
|
|
481
|
+
if (updatedComparison !== 0) {
|
|
482
|
+
return updatedComparison;
|
|
483
|
+
}
|
|
484
|
+
return left.id.localeCompare(right.id);
|
|
485
|
+
})[0];
|
|
486
|
+
}
|
|
487
|
+
function pickActivePrNumber(linkedPrs, currentPrsByRepoAndNumber, repoId) {
|
|
488
|
+
if (repoId !== undefined) {
|
|
489
|
+
const currentOpenPr = linkedPrs
|
|
490
|
+
.map((pr) => currentPrsByRepoAndNumber.get(`${repoId}:${pr.number}`))
|
|
491
|
+
.filter((pr) => pr !== undefined)
|
|
492
|
+
.sort(comparePullRequests)[0];
|
|
493
|
+
if (currentOpenPr) {
|
|
494
|
+
return currentOpenPr.number;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return linkedPrs[0]?.number;
|
|
498
|
+
}
|
|
499
|
+
function isRelevantStandalonePr(pr, repo, worktrees, previousPullRequestIds) {
|
|
500
|
+
if (!repo) {
|
|
501
|
+
return false;
|
|
502
|
+
}
|
|
503
|
+
if (pr.labels.includes(repo.labels.agentReview) ||
|
|
504
|
+
pr.labels.includes(repo.labels.agentInProgress) ||
|
|
505
|
+
pr.labels.includes(repo.labels.agentPlanApproved) ||
|
|
506
|
+
pr.labels.includes(repo.labels.planBreakdownNeeded) ||
|
|
507
|
+
pr.labels.includes(repo.labels.prepareForMerge)) {
|
|
508
|
+
return true;
|
|
509
|
+
}
|
|
510
|
+
if (previousPullRequestIds.has(pr.id)) {
|
|
511
|
+
return true;
|
|
512
|
+
}
|
|
513
|
+
return worktrees.some((worktree) => worktree.repoId === pr.repoId && worktree.branchName === pr.headBranch);
|
|
514
|
+
}
|
|
515
|
+
function getIssueWorkstreamIdForWorktree(worktree) {
|
|
516
|
+
if (worktree.issueNumber === undefined) {
|
|
517
|
+
return undefined;
|
|
518
|
+
}
|
|
519
|
+
const [owner, name] = worktree.repoSlug.split("/");
|
|
520
|
+
if (!owner || !name) {
|
|
521
|
+
return undefined;
|
|
522
|
+
}
|
|
523
|
+
return buildIssueWorkstreamId(owner, name, worktree.issueNumber);
|
|
524
|
+
}
|
|
525
|
+
function extractIssueNumberFromWorkstreamId(workstreamId) {
|
|
526
|
+
const match = workstreamId.match(/:issue:(\d+)$/);
|
|
527
|
+
return match ? Number(match[1]) : undefined;
|
|
528
|
+
}
|
|
529
|
+
function buildIssueWorkstreamId(owner, name, issueNumber) {
|
|
530
|
+
return `${owner}/${name}:issue:${issueNumber}`;
|
|
531
|
+
}
|
|
532
|
+
function buildPrWorkstreamId(owner, name, prNumber) {
|
|
533
|
+
return `${owner}/${name}:pr:${prNumber}`;
|
|
534
|
+
}
|
|
535
|
+
function compareIssues(left, right) {
|
|
536
|
+
const repoComparison = left.repoSlug.localeCompare(right.repoSlug);
|
|
537
|
+
if (repoComparison !== 0) {
|
|
538
|
+
return repoComparison;
|
|
539
|
+
}
|
|
540
|
+
return left.number - right.number;
|
|
541
|
+
}
|
|
542
|
+
function comparePullRequests(left, right) {
|
|
543
|
+
const repoComparison = left.repoSlug.localeCompare(right.repoSlug);
|
|
544
|
+
if (repoComparison !== 0) {
|
|
545
|
+
return repoComparison;
|
|
546
|
+
}
|
|
547
|
+
return left.number - right.number;
|
|
548
|
+
}
|
|
549
|
+
function compareWorktrees(left, right) {
|
|
550
|
+
const repoComparison = left.repoSlug.localeCompare(right.repoSlug);
|
|
551
|
+
if (repoComparison !== 0) {
|
|
552
|
+
return repoComparison;
|
|
553
|
+
}
|
|
554
|
+
return left.id.localeCompare(right.id);
|
|
555
|
+
}
|
|
556
|
+
function compareWorkstreams(left, right) {
|
|
557
|
+
const repoComparison = left.repoKey.localeCompare(right.repoKey);
|
|
558
|
+
if (repoComparison !== 0) {
|
|
559
|
+
return repoComparison;
|
|
560
|
+
}
|
|
561
|
+
return left.id.localeCompare(right.id);
|
|
562
|
+
}
|
|
563
|
+
function isDefined(value) {
|
|
564
|
+
return value !== undefined;
|
|
565
|
+
}
|
|
566
|
+
function deriveIntegrationState(activePullRequest, activePrNumber, activeWorktree, previous, defaultBranch) {
|
|
567
|
+
if (activePullRequest) {
|
|
568
|
+
if (activePullRequest.baseBranch !== activePullRequest.defaultBranch) {
|
|
569
|
+
return undefined;
|
|
570
|
+
}
|
|
571
|
+
return {
|
|
572
|
+
branchName: activePullRequest.headBranch,
|
|
573
|
+
prNumber: activePrNumber
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
if (activeWorktree) {
|
|
577
|
+
if (defaultBranch !== undefined &&
|
|
578
|
+
activeWorktree.baseBranch !== undefined &&
|
|
579
|
+
activeWorktree.baseBranch !== defaultBranch) {
|
|
580
|
+
return undefined;
|
|
581
|
+
}
|
|
582
|
+
return {
|
|
583
|
+
branchName: activeWorktree.branchName,
|
|
584
|
+
prNumber: activePrNumber
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
if (previous?.integration) {
|
|
588
|
+
return previous.integration;
|
|
589
|
+
}
|
|
590
|
+
return undefined;
|
|
591
|
+
}
|
|
592
|
+
function buildRepoPrKey(repoKey, prNumber) {
|
|
593
|
+
return `${repoKey}#${prNumber}`;
|
|
594
|
+
}
|
|
595
|
+
function collectMatchedPacketPrKeys(input) {
|
|
596
|
+
const activeParentIntegrationBranchesByRepo = new Map();
|
|
597
|
+
for (const workstream of input.issueWorkstreams) {
|
|
598
|
+
if (workstream.status === "open" &&
|
|
599
|
+
workstream.resolution === "unresolved" &&
|
|
600
|
+
workstream.integration?.branchName !== undefined) {
|
|
601
|
+
if (!activeParentIntegrationBranchesByRepo.has(workstream.repoKey)) {
|
|
602
|
+
activeParentIntegrationBranchesByRepo.set(workstream.repoKey, new Set());
|
|
603
|
+
}
|
|
604
|
+
activeParentIntegrationBranchesByRepo.get(workstream.repoKey).add(workstream.integration.branchName);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
for (const pr of input.pullRequests) {
|
|
608
|
+
if (pr.baseClassification === "top_level" && pr.state === "open") {
|
|
609
|
+
const repo = input.reposById.get(pr.repoId);
|
|
610
|
+
if (isRelevantStandalonePr(pr, repo, input.worktrees, input.previousPullRequestIds)) {
|
|
611
|
+
if (!activeParentIntegrationBranchesByRepo.has(pr.repoKey)) {
|
|
612
|
+
activeParentIntegrationBranchesByRepo.set(pr.repoKey, new Set());
|
|
613
|
+
}
|
|
614
|
+
activeParentIntegrationBranchesByRepo.get(pr.repoKey).add(pr.headBranch);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
const matched = new Set();
|
|
619
|
+
for (const pr of input.pullRequests) {
|
|
620
|
+
if (pr.baseClassification !== "non_default_parent") {
|
|
621
|
+
continue;
|
|
622
|
+
}
|
|
623
|
+
const branches = activeParentIntegrationBranchesByRepo.get(pr.repoKey);
|
|
624
|
+
if (branches?.has(pr.baseBranch)) {
|
|
625
|
+
matched.add(buildRepoPrKey(pr.repoKey, pr.number));
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
return matched;
|
|
629
|
+
}
|