@snipcodeit/mgw 0.1.0 → 0.1.2

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 CHANGED
@@ -1,8 +1,9 @@
1
1
  # MGW — My GSD Workflow
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/mgw)](https://www.npmjs.com/package/mgw)
3
+ [![npm version](https://img.shields.io/npm/v/@snipcodeit/mgw)](https://www.npmjs.com/package/@snipcodeit/mgw)
4
+ [![npm downloads](https://img.shields.io/npm/dm/@snipcodeit/mgw)](https://www.npmjs.com/package/@snipcodeit/mgw)
4
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
- [![node](https://img.shields.io/node/v/mgw)](https://nodejs.org)
6
+ [![node](https://img.shields.io/node/v/@snipcodeit/mgw)](https://nodejs.org)
6
7
  [![GitHub stars](https://img.shields.io/github/stars/snipcodeit/mgw)](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-draft.md (Fresh projects) Rolling decisions from questioning loop
188
- vision-brief.json (Fresh projects) Structured Vision Brief (MoSCoW, personas, scope)
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`.
@@ -207,37 +214,37 @@ Try MGW without installing anything:
207
214
 
208
215
  ```bash
209
216
  # See available commands
210
- npx mgw --help
217
+ npx @snipcodeit/mgw --help
211
218
 
212
219
  # List your open issues
213
- npx mgw issues
220
+ npx @snipcodeit/mgw issues
214
221
 
215
222
  # Sync local state with GitHub
216
- npx mgw sync
223
+ npx @snipcodeit/mgw sync
217
224
 
218
225
  # Cross-reference two issues
219
- npx mgw link 42 43
226
+ npx @snipcodeit/mgw link 42 43
220
227
  ```
221
228
 
222
- `npx mgw` gives you the full CLI subset that works without Claude Code. For the AI-powered pipeline commands (`run`, `issue`, `project`, `milestone`, etc.), do a full install below.
229
+ `npx @snipcodeit/mgw` gives you the full CLI subset that works without Claude Code. For the AI-powered pipeline commands (`run`, `issue`, `project`, `milestone`, etc.), do a full install below.
223
230
 
224
231
  ## Installation
225
232
 
226
- ### Full install (CLI + slash commands)
233
+ ### npm (recommended)
227
234
 
228
235
  ```bash
229
- git clone https://github.com/snipcodeit/mgw.git
230
- cd mgw
231
- npm install && npm run build
232
- npm link
233
- # Slash commands are installed automatically by npm postinstall
236
+ npm install -g @snipcodeit/mgw
237
+ # Slash commands are automatically deployed to ~/.claude/commands/mgw/
234
238
  ```
235
239
 
236
- ### Slash commands only (no CLI)
240
+ ### From source
237
241
 
238
242
  ```bash
239
- npm install -g mgw
240
- # Slash commands are automatically deployed to ~/.claude/commands/mgw/
243
+ git clone https://github.com/snipcodeit/mgw.git
244
+ cd mgw
245
+ npm install && npm run build
246
+ npm install -g . --prefix ~/.npm-global
247
+ # Slash commands are installed automatically by npm postinstall
241
248
  ```
242
249
 
243
250
  ### Verify
@@ -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 milestone.md
252
- # next.md pr.md project.md review.md run.md status.md sync.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,9 +273,9 @@ 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
- 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. Use `npx mgw` to explore the CLI and verify your GitHub setup before committing to a full install.
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
 
273
280
  ## Typical Workflow
274
281
 
@@ -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 unlink mgw
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
@@ -414,6 +414,7 @@ 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=()
419
420
  ISSUES_RUN=0
@@ -620,9 +621,30 @@ COMMENTEOF
620
621
  echo " ✓ #${ISSUE_NUMBER} — PR #${PR_NUMBER} created"
621
622
 
622
623
  else
623
- # Failure — post failure comment
624
+ # Failure — read failure_class from active issue state, then post failure comment
624
625
  FAILED_ISSUES+=("$ISSUE_NUMBER")
625
626
 
627
+ # Read failure_class and dead_letter from the active issue state file
628
+ ISSUE_FAILURE_CLASS=$(node -e "
629
+ const { loadActiveIssue } = require('./lib/state.cjs');
630
+ const state = loadActiveIssue(${ISSUE_NUMBER});
631
+ console.log((state && state.last_failure_class) ? state.last_failure_class : 'unknown');
632
+ " 2>/dev/null || echo "unknown")
633
+
634
+ ISSUE_DEAD_LETTER=$(node -e "
635
+ const { loadActiveIssue } = require('./lib/state.cjs');
636
+ const state = loadActiveIssue(${ISSUE_NUMBER});
637
+ console.log(state && state.dead_letter === true ? 'true' : 'false');
638
+ " 2>/dev/null || echo "false")
639
+
640
+ ISSUE_RETRY_COUNT=$(node -e "
641
+ const { loadActiveIssue } = require('./lib/state.cjs');
642
+ const state = loadActiveIssue(${ISSUE_NUMBER});
643
+ console.log((state && typeof state.retry_count === 'number') ? state.retry_count : 0);
644
+ " 2>/dev/null || echo "0")
645
+
646
+ FAILED_ISSUES_WITH_CLASS+=("${ISSUE_NUMBER}:${ISSUE_FAILURE_CLASS}")
647
+
626
648
  FAIL_BODY=$(cat <<COMMENTEOF
627
649
  > **MGW** · \`pipeline-failed\` · ${DONE_TIMESTAMP}
628
650
  > Milestone: ${MILESTONE_NAME} | Phase ${PHASE_NUM}: ${PHASE_NAME}
@@ -630,16 +652,22 @@ COMMENTEOF
630
652
  ### Pipeline Failed
631
653
 
632
654
  Issue #${ISSUE_NUMBER} did not produce a PR.
633
- Check the execution log for details.
655
+
656
+ | | |
657
+ |---|---|
658
+ | **Failure class** | \`${ISSUE_FAILURE_CLASS}\` |
659
+ | **Retries attempted** | ${ISSUE_RETRY_COUNT} of 3 |
660
+ | **Dead-lettered** | ${ISSUE_DEAD_LETTER} |
634
661
 
635
662
  Dependents of this issue will be skipped.
663
+ To retry after resolving root cause: \`/mgw:run ${ISSUE_NUMBER} --retry\`
636
664
  COMMENTEOF
637
665
  )
638
666
 
639
667
  gh issue comment ${ISSUE_NUMBER} --body "$FAIL_BODY" 2>/dev/null || true
640
668
  gh issue edit ${ISSUE_NUMBER} --add-label "pipeline-failed" 2>/dev/null || true
641
669
  gh label create "pipeline-failed" --description "Pipeline execution failed" --color "d73a4a" --force 2>/dev/null || true
642
- echo " ✗ #${ISSUE_NUMBER} — Failed (no PR created)"
670
+ echo " ✗ #${ISSUE_NUMBER} — Failed (class: ${ISSUE_FAILURE_CLASS}, no PR created)"
643
671
  fi
644
672
 
645
673
  # Update project.json checkpoint (MLST-05)
@@ -684,16 +712,19 @@ Every comment posted during milestone orchestration includes:
684
712
  <details>
685
713
  <summary>Milestone Progress ({done}/{total} complete)</summary>
686
714
 
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 |
715
+ | # | Issue | Status | PR | Failure Class |
716
+ |---|-------|--------|----|---------------|
717
+ | N | title | ✓ Done | #PR | |
718
+ | M | title | ✗ Failed | — | `permanent` |
719
+ | K | title | ○ Pending | — | |
720
+ | J | title | ◆ Running | — | |
721
+ | L | title | ⊘ Blocked | — | |
694
722
 
695
723
  </details>
696
724
  ```
725
+
726
+ The **Failure Class** column surfaces `last_failure_class` from the active issue state file.
727
+ Values: `transient` (retried and exhausted), `permanent` (unrecoverable), `needs-info` (ambiguous issue), `unknown` (no state file or pre-retry issue), `—` (not failed).
697
728
  </step>
698
729
 
699
730
  <step name="post_loop">
@@ -793,9 +824,18 @@ writeProjectState(state);
793
824
 
794
825
  5. Milestone mapping verification:
795
826
 
796
- After advancing to the next milestone, check its GSD linkage:
827
+ After advancing to the next milestone, check its GSD linkage using `getGsdState()`
828
+ from `lib/gsd-adapter.cjs` to read current GSD execution state (.planning/STATE.md
829
+ and ROADMAP.md) alongside the project.json milestone map:
797
830
 
798
831
  ```bash
832
+ # Read current GSD state from .planning/ via the adapter
833
+ GSD_STATE=$(node -e "
834
+ const { getGsdState } = require('./lib/gsd-adapter.cjs');
835
+ const state = getGsdState();
836
+ console.log(JSON.stringify(state));
837
+ " 2>/dev/null || echo "null")
838
+
799
839
  NEXT_MILESTONE_CHECK=$(node -e "
800
840
  const { loadProjectState, resolveActiveMilestoneIndex } = require('./lib/state.cjs');
801
841
  const state = loadProjectState();
@@ -906,6 +946,23 @@ Draft release created: ${RELEASE_TAG}
906
946
 
907
947
  **If some issues failed:**
908
948
 
949
+ Build failure class lookup from `FAILED_ISSUES_WITH_CLASS` array for display:
950
+ ```bash
951
+ # Build failure class map: { issue_number → failure_class }
952
+ FAILURE_CLASS_MAP=$(python3 -c "
953
+ import json, sys
954
+
955
+ entries = '${FAILED_ISSUES_WITH_CLASS[@]}'.split()
956
+ result = {}
957
+ for entry in entries:
958
+ if ':' in entry:
959
+ num, cls = entry.split(':', 1)
960
+ result[num] = cls
961
+ print(json.dumps(result))
962
+ " 2>/dev/null || echo "{}")
963
+ ```
964
+
965
+ Display results table including failure_class for each failed issue:
909
966
  ```
910
967
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
911
968
  MGW ► MILESTONE ${MILESTONE_NUM} INCOMPLETE
@@ -913,15 +970,78 @@ Draft release created: ${RELEASE_TAG}
913
970
 
914
971
  ${MILESTONE_NAME}
915
972
 
916
- | # | Issue | Status | PR |
917
- |---|-------|--------|----|
918
- ${results_table}
973
+ | # | Issue | Status | PR | Failure Class |
974
+ |---|-------|--------|----|---------------|
975
+ ${results_table_with_failure_class}
919
976
 
920
977
  Completed: ${TOTAL_DONE}/${TOTAL_ISSUES}
921
978
  Failed: ${TOTAL_FAILED}
922
979
  Blocked: ${TOTAL_BLOCKED}
980
+ ```
981
+
982
+ For each failed issue, present recovery options:
983
+ ```bash
984
+ for ENTRY in "${FAILED_ISSUES_WITH_CLASS[@]}"; do
985
+ FAIL_NUM=$(echo "$ENTRY" | cut -d':' -f1)
986
+ FAIL_CLASS=$(echo "$ENTRY" | cut -d':' -f2)
987
+
988
+ echo ""
989
+ echo " Failed: #${FAIL_NUM} (class: ${FAIL_CLASS})"
990
+ AskUserQuestion(
991
+ header: "Recovery — Issue #${FAIL_NUM}",
992
+ question: "Issue #${FAIL_NUM} failed (failure class: ${FAIL_CLASS}). What would you like to do?",
993
+ options: [
994
+ {
995
+ label: "Retry",
996
+ description: "Reset retry state via resetRetryState() and re-run /mgw:run #${FAIL_NUM} --retry"
997
+ },
998
+ {
999
+ label: "Skip",
1000
+ description: "Mark as skipped and continue to next issue (dependents will remain blocked)"
1001
+ },
1002
+ {
1003
+ label: "Abort",
1004
+ description: "Stop milestone recovery here"
1005
+ }
1006
+ ]
1007
+ )
923
1008
 
924
- Milestone NOT closed. Resolve failures and re-run:
1009
+ case "$RECOVERY_CHOICE" in
1010
+ Retry)
1011
+ # Call resetRetryState() to clear retry_count, last_failure_class, dead_letter
1012
+ node -e "
1013
+ const { resetRetryState } = require('./lib/retry.cjs');
1014
+ const fs = require('fs'), path = require('path');
1015
+ const activeDir = path.join(process.cwd(), '.mgw', 'active');
1016
+ const files = fs.readdirSync(activeDir);
1017
+ const file = files.find(f => f.startsWith('${FAIL_NUM}-') && f.endsWith('.json'));
1018
+ if (!file) { console.error('No state file for #${FAIL_NUM}'); process.exit(1); }
1019
+ const filePath = path.join(activeDir, file);
1020
+ const state = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
1021
+ const reset = resetRetryState(state);
1022
+ reset.pipeline_stage = 'triaged';
1023
+ fs.writeFileSync(filePath, JSON.stringify(reset, null, 2));
1024
+ console.log('Retry state cleared for #${FAIL_NUM}');
1025
+ "
1026
+ # Remove pipeline-failed label before re-run
1027
+ gh issue edit ${FAIL_NUM} --remove-label "pipeline-failed" 2>/dev/null || true
1028
+ # Re-run the pipeline for this issue
1029
+ /mgw:run ${FAIL_NUM} --retry
1030
+ ;;
1031
+ Skip)
1032
+ echo " ⊘ #${FAIL_NUM} — Skipped (will not retry)"
1033
+ ;;
1034
+ Abort)
1035
+ echo "Milestone recovery aborted at #${FAIL_NUM}."
1036
+ break
1037
+ ;;
1038
+ esac
1039
+ done
1040
+ ```
1041
+
1042
+ After recovery loop:
1043
+ ```
1044
+ Milestone NOT closed. Re-run after resolving remaining failures:
925
1045
  /mgw:milestone ${MILESTONE_NUM}
926
1046
  ```
927
1047
 
@@ -943,6 +1063,9 @@ gh issue comment ${FIRST_ISSUE_NUMBER} --body "$FINAL_RESULTS_COMMENT"
943
1063
  - [ ] Sequential execution via /mgw:run Task() delegation (MLST-01)
944
1064
  - [ ] Per-issue checkpoint to project.json after completion (MLST-05)
945
1065
  - [ ] Failure handling: skip failed, label, comment, block dependents
1066
+ - [ ] failure_class surfaced in results table and failure comment for each failed issue
1067
+ - [ ] Retry option calls resetRetryState() then re-invokes /mgw:run --retry for failed issues
1068
+ - [ ] FAILED_ISSUES_WITH_CLASS tracks "number:class" for display in results table
946
1069
  - [ ] Progress table in every GitHub comment
947
1070
  - [ ] Milestone close + draft release on full completion
948
1071
  - [ ] current_milestone pointer advanced on completion