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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions .claude/hooks/post-git-ops.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# post-git-ops.sh — PostToolUse hook for Bash tool calls
# Detects git operations that change file state (rebase, revert, cherry-pick,
# merge, pull) and:
# 1. Rebuilds the codegraph incrementally (fixes stale dependency context)
# 1. Runs a full codegraph rebuild (recomputes all analysis data, not just edges)
# 2. Logs changed files to session-edits.log (so commit validation works)
# Always exits 0 (informational only, never blocks).

Expand Down Expand Up @@ -33,12 +33,23 @@ fi
PROJECT_DIR=$(git rev-parse --show-toplevel 2>/dev/null) || PROJECT_DIR="${CLAUDE_PROJECT_DIR:-.}"

# --- 1. Rebuild codegraph ---
# Git operations (merge, rebase, pull) can change many files at once, including
# files not directly edited in this session. A full rebuild ensures complexity,
# dataflow, and directory cohesion metrics are recomputed for all affected files
# — not just the ones the incremental pipeline would detect via reverse-deps.
# See docs/guides/incremental-builds.md for what incremental skips.
DB_PATH="$PROJECT_DIR/.codegraph/graph.db"
if [ -f "$DB_PATH" ]; then
BUILD_OK=0
if command -v codegraph &>/dev/null; then
codegraph build "$PROJECT_DIR" -d "$DB_PATH" 2>/dev/null || true
codegraph build "$PROJECT_DIR" -d "$DB_PATH" --no-incremental 2>/dev/null && BUILD_OK=1 || true
else
node "${CLAUDE_PROJECT_DIR:-$PROJECT_DIR}/src/cli.js" build "$PROJECT_DIR" -d "$DB_PATH" 2>/dev/null || true
node "${CLAUDE_PROJECT_DIR:-$PROJECT_DIR}/src/cli.js" build "$PROJECT_DIR" -d "$DB_PATH" --no-incremental 2>/dev/null && BUILD_OK=1 || true
fi
# Update staleness marker only if the full rebuild succeeded
if [ "$BUILD_OK" -eq 1 ]; then
MARKER="$PROJECT_DIR/.codegraph/last-full-build"
touch "$MARKER"
fi
fi

Expand Down
47 changes: 41 additions & 6 deletions .claude/hooks/update-graph.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#!/usr/bin/env bash
# rebuild-graph.sh — PostToolUse hook for Edit and Write tools
# Incrementally updates the codegraph after source file edits.
# On the first edit of a stale session (no full rebuild in >24h), upgrades
# to a full rebuild so complexity/dataflow/cohesion data stays fresh.
# Always exits 0 (informational only, never blocks).

set -euo pipefail
Expand Down Expand Up @@ -38,18 +40,51 @@ if echo "$FILE_PATH" | grep -qE '(fixtures|__fixtures__|testdata)/'; then
fi

# Guard: codegraph DB must exist (project has been built at least once)
# Use git worktree root so each worktree uses its own DB (avoids WAL contention)
WORK_ROOT=$(git rev-parse --show-toplevel 2>/dev/null) || WORK_ROOT="${CLAUDE_PROJECT_DIR:-.}"
DB_PATH="$WORK_ROOT/.codegraph/graph.db"
PROJECT_DIR=$(git rev-parse --show-toplevel 2>/dev/null) || PROJECT_DIR="${CLAUDE_PROJECT_DIR:-.}"
DB_PATH="$PROJECT_DIR/.codegraph/graph.db"
if [ ! -f "$DB_PATH" ]; then
exit 0
fi

# Run incremental build (skips unchanged files via hash check)
# --- Staleness check ---
# If no full rebuild has happened in >24h, upgrade this one build to
# --no-incremental so complexity/dataflow/cohesion are recomputed for
# all files. Subsequent edits in the same session stay incremental.
# See docs/guides/incremental-builds.md for what incremental skips.
MARKER="$PROJECT_DIR/.codegraph/last-full-build"
BUILD_FLAGS=""
STALE_SECONDS=86400 # 24 hours

