@snipcodeit/mgw 0.1.1 → 0.1.3
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/README.md +20 -9
- package/commands/board.md +75 -0
- package/commands/issue.md +9 -10
- package/commands/milestone.md +180 -15
- package/commands/project.md +55 -1651
- package/commands/run.md +319 -20
- package/commands/sync.md +409 -1
- package/commands/workflows/github.md +19 -4
- package/dist/bin/mgw.cjs +2 -2
- package/dist/{claude-Vp9qvImH.cjs → claude-Dk1oVsaG.cjs} +156 -0
- package/dist/lib/index.cjs +237 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# MGW — My GSD Workflow
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/mgw)
|
|
3
|
+
[](https://www.npmjs.com/package/@snipcodeit/mgw)
|
|
4
|
+
[](https://www.npmjs.com/package/@snipcodeit/mgw)
|
|
4
5
|
[](https://opensource.org/licenses/MIT)
|
|
5
|
-
[](https://nodejs.org)
|
|
6
|
+
[](https://nodejs.org)
|
|
6
7
|
[](https://github.com/snipcodeit/mgw)
|
|
7
8
|
|
|
8
9
|
> Issue in. PR out. No excuses.
|
|
@@ -95,6 +96,9 @@ If you're already using Claude Code and GSD for development, MGW is the missing
|
|
|
95
96
|
| `/mgw:review <n>` | Review and classify new comments on an issue since last triage |
|
|
96
97
|
| `/mgw:link <ref> <ref>` | Cross-reference issues, PRs, and branches (including milestone ↔ GSD milestone maps-to links) |
|
|
97
98
|
| `/mgw:status [n]` | Project dashboard — milestone progress, issue stages, open PRs |
|
|
99
|
+
| `/mgw:roadmap [--set-dates] [--post-discussion]` | Render project milestones as a roadmap table; optionally set GitHub due dates or post as a Discussion |
|
|
100
|
+
| `/mgw:assign <n> [user]` | Claim or reassign an issue; resolves GitHub noreply co-author tag |
|
|
101
|
+
| `/mgw:board [--sync]` | Create, configure, and sync a GitHub Projects v2 board |
|
|
98
102
|
| `/mgw:sync` | Reconcile local state with GitHub; verifies GSD milestone consistency |
|
|
99
103
|
| `/mgw:help` | Command reference |
|
|
100
104
|
|
|
@@ -184,9 +188,12 @@ MGW tracks pipeline state in a local `.mgw/` directory (gitignored, per-develope
|
|
|
184
188
|
42-fix-auth.json Issue state: triage results, pipeline stage, artifacts
|
|
185
189
|
completed/ Archived after PR merge
|
|
186
190
|
cross-refs.json Bidirectional issue/PR/branch/milestone links
|
|
187
|
-
vision-
|
|
188
|
-
vision-
|
|
191
|
+
vision-research.json (Fresh projects) Domain research from vision-researcher agent
|
|
192
|
+
vision-draft.md (Fresh projects) Rolling decisions from questioning loop
|
|
193
|
+
vision-brief.json (Fresh projects) Structured Vision Brief (MoSCoW, personas, scope)
|
|
194
|
+
vision-handoff.md (Fresh projects) Condensed brief handed off to gsd:new-project
|
|
189
195
|
alignment-report.json (GSD-Only projects) GSD state mapped for milestone backfill
|
|
196
|
+
drift-report.json (Diverged projects) Reconciliation table from drift-analyzer agent
|
|
190
197
|
```
|
|
191
198
|
|
|
192
199
|
Pipeline stages flow: `new` → `triaged` → `planning` → `executing` → `verifying` → `pr-created` → `done` (or `failed`/`blocked`). Bugs routed to `gsd:diagnose-issues` pass through `diagnosing` before `planning`.
|
|
@@ -248,9 +255,9 @@ mgw --version
|
|
|
248
255
|
|
|
249
256
|
# Slash commands (installed automatically by postinstall)
|
|
250
257
|
ls ~/.claude/commands/mgw/
|
|
251
|
-
# ask.md board.md help.md init.md issue.md issues.md link.md
|
|
252
|
-
# next.md pr.md project.md review.md
|
|
253
|
-
# update.md workflows/
|
|
258
|
+
# ask.md assign.md board.md help.md init.md issue.md issues.md link.md
|
|
259
|
+
# milestone.md next.md pr.md project.md review.md roadmap.md run.md
|
|
260
|
+
# status.md sync.md update.md workflows/
|
|
254
261
|
```
|
|
255
262
|
|
|
256
263
|
Then in Claude Code:
|
|
@@ -266,7 +273,7 @@ Not all commands work via `npx`. The CLI has two tiers:
|
|
|
266
273
|
| Tier | Commands | Requirements |
|
|
267
274
|
|------|----------|--------------|
|
|
268
275
|
| **CLI-only** (works with npx) | `issues`, `sync`, `link`, `help`, `--help`, `--version` | Node.js >= 18, `gh` CLI |
|
|
269
|
-
| **AI-powered** (requires full install) | `run`, `init`, `project`, `milestone`, `next`, `issue`, `update`, `pr`, `ask`, `review` | Node.js >= 18, `gh` CLI, Claude Code CLI, GSD |
|
|
276
|
+
| **AI-powered** (requires full install) | `run`, `init`, `project`, `milestone`, `next`, `issue`, `update`, `pr`, `ask`, `review`, `assign`, `board`, `roadmap`, `status` | Node.js >= 18, `gh` CLI, Claude Code CLI, GSD |
|
|
270
277
|
|
|
271
278
|
AI-powered commands call `claude -p` under the hood and require the [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code/overview) to be installed and authenticated. The slash command `.md` files must also be deployed to `~/.claude/commands/mgw/` for the full pipeline to work — this happens automatically via `postinstall`. Use `npx @snipcodeit/mgw` to explore the CLI and verify your GitHub setup before committing to a full install.
|
|
272
279
|
|
|
@@ -336,12 +343,15 @@ lib/
|
|
|
336
343
|
claude.cjs Claude Code invocation helpers
|
|
337
344
|
github.cjs GitHub CLI wrappers (issues, PRs, milestones, Projects v2)
|
|
338
345
|
gsd.cjs GSD integration
|
|
346
|
+
gsd-adapter.cjs GSD route adapter (maps triage results to GSD spawn args)
|
|
339
347
|
state.cjs .mgw/ state management (migrateProjectState, resolveActiveMilestoneIndex)
|
|
340
348
|
output.cjs Logging and formatting
|
|
349
|
+
retry.cjs Retry logic for GitHub API calls
|
|
341
350
|
templates.cjs Template system
|
|
342
351
|
template-loader.cjs Output validation (JSON Schema) + parseRoadmap()
|
|
343
352
|
commands/ Slash command source files (deployed to ~/.claude/commands/mgw/ at install time)
|
|
344
353
|
ask.md Contextual question routing during milestone execution
|
|
354
|
+
assign.md Claim/reassign issues; resolves GitHub noreply co-author tag
|
|
345
355
|
board.md GitHub Projects v2 board management
|
|
346
356
|
help.md Command reference display
|
|
347
357
|
init.md One-time repo bootstrap (state, templates, labels)
|
|
@@ -350,6 +360,7 @@ commands/ Slash command source files (deployed to ~/.claude/com
|
|
|
350
360
|
issue.md Deep triage with agent analysis
|
|
351
361
|
next.md Next unblocked issue picker (surfaces failed issues as advisory)
|
|
352
362
|
review.md Comment review and classification since last triage
|
|
363
|
+
roadmap.md Milestone roadmap table; optional GitHub due-date setter and Discussion post
|
|
353
364
|
run.md Autonomous pipeline orchestrator (cross-milestone enforcement)
|
|
354
365
|
milestone.md Milestone execution with dependency ordering and failed-issue recovery
|
|
355
366
|
update.md Structured GitHub comment templates
|
|
@@ -491,7 +502,7 @@ If rate-limited, wait for the reset window (usually under an hour) or reduce the
|
|
|
491
502
|
|
|
492
503
|
```bash
|
|
493
504
|
# Remove CLI
|
|
494
|
-
npm
|
|
505
|
+
npm uninstall -g @snipcodeit/mgw
|
|
495
506
|
|
|
496
507
|
# Remove slash commands
|
|
497
508
|
rm -rf ~/.claude/commands/mgw/
|
package/commands/board.md
CHANGED
|
@@ -176,6 +176,81 @@ for name, data in fields.items():
|
|
|
176
176
|
fi
|
|
177
177
|
```
|
|
178
178
|
|
|
179
|
+
**Board discovery: check GitHub for an existing board before creating a new one:**
|
|
180
|
+
|
|
181
|
+
One lightweight GraphQL list call. Searches the first 20 user/org projects for a title
|
|
182
|
+
containing the project name. If found, registers it in project.json and exits — no fields
|
|
183
|
+
created, no board duplicated. Only runs when `BOARD_CONFIGURED = false`.
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
echo "Checking GitHub for existing boards..."
|
|
187
|
+
DISCOVERED=$(node -e "
|
|
188
|
+
const { findExistingBoard, getProjectFields } = require('./lib/github.cjs');
|
|
189
|
+
const board = findExistingBoard('${OWNER}', '${PROJECT_NAME}');
|
|
190
|
+
if (!board) { process.stdout.write(''); process.exit(0); }
|
|
191
|
+
const fields = getProjectFields('${OWNER}', board.number) || {};
|
|
192
|
+
console.log(JSON.stringify({ ...board, fields }));
|
|
193
|
+
" 2>/dev/null || echo "")
|
|
194
|
+
|
|
195
|
+
if [ -n "$DISCOVERED" ]; then
|
|
196
|
+
DISC_NUMBER=$(echo "$DISCOVERED" | python3 -c "import json,sys; print(json.load(sys.stdin)['number'])")
|
|
197
|
+
DISC_URL=$(echo "$DISCOVERED" | python3 -c "import json,sys; print(json.load(sys.stdin)['url'])")
|
|
198
|
+
DISC_NODE_ID=$(echo "$DISCOVERED" | python3 -c "import json,sys; print(json.load(sys.stdin)['nodeId'])")
|
|
199
|
+
DISC_TITLE=$(echo "$DISCOVERED" | python3 -c "import json,sys; print(json.load(sys.stdin)['title'])")
|
|
200
|
+
DISC_FIELDS=$(echo "$DISCOVERED" | python3 -c "import json,sys; print(json.dumps(json.load(sys.stdin).get('fields', {})))")
|
|
201
|
+
|
|
202
|
+
echo " Found existing board: #${DISC_NUMBER} \"${DISC_TITLE}\" — ${DISC_URL}"
|
|
203
|
+
|
|
204
|
+
python3 << PYEOF
|
|
205
|
+
import json
|
|
206
|
+
|
|
207
|
+
with open('${MGW_DIR}/project.json') as f:
|
|
208
|
+
project = json.load(f)
|
|
209
|
+
|
|
210
|
+
fields = json.loads('''${DISC_FIELDS}''') if '${DISC_FIELDS}' not in ('', '{}') else {}
|
|
211
|
+
|
|
212
|
+
project['project']['project_board'] = {
|
|
213
|
+
'number': int('${DISC_NUMBER}'),
|
|
214
|
+
'url': '${DISC_URL}',
|
|
215
|
+
'node_id': '${DISC_NODE_ID}',
|
|
216
|
+
'fields': fields
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
with open('${MGW_DIR}/project.json', 'w') as f:
|
|
220
|
+
json.dump(project, f, indent=2)
|
|
221
|
+
|
|
222
|
+
print('project.json updated')
|
|
223
|
+
PYEOF
|
|
224
|
+
|
|
225
|
+
echo ""
|
|
226
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
227
|
+
echo " MGW ► EXISTING BOARD REGISTERED"
|
|
228
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
229
|
+
echo ""
|
|
230
|
+
echo "Board: #${DISC_NUMBER} — ${DISC_URL}"
|
|
231
|
+
echo "Node ID: ${DISC_NODE_ID}"
|
|
232
|
+
echo ""
|
|
233
|
+
if [ "$DISC_FIELDS" != "{}" ] && [ -n "$DISC_FIELDS" ]; then
|
|
234
|
+
echo "Fields registered:"
|
|
235
|
+
echo "$DISC_FIELDS" | python3 -c "
|
|
236
|
+
import json,sys
|
|
237
|
+
fields = json.load(sys.stdin)
|
|
238
|
+
for name, data in fields.items():
|
|
239
|
+
ftype = data.get('type', '?')
|
|
240
|
+
print(f' {name}: {data.get(\"field_id\",\"?\")} ({ftype})')
|
|
241
|
+
" 2>/dev/null
|
|
242
|
+
else
|
|
243
|
+
echo " (no custom fields found — run /mgw:board configure to add them)"
|
|
244
|
+
fi
|
|
245
|
+
echo ""
|
|
246
|
+
echo "To see board items: /mgw:board show"
|
|
247
|
+
exit 0
|
|
248
|
+
fi
|
|
249
|
+
|
|
250
|
+
echo " No existing board found — creating new board..."
|
|
251
|
+
echo ""
|
|
252
|
+
```
|
|
253
|
+
|
|
179
254
|
**Get owner and repo node IDs (required for GraphQL mutations):**
|
|
180
255
|
|
|
181
256
|
```bash
|
package/commands/issue.md
CHANGED
|
@@ -287,16 +287,15 @@ MISSING_FIELDS_LIST = gate_result.missing_fields formatted as "- ${field}" list
|
|
|
287
287
|
```
|
|
288
288
|
|
|
289
289
|
Use the "Gate Blocked Comment" template from @~/.claude/commands/mgw/workflows/github.md.
|
|
290
|
-
Post comment and apply label:
|
|
290
|
+
Post comment and apply label using the highest-severity blocker (security > detail > validity):
|
|
291
291
|
```bash
|
|
292
|
-
#
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
# For security failures:
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
#
|
|
299
|
-
# If multiple blockers, use the highest-severity label (security > detail > validity)
|
|
292
|
+
# For validity or detail failures:
|
|
293
|
+
remove_mgw_labels_and_apply ${ISSUE_NUMBER} "mgw:needs-info"
|
|
294
|
+
|
|
295
|
+
# For security failures (highest severity — takes precedence over needs-info):
|
|
296
|
+
remove_mgw_labels_and_apply ${ISSUE_NUMBER} "mgw:needs-security-review"
|
|
297
|
+
|
|
298
|
+
# If multiple blockers, apply security label if security gate failed; otherwise needs-info.
|
|
300
299
|
```
|
|
301
300
|
|
|
302
301
|
**If gates passed (gate_result.status == "passed"):**
|
|
@@ -316,7 +315,7 @@ ROUTE_REASONING = triage reasoning
|
|
|
316
315
|
|
|
317
316
|
Post comment and apply label:
|
|
318
317
|
```bash
|
|
319
|
-
|
|
318
|
+
remove_mgw_labels_and_apply ${ISSUE_NUMBER} "mgw:triaged"
|
|
320
319
|
```
|
|
321
320
|
</step>
|
|
322
321
|
|
package/commands/milestone.md
CHANGED
|
@@ -414,8 +414,10 @@ Track state for progress table:
|
|
|
414
414
|
```bash
|
|
415
415
|
COMPLETED_ISSUES=()
|
|
416
416
|
FAILED_ISSUES=()
|
|
417
|
+
FAILED_ISSUES_WITH_CLASS=() # Entries: "issue_number:failure_class" for results display
|
|
417
418
|
BLOCKED_ISSUES=()
|
|
418
419
|
SKIPPED_ISSUES=()
|
|
420
|
+
LABEL_DRIFT_ISSUES=() # Issues where label reconciliation detected drift
|
|
419
421
|
ISSUES_RUN=0
|
|
420
422
|
```
|
|
421
423
|
|
|
@@ -561,6 +563,43 @@ COMMENTEOF
|
|
|
561
563
|
description="Run pipeline for #${ISSUE_NUMBER}"
|
|
562
564
|
)
|
|
563
565
|
|
|
566
|
+
# ── POST-WORK: Post-subagent label verification ──
|
|
567
|
+
# Read the pipeline_stage from the issue's active state file after Task() returns
|
|
568
|
+
ISSUE_STAGE=$(node -e "
|
|
569
|
+
const { loadActiveIssue } = require('./lib/state.cjs');
|
|
570
|
+
const state = loadActiveIssue(${ISSUE_NUMBER});
|
|
571
|
+
console.log((state && state.pipeline_stage) ? state.pipeline_stage : 'unknown');
|
|
572
|
+
" 2>/dev/null || echo "unknown")
|
|
573
|
+
|
|
574
|
+
# Determine the expected MGW label for this pipeline stage
|
|
575
|
+
EXPECTED_LABEL=$(python3 -c "
|
|
576
|
+
stage_to_label = {
|
|
577
|
+
'done': '',
|
|
578
|
+
'pr-created': '',
|
|
579
|
+
'verifying': 'mgw:in-progress',
|
|
580
|
+
'executing': 'mgw:in-progress',
|
|
581
|
+
'planning': 'mgw:in-progress',
|
|
582
|
+
'blocked': 'mgw:blocked',
|
|
583
|
+
'failed': '',
|
|
584
|
+
}
|
|
585
|
+
print(stage_to_label.get('${ISSUE_STAGE}', ''))
|
|
586
|
+
")
|
|
587
|
+
|
|
588
|
+
# Compare expected label against live GitHub labels
|
|
589
|
+
LIVE_LABELS=$(gh issue view ${ISSUE_NUMBER} --json labels --jq '[.labels[].name] | join(",")' 2>/dev/null || echo "")
|
|
590
|
+
|
|
591
|
+
if [ -n "$EXPECTED_LABEL" ] && ! echo "$LIVE_LABELS" | grep -q "$EXPECTED_LABEL"; then
|
|
592
|
+
echo "MGW WARNING: label drift on #${ISSUE_NUMBER} — expected $EXPECTED_LABEL, live: $LIVE_LABELS" >&2
|
|
593
|
+
LABEL_DRIFT="drift"
|
|
594
|
+
else
|
|
595
|
+
LABEL_DRIFT="ok"
|
|
596
|
+
fi
|
|
597
|
+
|
|
598
|
+
# Track drifted issues for milestone summary
|
|
599
|
+
if [ "$LABEL_DRIFT" = "drift" ]; then
|
|
600
|
+
LABEL_DRIFT_ISSUES+=("$ISSUE_NUMBER")
|
|
601
|
+
fi
|
|
602
|
+
|
|
564
603
|
# ── POST-WORK: Detect result and post completion comment ──
|
|
565
604
|
# Check if PR was created by looking for state file or PR
|
|
566
605
|
PR_NUMBER=$(gh pr list --head "issue/${ISSUE_NUMBER}-*" --json number -q '.[0].number' 2>/dev/null || echo "")
|
|
@@ -620,9 +659,30 @@ COMMENTEOF
|
|
|
620
659
|
echo " ✓ #${ISSUE_NUMBER} — PR #${PR_NUMBER} created"
|
|
621
660
|
|
|
622
661
|
else
|
|
623
|
-
# Failure — post failure comment
|
|
662
|
+
# Failure — read failure_class from active issue state, then post failure comment
|
|
624
663
|
FAILED_ISSUES+=("$ISSUE_NUMBER")
|
|
625
664
|
|
|
665
|
+
# Read failure_class and dead_letter from the active issue state file
|
|
666
|
+
ISSUE_FAILURE_CLASS=$(node -e "
|
|
667
|
+
const { loadActiveIssue } = require('./lib/state.cjs');
|
|
668
|
+
const state = loadActiveIssue(${ISSUE_NUMBER});
|
|
669
|
+
console.log((state && state.last_failure_class) ? state.last_failure_class : 'unknown');
|
|
670
|
+
" 2>/dev/null || echo "unknown")
|
|
671
|
+
|
|
672
|
+
ISSUE_DEAD_LETTER=$(node -e "
|
|
673
|
+
const { loadActiveIssue } = require('./lib/state.cjs');
|
|
674
|
+
const state = loadActiveIssue(${ISSUE_NUMBER});
|
|
675
|
+
console.log(state && state.dead_letter === true ? 'true' : 'false');
|
|
676
|
+
" 2>/dev/null || echo "false")
|
|
677
|
+
|
|
678
|
+
ISSUE_RETRY_COUNT=$(node -e "
|
|
679
|
+
const { loadActiveIssue } = require('./lib/state.cjs');
|
|
680
|
+
const state = loadActiveIssue(${ISSUE_NUMBER});
|
|
681
|
+
console.log((state && typeof state.retry_count === 'number') ? state.retry_count : 0);
|
|
682
|
+
" 2>/dev/null || echo "0")
|
|
683
|
+
|
|
684
|
+
FAILED_ISSUES_WITH_CLASS+=("${ISSUE_NUMBER}:${ISSUE_FAILURE_CLASS}")
|
|
685
|
+
|
|
626
686
|
FAIL_BODY=$(cat <<COMMENTEOF
|
|
627
687
|
> **MGW** · \`pipeline-failed\` · ${DONE_TIMESTAMP}
|
|
628
688
|
> Milestone: ${MILESTONE_NAME} | Phase ${PHASE_NUM}: ${PHASE_NAME}
|
|
@@ -630,16 +690,22 @@ COMMENTEOF
|
|
|
630
690
|
### Pipeline Failed
|
|
631
691
|
|
|
632
692
|
Issue #${ISSUE_NUMBER} did not produce a PR.
|
|
633
|
-
|
|
693
|
+
|
|
694
|
+
| | |
|
|
695
|
+
|---|---|
|
|
696
|
+
| **Failure class** | \`${ISSUE_FAILURE_CLASS}\` |
|
|
697
|
+
| **Retries attempted** | ${ISSUE_RETRY_COUNT} of 3 |
|
|
698
|
+
| **Dead-lettered** | ${ISSUE_DEAD_LETTER} |
|
|
634
699
|
|
|
635
700
|
Dependents of this issue will be skipped.
|
|
701
|
+
To retry after resolving root cause: \`/mgw:run ${ISSUE_NUMBER} --retry\`
|
|
636
702
|
COMMENTEOF
|
|
637
703
|
)
|
|
638
704
|
|
|
639
705
|
gh issue comment ${ISSUE_NUMBER} --body "$FAIL_BODY" 2>/dev/null || true
|
|
640
706
|
gh issue edit ${ISSUE_NUMBER} --add-label "pipeline-failed" 2>/dev/null || true
|
|
641
707
|
gh label create "pipeline-failed" --description "Pipeline execution failed" --color "d73a4a" --force 2>/dev/null || true
|
|
642
|
-
echo " ✗ #${ISSUE_NUMBER} — Failed (no PR created)"
|
|
708
|
+
echo " ✗ #${ISSUE_NUMBER} — Failed (class: ${ISSUE_FAILURE_CLASS}, no PR created)"
|
|
643
709
|
fi
|
|
644
710
|
|
|
645
711
|
# Update project.json checkpoint (MLST-05)
|
|
@@ -684,16 +750,20 @@ Every comment posted during milestone orchestration includes:
|
|
|
684
750
|
<details>
|
|
685
751
|
<summary>Milestone Progress ({done}/{total} complete)</summary>
|
|
686
752
|
|
|
687
|
-
| # | Issue | Status | PR |
|
|
688
|
-
|
|
689
|
-
| N | title | ✓ Done | #PR |
|
|
690
|
-
| M | title | ✗ Failed | — |
|
|
691
|
-
| K | title | ○ Pending | — |
|
|
692
|
-
| J | title | ◆ Running | — |
|
|
693
|
-
| L | title | ⊘ Blocked | — |
|
|
753
|
+
| # | Issue | Status | PR | Failure Class | Label Drift |
|
|
754
|
+
|---|-------|--------|----|---------------|-------------|
|
|
755
|
+
| N | title | ✓ Done | #PR | — | ok |
|
|
756
|
+
| M | title | ✗ Failed | — | `permanent` | — |
|
|
757
|
+
| K | title | ○ Pending | — | — | — |
|
|
758
|
+
| J | title | ◆ Running | — | — | ok |
|
|
759
|
+
| L | title | ⊘ Blocked | — | — | — |
|
|
694
760
|
|
|
695
761
|
</details>
|
|
696
762
|
```
|
|
763
|
+
|
|
764
|
+
The **Failure Class** column surfaces `last_failure_class` from the active issue state file.
|
|
765
|
+
The **Label Drift** column shows the result of post-subagent label reconciliation: `ok` (labels matched expected), `drift` (label mismatch detected — MGW WARNING logged), or `—` (not checked / issue not run).
|
|
766
|
+
Values: `transient` (retried and exhausted), `permanent` (unrecoverable), `needs-info` (ambiguous issue), `unknown` (no state file or pre-retry issue), `—` (not failed).
|
|
697
767
|
</step>
|
|
698
768
|
|
|
699
769
|
<step name="post_loop">
|
|
@@ -793,9 +863,18 @@ writeProjectState(state);
|
|
|
793
863
|
|
|
794
864
|
5. Milestone mapping verification:
|
|
795
865
|
|
|
796
|
-
After advancing to the next milestone, check its GSD linkage
|
|
866
|
+
After advancing to the next milestone, check its GSD linkage using `getGsdState()`
|
|
867
|
+
from `lib/gsd-adapter.cjs` to read current GSD execution state (.planning/STATE.md
|
|
868
|
+
and ROADMAP.md) alongside the project.json milestone map:
|
|
797
869
|
|
|
798
870
|
```bash
|
|
871
|
+
# Read current GSD state from .planning/ via the adapter
|
|
872
|
+
GSD_STATE=$(node -e "
|
|
873
|
+
const { getGsdState } = require('./lib/gsd-adapter.cjs');
|
|
874
|
+
const state = getGsdState();
|
|
875
|
+
console.log(JSON.stringify(state));
|
|
876
|
+
" 2>/dev/null || echo "null")
|
|
877
|
+
|
|
799
878
|
NEXT_MILESTONE_CHECK=$(node -e "
|
|
800
879
|
const { loadProjectState, resolveActiveMilestoneIndex } = require('./lib/state.cjs');
|
|
801
880
|
const state = loadProjectState();
|
|
@@ -906,6 +985,23 @@ Draft release created: ${RELEASE_TAG}
|
|
|
906
985
|
|
|
907
986
|
**If some issues failed:**
|
|
908
987
|
|
|
988
|
+
Build failure class lookup from `FAILED_ISSUES_WITH_CLASS` array for display:
|
|
989
|
+
```bash
|
|
990
|
+
# Build failure class map: { issue_number → failure_class }
|
|
991
|
+
FAILURE_CLASS_MAP=$(python3 -c "
|
|
992
|
+
import json, sys
|
|
993
|
+
|
|
994
|
+
entries = '${FAILED_ISSUES_WITH_CLASS[@]}'.split()
|
|
995
|
+
result = {}
|
|
996
|
+
for entry in entries:
|
|
997
|
+
if ':' in entry:
|
|
998
|
+
num, cls = entry.split(':', 1)
|
|
999
|
+
result[num] = cls
|
|
1000
|
+
print(json.dumps(result))
|
|
1001
|
+
" 2>/dev/null || echo "{}")
|
|
1002
|
+
```
|
|
1003
|
+
|
|
1004
|
+
Display results table including failure_class for each failed issue:
|
|
909
1005
|
```
|
|
910
1006
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
911
1007
|
MGW ► MILESTONE ${MILESTONE_NUM} INCOMPLETE
|
|
@@ -913,15 +1009,78 @@ Draft release created: ${RELEASE_TAG}
|
|
|
913
1009
|
|
|
914
1010
|
${MILESTONE_NAME}
|
|
915
1011
|
|
|
916
|
-
| # | Issue | Status | PR |
|
|
917
|
-
|
|
918
|
-
${
|
|
1012
|
+
| # | Issue | Status | PR | Failure Class |
|
|
1013
|
+
|---|-------|--------|----|---------------|
|
|
1014
|
+
${results_table_with_failure_class}
|
|
919
1015
|
|
|
920
1016
|
Completed: ${TOTAL_DONE}/${TOTAL_ISSUES}
|
|
921
1017
|
Failed: ${TOTAL_FAILED}
|
|
922
1018
|
Blocked: ${TOTAL_BLOCKED}
|
|
1019
|
+
```
|
|
923
1020
|
|
|
924
|
-
|
|
1021
|
+
For each failed issue, present recovery options:
|
|
1022
|
+
```bash
|
|
1023
|
+
for ENTRY in "${FAILED_ISSUES_WITH_CLASS[@]}"; do
|
|
1024
|
+
FAIL_NUM=$(echo "$ENTRY" | cut -d':' -f1)
|
|
1025
|
+
FAIL_CLASS=$(echo "$ENTRY" | cut -d':' -f2)
|
|
1026
|
+
|
|
1027
|
+
echo ""
|
|
1028
|
+
echo " Failed: #${FAIL_NUM} (class: ${FAIL_CLASS})"
|
|
1029
|
+
AskUserQuestion(
|
|
1030
|
+
header: "Recovery — Issue #${FAIL_NUM}",
|
|
1031
|
+
question: "Issue #${FAIL_NUM} failed (failure class: ${FAIL_CLASS}). What would you like to do?",
|
|
1032
|
+
options: [
|
|
1033
|
+
{
|
|
1034
|
+
label: "Retry",
|
|
1035
|
+
description: "Reset retry state via resetRetryState() and re-run /mgw:run #${FAIL_NUM} --retry"
|
|
1036
|
+
},
|
|
1037
|
+
{
|
|
1038
|
+
label: "Skip",
|
|
1039
|
+
description: "Mark as skipped and continue to next issue (dependents will remain blocked)"
|
|
1040
|
+
},
|
|
1041
|
+
{
|
|
1042
|
+
label: "Abort",
|
|
1043
|
+
description: "Stop milestone recovery here"
|
|
1044
|
+
}
|
|
1045
|
+
]
|
|
1046
|
+
)
|
|
1047
|
+
|
|
1048
|
+
case "$RECOVERY_CHOICE" in
|
|
1049
|
+
Retry)
|
|
1050
|
+
# Call resetRetryState() to clear retry_count, last_failure_class, dead_letter
|
|
1051
|
+
node -e "
|
|
1052
|
+
const { resetRetryState } = require('./lib/retry.cjs');
|
|
1053
|
+
const fs = require('fs'), path = require('path');
|
|
1054
|
+
const activeDir = path.join(process.cwd(), '.mgw', 'active');
|
|
1055
|
+
const files = fs.readdirSync(activeDir);
|
|
1056
|
+
const file = files.find(f => f.startsWith('${FAIL_NUM}-') && f.endsWith('.json'));
|
|
1057
|
+
if (!file) { console.error('No state file for #${FAIL_NUM}'); process.exit(1); }
|
|
1058
|
+
const filePath = path.join(activeDir, file);
|
|
1059
|
+
const state = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
1060
|
+
const reset = resetRetryState(state);
|
|
1061
|
+
reset.pipeline_stage = 'triaged';
|
|
1062
|
+
fs.writeFileSync(filePath, JSON.stringify(reset, null, 2));
|
|
1063
|
+
console.log('Retry state cleared for #${FAIL_NUM}');
|
|
1064
|
+
"
|
|
1065
|
+
# Remove pipeline-failed label before re-run
|
|
1066
|
+
gh issue edit ${FAIL_NUM} --remove-label "pipeline-failed" 2>/dev/null || true
|
|
1067
|
+
# Re-run the pipeline for this issue
|
|
1068
|
+
/mgw:run ${FAIL_NUM} --retry
|
|
1069
|
+
;;
|
|
1070
|
+
Skip)
|
|
1071
|
+
echo " ⊘ #${FAIL_NUM} — Skipped (will not retry)"
|
|
1072
|
+
;;
|
|
1073
|
+
Abort)
|
|
1074
|
+
echo "Milestone recovery aborted at #${FAIL_NUM}."
|
|
1075
|
+
break
|
|
1076
|
+
;;
|
|
1077
|
+
esac
|
|
1078
|
+
done
|
|
1079
|
+
```
|
|
1080
|
+
|
|
1081
|
+
After recovery loop:
|
|
1082
|
+
```
|
|
1083
|
+
Milestone NOT closed. Re-run after resolving remaining failures:
|
|
925
1084
|
/mgw:milestone ${MILESTONE_NUM}
|
|
926
1085
|
```
|
|
927
1086
|
|
|
@@ -943,7 +1102,13 @@ gh issue comment ${FIRST_ISSUE_NUMBER} --body "$FINAL_RESULTS_COMMENT"
|
|
|
943
1102
|
- [ ] Sequential execution via /mgw:run Task() delegation (MLST-01)
|
|
944
1103
|
- [ ] Per-issue checkpoint to project.json after completion (MLST-05)
|
|
945
1104
|
- [ ] Failure handling: skip failed, label, comment, block dependents
|
|
1105
|
+
- [ ] failure_class surfaced in results table and failure comment for each failed issue
|
|
1106
|
+
- [ ] Retry option calls resetRetryState() then re-invokes /mgw:run --retry for failed issues
|
|
1107
|
+
- [ ] FAILED_ISSUES_WITH_CLASS tracks "number:class" for display in results table
|
|
946
1108
|
- [ ] Progress table in every GitHub comment
|
|
1109
|
+
- [ ] Post-subagent label reconciliation run per issue after Task() returns
|
|
1110
|
+
- [ ] LABEL_DRIFT tracked per issue (ok/drift) and shown in progress table Label Drift column
|
|
1111
|
+
- [ ] Label drift issues logged as MGW WARNING to stderr
|
|
947
1112
|
- [ ] Milestone close + draft release on full completion
|
|
948
1113
|
- [ ] current_milestone pointer advanced on completion
|
|
949
1114
|
- [ ] --interactive flag pauses between issues
|