Boot sessions run in `deacon/dogs/boot/` but were incorrectly detected
as deacon role because the deacon check matched first. This caused Boot
to receive Deacon's context instead of Boot-specific context.
Changes:
- Add RoleBoot constant
- Add boot path detection before deacon check in detectRole()
- Add boot case in buildRoleAnnouncement()
- Add boot case in getAgentIdentity() (returns "boot")
- Add boot case in getAgentBeadID() (uses deacon's bead as subprocess)
The boot.md.tmpl template already exists and will now be used.
Fixes#318
Since the self-cleaning model (Jan 10), polecats push branches to origin
before `gt done`. The refinery was only deleting local branches after
merge, causing stale `polecat/*` branches to accumulate on the remote.
Now deletes both local and remote branches after successful merge.
Uses existing `git.DeleteRemoteBranch()` function. Remote deletion is
non-fatal if the branch doesn't exist.
Fixes#359
* test(util): add comprehensive tests for atomic write functions
Add tests for:
- File permissions
- Empty data handling
- Various JSON types (string, int, float, bool, null, array, nested)
- Unmarshallable types error handling
- Read-only directory permission errors
- Concurrent writes
- Original content preservation on failure
- Struct serialization/deserialization
- Large data (1MB)
* test(connection): add edge case tests for address parsing
Add comprehensive test coverage for ParseAddress edge cases:
- Empty/whitespace/slash-only inputs
- Leading/trailing slash handling
- Machine prefix edge cases (colons, empty machine)
- Multiple slashes in polecat name (SplitN behavior)
- Unicode and emoji support
- Very long addresses
- Special characters (hyphens, underscores, dots)
- Whitespace in components
Also adds tests for MustParseAddress panic behavior and RigPath method.
Closes: gt-xgjyp
* test(checkpoint): add comprehensive test coverage for checkpoint package
Tests all public functions: Read, Write, Remove, Capture, WithMolecule,
WithHookedBead, WithNotes, Age, IsStale, Summary, Path.
Edge cases covered: missing file, corrupted JSON, stale detection.
Closes: gt-09yn1
* test(lock): add comprehensive tests for lock package
Add lock_test.go with tests covering:
- LockInfo.IsStale() with valid/invalid PIDs
- Lock.Acquire/Release lifecycle
- Re-acquiring own lock (session refresh)
- Stale lock cleanup during Acquire
- Lock.Read() for missing/invalid/valid files
- Lock.Check() for unlocked/owned/stale scenarios
- Lock.Status() string formatting
- Lock.ForceRelease()
- processExists() helper
- FindAllLocks() directory scanning
- CleanStaleLocks() with mocked tmux
- getActiveTmuxSessions() parsing
- splitOnColon() and splitLines() helpers
- DetectCollisions() for stale/orphaned locks
Coverage: 84.4%
* test(keepalive): add example tests demonstrating usage patterns
Add ExampleTouchInWorkspace, ExampleRead, and ExampleState_Age to
serve as documentation for how to use the keepalive package.
* fix(test): correct boundary test timing race in checkpoint_test.go
The 'exactly threshold' test case was flaky due to timing: by the time
time.Since() runs after setting Timestamp, microseconds have passed,
making age > threshold. Changed expectation to true since at-threshold
is effectively stale.
---------
Co-authored-by: slit <gt@gastown.local>
* test(costs): add failing test for multi-location session event query
Add integration test that verifies querySessionEvents finds session.ended
events from both town-level and rig-level beads databases.
The test demonstrates the bug: events created by rig-level agents (polecats,
witness, etc.) are stored in the rig's .beads database, but querySessionEvents
only queries the town-level beads, missing rig-level events.
Test setup:
- Creates town with gt install
- Adds rig with gt rig add (separate beads DB)
- Creates session.ended event in town beads (simulating mayor)
- Creates session.ended event in rig beads (simulating polecat)
- Verifies querySessionEvents finds both events
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(costs): query all beads locations for session events
querySessionEvents previously only queried the town-level beads database,
missing session.ended events created by rig-level agents (polecats, witness,
refinery, crew) which are stored in each rig's own .beads database.
The fix:
- Load rigs from mayor/rigs.json
- Query each rig's beads location in addition to town-level beads
- Merge and deduplicate results by session ID + timestamp
This ensures `gt costs` finds all session cost events regardless of which
agent's beads database they were recorded in.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: julianknutsen <julianknutsen@users.noreply.github>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
When a repo with tracked .beads/ is added as a rig, the beads.db file
doesn't exist because it's gitignored. Previously, bd init was only run
if prefix detection succeeded. If there were no issues in issues.jsonl,
detection failed and bd init was never run, causing "Error: no beads
database found" when running bd commands.
Changes:
- Always run bd init when tracked beads exist but db is missing
- Detect prefix from existing issues in issues.jsonl
- Only error on prefix mismatch if user explicitly passed --prefix
- If no issues exist, use the derived/provided prefix
Fixes#72
Co-authored-by: julianknutsen <julianknutsen@users.noreply.github>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Update identity.md to reflect the implemented polecat identity model.
The previous text incorrectly stated "Polecats are ephemeral... no
persistent polecat CV" which contradicted the polecat-lifecycle.md
docs and the gt polecat identity implementation.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
sessionWorkDir had cases for mayor, deacon, crew, witness, and refinery
but not polecats. When gt handoff was run from a polecat session like
gt-tanwa_info-slit, it failed with "unknown session type".
Fix uses session.ParseSessionName to parse the session name and extract
rig/name for polecat sessions, mapping to <townRoot>/<rig>/polecats/<name>.
Fixes: gm-lie6
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The --naked flag (skip tmux session creation) was a vestige of an earlier
design requiring manual session management. With the current polecat
architecture where polecats are witness-managed, ephemeral, and self-deleting
after task completion, manual session management is no longer needed.
The flag also created invalid states (e.g., slinging to crew --naked left
them unreachable since crew require tmux sessions for communication).
Closes gt-xhn5s
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Plugin System (gt-n08ix):
- Deacon-dispatched periodic automation
- Dog execution model (non-blocking)
- Wisps for state tracking (no state.json)
- Gate types: cooldown, cron, condition, event
- First plugin: rebuild-gt for stale binary detection
Escalation System (gt-i9r20):
- Unified gt escalate command with severity routing
- Config-driven: settings/escalation.json
- Escalation beads for tracking
- Stale escalation re-escalation
- Actions: bead, mail, email, sms
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add new `gt polecat identity` (alias: `id`) subcommand group with commands:
- add <rig> [name]: Create identity bead (auto-generates name if omitted)
- list <rig>: List polecat identity beads with session/worktree status
- show <rig> <name>: Show identity details and CV (work history)
- rename <rig> <old> <new>: Rename identity, preserving CV chain
- remove <rig> <name>: Remove identity with safety checks
Each command manipulates agent beads with role_type=polecat. Safety checks
prevent removal of identities with active sessions or work on hook.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add deprecation warning pointing users to 'gt polecat identity add':
- Cobra Deprecated field emits automatic warning on command use
- Custom warning in runPolecatAdd for prominent stderr output
- Updated help text with deprecation notice and new command example
The command still functions but will be removed in v1.0.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Improve tmux statusline: sort rigs by activity and add visual grouping
- Sort rigs by running state, then polecat count, then operational state
- Add visual grouping with | separators between state groups
- Show process state with icons (🟢 both running, 🟡 one running, 🅿️ parked, 🛑 docked, ⚫ idle)
- Display polecat counts for active rigs
- Improve icon spacing: 2 spaces after Park emoji, 1 space for others
* Fix golangci-lint warnings
- Check error return from os.Setenv
- Check error return from lock.Unlock
- Mark intentionally unused parameters with _
---------
Co-authored-by: joshuavial <git@codewithjv.com>
When polecats run 'gt done' without --cleanup-status, the witness may
prematurely nuke the worktree before the refinery can merge.
This fix auto-detects git state:
- uncommitted: has uncommitted changes
- stash: has stashed changes
- unpushed: branch not pushed or has unpushed commits
- clean: everything pushed
Uses BranchPushedToRemote() which properly handles polecat branches
that don't have upstream tracking (compares against origin/main).
On error, defaults to 'unpushed' to prevent accidental data loss.
Fixes: #342
Co-authored-by: mayor <mayor@gastown.local>
When running `gt install --wrappers` in an existing Gas Town HQ,
the command now installs wrappers directly without requiring --force
or recreating the entire HQ structure.
Previously, `gt install --wrappers` would fail with "directory is
already a Gas Town HQ" unless --force was used, which would then
unnecessarily reinitialize the entire workspace.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
When bd --no-daemon show <id> does not find an issue, it incorrectly exits
with code 0 (success) but writes the error to stderr and leaves stdout empty.
This causes JSON parse failures throughout gt when code tries to unmarshal
the empty stdout.
This PR handles the bug defensively in all affected code paths:
- beads.go run(): Detect empty stdout + non-empty stderr as error
- beads.go wrapError(): Add 'no issue found' to ErrNotFound patterns
- sling.go: Check len(out) == 0 in multiple functions
- convoy.go getIssueDetails(): Check stdout.Len() == 0
- prime_molecule.go: Check stdout.Len() == 0
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The verifyFormulaExists function now checks for non-empty output,
so the test stub must output something for formula show commands.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix beads.run() to always explicitly set BEADS_DIR based on the working
directory or explicit override
- This prevents inherited environment variables (e.g., from mayor session
with BEADS_DIR=/home/erik/gt/.beads) from causing prefix mismatch errors
when creating agent beads for rigs
- Update polecat manager to use NewWithBeadsDir for explicitness
- Add comprehensive test coverage for BEADS_DIR routing and validation
- Add SessionLister interface for deterministic orphan session testing
Root cause: When BEADS_DIR was set in the parent environment, all bd
commands used the town database (hq- prefix) instead of the rig database
(gt- prefix), causing "prefix mismatch: database uses 'hq' but you
specified 'gt'" errors during polecat spawn.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fixes CI lint failures by handling unchecked error returns and marking
unused parameters with blank identifiers.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Adds mark-read and mark-unread commands that allow marking messages
as read without archiving them. Uses a "read" label to track status.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Makes PR rules conditional on repo ownership instead of absolute ban.
Non-maintainer repos may require PRs for external contributors.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- shiny.formula.toml: defers to role's git workflow instead of hardcoding PR
- crew.md.tmpl: checks remote origin ownership instead of absolute PR ban
- tmux.go: minor comment fix
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The previous NEVER create GitHub PRs language was too weak. Strengthened to:
- ABSOLUTELY FORBIDDEN header
- This is not negotiable
- Explicit STOP if about to run gh pr create
- Clarified PR Sheriff reviews incoming PRs, does not create them
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
NudgeSession and NudgePane now send Escape key before Enter to exit
vim INSERT mode if enabled. Harmless in normal mode.
Fixes#307
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove references to idle state. Polecats self-nuke after work - there is
no idle state. The Witness handles crash recovery and orphan cleanup.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Issue #336: Consolidate down/shutdown/stop commands
Changes:
- Add `gt down --polecats` flag to stop all polecat sessions
- Deprecate `gt stop` command (prints warning, directs to `gt down --polecats`)
- Update help text to clarify down vs shutdown distinction:
- down = pause (reversible, keeps worktrees)
- shutdown = done (permanent cleanup)
- Integrate --polecats with new --dry-run mode from recent PR
Note: The issue proposed renaming --nuke to --tmux, but PR #330 just
landed with --nuke having better safety (GT_NUKE_ACKNOWLEDGED env var),
so keeping --nuke as-is. The new --polecats flag absorbs gt stop
functionality as proposed.
Closes#336
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When a polecat is nuked and re-spawned with the same name, CreateAgentBead
fails with a UNIQUE constraint error because the old agent bead exists as
a tombstone.
This adds CreateOrReopenAgentBead that:
1. First tries to create the agent bead normally
2. If UNIQUE constraint fails, reopens the existing bead and updates fields
Updated both spawn paths in polecat manager to use the new function.
Fixes#332
Co-authored-by: Claude <noreply@anthropic.com>
* fix(down): add refinery shutdown to gt down
Refineries were not being stopped by gt down, causing them to continue
running after shutdown. This adds a refinery shutdown loop before
witnesses, fixing problem P3 from the v2.4 proposal.
Changes:
- Add Phase 1: Stop refineries (gt-<rig>-refinery sessions)
- Renumber existing phases (witnesses now Phase 2, etc.)
- Include refineries in halt event logging
* feat(beads): add StopAllBdProcesses for shutdown
Add functions to stop bd daemon and bd activity processes:
- StopAllBdProcesses(dryRun, force) - main entry point
- CountBdDaemons() - count running bd daemons
- CountBdActivityProcesses() - count running bd activity processes
- stopBdDaemons() - uses bd daemon killall
- stopBdActivityProcesses() - SIGTERM->wait->SIGKILL pattern
This solves problems P1 (bd daemon respawns sessions) and P2 (bd activity
causes instant wakeups) from the v2.4 proposal.
* feat(down): rename --all to --nuke, add new --all and --dry-run flags
BREAKING CHANGE: --all now stops bd processes instead of killing tmux server.
Use --nuke for the old --all behavior (killing the entire tmux server).
New flags:
- --all: Stop bd daemons/activity processes and verify shutdown
- --nuke: Kill entire tmux server (DESTRUCTIVE, with warning)
- --dry-run: Preview what would be stopped without taking action
This solves problem P4 (old --all was too destructive) from the v2.4 proposal.
The --nuke flag now requires GT_NUKE_ACKNOWLEDGED=1 environment variable
to suppress the warning about destroying all tmux sessions.
* feat(down): add shutdown lock to prevent concurrent runs
Add Phase 0 that acquires a file lock before shutdown to prevent race
conditions when multiple gt down commands are run concurrently.
- Uses gofrs/flock for cross-platform file locking
- Lock file stored at ~/gt/daemon/shutdown.lock
- 5 second timeout with 100ms retry interval
- Lock released via defer on successful acquisition
- Dry-run mode skips lock acquisition
This solves problem P6 (concurrent shutdown race) from the v2.4 proposal.
* feat(down): add verification phase for respawn detection
Add Phase 5 that verifies shutdown was complete after stopping all services:
- Waits 500ms for processes to fully terminate
- Checks for respawned bd daemons
- Checks for respawned bd activity processes
- Checks for remaining gt-*/hq-* tmux sessions
- Checks if daemon PID is still running
If anything respawned, warns user and suggests checking systemd/launchd.
This solves problem P5 (no verification) from the v2.4 proposal.
* test(down): add unit tests for shutdown functionality
Add tests for:
- parseBdDaemonCount() - array, object with count, object with daemons, empty, invalid
- CountBdActivityProcesses() - integration test
- CountBdDaemons() - integration test (skipped if bd not installed)
- StopAllBdProcesses() - dry-run mode test
- isProcessRunning() - current process, invalid PID, max PID
These tests cover the core parsing and process detection logic added
in the v2.4 shutdown enhancement.
* fix(review): add tmux check and pkill fallback for bd shutdown
Address review gaps against proposal v2.4 AC:
- AC1: Add tmux availability check BEFORE acquiring shutdown lock
- AC2: Add pkill fallback for bd daemon when killall incomplete
- AC2: Return remaining count from stop functions for error reporting
- Style: interface{} → any (Go 1.18+)
* fix(prime): add validation for --state flag combination
The --state flag should be standalone and not combined with other flags.
Add validation at start of runPrime to enforce this.
Fixes TestPrimeFlagCombinations test failures.
* fix(review): address bot review critical issues
- isProcessRunning: handle pid<=0 as invalid (return false)
- isProcessRunning: handle EPERM as process exists (return true)
- stopBdDaemons: prevent negative killed count from race conditions
- stopBdActivityProcesses: prevent negative killed count from race conditions
* fix(review): critical fixes from deep review
Platform fixes:
- CountBdActivityProcesses: use sh -c "pgrep | wc -l" for macOS compatibility
(pgrep -c flag not available on BSD/macOS)
Correctness fixes:
- stopSession: return (wasRunning, error) to distinguish "stopped" vs "not running"
- daemon.IsRunning: handle error instead of ignoring with blank identifier
- stopBdDaemons/stopBdActivityProcesses: guard against negative killed counts
Safety fixes:
- --nuke: require GT_NUKE_ACKNOWLEDGED=1, don't just warn and proceed
- pkill patterns: document limitation about broad matching
Code cleanup:
- EnsureBdDaemonHealth: remove unused issues variable
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Updated comment to use "orphaned polecats" instead of "idle polecats".
With the self-cleaning model, polecats self-nuke on completion.
An orphan is from a crash, not a normal idle state.
Closes: gt-7l8y1
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The comment incorrectly referred to polecats without hooked work as "idle".
With the self-cleaning model, polecats self-nuke on completion - there are
no idle polecats. A polecat without work is orphaned (needs cleanup).
Closes: gt-0jn0k
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The swarm dispatch command now always spawns fresh polecats instead of
searching for idle ones to reuse. With the self-cleaning model, polecats
self-nuke when done - there are no idle polecats to reuse.
Closes: gt-h4yc3
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When a polecat runs `gt done` with COMPLETED status, it now nukes its own
worktree before exiting. This is the self-cleaning model - polecats clean
up after themselves, reducing Witness/Deacon cleanup burden.
The self-nuke is:
- Only attempted for polecats (not Mayor/Witness/Deacon/Refinery)
- Only on COMPLETED status (not ESCALATED/DEFERRED)
- Non-fatal: if it fails, Witness will handle cleanup
Closes: gt-fqcst
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
BeadsPath() was incorrectly returning <rig>/mayor/rig when HasMayor was
true, bypassing the redirect system at <rig>/.beads/redirect. This caused
beads operations to fail when the user's repo doesn't have tracked beads.
The redirect architecture is:
- <rig>/.beads/redirect -> mayor/rig/.beads (when repo tracks .beads/)
- <rig>/.beads/ contains local database (when repo doesn't track .beads/)
By always returning the rig root, all callers now go through the redirect
system which is set up by initBeads() during rig creation.
Affected callers (all now work correctly):
- internal/refinery/manager.go - Queue() for merge requests
- internal/swarm/manager.go - swarm operations
- internal/cmd/swarm.go - swarm CLI commands
- internal/cmd/status.go - rig status display
- internal/cmd/mq_next.go - merge queue operations
- internal/cmd/mq_list.go - merge queue listing
- internal/cmd/rig_dock.go - dock/undock operations
Fixes#317
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
gt done now always exits the session. The --exit flag is removed since
exit is the only sensible behavior - polecats don't stay alive after
signaling completion.
Closes: gt-yrz4k
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The --state flag is meant for quick state checks and cannot be
combined with --hook, --dry-run, or --explain flags.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Extract prime.go into focused files:
- prime_session.go: session ID handling, hooks, persistence
- prime_output.go: all output/rendering functions
- prime_molecule.go: molecule workflow context
- prime_state.go: handoff markers, session state detection
Main prime.go now ~730 lines with core flow visible as "table of contents".
No behavior changes - pure file organization following Go idioms.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
IsClaudeRunning now checks for child processes when the pane command is
a shell (bash/zsh). This fixes gt crew start --all killing running crew
members that were started with "export ... && claude ..." commands.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When a rig is added with --branch <non-default>, polecats and dogs now
correctly create worktrees from origin/<configured-branch> instead of
always using main/HEAD.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The witness manager was using rig-level beads path to look up role
configuration, but role beads use the hq- prefix and live in town-level
beads. This caused "unexpected end of JSON input" errors when starting
witnesses because the rig database (with gt- prefix) couldn't find
hq-witness-role.
Changed roleConfig() to use townRoot instead of rig.BeadsPath() to
correctly resolve town-level role beads.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
CreateAgentBead was creating beads with only --labels=gt:agent but
bd create defaults to --type=task. The bd slot set command requires
type=agent to set slots, causing warnings during gt install and
gt rig add.
Fixes#315
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
AGENTS.md had grown to 50 lines (above the 20-line bootstrap pointer
threshold) after dependency management docs were added in commit 14085db3.
The "Landing the Plane" and "Dependency Management" content belongs in
role templates (injected by gt prime), not in the on-disk bootstrap pointer.
This completes the fix for #316 - the AGENTS.md issue was caused by the
source repo having a large AGENTS.md that got cloned into rigs.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fresh installs and rig adds were creating full CLAUDE.md files (285 lines
for mayor, ~100 lines for other roles), causing gt doctor to fail the
priming check immediately.
Per the priming architecture, CLAUDE.md should be a minimal bootstrap
pointer (<30 lines) that tells agents to run gt prime. Full context is
injected ephemerally at session start.
Changes:
- install.go: createMayorCLAUDEmd now writes 12-line bootstrap pointer
- manager.go: createRoleCLAUDEmd now writes role-specific bootstrap pointers
for mayor, refinery, crew, and polecat roles
Note: The AGENTS.md issue mentioned in #316 could not be reproduced - the
code does not appear to create AGENTS.md at rig level. May be from an older
version or different configuration.
Partial fix for #316
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Polecats were burning 48k+ tokens on exploratory work when spawned because
the startup beacon was informational-only. By the time the propulsion nudge
arrived 2 seconds later, the agent had already started exploring.
The handoff topic already had explicit instructions; this adds the same
pattern for assigned work: "Work is on your hook. Run gt hook now."
Fixes#319
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>