if [ ! -f "$MARKER" ]; then
# No marker = never had a tracked full rebuild — do one now
BUILD_FLAGS="--no-incremental"
else
# Check marker age (cross-platform: use node for reliable epoch math)
MARKER_AGE=$(node -e "
const fs = require('fs');
try {
const mtime = fs.statSync('${MARKER//\\/\\\\}').mtimeMs;
console.log(Math.floor((Date.now() - mtime) / 1000));
} catch { console.log('999999'); }
" 2>/dev/null) || MARKER_AGE=999999

if [ "$MARKER_AGE" -gt "$STALE_SECONDS" ]; then
BUILD_FLAGS="--no-incremental"
fi
fi

# Run the build
BUILD_OK=0
if command -v codegraph &>/dev/null; then
codegraph build "$WORK_ROOT" -d "$DB_PATH" 2>/dev/null || true
codegraph build "$PROJECT_DIR" -d "$DB_PATH" $BUILD_FLAGS 2>/dev/null && BUILD_OK=1 || true
else
node "${CLAUDE_PROJECT_DIR:-$WORK_ROOT}/src/cli.js" build "$WORK_ROOT" -d "$DB_PATH" 2>/dev/null || true
node "${CLAUDE_PROJECT_DIR:-$PROJECT_DIR}/src/cli.js" build "$PROJECT_DIR" -d "$DB_PATH" $BUILD_FLAGS 2>/dev/null && BUILD_OK=1 || true
fi

# Update marker only if we did a full rebuild AND it succeeded
if [ -n "$BUILD_FLAGS" ] && [ "$BUILD_OK" -eq 1 ]; then
mkdir -p "$(dirname "$MARKER")"
touch "$MARKER"
fi

exit 0
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,35 @@ The graph stays current without re-parsing your entire codebase. Three-tier chan

**Result:** change one file in a 3,000-file project and the rebuild completes in under a second. Put it in a commit hook, a file watcher, or let your AI agent trigger it.

#### What incremental rebuilds refresh — and what they don't

Incremental builds re-parse changed files and rebuild their edges, structure metrics, and role classifications. But some data is **only fully refreshed on a full rebuild:**

| Data | Incremental | Full rebuild |
|------|:-----------:|:------------:|
| Symbols & edges for changed files | Yes | Yes |
| Reverse-dependency cascade (importers of changed files) | Yes | Yes |
| AST nodes, complexity, CFG, dataflow for changed files | Yes | Yes |
| Directory-level cohesion metrics | Partial (skipped for ≤5 files) | Yes |
| Advisory checks (orphaned embeddings, stale embeddings, unused exports) | Skipped | Yes |
| Build metadata persistence | Skipped for ≤3 files | Yes |
| Incremental drift detection | Skipped | Yes |

**When to run a full rebuild:**

```bash
codegraph build --no-incremental # Force full rebuild
```

- **After large refactors** (renames, moves, deleted files) — the reverse-dependency cascade handles most cases, but a full rebuild ensures nothing is stale
- **If you suspect stale analysis data** — complexity or dataflow results for files you didn't directly edit won't update incrementally
- **Periodically** — if you rely heavily on `complexity`, `dataflow`, `roles --role dead`, or `communities` queries, run a full rebuild weekly or after major merges
- **After upgrading codegraph** — engine, schema, or version changes trigger an automatic full rebuild, but if you skip versions you may want to force one

Codegraph auto-detects and forces a full rebuild when the engine, schema version, or codegraph version changes between builds. For everything else, incremental is the safe default — a full rebuild is a correctness guarantee, not a frequent necessity.

> **Detailed guide:** See [docs/guides/incremental-builds.md](docs/guides/incremental-builds.md) for a complete breakdown of what each build mode refreshes and recommended rebuild schedules.

### Dual Engine

Codegraph ships with two parsing engines:
Expand Down
32 changes: 30 additions & 2 deletions docs/examples/claude-code-hooks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ If an instruction matters, make it a blocking hook. If it's in CLAUDE.md but not

| Hook | Trigger | What it does |
|------|---------|-------------|
| `update-graph.sh` | PostToolUse on Edit/Write | Runs `codegraph build` incrementally after source file edits to keep the graph fresh |
| `post-git-ops.sh` | PostToolUse on Bash | Detects `git rebase/revert/cherry-pick/merge/pull` and rebuilds the graph + logs changed files to the edit log |
| `update-graph.sh` | PostToolUse on Edit/Write | Runs `codegraph build` incrementally after source file edits to keep the graph fresh (see [freshness note](#incremental-build-freshness)) |
| `post-git-ops.sh` | PostToolUse on Bash | Detects `git rebase/revert/cherry-pick/merge/pull`, runs a **full rebuild** (recomputes all analysis data), and logs changed files to the edit log |

## Pre-commit consolidation

Expand All @@ -80,6 +80,34 @@ All session-local state files (`session-edits.log`) use `git rev-parse --show-to

**Branch name validation:** The `guard-git.sh` in this repo's `.claude/hooks/` validates branch names against conventional prefixes (`feat/`, `fix/`, etc.). The example version omits this — add your own validation if needed.

## Incremental build freshness

The `update-graph.sh` hook runs **incremental** builds — it only re-parses files you directly edited. This keeps symbols, edges, and caller data fresh during a session. However, some analysis data is only recomputed for directly modified files:

- **Complexity, dataflow, and CFG metrics** for files you didn't edit remain from the last full build
- **Directory-level cohesion metrics** are skipped for small changes (≤5 files)
- **Advisory checks** (orphaned embeddings, unused exports) are skipped entirely

**This means:** If you edit `utils.ts` and `handler.ts` imports it, the import edges from `handler.ts` are rebuilt, but `handler.ts`'s complexity and dataflow data are not recomputed.

### Automatic staleness detection

The hooks handle this automatically via a **staleness marker** (`.codegraph/last-full-build`):

1. **First edit of a stale session** — `update-graph.sh` checks the marker. If missing or older than 24 hours, it upgrades that one build to `--no-incremental` (~3.5s instead of ~1.5s) and updates the marker.
2. **Subsequent edits** — marker is fresh, so builds stay incremental (fast).
3. **After git merge/rebase/pull** — `post-git-ops.sh` always runs a full rebuild and updates the marker.

This means you never need to manually run `codegraph build --no-incremental` — the first edit after a stale period triggers it automatically. The 24-hour threshold ensures complexity, dataflow, and cohesion data never drifts more than a day behind.

Add `.codegraph/last-full-build` to `.gitignore` — it's session-local state:

```
.codegraph/
```

For a detailed breakdown of what incremental builds skip, see the [incremental builds guide](../../guides/incremental-builds.md).

## Requirements

- Node.js >= 20
Expand Down
18 changes: 14 additions & 4 deletions docs/examples/claude-code-hooks/post-git-ops.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# post-git-ops.sh — PostToolUse hook for Bash tool calls
# Detects git operations that change file state (rebase, revert, cherry-pick,
# merge, pull) and:
# 1. Rebuilds the codegraph incrementally (fixes stale dependency context)
# 1. Runs a full codegraph rebuild (recomputes all analysis data, not just edges)
# 2. Logs changed files to session-edits.log (so commit validation works)
# Always exits 0 (informational only, never blocks).

Expand Down Expand Up @@ -32,13 +32,23 @@ fi
# Use git worktree root so each worktree session has its own state
PROJECT_DIR=$(git rev-parse --show-toplevel 2>/dev/null) || PROJECT_DIR="${CLAUDE_PROJECT_DIR:-.}"

# --- 1. Rebuild codegraph ---
# --- 1. Full rebuild of codegraph ---
# Git operations (merge, rebase, pull) can change many files at once. A full
# rebuild ensures complexity, dataflow, and directory cohesion metrics are
# recomputed for all affected files — not just direct edits.
# See docs/guides/incremental-builds.md for what incremental skips.
DB_PATH="$PROJECT_DIR/.codegraph/graph.db"
if [ -f "$DB_PATH" ]; then
BUILD_OK=0
if command -v codegraph &>/dev/null; then
codegraph build "$PROJECT_DIR" -d "$DB_PATH" 2>/dev/null || true
codegraph build "$PROJECT_DIR" -d "$DB_PATH" --no-incremental 2>/dev/null && BUILD_OK=1 || true
else
npx --yes @optave/codegraph build "$PROJECT_DIR" -d "$DB_PATH" 2>/dev/null || true
npx --yes @optave/codegraph build "$PROJECT_DIR" -d "$DB_PATH" --no-incremental 2>/dev/null && BUILD_OK=1 || true
fi
# Update staleness marker only if the full rebuild succeeded
if [ "$BUILD_OK" -eq 1 ]; then
MARKER="$PROJECT_DIR/.codegraph/last-full-build"
touch "$MARKER"
fi
fi

Expand Down
47 changes: 41 additions & 6 deletions docs/examples/claude-code-hooks/update-graph.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#!/usr/bin/env bash
# update-graph.sh — PostToolUse hook for Edit and Write tools
# Incrementally updates the codegraph after source file edits.
# On the first edit of a stale session (no full rebuild in >24h), upgrades
# to a full rebuild so complexity/dataflow/cohesion data stays fresh.
# Always exits 0 (informational only, never blocks).

set -euo pipefail
Expand Down Expand Up @@ -38,18 +40,51 @@ if echo "$FILE_PATH" | grep -qE '(fixtures|__fixtures__|testdata)/'; then
fi

# Guard: codegraph DB must exist (project has been built at least once)
# Use git worktree root so each worktree uses its own DB (avoids WAL contention)
WORK_ROOT=$(git rev-parse --show-toplevel 2>/dev/null) || WORK_ROOT="${CLAUDE_PROJECT_DIR:-.}"
DB_PATH="$WORK_ROOT/.codegraph/graph.db"
PROJECT_DIR=$(git rev-parse --show-toplevel 2>/dev/null) || PROJECT_DIR="${CLAUDE_PROJECT_DIR:-.}"
DB_PATH="$PROJECT_DIR/.codegraph/graph.db"
if [ ! -f "$DB_PATH" ]; then
exit 0
fi

# Run incremental build (skips unchanged files via hash check)
# --- Staleness check ---
# If no full rebuild has happened in >24h, upgrade this one build to
# --no-incremental so complexity/dataflow/cohesion are recomputed for
# all files. Subsequent edits in the same session stay incremental.
# See docs/guides/incremental-builds.md for what incremental skips.
MARKER="$PROJECT_DIR/.codegraph/last-full-build"
BUILD_FLAGS=""
STALE_SECONDS=86400 # 24 hours

if [ ! -f "$MARKER" ]; then
# No marker = never had a tracked full rebuild — do one now
BUILD_FLAGS="--no-incremental"
else
# Check marker age (cross-platform: use node for reliable epoch math)
MARKER_AGE=$(node -e "
const fs = require('fs');
try {
const mtime = fs.statSync('${MARKER//\\/\\\\}').mtimeMs;
console.log(Math.floor((Date.now() - mtime) / 1000));
} catch { console.log('999999'); }
" 2>/dev/null) || MARKER_AGE=999999

if [ "$MARKER_AGE" -gt "$STALE_SECONDS" ]; then
BUILD_FLAGS="--no-incremental"
fi
fi

# Run the build
BUILD_OK=0
if command -v codegraph &>/dev/null; then
codegraph build "$WORK_ROOT" -d "$DB_PATH" 2>/dev/null || true
codegraph build "$PROJECT_DIR" -d "$DB_PATH" $BUILD_FLAGS 2>/dev/null && BUILD_OK=1 || true
else
npx --yes @optave/codegraph build "$WORK_ROOT" -d "$DB_PATH" 2>/dev/null || true
npx --yes @optave/codegraph build "$PROJECT_DIR" -d "$DB_PATH" $BUILD_FLAGS 2>/dev/null && BUILD_OK=1 || true
fi

# Update marker only if we did a full rebuild AND it succeeded
if [ -n "$BUILD_FLAGS" ] && [ "$BUILD_OK" -eq 1 ]; then
mkdir -p "$(dirname "$MARKER")"
touch "$MARKER"
fi

exit 0
Loading
Loading