@snipcodeit/mgw 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +517 -0
- package/bin/mgw-install.cjs +81 -0
- package/commands/ask.md +416 -0
- package/commands/assign.md +333 -0
- package/commands/board.md +1679 -0
- package/commands/help.md +119 -0
- package/commands/init.md +250 -0
- package/commands/issue.md +469 -0
- package/commands/issues.md +109 -0
- package/commands/link.md +122 -0
- package/commands/milestone.md +952 -0
- package/commands/next.md +375 -0
- package/commands/pr.md +277 -0
- package/commands/project.md +1801 -0
- package/commands/review.md +260 -0
- package/commands/roadmap.md +489 -0
- package/commands/run.md +1282 -0
- package/commands/status.md +526 -0
- package/commands/sync.md +243 -0
- package/commands/update.md +282 -0
- package/commands/workflows/board-sync.md +404 -0
- package/commands/workflows/github.md +385 -0
- package/commands/workflows/gsd.md +377 -0
- package/commands/workflows/state.md +412 -0
- package/commands/workflows/validation.md +144 -0
- package/dist/bin/mgw.cjs +291 -0
- package/dist/claude-Vp9qvImH.cjs +466 -0
- package/dist/lib/index.cjs +395 -0
- package/package.json +51 -0
- package/templates/schema.json +164 -0
- package/templates/vision-brief-schema.json +98 -0
package/commands/next.md
ADDED
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mgw:next
|
|
3
|
+
description: Show next unblocked issue — what to work on now, based on declared dependencies
|
|
4
|
+
argument-hint: ""
|
|
5
|
+
allowed-tools:
|
|
6
|
+
- Bash
|
|
7
|
+
- Read
|
|
8
|
+
- AskUserQuestion
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
<objective>
|
|
12
|
+
Surface the next unblocked issue across the current milestone based on dependency
|
|
13
|
+
declarations. Local-first: reads project.json for fast answer, then does a quick
|
|
14
|
+
`gh` API check to verify the issue is still open.
|
|
15
|
+
|
|
16
|
+
This is a read-only command — it does NOT modify state, run pipelines, or create
|
|
17
|
+
worktrees. After displaying the brief, it offers to run `/mgw:run` for the
|
|
18
|
+
recommended issue.
|
|
19
|
+
</objective>
|
|
20
|
+
|
|
21
|
+
<execution_context>
|
|
22
|
+
@~/.claude/commands/mgw/workflows/state.md
|
|
23
|
+
@~/.claude/commands/mgw/workflows/github.md
|
|
24
|
+
</execution_context>
|
|
25
|
+
|
|
26
|
+
<process>
|
|
27
|
+
|
|
28
|
+
<step name="load_state">
|
|
29
|
+
**Load project.json and validate:**
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
REPO_ROOT=$(git rev-parse --show-toplevel)
|
|
33
|
+
MGW_DIR="${REPO_ROOT}/.mgw"
|
|
34
|
+
|
|
35
|
+
if [ ! -f "${MGW_DIR}/project.json" ]; then
|
|
36
|
+
echo "No project initialized. Run /mgw:project first."
|
|
37
|
+
exit 1
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
PROJECT_JSON=$(cat "${MGW_DIR}/project.json")
|
|
41
|
+
|
|
42
|
+
# Resolve active milestone index using state resolution (supports both schema versions)
|
|
43
|
+
ACTIVE_IDX=$(node -e "
|
|
44
|
+
const { loadProjectState, resolveActiveMilestoneIndex } = require('./lib/state.cjs');
|
|
45
|
+
const state = loadProjectState();
|
|
46
|
+
console.log(resolveActiveMilestoneIndex(state));
|
|
47
|
+
")
|
|
48
|
+
|
|
49
|
+
# Get milestone data
|
|
50
|
+
MILESTONE_DATA=$(echo "$PROJECT_JSON" | python3 -c "
|
|
51
|
+
import json,sys
|
|
52
|
+
p = json.load(sys.stdin)
|
|
53
|
+
idx = ${ACTIVE_IDX}
|
|
54
|
+
if idx < 0 or idx >= len(p['milestones']):
|
|
55
|
+
print(json.dumps({'error': 'No more milestones'}))
|
|
56
|
+
sys.exit(0)
|
|
57
|
+
m = p['milestones'][idx]
|
|
58
|
+
print(json.dumps(m))
|
|
59
|
+
")
|
|
60
|
+
|
|
61
|
+
MILESTONE_NAME=$(echo "$MILESTONE_DATA" | python3 -c "import json,sys; print(json.load(sys.stdin)['name'])")
|
|
62
|
+
ISSUES_JSON=$(echo "$MILESTONE_DATA" | python3 -c "import json,sys; print(json.dumps(json.load(sys.stdin)['issues']))")
|
|
63
|
+
TOTAL_ISSUES=$(echo "$ISSUES_JSON" | python3 -c "import json,sys; print(len(json.load(sys.stdin)))")
|
|
64
|
+
```
|
|
65
|
+
</step>
|
|
66
|
+
|
|
67
|
+
<step name="resolve_dependencies">
|
|
68
|
+
**Compute dependency graph and ordered issue list:**
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
DEPENDENCY_RESULT=$(echo "$ISSUES_JSON" | python3 -c "
|
|
72
|
+
import json, sys
|
|
73
|
+
from collections import defaultdict
|
|
74
|
+
|
|
75
|
+
issues = json.load(sys.stdin)
|
|
76
|
+
|
|
77
|
+
# Build slug-to-issue and number-to-issue mappings
|
|
78
|
+
slug_to_issue = {}
|
|
79
|
+
num_to_issue = {}
|
|
80
|
+
slug_to_num = {}
|
|
81
|
+
for issue in issues:
|
|
82
|
+
title = issue.get('title', '')
|
|
83
|
+
slug = title.lower().replace(' ', '-')[:40]
|
|
84
|
+
slug_to_issue[slug] = issue
|
|
85
|
+
num_to_issue[issue['github_number']] = issue
|
|
86
|
+
slug_to_num[slug] = issue['github_number']
|
|
87
|
+
|
|
88
|
+
# Build forward and reverse dependency maps
|
|
89
|
+
# forward: issue -> what it depends on
|
|
90
|
+
# reverse: issue -> what it unblocks
|
|
91
|
+
forward_deps = {} # slug -> [blocking_slugs]
|
|
92
|
+
reverse_deps = defaultdict(list) # slug -> [dependent_slugs]
|
|
93
|
+
|
|
94
|
+
for issue in issues:
|
|
95
|
+
title = issue.get('title', '')
|
|
96
|
+
slug = title.lower().replace(' ', '-')[:40]
|
|
97
|
+
deps = issue.get('depends_on_slugs', [])
|
|
98
|
+
forward_deps[slug] = deps
|
|
99
|
+
for dep_slug in deps:
|
|
100
|
+
if dep_slug in slug_to_issue:
|
|
101
|
+
reverse_deps[dep_slug].append(slug)
|
|
102
|
+
|
|
103
|
+
# Find unblocked issues:
|
|
104
|
+
# - pipeline_stage == 'new' (not done, not failed, not in-progress)
|
|
105
|
+
# - ALL depends_on issues have pipeline_stage == 'done'
|
|
106
|
+
unblocked = []
|
|
107
|
+
blocked = []
|
|
108
|
+
done_count = 0
|
|
109
|
+
failed_issues = []
|
|
110
|
+
|
|
111
|
+
for issue in issues:
|
|
112
|
+
title = issue.get('title', '')
|
|
113
|
+
slug = title.lower().replace(' ', '-')[:40]
|
|
114
|
+
stage = issue.get('pipeline_stage', 'new')
|
|
115
|
+
|
|
116
|
+
if stage == 'done':
|
|
117
|
+
done_count += 1
|
|
118
|
+
continue
|
|
119
|
+
if stage == 'failed':
|
|
120
|
+
failed_issues.append(issue)
|
|
121
|
+
continue
|
|
122
|
+
if stage not in ('new',):
|
|
123
|
+
continue # in-progress, skip for now
|
|
124
|
+
|
|
125
|
+
# Check if all dependencies are done
|
|
126
|
+
deps = issue.get('depends_on_slugs', [])
|
|
127
|
+
all_deps_done = True
|
|
128
|
+
blocking_info = []
|
|
129
|
+
for dep_slug in deps:
|
|
130
|
+
if dep_slug in slug_to_issue:
|
|
131
|
+
dep_issue = slug_to_issue[dep_slug]
|
|
132
|
+
if dep_issue.get('pipeline_stage') != 'done':
|
|
133
|
+
all_deps_done = False
|
|
134
|
+
blocking_info.append({
|
|
135
|
+
'number': dep_issue['github_number'],
|
|
136
|
+
'title': dep_issue['title'],
|
|
137
|
+
'stage': dep_issue.get('pipeline_stage', 'new')
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
if all_deps_done:
|
|
141
|
+
# Compute what this issue unblocks
|
|
142
|
+
unblocks = []
|
|
143
|
+
for dep_slug in reverse_deps.get(slug, []):
|
|
144
|
+
if dep_slug in slug_to_issue:
|
|
145
|
+
dep_issue = slug_to_issue[dep_slug]
|
|
146
|
+
unblocks.append({
|
|
147
|
+
'number': dep_issue['github_number'],
|
|
148
|
+
'title': dep_issue['title']
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
# Compute resolved dependencies
|
|
152
|
+
resolved_deps = []
|
|
153
|
+
for dep_slug in deps:
|
|
154
|
+
if dep_slug in slug_to_issue:
|
|
155
|
+
dep_issue = slug_to_issue[dep_slug]
|
|
156
|
+
resolved_deps.append({
|
|
157
|
+
'number': dep_issue['github_number'],
|
|
158
|
+
'title': dep_issue['title']
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
unblocked.append({
|
|
162
|
+
'issue': issue,
|
|
163
|
+
'unblocks': unblocks,
|
|
164
|
+
'resolved_deps': resolved_deps
|
|
165
|
+
})
|
|
166
|
+
else:
|
|
167
|
+
blocked.append({
|
|
168
|
+
'issue': issue,
|
|
169
|
+
'blocked_by': blocking_info
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
# Sort unblocked by phase_number (first = recommended)
|
|
173
|
+
unblocked.sort(key=lambda x: x['issue'].get('phase_number', 999))
|
|
174
|
+
|
|
175
|
+
result = {
|
|
176
|
+
'unblocked': unblocked,
|
|
177
|
+
'blocked': blocked,
|
|
178
|
+
'done_count': done_count,
|
|
179
|
+
'total': len(issues),
|
|
180
|
+
'failed': [{'number': i['github_number'], 'title': i['title']} for i in failed_issues]
|
|
181
|
+
}
|
|
182
|
+
print(json.dumps(result, indent=2))
|
|
183
|
+
")
|
|
184
|
+
```
|
|
185
|
+
</step>
|
|
186
|
+
|
|
187
|
+
<step name="handle_nothing_unblocked">
|
|
188
|
+
**If no issues are unblocked:**
|
|
189
|
+
|
|
190
|
+
Check if ALL issues are done:
|
|
191
|
+
```bash
|
|
192
|
+
ALL_DONE=$(echo "$DEPENDENCY_RESULT" | python3 -c "
|
|
193
|
+
import json,sys
|
|
194
|
+
r = json.load(sys.stdin)
|
|
195
|
+
print('true' if r['done_count'] == r['total'] else 'false')
|
|
196
|
+
")
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
If all done:
|
|
200
|
+
```
|
|
201
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
202
|
+
MGW ► ALL DONE
|
|
203
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
204
|
+
|
|
205
|
+
All ${TOTAL_ISSUES} issues in milestone "${MILESTONE_NAME}" are complete.
|
|
206
|
+
|
|
207
|
+
Run /mgw:milestone to finalize (close milestone, create release).
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
If not all done (some are blocked/failed):
|
|
211
|
+
```
|
|
212
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
213
|
+
MGW ► BLOCKED
|
|
214
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
215
|
+
|
|
216
|
+
No unblocked issues in milestone "${MILESTONE_NAME}".
|
|
217
|
+
|
|
218
|
+
| Issue | Blocked By | Blocker Status |
|
|
219
|
+
|-------|-----------|----------------|
|
|
220
|
+
| #N title | #M title | ◆ In progress |
|
|
221
|
+
| #K title | #J title | ✗ Failed |
|
|
222
|
+
|
|
223
|
+
${failed_advice}
|
|
224
|
+
|
|
225
|
+
Progress: ${done_count}/${total} complete
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Where `${failed_advice}` is: "Resolve #J to unblock #K." (specific actionable advice for each failed blocker).
|
|
229
|
+
</step>
|
|
230
|
+
|
|
231
|
+
<step name="verify_live">
|
|
232
|
+
**Quick GitHub verification for recommended issue:**
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
RECOMMENDED=$(echo "$DEPENDENCY_RESULT" | python3 -c "
|
|
236
|
+
import json,sys
|
|
237
|
+
r = json.load(sys.stdin)
|
|
238
|
+
if r['unblocked']:
|
|
239
|
+
print(json.dumps(r['unblocked'][0]))
|
|
240
|
+
else:
|
|
241
|
+
print('null')
|
|
242
|
+
")
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
If recommended issue exists:
|
|
246
|
+
```bash
|
|
247
|
+
REC_NUMBER=$(echo "$RECOMMENDED" | python3 -c "import json,sys; print(json.load(sys.stdin)['issue']['github_number'])")
|
|
248
|
+
|
|
249
|
+
# Quick GitHub check — verify issue is still open
|
|
250
|
+
GH_CHECK=$(gh issue view ${REC_NUMBER} --json state,title,labels -q '{state: .state, title: .title}' 2>/dev/null)
|
|
251
|
+
|
|
252
|
+
if [ -n "$GH_CHECK" ]; then
|
|
253
|
+
GH_STATE=$(echo "$GH_CHECK" | python3 -c "import json,sys; print(json.load(sys.stdin)['state'])")
|
|
254
|
+
if [ "$GH_STATE" != "OPEN" ]; then
|
|
255
|
+
# Issue was closed externally — skip to next unblocked
|
|
256
|
+
echo "Issue #${REC_NUMBER} is ${GH_STATE} on GitHub. Checking next..."
|
|
257
|
+
# Remove from unblocked list, try next
|
|
258
|
+
fi
|
|
259
|
+
VERIFIED="(verified)"
|
|
260
|
+
else
|
|
261
|
+
VERIFIED="(GitHub state unverified)"
|
|
262
|
+
fi
|
|
263
|
+
```
|
|
264
|
+
</step>
|
|
265
|
+
|
|
266
|
+
<step name="display_brief">
|
|
267
|
+
**Display full brief for recommended issue:**
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
REC_ISSUE=$(echo "$RECOMMENDED" | python3 -c "import json,sys; r=json.load(sys.stdin); print(json.dumps(r['issue']))")
|
|
271
|
+
REC_TITLE=$(echo "$REC_ISSUE" | python3 -c "import json,sys; print(json.load(sys.stdin)['title'])")
|
|
272
|
+
REC_GSD_ROUTE=$(echo "$REC_ISSUE" | python3 -c "import json,sys; print(json.load(sys.stdin).get('gsd_route','plan-phase'))")
|
|
273
|
+
REC_LABELS=$(echo "$REC_ISSUE" | python3 -c "import json,sys; print(', '.join(json.load(sys.stdin).get('labels',[])))")
|
|
274
|
+
REC_PHASE=$(echo "$REC_ISSUE" | python3 -c "import json,sys; print(json.load(sys.stdin).get('phase_number',''))")
|
|
275
|
+
REC_PHASE_NAME=$(echo "$REC_ISSUE" | python3 -c "import json,sys; print(json.load(sys.stdin).get('phase_name',''))")
|
|
276
|
+
|
|
277
|
+
DONE_COUNT=$(echo "$DEPENDENCY_RESULT" | python3 -c "import json,sys; print(json.load(sys.stdin)['done_count'])")
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Display:
|
|
281
|
+
```
|
|
282
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
283
|
+
MGW ► NEXT UP
|
|
284
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
285
|
+
|
|
286
|
+
Issue: #${REC_NUMBER} — ${REC_TITLE} ${VERIFIED}
|
|
287
|
+
GSD Route: ${REC_GSD_ROUTE}
|
|
288
|
+
Phase: ${REC_PHASE}: ${REC_PHASE_NAME}
|
|
289
|
+
Labels: ${REC_LABELS}
|
|
290
|
+
Milestone: ${MILESTONE_NAME} (${DONE_COUNT}/${TOTAL_ISSUES} complete)
|
|
291
|
+
|
|
292
|
+
Dependencies (all done):
|
|
293
|
+
✓ #${dep1_number} — ${dep1_title}
|
|
294
|
+
✓ #${dep2_number} — ${dep2_title}
|
|
295
|
+
(or: None — this is an independent issue)
|
|
296
|
+
|
|
297
|
+
Unblocks:
|
|
298
|
+
○ #${next1_number} — ${next1_title}
|
|
299
|
+
○ #${next2_number} — ${next2_title}
|
|
300
|
+
(or: None — no downstream dependencies)
|
|
301
|
+
|
|
302
|
+
───────────────────────────────────────────────────────────────
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
If other unblocked alternatives exist:
|
|
306
|
+
```bash
|
|
307
|
+
ALT_COUNT=$(echo "$DEPENDENCY_RESULT" | python3 -c "
|
|
308
|
+
import json,sys
|
|
309
|
+
r = json.load(sys.stdin)
|
|
310
|
+
print(len(r['unblocked']) - 1)
|
|
311
|
+
")
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
If ALT_COUNT > 0:
|
|
315
|
+
```
|
|
316
|
+
Also unblocked:
|
|
317
|
+
#${alt1_number} — ${alt1_title} (${alt1_gsd_route})
|
|
318
|
+
#${alt2_number} — ${alt2_title} (${alt2_gsd_route})
|
|
319
|
+
```
|
|
320
|
+
</step>
|
|
321
|
+
|
|
322
|
+
<step name="offer_run">
|
|
323
|
+
**Offer to run the recommended issue:**
|
|
324
|
+
|
|
325
|
+
```
|
|
326
|
+
AskUserQuestion(
|
|
327
|
+
header: "Ready to Start",
|
|
328
|
+
question: "Run /mgw:run #${REC_NUMBER} now?",
|
|
329
|
+
options: [
|
|
330
|
+
{ label: "Yes", description: "Start the pipeline for this issue" },
|
|
331
|
+
{ label: "No", description: "Just viewing — I'll run it later" },
|
|
332
|
+
{ label: "Pick different", description: "Choose a different unblocked issue" }
|
|
333
|
+
]
|
|
334
|
+
)
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
**If "Yes":** Display the command to run:
|
|
338
|
+
```
|
|
339
|
+
Start the pipeline:
|
|
340
|
+
|
|
341
|
+
/mgw:run ${REC_NUMBER}
|
|
342
|
+
|
|
343
|
+
<sub>/clear first → fresh context window</sub>
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
Note: /mgw:next is read-only (allowed-tools don't include Task). It cannot invoke
|
|
347
|
+
/mgw:run directly. It displays the command for the user to run.
|
|
348
|
+
|
|
349
|
+
**If "Pick different":** Display the alternatives list and ask user to pick:
|
|
350
|
+
```
|
|
351
|
+
AskUserQuestion(
|
|
352
|
+
header: "Select Issue",
|
|
353
|
+
question: "Which issue do you want to work on?",
|
|
354
|
+
options: [alt_issues_as_options]
|
|
355
|
+
)
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Then re-display the brief for the selected issue.
|
|
359
|
+
|
|
360
|
+
**If "No":** Exit cleanly.
|
|
361
|
+
</step>
|
|
362
|
+
|
|
363
|
+
</process>
|
|
364
|
+
|
|
365
|
+
<success_criteria>
|
|
366
|
+
- [ ] project.json loaded and current milestone identified (MLST-02)
|
|
367
|
+
- [ ] Dependency graph computed from depends_on_slugs
|
|
368
|
+
- [ ] Single recommended issue surfaced (dependency order, then phase order)
|
|
369
|
+
- [ ] Full brief displayed: number, title, GSD route, labels, dependencies, what it unblocks, milestone context
|
|
370
|
+
- [ ] Alternatives listed when multiple issues are unblocked
|
|
371
|
+
- [ ] Blocking chain shown when nothing is unblocked
|
|
372
|
+
- [ ] Live GitHub verification attempted for recommended issue
|
|
373
|
+
- [ ] Offer to run /mgw:run displayed
|
|
374
|
+
- [ ] Read-only: no state modifications, no pipeline execution
|
|
375
|
+
</success_criteria>
|
package/commands/pr.md
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mgw:pr
|
|
3
|
+
description: Create a pull request from GSD artifacts and linked issue context
|
|
4
|
+
argument-hint: "[issue-number] [--base <branch>]"
|
|
5
|
+
allowed-tools:
|
|
6
|
+
- Bash
|
|
7
|
+
- Read
|
|
8
|
+
- Write
|
|
9
|
+
- Edit
|
|
10
|
+
- Glob
|
|
11
|
+
- Grep
|
|
12
|
+
- Task
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
<objective>
|
|
16
|
+
Create a PR with context pulled from GSD artifacts (SUMMARY.md, VERIFICATION.md)
|
|
17
|
+
and the linked GitHub issue. Builds a structured PR description with summary,
|
|
18
|
+
testing procedures, and cross-references.
|
|
19
|
+
|
|
20
|
+
Works in two modes:
|
|
21
|
+
1. **Linked mode:** Issue number provided or found in .mgw/active/ — pulls issue
|
|
22
|
+
context, GSD artifacts, and cross-refs into the PR.
|
|
23
|
+
2. **Standalone mode:** No issue — builds PR from GSD artifacts in .planning/ or
|
|
24
|
+
from the branch diff.
|
|
25
|
+
</objective>
|
|
26
|
+
|
|
27
|
+
<execution_context>
|
|
28
|
+
@~/.claude/commands/mgw/workflows/state.md
|
|
29
|
+
@~/.claude/commands/mgw/workflows/github.md
|
|
30
|
+
@~/.claude/commands/mgw/workflows/gsd.md
|
|
31
|
+
@~/.claude/commands/mgw/workflows/validation.md
|
|
32
|
+
</execution_context>
|
|
33
|
+
|
|
34
|
+
<context>
|
|
35
|
+
$ARGUMENTS — optional: [issue-number] [--base <branch>]
|
|
36
|
+
</context>
|
|
37
|
+
|
|
38
|
+
<process>
|
|
39
|
+
|
|
40
|
+
<step name="detect_mode">
|
|
41
|
+
**Determine linked vs standalone mode:**
|
|
42
|
+
|
|
43
|
+
If issue number in $ARGUMENTS → linked mode.
|
|
44
|
+
Else if exactly one file in .mgw/active/ → linked mode with that issue.
|
|
45
|
+
Else → standalone mode.
|
|
46
|
+
|
|
47
|
+
Parse `--base <branch>` if provided, default to repo default branch:
|
|
48
|
+
```bash
|
|
49
|
+
DEFAULT_BRANCH=$(gh repo view --json defaultBranchRef -q .defaultBranchRef.name)
|
|
50
|
+
```
|
|
51
|
+
</step>
|
|
52
|
+
|
|
53
|
+
<step name="gather_gsd_artifacts">
|
|
54
|
+
**Gather GSD artifacts:**
|
|
55
|
+
|
|
56
|
+
**Linked mode:** Read state file for gsd_artifacts.path, then:
|
|
57
|
+
```bash
|
|
58
|
+
# Structured summary data via gsd-tools (returns JSON with one_liner, key_files, tech_added, patterns, decisions)
|
|
59
|
+
SUMMARY_DATA=$(node ~/.claude/get-shit-done/bin/gsd-tools.cjs summary-extract "${gsd_artifacts_path}/*SUMMARY*" 2>/dev/null || echo '{}')
|
|
60
|
+
# Also read raw artifacts for full context
|
|
61
|
+
SUMMARY_RAW=$(cat ${gsd_artifacts_path}/*SUMMARY* 2>/dev/null)
|
|
62
|
+
VERIFICATION=$(cat ${gsd_artifacts_path}/*VERIFICATION* 2>/dev/null)
|
|
63
|
+
# Progress table for details section
|
|
64
|
+
PROGRESS_TABLE=$(node ~/.claude/get-shit-done/bin/gsd-tools.cjs progress table --raw 2>/dev/null || echo "")
|
|
65
|
+
|
|
66
|
+
# Milestone/phase context for PR body
|
|
67
|
+
MILESTONE_TITLE=""
|
|
68
|
+
PHASE_INFO=""
|
|
69
|
+
DEPENDENCY_CHAIN=""
|
|
70
|
+
if [ -f "${REPO_ROOT}/.mgw/project.json" ]; then
|
|
71
|
+
MILESTONE_TITLE=$(python3 -c "
|
|
72
|
+
import json
|
|
73
|
+
p = json.load(open('${REPO_ROOT}/.mgw/project.json'))
|
|
74
|
+
for m in p['milestones']:
|
|
75
|
+
for i in m.get('issues', []):
|
|
76
|
+
if i.get('github_number') == ${ISSUE_NUMBER}:
|
|
77
|
+
print(m['name'])
|
|
78
|
+
break
|
|
79
|
+
" 2>/dev/null || echo "")
|
|
80
|
+
|
|
81
|
+
PHASE_INFO=$(python3 -c "
|
|
82
|
+
import json
|
|
83
|
+
p = json.load(open('${REPO_ROOT}/.mgw/project.json'))
|
|
84
|
+
for m in p['milestones']:
|
|
85
|
+
for i in m.get('issues', []):
|
|
86
|
+
if i.get('github_number') == ${ISSUE_NUMBER}:
|
|
87
|
+
total = len(m.get('issues', []))
|
|
88
|
+
idx = [x['github_number'] for x in m['issues']].index(${ISSUE_NUMBER}) + 1
|
|
89
|
+
print(f\"Phase {i['phase_number']}: {i['phase_name']} (issue {idx}/{total} in milestone)\")
|
|
90
|
+
break
|
|
91
|
+
" 2>/dev/null || echo "")
|
|
92
|
+
|
|
93
|
+
DEPENDENCY_CHAIN=$(python3 -c "
|
|
94
|
+
import json
|
|
95
|
+
p = json.load(open('${REPO_ROOT}/.mgw/project.json'))
|
|
96
|
+
refs = json.load(open('${REPO_ROOT}/.mgw/cross-refs.json'))
|
|
97
|
+
blockers = [l['b'].split(':')[1] for l in refs.get('links', [])
|
|
98
|
+
if l.get('type') == 'blocked-by' and l['a'] == 'issue:${ISSUE_NUMBER}']
|
|
99
|
+
blocks = [l['a'].split(':')[1] for l in refs.get('links', [])
|
|
100
|
+
if l.get('type') == 'blocked-by' and l['b'] == 'issue:${ISSUE_NUMBER}']
|
|
101
|
+
parts = []
|
|
102
|
+
if blockers: parts.append('Blocked by: ' + ', '.join(f'#{b}' for b in blockers))
|
|
103
|
+
if blocks: parts.append('Unblocks: ' + ', '.join(f'#{b}' for b in blocks))
|
|
104
|
+
print(' | '.join(parts) if parts else '')
|
|
105
|
+
" 2>/dev/null || echo "")
|
|
106
|
+
fi
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Standalone mode:** Search .planning/ for recent artifacts:
|
|
110
|
+
```bash
|
|
111
|
+
# Check quick tasks first, then phases
|
|
112
|
+
SUMMARY_PATH=$(ls -t .planning/quick/*/SUMMARY.md .planning/phases/*/SUMMARY.md 2>/dev/null | head -1)
|
|
113
|
+
if [ -n "$SUMMARY_PATH" ]; then
|
|
114
|
+
SUMMARY_DATA=$(node ~/.claude/get-shit-done/bin/gsd-tools.cjs summary-extract "$SUMMARY_PATH" 2>/dev/null || echo '{}')
|
|
115
|
+
SUMMARY_RAW=$(cat "$SUMMARY_PATH" 2>/dev/null)
|
|
116
|
+
fi
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
If no GSD artifacts found → fall back to `git log ${base}..HEAD --oneline` for PR content.
|
|
120
|
+
</step>
|
|
121
|
+
|
|
122
|
+
<step name="build_pr_body">
|
|
123
|
+
**Build PR description:**
|
|
124
|
+
|
|
125
|
+
Spawn task agent to compose the PR body:
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
Task(
|
|
129
|
+
prompt="
|
|
130
|
+
<files_to_read>
|
|
131
|
+
- ./CLAUDE.md (Project instructions — if exists, follow all guidelines)
|
|
132
|
+
- .agents/skills/ (Project skills — if dir exists, list skills, read SKILL.md for each, follow relevant rules)
|
|
133
|
+
</files_to_read>
|
|
134
|
+
|
|
135
|
+
Build a GitHub PR description from these inputs.
|
|
136
|
+
|
|
137
|
+
<issue_context>
|
|
138
|
+
${issue_title_and_body_if_linked}
|
|
139
|
+
Issue: #${ISSUE_NUMBER} (or 'none — standalone')
|
|
140
|
+
</issue_context>
|
|
141
|
+
|
|
142
|
+
<gsd_summary_structured>
|
|
143
|
+
${SUMMARY_DATA}
|
|
144
|
+
</gsd_summary_structured>
|
|
145
|
+
|
|
146
|
+
<gsd_summary_raw>
|
|
147
|
+
${SUMMARY_RAW}
|
|
148
|
+
</gsd_summary_raw>
|
|
149
|
+
|
|
150
|
+
<gsd_verification>
|
|
151
|
+
${VERIFICATION}
|
|
152
|
+
</gsd_verification>
|
|
153
|
+
|
|
154
|
+
<cross_refs>
|
|
155
|
+
${CROSS_REFS_FOR_THIS_ISSUE}
|
|
156
|
+
</cross_refs>
|
|
157
|
+
|
|
158
|
+
<commits>
|
|
159
|
+
${GIT_LOG_BASE_TO_HEAD}
|
|
160
|
+
</commits>
|
|
161
|
+
|
|
162
|
+
<milestone_context>
|
|
163
|
+
Milestone: ${MILESTONE_TITLE}
|
|
164
|
+
Phase: ${PHASE_INFO}
|
|
165
|
+
Dependencies: ${DEPENDENCY_CHAIN}
|
|
166
|
+
</milestone_context>
|
|
167
|
+
|
|
168
|
+
<output_format>
|
|
169
|
+
Return EXACTLY two sections separated by ===TESTING===:
|
|
170
|
+
|
|
171
|
+
SECTION 1 — PR body:
|
|
172
|
+
## Summary
|
|
173
|
+
- [2-4 bullet points of what changed and why]
|
|
174
|
+
- [Use one_liner from gsd_summary_structured if available]
|
|
175
|
+
|
|
176
|
+
${if_linked: 'Closes #${ISSUE_NUMBER}'}
|
|
177
|
+
|
|
178
|
+
${if MILESTONE_TITLE non-empty:
|
|
179
|
+
## Milestone Context
|
|
180
|
+
- **Milestone:** ${MILESTONE_TITLE}
|
|
181
|
+
- **Phase:** ${PHASE_INFO}
|
|
182
|
+
- **Dependencies:** ${DEPENDENCY_CHAIN}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
## Changes
|
|
186
|
+
- [File-level changes grouped by system/module]
|
|
187
|
+
- [Use key_files from gsd_summary_structured if available]
|
|
188
|
+
|
|
189
|
+
## Cross-References
|
|
190
|
+
${cross_ref_list_or_omit_if_none}
|
|
191
|
+
|
|
192
|
+
${if PROGRESS_TABLE non-empty:
|
|
193
|
+
<details>
|
|
194
|
+
<summary>GSD Progress</summary>
|
|
195
|
+
|
|
196
|
+
${PROGRESS_TABLE}
|
|
197
|
+
|
|
198
|
+
</details>
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
===TESTING===
|
|
202
|
+
|
|
203
|
+
SECTION 2 — Testing procedures comment:
|
|
204
|
+
## Testing Procedures
|
|
205
|
+
- [ ] [Step-by-step verification checklist]
|
|
206
|
+
- [ ] [Derived from VERIFICATION.md if available]
|
|
207
|
+
- [ ] [Or from the changes themselves]
|
|
208
|
+
</output_format>
|
|
209
|
+
",
|
|
210
|
+
subagent_type="general-purpose",
|
|
211
|
+
model="sonnet",
|
|
212
|
+
description="Build PR description for #${ISSUE_NUMBER}"
|
|
213
|
+
)
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Split agent output on `===TESTING===` into $PR_BODY and $TESTING_COMMENT.
|
|
217
|
+
</step>
|
|
218
|
+
|
|
219
|
+
<step name="create_pr">
|
|
220
|
+
**Create the PR:**
|
|
221
|
+
|
|
222
|
+
Determine PR title:
|
|
223
|
+
- Linked: from issue title, prefixed with type (fix:, feat:, etc.)
|
|
224
|
+
- Standalone: from first commit message or branch name
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
CURRENT_BRANCH=$(git branch --show-current)
|
|
228
|
+
gh pr create --title "${PR_TITLE}" --base "${BASE_BRANCH}" --body "${PR_BODY}"
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Capture PR number and URL from output.
|
|
232
|
+
</step>
|
|
233
|
+
|
|
234
|
+
<step name="post_testing">
|
|
235
|
+
**Post testing procedures as PR comment:**
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
gh pr comment ${PR_NUMBER} --body "${TESTING_COMMENT}"
|
|
239
|
+
```
|
|
240
|
+
</step>
|
|
241
|
+
|
|
242
|
+
<step name="update_state">
|
|
243
|
+
**Update .mgw/ state (linked mode only):**
|
|
244
|
+
|
|
245
|
+
Update state file:
|
|
246
|
+
- Set linked_pr to PR number
|
|
247
|
+
- Set pipeline_stage to "pr-created"
|
|
248
|
+
|
|
249
|
+
Add cross-ref: issue → PR link in cross-refs.json.
|
|
250
|
+
</step>
|
|
251
|
+
|
|
252
|
+
<step name="report">
|
|
253
|
+
**Report:**
|
|
254
|
+
|
|
255
|
+
```
|
|
256
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
257
|
+
MGW ► PR CREATED
|
|
258
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
259
|
+
|
|
260
|
+
PR #${PR_NUMBER}: ${PR_TITLE}
|
|
261
|
+
URL: ${PR_URL}
|
|
262
|
+
${if_linked: "Closes: #${ISSUE_NUMBER}"}
|
|
263
|
+
Testing procedures posted as PR comment.
|
|
264
|
+
```
|
|
265
|
+
</step>
|
|
266
|
+
|
|
267
|
+
</process>
|
|
268
|
+
|
|
269
|
+
<success_criteria>
|
|
270
|
+
- [ ] Mode detected correctly (linked vs standalone)
|
|
271
|
+
- [ ] GSD artifacts found and read (or fallback to git log)
|
|
272
|
+
- [ ] PR body includes summary, changes, cross-refs, and Closes #N
|
|
273
|
+
- [ ] PR created via gh pr create
|
|
274
|
+
- [ ] Testing procedures posted as separate PR comment
|
|
275
|
+
- [ ] State file updated with PR number (linked mode)
|
|
276
|
+
- [ ] Cross-ref added (linked mode)
|
|
277
|
+
</success_criteria>
|