@snipcodeit/mgw 0.1.2 → 0.2.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/commands/init.md +115 -5
- package/commands/issue.md +9 -10
- package/commands/issues.md +63 -1
- package/commands/milestone.md +92 -7
- package/commands/run.md +34 -9
- package/commands/status.md +95 -1
- package/commands/sync.md +77 -0
- package/commands/workflows/github.md +19 -4
- package/completions/mgw.bash +112 -0
- package/completions/mgw.fish +99 -0
- package/completions/mgw.zsh +142 -0
- package/dist/bin/mgw.cjs +99 -29
- package/dist/index-CXfe9U4l.cjs +1818 -0
- package/dist/lib/index.cjs +109 -8
- package/package.json +6 -1
- package/dist/claude-Dk1oVsaG.cjs +0 -622
package/commands/init.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: mgw:init
|
|
3
|
-
description: Bootstrap current repo for MGW integration — creates .mgw/ state, GitHub templates, gitignore entries
|
|
4
|
-
argument-hint: ""
|
|
3
|
+
description: Bootstrap current repo for MGW integration — creates .mgw/ state, GitHub templates, gitignore entries, installs shell completions, and runs config wizard
|
|
4
|
+
argument-hint: "[--no-config]"
|
|
5
5
|
allowed-tools:
|
|
6
6
|
- Bash
|
|
7
7
|
- Read
|
|
@@ -12,8 +12,9 @@ allowed-tools:
|
|
|
12
12
|
|
|
13
13
|
<objective>
|
|
14
14
|
One-time setup for a repo to work with MGW. Creates the .mgw/ state directory,
|
|
15
|
-
GitHub issue/PR templates,
|
|
16
|
-
|
|
15
|
+
GitHub issue/PR templates, ensures gitignore entries, and runs an interactive
|
|
16
|
+
config wizard for first-time setup preferences. Safe to re-run — skips anything
|
|
17
|
+
that already exists. Pass --no-config to skip the wizard.
|
|
17
18
|
</objective>
|
|
18
19
|
|
|
19
20
|
<execution_context>
|
|
@@ -213,6 +214,109 @@ gh label create "mgw:blocked" --description "Pipeline blocked by stakeholder com
|
|
|
213
214
|
`--force` updates existing labels without error.
|
|
214
215
|
</step>
|
|
215
216
|
|
|
217
|
+
<step name="install_completions">
|
|
218
|
+
**Offer to install shell completions (opt-in):**
|
|
219
|
+
|
|
220
|
+
Locate the completions directory bundled with the MGW package:
|
|
221
|
+
```bash
|
|
222
|
+
MGW_PKG_DIR=$(node -e "const path = require('path'); console.log(path.resolve(__dirname, '..', '..'))" 2>/dev/null || echo "")
|
|
223
|
+
COMPLETIONS_DIR="${MGW_PKG_DIR}/completions"
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
If completions are not found (package not globally installed or completions dir absent) → skip silently, note "Shell completions not available (mgw not installed globally)" in report.
|
|
227
|
+
|
|
228
|
+
If completions are found, detect the user's shell and determine the install target:
|
|
229
|
+
```bash
|
|
230
|
+
CURRENT_SHELL=$(basename "${SHELL:-}")
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Shell → target directory mapping:
|
|
234
|
+
- `bash` → `~/.local/share/bash-completion/completions/` (source file: `mgw.bash`)
|
|
235
|
+
- `zsh` → `~/.zsh/completions/` (source file: `mgw.zsh`)
|
|
236
|
+
- `fish` → `~/.config/fish/completions/` (source file: `mgw.fish`)
|
|
237
|
+
|
|
238
|
+
If shell is unrecognized or `SHELL` is unset → show all three install commands and skip auto-install.
|
|
239
|
+
|
|
240
|
+
**Interactive mode (default):** Ask the user:
|
|
241
|
+
```
|
|
242
|
+
Shell completions are available for ${CURRENT_SHELL}.
|
|
243
|
+
|
|
244
|
+
Install to ${COMPLETION_TARGET_DIR}? [Y/n]
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
If the user answers yes (or presses Enter for the default):
|
|
248
|
+
```bash
|
|
249
|
+
mkdir -p "${COMPLETION_TARGET_DIR}"
|
|
250
|
+
cp "${COMPLETIONS_DIR}/mgw.${SHELL_EXT}" "${COMPLETION_TARGET_DIR}/mgw.${SHELL_EXT}"
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Then show the source/activation line appropriate for the shell:
|
|
254
|
+
- bash: `# Reload with: source ~/.local/share/bash-completion/completions/mgw.bash`
|
|
255
|
+
(or add to ~/.bashrc: `source ~/.local/share/bash-completion/completions/mgw.bash`)
|
|
256
|
+
- zsh: Add to ~/.zshrc (required — ~/.zsh/completions is not in default $fpath):
|
|
257
|
+
```
|
|
258
|
+
fpath=(~/.zsh/completions $fpath)
|
|
259
|
+
autoload -Uz compinit
|
|
260
|
+
compinit
|
|
261
|
+
```
|
|
262
|
+
Then reload: `source ~/.zshrc`
|
|
263
|
+
- fish: `# Completions loaded automatically from ~/.config/fish/completions/`
|
|
264
|
+
|
|
265
|
+
If user answers no → skip, note "Shell completions: skipped" in report.
|
|
266
|
+
|
|
267
|
+
If the completion file already exists at the target → overwrite (idempotent re-run).
|
|
268
|
+
|
|
269
|
+
**Non-interactive mode** (stdin is not a TTY): Skip the prompt entirely, print the install command as a hint:
|
|
270
|
+
```
|
|
271
|
+
Shell completions available. Install manually:
|
|
272
|
+
cp ${COMPLETIONS_DIR}/mgw.${SHELL_EXT} ${COMPLETION_TARGET_DIR}/mgw.${SHELL_EXT}
|
|
273
|
+
```
|
|
274
|
+
</step>
|
|
275
|
+
|
|
276
|
+
<step name="run_config_wizard">
|
|
277
|
+
**Run interactive config wizard for first-time setup (skip if --no-config or config already exists):**
|
|
278
|
+
|
|
279
|
+
Check whether the wizard should run:
|
|
280
|
+
```bash
|
|
281
|
+
# Skip if --no-config flag is present
|
|
282
|
+
# Skip if stdin is not a TTY (CI/piped context)
|
|
283
|
+
# Skip if .mgw/config.json already exists
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Use `lib/config-wizard.cjs` to determine and execute:
|
|
287
|
+
```javascript
|
|
288
|
+
const { runWizard, shouldRunWizard } = require('./lib/config-wizard.cjs');
|
|
289
|
+
const mgwDir = path.join(REPO_ROOT, '.mgw');
|
|
290
|
+
|
|
291
|
+
if (shouldRunWizard(mgwDir, process.argv)) {
|
|
292
|
+
await runWizard(mgwDir);
|
|
293
|
+
} else if (fs.existsSync(path.join(mgwDir, 'config.json'))) {
|
|
294
|
+
// report: ".mgw/config.json exists, skipping wizard"
|
|
295
|
+
} else {
|
|
296
|
+
// report: ".mgw/config.json skipped (--no-config)"
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
The wizard asks the user four questions in order:
|
|
301
|
+
1. **GitHub username** — auto-detected from `gh api user -q .login`; user may accept or override
|
|
302
|
+
2. **Default issue state filter** — `open` (default) or `all`
|
|
303
|
+
3. **Default issue limit** — `10`, `25` (default), or `50`
|
|
304
|
+
4. **Default assignee filter** — `me` (default) or `all`
|
|
305
|
+
|
|
306
|
+
Answers are written to `${REPO_ROOT}/.mgw/config.json`:
|
|
307
|
+
```json
|
|
308
|
+
{
|
|
309
|
+
"github_username": "...",
|
|
310
|
+
"default_issue_state": "open",
|
|
311
|
+
"default_issue_limit": 25,
|
|
312
|
+
"default_assignee": "me",
|
|
313
|
+
"created_at": "<ISO timestamp>"
|
|
314
|
+
}
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
If the wizard errors or is interrupted, report the failure but do not abort the overall init — config is optional.
|
|
318
|
+
</step>
|
|
319
|
+
|
|
216
320
|
<step name="report">
|
|
217
321
|
**Report setup status:**
|
|
218
322
|
|
|
@@ -223,11 +327,13 @@ gh label create "mgw:blocked" --description "Pipeline blocked by stakeholder com
|
|
|
223
327
|
|
|
224
328
|
.mgw/ ${created|exists}
|
|
225
329
|
.mgw/cross-refs.json ${created|exists}
|
|
330
|
+
.mgw/config.json ${written|exists|skipped}
|
|
226
331
|
.gitignore entries ${added|exists}
|
|
227
332
|
Issue templates ${created|exists}
|
|
228
333
|
PR template ${created|exists}
|
|
229
334
|
GitHub labels synced
|
|
230
335
|
MGW pipeline labels synced (7 labels)
|
|
336
|
+
Shell completions ${installed (bash|zsh|fish)|skipped|not available}
|
|
231
337
|
|
|
232
338
|
Ready to use:
|
|
233
339
|
/mgw:issues Browse issues
|
|
@@ -242,9 +348,13 @@ Ready to use:
|
|
|
242
348
|
- [ ] .mgw/ directory structure created
|
|
243
349
|
- [ ] .mgw/ and .worktrees/ in .gitignore
|
|
244
350
|
- [ ] cross-refs.json initialized
|
|
351
|
+
- [ ] Config wizard run (or skipped via --no-config / pre-existing config.json)
|
|
352
|
+
- [ ] .mgw/config.json written with user preferences (unless skipped)
|
|
245
353
|
- [ ] Issue templates created (bug + enhancement)
|
|
246
354
|
- [ ] PR template created
|
|
247
355
|
- [ ] GitHub labels ensured (bug, enhancement)
|
|
248
356
|
- [ ] MGW pipeline labels ensured (7 mgw:* labels)
|
|
249
|
-
- [ ]
|
|
357
|
+
- [ ] Shell completion install offered (interactive) or hint printed (non-interactive)
|
|
358
|
+
- [ ] Completion install skipped gracefully if completions dir not found
|
|
359
|
+
- [ ] Setup report shown with completion status line
|
|
250
360
|
</success_criteria>
|
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/issues.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: mgw:issues
|
|
3
3
|
description: List and filter GitHub issues, pick one to triage
|
|
4
|
-
argument-hint: "[--label <label>] [--milestone <name>] [--assignee <user>] [--state open|closed|all]"
|
|
4
|
+
argument-hint: "[--label <label>] [--milestone <name>] [--assignee <user>] [--state open|closed|all] [--search <query>]"
|
|
5
5
|
allowed-tools:
|
|
6
6
|
- Bash
|
|
7
7
|
- Read
|
|
@@ -14,6 +14,10 @@ Browse GitHub issues for the current repo. Presents a scannable table filtered b
|
|
|
14
14
|
assignment (defaults to @me), labels, milestone, or state. Pick an issue to route
|
|
15
15
|
into triage via /mgw:issue.
|
|
16
16
|
|
|
17
|
+
This is the Claude Code slash-command variant (table + AskUserQuestion). When running
|
|
18
|
+
from the CLI (`mgw issues`), an interactive TUI browser launches instead — see
|
|
19
|
+
`docs/TUI-DESIGN.md` and `lib/tui/index.cjs` for the TUI implementation.
|
|
20
|
+
|
|
17
21
|
No side effects — read-only GitHub access. Safe to run anytime.
|
|
18
22
|
</objective>
|
|
19
23
|
|
|
@@ -107,3 +111,61 @@ If invalid → re-prompt.
|
|
|
107
111
|
- [ ] User can pick an issue number
|
|
108
112
|
- [ ] Routes to /mgw:issue <number>
|
|
109
113
|
</success_criteria>
|
|
114
|
+
|
|
115
|
+
## TUI Mode (CLI only)
|
|
116
|
+
|
|
117
|
+
When `mgw issues` runs from the CLI entry point (`bin/mgw.cjs`) in an interactive
|
|
118
|
+
terminal, it launches a full TUI browser instead of a static table.
|
|
119
|
+
|
|
120
|
+
**Entry point:** `lib/tui/index.cjs` — `createIssuesBrowser(options)`
|
|
121
|
+
|
|
122
|
+
**CLI options:**
|
|
123
|
+
```
|
|
124
|
+
mgw issues [options]
|
|
125
|
+
-l, --label <label> Filter by label
|
|
126
|
+
-m, --milestone <name> Filter by milestone
|
|
127
|
+
-a, --assignee <user> Assignee filter (default: @me, 'all' = no filter)
|
|
128
|
+
-s, --search <query> Pre-populate the fuzzy search input
|
|
129
|
+
--state <state> Issue state: open|closed|all (default: open)
|
|
130
|
+
--limit <n> Max issues to load (default: 50)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**TUI keyboard shortcuts:**
|
|
134
|
+
| Key | Action |
|
|
135
|
+
|-----|--------|
|
|
136
|
+
| `j` / `↓` | Scroll down |
|
|
137
|
+
| `k` / `↑` | Scroll up |
|
|
138
|
+
| `/` | Focus search input |
|
|
139
|
+
| `Enter` | Select issue → prints `#N — Title` and exits |
|
|
140
|
+
| `q` / `Esc` | Quit |
|
|
141
|
+
| `Tab` | Cycle focus (list → detail → filter) |
|
|
142
|
+
| `g` / `Home` | Jump to top |
|
|
143
|
+
| `G` / `End` | Jump to bottom |
|
|
144
|
+
| `?` | Toggle keyboard help |
|
|
145
|
+
|
|
146
|
+
**Non-interactive fallback:**
|
|
147
|
+
When stdout is not a TTY (piped, CI, `MGW_NO_TUI=1`), the static table is printed
|
|
148
|
+
to stdout. Pipe-friendly — no ANSI codes, no interactive elements.
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
mgw issues | grep "auth"
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**Implementation modules:**
|
|
155
|
+
```
|
|
156
|
+
lib/tui/
|
|
157
|
+
index.cjs — createIssuesBrowser(options) — entry point
|
|
158
|
+
search.cjs — FuzzySearch class — pure, no UI dependency
|
|
159
|
+
keyboard.cjs — KeyboardHandler (EventEmitter)
|
|
160
|
+
renderer.cjs — createRenderer() — blessed/neo-blessed adapter
|
|
161
|
+
graceful.cjs — isInteractive(), renderStaticTable()
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**Design document:** `docs/TUI-DESIGN.md` — library selection rationale, wireframe, full interface contracts.
|
|
165
|
+
|
|
166
|
+
**Rendering library:** `neo-blessed` (optional dependency). Renderer is swappable via `lib/tui/renderer.cjs`.
|
|
167
|
+
|
|
168
|
+
**Slash command vs CLI:**
|
|
169
|
+
This slash command (`/mgw:issues`) uses the static table + `AskUserQuestion` pattern
|
|
170
|
+
because Claude Code sessions don't have raw TTY access. The TUI is CLI-only (`mgw issues`).
|
|
171
|
+
Both paths route to `/mgw:issue <number>` for triage.
|
package/commands/milestone.md
CHANGED
|
@@ -141,6 +141,28 @@ Display:
|
|
|
141
141
|
Issues: ${TOTAL_ISSUES}
|
|
142
142
|
Mode: ${INTERACTIVE ? "Interactive" : "Autonomous"}
|
|
143
143
|
```
|
|
144
|
+
|
|
145
|
+
Then print the initial milestone progress bar (0 done, TOTAL_ISSUES total):
|
|
146
|
+
```bash
|
|
147
|
+
ISSUES_WITH_STAGES=$(echo "$ISSUES_JSON" | python3 -c "
|
|
148
|
+
import json,sys
|
|
149
|
+
issues = json.load(sys.stdin)
|
|
150
|
+
result = [{'number': i['github_number'], 'pipeline_stage': i.get('pipeline_stage', 'new')} for i in issues]
|
|
151
|
+
print(json.dumps(result))
|
|
152
|
+
")
|
|
153
|
+
|
|
154
|
+
node -e "
|
|
155
|
+
const { printMilestoneProgress } = require('./lib/progress.cjs');
|
|
156
|
+
const issues = JSON.parse(process.env.ISSUES_WITH_STAGES || '[]');
|
|
157
|
+
const doneCount = issues.filter(i => i.pipeline_stage === 'done' || i.pipeline_stage === 'pr-created').length;
|
|
158
|
+
printMilestoneProgress({
|
|
159
|
+
done: doneCount,
|
|
160
|
+
total: issues.length,
|
|
161
|
+
label: process.env.MILESTONE_NAME,
|
|
162
|
+
issues
|
|
163
|
+
});
|
|
164
|
+
" MILESTONE_NAME="$MILESTONE_NAME" ISSUES_WITH_STAGES="$ISSUES_WITH_STAGES"
|
|
165
|
+
```
|
|
144
166
|
</step>
|
|
145
167
|
|
|
146
168
|
<step name="resolve_execution_order">
|
|
@@ -417,6 +439,7 @@ FAILED_ISSUES=()
|
|
|
417
439
|
FAILED_ISSUES_WITH_CLASS=() # Entries: "issue_number:failure_class" for results display
|
|
418
440
|
BLOCKED_ISSUES=()
|
|
419
441
|
SKIPPED_ISSUES=()
|
|
442
|
+
LABEL_DRIFT_ISSUES=() # Issues where label reconciliation detected drift
|
|
420
443
|
ISSUES_RUN=0
|
|
421
444
|
```
|
|
422
445
|
|
|
@@ -562,6 +585,43 @@ COMMENTEOF
|
|
|
562
585
|
description="Run pipeline for #${ISSUE_NUMBER}"
|
|
563
586
|
)
|
|
564
587
|
|
|
588
|
+
# ── POST-WORK: Post-subagent label verification ──
|
|
589
|
+
# Read the pipeline_stage from the issue's active state file after Task() returns
|
|
590
|
+
ISSUE_STAGE=$(node -e "
|
|
591
|
+
const { loadActiveIssue } = require('./lib/state.cjs');
|
|
592
|
+
const state = loadActiveIssue(${ISSUE_NUMBER});
|
|
593
|
+
console.log((state && state.pipeline_stage) ? state.pipeline_stage : 'unknown');
|
|
594
|
+
" 2>/dev/null || echo "unknown")
|
|
595
|
+
|
|
596
|
+
# Determine the expected MGW label for this pipeline stage
|
|
597
|
+
EXPECTED_LABEL=$(python3 -c "
|
|
598
|
+
stage_to_label = {
|
|
599
|
+
'done': '',
|
|
600
|
+
'pr-created': '',
|
|
601
|
+
'verifying': 'mgw:in-progress',
|
|
602
|
+
'executing': 'mgw:in-progress',
|
|
603
|
+
'planning': 'mgw:in-progress',
|
|
604
|
+
'blocked': 'mgw:blocked',
|
|
605
|
+
'failed': '',
|
|
606
|
+
}
|
|
607
|
+
print(stage_to_label.get('${ISSUE_STAGE}', ''))
|
|
608
|
+
")
|
|
609
|
+
|
|
610
|
+
# Compare expected label against live GitHub labels
|
|
611
|
+
LIVE_LABELS=$(gh issue view ${ISSUE_NUMBER} --json labels --jq '[.labels[].name] | join(",")' 2>/dev/null || echo "")
|
|
612
|
+
|
|
613
|
+
if [ -n "$EXPECTED_LABEL" ] && ! echo "$LIVE_LABELS" | grep -q "$EXPECTED_LABEL"; then
|
|
614
|
+
echo "MGW WARNING: label drift on #${ISSUE_NUMBER} — expected $EXPECTED_LABEL, live: $LIVE_LABELS" >&2
|
|
615
|
+
LABEL_DRIFT="drift"
|
|
616
|
+
else
|
|
617
|
+
LABEL_DRIFT="ok"
|
|
618
|
+
fi
|
|
619
|
+
|
|
620
|
+
# Track drifted issues for milestone summary
|
|
621
|
+
if [ "$LABEL_DRIFT" = "drift" ]; then
|
|
622
|
+
LABEL_DRIFT_ISSUES+=("$ISSUE_NUMBER")
|
|
623
|
+
fi
|
|
624
|
+
|
|
565
625
|
# ── POST-WORK: Detect result and post completion comment ──
|
|
566
626
|
# Check if PR was created by looking for state file or PR
|
|
567
627
|
PR_NUMBER=$(gh pr list --head "issue/${ISSUE_NUMBER}-*" --json number -q '.[0].number' 2>/dev/null || echo "")
|
|
@@ -685,6 +745,27 @@ writeProjectState(state);
|
|
|
685
745
|
|
|
686
746
|
ISSUES_RUN=$((ISSUES_RUN + 1))
|
|
687
747
|
|
|
748
|
+
# Update and print milestone progress bar after each issue completes
|
|
749
|
+
ISSUES_WITH_STAGES=$(node -e "
|
|
750
|
+
const { loadProjectState, resolveActiveMilestoneIndex } = require('./lib/state.cjs');
|
|
751
|
+
const state = loadProjectState();
|
|
752
|
+
const idx = resolveActiveMilestoneIndex(state);
|
|
753
|
+
if (idx < 0) { console.log('[]'); process.exit(0); }
|
|
754
|
+
const issues = state.milestones[idx].issues || [];
|
|
755
|
+
console.log(JSON.stringify(issues.map(i => ({ number: i.github_number, pipeline_stage: i.pipeline_stage || 'new' }))));
|
|
756
|
+
" 2>/dev/null || echo "[]")
|
|
757
|
+
|
|
758
|
+
node -e "
|
|
759
|
+
const { printMilestoneProgress } = require('./lib/progress.cjs');
|
|
760
|
+
const issues = JSON.parse(process.env.ISSUES_WITH_STAGES || '[]');
|
|
761
|
+
const doneCount = issues.filter(i => i.pipeline_stage === 'done' || i.pipeline_stage === 'pr-created').length;
|
|
762
|
+
printMilestoneProgress({
|
|
763
|
+
done: doneCount,
|
|
764
|
+
total: issues.length,
|
|
765
|
+
issues
|
|
766
|
+
});
|
|
767
|
+
" ISSUES_WITH_STAGES="$ISSUES_WITH_STAGES"
|
|
768
|
+
|
|
688
769
|
# If --interactive: pause between issues
|
|
689
770
|
if [ "$INTERACTIVE" = true ]; then
|
|
690
771
|
AskUserQuestion(
|
|
@@ -712,18 +793,19 @@ Every comment posted during milestone orchestration includes:
|
|
|
712
793
|
<details>
|
|
713
794
|
<summary>Milestone Progress ({done}/{total} complete)</summary>
|
|
714
795
|
|
|
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 | — | — |
|
|
796
|
+
| # | Issue | Status | PR | Failure Class | Label Drift |
|
|
797
|
+
|---|-------|--------|----|---------------|-------------|
|
|
798
|
+
| N | title | ✓ Done | #PR | — | ok |
|
|
799
|
+
| M | title | ✗ Failed | — | `permanent` | — |
|
|
800
|
+
| K | title | ○ Pending | — | — | — |
|
|
801
|
+
| J | title | ◆ Running | — | — | ok |
|
|
802
|
+
| L | title | ⊘ Blocked | — | — | — |
|
|
722
803
|
|
|
723
804
|
</details>
|
|
724
805
|
```
|
|
725
806
|
|
|
726
807
|
The **Failure Class** column surfaces `last_failure_class` from the active issue state file.
|
|
808
|
+
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).
|
|
727
809
|
Values: `transient` (retried and exhausted), `permanent` (unrecoverable), `needs-info` (ambiguous issue), `unknown` (no state file or pre-retry issue), `—` (not failed).
|
|
728
810
|
</step>
|
|
729
811
|
|
|
@@ -1067,6 +1149,9 @@ gh issue comment ${FIRST_ISSUE_NUMBER} --body "$FINAL_RESULTS_COMMENT"
|
|
|
1067
1149
|
- [ ] Retry option calls resetRetryState() then re-invokes /mgw:run --retry for failed issues
|
|
1068
1150
|
- [ ] FAILED_ISSUES_WITH_CLASS tracks "number:class" for display in results table
|
|
1069
1151
|
- [ ] Progress table in every GitHub comment
|
|
1152
|
+
- [ ] Post-subagent label reconciliation run per issue after Task() returns
|
|
1153
|
+
- [ ] LABEL_DRIFT tracked per issue (ok/drift) and shown in progress table Label Drift column
|
|
1154
|
+
- [ ] Label drift issues logged as MGW WARNING to stderr
|
|
1070
1155
|
- [ ] Milestone close + draft release on full completion
|
|
1071
1156
|
- [ ] current_milestone pointer advanced on completion
|
|
1072
1157
|
- [ ] --interactive flag pauses between issues
|
package/commands/run.md
CHANGED
|
@@ -418,9 +418,7 @@ Add cross-ref (at `${REPO_ROOT}/.mgw/cross-refs.json`): issue → branch.
|
|
|
418
418
|
|
|
419
419
|
**Apply in-progress label:**
|
|
420
420
|
```bash
|
|
421
|
-
|
|
422
|
-
gh issue edit ${ISSUE_NUMBER} --remove-label "mgw:triaged" 2>/dev/null
|
|
423
|
-
gh issue edit ${ISSUE_NUMBER} --add-label "mgw:in-progress" 2>/dev/null
|
|
421
|
+
remove_mgw_labels_and_apply ${ISSUE_NUMBER} "mgw:in-progress"
|
|
424
422
|
```
|
|
425
423
|
|
|
426
424
|
**PATH CONVENTION for remaining steps:**
|
|
@@ -531,8 +529,7 @@ fi
|
|
|
531
529
|
|
|
532
530
|
**When blocking comment detected — apply label:**
|
|
533
531
|
```bash
|
|
534
|
-
|
|
535
|
-
gh issue edit ${ISSUE_NUMBER} --add-label "mgw:blocked" 2>/dev/null
|
|
532
|
+
remove_mgw_labels_and_apply ${ISSUE_NUMBER} "mgw:blocked"
|
|
536
533
|
```
|
|
537
534
|
|
|
538
535
|
If no new comments detected, continue normally.
|
|
@@ -949,8 +946,7 @@ PHASE_COUNT="TBD (determined by roadmapper)"
|
|
|
949
946
|
|
|
950
947
|
Set pipeline_stage to "discussing" and apply "mgw:discussing" label:
|
|
951
948
|
```bash
|
|
952
|
-
|
|
953
|
-
gh issue edit ${ISSUE_NUMBER} --add-label "mgw:discussing" 2>/dev/null
|
|
949
|
+
remove_mgw_labels_and_apply ${ISSUE_NUMBER} "mgw:discussing"
|
|
954
950
|
```
|
|
955
951
|
|
|
956
952
|
Present to user:
|
|
@@ -1461,9 +1457,38 @@ rmdir "${REPO_ROOT}/.worktrees/issue" 2>/dev/null
|
|
|
1461
1457
|
rmdir "${REPO_ROOT}/.worktrees" 2>/dev/null
|
|
1462
1458
|
```
|
|
1463
1459
|
|
|
1464
|
-
|
|
1460
|
+
Clear MGW labels at completion:
|
|
1465
1461
|
```bash
|
|
1466
|
-
|
|
1462
|
+
# Pass empty string — removes all mgw: labels without applying a new one
|
|
1463
|
+
remove_mgw_labels_and_apply ${ISSUE_NUMBER} ""
|
|
1464
|
+
```
|
|
1465
|
+
|
|
1466
|
+
Post-completion label reconciliation:
|
|
1467
|
+
```bash
|
|
1468
|
+
# Post-completion label reconciliation — verify no stray MGW labels remain
|
|
1469
|
+
LIVE_LABELS=$(gh issue view ${ISSUE_NUMBER} --json labels --jq '[.labels[].name]' 2>/dev/null || echo "[]")
|
|
1470
|
+
STRAY_MGW=$(echo "$LIVE_LABELS" | python3 -c "
|
|
1471
|
+
import json, sys
|
|
1472
|
+
labels = json.load(sys.stdin)
|
|
1473
|
+
stray = [l for l in labels if l.startswith('mgw:')]
|
|
1474
|
+
print('\n'.join(stray))
|
|
1475
|
+
" 2>/dev/null || echo "")
|
|
1476
|
+
|
|
1477
|
+
if [ -n "$STRAY_MGW" ]; then
|
|
1478
|
+
echo "MGW WARNING: unexpected MGW labels still on issue after completion: $STRAY_MGW" >&2
|
|
1479
|
+
fi
|
|
1480
|
+
|
|
1481
|
+
# Sync live labels back to .mgw/active state file
|
|
1482
|
+
LIVE_LABELS_LIST=$(gh issue view ${ISSUE_NUMBER} --json labels --jq '[.labels[].name]' 2>/dev/null || echo "[]")
|
|
1483
|
+
# Update labels field in ${REPO_ROOT}/.mgw/active/${STATE_FILE} using python3 json patch:
|
|
1484
|
+
python3 -c "
|
|
1485
|
+
import json, sys
|
|
1486
|
+
path = sys.argv[1]
|
|
1487
|
+
live = json.loads(sys.argv[2])
|
|
1488
|
+
with open(path) as f: state = json.load(f)
|
|
1489
|
+
state['labels'] = live
|
|
1490
|
+
with open(path, 'w') as f: json.dump(state, f, indent=2)
|
|
1491
|
+
" "${REPO_ROOT}/.mgw/active/${STATE_FILE}" "$LIVE_LABELS_LIST" 2>/dev/null || true
|
|
1467
1492
|
```
|
|
1468
1493
|
|
|
1469
1494
|
Extract one-liner summary for concise comment:
|
package/commands/status.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: mgw:status
|
|
3
3
|
description: Project status dashboard — milestone progress, issue pipeline stages, open PRs
|
|
4
|
-
argument-hint: "[milestone_number] [--json] [--board]"
|
|
4
|
+
argument-hint: "[milestone_number] [--json] [--board] [--watch [--interval N]]"
|
|
5
5
|
allowed-tools:
|
|
6
6
|
- Bash
|
|
7
7
|
- Read
|
|
@@ -13,6 +13,11 @@ pipeline stages, open PRs, and next milestone preview. Pure read-only — no sta
|
|
|
13
13
|
mutations, no agent spawns, no GitHub writes.
|
|
14
14
|
|
|
15
15
|
Falls back gracefully when no project.json exists (lists active issues only via GitHub API).
|
|
16
|
+
|
|
17
|
+
When `--watch` is passed, enters live-refresh mode: clears the terminal and redraws the
|
|
18
|
+
dashboard every N seconds (default 30). Displays a "Last refreshed" timestamp. User exits
|
|
19
|
+
by pressing 'q' or Ctrl+C. If both `--watch` and `--json` are supplied, print an error and
|
|
20
|
+
exit 1.
|
|
16
21
|
</objective>
|
|
17
22
|
|
|
18
23
|
<execution_context>
|
|
@@ -35,11 +40,21 @@ Repo detected via: gh repo view --json nameWithOwner -q .nameWithOwner
|
|
|
35
40
|
MILESTONE_NUM=""
|
|
36
41
|
JSON_OUTPUT=false
|
|
37
42
|
OPEN_BOARD=false
|
|
43
|
+
WATCH_MODE=false
|
|
44
|
+
WATCH_INTERVAL=30
|
|
45
|
+
NEXT_IS_INTERVAL=false
|
|
38
46
|
|
|
39
47
|
for ARG in $ARGUMENTS; do
|
|
48
|
+
if [ "$NEXT_IS_INTERVAL" = true ]; then
|
|
49
|
+
WATCH_INTERVAL="$ARG"
|
|
50
|
+
NEXT_IS_INTERVAL=false
|
|
51
|
+
continue
|
|
52
|
+
fi
|
|
40
53
|
case "$ARG" in
|
|
41
54
|
--json) JSON_OUTPUT=true ;;
|
|
42
55
|
--board) OPEN_BOARD=true ;;
|
|
56
|
+
--watch) WATCH_MODE=true ;;
|
|
57
|
+
--interval) NEXT_IS_INTERVAL=true ;;
|
|
43
58
|
[0-9]*) MILESTONE_NUM="$ARG" ;;
|
|
44
59
|
esac
|
|
45
60
|
done
|
|
@@ -418,6 +433,77 @@ Rendering rules:
|
|
|
418
433
|
- If no open PRs matched to milestone, show "No open PRs for this milestone."
|
|
419
434
|
- If no next milestone, show "No more milestones planned."
|
|
420
435
|
- If `TARGET_MILESTONE != CURRENT_MILESTONE`, add "(viewing milestone ${TARGET_MILESTONE})" to header
|
|
436
|
+
- In watch mode, append the footer: `[ Refreshing every ${WATCH_INTERVAL}s — last refreshed HH:MM:SS | press q to quit ]`
|
|
437
|
+
</step>
|
|
438
|
+
|
|
439
|
+
<step name="watch_mode">
|
|
440
|
+
**If --watch flag: enter live-refresh loop:**
|
|
441
|
+
|
|
442
|
+
`--watch` is incompatible with `--json`. If both are passed, print an error and exit 1.
|
|
443
|
+
|
|
444
|
+
The watch loop is implemented as a Node.js one-shot script executed via `node -e` (or saved
|
|
445
|
+
to a temp file). It wraps the full dashboard render cycle with `setInterval`, clears the
|
|
446
|
+
terminal before each redraw, and uses `process.stdin.setRawMode(true)` to detect a 'q'
|
|
447
|
+
keypress for clean exit.
|
|
448
|
+
|
|
449
|
+
```javascript
|
|
450
|
+
// watch-mode runner (pseudocode — shows the pattern)
|
|
451
|
+
const { execSync } = require('child_process');
|
|
452
|
+
const INTERVAL = parseInt(process.env.WATCH_INTERVAL || '30', 10) * 1000;
|
|
453
|
+
const REPO_ROOT = process.env.REPO_ROOT;
|
|
454
|
+
|
|
455
|
+
function renderDashboard() {
|
|
456
|
+
// Re-run all data collection and dashboard build steps synchronously
|
|
457
|
+
// (same logic as the non-watch single-shot path above, but called in a loop)
|
|
458
|
+
const output = buildDashboardOutput(); // all the python/gh calls assembled into a string
|
|
459
|
+
// Implementation note: buildDashboardOutput() is a pseudocode placeholder.
|
|
460
|
+
// The executor must refactor the full `display_dashboard` step into a reusable
|
|
461
|
+
// function that collects all GitHub and project.json data and returns the rendered
|
|
462
|
+
// dashboard string, then call it from both the single-shot path and the watch loop.
|
|
463
|
+
const now = new Date().toLocaleTimeString();
|
|
464
|
+
process.stdout.write('\x1B[2J\x1B[H'); // clear terminal, cursor home
|
|
465
|
+
process.stdout.write(output);
|
|
466
|
+
process.stdout.write(`\n[ Refreshing every ${INTERVAL / 1000}s — last refreshed ${now} | press q to quit ]\n`);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Initial render
|
|
470
|
+
renderDashboard();
|
|
471
|
+
|
|
472
|
+
// Poll on interval
|
|
473
|
+
const timer = setInterval(renderDashboard, INTERVAL);
|
|
474
|
+
|
|
475
|
+
// Detect 'q' to exit
|
|
476
|
+
if (process.stdin.isTTY) {
|
|
477
|
+
process.stdin.setRawMode(true);
|
|
478
|
+
process.stdin.resume();
|
|
479
|
+
process.stdin.setEncoding('utf8');
|
|
480
|
+
process.stdin.on('data', (key) => {
|
|
481
|
+
if (key === 'q' || key === '\u0003') { // 'q' or Ctrl+C
|
|
482
|
+
clearInterval(timer);
|
|
483
|
+
process.stdin.setRawMode(false);
|
|
484
|
+
process.stdout.write('\nWatch mode exited.\n');
|
|
485
|
+
process.exit(0);
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
Implementation notes for the executor:
|
|
492
|
+
- The watch runner re-executes the full data fetch on each interval tick (re-reads
|
|
493
|
+
project.json, re-calls `gh pr list`, etc.) so the display reflects live GitHub state.
|
|
494
|
+
- `process.stdout.write('\x1B[2J\x1B[H')` clears the screen without spawning `clear`.
|
|
495
|
+
- The footer line shows "last refreshed HH:MM:SS" only. No countdown timer.
|
|
496
|
+
- SIGINT (Ctrl+C) should also trigger clean exit — the `'\u0003'` check above handles it,
|
|
497
|
+
but also register `process.on('SIGINT', ...)` as a fallback when `setRawMode` is false
|
|
498
|
+
(non-TTY or piped stdin).
|
|
499
|
+
- If `--watch` and `--json` are both supplied, print the error message and exit 1 before
|
|
500
|
+
entering the watch loop.
|
|
501
|
+
|
|
502
|
+
If both flags are supplied, emit:
|
|
503
|
+
```
|
|
504
|
+
Error: --watch and --json cannot be used together.
|
|
505
|
+
```
|
|
506
|
+
and exit 1.
|
|
421
507
|
</step>
|
|
422
508
|
|
|
423
509
|
<step name="json_output">
|
|
@@ -523,4 +609,12 @@ The JSON structure:
|
|
|
523
609
|
- [ ] Velocity computed from .mgw/active/ and .mgw/completed/ file mtimes
|
|
524
610
|
- [ ] --json output includes board_url and milestone.health object
|
|
525
611
|
- [ ] Board URL line omitted when board_url is not set in project.json
|
|
612
|
+
- [ ] --watch flag enters live-refresh loop, refreshing every N seconds (default 30)
|
|
613
|
+
- [ ] --interval N overrides the default 30s refresh interval
|
|
614
|
+
- [ ] Watch mode clears terminal before each redraw
|
|
615
|
+
- [ ] Watch mode footer shows last refresh time
|
|
616
|
+
- [ ] 'q' keypress exits watch mode cleanly (stdin raw mode)
|
|
617
|
+
- [ ] Ctrl+C (SIGINT) exits watch mode cleanly
|
|
618
|
+
- [ ] --watch and --json are mutually exclusive — error + exit 1 if both supplied
|
|
619
|
+
- [ ] Watch mode re-fetches all data on each tick (live GitHub state)
|
|
526
620
|
</success_criteria>
|