gsd-pi 0.2.9 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/README.md +23 -0
  2. package/dist/cli.js +47 -5
  3. package/dist/wizard.js +2 -1
  4. package/package.json +1 -1
  5. package/src/resources/extensions/gsd/commands.ts +9 -3
  6. package/src/resources/extensions/gsd/dashboard-overlay.ts +6 -1
  7. package/src/resources/extensions/gsd/files.ts +7 -7
  8. package/src/resources/extensions/gsd/gitignore.ts +1 -0
  9. package/src/resources/extensions/gsd/index.ts +36 -1
  10. package/src/resources/extensions/gsd/migrate/command.ts +215 -0
  11. package/src/resources/extensions/gsd/migrate/index.ts +42 -0
  12. package/src/resources/extensions/gsd/migrate/parser.ts +323 -0
  13. package/src/resources/extensions/gsd/migrate/parsers.ts +624 -0
  14. package/src/resources/extensions/gsd/migrate/preview.ts +48 -0
  15. package/src/resources/extensions/gsd/migrate/transformer.ts +346 -0
  16. package/src/resources/extensions/gsd/migrate/types.ts +370 -0
  17. package/src/resources/extensions/gsd/migrate/validator.ts +53 -0
  18. package/src/resources/extensions/gsd/migrate/writer.ts +539 -0
  19. package/src/resources/extensions/gsd/prompts/review-migration.md +66 -0
  20. package/src/resources/extensions/gsd/prompts/worktree-merge.md +89 -0
  21. package/src/resources/extensions/gsd/tests/migrate-command.test.ts +390 -0
  22. package/src/resources/extensions/gsd/tests/migrate-parser.test.ts +786 -0
  23. package/src/resources/extensions/gsd/tests/migrate-transformer.test.ts +657 -0
  24. package/src/resources/extensions/gsd/tests/migrate-validator-parsers.test.ts +443 -0
  25. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +318 -0
  26. package/src/resources/extensions/gsd/tests/migrate-writer.test.ts +420 -0
  27. package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +160 -0
  28. package/src/resources/extensions/gsd/worktree-command.ts +527 -0
  29. package/src/resources/extensions/gsd/worktree-manager.ts +302 -0
