From 510e925f2090f4c3305f3d5ea27a5e62c02915c5 Mon Sep 17 00:00:00 2001 From: Strycher Date: Thu, 2 Apr 2026 02:10:40 -0400 Subject: [PATCH 1/3] fix(#237): rename Beads to Citadel in firmware DevOps screen Update JSON source key from "beads" to "citadel" in dashboard parser. Rename UI labels from "Beads Projects" to "Citadel Projects". Both crowpanel5 and crowpanel7 build clean. Co-Authored-By: Claude Opus 4.6 (1M context) --- firmware/include/ui/screens/devops_screen.h | 4 ++-- firmware/src/dashboard_data.cpp | 4 ++-- firmware/src/ui/screens/devops_screen.cpp | 22 ++++++++++----------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/firmware/include/ui/screens/devops_screen.h b/firmware/include/ui/screens/devops_screen.h index 3e772aa..1e2f8b0 100644 --- a/firmware/include/ui/screens/devops_screen.h +++ b/firmware/include/ui/screens/devops_screen.h @@ -1,5 +1,5 @@ /** - * DevOps Screen — GitHub repos + Beads/agent status. + * DevOps Screen — GitHub repos + Citadel/agent status. * Scrollable repo cards with CI badges, agent activity summary. */ @@ -18,7 +18,7 @@ class DevOpsScreen : public BaseScreen { const DashboardData* _lastData = nullptr; bool _dirty = false; - /* Beads — per-project rows */ + /* Citadel — per-project rows */ lv_obj_t* _beadsContainer = nullptr; // flex column, rebuilt on update /* Agent status */ diff --git a/firmware/src/dashboard_data.cpp b/firmware/src/dashboard_data.cpp index f27db95..a82c28e 100644 --- a/firmware/src/dashboard_data.cpp +++ b/firmware/src/dashboard_data.cpp @@ -447,8 +447,8 @@ void DashboardParser::parse(const JsonDocument& doc, DashboardData& out) { out.home_assistant.status = SourceStatus::MISSING; } - /* Beads — bridge: data.projects{name: {open, in_progress, blocked, display_name}} */ - JsonObjectConst bd = sources["beads"]; + /* Citadel — bridge: data.projects{name: {open, in_progress, blocked, display_name}} */ + JsonObjectConst bd = sources["citadel"]; if (!bd.isNull()) { parseSourceMeta(bd, out.beads); JsonObjectConst bdd = bd["data"]; diff --git a/firmware/src/ui/screens/devops_screen.cpp b/firmware/src/ui/screens/devops_screen.cpp index 1d6f11f..4efca38 100644 --- a/firmware/src/ui/screens/devops_screen.cpp +++ b/firmware/src/ui/screens/devops_screen.cpp @@ -1,7 +1,7 @@ /** * DevOps Screen — Implementation * Content area: y=30..430. - * Left: scrollable repo cards. Right: Beads + agent status. + * Left: scrollable repo cards. Right: Citadel + agent status. */ #include "ui/screens/devops_screen.h" @@ -70,17 +70,17 @@ void DevOpsScreen::create(lv_obj_t* parent) { lv_obj_set_style_pad_row(_repoList, SU(6), 0); lv_obj_set_flex_flow(_repoList, LV_FLEX_FLOW_COLUMN); - /* === Right: Beads per-project card === */ - lv_obj_t* beadsCard = makeCard(_screen, SX(500), UI_CONTENT_Y + UI_PAD, + /* === Right: Citadel per-project card === */ + lv_obj_t* citadelCard = makeCard(_screen, SX(500), UI_CONTENT_Y + UI_PAD, SX(290), SY(180)); - lv_obj_t* beadsHeader = lv_label_create(beadsCard); - lv_label_set_text(beadsHeader, "Beads Projects"); - lv_obj_set_style_text_font(beadsHeader, &lv_font_montserrat_14, 0); - lv_obj_set_style_text_color(beadsHeader, TEXT_SECONDARY, 0); - lv_obj_align(beadsHeader, LV_ALIGN_TOP_LEFT, 0, 0); + lv_obj_t* citadelHeader = lv_label_create(citadelCard); + lv_label_set_text(citadelHeader, "Citadel Projects"); + lv_obj_set_style_text_font(citadelHeader, &lv_font_montserrat_14, 0); + lv_obj_set_style_text_color(citadelHeader, TEXT_SECONDARY, 0); + lv_obj_align(citadelHeader, LV_ALIGN_TOP_LEFT, 0, 0); - _beadsContainer = lv_obj_create(beadsCard); + _beadsContainer = lv_obj_create(citadelCard); lv_obj_set_size(_beadsContainer, SX(266), SY(146)); lv_obj_align(_beadsContainer, LV_ALIGN_TOP_LEFT, 0, SY(22)); lv_obj_set_style_bg_opa(_beadsContainer, LV_OPA_TRANSP, 0); @@ -260,7 +260,7 @@ void DevOpsScreen::update(const DashboardData& data) { _dirty = false; } - /* Beads — per-project rows */ + /* Citadel — per-project rows */ if (data.beads.status == SourceStatus::OK) { lv_obj_clean(_beadsContainer); const BeadsData& bd = data.beads.data; @@ -311,7 +311,7 @@ void DevOpsScreen::update(const DashboardData& data) { lv_obj_t* lbl = lv_label_create(_beadsContainer); lv_obj_set_style_text_font(lbl, &lv_font_montserrat_14, 0); lv_obj_set_style_text_color(lbl, TEXT_SECONDARY, 0); - lv_label_set_text(lbl, "No beads data"); + lv_label_set_text(lbl, "No Citadel data"); } /* Claude agent */ From c21e852faa818890aab1560174061399d47217de Mon Sep 17 00:00:00 2001 From: Strycher Date: Thu, 16 Apr 2026 23:11:49 -0400 Subject: [PATCH 2/3] chore: CLAUDE.md references canonical standards path (DifferentWire/standards#21) Canonical standards live at C:\Dev\DifferentWire\standards\, not C:\Dev\. The stale files at C:\Dev\ have been replaced with stubs that redirect here. See DifferentWire/standards#21. --- CLAUDE.md | 92 +++++++++++++++++++++++-------------------------------- 1 file changed, 38 insertions(+), 54 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 988ac1c..964de70 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,8 +1,8 @@ # Desk Command Center — Project Guide > **Standards:** This project follows DifferentWire standards. -> **Read and apply:** `C:\Dev\CLAUDE-BASE.md`, `C:\Dev\SAFELANE.md` -> **Credential inventory:** `C:\Dev\.credentials.env` +> **Read and apply:** `C:\Dev\DifferentWire\standards\CLAUDE-BASE.md`, `C:\Dev\DifferentWire\standards\SAFELANE.md` +> **Credential inventory:** `C:\Dev\DifferentWire\standards\.credentials.env` > **Hardware:** CrowPanel Advance 5.0" HMI (ESP32-S3-WROOM-1-N16R8) > **Framework:** ESP-IDF / Arduino + LVGL @@ -47,7 +47,7 @@ peripheral communication. 1. **Register with Agent Mail** — `ensure_project()` → `register_agent()` 2. **Check inbox** — `fetch_inbox()` for coordination messages -3. **Find work** — `bd ready` (Beads) for next available task +3. **Find work** — `dw ready` (Citadel) for next available task 4. **Claim ONE task** — Move to In Progress, begin work ### Session Modes @@ -55,60 +55,59 @@ peripheral communication. | Mode | Trigger | Behavior | |---------------|-------------|---------------------------------------------| | **Interactive** | Default | Answer questions, discuss design, edit docs. Do NOT claim tasks or write code autonomously. | -| **Worker** | `/work` | Full autonomous operation. Claim tasks, write code, commit, repeat until `bd ready` is empty. | +| **Worker** | `/work` | Full autonomous operation. Claim tasks, write code, commit, repeat until `dw ready` is empty. | ### Between-Task Reset (Worker Mode) 1. Release any file reservations 2. Check Agent Mail inbox -3. Run `bd ready` → pick highest-priority task +3. Run `dw ready` → pick highest-priority task 4. Repeat until queue is empty --- -## Task Management — GitHub + Beads +## Task Management — GitHub + Citadel **GitHub is the definitive source of truth.** Every task, bug, feature, and epic -MUST exist as a GitHub Issue first. Beads is the secondary workflow system that -drives agent task execution. +MUST exist as a GitHub Issue first. Citadel is the coordination layer that +drives agent task execution and enforces governance. ### GitHub-First Workflow (MANDATORY) **The pattern for ALL work:** 1. **Epic** — Create a GitHub Issue for the epic with checklist of stories -2. **Beads Epic** — Create a matching Beads epic: `bd create --title="Epic title (GH #N)"` +2. **Citadel Epic** — Register in Citadel: task title includes `(#N)` referencing GitHub issue 3. **Story Issues** — During breakdown, create a GitHub Issue for EVERY story/task 4. **Sub-issue links** — Link each story as a tracked sub-issue of the epic - (use `gh api graphql` with `addSubIssue` mutation) -5. **Beads tasks** — Create matching Beads tasks: `bd create --title="Story title (GH #N)"` -6. **Dependency chain** — Wire Beads dependencies: `bd dep add ` +5. **Citadel tasks** — Register tasks in Citadel with `external_issue_number` set +6. **Dependency chain** — Wire Citadel dependencies via API -**EVERY Beads task MUST have a corresponding GitHub Issue. NO EXCEPTIONS.** +**EVERY Citadel task MUST have a corresponding GitHub Issue. NO EXCEPTIONS.** **EVERY GitHub Issue for a story MUST be linked as a sub-issue of its parent epic.** ### Sync Rules (MANDATORY) -- **Creating work:** GitHub Issue FIRST → then Beads task with `(GH #N)` in title -- **Starting work:** `bd update --status=in_progress` AND +- **Creating work:** GitHub Issue FIRST → then Citadel task with `(#N)` in title +- **Starting work:** `dw claim ` AND `gh issue edit --add-label "board:in-progress"` -- **Closing work:** `bd close ` AND `gh issue close --comment "Completed."` +- **Closing work:** `dw close --reason "description"` AND `gh issue close --comment "Completed."` AND update epic body checkbox to `[x]` - **Board status:** Use `board:*` labels (see Label-Based Board Sync below) -- **Never use `gh project` commands** — they consume the shared GraphQL budget +- **Be judicious with `gh project` commands** — they consume the shared GraphQL budget - **Never skip the GitHub Issue** — if it doesn't exist in GitHub, it's wrong ### Commands -| Command | Purpose | -|-------------------|----------------------------------| -| `bd ready` | List tasks available to work | -| `bd list` | List all tasks | -| `bd show ` | Show task detail | -| `bd create` | Create new task | -| `bd update ` | Update task fields | -| `bd close ` | Close completed task | -| `bd dep tree` | Show dependency graph | +| Command | Purpose | +|------------------------------|----------------------------------| +| `dw ready` | List tasks available to work | +| `dw list` | List all tasks | +| `dw show ` | Show task detail | +| `dw claim ` | Claim a task (in_progress) | +| `dw close -r "reason"` | Close completed task | +| `dw blocked` | Show blocked tasks | +| `dw stats` | Project health statistics | ### Task Lifecycle @@ -116,7 +115,7 @@ drives agent task execution. Backlog → Todo → Ready → In Progress → Testing → Done ``` -- `open` status = approved for development (visible to `bd ready`) +- `ready` status = approved for development (visible to `dw ready`) - `backlog` / `todo` = hidden from agent work queues ### Task Sizing (MANDATORY) @@ -304,31 +303,16 @@ ssh strycher@192.168.50.24 '/home/strycher/dcc-bridge/update_config.sh --restore --- -## Dolt Servers +## Citadel (Task Management) -### Pi 5 Dolt (DCC Beads — canonical for BOTH `bd` CLI and bridge) - -| Field | Value | -|--------------|-----------------------------------------------------------| -| Container | `dcc-dolt` on Pi 5 (`192.168.50.24`) | -| Database | `beads_DCC` | -| Port | `3306` | -| Used by | `bd` CLI (via `.beads/metadata.json`) AND bridge adapter | - -`.beads/metadata.json` points to `192.168.50.24` with `dolt_database: beads_DCC`. -There is NO local embedded Dolt for DCC. A legacy `.beads/dolt/` directory may -exist but is unused and deprecated. - -### Hetzner Dolt (Unfocused/FC Beads — NOT for DCC) - -| Field | Value | -|--------------|-----------------------------------------------------------| -| Container | `dolt-beads` on Hetzner (`46.224.181.82`) | -| Databases | `beads_Unfocused`, `beads_FC` | -| Tunnel | `ssh -fNL 3307:127.0.0.1:3307 unfocused@46.224.181.82` | -| Used by | Unfocused and Field Compass projects ONLY | - -**DCC has NO database on Hetzner.** Do not point DCC `bd` commands at Hetzner. +| Field | Value | +|--------------|--------------------------------------------------| +| API | `https://getunfocused.app/citadel/` | +| Health | `https://getunfocused.app/citadel/health` | +| Server | Hetzner `46.224.181.82` (Docker: FastAPI + PostgreSQL 16) | +| CLI | `dw` (Python) | +| Project | `Desk_Command_Center` | +| GitHub Org | `Strycher` (NOT DifferentWire) | --- @@ -336,7 +320,7 @@ exist but is unused and deprecated. If any of the following are unreachable, **STOP and alert the user:** -- **Beads** (Dolt database via SSH tunnel) +- **Citadel** (task management API) - **Agent Mail** (MCP server) Do not fall back to GitHub-only workflows. These tools are required for @@ -404,6 +388,6 @@ Labels are mutually exclusive within their group — the Action auto-removes old ## GitHub API Budget - Use `gh` CLI (REST API) for issues/PRs — separate 5,000/hr budget -- **NEVER** use `gh project` commands (GraphQL, shared 5,000 pt/hr budget). Use labels instead (see Label-Based Board Sync above). -- Never query GitHub to find work — use `bd ready` +- **Be judicious with `gh project` commands** (GraphQL, shared 5,000 pt/hr budget). Prefer labels (see Label-Based Board Sync above). +- Never query GitHub to find work — use `dw ready` - No polling loops against GitHub API From 47b104fafca345efdb799002e3eb50b3ba7a2bf2 Mon Sep 17 00:00:00 2001 From: Strycher Date: Thu, 21 May 2026 13:15:19 -0400 Subject: [PATCH 3/3] chore: preflight gains canonical-cmd sync (#61) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Byte-identical propagation of DifferentWire/standards@77d73f5 with PROJECT_DIR=Desk_Command_Center. New preflight step 5 syncs canonical slash commands from standards/.claude/commands/ into this repo's .claude/commands/ on every SessionStart — auto-installs /refresh-context and any future canonical commands. Bypass: DW_SKIP_CITADEL_CHECK=1 used per user-approved rollout-batch bypass (DifferentWire/standards#61 approval comment, 2026-05-21). Tracked under Citadel standards-fb2. --- .claude/hooks/preflight.sh | 278 +++++++++++-------------------------- 1 file changed, 79 insertions(+), 199 deletions(-) diff --git a/.claude/hooks/preflight.sh b/.claude/hooks/preflight.sh index cbeb198..9e59919 100644 --- a/.claude/hooks/preflight.sh +++ b/.claude/hooks/preflight.sh @@ -1,30 +1,30 @@ #!/usr/bin/env bash # ============================================================================= -# Desktop Command Center — CLI Agent Pre-flight Check +# DifferentWire Standard Preflight Hook # ============================================================================= # Runs on SessionStart. Blocks the session (exit 2) if required coordination -# services are unavailable. Auto-starts the Dolt SSH tunnel if it's down. +# services are unavailable. ALL checks are hard blocks. # # Required services: -# 1. Dolt SSH tunnel (localhost → Hetzner:3307) — for Beads task tracking -# 2. MCP Agent Mail (getunfocused.app/mcp/) — for multi-agent coordination -# -# Zombie port detection: -# If a previous session's SSH tunnel died without closing its socket, -# the port stays LISTENING with a dead PID. This hook detects that and -# falls back to alternate ports (3308, 3309). +# 1. Citadel API — task management (HARD BLOCK) +# 2. Agent Mail — multi-agent coordination (HARD BLOCK) # # Exit codes: -# 0 = all checks passed, session may proceed -# 2 = BLOCKING — required service unavailable, session must not start +# 0 = all checks passed +# 2 = BLOCKING — required service unavailable +# +# Projects copy this to .claude/hooks/preflight.sh and customize PROJECT_DIR. # ============================================================================= set -euo pipefail -HETZNER_HOST="unfocused@46.224.181.82" -DOLT_PORTS=(3307 3308 3309) # Cascade: try each until one works +# ─── Project-specific overrides (edit these) ────────────────────────────── +PROJECT_DIR="/c/Dev/Desk_Command_Center" +# ────────────────────────────────────────────────────────────────────────── + +CITADEL_HEALTH="https://getunfocused.app/citadel/health" AGENT_MAIL_HEALTH="https://getunfocused.app/health/liveness" -PID_FILE="$HOME/.dcc-tunnel.pid" +HETZNER_HOST="unfocused@46.224.181.82" PASS="✓" FAIL="✗" @@ -37,215 +37,106 @@ fail() { log "$FAIL $1"; errors=$((errors + 1)); } warn() { log "$WARN $1"; } log "" -log "═══ Desktop Command Center Pre-flight Check ═══" +log "═══ Preflight Check ═══" log "" -# ─── Helper: check if a PID is a live process ───────────────────────────── -pid_alive() { - local pid=$1 - # tasklist on Windows — returns 0 if process exists - if tasklist //FI "PID eq $pid" 2>/dev/null | grep -q "$pid"; then - return 0 - fi - return 1 -} - -# ─── Helper: check if port is listening AND the PID is alive ────────────── -port_healthy() { - local port=$1 - local pid - pid=$(netstat -ano 2>/dev/null | grep "127.0.0.1:${port}" | grep -i "listen" | awk '{print $NF}' | head -1) - - if [ -z "$pid" ]; then - return 1 # Port not listening at all - fi - - if pid_alive "$pid"; then - return 0 # Port listening with a live process — healthy - else - warn "Port $port held by zombie PID $pid (process dead, socket orphaned)" - return 1 - fi -} - -# ─── Helper: kill old tunnel from PID file ──────────────────────────────── -cleanup_old_tunnel() { - if [ -f "$PID_FILE" ]; then - local old_pid - old_pid=$(cat "$PID_FILE" 2>/dev/null || true) - if [ -n "$old_pid" ] && pid_alive "$old_pid"; then - log " Killing previous tunnel (PID $old_pid)..." - taskkill //F //PID "$old_pid" 2>/dev/null || kill "$old_pid" 2>/dev/null || true - sleep 1 - fi - rm -f "$PID_FILE" - fi -} - -# ─── 1. Dolt SSH Tunnel ───────────────────────────────────────────────────── -# Beads connects to Dolt via localhost. This requires an SSH tunnel -# forwarding to the Hetzner server where Dolt runs. - -ACTIVE_PORT="" - -# First, try to clean up any previous tunnel -cleanup_old_tunnel - -# Try each port in the cascade -for port in "${DOLT_PORTS[@]}"; do - if port_healthy "$port"; then - ACTIVE_PORT=$port - pass "Dolt SSH tunnel (port $port — existing healthy tunnel)" - break - fi - - # Port not healthy (either not listening or zombie). Try to start tunnel. - # -o ExitOnForwardFailure=yes = fail immediately if port is already bound - if ssh -o ExitOnForwardFailure=yes -fNL "${port}:127.0.0.1:3307" "$HETZNER_HOST" 2>/dev/null; then - sleep 2 - if port_healthy "$port"; then - # Save the PID for cleanup by next session - local_pid=$(netstat -ano 2>/dev/null | grep "127.0.0.1:${port}" | grep -i "listen" | awk '{print $NF}' | head -1) - echo "$local_pid" > "$PID_FILE" - ACTIVE_PORT=$port - pass "Dolt SSH tunnel (auto-started on port $port)" - break - fi - fi - - # This port didn't work (zombie or bind failure), try next - if [ "$port" != "${DOLT_PORTS[-1]}" ]; then - warn "Port $port unavailable, trying next..." - fi -done - -if [ -z "$ACTIVE_PORT" ]; then - fail "Dolt SSH tunnel — all ports (${DOLT_PORTS[*]}) failed" - log " Zombie ports may require system reboot to release." - log " Manual fix: ssh -fNL 3307:127.0.0.1:3307 ${HETZNER_HOST}" -fi - -# ─── 2. Beads Connectivity ────────────────────────────────────────────────── -# Verify Beads can actually talk to Dolt through the tunnel. - -if [ -n "$ACTIVE_PORT" ]; then - export PATH="/c/Dev/flutter/bin:$PATH" - export BD_DSN="root:@tcp(127.0.0.1:${ACTIVE_PORT})/Desk_Command_Center" - - if bd list --quiet 2>/dev/null | head -1 >/dev/null 2>&1; then - pass "Beads (bd list via port $ACTIVE_PORT)" - - # ─── 2b. Pull latest Beads data from Dolt remote ───────────── - # Prevents agents from starting with stale task state. - # Warning only — stale data is better than a blocked session. - # - # Auto-commit metadata first: `dolt push` writes tracking data to - # the metadata table outside SQL transactions, leaving dirty state - # that blocks all future pulls. Commit it silently before pulling. - ssh -o ConnectTimeout=5 "$HETZNER_HOST" \ - "docker exec dolt-beads dolt --data-dir=/var/lib/dolt sql -q \ - \"USE Desk_Command_Center; CALL dolt_add('metadata'); CALL dolt_commit('-m', 'chore: auto-commit metadata before pull', '--allow-empty');\"" \ - 2>/dev/null || true - - if bd dolt pull 2>/dev/null; then - pass "Beads data synced (bd dolt pull)" - else - warn "bd dolt pull failed — session may have stale Beads data" - log " This is a warning, not a blocker. Continuing." - fi - else - # Tunnel is up but Beads can't connect — maybe Dolt server isn't running - fail "Beads cannot reach Dolt (tunnel up but Dolt may not be running)" - log " Check Dolt on server: ssh ${HETZNER_HOST} \"docker ps | grep dolt\"" - fi +# ─── 1. Citadel API (HARD BLOCK) ──────────────────────────────────────── +# Validate response body contains "citadel", not just HTTP 200. +# The Flutter catch-all returns 200 with HTML for any path — checking +# status code alone produces false positives. +CITADEL_BODY=$(curl -s --max-time 5 "$CITADEL_HEALTH" 2>/dev/null || echo "") +if echo "$CITADEL_BODY" | grep -q '"citadel"'; then + pass "Citadel API" +else + fail "Citadel API unreachable or returning wrong content" + log " Response: ${CITADEL_BODY:0:80}" + log " No task management = no work. Fix before proceeding." + log " Check: ssh ${HETZNER_HOST} \"docker ps | grep api\"" fi -# ─── 3. MCP Agent Mail ────────────────────────────────────────────────────── -# Agent Mail runs on Hetzner, accessed via HTTPS through Cloudflare/nginx. - +# ─── 2. Agent Mail (HARD BLOCK) ───────────────────────────────────────── if curl -s --connect-timeout 5 "$AGENT_MAIL_HEALTH" 2>/dev/null | grep -q "alive"; then - pass "Agent Mail (${AGENT_MAIL_HEALTH})" + pass "Agent Mail" else - fail "Agent Mail is unreachable" + fail "Agent Mail unreachable" + log " No coordination layer = no multi-agent safety. Fix before proceeding." log " Check: ssh ${HETZNER_HOST} \"cd /opt/mcp_agent_mail && docker compose -f docker-compose.prod.yml logs --tail 20\"" - log " Restart: ssh ${HETZNER_HOST} \"cd /opt/mcp_agent_mail && docker compose -f docker-compose.prod.yml down && docker compose -f docker-compose.prod.yml up -d\"" fi -# ─── 4. Git Hooks Path ───────────────────────────────────────────────────── -# Ensure core.hookspath points to .claude/hooks/ (tracked, works in worktrees). -# .beads/hooks/ is gitignored and doesn't exist in worktrees → no hooks run. - -HOOKS_PATH=$(cd /c/Dev/Desk_Command_Center && git config core.hookspath 2>/dev/null || echo "") +# ─── 3. Git Hooks Path ────────────────────────────────────────────────── +HOOKS_PATH=$(cd "$PROJECT_DIR" && git config core.hookspath 2>/dev/null || echo "") if [ "$HOOKS_PATH" = ".claude/hooks" ]; then - pass "Git hooks path (.claude/hooks — tracked, worktree-safe)" + pass "Git hooks path (.claude/hooks)" else warn "Git hooks path is '${HOOKS_PATH:-}' — fixing to .claude/hooks" - cd /c/Dev/Desk_Command_Center && git config core.hookspath .claude/hooks - pass "Git hooks path fixed to .claude/hooks" + cd "$PROJECT_DIR" && git config core.hookspath .claude/hooks + pass "Git hooks path fixed" fi -# ─── 5. Abandoned Worktree Detection ────────────────────────────────────── -# Scan for worktrees that might be leftovers from crashed agent sessions. -# Warning only — don't auto-delete (might have uncommitted work). - +# ─── 4. Abandoned Worktree Detection ──────────────────────────────────── ABANDONED=() while IFS= read -r wt_line; do wt_path=$(echo "$wt_line" | awk '{print $1}') wt_branch=$(echo "$wt_line" | awk '{print $2}' | tr -d '[]') - - # Skip bare repo and main worktree - if [ "$wt_path" = "/c/Dev/Desk_Command_Center" ] || [ "$wt_path" = "/c/Dev/Desk_Command_Center-main" ]; then - continue - fi - - # Skip if it looks like a known pattern but check age + if [ "$wt_path" = "$PROJECT_DIR" ]; then continue; fi if [ -d "$wt_path" ]; then - # Check last commit time in the worktree LAST_COMMIT=$(cd "$wt_path" 2>/dev/null && git log -1 --format=%ct 2>/dev/null || echo "0") NOW=$(date +%s) AGE_HOURS=$(( (NOW - LAST_COMMIT) / 3600 )) - if [ "$AGE_HOURS" -gt 24 ]; then - ABANDONED+=("$wt_path (branch: $wt_branch, last commit: ${AGE_HOURS}h ago)") + ABANDONED+=("$wt_path (branch: $wt_branch, ${AGE_HOURS}h ago)") fi fi -done < <(cd /c/Dev/Desk_Command_Center && git worktree list 2>/dev/null | grep -v "^$") +done < <(cd "$PROJECT_DIR" && git worktree list 2>/dev/null | grep -v "^$") if [ ${#ABANDONED[@]} -gt 0 ]; then warn "Found ${#ABANDONED[@]} potentially abandoned worktree(s):" for wt in "${ABANDONED[@]}"; do log " $wt" done - log " To clean up: git worktree remove && git branch -d " - log " These may contain uncommitted work from crashed agent sessions." fi -# ─── 6. Claude Heartbeat to Bridge ────────────────────────────────────────── -# DCC-specific: send heartbeat to the Pi5 bridge for status display. - -BRIDGE_URL="http://192.168.50.24:8080" -AGENT_NAME="" -if [ -f "${CLAUDE_PROJECT_DIR:-.}/.agent-identity" ]; then - AGENT_NAME=$(cat "${CLAUDE_PROJECT_DIR:-.}/.agent-identity" 2>/dev/null | tr -d '[:space:]') -fi -if [ -z "$AGENT_NAME" ]; then - AGENT_NAME="Claude-$$" +# ─── 5. Canonical Slash Commands Sync ─────────────────────────────────── +# Sync command files from standards/.claude/commands/ into this project's +# .claude/commands/. Convention: any file in standards/.claude/commands/ +# is canonical — meant to be available in every DW project. Updates +# propagate to all projects at next SessionStart, OR when /refresh-context +# is invoked in a long-running session (since /refresh-context invokes this +# preflight). Project-local-only commands (like /work) live in +# /.claude/commands/ but have no canonical counterpart, so the +# sync never touches them. + +CANONICAL_CMDS="/c/Dev/DifferentWire/standards/.claude/commands" +PROJECT_CMDS="$PROJECT_DIR/.claude/commands" + +if [ -d "$CANONICAL_CMDS" ]; then + mkdir -p "$PROJECT_CMDS" + synced=0 + installed=0 + # Guard against `set -e` aborting on the cmp/cp non-zero returns + for canonical in "$CANONICAL_CMDS"/*.md; do + [ -f "$canonical" ] || continue + name=$(basename "$canonical") + local="$PROJECT_CMDS/$name" + if [ ! -f "$local" ]; then + cp "$canonical" "$local" && installed=$((installed + 1)) || true + elif ! cmp -s "$canonical" "$local"; then + cp "$canonical" "$local" && synced=$((synced + 1)) || true + fi + done + if [ "$installed" -gt 0 ] || [ "$synced" -gt 0 ]; then + pass "Canonical commands: $installed installed, $synced updated" + else + pass "Canonical commands: up to date" + fi +else + warn "Canonical commands dir not found at $CANONICAL_CMDS — standards repo not at canonical path?" fi -curl -sf --max-time 3 -X POST "$BRIDGE_URL/api/claude/heartbeat" \ - -H "Content-Type: application/json" \ - -d "{\"agent\": \"$AGENT_NAME\", \"task\": \"Session starting\", \"program\": \"claude-code\"}" \ - >/dev/null 2>&1 && pass "Claude heartbeat sent ($AGENT_NAME)" || warn "Claude heartbeat failed (non-blocking)" - -# ─── 7. Session State (Compaction Recovery) ────────────────────────────────── -# If an agent session is active, output its state. This output becomes a -# system reminder that survives context compaction, allowing the agent to -# recover its working context (current epic, task, branch, budget). - -# Resolve script path relative to this file (works in any worktree) +# ─── 6. Session State Recovery ─────────────────────────────────────────── PREFLIGHT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SESSION_STATE_SCRIPT="$PREFLIGHT_DIR/session-state.sh" -SESSION_STATE_FILE="/c/Dev/Desk_Command_Center/.claude/agent-session.json" +SESSION_STATE_FILE="$PROJECT_DIR/.claude/agent-session.json" if [ -f "$SESSION_STATE_FILE" ] && [ -f "$SESSION_STATE_SCRIPT" ]; then SESSION_OUTPUT=$(bash "$SESSION_STATE_SCRIPT" read 2>/dev/null || true) @@ -257,29 +148,18 @@ if [ -f "$SESSION_STATE_FILE" ] && [ -f "$SESSION_STATE_SCRIPT" ]; then log " $line" done <<< "$SESSION_OUTPUT" log "" - log " This session state was recovered from disk." - log " If /work was invoked, resume your current task." - log " If this is a new interactive session, ignore this — run" - log " 'bash .claude/hooks/session-state.sh reset' to clear stale state." - log "" fi fi -# ─── Result ────────────────────────────────────────────────────────────────── - +# ─── Result ────────────────────────────────────────────────────────────── log "" if [ "$errors" -gt 0 ]; then log "═══ BLOCKED: $errors check(s) failed ═══" - log "Fix the issues above, then retry. Agents must not operate without" - log "Beads (task coordination) and Agent Mail (file reservation)." + log "Agents must not operate without Citadel AND Agent Mail." log "" exit 2 fi -if [ -n "$ACTIVE_PORT" ]; then - log " Export: BD_DSN=root:@tcp(127.0.0.1:${ACTIVE_PORT})/Desk_Command_Center" -fi - log "═══ All checks passed — session ready ═══" log "" exit 0