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
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ Use Codegraph alongside text search and compilers: text search finds exact strin
| "Where should I start in this repo?" | `codegraph orient --root . --budget small` | Central modules, a bounded tree, and copyable follow-ups |
| "How does this feature work?" | `codegraph explore "<question>" --root .` | Ranked anchors, source packets, dependency paths, blast radius, and likely tests |
| "What could this change break?" | `codegraph review --base HEAD --head WORKTREE --summary` | Changed symbols, risk signals, candidate tests, duplicate leads, and review tasks |
| "Which tests should I run?" | `codegraph affected --base HEAD --head WORKTREE --quiet` | Deterministic affected test paths from changed files and reverse dependencies |
| "What depends on this file?" | `codegraph rdeps src/file.ts --json` | Reverse dependencies from the resolved project graph |
| "Where is this symbol defined or used?" | `codegraph goto <file> <line> <column>` and `codegraph refs ...` | Semantic definitions and references across supported languages |
| "Which declaration matches this name?" | `codegraph symbols "CodeReviewSession" --root .` | Ranked symbols with portable handles, exact ranges, provenance, and omissions |
Expand Down Expand Up @@ -93,6 +94,7 @@ git clone https://github.com/lzehrung/codegraph.git
cd codegraph
npm install
npm run build

node ./dist/cli.js doctor
node ./dist/cli.js orient --root . --budget small
```
Expand Down Expand Up @@ -143,6 +145,9 @@ codegraph review --base HEAD --head WORKTREE --summary

# Broader blast-radius map when the summary needs expansion
codegraph impact --base HEAD --head WORKTREE

# Deterministic affected-test paths for focused validation
codegraph affected --base HEAD --head WORKTREE --quiet
```

Use `--head STAGED` to compare `HEAD` with the index, or use refs such as `--base origin/main --head HEAD` for a branch review.
Expand Down
5 changes: 4 additions & 1 deletion codegraph-skill/codegraph/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Use Codegraph when a repository question depends on structure rather than exact

- architecture, hotspots, cycles, unresolved imports, and public API surface
- definitions, references, dependencies, reverse dependencies, and paths
- PR or worktree impact, candidate tests, and risk signals
- PR or worktree impact, candidate tests, affected test lists, and risk signals
- duplicate cleanup and refactor-risk triage
- bounded context for agents through explore, orientation, search, packets, explain, and MCP

Expand All @@ -24,6 +24,7 @@ Bare `codegraph` prints five task-first routes without scanning the project. Use
| Review staged and unstaged work | `codegraph review --base HEAD --head WORKTREE --summary` |
| Review a branch against main | `codegraph review --base origin/main --head HEAD --summary` |
| Map the wider blast radius of a change | `codegraph impact --base HEAD --head WORKTREE` |
| Select deterministic test paths for changed files | `codegraph affected --base HEAD --head WORKTREE --quiet` |
| Answer a concrete question about an unfamiliar repo | `codegraph explore "how does auth reach db?" --root .` |
| Map a repo before you know the question | `codegraph orient --root . --budget small` |
| Diagnose installation, native runtime, or artifact health | `codegraph doctor` |
Expand All @@ -36,6 +37,7 @@ Use `--root` to define the boundary for config lookup, cache scope, path confine

