Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 10 additions & 106 deletions .bashrc
Original file line number Diff line number Diff line change
Expand Up @@ -330,120 +330,24 @@ if [ -z "${ZELLIJ:-}" ] && [ -z "${BASH_EXECUTION_STRING:-}" ] && command -v zel
fi

# ═══════════════════════════════════════════════════
# Agent + Cockpit helpers with Zellij Tab Awareness
# Agent helpers
# ═══════════════════════════════════════════════════
_agent_resolve_project_dir() {
local CONFIG="$HOME/.config/claude-projects.conf"
[ -f "$CONFIG" ] || return 1

local TAB_NAME TMP="/tmp/_claude_zt_$$.txt"
zellij action dump-layout > "$TMP" 2>/dev/null
TAB_NAME=$(grep 'focus=true' "$TMP" | grep 'tab name=' | sed 's/.*tab name="\([^"]*\)".*/\1/')
rm -f "$TMP"
[ -z "$TAB_NAME" ] && return 1

local DIR
DIR=$(while IFS='|' read -r name dir; do
[[ "$name" =~ ^#.*$ ]] && continue
[ -z "$name" ] && continue
local clean_tab=$(echo "$TAB_NAME" | sed 's/[$[:space:]]*$//')
local clean_name=$(echo "$name" | sed 's/[$[:space:]]*$//')
if [ "${clean_tab,,}" = "${clean_name,,}" ]; then
echo "$dir"
break
fi
done < "$CONFIG")

[ -z "$DIR" ] && return 1
[ -d "$DIR" ] || return 1
echo "$DIR"
}

_agent_cd_from_tab_if_home() {
[ "$PWD" = "$HOME" ] || return 0
[ -n "${ZELLIJ:-}" ] || return 0

local PROJECT_DIR
PROJECT_DIR=$(_agent_resolve_project_dir)
if [ -n "$PROJECT_DIR" ]; then
echo " -> Tab -> $PROJECT_DIR"
cd "$PROJECT_DIR" || return 1
fi
}

claude() {
_agent_cd_from_tab_if_home || true

local SESSION_DIR="$HOME/.claude/sessions"
mkdir -p "$SESSION_DIR"

local DIR_HASH=$(echo "$(pwd)" | md5sum | cut -d' ' -f1)
local SESSION_FILE="$SESSION_DIR/$DIR_HASH.txt"
local PROJECT=$(basename "$(pwd)")

if [ -f "$SESSION_FILE" ]; then
local SAVED_TIME=$(cut -d'|' -f2 "$SESSION_FILE" 2>/dev/null || echo "0")
local NOW=$(date +%s)
local DIFF=$((NOW - SAVED_TIME))

if [ $DIFF -gt 120 ] && [ $DIFF -lt 172800 ]; then
local SAVED_PROJECT=$(cut -d'|' -f1 "$SESSION_FILE")
local HOURS=$((DIFF / 3600))
local MINS=$(((DIFF % 3600) / 60))

local TIME_AGO=""
if [ $HOURS -gt 0 ]; then
TIME_AGO="${HOURS}h ${MINS}m ago"
else
TIME_AGO="${MINS}m ago"
fi

echo ""
echo " Previous session: $SAVED_PROJECT ($TIME_AGO)"
echo " Tell Claude: \"continue\""
echo ""
fi
fi

echo "$PROJECT|$(date +%s)|$(pwd)" > "$SESSION_FILE"

# Write tab identity keyed by ZELLIJ_PANE_ID so Claude can detect its project
# even after context-limit continuations (which don't re-run this wrapper).
if [ -n "$ZELLIJ_PANE_ID" ]; then
local _tab
_tab=$(zellij action dump-layout 2>/dev/null \
| grep 'focus=true' \
| grep 'tab name=' \
| sed 's/.*tab name="\([^"]*\)".*/\1/' \
| head -1)
[ -n "$_tab" ] && printf '%s' "$_tab" > "/tmp/claude-pane-${ZELLIJ_PANE_ID}"

# Record which monitor the cursor is on right now — cursor is in the terminal
# at invocation time, so this is the terminal's screen. beacon.py reads this.
python3 "$HOME/dev/cockpit/scripts/record-terminal-screen.py" \
"${ZELLIJ_PANE_ID}" 2>/dev/null &
fi
# The zellij-tab -> project mapping that used to live here was removed on
# 2026-08-27. It resolved the focused tab name against ~/.config/
# claude-projects.conf and silently cd'd there. Tabs have not been renamed per
# project for months (they read "Tab #9"), so it matched nothing — and when it
# did match it could move you into the wrong project. cwd is the project.

command claude "$@"
rm -f "/tmp/claude-pane-${ZELLIJ_PANE_ID}" "/tmp/claude-screen-${ZELLIJ_PANE_ID}"
}

codex() {
_agent_cd_from_tab_if_home || true
command codex --no-alt-screen "$@"
}

cockpit() {
cd "$HOME/dev/cockpit" || return 1
npm run dev "$@"
}
# claude() and codex() used to be defined here. Both were overridden ~130
# lines below ("Override existing claude/codex"), so these copies never ran —
# the live definitions are the later ones. cockpit() cd'd into ~/dev/cockpit,
# which no longer exists (the repo became fleetcrown). Removed 2026-08-27.

# Secrets (passwords, tokens) — loaded from .env, which is gitignored
[ -f "$HOME/.env" ] && source "$HOME/.env"

# --- Agent Orchestration Wrappers ---
_agent_pre_launch() {
_agent_cd_from_tab_if_home || true
if [ -n "$ZELLIJ_PANE_ID" ]; then
local _tab
_tab=$(zellij action dump-layout 2>/dev/null | grep 'focus=true' | grep 'tab name=' | sed 's/.*tab name="\([^"]*\)".*/\1/' | head -1)
Expand Down
16 changes: 0 additions & 16 deletions .claude/hooks/beacon.py

This file was deleted.

6 changes: 0 additions & 6 deletions .claude/hooks/lib.sh

This file was deleted.

10 changes: 9 additions & 1 deletion .claude/hooks/notification.sh
Original file line number Diff line number Diff line change
@@ -1,2 +1,10 @@
#!/usr/bin/env bash
exec "/home/g/.local/share/fleetcrown-beacon/agent-hook-bridge.sh" notification
# No-op since 2026-08-27.
#
# This previously exec'd ~/.local/share/fleetcrown-beacon/agent-hook-bridge.sh,
# which has not existed since the bash bridge was retired — so every
# Notification event failed here. Dispatch on idle now belongs to FleetCrown's
# Fleet Runner (home/watcher.ts -> notifyOnIdle), not to this repo.
#
# Kept as a wired no-op rather than deleted so settings.json stays valid.
exit 0
25 changes: 0 additions & 25 deletions .config/claude-projects.conf

This file was deleted.

2 changes: 1 addition & 1 deletion .config/claude-prompts.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@
"label": "Close session",
"style": "more",
"category": "ship",
"prompt": "Close this work session cleanly so the next session can start cold. Perform these steps in order:\n\nStep 1 — Finish uncommitted work:\nRun git status. If there are uncommitted changes, commit them with a conventional commit message (<type>(<scope>): <description>). If scratch/debug work, stash or discard it.\n\nStep 2 — Quality gate:\nRun the project type checker and linter (e.g. tsc --noEmit && eslint src/ for TypeScript). They must pass with zero errors. Fix any failures before continuing.\n\nStep 3 — Run tests:\nRun the project test suite. Note the result (N pass / N fail).\n\nStep 4 — Write session handoff:\nDetermine the tab name: run `grep -i \"$(pwd -P)\" ~/.config/claude-projects.conf | head -1 | cut -d'|' -f1 | tr -d ' '`. Use that as TAB (fall back to directory name if empty). Write exactly these lines to ~/.claude/sessions/<TAB>.md:\ndone: <one sentence — what was completed this session>\nnext: <one sentence — the single most important action for next session>\ntests: <e.g. \"18/18 smoke pass · tsc clean · eslint clean\">\ntodos: <e.g. \"0 TODOs\">\nhealth: <good | degraded | critical> — HEAD <short-git-hash>, <one-line status>\n\nStep 5 — Final report:\nPrint a clear summary:\n- Uncommitted changes: YES (committed as <hash>) / NO / STASHED\n- Quality checks: PASS / FAIL\n- Tests: PASS (N/N) / FAIL\n- Session file written to: ~/.claude/sessions/<TAB>.md\n- Safe to close this tab: YES / NO — <reason if NO>\n\n(Note: the popup-suppression sentinel is written automatically by the infrastructure — you do not need to write any /tmp files.)"
"prompt": "Close this work session cleanly so the next session can start cold. Perform these steps in order:\n\nStep 1 — Finish uncommitted work:\nRun git status. If there are uncommitted changes, commit them with a conventional commit message (<type>(<scope>): <description>). If scratch/debug work, stash or discard it.\n\nStep 2 — Quality gate:\nRun the project type checker and linter (e.g. tsc --noEmit && eslint src/ for TypeScript). They must pass with zero errors. Fix any failures before continuing.\n\nStep 3 — Run tests:\nRun the project test suite. Note the result (N pass / N fail).\n\nStep 4 — Write session handoff:\nDetermine the project: run `basename \"$(git rev-parse --show-toplevel 2>/dev/null || pwd -P)\"`. Use that as PROJECT (a worktree resolves to its repo root, not the worktree name). Write exactly these lines to ~/.claude/sessions/<PROJECT>.md:\ndone: <one sentence — what was completed this session>\nnext: <one sentence — the single most important action for next session>\ntests: <e.g. \"18/18 smoke pass · tsc clean · eslint clean\">\ntodos: <e.g. \"0 TODOs\">\nhealth: <good | degraded | critical> — HEAD <short-git-hash>, <one-line status>\n\nStep 5 — Final report:\nPrint a clear summary:\n- Uncommitted changes: YES (committed as <hash>) / NO / STASHED\n- Quality checks: PASS / FAIL\n- Tests: PASS (N/N) / FAIL\n- Session file written to: ~/.claude/sessions/<PROJECT>.md\n- Safe to close this tab: YES / NO — <reason if NO>\n\n(Note: the popup-suppression sentinel is written automatically by the infrastructure — you do not need to write any /tmp files.)"
},
{
"key": "continue",
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,7 @@ __pycache__/

# Per-session agent worktrees — scratch, never part of the repo.
.claude/worktrees/

# FleetCrown Fleet Runner generates these on startup and rewrites them.
# Their source is FleetCrown (capture-hook.ts), not this repo.
.claude/hooks/fleetcrown-*.sh
100 changes: 65 additions & 35 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,56 +1,86 @@
## Mission
Turn AI-assisted development into a reliable autopilot: Claude autonomously maintains and advances 19+ projects, surfaces the right work at the right time, and keeps the human in control with minimal friction.

## What This Repo Is
George's dotfiles and Claude Code automation system. The Claude hooks in `.claude/hooks/` are the core product — a real-time agentic development platform.

George's dotfiles, plus the **fleet's central automation** — the CI checks and
audits that run from here against every other repo, and `SHARED.md`, the
registry of shared packages.

**It is no longer the agent runtime.** The stop/notify/dispatch loop that used
to live here — the PyQt beacon, prompt injection into Zellij tabs, the bash
bridge — migrated to FleetCrown (Fleet Runner) over 2026-06. What remains in
`.claude/hooks/` is the thin per-session edge; anything about *dispatching
work to agents* belongs in FleetCrown, not here.

## Architecture

```
.claude/hooks/
claude-popup.py PyQt6 stop/confirm popup (Whisper STT, progressive disclosure)
claude-dashboard.py PyQt6 projects dashboard (All Projects overview + profiles)
stop.sh Fires on Claude finish → popup → prompt injection into Zellij tab
notification.sh Fires on Claude pause → auto-injects "continue"
pre-tool-use.sh Auto-approves safe commands, shows confirm popup for destructive ops
lib.sh Shared: resolve_tab, get_prompt, inject_prompt, play_sound

.config/
claude-prompts.json SSOT for all 13 injected prompts (next_best, test_and_fix, etc.)
claude-projects.conf Zellij tab name → project directory registry (19 projects)
claude-dashboard-settings.json Runtime settings: whisper model, countdown, Claude model
scripts/ci/ Fleet-wide audits. These run FROM here against every repo,
so there is nothing to adopt and nothing to drift:
auto-merge-sweep.sh the canonical merge policy (16 repos
call it as a reusable workflow)
model-pin-audit.mjs is any pinned model id still served?
verify-floor-audit.sh does every repo's `verify` really run
lint + typecheck + test?
shared-inventory.sh the duplication ratchet
ui-defect-audit.mjs, verify-predicates.sh, find-false-needs.py
Each has a test-*.sh / test-*.mjs beside it. Keep it that way.
scripts/fleet/ Local upkeep: gc-merged-worktrees.sh, stranded-work.sh
scripts/audit/ secret-in-response.py
templates/ci/ Golden CI workflows + pre-commit. ONE central copy, never a
fork per repo — see templates/ci/README.md.
.github/workflows/ This repo's own CI, plus the scheduled fleet audits.
SHARED.md The shared-package registry and the duplication ratchet.
Read it before building anything cross-cutting.

.claude/hooks/ Per-session edge only (see the table below).
.claude/skills/ fix-lint, fix-types, write-tests
.config/ kitty, starship, zellij, git hooks
.bashrc .ssh/config .editorconfig install.sh
```

## Design System (PyQt6)
All design tokens live in `claude-dashboard.py` at the top as module-level constants.
**SSOT rule**: never hardcode a color, size, or spacing inline — use the constants.
## The hooks, and which are actually alive

Font stack (set via `QFont` in `main()`):
- UI: Inter → Segoe UI → Helvetica Neue → sans-serif
- Mono: JetBrains Mono → Fira Code → Cascadia Code
`settings.json` is the SSOT for what runs. Verified 2026-08-27:

Spacing uses an 8px grid: 8, 16, 24, 32, 48.
Border radius: sm=8, md=12, lg=16, card=12.
| Hook | Event | State |
|---|---|---|
| `pre-tool-use.sh` | PreToolUse | **alive** — auto-approves; audits destructive commands to `dangerous-commands.log` |
| `post-tool-use.sh` | PostToolUse | **alive** — typechecks after `.ts/.tsx` writes and feeds errors back |
| `keep-awake-acquire.sh` / `-release.sh` | UserPromptSubmit / Stop | **alive** — inhibits sleep while a session is working |
| `fleetcrown-capture.sh`, `fleetcrown-session-end.sh` | UserPromptSubmit / Stop | **alive, but not ours** — generated by FleetCrown's Fleet Runner, which rewrites them on startup. Gitignored deliberately. Do not edit here. |
| `stop.sh` | Stop | **no-op since 2026-06-11.** Dispatch moved to Fleet Runner. |
| `notification.sh` | Notification | **no-op since 2026-08-27.** Previously exec'd a bridge under `~/.local/share/fleetcrown-beacon/` that no longer exists, so it failed on every pause. |

If you are about to add agent-dispatch logic here, stop: that is FleetCrown's job
now. This repo's half is the environment and the fleet-wide checks.

## When Working Here

**Test changes (always run both):**
**Test changes:**
```bash
python3 -c "import py_compile; py_compile.compile('.claude/hooks/claude-dashboard.py', doraise=True)"
DISPLAY=:0 timeout 4 python3 .claude/hooks/claude-dashboard.py
bash -n .claude/hooks/*.sh # syntax
scripts/ci/test-auto-merge-sweep.sh # the audits have real suites — run
scripts/ci/test-model-pin-audit.mjs # the one you touched
scripts/ci/test-shared-inventory.sh
scripts/fleet/test-stranded-work.sh
```

**Commit frequently** — these hooks power all 19 projects. Every working state should be committed.
**Commit frequently** — these checks gate every repo in the fleet.

**After editing a hook**: takes effect on the next session event. There is no
daemon to restart.

**Before adding a fleet-wide checker**: it probably exists in `scripts/ci/`.
Before adding a shared library: read `SHARED.md`.

**After editing prompts** (`claude-prompts.json`): the changes take effect immediately on the next Stop hook fire. No restart needed.
## Project detection

**After editing hooks** (`stop.sh`, `notification.sh`, etc.): changes take effect on the next Claude session finish/pause.
Use `cwd`. The session registry at `~/.claude/sessions/<pid>.json` is the SSOT
for what else is running.

## Key Constraints
- PyQt6 only — no web tech, no Electron, no external servers
- Prompts injected via `zellij action write-chars` — no length limit but keep them focused
- Session files at `~/.claude/sessions/<TabName>.md` — one per project, updated by Claude
- Settings at `~/.config/claude-dashboard-settings.json` — shared between popup and dashboard
**Zellij tab names are not project identity.** Tabs read `Tab #9`, one pane hosts
many concurrent sessions, and the `claude-projects.conf` mapping was removed on
2026-08-27 after it had been dead for months (nothing could match `Tab #9`).
Do not reintroduce it.

## Quality Standards
@~/.claude/CLAUDE.md
42 changes: 29 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,38 @@
# dotfiles

Personal development environment configuration for Linux.
George's Linux environment, plus the fleet's central automation.

## What's included
Two things live here, and only these two:

- Shell configuration (Zsh/Bash)
- Editor settings
- Git configuration
- Tool configs and aliases

## Setup
**1. The environment.** `.bashrc`, `.ssh/config`, `.editorconfig`, and
`.config/` (kitty, starship, zellij, git hooks). Installed by symlink:

```bash
git clone https://github.com/g-but/dotfiles.git ~/dotfiles
cd ~/dotfiles
# Symlink configs as needed
git clone git@github.com:catomean/dotfiles.git ~/dev/dotfiles
cd ~/dev/dotfiles && ./install.sh
```

## Structure
**2. Fleet-wide automation.** Checks that run *from this repo against every
other repo*, so there is one copy and nothing can drift:

| | |
|---|---|
| [`SHARED.md`](SHARED.md) | the shared-package registry and the duplication ratchet — **read before building anything cross-cutting** |
| `scripts/ci/auto-merge-sweep.sh` | the canonical merge policy; 16 repos call it as a reusable workflow |
| `scripts/ci/model-pin-audit.mjs` | runs daily: is any model id the fleet pins still served by its vendor? |
| `scripts/ci/verify-floor-audit.sh` | does every repo's `verify` actually run lint + typecheck + test? |
| `scripts/ci/shared-inventory.sh` | counts duplication across the fleet and holds it as a ratchet |
| `templates/ci/` | golden CI workflows + pre-commit, deliberately one central copy |

Every audit has a test suite beside it (`test-*.sh`, `test-*.mjs`). Keep it
that way — these gate every repo, so a broken checker is a fleet-wide outage.

## What does *not* live here

Agent dispatch. The stop/notify loop, prompt injection and the PyQt beacon
migrated to **FleetCrown** (Fleet Runner) over 2026-06. `.claude/hooks/` keeps
only the thin per-session edge — see [CLAUDE.md](CLAUDE.md) for which hooks are
alive and which are deliberate no-ops.

Configuration files are organized by tool. Symlink or source them from your home directory.
Product and business decisions belong in the repo that owns them, not beside
`SHARED.md`.
2 changes: 0 additions & 2 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,6 @@ link .config/kitty/kitty.conf
link .config/starship.toml
link .config/zellij/config.kdl

# Project mapping
link .config/claude-projects.conf
link .config/claude-prompts-meta.json

# Editor
Expand Down