@@ -0,0 +1,539 @@
1
+ // GSD Directory Writer — Format Functions & Directory Orchestrator
2
+ // Format functions: pure string-returning functions that serialize GSD types into the exact markdown
3
+ // format that GSD-2's parsers expect (parseRoadmap, parsePlan, parseSummary, parseRequirementCounts).
4
+ // writeGSDDirectory: orchestrator that writes a complete .gsd directory tree from a GSDProject.
5
+
6
+ import { join } from 'node:path';
7
+ import { saveFile } from '../files.ts';
8
+
9
+ import type {
10
+ GSDMilestone,
11
+ GSDSlice,
12
+ GSDTask,
13
+ GSDRequirement,
14
+ GSDProject,
15
+ } from './types.ts';
16
+
17
+ // ─── Types ─────────────────────────────────────────────────────────────────
18
+
19
+ /** Result of writeGSDDirectory — lists all files that were written. */
20
+ export interface WrittenFiles {
21
+ /** Absolute paths of all files written */
22
+ paths: string[];
23
+ /** Count by category */
24
+ counts: {
25
+ roadmaps: number;
26
+ plans: number;
27
+ taskPlans: number;
28
+ taskSummaries: number;
29
+ sliceSummaries: number;
30
+ research: number;
31
+ requirements: number;
32
+ contexts: number;
33
+ other: number;
34
+ };
35
+ }
36
+
37
+ /** Pre-write statistics computed from a GSDProject without I/O. */
38
+ export interface MigrationPreview {
39
+ milestoneCount: number;
40
+ totalSlices: number;
41
+ totalTasks: number;
42
+ doneSlices: number;
43
+ doneTasks: number;
44
+ sliceCompletionPct: number;
45
+ taskCompletionPct: number;
46
+ requirements: {
47
+ active: number;
48
+ validated: number;
49
+ deferred: number;
50
+ outOfScope: number;
51
+ total: number;
52
+ };
53
+ }
54
+
55
+ // ─── Local Helpers ─────────────────────────────────────────────────────────
56
+
57
+ /**
58
+ * Serialize a flat key-value map into YAML frontmatter block.
59
+ * Matches parseFrontmatterMap() expectations:
60
+ * - Scalars: `key: value`
61
+ * - Arrays of strings: `key:\n - item`
62
+ * - Empty arrays: `key: []`
63
+ * - Arrays of objects: `key:\n - field1: val\n field2: val`
64
+ * - Boolean: `key: true/false`
65
+ */
66
+ function serializeFrontmatter(data: Record<string, unknown>): string {
67
+ const lines: string[] = ['---'];
68
+
69
+ for (const [key, value] of Object.entries(data)) {
70
+ if (value === undefined || value === null) continue;
71
+
72
+ if (typeof value === 'boolean') {
73
+ lines.push(`${key}: ${value}`);
74
+ } else if (typeof value === 'string' || typeof value === 'number') {
75
+ lines.push(`${key}: ${value}`);
76
+ } else if (Array.isArray(value)) {
77
+ if (value.length === 0) {
78
+ lines.push(`${key}: []`);
79
+ } else if (typeof value[0] === 'object' && value[0] !== null) {
80
+ // Array of objects
81
+ lines.push(`${key}:`);
82
+ for (const obj of value) {
83
+ const entries = Object.entries(obj as Record<string, string>);
84
+ if (entries.length > 0) {
85
+ lines.push(` - ${entries[0][0]}: ${entries[0][1]}`);
86
+ for (let i = 1; i < entries.length; i++) {
87
+ lines.push(` ${entries[i][0]}: ${entries[i][1]}`);
88
+ }
89
+ }
90
+ }
91
+ } else {
92
+ // Array of scalars
93
+ lines.push(`${key}:`);
94
+ for (const item of value) {
95
+ lines.push(` - ${item}`);
96
+ }
97
+ }
98
+ }
99
+ }
100
+
101
+ lines.push('---');
102
+ return lines.join('\n');
103
+ }
104
+
105
+ // ─── Format Functions ──────────────────────────────────────────────────────
106
+
107
+ /**
108
+ * Format a milestone's ROADMAP.md content.
109
+ * Output must parse correctly through parseRoadmap().
110
+ */
111
+ export function formatRoadmap(milestone: GSDMilestone): string {
112
+ const lines: string[] = [];
113
+
114
+ lines.push(`# ${milestone.id}: ${milestone.title}`);
115
+ lines.push('');
116
+ lines.push(`**Vision:** ${milestone.vision || '(migrated project)'}`);
117
+ lines.push('');
118
+
119
+ lines.push('## Success Criteria');
120
+ lines.push('');
121
+ if (milestone.successCriteria.length > 0) {
122
+ for (const criterion of milestone.successCriteria) {
123
+ lines.push(`- ${criterion}`);
124
+ }
125
+ }
126
+ lines.push('');
127
+
128
+ lines.push('## Slices');
129
+ lines.push('');
130
+ for (const slice of milestone.slices) {
131
+ const check = slice.done ? 'x' : ' ';
132
+ const depsStr = slice.depends.length > 0 ? slice.depends.join(', ') : '';
133
+ lines.push(`- [${check}] **${slice.id}: ${slice.title}** \`risk:${slice.risk}\` \`depends:[${depsStr}]\``);
134
+ if (slice.demo) {
135
+ lines.push(` > After this: ${slice.demo}`);
136
+ }
137
+ }
138
+
139
+ // Skip Boundary Map section entirely per D004
140
+
141
+ return lines.join('\n') + '\n';
142
+ }
143
+
144
+ /**
145
+ * Format a slice's PLAN.md (S01-PLAN.md).
146
+ * Output must parse correctly through parsePlan().
147
+ */
148
+ export function formatPlan(slice: GSDSlice): string {
149
+ const lines: string[] = [];
150
+
151
+ lines.push(`# ${slice.id}: ${slice.title}`);
152
+ lines.push('');
153
+ lines.push(`**Goal:** ${slice.goal || slice.title}`);
154
+ lines.push(`**Demo:** ${slice.demo || slice.title}`);
155
+ lines.push('');
156
+
157
+ lines.push('## Must-Haves');
158
+ lines.push('');
159
+ // No must-haves in migrated data — empty section
160
+ lines.push('');
161
+
162
+ lines.push('## Tasks');
163
+ lines.push('');
164
+ for (const task of slice.tasks) {
165
+ const check = task.done ? 'x' : ' ';
166
+ const estPart = task.estimate ? ` \`est:${task.estimate}\`` : '';
167
+ lines.push(`- [${check}] **${task.id}: ${task.title}**${estPart}`);
168
+ if (task.description) {
169
+ lines.push(` - ${task.description}`);
170
+ }
171
+ }
172
+ lines.push('');
173
+
174
+ lines.push('## Files Likely Touched');
175
+ lines.push('');
176
+ for (const task of slice.tasks) {
177
+ for (const file of task.files) {
178
+ lines.push(`- \`${file}\``);
179
+ }
180
+ }
181
+
182
+ return lines.join('\n') + '\n';
183
+ }
184
+
185
+ /**
186
+ * Format a slice summary (S01-SUMMARY.md).
187
+ * Output must parse correctly through parseSummary().
188
+ */
189
+ export function formatSliceSummary(slice: GSDSlice, milestoneId: string): string {
190
+ if (!slice.summary) return '';
191
+
192
+ const s = slice.summary;
193
+ const fm = serializeFrontmatter({
194
+ id: slice.id,
195
+ parent: milestoneId,
196
+ milestone: milestoneId,
197
+ provides: s.provides,
198
+ requires: [],
199
+ affects: [],
200
+ key_files: s.keyFiles,
201
+ key_decisions: s.keyDecisions,
202
+ patterns_established: s.patternsEstablished,
203
+ observability_surfaces: [],
204
+ drill_down_paths: [],
205
+ duration: s.duration || '',
206
+ verification_result: 'passed',
207
+ completed_at: s.completedAt || '',
208
+ blocker_discovered: false,
209
+ });
210
+
211
+ const body = [
212
+ '',
213
+ `# ${slice.id}: ${slice.title}`,
214
+ '',
215
+ `**${s.whatHappened ? s.whatHappened.split('\n')[0] : 'Migrated from legacy format'}**`,
216
+ '',
217
+ '## What Happened',
218
+ '',
219
+ s.whatHappened || 'Migrated from legacy planning format.',
220
+ ];
221
+
222
+ return fm + body.join('\n') + '\n';
223
+ }
224
+
225
+ /**
226
+ * Format a task summary (T01-SUMMARY.md).
227
+ * Output must parse correctly through parseSummary().
228
+ */
229
+ export function formatTaskSummary(task: GSDTask, sliceId: string, milestoneId: string): string {
230
+ if (!task.summary) return '';
231
+
232
+ const s = task.summary;
233
+ const fm = serializeFrontmatter({
234
+ id: task.id,
235
+ parent: sliceId,
236
+ milestone: milestoneId,
237
+ provides: s.provides,
238
+ requires: [],
239
+ affects: [],
240
+ key_files: s.keyFiles,
241
+ key_decisions: [],
242
+ patterns_established: [],
243
+ observability_surfaces: [],
244
+ drill_down_paths: [],
245
+ duration: s.duration || '',
246
+ verification_result: 'passed',
247
+ completed_at: s.completedAt || '',
248
+ blocker_discovered: false,
249
+ });
250
+
251
+ const body = [
252
+ '',
253
+ `# ${task.id}: ${task.title}`,
254
+ '',
255
+ `**${s.whatHappened ? s.whatHappened.split('\n')[0] : 'Migrated from legacy format'}**`,
256
+ '',
257
+ '## What Happened',
258
+ '',
259
+ s.whatHappened || 'Migrated from legacy planning format.',
260
+ ];
261
+
262
+ return fm + body.join('\n') + '\n';
263
+ }
264
+
265
+ /**
266
+ * Format a task plan (T01-PLAN.md).
267
+ * deriveState() only checks for file existence, not content.
268
+ * Keep it minimal but valid markdown.
269
+ */
270
+ export function formatTaskPlan(task: GSDTask, sliceId: string, milestoneId: string): string {
271
+ const lines: string[] = [];
272
+ lines.push(`# ${task.id}: ${task.title}`);
273
+ lines.push('');
274
+ lines.push(`**Slice:** ${sliceId} — **Milestone:** ${milestoneId}`);
275
+ lines.push('');
276
+ lines.push('## Description');
277
+ lines.push('');
278
+ lines.push(task.description || 'Migrated from legacy planning format.');
279
+ lines.push('');
280
+
281
+ if (task.mustHaves.length > 0) {
282
+ lines.push('## Must-Haves');
283
+ lines.push('');
284
+ for (const mh of task.mustHaves) {
285
+ lines.push(`- [ ] ${mh}`);
286
+ }
287
+ lines.push('');
288
+ }
289
+
290
+ if (task.files.length > 0) {
291
+ lines.push('## Files');
292
+ lines.push('');
293
+ for (const f of task.files) {
294
+ lines.push(`- \`${f}\``);
295
+ }
296
+ lines.push('');
297
+ }
298
+
299
+ return lines.join('\n');
300
+ }
301
+
302
+ /**
303
+ * Format REQUIREMENTS.md grouped by status.
304
+ * Output must parse correctly through parseRequirementCounts().
305
+ * parseRequirementCounts expects: ## Active/## Validated/## Deferred/## Out of Scope sections
306
+ * with ### R001 — Title headings under each section.
307
+ */
308
+ export function formatRequirements(requirements: GSDRequirement[]): string {
309
+ const lines: string[] = [];
310
+ lines.push('# Requirements');
311
+ lines.push('');
312
+
313
+ const groups: Record<string, GSDRequirement[]> = {
314
+ active: [],
315
+ validated: [],
316
+ deferred: [],
317
+ 'out-of-scope': [],
318
+ };
319
+
320
+ for (const req of requirements) {
321
+ const status = req.status.toLowerCase();
322
+ if (status in groups) {
323
+ groups[status].push(req);
324
+ } else {
325
+ groups.active.push(req);
326
+ }
327
+ }
328
+
329
+ const sectionMap: [string, string][] = [
330
+ ['active', 'Active'],
331
+ ['validated', 'Validated'],
332
+ ['deferred', 'Deferred'],
333
+ ['out-of-scope', 'Out of Scope'],
334
+ ];
335
+
336
+ for (const [key, heading] of sectionMap) {
337
+ lines.push(`## ${heading}`);
338
+ lines.push('');
339
+ for (const req of groups[key]) {
340
+ lines.push(`### ${req.id} — ${req.title}`);
341
+ lines.push('');
342
+ lines.push(`- Status: ${req.status}`);
343
+ lines.push(`- Class: ${req.class}`);
344
+ lines.push(`- Source: ${req.source}`);
345
+ lines.push(`- Primary Slice: ${req.primarySlice}`);
346
+ lines.push('');
347
+ if (req.description) {
348
+ lines.push(req.description);
349
+ lines.push('');
350
+ }
351
+ }
352
+ }
353
+
354
+ return lines.join('\n');
355
+ }
356
+
357
+ // ─── Passthrough Format Helpers ────────────────────────────────────────────
358
+
359
+ /**
360
+ * Format PROJECT.md content.
361
+ * If content is empty, produce a minimal valid stub.
362
+ */
363
+ export function formatProject(content: string): string {
364
+ if (!content || !content.trim()) {
365
+ return '# Project\n\n(Migrated project — no description available.)\n';
366
+ }
367
+ return content.endsWith('\n') ? content : content + '\n';
368
+ }
369
+
370
+ /**
371
+ * Format DECISIONS.md content.
372
+ * If content is empty, produce the standard header.
373
+ */
374
+ export function formatDecisions(content: string): string {
375
+ if (!content || !content.trim()) {
376
+ return '# Decisions\n\n<!-- Append-only register of architectural and pattern decisions -->\n\n| ID | Decision | Rationale | Date |\n|----|----------|-----------|------|\n';
377
+ }
378
+ return content.endsWith('\n') ? content : content + '\n';
379
+ }
380
+
381
+ /**
382
+ * Format a milestone CONTEXT.md.
383
+ * Minimal context with no depends — migrated milestones have no upstream dependencies.
384
+ */
385
+ export function formatContext(milestoneId: string): string {
386
+ return `# ${milestoneId} Context\n\nMigrated milestone — no upstream dependencies.\n`;
387
+ }
388
+
389
+ /**
390
+ * Format STATE.md.
391
+ * deriveState() does not read STATE.md — it recomputes from scratch.
392
+ * Write a minimal stub that will be overwritten on first /gsd status.
393
+ */
394
+ export function formatState(milestones: GSDMilestone[]): string {
395
+ const lines: string[] = [];
396
+ lines.push('# GSD State');
397
+ lines.push('');
398
+ lines.push('<!-- Auto-generated. Updated by deriveState(). -->');
399
+ lines.push('');
400
+ for (const m of milestones) {
401
+ const doneSlices = m.slices.filter(s => s.done).length;
402
+ const totalSlices = m.slices.length;
403
+ lines.push(`## ${m.id}: ${m.title}`);
404
+ lines.push('');
405
+ lines.push(`- Slices: ${doneSlices}/${totalSlices}`);
406
+ lines.push('');
407
+ }
408
+ return lines.join('\n');
409
+ }
410
+
411
+ // ─── Directory Writer Orchestrator ─────────────────────────────────────────
412
+
413
+ /**
414
+ * Write a complete .gsd directory tree from a GSDProject.
415
+ * Iterates milestones → slices → tasks, calls format functions,
416
+ * and writes each file via saveFile(). Returns a manifest of written paths.
417
+ *
418
+ * Skips research/summary files when null (does not write empty stubs).
419
+ */
420
+ export async function writeGSDDirectory(
421
+ project: GSDProject,
422
+ targetPath: string,
423
+ ): Promise<WrittenFiles> {
424
+ const gsdDir = join(targetPath, '.gsd');
425
+ const milestonesBase = join(gsdDir, 'milestones');
426
+ const paths: string[] = [];
427
+ const counts: WrittenFiles['counts'] = {
428
+ roadmaps: 0,
429
+ plans: 0,
430
+ taskPlans: 0,
431
+ taskSummaries: 0,
432
+ sliceSummaries: 0,
433
+ research: 0,
434
+ requirements: 0,
435
+ contexts: 0,
436
+ other: 0,
437
+ };
438
+
439
+ // Root-level files
440
+ const projectPath = join(gsdDir, 'PROJECT.md');
441
+ await saveFile(projectPath, formatProject(project.projectContent));
442
+ paths.push(projectPath);
443
+ counts.other++;
444
+
445
+ const decisionsPath = join(gsdDir, 'DECISIONS.md');
446
+ await saveFile(decisionsPath, formatDecisions(project.decisionsContent));
447
+ paths.push(decisionsPath);
448
+ counts.other++;
449
+
450
+ const statePath = join(gsdDir, 'STATE.md');
451
+ await saveFile(statePath, formatState(project.milestones));
452
+ paths.push(statePath);
453
+ counts.other++;
454
+
455
+ if (project.requirements.length > 0) {
456
+ const reqPath = join(gsdDir, 'REQUIREMENTS.md');
457
+ await saveFile(reqPath, formatRequirements(project.requirements));
458
+ paths.push(reqPath);
459
+ counts.requirements++;
460
+ }
461
+
462
+ // Milestones
463
+ for (const milestone of project.milestones) {
464
+ const mDir = join(milestonesBase, milestone.id);
465
+
466
+ // Roadmap (always written, even for empty milestones)
467
+ const roadmapPath = join(mDir, `${milestone.id}-ROADMAP.md`);
468
+ await saveFile(roadmapPath, formatRoadmap(milestone));
469
+ paths.push(roadmapPath);
470
+ counts.roadmaps++;
471
+
472
+ // Context
473
+ const contextPath = join(mDir, `${milestone.id}-CONTEXT.md`);
474
+ await saveFile(contextPath, formatContext(milestone.id));
475
+ paths.push(contextPath);
476
+ counts.contexts++;
477
+
478
+ // Research (skip if null)
479
+ if (milestone.research !== null) {
480
+ const researchPath = join(mDir, `${milestone.id}-RESEARCH.md`);
481
+ await saveFile(researchPath, milestone.research);
482
+ paths.push(researchPath);
483
+ counts.research++;
484
+ }
485
+
486
+ // Slices
487
+ for (const slice of milestone.slices) {
488
+ const sDir = join(mDir, 'slices', slice.id);
489
+ const tasksDir = join(sDir, 'tasks');
490
+
491
+ // Slice plan
492
+ const planPath = join(sDir, `${slice.id}-PLAN.md`);
493
+ await saveFile(planPath, formatPlan(slice));
494
+ paths.push(planPath);
495
+ counts.plans++;
496
+
497
+ // Slice research (skip if null)
498
+ if (slice.research !== null) {
499
+ const sliceResearchPath = join(sDir, `${slice.id}-RESEARCH.md`);
500
+ await saveFile(sliceResearchPath, slice.research);
501
+ paths.push(sliceResearchPath);
502
+ counts.research++;
503
+ }
504
+
505
+ // Slice summary (skip if null)
506
+ if (slice.summary !== null) {
507
+ const summaryContent = formatSliceSummary(slice, milestone.id);
508
+ if (summaryContent) {
509
+ const summaryPath = join(sDir, `${slice.id}-SUMMARY.md`);
510
+ await saveFile(summaryPath, summaryContent);
511
+ paths.push(summaryPath);
512
+ counts.sliceSummaries++;
513
+ }
514
+ }
515
+
516
+ // Tasks
517
+ for (const task of slice.tasks) {
518
+ // Task plan (always written)
519
+ const taskPlanPath = join(tasksDir, `${task.id}-PLAN.md`);
520
+ await saveFile(taskPlanPath, formatTaskPlan(task, slice.id, milestone.id));
521
+ paths.push(taskPlanPath);
522
+ counts.taskPlans++;
523
+
524
+ // Task summary (skip if null)
525
+ if (task.summary !== null) {
526
+ const taskSummaryContent = formatTaskSummary(task, slice.id, milestone.id);
527
+ if (taskSummaryContent) {
528
+ const taskSummaryPath = join(tasksDir, `${task.id}-SUMMARY.md`);
529
+ await saveFile(taskSummaryPath, taskSummaryContent);
530
+ paths.push(taskSummaryPath);
531
+ counts.taskSummaries++;
532
+ }
533
+ }
534
+ }
535
+ }
536
+ }
537
+
538
+ return { paths, counts };
539
+ }
@@ -0,0 +1,66 @@
1
+ ## Review Migrated .gsd Directory
2
+
3
+ A `/gsd migrate` command just wrote a `.gsd/` directory from an old `.planning` source. Your job is to audit the output and verify it meets GSD-2 standards before the user starts working with it.
4
+
5
+ ### Source
6
+ - Old `.planning` directory: `{{sourcePath}}`
7
+ - Written `.gsd` directory: `{{gsdPath}}`
8
+
9
+ ### Migration Stats
10
+ {{previewStats}}
11
+
12
+ ### Review Checklist
13
+
14
+ Work through each check. Report PASS/FAIL with specifics. Fix anything fixable in-place.
15
+
16
+ #### 1. Structure Validation
17
+ - Run `deriveState()` on the `.gsd` directory (import from `state.ts`, pass the **project root** as basePath)
18
+ - Confirm it returns a coherent phase (not `pre-planning` unless the project is truly empty)
19
+ - Confirm activeMilestone, activeSlice, activeTask are sensible for the project's completion state
20
+ - Confirm progress counts match the migration preview stats
21
+
22
+ #### 2. Roadmap Quality
23
+ - Read `M001-ROADMAP.md` (and any other milestone roadmaps)
24
+ - Confirm slice entries have meaningful titles (not file paths or garbled text)
25
+ - Confirm `[x]`/`[ ]` completion markers are correct relative to the old roadmap
26
+ - Confirm vision statement is present and meaningful (not empty or "Migration")
27
+
28
+ #### 3. Content Spot-Check
29
+ - Pick 2-3 slices with the most tasks and read their plan files
30
+ - Confirm task titles and descriptions carry over meaningfully from the old plans
31
+ - Confirm summary files exist for completed tasks and contain relevant content
32
+ - Check that research files (if present) contain consolidated content, not empty stubs
33
+
34
+ #### 4. Requirements (if any)
35
+ - Read REQUIREMENTS.md
36
+ - Confirm requirement IDs are present and non-duplicate
37
+ - Confirm statuses make sense: completed old requirements should be `validated`, in-progress should be `active`
38
+
39
+ #### 5. PROJECT.md
40
+ - Read the written PROJECT.md
41
+ - Confirm it contains the old project's description, not boilerplate
42
+ - Confirm it reads like a useful project summary
43
+
44
+ #### 6. Decisions
45
+ - If DECISIONS.md was written, confirm it contains extracted decisions from old summaries (or is empty if no decisions existed)
46
+
47
+ ### Output Format
48
+
49
+ Summarize your findings as:
50
+
51
+ ```
52
+ Migration Review: <project name>
53
+ ================================
54
+ Structure: PASS/FAIL — <details>
55
+ Roadmap: PASS/FAIL — <details>
56
+ Content: PASS/FAIL — <details>
57
+ Requirements: PASS/FAIL/SKIP — <details>
58
+ Project: PASS/FAIL — <details>
59
+ Decisions: PASS/FAIL/SKIP — <details>
60
+
61
+ Overall: PASS / PASS WITH NOTES / FAIL
62
+ Issues: <list any problems found>
63
+ Fixes applied: <list any in-place fixes made>
64
+ ```
65
+
66
+ If the overall result is FAIL, explain what needs manual attention. If PASS WITH NOTES, explain what's imperfect but acceptable. If PASS, confirm the `.gsd` directory is ready for GSD-2 auto-mode.
@@ -0,0 +1,89 @@
1
+ You are merging GSD artifacts from worktree **{{worktreeName}}** (branch `{{worktreeBranch}}`) into target branch `{{mainBranch}}`.
2
+
3
+ ## Context
4
+
5
+ The worktree was created as a parallel workspace. It may contain new milestones, updated roadmaps, new plans, research, decisions, or other GSD artifacts that need to be reconciled with the main branch.
6
+
7
+ ### Commit History (worktree)
8
+
9
+ ```
10
+ {{commitLog}}
11
+ ```
12
+
13
+ ### GSD Artifact Changes
14
+
15
+ **Added files:**
16
+ {{addedFiles}}
17
+
18
+ **Modified files:**
19
+ {{modifiedFiles}}
20
+
21
+ **Removed files:**
22
+ {{removedFiles}}
23
+
24
+ ### Full Diff
25
+
26
+ ```diff
27
+ {{fullDiff}}
28
+ ```
29
+
30
+ ## Your Task
31
+
32
+ Analyze the changes and guide the merge. Follow these steps exactly:
33
+
34
+ ### Step 1: Categorize Changes
35
+
36
+ Classify each changed GSD artifact:
37
+ - **New milestones** — entirely new M###/ directories with roadmaps
38
+ - **New slices/tasks** — new planning artifacts within existing milestones
39
+ - **Updated roadmaps** — modifications to existing M###-ROADMAP.md files
40
+ - **Updated plans** — modifications to existing slice or task plans
41
+ - **Research/context** — new or updated RESEARCH.md, CONTEXT.md files
42
+ - **Decisions** — changes to DECISIONS.md
43
+ - **Requirements** — changes to REQUIREMENTS.md
44
+ - **Other** — anything else
45
+
46
+ ### Step 2: Conflict Assessment
47
+
48
+ For each **modified** file, check whether the main branch version has also changed since the worktree branched off. Flag any files where both branches have diverged — these need manual reconciliation.
49
+
50
+ Read the current main-branch version of each modified file and compare it against both the worktree version and the common ancestor to identify:
51
+ - **Clean merges** — main hasn't changed, worktree changes can apply directly
52
+ - **Conflicts** — both branches changed the same file; needs reconciliation
53
+ - **Stale changes** — worktree modified a file that main has since replaced or removed
54
+
55
+ ### Step 3: Merge Strategy
56
+
57
+ Present a merge plan to the user:
58
+
59
+ 1. For **clean merges**: list files that will merge without conflict
60
+ 2. For **conflicts**: show both versions side-by-side and propose a reconciled version
61
+ 3. For **new artifacts**: confirm they should be added to the main branch
62
+ 4. For **removed artifacts**: confirm the removals are intentional
63
+
64
+ Ask the user to confirm the merge plan before proceeding.
65
+
66
+ ### Step 4: Execute Merge
67
+
68
+ Once confirmed:
69
+
70
+ 1. If there are conflicts requiring manual reconciliation, apply the reconciled versions to the main branch working tree
71
+ 2. Run `git merge --squash {{worktreeBranch}}` to bring in all changes
72
+ 3. Review the staged changes — if any reconciled files need adjustment, apply them now
73
+ 4. Commit with message: `merge(worktree/{{worktreeName}}): <summary of what was merged>`
74
+ 5. Report what was merged
75
+
76
+ ### Step 5: Cleanup Prompt
77
+
78
+ After a successful merge, ask the user whether to:
79
+ - **Remove the worktree** — delete `.gsd/worktrees/{{worktreeName}}/` and the `{{worktreeBranch}}` branch
80
+ - **Keep the worktree** — leave it for continued parallel work
81
+
82
+ If the user chooses to remove it, run `/worktree remove {{worktreeName}}`.
83
+
84
+ ## Important
85
+
86
+ - Never silently discard changes from either branch
87
+ - When in doubt about a conflict, show both versions and ask the user
88
+ - Preserve all GSD artifact formatting conventions (frontmatter, section structure, checkbox states)
89
+ - If the worktree introduced new milestone IDs that conflict with main, flag this immediately