UNPKG

pi-lens

Version:

Real-time code feedback for pi — LSP, linters, formatters, type-checking, structural analysis & booboo

4,693 lines • 165 kB
import { createRequire as __pilensCreateRequire } from "node:module"; const require = __pilensCreateRequire(import.meta.url);
import {
  augmentPythonEnvironment,
  detectPythonEnvironment,
  detectPythonVenv,
  pythonEnvironmentToolCandidates
} from "./chunk-DIULIWKU.js";
import {
  resolveOpengrepConfig
} from "./chunk-VDBPVIKD.js";
import {
  extensionsForLanguage
} from "./chunk-TGPXLY2J.js";
import {
  load,
  resolvePackagePath
} from "./chunk-QOZXJ4NA.js";
import {
  hasCargoWorkspaceTable,
  readCargoWorkspaceExclude,
  readCargoWorkspaceMembers,
  readTextFileOrUndefined
} from "./chunk-PAK6IDME.js";
import {
  HOOK_WALL_BUDGET_MS,
  bounded
} from "./chunk-GUOGVTCQ.js";
import {
  ensureTool,
  findManagedToolBinary,
  getInstallAttempt,
  getToolEnvironment,
  getToolPath,
  isZizmorAuditTarget,
  logSessionStart,
  resolveZizmorGitHubToken
} from "./chunk-OQGFCZJ4.js";
import {
  resolveToolCwd
} from "./chunk-RUOG5D2C.js";
import {
  findGlobalBinary
} from "./chunk-U3VGR4VU.js";
import {
  classifyProbeFailure,
  createAvailabilityLatch,
  describeInstallAttempt,
  logAvailabilityDecision,
  startHostStallSampler
} from "./chunk-WONERJTP.js";
import {
  OWNER_TAG_ENV,
  SpawnFailureError,
  classifySpawnFailure,
  createGenerationSource,
  ephemeralStagingRoot,
  getGlobalPiLensDir,
  getProjectIgnoreGlobs,
  hasSpawnFailureKind,
  holdOwnChildPid,
  isCommandAvailableAsync,
  isPathIgnoredByProject,
  ownerTagForChildren,
  safeSpawnAsync
} from "./chunk-VN2AXLEX.js";
import {
  DOTNET_CSHARP_ROOT_MARKERS,
  DOTNET_FSHARP_ROOT_MARKERS,
  KIND_EXTENSIONS
} from "./chunk-5ZEJHV35.js";
import {
  lspSectionOf,
  migrationSubject,
  reportConfigReadFailure,
  reportPiLensConfigRecords,
  resolvePiLensConfig,
  summarizeConfigResolution
} from "./chunk-DQ2NZ7C4.js";
import {
  FRESHNESS_CADENCE_MS,
  LSP_NAMESPACE_KEY,
  dirMtimeRecordsAsync,
  dirMtimesStillFreshAsync,
  getPiLensGlobalConfigPath,
  unknownDirMtimeRecords
} from "./chunk-VNMT7C64.js";
import {
  incrementDegradationCount,
  recordDegradationOnce
} from "./chunk-N3YQJI6O.js";
import {
  claimPhaseOncePerSession,
  currentSessionRecordId,
  logLatency,
  releasePhaseClaim
} from "./chunk-5THXD6X5.js";
import {
  getGlobalPiLensLogDir,
  getProcessSingleton,
  isTestMode,
  redactSecrets
} from "./chunk-UK3CMEAL.js";
import {
  STAGE_TMP_PATTERN
} from "./chunk-LJDODBBZ.js";
import {
  BoundedFifoMap,
  BoundedLruCache
} from "./chunk-ABBOA7UD.js";
import {
  CARGO_WORKSPACE_MEMBER_DIALECT,
  direntsHaveMarkerGlobMatch,
  findLocalToolConfig,
  isAtOrAboveHomeDir,
  isFullyQualified,
  isWindowsPath,
  matchesWorkspaceMemberPattern,
  normalizeEphemeralMapKey,
  normalizeFilePath,
  normalizeMapKey,
  pathsEqual,
  toPosix
} from "./chunk-Q5U6FMDI.js";
import {
  __require
} from "./chunk-NXL4FFQQ.js";

// dist/clients/lsp/server.js
import { existsSync as existsSync3, mkdirSync as mkdirSync2, statSync as statSync3 } from "node:fs";
import { access, readFile, readdir, stat } from "node:fs/promises";
import os6 from "node:os";
import path7 from "node:path";

