socraticode
Version:
SocratiCode — MCP server for local codebase indexing, semantic search, and code dependency graphs. All private, all local via Docker.
234 lines • 11.7 kB
TypeScript
import { Lang } from "@ast-grep/napi";
import type { CodeGraph, SymbolEdge, SymbolNode } from "../types.js";
import type { GitRefreshTarget } from "./git-state.js";
import { type GraphChangeSummary, type GraphInputRecord, type GraphInputRecorder, type GraphRebuildDecision } from "./graph-inputs.js";
import { type GodotSymbolResolutionContext } from "./graph-symbol-resolution.js";
import { type RustUseBinding } from "./graph-symbols.js";
export { describeGraphBuilder, describeUnresolvedSymbolEdges, findCircularDependencies, generateMermaidDiagram, getFileDependencies, getGraphStats, isGraphBuilderStale, isImportResolutionLow } from "./graph-analysis.js";
/** Progress details for an in-flight graph build operation */
export interface GraphBuildProgress {
startedAt: number;
filesTotal: number;
filesProcessed: number;
/**
* Files counted in `filesProcessed` that got no node of their own; an importer
* may still have created a placeholder for them. Absent until the first skip.
*/
filesSkipped?: number;
phase: string;
error?: string;
}
/** Summary of a completed graph build operation */
export interface GraphBuildCompleted {
completedAt: number;
durationMs: number;
filesProcessed: number;
/**
* Files that got no node of their own during this build; an importer may still
* have created a placeholder for them. Absent when none were skipped.
*/
filesSkipped?: number;
nodesCreated: number;
edgesCreated: number;
error?: string;
/**
* Set when the file-import graph was built and saved but the symbol graph
* could not be persisted. That half-failure used to be logged and otherwise
* dropped, so the build reported success while `codebase_impact` silently had
* nothing to answer with; recording it here is what lets status say so.
*/
symbolGraphError?: string;
}
/** Check if a graph build is currently in progress for a project */
export declare function isGraphBuildInProgress(projectPath: string): boolean;
/** Get progress details for a graph build currently in progress */
export declare function getGraphBuildProgress(projectPath: string): GraphBuildProgress | null;
/** Get the last completed graph build for a project */
export declare function getLastGraphBuildCompleted(projectPath: string): GraphBuildCompleted | null;
/** Get all projects currently building a graph */
export declare function getGraphBuildInProgressProjects(): string[];
/** Invalidate graph cache for a project (called by watcher on file changes) */
export declare function invalidateGraphCache(projectPath: string): void;
/** A pinned identity can be cached under more than one checkout path; drop them all. */
export declare function invalidateGraphCacheForIdentity(projectId: string): void;
/** Get a cached graph, or load from Qdrant, or build one */
export declare function getOrBuildGraph(projectPath: string, extraExtensions?: Set<string>): Promise<CodeGraph>;
/** Get a cached or persisted graph without creating one when it is absent. */
export declare function getExistingGraph(projectPath: string): Promise<CodeGraph | null>;
/** Options for `rebuildGraph` controlling which layers are rebuilt. */
export interface RebuildGraphOptions {
/** Storage identity captured by the enclosing index operation. */
projectId?: string;
/** Git refreshes require both graph layers to finish for the observed state. */
target?: GitRefreshTarget;
/** Extra file extensions to treat as graph nodes. */
extraExtensions?: Set<string>;
/**
* When `true`, skip the symbol-graph extraction + persistence step entirely.
* The file-import graph is still rebuilt and persisted. The caller is then
* expected to update the symbol graph incrementally via
* `updateChangedFilesSymbolGraph` from `symbol-graph-incremental.ts`.
* Default: `false`.
*/
skipSymbolGraph?: boolean;
}
/**
* Whether an incremental update has to rebuild the code graph, and why.
*
* The answer comes from the record the last build left behind — the files it
* read and the settings it read them under — never from a predicate describing
* which files a graph is built from. There is no such predicate to keep in
* step, which is the whole reason the build reports its own inputs.
*
* Conservative wherever the record cannot settle the question: a project with
* no usable record rebuilds once and writes one, and so does any addition, any
* settings change, and any recorded input that moved. Only a change proven to
* touch nothing the build read is skipped.
*/
export declare function shouldRebuildGraph(projectPath: string, change: GraphChangeSummary, extraExtensions?: Set<string>, projectId?: string): Promise<GraphRebuildDecision>;
/**
* The parsers this process actually has, as one hash.
*
* Read after {@link ensureDynamicLanguages} has run, which every path into the
* build and the decision does first, so it describes the process answering
* rather than some other one.
*/
export declare function currentGraphCapabilities(): Promise<string>;
/** Force-rebuild, cache, and persist a graph.
* If a build is already in progress for this project, returns the existing
* in-flight promise (deduplication — same as indexer concurrency guard).
*
* Backward-compatible: accepts either `extraExtensions` (legacy positional
* Set) or a `RebuildGraphOptions` object.
*/
export declare function rebuildGraph(projectPath: string, optsOrExtras?: Set<string> | RebuildGraphOptions): Promise<CodeGraph>;
/**
* Wait for any in-flight graph build to finish for a project.
* Resolves immediately if no build is in progress.
* Swallows errors — the caller typically wants to proceed regardless.
*/
export declare function awaitGraphBuild(projectPath: string): Promise<void>;
/** Remove a persisted code graph from Qdrant and clear cache */
export declare function removeGraph(projectPath: string): Promise<void>;
/** Check if a graph exists (in cache or persisted) */
export declare function hasGraph(projectPath: string): Promise<boolean>;
/** Get graph metadata for status display */
export declare function getGraphStatus(projectPath: string): Promise<{
lastBuiltAt: string;
nodeCount: number;
edgeCount: number;
/** Import specifiers captured across all files, resolved or not. Absent on
* graphs persisted before this field was recorded. */
importCount?: number;
/** SocratiCode version that built this graph, which is not necessarily the
* one serving it. Absent on graphs persisted before this field was
* recorded. */
builtByVersion?: string;
cached: boolean;
symbol?: {
fileCount: number;
symbolCount: number;
edgeCount: number;
unresolvedEdgePct: number;
builtAt: number;
};
} | null>;
/** Snapshot of dynamic-language registration state, for diagnostics. */
export interface DynamicLanguageStatus {
loaded: string[];
failed: Array<{
name: string;
error: string;
}>;
}
/** Returns which dynamic ast-grep grammars registered successfully and which failed. */
export declare function getDynamicLanguageStatus(): DynamicLanguageStatus;
/**
* Whether the GDScript tree-sitter parser was successfully registered.
* tree-sitter-gdscript ships platform-specific prebuilds; on platforms
* without a compatible artifact (e.g. linux-arm64) this stays false so
* callers can skip AST processing and use syntax-aware fallback extraction.
*/
export { gdscriptParserAvailable } from "./parser-availability.js";
export declare function ensureDynamicLanguages(): void;
/**
* Map a file extension to its ast-grep grammar (or null when none). An
* EXTENSION_LANGUAGE_MAP override is resolved through the target language's
* canonical extension, so a mapped extension (e.g. `.inc` → php) gets the same
* grammar a native file of that language would, keeping symbol extraction and
* AST chunking consistent with the language label.
*/
export declare function getAstGrepLang(ext: string, override?: Map<string, string>): Lang | string | null;
/**
* Get all source files in a project for graph analysis, with the detected
* extension of every extensionless file admitted by content detection.
*
* Includes files with known AST grammars, mixed Elixir templates handled by their
* dedicated parsers, and any extra extensions. Extensionless files are head-read
* here to decide admission, and the extension that decision
* used is returned in `detectedExts` so the build pass can reuse it instead of
* reading the head again.
*
* `files` is sorted lexicographically. Node documents no readdir ordering, and a
* depth-first walk additionally interleaves a directory's contents with the
* sibling entries that sort after it — `a/x.ts` is yielded before `a.ts`.
* Processing order determines node insertion order, and `buildJvmSuffixMap`'s
* first-match-wins tie-break for duplicate class paths reads the set in that
* order directly (`buildCsNamespaceMap` and `buildGoModuleInfo` sort their own
* filtered views instead). Normalize here rather than leaving it to the traversal.
*/
export declare function getGraphableFiles(projectPath: string, extraExts?: Set<string>, recorder?: GraphInputRecorder): Promise<{
files: string[];
detectedExts: Map<string, string>;
}>;
/**
* Build a code graph for a project using ast-grep for polyglot support.
* Files with extra extensions (no AST grammar) are included as leaf nodes
* that can be targets of import edges from other files.
*
* Also extracts symbols and call sites in the same pass — returned via
* `symbolsByFile` / `outgoingCallsByFile` and persisted by `doRebuildGraph`.
*/
export declare function buildCodeGraph(projectPath: string, extraExtensions?: Set<string>, progress?: GraphBuildProgress): Promise<CodeGraph & {
symbolsByFile: Map<string, SymbolNode[]>;
outgoingCallsByFile: Map<string, SymbolEdge[]>;
autoloadTable?: Map<string, string>;
resToRepoPathMap: Map<string, string>;
godotContext?: GodotSymbolResolutionContext;
inferredTypesByFile: Map<string, Map<string, Array<{
type: string;
startLine: number;
endLine: number;
}>>>;
memberAssignmentsByFile: Map<string, Array<{
receiver: string;
memberName: string;
valueType: string;
}>>;
/** Rust `use` bindings per file — resolution input only, never persisted. */
rustBindingsByFile: Map<string, RustUseBinding[]>;
/** Rust file → its crate's directory prefix. Resolution input only. */
rustCrateRootByFile: Map<string, string>;
/** Rust file → every target root its parsed Cargo manifest declares. */
rustCrateRootsByFile: Map<string, string[]>;
/**
* The call edges whose qualifier is rooted in an inline `mod` — a scope with
* no file to name. Resolution leaves them unresolved. Held by identity, so
* nothing about it reaches an edge or the store.
*/
rustInlineScopedCalls: Set<SymbolEdge>;
/**
* The ids of the Rust symbols declared inside an inline `mod`. A path
* anchored at a file's own module cannot reach them, so resolution drops
* them from that path's candidates. Resolution input only, never persisted.
*/
rustInlineDeclaredSymbols: Map<string, string>;
/**
* Every input this build consumed, for the next incremental update to check
* a change against. Reported from the build rather than described beside it,
* so no second answer to "does this file contribute to the graph?" exists to
* drift from this one.
*/
graphInputs: GraphInputRecord;
}>;
//# sourceMappingURL=code-graph.d.ts.map