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
7 changes: 7 additions & 0 deletions docs/examples/claude-code-hooks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ echo ".claude/codegraph-checked.log" >> .gitignore
| `update-graph.sh` | PostToolUse on Edit/Write | Runs `codegraph build` incrementally after each source file edit 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, and resets the remind tracker |

### Doc hygiene hooks

| Hook | Trigger | What it does |
|------|---------|-------------|
| `check-readme.sh` | PreToolUse on Bash | Blocks `git commit` when source files are staged but `README.md`, `CLAUDE.md`, or `ROADMAP.md` aren't — prompts the agent to review whether docs need updating |

### Parallel session safety hooks (recommended for multi-agent workflows)

| Hook | Trigger | What it does |
Expand Down Expand Up @@ -62,6 +68,7 @@ Without this fix, `CLAUDE_PROJECT_DIR` (which always points to the main project

- **Solo developer:** `enrich-context.sh` + `update-graph.sh` + `post-git-ops.sh`
- **With reminders:** Add `remind-codegraph.sh`
- **Doc hygiene:** Add `check-readme.sh` to catch source commits that may need doc updates
- **Multi-agent / worktrees:** Add `guard-git.sh` + `track-edits.sh` + `track-moves.sh`

**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.
Expand Down
13 changes: 13 additions & 0 deletions docs/guides/ai-agent-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,7 @@ Hooks automate codegraph integration so the agent gets structural context withou
| `enrich-context.sh` | PreToolUse (Read, Grep) | Injects dependency info before file reads |
| `remind-codegraph.sh` | PreToolUse (Edit, Write) | Reminds agent to check context/impact before editing |
| `update-graph.sh` | PostToolUse (Edit, Write) | Rebuilds graph after code changes |
| `check-readme.sh` | PreToolUse (Bash) | Blocks commits when source changes may need doc updates |
| `guard-git.sh` | PreToolUse (Bash) | Blocks dangerous git ops, validates commits |
| `track-edits.sh` | PostToolUse (Edit, Write) | Logs edits for commit validation |

Expand Down Expand Up @@ -703,6 +704,14 @@ Before editing, always: (1) where <name>, (2) explain src/parser.js,

**Result:** The graph stays current as the agent edits code. Subsequent `context`, `fn-impact`, and `diff-impact` calls reflect the latest changes.

### `check-readme.sh` — Enforce doc updates alongside source changes

**Trigger:** Before any Bash command (PreToolUse).

**What it does:** Intercepts `git commit` commands and checks whether source files are staged (anything under `src/`, `cli.js`, `constants.js`, `parser.js`, `package.json`, or `grammars/`). If so, it verifies that `README.md`, `CLAUDE.md`, and `ROADMAP.md` are also staged. Missing docs trigger a `deny` decision listing which files weren't staged and what to review in each — language support tables, architecture docs, feature lists, roadmap phases, etc.

**Allows:** Commits that only touch non-source files (tests, docs, config) pass through without checks. Commits where all three docs are staged also pass through.

### `guard-git.sh` — Prevent unsafe git operations

**Trigger:** Before any Bash command.
Expand Down Expand Up @@ -749,6 +758,10 @@ Add to `.claude/settings.json`:
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/check-readme.sh"
},
{
"type": "command",
"command": "bash .claude/hooks/guard-git.sh"
Expand Down
13 changes: 13 additions & 0 deletions docs/guides/recommended-practices.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,16 @@ You can configure [Claude Code hooks](https://docs.anthropic.com/en/docs/claude-
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/check-readme.sh\"",
"timeout": 10
}
]
},
{
"matcher": "Read|Grep",
"hooks": [
Expand Down Expand Up @@ -288,6 +298,8 @@ You can configure [Claude Code hooks](https://docs.anthropic.com/en/docs/claude-
> }
> ```

**Doc check hook** (PreToolUse on Bash): when Claude runs `git commit` with source files staged (anything under `src/`, `cli.js`, `constants.js`, `parser.js`, `package.json`, or `grammars/`), the hook checks whether `README.md`, `CLAUDE.md`, and `ROADMAP.md` are also staged. If any are missing, it blocks the commit with a `deny` decision listing which docs weren't staged and what to review in each (language support tables, architecture docs, roadmap phases, etc.). Non-source-only commits (tests, docs, config) pass through without checks.

**Edit reminder hook** (PreToolUse on Edit/Write): before the agent writes code, a reminder is injected via `additionalContext` prompting it to check `where`, `explain`, `context`, and `fn-impact` first. Only fires once per file per session (tracks in `.claude/codegraph-checked.log`, gitignored). Non-blocking — it nudges but never prevents the edit. Skips non-source files like `.md`, `.json`, `.yml`.

**Graph update hook** (PostToolUse on Edit/Write): keeps the graph incrementally updated after each file edit. Only changed files are re-parsed.
Expand All @@ -301,6 +313,7 @@ You can configure [Claude Code hooks](https://docs.anthropic.com/en/docs/claude-
- `remind-codegraph.sh` — pre-edit reminder to check context/impact
- `update-graph.sh` — incremental graph updates after edits
- `post-git-ops.sh` — graph rebuild + edit tracking after rebase/revert/merge
- `check-readme.sh` — blocks commits when source changes may require doc updates
- `guard-git.sh` — blocks dangerous git commands + validates commits
- `track-edits.sh` — logs edited files for commit validation
- `track-moves.sh` — logs file moves/copies for commit validation
Expand Down
111 changes: 108 additions & 3 deletions src/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,14 @@ import {
MODELS,
search,
} from './embedder.js';
import { exportDOT, exportJSON, exportMermaid } from './export.js';
import {
exportDOT,
exportGraphML,
exportGraphSON,
exportJSON,
exportMermaid,
exportNeo4jCSV,
} from './export.js';
import { setVerbose } from './logger.js';
import { printNdjson } from './paginate.js';
import {
Expand Down Expand Up @@ -446,9 +453,13 @@ program

program
.command('export')
.description('Export dependency graph as DOT (Graphviz), Mermaid, or JSON')
.description('Export dependency graph as DOT, Mermaid, JSON, GraphML, GraphSON, or Neo4j CSV')
.option('-d, --db <path>', 'Path to graph.db')
.option('-f, --format <format>', 'Output format: dot, mermaid, json', 'dot')
.option(
'-f, --format <format>',
'Output format: dot, mermaid, json, graphml, graphson, neo4j',
'dot',
)
.option('--functions', 'Function-level graph instead of file-level')
.option('-T, --no-tests', 'Exclude test/spec files')
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
Expand All @@ -472,6 +483,25 @@ program
case 'json':
output = JSON.stringify(exportJSON(db, exportOpts), null, 2);
break;
case 'graphml':
output = exportGraphML(db, exportOpts);
break;
case 'graphson':
output = JSON.stringify(exportGraphSON(db, exportOpts), null, 2);
break;
case 'neo4j': {
const csv = exportNeo4jCSV(db, exportOpts);
if (opts.output) {
const base = opts.output.replace(/\.[^.]+$/, '') || opts.output;
fs.writeFileSync(`${base}-nodes.csv`, csv.nodes, 'utf-8');
fs.writeFileSync(`${base}-relationships.csv`, csv.relationships, 'utf-8');
db.close();
console.log(`Exported to ${base}-nodes.csv and ${base}-relationships.csv`);
return;
}
output = `--- nodes.csv ---\n${csv.nodes}\n\n--- relationships.csv ---\n${csv.relationships}`;
break;
}
default:
output = exportDOT(db, exportOpts);
break;
Expand All @@ -487,6 +517,81 @@ program
}
});

program
.command('plot')
.description('Generate an interactive HTML dependency graph viewer')
.option('-d, --db <path>', 'Path to graph.db')
.option('--functions', 'Function-level graph instead of file-level')
.option('-T, --no-tests', 'Exclude test/spec files')
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
.option('--min-confidence <score>', 'Minimum edge confidence threshold (default: 0.5)', '0.5')
.option('-o, --output <file>', 'Write HTML to file')
.option('-c, --config <path>', 'Path to .plotDotCfg config file')
.option('--no-open', 'Do not open in browser')
.option('--cluster <mode>', 'Cluster nodes: none | community | directory')
.option('--overlay <list>', 'Comma-separated overlays: complexity,risk')
.option('--seed <strategy>', 'Seed strategy: all | top-fanin | entry')
.option('--seed-count <n>', 'Number of seed nodes (default: 30)')
.option('--size-by <metric>', 'Size nodes by: uniform | fan-in | fan-out | complexity')
.option('--color-by <mode>', 'Color nodes by: kind | role | community | complexity')
.action(async (opts) => {
const { generatePlotHTML, loadPlotConfig } = await import('./viewer.js');
const os = await import('node:os');
const db = openReadonlyOrFail(opts.db);

let plotCfg;
if (opts.config) {
try {
plotCfg = JSON.parse(fs.readFileSync(opts.config, 'utf-8'));
} catch (e) {
console.error(`Failed to load config: ${e.message}`);
db.close();
process.exitCode = 1;
return;
}
} else {
plotCfg = loadPlotConfig(process.cwd());
}

// Merge CLI flags into config
if (opts.cluster) plotCfg.clusterBy = opts.cluster;
if (opts.colorBy) plotCfg.colorBy = opts.colorBy;
if (opts.sizeBy) plotCfg.sizeBy = opts.sizeBy;
if (opts.seed) plotCfg.seedStrategy = opts.seed;
if (opts.seedCount) plotCfg.seedCount = parseInt(opts.seedCount, 10);
if (opts.overlay) {
const parts = opts.overlay.split(',').map((s) => s.trim());
if (!plotCfg.overlays) plotCfg.overlays = {};
if (parts.includes('complexity')) plotCfg.overlays.complexity = true;
if (parts.includes('risk')) plotCfg.overlays.risk = true;
}

const html = generatePlotHTML(db, {
fileLevel: !opts.functions,
noTests: resolveNoTests(opts),
minConfidence: parseFloat(opts.minConfidence),
config: plotCfg,
});
db.close();

const outPath = opts.output || path.join(os.tmpdir(), `codegraph-plot-${Date.now()}.html`);
fs.writeFileSync(outPath, html, 'utf-8');
console.log(`Plot written to ${outPath}`);

if (opts.open !== false) {
const { execFile } = await import('node:child_process');
const args =
process.platform === 'win32'
? ['cmd', ['/c', 'start', '', outPath]]
: process.platform === 'darwin'
? ['open', [outPath]]
: ['xdg-open', [outPath]];
execFile(args[0], args[1], (err) => {
if (err) console.error('Could not open browser:', err.message);
});
}
});

program
.command('cycles')
.description('Detect circular dependencies in the codebase')
Expand Down
Loading