Skip to content
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
- For repo-understanding flows, start with `node ./dist/cli.js doctor` and `node ./dist/cli.js orient --root . --budget small --json` when `dist` is built; build first if validating the working tree from a fresh checkout.
- For source-checkout validation and contributor examples, prefer `node ./dist/cli.js ...`; reserve bare `codegraph ...` for published/global install guidance.
- When package metadata, install scripts, optional native dependencies, or the resolved npm graph changes, update `package-lock.json` in the same change and verify with `npm ci --ignore-scripts --dry-run` unless lifecycle scripts are part of the behavior under test.
- Treat `--root` as the project boundary for config lookup, cache/manifests, path confinement, and output normalization. When `--root` is set, positional paths are include roots; for `orient` and `drift`, positional paths are always include roots.
- Treat `--root` as the project boundary for config lookup, path confinement, and output normalization. Cache/manifests may use the resolved cache anchor (`--cache-dir`/`CODEGRAPH_CACHE_DIR`, repository metadata, or project root); cached contents remain project-relative.
- Keep discovery glob guidance accurate: `codegraph.config.json` globs are project-root-relative, while CLI `--include-glob`/`--ignore-glob` values are one-off filters relative to each active scan root.
- Within any claimed cross-language capability, behavior should stay consistent across all supported languages for that capability. Avoid language-subset branches; if a limitation is intentional, document it in the parity docs and cover it with explicit tests in the same change.
- When language support changes, update `docs/language-parity.md` and `docs/scenario-catalog.md` in the same change so support claims, limitations, and fixture coverage stay aligned.
Expand Down
2 changes: 1 addition & 1 deletion codegraph-skill/codegraph/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Prefer `review` before `impact`: review is the compact reviewer handoff; impact

## Keep the Project Boundary Explicit

Use `--root` to define the boundary for config lookup, cache scope, path confinement, and output normalization.
Use `--root` to define the boundary for config lookup, path confinement, and output normalization. Cache contents use project-relative paths and may live at the resolved repository anchor; override location with `--cache-dir` or `CODEGRAPH_CACHE_DIR`.

- 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.
Expand Down
3 changes: 3 additions & 0 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ The `graph` command without output-format flags writes Mermaid to stdout. Use `-
Numeric options such as `--limit`, `--threads`, `--depth`, `--max-refs`, and token bounds must be integers in their documented ranges; invalid numeric values fail instead of being silently clamped or ignored.

Default workflow:
## Cache location

Index caches store project-relative paths, so a cache can be moved with its project. Cache selection precedence is `--cache-dir`, `CODEGRAPH_CACHE_DIR`, `cache.location` in project config (then user config), repository metadata, then the project root. `cache.location` accepts `project`, `repo`, `user`, or an absolute path; `--root` remains the project scope boundary.

- code review: `codegraph review`
- blast-radius follow-up: `codegraph impact --base HEAD --head WORKTREE`
Expand Down
13 changes: 12 additions & 1 deletion src/cli/doctor.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import fs from "node:fs";
import path from "node:path";
import { cacheRoot, resolveCacheAnchor } from "../indexer/build-cache/module-cache.js";
import {
isNativeTreeSitterAvailable,
getNativeBindingOrigin,
Expand Down Expand Up @@ -34,7 +35,6 @@ export type DoctorNativeUpdateReport = {
installedVersion?: string;
reason?: string;
};

export type DoctorReport = {
package: CodegraphPackageIdentity;
native: {
Expand All @@ -44,6 +44,11 @@ export type DoctorReport = {
origin?: DoctorNativeOriginReport;
update?: DoctorNativeUpdateReport;
};
cache: {
path: string;
anchor: string;
layer: string;
};
indexArtifact?: IndexedArtifactReport;
};

Expand Down Expand Up @@ -172,8 +177,14 @@ export function buildDoctorReport(indexPath?: string): DoctorReport {
const origin = getNativeBindingOrigin();
const runtimeIdentity = captureCodegraphRuntimeIdentity(origin);
const update = createInstalledVersionChecker(runtimeIdentity, { warn: () => undefined }).check(true);
const cacheResolution = resolveCacheAnchor(process.cwd());
return {
package: packageIdentity,
cache: {
path: normalizePathForDisplay(cacheRoot(process.cwd())),
anchor: normalizePathForDisplay(cacheResolution.anchor),
layer: cacheResolution.layer,
},
native: {
available: isNativeTreeSitterAvailable(),
...(loadError ? { loadError: String(loadError) } : {}),
Expand Down
5 changes: 3 additions & 2 deletions src/cli/help.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ Build Options:
--native <mode> Native runtime mode: auto, on, off
--workers Force Piscina native-extraction workers (auto above 250 files)
--cache <mode> Cache mode: disk, memory, off
--cache-dir <path> Cache location override (also CODEGRAPH_CACHE_DIR)
--limit N Result limit for hotspots/inspect summaries
--cache-strict Force strict content-hash cache validation
--cache-verify Re-stat cached files before trusting disk cache entries
Expand Down Expand Up @@ -178,9 +179,9 @@ Usage: codegraph uninstall [target] [--target <codex,claude,cursor,gemini,openco
Safety:
Removes only Codegraph-owned marker blocks, marker files, exact bundled skill payloads, or exact installer-owned MCP entries.
`;

