nullhex

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 tokensWhat it means
< 5MFresh session, business as usual
5-15MNormal active session, still healthy
15-30MGetting heavy - consider wrapping up and restarting
> 30MOne 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.sh and extract-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.

LayerStoreQuestion it answers
1. CortexPostgres + pgvector + graph schema"What did we decide and why"
2. GitNexusLadybugDB code knowledge graph"What calls what"
3. Memory FreshnessBun 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:

  1. The lint script (section 6) runs daily over the markdown files, checking for broken links, orphans, and stale dates.
  2. 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)

  1. 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.
  2. 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.
  3. 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.
  4. Session end: Cortex's SessionEnd hook captures whatever was learned. The next session inherits it for free.
  5. Every 4 hours: Memory Freshness scans every memory, scores it for staleness against external signals, and pings Telegram if anything has rotted.
  6. Daily 5 AM: Lint runs over the markdown archive (broken links, orphans, stale dates).
  7. 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:

  1. Orient - Read all memory files (parallel reads first, then writes)
  2. Triage stale - Check claims against current codebase state
  3. Gather signal - Look for drift in recent git activity
  4. Lint - Fix contradictions, missing cross-references, orphans, near-duplicates
  5. Consolidate - Apply ingest ripple (update related memories when one changes)
  6. 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

  1. 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
  1. Add the cortex marketplace to your settings.json under extraKnownMarketplaces:
"extraKnownMarketplaces": [
  "https://github.com/cdeust/Cortex"
]
  1. Install via /install cortex@cortex-plugins in a Claude session.

  2. Patch the plugin's DATABASE_URL to 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):

HookWhenWhat it does
SessionStartNew session opensPulls relevant Cortex entities and relationships into the opening context
PostToolUseEvery tool callCaptures new observations as they happen
Notification:compactionContext about to be compressedSaves important things before they are summarised into oblivion
SessionEndSession closesCaptures 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:

  1. Recent git activity in the repository the memory is about
  2. Whether the file the memory references still exists at the path it claims
  3. 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

  1. Create a Telegram bot via @BotFather, get the token
  2. Install the Telegram plugin: it's in claude-plugins-official
  3. Configure access via the /telegram:access skill 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:

  1. Account-based: What are specific people we follow saying? (researchers, tool authors, Anthropic engineers)
  2. 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

  1. Install Claude Code CLI
  2. Create ~/.claude/settings.json with hooks and plugins (Section 2)
  3. Create the token-tracking status line script (Section 3)
  4. Create the auto-format and push-notify hooks (Section 4)
  5. Create your CLAUDE.md with memory wiki rules (Section 10)
  6. Add cs function 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.