@snipcodeit/mgw 0.1.1 → 0.1.2

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.
@@ -68,1713 +68,116 @@ fi
68
68
  </step>
69
69
 
70
70
  <step name="align_from_gsd">
71
- **Align MGW state from existing GSD artifacts (STATE_CLASS = GSD-Only or GSD-Mid-Exec):**
72
-
73
- Spawn alignment-analyzer agent:
74
-
75
- Task(
76
- description="Analyze GSD state for alignment",
77
- subagent_type="general-purpose",
78
- prompt="
79
- <files_to_read>
80
- - ./CLAUDE.md
81
- - .planning/PROJECT.md (if exists)
82
- - .planning/ROADMAP.md (if exists)
83
- - .planning/MILESTONES.md (if exists)
84
- - .planning/STATE.md (if exists)
85
- </files_to_read>
86
-
87
- Analyze existing GSD project state and produce an alignment report.
88
-
89
- Read each file that exists. Extract:
90
- - Project name and description from PROJECT.md (H1 heading, description paragraph)
91
- - Active milestone: from ROADMAP.md header or STATE.md current milestone name
92
- - Archived milestones: from MILESTONES.md — list each milestone with name and phase count
93
- - Phases per milestone: from ROADMAP.md sections (### Phase N:) and MILESTONES.md
94
-
95
- For each milestone found:
96
- - name: milestone name string
97
- - source: 'ROADMAP' (if from current ROADMAP.md) or 'MILESTONES' (if archived)
98
- - state: 'active' (ROADMAP source), 'completed' (archived in MILESTONES.md), 'planned' (referenced but not yet created)
99
- - phases: array of { number, name, status } objects
100
-
101
- <output>
102
- Write JSON to .mgw/alignment-report.json:
103
- {
104
- \"project_name\": \"extracted from PROJECT.md\",
105
- \"project_description\": \"extracted from PROJECT.md\",
106
- \"milestones\": [
107
- {
108
- \"name\": \"milestone name\",
109
- \"source\": \"ROADMAP|MILESTONES\",
110
- \"state\": \"active|completed|planned\",
111
- \"phases\": [{ \"number\": N, \"name\": \"...\", \"status\": \"...\" }]
112
- }
113
- ],
114
- \"active_milestone\": \"name of currently active milestone or null\",
115
- \"total_phases\": N,
116
- \"total_issues_estimated\": N
117
- }
118
- </output>
119
- "
120
- )
121
-
122
- After agent completes:
123
- 1. Read .mgw/alignment-report.json
124
- 2. Display alignment summary to user:
125
- - Project: {project_name}
126
- - Milestones found: {count} ({active_milestone} active, N completed)
127
- - Phases: {total_phases} total, ~{total_issues_estimated} issues estimated
128
- 3. Ask: "Import this GSD state into MGW? This will create GitHub milestones and issues, and build project.json. (Y/N)"
129
- 4. If Y: proceed to step milestone_mapper
130
- 5. If N: exit with message "Run /mgw:project again when ready to import."
71
+ @workflows/align-from-gsd.md
131
72
  </step>
132
73
 
133
74
  <step name="milestone_mapper">
134
- **Map GSD milestones to GitHub milestones:**
135
-
136
- Read .mgw/alignment-report.json produced by the alignment-analyzer agent.
137
-
138
- ```bash
139
- ALIGNMENT=$(python3 -c "
140
- import json
141
- with open('.mgw/alignment-report.json') as f:
142
- data = json.load(f)
143
- print(json.dumps(data))
144
- ")
145
- ```
146
-
147
- For each milestone in the alignment report:
148
- 1. Check if a GitHub milestone with a matching title already exists:
149
- ```bash
150
- gh api repos/${REPO}/milestones --jq '.[].title'
151
- ```
152
- 2. If not found: create it:
153
- ```bash
154
- gh api repos/${REPO}/milestones -X POST \
155
- -f title="${MILESTONE_NAME}" \
156
- -f description="Imported from GSD: ${MILESTONE_SOURCE}" \
157
- -f state="open"
158
- ```
159
- Capture the returned `number` as GITHUB_MILESTONE_NUMBER.
160
- 3. If found: use the existing milestone's number.
161
- 4. For each phase in the milestone: create GitHub issues (one per phase, title = phase name, body includes phase goals and gsd_route). Use the same issue creation pattern as the existing `create_issues` step.
162
- 5. Add project.json entry for this milestone using the new schema fields:
163
- ```json
164
- {
165
- "github_number": GITHUB_MILESTONE_NUMBER,
166
- "name": milestone_name,
167
- "gsd_milestone_id": null,
168
- "gsd_state": "active|completed based on alignment report state",
169
- "roadmap_archived_at": null
170
- }
171
- ```
172
- 6. Add maps-to cross-ref entry:
173
- ```bash
174
- # Append to .mgw/cross-refs.json
175
- TIMESTAMP=$(node ~/.claude/get-shit-done/bin/gsd-tools.cjs current-timestamp --raw 2>/dev/null || date -u +"%Y-%m-%dT%H:%M:%SZ")
176
- # Add entry: { "a": "milestone:${GITHUB_NUMBER}", "b": "gsd-milestone:${GSD_ID}", "type": "maps-to", "created": "${TIMESTAMP}" }
177
- ```
178
-
179
- After all milestones are mapped:
180
- - Write updated project.json with all milestone entries and new schema fields
181
- - Set active_gsd_milestone to the name of the 'active' milestone from alignment report
182
- - Display mapping summary:
183
- ```
184
- Mapped N GSD milestones → GitHub milestones:
185
- ✓ "Milestone Name" → #N (created/existing)
186
- ...
187
- cross-refs.json updated with N maps-to entries
188
- ```
189
- - Proceed to create_project_board step (existing step — reused for new project)
75
+ @workflows/milestone-mapper.md
190
76
  </step>
191
77
 
192
78
  <step name="reconcile_drift">
