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
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,18 @@ This repo keeps test fixtures out of default Codegraph scans with `codegraph.con
{
"discovery": {
"ignoreGlobs": ["tests/samples/**", "tests/languages/samples/**"]
},
"languages": {
"extensions": {
".tpl": "php"
}
}
}
```

Use this pattern in other repos when large fixture, generated, or vendored trees should not participate in search, unresolved-import checks, graphing, indexing, inspect, impact, or review runs. Config globs are project-root-relative. CLI `--include-glob` and `--ignore-glob` stay relative to each active scan root.
Use discovery globs in other repos when large fixture, generated, or vendored trees should not participate in search, unresolved-import checks, graphing, indexing, inspect, impact, or review runs. Config globs are project-root-relative, while CLI `--include-glob` and `--ignore-glob` stay relative to each active scan root.

`languages.extensions` maps additional or built-in literal file suffixes to supported language IDs; longer suffixes win, and built-ins remain active unless explicitly remapped. Suffixes may contain letters, digits, `.`, `_`, `+`, and `-`. The `.vue` and `.svelte` suffixes are always handled as single-file components and cannot be remapped.

## Quick start

Expand Down
3 changes: 2 additions & 1 deletion codegraph-skill/codegraph/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ Fall back to CLI when MCP is unavailable.

## Discovery

Durable repo-local ignores belong in `codegraph.config.json`.
Durable repo-local ignores and custom language suffixes belong in `codegraph.config.json`.
Use `languages.extensions` for literal suffix-to-language mappings such as `.tpl` to `php`; keys start with `.`, may contain letters, digits, `.`, `_`, `+`, and `-`, and values are supported language IDs. The longest suffix wins; `.vue` and `.svelte` are always handled as single-file components and cannot be remapped.
One-off CLI filters use scan-root-relative `--include-glob` and `--ignore-glob`.
Use `--no-gitignore` only when ignored files are intentionally in scope.

