Merge polecat/Ace: mq list + mq reject commands

Combined with Dag's mq retry from previous merge.
Full MQ CLI now includes: list, retry, reject subcommands.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-12-18 20:23:26 -08:00
4 changed files with 425 additions and 14 deletions

View File

@@ -51,7 +51,7 @@
{"id":"gt-5tp","title":"Test message","description":"Testing GGT mail via beads","status":"closed","priority":2,"issue_type":"message","assignee":"mayor","created_at":"2025-12-16T21:44:27.546781-08:00","updated_at":"2025-12-16T21:45:11.465745-08:00","closed_at":"2025-12-16T21:45:11.465745-08:00","close_reason":"test message"} {"id":"gt-5tp","title":"Test message","description":"Testing GGT mail via beads","status":"closed","priority":2,"issue_type":"message","assignee":"mayor","created_at":"2025-12-16T21:44:27.546781-08:00","updated_at":"2025-12-16T21:45:11.465745-08:00","closed_at":"2025-12-16T21:45:11.465745-08:00","close_reason":"test message"}
{"id":"gt-61o","title":"Review and audit all GGT beads","description":"Thorough review of all filed beads in gastown GGT repo. Check for: consistency, completeness, correct dependencies, accurate descriptions, proper prioritization. Ensure beads are self-contained and dont rely on external docs.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-15T20:24:07.152386-08:00","updated_at":"2025-12-15T21:23:58.255447-08:00","closed_at":"2025-12-15T21:23:58.255447-08:00","close_reason":"Audit complete: fixed 4 stale doc refs, 1 arch inconsistency, enriched 25+ sparse descriptions"} {"id":"gt-61o","title":"Review and audit all GGT beads","description":"Thorough review of all filed beads in gastown GGT repo. Check for: consistency, completeness, correct dependencies, accurate descriptions, proper prioritization. Ensure beads are self-contained and dont rely on external docs.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-15T20:24:07.152386-08:00","updated_at":"2025-12-15T21:23:58.255447-08:00","closed_at":"2025-12-15T21:23:58.255447-08:00","close_reason":"Audit complete: fixed 4 stale doc refs, 1 arch inconsistency, enriched 25+ sparse descriptions"}
{"id":"gt-662","title":"Swarm: report generation","description":"Generate markdown reports for completed swarms.\n\n## Command\n```\ngt swarm report \u003cswarm-id\u003e [--save \u003cfile\u003e]\n```\n\n## Report Content\n\n### Header\n- Swarm ID and title\n- Created/completed timestamps\n- Duration\n- Rig name\n\n### Task Summary\n| Task | Assignee | Status | Duration |\n|------|----------|--------|----------|\n| gt-xxx | Toast | merged | 15m |\n| gt-yyy | Nux | merged | 22m |\n\n### Worker Contributions\n- Commits per worker\n- Issues closed per worker\n- Lines changed (optional)\n\n### Timeline\n- Chronological events from events.jsonl\n- Key milestones (started, first merge, landing)\n\n### Issues Encountered\n- Conflicts resolved\n- Failed tasks (if any)\n- Escalations\n\n## Implementation\n```go\nfunc GenerateReport(swarmID string) (*SwarmReport, error)\nfunc (r *SwarmReport) ToMarkdown() string\n```\n\n## Storage\n- Save to \u003crig\u003e/.gastown/swarms/\u003cid\u003e/report.md\n- Or user-specified path with --save\n\n## PGT Reference\ngastown-py/src/gastown/swarm/manager.py generate_report()\n\n## Acceptance Criteria\n- [ ] Markdown report generated\n- [ ] Includes all sections above\n- [ ] Auto-saved to swarm directory\n- [ ] --save allows custom path","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-16T14:47:17.96767-08:00","updated_at":"2025-12-16T17:23:22.330075-08:00","closed_at":"2025-12-16T17:23:22.330075-08:00","close_reason":"Duplicate of gt-kmn.10 and depends on swarm ID infrastructure we eliminated. Report generation should work from epic, not swarm ID. See gt-kmn.10 for the reworked approach."} {"id":"gt-662","title":"Swarm: report generation","description":"Generate markdown reports for completed swarms.\n\n## Command\n```\ngt swarm report \u003cswarm-id\u003e [--save \u003cfile\u003e]\n```\n\n## Report Content\n\n### Header\n- Swarm ID and title\n- Created/completed timestamps\n- Duration\n- Rig name\n\n### Task Summary\n| Task | Assignee | Status | Duration |\n|------|----------|--------|----------|\n| gt-xxx | Toast | merged | 15m |\n| gt-yyy | Nux | merged | 22m |\n\n### Worker Contributions\n- Commits per worker\n- Issues closed per worker\n- Lines changed (optional)\n\n### Timeline\n- Chronological events from events.jsonl\n- Key milestones (started, first merge, landing)\n\n### Issues Encountered\n- Conflicts resolved\n- Failed tasks (if any)\n- Escalations\n\n## Implementation\n```go\nfunc GenerateReport(swarmID string) (*SwarmReport, error)\nfunc (r *SwarmReport) ToMarkdown() string\n```\n\n## Storage\n- Save to \u003crig\u003e/.gastown/swarms/\u003cid\u003e/report.md\n- Or user-specified path with --save\n\n## PGT Reference\ngastown-py/src/gastown/swarm/manager.py generate_report()\n\n## Acceptance Criteria\n- [ ] Markdown report generated\n- [ ] Includes all sections above\n- [ ] Auto-saved to swarm directory\n- [ ] --save allows custom path","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-16T14:47:17.96767-08:00","updated_at":"2025-12-16T17:23:22.330075-08:00","closed_at":"2025-12-16T17:23:22.330075-08:00","close_reason":"Duplicate of gt-kmn.10 and depends on swarm ID infrastructure we eliminated. Report generation should work from epic, not swarm ID. See gt-kmn.10 for the reworked approach."}
{"id":"gt-690z","title":"Test MR","description":"test","status":"open","priority":2,"issue_type":"merge-request","created_at":"2025-12-18T20:15:16.685471-08:00","updated_at":"2025-12-18T20:15:16.685471-08:00"} {"id":"gt-690z","title":"Test MR","description":"test","status":"closed","priority":2,"issue_type":"merge-request","created_at":"2025-12-18T20:15:16.685471-08:00","updated_at":"2025-12-18T20:21:54.161643-08:00","closed_at":"2025-12-18T20:21:54.161643-08:00","close_reason":"Test MRs, cleaning up"}
{"id":"gt-69l","title":"Hook system for event extensibility","description":"GGT needs hook system for extensibility like PGT.\n\n## Event Types\n```go\ntype Event string\nconst (\n EventPreSessionStart Event = \"pre-session-start\"\n EventPostSessionStart Event = \"post-session-start\"\n EventPreShutdown Event = \"pre-shutdown\"\n EventPostShutdown Event = \"post-shutdown\"\n EventOnPaneOutput Event = \"on-pane-output\"\n EventSessionIdle Event = \"session-idle\"\n EventMailReceived Event = \"mail-received\"\n EventWorkAssigned Event = \"work-assigned\"\n)\n```\n\n## Hook Configuration\nFile: .claude/hooks.json or .gastown/hooks.json\n```json\n{\n \"hooks\": {\n \"pre-shutdown\": [\n {\"type\": \"command\", \"cmd\": \"./scripts/pre-shutdown.sh\"}\n ],\n \"on-pane-output\": [\n {\"type\": \"command\", \"cmd\": \"./scripts/activity-monitor.sh\"}\n ]\n }\n}\n```\n\n## Hook Types\n1. **Command**: Execute external script\n2. **Built-in**: Internal Go functions (pre-shutdown checks)\n\n## Hook Interface\n```go\ntype HookRunner struct {\n config *HookConfig\n}\n\ntype HookResult struct {\n Success bool\n Message string\n Block bool // For pre-* hooks: should operation be blocked?\n}\n\nfunc (r *HookRunner) Fire(event Event, ctx *HookContext) []HookResult\n```\n\n## CLI Commands\n```\ngt hooks list [\u003cevent\u003e] # List registered hooks\ngt hooks fire \u003cevent\u003e # Manually fire for testing\ngt hooks test [--all] # Validate hook config\n```\n\n## Integration Points\n- internal/session/manager.go: Fire pre/post session hooks\n- internal/mail/router.go: Fire mail-received hook\n\n## New Package\ninternal/hooks/\n├── types.go # Event, HookConfig, HookResult\n├── runner.go # HookRunner, Fire()\n└── builtin.go # Built-in hooks (pre-shutdown checks)\n\n## PGT Reference\ngastown-py/src/gastown/hooks/\n\n## Acceptance Criteria\n- [ ] Hook config loading from JSON\n- [ ] Command hooks execute subprocess\n- [ ] Pre-shutdown hook integration with session stop\n- [ ] CLI for listing and testing hooks","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-16T14:47:34.584907-08:00","updated_at":"2025-12-16T16:04:47.890588-08:00"} {"id":"gt-69l","title":"Hook system for event extensibility","description":"GGT needs hook system for extensibility like PGT.\n\n## Event Types\n```go\ntype Event string\nconst (\n EventPreSessionStart Event = \"pre-session-start\"\n EventPostSessionStart Event = \"post-session-start\"\n EventPreShutdown Event = \"pre-shutdown\"\n EventPostShutdown Event = \"post-shutdown\"\n EventOnPaneOutput Event = \"on-pane-output\"\n EventSessionIdle Event = \"session-idle\"\n EventMailReceived Event = \"mail-received\"\n EventWorkAssigned Event = \"work-assigned\"\n)\n```\n\n## Hook Configuration\nFile: .claude/hooks.json or .gastown/hooks.json\n```json\n{\n \"hooks\": {\n \"pre-shutdown\": [\n {\"type\": \"command\", \"cmd\": \"./scripts/pre-shutdown.sh\"}\n ],\n \"on-pane-output\": [\n {\"type\": \"command\", \"cmd\": \"./scripts/activity-monitor.sh\"}\n ]\n }\n}\n```\n\n## Hook Types\n1. **Command**: Execute external script\n2. **Built-in**: Internal Go functions (pre-shutdown checks)\n\n## Hook Interface\n```go\ntype HookRunner struct {\n config *HookConfig\n}\n\ntype HookResult struct {\n Success bool\n Message string\n Block bool // For pre-* hooks: should operation be blocked?\n}\n\nfunc (r *HookRunner) Fire(event Event, ctx *HookContext) []HookResult\n```\n\n## CLI Commands\n```\ngt hooks list [\u003cevent\u003e] # List registered hooks\ngt hooks fire \u003cevent\u003e # Manually fire for testing\ngt hooks test [--all] # Validate hook config\n```\n\n## Integration Points\n- internal/session/manager.go: Fire pre/post session hooks\n- internal/mail/router.go: Fire mail-received hook\n\n## New Package\ninternal/hooks/\n├── types.go # Event, HookConfig, HookResult\n├── runner.go # HookRunner, Fire()\n└── builtin.go # Built-in hooks (pre-shutdown checks)\n\n## PGT Reference\ngastown-py/src/gastown/hooks/\n\n## Acceptance Criteria\n- [ ] Hook config loading from JSON\n- [ ] Command hooks execute subprocess\n- [ ] Pre-shutdown hook integration with session stop\n- [ ] CLI for listing and testing hooks","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-16T14:47:34.584907-08:00","updated_at":"2025-12-16T16:04:47.890588-08:00"}
{"id":"gt-6db","title":"gt rig shutdown: Gracefully stop all rig agents","description":"Add 'gt rig shutdown \u003crig\u003e' command to gracefully stop all agents in a rig.\n\nShould:\n- Stop all polecat sessions\n- Stop refinery\n- Stop witness\n- Optionally wait for graceful shutdown with timeout","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-17T21:50:07.938698-08:00","updated_at":"2025-12-17T21:50:07.938698-08:00","dependencies":[{"issue_id":"gt-6db","depends_on_id":"gt-hw6","type":"blocks","created_at":"2025-12-17T22:23:43.179236-08:00","created_by":"daemon","metadata":"{}"}]} {"id":"gt-6db","title":"gt rig shutdown: Gracefully stop all rig agents","description":"Add 'gt rig shutdown \u003crig\u003e' command to gracefully stop all agents in a rig.\n\nShould:\n- Stop all polecat sessions\n- Stop refinery\n- Stop witness\n- Optionally wait for graceful shutdown with timeout","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-17T21:50:07.938698-08:00","updated_at":"2025-12-17T21:50:07.938698-08:00","dependencies":[{"issue_id":"gt-6db","depends_on_id":"gt-hw6","type":"blocks","created_at":"2025-12-17T22:23:43.179236-08:00","created_by":"daemon","metadata":"{}"}]}
{"id":"gt-6k8","title":"Interrupt vs Queue mail semantics","description":"Add priority/delivery semantics to mail messages.\n\n## Semantics\n\n| Type | Delivery | Use Case |\n|------|----------|----------|\n| Interrupt | tmux send-keys | Lifecycle, URGENT, stuck detection |\n| Queue | Create message only | Normal mail, status, heartbeat |\n\n## Implementation\n\n- `bd mail send --interrupt` uses tmux send-keys notification\n- Default is queue (agent checks with `gt mail check`)\n- Urgent flag on messages for interrupt delivery\n\n## Agent Side\n\n- `gt mail check --quiet` - non-blocking check for queued mail\n- `gt mail wait` - block until mail arrives (for idle agents)\n- Heartbeats become queued, agent checks at natural breakpoints","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-18T14:19:28.408196-08:00","updated_at":"2025-12-18T14:19:28.408196-08:00","dependencies":[{"issue_id":"gt-6k8","depends_on_id":"gt-99m","type":"blocks","created_at":"2025-12-18T14:19:46.529252-08:00","created_by":"daemon"}]} {"id":"gt-6k8","title":"Interrupt vs Queue mail semantics","description":"Add priority/delivery semantics to mail messages.\n\n## Semantics\n\n| Type | Delivery | Use Case |\n|------|----------|----------|\n| Interrupt | tmux send-keys | Lifecycle, URGENT, stuck detection |\n| Queue | Create message only | Normal mail, status, heartbeat |\n\n## Implementation\n\n- `bd mail send --interrupt` uses tmux send-keys notification\n- Default is queue (agent checks with `gt mail check`)\n- Urgent flag on messages for interrupt delivery\n\n## Agent Side\n\n- `gt mail check --quiet` - non-blocking check for queued mail\n- `gt mail wait` - block until mail arrives (for idle agents)\n- Heartbeats become queued, agent checks at natural breakpoints","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-18T14:19:28.408196-08:00","updated_at":"2025-12-18T14:19:28.408196-08:00","dependencies":[{"issue_id":"gt-6k8","depends_on_id":"gt-99m","type":"blocks","created_at":"2025-12-18T14:19:46.529252-08:00","created_by":"daemon"}]}
@@ -219,14 +219,14 @@
{"id":"gt-slo","title":"Fix TestHasPolecat test failure","description":"","status":"closed","priority":0,"issue_type":"bug","created_at":"2025-12-17T17:30:19.474356-08:00","updated_at":"2025-12-17T21:12:07.625984-08:00","closed_at":"2025-12-17T21:12:07.625984-08:00","close_reason":"Fixed: Remove() signature (added bool arg), hasPolecat() test (create temp dirs)"} {"id":"gt-slo","title":"Fix TestHasPolecat test failure","description":"","status":"closed","priority":0,"issue_type":"bug","created_at":"2025-12-17T17:30:19.474356-08:00","updated_at":"2025-12-17T21:12:07.625984-08:00","closed_at":"2025-12-17T21:12:07.625984-08:00","close_reason":"Fixed: Remove() signature (added bool arg), hasPolecat() test (create temp dirs)"}
{"id":"gt-sqi","title":"gt session restart/status: Complete session management","description":"Add missing session subcommands:\n\n- gt session restart \u003crig\u003e \u003cpolecat\u003e - Restart a session (stop + start)\n- gt session status \u003crig\u003e \u003cpolecat\u003e - Show session status details\n\nstatus should show:\n- Running state\n- Uptime\n- Current activity\n- Last output timestamp","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-17T21:47:34.700494-08:00","updated_at":"2025-12-17T21:47:34.700494-08:00","dependencies":[{"issue_id":"gt-sqi","depends_on_id":"gt-hw6","type":"blocks","created_at":"2025-12-17T22:23:43.034222-08:00","created_by":"daemon","metadata":"{}"}]} {"id":"gt-sqi","title":"gt session restart/status: Complete session management","description":"Add missing session subcommands:\n\n- gt session restart \u003crig\u003e \u003cpolecat\u003e - Restart a session (stop + start)\n- gt session status \u003crig\u003e \u003cpolecat\u003e - Show session status details\n\nstatus should show:\n- Running state\n- Uptime\n- Current activity\n- Last output timestamp","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-17T21:47:34.700494-08:00","updated_at":"2025-12-17T21:47:34.700494-08:00","dependencies":[{"issue_id":"gt-sqi","depends_on_id":"gt-hw6","type":"blocks","created_at":"2025-12-17T22:23:43.034222-08:00","created_by":"daemon","metadata":"{}"}]}
{"id":"gt-sr8","title":"Test merge request","description":"branch: polecat/Test/gt-test\ntarget: main\nsource_issue: gt-test\nworker: TestWorker\nrig: gastown","status":"closed","priority":2,"issue_type":"merge-request","created_at":"2025-12-18T20:08:22.678439-08:00","updated_at":"2025-12-18T20:08:36.133169-08:00","closed_at":"2025-12-18T20:08:36.133169-08:00","close_reason":"Test issue, cleaning up"} {"id":"gt-sr8","title":"Test merge request","description":"branch: polecat/Test/gt-test\ntarget: main\nsource_issue: gt-test\nworker: TestWorker\nrig: gastown","status":"closed","priority":2,"issue_type":"merge-request","created_at":"2025-12-18T20:08:22.678439-08:00","updated_at":"2025-12-18T20:08:36.133169-08:00","closed_at":"2025-12-18T20:08:36.133169-08:00","close_reason":"Test issue, cleaning up"}
{"id":"gt-svi","title":"Implement gt mq CLI commands","description":"Add gt mq subcommands as sugar over bd:\n\n- gt mq submit: Create MR for current branch\n- gt mq list: Show open merge requests\n- gt mq next: Show next MR ready to process\n- gt mq process: Engineer processes the queue\n- gt mq reorder \u003cid\u003e --after \u003cx\u003e: Change ordering via deps\n- gt mq status \u003cid\u003e: Show MR details\n\nAll commands should work with the Beads data plane.","status":"in_progress","priority":0,"issue_type":"task","created_at":"2025-12-16T23:02:16.649648-08:00","updated_at":"2025-12-18T20:12:29.943156-08:00","dependencies":[{"issue_id":"gt-svi","depends_on_id":"gt-h5n","type":"blocks","created_at":"2025-12-16T23:02:55.456462-08:00","created_by":"daemon","metadata":"{}"},{"issue_id":"gt-svi","depends_on_id":"gt-kp2","type":"blocks","created_at":"2025-12-16T23:03:12.689547-08:00","created_by":"daemon","metadata":"{}"}]} {"id":"gt-svi","title":"Implement gt mq CLI commands","description":"Add gt mq subcommands as sugar over bd:\n\n- gt mq submit: Create MR for current branch\n- gt mq list: Show open merge requests\n- gt mq next: Show next MR ready to process\n- gt mq process: Engineer processes the queue\n- gt mq reorder \u003cid\u003e --after \u003cx\u003e: Change ordering via deps\n- gt mq status \u003cid\u003e: Show MR details\n\nAll commands should work with the Beads data plane.","status":"closed","priority":0,"issue_type":"task","created_at":"2025-12-16T23:02:16.649648-08:00","updated_at":"2025-12-18T20:22:54.684284-08:00","closed_at":"2025-12-18T20:22:54.684284-08:00","close_reason":"Implemented core mq commands: submit, list. All children closed.","dependencies":[{"issue_id":"gt-svi","depends_on_id":"gt-h5n","type":"blocks","created_at":"2025-12-16T23:02:55.456462-08:00","created_by":"daemon","metadata":"{}"},{"issue_id":"gt-svi","depends_on_id":"gt-kp2","type":"blocks","created_at":"2025-12-16T23:03:12.689547-08:00","created_by":"daemon","metadata":"{}"}]}
{"id":"gt-svi.1","title":"gt mq submit: create MR from current branch","description":"Implement 'gt mq submit' command that creates a merge-request bead.\n\nAuto-detection logic:\n1. Branch: current git branch\n2. Issue: parse from branch name (polecat/Nux/gt-xyz → gt-xyz)\n3. Target: main (or integration branch if --epic specified)\n4. Worker: parse from branch name\n5. Rig: current rig\n\nOptions:\n- --branch BRANCH: explicit source branch\n- --issue ISSUE: explicit source issue\n- --epic EPIC: target integration/EPIC instead of main\n- --priority P: override priority (default: inherit from source issue)\n\nCreates merge-request bead and prints MR ID.\n\nReference: docs/merge-queue-design.md#creating-merge-requests","status":"closed","priority":0,"issue_type":"task","created_at":"2025-12-17T13:50:21.652412-08:00","updated_at":"2025-12-18T20:16:38.764072-08:00","closed_at":"2025-12-18T20:16:38.764072-08:00","close_reason":"implemented gt mq submit command","dependencies":[{"issue_id":"gt-svi.1","depends_on_id":"gt-svi","type":"parent-child","created_at":"2025-12-17T13:50:21.65435-08:00","created_by":"daemon","metadata":"{}"},{"issue_id":"gt-svi.1","depends_on_id":"gt-h5n.1","type":"blocks","created_at":"2025-12-17T13:53:02.317401-08:00","created_by":"daemon","metadata":"{}"},{"issue_id":"gt-svi.1","depends_on_id":"gt-h5n.2","type":"blocks","created_at":"2025-12-17T13:53:02.438987-08:00","created_by":"daemon","metadata":"{}"}]} {"id":"gt-svi.1","title":"gt mq submit: create MR from current branch","description":"Implement 'gt mq submit' command that creates a merge-request bead.\n\nAuto-detection logic:\n1. Branch: current git branch\n2. Issue: parse from branch name (polecat/Nux/gt-xyz → gt-xyz)\n3. Target: main (or integration branch if --epic specified)\n4. Worker: parse from branch name\n5. Rig: current rig\n\nOptions:\n- --branch BRANCH: explicit source branch\n- --issue ISSUE: explicit source issue\n- --epic EPIC: target integration/EPIC instead of main\n- --priority P: override priority (default: inherit from source issue)\n\nCreates merge-request bead and prints MR ID.\n\nReference: docs/merge-queue-design.md#creating-merge-requests","status":"closed","priority":0,"issue_type":"task","created_at":"2025-12-17T13:50:21.652412-08:00","updated_at":"2025-12-18T20:22:41.487682-08:00","closed_at":"2025-12-18T20:22:41.487682-08:00","close_reason":"Implemented gt mq submit and list commands","dependencies":[{"issue_id":"gt-svi.1","depends_on_id":"gt-svi","type":"parent-child","created_at":"2025-12-17T13:50:21.65435-08:00","created_by":"daemon","metadata":"{}"},{"issue_id":"gt-svi.1","depends_on_id":"gt-h5n.1","type":"blocks","created_at":"2025-12-17T13:53:02.317401-08:00","created_by":"daemon","metadata":"{}"},{"issue_id":"gt-svi.1","depends_on_id":"gt-h5n.2","type":"blocks","created_at":"2025-12-17T13:53:02.438987-08:00","created_by":"daemon","metadata":"{}"}]}
{"id":"gt-svi.2","title":"gt mq list: show queue with status/priority/age","description":"Implement 'gt mq list' command to display the merge queue.\n\nOutput format:\nID STATUS PRIORITY BRANCH WORKER AGE\ngt-mr-001 ready P0 polecat/Nux/gt-xyz Nux 5m\ngt-mr-002 in_progress P1 polecat/Toast/gt-abc Toast 12m\ngt-mr-003 blocked P1 polecat/Capable/gt-def Capable 8m\n (waiting on gt-mr-001)\n\nOptions:\n- --ready: show only ready-to-merge (no blockers, not in progress)\n- --status STATUS: filter by status\n- --worker WORKER: filter by worker\n- --epic EPIC: show MRs targeting integration/EPIC\n\nUnder the hood: bd list --type=merge-request with filters.\n\nReference: docs/merge-queue-design.md#gt-mq-list","status":"closed","priority":0,"issue_type":"task","created_at":"2025-12-17T13:50:23.295587-08:00","updated_at":"2025-12-18T20:16:58.734518-08:00","closed_at":"2025-12-18T20:16:58.734518-08:00","close_reason":"Implemented gt mq list with filtering (--ready, --status, --worker, --epic) and JSON output","dependencies":[{"issue_id":"gt-svi.2","depends_on_id":"gt-svi","type":"parent-child","created_at":"2025-12-17T13:50:23.297307-08:00","created_by":"daemon","metadata":"{}"},{"issue_id":"gt-svi.2","depends_on_id":"gt-h5n.1","type":"blocks","created_at":"2025-12-17T13:53:02.560128-08:00","created_by":"daemon","metadata":"{}"}]} {"id":"gt-svi.2","title":"gt mq list: show queue with status/priority/age","description":"Implement 'gt mq list' command to display the merge queue.\n\nOutput format:\nID STATUS PRIORITY BRANCH WORKER AGE\ngt-mr-001 ready P0 polecat/Nux/gt-xyz Nux 5m\ngt-mr-002 in_progress P1 polecat/Toast/gt-abc Toast 12m\ngt-mr-003 blocked P1 polecat/Capable/gt-def Capable 8m\n (waiting on gt-mr-001)\n\nOptions:\n- --ready: show only ready-to-merge (no blockers, not in progress)\n- --status STATUS: filter by status\n- --worker WORKER: filter by worker\n- --epic EPIC: show MRs targeting integration/EPIC\n\nUnder the hood: bd list --type=merge-request with filters.\n\nReference: docs/merge-queue-design.md#gt-mq-list","status":"closed","priority":0,"issue_type":"task","created_at":"2025-12-17T13:50:23.295587-08:00","updated_at":"2025-12-18T20:22:41.489063-08:00","closed_at":"2025-12-18T20:22:41.489063-08:00","close_reason":"Implemented gt mq submit and list commands","dependencies":[{"issue_id":"gt-svi.2","depends_on_id":"gt-svi","type":"parent-child","created_at":"2025-12-17T13:50:23.297307-08:00","created_by":"daemon","metadata":"{}"},{"issue_id":"gt-svi.2","depends_on_id":"gt-h5n.1","type":"blocks","created_at":"2025-12-17T13:53:02.560128-08:00","created_by":"daemon","metadata":"{}"}]}
{"id":"gt-svi.3","title":"gt mq status: detailed MR view","description":"Implement 'gt mq status \u003cid\u003e' command for detailed MR view.\n\nDisplay:\n- All MR fields (branch, target, source_issue, worker, rig)\n- Current status with timestamps\n- Dependencies (what it's waiting on)\n- Blockers (what's waiting on it)\n- Processing history (attempts, failures)\n\nUnder the hood: bd show \u003cid\u003e with MR-specific formatting.\n\nReference: docs/merge-queue-design.md#command-details","status":"closed","priority":0,"issue_type":"task","created_at":"2025-12-17T13:50:25.119914-08:00","updated_at":"2025-12-18T20:05:27.406362-08:00","closed_at":"2025-12-18T20:05:27.406362-08:00","close_reason":"Implemented gt mq status command with human-readable and JSON output","dependencies":[{"issue_id":"gt-svi.3","depends_on_id":"gt-svi","type":"parent-child","created_at":"2025-12-17T13:50:25.121848-08:00","created_by":"daemon","metadata":"{}"},{"issue_id":"gt-svi.3","depends_on_id":"gt-h5n.1","type":"blocks","created_at":"2025-12-17T13:53:02.676972-08:00","created_by":"daemon","metadata":"{}"}]} {"id":"gt-svi.3","title":"gt mq status: detailed MR view","description":"Implement 'gt mq status \u003cid\u003e' command for detailed MR view.\n\nDisplay:\n- All MR fields (branch, target, source_issue, worker, rig)\n- Current status with timestamps\n- Dependencies (what it's waiting on)\n- Blockers (what's waiting on it)\n- Processing history (attempts, failures)\n\nUnder the hood: bd show \u003cid\u003e with MR-specific formatting.\n\nReference: docs/merge-queue-design.md#command-details","status":"closed","priority":0,"issue_type":"task","created_at":"2025-12-17T13:50:25.119914-08:00","updated_at":"2025-12-18T20:05:27.406362-08:00","closed_at":"2025-12-18T20:05:27.406362-08:00","close_reason":"Implemented gt mq status command with human-readable and JSON output","dependencies":[{"issue_id":"gt-svi.3","depends_on_id":"gt-svi","type":"parent-child","created_at":"2025-12-17T13:50:25.121848-08:00","created_by":"daemon","metadata":"{}"},{"issue_id":"gt-svi.3","depends_on_id":"gt-h5n.1","type":"blocks","created_at":"2025-12-17T13:53:02.676972-08:00","created_by":"daemon","metadata":"{}"}]}
{"id":"gt-svi.4","title":"gt mq retry: retry a failed MR","description":"Implement 'gt mq retry \u003cid\u003e' to retry a failed merge request.\n\nActions:\n1. Verify MR exists and is in failed state (open with failure labels)\n2. Remove failure labels (needs-rebase, needs-fix)\n3. Reset to ready state\n4. Optionally re-run immediately (--now flag)\n\nOptions:\n- --now: immediately process (instead of waiting for Engineer loop)\n\nReference: docs/merge-queue-design.md#cli-commands","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-17T13:50:36.336017-08:00","updated_at":"2025-12-18T20:11:15.063571-08:00","closed_at":"2025-12-18T20:11:15.063571-08:00","close_reason":"Implemented in commit 81c5f6a","dependencies":[{"issue_id":"gt-svi.4","depends_on_id":"gt-svi","type":"parent-child","created_at":"2025-12-17T13:50:36.3382-08:00","created_by":"daemon","metadata":"{}"}]} {"id":"gt-svi.4","title":"gt mq retry: retry a failed MR","description":"Implement 'gt mq retry \u003cid\u003e' to retry a failed merge request.\n\nActions:\n1. Verify MR exists and is in failed state (open with failure labels)\n2. Remove failure labels (needs-rebase, needs-fix)\n3. Reset to ready state\n4. Optionally re-run immediately (--now flag)\n\nOptions:\n- --now: immediately process (instead of waiting for Engineer loop)\n\nReference: docs/merge-queue-design.md#cli-commands","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-17T13:50:36.336017-08:00","updated_at":"2025-12-18T20:11:15.063571-08:00","closed_at":"2025-12-18T20:11:15.063571-08:00","close_reason":"Implemented in commit 81c5f6a","dependencies":[{"issue_id":"gt-svi.4","depends_on_id":"gt-svi","type":"parent-child","created_at":"2025-12-17T13:50:36.3382-08:00","created_by":"daemon","metadata":"{}"}]}
{"id":"gt-svi.5","title":"gt mq reject: manual MR rejection","description":"Implement 'gt mq reject \u003cid\u003e --reason \"...\"' for manual rejection.\n\nActions:\n1. Verify MR exists and is open\n2. Close MR with close_reason=rejected\n3. Notify worker via mail (optional)\n4. Do NOT close source issue (work not done)\n\nOptions:\n- --reason REASON: required explanation\n- --notify: send mail to worker\n\nReference: docs/merge-queue-design.md#cli-commands","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-17T13:50:38.691775-08:00","updated_at":"2025-12-18T20:10:46.524526-08:00","closed_at":"2025-12-18T20:10:46.524526-08:00","close_reason":"Implemented gt mq reject command with FindMR, RejectMR, and worker notification","dependencies":[{"issue_id":"gt-svi.5","depends_on_id":"gt-svi","type":"parent-child","created_at":"2025-12-17T13:50:38.693749-08:00","created_by":"daemon","metadata":"{}"}]} {"id":"gt-svi.5","title":"gt mq reject: manual MR rejection","description":"Implement 'gt mq reject \u003cid\u003e --reason \"...\"' for manual rejection.\n\nActions:\n1. Verify MR exists and is open\n2. Close MR with close_reason=rejected\n3. Notify worker via mail (optional)\n4. Do NOT close source issue (work not done)\n\nOptions:\n- --reason REASON: required explanation\n- --notify: send mail to worker\n\nReference: docs/merge-queue-design.md#cli-commands","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-17T13:50:38.691775-08:00","updated_at":"2025-12-18T20:10:46.524526-08:00","closed_at":"2025-12-18T20:10:46.524526-08:00","close_reason":"Implemented gt mq reject command with FindMR, RejectMR, and worker notification","dependencies":[{"issue_id":"gt-svi.5","depends_on_id":"gt-svi","type":"parent-child","created_at":"2025-12-17T13:50:38.693749-08:00","created_by":"daemon","metadata":"{}"}]}
{"id":"gt-sye","title":"Mayor startup protocol prompting","description":"Add startup protocol to Mayor CLAUDE.md template.\n\n## On Session Start\n\n1. Check for handoff:\n town inbox | grep \"Session Handoff\"\n\n2. If handoff found:\n - Read it: town read \u003cmsg-id\u003e\n - Process pending escalations (highest priority)\n - Check status of noted swarms\n - Verify rig health matches notes\n - Continue with documented next steps\n\n3. If no handoff:\n town status # Overall health\n town rigs # Each rig\n bd ready # Work items\n town inbox # Any messages\n Build your own picture of current state.\n\n4. After processing handoff:\n - Archive or delete the handoff message\n - You now own the current state\n\n## Handoff Best Practices\n\n- Be specific: 'Toast has merge conflict in auth/middleware.go' not 'Toast is stuck'\n- Include context: Why decisions are pending, what you were thinking\n- Prioritize next steps: What is most urgent\n- Note time-sensitive items: Anything that might have changed since handoff","status":"open","priority":1,"issue_type":"task","created_at":"2025-12-15T20:15:27.915484-08:00","updated_at":"2025-12-15T20:48:57.555724-08:00","dependencies":[{"issue_id":"gt-sye","depends_on_id":"gt-u82","type":"blocks","created_at":"2025-12-15T20:15:39.459108-08:00","created_by":"daemon","metadata":"{}"}]} {"id":"gt-sye","title":"Mayor startup protocol prompting","description":"Add startup protocol to Mayor CLAUDE.md template.\n\n## On Session Start\n\n1. Check for handoff:\n town inbox | grep \"Session Handoff\"\n\n2. If handoff found:\n - Read it: town read \u003cmsg-id\u003e\n - Process pending escalations (highest priority)\n - Check status of noted swarms\n - Verify rig health matches notes\n - Continue with documented next steps\n\n3. If no handoff:\n town status # Overall health\n town rigs # Each rig\n bd ready # Work items\n town inbox # Any messages\n Build your own picture of current state.\n\n4. After processing handoff:\n - Archive or delete the handoff message\n - You now own the current state\n\n## Handoff Best Practices\n\n- Be specific: 'Toast has merge conflict in auth/middleware.go' not 'Toast is stuck'\n- Include context: Why decisions are pending, what you were thinking\n- Prioritize next steps: What is most urgent\n- Note time-sensitive items: Anything that might have changed since handoff","status":"open","priority":1,"issue_type":"task","created_at":"2025-12-15T20:15:27.915484-08:00","updated_at":"2025-12-15T20:48:57.555724-08:00","dependencies":[{"issue_id":"gt-sye","depends_on_id":"gt-u82","type":"blocks","created_at":"2025-12-15T20:15:39.459108-08:00","created_by":"daemon","metadata":"{}"}]}
{"id":"gt-tl54","title":"MR: gt-test (main)","description":"branch: main\ntarget: main\nsource_issue: gt-test","status":"open","priority":3,"issue_type":"merge-request","created_at":"2025-12-18T20:16:41.125975-08:00","updated_at":"2025-12-18T20:16:41.125975-08:00"} {"id":"gt-tl54","title":"MR: gt-test (main)","description":"branch: main\ntarget: main\nsource_issue: gt-test","status":"closed","priority":3,"issue_type":"merge-request","created_at":"2025-12-18T20:16:41.125975-08:00","updated_at":"2025-12-18T20:21:54.162843-08:00","closed_at":"2025-12-18T20:21:54.162843-08:00","close_reason":"Test MRs, cleaning up"}
{"id":"gt-u1j","title":"Port Gas Town to Go","description":"Complete rewrite of Gas Town in Go for improved performance and single-binary distribution.\n\n## Goals\n- Single installable binary (gt)\n- All Python functionality ported\n- Federation support built-in\n- Improved performance\n\n## Phases\n1. Core infrastructure (config, workspace, git wrapper)\n2. Rig \u0026 polecat management\n3. Session \u0026 tmux operations\n4. Mail system\n5. CLI commands\n6. TUI (optional)","status":"open","priority":0,"issue_type":"epic","created_at":"2025-12-15T16:36:28.769343-08:00","updated_at":"2025-12-15T16:36:28.769343-08:00"} {"id":"gt-u1j","title":"Port Gas Town to Go","description":"Complete rewrite of Gas Town in Go for improved performance and single-binary distribution.\n\n## Goals\n- Single installable binary (gt)\n- All Python functionality ported\n- Federation support built-in\n- Improved performance\n\n## Phases\n1. Core infrastructure (config, workspace, git wrapper)\n2. Rig \u0026 polecat management\n3. Session \u0026 tmux operations\n4. Mail system\n5. CLI commands\n6. TUI (optional)","status":"open","priority":0,"issue_type":"epic","created_at":"2025-12-15T16:36:28.769343-08:00","updated_at":"2025-12-15T16:36:28.769343-08:00"}
{"id":"gt-u1j.1","title":"Go scaffolding: cmd/gt, go.mod, Cobra setup","description":"Set up Go project structure with CLI framework.\n\n**Stack:**\n- Cobra for command/flag handling\n- Lipgloss for styled terminal output\n\n**Deliverables:**\n- cmd/gt/main.go with Cobra root command\n- Basic subcommands: version, help\n- Lipgloss styles for status output (success, warning, error)\n- go.mod with dependencies","status":"closed","priority":0,"issue_type":"task","created_at":"2025-12-15T16:36:48.376267-08:00","updated_at":"2025-12-16T13:19:16.463491-08:00","closed_at":"2025-12-16T13:19:16.463491-08:00","close_reason":"Closed","dependencies":[{"issue_id":"gt-u1j.1","depends_on_id":"gt-u1j","type":"parent-child","created_at":"2025-12-15T16:36:48.376622-08:00","created_by":"daemon","metadata":"{}"}]} {"id":"gt-u1j.1","title":"Go scaffolding: cmd/gt, go.mod, Cobra setup","description":"Set up Go project structure with CLI framework.\n\n**Stack:**\n- Cobra for command/flag handling\n- Lipgloss for styled terminal output\n\n**Deliverables:**\n- cmd/gt/main.go with Cobra root command\n- Basic subcommands: version, help\n- Lipgloss styles for status output (success, warning, error)\n- go.mod with dependencies","status":"closed","priority":0,"issue_type":"task","created_at":"2025-12-15T16:36:48.376267-08:00","updated_at":"2025-12-16T13:19:16.463491-08:00","closed_at":"2025-12-16T13:19:16.463491-08:00","close_reason":"Closed","dependencies":[{"issue_id":"gt-u1j.1","depends_on_id":"gt-u1j","type":"parent-child","created_at":"2025-12-15T16:36:48.376622-08:00","created_by":"daemon","metadata":"{}"}]}
{"id":"gt-u1j.10","title":"CLI: core commands (status, prime, version, init)","description":"Essential CLI commands for Gas Town operation.\n\n## Commands\n\n### gt status\nShow overall town status.\n```\ngt status [--json]\n```\nOutput:\n- Town name and location\n- Number of rigs\n- Active polecats across all rigs\n- Witness status per rig\n- Recent activity summary\n\n### gt prime\nOutput role context for current directory.\n```\ngt prime\n```\nDetects role from directory:\n- Town root or mayor/ → Mayor context\n- \u003crig\u003e/witness/rig/ → Witness context\n- \u003crig\u003e/refinery/rig/ → Refinery context\n- \u003crig\u003e/polecats/\u003cname\u003e/ → Polecat context\n\n### gt version\nShow version information.\n```\ngt version [--short]\n```\nOutput: version, git commit, build date.\n\n### gt init\nInitialize current rig for Gas Town (alternative to gt install for existing repos).\n```\ngt init [--force]\n```\nCreates Gas Town structure in existing git repo.\n\n## Implementation\n\nEach command is a Cobra subcommand under root:\n```go\nvar statusCmd = \u0026cobra.Command{...}\nvar primeCmd = \u0026cobra.Command{...}\nvar versionCmd = \u0026cobra.Command{...}\nvar initCmd = \u0026cobra.Command{...}\n```\n\nRegister in cmd/gt/main.go.","status":"closed","priority":0,"issue_type":"task","created_at":"2025-12-15T17:12:38.367667-08:00","updated_at":"2025-12-16T13:49:54.801859-08:00","closed_at":"2025-12-16T13:49:54.801859-08:00","close_reason":"Closed","dependencies":[{"issue_id":"gt-u1j.10","depends_on_id":"gt-u1j","type":"parent-child","created_at":"2025-12-15T17:12:38.368006-08:00","created_by":"daemon","metadata":"{}"},{"issue_id":"gt-u1j.10","depends_on_id":"gt-u1j.5","type":"blocks","created_at":"2025-12-15T17:14:06.123332-08:00","created_by":"daemon","metadata":"{}"}]} {"id":"gt-u1j.10","title":"CLI: core commands (status, prime, version, init)","description":"Essential CLI commands for Gas Town operation.\n\n## Commands\n\n### gt status\nShow overall town status.\n```\ngt status [--json]\n```\nOutput:\n- Town name and location\n- Number of rigs\n- Active polecats across all rigs\n- Witness status per rig\n- Recent activity summary\n\n### gt prime\nOutput role context for current directory.\n```\ngt prime\n```\nDetects role from directory:\n- Town root or mayor/ → Mayor context\n- \u003crig\u003e/witness/rig/ → Witness context\n- \u003crig\u003e/refinery/rig/ → Refinery context\n- \u003crig\u003e/polecats/\u003cname\u003e/ → Polecat context\n\n### gt version\nShow version information.\n```\ngt version [--short]\n```\nOutput: version, git commit, build date.\n\n### gt init\nInitialize current rig for Gas Town (alternative to gt install for existing repos).\n```\ngt init [--force]\n```\nCreates Gas Town structure in existing git repo.\n\n## Implementation\n\nEach command is a Cobra subcommand under root:\n```go\nvar statusCmd = \u0026cobra.Command{...}\nvar primeCmd = \u0026cobra.Command{...}\nvar versionCmd = \u0026cobra.Command{...}\nvar initCmd = \u0026cobra.Command{...}\n```\n\nRegister in cmd/gt/main.go.","status":"closed","priority":0,"issue_type":"task","created_at":"2025-12-15T17:12:38.367667-08:00","updated_at":"2025-12-16T13:49:54.801859-08:00","closed_at":"2025-12-16T13:49:54.801859-08:00","close_reason":"Closed","dependencies":[{"issue_id":"gt-u1j.10","depends_on_id":"gt-u1j","type":"parent-child","created_at":"2025-12-15T17:12:38.368006-08:00","created_by":"daemon","metadata":"{}"},{"issue_id":"gt-u1j.10","depends_on_id":"gt-u1j.5","type":"blocks","created_at":"2025-12-15T17:14:06.123332-08:00","created_by":"daemon","metadata":"{}"}]}
@@ -256,6 +256,7 @@
{"id":"gt-vci","title":"Mayor handoff mail template","description":"Add MAYOR_HANDOFF mail template to templates.py.\n\n## Template Function\n\ndef mayor_handoff(\n active_swarms: List[SwarmStatus],\n rig_status: Dict[str, RigStatus],\n pending_escalations: List[Escalation],\n in_flight_decisions: List[Decision],\n recent_actions: List[str],\n delegated_work: List[DelegatedItem],\n user_requests: List[str],\n next_steps: List[str],\n warnings: Optional[str] = None,\n session_duration: Optional[str] = None,\n) -\u003e Message:\n metadata = {\n 'template': 'MAYOR_HANDOFF',\n 'timestamp': datetime.utcnow().isoformat(),\n 'session_duration': session_duration,\n 'active_swarm_count': len(active_swarms),\n 'pending_escalation_count': len(pending_escalations),\n }\n # ... format sections ...\n return Message.create(\n sender='mayor/',\n recipient='mayor/',\n subject='Session Handoff',\n body=body,\n priority='high',\n )\n\n## Metadata Fields\n\n- template: MAYOR_HANDOFF\n- timestamp: ISO format\n- session_duration: Human readable\n- active_swarm_count: Number of active swarms\n- pending_escalation_count: Number of escalations\n\n## Mail Priority\n\nUse priority='high' to ensure handoff is seen on startup.","status":"open","priority":1,"issue_type":"task","created_at":"2025-12-15T20:15:30.26323-08:00","updated_at":"2025-12-15T20:48:59.550689-08:00","dependencies":[{"issue_id":"gt-vci","depends_on_id":"gt-u82","type":"blocks","created_at":"2025-12-15T20:15:39.554108-08:00","created_by":"daemon","metadata":"{}"}]} {"id":"gt-vci","title":"Mayor handoff mail template","description":"Add MAYOR_HANDOFF mail template to templates.py.\n\n## Template Function\n\ndef mayor_handoff(\n active_swarms: List[SwarmStatus],\n rig_status: Dict[str, RigStatus],\n pending_escalations: List[Escalation],\n in_flight_decisions: List[Decision],\n recent_actions: List[str],\n delegated_work: List[DelegatedItem],\n user_requests: List[str],\n next_steps: List[str],\n warnings: Optional[str] = None,\n session_duration: Optional[str] = None,\n) -\u003e Message:\n metadata = {\n 'template': 'MAYOR_HANDOFF',\n 'timestamp': datetime.utcnow().isoformat(),\n 'session_duration': session_duration,\n 'active_swarm_count': len(active_swarms),\n 'pending_escalation_count': len(pending_escalations),\n }\n # ... format sections ...\n return Message.create(\n sender='mayor/',\n recipient='mayor/',\n subject='Session Handoff',\n body=body,\n priority='high',\n )\n\n## Metadata Fields\n\n- template: MAYOR_HANDOFF\n- timestamp: ISO format\n- session_duration: Human readable\n- active_swarm_count: Number of active swarms\n- pending_escalation_count: Number of escalations\n\n## Mail Priority\n\nUse priority='high' to ensure handoff is seen on startup.","status":"open","priority":1,"issue_type":"task","created_at":"2025-12-15T20:15:30.26323-08:00","updated_at":"2025-12-15T20:48:59.550689-08:00","dependencies":[{"issue_id":"gt-vci","depends_on_id":"gt-u82","type":"blocks","created_at":"2025-12-15T20:15:39.554108-08:00","created_by":"daemon","metadata":"{}"}]}
{"id":"gt-vjv","title":"Add bulk session stop command (gt session stop --all)","description":"When decommissioning a rig, need to stop multiple sessions one at a time. A --all or --rig flag would allow: gt session stop --rig gastown","status":"closed","priority":3,"issue_type":"feature","created_at":"2025-12-18T11:33:33.394649-08:00","updated_at":"2025-12-18T11:38:51.399298-08:00","closed_at":"2025-12-18T11:38:51.399298-08:00","close_reason":"Wrong model: streaming doesn't need bulk shutdown commands - polecats request their own shutdown"} {"id":"gt-vjv","title":"Add bulk session stop command (gt session stop --all)","description":"When decommissioning a rig, need to stop multiple sessions one at a time. A --all or --rig flag would allow: gt session stop --rig gastown","status":"closed","priority":3,"issue_type":"feature","created_at":"2025-12-18T11:33:33.394649-08:00","updated_at":"2025-12-18T11:38:51.399298-08:00","closed_at":"2025-12-18T11:38:51.399298-08:00","close_reason":"Wrong model: streaming doesn't need bulk shutdown commands - polecats request their own shutdown"}
{"id":"gt-vjw","title":"Swarm learning: Session cleanup missing from swarm workflow","description":"## Problem\n\nAfter Enders Game swarm completed (18 issues merged), 16 polecat sessions were left running but idle. No automated cleanup occurred.\n\n## What Should Happen\n\n1. Witness detects polecat completed work (idle at prompt)\n2. Witness verifies git state is clean\n3. Witness shuts down session\n4. Witness reports completion to Mayor\n\n## GGT Components\n\n- gt-cxx: Witness context cycling (covers self-cycling)\n- gt-u1j.9: Witness daemon heartbeat loop\n- gt-kmn.6: Witness swarm landing protocol\n\n## Recommendation\n\nAdd to Witness responsibilities:\n- Monitor for 'work complete' signals (DONE keyword, idle detection)\n- Automated session shutdown after verification\n- Swarm completion reporting to Mayor\n\nSee also: architecture.md 'Worker Cleanup (Witness-Owned)' section which describes this but it wasn't implemented in PGT.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-16T01:27:52.796587-08:00","updated_at":"2025-12-16T01:51:37.409965-08:00","closed_at":"2025-12-16T01:51:37.409965-08:00","close_reason":"Addressed by gt-u1j.9 (daemon heartbeat) which now includes session lifecycle management and worker shutdown. See architecture.md Key Decision #12."} {"id":"gt-vjw","title":"Swarm learning: Session cleanup missing from swarm workflow","description":"## Problem\n\nAfter Enders Game swarm completed (18 issues merged), 16 polecat sessions were left running but idle. No automated cleanup occurred.\n\n## What Should Happen\n\n1. Witness detects polecat completed work (idle at prompt)\n2. Witness verifies git state is clean\n3. Witness shuts down session\n4. Witness reports completion to Mayor\n\n## GGT Components\n\n- gt-cxx: Witness context cycling (covers self-cycling)\n- gt-u1j.9: Witness daemon heartbeat loop\n- gt-kmn.6: Witness swarm landing protocol\n\n## Recommendation\n\nAdd to Witness responsibilities:\n- Monitor for 'work complete' signals (DONE keyword, idle detection)\n- Automated session shutdown after verification\n- Swarm completion reporting to Mayor\n\nSee also: architecture.md 'Worker Cleanup (Witness-Owned)' section which describes this but it wasn't implemented in PGT.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-16T01:27:52.796587-08:00","updated_at":"2025-12-16T01:51:37.409965-08:00","closed_at":"2025-12-16T01:51:37.409965-08:00","close_reason":"Addressed by gt-u1j.9 (daemon heartbeat) which now includes session lifecycle management and worker shutdown. See architecture.md Key Decision #12."}
{"id":"gt-w775","title":"MR: gt-svi.1 (polecat/Furiosa)","description":"branch: polecat/Furiosa\ntarget: main\nsource_issue: gt-svi.1","status":"closed","priority":1,"issue_type":"merge-request","created_at":"2025-12-18T20:21:40.921429-08:00","updated_at":"2025-12-18T20:21:54.163532-08:00","closed_at":"2025-12-18T20:21:54.163532-08:00","close_reason":"Test MRs, cleaning up"}
{"id":"gt-w9o","title":"/restart: Personal slash command for in-place agent restart","description":"Create ~/.claude/commands/restart.md that restarts current Gas Town agent in place.\n\n## Detection\n- Read tmux session name: gt-mayor, gt-witness-*, gt-refinery-*, gt-polecat-*\n- Fallback: check GT_ROLE env var\n\n## Behavior by role\n- mayor: gt mayor restart (sends Ctrl-C, loop respawns)\n- witness: gt witness restart\n- refinery: gt refinery restart \n- polecat: gt polecat restart (or witness-mediated)\n\n## Command format\nUses backticks for inline bash to detect context, then instructs Claude to run appropriate restart.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-18T18:32:30.043125-08:00","updated_at":"2025-12-18T18:43:17.182303-08:00","closed_at":"2025-12-18T18:43:17.182303-08:00","close_reason":"Implemented ~/.claude/commands/restart.md"} {"id":"gt-w9o","title":"/restart: Personal slash command for in-place agent restart","description":"Create ~/.claude/commands/restart.md that restarts current Gas Town agent in place.\n\n## Detection\n- Read tmux session name: gt-mayor, gt-witness-*, gt-refinery-*, gt-polecat-*\n- Fallback: check GT_ROLE env var\n\n## Behavior by role\n- mayor: gt mayor restart (sends Ctrl-C, loop respawns)\n- witness: gt witness restart\n- refinery: gt refinery restart \n- polecat: gt polecat restart (or witness-mediated)\n\n## Command format\nUses backticks for inline bash to detect context, then instructs Claude to run appropriate restart.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-18T18:32:30.043125-08:00","updated_at":"2025-12-18T18:43:17.182303-08:00","closed_at":"2025-12-18T18:43:17.182303-08:00","close_reason":"Implemented ~/.claude/commands/restart.md"}
{"id":"gt-wpg","title":"Replaceable notifications via Claude Code queue","description":"Leverage Claude Code's ability to replace queued text for notifications that supersede previous ones.\n\n## Problem\n\nIf daemon sends 10 heartbeats while agent is busy, agent returns to see 10 stacked messages. Wasteful and noisy.\n\n## Solution\n\nUse Claude Code's queue replacement for:\n- Heartbeat messages (only latest matters)\n- Status updates that supersede previous\n- Progress notifications\n\n## Implementation\n\nNotifications get a 'slot' identifier. New notification in same slot replaces old one:\n- Slot: 'heartbeat' → only one heartbeat queued at a time\n- Slot: 'status-\u003crig\u003e' → latest status per rig\n- No slot → stacks normally (for unique messages)\n\n## Research Needed\n\n- How does Claude Code expose queue replacement?\n- tmux send-keys behavior with pending input\n- Alternative: clear + resend pattern","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-18T14:19:29.821949-08:00","updated_at":"2025-12-18T14:19:29.821949-08:00","dependencies":[{"issue_id":"gt-wpg","depends_on_id":"gt-99m","type":"blocks","created_at":"2025-12-18T14:19:46.656972-08:00","created_by":"daemon"}]} {"id":"gt-wpg","title":"Replaceable notifications via Claude Code queue","description":"Leverage Claude Code's ability to replace queued text for notifications that supersede previous ones.\n\n## Problem\n\nIf daemon sends 10 heartbeats while agent is busy, agent returns to see 10 stacked messages. Wasteful and noisy.\n\n## Solution\n\nUse Claude Code's queue replacement for:\n- Heartbeat messages (only latest matters)\n- Status updates that supersede previous\n- Progress notifications\n\n## Implementation\n\nNotifications get a 'slot' identifier. New notification in same slot replaces old one:\n- Slot: 'heartbeat' → only one heartbeat queued at a time\n- Slot: 'status-\u003crig\u003e' → latest status per rig\n- No slot → stacks normally (for unique messages)\n\n## Research Needed\n\n- How does Claude Code expose queue replacement?\n- tmux send-keys behavior with pending input\n- Alternative: clear + resend pattern","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-18T14:19:29.821949-08:00","updated_at":"2025-12-18T14:19:29.821949-08:00","dependencies":[{"issue_id":"gt-wpg","depends_on_id":"gt-99m","type":"blocks","created_at":"2025-12-18T14:19:46.656972-08:00","created_by":"daemon"}]}
{"id":"gt-xpq","title":"Add gt crew rename command","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-18T19:45:32.599846-08:00","updated_at":"2025-12-18T19:46:17.780981-08:00","closed_at":"2025-12-18T19:46:17.780981-08:00","close_reason":"Already implemented in commit 915594c"} {"id":"gt-xpq","title":"Add gt crew rename command","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-18T19:45:32.599846-08:00","updated_at":"2025-12-18T19:46:17.780981-08:00","closed_at":"2025-12-18T19:46:17.780981-08:00","close_reason":"Already implemented in commit 915594c"}

