@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
|
@@ -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>
|