diff --git a/src/agent/query-index/store.ts b/src/agent/query-index/store.ts index c673b948..27761a6c 100644 --- a/src/agent/query-index/store.ts +++ b/src/agent/query-index/store.ts @@ -119,6 +119,7 @@ export class QueryIndexStore { try { ensureQueryIndexSchema(db); db.pragma("journal_mode = WAL"); + db.pragma("synchronous = NORMAL"); db.pragma("foreign_keys = ON"); } catch (error) { db.close(); diff --git a/src/agent/search.ts b/src/agent/search.ts index b645a0fb..4813fd5d 100644 --- a/src/agent/search.ts +++ b/src/agent/search.ts @@ -974,8 +974,9 @@ function collectReachableFiles( relation: "anchor", })); - while (queue.length) { - const current = queue.shift()!; + let queueHead = 0; + while (queueHead < queue.length) { + const current = queue[queueHead++]; const existing = reachable.get(current.file); if (existing && existing.distance <= current.distance) continue; reachable.set(current.file, current); diff --git a/src/indexer/build-cache/manifest.ts b/src/indexer/build-cache/manifest.ts index 82530197..be6240e1 100644 --- a/src/indexer/build-cache/manifest.ts +++ b/src/indexer/build-cache/manifest.ts @@ -16,12 +16,7 @@ import { import { assertFilePathWithinRoot, fileIdentityKey, isFilePathWithinRoot } from "../../util/paths.js"; import { getGitBlobHashes } from "../../util/git.js"; import { stringifyUnknown } from "../../util/ast.js"; -import { - cacheAbsolutePath, - cacheRelativePath, - cacheRoot, - fileSignature, -} from "./module-cache.js"; +import { cacheAbsolutePath, cacheRelativePath, cacheRoot, fileSignature } from "./module-cache.js"; import type { BuildOptions } from "../types.js"; import type { ManifestBuildOptions } from "./options.js"; @@ -39,19 +34,17 @@ export async function collectWorkspaceManifestDependencyEdges( allowedManifestFiles?: ReadonlySet, logLevel?: LogLevel, ): Promise { - const manifestPaths = await listProjectFiles(projectRoot, ["**/package.json"], { - ...discovery, - ...(logLevel ? { logLevel } : {}), - }); - const scopedManifestPaths = allowedManifestFiles - ? manifestPaths.filter((manifestPath) => allowedManifestFiles.has(manifestPath)) - : manifestPaths; - if (!scopedManifestPaths.length) return []; - + const manifestPaths = allowedManifestFiles + ? [...allowedManifestFiles].filter((manifestPath) => path.basename(manifestPath) === "package.json") + : await listProjectFiles(projectRoot, ["**/package.json"], { + ...discovery, + ...(logLevel ? { logLevel } : {}), + }); + if (!manifestPaths.length) return []; const manifestByPackageName = new Map(); const parsedByPath = new Map(); - for (const manifestPath of scopedManifestPaths) { + for (const manifestPath of manifestPaths) { try { const raw = await fsp.readFile(manifestPath, "utf8"); const parsed = JSON.parse(raw) as PackageJsonDependencyInfo; @@ -133,7 +126,9 @@ export function transformManifestEntries( edge.to.type === "file" ? { ...edge.to, - path: toRelative ? cacheRelativePath(projectRoot, edge.to.path) : cacheAbsolutePath(projectRoot, edge.to.path), + path: toRelative + ? cacheRelativePath(projectRoot, edge.to.path) + : cacheAbsolutePath(projectRoot, edge.to.path), } : edge.to, })), diff --git a/src/indexer/build-index.ts b/src/indexer/build-index.ts index 315af76a..7166253f 100644 --- a/src/indexer/build-index.ts +++ b/src/indexer/build-index.ts @@ -541,11 +541,12 @@ async function prepareFileSignatures(args: { opts: BuildOptions | undefined; gitSigMap: Map; cacheEnabled: boolean; + needsContentHash: boolean; concurrency: number; }): Promise> { const entries = await mapLimit(args.files, args.concurrency, async (file) => { const gitSig = args.gitSigMap.get(file); - const sigInfo = await fileSignature(file, args.opts?.cacheStrict, gitSig, { + const sigInfo = await fileSignature(file, args.needsContentHash ? args.opts?.cacheStrict : false, gitSig, { forceContentHash: args.cacheEnabled && !gitSig, }); return [file, sigInfo] as const; @@ -658,6 +659,7 @@ async function buildIndexFromFileListShared( opts, gitSigMap, cacheEnabled, + needsContentHash: true, concurrency: conc, }); const sqlCorpusSig = sqlCorpusSignature(sqlFiles, fileSignatures); @@ -1432,6 +1434,7 @@ export async function buildProjectIndexIncremental( opts, gitSigMap, cacheEnabled, + needsContentHash: cacheEnabled || useManifest || opts?.cacheStrict === true, concurrency: conc, }); const modules = new Map(); diff --git a/src/util/projectFiles.ts b/src/util/projectFiles.ts index f771cc68..bc451594 100644 --- a/src/util/projectFiles.ts +++ b/src/util/projectFiles.ts @@ -155,6 +155,7 @@ type SafeSymlinkDirectoryCrawlOptions = { onlyFiles?: boolean; markDirectories?: boolean; knownSymlinkDirectories?: readonly string[]; + resolvedSafeSymlinkDirectories?: readonly string[]; onSymlinkDirectoriesDiscovered?: (directories: readonly string[]) => void; }; @@ -335,6 +336,13 @@ export async function listProjectFiles( picomatch(globPattern, { dot: true }), ); const patternMatchers = patterns.map((pattern) => picomatch(normalizeGlobPattern(pattern), { dot: true })); + const projectFileDefinitionMatchers = PROJECT_FILE_DEFINITIONS.map((definition) => + definition.patterns.map((pattern) => + pattern.includes("*") || pattern.includes("?") + ? new RegExp("^" + pattern.replace(/\./g, "\\.").replace(/\*/g, ".*") + "$") + : undefined, + ), + ); const translatedUserIgnoreGlobs = translateGlobRootIgnoreGlobsForScanRoot(root, globRoot, userIgnoreGlobs); const fastGlobIgnoreGlobs = [...DEFAULT_PROJECT_FILE_IGNORES, ...translatedUserIgnoreGlobs]; @@ -365,15 +373,25 @@ export async function listProjectFiles( ignore: translatedUserIgnoreGlobs, }) : []; - const linkedFiles = await listEntriesFromSafeSymlinkDirectories(root, realRoot, patterns, fastGlobIgnoreGlobs, { + const symlinkOptions = { globRoot, - filterIgnoreGlobs: [...DEFAULT_PROJECT_FILE_IGNORES, ...userIgnoreGlobs], ...(options?.knownSymlinkDirectories !== undefined ? { knownSymlinkDirectories: options.knownSymlinkDirectories } : {}), ...(options?.onSymlinkDirectoriesDiscovered ? { onSymlinkDirectoriesDiscovered: options.onSymlinkDirectoriesDiscovered } : {}), + }; + const safeSymlinkDirectories = await resolveSafeSymlinkDirectories( + root, + realRoot, + fastGlobIgnoreGlobs, + symlinkOptions, + ); + const linkedFiles = await listEntriesFromSafeSymlinkDirectories(root, realRoot, patterns, fastGlobIgnoreGlobs, { + ...symlinkOptions, + filterIgnoreGlobs: [...DEFAULT_PROJECT_FILE_IGNORES, ...userIgnoreGlobs], + resolvedSafeSymlinkDirectories: safeSymlinkDirectories, }); const linkedOverrideFiles = includeGlobs.length === 0 @@ -381,9 +399,7 @@ export async function listProjectFiles( : await listEntriesFromSafeSymlinkDirectories(root, realRoot, patterns, translatedUserIgnoreGlobs, { globRoot, filterIgnoreGlobs: userIgnoreGlobs, - ...(options?.knownSymlinkDirectories !== undefined - ? { knownSymlinkDirectories: options.knownSymlinkDirectories } - : {}), + resolvedSafeSymlinkDirectories: safeSymlinkDirectories, }); const rootSafeFiles = await filterRealPathsWithinRootEntries( [...files, ...includedOverrideFiles, ...linkedFiles, ...linkedOverrideFiles], @@ -537,7 +553,8 @@ async function listEntriesFromSafeSymlinkDirectories( .filter(Boolean) .map((globPattern) => picomatch(globPattern, { dot: true })); const locationIndependentIgnores = ignore.map(normalizeGlobPattern).filter(isLocationIndependentGlob); - const safeSymlinkDirectories = await resolveSafeSymlinkDirectories(root, realRoot, ignore, options); + const safeSymlinkDirectories = + options.resolvedSafeSymlinkDirectories ?? (await resolveSafeSymlinkDirectories(root, realRoot, ignore, options)); if (!safeSymlinkDirectories.length) return []; const filesByPath = new Map(); const filesByDirectory = await mapLimitSemaphore( @@ -659,22 +676,27 @@ export async function discoverProjectFiles( realRoot, ); + const projectFileDefinitionMatchers = PROJECT_FILE_DEFINITIONS.map((definition) => + definition.patterns.map((pattern) => + pattern.includes("*") || pattern.includes("?") + ? new RegExp("^" + pattern.replace(/\./g, "\\.").replace(/\*/g, ".*") + "$") + : undefined, + ), + ); const entries: ProjectFileInfo[] = []; const matchTasks = rootSafeMatches.map(async (cleanMatch) => { const stats = await fsp.stat(cleanMatch); const isDir = stats.isDirectory(); const fileName = path.basename(cleanMatch); - for (const def of PROJECT_FILE_DEFINITIONS) { + for (let definitionIndex = 0; definitionIndex < PROJECT_FILE_DEFINITIONS.length; definitionIndex++) { + const def = PROJECT_FILE_DEFINITIONS[definitionIndex]; if (isDir && def.kind !== "dir") continue; if (!isDir && def.kind !== "file") continue; - const matchesPattern = def.patterns.some((p) => { - if (p.includes("*") || p.includes("?")) { - const re = new RegExp("^" + p.replace(/\./g, "\\.").replace(/\*/g, ".*") + "$"); - return re.test(fileName); - } - return p === fileName; + const matchesPattern = def.patterns.some((pattern, patternIndex) => { + const matcher = projectFileDefinitionMatchers[definitionIndex][patternIndex]; + return matcher ? matcher.test(fileName) : pattern === fileName; }); if (matchesPattern) {