@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.
- package/README.md +20 -9
- package/commands/board.md +75 -0
- package/commands/milestone.md +138 -15
- package/commands/project.md +55 -1651
- package/commands/run.md +285 -11
- package/commands/sync.md +332 -1
- package/dist/bin/mgw.cjs +2 -2
- package/dist/{claude-Vp9qvImH.cjs → claude-Dk1oVsaG.cjs} +156 -0
- package/dist/lib/index.cjs +237 -12
- package/package.json +1 -1
package/commands/project.md
CHANGED
|
@@ -68,1713 +68,116 @@ fi
|
|
|
68
68
|
</step>
|
|
69
69
|
|
|
70
70
|
<step name="align_from_gsd">
|
|
71
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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="
|
|
254
|
-
**
|
|
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
|
-
|
|
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
|
|
91
|
+
**Gather project inputs (non-extend path only):**
|
|
744
92
|
|
|
745
|
-
If STATE_CLASS = Fresh: skip
|
|
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
|
-
|
|
96
|
+
Otherwise, ask conversationally:
|
|
748
97
|
|
|
749
98
|
**Question 1:** "What are you building?"
|
|
750
|
-
- Capture
|
|
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
|
|
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.
|
|
104
|
+
Do NOT ask the user to pick a template type.
|
|
756
105
|
|
|
757
|
-
**Infer parameters from environment
|
|
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
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
elif [ -f "${REPO_ROOT}/
|
|
767
|
-
|
|
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
|
-
|
|
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="
|
|
1339
|
-
**
|
|
125
|
+
<step name="discover_board">
|
|
126
|
+
**Non-blocking board discovery before GitHub structure creation:**
|
|
1340
127
|
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1352
|
-
BOARD_NODE_ID=$(python3 -c "
|
|
136
|
+
EXISTING_NODE_ID=$(python3 -c "
|
|
1353
137
|
import json
|
|
1354
138
|
try:
|
|
1355
|
-
|
|
1356
|
-
|
|
139
|
+
p = json.load(open('${MGW_DIR}/project.json'))
|
|
140
|
+
print(p.get('project', {}).get('project_board', {}).get('node_id', ''))
|
|
1357
141
|
except:
|
|
1358
|
-
|
|
142
|
+
print('')
|
|
1359
143
|
" 2>/dev/null || echo "")
|
|
1360
144
|
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
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
|
-
|
|
1379
|
-
|
|
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
|
-
|
|
158
|
+
with open('${MGW_DIR}/project.json') as f:
|
|
159
|
+
project = json.load(f)
|
|
1570
160
|
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
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
|
-
|
|
169
|
+
with open('${MGW_DIR}/project.json', 'w') as f:
|
|
170
|
+
json.dump(project, f, indent=2)
|
|
1636
171
|
|
|
1637
|
-
|
|
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="
|
|
1720
|
-
|
|
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)
|