/**
 * 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