@snipcodeit/mgw 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,469 @@
1
+ ---
2
+ name: mgw:issue
3
+ description: Triage a GitHub issue — analyze against codebase, validate scope/security, recommend GSD route
4
+ argument-hint: "<issue-number>"
5
+ allowed-tools:
6
+ - Bash
7
+ - Read
8
+ - Write
9
+ - Edit
10
+ - Glob
11
+ - Grep
12
+ - Task
13
+ - AskUserQuestion
14
+ ---
15
+
16
+ <objective>
17
+ Deep analysis of a single GitHub issue against the codebase. Fetches the issue,
18
+ assigns to self if needed, spawns a task agent for full analysis (scope, validity,
19
+ purpose, security, conflicts), presents a triage report, and recommends a GSD route.
20
+
21
+ Creates .mgw/ state file for the issue. Optionally routes to /mgw:run.
22
+ </objective>
23
+
24
+ <execution_context>
25
+ @~/.claude/commands/mgw/workflows/state.md
26
+ @~/.claude/commands/mgw/workflows/github.md
27
+ @~/.claude/commands/mgw/workflows/gsd.md
28
+ @~/.claude/commands/mgw/workflows/validation.md
29
+ </execution_context>
30
+
31
+ <context>
32
+ Issue number: $ARGUMENTS
33
+
34
+ Active issues context: .mgw/active/ (check for conflicts)
35
+ </context>
36
+
37
+ <process>
38
+
39
+ <step name="validate_input">
40
+ **Validate issue number provided:**
41
+
42
+ Parse $ARGUMENTS for a numeric issue number. If missing:
43
+ ```
44
+ AskUserQuestion(
45
+ header: "Issue Number Required",
46
+ question: "Which issue number do you want to triage?",
47
+ followUp: "Enter the GitHub issue number (e.g., 42)"
48
+ )
49
+ ```
50
+ </step>
51
+
52
+ <step name="init_state">
53
+ **Initialize .mgw/ directory:**
54
+
55
+ Follow initialization procedure from @~/.claude/commands/mgw/workflows/state.md.
56
+ Ensure .mgw/, active/, completed/ exist and .gitignore includes .mgw/.
57
+ </step>
58
+
59
+ <step name="fetch_issue">
60
+ **Fetch issue from GitHub:**
61
+
62
+ ```bash
63
+ gh issue view $ISSUE_NUMBER --json number,title,body,labels,assignees,state,comments,url,milestone
64
+ ```
65
+
66
+ If issue not found → error: "Issue #$ISSUE_NUMBER not found in this repo."
67
+
68
+ Store as $ISSUE_DATA.
69
+
70
+ **Capture comment tracking snapshot:**
71
+
72
+ Record the current comment count and timestamp of the most recent comment. These
73
+ are stored in the triage state and used by run.md's pre-flight comment check to
74
+ detect new comments posted between triage and execution.
75
+
76
+ ```bash
77
+ COMMENT_COUNT=$(echo "$ISSUE_DATA" | python3 -c "import json,sys; d=json.load(sys.stdin); print(len(d.get('comments', [])))")
78
+ LAST_COMMENT_AT=$(echo "$ISSUE_DATA" | python3 -c "
79
+ import json,sys
80
+ d = json.load(sys.stdin)
81
+ comments = d.get('comments', [])
82
+ if comments:
83
+ print(comments[-1].get('createdAt', ''))
84
+ else:
85
+ print('')
86
+ " 2>/dev/null || echo "")
87
+ ```
88
+
89
+ Store $COMMENT_COUNT and $LAST_COMMENT_AT for use in the write_state step.
90
+ </step>
91
+
92
+ <step name="assign_self">
93
+ **Assign to self if unassigned:**
94
+
95
+ Check if current user is in assignees list:
96
+ ```bash
97
+ GH_USER=$(gh api user -q .login)
98
+ ```
99
+
100
+ If not assigned:
101
+ ```bash
102
+ gh issue edit $ISSUE_NUMBER --add-assignee @me
103
+ ```
104
+
105
+ Report: "Assigned #$ISSUE_NUMBER to $GH_USER"
106
+ </step>
107
+
108
+ <step name="spawn_analysis">
109
+ **Spawn task agent for codebase analysis:**
110
+
111
+ Gather GSD project history for context (if available):
112
+ ```bash
113
+ HISTORY=$(node ~/.claude/get-shit-done/bin/gsd-tools.cjs history-digest 2>/dev/null || echo "")
114
+ ```
115
+
116
+ Build analysis prompt from issue data and spawn:
117
+
118
+ ```
119
+ Task(
120
+ prompt="
121
+ <files_to_read>
122
+ - ./CLAUDE.md (Project instructions — if exists, follow all guidelines)
123
+ - .agents/skills/ (Project skills — if dir exists, list skills, read SKILL.md for each, follow relevant rules)
124
+ </files_to_read>
125
+
126
+ Analyze GitHub issue #${ISSUE_NUMBER} against this codebase.
127
+
128
+ <issue>
129
+ Title: ${title}
130
+ Body: ${body}
131
+ Labels: ${labels}
132
+ Comments: ${comments_summary}
133
+ </issue>
134
+
135
+ <project_history>
136
+ ${HISTORY}
137
+ </project_history>
138
+
139
+ <analysis_dimensions>
140
+
141
+ 1. **Scope:** Search the codebase for files and systems related to this issue.
142
+ - List affected files with paths
143
+ - List affected systems/modules
144
+ - Estimate: small (1-2 files), medium (3-8 files), large (9+ files or new system)
145
+
146
+ 2. **Validity:** Based on the codebase, is this a real problem?
147
+ - Can the issue be confirmed by reading the code?
148
+ - Are there existing tests that cover this area?
149
+ - Is the described behavior actually a bug or expected?
150
+
151
+ 3. **Purpose/Benefit:** What does resolving this achieve?
152
+ - Who benefits (users, developers, ops)?
153
+ - What's the impact of NOT doing this?
154
+
155
+ 4. **Security:** Does this touch sensitive areas?
156
+ - Authentication/authorization
157
+ - User data handling
158
+ - External API calls
159
+ - Input validation/sanitization
160
+ - If yes to any: note specific concerns
161
+
162
+ 5. **Conflicts:** Read all JSON files in .mgw/active/ directory.
163
+ - Do any active issues touch the same files/systems?
164
+ - Note overlaps with file paths and issue numbers
165
+
166
+ </analysis_dimensions>
167
+
168
+ <output_format>
169
+ Return a structured report:
170
+
171
+ ## Triage Report: #${ISSUE_NUMBER}
172
+
173
+ ### Scope
174
+ - Files: [list]
175
+ - Systems: [list]
176
+ - Size: small|medium|large
177
+
178
+ ### Validity
179
+ - Status: confirmed|questionable|invalid
180
+ - Evidence: [explanation]
181
+
182
+ ### Purpose
183
+ - Benefit: [who and how]
184
+ - Impact if skipped: [consequence]
185
+
186
+ ### Security
187
+ - Risk: none|low|medium|high
188
+ - Notes: [specific concerns or 'none']
189
+
190
+ ### Conflicts
191
+ - Active overlaps: [list or 'none']
192
+
193
+ ### Recommended GSD Route
194
+ - Route: gsd:quick | gsd:quick --full | gsd:new-milestone
195
+ - Reasoning: [why this route]
196
+ </output_format>
197
+ ",
198
+ subagent_type="general-purpose",
199
+ description="Triage issue #${ISSUE_NUMBER}"
200
+ )
201
+ ```
202
+ </step>
203
+
204
+ <step name="evaluate_gates">
205
+ **Evaluate triage quality gates:**
206
+
207
+ After the analysis agent returns, evaluate three quality gates against the triage report
208
+ and the original issue data. This determines whether the issue can proceed to pipeline
209
+ execution or requires additional information/review.
210
+
211
+ Initialize gate result:
212
+ ```
213
+ gate_result = {
214
+ "status": "passed",
215
+ "blockers": [],
216
+ "warnings": [],
217
+ "missing_fields": []
218
+ }
219
+ ```
220
+
221
+ **Gate 1: Validity**
222
+ ```
223
+ if triage.validity == "invalid":
224
+ gate_result.blockers.push("Validity gate failed: issue could not be confirmed against codebase")
225
+ gate_result.status = "blocked"
226
+ ```
227
+
228
+ **Gate 2: Security**
229
+ ```
230
+ if triage.security_risk == "high":
231
+ gate_result.blockers.push("Security gate: high-risk issue requires security review before execution")
232
+ gate_result.status = "blocked"
233
+ ```
234
+
235
+ **Gate 3: Detail Sufficiency**
236
+ Evaluate the original issue body (not the triage report):
237
+ ```
238
+ BODY_LENGTH = len(issue_body.strip())
239
+ HAS_AC = issue has acceptance criteria field filled OR body contains "- [ ]" checklist items
240
+ IS_FEATURE = "enhancement" in issue_labels OR issue template is feature_request
241
+
242
+ if BODY_LENGTH < 200:
243
+ gate_result.blockers.push("Insufficient detail: issue body is ${BODY_LENGTH} characters (minimum 200)")
244
+ gate_result.missing_fields.push("Expand issue description with more detail")
245
+ gate_result.status = "blocked"
246
+
247
+ if IS_FEATURE and not HAS_AC:
248
+ gate_result.blockers.push("Feature requests require acceptance criteria")
249
+ gate_result.missing_fields.push("Add acceptance criteria (checklist of testable conditions)")
250
+ gate_result.status = "blocked"
251
+ ```
252
+
253
+ **Gate warnings (non-blocking):**
254
+ ```
255
+ if triage.security_risk == "medium":
256
+ gate_result.warnings.push("Medium security risk — consider review before execution")
257
+
258
+ if triage.scope.size == "large" and gsd_route != "gsd:new-milestone":
259
+ gate_result.warnings.push("Large scope detected but not routed to new-milestone")
260
+ ```
261
+
262
+ Set `gate_result.status = "passed"` if no blockers. Store gate_result for state file.
263
+ </step>
264
+
265
+ <step name="post_triage_github">
266
+ **Post immediate triage feedback to GitHub:**
267
+
268
+ This step posts a comment on the GitHub issue IMMEDIATELY during /mgw:issue, not
269
+ deferred to /mgw:run. This gives stakeholders visibility into triage results as
270
+ soon as they happen.
271
+
272
+ Generate timestamp:
273
+ ```bash
274
+ TIMESTAMP=$(node ~/.claude/get-shit-done/bin/gsd-tools.cjs current-timestamp --raw 2>/dev/null || date -u +"%Y-%m-%dT%H:%M:%SZ")
275
+ ```
276
+
277
+ **If gates blocked (gate_result.status == "blocked"):**
278
+
279
+ Build gate table rows from gate_result.blockers:
280
+ ```
281
+ GATE_TABLE_ROWS = gate_result.blockers formatted as "| ${blocker} | Blocked |" rows
282
+ ```
283
+
284
+ Build missing fields list from gate_result.missing_fields:
285
+ ```
286
+ MISSING_FIELDS_LIST = gate_result.missing_fields formatted as "- ${field}" list
287
+ ```
288
+
289
+ Use the "Gate Blocked Comment" template from @~/.claude/commands/mgw/workflows/github.md.
290
+ Post comment and apply label:
291
+ ```bash
292
+ # Use remove_mgw_labels_and_apply pattern from github.md
293
+ # For validity failures:
294
+ # pipeline_stage = "needs-info", label = "mgw:needs-info"
295
+ # For security failures:
296
+ # pipeline_stage = "needs-security-review", label = "mgw:needs-security-review"
297
+ # For detail failures:
298
+ # pipeline_stage = "needs-info", label = "mgw:needs-info"
299
+ # If multiple blockers, use the highest-severity label (security > detail > validity)
300
+ ```
301
+
302
+ **If gates passed (gate_result.status == "passed"):**
303
+
304
+ Use the "Gate Passed Comment" template from @~/.claude/commands/mgw/workflows/github.md.
305
+
306
+ Populate template variables:
307
+ ```
308
+ SCOPE_SIZE = triage.scope.size
309
+ FILE_COUNT = triage.scope.file_count
310
+ SYSTEM_LIST = triage.scope.systems joined with ", "
311
+ VALIDITY = triage.validity
312
+ SECURITY_RISK = triage.security_notes
313
+ gsd_route = recommended route
314
+ ROUTE_REASONING = triage reasoning
315
+ ```
316
+
317
+ Post comment and apply label:
318
+ ```bash
319
+ gh issue edit ${ISSUE_NUMBER} --add-label "mgw:triaged" 2>/dev/null
320
+ ```
321
+ </step>
322
+
323
+ <step name="present_report">
324
+ **Present triage report to user:**
325
+
326
+ Display the analysis agent's report verbatim, then display gate results:
327
+
328
+ ```
329
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
330
+ MGW ► TRIAGE COMPLETE
331
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
332
+
333
+ Recommended route: ${recommended_route}
334
+ Reasoning: ${reasoning}
335
+ ```
336
+
337
+ Then display gate results:
338
+
339
+ ```
340
+ ${if gate_result.status == "blocked":}
341
+ GATES: BLOCKED
342
+ ${for blocker in gate_result.blockers:}
343
+ - ${blocker}
344
+ ${end}
345
+
346
+ ${if gate_result.missing_fields:}
347
+ Missing information:
348
+ ${for field in gate_result.missing_fields:}
349
+ - ${field}
350
+ ${end}
351
+ ${end}
352
+
353
+ The issue needs updates before pipeline execution.
354
+ Options:
355
+ 1) Wait for updates → issue stays in needs-info/needs-security-review
356
+ 2) Override → proceed despite gate failures (adds acknowledgment to state)
357
+ 3) Reject → issue is invalid or out of scope
358
+
359
+ ${else:}
360
+ GATES: PASSED ${if gate_result.warnings: '(with warnings)'}
361
+ ${for warning in gate_result.warnings:}
362
+ - Warning: ${warning}
363
+ ${end}
364
+
365
+ Options:
366
+ 1) Accept recommendation → proceed with ${recommended_route}
367
+ 2) Override route → choose different GSD entry point
368
+ 3) Reject → issue is invalid or out of scope
369
+ ${end}
370
+ ```
371
+
372
+ ```
373
+ AskUserQuestion(
374
+ header: "Triage Decision",
375
+ question: "${gate_result.status == 'blocked' ? 'Override gates (1), wait for updates (2), or reject (3)?' : 'Accept recommendation (1), override (2), or reject (3)?'}",
376
+ followUp: "${gate_result.status == 'blocked' ? 'Override will log acknowledgment. Wait keeps issue in blocked state.' : 'If overriding, specify: quick, quick --full, or new-milestone'}"
377
+ )
378
+ ```
379
+ </step>
380
+
381
+ <step name="write_state">
382
+ **Write issue state file:**
383
+
384
+ If accepted, overridden, or gates blocked but user overrides (not rejected, not "wait"):
385
+
386
+ Generate slug from title using gsd-tools:
387
+ ```bash
388
+ SLUG=$(node ~/.claude/get-shit-done/bin/gsd-tools.cjs generate-slug "${issue_title}" --raw)
389
+ SLUG="${SLUG:0:40}" # gsd-tools doesn't truncate; MGW enforces 40-char limit
390
+ ```
391
+ Write to `.mgw/active/${ISSUE_NUMBER}-${slug}.json` using the schema from state.md.
392
+
393
+ Populate:
394
+ - issue: from $ISSUE_DATA
395
+ - triage: from analysis report
396
+ - triage.last_comment_count: from $COMMENT_COUNT (captured in fetch_issue step)
397
+ - triage.last_comment_at: from $LAST_COMMENT_AT (captured in fetch_issue step, null if no comments)
398
+ - triage.gate_result: from gate_result evaluation (status, blockers, warnings, missing_fields)
399
+ - gsd_route: confirmed or overridden route
400
+ - pipeline_stage: set based on gate outcome:
401
+ - Gates blocked + validity failure (and not overridden): `"needs-info"`
402
+ - Gates blocked + security failure (and not overridden): `"needs-security-review"`
403
+ - Gates blocked + user overrides: `"triaged"` (with override_log entry noting acknowledged gate failures)
404
+ - Gates passed: `"triaged"`
405
+ - All other fields: defaults (empty arrays, null)
406
+
407
+ Also add branch cross-ref:
408
+ ```bash
409
+ BRANCH=$(git branch --show-current)
410
+ ```
411
+ Add to linked_branches if not main/master.
412
+ </step>
413
+
414
+ <step name="offer_next">
415
+ **Offer next steps:**
416
+
417
+ If accepted/overridden (gates passed):
418
+ ```
419
+ Issue #${ISSUE_NUMBER} triaged and tracked in .mgw/active/${filename}.
420
+
421
+ Next steps:
422
+ → /mgw:run ${ISSUE_NUMBER} — Start autonomous pipeline
423
+ → /mgw:update ${ISSUE_NUMBER} — Post triage comment to GitHub
424
+ ```
425
+
426
+ If gates blocked and user chose "wait" (no state file written):
427
+ ```
428
+ Issue #${ISSUE_NUMBER} is blocked pending more information.
429
+ A comment has been posted to GitHub explaining what's needed.
430
+
431
+ When the issue is updated:
432
+ → /mgw:issue ${ISSUE_NUMBER} — Re-triage with updated context
433
+ ```
434
+
435
+ If gates blocked and user overrode:
436
+ ```
437
+ Issue #${ISSUE_NUMBER} triaged with gate override. Tracked in .mgw/active/${filename}.
438
+
439
+ Note: Gate failures acknowledged. Override logged in state.
440
+
441
+ Next steps:
442
+ → /mgw:run ${ISSUE_NUMBER} — Start autonomous pipeline
443
+ ```
444
+
445
+ If rejected:
446
+ ```
447
+ Issue #${ISSUE_NUMBER} rejected. No state file created.
448
+ Consider closing or commenting on the issue with your reasoning.
449
+ ```
450
+ </step>
451
+
452
+ </process>
453
+
454
+ <success_criteria>
455
+ - [ ] Issue fetched from GitHub via gh CLI
456
+ - [ ] Comment tracking snapshot captured (count + last timestamp)
457
+ - [ ] Self-assigned if not already
458
+ - [ ] Analysis agent spawned and returned structured report
459
+ - [ ] Scope, validity, security, conflicts all assessed
460
+ - [ ] GSD route recommended with reasoning
461
+ - [ ] Triage gates evaluated (validity, security, detail sufficiency)
462
+ - [ ] Gate result stored in state file
463
+ - [ ] Triage comment posted IMMEDIATELY to GitHub
464
+ - [ ] Blocked issues get appropriate mgw: label (mgw:needs-info or mgw:needs-security-review)
465
+ - [ ] Passed issues get mgw:triaged label
466
+ - [ ] User confirms, overrides, or rejects
467
+ - [ ] State file written to .mgw/active/ (if accepted) with comment tracking fields and gate_result
468
+ - [ ] Next steps offered
469
+ </success_criteria>
@@ -0,0 +1,109 @@
1
+ ---
2
+ name: mgw:issues
3
+ description: List and filter GitHub issues, pick one to triage
4
+ argument-hint: "[--label &lt;label&gt;] [--milestone &lt;name&gt;] [--assignee &lt;user&gt;] [--state open|closed|all]"
5
+ allowed-tools:
6
+ - Bash
7
+ - Read
8
+ - Write
9
+ - AskUserQuestion
10
+ ---
11
+
12
+ <objective>
13
+ Browse GitHub issues for the current repo. Presents a scannable table filtered by
14
+ assignment (defaults to @me), labels, milestone, or state. Pick an issue to route
15
+ into triage via /mgw:issue.
16
+
17
+ No side effects — read-only GitHub access. Safe to run anytime.
18
+ </objective>
19
+
20
+ <execution_context>
21
+ @~/.claude/commands/mgw/workflows/github.md
22
+ </execution_context>
23
+
24
+ <context>
25
+ $ARGUMENTS
26
+
27
+ Repo detected via: gh repo view --json nameWithOwner -q .nameWithOwner
28
+ </context>
29
+
30
+ <process>
31
+
32
+ <step name="parse_filters">
33
+ **Parse arguments into gh filters:**
34
+
35
+ Defaults if no arguments:
36
+ - `--assignee @me`
37
+ - `--state open`
38
+ - `--limit 25`
39
+
40
+ Override with explicit flags from $ARGUMENTS:
41
+ - `--label <label>` → `gh --label`
42
+ - `--milestone <name>` → `gh --milestone`
43
+ - `--assignee <user>` → `gh --assignee` (use "all" to skip filter)
44
+ - `--state <state>` → `gh --state`
45
+ </step>
46
+
47
+ <step name="fetch_issues">
48
+ **Fetch issues from GitHub:**
49
+
50
+ ```bash
51
+ gh issue list --assignee @me --state open --limit 25 --json number,title,labels,createdAt,comments,assignees
52
+ ```
53
+
54
+ Adjust flags based on parsed filters.
55
+
56
+ If result is empty:
57
+ ```
58
+ No issues found matching filters.
59
+ Try: /mgw:issues --assignee all --state open
60
+ ```
61
+ </step>
62
+
63
+ <step name="display_table">
64
+ **Present issues as a scannable table:**
65
+
66
+ ```
67
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
68
+ MGW ► ISSUES
69
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
70
+
71
+ | # | Title | Labels | Age | Comments |
72
+ |----|----------------------------|--------------|------|----------|
73
+ | 42 | Fix auth bug in login flow | bug, auth | 3d | 2 |
74
+ | 38 | Add caching layer | enhancement | 1w | 5 |
75
+ | ... |
76
+
77
+ Enter issue number to triage, or 'q' to quit.
78
+ ```
79
+
80
+ Calculate age as human-readable relative time from createdAt.
81
+ Truncate title to 30 chars if needed.
82
+ Format labels as comma-separated.
83
+ </step>
84
+
85
+ <step name="pick_issue">
86
+ **User selects an issue:**
87
+
88
+ ```
89
+ AskUserQuestion(
90
+ header: "Select Issue",
91
+ question: "Which issue number do you want to triage?",
92
+ followUp: "Enter a number from the table above, or 'q' to quit"
93
+ )
94
+ ```
95
+
96
+ If valid number → suggest: "Run /mgw:issue <number> to triage this issue."
97
+ If 'q' → exit cleanly.
98
+ If invalid → re-prompt.
99
+ </step>
100
+
101
+ </process>
102
+
103
+ <success_criteria>
104
+ - [ ] Issues fetched from current repo via gh CLI
105
+ - [ ] Filters applied correctly (defaults to @me + open)
106
+ - [ ] Table displayed with number, title, labels, age, comments
107
+ - [ ] User can pick an issue number
108
+ - [ ] Routes to /mgw:issue <number>
109
+ </success_criteria>
@@ -0,0 +1,122 @@
1
+ ---
2
+ name: mgw:link
3
+ description: Cross-reference issues, PRs, and branches to each other
4
+ argument-hint: "<ref-a> <ref-b> [--quiet]"
5
+ allowed-tools:
6
+ - Bash
7
+ - Read
8
+ - Write
9
+ - Edit
10
+ ---
11
+
12
+ <objective>
13
+ Track relationships between issues, PRs, and branches. Writes bidirectional links
14
+ to .mgw/cross-refs.json. Optionally posts comments on linked GitHub issues/PRs.
15
+
16
+ Future mgw:update and mgw:pr calls automatically include these references.
17
+
18
+ Reference formats:
19
+ - Issue: 42 or #42 or issue:42
20
+ - PR: pr:15 or pr:#15
21
+ - Branch: branch:fix/auth-42
22
+ - GitHub Milestone: milestone:N
23
+ - GSD Milestone: gsd-milestone:name
24
+ </objective>
25
+
26
+ <execution_context>
27
+ @~/.claude/commands/mgw/workflows/state.md
28
+ @~/.claude/commands/mgw/workflows/github.md
29
+ </execution_context>
30
+
31
+ <context>
32
+ $ARGUMENTS — expects: <ref-a> <ref-b> [--quiet]
33
+ </context>
34
+
35
+ <process>
36
+
37
+ <step name="parse_refs">
38
+ **Parse two references from arguments:**
39
+
40
+ Normalize reference formats:
41
+ - Bare number or #N → "issue:N"
42
+ - pr:N or pr:#N → "pr:N"
43
+ - branch:name → "branch:name"
44
+ - milestone:N → "milestone:N" (GitHub milestone by number)
45
+ - gsd-milestone:name → "gsd-milestone:name" (GSD milestone by id/name)
46
+
47
+ If fewer than 2 refs provided:
48
+ ```
49
+ AskUserQuestion(
50
+ header: "Link References",
51
+ question: "Provide two references to link (e.g., 42 #43, or 42 branch:fix/auth)",
52
+ followUp: null
53
+ )
54
+ ```
55
+
56
+ Check for `--quiet` flag → skip GitHub comment posting if present.
57
+ </step>
58
+
59
+ <step name="init_state">
60
+ **Initialize .mgw/ if needed:**
61
+
62
+ Follow initialization from state.md workflow.
63
+ </step>
64
+
65
+ <step name="write_crossref">
66
+ **Write bidirectional link:**
67
+
68
+ Read `.mgw/cross-refs.json`.
69
+
70
+ Determine link type:
71
+ - issue + issue → "related"
72
+ - issue + pr → "implements"
73
+ - issue + branch → "tracks"
74
+ - pr + branch → "tracks"
75
+ - milestone + gsd-milestone → "maps-to" (maps GitHub milestone to GSD milestone)
76
+
77
+ Check for duplicate (same a+b pair exists). If duplicate, report and skip.
78
+
79
+ Append new link:
80
+ ```json
81
+ { "a": "${ref_a}", "b": "${ref_b}", "type": "${link_type}", "created": "$(node ~/.claude/get-shit-done/bin/gsd-tools.cjs current-timestamp --raw)" }
82
+ ```
83
+
84
+ Write back to `.mgw/cross-refs.json`.
85
+
86
+ Also update any matching .mgw/active/ state files:
87
+ - If ref_a is an issue with a state file → add ref_b to appropriate linked_* array
88
+ - If ref_b is an issue with a state file → add ref_a to appropriate linked_* array
89
+ </step>
90
+
91
+ <step name="post_comments">
92
+ **Post GitHub comments (unless --quiet):**
93
+
94
+ For each reference that is an issue or PR:
95
+ ```bash
96
+ gh issue comment ${NUMBER} --body "Linked to ${other_ref} (${link_type}) via MGW"
97
+ ```
98
+
99
+ Or for PRs:
100
+ ```bash
101
+ gh pr comment ${NUMBER} --body "Linked to ${other_ref} (${link_type}) via MGW"
102
+ ```
103
+ </step>
104
+
105
+ <step name="report">
106
+ **Report:**
107
+
108
+ ```
109
+ Linked ${ref_a} ↔ ${ref_b} (${link_type})
110
+ ${comment_status}
111
+ ```
112
+ </step>
113
+
114
+ </process>
115
+
116
+ <success_criteria>
117
+ - [ ] Two references parsed and normalized
118
+ - [ ] Duplicate detection works
119
+ - [ ] Bidirectional link written to cross-refs.json
120
+ - [ ] Active state files updated if matching
121
+ - [ ] GitHub comments posted (unless --quiet)
122
+ </success_criteria>