sync: 2026-03-16 17:30 — 21 files from Alexandria
Some checks failed
Lint & Format / detect (push) Has been cancelled
Lint & Format / js-lint (push) Has been cancelled
Lint & Format / py-lint (push) Has been cancelled
Lint & Format / sh-lint (push) Has been cancelled
Lint & Format / go-lint (push) Has been cancelled
Monorepo Lint / lint-shell (push) Has been cancelled
Monorepo Lint / lint-js (push) Has been cancelled

RoadChain-SHA2048: 2bb8ae01244552ce
RoadChain-Identity: alexa@sovereign
RoadChain-Full: 2bb8ae01244552cecab2d00dcd4d9be800c7f72efd48c0042d181cbe095735be688df2988e3e51f8e6ea2562aaf6e4a842be17389c27b9c02b0680dec7fb1a89a8117868ffa1457a937dccb40674e118d9394e4131c5000ee0e516109be0623b37d46fa3eb1f2c8916755e5060648c3dee42a48ca180464f284d95844a67c9e1708a1b4da531c5135deaa21f9bbe0de9e1695c09909c55c4f395cbbacde4778f9e4620cbb7b055d7b8b41dbf384076cd3944d7561aebb6dc8720ab432c2720722e162a6a9d08c1decf429b62e1ff12a6517b569ddc8e0664bd785ff1f56c7c9735fef29c60c9899bc1fb55945e9ee015d9cae0ef5023519ac656f29fa8e27a2e
This commit is contained in:
2026-03-16 17:30:02 -05:00
parent e438144610
commit 24ed26bbc7
21 changed files with 1612 additions and 155 deletions

View File

@@ -0,0 +1,52 @@
# BlackRoad Collaboration Protocol
Every Claude session is part of a team. You are never working alone.
## On Session Start
1. `memory-collaboration.sh inbox` — check for handoffs from previous sessions
2. `memory-collaboration.sh register "<what you're working on>"` — announce yourself
3. Check codex before solving — don't reinvent what's already solved
## During Work
- `memory-collaboration.sh announce "<update>"` — share progress with the team
- `memory-collaboration.sh post "<message>"` — quick Slack message
- `memory-agent-dispatch.sh progress "<update>"` — structured progress update
- `memory-slack.sh ask <agent> "<question>"` — ask an AI agent for help
## When Done
- `memory-collaboration.sh handoff "<what's next>"` — leave context for the next session
- `memory-planner.sh wrap` — auto-generate handoff from session activity
- `memory-collaboration.sh complete` — mark session as done
## Slack Agents
| Agent | Specialty | Use for |
|-------|-----------|---------|
| alice | Network, DNS, security | Infrastructure questions |
| cecilia | AI, models, embedding | ML/AI questions |
| octavia | Architecture, Gitea, Docker | Systems design |
| lucidia | Web apps, GitHub Actions | Frontend, deployment |
| shellfish | Security, vulnerabilities | Security review |
| caddy | CI/CD, builds | Build issues |
| alexa | CEO, strategy | Business decisions |
| road | BlackRoad OS overall | General questions |
## CLI Shortcuts
```bash
br collab status # Full collaboration dashboard
br collab inbox # Check messages
br collab announce "x" # Broadcast update
br collab handoff "x" # Leave handoff note
br collab ask agent "q" # Ask an AI agent
br collab debate "topic"# Multi-agent debate
br collab standup # Generate standup
br collab roadmap # Project roadmap
br collab sprint # Sprint board
br collab wrap # End-of-session wrap-up
br slack say "message" # Quick Slack post
br plan next # What to work on next
br dispatch queue # Show all available work
br watchdog heartbeat # Health check
```
## Key Principle
**Every session leaves the system better than it found it.** Log your work, broadcast your learnings, and leave a clear handoff for the next Claude.

View File

