@snipcodeit/mgw 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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
- # 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)
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
- gh issue edit ${ISSUE_NUMBER} --add-label "mgw:triaged" 2>/dev/null
318
+ remove_mgw_labels_and_apply ${ISSUE_NUMBER} "mgw:triaged"
320
319
  ```
321
320
  </step>
322
321
 
@@ -417,6 +417,7 @@ FAILED_ISSUES=()
417
417
  FAILED_ISSUES_WITH_CLASS=() # Entries: "issue_number:failure_class" for results display
418
418
  BLOCKED_ISSUES=()
419
419
  SKIPPED_ISSUES=()
420
+ LABEL_DRIFT_ISSUES=() # Issues where label reconciliation detected drift
420
421
  ISSUES_RUN=0
421
422
  ```
422
423
 
@@ -562,6 +563,43 @@ COMMENTEOF
562
563
  description="Run pipeline for #${ISSUE_NUMBER}"
563
564
  )
564
565
 
566
+ # ── POST-WORK: Post-subagent label verification ──
567
+ # Read the pipeline_stage from the issue's active state file after Task() returns
568
+ ISSUE_STAGE=$(node -e "
569
+ const { loadActiveIssue } = require('./lib/state.cjs');
570
+ const state = loadActiveIssue(${ISSUE_NUMBER});
571
+ console.log((state && state.pipeline_stage) ? state.pipeline_stage : 'unknown');
572
+ " 2>/dev/null || echo "unknown")
573
+
574
+ # Determine the expected MGW label for this pipeline stage
575
+ EXPECTED_LABEL=$(python3 -c "
576
+ stage_to_label = {
577
+ 'done': '',
578
+ 'pr-created': '',
579
+ 'verifying': 'mgw:in-progress',
580
+ 'executing': 'mgw:in-progress',
581
+ 'planning': 'mgw:in-progress',
582
+ 'blocked': 'mgw:blocked',
583
+ 'failed': '',
584
+ }
585
+ print(stage_to_label.get('${ISSUE_STAGE}', ''))
586
+ ")
587
+
588
+ # Compare expected label against live GitHub labels
589
+ LIVE_LABELS=$(gh issue view ${ISSUE_NUMBER} --json labels --jq '[.labels[].name] | join(",")' 2>/dev/null || echo "")
590
+
591
+ if [ -n "$EXPECTED_LABEL" ] && ! echo "$LIVE_LABELS" | grep -q "$EXPECTED_LABEL"; then
592
+ echo "MGW WARNING: label drift on #${ISSUE_NUMBER} — expected $EXPECTED_LABEL, live: $LIVE_LABELS" >&2
593
+ LABEL_DRIFT="drift"
594
+ else
595
+ LABEL_DRIFT="ok"
596
+ fi
597
+
598
+ # Track drifted issues for milestone summary
599
+ if [ "$LABEL_DRIFT" = "drift" ]; then
600
+ LABEL_DRIFT_ISSUES+=("$ISSUE_NUMBER")
601
+ fi
602
+
565
603
  # ── POST-WORK: Detect result and post completion comment ──
566
604
  # Check if PR was created by looking for state file or PR
567
605
  PR_NUMBER=$(gh pr list --head "issue/${ISSUE_NUMBER}-*" --json number -q '.[0].number' 2>/dev/null || echo "")
@@ -712,18 +750,19 @@ Every comment posted during milestone orchestration includes:
712
750
  <details>
713
751
  <summary>Milestone Progress ({done}/{total} complete)</summary>
714
752
 
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 | — | — |
753
+ | # | Issue | Status | PR | Failure Class | Label Drift |
754
+ |---|-------|--------|----|---------------|-------------|
755
+ | N | title | ✓ Done | #PR | — | ok |
756
+ | M | title | ✗ Failed | — | `permanent` | — |
757
+ | K | title | ○ Pending | — | — | — |
758
+ | J | title | ◆ Running | — | — | ok |
759
+ | L | title | ⊘ Blocked | — | — | — |
722
760
 
723
761
  </details>
724
762
  ```
725
763
 
726
764
  The **Failure Class** column surfaces `last_failure_class` from the active issue state file.
765
+ The **Label Drift** column shows the result of post-subagent label reconciliation: `ok` (labels matched expected), `drift` (label mismatch detected — MGW WARNING logged), or `—` (not checked / issue not run).
727
766
  Values: `transient` (retried and exhausted), `permanent` (unrecoverable), `needs-info` (ambiguous issue), `unknown` (no state file or pre-retry issue), `—` (not failed).
728
767
  </step>
729
768
 
@@ -1067,6 +1106,9 @@ gh issue comment ${FIRST_ISSUE_NUMBER} --body "$FINAL_RESULTS_COMMENT"
1067
1106
  - [ ] Retry option calls resetRetryState() then re-invokes /mgw:run --retry for failed issues
1068
1107
  - [ ] FAILED_ISSUES_WITH_CLASS tracks "number:class" for display in results table
1069
1108
  - [ ] Progress table in every GitHub comment
1109
+ - [ ] Post-subagent label reconciliation run per issue after Task() returns
1110
+ - [ ] LABEL_DRIFT tracked per issue (ok/drift) and shown in progress table Label Drift column
1111
+ - [ ] Label drift issues logged as MGW WARNING to stderr
1070
1112
  - [ ] Milestone close + draft release on full completion
1071
1113
  - [ ] current_milestone pointer advanced on completion
1072
1114
  - [ ] --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
- # Use remove_mgw_labels_and_apply pattern from github.md
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
- gh issue edit ${ISSUE_NUMBER} --remove-label "mgw:in-progress" 2>/dev/null
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
- gh issue edit ${ISSUE_NUMBER} --remove-label "mgw:in-progress" 2>/dev/null
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
- Remove in-progress label at completion:
1460
+ Clear MGW labels at completion:
1465
1461
  ```bash
1466
- gh issue edit ${ISSUE_NUMBER} --remove-label "mgw:in-progress" 2>/dev/null
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/sync.md CHANGED
@@ -464,6 +464,81 @@ Classify each issue into:
464
464
  - **Unreviewed comments:** COMMENT_DELTA > 0 — new comments posted since triage that haven't been classified
465
465
  </step>
466
466
 
467
+ <step name="label_drift_detection">
468
+ **Label Drift Detection:**
469
+
470
+ For each active issue state file in `.mgw/active/`, compare the stored `labels` field
471
+ against the live GitHub labels to detect MGW pipeline label drift:
472
+
473
+ ```python
474
+ label_drift = []
475
+
476
+ for each state_file in .mgw/active/*.json:
477
+ stored_labels = state['labels'] # list stored at last pipeline write
478
+ live_labels_json = subprocess.run(
479
+ ['gh', 'issue', 'view', str(issue_number), '--json', 'labels', '--jq', '[.labels[].name]'],
480
+ capture_output=True, text=True
481
+ ).stdout.strip()
482
+ live_labels = json.loads(live_labels_json or '[]')
483
+
484
+ # Find MGW labels in stored vs live
485
+ stored_mgw = [l for l in stored_labels if l.startswith('mgw:')]
486
+ live_mgw = [l for l in live_labels if l.startswith('mgw:')]
487
+
488
+ if set(stored_mgw) != set(live_mgw):
489
+ label_drift.append({
490
+ 'issue': issue_number,
491
+ 'stored': stored_mgw,
492
+ 'live': live_mgw,
493
+ 'pipeline_stage': state['pipeline_stage']
494
+ })
495
+ ```
496
+
497
+ If label drift detected, include in the drift report output:
498
+
499
+ ```
500
+ Label drift detected for ${COUNT} issue(s):
501
+ #N: stored=[mgw:in-progress], live=[] — pipeline_stage=${STAGE}
502
+ ```
503
+
504
+ **Offer repair:**
505
+ ```
506
+ AskUserQuestion(
507
+ header: "Label Drift Detected",
508
+ question: "Live GitHub labels don't match .mgw state for ${COUNT} issue(s). Repair?",
509
+ options: [
510
+ { label: "Repair all", description: "Re-apply correct label for each issue's pipeline_stage via remove_mgw_labels_and_apply" },
511
+ { label: "Skip", description: "Log drift only — no changes" }
512
+ ]
513
+ )
514
+ ```
515
+
516
+ If "Repair all":
517
+ ```bash
518
+ # For each drifted issue, determine correct label from pipeline_stage and re-apply
519
+ CORRECT_LABEL=$(python3 -c "
520
+ stage_to_label = {
521
+ 'triaged': 'mgw:triaged',
522
+ 'needs-info': 'mgw:needs-info',
523
+ 'needs-security-review': 'mgw:needs-security-review',
524
+ 'discussing': 'mgw:discussing',
525
+ 'approved': 'mgw:approved',
526
+ 'planning': 'mgw:in-progress',
527
+ 'executing': 'mgw:in-progress',
528
+ 'verifying': 'mgw:in-progress',
529
+ 'blocked': 'mgw:blocked',
530
+ 'done': '',
531
+ 'pr-created': '',
532
+ 'failed': '',
533
+ }
534
+ print(stage_to_label.get('${PIPELINE_STAGE}', ''))
535
+ ")
536
+ remove_mgw_labels_and_apply ${ISSUE_NUMBER} "${CORRECT_LABEL}"
537
+ ```
538
+
539
+ Log repair actions: "Repaired label for #N: applied ${CORRECT_LABEL} (pipeline_stage=${STAGE})"
540
+ </step>
541
+
467
542
  <step name="health_check">
468
543
  **GSD health check (if .planning/ exists):**
469
544
 
@@ -570,5 +645,7 @@ ${gsd_milestone_consistency ? 'GSD Milestone Links:\n' + gsd_milestone_consisten
570
645
  - [ ] Lingering worktrees cleaned up for completed items
571
646
  - [ ] Branch deletion offered for completed items
572
647
  - [ ] Stale/orphaned/drift items flagged (including comment drift and milestone inconsistencies)
648
+ - [ ] Label drift detected between .mgw/active labels field and live GitHub labels
649
+ - [ ] Repair offered for drifted issues via remove_mgw_labels_and_apply
573
650
  - [ ] Summary presented
574
651
  </success_criteria>
@@ -95,12 +95,15 @@ Seven labels for pipeline stage tracking. Created by init.md, managed by issue.m
95
95
  | `mgw:blocked` | `b60205` | Pipeline blocked by stakeholder comment |
96
96
 
97
97
  ### Remove MGW Labels and Apply New
98
- Used when transitioning pipeline stages. Removes all `mgw:*` pipeline labels, then applies the target label.
98
+ Used when transitioning pipeline stages. Removes all `mgw:*` pipeline labels, then applies the target label. Returns non-zero if any gh operation failed.
99
99
  ```bash
100
- # Remove all mgw: pipeline labels from issue, then apply new one
100
+ # Remove all mgw: pipeline labels from issue, then apply new one.
101
+ # Pass empty string as NEW_LABEL to remove all MGW labels without applying a new one.
102
+ # Returns non-zero if any label operation failed.
101
103
  remove_mgw_labels_and_apply() {
102
104
  local ISSUE_NUMBER="$1"
103
105
  local NEW_LABEL="$2"
106
+ local LABEL_FAILED=0
104
107
 
105
108
  # Get current labels
106
109
  CURRENT_LABELS=$(gh issue view "$ISSUE_NUMBER" --json labels --jq '.labels[].name' 2>/dev/null)
@@ -109,15 +112,27 @@ remove_mgw_labels_and_apply() {
109
112
  for LABEL in $CURRENT_LABELS; do
110
113
  case "$LABEL" in
111
114
  mgw:triaged|mgw:needs-info|mgw:needs-security-review|mgw:discussing|mgw:approved|mgw:in-progress|mgw:blocked)
112
- gh issue edit "$ISSUE_NUMBER" --remove-label "$LABEL" 2>/dev/null
115
+ gh issue edit "$ISSUE_NUMBER" --remove-label "$LABEL"
116
+ RC=$?
117
+ if [ $RC -ne 0 ]; then
118
+ echo "MGW WARNING: failed to remove label $LABEL from issue $ISSUE_NUMBER (exit $RC)" >&2
119
+ LABEL_FAILED=1
120
+ fi
113
121
  ;;
114
122
  esac
115
123
  done
116
124
 
117
125
  # Apply new label
118
126
  if [ -n "$NEW_LABEL" ]; then
119
- gh issue edit "$ISSUE_NUMBER" --add-label "$NEW_LABEL" 2>/dev/null
127
+ gh issue edit "$ISSUE_NUMBER" --add-label "$NEW_LABEL"
128
+ RC=$?
129
+ if [ $RC -ne 0 ]; then
130
+ echo "MGW WARNING: failed to add label $NEW_LABEL to issue $ISSUE_NUMBER (exit $RC)" >&2
131
+ LABEL_FAILED=1
132
+ fi
120
133
  fi
134
+
135
+ return $LABEL_FAILED
121
136
  }
122
137
  ```
123
138
 
package/dist/bin/mgw.cjs CHANGED
@@ -9,7 +9,7 @@ var require$$0$1 = require('child_process');
9
9
 
10
10
  var mgw$1 = {};
11
11
 
12
- var version = "0.1.2";
12
+ var version = "0.1.3";
13
13
  var require$$8 = {
14
14
  version: version};
15
15
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@snipcodeit/mgw",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "GitHub-native issue-to-PR automation for Claude Code, powered by Get Shit Done",
5
5
  "bin": {
6
6
  "mgw": "./dist/bin/mgw.cjs"