193
- **Reconcile diverged state (STATE_CLASS = Diverged):**
194
-
195
- Spawn drift-analyzer agent:
196
-
197
- Task(
198
- description="Analyze project state drift",
199
- subagent_type="general-purpose",
200
- prompt="
201
- <files_to_read>
202
- - ./CLAUDE.md
203
- - .mgw/project.json
204
- </files_to_read>
205
-
206
- Compare .mgw/project.json with live GitHub state.
207
-
208
- 1. Read project.json: parse milestones array, get repo name from project.repo
209
- 2. Query GitHub milestones:
210
- gh api repos/{REPO}/milestones --jq '.[] | {number, title, state, open_issues, closed_issues}'
211
- 3. For each milestone in project.json:
212
- - Does a GitHub milestone with matching title exist? (fuzzy: case-insensitive, strip emoji)
213
- - If no match: flag as missing_github
214
- - If match: compare issue count (open + closed GitHub vs issues array length)
215
- 4. For each GitHub milestone NOT matched to project.json entry: flag as missing_local
216
- 5. For issues: check pipeline_stage vs GitHub issue state
217
- - GitHub closed + local not 'done' or 'pr-created': flag as stage_mismatch
218
-
219
- <output>
220
- Write JSON to .mgw/drift-report.json:
221
- {
222
- \"mismatches\": [
223
- {\"type\": \"missing_github\", \"milestone_name\": \"...\", \"local_issue_count\": N, \"action\": \"create_github_milestone\"},
224
- {\"type\": \"missing_local\", \"github_number\": N, \"github_title\": \"...\", \"action\": \"import_to_project_json\"},
225
- {\"type\": \"count_mismatch\", \"milestone_name\": \"...\", \"local\": N, \"github\": M, \"action\": \"review_manually\"},
226
- {\"type\": \"stage_mismatch\", \"issue\": N, \"local_stage\": \"...\", \"github_state\": \"closed\", \"action\": \"update_local_stage\"}
227
- ],
228
- \"summary\": \"N mismatches found across M milestones\"
229
- }
230
- </output>
231
- "
232
- )
233
-
234
- After agent completes:
235
- 1. Read .mgw/drift-report.json
236
- 2. Display mismatches as a table:
237
-
238
- | Type | Detail | Suggested Action |
239
- |------|--------|-----------------|
240
- | missing_github | Milestone: {name} ({N} local issues) | Create GitHub milestone |
241
- | missing_local | GitHub #N: {title} | Import to project.json |
242
- | count_mismatch | {name}: local={N}, github={M} | Review manually |
243
- | stage_mismatch | Issue #{N}: local={stage}, github=closed | Update local stage to done |
244
-
245
- 3. If no mismatches: echo "No drift detected — state is consistent. Reclassifying as Aligned." and proceed to report alignment status.
246
- 4. If mismatches: Ask "Apply auto-fixes? Options: (A)ll / (S)elective / (N)one"
247
- - All: apply each action (create missing milestones, update stages in project.json)
248
- - Selective: present each fix individually, Y/N per item
249
- - None: exit with "Drift noted. Run /mgw:sync to reconcile later."
250
- 5. After applying fixes: write updated project.json and display summary.
79
+ @workflows/drift-reconcile.md
251
80
  </step>
252
81
 
253
- <step name="vision_intake">
254
- **Intake: capture the raw project idea (Fresh path only)**
82
+ <step name="vision_cycle">
83
+ **Vision Collaboration Cycle: 6-stage Fresh project onboarding (Fresh path only)**
255
84
 
256
85
  If STATE_CLASS != Fresh: skip this step.
257
86
 
258
- Display to user:
259
- ```
260
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
261
- MGW ► VISION CYCLE — Let's Build Your Project
262
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
263
-
264
- Tell me about the project you want to build. Don't worry
265
- about being complete or precise — just describe the idea,
266
- the problem you're solving, and who it's for.
267
- ```
268
-
269
- Capture freeform user input as RAW_IDEA.
270
-
271
- ```bash
272
- TIMESTAMP=$(node ~/.claude/get-shit-done/bin/gsd-tools.cjs current-timestamp --raw 2>/dev/null || date -u +"%Y-%m-%dT%H:%M:%SZ")
273
- ```
274
-
275
- Save to `.mgw/vision-draft.md`:
276
- ```markdown
277
- ---
278
- current_stage: intake
279
- rounds_completed: 0
280
- soft_cap_reached: false
281
- ---
282
-
283
- # Vision Draft
284
-
285
- ## Intake
286
- **Raw Idea:** {RAW_IDEA}
287
- **Captured:** {TIMESTAMP}
288
- ```
289
-
290
- Proceed to vision_research step.
291
- </step>
292
-
293
- <step name="vision_research">
294
- **Domain Expansion: spawn vision-researcher agent (silent)**
295
-
296
- If STATE_CLASS != Fresh: skip this step.
297
-
298
- Spawn vision-researcher Task agent:
299
-
300
- Task(
301
- description="Research project domain and platform requirements",
302
- subagent_type="general-purpose",
303
- prompt="
304
- You are a domain research agent for a new software project.
305
-
306
- Raw idea from user:
307
- {RAW_IDEA}
308
-
309
- Research this project idea and produce a domain analysis. Write your output to .mgw/vision-research.json.
310
-
311
- Your analysis must include:
312
-
313
- 1. **domain_analysis**: What does this domain actually require to succeed?
314
- - Core capabilities users expect
315
- - Table stakes vs differentiators
316
- - Common failure modes in this domain
317
-
318
- 2. **platform_requirements**: Specific technical/integration needs
319
- - APIs, third-party services the domain typically needs
320
- - Compliance or regulatory considerations
321
- - Platform targets (mobile, web, desktop, API-only)
322
-
323
- 3. **competitive_landscape**: What similar solutions exist?
324
- - 2-3 examples with their key approaches
325
- - Gaps in existing solutions that this could fill
326
-
327
- 4. **risk_factors**: Common failure modes for this type of project
328
- - Technical risks
329
- - Business/adoption risks
330
- - Scope creep patterns in this domain
331
-
332
- 5. **suggested_questions**: 6-10 targeted questions to ask the user
333
- - Prioritized by most impactful for scoping
334
- - Each question should clarify a decision that affects architecture or milestone structure
335
- - Format: [{\"question\": \"...\", \"why_it_matters\": \"...\"}, ...]
336
-
337
- Output format — write to .mgw/vision-research.json:
338
- {
339
- \"domain_analysis\": {\"core_capabilities\": [...], \"differentiators\": [...], \"failure_modes\": [...]},
340
- \"platform_requirements\": [...],
341
- \"competitive_landscape\": [{\"name\": \"...\", \"approach\": \"...\"}],
342
- \"risk_factors\": [...],
343
- \"suggested_questions\": [{\"question\": \"...\", \"why_it_matters\": \"...\"}]
344
- }
345
- "
346
- )
347
-
348
- After agent completes:
349
- - Read .mgw/vision-research.json
350
- - Append research summary to .mgw/vision-draft.md:
351
- ```markdown
352
- ## Domain Research (silent)
353
- - Domain: {domain from analysis}
354
- - Key platform requirements: {top 3}
355
- - Risks identified: {count}
356
- - Questions generated: {count}
357
- ```
358
- - Update vision-draft.md frontmatter: current_stage: questioning
359
- - Proceed to vision_questioning step.
360
- </step>
361
-
362
- <step name="vision_questioning">
363
- **Structured Questioning Loop (Fresh path only)**
364
-
365
- If STATE_CLASS != Fresh: skip this step.
366
-
367
- Read .mgw/vision-research.json to get suggested_questions.
368
- Read .mgw/vision-draft.md to get current state.
369
-
370
- Initialize loop:
371
- ```bash
372
- ROUND=0
373
- SOFT_CAP=8
374
- HARD_CAP=15
375
- SOFT_CAP_REACHED=false
376
- ```
377
-
378
- **Questioning loop:**
379
-
380
- Each round:
381
-
382
- 1. Load questions remaining from .mgw/vision-research.json suggested_questions (dequeue used ones).
383
- Also allow orchestrator to generate follow-up questions based on previous answers.
384
-
385
- 2. Present 2-4 questions to user (never more than 4 per round):
386
- ```
387
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
388
- Vision Cycle — Round {N} of {SOFT_CAP}
389
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
390
-
391
- 1) {question_1}
392
- 2) {question_2}
393
- 3) {question_3}
394
-
395
- (Answer all, some, or type 'done' to proceed to synthesis)
396
- ```
397
-
398
- 3. Capture user answers as ANSWERS_ROUND_N.
399
-
400
- 4. Append round to .mgw/vision-draft.md:
401
- ```markdown
402
- ## Round {N} — {TIMESTAMP}
403
- **Questions asked:**
404
- 1. {q1}
405
- 2. {q2}
406
-
407
- **Answers:**
408
- {ANSWERS_ROUND_N}
409
-
410
- **Key decisions extracted:**
411
- - {decision_1}
412
- - {decision_2}
413
- ```
414
- (Key decisions: orchestrator extracts 1-3 concrete decisions from answers inline — no agent spawn needed)
415
-
416
- 5. Increment ROUND.
417
- Update .mgw/vision-draft.md frontmatter: rounds_completed={ROUND}
418
-
419
- 6. **Soft cap check** (after round {SOFT_CAP}):
420
- If ROUND >= SOFT_CAP and !SOFT_CAP_REACHED:
421
- Set SOFT_CAP_REACHED=true
422
- Update vision-draft.md frontmatter: soft_cap_reached=true
423
- Display:
424
- ```
425
- ─────────────────────────────────────
426
- We've covered {ROUND} rounds of questions.
427
-
428
- Options:
429
- D) Dig deeper — continue questioning (up to {HARD_CAP} rounds total)
430
- S) Synthesize — proceed to Vision Brief generation
431
- ─────────────────────────────────────
432
- ```
433
- If user chooses S: exit loop and proceed to vision_synthesis
434
- If user chooses D: continue loop
435
-
436
- 7. **Hard cap** (ROUND >= HARD_CAP): automatically exit loop with notice:
437
- ```
438
- Reached {HARD_CAP}-round limit. Proceeding to synthesis.
439
- ```
440
-
441
- 8. **User 'done'**: if user types 'done' as answer: exit loop immediately.
442
-
443
- After loop exits:
444
- - Update vision-draft.md frontmatter: current_stage: synthesizing
445
- - Display: "Questioning complete ({ROUND} rounds). Generating Vision Brief..."
446
- - Proceed to vision_synthesis step.
447
- </step>
448
-
449
- <step name="vision_synthesis">
450
- **Vision Synthesis: spawn vision-synthesizer agent and review loop (Fresh path only)**
451
-
452
- If STATE_CLASS != Fresh: skip this step.
453
-
454
- Display: "Generating Vision Brief from {rounds_completed} rounds of input..."
455
-
456
- **Synthesizer spawn:**
457
-
458
- Task(
459
- description="Synthesize Vision Brief from research and questioning",
460
- subagent_type="general-purpose",
461
- prompt="
462
- You are the vision-synthesizer agent for a software project planning cycle.
463
-
464
- Read these files:
465
- - .mgw/vision-draft.md — all rounds of user questions and answers, raw idea
466
- - .mgw/vision-research.json — domain research, platform requirements, risks
467
-
468
- Synthesize a comprehensive Vision Brief. Write it to .mgw/vision-brief.json using this schema (templates/vision-brief-schema.json):
469
-
470
- {
471
- \"project_identity\": { \"name\": \"...\", \"tagline\": \"...\", \"domain\": \"...\" },
472
- \"target_users\": [{ \"persona\": \"...\", \"needs\": [...], \"pain_points\": [...] }],
473
- \"core_value_proposition\": \"1-2 sentences: who, what, why different\",
474
- \"feature_categories\": {
475
- \"must_have\": [{ \"name\": \"...\", \"description\": \"...\", \"rationale\": \"why non-negotiable\" }],
476
- \"should_have\": [{ \"name\": \"...\", \"description\": \"...\" }],
477
- \"could_have\": [{ \"name\": \"...\", \"description\": \"...\" }],
478
- \"wont_have\": [{ \"name\": \"...\", \"reason\": \"explicit out-of-scope reasoning\" }]
479
- },
480
- \"technical_constraints\": [...],
481
- \"success_metrics\": [...],
482
- \"estimated_scope\": { \"milestones\": N, \"phases\": N, \"complexity\": \"small|medium|large|enterprise\" },
483
- \"recommended_milestone_structure\": [{ \"name\": \"...\", \"focus\": \"...\", \"deliverables\": [...] }]
484
- }
485
-
486
- Be specific and concrete. Use the user's actual answers from vision-draft.md. Do NOT pad with generic content.
487
- "
488
- )
489
-
490
- After synthesizer completes:
491
- 1. Read .mgw/vision-brief.json
492
- 2. Display the Vision Brief to user in structured format:
493
- ```
494
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
495
- Vision Brief: {project_identity.name}
496
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
497
-
498
- Tagline: {tagline}
499
- Domain: {domain}
500
-
501
- Target Users:
502
- • {persona_1}: {needs summary}
503
- • {persona_2}: ...
504
-
505
- Core Value: {core_value_proposition}
506
-
507
- Must-Have Features ({count}):
508
- • {feature_1}: {rationale}
509
- • ...
510
-
511
- Won't Have ({count}): {list}
512
-
513
- Estimated Scope: {complexity} — {milestones} milestones, ~{phases} phases
514
-
515
- Recommended Milestones:
516
- 1. {name}: {focus}
517
- 2. ...
518
- ```
519
-
520
- 3. Present review options:
521
- ```
522
- ─────────────────────────────────────────
523
- Review Options:
524
- A) Accept — proceed to condensing and project creation
525
- R) Revise — tell me what to change, regenerate
526
- D) Dig deeper on: [specify area]
527
- ─────────────────────────────────────────
528
- ```
529
-
530
- 4. If Accept: proceed to vision_condense step
531
- 5. If Revise: capture correction, spawn vision-synthesizer again with correction appended to vision-draft.md, loop back to step 2
532
- 6. If Dig deeper: append "Deeper exploration of {area}" to vision-draft.md, spawn vision-synthesizer again
533
- </step>
534
-
535
- <step name="vision_condense">
536
- **Vision Condense: produce gsd:new-project handoff document (Fresh path only)**
537
-
538
- If STATE_CLASS != Fresh: skip this step.
539
-
540
- Display: "Condensing Vision Brief into project handoff..."
541
-
542
- Task(
543
- description="Condense Vision Brief into gsd:new-project handoff",
544
- subagent_type="general-purpose",
545
- prompt="
546
- You are the vision-condenser agent. Your job is to produce a handoff document
547
- that will be passed as context to a gsd:new-project spawn.
548
-
549
- Read .mgw/vision-brief.json.
550
-
551
- Produce a structured handoff document at .mgw/vision-handoff.md that:
552
-
553
- 1. Opens with a context block that gsd:new-project can use directly to produce PROJECT.md:
554
- - Project name, tagline, domain
555
- - Target users and their core needs
556
- - Core value proposition
557
- - Must-have feature list with rationale
558
- - Won't-have list (explicit out-of-scope)
559
- - Technical constraints
560
- - Success metrics
561
-
562
- 2. Includes recommended milestone structure as a numbered list:
563
- - Each milestone: name, focus area, key deliverables
564
-
565
- 3. Closes with an instruction for gsd:new-project:
566
- 'Use the above as the full project context when creating PROJECT.md.
567
- The project name, scope, users, and milestones above reflect decisions
568
- made through {rounds_completed} rounds of collaborative planning.
569
- Do not hallucinate scope beyond what is specified.'
570
-
571
- Format as clean markdown. This document becomes the prompt prefix for gsd:new-project.
572
- "
573
- )
574
-
575
- After condenser completes:
576
- 1. Verify .mgw/vision-handoff.md exists and has content
577
- 2. Display: "Vision Brief condensed. Ready to initialize project structure."
578
- 3. Update .mgw/vision-draft.md frontmatter: current_stage: spawning
579
- 4. Proceed to spawn_gsd_new_project step.
580
- </step>
581
-
582
- <step name="spawn_gsd_new_project">
583
- **Spawn gsd:new-project with Vision Brief context (Fresh path only)**
584
-
585
- If STATE_CLASS != Fresh: skip this step.
586
-
587
- Read .mgw/vision-handoff.md:
588
- ```bash
589
- HANDOFF_CONTENT=$(cat .mgw/vision-handoff.md)
590
- ```
591
-
592
- Display: "Spawning gsd:new-project with full vision context..."
593
-
594
- Spawn gsd:new-project as a Task agent, passing the handoff document as context prefix:
595
-
596
- Task(
597
- description="Initialize GSD project from Vision Brief",
598
- subagent_type="general-purpose",
599
- prompt="
600
- ${HANDOFF_CONTENT}
601
-
602
- ---
603
-
604
- You are now running gsd:new-project. Using the Vision Brief above as your full project context, create:
605
-
606
- 1. .planning/PROJECT.md — Complete project definition following GSD format:
607
- - Project name and one-line description from vision brief
608
- - Vision and goals aligned with the value proposition
609
- - Target users from the personas
610
- - Core requirements mapping to the must-have features
611
- - Non-goals matching the wont-have list
612
- - Success criteria from success_metrics
613
- - Technical constraints listed explicitly
614
-
615
- 2. .planning/ROADMAP.md — First milestone plan following GSD format:
616
- - Use the first milestone from recommended_milestone_structure
617
- - Break it into 3-8 phases
618
- - Each phase has: number, name, goal, requirements, success criteria
619
- - Phase numbering starts at 1
620
- - Include a progress table at the top
621
-
622
- Write both files. Do not create additional files. Do not deviate from the Vision Brief scope.
623
- "
624
- )
625
-
626
- After agent completes:
627
- 1. Verify .planning/PROJECT.md exists:
628
- ```bash
629
- if [ ! -f .planning/PROJECT.md ]; then
630
- echo "ERROR: gsd:new-project did not create .planning/PROJECT.md"
631
- echo "Check the agent output and retry, or create PROJECT.md manually."
632
- exit 1
633
- fi
634
- ```
635
-
636
- 2. Verify .planning/ROADMAP.md exists:
637
- ```bash
638
- if [ ! -f .planning/ROADMAP.md ]; then
639
- echo "ERROR: gsd:new-project did not create .planning/ROADMAP.md"
640
- echo "Check the agent output and retry, or create ROADMAP.md manually."
641
- exit 1
642
- fi
643
- ```
644
-
645
- 3. Display success:
646
- ```
647
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
648
- GSD Project Initialized
649
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
650
-
651
- .planning/PROJECT.md created
652
- .planning/ROADMAP.md created (first milestone phases ready)
653
-
654
- Vision cycle: {rounds_completed} rounds -> Vision Brief -> PROJECT.md
655
- ```
656
-
657
- 4. Update .mgw/vision-draft.md frontmatter: current_stage: complete
658
-
659
- 4b. Synthesize alignment-report.json for milestone_mapper:
660
-
661
- The Fresh path skips `align_from_gsd`, so `.mgw/alignment-report.json` does not exist yet.
662
- Synthesize it from the freshly created ROADMAP.md and PROJECT.md so `milestone_mapper` has
663
- consistent input regardless of which path was taken.
664
-
665
- ```bash
666
- python3 << 'PYEOF'
667
- import json, re, os
668
-
669
- repo_root = os.environ.get("REPO_ROOT", ".")
670
-
671
- # --- Parse PROJECT.md for name and description ---
672
- project_path = os.path.join(repo_root, ".planning", "PROJECT.md")
673
- with open(project_path, "r") as f:
674
- project_text = f.read()
675
-
676
- # Extract H1 heading as project name
677
- name_match = re.search(r"^#\s+(.+)$", project_text, re.MULTILINE)
678
- project_name = name_match.group(1).strip() if name_match else "Untitled Project"
679
-
680
- # Extract first paragraph after H1 as description
681
- desc_match = re.search(r"^#\s+.+\n+(.+?)(?:\n\n|\n#)", project_text, re.MULTILINE | re.DOTALL)
682
- project_description = desc_match.group(1).strip() if desc_match else ""
683
-
684
- # --- Parse ROADMAP.md for phases ---
685
- roadmap_path = os.path.join(repo_root, ".planning", "ROADMAP.md")
686
- with open(roadmap_path, "r") as f:
687
- roadmap_text = f.read()
688
-
689
- # Extract milestone name from first heading after any frontmatter
690
- roadmap_body = re.sub(r"^---\n.*?\n---\n?", "", roadmap_text, flags=re.DOTALL)
691
- milestone_heading = re.search(r"^#{1,2}\s+(.+)$", roadmap_body, re.MULTILINE)
692
- milestone_name = milestone_heading.group(1).strip() if milestone_heading else "Milestone 1"
693
-
694
- # Extract phases (### Phase N: Name or ## Phase N: Name)
695
- phase_pattern = re.compile(r"^#{2,3}\s+Phase\s+(\d+)[:\s]+(.+)$", re.MULTILINE)
696
- phases = []
697
- for m in phase_pattern.finditer(roadmap_text):
698
- phases.append({
699
- "number": int(m.group(1)),
700
- "name": m.group(2).strip(),
701
- "status": "pending"
702
- })
703
-
704
- if not phases:
705
- phases = [{"number": 1, "name": milestone_name, "status": "pending"}]
706
-
707
- # Estimate ~2 issues per phase as a rough default
708
- total_issues_estimated = len(phases) * 2
709
-
710
- report = {
711
- "project_name": project_name,
712
- "project_description": project_description,
713
- "milestones": [
714
- {
715
- "name": milestone_name,
716
- "source": "ROADMAP",
717
- "state": "active",
718
- "phases": phases
719
- }
720
- ],
721
- "active_milestone": milestone_name,
722
- "total_phases": len(phases),
723
- "total_issues_estimated": total_issues_estimated
724
- }
725
-
726
- output_path = os.path.join(repo_root, ".mgw", "alignment-report.json")
727
- os.makedirs(os.path.dirname(output_path), exist_ok=True)
728
- with open(output_path, "w") as f:
729
- json.dump(report, f, indent=2)
730
-
731
- print(f"Synthesized alignment-report.json: {len(phases)} phases, milestone='{milestone_name}'")
732
- PYEOF
733
- ```
734
-
735
- 5. Proceed to milestone_mapper step:
736
- The ROADMAP.md now exists, so PATH A (HAS_ROADMAP=true) logic applies.
737
- Call the milestone_mapper step to read ROADMAP.md and create GitHub milestones/issues.
738
- (Note: at this point STATE_CLASS was Fresh but now GSD files exist — the milestone_mapper
739
- step was designed for the GSD-Only path but works identically here. Proceed to it directly.)
87
+ @workflows/vision-cycle.md
740
88
  </step>
741
89
 
742
90
  <step name="gather_inputs">
743
- **Gather project inputs conversationally:**
91
+ **Gather project inputs (non-extend path only):**
744
92
 
745
- If STATE_CLASS = Fresh: skip this step (handled by vision_intake through spawn_gsd_new_project above — proceed directly to milestone_mapper).
93
+ If STATE_CLASS = Fresh: skip (handled by vision_cycle — proceed to milestone_mapper).
94
+ If EXTEND_MODE = true: @workflows/extend-project.md
746
95
 
747
- Ask the following questions in sequence:
96
+ Otherwise, ask conversationally:
748
97
 
749
98
  **Question 1:** "What are you building?"
750
- - Capture the project description as `$DESCRIPTION`. Be conversational and encourage detail about the domain, purpose, and target users.
99
+ - Capture as `$DESCRIPTION`. Encourage detail about domain, purpose, and target users.
751
100
 
752
- **Question 2 (optional):** "Anything else I should know? (tech stack preferences, target audience, key constraints — or press Enter to skip)"
101
+ **Question 2 (optional):** "Anything else I should know? (tech stack, audience, constraints — or Enter to skip)"
753
102
  - Append any additional context to `$DESCRIPTION` if provided.
754
103
 
755
- Do NOT ask the user to pick a template type. Do NOT present a list of web-app/cli-tool/library options. The AI will generate project-specific content directly from the description.
104
+ Do NOT ask the user to pick a template type.
756
105
 
757
- **Infer parameters from environment (only ask for what cannot be inferred):**
106
+ **Infer parameters from environment:**
758
107
 
759
108
  ```bash
760
- # Project name: last segment of owner/repo
761
109
  PROJECT_NAME=$(echo "$REPO" | cut -d'/' -f2)
762
110
 
763
- # Stack: detect from existing files
764
- if [ -f "${REPO_ROOT}/package.json" ]; then
765
- STACK="node"
766
- elif [ -f "${REPO_ROOT}/Cargo.toml" ]; then
767
- STACK="rust"
768
- elif [ -f "${REPO_ROOT}/go.mod" ]; then
769
- STACK="go"
770
- elif [ -f "${REPO_ROOT}/requirements.txt" ] || [ -f "${REPO_ROOT}/pyproject.toml" ]; then
771
- STACK="python"
772
- else
773
- STACK="unknown"
774
- fi
111
+ if [ -f "${REPO_ROOT}/package.json" ]; then STACK="node"
112
+ elif [ -f "${REPO_ROOT}/Cargo.toml" ]; then STACK="rust"
113
+ elif [ -f "${REPO_ROOT}/go.mod" ]; then STACK="go"
114
+ elif [ -f "${REPO_ROOT}/requirements.txt" ] || [ -f "${REPO_ROOT}/pyproject.toml" ]; then STACK="python"
115
+ else STACK="unknown"; fi
775
116
 
776
- # Prefix: default v1
777
117
  PREFIX="v1"
778
118
  ```
779
-
780
- **In extend mode, load existing metadata and ask for new milestones:**
781
-
782
- When `EXTEND_MODE=true`, skip the questions above and instead:
783
-
784
- ```bash
785
- if [ "$EXTEND_MODE" = true ]; then
786
- # Load existing project metadata — name, repo, stack, prefix are already known
787
- PROJECT_NAME=$(python3 -c "import json; print(json.load(open('${REPO_ROOT}/.mgw/project.json'))['project']['name'])")
788
- STACK=$(python3 -c "import json; print(json.load(open('${REPO_ROOT}/.mgw/project.json'))['project'].get('stack','unknown'))")
789
- PREFIX=$(python3 -c "import json; print(json.load(open('${REPO_ROOT}/.mgw/project.json'))['project'].get('prefix','v1'))")
790
- EXISTING_MILESTONE_NAMES=$(python3 -c "import json; p=json.load(open('${REPO_ROOT}/.mgw/project.json')); print(', '.join(m['name'] for m in p['milestones']))")
791
-
792
- # Assemble project history context for the template generator
793
- MILESTONE_HISTORY=$(python3 -c "
794
- import json
795
- p = json.load(open('${REPO_ROOT}/.mgw/project.json'))
796
- lines = []
797
- for m in p['milestones']:
798
- lines.append(f\"Milestone: {m['name']}\")
799
- for i in m.get('issues', []):
800
- lines.append(f\" - {i['title']} ({i.get('pipeline_stage','unknown')})\")
801
- print('\n'.join(lines))
802
- ")
803
-
804
- GSD_DIGEST=$(node ~/.claude/get-shit-done/bin/gsd-tools.cjs history-digest 2>/dev/null || echo "")
805
-
806
- HISTORY_CONTEXT="Previous milestones and issues built so far:
807
- ${MILESTONE_HISTORY}
808
-
809
- GSD build history (phases and decisions already made):
810
- ${GSD_DIGEST:-No GSD history available.}"
811
-
812
- # ── Growth Analytics ────────────────────────────────────────────────────────
813
- # Compute milestone completion stats and issue velocity from project.json.
814
- # Query open blockers from GitHub API. Spawn an AI suggester agent.
815
- # Display a summary banner BEFORE asking for the extension description.
816
-
817
- ANALYTICS=$(python3 -c "
818
- import json, sys
819
-
820
- p = json.load(open('${REPO_ROOT}/.mgw/project.json'))
821
- milestones = p.get('milestones', [])
822
- total_milestones = len(milestones)
823
- completed_milestones = sum(1 for m in milestones if m.get('gsd_state') == 'completed')
824
-
825
- # Issue velocity: avg closed issues per completed milestone
826
- total_closed = sum(
827
- sum(1 for i in m.get('issues', []) if i.get('pipeline_stage') in ('done', 'pr-created'))
828
- for m in milestones if m.get('gsd_state') == 'completed'
829
- )
830
- velocity = round(total_closed / completed_milestones, 1) if completed_milestones > 0 else 0
831
-
832
- print(f'{completed_milestones}|{total_milestones}|{velocity}|{total_closed}')
833
- ")
834
-
835
- COMPLETED_COUNT=$(echo "$ANALYTICS" | cut -d'|' -f1)
836
- TOTAL_COUNT=$(echo "$ANALYTICS" | cut -d'|' -f2)
837
- VELOCITY=$(echo "$ANALYTICS" | cut -d'|' -f3)
838
- TOTAL_CLOSED=$(echo "$ANALYTICS" | cut -d'|' -f4)
839
-
840
- # Count open issues labeled blocked-by:* or with a "blocked" status
841
- OPEN_BLOCKERS=$(gh api "repos/${REPO}/issues" \
842
- --jq '[.[] | select(.state=="open") | select(.labels[].name | test("^blocked-by:"))] | length' \
843
- 2>/dev/null || echo "0")
844
-
845
- # Spawn growth-suggester agent — reads milestone names + completed issue titles
846
- # and produces natural next-area suggestions. No application code reads.
847
- COMPLETED_ISSUE_TITLES=$(python3 -c "
848
- import json
849
- p = json.load(open('${REPO_ROOT}/.mgw/project.json'))
850
- titles = []
851
- for m in p.get('milestones', []):
852
- if m.get('gsd_state') == 'completed':
853
- for i in m.get('issues', []):
854
- if i.get('pipeline_stage') in ('done', 'pr-created'):
855
- titles.append(f\" - [{m['name']}] {i['title']}\")
856
- print('\n'.join(titles) if titles else ' (no completed issues recorded)')
857
- ")
858
-
859
- MILESTONE_LIST=$(python3 -c "
860
- import json
861
- p = json.load(open('${REPO_ROOT}/.mgw/project.json'))
862
- for m in p.get('milestones', []):
863
- state = m.get('gsd_state', 'unknown')
864
- print(f\" {m['name']} ({state})\")
865
- ")
866
-
867
- MODEL=$(node ~/.claude/get-shit-done/bin/gsd-tools.cjs resolve-model general-purpose 2>/dev/null || echo "claude-sonnet-4-5")
868
-
869
- SUGGESTER_OUTPUT=$(Task(
870
- description="Suggest natural next milestone areas for project extension",
871
- subagent_type="general-purpose",
872
- prompt="
873
- You are a growth-suggester agent for a software project planning tool.
874
-
875
- Project: ${PROJECT_NAME}
876
- Repo: ${REPO}
877
-
878
- Milestones built so far:
879
- ${MILESTONE_LIST}
880
-
881
- Completed issues (represents what has been built):
882
- ${COMPLETED_ISSUE_TITLES}
883
-
884
- Based ONLY on the above context — what has been built and what the project does —
885
- suggest 3-5 natural next areas for extension milestones. Each suggestion should:
886
- - Build on or complement existing milestones (no overlap)
887
- - Be a coherent milestone-sized body of work (not a single feature)
888
- - Be expressed as a short label (5-10 words) with a 1-sentence rationale
889
-
890
- Output format — plain text only, one suggestion per line:
891
- 1. {Milestone Area Name}: {one-sentence rationale}
892
- 2. ...
893
-
894
- Do not add preamble, headers, or closing text. Output ONLY the numbered list.
895
- "
896
- ))
897
-
898
- AI_SUGGESTIONS="${SUGGESTER_OUTPUT:- (AI suggestions unavailable)}"
899
-
900
- # Display growth analytics banner
901
- echo ""
902
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
903
- echo " ${PROJECT_NAME} — Growth Analytics"
904
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
905
- echo ""
906
- echo " Milestones completed : ${COMPLETED_COUNT} of ${TOTAL_COUNT}"
907
- echo " Issue velocity : ${VELOCITY} issues/milestone avg (${TOTAL_CLOSED} total closed)"
908
- echo " Open blockers : ${OPEN_BLOCKERS}"
909
- echo ""
910
- echo " AI-suggested next areas:"
911
- echo "${AI_SUGGESTIONS}" | sed 's/^/ /'
912
- echo ""
913
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
914
- echo ""
915
- # ── End Growth Analytics ────────────────────────────────────────────────────
916
-
917
- # Ask only for the new work — different question for extend mode
918
- # Ask: "What new milestones should we add to ${PROJECT_NAME}?"
919
- # Capture as EXTENSION_DESCRIPTION
920
-
921
- DESCRIPTION="Extension of existing project. Existing milestones: ${EXISTING_MILESTONE_NAMES}. New work: ${EXTENSION_DESCRIPTION}"
922
- fi
923
- ```
924
119
  </step>
925
120
 
926
121
  <step name="generate_template">
927
- **Generate a project-specific template using AI:**
928
-
929
- First, read the schema to understand the required output structure:
930
-
931
- ```bash
932
- SCHEMA=$(node "${REPO_ROOT}/lib/template-loader.cjs" schema)
933
- ```
934
-
935
- Now, as the AI executing this command, generate a complete project template JSON for this specific project. The JSON must:
936
-
937
- 1. Match the schema structure: milestones > phases > issues with all required fields
938
- 2. Use a descriptive `type` value that fits the project (e.g., "game", "mobile-app", "saas-platform", "data-pipeline", "api-service", "developer-tool", "browser-extension", etc. — NOT limited to web-app/cli-tool/library)
939
- 3. Contain 2-4 milestones with 1-3 phases each, each phase having 2-4 issues
940
- 4. Have issue titles that are specific and actionable — referencing the actual project domain, not generic placeholders like "Implement primary feature set"
941
- 5. Have issue descriptions that reference the actual project context
942
- 6. Use `depends_on` slugs following the convention: lowercase title, spaces-to-hyphens, truncated to 40 chars (e.g., "design-core-game-loop-and-player-mechanic")
943
- 7. Choose `gsd_route` values appropriately:
944
- - `plan-phase` for complex multi-step implementation work
945
- - `quick` for small well-defined tasks
946
- - `research-phase` for unknowns requiring investigation
947
- - `execute-phase` for straightforward mechanical execution
948
- 8. Use specific, relevant labels (not just "phase-N") — e.g., "backend", "frontend", "game-design", "ml", "database", "ui/ux", "performance", "security"
949
- 9. Set `version` to "1.0.0"
950
- 10. Include the standard `parameters` section with `project_name` and `description` as required params, and `repo`, `stack`, `prefix` as optional params
951
- 11. Include a `project` object with `name`, `description`, `repo`, `stack`, and `prefix` fields filled from the gathered inputs
952
-
953
- Output the generated JSON as a fenced code block (```json ... ```).
954
-
955
- The project details for generation:
956
- - **Project name:** `$PROJECT_NAME`
957
- - **Description:** `$DESCRIPTION`
958
- - **Stack:** `$STACK`
959
- - **Repo:** `$REPO`
960
- - **Prefix:** `$PREFIX`
961
-
962
- <project_history>
963
- ${HISTORY_CONTEXT:-No prior history available.}
964
- </project_history>
965
-
966
- When in extend mode (HISTORY_CONTEXT is populated): do NOT suggest features or systems that already
967
- appear in the project history above. Build new milestones that complement and extend what exists.
968
-
969
- After generating the JSON, extract it and write to a temp file:
970
-
971
- ```bash
972
- # Write AI-generated JSON to temp file
973
- # (Claude writes the JSON using the Write tool to /tmp/mgw-template.json)
974
- ```
975
-
976
- **Validate the generated JSON:**
977
-
978
- ```bash
979
- node "${REPO_ROOT}/lib/template-loader.cjs" validate < /tmp/mgw-template.json
980
- ```
981
-
982
- If validation fails, review the errors and regenerate with corrections. Repeat until validation passes.
983
-
984
- If validation passes, parse key metrics:
985
-
986
- ```bash
987
- MILESTONE_COUNT=$(python3 -c "import json; d=json.load(open('/tmp/mgw-template.json')); print(len(d['milestones']))")
988
- TOTAL_PHASES=$(python3 -c "import json; d=json.load(open('/tmp/mgw-template.json')); print(sum(len(m['phases']) for m in d['milestones']))")
989
- GENERATED_TYPE=$(python3 -c "import json; d=json.load(open('/tmp/mgw-template.json')); print(d['type'])")
990
- ```
991
- </step>
992
-
993
- <step name="create_milestones">
994
- **Pass 1a: Create GitHub milestones**
995
-
996
- For each milestone in the generated template (iterate by index):
997
-
998
- ```bash
999
- # Iterate over milestones in the generated template
1000
- MILESTONE_MAP=() # bash array: index -> "number:id:url"
1001
-
1002
- for MILESTONE_INDEX in $(seq 0 $((MILESTONE_COUNT - 1))); do
1003
- MILESTONE_NAME=$(python3 -c "
1004
- import json
1005
- d=json.load(open('/tmp/mgw-template.json'))
1006
- print(d['milestones'][${MILESTONE_INDEX}]['name'])
1007
- ")
1008
- MILESTONE_DESC=$(python3 -c "
1009
- import json
1010
- d=json.load(open('/tmp/mgw-template.json'))
1011
- print(d['milestones'][${MILESTONE_INDEX}].get('description',''))
1012
- ")
1013
-
1014
- MILESTONE_JSON_RESP=$(gh api "repos/${REPO}/milestones" --method POST \
1015
- -f title="$MILESTONE_NAME" \
1016
- -f description="$MILESTONE_DESC" \
1017
- -f state="open" 2>&1)
1018
-
1019
- if echo "$MILESTONE_JSON_RESP" | python3 -c "import json,sys; json.load(sys.stdin)" 2>/dev/null; then
1020
- M_NUMBER=$(echo "$MILESTONE_JSON_RESP" | python3 -c "import json,sys; print(json.load(sys.stdin)['number'])")
1021
- M_ID=$(echo "$MILESTONE_JSON_RESP" | python3 -c "import json,sys; print(json.load(sys.stdin)['id'])")
1022
- M_URL=$(echo "$MILESTONE_JSON_RESP" | python3 -c "import json,sys; print(json.load(sys.stdin)['html_url'])")
1023
- MILESTONE_MAP+=("${MILESTONE_INDEX}:${M_NUMBER}:${M_ID}:${M_URL}")
1024
- echo " Created milestone: #${M_NUMBER} ${MILESTONE_NAME}"
1025
- else
1026
- echo " WARNING: Failed to create milestone '${MILESTONE_NAME}': ${MILESTONE_JSON_RESP}"
1027
- MILESTONE_MAP+=("${MILESTONE_INDEX}:FAILED:0:")
1028
- fi
1029
- done
1030
- ```
1031
- </step>
1032
-
1033
- <step name="create_issues">
1034
- **Pass 1b: Create GitHub issues for each phase**
1035
-
1036
- Build a slug-to-issue-number mapping as issues are created for Pass 2 dependency resolution.
1037
-
1038
- ```bash
1039
- SLUG_TO_NUMBER=() # bash array: "slug:number"
1040
- ISSUE_RECORDS=() # for project.json
1041
- PHASE_MAP_JSON="{}"
1042
- TOTAL_ISSUES_CREATED=0
1043
- FAILED_SLUGS=()
1044
-
1045
- # Global phase counter across milestones
1046
- # In extend mode, continue numbering from last existing phase
1047
- if [ "$EXTEND_MODE" = true ]; then
1048
- GLOBAL_PHASE_NUM=$EXISTING_PHASE_COUNT
1049
- else
1050
- GLOBAL_PHASE_NUM=0
1051
- fi
1052
-
1053
- for MILESTONE_INDEX in $(seq 0 $((MILESTONE_COUNT - 1))); do
1054
- # Get this milestone's GitHub number from MILESTONE_MAP
1055
- M_ENTRY="${MILESTONE_MAP[$MILESTONE_INDEX]}"
1056
- M_NUMBER=$(echo "$M_ENTRY" | cut -d':' -f2)
1057
-
1058
- if [ "$M_NUMBER" = "FAILED" ]; then
1059
- echo " Skipping issues for failed milestone at index ${MILESTONE_INDEX}"
1060
- continue
1061
- fi
1062
-
1063
- PHASE_COUNT=$(python3 -c "
1064
- import json
1065
- d=json.load(open('/tmp/mgw-template.json'))
1066
- print(len(d['milestones'][${MILESTONE_INDEX}]['phases']))
1067
- ")
1068
-
1069
- for PHASE_INDEX in $(seq 0 $((PHASE_COUNT - 1))); do
1070
- GLOBAL_PHASE_NUM=$((GLOBAL_PHASE_NUM + 1))
1071
-
1072
- PHASE_NAME=$(python3 -c "
1073
- import json
1074
- d=json.load(open('/tmp/mgw-template.json'))
1075
- print(d['milestones'][${MILESTONE_INDEX}]['phases'][${PHASE_INDEX}]['name'])
1076
- ")
1077
- PHASE_DESC=$(python3 -c "
1078
- import json
1079
- d=json.load(open('/tmp/mgw-template.json'))
1080
- print(d['milestones'][${MILESTONE_INDEX}]['phases'][${PHASE_INDEX}].get('description',''))
1081
- ")
1082
- GSD_ROUTE=$(python3 -c "
1083
- import json
1084
- d=json.load(open('/tmp/mgw-template.json'))
1085
- print(d['milestones'][${MILESTONE_INDEX}]['phases'][${PHASE_INDEX}].get('gsd_route','plan-phase'))
1086
- ")
1087
- MILESTONE_NAME=$(python3 -c "
1088
- import json
1089
- d=json.load(open('/tmp/mgw-template.json'))
1090
- print(d['milestones'][${MILESTONE_INDEX}]['name'])
1091
- ")
1092
-
1093
- # Generate phase slug for label
1094
- PHASE_SLUG=$(node ~/.claude/get-shit-done/bin/gsd-tools.cjs generate-slug "${PHASE_NAME}" --raw 2>/dev/null | head -c 40)
1095
- if [ -z "$PHASE_SLUG" ] && [ -n "$PHASE_NAME" ]; then
1096
- PHASE_SLUG=$(echo "$PHASE_NAME" | tr '[:upper:]' '[:lower:]' | tr ' ' '-' | tr -cd 'a-z0-9-' | cut -c1-40)
1097
- fi
1098
-
1099
- # Create phase label (idempotent)
1100
- gh label create "phase:${GLOBAL_PHASE_NUM}-${PHASE_SLUG}" \
1101
- --description "Phase ${GLOBAL_PHASE_NUM}: ${PHASE_NAME}" \
1102
- --color "0075ca" \
1103
- --force 2>/dev/null
1104
-
1105
- ISSUE_COUNT=$(python3 -c "
1106
- import json
1107
- d=json.load(open('/tmp/mgw-template.json'))
1108
- print(len(d['milestones'][${MILESTONE_INDEX}]['phases'][${PHASE_INDEX}]['issues']))
1109
- ")
1110
-
1111
- for ISSUE_INDEX in $(seq 0 $((ISSUE_COUNT - 1))); do
1112
- ISSUE_TITLE=$(python3 -c "
1113
- import json
1114
- d=json.load(open('/tmp/mgw-template.json'))
1115
- print(d['milestones'][${MILESTONE_INDEX}]['phases'][${PHASE_INDEX}]['issues'][${ISSUE_INDEX}]['title'])
1116
- ")
1117
- ISSUE_DESC=$(python3 -c "
1118
- import json
1119
- d=json.load(open('/tmp/mgw-template.json'))
1120
- print(d['milestones'][${MILESTONE_INDEX}]['phases'][${PHASE_INDEX}]['issues'][${ISSUE_INDEX}].get('description',''))
1121
- ")
1122
- ISSUE_LABELS_JSON=$(python3 -c "
1123
- import json
1124
- d=json.load(open('/tmp/mgw-template.json'))
1125
- labels=d['milestones'][${MILESTONE_INDEX}]['phases'][${PHASE_INDEX}]['issues'][${ISSUE_INDEX}].get('labels',[])
1126
- print(','.join(labels))
1127
- ")
1128
- DEPENDS_ON_JSON=$(python3 -c "
1129
- import json
1130
- d=json.load(open('/tmp/mgw-template.json'))
1131
- deps=d['milestones'][${MILESTONE_INDEX}]['phases'][${PHASE_INDEX}]['issues'][${ISSUE_INDEX}].get('depends_on',[])
1132
- print(','.join(deps))
1133
- ")
1134
-
1135
- # Generate slug for this issue (for dependency resolution)
1136
- ISSUE_SLUG=$(node ~/.claude/get-shit-done/bin/gsd-tools.cjs generate-slug "${ISSUE_TITLE}" --raw 2>/dev/null | head -c 40)
1137
- if [ -z "$ISSUE_SLUG" ] && [ -n "$ISSUE_TITLE" ]; then
1138
- ISSUE_SLUG=$(echo "$ISSUE_TITLE" | tr '[:upper:]' '[:lower:]' | tr ' ' '-' | tr -cd 'a-z0-9-' | cut -c1-40)
1139
- fi
1140
-
1141
- # Build structured issue body (heredoc — preserves newlines)
1142
- DEPENDS_DISPLAY="${DEPENDS_ON_JSON:-_none_}"
1143
- if [ -z "$DEPENDS_ON_JSON" ]; then
1144
- DEPENDS_DISPLAY="_none_"
1145
- fi
1146
-
1147
- ISSUE_BODY=$(printf '## Description\n%s\n\n## Acceptance Criteria\n- [ ] %s\n\n## GSD Route\n%s\n\n## Phase Context\nPhase %s: %s of %s\n\n## Depends on\n%s' \
1148
- "$ISSUE_DESC" \
1149
- "$ISSUE_TITLE" \
1150
- "$GSD_ROUTE" \
1151
- "$GLOBAL_PHASE_NUM" \
1152
- "$PHASE_NAME" \
1153
- "$MILESTONE_NAME" \
1154
- "$DEPENDS_DISPLAY")
1155
-
1156
- # Build label args
1157
- LABEL_ARGS=(-f "labels[]=phase:${GLOBAL_PHASE_NUM}-${PHASE_SLUG}")
1158
- if [ -n "$ISSUE_LABELS_JSON" ]; then
1159
- for LBL in $(echo "$ISSUE_LABELS_JSON" | tr ',' '\n'); do
1160
- LABEL_ARGS+=(-f "labels[]=${LBL}")
1161
- done
1162
- fi
1163
-
1164
- ISSUE_API_JSON=$(gh api "repos/${REPO}/issues" --method POST \
1165
- -f title="$ISSUE_TITLE" \
1166
- -f body="$ISSUE_BODY" \
1167
- -F milestone="$M_NUMBER" \
1168
- "${LABEL_ARGS[@]}" 2>&1)
1169
-
1170
- if echo "$ISSUE_API_JSON" | python3 -c "import json,sys; json.load(sys.stdin)" 2>/dev/null; then
1171
- ISSUE_NUMBER=$(echo "$ISSUE_API_JSON" | python3 -c "import json,sys; print(json.load(sys.stdin)['number'])")
1172
- SLUG_TO_NUMBER+=("${ISSUE_SLUG}:${ISSUE_NUMBER}")
1173
- ISSUE_RECORDS+=("${MILESTONE_INDEX}:${ISSUE_NUMBER}:${ISSUE_TITLE}:${GLOBAL_PHASE_NUM}:${PHASE_NAME}:${GSD_ROUTE}:${DEPENDS_ON_JSON}")
1174
- TOTAL_ISSUES_CREATED=$((TOTAL_ISSUES_CREATED + 1))
1175
- echo " Created issue #${ISSUE_NUMBER}: ${ISSUE_TITLE}"
1176
- else
1177
- echo " WARNING: Failed to create issue '${ISSUE_TITLE}': ${ISSUE_API_JSON}"
1178
- FAILED_SLUGS+=("$ISSUE_SLUG")
1179
- fi
1180
- done
1181
-
1182
- # Record phase in phase_map
1183
- PHASE_MAP_JSON=$(echo "$PHASE_MAP_JSON" | python3 -c "
1184
- import json,sys
1185
- d=json.loads(sys.stdin.read())
1186
- d['${GLOBAL_PHASE_NUM}']={'milestone_index':${MILESTONE_INDEX},'gsd_route':'${GSD_ROUTE}','name':'${PHASE_NAME}'}
1187
- print(json.dumps(d))
1188
- ")
1189
- done
1190
- done
1191
- ```
1192
- </step>
1193
-
1194
- <step name="apply_dependencies">
1195
- **Pass 2: Apply dependency labels**
1196
-
1197
- For each issue with `depends_on` entries, resolve slug → issue number, create
1198
- blocked-by labels, and record in cross-refs.json.
1199
-
1200
- ```bash
1201
- DEPENDENCY_ENTRIES=()
1202
- DEPENDENCY_DISPLAY=()
1203
-
1204
- for RECORD in "${ISSUE_RECORDS[@]}"; do
1205
- DEPENDENT_NUMBER=$(echo "$RECORD" | cut -d':' -f2)
1206
- DEPENDS_ON_SLUGS=$(echo "$RECORD" | cut -d':' -f7)
1207
-
1208
- if [ -z "$DEPENDS_ON_SLUGS" ]; then
1209
- continue
1210
- fi
1211
-
1212
- for BLOCKING_SLUG in $(echo "$DEPENDS_ON_SLUGS" | tr ',' '\n'); do
1213
- BLOCKING_SLUG=$(echo "$BLOCKING_SLUG" | tr -d ' ')
1214
- if [ -z "$BLOCKING_SLUG" ]; then continue; fi
1215
-
1216
- # Resolve slug to issue number
1217
- BLOCKING_NUMBER=""
1218
- for MAPPING in "${SLUG_TO_NUMBER[@]}"; do
1219
- MAP_SLUG=$(echo "$MAPPING" | cut -d':' -f1)
1220
- MAP_NUM=$(echo "$MAPPING" | cut -d':' -f2)
1221
- if [ "$MAP_SLUG" = "$BLOCKING_SLUG" ]; then
1222
- BLOCKING_NUMBER="$MAP_NUM"
1223
- break
1224
- fi
1225
- done
1226
-
1227
- if [ -z "$BLOCKING_NUMBER" ]; then
1228
- echo " WARNING: Cannot resolve dependency slug '${BLOCKING_SLUG}' for issue #${DEPENDENT_NUMBER} — skipping"
1229
- continue
1230
- fi
1231
-
1232
- # Create label (idempotent)
1233
- gh label create "blocked-by:#${BLOCKING_NUMBER}" \
1234
- --description "Blocked by issue #${BLOCKING_NUMBER}" \
1235
- --color "e4e669" \
1236
- --force 2>/dev/null
1237
-
1238
- # Apply label to dependent issue
1239
- gh issue edit "${DEPENDENT_NUMBER}" --add-label "blocked-by:#${BLOCKING_NUMBER}" 2>/dev/null
1240
- echo " Applied: #${DEPENDENT_NUMBER} blocked-by:#${BLOCKING_NUMBER}"
1241
- DEPENDENCY_DISPLAY+=("#${DEPENDENT_NUMBER} blocked-by:#${BLOCKING_NUMBER}")
1242
-
1243
- # Build cross-refs.json entry
1244
- TIMESTAMP=$(node ~/.claude/get-shit-done/bin/gsd-tools.cjs current-timestamp --raw 2>/dev/null || date -u +"%Y-%m-%dT%H:%M:%SZ")
1245
- DEPENDENCY_ENTRIES+=("{\"a\":\"issue:${DEPENDENT_NUMBER}\",\"b\":\"issue:${BLOCKING_NUMBER}\",\"type\":\"blocked-by\",\"created\":\"${TIMESTAMP}\"}")
1246
- done
1247
- done
1248
-
1249
- # Write new entries to cross-refs.json
1250
- if [ ${#DEPENDENCY_ENTRIES[@]} -gt 0 ]; then
1251
- EXISTING_LINKS=$(python3 -c "
1252
- import json
1253
- try:
1254
- with open('${MGW_DIR}/cross-refs.json') as f:
1255
- d=json.load(f)
1256
- print(json.dumps(d.get('links',[])))
1257
- except:
1258
- print('[]')
1259
- ")
1260
- ENTRIES_JSON=$(printf '%s\n' "${DEPENDENCY_ENTRIES[@]}" | python3 -c "
1261
- import json,sys
1262
- entries=[json.loads(line) for line in sys.stdin if line.strip()]
1263
- print(json.dumps(entries))
1264
- ")
1265
- python3 -c "
1266
- import json
1267
- existing=json.loads('''${EXISTING_LINKS}''')
1268
- new_entries=json.loads('''${ENTRIES_JSON}''')
1269
- combined={'links': existing + new_entries}
1270
- with open('${MGW_DIR}/cross-refs.json','w') as f:
1271
- json.dump(combined, f, indent=2)
1272
- print('cross-refs.json updated with',len(new_entries),'entries')
1273
- "
1274
- fi
1275
- ```
1276
- </step>
1277
-
1278
- <step name="create_project_board">
1279
- **Create GitHub Projects v2 board and add all issues:**
1280
-
1281
- ```bash
1282
- OWNER=$(echo "$REPO" | cut -d'/' -f1)
1283
-
1284
- if [ "$EXTEND_MODE" = true ]; then
1285
- # Reuse existing project board — load number and URL from project.json
1286
- EXISTING_BOARD=$(python3 -c "
1287
- import json
1288
- p = json.load(open('${REPO_ROOT}/.mgw/project.json'))
1289
- board = p.get('project', {}).get('project_board', {})
1290
- print(json.dumps(board))
1291
- ")
1292
- PROJECT_NUMBER=$(echo "$EXISTING_BOARD" | python3 -c "import json,sys; print(json.load(sys.stdin).get('number',''))")
1293
- PROJECT_URL=$(echo "$EXISTING_BOARD" | python3 -c "import json,sys; print(json.load(sys.stdin).get('url',''))")
1294
-
1295
- if [ -n "$PROJECT_NUMBER" ]; then
1296
- echo " Reusing existing project board: #${PROJECT_NUMBER} — ${PROJECT_URL}"
1297
-
1298
- # Add only NEW issues to the existing board
1299
- for RECORD in "${ISSUE_RECORDS[@]}"; do
1300
- ISSUE_NUM=$(echo "$RECORD" | cut -d':' -f2)
1301
- ISSUE_URL="https://github.com/${REPO}/issues/${ISSUE_NUM}"
1302
- gh project item-add "$PROJECT_NUMBER" --owner "$OWNER" --url "$ISSUE_URL" 2>/dev/null || true
1303
- done
1304
- echo " Added ${TOTAL_ISSUES_CREATED} new issues to existing project board"
1305
- else
1306
- # Board not found — fall through to create a new one
1307
- EXTEND_MODE_BOARD=false
1308
- fi
1309
- fi
1310
-
1311
- if [ "$EXTEND_MODE" != true ] || [ "$EXTEND_MODE_BOARD" = false ]; then
1312
- # Create a new project board (standard flow or extend fallback)
1313
- PROJECT_RESP=$(gh project create --owner "$OWNER" --title "${PROJECT_NAME} Roadmap" --format json 2>&1)
1314
- PROJECT_NUMBER=$(echo "$PROJECT_RESP" | python3 -c "import json,sys; print(json.load(sys.stdin)['number'])" 2>/dev/null || echo "")
1315
- PROJECT_URL=$(echo "$PROJECT_RESP" | python3 -c "import json,sys; print(json.load(sys.stdin)['url'])" 2>/dev/null || echo "")
1316
-
1317
- if [ -n "$PROJECT_NUMBER" ]; then
1318
- echo " Created project board: #${PROJECT_NUMBER} — ${PROJECT_URL}"
1319
-
1320
- # Add all issues to the board
1321
- for RECORD in "${ISSUE_RECORDS[@]}"; do
1322
- ISSUE_NUM=$(echo "$RECORD" | cut -d':' -f2)
1323
- ISSUE_URL="https://github.com/${REPO}/issues/${ISSUE_NUM}"
1324
- gh project item-add "$PROJECT_NUMBER" --owner "$OWNER" --url "$ISSUE_URL" 2>/dev/null || true
1325
- done
1326
- echo " Added ${TOTAL_ISSUES_CREATED} issues to project board"
1327
- else
1328
- echo " WARNING: Failed to create project board: ${PROJECT_RESP}"
1329
- PROJECT_NUMBER=""
1330
- PROJECT_URL=""
1331
- fi
1332
- fi
1333
- ```
1334
-
1335
- Store `PROJECT_NUMBER` and `PROJECT_URL` for inclusion in project.json and the summary report.
122
+ @workflows/generate-template.md
1336
123
  </step>
1337
124
 
1338
- <step name="sync_milestone_to_board">
1339
- **Sync newly created issues onto the board as items with field values (non-blocking):**
125
+ <step name="discover_board">
126
+ **Non-blocking board discovery before GitHub structure creation:**
1340
127
 
1341
- This step runs after `create_project_board` (both init and extend modes). It adds each
1342
- newly created issue as a board item and sets Milestone, Phase, and GSD Route field values.
1343
- Board item IDs are collected here and stored in project.json (as `board_item_id` per issue).
128
+ If `project.project_board.node_id` is empty, search GitHub for an existing board whose
129
+ title contains the project name. If found, register it in project.json so the board sync
130
+ step inside `create-github-structure.md` can use it immediately without requiring a
131
+ separate `/mgw:board create` run.
1344
132
 
1345
- If no board is configured (PROJECT_NUMBER is empty) or the board has no custom fields
1346
- configured (node_id or fields missing from project.json), skip silently.
1347
-
1348
- Non-blocking: any GraphQL error is logged as a WARNING and does not halt the pipeline.
133
+ Silently skipped if board is already registered or if the API call fails.
1349
134
 
1350
135
  ```bash
1351
- # Load board field metadata from project.json
1352
- BOARD_NODE_ID=$(python3 -c "
136
+ EXISTING_NODE_ID=$(python3 -c "
1353
137
  import json
1354
138
  try:
1355
- p = json.load(open('${MGW_DIR}/project.json'))
1356
- print(p.get('project', {}).get('project_board', {}).get('node_id', ''))
139
+ p = json.load(open('${MGW_DIR}/project.json'))
140
+ print(p.get('project', {}).get('project_board', {}).get('node_id', ''))
1357
141
  except:
1358
- print('')
142
+ print('')
1359
143
  " 2>/dev/null || echo "")
1360
144
 
1361
- BOARD_FIELDS_JSON=$(python3 -c "
1362
- import json
1363
- try:
1364
- p = json.load(open('${MGW_DIR}/project.json'))
1365
- fields = p.get('project', {}).get('project_board', {}).get('fields', {})
1366
- print(json.dumps(fields))
1367
- except:
1368
- print('{}')
1369
- " 2>/dev/null || echo "{}")
1370
-
1371
- # Resolve field IDs from stored metadata
1372
- MILESTONE_FIELD_ID=$(echo "$BOARD_FIELDS_JSON" | python3 -c "
1373
- import json,sys
1374
- fields = json.load(sys.stdin)
1375
- print(fields.get('milestone', {}).get('field_id', ''))
145
+ if [ -z "$EXISTING_NODE_ID" ]; then
146
+ DISCOVERED=$(node -e "
147
+ const { findExistingBoard, getProjectFields } = require('./lib/github.cjs');
148
+ const board = findExistingBoard('${OWNER}', '${PROJECT_NAME}');
149
+ if (!board) { process.stdout.write(''); process.exit(0); }
150
+ const fields = getProjectFields('${OWNER}', board.number) || {};
151
+ console.log(JSON.stringify({ ...board, fields }));
1376
152
  " 2>/dev/null || echo "")
1377
153
 
1378
- PHASE_FIELD_ID=$(echo "$BOARD_FIELDS_JSON" | python3 -c "
1379
- import json,sys
1380
- fields = json.load(sys.stdin)
1381
- print(fields.get('phase', {}).get('field_id', ''))
1382
- " 2>/dev/null || echo "")
1383
-
1384
- GSD_ROUTE_FIELD_ID=$(echo "$BOARD_FIELDS_JSON" | python3 -c "
1385
- import json,sys
1386
- fields = json.load(sys.stdin)
1387
- print(fields.get('gsd_route', {}).get('field_id', ''))
1388
- " 2>/dev/null || echo "")
1389
-
1390
- GSD_ROUTE_OPTIONS=$(echo "$BOARD_FIELDS_JSON" | python3 -c "
1391
- import json,sys
1392
- fields = json.load(sys.stdin)
1393
- print(json.dumps(fields.get('gsd_route', {}).get('options', {})))
1394
- " 2>/dev/null || echo "{}")
1395
-
1396
- # Determine if sync is possible
1397
- BOARD_SYNC_ENABLED=false
1398
- if [ -n "$PROJECT_NUMBER" ] && [ -n "$BOARD_NODE_ID" ]; then
1399
- BOARD_SYNC_ENABLED=true
1400
- echo ""
1401
- echo "Syncing ${TOTAL_ISSUES_CREATED} issues onto board #${PROJECT_NUMBER}..."
1402
- elif [ -n "$PROJECT_NUMBER" ] && [ -z "$BOARD_NODE_ID" ]; then
1403
- echo ""
1404
- echo "NOTE: Board #${PROJECT_NUMBER} exists but custom fields not configured."
1405
- echo " Run /mgw:board create to set up fields, then board sync will be available."
1406
- fi
1407
-
1408
- # ISSUE_RECORD format: "milestone_index:issue_number:title:phase_num:phase_name:gsd_route:depends_on"
1409
- # ITEM_ID_MAP accumulates: "issue_number:item_id" for project.json storage
1410
- ITEM_ID_MAP=()
1411
- BOARD_SYNC_WARNINGS=()
1412
-
1413
- if [ "$BOARD_SYNC_ENABLED" = "true" ]; then
1414
- for RECORD in "${ISSUE_RECORDS[@]}"; do
1415
- ISSUE_NUM=$(echo "$RECORD" | cut -d':' -f2)
1416
- ISSUE_PHASE_NUM=$(echo "$RECORD" | cut -d':' -f4)
1417
- ISSUE_PHASE_NAME=$(echo "$RECORD" | cut -d':' -f5)
1418
- ISSUE_GSD_ROUTE=$(echo "$RECORD" | cut -d':' -f6)
1419
- ISSUE_MILESTONE_IDX=$(echo "$RECORD" | cut -d':' -f1)
1420
-
1421
- # Get milestone name for this issue
1422
- ISSUE_MILESTONE_NAME=$(python3 -c "
154
+ if [ -n "$DISCOVERED" ]; then
155
+ python3 << PYEOF
1423
156
  import json
1424
- try:
1425
- d = json.load(open('/tmp/mgw-template.json'))
1426
- print(d['milestones'][${ISSUE_MILESTONE_IDX}]['name'])
1427
- except:
1428
- print('')
1429
- " 2>/dev/null || echo "")
1430
-
1431
- # Resolve GitHub issue node ID (needed for addProjectV2ItemById)
1432
- ISSUE_NODE_ID=$(gh api graphql -f query='
1433
- query($owner: String!, $repo: String!, $number: Int!) {
1434
- repository(owner: $owner, name: $repo) {
1435
- issue(number: $number) { id }
1436
- }
1437
- }
1438
- ' -f owner="$OWNER" -f repo="$REPO_NAME" -F number="${ISSUE_NUM}" \
1439
- --jq '.data.repository.issue.id' 2>/dev/null || echo "")
1440
-
1441
- if [ -z "$ISSUE_NODE_ID" ]; then
1442
- BOARD_SYNC_WARNINGS+=("WARNING: Could not resolve node ID for issue #${ISSUE_NUM} — skipping board sync for this issue")
1443
- continue
1444
- fi
1445
-
1446
- # Add issue to board
1447
- ADD_RESULT=$(gh api graphql -f query='
1448
- mutation($projectId: ID!, $contentId: ID!) {
1449
- addProjectV2ItemById(input: {
1450
- projectId: $projectId
1451
- contentId: $contentId
1452
- }) {
1453
- item { id }
1454
- }
1455
- }
1456
- ' -f projectId="$BOARD_NODE_ID" -f contentId="$ISSUE_NODE_ID" 2>/dev/null)
1457
-
1458
- ITEM_ID=$(echo "$ADD_RESULT" | python3 -c "
1459
- import json,sys
1460
- try:
1461
- d = json.load(sys.stdin)
1462
- print(d['data']['addProjectV2ItemById']['item']['id'])
1463
- except:
1464
- print('')
1465
- " 2>/dev/null || echo "")
1466
-
1467
- if [ -z "$ITEM_ID" ]; then
1468
- BOARD_SYNC_WARNINGS+=("WARNING: Failed to add issue #${ISSUE_NUM} to board")
1469
- continue
1470
- fi
1471
-
1472
- echo " Added #${ISSUE_NUM} to board (item: ${ITEM_ID})"
1473
- ITEM_ID_MAP+=("${ISSUE_NUM}:${ITEM_ID}")
1474
-
1475
- # Set Milestone field (TEXT)
1476
- if [ -n "$MILESTONE_FIELD_ID" ] && [ -n "$ISSUE_MILESTONE_NAME" ]; then
1477
- gh api graphql -f query='
1478
- mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $value: String!) {
1479
- updateProjectV2ItemFieldValue(input: {
1480
- projectId: $projectId
1481
- itemId: $itemId
1482
- fieldId: $fieldId
1483
- value: { text: $value }
1484
- }) { projectV2Item { id } }
1485
- }
1486
- ' -f projectId="$BOARD_NODE_ID" -f itemId="$ITEM_ID" \
1487
- -f fieldId="$MILESTONE_FIELD_ID" -f value="$ISSUE_MILESTONE_NAME" \
1488
- 2>/dev/null || BOARD_SYNC_WARNINGS+=("WARNING: Failed to set Milestone field on board item for #${ISSUE_NUM}")
1489
- fi
1490
-
1491
- # Set Phase field (TEXT) — "Phase N: Phase Name"
1492
- if [ -n "$PHASE_FIELD_ID" ]; then
1493
- PHASE_DISPLAY="Phase ${ISSUE_PHASE_NUM}: ${ISSUE_PHASE_NAME}"
1494
- gh api graphql -f query='
1495
- mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $value: String!) {
1496
- updateProjectV2ItemFieldValue(input: {
1497
- projectId: $projectId
1498
- itemId: $itemId
1499
- fieldId: $fieldId
1500
- value: { text: $value }
1501
- }) { projectV2Item { id } }
1502
- }
1503
- ' -f projectId="$BOARD_NODE_ID" -f itemId="$ITEM_ID" \
1504
- -f fieldId="$PHASE_FIELD_ID" -f value="$PHASE_DISPLAY" \
1505
- 2>/dev/null || BOARD_SYNC_WARNINGS+=("WARNING: Failed to set Phase field on board item for #${ISSUE_NUM}")
1506
- fi
1507
-
1508
- # Set GSD Route field (SINGLE_SELECT) — look up option ID from stored map
1509
- if [ -n "$GSD_ROUTE_FIELD_ID" ]; then
1510
- # Map template gsd_route to board option key (e.g. "plan-phase" → "gsd:plan-phase")
1511
- # GSD_ROUTE_OPTIONS stores keys like "gsd:quick", "gsd:plan-phase", etc.
1512
- ROUTE_OPTION_ID=$(echo "$GSD_ROUTE_OPTIONS" | python3 -c "
1513
- import json,sys
1514
- opts = json.load(sys.stdin)
1515
- # Try exact match on gsd: prefix first, then plain match
1516
- route = '${ISSUE_GSD_ROUTE}'
1517
- for key, val in opts.items():
1518
- if key == 'gsd:' + route or key == route:
1519
- print(val)
1520
- sys.exit(0)
1521
- # Fallback: plain match on the route name without prefix
1522
- for key, val in opts.items():
1523
- if key.endswith(':' + route) or key == route:
1524
- print(val)
1525
- sys.exit(0)
1526
- print('')
1527
- " 2>/dev/null || echo "")
1528
-
1529
- if [ -n "$ROUTE_OPTION_ID" ]; then
1530
- gh api graphql -f query='
1531
- mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) {
1532
- updateProjectV2ItemFieldValue(input: {
1533
- projectId: $projectId
1534
- itemId: $itemId
1535
- fieldId: $fieldId
1536
- value: { singleSelectOptionId: $optionId }
1537
- }) { projectV2Item { id } }
1538
- }
1539
- ' -f projectId="$BOARD_NODE_ID" -f itemId="$ITEM_ID" \
1540
- -f fieldId="$GSD_ROUTE_FIELD_ID" -f optionId="$ROUTE_OPTION_ID" \
1541
- 2>/dev/null || BOARD_SYNC_WARNINGS+=("WARNING: Failed to set GSD Route field on board item for #${ISSUE_NUM}")
1542
- fi
1543
- fi
1544
- done
1545
-
1546
- if [ ${#BOARD_SYNC_WARNINGS[@]} -gt 0 ]; then
1547
- echo ""
1548
- echo "Board sync warnings:"
1549
- for W in "${BOARD_SYNC_WARNINGS[@]}"; do
1550
- echo " $W"
1551
- done
1552
- fi
1553
-
1554
- BOARD_SYNC_COUNT=$((${#ITEM_ID_MAP[@]}))
1555
- echo " Board sync complete: ${BOARD_SYNC_COUNT}/${TOTAL_ISSUES_CREATED} issues synced"
1556
- fi
1557
- ```
1558
- </step>
1559
-
1560
- <step name="write_project_json">
1561
- **Write .mgw/project.json with project state**
1562
-
1563
- Build and write the project.json using the schema from 03-RESEARCH.md Pattern 4:
1564
-
1565
- ```bash
1566
- CREATED=$(node ~/.claude/get-shit-done/bin/gsd-tools.cjs current-timestamp --raw 2>/dev/null || date -u +"%Y-%m-%dT%H:%M:%SZ")
1567
- ```
1568
157
 
1569
- Construct the JSON using python3 (to handle proper JSON encoding):
158
+ with open('${MGW_DIR}/project.json') as f:
159
+ project = json.load(f)
1570
160
 
1571
- ```python
1572
- import json, sys
1573
-
1574
- template_data = json.load(open('/tmp/mgw-template.json'))
1575
-
1576
- milestones_out = []
1577
- global_phase_num = 0
1578
-
1579
- for m_idx, milestone in enumerate(template_data['milestones']):
1580
- # Find GitHub data from MILESTONE_MAP
1581
- m_entry = MILESTONE_MAP[m_idx] # "idx:number:id:url"
1582
- m_number = int(m_entry.split(':')[1]) if m_entry.split(':')[1] != 'FAILED' else None
1583
- m_id_val = int(m_entry.split(':')[2]) if m_entry.split(':')[1] != 'FAILED' else None
1584
- m_url = m_entry.split(':',3)[3] if m_entry.split(':')[1] != 'FAILED' else ''
1585
-
1586
- issues_out = []
1587
- for phase in milestone.get('phases', []):
1588
- global_phase_num += 1
1589
- for issue in phase.get('issues', []):
1590
- # Find github number from SLUG_TO_NUMBER
1591
- slug = slugify(issue['title'])[:40]
1592
- gh_num = SLUG_TO_NUMBER_MAP.get(slug)
1593
- # Look up board_item_id from ITEM_ID_MAP if available
1594
- item_id = ITEM_ID_MAP_DICT.get(gh_num, None)
1595
- issues_out.append({
1596
- "github_number": gh_num,
1597
- "title": issue['title'],
1598
- "phase_number": global_phase_num,
1599
- "phase_name": phase['name'],
1600
- "gsd_route": phase.get('gsd_route', 'plan-phase'),
1601
- "labels": issue.get('labels', []),
1602
- "depends_on_slugs": issue.get('depends_on', []),
1603
- "pipeline_stage": "new",
1604
- "board_item_id": item_id
1605
- })
1606
-
1607
- milestones_out.append({
1608
- "github_number": m_number,
1609
- "github_id": m_id_val,
1610
- "github_url": m_url,
1611
- "name": milestone['name'],
1612
- "template_milestone_index": m_idx,
1613
- "issues": issues_out
1614
- })
1615
-
1616
- project_json = {
1617
- "project": {
1618
- "name": PROJECT_NAME,
1619
- "description": DESCRIPTION,
1620
- "repo": REPO,
1621
- "template": GENERATED_TYPE,
1622
- "created": CREATED,
1623
- "project_board": {
1624
- "number": PROJECT_NUMBER or None,
1625
- "url": PROJECT_URL or None
1626
- }
1627
- },
1628
- "milestones": milestones_out,
1629
- "current_milestone": 1,
1630
- "phase_map": PHASE_MAP
161
+ d = json.loads('${DISCOVERED}')
162
+ project['project']['project_board'] = {
163
+ 'number': d['number'],
164
+ 'url': d['url'],
165
+ 'node_id': d['nodeId'],
166
+ 'fields': d.get('fields', {})
1631
167
  }
1632
- print(json.dumps(project_json, indent=2))
1633
- ```
1634
168
 
1635
- Write the output to `${MGW_DIR}/project.json`.
169
+ with open('${MGW_DIR}/project.json', 'w') as f:
170
+ json.dump(project, f, indent=2)
1636
171
 
1637
- **In practice** (bash + python3 inline): construct the full project.json by assembling
1638
- data from `/tmp/mgw-template.json`, the milestone map built in create_milestones, the slug-to-number
1639
- map from create_issues, and the phase_map built during create_issues. Write using:
1640
-
1641
- ```bash
1642
- python3 << 'PYEOF' > "${MGW_DIR}/project.json"
1643
- import json, sys
1644
-
1645
- # Read template data from the validated generated file
1646
- template_data = json.load(open('/tmp/mgw-template.json'))
1647
-
1648
- # Build ITEM_ID_MAP_DICT from bash ITEM_ID_MAP array ("issue_num:item_id" entries)
1649
- # This dict maps github_number (int) -> board_item_id (str)
1650
- ITEM_ID_MAP_DICT = {}
1651
- for entry in [x for x in '''${ITEM_ID_MAP[*]}'''.split() if ':' in x]:
1652
- parts = entry.split(':', 1)
1653
- try:
1654
- ITEM_ID_MAP_DICT[int(parts[0])] = parts[1]
1655
- except (ValueError, IndexError):
1656
- pass
1657
-
1658
- # ... (construct from available bash variables — see pseudocode above)
172
+ print(f"Board auto-discovered and registered: #{d['number']} {d['url']}")
1659
173
  PYEOF
1660
- ```
1661
-
1662
- The simplest implementation: build the JSON structure incrementally during the
1663
- issue/milestone creation steps (maintaining bash arrays), then assemble them into
1664
- a python3 dictionary and write with `json.dumps(indent=2)` at this step.
1665
-
1666
- The `ITEM_ID_MAP` bash array (populated in `sync_milestone_to_board`) contains entries
1667
- in `"issue_number:board_item_id"` format. Decode it into `ITEM_ID_MAP_DICT` (as shown
1668
- above) and use it when building each issue record so `board_item_id` is stored.
1669
- If board sync was skipped (ITEM_ID_MAP is empty), `board_item_id` is null for all issues.
1670
-
1671
- Note: use `GENERATED_TYPE` (read from `/tmp/mgw-template.json`) for the `template` field in project.json,
1672
- not a hardcoded template name.
1673
-
1674
- **In extend mode, use mergeProjectState instead of full write:**
1675
-
1676
- When `EXTEND_MODE=true`, do NOT write a full project.json. Instead, build only the new milestones
1677
- and phase_map entries (with `template_milestone_index` offset by `EXISTING_MILESTONE_COUNT`), then call:
1678
-
1679
- ```bash
1680
- # Compute the current_milestone pointer for the first new milestone (1-indexed)
1681
- NEW_CURRENT_MILESTONE=$((EXISTING_MILESTONE_COUNT + 1))
1682
-
1683
- # Call mergeProjectState via Node — appends without overwriting existing data
1684
- node -e "
1685
- const { mergeProjectState } = require('${REPO_ROOT}/lib/state.cjs');
1686
- const newMilestones = JSON.parse(process.argv[1]);
1687
- const newPhaseMap = JSON.parse(process.argv[2]);
1688
- const newCurrentMilestone = parseInt(process.argv[3]);
1689
- const merged = mergeProjectState(newMilestones, newPhaseMap, newCurrentMilestone);
1690
- console.log('project.json updated: ' + merged.milestones.length + ' total milestones');
1691
- " "$NEW_MILESTONES_JSON" "$NEW_PHASE_MAP_JSON" "$NEW_CURRENT_MILESTONE"
1692
- ```
1693
-
1694
- Where `NEW_MILESTONES_JSON` and `NEW_PHASE_MAP_JSON` are JSON-encoded strings built from only
1695
- the newly created milestones/phases (matching the existing project.json schema). The
1696
- `template_milestone_index` for each new milestone should be offset by `EXISTING_MILESTONE_COUNT`
1697
- so indices remain globally unique.
1698
-
1699
- When `EXTEND_MODE` is false, the existing write logic (full project.json from scratch) is unchanged.
1700
-
1701
- **Extend mode: verify new milestone GSD linkage**
1702
-
1703
- After writing the updated project.json in extend mode, report the GSD linkage status for each newly added milestone:
1704
-
1705
- ```bash
1706
- if [ "$EXTEND_MODE" = true ]; then
1707
- echo ""
1708
- echo "New milestone linkage status:"
1709
- for MILESTONE in "${NEW_MILESTONES[@]}"; do
1710
- MILE_NAME=$(echo "$MILESTONE" | python3 -c "import json,sys; print(json.load(sys.stdin)['name'])" 2>/dev/null || echo "unknown")
1711
- echo " o '${MILE_NAME}' — no GSD milestone linked yet"
1712
- echo " -> Run /gsd:new-milestone after completing the previous milestone to link"
1713
- done
1714
- echo ""
174
+ fi
1715
175
  fi
1716
176
  ```
1717
177
  </step>
1718
178
 
1719
- <step name="report">
1720
- **Display post-init summary:**
1721
-
1722
- In extend mode, show the extended banner:
1723
-
1724
- ```
1725
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1726
- MGW ► PROJECT EXTENDED — {PROJECT_NAME}
1727
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1728
-
1729
- Extended with {NEW_MILESTONE_COUNT} new milestones (total: {TOTAL_MILESTONES})
1730
- Phase numbering: continued from {EXISTING_PHASE_COUNT} (new phases: {EXISTING_PHASE_COUNT+1}–{NEW_MAX_PHASE})
1731
- Board: reused #{PROJECT_NUMBER}
1732
-
1733
- (remaining output follows the same format as project init for the new milestones/issues)
1734
- ```
1735
-
1736
- In standard (non-extend) mode, show the original init banner:
1737
-
1738
- ```
1739
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1740
- MGW ► PROJECT INIT — {PROJECT_NAME}
1741
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1742
-
1743
- Type: {GENERATED_TYPE} ({MILESTONE_COUNT} milestones, {TOTAL_PHASES} phases)
1744
- Repo: {REPO}
1745
- Board: ${PROJECT_URL ? PROJECT_URL : '(not created)'}
1746
-
1747
- Milestones created:
1748
- #{number} {name} → {url}
1749
- ...
1750
-
1751
- Issues scaffolded: {TOTAL_ISSUES_CREATED} total across {TOTAL_PHASES} phases
1752
-
1753
- Dependencies:
1754
- {list of "#{dependent} blocked-by:#{blocking}" entries}
1755
- (or: "None declared in template")
1756
-
1757
- State:
1758
- .mgw/project.json written
1759
- .mgw/cross-refs.json {updated with N entries|unchanged}
1760
-
1761
- Next:
1762
- /gsd:new-milestone Create GSD ROADMAP.md (if needed)
1763
- /mgw:milestone start Execute first milestone
1764
- ```
1765
-
1766
- If any milestones or issues failed to create, include:
1767
- ```
1768
- Warnings:
1769
- Failed to create milestone: {name}
1770
- Failed to create issue: {title}
1771
- (review above and create missing items manually or re-run)
1772
- ```
1773
-
1774
- **CRITICAL BOUNDARY (PROJ-05):** This command ends here. It does NOT:
1775
- - Trigger /mgw:milestone or any execution workflow
1776
- - Write to .planning/ (GSD owns that directory — run /gsd:new-milestone to scaffold)
1777
- - Execute any issues or plans
179
+ <step name="create_github_structure">
180
+ @workflows/create-github-structure.md
1778
181
  </step>
1779
182
 
1780
183
  </process>
@@ -1789,6 +192,7 @@ Warnings:
1789
192
  - [ ] Slug-to-number mapping built during Pass 1b
1790
193
  - [ ] Dependency labels applied (Pass 2) — blocked-by:#N on dependent issues
1791
194
  - [ ] cross-refs.json updated with dependency entries
195
+ - [ ] Board discovery: if project_board.node_id empty before create_github_structure runs, findExistingBoard() called; if found, registered silently in project.json
1792
196
  - [ ] Board sync: if board configured (PROJECT_NUMBER + BOARD_NODE_ID in project.json), each new issue added as board item
1793
197
  - [ ] Board sync: Milestone, Phase, and GSD Route fields set on each board item where field IDs are available
1794
198
  - [ ] Board sync: board_item_id stored per issue in project.json (null if board sync skipped or failed)