diff --git a/commands/status.md b/commands/status.md index 8213ad9..59f787b 100644 --- a/commands/status.md +++ b/commands/status.md @@ -1,7 +1,7 @@ --- name: mgw:status description: Project status dashboard — milestone progress, issue pipeline stages, open PRs -argument-hint: "[milestone_number] [--json]" +argument-hint: "[milestone_number] [--json] [--board]" allowed-tools: - Bash - Read @@ -34,10 +34,12 @@ Repo detected via: gh repo view --json nameWithOwner -q .nameWithOwner ```bash MILESTONE_NUM="" JSON_OUTPUT=false +OPEN_BOARD=false for ARG in $ARGUMENTS; do case "$ARG" in --json) JSON_OUTPUT=true ;; + --board) OPEN_BOARD=true ;; [0-9]*) MILESTONE_NUM="$ARG" ;; esac done @@ -52,6 +54,7 @@ REPO_ROOT=$(git rev-parse --show-toplevel) MGW_DIR="${REPO_ROOT}/.mgw" REPO_NAME=$(gh repo view --json nameWithOwner -q .nameWithOwner 2>/dev/null || basename "$REPO_ROOT") +BOARD_URL="" if [ ! -f "${MGW_DIR}/project.json" ]; then # No project.json — fall back to GitHub-only mode FALLBACK_MODE=true @@ -124,9 +127,38 @@ print(json.dumps(m)) MILESTONE_NAME=$(echo "$MILESTONE_DATA" | python3 -c "import json,sys; print(json.load(sys.stdin)['name'])") ISSUES_JSON=$(echo "$MILESTONE_DATA" | python3 -c "import json,sys; print(json.dumps(json.load(sys.stdin)['issues']))") TOTAL_ISSUES=$(echo "$ISSUES_JSON" | python3 -c "import json,sys; print(len(json.load(sys.stdin)))") + +# Extract board URL from project.json (top-level board_url or nested board.url) +BOARD_URL=$(echo "$PROJECT_JSON" | python3 -c " +import json, sys +p = json.load(sys.stdin) +# Check top-level board_url first, then board.url (nested) +url = p.get('board_url') or (p.get('board') or {}).get('url', '') +print(url or '') +" 2>/dev/null || echo "") ``` + +**Handle --board flag — open board in browser and exit early:** + +```bash +if [ "$OPEN_BOARD" = true ]; then + if [ -z "$BOARD_URL" ]; then + echo "No board configured in project.json. Run /mgw:board create first." >&2 + exit 1 + fi + echo "Opening board: ${BOARD_URL}" + xdg-open "${BOARD_URL}" 2>/dev/null \ + || open "${BOARD_URL}" 2>/dev/null \ + || echo "Could not open browser. Board URL: ${BOARD_URL}" + exit 0 +fi +``` + +This step exits early — do not continue to the dashboard display. + + **Compute pipeline stage counts and progress:** @@ -172,6 +204,71 @@ print(json.dumps({ ``` + +**Compute milestone health metrics — velocity, done count, blocked count:** + +```bash +HEALTH_DATA=$(echo "$ISSUES_JSON" | python3 -c " +import json, sys, os, glob + +issues = json.load(sys.stdin) +repo_root = os.environ.get('REPO_ROOT', os.getcwd()) +mgw_dir = os.path.join(repo_root, '.mgw') + +done_stages = {'done', 'pr-created'} +blocked_stages = {'blocked'} + +done_count = 0 +blocked_count = 0 +done_timestamps = [] + +for issue in issues: + stage = issue.get('pipeline_stage', 'new') + num = issue.get('github_number', 0) + + if stage in done_stages: + done_count += 1 + # Use .mgw/active/ or .mgw/completed/ file mtime as done timestamp proxy + for subdir in ['active', 'completed']: + pattern = os.path.join(mgw_dir, subdir, str(num) + '-*.json') + matches = glob.glob(pattern) + if matches: + try: + done_timestamps.append(os.path.getmtime(matches[0])) + except Exception: + pass + break + elif stage in blocked_stages: + blocked_count += 1 + +# Compute velocity (issues completed per day) +if done_count == 0: + velocity_str = '0/day' +elif len(done_timestamps) >= 2: + span_days = (max(done_timestamps) - min(done_timestamps)) / 86400.0 + if span_days >= 0.1: + velocity_str = '{:.1f}/day'.format(done_count / span_days) + else: + velocity_str = str(done_count) + ' (same day)' +elif done_count == 1: + velocity_str = '1 (single)' +else: + velocity_str = str(done_count) + '/day' + +import json as json2 +print(json2.dumps({ + 'done_count': done_count, + 'blocked_count': blocked_count, + 'velocity': velocity_str +})) +") + +HEALTH_DONE=$(echo "$HEALTH_DATA" | python3 -c "import json,sys; print(json.load(sys.stdin)['done_count'])" 2>/dev/null || echo "0") +HEALTH_BLOCKED=$(echo "$HEALTH_DATA" | python3 -c "import json,sys; print(json.load(sys.stdin)['blocked_count'])" 2>/dev/null || echo "0") +HEALTH_VELOCITY=$(echo "$HEALTH_DATA" | python3 -c "import json,sys; print(json.load(sys.stdin)['velocity'])" 2>/dev/null || echo "N/A") +``` + + **Build per-issue status lines:** @@ -243,6 +340,13 @@ fi **Display the status dashboard:** +```bash +# Print board URL prominently at top if configured +if [ -n "$BOARD_URL" ]; then + echo " Board: ${BOARD_URL}" +fi +``` + ``` ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ MGW > PROJECT STATUS: ${REPO_NAME} @@ -256,16 +360,51 @@ Progress: ${bar} ${pct}% #37 ⏳ new /mgw:status dashboard #38 🔒 blocked contextual routing (blocked by #37) +Milestone Health: + Completed: ${HEALTH_DONE}/${TOTAL_ISSUES} + Velocity: ${HEALTH_VELOCITY} + Blocked: ${HEALTH_BLOCKED} + Open PRs: #40 ← #36 comment-aware pipeline (review requested) Next Milestone: ${next_name} (${next_done}/${next_total} done) ``` +Full display example with board configured: +``` + Board: https://github.com/orgs/snipcodeit/projects/1 +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + MGW > PROJECT STATUS: snipcodeit/mgw +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +Current Milestone: v2 — Team Collaboration (3/6 done) +Progress: ████████░░░░░░░░ 50% + + #80 ✅ done Add mgw:assign command + #81 ✅ done Post board link to Discussions + #82 ✅ done Add mgw:board sync + #83 🔄 executing Add milestone health report + #84 ⏳ new Create mgw:roadmap command + #85 ⏳ new Add growth analytics + +Milestone Health: + Completed: 3/6 + Velocity: 2.1/day + Blocked: 0 + +Open PRs: + (none matched to this milestone) + +Next Milestone: v3 — Analytics & Extensions (0/5 done) +``` + Rendering rules: +- Print board URL line (` Board: ${BOARD_URL}`) only when BOARD_URL is non-empty - Use stage icons from the issue table - Right-align issue numbers - Truncate titles to 50 chars +- Milestone Health section always appears in project mode (after issue table, before Open PRs) - If no open PRs matched to milestone, show "No open PRs for this milestone." - If no next milestone, show "No more milestones planned." - If `TARGET_MILESTONE != CURRENT_MILESTONE`, add "(viewing milestone ${TARGET_MILESTONE})" to header @@ -282,6 +421,7 @@ import json result = { 'repo': '${REPO_NAME}', + 'board_url': '${BOARD_URL}', 'current_milestone': ${CURRENT_MILESTONE}, 'viewing_milestone': ${TARGET_MILESTONE}, 'milestone': { @@ -289,6 +429,12 @@ result = { 'total_issues': ${TOTAL_ISSUES}, 'done': done_count, 'progress_pct': pct, + 'health': { + 'done': int('${HEALTH_DONE}' or '0'), + 'total': ${TOTAL_ISSUES}, + 'blocked': int('${HEALTH_BLOCKED}' or '0'), + 'velocity': '${HEALTH_VELOCITY}' + }, 'issues': issues_with_stages }, 'open_prs': matched_prs, @@ -305,32 +451,39 @@ The JSON structure: ```json { "repo": "owner/repo", + "board_url": "https://github.com/orgs/snipcodeit/projects/1", "current_milestone": 2, "viewing_milestone": 2, "milestone": { - "name": "v1 — Pipeline Intelligence", - "total_issues": 4, - "done": 2, + "name": "v2 — Team Collaboration & Lifecycle Orchestration", + "total_issues": 6, + "done": 3, "progress_pct": 50, + "health": { + "done": 3, + "total": 6, + "blocked": 0, + "velocity": "2.1/day" + }, "issues": [ { - "number": 35, - "title": "refactor: remove .planning/ writes", + "number": 80, + "title": "Add mgw:assign command", "pipeline_stage": "done", - "labels": ["refactor"] + "labels": ["enhancement"] } ] }, "open_prs": [ { - "number": 40, - "title": "comment-aware pipeline", - "linked_issue": 36, - "review_status": "review_requested" + "number": 95, + "title": "Add mgw:assign command", + "linked_issue": 80, + "review_status": "approved" } ], "next_milestone": { - "name": "v1 — NPM Publishing & Distribution", + "name": "v3 — Analytics & Extensions", "total_issues": 5, "done": 0 } @@ -351,4 +504,11 @@ The JSON structure: - [ ] Milestone number argument selects non-current milestone - [ ] Read-only: no state modifications, no GitHub writes - [ ] No agent spawns, no side effects +- [ ] Board URL displayed before header when board_url is set in project.json +- [ ] --board flag opens board URL via xdg-open (open on macOS fallback) and exits 0 +- [ ] --board flag exits 1 with helpful error when no board configured +- [ ] Milestone Health section shows Completed N/total, Velocity, and Blocked count +- [ ] Velocity computed from .mgw/active/ and .mgw/completed/ file mtimes +- [ ] --json output includes board_url and milestone.health object +- [ ] Board URL line omitted when board_url is not set in project.json