@sackville-mcp/lsp 0.0.1-alpha.0

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":["defaultDelay","defaultReadFile"],"sources":["../src/encoding.ts","../src/normalize.ts","../src/client.ts","../src/registry.ts","../src/manager.ts","../src/confine.ts","../src/query.ts","../src/apply.ts","../src/rename.ts"],"sourcesContent":["/**\n * Position-encoding conversion — the single highest-correctness-risk corner of the LSP\n * bridge (ADR 0011), and the reason this is the first slice. An LSP `Position.character`\n * is a 0-based offset measured in **code units of the negotiated encoding**, NOT a column.\n * JS strings are UTF-16, so a naive `col - 1` passes every ASCII test and then silently\n * points at the WRONG symbol the moment a line contains a non-BMP character (emoji, CJK,\n * combining marks) under a server that negotiated UTF-8. That is the worst failure for an\n * agent tool — plausible, wrong, and silent — so the conversion is a pure, separately\n * unit-tested function exercised against non-BMP fixtures here, before any server exists.\n *\n * Human columns are 1-based and count **Unicode code points** (what a person counts);\n * LSP characters are 0-based code-unit offsets in the negotiated encoding.\n */\n\nexport type PositionEncoding = 'utf-8' | 'utf-16' | 'utf-32'\n\nconst SUPPORTED: readonly string[] = ['utf-8', 'utf-16', 'utf-32']\n\n/**\n * The encodings we advertise to the server, in preference order. UTF-16 is first\n * deliberately: it is the JS-native, best-tested path (a server honouring our preference\n * picks the one we exercise most). We still implement all three for servers that only\n * speak UTF-8 (e.g. clangd).\n */\nexport const PREFERRED_ENCODINGS: readonly PositionEncoding[] = ['utf-16', 'utf-8']\n\n/**\n * Resolve the encoding to use from the server's reported `positionEncoding`. Absent ⇒ the\n * spec default, UTF-16. **Present but unsupported ⇒ throw** — never silently default, or\n * we would do offset math in the wrong unit and return wrong locations.\n */\nexport function resolvePositionEncoding(reported: string | undefined): PositionEncoding {\n if (reported === undefined) return 'utf-16'\n if (SUPPORTED.includes(reported)) return reported as PositionEncoding\n throw new Error(\n `server negotiated an unsupported position encoding: ${reported} (expected one of ${SUPPORTED.join(', ')})`,\n )\n}\n\n/** Length of a string in the code units of `encoding`. */\nfunction codeUnits(s: string, encoding: PositionEncoding): number {\n switch (encoding) {\n case 'utf-16':\n return s.length // a JS string's length IS its UTF-16 code-unit count\n case 'utf-8':\n return Buffer.byteLength(s, 'utf8')\n case 'utf-32':\n return [...s].length // code points\n }\n}\n\n/** Strip a single leading BOM (U+FEFF) — it must not shift line-1 column math. */\nfunction stripBom(text: string): string {\n return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text\n}\n\n/**\n * Convert a 1-based human column (counting code points) on `lineText` to a 0-based LSP\n * `character` offset in `encoding`. A column past the line end clamps to the line length.\n */\nexport function toLspCharacter(\n lineText: string,\n humanColumn: number,\n encoding: PositionEncoding,\n): number {\n const cps = [...lineText]\n const take = Math.max(0, Math.min(humanColumn - 1, cps.length))\n return codeUnits(cps.slice(0, take).join(''), encoding)\n}\n\n/**\n * Inverse of {@link toLspCharacter}: map a 0-based LSP `character` offset back to a 1-based\n * human code-point column. An offset landing inside a multi-unit code point clamps to that\n * code point's start; an offset past the line end clamps to one past the last column.\n */\nexport function fromLspCharacter(\n lineText: string,\n lspCharacter: number,\n encoding: PositionEncoding,\n): number {\n if (lspCharacter <= 0) return 1\n const cps = [...lineText]\n let units = 0\n for (let k = 0; k < cps.length; k++) {\n const u = codeUnits(cps[k] as string, encoding)\n // Offset lands inside code point k → clamp to its (1-based) column.\n if (units + u > lspCharacter) return k + 1\n units += u\n }\n return cps.length + 1\n}\n\nexport interface LspPositionParts {\n line: number\n character: number\n}\n\n/** Split text into line contents on LF / CR / CRLF, excluding the terminators (LSP's model). */\nfunction splitLines(text: string): string[] {\n return text.split(/\\r\\n|\\r|\\n/)\n}\n\n/**\n * Convert a human 1-based line:column over the full source `text` to a 0-based LSP\n * `Position` in `encoding`. Lines are split on LF/CR/CRLF WITHOUT normalizing the document\n * (the text we send the server is its source of truth); a leading BOM is stripped before\n * line-1 column math.\n */\nexport function toLspPosition(\n text: string,\n humanLine: number,\n humanColumn: number,\n encoding: PositionEncoding,\n): LspPositionParts {\n const lines = splitLines(stripBom(text))\n const lineText = lines[humanLine - 1] ?? ''\n return { line: humanLine - 1, character: toLspCharacter(lineText, humanColumn, encoding) }\n}\n\nexport interface HumanPosition {\n line: number\n column: number\n}\n\n/** Inverse of {@link toLspPosition}: map an LSP `Position` back to human 1-based line:column. */\nexport function fromLspPosition(\n text: string,\n position: LspPositionParts,\n encoding: PositionEncoding,\n): HumanPosition {\n const lines = splitLines(stripBom(text))\n const lineText = lines[position.line] ?? ''\n return {\n line: position.line + 1,\n column: fromLspCharacter(lineText, position.character, encoding),\n }\n}\n\n/** JS-string (UTF-16) offset WITHIN `lineText` for a 0-based LSP `character` in `encoding`. */\nfunction lspCharacterToJsOffset(\n lineText: string,\n character: number,\n encoding: PositionEncoding,\n): number {\n if (character <= 0) return 0\n let units = 0\n let js = 0\n for (const cp of lineText) {\n if (units >= character) break\n const u = codeUnits(cp, encoding)\n if (units + u > character) break // offset lands inside this code point → clamp to its start\n units += u\n js += cp.length\n }\n return js\n}\n\n/**\n * Convert an LSP `Position` to an ABSOLUTE JS-string (UTF-16 `.length`) index into `text`, for\n * splicing edits (the write-mode core). Unlike {@link toLspPosition} this does NOT use\n * `splitLines` — that splitter discards terminator identity and would shift the offset by one\n * JS code unit per CRLF line above the position. We raw-scan terminators (counting their actual\n * length) and count negotiated code units only WITHIN the target line, returning the cumulative\n * JS index. A leading BOM is stripped for line/character math (matching `toLspPosition`) but its\n * length is added back, so the returned offset indexes into the real, BOM-bearing `text`.\n */\nexport function lspPositionToOffset(\n text: string,\n position: LspPositionParts,\n encoding: PositionEncoding,\n): number {\n const bom = text.charCodeAt(0) === 0xfeff ? 1 : 0\n const body = bom ? text.slice(1) : text\n const n = body.length\n // Walk to the start of `position.line`, counting the ACTUAL terminator length per line.\n let i = 0\n let line = 0\n while (line < position.line && i < n) {\n const ch = body.charCodeAt(i)\n if (ch === 0x0d) {\n i += body.charCodeAt(i + 1) === 0x0a ? 2 : 1 // CRLF or a lone CR\n line++\n } else if (ch === 0x0a) {\n i += 1\n line++\n } else {\n i += 1\n }\n }\n // Find the end of the target line (exclusive of its terminator).\n let lineEnd = i\n while (lineEnd < n) {\n const ch = body.charCodeAt(lineEnd)\n if (ch === 0x0d || ch === 0x0a) break\n lineEnd++\n }\n const within = lspCharacterToJsOffset(body.slice(i, lineEnd), position.character, encoding)\n return bom + i + within\n}\n","/**\n * Pure LSP-result normalizers (ADR 0011, slice 1). LSP responses are polymorphic in ways\n * that silently corrupt an agent-facing tool if mishandled — these reducers collapse each\n * shape to one Sackville form, with no I/O. The traps the adversarial pass flagged:\n *\n * - **`Location` vs `LocationLink`.** definition/typeDefinition return\n * `Location | Location[] | LocationLink[] | null`; `LocationLink` uses `targetUri` /\n * `targetSelectionRange` — DIFFERENT field names than `Location`'s `uri`/`range`. Reading\n * `.uri`/`.range` off a link yields `undefined` → silently empty navigation.\n * - **`DocumentSymbol` vs `SymbolInformation`.** documentSymbol returns hierarchical\n * `DocumentSymbol[]` (has `children`/`selectionRange`) OR flat `SymbolInformation[]` (has\n * `location`) — two incompatible shapes under one method.\n * - **Hover contents** can be `MarkupContent | MarkedString | MarkedString[]`.\n *\n * Ranges are kept in LSP 0-based form here; mapping them back to human 1-based line:column\n * needs the file text + the negotiated encoding and lives in `encoding.ts`\n * (`fromLspPosition`), applied at the surface.\n */\n\nexport interface LspPosition {\n line: number\n character: number\n}\nexport interface LspRange {\n start: LspPosition\n end: LspPosition\n}\n\nexport interface Location {\n uri: string\n range: LspRange\n}\n\nexport interface LocationLink {\n targetUri: string\n targetRange: LspRange\n targetSelectionRange: LspRange\n originSelectionRange?: LspRange\n}\n\nexport interface NormalizedLocation {\n uri: string\n /** The precise symbol range (LocationLink.targetSelectionRange, or Location.range). */\n range: LspRange\n /** The full enclosing range, when the server sent a LocationLink. */\n fullRange?: LspRange\n}\n\nfunction isLocationLink(item: Location | LocationLink): item is LocationLink {\n return (item as LocationLink).targetUri !== undefined\n}\n\n/** Normalize any definition/references/typeDefinition result shape to a flat location list. */\nexport function normalizeLocations(\n result: Location | Location[] | LocationLink[] | null | undefined,\n): NormalizedLocation[] {\n if (result == null) return []\n const items = Array.isArray(result) ? result : [result]\n return items.map((item) => {\n if (isLocationLink(item)) {\n return { uri: item.targetUri, range: item.targetSelectionRange, fullRange: item.targetRange }\n }\n return { uri: item.uri, range: item.range }\n })\n}\n\nexport type MarkedString = string | { language: string; value: string }\nexport interface MarkupContent {\n kind: 'plaintext' | 'markdown'\n value: string\n}\nexport interface Hover {\n contents: MarkupContent | MarkedString | MarkedString[]\n range?: LspRange\n}\n\nexport interface NormalizedHover {\n value: string\n range?: LspRange\n}\n\nfunction markedToString(m: MarkedString): string {\n if (typeof m === 'string') return m\n return `\\`\\`\\`${m.language}\\n${m.value}\\n\\`\\`\\``\n}\n\n/** Normalize hover contents (MarkupContent | MarkedString | MarkedString[]) to one string. */\nexport function normalizeHover(hover: Hover | null | undefined): NormalizedHover | null {\n if (hover == null) return null\n const c = hover.contents\n let value: string\n if (Array.isArray(c)) {\n value = c.map(markedToString).join('\\n\\n')\n } else if (typeof c === 'string') {\n value = c\n } else if ('kind' in c) {\n value = c.value // MarkupContent\n } else {\n value = markedToString(c) // {language, value} MarkedString\n }\n return hover.range ? { value, range: hover.range } : { value }\n}\n\n// LSP SymbolKind enum (1-based), https://microsoft.github.io/language-server-protocol/.\nconst SYMBOL_KIND_NAMES = [\n 'File',\n 'Module',\n 'Namespace',\n 'Package',\n 'Class',\n 'Method',\n 'Property',\n 'Field',\n 'Constructor',\n 'Enum',\n 'Interface',\n 'Function',\n 'Variable',\n 'Constant',\n 'String',\n 'Number',\n 'Boolean',\n 'Array',\n 'Object',\n 'Key',\n 'Null',\n 'EnumMember',\n 'Struct',\n 'Event',\n 'Operator',\n 'TypeParameter',\n] as const\n\nexport function symbolKindName(kind: number): string {\n return SYMBOL_KIND_NAMES[kind - 1] ?? 'Unknown'\n}\n\nexport interface DocumentSymbol {\n name: string\n kind: number\n detail?: string\n range: LspRange\n selectionRange: LspRange\n children?: DocumentSymbol[]\n}\n\nexport interface SymbolInformation {\n name: string\n kind: number\n location: Location\n containerName?: string\n}\n\nexport interface NormalizedSymbol {\n name: string\n kind: number\n kindName: string\n detail?: string\n range: LspRange\n children?: NormalizedSymbol[]\n /** Set only for flat SymbolInformation results. */\n container?: string\n}\n\nfunction isSymbolInformation(s: DocumentSymbol | SymbolInformation): s is SymbolInformation {\n return (s as SymbolInformation).location !== undefined\n}\n\nfunction normalizeDocumentSymbol(s: DocumentSymbol): NormalizedSymbol {\n const out: NormalizedSymbol = {\n name: s.name,\n kind: s.kind,\n kindName: symbolKindName(s.kind),\n range: s.range,\n }\n if (s.detail !== undefined) out.detail = s.detail\n if (s.children && s.children.length > 0) out.children = s.children.map(normalizeDocumentSymbol)\n return out\n}\n\n/** Normalize documentSymbol's dual shape (hierarchical DocumentSymbol[] or flat SymbolInformation[]). */\nexport function normalizeDocumentSymbols(\n result: DocumentSymbol[] | SymbolInformation[] | null | undefined,\n): NormalizedSymbol[] {\n if (result == null || result.length === 0) return []\n const first = result[0] as DocumentSymbol | SymbolInformation\n if (isSymbolInformation(first)) {\n return (result as SymbolInformation[]).map((s) => {\n const out: NormalizedSymbol = {\n name: s.name,\n kind: s.kind,\n kindName: symbolKindName(s.kind),\n range: s.location.range,\n }\n if (s.containerName !== undefined) out.container = s.containerName\n return out\n })\n }\n return (result as DocumentSymbol[]).map(normalizeDocumentSymbol)\n}\n\n// --- Workspace symbol search (ADR 0011 staged tail) ----------------------------------------\n\n/**\n * A raw `workspace/symbol` result member. Two shapes under one method (LSP 3.17):\n * - `SymbolInformation`: `location` is a full `Location` (`{uri, range}`).\n * - `WorkspaceSymbol`: `location` may be a full `Location` OR a uri-only `{uri}` — the server\n * defers the range to a `workspaceSymbol/resolve` round-trip. Reading `.location.range` off\n * the uri-only form yields `undefined`; we surface the uri and omit the range (v1 does not\n * resolve — see fixtures README; the real `typescript-language-server` 5.3.0 sends the full\n * `Location` form).\n */\nexport interface WorkspaceSymbol {\n name: string\n kind: number\n containerName?: string\n location: Location | { uri: string }\n}\n\nexport interface NormalizedWorkspaceSymbol {\n name: string\n kind: number\n kindName: string\n uri: string\n /** Absent when the server returned a uri-only `WorkspaceSymbol` (no range without resolve). */\n range?: LspRange\n /** The declaring container (class/namespace), when the server reported one. */\n container?: string\n}\n\nfunction hasRange(loc: Location | { uri: string }): loc is Location {\n return (loc as Location).range !== undefined\n}\n\n/** Normalize a `workspace/symbol` result (flat `SymbolInformation[]`/`WorkspaceSymbol[]`). */\nexport function normalizeWorkspaceSymbols(\n result: WorkspaceSymbol[] | null | undefined,\n): NormalizedWorkspaceSymbol[] {\n if (result == null) return []\n return result.map((s) => {\n const out: NormalizedWorkspaceSymbol = {\n name: s.name,\n kind: s.kind,\n kindName: symbolKindName(s.kind),\n uri: s.location.uri,\n }\n if (hasRange(s.location)) out.range = s.location.range\n if (s.containerName !== undefined) out.container = s.containerName\n return out\n })\n}\n\n// --- Diagnostics (ADR 0011 staged tail; PUSH model) ----------------------------------------\n\n// LSP DiagnosticSeverity (1-based) + DiagnosticTag enums.\nconst SEVERITY_NAMES = ['Error', 'Warning', 'Information', 'Hint'] as const\nconst TAG_NAMES: Record<number, string> = { 1: 'Unnecessary', 2: 'Deprecated' }\n\nexport function diagnosticSeverityName(severity: number): string {\n return SEVERITY_NAMES[severity - 1] ?? 'Unknown'\n}\n\n/** A raw LSP `Diagnostic` (the member of a `publishDiagnostics` notification's `diagnostics`). */\nexport interface Diagnostic {\n range: LspRange\n message: string\n severity?: number\n code?: number | string\n source?: string\n tags?: number[]\n relatedInformation?: { location: Location; message: string }[]\n}\n\nexport interface NormalizedRelatedInfo {\n uri: string\n /** LSP 0-based range (mapped to human coords against its own file at the surface). */\n range: LspRange\n message: string\n}\n\nexport interface NormalizedDiagnostic {\n /** LSP 0-based range (mapped to human coords in the queried file at the surface). */\n range: LspRange\n message: string\n severity?: number\n /** `Error`/`Warning`/`Information`/`Hint`, when the server set a severity. */\n severityName?: string\n code?: number | string\n source?: string\n /** `Unnecessary`/`Deprecated`, only when non-empty. */\n tags?: string[]\n related?: NormalizedRelatedInfo[]\n}\n\n/** Normalize a `publishDiagnostics` `diagnostics[]` (push model) — severity/tag names, related info. */\nexport function normalizeDiagnostics(\n items: Diagnostic[] | null | undefined,\n): NormalizedDiagnostic[] {\n if (items == null) return []\n return items.map((d) => {\n const out: NormalizedDiagnostic = { range: d.range, message: d.message }\n if (d.severity !== undefined) {\n out.severity = d.severity\n out.severityName = diagnosticSeverityName(d.severity)\n }\n if (d.code !== undefined) out.code = d.code\n if (d.source !== undefined) out.source = d.source\n if (d.tags && d.tags.length > 0) {\n out.tags = d.tags.map((t) => TAG_NAMES[t] ?? `Tag(${t})`)\n }\n if (d.relatedInformation && d.relatedInformation.length > 0) {\n out.related = d.relatedInformation.map((r) => ({\n uri: r.location.uri,\n range: r.location.range,\n message: r.message,\n }))\n }\n return out\n })\n}\n\n/** A `textDocument/diagnostic` PULL report (LSP 3.17): a full item set or an unchanged marker. */\nexport interface RawDocumentDiagnosticReport {\n kind?: 'full' | 'unchanged'\n items?: Diagnostic[]\n}\n\n/**\n * Diagnostics from a PULL-model `textDocument/diagnostic` report. A `full` report carries `items`;\n * an `unchanged` report means \"identical to the previousResultId\" — Sackville's single-shot reads\n * send no previousResultId, so a server should always answer `full`; we treat `unchanged`/absent\n * defensively as no items. The items themselves are normalized by the shared `normalizeDiagnostics`\n * (the report is just the pull envelope around the same `Diagnostic[]` the push model publishes).\n */\nexport function diagnosticsFromReport(\n report: RawDocumentDiagnosticReport | null | undefined,\n): NormalizedDiagnostic[] {\n return report?.kind === 'full' ? normalizeDiagnostics(report.items) : []\n}\n\n// --- Call hierarchy (ADR 0011 staged tail) -------------------------------------------------\n\n/** A raw `CallHierarchyItem` (prepareCallHierarchy result + the node inside incoming/outgoing). */\nexport interface CallHierarchyItem {\n name: string\n kind: number\n detail?: string\n uri: string\n range: LspRange\n selectionRange: LspRange\n}\n\nexport interface NormalizedCallItem {\n name: string\n kind: number\n kindName: string\n detail?: string\n uri: string\n range: LspRange\n selectionRange: LspRange\n}\n\n/** One edge of the call hierarchy: the other item + the ranges where the call occurs. */\nexport interface NormalizedCall {\n item: NormalizedCallItem\n fromRanges: LspRange[]\n}\n\nexport function normalizeCallHierarchyItem(item: CallHierarchyItem): NormalizedCallItem {\n const out: NormalizedCallItem = {\n name: item.name,\n kind: item.kind,\n kindName: symbolKindName(item.kind),\n uri: item.uri,\n range: item.range,\n selectionRange: item.selectionRange,\n }\n if (item.detail !== undefined && item.detail !== '') out.detail = item.detail\n return out\n}\n\nexport function normalizeCallHierarchyItems(\n result: CallHierarchyItem[] | null | undefined,\n): NormalizedCallItem[] {\n if (result == null) return []\n return result.map(normalizeCallHierarchyItem)\n}\n\n/** `callHierarchy/incomingCalls` → `{from, fromRanges}[]`; the edge item is the CALLER. */\nexport function normalizeIncomingCalls(\n result: { from: CallHierarchyItem; fromRanges: LspRange[] }[] | null | undefined,\n): NormalizedCall[] {\n if (result == null) return []\n return result.map((c) => ({\n item: normalizeCallHierarchyItem(c.from),\n fromRanges: c.fromRanges ?? [],\n }))\n}\n\n/** `callHierarchy/outgoingCalls` → `{to, fromRanges}[]`; the edge item is the CALLEE. */\nexport function normalizeOutgoingCalls(\n result: { to: CallHierarchyItem; fromRanges: LspRange[] }[] | null | undefined,\n): NormalizedCall[] {\n if (result == null) return []\n return result.map((c) => ({\n item: normalizeCallHierarchyItem(c.to),\n fromRanges: c.fromRanges ?? [],\n }))\n}\n\n/**\n * The tri-state query outcome (ADR 0011): an empty result is only authoritatively\n * `no_result` when the server is READY; while it is still indexing an empty result is\n * `not_ready` and must never be collapsed into \"no definition\" (an agent would act on the\n * lie). Readiness is decided by the client (slice 2); this is the pure combinator.\n */\nexport type QueryStatus = 'ok' | 'not_ready' | 'no_result'\n\nexport function decideStatus(isEmpty: boolean, ready: boolean): QueryStatus {\n if (!isEmpty) return 'ok'\n return ready ? 'no_result' : 'not_ready'\n}\n\n// --- WorkspaceEdit (write-mode, ADR 0011 addendum) -----------------------------------------\n\n/** A raw LSP `TextEdit` (or `AnnotatedTextEdit`, which adds `annotationId`). */\nexport interface RawTextEdit {\n range: LspRange\n newText: string\n annotationId?: string\n}\n\n/** A raw `TextDocumentEdit` documentChanges member: a versioned doc + its edits. */\nexport interface RawTextDocumentEdit {\n textDocument: { uri: string; version?: number | null }\n edits: RawTextEdit[]\n}\n\n/** Resource-operation options (LSP `CreateFileOptions`/`RenameFileOptions`/`DeleteFileOptions`). */\nexport interface ResourceOpOptions {\n overwrite?: boolean\n ignoreIfExists?: boolean\n ignoreIfNotExists?: boolean\n recursive?: boolean\n}\n\n/** A raw file-resource operation documentChanges member (CreateFile/RenameFile/DeleteFile). */\nexport interface RawResourceOperation {\n kind: 'create' | 'rename' | 'delete'\n uri?: string\n oldUri?: string\n newUri?: string\n options?: ResourceOpOptions\n}\n\nexport interface RawWorkspaceEdit {\n changes?: Record<string, RawTextEdit[]>\n documentChanges?: (RawTextDocumentEdit | RawResourceOperation)[]\n changeAnnotations?: Record<string, { label?: string; needsConfirmation?: boolean }>\n}\n\n/** One edit on a file, range kept in LSP 0-based form (mapped to human coords at the surface). */\nexport interface NormalizedFileEdit {\n range: LspRange\n newText: string\n /** A `needsConfirmation` change annotation rode this edit — preview-only, excluded from apply. */\n needsConfirmation?: boolean\n /** The change-annotation label, when one was attached. */\n annotationLabel?: string\n}\n\nexport interface NormalizedFileEdits {\n uri: string\n edits: NormalizedFileEdit[]\n}\n\n/** A flagged resource operation — surfaced in the preview (paths relativized at the surface). */\nexport interface NormalizedResourceOp {\n kind: 'create' | 'rename' | 'delete'\n uris: string[]\n}\n\n/**\n * One operation in `documentChanges` ORDER (the apply engine executes these in sequence — a\n * \"Move to file\" is `create`→`edit`(new)→`delete`(old), so order is load-bearing). `files`/\n * `resourceOps` are the order-free buckets kept for preview/back-compat.\n */\nexport type NormalizedOp =\n | { type: 'edit'; uri: string; edits: NormalizedFileEdit[] }\n | { type: 'create'; uri: string; options?: ResourceOpOptions }\n | { type: 'rename'; oldUri: string; newUri: string; options?: ResourceOpOptions }\n | { type: 'delete'; uri: string; options?: ResourceOpOptions }\n\nexport interface NormalizedWorkspaceEdit {\n files: NormalizedFileEdits[]\n resourceOps: NormalizedResourceOp[]\n /** The operations in `documentChanges` order (the apply engine's authority). */\n operations: NormalizedOp[]\n}\n\nfunction isResourceOp(\n member: RawTextDocumentEdit | RawResourceOperation,\n): member is RawResourceOperation {\n const kind = (member as RawResourceOperation).kind\n return kind === 'create' || kind === 'rename' || kind === 'delete'\n}\n\n/**\n * Normalize a `WorkspaceEdit` to a uniform `files → edits` list plus a separate, flagged list of\n * resource operations. Handles both shapes (ADR 0011 addendum §2.5):\n * - `documentChanges` takes PRECEDENCE over `changes` when both are present (never merged).\n * - Per-file and per-edit order is preserved.\n * - `CreateFile`/`RenameFile`/`DeleteFile` members are surfaced under `resourceOps`, never\n * translated into a TextEdit (v1 refuses to apply them).\n * - An `AnnotatedTextEdit` is normalized to `{range,newText}`; if its annotation is\n * `needsConfirmation` the edit carries `needsConfirmation: true` + the label (a preview-only\n * signal, excluded from apply) so the server's safety signal is never silently dropped.\n *\n * NOTE: real `typescript-language-server` 5.3.0 returns the legacy `changes` map for an ordinary\n * rename even when the client advertises `documentChanges` (see `test/fixtures/README.md`); the\n * `documentChanges` branch is exercised by a synthesized fixture.\n */\nexport function normalizeWorkspaceEdit(\n raw: RawWorkspaceEdit | null | undefined,\n): NormalizedWorkspaceEdit {\n if (raw == null) return { files: [], resourceOps: [], operations: [] }\n const annotations = raw.changeAnnotations ?? {}\n const mapEdit = (e: RawTextEdit): NormalizedFileEdit => {\n const out: NormalizedFileEdit = { range: e.range, newText: e.newText }\n if (e.annotationId !== undefined) {\n const ann = annotations[e.annotationId]\n if (ann?.needsConfirmation) out.needsConfirmation = true\n if (ann?.label !== undefined) out.annotationLabel = ann.label\n }\n return out\n }\n\n if (raw.documentChanges !== undefined) {\n const files: NormalizedFileEdits[] = []\n const resourceOps: NormalizedResourceOp[] = []\n const operations: NormalizedOp[] = []\n for (const member of raw.documentChanges) {\n if (isResourceOp(member)) {\n const uris =\n member.kind === 'rename'\n ? [member.oldUri, member.newUri].filter((u): u is string => u !== undefined)\n : member.uri !== undefined\n ? [member.uri]\n : []\n resourceOps.push({ kind: member.kind, uris })\n if (member.kind === 'rename') {\n operations.push({\n type: 'rename',\n oldUri: member.oldUri ?? '',\n newUri: member.newUri ?? '',\n ...(member.options ? { options: member.options } : {}),\n })\n } else {\n operations.push({\n type: member.kind,\n uri: member.uri ?? '',\n ...(member.options ? { options: member.options } : {}),\n })\n }\n } else {\n const edits = member.edits.map(mapEdit)\n files.push({ uri: member.textDocument.uri, edits })\n operations.push({ type: 'edit', uri: member.textDocument.uri, edits })\n }\n }\n return { files, resourceOps, operations }\n }\n\n if (raw.changes !== undefined) {\n // A `changes` map never carries resource ops — every operation is a plain edit (order = key order).\n const files = Object.entries(raw.changes).map(([uri, edits]) => ({\n uri,\n edits: edits.map(mapEdit),\n }))\n return {\n files,\n resourceOps: [],\n operations: files.map((f) => ({ type: 'edit', uri: f.uri, edits: f.edits })),\n }\n }\n\n return { files: [], resourceOps: [], operations: [] }\n}\n\n/** `textDocument/prepareRename` raw result variants. */\nexport type RawPrepareRename =\n | LspRange\n | { range: LspRange; placeholder?: string }\n | { defaultBehavior: boolean }\n\n/** Normalized prepareRename outcome — a non-null value means the position IS renameable. */\nexport interface PrepareRenameOutcome {\n /** The renameable token range, when the server provided one. */\n range?: LspRange\n /** A suggested placeholder (the existing identifier), when provided. */\n placeholder?: string\n /** The server signalled default behavior (renameable; it derives the range itself). */\n defaultBehavior?: boolean\n}\n\n/**\n * Normalize a `prepareRename` result. `null` ⇒ the position is NOT renameable (the engine maps\n * this to a structured refusal, distinct from a tri-state `no_result`). A bare `Range`,\n * `{range, placeholder}`, and `{defaultBehavior}` all mean renameable — the real\n * `typescript-language-server` 5.3.0 returns a bare `Range` (see `test/fixtures/README.md`).\n */\nexport function normalizePrepareRename(\n raw: RawPrepareRename | null | undefined,\n): PrepareRenameOutcome | null {\n if (raw == null) return null\n if ('start' in raw && 'end' in raw) return { range: raw }\n if ('range' in raw) {\n const out: PrepareRenameOutcome = { range: raw.range }\n if (raw.placeholder !== undefined) out.placeholder = raw.placeholder\n return out\n }\n if ('defaultBehavior' in raw) return { defaultBehavior: raw.defaultBehavior }\n return {}\n}\n","/**\n * The LSP JSON-RPC client (ADR 0011, slice 2) — one live, stateful, bidirectional session\n * with a language-server subprocess, the documented exception to ARCHITECTURE §1's\n * no-live-RPC rule. It leans on Microsoft's reference transport (`vscode-jsonrpc` for\n * Content-Length framing + id correlation, `vscode-languageserver-protocol` for method\n * constants) — the playwright-core pattern, NOT a hand-rolled framing layer.\n *\n * The five corners the adversarial pass flagged as load-bearing, all handled here:\n *\n * 1. **Encoding negotiation.** Advertise `positionEncodings: [\"utf-16\",\"utf-8\"]`, read back\n * `ServerCapabilities.positionEncoding`, and do ALL offset math in that unit (via\n * `encoding.ts`). Absent ⇒ spec-default UTF-16; present-but-unsupported ⇒ fail loud.\n * 2. **Tri-state readiness.** An empty result is `no_result` only when the server is READY;\n * while an indexing `$/progress` work-done token is active it is `not_ready` — never\n * collapsed into \"no definition\". One authoritative deadline (the operator timeout) with\n * the bounded retry/backoff living INSIDE it; the first call returns `not_ready` fast.\n * 3. **Deadlock-safe inbound replies.** The client MUST answer every id-bearing server\n * request (`workspace/configuration`, `window/workDoneProgress/create`,\n * `client/registerCapability`) or it deadlocks — in particular it must answer\n * `workDoneProgress/create` before the `$/progress` that drives readiness arrives.\n * 4. **Document lifecycle.** `didOpen` full-text once, reference-counted, NO `didClose` by\n * default (the per-(server,uri) mutex that serializes the open+query critical section\n * lives in the manager — slice 3).\n * 5. **Capability gating + provenance.** Every request is gated on its `*Provider`\n * capability; `serverInfo.{name,version}` rides on every result (turns \"silently wrong\"\n * into \"wrong-but-attributed\").\n *\n * All time-based code goes through the injected clock (`now`/`delay`) — the production code\n * never calls `setTimeout`/`setInterval` directly except inside the default `delay` seam, so\n * the gate drives retry/backoff deterministically with a fake clock.\n */\n\nimport { spawn } from 'node:child_process'\nimport {\n createMessageConnection,\n type MessageConnection,\n ResponseError,\n StreamMessageReader,\n StreamMessageWriter,\n} from 'vscode-jsonrpc/node.js'\nimport {\n ApplyWorkspaceEditRequest,\n CallHierarchyIncomingCallsRequest,\n CallHierarchyOutgoingCallsRequest,\n CallHierarchyPrepareRequest,\n ConfigurationRequest,\n DefinitionRequest,\n DidChangeTextDocumentNotification,\n DidChangeWorkspaceFoldersNotification,\n DidCloseTextDocumentNotification,\n DidOpenTextDocumentNotification,\n DocumentDiagnosticRequest,\n DocumentSymbolRequest,\n ExitNotification,\n HoverRequest,\n InitializedNotification,\n InitializeRequest,\n PrepareRenameRequest,\n PublishDiagnosticsNotification,\n ReferencesRequest,\n RegistrationRequest,\n RenameRequest,\n ShutdownRequest,\n TypeDefinitionRequest,\n UnregistrationRequest,\n WorkDoneProgressCreateRequest,\n WorkspaceSymbolRequest,\n} from 'vscode-languageserver-protocol'\nimport { type PositionEncoding, PREFERRED_ENCODINGS, resolvePositionEncoding } from './encoding.js'\nimport {\n type CallHierarchyItem,\n type Diagnostic,\n type DocumentSymbol,\n decideStatus,\n diagnosticsFromReport,\n type Hover,\n type Location,\n type LocationLink,\n type NormalizedCall,\n type NormalizedCallItem,\n type NormalizedDiagnostic,\n type NormalizedHover,\n type NormalizedLocation,\n type NormalizedSymbol,\n type NormalizedWorkspaceEdit,\n type NormalizedWorkspaceSymbol,\n normalizeCallHierarchyItem,\n normalizeDiagnostics,\n normalizeDocumentSymbols,\n normalizeHover,\n normalizeIncomingCalls,\n normalizeLocations,\n normalizeOutgoingCalls,\n normalizePrepareRename,\n normalizeWorkspaceEdit,\n normalizeWorkspaceSymbols,\n type PrepareRenameOutcome,\n type QueryStatus,\n type RawDocumentDiagnosticReport,\n type RawPrepareRename,\n type RawWorkspaceEdit,\n type SymbolInformation,\n type WorkspaceSymbol,\n} from './normalize.js'\n\n/** An operator-registry server entry: the binary + argv to spawn (structurally separate). */\nexport interface ServerSpec {\n command: string\n args: string[]\n initializationOptions?: unknown\n}\n\n/** An established connection to a language server, plus how to tear the process down. */\nexport interface LspConnection {\n connection: MessageConnection\n /** Hard teardown (SIGKILL + stream close) — last resort after graceful `shutdown`. */\n dispose(): void\n}\n\n/** Injected spawn seam — the only place a real process is created (tests inject a fake peer). */\nexport type ServerSpawn = (spec: ServerSpec) => LspConnection\n\n/** Default live spawn: a child process over stdio, framed by the reference transport. */\nexport const defaultServerSpawn: ServerSpawn = (spec) => {\n const child = spawn(spec.command, spec.args, { stdio: ['pipe', 'pipe', 'pipe'] })\n const connection = createMessageConnection(\n new StreamMessageReader(child.stdout),\n new StreamMessageWriter(child.stdin),\n )\n return {\n connection,\n dispose() {\n connection.dispose()\n child.kill('SIGKILL')\n },\n }\n}\n\nexport interface LspClientOptions {\n /** OPERATOR per-request wall-clock cap (ms) — the single authoritative deadline. */\n timeoutMs: number\n /** Single-attempt mode: no retry/backoff (the gate's deterministic default). */\n noRetry?: boolean\n /** Injected clock. Default `Date.now`. */\n now?: () => number\n /** Injected cancellable delay. Default a `setTimeout` promise (the one seam that may). */\n delay?: (ms: number) => Promise<void>\n /** Base backoff (ms) for the empty-result retry. Default 50. */\n baseBackoffMs?: number\n /** Max backoff (ms). Default 1000. */\n maxBackoffMs?: number\n}\n\n/** Server provenance from `initialize` — `serverInfo`, optional per the protocol. */\nexport interface ServerInfo {\n name: string\n version?: string\n}\n\n/** A call-hierarchy group: one prepared source item + the calls in the requested direction. */\nexport interface CallHierarchyGroup {\n source: NormalizedCallItem\n calls: NormalizedCall[]\n}\n\nexport type CallDirection = 'incoming' | 'outgoing'\n\n/** A navigation result with its tri-state status and version/encoding provenance. */\nexport interface NavResult<T> {\n status: QueryStatus\n result: T\n serverInfo?: ServerInfo\n encoding: PositionEncoding\n}\n\nexport interface InitializeSummary {\n encoding: PositionEncoding\n serverInfo?: ServerInfo\n capabilities: ServerCapabilities\n}\n\n/** The subset of `ServerCapabilities` this slice reads (any non-`false`/non-absent ⇒ enabled). */\ntype ServerCapabilities = Record<string, unknown>\n\ninterface InitializeResult {\n capabilities?: ServerCapabilities\n serverInfo?: ServerInfo\n}\n\ninterface ProgressParams {\n token: string | number\n value?: { kind?: string }\n}\n\n/** Thrown when a navigation is requested against a capability the server did not advertise. */\nexport class LspUnsupportedError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'LspUnsupportedError'\n }\n}\n\nconst defaultDelay = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms))\n\n/**\n * `ResponseError` codes a server uses to signal \"not ready / nothing to answer here\" rather than\n * returning an empty result. Some servers (rust-analyzer) ERROR where tsserver returns empty —\n * `withRetry` routes these through the SAME tri-state path as an empty result (indexing ⇒ not_ready\n * and retry within the deadline; settled ⇒ no_result), instead of letting them throw as a hard\n * failure. Any OTHER error (a genuine protocol/internal failure) propagates unchanged.\n * -32801 ContentModified / -32802 ServerCancelled / -32800 RequestCancelled — the spec's\n * retry/cancel codes; -32602 InvalidParams — rust-analyzer's \"No references found at position\".\n */\nconst SOFT_NOT_READY_CODES = new Set([-32801, -32802, -32800, -32602])\n\nexport class LspClient {\n private readonly conn: MessageConnection\n private readonly timeoutMs: number\n private readonly noRetry: boolean\n private readonly now: () => number\n private readonly delay: (ms: number) => Promise<void>\n private readonly baseBackoffMs: number\n private readonly maxBackoffMs: number\n\n private listening = false\n private _encoding: PositionEncoding = 'utf-16'\n private _serverInfo: ServerInfo | undefined\n private _capabilities: ServerCapabilities = {}\n\n /** Active indexing work-done-progress tokens — non-empty ⇒ the server is still indexing. */\n private readonly activeProgress = new Set<string | number>()\n /** Resolvers waiting for indexing to DRAIN (the active set to empty); fired on the final `end`. */\n private readonly drainWaiters: Array<() => void> = []\n /**\n * Open documents (open-once, no `didClose` by default). `version` is the per-uri monotonic\n * document version: seeded at 1 by `didOpen`, pre-incremented by each `applyEdited` `didChange`\n * — versions MUST strictly increase or the server ignores the change and keeps stale text.\n */\n private readonly open = new Map<string, { refs: number; version: number; languageId: string }>()\n\n /**\n * Pushed diagnostics per uri (the PUSH model — `textDocument/publishDiagnostics` is a server\n * notification, not a request; tsserver advertises no `diagnosticProvider`, so pull diagnostics\n * are unavailable). An entry's absence ⇒ the server hasn't published for that uri yet.\n */\n private readonly diagnostics = new Map<string, { items: Diagnostic[] }>()\n /** Uris freshly `didOpen`ed whose first post-open publish hasn't arrived yet. */\n private readonly awaitingDiagnostics = new Set<string>()\n /** Resolvers waiting for the NEXT publish on a uri (keyed); fired by the publish handler. */\n private readonly diagnosticsWaiters = new Map<string, Array<() => void>>()\n\n constructor(connection: MessageConnection, options: LspClientOptions) {\n this.conn = connection\n this.timeoutMs = options.timeoutMs\n this.noRetry = options.noRetry ?? false\n this.now = options.now ?? Date.now\n this.delay = options.delay ?? defaultDelay\n this.baseBackoffMs = options.baseBackoffMs ?? 50\n this.maxBackoffMs = options.maxBackoffMs ?? 1000\n }\n\n get encoding(): PositionEncoding {\n return this._encoding\n }\n get serverInfo(): ServerInfo | undefined {\n return this._serverInfo\n }\n get capabilities(): ServerCapabilities {\n return this._capabilities\n }\n /** Indexing is active when at least one `$/progress` work-done token is open. */\n get indexing(): boolean {\n return this.activeProgress.size > 0\n }\n\n /** Any non-`false`, non-absent `*Provider` value counts as enabled (LSP convention). */\n supports(provider: string): boolean {\n const v = this._capabilities[provider]\n return v !== undefined && v !== false\n }\n\n /**\n * `prepareRename` is advertised only by the OBJECT form `renameProvider: {prepareProvider:true}`\n * — the boolean `supports()` helper cannot detect it, so the engine must check this before\n * calling `prepareRename` (bare `renameProvider: true` supports rename but not prepare).\n */\n get supportsPrepareRename(): boolean {\n const rp = this._capabilities.renameProvider as\n | { prepareProvider?: boolean }\n | boolean\n | undefined\n return typeof rp === 'object' && rp !== null && rp.prepareProvider === true\n }\n\n /**\n * Whether the server accepts `workspace/didChangeWorkspaceFolders` — the gate for the manager's\n * grow-only warm-server reuse. Advertised by `ServerCapabilities.workspace.workspaceFolders`:\n * `supported !== false` AND `changeNotifications` truthy (the spec allows `true` OR a string\n * registration id). rust-analyzer advertises it; the captured tsserver 5.3.0 does NOT, so the\n * manager falls back to spawning a fresh per-group server there.\n */\n get supportsWorkspaceFolderChange(): boolean {\n const ws = this._capabilities.workspace as\n | { workspaceFolders?: { supported?: boolean; changeNotifications?: boolean | string } }\n | undefined\n const wf = ws?.workspaceFolders\n if (wf === undefined || wf.supported === false) return false\n return wf.changeNotifications === true || typeof wf.changeNotifications === 'string'\n }\n\n /**\n * Handshake. Registers the deadlock-safe inbound handlers + the `$/progress` listener\n * BEFORE `listen()`, advertises the preferred encodings, then reads back the negotiated\n * encoding + capabilities + provenance and sends `initialized`.\n */\n async initialize(\n rootUri: string,\n opts: {\n initializationOptions?: unknown\n /** Multi-root workspace folders. Defaults to the single `[{rootUri, 'root'}]` (ADR 0011 tail). */\n workspaceFolders?: { uri: string; name: string }[]\n } = {},\n ): Promise<InitializeSummary> {\n this.installInboundHandlers()\n if (!this.listening) {\n this.conn.listen()\n this.listening = true\n }\n const result = (await this.conn.sendRequest(InitializeRequest.method, {\n processId: process.pid ?? null,\n clientInfo: { name: 'sackville-lsp' },\n rootUri,\n capabilities: {\n general: { positionEncodings: PREFERRED_ENCODINGS },\n textDocument: {\n synchronization: { dynamicRegistration: false },\n definition: { linkSupport: true },\n typeDefinition: { linkSupport: true },\n references: {},\n hover: { contentFormat: ['markdown', 'plaintext'] },\n documentSymbol: { hierarchicalDocumentSymbolSupport: true },\n // Push diagnostics (ADR 0011 staged tail): advertise client support so the server sends\n // related-information + tags. There is NO `*Provider` to gate on — every server may push.\n publishDiagnostics: {\n relatedInformation: true,\n tagSupport: { valueSet: [1, 2] },\n versionSupport: true,\n codeDescriptionSupport: true,\n },\n // Pull diagnostics (ADR 0011 staged tail): advertise client support so a server that\n // prefers the pull model (e.g. rust-analyzer's `diagnosticProvider`) enables\n // `textDocument/diagnostic`. `relatedDocumentSupport:false` — v1 reads one file's report.\n diagnostic: { dynamicRegistration: false, relatedDocumentSupport: false },\n callHierarchy: { dynamicRegistration: false },\n rename: {\n dynamicRegistration: false,\n prepareSupport: true,\n prepareSupportDefaultBehavior: 1,\n },\n },\n window: { workDoneProgress: true },\n workspace: {\n configuration: true,\n workspaceFolders: true,\n // workspace/symbol search (ADR 0011 staged tail). No `resolveSupport` — v1 does not do\n // the `workspaceSymbol/resolve` round-trip, so the server returns full `Location`s\n // (range present) rather than the uri-only `WorkspaceSymbol` form.\n symbol: { dynamicRegistration: false },\n // Write-mode (ADR 0011 addendum): advertise WorkspaceEdit support so the server returns\n // a good rename edit. We DO apply file ops now, so advertise `resourceOperations` —\n // some servers (rust-analyzer's module rename) REFUSE the rename otherwise, since the\n // edit they'd produce needs a RenameFile. normalizesLineEndings:false — bytes verbatim.\n workspaceEdit: {\n documentChanges: true,\n resourceOperations: ['create', 'rename', 'delete'],\n normalizesLineEndings: false,\n },\n },\n },\n workspaceFolders: opts.workspaceFolders ?? [{ uri: rootUri, name: 'root' }],\n initializationOptions: opts.initializationOptions,\n })) as InitializeResult\n\n this._capabilities = result.capabilities ?? {}\n this._serverInfo = result.serverInfo\n this._encoding = resolvePositionEncoding(\n this._capabilities.positionEncoding as string | undefined,\n )\n this.conn.sendNotification(InitializedNotification.method, {})\n return {\n encoding: this._encoding,\n serverInfo: this._serverInfo,\n capabilities: this._capabilities,\n }\n }\n\n /** Register the id-bearing inbound replies (deadlock-safe) + the `$/progress` tracker. */\n private installInboundHandlers(): void {\n this.conn.onRequest(ConfigurationRequest.method, (params: { items?: unknown[] }) =>\n (params?.items ?? []).map(() => null),\n )\n this.conn.onRequest(WorkDoneProgressCreateRequest.method, () => null)\n this.conn.onRequest(RegistrationRequest.method, () => null)\n this.conn.onRequest(UnregistrationRequest.method, () => null)\n // Server-initiated `workspace/applyEdit` (e.g. a server that drives renames by asking the\n // client to apply). Its result is an OBJECT (not null) — an unanswered id-bearing request\n // deadlocks the shared server. Sackville applies rename edits itself, so we DECLINE.\n this.conn.onRequest(ApplyWorkspaceEditRequest.method, () => ({\n applied: false,\n failureReason: 'sackville applies rename edits itself; server-initiated edits are declined',\n }))\n // Push diagnostics: cache the latest per uri, clear the \"awaiting first publish\" flag, and wake\n // any `documentDiagnostics` waiter. An empty `diagnostics` array is a legitimate publish (the\n // server clearing a now-clean file), so it counts as a real answer.\n this.conn.onNotification(\n PublishDiagnosticsNotification.method,\n (p: { uri: string; diagnostics?: Diagnostic[] }) => {\n this.diagnostics.set(p.uri, { items: p.diagnostics ?? [] })\n this.awaitingDiagnostics.delete(p.uri)\n for (const w of this.diagnosticsWaiters.get(p.uri)?.splice(0) ?? []) w()\n },\n )\n this.conn.onNotification('$/progress', (p: ProgressParams) => {\n const kind = p?.value?.kind\n if (kind === 'begin') this.activeProgress.add(p.token)\n else if (kind === 'end') {\n this.activeProgress.delete(p.token)\n // The project finished loading — wake anyone waiting for the index to settle.\n if (this.activeProgress.size === 0) {\n for (const w of this.drainWaiters.splice(0)) w()\n }\n }\n })\n }\n\n /** A promise that resolves the next time indexing drains to empty (an `end` clears the set). */\n private indexingDrain(): Promise<void> {\n return new Promise((resolve) => this.drainWaiters.push(resolve))\n }\n\n /**\n * Wait until the server is NOT indexing, bounded by `deadline`. Returns true if it settled,\n * false if the deadline elapsed while still indexing. Event-driven (resolves on the `$/progress`\n * `end`), with the injected `delay` as the deadline backstop — so the gate drives it\n * deterministically and a real session blocks no longer than the operator timeout.\n */\n private async awaitIndexingSettled(deadline: number): Promise<boolean> {\n while (this.indexing) {\n const remaining = deadline - this.now()\n if (remaining <= 0) return false\n const drained = this.indexingDrain()\n if (!this.indexing) return true // an `end` raced in between the check and the registration\n await Promise.race([drained, this.delay(remaining)])\n }\n return true\n }\n\n /** Open a document full-text once (version 1); subsequent calls just bump the refcount. */\n ensureOpen(uri: string, languageId: string, text: string): void {\n const entry = this.open.get(uri)\n if (entry !== undefined) {\n entry.refs += 1\n return\n }\n this.open.set(uri, { refs: 1, version: 1, languageId })\n // A fresh open triggers the server's first diagnostics publish for this uri; mark it pending so\n // `documentDiagnostics` waits for that publish rather than trusting a stale/absent cache.\n this.awaitingDiagnostics.add(uri)\n this.conn.sendNotification(DidOpenTextDocumentNotification.method, {\n textDocument: { uri, languageId, version: 1, text },\n })\n }\n\n /** Decrement a document's refcount (keeps the entry + version). Does NOT `didClose`. */\n releaseDoc(uri: string): void {\n const entry = this.open.get(uri)\n if (entry === undefined) return\n entry.refs = Math.max(0, entry.refs - 1)\n }\n\n /**\n * After Sackville writes `newText` to `uri` on disk (write-mode, ADR 0011 addendum), resync the\n * server's in-memory buffer with a **full-text `didChange`** so a later navigation sees\n * post-rename positions (we never `didClose`, so the server still holds the pre-rename text).\n * The version is **pre-incremented** — it must be strictly greater than the last `didOpen`/\n * `didChange` or the server ignores the change. No-op for a uri the server never opened (it\n * re-reads fresh on the next `didOpen`). Full-text (no incremental ranges) — correctness over\n * bytes, and it avoids re-introducing offset math on the server-bound path.\n */\n applyEdited(uri: string, newText: string): void {\n const entry = this.open.get(uri)\n if (entry === undefined) return\n entry.version += 1\n this.conn.sendNotification(DidChangeTextDocumentNotification.method, {\n textDocument: { uri, version: entry.version },\n contentChanges: [{ text: newText }],\n })\n }\n\n /**\n * A file moved on disk (resource-op `RenameFile`). The open-once/no-`didClose` invariant keys the\n * server buffer by `oldUri`, which now names a non-existent path — a later query would be silently\n * wrong (the worst failure class). MIGRATE the open entry: `didClose(oldUri)` + `didOpen(newUri)`\n * with the moved text, carrying the refcount and the languageId to the new key. NO-OP if `oldUri`\n * was never opened (Sackville opens only the queried file, so the renamed file is usually closed).\n * Run inside the held multi-URI lock so no concurrent query races the key migration.\n */\n didFileRename(oldUri: string, newUri: string, newText: string): void {\n const entry = this.open.get(oldUri)\n if (entry === undefined) return\n this.open.delete(oldUri)\n this.diagnostics.delete(oldUri)\n this.awaitingDiagnostics.delete(oldUri)\n this.conn.sendNotification(DidCloseTextDocumentNotification.method, {\n textDocument: { uri: oldUri },\n })\n // Reopen under the new uri (a fresh document → version restarts at 1), carrying the refcount.\n this.open.set(newUri, { refs: entry.refs, version: 1, languageId: entry.languageId })\n this.awaitingDiagnostics.add(newUri)\n this.conn.sendNotification(DidOpenTextDocumentNotification.method, {\n textDocument: { uri: newUri, languageId: entry.languageId, version: 1, text: newText },\n })\n }\n\n /**\n * A file was deleted on disk (resource-op `DeleteFile`). `didClose` + evict the open entry so the\n * server stops tracking a buffer for a path that no longer exists. NO-OP if it was never opened.\n */\n didFileDelete(uri: string): void {\n if (this.open.get(uri) === undefined) return\n this.open.delete(uri)\n this.diagnostics.delete(uri)\n this.awaitingDiagnostics.delete(uri)\n this.conn.sendNotification(DidCloseTextDocumentNotification.method, {\n textDocument: { uri },\n })\n }\n\n /**\n * Notify the server that the workspace-folder set changed (`workspace/didChangeWorkspaceFolders`)\n * — the wire primitive behind the manager's grow-only warm-server reuse. A fire-and-forget\n * notification, so it is ordered on the single connection BEFORE any subsequent request: a query\n * sent right after sees the new folder scope. Capability gating is the caller's job (the manager\n * only calls this when {@link supportsWorkspaceFolderChange}); the folders are operator-allowlisted\n * roots, never agent-supplied paths.\n */\n changeWorkspaceFolders(\n added: { uri: string; name: string }[],\n removed: { uri: string; name: string }[],\n ): void {\n this.conn.sendNotification(DidChangeWorkspaceFoldersNotification.method, {\n event: { added, removed },\n })\n }\n\n async definition(\n uri: string,\n position: { line: number; character: number },\n ): Promise<NavResult<NormalizedLocation[]>> {\n if (!this.supports('definitionProvider')) {\n throw new LspUnsupportedError('server does not advertise definition support')\n }\n return this.navigateLocations(DefinitionRequest.method, { textDocument: { uri }, position })\n }\n\n async typeDefinition(\n uri: string,\n position: { line: number; character: number },\n ): Promise<NavResult<NormalizedLocation[]>> {\n if (!this.supports('typeDefinitionProvider')) {\n throw new LspUnsupportedError('server does not advertise typeDefinition support')\n }\n return this.navigateLocations(TypeDefinitionRequest.method, { textDocument: { uri }, position })\n }\n\n async references(\n uri: string,\n position: { line: number; character: number },\n ): Promise<NavResult<NormalizedLocation[]>> {\n if (!this.supports('referencesProvider')) {\n throw new LspUnsupportedError('server does not advertise references support')\n }\n return this.navigateLocations(ReferencesRequest.method, {\n textDocument: { uri },\n position,\n context: { includeDeclaration: true },\n })\n }\n\n async hover(\n uri: string,\n position: { line: number; character: number },\n ): Promise<NavResult<NormalizedHover | null>> {\n if (!this.supports('hoverProvider')) {\n throw new LspUnsupportedError('server does not advertise hover support')\n }\n return this.withRetry(\n () => this.conn.sendRequest(HoverRequest.method, { textDocument: { uri }, position }),\n (raw) => normalizeHover(raw as Hover | null),\n (h) => h === null,\n )\n }\n\n /** Document symbols — the file outline. Position-less (whole document); tri-state like the rest. */\n async documentSymbols(uri: string): Promise<NavResult<NormalizedSymbol[]>> {\n if (!this.supports('documentSymbolProvider')) {\n throw new LspUnsupportedError('server does not advertise documentSymbol support')\n }\n return this.withRetry(\n () => this.conn.sendRequest(DocumentSymbolRequest.method, { textDocument: { uri } }),\n (raw) => normalizeDocumentSymbols(raw as DocumentSymbol[] | SymbolInformation[] | null),\n (syms) => syms.length === 0,\n )\n }\n\n /** A promise that resolves on the next `publishDiagnostics` for `uri`. */\n private nextDiagnostics(uri: string): Promise<void> {\n return new Promise((resolve) => {\n const list = this.diagnosticsWaiters.get(uri) ?? []\n list.push(resolve)\n this.diagnosticsWaiters.set(uri, list)\n })\n }\n\n /**\n * Diagnostics for an OPEN document (ADR 0011 staged tail; PUSH model). NOT capability-gated —\n * `textDocument/publishDiagnostics` is a server notification every server may send, and tsserver\n * advertises no `diagnosticProvider` (pull diagnostics are staged). The caller (manager.run) has\n * already `didOpen`ed the file, which triggers the server's publish.\n *\n * Readiness (grounded in the captured timeline — `didOpen` → `$/progress` begin/end → publish\n * ~60ms AFTER the project loads): wait out the project-load `$/progress`, then return the publish\n * once the file's first post-open publish has arrived. An EMPTY publish is a legitimate `ok` (a\n * clean file), never `no_result`. If the project never settles or no publish arrives within the\n * deadline ⇒ `not_ready` (retry), the same honest-tri-state posture as the navigation reads.\n */\n async documentDiagnostics(uri: string): Promise<NavResult<NormalizedDiagnostic[]>> {\n // Prefer the PULL model when the server advertises `diagnosticProvider` (a deterministic\n // request/response, ideal for single-shot — no race on \"did the publish already land?\"). Fall\n // back to the PUSH model otherwise (tsserver advertises no provider). Same result shape either\n // way, so the query engine / surface are agnostic to which path ran.\n if (this.supports('diagnosticProvider')) return this.pullDiagnostics(uri)\n return this.pushDiagnostics(uri)\n }\n\n /** PUSH model — accumulate the server's `publishDiagnostics` for an open file (see {@link documentDiagnostics}). */\n private async pushDiagnostics(uri: string): Promise<NavResult<NormalizedDiagnostic[]>> {\n const deadline = this.now() + this.timeoutMs\n while (true) {\n await this.awaitIndexingSettled(deadline)\n // Once the project is loaded AND the file's first post-open publish has landed, the cached\n // diagnostics are authoritative (a warm re-query takes this path immediately).\n if (!this.indexing && !this.awaitingDiagnostics.has(uri)) {\n const cached = this.diagnostics.get(uri)\n return this.wrap('ok', normalizeDiagnostics(cached?.items))\n }\n const remaining = deadline - this.now()\n if (remaining <= 0) {\n const cached = this.diagnostics.get(uri)\n return this.wrap('not_ready', normalizeDiagnostics(cached?.items))\n }\n await Promise.race([this.nextDiagnostics(uri), this.delay(remaining)])\n }\n }\n\n /**\n * PULL model — `textDocument/diagnostic` (LSP 3.17), capability-gated on `diagnosticProvider`.\n * A request/response (unlike push), so it is deterministic for a single-shot read. Echoes the\n * provider's `identifier` when one was advertised (rust-analyzer requires it). Tri-state, with\n * the diagnostics-specific twist that an EMPTY report is a legitimate `ok` (a clean file), NEVER\n * `no_result` — so this does NOT reuse {@link withRetry} (whose empty ⇒ no_result is wrong here).\n * Readiness: wait out the project-load `$/progress`; if the send (re)starts indexing, re-query the\n * loaded project within the deadline; a soft \"not ready\" `ResponseError` backs off and retries\n * inside the deadline. Still indexing at the deadline ⇒ `not_ready` (retry).\n */\n private async pullDiagnostics(uri: string): Promise<NavResult<NormalizedDiagnostic[]>> {\n const provider = this._capabilities.diagnosticProvider as { identifier?: string } | undefined\n const deadline = this.now() + this.timeoutMs\n let attempt = 0\n while (true) {\n await this.awaitIndexingSettled(deadline)\n if (this.indexing) return this.wrap('not_ready', []) // deadline hit still indexing\n let report: RawDocumentDiagnosticReport | null = null\n let softNotReady = false\n try {\n report = (await this.conn.sendRequest(DocumentDiagnosticRequest.method, {\n textDocument: { uri },\n ...(provider?.identifier ? { identifier: provider.identifier } : {}),\n })) as RawDocumentDiagnosticReport | null\n } catch (err) {\n if (!(err instanceof ResponseError) || !SOFT_NOT_READY_CODES.has(err.code)) throw err\n softNotReady = true\n }\n // The send itself may have kicked off the configured-project load — re-query once settled.\n if (this.indexing && this.now() < deadline) continue\n if (!softNotReady)\n return this.wrap(this.indexing ? 'not_ready' : 'ok', diagnosticsFromReport(report))\n // Soft error while settled: it isn't ready yet. Back off and retry inside the deadline.\n attempt += 1\n const backoff = Math.min(this.baseBackoffMs * 2 ** (attempt - 1), this.maxBackoffMs)\n if (this.noRetry || this.now() + backoff > deadline) return this.wrap('not_ready', [])\n await this.delay(backoff)\n }\n }\n\n /**\n * `workspace/symbol` — project-wide symbol search by name (ADR 0011 staged tail). Position-less\n * and file-less: the query is just a name fragment matched against the whole indexed workspace,\n * so it needs no open document (the project is loaded at `initialize`). Tri-state like the rest —\n * an empty result while the project is still indexing is `not_ready`, never collapsed into\n * \"no such symbol\" (the cold-load trap the rest of the client already guards). Handles both the\n * flat `SymbolInformation[]` (range present) and the uri-only `WorkspaceSymbol[]` shapes.\n */\n async workspaceSymbols(query: string): Promise<NavResult<NormalizedWorkspaceSymbol[]>> {\n if (!this.supports('workspaceSymbolProvider')) {\n throw new LspUnsupportedError('server does not advertise workspace symbol support')\n }\n return this.withRetry(\n () => this.conn.sendRequest(WorkspaceSymbolRequest.method, { query }),\n (raw) => normalizeWorkspaceSymbols(raw as WorkspaceSymbol[] | null),\n (syms) => syms.length === 0,\n )\n }\n\n /**\n * `textDocument/prepareRename` — the cheap validate-first pre-flight (write-mode, ADR 0011\n * addendum). Tri-state: `null` while indexing ⇒ `not_ready`; `null` while ready ⇒ `no_result`\n * (the engine maps that to a structured \"not renameable here\" refusal); a non-null outcome ⇒\n * `ok`. Only callable when {@link supportsPrepareRename} (the engine skips it otherwise).\n */\n async prepareRename(\n uri: string,\n position: { line: number; character: number },\n ): Promise<NavResult<PrepareRenameOutcome | null>> {\n if (!this.supportsPrepareRename) {\n throw new LspUnsupportedError('server does not advertise prepareRename support')\n }\n return this.withRetry(\n () => this.conn.sendRequest(PrepareRenameRequest.method, { textDocument: { uri }, position }),\n (raw) => normalizePrepareRename(raw as RawPrepareRename | null),\n (outcome) => outcome === null,\n )\n }\n\n /**\n * `textDocument/rename` — compute the cross-file `WorkspaceEdit` for renaming the symbol at\n * `position` to `newName`. Capability-gated on `renameProvider`; normalized to the uniform\n * `files`/`resourceOps` shape; tri-state (empty/`null` while indexing ⇒ `not_ready`). This\n * computes only — applying the edit to disk is the gated engine's job (Slice F), never here.\n */\n async rename(\n uri: string,\n position: { line: number; character: number },\n newName: string,\n ): Promise<NavResult<NormalizedWorkspaceEdit>> {\n if (!this.supports('renameProvider')) {\n throw new LspUnsupportedError('server does not advertise rename support')\n }\n return this.withRetry(\n () =>\n this.conn.sendRequest(RenameRequest.method, { textDocument: { uri }, position, newName }),\n (raw) => normalizeWorkspaceEdit(raw as RawWorkspaceEdit | null),\n (we) => we.files.length === 0 && we.resourceOps.length === 0,\n )\n }\n\n /**\n * Call hierarchy — a TWO-round-trip protocol. `prepareCallHierarchy` resolves the symbol at\n * `position` to one or more `CallHierarchyItem`s (null vs empty is distinct; overloads yield\n * MANY — we keep them all, never silently the first); then per item we fetch incoming or\n * outgoing calls. Tri-state lives on the PREPARE step (empty-while-indexing ⇒ not_ready). A\n * prepared item with no callers/callees is a legitimate `ok` with empty `calls`. The RAW item\n * is passed back to the calls request (it may carry an opaque `data` field the server needs).\n */\n async callHierarchy(\n uri: string,\n position: { line: number; character: number },\n direction: CallDirection,\n ): Promise<NavResult<CallHierarchyGroup[]>> {\n if (!this.supports('callHierarchyProvider')) {\n throw new LspUnsupportedError('server does not advertise callHierarchy support')\n }\n const prepared = await this.withRetry(\n () =>\n this.conn.sendRequest(CallHierarchyPrepareRequest.method, {\n textDocument: { uri },\n position,\n }),\n (raw) => (raw as CallHierarchyItem[] | null) ?? [],\n (items) => items.length === 0,\n )\n if (prepared.status !== 'ok') return this.wrap(prepared.status, [])\n\n const method =\n direction === 'incoming'\n ? CallHierarchyIncomingCallsRequest.method\n : CallHierarchyOutgoingCallsRequest.method\n const groups: CallHierarchyGroup[] = []\n for (const item of prepared.result) {\n const raw = await this.conn.sendRequest(method, { item })\n const calls =\n direction === 'incoming'\n ? normalizeIncomingCalls(\n raw as\n | { from: CallHierarchyItem; fromRanges: NormalizedLocation['range'][] }[]\n | null,\n )\n : normalizeOutgoingCalls(\n raw as { to: CallHierarchyItem; fromRanges: NormalizedLocation['range'][] }[] | null,\n )\n groups.push({ source: normalizeCallHierarchyItem(item), calls })\n }\n return this.wrap('ok', groups)\n }\n\n private navigateLocations(\n method: string,\n params: unknown,\n ): Promise<NavResult<NormalizedLocation[]>> {\n return this.withRetry(\n () => this.conn.sendRequest(method, params),\n (raw) => normalizeLocations(raw as Location | Location[] | LocationLink[] | null),\n (locs) => locs.length === 0,\n )\n }\n\n /**\n * The tri-state request loop: settle → send → decide, all inside one operator deadline.\n *\n * The load-bearing rule (ADR 0011 addendum — proven by a live `typescript-language-server`\n * capture): **a result returned while the server is still indexing the project is NOT\n * trustworthy** — tsserver answers an early request from a single-file *inferred* project\n * (a non-empty BUT PARTIAL answer — e.g. a cross-file rename that sees only the opened file)\n * and only *then* finishes loading the configured project. So:\n *\n * 1. Before sending, wait out any in-flight indexing (`awaitIndexingSettled`) so we hit the\n * loaded project.\n * 2. After sending, if indexing is active (the send itself triggered the configured-project\n * load), the answer is from the unstable inferred project — loop to settle + re-query.\n * 3. Once the server is settled: a non-empty result is `ok`; an empty result is `no_result`\n * (retried with bounded backoff) — or `not_ready` only if we hit the deadline still indexing.\n *\n * This trades the old \"return `not_ready` fast\" for \"wait for the correct answer within the\n * deadline\" — correctness over latency, bounded by the operator timeout.\n */\n private async withRetry<T>(\n send: () => Promise<unknown>,\n normalize: (raw: unknown) => T,\n isEmpty: (value: T) => boolean,\n ): Promise<NavResult<T>> {\n const deadline = this.now() + this.timeoutMs\n let attempt = 0\n while (true) {\n // (1) Never query a still-loading project; wait for the indexing $/progress to drain.\n await this.awaitIndexingSettled(deadline)\n let value: T\n let softError = false\n try {\n value = normalize(await send())\n } catch (err) {\n // A server that signals \"not ready / nothing here\" via a recognized ResponseError (e.g.\n // rust-analyzer's -32602) is treated as the EMPTY outcome — readiness decides below. Any\n // other error is a genuine failure and propagates unchanged.\n if (!(err instanceof ResponseError) || !SOFT_NOT_READY_CODES.has(err.code)) throw err\n value = normalize(null)\n softError = true\n }\n // (2) The send may have kicked off the configured-project load and been answered from the\n // inferred project — re-query once it settles (unless we are out of deadline).\n if (this.indexing && this.now() < deadline) continue\n\n const status = decideStatus(isEmpty(value), !this.indexing)\n if (status !== 'no_result') return this.wrap(status, value)\n\n // A settled soft-error is terminal — a genuine \"nothing renameable here\", not eventual\n // consistency; do not spin retrying it (an empty-but-no-error result still gets the backoff).\n if (softError) return this.wrap('no_result', value)\n\n // (3) Empty + settled: retry with bounded backoff strictly inside the deadline.\n attempt += 1\n const backoff = Math.min(this.baseBackoffMs * 2 ** (attempt - 1), this.maxBackoffMs)\n if (this.noRetry || this.now() + backoff > deadline) return this.wrap('no_result', value)\n await this.delay(backoff)\n }\n }\n\n private wrap<T>(status: QueryStatus, result: T): NavResult<T> {\n return { status, result, serverInfo: this._serverInfo, encoding: this._encoding }\n }\n\n /** Graceful teardown: LSP `shutdown` request then the `exit` notification. */\n async shutdown(): Promise<void> {\n try {\n await this.conn.sendRequest(ShutdownRequest.method)\n } catch {\n // a dead/uncooperative server — fall through to exit.\n }\n try {\n this.conn.sendNotification(ExitNotification.method)\n } catch {\n // best-effort.\n }\n }\n}\n","/**\n * The operator-bound language→server registry (ADR 0011). Safety-critical: the agent supplies\n * only a `language` string and NEVER a binary, argv, or path. The operator binds the registry\n * out-of-band (`SACKVILLE_LSP_SERVERS`, JSON); a language absent from it is refused, never\n * spawned.\n *\n * The registry is **JSON with `command` and `args[]` structurally separate** — deliberately\n * NOT a `lang=cmd args;…` mini-DSL the engine would re-split, because real server commands\n * routinely contain spaces, `=`, and wrapper prefixes (`rustup run stable rust-analyzer`).\n * Splitting a string would corrupt those; an explicit array cannot.\n */\n\n/** One operator-registered language server: the binary + argv, kept structurally separate. */\nexport interface ServerRegistryEntry {\n command: string\n args: string[]\n /** Passed verbatim as the LSP `initialize` `initializationOptions` (hardening flags, etc.). */\n initializationOptions?: unknown\n /** The `languageId` sent in `didOpen`; defaults to the registry key when omitted. */\n languageId?: string\n}\n\n/** The full registry, keyed by the agent-facing `language` string. */\nexport type ServerRegistry = Record<string, ServerRegistryEntry>\n\n/** Thrown on a malformed operator registry or a request for an unbound language. */\nexport class LspRegistryError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'LspRegistryError'\n }\n}\n\nfunction isStringArray(v: unknown): v is string[] {\n return Array.isArray(v) && v.every((x) => typeof x === 'string')\n}\n\n/**\n * Parse + validate the operator JSON registry. Fails loud on anything malformed — an\n * operator misconfiguration must be a clear error, never a silently-empty or\n * partially-parsed registry an agent then queries against.\n */\nexport function parseServerRegistry(json: string): ServerRegistry {\n let raw: unknown\n try {\n raw = JSON.parse(json)\n } catch (e) {\n throw new LspRegistryError(`registry is not valid JSON: ${(e as Error).message}`)\n }\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {\n throw new LspRegistryError('registry must be a JSON object keyed by language')\n }\n const out: ServerRegistry = {}\n for (const [language, value] of Object.entries(raw as Record<string, unknown>)) {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new LspRegistryError(`registry entry for \"${language}\" must be an object`)\n }\n const entry = value as Record<string, unknown>\n if (typeof entry.command !== 'string' || entry.command.length === 0) {\n throw new LspRegistryError(\n `registry entry for \"${language}\" needs a non-empty string command`,\n )\n }\n if (entry.args !== undefined && !isStringArray(entry.args)) {\n throw new LspRegistryError(\n `registry entry for \"${language}\" args must be an array of strings`,\n )\n }\n if (entry.languageId !== undefined && typeof entry.languageId !== 'string') {\n throw new LspRegistryError(`registry entry for \"${language}\" languageId must be a string`)\n }\n out[language] = {\n command: entry.command,\n args: (entry.args as string[] | undefined) ?? [],\n ...(entry.initializationOptions !== undefined\n ? { initializationOptions: entry.initializationOptions }\n : {}),\n ...(entry.languageId !== undefined ? { languageId: entry.languageId as string } : {}),\n }\n }\n if (Object.keys(out).length === 0) {\n throw new LspRegistryError('registry is empty — bind at least one language→server')\n }\n return out\n}\n\n/** Resolve a language to its registered server, or refuse it (never spawns an unbound server). */\nexport function resolveServer(registry: ServerRegistry, language: string): ServerRegistryEntry {\n const entry = registry[language]\n if (!entry) {\n const bound = Object.keys(registry).join(', ') || '(none)'\n throw new LspRegistryError(`no server bound for language \"${language}\" (bound: ${bound})`)\n }\n return entry\n}\n","/**\n * The `LanguageServerManager` (ADR 0011, slice 3) — owns the resident language-server\n * subprocesses. The right analogy is the browser pillar's `BrowserManager` (a resident,\n * code-executing subprocess), NOT the short-lived test-runner: lazy spawn, idle reaper,\n * caps, injected clock. Three deliberate divergences the browser analogy hides:\n *\n * - **Keyed by `(language, projectRoot)` and SHARED across MCP sessions** (not one ephemeral\n * context per session). A server is expensive to spawn and *warm* (indexing takes\n * seconds-to-minutes), so it is reused across calls with a longer idle TTL than browser's.\n * - **Per-`(server, uri)` async mutex.** JSON-RPC id-correlation makes concurrent *requests*\n * safe, but the document lifecycle is shared mutable state: two concurrent queries each\n * sending `didOpen(version 1)` on the same file is a protocol violation. The mutex\n * serializes the open+query critical section per file (open-once + refcount lives in the\n * client; the mutex is what makes that race-free under concurrency).\n * - **The reaper respects an in-flight counter** and resets the idle clock on request start:\n * never reap a server with `inFlight > 0`; on reap send LSP `shutdown` → `exit` with a\n * clock-driven grace before the hard `dispose()` (SIGKILL). Deterministic in tests via the\n * injected clock + `sweepIdle(nowMs)`.\n *\n * Safety (re-derived from the browser pillar): `rootUri`/`workspaceFolders` are pinned to the\n * operator-allowlisted `projectRoot` — the agent never supplies a root, and a root outside\n * `allowedRoots` is refused before any spawn. The agent supplies only a `language`; an unbound\n * language is refused by the registry, never spawned. `allowRun` (the paired gate) lives one\n * layer up in `query.ts`.\n */\n\nimport { basename, resolve } from 'node:path'\nimport { pathToFileURL } from 'node:url'\nimport { defaultServerSpawn, LspClient, type LspConnection, type ServerSpawn } from './client.js'\nimport { resolveServer, type ServerRegistry } from './registry.js'\n\n/** Thrown when the manager refuses a request (root outside the allowlist, server cap reached). */\nexport class LspManagerError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'LspManagerError'\n }\n}\n\nexport interface LanguageServerManagerOptions {\n /** The operator-bound language→server registry (the agent picks only a language). */\n registry: ServerRegistry\n /** OPERATOR allowlist of project roots a server may be initialized against. */\n allowedRoots: string[]\n /** Per-request wall-clock cap (ms) handed to each `LspClient`. */\n timeoutMs: number\n /** Injected spawn seam. Default {@link defaultServerSpawn} (real `child_process.spawn`). */\n serverSpawn?: ServerSpawn\n /** Idle time before a server is eligible for reaping, in ms. Default 15 min (warm servers). */\n idleTtlMs?: number\n /** Max concurrent servers. Default 8. */\n maxServers?: number\n /** Grace (ms) between the LSP `shutdown`/`exit` and the hard `dispose()`. Default 2000. */\n shutdownGraceMs?: number\n /** Single-attempt client mode (the gate's deterministic default; production retries). */\n noRetry?: boolean\n /** Injected clock. Default `Date.now`. */\n now?: () => number\n /** Injected delay (the shutdown grace). Default a `setTimeout` promise. */\n delay?: (ms: number) => Promise<void>\n}\n\nexport interface QueryInput {\n /** The agent-facing language id (resolved against the operator registry). */\n language: string\n /** The primary project root — where the queried file lives; must be in `allowedRoots`. */\n projectRoot: string\n /** The document URI being queried (`file://…`). */\n uri: string\n /** The document's full text (sent verbatim in `didOpen`). */\n text: string\n /**\n * Additional allowlisted roots bound as workspace folders on the SAME server (multi-root, ADR\n * 0011 tail). The server is keyed by the sorted root GROUP, so one server handles the whole\n * group and cross-root navigation resolves. Each must be in `allowedRoots`. Default: none.\n */\n workspaceRoots?: string[]\n}\n\ninterface ServerEntry {\n key: string\n language: string\n /** Representative (first sorted) root of the group; equals the single root for a 1-root server. */\n projectRoot: string\n /** The full sorted root group bound as workspace folders (length 1 for a single-root server). */\n roots: string[]\n client: LspClient\n connection: LspConnection\n lastUsedAt: number\n inFlight: number\n /** Per-uri lock chains (the `(server, uri)` mutex). */\n locks: Map<string, Promise<void>>\n}\n\n/** The LSP capabilities `lsp_languages` reports per active server (v1 + staged tools). */\nconst REPORTED_CAPABILITIES: Record<string, string> = {\n definition: 'definitionProvider',\n references: 'referencesProvider',\n hover: 'hoverProvider',\n typeDefinition: 'typeDefinitionProvider',\n documentSymbol: 'documentSymbolProvider',\n workspaceSymbol: 'workspaceSymbolProvider',\n callHierarchy: 'callHierarchyProvider',\n}\n\n/** Provenance for one live server — what `lsp_languages` surfaces (never the command/path). */\nexport interface ServerDescription {\n language: string\n projectRoot: string\n /** The full root group, set only for a multi-root server (length > 1). */\n roots?: string[]\n serverInfo?: { name: string; version?: string }\n capabilities: Record<string, boolean>\n}\n\n/** A server is keyed by `(language, sorted root group)`; parts are joined with `\\x1f` (unit\n * separator) so a path with a space can never collide with the language separator — and, unlike\n * a raw NUL, it keeps this file as plain text for grep/find-replace tooling. */\nconst serverKey = (language: string, roots: string[]): string =>\n `${language}\\x1f${roots.join('\\x1f')}`\n\nconst defaultDelay = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms))\n\nexport class LanguageServerManager {\n private readonly registry: ServerRegistry\n private readonly allowedRoots: string[]\n private readonly timeoutMs: number\n private readonly serverSpawn: ServerSpawn\n private readonly idleTtlMs: number\n private readonly maxServers: number\n private readonly shutdownGraceMs: number\n private readonly noRetry: boolean\n private readonly now: () => number\n private readonly delay: (ms: number) => Promise<void>\n\n private readonly servers = new Map<string, ServerEntry>()\n /** In-flight spawn+initialize, so concurrent first callers share one initialization. */\n private readonly initing = new Map<string, Promise<ServerEntry>>()\n private reaper: ReturnType<typeof setInterval> | undefined\n\n constructor(options: LanguageServerManagerOptions) {\n this.registry = options.registry\n this.allowedRoots = options.allowedRoots\n this.timeoutMs = options.timeoutMs\n this.serverSpawn = options.serverSpawn ?? defaultServerSpawn\n this.idleTtlMs = options.idleTtlMs ?? 15 * 60_000\n this.maxServers = options.maxServers ?? 8\n this.shutdownGraceMs = options.shutdownGraceMs ?? 2000\n this.noRetry = options.noRetry ?? false\n this.now = options.now ?? Date.now\n this.delay = options.delay ?? defaultDelay\n }\n\n get serverCount(): number {\n return this.servers.size\n }\n\n /**\n * Run `fn` against the (shared, lazily-spawned) server for `(language, projectRoot)`, under\n * the per-`(server, uri)` mutex. The document is opened once (refcounted in the client) and\n * the server's idle clock is reset on entry and exit; `inFlight` is held for the duration so\n * the reaper cannot tear the server down mid-request.\n */\n async run<T>(input: QueryInput, fn: (client: LspClient) => Promise<T>): Promise<T> {\n const entry = await this.acquire(input.language, input.projectRoot, input.workspaceRoots)\n return this.withUriLock(entry, input.uri, async () => {\n entry.inFlight += 1\n entry.lastUsedAt = this.now()\n try {\n const languageId = this.registry[input.language]?.languageId ?? input.language\n entry.client.ensureOpen(input.uri, languageId, input.text)\n const result = await fn(entry.client)\n entry.lastUsedAt = this.now()\n return result\n } finally {\n entry.inFlight -= 1\n }\n })\n }\n\n /**\n * Like {@link run}, but holds the per-`(server, uri)` lock for MANY uris at once — the\n * primitive a multi-file write needs (Slice F′). Locks are acquired in a deterministic SORTED\n * order so two concurrent multi-file renames can never deadlock (no lock-ordering cycle), and\n * are all held across the whole `fn` (the stage+commit+`didChange` window). Does NOT `didOpen`\n * any document — the caller opened what it needs during the compute phase (open-once/refcount).\n */\n async runWithUris<T>(\n input: { language: string; projectRoot: string; uris: string[]; workspaceRoots?: string[] },\n fn: (client: LspClient) => Promise<T>,\n ): Promise<T> {\n const entry = await this.acquire(input.language, input.projectRoot, input.workspaceRoots)\n return this.withUriLocks(entry, input.uris, async () => {\n entry.inFlight += 1\n entry.lastUsedAt = this.now()\n try {\n const result = await fn(entry.client)\n entry.lastUsedAt = this.now()\n return result\n } finally {\n entry.inFlight -= 1\n }\n })\n }\n\n /**\n * The resolved, sorted, de-duplicated root group for a query — the primary `projectRoot` plus any\n * `workspaceRoots`. EVERY member is `assertRootAllowed`'d (refused before any spawn), so a\n * multi-root group cannot smuggle in an un-allowlisted folder.\n */\n private resolveGroup(projectRoot: string, workspaceRoots: string[]): string[] {\n const all = [projectRoot, ...workspaceRoots].map((r) => resolve(r))\n for (const r of all) this.assertRootAllowed(r)\n return [...new Set(all)].sort()\n }\n\n /** Resolve (and lazily spawn+initialize) the shared server for `(language, root group)`. */\n private async acquire(\n language: string,\n projectRoot: string,\n workspaceRoots: string[] = [],\n ): Promise<ServerEntry> {\n const roots = this.resolveGroup(projectRoot, workspaceRoots)\n const spec = resolveServer(this.registry, language) // throws (unbound) BEFORE any spawn\n const key = serverKey(language, roots)\n const existing = this.servers.get(key)\n if (existing) {\n existing.lastUsedAt = this.now()\n return existing\n }\n // Grow-only reuse: rather than spawning a fresh server (and re-paying indexing), extend a warm\n // same-language server whose folder GROUP is a strict subset of this one — sending the delta via\n // `workspace/didChangeWorkspaceFolders` and re-keying it to the larger group. Every member of\n // `roots` is already allowlist-gated (resolveGroup), so a grow can never smuggle in an\n // un-allowlisted folder. This whole branch is synchronous (no `await`) so two concurrent acquires\n // can't both mutate the same warm server — the first runs to its re-key before the second scans.\n const grown = this.tryGrowExisting(language, roots, key)\n if (grown) return grown\n const pending = this.initing.get(key)\n if (pending) return pending\n const p = this.spawnAndInit(language, roots, key, spec)\n this.initing.set(key, p)\n try {\n return await p\n } finally {\n this.initing.delete(key)\n }\n }\n\n /**\n * Find the UNIQUELY-largest warm server whose root group is a strict subset of `roots` (same\n * language, and it must advertise workspace-folder change support), grow it to `roots` in place,\n * and re-key it. Returns the grown entry, or `undefined` when there is no candidate or the largest\n * subset is ambiguous (≥2 candidates tie for the most roots) — in which case we spawn fresh rather\n * than guess which to mutate. Grow-only: a subset request of an existing LARGER server is NOT\n * shrunk (it spawns its own server) — keeping keys, `describe()`, and confinement unambiguous.\n */\n private tryGrowExisting(\n language: string,\n roots: string[],\n newKey: string,\n ): ServerEntry | undefined {\n const group = new Set(roots)\n const candidates = [...this.servers.values()].filter(\n (e) =>\n e.language === language &&\n e.client.supportsWorkspaceFolderChange &&\n e.roots.length < roots.length &&\n e.roots.every((r) => group.has(r)),\n )\n if (candidates.length === 0) return undefined\n const maxSize = Math.max(...candidates.map((e) => e.roots.length))\n const largest = candidates.filter((e) => e.roots.length === maxSize)\n if (largest.length !== 1) return undefined // ambiguous tie — spawn fresh, never guess\n const entry = largest[0] as ServerEntry\n const added = roots\n .filter((r) => !entry.roots.includes(r))\n .map((r) => ({ uri: pathToFileURL(r).toString(), name: basename(r) }))\n entry.client.changeWorkspaceFolders(added, [])\n this.servers.delete(entry.key)\n entry.key = newKey\n entry.roots = roots\n entry.projectRoot = roots[0] ?? ''\n entry.lastUsedAt = this.now()\n this.servers.set(newKey, entry)\n return entry\n }\n\n private async spawnAndInit(\n language: string,\n roots: string[],\n key: string,\n spec: { command: string; args: string[]; initializationOptions?: unknown },\n ): Promise<ServerEntry> {\n if (this.servers.size >= this.maxServers) {\n throw new LspManagerError(`max servers reached (${this.maxServers}); reap or wait`)\n }\n const connection = this.serverSpawn({\n command: spec.command,\n args: spec.args,\n initializationOptions: spec.initializationOptions,\n })\n const client = new LspClient(connection.connection, {\n timeoutMs: this.timeoutMs,\n noRetry: this.noRetry,\n now: this.now,\n delay: this.delay,\n })\n // rootUri/workspaceFolders pinned to the allowlisted root GROUP — never an agent input. Every\n // group root is allowlisted (resolveGroup). rootUri is the deprecated single-root field, so it\n // takes the first (sorted) root; workspaceFolders is the authoritative multi-root list.\n const folders = roots.map((r) => ({ uri: pathToFileURL(r).toString(), name: basename(r) }))\n await client.initialize(folders[0]?.uri ?? '', {\n initializationOptions: spec.initializationOptions,\n workspaceFolders: folders,\n })\n const entry: ServerEntry = {\n key,\n language,\n projectRoot: roots[0] ?? '',\n roots,\n client,\n connection,\n lastUsedAt: this.now(),\n inFlight: 0,\n locks: new Map(),\n }\n this.servers.set(key, entry)\n return entry\n }\n\n /** Provenance for every currently-live server (drives the always-on `lsp_languages` tool). */\n describe(): ServerDescription[] {\n return [...this.servers.values()].map((entry) => {\n const capabilities: Record<string, boolean> = {}\n for (const [name, provider] of Object.entries(REPORTED_CAPABILITIES)) {\n capabilities[name] = entry.client.supports(provider)\n }\n return {\n language: entry.language,\n projectRoot: entry.projectRoot,\n ...(entry.roots.length > 1 ? { roots: entry.roots } : {}),\n ...(entry.client.serverInfo ? { serverInfo: entry.client.serverInfo } : {}),\n capabilities,\n }\n })\n }\n\n private assertRootAllowed(projectRoot: string): void {\n const root = resolve(projectRoot)\n const allowed = this.allowedRoots.map((r) => resolve(r))\n if (!allowed.includes(root)) {\n throw new LspManagerError(`project root ${projectRoot} is not in the operator allowlist`)\n }\n }\n\n /** Serialize `fn` against all other callers holding the same `(server, uri)` lock. */\n private async withUriLock<T>(entry: ServerEntry, uri: string, fn: () => Promise<T>): Promise<T> {\n const prev = entry.locks.get(uri) ?? Promise.resolve()\n let release!: () => void\n const current = new Promise<void>((r) => {\n release = r\n })\n entry.locks.set(\n uri,\n prev.then(() => current),\n )\n await prev\n try {\n return await fn()\n } finally {\n release()\n }\n }\n\n /**\n * Acquire the per-uri locks for EVERY uri (deduped, SORTED — deadlock-free ordering) and hold\n * them all across `fn`, releasing in reverse on exit. Each lock chains on the same per-uri\n * promise the single-uri `withUriLock` uses, so a multi-file write serializes correctly against\n * any concurrent single-file query on one of its files.\n */\n private async withUriLocks<T>(\n entry: ServerEntry,\n uris: string[],\n fn: () => Promise<T>,\n ): Promise<T> {\n const sorted = [...new Set(uris)].sort()\n const releases: Array<() => void> = []\n for (const uri of sorted) {\n const prev = entry.locks.get(uri) ?? Promise.resolve()\n let release!: () => void\n const current = new Promise<void>((r) => {\n release = r\n })\n entry.locks.set(\n uri,\n prev.then(() => current),\n )\n await prev\n releases.push(release)\n }\n try {\n return await fn()\n } finally {\n for (const release of releases.reverse()) release()\n }\n }\n\n /**\n * Reap every server idle for at least `idleTtlMs` that has NO in-flight request. Returns the\n * reaped keys. Drives the deterministic reaper logic; `nowMs` defaults to the injected clock.\n */\n async sweepIdle(nowMs: number = this.now()): Promise<string[]> {\n const reaped: string[] = []\n for (const [key, entry] of this.servers) {\n if (entry.inFlight > 0) continue // never reap a server mid-request\n if (nowMs - entry.lastUsedAt >= this.idleTtlMs) reaped.push(key)\n }\n await Promise.all(reaped.map((key) => this.reap(key)))\n return reaped\n }\n\n private async reap(key: string): Promise<void> {\n const entry = this.servers.get(key)\n if (!entry) return\n this.servers.delete(key)\n await this.gracefulStop(entry)\n }\n\n /** LSP `shutdown` → `exit` (graceful), then a clock-driven grace, then the hard `dispose()`. */\n private async gracefulStop(entry: ServerEntry): Promise<void> {\n try {\n await entry.client.shutdown()\n } catch {\n // a dead/uncooperative server — fall through to the hard dispose.\n }\n await this.delay(this.shutdownGraceMs)\n entry.connection.dispose()\n }\n\n /** Start the production idle reaper (a `setInterval` driving the injected-clock `sweepIdle`). */\n startReaper(intervalMs: number): void {\n this.stopReaper()\n this.reaper = setInterval(() => {\n void this.sweepIdle()\n }, intervalMs)\n this.reaper.unref?.()\n }\n\n stopReaper(): void {\n if (this.reaper) {\n clearInterval(this.reaper)\n this.reaper = undefined\n }\n }\n\n /** Gracefully stop and dispose every server; the manager can be reused afterward. */\n async shutdown(): Promise<void> {\n this.stopReaper()\n const entries = [...this.servers.values()]\n this.servers.clear()\n this.initing.clear()\n await Promise.all(entries.map((entry) => this.gracefulStop(entry)))\n }\n}\n","/**\n * The shared paired-gate + path-confinement guards for the LSP engines (ADR 0011). Factored\n * out of `query.ts` so the read engine (`LspQueryEngine`) and the write engine\n * (`LspRenameEngine`, Slice F) share one implementation — with a `resolveSymlinks` mode that\n * the WRITE path always sets.\n *\n * The read path confines the single queried file lexically (lower stakes). The write path must\n * confine EVERY file a `WorkspaceEdit` would touch, and lexically is not enough: `resolve()`\n * does not canonicalize symlinks, so a symlink INSIDE the root pointing OUTSIDE it passes a\n * prefix check and a write would clobber the out-of-root target. The write path therefore\n * `realpath`-canonicalizes the root and each target's nearest existing ancestor and re-asserts\n * containment, and refuses any non-`file://` scheme. Confinement is pure path/metadata work —\n * it runs BEFORE any target file content is read.\n */\n\nimport { existsSync, realpathSync } from 'node:fs'\nimport { basename, dirname, resolve, sep } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\n/** Thrown when the paired operator gate denies a query/edit (allowRun off, out-of-bounds, bad scheme). */\nexport class LspGateError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'LspGateError'\n }\n}\n\n/** The paired deny-by-default gate: `allowRun` + the operator root allowlist. */\nexport function assertAllowed(\n allowRun: boolean,\n allowedRoots: string[],\n projectRoot: string,\n): void {\n if (!allowRun) {\n throw new LspGateError('LSP navigation is not enabled (the operator must set allowRun)')\n }\n const root = resolve(projectRoot)\n if (!allowedRoots.map((r) => resolve(r)).includes(root)) {\n throw new LspGateError(`project root ${projectRoot} is not in the operator allowlist`)\n }\n}\n\n/** Lexical containment: `child` must equal `root` or sit beneath it. */\nfunction assertInside(root: string, child: string, message: string): void {\n if (child !== root && !child.startsWith(root + sep)) {\n throw new LspGateError(message)\n }\n}\n\n/** Canonicalize `abs` by realpath-ing its deepest existing ancestor and re-appending the tail. */\nfunction realpathNearest(abs: string): string {\n let existing = abs\n const tail: string[] = []\n while (!existsSync(existing)) {\n const parent = dirname(existing)\n if (parent === existing) break // filesystem root\n tail.unshift(basename(existing))\n existing = parent\n }\n const real = realpathSync(existing)\n return tail.length > 0 ? resolve(real, ...tail) : real\n}\n\n/**\n * Confine a project-relative-or-absolute `file` to `projectRoot`, returning the absolute path.\n * Read path: lexical (default). Write path (`resolveSymlinks: true`): additionally\n * realpath-canonicalizes the root + the target's nearest existing ancestor and re-asserts\n * containment, closing the symlink-escape hole a lexical `resolve()` misses.\n */\nexport function confineFile(\n projectRoot: string,\n file: string,\n opts: { resolveSymlinks?: boolean } = {},\n): string {\n const root = resolve(projectRoot)\n const abs = resolve(root, file)\n assertInside(root, abs, `file ${file} escapes the project root ${projectRoot}`)\n if (opts.resolveSymlinks) {\n let realRoot: string\n try {\n realRoot = realpathSync(root)\n } catch {\n throw new LspGateError(`project root ${projectRoot} does not exist`)\n }\n assertInside(\n realRoot,\n realpathNearest(abs),\n `file ${file} escapes the project root ${projectRoot} after symlink resolution`,\n )\n }\n return abs\n}\n\n/**\n * Confine an edited document URI (from a server `WorkspaceEdit`) to `projectRoot` for WRITING.\n * Refuses any non-`file://` scheme (`jdt://`, in-memory, untitled) and realpath-hardens the\n * target. Returns the absolute filesystem path.\n */\nexport function confineEditedUri(projectRoot: string, uri: string): string {\n let abs: string\n try {\n abs = fileURLToPath(uri)\n } catch {\n throw new LspGateError(`edited document ${uri} is not a file:// URI (refused for write)`)\n }\n return confineFile(projectRoot, abs, { resolveSymlinks: true })\n}\n\n/**\n * Like {@link confineEditedUri}, but confines to a GROUP of allowlisted roots (the multi-root\n * write path): a `WorkspaceEdit` for a monorepo legitimately edits files in any bound workspace\n * folder, so the edited URI is accepted when it realpath-confines to ANY root in the group, and\n * refused only when it escapes EVERY root. Refuses a non-`file://` scheme once, up front (it can\n * never confine to any root). Returns the absolute filesystem path.\n */\nexport function confineEditedUriToRoots(roots: string[], uri: string): string {\n let abs: string\n try {\n abs = fileURLToPath(uri)\n } catch {\n throw new LspGateError(`edited document ${uri} is not a file:// URI (refused for write)`)\n }\n for (const root of roots) {\n try {\n return confineFile(root, abs, { resolveSymlinks: true })\n } catch {\n // Not under this root; try the next. Refused below only if it escapes them all.\n }\n }\n throw new LspGateError(\n `edited document ${uri} escapes every allowlisted root (refused for write)`,\n )\n}\n\n/**\n * Confine EVERY edited URI to the project root, all-or-nothing — one out-of-root / `..` /\n * symlink-escape / non-`file://` URI throws before any target file is read. Returns a map from\n * each URI to its confined absolute path.\n */\nexport function confineEditedUris(projectRoot: string, uris: string[]): Map<string, string> {\n const out = new Map<string, string>()\n for (const uri of uris) out.set(uri, confineEditedUri(projectRoot, uri))\n return out\n}\n","/**\n * The gated LSP query engine (ADR 0011, slice 4) — the agent-facing entry that ties the\n * operator gate, the `LanguageServerManager`, and the encoding core together. Mirrors\n * coverage's `runScoped`: a **paired deny-by-default operator gate** (`allowRun` +\n * `allowedRoots` + the manager's per-request deadline), because every navigation answer\n * requires a live, code-executing, indexing daemon to exist. There is **no \"free read\" tier**\n * here — unlike `search_docs`/`list_requests`.\n *\n * The engine owns the I/O the protocol-level client must not: it **confines the queried file\n * to the project root** (no traversal), reads its text, converts the human 1-based line:col to\n * a 0-based LSP `Position` in the server's **negotiated encoding**, drives `manager.run`, and\n * maps the result ranges back to human 1-based line:col (reading each target file's text for an\n * encoding-faithful inverse; best-effort `+1` fallback when a target — e.g. a dep's `.d.ts` —\n * is unreadable). Tri-state status passes through untouched (never collapse `not_ready` into\n * \"no result\"). `serverInfo` provenance rides on every result; its absence is surfaced as a\n * `versionWarning` (an answer that cannot be attributed to a server version). The richer\n * warn-on-toolchain-mismatch heuristic (reusing `core.detectInstalledVersion`) is staged to the\n * surface, which can pass detected `toolchain` provenance to echo here.\n */\n\nimport { readFileSync } from 'node:fs'\nimport { fileURLToPath, pathToFileURL } from 'node:url'\nimport type { CallDirection, CallHierarchyGroup, NavResult, ServerInfo } from './client.js'\nimport { assertAllowed, confineFile, LspGateError } from './confine.js'\nimport {\n fromLspPosition,\n type HumanPosition,\n type PositionEncoding,\n toLspPosition,\n} from './encoding.js'\nimport type { LanguageServerManager } from './manager.js'\nimport type {\n LspRange,\n NormalizedCallItem,\n NormalizedDiagnostic,\n NormalizedHover,\n NormalizedLocation,\n NormalizedSymbol,\n NormalizedWorkspaceSymbol,\n QueryStatus,\n} from './normalize.js'\n\n// The paired-gate + confinement guards now live in `confine.ts` (shared with the write engine);\n// re-exported here so existing importers (barrel, tests) are unaffected.\nexport { LspGateError }\n\n/** Reads a file's text, or `undefined` if it cannot be read. */\nexport type FileReader = (absolutePath: string) => string | undefined\n\nconst defaultReadFile: FileReader = (p) => {\n try {\n return readFileSync(p, 'utf8')\n } catch {\n return undefined\n }\n}\n\nexport interface LspQueryEngineOptions {\n manager: LanguageServerManager\n /** OPERATOR opt-in to run navigation (which requires a live indexing daemon). Deny-by-default. */\n allowRun: boolean\n /** OPERATOR allowlist of project roots. Load-bearing even with allowRun. */\n allowedRoots: string[]\n /** Injected file reader (default `readFileSync`). */\n readFile?: FileReader\n}\n\nexport type LspQueryKind =\n | 'definition'\n | 'typeDefinition'\n | 'references'\n | 'hover'\n | 'documentSymbols'\n | 'workspaceSymbol'\n | 'diagnostics'\n | 'callHierarchy'\n\n/** The position-based kinds — those that require a `line`/`column`. */\nconst POSITION_KINDS: ReadonlySet<LspQueryKind> = new Set([\n 'definition',\n 'typeDefinition',\n 'references',\n 'hover',\n 'callHierarchy',\n])\n\nexport interface LspQueryInput {\n /** The agent-facing language (resolved against the operator registry). */\n language: string\n /** Project root — must be in `allowedRoots`; pinned to the server's `rootUri`. */\n projectRoot: string\n /**\n * Additional allowlisted roots bound as workspace folders on the SAME server (multi-root, ADR\n * 0011 tail) — so cross-root navigation resolves through one server. Each must be in\n * `allowedRoots`. The queried `file` still lives under the primary `projectRoot`.\n */\n workspaceRoots?: string[]\n /**\n * The file to query, relative to `projectRoot` (or absolute within it). Required for every kind\n * EXCEPT `workspaceSymbol`, which searches the whole indexed project and needs no open file.\n */\n file?: string\n /** 1-based human line — required for the position-based kinds, ignored for `documentSymbols`. */\n line?: number\n /** 1-based human column (code points) — required for the position-based kinds. */\n column?: number\n kind: LspQueryKind\n /** The search string — required for (and only used by) `workspaceSymbol`. */\n query?: string\n /** Call-hierarchy direction (callers vs callees); defaults to `incoming`. */\n direction?: CallDirection\n /** Optional toolchain provenance to echo (the surface computes via `detectInstalledVersion`). */\n toolchain?: { name: string; version: string | null }\n}\n\nexport interface HumanRange {\n start: HumanPosition\n end: HumanPosition\n}\n\nexport interface ResultLocation {\n uri: string\n range: HumanRange\n /** The full enclosing range, when the server sent a LocationLink. */\n fullRange?: HumanRange\n /** False ⇒ the target file was unreadable; the range is a best-effort `+1` of the LSP offsets. */\n mapped: boolean\n}\n\n/** A document symbol with its range mapped to human 1-based coords; children recurse. */\nexport interface ResultSymbol {\n name: string\n kind: number\n kindName: string\n detail?: string\n range: HumanRange\n /** Set only for flat `SymbolInformation` results. */\n container?: string\n children?: ResultSymbol[]\n}\n\n/** A diagnostic with its range(s) mapped to human 1-based coords. */\nexport interface ResultDiagnostic {\n range: HumanRange\n message: string\n severity?: number\n severityName?: string\n code?: number | string\n source?: string\n tags?: string[]\n related?: { uri: string; range: HumanRange; message: string }[]\n}\n\n/** A workspace symbol with its range (if any) mapped to human 1-based coords. */\nexport interface ResultWorkspaceSymbol {\n name: string\n kind: number\n kindName: string\n uri: string\n /** The declaring container (class/namespace), when the server reported one. */\n container?: string\n /** Absent when the server returned a uri-only `WorkspaceSymbol` (no range without resolve). */\n range?: HumanRange\n /** True when a range was present AND its target file was readable (encoding-faithful map). */\n mapped: boolean\n}\n\n/** A call-hierarchy item with its ranges mapped to human 1-based coords. */\nexport interface ResultCallItem {\n name: string\n kind: number\n kindName: string\n detail?: string\n uri: string\n range: HumanRange\n selectionRange: HumanRange\n}\n\n/** One call edge: the other item + the human-coord ranges where the call occurs. */\nexport interface ResultCall {\n item: ResultCallItem\n fromRanges: HumanRange[]\n}\n\nexport interface ResultCallGroup {\n source: ResultCallItem\n direction: CallDirection\n calls: ResultCall[]\n}\n\nexport interface LspQueryResult {\n status: QueryStatus\n kind: LspQueryKind\n locations?: ResultLocation[]\n hover?: { value: string; range?: HumanRange }\n symbols?: ResultSymbol[]\n workspaceSymbols?: ResultWorkspaceSymbol[]\n diagnostics?: ResultDiagnostic[]\n callHierarchy?: ResultCallGroup[]\n serverInfo?: ServerInfo\n toolchain?: { name: string; version: string | null }\n encoding: PositionEncoding\n versionWarning?: string\n}\n\nexport class LspQueryEngine {\n private readonly manager: LanguageServerManager\n private readonly allowRun: boolean\n private readonly allowedRoots: string[]\n private readonly readFile: FileReader\n\n constructor(options: LspQueryEngineOptions) {\n this.manager = options.manager\n this.allowRun = options.allowRun\n this.allowedRoots = options.allowedRoots\n this.readFile = options.readFile ?? defaultReadFile\n }\n\n async query(input: LspQueryInput): Promise<LspQueryResult> {\n assertAllowed(this.allowRun, this.allowedRoots, input.projectRoot)\n // Every additional multi-root folder is gated the same way as the primary root.\n for (const root of input.workspaceRoots ?? []) {\n assertAllowed(this.allowRun, this.allowedRoots, root)\n }\n\n // workspace/symbol is file-less + position-less: it searches the whole indexed project, so it\n // takes a `query` string and opens no document (the manager acquires/initializes the server,\n // which loads the project; `runWithUris([])` holds no per-uri lock).\n if (input.kind === 'workspaceSymbol') {\n return this.queryWorkspaceSymbol(input)\n }\n\n if (input.file === undefined) {\n throw new LspGateError(`the ${input.kind} query requires a file`)\n }\n const absFile = confineFile(input.projectRoot, input.file)\n const text = this.readFile(absFile)\n if (text === undefined) {\n throw new LspGateError(`cannot read file ${input.file} in ${input.projectRoot}`)\n }\n const uri = pathToFileURL(absFile).toString()\n if (\n POSITION_KINDS.has(input.kind) &&\n (input.line === undefined || input.column === undefined)\n ) {\n throw new LspGateError(`the ${input.kind} query requires a line and column`)\n }\n\n type Nav =\n | NavResult<NormalizedLocation[]>\n | NavResult<NormalizedHover | null>\n | NavResult<NormalizedSymbol[]>\n | NavResult<NormalizedDiagnostic[]>\n | NavResult<CallHierarchyGroup[]>\n const nav = await this.manager.run<Nav>(\n {\n language: input.language,\n projectRoot: input.projectRoot,\n uri,\n text,\n ...(input.workspaceRoots ? { workspaceRoots: input.workspaceRoots } : {}),\n },\n (client): Promise<Nav> => {\n switch (input.kind) {\n case 'documentSymbols':\n return client.documentSymbols(uri)\n case 'diagnostics':\n return client.documentDiagnostics(uri)\n default: {\n const pos = toLspPosition(\n text,\n input.line as number,\n input.column as number,\n client.encoding,\n )\n switch (input.kind) {\n case 'definition':\n return client.definition(uri, pos)\n case 'typeDefinition':\n return client.typeDefinition(uri, pos)\n case 'references':\n return client.references(uri, pos)\n case 'hover':\n return client.hover(uri, pos)\n case 'callHierarchy':\n return client.callHierarchy(uri, pos, input.direction ?? 'incoming')\n default:\n // `workspaceSymbol` is file-less and handled by an early return in `query()`;\n // it never reaches this position-based dispatch.\n throw new LspGateError(`unsupported position-based query kind: ${input.kind}`)\n }\n }\n }\n },\n )\n\n return this.shape(input, nav, uri, text)\n }\n\n /**\n * Run the project-wide `workspace/symbol` search and shape its cross-file result.\n *\n * `workspace/symbol` takes no position, but a real server still needs a *project* to search.\n * Some servers — notably `typescript-language-server` — only build a project once a document is\n * open, and answer `workspace/symbol` with a \"No Project\" error otherwise (caught running the\n * greeter example live, the cold-load lesson again). So the agent may pass an OPTIONAL anchor\n * `file`: when present we open it (establishing the project) before searching; when absent we\n * search with no document open, which works for eager indexers (gopls, rust-analyzer) that load\n * the project at `initialize`.\n */\n private async queryWorkspaceSymbol(input: LspQueryInput): Promise<LspQueryResult> {\n if (input.query === undefined) {\n throw new LspGateError('the workspaceSymbol query requires a `query` string')\n }\n const query = input.query\n let nav: NavResult<NormalizedWorkspaceSymbol[]>\n if (input.file !== undefined) {\n const absFile = confineFile(input.projectRoot, input.file)\n const text = this.readFile(absFile)\n if (text === undefined) {\n throw new LspGateError(`cannot read anchor file ${input.file} in ${input.projectRoot}`)\n }\n const uri = pathToFileURL(absFile).toString()\n nav = await this.manager.run<NavResult<NormalizedWorkspaceSymbol[]>>(\n { language: input.language, projectRoot: input.projectRoot, uri, text },\n (client) => client.workspaceSymbols(query),\n )\n } else {\n nav = await this.manager.runWithUris<NavResult<NormalizedWorkspaceSymbol[]>>(\n {\n language: input.language,\n projectRoot: input.projectRoot,\n uris: [],\n ...(input.workspaceRoots ? { workspaceRoots: input.workspaceRoots } : {}),\n },\n (client) => client.workspaceSymbols(query),\n )\n }\n const base = this.baseResult(input, nav)\n const cache = new Map<string, string | undefined>()\n return {\n ...base,\n workspaceSymbols: nav.result.map((s) => this.mapWorkspaceSymbol(s, nav.encoding, cache)),\n }\n }\n\n /** The shared provenance/status envelope every result carries (status + encoding + provenance). */\n private baseResult(input: LspQueryInput, nav: NavResult<unknown>): LspQueryResult {\n const { encoding, serverInfo } = nav\n const versionWarning =\n serverInfo === undefined\n ? 'the language server did not report its version (serverInfo); the answer cannot be attributed to a specific server version'\n : undefined\n return {\n status: nav.status,\n kind: input.kind,\n encoding,\n ...(serverInfo ? { serverInfo } : {}),\n ...(input.toolchain ? { toolchain: input.toolchain } : {}),\n ...(versionWarning ? { versionWarning } : {}),\n }\n }\n\n private shape(\n input: LspQueryInput,\n nav:\n | NavResult<NormalizedLocation[]>\n | NavResult<NormalizedHover | null>\n | NavResult<NormalizedSymbol[]>\n | NavResult<NormalizedDiagnostic[]>\n | NavResult<CallHierarchyGroup[]>,\n queriedUri: string,\n queriedText: string,\n ): LspQueryResult {\n const { encoding } = nav\n const base = this.baseResult(input, nav)\n\n if (input.kind === 'diagnostics') {\n const diags = (nav as NavResult<NormalizedDiagnostic[]>).result\n // Diagnostics are all in the QUERIED file; relatedInformation may point at other files.\n const cache = new Map<string, string | undefined>([[queriedUri, queriedText]])\n return {\n ...base,\n diagnostics: diags.map((d) => this.mapDiagnostic(d, queriedText, encoding, cache)),\n }\n }\n\n if (input.kind === 'hover') {\n const hover = (nav as NavResult<NormalizedHover | null>).result\n if (!hover) return base\n return {\n ...base,\n hover: {\n value: hover.value,\n // A hover range is in the QUERIED document.\n ...(hover.range ? { range: this.mapRange(queriedText, hover.range, encoding) } : {}),\n },\n }\n }\n\n if (input.kind === 'documentSymbols') {\n const symbols = (nav as NavResult<NormalizedSymbol[]>).result\n // Document symbols are all in the QUERIED file; map every range with its text.\n return { ...base, symbols: symbols.map((s) => this.mapSymbol(s, queriedText, encoding)) }\n }\n\n if (input.kind === 'callHierarchy') {\n const direction = input.direction ?? 'incoming'\n const groups = (nav as NavResult<CallHierarchyGroup[]>).result\n const cache = new Map<string, string | undefined>([[queriedUri, queriedText]])\n return {\n ...base,\n callHierarchy: groups.map((g) =>\n this.mapCallGroup(g, direction, encoding, queriedUri, cache),\n ),\n }\n }\n\n const locations = (nav as NavResult<NormalizedLocation[]>).result\n const cache = new Map<string, string | undefined>()\n return {\n ...base,\n locations: locations.map((loc) =>\n this.mapLocation(loc, encoding, queriedUri, queriedText, cache),\n ),\n }\n }\n\n private mapLocation(\n loc: NormalizedLocation,\n encoding: PositionEncoding,\n queriedUri: string,\n queriedText: string,\n cache: Map<string, string | undefined>,\n ): ResultLocation {\n // The target file's OWN text is needed for an encoding-faithful inverse; a definition\n // legitimately lives in another file (incl. a dependency outside the project root), so\n // reading it for line:col mapping is read-only and not gated.\n const text = loc.uri === queriedUri ? queriedText : this.textForUri(loc.uri, cache)\n return {\n uri: loc.uri,\n range: this.mapRange(text, loc.range, encoding),\n ...(loc.fullRange ? { fullRange: this.mapRange(text, loc.fullRange, encoding) } : {}),\n mapped: text !== undefined,\n }\n }\n\n /** Map a normalized document symbol (and its children) to human coords in the queried file. */\n private mapSymbol(s: NormalizedSymbol, text: string, encoding: PositionEncoding): ResultSymbol {\n const out: ResultSymbol = {\n name: s.name,\n kind: s.kind,\n kindName: s.kindName,\n range: this.mapRange(text, s.range, encoding),\n }\n if (s.detail !== undefined) out.detail = s.detail\n if (s.container !== undefined) out.container = s.container\n if (s.children && s.children.length > 0) {\n out.children = s.children.map((c) => this.mapSymbol(c, text, encoding))\n }\n return out\n }\n\n /** Map a diagnostic's range (queried file) + any relatedInformation ranges (their own files). */\n private mapDiagnostic(\n d: NormalizedDiagnostic,\n queriedText: string,\n encoding: PositionEncoding,\n cache: Map<string, string | undefined>,\n ): ResultDiagnostic {\n const out: ResultDiagnostic = {\n range: this.mapRange(queriedText, d.range, encoding),\n message: d.message,\n }\n if (d.severity !== undefined) out.severity = d.severity\n if (d.severityName !== undefined) out.severityName = d.severityName\n if (d.code !== undefined) out.code = d.code\n if (d.source !== undefined) out.source = d.source\n if (d.tags !== undefined) out.tags = d.tags\n if (d.related !== undefined) {\n out.related = d.related.map((r) => ({\n uri: r.uri,\n range: this.mapRange(this.textForUri(r.uri, cache), r.range, encoding),\n message: r.message,\n }))\n }\n return out\n }\n\n /** Map a workspace symbol to human coords, reading its OWN target file (cross-file, read-only). */\n private mapWorkspaceSymbol(\n s: NormalizedWorkspaceSymbol,\n encoding: PositionEncoding,\n cache: Map<string, string | undefined>,\n ): ResultWorkspaceSymbol {\n const out: ResultWorkspaceSymbol = {\n name: s.name,\n kind: s.kind,\n kindName: s.kindName,\n uri: s.uri,\n mapped: false,\n }\n if (s.container !== undefined) out.container = s.container\n if (s.range !== undefined) {\n const text = this.textForUri(s.uri, cache)\n out.range = this.mapRange(text, s.range, encoding)\n out.mapped = text !== undefined\n }\n return out\n }\n\n private mapCallItem(\n item: NormalizedCallItem,\n encoding: PositionEncoding,\n cache: Map<string, string | undefined>,\n ): ResultCallItem {\n const text = this.textForUri(item.uri, cache)\n return {\n name: item.name,\n kind: item.kind,\n kindName: item.kindName,\n ...(item.detail !== undefined ? { detail: item.detail } : {}),\n uri: item.uri,\n range: this.mapRange(text, item.range, encoding),\n selectionRange: this.mapRange(text, item.selectionRange, encoding),\n }\n }\n\n private mapCallGroup(\n g: CallHierarchyGroup,\n direction: CallDirection,\n encoding: PositionEncoding,\n queriedUri: string,\n cache: Map<string, string | undefined>,\n ): ResultCallGroup {\n return {\n source: this.mapCallItem(g.source, encoding, cache),\n direction,\n calls: g.calls.map((c) => {\n // fromRanges live in the CALLER's file: incoming ⇒ the edge item (the caller);\n // outgoing ⇒ the source (the queried caller) itself.\n const fromText = this.textForUri(direction === 'incoming' ? c.item.uri : queriedUri, cache)\n return {\n item: this.mapCallItem(c.item, encoding, cache),\n fromRanges: c.fromRanges.map((r) => this.mapRange(fromText, r, encoding)),\n }\n }),\n }\n }\n\n private textForUri(uri: string, cache: Map<string, string | undefined>): string | undefined {\n if (cache.has(uri)) return cache.get(uri)\n let text: string | undefined\n try {\n text = this.readFile(fileURLToPath(uri))\n } catch {\n text = undefined // a non-file:// uri (e.g. an in-memory or jdt:// scheme)\n }\n cache.set(uri, text)\n return text\n }\n\n /** Map an LSP 0-based range to a human 1-based range, encoding-faithfully when text is known. */\n private mapRange(\n text: string | undefined,\n range: LspRange,\n encoding: PositionEncoding,\n ): HumanRange {\n if (text === undefined) {\n // Best-effort fallback: the file is unreadable, so we cannot count code units — surface\n // the raw LSP offsets +1 (documented; `mapped:false` flags it).\n return {\n start: { line: range.start.line + 1, column: range.start.character + 1 },\n end: { line: range.end.line + 1, column: range.end.character + 1 },\n }\n }\n return {\n start: fromLspPosition(text, range.start, encoding),\n end: fromLspPosition(text, range.end, encoding),\n }\n }\n}\n","/**\n * The pure write-mode apply core (ADR 0011 write-mode addendum, Slice A). No I/O, no spawn —\n * the most defensible TDD entry for write-mode, pinning down the corruption-class vectors\n * (encoding-wrong offsets, overlapping edits, edit-ordering) before any disk write exists.\n *\n * The silent-wrong trap mirrors the read path: a TextEdit `range.character` is in the\n * NEGOTIATED encoding's code units, so applying it must resolve each position to an absolute\n * JS index via `lspPositionToOffset` (which is CRLF/BOM/non-BMP faithful) and never via a\n * naive line:column arithmetic.\n */\n\nimport { lspPositionToOffset, type PositionEncoding } from './encoding.js'\nimport type { LspRange } from './normalize.js'\n\nexport interface TextEdit {\n range: LspRange\n newText: string\n}\n\n/** Thrown when two edits in one document overlap or share a start offset. */\nexport class OverlappingEditError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'OverlappingEditError'\n }\n}\n\n/**\n * Apply a set of LSP TextEdits to one document's `text`, encoding-faithfully. Each edit's\n * positions are resolved to absolute JS offsets, validated, then spliced in DESCENDING start\n * order so an earlier edit never invalidates a later one's offsets.\n *\n * Enforced invariants (throw {@link OverlappingEditError}, never silently corrupt):\n * - Two edits sharing a start offset are refused (subsumes a zero-length double insertion);\n * with distinct starts the splice order is total and JS sort stability is never relied on.\n * - A true overlap (`prev.end > next.start`) is refused. Adjacency (`prev.end == next.start`)\n * is allowed.\n */\nexport function applyTextEdits(\n text: string,\n edits: TextEdit[],\n encoding: PositionEncoding,\n): string {\n const spans = edits.map((e) => {\n const start = lspPositionToOffset(text, e.range.start, encoding)\n const end = lspPositionToOffset(text, e.range.end, encoding)\n if (end < start) {\n throw new OverlappingEditError(\n `edit range end (${end}) precedes its start (${start}) — malformed range`,\n )\n }\n return { start, end, newText: e.newText }\n })\n const sorted = [...spans].sort((a, b) => a.start - b.start)\n for (let k = 1; k < sorted.length; k++) {\n const prev = sorted[k - 1] as (typeof sorted)[number]\n const cur = sorted[k] as (typeof sorted)[number]\n if (cur.start === prev.start) {\n throw new OverlappingEditError(`two edits share start offset ${cur.start}`)\n }\n if (prev.end > cur.start) {\n throw new OverlappingEditError(\n `edits overlap: [${prev.start},${prev.end}) and [${cur.start},${cur.end})`,\n )\n }\n }\n let out = text\n for (let k = sorted.length - 1; k >= 0; k--) {\n const s = sorted[k] as (typeof sorted)[number]\n out = out.slice(0, s.start) + s.newText + out.slice(s.end)\n }\n return out\n}\n\nconst MAX_RENAME_NAME_LENGTH = 255\n\n/**\n * A coarse injection guard for a rename target. `newName` is sent verbatim to the server and\n * then written verbatim into EVERY edited site, so a newline or path separator in it is a\n * corruption/injection vector. This is a defensive bound, NOT a per-language identifier\n * validator: it rejects empty / over-length / multi-line / path-separator / control-character\n * names and accepts everything else (incl. non-ASCII letters). Validated before the rename\n * request is sent to the server.\n */\nexport function isPlausibleRenameName(newName: string): boolean {\n if (typeof newName !== 'string') return false\n if (newName.length === 0 || newName.length > MAX_RENAME_NAME_LENGTH) return false\n if (newName.includes('/') || newName.includes('\\\\')) return false\n // biome-ignore lint/suspicious/noControlCharactersInRegex: rejecting control chars is the intent\n if (/[\\u0000-\\u001f\\u007f]/.test(newName)) return false\n return true\n}\n","/**\n * The gated LSP rename engine (ADR 0011 write-mode addendum, Slices F + F′). The first WRITE\n * surface of the pillar. It mirrors `LspQueryEngine` but layers a SECOND operator gate —\n * `allowWrite` — on top of the read gate (`allowRun` + `allowedRoots`): rename is **dry-run by\n * default** (compute + preview, ZERO disk writes) and applies to disk only when `allowWrite` is\n * set AND every safety condition holds.\n *\n * Apply is a separate phase from compute (it may need more locks than the compute phase held).\n * Single- AND multi-file edits apply, the latter under the manager's multi-URI lock (sorted,\n * deadlock-free) held across the whole stage→commit→`didChange` window. Every touched file is\n * confined to the root group (realpath, all-or-nothing) BEFORE any I/O. Resource operations\n * (CreateFile/RenameFile/DeleteFile) APPLY in `documentChanges` order interleaved with text edits;\n * the replay runs over a per-file `Fate` VFS keyed by ORIGINAL uri, so content flows through a\n * rename (an edit to a renamed file's new path composes onto the carried content) and net-no-op\n * batches (create-then-delete) drop out. `ignoreIfExists`/`ignoreIfNotExists` are conditional\n * no-ops. `overwrite` (Create/Rename truncate-and-replace of an EXISTING regular file) APPLIES only\n * behind the separate operator `allowDestructiveResourceOps` gate, auditing the clobbered bytes and\n * surfacing `overwritten[]`; a symlink/directory target, recursive/directory delete, `overwrite` on\n * a delete, and genuinely ambiguous batches (a rename cycle, two renames into one target, editing a\n * renamed-away path, deleting a path that is also a rename/create target) all stay refused. A\n * mid-commit fault is terminal (`partial`).\n *\n * The adversarial corrections baked in here: oldText is sliced with absolute offsets (never\n * reconstructed from line:col); apply is staleness-guarded (the queried file vs its compute hash;\n * each on-disk edit site vs the old identifier — a not-yet-on-disk rename target is skipped, so an\n * import fix-up in a moved file never trips it) then stage-then-commit (the injected writer);\n * out-of-root edits never have their bytes read/surfaced; the post-commit `didChange`/`didFileRename`\n * doc-sync runs inside the held lock(s) and carries the bytes that ACTUALLY landed (pristine on a\n * partial commit, never the projected edit); secrets are redacted in every surfaced hunk.\n */\n\nimport { createHash } from 'node:crypto'\nimport {\n closeSync,\n fsyncSync,\n lstatSync,\n mkdirSync,\n openSync,\n readdirSync,\n readFileSync,\n realpathSync,\n renameSync,\n statSync,\n unlinkSync,\n writeFileSync,\n} from 'node:fs'\nimport { dirname, extname, join, relative } from 'node:path'\nimport { pathToFileURL } from 'node:url'\nimport { applyTextEdits, isPlausibleRenameName } from './apply.js'\nimport type { NavResult, ServerInfo } from './client.js'\nimport { assertAllowed, confineEditedUriToRoots, confineFile, LspGateError } from './confine.js'\nimport {\n fromLspPosition,\n lspPositionToOffset,\n type PositionEncoding,\n toLspPosition,\n} from './encoding.js'\nimport type { LanguageServerManager } from './manager.js'\nimport type {\n LspRange,\n NormalizedFileEdit,\n NormalizedResourceOp,\n NormalizedWorkspaceEdit,\n QueryStatus,\n} from './normalize.js'\nimport type { HumanRange } from './query.js'\n\n/** Reads a file's text, or `undefined` if it cannot be read. */\nexport type FileReader = (absolutePath: string) => string | undefined\n\nconst defaultReadFile: FileReader = (p) => {\n try {\n return readFileSync(p, 'utf8')\n } catch {\n return undefined\n }\n}\n\n/**\n * Lists candidate source files (absolute paths) under the allowlisted root group to scan for the\n * partial-rename completeness guard — same-language source only (`extension`, e.g. `.py`). Injected\n * so the gate never walks a real tree; `truncated` ⇒ a cap was hit and the scan is not exhaustive.\n */\nexport type ProjectFileLister = (\n roots: string[],\n opts: { extension: string },\n) => { files: string[]; truncated: boolean }\n\n// Directories never worth scanning for source references (deps, VCS, caches, build output).\nconst SKIP_DIRS = new Set([\n 'node_modules',\n '.git',\n '.hg',\n '.svn',\n '__pycache__',\n '.venv',\n 'venv',\n 'env',\n '.env',\n '.tox',\n '.nox',\n '.mypy_cache',\n '.pytest_cache',\n '.ruff_cache',\n 'dist',\n 'build',\n 'target',\n '.idea',\n '.vscode',\n])\nconst MAX_SCAN_FILES = 5000\n\n/** Default lister: a bounded, symlink-safe recursive walk collecting `extension` files, skipping\n * dependency/VCS/cache/build dirs and any dotdir. Stops (`truncated`) at {@link MAX_SCAN_FILES}.\n * The partial-rename guard is OFF until a lister is wired (like `redact`, the surfaces wire this). */\nexport const defaultListFiles: ProjectFileLister = (roots, { extension }) => {\n const files: string[] = []\n const seenDirs = new Set<string>()\n let truncated = false\n const walk = (dir: string): void => {\n if (truncated) return\n let real: string\n try {\n real = realpathSync(dir)\n } catch {\n return\n }\n if (seenDirs.has(real)) return // symlink-loop guard\n seenDirs.add(real)\n let entries: import('node:fs').Dirent[]\n try {\n entries = readdirSync(dir, { withFileTypes: true })\n } catch {\n return\n }\n for (const e of entries) {\n if (truncated) return\n const full = join(dir, e.name)\n if (e.isDirectory()) {\n if (SKIP_DIRS.has(e.name) || e.name.startsWith('.')) continue\n walk(full)\n } else if (e.isFile() && extname(e.name) === extension) {\n if (files.length >= MAX_SCAN_FILES) {\n truncated = true\n return\n }\n files.push(full)\n }\n }\n }\n for (const r of roots) walk(r)\n return { files, truncated }\n}\n\nconst realpathOrSelf = (p: string): string => {\n try {\n return realpathSync(p)\n } catch {\n return p\n }\n}\n\nconst ID_CHAR = /[\\p{L}\\p{N}_$]/u\n\n/** The identifier token (code-point aware) spanning a 1-based human `line`/`column` in `text`. */\nfunction identifierAt(text: string, line: number, column: number): string {\n const lines = text.split(/\\r\\n|\\r|\\n/)\n const ln = lines[line - 1]\n if (ln === undefined) return ''\n const cps = [...ln]\n let i = column - 1\n // The position may sit just past the token's end; step back onto it.\n if (\n (i < 0 || i >= cps.length || !ID_CHAR.test(cps[i] as string)) &&\n i > 0 &&\n ID_CHAR.test(cps[i - 1] as string)\n ) {\n i -= 1\n }\n if (i < 0 || i >= cps.length || !ID_CHAR.test(cps[i] as string)) return ''\n let s = i\n let e = i + 1\n while (s > 0 && ID_CHAR.test(cps[s - 1] as string)) s -= 1\n while (e < cps.length && ID_CHAR.test(cps[e] as string)) e += 1\n return cps.slice(s, e).join('')\n}\n\n/** A whole-word matcher for `name` (not flanked by identifier chars). `name` is regex-escaped. */\nfunction wholeWordRegex(name: string): RegExp {\n const esc = name.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n return new RegExp(`(?<![\\\\p{L}\\\\p{N}_$])${esc}(?![\\\\p{L}\\\\p{N}_$])`, 'u')\n}\n\n/** How exhaustively the partial-rename guard could verify the edit covers every textual use. */\nexport type RenameCompleteness = 'complete' | 'suspect' | 'unknown'\nconst MAX_SUSPECTS = 50\n\n/**\n * One physical filesystem action in an apply commit. A `write` creates-or-overwrites a file's\n * content (the fold of a CreateFile + its edits, or an edit to a pre-existing file); `rename`/\n * `delete` are the resource-op file moves/removals (`RenameFile`/`DeleteFile`).\n */\nexport type PhysicalOp =\n | { kind: 'write'; absPath: string; newText: string }\n | { kind: 'rename'; fromAbs: string; toAbs: string }\n | { kind: 'delete'; absPath: string }\n\n/**\n * The projected fate of one file during the apply replay, keyed by its ORIGINAL uri. A `live` file\n * ends at `finalUri` (≠ origin ⇒ it was renamed) with the given projected `content`; a `deleted`\n * file ends removed. Content flows through a rename inside this record, so an edit to a renamed\n * file's new path composes onto the carried content without a copy.\n */\ntype Fate = { kind: 'live'; finalUri: string; content: string } | { kind: 'deleted' }\n\nexport interface CommitResult {\n /** The ops that completed, in order. */\n completed: PhysicalOp[]\n /** True ⇒ an op faulted mid-execute; `completed` is the TERMINAL landed set (rename/delete are\n * irreversible — there is no rollback; reconcile via VCS). */\n partial: boolean\n error?: string\n}\n\n/**\n * The write seam (injected; tests substitute a fake so the gate never touches disk). The default\n * **stages every `write` to a sibling temp (+ fsync) first** (no target touched until all temps\n * exist — the strength the pure-text rename relies on), then executes every op IN ORDER: a `write`\n * commits via atomic rename of its temp, a `rename`/`delete` runs the fs primitive. Resource ops\n * are irreversible and cannot be staged, so a fault during the execute phase is terminal —\n * `partial: true` names exactly what landed.\n */\nexport interface RenameWriter {\n commit(ops: PhysicalOp[]): CommitResult\n}\n\nlet tempCounter = 0\n\nexport const defaultRenameWriter: RenameWriter = {\n commit(ops) {\n // Phase A — stage every `write` to a sibling temp (+fsync); mkdir -p a CreateFile's new dir.\n const temps = new Map<string, string>()\n try {\n for (const op of ops) {\n if (op.kind !== 'write') continue\n mkdirSync(dirname(op.absPath), { recursive: true })\n tempCounter += 1\n const tmp = `${op.absPath}.sackville-rename-${process.pid}-${tempCounter}`\n writeFileSync(tmp, op.newText, 'utf8')\n const fd = openSync(tmp, 'r+')\n fsyncSync(fd)\n closeSync(fd)\n temps.set(op.absPath, tmp)\n }\n } catch (err) {\n for (const tmp of temps.values()) {\n try {\n unlinkSync(tmp)\n } catch {\n // best-effort cleanup; nothing was committed yet so no target is corrupt.\n }\n }\n throw err\n }\n // Phase B — execute in order. A fault here is terminal (rename/delete are irreversible).\n const completed: PhysicalOp[] = []\n try {\n for (const op of ops) {\n if (op.kind === 'write') renameSync(temps.get(op.absPath) as string, op.absPath)\n else if (op.kind === 'rename') renameSync(op.fromAbs, op.toAbs)\n else unlinkSync(op.absPath)\n completed.push(op)\n }\n } catch (err) {\n for (const [abs, tmp] of temps) {\n if (!completed.some((c) => c.kind === 'write' && c.absPath === abs)) {\n try {\n unlinkSync(tmp)\n } catch {\n // best-effort: an uncommitted temp.\n }\n }\n }\n return { completed, partial: true, error: (err as Error).message }\n }\n return { completed, partial: false }\n },\n}\n\nfunction isRegularFile(abs: string): boolean {\n try {\n return statSync(abs).isFile()\n } catch {\n return false\n }\n}\n\n/**\n * A symlink-AWARE regular-file check for an OVERWRITE target. Refuses a symlink (no clobber THROUGH a\n * link — the digest would read the link target's bytes while `renameSync`/atomic-write replaces the\n * link itself, a silent audit lie + the real file survives) and refuses a directory. `lstatSync` does\n * NOT follow the link, so a symlink ⇒ `isFile()` is false ⇒ refused. Used only on an overwrite target.\n */\nfunction isOverwritableRegularFile(abs: string): boolean {\n try {\n return lstatSync(abs).isFile()\n } catch {\n return false\n }\n}\n\n/** True if a path exists on disk (any kind), WITHOUT following a symlink. Used to detect an overwrite\n * destination that `liveOccupied` (content-read-based) misses — notably a DIRECTORY, whose content\n * read returns undefined so it would otherwise look unoccupied. */\nfunction existsLstat(abs: string): boolean {\n try {\n lstatSync(abs)\n return true\n } catch {\n return false\n }\n}\n\nexport interface LspRenameEngineOptions {\n manager: LanguageServerManager\n /** OPERATOR opt-in to run navigation (which requires a live indexing daemon). Deny-by-default. */\n allowRun: boolean\n /** OPERATOR allowlist of project roots. Load-bearing even with allowRun. */\n allowedRoots: string[]\n /** OPERATOR opt-in to WRITE the edit to disk. Distinct from allowRun; off ⇒ dry-run preview. */\n allowWrite: boolean\n /**\n * OPERATOR opt-in to APPLY a rename whose completeness verdict is `suspect` — i.e. the old\n * identifier also appears in same-language files the server's edit does NOT touch (the hallmark\n * of an open-files-scoped server like pyright, whose cross-file rename can be silently partial).\n * Deny-by-default: a suspect rename is REFUSED for write unless this is set. Never an agent input.\n */\n allowPartialRename?: boolean\n /**\n * OPERATOR opt-in to APPLY a DESTRUCTIVE resource op — `overwrite: true` on a CreateFile\n * (truncate-and-replace an existing file) or a RenameFile (clobber an existing REGULAR-FILE\n * target). Deny-by-default; refused for write unless set. Recursive/dir delete + symlink/dir\n * targets STAY refused even with this gate. Meaningless without `allowWrite` (the engine\n * re-checks both before any destructive op); never an agent input.\n */\n allowDestructiveResourceOps?: boolean\n readFile?: FileReader\n /** Lists same-language source files to scan for the partial-rename guard. UNSET ⇒ the guard is\n * inactive (no scan); the bin/CLI/MCP wire `defaultListFiles` to turn it on (cf. `redact`). */\n listFiles?: ProjectFileLister\n writer?: RenameWriter\n /** Secret redaction over every surfaced hunk (default identity; the bin wires @sackville-mcp/safety). */\n redact?: (text: string) => string\n}\n\nexport interface LspRenameInput {\n language: string\n projectRoot: string\n /** The file to rename in, relative to projectRoot (or absolute within it). */\n file: string\n /** 1-based human line of the symbol to rename. */\n line: number\n /** 1-based human column (code points) of the symbol to rename. */\n column: number\n /** The new identifier. Validated by isPlausibleRenameName before reaching the server. */\n newName: string\n /**\n * Additional allowlisted roots bound as workspace folders on the SAME server (multi-root). A\n * cross-root rename may edit files in any of these; each must be in `allowedRoots`, and edited\n * files confine to the GROUP (`projectRoot` ∪ `workspaceRoots`), not just the primary root.\n */\n workspaceRoots?: string[]\n /** Optional toolchain provenance to echo. */\n toolchain?: { name: string; version: string | null }\n}\n\nexport interface RenamePreviewEdit {\n range: HumanRange\n oldText: string\n newText: string\n needsConfirmation?: boolean\n annotationLabel?: string\n}\n\nexport interface RenamePreviewFile {\n uri: string\n /** Project-relative path (never absolute). `(out of project root)` for an out-of-root edit. */\n file: string\n editCount: number\n /** True ⇒ the edit targets a file outside the allowlisted root; its bytes are NOT surfaced. */\n outOfRoot?: boolean\n /** The per-edit hunks (omitted for an out-of-root or unreadable file). */\n hunks?: RenamePreviewEdit[]\n}\n\nexport interface RenameDigest {\n file: string\n before: string\n after: string\n}\n\nexport interface LspRenameResult {\n status: QueryStatus\n kind: 'rename'\n applied: boolean\n /** A structured reason the rename was not applied (not renameable, multi-file, resource ops, drift). */\n refused?: string\n newName: string\n fileCount: number\n totalEditCount: number\n edits: RenamePreviewFile[]\n resourceOps?: NormalizedResourceOp[]\n /** Per-file pre/post SHA-256 digests — the apply audit (only when applied). */\n digests?: RenameDigest[]\n /** Project-relative paths whose prior content a DESTRUCTIVE overwrite clobbered (gated; landed\n * only) — so a destructive clobber is always explicit in the envelope, not inferred from a digest. */\n overwritten?: string[]\n /** True ⇒ an irreversible resource op faulted mid-commit; `digests` names what landed (no\n * rollback — reconcile via VCS). */\n partial?: boolean\n partialError?: string\n /**\n * The partial-rename guard verdict (omitted when the rename was not `ok`). `complete` — every\n * same-language file mentioning the old name is in the edit; `suspect` — some are NOT (the edit\n * may be partial, e.g. an open-files-scoped server); `unknown` — the scan was truncated.\n */\n completeness?: RenameCompleteness\n /** Project-relative same-language files that mention the old identifier but are absent from the\n * edit (capped). Populated when `completeness === 'suspect'`. */\n suspectedMissedFiles?: string[]\n serverInfo?: ServerInfo\n toolchain?: { name: string; version: string | null }\n encoding: PositionEncoding\n versionWarning?: string\n}\n\nconst sha256 = (s: string): string => createHash('sha256').update(s, 'utf8').digest('hex')\n\ninterface ApplyOutcome {\n applied: boolean\n refused?: string\n digests?: RenameDigest[]\n /** Project-relative paths whose prior content a DESTRUCTIVE overwrite clobbered (landed only). */\n overwritten?: string[]\n partial?: boolean\n partialError?: string\n}\n\ninterface RunOutcome {\n status: QueryStatus\n edit: NormalizedWorkspaceEdit\n encoding: PositionEncoding\n serverInfo?: ServerInfo\n refused?: string\n apply: ApplyOutcome\n}\n\nexport class LspRenameEngine {\n private readonly manager: LanguageServerManager\n private readonly allowRun: boolean\n private readonly allowedRoots: string[]\n private readonly allowWrite: boolean\n private readonly allowPartialRename: boolean\n private readonly allowDestructiveResourceOps: boolean\n private readonly readFile: FileReader\n /** When unset the partial-rename guard is inactive (the bin/CLI/MCP wire `defaultListFiles`). */\n private readonly listFiles?: ProjectFileLister\n private readonly writer: RenameWriter\n private readonly redact: (text: string) => string\n\n constructor(options: LspRenameEngineOptions) {\n this.manager = options.manager\n this.allowRun = options.allowRun\n this.allowedRoots = options.allowedRoots\n this.allowWrite = options.allowWrite\n this.allowPartialRename = options.allowPartialRename ?? false\n this.allowDestructiveResourceOps = options.allowDestructiveResourceOps ?? false\n this.readFile = options.readFile ?? defaultReadFile\n this.listFiles = options.listFiles\n this.writer = options.writer ?? defaultRenameWriter\n this.redact = options.redact ?? ((t) => t)\n }\n\n async rename(input: LspRenameInput): Promise<LspRenameResult> {\n assertAllowed(this.allowRun, this.allowedRoots, input.projectRoot)\n // Every additional workspace root must also be allowlisted (refused before any spawn).\n for (const root of input.workspaceRoots ?? []) {\n assertAllowed(this.allowRun, this.allowedRoots, root)\n }\n const queriedAbs = confineFile(input.projectRoot, input.file)\n const text = this.readFile(queriedAbs)\n if (text === undefined) {\n throw new LspGateError(`cannot read file ${input.file} in ${input.projectRoot}`)\n }\n if (!isPlausibleRenameName(input.newName)) {\n throw new LspGateError(`invalid rename target ${JSON.stringify(input.newName)}`)\n }\n const queriedUri = pathToFileURL(queriedAbs).toString()\n\n const run = await this.manager.run<RunOutcome>(\n {\n language: input.language,\n projectRoot: input.projectRoot,\n uri: queriedUri,\n text,\n ...(input.workspaceRoots ? { workspaceRoots: input.workspaceRoots } : {}),\n },\n async (client): Promise<RunOutcome> => {\n const pos = toLspPosition(text, input.line, input.column, client.encoding)\n const empty: NormalizedWorkspaceEdit = { files: [], resourceOps: [], operations: [] }\n\n if (client.supportsPrepareRename) {\n const prep = await client.prepareRename(queriedUri, pos)\n if (prep.status === 'not_ready') {\n return base(prep, empty, { applied: false })\n }\n if (prep.status === 'no_result') {\n return base(prep, empty, { applied: false }, 'rename is not valid at this position')\n }\n }\n\n const r = await client.rename(queriedUri, pos, input.newName)\n return base(r, r.result, { applied: false })\n },\n )\n\n // Partial-rename guard: scan the allowlisted root group for same-language files that mention\n // the old identifier but are NOT in the server's edit. An open-files-scoped server (pyright)\n // can return a too-narrow edit that, applied verbatim, would silently break the untouched uses.\n const guard =\n run.status === 'ok' && this.listFiles\n ? this.assessCompleteness(run.edit, input, queriedAbs, text)\n : undefined\n // A DESTRUCTIVE batch (any overwrite) raises the bar: an `unknown` (truncated, hence\n // unverifiable) completeness verdict is treated as blocking too — an irreversible clobber must\n // not ride on a scan we could not finish. A `suspect` verdict always blocks (any batch).\n const destructiveBatch =\n run.status === 'ok' &&\n run.edit.operations.some((o) => o.type !== 'edit' && o.options?.overwrite === true)\n const blockedByGuard =\n (guard?.completeness === 'suspect' ||\n (destructiveBatch && guard?.completeness === 'unknown')) &&\n !this.allowPartialRename\n\n // Apply is a SEPARATE phase (it may need more locks than the compute phase held — the\n // multi-URI lock). The queried file stays open from compute (open-once), so its post-write\n // didChange still fires. Dry-run by default: only when allowWrite + ok do we touch disk — and\n // a suspect verdict refuses the WRITE (deny-by-default) unless the operator set allowPartialRename.\n const apply =\n run.status === 'ok' && this.allowWrite && !blockedByGuard\n ? await this.applyEdit(run.edit, input, queriedUri, text, run.encoding)\n : { applied: false }\n const guardRefusal = !(this.allowWrite && blockedByGuard)\n ? undefined\n : guard?.completeness === 'suspect'\n ? `rename may be INCOMPLETE: the symbol ${JSON.stringify(\n guard?.oldName ?? '',\n )} also appears in ${guard?.suspectFiles.length} same-language file(s) not in this edit (e.g. ${guard?.suspectFiles\n .slice(0, 3)\n .join(\n ', ',\n )}). The language server may scope rename to open files; nothing was written. Re-run with allowPartialRename to apply anyway.`\n : `rename completeness could not be verified: the same-language scan for ${JSON.stringify(\n guard?.oldName ?? '',\n )} was TRUNCATED, and this batch contains a DESTRUCTIVE overwrite — an unverifiable scan is treated as blocking; nothing was written. Re-run with allowPartialRename to apply anyway.`\n\n return this.shape(\n input,\n { ...run, apply, refused: guardRefusal ?? apply.refused ?? run.refused },\n queriedUri,\n text,\n guard,\n )\n }\n\n /**\n * Decide + execute the apply, consuming the ordered `operations` (text edits interleaved with\n * CreateFile/RenameFile/DeleteFile). Confine EVERY touched URI (edit + create + rename old&new +\n * delete) to the root group all-or-nothing BEFORE any I/O; refuse the v1 cuts early (non-default\n * resource-op options; editing a file also renamed in the same batch). Then, under the multi-URI\n * lock over ALL touched URIs, replay the ops over a virtual content map (no writes) with the\n * staleness guards, build a physical plan, stage-then-commit it, and resync any open buffer\n * (`didChange` for an edited file, `didClose`+`didOpen` migration for a renamed/deleted one).\n */\n private async applyEdit(\n edit: NormalizedWorkspaceEdit,\n input: LspRenameInput,\n queriedUri: string,\n text: string,\n encoding: PositionEncoding,\n ): Promise<ApplyOutcome> {\n const ops = edit.operations\n if (ops.length === 0) return { applied: false }\n const group = [input.projectRoot, ...(input.workspaceRoots ?? [])]\n const abs = new Map<string, string>()\n const rel = (uri: string) => relative(input.projectRoot, abs.get(uri) as string)\n\n // (a) Confine EVERY touched URI to the group, all-or-nothing, BEFORE any I/O.\n for (const op of ops) {\n const uris = op.type === 'rename' ? [op.oldUri, op.newUri] : [op.uri]\n for (const u of uris) {\n if (abs.has(u)) continue\n try {\n abs.set(u, confineEditedUriToRoots(group, u))\n } catch {\n return {\n applied: false,\n refused: 'an edited file is outside the project root; previewed only',\n }\n }\n }\n }\n\n // (b) Refuse the DESTRUCTIVE / malformed resource-op options EARLY (before any I/O), per op\n // type so the invariants are STRUCTURAL, not message-dependent: `recursive` is ALWAYS refused\n // (no rm -rf from a server payload); `overwrite` on create/rename is gated; `overwrite` on a\n // delete is malformed. The gate is SELF-ENFORCING — it re-requires `allowWrite` even though the\n // bin throws without it (mirrors assertAllowed re-checking allowRun). The other v1 cuts\n // (edit-of-a-renamed-file, ordering, cycles) are enforced inline in the replay below.\n const destructiveAllowed = this.allowWrite && this.allowDestructiveResourceOps\n for (const op of ops) {\n if (op.type === 'edit') continue\n if (op.type === 'delete' && op.options?.recursive === true) {\n return {\n applied: false,\n refused: 'recursive/directory delete is unsupported (refused — not enabled by any gate)',\n }\n }\n if (op.type === 'delete' && op.options?.overwrite === true) {\n return {\n applied: false,\n refused: 'overwrite is not a valid option on a delete (malformed; refused)',\n }\n }\n if (\n (op.type === 'create' || op.type === 'rename') &&\n op.options?.overwrite === true &&\n !destructiveAllowed\n ) {\n return {\n applied: false,\n refused:\n 'resource-op overwrite requires the operator destructive-resource-ops gate (allowDestructiveResourceOps + allowWrite); previewed only',\n }\n }\n }\n\n return this.manager.runWithUris(\n {\n language: input.language,\n projectRoot: input.projectRoot,\n uris: [...abs.keys()],\n ...(input.workspaceRoots ? { workspaceRoots: input.workspaceRoots } : {}),\n },\n async (client): Promise<ApplyOutcome> => {\n const refuse = (msg: string): ApplyOutcome => ({ applied: false, refused: msg })\n // PHASE 1 (no writes): replay ops over a VFS keyed by the file's ORIGINAL uri. Content flows\n // THROUGH a rename inside one record (rename(A→B) carries A's content to finalUri B; a later\n // edit(B) resolves to A and edits the carried content) — so edit composed with rename/delete\n // works without copies, in documentChanges order.\n const vfs = new Map<string, Fate>()\n const created = new Set<string>() // born in-batch (keyed by origin)\n const order: string[] = [] // first-touch order — drives the physical-plan emission\n const ordered = new Set<string>() // O(1) membership companion to `order`\n const aliasMap = new Map<string, string>() // a rename target uri -> its origin uri\n const diskBefore = new Map<string, string>() // origin -> pre-batch disk content ('' if absent)\n const renamedOld = new Set<string>() // oldUris consumed by a rename (E-OLD + cycle guards)\n // A create-overwrite on a PRE-EXISTING on-disk file. Kept SEPARATE from `created` because it\n // is a real inode: a following delete must still emit a physical delete (blocker), so the\n // TIER-2 delete + collision guards keep keying on `created`, while the \"no prior on-disk\n // identifier / always-write\" checks treat it like a created file.\n const overwroteExisting = new Set<string>()\n // Destructive clobbers, keyed by the FINAL absolute path -> project-relative path, surfaced as\n // `overwritten` once we know which physical op actually landed (post-commit).\n const overwrites = new Map<string, string>()\n // An overwrite-RENAME's destroyed destination, keyed by newUri -> prior disk bytes; drives the\n // `(overwritten)` audit row attached to the rename PhysicalOp (no `order`/`diskBefore` entry).\n const clobbered = new Map<string, string>()\n const diskCache = new Map<string, string | undefined>()\n const readDisk = (u: string): string | undefined => {\n if (!diskCache.has(u)) diskCache.set(u, this.readFile(abs.get(u) as string))\n return diskCache.get(u)\n }\n const resolveOrig = (u: string): string => aliasMap.get(u) ?? u\n const touch = (o: string): void => {\n if (!ordered.has(o)) {\n ordered.add(o)\n order.push(o)\n }\n if (!diskBefore.has(o)) diskBefore.set(o, readDisk(o) ?? '')\n }\n // Current projected content of `u` (undefined ⇒ absent: deleted, or never created/on-disk).\n const contentOf = (u: string): string | undefined => {\n const f = vfs.get(resolveOrig(u))\n if (f) return f.kind === 'live' ? f.content : undefined\n return readDisk(u)\n }\n // Does `u` name a LIVE file (on disk / created / edited-in-place) — NOT a pending rename target?\n const liveOccupied = (u: string): boolean => {\n const f = vfs.get(resolveOrig(u))\n return f ? f.kind === 'live' : readDisk(u) !== undefined\n }\n let expectedOld: string | undefined\n\n for (const op of ops) {\n if (op.type === 'create') {\n if (resolveOrig(op.uri) !== op.uri) {\n return refuse(`cannot create ${rel(op.uri)}: conflicts with another operation`)\n }\n if (liveOccupied(op.uri)) {\n if (op.options?.overwrite === true) {\n // overwrite WINS over ignoreIfExists (LSP CreateFileOptions). The gate is already\n // verified by the early-refusal loop. Refuse a symlink/directory target; clobber a\n // regular file only. `touch` snapshots the prior disk bytes so the digest audits the\n // destroyed content; `overwroteExisting` (NOT `created`) keeps a following delete real.\n const a = abs.get(op.uri) as string\n if (!isOverwritableRegularFile(a)) {\n return refuse(\n `cannot overwrite ${rel(op.uri)}: not a regular file on disk (symlink/directory — refused)`,\n )\n }\n if (op.uri === queriedUri && sha256(readDisk(op.uri) ?? '') !== sha256(text)) {\n return refuse(\n 'the file changed on disk since the rename was computed; re-query and retry',\n )\n }\n touch(op.uri)\n vfs.set(op.uri, { kind: 'live', finalUri: op.uri, content: '' })\n overwroteExisting.add(op.uri)\n overwrites.set(a, rel(op.uri))\n continue\n }\n if (op.options?.ignoreIfExists === true) continue // safe no-op: leave the file as-is\n return refuse(`cannot create ${rel(op.uri)}: it already exists`)\n }\n touch(op.uri)\n vfs.set(op.uri, { kind: 'live', finalUri: op.uri, content: '' })\n created.add(op.uri)\n } else if (op.type === 'delete') {\n const base = contentOf(op.uri)\n if (base === undefined) {\n if (op.options?.ignoreIfNotExists === true) continue // safe no-op: nothing to delete\n return refuse(`cannot delete ${rel(op.uri)}: it does not exist`)\n }\n const o = resolveOrig(op.uri)\n if (!created.has(o) && !isRegularFile(abs.get(o) as string)) {\n return refuse(\n `cannot delete ${rel(op.uri)}: not a regular file (recursive/directory delete unsupported in v1)`,\n )\n }\n touch(o)\n vfs.set(o, { kind: 'deleted' })\n } else if (op.type === 'rename') {\n const src = contentOf(op.oldUri)\n if (src === undefined)\n return refuse(`cannot rename ${rel(op.oldUri)}: it does not exist`)\n const o = resolveOrig(op.oldUri)\n if (renamedOld.has(op.newUri) || resolveOrig(op.newUri) === o) {\n return refuse(\n `cannot rename ${rel(op.oldUri)} onto a same-batch source (rename cycle in this edit)`,\n )\n }\n if (resolveOrig(op.newUri) !== op.newUri) {\n return refuse(\n `cannot rename to ${rel(op.newUri)}: it is already a target in this edit`,\n )\n }\n const newAbs = abs.get(op.newUri) as string\n // An overwrite clobbers an EXISTING destination — detect it via liveOccupied (regular file\n // / through-symlink) OR a raw lstat (catches a DIRECTORY, which reads as unoccupied).\n if (\n op.options?.overwrite === true &&\n (liveOccupied(op.newUri) || existsLstat(newAbs))\n ) {\n // gate verified by the early-refusal loop. Clobber an EXISTING on-disk regular file only\n // — refuse a target created/edited IN this same batch (structural two-into-one), a\n // symlink/directory (no clobber-through-link audit lie), and a drifted open file.\n if (vfs.get(op.newUri)?.kind === 'live') {\n return refuse(\n `cannot overwrite ${rel(op.newUri)}: it is created or edited in this same edit (refused)`,\n )\n }\n if (!isOverwritableRegularFile(newAbs)) {\n return refuse(\n `cannot overwrite ${rel(op.newUri)}: not a regular file on disk (symlink/directory — refused)`,\n )\n }\n if (op.newUri === queriedUri && sha256(readDisk(op.newUri) ?? '') !== sha256(text)) {\n return refuse(\n 'the file changed on disk since the rename was computed; re-query and retry',\n )\n }\n // capture the destroyed bytes WITHOUT touching `order` (no phantom origin entry); the\n // clobber digest row + `overwritten` are emitted post-plan / post-commit.\n clobbered.set(op.newUri, readDisk(op.newUri) ?? '')\n overwrites.set(newAbs, rel(op.newUri))\n // fall through to normal rename bookkeeping.\n } else if (liveOccupied(op.newUri)) {\n if (op.options?.ignoreIfExists === true) continue // safe no-op: skip; old stays\n return refuse(`cannot rename to ${rel(op.newUri)}: it already exists`)\n }\n touch(o)\n vfs.set(o, { kind: 'live', finalUri: op.newUri, content: src })\n aliasMap.set(op.newUri, o)\n renamedOld.add(op.oldUri)\n } else {\n // edit. An edit naming a uri that was itself renamed earlier is ambiguous (edit-the-moved\n // file vs write-a-shim into the freed slot) — refuse rather than silently guess.\n if (renamedOld.has(op.uri)) {\n return refuse(\n `cannot edit ${rel(op.uri)}: it was renamed in this edit; address the new path`,\n )\n }\n const base = contentOf(op.uri)\n if (base === undefined) return refuse(`cannot read edited file ${rel(op.uri)}`)\n if (op.uri === queriedUri && sha256(base) !== sha256(text)) {\n return refuse(\n 'the file changed on disk since the rename was computed; re-query and retry',\n )\n }\n if (op.uri === queriedUri && op.edits[0]) {\n expectedOld = sliceByOffsets(base, op.edits[0].range, encoding)\n }\n const o = resolveOrig(op.uri)\n const f = vfs.get(o)\n const finalUri = f?.kind === 'live' ? f.finalUri : op.uri\n touch(o)\n vfs.set(o, {\n kind: 'live',\n finalUri,\n content: applyTextEdits(base, op.edits, encoding),\n })\n }\n }\n\n // Old-identifier staleness guard. Reads each edited file's CURRENT on-disk slice; a rename\n // TARGET (not yet on disk) reads `undefined` and is skipped, so an import fix-up in a moved\n // file never trips it. Created files have no on-disk old identifier to match.\n if (expectedOld !== undefined) {\n for (const op of ops) {\n // A created OR overwrite-created file is intentionally truncate-and-replaced, so its\n // on-disk old identifier is irrelevant — documented skip (no stale-symbol false positive).\n if (op.type !== 'edit') continue\n const oo = resolveOrig(op.uri)\n if (created.has(oo) || overwroteExisting.has(oo)) continue\n const cur = readDisk(op.uri)\n if (cur === undefined) continue\n for (const e of op.edits) {\n if (sliceByOffsets(cur, e.range, encoding) !== expectedOld) {\n return refuse(\n 'an edit site no longer matches the renamed symbol; re-query and retry',\n )\n }\n }\n }\n }\n\n // Collision guard (data-loss): a path being DELETED must not also be a live rename/create\n // target in the same batch (else its physical delete would unlink the just-moved file).\n const liveFinalAbs = new Set<string>()\n for (const o of order) {\n const f = vfs.get(o)\n if (f?.kind === 'live') liveFinalAbs.add(abs.get(f.finalUri) as string)\n }\n for (const o of order) {\n const f = vfs.get(o)\n if (f?.kind !== 'deleted' || created.has(o)) continue\n if (liveFinalAbs.has(abs.get(o) as string)) {\n return refuse(\n `cannot delete ${rel(o)}: its path is a rename or create target in this edit`,\n )\n }\n }\n\n // Build the physical plan + audit digests. `digestForPhysical` maps every physical op to its\n // digest row (an edited-AND-renamed pair is TWO ops → ONE shared row, so a partial commit\n // that lands either half still surfaces the row). `before` is ALWAYS the pre-batch disk\n // snapshot, so a delete-then-create revive reports the file's real prior content.\n const physical: PhysicalOp[] = []\n const digests: RenameDigest[] = []\n const digestForPhysical: number[] = []\n // Extra digest rows carried by a physical op (e.g. an overwrite-rename's destroyed-target\n // `(overwritten)` row attached to the rename) — ONE reconstruction rule for a partial commit.\n const extraRowsForPhysical: number[][] = []\n const push = (p: PhysicalOp, d: number, extra: number[] = []): void => {\n physical.push(p)\n digestForPhysical.push(d)\n extraRowsForPhysical.push(extra)\n }\n // Build the `(overwritten)` clobber row for a rename whose destination is being clobbered,\n // returning its digest index (to attach to the rename op) or `[]` when nothing was clobbered.\n const clobberRow = (finalUri: string): number[] => {\n if (!clobbered.has(finalUri)) return []\n return [\n digests.push({\n file: `${rel(finalUri)} (overwritten)`,\n before: sha256(clobbered.get(finalUri) as string),\n after: '',\n }) - 1,\n ]\n }\n // TIER 1 — writes & renames, in first-touch order.\n for (const o of order) {\n const f = vfs.get(o)\n if (f?.kind !== 'live') continue\n const moved = f.finalUri !== o\n const before = sha256(diskBefore.get(o) ?? '')\n // A created OR overwrite-created file always emits its write (an intentional truncate-and-\n // replace), even when the new bytes happen to equal the prior on-disk bytes.\n const contentChanged =\n created.has(o) || overwroteExisting.has(o) || sha256(f.content) !== before\n if (!moved) {\n if (contentChanged) {\n const d = digests.push({ file: rel(o), before, after: sha256(f.content) }) - 1\n push({ kind: 'write', absPath: abs.get(o) as string, newText: f.content }, d)\n }\n } else if (created.has(o)) {\n // created then renamed pre-write: no inode to move — one write at the final path.\n const d = digests.push({ file: rel(f.finalUri), before, after: sha256(f.content) }) - 1\n push({ kind: 'write', absPath: abs.get(f.finalUri) as string, newText: f.content }, d)\n } else if (contentChanged) {\n // edited AND renamed: ONE digest row, TWO physical ops (rename THEN write) sharing it.\n const d =\n digests.push({\n file: `${rel(o)} → ${rel(f.finalUri)}`,\n before,\n after: sha256(f.content),\n }) - 1\n // the destroyed-target row (if any) is attached to the RENAME op — the op that clobbers.\n push(\n {\n kind: 'rename',\n fromAbs: abs.get(o) as string,\n toAbs: abs.get(f.finalUri) as string,\n },\n d,\n clobberRow(f.finalUri),\n )\n push({ kind: 'write', absPath: abs.get(f.finalUri) as string, newText: f.content }, d)\n } else {\n // pure rename (content unchanged).\n const d =\n digests.push({ file: `${rel(o)} → ${rel(f.finalUri)}`, before, after: before }) - 1\n push(\n {\n kind: 'rename',\n fromAbs: abs.get(o) as string,\n toAbs: abs.get(f.finalUri) as string,\n },\n d,\n clobberRow(f.finalUri),\n )\n }\n }\n // TIER 2 — deletes, in first-touch order, after every write/rename.\n for (const o of order) {\n const f = vfs.get(o)\n if (f?.kind !== 'deleted' || created.has(o)) continue\n const d =\n digests.push({\n file: `${rel(o)} (deleted)`,\n before: sha256(diskBefore.get(o) ?? ''),\n after: '',\n }) - 1\n push({ kind: 'delete', absPath: abs.get(o) as string }, d)\n }\n if (physical.length === 0) return { applied: false }\n\n // PHASE 2: stage-then-commit, then resync the server's open buffer for what ACTUALLY landed\n // (a partial commit must never push projected bytes the disk does not hold).\n const res = this.writer.commit(physical)\n const landed = new Set(res.completed)\n const landedWrite = (a: string) =>\n res.completed.some((p) => p.kind === 'write' && p.absPath === a)\n const landedRename = (a: string) =>\n res.completed.some((p) => p.kind === 'rename' && p.toAbs === a)\n const landedDelete = (a: string) =>\n res.completed.some((p) => p.kind === 'delete' && p.absPath === a)\n\n for (const o of order) {\n const f = vfs.get(o)\n if (f?.kind !== 'live') continue\n const moved = f.finalUri !== o\n const toAbs = abs.get(f.finalUri) as string\n if (created.has(o)) {\n if (landedWrite(toAbs)) {\n // A created file that is ALSO the open (queried) file — only reachable via a\n // delete→create→rename revive — must migrate the open buffer, not no-op applyEdited.\n if (o === queriedUri && moved) client.didFileRename(o, f.finalUri, f.content)\n else client.applyEdited(f.finalUri, f.content)\n }\n } else if (moved) {\n // Migrate the open buffer ONLY if the physical rename actually landed. (The paired write\n // executes AFTER the rename, so a landed write always implies a landed rename; we never\n // tell the server a file moved when its origin still holds the bytes on disk.)\n if (landedRename(toAbs)) {\n // An overwrite-rename clobbered the destination — close any open buffer for the OLD\n // destination first (didFileRename blindly re-opens newUri; a stale dest buffer would\n // otherwise linger). No-op if the destination was never open.\n if (clobbered.has(f.finalUri)) client.didFileDelete(f.finalUri)\n // bytes now at finalUri = the edited content IFF the paired write landed; else pristine.\n const wl = landedWrite(toAbs)\n client.didFileRename(o, f.finalUri, wl ? f.content : (diskBefore.get(o) ?? ''))\n }\n } else if (landedWrite(abs.get(o) as string)) {\n client.applyEdited(o, f.content)\n }\n }\n for (const o of order) {\n const f = vfs.get(o)\n if (f?.kind !== 'deleted' || created.has(o)) continue\n if (landedDelete(abs.get(o) as string)) client.didFileDelete(o)\n }\n\n const outDigests = res.partial\n ? [\n ...new Set(\n physical.flatMap((p, i) =>\n landed.has(p)\n ? [digestForPhysical[i] as number, ...(extraRowsForPhysical[i] as number[])]\n : [],\n ),\n ),\n ].map((i) => digests[i] as RenameDigest)\n : digests\n // A destructive clobber is surfaced ONLY when the physical op that destroyed the prior bytes\n // actually landed (a write at the path, or a rename onto it) — never on a previewed-but-aborted\n // or partial-non-landed op.\n const overwritten = [...overwrites]\n .filter(([a]) => landedWrite(a) || landedRename(a))\n .map(([, r]) => r)\n return {\n applied: true,\n digests: outDigests,\n ...(overwritten.length ? { overwritten } : {}),\n ...(res.partial ? { partial: true, partialError: res.error } : {}),\n }\n },\n )\n }\n\n /**\n * The partial-rename completeness guard. Extracts the old identifier at the queried position,\n * then scans the allowlisted root group for same-language files that mention it as a whole word\n * but are NOT covered by the server's edit (text edits + resource-op endpoints). Any such file is\n * a SUSPECT — the edit is likely partial (an open-files-scoped server). `unknown` ⇒ the scan was\n * truncated (cap hit). Server-agnostic: a whole-project-rename server covers every use ⇒ `complete`.\n */\n private assessCompleteness(\n edit: NormalizedWorkspaceEdit,\n input: LspRenameInput,\n queriedAbs: string,\n queriedText: string,\n ): { completeness: RenameCompleteness; suspectFiles: string[]; oldName: string } {\n const lister = this.listFiles\n const oldName = identifierAt(queriedText, input.line, input.column)\n const group = [input.projectRoot, ...(input.workspaceRoots ?? [])]\n if (!lister || !oldName) return { completeness: 'complete', suspectFiles: [], oldName }\n const covered = new Set<string>()\n const cover = (uri: string): void => {\n try {\n covered.add(confineEditedUriToRoots(group, uri))\n } catch {\n // out of the root group — not scannable, not a \"missed\" in-project file\n }\n }\n for (const f of edit.files) cover(f.uri)\n for (const op of edit.operations) {\n if (op.type === 'rename') {\n cover(op.oldUri)\n cover(op.newUri)\n } else cover(op.uri)\n }\n const { files, truncated } = lister(group, { extension: extname(queriedAbs) })\n const re = wholeWordRegex(oldName)\n const suspectsAbs: string[] = []\n for (const f of files) {\n const real = realpathOrSelf(f)\n if (covered.has(real)) continue\n const t = this.readFile(real)\n if (t === undefined) continue\n if (re.test(t)) {\n suspectsAbs.push(real)\n if (suspectsAbs.length >= MAX_SUSPECTS) break\n }\n }\n const completeness: RenameCompleteness = suspectsAbs.length\n ? 'suspect'\n : truncated\n ? 'unknown'\n : 'complete'\n return {\n completeness,\n suspectFiles: suspectsAbs.map((p) => relative(input.projectRoot, p)),\n oldName,\n }\n }\n\n private shape(\n input: LspRenameInput,\n run: RunOutcome,\n queriedUri: string,\n queriedText: string,\n guard?: { completeness: RenameCompleteness; suspectFiles: string[] },\n ): LspRenameResult {\n const { edit, encoding, serverInfo } = run\n const totalEditCount = edit.files.reduce((n, f) => n + f.edits.length, 0)\n const group = [input.projectRoot, ...(input.workspaceRoots ?? [])]\n const edits = edit.files.map((f) =>\n this.previewFile(f, group, queriedUri, queriedText, encoding),\n )\n const versionWarning =\n serverInfo === undefined\n ? 'the language server did not report its version (serverInfo); the rename cannot be attributed to a specific server version'\n : undefined\n // Resource-op paths are surfaced PROJECT-RELATIVE (never an absolute URI — no home-dir/secret\n // leakage); a URI outside every allowlisted root shows `(out of project root)`, never its path.\n const relUri = (uri: string): string => {\n try {\n return relative(input.projectRoot, confineEditedUriToRoots(group, uri))\n } catch {\n return '(out of project root)'\n }\n }\n const resourceOps = edit.resourceOps.map((op) => ({ kind: op.kind, uris: op.uris.map(relUri) }))\n return {\n status: run.status,\n kind: 'rename',\n applied: run.apply.applied,\n ...(run.refused ? { refused: run.refused } : {}),\n newName: input.newName,\n fileCount: edit.files.length,\n totalEditCount,\n edits,\n ...(resourceOps.length > 0 ? { resourceOps } : {}),\n ...(run.apply.digests ? { digests: run.apply.digests } : {}),\n ...(run.apply.overwritten ? { overwritten: run.apply.overwritten } : {}),\n ...(run.apply.partial ? { partial: true } : {}),\n ...(run.apply.partialError ? { partialError: run.apply.partialError } : {}),\n ...(serverInfo ? { serverInfo } : {}),\n ...(input.toolchain ? { toolchain: input.toolchain } : {}),\n ...(guard ? { completeness: guard.completeness } : {}),\n ...(guard && guard.suspectFiles.length > 0\n ? { suspectedMissedFiles: guard.suspectFiles }\n : {}),\n encoding,\n ...(versionWarning ? { versionWarning } : {}),\n }\n }\n\n private previewFile(\n f: { uri: string; edits: NormalizedFileEdit[] },\n group: string[],\n queriedUri: string,\n queriedText: string,\n encoding: PositionEncoding,\n ): RenamePreviewFile {\n let abs: string | undefined\n try {\n // Confine to the GROUP — a cross-root edit in an allowlisted workspace folder is in-root.\n abs = confineEditedUriToRoots(group, f.uri)\n } catch {\n // Out of every root: surface path + count ONLY, never read/surface its bytes (disclosure guard).\n return {\n uri: f.uri,\n file: '(out of project root)',\n editCount: f.edits.length,\n outOfRoot: true,\n }\n }\n const text = f.uri === queriedUri ? queriedText : this.readFile(abs)\n const out: RenamePreviewFile = {\n uri: f.uri,\n file: relative(group[0] ?? '', abs),\n editCount: f.edits.length,\n }\n if (text !== undefined) out.hunks = f.edits.map((e) => this.hunk(text, e, encoding))\n return out\n }\n\n private hunk(text: string, e: NormalizedFileEdit, encoding: PositionEncoding): RenamePreviewEdit {\n const start = fromLspPosition(text, e.range.start, encoding)\n const end = fromLspPosition(text, e.range.end, encoding)\n const range: HumanRange = { start, end }\n const out: RenamePreviewEdit = {\n range,\n oldText: this.redact(sliceByOffsets(text, e.range, encoding)),\n newText: this.redact(e.newText),\n }\n if (e.needsConfirmation) out.needsConfirmation = true\n if (e.annotationLabel !== undefined) out.annotationLabel = this.redact(e.annotationLabel)\n return out\n }\n}\n\n/** The OLD text of an edit, sliced by absolute offsets — NEVER reconstructed from line:col. */\nfunction sliceByOffsets(text: string, range: LspRange, encoding: PositionEncoding): string {\n // lspPositionToOffset is CRLF/BOM/non-BMP faithful; reusing it keeps oldText byte-accurate.\n return text.slice(\n lspPositionToOffset(text, range.start, encoding),\n lspPositionToOffset(text, range.end, encoding),\n )\n}\n\nfunction base(\n nav: NavResult<unknown>,\n edit: NormalizedWorkspaceEdit,\n apply: ApplyOutcome,\n refused?: string,\n): RunOutcome {\n return {\n status: nav.status,\n edit,\n encoding: nav.encoding,\n ...(nav.serverInfo ? { serverInfo: nav.serverInfo } : {}),\n ...(refused ? { refused } : {}),\n apply,\n }\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@sackville-mcp/lsp",
3
+ "version": "0.0.1-alpha.0",
4
+ "type": "module",
5
+ "license": "Apache-2.0",
6
+ "exports": {
7
+ ".": {
8
+ "import": {
9
+ "types": "./dist/index.d.mts",
10
+ "default": "./dist/index.mjs"
11
+ }
12
+ }
13
+ },
14
+ "main": "./dist/index.mjs",
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "dependencies": {
19
+ "vscode-jsonrpc": "^8.2.1",
20
+ "vscode-languageserver-protocol": "^3.17.5"
21
+ },
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/ceautery/sackville.git",
28
+ "directory": "packages/lsp"
29
+ },
30
+ "scripts": {
31
+ "build": "tsdown src/index.ts --dts",
32
+ "typecheck": "tsc --noEmit"
33
+ }
34
+ }