// dist/clients/sgconfig.js
import { createHash } from "node:crypto";
import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
var SGCONFIG_NAMES = ["sgconfig.yml", "sgconfig.yaml"];
function findLocalSgconfig(startDir) {
  return findLocalToolConfig(startDir, SGCONFIG_NAMES);
}
function canonicalDir(dir) {
  try {
    return fs.realpathSync.native(dir);
  } catch {
    return path.resolve(dir);
  }
}
function getAstGrepRuleSources(projectRoot = process.cwd()) {
  const root = path.resolve(projectRoot || process.cwd());
  const packageRoot = resolvePackagePath(import.meta.url);
  const candidates = [];
  if (canonicalDir(root) !== canonicalDir(packageRoot)) {
    candidates.push({
      dir: path.join(root, "rules", "ast-grep-rules", "rules"),
      origin: "project",
      tier: "primary"
    }, {
      dir: path.join(root, "rules", "ast-grep-rules", "coderabbit", "rules"),
      origin: "project",
      tier: "secondary"
    });
  }
  candidates.push({
    dir: path.join(packageRoot, "rules", "ast-grep-rules", "rules"),
    origin: "bundled",
    tier: "primary"
  }, {
    dir: path.join(packageRoot, "rules", "ast-grep-rules", "coderabbit", "rules"),
    origin: "bundled",
    tier: "secondary"
  });
  const seen = /* @__PURE__ */ new Set();
  return candidates.filter((source) => {
    if (!fs.existsSync(source.dir))
      return false;
    const key = canonicalDir(source.dir);
    if (seen.has(key))
      return false;
    seen.add(key);
    return true;
  });
}
function findYamlFiles(dir) {
  const files = [];
  let entries;
  try {
    entries = fs.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
  } catch {
    return files;
  }
  for (const entry of entries) {
    const full = path.join(dir, entry.name);
    if (entry.isDirectory()) {
      files.push(...findYamlFiles(full));
    } else if (entry.isFile() && /\.ya?ml$/i.test(entry.name)) {
      files.push(full);
    }
  }
  return files;
}
function parseRuleId(raw) {
  const scalar = raw.match(/^id:\s*(.*?)\s*$/m)?.[1];
  if (!scalar)
    return void 0;
  const withoutComment = scalar.replace(/\s+#.*$/, "").trim();
  const unquoted = withoutComment.startsWith('"') && withoutComment.endsWith('"') || withoutComment.startsWith("'") && withoutComment.endsWith("'") ? withoutComment.slice(1, -1) : withoutComment;
  if (/^[A-Za-z0-9_.:-]+$/.test(unquoted))
    return unquoted;
  try {
    const parsed = load(raw);
    if (!parsed || typeof parsed !== "object" || !("id" in parsed)) {
      return void 0;
    }
    const id = parsed.id;
    return typeof id === "string" ? id : void 0;
  } catch {
    return void 0;
  }
}
function parseRuleDocuments(content) {
  return content.split(/^---\s*$/m).filter((document) => document.trim()).map((raw) => ({ raw, id: parseRuleId(raw) }));
}
var bundledSnapshots = /* @__PURE__ */ new Map();
function snapshotRuleSource(source) {
  const cacheKey = canonicalDir(source.dir);
  if (source.origin === "bundled") {
    const cached = bundledSnapshots.get(cacheKey);
    if (cached)
      return cached;
  }
  const hash = createHash("sha256");
  const files = [];
  for (const file of findYamlFiles(source.dir)) {
    const relativePath = path.relative(source.dir, file);
    hash.update(relativePath);
    hash.update("\0");
    let content;
    try {
      content = fs.readFileSync(file, "utf8");
    } catch {
      hash.update("missing");
      hash.update("\0");
      continue;
    }
    hash.update(content);
    hash.update("\0");
    files.push({ file, relativePath, documents: parseRuleDocuments(content) });
  }
  const snapshot = { ...source, files, digest: hash.digest("hex") };
  if (source.origin === "bundled")
    bundledSnapshots.set(cacheKey, snapshot);
  return snapshot;
}
function sourceFingerprint(sources) {
  const hash = createHash("sha256");
  for (const source of sources) {
    hash.update(source.origin);
    hash.update("\0");
    hash.update(source.tier);
    hash.update("\0");
    hash.update(canonicalDir(source.dir));
    hash.update("\0");
    hash.update(source.digest);
    hash.update("\0");
  }
  return hash.digest("hex");
}
function materializeMergedRuleDir(sources, mergedDir) {
  fs.rmSync(mergedDir, { recursive: true, force: true });
  fs.mkdirSync(mergedDir, { recursive: true });
  const claimedByEarlierSource = /* @__PURE__ */ new Set();
  for (const [sourceIndex, source] of sources.entries()) {
    const claimedThisSource = /* @__PURE__ */ new Set();
    for (const [fileIndex, file] of source.files.entries()) {
      const survivingDocuments = file.documents.filter((document) => {
        if (!document.id)
          return true;
        if (claimedByEarlierSource.has(document.id))
          return false;
        claimedThisSource.add(document.id);
        return true;
      });
      if (survivingDocuments.length === 0)
        continue;
      const destination = path.join(mergedDir, `source${sourceIndex}-${String(fileIndex).padStart(5, "0")}.yml`);
      if (survivingDocuments.length === file.documents.length) {
        fs.copyFileSync(file.file, destination);
      } else {
        fs.writeFileSync(destination, `${survivingDocuments.map((document) => document.raw.trim()).join("\n---\n")}
`);
      }
    }
    for (const id of claimedThisSource)
      claimedByEarlierSource.add(id);
  }
}
var cachedBaselines = /* @__PURE__ */ new Map();
function rootArtifactKey(root) {
  return createHash("sha256").update(root).digest("hex").slice(0, 12);
}
function resolveBaselineSgconfig(projectRoot = process.cwd()) {
  const root = canonicalDir(path.resolve(projectRoot || process.cwd()));
  const sources = getAstGrepRuleSources(root).map(snapshotRuleSource);
  if (sources.length === 0)
    return void 0;
  const fingerprint = sourceFingerprint(sources);
  const cached = cachedBaselines.get(root);
  if (cached?.fingerprint === fingerprint && fs.existsSync(cached.path) && fs.existsSync(cached.mergedDir)) {
    return cached.path;
  }
  const dir = path.join(os.tmpdir(), "pi-lens-ast-grep");
  fs.mkdirSync(dir, { recursive: true });
  const artifactKey = rootArtifactKey(root);
  const file = path.join(dir, `baseline-${process.pid}-${artifactKey}.sgconfig.yml`);
  const mergedDir = path.join(dir, `baseline-${process.pid}-${artifactKey}.rules`);
  const protectedArtifacts = /* @__PURE__ */ new Set([file, mergedDir]);
  for (const baseline of cachedBaselines.values()) {
    protectedArtifacts.add(baseline.path);
    protectedArtifacts.add(baseline.mergedDir);
  }
  cleanupStaleBaselines(dir, protectedArtifacts);
  materializeMergedRuleDir(sources, mergedDir);
  const ruleDirForYaml = mergedDir.split(path.sep).join("/");
  fs.writeFileSync(file, `ruleDirs:
  - ${JSON.stringify(ruleDirForYaml)}
`);
  cachedBaselines.set(root, { fingerprint, path: file, mergedDir });
  enforceBaselineEntryCap(dir, /* @__PURE__ */ new Set([file, mergedDir]));
  return file;
}
var SG_BASELINE_RETAINED_ENTRIES = 24;
function enforceBaselineEntryCap(dir, keep) {
  let names;
  try {
    names = fs.readdirSync(dir);
  } catch {
    return;
  }
  const candidates = [];
  for (const name of names) {
    const isConfig = /^baseline(?:-\d+(?:-[a-f0-9]+)?)?\.sgconfig\.yml$/.test(name);
    const isMergedDir = /^baseline-\d+(?:-[a-f0-9]+)?\.rules$/.test(name);
    if (!isConfig && !isMergedDir)
      continue;
    const full = path.join(dir, name);
    if (keep.has(full))
      continue;
    try {
      candidates.push({
        full,
        mtimeMs: fs.statSync(full).mtimeMs,
        recursive: isMergedDir
      });
    } catch {
    }
  }
  const over = candidates.length + keep.size - SG_BASELINE_RETAINED_ENTRIES;
  if (over <= 0)
    return;
  candidates.sort((a, b) => a.mtimeMs - b.mtimeMs);
  let evicted = 0;
  for (const candidate of candidates.slice(0, over)) {
    try {
      fs.rmSync(candidate.full, {
        recursive: candidate.recursive,
        force: true
      });
      evicted += 1;
      for (const [root, baseline] of cachedBaselines) {
        if (baseline.path === candidate.full || baseline.mergedDir === candidate.full) {
          cachedBaselines.delete(root);
        }
      }
    } catch {
    }
  }
  if (evicted > 0) {
    recordDegradationOnce({
      kind: "sgconfig-baseline-cap-evict",
      subject: dir,
      reason: `evicted ${evicted} oldest baseline entries over the ${SG_BASELINE_RETAINED_ENTRIES}-entry cap`
    });
  }
}
function cleanupStaleBaselines(dir, keep) {
  try {
    const cutoff = Date.now() - 7 * 24 * 60 * 60 * 1e3;
    for (const name of fs.readdirSync(dir)) {
      const isConfig = /^baseline(?:-\d+(?:-[a-f0-9]+)?)?\.sgconfig\.yml$/.test(name);
      const isMergedDir = /^baseline-\d+(?:-[a-f0-9]+)?\.rules$/.test(name);
      if (!isConfig && !isMergedDir)
        continue;
      const full = path.join(dir, name);
      if (keep.has(full))
        continue;
      try {
        const stat2 = fs.statSync(full);
        if (stat2.mtimeMs < cutoff) {
          fs.rmSync(full, { recursive: stat2.isDirectory(), force: true });
        }
      } catch {
      }
    }
  } catch {
  }
}

// dist/clients/typos-config.js
var LOCAL_TYPOS_CONFIG_NAMES = [
  "typos.toml",
  "_typos.toml",
  ".typos.toml"
];
function findLocalTyposConfig(startDir) {
  return findLocalToolConfig(startDir, LOCAL_TYPOS_CONFIG_NAMES);
}

// dist/clients/lsp/config.js
import * as os3 from "node:os";
import path4 from "node:path";

// dist/clients/dispatch/known-runner-ids.js
var knownRunnerIds = /* @__PURE__ */ new Set();
function registerRunnerId(id) {
  knownRunnerIds.add(id);
}
function runnerIdentityPopulated() {
  return knownRunnerIds.size > 0;
}
function isKnownRunnerId(id) {
  return knownRunnerIds.has(id);
}

// dist/clients/lsp/launch.js
import { execFileSync, spawn as nodeSpawn } from "node:child_process";
import fs3 from "node:fs";
import os2 from "node:os";
import path2 from "node:path";

// dist/clients/lsp/ruby-drive-dirs.js
import * as fs2 from "node:fs";
var RUBY_VERSION_DIR = /^ruby\d/i;
var rubyDirNamesCache = new BoundedLruCache(16);
function rubyDriveKey(driveRoot) {
  return normalizeMapKey(driveRoot);
}
function filterRubyDirNames(entries) {
  return entries.filter((name) => RUBY_VERSION_DIR.test(name));
}
function getRubyVersionDirNamesSync(driveRoot) {
  const key = rubyDriveKey(driveRoot);
  const cached = rubyDirNamesCache.get(key);
  if (cached !== void 0)
    return cached;
  let names;
  try {
    names = filterRubyDirNames(fs2.readdirSync(driveRoot));
  } catch {
    names = [];
  }
  rubyDirNamesCache.set(key, names);
  return names;
}
async function getRubyVersionDirNamesAsync(driveRoot) {
  const key = rubyDriveKey(driveRoot);
  const cached = rubyDirNamesCache.get(key);
  if (cached !== void 0)
    return cached;
  let names;
  try {
    names = filterRubyDirNames(await fs2.promises.readdir(driveRoot));
  } catch {
    names = [];
  }
  rubyDirNamesCache.set(key, names);
  return names;
}

// dist/clients/lsp/launch.js
var isWindows = process.platform === "win32";
var OWNER_TAG_READ_MS = 5e3;
function computeNeedsShell(resolvedCommand) {
  return isWindows && (resolvedCommand.includes(" ") || /\.(cmd|bat)$/i.test(resolvedCommand) || !/\.(exe|cmd|bat)$/i.test(resolvedCommand));
}
var DEFAULT_STARTUP_FAILURE_WINDOW_MS = 50;
var WINDOWS_NAV_STARTUP_FAILURE_WINDOW_MS = 500;
var SESSIONSTART_LOG_DIR = getGlobalPiLensLogDir();
var SESSIONSTART_LOG = path2.join(SESSIONSTART_LOG_DIR, "sessionstart.log");
var PI_LENS_BIN_DIR = path2.join(getGlobalPiLensDir(), "bin");
var PI_LENS_TOOLS_BIN_DIR = path2.join(getGlobalPiLensDir(), "tools", "node_modules", ".bin");
function logSessionStart2(msg) {
  if (isTestMode()) {
    return;
  }
  const line = redactSecrets(`[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}
`);
  try {
    fs3.mkdirSync(SESSIONSTART_LOG_DIR, { recursive: true });
    fs3.appendFileSync(SESSIONSTART_LOG, line);
  } catch {
  }
}
function compactLogValue(value, max = 280) {
  const normalized = value.replace(/\s+/g, " ").trim();
  if (!normalized)
    return "";
  return normalized.length > max ? `${normalized.slice(0, max)}...` : normalized;
}
function delimiterForPlatform(platform) {
  return platform === "win32" ? ";" : ":";
}
function splitPathEntries(value, delimiter) {
  if (!value)
    return [];
  return value.split(delimiter).map((entry) => entry.trim()).filter((entry) => entry.length > 0);
}
function normalizePathEntry(entry, platform) {
  if (platform !== "win32")
    return path2.posix.normalize(entry);
  return path2.win32.normalize(entry).toLowerCase();
}
function combinePathValuesForPlatform(values, platform = process.platform) {
  const unique = [];
  const seen = /* @__PURE__ */ new Set();
  const delimiter = delimiterForPlatform(platform);
  for (const value of values) {
    for (const entry of splitPathEntries(value, delimiter)) {
      const key = normalizePathEntry(entry, platform);
      if (seen.has(key))
        continue;
      seen.add(key);
      unique.push(entry);
    }
  }
  return unique.join(delimiter);
}
function resolvePathValue(env) {
  return combinePathValuesForPlatform([env.PATH, env.Path, env.path]);
}
function runStderrGuardedProbe(command, args) {
  try {
    const { execFileSync: execFileSync2 } = __require("node:child_process");
    const out = execFileSync2(command, args, {
      timeout: 3e3,
      encoding: "utf8",
      // #2095 sibling: execFileSync inherits stderr to the parent by
      // default (only stdout is captured into `out`), so a failed
      // powershell.exe launch would print raw diagnostics into the pi
      // TUI even though the surrounding catch already returns "" (read as
      // "unknown" PATH by every caller). Pipe stderr instead of inheriting it.
      stdio: ["ignore", "pipe", "ignore"]
    });
    return out.trim();
  } catch {
    return "";
  }
}
function readWindowsRegistryPath() {
  return runStderrGuardedProbe("powershell.exe", [
    "-NoProfile",
    "-NonInteractive",
    "-Command",
    "[System.Environment]::GetEnvironmentVariable('Path','Machine') + ';' + [System.Environment]::GetEnvironmentVariable('Path','User')"
  ]);
}
var _liveWindowsPath = null;
function getLiveWindowsPath() {
  if (_liveWindowsPath === null) {
    _liveWindowsPath = readWindowsRegistryPath();
  }
  return _liveWindowsPath;
}
async function buildAugmentedPath(basePath) {
  const candidates = [];
  const nodeDir = path2.dirname(process.execPath);
  if (nodeDir) {
    candidates.push(nodeDir);
  }
  if (isWindows) {
    const home = os2.homedir();
    const driveRoot = path2.parse(home).root;
    candidates.push(path2.join(home, ".cargo", "bin"));
    candidates.push(path2.join(home, "go", "bin"));
    candidates.push(path2.join(home, ".dotnet", "tools"));
    candidates.push(PI_LENS_BIN_DIR);
    candidates.push(PI_LENS_TOOLS_BIN_DIR);
    candidates.push(path2.join(driveRoot, "Program Files", "Go", "bin"));
    candidates.push(path2.join(driveRoot, "Go", "bin"));
    for (const entry of await getRubyVersionDirNamesAsync(driveRoot)) {
      candidates.push(path2.join(driveRoot, entry, "bin"));
    }
  }
  const effectiveBase = isWindows ? combinePathValuesForPlatform([basePath, getLiveWindowsPath()]) : basePath;
  const existing = /* @__PURE__ */ new Set();
  for (const entry of splitPathEntries(effectiveBase, path2.delimiter)) {
    if (!entry)
      continue;
    existing.add(normalizePathEntry(entry, process.platform));
  }
  const toAppend = [];
  for (const candidate of candidates) {
    if (!candidate || !fs3.existsSync(candidate))
      continue;
    const normalized = normalizePathEntry(candidate, process.platform);
    if (existing.has(normalized))
      continue;
    toAppend.push(candidate);
    existing.add(normalized);
  }
  if (toAppend.length === 0)
    return basePath ?? "";
  if (!basePath)
    return toAppend.join(path2.delimiter);
  return `${basePath}${path2.delimiter}${toAppend.join(path2.delimiter)}`;
}
function isCmdShimValid(cmdPath) {
  try {
    const content = fs3.readFileSync(cmdPath, "utf-8");
    const match = content.match(/"%~dp0[/\\]\.\.[/\\]((?:[\w./@-]|\\)+\.(?:mjs|cjs|js))"/i);
    if (!match)
      return true;
    const relPath = match[1].replace(/[/\\]/g, path2.sep);
    const target = path2.resolve(path2.dirname(cmdPath), "..", relPath);
    return fs3.existsSync(target);
  } catch {
    return true;
  }
}
function bypassPs1OnWindows(ps1Path, args) {
  const cmdPath = `${ps1Path.slice(0, -4)}.cmd`;
  if (fs3.existsSync(cmdPath)) {
    return { command: cmdPath, args, needsShell: true };
  }
  try {
    const content = fs3.readFileSync(ps1Path, "utf-8");
    const match = content.match(/"\$basedir[/\\]\.\.[/\\]((?:[\w./@-]|\\)+\.(?:mjs|cjs|js))"/i);
    if (match) {
      const relPath = match[1].replace(/[/\\]/g, path2.sep);
      const jsPath = path2.resolve(path2.dirname(ps1Path), "..", relPath);
      if (fs3.existsSync(jsPath)) {
        return {
          command: process.execPath,
          args: [jsPath, ...args],
          needsShell: false
        };
      }
    }
  } catch {
  }
  return void 0;
}
function findBinaryOnPath(command, env) {
  try {
    const result = execFileSync(isWindows ? "where" : "which", [command], {
      encoding: "utf-8",
      stdio: ["ignore", "pipe", "ignore"],
      env,
      // #1980 population sweep: this was the one synchronous
      // child-process site in the repo with no bound at all. It runs on
      // the LSP server spawn path, so an unresponsive `where`/`which` — a
      // stale network PATH entry is the classic cause — parked the event
      // loop with no ceiling, and read as ordinary compute in
      // `loop_block`. 5s matches `isCommandAvailable`/`findCommand` in
      // clients/safe-spawn.ts, which run these same two binaries.
      timeout: 5e3
    }).split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
    for (const candidate of result) {
      if (fs3.existsSync(candidate)) {
        return candidate;
      }
    }
  } catch {
  }
  return void 0;
}
function trySpawn(command, args, cwd, env, needsShell) {
  let proc;
  if (needsShell) {
    const escapeCmdArg = (s) => {
      const escaped = s.replace(/([&|<>^()!])/g, "^$1");
      return /[\s"]/.test(escaped) ? `"${escaped.replace(/"/g, '""')}"` : escaped;
    };
    const shellCommand = `"${command}" ${args.map(escapeCmdArg).join(" ")}`;
    proc = nodeSpawn(shellCommand, [], {
      cwd,
      env,
      stdio: ["pipe", "pipe", "pipe"],
      detached: !isWindows,
      windowsHide: true,
      shell: true
    });
  } else {
    proc = nodeSpawn(command, args, {
      cwd,
      env,
      stdio: ["pipe", "pipe", "pipe"],
      detached: !isWindows,
      windowsHide: isWindows
    });
  }
  if (!proc.stdin || !proc.stdout || !proc.stderr) {
    throw new Error(`Failed to spawn LSP server: ${command}`);
  }
  if (proc.exitCode !== null || proc.killed) {
    throw new Error(`LSP server ${command} exited immediately (code: ${proc.exitCode}). The binary may be missing or corrupted.`);
  }
  holdOwnChildPid(proc.pid, "lsp-spawn");
  return proc;
}
function unrefLspProcessHandles(proc) {
  try {
    proc.unref();
  } catch {
  }
  for (const stream of [proc.stdin, proc.stdout, proc.stderr]) {
    try {
      stream?.unref?.();
    } catch {
    }
  }
}
function _attachErrorHandler(proc, context, logContext, rejectOnImmediateError) {
  let stderrPreview = "";
  let closeLogged = false;
  const onStderr = (chunk) => {
    if (stderrPreview.length >= 4e3)
      return;
    stderrPreview += chunk.toString();
  };
  proc.stderr?.on("data", onStderr);
  proc.on("error", (err) => {
    if (logContext) {
      logSessionStart2("lsp process " + context + ": spawn-error command=" + logContext.command + " args=" + JSON.stringify(logContext.args) + " cwd=" + logContext.cwd + " pid=" + (logContext.pid ?? 0) + " error=" + err.message + (stderrPreview ? " stderr=" + compactLogValue(stderrPreview) : ""));
    }
    if (rejectOnImmediateError && logContext) {
      void classifySpawnFailure(err, {
        command: logContext.command,
        cwd: logContext.cwd
      }).then(rejectOnImmediateError);
    }
  });
  proc.on("close", (code, signal) => {
    if (closeLogged)
      return;
    closeLogged = true;
    proc.stderr?.off("data", onStderr);
    if (code !== 0 && code !== null) {
      if (logContext) {
        logSessionStart2("lsp process " + context + ": closed code=" + code + (signal ? " signal=" + signal : "") + " command=" + logContext.command + " args=" + JSON.stringify(logContext.args) + " cwd=" + logContext.cwd + " pid=" + (logContext.pid ?? 0) + (stderrPreview ? " stderr=" + compactLogValue(stderrPreview) : ""));
      }
    } else if (signal && logContext) {
      logSessionStart2("lsp process " + context + ": closed signal=" + signal + " command=" + logContext.command + " args=" + JSON.stringify(logContext.args) + " cwd=" + logContext.cwd + " pid=" + (logContext.pid ?? 0) + (stderrPreview ? " stderr=" + compactLogValue(stderrPreview) : ""));
    }
  });
}
async function launchLSP(command, args = [], options = {}) {
  const cwd = String(options.cwd ?? process.cwd());
  const mergedEnv = { ...process.env, ...options.env };
  const augmentedPath = await buildAugmentedPath(resolvePathValue(mergedEnv));
  const ownerTag = await ownerTagForChildren({ timeoutMs: OWNER_TAG_READ_MS });
  const env = {
    ...mergedEnv,
    PATH: augmentedPath,
    ...isWindows ? { Path: augmentedPath } : {}
  };
  if (ownerTag === void 0)
    delete env[OWNER_TAG_ENV];
  else
    env[OWNER_TAG_ENV] = ownerTag;
  const isRelativePath = !isFullyQualified(command) && (command.includes(path2.sep) || command.includes("/"));
  const explicitCommand = isRelativePath ? path2.resolve(cwd, command) : command;
  const resolvedCommand = !isFullyQualified(command) && !command.includes(path2.sep) && !command.includes("/") ? findBinaryOnPath(command, env) ?? explicitCommand : explicitCommand;
  let needsShell = computeNeedsShell(resolvedCommand);
  let spawnCommand = resolvedCommand;
  if (!isFullyQualified(command) && !command.includes(path2.sep) && !command.includes("/")) {
    const globalBinPath = await findGlobalBinary(command);
    if (globalBinPath) {
      spawnCommand = globalBinPath;
      needsShell = computeNeedsShell(spawnCommand);
    }
  }
  if (isWindows && /\.(cmd|bat)$/i.test(spawnCommand) && !isCmdShimValid(spawnCommand)) {
    logSessionStart2(`lsp cmd-shim-invalid: ${spawnCommand} target missing \u2014 skipping candidate`);
    const cause = Object.assign(new Error(`LSP .cmd shim target not found: ${spawnCommand}`), { code: "ENOENT" });
    throw await classifySpawnFailure(cause, { command, cwd });
  }
  if (isWindows && /\.ps1$/i.test(spawnCommand)) {
    const bypass = bypassPs1OnWindows(spawnCommand, args);
    if (bypass) {
      logSessionStart2(`lsp ps1-bypass: ${spawnCommand} \u2192 ${bypass.command} shell=${bypass.needsShell}`);
      spawnCommand = bypass.command;
      args = bypass.args;
      needsShell = bypass.needsShell;
    } else {
      logSessionStart2(`lsp ps1-bypass: no .cmd or JS entry found for ${spawnCommand}, spawn may hang`);
    }
  }
  let proc;
  try {
    proc = trySpawn(spawnCommand, args, cwd, env, needsShell);
  } catch (err) {
    if (!isFullyQualified(command) && !command.includes(path2.sep) && !command.includes("/")) {
      const globalBinPath = await findGlobalBinary(command);
      if (globalBinPath && globalBinPath !== spawnCommand) {
        const needsShellGlobal = computeNeedsShell(globalBinPath);
        proc = trySpawn(globalBinPath, args, cwd, env, needsShellGlobal);
      } else {
        throw await classifySpawnFailure(err, { command, cwd });
      }
    } else {
      throw await classifySpawnFailure(err, { command, cwd });
    }
  }
  if (!proc.stdin || !proc.stdout || !proc.stderr) {
    throw new Error(`Failed to spawn LSP server: ${command}`);
  }
  if (proc.exitCode !== null || proc.killed) {
    throw new Error(`LSP server ${command} exited immediately (code: ${proc.exitCode}). The binary may be missing or corrupted.`);
  }
  logSessionStart2(`lsp launch: command=${command} resolved=${spawnCommand} args=${JSON.stringify(args)} cwd=${cwd} shell=${needsShell ? "true" : "false"} pid=${proc.pid ?? 0}`);
  const formatStartupStderr = (stderr) => {
    const normalized = compactLogValue(stderr);
    if (!normalized)
      return "";
    return ` stderr=${normalized}`;
  };
  let startupStderr = "";
  const onStartupStderr = (chunk) => {
    if (startupStderr.length >= 4e3)
      return;
    startupStderr += chunk.toString();
  };
  proc.stderr?.on("data", onStartupStderr);
  try {
    await new Promise((resolve3, reject) => {
      let settled = false;
      proc.on("error", (err) => {
        if (!settled) {
          settled = true;
          void classifySpawnFailure(err, { command, cwd }).then((failure) => {
            const detail = formatStartupStderr(startupStderr);
            reject(detail ? new SpawnFailureError(failure.kind, `${failure.message}${detail}`, failure.cause) : failure);
          });
        }
      });
      proc.on("exit", (code) => {
        if (!settled && code !== null) {
          settled = true;
          const isWindowsCmd = isWindows && command.endsWith(".cmd");
          const errorMsg = isWindowsCmd && code === 1 ? `npm .cmd shim failed (underlying binary not installed). Run 'npm install' in this project or use a global installation.` : `The binary may be missing or corrupted.`;
          reject(new Error(`LSP server ${command} exited immediately with code ${code}. ${errorMsg}${formatStartupStderr(startupStderr)}`));
        }
      });
      const startupFailureWindowMs = (() => {
        if (options?.startupFailureWindowMs) {
          return options.startupFailureWindowMs;
        }
        if (isWindows && needsShell) {
          return WINDOWS_NAV_STARTUP_FAILURE_WINDOW_MS;
        }
        return DEFAULT_STARTUP_FAILURE_WINDOW_MS;
      })();
      setTimeout(() => {
        if (!settled) {
          settled = true;
          resolve3();
        }
      }, startupFailureWindowMs);
    });
  } finally {
    proc.stderr?.off("data", onStartupStderr);
  }
  _attachErrorHandler(proc, command, {
    command: spawnCommand,
    args,
    cwd,
    pid: proc.pid ?? 0
  });
  unrefLspProcessHandles(proc);
  return {
    process: proc,
    stdin: proc.stdin,
    stdout: proc.stdout,
    stderr: proc.stderr,
    pid: proc.pid ?? 0,
    command: spawnCommand,
    args
  };
}

// dist/clients/lsp/session-roots.js
import path3 from "node:path";
var SESSION_ROOT_CAP = 128;
var sessionRoots = new BoundedFifoMap(SESSION_ROOT_CAP);
function noteEvictedRoots(evicted) {
  const roots = evicted.map(([root]) => root);
  if (roots.length > 0) {
    incrementDegradationCount({
      kind: "lsp-session-root-evicted",
      subject: `cap=${SESSION_ROOT_CAP}`,
      reason: `session root registry at capacity; will load the dropped root again on the next session start or tool call naming it (dropped: ${roots[0]})`
    });
  }
  return roots;
}
function registerSessionRoot(cwd) {
  const root = path3.resolve(cwd);
  if (sessionRoots.has(root))
    return [];
  return noteEvictedRoots(sessionRoots.set(root, void 0));
}
function setSessionRootConfig(cwd, config) {
  return noteEvictedRoots(sessionRoots.set(path3.resolve(cwd), config));
}
function sessionRootConfigEntries() {
  return sessionRoots;
}
function isSessionRootRegistered(cwd) {
  return sessionRoots.has(path3.resolve(cwd));
}
function shouldInitializeSessionRoot(cwd, readyRoots) {
  const normalized = path3.resolve(cwd);
  return !readyRoots.has(normalized) || !isSessionRootRegistered(normalized);
}
function isOutsideAllSessionRoots(filePath) {
  if (sessionRoots.size === 0)
    return false;
  const resolved = path3.resolve(filePath);
  for (const root of sessionRoots.keys()) {
    if (isSameOrWithin(root, resolved))
      return false;
  }
  return true;
}
function getSessionRootsForTelemetry(limit = 4) {
  return [...sessionRoots.keys()].sort((a, b) => Number(a > b) - Number(a < b)).slice(0, limit);
}

// dist/clients/lsp/config.js
function asRecord(value) {
  return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
}
var CONFIG_RESOLVED_PHASE = "config_resolved";
function configResolutionKey(cwd) {
  return normalizeFilePath(path4.resolve(normalizeFilePath(cwd)));
}
function publishConfigResolutionPending(cwd) {
  logSessionStart(`session_start config_resolution_pending session=${currentSessionRecordId()} root=${configResolutionKey(cwd)}`);
}
function recordConfigResolved(cwd, resolution, lspConfig, homeDir, resolveMs) {
  const root = configResolutionKey(cwd);
  if (!claimPhaseOncePerSession(CONFIG_RESOLVED_PHASE, root)) {
    return;
  }
  const summary = summarizeConfigResolution(resolution, homeDir);
  const sessionId = currentSessionRecordId();
  const deniedServers = lspConfig.disabledServers?.length ?? 0;
  const legacyDocuments = summary.documents.filter((document) => document.legacy).length;
  logLatency({
    type: "phase",
    phase: CONFIG_RESOLVED_PHASE,
    filePath: root,
    durationMs: resolveMs,
    metadata: {
      sessionId,
      documents: summary.documents,
      countsByTier: summary.countsByTier,
      recordCount: summary.recordCount,
      deniedServers,
      resolveMs
    }
  });
  logSessionStart(`config resolved documents=${summary.documents.length} legacy=${legacyDocuments} records=${summary.recordCount} deniedServers=${deniedServers} resolveMs=${resolveMs} session=${sessionId} root=${root}`);
}
async function loadLSPConfig(cwd, homeDir = os3.homedir(), options = {}) {
  const reporting = options.report !== false;
  publishConfigResolutionPending(cwd);
  const resolveStartedAt = Date.now();
  const resolution = resolvePiLensConfig({
    cwd,
    globalDir: getGlobalPiLensDir(),
    // The global tier reads the PRODUCTION resolution (global-config-location
    // PR, refs #2457): which file supplies it is an env fact (PI_LENS_CONFIG_PATH,
    // then a legacy default, then PI_CODING_AGENT_DIR, then the
    // PI_LENS_HOME-relocated canonical default), not a property of the
    // `$HOME` this call's project walk is ceiling-bounded by. `homeDir`
    // stays threaded below for the walk and `globalDir`; a test redirecting
    // the global tier does it through the env, the same way production is
    // controlled.
    globalConfigPath: getPiLensGlobalConfigPath(),
    homeDir,
    // The subsystem comes from the failing DOCUMENT, not from this loader
    // (#2445). This resolution opens `~/.pi-lens/config.json` and
    // `.pi-lens.json` as well as the LSP-scoped files, and reporting all of
    // them as `lsp-config` announced an "invalid LSP config" for a file whose
    // contents are pi-lens settings. An LSP-scoped file still reports here.
    ...reporting ? { onReadError: reportConfigReadFailure } : {}
  });
  const coversProblems = newCoversClaimProblems();
  const config = lspConfigOf(resolution.value, coversProblems);
  if (reporting) {
    reportPiLensConfigRecords([
      ...resolution.records,
      ...coversClaimRecords(resolution, coversProblems)
    ]);
  }
  recordCoversUnvalidated(resolution, coversProblems);
  recordConfigResolved(cwd, resolution, config, homeDir, Date.now() - resolveStartedAt);
  return config;
}
function newCoversClaimProblems() {
  return { refusals: [], unvalidated: [] };
}
function coversPointer(serverId) {
  return `/${LSP_NAMESPACE_KEY}/servers/${serverId}/covers`;
}
function coversClaimSource(resolution, pointer) {
  let candidate = pointer;
  while (candidate !== void 0) {
    const entry = resolution.provenance.get(candidate);
    if (entry) {
      return {
        file: entry.file ?? "",
        ...entry.tier ? { tier: entry.tier } : {}
      };
    }
    const cut = candidate.lastIndexOf("/");
    candidate = cut > 0 ? candidate.slice(0, cut) : void 0;
  }
  return { file: resolution.documents.at(-1)?.file ?? "" };
}
function coversClaimRecords(resolution, problems) {
  return problems.refusals.map((refusal) => {
    const pointer = coversPointer(refusal.serverId);
    const { file, tier } = coversClaimSource(resolution, pointer);
    const dropped = refusal.declaredCount - refusal.keptCount;
    return {
      code: "PILENS_CFG_0005",
      file,
      key: pointer,
      subject: migrationSubject(file, pointer),
      reason: `lsp.servers.${refusal.serverId}.covers declares member(s) that are not recognized dispatch runner ids; ${dropped} of ${refusal.declaredCount} member(s) dropped, the server still ` + keptTail(refusal.keptCount),
      ...tier ? { tier } : {}
    };
  });
}
function keptTail(keptCount) {
  return keptCount > 0 ? "registers with the remaining covers claim" : "registers with no covers claim";
}
function recordCoversUnvalidated(resolution, problems) {
  for (const unvalidated of problems.unvalidated) {
    const pointer = coversPointer(unvalidated.serverId);
    const { file } = coversClaimSource(resolution, pointer);
    recordDegradationOnce({
      kind: "lsp-covers-unvalidated",
      subject: `${file}\0${pointer}`,
      reason: "no runner registry has populated this process yet; the covers claim was accepted without runner-id validation and the next config load validates it",
      metadata: { pointer }
    });
  }
}
function coversValidatedServers(servers, problems = void 0) {
  const out = {};
  for (const [id, raw] of Object.entries(servers)) {
    const entry = raw;
    if (!entry.covers?.length) {
      out[id] = entry;
      continue;
    }
    if (!runnerIdentityPopulated()) {
      problems?.unvalidated.push({ serverId: id });
      out[id] = entry;
      continue;
    }
    const kept = [];
    for (const member of entry.covers) {
      if (isKnownRunnerId(member))
        kept.push(member);
    }
    if (kept.length === entry.covers.length) {
      out[id] = entry;
      continue;
    }
    problems?.refusals.push({
      serverId: id,
      declaredCount: entry.covers.length,
      keptCount: kept.length
    });
    const { covers: _dropped, ...rest } = entry;
    out[id] = kept.length > 0 ? { ...rest, covers: kept } : rest;
  }
  return out;
}
function customServerSpecsOf(value) {
  const servers = asRecord(lspSectionOf(value).servers);
  return servers ? coversValidatedServers(servers) : {};
}
function lspConfigOf(value, problems) {
  const section = lspSectionOf(value);
  const config = {};
  const servers = asRecord(section.servers);
  if (servers)
    config.servers = coversValidatedServers(servers, problems);
  const serverOverrides = asRecord(section.serverOverrides);
  if (serverOverrides) {
    config.serverOverrides = serverOverrides;
  }
  if (Array.isArray(section.disabledServers)) {
    config.disabledServers = section.disabledServers;
  }
  if (Array.isArray(section.warmFiles)) {
    config.warmFiles = section.warmFiles;
  }
  return config;
}
function createCustomServer(config, id) {
  return {
    id,
    name: config.name,
    custom: true,
    // The own-command token the runner-coverage seam gates this row's
    // covers claim on (#3968 F2): the declared covering lane's binary must
    // be probeable before the claim can defer a CLI runner.
    command: config.command,
    extensions: config.extensions,
    idleEviction: "unmeasured",
    // The config-declared covers channel (#3968): the claim the loader
    // validated (`lspConfigOf`'s projection drops unknown runner ids) rides
    // the server entry into the runner-coverage seam.
    ...config.covers ? { covers: config.covers } : {},
    ...config.rootMarkers ? { rootMarkers: config.rootMarkers } : {},
    root: config.rootMarkers ? async (file) => resolveToolCwd("lsp", id, file, {
      cwd: process.cwd(),
      ...config.rootMarkers ? { rootMarkers: config.rootMarkers } : {}
    }).cwd : async (file) => resolveToolCwd("lsp", id, file, { cwd: process.cwd() }).cwd,
    async spawn(root) {
      const proc = await launchLSP(config.command, config.args ?? ["--stdio"], {
        cwd: root,
        env: config.env ? { ...process.env, ...config.env } : process.env
      });
      return { process: proc };
    }
  };
}
var EMPTY_CONFIG = {
  customServers: [],
  disabledServerIds: /* @__PURE__ */ new Set(),
  serverOverrides: /* @__PURE__ */ new Map()
};
var configInFlight = /* @__PURE__ */ new Map();
function normalizeWorkspacePath(cwd) {
  return path4.resolve(cwd);
}
function isSameOrChildPath(filePath, candidateRoot) {
  if (filePath === candidateRoot)
    return true;
  return filePath.startsWith(`${candidateRoot}${path4.sep}`);
}
function getConfigForFile(filePath) {
  const resolvedFilePath = path4.resolve(filePath);
  let bestMatch;
  for (const [root, config] of sessionRootConfigEntries()) {
    if (config === void 0)
      continue;
    if (!isSameOrChildPath(resolvedFilePath, root))
      continue;
    if (!bestMatch || root.length > bestMatch.root.length) {
      bestMatch = { root, config };
    }
  }
  return bestMatch?.config ?? EMPTY_CONFIG;
}
function registerLSPConfig(config) {
  const customServers = [];
  const disabledServerIds = new Set(config.disabledServers ?? []);
  if (config.servers) {
    for (const [id, serverConfig] of Object.entries(config.servers)) {
      try {
        const server = createCustomServer(serverConfig, id);
        customServers.push(server);
      } catch {
      }
    }
  }
  const serverOverrides = /* @__PURE__ */ new Map();
  if (config.serverOverrides) {
    for (const [id, entry] of Object.entries(config.serverOverrides)) {
      if (entry && typeof entry === "object" && !Array.isArray(entry)) {
        const initOpts = entry.initializationOptions;
        if (initOpts !== void 0 && typeof initOpts === "object" && initOpts !== null && !Array.isArray(initOpts)) {
          serverOverrides.set(id, {
            initializationOptions: initOpts
          });
        }
      }
    }
  }
  return { customServers, disabledServerIds, serverOverrides };
}
function forgetConfigResolvedClaims(evictedRoots) {
  for (const root of evictedRoots) {
    releasePhaseClaim(CONFIG_RESOLVED_PHASE, configResolutionKey(root));
  }
}
async function initLSPConfig(cwd) {
  const normalizedCwd = normalizeWorkspacePath(cwd);
  forgetConfigResolvedClaims(registerSessionRoot(normalizedCwd));
  const existing = configInFlight.get(normalizedCwd);
  if (existing)
    return existing;
  const promise = (async () => {
    const config = registerLSPConfig(await loadLSPConfig(cwd, os3.homedir()));
    forgetConfigResolvedClaims(setSessionRootConfig(normalizedCwd, config));
  })();
  configInFlight.set(normalizedCwd, promise);
  try {
    await promise;
  } finally {
    if (configInFlight.get(normalizedCwd) === promise) {
      configInFlight.delete(normalizedCwd);
    }
  }
}
function registeredServers(config) {
  return [...LSP_SERVERS, ...config.customServers];
}
function selectionReason(server, config, filePath, ext, base) {
  if (config.disabledServerIds.has(server.id))
    return "disabled-by-config";
  let matched = false;
  for (const value of server.extensions) {
    const lower = value.toLowerCase();
    if (lower === ext || lower === base) {
      matched = true;
      break;
    }
  }
  if (!matched)
    return "extension-mismatch";
  if (server.pathFilter && !server.pathFilter(filePath))
    return "path-filter";
  return "selected";
}
function explainServersForFile(filePath, config = getConfigForFile(filePath)) {
  const ext = path4.extname(filePath).toLowerCase();
  const base = path4.basename(filePath).toLowerCase();
  return registeredServers(config).map((server) => {
    const reason = selectionReason(server, config, filePath, ext, base);
    return { server, selected: reason === "selected", reason };
  });
}
function getServersForFileWithConfig(filePath) {
  const config = getConfigForFile(filePath);
  const ext = path4.extname(filePath).toLowerCase();
  const base = path4.basename(filePath).toLowerCase();
  return registeredServers(config).filter((server) => selectionReason(server, config, filePath, ext, base) === "selected");
}
function primaryServerEntry(filePath) {
  return getServersForFileWithConfig(filePath).find((s) => s.role !== "auxiliary");
}
function primaryServerId(filePath) {
  return primaryServerEntry(filePath)?.id;
}
async function resolveLspCwdForFile(filePath, sessionCwd, onRootFallback) {
  const primary = primaryServerEntry(filePath);
  if (!primary)
    return void 0;
  return resolveLspServerCwd(primary, filePath, sessionCwd, void 0, onRootFallback);
}
function getServerInitOverride(serverId, filePath) {
  return getConfigForFile(filePath).serverOverrides.get(serverId);
}

// dist/clients/lsp/wait-policy/strategies.js
import { createRequire } from "node:module";
var _require = createRequire(import.meta.url);
function astGrepNativePackageName(platform, arch) {
  switch (platform) {
    case "win32":
      if (arch === "x64")
        return "@ast-grep/cli-win32-x64-msvc";
      if (arch === "arm64")
        return "@ast-grep/cli-win32-arm64-msvc";
      if (arch === "ia32")
        return "@ast-grep/cli-win32-ia32-msvc";
      return void 0;
    case "darwin":
      if (arch === "arm64")
        return "@ast-grep/cli-darwin-arm64";
      if (arch === "x64")
        return "@ast-grep/cli-darwin-x64";
      return void 0;
    case "linux":
      if (arch === "x64")
        return "@ast-grep/cli-linux-x64-gnu";
      if (arch === "arm64")
        return "@ast-grep/cli-linux-arm64-gnu";
      return void 0;
    default:
      return void 0;
  }
}
function resolveAstGrepNativeExe(platform = process.platform, arch = process.arch) {
  const pkgName = astGrepNativePackageName(platform, arch);
  if (!pkgName)
    return void 0;
  const binaryName = platform === "win32" ? "ast-grep.exe" : "ast-grep";
  try {
    return _require.resolve(`${pkgName}/${binaryName}`);
  } catch {
    return void 0;
  }
}
var SERVER_DIAGNOSTIC_STRATEGIES = {
  typescript: {
    seedFirstPush: true,
    pullRetryBudgetMs: 0,
    debounceMs: 50,
    aggregateWaitMs: 1e3,
    expectSemanticSecondPush: false,
    // Tier 3 (#458): typescript-language-server publishes nothing on a
    // clean→clean edit (docs/lsp-capability-matrix.md, re-confirmed
    // 2026-07-12). It's the lone core-set tier-3 server, which is exactly
    // why the cascade lane's in-lane wait is worth skipping for it
    // specifically. Applies to the CLASSIC server only (#524/#529/#558)
    // — TS7's native `tsc --lsp --stdio` variant shares this
    // "typescript" server id but the 2026-07-12 dual-environment
    // re-measurement found it now publishes on clean (a drift from the
    // #541 measurement); wait-policy/classification.ts's classifier checks the live
    // snapshot's launchVariant and never applies this flag to a
    // native-ts7 instance.
    silentOnClean: true
  },
  "rust-analyzer": {
    seedFirstPush: false,
    pullRetryBudgetMs: 500,
    debounceMs: 150,
    aggregateWaitMs: 3e3,
    expectSemanticSecondPush: true
  },
  // PythonServer (pyright / basedpyright) — openFilesOnly mode: lazy per-file
  // analysis, startup similar to jedi. seedFirstPush: true because pyright's
  // first publishDiagnostics after didOpen is the complete result for that file.
  python: {
    seedFirstPush: true,
    pullRetryBudgetMs: 0,
    debounceMs: 100,
    aggregateWaitMs: 1500,
    expectSemanticSecondPush: false
  },
  // jedi-language-server is push-only (no pull diagnostics) and its FIRST
  // publishDiagnostics is the complete result (seedFirstPush). But that first
  // push lands just after didOpen+~1s on cold start (Python/parso import) —
  // measured ~1011ms — so a 1000ms aggregate budget misses it by a hair and
  // returns zero. 3000ms gives cold-start headroom without stalling the warm path.
  "python-jedi": {
    seedFirstPush: true,
    pullRetryBudgetMs: 0,
    debounceMs: 100,
    aggregateWaitMs: 3e3,
    expectSemanticSecondPush: false
  },
  eslint: {
    seedFirstPush: true,
    pullRetryBudgetMs: 0,
    debounceMs: 200,
    aggregateWaitMs: 2e3,
    expectSemanticSecondPush: false
  },
  // Opengrep security scanner (cross-language LSP). It pushes an EMPTY result
  // during the one-time rule-load window at startup, then the real scan after
  // `semgrep/rulesRefreshed` — so never seed the first push. Push-only (no pull
  // diagnostics). Warm per-file scan ~1.3s; the first touch in a session may
  // also pay rule-load (~3.5s cold). aggregateWaitMs is 3500: it's this
  // server's OWN deadline, bounded by the per-edit caller cap as a ceiling
  // (#242), so on a 2500ms-capped edit opengrep waits min(2500, 3500)=2500
  // and on uncapped paths it gets its full 3500. 3500 covers warm and most
  // cold; a cold scan that overruns isn't lost — late diagnostics are cached
  // and surface on the next unchanged-content read through the content-bound
  // auxiliary carry-over path.
  opengrep: {
    seedFirstPush: false,
    pullRetryBudgetMs: 0,
    debounceMs: 250,
    aggregateWaitMs: 3500,
    expectSemanticSecondPush: false,
    // Opengrep re-scans only on a fresh didOpen — didChange is a no-op for it.
    reopenOnResync: true,
    rescansOnSave: true
  },
  // ast-grep structural linter (sgconfig-gated auxiliary LSP). Push-only,
  // compiles the project rules on the first scan of a session, and — like
  // Opengrep — is re-synced via didClose+didOpen so edits trigger a re-scan.
  // Conservative budget until measured against real projects (#239).
  // ast-grep re-scans on didChange (verified: toggling the violation count
  // 3→1→4→2 returns the correct fresh count each touch, with matching doc
  // versions), so reopen isn't needed and didChange is the lighter path.
  // aggregateWaitMs is 1800: its warm scan is ~1.3s, so 1000 was under-budgeted
  // (only masked before because the old with-auxiliary deadline was a global
  // max() floor; now each server has its own caller-cap-bounded deadline, #242,
  // so the budget must actually cover the scan).
  "ast-grep": {
    seedFirstPush: false,
    pullRetryBudgetMs: 0,
    debounceMs: 150,
    aggregateWaitMs: 1800,
    expectSemanticSecondPush: false,
    reopenOnResync: false
  },
  // zizmor (GitHub Actions security scanner, auxiliary LSP #272). Push-only
  // (no pull diagnostics). Its audit set is compiled-in — there's no rule-load
  // window like Opengrep — so the FIRST publishDiagnostics after didOpen IS the
  // complete result: seedFirstPush. It re-scans on didChange (FULL sync), so no
  // reopen-on-resync. A native single-workflow audit is sub-second offline;
  // online mode may add a GitHub-API round-trip, so 2000ms gives headroom
  // (bounded by the per-edit caller cap as a ceiling, #242), and any late online
  // finding can surface on the next unchanged-content read through the
  // content-bound auxiliary carry-over path.
  zizmor: {
    seedFirstPush: true,
    pullRetryBudgetMs: 0,
    debounceMs: 150,
    aggregateWaitMs: 2e3,
    expectSemanticSecondPush: false,
    reopenOnResync: false,
    // #3548: zizmor@main `crates/zizmor/src/lsp.rs` `initialized()`
    // registers `textDocument/didSave` DYNAMICALLY (`include_text: true`),
    // and `did_save` re-audits and republishes with `version: None` — the
    // same surplus shape as opengrep's static save rescan (#3482). This is
    // presently INERT: `applyDynamicCapabilities` (client.ts) does not map
    // a dynamic didSave registration to anything, and `negotiateSaveOptions`
    // (sync-kind.ts) reads only the static `initialize` reply, so
    // `state.saveOptions` never gets set for zizmor and `sendDidSave` never
    // fires (its `if (!save) return;` guard). Set now anyway, measured
    // fact style (like `silentOnClean`'s launch-variant scoping above): the
    // day dynamic registration is honoured, this is the correct value with
    // no further plumbing, and `expectSaveRescan`/`rescansOnSave`'s
    // counting logic does not care how `saveOptions` got set — proven by
    // `ZIZMOR-SAVE-RESCAN-EXPECTED` in
    // tests/clients/lsp/late-auxiliary-findings.test.ts, which sets
    // `state.saveOptions` directly (the same shape as the pre-existing
    // `SAVE-RESCAN-EXPECTED` opengrep test) rather than through the
    // unbuilt dynamic-capability path.
    rescansOnSave: true
  },
  // typos (source-code spell checker, auxiliary LSP #283). Push-only (no pull
  // diagnostics). Its dictionary is compiled in — there's NO rule-load window
  // like Opengrep — so the FIRST publishDiagnostics after didOpen IS the
  // complete result: seedFirstPush. It re-scans on didChange (FULL sync), so no
  // reopen-on-resync. A native single-file spell scan is sub-100ms, so the seed
  // arrives near-instantly; aggregateWaitMs is a generous ceiling (bounded by
  // the per-edit caller cap, #242) that the early seed resolves well under.
  typos: {
    seedFirstPush: true,
    pullRetryBudgetMs: 0,
    debounceMs: 150,
    aggregateWaitMs: 1500,
    expectSemanticSecondPush: false,
    reopenOnResync: false,
    // #3548: see `publishesOnClose`'s doc comment above — typos'
    // `did_close` always publishes an empty, version-less set.
    publishesOnClose: true
  },
  // marksman (Markdown LSP, #274). Push-based; native binary so the per-file
  // parse is fast, but its value is CROSS-file (broken intra-repo links,
  // missing anchors/heading refs) which needs the workspace index — so the
  // first push after didOpen can be empty before indexing completes. Don't
  // seed it (like opengrep/rust-analyzer); a modest 1500ms aggregate covers
  // warm edits, and any late cross-file finding surfaces on the next touch.
  marksman: {
    seedFirstPush: false,
    pullRetryBudgetMs: 0,
    debounceMs: 150,
    aggregateWaitMs: 1500,
    expectSemanticSecondPush: false,
    // #645: a full-tree sweep opens every markdown file, all racing the
    // SAME one-time workspace index build — pay the 1500ms budget once
    // per sweep (the first markdown file touched), not once per file.
    // 250ms covers a warm per-file publish (native binary, fast parse)
    // once the index has already had a full aggregateWaitMs window.
    workspaceIndexing: true,
    workspaceIndexingWarmWaitMs: 250,
    // #799 / run 35914033696: marksman is push-only and publishes NOTHING on
    // a clean transition — there is no pull fallback and no sync-confirm protocol (unlike
    // typescript's tsserver commands), so a clean markdown file's touch
    // waits its full budget with zero signal either way. Marking it
    // `silentOnClean` lets the generic push-only clean-confirm gate
    // (`touchFile`, the `classifyCascadeWaitTier`-driven fallback next to
    // the tsserver sync-confirm block) treat "no publish within budget,
    // notify succeeded" as CONFIRMED clean instead of inconclusive/timed
    // out — closing the observed 2x20s-per-sweep burn on clean markdown
    // (evidence: agents.md timed out at 20009ms/20014ms on both the
    // warm-up attempt and its retry, with zero diagnostics either time).
    silentOnClean: true
  },
  // cue lsp serve (#1522, #1519, #1520). Measured directly against the real
  // v0.17.1 binary (`getWorkspaceDiagnosticsSupport` reports
  // `mode: "push-only"`; no pull support advertised).
  //
  // It reports load/parse (syntax) errors as you type, but leaves
  // conflicting-value/constraint-failure (evaluation) errors to `cue vet`
  // — the #1522 cue-vet auxiliary runner covers that gap. So the syntax
  // diagnostics this strategy governs are a REAL but PARTIAL signal, not
  // full CUE validation.
  //
  // The repaired clean-signal probe measured cue as publishing a versioned
  // clean-transition set (run 35914033696), so this marker stays absent:
  // the cascade must retain its normal early-publish path.
  //
  // seedFirstPush: true — the one cold-open case that DOES publish (a
  // file that is already broken) sends the complete single diagnostic on
  // its first push; there is no observed partial-then-complete sequence to
  // wait out.
  //
  // aggregateWaitMs: 2000 covers the measured warm edit-to-publish latency
  // (~930ms, syntax reparse) with margin; a cold spawn's first push (~2.1s
  // end-to-end including process start) is bounded separately by
  // maxClientWaitMs, not this per-diagnostics budget.
  // intelephense (php, #3310). Measured directly against the real v1.18.5
  // binary over a raw JSON-RPC session answering `workspace/configuration`
  // the way pi-lens does (2026-09-23, linux):
  //
  //   +284ms  initialize response — NO diagnosticProvider (push-only)
  //   +295ms  publishDiagnostics diags=0  version=undefined   <- pre-index
  //   +301ms  indexingStarted
  //   +689ms  indexingEnded
  //   +696ms  publishDiagnostics diags=2  version=undefined   <- the answer
  //
  // so `emptyFirstPublish: "indexing"`: the first publish is an artifact of
  // the cold index, not an answer. The same session on a genuinely CLEAN
  // file publishes `[]` at +304ms and `[]` AGAIN at +663ms (right after
  // `indexingEnded`), which is what lets the held publish be released by
  // the server's own second publish instead of by a timer — and a WARM
  // touch (a second file, index already built) publishes the real set
  // FIRST at +5ms, so the hold costs nothing once the session is warm.
  //
  // seedFirstPush: false — the measurement above is the direct refutation
  // of "the first push is complete" for this server.
  // pullRetryBudgetMs: 0 — no pull provider is advertised at all.
  // aggregateWaitMs: 8000 — the budget must cover the cold index window,
  // which is what the second (real) publish waits on: ~0.4s for a 139-file
  // index on the dev box, ~5s on the nightly ubuntu runner (#3217's
  // transcript). Per-edit callers cap this as a CEILING (#242), so an
  // interactive edit still waits only its own cap; the uncapped paths (the
  // tool-smoke LSP gate, which itself caps at 8000ms) get the full budget,
  // and the cold index is paid once per session, not once per touch.
  php: {
    seedFirstPush: false,
    pullRetryBudgetMs: 0,
    debounceMs: 150,
    aggregateWaitMs: 8e3,
    expectSemanticSecondPush: false,
    emptyFirstPublish: "indexing",
    // #3484: the fence reply (+2..4 ms) precedes the publish (~+1 s).
    diagnosticsFence: "reply-first"
  },
  // terraform-ls (#3310). The nightly tool-smoke measurement was identical
  // on 2026-09-27, 2026-09-28, and 2026-09-29:
  //
  //   [terraform] clean-behavior=publishes-unversioned (tier 2*)
  //   dirtyPubs=2(v:0) cleanPubs=2(v:0) first-publish=empty-first
  //   provisional pre-index publish
  //
  // The first empty, unversioned publish is the cold-index artifact, so hold
  // it once and let terraform-ls's next publish resolve the wait. A held
  // clean-only trace therefore reaches the caller as unanswered/indeterminate
  // at the wait ceiling rather than a false clean, by design (#3310). The
  // marker is measured policy, not a blanket rule for Tier 2* push servers.
  terraform: {
    seedFirstPush: false,
    pullRetryBudgetMs: 0,
    debounceMs: 150,
    aggregateWaitMs: 1500,
    expectSemanticSecondPush: false,
    emptyFirstPublish: "indexing"
  },
  // yaml-language-server (#3484): the default strategy plus the measured
  // fence order — reply +3..6 ms, publish +207..242 ms after didChange.
  yaml: {
    seedFirstPush: false,
    pullRetryBudgetMs: 250,
    debounceMs: 150,
    aggregateWaitMs: 1500,
    expectSemanticSecondPush: false,
    diagnosticsFence: "reply-first"
  },
  // Svelte's pull diagnostics settle after the default 1500ms budget on a
  // cold server. The tool-smoke gate caps waits at 8000ms, so give this
  // server enough aggregate budget to return its seeded findings (#3311).
  svelte: {
    seedFirstPush: false,
    pullRetryBudgetMs: 0,
    debounceMs: 150,
    aggregateWaitMs: 4e3,
    expectSemanticSecondPush: false
  },
  // csharp-ls loads (design-time-builds) the restored project AFTER
  // `initialize` returns, so the publish for a seeded CS0029 lands inside
  // `waitForDiagnostics`, not inside the client wait. MEASURED A/B over two
  // nightly Tool-smoke runs of the same fixture with `dotnet restore` already
  // done — the ONLY difference between them was this budget:
  //   * `aggregateWaitMs` 1500 (the default): run 36054901266 →
  //     `[csharp] touched=0`, 4.05s after `Restored …/toolsmoke.csproj`.
  //     A real compiler error read as clean.
  //   * 8000: run 36058292424 → `[csharp] touched=1`, 6.49s after the same
  //     line; gate run 36059988117 → `lsp_diagnostics returned 1 primary
  //     finding`, 4.86s after it.
  // Both windows INCLUDE workspace bootstrap + spawn + initialize, so they
  // bound the wait from above: the required budget is in (1500, 4860] ms.
  // 6000 covers the measured gate window with margin and stays under the
  // tool-smoke gate's own 8000ms ceiling (that relation is pinned by
  // tests/config/lsp-gate-population.test.ts), so the gate can still witness
  // this budget. CONFIRMED at 6000 by run 36064829436: `✓ csharp csharp-ls 1
  // lsp_diagnostics returned 1 primary finding`, 4.78s after `Restored
  // …csproj`, census unchanged at gated 31 / handshake-only 8 / unavailable 6
  // — so 6000 is measured as sufficient, not merely inferred from the 8000
  // runs above. It is deliberately NOT the ceiling: an `lsp_diagnostics` call
  // that passes no `waitMs` pays this budget in full on a file the server never
  // publishes for (`tools/lsp-diagnostics.ts` leaves `maxClientWaitMs`
  // undefined → `perServerTimeout` has no caller cap), so every 1000ms here
  // is 1000ms of turn latency on the no-publication path (#3402 review r2).
  // Every other field is DEFAULT_STRATEGY's value on purpose: csharp-ls is
  // `mode=pull`/tier-1 authoritative-clean (#3311 investigator table), and
  // nothing has measured its pull retry, so this entry moves the one field
  // that was measured and no other (pinned in
  // tests/clients/lsp/server-strategies.test.ts).
  // fsharp/expert/vue deliberately have NO entry: the same two runs show
  // `touched=0` at BOTH 1500 and 8000, so the budget is not what stops them
  // publishing and an 8000 entry would buy nothing while costing every
  // uncapped production call 6.5 extra seconds. Their fixture rows carry the
  // observed-behavior exemption instead (`scripts/smoke-tools.mjs`), and a
  // probe that wants a long window sets `PI_LENS_LSP_DIAGNOSTICS_MAX_WAIT_MS`
  // — a flat harness override that needs no production budget.
  csharp: {
    seedFirstPush: false,
    pullRetryBudgetMs: 250,
    debounceMs: 150,
    aggregateWaitMs: 6e3,
    expectSemanticSecondPush: false
  },
  cue: {
    seedFirstPush: true,
    pullRetryBudgetMs: 0,
    debounceMs: 150,
    aggregateWaitMs: 2e3,
    expectSemanticSecondPush: false,
    reopenOnResync: false
  },
  // lua-language-server is push-only and was silent on clean transitions in
  // the repaired probe (run 35914033696). Keep the marker on the measured
  // server id so the cascade can skip its in-lane wait safely.
  lua: {
    seedFirstPush: true,
    pullRetryBudgetMs: 0,
    debounceMs: 150,
    aggregateWaitMs: 2e3,
    expectSemanticSecondPush: false,
    silentOnClean: true
  }
};
var NATIVE_TS7_DIAGNOSTIC_STRATEGY = {
  ...SERVER_DIAGNOSTIC_STRATEGIES.typescript,
  seedFirstPush: false,
  // Native TS7 demonstrably publishes on clean, so the classic-only marker
  // must not leak through the shared server id.
  silentOnClean: false
};
var DEFAULT_STRATEGY = {
  seedFirstPush: false,
  pullRetryBudgetMs: 250,
  debounceMs: 150,
  aggregateWaitMs: 1500,
  expectSemanticSecondPush: false
};
function getStrategy(serverId, launchVariant) {
  if (serverId === "typescript" && launchVariant === "native-ts7") {
    return NATIVE_TS7_DIAGNOSTIC_STRATEGY;
  }
  return SERVER_DIAGNOSTIC_STRATEGIES[serverId] ?? DEFAULT_STRATEGY;
}

// dist/clients/lsp/wait-policy/classification.js
function classifyServerWaitTier(serverId, snapshot) {
  if (!snapshot)
    return "waits";
  const mode = snapshot.workspaceDiagnosticsSupport?.mode;
  if (mode === "pull")
    return "pull-capable";
  if (snapshot.customServer === true && mode === "push-only" && snapshot.diagnosticsUnsupported === true && snapshot.diagnosticsPublished !== true) {
    return "diagnostics-unsupported";
  }
  if (mode !== "push-only")
    return "waits";
  const strategy = getStrategy(serverId, snapshot.launchVariant);
  if (strategy.silentOnClean !== true)
    return "waits";
  if (snapshot.launchVariant === "native-ts7")
    return "waits";
  return "tier3-silent";
}
function resolvePrimaryServerForWaitPolicy(filePath, snapshots) {
  const servers = getServersForFileWithConfig(filePath).filter((s) => s.role !== "auxiliary");
  const primary = servers[0];
  if (!primary)
    return void 0;
  return {
    serverId: primary.id,
    snapshot: snapshots.find((s) => s.serverId === primary.id)
  };
}
function classifyCascadeWaitTier(lspService, filePath, snapshots) {
  void lspService;
  const primary = resolvePrimaryServerForWaitPolicy(filePath, snapshots);
  if (!primary)
    return "waits";
  return classifyServerWaitTier(primary.serverId, primary.snapshot);
}

// dist/clients/lsp/lombok.js
import * as fs4 from "node:fs";
import * as os4 from "node:os";
import * as path5 from "node:path";
var LOMBOK_ENV_KEYS = ["PI_LENS_LOMBOK_JAR", "LOMBOK_JAR"];
var LOMBOK_PROJECT_FILES = [
  "lombok.config",
  "pom.xml",
  "build.gradle",
  "build.gradle.kts",
  path5.join("gradle", "libs.versions.toml")
];
function fileExists(filePath) {
  if (!filePath)
    return false;
  try {
    return fs4.statSync(filePath).isFile();
  } catch {
    return false;
  }
}
function readTextIfSmall(filePath, maxBytes = 512 * 1024) {
  try {
    const stat2 = fs4.statSync(filePath);
    if (!stat2.isFile() || stat2.size > maxBytes)
      return "";
    return fs4.readFileSync(filePath, "utf-8");
  } catch {
    return "";
  }
}
function containsLombokDependency(text) {
  return /\borg\.projectlombok\b/i.test(text) || /<artifactId>\s*lombok\s*<\/artifactId>/i.test(text) || /\b(annotationProcessor|compileOnly|testCompileOnly|testAnnotationProcessor)\b[^\n]*\blombok\b/i.test(text);
}
function hasLombokProject(root) {
  const lombokConfig = path5.join(root, "lombok.config");
  if (fileExists(lombokConfig))
    return true;
  for (const name of LOMBOK_PROJECT_FILES) {
    if (name === "lombok.config")
      continue;
    const text = readTextIfSmall(path5.join(root, name));
    if (text && containsLombokDependency(text))
      return true;
  }
  return false;
}
function envJarCandidate(env) {
  for (const key of LOMBOK_ENV_KEYS) {
    const value = env[key]?.trim();
    if (fileExists(value))
      return path5.resolve(value);
  }
  return void 0;
}
function localJarCandidates(root) {
  return [
    path5.join(root, "lombok.jar"),
    path5.join(root, ".lombok", "lombok.jar"),
    path5.join(root, "lib", "lombok.jar"),
    path5.join(root, "libs", "lombok.jar")
  ];
}
function newestExistingJar(candidates) {
  let best;
  for (const candidate of candidates) {
    try {
      const stat2 = fs4.statSync(candidate);
      if (!stat2.isFile())
        continue;
      if (!best || stat2.mtimeMs > best.mtimeMs) {
        best = { filePath: candidate, mtimeMs: stat2.mtimeMs };
      }
    } catch {
    }
  }
  return best?.filePath;
}
function mavenLocalLombokJar(home = os4.homedir()) {
  const base = path5.join(home, ".m2", "repository", "org", "projectlombok", "lombok");
  let versions = [];
  try {
    versions = fs4.readdirSync(base);
  } catch {
    return void 0;
  }
  return newestExistingJar(versions.map((version) => path5.join(base, version, `lombok-${version}.jar`)));
}
function gradleCacheLombokJar(home = os4.homedir()) {
  const base = path5.join(home, ".gradle", "caches", "modules-2", "files-2.1", "org.projectlombok", "lombok");
  const candidates = [];
  let versions = [];
  try {
    versions = fs4.readdirSync(base);
  } catch {
    return void 0;
  }
  for (const version of versions) {
    const versionDir = path5.join(base, version);
    let hashes = [];
    try {
      hashes = fs4.readdirSync(versionDir);
    } catch {
      continue;
    }
    for (const hash of hashes) {
      candidates.push(path5.join(versionDir, hash, `lombok-${version}.jar`));
    }
  }
  return newestExistingJar(candidates);
}
function resolveLombokJar(root, env = process.env) {
  const explicit = envJarCandidate(env);
  if (explicit)
    return explicit;
  return newestExistingJar(localJarCandidates(root)) ?? mavenLocalLombokJar() ?? gradleCacheLombokJar();
}
function hasLombokJavaAgent(jvmArgs) {
  return /(^|\s)-javaagent:(?:"[^"]*lombok[^"]*\.jar"|\S*lombok\S*\.jar)/i.test(jvmArgs ?? "");
}
function resolveLombokJavaAgentArg(root, baseEnv = process.env) {
  if (baseEnv.PI_LENS_JAVA_LOMBOK === "0")
    return void 0;
  if (hasLombokJavaAgent(baseEnv.JDTLS_JVM_ARGS))
    return void 0;
  const explicitJar = envJarCandidate(baseEnv);
  if (!explicitJar && !hasLombokProject(root))
    return void 0;
  const jar = explicitJar ?? resolveLombokJar(root, baseEnv);
  return jar ? `-javaagent:${jar}` : void 0;
}
function createLombokJdtlsArgs(root, baseEnv = process.env) {
  const javaAgent = resolveLombokJavaAgentArg(root, baseEnv);
  return javaAgent ? [`--jvm-arg=${javaAgent}`] : [];
}

// dist/clients/lsp/jvm-runtime.js
import { existsSync as existsSync2, readdirSync as readdirSync4 } from "node:fs";
import os5 from "node:os";
import path6 from "node:path";
var JAVA_EXE = process.platform === "win32" ? "java.exe" : "java";
var JAVA_PROBE_TIMEOUT_MS = 5e3;
var MIN_JAVA_MAJOR = 17;
function jdkHomeFrom(dir) {
  if (existsSync2(path6.join(dir, "bin", JAVA_EXE)))
    return dir;
  const macHome = path6.join(dir, "Contents", "Home");
  if (existsSync2(path6.join(macHome, "bin", JAVA_EXE)))
    return macHome;
  return void 0;
}
function parseMajorVersion(name) {
  const legacy = name.match(/(?<!\d)1\.(\d+)/);
  if (legacy)
    return Number.parseInt(legacy[1], 10);
  const modern = name.match(/(?<!\d)(\d{1,2})/);
  return modern ? Number.parseInt(modern[1], 10) : 0;
}
function childDirs(base) {
  try {
    return readdirSync4(base, { withFileTypes: true }).filter((e) => e.isDirectory() || e.isSymbolicLink()).map((e) => path6.join(base, e.name));
  } catch {
    return [];
  }
}
function candidateRoots() {
  const home = os5.homedir();
  const roots = [];
  roots.push(path6.join(home, ".jdks"));
  roots.push(path6.join(getGlobalPiLensDir(), "tools"));
  if (process.platform === "win32") {
    const progFiles = [
      process.env.ProgramFiles,
      process.env["ProgramFiles(x86)"],
      process.env.ProgramW6432,
      path6.join(process.env.LOCALAPPDATA ?? path6.join(home, "AppData", "Local"), "Programs")
    ].filter((p) => Boolean(p));
    for (const base of progFiles) {
      roots.push(path6.join(base, "Eclipse Adoptium"));
      roots.push(path6.join(base, "Java"));
      roots.push(path6.join(base, "Microsoft"));
      roots.push(path6.join(base, "Zulu"));
      roots.push(path6.join(base, "Amazon Corretto"));
      roots.push(path6.join(base, "BellSoft"));
    }
  } else if (process.platform === "darwin") {
    roots.push("/Library/Java/JavaVirtualMachines");
    roots.push(path6.join(home, "Library", "Java", "JavaVirtualMachines"));
    roots.push("/opt/homebrew/opt");
    roots.push("/usr/local/opt");
  } else {
    roots.push("/usr/lib/jvm");
    roots.push("/usr/java");
    roots.push("/opt/java");
  }
  return roots;
}
function discoverJdkHome(roots = candidateRoots(), javaHome = process.env.JAVA_HOME) {
  const found = [];
  if (javaHome) {
    const resolved = jdkHomeFrom(javaHome);
    if (resolved)
      found.push({ home: resolved, major: Number.MAX_SAFE_INTEGER });
  }
  for (const root of roots) {
    for (const dir of childDirs(root)) {
      const resolved = jdkHomeFrom(dir);
      if (!resolved)
        continue;
      found.push({
        home: resolved,
        major: parseMajorVersion(path6.basename(dir))
      });
    }
  }
  const usable = found.filter((c) => c.major >= MIN_JAVA_MAJOR);
  if (usable.length === 0)
    return void 0;
  usable.sort((a, b) => b.major - a.major);
  return usable[0].home;
}
var _cachedEnv;
var javaAvailabilityLatch = createAvailabilityLatch();
var inFlightJavaProbe = null;
function probeJavaOnPath() {
  const memo = javaAvailabilityLatch.read();
  if (memo !== null) {
    if (javaAvailabilityLatch.getOutcome() === "transient") {
      logAvailabilityDecision({
        tool: "java",
        verdict: "unavailable",
        outcome: "transient",
        cause: javaAvailabilityLatch.getCause() ?? "probe-timeout",
        elapsedMs: 0,
        latched: false,
        retryAfterMs: Math.max(0, javaAvailabilityLatch.getRetryAtMs() - Date.now()),
        budgetMs: JAVA_PROBE_TIMEOUT_MS,
        // No probe ran here: the latch's own remembered cause is replayed
        // as-is, so the call site is the one asserting it (#2209).
        classifiedBy: "caller"
      });
      return Promise.resolve(void 0);
    }
    return Promise.resolve(memo);
  }
  if (inFlightJavaProbe)
    return inFlightJavaProbe;
  inFlightJavaProbe = runJavaProbe().finally(() => {
    inFlightJavaProbe = null;
  });
  return inFlightJavaProbe;
}
async function runJavaProbe() {
  const finder = process.platform === "win32" ? "where" : "which";
  const startedAt = Date.now();
  const sampler = startHostStallSampler();
  let result;
  let hostStallMs;
  try {
    result = await safeSpawnAsync(finder, ["java"], {
      timeout: JAVA_PROBE_TIMEOUT_MS
    });
  } finally {
    hostStallMs = sampler.stop();
  }
  const elapsedMs = Date.now() - startedAt;
  if (result.status === 0 && !result.error) {
    javaAvailabilityLatch.noteAvailable();
    logAvailabilityDecision({
      tool: "java",
      verdict: "available",
      outcome: "success",
      cause: "ok",
      elapsedMs,
      latched: true,
      hostStallMs,
      budgetMs: JAVA_PROBE_TIMEOUT_MS,
      classifiedBy: "probe"
    });
    return true;
  }
  const { outcome, cause } = classifyProbeFailure(result, {
    hostStallMs,
    unclassifiedFailureOutcome: "missing"
  });
  const retryAfterMs = javaAvailabilityLatch.noteUnavailable(outcome, cause);
  logAvailabilityDecision({
    tool: "java",
    verdict: "unavailable",
    outcome,
    cause,
    elapsedMs,
    latched: outcome !== "transient",
    hostStallMs,
    ...retryAfterMs > 0 && { retryAfterMs },
    budgetMs: JAVA_PROBE_TIMEOUT_MS,
    classifiedBy: "probe"
  });
  return outcome === "transient" ? void 0 : false;
}
async function resolveJavaRuntimeEnv() {
  if (_cachedEnv)
    return _cachedEnv.value;
  const javaOnPath = await probeJavaOnPath();
  if (javaOnPath === true) {
    _cachedEnv = { value: void 0 };
    return void 0;
  }
  const home = discoverJdkHome();
  if (home) {
    const binDir = path6.join(home, "bin");
    const currentPath = process.env.PATH ?? process.env.Path ?? "";
    _cachedEnv = {
      value: {
        JAVA_HOME: home,
        PATH: binDir + path6.delimiter + currentPath
      }
    };
    return _cachedEnv.value;
  }
  if (javaOnPath === false) {
    _cachedEnv = { value: void 0 };
  }
  return void 0;
}

// dist/clients/lsp/server-covers.js
var BUILTIN_SERVER_RUNNER_COVERS = /* @__PURE__ */ new Map([
  [
    "bash",
    { runnerIds: ["shellcheck"], gateCommands: ["bash-language-server"] }
  ],
  ["shuck", { runnerIds: ["shellcheck"], gateCommands: ["shuck"] }],
  ["toml", { runnerIds: ["taplo"], gateCommands: ["taplo"] }]
]);

// dist/clients/lsp/server.js
function withRootMarkers(root, markers) {
  root.rootMarkers = markers;
  return root;
}
function describeRootFallback(fallback) {
  const tail = "so pi-lens cannot confirm the server analysed the file";
  if (fallback.cause === "failed") {
    return `${fallback.serverName}: resolving the project root for this file failed, ${tail}`;
  }
  if (fallback.cause === "unselected") {
    return `${fallback.serverName}: found ${fallback.marker} for this file but pi-lens did not select it as the project root (a test-fixture or git-ignored directory is never used as one), ${tail}`;
  }
  return `${fallback.serverName}: no project root found for this file (looked for ${fallback.rootMarkers.join(" / ")}); the server was started at the file's directory and may not have analysed it`;
}
async function resolveLspServerCwd(server, filePath, sessionCwd, onRootFailure, onRootFallback) {
  const stagingRoot = ephemeralStagingRoot(filePath);
  if (stagingRoot) {
    recordDegradationOnce({
      kind: "lsp-root-declined",
      subject: stagingRoot,
      reason: "LSP root declined for host pi-agent staging directory"
    });
    return void 0;
  }
  const rootMarkers = server.rootMarkers ?? server.root.rootMarkers;
  let serverRoot;
  let rootFailed = false;
  try {
    serverRoot = await server.root(filePath);
  } catch (error) {
    const reason = error instanceof Error ? error.message : String(error);
    rootFailed = true;
    onRootFailure?.(reason);
  }
  const isFileDirFallback = serverRoot !== void 0 && rootMarkers?.length && path7.resolve(serverRoot) === path7.resolve(path7.dirname(filePath));
  if (!serverRoot || isFileDirFallback) {
    const probedMarker = onRootFallback && !rootFailed && rootMarkers?.length ? resolveToolCwd("lsp", server.id, filePath, {
      cwd: sessionCwd,
      rootMarkers,
      suppressTelemetry: true
    }).marker : void 0;
    const refusedMarker = probedMarker !== void 0 && rootMarkers?.includes(probedMarker) ? probedMarker : void 0;
    const fallbackCwd = rootMarkers?.length ? resolveToolCwd("lsp", server.id, filePath, {
      cwd: path7.dirname(path7.resolve(filePath)),
      rootMarkers
    }) : void 0;
    if (!serverRoot) {
      recordDegradationOnce({
        kind: "tool-cwd-resolution",
        subject: server.id,
        reason: `lsp:server-root-${rootFailed ? "failed" : "fallback"}:${filePath}`
      });
      onRootFallback?.({
        serverId: server.id,
        serverName: server.name ?? server.id,
        rootMarkers: rootMarkers ?? [],
        requiresProjectRoot: server.requiresProjectRoot === true,
        cause: rootFailed ? "failed" : refusedMarker !== void 0 ? "unselected" : "none",
        ...refusedMarker !== void 0 && { marker: refusedMarker }
      });
    }
    return fallbackCwd?.cwd;
  }
  const boundedServerRoot = enforceLspRootCeiling(serverRoot, sessionCwd, filePath);
  return resolveToolCwd("lsp", server.id, filePath, {
    cwd: sessionCwd,
    rootMarkers,
    serverRoot: boundedServerRoot
  }).cwd;
}
var FIXTURE_ROOT_SEGMENTS = /* @__PURE__ */ new Set(["__fixtures__", "testdata"]);
var FALLBACK_PROJECT_MARKERS = [
  ".git",
  "package.json",
  "go.work",
  "go.mod",
  "Cargo.toml",
  "pyproject.toml",
  "pom.xml",
  "build.gradle",
  "build.gradle.kts",
  "mix.exs",
  "pubspec.yaml",
  "Package.swift"
];
var PROJECT_BOUNDARY_MARKERS = [
  "package.json",
  "package-lock.json",
  "pnpm-lock.yaml",
  "yarn.lock",
  "bun.lock",
  "bun.lockb",
  "go.work",
  "go.mod",
  "Cargo.toml",
  "Cargo.lock",
  "pyproject.toml",
  "uv.lock",
  "poetry.lock",
  "Pipfile",
  "Pipfile.lock",
  "pom.xml",
  "build.gradle",
  "build.gradle.kts",
  "settings.gradle",
  "settings.gradle.kts",
  "mix.exs",
  "pubspec.yaml",
  "Package.swift"
];
var loggedRootCeilingClamps = /* @__PURE__ */ new Set();
var posixCaseInsensitiveByPath = /* @__PURE__ */ new Map();
function posixFilesystemIsCaseInsensitive(root) {
  const resolved = path7.resolve(root);
  const cached = posixCaseInsensitiveByPath.get(resolved);
  if (cached !== void 0)
    return cached;
  if (!existsSync3(resolved)) {
    return false;
  }
  const name = path7.basename(resolved);
  const alternate = name.toLowerCase() === name ? name.toUpperCase() : name.toLowerCase();
  let insensitive = false;
  const alternatePath = path7.join(path7.dirname(resolved), alternate);
  if (alternate !== name && existsSync3(alternatePath)) {
    try {
      const actual = statSync3(resolved);
      const alternateStat = statSync3(alternatePath);
      insensitive = actual.dev === alternateStat.dev && actual.ino === alternateStat.ino;
    } catch {
      insensitive = false;
    }
  }
  posixCaseInsensitiveByPath.set(resolved, insensitive);
  return insensitive;
}
function resetLSPCaseSensitivityState() {
  posixCaseInsensitiveByPath.clear();
}
function isSameOrWithin(ancestor, candidate, deps = {}) {
  const windowsShaped = isWindowsPath(ancestor) || isWindowsPath(candidate);
  const pathApi = windowsShaped ? path7.win32 : path7;
  const resolvedAncestor = pathApi.resolve(ancestor);
  const resolvedCandidate = pathApi.resolve(candidate);
  let caseInsensitive = false;
  if (!windowsShaped) {
    if (deps.caseInsensitiveProbe) {
      const cached = posixCaseInsensitiveByPath.get(resolvedAncestor);
      caseInsensitive = cached ?? deps.caseInsensitiveProbe(resolvedAncestor);
      if (cached === void 0) {
        posixCaseInsensitiveByPath.set(resolvedAncestor, caseInsensitive);
      }
    } else {
      caseInsensitive = posixFilesystemIsCaseInsensitive(resolvedAncestor);
    }
  }
  const relative2 = pathApi.relative(caseInsensitive ? resolvedAncestor.toLowerCase() : resolvedAncestor, caseInsensitive ? resolvedCandidate.toLowerCase() : resolvedCandidate);
  return relative2 === "" || !relative2.startsWith("..") && !pathApi.isAbsolute(relative2);
}
function enforceLspRootCeiling(root, sessionCwd, filePath) {
  const windowsShaped = isWindowsPath(root) || isWindowsPath(sessionCwd);
  const pathApi = windowsShaped ? path7.win32 : path7;
  const resolvedRoot = pathApi.resolve(root);
  const resolvedCwd = pathApi.resolve(sessionCwd);
  if (filePath && !isSameOrWithin(resolvedCwd, pathApi.resolve(filePath))) {
    return resolvedRoot;
  }
  if (isSameOrWithin(resolvedCwd, resolvedRoot))
    return resolvedRoot;
  const logKey = `${normalizeMapKey(resolvedCwd)}:${normalizeMapKey(resolvedRoot)}`;
  if (!loggedRootCeilingClamps.has(logKey)) {
    loggedRootCeilingClamps.add(logKey);
    logSessionStart(`lsp root clamped to session cwd: candidate=${resolvedRoot} cwd=${resolvedCwd}`);
  }
  return resolvedCwd;
}
async function hasProjectBoundaryMarker(dir) {
  if (await markerExists(dir, ".git"))
    return true;
  for (const marker of PROJECT_BOUNDARY_MARKERS) {
    if (await markerExists(dir, marker))
      return true;
  }
  return false;
}
function pathSegments(dir) {
  const parsed = path7.parse(path7.resolve(dir));
  return path7.relative(parsed.root, path7.resolve(dir)).split(path7.sep).filter(Boolean).map((segment) => segment.toLowerCase());
}
function hasFixtureConvention(dir) {
  const segments = pathSegments(dir);
  if (segments.some((segment) => FIXTURE_ROOT_SEGMENTS.has(segment)))
    return true;
  return segments.some((segment, index) => segment === "tests" && segments[index + 1] === "fixtures");
}
function hasAtomicStageSegment(dir) {
  return pathSegments(dir).some((segment) => STAGE_TMP_PATTERN.test(segment));
}
async function findGitBoundary(dir) {
  let current = path7.resolve(dir);
  const fsRoot = path7.parse(current).root;
  while (true) {
    if (await markerExists(current, ".git"))
      return current;
    if (current === fsRoot)
      return void 0;
    current = path7.dirname(current);
  }
}
async function isExcludedLspRoot(dir) {
  const candidate = path7.resolve(dir);
  if (hasFixtureConvention(candidate) || hasAtomicStageSegment(candidate))
    return true;
  const gitRoot = await findGitBoundary(candidate);
  if (!gitRoot || gitRoot === candidate)
    return false;
  if (getProjectIgnoreGlobs(gitRoot).length === 0)
    return false;
  return isPathIgnoredByProject(candidate, gitRoot, true);
}
async function nearestNonExcludedFallbackRoot(candidate) {
  if (!await isExcludedLspRoot(candidate))
    return path7.resolve(candidate);
  let current = path7.dirname(path7.resolve(candidate));
  const fsRoot = path7.parse(current).root;
  let nearestAllowed;
  while (true) {
    if (!await isExcludedLspRoot(current)) {
      nearestAllowed ??= current;
      for (const marker of FALLBACK_PROJECT_MARKERS) {
        if (await markerExists(current, marker))
          return current;
      }
    }
    if (current === fsRoot)
      break;
    current = path7.dirname(current);
  }
  return nearestAllowed ?? fsRoot;
}
function isLspInstallDisabled() {
  return process.env.PI_LENS_DISABLE_LSP_INSTALL === "1";
}
function canInstall(allowInstall) {
  return allowInstall !== false && !isLspInstallDisabled();
}
var DIRECT_LSP_NEGATIVE_TTL_MS = Math.max(3e4, Number.parseInt(process.env.PI_LENS_DIRECT_LSP_NEGATIVE_TTL_MS ?? "600000", 10) || 6e5);
var directLspCommandUnavailableUntil = /* @__PURE__ */ new Map();
var directLspCommandSkipLoggedUntil = /* @__PURE__ */ new Map();
var LSP_SERVICE_GENERATION_FAMILY = "lsp.service.generation";
var LSP_SERVICE_GENERATION_VERSION = 1;
function lspServiceGenerationCell() {
  let seed = 0;
  return getProcessSingleton(LSP_SERVICE_GENERATION_FAMILY, LSP_SERVICE_GENERATION_VERSION, () => ({ generation: seed }), (previous) => {
    const value = previous?.generation;
    seed = typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : 0;
  });
}
var lspLaunchAvailabilityGeneration = createGenerationSource("lsp-launch-availability", lspServiceGenerationCell);
function resetLspLaunchAvailabilityGeneration() {
  lspLaunchAvailabilityGeneration.bump();
}
function staleLaunch(generation, subject, proc) {
  if (generation.isCurrent())
    return false;
  try {
    proc?.process?.kill();
  } catch {
  }
  generation.guardedWrite(subject, () => void 0);
  return true;
}
async function installEvidenceForLaunch(toolId, installed, attempt) {
  const evidence = describeInstallAttempt(attempt);
  const confirmedManagedPath = await findManagedToolBinary(toolId);
  return {
    ...evidence,
    binary: path7.basename(installed),
    ...confirmedManagedPath !== void 0 && pathsEqual(confirmedManagedPath, installed) && {
      source: "managed-dir"
    }
  };
}
function captureInstallAttempt(toolId) {
  try {
    return getInstallAttempt?.(toolId);
  } catch {
  }
  return void 0;
}
function resetDirectLspCommandAvailability() {
  directLspCommandUnavailableUntil.clear();
  directLspCommandSkipLoggedUntil.clear();
}
function pruneExpiredDirectLspNegativeEntries(now = Date.now()) {
  for (const [command, until] of directLspCommandUnavailableUntil) {
    if (until <= now) {
      directLspCommandUnavailableUntil.delete(command);
      directLspCommandSkipLoggedUntil.delete(command);
    }
  }
  for (const [command, until] of directLspCommandSkipLoggedUntil) {
    if (until <= now)
      directLspCommandSkipLoggedUntil.delete(command);
  }
}
function isSimpleCommand(command) {
  return !isFullyQualified(command) && !command.includes("/") && !command.includes("\\");
}
function isDirectLspCommandTemporarilyUnavailable(command) {
  const now = Date.now();
  pruneExpiredDirectLspNegativeEntries(now);
  const until = directLspCommandUnavailableUntil.get(command);
  if (!until || until <= now) {
    directLspCommandUnavailableUntil.delete(command);
    return false;
  }
  const loggedUntil = directLspCommandSkipLoggedUntil.get(command) ?? 0;
  if (loggedUntil <= now) {
    logSessionStart(`lsp direct command ${command}: skipped by negative availability cache (${Math.max(0, until - now)}ms remaining)`);
    directLspCommandSkipLoggedUntil.set(command, until);
  }
  return true;
}
function markDirectLspCommandUnavailable(command) {
  if (!isSimpleCommand(command))
    return;
  directLspCommandUnavailableUntil.set(command, Date.now() + DIRECT_LSP_NEGATIVE_TTL_MS);
  directLspCommandSkipLoggedUntil.delete(command);
}
var PI_LENS_BIN_DIR2 = path7.join(getGlobalPiLensDir(), "bin");
async function resolveAndLaunch(spec, allowInstall) {
  const generation = lspLaunchAvailabilityGeneration.capture();
  const toolLabel = spec.managedToolId ?? spec.candidates[spec.candidates.length - 1] ?? "unknown";
  let lastRuntimeFailure;
  const trackRuntimeFailure = (err) => {
    const message = err instanceof Error ? err.message : String(err);
    if (!hasSpawnFailureKind(err, "tool-not-found")) {
      lastRuntimeFailure = err instanceof Error ? err : new Error(message);
    }
  };
  const managedProbeStartedAt = Date.now();
  const managedCandidate = spec.managedToolId ? await findManagedToolBinary(spec.managedToolId) : void 0;
  if (staleLaunch(generation, `${toolLabel}:findManagedToolBinary`)) {
    return void 0;
  }
  const candidates = managedCandidate && !spec.candidates.includes(managedCandidate) ? [managedCandidate, ...spec.candidates] : spec.candidates;
  const candidateFailures = [];
  for (const [index, command] of candidates.entries()) {
    logLatency({
      type: "phase",
      phase: "lsp_launch_candidate_attempt",
      filePath: spec.cwd,
      durationMs: 0,
      metadata: {
        tool: toolLabel,
        command,
        index,
        totalCandidates: candidates.length,
        allowInstall: canInstall(allowInstall)
      }
    });
    logSessionStart(`lsp launch candidate attempt tool=${toolLabel} idx=${index}/${candidates.length - 1} command=${command} cwd=${spec.cwd}`);
    try {
      const proc = await launchLSP(command, spec.args, {
        cwd: spec.cwd,
        env: spec.env
      });
      if (staleLaunch(generation, `${toolLabel}:launchLSP:${command}`, proc)) {
        return void 0;
      }
      logLatency({
        type: "phase",
        phase: "lsp_launch_candidate_success",
        filePath: spec.cwd,
        durationMs: 0,
        metadata: {
          tool: toolLabel,
          command,
          index,
          source: "direct"
        }
      });
      logSessionStart(`lsp launch candidate success tool=${toolLabel} idx=${index} command=${command} source=direct`);
      if (managedCandidate !== void 0 && command === managedCandidate) {
        logAvailabilityDecision({
          tool: toolLabel,
          verdict: "available",
          outcome: "success",
          cause: "ok",
          elapsedMs: Date.now() - managedProbeStartedAt,
          latched: false,
          producer: "lsp-launch",
          classifiedBy: "probe",
          evidence: {
            binary: path7.basename(managedCandidate),
            source: "managed-dir",
            correctsLatchedRow: false
          }
        });
      }
      return { process: proc, source: "direct" };
    } catch (err) {
      if (staleLaunch(generation, `${toolLabel}:launchLSP:${command}`)) {
        return void 0;
      }
      const message = err instanceof Error ? err.message : String(err);
      candidateFailures.push({ index, command, message, err });
    }
  }
  for (const failure of candidateFailures) {
    logLatency({
      type: "phase",
      phase: "lsp_launch_candidate_failed",
      filePath: spec.cwd,
      durationMs: 0,
      metadata: {
        tool: toolLabel,
        command: failure.command,
        index: failure.index,
        error: failure.message
      }
    });
    logSessionStart(`lsp launch candidate failed tool=${toolLabel} idx=${failure.index} command=${failure.command} error=${failure.message}`);
    trackRuntimeFailure(failure.err);
  }
  const hasOnlyRepairableCandidateFailures = candidateFailures.every((failure) => hasSpawnFailureKind(failure.err, "tool-not-found"));
  if (!hasOnlyRepairableCandidateFailures) {
    if (lastRuntimeFailure)
      throw lastRuntimeFailure;
    return void 0;
  }
  if (!canInstall(allowInstall)) {
    logSessionStart(`lsp launch install blocked tool=${toolLabel} cwd=${spec.cwd} allowInstall=${allowInstall !== false} globalDisabled=${isLspInstallDisabled()}`);
    logLatency({
      type: "phase",
      phase: "lsp_launch_install_blocked",
      filePath: spec.cwd,
      durationMs: 0,
      metadata: {
        tool: toolLabel,
        allowInstall,
        globalInstallDisabled: isLspInstallDisabled()
      }
    });
    return void 0;
  }
  if (spec.managedToolId) {
    const failedCandidate = candidateFailures.at(-1);
    const classifiedFailure = classifyProbeFailure({
      error: failedCandidate?.err instanceof Error ? failedCandidate.err : void 0,
      spawnFailure: {
        kind: hasSpawnFailureKind(failedCandidate?.err, "tool-not-found") ? "tool-not-found" : void 0
      }
    }, { command: failedCandidate?.command });
    logAvailabilityDecision({
      tool: spec.managedToolId,
      verdict: "unavailable",
      outcome: classifiedFailure.outcome,
      cause: classifiedFailure.cause,
      elapsedMs: Date.now() - managedProbeStartedAt,
      latched: false,
      classifiedBy: "probe",
      producer: "lsp-launch",
      evidence: classifiedFailure.evidence
    });
    logSessionStart(`lsp launch ensure-tool start tool=${spec.managedToolId} cwd=${spec.cwd}`);
    const installStartedAt = Date.now();
    const installed = await ensureTool(spec.managedToolId);
    const installAttempt = captureInstallAttempt(spec.managedToolId);
    if (staleLaunch(generation, `${toolLabel}:ensureTool`)) {
      return void 0;
    }
    logSessionStart(`lsp launch ensure-tool result tool=${spec.managedToolId} installed=${installed ? "yes" : "no"} path=${installed ?? ""}`);
    logLatency({
      type: "phase",
      phase: "lsp_launch_ensure_tool_result",
      filePath: spec.cwd,
      durationMs: 0,
      metadata: {
        tool: spec.managedToolId,
        installed: Boolean(installed),
        path: installed
      }
    });
    if (installed) {
      try {
        const proc = await launchLSP(installed, spec.args, {
          cwd: spec.cwd,
          env: spec.env
        });
        if (staleLaunch(generation, `${toolLabel}:launchLSP:managed`, proc)) {
          return void 0;
        }
        logSessionStart(`lsp launch managed success tool=${spec.managedToolId} command=${installed} source=managed`);
        logLatency({
          type: "phase",
          phase: "lsp_launch_managed_success",
          filePath: spec.cwd,
          durationMs: 0,
          metadata: {
            tool: spec.managedToolId,
            command: installed
          }
        });
        const evidence = await installEvidenceForLaunch(spec.managedToolId, installed, installAttempt);
        if (staleLaunch(generation, `${toolLabel}:managed-evidence`, proc)) {
          return void 0;
        }
        logAvailabilityDecision({
          tool: spec.managedToolId,
          verdict: "available",
          outcome: "success",
          cause: "ok",
          elapsedMs: Date.now() - installStartedAt,
          latched: false,
          classifiedBy: "caller",
          producer: "lsp-launch",
          evidence: {
            ...evidence,
            correctsLatchedRow: false
          }
        });
        return { process: proc, source: "managed" };
      } catch (err) {
        if (staleLaunch(generation, `${toolLabel}:launchLSP:managed-rejection`)) {
          return void 0;
        }
        const message = err instanceof Error ? err.message : String(err);
        logSessionStart(`lsp launch managed failed tool=${spec.managedToolId} command=${installed} error=${message}`);
        logLatency({
          type: "phase",
          phase: "lsp_launch_managed_failed",
          filePath: spec.cwd,
          durationMs: 0,
          metadata: {
            tool: spec.managedToolId,
            command: installed,
            error: message
          }
        });
        trackRuntimeFailure(err);
        const looksPathResolved = !installed.includes("/") && !installed.includes("\\");
        if (looksPathResolved && hasSpawnFailureKind(err, "tool-not-found")) {
          logSessionStart(`lsp launch managed retry force-reinstall tool=${spec.managedToolId}`);
          const reinstalled = await ensureTool(spec.managedToolId, {
            forceReinstall: true
          });
          const reinstallAttempt = captureInstallAttempt(spec.managedToolId);
          if (staleLaunch(generation, `${toolLabel}:forceReinstall`)) {
            return void 0;
          }
          if (reinstalled) {
            try {
              const proc = await launchLSP(reinstalled, spec.args, {
                cwd: spec.cwd,
                env: spec.env
              });
              if (staleLaunch(generation, `${toolLabel}:launchLSP:forceReinstall`, proc)) {
                return void 0;
              }
              logSessionStart(`lsp launch managed force-reinstall success tool=${spec.managedToolId} command=${reinstalled}`);
              logLatency({
                type: "phase",
                phase: "lsp_launch_managed_force_reinstall_success",
                filePath: spec.cwd,
                durationMs: 0,
                metadata: {
                  tool: spec.managedToolId,
                  command: reinstalled
                }
              });
              const evidence = await installEvidenceForLaunch(spec.managedToolId, reinstalled, reinstallAttempt);
              if (staleLaunch(generation, `${toolLabel}:forceReinstall-evidence`, proc)) {
                return void 0;
              }
              logAvailabilityDecision({
                tool: spec.managedToolId,
                verdict: "available",
                outcome: "success",
                cause: "ok",
                elapsedMs: Date.now() - installStartedAt,
                latched: false,
                classifiedBy: "caller",
                producer: "lsp-launch",
                evidence: {
                  ...evidence,
                  correctsLatchedRow: false
                }
              });
              return { process: proc, source: "managed" };
            } catch (retryErr) {
              logSessionStart(`lsp launch managed force-reinstall failed tool=${spec.managedToolId} error=${retryErr instanceof Error ? retryErr.message : String(retryErr)}`);
            }
          }
        }
      }
    }
  }
  const runtimeInstall = spec.runtimeInstall;
  const runtimeAvailable = runtimeInstall ? await isOnPath(runtimeInstall.runtimeCommand) : false;
  if (staleLaunch(generation, `${toolLabel}:runtimeAvailability`)) {
    return void 0;
  }
  if (runtimeInstall && runtimeAvailable) {
    const ok = await runtimeInstall.install();
    if (staleLaunch(generation, `${toolLabel}:runtimeInstall`)) {
      return void 0;
    }
    if (ok) {
      const retry = runtimeInstall.retryCandidates ?? spec.candidates;
      for (const command of retry) {
        try {
          const proc = await launchLSP(command, spec.args, {
            cwd: spec.cwd,
            env: spec.env
          });
          if (staleLaunch(generation, `${toolLabel}:launchLSP:runtime`, proc)) {
            return void 0;
          }
          return { process: proc, source: "managed" };
        } catch (err) {
          if (staleLaunch(generation, `${toolLabel}:launchLSP:runtime`)) {
            return void 0;
          }
          trackRuntimeFailure(err);
        }
      }
    }
  }
  if (lastRuntimeFailure) {
    throw lastRuntimeFailure;
  }
  return void 0;
}
async function resolveAndLaunchBundle(spec, allowInstall) {
  let runtime;
  for (const candidate of spec.runtimeCandidates) {
    if (await isOnPath(candidate)) {
      runtime = candidate;
      break;
    }
  }
  if (!runtime) {
    logSessionStart(`lsp launch bundle skip tool=${spec.bundleToolId}: no runtime on PATH (tried ${spec.runtimeCandidates.join(", ")})`);
    return void 0;
  }
  let bundleDir = await getToolPath(spec.bundleToolId);
  if (!bundleDir && canInstall(allowInstall)) {
    bundleDir = await ensureTool(spec.bundleToolId);
  }
  if (!bundleDir) {
    logSessionStart(`lsp launch bundle skip tool=${spec.bundleToolId}: bundle not installed (allowInstall=${allowInstall !== false})`);
    return void 0;
  }
  try {
    const proc = await launchLSP(runtime, spec.args(bundleDir), {
      cwd: spec.cwd,
      env: spec.env
    });
    logSessionStart(`lsp launch bundle success tool=${spec.bundleToolId} runtime=${runtime} bundle=${bundleDir}`);
    return { process: proc, source: "managed" };
  } catch (err) {
    const message = err instanceof Error ? err.message : String(err);
    logSessionStart(`lsp launch bundle failed tool=${spec.bundleToolId} runtime=${runtime} error=${message}`);
    return void 0;
  }
}
async function resolveAndLaunchTreeBinary(spec, allowInstall) {
  for (const command of spec.candidates) {
    try {
      const proc = await launchLSP(command, spec.args, {
        cwd: spec.cwd,
        env: spec.env
      });
      return { process: proc, source: "direct" };
    } catch {
    }
  }
  let bundleDir = await getToolPath(spec.bundleToolId);
  if (!bundleDir && canInstall(allowInstall)) {
    bundleDir = await ensureTool(spec.bundleToolId);
  }
  if (!bundleDir) {
    logSessionStart(`lsp launch tree-bin skip tool=${spec.bundleToolId}: not on PATH and bundle not installed (allowInstall=${allowInstall !== false})`);
    return void 0;
  }
  const suffix = process.platform === "win32" ? ".exe" : "";
  const binPath = path7.join(bundleDir, ...spec.binRelPath.split("/")) + suffix;
  try {
    const proc = await launchLSP(binPath, spec.args, {
      cwd: spec.cwd,
      env: spec.env
    });
    logSessionStart(`lsp launch tree-bin success tool=${spec.bundleToolId} bin=${binPath}`);
    return { process: proc, source: "managed" };
  } catch (err) {
    const message = err instanceof Error ? err.message : String(err);
    logSessionStart(`lsp launch tree-bin failed tool=${spec.bundleToolId} bin=${binPath} error=${message}`);
    return void 0;
  }
}
function nodeBinLocalCandidates(root, baseName) {
  const localBase = path7.join(root, "node_modules", ".bin", baseName);
  if (process.platform === "win32") {
    return [`${localBase}.cmd`, `${localBase}.exe`];
  }
  return [localBase];
}
function nodeBinCandidates(root, baseName) {
  return [...nodeBinLocalCandidates(root, baseName), baseName];
}
function piAgentExtensionsRootKey(file) {
  const dirKey = normalizeEphemeralMapKey(path7.dirname(path7.resolve(file)));
  const marker = "/.pi/agent/extensions";
  const index = dirKey.indexOf(marker);
  if (index === -1)
    return void 0;
  return dirKey.slice(0, index + marker.length);
}
function IgnoreHomeRoot(primary) {
  const homeKey = normalizeEphemeralMapKey(path7.resolve(os6.homedir()));
  return withRootMarkers(async (file) => {
    const root = await primary(file);
    if (!root)
      return void 0;
    return normalizeEphemeralMapKey(path7.resolve(root)) === homeKey ? void 0 : root;
  }, primary.rootMarkers ?? []);
}
function rubyBinCandidates(baseName) {
  const candidates = [];
  const home = os6.homedir();
  const isWin = process.platform === "win32";
  const ext = isWin ? ".bat" : "";
  candidates.push(path7.join(home, ".local", "share", "mise", "installs", "ruby", "bin", `${baseName}${ext}`));
  candidates.push(path7.join(home, ".asdf", "installs", "ruby", "bin", `${baseName}${ext}`));
  if (isWin) {
    const driveRoot = path7.parse(home).root;
    for (const entry of getRubyVersionDirNamesSync(driveRoot)) {
      candidates.push(path7.join(driveRoot, entry, "bin", `${baseName}.bat`));
      candidates.push(path7.join(driveRoot, entry, "bin", baseName));
    }
  }
  return candidates;
}
function createInteractiveServer(spec) {
  return {
    id: spec.id,
    name: spec.name,
    extensions: spec.extensions,
    idleEviction: "unmeasured",
    root: spec.root,
    rootMarkers: spec.root.rootMarkers,
    fallbackFor: spec.fallbackFor,
    requiresProjectRoot: spec.requiresProjectRoot,
    availabilityKey: typeof spec.command === "string" && isSimpleCommand(spec.command) ? spec.command : void 0,
    async spawn(root) {
      const command = typeof spec.command === "function" ? spec.command(root) : spec.command;
      const args = typeof spec.args === "function" ? spec.args(root) : spec.args || [];
      if (isSimpleCommand(command) && isDirectLspCommandTemporarilyUnavailable(command)) {
        return void 0;
      }
      const runtimeEnv = spec.runtime === "java" ? await resolveJavaRuntimeEnv() : void 0;
      try {
        const proc = await launchLSP(command, args, {
          cwd: root,
          ...runtimeEnv ? { env: runtimeEnv } : {}
        });
        const initialization = typeof spec.initialization === "function" ? spec.initialization(root) : spec.initialization;
        return { process: proc, source: "direct", initialization };
      } catch (err) {
        if (hasSpawnFailureKind(err, "tool-not-found")) {
          markDirectLspCommandUnavailable(command);
        }
        return void 0;
      }
    }
  };
}
function PriorityRoot(markerGroups, excludePatterns, stopDir) {
  const resolvers = markerGroups.map((markers) => NearestRoot(markers, excludePatterns, stopDir));
  return withRootMarkers(async (file) => {
    for (const resolve3 of resolvers) {
      const root = await resolve3(file);
      if (root)
        return root;
    }
    return void 0;
  }, markerGroups.flat());
}
var FileDirRoot = async (file) => {
  const candidate = path7.resolve(path7.dirname(file));
  return nearestNonExcludedFallbackRoot(candidate);
};
function RootWithFallback(primary, fallback = FileDirRoot) {
  return withRootMarkers(async (file) => {
    const primaryRoot = await primary(file);
    if (primaryRoot)
      return primaryRoot;
    return fallback(file);
  }, primary.rootMarkers ?? []);
}
function WorkspacePriorityRoot(markerGroups, excludePatterns) {
  return withRootMarkers(async (file) => PriorityRoot(markerGroups, excludePatterns, process.cwd())(file), markerGroups.flat());
}
function isPermissionFsError(err) {
  const code = err?.code;
  return code === "EACCES" || code === "EPERM";
}
function markerProbeDir(dir, pattern) {
  const normalized = toPosix(pattern);
  const slash = normalized.lastIndexOf("/");
  const parentPattern = slash >= 0 ? normalized.slice(0, slash) : "";
  return parentPattern ? path7.join(dir, ...parentPattern.split("/").filter(Boolean)) : dir;
}
async function markerExists(dir, pattern) {
  if (!pattern.includes("*")) {
    try {
      await stat(path7.join(dir, pattern));
      return true;
    } catch (err) {
      if (isPermissionFsError(err)) {
        logSessionStart(`lsp root marker skipped: permission error stat ${path7.join(dir, pattern)}`);
      }
      return false;
    }
  }
  const normalized = pattern.replace(/\\/g, "/");
  const slash = normalized.lastIndexOf("/");
  const basenamePattern = slash >= 0 ? normalized.slice(slash + 1) : normalized;
  if (!basenamePattern)
    return false;
  const targetDir = markerProbeDir(dir, normalized);
  try {
    const entries = await readdir(targetDir, { withFileTypes: true });
    return direntsHaveMarkerGlobMatch(entries, basenamePattern);
  } catch (err) {
    if (isPermissionFsError(err)) {
      logSessionStart(`lsp root marker skipped: permission error read ${targetDir}`);
    }
    return false;
  }
}
var rootMtimeBound = (label) => ({
  ms: HOOK_WALL_BUDGET_MS.tool_result_edit,
  signal: void 0,
  hook: "tool_result_edit",
  label
});
function NearestRoot(includePatterns, excludePatterns, stopDir) {
  const cache = /* @__PURE__ */ new Map();
  const inFlight = /* @__PURE__ */ new Map();
  const subdirPatterns = [];
  for (const pattern of [...includePatterns, ...excludePatterns ?? []]) {
    if (markerProbeDir("", pattern))
      subdirPatterns.push(pattern);
  }
  return withRootMarkers(async (file) => {
    const startDir = path7.resolve(path7.dirname(file));
    const dirKey = normalizeMapKey(startDir);
    const cached = cache.get(dirKey);
    if (cached !== void 0 && Date.now() - cached.walkedAt < FRESHNESS_CADENCE_MS && await bounded(dirMtimesStillFreshAsync(cached.probedDirs), rootMtimeBound("lsp-root-memo-freshness")) === true)
      return cached.root;
    const flying = inFlight.get(dirKey);
    if (flying)
      return flying;
    const probedDirs = [];
    const walkedAt = Date.now();
    const promise = (async () => {
      let currentDir = startDir;
      const fsRoot = path7.parse(currentDir).root;
      const stop = stopDir ? path7.resolve(stopDir) : fsRoot;
      while (true) {
        if (stop !== fsRoot && currentDir.startsWith(stop + path7.sep) === false && currentDir !== stop) {
          break;
        }
        const stepDirs = [
          currentDir,
          ...subdirPatterns.map((pattern) => markerProbeDir(currentDir, pattern))
        ];
        const stepRecords = await bounded(dirMtimeRecordsAsync(stepDirs), rootMtimeBound("lsp-root-walk-dir-mtimes"));
        probedDirs.push(...stepRecords ?? unknownDirMtimeRecords(stepDirs));
        if (excludePatterns) {
          let excluded = false;
          for (const pattern of excludePatterns) {
            if (await markerExists(currentDir, pattern)) {
              excluded = true;
              break;
            }
          }
          if (excluded) {
            currentDir = path7.dirname(currentDir);
            continue;
          }
        }
        for (const pattern of includePatterns) {
          if (await markerExists(currentDir, pattern) && (pattern !== ".git" || currentDir !== os6.tmpdir()) && !await isExcludedLspRoot(currentDir)) {
            return enforceLspRootCeiling(currentDir, process.cwd(), file);
          }
        }
        if (currentDir === stop || currentDir === fsRoot) {
          break;
        }
        currentDir = path7.dirname(currentDir);
      }
      return void 0;
    })();
    inFlight.set(dirKey, promise);
    try {
      const result = await promise;
      if (result !== void 0)
        cache.set(dirKey, { root: result, probedDirs, walkedAt });
      return result;
    } finally {
      inFlight.delete(dirKey);
    }
  }, includePatterns);
}
var createRootDetector = NearestRoot;
function isOnPath(command) {
  return isCommandAvailableAsync(command);
}
async function tryGoInstallGopls() {
  const isWindows2 = process.platform === "win32";
  const result = await safeSpawnAsync(isWindows2 ? "go.exe" : "go", ["install", "golang.org/x/tools/gopls@latest"], { timeout: 18e4, ignoreAmbientSignal: true });
  return !result.error && result.status === 0;
}
async function tryDotnetToolInstall(tool) {
  mkdirSync2(PI_LENS_BIN_DIR2, { recursive: true });
  const result = await safeSpawnAsync("dotnet", ["tool", "install", "--tool-path", PI_LENS_BIN_DIR2, tool], { timeout: 18e4, ignoreAmbientSignal: true });
  if (!result.error && result.status === 0)
    return true;
  const stderr = result.stderr ?? "";
  if (stderr.includes("No NuGet sources are defined or enabled")) {
    logSessionStart(`lsp dotnet-install: NuGet sources missing \u2014 cannot install ${tool}. Run: dotnet nuget add source https://api.nuget.org/v3/index.json -n nuget.org`);
    return false;
  }
  const updateResult = await safeSpawnAsync("dotnet", ["tool", "update", "--tool-path", PI_LENS_BIN_DIR2, tool], { timeout: 18e4, ignoreAmbientSignal: true });
  return !updateResult.error && updateResult.status === 0;
}
async function findAncestorFile(startDir, ...relativeSegments) {
  return findAncestorFileAmong(startDir, [relativeSegments]);
}
async function findAncestorFileAmong(startDir, candidateSegmentLists) {
  const fs5 = await import("node:fs/promises");
  let currentDir = path7.resolve(startDir);
  while (!isAtOrAboveHomeDir(currentDir)) {
    for (const segments of candidateSegmentLists) {
      const candidate = path7.join(currentDir, ...segments);
      try {
        await fs5.access(candidate);
        return candidate;
      } catch {
      }
    }
    const parent = path7.dirname(currentDir);
    if (parent === currentDir)
      break;
    currentDir = parent;
  }
  return void 0;
}
var CLASSIC_TS_REPAIR_FAMILY = "lsp.classic-ts-repair-guard";
var CLASSIC_TS_REPAIR_VERSION = 1;
function classicTsRepairState() {
  return getProcessSingleton(CLASSIC_TS_REPAIR_FAMILY, CLASSIC_TS_REPAIR_VERSION, () => ({ attempted: false }));
}
function resetClassicTsRepairGuard() {
  classicTsRepairState().attempted = false;
}
function typescriptDirsForTsc(tscPath) {
  const binDir = path7.dirname(tscPath);
  return [
    path7.join(binDir, "..", "typescript"),
    path7.join(binDir, "..", "..", "typescript")
  ];
}
async function typescriptVersionForTsc(tscPath) {
  if (!path7.isAbsolute(tscPath))
    return void 0;
  for (const dir of typescriptDirsForTsc(tscPath)) {
    let manifest;
    try {
      manifest = await readFile(path7.join(dir, "package.json"), "utf8");
    } catch {
      continue;
    }
    let version;
    try {
      const parsed = JSON.parse(manifest);
      if (typeof parsed !== "object" || parsed === null || !("version" in parsed) || typeof parsed.version !== "string") {
        return void 0;
      }
      version = parsed.version;
    } catch {
      return void 0;
    }
    const majorText = version.split(".", 1)[0] ?? "";
    const major = /^\d+$/.test(majorText) ? Number(majorText) : Number.NaN;
    if (!Number.isFinite(major))
      return void 0;
    return { version, major };
  }
  return void 0;
}
async function findTsserverPath(root, allowInstall) {
  const fs5 = await import("node:fs/promises");
  const ancestorHit = await findAncestorFile(root, "node_modules", "typescript", "lib", "tsserver.js");
  if (ancestorHit)
    return ancestorHit;
  const cwdCandidate = path7.join(process.cwd(), "node_modules", "typescript", "lib", "tsserver.js");
  try {
    await fs5.access(cwdCandidate);
    return cwdCandidate;
  } catch {
  }
  const tsserverForTsc = async (tscPath) => {
    if (!tscPath)
      return void 0;
    for (const dir of typescriptDirsForTsc(tscPath)) {
      const candidate = path7.join(dir, "lib", "tsserver.js");
      try {
        await fs5.access(candidate);
        return candidate;
      } catch {
      }
    }
    return void 0;
  };
  const installAllowed = canInstall(allowInstall);
  const discoveredTsc = await ensureTool("typescript", {
    allowInstall: installAllowed
  });
  const discoveredTsserver = await tsserverForTsc(discoveredTsc);
  if (discoveredTsserver || !discoveredTsc || !installAllowed || classicTsRepairState().attempted) {
    return discoveredTsserver;
  }
  const discoveredVersion = await typescriptVersionForTsc(discoveredTsc);
  if (!discoveredVersion || discoveredVersion.major < 7)
    return void 0;
  classicTsRepairState().attempted = true;
  logSessionStart(`lsp typescript: managed compiler resolved to TypeScript ${discoveredVersion.version}, which ships no tsserver.js; reinstalling pinned classic fallback`);
  const repairedTsc = await ensureTool("typescript", {
    allowInstall: true,
    forceReinstall: true
  });
  return tsserverForTsc(repairedTsc);
}
async function findNativeTypeScriptLsp(root) {
  let currentDir = path7.resolve(root);
  while (!isAtOrAboveHomeDir(currentDir)) {
    const typescriptDir = path7.join(currentDir, "node_modules", "typescript");
    const packageJsonPath = path7.join(typescriptDir, "package.json");
    let packageJsonText;
    try {
      packageJsonText = await readFile(packageJsonPath, "utf8");
    } catch (error) {
      if (error.code !== "ENOENT") {
        return void 0;
      }
      try {
        const dirStat = await stat(typescriptDir);
        if (dirStat.isDirectory())
          return void 0;
      } catch {
      }
      const parent = path7.dirname(currentDir);
      if (parent === currentDir)
        return void 0;
      currentDir = parent;
      continue;
    }
    let version;
    try {
      const parsed = JSON.parse(packageJsonText);
      if (typeof parsed !== "object" || parsed === null || !("version" in parsed) || typeof parsed.version !== "string") {
        return void 0;
      }
      version = parsed.version;
    } catch {
      return void 0;
    }
    const majorText = version.split(".", 1)[0] ?? "";
    const major = /^\d+$/.test(majorText) ? Number(majorText) : Number.NaN;
    if (!Number.isFinite(major) || major < 7)
      return void 0;
    const localTsc = path7.join(currentDir, "node_modules", ".bin", "tsc");
    const candidates = process.platform === "win32" ? [`${localTsc}.cmd`, `${localTsc}.exe`, localTsc] : [localTsc];
    for (const command of candidates) {
      try {
        await access(command);
        return { command, version };
      } catch {
      }
    }
    return void 0;
  }
  return void 0;
}
function dotnetToolCandidates(tool) {
  const home = os6.homedir();
  return [
    path7.join(PI_LENS_BIN_DIR2, `${tool}.exe`),
    path7.join(PI_LENS_BIN_DIR2, tool),
    path7.join(home, ".dotnet", "tools", `${tool}.exe`),
    path7.join(home, ".dotnet", "tools", tool),
    tool
  ].filter(Boolean);
}
function binExeVariants(dir, tool) {
  return process.platform === "win32" ? [path7.join(dir, `${tool}.exe`), path7.join(dir, tool)] : [path7.join(dir, tool)];
}
function goBinCandidates(tool) {
  const gopath = process.env.GOPATH?.split(path7.delimiter)[0] || path7.join(os6.homedir(), "go");
  return [tool, ...binExeVariants(path7.join(gopath, "bin"), tool)];
}
function cargoBinCandidates(tool) {
  const cargoHome = process.env.CARGO_HOME || path7.join(os6.homedir(), ".cargo");
  return [tool, ...binExeVariants(path7.join(cargoHome, "bin"), tool)];
}
async function tryGemInstall(gem) {
  const { join: join3 } = await import("node:path");
  const binDir = join3(getGlobalPiLensDir(), "bin");
  const { mkdir } = await import("node:fs/promises");
  await mkdir(binDir, { recursive: true });
  const result = await safeSpawnAsync("gem", ["install", gem, "--bindir", binDir, "--no-document"], { timeout: 18e4, ignoreAmbientSignal: true });
  const ok = !result.error && result.status === 0;
  if (ok) {
    const sep2 = process.platform === "win32" ? ";" : ":";
    if (!process.env.PATH?.includes(binDir)) {
      process.env.PATH = `${binDir}${sep2}${process.env.PATH ?? ""}`;
    }
  }
  return ok;
}
function DenoExcludeRoot(primary) {
  const denoDetector = createRootDetector(["deno.json", "deno.jsonc"]);
  return async (file) => {
    const denoRoot = await denoDetector(file);
    if (denoRoot)
      return void 0;
    return primary(file);
  };
}
var JS_TS_LSP_EXTENSIONS = KIND_EXTENSIONS["jsts"].filter((ext) => ext !== ".svelte" && ext !== ".vue");
var TS_CONFIG_MARKERS = ["tsconfig.json", "jsconfig.json"];
var TS_TOOLING_MARKERS = [
  "package-lock.json",
  "bun.lockb",
  "bun.lock",
  "pnpm-lock.yaml",
  "yarn.lock",
  "package.json"
];
var TS_FAMILY_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".mts", ".cts"]);
function isTsFamilyFile(file) {
  return TS_FAMILY_EXTENSIONS.has(path7.extname(file).toLowerCase());
}
function tsConfigMarkersForFile(file) {
  return isTsFamilyFile(file) ? ["tsconfig.json"] : TS_CONFIG_MARKERS;
}
var TypeScriptConfigRootTsOnly = IgnoreHomeRoot(createRootDetector(["tsconfig.json"]));
var TypeScriptConfigRootEither = IgnoreHomeRoot(createRootDetector([...TS_CONFIG_MARKERS]));
var TypeScriptToolingRoot = IgnoreHomeRoot(createRootDetector([...TS_TOOLING_MARKERS]));
async function findTypeScriptProjectRoot(file) {
  const configDetector = isTsFamilyFile(file) ? TypeScriptConfigRootTsOnly : TypeScriptConfigRootEither;
  const [configRoot, toolingRoot] = await Promise.all([
    configDetector(file),
    TypeScriptToolingRoot(file)
  ]);
  if (!configRoot)
    return toolingRoot;
  if (!toolingRoot)
    return configRoot;
  return isSameOrWithin(toolingRoot, configRoot) ? configRoot : toolingRoot;
}
async function findExtensionBoundedRoot(file, extensionRootKey) {
  const startDir = path7.resolve(path7.dirname(file));
  let currentDir = startDir;
  const configMarkers = tsConfigMarkersForFile(file);
  while (true) {
    for (const pattern of configMarkers) {
      try {
        await stat(path7.join(currentDir, pattern));
        return currentDir;
      } catch {
      }
    }
    for (const pattern of TS_TOOLING_MARKERS) {
      try {
        await stat(path7.join(currentDir, pattern));
        return currentDir;
      } catch {
      }
    }
    const currentKey = normalizeEphemeralMapKey(path7.resolve(currentDir));
    if (currentKey === extensionRootKey)
      return void 0;
    const parent = path7.dirname(currentDir);
    if (parent === currentDir)
      return void 0;
    currentDir = parent;
  }
}
async function hasAgentLevelProjectMarker(extensionRootKey) {
  const agentDir = path7.dirname(extensionRootKey);
  if (!agentDir || agentDir === extensionRootKey)
    return false;
  for (const pattern of [...TS_CONFIG_MARKERS, ...TS_TOOLING_MARKERS]) {
    try {
      await stat(path7.join(agentDir, pattern));
      return true;
    } catch {
    }
  }
  return false;
}
var TypeScriptRoot = withRootMarkers(DenoExcludeRoot(async (file) => {
  const extensionRootKey = piAgentExtensionsRootKey(file);
  if (extensionRootKey) {
    const bounded2 = await findExtensionBoundedRoot(file, extensionRootKey);
    if (bounded2)
      return bounded2;
    if (await hasAgentLevelProjectMarker(extensionRootKey)) {
      return FileDirRoot(file);
    }
    return void 0;
  }
  const projectRoot = await findTypeScriptProjectRoot(file);
  if (projectRoot)
    return projectRoot;
  return FileDirRoot(file);
}), [...TS_CONFIG_MARKERS, ...TS_TOOLING_MARKERS]);
var TypeScriptServer = {
  id: "typescript",
  idleEviction: "transparent",
  name: "TypeScript Language Server",
  extensions: JS_TS_LSP_EXTENSIONS,
  autoPropagateDiagnostics: true,
  root: TypeScriptRoot,
  rootMarkers: TypeScriptRoot.rootMarkers,
  async spawn(root, options) {
    const fs5 = await import("node:fs/promises");
    const nativeLsp = await findNativeTypeScriptLsp(root);
    if (nativeLsp) {
      const env2 = await getToolEnvironment();
      logSessionStart(`lsp typescript-native: version=${nativeLsp.version} command=${nativeLsp.command}`);
      const proc2 = await launchLSP(nativeLsp.command, ["--lsp", "--stdio"], {
        cwd: root,
        env: env2
      });
      return { process: proc2, source: "direct", launchVariant: "native-ts7" };
    }
    let source = "direct";
    let lspPath = await findAncestorFileAmong(root, [
      ["node_modules", ".bin", "typescript-language-server.cmd"],
      ["node_modules", ".bin", "typescript-language-server"]
    ]);
    if (!lspPath) {
      lspPath = await ensureTool("typescript-language-server", {
        allowInstall: canInstall(options?.allowInstall)
      });
      if (lspPath)
        source = "managed";
      if (!lspPath) {
        return void 0;
      }
    }
    let tsserverPath = await findTsserverPath(root, options?.allowInstall);
    if (!tsserverPath) {
      const localCandidate = path7.join(path7.dirname(lspPath), "..", "typescript", "lib", "tsserver.js");
      try {
        await fs5.access(localCandidate);
        tsserverPath = localCandidate;
      } catch {
      }
    }
    if (tsserverPath)
      source = "managed";
    const env = await getToolEnvironment();
    const proc = await launchLSP(lspPath, ["--stdio"], {
      cwd: root,
      env: {
        ...env,
        TSSERVER_PATH: tsserverPath
      }
    });
    return {
      process: proc,
      source,
      initialization: tsserverPath ? { tsserver: { path: tsserverPath } } : void 0,
      launchVariant: "classic"
    };
  }
};
var DenoServer = {
  id: "deno",
  idleEviction: "transparent",
  name: "Deno Language Server",
  fallbackFor: "typescript",
  extensions: JS_TS_LSP_EXTENSIONS,
  autoPropagateDiagnostics: true,
  root: createRootDetector(["deno.json", "deno.jsonc"]),
  async spawn(root, options) {
    return resolveAndLaunch({
      candidates: ["deno"],
      args: ["lsp"],
      cwd: root,
      managedToolId: "deno"
    }, options?.allowInstall);
  }
};
var PythonServer = {
  id: "python",
  idleEviction: "transparent",
  name: "Pyright Language Server",
  extensions: KIND_EXTENSIONS["python"],
  root: RootWithFallback(createRootDetector([
    ".git",
    "pyproject.toml",
    "setup.py",
    "setup.cfg",
    "requirements.txt",
    "Pipfile",
    "poetry.lock"
  ])),
  async spawn(root, options) {
    const pythonEnvironment = await detectPythonEnvironment(root);
    const env = augmentPythonEnvironment(await getToolEnvironment(), pythonEnvironment);
    let source = "direct";
    const pyrightInit = (pythonPath) => ({
      ...pythonPath ? { pythonPath } : {},
      openFilesOnly: true
    });
    const projectPyright = await resolveAndLaunch({
      candidates: [
        ...pythonEnvironmentToolCandidates(pythonEnvironment, "pyright-langserver"),
        ...nodeBinLocalCandidates(root, "pyright-langserver"),
        ...pythonEnvironmentToolCandidates(pythonEnvironment, "basedpyright-langserver"),
        ...nodeBinLocalCandidates(root, "basedpyright-langserver")
      ],
      args: ["--stdio"],
      cwd: root,
      env
    }, false);
    if (projectPyright) {
      return {
        process: projectPyright.process,
        source: projectPyright.source,
        initialization: pyrightInit(pythonEnvironment?.pythonPath)
      };
    }
    const projectTy = await resolveAndLaunch({
      candidates: pythonEnvironmentToolCandidates(pythonEnvironment, "ty"),
      args: ["server"],
      cwd: root,
      env
    }, false);
    if (projectTy) {
      return { process: projectTy.process, source: projectTy.source };
    }
    const pathPyright = await resolveAndLaunch({
      candidates: ["pyright-langserver", "basedpyright-langserver"],
      args: ["--stdio"],
      cwd: root,
      env
    }, false);
    if (pathPyright) {
      return {
        process: pathPyright.process,
        source: pathPyright.source,
        initialization: pyrightInit(pythonEnvironment?.pythonPath)
      };
    }
    const pathTy = await resolveAndLaunch({
      candidates: ["ty"],
      args: ["server"],
      cwd: root,
      env
    }, false);
    if (pathTy) {
      return { process: pathTy.process, source: pathTy.source };
    }
    const pyrightPath = await ensureTool("pyright", {
      allowInstall: canInstall(options?.allowInstall)
    });
    if (!pyrightPath)
      return void 0;
    source = "managed";
    const binDir = path7.dirname(pyrightPath);
    const isWindows2 = process.platform === "win32";
    const managedCandidates = isWindows2 ? [
      path7.join(binDir, "pyright-langserver.cmd"),
      path7.join(binDir, "pyright-langserver"),
      "pyright-langserver"
    ] : [path7.join(binDir, "pyright-langserver"), "pyright-langserver"];
    const resolved = await resolveAndLaunch({ candidates: managedCandidates, args: ["--stdio"], cwd: root, env }, false);
    if (!resolved)
      return void 0;
    return {
      process: resolved.process,
      source,
      initialization: pyrightInit(pythonEnvironment?.pythonPath)
    };
  }
};
var PythonJediServer = {
  id: "python-jedi",
  idleEviction: "unmeasured",
  name: "Jedi Language Server",
  fallbackFor: "python",
  extensions: KIND_EXTENSIONS["python"],
  root: RootWithFallback(createRootDetector([
    ".git",
    "pyproject.toml",
    "setup.py",
    "setup.cfg",
    "requirements.txt",
    "Pipfile",
    "poetry.lock"
  ])),
  async spawn(root, options) {
    const launched = await resolveAndLaunch({
      candidates: ["jedi-language-server"],
      args: [],
      cwd: root,
      managedToolId: "jedi-language-server"
    }, options?.allowInstall);
    if (!launched)
      return void 0;
    const pythonPath = await detectPythonVenv(root);
    return {
      ...launched,
      initialization: pythonPath ? { workspace: { environmentPath: pythonPath } } : {}
    };
  }
};
var GoServer = {
  id: "go",
  idleEviction: "unmeasured",
  name: "gopls",
  extensions: KIND_EXTENSIONS["go"],
  root: RootWithFallback(WorkspacePriorityRoot([["go.work"], ["go.mod", "go.sum"], [".git"]])),
  async spawn(root, options) {
    const result = await resolveAndLaunch({
      // Canonical-bin discovery (#241): include $GOPATH/bin so a gopls that
      // `go install` dropped there resolves even when it isn't on PATH —
      // which is also the retry target after the runtimeInstall below.
      candidates: goBinCandidates("gopls"),
      args: [],
      cwd: root,
      runtimeInstall: {
        runtimeCommand: "go",
        install: tryGoInstallGopls
      }
    }, options?.allowInstall);
    if (!result)
      return void 0;
    return { ...result, initialization: { ui: { semanticTokens: true } } };
  }
};
function cargoWorkspaceDeclaresMember(workspaceContent, parentDir, childDir) {
  const relativePath = path7.relative(parentDir, childDir).split(path7.sep).join("/");
  if (relativePath === "" || relativePath.startsWith(".."))
    return false;
  const matches = (pattern) => matchesWorkspaceMemberPattern(pattern, relativePath, CARGO_WORKSPACE_MEMBER_DIALECT, () => recordDegradationOnce({
    kind: "workspace-glob-cap",
    subject: String(pattern.length),
    reason: "workspace glob matcher exceeded its memo-table cell cap"
  }));
  const excluded = readCargoWorkspaceExclude(workspaceContent);
  if (excluded.some(matches))
    return false;
  return readCargoWorkspaceMembers(workspaceContent).some(matches);
}
function sessionHoistCeiling(sessionCwd, file) {
  return isSameOrWithin(path7.resolve(sessionCwd), path7.resolve(file)) ? path7.resolve(sessionCwd) : void 0;
}
function RustWorkspaceRoot() {
  const crateRoot = createRootDetector(["Cargo.toml", "Cargo.lock"]);
  return withRootMarkers(async (file) => {
    const root = await crateRoot(file);
    if (!root)
      return void 0;
    const sessionCwd = process.cwd();
    const stop = sessionHoistCeiling(sessionCwd, file);
    let current = root;
    const fsRoot = path7.parse(current).root;
    while (true) {
      if (stop !== void 0 && current === stop)
        break;
      const parent = path7.dirname(current);
      if (parent === current || parent === fsRoot)
        break;
      const parentCargoPath = path7.join(parent, "Cargo.toml");
      const parentCargoContent = await readTextFileOrUndefined(parentCargoPath);
      if (parentCargoContent !== void 0 && hasCargoWorkspaceTable(parentCargoContent)) {
        if (cargoWorkspaceDeclaresMember(parentCargoContent, parent, root)) {
          return enforceLspRootCeiling(parent, sessionCwd, file);
        }
        break;
      }
      current = parent;
    }
    return root;
  }, crateRoot.rootMarkers ?? []);
}
function stripXmlComments(content) {
  let result = content;
  let previous;
  do {
    previous = result;
    result = result.replace(/<!--[\s\S]*?-->/g, "");
  } while (result !== previous);
  return result;
}
async function declaresMavenModule(parentDir, parentPomPath, childDir) {
  try {
    const content = stripXmlComments(await readFile(parentPomPath, "utf-8"));
    const modulesBlock = content.match(/<modules>([\s\S]*?)<\/modules>/);
    if (!modulesBlock)
      return false;
    const resolvedChild = path7.resolve(childDir);
    for (const match of modulesBlock[1].matchAll(/<module>\s*([^<]+?)\s*<\/module>/g)) {
      if (pathsEqual(path7.resolve(parentDir, match[1]), resolvedChild))
        return true;
    }
    return false;
  } catch {
    return false;
  }
}
function JavaWorkspaceRoot() {
  const moduleRoot = createRootDetector([
    "pom.xml",
    "build.gradle",
    ".classpath"
  ]);
  return withRootMarkers(async (file) => {
    const root = await moduleRoot(file);
    if (!root)
      return void 0;
    if (!await markerExists(root, "pom.xml"))
      return root;
    const sessionCwd = process.cwd();
    const stop = sessionHoistCeiling(sessionCwd, file);
    let current = root;
    let lastHop = root;
    const fsRoot = path7.parse(current).root;
    while (true) {
      if (stop !== void 0 && current === stop)
        break;
      const parent = path7.dirname(current);
      if (parent === current || parent === fsRoot)
        break;
      if (await markerExists(parent, "pom.xml")) {
        const parentPom = path7.join(parent, "pom.xml");
        if (!await declaresMavenModule(parent, parentPom, lastHop))
          break;
        current = parent;
        lastHop = parent;
        continue;
      }
      current = parent;
    }
    return enforceLspRootCeiling(lastHop, sessionCwd, file);
  }, moduleRoot.rootMarkers ?? []);
}
var RustServer = {
  id: "rust",
  idleEviction: "unmeasured",
  // Measured (#3750): rust-analyzer answers an empty result for a detached file.
  requiresProjectRoot: true,
  name: "rust-analyzer",
  extensions: KIND_EXTENSIONS["rust"],
  // No FileDirRoot fallback (#201): rust-analyzer is a heavy workspace server
  // that is useless without a Cargo manifest. With the fallback, every .rs file
  // written before a Cargo.toml exists resolved to its OWN directory as the
  // root, and since clients dedup by `${serverId}:${root}`, each directory
  // spawned a separate rust-analyzer (one per file/dir during scaffolding).
  // Returning undefined here skips the spawn until a Cargo.toml gives a stable,
  // shared crate root — then all files share one server.
  root: RustWorkspaceRoot(),
  async spawn(root, options) {
    const result = await resolveAndLaunch({
      candidates: cargoBinCandidates("rust-analyzer"),
      args: [],
      cwd: root,
      managedToolId: "rust-analyzer"
    }, options?.allowInstall);
    if (!result)
      return void 0;
    return {
      ...result,
      initialization: {
        cargo: { buildScripts: { enable: true } },
        procMacro: { enable: true },
        diagnostics: { enable: true }
      }
    };
  }
};
var RubyServer = {
  id: "ruby",
  idleEviction: "unmeasured",
  name: "Ruby LSP",
  extensions: KIND_EXTENSIONS["ruby"],
  root: RootWithFallback(PriorityRoot([["Gemfile", ".ruby-version"], [".git"]])),
  // Ruby LSP may need extra time to finish composed-bundle setup before it can
  // answer initialize/documentSymbol on cold start.
  initializeTimeoutMs: 3e4,
  clientWaitTimeoutMs: 3e4,
  async spawn(root, options) {
    const rubylsp = await resolveAndLaunch({
      candidates: ["ruby-lsp", ...rubyBinCandidates("ruby-lsp")],
      args: [],
      cwd: root,
      runtimeInstall: {
        runtimeCommand: "gem",
        install: () => tryGemInstall("ruby-lsp"),
        retryCandidates: ["ruby-lsp", ...rubyBinCandidates("ruby-lsp")]
      }
    }, options?.allowInstall);
    if (rubylsp)
      return rubylsp;
    const solargraph = await resolveAndLaunch({
      candidates: ["solargraph", ...rubyBinCandidates("solargraph")],
      args: ["stdio"],
      cwd: root
    }, false);
    if (solargraph)
      return solargraph;
    return resolveAndLaunch({
      candidates: ["rubocop", ...rubyBinCandidates("rubocop")],
      args: ["--lsp"],
      cwd: root
    }, false);
  }
};
var PHPServer = {
  id: "php",
  idleEviction: "transparent",
  name: "Intelephense",
  extensions: KIND_EXTENSIONS["php"],
  root: RootWithFallback(createRootDetector(["composer.json", "composer.lock"])),
  async spawn(root, options) {
    const result = await resolveAndLaunch({
      candidates: nodeBinCandidates(root, "intelephense"),
      args: ["--stdio"],
      cwd: root,
      managedToolId: "intelephense"
    }, options?.allowInstall);
    if (!result)
      return void 0;
    return {
      ...result,
      initialization: {
        storagePath: path7.join(getGlobalPiLensDir(), "intelephense")
      }
    };
  }
};
function buildPsesArgs(bundleDir) {
  const script = path7.join(bundleDir, "PowerShellEditorServices", "Start-EditorServices.ps1");
  const sessionDir = path7.join(getGlobalPiLensDir(), "pses", `${process.pid}-${Date.now()}`);
  mkdirSync2(sessionDir, { recursive: true });
  const logPath = path7.join(sessionDir, "pses.log");
  const sessionDetailsPath = path7.join(sessionDir, "session.json");
  return [
    "-NoLogo",
    "-NoProfile",
    "-NonInteractive",
    // Unsigned bundled script + mark-of-the-web on Windows — Bypass so it runs;
    // ignored by non-Windows pwsh.
    "-ExecutionPolicy",
    "Bypass",
    "-File",
    script,
    "-HostName",
    "pi-lens",
    "-HostProfileId",
    "pi-lens",
    "-HostVersion",
    "1.0.0",
    "-BundledModulesPath",
    bundleDir,
    "-LogPath",
    logPath,
    "-LogLevel",
    "Warning",
    "-SessionDetailsPath",
    sessionDetailsPath,
    "-Stdio",
    "-LanguageServiceOnly"
  ];
}
var PowerShellServer = {
  id: "powershell",
  idleEviction: "unmeasured",
  name: "PowerShell Editor Services",
  extensions: KIND_EXTENSIONS["powershell"],
  // Index at the workspace (script modules reference siblings); fall back to the
  // file dir.
  root: RootWithFallback(createRootDetector([".git"])),
  spawn(root, options) {
    return resolveAndLaunchBundle({
      runtimeCandidates: ["pwsh", "powershell"],
      bundleToolId: "powershell-editor-services",
      cwd: root,
      args: buildPsesArgs
    }, options?.allowInstall);
  }
};
var CSharpServer = {
  id: "csharp",
  idleEviction: "unmeasured",
  // Documented (#3750): csharp-ls needs a solution or project.
  requiresProjectRoot: true,
  name: "csharp-ls",
  extensions: KIND_EXTENSIONS["csharp"],
  // No FileDirRoot fallback (#201): csharp-ls is a workspace server and should
  // not spawn once per source directory before a .sln/.csproj exists. Glob root
  // markers match real project filenames such as `App.csproj` / `App.sln`
  // (shared marker list — see file-kinds.ts, refs #895).
  root: createRootDetector([...DOTNET_CSHARP_ROOT_MARKERS]),
  async spawn(root, options) {
    const candidates = dotnetToolCandidates("csharp-ls");
    return resolveAndLaunch({
      candidates,
      args: [],
      cwd: root,
      runtimeInstall: {
        runtimeCommand: "dotnet",
        install: () => tryDotnetToolInstall("csharp-ls"),
        retryCandidates: candidates
      }
    }, options?.allowInstall);
  }
};
var OmniSharpServer = createInteractiveServer({
  id: "omnisharp",
  name: "OmniSharp",
  // Documented (#3750): OmniSharp needs a solution or project.
  requiresProjectRoot: true,
  fallbackFor: "csharp",
  extensions: KIND_EXTENSIONS["csharp"],
  root: createRootDetector([...DOTNET_CSHARP_ROOT_MARKERS]),
  language: "csharp",
  command: "OmniSharp",
  args: ["--languageserver"]
});
var FSharpServer = {
  id: "fsharp",
  idleEviction: "unmeasured",
  // Documented (#3750): FSAutocomplete needs a project.
  requiresProjectRoot: true,
  name: "FSAutocomplete",
  extensions: KIND_EXTENSIONS["fsharp"],
  root: createRootDetector([...DOTNET_FSHARP_ROOT_MARKERS]),
  async spawn(root, options) {
    const candidates = dotnetToolCandidates("fsautocomplete");
    return resolveAndLaunch({
      candidates,
      args: [],
      cwd: root,
      runtimeInstall: {
        runtimeCommand: "dotnet",
        install: () => tryDotnetToolInstall("fsautocomplete"),
        retryCandidates: candidates
      }
    }, options?.allowInstall);
  }
};
var JavaServer = createInteractiveServer({
  id: "java",
  name: "JDT Language Server",
  extensions: KIND_EXTENSIONS["java"],
  root: RootWithFallback(JavaWorkspaceRoot()),
  language: "java",
  command: () => process.env.JDTLS_PATH || "jdtls",
  args: (root) => createLombokJdtlsArgs(root),
  runtime: "java"
});
var KotlinServer = {
  id: "kotlin",
  idleEviction: "unmeasured",
  name: "Kotlin Language Server",
  extensions: KIND_EXTENSIONS["kotlin"],
  root: RootWithFallback(createRootDetector(["build.gradle.kts", "build.gradle", "pom.xml"])),
  async spawn(root, options) {
    return resolveAndLaunch({
      candidates: ["kotlin-lsp", "kotlin-language-server"],
      args: [],
      cwd: root,
      // #3400: a managed fwcd install when no PATH candidate launches. The
      // managed shim is consulted FIRST, but only exists once no PATH
      // candidate answered, so a PATH `kotlin-lsp` wins on a box that had
      // it before the managed install; one added afterwards does not.
      managedToolId: "kotlin-language-server"
    }, options?.allowInstall);
  }
};
var SwiftServer = createInteractiveServer({
  id: "swift",
  name: "SourceKit-LSP",
  extensions: KIND_EXTENSIONS["swift"],
  root: createRootDetector(["Package.swift"]),
  language: "swift",
  command: "sourcekit-lsp"
});
var DartServer = createInteractiveServer({
  id: "dart",
  name: "Dart Analysis Server",
  extensions: KIND_EXTENSIONS["dart"],
  root: RootWithFallback(createRootDetector(["pubspec.yaml"])),
  language: "dart",
  command: "dart",
  args: ["language-server", "--protocol=lsp"]
});
function createTreeBinaryServer(spec) {
  return {
    id: spec.id,
    name: spec.name,
    extensions: spec.extensions,
    idleEviction: spec.idleEviction ?? "unmeasured",
    root: spec.root,
    spawn(root, options) {
      return resolveAndLaunchTreeBinary({
        candidates: [spec.binaryName],
        bundleToolId: spec.binaryName,
        binRelPath: spec.binRelPath,
        cwd: root,
        args: spec.args ?? []
      }, options?.allowInstall);
    }
  };
}
var LuaServer = createTreeBinaryServer({
  id: "lua",
  name: "Lua Language Server",
  extensions: KIND_EXTENSIONS["lua"],
  root: createRootDetector([".luarc.json", ".luacheckrc"]),
  binaryName: "lua-language-server",
  binRelPath: "bin/lua-language-server"
});
var CppServer = createTreeBinaryServer({
  id: "cpp",
  idleEviction: "transparent",
  name: "clangd",
  extensions: KIND_EXTENSIONS["cxx"],
  root: RootWithFallback(createRootDetector([
    "compile_commands.json",
    ".clangd",
    "CMakeLists.txt",
    "Makefile"
  ])),
  binaryName: "clangd",
  binRelPath: "bin/clangd",
  args: ["--background-index"]
});
var ZigServer = {
  id: "zig",
  idleEviction: "unmeasured",
  name: "ZLS",
  extensions: KIND_EXTENSIONS["zig"],
  root: RootWithFallback(createRootDetector(["build.zig"])),
  spawn(root, options) {
    return resolveAndLaunch({
      candidates: ["zls"],
      args: [],
      cwd: root,
      managedToolId: "zls"
    }, options?.allowInstall);
  }
};
var HaskellServer = createInteractiveServer({
  id: "haskell",
  name: "Haskell Language Server",
  extensions: KIND_EXTENSIONS["haskell"],
  root: createRootDetector(["stack.yaml", "cabal.project", "*.cabal"]),
  language: "haskell",
  command: "haskell-language-server-wrapper",
  args: ["--lsp"]
});
var ElixirServer = createInteractiveServer({
  id: "elixir",
  name: "ElixirLS",
  extensions: KIND_EXTENSIONS["elixir"],
  root: RootWithFallback(createRootDetector(["mix.exs"])),
  language: "elixir",
  command: "elixir-ls"
});
var ElixirExpertServer = {
  id: "expert",
  idleEviction: "unmeasured",
  name: "Expert",
  fallbackFor: "elixir",
  extensions: KIND_EXTENSIONS["elixir"],
  root: RootWithFallback(createRootDetector(["mix.exs"])),
  availabilityKey: "expert",
  async spawn(root, options) {
    return resolveAndLaunch({
      candidates: ["expert"],
      args: ["--stdio"],
      cwd: root,
      managedToolId: "expert"
    }, options?.allowInstall);
  },
  autoInstall: async () => Boolean(await ensureTool("expert"))
};
var GleamServer = {
  id: "gleam",
  idleEviction: "unmeasured",
  name: "Gleam LSP",
  extensions: KIND_EXTENSIONS["gleam"],
  root: RootWithFallback(createRootDetector(["gleam.toml"])),
  async spawn(root, options) {
    return resolveAndLaunch({
      candidates: ["gleam"],
      args: ["lsp"],
      cwd: root,
      managedToolId: "gleam"
    }, options?.allowInstall);
  }
};
var TinymistServer = {
  id: "tinymist",
  idleEviction: "unmeasured",
  name: "Tinymist",
  extensions: extensionsForLanguage("typst"),
  root: RootWithFallback(createRootDetector(["typst.toml", ".git"])),
  availabilityKey: "tinymist",
  async spawn(root, options) {
    return resolveAndLaunch({
      candidates: ["tinymist"],
      args: ["lsp"],
      cwd: root,
      managedToolId: "tinymist"
    }, options?.allowInstall);
  }
};
var MarksmanServer = {
  id: "marksman",
  idleEviction: "transparent",
  name: "Marksman",
  extensions: KIND_EXTENSIONS["markdown"],
  // Index at the workspace root so cross-file checks (broken intra-repo links,
  // missing/renamed anchors, heading refs) see the whole tree; fall back to the
  // file's directory when there's no project marker.
  root: RootWithFallback(createRootDetector([".marksman.toml", ".git"])),
  spawn(root, options) {
    return resolveAndLaunch({
      candidates: ["marksman"],
      args: ["server"],
      cwd: root,
      managedToolId: "marksman"
    }, options?.allowInstall);
  }
};
var OCamlServer = createInteractiveServer({
  id: "ocaml",
  name: "ocamllsp",
  extensions: KIND_EXTENSIONS["ocaml"],
  root: createRootDetector(["dune-project", "opam"]),
  language: "ocaml",
  command: "ocamllsp"
});
var ClojureServer = {
  id: "clojure",
  idleEviction: "transparent",
  name: "Clojure LSP",
  extensions: KIND_EXTENSIONS["clojure"],
  root: createRootDetector(["deps.edn", "project.clj"]),
  async spawn(root, options) {
    return resolveAndLaunch({
      candidates: ["clojure-lsp"],
      args: [],
      cwd: root,
      managedToolId: "clojure-lsp"
    }, options?.allowInstall);
  }
};
var CueServer = {
  id: "cue",
  idleEviction: "unmeasured",
  name: "CUE Language Server",
  extensions: KIND_EXTENSIONS["cue"],
  root: RootWithFallback(createRootDetector(["cue.mod", ".git"])),
  async spawn(root, options) {
    return resolveAndLaunch({
      candidates: ["cue"],
      args: ["lsp", "serve"],
      cwd: root,
      managedToolId: "cue"
    }, options?.allowInstall);
  }
};
var TerraformServer = {
  id: "terraform",
  idleEviction: "unmeasured",
  name: "Terraform LSP",
  extensions: KIND_EXTENSIONS["terraform"],
  root: RootWithFallback(createRootDetector([".terraform.lock.hcl", ".terraform"])),
  spawn(root, options) {
    return resolveAndLaunch({
      candidates: ["terraform-ls"],
      args: ["serve"],
      cwd: root,
      managedToolId: "terraform-ls"
    }, options?.allowInstall);
  }
};
var NixServer = createInteractiveServer({
  id: "nix",
  name: "nixd",
  extensions: KIND_EXTENSIONS["nix"],
  root: createRootDetector(["flake.nix"]),
  language: "nix",
  command: "nixd"
});
var BashServer = {
  id: "bash",
  idleEviction: "transparent",
  name: "Bash Language Server",
  // #3968: narrowed from [".bash", ".sh", ".zsh"] — bash-language-server does
  // not support zsh (its own analyzer refuses the dialect), and its ≥5.7.0
  // lint flow even feeds shebang'd zsh to its embedded shellcheck, importing
  // SC1071-class noise through the LSP lane. `shuck` owns `.zsh` when
  // installed; when it is not, the shellcheck runner's dialect gate keeps
  // zsh quiet instead of falling back to a zsh-illiterate analyzer.
  extensions: [".bash", ".sh"],
  root: FileDirRoot,
  // #2194 bounded the installer's own verification at 20s; the dispatch
  // touch's client-wait floor stayed at the shared 5s default, so a cold
  // spawn could still race that budget and read as unavailable even after
  // installer verification cleared it. Match the installer bound (#2169).
  // `initializeTimeoutMs` matches it too: the measured cold start
  // (9,667ms, #2194) has headroom under the unraised 15s default, but
  // leaving the two fields mismatched is the same latent shape that made
  // the Prisma/Vue raise a no-op (fix-round F1, #2233) — a slower host
  // than the one measured would still hit the 15s inner kill despite the
  // caller being willing to wait 20s. Equalizing costs nothing on the
  // success path and removes the mismatch outright.
  clientWaitTimeoutMs: 2e4,
  initializeTimeoutMs: 2e4,
  spawn(root, options) {
    return resolveAndLaunch({
      candidates: nodeBinCandidates(root, "bash-language-server"),
      args: ["start"],
      cwd: root,
      managedToolId: "bash-language-server"
    }, options?.allowInstall);
  }
};
var ShuckServer = createInteractiveServer({
  id: "shuck",
  name: "Shuck",
  extensions: [".zsh"],
  root: FileDirRoot,
  language: "shell",
  command: "shuck",
  args: ["server"]
});
var FishServer = {
  id: "fish",
  idleEviction: "transparent",
  name: "Fish Language Server",
  extensions: KIND_EXTENSIONS["fish"],
  root: RootWithFallback(createRootDetector([".git"])),
  spawn(root, options) {
    return resolveAndLaunch({
      candidates: nodeBinCandidates(root, "fish-lsp"),
      args: ["start", "--stdio"],
      cwd: root,
      managedToolId: "fish-lsp"
    }, options?.allowInstall);
  }
};
var CMakeServer = {
  id: "cmake",
  idleEviction: "unmeasured",
  name: "CMake Language Server",
  // CMake's canonical project file has no .cmake suffix. The configured-server
  // matcher supports exact basenames as well as extensions.
  extensions: [...KIND_EXTENSIONS["cmake"], "CMakeLists.txt"],
  root: RootWithFallback(createRootDetector(["CMakeLists.txt", ".git"])),
  spawn(root, options) {
    return resolveAndLaunch({
      candidates: ["cmake-language-server"],
      args: [],
      cwd: root,
      managedToolId: "cmake-language-server"
    }, options?.allowInstall);
  }
};
var DockerServer = {
  id: "docker",
  idleEviction: "unmeasured",
  name: "Dockerfile Language Server",
  extensions: [".dockerfile", "Dockerfile"],
  root: RootWithFallback(PriorityRoot([
    [
      "docker-compose.yml",
      "docker-compose.yaml",
      "compose.yml",
      "compose.yaml"
    ],
    [".git"]
  ])),
  spawn(root, options) {
    return resolveAndLaunch({
      candidates: nodeBinCandidates(root, "docker-langserver"),
      args: ["--stdio"],
      cwd: root,
      managedToolId: "dockerfile-language-server-nodejs"
    }, options?.allowInstall);
  }
};
var DockerOfficialServer = {
  id: "docker-official",
  idleEviction: "unmeasured",
  name: "Docker Language Server (official)",
  fallbackFor: "docker",
  extensions: [".dockerfile", "Dockerfile"],
  root: RootWithFallback(PriorityRoot([
    [
      "docker-compose.yml",
      "docker-compose.yaml",
      "compose.yml",
      "compose.yaml"
    ],
    [".git"]
  ])),
  spawn(root, options) {
    return resolveAndLaunch({
      candidates: nodeBinCandidates(root, "docker-language-server"),
      args: ["start", "--stdio"],
      cwd: root
    }, options?.allowInstall);
  }
};
var YamlServer = {
  id: "yaml",
  idleEviction: "transparent",
  name: "YAML Language Server",
  extensions: KIND_EXTENSIONS["yaml"],
  root: RootWithFallback(PriorityRoot([
    [".yamllint", "yamllint.yml", "yamllint.yaml", "pyproject.toml"],
    [".git"]
  ])),
  spawn(root, options) {
    return resolveAndLaunch({
      candidates: nodeBinCandidates(root, "yaml-language-server"),
      args: ["--stdio"],
      cwd: root,
      managedToolId: "yaml-language-server"
    }, options?.allowInstall);
  }
};
var JsonServer = {
  id: "json",
  idleEviction: "unmeasured",
  name: "VSCode JSON Language Server",
  extensions: KIND_EXTENSIONS["json"],
  root: RootWithFallback(WorkspacePriorityRoot([
    ["package.json", "tsconfig.json", "jsconfig.json"],
    [".git"]
  ])),
  // See BashServer above: the installer's 20s verification bound (#2194)
  // does not, on its own, raise the dispatch touch's cold-spawn wait floor.
  // Mirror it here so a cold spawn cannot lose that race either (#2169).
  // `initializeTimeoutMs` matches it for the same reason as BashServer
  // above (fix-round F1, #2233).
  clientWaitTimeoutMs: 2e4,
  initializeTimeoutMs: 2e4,
  spawn(root, options) {
    return resolveAndLaunch({
      candidates: ["vscode-json-language-server"],
      args: ["--stdio"],
      cwd: root,
      managedToolId: "vscode-json-language-server"
    }, options?.allowInstall);
  }
};
var HtmlServer = {
  id: "html",
  idleEviction: "transparent",
  name: "VSCode HTML Language Server",
  extensions: KIND_EXTENSIONS["html"],
  root: RootWithFallback(IgnoreHomeRoot(PriorityRoot([["package.json", "index.html", "vite.config.ts"]]))),
  spawn(root, options) {
    return resolveAndLaunch({
      candidates: nodeBinCandidates(root, "vscode-html-language-server"),
      args: ["--stdio"],
      cwd: root,
      managedToolId: "vscode-html-languageserver-bin"
    }, options?.allowInstall);
  }
};
var TomlServer = {
  id: "toml",
  idleEviction: "unmeasured",
  name: "Taplo",
  extensions: KIND_EXTENSIONS["toml"],
  root: RootWithFallback(PriorityRoot([["pyproject.toml", "Cargo.toml", "taplo.toml"], [".git"]])),
  spawn(root, options) {
    return resolveAndLaunch({
      candidates: ["taplo"],
      args: ["lsp", "stdio"],
      cwd: root,
      managedToolId: "taplo"
    }, options?.allowInstall);
  }
};
var PrismaServer = {
  id: "prisma",
  idleEviction: "transparent",
  name: "Prisma Language Server",
  extensions: KIND_EXTENSIONS["prisma"],
  root: RootWithFallback(createRootDetector(["prisma/schema.prisma", "schema.prisma"])),
  // Matches the installer's 40s verification bound above (#2169): the
  // dispatch touch's cold-spawn wait floor defaults to 5s and would
  // otherwise time the client out well before the binary answers.
  // `initializeTimeoutMs` MUST be set alongside it (RubyServer's own
  // precedent below): unset, it falls back to the 15s
  // `INITIALIZE_TIMEOUT_MS` default in `clients/lsp/client.ts`, which
  // hard-kills the child mid-handshake — a real cold Prisma run measured
  // up to 27.3s, so the spawn would die there before this wait ever gets
  // a chance to matter (fix-round F1, #2233).
  clientWaitTimeoutMs: 4e4,
  initializeTimeoutMs: 4e4,
  spawn(root, options) {
    return resolveAndLaunch({
      candidates: nodeBinCandidates(root, "prisma-language-server"),
      args: ["--stdio"],
      cwd: root,
      managedToolId: "@prisma/language-server"
    }, options?.allowInstall);
  }
};
var VueServer = {
  id: "vue",
  idleEviction: "unmeasured",
  name: "Vue Language Server",
  extensions: [".vue"],
  root: RootWithFallback(IgnoreHomeRoot(createRootDetector([
    "package.json",
    "package-lock.json",
    "bun.lockb",
    "bun.lock",
    "pnpm-lock.yaml",
    "yarn.lock"
  ]))),
  // Vue's launcher loads the full language-service bundle before answering,
  // matching the installer's 30s verification bound (#2176). The dispatch
  // touch's cold-spawn wait floor defaults to 5s and needs the same raise so
  // a cold spawn cannot time out there instead. `initializeTimeoutMs` MUST
  // match: unset, it falls back to the 15s default in `clients/lsp/client.ts`
  // and hard-kills a cold spawn (measured up to 22.6s in #2188, 16.2s here)
  // mid-handshake before this wait can matter (fix-round F1, #2233).
  clientWaitTimeoutMs: 3e4,
  initializeTimeoutMs: 3e4,
  async spawn(root, options) {
    const tsserverPath = await findTsserverPath(root, options?.allowInstall);
    const hasPackageJson = existsSync3(path7.join(root, "package.json"));
    const hasNodeModules = existsSync3(path7.join(root, "node_modules"));
    if (hasPackageJson && !hasNodeModules) {
      logSessionStart(`lsp vue: node_modules missing in ${root} \u2014 Vue navigation may be limited. Run: npm install (or pnpm/yarn install) in this project.`);
    }
    const proc = await resolveAndLaunch({
      candidates: nodeBinCandidates(root, "vue-language-server"),
      args: ["--stdio"],
      cwd: root,
      managedToolId: "@vue/language-server"
    }, options?.allowInstall);
    if (!proc)
      return void 0;
    return {
      process: proc.process,
      source: proc.source,
      initialization: tsserverPath ? { typescript: { tsdk: path7.dirname(tsserverPath) } } : void 0
    };
  }
};
var SvelteServer = {
  id: "svelte",
  idleEviction: "unmeasured",
  name: "Svelte Language Server",
  extensions: [".svelte"],
  root: RootWithFallback(IgnoreHomeRoot(createRootDetector([
    "package.json",
    "package-lock.json",
    "bun.lockb",
    "bun.lock",
    "pnpm-lock.yaml",
    "yarn.lock"
  ]))),
  // Matches the installer's 20s verification bound above (#2169): the
  // dispatch touch's cold-spawn wait floor defaults to 5s and would
  // otherwise time the client out well before the binary answers.
  // `initializeTimeoutMs` matches it for the same reason as BashServer
  // above (fix-round F1, #2233).
  clientWaitTimeoutMs: 2e4,
  initializeTimeoutMs: 2e4,
  async spawn(root, options) {
    const tsserverPath = await findTsserverPath(root, options?.allowInstall);
    const proc = await resolveAndLaunch({
      candidates: [
        ...nodeBinCandidates(root, "svelteserver"),
        ...nodeBinCandidates(root, "svelte-language-server")
      ],
      args: ["--stdio"],
      cwd: root,
      managedToolId: "svelte-language-server"
    }, options?.allowInstall);
    if (!proc)
      return void 0;
    return {
      process: proc.process,
      source: proc.source,
      initialization: tsserverPath ? { typescript: { tsdk: path7.dirname(tsserverPath) } } : void 0
    };
  }
};
var CssServer = {
  id: "css",
  idleEviction: "transparent",
  name: "CSS Language Server",
  extensions: KIND_EXTENSIONS["css"],
  root: RootWithFallback(IgnoreHomeRoot(PriorityRoot([
    [
      "package.json",
      "postcss.config.js",
      "tailwind.config.js",
      "vite.config.ts"
    ]
  ]))),
  spawn(root, options) {
    return resolveAndLaunch({
      candidates: nodeBinCandidates(root, "vscode-css-language-server"),
      args: ["--stdio"],
      cwd: root,
      managedToolId: "vscode-css-languageserver"
    }, options?.allowInstall);
  }
};
var OPENGREP_KINDS = [
  "csharp",
  "css",
  "cxx",
  "dart",
  "docker",
  "go",
  "html",
  "java",
  "json",
  "jsts",
  "kotlin",
  "lua",
  "php",
  "python",
  "ruby",
  "rust",
  "shell",
  "swift",
  "terraform",
  "yaml"
];
var OPENGREP_EXTENSIONS = Array.from(new Set(OPENGREP_KINDS.flatMap((k) => KIND_EXTENSIONS[k] ?? [])));
function opengrepInitialization(root) {
  const resolved = resolveOpengrepConfig(root, { enabled: true });
  return {
    scan: {
      configuration: [resolved.configArg ?? "auto"],
      onlyGitDirty: false,
      jobs: 16
    },
    metrics: { enabled: false },
    doHover: false
  };
}
var OpengrepServer = {
  id: "opengrep",
  idleEviction: "transparent",
  name: "Opengrep Security Scanner",
  role: "auxiliary",
  extensions: OPENGREP_EXTENSIONS,
  // Stable per-repo root so ONE warm server serves the whole project (a
  // per-directory root would spawn a fresh server — and re-pay rule load —
  // for every folder).
  root: RootWithFallback(NearestRoot([".git"]), async () => process.cwd()),
  availabilityKey: "opengrep",
  // Rule compilation can take a few seconds on the first scan of a session.
  initializeTimeoutMs: 15e3,
  async spawn(root, options) {
    const launched = await resolveAndLaunch({
      candidates: ["opengrep"],
      args: ["lsp", "--experimental"],
      cwd: root,
      managedToolId: "opengrep"
    }, options?.allowInstall);
    if (!launched)
      return void 0;
    return { ...launched, initialization: opengrepInitialization(root) };
  },
  autoInstall: async () => Boolean(await ensureTool("opengrep"))
};
var AST_GREP_KINDS = [
  "csharp",
  "cxx",
  "css",
  "elixir",
  "go",
  "haskell",
  "html",
  "java",
  "json",
  "jsts",
  "kotlin",
  "lua",
  "nix",
  "php",
  "python",
  "ruby",
  "rust",
  "scala",
  "shell",
  "swift",
  "yaml"
];
var AST_GREP_EXTENSIONS = Array.from(new Set(AST_GREP_KINDS.flatMap((k) => KIND_EXTENSIONS[k] ?? [])));
var AstGrepServer = {
  id: "ast-grep",
  idleEviction: "unmeasured",
  name: "ast-grep structural linter",
  role: "auxiliary",
  extensions: AST_GREP_EXTENSIONS,
  // Attaches everywhere (#239 Phase 2): prefer a project `sgconfig.y[a]ml` root,
  // else the repo root (.git) or cwd — like Opengrep. When there's no project
  // sgconfig the spawn launches with `--config <shipped baseline>` so the team's
  // rules still run; the napi runner steps aside when this server is available
  // (it falls back to napi when the ast-grep binary is absent — Gate B).
  root: RootWithFallback(createRootDetector(["sgconfig.yml", "sgconfig.yaml"]), RootWithFallback(NearestRoot([".git"]), async () => process.cwd())),
  availabilityKey: "ast-grep",
  // #1714: the one auxiliary with a measured wedge ceiling. A `lens_diagnostics
  // mode=full` sweep of 225 files drove this server into an unrecoverable stall
  // twice in two exposures (writes outstanding 5.9 s and 9.4 s, then a forced
  // shutdown that timed out both the request and the exit notify). It re-parses
  // the whole file on every didOpen, so it absorbs a sweep more slowly than the
  // other scanners — hold it to half the shared default.
  notifyInflightLimit: 4,
  // First scan of a session compiles the rules.
  initializeTimeoutMs: 15e3,
  async spawn(root, options) {
    const projectSgconfig = findLocalSgconfig(root);
    let args = ["lsp"];
    if (!projectSgconfig) {
      const baseline = resolveBaselineSgconfig(root);
      if (baseline)
        args = ["lsp", "--config", baseline];
    }
    const nativeExe = resolveAstGrepNativeExe();
    const candidates = nativeExe ? [nativeExe, "ast-grep"] : ["ast-grep"];
    return resolveAndLaunch({
      candidates,
      args,
      cwd: root,
      managedToolId: "ast-grep"
    }, options?.allowInstall);
  },
  autoInstall: async () => Boolean(await ensureTool("ast-grep"))
};
var ZIZMOR_EXTENSIONS = KIND_EXTENSIONS["yaml"];
var ZizmorServer = {
  id: "zizmor",
  idleEviction: "unmeasured",
  name: "zizmor Actions Security Scanner",
  role: "auxiliary",
  extensions: ZIZMOR_EXTENSIONS,
  pathFilter: isZizmorAuditTarget,
  // Stable per-repo root so ONE warm server serves the whole project (like
  // Opengrep) — config + workflow discovery is repo-relative.
  root: RootWithFallback(NearestRoot([".git"]), async () => process.cwd()),
  availabilityKey: "zizmor",
  async spawn(root, options) {
    const ghToken = await resolveZizmorGitHubToken();
    return resolveAndLaunch({
      candidates: ["zizmor"],
      args: ["--lsp"],
      cwd: root,
      managedToolId: "zizmor",
      ...ghToken ? { env: { GH_TOKEN: ghToken } } : {}
    }, options?.allowInstall);
  },
  autoInstall: async () => Boolean(await ensureTool("zizmor"))
};
var TYPOS_EXTENSIONS = Array.from(/* @__PURE__ */ new Set([...OPENGREP_EXTENSIONS, ...KIND_EXTENSIONS["markdown"]]));
function typosInitialization(root) {
  const localConfig = findLocalTyposConfig(root);
  if (localConfig) {
    logLatency({
      type: "phase",
      phase: "typos_config_resolved",
      filePath: root,
      durationMs: 0,
      metadata: { mode: "project_config", configPath: localConfig }
    });
    logSessionStart(`typos config resolved mode=project_config configPath=${localConfig}`);
    return void 0;
  }
  const configPath = resolvePackagePath(import.meta.url, "rules", "typos", "_typos.toml");
  logLatency({
    type: "phase",
    phase: "typos_config_resolved",
    filePath: root,
    durationMs: 0,
    metadata: { mode: "injected_default", configPath }
  });
  logSessionStart(`typos config resolved mode=injected_default configPath=${configPath}`);
  return { config: configPath };
}
var TyposServer = {
  id: "typos",
  idleEviction: "unmeasured",
  name: "typos Spell Checker",
  role: "auxiliary",
  extensions: TYPOS_EXTENSIONS,
  // Stable per-repo root so ONE warm server serves the whole project (like the
  // other auxiliaries) — typos.toml discovery is repo-relative.
  root: RootWithFallback(NearestRoot([".git"]), async () => process.cwd()),
  availabilityKey: "typos-lsp",
  async spawn(root, options) {
    const launched = await resolveAndLaunch({
      candidates: ["typos-lsp"],
      args: [],
      cwd: root,
      managedToolId: "typos-lsp"
    }, options?.allowInstall);
    if (!launched)
      return void 0;
    const initialization = typosInitialization(root);
    return initialization ? { ...launched, initialization } : launched;
  },
  autoInstall: async () => Boolean(await ensureTool("typos-lsp"))
};
var LSP_SERVERS = [
  TypeScriptServer,
  DenoServer,
  PythonServer,
  // pyright / basedpyright — preferred; openFilesOnly avoids cold-start; ty (#717) is a local-only opt-in fallback
  PythonJediServer,
  // fallback when neither pyright nor basedpyright is available
  GoServer,
  RustServer,
  RubyServer,
  PHPServer,
  PowerShellServer,
  // PowerShell Editor Services — pwsh-bootstrapped module bundle (#278)
  CSharpServer,
  OmniSharpServer,
  FSharpServer,
  JavaServer,
  KotlinServer,
  SwiftServer,
  DartServer,
  LuaServer,
  CppServer,
  ZigServer,
  HaskellServer,
  ElixirServer,
  ElixirExpertServer,
  GleamServer,
  TinymistServer,
  MarksmanServer,
  OCamlServer,
  ClojureServer,
  CueServer,
  TerraformServer,
  NixServer,
  BashServer,
  ShuckServer,
  FishServer,
  CMakeServer,
  DockerServer,
  DockerOfficialServer,
  // alternate of docker; acquired only when docker declines (#3939)
  YamlServer,
  JsonServer,
  HtmlServer,
  TomlServer,
  PrismaServer,
  // Web frameworks & styling
  VueServer,
  SvelteServer,
  CssServer,
  // Auxiliary (cross-cutting, diagnostic-only) servers go last — never primary.
  OpengrepServer,
  AstGrepServer,
  ZizmorServer,
  TyposServer
];

export {
  findLocalTyposConfig,
  getAstGrepRuleSources,
  registerRunnerId,
  BUILTIN_SERVER_RUNNER_COVERS,
  describeRootFallback,
  resolveLspServerCwd,
  resetLSPCaseSensitivityState,
  isSameOrWithin,
  enforceLspRootCeiling,
  hasProjectBoundaryMarker,
  resetLspLaunchAvailabilityGeneration,
  resetDirectLspCommandAvailability,
  isDirectLspCommandTemporarilyUnavailable,
  resetClassicTsRepairGuard,
  LSP_SERVERS,
  shouldInitializeSessionRoot,
  isOutsideAllSessionRoots,
  getSessionRootsForTelemetry,
  loadLSPConfig,
  customServerSpecsOf,
  lspConfigOf,
  registerLSPConfig,
  initLSPConfig,
  explainServersForFile,
  getServersForFileWithConfig,
  primaryServerId,
  resolveLspCwdForFile,
  getServerInitOverride,
  getStrategy,
  classifyServerWaitTier,
  classifyCascadeWaitTier
};