socraticode
Version:
SocratiCode — MCP server for local codebase indexing, semantic search, and code dependency graphs. All private, all local via Docker.
105 lines • 5.78 kB
TypeScript
/**
* Read up to `maxBytes` bytes from the start of a file and decode as UTF-8.
* Reads only the head (not the whole file) so scanning large extensionless
* binaries/data files stays cheap. Opens non-blocking and throws for a
* non-regular file (FIFO/socket/device) so a mistyped or swapped path can't
* block the open; may also throw on open/read errors.
*/
export declare function readFileHead(absolutePath: string, maxBytes?: number): Promise<string>;
/**
* Detect the canonical extension of source text already held in memory, scoring
* the same window {@link readFileHead} reads from disk: the first
* {@link DETECT_HEAD_BYTES} **bytes** of UTF-8, not characters.
*
* The byte window is load-bearing, not incidental. `detectExtensionlessExtension`
* counts pattern hits, so a larger window can change its answer; a character
* slice of a file with multibyte text near the top would cover more content than
* the disk head and could disagree about identical bytes. Re-encoding keeps the
* two answers comparable — including at the boundary, where each decodes a
* character split by the cut to U+FFFD.
*
* Content that was lossily decoded — invalid UTF-8 replaced by U+FFFD — re-encodes
* to at least as many bytes as it came from, three per U+FFFD, so it scores a
* window no longer than the raw file's. A decoded string cannot recover the bytes
* it came from, so the in-memory and on-disk sides cannot be made to score the
* same span of such a file; the disk-side resolver routes through this helper so
* both settle on the narrower window instead of disagreeing. The window is
* therefore about {@link DETECT_HEAD_BYTES} / 3 characters for wholly lossy
* content, and a latin-1 file whose only code markers sit past that point reads as
* "not code" wherever this window is scored.
*
* The leading character slice only bounds the allocation, keeping a whole-file
* encode off this path: the first N characters always encode to at least N
* bytes, so slicing to N characters cannot drop anything inside the N-byte
* window.
*/
export declare function detectExtensionFromSource(source: string): string | null;
/**
* The canonical detection window for this content.
*
* The one place the window's end is decided, so that a head read from disk and
* the same window taken from whole-file content are comparable — which they
* are not if either side skips this, because a U+FFFD standing in for an
* invalid byte costs three bytes of budget on the way back out and slides the
* end of the window.
*
* Idempotent: the result is already at most `maxBytes`, so normalising it again
* changes nothing, and {@link readFileHead} can return a normalised head
* without altering what detection scores.
*/
export declare function detectionHeadWindow(source: string, maxBytes?: number): string;
/**
* Like {@link resolveExtensionlessExtension} but **throws** on a read/stat
* failure instead of collapsing it to `null`, so a caller that must not conflate
* "unreadable" with "not code" can tell them apart — e.g. the incremental
* symbol-graph purge, which would otherwise drop a still-valid payload on a
* transient I/O blip. Returns `null` only for a genuine non-match: detection
* disabled, a {@link SPECIAL_FILES} name, a non-regular file, or content that is
* not indexable code.
*/
export declare function resolveExtensionlessExtensionStrict(absolutePath: string): Promise<string | null>;
/** What a head-read decided, together with the bytes it decided on. */
export interface ExtensionlessDetection {
/** The canonical extension detected, or null for a genuine non-match. */
extension: string | null;
/**
* The head the answer was scored on, or null when nothing was read at all —
* detection disabled, a {@link SPECIAL_FILES} name, or a non-regular file.
*
* A caller that records what the build consumed needs this: the answer was
* derived from these bytes and from nothing else, so they are the thing to
* watch. Watching the file's size instead would miss a same-length edit that
* flips the answer (`# hello!!` becoming `#!/bin/sh`), and watching its whole
* content would hash bytes the decision never saw.
*/
head: string | null;
/**
* Whether the head-read was attempted and threw, as opposed to never being
* attempted. A caller recording what the build consumed needs the two apart:
* a file it could not open may start being code the moment it can be read,
* while one it never opened (detection off, a {@link SPECIAL_FILES} name, a
* FIFO) is not an input at all.
*/
unreadable: boolean;
}
/**
* Like {@link resolveExtensionlessExtensionStrict}, but also reports the head
* the detection ran on. Throws on a read/stat failure for the same reason the
* strict variant does.
*/
export declare function resolveExtensionlessDetectionStrict(absolutePath: string): Promise<ExtensionlessDetection>;
/**
* Detect the canonical extension of an extensionless file by head content, or
* `null` when detection is disabled, the name is a {@link SPECIAL_FILES} entry,
* the file is unreadable, or the content is not indexable code. Callers that
* need graph-eligibility (grammar-bearing only) additionally check
* `getAstGrepLang(result) !== null`.
*/
export declare function resolveExtensionlessExtension(absolutePath: string): Promise<string | null>;
/**
* {@link resolveExtensionlessExtension}, reporting the head the answer was
* scored on so a caller can record what it consumed. A failure reads as
* "nothing was read", which is what it was.
*/
export declare function resolveExtensionlessDetection(absolutePath: string): Promise<ExtensionlessDetection>;
//# sourceMappingURL=extensionless.d.ts.map