@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.
@@ -0,0 +1,526 @@
1
+ ---
2
+ name: mgw:status
3
+ description: Project status dashboard — milestone progress, issue pipeline stages, open PRs
4
+ argument-hint: "[milestone_number] [--json] [--board]"
5
+ allowed-tools:
6
+ - Bash
7
+ - Read
8
+ ---
9
+
10
+ <objective>
11
+ Display a structured project status dashboard showing milestone progress, per-issue
12
+ pipeline stages, open PRs, and next milestone preview. Pure read-only — no state
13
+ mutations, no agent spawns, no GitHub writes.
14
+
15
+ Falls back gracefully when no project.json exists (lists active issues only via GitHub API).
16
+ </objective>
17
+
18
+ <execution_context>
19
+ @~/.claude/commands/mgw/workflows/state.md
20
+ @~/.claude/commands/mgw/workflows/github.md
21
+ </execution_context>
22
+
23
+ <context>
24
+ $ARGUMENTS
25
+
26
+ Repo detected via: gh repo view --json nameWithOwner -q .nameWithOwner
27
+ </context>
28
+
29
+ <process>
30
+
31
+ <step name="parse_arguments">
32
+ **Parse $ARGUMENTS for milestone number and flags:**
33
+
34
+ ```bash
35
+ MILESTONE_NUM=""
36
+ JSON_OUTPUT=false
37
+ OPEN_BOARD=false
38
+
39
+ for ARG in $ARGUMENTS; do
40
+ case "$ARG" in
41
+ --json) JSON_OUTPUT=true ;;
42
+ --board) OPEN_BOARD=true ;;
43
+ [0-9]*) MILESTONE_NUM="$ARG" ;;
44
+ esac
45
+ done
46
+ ```
47
+ </step>
48
+
49
+ <step name="detect_project">
50
+ **Check if project.json exists:**
51
+
52
+ ```bash
53
+ REPO_ROOT=$(git rev-parse --show-toplevel)
54
+ MGW_DIR="${REPO_ROOT}/.mgw"
55
+ REPO_NAME=$(gh repo view --json nameWithOwner -q .nameWithOwner 2>/dev/null || basename "$REPO_ROOT")
56
+
57
+ BOARD_URL=""
58
+ if [ ! -f "${MGW_DIR}/project.json" ]; then
59
+ # No project.json — fall back to GitHub-only mode
60
+ FALLBACK_MODE=true
61
+ else
62
+ FALLBACK_MODE=false
63
+ fi
64
+ ```
65
+ </step>
66
+
67
+ <step name="fallback_github_only">
68
+ **If no project.json — display GitHub-only status:**
69
+
70
+ When `FALLBACK_MODE=true`, skip all project.json logic and show active issues from GitHub:
71
+
72
+ ```bash
73
+ if [ "$FALLBACK_MODE" = true ]; then
74
+ OPEN_ISSUES=$(gh issue list --state open --limit 50 --json number,title,labels,assignees,state,createdAt)
75
+ OPEN_PRS=$(gh pr list --state open --limit 20 --json number,title,headRefName,isDraft,reviewDecision,url)
76
+ fi
77
+ ```
78
+
79
+ Display:
80
+ ```
81
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
82
+ MGW > PROJECT STATUS: ${REPO_NAME}
83
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
84
+
85
+ No project.json found. Showing GitHub state only.
86
+
87
+ Open Issues (${issue_count}):
88
+ #N title [labels]
89
+ #M title [labels]
90
+
91
+ Open PRs (${pr_count}):
92
+ #P title (draft|review requested|approved)
93
+
94
+ Run /mgw:project to initialize project tracking.
95
+ ```
96
+
97
+ If `--json` flag: output as JSON with `{ "mode": "github-only", "issues": [...], "prs": [...] }`
98
+
99
+ Exit after display.
100
+ </step>
101
+
102
+ <step name="load_project">
103
+ **Load project.json and milestone data:**
104
+
105
+ ```bash
106
+ PROJECT_JSON=$(cat "${MGW_DIR}/project.json")
107
+
108
+ # Resolve active milestone index (0-based) via state resolution (supports both schema versions)
109
+ ACTIVE_IDX=$(node -e "
110
+ const { loadProjectState, resolveActiveMilestoneIndex } = require('./lib/state.cjs');
111
+ const state = loadProjectState();
112
+ const idx = resolveActiveMilestoneIndex(state);
113
+ const milestone = state.milestones ? state.milestones[idx] : null;
114
+ const gsdId = state.active_gsd_milestone || ('legacy:' + state.current_milestone);
115
+ console.log(JSON.stringify({ idx, gsd_id: gsdId, name: milestone ? milestone.name : 'unknown' }));
116
+ ")
117
+ CURRENT_MILESTONE_IDX=$(echo "$ACTIVE_IDX" | python3 -c "import json,sys; print(json.load(sys.stdin)['idx'])")
118
+ # Convert 0-based index to 1-indexed milestone number for display and compatibility
119
+ CURRENT_MILESTONE=$((CURRENT_MILESTONE_IDX + 1))
120
+ TOTAL_MILESTONES=$(echo "$PROJECT_JSON" | python3 -c "import json,sys; print(len(json.load(sys.stdin)['milestones']))")
121
+
122
+ # Use specified milestone or current
123
+ TARGET_MILESTONE=${MILESTONE_NUM:-$CURRENT_MILESTONE}
124
+
125
+ # Load target milestone data
126
+ MILESTONE_DATA=$(echo "$PROJECT_JSON" | python3 -c "
127
+ import json,sys
128
+ p = json.load(sys.stdin)
129
+ idx = ${TARGET_MILESTONE} - 1
130
+ if idx < 0 or idx >= len(p['milestones']):
131
+ print(json.dumps({'error': 'Milestone ${TARGET_MILESTONE} not found'}))
132
+ sys.exit(1)
133
+ m = p['milestones'][idx]
134
+ print(json.dumps(m))
135
+ ")
136
+
137
+ MILESTONE_NAME=$(echo "$MILESTONE_DATA" | python3 -c "import json,sys; print(json.load(sys.stdin)['name'])")
138
+ ISSUES_JSON=$(echo "$MILESTONE_DATA" | python3 -c "import json,sys; print(json.dumps(json.load(sys.stdin)['issues']))")
139
+ TOTAL_ISSUES=$(echo "$ISSUES_JSON" | python3 -c "import json,sys; print(len(json.load(sys.stdin)))")
140
+
141
+ # Extract board URL from project.json (top-level board_url or nested board.url)
142
+ BOARD_URL=$(echo "$PROJECT_JSON" | python3 -c "
143
+ import json, sys
144
+ p = json.load(sys.stdin)
145
+ # Check top-level board_url first, then board.url (nested)
146
+ url = p.get('board_url') or (p.get('board') or {}).get('url', '')
147
+ print(url or '')
148
+ " 2>/dev/null || echo "")
149
+ ```
150
+ </step>
151
+
152
+ <step name="open_board">
153
+ **Handle --board flag — open board in browser and exit early:**
154
+
155
+ ```bash
156
+ if [ "$OPEN_BOARD" = true ]; then
157
+ if [ -z "$BOARD_URL" ]; then
158
+ echo "No board configured in project.json. Run /mgw:board create first." >&2
159
+ exit 1
160
+ fi
161
+ echo "Opening board: ${BOARD_URL}"
162
+ xdg-open "${BOARD_URL}" 2>/dev/null \
163
+ || open "${BOARD_URL}" 2>/dev/null \
164
+ || echo "Could not open browser. Board URL: ${BOARD_URL}"
165
+ exit 0
166
+ fi
167
+ ```
168
+
169
+ This step exits early — do not continue to the dashboard display.
170
+ </step>
171
+
172
+ <step name="compute_progress">
173
+ **Compute pipeline stage counts and progress:**
174
+
175
+ ```bash
176
+ STAGE_COUNTS=$(echo "$ISSUES_JSON" | python3 -c "
177
+ import json, sys
178
+ issues = json.load(sys.stdin)
179
+
180
+ counts = {'done': 0, 'executing': 0, 'new': 0, 'blocked': 0, 'failed': 0, 'other': 0}
181
+ stage_map = {
182
+ 'done': 'done',
183
+ 'pr-created': 'done',
184
+ 'new': 'new',
185
+ 'triaged': 'executing',
186
+ 'planning': 'executing',
187
+ 'executing': 'executing',
188
+ 'verifying': 'executing',
189
+ 'failed': 'failed',
190
+ 'blocked': 'blocked'
191
+ }
192
+
193
+ for issue in issues:
194
+ stage = issue.get('pipeline_stage', 'new')
195
+ category = stage_map.get(stage, 'other')
196
+ counts[category] += 1
197
+
198
+ total = len(issues)
199
+ done = counts['done']
200
+ pct = int((done / total) * 100) if total > 0 else 0
201
+
202
+ # Build progress bar (16 chars wide)
203
+ filled = int(pct / 100 * 16)
204
+ bar = chr(9608) * filled + chr(9617) * (16 - filled)
205
+
206
+ print(json.dumps({
207
+ 'counts': counts,
208
+ 'total': total,
209
+ 'done': done,
210
+ 'pct': pct,
211
+ 'bar': bar
212
+ }))
213
+ ")
214
+ ```
215
+ </step>
216
+
217
+ <step name="compute_health">
218
+ **Compute milestone health metrics — velocity, done count, blocked count:**
219
+
220
+ ```bash
221
+ HEALTH_DATA=$(echo "$ISSUES_JSON" | python3 -c "
222
+ import json, sys, os, glob
223
+
224
+ issues = json.load(sys.stdin)
225
+ repo_root = os.environ.get('REPO_ROOT', os.getcwd())
226
+ mgw_dir = os.path.join(repo_root, '.mgw')
227
+
228
+ done_stages = {'done', 'pr-created'}
229
+ blocked_stages = {'blocked'}
230
+
231
+ done_count = 0
232
+ blocked_count = 0
233
+ done_timestamps = []
234
+
235
+ for issue in issues:
236
+ stage = issue.get('pipeline_stage', 'new')
237
+ num = issue.get('github_number', 0)
238
+
239
+ if stage in done_stages:
240
+ done_count += 1
241
+ # Use .mgw/active/ or .mgw/completed/ file mtime as done timestamp proxy
242
+ for subdir in ['active', 'completed']:
243
+ pattern = os.path.join(mgw_dir, subdir, str(num) + '-*.json')
244
+ matches = glob.glob(pattern)
245
+ if matches:
246
+ try:
247
+ done_timestamps.append(os.path.getmtime(matches[0]))
248
+ except Exception:
249
+ pass
250
+ break
251
+ elif stage in blocked_stages:
252
+ blocked_count += 1
253
+
254
+ # Compute velocity (issues completed per day)
255
+ if done_count == 0:
256
+ velocity_str = '0/day'
257
+ elif len(done_timestamps) >= 2:
258
+ span_days = (max(done_timestamps) - min(done_timestamps)) / 86400.0
259
+ if span_days >= 0.1:
260
+ velocity_str = '{:.1f}/day'.format(done_count / span_days)
261
+ else:
262
+ velocity_str = str(done_count) + ' (same day)'
263
+ elif done_count == 1:
264
+ velocity_str = '1 (single)'
265
+ else:
266
+ velocity_str = str(done_count) + '/day'
267
+
268
+ import json as json2
269
+ print(json2.dumps({
270
+ 'done_count': done_count,
271
+ 'blocked_count': blocked_count,
272
+ 'velocity': velocity_str
273
+ }))
274
+ ")
275
+
276
+ HEALTH_DONE=$(echo "$HEALTH_DATA" | python3 -c "import json,sys; print(json.load(sys.stdin)['done_count'])" 2>/dev/null || echo "0")
277
+ HEALTH_BLOCKED=$(echo "$HEALTH_DATA" | python3 -c "import json,sys; print(json.load(sys.stdin)['blocked_count'])" 2>/dev/null || echo "0")
278
+ HEALTH_VELOCITY=$(echo "$HEALTH_DATA" | python3 -c "import json,sys; print(json.load(sys.stdin)['velocity'])" 2>/dev/null || echo "N/A")
279
+ ```
280
+ </step>
281
+
282
+ <step name="build_issue_table">
283
+ **Build per-issue status lines:**
284
+
285
+ ```bash
286
+ ISSUE_LINES=$(echo "$ISSUES_JSON" | python3 -c "
287
+ import json, sys
288
+ issues = json.load(sys.stdin)
289
+
290
+ stage_icons = {
291
+ 'done': ('done', chr(9989)),
292
+ 'pr-created': ('done', chr(9989)),
293
+ 'new': ('new', chr(9203)),
294
+ 'triaged': ('executing', chr(128260)),
295
+ 'planning': ('executing', chr(128260)),
296
+ 'executing': ('executing', chr(128260)),
297
+ 'verifying': ('executing', chr(128260)),
298
+ 'failed': ('failed', chr(10060)),
299
+ 'blocked': ('blocked', chr(128274))
300
+ }
301
+
302
+ lines = []
303
+ for issue in issues:
304
+ num = issue['github_number']
305
+ title = issue['title'][:50]
306
+ stage = issue.get('pipeline_stage', 'new')
307
+ label, icon = stage_icons.get(stage, ('other', '?'))
308
+ lines.append({
309
+ 'number': num,
310
+ 'title': title,
311
+ 'stage': stage,
312
+ 'label': label,
313
+ 'icon': icon
314
+ })
315
+
316
+ print(json.dumps(lines))
317
+ ")
318
+ ```
319
+ </step>
320
+
321
+ <step name="fetch_open_prs">
322
+ **Fetch open PRs from GitHub:**
323
+
324
+ ```bash
325
+ OPEN_PRS=$(gh pr list --state open --limit 20 --json number,title,headRefName,isDraft,reviewDecision,url 2>/dev/null || echo "[]")
326
+ ```
327
+
328
+ Match PRs to milestone issues by branch name pattern (`issue/N-*`) or PR body (`Closes #N`).
329
+ </step>
330
+
331
+ <step name="load_next_milestone">
332
+ **Load next milestone preview (if exists):**
333
+
334
+ ```bash
335
+ NEXT_MILESTONE=""
336
+ if [ "$TARGET_MILESTONE" -lt "$TOTAL_MILESTONES" ]; then
337
+ NEXT_IDX=$((TARGET_MILESTONE))
338
+ NEXT_MILESTONE=$(echo "$PROJECT_JSON" | python3 -c "
339
+ import json,sys
340
+ p = json.load(sys.stdin)
341
+ m = p['milestones'][${NEXT_IDX}]
342
+ total = len(m['issues'])
343
+ done = sum(1 for i in m['issues'] if i.get('pipeline_stage') in ('done', 'pr-created'))
344
+ print(json.dumps({'name': m['name'], 'total': total, 'done': done}))
345
+ ")
346
+ fi
347
+ ```
348
+ </step>
349
+
350
+ <step name="display_dashboard">
351
+ **Display the status dashboard:**
352
+
353
+ ```bash
354
+ # Print board URL prominently at top if configured
355
+ if [ -n "$BOARD_URL" ]; then
356
+ echo " Board: ${BOARD_URL}"
357
+ fi
358
+ ```
359
+
360
+ ```
361
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
362
+ MGW > PROJECT STATUS: ${REPO_NAME}
363
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
364
+
365
+ Current Milestone: ${MILESTONE_NAME} (${done}/${total} done)
366
+ Progress: ${bar} ${pct}%
367
+
368
+ #35 ✅ done refactor: remove .planning/ writes
369
+ #36 🔄 executing comment-aware pipeline
370
+ #37 ⏳ new /mgw:status dashboard
371
+ #38 🔒 blocked contextual routing (blocked by #37)
372
+
373
+ Milestone Health:
374
+ Completed: ${HEALTH_DONE}/${TOTAL_ISSUES}
375
+ Velocity: ${HEALTH_VELOCITY}
376
+ Blocked: ${HEALTH_BLOCKED}
377
+
378
+ Open PRs:
379
+ #40 ← #36 comment-aware pipeline (review requested)
380
+
381
+ Next Milestone: ${next_name} (${next_done}/${next_total} done)
382
+ ```
383
+
384
+ Full display example with board configured:
385
+ ```
386
+ Board: https://github.com/orgs/snipcodeit/projects/1
387
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
388
+ MGW > PROJECT STATUS: snipcodeit/mgw
389
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
390
+
391
+ Current Milestone: v2 — Team Collaboration (3/6 done)
392
+ Progress: ████████░░░░░░░░ 50%
393
+
394
+ #80 ✅ done Add mgw:assign command
395
+ #81 ✅ done Post board link to Discussions
396
+ #82 ✅ done Add mgw:board sync
397
+ #83 🔄 executing Add milestone health report
398
+ #84 ⏳ new Create mgw:roadmap command
399
+ #85 ⏳ new Add growth analytics
400
+
401
+ Milestone Health:
402
+ Completed: 3/6
403
+ Velocity: 2.1/day
404
+ Blocked: 0
405
+
406
+ Open PRs:
407
+ (none matched to this milestone)
408
+
409
+ Next Milestone: v3 — Analytics & Extensions (0/5 done)
410
+ ```
411
+
412
+ Rendering rules:
413
+ - Print board URL line (` Board: ${BOARD_URL}`) only when BOARD_URL is non-empty
414
+ - Use stage icons from the issue table
415
+ - Right-align issue numbers
416
+ - Truncate titles to 50 chars
417
+ - Milestone Health section always appears in project mode (after issue table, before Open PRs)
418
+ - If no open PRs matched to milestone, show "No open PRs for this milestone."
419
+ - If no next milestone, show "No more milestones planned."
420
+ - If `TARGET_MILESTONE != CURRENT_MILESTONE`, add "(viewing milestone ${TARGET_MILESTONE})" to header
421
+ </step>
422
+
423
+ <step name="json_output">
424
+ **If --json flag: output machine-readable JSON instead:**
425
+
426
+ ```bash
427
+ if [ "$JSON_OUTPUT" = true ]; then
428
+ # Build structured JSON output
429
+ OUTPUT=$(python3 -c "
430
+ import json
431
+
432
+ result = {
433
+ 'repo': '${REPO_NAME}',
434
+ 'board_url': '${BOARD_URL}',
435
+ 'current_milestone': ${CURRENT_MILESTONE}, # 1-indexed (legacy compat)
436
+ 'active_milestone_idx': ${CURRENT_MILESTONE_IDX}, # 0-based resolved index
437
+ 'viewing_milestone': ${TARGET_MILESTONE},
438
+ 'milestone': {
439
+ 'name': '${MILESTONE_NAME}',
440
+ 'total_issues': ${TOTAL_ISSUES},
441
+ 'done': done_count,
442
+ 'progress_pct': pct,
443
+ 'health': {
444
+ 'done': int('${HEALTH_DONE}' or '0'),
445
+ 'total': ${TOTAL_ISSUES},
446
+ 'blocked': int('${HEALTH_BLOCKED}' or '0'),
447
+ 'velocity': '${HEALTH_VELOCITY}'
448
+ },
449
+ 'issues': issues_with_stages
450
+ },
451
+ 'open_prs': matched_prs,
452
+ 'next_milestone': next_milestone_data
453
+ }
454
+ print(json.dumps(result, indent=2))
455
+ ")
456
+ echo "$OUTPUT"
457
+ # Exit — do not display the formatted dashboard
458
+ fi
459
+ ```
460
+
461
+ The JSON structure:
462
+ ```json
463
+ {
464
+ "repo": "owner/repo",
465
+ "board_url": "https://github.com/orgs/snipcodeit/projects/1",
466
+ "current_milestone": 2,
467
+ "active_milestone_idx": 1,
468
+ "viewing_milestone": 2,
469
+ "milestone": {
470
+ "name": "v2 — Team Collaboration & Lifecycle Orchestration",
471
+ "total_issues": 6,
472
+ "done": 3,
473
+ "progress_pct": 50,
474
+ "health": {
475
+ "done": 3,
476
+ "total": 6,
477
+ "blocked": 0,
478
+ "velocity": "2.1/day"
479
+ },
480
+ "issues": [
481
+ {
482
+ "number": 80,
483
+ "title": "Add mgw:assign command",
484
+ "pipeline_stage": "done",
485
+ "labels": ["enhancement"]
486
+ }
487
+ ]
488
+ },
489
+ "open_prs": [
490
+ {
491
+ "number": 95,
492
+ "title": "Add mgw:assign command",
493
+ "linked_issue": 80,
494
+ "review_status": "approved"
495
+ }
496
+ ],
497
+ "next_milestone": {
498
+ "name": "v3 — Analytics & Extensions",
499
+ "total_issues": 5,
500
+ "done": 0
501
+ }
502
+ }
503
+ ```
504
+ </step>
505
+
506
+ </process>
507
+
508
+ <success_criteria>
509
+ - [ ] project.json loaded and target milestone identified
510
+ - [ ] Graceful fallback when project.json missing (GitHub-only mode)
511
+ - [ ] Progress bar rendered with correct percentage
512
+ - [ ] Per-issue status shown with pipeline stage icons
513
+ - [ ] Open PRs fetched and matched to milestone issues
514
+ - [ ] Next milestone preview displayed (if exists)
515
+ - [ ] --json flag outputs machine-readable JSON
516
+ - [ ] Milestone number argument selects non-current milestone
517
+ - [ ] Read-only: no state modifications, no GitHub writes
518
+ - [ ] No agent spawns, no side effects
519
+ - [ ] Board URL displayed before header when board_url is set in project.json
520
+ - [ ] --board flag opens board URL via xdg-open (open on macOS fallback) and exits 0
521
+ - [ ] --board flag exits 1 with helpful error when no board configured
522
+ - [ ] Milestone Health section shows Completed N/total, Velocity, and Blocked count
523
+ - [ ] Velocity computed from .mgw/active/ and .mgw/completed/ file mtimes
524
+ - [ ] --json output includes board_url and milestone.health object
525
+ - [ ] Board URL line omitted when board_url is not set in project.json
526
+ </success_criteria>