UNPKG

pi-lens

Version:

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

2,627 lines • 87.8 kB
import { createRequire as __pilensCreateRequire } from "node:module"; const require = __pilensCreateRequire(import.meta.url);
import {
  tryLazyInstallForFormatter
} from "./chunk-C2AUYXQH.js";
import {
  stripAnsi
} from "./chunk-FC4RP4I2.js";
import {
  OXFMT_SUPPORTED_EXTENSIONS,
  findPSScriptAnalyzerConfigPath,
  getAutoInstallToolIdForFormatter,
  getFormatterPolicyForFile,
  getSmartDefaultFormatterName,
  gradleBlockRanges,
  hasBiomeConfig,
  hasBlackConfig,
  hasClangFormatConfig,
  hasCljfmtConfig,
  hasCmakeFormatConfig,
  hasCsharpierConfig,
  hasDetektConfig,
  hasFantomasConfig,
  hasGolangciConfig,
  hasGoogleJavaFormatConfig,
  hasGradleKtlintPlugin,
  hasKtfmtConfig,
  hasKtlintConfig,
  hasMarkdownlintConfig,
  hasMixFormatConfig,
  hasNearestPackageJsonDependency,
  hasNearestPackageJsonField,
  hasOcamlformatConfig,
  hasOrmoluConfig,
  hasOxfmtConfig,
  hasOxfmtSvelteConfig,
  hasPSScriptAnalyzerConfig,
  hasPhpCsFixerConfig,
  hasPrettierConfig,
  hasRubocopConfig,
  hasRuffConfig,
  hasSqlfluffConfig,
  hasStandardrbConfig,
  hasStyluaConfig,
  hasSwiftformatConfig,
  hasTaploConfig,
  hasTerraformConfig,
  hasVitePlusConfig,
  stripGradleCommentsAndStrings
} from "./chunk-HHYAELLJ.js";
import {
  load
} from "./chunk-QOZXJ4NA.js";
import {
  readTextFileOrUndefined,
  resolveCargoPackageEdition
} from "./chunk-PAK6IDME.js";
import {
  getToolPath
} from "./chunk-OQGFCZJ4.js";
import {
  resolveToolCwd
} from "./chunk-RUOG5D2C.js";
import {
  VENDOR_BIN_DIRS,
  VENV_BIN_DIRS,
  findGlobalBinary,
  findLocalBinUpwards
} from "./chunk-U3VGR4VU.js";
import {
  assertInstallAllowed
} from "./chunk-NTUJEGDZ.js";
import {
  probeToolAsync
} from "./chunk-EOKRQ3SA.js";
import {
  classifyProbeFailure,
  createAvailabilityLatch,
  logAvailabilityDecision,
  startHostStallSampler
} from "./chunk-WONERJTP.js";
import {
  createGenerationSource,
  safeSpawnAsync
} from "./chunk-VN2AXLEX.js";
import {
  TERRAGRUNT_FILENAMES
} from "./chunk-5ZEJHV35.js";
import {
  compareOrdinal,
  getDegradationLedgerGeneration,
  incrementDegradationCount,
  recordDegradationOnce
} from "./chunk-N3YQJI6O.js";
import {
  logExtension
} from "./chunk-O6TQT6RI.js";
import {
  logLatency
} from "./chunk-5THXD6X5.js";
import {
  BoundedFifoMap,
  BoundedLruCache
} from "./chunk-ABBOA7UD.js";
import {
  findNearestContaining,
  findNearestMarkerRoot,
  normalizeMapKey,
  toPosix
} from "./chunk-Q5U6FMDI.js";

// dist/clients/formatters.js
import { AsyncLocalStorage } from "node:async_hooks";
import * as fs2 from "node:fs/promises";
import * as os2 from "node:os";
import * as path4 from "node:path";

