Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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