@zoisythe/codex-codeintel 0.10.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -28,6 +28,13 @@ var init_budgets = __esm({
28
28
  merge: 200,
29
29
  mergeMax: 1e3,
30
30
  warmupDelay: 2e3,
31
+ warmup: 2e4,
32
+ planTtl: 30 * 60 * 1e3,
33
+ validation: 3e4,
34
+ validationServer: 1e4,
35
+ validationExtraFiles: 20,
36
+ validationFiles: 20,
37
+ validationBytes: 2 * 1024 * 1024,
31
38
  idle: 3e5,
32
39
  cleanup: 1e3,
33
40
  postWait: 3500,
@@ -535,9 +542,10 @@ var init_results = __esm({
535
542
  "src/results.ts"() {
536
543
  "use strict";
537
544
  WriteFailure = class extends Error {
538
- constructor(reason, modifiedPaths) {
545
+ constructor(reason, modifiedPaths, planFailure) {
539
546
  super(reason);
540
547
  this.modifiedPaths = modifiedPaths;
548
+ this.planFailure = planFailure;
541
549
  }
542
550
  };
543
551
  }
@@ -618,7 +626,7 @@ function server(value, language, source) {
618
626
  const item = builtin ?? value;
619
627
  if (!record(item)) throw new Error(`lsp.${language} must be a built-in name, custom server or false`);
620
628
  for (const key of Object.keys(item))
621
- if (!["command", "extensions", "env", "initialization"].includes(key))
629
+ if (!["command", "extensions", "env", "initialization", "settings", "languageId"].includes(key))
622
630
  throw new Error(`Invalid lsp.${language}.${key}; priority/disabled were removed; use false to disable`);
623
631
  const command = item["command"];
624
632
  const extensions = item["extensions"];
@@ -628,10 +636,15 @@ function server(value, language, source) {
628
636
  throw new Error(`lsp.${language}.extensions requires dot-prefixed extensions`);
629
637
  const env = item["env"];
630
638
  const initialization = item["initialization"];
639
+ const settings = item["settings"];
640
+ const languageId = item["languageId"];
631
641
  if (env !== void 0 && (!record(env) || !Object.values(env).every((v) => typeof v === "string")))
632
642
  throw new Error(`Invalid lsp.${language}.env`);
633
643
  if (initialization !== void 0 && !record(initialization))
634
644
  throw new Error(`Invalid lsp.${language}.initialization`);
645
+ if (settings !== void 0 && !record(settings)) throw new Error(`Invalid lsp.${language}.settings`);
646
+ if (languageId !== void 0 && (typeof languageId !== "string" || !languageId.trim()))
647
+ throw new Error(`Invalid lsp.${language}.languageId`);
635
648
  return {
636
649
  id: typeof value === "string" ? value : language,
637
650
  command,
@@ -639,7 +652,9 @@ function server(value, language, source) {
639
652
  explicit: !builtin,
640
653
  source,
641
654
  ...env ? { env } : {},
642
- ...initialization ? { initialization } : {}
655
+ ...initialization ? { initialization } : {},
656
+ ...settings ? { settings } : {},
657
+ ...typeof languageId === "string" ? { languageId } : {}
643
658
  };
644
659
  }
645
660
  function freeze(value) {
@@ -1284,9 +1299,9 @@ function validateCwd(cwd) {
1284
1299
  }
1285
1300
  }
1286
1301
  function wrap(proc) {
1287
- const exitedPromise = new Promise((resolve13) => {
1288
- proc.once("close", (code) => resolve13(code ?? 0));
1289
- proc.once("error", () => resolve13(1));
1302
+ const exitedPromise = new Promise((resolve16) => {
1303
+ proc.once("close", (code) => resolve16(code ?? 0));
1304
+ proc.once("error", () => resolve16(1));
1290
1305
  });
1291
1306
  if (!proc.stdin || !proc.stdout || !proc.stderr) {
1292
1307
  throw new LspProcessSpawnError("Spawned process is missing one of stdin/stdout/stderr pipes");
@@ -1573,11 +1588,11 @@ async function findTool(root, path, command, explicit = false, allowTemporary =
1573
1588
  const name = command[0];
1574
1589
  if (!name) throw new Error("Empty command");
1575
1590
  const executableName = basename3(name).replace(/\.(?:exe|cmd|bat)$/i, "");
1576
- if (automaticExecution() && (["uvx", "npx", "pipx"].includes(executableName) || executableName === "uv" && command[1] === "tool" && command[2] === "run"))
1591
+ if ((automaticExecution() || !allowTemporary) && (["uvx", "npx", "pipx"].includes(executableName) || executableName === "uv" && command[1] === "tool" && command[2] === "run"))
1577
1592
  return resolution(
1578
1593
  [...command],
1579
1594
  "missing",
1580
- `Automatic temporary launcher disabled: ${name}; install a local server or use explicit active MCP`
1595
+ `${automaticExecution() ? "Automatic" : "Local-only"} temporary launcher disabled: ${name}; install a local server`
1581
1596
  );
1582
1597
  if (explicit) {
1583
1598
  const entry = isAbsolute4(name) || name.includes("/") || name.includes("\\") ? resolve2(root, name) : await pathExecutable(name);
@@ -1638,10 +1653,10 @@ function languageFor(config, path) {
1638
1653
  if (server2 !== false && server2.extensions.includes(extname(path))) return { language, server: server2 };
1639
1654
  return void 0;
1640
1655
  }
1641
- async function resolveServer(root, path, config) {
1656
+ async function resolveServer(root, path, config, allowTemporary = true) {
1642
1657
  const entry = languageFor(config, path);
1643
1658
  if (!entry) throw new Error(`Language disabled or unsupported: ${extname(path) || path}`);
1644
- const tool2 = await resolveTool(root, path, entry.server.command, entry.server.explicit);
1659
+ const tool2 = await resolveTool(root, path, entry.server.command, entry.server.explicit, allowTemporary);
1645
1660
  return {
1646
1661
  language: entry.language,
1647
1662
  tool: tool2,
@@ -1651,7 +1666,9 @@ async function resolveServer(root, path, config) {
1651
1666
  extensions: [...entry.server.extensions],
1652
1667
  priority: 0,
1653
1668
  ...entry.server.env ? { env: { ...entry.server.env } } : {},
1654
- ...entry.server.initialization ? { initialization: { ...entry.server.initialization } } : {}
1669
+ ...entry.server.initialization ? { initialization: { ...entry.server.initialization } } : {},
1670
+ ...entry.server.settings ? { settings: { ...entry.server.settings } } : {},
1671
+ ...entry.server.languageId ? { languageId: entry.server.languageId } : {}
1655
1672
  }
1656
1673
  };
1657
1674
  }
@@ -1690,7 +1707,7 @@ import { basename as basename4, dirname as dirname4, extname as extname2, join a
1690
1707
  async function run(command, args, cwd, signal, input, options = {}) {
1691
1708
  signal.throwIfAborted();
1692
1709
  const environment = subprocessEnvironment(options.environment ?? executionEnvironment());
1693
- return new Promise((resolve13, reject) => {
1710
+ return new Promise((resolve16, reject) => {
1694
1711
  const prepared = createSpawnCommand([command, ...args], process.platform, environment["ComSpec"], environment);
1695
1712
  const child = spawn2(prepared.command, prepared.args, {
1696
1713
  ...HIDDEN_PROCESS,
@@ -1733,13 +1750,49 @@ async function run(command, args, cwd, signal, input, options = {}) {
1733
1750
  if (code !== 0 && code !== 1 && !signal.aborted) void logEvent("abnormal-exit");
1734
1751
  if (signal.aborted) reject(new Error("Runner cancelled"));
1735
1752
  else if (exceeded) reject(new Error("Runner exceeded time/output budget"));
1736
- else resolve13({ stdout, stderr, code: code ?? -1 });
1753
+ else resolve16({ stdout, stderr, code: code ?? -1 });
1737
1754
  });
1738
1755
  child.stdin.on("error", () => {
1739
1756
  });
1740
1757
  child.stdin.end(input);
1741
1758
  });
1742
1759
  }
1760
+ async function lintContent(runner, root, path, content, signal) {
1761
+ if (runner.name === "biome") throw new Error("unsupported: Biome diagnostics require a private project snapshot");
1762
+ const absolute = await workspacePath(root, path);
1763
+ const cwd = await lintDirectory(root, absolute);
1764
+ if (runner.name === "eslint") {
1765
+ const printed = await run(runner.command, [...runner.prefix, "--print-config", absolute], cwd, signal);
1766
+ if (printed.code !== 0) throw new Error(printed.stderr || "ESLint configuration unavailable");
1767
+ const config = JSON.parse(printed.stdout);
1768
+ if (!record(config)) throw new Error("unsupported: ESLint file has no matching configuration");
1769
+ const options = record(config["languageOptions"]) ? config["languageOptions"]["parserOptions"] : config["parserOptions"];
1770
+ if (record(options) && (options["project"] || options["projectService"]))
1771
+ throw new Error(
1772
+ "unsupported: type-aware ESLint project services cannot consume all plan overlays; use TypeScript diagnostics for types"
1773
+ );
1774
+ }
1775
+ const args = runner.name === "ruff" ? [
1776
+ "check",
1777
+ "--no-fix",
1778
+ "--no-fix-only",
1779
+ "--no-cache",
1780
+ "--output-format",
1781
+ "json",
1782
+ "--stdin-filename",
1783
+ absolute,
1784
+ "-"
1785
+ ] : ["--stdin", "--stdin-filename", absolute, "--format", "json", "--no-warn-ignored"];
1786
+ const output = await run(runner.command, [...runner.prefix, ...args], cwd, signal, content);
1787
+ if (![0, 1].includes(output.code)) throw new Error(output.stderr || `Runner exit ${output.code}`);
1788
+ const data = JSON.parse(output.stdout);
1789
+ if (runner.name === "eslint" && (!Array.isArray(data) || data.length !== 1))
1790
+ throw new Error("unsupported: ESLint ignored the file or produced no report");
1791
+ const findings = parseLint(runner.name, data, path, content);
1792
+ if (findings.some((finding) => /ignored|not processed|configuration.*not found/i.test(finding.message)))
1793
+ throw new Error("unsupported: lint file ignored or configuration unavailable");
1794
+ return findings.map(({ lint: _lint, ...finding }) => finding);
1795
+ }
1743
1796
  async function exists(path) {
1744
1797
  try {
1745
1798
  await access3(path);
@@ -2981,6 +3034,9 @@ import { randomUUID as randomUUID5 } from "node:crypto";
2981
3034
  import { lstat as lstat4, readFile as readFile11, writeFile as writeFile4 } from "node:fs/promises";
2982
3035
  import { relative as relative4 } from "node:path";
2983
3036
  import { fileURLToPath as fileURLToPath2 } from "node:url";
3037
+ function isPlanFailureCode(value) {
3038
+ return typeof value === "string" && ["consumed", "expired", "evicted", "unavailable", "stale", "conflict"].includes(value);
3039
+ }
2984
3040
  async function fileIdentity(path) {
2985
3041
  const info = await lstat4(path);
2986
3042
  if (!info.isFile() || info.size > BUDGET.fileBytes) throw new Error("Unsupported or oversized edit target");
@@ -3038,22 +3094,49 @@ function diffLines(file) {
3038
3094
  ...after.slice(start, endAfter).map((line) => `+${line}`)
3039
3095
  ];
3040
3096
  }
3041
- var EditPlans;
3097
+ var PlanFailure, EditPlans;
3042
3098
  var init_edit_plans = __esm({
3043
3099
  "src/edit-plans.ts"() {
3044
3100
  "use strict";
3045
3101
  init_budgets();
3046
3102
  init_files();
3047
3103
  init_results();
3104
+ PlanFailure = class extends Error {
3105
+ constructor(planFailure, reason) {
3106
+ super(reason);
3107
+ this.planFailure = planFailure;
3108
+ }
3109
+ };
3048
3110
  EditPlans = class {
3049
- constructor() {
3111
+ constructor(now = Date.now) {
3112
+ this.now = now;
3050
3113
  this.plans = /* @__PURE__ */ new Map();
3114
+ this.ended = /* @__PURE__ */ new Map();
3115
+ }
3116
+ remove(id, reason) {
3117
+ this.plans.delete(id);
3118
+ this.ended.set(id, { reason, until: this.now() + BUDGET.planTtl });
3119
+ while (this.ended.size > 64) this.ended.delete(this.ended.keys().next().value ?? "");
3120
+ }
3121
+ prune() {
3122
+ const now = this.now();
3123
+ for (const [id, entry] of this.ended) if (entry.until <= now) this.ended.delete(id);
3124
+ for (const [id, plan] of this.plans) if (Date.parse(plan.expiresAt) <= now) this.remove(id, "expired");
3125
+ }
3126
+ hasLivePlans() {
3127
+ this.prune();
3128
+ return [...this.plans.values()].some((plan) => plan.files.length > 0);
3051
3129
  }
3052
3130
  create(identity, files, paths = files.map((file) => file.path)) {
3131
+ this.prune();
3132
+ const now = this.now();
3053
3133
  const plan = {
3054
3134
  id: randomUUID5(),
3055
3135
  identity,
3056
3136
  paths,
3137
+ createdAt: new Date(now).toISOString(),
3138
+ expiresAt: new Date(now + BUDGET.planTtl).toISOString(),
3139
+ storage: "memory",
3057
3140
  files: files.filter((file) => file.before !== file.after).sort((a, b) => a.path.localeCompare(b.path))
3058
3141
  };
3059
3142
  if (files.length > 200 || files.reduce((sum, file) => sum + Buffer.byteLength(file.before + file.after), 0) > 8 * BUDGET.outputBytes)
@@ -3061,29 +3144,37 @@ var init_edit_plans = __esm({
3061
3144
  this.plans.set(plan.id, plan);
3062
3145
  const memory = () => [...this.plans.values()].flatMap((plan2) => plan2.files).reduce((sum, file) => sum + Buffer.byteLength(file.before + file.after), 0);
3063
3146
  while (this.plans.size > 16 || memory() > 8 * BUDGET.outputBytes)
3064
- this.plans.delete(this.plans.keys().next().value ?? "");
3147
+ this.remove(this.plans.keys().next().value ?? "", "evicted");
3065
3148
  return plan;
3066
3149
  }
3067
3150
  get(id) {
3151
+ this.prune();
3068
3152
  const plan = this.plans.get(id);
3069
- if (!plan) throw new Error("Plan missing, consumed or expired; generate a new preview");
3153
+ if (!plan) {
3154
+ const reason = this.ended.get(id)?.reason ?? "unavailable";
3155
+ throw new PlanFailure(reason, `Plan missing: ${reason}; generate a new preview`);
3156
+ }
3070
3157
  return plan;
3071
3158
  }
3072
3159
  clear() {
3073
3160
  this.plans.clear();
3161
+ this.ended.clear();
3074
3162
  }
3075
- discard(id) {
3076
- this.plans.delete(id);
3163
+ discard(id, reason = "stale") {
3164
+ if (this.plans.has(id)) this.remove(id, reason);
3077
3165
  }
3078
3166
  async apply(id, identity, root, signal) {
3079
3167
  const plan = this.get(id);
3080
- this.plans.delete(id);
3168
+ this.remove(id, "consumed");
3081
3169
  if (plan.identity !== identity)
3082
- throw new Error("Plan stale: workspace, configuration, tools or environment changed; preview again");
3170
+ throw new PlanFailure(
3171
+ "stale",
3172
+ "Plan stale: workspace, configuration, tools or environment changed; preview again"
3173
+ );
3083
3174
  const validate = async (file) => {
3084
3175
  const actual = await workspacePath(root, file.path);
3085
3176
  if (actual !== file.absolute || await fileIdentity(actual) !== file.fileIdentity || await readFile11(actual, "utf8") !== file.before)
3086
- throw new Error(`Plan conflict: ${file.path}`);
3177
+ throw new PlanFailure("conflict", `Plan conflict: ${file.path}`);
3087
3178
  };
3088
3179
  for (const file of plan.files) {
3089
3180
  signal.throwIfAborted();
@@ -3104,7 +3195,7 @@ var init_edit_plans = __esm({
3104
3195
  } catch (error) {
3105
3196
  if (writing && !modified.includes(writing.path) && await readFile11(writing.absolute, "utf8").catch(() => null) !== writing.before)
3106
3197
  modified.push(writing.path);
3107
- throw new WriteFailure(message(error), modified);
3198
+ throw new WriteFailure(message(error), modified, error instanceof PlanFailure ? error.planFailure : void 0);
3108
3199
  }
3109
3200
  return modified;
3110
3201
  }
@@ -3179,6 +3270,10 @@ var init_output_pages = __esm({
3179
3270
  "state",
3180
3271
  "job",
3181
3272
  "plan",
3273
+ "createdAt",
3274
+ "expiresAt",
3275
+ "storage",
3276
+ "planFailure",
3182
3277
  "partial",
3183
3278
  "errors",
3184
3279
  "warnings",
@@ -3233,6 +3328,66 @@ var init_output_pages = __esm({
3233
3328
  }
3234
3329
  });
3235
3330
 
3331
+ // src/plan-validation.ts
3332
+ function diagnosticFindings(path, items2) {
3333
+ return items2.filter((item) => item.severity === 1 || item.severity === 2).map((item) => ({
3334
+ path,
3335
+ line: item.range.start.line + 1,
3336
+ column: item.range.start.character + 1,
3337
+ severity: item.severity === 1 ? "error" : "warning",
3338
+ source: `${item.source ?? "lsp"}${item.code === void 0 ? "" : `/${item.code}`}`,
3339
+ ...item.code !== void 0 ? { rule: String(item.code) } : {},
3340
+ endLine: item.range.end.line + 1,
3341
+ endColumn: item.range.end.character + 1,
3342
+ message: item.message
3343
+ }));
3344
+ }
3345
+ function validationResult(state, paths, reason, before = [], after = [], provider = "ty") {
3346
+ const introduced = addedFindings(before, after);
3347
+ const resolved = addedFindings(after, before);
3348
+ const errorsIntroduced = state === "complete" ? introduced.filter((item) => item.severity === "error").length : null;
3349
+ const errorsResolved = state === "complete" ? resolved.filter((item) => item.severity === "error").length : null;
3350
+ return {
3351
+ state,
3352
+ provider,
3353
+ paths,
3354
+ introduced,
3355
+ resolved,
3356
+ errorsIntroduced,
3357
+ errorsResolved,
3358
+ netDelta: errorsIntroduced === null || errorsResolved === null ? null : errorsIntroduced - errorsResolved,
3359
+ limitations: [
3360
+ "Only listed files and this provider were checked; builds and runtime behavior were not validated. Zero delta is not a safety guarantee."
3361
+ ],
3362
+ ...reason ? { reason } : {}
3363
+ };
3364
+ }
3365
+ function combineValidation(paths, checks) {
3366
+ const active = checks.filter((check) => check.state !== "skipped");
3367
+ const complete = active.filter((check) => check.state === "complete");
3368
+ const state = complete.length === active.length && complete.length ? "complete" : complete.length ? "partial" : active.some((check) => check.state === "pending") ? "pending" : active.some((check) => check.state === "failed") ? "failed" : active.length ? "unsupported" : "skipped";
3369
+ const result = validationResult(state, paths, void 0, [], [], "combined");
3370
+ result.checks = checks;
3371
+ result.introduced = complete.flatMap((check) => check.introduced);
3372
+ result.resolved = complete.flatMap((check) => check.resolved);
3373
+ if (state === "complete") {
3374
+ result.errorsIntroduced = complete.reduce((sum, check) => sum + (check.errorsIntroduced ?? 0), 0);
3375
+ result.errorsResolved = complete.reduce((sum, check) => sum + (check.errorsResolved ?? 0), 0);
3376
+ result.netDelta = result.errorsIntroduced - result.errorsResolved;
3377
+ }
3378
+ result.limitations = [
3379
+ "Only changed files and explicit validationPaths were checked; other dependents, builds and runtime behavior were not validated.",
3380
+ "Counts are provider observations; different providers can report the same problem. Consult checks for per-provider coverage and completeness."
3381
+ ];
3382
+ return result;
3383
+ }
3384
+ var init_plan_validation = __esm({
3385
+ "src/plan-validation.ts"() {
3386
+ "use strict";
3387
+ init_diagnostic_delta();
3388
+ }
3389
+ });
3390
+
3236
3391
  // src/symbols.ts
3237
3392
  import { readFile as readFile12 } from "node:fs/promises";
3238
3393
  import { relative as relative5 } from "node:path";
@@ -3397,6 +3552,7 @@ var init_intelligence = __esm({
3397
3552
  init_edit_plans();
3398
3553
  init_files();
3399
3554
  init_output_pages();
3555
+ init_plan_validation();
3400
3556
  init_results();
3401
3557
  init_runners();
3402
3558
  init_symbols();
@@ -3429,14 +3585,33 @@ var init_intelligence = __esm({
3429
3585
  while (this.actions.size > 512) this.actions.delete(this.actions.keys().next().value ?? "");
3430
3586
  return value;
3431
3587
  }
3432
- preview(identity, files) {
3588
+ async preview(identity, files, args, signal) {
3433
3589
  const plan = this.plans.create(identity, files, this.scope);
3590
+ let validation2;
3591
+ try {
3592
+ validation2 = args["validate"] === false ? validationResult(
3593
+ "skipped",
3594
+ plan.files.map((file) => file.path),
3595
+ "Validation explicitly disabled"
3596
+ ) : await this.language.validatePlan(
3597
+ plan.files,
3598
+ signal,
3599
+ Array.isArray(args["validationPaths"]) ? args["validationPaths"].filter((path) => typeof path === "string") : []
3600
+ );
3601
+ } catch (error) {
3602
+ this.plans.discard(plan.id);
3603
+ throw error;
3604
+ }
3434
3605
  return {
3435
3606
  status: plan.files.length ? "planned" : "unchanged",
3436
3607
  plan: plan.id,
3608
+ createdAt: plan.createdAt,
3609
+ expiresAt: plan.expiresAt,
3610
+ storage: plan.storage,
3437
3611
  files: plan.files.map((file) => file.path),
3438
3612
  diff: plan.files.flatMap(diffLines),
3439
- note: "Text edit plan only; apply writes it. Type/build effects have not been simulated."
3613
+ validation: validation2,
3614
+ note: "Only apply writes this single-use plan. Validation is scoped evidence, not a build or behavior guarantee; restart or eviction may invalidate it before expiry."
3440
3615
  };
3441
3616
  }
3442
3617
  async execute(operation, args, paths, config, identity, signal, bindings = paths) {
@@ -3446,7 +3621,7 @@ var init_intelligence = __esm({
3446
3621
  if (operation === "navigate") output = await this.language.navigate(args, signal);
3447
3622
  else if (operation === "rename") {
3448
3623
  const edit = await this.language.renameEdit(args, signal);
3449
- output = this.preview(identity, await workspaceEdits(this.root, edit));
3624
+ output = await this.preview(identity, await workspaceEdits(this.root, edit), args, signal);
3450
3625
  } else if (operation === "format") {
3451
3626
  if (!config.formattingEnabled) throw new Error("Configuration error: formatting disabled");
3452
3627
  const files = [];
@@ -3458,7 +3633,7 @@ var init_intelligence = __esm({
3458
3633
  const after = formatted?.after ?? applyTextChanges(before, await this.language.formatEdits(path, signal));
3459
3634
  files.push(await plannedFile(this.root, path, after, before));
3460
3635
  }
3461
- output = this.preview(identity, files);
3636
+ output = await this.preview(identity, files, args, signal);
3462
3637
  } else if (operation === "inspect")
3463
3638
  output = args["operation"] === "impact" ? await this.impact(args, paths, signal) : await this.inspect(args, signal);
3464
3639
  else if (operation === "actions") output = await this.actionRequest(args, paths, config, identity, signal);
@@ -3755,7 +3930,10 @@ ${String(docs["url"])}` : "") + "\nConfiguration source origin is unknown.";
3755
3930
  group.before
3756
3931
  )
3757
3932
  );
3758
- return { ...this.preview(identity, files), ...issues.length ? { partial: true, issues } : {} };
3933
+ return {
3934
+ ...await this.preview(identity, files, args, signal),
3935
+ ...issues.length ? { partial: true, issues } : {}
3936
+ };
3759
3937
  }
3760
3938
  return this.listActions(args, paths, config, identity, signal);
3761
3939
  }
@@ -3904,6 +4082,198 @@ ${String(docs["url"])}` : "") + "\nConfiguration source origin is unknown.";
3904
4082
  }
3905
4083
  });
3906
4084
 
4085
+ // src/preview-workspace.ts
4086
+ import { access as access4, mkdir as mkdir5, mkdtemp, readdir as readdir4, readFile as readFile14, rm as rm4, stat as stat5, symlink, writeFile as writeFile5 } from "node:fs/promises";
4087
+ import { tmpdir as tmpdir3 } from "node:os";
4088
+ import { dirname as dirname6, join as join11, relative as relative7, resolve as resolve8, sep as sep2 } from "node:path";
4089
+ async function withPreviewWorkspace(root, files, config, signal, use) {
4090
+ const snapshot = await inventory(root, 2e3, signal, config.exclude);
4091
+ if (!snapshot.complete) throw new Error("unsupported: preview snapshot exceeds 2000 files or is incomplete");
4092
+ const temporary2 = await mkdtemp(join11(tmpdir3(), "codeintel-preview-"));
4093
+ const shadow = join11(temporary2, "workspace");
4094
+ const map = (path) => {
4095
+ const absolute = resolve8(root, path);
4096
+ if (!inside(root, absolute)) throw new Error("Preview path outside workspace");
4097
+ return join11(shadow, relative7(root, absolute));
4098
+ };
4099
+ const translate = (text2) => {
4100
+ for (const [from, to] of [
4101
+ [root.replaceAll("\\", "/"), shadow.replaceAll("\\", "/")],
4102
+ [root.replaceAll("\\", "\\\\"), shadow.replaceAll("\\", "\\\\")],
4103
+ [root, shadow]
4104
+ ]) {
4105
+ text2 = text2 === from ? to : text2;
4106
+ text2 = text2.replaceAll(`${from}/`, `${to}/`).replaceAll(`${from}\\`, `${to}\\`).replaceAll(`"${from}"`, `"${to}"`).replaceAll(`=${from}`, `=${to}`);
4107
+ }
4108
+ return text2;
4109
+ };
4110
+ try {
4111
+ await mkdir5(shadow);
4112
+ let bytes2 = 0;
4113
+ const selected = /* @__PURE__ */ new Set([...snapshot.files.keys(), ...files.map((file) => file.path)]);
4114
+ for (const file of files)
4115
+ for (let dir = dirname6(file.absolute); inside(root, dir); dir = dirname6(dir)) {
4116
+ for (const name of [
4117
+ "biome.json",
4118
+ "biome.jsonc",
4119
+ ".clangd",
4120
+ "compile_commands.json",
4121
+ "compile_flags.txt",
4122
+ "package.json"
4123
+ ])
4124
+ if (await access4(join11(dir, name)).then(
4125
+ () => true,
4126
+ () => false
4127
+ ))
4128
+ selected.add(relative7(root, join11(dir, name)));
4129
+ if (dir === root || dirname6(dir) === dir) break;
4130
+ }
4131
+ if (selected.size > 2e3) throw new Error("unsupported: preview snapshot exceeds 2000 files");
4132
+ const names = [...selected];
4133
+ let copied = names.length;
4134
+ for (const path of names) {
4135
+ signal.throwIfAborted();
4136
+ const absolute = await workspacePath(root, path);
4137
+ bytes2 += (await stat5(absolute)).size;
4138
+ if (bytes2 > 32 * 1024 * 1024) throw new Error("unsupported: preview snapshot exceeds 32 MiB");
4139
+ const destination = map(path);
4140
+ await mkdir5(dirname6(destination), { recursive: true });
4141
+ const content = await readFile14(absolute);
4142
+ const configuration2 = /(?:^|[\\/])(?:biome\.jsonc?|\.clangd|compile_commands\.json|compile_flags\.txt)$/.test(
4143
+ path
4144
+ );
4145
+ await writeFile5(destination, configuration2 ? translate(content.toString("utf8")) : content);
4146
+ }
4147
+ for (const name of ["build", "dist", "target", "vendor"]) {
4148
+ const directory2 = join11(root, name);
4149
+ if (!(await stat5(directory2).catch(() => void 0))?.isDirectory()) continue;
4150
+ await mkdir5(map(name), { recursive: true });
4151
+ for (const entry of await readdir4(directory2, { withFileTypes: true })) {
4152
+ signal.throwIfAborted();
4153
+ const path = join11(name, entry.name), destination = map(path);
4154
+ if (await access4(destination).then(
4155
+ () => true,
4156
+ () => false
4157
+ ))
4158
+ continue;
4159
+ if (entry.name === "compile_commands.json" || /\.(?:h|hh|hpp|hxx|inc|def|modulemap|json|txt|cmake)$/.test(entry.name)) {
4160
+ if (++copied > 2e3) throw new Error("unsupported: preview snapshot exceeds 2000 copied files");
4161
+ bytes2 += (await stat5(join11(root, path))).size;
4162
+ if (bytes2 > 32 * 1024 * 1024) throw new Error("unsupported: preview snapshot exceeds 32 MiB");
4163
+ const content = await readFile14(join11(root, path), "utf8");
4164
+ await writeFile5(destination, entry.name === "compile_commands.json" ? translate(content) : content);
4165
+ } else if (entry.isDirectory())
4166
+ await symlink(join11(root, path), destination, process.platform === "win32" ? "junction" : "dir");
4167
+ }
4168
+ }
4169
+ const linked = /* @__PURE__ */ new Set();
4170
+ for (const file of files)
4171
+ for (let dir = dirname6(file.absolute); inside(root, dir); dir = dirname6(dir)) {
4172
+ const dependencies = join11(dir, "node_modules");
4173
+ if (!linked.has(dependencies) && await access4(dependencies).then(
4174
+ () => true,
4175
+ () => false
4176
+ )) {
4177
+ const destination = map(dependencies);
4178
+ await mkdir5(dirname6(destination), { recursive: true });
4179
+ await symlink(dependencies, destination, process.platform === "win32" ? "junction" : "dir");
4180
+ linked.add(dependencies);
4181
+ }
4182
+ if (dir === root || dirname6(dir) === dir || relative7(root, dir).split(sep2).includes("node_modules")) break;
4183
+ }
4184
+ return await use({
4185
+ root: shadow,
4186
+ map,
4187
+ translate,
4188
+ writePhase: async (after) => {
4189
+ for (const file of files) {
4190
+ signal.throwIfAborted();
4191
+ await writeFile5(map(file.path), after ? file.after : file.before);
4192
+ }
4193
+ }
4194
+ });
4195
+ } finally {
4196
+ await rm4(temporary2, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
4197
+ }
4198
+ }
4199
+ var init_preview_workspace = __esm({
4200
+ "src/preview-workspace.ts"() {
4201
+ "use strict";
4202
+ init_files();
4203
+ }
4204
+ });
4205
+
4206
+ // src/lint-preview.ts
4207
+ import { resolve as resolve9 } from "node:path";
4208
+ async function validateBiome(runner, root, scope2, files, config, signal) {
4209
+ return withPreviewWorkspace(root, scope2, config, signal, async (workspace) => {
4210
+ const collect = async (after2) => {
4211
+ await workspace.writePhase(after2);
4212
+ const environment = executionEnvironment();
4213
+ if (environment["BIOME_CONFIG_PATH"])
4214
+ environment["BIOME_CONFIG_PATH"] = workspace.translate(environment["BIOME_CONFIG_PATH"]);
4215
+ const output = await run(
4216
+ runner.command,
4217
+ [
4218
+ ...runner.prefix,
4219
+ "lint",
4220
+ "--reporter=json",
4221
+ "--max-diagnostics=none",
4222
+ "--vcs-enabled=false",
4223
+ ...files.map((file) => workspace.map(file.path))
4224
+ ],
4225
+ workspace.root,
4226
+ signal,
4227
+ void 0,
4228
+ { environment }
4229
+ );
4230
+ if (![0, 1].includes(output.code)) throw new Error(output.stderr || `Biome exit ${output.code}`);
4231
+ const data = JSON.parse(output.stdout);
4232
+ if (!record(data) || !record(data["summary"]) || Number(data["summary"]["skipped"]) > 0 || Number(data["summary"]["changed"]) + Number(data["summary"]["unchanged"]) !== files.length)
4233
+ throw new Error("unsupported: Biome did not process every requested file");
4234
+ const split = splitLint("biome", data, workspace.root, workspace.root);
4235
+ const findings = [];
4236
+ for (const file of files) {
4237
+ const path = workspace.map(file.path);
4238
+ const relative12 = path.slice(resolve9(workspace.root).length + 1);
4239
+ findings.push(
4240
+ ...parseLint(
4241
+ "biome",
4242
+ split.get(relative12) ?? { diagnostics: [] },
4243
+ file.path,
4244
+ after2 ? file.after : file.before
4245
+ ).map(({ lint: _lint, ...finding }) => finding)
4246
+ );
4247
+ }
4248
+ return findings;
4249
+ };
4250
+ const before = await collect(false), after = await collect(true);
4251
+ const result = validationResult(
4252
+ "complete",
4253
+ files.map((file) => file.path),
4254
+ void 0,
4255
+ before,
4256
+ after,
4257
+ "biome"
4258
+ );
4259
+ result.limitations.push(
4260
+ "Bounded temporary project snapshot; VCS discovery disabled after workspace file selection. Project file includes and rule configuration still apply."
4261
+ );
4262
+ return result;
4263
+ });
4264
+ }
4265
+ var init_lint_preview = __esm({
4266
+ "src/lint-preview.ts"() {
4267
+ "use strict";
4268
+ init_environment();
4269
+ init_lint_output();
4270
+ init_plan_validation();
4271
+ init_preview_workspace();
4272
+ init_results();
4273
+ init_runners();
4274
+ }
4275
+ });
4276
+
3907
4277
  // src/lsp/constants.ts
3908
4278
  var REQUEST_TIMEOUT_MS, INIT_TIMEOUT_MS, IDLE_TIMEOUT_MS, REAPER_INTERVAL_MS, STOP_HARD_KILL_TIMEOUT_MS, STOP_SIGKILL_GRACE_MS;
3909
4279
  var init_constants = __esm({
@@ -4016,10 +4386,10 @@ var init_json_rpc_connection = __esm({
4016
4386
  const id = this.nextRequestId;
4017
4387
  this.nextRequestId += 1;
4018
4388
  const message2 = params === void 0 ? { jsonrpc: "2.0", id, method } : { jsonrpc: "2.0", id, method, params };
4019
- const responsePromise = new Promise((resolve13, reject) => {
4389
+ const responsePromise = new Promise((resolve16, reject) => {
4020
4390
  this.pendingRequests.set(String(id), {
4021
4391
  resolve(result) {
4022
- resolve13(result);
4392
+ resolve16(result);
4023
4393
  },
4024
4394
  reject
4025
4395
  });
@@ -4164,13 +4534,13 @@ var init_json_rpc_connection = __esm({
4164
4534
  const payload = `Content-Length: ${Buffer.byteLength(body, "utf8")}\r
4165
4535
  \r
4166
4536
  ${body}`;
4167
- return new Promise((resolve13, reject) => {
4537
+ return new Promise((resolve16, reject) => {
4168
4538
  this.writer.write(payload, (error) => {
4169
4539
  if (error) {
4170
4540
  reject(error);
4171
4541
  return;
4172
4542
  }
4173
- resolve13();
4543
+ resolve16();
4174
4544
  });
4175
4545
  });
4176
4546
  }
@@ -4184,7 +4554,8 @@ ${body}`;
4184
4554
  });
4185
4555
 
4186
4556
  // src/lsp/transport.ts
4187
- import { delimiter as delimiter3 } from "node:path";
4557
+ import { basename as basename5, delimiter as delimiter3 } from "node:path";
4558
+ import { pathToFileURL as pathToFileURL2 } from "node:url";
4188
4559
  function isRecord(value) {
4189
4560
  return typeof value === "object" && value !== null && !Array.isArray(value);
4190
4561
  }
@@ -4238,6 +4609,9 @@ var init_transport = __esm({
4238
4609
  command() {
4239
4610
  return [...this.server.command];
4240
4611
  }
4612
+ configurationSettings() {
4613
+ return { json: { validate: { enable: true } }, ...this.server.settings };
4614
+ }
4241
4615
  async start() {
4242
4616
  const env = {
4243
4617
  ...this.environment,
@@ -4254,7 +4628,7 @@ var init_transport = __esm({
4254
4628
  env
4255
4629
  });
4256
4630
  this.startStderrReading();
4257
- await new Promise((resolve13) => setTimeout(resolve13, 100));
4631
+ await new Promise((resolve16) => setTimeout(resolve16, 100));
4258
4632
  if (this.proc.exitCode !== null) {
4259
4633
  const stderr = this.stderrBuffer.join("\n");
4260
4634
  throw new LspProcessExitedError(this.server.id, this.root, this.proc.exitCode, stderr.slice(-2e3));
@@ -4269,12 +4643,16 @@ var init_transport = __esm({
4269
4643
  this.connection.onRequest("workspace/configuration", (params) => {
4270
4644
  const items2 = parseConfigurationItems(params);
4271
4645
  return items2.map((item) => {
4272
- if (item.section === "json") return { validate: { enable: true } };
4273
- return {};
4646
+ let value = this.configurationSettings();
4647
+ for (const key of item.section?.split(".") ?? []) value = isRecord(value) ? value[key] : void 0;
4648
+ return value ?? {};
4274
4649
  });
4275
4650
  });
4276
4651
  this.connection.onRequest("client/registerCapability", () => null);
4277
4652
  this.connection.onRequest("window/workDoneProgress/create", () => null);
4653
+ this.connection.onRequest("workspace/workspaceFolders", () => [
4654
+ { uri: pathToFileURL2(this.root).href, name: basename5(this.root) }
4655
+ ]);
4278
4656
  this.connection.onClose(() => {
4279
4657
  this.processExited = true;
4280
4658
  });
@@ -4358,8 +4736,8 @@ var init_transport = __esm({
4358
4736
  try {
4359
4737
  proc.kill();
4360
4738
  let timeoutId;
4361
- const timeoutPromise = new Promise((resolve13) => {
4362
- timeoutId = setTimeout(resolve13, STOP_HARD_KILL_TIMEOUT_MS);
4739
+ const timeoutPromise = new Promise((resolve16) => {
4740
+ timeoutId = setTimeout(resolve16, STOP_HARD_KILL_TIMEOUT_MS);
4363
4741
  });
4364
4742
  await Promise.race([
4365
4743
  proc.exited.then(() => {
@@ -4374,7 +4752,7 @@ var init_transport = __esm({
4374
4752
  proc.kill("SIGKILL");
4375
4753
  await Promise.race([
4376
4754
  proc.exited,
4377
- new Promise((resolve13) => setTimeout(resolve13, STOP_SIGKILL_GRACE_MS))
4755
+ new Promise((resolve16) => setTimeout(resolve16, STOP_SIGKILL_GRACE_MS))
4378
4756
  ]);
4379
4757
  } catch (error) {
4380
4758
  reportBestEffortCleanupError("hard process kill", error);
@@ -4395,7 +4773,7 @@ var init_transport = __esm({
4395
4773
  });
4396
4774
 
4397
4775
  // src/lsp/connection.ts
4398
- import { pathToFileURL as pathToFileURL2 } from "node:url";
4776
+ import { pathToFileURL as pathToFileURL3 } from "node:url";
4399
4777
  var INITIALIZE_SETTLE_MS, LspClientConnection;
4400
4778
  var init_connection = __esm({
4401
4779
  "src/lsp/connection.ts"() {
@@ -4404,7 +4782,7 @@ var init_connection = __esm({
4404
4782
  INITIALIZE_SETTLE_MS = 300;
4405
4783
  LspClientConnection = class extends LspClientTransport {
4406
4784
  async initialize() {
4407
- const rootUri = pathToFileURL2(this.root).href;
4785
+ const rootUri = pathToFileURL3(this.root).href;
4408
4786
  await this.sendRequest("initialize", {
4409
4787
  processId: process.pid,
4410
4788
  rootUri,
@@ -4466,7 +4844,7 @@ var init_connection = __esm({
4466
4844
  });
4467
4845
  await this.sendNotification("initialized");
4468
4846
  await this.sendNotification("workspace/didChangeConfiguration", {
4469
- settings: { json: { validate: { enable: true } } }
4847
+ settings: this.configurationSettings()
4470
4848
  });
4471
4849
  await new Promise((r) => setTimeout(r, INITIALIZE_SETTLE_MS));
4472
4850
  }
@@ -4619,8 +4997,8 @@ var init_language_mappings = __esm({
4619
4997
 
4620
4998
  // src/lsp/client.ts
4621
4999
  import { readFileSync as readFileSync2 } from "node:fs";
4622
- import { extname as extname4, resolve as resolve8 } from "node:path";
4623
- import { pathToFileURL as pathToFileURL3 } from "node:url";
5000
+ import { extname as extname4, resolve as resolve10 } from "node:path";
5001
+ import { pathToFileURL as pathToFileURL4 } from "node:url";
4624
5002
  var POST_OPEN_DELAY_MS, POST_DIAGNOSTICS_WAIT_MS, LspClient;
4625
5003
  var init_client = __esm({
4626
5004
  "src/lsp/client.ts"() {
@@ -4641,12 +5019,12 @@ var init_client = __esm({
4641
5019
  return this.diagnosticPullErrors;
4642
5020
  }
4643
5021
  async openFile(filePath) {
4644
- const absPath = resolve8(filePath);
4645
- const uri = pathToFileURL3(absPath).href;
5022
+ const absPath = resolve10(filePath);
5023
+ const uri = pathToFileURL4(absPath).href;
4646
5024
  const text2 = readFileSync2(absPath, "utf-8");
4647
5025
  if (!this.openedFiles.has(absPath)) {
4648
5026
  const ext = extname4(absPath);
4649
- const languageId = getLanguageId(ext);
5027
+ const languageId = this.server.languageId ?? getLanguageId(ext);
4650
5028
  const version = 1;
4651
5029
  await this.sendNotification("textDocument/didOpen", {
4652
5030
  textDocument: {
@@ -4679,30 +5057,30 @@ var init_client = __esm({
4679
5057
  });
4680
5058
  }
4681
5059
  async definition(filePath, line, character) {
4682
- const absPath = resolve8(filePath);
5060
+ const absPath = resolve10(filePath);
4683
5061
  await this.openFile(absPath);
4684
5062
  return this.sendRequest(
4685
5063
  "textDocument/definition",
4686
5064
  {
4687
- textDocument: { uri: pathToFileURL3(absPath).href },
5065
+ textDocument: { uri: pathToFileURL4(absPath).href },
4688
5066
  position: { line: line - 1, character }
4689
5067
  }
4690
5068
  );
4691
5069
  }
4692
5070
  async references(filePath, line, character, includeDeclaration = true) {
4693
- const absPath = resolve8(filePath);
5071
+ const absPath = resolve10(filePath);
4694
5072
  await this.openFile(absPath);
4695
5073
  return this.sendRequest("textDocument/references", {
4696
- textDocument: { uri: pathToFileURL3(absPath).href },
5074
+ textDocument: { uri: pathToFileURL4(absPath).href },
4697
5075
  position: { line: line - 1, character },
4698
5076
  context: { includeDeclaration }
4699
5077
  });
4700
5078
  }
4701
5079
  async documentSymbols(filePath) {
4702
- const absPath = resolve8(filePath);
5080
+ const absPath = resolve10(filePath);
4703
5081
  await this.openFile(absPath);
4704
5082
  return this.sendRequest("textDocument/documentSymbol", {
4705
- textDocument: { uri: pathToFileURL3(absPath).href }
5083
+ textDocument: { uri: pathToFileURL4(absPath).href }
4706
5084
  });
4707
5085
  }
4708
5086
  async workspaceSymbols(query) {
@@ -4715,8 +5093,8 @@ var init_client = __esm({
4715
5093
  return /unsupported|not supported|method not found|unknown request/i.test(error.message);
4716
5094
  }
4717
5095
  async diagnostics(filePath) {
4718
- const absPath = resolve8(filePath);
4719
- const uri = pathToFileURL3(absPath).href;
5096
+ const absPath = resolve10(filePath);
5097
+ const uri = pathToFileURL4(absPath).href;
4720
5098
  await this.openFile(absPath);
4721
5099
  await new Promise((r) => setTimeout(r, POST_DIAGNOSTICS_WAIT_MS));
4722
5100
  try {
@@ -4734,21 +5112,21 @@ var init_client = __esm({
4734
5112
  return { items: this.getStoredDiagnostics(uri) };
4735
5113
  }
4736
5114
  async prepareRename(filePath, line, character) {
4737
- const absPath = resolve8(filePath);
5115
+ const absPath = resolve10(filePath);
4738
5116
  await this.openFile(absPath);
4739
5117
  return this.sendRequest(
4740
5118
  "textDocument/prepareRename",
4741
5119
  {
4742
- textDocument: { uri: pathToFileURL3(absPath).href },
5120
+ textDocument: { uri: pathToFileURL4(absPath).href },
4743
5121
  position: { line: line - 1, character }
4744
5122
  }
4745
5123
  );
4746
5124
  }
4747
5125
  async rename(filePath, line, character, newName) {
4748
- const absPath = resolve8(filePath);
5126
+ const absPath = resolve10(filePath);
4749
5127
  await this.openFile(absPath);
4750
5128
  return this.sendRequest("textDocument/rename", {
4751
- textDocument: { uri: pathToFileURL3(absPath).href },
5129
+ textDocument: { uri: pathToFileURL4(absPath).href },
4752
5130
  position: { line: line - 1, character },
4753
5131
  newName
4754
5132
  });
@@ -4845,7 +5223,7 @@ async function stopClientBestEffort(client) {
4845
5223
  }
4846
5224
  function awaitWithSignal(promise, signal) {
4847
5225
  if (!signal) return promise;
4848
- return new Promise((resolve13, reject) => {
5226
+ return new Promise((resolve16, reject) => {
4849
5227
  let settled = false;
4850
5228
  const onAbort = () => {
4851
5229
  if (settled) return;
@@ -4862,7 +5240,7 @@ function awaitWithSignal(promise, signal) {
4862
5240
  if (settled) return;
4863
5241
  settled = true;
4864
5242
  signal.removeEventListener("abort", onAbort);
4865
- resolve13(value);
5243
+ resolve16(value);
4866
5244
  },
4867
5245
  (err) => {
4868
5246
  if (settled) return;
@@ -5139,7 +5517,7 @@ function createSharedAbortableOperation(run2, onSettled, onAbandoned) {
5139
5517
  function awaitSharedAbortableOperation(operation, signal) {
5140
5518
  signal?.throwIfAborted();
5141
5519
  operation.waiterCount += 1;
5142
- return new Promise((resolve13, reject) => {
5520
+ return new Promise((resolve16, reject) => {
5143
5521
  let settled = false;
5144
5522
  const onAbort = () => {
5145
5523
  if (settled) return;
@@ -5155,7 +5533,7 @@ function awaitSharedAbortableOperation(operation, signal) {
5155
5533
  settled = true;
5156
5534
  signal?.removeEventListener("abort", onAbort);
5157
5535
  releaseSharedOperationWaiter(operation);
5158
- resolve13(value);
5536
+ resolve16(value);
5159
5537
  },
5160
5538
  (error) => {
5161
5539
  if (settled) return;
@@ -5175,7 +5553,7 @@ var init_abortable_shared_operation = __esm({
5175
5553
 
5176
5554
  // src/lsp/cargo-manifest-snapshot.ts
5177
5555
  import { readFileSync as readFileSync3 } from "node:fs";
5178
- import { dirname as dirname6, join as join11 } from "node:path";
5556
+ import { dirname as dirname7, join as join12 } from "node:path";
5179
5557
  function isMissingManifestError(error) {
5180
5558
  if (!(error instanceof Error)) return false;
5181
5559
  const code = "code" in error ? error.code : void 0;
@@ -5207,13 +5585,13 @@ function ancestorManifestPaths(manifestDir) {
5207
5585
  let dir = manifestDir;
5208
5586
  let prev = "";
5209
5587
  while (dir !== prev) {
5210
- const manifestPath = join11(dir, "Cargo.toml");
5588
+ const manifestPath = join12(dir, "Cargo.toml");
5211
5589
  if (!seen.has(manifestPath)) {
5212
5590
  seen.add(manifestPath);
5213
5591
  paths.push(manifestPath);
5214
5592
  }
5215
5593
  prev = dir;
5216
- dir = dirname6(dir);
5594
+ dir = dirname7(dir);
5217
5595
  }
5218
5596
  return paths;
5219
5597
  }
@@ -5657,7 +6035,7 @@ function skipVoid(str, ptr, banNewLines, banComments) {
5657
6035
  }
5658
6036
  return ptr;
5659
6037
  }
5660
- function skipUntil(str, ptr, sep4, end, banNewLines = false) {
6038
+ function skipUntil(str, ptr, sep5, end, banNewLines = false) {
5661
6039
  if (!end) {
5662
6040
  ptr = indexOfNewline(str, ptr);
5663
6041
  return ptr < 0 ? str.length : ptr;
@@ -5666,7 +6044,7 @@ function skipUntil(str, ptr, sep4, end, banNewLines = false) {
5666
6044
  let c = str[i];
5667
6045
  if (c === "#") {
5668
6046
  i = indexOfNewline(str, i);
5669
- } else if (c === sep4) {
6047
+ } else if (c === sep5) {
5670
6048
  return i + 1;
5671
6049
  } else if (c === end || banNewLines && (c === "\n" || c === "\r" && str[i + 1] === "\n")) {
5672
6050
  return i;
@@ -6222,7 +6600,7 @@ var init_dist = __esm({
6222
6600
 
6223
6601
  // src/lsp/cargo-metadata-parser.ts
6224
6602
  import { readFileSync as readFileSync4, realpathSync, statSync as statSync2 } from "node:fs";
6225
- import { isAbsolute as isAbsolute6, join as join12, relative as relative7, sep as sep2 } from "node:path";
6603
+ import { isAbsolute as isAbsolute6, join as join13, relative as relative8, sep as sep3 } from "node:path";
6226
6604
  function isRecord2(value) {
6227
6605
  return typeof value === "object" && value !== null && !Array.isArray(value);
6228
6606
  }
@@ -6242,7 +6620,7 @@ function canonicalManifest(path) {
6242
6620
  return void 0;
6243
6621
  }
6244
6622
  }
6245
- function readFile14(path) {
6623
+ function readFile15(path) {
6246
6624
  try {
6247
6625
  return readFileSync4(path, "utf8");
6248
6626
  } catch {
@@ -6250,7 +6628,7 @@ function readFile14(path) {
6250
6628
  }
6251
6629
  }
6252
6630
  function readCargoManifestKind(path) {
6253
- const content = readFile14(path);
6631
+ const content = readFile15(path);
6254
6632
  if (content === void 0) return void 0;
6255
6633
  try {
6256
6634
  return Object.hasOwn(parse(content), "workspace") ? "workspace" : "ordinary";
@@ -6259,8 +6637,8 @@ function readCargoManifestKind(path) {
6259
6637
  }
6260
6638
  }
6261
6639
  function isContainedPath(root, path) {
6262
- const relativePath = relative7(root, path);
6263
- return relativePath === "" || !isAbsolute6(relativePath) && relativePath !== ".." && !relativePath.startsWith(`..${sep2}`);
6640
+ const relativePath = relative8(root, path);
6641
+ return relativePath === "" || !isAbsolute6(relativePath) && relativePath !== ".." && !relativePath.startsWith(`..${sep3}`);
6264
6642
  }
6265
6643
  function parseCargoMetadata(output) {
6266
6644
  let parsed;
@@ -6294,7 +6672,7 @@ function parseCargoMetadata(output) {
6294
6672
  function validateCargoMetadata(requestedManifestPath, metadata) {
6295
6673
  const workspaceRoot = canonicalDirectory(metadata.workspaceRoot);
6296
6674
  if (workspaceRoot === void 0) return void 0;
6297
- const rootManifestPath = canonicalManifest(join12(workspaceRoot, "Cargo.toml"));
6675
+ const rootManifestPath = canonicalManifest(join13(workspaceRoot, "Cargo.toml"));
6298
6676
  const requestedManifest = canonicalManifest(requestedManifestPath);
6299
6677
  if (rootManifestPath === void 0 || requestedManifest === void 0) return void 0;
6300
6678
  if (!isContainedPath(workspaceRoot, requestedManifest)) return void 0;
@@ -6386,8 +6764,8 @@ async function defaultCargoMetadataLoader(manifestPath, signal, environment = ex
6386
6764
  let terminationError;
6387
6765
  let settled = false;
6388
6766
  let resolveTermination;
6389
- const terminationComplete = new Promise((resolve13) => {
6390
- resolveTermination = resolve13;
6767
+ const terminationComplete = new Promise((resolve16) => {
6768
+ resolveTermination = resolve16;
6391
6769
  });
6392
6770
  const finishTermination = () => {
6393
6771
  resolveTermination?.();
@@ -6520,7 +6898,7 @@ var init_cargo_metadata_process = __esm({
6520
6898
 
6521
6899
  // src/lsp/cargo-workspace-root.ts
6522
6900
  import { existsSync as existsSync2, realpathSync as realpathSync2 } from "node:fs";
6523
- import { dirname as dirname7, join as join13 } from "node:path";
6901
+ import { dirname as dirname8, join as join14 } from "node:path";
6524
6902
  function realpathSafe(path) {
6525
6903
  try {
6526
6904
  return realpathSync2.native(path);
@@ -6532,9 +6910,9 @@ function nearestCargoManifestDir(startDir) {
6532
6910
  let dir = startDir;
6533
6911
  let prev = "";
6534
6912
  while (dir !== prev) {
6535
- if (existsSync2(join13(dir, "Cargo.toml"))) return dir;
6913
+ if (existsSync2(join14(dir, "Cargo.toml"))) return dir;
6536
6914
  prev = dir;
6537
- dir = dirname7(dir);
6915
+ dir = dirname8(dir);
6538
6916
  }
6539
6917
  return void 0;
6540
6918
  }
@@ -6545,14 +6923,14 @@ function cacheEntryFor(root, memberManifestDir) {
6545
6923
  function prepareCargoWorkspaceCache(manifestDir, metadata) {
6546
6924
  const entries = /* @__PURE__ */ new Map();
6547
6925
  for (const manifestPath of metadata.memberManifestPaths) {
6548
- const manifestDir2 = dirname7(manifestPath);
6926
+ const manifestDir2 = dirname8(manifestPath);
6549
6927
  const entry = cacheEntryFor(metadata.workspaceRoot, manifestDir2);
6550
6928
  if (entry === void 0) return void 0;
6551
6929
  entries.set(manifestDir2, entry);
6552
6930
  }
6553
- const requestedManifestPath = canonicalManifest(join13(manifestDir, "Cargo.toml"));
6931
+ const requestedManifestPath = canonicalManifest(join14(manifestDir, "Cargo.toml"));
6554
6932
  if (requestedManifestPath === void 0) return void 0;
6555
- const requestedEntry = cacheEntryFor(metadata.workspaceRoot, dirname7(requestedManifestPath));
6933
+ const requestedEntry = cacheEntryFor(metadata.workspaceRoot, dirname8(requestedManifestPath));
6556
6934
  if (requestedEntry === void 0) return void 0;
6557
6935
  entries.set(manifestDir, requestedEntry);
6558
6936
  return { root: metadata.workspaceRoot, entries };
@@ -6620,7 +6998,7 @@ function createInFlightCargoWorkspaceRoot(request) {
6620
6998
  async function loadCargoWorkspaceRoot(request) {
6621
6999
  try {
6622
7000
  request.signal?.throwIfAborted();
6623
- const manifestPath = join13(request.manifestDir, "Cargo.toml");
7001
+ const manifestPath = join14(request.manifestDir, "Cargo.toml");
6624
7002
  const output = await request.loader(manifestPath, request.signal);
6625
7003
  request.signal?.throwIfAborted();
6626
7004
  if (!snapshotsAreFresh(request.generation.snapshots)) {
@@ -6699,7 +7077,7 @@ var init_cargo_workspace_root = __esm({
6699
7077
 
6700
7078
  // src/lsp/workspace-root.ts
6701
7079
  import { existsSync as existsSync3, statSync as statSync3 } from "node:fs";
6702
- import { dirname as dirname8, join as join14, resolve as resolve9 } from "node:path";
7080
+ import { dirname as dirname9, join as join15, resolve as resolve11 } from "node:path";
6703
7081
  function isDirectoryPath(filePath) {
6704
7082
  try {
6705
7083
  return statSync3(filePath).isDirectory();
@@ -6708,10 +7086,10 @@ function isDirectoryPath(filePath) {
6708
7086
  }
6709
7087
  }
6710
7088
  async function findWorkspaceRoot(filePath, server2, options = {}) {
6711
- const abs = resolve9(filePath);
7089
+ const abs = resolve11(filePath);
6712
7090
  let dir = abs;
6713
7091
  if (!isDirectoryPath(dir)) {
6714
- dir = dirname8(dir);
7092
+ dir = dirname9(dir);
6715
7093
  }
6716
7094
  if (server2?.id === "rust") {
6717
7095
  const cargoRoot = await resolveCargoWorkspaceRoot(dir, options);
@@ -6721,14 +7099,14 @@ async function findWorkspaceRoot(filePath, server2, options = {}) {
6721
7099
  while (dir !== prevDir) {
6722
7100
  const markers = server2?.extensions.some((extension) => [".c", ".cpp", ".cc", ".h"].includes(extension)) ? [".clangd", "compile_commands.json", "compile_flags.txt", ...WORKSPACE_MARKERS] : WORKSPACE_MARKERS;
6723
7101
  for (const marker of markers) {
6724
- if (existsSync3(join14(dir, marker))) {
7102
+ if (existsSync3(join15(dir, marker))) {
6725
7103
  return dir;
6726
7104
  }
6727
7105
  }
6728
7106
  prevDir = dir;
6729
- dir = dirname8(dir);
7107
+ dir = dirname9(dir);
6730
7108
  }
6731
- return dirname8(abs);
7109
+ return dirname9(abs);
6732
7110
  }
6733
7111
  var WORKSPACE_MARKERS;
6734
7112
  var init_workspace_root = __esm({
@@ -6741,13 +7119,13 @@ var init_workspace_root = __esm({
6741
7119
 
6742
7120
  // src/language.ts
6743
7121
  import { AsyncLocalStorage as AsyncLocalStorage3 } from "node:async_hooks";
6744
- import { readFile as readFile15 } from "node:fs/promises";
6745
- import { dirname as dirname9, extname as extname5, resolve as resolve10 } from "node:path";
6746
- import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL4 } from "node:url";
7122
+ import { readFile as readFile16, stat as stat6 } from "node:fs/promises";
7123
+ import { basename as basename6, dirname as dirname10, extname as extname5, resolve as resolve12 } from "node:path";
7124
+ import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL5 } from "node:url";
6747
7125
  function diagnosticUriKey(uri) {
6748
7126
  try {
6749
7127
  const path = fileURLToPath5(uri);
6750
- return pathToFileURL4(
7128
+ return pathToFileURL5(
6751
7129
  process.platform === "win32" ? path.replace(/^[A-Z]:/, (drive) => drive.toLowerCase()) : path
6752
7130
  ).href;
6753
7131
  } catch {
@@ -6762,12 +7140,15 @@ var init_language = __esm({
6762
7140
  init_config_files();
6763
7141
  init_environment();
6764
7142
  init_files();
7143
+ init_lint_preview();
6765
7144
  init_log();
6766
7145
  init_client();
6767
7146
  init_language_mappings();
6768
7147
  init_manager();
6769
7148
  init_workspace_root();
6770
7149
  init_metrics();
7150
+ init_plan_validation();
7151
+ init_preview_workspace();
6771
7152
  init_results();
6772
7153
  init_runners();
6773
7154
  init_symbols();
@@ -6813,7 +7194,7 @@ var init_language = __esm({
6813
7194
  const busy = () => this.serverBusy || this.progress.size > 0 || Date.now() - this.progressAt < BUDGET.lspQuiet;
6814
7195
  while (busy() && Date.now() < deadline) {
6815
7196
  signal?.throwIfAborted();
6816
- await new Promise((resolve13) => setTimeout(resolve13, 25));
7197
+ await new Promise((resolve16) => setTimeout(resolve16, 25));
6817
7198
  }
6818
7199
  signal?.throwIfAborted();
6819
7200
  return !busy();
@@ -6917,7 +7298,7 @@ var init_language = __esm({
6917
7298
  if (!this.capabilities[capability[method] ?? ""]) return { status: "unsupported", operation: method };
6918
7299
  await this.openFile(path);
6919
7300
  return this.sendRequest(`textDocument/${method}`, {
6920
- textDocument: { uri: pathToFileURL4(path).href },
7301
+ textDocument: { uri: pathToFileURL5(path).href },
6921
7302
  position: { line: line - 1, character }
6922
7303
  });
6923
7304
  }
@@ -6956,10 +7337,12 @@ var init_language = __esm({
6956
7337
  });
6957
7338
  }
6958
7339
  async openFile(path) {
6959
- const uri = pathToFileURL4(path).href;
6960
- const content = await readFile15(path, "utf8");
7340
+ await this.openContent(path, await readFile16(path, "utf8"));
7341
+ }
7342
+ async openContent(path, content, force = false) {
7343
+ const uri = pathToFileURL5(path).href;
6961
7344
  const previous = this.versions.get(uri);
6962
- if (previous?.content === content && !this.dirty.has(uri)) return;
7345
+ if (!force && previous?.content === content && !this.dirty.has(uri)) return;
6963
7346
  this.dirty.delete(uri);
6964
7347
  this.published.delete(diagnosticUriKey(uri));
6965
7348
  const version = (previous?.version ?? 0) + 1;
@@ -6971,11 +7354,93 @@ var init_language = __esm({
6971
7354
  });
6972
7355
  } else {
6973
7356
  await this.sendNotification("textDocument/didOpen", {
6974
- textDocument: { uri, version, languageId: getLanguageId(extname5(path)), text: content }
7357
+ textDocument: {
7358
+ uri,
7359
+ version,
7360
+ languageId: this.server.languageId ?? getLanguageId(extname5(path)),
7361
+ text: content
7362
+ }
6975
7363
  });
6976
7364
  }
6977
7365
  if (this.isRustAnalyzer()) this.progressAt = Date.now();
6978
7366
  }
7367
+ async overlayDiagnostics(path, signal) {
7368
+ if (!this.connection) throw new Error("LSP connection unavailable");
7369
+ const uri = pathToFileURL5(path).href;
7370
+ const version = this.versions.get(uri)?.version;
7371
+ if (!this.capabilities["diagnosticProvider"]) {
7372
+ if (this.server.command.some((part) => /typescript-language-server/.test(part)))
7373
+ return this.typescriptOverlayDiagnostics(path, signal);
7374
+ return this.versionedOverlayDiagnostics(uri, version, signal);
7375
+ }
7376
+ const result = await this.connection.sendRequest(
7377
+ "textDocument/diagnostic",
7378
+ { textDocument: { uri } },
7379
+ signal
7380
+ );
7381
+ signal.throwIfAborted();
7382
+ if (result.kind !== "full" && this.server.command.some((part) => /gopls(?:\.exe)?$/.test(part)))
7383
+ return this.versionedOverlayDiagnostics(uri, version, signal);
7384
+ if (version !== this.versions.get(uri)?.version || result.kind !== "full" || !Array.isArray(result.items))
7385
+ throw new Error("pending: no full diagnostics for the expected overlay version");
7386
+ return result.items;
7387
+ }
7388
+ async versionedOverlayDiagnostics(uri, version, signal) {
7389
+ while (!signal.aborted) {
7390
+ const published = this.published.get(diagnosticUriKey(uri));
7391
+ if (published && version !== void 0 && published.version === version && Date.now() - published.time >= BUDGET.lspQuiet && !this.progress.size)
7392
+ return published.items;
7393
+ await new Promise((resolve16) => setTimeout(resolve16, 25));
7394
+ }
7395
+ signal.throwIfAborted();
7396
+ throw new Error("pending: no versioned overlay diagnostics");
7397
+ }
7398
+ async reassertOverlay(path, content) {
7399
+ if (this.server.command.some((part) => /clangd(?:\.exe)?$/.test(part))) {
7400
+ await this.openContent(path, `${content}
7401
+ `, true);
7402
+ await this.query("textDocument/documentSymbol", { textDocument: { uri: pathToFileURL5(path).href } });
7403
+ }
7404
+ await this.openContent(path, content, true);
7405
+ }
7406
+ async typescriptOverlayDiagnostics(path, signal) {
7407
+ if (!this.connection) throw new Error("LSP connection unavailable");
7408
+ const items2 = [];
7409
+ for (const command of ["syntacticDiagnosticsSync", "semanticDiagnosticsSync"]) {
7410
+ const response = await this.connection.sendRequest(
7411
+ "workspace/executeCommand",
7412
+ {
7413
+ command: "typescript.tsserverRequest",
7414
+ arguments: [
7415
+ command,
7416
+ { file: path, includeLinePosition: false },
7417
+ { executionTarget: 0, expectsResult: true, isAsync: false }
7418
+ ]
7419
+ },
7420
+ signal
7421
+ );
7422
+ if (!record(response) || !Array.isArray(response["body"]) || response["success"] === false)
7423
+ throw new Error("unsupported: synchronous tsserver diagnostics unavailable");
7424
+ for (const item of response["body"]) {
7425
+ if (!record(item) || !record(item["start"]) || !record(item["end"]) || typeof item["text"] !== "string")
7426
+ throw new Error("Invalid tsserver diagnostic");
7427
+ const at = (value) => {
7428
+ const line = value["line"], offset = value["offset"];
7429
+ if (typeof line !== "number" || typeof offset !== "number" || line < 1 || offset < 1)
7430
+ throw new Error("Invalid tsserver diagnostic position");
7431
+ return { line: line - 1, character: offset - 1 };
7432
+ };
7433
+ items2.push({
7434
+ range: { start: at(item["start"]), end: at(item["end"]) },
7435
+ severity: item["category"] === "error" ? 1 : 2,
7436
+ message: item["text"],
7437
+ source: "typescript",
7438
+ ...typeof item["code"] === "number" ? { code: item["code"] } : {}
7439
+ });
7440
+ }
7441
+ }
7442
+ return items2;
7443
+ }
6979
7444
  async refresh(changes) {
6980
7445
  changes = changes.filter(
6981
7446
  ({ path }) => inside(this.root, path) && (!configurationImpact(path).length || configurationImpact(path).includes("types")) && (this.server.extensions.includes(extname5(path)) || configurationLanguages(path).some(
@@ -6991,7 +7456,7 @@ var init_language = __esm({
6991
7456
  this.pulls.delete(uri);
6992
7457
  }
6993
7458
  for (const { path, type } of changes) {
6994
- const uri = pathToFileURL4(path).href;
7459
+ const uri = pathToFileURL5(path).href;
6995
7460
  if (type === 3) {
6996
7461
  if (this.versions.has(uri)) await this.sendNotification("textDocument/didClose", { textDocument: { uri } });
6997
7462
  this.versions.delete(uri);
@@ -7001,11 +7466,11 @@ var init_language = __esm({
7001
7466
  } else if (this.versions.has(uri)) await this.openFile(path);
7002
7467
  }
7003
7468
  await this.sendNotification("workspace/didChangeWatchedFiles", {
7004
- changes: changes.map(({ path, type }) => ({ uri: pathToFileURL4(path).href, type }))
7469
+ changes: changes.map(({ path, type }) => ({ uri: pathToFileURL5(path).href, type }))
7005
7470
  });
7006
7471
  }
7007
7472
  async collect(path, signal) {
7008
- const uri = pathToFileURL4(path).href;
7473
+ const uri = pathToFileURL5(path).href;
7009
7474
  const cold = !this.versions.has(uri);
7010
7475
  await this.openFile(path);
7011
7476
  const settling = Date.now() + (cold ? BUDGET.lspCold : BUDGET.lspWarm);
@@ -7052,7 +7517,7 @@ var init_language = __esm({
7052
7517
  };
7053
7518
  }
7054
7519
  }
7055
- await new Promise((resolve13) => setTimeout(resolve13, 25));
7520
+ await new Promise((resolve16) => setTimeout(resolve16, 25));
7056
7521
  }
7057
7522
  return { items: [], ready: false };
7058
7523
  }
@@ -7060,12 +7525,12 @@ var init_language = __esm({
7060
7525
  if (!this.supports("format")) throw new Error("unsupported: document formatting");
7061
7526
  await this.openFile(path);
7062
7527
  return await this.sendRequest("textDocument/formatting", {
7063
- textDocument: { uri: pathToFileURL4(path).href },
7528
+ textDocument: { uri: pathToFileURL5(path).href },
7064
7529
  options
7065
7530
  }) ?? [];
7066
7531
  }
7067
7532
  };
7068
- Languages = class {
7533
+ Languages = class _Languages {
7069
7534
  constructor(root, config) {
7070
7535
  this.root = root;
7071
7536
  this.config = config;
@@ -7096,7 +7561,7 @@ var init_language = __esm({
7096
7561
  (path) => files.get(path) !== this.snapshot.get(path)
7097
7562
  );
7098
7563
  const events = changed.map((path) => ({
7099
- path: resolve10(this.root, path),
7564
+ path: resolve12(this.root, path),
7100
7565
  type: !files.has(path) ? 3 : !this.snapshot.has(path) ? 1 : 2
7101
7566
  }));
7102
7567
  this.snapshot = new Map(files);
@@ -7119,7 +7584,7 @@ var init_language = __esm({
7119
7584
  this.config = config;
7120
7585
  }
7121
7586
  async invalidate(path) {
7122
- const directory2 = dirname9(resolve10(this.root, path));
7587
+ const directory2 = dirname10(resolve12(this.root, path));
7123
7588
  const language = configurationImpact(path).length ? void 0 : (await resolveServer(this.root, path, this.config).catch(() => void 0))?.language;
7124
7589
  const languages = language ? [language] : configurationLanguages(path);
7125
7590
  for (const managed of this.manager.getSnapshot()) {
@@ -7139,22 +7604,22 @@ var init_language = __esm({
7139
7604
  reason = message(error);
7140
7605
  }
7141
7606
  const failure = resolved ? [...this.failures.values()].filter(
7142
- (entry) => entry.identity === resolved?.tool.identity && inside(entry.root, resolve10(this.root, path))
7607
+ (entry) => entry.identity === resolved?.tool.identity && inside(entry.root, resolve12(this.root, path))
7143
7608
  ).sort((a, b) => b.root.length - a.root.length)[0] : void 0;
7144
7609
  const identity = resolved ? `${resolved.server.id}:${hash(JSON.stringify([resolved.server, resolved.tool.identity])).slice(0, 16)}` : "";
7145
7610
  const client = [...this.clients].find(
7146
- (client2) => client2.isAlive() && client2.serverIdentity() === identity && inside(client2.workspaceRoot(), resolve10(this.root, path))
7611
+ (client2) => client2.isAlive() && client2.serverIdentity() === identity && inside(client2.workspaceRoot(), resolve12(this.root, path))
7147
7612
  );
7148
7613
  let lint;
7149
7614
  try {
7150
- lint = await select(this.root, resolve10(this.root, path), false, this.config) ?? {
7615
+ lint = await select(this.root, resolve12(this.root, path), false, this.config) ?? {
7151
7616
  unavailable: "Lint disabled or no matching runner"
7152
7617
  };
7153
7618
  } catch (error) {
7154
7619
  lint = { unavailable: message(error) };
7155
7620
  }
7156
7621
  const lintHealth = await lintAvailability(this.root, path, this.config);
7157
- const formatter = await select(this.root, resolve10(this.root, path), true, this.config).catch(() => void 0);
7622
+ const formatter = await select(this.root, resolve12(this.root, path), true, this.config).catch(() => void 0);
7158
7623
  const unavailable = !this.config.valid || !resolved || resolved.tool.source === "missing" || !!failure;
7159
7624
  const status = (operation) => unavailable ? "unavailable" : !client ? "unverified" : client.supports(operation) ? "available" : "unsupported";
7160
7625
  return {
@@ -7196,13 +7661,290 @@ var init_language = __esm({
7196
7661
  }
7197
7662
  );
7198
7663
  }
7199
- async withLspClient(path, fn, _tool, options) {
7200
- const resolved = await resolveServer(this.root, path, this.config);
7664
+ async warmup(paths, signal) {
7665
+ const items2 = [];
7666
+ const seen = /* @__PURE__ */ new Set();
7667
+ for (const path of paths) {
7668
+ const start = Date.now();
7669
+ try {
7670
+ signal.throwIfAborted();
7671
+ const target = await this.serverTarget(await workspacePath(this.root, path), signal, false);
7672
+ const key = JSON.stringify([target.root, target.resolved.server, target.resolved.tool.identity]);
7673
+ if (seen.has(key)) continue;
7674
+ seen.add(key);
7675
+ const reused = this.manager.getSnapshot().some(
7676
+ (entry) => entry.root === target.root && entry.alive && entry.serverId === `${target.resolved.server.id}:${hash(JSON.stringify([target.resolved.server, target.resolved.tool.identity])).slice(0, 16)}`
7677
+ );
7678
+ await withExecution(
7679
+ executionEnvironment(),
7680
+ true,
7681
+ () => this.withLspClient(path, async () => void 0, "warmup", { manager: this.manager, signal, target })
7682
+ );
7683
+ items2.push({ path, status: reused ? "reused" : "initialized", elapsedMs: Date.now() - start });
7684
+ } catch (error) {
7685
+ const reason = message(error);
7686
+ items2.push({
7687
+ path,
7688
+ status: signal.aborted ? "pending" : /missing|disabled|unsupported|launcher disabled/i.test(reason) ? "unsupported" : "failed",
7689
+ reason,
7690
+ elapsedMs: Date.now() - start
7691
+ });
7692
+ }
7693
+ }
7694
+ const ready = items2.filter((entry) => ["initialized", "reused"].includes(entry.status)).length;
7695
+ return {
7696
+ status: ready === items2.length && ready ? "complete" : ready ? "partial" : items2.some((entry) => entry.status === "pending") ? "pending" : items2.some((entry) => entry.status === "failed") ? "failed" : "unsupported",
7697
+ items: items2,
7698
+ note: "Initialization only; no diagnostics baseline collected. Local tools only."
7699
+ };
7700
+ }
7701
+ async validatePlan(files, signal, validationPaths = []) {
7702
+ signal.throwIfAborted();
7703
+ const paths = [.../* @__PURE__ */ new Set([...files.map((file) => file.path), ...validationPaths])];
7704
+ if (!files.length) return validationResult("skipped", paths, "No changed files", [], [], "combined");
7705
+ if (files.length > BUDGET.validationFiles || validationPaths.length > BUDGET.validationExtraFiles)
7706
+ return validationResult(
7707
+ "unsupported",
7708
+ paths,
7709
+ "Validation limit: 20 changed and 20 additional files",
7710
+ [],
7711
+ [],
7712
+ "combined"
7713
+ );
7714
+ const bounded2 = AbortSignal.any([signal, AbortSignal.timeout(BUDGET.validation)]);
7715
+ const scope2 = [...files];
7716
+ let bytes2 = files.reduce((sum, file) => sum + Buffer.byteLength(file.before + file.after), 0);
7717
+ for (const path of validationPaths) {
7718
+ bounded2.throwIfAborted();
7719
+ const absolute = await workspacePath(this.root, path);
7720
+ if (scope2.some((file) => file.absolute === absolute)) continue;
7721
+ bytes2 += 2 * (await stat6(absolute)).size;
7722
+ if (bytes2 > BUDGET.validationBytes)
7723
+ return validationResult(
7724
+ "unsupported",
7725
+ paths,
7726
+ "Validation limit: 2 MiB including additional files",
7727
+ [],
7728
+ [],
7729
+ "combined"
7730
+ );
7731
+ const content = await readFile16(absolute, "utf8");
7732
+ scope2.push({ path, absolute, before: content, after: content, fileIdentity: "validation-only" });
7733
+ }
7734
+ if (scope2.reduce((sum, file) => sum + Buffer.byteLength(file.before + file.after), 0) > BUDGET.validationBytes)
7735
+ return validationResult(
7736
+ "unsupported",
7737
+ paths,
7738
+ "Validation limit: 2 MiB of before/after content including additional files",
7739
+ [],
7740
+ [],
7741
+ "combined"
7742
+ );
7743
+ const reports = [];
7744
+ const jobs = [];
7745
+ const groups = /* @__PURE__ */ new Map();
7746
+ const lintGroups = /* @__PURE__ */ new Map();
7747
+ const failure = (provider, paths2, error, aborted = bounded2.aborted) => {
7748
+ signal.throwIfAborted();
7749
+ const reason = message(error);
7750
+ return validationResult(
7751
+ aborted || /pending|cancel|modified|timeout/i.test(reason) ? "pending" : /missing|not installed|unavailable|unsupported|disabled/i.test(reason) ? "unsupported" : "failed",
7752
+ paths2,
7753
+ reason,
7754
+ [],
7755
+ [],
7756
+ provider
7757
+ );
7758
+ };
7759
+ for (const file of scope2) {
7760
+ const language = codeLanguage(this.config, file.path);
7761
+ const supported = language && ["python", "typescript", "go", "cpp"].includes(language);
7762
+ if (!supported) {
7763
+ reports.push(
7764
+ validationResult(
7765
+ "unsupported",
7766
+ [file.path],
7767
+ "No plan diagnostic provider for this language",
7768
+ [],
7769
+ [],
7770
+ "lsp"
7771
+ )
7772
+ );
7773
+ } else if (this.config.servers[language] !== false) {
7774
+ try {
7775
+ const target = await this.serverTarget(file.absolute, bounded2, false);
7776
+ const provider = language === "python" ? "ty" : language === "typescript" ? "typescript" : language === "go" ? "gopls" : "clangd";
7777
+ const commands = target.resolved.server.command.join(" ");
7778
+ const known = provider === "typescript" ? /typescript-language-server|(?:^|[\\/ ])(?:tsc|tsgo)(?:\.exe|\.cmd)?(?: |$)/i.test(commands) : target.resolved.server.id === provider || new RegExp(provider, "i").test(basename6(target.resolved.tool.command[0] ?? ""));
7779
+ if (!known) throw new Error("unsupported: configured server has no validated overlay adapter");
7780
+ if (provider === "gopls")
7781
+ target.resolved.server.initialization = {
7782
+ ...target.resolved.server.initialization,
7783
+ pullDiagnostics: true,
7784
+ diagnosticsTrigger: "Edit"
7785
+ };
7786
+ if (provider === "clangd") target.resolved.server.command.push("--background-index=false");
7787
+ const key = JSON.stringify([target.root, target.resolved.server, target.resolved.tool.identity]);
7788
+ const group = groups.get(key) ?? { target, provider, files: [] };
7789
+ group.files.push(file);
7790
+ groups.set(key, group);
7791
+ } catch (error) {
7792
+ reports.push(failure(language, [file.path], error));
7793
+ }
7794
+ }
7795
+ try {
7796
+ const runner = await select(this.root, file.absolute, false, this.config, false, bounded2);
7797
+ if (runner) {
7798
+ const key = JSON.stringify(runner);
7799
+ const group = lintGroups.get(key) ?? { runner, files: [] };
7800
+ group.files.push(file);
7801
+ lintGroups.set(key, group);
7802
+ }
7803
+ } catch (error) {
7804
+ reports.push(failure("lint", [file.path], error));
7805
+ }
7806
+ }
7807
+ for (const { target, provider, files: files2 } of groups.values())
7808
+ jobs.push(async () => {
7809
+ if ([...groups.values()].filter((group) => group.target.resolved.language === target.resolved.language).length > 1)
7810
+ return validationResult(
7811
+ "unsupported",
7812
+ files2.map((file) => file.path),
7813
+ "Cross-project overlays for one language are unsupported; narrow the plan and validationPaths to one project",
7814
+ [],
7815
+ [],
7816
+ provider
7817
+ );
7818
+ const checkSignal = AbortSignal.any([bounded2, AbortSignal.timeout(BUDGET.validationServer)]);
7819
+ const paths2 = files2.map((file) => file.path);
7820
+ const run2 = async (root, files3, target2, phase) => {
7821
+ const isolated = new _Languages(root, this.config);
7822
+ try {
7823
+ return await withExecution(
7824
+ executionEnvironment(),
7825
+ true,
7826
+ () => isolated.withLspClient(
7827
+ target2.root,
7828
+ async (client) => {
7829
+ if (!(client instanceof Client)) throw new Error("Unexpected LSP client");
7830
+ const collect = async (after2) => {
7831
+ await phase?.(after2);
7832
+ if (phase && after2)
7833
+ await client.refresh(files3.map((file) => ({ path: file.absolute, type: 2 })));
7834
+ for (const file of files3) {
7835
+ checkSignal.throwIfAborted();
7836
+ await client.openContent(file.absolute, after2 ? file.after : file.before, true);
7837
+ }
7838
+ for (const file of files3) {
7839
+ checkSignal.throwIfAborted();
7840
+ await client.reassertOverlay(file.absolute, after2 ? file.after : file.before);
7841
+ }
7842
+ const findings = [];
7843
+ for (const file of files3)
7844
+ findings.push(
7845
+ ...diagnosticFindings(
7846
+ file.path,
7847
+ await client.overlayDiagnostics(file.absolute, checkSignal)
7848
+ )
7849
+ );
7850
+ return findings;
7851
+ };
7852
+ const before = await collect(false), after = await collect(true);
7853
+ if (provider === "clangd" && before.some(
7854
+ (finding) => /file not found|cannot open (?:source|include) file|unknown argument/i.test(
7855
+ finding.message
7856
+ )
7857
+ ))
7858
+ return validationResult(
7859
+ "unsupported",
7860
+ paths2,
7861
+ "C/C++ baseline compilation context is incomplete",
7862
+ [],
7863
+ [],
7864
+ provider
7865
+ );
7866
+ return validationResult("complete", paths2, void 0, before, after, provider);
7867
+ },
7868
+ "plan validation",
7869
+ { manager: isolated.manager, signal: checkSignal, target: target2 }
7870
+ )
7871
+ );
7872
+ } finally {
7873
+ await isolated.close();
7874
+ }
7875
+ };
7876
+ try {
7877
+ if (provider !== "clangd") return await run2(this.root, files2, target);
7878
+ return await withPreviewWorkspace(this.root, scope2, this.config, checkSignal, async (workspace) => {
7879
+ const mapped = files2.map((file) => ({ ...file, absolute: workspace.map(file.path) }));
7880
+ const shadowTarget = {
7881
+ ...target,
7882
+ root: workspace.map(target.root),
7883
+ resolved: {
7884
+ ...target.resolved,
7885
+ server: {
7886
+ ...target.resolved.server,
7887
+ command: target.resolved.server.command.map(workspace.translate)
7888
+ }
7889
+ }
7890
+ };
7891
+ const result = await run2(workspace.root, mapped, shadowTarget, workspace.writePhase);
7892
+ result.limitations.push(
7893
+ "Bounded temporary project snapshot with private header contents and remapped local compilation paths."
7894
+ );
7895
+ return result;
7896
+ });
7897
+ } catch (error) {
7898
+ return failure(provider, paths2, error, checkSignal.aborted);
7899
+ }
7900
+ });
7901
+ for (const { runner, files: files2 } of lintGroups.values())
7902
+ jobs.push(async () => {
7903
+ const paths2 = files2.map((file) => file.path);
7904
+ try {
7905
+ return await withExecution(executionEnvironment(), true, async () => {
7906
+ if (runner.name === "biome")
7907
+ return validateBiome(runner, this.root, scope2, files2, this.config, bounded2);
7908
+ const before = [], after = [];
7909
+ for (const file of files2) {
7910
+ before.push(...await lintContent(runner, this.root, file.path, file.before, bounded2));
7911
+ after.push(...await lintContent(runner, this.root, file.path, file.after, bounded2));
7912
+ }
7913
+ const result = validationResult("complete", paths2, void 0, before, after, runner.name);
7914
+ result.limitations.push(
7915
+ "Stdin lint is file-level; custom rules reading other files still see original disk contents."
7916
+ );
7917
+ return result;
7918
+ });
7919
+ } catch (error) {
7920
+ return failure(runner.name, paths2, error);
7921
+ }
7922
+ });
7923
+ let next = 0;
7924
+ const completed = new Array(jobs.length);
7925
+ const settled = await Promise.allSettled(
7926
+ Array.from({ length: Math.min(4, jobs.length) }, async () => {
7927
+ while (next < jobs.length) {
7928
+ const index = next++, job = jobs[index];
7929
+ if (job) completed[index] = await job();
7930
+ }
7931
+ })
7932
+ );
7933
+ signal.throwIfAborted();
7934
+ for (const result of settled) if (result.status === "rejected") throw result.reason;
7935
+ return combineValidation(paths, [...reports, ...completed]);
7936
+ }
7937
+ async serverTarget(path, signal, allowTemporary = true) {
7938
+ const resolved = await resolveServer(this.root, path, this.config, allowTemporary);
7201
7939
  if (resolved.tool.source === "missing") throw new Error(resolved.tool.note);
7202
- let root = await findWorkspaceRoot(path, resolved.server, { signal: options.signal });
7940
+ let root = await findWorkspaceRoot(path, resolved.server, { signal });
7203
7941
  if (!inside(this.root, root)) root = this.root;
7204
7942
  if (resolved.language === "python" && resolved.tool.source === "project" && resolved.tool.command[0]?.includes(".venv"))
7205
- root = dirname9(dirname9(dirname9(resolved.tool.command[0])));
7943
+ root = dirname10(dirname10(dirname10(resolved.tool.command[0])));
7944
+ return { root, resolved };
7945
+ }
7946
+ async withLspClient(path, fn, _tool, options) {
7947
+ const { root, resolved } = options.target ?? await this.serverTarget(path, options.signal);
7206
7948
  const failureKey = hash(JSON.stringify([root, resolved.server, resolved.tool.identity]));
7207
7949
  const failed = this.failures.get(failureKey);
7208
7950
  if (failed && Date.now() < failed.retryAt) throw new Error(failed.reason);
@@ -7323,7 +8065,7 @@ var init_language = __esm({
7323
8065
  }
7324
8066
  const pos = await position2(path, args, symbols);
7325
8067
  await client.openFile(path);
7326
- const textDocument = { uri: pathToFileURL4(path).href };
8068
+ const textDocument = { uri: pathToFileURL5(path).href };
7327
8069
  const point = { line: pos.line - 1, character: pos.column - 1 };
7328
8070
  let result;
7329
8071
  if (operation === "rename") {
@@ -7405,8 +8147,8 @@ var init_language = __esm({
7405
8147
 
7406
8148
  // src/engine.ts
7407
8149
  import { randomUUID as randomUUID8 } from "node:crypto";
7408
- import { readFile as readFile16, stat as stat5 } from "node:fs/promises";
7409
- import { dirname as dirname10, relative as relative8, sep as sep3 } from "node:path";
8150
+ import { readFile as readFile17, stat as stat7 } from "node:fs/promises";
8151
+ import { dirname as dirname11, relative as relative9, sep as sep4 } from "node:path";
7410
8152
  var Engine;
7411
8153
  var init_engine = __esm({
7412
8154
  "src/engine.ts"() {
@@ -7414,6 +8156,7 @@ var init_engine = __esm({
7414
8156
  init_budgets();
7415
8157
  init_config();
7416
8158
  init_config_files();
8159
+ init_edit_plans();
7417
8160
  init_environment();
7418
8161
  init_files();
7419
8162
  init_hook_events();
@@ -7464,26 +8207,40 @@ var init_engine = __esm({
7464
8207
  this.generation = 0;
7465
8208
  this.pages = /* @__PURE__ */ new Map();
7466
8209
  }
8210
+ hasLivePlans() {
8211
+ return this.intelligence?.plans.hasLivePlans() ?? false;
8212
+ }
7467
8213
  async warmup(signal) {
7468
8214
  const config = await configuration(this.root);
7469
8215
  if (config.automaticDiagnostics.postToolUse === "off" && config.automaticDiagnostics.stop === "off") return;
7470
- const snapshot = latestInventory(this.root);
7471
- if (!snapshot?.complete) return;
7472
- const representatives = /* @__PURE__ */ new Map();
7473
- for (const path of snapshot.files.keys()) {
7474
- const language = codeLanguage(config, path);
7475
- if (!language || configurationImpact(path).length || ["json", "yaml", "css", "html"].includes(language))
7476
- continue;
7477
- const entry = representatives.get(language);
7478
- if (entry) entry.count++;
7479
- else representatives.set(language, { path, count: 1 });
8216
+ await this.dispatch("warmup", {}, signal);
8217
+ }
8218
+ async prepareServers(args, config, signal) {
8219
+ if (args["cursor"]) throw new Error("Cursor invalid; repeat warmup without cursor");
8220
+ const bounded2 = AbortSignal.any([signal, AbortSignal.timeout(BUDGET.warmup)]);
8221
+ let paths;
8222
+ if (args["path"] || args["paths"]) paths = (await this.paths(args, bounded2)).paths;
8223
+ else {
8224
+ const snapshot = await inventory(this.root, BUDGET.files, bounded2, config.exclude);
8225
+ if (!snapshot.complete)
8226
+ return { operation: "warmup", status: "partial", items: [], reason: "Workspace inventory incomplete" };
8227
+ const representatives = /* @__PURE__ */ new Map();
8228
+ for (const path of [...snapshot.files.keys()].sort()) {
8229
+ const language = codeLanguage(config, path);
8230
+ if (!language || configurationImpact(path).length || ["json", "yaml", "css", "html"].includes(language))
8231
+ continue;
8232
+ const entry = representatives.get(language);
8233
+ if (entry) entry.count++;
8234
+ else representatives.set(language, { path, count: 1 });
8235
+ }
8236
+ paths = [...representatives.entries()].sort(([a, x], [b, y]) => y.count - x.count || a.localeCompare(b)).slice(0, 2).map(([, entry]) => entry.path);
7480
8237
  }
8238
+ if (paths.length > 200) throw new Error("At most 200 explicit warmup files; narrow scope");
7481
8239
  this.language ??= new Languages(this.root, config);
7482
8240
  await this.language.update(config);
7483
- for (const entry of [...representatives.values()].sort((a, b) => b.count - a.count).slice(0, 2)) {
7484
- signal.throwIfAborted();
7485
- await this.language.preflight(entry.path, signal).catch(() => void 0);
7486
- }
8241
+ const result = await this.language.warmup(paths, bounded2);
8242
+ signal.throwIfAborted();
8243
+ return { operation: "warmup", ...result };
7487
8244
  }
7488
8245
  session(id, turn) {
7489
8246
  let session = this.sessions.get(id);
@@ -7538,16 +8295,16 @@ var init_engine = __esm({
7538
8295
  signal.throwIfAborted();
7539
8296
  try {
7540
8297
  const absolute = await workspacePath(this.root, requested);
7541
- const path = relative8(this.root, absolute);
8298
+ const path = relative9(this.root, absolute);
7542
8299
  session.touched.add(path);
7543
8300
  session.current.add(path);
7544
- if ((await stat5(absolute)).size > BUDGET.fileBytes) {
8301
+ if ((await stat7(absolute)).size > BUDGET.fileBytes) {
7545
8302
  results.set(path, { path, state: "skipped", findings: [], note: "File exceeds 1 MiB" });
7546
8303
  continue;
7547
8304
  }
7548
8305
  const identity = await analysisIdentity(this.root, [path], config, signal);
7549
8306
  this.identities.set(path, identity);
7550
- const content = hash(await readFile16(absolute, { encoding: "utf8", signal }));
8307
+ const content = hash(await readFile17(absolute, { encoding: "utf8", signal }));
7551
8308
  const key = `${source === "both" ? "" : `${source}:`}${path}`;
7552
8309
  const cached = this.cache.get(key);
7553
8310
  if (snapshot.complete && cached?.version === this.projectVersion(snapshot, path, config) && cached.identity === identity && cached.content === content && cached.result.state === "complete")
@@ -7574,7 +8331,7 @@ var init_engine = __esm({
7574
8331
  note: "Checker returned no result"
7575
8332
  };
7576
8333
  try {
7577
- if (hash(await readFile16(await workspacePath(this.root, entry.path), { encoding: "utf8", signal })) !== entry.content || await analysisIdentity(this.root, [entry.path], await configuration(this.root), signal) !== entry.identity || this.projectVersion(latestInventory(this.root) ?? snapshot, entry.path, config) !== this.projectVersion(snapshot, entry.path, config)) {
8334
+ if (hash(await readFile17(await workspacePath(this.root, entry.path), { encoding: "utf8", signal })) !== entry.content || await analysisIdentity(this.root, [entry.path], await configuration(this.root), signal) !== entry.identity || this.projectVersion(latestInventory(this.root) ?? snapshot, entry.path, config) !== this.projectVersion(snapshot, entry.path, config)) {
7578
8335
  result.state = "stale";
7579
8336
  result.note = "File/tool/configuration changed during diagnostics; retry";
7580
8337
  }
@@ -7613,13 +8370,13 @@ var init_engine = __esm({
7613
8370
  return withToolResolution(() => this.execute(operation, args, signal));
7614
8371
  });
7615
8372
  this.queue = task.catch(() => void 0);
7616
- return new Promise((resolve13, reject) => {
8373
+ return new Promise((resolve16, reject) => {
7617
8374
  const abort = () => {
7618
8375
  if (!started || !writes) reject(new Error("Request cancelled while queued or executing"));
7619
8376
  };
7620
8377
  signal.addEventListener("abort", abort, { once: true });
7621
8378
  if (signal.aborted) abort();
7622
- void task.then(resolve13, reject).finally(() => signal.removeEventListener("abort", abort));
8379
+ void task.then(resolve16, reject).finally(() => signal.removeEventListener("abort", abort));
7623
8380
  });
7624
8381
  }
7625
8382
  async paths(args, signal) {
@@ -7633,7 +8390,7 @@ var init_engine = __esm({
7633
8390
  if (typeof value !== "string") continue;
7634
8391
  signal.throwIfAborted();
7635
8392
  const absolute = await workspacePath(this.root, value);
7636
- if ((await stat5(absolute)).isFile()) paths.add(relative8(this.root, absolute));
8393
+ if ((await stat7(absolute)).isFile()) paths.add(relative9(this.root, absolute));
7637
8394
  else {
7638
8395
  const scoped = await inventory(this.root, BUDGET.files, signal, config.exclude, absolute);
7639
8396
  complete &&= scoped.complete;
@@ -7713,6 +8470,7 @@ var init_engine = __esm({
7713
8470
  };
7714
8471
  }
7715
8472
  if (!(operation === "diagnostics" && args["run"] === "cached")) assertConfiguration(config);
8473
+ if (operation === "warmup") return this.prepareServers(args, config, signal);
7716
8474
  if (operation === "automatic_batch") {
7717
8475
  const paths = Array.isArray(args["paths"]) ? args["paths"].filter((path) => typeof path === "string") : [];
7718
8476
  return {
@@ -7767,14 +8525,18 @@ var init_engine = __esm({
7767
8525
  .../* @__PURE__ */ new Set([
7768
8526
  ...paths,
7769
8527
  ...typeof args["path"] === "string" ? [args["path"]] : [],
7770
- ...Array.isArray(args["paths"]) ? args["paths"].filter((path) => typeof path === "string") : []
8528
+ ...Array.isArray(args["paths"]) ? args["paths"].filter((path) => typeof path === "string") : [],
8529
+ ...Array.isArray(args["validationPaths"]) ? args["validationPaths"].filter((path) => typeof path === "string") : []
7771
8530
  ])
7772
8531
  ];
7773
8532
  let identity;
7774
8533
  try {
7775
8534
  identity = await this.intelligenceIdentity(bindings, config, signal);
7776
8535
  } catch (error) {
7777
- if (operation === "apply") this.intelligence.plans.discard(text(args["plan"]));
8536
+ if (operation === "apply") {
8537
+ this.intelligence.plans.discard(text(args["plan"]));
8538
+ throw new PlanFailure("stale", `Plan stale: ${message(error)}; preview again`);
8539
+ }
7778
8540
  throw error;
7779
8541
  }
7780
8542
  if (operation === "apply") {
@@ -7798,8 +8560,10 @@ var init_engine = __esm({
7798
8560
  }
7799
8561
  }
7800
8562
  const output = await this.intelligence.execute(operation, args, paths, config, identity, signal, bindings);
7801
- if (identity !== await this.intelligenceIdentity(bindings, config, signal))
8563
+ if (identity !== await this.intelligenceIdentity(bindings, config, signal)) {
8564
+ if (typeof output["plan"] === "string") this.intelligence.plans.discard(output["plan"]);
7802
8565
  throw new Error("Workspace or tools changed during query; repeat without cursor");
8566
+ }
7803
8567
  return { ...output, operation };
7804
8568
  }
7805
8569
  throw new Error("Unknown tool");
@@ -7907,13 +8671,13 @@ var init_engine = __esm({
7907
8671
  for (const value of values) {
7908
8672
  if (typeof value !== "string") throw new Error("paths requires strings");
7909
8673
  const absolute = await workspacePath(this.root, value);
7910
- const path = relative8(this.root, absolute);
7911
- if ((await stat5(absolute)).isFile()) {
8674
+ const path = relative9(this.root, absolute);
8675
+ if ((await stat7(absolute)).isFile()) {
7912
8676
  if (!codeLanguage(config, path)) throw new Error(`Language disabled or unsupported: ${path}`);
7913
8677
  selected2.add(path);
7914
8678
  } else
7915
8679
  for (const candidate of (snapshot.complete ? snapshot : await inventory(this.root, BUDGET.files, signal, config.exclude, absolute)).files.keys())
7916
- if ((!path || candidate.startsWith(`${path}${sep3}`)) && codeLanguage(config, candidate))
8680
+ if ((!path || candidate.startsWith(`${path}${sep4}`)) && codeLanguage(config, candidate))
7917
8681
  selected2.add(candidate);
7918
8682
  }
7919
8683
  paths = [...selected2].sort();
@@ -8024,11 +8788,11 @@ var init_engine = __esm({
8024
8788
  }
8025
8789
  projectVersion(snapshot, path, config) {
8026
8790
  const language = codeLanguage(config, path);
8027
- let owner = dirname10(path);
8791
+ let owner = dirname11(path);
8028
8792
  while (owner !== "." && ![...snapshot.files.keys()].some(
8029
- (candidate) => dirname10(candidate) === owner && configurationLanguages(candidate).includes(language ?? "")
8793
+ (candidate) => dirname11(candidate) === owner && configurationLanguages(candidate).includes(language ?? "")
8030
8794
  ))
8031
- owner = dirname10(owner);
8795
+ owner = dirname11(owner);
8032
8796
  const prefix = owner === "." ? "" : `${owner}/`;
8033
8797
  return hash(
8034
8798
  JSON.stringify(
@@ -8043,9 +8807,9 @@ var init_engine = __esm({
8043
8807
  if (existing) return existing;
8044
8808
  try {
8045
8809
  const absolute = await workspacePath(this.root, path);
8046
- const info = await stat5(absolute);
8810
+ const info = await stat7(absolute);
8047
8811
  if (info.size > BUDGET.fileBytes) return "oversized";
8048
- return hash(await readFile16(absolute, { encoding: "utf8", signal }));
8812
+ return hash(await readFile17(absolute, { encoding: "utf8", signal }));
8049
8813
  } catch {
8050
8814
  signal.throwIfAborted();
8051
8815
  return "unavailable";
@@ -8075,29 +8839,29 @@ var init_engine = __esm({
8075
8839
  // src/ipc.ts
8076
8840
  import { spawn as spawn4 } from "node:child_process";
8077
8841
  import { randomUUID as randomUUID9 } from "node:crypto";
8078
- import { chmod, lstat as lstat5, mkdir as mkdir5, readFile as readFile17, writeFile as writeFile5 } from "node:fs/promises";
8842
+ import { chmod, lstat as lstat5, mkdir as mkdir6, readFile as readFile18, writeFile as writeFile6 } from "node:fs/promises";
8079
8843
  import { createConnection } from "node:net";
8080
- import { homedir as homedir6, tmpdir as tmpdir3 } from "node:os";
8081
- import { join as join15 } from "node:path";
8844
+ import { homedir as homedir6, tmpdir as tmpdir4 } from "node:os";
8845
+ import { join as join16 } from "node:path";
8082
8846
  import { setTimeout as delay2 } from "node:timers/promises";
8083
8847
  import { fileURLToPath as fileURLToPath6 } from "node:url";
8084
8848
  async function serviceLocation(root) {
8085
8849
  const user = process.getuid?.() ?? hash(homedir6()).slice(0, 10);
8086
- const directory2 = join15(tmpdir3(), `clsp6-${user}`);
8087
- await mkdir5(directory2, { recursive: true, mode: 448 });
8850
+ const directory2 = join16(tmpdir4(), `clsp6-${user}`);
8851
+ await mkdir6(directory2, { recursive: true, mode: 448 });
8088
8852
  const info = await lstat5(directory2);
8089
8853
  if (info.isSymbolicLink() || process.platform !== "win32" && (info.uid !== process.getuid?.() || info.mode & 63))
8090
8854
  throw new Error("Unsafe service directory permissions");
8091
8855
  const identity = await workspaceIdentity(root);
8092
- const address = process.platform === "win32" ? `\\\\.\\pipe\\codex-lsp-${user}-${identity}` : join15(directory2, `${identity.slice(0, 32)}.sock`);
8093
- return { directory: directory2, identity, address, endpoint: join15(directory2, `${identity}.endpoint`) };
8856
+ const address = process.platform === "win32" ? `\\\\.\\pipe\\codex-lsp-${user}-${identity}` : join16(directory2, `${identity.slice(0, 32)}.sock`);
8857
+ return { directory: directory2, identity, address, endpoint: join16(directory2, `${identity}.endpoint`) };
8094
8858
  }
8095
8859
  async function readEndpoint(location) {
8096
8860
  try {
8097
8861
  const info = await lstat5(location.endpoint);
8098
8862
  if (info.isSymbolicLink() || process.platform !== "win32" && (info.uid !== process.getuid?.() || info.mode & 63))
8099
8863
  throw new Error("Unsafe service endpoint permissions");
8100
- const value = JSON.parse(await readFile17(location.endpoint, "utf8"));
8864
+ const value = JSON.parse(await readFile18(location.endpoint, "utf8"));
8101
8865
  if (!record(value) || value["identity"] !== location.identity || value["protocol"] !== SERVICE_PROTOCOL || typeof value["pid"] !== "number" || typeof value["token"] !== "string" || value["address"] !== location.address)
8102
8866
  throw new Error("Invalid service endpoint");
8103
8867
  return value;
@@ -8108,7 +8872,7 @@ async function readEndpoint(location) {
8108
8872
  }
8109
8873
  async function exchange(endpoint, operation, args, signal) {
8110
8874
  signal.throwIfAborted();
8111
- return new Promise((resolve13, reject) => {
8875
+ return new Promise((resolve16, reject) => {
8112
8876
  const socket = createConnection(endpoint.address);
8113
8877
  let received = "";
8114
8878
  let settled = false;
@@ -8119,7 +8883,7 @@ async function exchange(endpoint, operation, args, signal) {
8119
8883
  signal.removeEventListener("abort", cancel);
8120
8884
  socket.destroy();
8121
8885
  if (error) reject(error);
8122
- else if (output) resolve13(output);
8886
+ else if (output) resolve16(output);
8123
8887
  else reject(new Error("Missing service output"));
8124
8888
  };
8125
8889
  const cancel = () => {
@@ -8159,8 +8923,9 @@ async function exchange(endpoint, operation, args, signal) {
8159
8923
  finish(
8160
8924
  Array.isArray(paths) ? new WriteFailure(
8161
8925
  response["error"],
8162
- paths.filter((path) => typeof path === "string")
8163
- ) : new Error(response["error"])
8926
+ paths.filter((path) => typeof path === "string"),
8927
+ typeof response["planFailure"] === "string" ? response["planFailure"] : void 0
8928
+ ) : isPlanFailureCode(response["planFailure"]) ? new PlanFailure(response["planFailure"], response["error"]) : new Error(response["error"])
8164
8929
  );
8165
8930
  } else if (record(response["output"]) && typeof response["output"]["operation"] === "string")
8166
8931
  finish(void 0, response["output"]);
@@ -8233,7 +8998,7 @@ async function ensureService(root, signal) {
8233
8998
  async function publishEndpoint(location, token) {
8234
8999
  if (process.platform !== "win32") await chmod(location.address, 384);
8235
9000
  const temporary2 = `${location.endpoint}.${randomUUID9()}`;
8236
- await writeFile5(
9001
+ await writeFile6(
8237
9002
  temporary2,
8238
9003
  JSON.stringify({
8239
9004
  pid: process.pid,
@@ -8251,6 +9016,7 @@ var init_ipc = __esm({
8251
9016
  "use strict";
8252
9017
  init_directory_lock();
8253
9018
  init_directory_lock();
9019
+ init_edit_plans();
8254
9020
  init_environment();
8255
9021
  init_files();
8256
9022
  init_process_options();
@@ -8261,8 +9027,8 @@ var init_ipc = __esm({
8261
9027
 
8262
9028
  // src/hook-inbox.ts
8263
9029
  import { randomUUID as randomUUID10 } from "node:crypto";
8264
- import { lstat as lstat6, readdir as readdir4, readFile as readFile18, rm as rm4, writeFile as writeFile6 } from "node:fs/promises";
8265
- import { join as join16 } from "node:path";
9030
+ import { lstat as lstat6, readdir as readdir5, readFile as readFile19, rm as rm5, writeFile as writeFile7 } from "node:fs/promises";
9031
+ import { join as join17 } from "node:path";
8266
9032
  async function registerHook(root, input, signal) {
8267
9033
  const location = await serviceLocation(root);
8268
9034
  const parent = text(input["session_id"]);
@@ -8328,8 +9094,8 @@ async function registerHook(root, input, signal) {
8328
9094
  return true;
8329
9095
  });
8330
9096
  if (terminal) return { id, session };
8331
- const path = join16(location.directory, `${location.identity}.${id}.hook`);
8332
- await writeFile6(
9097
+ const path = join17(location.directory, `${location.identity}.${id}.hook`);
9098
+ await writeFile7(
8333
9099
  path,
8334
9100
  JSON.stringify({
8335
9101
  protocol: SERVICE_PROTOCOL,
@@ -8350,14 +9116,14 @@ async function registerHook(root, input, signal) {
8350
9116
  async function registeredHooks(root) {
8351
9117
  const location = await serviceLocation(root);
8352
9118
  const result = [];
8353
- for (const name of (await readdir4(location.directory)).sort()) {
9119
+ for (const name of (await readdir5(location.directory)).sort()) {
8354
9120
  if (!name.startsWith(`${location.identity}.`) || !name.endsWith(".hook")) continue;
8355
- const path = join16(location.directory, name);
9121
+ const path = join17(location.directory, name);
8356
9122
  try {
8357
9123
  const info = await lstat6(path);
8358
9124
  if (info.isSymbolicLink() || info.size > 1024 * 1024 || process.platform !== "win32" && (info.uid !== process.getuid?.() || info.mode & 63))
8359
9125
  throw new Error("Unsafe hook registration");
8360
- const value = JSON.parse(await readFile18(path, "utf8"));
9126
+ const value = JSON.parse(await readFile19(path, "utf8"));
8361
9127
  if (!record(value) || value["identity"] !== location.identity || value["protocol"] !== SERVICE_PROTOCOL || typeof value["epoch"] !== "string" || typeof value["id"] !== "string" || typeof value["created"] !== "number" || typeof value["session"] !== "string" || !record(value["input"]) || !record(value["environment"]) || !Object.values(value["environment"]).every((entry) => typeof entry === "string"))
8362
9128
  throw new Error("Invalid hook registration");
8363
9129
  result.push({
@@ -8370,7 +9136,7 @@ async function registeredHooks(root) {
8370
9136
  environment: value["environment"]
8371
9137
  });
8372
9138
  } catch {
8373
- await rm4(path, { force: true });
9139
+ await rm5(path, { force: true });
8374
9140
  }
8375
9141
  }
8376
9142
  return result;
@@ -8399,6 +9165,7 @@ var init_runtime = __esm({
8399
9165
  "use strict";
8400
9166
  init_budgets();
8401
9167
  init_config();
9168
+ init_edit_plans();
8402
9169
  init_engine();
8403
9170
  init_environment();
8404
9171
  init_files();
@@ -8571,9 +9338,18 @@ var init_runtime = __esm({
8571
9338
  const endpoint = existing ?? await ensureService(root, signal);
8572
9339
  return await this.output(root, operation, args, await exchange(endpoint, operation, args, signal), signal);
8573
9340
  } catch (error) {
8574
- if (["check", "diagnostics", "status", "navigate", "rename", "format", "inspect", "actions", "apply"].includes(
8575
- operation
8576
- ))
9341
+ if ([
9342
+ "check",
9343
+ "diagnostics",
9344
+ "status",
9345
+ "warmup",
9346
+ "navigate",
9347
+ "rename",
9348
+ "format",
9349
+ "inspect",
9350
+ "actions",
9351
+ "apply"
9352
+ ].includes(operation))
8577
9353
  return this.output(
8578
9354
  root,
8579
9355
  operation,
@@ -8583,6 +9359,7 @@ var init_runtime = __esm({
8583
9359
  isError: true,
8584
9360
  status: failureKind(message(error)),
8585
9361
  reason: message(error),
9362
+ ...error instanceof PlanFailure || error instanceof WriteFailure && error.planFailure ? { planFailure: error.planFailure } : {},
8586
9363
  ...operation === "apply" ? { modifiedPaths: error instanceof WriteFailure ? error.modifiedPaths : [] } : {}
8587
9364
  },
8588
9365
  new AbortController().signal
@@ -8660,8 +9437,8 @@ async function runHookCli() {
8660
9437
  const output = renderHook(result["output"]);
8661
9438
  if (output) {
8662
9439
  await new Promise(
8663
- (resolve13, reject) => process.stdout.write(`${JSON.stringify(output)}
8664
- `, (error) => error ? reject(error) : resolve13())
9440
+ (resolve16, reject) => process.stdout.write(`${JSON.stringify(output)}
9441
+ `, (error) => error ? reject(error) : resolve16())
8665
9442
  );
8666
9443
  if (typeof result["deliveryId"] === "string") {
8667
9444
  await runtime.request(
@@ -8708,6 +9485,10 @@ function tool(name, description, properties, required, readOnly) {
8708
9485
  };
8709
9486
  }
8710
9487
  function validateArguments(name, args) {
9488
+ if (name === "actions" && (args["validate"] !== void 0 || args["validationPaths"] !== void 0) && args["operation"] !== "preview" && !args["cursor"])
9489
+ throw new Error("validate requires actions operation=preview");
9490
+ if (args["validate"] === false && args["validationPaths"] !== void 0)
9491
+ throw new Error("validationPaths requires validation enabled");
8711
9492
  if (args["refresh"] !== void 0 && !(name === "status" || ["diagnostics", "check"].includes(name) && args["run"] !== "cached"))
8712
9493
  throw new Error("refresh requires active diagnostics or status");
8713
9494
  const definition = TOOLS.find((entry) => entry.name === name);
@@ -8726,8 +9507,8 @@ function validateArguments(name, args) {
8726
9507
  if (typeof value !== "number" || !Number.isInteger(value) || typeof schema["minimum"] === "number" && value < schema["minimum"] || typeof schema["maximum"] === "number" && value > schema["maximum"])
8727
9508
  throw new Error(`Invalid integer: ${key}`);
8728
9509
  }
8729
- if (schema["type"] === "array" && (!Array.isArray(value) || value.length > 200 || !value.every((item) => typeof item === "string")))
8730
- throw new Error(`${key} must contain at most 200 strings`);
9510
+ if (schema["type"] === "array" && (!Array.isArray(value) || value.length > (typeof schema["maxItems"] === "number" ? schema["maxItems"] : 200) || !value.every((item) => typeof item === "string")))
9511
+ throw new Error(`${key} must contain at most ${schema["maxItems"] ?? 200} strings`);
8731
9512
  if (Array.isArray(schema["enum"]) && !schema["enum"].includes(value)) throw new Error(`Invalid ${key}`);
8732
9513
  }
8733
9514
  }
@@ -8759,7 +9540,7 @@ async function runMcp(input = process.stdin, output = process.stdout) {
8759
9540
  if (method === "initialize") {
8760
9541
  ok({
8761
9542
  protocolVersion: text(params["protocolVersion"], "2024-11-05"),
8762
- serverInfo: { name: "codex-codeintel", version: "0.10.0" },
9543
+ serverInfo: { name: "codex-codeintel", version: "0.10.1" },
8763
9544
  // keep in sync with package.json
8764
9545
  capabilities: { tools: { listChanged: false } }
8765
9546
  });
@@ -8872,7 +9653,7 @@ async function runMcp(input = process.stdin, output = process.stdout) {
8872
9653
  output.removeListener("error", exit);
8873
9654
  }
8874
9655
  }
8875
- var string, scope, paging, selector, cursor, choice, LEGACY_TOOLS, TOOLS;
9656
+ var string, scope, paging, selector, cursor, validation, choice, LEGACY_TOOLS, TOOLS;
8876
9657
  var init_protocol = __esm({
8877
9658
  "src/protocol.ts"() {
8878
9659
  "use strict";
@@ -8896,6 +9677,18 @@ var init_protocol = __esm({
8896
9677
  pattern: string
8897
9678
  };
8898
9679
  cursor = { cursor: string };
9680
+ validation = {
9681
+ validate: {
9682
+ type: "boolean",
9683
+ description: "Preview scoped diagnostics with installed local language/lint tools; false skips simulation."
9684
+ },
9685
+ validationPaths: {
9686
+ type: "array",
9687
+ items: string,
9688
+ maxItems: 20,
9689
+ description: "Up to 20 additional existing files (for example unchanged callers) to check against all plan overlays."
9690
+ }
9691
+ };
8899
9692
  choice = (...values) => ({ type: "string", enum: values });
8900
9693
  LEGACY_TOOLS = {
8901
9694
  check_project: "check",
@@ -8936,6 +9729,13 @@ var init_protocol = __esm({
8936
9729
  [],
8937
9730
  true
8938
9731
  ),
9732
+ tool(
9733
+ "warmup",
9734
+ "Initialize local LSP servers without diagnostics or downloads. Explicit files or two representative languages.",
9735
+ { ...scope, ...cursor },
9736
+ [],
9737
+ true
9738
+ ),
8939
9739
  tool(
8940
9740
  "navigate",
8941
9741
  "Read-only navigation; 1-based UTF-16 positions, symbol path or @@ anchor. Follow next for large results.",
@@ -8967,14 +9767,14 @@ var init_protocol = __esm({
8967
9767
  tool(
8968
9768
  "rename",
8969
9769
  "Preview a cross-file rename. Returns a plan; only apply writes files.",
8970
- { ...scope, ...selector, ...cursor, newName: string },
9770
+ { ...scope, ...selector, ...cursor, ...validation, newName: string },
8971
9771
  ["path", "newName"],
8972
9772
  true
8973
9773
  ),
8974
9774
  tool(
8975
9775
  "format",
8976
9776
  "Preview formatting of explicit files. Returns a plan; only apply writes files.",
8977
- { ...scope, ...cursor },
9777
+ { ...scope, ...cursor, ...validation },
8978
9778
  [],
8979
9779
  true
8980
9780
  ),
@@ -9009,7 +9809,8 @@ var init_protocol = __esm({
9009
9809
  source: choice("lint", "lsp", "both"),
9010
9810
  rule: string,
9011
9811
  ids: { type: "array", items: string, maxItems: 200 },
9012
- details: { type: "boolean" }
9812
+ details: { type: "boolean" },
9813
+ ...validation
9013
9814
  },
9014
9815
  ["operation"],
9015
9816
  true
@@ -9215,10 +10016,10 @@ var init_automatic = __esm({
9215
10016
  // src/deadline.ts
9216
10017
  function waitWithSignal(task, signal) {
9217
10018
  signal.throwIfAborted();
9218
- return new Promise((resolve13, reject) => {
10019
+ return new Promise((resolve16, reject) => {
9219
10020
  const abort = () => reject(signal.reason);
9220
10021
  signal.addEventListener("abort", abort, { once: true });
9221
- task.then(resolve13, reject).finally(() => signal.removeEventListener("abort", abort));
10022
+ task.then(resolve16, reject).finally(() => signal.removeEventListener("abort", abort));
9222
10023
  });
9223
10024
  }
9224
10025
  var init_deadline = __esm({
@@ -9228,7 +10029,7 @@ var init_deadline = __esm({
9228
10029
  });
9229
10030
 
9230
10031
  // src/hook-coordinator.ts
9231
- import { rm as rm5 } from "node:fs/promises";
10032
+ import { rm as rm6 } from "node:fs/promises";
9232
10033
  import { setTimeout as delay4 } from "node:timers/promises";
9233
10034
  var HookCoordinator;
9234
10035
  var init_hook_coordinator = __esm({
@@ -9287,7 +10088,7 @@ var init_hook_coordinator = __esm({
9287
10088
  const state = await store.read(entry.session);
9288
10089
  const status = state.hookEvents[entry.id]?.state;
9289
10090
  if (parent.epoch !== entry.epoch || state.epoch !== entry.epoch || status && !["queued", "running"].includes(status)) {
9290
- await rm5(entry.path, { force: true });
10091
+ await rm6(entry.path, { force: true });
9291
10092
  return;
9292
10093
  }
9293
10094
  this.seen.add(entry.path);
@@ -9297,7 +10098,7 @@ var init_hook_coordinator = __esm({
9297
10098
  const item = current.hookEvents[entry.id];
9298
10099
  if (item) item.state = "complete";
9299
10100
  });
9300
- await rm5(entry.path, { force: true });
10101
+ await rm6(entry.path, { force: true });
9301
10102
  this.seen.delete(entry.path);
9302
10103
  this.changed();
9303
10104
  return;
@@ -9388,7 +10189,7 @@ var init_hook_coordinator = __esm({
9388
10189
  } finally {
9389
10190
  if (this.jobs.get(key) === job) this.jobs.delete(key);
9390
10191
  for (const entry of job.entries) {
9391
- await rm5(entry.path, { force: true }).catch(() => logEvent("cancel-cleanup-failure"));
10192
+ await rm6(entry.path, { force: true }).catch(() => logEvent("cancel-cleanup-failure"));
9392
10193
  this.seen.delete(entry.path);
9393
10194
  }
9394
10195
  this.tasks.delete(job);
@@ -9439,8 +10240,8 @@ var init_hook_coordinator = __esm({
9439
10240
  const first = await inspect();
9440
10241
  if (!["queued", "running"].includes(first.state ?? "") || input["revision"] !== this.revision) return first;
9441
10242
  let wake = () => void 0;
9442
- const change = new Promise((resolve13) => {
9443
- wake = resolve13;
10243
+ const change = new Promise((resolve16) => {
10244
+ wake = resolve16;
9444
10245
  this.listeners.add(wake);
9445
10246
  });
9446
10247
  try {
@@ -9584,14 +10385,14 @@ var init_go_output = __esm({
9584
10385
  });
9585
10386
 
9586
10387
  // src/project-output.ts
9587
- import { readFile as readFile19 } from "node:fs/promises";
9588
- import { relative as relative9, resolve as resolve11 } from "node:path";
10388
+ import { readFile as readFile20 } from "node:fs/promises";
10389
+ import { relative as relative10, resolve as resolve13 } from "node:path";
9589
10390
  import { fileURLToPath as fileURLToPath7 } from "node:url";
9590
10391
  async function projectOutput(check, output, root, cwd) {
9591
10392
  const pathOf = (file) => {
9592
- const absolute = resolve11(cwd, file);
10393
+ const absolute = resolve13(cwd, file);
9593
10394
  if (!inside(root, absolute)) throw new Error("Checker diagnostic outside workspace");
9594
- return relative9(root, absolute);
10395
+ return relative10(root, absolute);
9595
10396
  };
9596
10397
  if (check.parser === "go") {
9597
10398
  const findings2 = [];
@@ -9635,7 +10436,7 @@ async function projectOutput(check, output, root, cwd) {
9635
10436
  findings2.map(async (finding) => {
9636
10437
  let content = contents.get(finding.path);
9637
10438
  if (!content) {
9638
- content = readFile19(resolve11(root, finding.path), "utf8");
10439
+ content = readFile20(resolve13(root, finding.path), "utf8");
9639
10440
  contents.set(finding.path, content);
9640
10441
  }
9641
10442
  const line = (await content).split("\n")[finding.line - 1] ?? "";
@@ -9678,7 +10479,7 @@ async function projectOutput(check, output, root, cwd) {
9678
10479
  const split = splitLint(check.parser, JSON.parse(output), root, cwd);
9679
10480
  const findings2 = [];
9680
10481
  for (const [path, data] of split)
9681
- findings2.push(...parseLint(check.parser, data, path, await readFile19(resolve11(root, path), "utf8")));
10482
+ findings2.push(...parseLint(check.parser, data, path, await readFile20(resolve13(root, path), "utf8")));
9682
10483
  return findings2;
9683
10484
  }
9684
10485
  if (check.parser === "json") {
@@ -9749,9 +10550,9 @@ var init_project_output = __esm({
9749
10550
 
9750
10551
  // src/project-checks.ts
9751
10552
  import { randomUUID as randomUUID11 } from "node:crypto";
9752
- import { readdir as readdir5, readFile as readFile20, stat as stat6 } from "node:fs/promises";
10553
+ import { readdir as readdir6, readFile as readFile21, stat as stat8 } from "node:fs/promises";
9753
10554
  import * as nodePath2 from "node:path";
9754
- import { basename as basename5, join as join17, relative as relative10, resolve as resolve12 } from "node:path";
10555
+ import { basename as basename7, join as join18, relative as relative11, resolve as resolve14 } from "node:path";
9755
10556
  import { setTimeout as delay5 } from "node:timers/promises";
9756
10557
  function normalizedPattern(pattern) {
9757
10558
  return pattern.replace(/^(?:\.\/)+/, "").replace(/\/$/, "");
@@ -9767,7 +10568,7 @@ function covers(check, path) {
9767
10568
  if (check.parser === "tsc" && !/\.[cm]?[jt]sx?$/.test(path)) return false;
9768
10569
  if (check.parser === "cargo" && !/\.rs$/.test(path)) return false;
9769
10570
  if (check.parser === "go" && !/\.go$/.test(path)) return false;
9770
- const local = relative10(check.cwd === "." ? "" : check.cwd, path).split("\\").join("/");
10571
+ const local = relative11(check.cwd === "." ? "" : check.cwd, path).split("\\").join("/");
9771
10572
  return local !== ".." && !local.startsWith("../") && check.coverage.some((pattern) => nodePath2.matchesGlob(local, pattern)) && !check.excluded?.some((pattern) => {
9772
10573
  const normalized = normalizedPattern(pattern);
9773
10574
  return normalized === "." || normalized === "" || nodePath2.matchesGlob(local, normalized) || nodePath2.matchesGlob(local, `${normalized}/**`);
@@ -9786,6 +10587,7 @@ var init_project_checks = __esm({
9786
10587
  init_files();
9787
10588
  init_go_output();
9788
10589
  init_identity();
10590
+ init_log();
9789
10591
  init_metadata();
9790
10592
  init_project_output();
9791
10593
  init_results();
@@ -9836,13 +10638,15 @@ var init_project_checks = __esm({
9836
10638
  return { introduced, resolved, unattributed };
9837
10639
  }
9838
10640
  get active() {
9839
- return [...this.jobs.values()].some((job) => job.results.some((result) => result.state === "running"));
10641
+ return [...this.jobs.values()].some(
10642
+ (job) => job.persisting || job.results.some((result) => result.state === "running")
10643
+ );
9840
10644
  }
9841
10645
  async discover(config, signal) {
9842
10646
  if (config.projectChecks !== "auto") return [...config.projectChecks];
9843
10647
  const checks = [];
9844
- const names = await readdir5(this.root);
9845
- const has = async (name) => (await stat6(join17(this.root, name)).catch(() => void 0))?.isFile();
10648
+ const names = await readdir6(this.root);
10649
+ const has = async (name) => (await stat8(join18(this.root, name)).catch(() => void 0))?.isFile();
9846
10650
  if (await has("tsconfig.json")) {
9847
10651
  checks.push({
9848
10652
  name: "typescript",
@@ -9882,7 +10686,7 @@ var init_project_checks = __esm({
9882
10686
  );
9883
10687
  if (!enabled) continue;
9884
10688
  const expected = path.endsWith("py") ? "ruff" : config.javascript === "eslint" || !await has("biome.json") && !await has("biome.jsonc") ? "eslint" : "biome";
9885
- const runner = await select(this.root, join17(this.root, path), false, config, false, signal).catch(() => ({
10689
+ const runner = await select(this.root, join18(this.root, path), false, config, false, signal).catch(() => ({
9886
10690
  name: expected,
9887
10691
  command: expected,
9888
10692
  prefix: []
@@ -9908,7 +10712,7 @@ var init_project_checks = __esm({
9908
10712
  const cwd = await workspacePath(this.root, check.cwd);
9909
10713
  const tool2 = await resolveTool(
9910
10714
  config.projectChecks === "auto" ? this.root : cwd,
9911
- join17(cwd, "representative"),
10715
+ join18(cwd, "representative"),
9912
10716
  check.command,
9913
10717
  config.projectChecks !== "auto",
9914
10718
  false
@@ -9921,16 +10725,16 @@ var init_project_checks = __esm({
9921
10725
  // Only interpreter launch scripts belong to the tool identity.
9922
10726
  tool2.command.slice(1).filter(
9923
10727
  (arg, index, args) => /^(?:node|python(?:\d+(?:\.\d+)*)?|bash|sh)(?:\.exe)?$/.test(
9924
- basename5(tool2.command[0] ?? "")
10728
+ basename7(tool2.command[0] ?? "")
9925
10729
  ) && /\.(?:[cm]?js|py|sh)$/.test(arg) && !args.slice(0, index).some((previous) => /\.(?:[cm]?js|py|sh)$/.test(previous))
9926
10730
  ).map(async (arg) => {
9927
- const info = await stat6(resolve12(cwd, arg)).catch(() => void 0);
10731
+ const info = await stat8(resolve14(cwd, arg)).catch(() => void 0);
9928
10732
  return [arg, info?.size, info?.mtimeMs, info?.ctimeMs, info?.ino];
9929
10733
  })
9930
10734
  ),
9931
10735
  await analysisIdentity(
9932
10736
  this.root,
9933
- [relative10(this.root, join17(cwd, "representative"))],
10737
+ [relative11(this.root, join18(cwd, "representative"))],
9934
10738
  config,
9935
10739
  signal,
9936
10740
  false
@@ -9942,7 +10746,7 @@ var init_project_checks = __esm({
9942
10746
  }
9943
10747
  output(job, cursor2 = 0) {
9944
10748
  const diagnostics = job.results.flatMap((result) => result.findings);
9945
- const state = job.results.some((result) => result.state === "running") ? "running" : job.results.some((result) => result.state === "stale") ? "stale" : job.results.some((result) => result.state === "failed") || !job.checks.length ? "failed" : "complete";
10749
+ const state = job.persisting || job.results.some((result) => result.state === "running") ? "running" : job.results.some((result) => result.state === "stale") ? "stale" : job.results.some((result) => result.state === "failed") || !job.checks.length ? "failed" : "complete";
9946
10750
  return {
9947
10751
  operation: "check",
9948
10752
  job: job.id,
@@ -10006,7 +10810,7 @@ var init_project_checks = __esm({
10006
10810
  if (!Number.isInteger(cursor2) || cursor2 < 0) throw new Error("Invalid project cursor");
10007
10811
  return cursor2;
10008
10812
  }
10009
- async start(checks, config, environment, identity, before) {
10813
+ async start(checks, config, environment, identity, before, baseline) {
10010
10814
  const job = {
10011
10815
  id: randomUUID11(),
10012
10816
  identity,
@@ -10023,8 +10827,18 @@ var init_project_checks = __esm({
10023
10827
  state: "running",
10024
10828
  findings: []
10025
10829
  })),
10026
- done: []
10830
+ done: [],
10831
+ persisting: !!baseline
10027
10832
  };
10833
+ if (baseline) {
10834
+ const state = await new Metadata(this.root).update(baseline.session, job.controller.signal, (current) => {
10835
+ if (current.epoch !== baseline.epoch || current.edited || current.turn === "__ended__") return false;
10836
+ current.shown["baselineJob"] = job.id;
10837
+ return true;
10838
+ });
10839
+ if (state.shown["baselineJob"] !== job.id) throw new Error("Session changed before baseline started");
10840
+ this.sessionJobs.set(baseline.session, job.id);
10841
+ }
10028
10842
  this.jobs.set(job.id, job);
10029
10843
  this.latest = job.id;
10030
10844
  for (const [index, check] of checks.entries())
@@ -10039,7 +10853,7 @@ var init_project_checks = __esm({
10039
10853
  const beforeFiles = before;
10040
10854
  const tool2 = await resolveTool(
10041
10855
  config.projectChecks === "auto" ? this.root : cwd,
10042
- join17(cwd, "representative"),
10856
+ join18(cwd, "representative"),
10043
10857
  check.command,
10044
10858
  config.projectChecks !== "auto",
10045
10859
  false
@@ -10055,7 +10869,7 @@ var init_project_checks = __esm({
10055
10869
  { timeout: BUDGET.project, environment }
10056
10870
  );
10057
10871
  if (shown.code !== 0) throw new Error(`Go package discovery failed: ${shown.stderr}`);
10058
- result.coverage = goFiles(shown.stdout).map((path) => relative10(cwd, path).replaceAll("\\", "/"));
10872
+ result.coverage = goFiles(shown.stdout).map((path) => relative11(cwd, path).replaceAll("\\", "/"));
10059
10873
  }
10060
10874
  if (config.projectChecks === "auto" && check.parser === "tsc") {
10061
10875
  const shown = await run(tool2.command[0] ?? "", ["--showConfig"], cwd, signal, void 0, {
@@ -10066,7 +10880,7 @@ var init_project_checks = __esm({
10066
10880
  if (shown.code !== 0 || !record(data))
10067
10881
  throw new Error("TypeScript effective configuration unavailable");
10068
10882
  const pkg = JSON.parse(
10069
- await readFile20(join17(cwd, "package.json"), "utf8").catch(() => "{}")
10883
+ await readFile21(join18(cwd, "package.json"), "utf8").catch(() => "{}")
10070
10884
  );
10071
10885
  if (Array.isArray(data["references"]) && data["references"].length || record(pkg) && pkg["workspaces"])
10072
10886
  throw new Error("TypeScript references/multi-package projects require explicit projectChecks");
@@ -10080,8 +10894,8 @@ var init_project_checks = __esm({
10080
10894
  result.excluded.push("**/*.{js,jsx,mjs,cjs}");
10081
10895
  }
10082
10896
  if (config.projectChecks === "auto" && check.parser === "ty") {
10083
- const content = await readFile20(join17(cwd, "ty.toml"), "utf8").catch(() => void 0);
10084
- const data = content ? parse(content) : parse(await readFile20(join17(cwd, "pyproject.toml"), "utf8").catch(() => ""));
10897
+ const content = await readFile21(join18(cwd, "ty.toml"), "utf8").catch(() => void 0);
10898
+ const data = content ? parse(content) : parse(await readFile21(join18(cwd, "pyproject.toml"), "utf8").catch(() => ""));
10085
10899
  const toolConfig = content ? data : record(data["tool"]) && record(data["tool"]["ty"]) ? data["tool"]["ty"] : {};
10086
10900
  const src = record(toolConfig["src"]) ? toolConfig["src"] : {};
10087
10901
  if (Array.isArray(src["include"]))
@@ -10116,15 +10930,49 @@ var init_project_checks = __esm({
10116
10930
  }
10117
10931
  })
10118
10932
  );
10933
+ if (baseline)
10934
+ job.done.push(
10935
+ Promise.allSettled(job.done).then(async () => {
10936
+ try {
10937
+ await withExecution(
10938
+ environment,
10939
+ true,
10940
+ () => this.saveBaseline(
10941
+ job,
10942
+ baseline,
10943
+ AbortSignal.any([job.controller.signal, AbortSignal.timeout(BUDGET.lock)])
10944
+ )
10945
+ );
10946
+ } catch {
10947
+ if (!job.controller.signal.aborted) await logEvent("background-failure");
10948
+ } finally {
10949
+ job.persisting = false;
10950
+ }
10951
+ })
10952
+ );
10119
10953
  while (this.jobs.size > 32) {
10120
10954
  const first = [...this.jobs.values()].find(
10121
- (entry) => entry.id !== job.id && entry.results.every((result) => result.state !== "running")
10955
+ (entry) => entry.id !== job.id && !entry.persisting && entry.results.every((result) => result.state !== "running")
10122
10956
  );
10123
10957
  if (!first) break;
10124
10958
  this.jobs.delete(first.id);
10125
10959
  }
10126
10960
  return job;
10127
10961
  }
10962
+ async saveBaseline(job, baseline, signal) {
10963
+ const store = new Metadata(this.root);
10964
+ let reference = "";
10965
+ await store.update(baseline.session, signal, async (current) => {
10966
+ if (current.epoch !== baseline.epoch || current.turn === "__ended__" || current.shown["baselineJob"] !== job.id || (await configuration(this.root)).version !== job.configuration)
10967
+ return false;
10968
+ reference = await store.shared({ identity: job.identity, results: job.results, before: job.before });
10969
+ current.diagnosticBaseline = reference;
10970
+ current.configuration = job.configuration;
10971
+ return true;
10972
+ });
10973
+ if (!reference) throw new Error("Session or configuration changed before baseline was saved");
10974
+ return reference;
10975
+ }
10128
10976
  async baseline(session, paths, environment, signal, wait) {
10129
10977
  const config = await configuration(this.root);
10130
10978
  assertConfiguration(config);
@@ -10159,13 +11007,7 @@ var init_project_checks = __esm({
10159
11007
  if (!record(before) || Object.keys(before).length !== snapshot2.files.size || [...snapshot2.files].some(([path, content]) => before[path] !== content))
10160
11008
  throw new Error("Files changed before baseline started; diagnostics remain unattributed");
10161
11009
  }
10162
- job = await this.start(checks, config, environment, identity, snapshot2);
10163
- this.sessionJobs.set(session, job.id);
10164
- await store.update(session, signal, (current2) => {
10165
- if (current2.epoch !== state.epoch || current2.turn === "__ended__") return false;
10166
- current2.shown["baselineJob"] = job?.id ?? "";
10167
- return true;
10168
- });
11010
+ job = await this.start(checks, config, environment, identity, snapshot2, { session, epoch: state.epoch });
10169
11011
  }
10170
11012
  const selected = job.checks.map((check, index) => ({ check, index })).filter(({ check }) => !paths || paths.some((path) => covers(check, path)));
10171
11013
  await Promise.race([
@@ -10175,13 +11017,7 @@ var init_project_checks = __esm({
10175
11017
  recovered = { identity, results: job.results, before: job.before };
10176
11018
  }
10177
11019
  const results = recovered.results;
10178
- const reference = await store.shared(recovered);
10179
- await store.update(session, signal, (current) => {
10180
- if (current.epoch !== state.epoch || current.turn === "__ended__") return false;
10181
- current.diagnosticBaseline = reference;
10182
- current.configuration = config.version;
10183
- return true;
10184
- });
11020
+ const reference = job ? await this.saveBaseline(job, { session, epoch: state.epoch }, signal) : await store.shared(recovered);
10185
11021
  const snapshot = paths ? void 0 : await inventory(this.root, BUDGET.files, signal, config.exclude);
10186
11022
  const targets = paths ?? [...snapshot?.files.keys() ?? []].filter(
10187
11023
  (path) => codeLanguage(config, path) && !/(?:json|yaml|css|html)$/.test(codeLanguage(config, path) ?? "")
@@ -10250,7 +11086,7 @@ var init_project_checks = __esm({
10250
11086
  });
10251
11087
 
10252
11088
  // src/service.ts
10253
- import { rm as rm6 } from "node:fs/promises";
11089
+ import { rm as rm7 } from "node:fs/promises";
10254
11090
  import { createServer } from "node:net";
10255
11091
  async function runService(root, identity, token) {
10256
11092
  const location = await serviceLocation(root);
@@ -10261,8 +11097,8 @@ async function runService(root, identity, token) {
10261
11097
  if (await existingService(root, startup)) return;
10262
11098
  const previous = await readEndpoint(location);
10263
11099
  if (previous && alive(previous.pid)) throw new Error("Service alive but unavailable; refusing duplicate startup");
10264
- await rm6(location.endpoint, { force: true });
10265
- if (process.platform !== "win32") await rm6(location.address, { force: true });
11100
+ await rm7(location.endpoint, { force: true });
11101
+ if (process.platform !== "win32") await rm7(location.address, { force: true });
10266
11102
  await serve(root, identity, token);
10267
11103
  } finally {
10268
11104
  await release();
@@ -10372,9 +11208,17 @@ async function serve(root, identity, token) {
10372
11208
  }
10373
11209
  return { operation, ...await coordinator.request(args, controller.signal) };
10374
11210
  }
10375
- if (!["status", "diagnostics", "navigate", "rename", "format", "inspect", "actions", "apply"].includes(
10376
- operation
10377
- ))
11211
+ if (![
11212
+ "status",
11213
+ "diagnostics",
11214
+ "warmup",
11215
+ "navigate",
11216
+ "rename",
11217
+ "format",
11218
+ "inspect",
11219
+ "actions",
11220
+ "apply"
11221
+ ].includes(operation))
10378
11222
  throw new Error("Unknown service operation");
10379
11223
  const config = await configuration(root);
10380
11224
  if (!config.valid || config.automaticDiagnostics.postToolUse === "off" && config.automaticDiagnostics.stop === "off")
@@ -10405,7 +11249,7 @@ async function serve(root, identity, token) {
10405
11249
  } catch (error) {
10406
11250
  if (!socket.destroyed)
10407
11251
  socket.end(
10408
- `${JSON.stringify({ error: message(error), ...error instanceof WriteFailure ? { modifiedPaths: error.modifiedPaths } : {} })}
11252
+ `${JSON.stringify({ error: message(error), ...error instanceof WriteFailure ? { modifiedPaths: error.modifiedPaths } : {}, ...error instanceof PlanFailure || error instanceof WriteFailure && error.planFailure ? { planFailure: error.planFailure } : {} })}
10409
11253
  `
10410
11254
  );
10411
11255
  } finally {
@@ -10446,7 +11290,7 @@ async function serve(root, identity, token) {
10446
11290
  await projects.dispose();
10447
11291
  await engine.dispose();
10448
11292
  if ((await readEndpoint(location).catch(() => void 0))?.token === token)
10449
- await rm6(location.endpoint, { force: true });
11293
+ await rm7(location.endpoint, { force: true });
10450
11294
  };
10451
11295
  let revalidation;
10452
11296
  const idle = setInterval(() => {
@@ -10457,7 +11301,7 @@ async function serve(root, identity, token) {
10457
11301
  });
10458
11302
  if (requests || coordinator.active || automatic.active || projects.active || warming || warmupTimer && !warmupTask)
10459
11303
  lastActivity = Date.now();
10460
- else if (Date.now() - lastActivity >= BUDGET.idle) void stop();
11304
+ else if (Date.now() - lastActivity >= BUDGET.idle && !engine.hasLivePlans()) void stop();
10461
11305
  }, 250);
10462
11306
  process.once("SIGTERM", () => {
10463
11307
  void stop();
@@ -10465,9 +11309,9 @@ async function serve(root, identity, token) {
10465
11309
  process.once("SIGINT", () => {
10466
11310
  void stop();
10467
11311
  });
10468
- await new Promise((resolve13, reject) => {
11312
+ await new Promise((resolve16, reject) => {
10469
11313
  server2.once("error", reject);
10470
- server2.listen(location.address, resolve13);
11314
+ server2.listen(location.address, resolve16);
10471
11315
  });
10472
11316
  await publishEndpoint(location, token);
10473
11317
  await coordinator.ingest();
@@ -10479,6 +11323,7 @@ var init_service = __esm({
10479
11323
  init_budgets();
10480
11324
  init_config();
10481
11325
  init_directory_lock();
11326
+ init_edit_plans();
10482
11327
  init_engine();
10483
11328
  init_environment();
10484
11329
  init_hook_coordinator();
@@ -10496,37 +11341,48 @@ var main_exports = {};
10496
11341
  __export(main_exports, {
10497
11342
  main: () => main
10498
11343
  });
11344
+ import { resolve as resolve15 } from "node:path";
10499
11345
  async function main() {
10500
11346
  restoreInstalledHome();
10501
11347
  const [command = "mcp"] = process.argv.slice(2);
10502
- if (command === "mcp") await runMcp();
11348
+ if (["--help", "help", "-h"].includes(command)) {
11349
+ process.stdout.write("Usage: codeintel [mcp | hook | check [workspace] | warmup [workspace] [--path path]]\n");
11350
+ } else if (command === "mcp") await runMcp();
10503
11351
  else if (command === "hook") await runHookCli();
10504
11352
  else if (command === "service")
10505
11353
  await runService(process.argv[3] ?? "", process.argv[4] ?? "", process.argv[5] ?? "");
10506
- else if (command === "check") {
11354
+ else if (command === "check" || command === "warmup") {
10507
11355
  const runtime = new Runtime();
10508
- const options = { run: "active" };
11356
+ const options = command === "check" ? { run: "active" } : {};
10509
11357
  const tokens = process.argv.slice(3);
10510
11358
  const workspace = tokens[0] && !tokens[0].startsWith("--") ? tokens.shift() ?? process.cwd() : process.cwd();
10511
11359
  while (tokens.length) {
10512
11360
  const flag = tokens.shift();
10513
- if (flag === "--refresh") options["refresh"] = true;
10514
- else if (["--run", "--job", "--cursor"].includes(flag ?? "")) {
11361
+ if (command === "check" && flag === "--refresh") options["refresh"] = true;
11362
+ else if (command === "warmup" && flag === "--path") {
11363
+ const value = tokens.shift();
11364
+ if (!value || value.startsWith("--")) throw new Error("Missing value for --path");
11365
+ const paths = options["paths"] ?? [];
11366
+ paths.push(value);
11367
+ if (paths.length > 200) throw new Error("At most 200 paths");
11368
+ options["paths"] = paths;
11369
+ } else if (command === "check" && ["--run", "--job", "--cursor"].includes(flag ?? "") || flag === "--cursor") {
10515
11370
  const value = tokens.shift();
10516
11371
  if (!value || value.startsWith("--")) throw new Error(`Missing value for ${flag}`);
10517
11372
  options[flag?.slice(2) ?? ""] = value;
10518
11373
  } else throw new Error(`Unknown option ${flag}`);
10519
11374
  }
10520
- if (!["active", "cached"].includes(String(options["run"]))) throw new Error("run must be active or cached");
11375
+ if (command === "check" && !["active", "cached"].includes(String(options["run"])))
11376
+ throw new Error("run must be active or cached");
10521
11377
  try {
10522
11378
  process.stdout.write(
10523
- `${JSON.stringify(await runtime.request(workspace, "check", options, AbortSignal.timeout(BUDGET.request)))}
11379
+ `${JSON.stringify(await runtime.request(resolve15(workspace), command, options, AbortSignal.timeout(BUDGET.request)))}
10524
11380
  `
10525
11381
  );
10526
11382
  } finally {
10527
11383
  await runtime.close();
10528
11384
  }
10529
- } else throw new Error("Usage: codex-codeintel [mcp | hook | check [workspace]]");
11385
+ } else throw new Error("Usage: codeintel [mcp | hook | check [workspace] | warmup [workspace] [--path path]]");
10530
11386
  }
10531
11387
  var init_main = __esm({
10532
11388
  "src/main.ts"() {
@@ -10540,10 +11396,17 @@ var init_main = __esm({
10540
11396
  }
10541
11397
  });
10542
11398
 
11399
+ // src/node-version.ts
11400
+ function supportsNode(version) {
11401
+ const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(version);
11402
+ if (!match) return false;
11403
+ const major = Number(match[1]), minor = Number(match[2]);
11404
+ return major === 24 && minor >= 20 || major === 26 && minor >= 13;
11405
+ }
11406
+
10543
11407
  // src/cli.ts
10544
- var [major = 0, minor = 0] = process.versions.node.split(".").map(Number);
10545
- if (major < 22 || major === 22 && minor < 12) {
10546
- const reason = `Codex CodeIntel requires Node >=22.12.0; found ${process.versions.node}.`;
11408
+ if (!supportsNode(process.versions.node)) {
11409
+ const reason = `CodeIntel requires Node ^24.20.0 || ^26.13.0; found ${process.versions.node}.`;
10547
11410
  if (process.argv[2] === "hook") {
10548
11411
  let event = "";
10549
11412
  let passthrough = false;