@lzehrung/codegraph 2.3.6 → 2.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -7
- package/codegraph-skill/codegraph/SKILL.md +1 -0
- package/dist/bin/cli.js +164 -35
- package/dist/indexer/build-index.d.ts.map +1 -1
- package/dist/indexer/build-index.js +77 -16
- package/dist/indexer/build-index.js.map +1 -1
- package/dist/indexer/finalize.d.ts.map +1 -1
- package/dist/indexer/finalize.js +7 -2
- package/dist/indexer/finalize.js.map +1 -1
- package/dist/indexer/types.d.ts +2 -0
- package/dist/indexer/types.d.ts.map +1 -1
- package/dist/lifecycle/manifest.d.ts.map +1 -1
- package/dist/lifecycle/manifest.js +51 -1
- package/dist/lifecycle/manifest.js.map +1 -1
- package/dist/util/git.d.ts +13 -0
- package/dist/util/git.d.ts.map +1 -1
- package/dist/util/git.js +37 -0
- package/dist/util/git.js.map +1 -1
- package/dist/util/projectFiles.d.ts +6 -3
- package/dist/util/projectFiles.d.ts.map +1 -1
- package/dist/util/projectFiles.js +14 -27
- package/dist/util/projectFiles.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -18,8 +18,6 @@ codegraph is a local CLI **and TypeScript library** that turns a source tree int
|
|
|
18
18
|
|
|
19
19
|
Without structural context, an agent burns early turns listing directories, guessing search terms, opening candidate files, and reconstructing relationships. codegraph does that discovery once so the context window can stay focused on the problem.
|
|
20
20
|
|
|
21
|
-
Warm-cache timings for `codegraph orient --root . --budget small --json` on this repository have ranged from about **0.6s** (Windows 11, Node 24) to about **2.5s** (a 4-core Linux container, Node 22), so treat them as machine-dependent rather than a headline figure; the matching MCP `orient` call returned in about **100ms** on the faster of the two. [The performance program index](docs/plans/2026-07-25-performance-program-index.md) records the current measurements and the conditions behind them.
|
|
22
|
-
|
|
23
21
|
With Node.js 22.16 or newer, install from npm:
|
|
24
22
|
|
|
25
23
|
```bash
|
|
@@ -132,13 +130,16 @@ Do not begin by generating every possible report. Start with the question you ac
|
|
|
132
130
|
### Understand an unfamiliar repo
|
|
133
131
|
|
|
134
132
|
```bash
|
|
135
|
-
#
|
|
136
|
-
codegraph explore "how does the CLI reach review analysis?" --root .
|
|
137
|
-
|
|
138
|
-
# If you do not know the question yet, get a bounded map
|
|
133
|
+
# Map the repository before selecting a target
|
|
139
134
|
codegraph orient --root . --budget small
|
|
140
135
|
|
|
141
|
-
#
|
|
136
|
+
# Search a concrete term
|
|
137
|
+
codegraph search "review analysis CLI" --json
|
|
138
|
+
|
|
139
|
+
# Use explore only when those results need packets and dependency paths
|
|
140
|
+
codegraph explore "review analysis CLI" --root .
|
|
141
|
+
|
|
142
|
+
# Follow a returned target
|
|
142
143
|
codegraph explain src/review.ts
|
|
143
144
|
codegraph deps src/review.ts --json
|
|
144
145
|
codegraph refs src/review.ts:215:23
|
|
@@ -131,6 +131,7 @@ Current-state commands validate the on-disk index automatically and default to t
|
|
|
131
131
|
- Repeated agent queries over one repo snapshot: prefer MCP so the index stays warm. MCP inputs are flat JSON objects and the server root is fixed at startup; send only fields in the mounted tool schema, never CLI flags such as `--root` or `--json`. Unknown tool fields are rejected as invalid parameters. If the first MCP call fails at startup or loses its transport, do not retry the same server; run `codegraph doctor`, use the equivalent CLI command for this session, and restart the agent client after package upgrades.
|
|
132
132
|
- Durable graph handoff: use `codegraph graph --root . ./src --json --output codegraph.json` rather than parsing display text. `graph --json` and `index --json` payloads include an `analysis` object (`mode`, `backend`, `label`, fallback counts); when `mode` is `mixed` or `reduced` the run used regex/graph-only extraction for some or all files, so treat symbol accuracy accordingly. Degraded runs also print a `Backend:` warning on stderr even without `--progress`.
|
|
133
133
|
- Search and inspect performance diagnosis: add `--report` for JSON on stderr or `--report-file <path>` for a file while keeping normal command output unchanged.
|
|
134
|
+
- Index discovery diagnosis: `--report` timing JSON can include `sourceDiscoveryMs` and `metadataDiscoveryMs` when those discovery steps run.
|
|
134
135
|
|
|
135
136
|
### Human Graph Viewer
|
|
136
137
|
|
package/dist/bin/cli.js
CHANGED
|
@@ -13534,6 +13534,7 @@ __export(git_exports, {
|
|
|
13534
13534
|
isGitWorktreeSentinel: () => isGitWorktreeSentinel,
|
|
13535
13535
|
listChangedFiles: () => listChangedFiles,
|
|
13536
13536
|
listGitExcludeFiles: () => listGitExcludeFiles,
|
|
13537
|
+
listGitIgnoreFiles: () => listGitIgnoreFiles,
|
|
13537
13538
|
listGitSubmoduleDirectories: () => listGitSubmoduleDirectories,
|
|
13538
13539
|
listTrackedFiles: () => listTrackedFiles,
|
|
13539
13540
|
listUntrackedFiles: () => listUntrackedFiles,
|
|
@@ -13969,6 +13970,32 @@ async function listTrackedFiles(projectRoot, opts) {
|
|
|
13969
13970
|
throw createGitError(projectRoot, args, error51);
|
|
13970
13971
|
}
|
|
13971
13972
|
}
|
|
13973
|
+
async function listGitIgnoreFiles(projectRoot, opts) {
|
|
13974
|
+
if (!(opts?.gitAvailable ?? true))
|
|
13975
|
+
return [];
|
|
13976
|
+
const pathspec = [".gitignore", ":(glob)**/.gitignore"];
|
|
13977
|
+
const commands = [
|
|
13978
|
+
["ls-files", "--cached", "-z", "--", ...pathspec],
|
|
13979
|
+
["ls-files", "--others", "--exclude-standard", "-z", "--", ...pathspec],
|
|
13980
|
+
["ls-files", "--others", "--ignored", "--exclude-standard", "-z", "--", ...pathspec]
|
|
13981
|
+
];
|
|
13982
|
+
const results = await Promise.all(commands.map(async (args) => {
|
|
13983
|
+
try {
|
|
13984
|
+
return await runGit(projectRoot, args, { maxBuffer: 64 * 1024 * 1024 });
|
|
13985
|
+
} catch (error51) {
|
|
13986
|
+
throw createGitError(projectRoot, args, error51);
|
|
13987
|
+
}
|
|
13988
|
+
}));
|
|
13989
|
+
const out = [];
|
|
13990
|
+
for (const { stdout } of results) {
|
|
13991
|
+
for (const rel of stdout.split("\0").filter(Boolean)) {
|
|
13992
|
+
const abs = resolveGitListedPath(projectRoot, rel);
|
|
13993
|
+
if (abs)
|
|
13994
|
+
out.push(abs);
|
|
13995
|
+
}
|
|
13996
|
+
}
|
|
13997
|
+
return Array.from(new Set(out));
|
|
13998
|
+
}
|
|
13972
13999
|
async function listGitSubmoduleDirectories(projectRoot, opts) {
|
|
13973
14000
|
if (!(opts?.gitAvailable ?? true))
|
|
13974
14001
|
return [];
|
|
@@ -14380,24 +14407,24 @@ function collectCandidateAncestorDirectories(root, files) {
|
|
|
14380
14407
|
}
|
|
14381
14408
|
return directories;
|
|
14382
14409
|
}
|
|
14383
|
-
async function findGitIgnoreSources(sourceRoots
|
|
14410
|
+
async function findGitIgnoreSources(sourceRoots) {
|
|
14384
14411
|
const roots = Array.from(new Map(sourceRoots.map(({ path: sourceRoot, repositoryRoot }) => [
|
|
14385
14412
|
fileIdentityKey(sourceRoot),
|
|
14386
14413
|
{ path: normalizePath(sourceRoot), repositoryRoot: normalizePath(repositoryRoot) }
|
|
14387
14414
|
])).values());
|
|
14388
14415
|
const sources = [];
|
|
14389
14416
|
for (const { path: sourceRoot, repositoryRoot } of roots) {
|
|
14390
|
-
const
|
|
14391
|
-
|
|
14392
|
-
|
|
14393
|
-
|
|
14394
|
-
|
|
14395
|
-
|
|
14396
|
-
|
|
14397
|
-
|
|
14398
|
-
|
|
14399
|
-
|
|
14400
|
-
sources.push(...
|
|
14417
|
+
const [gitignoreFiles, excludeFiles] = await Promise.all([
|
|
14418
|
+
listGitIgnoreFiles(sourceRoot),
|
|
14419
|
+
listGitExcludeFiles(sourceRoot)
|
|
14420
|
+
]);
|
|
14421
|
+
sources.push(...gitignoreFiles.map((file2) => ({
|
|
14422
|
+
file: file2,
|
|
14423
|
+
baseDir: path9.dirname(file2),
|
|
14424
|
+
repositoryRoot,
|
|
14425
|
+
priority: 2
|
|
14426
|
+
})));
|
|
14427
|
+
sources.push(...excludeFiles.map((source) => ({
|
|
14401
14428
|
...source,
|
|
14402
14429
|
repositoryRoot: source.baseDir
|
|
14403
14430
|
})));
|
|
@@ -14428,13 +14455,6 @@ async function listGitCandidateFiles(root, logLevel) {
|
|
|
14428
14455
|
const rawFiles = Array.from(/* @__PURE__ */ new Set([...tracked, ...untracked, ...submoduleUntracked.flat()])).sort();
|
|
14429
14456
|
const needsLogicalRemap = normalizePath(gitRoot) !== normalizePath(root);
|
|
14430
14457
|
const files = Array.from(new Set(rawFiles.map((file2) => needsLogicalRemap && isFilePathWithinRoot(realRoot, file2) ? normalizePath(path9.resolve(root, path9.relative(realRoot, file2))) : normalizePath(file2)))).sort();
|
|
14431
|
-
const physicalFiles = normalizePath(root) === realRoot ? [] : (await mapLimitSemaphore(files, REALPATH_FILTER_CONCURRENCY, async (file2) => {
|
|
14432
|
-
try {
|
|
14433
|
-
return normalizePath(await fsp5.realpath(file2));
|
|
14434
|
-
} catch {
|
|
14435
|
-
return null;
|
|
14436
|
-
}
|
|
14437
|
-
})).filter((file2) => file2 !== null);
|
|
14438
14458
|
const gitignoreRoots = [gitRepositoryRoot, ...submoduleRoots.map(({ physicalPath }) => physicalPath)].map(normalizePath);
|
|
14439
14459
|
const sourceRoots = [
|
|
14440
14460
|
{ path: root, repositoryRoot: gitRepositoryRoot },
|
|
@@ -14447,7 +14467,7 @@ async function listGitCandidateFiles(root, logLevel) {
|
|
|
14447
14467
|
];
|
|
14448
14468
|
return {
|
|
14449
14469
|
files,
|
|
14450
|
-
gitignoreFiles: await findGitIgnoreSources(sourceRoots
|
|
14470
|
+
gitignoreFiles: await findGitIgnoreSources(sourceRoots),
|
|
14451
14471
|
gitignoreRoots,
|
|
14452
14472
|
gitignoreAliases: sourceRoots
|
|
14453
14473
|
};
|
|
@@ -53074,12 +53094,17 @@ var init_parsed_cache = __esm({
|
|
|
53074
53094
|
// dist/indexer/finalize.js
|
|
53075
53095
|
import { performance as performance2 } from "node:perf_hooks";
|
|
53076
53096
|
async function finalizeProjectIndex(args) {
|
|
53077
|
-
|
|
53078
|
-
|
|
53097
|
+
const discoveryTimings = args.opts?.report ? args.opts.report.timings ??= {} : void 0;
|
|
53098
|
+
const metadataDiscoveryStart = args.projectFiles === void 0 ? performance2.now() : void 0;
|
|
53079
53099
|
const projectFiles = await (args.projectFiles ?? discoverProjectFilesWithGitCandidates(args.projectRoot, {
|
|
53080
53100
|
...args.opts?.logLevel ? { logLevel: args.opts.logLevel } : {},
|
|
53081
53101
|
...args.knownGitCandidates !== void 0 ? { knownGitCandidates: args.knownGitCandidates } : {}
|
|
53082
53102
|
}));
|
|
53103
|
+
if (metadataDiscoveryStart !== void 0 && discoveryTimings) {
|
|
53104
|
+
discoveryTimings.metadataDiscoveryMs = Math.round(performance2.now() - metadataDiscoveryStart);
|
|
53105
|
+
}
|
|
53106
|
+
if (args.timings)
|
|
53107
|
+
args.timings.totalMs = Math.round(performance2.now() - args.totalStart);
|
|
53083
53108
|
const languageExtensions = normalizeLanguageExtensions(args.opts?.languageExtensions);
|
|
53084
53109
|
const parsed = retainedParsedCache(args.parsedMap, args.opts);
|
|
53085
53110
|
return {
|
|
@@ -55698,6 +55723,7 @@ async function buildIndexFromFileListShared(projectRoot, rawFiles, opts, helperO
|
|
|
55698
55723
|
}
|
|
55699
55724
|
}
|
|
55700
55725
|
async function buildProjectIndexWithManifestOptions(projectRoot, opts, helperOpts) {
|
|
55726
|
+
const timings = opts?.report ? opts.report.timings ??= {} : void 0;
|
|
55701
55727
|
await initializeFileIdentityCaseSensitivity(projectRoot);
|
|
55702
55728
|
try {
|
|
55703
55729
|
const useDiskCache = (opts?.cache ?? "off") === "disk";
|
|
@@ -55717,6 +55743,7 @@ async function buildProjectIndexWithManifestOptions(projectRoot, opts, helperOpt
|
|
|
55717
55743
|
const onDiscoveryProgress = helperOpts?.reportDiscoveryProgress ? (progress) => emitIndexCheckActivity(opts, progress.activity, progress.current, progress.total) : void 0;
|
|
55718
55744
|
if (helperOpts?.reportDiscoveryProgress)
|
|
55719
55745
|
emitIndexCheckActivity(opts, "Discovering source files");
|
|
55746
|
+
const sourceDiscoveryStart = performance5.now();
|
|
55720
55747
|
const discoveredFiles = await listProjectFilesWithGitCandidates(projectRoot, projectPatternsForLanguageExtensions(opts), {
|
|
55721
55748
|
...opts?.discovery,
|
|
55722
55749
|
...opts?.logLevel ? { logLevel: opts.logLevel } : {},
|
|
@@ -55725,6 +55752,8 @@ async function buildProjectIndexWithManifestOptions(projectRoot, opts, helperOpt
|
|
|
55725
55752
|
onGitCandidatesDiscovered,
|
|
55726
55753
|
...onDiscoveryProgress ? { onDiscoveryProgress } : {}
|
|
55727
55754
|
});
|
|
55755
|
+
if (timings)
|
|
55756
|
+
timings.sourceDiscoveryMs = Math.round(performance5.now() - sourceDiscoveryStart);
|
|
55728
55757
|
const additionalFileCandidates = await normalizeIndexedFileInputsWithinRoot(projectRoot, opts?.additionalFiles ?? [], "Additional index file");
|
|
55729
55758
|
const additionalFileExistence = await probePathExistence(additionalFileCandidates, buildConcurrency(opts));
|
|
55730
55759
|
const additionalFiles = additionalFileCandidates.filter((file2) => additionalFileExistence.get(file2));
|
|
@@ -55733,12 +55762,15 @@ async function buildProjectIndexWithManifestOptions(projectRoot, opts, helperOpt
|
|
|
55733
55762
|
const transientFiles = additionalFiles.filter((file2) => !discoveredFileSet.has(file2));
|
|
55734
55763
|
if (helperOpts?.reportDiscoveryProgress)
|
|
55735
55764
|
emitIndexCheckActivity(opts, "Discovering project metadata");
|
|
55765
|
+
const metadataDiscoveryStart = performance5.now();
|
|
55736
55766
|
const projectFiles = await discoverProjectFilesWithGitCandidates(projectRoot, {
|
|
55737
55767
|
...opts?.logLevel ? { logLevel: opts.logLevel } : {},
|
|
55738
55768
|
...discoveredSymlinkDirectories !== void 0 ? { knownSymlinkDirectories: discoveredSymlinkDirectories } : {},
|
|
55739
55769
|
...discoveredGitCandidates !== void 0 ? { knownGitCandidates: discoveredGitCandidates } : {},
|
|
55740
55770
|
...onDiscoveryProgress ? { onDiscoveryProgress } : {}
|
|
55741
55771
|
});
|
|
55772
|
+
if (timings)
|
|
55773
|
+
timings.metadataDiscoveryMs = Math.round(performance5.now() - metadataDiscoveryStart);
|
|
55742
55774
|
return await buildIndexFromFileListShared(projectRoot, files, opts, {
|
|
55743
55775
|
manifestMode: useDiskCache ? "read-write" : "off",
|
|
55744
55776
|
warnNoFilesMessage: `Warning: No files found in project root: ${projectRoot}. Check codegraph.config.json globs and CLI --include-glob/--ignore-glob filters. Diagnostic: codegraph doctor`,
|
|
@@ -55794,6 +55826,7 @@ async function buildProjectIndexIncremental(projectRoot, opts) {
|
|
|
55794
55826
|
if (strictIncremental && graphOptions.fast)
|
|
55795
55827
|
graphOptions.fast = false;
|
|
55796
55828
|
const { normalizedProjectRoot, report, timings, totalStart, cacheMode, cacheEnabled, onFallbackImportExtraction } = createIndexBuildRunState(projectRoot, opts, graphOptions);
|
|
55829
|
+
const discoveryTimings = opts?.report ? opts.report.timings ??= {} : void 0;
|
|
55797
55830
|
let checkProgressActive = false;
|
|
55798
55831
|
const startCheckProgress = () => {
|
|
55799
55832
|
if (checkProgressActive)
|
|
@@ -55886,13 +55919,31 @@ async function buildProjectIndexIncremental(projectRoot, opts) {
|
|
|
55886
55919
|
const hasExplicitGitRange = !!opts?.gitBase || !!opts?.gitHead;
|
|
55887
55920
|
const shouldDiffAgainstWorkingTree = !hasExplicitGitRange && gitAvailable && !!manifest.lastCommit;
|
|
55888
55921
|
const canReuseReconciliation = opts?.reconciledManifestUpdatedAt === manifest.updatedAt;
|
|
55922
|
+
let sourceDiscoveryMs = 0;
|
|
55923
|
+
let sourceDiscoveryPerformed = false;
|
|
55924
|
+
const measureSourceDiscovery = async (operation) => {
|
|
55925
|
+
if (!discoveryTimings || opts?.filesAreProjectScope)
|
|
55926
|
+
return await operation();
|
|
55927
|
+
sourceDiscoveryPerformed = true;
|
|
55928
|
+
const start = performance5.now();
|
|
55929
|
+
try {
|
|
55930
|
+
return await operation();
|
|
55931
|
+
} finally {
|
|
55932
|
+
sourceDiscoveryMs += performance5.now() - start;
|
|
55933
|
+
}
|
|
55934
|
+
};
|
|
55889
55935
|
let manifestDiffFiles = [];
|
|
55890
55936
|
if (shouldDiffAgainstWorkingTree) {
|
|
55891
55937
|
try {
|
|
55892
|
-
|
|
55893
|
-
|
|
55894
|
-
|
|
55895
|
-
}
|
|
55938
|
+
const reconciledWorkingTreeDiffFiles = canReuseReconciliation ? opts?.reconciledWorkingTreeDiffFiles : void 0;
|
|
55939
|
+
if (reconciledWorkingTreeDiffFiles !== void 0) {
|
|
55940
|
+
manifestDiffFiles = reconciledWorkingTreeDiffFiles;
|
|
55941
|
+
} else {
|
|
55942
|
+
manifestDiffFiles = await measureSourceDiscovery(() => listChangedFiles(projectRoot, {
|
|
55943
|
+
base: manifest.lastCommit,
|
|
55944
|
+
head: "WORKTREE"
|
|
55945
|
+
}));
|
|
55946
|
+
}
|
|
55896
55947
|
} catch (error51) {
|
|
55897
55948
|
if (!isMissingGitRevisionError(error51))
|
|
55898
55949
|
throw error51;
|
|
@@ -55937,13 +55988,21 @@ async function buildProjectIndexIncremental(projectRoot, opts) {
|
|
|
55937
55988
|
const previousTransientFiles = sanitizeManifestTransientFilesForRoot(projectRoot, projectRoot, manifest.transientFiles).filter((file2) => Object.hasOwn(trackedEntries, file2));
|
|
55938
55989
|
const previousTransientFileSet = new Set(previousTransientFiles);
|
|
55939
55990
|
const needsGitScan = !!opts?.gitBase || !!opts?.changedSince;
|
|
55940
|
-
|
|
55991
|
+
let gitFiles = [];
|
|
55992
|
+
if (needsGitScan) {
|
|
55993
|
+
gitFiles = await measureSourceDiscovery(() => listChangedFiles(projectRoot, buildIncrementalGitDiffOptions(opts)));
|
|
55994
|
+
}
|
|
55941
55995
|
let untrackedFiles = [];
|
|
55942
55996
|
let rediscoveredFiles = [];
|
|
55943
55997
|
let discoveredGitCandidates;
|
|
55944
55998
|
if (canUseIncrementalDiscoveryFastPath(gitAvailable, opts?.cacheStrict)) {
|
|
55945
55999
|
try {
|
|
55946
|
-
|
|
56000
|
+
const reconciledUntrackedFiles = canReuseReconciliation ? opts?.reconciledUntrackedFiles : void 0;
|
|
56001
|
+
if (reconciledUntrackedFiles !== void 0) {
|
|
56002
|
+
untrackedFiles = reconciledUntrackedFiles;
|
|
56003
|
+
} else {
|
|
56004
|
+
untrackedFiles = await measureSourceDiscovery(() => listUntrackedProjectFiles(projectRoot, opts?.discovery, gitAvailable));
|
|
56005
|
+
}
|
|
55947
56006
|
} catch (error51) {
|
|
55948
56007
|
if (manifestReport) {
|
|
55949
56008
|
manifestReport.reason = "gitUntrackedScanFailed";
|
|
@@ -55956,14 +56015,17 @@ async function buildProjectIndexIncremental(projectRoot, opts) {
|
|
|
55956
56015
|
});
|
|
55957
56016
|
}
|
|
55958
56017
|
} else if (!opts?.filesAreProjectScope) {
|
|
55959
|
-
rediscoveredFiles = await listProjectFilesWithGitCandidates(projectRoot, projectPatternsForLanguageExtensions(opts), {
|
|
56018
|
+
rediscoveredFiles = await measureSourceDiscovery(() => listProjectFilesWithGitCandidates(projectRoot, projectPatternsForLanguageExtensions(opts), {
|
|
55960
56019
|
...opts?.discovery,
|
|
55961
56020
|
...opts?.logLevel ? { logLevel: opts.logLevel } : {},
|
|
55962
56021
|
...!opts?.cacheStrict && manifest.symlinkDirectories !== void 0 ? { knownSymlinkDirectories: manifest.symlinkDirectories } : {},
|
|
55963
56022
|
onGitCandidatesDiscovered: (candidates) => {
|
|
55964
56023
|
discoveredGitCandidates = candidates;
|
|
55965
56024
|
}
|
|
55966
|
-
});
|
|
56025
|
+
}));
|
|
56026
|
+
}
|
|
56027
|
+
if (discoveryTimings && sourceDiscoveryPerformed) {
|
|
56028
|
+
discoveryTimings.sourceDiscoveryMs = Math.round(sourceDiscoveryMs);
|
|
55967
56029
|
}
|
|
55968
56030
|
const candidateFiles = [
|
|
55969
56031
|
...explicitFiles,
|
|
@@ -56062,10 +56124,16 @@ async function buildProjectIndexIncremental(projectRoot, opts) {
|
|
|
56062
56124
|
if (!snapshotLoad)
|
|
56063
56125
|
return null;
|
|
56064
56126
|
const snapshot = snapshotLoad.index;
|
|
56065
|
-
snapshot.
|
|
56066
|
-
|
|
56067
|
-
|
|
56068
|
-
|
|
56127
|
+
snapshot.manifestSignaturesFresh = false;
|
|
56128
|
+
if (snapshot.projectFiles === void 0) {
|
|
56129
|
+
const metadataDiscoveryStart = performance5.now();
|
|
56130
|
+
snapshot.projectFiles = await discoverProjectFilesWithGitCandidates(projectRoot, {
|
|
56131
|
+
...opts?.logLevel ? { logLevel: opts.logLevel } : {},
|
|
56132
|
+
...discoveredGitCandidates !== void 0 ? { knownGitCandidates: discoveredGitCandidates } : {}
|
|
56133
|
+
});
|
|
56134
|
+
if (discoveryTimings)
|
|
56135
|
+
discoveryTimings.metadataDiscoveryMs = Math.round(performance5.now() - metadataDiscoveryStart);
|
|
56136
|
+
}
|
|
56069
56137
|
if (opts?.cache) {
|
|
56070
56138
|
snapshot.cacheMode = opts.cache;
|
|
56071
56139
|
snapshot.cacheRootDir = cacheRoot(projectRoot, opts);
|
|
@@ -56350,6 +56418,17 @@ async function buildProjectIndexIncremental(projectRoot, opts) {
|
|
|
56350
56418
|
edges: []
|
|
56351
56419
|
});
|
|
56352
56420
|
}
|
|
56421
|
+
for (const [file2, entry] of manifestEntries) {
|
|
56422
|
+
const signature = fileSignatures.get(file2);
|
|
56423
|
+
if (!signature)
|
|
56424
|
+
continue;
|
|
56425
|
+
const { gitSig: _previousGitSig, ...entryWithoutGitSig } = entry;
|
|
56426
|
+
manifestEntries.set(file2, {
|
|
56427
|
+
...entryWithoutGitSig,
|
|
56428
|
+
sig: signature.sig,
|
|
56429
|
+
...signature.gitSig ? { gitSig: signature.gitSig } : {}
|
|
56430
|
+
});
|
|
56431
|
+
}
|
|
56353
56432
|
await writeIndexManifestSnapshot({
|
|
56354
56433
|
projectRoot,
|
|
56355
56434
|
opts,
|
|
@@ -58577,7 +58656,7 @@ async function buildLifecycleManifest(root, buildOptions, existing, options = {}
|
|
|
58577
58656
|
configHash: await hashConfig(root, buildOptions?.logLevel),
|
|
58578
58657
|
buildOptionsHash: hashBuildOptions(buildOptions),
|
|
58579
58658
|
fileCount: snapshot.files.length,
|
|
58580
|
-
fileSignatureHash: await hashDiscoveredFiles(snapshot.files, root),
|
|
58659
|
+
fileSignatureHash: hashManifestEntries(snapshot.files, root, snapshot.index.manifestEntries, snapshot.index.manifestSignaturesFresh) ?? await hashDiscoveredFiles(snapshot.files, root),
|
|
58581
58660
|
files: discoveredFileRelativePaths(snapshot.files, root),
|
|
58582
58661
|
analysis: snapshot.analysis
|
|
58583
58662
|
};
|
|
@@ -58653,6 +58732,56 @@ function isMissingStatRace2(error51) {
|
|
|
58653
58732
|
return false;
|
|
58654
58733
|
return error51.code === "ENOENT" || error51.code === "ENOTDIR";
|
|
58655
58734
|
}
|
|
58735
|
+
function hashManifestEntries(files, root, entries, signaturesFresh) {
|
|
58736
|
+
if (!signaturesFresh || !entries || entries.size !== files.length)
|
|
58737
|
+
return void 0;
|
|
58738
|
+
const sorted = [...files].sort((left, right) => left.localeCompare(right));
|
|
58739
|
+
const signatures = /* @__PURE__ */ new Map();
|
|
58740
|
+
for (const file2 of sorted) {
|
|
58741
|
+
const entry = entries.get(file2);
|
|
58742
|
+
const signature = parseLifecycleFileSignature(entry?.sig);
|
|
58743
|
+
if (!signature)
|
|
58744
|
+
return void 0;
|
|
58745
|
+
signatures.set(file2, signature);
|
|
58746
|
+
}
|
|
58747
|
+
const hash2 = createHash4("sha256");
|
|
58748
|
+
for (const file2 of sorted) {
|
|
58749
|
+
const signature = signatures.get(file2);
|
|
58750
|
+
if (!signature)
|
|
58751
|
+
return void 0;
|
|
58752
|
+
const relative = path51.relative(root, file2).replace(/\\/g, "/");
|
|
58753
|
+
hash2.update(relative);
|
|
58754
|
+
hash2.update("\0");
|
|
58755
|
+
hash2.update(signature.size);
|
|
58756
|
+
hash2.update("\0");
|
|
58757
|
+
hash2.update(signature.mtimeMs);
|
|
58758
|
+
hash2.update("\0");
|
|
58759
|
+
}
|
|
58760
|
+
return hash2.digest("hex");
|
|
58761
|
+
}
|
|
58762
|
+
function parseLifecycleFileSignature(value) {
|
|
58763
|
+
if (typeof value !== "string")
|
|
58764
|
+
return void 0;
|
|
58765
|
+
if (value === "0:0")
|
|
58766
|
+
return void 0;
|
|
58767
|
+
const parts = value.split(":");
|
|
58768
|
+
if (parts.length !== 2)
|
|
58769
|
+
return void 0;
|
|
58770
|
+
const [mtimeMs, size] = parts;
|
|
58771
|
+
if (!mtimeMs || !size)
|
|
58772
|
+
return void 0;
|
|
58773
|
+
if (!/^-?(?:0|[1-9]\d*)(?:\.\d+)?$/.test(mtimeMs))
|
|
58774
|
+
return void 0;
|
|
58775
|
+
if (!/^(?:0|[1-9]\d*)$/.test(size))
|
|
58776
|
+
return void 0;
|
|
58777
|
+
const numericMtimeMs = Number(mtimeMs);
|
|
58778
|
+
const numericSize = Number(size);
|
|
58779
|
+
if (!Number.isFinite(numericMtimeMs) || String(numericMtimeMs) !== mtimeMs)
|
|
58780
|
+
return void 0;
|
|
58781
|
+
if (!Number.isSafeInteger(numericSize) || String(numericSize) !== size)
|
|
58782
|
+
return void 0;
|
|
58783
|
+
return { size, mtimeMs };
|
|
58784
|
+
}
|
|
58656
58785
|
async function hashDiscoveredFiles(files, root) {
|
|
58657
58786
|
const sorted = [...files].sort((left, right) => left.localeCompare(right));
|
|
58658
58787
|
const signatures = /* @__PURE__ */ new Map();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-index.d.ts","sourceRoot":"","sources":["../../src/indexer/build-index.ts"],"names":[],"mappings":"AAuFA,OAAO,EACL,KAAK,YAAY,EAEjB,KAAK,gBAAgB,EAErB,KAAK,uBAAuB,EAI5B,KAAK,YAAY,EAIlB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"build-index.d.ts","sourceRoot":"","sources":["../../src/indexer/build-index.ts"],"names":[],"mappings":"AAuFA,OAAO,EACL,KAAK,YAAY,EAEjB,KAAK,gBAAgB,EAErB,KAAK,uBAAuB,EAI5B,KAAK,YAAY,EAIlB,MAAM,YAAY,CAAC;AA8kCpB;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAEvG;AAED;;;;;;GAMG;AACH,wBAAsB,0BAA0B,CAC9C,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAAE,EACpB,IAAI,CAAC,EAAE,YAAY,GAClB,OAAO,CAAC,YAAY,CAAC,CAgBvB;AA4BD;;;;;;GAMG;AACH,wBAAsB,4BAA4B,CAChD,WAAW,EAAE,MAAM,EACnB,IAAI,CAAC,EAAE,uBAAuB,GAC7B,OAAO,CAAC,YAAY,CAAC,CAmwBvB;AAED,wBAAsB,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,uBAAuB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA8IpH"}
|
|
@@ -853,6 +853,7 @@ async function buildIndexFromFileListShared(projectRoot, rawFiles, opts, helperO
|
|
|
853
853
|
}
|
|
854
854
|
}
|
|
855
855
|
async function buildProjectIndexWithManifestOptions(projectRoot, opts, helperOpts) {
|
|
856
|
+
const timings = opts?.report ? (opts.report.timings ??= {}) : undefined;
|
|
856
857
|
await initializeFileIdentityCaseSensitivity(projectRoot);
|
|
857
858
|
try {
|
|
858
859
|
const useDiskCache = (opts?.cache ?? "off") === "disk";
|
|
@@ -888,6 +889,7 @@ async function buildProjectIndexWithManifestOptions(projectRoot, opts, helperOpt
|
|
|
888
889
|
// Git a second time.
|
|
889
890
|
if (helperOpts?.reportDiscoveryProgress)
|
|
890
891
|
emitIndexCheckActivity(opts, "Discovering source files");
|
|
892
|
+
const sourceDiscoveryStart = performance.now();
|
|
891
893
|
const discoveredFiles = await listProjectFilesWithGitCandidates(projectRoot, projectPatternsForLanguageExtensions(opts), {
|
|
892
894
|
...opts?.discovery,
|
|
893
895
|
...(opts?.logLevel ? { logLevel: opts.logLevel } : {}),
|
|
@@ -896,6 +898,8 @@ async function buildProjectIndexWithManifestOptions(projectRoot, opts, helperOpt
|
|
|
896
898
|
onGitCandidatesDiscovered,
|
|
897
899
|
...(onDiscoveryProgress ? { onDiscoveryProgress } : {}),
|
|
898
900
|
});
|
|
901
|
+
if (timings)
|
|
902
|
+
timings.sourceDiscoveryMs = Math.round(performance.now() - sourceDiscoveryStart);
|
|
899
903
|
const additionalFileCandidates = await normalizeIndexedFileInputsWithinRoot(projectRoot, opts?.additionalFiles ?? [], "Additional index file");
|
|
900
904
|
const additionalFileExistence = await probePathExistence(additionalFileCandidates, buildConcurrency(opts));
|
|
901
905
|
const additionalFiles = additionalFileCandidates.filter((file) => additionalFileExistence.get(file));
|
|
@@ -904,12 +908,15 @@ async function buildProjectIndexWithManifestOptions(projectRoot, opts, helperOpt
|
|
|
904
908
|
const transientFiles = additionalFiles.filter((file) => !discoveredFileSet.has(file));
|
|
905
909
|
if (helperOpts?.reportDiscoveryProgress)
|
|
906
910
|
emitIndexCheckActivity(opts, "Discovering project metadata");
|
|
911
|
+
const metadataDiscoveryStart = performance.now();
|
|
907
912
|
const projectFiles = await discoverProjectFilesWithGitCandidates(projectRoot, {
|
|
908
913
|
...(opts?.logLevel ? { logLevel: opts.logLevel } : {}),
|
|
909
914
|
...(discoveredSymlinkDirectories !== undefined ? { knownSymlinkDirectories: discoveredSymlinkDirectories } : {}),
|
|
910
915
|
...(discoveredGitCandidates !== undefined ? { knownGitCandidates: discoveredGitCandidates } : {}),
|
|
911
916
|
...(onDiscoveryProgress ? { onDiscoveryProgress } : {}),
|
|
912
917
|
});
|
|
918
|
+
if (timings)
|
|
919
|
+
timings.metadataDiscoveryMs = Math.round(performance.now() - metadataDiscoveryStart);
|
|
913
920
|
return await buildIndexFromFileListShared(projectRoot, files, opts, {
|
|
914
921
|
manifestMode: useDiskCache ? "read-write" : "off",
|
|
915
922
|
warnNoFilesMessage: `Warning: No files found in project root: ${projectRoot}. Check codegraph.config.json globs and CLI --include-glob/--ignore-glob filters. Diagnostic: codegraph doctor`,
|
|
@@ -998,6 +1005,7 @@ export async function buildProjectIndexIncremental(projectRoot, opts) {
|
|
|
998
1005
|
if (strictIncremental && graphOptions.fast)
|
|
999
1006
|
graphOptions.fast = false;
|
|
1000
1007
|
const { normalizedProjectRoot, report, timings, totalStart, cacheMode, cacheEnabled, onFallbackImportExtraction } = createIndexBuildRunState(projectRoot, opts, graphOptions);
|
|
1008
|
+
const discoveryTimings = opts?.report ? (opts.report.timings ??= {}) : undefined;
|
|
1001
1009
|
let checkProgressActive = false;
|
|
1002
1010
|
const startCheckProgress = () => {
|
|
1003
1011
|
if (checkProgressActive)
|
|
@@ -1108,15 +1116,35 @@ export async function buildProjectIndexIncremental(projectRoot, opts) {
|
|
|
1108
1116
|
// covers ordinary new-commit history when the working tree is clean at the new HEAD.
|
|
1109
1117
|
const shouldDiffAgainstWorkingTree = !hasExplicitGitRange && gitAvailable && !!manifest.lastCommit;
|
|
1110
1118
|
const canReuseReconciliation = opts?.reconciledManifestUpdatedAt === manifest.updatedAt;
|
|
1119
|
+
let sourceDiscoveryMs = 0;
|
|
1120
|
+
let sourceDiscoveryPerformed = false;
|
|
1121
|
+
const measureSourceDiscovery = async (operation) => {
|
|
1122
|
+
if (!discoveryTimings || opts?.filesAreProjectScope)
|
|
1123
|
+
return await operation();
|
|
1124
|
+
sourceDiscoveryPerformed = true;
|
|
1125
|
+
const start = performance.now();
|
|
1126
|
+
try {
|
|
1127
|
+
return await operation();
|
|
1128
|
+
}
|
|
1129
|
+
finally {
|
|
1130
|
+
sourceDiscoveryMs += performance.now() - start;
|
|
1131
|
+
}
|
|
1132
|
+
};
|
|
1111
1133
|
let manifestDiffFiles = [];
|
|
1112
1134
|
if (shouldDiffAgainstWorkingTree) {
|
|
1113
1135
|
try {
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1136
|
+
const reconciledWorkingTreeDiffFiles = canReuseReconciliation
|
|
1137
|
+
? opts?.reconciledWorkingTreeDiffFiles
|
|
1138
|
+
: undefined;
|
|
1139
|
+
if (reconciledWorkingTreeDiffFiles !== undefined) {
|
|
1140
|
+
manifestDiffFiles = reconciledWorkingTreeDiffFiles;
|
|
1141
|
+
}
|
|
1142
|
+
else {
|
|
1143
|
+
manifestDiffFiles = await measureSourceDiscovery(() => listChangedFiles(projectRoot, {
|
|
1144
|
+
base: manifest.lastCommit,
|
|
1145
|
+
head: "WORKTREE",
|
|
1146
|
+
}));
|
|
1147
|
+
}
|
|
1120
1148
|
}
|
|
1121
1149
|
catch (error) {
|
|
1122
1150
|
if (!isMissingGitRevisionError(error))
|
|
@@ -1163,7 +1191,10 @@ export async function buildProjectIndexIncremental(projectRoot, opts) {
|
|
|
1163
1191
|
const previousTransientFiles = sanitizeManifestTransientFilesForRoot(projectRoot, projectRoot, manifest.transientFiles).filter((file) => Object.hasOwn(trackedEntries, file));
|
|
1164
1192
|
const previousTransientFileSet = new Set(previousTransientFiles);
|
|
1165
1193
|
const needsGitScan = !!opts?.gitBase || !!opts?.changedSince;
|
|
1166
|
-
|
|
1194
|
+
let gitFiles = [];
|
|
1195
|
+
if (needsGitScan) {
|
|
1196
|
+
gitFiles = await measureSourceDiscovery(() => listChangedFiles(projectRoot, buildIncrementalGitDiffOptions(opts)));
|
|
1197
|
+
}
|
|
1167
1198
|
// New files that were never committed, staged, or passed explicitly have no tracked
|
|
1168
1199
|
// manifest entry and no working-tree-diff record, so they would otherwise stay
|
|
1169
1200
|
// invisible to an incremental build until the next full rebuild. Detecting them via
|
|
@@ -1183,9 +1214,13 @@ export async function buildProjectIndexIncremental(projectRoot, opts) {
|
|
|
1183
1214
|
let discoveredGitCandidates;
|
|
1184
1215
|
if (canUseIncrementalDiscoveryFastPath(gitAvailable, opts?.cacheStrict)) {
|
|
1185
1216
|
try {
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1217
|
+
const reconciledUntrackedFiles = canReuseReconciliation ? opts?.reconciledUntrackedFiles : undefined;
|
|
1218
|
+
if (reconciledUntrackedFiles !== undefined) {
|
|
1219
|
+
untrackedFiles = reconciledUntrackedFiles;
|
|
1220
|
+
}
|
|
1221
|
+
else {
|
|
1222
|
+
untrackedFiles = await measureSourceDiscovery(() => listUntrackedProjectFiles(projectRoot, opts?.discovery, gitAvailable));
|
|
1223
|
+
}
|
|
1189
1224
|
}
|
|
1190
1225
|
catch (error) {
|
|
1191
1226
|
if (manifestReport) {
|
|
@@ -1200,7 +1235,7 @@ export async function buildProjectIndexIncremental(projectRoot, opts) {
|
|
|
1200
1235
|
}
|
|
1201
1236
|
}
|
|
1202
1237
|
else if (!opts?.filesAreProjectScope) {
|
|
1203
|
-
rediscoveredFiles = await listProjectFilesWithGitCandidates(projectRoot, projectPatternsForLanguageExtensions(opts), {
|
|
1238
|
+
rediscoveredFiles = await measureSourceDiscovery(() => listProjectFilesWithGitCandidates(projectRoot, projectPatternsForLanguageExtensions(opts), {
|
|
1204
1239
|
...opts?.discovery,
|
|
1205
1240
|
...(opts?.logLevel ? { logLevel: opts.logLevel } : {}),
|
|
1206
1241
|
...(!opts?.cacheStrict && manifest.symlinkDirectories !== undefined
|
|
@@ -1209,7 +1244,10 @@ export async function buildProjectIndexIncremental(projectRoot, opts) {
|
|
|
1209
1244
|
onGitCandidatesDiscovered: (candidates) => {
|
|
1210
1245
|
discoveredGitCandidates = candidates;
|
|
1211
1246
|
},
|
|
1212
|
-
});
|
|
1247
|
+
}));
|
|
1248
|
+
}
|
|
1249
|
+
if (discoveryTimings && sourceDiscoveryPerformed) {
|
|
1250
|
+
discoveryTimings.sourceDiscoveryMs = Math.round(sourceDiscoveryMs);
|
|
1213
1251
|
}
|
|
1214
1252
|
const candidateFiles = [
|
|
1215
1253
|
...explicitFiles,
|
|
@@ -1314,10 +1352,19 @@ export async function buildProjectIndexIncremental(projectRoot, opts) {
|
|
|
1314
1352
|
if (!snapshotLoad)
|
|
1315
1353
|
return null;
|
|
1316
1354
|
const snapshot = snapshotLoad.index;
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1355
|
+
// The early Git fast path does not stat tracked files. Its persisted mtime:size
|
|
1356
|
+
// values can therefore be stale after a byte-identical rewrite; lifecycle must
|
|
1357
|
+
// re-stat until the validated path below replaces every signature.
|
|
1358
|
+
snapshot.manifestSignaturesFresh = false;
|
|
1359
|
+
if (snapshot.projectFiles === undefined) {
|
|
1360
|
+
const metadataDiscoveryStart = performance.now();
|
|
1361
|
+
snapshot.projectFiles = await discoverProjectFilesWithGitCandidates(projectRoot, {
|
|
1362
|
+
...(opts?.logLevel ? { logLevel: opts.logLevel } : {}),
|
|
1363
|
+
...(discoveredGitCandidates !== undefined ? { knownGitCandidates: discoveredGitCandidates } : {}),
|
|
1364
|
+
});
|
|
1365
|
+
if (discoveryTimings)
|
|
1366
|
+
discoveryTimings.metadataDiscoveryMs = Math.round(performance.now() - metadataDiscoveryStart);
|
|
1367
|
+
}
|
|
1321
1368
|
if (opts?.cache) {
|
|
1322
1369
|
snapshot.cacheMode = opts.cache;
|
|
1323
1370
|
snapshot.cacheRootDir = cacheRoot(projectRoot, opts);
|
|
@@ -1646,6 +1693,20 @@ export async function buildProjectIndexIncremental(projectRoot, opts) {
|
|
|
1646
1693
|
edges: [],
|
|
1647
1694
|
});
|
|
1648
1695
|
}
|
|
1696
|
+
// A Git blob match proves source bytes, not mtime. Refresh every retained
|
|
1697
|
+
// entry from the stat signatures computed above before declaring lifecycle
|
|
1698
|
+
// signatures fresh.
|
|
1699
|
+
for (const [file, entry] of manifestEntries) {
|
|
1700
|
+
const signature = fileSignatures.get(file);
|
|
1701
|
+
if (!signature)
|
|
1702
|
+
continue;
|
|
1703
|
+
const { gitSig: _previousGitSig, ...entryWithoutGitSig } = entry;
|
|
1704
|
+
manifestEntries.set(file, {
|
|
1705
|
+
...entryWithoutGitSig,
|
|
1706
|
+
sig: signature.sig,
|
|
1707
|
+
...(signature.gitSig ? { gitSig: signature.gitSig } : {}),
|
|
1708
|
+
});
|
|
1709
|
+
}
|
|
1649
1710
|
await writeIndexManifestSnapshot({
|
|
1650
1711
|
projectRoot,
|
|
1651
1712
|
opts,
|