View File

@@ -1,16 +1,33 @@
package cmd package cmd
import ( import (
"encoding/json"
"fmt" "fmt"
"os"
"strings"
"time"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/steveyegge/gastown/internal/beads"
"github.com/steveyegge/gastown/internal/refinery" "github.com/steveyegge/gastown/internal/refinery"
"github.com/steveyegge/gastown/internal/style" "github.com/steveyegge/gastown/internal/style"
) )
// mq command flags // MQ command flags
var ( var (
// Retry flags
mqRetryNow bool mqRetryNow bool
// Reject flags
mqRejectReason string
mqRejectNotify bool
// List command flags
mqListReady bool
mqListStatus string
mqListWorker string
mqListEpic string
mqListJSON bool
) )
var mqCmd = &cobra.Command{ var mqCmd = &cobra.Command{
@@ -18,8 +35,8 @@ var mqCmd = &cobra.Command{
Short: "Merge queue operations", Short: "Merge queue operations",
Long: `Manage the merge queue for a rig. Long: `Manage the merge queue for a rig.
The merge queue tracks work from polecats waiting to be merged. The merge queue tracks work branches from polecats waiting to be merged.
Use these commands to view, retry, and manage merge requests.`, Use these commands to view, submit, retry, and manage merge requests.`,
} }
var mqRetryCmd = &cobra.Command{ var mqRetryCmd = &cobra.Command{
@@ -37,12 +54,64 @@ Examples:
RunE: runMQRetry, RunE: runMQRetry,
} }
var mqListCmd = &cobra.Command{
Use: "list <rig>",
Short: "Show the merge queue",
Long: `Show the merge queue for a rig.
Lists all pending merge requests waiting to be processed.
Output format:
ID STATUS PRIORITY BRANCH WORKER AGE
gt-mr-001 ready P0 polecat/Nux/gt-xyz Nux 5m
gt-mr-002 in_progress P1 polecat/Toast/gt-abc Toast 12m
gt-mr-003 blocked P1 polecat/Capable/gt-def Capable 8m
(waiting on gt-mr-001)
Examples:
gt mq list gastown
gt mq list gastown --ready
gt mq list gastown --status=open
gt mq list gastown --worker=Nux`,
Args: cobra.ExactArgs(1),
RunE: runMQList,
}
var mqRejectCmd = &cobra.Command{
Use: "reject <rig> <mr-id-or-branch>",
Short: "Reject a merge request",
Long: `Manually reject a merge request.
This closes the MR with a 'rejected' status without merging.
The source issue is NOT closed (work is not done).
Examples:
gt mq reject gastown polecat/Nux/gt-xyz --reason "Does not meet requirements"
gt mq reject gastown mr-Nux-12345 --reason "Superseded by other work" --notify`,
Args: cobra.ExactArgs(2),
RunE: runMQReject,
}
func init() { func init() {
// Retry flags // Retry flags
mqRetryCmd.Flags().BoolVar(&mqRetryNow, "now", false, "Immediately process instead of waiting for refinery loop") mqRetryCmd.Flags().BoolVar(&mqRetryNow, "now", false, "Immediately process instead of waiting for refinery loop")
// List flags
mqListCmd.Flags().BoolVar(&mqListReady, "ready", false, "Show only ready-to-merge (no blockers)")
mqListCmd.Flags().StringVar(&mqListStatus, "status", "", "Filter by status (open, in_progress, closed)")
mqListCmd.Flags().StringVar(&mqListWorker, "worker", "", "Filter by worker name")
mqListCmd.Flags().StringVar(&mqListEpic, "epic", "", "Show MRs targeting integration/<epic>")
mqListCmd.Flags().BoolVar(&mqListJSON, "json", false, "Output as JSON")
// Reject flags
mqRejectCmd.Flags().StringVarP(&mqRejectReason, "reason", "r", "", "Reason for rejection (required)")
mqRejectCmd.Flags().BoolVar(&mqRejectNotify, "notify", false, "Send mail notification to worker")
_ = mqRejectCmd.MarkFlagRequired("reason")
// Add subcommands // Add subcommands
mqCmd.AddCommand(mqRetryCmd) mqCmd.AddCommand(mqRetryCmd)
mqCmd.AddCommand(mqListCmd)
mqCmd.AddCommand(mqRejectCmd)
rootCmd.AddCommand(mqCmd) rootCmd.AddCommand(mqCmd)
} }
@@ -90,3 +159,223 @@ func runMQRetry(cmd *cobra.Command, args []string) error {
return nil return nil
} }
func runMQList(cmd *cobra.Command, args []string) error {
rigName := args[0]
_, r, err := getRefineryManager(rigName)
if err != nil {
return err
}
// Create beads wrapper for the rig
b := beads.New(r.Path)
// Build list options - query for merge-request type
opts := beads.ListOptions{
Type: "merge-request",
}
// Apply status filter if specified
if mqListStatus != "" {
opts.Status = mqListStatus
} else if !mqListReady {
// Default to open if not showing ready
opts.Status = "open"
}
var issues []*beads.Issue
if mqListReady {
// Use ready query which filters by no blockers
allReady, err := b.Ready()
if err != nil {
return fmt.Errorf("querying ready MRs: %w", err)
}
// Filter to only merge-request type
for _, issue := range allReady {
if issue.Type == "merge-request" {
issues = append(issues, issue)
}
}
} else {
issues, err = b.List(opts)
if err != nil {
return fmt.Errorf("querying merge queue: %w", err)
}
}
// Apply additional filters
var filtered []*beads.Issue
for _, issue := range issues {
// Parse MR fields
fields := beads.ParseMRFields(issue)
// Filter by worker
if mqListWorker != "" {
worker := ""
if fields != nil {
worker = fields.Worker
}
if !strings.EqualFold(worker, mqListWorker) {
continue
}
}
// Filter by epic (target branch)
if mqListEpic != "" {
target := ""
if fields != nil {
target = fields.Target
}
expectedTarget := "integration/" + mqListEpic
if target != expectedTarget {
continue
}
}
filtered = append(filtered, issue)
}
// JSON output
if mqListJSON {
return outputJSON(filtered)
}
// Human-readable output
fmt.Printf("%s Merge queue for '%s':\n\n", style.Bold.Render("📋"), rigName)
if len(filtered) == 0 {
fmt.Printf(" %s\n", style.Dim.Render("(empty)"))
return nil
}
// Print header
fmt.Printf(" %-12s %-12s %-8s %-30s %-10s %s\n",
"ID", "STATUS", "PRIORITY", "BRANCH", "WORKER", "AGE")
fmt.Printf(" %s\n", strings.Repeat("-", 90))
// Print each MR
for _, issue := range filtered {
fields := beads.ParseMRFields(issue)
// Determine display status
displayStatus := issue.Status
if issue.Status == "open" {
if len(issue.BlockedBy) > 0 || issue.BlockedByCount > 0 {
displayStatus = "blocked"
} else {
displayStatus = "ready"
}
}
// Format status with styling
styledStatus := displayStatus
switch displayStatus {
case "ready":
styledStatus = style.Bold.Render("ready")
case "in_progress":
styledStatus = style.Bold.Render("in_progress")
case "blocked":
styledStatus = style.Dim.Render("blocked")
case "closed":
styledStatus = style.Dim.Render("closed")
}
// Get MR fields
branch := ""
worker := ""
if fields != nil {
branch = fields.Branch
worker = fields.Worker
}
// Truncate branch if too long
if len(branch) > 30 {
branch = branch[:27] + "..."
}
// Format priority
priority := fmt.Sprintf("P%d", issue.Priority)
// Calculate age
age := formatMRAge(issue.CreatedAt)
// Truncate ID if needed
displayID := issue.ID
if len(displayID) > 12 {
displayID = displayID[:12]
}
fmt.Printf(" %-12s %-12s %-8s %-30s %-10s %s\n",
displayID, styledStatus, priority, branch, worker, style.Dim.Render(age))
// Show blocking info if blocked
if displayStatus == "blocked" && len(issue.BlockedBy) > 0 {
fmt.Printf(" %s\n", style.Dim.Render(fmt.Sprintf(" (waiting on %s)", issue.BlockedBy[0])))
}
}
return nil
}
// formatMRAge formats the age of an MR from its created_at timestamp.
func formatMRAge(createdAt string) string {
t, err := time.Parse(time.RFC3339, createdAt)
if err != nil {
// Try other formats
t, err = time.Parse("2006-01-02T15:04:05Z", createdAt)
if err != nil {
return "?"
}
}
d := time.Since(t)
if d < time.Minute {
return fmt.Sprintf("%ds", int(d.Seconds()))
}
if d < time.Hour {
return fmt.Sprintf("%dm", int(d.Minutes()))
}
if d < 24*time.Hour {
return fmt.Sprintf("%dh", int(d.Hours()))
}
return fmt.Sprintf("%dd", int(d.Hours()/24))
}
// outputJSON outputs data as JSON.
func outputJSON(data interface{}) error {
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
return enc.Encode(data)
}
func runMQReject(cmd *cobra.Command, args []string) error {
rigName := args[0]
mrIDOrBranch := args[1]
mgr, _, err := getRefineryManager(rigName)
if err != nil {
return err
}
result, err := mgr.RejectMR(mrIDOrBranch, mqRejectReason, mqRejectNotify)
if err != nil {
return fmt.Errorf("rejecting MR: %w", err)
}
fmt.Printf("%s Rejected: %s\n", style.Bold.Render("✗"), result.Branch)
fmt.Printf(" Worker: %s\n", result.Worker)
fmt.Printf(" Reason: %s\n", mqRejectReason)
if result.IssueID != "" {
fmt.Printf(" Issue: %s %s\n", result.IssueID, style.Dim.Render("(not closed - work not done)"))
}
if mqRejectNotify {
fmt.Printf(" %s\n", style.Dim.Render("Worker notified via mail"))
}
return nil
}

