Skip to content

Commit 8f98acb

Browse files
authored
Merge pull request #89 from snipcodeit/issue/74-auto-update-board-status-field-on-p
feat(board): auto-update board Status field on pipeline_stage transitions
2 parents 2dc0176 + 8bce926 commit 8f98acb

4 files changed

Lines changed: 349 additions & 1 deletion

File tree

.claude/commands/mgw/issue.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,74 @@ Also add branch cross-ref:
409409
BRANCH=$(git branch --show-current)
410410
```
411411
Add to linked_branches if not main/master.
412+
413+
After writing the state file, sync the board Status field (non-blocking):
414+
```bash
415+
# Board sync — update board Status field to reflect new pipeline_stage
416+
# Source the shared utility from board-sync.md, then call it
417+
# Reads REPO_ROOT from environment (set in validate_and_load / init_state)
418+
update_board_status() {
419+
local ISSUE_NUMBER="$1"
420+
local NEW_STAGE="$2"
421+
if [ -z "$ISSUE_NUMBER" ] || [ -z "$NEW_STAGE" ]; then return 0; fi
422+
BOARD_NODE_ID=$(python3 -c "
423+
import json,sys,os
424+
try:
425+
p=json.load(open('${REPO_ROOT}/.mgw/project.json'))
426+
print(p.get('project',{}).get('project_board',{}).get('node_id',''))
427+
except: print('')
428+
" 2>/dev/null || echo "")
429+
if [ -z "$BOARD_NODE_ID" ]; then return 0; fi
430+
ITEM_ID=$(python3 -c "
431+
import json,sys
432+
try:
433+
p=json.load(open('${REPO_ROOT}/.mgw/project.json'))
434+
for m in p.get('milestones',[]):
435+
for i in m.get('issues',[]):
436+
if i.get('github_number')==${ISSUE_NUMBER}:
437+
print(i.get('board_item_id','')); sys.exit(0)
438+
print('')
439+
except: print('')
440+
" 2>/dev/null || echo "")
441+
if [ -z "$ITEM_ID" ]; then return 0; fi
442+
FIELD_ID=$(python3 -c "
443+
import json,sys,os
444+
try:
445+
s='${REPO_ROOT}/.mgw/board-schema.json'
446+
if os.path.exists(s):
447+
print(json.load(open(s)).get('fields',{}).get('status',{}).get('field_id',''))
448+
else:
449+
p=json.load(open('${REPO_ROOT}/.mgw/project.json'))
450+
print(p.get('project',{}).get('project_board',{}).get('fields',{}).get('status',{}).get('field_id',''))
451+
except: print('')
452+
" 2>/dev/null || echo "")
453+
if [ -z "$FIELD_ID" ]; then return 0; fi
454+
OPTION_ID=$(python3 -c "
455+
import json,sys,os
456+
try:
457+
stage='${NEW_STAGE}'
458+
s='${REPO_ROOT}/.mgw/board-schema.json'
459+
if os.path.exists(s):
460+
print(json.load(open(s)).get('fields',{}).get('status',{}).get('options',{}).get(stage,''))
461+
else:
462+
p=json.load(open('${REPO_ROOT}/.mgw/project.json'))
463+
print(p.get('project',{}).get('project_board',{}).get('fields',{}).get('status',{}).get('options',{}).get(stage,''))
464+
except: print('')
465+
" 2>/dev/null || echo "")
466+
if [ -z "$OPTION_ID" ]; then return 0; fi
467+
gh api graphql -f query='
468+
mutation($projectId:ID!,$itemId:ID!,$fieldId:ID!,$optionId:String!){
469+
updateProjectV2ItemFieldValue(input:{projectId:$projectId,itemId:$itemId,fieldId:$fieldId,value:{singleSelectOptionId:$optionId}}){projectV2Item{id}}
470+
}
471+
' -f projectId="$BOARD_NODE_ID" -f itemId="$ITEM_ID" \
472+
-f fieldId="$FIELD_ID" -f optionId="$OPTION_ID" 2>/dev/null || true
473+
}
474+
475+
# Call after state file is written — non-blocking, never fails the pipeline
476+
update_board_status $ISSUE_NUMBER "$pipeline_stage"
477+
```
478+
479+
See @~/.claude/commands/mgw/workflows/board-sync.md for the full utility and data source reference.
412480
</step>
413481

414482
<step name="offer_next">
@@ -465,5 +533,6 @@ Consider closing or commenting on the issue with your reasoning.
465533
- [ ] Passed issues get mgw:triaged label
466534
- [ ] User confirms, overrides, or rejects
467535
- [ ] State file written to .mgw/active/ (if accepted) with comment tracking fields and gate_result
536+
- [ ] Board Status field updated via update_board_status (non-blocking — failure does not block)
468537
- [ ] Next steps offered
469538
</success_criteria>

.claude/commands/mgw/run.md

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ Checkpoints requiring user input:
3838
@~/.claude/commands/mgw/workflows/github.md
3939
@~/.claude/commands/mgw/workflows/gsd.md
4040
@~/.claude/commands/mgw/workflows/validation.md
41+
@~/.claude/commands/mgw/workflows/board-sync.md
4142
</execution_context>
4243

4344
<context>
@@ -57,6 +58,66 @@ REPO_ROOT=$(git rev-parse --show-toplevel)
5758
DEFAULT=$(gh repo view --json defaultBranchRef -q .defaultBranchRef.name)
5859
```
5960

61+
Define the board sync utility (non-blocking — see board-sync.md for full reference):
62+
```bash
63+
update_board_status() {
64+
local ISSUE_NUMBER="$1"
65+
local NEW_STAGE="$2"
66+
if [ -z "$ISSUE_NUMBER" ] || [ -z "$NEW_STAGE" ]; then return 0; fi
67+
BOARD_NODE_ID=$(python3 -c "
68+
import json,sys,os
69+
try:
70+
p=json.load(open('${REPO_ROOT}/.mgw/project.json'))
71+
print(p.get('project',{}).get('project_board',{}).get('node_id',''))
72+
except: print('')
73+
" 2>/dev/null || echo "")
74+
if [ -z "$BOARD_NODE_ID" ]; then return 0; fi
75+
ITEM_ID=$(python3 -c "
76+
import json,sys
77+
try:
78+
p=json.load(open('${REPO_ROOT}/.mgw/project.json'))
79+
for m in p.get('milestones',[]):
80+
for i in m.get('issues',[]):
81+
if i.get('github_number')==${ISSUE_NUMBER}:
82+
print(i.get('board_item_id','')); sys.exit(0)
83+
print('')
84+
except: print('')
85+
" 2>/dev/null || echo "")
86+
if [ -z "$ITEM_ID" ]; then return 0; fi
87+
FIELD_ID=$(python3 -c "
88+
import json,sys,os
89+
try:
90+
s='${REPO_ROOT}/.mgw/board-schema.json'
91+
if os.path.exists(s):
92+
print(json.load(open(s)).get('fields',{}).get('status',{}).get('field_id',''))
93+
else:
94+
p=json.load(open('${REPO_ROOT}/.mgw/project.json'))
95+
print(p.get('project',{}).get('project_board',{}).get('fields',{}).get('status',{}).get('field_id',''))
96+
except: print('')
97+
" 2>/dev/null || echo "")
98+
if [ -z "$FIELD_ID" ]; then return 0; fi
99+
OPTION_ID=$(python3 -c "
100+
import json,sys,os
101+
try:
102+
stage='${NEW_STAGE}'
103+
s='${REPO_ROOT}/.mgw/board-schema.json'
104+
if os.path.exists(s):
105+
print(json.load(open(s)).get('fields',{}).get('status',{}).get('options',{}).get(stage,''))
106+
else:
107+
p=json.load(open('${REPO_ROOT}/.mgw/project.json'))
108+
print(p.get('project',{}).get('project_board',{}).get('fields',{}).get('status',{}).get('options',{}).get(stage,''))
109+
except: print('')
110+
" 2>/dev/null || echo "")
111+
if [ -z "$OPTION_ID" ]; then return 0; fi
112+
gh api graphql -f query='
113+
mutation($projectId:ID!,$itemId:ID!,$fieldId:ID!,$optionId:String!){
114+
updateProjectV2ItemFieldValue(input:{projectId:$projectId,itemId:$itemId,fieldId:$fieldId,value:{singleSelectOptionId:$optionId}}){projectV2Item{id}}
115+
}
116+
' -f projectId="$BOARD_NODE_ID" -f itemId="$ITEM_ID" \
117+
-f fieldId="$FIELD_ID" -f optionId="$OPTION_ID" 2>/dev/null || true
118+
}
119+
```
120+
60121
Parse $ARGUMENTS for issue number. If missing:
61122
```
62123
AskUserQuestion(
@@ -248,7 +309,7 @@ Return ONLY valid JSON:
248309
|---------------|--------|
249310
| **informational** | Log: "MGW: ${NEW_COUNT} new comment(s) reviewed — informational, continuing." Update `triage.last_comment_count` in state file. Continue pipeline. |
250311
| **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). |
251-
| **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. |
312+
| **blocking** | Log: "MGW: Blocking comment detected — pipeline paused." Update state: `pipeline_stage = "blocked"`. Apply mgw:blocked label. Call `update_board_status $ISSUE_NUMBER "blocked"` (non-blocking). Post comment on issue: `> **MGW** . \`pipeline-blocked\` . Blocked by stakeholder comment. Reason: ${blocking_reason}`. Stop pipeline execution. |
252313

253314
**Security keyword check for material comments:**
254315
```bash
@@ -339,6 +400,9 @@ Log comment in state file (at `${REPO_ROOT}/.mgw/active/`).
339400
Only run this step if gsd_route is "gsd:quick" or "gsd:quick --full".
340401

341402
Update pipeline_stage to "executing" in state file (at `${REPO_ROOT}/.mgw/active/`).
403+
```bash
404+
update_board_status $ISSUE_NUMBER "executing" # non-blocking board sync
405+
```
342406

343407
Determine flags:
344408
- "gsd:quick" → $QUICK_FLAGS = ""
@@ -539,6 +603,9 @@ node ~/.claude/get-shit-done/bin/gsd-tools.cjs commit "docs(quick-${next_num}):
539603
```
540604

541605
Update state (at `${REPO_ROOT}/.mgw/active/`): gsd_artifacts.path = $QUICK_DIR, pipeline_stage = "verifying".
606+
```bash
607+
update_board_status $ISSUE_NUMBER "verifying" # non-blocking board sync
608+
```
542609
</step>
543610

544611
<step name="execute_gsd_milestone">
@@ -576,6 +643,7 @@ Set pipeline_stage to "discussing" and apply "mgw:discussing" label:
576643
```bash
577644
gh issue edit ${ISSUE_NUMBER} --remove-label "mgw:in-progress" 2>/dev/null
578645
gh issue edit ${ISSUE_NUMBER} --add-label "mgw:discussing" 2>/dev/null
646+
update_board_status $ISSUE_NUMBER "discussing" # non-blocking board sync
579647
```
580648

581649
Present to user:
@@ -623,6 +691,9 @@ If proceed: apply "mgw:approved" label and continue.
623691
```
624692
625693
Update pipeline_stage to "planning" (at `${REPO_ROOT}/.mgw/active/`).
694+
```bash
695+
update_board_status $ISSUE_NUMBER "planning" # non-blocking board sync
696+
```
626697

627698
2. **If resuming with pipeline_stage = "planning" and ROADMAP.md exists:**
628699
Discover phases from ROADMAP and run the full per-phase GSD lifecycle:
@@ -785,6 +856,9 @@ COMMENTEOF
785856
```
786857

787858
After ALL phases complete → update pipeline_stage to "verifying" (at `${REPO_ROOT}/.mgw/active/`).
859+
```bash
860+
update_board_status $ISSUE_NUMBER "verifying" # non-blocking board sync
861+
```
788862
</step>
789863

790864
<step name="post_execution_update">
@@ -822,6 +896,9 @@ gh issue comment ${ISSUE_NUMBER} --body "$EXEC_BODY" 2>/dev/null || true
822896
```
823897

824898
Update pipeline_stage to "pr-pending" (at `${REPO_ROOT}/.mgw/active/`).
899+
```bash
900+
update_board_status $ISSUE_NUMBER "pr-created" # non-blocking board sync (pr-pending maps to pr-created on board)
901+
```
825902
</step>
826903

827904
<step name="create_pr">
@@ -992,6 +1069,10 @@ Update state (at `${REPO_ROOT}/.mgw/active/`):
9921069
- linked_pr = PR number
9931070
- pipeline_stage = "pr-created"
9941071

1072+
```bash
1073+
update_board_status $ISSUE_NUMBER "pr-created" # non-blocking board sync
1074+
```
1075+
9951076
Add cross-ref (at `${REPO_ROOT}/.mgw/cross-refs.json`): issue → PR.
9961077
</step>
9971078

@@ -1052,6 +1133,9 @@ gh issue comment ${ISSUE_NUMBER} --body "$PR_READY_BODY" 2>/dev/null || true
10521133
```
10531134

10541135
Update pipeline_stage to "done" (at `${REPO_ROOT}/.mgw/active/`).
1136+
```bash
1137+
update_board_status $ISSUE_NUMBER "done" # non-blocking board sync
1138+
```
10551139

10561140
Report to user:
10571141
```
@@ -1092,5 +1176,7 @@ Next:
10921176
- [ ] Worktree cleaned up, user returned to main workspace
10931177
- [ ] mgw:in-progress label removed at completion
10941178
- [ ] State file updated through all pipeline stages
1179+
- [ ] Board Status field synced at each pipeline_stage transition (non-blocking)
1180+
- [ ] Board sync failures never block pipeline execution
10951181
- [ ] User prompted to run /mgw:sync after merge
10961182
</success_criteria>

0 commit comments

Comments
 (0)