- Positional paths are include roots inside the project boundary for `orient`, `drift`, and positional graph commands.
- `codegraph.config.json` discovery globs are project-root-relative.
- `languages.extensions` maps literal suffixes such as `.tpl` to supported language IDs; longest suffix wins, while `.vue` and `.svelte` cannot be remapped.
- CLI `--include-glob` and `--ignore-glob` values are one-off filters relative to each active scan root.
- Use `--no-gitignore` only when ignored files are intentionally in scope.
- Commands that load the project index first report cache validation as `Checking project index`, then report build or update progress only when index work is required. Warm cache hits complete as `Checked project index` without claiming a rebuild. Use `--progress` for redirected logs or `--no-progress` to suppress feedback, and JSON stdout remains unchanged.
Expand Down Expand Up @@ -88,6 +90,7 @@ Safe shorthand: `impact` and git-backed `drift` default to `HEAD..WORKTREE`; `ar

- compact review handoff: `codegraph review --base HEAD --head WORKTREE --summary`
- broader change impact: `codegraph impact --base HEAD --head WORKTREE`
- affected test paths: `codegraph affected --base HEAD --head WORKTREE --quiet`
- architecture drift: `codegraph drift ./src --base origin/main --head HEAD --graph-edges summary --public-api removals`
- architecture summary: `codegraph inspect ./src --limit 20`
- prioritized cycles: `codegraph cycles --sort priority`
Expand Down
24 changes: 23 additions & 1 deletion docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,18 +51,27 @@ Commands that scan a project read `codegraph.config.json` from `--root` when it
"includeGlobs": ["src/**/*.ts"],
"ignoreGlobs": ["tests/samples/**", "tests/languages/samples/**"],
"useGitignore": true
},
"languages": {
"extensions": {
".tpl": "php",
".inc.php": "php",
".build.ts": "ts"
}
}
}
```

- `discovery.includeGlobs` and `discovery.ignoreGlobs` are project-root-relative, even when a command scans child include roots.
- `discovery.ignoreGlobs` is for large fixture, generated, or vendored folders that should not be indexed.
- `languages.extensions` maps additional or built-in literal suffixes to supported language IDs; keys must start with `.` and may contain letters, digits, `.`, `_`, `+`, and `-`, values must name a supported language, and the longest suffix wins.
- Built-in suffixes remain active unless explicitly remapped by `languages.extensions`; `.vue` and `.svelte` are always handled as single-file components and cannot be remapped.
- CLI `--include-glob` and `--ignore-glob` values are one-off additions relative to each scanned root.
- `inspect` follow-up commands preserve the selected `--root` and include roots.
- `--no-gitignore` overrides `useGitignore`.

Config globs and one-off CLI globs apply at different layers. `codegraph.config.json` globs are durable and project-root-relative. CLI scan-root globs are additive for a single command and are evaluated relative to each active scan root. `--no-gitignore` disables `.gitignore` filtering for that command only; it does not change config.
Cache and manifest reuse is rooted at `--root`. Reusing a project root lets commands share compatible index and graph entries when the file signatures, config, graph options, and relevant build options still match. Changing `--root`, changing discovery config, or changing graph options creates a different reuse boundary. Child include-root scans can reuse project-root cache entries, but command summaries and follow-up commands stay scoped to the selected include roots.
Configured language extensions automatically extend discovery for matching files and participate in cache compatibility checks. Reusing a project root lets commands share compatible index and graph entries when the file signatures, config, graph options, and relevant build options still match. Changing `--root`, changing discovery or language-extension config, or changing graph options creates a different reuse boundary. Child include-root scans can reuse project-root cache entries, but command summaries and follow-up commands stay scoped to the selected include roots.

## Core commands

Expand Down Expand Up @@ -159,6 +168,19 @@ Graph, index, search, inspect, and review reports include `backend.native.byLang
- `uninit` removes only recognized lifecycle state by default and leaves any root `.gitignore` rule in place. It refuses unknown `.codegraph/` entries unless `--force` is passed.
- Lifecycle commands accept either a positional project path or `--root <path>`. They reject using both together because lifecycle manifests and automatic ignore updates always use one resolved project boundary, not include-root subsets.

### Affected tests

- `affected` maps changed source files to likely test files by traversing reverse dependencies through the project graph. It also includes directly changed test files at depth 0.
- Inputs can be positional files, newline-delimited `--stdin`, or a Git range with `--base <ref> --head <ref>`. Paths are normalized under `--root` and output as project-root-relative paths.
- Use `--depth <n>` to expand transitive reverse dependencies, `--filter <glob>` to restrict returned test paths, `--quiet` for path-only output, or `--json` for `schemaVersion: 1`, `changedFiles`, `affectedTests`, and `omittedCounts`.

```bash
codegraph affected src/auth.ts src/db.ts
codegraph affected --stdin --quiet
codegraph affected --base main --head HEAD --json
codegraph affected --base HEAD --head WORKTREE --filter "tests/**/*.test.ts" --quiet
```

### Symbols, navigation, grep, and chunking

```bash
Expand Down
5 changes: 4 additions & 1 deletion docs/library-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ For repeated calls, prefer one warm session instead of rebuilding indexes ad hoc
- `createCodeReviewSession()` for repeated navigation and impact work in library code
- `createAgentSession()` or MCP for repeated orient/search/explain/packet work in agent hosts