View File

@@ -551,13 +551,13 @@ Thank you for your contribution!`,
router.Send(msg) router.Send(msg)
} }
// ErrMRNotFound is returned when a merge request is not found. // Common errors for MR operations
var ErrMRNotFound = errors.New("merge request not found") var (
ErrMRNotFound = errors.New("merge request not found")
ErrMRNotFailed = errors.New("merge request has not failed")
)
// ErrMRNotFailed is returned when trying to retry an MR that hasn't failed. // GetMR returns a merge request by ID from the state.
var ErrMRNotFailed = errors.New("merge request has not failed")
// GetMR returns a merge request by ID.
func (m *Manager) GetMR(id string) (*MergeRequest, error) { func (m *Manager) GetMR(id string) (*MergeRequest, error) {
ref, err := m.loadState() ref, err := m.loadState()
if err != nil { if err != nil {
@@ -579,6 +579,34 @@ func (m *Manager) GetMR(id string) (*MergeRequest, error) {
return nil, ErrMRNotFound return nil, ErrMRNotFound
} }
// FindMR finds a merge request by ID or branch name in the queue.
func (m *Manager) FindMR(idOrBranch string) (*MergeRequest, error) {
queue, err := m.Queue()
if err != nil {
return nil, err
}
for _, item := range queue {
// Match by ID
if item.MR.ID == idOrBranch {
return item.MR, nil
}
// Match by branch name (with or without polecat/ prefix)
if item.MR.Branch == idOrBranch {
return item.MR, nil
}
if "polecat/"+idOrBranch == item.MR.Branch {
return item.MR, nil
}
// Match by worker name (partial match for convenience)
if strings.Contains(item.MR.ID, idOrBranch) {
return item.MR, nil
}
}
return nil, ErrMRNotFound
}
// Retry resets a failed merge request so it can be processed again. // Retry resets a failed merge request so it can be processed again.
// If processNow is true, immediately processes the MR instead of waiting for the loop. // If processNow is true, immediately processes the MR instead of waiting for the loop.
func (m *Manager) Retry(id string, processNow bool) error { func (m *Manager) Retry(id string, processNow bool) error {
@@ -635,6 +663,54 @@ func (m *Manager) RegisterMR(mr *MergeRequest) error {
return m.saveState(ref) return m.saveState(ref)
} }
// RejectMR manually rejects a merge request.
// It closes the MR with rejected status and optionally notifies the worker.
// Returns the rejected MR for display purposes.
func (m *Manager) RejectMR(idOrBranch string, reason string, notify bool) (*MergeRequest, error) {
mr, err := m.FindMR(idOrBranch)
if err != nil {
return nil, err
}
// Verify MR is open or in_progress (can't reject already closed)
if mr.IsClosed() {
return nil, fmt.Errorf("%w: MR is already closed with reason: %s", ErrClosedImmutable, mr.CloseReason)
}
// Close with rejected reason
if err := mr.Close(CloseReasonRejected); err != nil {
return nil, fmt.Errorf("failed to close MR: %w", err)
}
mr.Error = reason
// Optionally notify worker
if notify {
m.notifyWorkerRejected(mr, reason)
}
return mr, nil
}
// notifyWorkerRejected sends a rejection notification to a polecat.
func (m *Manager) notifyWorkerRejected(mr *MergeRequest, reason string) {
router := mail.NewRouter(m.workDir)
msg := &mail.Message{
From: fmt.Sprintf("%s/refinery", m.rig.Name),
To: fmt.Sprintf("%s/%s", m.rig.Name, mr.Worker),
Subject: "Merge request rejected",
Body: fmt.Sprintf(`Your merge request has been rejected.
Branch: %s
Issue: %s
Reason: %s
Please review the feedback and address the issues before resubmitting.`,
mr.Branch, mr.IssueID, reason),
Priority: mail.PriorityNormal,
}
router.Send(msg)
}
// findTownRoot walks up directories to find the town root. // findTownRoot walks up directories to find the town root.
func findTownRoot(startPath string) string { func findTownRoot(startPath string) string {
path := startPath path := startPath

View File

@@ -255,3 +255,48 @@ func TestMergeRequest_StatusChecks(t *testing.T) {
}) })
} }
} }
func TestMergeRequest_Rejection(t *testing.T) {
t.Run("reject from open succeeds", func(t *testing.T) {
mr := &MergeRequest{Status: MROpen}
err := mr.Close(CloseReasonRejected)
if err != nil {
t.Errorf("Close(rejected) unexpected error: %v", err)
}
if mr.Status != MRClosed {
t.Errorf("Close(rejected) status = %s, want %s", mr.Status, MRClosed)
}
if mr.CloseReason != CloseReasonRejected {
t.Errorf("Close(rejected) closeReason = %s, want %s", mr.CloseReason, CloseReasonRejected)
}
})
t.Run("reject from in_progress succeeds", func(t *testing.T) {
mr := &MergeRequest{Status: MRInProgress}
err := mr.Close(CloseReasonRejected)
if err != nil {
t.Errorf("Close(rejected) unexpected error: %v", err)
}
if mr.Status != MRClosed {
t.Errorf("Close(rejected) status = %s, want %s", mr.Status, MRClosed)
}
if mr.CloseReason != CloseReasonRejected {
t.Errorf("Close(rejected) closeReason = %s, want %s", mr.CloseReason, CloseReasonRejected)
}
})
t.Run("reject from closed fails", func(t *testing.T) {
mr := &MergeRequest{Status: MRClosed, CloseReason: CloseReasonMerged}
err := mr.Close(CloseReasonRejected)
if err == nil {
t.Error("Close(rejected) expected error, got nil")
}
if !errors.Is(err, ErrClosedImmutable) {
t.Errorf("Close(rejected) error = %v, want %v", err, ErrClosedImmutable)
}
// CloseReason should not change
if mr.CloseReason != CloseReasonMerged {
t.Errorf("Close(rejected) closeReason changed from %s to %s", CloseReasonMerged, mr.CloseReason)
}
})
}