Expand Down
11 changes: 10 additions & 1 deletion docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,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
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 @@ -37,9 +37,12 @@ const root = process.cwd();
const config = await loadCodegraphConfig(root);
const index = await buildProjectIndex(root, {
...(config.discovery ? { discovery: config.discovery } : {}),
...(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
20 changes: 15 additions & 5 deletions src/agent/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import type { BuildOptions, BuildReport, ProjectIndex } from "../indexer/types.j
import { buildSymbolGraphDetailed } from "../graphs/symbol-graph-detailed.js";
import { type SymbolGraph } from "../graphs/symbol-graph.js";
import type { Graph } from "../types.js";
import { listProjectFiles, type ProjectFileDiscoveryOptions } from "../util/projectFiles.js";
import { DEFAULT_PROJECT_PATTERNS, listProjectFiles, type ProjectFileDiscoveryOptions } from "../util/projectFiles.js";
import { hasDiscoveryOptions, loadCodegraphConfig, mergeDiscoveryOptions } from "../config.js";
import { languageExtensionPatterns, normalizeLanguageExtensions } from "../languages.js";
import { createAgentFileLookup } from "./normalize.js";
import { summarizeAnalysis, type AnalysisSummary } from "../analysisSummary.js";

Expand Down Expand Up @@ -81,6 +82,7 @@ export type AgentFileSignature = {

type AgentDiscoverySettings = {
discoveryOptions?: ProjectFileDiscoveryOptions;
languageExtensions?: BuildOptions["languageExtensions"];
};

type AgentFreshnessDiff = {
Expand All @@ -96,12 +98,19 @@ async function resolveAgentDiscoverySettings(options: AgentSessionOptions): Prom
const discoveryOptions = hasDiscoveryOptions(discovery)
? { ...discovery, globRoot: discovery.globRoot ?? options.root }
: undefined;
return discoveryOptions ? { discoveryOptions } : {};
const languageExtensions =
normalizeLanguageExtensions(options.buildOptions?.languageExtensions) ?? config.languages?.extensions;
return {
...(discoveryOptions ? { discoveryOptions } : {}),
...(languageExtensions ? { languageExtensions } : {}),
};
}

export async function listAgentSessionFiles(options: AgentSessionOptions): Promise<string[]> {
const { discoveryOptions } = await resolveAgentDiscoverySettings(options);
return await listProjectFiles(options.root, undefined, discoveryOptions);
const { discoveryOptions, languageExtensions } = await resolveAgentDiscoverySettings(options);
const customPatterns = languageExtensionPatterns(languageExtensions);
const patterns = customPatterns.length ? [...DEFAULT_PROJECT_PATTERNS, ...customPatterns] : undefined;
return await listProjectFiles(options.root, patterns, discoveryOptions);
}

function isMissingStatRace(error: unknown): boolean {
Expand Down Expand Up @@ -200,7 +209,7 @@ export function createAgentSession(options: AgentSessionOptions): AgentSession {
const loadBase = async (): Promise<AgentProjectBaseSnapshot> => {
if (cachedBase) return cachedBase;
const loadPromise = (async () => {
const { discoveryOptions } = await resolveAgentDiscoverySettings(options);
const { discoveryOptions, languageExtensions } = await resolveAgentDiscoverySettings(options);
const files = await loadFiles();
cachedFileSignatures = await collectAgentFileSignatures(files);
const buildOptions: BuildOptions & { files: string[] } = {
Expand All @@ -209,6 +218,7 @@ export function createAgentSession(options: AgentSessionOptions): AgentSession {
keepParsed: options.buildOptions?.keepParsed ?? true,
files,
...(discoveryOptions ? { discovery: discoveryOptions } : {}),
...(languageExtensions ? { languageExtensions } : {}),
};
if (
options.buildOptions?.useNativeWorkers === undefined &&
Expand Down
16 changes: 15 additions & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import { handleReviewCommand } from "./cli/review.js";
import { handleSearchCommand } from "./cli/search.js";
import { handleSkillCommand } from "./cli/skill.js";
import { hasDiscoveryOptions, loadCodegraphConfig, mergeDiscoveryOptions } from "./config.js";
import { languageExtensionPatterns } from "./languages.js";
import { listChangedFiles } from "./util/git.js";
import { DEFAULT_PROJECT_PATTERNS, listProjectFiles, type ProjectFileDiscoveryOptions } from "./util/projectFiles.js";
import { normalizePath, resolveFilePathFromRoot, toProjectDisplayPath } from "./util/paths.js";
Expand Down Expand Up @@ -191,6 +192,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 @@ -438,7 +440,11 @@ async function runCliWithActiveRuntime(rawArgs: string[]) {
};

const resolveFilesFromRoots = async (): Promise<string[]> => {
const patterns = cmd === "duplicates" ? DUPLICATE_PROJECT_PATTERNS : undefined;
const basePatterns = cmd === "duplicates" ? DUPLICATE_PROJECT_PATTERNS : undefined;
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 @@ -700,6 +706,7 @@ async function runCliWithActiveRuntime(rawArgs: string[]) {
await handleGraphDeltaCommand({
projectRootFs,
files,
languageExtensions: config.languages?.extensions,
getOpt,
hasFlag,
cwd: getCwd,
Expand Down Expand Up @@ -756,6 +763,7 @@ async function runCliWithActiveRuntime(rawArgs: string[]) {
discoveryOptions,
nativeMode,
workerOpts,
languageExtensions: config.languages?.extensions,
progressHandler,
graphOptions: hasGraphOverrides ? buildGraphOptions() : undefined,
reportEnabled,
Expand Down Expand Up @@ -785,6 +793,7 @@ async function runCliWithActiveRuntime(rawArgs: string[]) {
onProgress: progressHandler,
discovery: discoveryOptions,
...(nativeMode !== "auto" ? { native: nativeMode } : {}),
...(config.languages?.extensions ? { languageExtensions: config.languages.extensions } : {}),
...workerOpts,
},
writeJSONLine,
Expand All @@ -807,6 +816,7 @@ async function runCliWithActiveRuntime(rawArgs: string[]) {
discovery: discoveryOptions,
...(hasGraphOverrides ? { graph: buildGraphOptions() } : {}),
...(nativeMode !== "auto" ? { native: nativeMode } : {}),
...(config.languages?.extensions ? { languageExtensions: config.languages.extensions } : {}),
...workerOpts,
},
writeJSONLine,
Expand Down Expand Up @@ -956,6 +966,7 @@ async function runCliWithActiveRuntime(rawArgs: string[]) {
discovery: discoveryOptions,
...(graphOptions ? { graph: graphOptions } : {}),
...(nativeMode !== "auto" ? { native: nativeMode } : {}),
...(config.languages?.extensions ? { languageExtensions: config.languages.extensions } : {}),
...workerOpts,
});

Expand Down Expand Up @@ -1008,6 +1019,7 @@ async function runCliWithActiveRuntime(rawArgs: string[]) {
projectRootFs,
includeRootsAbs,
discoveryOptions,
languageExtensions: config.languages?.extensions,
graphOptions: hasGraphOverrides || nativeMode !== "auto" ? buildGraphOptions() : undefined,
nativeMode,
workerOpts,
Expand All @@ -1027,6 +1039,7 @@ async function runCliWithActiveRuntime(rawArgs: string[]) {
projectRootFs,
includeRootsAbs,
discoveryOptions,
languageExtensions: config.languages?.extensions,
graphOptions: hasGraphOverrides || nativeMode !== "auto" ? buildGraphOptions() : undefined,
nativeMode,
workerOpts,
Expand Down Expand Up @@ -1058,6 +1071,7 @@ async function runCliWithActiveRuntime(rawArgs: string[]) {
onProgress: progressHandler,
discovery: discoveryOptions,
...(nativeMode !== "auto" ? { native: nativeMode } : {}),
...(config.languages?.extensions ? { languageExtensions: config.languages.extensions } : {}),
...workerOpts,
},
});
Expand Down
2 changes: 2 additions & 0 deletions src/cli/graphDelta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export type GraphDeltaCommandContext = {
nativeMode: NativeRuntimeMode;
workerOpts: { useNativeWorkers: true } | Record<string, never>;
graphOptions: GraphBuildOptions | undefined;
languageExtensions: IncrementalBuildOptions["languageExtensions"];
gitBase: string | undefined;
gitHead: string | undefined;
changedSince: string | undefined;
Expand All @@ -36,6 +37,7 @@ export async function handleGraphDeltaCommand(context: GraphDeltaCommandContext)
incrementalStrict,
files: context.files,
};
if (context.languageExtensions) deltaOptions.languageExtensions = context.languageExtensions;
if (context.nativeMode !== "auto") deltaOptions.native = context.nativeMode;
if (cache !== undefined) deltaOptions.cache = cache;
if (context.gitBase) deltaOptions.gitBase = context.gitBase;
Expand Down
2 changes: 2 additions & 0 deletions src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export type IndexCommandContext = {
discoveryOptions: ProjectFileDiscoveryOptions;
nativeMode: NativeRuntimeMode;
workerOpts: { useNativeWorkers: true } | Record<string, never>;
languageExtensions: BuildOptions["languageExtensions"];
progressHandler: ((update: { current: number; total: number }) => void) | undefined;
graphOptions: GraphBuildOptions | undefined;
reportEnabled: boolean;
Expand Down Expand Up @@ -66,6 +67,7 @@ export async function handleIndexCommand(context: IndexCommandContext): Promise<
threads,
discovery: context.discoveryOptions,
...(context.nativeMode !== "auto" ? { native: context.nativeMode } : {}),
...(context.languageExtensions ? { languageExtensions: context.languageExtensions } : {}),
...context.workerOpts,
...(cache !== undefined ? { cache } : {}),
cacheStrict,
Expand Down
Loading