// dist/clients/gradle-ktfmt-style.js
import * as os from "node:os";
import * as path from "node:path";
var KTFMT_GRADLE_ROOT_FILES = [
  "build.gradle.kts",
  "build.gradle",
  "settings.gradle.kts",
  "settings.gradle"
];
var MAX_GRADLE_ANCESTOR_HOPS = 16;
var MAX_SPOTLESS_KTFMT_CALL_CHARS = 4096;
var KTFMT_STYLE_CLI_FLAG = {
  google: "--google-style",
  kotlinlang: "--kotlinlang-style"
};
function styleFromKtfmtBlockBody(body) {
  let style;
  let lastIndex = -1;
  for (const [call, candidate] of [
    [/\bgoogleStyle\s*\(\s*\)/g, "google"],
    [/\bkotlinLangStyle\s*\(\s*\)/g, "kotlinlang"]
  ]) {
    for (const match of body.matchAll(call)) {
      if (match.index > lastIndex) {
        lastIndex = match.index;
        style = candidate;
      }
    }
  }
  return style;
}
function findBalancedCallEnd(source, openingParen) {
  let depth = 0;
  const limit = Math.min(source.length, openingParen + MAX_SPOTLESS_KTFMT_CALL_CHARS);
  for (let index = openingParen; index < limit; index += 1) {
    if (source[index] === "(")
      depth += 1;
    if (source[index] !== ")")
      continue;
    depth -= 1;
    if (depth === 0)
      return index + 1;
  }
  return void 0;
}
function styleFromSpotlessKotlinBody(body) {
  let style;
  for (const call of body.matchAll(/\bktfmt\s*(?=\()/g)) {
    const callOpenParen = call.index + call[0].length;
    const closeParen = findBalancedCallEnd(body, callOpenParen);
    if (closeParen === void 0)
      continue;
    const chain = /^\s*((?:\.\s*(?:googleStyle|kotlinlangStyle|dropboxStyle)\s*\(\s*\)\s*)+)/.exec(body.slice(closeParen));
    if (!chain)
      continue;
    const chainBody = chain[1];
    if (!chainBody)
      continue;
    for (const method of chainBody.matchAll(/\.\s*(googleStyle|kotlinlangStyle|dropboxStyle)\s*\(\s*\)/g)) {
      if (method[1] === "googleStyle")
        style = "google";
      if (method[1] === "kotlinlangStyle")
        style = "kotlinlang";
      if (method[1] === "dropboxStyle")
        style = "spotless-dropbox-unsupported";
    }
  }
  return style;
}
var SCOPE_BY_ENCLOSING_BLOCK = {
  subprojects: "descendants",
  allprojects: "both"
};
function scopeOfGradleStyleBlock(range, ranges) {
  const enclosing = ranges.filter((outer) => outer.start < range.start && outer.end >= range.end);
  if (enclosing.length === 0)
    return "own";
  if (enclosing.length > 1)
    return void 0;
  return SCOPE_BY_ENCLOSING_BLOCK[enclosing[0].name];
}
function styleSelectionForGradleBlock(block, source, allBlockRanges, nonSpotlessRanges) {
  if (block.name === "ktfmt") {
    const scope2 = scopeOfGradleStyleBlock(block, allBlockRanges);
    if (!scope2)
      return void 0;
    const style2 = styleFromKtfmtBlockBody(source.slice(block.start, block.end));
    return style2 ? { scope: scope2, style: style2 } : void 0;
  }
  if (block.name !== "kotlin")
    return void 0;
  const enclosingSpotless = [];
  for (const outer of allBlockRanges) {
    if (outer.name === "spotless" && outer.start < block.start && outer.end >= block.end) {
      enclosingSpotless.push(outer);
    }
  }
  if (enclosingSpotless.length !== 1)
    return void 0;
  const scope = scopeOfGradleStyleBlock(block, nonSpotlessRanges);
  if (!scope)
    return void 0;
  const style = styleFromSpotlessKotlinBody(source.slice(block.start, block.end));
  return style ? { scope, style } : void 0;
}
function stylesFromGradleContent(content) {
  const stripped = stripGradleCommentsAndStrings(content);
  const blocks = gradleBlockRanges(stripped);
  const scopeRanges = [];
  for (const range of blocks) {
    if (range.name !== "spotless")
      scopeRanges.push(range);
  }
  const styles = {};
  for (const range of blocks) {
    const selection = styleSelectionForGradleBlock(range, stripped, blocks, scopeRanges);
    if (!selection)
      continue;
    if (selection.scope !== "descendants")
      styles.own = selection.style;
    if (selection.scope !== "own")
      styles.descendants = selection.style;
  }
  return styles;
}
async function stylesForGradleDir(gradleDir) {
  for (const gradleFile of KTFMT_GRADLE_ROOT_FILES) {
    const content = await readTextFileOrUndefined(path.join(gradleDir, gradleFile));
    if (content === void 0)
      continue;
    const styles = stylesFromGradleContent(content);
    if (styles.own || styles.descendants)
      return styles;
  }
  return {};
}
async function resolveKtfmtGradleStyle(filePath, homeDir = os.homedir()) {
  let searchDir = path.dirname(path.resolve(filePath));
  for (let hop = 0; hop < MAX_GRADLE_ANCESTOR_HOPS; hop += 1) {
    const gradleDir = findNearestMarkerRoot(searchDir, KTFMT_GRADLE_ROOT_FILES, {
      homeDir
    });
    if (!gradleDir)
      return void 0;
    const styles = await stylesForGradleDir(gradleDir);
    const style = hop === 0 ? styles.own : styles.descendants;
    if (style === "spotless-dropbox-unsupported") {
      const reason = "Spotless ktfmt dropboxStyle(): pi-lens standalone CLI cannot express the style; falling back to bare ktfmt";
      const firstOccurrence = incrementDegradationCount({
        kind: "formatter-skip",
        subject: "ktfmt:spotless-dropbox",
        reason,
        metadata: { filePath, gradleDir }
      });
      if (firstOccurrence) {
        logExtension({
          subsystem: "format",
          level: "debug",
          message: reason,
          metadata: { filePath, gradleDir }
        });
      }
      return void 0;
    }
    if (style)
      return KTFMT_STYLE_CLI_FLAG[style];
    const parent = path.dirname(gradleDir);
    if (parent === gradleDir)
      return void 0;
    searchDir = parent;
  }
  return void 0;
}

// dist/clients/php-cs-fixer-config.js
import { existsSync } from "node:fs";
import * as path2 from "node:path";
var PHP_CS_FIXER_CONFIG_NAMES = [
  ".php-cs-fixer.php",
  ".php-cs-fixer.dist.php"
];
function resolvePhpCsFixerConfig(filePath) {
  const absolute = path2.resolve(filePath);
  const startDir = resolveToolCwd("formatter", "php-cs-fixer", absolute, {
    cwd: path2.parse(absolute).root,
    allowHomeMarker: true
  }).cwd;
  return PHP_CS_FIXER_CONFIG_NAMES.map((name) => path2.join(startDir, name)).find(existsSync);
}

// dist/clients/dispatch/indent-detect.js
var DEFAULT_INDENTATION = { style: "space", width: 2 };
function detectIndentation(content) {
  const lines = structuralLines(content.split(/\r?\n/));
  const tabs = lines.filter((line) => /^\t+\S/.test(line)).length;
  const spaceCounts = lines.map((line) => line.match(/^ +(?=\S)/)?.[0].length ?? 0).filter((count) => count > 0);
  if (tabs === 0 && spaceCounts.length === 0) {
    return hasDetectableIndentation(content) ? void 0 : DEFAULT_INDENTATION;
  }
  if (tabs > spaceCounts.length)
    return { style: "tab", width: 1 };
  if (spaceCounts.length > tabs) {
    const minimum = Math.min(...spaceCounts);
    const gcd = spaceCounts.reduce((unit, count) => greatestCommonDivisor(unit, count), 0);
    const nonBlank = lines.map((line) => line.match(/^( *)\S/)?.[1]?.length).filter((count) => count !== void 0);
    const hasStructuralBoundary = nonBlank.some((count, index) => {
      const previous = nonBlank[index - 1];
      return count === minimum && previous !== void 0 && previous < minimum;
    });
    if (hasStructuralBoundary && minimum <= 8) {
      return { style: "space", width: minimum };
    }
    if (gcd < minimum && gcd <= 8) {
      return { style: "space", width: gcd };
    }
    return void 0;
  }
  return DEFAULT_INDENTATION;
}
function opensBlockComment(line) {
  const blockAt = line.indexOf("/*");
  if (blockAt < 0)
    return false;
  const lineAt = line.indexOf("//");
  if (lineAt >= 0 && lineAt < blockAt)
    return false;
  return line.indexOf("*/", blockAt + 2) < 0;
}
function blockCommentInteriorMask(lines) {
  const mask = Array.from({ length: lines.length }, () => false);
  let pendingStart = -1;
  for (const [i, line] of lines.entries()) {
    if (pendingStart >= 0) {
      mask[i] = true;
      if (line.includes("*/"))
        pendingStart = -1;
      continue;
    }
    if (opensBlockComment(line))
      pendingStart = i;
  }
  if (pendingStart >= 0) {
    for (let i = pendingStart + 1; i < lines.length; i += 1)
      mask[i] = false;
  }
  return mask;
}
function hasLiveFrame(stack) {
  return stack.some((frame) => frame.kind !== "block");
}
function advanceTemplateState(line, stack) {
  let j = 0;
  while (j < line.length) {
    const top = stack[stack.length - 1];
    if (top?.kind === "template") {
      if (line[j] === "\\") {
        j += 2;
        continue;
      }
      if (line.startsWith("${", j)) {
        stack.push({ kind: "expr", braceDepth: 1 });
        j += 2;
        continue;
      }
      if (line[j] === "`") {
        stack.pop();
        j += 1;
        continue;
      }
      j += 1;
      continue;
    }
    if (top?.kind === "block") {
      const closeAt = line.indexOf("*/", j);
      if (closeAt < 0)
        return;
      stack.pop();
      j = closeAt + 2;
      continue;
    }
    if (line.startsWith("//", j))
      return;
    if (line.startsWith("/*", j)) {
      stack.push({ kind: "block" });
      j += 2;
      continue;
    }
    if (line[j] === "/" && isRegexOpenerPosition(line, j)) {
      const after = skipRegexLiteral(line, j);
      if (after >= 0) {
        j = after;
        continue;
      }
    }
    const ch = line[j];
    if (ch === '"' || ch === "'") {
      j = skipQuoted(line, j, ch);
      continue;
    }
    if (ch === "`") {
      stack.push({ kind: "template" });
      j += 1;
      continue;
    }
    if (top?.kind === "expr") {
      if (ch === "{") {
        top.braceDepth += 1;
        j += 1;
        continue;
      }
      if (ch === "}") {
        top.braceDepth -= 1;
        j += 1;
        if (top.braceDepth === 0)
          stack.pop();
        continue;
      }
    }
    j += 1;
  }
}
function skipQuoted(line, start, quote) {
  let j = start + 1;
  while (j < line.length) {
    if (line[j] === "\\") {
      j += 2;
      continue;
    }
    if (line[j] === quote)
      return j + 1;
    j += 1;
  }
  return line.length;
}
var REGEX_OPENER_PUNCTUATION = /* @__PURE__ */ new Set([
  "(",
  ",",
  "=",
  ":",
  "[",
  "!",
  "&",
  "|",
  "?"
]);
var REGEX_OPENER_KEYWORD = /(?:^|[^A-Za-z0-9_$])(?:return|typeof|case)$/;
function isRegexOpenerPosition(line, at) {
  const before = line.slice(0, at).replace(/[ \t]+$/, "");
  if (before === "")
    return true;
  if (REGEX_OPENER_KEYWORD.test(before))
    return true;
  return REGEX_OPENER_PUNCTUATION.has(before.at(-1) ?? "");
}
var REGEX_FLAG_CHARS = "dgimsuvy";
function skipRegexLiteral(line, start) {
  let j = start + 1;
  let inClass = false;
  while (j < line.length) {
    const c = line[j];
    if (c === "\\") {
      j += 2;
      continue;
    }
    if (inClass) {
      if (c === "]")
        inClass = false;
      j += 1;
      continue;
    }
    if (c === "[") {
      inClass = true;
      j += 1;
      continue;
    }
    if (c === "/") {
      let k = j + 1;
      while (k < line.length && REGEX_FLAG_CHARS.includes(line[k] ?? ""))
        k += 1;
      if (k < line.length && /[A-Za-z0-9_$]/.test(line[k] ?? ""))
        return -1;
      return j + 1;
    }
    j += 1;
  }
  return -1;
}
function templateLiteralInteriorMask(lines) {
  const mask = Array.from({ length: lines.length }, () => false);
  const stack = [];
  let openLine = -1;
  for (const [i, line] of lines.entries()) {
    if (hasLiveFrame(stack)) {
      mask[i] = true;
    } else {
      openLine = i;
    }
    advanceTemplateState(line, stack);
  }
  if (hasLiveFrame(stack)) {
    for (let i = openLine + 1; i < lines.length; i += 1)
      mask[i] = false;
  }
  return mask;
}
function structuralLines(lines) {
  const blockCommentMask = blockCommentInteriorMask(lines);
  const templateMask = templateLiteralInteriorMask(lines);
  return lines.filter((_, index) => !blockCommentMask[index] && !templateMask[index]);
}
function greatestCommonDivisor(left, right) {
  while (right !== 0)
    [left, right] = [right, left % right];
  return left;
}
function hasDetectableIndentation(content) {
  return /^(?:\t+| {1,})\S/m.test(content);
}

// dist/clients/tool-agreement.js
import * as fs from "node:fs";
import * as path3 from "node:path";
var NODE_PACKAGES = {
  biome: "@biomejs/biome",
  eslint: "eslint",
  markdownlint: "markdownlint-cli2",
  oxfmt: "oxfmt",
  oxlint: "oxlint",
  prettier: "prettier",
  stylelint: "stylelint"
};
var NODE_LOCKFILE_PRECEDENCE = [
  "package-lock.json",
  "pnpm-lock.yaml",
  "yarn.lock"
];
var NODE_LOCKFILE_MAX_BYTES = 16 * 1024 * 1024;
var TOOL_AGREEMENT_POLICIES = {
  biome: { bucket: "node-lockfile", withoutEvidence: "decline" },
  eslint: { bucket: "node-lockfile", withoutEvidence: "decline" },
  markdownlint: {
    bucket: "node-lockfile",
    withoutEvidence: "decline",
    check: hasMarkdownlintConfig
  },
  oxfmt: { bucket: "node-lockfile", withoutEvidence: "decline" },
  oxlint: { bucket: "node-lockfile", withoutEvidence: "decline" },
  prettier: { bucket: "node-lockfile", withoutEvidence: "decline" },
  stylelint: { bucket: "node-lockfile", withoutEvidence: "decline" },
  ruff: {
    bucket: "project-config",
    withoutEvidence: "decline",
    check: hasRuffConfig
  },
  black: {
    bucket: "project-config",
    withoutEvidence: "decline",
    check: hasBlackConfig
  },
  sqlfluff: {
    bucket: "project-config",
    withoutEvidence: "decline",
    check: hasSqlfluffConfig
  },
  rubocop: {
    bucket: "project-config",
    withoutEvidence: "decline",
    check: hasRubocopConfig
  },
  standardrb: {
    bucket: "project-config",
    withoutEvidence: "decline",
    check: hasStandardrbConfig
  },
  ktlint: { bucket: "standalone-cli", withoutEvidence: "decline" },
  // typstyle is a standalone formatter binary. Its smart-default policy is
  // autonomous, so PATH or managed-install availability is sufficient
  // agreement; it is not owned by a project manifest (#3037).
  typstyle: { bucket: "standalone-cli", withoutEvidence: "decline" },
  ktfmt: {
    bucket: "project-config",
    withoutEvidence: "decline",
    check: hasKtfmtConfig
  },
  "rust-clippy": {
    bucket: "project-config",
    withoutEvidence: "decline",
    check: (cwd) => hasMarker(cwd, ["Cargo.toml"])
  },
  "dart-analyze": {
    bucket: "project-config",
    withoutEvidence: "decline",
    check: (cwd) => hasMarker(cwd, ["pubspec.yaml"])
  },
  "golangci-lint": {
    bucket: "project-config",
    withoutEvidence: "decline",
    check: hasGolangciConfig
  },
  detekt: {
    bucket: "project-config",
    withoutEvidence: "decline",
    check: hasDetektConfig
  },
  gofmt: {
    bucket: "project-config",
    withoutEvidence: "decline",
    check: (cwd) => hasMarker(cwd, ["go.mod"])
  },
  rustfmt: {
    bucket: "project-config",
    withoutEvidence: "decline",
    check: (cwd) => hasMarker(cwd, ["Cargo.toml"])
  },
  zig: {
    bucket: "project-config",
    withoutEvidence: "decline",
    check: (cwd) => hasMarker(cwd, ["build.zig"])
  },
  dart: {
    bucket: "project-config",
    withoutEvidence: "decline",
    check: (cwd) => hasMarker(cwd, ["pubspec.yaml"])
  },
  shfmt: {
    bucket: "project-config",
    withoutEvidence: "decline",
    check: (cwd) => hasMarker(cwd, [".editorconfig"])
  },
  // nixfmt has no honest project marker. It remains in the population and is
  // declined by the conservative absent-evidence path below.
  nixfmt: { bucket: "project-config", withoutEvidence: "decline" },
  mix: {
    bucket: "project-config",
    withoutEvidence: "decline",
    check: hasMixFormatConfig
  },
  ocamlformat: {
    bucket: "project-config",
    withoutEvidence: "decline",
    check: hasOcamlformatConfig
  },
  "clang-format": {
    bucket: "project-config",
    withoutEvidence: "decline",
    check: hasClangFormatConfig
  },
  gleam: {
    bucket: "project-config",
    withoutEvidence: "decline",
    check: (cwd) => hasMarker(cwd, ["gleam.toml"])
  },
  terraform: {
    bucket: "project-config",
    withoutEvidence: "decline",
    check: hasTerraformConfig
  },
  "terragrunt-hcl": {
    bucket: "project-config",
    withoutEvidence: "decline",
    check: (cwd) => hasMarker(cwd, ["terragrunt.hcl", "terragrunt.hcl.json"])
  },
  "php-cs-fixer": {
    bucket: "project-config",
    withoutEvidence: "decline",
    check: hasPhpCsFixerConfig
  },
  csharpier: {
    bucket: "project-config",
    withoutEvidence: "decline",
    check: hasCsharpierConfig
  },
  fantomas: {
    bucket: "project-config",
    withoutEvidence: "decline",
    check: hasFantomasConfig
  },
  swiftformat: {
    bucket: "project-config",
    withoutEvidence: "decline",
    check: hasSwiftformatConfig
  },
  stylua: {
    bucket: "project-config",
    withoutEvidence: "decline",
    check: hasStyluaConfig
  },
  ormolu: {
    bucket: "project-config",
    withoutEvidence: "decline",
    check: hasOrmoluConfig
  },
  taplo: {
    bucket: "project-config",
    withoutEvidence: "decline",
    check: hasTaploConfig
  },
  "google-java-format": {
    bucket: "project-config",
    withoutEvidence: "decline",
    check: hasGoogleJavaFormatConfig
  },
  cljfmt: {
    bucket: "project-config",
    withoutEvidence: "decline",
    check: hasCljfmtConfig
  },
  "cmake-format": {
    bucket: "project-config",
    withoutEvidence: "decline",
    check: hasCmakeFormatConfig
  },
  "psscriptanalyzer-format": {
    bucket: "project-config",
    withoutEvidence: "decline",
    check: (cwd) => hasMarker(cwd, [
      "PSScriptAnalyzerSettings.psd1",
      "ScriptAnalyzerSettings.psd1"
    ])
  },
  cue: {
    bucket: "project-config",
    withoutEvidence: "decline",
    check: (cwd) => hasMarker(cwd, ["cue.mod"])
  }
};
function hasMarker(cwd, markers) {
  return findNearestMarkerRoot(cwd, markers, {
    boundaries: [".git", ".hg", ".svn"]
  }) !== null;
}
function readJson(filePath) {
  if (!fs.existsSync(filePath))
    return { kind: "missing" };
  try {
    const value = JSON.parse(fs.readFileSync(filePath, "utf8"));
    return value && typeof value === "object" ? { kind: "value", value } : { kind: "unparseable" };
  } catch (error) {
    return {
      kind: error instanceof SyntaxError ? "unparseable" : "unreadable"
    };
  }
}
function declaredRange(pkg, name) {
  for (const field of [
    "dependencies",
    "devDependencies",
    "optionalDependencies"
  ]) {
    const deps = pkg[field];
    if (deps && typeof deps === "object") {
      const range = deps[name];
      if (typeof range === "string")
        return range;
    }
  }
  return void 0;
}
function parseCoreVersion(version) {
  const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(version);
  if (!match)
    return void 0;
  const parts = match.slice(1).map(Number);
  if (parts.some((part) => !Number.isSafeInteger(part)))
    return void 0;
  return parts;
}
function exactOrSimpleRangeMatches(range, version) {
  const clean = range.trim().replace(/^v/, "");
  const actual = parseCoreVersion(version);
  if (!actual)
    return { matches: false, unsupported: version.includes("+") };
  if (/^\d+\.\d+\.\d+$/.test(clean))
    return { matches: clean === version, unsupported: false };
  const caret = clean.match(/^\^(\d+)\.(\d+)\.(\d+)$/);
  if (caret) {
    const [major, minor, patch] = caret.slice(1).map(Number);
    return {
      matches: actual[0] === major && (major !== 0 || actual[1] === minor) && (major !== 0 || minor !== 0 || actual[2] === patch) && (actual[1] > minor || actual[1] === minor && actual[2] >= patch),
      unsupported: false
    };
  }
  const tilde = clean.match(/^~(\d+)\.(\d+)\.(\d+)$/);
  if (tilde) {
    const [major, minor, patch] = tilde.slice(1).map(Number);
    return {
      matches: actual[0] === major && actual[1] === minor && actual[2] >= patch,
      unsupported: false
    };
  }
  return { matches: false, unsupported: true };
}
function pnpmEvidence(doc, packageName, importerKey) {
  if (doc && typeof doc === "object" && !Array.isArray(doc)) {
    const docRecord = doc;
    const importers = docRecord.importers;
    if (importers && typeof importers === "object" && !Array.isArray(importers)) {
      const importer = importers[importerKey];
      if (importer && typeof importer === "object") {
        for (const field of ["dependencies", "devDependencies"]) {
          const deps = importer[field];
          if (deps && typeof deps === "object") {
            const entry = deps[packageName];
            if (entry && typeof entry === "object") {
              return {
                kind: "resolved",
                version: entry.version
              };
            }
          }
        }
      }
      return { kind: "resolved", version: void 0 };
    }
    let sawTopLevelMaps = false;
    let v6version;
    let v6found = false;
    for (const field of ["dependencies", "devDependencies"]) {
      const deps = docRecord[field];
      if (deps && typeof deps === "object" && !Array.isArray(deps)) {
        sawTopLevelMaps = true;
        const entry = deps[packageName];
        if (entry && typeof entry === "object") {
          v6version = entry.version;
          v6found = true;
          break;
        }
      }
    }
    if (v6found || sawTopLevelMaps) {
      return { kind: "resolved", version: v6found ? v6version : void 0 };
    }
  }
  return { kind: "unsupported-shape" };
}
function decideNodeVersionAgreement(tool, packageName, range, version, supplier) {
  const comparison = typeof version === "string" ? exactOrSimpleRangeMatches(range, version) : { matches: false, unsupported: false };
  if (typeof version !== "string" || !comparison.matches) {
    const reasonCode = typeof version === "string" && comparison.unsupported ? "evidence-unsupported" : typeof version === "string" && version.includes("+") ? "evidence-unsupported" : "evidence-unparseable";
    const reason = typeof version === "string" && comparison.unsupported ? `the project declares ${packageName}@${range} in package.json, but the lockfile shape ${packageName}@${version} in ${supplier} or its range is unsupported; tool agreement cannot be established` : typeof version === "string" && parseCoreVersion(version) ? `the project declares ${packageName}@${range} in package.json, but the lockfile resolves ${packageName}@${version} in ${supplier}; agreement disagrees` : `the project declares ${packageName}@${range} in package.json, but ${supplier} does not establish its resolved version; tool agreement cannot be established`;
    return {
      decision: "decline",
      subject: `node:${tool}`,
      reason,
      reasonCode
    };
  }
  return { decision: "established", lockfile: supplier };
}
function readLockfileBounded(filePath) {
  let fd;
  try {
    fd = fs.openSync(filePath, "r");
    const buffer = Buffer.allocUnsafe(Math.min(fs.fstatSync(fd).size, NODE_LOCKFILE_MAX_BYTES) + 1);
    let total = 0;
    while (total < buffer.length) {
      const read = fs.readSync(fd, buffer, total, buffer.length - total, null);
      if (read === 0)
        break;
      total += read;
    }
    return total > NODE_LOCKFILE_MAX_BYTES ? { kind: "too-large" } : { kind: "text", text: buffer.toString("utf8", 0, total) };
  } catch {
    return { kind: "unreadable" };
  } finally {
    if (fd !== void 0) {
      try {
        fs.closeSync(fd);
      } catch {
      }
    }
  }
}
function lockfileReadDecline(tool, lockfile, read) {
  return read.kind === "too-large" ? {
    decision: "decline",
    subject: `node:${tool}`,
    reason: `the project ${lockfile} exceeds the ${NODE_LOCKFILE_MAX_BYTES}-byte lockfile read bound; tool agreement cannot be established`,
    reasonCode: "evidence-too-large"
  } : {
    decision: "decline",
    subject: `node:${tool}`,
    reason: `the project ${lockfile} is unreadable; tool agreement cannot be established`,
    reasonCode: "evidence-unreadable"
  };
}
function stripPnpmPeerSuffix(version) {
  const core = /^\d+\.\d+\.\d+/.exec(version)?.[0];
  if (!core || core.length === version.length)
    return version;
  let depth = 0;
  for (let i = core.length; i < version.length; i += 1) {
    const char = version[i];
    if (char === "(") {
      depth += 1;
    } else if (char === ")") {
      if (depth === 0 || version[i - 1] === "(")
        return version;
      depth -= 1;
    } else if (depth === 0) {
      return version;
    }
  }
  return depth === 0 ? core : version;
}
function pnpmAgreement(tool, packageName, range, root, lockDir) {
  const filePath = path3.join(lockDir, "pnpm-lock.yaml");
  const read = readLockfileBounded(filePath);
  if (read.kind !== "text") {
    return lockfileReadDecline(tool, "pnpm-lock.yaml", read);
  }
  let doc;
  try {
    doc = load(read.text);
  } catch {
    return {
      decision: "decline",
      subject: `node:${tool}`,
      reason: "the project pnpm-lock.yaml is unparseable; tool agreement cannot be established",
      reasonCode: "evidence-unparseable"
    };
  }
  const importerKey = lockDir === path3.resolve(root) ? "." : toPosix(path3.relative(lockDir, root));
  const evidence = pnpmEvidence(doc, packageName, importerKey);
  if (evidence.kind === "unsupported-shape") {
    return {
      decision: "decline",
      subject: `node:${tool}`,
      reason: "the project pnpm-lock.yaml has an unsupported lockfile shape; tool agreement cannot be established",
      reasonCode: "evidence-unsupported"
    };
  }
  return decideNodeVersionAgreement(tool, packageName, range, typeof evidence.version === "string" ? stripPnpmPeerSuffix(evidence.version) : evidence.version, "pnpm-lock.yaml");
}
function yarnV1Blocks(text, packageName) {
  const found = [];
  for (const block of text.split(/\n[ \t]*\n/)) {
    const lines = block.split("\n");
    const header = (lines[0] ?? "").trim();
    if (!header.endsWith(":"))
      continue;
    const prefix = `${packageName}@`;
    const descriptors = [];
    for (const rawPart of header.slice(0, -1).split(",")) {
      const part = rawPart.trim().replace(/^"|"$/g, "").trim();
      if (part.startsWith(prefix) && !part.slice(prefix.length).includes(":")) {
        descriptors.push(part);
      }
    }
    if (descriptors.length === 0)
      continue;
    let version;
    for (const line of lines.slice(1)) {
      const match = /^\s*version\s+"([^"]*)"/.exec(line);
      if (match) {
        version = match[1];
        break;
      }
    }
    found.push({ descriptors, version });
  }
  return found;
}
function yarnBerryEntries(doc, packageName) {
  if (!doc || typeof doc !== "object" || Array.isArray(doc))
    return void 0;
  const docRecord = doc;
  if (!docRecord.__metadata || typeof docRecord.__metadata !== "object")
    return void 0;
  const found = [];
  const marker = "@npm:";
  for (const [key, entry] of Object.entries(docRecord)) {
    const unquoted = key.replace(/^"|"$/g, "");
    const at = unquoted.lastIndexOf(marker);
    if (at < 0)
      continue;
    if (unquoted.slice(0, at) !== packageName)
      continue;
    const version = entry && typeof entry === "object" ? entry.version : void 0;
    found.push({ descriptors: [unquoted.slice(at + marker.length)], version });
  }
  return found;
}
function selectNodeCandidate(candidates, range) {
  for (const candidate of candidates) {
    if (candidate.descriptors.some((descriptor) => descriptor === range)) {
      return typeof candidate.version === "string" ? candidate.version : void 0;
    }
  }
  const first = candidates.length > 0 ? candidates[0]?.version : void 0;
  return typeof first === "string" ? first : void 0;
}
function yarnAgreement(tool, packageName, range, filePath) {
  const read = readLockfileBounded(filePath);
  if (read.kind !== "text")
    return lockfileReadDecline(tool, "yarn.lock", read);
  const raw = read.text.replace(/\r\n?/g, "\n");
  let doc;
  let yamlOk = true;
  try {
    doc = load(raw);
  } catch {
    yamlOk = false;
  }
  if (yamlOk) {
    const berry = yarnBerryEntries(doc, packageName);
    if (berry) {
      return decideNodeVersionAgreement(tool, packageName, range, selectNodeCandidate(berry, range), "yarn.lock");
    }
  }
  const v1 = yarnV1Blocks(raw, packageName);
  if (v1.length > 0) {
    return decideNodeVersionAgreement(tool, packageName, range, selectNodeCandidate(v1, range), "yarn.lock");
  }
  if (!yamlOk) {
    return {
      decision: "decline",
      subject: `node:${tool}`,
      reason: "the project yarn.lock is unparseable; tool agreement cannot be established",
      reasonCode: "evidence-unparseable"
    };
  }
  return {
    decision: "decline",
    subject: `node:${tool}`,
    reason: "the project yarn.lock has an unsupported lockfile shape; tool agreement cannot be established",
    reasonCode: "evidence-unsupported"
  };
}
function nodeAgreement(tool, root) {
  const packageName = NODE_PACKAGES[tool];
  if (!packageName)
    return void 0;
  const pkg = readJson(path3.join(root, "package.json"));
  if (pkg.kind === "unreadable" || pkg.kind === "unparseable") {
    return {
      decision: "decline",
      subject: `node:${tool}`,
      reason: `the project package.json is ${pkg.kind}; tool agreement cannot be established`,
      reasonCode: `evidence-${pkg.kind}`
    };
  }
  const range = pkg.kind === "value" ? declaredRange(pkg.value, packageName) : void 0;
  if (!range)
    return void 0;
  const lockDir = findNearestMarkerRoot(root, [...NODE_LOCKFILE_PRECEDENCE], {
    boundaries: [".git", ".hg", ".svn"]
  });
  if (!lockDir) {
    return {
      decision: "decline",
      subject: `node:${tool}`,
      reason: `the project declares ${packageName}@${range} in package.json, but no lockfile (package-lock.json, pnpm-lock.yaml, yarn.lock) establishes its resolved version; tool agreement cannot be established`,
      reasonCode: "evidence-absent"
    };
  }
  const supplier = fs.existsSync(path3.join(lockDir, "package-lock.json")) ? "package-lock.json" : fs.existsSync(path3.join(lockDir, "pnpm-lock.yaml")) ? "pnpm-lock.yaml" : "yarn.lock";
  if (supplier === "package-lock.json") {
    const lock = readJson(path3.join(lockDir, "package-lock.json"));
    if (lock.kind === "unreadable" || lock.kind === "unparseable") {
      return {
        decision: "decline",
        subject: `node:${tool}`,
        reason: `the project package-lock.json is ${lock.kind}; tool agreement cannot be established`,
        reasonCode: `evidence-${lock.kind}`
      };
    }
    const packages = lock.kind === "value" ? lock.value.packages : void 0;
    const entry = packages && typeof packages === "object" ? packages[`node_modules/${packageName}`] : void 0;
    const version = entry && typeof entry === "object" ? entry.version : void 0;
    const comparison = typeof version === "string" ? exactOrSimpleRangeMatches(range, version) : { matches: false, unsupported: false };
    if (typeof version !== "string" || !comparison.matches) {
      const reasonCode = typeof version === "string" && comparison.unsupported ? "evidence-unsupported" : typeof version === "string" && version.includes("+") ? "evidence-unsupported" : "evidence-unparseable";
      const reason = typeof version === "string" && comparison.unsupported ? `the project declares ${packageName}@${range}, but the lockfile shape ${packageName}@${version} or its range is unsupported; tool agreement cannot be established` : typeof version === "string" && parseCoreVersion(version) ? `the project declares ${packageName}@${range} in package.json, but the lockfile resolves ${packageName}@${version} in package-lock.json; agreement disagrees` : `the project declares ${packageName}@${range} in package.json, but package-lock.json does not establish its resolved version; tool agreement cannot be established`;
      return {
        decision: "decline",
        subject: `node:${tool}`,
        reason,
        reasonCode
      };
    }
    return { decision: "established", lockfile: supplier };
  }
  if (supplier === "pnpm-lock.yaml") {
    return pnpmAgreement(tool, packageName, range, root, lockDir);
  }
  return yarnAgreement(tool, packageName, range, path3.join(lockDir, "yarn.lock"));
}
function establishToolAgreement(tool, cwd) {
  const key = `${getDegradationLedgerGeneration()}\0${path3.resolve(cwd)}\0${tool}`;
  const cached = agreementCache.get(key);
  if (cached)
    return cached;
  agreementResolutionCount += 1;
  const policy = TOOL_AGREEMENT_POLICIES[tool];
  if (!policy) {
    const agreement2 = {
      decision: "decline",
      subject: `tool:${tool}`,
      reason: "the autonomous writer is not registered with a project-evidence policy; tool agreement cannot be established",
      reasonCode: "evidence-unsupported"
    };
    agreementCache.set(key, agreement2);
    return agreement2;
  }
  let agreement = { decision: "established" };
  if (tool === "ktlint") {
    const ownership = hasGradleKtlintPlugin(cwd);
    if (ownership.kind === "owned" || hasKtlintConfig(cwd)) {
      agreement = {
        decision: "decline",
        subject: "kotlin:gradle-ktlint",
        reason: "the project resolves ktlint through Gradle, so CLI agreement cannot be established from project data",
        reasonCode: "evidence-unsupported"
      };
    } else if (ownership.kind === "indeterminate") {
      agreement = {
        decision: "decline",
        subject: "kotlin:gradle-ktlint",
        reason: "Gradle ownership evidence is unreadable or exceeded its scan budget; tool agreement cannot be established",
        reasonCode: "evidence-unreadable"
      };
    }
  }
  if (agreement.decision === "established" && policy.bucket === "node-lockfile") {
    const root = findNearestMarkerRoot(cwd, ["package.json"], {
      boundaries: [".git", ".hg", ".svn"]
    });
    const node = root ? nodeAgreement(tool, root) : void 0;
    agreement = node ?? {
      decision: "decline",
      subject: `node:${tool}`,
      reason: "the project has no package declaration and lockfile evidence for this tool",
      reasonCode: "evidence-absent"
    };
  } else if (agreement.decision === "established" && policy.bucket === "project-config" && (!policy.check || !policy.check(cwd))) {
    agreement = {
      decision: "decline",
      subject: `project:${tool}`,
      reason: "the project has no readable configuration or manifest evidence selecting this tool",
      reasonCode: "evidence-absent"
    };
  }
  if (agreement.decision === "established" && !policy.bucket) {
    agreement = {
      decision: "decline",
      subject: `tool:${tool}`,
      reason: "the tool evidence bucket is unsupported",
      reasonCode: "evidence-unsupported"
    };
  }
  agreementCache.set(key, agreement);
  return agreement;
}
var agreementCache = new BoundedFifoMap(512);
var agreementResolutionCount = 0;