const SHARED_INDEX_OPTIONS_HELP = `Index options:
Supports shared --cache, --cache-strict, --cache-verify, --threads, --native, --workers, --include-glob, --ignore-glob, and --no-gitignore options.
Supports shared --cache, --cache-dir, --cache-strict, --cache-verify, --threads, --native, --workers, --include-glob, --ignore-glob, and --no-gitignore options.
Cache precedence is --cache-dir, then CODEGRAPH_CACHE_DIR, then cache.location from project/user config, then repository metadata, then the project root. Use cache.location "project", "repo", "user", or an absolute path.
Index builds report progress automatically on an interactive stderr terminal. Use --progress to force redirected progress logs or --no-progress to suppress feedback.`;

export const EXPLORE_HELP_TEXT = `codegraph explore - Answer a broad repo question with bounded repo context
Expand Down
3 changes: 2 additions & 1 deletion src/cli/inspect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
getNativeTreeSitterSupportedLanguageIds,
isNativeTreeSitterAvailable,
} from "../native/treeSitterNative.js";
import { cacheRoot } from "../indexer/build-cache/module-cache.js";
import type { NativeRuntimeMode } from "../native/treeSitterNative.js";
import type { Graph } from "../types.js";
import { restrictGraphToIncludeRoots } from "../util/includeRoots.js";
Expand Down Expand Up @@ -118,7 +119,7 @@ export type InspectCommandContext = {
};

function defaultCacheIndexPath(projectRoot: string): string {
return path.join(projectRoot, ".codegraph-cache", "index-v1");
return cacheRoot(projectRoot, { cache: "disk" });
}

function defaultCacheManifestPath(projectRoot: string): string {
Expand Down
3 changes: 3 additions & 0 deletions src/cli/invocationContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -450,11 +450,14 @@ export async function loadCliProjectContext(base: CliBaseContext): Promise<CliPr
const buildAgentOptions = (): BuildOptions => {
const cache = parseCacheModeOption(getOpt("--cache"));
const threads = parseOptionalNonNegativeIntegerOption(getOpt("--threads"), "--threads");
const cacheDir = getOpt("--cache-dir");
return {
...(base.progressHandler ? { onProgress: base.progressHandler } : {}),
discovery: discoveryOptions,
...(config.languages?.extensions ? { languageExtensions: config.languages.extensions } : {}),
...(cache !== undefined ? { cache } : {}),
...(cacheDir ? { cacheDir } : {}),
...(config.cache?.location ? { cacheLocation: config.cache.location } : {}),
...(hasFlag("--cache-strict") ? { cacheStrict: true } : {}),
...(hasFlag("--cache-verify") ? { cacheVerify: true } : {}),
...(hasGraphOverrides || base.nativeMode !== "auto" ? { graph: base.buildGraphOptions() } : {}),
Expand Down
6 changes: 5 additions & 1 deletion src/cli/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const CLI_VALUE_OPTIONS = new Set<string>([
"--threads",
"--native",
"--cache",
"--cache-dir",
"--changed-since",
"--git-base",
"--git-head",
Expand Down Expand Up @@ -101,7 +102,9 @@ const CLI_VALUE_OPTIONS = new Set<string>([
]);

type CliPositionalPolicy =
{ kind: "any" } | { kind: "max"; max: number; usage: string } | { kind: "none"; usage: string };
| { kind: "any" }
| { kind: "max"; max: number; usage: string }
| { kind: "none"; usage: string };

type CliCommandSchema = {
flags?: readonly string[];
Expand All @@ -125,6 +128,7 @@ const SHARED_BUILD_OPTIONS = [
"--threads",
"--native",
"--cache",
"--cache-dir",
"--include-glob",
"--ignore-glob",
"--resolution-hint",
Expand Down
34 changes: 29 additions & 5 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import fsp from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { z } from "zod";
import {
Expand Down Expand Up @@ -41,12 +42,20 @@ const codegraphConfigSchema = z
})
.strict()
.optional(),
cache: z
.object({
location: z.string().trim().min(1),
})
.optional(),
})
.strict();

type ParsedCodegraphConfig = z.infer<typeof codegraphConfigSchema>;

export type CodegraphConfig = {
cache?: {
location: string;
};
discovery?: ProjectFileDiscoveryOptions;
languages?: {
extensions?: LanguageExtensionMap;
Expand Down Expand Up @@ -147,27 +156,41 @@ function normalizeConfigLanguageExtensions(
}
return normalizeLanguageExtensions(extensions);
}
async function loadUserCacheLocation(): Promise<string | undefined> {
const configRoot =
process.platform === "win32"
? process.env.APPDATA?.trim() || path.join(os.homedir(), "AppData", "Roaming")
: process.env.XDG_CONFIG_HOME?.trim() || path.join(os.homedir(), ".config");
const configPath = path.join(configRoot, "codegraph", "config.json");
try {
const parsedJson = JSON.parse(await fsp.readFile(configPath, "utf8")) as unknown;
const parsed = codegraphConfigSchema.safeParse(parsedJson);
if (!parsed.success) throw new Error(z.prettifyError(parsed.error));
return parsed.data.cache?.location;
} catch (error) {
if (error instanceof Error && "code" in error && error.code === "ENOENT") return undefined;
throw new Error(`Invalid user codegraph config: ${errorMessage(error)}`);
}
}

export async function loadCodegraphConfig(projectRoot: string): Promise<CodegraphConfig> {
const userCacheLocation = await loadUserCacheLocation();
const configPath = path.join(projectRoot, CODEGRAPH_CONFIG_FILE);
let raw: string;
try {
raw = await fsp.readFile(configPath, "utf8");
} catch (error) {
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
return {};
return userCacheLocation ? { cache: { location: userCacheLocation } } : {};
}
throw error;
}

let parsedJson: unknown;
try {
parsedJson = JSON.parse(raw);
} catch (error) {
const message = errorMessage(error);
throw new Error(`Invalid ${CODEGRAPH_CONFIG_FILE}: ${message}`);
throw new Error(`Invalid ${CODEGRAPH_CONFIG_FILE}: ${errorMessage(error)}`);
}

const parsed = codegraphConfigSchema.safeParse(parsedJson);
if (!parsed.success) {
throw new Error(`Invalid ${CODEGRAPH_CONFIG_FILE}: ${z.prettifyError(parsed.error)}`);
Expand All @@ -177,6 +200,7 @@ export async function loadCodegraphConfig(projectRoot: string): Promise<Codegrap
const resolutionHints = normalizeResolutionHints(parsed.data.graph?.resolutionHints);
const graph = resolutionHints.length ? { resolutionHints } : undefined;
return {
cache: { location: parsed.data.cache?.location ?? userCacheLocation ?? "project" },
...(discovery ? { discovery } : {}),
...(graph ? { graph } : {}),
...(languageExtensions ? { languages: { extensions: languageExtensions } } : {}),
Expand Down
Loading
Loading