CLI commands and agent sessions read `codegraph.config.json` from the project root when it exists. Core indexing APIs keep discovery explicit, so pass `discovery` options directly when you want the same scan scope in custom code:
CLI commands and agent sessions read `codegraph.config.json` from the project root when it exists. Core indexing APIs keep discovery and language mappings explicit, so pass both options directly when you want the same behavior in custom code:

```ts
import { buildProjectIndex, loadCodegraphConfig } from "@lzehrung/codegraph";
Expand All @@ -38,9 +38,12 @@ const config = await loadCodegraphConfig(root);
const index = await buildProjectIndex(root, {
...(config.discovery ? { discovery: config.discovery } : {}),
...(config.graph ? { graph: config.graph } : {}),
...(config.languages?.extensions ? { languageExtensions: config.languages.extensions } : {}),
});
```

`languageExtensions` uses normalized literal suffixes beginning with `.`, supported language IDs, and longest-suffix matching. Suffixes may contain letters, digits, `.`, `_`, `+`, and `-`; `.vue` and `.svelte` remain single-file components and cannot be remapped.

## Public API Boundary

The npm package exposes these supported entry points:
Expand Down
4 changes: 2 additions & 2 deletions docs/plans/2026-07-03-16-config-extension-mapping.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ type CodegraphConfig = {

Rules:

- Extension keys must start with `.`.
- Extension keys must be literal suffixes starting with `.` and containing only letters, digits, `.`, `_`, `+`, or `-`.
- Values must be supported language ids.
- Longer extension keys win first, so `.inc.php` beats `.php`.
- Built-in extensions remain unless explicitly remapped.
- Built-in extensions remain unless explicitly remapped; `.vue` and `.svelte` remain single-file components and cannot be remapped.
- Invalid mappings fail config validation with actionable errors.

## Integration
Expand Down
18 changes: 15 additions & 3 deletions src/agent/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { listProjectFiles, type ProjectFileDiscoveryOptions } from "../util/proj
import { mapLimit } from "../util/concurrency.js";
import { normalizePath, toProjectDisplayPath } from "../util/paths.js";
import { hasDiscoveryOptions, loadCodegraphConfig, mergeDiscoveryOptions, mergeGraphOptions } from "../config.js";
import { languageExtensionPatterns, normalizeLanguageExtensions } from "../languages.js";
import { createAgentFileLookup } from "./normalize.js";
import { summarizeAnalysis, type AnalysisSummary } from "../analysisSummary.js";
import { runSessionInvalidationHooks } from "./sessionLifecycle.js";
Expand Down Expand Up @@ -92,6 +93,7 @@ export type AgentFileSignature = {
type AgentDiscoverySettings = {
discoveryOptions?: ProjectFileDiscoveryOptions;
graphOptions?: BuildOptions["graph"];
languageExtensions?: BuildOptions["languageExtensions"];
};

type AgentSessionFilePlan = AgentDiscoverySettings & {
Expand All @@ -111,24 +113,28 @@ async function resolveAgentDiscoverySettings(options: AgentSessionOptions): Prom
const discovery = mergeDiscoveryOptions(config.discovery, optionDiscovery);
const graph = mergeGraphOptions(config.graph, options.buildOptions?.graph);
const graphOptions = config.graph || options.buildOptions?.graph ? graph : undefined;
const languageExtensions =
normalizeLanguageExtensions(options.buildOptions?.languageExtensions) ?? config.languages?.extensions;
const discoveryOptions = hasDiscoveryOptions(discovery)
? { ...discovery, globRoot: discovery.globRoot ?? options.root }
: undefined;
return {
...(discoveryOptions ? { discoveryOptions } : {}),
...(graphOptions ? { graphOptions } : {}),
...(languageExtensions ? { languageExtensions } : {}),
};
}

async function resolveAgentSessionFilePlan(options: AgentSessionOptions): Promise<AgentSessionFilePlan> {
const { discoveryOptions, graphOptions } = await resolveAgentDiscoverySettings(options);
const { discoveryOptions, graphOptions, languageExtensions } = await resolveAgentDiscoverySettings(options);
// Prefer the manifest-plus-Git reconciliation over a full recursive scan whenever it
// can be trusted. Preserve its changed/untracked evidence so the indexer does not
// repeat the same Git subprocesses immediately afterward.
const incrementalOptions: BuildOptions = {
...options.buildOptions,
...(discoveryOptions ? { discovery: discoveryOptions } : {}),
...(graphOptions ? { graph: graphOptions } : {}),
...(languageExtensions ? { languageExtensions } : {}),
};
const incrementalPlan = await resolveIncrementalFilePlan(options.root, incrementalOptions);
if (incrementalPlan) {
Expand All @@ -137,13 +143,18 @@ async function resolveAgentSessionFilePlan(options: AgentSessionOptions): Promis
incrementalPlan,
...(discoveryOptions ? { discoveryOptions } : {}),
...(graphOptions ? { graphOptions } : {}),
...(languageExtensions ? { languageExtensions } : {}),
};
}
const files = await listProjectFiles(options.root, undefined, discoveryOptions);
const { DEFAULT_PROJECT_PATTERNS } = await import("../util/projectFiles.js");
const customPatterns = languageExtensionPatterns(languageExtensions);
const patterns = customPatterns.length ? [...DEFAULT_PROJECT_PATTERNS, ...customPatterns] : undefined;
const files = await listProjectFiles(options.root, patterns, discoveryOptions);
return {
files,
...(discoveryOptions ? { discoveryOptions } : {}),
...(graphOptions ? { graphOptions } : {}),
...(languageExtensions ? { languageExtensions } : {}),
};
}

Expand Down Expand Up @@ -299,7 +310,7 @@ export function createAgentSession(options: AgentSessionOptions): AgentSession {
const loadBase = async (): Promise<AgentProjectBaseSnapshot> => {
if (cachedBase) return cachedBase;
const loadPromise = (async () => {
const { files, discoveryOptions, graphOptions, incrementalPlan } = await loadFilePlan();
const { files, discoveryOptions, graphOptions, languageExtensions, incrementalPlan } = await loadFilePlan();
const buildOptions: IncrementalBuildOptions = {
...options.buildOptions,
...(graphOptions ? { graph: graphOptions } : {}),
Expand All @@ -315,6 +326,7 @@ export function createAgentSession(options: AgentSessionOptions): AgentSession {
}
: {}),
...(discoveryOptions ? { discovery: discoveryOptions } : {}),
...(languageExtensions ? { languageExtensions } : {}),
};
if (options.buildOptions?.useNativeWorkers === undefined && files.length >= NATIVE_WORKER_AUTO_FILE_THRESHOLD) {
buildOptions.useNativeWorkers = true;
Expand Down
35 changes: 34 additions & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import {
export { isRelativePathInside as isCliDiscoveryRelativePathInside } from "./util/discoveryPath.js";
export const CLI_DISPATCHABLE_COMMANDS = [
"apisurface",
"affected",
"artifact",
"callees",
"callers",
Expand Down Expand Up @@ -301,6 +302,7 @@ async function runCliWithActiveRuntime(rawArgs: string[]) {
return {
...(progressHandler ? { onProgress: progressHandler } : {}),
discovery: discoveryOptions,
...(config.languages?.extensions ? { languageExtensions: config.languages.extensions } : {}),
...(cache !== undefined ? { cache } : {}),
...(hasFlag("--cache-strict") ? { cacheStrict: true } : {}),
...(hasFlag("--cache-verify") ? { cacheVerify: true } : {}),
Expand Down Expand Up @@ -588,7 +590,15 @@ async function runCliWithActiveRuntime(rawArgs: string[]) {

const resolveFilesFromRoots = async (): Promise<string[]> => {
const { listProjectFiles } = await loadProjectFilesHelpers();
const patterns = cmd === "duplicates" ? await getDuplicateProjectPatterns() : undefined;
const basePatterns = cmd === "duplicates" ? await getDuplicateProjectPatterns() : undefined;
const [{ languageExtensionPatterns }, { DEFAULT_PROJECT_PATTERNS }] = await Promise.all([
import("./languages.js"),
loadProjectFilesHelpers(),
]);
const customPatterns = languageExtensionPatterns(config.languages?.extensions);
const patterns = customPatterns.length
? [...(basePatterns ?? DEFAULT_PROJECT_PATTERNS), ...customPatterns]
: basePatterns;
if (!includeRootsAbs.length) {
const diagnosticFiles = await listProjectFiles(projectRootFs, patterns, {
...diagnosticDiscoveryOptions,
Expand Down Expand Up @@ -963,6 +973,7 @@ async function runCliWithActiveRuntime(rawArgs: string[]) {
await handleGraphDeltaCommand({
projectRootFs,
files,
languageExtensions: config.languages?.extensions,
getOpt,
hasFlag,
cwd: getCwd,
Expand Down Expand Up @@ -1022,6 +1033,7 @@ async function runCliWithActiveRuntime(rawArgs: string[]) {
discoveryOptions,
nativeMode,
workerOpts,
languageExtensions: config.languages?.extensions,
progressHandler,
graphOptions: hasGraphOverrides ? buildGraphOptions() : undefined,
reportEnabled,
Expand Down Expand Up @@ -1053,6 +1065,7 @@ async function runCliWithActiveRuntime(rawArgs: string[]) {
onProgress: progressHandler,
discovery: discoveryOptions,
...(nativeMode !== "auto" ? { native: nativeMode } : {}),
...(config.languages?.extensions ? { languageExtensions: config.languages.extensions } : {}),
...workerOpts,
},
writeJSONLine,
Expand Down Expand Up @@ -1184,6 +1197,24 @@ async function runCliWithActiveRuntime(rawArgs: string[]) {
return;
}

if (cmd === "affected") {
const { handleAffectedCommand } = await import("./cli/affected.js");
await handleAffectedCommand({
projectRootFs,
buildOptions: buildAgentOptions(),
positionals: parsed.positionals,
getOpt,
hasFlag,
parsedOptions: parsed.options,
readStdin: readCliStdin,
writeJSONLine,
writeStdoutLine,
writeStderrLine,
exit: exitCli,
});
return;
}

// Review entry point: CLI workflow for review reports.
if (cmd === "review") {
const commandReport: CommandReport | undefined = reportEnabled ? { command: "review", timings: {} } : undefined;
Expand Down Expand Up @@ -1271,6 +1302,7 @@ async function runCliWithActiveRuntime(rawArgs: string[]) {
projectRootFs,
includeRootsAbs,
discoveryOptions,
languageExtensions: config.languages?.extensions,
graphOptions: hasGraphOverrides || nativeMode !== "auto" ? buildGraphOptions() : undefined,
nativeMode,
workerOpts,
Expand All @@ -1294,6 +1326,7 @@ async function runCliWithActiveRuntime(rawArgs: string[]) {
projectRootFs,
includeRootsAbs,
discoveryOptions,
languageExtensions: config.languages?.extensions,
graphOptions: hasGraphOverrides || nativeMode !== "auto" ? buildGraphOptions() : undefined,
nativeMode,
workerOpts,
Expand Down
Loading