// dist/clients/formatters.js
async function tryLazyInstallFormatterTool(tool, cwd) {
  const ok = await tryLazyInstallForFormatter(tool, cwd);
  if (ok)
    resetWhichLatches();
  return ok;
}
var SKIP_FORMATTING = "skip-formatting";
var FORMATTER_UNAVAILABLE = "formatter-unavailable";
var FORMATTER_AGREEMENT_UNAVAILABLE = "formatter-agreement-unavailable";
async function fileExists(filePath) {
  try {
    await fs2.access(filePath);
    return true;
  } catch {
    return false;
  }
}
async function findUp(targets, startDir, stopDir = path4.parse(startDir).root) {
  const found = [];
  let currentDir = startDir;
  while (currentDir !== stopDir) {
    let entries;
    try {
      entries = await fs2.readdir(currentDir);
    } catch {
      entries = [];
    }
    if (entries.length > 0) {
      const foldCase = process.platform === "win32" || process.platform === "darwin";
      const entrySet = new Set(foldCase ? entries.map((e) => e.toLowerCase()) : entries);
      for (const target of targets) {
        if (target.includes("/") || target.includes("\\"))
          continue;
        if (entrySet.has(foldCase ? target.toLowerCase() : target)) {
          const candidate = path4.join(currentDir, target);
          if (await fileExists(candidate))
            found.push(candidate);
        }
      }
    }
    for (const target of targets) {
      if (!target.includes("/") && !target.includes("\\"))
        continue;
      const candidate = path4.join(currentDir, target);
      if (await fileExists(candidate))
        found.push(candidate);
    }
    const parent = path4.dirname(currentDir);
    if (parent === currentDir)
      break;
    currentDir = parent;
  }
  return found;
}
function _findUpForTests(targets, startDir, stopDir) {
  return findUp(targets, startDir, stopDir);
}
var WHICH_BUDGET_MS = 5e3;
var whichLatchByCommand = /* @__PURE__ */ new Map();
var whichTransientCommands = /* @__PURE__ */ new Set();
var probedCommandsStorage = new AsyncLocalStorage();
async function detectCandidate(formatter, cwd) {
  const probed = /* @__PURE__ */ new Set();
  let detected = false;
  let error;
  try {
    detected = await probedCommandsStorage.run(probed, () => formatter.detect(cwd));
  } catch (err) {
    error = err;
  }
  const stalledCommands = [...probed].filter((command) => whichTransientCommands.has(command));
  return {
    detected,
    ...error !== void 0 && { error },
    outcome: {
      name: formatter.name,
      verdict: detected ? "available" : stalledCommands.length > 0 ? "unreachable" : "missing",
      stalledCommands
    }
  };
}
function resetWhichLatches() {
  whichLatchByCommand.clear();
  whichTransientCommands.clear();
  cooldownRecordedForRetryAtMs.clear();
}
var cooldownRecordedForRetryAtMs = /* @__PURE__ */ new Map();
function noteCooldownServedVerdict(command, latch) {
  if (latch.getOutcome() !== "transient")
    return;
  const retryAtMs = latch.getRetryAtMs();
  if (retryAtMs <= 0)
    return;
  if (cooldownRecordedForRetryAtMs.get(command) === retryAtMs)
    return;
  cooldownRecordedForRetryAtMs.set(command, retryAtMs);
  const cause = latch.getCause();
  if (cause === null) {
    logExtension({
      subsystem: "format",
      message: `which latch: transient outcome with no cause for ${command}; cooldown-served record dropped`,
      metadata: { tool: command, retryAtMs }
    });
    return;
  }
  logAvailabilityDecision({
    tool: command,
    verdict: "unavailable",
    outcome: "transient",
    cause,
    elapsedMs: 0,
    latched: false,
    retryAfterMs: Math.max(1, retryAtMs - Date.now()),
    budgetMs: WHICH_BUDGET_MS,
    servedFromCooldown: true,
    // 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"
  });
}
async function which(command) {
  probedCommandsStorage.getStore()?.add(command);
  let entry = whichLatchByCommand.get(command);
  if (!entry) {
    entry = { latch: createAvailabilityLatch(), resolved: null };
    whichLatchByCommand.set(command, entry);
  }
  const memo = entry.latch.read();
  if (memo !== null) {
    if (!memo)
      noteCooldownServedVerdict(command, entry.latch);
    return memo ? entry.resolved : null;
  }
  const stallSampler = startHostStallSampler();
  const startedAt = Date.now();
  const result = await safeSpawnAsync(process.platform === "win32" ? "where" : "which", [command], { timeout: WHICH_BUDGET_MS });
  const hostStallMs = stallSampler.stop();
  const elapsedMs = Date.now() - startedAt;
  const resolved = !result.error && result.status === 0 ? result.stdout?.trim().split(/\r?\n/)[0] ?? null : null;
  if (resolved) {
    entry.resolved = resolved;
    entry.latch.noteAvailable();
    whichTransientCommands.delete(command);
    cooldownRecordedForRetryAtMs.delete(command);
    logAvailabilityDecision({
      tool: command,
      verdict: "available",
      outcome: "success",
      cause: "ok",
      elapsedMs,
      latched: true,
      hostStallMs,
      budgetMs: WHICH_BUDGET_MS,
      classifiedBy: "probe"
    });
    return resolved;
  }
  entry.resolved = null;
  const proberRan = !result.error;
  const classified = classifyProbeFailure(result, {
    hostStallMs,
    ...proberRan && { unclassifiedFailureOutcome: "missing" }
  });
  let { outcome, cause } = classified;
  let outcomeForced = false;
  if (!proberRan && outcome !== "transient") {
    outcome = "transient";
    cause = "probe-rejected";
    outcomeForced = true;
  }
  const retryAfterMs = entry.latch.noteUnavailable(outcome, cause);
  if (outcome === "transient")
    whichTransientCommands.add(command);
  else
    whichTransientCommands.delete(command);
  cooldownRecordedForRetryAtMs.delete(command);
  logAvailabilityDecision({
    tool: command,
    verdict: "unavailable",
    outcome,
    cause,
    elapsedMs,
    latched: retryAfterMs === 0,
    hostStallMs,
    ...retryAfterMs > 0 && { retryAfterMs },
    budgetMs: WHICH_BUDGET_MS,
    classifiedBy: outcomeForced ? "caller" : "probe"
  });
  return null;
}
async function resolveGoFmtBinary() {
  const inPath = await which("gofmt");
  if (inPath)
    return inPath;
  const goCheck = await probeToolAsync("go", ["env", "GOROOT"], {
    timeout: 5e3
  });
  if (goCheck.error || goCheck.status !== 0)
    return null;
  const goroot = (goCheck.stdout ?? "").trim();
  if (!goroot)
    return null;
  const binary = path4.join(goroot, "bin", process.platform === "win32" ? "gofmt.exe" : "gofmt");
  return await fileExists(binary) ? binary : null;
}
async function findInVenv(binary, cwd) {
  return findLocalBinUpwards(binary, cwd, {
    windowsExt: ".exe",
    binDirs: VENV_BIN_DIRS
  }) ?? null;
}
async function findInVendorBin(binary, cwd) {
  return findLocalBinUpwards(binary, cwd, {
    windowsExt: ".bat",
    binDirs: VENDOR_BIN_DIRS
  }) ?? null;
}
async function findInNodeModules(binary, cwd) {
  return findLocalBinUpwards(binary, cwd) ?? null;
}
async function canUseBundleExec(cwd) {
  if (await which("bundle") === null)
    return false;
  const lockfiles = await findUp(["Gemfile.lock"], cwd);
  return lockfiles.length > 0;
}
async function resolveManagedSmartDefaultCommand(formatterName, filePath, args) {
  const toolId = getAutoInstallToolIdForFormatter(formatterName);
  if (!toolId)
    return FORMATTER_UNAVAILABLE;
  const { ensureTool } = await import("./probes/installer.js");
  const installed = await ensureTool(toolId);
  if (!installed) {
    recordDegradationOnce({
      kind: "formatter-unavailable",
      subject: formatterName,
      reason: "managed formatter lookup and installation returned no executable"
    });
    return FORMATTER_UNAVAILABLE;
  }
  return [installed, ...args, filePath];
}
async function resolveManagedFormatterCommand(toolId, filePath, args, findLocal, cwd) {
  const local = findLocal && cwd ? await findLocal(toolId, cwd) : null;
  const installed = local ?? await which(toolId) ?? await getToolPath(toolId);
  return installed ? [installed, ...args, filePath] : FORMATTER_UNAVAILABLE;
}
function managedFormatterResolver(toolId, args, findLocal) {
  return (filePath, cwd) => resolveManagedFormatterCommand(toolId, filePath, args, findLocal, cwd);
}
function managedToolDetect(toolId, configDetect, nativeDetect) {
  return async (cwd) => {
    const available = nativeDetect && await nativeDetect(cwd) || Boolean(await getToolPath(toolId));
    return available && (configDetect ? await configDetect(cwd) : true);
  };
}
var EXPLICIT_FORMATTER_CONFIG_CHECKS = /* @__PURE__ */ new Map([
  ["biome", (cwd) => hasBiomeConfig(cwd)],
  [
    "prettier",
    (cwd) => hasPrettierConfig(cwd) || hasNearestPackageJsonField(cwd, "prettier")
  ],
  // .svelte is conditional beyond "an oxfmt config exists": oxfmt requires the
  // `svelte` package installed AND the config's `svelte` flag enabled
  // (verified empirically — see hasOxfmtSvelteConfig). The generic checks
  // below are NOT sufficient for .svelte — an oxfmt.toml with no svelte flag,
  // or an oxfmt dependency alone, both fail at runtime for .svelte
  // specifically (other extensions are unaffected by this stricter gate).
  [
    "oxfmt",
    (cwd, ext) => ext === ".svelte" ? hasOxfmtSvelteConfig(cwd) : hasOxfmtConfig(cwd) || hasVitePlusConfig(cwd) || // The published package is `oxfmt`; `@oxc-project/oxfmt` does not
    // exist on npm. Accept both (scoped kept for forward-compat).
    hasNearestPackageJsonDependency(cwd, "oxfmt") || hasNearestPackageJsonDependency(cwd, "@oxc-project/oxfmt")
  ],
  ["ruff", (cwd) => hasRuffConfig(cwd)],
  ["black", (cwd) => hasBlackConfig(cwd)],
  ["sqlfluff", (cwd) => hasSqlfluffConfig(cwd)],
  ["rubocop", (cwd) => hasRubocopConfig(cwd)],
  ["standardrb", (cwd) => hasStandardrbConfig(cwd)],
  ["clang-format", (cwd) => hasClangFormatConfig(cwd)],
  ["php-cs-fixer", (cwd) => hasPhpCsFixerConfig(cwd)],
  ["stylua", (cwd) => hasStyluaConfig(cwd)],
  ["ocamlformat", (cwd) => hasOcamlformatConfig(cwd)],
  ["google-java-format", (cwd) => hasGoogleJavaFormatConfig(cwd)],
  ["ktfmt", (cwd) => hasKtfmtConfig(cwd)],
  ["ktlint", (cwd) => hasKtlintConfig(cwd)],
  ["cljfmt", (cwd) => hasCljfmtConfig(cwd)],
  ["cmake-format", (cwd) => hasCmakeFormatConfig(cwd)],
  ["psscriptanalyzer-format", (cwd) => hasPSScriptAnalyzerConfig(cwd)],
  // #1595 sweep — see the comment above hasCsharpierConfig et al. in
  // tool-policy.ts for why nixfmt (the 8th formatter #1572 flagged) is NOT
  // wired here: it has no config-file convention and no manifest-marker
  // equivalent to `.terraform.lock.hcl`, so there is no honest opt-in signal.
  ["csharpier", (cwd) => hasCsharpierConfig(cwd)],
  ["ormolu", (cwd) => hasOrmoluConfig(cwd)],
  ["taplo", (cwd) => hasTaploConfig(cwd)],
  ["terraform", (cwd) => hasTerraformConfig(cwd)],
  ["swiftformat", (cwd) => hasSwiftformatConfig(cwd)],
  ["fantomas", (cwd) => hasFantomasConfig(cwd)],
  ["mix", (cwd) => hasMixFormatConfig(cwd)]
]);
function hasExplicitFormatterConfig(formatterName, cwd, ext) {
  return EXPLICIT_FORMATTER_CONFIG_CHECKS.get(formatterName)?.(cwd, ext) ?? false;
}
var FORMATTERS_WITH_EXPLICIT_CONFIG_CHECK = new Set(EXPLICIT_FORMATTER_CONFIG_CHECKS.keys());
async function hasEditorConfig(cwd) {
  return findNearestContaining(cwd, [".editorconfig"]) !== void 0;
}
async function indentationArgs(filePath, tool, cwd) {
  if (await hasEditorConfig(cwd) || hasExplicitFormatterConfig(tool, cwd, path4.extname(filePath))) {
    return [];
  }
  let content;
  try {
    content = await fs2.readFile(filePath, "utf8");
  } catch {
    return [];
  }
  if (!hasDetectableIndentation(content))
    return null;
  const indentation = detectIndentation(content);
  if (!indentation) {
    recordDegradationOnce({
      kind: "formatter-skip",
      subject: tool,
      reason: "indentation evidence is ambiguous; formatter style not pinned"
    });
    return null;
  }
  if (tool === "shfmt")
    return indentation.style === "tab" ? ["-i", "0"] : ["-i", String(indentation.width)];
  if (tool === "prettier") {
    return [
      indentation.style === "tab" ? "--use-tabs" : "--no-use-tabs",
      "--tab-width",
      String(indentation.width)
    ];
  }
  if (tool === "ruff") {
    return [
      "--config",
      `indent-width=${indentation.width}`,
      "--config",
      `format.indent-style='${indentation.style}'`
    ];
  }
  return [
    "--indent-style",
    indentation.style,
    "--indent-width",
    String(indentation.width)
  ];
}
function resolveFormatterCwd(absolutePath, formatterName, homeDir = os2.homedir()) {
  return resolveToolCwd("formatter", formatterName ?? "unknown", absolutePath, {
    // Formatter discovery historically walks above the file directory. The
    // filesystem root is the neutral dispatch boundary; the seam's `$HOME`
    // ceiling prevents it from escaping the user's workspace.
    cwd: path4.parse(path4.resolve(absolutePath)).root,
    homeDir
  }).cwd;
}
var BIOME_UNMATCHED_FLAG = "--no-errors-on-unmatched";
var biomeFormatter = {
  name: "biome",
  command: [
    "npx",
    "@biomejs/biome",
    "format",
    "--write",
    BIOME_UNMATCHED_FLAG,
    "$FILE"
  ],
  async resolveCommand(filePath, cwd) {
    const editorConfigFlag = await hasEditorConfig(cwd) ? ["--use-editorconfig=true"] : [];
    const styleArgs = await indentationArgs(filePath, "biome", cwd);
    if (styleArgs === null)
      return SKIP_FORMATTING;
    const args = [
      "format",
      "--write",
      BIOME_UNMATCHED_FLAG,
      ...editorConfigFlag,
      ...styleArgs
    ];
    const local = await findInNodeModules("biome", cwd);
    if (local)
      return [local, ...args, filePath];
    const global = await findGlobalBinary("biome");
    if (global)
      return [global, ...args, filePath];
    const toolId = getAutoInstallToolIdForFormatter("biome");
    if (!toolId)
      return null;
    const { ensureTool } = await import("./probes/installer.js");
    const installed = await ensureTool(toolId);
    if (installed)
      return [installed, ...args, filePath];
    return null;
  },
  extensions: [
    ".js",
    ".jsx",
    ".mjs",
    ".cjs",
    ".ts",
    ".tsx",
    ".mts",
    ".cts",
    ".json",
    ".jsonc",
    ".css",
    ".scss",
    ".sass",
    ".vue",
    ".svelte",
    ".html",
    ".htm"
  ],
  async detect(cwd) {
    return hasBiomeConfig(cwd) || hasNearestPackageJsonDependency(cwd, "@biomejs/biome");
  }
};
var prettierFormatter = {
  name: "prettier",
  command: ["npx", "prettier", "--write", "$FILE"],
  async resolveCommand(filePath, cwd) {
    const styleArgs = await indentationArgs(filePath, "prettier", cwd);
    if (styleArgs === null)
      return SKIP_FORMATTING;
    const args = ["--write", ...styleArgs];
    const local = await findInNodeModules("prettier", cwd);
    if (local)
      return [local, ...args, filePath];
    const global = await findGlobalBinary("prettier");
    if (global)
      return [global, ...args, filePath];
    return resolveManagedSmartDefaultCommand("prettier", filePath, args);
  },
  extensions: [
    ".js",
    ".jsx",
    ".mjs",
    ".cjs",
    ".ts",
    ".tsx",
    ".mts",
    ".cts",
    ".json",
    ".jsonc",
    ".css",
    ".scss",
    ".sass",
    ".less",
    ".vue",
    ".svelte",
    ".html",
    ".htm",
    ".md",
    ".mdx",
    ".yaml",
    ".yml",
    ".graphql",
    ".gql"
  ],
  async detect(cwd) {
    return hasPrettierConfig(cwd) || hasNearestPackageJsonDependency(cwd, "prettier") || hasNearestPackageJsonField(cwd, "prettier");
  }
};
var OXFMT_NO_ERROR_ON_UNMATCHED = "--no-error-on-unmatched-pattern";
var oxfmtFormatter = {
  name: "oxfmt",
  command: ["oxfmt", OXFMT_NO_ERROR_ON_UNMATCHED, "$FILE"],
  // #1337 audit: oxfmt (and the `vp fmt --write` path) publish no exit-code
  // table. `--write` is the default and `--check` is the separate verification
  // mode, so there is no documented nonzero-on-reformat. Absent a documented
  // benign-nonzero mode, it stays strict — the safe direction, since the failure
  // mode of guessing wrong the other way is a silent no-op (#1336).
  async resolveCommand(filePath, cwd) {
    if (hasVitePlusConfig(cwd)) {
      const localVp = await findInNodeModules("vp", cwd);
      if (localVp)
        return [localVp, "fmt", filePath, "--write"];
      const globalVp = await which("vp");
      if (globalVp)
        return [globalVp, "fmt", filePath, "--write"];
    }
    const local = await findInNodeModules("oxfmt", cwd);
    if (local)
      return [local, OXFMT_NO_ERROR_ON_UNMATCHED, filePath];
    const found = await which("oxfmt");
    if (found)
      return [found, OXFMT_NO_ERROR_ON_UNMATCHED, filePath];
    return await resolveManagedFormatterCommand("oxfmt", filePath, [OXFMT_NO_ERROR_ON_UNMATCHED], findInNodeModules, cwd) ?? FORMATTER_UNAVAILABLE;
  },
  // Single source of truth: OXFMT_SUPPORTED_EXTENSIONS in tool-policy.ts.
  // Do not hand-maintain a second copy of this list (#1134 — previously two
  // parallel hand-maintained lists, the #883 single-source-of-truth class).
  extensions: [...OXFMT_SUPPORTED_EXTENSIONS],
  async detect(cwd) {
    return hasOxfmtConfig(cwd) || hasVitePlusConfig(cwd) || // Published package is `oxfmt` (the scoped name does not exist on npm).
    hasNearestPackageJsonDependency(cwd, "oxfmt") || hasNearestPackageJsonDependency(cwd, "@oxc-project/oxfmt");
  }
};
var ruffFormatter = {
  name: "ruff",
  command: ["ruff", "format", "$FILE"],
  extensions: [".py", ".pyi"],
  // Strict (the #1337 default): `ruff format` exits 0 on a successful in-place
  // rewrite and 2 on argument rejection / syntax error (verified, ruff 0.x:
  // well-formed → 0, reformatted → 0, unparseable → 2). The exit-2 no-op is
  // exactly what hid the bad --indent-style flags for a full release cycle.
  async resolveCommand(filePath, cwd) {
    const styleArgs = await indentationArgs(filePath, "ruff", cwd);
    if (styleArgs === null)
      return SKIP_FORMATTING;
    const args = ["format", ...styleArgs];
    const venv = await findInVenv("ruff", cwd);
    if (venv)
      return [venv, ...args, filePath];
    const toolId = getAutoInstallToolIdForFormatter("ruff");
    if (!toolId)
      return null;
    const { ensureTool } = await import("./probes/installer.js");
    const installed = await ensureTool(toolId);
    if (installed)
      return [installed, ...args, filePath];
    return FORMATTER_UNAVAILABLE;
  },
  async detect(cwd) {
    if (hasRuffConfig(cwd))
      return true;
    return Boolean(await getToolPath("ruff"));
  }
};
var blackFormatter = {
  name: "black",
  command: ["black", "$FILE"],
  extensions: [".py", ".pyi"],
  resolveCommand: managedFormatterResolver("black", [], findInVenv),
  async detect(cwd) {
    return hasBlackConfig(cwd);
  }
};
var sqlfluffFormatter = {
  name: "sqlfluff",
  command: ["sqlfluff", "fix", "--force", "$FILE"],
  extensions: [".sql"],
  lenientExitCode: "lint-autofix: `sqlfluff fix` writes the corrected file and then exits 1 when unfixable violations remain (its documented exit codes are 0 = all clean, 1 = violations remain, 2 = command/config failure), so a nonzero exit routinely accompanies a successful rewrite.",
  lenientStatuses: [1],
  async resolveCommand(filePath, cwd) {
    const venv = await findInVenv("sqlfluff", cwd);
    if (venv)
      return [venv, "fix", "--force", filePath];
    return null;
  },
  async detect(cwd) {
    return hasSqlfluffConfig(cwd);
  }
};
var gofmtFormatter = {
  name: "gofmt",
  command: ["gofmt", "-w", "$FILE"],
  extensions: [".go"],
  async resolveCommand(filePath, _cwd) {
    const gofmtBinary = await resolveGoFmtBinary();
    if (!gofmtBinary)
      return null;
    return [gofmtBinary, "-w", filePath];
  },
  async detect(_cwd) {
    return await resolveGoFmtBinary() !== null;
  }
};
var rustfmtFormatter = {
  name: "rustfmt",
  command: ["rustfmt", "$FILE"],
  extensions: [".rs"],
  // #2466: rustfmt defaults to an OLDER edition than the file's actual Cargo
  // package when invoked bare, so it can reject valid newer-edition syntax
  // (e.g. Rust 2024). Carry the nearest package's `edition` (honoring
  // `edition.workspace = true` inheritance) through `--edition`; `undefined`
  // (unreadable/unparseable manifest) falls back to the static command
  // above, unchanged from pre-#2466 behavior.
  async resolveCommand(filePath, _cwd) {
    const edition = await resolveCargoPackageEdition(filePath);
    if (edition === void 0)
      return null;
    return ["rustfmt", "--edition", edition, filePath];
  },
  async detect(cwd) {
    if (await which("rustfmt") !== null)
      return true;
    const rustProject = (await findUp(["Cargo.toml"], cwd)).length > 0;
    if (!rustProject)
      return false;
    if (await which("rustup") === null)
      return false;
    await tryLazyInstallFormatterTool("rustfmt", cwd);
    return await which("rustfmt") !== null;
  }
};
var zigFormatter = {
  name: "zig",
  command: ["zig", "fmt", "$FILE"],
  extensions: [".zig", ".zon"],
  async detect(_cwd) {
    return await which("zig") !== null;
  }
};
var dartFormatter = {
  name: "dart",
  command: ["dart", "format", "$FILE"],
  extensions: [".dart"],
  async detect(_cwd) {
    return await which("dart") !== null;
  }
};
var shfmtFormatter = {
  name: "shfmt",
  command: ["shfmt", "-w", "$FILE"],
  extensions: [".sh", ".bash"],
  async resolveCommand(filePath, cwd) {
    const styleArgs = await indentationArgs(filePath, "shfmt", cwd);
    if (styleArgs === null)
      return SKIP_FORMATTING;
    const inPath = await which("shfmt");
    if (inPath)
      return [inPath, "-w", ...styleArgs, filePath];
    return resolveManagedSmartDefaultCommand("shfmt", filePath, [
      "-w",
      ...styleArgs
    ]);
  },
  detect: managedToolDetect("shfmt", void 0, async () => await which("shfmt") !== null)
};
var nixfmtFormatter = {
  name: "nixfmt",
  command: ["nixfmt", "$FILE"],
  // #1337 audit: nixfmt's README documents in-place formatting but has no
  // exit-code section and no change-detection mode, so nothing documents a
  // benign nonzero. Strict by default (see oxfmt above for the rationale).
  extensions: [".nix"],
  async detect(_cwd) {
    return await which("nixfmt") !== null;
  }
};
var mixFormatter = {
  name: "mix",
  command: ["mix", "format", "$FILE"],
  extensions: [".ex", ".exs", ".eex", ".heex", ".leex"],
  async detect(_cwd) {
    return await which("mix") !== null;
  }
};
var ocamlformatFormatter = {
  name: "ocamlformat",
  command: ["ocamlformat", "-i", "$FILE"],
  extensions: [".ml", ".mli"],
  async detect(cwd) {
    const hasBinary = await which("ocamlformat") !== null;
    if (!hasBinary)
      return false;
    const configs = [".ocamlformat"];
    const found = await findUp(configs, cwd);
    return found.length > 0;
  }
};
var clangFormatFormatter = {
  name: "clang-format",
  command: ["clang-format", "-i", "$FILE"],
  extensions: [".c", ".cc", ".cpp", ".cxx", ".h", ".hpp", ".ino"],
  async detect(cwd) {
    const hasBinary = await which("clang-format") !== null;
    if (!hasBinary)
      return false;
    const configs = [".clang-format", "_clang-format"];
    const found = await findUp(configs, cwd);
    return found.length > 0;
  }
};
var ktlintFormatter = {
  name: "ktlint",
  command: ["ktlint", "-F", "$FILE"],
  extensions: [".kt", ".kts"],
  lenientExitCode: "lint-autofix: `ktlint -F` autocorrects what it can and then exits 1 if any lint error remains unfixed \u2014 the documented CLI contract (ktlint exits nonzero whenever violations are reported, and -F does not suppress the ones it cannot correct).",
  lenientStatuses: [1],
  async resolveCommand(filePath, _cwd) {
    const inPath = await which("ktlint");
    if (inPath)
      return [inPath, "-F", filePath];
    return resolveManagedSmartDefaultCommand("ktlint", filePath, ["-F"]);
  },
  detect: managedToolDetect("ktlint", void 0, async () => await which("ktlint") !== null)
};
var ktfmtFormatter = {
  name: "ktfmt",
  // ktfmt formats in place when given a file path (no flag needed).
  command: ["ktfmt", "$FILE"],
  extensions: [".kt", ".kts"],
  // #2468: ktfmt's CLI never reads a project's Gradle `ktfmt { googleStyle()
  // | kotlinLangStyle() }` selection — style is CLI-flag-only
  // (`--google-style`/`--kotlinlang-style`, verified against ktfmt v0.63's
  // own arg parser). Carry the nearest module's declared style through;
  // `undefined` (no declaration, unreadable/unparseable manifest, or an
  // unsupported style like the removed `dropboxStyle()`) falls back to the
  // bare invocation, unchanged from pre-#2468 behavior.
  async resolveCommand(filePath, _cwd) {
    const styleFlag = await resolveKtfmtGradleStyle(filePath);
    const inPath = await which("ktfmt");
    if (inPath)
      return styleFlag ? [inPath, styleFlag, filePath] : [inPath, filePath];
    const { ensureTool } = await import("./probes/installer.js");
    const installed = await ensureTool("ktfmt");
    if (!installed)
      return FORMATTER_UNAVAILABLE;
    return styleFlag ? [installed, styleFlag, filePath] : [installed, filePath];
  },
  async detect(cwd) {
    return hasKtfmtConfig(cwd);
  }
};
var rubocopFormatter = {
  name: "rubocop",
  command: ["rubocop", "-a", "--no-color", "$FILE"],
  extensions: [".rb", ".rake", ".gemspec", ".ru"],
  lenientExitCode: "lint-autofix: `rubocop -a` exits 1 whenever ANY offense remains after it has already rewritten the file. Verified locally (rubocop on Ruby 3.4): a file with an unfixable Lint/UselessAssignment exits 1, and even a tidy file exits 1 on an unfixable Style/Documentation offense \u2014 nonzero is the normal outcome, not a failure.",
  lenientStatuses: [1],
  async resolveCommand(filePath, cwd) {
    if (await canUseBundleExec(cwd))
      return ["bundle", "exec", "rubocop", "-a", "--no-color", filePath];
    return null;
  },
  async detect(cwd) {
    if (!hasRubocopConfig(cwd))
      return false;
    if (await which("rubocop") !== null)
      return true;
    await tryLazyInstallFormatterTool("rubocop", cwd);
    return await which("rubocop") !== null;
  }
};
var standardrbFormatter = {
  name: "standardrb",
  command: ["standardrb", "--fix", "$FILE"],
  extensions: [".rb", ".rake"],
  lenientExitCode: "lint-autofix: standardrb is a RuboCop wrapper and inherits its exit contract \u2014 `--fix` exits 1 when offenses remain after the rewrite.",
  lenientStatuses: [1],
  async resolveCommand(filePath, cwd) {
    if (await canUseBundleExec(cwd))
      return ["bundle", "exec", "standardrb", "--fix", filePath];
    return null;
  },
  async detect(cwd) {
    if (!hasStandardrbConfig(cwd))
      return false;
    return await which("standardrb") !== null;
  }
};
var gleamFormatter = {
  name: "gleam",
  command: ["gleam", "format", "$FILE"],
  extensions: [".gleam"],
  async detect(cwd) {
    const found = await findUp(["gleam.toml"], cwd);
    if (found.length > 0)
      return await which("gleam") !== null;
    return false;
  }
};
var typstyleFormatter = {
  name: "typstyle",
  command: ["typstyle", "-i", "$FILE"],
  extensions: [".typ", ".typc"],
  async resolveCommand(filePath) {
    const inPath = await which("typstyle");
    if (inPath)
      return [inPath, "-i", filePath];
    return resolveManagedSmartDefaultCommand("typstyle", filePath, ["-i"]);
  },
  detect: managedToolDetect("typstyle", void 0, async () => await which("typstyle") !== null)
};
var terraformFormatter = {
  name: "terraform",
  command: ["terraform", "fmt", "$FILE"],
  extensions: [".tf", ".tfvars"],
  async detect(_cwd) {
    return await which("terraform") !== null;
  }
};
var terragruntHclFormatter = {
  name: "terragrunt-hcl",
  command: ["terragrunt", "hcl", "fmt", "--file", "$FILE"],
  extensions: [],
  filenames: TERRAGRUNT_FILENAMES,
  // Strict (the #1337 default): verified exit 0 on a successful in-place format
  // against terragrunt v1.1.2. A binary predating the `hcl` command group exits
  // nonzero and touches nothing, which unguarded reads as "already formatted".
  async detect(_cwd) {
    return await which("terragrunt") !== null;
  }
};
var phpCsFixerFormatter = {
  name: "php-cs-fixer",
  command: ["php-cs-fixer", "fix", "$FILE"],
  extensions: [".php"],
  // #2472: php-cs-fixer does NOT walk up parent directories looking for its
  // own config the way prettier/biome/eslint do (verified against upstream
  // `computeConfigFiles()` — see `resolvePhpCsFixerConfig`'s doc comment),
  // and `formatFile` spawns with cwd = the FILE's own directory, which is
  // not necessarily where the ancestor config `detect()` found actually
  // lives. Always resolve the binary explicitly here (vendor/bin first,
  // then global) rather than falling through to the static `command` above
  // — that static command can never carry `--config`, so a config found at
  // an ancestor would silently be dropped whenever the vendor lookup here
  // missed but a global binary still resolved. `--config` is attached
  // whenever a config resolves, even when it sits in the file's own
  // directory (AC3): unlike the pre-#2472 code, correctness no longer
  // depends on php-cs-fixer's own (nonexistent) upward search.
  async resolveCommand(filePath, cwd) {
    const configPath = resolvePhpCsFixerConfig(filePath);
    const binary = await findInVendorBin("php-cs-fixer", cwd) ?? await which("php-cs-fixer");
    const managed = await resolveManagedFormatterCommand("php-cs-fixer", filePath, [], findInVendorBin, cwd);
    if (managed === FORMATTER_UNAVAILABLE)
      return FORMATTER_UNAVAILABLE;
    const resolved = binary ?? managed[0];
    if (!resolved)
      return FORMATTER_UNAVAILABLE;
    return configPath ? [resolved, "fix", "--config", configPath, filePath] : [resolved, "fix", filePath];
  },
  detect: managedToolDetect("php-cs-fixer", async (cwd) => (await findUp([".php-cs-fixer.php", ".php-cs-fixer.dist.php"], cwd)).length > 0, async (cwd) => Boolean(await findInVendorBin("php-cs-fixer", cwd) || await which("php-cs-fixer")))
};
var csharpierFormatter = {
  name: "csharpier",
  // CSharpier ≥1.0 is a standalone `csharpier format <file>`; the `dotnet
  // csharpier <file>` form was removed (a bare `dotnet csharpier` now errors
  // "a dotnet-prefixed executable with this name could not be found"). Keep the
  // legacy form as a fallback for CSharpier 0.x via resolveCommand.
  command: ["csharpier", "format", "$FILE"],
  extensions: [".cs"],
  async resolveCommand(filePath, _cwd) {
    if (await which("csharpier") !== null) {
      return ["csharpier", "format", filePath];
    }
    if (await which("dotnet") !== null) {
      const legacy = await probeToolAsync("dotnet", ["csharpier", "--version"], { timeout: 5e3 });
      if (!legacy.error && legacy.status === 0) {
        return ["dotnet", "csharpier", filePath];
      }
    }
    return null;
  },
  async detect(_cwd) {
    if (await which("csharpier") !== null)
      return true;
    if (await which("dotnet") === null)
      return false;
    const result = await probeToolAsync("dotnet", ["csharpier", "--version"], {
      timeout: 5e3
    });
    return !result.error && result.status === 0;
  }
};
var fantomasFormatter = {
  name: "fantomas",
  command: ["fantomas", "$FILE"],
  extensions: [".fs", ".fsi", ".fsx"],
  async detect(_cwd) {
    return await which("fantomas") !== null;
  }
};
var swiftformatFormatter = {
  name: "swiftformat",
  command: ["swiftformat", "$FILE"],
  extensions: [".swift"],
  async detect(_cwd) {
    return await which("swiftformat") !== null;
  }
};
var styluaFormatter = {
  name: "stylua",
  command: ["stylua", "$FILE"],
  extensions: [".lua"],
  resolveCommand: managedFormatterResolver("stylua", [], findLocalBinUpwards),
  detect: managedToolDetect("stylua", async (cwd) => (await findUp(["stylua.toml", ".stylua.toml"], cwd)).length > 0, async (cwd) => Boolean(findLocalBinUpwards("stylua", cwd) || await which("stylua")))
};
var ormoluFormatter = {
  name: "ormolu",
  command: ["ormolu", "--mode", "inplace", "$FILE"],
  extensions: [".hs", ".lhs"],
  async detect(_cwd) {
    return await which("ormolu") !== null;
  }
};
var taploFormatter = {
  name: "taplo",
  command: ["taplo", "fmt", "$FILE"],
  extensions: [".toml"],
  async resolveCommand(filePath, _cwd) {
    const inPath = await which("taplo");
    if (inPath)
      return [inPath, "fmt", filePath];
    return resolveManagedSmartDefaultCommand("taplo", filePath, ["fmt"]);
  },
  detect: managedToolDetect("taplo", void 0, async () => await which("taplo") !== null)
};
var googleJavaFormatFormatter = {
  name: "google-java-format",
  command: ["google-java-format", "--replace", "$FILE"],
  extensions: [".java"],
  resolveCommand: managedFormatterResolver("google-java-format", ["--replace"]),
  detect: managedToolDetect("google-java-format", hasGoogleJavaFormatConfig, async () => await which("google-java-format") !== null)
};
var cljfmtFormatter = {
  name: "cljfmt",
  command: ["cljfmt", "fix", "$FILE"],
  extensions: [".clj", ".cljc", ".cljs"],
  resolveCommand: managedFormatterResolver("cljfmt", ["fix"]),
  detect: managedToolDetect("cljfmt", hasCljfmtConfig, async () => await which("cljfmt") !== null)
};
var cmakeFormatFormatter = {
  name: "cmake-format",
  command: ["cmake-format", "-i", "$FILE"],
  extensions: [".cmake"],
  resolveCommand: managedFormatterResolver("cmake-format", ["-i"]),
  detect: managedToolDetect("cmake-format", hasCmakeFormatConfig, async () => await which("cmake-format") !== null)
};
var cueFormatter = {
  name: "cue",
  // `cue fmt <file>` rewrites in place already. There is no `-w`: the only
  // flags are --check, -d/--diff and --files, so `cue fmt -w` aborts with
  // "unknown shorthand flag: 'w'" on every .cue write (verified on cue
  // v0.17.1).
  command: ["cue", "fmt", "$FILE"],
  extensions: [".cue"],
  async detect(_cwd) {
    return await which("cue") !== null;
  }
};
function psScriptAnalyzerCommand(filePath, settingsPath) {
  const quoted = filePath.replace(/'/g, "''");
  const settingsArg = settingsPath ? ` -Settings '${settingsPath.replace(/'/g, "''")}'` : "";
  return [
    "$ErrorActionPreference = 'Stop'",
    `$p = '${quoted}'`,
    "$content = Get-Content -Raw -LiteralPath $p",
    "if ([string]::IsNullOrWhiteSpace($content)) { exit 0 }",
    `$formatted = Invoke-Formatter -ScriptDefinition $content${settingsArg}`,
    "if ($null -eq $formatted) { throw 'Invoke-Formatter returned no output' }",
    "Set-Content -LiteralPath $p -Value $formatted"
  ].join("; ");
}
var psscriptanalyzerFormatFormatter = {
  name: "psscriptanalyzer-format",
  command: ["pwsh", "-NoProfile", "-Command", psScriptAnalyzerCommand("$FILE")],
  extensions: [".ps1", ".psm1", ".psd1"],
  async resolveCommand(filePath, cwd) {
    const pwsh = await which("pwsh") ?? await which("powershell");
    if (!pwsh)
      return null;
    const settingsPath = findPSScriptAnalyzerConfigPath(cwd);
    return [
      pwsh,
      "-NoProfile",
      "-Command",
      psScriptAnalyzerCommand(filePath, settingsPath)
    ];
  },
  async detect(_cwd) {
    const pwsh = await which("pwsh") ?? await which("powershell");
    if (!pwsh)
      return false;
    const result = await probeToolAsync(pwsh, [
      "-NoProfile",
      "-Command",
      "Get-Module -ListAvailable PSScriptAnalyzer | Select-Object -First 1 -ExpandProperty Name"
    ], { timeout: 5e3 });
    return (result.stdout ?? "").includes("PSScriptAnalyzer");
  }
};
var ALL_FORMATTERS = [
  biomeFormatter,
  prettierFormatter,
  oxfmtFormatter,
  ruffFormatter,
  blackFormatter,
  sqlfluffFormatter,
  gofmtFormatter,
  rustfmtFormatter,
  zigFormatter,
  dartFormatter,
  shfmtFormatter,
  nixfmtFormatter,
  mixFormatter,
  ocamlformatFormatter,
  clangFormatFormatter,
  ktlintFormatter,
  ktfmtFormatter,
  terraformFormatter,
  terragruntHclFormatter,
  phpCsFixerFormatter,
  csharpierFormatter,
  fantomasFormatter,
  swiftformatFormatter,
  styluaFormatter,
  ormoluFormatter,
  rubocopFormatter,
  standardrbFormatter,
  gleamFormatter,
  typstyleFormatter,
  taploFormatter,
  googleJavaFormatFormatter,
  cljfmtFormatter,
  cmakeFormatFormatter,
  cueFormatter,
  psscriptanalyzerFormatFormatter
];
var FILENAME_FORMATTER_BASENAMES = new Set(ALL_FORMATTERS.flatMap((f) => f.filenames ?? []));
var detectionCache = new BoundedLruCache(32);
var formatterSignatureFlights = new BoundedLruCache(32);
var formatterCacheGeneration = createGenerationSource("formatter-cache");
var FORMATTER_CONFIG_FILES = [
  "package.json",
  "biome.json",
  "biome.jsonc",
  ".prettierrc",
  ".prettierrc.json",
  ".prettierrc.yml",
  ".prettierrc.yaml",
  ".prettierrc.js",
  ".prettierrc.cjs",
  ".prettierrc.mjs",
  "prettier.config.js",
  "prettier.config.cjs",
  "prettier.config.mjs",
  "prettier.config.ts",
  "pyproject.toml",
  "ruff.toml",
  ".ruff.toml",
  "black.toml",
  ".black",
  "tox.ini",
  "setup.cfg",
  "requirements.txt",
  "Pipfile",
  ".sqlfluff",
  ".rubocop.yml",
  ".rubocop.yaml",
  "Gemfile",
  ".clang-format",
  "_clang-format",
  ".php-cs-fixer.php",
  ".php-cs-fixer.dist.php",
  "stylua.toml",
  ".stylua.toml",
  ".ocamlformat",
  ".editorconfig",
  ".google-java-format",
  ".ktfmt",
  ".ktfmt.kts",
  "build.gradle.kts",
  "build.gradle",
  "settings.gradle.kts",
  "settings.gradle",
  ".cljfmt.edn",
  "cljfmt.edn",
  ".cljfmt",
  ".cmake-format",
  ".cmake-format.yaml",
  ".cmake-format.yml",
  ".cmake-format.json",
  ".cmake-format.py",
  "cmake-format.py",
  "cmake-format.yaml",
  "cmake-format.yml",
  "oxfmt.toml",
  ".oxfmtrc.json",
  "vite-plus.json",
  "vite.config.ts",
  "vite.config.mts",
  "vite.config.cts",
  "vite.config.js",
  "vite.config.mjs",
  "vite.config.cjs",
  "PSScriptAnalyzerSettings.psd1",
  "ScriptAnalyzerSettings.psd1",
  // #1595 sweep additions.
  ".csharpierrc",
  ".csharpierrc.json",
  ".csharpierrc.yaml",
  ".csharpierrc.yml",
  ".ormolu",
  "taplo.toml",
  ".taplo.toml",
  ".terraform.lock.hcl",
  ".swiftformat",
  ".fantomasignore",
  ".formatter.exs"
];
async function formatterConfigSignature(cwd) {
  const paths = await findUp(FORMATTER_CONFIG_FILES, cwd);
  const parts = await Promise.all(paths.sort(compareOrdinal).map(async (filePath) => {
    try {
      const stat2 = await fs2.stat(filePath);
      return `${filePath}:${stat2.mtimeMs}:${stat2.size}`;
    } catch {
      return `${filePath}:missing`;
    }
  }));
  return parts.join("|");
}
async function getFormatterConfigSignature(cwd, normalizedCwd) {
  const existing = formatterSignatureFlights.get(normalizedCwd);
  if (existing)
    return existing.promise;
  const promise = formatterConfigSignature(cwd).finally(() => {
    const current = formatterSignatureFlights.get(normalizedCwd);
    if (current?.promise === promise)
      formatterSignatureFlights.delete(normalizedCwd);
  });
  formatterSignatureFlights.set(normalizedCwd, { promise });
  return promise;
}
async function getFormattersForFile(filePath, cwd) {
  const ext = path4.extname(filePath).toLowerCase();
  const base = path4.basename(filePath).toLowerCase();
  const normalizedCwd = normalizeMapKey(cwd);
  const cacheKey = FILENAME_FORMATTER_BASENAMES.has(base) ? `${normalizedCwd}:${ext}:${base}` : `${normalizedCwd}:${ext}`;
  let cached = detectionCache.get(normalizedCwd);
  if (cached?.entries.has(cacheKey)) {
    const enabledNames2 = cached.entries.get(cacheKey);
    const selectedFormatterName = enabledNames2 && enabledNames2.length > 0 ? enabledNames2[0] : null;
    logLatency({
      type: "phase",
      phase: "formatter_selected",
      filePath,
      durationMs: 0,
      metadata: {
        formatter: selectedFormatterName,
        reason: "cache",
        cached: true,
        outcome: "hit",
        cwd
      }
    });
    if (!enabledNames2 || enabledNames2.length === 0)
      return [];
    return ALL_FORMATTERS.filter((f) => enabledNames2.includes(f.name));
  }
  if (!cached) {
    const generation = formatterCacheGeneration.capture();
    const configSignature = await getFormatterConfigSignature(cwd, normalizedCwd);
    if (!generation.isCurrent()) {
      return getFormattersForFile(filePath, cwd);
    }
    cached = detectionCache.get(normalizedCwd);
    if (!cached) {
      cached = { signature: configSignature, entries: /* @__PURE__ */ new Map() };
      detectionCache.set(normalizedCwd, cached);
    }
  }
  const matching = ALL_FORMATTERS.filter((f) => f.extensions.includes(ext) || f.filenames?.includes(base));
  const formatterPolicy = getFormatterPolicyForFile(filePath);
  const smartDefaultFormatterName = getSmartDefaultFormatterName(filePath);
  const candidateFormatters = formatterPolicy?.formatterNames?.length ? matching.filter((f) => formatterPolicy.formatterNames.includes(f.name)) : matching;
  let selected;
  const candidateOutcomes = [];
  if (formatterPolicy) {
    const explicitlyConfigured = candidateFormatters.filter((formatter) => hasExplicitFormatterConfig(formatter.name, cwd, ext));
    if (explicitlyConfigured.length > 0) {
      selected = formatterPolicy.defaultFormatter ? explicitlyConfigured.find((f) => f.name === formatterPolicy.defaultFormatter) ?? explicitlyConfigured[0] : explicitlyConfigured[0];
    } else if (smartDefaultFormatterName) {
      const smartDefaultFormatter = candidateFormatters.find((f) => f.name === smartDefaultFormatterName);
      if (smartDefaultFormatter) {
        const autoInstallToolId = getAutoInstallToolIdForFormatter(smartDefaultFormatter.name);
        if (autoInstallToolId) {
          selected = smartDefaultFormatter;
        } else {
          const { detected, error, outcome } = await detectCandidate(smartDefaultFormatter, cwd);
          candidateOutcomes.push(outcome);
          if (error !== void 0)
            throw error;
          if (detected)
            selected = smartDefaultFormatter;
        }
      }
    }
  } else {
    for (const formatter of candidateFormatters) {
      const { detected, error, outcome } = await detectCandidate(formatter, cwd);
      candidateOutcomes.push(outcome);
      if (error !== void 0) {
        logExtension({
          subsystem: "format",
          message: `Detection failed for ${formatter.name}: ${error instanceof Error ? error.message : String(error)}`,
          metadata: { formatter: formatter.name, cwd }
        });
        continue;
      }
      if (detected) {
        selected = formatter;
        break;
      }
    }
  }
  const enabled = selected ? [selected] : [];
  const stalledCandidateCommands = [
    ...new Set(candidateOutcomes.flatMap((outcome) => outcome.stalledCommands))
  ];
  const poisonedByTransientProbe = enabled.length === 0 && stalledCandidateCommands.length > 0;
  const winnerIndex = selected ? candidateOutcomes.findIndex((outcome) => outcome.name === selected.name) : -1;
  const unreachablePreferred = candidateOutcomes.slice(0, winnerIndex === -1 ? 0 : winnerIndex).filter((outcome) => outcome.verdict === "unreachable").map((outcome) => outcome.name);
  const degradedSelection = unreachablePreferred.length > 0;
  let selectionReason;
  if (poisonedByTransientProbe) {
    selectionReason = "probe-timeout";
  } else if (degradedSelection) {
    selectionReason = "preferred-unreachable";
  } else if (!selected) {
    selectionReason = "none";
  } else if (!formatterPolicy) {
    selectionReason = "detect";
  } else {
    selectionReason = candidateFormatters.some((f) => hasExplicitFormatterConfig(f.name, cwd, ext)) ? "explicit-config" : "smart-default";
  }
  const provisional = poisonedByTransientProbe || degradedSelection;
  logLatency({
    type: "phase",
    phase: "formatter_selected",
    filePath,
    durationMs: 0,
    metadata: {
      formatter: selected?.name ?? null,
      reason: selectionReason,
      outcome: "miss",
      cwd,
      ...provisional && {
        cached: false,
        stalledProbes: stalledCandidateCommands
      },
      ...degradedSelection && { unreachablePreferred }
    }
  });
  if (provisional)
    return enabled;
  const enabledNames = enabled.map((f) => f.name);
  if (detectionCache.get(normalizedCwd) === cached) {
    cached.entries.set(cacheKey, enabledNames);
  }
  return enabled;
}
function clearFormatterCache() {
  formatterCacheGeneration.bump();
  formatterSignatureFlights.clear();
  detectionCache.clear();
  resetWhichLatches();
}
var formatterConfigBasenames = new Set(FORMATTER_CONFIG_FILES.map((fileName) => fileName.toLowerCase()));
function invalidateFormatterCacheForPath(filePath) {
  const basename2 = filePath.includes("\\") ? path4.win32.basename(filePath) : path4.basename(filePath);
  if (!formatterConfigBasenames.has(basename2.toLowerCase()))
    return;
  clearFormatterCache();
}
function _getFormatterResetStateForTests() {
  return {
    whichLatchByCommand,
    whichTransientCommands,
    cooldownRecordedForRetryAtMs,
    detectionCache,
    formatterSignatureFlights
  };
}
function clearFormatterRuntimeState() {
  clearFormatterCache();
}
var BOX_DRAWING_GLOBAL = /[\u2500-\u257F]/g;
var DECORATION_RULE = /^[\u2500-\u257F\s]*$/;
var DECORATION_HEADING = /^\S+[ \t]+[\u2500-\u257F][\u2500-\u257F\s]*$/;
function firstDiagnosticLine(text) {
  for (const raw of (text ?? "").split("\n")) {
    const line = stripAnsi(raw).trimEnd();
    if (isDecorativeLine(line))
      continue;
    return line.replace(BOX_DRAWING_GLOBAL, "").trim().slice(0, 300);
  }
  return void 0;
}
function isDecorativeLine(line) {
  return DECORATION_RULE.test(line) || DECORATION_HEADING.test(line);
}
var FORMATTER_ERROR_TAIL_LINES = 20;
function diagnosticTail(text, maxLines = FORMATTER_ERROR_TAIL_LINES) {
  const lines = [];
  for (const raw of (text ?? "").split("\n")) {
    const line = stripAnsi(raw).trimEnd();
    if (isDecorativeLine(line))
      continue;
    lines.push(line.slice(0, 300));
  }
  if (lines.length === 0)
    return void 0;
  return lines.slice(-maxLines).join("\n");
}
async function resolveFormatterCommand(formatter, absolutePath, cwd) {
  const agreement = establishToolAgreement(formatter.name, cwd);
  if (agreement.decision === "decline") {
    recordDegradationOnce({
      kind: "formatter-agreement-unavailable",
      subject: agreement.subject,
      reason: agreement.reason
    });
    return FORMATTER_AGREEMENT_UNAVAILABLE;
  }
  const resolved = formatter.resolveCommand ? await formatter.resolveCommand(absolutePath, cwd) : null;
  if (resolved === SKIP_FORMATTING)
    return SKIP_FORMATTING;
  if (resolved === FORMATTER_UNAVAILABLE)
    return FORMATTER_UNAVAILABLE;
  if (resolved !== null)
    return resolved;
  const fallback = formatter.command.map((c) => c.replace("$FILE", absolutePath));
  if (fallback[0] === "npx" && !assertInstallAllowed(`formatter npx fallback: ${formatter.name}`)) {
    return SKIP_FORMATTING;
  }
  return fallback;
}
async function formatFile(filePath, formatter, enter) {
  try {
    const absolutePath = path4.resolve(filePath);
    const cwd = path4.dirname(absolutePath);
    const formatterCwd = resolveToolCwd("formatter", formatter.name, absolutePath, { cwd: path4.parse(absolutePath).root }).cwd;
    const cmd = await resolveFormatterCommand(formatter, absolutePath, cwd);
    if (cmd === SKIP_FORMATTING) {
      return { success: true, changed: false, outcome: "skipped" };
    }
    if (cmd === FORMATTER_UNAVAILABLE) {
      return {
        success: true,
        changed: false,
        outcome: "unavailable",
        error: `${formatter.name}: formatter executable not found`
      };
    }
    if (cmd === FORMATTER_AGREEMENT_UNAVAILABLE) {
      return {
        success: true,
        changed: false,
        outcome: "unavailable",
        error: `${formatter.name}: project tool agreement could not be established`
      };
    }
    await enter?.();
    const contentBefore = await fs2.readFile(absolutePath, "utf-8");
    const result = await safeSpawnAsync(cmd[0], cmd.slice(1), {
      timeout: 15e3,
      cwd: formatterCwd
    });
    if (result.spawnFailure?.kind === "tool-not-found") {
      return {
        success: true,
        changed: false,
        outcome: "unavailable",
        error: result.spawnFailure.message
      };
    }
    const lenientOk = formatter.lenientExitCode !== void 0 && result.status !== null && (formatter.lenientStatuses ?? []).includes(result.status);
    if (result.error || result.status !== 0 && !lenientOk) {
      return {
        success: false,
        changed: false,
        outcome: "failed",
        error: result.error?.message || diagnosticTail(result.stderr) || // biome, ktlint and `mix format` report on STDOUT; without this
        // their diagnostic is discarded and the user is told only that
        // the tool "exited with status 1".
        diagnosticTail(result.stdout) || `${formatter.name} exited with status ${result.status}`
      };
    }
    const contentAfter = await fs2.readFile(absolutePath, "utf-8");
    const changed = contentBefore !== contentAfter;
    return {
      success: true,
      changed,
      outcome: changed ? "formatted" : "unchanged"
    };
  } catch (err) {
    return {
      success: false,
      changed: false,
      outcome: "failed",
      error: err instanceof Error ? err.message : String(err)
    };
  }
}
function listAllFormatters() {
  return ALL_FORMATTERS.map((f) => f.name);
}

export {
  tryLazyInstallFormatterTool,
  SKIP_FORMATTING,
  FORMATTER_UNAVAILABLE,
  FORMATTER_AGREEMENT_UNAVAILABLE,
  _findUpForTests,
  FORMATTERS_WITH_EXPLICIT_CONFIG_CHECK,
  resolveFormatterCwd,
  biomeFormatter,
  prettierFormatter,
  oxfmtFormatter,
  ruffFormatter,
  blackFormatter,
  sqlfluffFormatter,
  gofmtFormatter,
  rustfmtFormatter,
  zigFormatter,
  dartFormatter,
  shfmtFormatter,
  nixfmtFormatter,
  mixFormatter,
  ocamlformatFormatter,
  clangFormatFormatter,
  ktlintFormatter,
  ktfmtFormatter,
  rubocopFormatter,
  standardrbFormatter,
  gleamFormatter,
  typstyleFormatter,
  terraformFormatter,
  terragruntHclFormatter,
  phpCsFixerFormatter,
  csharpierFormatter,
  fantomasFormatter,
  swiftformatFormatter,
  styluaFormatter,
  ormoluFormatter,
  taploFormatter,
  googleJavaFormatFormatter,
  cljfmtFormatter,
  cmakeFormatFormatter,
  cueFormatter,
  psscriptanalyzerFormatFormatter,
  ALL_FORMATTERS,
  getFormattersForFile,
  clearFormatterCache,
  invalidateFormatterCacheForPath,
  _getFormatterResetStateForTests,
  clearFormatterRuntimeState,
  firstDiagnosticLine,
  diagnosticTail,
  formatFile,
  listAllFormatters
};