@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,952 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mgw:milestone
|
|
3
|
+
description: Execute a milestone's issues in dependency order — auto-sync, rate-limit guard, per-issue checkpoint
|
|
4
|
+
argument-hint: "[milestone-number] [--interactive] [--dry-run]"
|
|
5
|
+
allowed-tools:
|
|
6
|
+
- Bash
|
|
7
|
+
- Read
|
|
8
|
+
- Write
|
|
9
|
+
- Edit
|
|
10
|
+
- Glob
|
|
11
|
+
- Grep
|
|
12
|
+
- Task
|
|
13
|
+
- AskUserQuestion
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
<objective>
|
|
17
|
+
Orchestrate execution of all issues in a milestone by delegating each to `/mgw:run`
|
|
18
|
+
in dependency order. Sequential execution (one issue at a time), autonomous by default.
|
|
19
|
+
|
|
20
|
+
Handles: dependency resolution (topological sort), pre-sync against GitHub, rate limit
|
|
21
|
+
guard, per-issue checkpointing to project.json, failure cascading (skip failed, block
|
|
22
|
+
dependents, continue unblocked), resume detection, milestone close + draft release on
|
|
23
|
+
completion, and auto-advance to next milestone.
|
|
24
|
+
|
|
25
|
+
The `--interactive` flag pauses between issues for user confirmation.
|
|
26
|
+
The `--dry-run` flag shows the execution plan without running anything.
|
|
27
|
+
</objective>
|
|
28
|
+
|
|
29
|
+
<execution_context>
|
|
30
|
+
@~/.claude/commands/mgw/workflows/state.md
|
|
31
|
+
@~/.claude/commands/mgw/workflows/github.md
|
|
32
|
+
@~/.claude/commands/mgw/workflows/gsd.md
|
|
33
|
+
@~/.claude/commands/mgw/workflows/validation.md
|
|
34
|
+
</execution_context>
|
|
35
|
+
|
|
36
|
+
<context>
|
|
37
|
+
Milestone number: $ARGUMENTS (optional — defaults to current_milestone from project.json)
|
|
38
|
+
Flags: --interactive, --dry-run
|
|
39
|
+
</context>
|
|
40
|
+
|
|
41
|
+
<process>
|
|
42
|
+
|
|
43
|
+
<step name="parse_arguments">
|
|
44
|
+
**Parse $ARGUMENTS for milestone number and flags:**
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
MILESTONE_NUM=""
|
|
48
|
+
INTERACTIVE=false
|
|
49
|
+
DRY_RUN=false
|
|
50
|
+
|
|
51
|
+
for ARG in $ARGUMENTS; do
|
|
52
|
+
case "$ARG" in
|
|
53
|
+
--interactive) INTERACTIVE=true ;;
|
|
54
|
+
--dry-run) DRY_RUN=true ;;
|
|
55
|
+
[0-9]*) MILESTONE_NUM="$ARG" ;;
|
|
56
|
+
esac
|
|
57
|
+
done
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
If no milestone number provided, read from project.json:
|
|
61
|
+
```bash
|
|
62
|
+
REPO_ROOT=$(git rev-parse --show-toplevel)
|
|
63
|
+
MGW_DIR="${REPO_ROOT}/.mgw"
|
|
64
|
+
|
|
65
|
+
if [ -z "$MILESTONE_NUM" ]; then
|
|
66
|
+
if [ ! -f "${MGW_DIR}/project.json" ]; then
|
|
67
|
+
echo "No project initialized. Run /mgw:project first."
|
|
68
|
+
exit 1
|
|
69
|
+
fi
|
|
70
|
+
# Resolve active milestone index (0-based) and convert to 1-indexed milestone number
|
|
71
|
+
ACTIVE_IDX=$(node -e "
|
|
72
|
+
const { loadProjectState, resolveActiveMilestoneIndex } = require('./lib/state.cjs');
|
|
73
|
+
const state = loadProjectState();
|
|
74
|
+
console.log(resolveActiveMilestoneIndex(state));
|
|
75
|
+
")
|
|
76
|
+
if [ "$ACTIVE_IDX" -lt 0 ]; then
|
|
77
|
+
echo "No active milestone set. Run /mgw:project to initialize or set active_gsd_milestone."
|
|
78
|
+
exit 1
|
|
79
|
+
fi
|
|
80
|
+
MILESTONE_NUM=$((ACTIVE_IDX + 1))
|
|
81
|
+
fi
|
|
82
|
+
```
|
|
83
|
+
</step>
|
|
84
|
+
|
|
85
|
+
<step name="validate_and_sync">
|
|
86
|
+
**Run validate_and_load then batch staleness check (MLST-03):**
|
|
87
|
+
|
|
88
|
+
Follow initialization procedure from @~/.claude/commands/mgw/workflows/state.md:
|
|
89
|
+
- Ensure .mgw/, active/, completed/ exist
|
|
90
|
+
- Ensure .gitignore entries
|
|
91
|
+
- Initialize cross-refs.json if missing
|
|
92
|
+
|
|
93
|
+
Run batch staleness check (non-blocking):
|
|
94
|
+
```bash
|
|
95
|
+
# Batch staleness check from state.md
|
|
96
|
+
# If check fails (network error, API limit), log warning and continue
|
|
97
|
+
check_batch_staleness "${MGW_DIR}" 2>/dev/null || echo "MGW: Staleness check skipped (network unavailable)"
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
This satisfies MLST-03: pre-sync before starting.
|
|
101
|
+
</step>
|
|
102
|
+
|
|
103
|
+
<step name="load_milestone">
|
|
104
|
+
**Load project.json and extract milestone data:**
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
PROJECT_JSON=$(cat "${MGW_DIR}/project.json")
|
|
108
|
+
MILESTONE_NAME=$(echo "$PROJECT_JSON" | python3 -c "
|
|
109
|
+
import json,sys
|
|
110
|
+
p = json.load(sys.stdin)
|
|
111
|
+
idx = ${MILESTONE_NUM} - 1
|
|
112
|
+
if idx < 0 or idx >= len(p['milestones']):
|
|
113
|
+
print('ERROR: Milestone ${MILESTONE_NUM} not found')
|
|
114
|
+
sys.exit(1)
|
|
115
|
+
m = p['milestones'][idx]
|
|
116
|
+
print(m['name'])
|
|
117
|
+
")
|
|
118
|
+
|
|
119
|
+
MILESTONE_GH_NUMBER=$(echo "$PROJECT_JSON" | python3 -c "
|
|
120
|
+
import json,sys
|
|
121
|
+
p = json.load(sys.stdin)
|
|
122
|
+
print(p['milestones'][${MILESTONE_NUM} - 1].get('github_number', ''))
|
|
123
|
+
")
|
|
124
|
+
|
|
125
|
+
ISSUES_JSON=$(echo "$PROJECT_JSON" | python3 -c "
|
|
126
|
+
import json,sys
|
|
127
|
+
p = json.load(sys.stdin)
|
|
128
|
+
m = p['milestones'][${MILESTONE_NUM} - 1]
|
|
129
|
+
print(json.dumps(m['issues']))
|
|
130
|
+
")
|
|
131
|
+
|
|
132
|
+
TOTAL_ISSUES=$(echo "$ISSUES_JSON" | python3 -c "import json,sys; print(len(json.load(sys.stdin)))")
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Display:
|
|
136
|
+
```
|
|
137
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
138
|
+
MGW ► MILESTONE ${MILESTONE_NUM}: ${MILESTONE_NAME}
|
|
139
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
140
|
+
|
|
141
|
+
Issues: ${TOTAL_ISSUES}
|
|
142
|
+
Mode: ${INTERACTIVE ? "Interactive" : "Autonomous"}
|
|
143
|
+
```
|
|
144
|
+
</step>
|
|
145
|
+
|
|
146
|
+
<step name="resolve_execution_order">
|
|
147
|
+
**Topological sort of issues by dependency (Kahn's algorithm):**
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
SORTED_ISSUES=$(echo "$ISSUES_JSON" | python3 -c "
|
|
151
|
+
import json, sys
|
|
152
|
+
from collections import defaultdict, deque
|
|
153
|
+
|
|
154
|
+
issues = json.load(sys.stdin)
|
|
155
|
+
|
|
156
|
+
# Build slug-to-issue mapping
|
|
157
|
+
slug_to_issue = {}
|
|
158
|
+
num_to_issue = {}
|
|
159
|
+
for issue in issues:
|
|
160
|
+
title = issue.get('title', '')
|
|
161
|
+
slug = title.lower().replace(' ', '-')[:40]
|
|
162
|
+
slug_to_issue[slug] = issue
|
|
163
|
+
num_to_issue[issue['github_number']] = issue
|
|
164
|
+
|
|
165
|
+
# Build adjacency list and in-degree map
|
|
166
|
+
in_degree = defaultdict(int)
|
|
167
|
+
graph = defaultdict(list)
|
|
168
|
+
all_slugs = set()
|
|
169
|
+
|
|
170
|
+
for issue in issues:
|
|
171
|
+
title = issue.get('title', '')
|
|
172
|
+
slug = title.lower().replace(' ', '-')[:40]
|
|
173
|
+
all_slugs.add(slug)
|
|
174
|
+
for dep_slug in issue.get('depends_on_slugs', []):
|
|
175
|
+
if dep_slug in slug_to_issue:
|
|
176
|
+
graph[dep_slug].append(slug)
|
|
177
|
+
in_degree[slug] += 1
|
|
178
|
+
|
|
179
|
+
# Kahn's algorithm with phase_number tiebreak
|
|
180
|
+
queue = [s for s in all_slugs if in_degree[s] == 0]
|
|
181
|
+
order = []
|
|
182
|
+
while queue:
|
|
183
|
+
# Stable sort: prefer lower phase_number
|
|
184
|
+
current = min(queue, key=lambda s: slug_to_issue[s].get('phase_number', 999))
|
|
185
|
+
queue.remove(current)
|
|
186
|
+
order.append(current)
|
|
187
|
+
for neighbor in graph[current]:
|
|
188
|
+
in_degree[neighbor] -= 1
|
|
189
|
+
if in_degree[neighbor] == 0:
|
|
190
|
+
queue.append(neighbor)
|
|
191
|
+
|
|
192
|
+
# Cycle detection
|
|
193
|
+
if len(order) < len(all_slugs):
|
|
194
|
+
cycled = [s for s in all_slugs if s not in order]
|
|
195
|
+
print(json.dumps({'error': 'cycle', 'involved': cycled}))
|
|
196
|
+
sys.exit(1)
|
|
197
|
+
|
|
198
|
+
# Output ordered issues
|
|
199
|
+
result = [slug_to_issue[s] for s in order]
|
|
200
|
+
print(json.dumps(result))
|
|
201
|
+
")
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**If cycle detected:** Error with cycle details and refuse to proceed:
|
|
205
|
+
```
|
|
206
|
+
Circular dependency detected in milestone issues.
|
|
207
|
+
Involved: ${cycled_slugs}
|
|
208
|
+
Resolve the circular dependency in project.json or GitHub labels before running.
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**Filter out completed issues:**
|
|
212
|
+
```bash
|
|
213
|
+
UNFINISHED=$(echo "$SORTED_ISSUES" | python3 -c "
|
|
214
|
+
import json,sys
|
|
215
|
+
issues = json.load(sys.stdin)
|
|
216
|
+
unfinished = [i for i in issues if i.get('pipeline_stage') not in ('done',)]
|
|
217
|
+
print(json.dumps(unfinished))
|
|
218
|
+
")
|
|
219
|
+
|
|
220
|
+
UNFINISHED_COUNT=$(echo "$UNFINISHED" | python3 -c "import json,sys; print(len(json.load(sys.stdin)))")
|
|
221
|
+
DONE_COUNT=$((TOTAL_ISSUES - UNFINISHED_COUNT))
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
If all done:
|
|
225
|
+
```
|
|
226
|
+
All ${TOTAL_ISSUES} issues complete. Milestone already finished.
|
|
227
|
+
Run /mgw:sync to finalize.
|
|
228
|
+
```
|
|
229
|
+
</step>
|
|
230
|
+
|
|
231
|
+
<step name="rate_limit_guard">
|
|
232
|
+
**Check API rate limit before starting loop (MLST-04):**
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
# From github.md Rate Limit pattern
|
|
236
|
+
RATE_JSON=$(gh api rate_limit --jq '.resources.core' 2>/dev/null)
|
|
237
|
+
|
|
238
|
+
if [ -n "$RATE_JSON" ]; then
|
|
239
|
+
REMAINING=$(echo "$RATE_JSON" | python3 -c "import json,sys; print(json.load(sys.stdin)['remaining'])")
|
|
240
|
+
RESET_EPOCH=$(echo "$RATE_JSON" | python3 -c "import json,sys; print(json.load(sys.stdin)['reset'])")
|
|
241
|
+
RESET_TIME=$(date -d "@${RESET_EPOCH}" '+%H:%M:%S' 2>/dev/null || echo "unknown")
|
|
242
|
+
|
|
243
|
+
# Conservative: 25 calls per issue
|
|
244
|
+
ESTIMATED_CALLS=$((UNFINISHED_COUNT * 25))
|
|
245
|
+
SAFE_ISSUES=$((REMAINING / 25))
|
|
246
|
+
|
|
247
|
+
if [ "$REMAINING" -lt "$ESTIMATED_CALLS" ]; then
|
|
248
|
+
echo "Rate limit: ${REMAINING} calls remaining, need ~${ESTIMATED_CALLS} for ${UNFINISHED_COUNT} issues."
|
|
249
|
+
echo "Can safely run ${SAFE_ISSUES} of ${UNFINISHED_COUNT} issues."
|
|
250
|
+
echo "Limit resets at ${RESET_TIME}."
|
|
251
|
+
# Cap loop at safe count
|
|
252
|
+
MAX_ISSUES=$SAFE_ISSUES
|
|
253
|
+
else
|
|
254
|
+
MAX_ISSUES=$UNFINISHED_COUNT
|
|
255
|
+
fi
|
|
256
|
+
else
|
|
257
|
+
echo "MGW: Rate limit check unavailable — proceeding without cap"
|
|
258
|
+
MAX_ISSUES=$UNFINISHED_COUNT
|
|
259
|
+
fi
|
|
260
|
+
```
|
|
261
|
+
</step>
|
|
262
|
+
|
|
263
|
+
<step name="post_start_hook">
|
|
264
|
+
**Post milestone-start announcement to GitHub Discussions (or first-issue comment fallback):**
|
|
265
|
+
|
|
266
|
+
Runs once before the execute loop. Skipped if --dry-run is set. Failure is non-blocking — a warning is logged and execution continues.
|
|
267
|
+
|
|
268
|
+
```bash
|
|
269
|
+
if [ "$DRY_RUN" = true ]; then
|
|
270
|
+
echo "MGW: Skipping milestone-start announcement (dry-run mode)"
|
|
271
|
+
else
|
|
272
|
+
# Gather board URL from project.json if present (non-blocking)
|
|
273
|
+
BOARD_URL=$(echo "$PROJECT_JSON" | python3 -c "
|
|
274
|
+
import json,sys
|
|
275
|
+
p = json.load(sys.stdin)
|
|
276
|
+
m = p['milestones'][${MILESTONE_NUM} - 1]
|
|
277
|
+
print(m.get('board_url', ''))
|
|
278
|
+
" 2>/dev/null || echo "")
|
|
279
|
+
|
|
280
|
+
# Build issues JSON array with assignee and gsd_route per issue
|
|
281
|
+
ISSUES_PAYLOAD=$(echo "$ISSUES_JSON" | python3 -c "
|
|
282
|
+
import json,sys
|
|
283
|
+
issues = json.load(sys.stdin)
|
|
284
|
+
result = []
|
|
285
|
+
for i in issues:
|
|
286
|
+
result.append({
|
|
287
|
+
'number': i.get('github_number', 0),
|
|
288
|
+
'title': i.get('title', '')[:60],
|
|
289
|
+
'assignee': i.get('assignee') or None,
|
|
290
|
+
'gsdRoute': i.get('gsd_route', 'plan-phase')
|
|
291
|
+
})
|
|
292
|
+
print(json.dumps(result))
|
|
293
|
+
" 2>/dev/null || echo "[]")
|
|
294
|
+
|
|
295
|
+
# Get first issue number for fallback comment (non-blocking)
|
|
296
|
+
FIRST_ISSUE_NUM=$(echo "$ISSUES_JSON" | python3 -c "
|
|
297
|
+
import json,sys
|
|
298
|
+
issues = json.load(sys.stdin)
|
|
299
|
+
print(issues[0]['github_number'] if issues else '')
|
|
300
|
+
" 2>/dev/null || echo "")
|
|
301
|
+
|
|
302
|
+
REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner 2>/dev/null || echo "")
|
|
303
|
+
|
|
304
|
+
REPO="$REPO" \
|
|
305
|
+
MILESTONE_NAME="$MILESTONE_NAME" \
|
|
306
|
+
BOARD_URL="$BOARD_URL" \
|
|
307
|
+
ISSUES_PAYLOAD="$ISSUES_PAYLOAD" \
|
|
308
|
+
FIRST_ISSUE_NUM="$FIRST_ISSUE_NUM" \
|
|
309
|
+
node -e "
|
|
310
|
+
const { postMilestoneStartAnnouncement } = require('./lib/index.cjs');
|
|
311
|
+
const result = postMilestoneStartAnnouncement({
|
|
312
|
+
repo: process.env.REPO,
|
|
313
|
+
milestoneName: process.env.MILESTONE_NAME,
|
|
314
|
+
boardUrl: process.env.BOARD_URL || undefined,
|
|
315
|
+
issues: JSON.parse(process.env.ISSUES_PAYLOAD || '[]'),
|
|
316
|
+
firstIssueNumber: process.env.FIRST_ISSUE_NUM ? parseInt(process.env.FIRST_ISSUE_NUM) : undefined
|
|
317
|
+
});
|
|
318
|
+
if (result.posted) {
|
|
319
|
+
const detail = result.url ? ': ' + result.url : '';
|
|
320
|
+
console.log('MGW: Milestone-start announcement posted via ' + result.method + detail);
|
|
321
|
+
} else {
|
|
322
|
+
console.log('MGW: Milestone-start announcement skipped (Discussions unavailable, no fallback)');
|
|
323
|
+
}
|
|
324
|
+
" 2>/dev/null || echo "MGW: Announcement step failed (non-blocking) — continuing"
|
|
325
|
+
fi
|
|
326
|
+
```
|
|
327
|
+
</step>
|
|
328
|
+
|
|
329
|
+
<step name="dry_run">
|
|
330
|
+
**If --dry-run flag: display execution plan and exit:**
|
|
331
|
+
|
|
332
|
+
```bash
|
|
333
|
+
if [ "$DRY_RUN" = true ]; then
|
|
334
|
+
# Build execution plan table
|
|
335
|
+
# Show: order, issue number, title, status, depends on, blocks
|
|
336
|
+
fi
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
Display:
|
|
340
|
+
```
|
|
341
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
342
|
+
MGW ► DRY RUN — Milestone ${MILESTONE_NUM}
|
|
343
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
344
|
+
|
|
345
|
+
| Order | Issue | Title | Status | Depends On | Blocks |
|
|
346
|
+
|-------|-------|-------|--------|------------|--------|
|
|
347
|
+
| 1 | #N | title | ○ Pending | — | #M, #K |
|
|
348
|
+
| 2 | #M | title | ○ Pending | #N | #K |
|
|
349
|
+
| 3 | #K | title | ○ Pending | #N, #M | — |
|
|
350
|
+
|
|
351
|
+
Issues: ${TOTAL_ISSUES} total, ${DONE_COUNT} done, ${UNFINISHED_COUNT} remaining
|
|
352
|
+
Rate limit: ${REMAINING} calls available (~${SAFE_ISSUES} issues safe)
|
|
353
|
+
Estimated API calls: ~${ESTIMATED_CALLS}
|
|
354
|
+
|
|
355
|
+
No issues executed. Remove --dry-run to start.
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Exit after display.
|
|
359
|
+
</step>
|
|
360
|
+
|
|
361
|
+
<step name="resume_detection">
|
|
362
|
+
**Check for in-progress issues and clean up partial state:**
|
|
363
|
+
|
|
364
|
+
```bash
|
|
365
|
+
IN_PROGRESS=$(echo "$UNFINISHED" | python3 -c "
|
|
366
|
+
import json,sys
|
|
367
|
+
issues = json.load(sys.stdin)
|
|
368
|
+
in_prog = [i for i in issues if i.get('pipeline_stage') not in ('new', 'done', 'failed')]
|
|
369
|
+
print(json.dumps(in_prog))
|
|
370
|
+
")
|
|
371
|
+
|
|
372
|
+
IN_PROGRESS_COUNT=$(echo "$IN_PROGRESS" | python3 -c "import json,sys; print(len(json.load(sys.stdin)))")
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
If in-progress issues exist:
|
|
376
|
+
```bash
|
|
377
|
+
if [ "$IN_PROGRESS_COUNT" -gt 0 ]; then
|
|
378
|
+
echo "Resuming milestone — ${IN_PROGRESS_COUNT} in-progress issue(s) detected"
|
|
379
|
+
|
|
380
|
+
# For each in-progress issue:
|
|
381
|
+
# 1. Check if worktree exists
|
|
382
|
+
for ISSUE_NUM in $(echo "$IN_PROGRESS" | python3 -c "
|
|
383
|
+
import json,sys
|
|
384
|
+
for i in json.load(sys.stdin):
|
|
385
|
+
print(i['github_number'])
|
|
386
|
+
"); do
|
|
387
|
+
# Clean up lingering worktree (restart from scratch per design decision)
|
|
388
|
+
WORKTREE_PATH=$(git worktree list --porcelain 2>/dev/null | grep -B1 "issue/${ISSUE_NUM}" | head -1 | sed 's/worktree //')
|
|
389
|
+
if [ -n "$WORKTREE_PATH" ] && [ -d "$WORKTREE_PATH" ]; then
|
|
390
|
+
git worktree remove "$WORKTREE_PATH" --force 2>/dev/null
|
|
391
|
+
echo " Cleaned up partial worktree for #${ISSUE_NUM}"
|
|
392
|
+
fi
|
|
393
|
+
|
|
394
|
+
# Reset pipeline_stage to 'new' (will be re-run from scratch)
|
|
395
|
+
node -e "
|
|
396
|
+
const { loadProjectState, resolveActiveMilestoneIndex, writeProjectState } = require('./lib/state.cjs');
|
|
397
|
+
const state = loadProjectState();
|
|
398
|
+
const idx = resolveActiveMilestoneIndex(state);
|
|
399
|
+
if (idx < 0) { console.error('No active milestone'); process.exit(1); }
|
|
400
|
+
const milestone = state.milestones[idx];
|
|
401
|
+
const issue = (milestone.issues || []).find(i => i.github_number === ${ISSUE_NUM});
|
|
402
|
+
if (issue) { issue.pipeline_stage = 'new'; }
|
|
403
|
+
writeProjectState(state);
|
|
404
|
+
"
|
|
405
|
+
done
|
|
406
|
+
fi
|
|
407
|
+
```
|
|
408
|
+
</step>
|
|
409
|
+
|
|
410
|
+
<step name="execute_loop">
|
|
411
|
+
**Sequential loop over sorted issues (MLST-01, MLST-05):**
|
|
412
|
+
|
|
413
|
+
Track state for progress table:
|
|
414
|
+
```bash
|
|
415
|
+
COMPLETED_ISSUES=()
|
|
416
|
+
FAILED_ISSUES=()
|
|
417
|
+
BLOCKED_ISSUES=()
|
|
418
|
+
SKIPPED_ISSUES=()
|
|
419
|
+
ISSUES_RUN=0
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
For each issue in sorted order:
|
|
423
|
+
```bash
|
|
424
|
+
for ISSUE_DATA in $(echo "$UNFINISHED" | python3 -c "
|
|
425
|
+
import json,sys
|
|
426
|
+
for i in json.load(sys.stdin):
|
|
427
|
+
# Output as compact JSON per line
|
|
428
|
+
print(json.dumps(i))
|
|
429
|
+
"); do
|
|
430
|
+
ISSUE_NUMBER=$(echo "$ISSUE_DATA" | python3 -c "import json,sys; print(json.load(sys.stdin)['github_number'])")
|
|
431
|
+
ISSUE_TITLE=$(echo "$ISSUE_DATA" | python3 -c "import json,sys; print(json.load(sys.stdin)['title'])")
|
|
432
|
+
GSD_ROUTE=$(echo "$ISSUE_DATA" | python3 -c "import json,sys; print(json.load(sys.stdin).get('gsd_route','plan-phase'))")
|
|
433
|
+
|
|
434
|
+
# 1. Check if blocked by a failed issue
|
|
435
|
+
IS_BLOCKED=false
|
|
436
|
+
for FAILED_NUM in "${FAILED_ISSUES[@]}"; do
|
|
437
|
+
DEPS=$(echo "$ISSUE_DATA" | python3 -c "import json,sys; print(','.join(json.load(sys.stdin).get('depends_on_slugs',[])))")
|
|
438
|
+
# Check if any dependency maps to a failed issue
|
|
439
|
+
# If blocked: IS_BLOCKED=true
|
|
440
|
+
done
|
|
441
|
+
|
|
442
|
+
if [ "$IS_BLOCKED" = true ]; then
|
|
443
|
+
BLOCKED_ISSUES+=("$ISSUE_NUMBER")
|
|
444
|
+
echo " ⊘ #${ISSUE_NUMBER} — Blocked (dependency failed)"
|
|
445
|
+
# Update project.json: pipeline_stage = "blocked" (treat as skipped)
|
|
446
|
+
continue
|
|
447
|
+
fi
|
|
448
|
+
|
|
449
|
+
# 2. Check rate limit still OK
|
|
450
|
+
if [ "$ISSUES_RUN" -ge "$MAX_ISSUES" ]; then
|
|
451
|
+
echo "Rate limit cap reached. Stopping after ${ISSUES_RUN} issues."
|
|
452
|
+
echo "${REMAINING} API calls remaining. Limit resets at ${RESET_TIME}."
|
|
453
|
+
break
|
|
454
|
+
fi
|
|
455
|
+
|
|
456
|
+
# 3. Quick GitHub check — is issue still open?
|
|
457
|
+
ISSUE_STATE=$(gh issue view ${ISSUE_NUMBER} --json state -q .state 2>/dev/null || echo "OPEN")
|
|
458
|
+
if [ "$ISSUE_STATE" != "OPEN" ]; then
|
|
459
|
+
echo " ⊘ #${ISSUE_NUMBER} — Skipped (issue ${ISSUE_STATE})"
|
|
460
|
+
SKIPPED_ISSUES+=("$ISSUE_NUMBER")
|
|
461
|
+
continue
|
|
462
|
+
fi
|
|
463
|
+
|
|
464
|
+
# 4. Display terminal output
|
|
465
|
+
echo "Running issue #${ISSUE_NUMBER}..."
|
|
466
|
+
|
|
467
|
+
# ── PRE-WORK: Post triage/work-started comment on issue ──
|
|
468
|
+
# The ORCHESTRATOR posts this, not the inner agent. This guarantees it happens.
|
|
469
|
+
PHASE_NUM=$(echo "$ISSUE_DATA" | python3 -c "import json,sys; print(json.load(sys.stdin).get('phase_number','?'))")
|
|
470
|
+
PHASE_NAME=$(echo "$ISSUE_DATA" | python3 -c "import json,sys; print(json.load(sys.stdin).get('phase_name',''))")
|
|
471
|
+
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
472
|
+
|
|
473
|
+
# Build milestone progress table for this comment
|
|
474
|
+
PROGRESS_TABLE=$(echo "$SORTED_ISSUES" | python3 -c "
|
|
475
|
+
import json, sys
|
|
476
|
+
issues = json.load(sys.stdin)
|
|
477
|
+
completed = set(${COMPLETED_ISSUES_JSON:-'[]'})
|
|
478
|
+
current = ${ISSUE_NUMBER}
|
|
479
|
+
lines = ['| # | Issue | Status | PR |', '|---|-------|--------|----|']
|
|
480
|
+
for i in issues:
|
|
481
|
+
num = i['github_number']
|
|
482
|
+
title = i['title'][:45]
|
|
483
|
+
if num in completed:
|
|
484
|
+
lines.append(f'| {num} | {title} | ✓ Done | — |')
|
|
485
|
+
elif num == current:
|
|
486
|
+
lines.append(f'| **{num}** | **{title}** | ◆ In Progress | — |')
|
|
487
|
+
else:
|
|
488
|
+
lines.append(f'| {num} | {title} | ○ Pending | — |')
|
|
489
|
+
print('\n'.join(lines))
|
|
490
|
+
")
|
|
491
|
+
|
|
492
|
+
WORK_STARTED_BODY=$(cat <<COMMENTEOF
|
|
493
|
+
> **MGW** · \`work-started\` · ${TIMESTAMP}
|
|
494
|
+
> Milestone: ${MILESTONE_NAME} | Phase ${PHASE_NUM}: ${PHASE_NAME}
|
|
495
|
+
|
|
496
|
+
### Work Started
|
|
497
|
+
|
|
498
|
+
| | |
|
|
499
|
+
|---|---|
|
|
500
|
+
| **Issue** | #${ISSUE_NUMBER} — ${ISSUE_TITLE} |
|
|
501
|
+
| **Route** | \`${GSD_ROUTE}\` |
|
|
502
|
+
| **Phase** | ${PHASE_NUM} of ${TOTAL_PHASES} — ${PHASE_NAME} |
|
|
503
|
+
| **Milestone** | ${MILESTONE_NAME} |
|
|
504
|
+
|
|
505
|
+
<details>
|
|
506
|
+
<summary>Milestone Progress (${#COMPLETED_ISSUES[@]}/${TOTAL_ISSUES} complete)</summary>
|
|
507
|
+
|
|
508
|
+
${PROGRESS_TABLE}
|
|
509
|
+
|
|
510
|
+
</details>
|
|
511
|
+
COMMENTEOF
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
gh issue comment ${ISSUE_NUMBER} --body "$WORK_STARTED_BODY" 2>/dev/null || true
|
|
515
|
+
gh issue edit ${ISSUE_NUMBER} --add-assignee @me 2>/dev/null || true
|
|
516
|
+
|
|
517
|
+
# ── MAIN WORK: Spawn /mgw:run via Task() ──
|
|
518
|
+
# The agent focuses on: worktree → GSD execution → PR creation
|
|
519
|
+
# Comment posting is handled by THIS orchestrator, not the agent.
|
|
520
|
+
Task(
|
|
521
|
+
prompt="
|
|
522
|
+
<files_to_read>
|
|
523
|
+
- ./CLAUDE.md (Project instructions — if exists, follow all guidelines)
|
|
524
|
+
- .agents/skills/ (Project skills — if dir exists, list skills, read SKILL.md for each, follow relevant rules)
|
|
525
|
+
</files_to_read>
|
|
526
|
+
|
|
527
|
+
Run the MGW pipeline for issue #${ISSUE_NUMBER}.
|
|
528
|
+
Read ~/.claude/commands/mgw/run.md for the workflow steps.
|
|
529
|
+
|
|
530
|
+
**Your responsibilities (the orchestrator handles status comments):**
|
|
531
|
+
1. validate_and_load — load issue state from .mgw/active/
|
|
532
|
+
2. create_worktree — create isolated git worktree for issue branch
|
|
533
|
+
3. execute_gsd_quick or execute_gsd_milestone (route: ${GSD_ROUTE})
|
|
534
|
+
4. create_pr — push branch and create PR with this EXACT body structure:
|
|
535
|
+
|
|
536
|
+
PR BODY MUST include these sections IN ORDER:
|
|
537
|
+
## Summary
|
|
538
|
+
- 2-4 bullets of what was built and why
|
|
539
|
+
|
|
540
|
+
Closes #${ISSUE_NUMBER}
|
|
541
|
+
|
|
542
|
+
## Milestone Context
|
|
543
|
+
- **Milestone:** ${MILESTONE_NAME}
|
|
544
|
+
- **Phase:** ${PHASE_NUM} — ${PHASE_NAME}
|
|
545
|
+
- **Issue:** ${ISSUES_RUN + 1} of ${TOTAL_ISSUES} in milestone
|
|
546
|
+
|
|
547
|
+
## Changes
|
|
548
|
+
- File-level changes grouped by module
|
|
549
|
+
|
|
550
|
+
## Test Plan
|
|
551
|
+
- Verification checklist
|
|
552
|
+
|
|
553
|
+
5. cleanup_and_complete — clean up worktree, update .mgw/ state
|
|
554
|
+
|
|
555
|
+
**Do NOT post issue comments** — the orchestrator handles all GitHub comments.
|
|
556
|
+
|
|
557
|
+
Issue title: ${ISSUE_TITLE}
|
|
558
|
+
GSD route: ${GSD_ROUTE}
|
|
559
|
+
",
|
|
560
|
+
subagent_type="general-purpose",
|
|
561
|
+
description="Run pipeline for #${ISSUE_NUMBER}"
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
# ── POST-WORK: Detect result and post completion comment ──
|
|
565
|
+
# Check if PR was created by looking for state file or PR
|
|
566
|
+
PR_NUMBER=$(gh pr list --head "issue/${ISSUE_NUMBER}-*" --json number -q '.[0].number' 2>/dev/null || echo "")
|
|
567
|
+
PR_URL=""
|
|
568
|
+
if [ -z "$PR_NUMBER" ]; then
|
|
569
|
+
# Try broader search
|
|
570
|
+
PR_NUMBER=$(gh pr list --state all --search "Closes #${ISSUE_NUMBER}" --json number -q '.[0].number' 2>/dev/null || echo "")
|
|
571
|
+
fi
|
|
572
|
+
if [ -n "$PR_NUMBER" ]; then
|
|
573
|
+
PR_URL=$(gh pr view "$PR_NUMBER" --json url -q .url 2>/dev/null || echo "")
|
|
574
|
+
fi
|
|
575
|
+
|
|
576
|
+
DONE_TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
577
|
+
|
|
578
|
+
if [ -n "$PR_NUMBER" ]; then
|
|
579
|
+
# Success — post PR-ready comment
|
|
580
|
+
COMPLETED_ISSUES+=("$ISSUE_NUMBER")
|
|
581
|
+
COMPLETED_ISSUES_JSON=$(printf '%s\n' "${COMPLETED_ISSUES[@]}" | python3 -c "import json,sys; print(json.dumps([int(x.strip()) for x in sys.stdin if x.strip()]))")
|
|
582
|
+
|
|
583
|
+
# Rebuild progress table with updated state
|
|
584
|
+
DONE_PROGRESS=$(echo "$SORTED_ISSUES" | python3 -c "
|
|
585
|
+
import json, sys
|
|
586
|
+
issues = json.load(sys.stdin)
|
|
587
|
+
completed = set(${COMPLETED_ISSUES_JSON})
|
|
588
|
+
lines = ['| # | Issue | Status | PR |', '|---|-------|--------|----|']
|
|
589
|
+
for i in issues:
|
|
590
|
+
num = i['github_number']
|
|
591
|
+
title = i['title'][:45]
|
|
592
|
+
if num in completed:
|
|
593
|
+
lines.append(f'| {num} | {title} | ✓ Done | — |')
|
|
594
|
+
else:
|
|
595
|
+
lines.append(f'| {num} | {title} | ○ Pending | — |')
|
|
596
|
+
print('\n'.join(lines))
|
|
597
|
+
")
|
|
598
|
+
|
|
599
|
+
PR_READY_BODY=$(cat <<COMMENTEOF
|
|
600
|
+
> **MGW** · \`pr-ready\` · ${DONE_TIMESTAMP}
|
|
601
|
+
> Milestone: ${MILESTONE_NAME} | Phase ${PHASE_NUM}: ${PHASE_NAME}
|
|
602
|
+
|
|
603
|
+
### PR Ready
|
|
604
|
+
|
|
605
|
+
**PR #${PR_NUMBER}** — ${PR_URL}
|
|
606
|
+
|
|
607
|
+
Testing procedures posted on the PR.
|
|
608
|
+
This issue will auto-close when the PR is merged.
|
|
609
|
+
|
|
610
|
+
<details>
|
|
611
|
+
<summary>Milestone Progress (${#COMPLETED_ISSUES[@]}/${TOTAL_ISSUES} complete)</summary>
|
|
612
|
+
|
|
613
|
+
${DONE_PROGRESS}
|
|
614
|
+
|
|
615
|
+
</details>
|
|
616
|
+
COMMENTEOF
|
|
617
|
+
)
|
|
618
|
+
|
|
619
|
+
gh issue comment ${ISSUE_NUMBER} --body "$PR_READY_BODY" 2>/dev/null || true
|
|
620
|
+
echo " ✓ #${ISSUE_NUMBER} — PR #${PR_NUMBER} created"
|
|
621
|
+
|
|
622
|
+
else
|
|
623
|
+
# Failure — post failure comment
|
|
624
|
+
FAILED_ISSUES+=("$ISSUE_NUMBER")
|
|
625
|
+
|
|
626
|
+
FAIL_BODY=$(cat <<COMMENTEOF
|
|
627
|
+
> **MGW** · \`pipeline-failed\` · ${DONE_TIMESTAMP}
|
|
628
|
+
> Milestone: ${MILESTONE_NAME} | Phase ${PHASE_NUM}: ${PHASE_NAME}
|
|
629
|
+
|
|
630
|
+
### Pipeline Failed
|
|
631
|
+
|
|
632
|
+
Issue #${ISSUE_NUMBER} did not produce a PR.
|
|
633
|
+
Check the execution log for details.
|
|
634
|
+
|
|
635
|
+
Dependents of this issue will be skipped.
|
|
636
|
+
COMMENTEOF
|
|
637
|
+
)
|
|
638
|
+
|
|
639
|
+
gh issue comment ${ISSUE_NUMBER} --body "$FAIL_BODY" 2>/dev/null || true
|
|
640
|
+
gh issue edit ${ISSUE_NUMBER} --add-label "pipeline-failed" 2>/dev/null || true
|
|
641
|
+
gh label create "pipeline-failed" --description "Pipeline execution failed" --color "d73a4a" --force 2>/dev/null || true
|
|
642
|
+
echo " ✗ #${ISSUE_NUMBER} — Failed (no PR created)"
|
|
643
|
+
fi
|
|
644
|
+
|
|
645
|
+
# Update project.json checkpoint (MLST-05)
|
|
646
|
+
STAGE=$([ -n "$PR_NUMBER" ] && echo "done" || echo "failed")
|
|
647
|
+
node -e "
|
|
648
|
+
const { loadProjectState, resolveActiveMilestoneIndex, writeProjectState } = require('./lib/state.cjs');
|
|
649
|
+
const state = loadProjectState();
|
|
650
|
+
const idx = resolveActiveMilestoneIndex(state);
|
|
651
|
+
if (idx < 0) { console.error('No active milestone'); process.exit(1); }
|
|
652
|
+
const milestone = state.milestones[idx];
|
|
653
|
+
const issue = (milestone.issues || []).find(i => i.github_number === ${ISSUE_NUMBER});
|
|
654
|
+
if (issue) { issue.pipeline_stage = '${STAGE}'; }
|
|
655
|
+
writeProjectState(state);
|
|
656
|
+
"
|
|
657
|
+
|
|
658
|
+
ISSUES_RUN=$((ISSUES_RUN + 1))
|
|
659
|
+
|
|
660
|
+
# If --interactive: pause between issues
|
|
661
|
+
if [ "$INTERACTIVE" = true ]; then
|
|
662
|
+
AskUserQuestion(
|
|
663
|
+
header: "Issue Complete",
|
|
664
|
+
question: "#${ISSUE_NUMBER} done. Continue to next issue?",
|
|
665
|
+
options: [
|
|
666
|
+
{ label: "Continue", description: "Proceed to next unblocked issue" },
|
|
667
|
+
{ label: "Skip next", description: "Skip next issue and continue" },
|
|
668
|
+
{ label: "Abort", description: "Stop milestone execution here" }
|
|
669
|
+
]
|
|
670
|
+
)
|
|
671
|
+
# Handle response: Continue → proceed, Skip → skip next, Abort → break
|
|
672
|
+
fi
|
|
673
|
+
done
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
**Progress table format for GitHub comments:**
|
|
677
|
+
|
|
678
|
+
Every comment posted during milestone orchestration includes:
|
|
679
|
+
```markdown
|
|
680
|
+
**Issue #N — {Status}** {symbol}
|
|
681
|
+
|
|
682
|
+
{Status-specific detail (PR link, failure reason, etc.)}
|
|
683
|
+
|
|
684
|
+
<details>
|
|
685
|
+
<summary>Milestone Progress ({done}/{total} complete)</summary>
|
|
686
|
+
|
|
687
|
+
| # | Issue | Status | PR | Stage |
|
|
688
|
+
|---|-------|--------|----|-------|
|
|
689
|
+
| N | title | ✓ Done | #PR | done |
|
|
690
|
+
| M | title | ✗ Failed | — | failed |
|
|
691
|
+
| K | title | ○ Pending | — | new |
|
|
692
|
+
| J | title | ◆ Running | — | executing |
|
|
693
|
+
| L | title | ⊘ Blocked | — | blocked-by:#N |
|
|
694
|
+
|
|
695
|
+
</details>
|
|
696
|
+
```
|
|
697
|
+
</step>
|
|
698
|
+
|
|
699
|
+
<step name="post_loop">
|
|
700
|
+
**After loop completes — finalize milestone:**
|
|
701
|
+
|
|
702
|
+
Build final results table:
|
|
703
|
+
```bash
|
|
704
|
+
TOTAL_DONE=$((DONE_COUNT + ${#COMPLETED_ISSUES[@]}))
|
|
705
|
+
TOTAL_FAILED=${#FAILED_ISSUES[@]}
|
|
706
|
+
TOTAL_BLOCKED=${#BLOCKED_ISSUES[@]}
|
|
707
|
+
TOTAL_SKIPPED=${#SKIPPED_ISSUES[@]}
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
**If ALL issues completed (pipeline_stage == 'done' for all):**
|
|
711
|
+
|
|
712
|
+
1. Close GitHub milestone:
|
|
713
|
+
```bash
|
|
714
|
+
REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner)
|
|
715
|
+
gh api "repos/${REPO}/milestones/${MILESTONE_GH_NUMBER}" --method PATCH \
|
|
716
|
+
-f state="closed" 2>/dev/null
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
2. Create draft release:
|
|
720
|
+
```bash
|
|
721
|
+
RELEASE_TAG="milestone-${MILESTONE_NUM}-complete"
|
|
722
|
+
# Build release body with: milestone name, issues completed, PR links, stats
|
|
723
|
+
RELEASE_BODY="## Milestone ${MILESTONE_NUM}: ${MILESTONE_NAME}
|
|
724
|
+
|
|
725
|
+
### Issues Completed
|
|
726
|
+
${issues_completed_list}
|
|
727
|
+
|
|
728
|
+
### Pull Requests
|
|
729
|
+
${pr_links_list}
|
|
730
|
+
|
|
731
|
+
### Stats
|
|
732
|
+
- Issues: ${TOTAL_ISSUES}
|
|
733
|
+
- PRs created: ${pr_count}
|
|
734
|
+
|
|
735
|
+
---
|
|
736
|
+
*Auto-generated by MGW milestone orchestration*"
|
|
737
|
+
|
|
738
|
+
gh release create "$RELEASE_TAG" --draft \
|
|
739
|
+
--title "Milestone ${MILESTONE_NUM}: ${MILESTONE_NAME}" \
|
|
740
|
+
--notes "$RELEASE_BODY" 2>/dev/null
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
3. Finalize GSD milestone state (archive phases, clean up):
|
|
744
|
+
```bash
|
|
745
|
+
# Only run if .planning/phases exists (GSD was used for this milestone)
|
|
746
|
+
if [ -d ".planning/phases" ]; then
|
|
747
|
+
EXECUTOR_MODEL=$(node ~/.claude/get-shit-done/bin/gsd-tools.cjs resolve-model gsd-executor --raw)
|
|
748
|
+
Task(
|
|
749
|
+
prompt="
|
|
750
|
+
<files_to_read>
|
|
751
|
+
- ./CLAUDE.md (Project instructions -- if exists, follow all guidelines)
|
|
752
|
+
- .planning/ROADMAP.md (Current roadmap to archive)
|
|
753
|
+
- .planning/REQUIREMENTS.md (Requirements to archive)
|
|
754
|
+
</files_to_read>
|
|
755
|
+
|
|
756
|
+
Complete the GSD milestone. Follow the complete-milestone workflow:
|
|
757
|
+
@~/.claude/get-shit-done/workflows/complete-milestone.md
|
|
758
|
+
|
|
759
|
+
This archives the milestone's ROADMAP and REQUIREMENTS to .planning/milestones/,
|
|
760
|
+
cleans up ROADMAP.md for the next milestone, and tags the release in git.
|
|
761
|
+
|
|
762
|
+
Milestone: ${MILESTONE_NAME}
|
|
763
|
+
",
|
|
764
|
+
subagent_type="gsd-executor",
|
|
765
|
+
model="${EXECUTOR_MODEL}",
|
|
766
|
+
description="Complete GSD milestone: ${MILESTONE_NAME}"
|
|
767
|
+
)
|
|
768
|
+
fi
|
|
769
|
+
```
|
|
770
|
+
|
|
771
|
+
4. Advance active milestone pointer in project.json:
|
|
772
|
+
```bash
|
|
773
|
+
node -e "
|
|
774
|
+
const { loadProjectState, resolveActiveMilestoneIndex, writeProjectState } = require('./lib/state.cjs');
|
|
775
|
+
const state = loadProjectState();
|
|
776
|
+
const currentIdx = resolveActiveMilestoneIndex(state);
|
|
777
|
+
const nextMilestone = (state.milestones || [])[currentIdx + 1];
|
|
778
|
+
if (nextMilestone) {
|
|
779
|
+
// New schema: point active_gsd_milestone at the next milestone's gsd_milestone_id
|
|
780
|
+
state.active_gsd_milestone = nextMilestone.gsd_milestone_id || null;
|
|
781
|
+
// Backward compat: if next milestone has no gsd_milestone_id, fall back to legacy integer
|
|
782
|
+
if (!state.active_gsd_milestone) {
|
|
783
|
+
state.current_milestone = currentIdx + 2; // next 1-indexed
|
|
784
|
+
}
|
|
785
|
+
} else {
|
|
786
|
+
// All milestones complete — clear the active pointer
|
|
787
|
+
state.active_gsd_milestone = null;
|
|
788
|
+
state.current_milestone = currentIdx + 2; // past end, signals completion
|
|
789
|
+
}
|
|
790
|
+
writeProjectState(state);
|
|
791
|
+
"
|
|
792
|
+
```
|
|
793
|
+
|
|
794
|
+
5. Milestone mapping verification:
|
|
795
|
+
|
|
796
|
+
After advancing to the next milestone, check its GSD linkage:
|
|
797
|
+
|
|
798
|
+
```bash
|
|
799
|
+
NEXT_MILESTONE_CHECK=$(node -e "
|
|
800
|
+
const { loadProjectState, resolveActiveMilestoneIndex } = require('./lib/state.cjs');
|
|
801
|
+
const state = loadProjectState();
|
|
802
|
+
const activeIdx = resolveActiveMilestoneIndex(state);
|
|
803
|
+
|
|
804
|
+
if (activeIdx < 0 || activeIdx >= state.milestones.length) {
|
|
805
|
+
console.log('none');
|
|
806
|
+
process.exit(0);
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
const nextMilestone = state.milestones[activeIdx];
|
|
810
|
+
if (!nextMilestone) {
|
|
811
|
+
console.log('none');
|
|
812
|
+
process.exit(0);
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
const gsdId = nextMilestone.gsd_milestone_id;
|
|
816
|
+
const name = nextMilestone.name;
|
|
817
|
+
|
|
818
|
+
if (!gsdId) {
|
|
819
|
+
console.log('unlinked:' + name);
|
|
820
|
+
} else {
|
|
821
|
+
console.log('linked:' + name + ':' + gsdId);
|
|
822
|
+
}
|
|
823
|
+
")
|
|
824
|
+
|
|
825
|
+
case "$NEXT_MILESTONE_CHECK" in
|
|
826
|
+
none)
|
|
827
|
+
echo "All milestones complete — project is done!"
|
|
828
|
+
;;
|
|
829
|
+
unlinked:*)
|
|
830
|
+
NEXT_NAME=$(echo "$NEXT_MILESTONE_CHECK" | cut -d':' -f2-)
|
|
831
|
+
echo ""
|
|
832
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
833
|
+
echo " Next milestone '${NEXT_NAME}' has no GSD milestone linked."
|
|
834
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
835
|
+
echo ""
|
|
836
|
+
echo "Before running /mgw:milestone for the next milestone:"
|
|
837
|
+
echo " 1) Run /gsd:new-milestone to create GSD state for '${NEXT_NAME}'"
|
|
838
|
+
echo " 2) Run /mgw:project extend to link the new GSD milestone"
|
|
839
|
+
echo ""
|
|
840
|
+
;;
|
|
841
|
+
linked:*)
|
|
842
|
+
NEXT_NAME=$(echo "$NEXT_MILESTONE_CHECK" | cut -d':' -f2)
|
|
843
|
+
GSD_ID=$(echo "$NEXT_MILESTONE_CHECK" | cut -d':' -f3)
|
|
844
|
+
# Verify ROADMAP.md matches expected GSD milestone
|
|
845
|
+
ROADMAP_CHECK=$(python3 -c "
|
|
846
|
+
import os, sys
|
|
847
|
+
if not os.path.exists('.planning/ROADMAP.md'):
|
|
848
|
+
print('no_roadmap')
|
|
849
|
+
sys.exit()
|
|
850
|
+
with open('.planning/ROADMAP.md') as f:
|
|
851
|
+
content = f.read()
|
|
852
|
+
if '${GSD_ID}' in content:
|
|
853
|
+
print('match')
|
|
854
|
+
else:
|
|
855
|
+
print('mismatch')
|
|
856
|
+
" 2>/dev/null || echo "no_roadmap")
|
|
857
|
+
|
|
858
|
+
case "$ROADMAP_CHECK" in
|
|
859
|
+
match)
|
|
860
|
+
echo "Next milestone '${NEXT_NAME}' (GSD: ${GSD_ID}) — ROADMAP.md is ready."
|
|
861
|
+
;;
|
|
862
|
+
mismatch)
|
|
863
|
+
echo "Next milestone '${NEXT_NAME}' links to GSD milestone '${GSD_ID}'"
|
|
864
|
+
echo " but .planning/ROADMAP.md does not contain that milestone ID."
|
|
865
|
+
echo " Run /gsd:new-milestone to update ROADMAP.md before proceeding."
|
|
866
|
+
;;
|
|
867
|
+
no_roadmap)
|
|
868
|
+
echo "NOTE: Next milestone '${NEXT_NAME}' (GSD: ${GSD_ID}) linked."
|
|
869
|
+
echo " No .planning/ROADMAP.md found — run /gsd:new-milestone when ready."
|
|
870
|
+
;;
|
|
871
|
+
esac
|
|
872
|
+
;;
|
|
873
|
+
esac
|
|
874
|
+
```
|
|
875
|
+
|
|
876
|
+
6. Display completion banner:
|
|
877
|
+
```
|
|
878
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
879
|
+
MGW ► MILESTONE ${MILESTONE_NUM} COMPLETE ✓
|
|
880
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
881
|
+
|
|
882
|
+
${MILESTONE_NAME}
|
|
883
|
+
|
|
884
|
+
| # | Issue | Status | PR |
|
|
885
|
+
|---|-------|--------|----|
|
|
886
|
+
${results_table}
|
|
887
|
+
|
|
888
|
+
Issues: ${TOTAL_ISSUES} | PRs: ${pr_count}
|
|
889
|
+
Milestone closed on GitHub.
|
|
890
|
+
Draft release created: ${RELEASE_TAG}
|
|
891
|
+
|
|
892
|
+
───────────────────────────────────────────────────────────────
|
|
893
|
+
|
|
894
|
+
## ▶ Next Up
|
|
895
|
+
|
|
896
|
+
**Milestone ${NEXT_MILESTONE_NUM}** — next milestone
|
|
897
|
+
|
|
898
|
+
/mgw:milestone ${NEXT_MILESTONE_NUM}
|
|
899
|
+
|
|
900
|
+
<sub>/clear first → fresh context window</sub>
|
|
901
|
+
|
|
902
|
+
───────────────────────────────────────────────────────────────
|
|
903
|
+
```
|
|
904
|
+
|
|
905
|
+
7. Check if next milestone exists and offer auto-advance (only if no failures in current).
|
|
906
|
+
|
|
907
|
+
**If some issues failed:**
|
|
908
|
+
|
|
909
|
+
```
|
|
910
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
911
|
+
MGW ► MILESTONE ${MILESTONE_NUM} INCOMPLETE
|
|
912
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
913
|
+
|
|
914
|
+
${MILESTONE_NAME}
|
|
915
|
+
|
|
916
|
+
| # | Issue | Status | PR |
|
|
917
|
+
|---|-------|--------|----|
|
|
918
|
+
${results_table}
|
|
919
|
+
|
|
920
|
+
Completed: ${TOTAL_DONE}/${TOTAL_ISSUES}
|
|
921
|
+
Failed: ${TOTAL_FAILED}
|
|
922
|
+
Blocked: ${TOTAL_BLOCKED}
|
|
923
|
+
|
|
924
|
+
Milestone NOT closed. Resolve failures and re-run:
|
|
925
|
+
/mgw:milestone ${MILESTONE_NUM}
|
|
926
|
+
```
|
|
927
|
+
|
|
928
|
+
8. Post final results table as GitHub comment on the first issue in the milestone:
|
|
929
|
+
```bash
|
|
930
|
+
gh issue comment ${FIRST_ISSUE_NUMBER} --body "$FINAL_RESULTS_COMMENT"
|
|
931
|
+
```
|
|
932
|
+
</step>
|
|
933
|
+
|
|
934
|
+
</process>
|
|
935
|
+
|
|
936
|
+
<success_criteria>
|
|
937
|
+
- [ ] project.json loaded and milestone validated (MLST-01)
|
|
938
|
+
- [ ] Batch staleness check run before execution (MLST-03)
|
|
939
|
+
- [ ] Rate limit checked and execution capped if needed (MLST-04)
|
|
940
|
+
- [ ] Dependency resolution via topological sort (MLST-01)
|
|
941
|
+
- [ ] Cycle detection with clear error reporting
|
|
942
|
+
- [ ] Resume detection with partial state cleanup
|
|
943
|
+
- [ ] Sequential execution via /mgw:run Task() delegation (MLST-01)
|
|
944
|
+
- [ ] Per-issue checkpoint to project.json after completion (MLST-05)
|
|
945
|
+
- [ ] Failure handling: skip failed, label, comment, block dependents
|
|
946
|
+
- [ ] Progress table in every GitHub comment
|
|
947
|
+
- [ ] Milestone close + draft release on full completion
|
|
948
|
+
- [ ] current_milestone pointer advanced on completion
|
|
949
|
+
- [ ] --interactive flag pauses between issues
|
|
950
|
+
- [ ] --dry-run flag shows plan without executing
|
|
951
|
+
- [ ] Terminal output is minimal during run
|
|
952
|
+
</success_criteria>
|