@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,489 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mgw:roadmap
|
|
3
|
+
description: Render project milestones as a roadmap — completion table, due dates, and optional Discussion post
|
|
4
|
+
argument-hint: "[--set-dates] [--post-discussion] [--json]"
|
|
5
|
+
allowed-tools:
|
|
6
|
+
- Bash
|
|
7
|
+
- Read
|
|
8
|
+
- Write
|
|
9
|
+
- Edit
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
<objective>
|
|
13
|
+
Render the current MGW project's milestones as a roadmap view. Three capabilities:
|
|
14
|
+
|
|
15
|
+
- **Table** (always) — Prints a markdown table of milestone name, issue count, completion
|
|
16
|
+
percentage, and board URL for each milestone in project.json.
|
|
17
|
+
- **Due dates** (`--set-dates`) — Interactively prompts for a due date per milestone and
|
|
18
|
+
sets it on the corresponding GitHub milestone via the REST API. Enables the Roadmap
|
|
19
|
+
layout timeline in GitHub Projects v2.
|
|
20
|
+
- **Discussion post** (`--post-discussion`) — Posts the roadmap table as a new Discussion
|
|
21
|
+
in the repo's roadmap category (creates the category if it doesn't exist). Intended as
|
|
22
|
+
a persistent, pinnable roadmap artifact.
|
|
23
|
+
|
|
24
|
+
Reads `.mgw/project.json` and calls GitHub API only. Never reads application source code.
|
|
25
|
+
Follows delegation boundary: no Task() spawns needed — all operations are metadata-only.
|
|
26
|
+
</objective>
|
|
27
|
+
|
|
28
|
+
<execution_context>
|
|
29
|
+
@~/.claude/commands/mgw/workflows/state.md
|
|
30
|
+
@~/.claude/commands/mgw/workflows/github.md
|
|
31
|
+
</execution_context>
|
|
32
|
+
|
|
33
|
+
<context>
|
|
34
|
+
Flags: $ARGUMENTS
|
|
35
|
+
|
|
36
|
+
Repo detected via: gh repo view --json nameWithOwner -q .nameWithOwner
|
|
37
|
+
State: .mgw/project.json
|
|
38
|
+
</context>
|
|
39
|
+
|
|
40
|
+
<process>
|
|
41
|
+
|
|
42
|
+
<step name="parse_arguments">
|
|
43
|
+
**Parse $ARGUMENTS for flags:**
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
SET_DATES=false
|
|
47
|
+
POST_DISCUSSION=false
|
|
48
|
+
JSON_OUTPUT=false
|
|
49
|
+
|
|
50
|
+
for ARG in $ARGUMENTS; do
|
|
51
|
+
case "$ARG" in
|
|
52
|
+
--set-dates) SET_DATES=true ;;
|
|
53
|
+
--post-discussion) POST_DISCUSSION=true ;;
|
|
54
|
+
--json) JSON_OUTPUT=true ;;
|
|
55
|
+
esac
|
|
56
|
+
done
|
|
57
|
+
```
|
|
58
|
+
</step>
|
|
59
|
+
|
|
60
|
+
<step name="validate_environment">
|
|
61
|
+
**Validate git repo and GitHub remote:**
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
|
|
65
|
+
if [ -z "$REPO_ROOT" ]; then
|
|
66
|
+
echo "Not a git repository. Run from a repo root."
|
|
67
|
+
exit 1
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
MGW_DIR="${REPO_ROOT}/.mgw"
|
|
71
|
+
REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner 2>/dev/null)
|
|
72
|
+
if [ -z "$REPO" ]; then
|
|
73
|
+
echo "No GitHub remote found. MGW requires a GitHub repo."
|
|
74
|
+
exit 1
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
if [ ! -f "${MGW_DIR}/project.json" ]; then
|
|
78
|
+
echo "No project initialized. Run /mgw:project first."
|
|
79
|
+
exit 1
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
OWNER=$(echo "$REPO" | cut -d'/' -f1)
|
|
83
|
+
REPO_NAME=$(echo "$REPO" | cut -d'/' -f2)
|
|
84
|
+
```
|
|
85
|
+
</step>
|
|
86
|
+
|
|
87
|
+
<step name="load_project">
|
|
88
|
+
**Load project.json and extract milestone data:**
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
PROJECT_JSON=$(cat "${MGW_DIR}/project.json")
|
|
92
|
+
|
|
93
|
+
PROJECT_NAME=$(echo "$PROJECT_JSON" | python3 -c "
|
|
94
|
+
import json, sys
|
|
95
|
+
p = json.load(sys.stdin)
|
|
96
|
+
print(p.get('project', {}).get('name', 'unknown'))
|
|
97
|
+
")
|
|
98
|
+
|
|
99
|
+
# Extract board URL (top-level board_url or nested project_board.url)
|
|
100
|
+
BOARD_URL=$(echo "$PROJECT_JSON" | python3 -c "
|
|
101
|
+
import json, sys
|
|
102
|
+
p = json.load(sys.stdin)
|
|
103
|
+
url = (p.get('project', {}).get('project_board', {}).get('url', '')
|
|
104
|
+
or p.get('board_url', ''))
|
|
105
|
+
print(url or '')
|
|
106
|
+
" 2>/dev/null || echo "")
|
|
107
|
+
|
|
108
|
+
# Extract all milestones with computed stats
|
|
109
|
+
MILESTONES_DATA=$(echo "$PROJECT_JSON" | python3 -c "
|
|
110
|
+
import json, sys
|
|
111
|
+
|
|
112
|
+
p = json.load(sys.stdin)
|
|
113
|
+
milestones = p.get('milestones', [])
|
|
114
|
+
out = []
|
|
115
|
+
for m in milestones:
|
|
116
|
+
issues = m.get('issues', [])
|
|
117
|
+
total = len(issues)
|
|
118
|
+
done = sum(1 for i in issues if i.get('pipeline_stage') in ('done', 'pr-created'))
|
|
119
|
+
pct = int((done / total) * 100) if total > 0 else 0
|
|
120
|
+
out.append({
|
|
121
|
+
'github_number': m.get('github_number'),
|
|
122
|
+
'name': m.get('name', 'Unnamed'),
|
|
123
|
+
'gsd_state': m.get('gsd_state'),
|
|
124
|
+
'total': total,
|
|
125
|
+
'done': done,
|
|
126
|
+
'pct': pct,
|
|
127
|
+
'github_url': m.get('github_url', ''),
|
|
128
|
+
})
|
|
129
|
+
print(json.dumps(out))
|
|
130
|
+
")
|
|
131
|
+
|
|
132
|
+
TOTAL_MILESTONES=$(echo "$MILESTONES_DATA" | python3 -c "import json,sys; print(len(json.load(sys.stdin)))")
|
|
133
|
+
```
|
|
134
|
+
</step>
|
|
135
|
+
|
|
136
|
+
<step name="fetch_github_milestone_due_dates">
|
|
137
|
+
**Fetch current due dates from GitHub for each milestone:**
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
# Fetch all GitHub milestones for this repo once (avoids N+1 API calls)
|
|
141
|
+
GH_MILESTONES=$(gh api "repos/${REPO}/milestones?state=all&per_page=100" 2>/dev/null || echo "[]")
|
|
142
|
+
|
|
143
|
+
# Build map: github_number -> due_on
|
|
144
|
+
DUE_DATE_MAP=$(echo "$GH_MILESTONES" | python3 -c "
|
|
145
|
+
import json, sys
|
|
146
|
+
milestones = json.load(sys.stdin)
|
|
147
|
+
result = {}
|
|
148
|
+
for m in milestones:
|
|
149
|
+
due = m.get('due_on', '') or ''
|
|
150
|
+
# Trim to date only (GitHub returns ISO datetime)
|
|
151
|
+
if due:
|
|
152
|
+
due = due[:10]
|
|
153
|
+
result[str(m['number'])] = due
|
|
154
|
+
print(json.dumps(result))
|
|
155
|
+
")
|
|
156
|
+
```
|
|
157
|
+
</step>
|
|
158
|
+
|
|
159
|
+
<step name="build_roadmap_table">
|
|
160
|
+
**Compute per-milestone rows for the roadmap table:**
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
TABLE_DATA=$(echo "$MILESTONES_DATA" | python3 -c "
|
|
164
|
+
import json, sys, os
|
|
165
|
+
|
|
166
|
+
milestones = json.load(sys.stdin)
|
|
167
|
+
due_map = json.loads(os.environ.get('DUE_DATE_MAP', '{}'))
|
|
168
|
+
board_url = os.environ.get('BOARD_URL', '')
|
|
169
|
+
|
|
170
|
+
rows = []
|
|
171
|
+
for m in milestones:
|
|
172
|
+
num = str(m.get('github_number', ''))
|
|
173
|
+
due = due_map.get(num, '')
|
|
174
|
+
bar_filled = int(m['pct'] / 100 * 8)
|
|
175
|
+
bar = chr(9608) * bar_filled + chr(9617) * (8 - bar_filled)
|
|
176
|
+
|
|
177
|
+
# Status indicator
|
|
178
|
+
if m.get('gsd_state') == 'completed':
|
|
179
|
+
status = 'done'
|
|
180
|
+
elif m.get('gsd_state') == 'active':
|
|
181
|
+
status = 'active'
|
|
182
|
+
else:
|
|
183
|
+
status = 'planned'
|
|
184
|
+
|
|
185
|
+
rows.append({
|
|
186
|
+
'number': num,
|
|
187
|
+
'name': m['name'],
|
|
188
|
+
'status': status,
|
|
189
|
+
'done': m['done'],
|
|
190
|
+
'total': m['total'],
|
|
191
|
+
'pct': m['pct'],
|
|
192
|
+
'bar': bar,
|
|
193
|
+
'due': due or 'not set',
|
|
194
|
+
'github_url': m.get('github_url', ''),
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
print(json.dumps(rows))
|
|
198
|
+
")
|
|
199
|
+
|
|
200
|
+
# Export for use in due-date and discussion steps
|
|
201
|
+
export DUE_DATE_MAP BOARD_URL TABLE_DATA
|
|
202
|
+
```
|
|
203
|
+
</step>
|
|
204
|
+
|
|
205
|
+
<step name="display_roadmap_table">
|
|
206
|
+
**Print the roadmap table to the terminal:**
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
echo "$TABLE_DATA" | python3 -c "
|
|
210
|
+
import json, sys
|
|
211
|
+
|
|
212
|
+
rows = json.load(sys.stdin)
|
|
213
|
+
|
|
214
|
+
print()
|
|
215
|
+
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
|
|
216
|
+
print(' MGW > ROADMAP: ${PROJECT_NAME}')
|
|
217
|
+
if '${BOARD_URL}':
|
|
218
|
+
print(' Board: ${BOARD_URL}')
|
|
219
|
+
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
|
|
220
|
+
print()
|
|
221
|
+
|
|
222
|
+
# Header
|
|
223
|
+
print(f'{\"Milestone\":<48} {\"Status\":<8} {\"Issues\":<10} {\"Progress\":<14} {\"Due Date\":<12}')
|
|
224
|
+
print('-' * 98)
|
|
225
|
+
|
|
226
|
+
for r in rows:
|
|
227
|
+
name_cell = r['name'][:47]
|
|
228
|
+
issues_cell = f\"{r['done']}/{r['total']}\"
|
|
229
|
+
progress_cell = f\"{r['bar']} {r['pct']}%\"
|
|
230
|
+
print(f\"{name_cell:<48} {r['status']:<8} {issues_cell:<10} {progress_cell:<14} {r['due']:<12}\")
|
|
231
|
+
|
|
232
|
+
print()
|
|
233
|
+
total_issues = sum(r['total'] for r in rows)
|
|
234
|
+
done_issues = sum(r['done'] for r in rows)
|
|
235
|
+
overall_pct = int((done_issues / total_issues) * 100) if total_issues > 0 else 0
|
|
236
|
+
print(f'Overall: {done_issues}/{total_issues} issues done ({overall_pct}%)')
|
|
237
|
+
print()
|
|
238
|
+
"
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
**If `--json` flag:** emit JSON instead of formatted table and exit:
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
if [ "$JSON_OUTPUT" = true ]; then
|
|
245
|
+
echo "$TABLE_DATA" | python3 -c "
|
|
246
|
+
import json, sys
|
|
247
|
+
rows = json.load(sys.stdin)
|
|
248
|
+
result = {
|
|
249
|
+
'repo': '${REPO}',
|
|
250
|
+
'project_name': '${PROJECT_NAME}',
|
|
251
|
+
'board_url': '${BOARD_URL}',
|
|
252
|
+
'milestones': rows
|
|
253
|
+
}
|
|
254
|
+
print(json.dumps(result, indent=2))
|
|
255
|
+
"
|
|
256
|
+
exit 0
|
|
257
|
+
fi
|
|
258
|
+
```
|
|
259
|
+
</step>
|
|
260
|
+
|
|
261
|
+
<step name="set_due_dates">
|
|
262
|
+
**If `--set-dates`: interactively set GitHub milestone due dates:**
|
|
263
|
+
|
|
264
|
+
This step runs only when `SET_DATES=true`. It prompts for a date per milestone and
|
|
265
|
+
calls the GitHub REST API to set `due_on`. Setting due dates enables the Roadmap layout
|
|
266
|
+
timeline in GitHub Projects v2.
|
|
267
|
+
|
|
268
|
+
```bash
|
|
269
|
+
if [ "$SET_DATES" = true ]; then
|
|
270
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
271
|
+
echo " SET DUE DATES"
|
|
272
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
273
|
+
echo ""
|
|
274
|
+
echo "Enter due dates for each milestone (YYYY-MM-DD format, or press Enter to skip)."
|
|
275
|
+
echo ""
|
|
276
|
+
|
|
277
|
+
# Iterate each milestone and prompt
|
|
278
|
+
echo "$TABLE_DATA" | python3 -c "import json,sys; [print(r['number'], r['name']) for r in json.load(sys.stdin) if r['number']]" | \
|
|
279
|
+
while read -r MILESTONE_NUM MILESTONE_NAME; do
|
|
280
|
+
CURRENT_DUE=$(echo "$DUE_DATE_MAP" | python3 -c "
|
|
281
|
+
import json,sys
|
|
282
|
+
m = json.load(sys.stdin)
|
|
283
|
+
print(m.get('${MILESTONE_NUM}', 'not set'))
|
|
284
|
+
")
|
|
285
|
+
echo -n " ${MILESTONE_NAME} (current: ${CURRENT_DUE}): "
|
|
286
|
+
read -r INPUT_DATE
|
|
287
|
+
|
|
288
|
+
if [ -z "$INPUT_DATE" ]; then
|
|
289
|
+
echo " → skipped"
|
|
290
|
+
continue
|
|
291
|
+
fi
|
|
292
|
+
|
|
293
|
+
# Validate date format
|
|
294
|
+
if ! echo "$INPUT_DATE" | grep -qE '^[0-9]{4}-[0-9]{2}-[0-9]{2}$'; then
|
|
295
|
+
echo " → invalid format (expected YYYY-MM-DD), skipped"
|
|
296
|
+
continue
|
|
297
|
+
fi
|
|
298
|
+
|
|
299
|
+
# GitHub requires ISO 8601 with time: append T07:00:00Z (noon UTC)
|
|
300
|
+
DUE_ISO="${INPUT_DATE}T07:00:00Z"
|
|
301
|
+
|
|
302
|
+
RESULT=$(gh api "repos/${REPO}/milestones/${MILESTONE_NUM}" \
|
|
303
|
+
--method PATCH \
|
|
304
|
+
-f due_on="$DUE_ISO" \
|
|
305
|
+
--jq '.due_on' 2>&1)
|
|
306
|
+
|
|
307
|
+
if echo "$RESULT" | grep -q "^[0-9]"; then
|
|
308
|
+
echo " → set to ${INPUT_DATE}"
|
|
309
|
+
else
|
|
310
|
+
echo " → error setting date: ${RESULT}"
|
|
311
|
+
fi
|
|
312
|
+
done
|
|
313
|
+
|
|
314
|
+
echo ""
|
|
315
|
+
echo "Due dates updated. Roadmap timeline is now enabled in GitHub Projects v2."
|
|
316
|
+
echo "Open the board and switch to Roadmap view to see the timeline."
|
|
317
|
+
echo ""
|
|
318
|
+
fi
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
Note: `due_on` is set via the GitHub REST API at `PATCH /repos/{owner}/{repo}/milestones/{milestone_number}`.
|
|
322
|
+
The milestone number here is the GitHub milestone number (integer), not the project.json index.
|
|
323
|
+
</step>
|
|
324
|
+
|
|
325
|
+
<step name="post_discussion">
|
|
326
|
+
**If `--post-discussion`: post the roadmap table as a GitHub Discussion:**
|
|
327
|
+
|
|
328
|
+
This step runs only when `POST_DISCUSSION=true`. It creates (or finds) a "Roadmap"
|
|
329
|
+
Discussion category and posts the markdown table as a new Discussion titled
|
|
330
|
+
"Project Roadmap — {project_name}".
|
|
331
|
+
|
|
332
|
+
```bash
|
|
333
|
+
if [ "$POST_DISCUSSION" = true ]; then
|
|
334
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
335
|
+
echo " POST DISCUSSION"
|
|
336
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
337
|
+
echo ""
|
|
338
|
+
|
|
339
|
+
# Get repo node ID (needed for GraphQL Discussion mutation)
|
|
340
|
+
REPO_NODE_ID=$(gh api graphql -f query='
|
|
341
|
+
query($owner: String!, $name: String!) {
|
|
342
|
+
repository(owner: $owner, name: $name) { id }
|
|
343
|
+
}
|
|
344
|
+
' -f owner="$OWNER" -f name="$REPO_NAME" --jq '.data.repository.id')
|
|
345
|
+
|
|
346
|
+
# Find or create the Roadmap discussion category
|
|
347
|
+
CATEGORY_ID=$(gh api graphql -f query='
|
|
348
|
+
query($owner: String!, $name: String!) {
|
|
349
|
+
repository(owner: $owner, name: $name) {
|
|
350
|
+
discussionCategories(first: 25) {
|
|
351
|
+
nodes { id name }
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
' -f owner="$OWNER" -f name="$REPO_NAME" \
|
|
356
|
+
--jq '.data.repository.discussionCategories.nodes[] | select(.name == "Roadmap") | .id' 2>/dev/null)
|
|
357
|
+
|
|
358
|
+
if [ -z "$CATEGORY_ID" ]; then
|
|
359
|
+
echo "No 'Roadmap' discussion category found."
|
|
360
|
+
echo "Please create a 'Roadmap' category in GitHub Discussions settings, then re-run."
|
|
361
|
+
echo "Path: https://github.com/${REPO}/settings -> Discussions -> Categories"
|
|
362
|
+
echo ""
|
|
363
|
+
echo "Alternatively, posting to the 'Announcements' category (first available)..."
|
|
364
|
+
|
|
365
|
+
# Fallback: use the first available category
|
|
366
|
+
CATEGORY_ID=$(gh api graphql -f query='
|
|
367
|
+
query($owner: String!, $name: String!) {
|
|
368
|
+
repository(owner: $owner, name: $name) {
|
|
369
|
+
discussionCategories(first: 1) {
|
|
370
|
+
nodes { id name }
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
' -f owner="$OWNER" -f name="$REPO_NAME" \
|
|
375
|
+
--jq '.data.repository.discussionCategories.nodes[0].id' 2>/dev/null)
|
|
376
|
+
|
|
377
|
+
CATEGORY_NAME=$(gh api graphql -f query='
|
|
378
|
+
query($owner: String!, $name: String!) {
|
|
379
|
+
repository(owner: $owner, name: $name) {
|
|
380
|
+
discussionCategories(first: 1) {
|
|
381
|
+
nodes { id name }
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
' -f owner="$OWNER" -f name="$REPO_NAME" \
|
|
386
|
+
--jq '.data.repository.discussionCategories.nodes[0].name' 2>/dev/null)
|
|
387
|
+
|
|
388
|
+
echo "Using category: ${CATEGORY_NAME}"
|
|
389
|
+
echo ""
|
|
390
|
+
fi
|
|
391
|
+
|
|
392
|
+
if [ -z "$CATEGORY_ID" ]; then
|
|
393
|
+
echo "Could not find a discussion category. Discussions may not be enabled."
|
|
394
|
+
echo "Enable Discussions at: https://github.com/${REPO}/settings"
|
|
395
|
+
else
|
|
396
|
+
# Build discussion body (markdown table)
|
|
397
|
+
DISCUSSION_BODY=$(echo "$TABLE_DATA" | python3 -c "
|
|
398
|
+
import json, sys, os
|
|
399
|
+
|
|
400
|
+
rows = json.load(sys.stdin)
|
|
401
|
+
board_url = os.environ.get('BOARD_URL', '')
|
|
402
|
+
project_name = os.environ.get('PROJECT_NAME', 'MGW')
|
|
403
|
+
|
|
404
|
+
lines = []
|
|
405
|
+
lines.append('## Project Roadmap')
|
|
406
|
+
lines.append('')
|
|
407
|
+
if board_url:
|
|
408
|
+
lines.append(f'> Board: {board_url}')
|
|
409
|
+
lines.append('')
|
|
410
|
+
lines.append('| Milestone | Status | Issues | Progress | Due Date |')
|
|
411
|
+
lines.append('|-----------|--------|--------|----------|----------|')
|
|
412
|
+
|
|
413
|
+
for r in rows:
|
|
414
|
+
name_link = f\"[{r['name']}]({r['github_url']})\" if r.get('github_url') else r['name']
|
|
415
|
+
issues_cell = f\"{r['done']}/{r['total']}\"
|
|
416
|
+
progress_cell = f\"{r['bar']} {r['pct']}%\"
|
|
417
|
+
lines.append(f\"| {name_link} | {r['status']} | {issues_cell} | {progress_cell} | {r['due']} |\")
|
|
418
|
+
|
|
419
|
+
lines.append('')
|
|
420
|
+
total_issues = sum(r['total'] for r in rows)
|
|
421
|
+
done_issues = sum(r['done'] for r in rows)
|
|
422
|
+
overall_pct = int((done_issues / total_issues) * 100) if total_issues > 0 else 0
|
|
423
|
+
lines.append(f'**Overall:** {done_issues}/{total_issues} issues done ({overall_pct}%)')
|
|
424
|
+
lines.append('')
|
|
425
|
+
lines.append('---')
|
|
426
|
+
lines.append('*Auto-generated by [MGW](https://github.com/snipcodeit/mgw) — `/mgw:roadmap --post-discussion`*')
|
|
427
|
+
|
|
428
|
+
print('\n'.join(lines))
|
|
429
|
+
")
|
|
430
|
+
|
|
431
|
+
DISCUSSION_TITLE="Project Roadmap — ${PROJECT_NAME}"
|
|
432
|
+
|
|
433
|
+
# Create the Discussion via GraphQL
|
|
434
|
+
DISCUSSION_RESULT=$(gh api graphql -f query='
|
|
435
|
+
mutation($repoId: ID!, $categoryId: ID!, $title: String!, $body: String!) {
|
|
436
|
+
createDiscussion(input: {
|
|
437
|
+
repositoryId: $repoId,
|
|
438
|
+
categoryId: $categoryId,
|
|
439
|
+
title: $title,
|
|
440
|
+
body: $body
|
|
441
|
+
}) {
|
|
442
|
+
discussion { url number }
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
' \
|
|
446
|
+
-f repoId="$REPO_NODE_ID" \
|
|
447
|
+
-f categoryId="$CATEGORY_ID" \
|
|
448
|
+
-f title="$DISCUSSION_TITLE" \
|
|
449
|
+
-f body="$DISCUSSION_BODY" 2>&1)
|
|
450
|
+
|
|
451
|
+
DISCUSSION_URL=$(echo "$DISCUSSION_RESULT" | python3 -c "
|
|
452
|
+
import json, sys
|
|
453
|
+
try:
|
|
454
|
+
d = json.load(sys.stdin)
|
|
455
|
+
print(d['data']['createDiscussion']['discussion']['url'])
|
|
456
|
+
except Exception:
|
|
457
|
+
print('')
|
|
458
|
+
" 2>/dev/null)
|
|
459
|
+
|
|
460
|
+
if [ -n "$DISCUSSION_URL" ]; then
|
|
461
|
+
echo "Discussion posted: ${DISCUSSION_URL}"
|
|
462
|
+
echo ""
|
|
463
|
+
echo "To pin this roadmap: open the discussion and click 'Pin discussion'."
|
|
464
|
+
else
|
|
465
|
+
echo "Failed to post discussion. Response:"
|
|
466
|
+
echo "$DISCUSSION_RESULT" | python3 -m json.tool 2>/dev/null || echo "$DISCUSSION_RESULT"
|
|
467
|
+
fi
|
|
468
|
+
fi
|
|
469
|
+
fi
|
|
470
|
+
```
|
|
471
|
+
</step>
|
|
472
|
+
|
|
473
|
+
</process>
|
|
474
|
+
|
|
475
|
+
<success_criteria>
|
|
476
|
+
- [ ] project.json loaded; graceful error when missing
|
|
477
|
+
- [ ] Milestone table printed with name, status, done/total issues, progress bar + percentage, due date
|
|
478
|
+
- [ ] Overall summary line (total done/total across all milestones)
|
|
479
|
+
- [ ] --json flag outputs machine-readable JSON and exits 0
|
|
480
|
+
- [ ] --set-dates: prompts for date per milestone, sets GitHub milestone due_on via REST API
|
|
481
|
+
- [ ] --set-dates: skips milestones where Enter is pressed with no input
|
|
482
|
+
- [ ] --set-dates: validates YYYY-MM-DD format before API call; reports invalid format without exiting
|
|
483
|
+
- [ ] --post-discussion: finds or falls back from Roadmap category; creates Discussion with markdown table
|
|
484
|
+
- [ ] --post-discussion: outputs Discussion URL on success
|
|
485
|
+
- [ ] --post-discussion: error message when Discussions not enabled, no exit failure
|
|
486
|
+
- [ ] Board URL shown in header when present in project.json
|
|
487
|
+
- [ ] Read-only by default: no GitHub writes unless --set-dates or --post-discussion passed
|
|
488
|
+
- [ ] Delegation boundary respected: no application source reads, no Task() spawns needed
|
|
489
|
+
</success_criteria>
|