gsd-pi 2.23.0 → 2.24.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/dist/cli.js +12 -3
- package/dist/headless.d.ts +4 -0
- package/dist/headless.js +118 -10
- package/dist/help-text.js +22 -7
- package/dist/resource-loader.js +64 -9
- package/dist/resources/extensions/gsd/auto-dispatch.ts +51 -2
- package/dist/resources/extensions/gsd/auto-prompts.ts +73 -0
- package/dist/resources/extensions/gsd/auto-recovery.ts +41 -2
- package/dist/resources/extensions/gsd/auto-worktree.ts +15 -3
- package/dist/resources/extensions/gsd/auto.ts +123 -41
- package/dist/resources/extensions/gsd/commands.ts +176 -10
- package/dist/resources/extensions/gsd/complexity.ts +1 -0
- package/dist/resources/extensions/gsd/dashboard-overlay.ts +38 -0
- package/dist/resources/extensions/gsd/doctor.ts +56 -11
- package/dist/resources/extensions/gsd/exit-command.ts +2 -2
- package/dist/resources/extensions/gsd/gitignore.ts +1 -0
- package/dist/resources/extensions/gsd/guided-flow.ts +75 -0
- package/dist/resources/extensions/gsd/index.ts +34 -1
- package/dist/resources/extensions/gsd/parallel-eligibility.ts +233 -0
- package/dist/resources/extensions/gsd/parallel-merge.ts +156 -0
- package/dist/resources/extensions/gsd/parallel-orchestrator.ts +496 -0
- package/dist/resources/extensions/gsd/preferences.ts +65 -1
- package/dist/resources/extensions/gsd/prompts/discuss-headless.md +86 -0
- package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +40 -61
- package/dist/resources/extensions/gsd/provider-error-pause.ts +29 -2
- package/dist/resources/extensions/gsd/session-status-io.ts +197 -0
- package/dist/resources/extensions/gsd/state.ts +72 -30
- package/dist/resources/extensions/gsd/tests/agent-end-provider-error.test.ts +81 -0
- package/dist/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +20 -3
- package/dist/resources/extensions/gsd/tests/auto-preflight.test.ts +1 -0
- package/dist/resources/extensions/gsd/tests/auto-recovery.test.ts +202 -2
- package/dist/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +34 -0
- package/dist/resources/extensions/gsd/tests/complete-milestone.test.ts +8 -1
- package/dist/resources/extensions/gsd/tests/derive-state-db.test.ts +9 -15
- package/dist/resources/extensions/gsd/tests/derive-state-deps.test.ts +9 -0
- package/dist/resources/extensions/gsd/tests/derive-state-draft.test.ts +8 -0
- package/dist/resources/extensions/gsd/tests/derive-state.test.ts +14 -0
- package/dist/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +8 -0
- package/dist/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +5 -5
- package/dist/resources/extensions/gsd/tests/parallel-orchestration.test.ts +656 -0
- package/dist/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +354 -0
- package/dist/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +1 -0
- package/dist/resources/extensions/gsd/tests/validate-milestone.test.ts +316 -0
- package/dist/resources/extensions/gsd/tests/worker-registry.test.ts +148 -0
- package/dist/resources/extensions/gsd/types.ts +15 -1
- package/dist/resources/extensions/subagent/index.ts +5 -0
- package/dist/resources/extensions/subagent/worker-registry.ts +99 -0
- package/dist/update-check.d.ts +9 -0
- package/dist/update-check.js +97 -0
- package/package.json +6 -1
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +16 -7
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/azure-openai-responses.js +12 -4
- package/packages/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
- package/packages/pi-ai/dist/providers/google-vertex.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/google-vertex.js +21 -9
- package/packages/pi-ai/dist/providers/google-vertex.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.js +12 -4
- package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.js +12 -4
- package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
- package/packages/pi-ai/src/providers/anthropic.ts +21 -8
- package/packages/pi-ai/src/providers/azure-openai-responses.ts +16 -4
- package/packages/pi-ai/src/providers/google-vertex.ts +32 -17
- package/packages/pi-ai/src/providers/openai-completions.ts +16 -4
- package/packages/pi-ai/src/providers/openai-responses.ts +16 -4
- package/packages/pi-coding-agent/dist/core/agent-session.js +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +1 -1
- package/packages/pi-coding-agent/src/core/settings-manager.ts +2 -2
- package/scripts/postinstall.js +7 -109
- package/src/resources/extensions/gsd/auto-dispatch.ts +51 -2
- package/src/resources/extensions/gsd/auto-prompts.ts +73 -0
- package/src/resources/extensions/gsd/auto-recovery.ts +41 -2
- package/src/resources/extensions/gsd/auto-worktree.ts +15 -3
- package/src/resources/extensions/gsd/auto.ts +123 -41
- package/src/resources/extensions/gsd/commands.ts +176 -10
- package/src/resources/extensions/gsd/complexity.ts +1 -0
- package/src/resources/extensions/gsd/dashboard-overlay.ts +38 -0
- package/src/resources/extensions/gsd/doctor.ts +56 -11
- package/src/resources/extensions/gsd/exit-command.ts +2 -2
- package/src/resources/extensions/gsd/gitignore.ts +1 -0
- package/src/resources/extensions/gsd/guided-flow.ts +75 -0
- package/src/resources/extensions/gsd/index.ts +34 -1
- package/src/resources/extensions/gsd/parallel-eligibility.ts +233 -0
- package/src/resources/extensions/gsd/parallel-merge.ts +156 -0
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +496 -0
- package/src/resources/extensions/gsd/preferences.ts +65 -1
- package/src/resources/extensions/gsd/prompts/discuss-headless.md +86 -0
- package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +40 -61
- package/src/resources/extensions/gsd/provider-error-pause.ts +29 -2
- package/src/resources/extensions/gsd/session-status-io.ts +197 -0
- package/src/resources/extensions/gsd/state.ts +72 -30
- package/src/resources/extensions/gsd/tests/agent-end-provider-error.test.ts +81 -0
- package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +20 -3
- package/src/resources/extensions/gsd/tests/auto-preflight.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +202 -2
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +34 -0
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +8 -1
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +9 -15
- package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/derive-state-draft.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +5 -5
- package/src/resources/extensions/gsd/tests/parallel-orchestration.test.ts +656 -0
- package/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +354 -0
- package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +316 -0
- package/src/resources/extensions/gsd/tests/worker-registry.test.ts +148 -0
- package/src/resources/extensions/gsd/types.ts +15 -1
- package/src/resources/extensions/subagent/index.ts +5 -0
- package/src/resources/extensions/subagent/worker-registry.ts +99 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GSD Parallel Eligibility — Milestone parallelism analysis.
|
|
3
|
+
*
|
|
4
|
+
* Analyzes which milestones can safely run in parallel by checking
|
|
5
|
+
* dependency satisfaction and file overlap across slice plans.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { deriveState } from "./state.js";
|
|
9
|
+
import { parseRoadmap, parsePlan, loadFile } from "./files.js";
|
|
10
|
+
import { resolveMilestoneFile, resolveSliceFile } from "./paths.js";
|
|
11
|
+
import { findMilestoneIds } from "./guided-flow.js";
|
|
12
|
+
import type { MilestoneRegistryEntry } from "./types.js";
|
|
13
|
+
|
|
14
|
+
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
export interface EligibilityResult {
|
|
17
|
+
milestoneId: string;
|
|
18
|
+
title: string;
|
|
19
|
+
eligible: boolean;
|
|
20
|
+
reason: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ParallelCandidates {
|
|
24
|
+
eligible: EligibilityResult[];
|
|
25
|
+
ineligible: EligibilityResult[];
|
|
26
|
+
fileOverlaps: Array<{ mid1: string; mid2: string; files: string[] }>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ─── File Collection ─────────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Collect all `filesLikelyTouched` across every slice plan in a milestone.
|
|
33
|
+
* Returns a deduplicated list of file paths.
|
|
34
|
+
*/
|
|
35
|
+
async function collectTouchedFiles(
|
|
36
|
+
basePath: string,
|
|
37
|
+
milestoneId: string,
|
|
38
|
+
): Promise<string[]> {
|
|
39
|
+
const roadmapPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
|
|
40
|
+
if (!roadmapPath) return [];
|
|
41
|
+
|
|
42
|
+
const roadmapContent = await loadFile(roadmapPath);
|
|
43
|
+
if (!roadmapContent) return [];
|
|
44
|
+
|
|
45
|
+
const roadmap = parseRoadmap(roadmapContent);
|
|
46
|
+
const files = new Set<string>();
|
|
47
|
+
|
|
48
|
+
for (const slice of roadmap.slices) {
|
|
49
|
+
const planPath = resolveSliceFile(basePath, milestoneId, slice.id, "PLAN");
|
|
50
|
+
if (!planPath) continue;
|
|
51
|
+
|
|
52
|
+
const planContent = await loadFile(planPath);
|
|
53
|
+
if (!planContent) continue;
|
|
54
|
+
|
|
55
|
+
const plan = parsePlan(planContent);
|
|
56
|
+
for (const f of plan.filesLikelyTouched) {
|
|
57
|
+
files.add(f);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return [...files];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ─── Overlap Detection ──────────────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Compare file sets across milestones and return pairs with overlapping files.
|
|
68
|
+
*/
|
|
69
|
+
function detectFileOverlaps(
|
|
70
|
+
fileSets: Map<string, string[]>,
|
|
71
|
+
): Array<{ mid1: string; mid2: string; files: string[] }> {
|
|
72
|
+
const overlaps: Array<{ mid1: string; mid2: string; files: string[] }> = [];
|
|
73
|
+
const ids = [...fileSets.keys()];
|
|
74
|
+
|
|
75
|
+
for (let i = 0; i < ids.length; i++) {
|
|
76
|
+
const files1 = new Set(fileSets.get(ids[i])!);
|
|
77
|
+
for (let j = i + 1; j < ids.length; j++) {
|
|
78
|
+
const files2 = fileSets.get(ids[j])!;
|
|
79
|
+
const shared = files2.filter(f => files1.has(f));
|
|
80
|
+
if (shared.length > 0) {
|
|
81
|
+
overlaps.push({ mid1: ids[i], mid2: ids[j], files: shared.sort() });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return overlaps;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ─── Analysis ────────────────────────────────────────────────────────────────
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Analyze milestones for parallel execution eligibility.
|
|
93
|
+
*
|
|
94
|
+
* A milestone is eligible if:
|
|
95
|
+
* 1. It is not complete
|
|
96
|
+
* 2. Its dependencies (`dependsOn`) are all complete
|
|
97
|
+
* 3. It does not have file overlap with other eligible milestones
|
|
98
|
+
* (overlaps are flagged as warnings but do not disqualify)
|
|
99
|
+
*/
|
|
100
|
+
export async function analyzeParallelEligibility(
|
|
101
|
+
basePath: string,
|
|
102
|
+
): Promise<ParallelCandidates> {
|
|
103
|
+
const milestoneIds = findMilestoneIds(basePath);
|
|
104
|
+
const state = await deriveState(basePath);
|
|
105
|
+
const registry = state.registry;
|
|
106
|
+
|
|
107
|
+
// Build a lookup for quick status checks
|
|
108
|
+
const registryMap = new Map<string, MilestoneRegistryEntry>();
|
|
109
|
+
for (const entry of registry) {
|
|
110
|
+
registryMap.set(entry.id, entry);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const eligible: EligibilityResult[] = [];
|
|
114
|
+
const ineligible: EligibilityResult[] = [];
|
|
115
|
+
|
|
116
|
+
for (const mid of milestoneIds) {
|
|
117
|
+
const entry = registryMap.get(mid);
|
|
118
|
+
const title = entry?.title ?? mid;
|
|
119
|
+
const status = entry?.status ?? "pending";
|
|
120
|
+
|
|
121
|
+
// Rule 1: skip complete milestones
|
|
122
|
+
if (status === "complete") {
|
|
123
|
+
ineligible.push({
|
|
124
|
+
milestoneId: mid,
|
|
125
|
+
title,
|
|
126
|
+
eligible: false,
|
|
127
|
+
reason: "Already complete.",
|
|
128
|
+
});
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Rule 2: check dependency satisfaction
|
|
133
|
+
const deps = entry?.dependsOn ?? [];
|
|
134
|
+
const unsatisfied = deps.filter(dep => {
|
|
135
|
+
const depEntry = registryMap.get(dep);
|
|
136
|
+
return !depEntry || depEntry.status !== "complete";
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
if (unsatisfied.length > 0) {
|
|
140
|
+
ineligible.push({
|
|
141
|
+
milestoneId: mid,
|
|
142
|
+
title,
|
|
143
|
+
eligible: false,
|
|
144
|
+
reason: `Blocked by incomplete dependencies: ${unsatisfied.join(", ")}.`,
|
|
145
|
+
});
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
eligible.push({
|
|
150
|
+
milestoneId: mid,
|
|
151
|
+
title,
|
|
152
|
+
eligible: true,
|
|
153
|
+
reason: "All dependencies satisfied.",
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Rule 3: check file overlap among eligible milestones
|
|
158
|
+
const fileSets = new Map<string, string[]>();
|
|
159
|
+
for (const result of eligible) {
|
|
160
|
+
const files = await collectTouchedFiles(basePath, result.milestoneId);
|
|
161
|
+
fileSets.set(result.milestoneId, files);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const fileOverlaps = detectFileOverlaps(fileSets);
|
|
165
|
+
|
|
166
|
+
// Annotate eligible milestones that have file overlaps
|
|
167
|
+
const overlappingIds = new Set<string>();
|
|
168
|
+
for (const overlap of fileOverlaps) {
|
|
169
|
+
overlappingIds.add(overlap.mid1);
|
|
170
|
+
overlappingIds.add(overlap.mid2);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
for (const result of eligible) {
|
|
174
|
+
if (overlappingIds.has(result.milestoneId)) {
|
|
175
|
+
result.reason = "All dependencies satisfied. WARNING: has file overlap with another eligible milestone.";
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return { eligible, ineligible, fileOverlaps };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ─── Formatting ──────────────────────────────────────────────────────────────
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Produce a human-readable report of parallel eligibility analysis.
|
|
186
|
+
*/
|
|
187
|
+
export function formatEligibilityReport(candidates: ParallelCandidates): string {
|
|
188
|
+
const lines: string[] = [];
|
|
189
|
+
|
|
190
|
+
lines.push("# Parallel Eligibility Report");
|
|
191
|
+
lines.push("");
|
|
192
|
+
|
|
193
|
+
// Eligible milestones
|
|
194
|
+
lines.push(`## Eligible for Parallel Execution (${candidates.eligible.length})`);
|
|
195
|
+
lines.push("");
|
|
196
|
+
if (candidates.eligible.length === 0) {
|
|
197
|
+
lines.push("No milestones are currently eligible for parallel execution.");
|
|
198
|
+
} else {
|
|
199
|
+
for (const e of candidates.eligible) {
|
|
200
|
+
lines.push(`- **${e.milestoneId}** — ${e.title}`);
|
|
201
|
+
lines.push(` ${e.reason}`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
lines.push("");
|
|
205
|
+
|
|
206
|
+
// Ineligible milestones
|
|
207
|
+
lines.push(`## Ineligible (${candidates.ineligible.length})`);
|
|
208
|
+
lines.push("");
|
|
209
|
+
if (candidates.ineligible.length === 0) {
|
|
210
|
+
lines.push("All milestones are eligible.");
|
|
211
|
+
} else {
|
|
212
|
+
for (const e of candidates.ineligible) {
|
|
213
|
+
lines.push(`- **${e.milestoneId}** — ${e.title}`);
|
|
214
|
+
lines.push(` ${e.reason}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
lines.push("");
|
|
218
|
+
|
|
219
|
+
// File overlap warnings
|
|
220
|
+
if (candidates.fileOverlaps.length > 0) {
|
|
221
|
+
lines.push(`## File Overlap Warnings (${candidates.fileOverlaps.length})`);
|
|
222
|
+
lines.push("");
|
|
223
|
+
for (const overlap of candidates.fileOverlaps) {
|
|
224
|
+
lines.push(`- **${overlap.mid1}** <-> **${overlap.mid2}** — ${overlap.files.length} shared file(s):`);
|
|
225
|
+
for (const f of overlap.files) {
|
|
226
|
+
lines.push(` - \`${f}\``);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
lines.push("");
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return lines.join("\n");
|
|
233
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GSD Parallel Merge — Worktree reconciliation for parallel milestones.
|
|
3
|
+
*
|
|
4
|
+
* Handles merging completed milestone worktrees back to main branch
|
|
5
|
+
* with safety checks for parallel execution context.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { loadFile } from "./files.js";
|
|
9
|
+
import { resolveMilestoneFile } from "./paths.js";
|
|
10
|
+
import { mergeMilestoneToMain } from "./auto-worktree.js";
|
|
11
|
+
import { MergeConflictError } from "./git-service.js";
|
|
12
|
+
import { removeSessionStatus } from "./session-status-io.js";
|
|
13
|
+
import type { WorkerInfo } from "./parallel-orchestrator.js";
|
|
14
|
+
|
|
15
|
+
// ─── Types ─────────────────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
export interface MergeResult {
|
|
18
|
+
milestoneId: string;
|
|
19
|
+
success: boolean;
|
|
20
|
+
commitMessage?: string;
|
|
21
|
+
pushed?: boolean;
|
|
22
|
+
error?: string;
|
|
23
|
+
conflictFiles?: string[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type MergeOrder = "sequential" | "by-completion";
|
|
27
|
+
|
|
28
|
+
// ─── Merge Queue ───────────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Determine safe merge order for completed milestones.
|
|
32
|
+
* Sequential: merge in milestone ID order (M001 before M002).
|
|
33
|
+
* By-completion: merge in the order milestones finished.
|
|
34
|
+
*/
|
|
35
|
+
export function determineMergeOrder(
|
|
36
|
+
workers: WorkerInfo[],
|
|
37
|
+
order: MergeOrder = "sequential",
|
|
38
|
+
): string[] {
|
|
39
|
+
const completed = workers.filter(w => w.state === "stopped" && w.completedUnits > 0);
|
|
40
|
+
if (order === "by-completion") {
|
|
41
|
+
return completed
|
|
42
|
+
.sort((a, b) => a.startedAt - b.startedAt) // earliest first
|
|
43
|
+
.map(w => w.milestoneId);
|
|
44
|
+
}
|
|
45
|
+
return completed
|
|
46
|
+
.sort((a, b) => a.milestoneId.localeCompare(b.milestoneId))
|
|
47
|
+
.map(w => w.milestoneId);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Attempt to merge a single milestone's worktree back to main.
|
|
52
|
+
* Wraps mergeMilestoneToMain with error handling for parallel context.
|
|
53
|
+
*/
|
|
54
|
+
export async function mergeCompletedMilestone(
|
|
55
|
+
basePath: string,
|
|
56
|
+
milestoneId: string,
|
|
57
|
+
): Promise<MergeResult> {
|
|
58
|
+
try {
|
|
59
|
+
// Load the roadmap content (needed by mergeMilestoneToMain)
|
|
60
|
+
const roadmapPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
|
|
61
|
+
if (!roadmapPath) {
|
|
62
|
+
return {
|
|
63
|
+
milestoneId,
|
|
64
|
+
success: false,
|
|
65
|
+
error: `No roadmap found for ${milestoneId}`,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const roadmapContent = await loadFile(roadmapPath);
|
|
70
|
+
if (!roadmapContent) {
|
|
71
|
+
return {
|
|
72
|
+
milestoneId,
|
|
73
|
+
success: false,
|
|
74
|
+
error: `Could not read roadmap for ${milestoneId}`,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Attempt the merge
|
|
79
|
+
const result = mergeMilestoneToMain(basePath, milestoneId, roadmapContent);
|
|
80
|
+
|
|
81
|
+
// Clean up parallel session status
|
|
82
|
+
removeSessionStatus(basePath, milestoneId);
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
milestoneId,
|
|
86
|
+
success: true,
|
|
87
|
+
commitMessage: result.commitMessage,
|
|
88
|
+
pushed: result.pushed,
|
|
89
|
+
};
|
|
90
|
+
} catch (err) {
|
|
91
|
+
if (err instanceof MergeConflictError) {
|
|
92
|
+
return {
|
|
93
|
+
milestoneId,
|
|
94
|
+
success: false,
|
|
95
|
+
error: `Merge conflict: ${err.conflictedFiles.length} conflicting file(s)`,
|
|
96
|
+
conflictFiles: err.conflictedFiles,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
milestoneId,
|
|
101
|
+
success: false,
|
|
102
|
+
error: err instanceof Error ? err.message : String(err),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Merge all completed milestones in sequence.
|
|
109
|
+
* Stops on first conflict and returns results so far.
|
|
110
|
+
*/
|
|
111
|
+
export async function mergeAllCompleted(
|
|
112
|
+
basePath: string,
|
|
113
|
+
workers: WorkerInfo[],
|
|
114
|
+
order: MergeOrder = "sequential",
|
|
115
|
+
): Promise<MergeResult[]> {
|
|
116
|
+
const mergeOrder = determineMergeOrder(workers, order);
|
|
117
|
+
const results: MergeResult[] = [];
|
|
118
|
+
|
|
119
|
+
for (const mid of mergeOrder) {
|
|
120
|
+
const result = await mergeCompletedMilestone(basePath, mid);
|
|
121
|
+
results.push(result);
|
|
122
|
+
|
|
123
|
+
// Stop on first conflict — later merges may depend on this one
|
|
124
|
+
if (!result.success && result.conflictFiles) {
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return results;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Format merge results for display.
|
|
134
|
+
*/
|
|
135
|
+
export function formatMergeResults(results: MergeResult[]): string {
|
|
136
|
+
if (results.length === 0) return "No completed milestones to merge.";
|
|
137
|
+
|
|
138
|
+
const lines: string[] = ["# Merge Results\n"];
|
|
139
|
+
|
|
140
|
+
for (const r of results) {
|
|
141
|
+
if (r.success) {
|
|
142
|
+
const pushStatus = r.pushed ? " (pushed)" : "";
|
|
143
|
+
lines.push(`- **${r.milestoneId}** — merged successfully${pushStatus}`);
|
|
144
|
+
} else if (r.conflictFiles) {
|
|
145
|
+
lines.push(`- **${r.milestoneId}** — CONFLICT (${r.conflictFiles.length} file(s)):`);
|
|
146
|
+
for (const f of r.conflictFiles) {
|
|
147
|
+
lines.push(` - \`${f}\``);
|
|
148
|
+
}
|
|
149
|
+
lines.push(` Resolve conflicts manually and run \`/gsd parallel merge ${r.milestoneId}\` to retry.`);
|
|
150
|
+
} else {
|
|
151
|
+
lines.push(`- **${r.milestoneId}** — failed: ${r.error}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return lines.join("\n");
|
|
156
|
+
}
|