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
3 changes: 2 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@ Codegraph is **our own tool**. Use it to analyze this repository before making c
node src/cli.js build . # Build/update the graph (incremental)
node src/cli.js map --limit 20 # Module overview & most-connected nodes
node src/cli.js stats # Graph health and quality score
node src/cli.js fn <name> -T # Function call chain (callers + callees)
node src/cli.js query <name> -T # Function call chain (callers + callees)
node src/cli.js query <a> --path <b> -T # Shortest path between two symbols
node src/cli.js deps src/<file>.js # File-level imports and importers
node src/cli.js diff-impact main # Impact of current branch vs main
node src/cli.js complexity -T # Per-function complexity metrics
Expand Down
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,12 +237,12 @@ codegraph explain <function> # Function summary: signature, calls, callers, te

```bash
codegraph impact <file> # Transitive reverse dependency trace
codegraph fn <name> # Function-level: callers, callees, call chain
codegraph fn <name> --no-tests --depth 5
codegraph query <name> # Function-level: callers, callees, call chain
codegraph query <name> --no-tests --depth 5
codegraph fn-impact <name> # What functions break if this one changes
codegraph path <from> <to> # Shortest path between two symbols (A calls...calls B)
codegraph path <from> <to> --reverse # Follow edges backward
codegraph path <from> <to> --max-depth 5 --kinds calls,imports
codegraph query <from> --path <to> # Shortest path between two symbols (A calls...calls B)
codegraph query <from> --path <to> --reverse # Follow edges backward
codegraph query <from> --path <to> --depth 5 --kinds calls,imports
codegraph diff-impact # Impact of unstaged git changes
codegraph diff-impact --staged # Impact of staged changes
codegraph diff-impact HEAD~3 # Impact vs a specific ref
Expand Down Expand Up @@ -566,8 +566,8 @@ This project uses codegraph. The database is at `.codegraph/graph.db`.
### Other useful commands
- `codegraph build .` — rebuild the graph (incremental by default)
- `codegraph map` — module overview
- `codegraph fn <name> -T` — function call chain
- `codegraph path <from> <to> -T` — shortest call path between two symbols
- `codegraph query <name> -T` — function call chain (callers + callees)
- `codegraph query <from> --path <to> -T` — shortest call path between two symbols
- `codegraph deps <file>` — file-level dependencies
- `codegraph roles --role dead -T` — find dead code (unreferenced symbols)
- `codegraph roles --role core -T` — find core symbols (high fan-in)
Expand Down
4 changes: 1 addition & 3 deletions src/batch.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
fnDepsData,
fnImpactData,
impactAnalysisData,
queryNameData,
whereData,
} from './queries.js';

Expand All @@ -32,8 +31,7 @@ export const BATCH_COMMANDS = {
context: { fn: contextData, sig: 'name' },
explain: { fn: explainData, sig: 'target' },
where: { fn: whereData, sig: 'target' },
query: { fn: queryNameData, sig: 'name' },
fn: { fn: fnDepsData, sig: 'name' },
query: { fn: fnDepsData, sig: 'name' },
impact: { fn: impactAnalysisData, sig: 'file' },
deps: { fn: fileDepsData, sig: 'file' },
flow: { fn: flowData, sig: 'name' },
Expand Down
107 changes: 36 additions & 71 deletions src/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import {
fnImpact,
impactAnalysis,
moduleMap,
queryName,
roles,
stats,
symbolPath,
Expand Down Expand Up @@ -106,22 +105,50 @@ program

program
.command('query <name>')
.description('Find a function/class, show callers and callees')
.description('Function-level dependency chain or shortest path between symbols')
.option('-d, --db <path>', 'Path to graph.db')
.option('--depth <n>', 'Transitive caller depth', '3')
.option('-f, --file <path>', 'Scope search to functions in this file (partial match)')
.option('-k, --kind <kind>', 'Filter to a specific symbol kind')
.option('--path <to>', 'Path mode: find shortest path to <to>')
.option('--kinds <kinds>', 'Path mode: comma-separated edge kinds to follow (default: calls)')
.option('--reverse', 'Path mode: follow edges backward')
.option('--from-file <path>', 'Path mode: disambiguate source symbol by file')
.option('--to-file <path>', 'Path mode: disambiguate target symbol by file')
.option('-T, --no-tests', 'Exclude test/spec files from results')
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
.option('-j, --json', 'Output as JSON')
.option('--limit <number>', 'Max results to return')
.option('--offset <number>', 'Skip N results (default: 0)')
.option('--ndjson', 'Newline-delimited JSON output')
.action((name, opts) => {
queryName(name, opts.db, {
noTests: resolveNoTests(opts),
json: opts.json,
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
ndjson: opts.ndjson,
});
if (opts.kind && !ALL_SYMBOL_KINDS.includes(opts.kind)) {
console.error(`Invalid kind "${opts.kind}". Valid: ${ALL_SYMBOL_KINDS.join(', ')}`);
process.exit(1);
}
if (opts.path) {
symbolPath(name, opts.path, opts.db, {
maxDepth: opts.depth ? parseInt(opts.depth, 10) : 10,
edgeKinds: opts.kinds ? opts.kinds.split(',').map((s) => s.trim()) : undefined,
reverse: opts.reverse,
fromFile: opts.fromFile,
toFile: opts.toFile,
kind: opts.kind,
noTests: resolveNoTests(opts),
json: opts.json,
});
} else {
fnDeps(name, opts.db, {
depth: parseInt(opts.depth, 10),
file: opts.file,
kind: opts.kind,
noTests: resolveNoTests(opts),
json: opts.json,
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
ndjson: opts.ndjson,
});
}
});

program
Expand Down Expand Up @@ -190,36 +217,6 @@ program
});
});

program
.command('fn <name>')
.description('Function-level dependencies: callers, callees, and transitive call chain')
.option('-d, --db <path>', 'Path to graph.db')
.option('--depth <n>', 'Transitive caller depth', '3')
.option('-f, --file <path>', 'Scope search to functions in this file (partial match)')
.option('-k, --kind <kind>', 'Filter to a specific symbol kind')
.option('-T, --no-tests', 'Exclude test/spec files from results')
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
.option('-j, --json', 'Output as JSON')
.option('--limit <number>', 'Max results to return')
.option('--offset <number>', 'Skip N results (default: 0)')
.option('--ndjson', 'Newline-delimited JSON output')
.action((name, opts) => {
if (opts.kind && !ALL_SYMBOL_KINDS.includes(opts.kind)) {
console.error(`Invalid kind "${opts.kind}". Valid: ${ALL_SYMBOL_KINDS.join(', ')}`);
process.exit(1);
}
fnDeps(name, opts.db, {
depth: parseInt(opts.depth, 10),
file: opts.file,
kind: opts.kind,
noTests: resolveNoTests(opts),
json: opts.json,
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
ndjson: opts.ndjson,
});
});

program
.command('fn-impact <name>')
.description('Function-level impact: what functions break if this one changes')
Expand Down Expand Up @@ -250,36 +247,6 @@ program
});
});

program
.command('path <from> <to>')
.description('Find shortest path between two symbols (A calls...calls B)')
.option('-d, --db <path>', 'Path to graph.db')
.option('--max-depth <n>', 'Maximum BFS depth', '10')
.option('--kinds <kinds>', 'Comma-separated edge kinds to follow (default: calls)')
.option('--reverse', 'Follow edges backward (B is called by...called by A)')
.option('--from-file <path>', 'Disambiguate source symbol by file (partial match)')
.option('--to-file <path>', 'Disambiguate target symbol by file (partial match)')
.option('-k, --kind <kind>', 'Filter both symbols by kind')
.option('-T, --no-tests', 'Exclude test/spec files from results')
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
.option('-j, --json', 'Output as JSON')
.action((from, to, opts) => {
if (opts.kind && !ALL_SYMBOL_KINDS.includes(opts.kind)) {
console.error(`Invalid kind "${opts.kind}". Valid: ${ALL_SYMBOL_KINDS.join(', ')}`);
process.exit(1);
}
symbolPath(from, to, opts.db, {
maxDepth: parseInt(opts.maxDepth, 10),
edgeKinds: opts.kinds ? opts.kinds.split(',').map((s) => s.trim()) : undefined,
reverse: opts.reverse,
fromFile: opts.fromFile,
toFile: opts.toFile,
kind: opts.kind,
noTests: resolveNoTests(opts),
json: opts.json,
});
});

program
.command('context <name>')
.description('Full context for a function: source, deps, callers, tests, signature')
Expand Down Expand Up @@ -980,7 +947,6 @@ program
.option('--ndjson', 'Newline-delimited JSON output')
.option('--limit <number>', 'Max results to return')
.option('--offset <number>', 'Skip N results (default: 0)')
.option('--path <target>', 'Find data flow path to <target>')
.option('--impact', 'Show data-dependent blast radius')
.option('--depth <n>', 'Max traversal depth', '5')
.action(async (name, opts) => {
Expand All @@ -997,7 +963,6 @@ program
ndjson: opts.ndjson,
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
path: opts.path,
impact: opts.impact,
depth: opts.depth,
});
Expand Down
37 changes: 0 additions & 37 deletions src/dataflow.js
Original file line number Diff line number Diff line change
Expand Up @@ -1089,9 +1089,6 @@ export function dataflowImpactData(name, customDbPath, opts = {}) {
* CLI display for dataflow command.
*/
export function dataflow(name, customDbPath, opts = {}) {
if (opts.path) {
return dataflowPath(name, opts.path, customDbPath, opts);
}
if (opts.impact) {
return dataflowImpact(name, customDbPath, opts);
}
Expand Down Expand Up @@ -1168,40 +1165,6 @@ export function dataflow(name, customDbPath, opts = {}) {
}
}

/**
* CLI display for dataflow --path.
*/
function dataflowPath(from, to, customDbPath, opts = {}) {
const data = dataflowPathData(from, to, customDbPath, {
noTests: opts.noTests,
maxDepth: opts.depth ? Number(opts.depth) : 10,
});

if (opts.json) {
console.log(JSON.stringify(data, null, 2));
return;
}

if (data.warning) {
console.log(`⚠ ${data.warning}`);
return;
}
if (!data.found) {
console.log(data.error || `No data flow path found from "${from}" to "${to}".`);
return;
}

console.log(
`\nData flow path: ${from} → ${to} (${data.hops} hop${data.hops !== 1 ? 's' : ''})\n`,
);
for (let i = 0; i < data.path.length; i++) {
const p = data.path[i];
const prefix = i === 0 ? ' ●' : ` ${'│ '.repeat(i - 1)}├─`;
const edge = p.edgeKind ? ` [${p.edgeKind}]` : '';
console.log(`${prefix} ${p.name} (${p.file}:${p.line})${edge}`);
}
}

/**
* CLI display for dataflow --impact.
*/
Expand Down
Loading