@@ -0,0 +1,371 @@
#!/bin/bash
# BlackRoad Agent Dispatch — Task dispatch, sprint board, work queue
# Usage: memory-agent-dispatch.sh <command> [args]
set -e
COLLAB_DB="$HOME/.blackroad/collaboration.db"
TASKS_DB="$HOME/.blackroad/memory/tasks.db"
PROJECTS_DIR="$HOME/.blackroad/memory/infinite-todos/projects"
SESSION_FILE="$HOME/.blackroad/memory/current-collab-session"
JOURNAL="$HOME/.blackroad/memory/journals/master-journal.jsonl"
SLACK_API="https://blackroad-slack.amundsonalexa.workers.dev"
TODOS="$HOME/blackroad-operator/scripts/memory/memory-infinite-todos.sh"
COLLAB="$HOME/blackroad-operator/scripts/memory/memory-collaboration.sh"
PINK='\033[38;5;205m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
RED='\033[0;31m'
BOLD='\033[1m'
DIM='\033[2m'
NC='\033[0m'
sql_collab() { sqlite3 "$COLLAB_DB" "$@" 2>/dev/null; }
sql_tasks() { sqlite3 "$TASKS_DB" "$@" 2>/dev/null; }
get_session() {
[[ -f "$SESSION_FILE" ]] && cat "$SESSION_FILE" || echo "unknown"
}
post_to_slack() {
curl -s --max-time 3 --connect-timeout 2 \
-X POST "$SLACK_API/post" \
-H "Content-Type: application/json" \
-d "$(jq -n --arg t "$1" '{text:$t}')" >/dev/null 2>&1 || true
}
log_journal() {
local action="$1" entity="$2" details="$3"
local ts
ts=$(date -u +"%Y-%m-%dT%H:%M:%S.3NZ")
local hash
hash=$(echo -n "$ts$action$entity$details" | shasum -a 256 | cut -c1-16)
printf '{"timestamp":"%s","action":"%s","entity":"%s","details":"%s","hash":"%s"}\n' \
"$ts" "$action" "$entity" "$details" "$hash" >> "$JOURNAL" 2>/dev/null || true
}
# ── DISPATCH ──
cmd_dispatch() {
local project_id="$1"
[[ -z "$project_id" ]] && { echo -e "${RED}Usage: $0 dispatch <project-id>${NC}"; exit 1; }
local pf="$PROJECTS_DIR/${project_id}.json"
if [[ ! -f "$pf" ]]; then
echo -e "${RED}Project not found: $project_id${NC}"
exit 1
fi
local next_todo next_id
next_todo=$(jq -r '[.todos[] | select(.status == "pending")][0].text // empty' "$pf")
next_id=$(jq -r '[.todos[] | select(.status == "pending")][0].todo_id // empty' "$pf")
if [[ -z "$next_todo" ]]; then
echo -e "${GREEN}Project $project_id has no pending todos!${NC}"
return
fi
local title
title=$(jq -r '.title' "$pf")
local session
session=$(get_session)
echo -e "${PINK}[DISPATCH]${NC} ${BOLD}$title${NC}"
echo -e " ${YELLOW}${NC} [$next_id] $next_todo"
echo ""
# Post to Slack
post_to_slack "🎯 *Task Dispatched* [$session]\nProject: *$title*\nTodo: $next_todo\nID: $next_id" &
# Log
log_journal "dispatch" "$project_id" "Dispatched: $next_todo ($next_id)"
echo -e " ${GREEN}Dispatched and announced${NC}"
}
# ── QUEUE ──
cmd_queue() {
echo -e "${PINK}╔════════════════════════════════════════════════════════════╗${NC}"
echo -e "${PINK}${NC} ${BOLD}Available Work Queue${NC} ${PINK}${NC}"
echo -e "${PINK}╚════════════════════════════════════════════════════════════╝${NC}"
echo ""
# Project todos
echo -e " ${BOLD}📋 Project Todos:${NC}"
local found=0
for f in "$PROJECTS_DIR"/*.json; do
[[ ! -f "$f" ]] && continue
local status
status=$(jq -r '.status' "$f")
[[ "$status" != "active" ]] && continue
local pending
pending=$(jq '[.todos[] | select(.status == "pending")] | length' "$f")
[[ "$pending" -eq 0 ]] && continue
local pid title progress
pid=$(jq -r '.project_id' "$f")
title=$(jq -r '.title' "$f")
progress=$(jq -r '.progress' "$f")
echo -e " ${CYAN}$pid${NC}$title (${progress}%, $pending pending)"
jq -r '[.todos[] | select(.status == "pending")][0:3][] | " ⬜ [\(.todo_id)] \(.text)"' "$f"
found=1
done
[[ "$found" -eq 0 ]] && echo -e " ${GREEN}No pending project todos${NC}"
echo ""
# Marketplace tasks
if [[ -f "$TASKS_DB" ]]; then
echo -e " ${BOLD}🏪 Marketplace Tasks:${NC}"
local available
available=$(sql_tasks "SELECT count(*) FROM tasks WHERE status='available';")
echo -e " $available available task(s)"
sql_tasks "SELECT task_id, title FROM tasks WHERE status='available' ORDER BY created_at DESC LIMIT 5;" 2>/dev/null | while IFS='|' read -r tid title; do
echo -e " ${BLUE}$tid${NC}: $title"
done
fi
echo ""
}
# ── CLAIM ──
cmd_claim() {
local task_id="$1"
[[ -z "$task_id" ]] && { echo -e "${RED}Usage: $0 claim <task-id>${NC}"; exit 1; }
if [[ ! -f "$TASKS_DB" ]]; then
echo -e "${RED}Task marketplace DB not found${NC}"
exit 1
fi
local session
session=$(get_session)
local now
now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
sql_tasks "UPDATE tasks SET status='claimed', claimed_by='$session', claimed_at='$now' WHERE task_id='$task_id' AND status='available';"
local affected
affected=$(sql_tasks "SELECT changes();")
if [[ "$affected" -gt 0 ]]; then
local title
title=$(sql_tasks "SELECT title FROM tasks WHERE task_id='$task_id';")
echo -e "${GREEN}Claimed:${NC} $title"
post_to_slack "📌 *Task Claimed* [$session]: $title" &
log_journal "task-claim" "$task_id" "Claimed by $session: $title"
else
echo -e "${YELLOW}Could not claim $task_id (may not exist or already claimed)${NC}"
fi
}
# ── PROGRESS ──
cmd_progress() {
local msg="$*"
[[ -z "$msg" ]] && { echo -e "${RED}Usage: $0 progress <message>${NC}"; exit 1; }
local session
session=$(get_session)
local now
now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
# Log to collab DB
if [[ -f "$COLLAB_DB" ]]; then
local msg_id="prog-$(date +%s)-$$"
sql_collab "INSERT INTO messages (msg_id, session_id, type, message, created_at) VALUES ('$msg_id', '$session', 'progress', '$(echo "$msg" | sed "s/'/''/g")', '$now');"
sql_collab "UPDATE sessions SET last_seen='$now' WHERE session_id='$session';"
fi
echo -e "${GREEN}Progress:${NC} $msg"
post_to_slack "⚡ *Progress* [$session]: $msg" &
log_journal "progress" "$session" "$msg"
}
# ── DONE ──
cmd_done() {
local todo_id="$1"
local project_id="$2"
if [[ -z "$todo_id" ]]; then
echo -e "${RED}Usage: $0 done <todo-id> [project-id]${NC}"
exit 1
fi
# If project not specified, search for it
if [[ -z "$project_id" ]]; then
for f in "$PROJECTS_DIR"/*.json; do
[[ ! -f "$f" ]] && continue
if jq -e ".todos[] | select(.todo_id == \"$todo_id\")" "$f" >/dev/null 2>&1; then
project_id=$(jq -r '.project_id' "$f")
break
fi
done
fi
if [[ -z "$project_id" ]]; then
echo -e "${RED}Could not find todo $todo_id in any project${NC}"
exit 1
fi
# Complete via infinite todos
"$TODOS" complete-todo "$project_id" "$todo_id" 2>/dev/null || true
local session
session=$(get_session)
echo -e "${GREEN}Completed:${NC} $todo_id in $project_id"
post_to_slack "✅ *Todo Done* [$session]: $todo_id in $project_id" &
log_journal "todo-complete" "$project_id" "Completed $todo_id"
# Show next todo
local pf="$PROJECTS_DIR/${project_id}.json"
if [[ -f "$pf" ]]; then
local next
next=$(jq -r '[.todos[] | select(.status == "pending")][0].text // empty' "$pf")
if [[ -n "$next" ]]; then
echo -e "${YELLOW}Next:${NC} $next"
else
echo -e "${GREEN}All todos complete for $project_id!${NC}"
fi
fi
}
# ── SPRINT ──
cmd_sprint() {
echo -e "${PINK}╔════════════════════════════════════════════════════════════╗${NC}"
echo -e "${PINK}${NC} ${BOLD}Sprint Board${NC} ${PINK}${NC}"
echo -e "${PINK}╚════════════════════════════════════════════════════════════╝${NC}"
echo ""
# In Progress — active sessions with focus
echo -e " ${BOLD}🔄 In Progress:${NC}"
if [[ -f "$COLLAB_DB" ]]; then
sql_collab "SELECT session_id, focus FROM sessions WHERE status='active' AND focus != '' ORDER BY last_seen DESC LIMIT 10;" | while IFS='|' read -r sid focus; do
echo -e " ${GREEN}${NC} ${CYAN}${sid}${NC}$focus"
done
local no_focus
no_focus=$(sql_collab "SELECT count(*) FROM sessions WHERE status='active' AND (focus='' OR focus IS NULL);")
[[ "$no_focus" -gt 0 ]] && echo -e " ${DIM}+ $no_focus session(s) without focus${NC}"
fi
echo ""
# Claimed marketplace tasks
if [[ -f "$TASKS_DB" ]]; then
local claimed
claimed=$(sql_tasks "SELECT count(*) FROM tasks WHERE status='claimed';")
if [[ "$claimed" -gt 0 ]]; then
echo -e " ${BOLD}📌 Claimed Tasks:${NC}"
sql_tasks "SELECT task_id, title, claimed_by FROM tasks WHERE status='claimed' ORDER BY claimed_at DESC LIMIT 5;" | while IFS='|' read -r tid title by; do
echo -e " ${BLUE}$tid${NC}: $title ${DIM}(${by})${NC}"
done
echo ""
fi
fi
# Up Next — pending todos from top priority projects
echo -e " ${BOLD}📋 Up Next:${NC}"
local count=0
for f in "$PROJECTS_DIR"/*.json; do
[[ ! -f "$f" ]] && continue
[[ "$count" -ge 5 ]] && break
local status
status=$(jq -r '.status' "$f")
[[ "$status" != "active" ]] && continue
local pending
pending=$(jq '[.todos[] | select(.status == "pending")] | length' "$f")
[[ "$pending" -eq 0 ]] && continue
local pid next_todo
pid=$(jq -r '.project_id' "$f")
next_todo=$(jq -r '[.todos[] | select(.status == "pending")][0].text' "$f")
echo -e "${CYAN}$pid${NC}: $next_todo"
((count++))
done
echo ""
# Done today
if [[ -f "$COLLAB_DB" ]]; then
local done_today
done_today=$(sql_collab "SELECT count(*) FROM messages WHERE type='progress' AND created_at > datetime('now', '-24 hours');")
echo -e " ${BOLD}✅ Done today:${NC} $done_today progress updates"
sql_collab "SELECT message FROM messages WHERE type='progress' AND created_at > datetime('now', '-24 hours') ORDER BY created_at DESC LIMIT 5;" | while read -r msg; do
echo -e "$msg"
done
fi
}
# ── AUTOQUEUE ──
cmd_autoqueue() {
echo -e "${PINK}[DISPATCH]${NC} ${BOLD}Auto-generating tasks from projects...${NC}"
echo ""
if [[ ! -f "$TASKS_DB" ]]; then
echo -e "${RED}Task marketplace DB not found${NC}"
exit 1
fi
local added=0
for f in "$PROJECTS_DIR"/*.json; do
[[ ! -f "$f" ]] && continue
local status
status=$(jq -r '.status' "$f")
[[ "$status" != "active" ]] && continue
local pid title
pid=$(jq -r '.project_id' "$f")
title=$(jq -r '.title' "$f")
# Get pending todos not already in marketplace
jq -r '.todos[] | select(.status == "pending") | "\(.todo_id)|\(.text)"' "$f" | while IFS='|' read -r tid text; do
local existing
existing=$(sql_tasks "SELECT count(*) FROM tasks WHERE task_id='${pid}-${tid}';")
if [[ "$existing" -eq 0 ]]; then
local now
now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
sql_tasks "INSERT INTO tasks (task_id, title, description, status, priority, category, created_at) VALUES ('${pid}-${tid}', '$(echo "$text" | sed "s/'/''/g")', 'From project: $title', 'available', 'medium', '$pid', '$now');" 2>/dev/null || true
echo -e " ${GREEN}+${NC} ${CYAN}${pid}-${tid}${NC}: $text"
((added++)) || true
fi
done
done
echo ""
echo -e "${GREEN}Added $added task(s) to marketplace${NC}"
}
# ── HELP ──
cmd_help() {
cat <<EOF
${PINK}╔════════════════════════════════════════════════════════════╗${NC}
${PINK}║${NC} ${BOLD}BlackRoad Agent Dispatch${NC} ${PINK}║${NC}
${PINK}╚════════════════════════════════════════════════════════════╝${NC}
${BOLD}Task Management:${NC}
${CYAN}dispatch <project>${NC} Dispatch next todo for a project
${CYAN}queue${NC} Show all available work
${CYAN}claim <task-id>${NC} Claim a marketplace task
${CYAN}done <todo-id>${NC} Mark a todo as complete
${BOLD}Progress:${NC}
${CYAN}progress <msg>${NC} Post progress update (DB + Slack)
${CYAN}sprint${NC} Sprint board: in-progress, next, done
${BOLD}Automation:${NC}
${CYAN}autoqueue${NC} Auto-generate marketplace tasks from projects
EOF
}
case "${1:-help}" in
dispatch|dis) shift; cmd_dispatch "$@" ;;
queue|q) cmd_queue ;;
claim|c) shift; cmd_claim "$@" ;;
progress|prog) shift; cmd_progress "$@" ;;
done|complete) shift; cmd_done "$@" ;;
sprint|sp) cmd_sprint ;;
autoqueue|auto) cmd_autoqueue ;;
help|--help|-h) cmd_help ;;
*)
echo -e "${RED}Unknown: $1${NC}"
cmd_help
exit 1
;;
esac

View File

@@ -0,0 +1,444 @@
#!/bin/bash
# BlackRoad Planner — Project planning, focus tracking, standups, wrap-ups
# Usage: memory-planner.sh <command> [args]
set -e
COLLAB_DB="$HOME/.blackroad/collaboration.db"
PROJECTS_DIR="$HOME/.blackroad/memory/infinite-todos/projects"
SESSION_FILE="$HOME/.blackroad/memory/current-collab-session"
JOURNAL="$HOME/.blackroad/memory/journals/master-journal.jsonl"
SLACK_API="https://blackroad-slack.amundsonalexa.workers.dev"
TODOS="$HOME/blackroad-operator/scripts/memory/memory-infinite-todos.sh"
COLLAB="$HOME/blackroad-operator/scripts/memory/memory-collaboration.sh"
PINK='\033[38;5;205m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
RED='\033[0;31m'
BOLD='\033[1m'
DIM='\033[2m'
NC='\033[0m'
sql() { sqlite3 "$COLLAB_DB" "$@" 2>/dev/null; }
get_session() {
[[ -f "$SESSION_FILE" ]] && cat "$SESSION_FILE" || echo ""
}
post_to_slack() {
curl -s --max-time 3 --connect-timeout 2 \
-X POST "$SLACK_API/post" \
-H "Content-Type: application/json" \
-d "$(jq -n --arg t "$1" '{text:$t}')" >/dev/null 2>&1 || true
}
# ── PLAN ──
cmd_plan() {
local project_id="$1"
[[ -z "$project_id" ]] && { echo -e "${RED}Usage: $0 plan <project-id>${NC}"; exit 1; }
local project_file="$PROJECTS_DIR/${project_id}.json"
if [[ ! -f "$project_file" ]]; then
echo -e "${RED}Project not found: $project_id${NC}"
echo -e "${YELLOW}Available:${NC}"
ls "$PROJECTS_DIR"/*.json 2>/dev/null | while read -r f; do
local pid
pid=$(jq -r '.project_id' "$f")
local title
title=$(jq -r '.title' "$f")
local prog
prog=$(jq -r '.progress' "$f")
echo -e " ${CYAN}$pid${NC}$title (${prog}%)"
done
exit 1
fi
local title cadence status progress
title=$(jq -r '.title' "$project_file")
cadence=$(jq -r '.cadence' "$project_file")
status=$(jq -r '.status' "$project_file")
progress=$(jq -r '.progress' "$project_file")
echo -e "${PINK}╔════════════════════════════════════════════════════════════╗${NC}"
echo -e "${PINK}${NC} ${BOLD}$title${NC}"
echo -e "${PINK}╚════════════════════════════════════════════════════════════╝${NC}"
echo ""
echo -e " ${BOLD}ID:${NC} ${CYAN}${project_id}${NC} | ${BOLD}Cadence:${NC} ${cadence} | ${BOLD}Status:${NC} ${status}"
# Progress bar
local bar_len=40
local filled=$(( progress * bar_len / 100 ))
local empty=$(( bar_len - filled ))
local bar="${GREEN}"
for ((i=0; i<filled; i++)); do bar+="█"; done
bar+="${DIM}"
for ((i=0; i<empty; i++)); do bar+="░"; done
bar+="${NC}"
echo -e " ${BOLD}Progress:${NC} $bar ${progress}%"
echo ""
# Todos
local total_todos done_todos pending_todos
total_todos=$(jq '.todos | length' "$project_file")
done_todos=$(jq '[.todos[] | select(.status == "completed")] | length' "$project_file")
pending_todos=$(jq '[.todos[] | select(.status == "pending")] | length' "$project_file")
echo -e " ${BOLD}Todos:${NC} $done_todos/$total_todos done, $pending_todos pending"
echo ""
# Show completed
if [[ "$done_todos" -gt 0 ]]; then
echo -e " ${GREEN}Completed:${NC}"
jq -r '.todos[] | select(.status == "completed") | " ✅ \(.text)"' "$project_file"
echo ""
fi
# Show pending
if [[ "$pending_todos" -gt 0 ]]; then
echo -e " ${YELLOW}Pending:${NC}"
jq -r '.todos[] | select(.status == "pending") | " ⬜ [\(.todo_id)] \(.text)"' "$project_file"
echo ""
fi
}
# ── NEXT ──
cmd_next() {
echo -e "${PINK}[PLANNER]${NC} ${BOLD}Finding next action...${NC}"
echo ""
local best_project="" best_todo="" best_priority=999 best_progress=999
for f in "$PROJECTS_DIR"/*.json; do
[[ ! -f "$f" ]] && continue
local status
status=$(jq -r '.status' "$f")
[[ "$status" != "active" ]] && continue
local progress pending_count cadence pid title
progress=$(jq -r '.progress' "$f")
pending_count=$(jq '[.todos[] | select(.status == "pending")] | length' "$f")
[[ "$pending_count" -eq 0 ]] && continue
cadence=$(jq -r '.cadence' "$f")
pid=$(jq -r '.project_id' "$f")
title=$(jq -r '.title' "$f")
# Priority: weekly=1, monthly=2, forever=3
local priority=3
case "$cadence" in
weekly) priority=1 ;;
monthly) priority=2 ;;
esac
if [[ "$priority" -lt "$best_priority" ]] || \
[[ "$priority" -eq "$best_priority" && "$progress" -lt "$best_progress" ]]; then
best_priority=$priority
best_progress=$progress
best_project="$pid"
best_todo=$(jq -r '[.todos[] | select(.status == "pending")][0].text' "$f")
fi
done
if [[ -n "$best_project" ]]; then
echo -e " ${BOLD}Next up:${NC} ${CYAN}$best_project${NC} (${best_progress}%)"
echo -e " ${YELLOW}${NC} $best_todo"
echo ""
echo -e " ${DIM}Set focus: $0 focus $best_project${NC}"
else
echo -e " ${GREEN}All projects complete or no pending todos!${NC}"
fi
}
# ── FOCUS ──
cmd_focus() {
local project_id="$1"
[[ -z "$project_id" ]] && { echo -e "${RED}Usage: $0 focus <project-id>${NC}"; exit 1; }
local session
session=$(get_session)
if [[ -z "$session" ]]; then
echo -e "${RED}No active session. Run: memory-collaboration.sh register${NC}"
exit 1
fi
# Update session focus
if [[ -f "$COLLAB_DB" ]]; then
sql "UPDATE sessions SET focus='$project_id' WHERE session_id='$session';"
fi
# Get project title
local title=""
local pf="$PROJECTS_DIR/${project_id}.json"
[[ -f "$pf" ]] && title=$(jq -r '.title' "$pf")
echo -e "${GREEN}Focus set:${NC} ${CYAN}${project_id}${NC}${title:+ — $title}"
# Announce to Slack
"$COLLAB" announce "Focus: $project_id${title:+ — $title}" 2>/dev/null || true
}
# ── STANDUP ──
cmd_standup() {
echo -e "${PINK}╔════════════════════════════════════════════════════════════╗${NC}"
echo -e "${PINK}${NC} ${BOLD}Standup Report${NC} ${PINK}${NC}"
echo -e "${PINK}╚════════════════════════════════════════════════════════════╝${NC}"
echo ""
# What was done (last handoff picked up by this session)
echo -e " ${BOLD}✅ What was done (last handoff):${NC}"
if [[ -f "$COLLAB_DB" ]]; then
local last_handoff
last_handoff=$(sql "SELECT message FROM handoffs WHERE picked_up_by != '' ORDER BY picked_up_at DESC LIMIT 1;")
if [[ -n "$last_handoff" ]]; then
echo -e " $last_handoff"
else
echo -e " ${DIM}No handoff found${NC}"
fi
fi
echo ""
# What's planned (next action)
echo -e " ${BOLD}🎯 What's planned:${NC}"
for f in "$PROJECTS_DIR"/*.json; do
[[ ! -f "$f" ]] && continue
local status
status=$(jq -r '.status' "$f")
[[ "$status" != "active" ]] && continue
local pending
pending=$(jq '[.todos[] | select(.status == "pending")] | length' "$f")
[[ "$pending" -eq 0 ]] && continue
local pid next_todo
pid=$(jq -r '.project_id' "$f")
next_todo=$(jq -r '[.todos[] | select(.status == "pending")][0].text' "$f")
echo -e " ${CYAN}$pid${NC}: $next_todo"
done
echo ""
# Blockers
echo -e " ${BOLD}🚧 Blockers:${NC}"
echo -e " ${DIM}(add manually or check memory for known issues)${NC}"
echo ""
# Post to Slack
echo -e " ${DIM}Post to Slack: $0 standup --slack${NC}"
if [[ "$1" == "--slack" ]]; then
local msg="📋 *Standup Report*\n"
msg="${msg}✅ Done: $(sql "SELECT message FROM handoffs WHERE picked_up_by != '' ORDER BY picked_up_at DESC LIMIT 1;" 2>/dev/null || echo 'N/A')\n"
msg="${msg}🎯 Next: $(cmd_next 2>/dev/null | grep "→" | head -1 || echo 'checking...')"
post_to_slack "$msg"
echo -e " ${GREEN}Posted to Slack${NC}"
fi
}
# ── ROADMAP ──
cmd_roadmap() {
echo -e "${PINK}╔════════════════════════════════════════════════════════════╗${NC}"
echo -e "${PINK}${NC} ${BOLD}BlackRoad Project Roadmap${NC} ${PINK}${NC}"
echo -e "${PINK}╚════════════════════════════════════════════════════════════╝${NC}"
echo ""
for cadence in weekly monthly forever; do
local label icon
case "$cadence" in
weekly) label="WEEKLY"; icon="📆" ;;
monthly) label="MONTHLY"; icon="📊" ;;
forever) label="FOREVER"; icon="♾️" ;;
esac
local found=0
for f in "$PROJECTS_DIR"/*.json; do
[[ ! -f "$f" ]] && continue
local fc
fc=$(jq -r '.cadence' "$f")
[[ "$fc" != "$cadence" ]] && continue
if [[ "$found" -eq 0 ]]; then
echo -e " ${BOLD}$icon $label${NC}"
found=1
fi
local pid title progress status pending
pid=$(jq -r '.project_id' "$f")
title=$(jq -r '.title' "$f")
progress=$(jq -r '.progress' "$f")
status=$(jq -r '.status' "$f")
pending=$(jq '[.todos[] | select(.status == "pending")] | length' "$f")
local color="$GREEN"
[[ "$progress" -lt 50 ]] && color="$YELLOW"
[[ "$progress" -lt 20 ]] && color="$RED"
[[ "$progress" -eq 100 ]] && color="$GREEN"
local bar=""
local filled=$(( progress / 10 ))
for ((i=0; i<filled; i++)); do bar+="█"; done
for ((i=filled; i<10; i++)); do bar+="░"; done
echo -e " ${color}${bar}${NC} ${progress}% ${CYAN}${pid}${NC}$title ($pending pending)"
done
[[ "$found" -gt 0 ]] && echo ""
done
}
# ── REVIEW ──
cmd_review() {
echo -e "${PINK}╔════════════════════════════════════════════════════════════╗${NC}"
echo -e "${PINK}${NC} ${BOLD}Session Review${NC} ${PINK}${NC}"
echo -e "${PINK}╚════════════════════════════════════════════════════════════╝${NC}"
echo ""
local session
session=$(get_session)
echo -e " ${BOLD}Session:${NC} ${CYAN}${session:-unknown}${NC}"
echo ""
if [[ -f "$COLLAB_DB" && -n "$session" ]]; then
# Messages sent this session
echo -e " ${BOLD}Messages sent:${NC}"
sql "SELECT type, message, created_at FROM messages WHERE session_id='$session' ORDER BY created_at;" | while IFS='|' read -r type msg created; do
echo -e " [${created:11:8}] ${CYAN}${type}${NC}: ${msg:0:70}"
done
# Handoffs created
echo ""
echo -e " ${BOLD}Handoffs left:${NC}"
sql "SELECT message FROM handoffs WHERE from_session='$session';" | while read -r msg; do
echo -e " 🤝 $msg"
done
fi
# Recent journal entries
if [[ -f "$JOURNAL" ]]; then
echo ""
echo -e " ${BOLD}Recent journal activity:${NC}"
tail -10 "$JOURNAL" | while read -r line; do
local action entity ts
action=$(echo "$line" | jq -r '.action')
entity=$(echo "$line" | jq -r '.entity')
ts=$(echo "$line" | jq -r '.timestamp' | cut -dT -f2 | cut -d. -f1)
echo -e " [${ts}] ${action}${entity}"
done
fi
}
# ── SUGGEST ──
cmd_suggest() {
echo -e "${PINK}[PLANNER]${NC} ${BOLD}Analyzing projects...${NC}"
echo ""
local suggestions=()
for f in "$PROJECTS_DIR"/*.json; do
[[ ! -f "$f" ]] && continue
local status
status=$(jq -r '.status' "$f")
[[ "$status" != "active" ]] && continue
local pid progress pending cadence title
pid=$(jq -r '.project_id' "$f")
progress=$(jq -r '.progress' "$f")
pending=$(jq '[.todos[] | select(.status == "pending")] | length' "$f")
cadence=$(jq -r '.cadence' "$f")
title=$(jq -r '.title' "$f")
[[ "$pending" -eq 0 ]] && continue
local reason=""
if [[ "$cadence" == "weekly" && "$progress" -lt 100 ]]; then
reason="⚡ Weekly deadline — ${progress}% done"
elif [[ "$progress" -lt 20 ]]; then
reason="🔴 Barely started — needs attention"
elif [[ "$progress" -lt 50 ]]; then
reason="🟡 Under halfway — good opportunity"
fi
if [[ -n "$reason" ]]; then
echo -e " ${CYAN}$pid${NC}$title"
echo -e " $reason ($pending pending todos)"
echo ""
fi
done
}
# ── WRAP ──
cmd_wrap() {
local session
session=$(get_session)
if [[ -z "$session" ]]; then
echo -e "${RED}No active session to wrap${NC}"
exit 1
fi
echo -e "${PINK}[PLANNER]${NC} ${BOLD}Wrapping up session...${NC}"
# Gather session activity
local msgs=""
if [[ -f "$COLLAB_DB" ]]; then
msgs=$(sql "SELECT type, message FROM messages WHERE session_id='$session' ORDER BY created_at;" 2>/dev/null)
fi
# Auto-generate handoff
local handoff_msg="Session $session wrap-up:"
if [[ -n "$msgs" ]]; then
handoff_msg="$handoff_msg | Activity: $(echo "$msgs" | wc -l | tr -d ' ') messages"
local last_announce
last_announce=$(sql "SELECT message FROM messages WHERE session_id='$session' AND type='announce' ORDER BY created_at DESC LIMIT 1;" 2>/dev/null)
[[ -n "$last_announce" ]] && handoff_msg="$handoff_msg | Last: $last_announce"
fi
# Get current focus
local focus
focus=$(sql "SELECT focus FROM sessions WHERE session_id='$session';" 2>/dev/null)
[[ -n "$focus" ]] && handoff_msg="$handoff_msg | Focus was: $focus"
echo -e " ${BOLD}Handoff:${NC} $handoff_msg"
echo ""
# Create handoff
"$COLLAB" handoff "$handoff_msg" 2>/dev/null || true
# Mark session complete
"$COLLAB" complete 2>/dev/null || true
echo -e "${GREEN}Session wrapped up and handed off!${NC}"
}
# ── HELP ──
cmd_help() {
cat <<EOF
${PINK}╔════════════════════════════════════════════════════════════╗${NC}
${PINK}║${NC} ${BOLD}BlackRoad Planner${NC} ${PINK}║${NC}
${PINK}╚════════════════════════════════════════════════════════════╝${NC}
${BOLD}Planning:${NC}
${CYAN}plan <project-id>${NC} Show full project plan with todos
${CYAN}next${NC} What to work on next (auto-prioritized)
${CYAN}roadmap${NC} All projects grouped by cadence
${CYAN}suggest${NC} AI-suggested priorities
${BOLD}Session:${NC}
${CYAN}focus <project-id>${NC} Set session focus + announce
${CYAN}standup [--slack]${NC} Generate standup report
${CYAN}review${NC} Review this session's activity
${CYAN}wrap${NC} End session: auto-handoff + complete
EOF
}
case "${1:-help}" in
plan) shift; cmd_plan "$@" ;;
next) cmd_next ;;
focus) shift; cmd_focus "$@" ;;
standup) shift 2>/dev/null || true; cmd_standup "$@" ;;
roadmap|map) cmd_roadmap ;;
review|rev) cmd_review ;;
suggest|sug) cmd_suggest ;;
wrap|end) cmd_wrap ;;
help|--help|-h) cmd_help ;;
*)
echo -e "${RED}Unknown: $1${NC}"
cmd_help
exit 1
;;
esac

View File

@@ -0,0 +1,283 @@
#!/bin/bash
# BlackRoad Slack CLI — Direct agent communication bridge
# Usage: memory-slack.sh <command> [args]
set -e
SLACK_API="https://blackroad-slack.amundsonalexa.workers.dev"
SESSION_FILE="$HOME/.blackroad/memory/current-collab-session"
PINK='\033[38;5;205m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
RED='\033[0;31m'
BOLD='\033[1m'
NC='\033[0m'
get_session() {
if [[ -f "$SESSION_FILE" ]]; then
cat "$SESSION_FILE"
else
echo "unknown"
fi
}
# ── SAY ──
cmd_say() {
local msg="$*"
[[ -z "$msg" ]] && { echo -e "${RED}Usage: $0 say <message>${NC}"; exit 1; }
local session
session=$(get_session)
local text="[${session}] ${msg}"
curl -s --max-time 3 --connect-timeout 2 \
-X POST "$SLACK_API/post" \
-H "Content-Type: application/json" \
-d "$(jq -n --arg t "$text" '{text:$t}')" >/dev/null 2>&1 || true
echo -e "${GREEN}Sent to Slack:${NC} $msg"
}
# ── ASK ──
cmd_ask() {
local agent="$1"
shift || true
local msg="$*"
[[ -z "$agent" || -z "$msg" ]] && { echo -e "${RED}Usage: $0 ask <agent> <message>${NC}"; exit 1; }
echo -e "${BLUE}Asking ${CYAN}${agent}${BLUE}...${NC}"
local response
response=$(curl -s --max-time 15 --connect-timeout 3 \
-X POST "$SLACK_API/ask" \
-H "Content-Type: application/json" \
-d "$(jq -n --arg a "$agent" --arg m "$msg" '{agent:$a, message:$m, slack:true}')" 2>/dev/null)
if [[ -n "$response" ]]; then
local name reply
name=$(echo "$response" | jq -r '.agent // "?"')
reply=$(echo "$response" | jq -r '.reply // "no response"')
echo -e "${GREEN}${name}:${NC} ${reply}"
else
echo -e "${RED}No response — agent may be offline${NC}"
fi
}
# ── DEBATE ──
cmd_debate() {
local topic="$*"
[[ -z "$topic" ]] && { echo -e "${RED}Usage: $0 debate <topic>${NC}"; exit 1; }
echo -e "${BLUE}Starting debate: ${CYAN}${topic}${NC}"
echo -e "${BLUE}Agents: alice, cecilia, octavia, lucidia × 2 rounds${NC}"
echo ""
local response
response=$(curl -s --max-time 60 --connect-timeout 3 \
-X POST "$SLACK_API/group" \
-H "Content-Type: application/json" \
-d "$(jq -n --arg t "$topic" '{topic:$t, agents:["alice","cecilia","octavia","lucidia"], rounds:2}')" 2>/dev/null)
if [[ -n "$response" ]]; then
echo "$response" | jq -r '.transcript[]? | "\(.emoji) \(.agent) (round \(.round + 1)): \(.reply)"' 2>/dev/null
else
echo -e "${RED}No response — debate failed${NC}"
fi
}
# ── COUNCIL ──
cmd_council() {
local topic="$*"
[[ -z "$topic" ]] && { echo -e "${RED}Usage: $0 council <topic>${NC}"; exit 1; }
echo -e "${BLUE}Full council: ${CYAN}${topic}${NC}"
echo ""
local response
response=$(curl -s --max-time 60 --connect-timeout 3 \
-X POST "$SLACK_API/group" \
-H "Content-Type: application/json" \
-d "$(jq -n --arg t "$topic" '{topic:$t, agents:["alice","cecilia","octavia","lucidia","shellfish","caddy"], rounds:1}')" 2>/dev/null)
if [[ -n "$response" ]]; then
echo "$response" | jq -r '.transcript[]? | "\(.emoji) \(.agent): \(.reply)"' 2>/dev/null
else
echo -e "${RED}No response — council failed${NC}"
fi
}
# ── ALERT ──
cmd_alert() {
local msg="$*"
[[ -z "$msg" ]] && { echo -e "${RED}Usage: $0 alert <message>${NC}"; exit 1; }
curl -s --max-time 3 --connect-timeout 2 \
-X POST "$SLACK_API/alert" \
-H "Content-Type: application/json" \
-d "$(jq -n --arg t "$msg" '{text:$t}')" >/dev/null 2>&1 || true
echo -e "${RED}🚨 Alert sent:${NC} $msg"
}
# ── DEPLOY ──
cmd_deploy() {
local msg="$*"
[[ -z "$msg" ]] && { echo -e "${RED}Usage: $0 deploy <message>${NC}"; exit 1; }
curl -s --max-time 3 --connect-timeout 2 \
-X POST "$SLACK_API/deploy" \
-H "Content-Type: application/json" \
-d "$(jq -n --arg t "$msg" '{text:$t}')" >/dev/null 2>&1 || true
echo -e "${GREEN}🚀 Deploy notification sent:${NC} $msg"
}
# ── HEALTH ──
cmd_health() {
local response
response=$(curl -s --max-time 5 --connect-timeout 2 "$SLACK_API/health" 2>/dev/null)
if [[ -n "$response" ]]; then
local status agents
status=$(echo "$response" | jq -r '.status')
agents=$(echo "$response" | jq -r '.agents')
echo -e "${GREEN}Slack Worker:${NC} $status$agents agents"
else
echo -e "${RED}Slack Worker: unreachable${NC}"
fi
local full_status
full_status=$(curl -s --max-time 5 --connect-timeout 2 "$SLACK_API/status" 2>/dev/null)
if [[ -n "$full_status" ]]; then
local webhook bot
webhook=$(echo "$full_status" | jq -r '.webhook')
bot=$(echo "$full_status" | jq -r '.bot_token')
echo -e " Webhook: ${GREEN}$webhook${NC}"
echo -e " Bot token: ${YELLOW}$bot${NC}"
fi
}
# ── AGENTS ──
cmd_agents() {
local response
response=$(curl -s --max-time 5 --connect-timeout 2 "$SLACK_API/agents" 2>/dev/null)
echo -e "${PINK}╔════════════════════════════════════════════════════════════╗${NC}"
echo -e "${PINK}${NC} ${BOLD}BlackRoad Slack Agents${NC} ${PINK}${NC}"
echo -e "${PINK}╚════════════════════════════════════════════════════════════╝${NC}"
echo ""
if [[ -n "$response" ]]; then
echo "$response" | jq -r '.agents[]? | " \(.emoji) \(.name|@text) — \(.role)"' 2>/dev/null
else
echo -e " ${RED}Could not fetch agents${NC}"
fi
echo ""
}
# ── BROADCAST ──
cmd_broadcast() {
local msg="$*"
[[ -z "$msg" ]] && { echo -e "${RED}Usage: $0 broadcast <message>${NC}"; exit 1; }
local session
session=$(get_session)
# Post to Slack
curl -s --max-time 3 --connect-timeout 2 \
-X POST "$SLACK_API/collab" \
-H "Content-Type: application/json" \
-d "$(jq -n --arg t "announce" --arg m "$msg" --arg s "$session" \
'{type:$t, message:$m, session_id:$s}')" >/dev/null 2>&1 || true
# Also log to collab DB if available
if [[ -f "$HOME/.blackroad/collaboration.db" ]]; then
local msg_id="msg-$(date +%s)-$$"
local now
now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
sqlite3 "$HOME/.blackroad/collaboration.db" "INSERT INTO messages (msg_id, session_id, type, message, created_at) VALUES ('$msg_id', '$session', 'announce', '$(echo "$msg" | sed "s/'/''/g")', '$now');" 2>/dev/null || true
fi
echo -e "${GREEN}📢 Broadcast:${NC} $msg"
}
# ── THREAD ──
cmd_thread() {
local agent="$1"
shift || true
local initial="$*"
[[ -z "$agent" || -z "$initial" ]] && { echo -e "${RED}Usage: $0 thread <agent> <message>${NC}"; exit 1; }
echo -e "${PINK}╔════════════════════════════════════════════════════════════╗${NC}"
echo -e "${PINK}${NC} ${BOLD}Thread with ${agent}${NC} ${PINK}${NC}"
echo -e "${PINK}╚════════════════════════════════════════════════════════════╝${NC}"
echo ""
local context=""
local prompts=("$initial" "Can you elaborate on that?" "What should we do about it?")
for i in 0 1 2; do
local prompt="${prompts[$i]}"
if [[ "$i" -gt 0 ]]; then
prompt="Previous context: ${context}. Follow-up: ${prompts[$i]}"
fi
echo -e "${BLUE}You:${NC} ${prompts[$i]}"
local response
response=$(curl -s --max-time 15 --connect-timeout 3 \
-X POST "$SLACK_API/ask" \
-H "Content-Type: application/json" \
-d "$(jq -n --arg a "$agent" --arg m "$prompt" '{agent:$a, message:$m}')" 2>/dev/null)
if [[ -n "$response" ]]; then
local reply
reply=$(echo "$response" | jq -r '.reply // "..."')
echo -e "${GREEN}${agent}:${NC} ${reply}"
context="${context} ${reply}"
else
echo -e "${RED}(no response)${NC}"
fi
echo ""
done
}
# ── HELP ──
cmd_help() {
cat <<EOF
${PINK}╔════════════════════════════════════════════════════════════╗${NC}
${PINK}║${NC} ${BOLD}BlackRoad Slack CLI${NC} ${PINK}║${NC}
${PINK}╚════════════════════════════════════════════════════════════╝${NC}
${BOLD}Messaging:${NC}
${CYAN}say <message>${NC} Post to Slack with session prefix
${CYAN}broadcast <message>${NC} Broadcast via /collab + log to DB
${CYAN}alert <message>${NC} Send alert to Slack
${CYAN}deploy <message>${NC} Send deploy notification
${BOLD}Agents:${NC}
${CYAN}ask <agent> <msg>${NC} Ask an AI agent
${CYAN}debate <topic>${NC} 4-agent debate (2 rounds)
${CYAN}council <topic>${NC} 6-agent council (1 round)
${CYAN}thread <agent> <msg>${NC} 3-exchange conversation with agent
${CYAN}agents${NC} List all available agents
${BOLD}System:${NC}
${CYAN}health${NC} Check Slack Worker health
${BOLD}Agents:${NC} alice, cecilia, octavia, aria, lucidia, shellfish, caddy, alexa, road
EOF
}
case "${1:-help}" in
say|s) shift; cmd_say "$@" ;;
ask|a) shift; cmd_ask "$@" ;;
debate|d) shift; cmd_debate "$@" ;;
council|c) shift; cmd_council "$@" ;;
alert) shift; cmd_alert "$@" ;;
deploy) shift; cmd_deploy "$@" ;;
health|h) cmd_health ;;
agents|list) cmd_agents ;;
broadcast|bc) shift; cmd_broadcast "$@" ;;
thread|t) shift; cmd_thread "$@" ;;
help|--help|-h) cmd_help ;;
*)
echo -e "${RED}Unknown: $1${NC}"
cmd_help
exit 1
;;
esac

View File

@@ -0,0 +1,308 @@
#!/bin/bash
# BlackRoad Collaboration Watchdog — health monitoring, cleanup, digests
# Usage: memory-watchdog.sh <command> [args]
set -e
COLLAB_DB="$HOME/.blackroad/collaboration.db"
HANDOFF_DIR="$HOME/.blackroad/memory/handoffs"
JOURNAL="$HOME/.blackroad/memory/journals/master-journal.jsonl"
SLACK_API="https://blackroad-slack.amundsonalexa.workers.dev"
PINK='\033[38;5;205m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
RED='\033[0;31m'
BOLD='\033[1m'
NC='\033[0m'
sql() { sqlite3 "$COLLAB_DB" "$@" 2>/dev/null; }
post_to_slack() {
local text="$1"
curl -s --max-time 3 --connect-timeout 2 \
-X POST "$SLACK_API/post" \
-H "Content-Type: application/json" \
-d "$(jq -n --arg t "$text" '{text:$t}')" >/dev/null 2>&1 || true
}
check_db() {
if [[ ! -f "$COLLAB_DB" ]]; then
echo -e "${RED}Collaboration DB not found. Run: memory-collaboration.sh init${NC}"
exit 1
fi
}
# ── SWEEP — Clean stale data ──
cmd_sweep() {
check_db
echo -e "${PINK}[WATCHDOG]${NC} ${BOLD}Sweeping stale data...${NC}"
# Mark stale sessions as abandoned (>2h, still active)
local stale
stale=$(sql "SELECT count(*) FROM sessions WHERE status='active' AND last_seen < datetime('now', '-2 hours');")
if [[ "$stale" -gt 0 ]]; then
sql "UPDATE sessions SET status='abandoned' WHERE status='active' AND last_seen < datetime('now', '-2 hours');"
echo -e " ${YELLOW}Abandoned $stale stale session(s)${NC}"
else
echo -e " ${GREEN}No stale sessions${NC}"
fi
# Archive old messages (>7 days)
local old_msgs
old_msgs=$(sql "SELECT count(*) FROM messages WHERE created_at < datetime('now', '-7 days');")
if [[ "$old_msgs" -gt 0 ]]; then
sql "DELETE FROM messages WHERE created_at < datetime('now', '-7 days');"
echo -e " ${YELLOW}Archived $old_msgs old message(s)${NC}"
else
echo -e " ${GREEN}No old messages to archive${NC}"
fi
# Clean picked-up handoff files (>3 days)
local cleaned=0
for f in "$HANDOFF_DIR"/handoff-*.json; do
[[ ! -f "$f" ]] && continue
local pbu
pbu=$(jq -r '.picked_up_by // ""' "$f" 2>/dev/null)
if [[ -n "$pbu" && "$pbu" != "" && "$pbu" != "null" ]]; then
local created
created=$(jq -r '.created_at' "$f" 2>/dev/null)
local file_age
file_age=$(( $(date +%s) - $(date -jf "%Y-%m-%dT%H:%M:%SZ" "$created" +%s 2>/dev/null || echo $(date +%s)) ))
if [[ "$file_age" -gt 259200 ]]; then # 3 days
rm -f "$f"
((cleaned++))
fi
fi
done
[[ "$cleaned" -gt 0 ]] && echo -e " ${YELLOW}Cleaned $cleaned old handoff file(s)${NC}"
# Report orphaned handoffs
local orphaned
orphaned=$(sql "SELECT count(*) FROM handoffs WHERE picked_up_by='' AND created_at < datetime('now', '-24 hours');")
if [[ "$orphaned" -gt 0 ]]; then
echo -e " ${RED}$orphaned orphaned handoff(s) — never picked up (>24h)${NC}"
fi
# DB size
local db_size
db_size=$(du -h "$COLLAB_DB" | cut -f1)
echo -e " ${BLUE}DB size: $db_size${NC}"
# Total counts
local sessions msgs handoffs
sessions=$(sql "SELECT count(*) FROM sessions;")
msgs=$(sql "SELECT count(*) FROM messages;")
handoffs=$(sql "SELECT count(*) FROM handoffs;")
echo -e " ${BLUE}Totals: $sessions sessions, $msgs messages, $handoffs handoffs${NC}"
}
# ── HEARTBEAT — Quick health pulse ──
cmd_heartbeat() {
local slack_flag="${1:-}"
local issues=()
# Check DB
if [[ -f "$COLLAB_DB" ]]; then
local integrity
integrity=$(sql "PRAGMA integrity_check;" 2>/dev/null)
if [[ "$integrity" != "ok" ]]; then
issues+=("DB integrity: $integrity")
fi
else
issues+=("Collaboration DB missing")
fi
# Check Slack
local slack_status
slack_status=$(curl -s --max-time 2 --connect-timeout 1 "$SLACK_API/health" 2>/dev/null | jq -r '.status' 2>/dev/null || echo "unreachable")
if [[ "$slack_status" != "alive" ]]; then
issues+=("Slack Worker: $slack_status")
fi
# Check journal
if [[ -f "$JOURNAL" ]]; then
local last_entry
last_entry=$(tail -1 "$JOURNAL" 2>/dev/null)
echo "$last_entry" | jq . >/dev/null 2>&1 || issues+=("Journal: last entry not valid JSON")
else
issues+=("Journal file missing")
fi
# Check disk space
local disk_used
disk_used=$(du -sm "$HOME/.blackroad/" 2>/dev/null | cut -f1)
if [[ "${disk_used:-0}" -gt 500 ]]; then
issues+=("Disk: ~/.blackroad/ is ${disk_used}MB")
fi
# Active sessions
local active=0
if [[ -f "$COLLAB_DB" ]]; then
active=$(sql "SELECT count(*) FROM sessions WHERE status='active';" 2>/dev/null || echo 0)
fi
if [[ ${#issues[@]} -eq 0 ]]; then
echo -e "${GREEN}✅ All systems nominal${NC}$active active session(s), Slack connected, DB healthy"
if [[ "$slack_flag" == "--slack" ]]; then
post_to_slack "💚 Watchdog heartbeat: all systems nominal — $active active session(s)"
fi
else
echo -e "${RED}⚠️ Issues detected:${NC}"
for issue in "${issues[@]}"; do
echo -e " ${RED}$issue${NC}"
done
if [[ "$slack_flag" == "--slack" ]]; then
local msg="⚠️ Watchdog heartbeat: ${#issues[@]} issue(s)"
for issue in "${issues[@]}"; do
msg="$msg\n • $issue"
done
post_to_slack "$msg"
fi
fi
}
# ── DIGEST — Collaboration digest ──
cmd_digest() {
check_db
local slack_flag="${1:-}"
local sessions_24h msgs_24h handoffs_24h handoffs_picked
sessions_24h=$(sql "SELECT count(*) FROM sessions WHERE started_at > datetime('now', '-24 hours');")
msgs_24h=$(sql "SELECT count(*) FROM messages WHERE created_at > datetime('now', '-24 hours');")
handoffs_24h=$(sql "SELECT count(*) FROM handoffs WHERE created_at > datetime('now', '-24 hours');")
handoffs_picked=$(sql "SELECT count(*) FROM handoffs WHERE picked_up_at > datetime('now', '-24 hours') AND picked_up_by != '';")
echo -e "${PINK}╔════════════════════════════════════════════════════════════╗${NC}"
echo -e "${PINK}${NC} ${BOLD}Collaboration Digest (24h)${NC} ${PINK}${NC}"
echo -e "${PINK}╚════════════════════════════════════════════════════════════╝${NC}"
echo ""
echo -e " Sessions: ${GREEN}${sessions_24h}${NC}"
echo -e " Messages: ${BLUE}${msgs_24h}${NC}"
echo -e " Handoffs: ${CYAN}${handoffs_24h} created${NC}, ${GREEN}${handoffs_picked} picked up${NC}"
echo ""
# Recent sessions
echo -e " ${BOLD}Sessions:${NC}"
sql "SELECT session_id, status, focus FROM sessions WHERE started_at > datetime('now', '-24 hours') ORDER BY started_at DESC LIMIT 10;" | while IFS='|' read -r sid status focus; do
local icon="●"
case "$status" in
active) icon="${GREEN}${NC}" ;;
completed) icon="${BLUE}${NC}" ;;
abandoned) icon="${YELLOW}${NC}" ;;
esac
echo -e " $icon ${CYAN}${sid}${NC}${focus:+ — $focus}"
done
echo ""
# Top message types
echo -e " ${BOLD}Message Types:${NC}"
sql "SELECT type, count(*) as cnt FROM messages WHERE created_at > datetime('now', '-24 hours') GROUP BY type ORDER BY cnt DESC;" | while IFS='|' read -r type cnt; do
echo -e " ${type}: ${GREEN}${cnt}${NC}"
done
if [[ "$slack_flag" == "--slack" ]]; then
local msg="📊 *Collaboration Digest (24h)*\n"
msg="${msg}Sessions: ${sessions_24h} | Messages: ${msgs_24h} | Handoffs: ${handoffs_24h} created, ${handoffs_picked} picked up"
post_to_slack "$msg"
echo ""
echo -e " ${GREEN}Posted to Slack${NC}"
fi
}
# ── AUDIT — Full system audit ──
cmd_audit() {
check_db
echo -e "${PINK}╔════════════════════════════════════════════════════════════╗${NC}"
echo -e "${PINK}${NC} ${BOLD}Collaboration System Audit${NC} ${PINK}${NC}"
echo -e "${PINK}╚════════════════════════════════════════════════════════════╝${NC}"
echo ""
# DB integrity
local integrity
integrity=$(sql "PRAGMA integrity_check;")
echo -e " ${BOLD}DB Integrity:${NC} ${integrity}"
# All sessions
echo ""
echo -e " ${BOLD}All Sessions:${NC}"
sql "SELECT session_id, status, started_at, last_seen, focus FROM sessions ORDER BY started_at DESC;" | while IFS='|' read -r sid status started last focus; do
local icon
case "$status" in
active) icon="${GREEN}${NC}" ;;
completed) icon="${BLUE}${NC}" ;;
abandoned) icon="${YELLOW}${NC}" ;;
*) icon="?" ;;
esac
echo -e " $icon ${CYAN}${sid}${NC} [${status}] ${started:0:19}${focus:+ — $focus}"
done
# Orphaned handoffs
echo ""
local orphaned
orphaned=$(sql "SELECT count(*) FROM handoffs WHERE picked_up_by='' AND created_at < datetime('now', '-24 hours');")
echo -e " ${BOLD}Orphaned Handoffs (>24h, never picked up):${NC} ${orphaned}"
if [[ "$orphaned" -gt 0 ]]; then
sql "SELECT handoff_id, from_session, message FROM handoffs WHERE picked_up_by='' AND created_at < datetime('now', '-24 hours') LIMIT 5;" | while IFS='|' read -r hid from msg; do
echo -e " ${RED}${NC} ${hid} from ${CYAN}${from}${NC}: ${msg:0:60}"
done
fi
# Duplicate session check
echo ""
local dupes
dupes=$(sql "SELECT session_id, count(*) as cnt FROM sessions GROUP BY session_id HAVING cnt > 1;" 2>/dev/null)
if [[ -n "$dupes" ]]; then
echo -e " ${RED}Duplicate sessions found:${NC}"
echo "$dupes" | while IFS='|' read -r sid cnt; do
echo -e " ${RED}$sid: $cnt entries${NC}"
done
else
echo -e " ${GREEN}No duplicate sessions${NC}"
fi
# DB stats
echo ""
local db_size pages
db_size=$(du -h "$COLLAB_DB" | cut -f1)
pages=$(sql "PRAGMA page_count;")
local page_size
page_size=$(sql "PRAGMA page_size;")
echo -e " ${BOLD}DB Stats:${NC} $db_size, $pages pages × ${page_size}B"
echo -e " ${BOLD}Journal mode:${NC} $(sql 'PRAGMA journal_mode;')"
}
# ── HELP ──
cmd_help() {
cat <<EOF
${PINK}╔════════════════════════════════════════════════════════════╗${NC}
${PINK}║${NC} ${BOLD}BlackRoad Collaboration Watchdog${NC} ${PINK}║${NC}
${PINK}╚════════════════════════════════════════════════════════════╝${NC}
${BOLD}Commands:${NC}
${CYAN}heartbeat [--slack]${NC} Quick health check (post to Slack with --slack)
${CYAN}sweep${NC} Clean stale sessions, old messages, orphaned handoffs
${CYAN}digest [--slack]${NC} 24h collaboration digest (post to Slack with --slack)
${CYAN}audit${NC} Full system audit: integrity, sessions, orphans
${BOLD}Cron schedule:${NC}
*/30 heartbeat
3am sweep
9am digest --slack
EOF
}
case "${1:-help}" in
heartbeat|hb) shift 2>/dev/null || true; cmd_heartbeat "$@" ;;
sweep|clean) cmd_sweep ;;
digest|report) shift 2>/dev/null || true; cmd_digest "$@" ;;
audit) cmd_audit ;;
help|--help|-h) cmd_help ;;
*)
echo -e "${RED}Unknown: $1${NC}"
cmd_help
exit 1
;;
esac