Greebo - Full Stack Setup Guide
Claude Code - Full Stack Setup Guide
This documents the complete Claude Code setup running on a dedicated Linux dev server. It covers persistent memory with wiki-style maintenance, hooks, MCP servers, plugins, code intelligence, Telegram bot, AI feed monitoring, cron automation, and the multi-session tmux workflow.
The result: Claude as an always-on development partner with persistent memory across sessions, auto-formatting, code knowledge graphs, mobile access via Telegram, scheduled briefings, AI tool monitoring, and parallel session support.
Prerequisites
- Linux server (Ubuntu/Debian) with SSH access
- Node.js v24+ and npm
- PostgreSQL with pgvector extension (for Cortex memory)
- tmux
- jq
- Claude Code CLI installed (
npm install -g @anthropic-ai/claude-code) - Anthropic API key or Max subscription
1. Directory Structure
~/dev/ # Main workspace
~/.claude/ # Claude Code config root
~/.claude/settings.json # Global settings, hooks, plugins
~/.claude/hooks/ # Hook scripts
~/.claude/skills/ # Agent skills (auto-installed by plugins)
~/.claude/projects/ # Per-project memory directories
~/.claude/plugins/ # Installed plugins
~/dev/memory/ # Thinking loop, briefings, lint
~/dev/memory/hooks/ # Memory injection hooks
~/dev/memory/engine/ # Thinking loop + lint scripts
2. Global Settings (~/.claude/settings.json)
This is the core config. Paste this and adapt paths to your username.
{
"permissions": {
"allow": [
"Bash(node:*)",
"Bash(npm view:*)",
"WebSearch",
"Bash(cd:*)"
],
"defaultMode": "default"
},
"enableAllProjectMcpServers": true,
"hooks": {
"PostToolUse": [
{
"matcher": "Edit",
"hooks": [
{
"type": "command",
"command": "bash \"$HOME/.claude/hooks/auto-format.sh\"",
"timeout": 15
}
]
},
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "bash \"$HOME/.claude/hooks/auto-format.sh\"",
"timeout": 15
}
]
}
],
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "bash \"$HOME/dev/memory/hooks/pre_message.sh\""
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "bash \"$HOME/.claude/hooks/extract-memories.sh\"",
"timeout": 5
},
{
"type": "command",
"command": "bash \"$HOME/.claude/hooks/push-notify.sh\"",
"timeout": 3
}
]
}
]
},
"worktree": {
"symlinkDirectories": [
"node_modules",
".next",
".turbo"
]
},
"statusLine": {
"type": "command",
"command": "/home/YOURUSER/.claude/scripts/statusline.sh"
},
"enabledPlugins": {
"superpowers@claude-plugins-official": true,
"context7@claude-plugins-official": true,
"playwright@claude-plugins-official": true,
"figma@claude-plugins-official": true,
"telegram@claude-plugins-official": true,
"hookify@claude-plugins-official": true,
"commit-commands@claude-plugins-official": true,
"pr-review-toolkit@claude-plugins-official": true,
"code-review@claude-plugins-official": true,
"claude-md-management@claude-plugins-official": true,
"cortex@cortex-plugins": true,
"explanatory-output-style@claude-plugins-official": true
},
"extraKnownMarketplaces": {
"cortex-plugins": {
"source": {
"source": "github",
"repo": "cdeust/Cortex"
}
}
},
"effortLevel": "high"
}
What this gives you
- PostToolUse hooks: Auto-runs eslint --fix after every Edit/Write on TS/JS files
- UserPromptSubmit hook: Injects your MEMORY.md index into every message so Claude always has context
- Stop hooks: Auto-extracts memories after conversations + desktop notification
- Status line: Shows directory, git branch, session name, model, context % remaining, and cumulative session token cost (see Section 3)
- Worktree symlinks: When using git worktrees, symlinks node_modules/.next/.turbo instead of duplicating
- Plugins: Superpowers (TDD, debugging, brainstorming skills), Cortex (persistent vector memory), Context7 (live docs), Playwright (browser testing), and more
3. Status Line with Token Tracking
The built-in /usage command shows your current context window, but that's the size of the current snapshot - not what your session has actually cost. Every turn re-reads the full cached conversation, so a 500-turn session at 80k context has sent ~40M cache-read tokens to the API even though /usage will only ever show 80k.
This script parses the session's transcript JSONL and shows the real cumulative cost in the status line, split by raw vs API-weighted effective tokens. When you see effective tokens climbing past 20M in a single session, that's your cue to /exit and start fresh.
Why cache reads matter on MAX plans
The Anthropic API bills cache reads at ~10% of the normal input rate, so they feel cheap per token. But on MAX 20x plans those tokens still count toward weekly budgets. A long-lived session with 500+ turns can burn through hundreds of millions of cache reads invisibly - the kind of "stealth burner" that makes people hit limits unexpectedly.
The script
Save as ~/.claude/scripts/statusline.sh and chmod +x:
#!/usr/bin/env bash
# Claude Code statusline with cumulative session token usage.
# Input: JSON on stdin with session_id, transcript_path, cwd, model, workspace, session_name, context_window.
set -euo pipefail
input=$(cat)
cwd=$(echo "$input" | jq -r '.workspace.current_dir // .cwd // empty')
model=$(echo "$input" | jq -r '.model.display_name // empty')
session_name=$(echo "$input" | jq -r '.session_name // empty')
remaining=$(echo "$input" | jq -r '.context_window.remaining_percentage // empty')
transcript=$(echo "$input" | jq -r '.transcript_path // empty')
dir_name=$(basename "$cwd")
# Git branch (if any)
git_branch=""
if [ -d "$cwd/.git" ] || git -C "$cwd" rev-parse --git-dir > /dev/null 2>&1; then
git_branch=$(git -C "$cwd" --no-optional-locks branch --show-current 2>/dev/null)
[ -n "$git_branch" ] && git_branch=" ($git_branch)"
fi
# Session token totals from the transcript JSONL
tokens=""
if [ -n "$transcript" ] && [ -f "$transcript" ]; then
tokens=$(python3 - "$transcript" <<'PY'
import json, sys
path = sys.argv[1]
fin = fcw = fcr = fout = 0
try:
with open(path) as f:
for line in f:
try:
obj = json.loads(line)
except Exception:
continue
if obj.get("type") == "assistant":
u = obj.get("message", {}).get("usage", {}) or {}
fin += u.get("input_tokens", 0)
fcw += u.get("cache_creation_input_tokens", 0)
fcr += u.get("cache_read_input_tokens", 0)
fout += u.get("output_tokens", 0)
except Exception:
sys.exit(0)
raw = fin + fcw + fcr + fout
# Effective cost weights: input 1x, cache_write 1.25x, cache_read 0.1x, output 5x
eff = fin * 1.0 + fcw * 1.25 + fcr * 0.10 + fout * 5.0
def fmt(n):
if n >= 1_000_000_000:
return f"{n/1_000_000_000:.1f}B"
if n >= 1_000_000:
return f"{n/1_000_000:.1f}M"
if n >= 1_000:
return f"{n/1_000:.0f}k"
return str(int(n))
print(f"{fmt(raw)} raw / {fmt(eff)} eff")
PY
)
fi
# Assemble output
status="$dir_name$git_branch"
[ -n "$session_name" ] && status="$status | $session_name"
status="$status | $model"
[ -n "$remaining" ] && status="$status | Ctx: ${remaining}%"
[ -n "$tokens" ] && status="$status | $tokens"
echo "$status"
What you see
dev (main) | personal | Opus 4.6 | Ctx: 45% | 33.6M raw / 5.9M eff
- raw: every input, cache write, cache read, and output token counted 1x. The "what was sent across the wire" number.
- eff: API-weighted effective cost using Anthropic's billing rates - input 1x, cache write 1.25x, cache read 0.1x, output 5x. The number that matters for your weekly budget.
The gap between raw and effective tells you how much you're relying on cache. A large gap (raw much higher than effective) means cache is working well. A small gap means you're generating lots of fresh content.
Reading the numbers
| Effective tokens | What it means |
|---|---|
| < 5M | Fresh session, business as usual |
| 5-15M | Normal active session, still healthy |
| 15-30M | Getting heavy - consider wrapping up and restarting |
| > 30M | One session eating 10%+ of your weekly budget - /exit and start new |
Restart Claude Code (or start a new session) for the statusline change to take effect - the existing session caches its config.
4. Hook Scripts
Heads-up: The two memory hooks below (
pre_message.shandextract-memories.sh) were the original custom memory chain. They still work and the files remain on disk, but they have been superseded by the Cortex plugin's lifecycle hooks (see section 9). New installs should rely on Cortex for memory and only keep the auto-format and push-notify hooks as custom. The legacy memory hooks are documented here because they are still useful as a fallback if Postgres is down or you do not want to run Cortex.
Auto-format (PostToolUse)
~/.claude/hooks/auto-format.sh - runs eslint --fix on every file edit:
#!/usr/bin/env bash
input=$(cat)
tool_name=$(echo "$input" | jq -r '.tool_name // empty')
file_path=$(echo "$input" | jq -r '.tool_input.file_path // empty')
if [[ "$tool_name" != "Edit" && "$tool_name" != "Write" ]]; then
exit 0
fi
if [[ ! "$file_path" =~ \.(ts|tsx|js|jsx|mjs)$ ]]; then
exit 0
fi
project_dir=$(dirname "$file_path")
while [[ "$project_dir" != "/" ]]; do
if [[ -f "$project_dir/eslint.config.mjs" || -f "$project_dir/.eslintrc.json" || -f "$project_dir/.eslintrc.js" ]]; then
cd "$project_dir"
npx eslint --fix "$file_path" 2>/dev/null
exit 0
fi
project_dir=$(dirname "$project_dir")
done
Memory Injection (UserPromptSubmit)
~/dev/memory/hooks/pre_message.sh - injects the MEMORY.md index into every prompt:
#!/usr/bin/env bash
# The projects directory is auto-named by Claude Code based on your working directory.
# If you work from ~/dev/, it becomes: -home-YOURUSERNAME-dev
# Check ~/.claude/projects/ to find yours.
MEMORY_DIR="$HOME/.claude/projects/-home-$(whoami)-dev/memory"
MEMORY_FILE="$MEMORY_DIR/MEMORY.md"
if [ -f "$MEMORY_FILE" ]; then
echo "<memory-context>"
echo ""
echo "## Recent context"
echo ""
echo "### MEMORY ($MEMORY_FILE)"
cat "$MEMORY_FILE"
echo "</memory-context>"
fi
Memory Extraction (Stop)
~/.claude/hooks/extract-memories.sh - spawns a background agent to save durable memories after each conversation:
#!/usr/bin/env bash
set -euo pipefail
MEMORY_DIR="$HOME/.claude/projects/-home-$(whoami)-dev/memory"
[ -d "$MEMORY_DIR" ] || exit 0
# Throttle: only run every 3rd stop to avoid excessive API calls
COUNTER_FILE="/tmp/claude-extract-memories-counter"
COUNT=$(cat "$COUNTER_FILE" 2>/dev/null || echo "0")
COUNT=$((COUNT + 1))
echo "$COUNT" > "$COUNTER_FILE"
[ $((COUNT % 3)) -eq 0 ] || exit 0
LOCK_FILE="/tmp/claude-extract-memories.lock"
if [ -f "$LOCK_FILE" ]; then
if [ "$(find "$LOCK_FILE" -mmin +5 2>/dev/null)" ]; then
rm -f "$LOCK_FILE"
else
exit 0
fi
fi
touch "$LOCK_FILE"
MEMORY_MANIFEST=$(ls -1 "$MEMORY_DIR"/*.md 2>/dev/null | while read f; do
basename "$f"
done | tr '\n' ', ')
(
claude -p --print "You are the memory extraction subagent. Analyze the conversation that just ended and update persistent memory if warranted.
Available memory files: ${MEMORY_MANIFEST:-none}
Memory directory: $MEMORY_DIR
Only save information useful in FUTURE conversations:
- User preferences and corrections (feedback type)
- Project status changes (project type)
- New external references (reference type)
- User role/knowledge updates (user type)
Tag each memory with confidence: high (directly stated/verified), medium (inferred), low (single observation).
Save both corrections AND confirmed approaches. If you only save corrections, you'll avoid past mistakes but drift away from validated patterns.
Do NOT save code patterns, git history, debugging solutions, or ephemeral task details.
Use YAML frontmatter format (name, description, type, confidence fields).
Update MEMORY.md index if you create/modify files.
Check existing files before creating duplicates." \
--allowedTools "Read,Edit,Write,Glob,Grep" \
--max-turns 5 \
2>/dev/null || true
rm -f "$LOCK_FILE"
) &
exit 0
Push Notifications (Stop)
~/.claude/hooks/push-notify.sh - desktop notification when Claude finishes:
#!/usr/bin/env bash
set -euo pipefail
HOOK_INPUT=$(cat)
STOP_REASON=$(echo "$HOOK_INPUT" | python3 -c "
import sys, json
try:
data = json.load(sys.stdin)
print(data.get('stop_reason', 'unknown'))
except:
print('unknown')
" 2>/dev/null || echo "unknown")
case "$STOP_REASON" in
end_turn|tool_use) ;;
*) exit 0 ;;
esac
if command -v notify-send &>/dev/null; then
notify-send -i terminal "Claude Code" "Task completed" -t 3000 2>/dev/null || true
fi
# Optional: mobile push via ntfy.sh
# NTFY_TOPIC="your-topic"
# curl -s -d "Claude Code task completed" "https://ntfy.sh/$NTFY_TOPIC" &>/dev/null || true
exit 0
Make all hooks executable: chmod +x ~/.claude/hooks/*.sh ~/dev/memory/hooks/*.sh
5. Persistent Memory System (Three Layers)
The memory system grew up. What started as a flat directory of markdown files indexed by FTS5 is now three different stores doing three different jobs, with the markdown layer kept around as a fallback. Each layer answers a question the others cannot.
| Layer | Store | Question it answers |
|---|---|---|
| 1. Cortex | Postgres + pgvector + graph schema | "What did we decide and why" |
| 2. GitNexus | LadybugDB code knowledge graph | "What calls what" |
| 3. Memory Freshness | Bun TypeScript CLI + 4-hour cron | "Is any of this still true" |
Cortex is layer 1. It is the canonical store, not the optional add-on it was when this guide was first written. Setup is in section 9. The migration from the old markdown-only system happened on 2026-03-31 and moved 152 markdown memories into 281 entities and 1,482 relationships. Vector search is enabled. Cortex's plugin lifecycle hooks (SessionStart, SessionEnd, PostToolUse, compaction) replaced the old custom hook chain that section 4 documents.
GitNexus is layer 2. Tree-sitter parses repos into ASTs, LadybugDB stores nodes and edges, and PreToolUse hooks intercept Grep/Glob to add call-graph context. Setup is in section 8.
Memory Freshness is layer 3. A Bun TypeScript CLI that runs every four hours, scores every memory using an Ebbinghaus decay curve, and corroborates each one against external signals (git activity, file existence, systemd state). When something rots, it pings Telegram. Setup is in section 9.5.
Why Three
Cortex does not know about call graphs. Vector search over flat text is excellent at "what did we say about this thing", and the graph schema makes it excellent at "what is connected to what we said". It is not excellent at "what calls this function in CoachSync", because it does not have a parser. GitNexus does not know about decisions - it can tell you the entire call structure of a repository, but it has no idea why any particular function exists. The freshness daemon does not know either of those things, and it is not trying to. It is the smoke detector. It does not put out the fire and it does not investigate the cause. It just makes a noise when something needs looking at.
Composable, not redundant. Each layer fills a gap the others would have to be turned into something they are not in order to fill.
The Markdown Archive (Layer 0)
The original markdown system has not been deleted. It is preserved at the project memory directories (~/.claude/projects/.../memory/) and the FTS5 engine is archived at ~/dev/memory/engine/. The format is unchanged: YAML frontmatter, MEMORY.md index file, the same wiki rules. Two things still operate on this layer:
- The lint script (section 6) runs daily over the markdown files, checking for broken links, orphans, and stale dates.
- The dream consolidation script (section 7) runs nightly, reviewing and reorganising the markdown memories.
The archive is not load-bearing in the new world. Cortex is what new sessions inherit context from. The markdown directory is the fallback you can fall back to if Postgres is down, and it is also what the lint and dream scripts maintain so the structural patterns stay coherent.
Memory File Format (Markdown Archive)
Each file in the markdown archive uses YAML frontmatter:
---
name: Short descriptive name
description: One-line description used for relevance matching
type: user|feedback|project|reference
confidence: high|medium|low
verified: 2026-04-08
---
Content here. For feedback/project types, structure as:
Rule or fact.
**Why:** The reason behind it.
**How to apply:** When and where this guidance applies.
MEMORY.md Index (Markdown Archive)
The MEMORY.md file is the index for the markdown archive. Cortex does not need it, but pre_message.sh still injects it for legacy reasons and it remains useful when the archive is the source of truth (offline, fallback). Keep it under 200 lines:
# Memory
## Environment
- [tmux workflow](user_tmux_workflow.md) - cs alias for roaming sessions
## Project
- [MyApp status](project_myapp_status.md) - current state and priorities
## Feedback
- [Testing preference](feedback_testing.md) - always use integration tests, not mocks
- [API pattern worked](feedback_api_pattern.md) - batch endpoint approach validated, keep using it
Memory Wiki Rules (Karpathy LLM Wiki Pattern)
These rules apply to BOTH layers - Cortex memories and markdown archive memories. They live in the global CLAUDE.md so Claude follows them no matter which store the memory ends up in:
Ingest ripple - When creating or updating a memory, identify 3-5 related memories and check if they need updating too. Adding "project X retired" should also update any memory that still references X as active.
Query-as-page - When you do significant research or synthesis (evaluating tools, comparing approaches), save the non-obvious findings as a reference memory. The answer goes to the user; the durable insight goes to memory.
Confidence levels - Tag memories with high/medium/low confidence. High-confidence memories survive consolidation; low ones get pruned faster.
Save successes too - Feedback memories should record both what went wrong AND what worked well. If you only save corrections, you drift away from validated patterns.
How It Works (Three-Layer Flow)
- Session start: Cortex's SessionStart hook pulls the relevant entities and relationships into the opening context. The session begins already knowing the architecture for whatever it is about to work on.
- Tool use: Cortex's PostToolUse hook captures observations as they happen. GitNexus's PreToolUse hook augments Grep/Glob with call-graph context - the session does not have to know GitNexus exists, the answers just come back better.
- Compaction: Cortex's compaction checkpoint fires when the context is about to be compressed, so anything important on the way out gets saved before it is summarised into oblivion.
- Session end: Cortex's SessionEnd hook captures whatever was learned. The next session inherits it for free.
- Every 4 hours: Memory Freshness scans every memory, scores it for staleness against external signals, and pings Telegram if anything has rotted.
- Daily 5 AM: Lint runs over the markdown archive (broken links, orphans, stale dates).
- Nightly 3 AM: Dream consolidation reviews the markdown archive and reorganises it.
6. Memory Lint (Daily Health Check)
Layer: This script operates on the markdown archive (see section 5). Cortex memories have their own validation inside the plugin. Lint is still useful because the archive is preserved as a fallback and lint catches structural issues that would corrupt the archive's value as a fallback.
~/dev/memory/engine/lint-memories.sh - structural health check, no LLM needed:
#!/usr/bin/env bash
# Memory Lint - checks for broken links, orphans, stale dates, empty files
# Install: 0 5 * * * ~/dev/memory/engine/lint-memories.sh >> /tmp/memory-lint.log 2>&1
set -euo pipefail
MEMORY_DIR="$HOME/.claude/projects/-home-$(whoami)-dev/memory"
INDEX="$MEMORY_DIR/MEMORY.md"
REPORT="$HOME/dev/memory/lint-report.md"
NOW_EPOCH=$(date +%s)
STALE_DAYS=14
errors=0
warnings=0
broken_links=()
orphan_files=()
stale_memories=()
empty_files=()
# Check 1: MEMORY.md links resolve to actual files
if [ -f "$INDEX" ]; then
while IFS= read -r link; do
if [ ! -f "$MEMORY_DIR/$link" ]; then
broken_links+=("$link")
((errors++)) || true
fi
done < <(grep -oP '\]\(\K[^)]+\.md' "$INDEX" 2>/dev/null || true)
fi
# Check 2: All .md files are indexed in MEMORY.md
while IFS= read -r filepath; do
filename=$(basename "$filepath")
[ "$filename" = "MEMORY.md" ] && continue
if ! grep -qF "$filename" "$INDEX" 2>/dev/null; then
orphan_files+=("$filename")
((warnings++)) || true
fi
done < <(find "$MEMORY_DIR" -maxdepth 2 -name "*.md" -not -name "MEMORY.md" 2>/dev/null)
# Check 3: Stale verified dates (older than 14 days)
while IFS= read -r filepath; do
filename=$(basename "$filepath")
[ "$filename" = "MEMORY.md" ] && continue
verified=$(sed -n '/^---$/,/^---$/{ /^verified:/{ s/^verified: *//; p; } }' "$filepath" 2>/dev/null | head -1)
if [ -n "$verified" ]; then
verified_epoch=$(date -d "$verified" +%s 2>/dev/null || echo "0")
if [ "$verified_epoch" != "0" ]; then
days_old=$(( (NOW_EPOCH - verified_epoch) / 86400 ))
if [ "$days_old" -gt "$STALE_DAYS" ]; then
stale_memories+=("$filename (${days_old}d old)")
((warnings++)) || true
fi
fi
fi
done < <(find "$MEMORY_DIR" -maxdepth 2 -name "*.md" -not -name "MEMORY.md" 2>/dev/null)
# Check 4: Empty or near-empty files
while IFS= read -r filepath; do
filename=$(basename "$filepath")
[ "$filename" = "MEMORY.md" ] && continue
content_lines=$(sed '/^---$/,/^---$/d' "$filepath" 2>/dev/null | grep -c '[^ ]' 2>/dev/null || echo "0")
if [ "$content_lines" -lt 2 ]; then
empty_files+=("$filename (${content_lines} content lines)")
((warnings++)) || true
fi
done < <(find "$MEMORY_DIR" -maxdepth 2 -name "*.md" -not -name "MEMORY.md" 2>/dev/null)
# Generate report
{
echo "# Memory Lint Report"
echo "Generated: $(date '+%Y-%m-%d %H:%M')"
echo "Errors: $errors | Warnings: $warnings"
echo ""
if [ "$errors" -eq 0 ] && [ "$warnings" -eq 0 ]; then
echo "All clear."
fi
[ ${#broken_links[@]} -gt 0 ] && { echo "## Broken Links"; for l in "${broken_links[@]}"; do echo "- \`$l\`"; done; echo ""; }
[ ${#orphan_files[@]} -gt 0 ] && { echo "## Orphans"; for f in "${orphan_files[@]}"; do echo "- \`$f\`"; done; echo ""; }
[ ${#stale_memories[@]} -gt 0 ] && { echo "## Stale (>${STALE_DAYS}d)"; for m in "${stale_memories[@]}"; do echo "- \`$m\`"; done; echo ""; }
[ ${#empty_files[@]} -gt 0 ] && { echo "## Empty"; for f in "${empty_files[@]}"; do echo "- \`$f\`"; done; echo ""; }
} > "$REPORT"
echo "[$(date)] Lint: $errors errors, $warnings warnings"
[ "$errors" -eq 0 ]
7. Dream Consolidation (Nightly Memory Synthesis)
Layer: Like lint (section 6), this operates on the markdown archive. It is still useful because the archive remains a fallback and dream consolidation keeps it coherent. Cortex has its own consolidation built in.
A nightly cron job spawns a Claude agent that reviews all memories, fixes contradictions, merges duplicates, and prunes stale entries. Inspired by Karpathy's LLM Wiki lint operation.
The script gates on two conditions: 24+ hours since last run AND 5+ sessions since last consolidation. This prevents wasted API calls.
Key phases:
- Orient - Read all memory files (parallel reads first, then writes)
- Triage stale - Check claims against current codebase state
- Gather signal - Look for drift in recent git activity
- Lint - Fix contradictions, missing cross-references, orphans, near-duplicates
- Consolidate - Apply ingest ripple (update related memories when one changes)
- Prune - Keep MEMORY.md under 200 lines, one-line entries
Install via cron:
0 3 * * * ~/dev/scripts/dream-consolidation.sh >> /tmp/claude-dream.log 2>&1
The full script is ~150 lines. The core is a claude -p call with a structured prompt covering all six phases, run with --allowedTools "Read,Edit,Write,Glob,Grep,Bash" --dangerously-skip-permissions.
8. GitNexus (Code Intelligence)
GitNexus indexes your codebase into a knowledge graph - every dependency, call chain, cluster, and execution flow. It exposes 16 MCP tools so Claude can query code structure instead of spending tokens on Glob/Grep exploration.
Install
npm install -g gitnexus
gitnexus setup # Registers MCP server + hooks + skills for Claude Code
Index Your Repos
cd ~/dev/myapp && gitnexus analyze
# Output: 4,122 nodes | 8,597 edges | 147 clusters | 217 flows
What It Gives You
- 16 MCP tools: query (hybrid search), context (360-degree symbol view), impact (blast radius), detect_changes (git diff mapping), cypher (raw graph queries), route_map, and more
- PreToolUse hooks: When Claude uses Grep to search for
validateUser, GitNexus intercepts and adds "also called by handleLogin, handleRegister; part of LoginFlow process" as context. Sub-500ms. - PostToolUse hooks: Detects stale index after git commits and notifies
- 7 Claude Code skills: Exploring, Debugging, Impact Analysis, Refactoring, PR Review, Guide, CLI
Storage
Data lives in .gitnexus/lbug/ inside each repo (gitignored). Uses LadybugDB (embedded graph database), not your existing Postgres. Expect 50-200MB per medium repo.
Re-indexing
No incremental indexing yet - re-index after major refactors:
cd ~/dev/myapp && gitnexus analyze --force
License Note
GitNexus uses PolyForm Noncommercial 1.0.0. Free for personal, research, hobby, education, government use. Commercial use requires a paid license from akonlabs.com.
9. Cortex Plugin (Layer 1, Canonical Memory Store)
Cortex is the canonical memory store. v3.0.0 by Clement Deust, MIT-licensed, available at github.com/cdeust/Cortex. It is a Postgres plugin with pgvector and a knowledge graph schema, exposes 35 MCP tools, and provides Claude Code lifecycle hooks that took over the work the original custom hook chain (section 4) was doing.
Install
- Requires PostgreSQL with pgvector extension:
sudo apt install postgresql postgresql-contrib
sudo -u postgres createdb cortex
sudo -u postgres psql -c "CREATE EXTENSION IF NOT EXISTS vector;" cortex
- Add the cortex marketplace to your settings.json under
extraKnownMarketplaces:
"extraKnownMarketplaces": [
"https://github.com/cdeust/Cortex"
]
-
Install via
/install cortex@cortex-pluginsin a Claude session. -
Patch the plugin's
DATABASE_URLto point at your local Postgres:
# In ~/.claude/plugins/marketplaces/cortex-plugins/.claude-plugin/plugin.json
"DATABASE_URL": "postgresql://YOUR_USERNAME@localhost:5432/cortex"
Lifecycle Hooks
The plugin registers four hooks that supersede the old custom memory chain (pre_message.sh, extract-memories.sh, etc):
| Hook | When | What it does |
|---|---|---|
| SessionStart | New session opens | Pulls relevant Cortex entities and relationships into the opening context |
| PostToolUse | Every tool call | Captures new observations as they happen |
| Notification:compaction | Context about to be compressed | Saves important things before they are summarised into oblivion |
| SessionEnd | Session closes | Captures whatever was learned on the way out |
The custom scripts in section 4 still work, but the memory-related ones (pre_message.sh, extract-memories.sh) are now redundant. They can be left in place as a fallback or removed.
What It Provides
- Vector search: pgvector embeddings for semantic similarity
- Knowledge graph: structured entities and relationships, walked at retrieval time
- 35 MCP tools:
remember,recall,anchor,drill_down,navigate_memory,get_causal_chain,narrative,memory_stats, and more - Cognitive profiling: tracks reasoning signatures across sessions
- Heat/decay: thermodynamic memory model where frequently-accessed memories stay hot, unused ones cool down
- Codebase intelligence: integration with code analysis and the wider toolchain
Migration Notes
The migration from the markdown system happened on 2026-03-31 and moved 152 memories into 281 entities and 1,482 relationships. The old markdown directory is preserved at ~/.claude/projects/.../memory/ and operates as a fallback layer (see section 5). Lint and dream consolidation still run over the archive and keep it coherent.
A working example. A session starts on the BayOS codebase. Before the prompt has even rendered, the SessionStart hook has pulled the relevant Cortex entities for "BayOS / queue worker / February redesign" and dropped them into the opening context. The session begins already knowing that the queue worker was changed to use a different backoff strategy in February, that the change was made because a particular customer was hitting a particular failure mode, and that there is a regression test that pins the new behaviour. None of that has to be re-explained.
9.5. Memory Freshness CLI (Layer 3)
Memory rots. A note about a service that lives at one address last month becomes a lie when the service moves. A reference to a config file that has since been split into three files becomes a lie. None of those lies announce themselves. They just sit in the store, waiting to be retrieved by an unsuspecting future session that has no way of knowing the underlying world has moved on.
The freshness layer is what tells us when a memory is lying. It is a Bun TypeScript CLI that lives at ~/dev/memory-freshness/, runs on a 4-hour cron, and is exposed system-wide as mf so you can run it on demand from anywhere on the box.
Install
git clone <your fork or the source repo> ~/dev/memory-freshness
cd ~/dev/memory-freshness
bun install
chmod +x scan.sh mf.sh
sudo ln -s $(pwd)/mf.sh /usr/local/bin/mf
The CLI uses gray-matter to parse memory frontmatter and queries Cortex for the live memory corpus. It also reads the markdown archive layer to compare counts.
How It Scores
Every memory in the corpus gets a staleness score using an Ebbinghaus decay curve. The half-life is calibrated to the memory type - user preferences decay much slower than project status notes. The decay is not the only signal. The freshness daemon also corroborates each memory against:
- Recent git activity in the repository the memory is about
- Whether the file the memory references still exists at the path it claims
- Whether the systemd services it talks about are still defined and running
Two or more fresh signals override the decay-based staleness flag. One signal is not enough.
Output
When a project memory crosses seven days of compounded staleness with no corroboration, the freshness daemon pings @cat_devbot on Telegram with a one-line summary. The morning briefing also includes a freshness section, so even on quiet days the stale list is visible without anyone having to go and look for it.
The point is not to delete memories. Deleting is easy and rarely the right answer. The point is to surface memories that are lying, so the next session can fix the lie instead of inheriting it.
Commands
mf # Scan and report (default)
mf --update # Scan and update Cortex with new staleness scores
mf --dry-run # Show what would be flagged without making changes
mf --briefing # Output briefing-friendly format (used by cron)
mf --no-telegram # Scan without sending Telegram notifications
Cron
0 */4 * * * ~/dev/memory-freshness/scan.sh >> /tmp/memory-freshness.log 2>&1
scan.sh is the cron entry point - it runs bun run scan --briefing in the project directory. The full cron config including this line is in section 13.
The Pact
The freshness CLI shipped on 2026-03-31. It carries 83 tests, was built across four GSD phases against 25 written requirements, and was designed to do exactly one thing well. There are deliberately no escape hatches that try to be cleverer than the human about what to keep and what to throw away. The tool's job is to point. The decision to act stays where it belongs.
10. CLAUDE.md (Global Rules)
Place at ~/dev/CLAUDE.md (your working directory root). This is loaded into every session automatically.
# Workspace - Global Rules
## Verification Criteria
Every implementation task must include explicit pass/fail verification criteria before starting work.
1. State what "done" looks like - specific, testable conditions
2. Include at least one negative case (what should NOT happen)
3. After implementation, verify each criterion before marking complete
## Memory Wiki Rules
When creating or updating a memory file, apply these patterns:
### Ingest ripple
Don't just create/update one file in isolation. After writing or modifying a memory:
1. Identify 3-5 existing memories most related to the change
2. Check if they need updating too (stale references, new cross-links, contradicted claims)
3. Update them in the same pass
### Query-as-page
When you do significant research or synthesis, save the non-obvious findings as a
reference memory. The raw answer goes to the user; the durable insight goes to memory.
### Confidence levels
Tag memories with confidence: high (directly stated/verified), medium (inferred),
low (single observation). Low-confidence memories get pruned faster.
### Save successes too
Feedback memories should record both corrections AND confirmed approaches.
If you only save corrections, you drift away from validated patterns.
### Lint awareness
A structural lint runs daily at 5 AM. Report at ~/dev/memory/lint-report.md.
Fix orphans, broken links, and stale dates inline when spotted.
## Never Delegate Understanding
When spawning subagents, never write "based on your findings, fix it." Every
delegation must prove you understood: include file paths, line numbers, what
specifically to change.
## Two-Agent Review
After completing a feature branch or significant implementation:
1. Use a worktree agent (isolation: "worktree") as a second reviewer
2. The reviewer acts as a staff engineer - checks correctness, edge cases, test coverage
3. Reviewer should run tests and check the diff, not just read code
## Workflow Selection
- Multi-step features: use /gsd:plan-phase then /gsd:execute-phase
- Quick tasks: just do the work directly
- Debugging: use superpowers:systematic-debugging
- Brainstorming: use superpowers:brainstorming before implementation
- Code review: use superpowers:requesting-code-review
11. tmux Multi-Session Workflow
The cs Function
Add to ~/.bashrc:
cs() {
local name="${1:?Usage: cs <session-name> [project]}"
local project="${2:-}"
if tmux has-session -t "$name" 2>/dev/null; then
tmux attach -d -t "$name"
else
if [ -n "$project" ] && [ -d "$HOME/dev/$project/.git" ]; then
# Worktree mode: isolated git checkout for parallel feature work
tmux new -s "$name" -c "$HOME/dev/$project" \
"claude --dangerously-skip-permissions -n $name -w $name"
else
# Standard mode
tmux new -s "$name" "claude --dangerously-skip-permissions -n $name"
fi
fi
}
alias css='tmux list-sessions 2>/dev/null || echo "No sessions"'
Usage
cs myproject # Create/attach to session "myproject"
cs feature-x myapp # Create session with git worktree isolation for myapp
css # List all active sessions
The -d flag on attach auto-detaches other clients, so you can seamlessly switch between desktop and mobile (iPad/phone via SSH).
tmux Config
Add to ~/.tmux.conf:
set -g mouse on
set -g history-limit 50000
12. Telegram Bot (Mobile Access)
This gives you Claude on your phone via Telegram.
Setup
- Create a Telegram bot via @BotFather, get the token
- Install the Telegram plugin: it's in
claude-plugins-official - Configure access via the
/telegram:accessskill in an interactive session
Important: TELEGRAM_POLL Patch
The Telegram plugin starts polling getUpdates in every session by default. If you run multiple Claude sessions, they'll compete for your messages and most will vanish. Fix: patch the plugin so only the dedicated channel session polls.
In ~/.claude/plugins/marketplaces/claude-plugins-official/external_plugins/telegram/server.ts, find the bot.start() block near the bottom and wrap it:
const SHOULD_POLL = process.env.TELEGRAM_POLL === '1'
if (SHOULD_POLL) {
// existing bot.start() block here
} else {
// Tools-only mode: resolve bot username without polling
void bot.api.getMe().then(me => {
botUsername = me.username
process.stderr.write(`telegram channel: tools-only mode as @${me.username} (no polling)\n`)
}).catch(err => {
process.stderr.write(`telegram channel: getMe failed: ${err}\n`)
})
}
Also apply this patch to the cached version at ~/.claude/plugins/cache/claude-plugins-official/telegram/*/server.ts - new cache versions appear after plugin updates.
The dedicated channel wrapper exports TELEGRAM_POLL=1. All other sessions get tools-only mode.
Systemd Service
Create ~/.config/systemd/user/claude-telegram.service:
[Unit]
Description=Claude Code Telegram channel
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
KillMode=control-group
KillSignal=SIGTERM
Environment=PATH=/usr/local/bin:%h/.local/bin:%h/.bun/bin:/usr/bin:/bin
Environment=HOME=%h
Environment=TERM=xterm-256color
WorkingDirectory=%h/dev
ExecStart=%h/.local/bin/claude-telegram
Restart=on-failure
RestartSec=15
[Install]
WantedBy=default.target
Launcher Script
Create ~/.local/bin/claude-telegram:
#!/usr/bin/env bash
set -euo pipefail
SESSION="claude-telegram"
SOCKET="claude-telegram"
CLAUDE="$HOME/.local/bin/claude"
export PATH="/usr/local/bin:$HOME/.local/bin:$HOME/.bun/bin:$PATH"
export TERM="${TERM:-xterm-256color}"
export TELEGRAM_POLL=1
cleanup() {
tmux -L "$SOCKET" kill-server 2>/dev/null || true
}
trap cleanup EXIT
tmux -L "$SOCKET" kill-server 2>/dev/null || true
tmux -L "$SOCKET" new-session -d -s "$SESSION" -x 200 -y 50 \
"$CLAUDE --dangerously-skip-permissions --channels plugin:telegram@claude-plugins-official"
tmux -L "$SOCKET" set-hook -g pane-died "run-shell 'tmux -L $SOCKET wait-for -S pane-exit'"
tmux -L "$SOCKET" wait-for pane-exit
exit 1 # Triggers systemd restart
Make executable and enable:
chmod +x ~/.local/bin/claude-telegram
systemctl --user enable --now claude-telegram
13. Cron Automation
# Hourly briefing generation
0 * * * * ~/dev/memory/engine/thinking_loop.sh >> /tmp/thinking-loop.log 2>&1
# Memory freshness scan (layer 3, every 4 hours)
0 */4 * * * ~/dev/memory-freshness/scan.sh >> /tmp/memory-freshness.log 2>&1
# Dream consolidation (nightly memory synthesis on the markdown archive)
0 3 * * * ~/dev/claude-code-features/scripts/dream-consolidation.sh >> /tmp/claude-dream.log 2>&1
# Memory lint (markdown archive structural health check)
0 5 * * * ~/dev/memory/engine/lint-memories.sh >> /tmp/memory-lint.log 2>&1
# Morning briefing trigger (9:30 AM local)
30 9 * * * ~/dev/your-pa/cron/morning-briefing-trigger.sh >> /tmp/morning.log 2>&1
# Evening briefing (9 PM local)
0 21 * * * ~/dev/your-pa/cron/evening-briefing-trigger.sh >> /tmp/evening.log 2>&1
# Daily plugin update check (catches Cortex, GitNexus, GSD updates)
0 6 * * * ~/.local/bin/check-plugin-updates >> /tmp/plugin-updates.log 2>&1
# PA system backup (every 4 hours)
0 */4 * * * ~/.local/bin/backup-pa-system >> /tmp/pa-backup.log 2>&1
AI Feed Monitoring
The thinking loop includes a two-pronged AI feed search using Grok's X search API:
- Account-based: What are specific people we follow saying? (researchers, tool authors, Anthropic engineers)
- Topic-based: What's trending in Claude Code plugins, MCP servers, AI agent memory, knowledge graphs - regardless of who posted?
This catches both known voices and emerging tools/repos from unknown accounts. Requires an xAI API key in ~/dev/memory/x_account.env.
The briefing triggers send a message to the Telegram tmux session via tmux send-keys, so Claude reads the briefing and sends it to you on Telegram at the scheduled times.
14. MCP Servers (Optional)
MCP servers extend Claude with external tools. These are configured in ~/.claude.json:
- Playwright - browser automation and testing
- Context7 - live documentation lookup for any library/framework
- Figma - read/write Figma designs
Custom MCP servers you can build:
- PA (Personal Assistant) - email, calendar, printing via Google/Apple APIs
- X/Twitter - post, search, timeline, follow/unfollow via Twitter API + Grok search
- eBay - listing management via eBay API
Each MCP server is a Node.js process that exposes tools via the MCP protocol.
15. Community Skills (1,300+ Agent Skills)
Community skills are reusable instruction sets that extend Claude's capabilities - things like /seo, /deep-research, /react-best-practices, /security-audit, /playwright-skill, and hundreds more. They're defined as SKILL.md files and loaded on-demand when you invoke them.
The largest collection is antigravity-awesome-skills with 1,370+ skills covering SEO, security, frontend, backend, DevOps, automation, and more. Install them all with one command:
npx skills add sickn33/antigravity-awesome-skills --all
Additional skill packs worth installing:
# Vercel's official agent skills (Next.js, React, deployment)
npx skills add vercel-labs/agent-skills --all
# SEO specialist skills (14 skills - audits, keywords, content, schema)
npx skills add AgriciDaniel/claude-seo --all
Skills install to ~/.claude/skills/ (global) or .claude/skills/ (project-level). After installing, they appear in autocomplete when you type / in a Claude session.
Finding more skills
npx skills find # Interactive search
npx skills add <repo> --list # Preview what's in a repo before installing
npx skills check # Check for updates
npx skills update # Update all installed skills
How skills work
Each skill is a SKILL.md file with YAML frontmatter (name, description, when to use) and markdown instructions. When you invoke /skill-name, Claude reads the SKILL.md and follows its workflow. Skills can reference other files in their directory (rules, templates, examples).
Skills are different from plugins: plugins install tools and hooks that run as code. Skills are pure instructions - they tell Claude how to approach a task, not what tools to use.
See the companion tooling guide for a curated list of the most useful skills organized by workflow phase.
16. Environment Variable
For smoother terminal during long agent runs:
export CLAUDE_CODE_NO_FLICKER=1
Add to your ~/.bashrc or use when launching: CLAUDE_CODE_NO_FLICKER=1 claude
Quick Start Checklist
Layer 0 - Core
- Install Claude Code CLI
- Create
~/.claude/settings.jsonwith hooks and plugins (Section 2) - Create the token-tracking status line script (Section 3)
- Create the auto-format and push-notify hooks (Section 4)
- Create your CLAUDE.md with memory wiki rules (Section 10)
- Add
csfunction to~/.bashrc(Section 11)
Layer 1 - Cortex (canonical memory)
7. Install Postgres + pgvector and create the cortex database (Section 9)
8. Install the Cortex plugin via /install cortex@cortex-plugins
9. Patch DATABASE_URL to point at your local Postgres
10. Verify the lifecycle hooks register on next session start
Layer 2 - GitNexus (code intelligence)
11. Install GitNexus via npm install -g gitnexus and run gitnexus setup (Section 8)
12. Index your repos with gitnexus analyze
Layer 3 - Memory Freshness
13. Clone memory-freshness to ~/dev/memory-freshness, run bun install (Section 9.5)
14. Symlink mf.sh to /usr/local/bin/mf
15. Add the 4-hour scan cron (Section 13)
Markdown Archive (fallback) 16. Set up the project memory directory and MEMORY.md as fallback (Section 5) 17. Set up memory lint cron at 5 AM (Section 6) 18. Set up dream consolidation cron at 3 AM (Section 7)
Skills
19. Install community skills: npx skills add sickn33/antigravity-awesome-skills --all (Section 15)
20. Install Vercel skills: npx skills add vercel-labs/agent-skills --all (Section 15)
21. Install SEO skills: npx skills add AgriciDaniel/claude-seo --all (Section 15)
Optional 22. Set up Telegram bot for mobile access (Section 12) 23. Set up thinking loop + AI feed cron (Section 13) 24. Add MCP servers for email/calendar/social (Section 14)
Start a session: cs myproject and you're running with three-layer persistent memory, auto-formatting, code intelligence, verification criteria, freshness daemon, and the full skill stack.
Built by nullhex. Setup guide maintained by Greebo.