Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
85e1a81
docs(board): design GitHub Projects v2 board schema with MGW custom f…
Mar 1, 2026
b9d86a0
feat(board): add mgw:board command for project board management
Mar 1, 2026
86993e5
feat(board): sync milestone issues onto board items on project init a…
Mar 1, 2026
8bce926
feat(board): auto-update board Status field on pipeline_stage transit…
Mar 1, 2026
a9c74ce
feat(board): surface AI Agent State field during GSD execution
Mar 1, 2026
498091f
feat(board): sync linked PRs onto board items via cross-refs
Mar 1, 2026
092d342
feat(board): configure Board layout view with pipeline-stage swimlanes
Mar 1, 2026
9481696
feat(board): configure Table layout view for team triage planning
Mar 1, 2026
177e9e1
feat(board): configure Roadmap layout view with milestone-based timel…
Mar 1, 2026
ac9fe05
Merge issue/71-design-and-scaffold-github-projects-v2-b into milestone-1
Mar 1, 2026
87111a5
Merge issue/72-add-mgwboard-command-create-show-a into milestone-1
Mar 1, 2026
9d259b1
Merge issue/73-sync-project-json-milestones-into-b into milestone-1
Mar 1, 2026
0d3563e
Merge issue/74-auto-update-board-status-field-on-p into milestone-1
Mar 1, 2026
8e5c549
Merge issue/75-surface-ai-agent-state-field-on-bo into milestone-1
Mar 1, 2026
5c8d233
Merge issue/76-sync-linked-prs-and-branches-onto-b into milestone-1
Mar 1, 2026
609e1d9
Merge issue/77-configure-board-layout-with-pipelin into milestone-1
Mar 1, 2026
6ec6762
Merge issue/78-configure-table-layout-with-sortabl into milestone-1
Mar 1, 2026
e51337b
Merge issue/79-configure-roadmap-layout-with-miles into milestone-1
Mar 1, 2026
8d78061
feat(milestone): add post-start announcement hook for GitHub Discussions
Mar 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,245 changes: 1,245 additions & 0 deletions .claude/commands/mgw/board.md

Large diffs are not rendered by default.

