@snipcodeit/mgw 0.2.2 → 0.3.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/README.md +55 -5
- package/commands/board/configure.md +205 -0
- package/commands/board/create.md +496 -0
- package/commands/board/show.md +221 -0
- package/commands/board/sync.md +417 -0
- package/commands/board/views.md +230 -0
- package/commands/board.md +23 -1543
- package/commands/milestone.md +5 -38
- package/commands/review.md +222 -42
- package/commands/run/execute.md +675 -0
- package/commands/run/pr-create.md +282 -0
- package/commands/run/triage.md +510 -0
- package/commands/run/worktree.md +54 -0
- package/commands/run.md +23 -1547
- package/commands/workflows/gsd.md +1 -13
- package/dist/bin/mgw.cjs +95 -6
- package/dist/{index-BiwU0uWA.cjs → index-s7v-ifd0.cjs} +669 -48
- package/dist/lib/index.cjs +185 -142
- package/package.json +5 -2
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mgw:run/triage
|
|
3
|
+
description: Validate input, load state, preflight comment check, and post triage update
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<step name="validate_and_load">
|
|
7
|
+
**Validate input and load state:**
|
|
8
|
+
|
|
9
|
+
Store repo root and default branch (used throughout):
|
|
10
|
+
```bash
|
|
11
|
+
REPO_ROOT=$(git rev-parse --show-toplevel)
|
|
12
|
+
DEFAULT=$(gh repo view --json defaultBranchRef -q .defaultBranchRef.name)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Parse $ARGUMENTS for issue number and flags. If issue number missing:
|
|
16
|
+
```
|
|
17
|
+
AskUserQuestion(
|
|
18
|
+
header: "Issue Number Required",
|
|
19
|
+
question: "Which issue number do you want to run the pipeline for?",
|
|
20
|
+
followUp: null
|
|
21
|
+
)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Extract flags from $ARGUMENTS:
|
|
25
|
+
```bash
|
|
26
|
+
RETRY_FLAG=false
|
|
27
|
+
for ARG in $ARGUMENTS; do
|
|
28
|
+
case "$ARG" in
|
|
29
|
+
--retry) RETRY_FLAG=true ;;
|
|
30
|
+
esac
|
|
31
|
+
done
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Check for existing state: `${REPO_ROOT}/.mgw/active/${ISSUE_NUMBER}-*.json`
|
|
35
|
+
|
|
36
|
+
If no state file exists → issue not triaged yet. Run triage inline:
|
|
37
|
+
- Inform user: "Issue #${ISSUE_NUMBER} hasn't been triaged. Running triage first."
|
|
38
|
+
- Execute the mgw:issue triage flow (steps from issue.md) inline.
|
|
39
|
+
- After triage, reload state file.
|
|
40
|
+
|
|
41
|
+
If state file exists → load it. **Run migrateProjectState() to ensure retry fields exist:**
|
|
42
|
+
```bash
|
|
43
|
+
node -e "
|
|
44
|
+
const { migrateProjectState } = require('./lib/state.cjs');
|
|
45
|
+
migrateProjectState();
|
|
46
|
+
" 2>/dev/null || true
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Check pipeline_stage:
|
|
50
|
+
- "triaged" → proceed to GSD execution
|
|
51
|
+
- "planning" / "executing" → resume from where we left off
|
|
52
|
+
- "blocked" → "Pipeline for #${ISSUE_NUMBER} is blocked by a stakeholder comment. Review the issue comments, resolve the blocker, then re-run."
|
|
53
|
+
- "pr-created" / "done" → "Pipeline already completed for #${ISSUE_NUMBER}. Run /mgw:sync to reconcile."
|
|
54
|
+
- "failed" → Check for --retry flag:
|
|
55
|
+
- If --retry NOT present:
|
|
56
|
+
```
|
|
57
|
+
Pipeline for #${ISSUE_NUMBER} has failed (failure class: ${last_failure_class || "unknown"}).
|
|
58
|
+
dead_letter: ${dead_letter}
|
|
59
|
+
|
|
60
|
+
To retry: /mgw:run ${ISSUE_NUMBER} --retry
|
|
61
|
+
To inspect: /mgw:issue ${ISSUE_NUMBER}
|
|
62
|
+
```
|
|
63
|
+
STOP.
|
|
64
|
+
- If --retry present and dead_letter === true:
|
|
65
|
+
```bash
|
|
66
|
+
# Clear dead_letter and reset retry state via resetRetryState()
|
|
67
|
+
node -e "
|
|
68
|
+
const { loadActiveIssue } = require('./lib/state.cjs');
|
|
69
|
+
const { resetRetryState } = require('./lib/retry.cjs');
|
|
70
|
+
const fs = require('fs'), path = require('path');
|
|
71
|
+
const activeDir = path.join(process.cwd(), '.mgw', 'active');
|
|
72
|
+
const files = fs.readdirSync(activeDir);
|
|
73
|
+
const file = files.find(f => f.startsWith('${ISSUE_NUMBER}-') && f.endsWith('.json'));
|
|
74
|
+
if (!file) { console.error('No state file for #${ISSUE_NUMBER}'); process.exit(1); }
|
|
75
|
+
const filePath = path.join(activeDir, file);
|
|
76
|
+
const state = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
77
|
+
const reset = resetRetryState(state);
|
|
78
|
+
reset.pipeline_stage = 'triaged';
|
|
79
|
+
fs.writeFileSync(filePath, JSON.stringify(reset, null, 2));
|
|
80
|
+
console.log('Retry state cleared for #${ISSUE_NUMBER}');
|
|
81
|
+
"
|
|
82
|
+
# Remove pipeline-failed label
|
|
83
|
+
gh issue edit ${ISSUE_NUMBER} --remove-label "pipeline-failed" 2>/dev/null || true
|
|
84
|
+
```
|
|
85
|
+
Log: "MGW: dead_letter cleared for #${ISSUE_NUMBER} via --retry flag. Re-queuing."
|
|
86
|
+
Continue pipeline (treat as triaged).
|
|
87
|
+
- If --retry present and dead_letter !== true (manual retry of non-dead-lettered failure):
|
|
88
|
+
```bash
|
|
89
|
+
node -e "
|
|
90
|
+
const { resetRetryState } = require('./lib/retry.cjs');
|
|
91
|
+
const fs = require('fs'), path = require('path');
|
|
92
|
+
const activeDir = path.join(process.cwd(), '.mgw', 'active');
|
|
93
|
+
const files = fs.readdirSync(activeDir);
|
|
94
|
+
const file = files.find(f => f.startsWith('${ISSUE_NUMBER}-') && f.endsWith('.json'));
|
|
95
|
+
if (!file) { console.error('No state file'); process.exit(1); }
|
|
96
|
+
const filePath = path.join(activeDir, file);
|
|
97
|
+
const state = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
98
|
+
const reset = resetRetryState(state);
|
|
99
|
+
reset.pipeline_stage = 'triaged';
|
|
100
|
+
fs.writeFileSync(filePath, JSON.stringify(reset, null, 2));
|
|
101
|
+
console.log('Retry state reset for #${ISSUE_NUMBER}');
|
|
102
|
+
"
|
|
103
|
+
gh issue edit ${ISSUE_NUMBER} --remove-label "pipeline-failed" 2>/dev/null || true
|
|
104
|
+
```
|
|
105
|
+
Continue pipeline.
|
|
106
|
+
- "needs-info" → Check for --force flag in $ARGUMENTS:
|
|
107
|
+
If --force NOT present:
|
|
108
|
+
```
|
|
109
|
+
Pipeline for #${ISSUE_NUMBER} is blocked by triage gate (needs-info).
|
|
110
|
+
The issue requires more detail before execution can begin.
|
|
111
|
+
|
|
112
|
+
To override: /mgw:run ${ISSUE_NUMBER} --force
|
|
113
|
+
To review: /mgw:issue ${ISSUE_NUMBER} (re-triage after updating the issue)
|
|
114
|
+
```
|
|
115
|
+
STOP.
|
|
116
|
+
If --force present:
|
|
117
|
+
Log warning: "MGW: WARNING — Overriding needs-info gate for #${ISSUE_NUMBER}. Proceeding with --force."
|
|
118
|
+
Update state: pipeline_stage = "triaged", add override_log entry.
|
|
119
|
+
Continue pipeline.
|
|
120
|
+
- "needs-security-review" → Check for --security-ack flag in $ARGUMENTS:
|
|
121
|
+
If --security-ack NOT present:
|
|
122
|
+
```
|
|
123
|
+
Pipeline for #${ISSUE_NUMBER} requires security review.
|
|
124
|
+
This issue was flagged as high security risk during triage.
|
|
125
|
+
|
|
126
|
+
To acknowledge and proceed: /mgw:run ${ISSUE_NUMBER} --security-ack
|
|
127
|
+
To review: /mgw:issue ${ISSUE_NUMBER} (re-triage)
|
|
128
|
+
```
|
|
129
|
+
STOP.
|
|
130
|
+
If --security-ack present:
|
|
131
|
+
Log warning: "MGW: WARNING — Acknowledging security risk for #${ISSUE_NUMBER}. Proceeding with --security-ack."
|
|
132
|
+
Update state: pipeline_stage = "triaged", add override_log entry.
|
|
133
|
+
Continue pipeline.
|
|
134
|
+
|
|
135
|
+
**Route selection via gsd-adapter (runs after loading issue state):**
|
|
136
|
+
|
|
137
|
+
Use `selectGsdRoute()` from `lib/gsd-adapter.cjs` to determine the GSD execution
|
|
138
|
+
path. This centralizes the routing decision so it is auditable and consistent
|
|
139
|
+
across all pipeline commands:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
GSD_ROUTE=$(node -e "
|
|
143
|
+
const { selectGsdRoute } = require('./lib/gsd-adapter.cjs');
|
|
144
|
+
const issue = $(cat ${REPO_ROOT}/.mgw/active/${STATE_FILE});
|
|
145
|
+
const { loadProjectState } = require('./lib/state.cjs');
|
|
146
|
+
const projectState = loadProjectState() || {};
|
|
147
|
+
const route = selectGsdRoute(issue, projectState);
|
|
148
|
+
console.log(route);
|
|
149
|
+
")
|
|
150
|
+
# GSD_ROUTE is one of: quick | plan-phase | diagnose | execute-only | verify-only
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**Cross-milestone detection (runs after loading issue state):**
|
|
154
|
+
|
|
155
|
+
Check if this issue belongs to a non-active GSD milestone:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
CROSS_MILESTONE_WARN=$(node -e "
|
|
159
|
+
const { loadProjectState, resolveActiveMilestoneIndex } = require('./lib/state.cjs');
|
|
160
|
+
const state = loadProjectState();
|
|
161
|
+
if (!state) { console.log('none'); process.exit(0); }
|
|
162
|
+
|
|
163
|
+
const activeGsdId = state.active_gsd_milestone;
|
|
164
|
+
|
|
165
|
+
// Find this issue's milestone in project.json
|
|
166
|
+
const issueNum = ${ISSUE_NUMBER};
|
|
167
|
+
let issueMilestone = null;
|
|
168
|
+
for (const m of (state.milestones || [])) {
|
|
169
|
+
if ((m.issues || []).some(i => i.github_number === issueNum)) {
|
|
170
|
+
issueMilestone = m;
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (!issueMilestone) { console.log('none'); process.exit(0); }
|
|
176
|
+
|
|
177
|
+
const issueGsdId = issueMilestone.gsd_milestone_id;
|
|
178
|
+
|
|
179
|
+
// No active_gsd_milestone set (legacy schema): no warning
|
|
180
|
+
if (!activeGsdId) { console.log('none'); process.exit(0); }
|
|
181
|
+
|
|
182
|
+
// Issue is in the active milestone: no warning
|
|
183
|
+
if (issueGsdId === activeGsdId) { console.log('none'); process.exit(0); }
|
|
184
|
+
|
|
185
|
+
// Issue is in a different milestone
|
|
186
|
+
const gsdRoute = '${GSD_ROUTE}';
|
|
187
|
+
if (gsdRoute === 'quick' || gsdRoute === 'gsd:quick') {
|
|
188
|
+
console.log('isolation:' + issueMilestone.name + ':' + (issueGsdId || 'unlinked'));
|
|
189
|
+
} else {
|
|
190
|
+
console.log('warn:' + issueMilestone.name + ':' + (issueGsdId || 'unlinked') + ':' + activeGsdId);
|
|
191
|
+
}
|
|
192
|
+
")
|
|
193
|
+
|
|
194
|
+
case "$CROSS_MILESTONE_WARN" in
|
|
195
|
+
none)
|
|
196
|
+
# No cross-milestone issue — proceed normally
|
|
197
|
+
;;
|
|
198
|
+
isolation:*)
|
|
199
|
+
MILESTONE_NAME=$(echo "$CROSS_MILESTONE_WARN" | cut -d':' -f2)
|
|
200
|
+
GSD_ID=$(echo "$CROSS_MILESTONE_WARN" | cut -d':' -f3)
|
|
201
|
+
|
|
202
|
+
# Re-validate route against live GitHub labels (project.json may be stale from triage time)
|
|
203
|
+
LIVE_LABELS=$(gh issue view ${ISSUE_NUMBER} --json labels --jq '[.labels[].name] | join(",")' 2>/dev/null || echo "")
|
|
204
|
+
QUICK_CONFIRMED=false
|
|
205
|
+
if echo "$LIVE_LABELS" | grep -qiE "gsd-route:quick|gsd:quick|quick"; then
|
|
206
|
+
QUICK_CONFIRMED=true
|
|
207
|
+
fi
|
|
208
|
+
|
|
209
|
+
if [ "$QUICK_CONFIRMED" = "true" ]; then
|
|
210
|
+
echo ""
|
|
211
|
+
echo "NOTE: Issue #${ISSUE_NUMBER} belongs to milestone '${MILESTONE_NAME}' (GSD: ${GSD_ID})"
|
|
212
|
+
echo " Confirmed gsd:quick via live labels — running in isolation."
|
|
213
|
+
echo ""
|
|
214
|
+
else
|
|
215
|
+
# Route mismatch: project.json says quick but labels don't confirm it
|
|
216
|
+
echo ""
|
|
217
|
+
echo "⚠️ Route mismatch for cross-milestone issue #${ISSUE_NUMBER}:"
|
|
218
|
+
echo " project.json route: quick (set at triage time)"
|
|
219
|
+
echo " Live GitHub labels: ${LIVE_LABELS:-none}"
|
|
220
|
+
echo " Labels do not confirm gsd:quick — treating as plan-phase (requires milestone context)."
|
|
221
|
+
echo ""
|
|
222
|
+
echo "Options:"
|
|
223
|
+
echo " 1) Switch active milestone to '${GSD_ID}' and continue"
|
|
224
|
+
echo " 2) Re-triage this issue (/mgw:issue ${ISSUE_NUMBER}) to update its route"
|
|
225
|
+
echo " 3) Abort"
|
|
226
|
+
echo ""
|
|
227
|
+
read -p "Choice [1/2/3]: " ROUTE_MISMATCH_CHOICE
|
|
228
|
+
case "$ROUTE_MISMATCH_CHOICE" in
|
|
229
|
+
1)
|
|
230
|
+
node -e "
|
|
231
|
+
const { loadProjectState, writeProjectState } = require('./lib/state.cjs');
|
|
232
|
+
const state = loadProjectState();
|
|
233
|
+
state.active_gsd_milestone = '${GSD_ID}';
|
|
234
|
+
writeProjectState(state);
|
|
235
|
+
console.log('Switched active_gsd_milestone to: ${GSD_ID}');
|
|
236
|
+
"
|
|
237
|
+
# Validate ROADMAP.md matches (same check as option 1 in warn case)
|
|
238
|
+
ROADMAP_VALID=$(python3 -c "
|
|
239
|
+
import os
|
|
240
|
+
if not os.path.exists('.planning/ROADMAP.md'):
|
|
241
|
+
print('missing')
|
|
242
|
+
else:
|
|
243
|
+
with open('.planning/ROADMAP.md') as f:
|
|
244
|
+
content = f.read()
|
|
245
|
+
print('match' if '${GSD_ID}' in content else 'mismatch')
|
|
246
|
+
" 2>/dev/null || echo "missing")
|
|
247
|
+
if [ "$ROADMAP_VALID" != "match" ]; then
|
|
248
|
+
node -e "
|
|
249
|
+
const { loadProjectState, writeProjectState } = require('./lib/state.cjs');
|
|
250
|
+
const state = loadProjectState();
|
|
251
|
+
state.active_gsd_milestone = '$(echo "$CROSS_MILESTONE_WARN" | cut -d':' -f4)';
|
|
252
|
+
writeProjectState(state);
|
|
253
|
+
" 2>/dev/null || true
|
|
254
|
+
echo "Switch rolled back — ROADMAP.md does not match '${GSD_ID}'."
|
|
255
|
+
echo "Run /gsd:new-milestone to update ROADMAP.md first."
|
|
256
|
+
exit 0
|
|
257
|
+
fi
|
|
258
|
+
;;
|
|
259
|
+
2)
|
|
260
|
+
echo "Re-triage with: /mgw:issue ${ISSUE_NUMBER}"
|
|
261
|
+
exit 0
|
|
262
|
+
;;
|
|
263
|
+
*)
|
|
264
|
+
echo "Aborted."
|
|
265
|
+
exit 0
|
|
266
|
+
;;
|
|
267
|
+
esac
|
|
268
|
+
fi
|
|
269
|
+
;;
|
|
270
|
+
warn:*)
|
|
271
|
+
ISSUE_MILESTONE=$(echo "$CROSS_MILESTONE_WARN" | cut -d':' -f2)
|
|
272
|
+
ISSUE_GSD=$(echo "$CROSS_MILESTONE_WARN" | cut -d':' -f3)
|
|
273
|
+
ACTIVE_GSD=$(echo "$CROSS_MILESTONE_WARN" | cut -d':' -f4)
|
|
274
|
+
echo ""
|
|
275
|
+
echo "⚠️ Cross-milestone issue detected:"
|
|
276
|
+
echo " Issue #${ISSUE_NUMBER} belongs to: '${ISSUE_MILESTONE}' (GSD: ${ISSUE_GSD})"
|
|
277
|
+
echo " Active GSD milestone: ${ACTIVE_GSD}"
|
|
278
|
+
echo ""
|
|
279
|
+
echo "This issue requires plan-phase work that depends on ROADMAP.md context."
|
|
280
|
+
echo "Running it against the wrong active milestone may produce incorrect plans."
|
|
281
|
+
echo ""
|
|
282
|
+
echo "Options:"
|
|
283
|
+
echo " 1) Switch active milestone to '${ISSUE_GSD}' and continue"
|
|
284
|
+
echo " 2) Continue anyway (not recommended)"
|
|
285
|
+
echo " 3) Abort — run /gsd:new-milestone to set up the correct milestone first"
|
|
286
|
+
echo ""
|
|
287
|
+
read -p "Choice [1/2/3]: " MILESTONE_CHOICE
|
|
288
|
+
case "$MILESTONE_CHOICE" in
|
|
289
|
+
1)
|
|
290
|
+
node -e "
|
|
291
|
+
const { loadProjectState, writeProjectState } = require('./lib/state.cjs');
|
|
292
|
+
const state = loadProjectState();
|
|
293
|
+
state.active_gsd_milestone = '${ISSUE_GSD}';
|
|
294
|
+
writeProjectState(state);
|
|
295
|
+
console.log('Switched active_gsd_milestone to: ${ISSUE_GSD}');
|
|
296
|
+
"
|
|
297
|
+
# Validate ROADMAP.md matches the new active milestone
|
|
298
|
+
ROADMAP_VALID=$(python3 -c "
|
|
299
|
+
import os
|
|
300
|
+
if not os.path.exists('.planning/ROADMAP.md'):
|
|
301
|
+
print('missing')
|
|
302
|
+
else:
|
|
303
|
+
with open('.planning/ROADMAP.md') as f:
|
|
304
|
+
content = f.read()
|
|
305
|
+
print('match' if '${ISSUE_GSD}' in content else 'mismatch')
|
|
306
|
+
" 2>/dev/null || echo "missing")
|
|
307
|
+
if [ "$ROADMAP_VALID" = "match" ]; then
|
|
308
|
+
echo "Active milestone updated. ROADMAP.md confirmed for '${ISSUE_GSD}'."
|
|
309
|
+
else
|
|
310
|
+
# Roll back — ROADMAP.md doesn't match
|
|
311
|
+
node -e "
|
|
312
|
+
const { loadProjectState, writeProjectState } = require('./lib/state.cjs');
|
|
313
|
+
const state = loadProjectState();
|
|
314
|
+
state.active_gsd_milestone = '${ACTIVE_GSD}';
|
|
315
|
+
writeProjectState(state);
|
|
316
|
+
" 2>/dev/null || true
|
|
317
|
+
echo "Switch rolled back — ROADMAP.md does not match '${ISSUE_GSD}'."
|
|
318
|
+
echo "Run /gsd:new-milestone to update ROADMAP.md first."
|
|
319
|
+
exit 0
|
|
320
|
+
fi
|
|
321
|
+
;;
|
|
322
|
+
2)
|
|
323
|
+
echo "Proceeding with cross-milestone issue (may affect plan quality)."
|
|
324
|
+
;;
|
|
325
|
+
*)
|
|
326
|
+
echo "Aborted. Run /gsd:new-milestone then /mgw:project to align milestones."
|
|
327
|
+
exit 0
|
|
328
|
+
;;
|
|
329
|
+
esac
|
|
330
|
+
;;
|
|
331
|
+
esac
|
|
332
|
+
```
|
|
333
|
+
</step>
|
|
334
|
+
|
|
335
|
+
<step name="preflight_comment_check">
|
|
336
|
+
**Pre-flight comment check — detect new comments since triage:**
|
|
337
|
+
|
|
338
|
+
Before GSD execution begins, check if new comments have been posted on the issue
|
|
339
|
+
since triage. This prevents executing against a stale plan when stakeholders have
|
|
340
|
+
posted material changes, blockers, or scope updates.
|
|
341
|
+
|
|
342
|
+
```bash
|
|
343
|
+
# Fetch current comment count from GitHub
|
|
344
|
+
CURRENT_COMMENTS=$(gh issue view $ISSUE_NUMBER --json comments --jq '.comments | length' 2>/dev/null || echo "0")
|
|
345
|
+
STORED_COMMENTS="${triage.last_comment_count}" # From state file
|
|
346
|
+
|
|
347
|
+
# If stored count is missing (pre-comment-tracking state), skip check
|
|
348
|
+
if [ -z "$STORED_COMMENTS" ] || [ "$STORED_COMMENTS" = "null" ] || [ "$STORED_COMMENTS" = "0" ]; then
|
|
349
|
+
STORED_COMMENTS=0
|
|
350
|
+
fi
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
If new comments detected (`CURRENT_COMMENTS > STORED_COMMENTS`):
|
|
354
|
+
|
|
355
|
+
1. **Fetch new comment bodies:**
|
|
356
|
+
```bash
|
|
357
|
+
NEW_COUNT=$(($CURRENT_COMMENTS - $STORED_COMMENTS))
|
|
358
|
+
NEW_COMMENTS=$(gh issue view $ISSUE_NUMBER --json comments \
|
|
359
|
+
--jq "[.comments[-${NEW_COUNT}:]] | .[] | {author: .author.login, body: .body, createdAt: .createdAt}" 2>/dev/null)
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
2. **Spawn classification agent:**
|
|
363
|
+
```
|
|
364
|
+
Task(
|
|
365
|
+
prompt="
|
|
366
|
+
<files_to_read>
|
|
367
|
+
- ./CLAUDE.md (Project instructions — if exists, follow all guidelines)
|
|
368
|
+
</files_to_read>
|
|
369
|
+
|
|
370
|
+
Classify new comments on GitHub issue #${ISSUE_NUMBER}.
|
|
371
|
+
|
|
372
|
+
<issue_context>
|
|
373
|
+
Title: ${issue_title}
|
|
374
|
+
Current pipeline stage: ${pipeline_stage}
|
|
375
|
+
GSD Route: ${gsd_route}
|
|
376
|
+
Triage scope: ${triage.scope}
|
|
377
|
+
</issue_context>
|
|
378
|
+
|
|
379
|
+
<new_comments>
|
|
380
|
+
${NEW_COMMENTS}
|
|
381
|
+
</new_comments>
|
|
382
|
+
|
|
383
|
+
<classification_rules>
|
|
384
|
+
Classify each comment (and the overall batch) into ONE of:
|
|
385
|
+
|
|
386
|
+
- **material** — Comment changes scope, requirements, acceptance criteria, or design.
|
|
387
|
+
Examples: 'Actually we also need to handle X', 'Changed the requirement to Y',
|
|
388
|
+
'Don't forget about edge case Z'.
|
|
389
|
+
|
|
390
|
+
- **informational** — Status update, acknowledgment, question that doesn't change scope, +1.
|
|
391
|
+
Examples: 'Looks good', 'Thanks for picking this up', 'What's the ETA?', '+1'.
|
|
392
|
+
|
|
393
|
+
- **blocking** — Explicit instruction to stop or wait. Must contain clear hold language.
|
|
394
|
+
Examples: 'Don't work on this yet', 'Hold off', 'Blocked by external dependency',
|
|
395
|
+
'Wait for design review'.
|
|
396
|
+
|
|
397
|
+
If ANY comment in the batch is blocking, overall classification is blocking.
|
|
398
|
+
If ANY comment is material (and none blocking), overall classification is material.
|
|
399
|
+
Otherwise, informational.
|
|
400
|
+
</classification_rules>
|
|
401
|
+
|
|
402
|
+
<output_format>
|
|
403
|
+
Return ONLY valid JSON:
|
|
404
|
+
{
|
|
405
|
+
\"classification\": \"material|informational|blocking\",
|
|
406
|
+
\"reasoning\": \"Brief explanation of why this classification was chosen\",
|
|
407
|
+
\"new_requirements\": [\"list of new requirements if material, empty array otherwise\"],
|
|
408
|
+
\"blocking_reason\": \"reason if blocking, empty string otherwise\"
|
|
409
|
+
}
|
|
410
|
+
</output_format>
|
|
411
|
+
",
|
|
412
|
+
subagent_type="general-purpose",
|
|
413
|
+
description="Classify comments on #${ISSUE_NUMBER}"
|
|
414
|
+
)
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
3. **React based on classification:**
|
|
418
|
+
|
|
419
|
+
| Classification | Action |
|
|
420
|
+
|---------------|--------|
|
|
421
|
+
| **informational** | Log: "MGW: ${NEW_COUNT} new comment(s) reviewed — informational, continuing." Update `triage.last_comment_count` in state file. Continue pipeline. |
|
|
422
|
+
| **material** | Log: "MGW: Material comment(s) detected — scope may have changed." Update state: add new_requirements to triage context. Update `triage.last_comment_count`. Re-read issue body for updated requirements. Continue with enriched context (pass new_requirements to planner). Check for security keywords in material comments (see below). |
|
|
423
|
+
| **blocking** | Log: "MGW: Blocking comment detected — pipeline paused." Update state: `pipeline_stage = "blocked"`. Apply mgw:blocked label. Post comment on issue: `> **MGW** . \`pipeline-blocked\` . Blocked by stakeholder comment. Reason: ${blocking_reason}`. Stop pipeline execution. |
|
|
424
|
+
|
|
425
|
+
**Security keyword check for material comments:**
|
|
426
|
+
```bash
|
|
427
|
+
SECURITY_KEYWORDS="security|vulnerability|CVE|exploit|injection|XSS|CSRF|auth bypass"
|
|
428
|
+
if echo "$NEW_COMMENTS" | grep -qiE "$SECURITY_KEYWORDS"; then
|
|
429
|
+
# Add warning to gate_result and prompt user
|
|
430
|
+
echo "MGW: Security-related comment detected. Re-triage recommended."
|
|
431
|
+
# Prompt: "Security-related comment detected. Re-triage recommended. Continue or re-triage?"
|
|
432
|
+
fi
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
**When blocking comment detected — apply label:**
|
|
436
|
+
```bash
|
|
437
|
+
remove_mgw_labels_and_apply ${ISSUE_NUMBER} "mgw:blocked"
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
If no new comments detected, continue normally.
|
|
441
|
+
</step>
|
|
442
|
+
|
|
443
|
+
<step name="post_triage_update">
|
|
444
|
+
**Post work-starting comment on issue:**
|
|
445
|
+
|
|
446
|
+
Note: The triage gate evaluation and triage-complete/triage-blocked comment are now
|
|
447
|
+
posted IMMEDIATELY during /mgw:issue. This step posts a separate work-starting
|
|
448
|
+
notification when pipeline execution actually begins in run.md.
|
|
449
|
+
|
|
450
|
+
Gather enrichment data from triage state:
|
|
451
|
+
```bash
|
|
452
|
+
SCOPE_SIZE="${triage.scope.size}" # small|medium|large
|
|
453
|
+
FILE_COUNT="${triage.scope.file_count}"
|
|
454
|
+
SYSTEM_LIST="${triage.scope.systems}"
|
|
455
|
+
FILE_LIST="${triage.scope.files}"
|
|
456
|
+
CONFLICTS="${triage.conflicts}"
|
|
457
|
+
ROUTE_REASONING="${triage.route_reasoning}"
|
|
458
|
+
TIMESTAMP=$(node -e "try{process.stdout.write(require('./lib/gsd-adapter.cjs').getTimestamp())}catch(e){process.stdout.write(new Date().toISOString().replace(/\\.\\d{3}Z$/,'Z'))}")
|
|
459
|
+
|
|
460
|
+
# Load milestone/phase context from project.json if available
|
|
461
|
+
MILESTONE_CONTEXT=""
|
|
462
|
+
if [ -f "${REPO_ROOT}/.mgw/project.json" ]; then
|
|
463
|
+
MILESTONE_CONTEXT=$(node -e "
|
|
464
|
+
const { loadProjectState, resolveActiveMilestoneIndex } = require('./lib/state.cjs');
|
|
465
|
+
const state = loadProjectState();
|
|
466
|
+
if (!state) process.exit(0);
|
|
467
|
+
// Search all milestones for the issue (not just active) to handle cross-milestone lookups
|
|
468
|
+
for (const m of (state.milestones || [])) {
|
|
469
|
+
for (const i of (m.issues || [])) {
|
|
470
|
+
if (i.github_number === ${ISSUE_NUMBER}) {
|
|
471
|
+
console.log('Milestone: ' + m.name + ' | Phase ' + i.phase_number + ': ' + i.phase_name);
|
|
472
|
+
process.exit(0);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
" 2>/dev/null || echo "")
|
|
477
|
+
fi
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
Post the work-starting comment directly (no sub-agent — guarantees it happens):
|
|
481
|
+
|
|
482
|
+
```bash
|
|
483
|
+
WORK_STARTING_BODY=$(cat <<COMMENTEOF
|
|
484
|
+
> **MGW** · \`work-starting\` · ${TIMESTAMP}
|
|
485
|
+
> ${MILESTONE_CONTEXT}
|
|
486
|
+
|
|
487
|
+
### Work Starting
|
|
488
|
+
|
|
489
|
+
| | |
|
|
490
|
+
|---|---|
|
|
491
|
+
| **Route** | \`${gsd_route}\` — ${ROUTE_REASONING} |
|
|
492
|
+
| **Scope** | ${SCOPE_SIZE} — ${FILE_COUNT} files across ${SYSTEM_LIST} |
|
|
493
|
+
| **Conflicts** | ${CONFLICTS} |
|
|
494
|
+
|
|
495
|
+
Work begins on branch \`${BRANCH_NAME}\`.
|
|
496
|
+
|
|
497
|
+
<details>
|
|
498
|
+
<summary>Affected Files</summary>
|
|
499
|
+
|
|
500
|
+
${FILE_LIST as bullet points}
|
|
501
|
+
|
|
502
|
+
</details>
|
|
503
|
+
COMMENTEOF
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
gh issue comment ${ISSUE_NUMBER} --body "$WORK_STARTING_BODY" 2>/dev/null || true
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
Log comment in state file (at `${REPO_ROOT}/.mgw/active/`).
|
|
510
|
+
</step>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mgw:run/worktree
|
|
3
|
+
description: Create isolated worktree for issue work
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<step name="create_worktree">
|
|
7
|
+
**Create isolated worktree for issue work:**
|
|
8
|
+
|
|
9
|
+
Derive branch and worktree path:
|
|
10
|
+
```bash
|
|
11
|
+
BRANCH_NAME="issue/${ISSUE_NUMBER}-${slug}"
|
|
12
|
+
WORKTREE_DIR="${REPO_ROOT}/.worktrees/${BRANCH_NAME}"
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Ensure .worktrees/ is gitignored:
|
|
16
|
+
```bash
|
|
17
|
+
mkdir -p "$(dirname "${WORKTREE_DIR}")"
|
|
18
|
+
if ! git check-ignore -q .worktrees 2>/dev/null; then
|
|
19
|
+
echo ".worktrees/" >> "${REPO_ROOT}/.gitignore"
|
|
20
|
+
fi
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Create worktree with feature branch:
|
|
24
|
+
```bash
|
|
25
|
+
# If worktree already exists (resume in same session), skip creation
|
|
26
|
+
if [ -d "${WORKTREE_DIR}" ]; then
|
|
27
|
+
echo "Worktree exists, reusing"
|
|
28
|
+
# If branch already exists (resume from prior session)
|
|
29
|
+
elif git show-ref --verify --quiet "refs/heads/${BRANCH_NAME}"; then
|
|
30
|
+
git worktree add "${WORKTREE_DIR}" "${BRANCH_NAME}"
|
|
31
|
+
# New branch (first run)
|
|
32
|
+
else
|
|
33
|
+
git worktree add "${WORKTREE_DIR}" -b "${BRANCH_NAME}"
|
|
34
|
+
fi
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Switch working directory to worktree:**
|
|
38
|
+
```bash
|
|
39
|
+
cd "${WORKTREE_DIR}"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Update state (at `${REPO_ROOT}/.mgw/active/`): add branch to linked_branches.
|
|
43
|
+
Add cross-ref (at `${REPO_ROOT}/.mgw/cross-refs.json`): issue → branch.
|
|
44
|
+
|
|
45
|
+
**Apply in-progress label:**
|
|
46
|
+
```bash
|
|
47
|
+
remove_mgw_labels_and_apply ${ISSUE_NUMBER} "mgw:in-progress"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**PATH CONVENTION for remaining steps:**
|
|
51
|
+
- File operations, git commands, and agent work use **relative paths** (CWD = worktree)
|
|
52
|
+
- `.mgw/` state operations use **absolute paths**: `${REPO_ROOT}/.mgw/`
|
|
53
|
+
(`.mgw/` is gitignored — it only exists in the main repo, not the worktree)
|
|
54
|
+
</step>
|