69 changes: 69 additions & 0 deletions .claude/commands/mgw/issue.md
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,74 @@ Also add branch cross-ref:
BRANCH=$(git branch --show-current)
```
Add to linked_branches if not main/master.

After writing the state file, sync the board Status field (non-blocking):
```bash
# Board sync — update board Status field to reflect new pipeline_stage
# Source the shared utility from board-sync.md, then call it
# Reads REPO_ROOT from environment (set in validate_and_load / init_state)
update_board_status() {
local ISSUE_NUMBER="$1"
local NEW_STAGE="$2"
if [ -z "$ISSUE_NUMBER" ] || [ -z "$NEW_STAGE" ]; then return 0; fi
BOARD_NODE_ID=$(python3 -c "
import json,sys,os
try:
p=json.load(open('${REPO_ROOT}/.mgw/project.json'))
print(p.get('project',{}).get('project_board',{}).get('node_id',''))
except: print('')
" 2>/dev/null || echo "")
if [ -z "$BOARD_NODE_ID" ]; then return 0; fi
ITEM_ID=$(python3 -c "
import json,sys
try:
p=json.load(open('${REPO_ROOT}/.mgw/project.json'))
for m in p.get('milestones',[]):
for i in m.get('issues',[]):
if i.get('github_number')==${ISSUE_NUMBER}:
print(i.get('board_item_id','')); sys.exit(0)
print('')
except: print('')
" 2>/dev/null || echo "")
if [ -z "$ITEM_ID" ]; then return 0; fi
FIELD_ID=$(python3 -c "
import json,sys,os
try:
s='${REPO_ROOT}/.mgw/board-schema.json'
if os.path.exists(s):
print(json.load(open(s)).get('fields',{}).get('status',{}).get('field_id',''))
else:
p=json.load(open('${REPO_ROOT}/.mgw/project.json'))
print(p.get('project',{}).get('project_board',{}).get('fields',{}).get('status',{}).get('field_id',''))
except: print('')
" 2>/dev/null || echo "")
if [ -z "$FIELD_ID" ]; then return 0; fi
OPTION_ID=$(python3 -c "
import json,sys,os
try:
stage='${NEW_STAGE}'
s='${REPO_ROOT}/.mgw/board-schema.json'
if os.path.exists(s):
print(json.load(open(s)).get('fields',{}).get('status',{}).get('options',{}).get(stage,''))
else:
p=json.load(open('${REPO_ROOT}/.mgw/project.json'))
print(p.get('project',{}).get('project_board',{}).get('fields',{}).get('status',{}).get('options',{}).get(stage,''))
except: print('')
" 2>/dev/null || echo "")
if [ -z "$OPTION_ID" ]; then return 0; fi
gh api graphql -f query='
mutation($projectId:ID!,$itemId:ID!,$fieldId:ID!,$optionId:String!){
updateProjectV2ItemFieldValue(input:{projectId:$projectId,itemId:$itemId,fieldId:$fieldId,value:{singleSelectOptionId:$optionId}}){projectV2Item{id}}
}
' -f projectId="$BOARD_NODE_ID" -f itemId="$ITEM_ID" \
-f fieldId="$FIELD_ID" -f optionId="$OPTION_ID" 2>/dev/null || true
}

# Call after state file is written — non-blocking, never fails the pipeline
update_board_status $ISSUE_NUMBER "$pipeline_stage"
```

See @~/.claude/commands/mgw/workflows/board-sync.md for the full utility and data source reference.
</step>

<step name="offer_next">
Expand Down Expand Up @@ -465,5 +533,6 @@ Consider closing or commenting on the issue with your reasoning.
- [ ] Passed issues get mgw:triaged label
- [ ] User confirms, overrides, or rejects
- [ ] State file written to .mgw/active/ (if accepted) with comment tracking fields and gate_result
- [ ] Board Status field updated via update_board_status (non-blocking — failure does not block)
- [ ] Next steps offered
</success_criteria>
7 changes: 7 additions & 0 deletions .claude/commands/mgw/pr.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Works in two modes:
@~/.claude/commands/mgw/workflows/github.md
@~/.claude/commands/mgw/workflows/gsd.md
@~/.claude/commands/mgw/workflows/validation.md
@~/.claude/commands/mgw/workflows/board-sync.md
</execution_context>

<context>
Expand Down Expand Up @@ -247,6 +248,11 @@ Update state file:
- Set pipeline_stage to "pr-created"

Add cross-ref: issue → PR link in cross-refs.json.

Sync PR to board (non-blocking):
```bash
sync_pr_to_board $ISSUE_NUMBER $PR_NUMBER # non-blocking — add PR as board item
```
</step>

<step name="report">
Expand Down Expand Up @@ -274,4 +280,5 @@ Testing procedures posted as PR comment.
- [ ] Testing procedures posted as separate PR comment
- [ ] State file updated with PR number (linked mode)
- [ ] Cross-ref added (linked mode)
- [ ] PR added to board as board item after creation (non-blocking, linked mode only)
</success_criteria>
153 changes: 152 additions & 1 deletion .claude/commands/mgw/run.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Checkpoints requiring user input:
@~/.claude/commands/mgw/workflows/github.md
@~/.claude/commands/mgw/workflows/gsd.md
@~/.claude/commands/mgw/workflows/validation.md
@~/.claude/commands/mgw/workflows/board-sync.md
</execution_context>

<context>
Expand All @@ -57,6 +58,110 @@ REPO_ROOT=$(git rev-parse --show-toplevel)
DEFAULT=$(gh repo view --json defaultBranchRef -q .defaultBranchRef.name)
```

Define the board sync utilities (non-blocking — see board-sync.md for full reference):
```bash
update_board_status() {
local ISSUE_NUMBER="$1"
local NEW_STAGE="$2"
if [ -z "$ISSUE_NUMBER" ] || [ -z "$NEW_STAGE" ]; then return 0; fi
BOARD_NODE_ID=$(python3 -c "
import json,sys,os
try:
p=json.load(open('${REPO_ROOT}/.mgw/project.json'))
print(p.get('project',{}).get('project_board',{}).get('node_id',''))
except: print('')
" 2>/dev/null || echo "")
if [ -z "$BOARD_NODE_ID" ]; then return 0; fi
ITEM_ID=$(python3 -c "
import json,sys
try:
p=json.load(open('${REPO_ROOT}/.mgw/project.json'))
for m in p.get('milestones',[]):
for i in m.get('issues',[]):
if i.get('github_number')==${ISSUE_NUMBER}:
print(i.get('board_item_id','')); sys.exit(0)
print('')
except: print('')
" 2>/dev/null || echo "")
if [ -z "$ITEM_ID" ]; then return 0; fi
FIELD_ID=$(python3 -c "
import json,sys,os
try:
s='${REPO_ROOT}/.mgw/board-schema.json'
if os.path.exists(s):
print(json.load(open(s)).get('fields',{}).get('status',{}).get('field_id',''))
else:
p=json.load(open('${REPO_ROOT}/.mgw/project.json'))
print(p.get('project',{}).get('project_board',{}).get('fields',{}).get('status',{}).get('field_id',''))
except: print('')
" 2>/dev/null || echo "")
if [ -z "$FIELD_ID" ]; then return 0; fi
OPTION_ID=$(python3 -c "
import json,sys,os
try:
stage='${NEW_STAGE}'
s='${REPO_ROOT}/.mgw/board-schema.json'
if os.path.exists(s):
print(json.load(open(s)).get('fields',{}).get('status',{}).get('options',{}).get(stage,''))
else:
p=json.load(open('${REPO_ROOT}/.mgw/project.json'))
print(p.get('project',{}).get('project_board',{}).get('fields',{}).get('status',{}).get('options',{}).get(stage,''))
except: print('')
" 2>/dev/null || echo "")
if [ -z "$OPTION_ID" ]; then return 0; fi
gh api graphql -f query='
mutation($projectId:ID!,$itemId:ID!,$fieldId:ID!,$optionId:String!){
updateProjectV2ItemFieldValue(input:{projectId:$projectId,itemId:$itemId,fieldId:$fieldId,value:{singleSelectOptionId:$optionId}}){projectV2Item{id}}
}
' -f projectId="$BOARD_NODE_ID" -f itemId="$ITEM_ID" \
-f fieldId="$FIELD_ID" -f optionId="$OPTION_ID" 2>/dev/null || true
}

update_board_agent_state() {
local ISSUE_NUMBER="$1"
local STATE_TEXT="$2"
if [ -z "$ISSUE_NUMBER" ]; then return 0; fi
BOARD_NODE_ID=$(python3 -c "
import json,sys,os
try:
p=json.load(open('${REPO_ROOT}/.mgw/project.json'))
print(p.get('project',{}).get('project_board',{}).get('node_id',''))
except: print('')
" 2>/dev/null || echo "")
if [ -z "$BOARD_NODE_ID" ]; then return 0; fi
ITEM_ID=$(python3 -c "
import json,sys
try:
p=json.load(open('${REPO_ROOT}/.mgw/project.json'))
for m in p.get('milestones',[]):
for i in m.get('issues',[]):
if i.get('github_number')==${ISSUE_NUMBER}:
print(i.get('board_item_id','')); sys.exit(0)
print('')
except: print('')
" 2>/dev/null || echo "")
if [ -z "$ITEM_ID" ]; then return 0; fi
FIELD_ID=$(python3 -c "
import json,sys,os
try:
s='${REPO_ROOT}/.mgw/board-schema.json'
if os.path.exists(s):
print(json.load(open(s)).get('fields',{}).get('ai_agent_state',{}).get('field_id',''))
else:
p=json.load(open('${REPO_ROOT}/.mgw/project.json'))
print(p.get('project',{}).get('project_board',{}).get('fields',{}).get('ai_agent_state',{}).get('field_id',''))
except: print('')
" 2>/dev/null || echo "")
if [ -z "$FIELD_ID" ]; then return 0; fi
gh api graphql -f query='
mutation($projectId:ID!,$itemId:ID!,$fieldId:ID!,$text:String!){
updateProjectV2ItemFieldValue(input:{projectId:$projectId,itemId:$itemId,fieldId:$fieldId,value:{text:$text}}){projectV2Item{id}}
}
' -f projectId="$BOARD_NODE_ID" -f itemId="$ITEM_ID" \
-f fieldId="$FIELD_ID" -f text="$STATE_TEXT" 2>/dev/null || true
}
```

Parse $ARGUMENTS for issue number. If missing:
```
AskUserQuestion(
Expand Down Expand Up @@ -248,7 +353,7 @@ Return ONLY valid JSON:
|---------------|--------|
| **informational** | Log: "MGW: ${NEW_COUNT} new comment(s) reviewed — informational, continuing." Update `triage.last_comment_count` in state file. Continue pipeline. |
| **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). |
| **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. |
| **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. |

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

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

Determine flags:
- "gsd:quick" → $QUICK_FLAGS = ""
Expand Down Expand Up @@ -373,6 +481,9 @@ mkdir -p "$QUICK_DIR"
```

3. **Spawn planner (task agent):**
```bash
update_board_agent_state $ISSUE_NUMBER "Planning" # non-blocking agent state
```
```
Task(
prompt="
Expand Down Expand Up @@ -475,6 +586,9 @@ If issues found and iteration < 2: spawn planner revision, then re-check.
If iteration >= 2: offer force proceed or abort.

7. **Spawn executor (task agent):**
```bash
update_board_agent_state $ISSUE_NUMBER "Executing" # non-blocking agent state
```
```
Task(
prompt="
Expand Down Expand Up @@ -506,6 +620,9 @@ VERIFY_RESULT=$(node ~/.claude/get-shit-done/bin/gsd-tools.cjs verify-summary "$
Parse JSON result. Use `passed` field for go/no-go. Checks summary existence, files created, and commits.

9. **(If --full) Spawn verifier:**
```bash
update_board_agent_state $ISSUE_NUMBER "Verifying" # non-blocking agent state
```
```
Task(
prompt="
Expand Down Expand Up @@ -539,6 +656,9 @@ node ~/.claude/get-shit-done/bin/gsd-tools.cjs commit "docs(quick-${next_num}):
```

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

<step name="execute_gsd_milestone">
Expand Down Expand Up @@ -576,6 +696,7 @@ Set pipeline_stage to "discussing" and apply "mgw:discussing" label:
```bash
gh issue edit ${ISSUE_NUMBER} --remove-label "mgw:in-progress" 2>/dev/null
gh issue edit ${ISSUE_NUMBER} --add-label "mgw:discussing" 2>/dev/null
update_board_status $ISSUE_NUMBER "discussing" # non-blocking board sync
```

Present to user:
Expand Down Expand Up @@ -623,6 +744,9 @@ If proceed: apply "mgw:approved" label and continue.
```

Update pipeline_stage to "planning" (at `${REPO_ROOT}/.mgw/active/`).
```bash
update_board_status $ISSUE_NUMBER "planning" # non-blocking board sync
```

2. **If resuming with pipeline_stage = "planning" and ROADMAP.md exists:**
Discover phases from ROADMAP and run the full per-phase GSD lifecycle:
Expand Down Expand Up @@ -661,6 +785,9 @@ If proceed: apply "mgw:approved" label and continue.
```

**b. Spawn planner agent (gsd:plan-phase):**
```bash
update_board_agent_state $ISSUE_NUMBER "Planning phase ${PHASE_NUMBER}" # non-blocking agent state
```
```
Task(
prompt="
Expand Down Expand Up @@ -707,6 +834,7 @@ If proceed: apply "mgw:approved" label and continue.
```bash
EXEC_INIT=$(node ~/.claude/get-shit-done/bin/gsd-tools.cjs init execute-phase "${PHASE_NUMBER}")
# Parse EXEC_INIT JSON for: executor_model, verifier_model, phase_dir, plans, incomplete_plans, plan_count
update_board_agent_state $ISSUE_NUMBER "Executing phase ${PHASE_NUMBER}" # non-blocking agent state
```
```
Task(
Expand Down Expand Up @@ -739,6 +867,9 @@ If proceed: apply "mgw:approved" label and continue.
```

**e. Spawn verifier agent (gsd:verify-phase):**
```bash
update_board_agent_state $ISSUE_NUMBER "Verifying phase ${PHASE_NUMBER}" # non-blocking agent state
```
```
Task(
prompt="
Expand Down Expand Up @@ -785,6 +916,9 @@ COMMENTEOF
```

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

<step name="post_execution_update">
Expand Down Expand Up @@ -822,6 +956,9 @@ gh issue comment ${ISSUE_NUMBER} --body "$EXEC_BODY" 2>/dev/null || true
```

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

<step name="create_pr">
Expand Down Expand Up @@ -992,6 +1129,12 @@ Update state (at `${REPO_ROOT}/.mgw/active/`):
- linked_pr = PR number
- pipeline_stage = "pr-created"

```bash
update_board_status $ISSUE_NUMBER "pr-created" # non-blocking board sync
update_board_agent_state $ISSUE_NUMBER "" # clear agent state after PR creation (non-blocking)
sync_pr_to_board $ISSUE_NUMBER $PR_NUMBER # non-blocking — add PR as board item
```

Add cross-ref (at `${REPO_ROOT}/.mgw/cross-refs.json`): issue → PR.
</step>

Expand Down Expand Up @@ -1052,6 +1195,9 @@ gh issue comment ${ISSUE_NUMBER} --body "$PR_READY_BODY" 2>/dev/null || true
```

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

Report to user:
```
Expand Down Expand Up @@ -1092,5 +1238,10 @@ Next:
- [ ] Worktree cleaned up, user returned to main workspace
- [ ] mgw:in-progress label removed at completion
- [ ] State file updated through all pipeline stages
- [ ] Board Status field synced at each pipeline_stage transition (non-blocking)
- [ ] AI Agent State field set before each GSD agent spawn (non-blocking)
- [ ] AI Agent State field cleared after PR creation (non-blocking)
- [ ] PR added to board as board item after creation (non-blocking)
- [ ] Board sync failures never block pipeline execution
- [ ] User prompted to run /mgw:sync after merge
</success_criteria>
Loading