@cortexkit/aft-pi 0.39.3 → 0.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -11675,11 +11675,6 @@ function error(message, meta) {
11675
11675
  }
11676
11676
  }
11677
11677
  // ../aft-bridge/dist/bash-format.js
11678
- function appendPipeStripNote(output, note) {
11679
- return note ? `${output}
11680
-
11681
- ${note}` : output;
11682
- }
11683
11678
  function formatSeconds(ms) {
11684
11679
  return `${Number((ms / 1000).toFixed(1))}s`;
11685
11680
  }
@@ -11757,12 +11752,16 @@ function maybeAppendGrepSearchHint(output, command, aftSearchRegistered, project
11757
11752
  ${hint}`;
11758
11753
  }
11759
11754
  function shouldSuppressGrepSearchHint(command, projectRoot) {
11760
- const root = projectRoot?.trim();
11761
- if (!root)
11762
- return false;
11763
11755
  const statements = splitTopLevelStatements(command);
11764
11756
  if (statements === null)
11765
11757
  return false;
11758
+ if (allSearchPathOperandsAreDynamic(statements))
11759
+ return true;
11760
+ const root = projectRoot?.trim();
11761
+ if (!root)
11762
+ return false;
11763
+ const resolvedRoot = path.resolve(root);
11764
+ let effectiveCwd = resolvedRoot;
11766
11765
  let sawCodeSearchStatement = false;
11767
11766
  for (const statement of statements) {
11768
11767
  const firstStage = firstPipelineStage(statement);
@@ -11771,19 +11770,84 @@ function shouldSuppressGrepSearchHint(command, projectRoot) {
11771
11770
  const firstToken = readShellToken(firstStage, skipSpaces(firstStage, 0));
11772
11771
  if (firstToken === null)
11773
11772
  continue;
11774
- if (firstToken.token !== "grep" && firstToken.token !== "rg")
11773
+ const head = firstToken.token;
11774
+ if (head === "cd") {
11775
+ effectiveCwd = nextCwdAfterCd(effectiveCwd, firstStage, firstToken.end);
11776
+ continue;
11777
+ }
11778
+ if (head !== "grep" && head !== "rg")
11775
11779
  continue;
11776
11780
  sawCodeSearchStatement = true;
11781
+ if (effectiveCwd === null)
11782
+ continue;
11783
+ if (!isDirInsideProject(resolvedRoot, effectiveCwd))
11784
+ continue;
11777
11785
  const operands = collectPathOperands(firstStage, firstToken.end);
11778
11786
  if (operands.length === 0)
11779
11787
  return false;
11780
11788
  for (const operand of operands) {
11781
- if (isPathInsideProject(root, operand))
11789
+ if (isDynamicPathOperand(operand))
11790
+ continue;
11791
+ if (isPathInsideProject(resolvedRoot, effectiveCwd, operand))
11782
11792
  return false;
11783
11793
  }
11784
11794
  }
11785
11795
  return sawCodeSearchStatement;
11786
11796
  }
11797
+ function allSearchPathOperandsAreDynamic(statements) {
11798
+ let sawDynamicOnlySearch = false;
11799
+ for (const statement of statements) {
11800
+ const firstStage = firstPipelineStage(statement);
11801
+ if (firstStage === null)
11802
+ continue;
11803
+ const firstToken = readShellToken(firstStage, skipSpaces(firstStage, 0));
11804
+ if (firstToken === null)
11805
+ continue;
11806
+ const head = firstToken.token;
11807
+ if (head !== "grep" && head !== "rg")
11808
+ continue;
11809
+ const operands = collectPathOperands(firstStage, firstToken.end);
11810
+ if (operands.length === 0)
11811
+ return false;
11812
+ if (operands.some((operand) => !isDynamicPathOperand(operand)))
11813
+ return false;
11814
+ sawDynamicOnlySearch = true;
11815
+ }
11816
+ return sawDynamicOnlySearch;
11817
+ }
11818
+ function nextCwdAfterCd(currentCwd, firstStage, afterCd) {
11819
+ let index = skipSpaces(firstStage, afterCd);
11820
+ let target = null;
11821
+ while (index < firstStage.length) {
11822
+ const tokenResult = readShellToken(firstStage, index);
11823
+ if (tokenResult === null)
11824
+ break;
11825
+ const { token, end } = tokenResult;
11826
+ if (end <= index)
11827
+ break;
11828
+ index = skipSpaces(firstStage, end);
11829
+ if (token === "-")
11830
+ return null;
11831
+ if (token.length > 1 && token.startsWith("-"))
11832
+ continue;
11833
+ target = token;
11834
+ break;
11835
+ }
11836
+ if (target === null)
11837
+ return path.resolve(os.homedir());
11838
+ if (target.includes("$") || target.includes("`"))
11839
+ return null;
11840
+ const expanded = expandTilde(target);
11841
+ if (path.isAbsolute(expanded))
11842
+ return path.resolve(expanded);
11843
+ if (currentCwd === null)
11844
+ return null;
11845
+ return path.resolve(currentCwd, expanded);
11846
+ }
11847
+ function isDirInsideProject(resolvedRoot, dir) {
11848
+ const rel = path.relative(resolvedRoot, path.resolve(dir));
11849
+ return rel === "" || !rel.startsWith("..") && !path.isAbsolute(rel);
11850
+ }
11787
11851
  function collectPathOperands(firstStage, startAfterCommand) {
11788
11852
  const operands = [];
11789
11853
  let index = skipSpaces(firstStage, startAfterCommand);
@@ -11805,6 +11869,9 @@ function collectPathOperands(firstStage, startAfterCommand) {
11805
11869
  function looksLikePathOperand(token) {
11806
11870
  return token.includes("/") || token.startsWith("~") || token.startsWith("./") || token.startsWith("../");
11807
11871
  }
11872
+ function isDynamicPathOperand(token) {
11873
+ return token.startsWith("~") || token.includes("$") || token.includes("`");
11874
+ }
11808
11875
  function expandTilde(target) {
11809
11876
  if (!target.startsWith("~"))
11810
11877
  return target;
@@ -11817,10 +11884,9 @@ function resolvePathOperand(projectRoot, operand) {
11817
11884
  const expanded = expandTilde(operand);
11818
11885
  return path.isAbsolute(expanded) ? path.resolve(expanded) : path.resolve(projectRoot, expanded);
11819
11886
  }
11820
- function isPathInsideProject(projectRoot, operand) {
11821
- const root = path.resolve(projectRoot);
11822
- const resolved = resolvePathOperand(root, operand);
11823
- const rel = path.relative(root, resolved);
11887
+ function isPathInsideProject(resolvedRoot, baseCwd, operand) {
11888
+ const resolved = resolvePathOperand(baseCwd, operand);
11889
+ const rel = path.relative(resolvedRoot, resolved);
11824
11890
  return rel === "" || !rel.startsWith("..") && !path.isAbsolute(rel);
11825
11891
  }
11826
11892
  function splitTopLevelStatements(command) {
@@ -12013,6 +12079,7 @@ var LONG_RUNNING_COMMAND_TIMEOUT_MS = {
12013
12079
  trace_to_symbol: 60000,
12014
12080
  trace_data: 60000,
12015
12081
  impact: 60000,
12082
+ inspect: 60000,
12016
12083
  grep: 60000,
12017
12084
  glob: 60000,
12018
12085
  semantic_search: 60000
@@ -12071,7 +12138,6 @@ function formatStatusBar(counts) {
12071
12138
  // ../aft-bridge/dist/bridge.js
12072
12139
  var DEFAULT_BRIDGE_TIMEOUT_MS = 30000;
12073
12140
  var BRIDGE_HANG_TIMEOUT_THRESHOLD = 2;
12074
- var SEMANTIC_TIMEOUT_SAFETY_MARGIN_MS = 5000;
12075
12141
  var MAX_STDOUT_BUFFER = 64 * 1024 * 1024;
12076
12142
  var STDOUT_BUFFER_COMPACT_THRESHOLD = 64 * 1024;
12077
12143
  var TERMINAL_BASH_STATUSES = new Set([
@@ -12144,28 +12210,19 @@ function compareSemver(a, b) {
12144
12210
  }
12145
12211
  return 0;
12146
12212
  }
12147
- function clampSemanticTimeout(configOverrides, bridgeTimeoutMs) {
12148
- const semantic = configOverrides.semantic;
12149
- if (!semantic || typeof semantic !== "object" || Array.isArray(semantic)) {
12150
- return configOverrides;
12151
- }
12152
- const timeoutMs = semantic.timeout_ms;
12153
- if (typeof timeoutMs !== "number" || !Number.isFinite(timeoutMs)) {
12154
- return configOverrides;
12155
- }
12156
- const semanticTransportBudgetMs = Math.max(bridgeTimeoutMs, LONG_RUNNING_COMMAND_TIMEOUT_MS.semantic_search ?? 0);
12157
- const maxSemanticTimeoutMs = semanticTransportBudgetMs > SEMANTIC_TIMEOUT_SAFETY_MARGIN_MS ? semanticTransportBudgetMs - SEMANTIC_TIMEOUT_SAFETY_MARGIN_MS : Math.max(1, semanticTransportBudgetMs - 1);
12158
- if (timeoutMs <= maxSemanticTimeoutMs) {
12159
- return configOverrides;
12160
- }
12161
- warn(`semantic.timeout_ms=${timeoutMs} exceeds the semantic transport budget; clamping to ${maxSemanticTimeoutMs}ms (budget: ${semanticTransportBudgetMs}ms)`);
12162
- return {
12163
- ...configOverrides,
12164
- semantic: {
12165
- ...semantic,
12166
- timeout_ms: maxSemanticTimeoutMs
12213
+ function coerceConfigureDroppedKeys(value) {
12214
+ if (!Array.isArray(value))
12215
+ return [];
12216
+ const dropped = [];
12217
+ for (const item of value) {
12218
+ if (!item || typeof item !== "object" || Array.isArray(item))
12219
+ continue;
12220
+ const record = item;
12221
+ if (typeof record.key === "string" && typeof record.tier === "string" && typeof record.reason === "string") {
12222
+ dropped.push({ key: record.key, tier: record.tier, reason: record.reason });
12167
12223
  }
12168
- };
12224
+ }
12225
+ return dropped;
12169
12226
  }
12170
12227
 
12171
12228
  class BridgeReplacedDuringVersionCheck extends Error {
@@ -12177,6 +12234,21 @@ class BridgeReplacedDuringVersionCheck extends Error {
12177
12234
  }
12178
12235
  }
12179
12236
 
12237
+ class BridgeTransportTimeoutError extends Error {
12238
+ command;
12239
+ timeoutMs;
12240
+ code = "transport_timeout";
12241
+ constructor(command, timeoutMs, message) {
12242
+ super(message);
12243
+ this.command = command;
12244
+ this.timeoutMs = timeoutMs;
12245
+ this.name = "BridgeTransportTimeoutError";
12246
+ }
12247
+ }
12248
+ function isBridgeTransportTimeout(err) {
12249
+ return err instanceof Error && err.code === "transport_timeout";
12250
+ }
12251
+
12180
12252
  class BinaryBridge {
12181
12253
  static RESTART_RESET_MS = 5 * 60 * 1000;
12182
12254
  static STDERR_TAIL_MAX = 20;
@@ -12220,7 +12292,7 @@ class BinaryBridge {
12220
12292
  this.timeoutMs = options?.timeoutMs ?? DEFAULT_BRIDGE_TIMEOUT_MS;
12221
12293
  this.hangThreshold = options?.hangThreshold ?? BRIDGE_HANG_TIMEOUT_THRESHOLD;
12222
12294
  this.maxRestarts = options?.maxRestarts ?? 3;
12223
- this.configOverrides = clampSemanticTimeout(configOverrides ?? {}, this.timeoutMs);
12295
+ this.configOverrides = configOverrides ?? {};
12224
12296
  this.minVersion = options?.minVersion;
12225
12297
  this.onVersionMismatch = options?.onVersionMismatch;
12226
12298
  this.onConfigureWarnings = options?.onConfigureWarnings;
@@ -12404,7 +12476,7 @@ class BinaryBridge {
12404
12476
  } else {
12405
12477
  this.warnVia(timeoutMsg2);
12406
12478
  }
12407
- entry.reject(new Error(`${this.errorPrefix} Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms`));
12479
+ entry.reject(new BridgeTransportTimeoutError(command, effectiveTimeoutMs, `${this.errorPrefix} Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms`));
12408
12480
  return;
12409
12481
  }
12410
12482
  const childActiveSinceRequest = this.lastChildActivityAt > requestSentAt;
@@ -12457,18 +12529,24 @@ class BinaryBridge {
12457
12529
  }
12458
12530
  }
12459
12531
  async deliverConfigureWarnings(configResult, params, options) {
12460
- if (!this.onConfigureWarnings || !Array.isArray(configResult.warnings))
12532
+ if (!this.onConfigureWarnings)
12461
12533
  return;
12462
- if (configResult.warnings.length === 0)
12534
+ const warnings = Array.isArray(configResult.warnings) ? configResult.warnings : [];
12535
+ const configDroppedKeys = coerceConfigureDroppedKeys(configResult.config_dropped_keys);
12536
+ if (warnings.length === 0 && configDroppedKeys.length === 0)
12463
12537
  return;
12464
12538
  const sessionId = typeof params.session_id === "string" ? params.session_id : undefined;
12539
+ const context = {
12540
+ projectRoot: this.cwd,
12541
+ sessionId,
12542
+ client: options?.configureWarningClient ?? (sessionId ? this.configureWarningClients.get(sessionId) : undefined),
12543
+ warnings
12544
+ };
12545
+ if (configDroppedKeys.length > 0) {
12546
+ context.configDroppedKeys = configDroppedKeys;
12547
+ }
12465
12548
  try {
12466
- await this.onConfigureWarnings({
12467
- projectRoot: this.cwd,
12468
- sessionId,
12469
- client: options?.configureWarningClient ?? (sessionId ? this.configureWarningClients.get(sessionId) : undefined),
12470
- warnings: configResult.warnings
12471
- });
12549
+ await this.onConfigureWarnings(context);
12472
12550
  } catch (err) {
12473
12551
  this.warnVia(`configure warning delivery failed: ${err instanceof Error ? err.message : String(err)}`);
12474
12552
  } finally {
@@ -12974,13 +13052,17 @@ function joinNonEmpty(parts, separator = " · ") {
12974
13052
  function treeLine(depth, text) {
12975
13053
  return `${" ".repeat(depth)}${depth === 0 ? "" : "↳ "}${text}`;
12976
13054
  }
13055
+ function nameMatchEdgeMarker(record, theme) {
13056
+ return asString(record.resolved_by) === "name_match" ? ` ${theme.fg("warning", "~")}` : "";
13057
+ }
12977
13058
  function renderCallTreeNode(node, depth, lines, theme) {
12978
13059
  const name = asString(node.name) ?? "(unknown)";
12979
13060
  const file = shortenPath(asString(node.file) ?? "(unknown file)");
12980
13061
  const line = asNumber(node.line);
12981
13062
  const unresolved = node.resolved === false ? ` ${theme.fg("warning", "[unresolved]")}` : "";
13063
+ const nameMatch = nameMatchEdgeMarker(node, theme);
12982
13064
  const location = line !== undefined ? `[${file}:${line}]` : `[${file}]`;
12983
- lines.push(treeLine(depth, `${name} ${location}${unresolved}`));
13065
+ lines.push(treeLine(depth, `${name} ${location}${unresolved}${nameMatch}`));
12984
13066
  asRecords(node.children).forEach((child) => {
12985
13067
  renderCallTreeNode(child, depth + 1, lines, theme);
12986
13068
  });
@@ -12993,34 +13075,39 @@ function depthWarning(response, theme, depthField = "depth_limited", truncatedFi
12993
13075
  const detail = truncated > 0 ? `, ${truncated} truncated` : "";
12994
13076
  return theme.fg("warning", `(depth limited${detail})`);
12995
13077
  }
12996
- function renderTracePath(path2, index, lines) {
13078
+ function renderTracePath(path2, index, lines, theme) {
12997
13079
  lines.push(`Path ${index + 1}`);
12998
13080
  asRecords(path2.hops).forEach((hop, hopIndex) => {
12999
13081
  const symbol = asString(hop.symbol) ?? "(unknown)";
13000
13082
  const file = shortenPath(asString(hop.file) ?? "(unknown file)");
13001
13083
  const line = asNumber(hop.line);
13002
13084
  const entry = hop.is_entry_point === true ? " [entry]" : "";
13003
- lines.push(treeLine(hopIndex + 1, `${symbol}${entry} ${line !== undefined ? `[${file}:${line}]` : `[${file}]`}`));
13085
+ const nameMatch = nameMatchEdgeMarker(hop, theme);
13086
+ lines.push(treeLine(hopIndex + 1, `${symbol}${entry} ${line !== undefined ? `[${file}:${line}]` : `[${file}]`}${nameMatch}`));
13004
13087
  });
13005
13088
  }
13006
13089
  function renderCallersGroupLines(group, theme) {
13007
13090
  const file = shortenPath(asString(group.file) ?? "(unknown file)");
13008
13091
  const lines = [theme.fg("accent", file)];
13009
13092
  const callers = asRecords(group.callers);
13010
- const bySymbol = new Map;
13093
+ const bySymbolProvenance = new Map;
13011
13094
  for (const caller of callers) {
13012
13095
  const symbol = asString(caller.symbol) ?? "(unknown)";
13096
+ const provenanceKey = asString(caller.resolved_by) === "name_match" ? `${symbol}\x00name_match` : `${symbol}\x00exact`;
13013
13097
  const line = asNumber(caller.line);
13014
- const bucket = bySymbol.get(symbol) ?? [];
13098
+ const bucket = bySymbolProvenance.get(provenanceKey) ?? [];
13015
13099
  if (line !== undefined)
13016
13100
  bucket.push(line);
13017
- bySymbol.set(symbol, bucket);
13101
+ bySymbolProvenance.set(provenanceKey, bucket);
13018
13102
  }
13019
- const symbols = [...bySymbol.keys()].sort((a, b) => a.localeCompare(b));
13020
- for (const symbol of symbols) {
13021
- const lineNums = (bySymbol.get(symbol) ?? []).sort((a, b) => a - b);
13103
+ const keys = [...bySymbolProvenance.keys()].sort((a, b) => a.localeCompare(b));
13104
+ for (const key of keys) {
13105
+ const symbol = key.split("\x00")[0] ?? "(unknown)";
13106
+ const isNameMatch = key.endsWith("\x00name_match");
13107
+ const lineNums = (bySymbolProvenance.get(key) ?? []).sort((a, b) => a - b);
13022
13108
  const linePart = lineNums.length > 0 ? lineNums.map(String).join(", ") : "?";
13023
- lines.push(` ${symbol}:${linePart}`);
13109
+ const marker = isNameMatch ? ` ${theme.fg("warning", "~")}` : "";
13110
+ lines.push(` ↳ ${symbol}:${linePart}${marker}`);
13024
13111
  }
13025
13112
  return lines;
13026
13113
  }
@@ -13066,7 +13153,8 @@ function formatCallgraphSections(op, response, theme = PLAIN_CALLGRAPH_THEME) {
13066
13153
  const symbol = asString(hop.symbol) ?? "(unknown)";
13067
13154
  const file = shortenPath(asString(hop.file) ?? "(unknown file)");
13068
13155
  const line = asNumber(hop.line);
13069
- lines.push(treeLine(index + 1, `${symbol} ${line !== undefined ? `[${file}:${line}]` : `[${file}]`}`));
13156
+ const nameMatch = nameMatchEdgeMarker(hop, theme);
13157
+ lines.push(treeLine(index + 1, `${symbol} ${line !== undefined ? `[${file}:${line}]` : `[${file}]`}${nameMatch}`));
13070
13158
  });
13071
13159
  return lines;
13072
13160
  }
@@ -13086,7 +13174,7 @@ function formatCallgraphSections(op, response, theme = PLAIN_CALLGRAPH_THEME) {
13086
13174
  sections2.push(theme.fg("muted", "No entry paths found."));
13087
13175
  paths.forEach((path2, index) => {
13088
13176
  const lines = [];
13089
- renderTracePath(path2, index, lines);
13177
+ renderTracePath(path2, index, lines, theme);
13090
13178
  sections2.push(lines.join(`
13091
13179
  `));
13092
13180
  });
@@ -13111,11 +13199,12 @@ function formatCallgraphSections(op, response, theme = PLAIN_CALLGRAPH_THEME) {
13111
13199
  const symbol = asString(caller.caller_symbol) ?? "(unknown)";
13112
13200
  const line = asNumber(caller.line) ?? 0;
13113
13201
  const entry = caller.is_entry_point === true ? ` ${theme.fg("warning", "[entry]")}` : "";
13202
+ const nameMatch = nameMatchEdgeMarker(caller, theme);
13114
13203
  const expression = asString(caller.call_expression);
13115
13204
  const params = Array.isArray(caller.parameters) ? caller.parameters.map(String).join(", ") : "";
13116
13205
  sections2.push([
13117
13206
  `${theme.fg("accent", file)}:${line}`,
13118
- ` ↳ ${symbol}${entry}`,
13207
+ ` ↳ ${symbol}${entry}${nameMatch}`,
13119
13208
  expression ? ` ${theme.fg("muted", expression)}` : undefined,
13120
13209
  params ? ` ${theme.fg("muted", `params: ${params}`)}` : undefined
13121
13210
  ].filter(Boolean).join(`
@@ -13138,7 +13227,8 @@ function formatCallgraphSections(op, response, theme = PLAIN_CALLGRAPH_THEME) {
13138
13227
  const variable = asString(hop.variable) ?? "(unknown)";
13139
13228
  const line = asNumber(hop.line) ?? 0;
13140
13229
  const approximate = hop.approximate === true ? ` ${theme.fg("warning", "[approx]")}` : "";
13141
- sections.push(treeLine(index, `${variable} ${theme.fg("muted", `${asString(hop.flow_type) ?? "flow"}`)} ${symbol} [${file}:${line}]${approximate}`));
13230
+ const nameMatch = nameMatchEdgeMarker(hop, theme);
13231
+ sections.push(treeLine(index, `${variable} ${theme.fg("muted", `${asString(hop.flow_type) ?? "flow"}`)} ${symbol} [${file}:${line}]${approximate}${nameMatch}`));
13142
13232
  });
13143
13233
  return sections;
13144
13234
  }
@@ -13163,6 +13253,48 @@ function coerceStringArray(value) {
13163
13253
  }
13164
13254
  return [];
13165
13255
  }
13256
+ function coerceTargetParam(value) {
13257
+ if (Array.isArray(value)) {
13258
+ return value.filter((entry) => typeof entry === "string" && entry.length > 0);
13259
+ }
13260
+ if (typeof value === "string") {
13261
+ const trimmed = value.trim();
13262
+ if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
13263
+ try {
13264
+ const parsed = JSON.parse(trimmed);
13265
+ if (Array.isArray(parsed)) {
13266
+ return parsed.filter((entry) => typeof entry === "string" && entry.length > 0);
13267
+ }
13268
+ } catch {}
13269
+ }
13270
+ return value;
13271
+ }
13272
+ return value;
13273
+ }
13274
+ function coerceBoolean(value, defaultValue = false) {
13275
+ if (defaultValue) {
13276
+ if (value === undefined)
13277
+ return true;
13278
+ if (typeof value === "boolean")
13279
+ return value;
13280
+ if (typeof value === "number")
13281
+ return value !== 0;
13282
+ if (typeof value === "string") {
13283
+ const normalized = value.trim().toLowerCase();
13284
+ return normalized !== "false" && normalized !== "0";
13285
+ }
13286
+ return true;
13287
+ }
13288
+ if (typeof value === "boolean")
13289
+ return value;
13290
+ if (typeof value === "number")
13291
+ return value === 1;
13292
+ if (typeof value === "string") {
13293
+ const normalized = value.trim().toLowerCase();
13294
+ return normalized === "true" || normalized === "1";
13295
+ }
13296
+ return false;
13297
+ }
13166
13298
  function coerceOptionalInt(v, paramName, min, max) {
13167
13299
  if (v === undefined || v === null || v === "")
13168
13300
  return;
@@ -13188,10 +13320,43 @@ function isEmptyParam(value) {
13188
13320
  return Object.keys(value).length === 0;
13189
13321
  return false;
13190
13322
  }
13323
+ // ../aft-bridge/dist/config-tiers.js
13324
+ import { existsSync, readFileSync } from "node:fs";
13325
+ import { resolve as resolve2 } from "node:path";
13326
+ function readConfigTiers(opts) {
13327
+ const tiers = [];
13328
+ try {
13329
+ if (existsSync(opts.userConfigPath)) {
13330
+ const doc = readFileSync(opts.userConfigPath, "utf-8");
13331
+ tiers.push({
13332
+ tier: "user",
13333
+ source: resolve2(opts.userConfigPath),
13334
+ doc
13335
+ });
13336
+ }
13337
+ } catch {}
13338
+ try {
13339
+ if (existsSync(opts.projectConfigPath)) {
13340
+ const doc = readFileSync(opts.projectConfigPath, "utf-8");
13341
+ tiers.push({
13342
+ tier: "project",
13343
+ source: resolve2(opts.projectConfigPath),
13344
+ doc
13345
+ });
13346
+ }
13347
+ } catch {}
13348
+ return tiers;
13349
+ }
13350
+ function formatDroppedKeyWarnings(dropped) {
13351
+ if (!dropped || !Array.isArray(dropped)) {
13352
+ return [];
13353
+ }
13354
+ return dropped.map((item) => `Ignoring ${item.key} from ${item.tier} config (${item.reason})`);
13355
+ }
13191
13356
  // ../aft-bridge/dist/downloader.js
13192
13357
  import { spawnSync } from "node:child_process";
13193
13358
  import { createHash, randomUUID } from "node:crypto";
13194
- import { chmodSync, closeSync, copyFileSync, createWriteStream, existsSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, statSync, unlinkSync, writeSync } from "node:fs";
13359
+ import { chmodSync, closeSync, copyFileSync, createWriteStream, existsSync as existsSync2, mkdirSync, openSync, readFileSync as readFileSync2, renameSync, rmSync, statSync, unlinkSync, writeSync } from "node:fs";
13195
13360
  import { homedir as homedir4 } from "node:os";
13196
13361
  import { join as join3 } from "node:path";
13197
13362
  import { Readable } from "node:stream";
@@ -13263,7 +13428,7 @@ function getCachedBinaryPath(version) {
13263
13428
  if (!version)
13264
13429
  return null;
13265
13430
  const binaryPath = join3(getCacheDir(), version, getBinaryName());
13266
- return existsSync(binaryPath) ? binaryPath : null;
13431
+ return existsSync2(binaryPath) ? binaryPath : null;
13267
13432
  }
13268
13433
  async function downloadBinary(version) {
13269
13434
  const archMap = PLATFORM_ARCH_MAP[process.platform] ?? {};
@@ -13282,7 +13447,7 @@ async function downloadBinary(version) {
13282
13447
  const versionedCacheDir = join3(getCacheDir(), tag);
13283
13448
  const binaryName = getBinaryName();
13284
13449
  const binaryPath = join3(versionedCacheDir, binaryName);
13285
- if (existsSync(binaryPath) && isExpectedCachedBinary(binaryPath, tag)) {
13450
+ if (existsSync2(binaryPath) && isExpectedCachedBinary(binaryPath, tag)) {
13286
13451
  return binaryPath;
13287
13452
  }
13288
13453
  const downloadUrl = `https://github.com/${REPO}/releases/download/${tag}/${assetName}`;
@@ -13296,11 +13461,11 @@ async function downloadBinary(version) {
13296
13461
  let checksumTimeout = null;
13297
13462
  const tmpPath = `${binaryPath}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`;
13298
13463
  try {
13299
- if (!existsSync(versionedCacheDir)) {
13464
+ if (!existsSync2(versionedCacheDir)) {
13300
13465
  mkdirSync(versionedCacheDir, { recursive: true });
13301
13466
  }
13302
13467
  releaseLock = await acquireDownloadLock(lockPath);
13303
- if (existsSync(binaryPath) && isExpectedCachedBinary(binaryPath, tag)) {
13468
+ if (existsSync2(binaryPath) && isExpectedCachedBinary(binaryPath, tag)) {
13304
13469
  return binaryPath;
13305
13470
  }
13306
13471
  binaryController = new AbortController;
@@ -13365,7 +13530,7 @@ async function downloadBinary(version) {
13365
13530
  renameSync(tmpPath, binaryPath);
13366
13531
  }
13367
13532
  try {
13368
- if (existsSync(tmpPath))
13533
+ if (existsSync2(tmpPath))
13369
13534
  unlinkSync(tmpPath);
13370
13535
  } catch {
13371
13536
  warn(`Could not clean up temporary download file ${tmpPath} — it can be removed manually.`);
@@ -13375,7 +13540,7 @@ async function downloadBinary(version) {
13375
13540
  } catch (err) {
13376
13541
  const msg = err instanceof Error ? err.message : String(err);
13377
13542
  error(`Failed to download AFT binary: ${msg}`);
13378
- if (existsSync(tmpPath)) {
13543
+ if (existsSync2(tmpPath)) {
13379
13544
  try {
13380
13545
  unlinkSync(tmpPath);
13381
13546
  } catch {}
@@ -13419,7 +13584,7 @@ async function acquireDownloadLock(lockPath) {
13419
13584
  closeSync(fd);
13420
13585
  } catch {}
13421
13586
  try {
13422
- if (readFileSync(lockPath, "utf-8") === owner) {
13587
+ if (readFileSync2(lockPath, "utf-8") === owner) {
13423
13588
  rmSync(lockPath, { force: true });
13424
13589
  }
13425
13590
  } catch {}
@@ -13440,7 +13605,7 @@ async function acquireDownloadLock(lockPath) {
13440
13605
  if (Date.now() - startedAt > DOWNLOAD_LOCK_TIMEOUT_MS) {
13441
13606
  throw new Error(`Timed out waiting for download lock: ${lockPath}`);
13442
13607
  }
13443
- await new Promise((resolve2) => setTimeout(resolve2, 100));
13608
+ await new Promise((resolve3) => setTimeout(resolve3, 100));
13444
13609
  }
13445
13610
  }
13446
13611
  }
@@ -13495,7 +13660,7 @@ function formatEditSummary(data) {
13495
13660
  if (data.created === true) {
13496
13661
  let s2 = `Created file (${counts}).`;
13497
13662
  if (data.formatted)
13498
- s2 += " Auto-formatted.";
13663
+ s2 += formatAutoFormattedSuffix(data);
13499
13664
  return s2;
13500
13665
  }
13501
13666
  let detail = counts;
@@ -13506,9 +13671,21 @@ function formatEditSummary(data) {
13506
13671
  }
13507
13672
  let s = `Edited (${detail}).`;
13508
13673
  if (data.formatted)
13509
- s += " Auto-formatted.";
13674
+ s += formatAutoFormattedSuffix(data);
13510
13675
  return s;
13511
13676
  }
13677
+ function formatAutoFormattedSuffix(data) {
13678
+ const reflowText = data.reformatted?.text;
13679
+ if (typeof reflowText === "string" && reflowText.length > 0) {
13680
+ return `
13681
+ Auto-formatted — the formatter reflowed your edit. On disk now:
13682
+ ${reflowText}`;
13683
+ }
13684
+ if (data.reformatted?.extensive === true) {
13685
+ return " Auto-formatted — extensive reflow; re-read the file before your next anchored edit.";
13686
+ }
13687
+ return " Auto-formatted.";
13688
+ }
13512
13689
  // ../aft-bridge/dist/format.js
13513
13690
  function compressionSavingsPercent(original, compressed) {
13514
13691
  if (original <= 0)
@@ -13532,20 +13709,71 @@ function stripJsoncSymbols(value) {
13532
13709
  }
13533
13710
  // ../aft-bridge/dist/migration.js
13534
13711
  import { spawnSync as spawnSync2 } from "node:child_process";
13535
- import { existsSync as existsSync4, mkdirSync as mkdirSync4 } from "node:fs";
13536
- import { homedir as homedir6, tmpdir } from "node:os";
13537
- import { dirname as dirname2, join as join6 } from "node:path";
13712
+ import { closeSync as closeSync3, existsSync as existsSync5, mkdirSync as mkdirSync4, openSync as openSync3, readFileSync as readFileSync4, renameSync as renameSync4, rmSync as rmSync2, statSync as statSync2, unlinkSync as unlinkSync3, writeFileSync as writeFileSync2 } from "node:fs";
13713
+ import { homedir as homedir7, tmpdir } from "node:os";
13714
+ import { basename, dirname as dirname2, join as join6, resolve as resolve4 } from "node:path";
13538
13715
 
13539
13716
  // ../aft-bridge/dist/paths.js
13540
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync as renameSync2, writeFileSync } from "node:fs";
13541
- import { dirname, join as join4 } from "node:path";
13717
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, renameSync as renameSync2, writeFileSync } from "node:fs";
13718
+ import { homedir as homedir5 } from "node:os";
13719
+ import { dirname, isAbsolute as isAbsolute2, join as join4, resolve as resolve3 } from "node:path";
13720
+ function homeDir() {
13721
+ if (process.platform === "win32")
13722
+ return process.env.USERPROFILE || process.env.HOME || homedir5();
13723
+ return process.env.HOME || homedir5();
13724
+ }
13725
+ function configHome() {
13726
+ const xdg = process.env.XDG_CONFIG_HOME;
13727
+ if (xdg && isAbsolute2(xdg))
13728
+ return xdg;
13729
+ return join4(homeDir(), ".config");
13730
+ }
13731
+ function legacyOpenCodeConfigDir() {
13732
+ const envDir = process.env.OPENCODE_CONFIG_DIR?.trim();
13733
+ if (envDir)
13734
+ return resolve3(envDir);
13735
+ return join4(configHome(), "opencode");
13736
+ }
13737
+ function legacyPiAgentDir() {
13738
+ return join4(homeDir(), ".pi", "agent");
13739
+ }
13740
+ function legacySources(basePath, label, harness) {
13741
+ return [
13742
+ { path: `${basePath}.jsonc`, label: `${label} aft.jsonc`, harness },
13743
+ { path: `${basePath}.json`, label: `${label} aft.json`, harness }
13744
+ ];
13745
+ }
13746
+ function resolveCortexKitUserConfigPath() {
13747
+ return join4(configHome(), "cortexkit", "aft.jsonc");
13748
+ }
13749
+ function resolveCortexKitProjectConfigPath(projectDirectory) {
13750
+ return join4(projectDirectory, ".cortexkit", "aft.jsonc");
13751
+ }
13752
+ function resolveCortexKitConfigPaths(projectDirectory) {
13753
+ return {
13754
+ userConfigPath: resolveCortexKitUserConfigPath(),
13755
+ projectConfigPath: resolveCortexKitProjectConfigPath(projectDirectory)
13756
+ };
13757
+ }
13758
+ function resolveLegacyAftConfigSources(projectDirectory) {
13759
+ return {
13760
+ user: [
13761
+ ...legacySources(join4(legacyOpenCodeConfigDir(), "aft"), "OpenCode user", "opencode"),
13762
+ ...legacySources(join4(legacyPiAgentDir(), "aft"), "Pi user", "pi")
13763
+ ],
13764
+ project: [
13765
+ ...legacySources(join4(projectDirectory, ".opencode", "aft"), "OpenCode project", "opencode"),
13766
+ ...legacySources(join4(projectDirectory, ".pi", "aft"), "Pi project", "pi")
13767
+ ]
13768
+ };
13769
+ }
13542
13770
  function resolveHarnessStoragePath(storageRoot, harness, ...segments) {
13543
13771
  return join4(storageRoot, harness, ...segments);
13544
13772
  }
13545
13773
  function repairRootScopedStorageFile(storageRoot, harness, fileName) {
13546
13774
  const harnessPath = resolveHarnessStoragePath(storageRoot, harness, fileName);
13547
13775
  const rootPath = join4(storageRoot, fileName);
13548
- if (existsSync2(harnessPath) || !existsSync2(rootPath))
13776
+ if (existsSync3(harnessPath) || !existsSync3(rootPath))
13549
13777
  return harnessPath;
13550
13778
  try {
13551
13779
  mkdirSync2(dirname(harnessPath), { recursive: true });
@@ -13559,8 +13787,8 @@ function shouldShowAnnouncement(storageRoot, harness, currentVersion) {
13559
13787
  const versionFile = repairRootScopedStorageFile(storageRoot, harness, "last_announced_version");
13560
13788
  let lastVersion = "";
13561
13789
  try {
13562
- if (existsSync2(versionFile)) {
13563
- lastVersion = readFileSync2(versionFile, "utf-8").trim();
13790
+ if (existsSync3(versionFile)) {
13791
+ lastVersion = readFileSync3(versionFile, "utf-8").trim();
13564
13792
  }
13565
13793
  } catch {
13566
13794
  return false;
@@ -13588,9 +13816,9 @@ function markAnnouncementSeen(storageRoot, harness, currentVersion) {
13588
13816
 
13589
13817
  // ../aft-bridge/dist/resolver.js
13590
13818
  import { execSync } from "node:child_process";
13591
- import { chmodSync as chmodSync2, closeSync as closeSync2, copyFileSync as copyFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync3, openSync as openSync2, readSync, renameSync as renameSync3, unlinkSync as unlinkSync2 } from "node:fs";
13819
+ import { chmodSync as chmodSync2, closeSync as closeSync2, copyFileSync as copyFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync3, openSync as openSync2, readSync, renameSync as renameSync3, unlinkSync as unlinkSync2 } from "node:fs";
13592
13820
  import { createRequire as createRequire2 } from "node:module";
13593
- import { homedir as homedir5 } from "node:os";
13821
+ import { homedir as homedir6 } from "node:os";
13594
13822
  import { join as join5 } from "node:path";
13595
13823
  var ensureBinaryForResolver = ensureBinary;
13596
13824
  function copyToVersionedCache(npmBinaryPath, knownVersion) {
@@ -13603,7 +13831,7 @@ function copyToVersionedCache(npmBinaryPath, knownVersion) {
13603
13831
  const versionedDir = join5(cacheDir, tag);
13604
13832
  const ext = process.platform === "win32" ? ".exe" : "";
13605
13833
  const cachedPath = join5(versionedDir, `aft${ext}`);
13606
- if (existsSync3(cachedPath)) {
13834
+ if (existsSync4(cachedPath)) {
13607
13835
  const cachedVersion = readBinaryVersion(cachedPath);
13608
13836
  if (cachedVersion === version)
13609
13837
  return cachedPath;
@@ -13615,7 +13843,7 @@ function copyToVersionedCache(npmBinaryPath, knownVersion) {
13615
13843
  if (process.platform !== "win32") {
13616
13844
  chmodSync2(tmpPath, 493);
13617
13845
  }
13618
- if (process.platform === "win32" && existsSync3(cachedPath)) {
13846
+ if (process.platform === "win32" && existsSync4(cachedPath)) {
13619
13847
  try {
13620
13848
  unlinkSync2(cachedPath);
13621
13849
  } catch {}
@@ -13632,7 +13860,7 @@ function normalizeBareVersion(version) {
13632
13860
  return version.startsWith("v") ? version.slice(1) : version;
13633
13861
  }
13634
13862
  function homeDirFromEnv(env) {
13635
- return (process.platform === "win32" ? env.USERPROFILE || env.HOME : env.HOME) || homedir5();
13863
+ return (process.platform === "win32" ? env.USERPROFILE || env.HOME : env.HOME) || homedir6();
13636
13864
  }
13637
13865
  function cacheDirFromEnv(env) {
13638
13866
  if (process.platform === "win32") {
@@ -13644,7 +13872,7 @@ function cacheDirFromEnv(env) {
13644
13872
  }
13645
13873
  function cachedBinaryPathFromEnv(version, env, ext) {
13646
13874
  const binaryPath = join5(cacheDirFromEnv(env), version, `aft${ext}`);
13647
- return existsSync3(binaryPath) ? binaryPath : null;
13875
+ return existsSync4(binaryPath) ? binaryPath : null;
13648
13876
  }
13649
13877
  function isExpectedCachedBinary2(binaryPath, expectedVersion) {
13650
13878
  const expected = normalizeBareVersion(expectedVersion);
@@ -13733,7 +13961,7 @@ function findBinarySync(expectedVersion) {
13733
13961
  const packageBin = `@cortexkit/aft-${key}/bin/aft${ext}`;
13734
13962
  const req = createRequire2(import.meta.url);
13735
13963
  const resolved = req.resolve(packageBin);
13736
- if (existsSync3(resolved)) {
13964
+ if (existsSync4(resolved)) {
13737
13965
  const npmVersion = readBinaryVersion(resolved);
13738
13966
  if (npmVersion === null) {
13739
13967
  warn(`npm platform package binary at ${resolved} did not report a version; skipping (continuing to PATH lookup)`);
@@ -13763,7 +13991,7 @@ function findBinarySync(expectedVersion) {
13763
13991
  }
13764
13992
  } catch {}
13765
13993
  const cargoPath = join5(homeDirFromEnv(env), ".cargo", "bin", `aft${ext}`);
13766
- if (existsSync3(cargoPath)) {
13994
+ if (existsSync4(cargoPath)) {
13767
13995
  const usable = probeBinaryCandidate(cargoPath, "cargo", expectedVersion);
13768
13996
  if (usable)
13769
13997
  return usable;
@@ -13808,23 +14036,343 @@ function dataHome() {
13808
14036
  if (process.env.XDG_DATA_HOME)
13809
14037
  return process.env.XDG_DATA_HOME;
13810
14038
  if (process.platform === "win32") {
13811
- return process.env.LOCALAPPDATA || process.env.APPDATA || join6(homeDir(), "AppData", "Local");
14039
+ return process.env.LOCALAPPDATA || process.env.APPDATA || join6(homeDir2(), "AppData", "Local");
13812
14040
  }
13813
- return join6(homeDir(), ".local", "share");
14041
+ return join6(homeDir2(), ".local", "share");
13814
14042
  }
13815
- function homeDir() {
14043
+ function homeDir2() {
13816
14044
  if (process.platform === "win32")
13817
- return process.env.USERPROFILE || process.env.HOME || homedir6();
13818
- return process.env.HOME || homedir6();
14045
+ return process.env.USERPROFILE || process.env.HOME || homedir7();
14046
+ return process.env.HOME || homedir7();
13819
14047
  }
13820
14048
  function resolveLegacyStorageRoot(harness) {
13821
14049
  if (harness === "pi")
13822
- return join6(homeDir(), ".pi", "agent", "aft");
14050
+ return join6(homeDir2(), ".pi", "agent", "aft");
13823
14051
  return join6(dataHome(), "opencode", "storage", "plugin", "aft");
13824
14052
  }
13825
14053
  function resolveCortexKitStorageRoot() {
13826
14054
  return join6(dataHome(), "cortexkit", "aft");
13827
14055
  }
14056
+ function stripJsoncForParse(input) {
14057
+ let out = "";
14058
+ let inString = false;
14059
+ let escaped = false;
14060
+ for (let i = 0;i < input.length; i++) {
14061
+ const ch = input[i];
14062
+ const next = input[i + 1];
14063
+ if (inString) {
14064
+ out += ch;
14065
+ if (escaped) {
14066
+ escaped = false;
14067
+ } else if (ch === "\\") {
14068
+ escaped = true;
14069
+ } else if (ch === '"') {
14070
+ inString = false;
14071
+ }
14072
+ continue;
14073
+ }
14074
+ if (ch === '"') {
14075
+ inString = true;
14076
+ out += ch;
14077
+ continue;
14078
+ }
14079
+ if (ch === "/" && next === "/") {
14080
+ while (i < input.length && input[i] !== `
14081
+ `)
14082
+ i++;
14083
+ out += `
14084
+ `;
14085
+ continue;
14086
+ }
14087
+ if (ch === "/" && next === "*") {
14088
+ i += 2;
14089
+ while (i < input.length && !(input[i] === "*" && input[i + 1] === "/"))
14090
+ i++;
14091
+ i++;
14092
+ out += " ";
14093
+ continue;
14094
+ }
14095
+ out += ch;
14096
+ }
14097
+ let withoutTrailingCommas = "";
14098
+ inString = false;
14099
+ escaped = false;
14100
+ for (let i = 0;i < out.length; i++) {
14101
+ const ch = out[i];
14102
+ if (inString) {
14103
+ withoutTrailingCommas += ch;
14104
+ if (escaped) {
14105
+ escaped = false;
14106
+ } else if (ch === "\\") {
14107
+ escaped = true;
14108
+ } else if (ch === '"') {
14109
+ inString = false;
14110
+ }
14111
+ continue;
14112
+ }
14113
+ if (ch === '"') {
14114
+ inString = true;
14115
+ withoutTrailingCommas += ch;
14116
+ continue;
14117
+ }
14118
+ if (ch === ",") {
14119
+ let j = i + 1;
14120
+ while (j < out.length && /\s/.test(out[j]))
14121
+ j++;
14122
+ if (out[j] === "}" || out[j] === "]")
14123
+ continue;
14124
+ }
14125
+ withoutTrailingCommas += ch;
14126
+ }
14127
+ return withoutTrailingCommas;
14128
+ }
14129
+ function sortJson(value) {
14130
+ if (Array.isArray(value))
14131
+ return value.map(sortJson);
14132
+ if (value && typeof value === "object") {
14133
+ const sorted = {};
14134
+ for (const key of Object.keys(value).sort()) {
14135
+ sorted[key] = sortJson(value[key]);
14136
+ }
14137
+ return sorted;
14138
+ }
14139
+ return value;
14140
+ }
14141
+ function normalizedJsoncSemantics(content) {
14142
+ return JSON.stringify(sortJson(JSON.parse(stripJsoncForParse(content))));
14143
+ }
14144
+ function fileSemanticsMatch(a, b) {
14145
+ try {
14146
+ return normalizedJsoncSemantics(a) === normalizedJsoncSemantics(b);
14147
+ } catch {
14148
+ return a === b;
14149
+ }
14150
+ }
14151
+ function sleepSync(ms) {
14152
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
14153
+ }
14154
+ function acquireConfigMigrationLock(lockDir) {
14155
+ const deadline = Date.now() + 30000;
14156
+ while (true) {
14157
+ try {
14158
+ mkdirSync4(lockDir, { recursive: false });
14159
+ return () => {
14160
+ try {
14161
+ rmSync2(lockDir, { recursive: true, force: true });
14162
+ } catch {}
14163
+ };
14164
+ } catch (err) {
14165
+ const code = err?.code;
14166
+ if (code !== "EEXIST")
14167
+ throw err;
14168
+ try {
14169
+ const ageMs = Date.now() - statSync2(lockDir).mtimeMs;
14170
+ if (ageMs > 60000) {
14171
+ rmSync2(lockDir, { recursive: true, force: true });
14172
+ continue;
14173
+ }
14174
+ } catch {}
14175
+ if (Date.now() >= deadline) {
14176
+ throw new Error(`timed out waiting for config migration lock ${lockDir}`);
14177
+ }
14178
+ sleepSync(25);
14179
+ }
14180
+ }
14181
+ }
14182
+ function atomicCopyConfigFile(sourcePath, targetPath) {
14183
+ mkdirSync4(dirname2(targetPath), { recursive: true });
14184
+ const tmpPath = join6(dirname2(targetPath), `.${basename(targetPath)}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`);
14185
+ let fd = null;
14186
+ try {
14187
+ fd = openSync3(tmpPath, "wx", 384);
14188
+ writeFileSync2(fd, readFileSync4(sourcePath));
14189
+ closeSync3(fd);
14190
+ fd = null;
14191
+ renameSync4(tmpPath, targetPath);
14192
+ } catch (err) {
14193
+ if (fd !== null) {
14194
+ try {
14195
+ closeSync3(fd);
14196
+ } catch {}
14197
+ }
14198
+ try {
14199
+ unlinkSync3(tmpPath);
14200
+ } catch {}
14201
+ throw err;
14202
+ }
14203
+ }
14204
+ function atomicWriteConfigFile(targetPath, content) {
14205
+ mkdirSync4(dirname2(targetPath), { recursive: true });
14206
+ const tmpPath = join6(dirname2(targetPath), `.${basename(targetPath)}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`);
14207
+ let fd = null;
14208
+ try {
14209
+ fd = openSync3(tmpPath, "wx", 384);
14210
+ writeFileSync2(fd, content);
14211
+ closeSync3(fd);
14212
+ fd = null;
14213
+ renameSync4(tmpPath, targetPath);
14214
+ } catch (err) {
14215
+ if (fd !== null) {
14216
+ try {
14217
+ closeSync3(fd);
14218
+ } catch {}
14219
+ }
14220
+ try {
14221
+ unlinkSync3(tmpPath);
14222
+ } catch {}
14223
+ throw err;
14224
+ }
14225
+ }
14226
+ var MOVED_MARKER_SUFFIX = ".MOVED_READPLEASE";
14227
+ function nonClobberingSidecarPath(desiredPath) {
14228
+ if (!existsSync5(desiredPath))
14229
+ return desiredPath;
14230
+ for (let index = 1;index < Number.MAX_SAFE_INTEGER; index++) {
14231
+ const candidate = `${desiredPath}.${index}`;
14232
+ if (!existsSync5(candidate))
14233
+ return candidate;
14234
+ }
14235
+ throw new Error(`could not find available preservation sidecar path for ${desiredPath}`);
14236
+ }
14237
+ function movedMarkerContent(targetPath, originalName, originalContent) {
14238
+ const header = [
14239
+ "// AFT configuration moved.",
14240
+ "//",
14241
+ "// AFT now reads its configuration from one shared CortexKit location",
14242
+ "// instead of a per-agent path. The settings that were in this file have",
14243
+ "// been moved to:",
14244
+ "//",
14245
+ `// ${targetPath}`,
14246
+ "//",
14247
+ "// Edit that file to change AFT settings. This location is no longer read",
14248
+ "// by AFT.",
14249
+ "//",
14250
+ `// To undo, rename this file back to "${originalName}" (and remove the`,
14251
+ "// CortexKit copy above if you want this location to take precedence).",
14252
+ "//",
14253
+ "// Your original settings are preserved below for reference.",
14254
+ "",
14255
+ ""
14256
+ ].join(`
14257
+ `);
14258
+ return `${header}${originalContent}`;
14259
+ }
14260
+ function markLegacySourcesMovedAside(sources, targetPath, logger) {
14261
+ const warnings = [];
14262
+ const info = logger?.info ?? logger?.log;
14263
+ for (const source of sources) {
14264
+ const desiredMarkerPath = `${source.path}${MOVED_MARKER_SUFFIX}`;
14265
+ const markerPath = nonClobberingSidecarPath(desiredMarkerPath);
14266
+ try {
14267
+ const original = readFileSync4(source.path, "utf-8");
14268
+ if (markerPath !== desiredMarkerPath) {
14269
+ logger?.warn?.(`Preserving existing legacy AFT marker ${desiredMarkerPath}; writing new marker to ${markerPath}`);
14270
+ }
14271
+ atomicWriteConfigFile(markerPath, movedMarkerContent(targetPath, basename(source.path), original));
14272
+ unlinkSync3(source.path);
14273
+ info?.(`Moved legacy AFT config ${source.path} aside to ${markerPath}; AFT now reads ${targetPath}`);
14274
+ } catch (err) {
14275
+ const msg = err instanceof Error ? err.message : String(err);
14276
+ warnings.push(`AFT could not move legacy config ${source.path} aside (${msg}); it is now stale and ignored. Delete it manually — AFT reads ${targetPath}.`);
14277
+ logger?.warn?.(`Could not move legacy AFT config ${source.path} aside (${msg}); AFT reads ${targetPath}`);
14278
+ }
14279
+ }
14280
+ return warnings;
14281
+ }
14282
+ var PRESERVED_OLD_SUFFIX = "_OLD";
14283
+ function preservedOldContent(losingHarness, winningHarness, targetPath, originalContent) {
14284
+ const winnerLine = winningHarness ? `// configs differed, so ${winningHarness}'s config won (first harness to run the` : "// configs differed, so the config that was migrated first won and is the";
14285
+ const header = [
14286
+ `// AFT config from ${losingHarness} — preserved, NOT read.`,
14287
+ "//",
14288
+ "// AFT now reads one shared config for all harnesses. Your OpenCode and Pi",
14289
+ winnerLine,
14290
+ winningHarness ? "// migration) and is now the active config at:" : "// active config at:",
14291
+ "//",
14292
+ `// ${targetPath}`,
14293
+ "//",
14294
+ `// This file holds ${losingHarness}'s previous settings. Merge anything you`,
14295
+ "// want to keep into the active config above, then delete this file. AFT does",
14296
+ "// not read this path.",
14297
+ "",
14298
+ ""
14299
+ ].join(`
14300
+ `);
14301
+ return `${header}${originalContent}`;
14302
+ }
14303
+ function preserveDifferingSourceAsOld(source, winningHarness, targetPath, logger) {
14304
+ const losingHarness = source.harness ?? "pi";
14305
+ const desiredOldPath = `${targetPath}.${losingHarness}${PRESERVED_OLD_SUFFIX}`;
14306
+ const oldPath = nonClobberingSidecarPath(desiredOldPath);
14307
+ try {
14308
+ if (oldPath !== desiredOldPath) {
14309
+ logger?.warn?.(`Preserving existing ${losingHarness} config sidecar ${desiredOldPath}; writing new preserved config to ${oldPath}`);
14310
+ }
14311
+ atomicWriteConfigFile(oldPath, preservedOldContent(losingHarness, winningHarness, targetPath, source.content));
14312
+ } catch (err) {
14313
+ const msg = err instanceof Error ? err.message : String(err);
14314
+ logger?.warn?.(`Could not preserve ${losingHarness} config as ${oldPath} (${msg})`);
14315
+ }
14316
+ const usingDesc = winningHarness ? `${winningHarness}'s config` : "the existing shared config";
14317
+ return `AFT found different OpenCode and Pi configs during config unification. ` + `Using ${usingDesc} at ${targetPath}; preserved ${losingHarness}'s previous ` + `config at ${oldPath} — merge any settings you want to keep, then delete it.`;
14318
+ }
14319
+ function visibleConfigMigrationWarning(scope, targetPath, paths, reason) {
14320
+ const uniquePaths = [...new Set([targetPath, ...paths])];
14321
+ return `AFT ${scope} config migration refused: ${reason}. ` + `Legacy and CortexKit config paths collapse to one file, but AFT will not overwrite or merge them automatically. ` + `Please consolidate manually into ${targetPath}. Paths: ${uniquePaths.join(" ; ")}`;
14322
+ }
14323
+ function migrateAftConfigFile(opts) {
14324
+ const warnings = [];
14325
+ const resolvedTarget = resolve4(opts.targetPath);
14326
+ const existingSources = opts.legacySources.filter((source) => existsSync5(source.path) && resolve4(source.path) !== resolvedTarget);
14327
+ const info = opts.logger?.info ?? opts.logger?.log;
14328
+ if (existingSources.length === 0) {
14329
+ return { migrated: false, conflict: false, targetPath: opts.targetPath, warnings };
14330
+ }
14331
+ mkdirSync4(dirname2(opts.targetPath), { recursive: true });
14332
+ const release = acquireConfigMigrationLock(`${opts.targetPath}.lock`);
14333
+ try {
14334
+ const sources = existingSources.map((source) => ({
14335
+ ...source,
14336
+ content: readFileSync4(source.path, "utf-8")
14337
+ }));
14338
+ if (existsSync5(opts.targetPath)) {
14339
+ const targetContent = readFileSync4(opts.targetPath, "utf-8");
14340
+ for (const source of sources) {
14341
+ if (fileSemanticsMatch(source.content, targetContent))
14342
+ continue;
14343
+ warnings.push(preserveDifferingSourceAsOld(source, undefined, opts.targetPath, opts.logger));
14344
+ }
14345
+ info?.(`AFT ${opts.scope} config already present at ${opts.targetPath}; reconciled ${sources.length} legacy source(s)`);
14346
+ warnings.push(...markLegacySourcesMovedAside(sources, opts.targetPath, opts.logger));
14347
+ return { migrated: false, conflict: false, targetPath: opts.targetPath, warnings };
14348
+ }
14349
+ const winner = (opts.operatingHarness ? sources.find((source) => source.harness === opts.operatingHarness) : undefined) ?? sources[0];
14350
+ atomicCopyConfigFile(winner.path, opts.targetPath);
14351
+ info?.(`Migrated AFT ${opts.scope} config from ${winner.path} to ${opts.targetPath}`);
14352
+ for (const source of sources) {
14353
+ if (source.path === winner.path)
14354
+ continue;
14355
+ if (fileSemanticsMatch(source.content, winner.content))
14356
+ continue;
14357
+ warnings.push(preserveDifferingSourceAsOld(source, winner.harness, opts.targetPath, opts.logger));
14358
+ }
14359
+ warnings.push(...markLegacySourcesMovedAside(sources, opts.targetPath, opts.logger));
14360
+ return {
14361
+ migrated: true,
14362
+ conflict: false,
14363
+ sourcePath: winner.path,
14364
+ targetPath: opts.targetPath,
14365
+ warnings
14366
+ };
14367
+ } catch (err) {
14368
+ const message = visibleConfigMigrationWarning(opts.scope, opts.targetPath, existingSources.map((source) => source.path), `migration failed (${err instanceof Error ? err.message : String(err)})`);
14369
+ warnings.push(message);
14370
+ opts.logger?.warn?.(message);
14371
+ return { migrated: false, conflict: true, targetPath: opts.targetPath, warnings };
14372
+ } finally {
14373
+ release();
14374
+ }
14375
+ }
13828
14376
  function tail(value) {
13829
14377
  if (!value)
13830
14378
  return "";
@@ -13852,11 +14400,11 @@ async function ensureStorageMigrated(opts) {
13852
14400
  const newRoot = resolveCortexKitStorageRoot();
13853
14401
  const targetMarker = resolveHarnessStoragePath(newRoot, opts.harness, TARGET_MARKER);
13854
14402
  const info = opts.logger?.info ?? opts.logger?.log;
13855
- if (existsSync4(targetMarker)) {
14403
+ if (existsSync5(targetMarker)) {
13856
14404
  info?.(`AFT storage already migrated for ${opts.harness}; using ${newRoot}`);
13857
14405
  return;
13858
14406
  }
13859
- if (!existsSync4(legacyRoot)) {
14407
+ if (!existsSync5(legacyRoot)) {
13860
14408
  info?.(`AFT storage migration skipped for ${opts.harness}: no legacy data at ${legacyRoot}; ` + `using ${newRoot} for fresh install`);
13861
14409
  return;
13862
14410
  }
@@ -13904,14 +14452,14 @@ async function ensureStorageMigrated(opts) {
13904
14452
  throw new Error(`AFT storage migration failed (${detail}). ` + `Harness: ${opts.harness}. Legacy: ${legacyRoot}. Target: ${newRoot}. ` + `See log: ${logPath}. ` + `Plugin load aborted to prevent legacy/new state divergence.` + (stderrTail ? ` Stderr tail: ${stderrTail}` : "") + (stdoutTail ? ` Stdout tail: ${stdoutTail}` : ""));
13905
14453
  }
13906
14454
  // ../aft-bridge/dist/npm-resolver.js
13907
- import { readdirSync, statSync as statSync2 } from "node:fs";
13908
- import { homedir as homedir7 } from "node:os";
13909
- import { delimiter, dirname as dirname3, isAbsolute as isAbsolute2, join as join7 } from "node:path";
14455
+ import { readdirSync, statSync as statSync3 } from "node:fs";
14456
+ import { homedir as homedir8 } from "node:os";
14457
+ import { delimiter, dirname as dirname3, isAbsolute as isAbsolute3, join as join7 } from "node:path";
13910
14458
  function defaultDeps() {
13911
14459
  return {
13912
14460
  platform: process.platform,
13913
14461
  env: process.env,
13914
- home: homedir7(),
14462
+ home: homedir8(),
13915
14463
  execPath: process.execPath
13916
14464
  };
13917
14465
  }
@@ -13920,7 +14468,7 @@ function npmBinaryName(platform) {
13920
14468
  }
13921
14469
  function isFile(p) {
13922
14470
  try {
13923
- return statSync2(p).isFile();
14471
+ return statSync3(p).isFile();
13924
14472
  } catch {
13925
14473
  return false;
13926
14474
  }
@@ -13930,7 +14478,7 @@ function npmFromPath(deps) {
13930
14478
  const raw = deps.env.PATH ?? deps.env.Path ?? "";
13931
14479
  for (const entry of raw.split(delimiter)) {
13932
14480
  const dir = entry.trim().replace(/^"|"$/g, "");
13933
- if (!dir || !isAbsolute2(dir))
14481
+ if (!dir || !isAbsolute3(dir))
13934
14482
  continue;
13935
14483
  if (isFile(join7(dir, name)))
13936
14484
  return dir;
@@ -14020,8 +14568,8 @@ function npmSpawnEnv(resolved, baseEnv = process.env) {
14020
14568
  // ../aft-bridge/dist/onnx-runtime.js
14021
14569
  import { execFileSync } from "node:child_process";
14022
14570
  import { createHash as createHash2 } from "node:crypto";
14023
- import { chmodSync as chmodSync3, closeSync as closeSync3, copyFileSync as copyFileSync3, createWriteStream as createWriteStream2, existsSync as existsSync5, lstatSync, mkdirSync as mkdirSync5, openSync as openSync3, readdirSync as readdirSync2, readFileSync as readFileSync3, readlinkSync, realpathSync, rmSync as rmSync2, statSync as statSync3, symlinkSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync2 } from "node:fs";
14024
- import { basename, dirname as dirname4, isAbsolute as isAbsolute3, join as join8, relative as relative2, resolve as resolve2, win32 } from "node:path";
14571
+ import { chmodSync as chmodSync3, closeSync as closeSync4, copyFileSync as copyFileSync3, createWriteStream as createWriteStream2, existsSync as existsSync6, lstatSync, mkdirSync as mkdirSync5, openSync as openSync4, readdirSync as readdirSync2, readFileSync as readFileSync5, readlinkSync, realpathSync, rmSync as rmSync3, statSync as statSync4, symlinkSync, unlinkSync as unlinkSync4, writeFileSync as writeFileSync3 } from "node:fs";
14572
+ import { basename as basename2, dirname as dirname4, isAbsolute as isAbsolute4, join as join8, relative as relative2, resolve as resolve5, win32 } from "node:path";
14025
14573
  import { Readable as Readable2 } from "node:stream";
14026
14574
  import { pipeline as pipeline2 } from "node:stream/promises";
14027
14575
  var ORT_VERSION = "1.24.4";
@@ -14088,7 +14636,7 @@ async function ensureOnnxRuntime(storageDir) {
14088
14636
  const libName = info?.libName ?? "libonnxruntime.dylib";
14089
14637
  const resolvedOrtDir = resolveCachedOnnxRuntimeDir(ortVersionDir, libName);
14090
14638
  const libPath = join8(resolvedOrtDir, libName);
14091
- if (existsSync5(libPath)) {
14639
+ if (existsSync6(libPath)) {
14092
14640
  const meta = readOnnxInstalledMeta(ortVersionDir);
14093
14641
  if (meta?.sha256) {
14094
14642
  try {
@@ -14150,7 +14698,7 @@ function cleanupAbandonedStagingDirs(onnxBaseDir) {
14150
14698
  abandoned = true;
14151
14699
  } else {
14152
14700
  try {
14153
- const ageMs = Date.now() - statSync3(stagingDir).mtimeMs;
14701
+ const ageMs = Date.now() - statSync4(stagingDir).mtimeMs;
14154
14702
  abandoned = ageMs > STALE_LOCK_MS;
14155
14703
  } catch {
14156
14704
  abandoned = true;
@@ -14165,7 +14713,7 @@ function cleanupAbandonedStagingDirs(onnxBaseDir) {
14165
14713
  if (abandoned) {
14166
14714
  log(`[onnx] removing abandoned staging dir ${stagingDir}`);
14167
14715
  try {
14168
- rmSync2(stagingDir, { recursive: true, force: true });
14716
+ rmSync3(stagingDir, { recursive: true, force: true });
14169
14717
  } catch (err) {
14170
14718
  warn(`[onnx] failed to remove ${stagingDir}: ${err}`);
14171
14719
  }
@@ -14175,9 +14723,9 @@ function cleanupAbandonedStagingDirs(onnxBaseDir) {
14175
14723
  }
14176
14724
  function cleanupIncompleteTargetIfUnowned(ortDir) {
14177
14725
  try {
14178
- if (existsSync5(ortDir) && !existsSync5(join8(ortDir, ONNX_INSTALLED_META_FILE))) {
14726
+ if (existsSync6(ortDir) && !existsSync6(join8(ortDir, ONNX_INSTALLED_META_FILE))) {
14179
14727
  log(`[onnx] removing half-populated install dir ${ortDir} (no meta file)`);
14180
- rmSync2(ortDir, { recursive: true, force: true });
14728
+ rmSync3(ortDir, { recursive: true, force: true });
14181
14729
  }
14182
14730
  } catch (err) {
14183
14731
  warn(`[onnx] failed to sweep ${ortDir}: ${err}`);
@@ -14187,7 +14735,7 @@ var REQUIRED_ORT_MAJOR = 1;
14187
14735
  var REQUIRED_ORT_MIN_MINOR = 20;
14188
14736
  var INVALID_ORT_VERSION = "<invalid>";
14189
14737
  function parseOnnxVersionFromPath(value) {
14190
- const name = basename(value);
14738
+ const name = basename2(value);
14191
14739
  const semverish = name.match(/(?:^|[._-])(\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?)(?:\.(?:dylib|dll))?$/);
14192
14740
  if (semverish)
14193
14741
  return semverish[1].split(/[-+]/, 1)[0];
@@ -14204,7 +14752,7 @@ function parseOnnxVersionFromDirectoryPath(value) {
14204
14752
  }
14205
14753
  function isPathInsideRoot(root, candidate) {
14206
14754
  const rel = relative2(root, candidate);
14207
- return rel === "" || !rel.startsWith("../") && !rel.startsWith("..\\") && rel !== ".." && !isAbsolute3(rel) && !win32.isAbsolute(rel);
14755
+ return rel === "" || !rel.startsWith("../") && !rel.startsWith("..\\") && rel !== ".." && !isAbsolute4(rel) && !win32.isAbsolute(rel);
14208
14756
  }
14209
14757
  function detectOnnxVersion(libDir, libName) {
14210
14758
  try {
@@ -14220,7 +14768,7 @@ function detectOnnxVersion(libDir, libName) {
14220
14768
  return version;
14221
14769
  }
14222
14770
  const base = join8(libDir, libName);
14223
- if (existsSync5(base)) {
14771
+ if (existsSync6(base)) {
14224
14772
  try {
14225
14773
  const real = realpathSync(base);
14226
14774
  const version = parseOnnxVersionFromPath(real) ?? parseOnnxVersionFromDirectoryPath(real);
@@ -14255,7 +14803,7 @@ function pathEntriesForPlatform() {
14255
14803
  return pathEnvValue().split(delimiter2).map((entry) => entry.trim().replace(/^"|"$/g, "")).filter((entry) => {
14256
14804
  if (!entry || entry === "." || entry.includes("\x00"))
14257
14805
  return false;
14258
- return isAbsolute3(entry) || win32.isAbsolute(entry);
14806
+ return isAbsolute4(entry) || win32.isAbsolute(entry);
14259
14807
  });
14260
14808
  }
14261
14809
  function directoryContainsLibrary(dir, libName) {
@@ -14271,10 +14819,10 @@ function directoryContainsLibrary(dir, libName) {
14271
14819
  }
14272
14820
  }
14273
14821
  function resolveCachedOnnxRuntimeDir(ortVersionDir, libName) {
14274
- if (existsSync5(join8(ortVersionDir, libName)))
14822
+ if (existsSync6(join8(ortVersionDir, libName)))
14275
14823
  return ortVersionDir;
14276
14824
  const libSubdir = join8(ortVersionDir, "lib");
14277
- if (existsSync5(join8(libSubdir, libName)))
14825
+ if (existsSync6(join8(libSubdir, libName)))
14278
14826
  return libSubdir;
14279
14827
  return ortVersionDir;
14280
14828
  }
@@ -14295,7 +14843,7 @@ function findSystemOnnxRuntime(libName) {
14295
14843
  if (!userProfile)
14296
14844
  return nugetPaths;
14297
14845
  const nugetPackageDir = join8(userProfile, ".nuget", "packages", "microsoft.ml.onnxruntime");
14298
- if (!existsSync5(nugetPackageDir))
14846
+ if (!existsSync6(nugetPackageDir))
14299
14847
  return nugetPaths;
14300
14848
  try {
14301
14849
  for (const entry of readdirSync2(nugetPackageDir, { withFileTypes: true })) {
@@ -14315,7 +14863,7 @@ function findSystemOnnxRuntime(libName) {
14315
14863
  const normalizeCase = process.platform === "win32" || process.platform === "darwin";
14316
14864
  const seen = new Set;
14317
14865
  const uniquePaths = searchPaths.filter((p) => {
14318
- let key = resolve2(p).replace(/[/\\]+$/, "");
14866
+ let key = resolve5(p).replace(/[/\\]+$/, "");
14319
14867
  if (normalizeCase)
14320
14868
  key = key.toLowerCase();
14321
14869
  if (seen.has(key))
@@ -14329,7 +14877,7 @@ function findSystemOnnxRuntime(libName) {
14329
14877
  if (process.platform === "win32") {
14330
14878
  if (!directoryContainsLibrary(dir, libName))
14331
14879
  continue;
14332
- } else if (!existsSync5(libPath)) {
14880
+ } else if (!existsSync6(libPath)) {
14333
14881
  continue;
14334
14882
  }
14335
14883
  const version = detectOnnxVersion(dir, libName);
@@ -14378,7 +14926,7 @@ async function downloadFileWithCap(url, destPath) {
14378
14926
  await pipeline2(nodeStream, createWriteStream2(destPath), { signal: controller.signal });
14379
14927
  } catch (err) {
14380
14928
  try {
14381
- unlinkSync3(destPath);
14929
+ unlinkSync4(destPath);
14382
14930
  } catch {}
14383
14931
  throw err;
14384
14932
  } finally {
@@ -14395,15 +14943,15 @@ function validateExtractedTree(stagingRoot) {
14395
14943
  const lst = lstatSync(fullPath);
14396
14944
  if (lst.isSymbolicLink()) {
14397
14945
  const linkTarget = readlinkSync(fullPath);
14398
- const resolvedTarget = resolve2(dirname4(fullPath), linkTarget);
14946
+ const resolvedTarget = resolve5(dirname4(fullPath), linkTarget);
14399
14947
  const rel2 = relative2(realRoot, resolvedTarget);
14400
- if (rel2.startsWith("..") || isAbsolute3(rel2) || win32.isAbsolute(rel2)) {
14948
+ if (rel2.startsWith("..") || isAbsolute4(rel2) || win32.isAbsolute(rel2)) {
14401
14949
  throw new Error(`extracted symlink ${fullPath} points outside staging root: ${linkTarget}`);
14402
14950
  }
14403
14951
  continue;
14404
14952
  }
14405
14953
  const rel = relative2(realRoot, fullPath);
14406
- if (rel.startsWith("..") || isAbsolute3(rel) || win32.isAbsolute(rel)) {
14954
+ if (rel.startsWith("..") || isAbsolute4(rel) || win32.isAbsolute(rel)) {
14407
14955
  throw new Error(`extracted entry ${fullPath} escapes staging root`);
14408
14956
  }
14409
14957
  if (lst.isDirectory()) {
@@ -14439,11 +14987,11 @@ async function downloadOnnxRuntime(info, targetDir) {
14439
14987
  await extractZipArchive(archivePath, tmpDir);
14440
14988
  }
14441
14989
  try {
14442
- unlinkSync3(archivePath);
14990
+ unlinkSync4(archivePath);
14443
14991
  } catch {}
14444
14992
  validateExtractedTree(tmpDir);
14445
14993
  const extractedDir = join8(tmpDir, info.assetName, "lib");
14446
- if (!existsSync5(extractedDir)) {
14994
+ if (!existsSync6(extractedDir)) {
14447
14995
  throw new Error(`Expected directory not found: ${extractedDir}`);
14448
14996
  }
14449
14997
  mkdirSync5(targetDir, { recursive: true });
@@ -14474,16 +15022,16 @@ async function downloadOnnxRuntime(info, targetDir) {
14474
15022
  warn(`Could not hash newly-installed ONNX library at ${libPath}: ${err}`);
14475
15023
  }
14476
15024
  writeOnnxInstalledMeta(targetDir, ORT_VERSION, libHash, archiveSha256);
14477
- rmSync2(tmpDir, { recursive: true, force: true });
15025
+ rmSync3(tmpDir, { recursive: true, force: true });
14478
15026
  log(`ONNX Runtime v${ORT_VERSION} installed to ${targetDir}`);
14479
15027
  return targetDir;
14480
15028
  } catch (err) {
14481
15029
  error(`Failed to download ONNX Runtime: ${err}`);
14482
15030
  try {
14483
- rmSync2(tmpDir, { recursive: true, force: true });
15031
+ rmSync3(tmpDir, { recursive: true, force: true });
14484
15032
  } catch {}
14485
15033
  try {
14486
- rmSync2(targetDir, { recursive: true, force: true });
15034
+ rmSync3(targetDir, { recursive: true, force: true });
14487
15035
  } catch {}
14488
15036
  return null;
14489
15037
  }
@@ -14500,7 +15048,7 @@ function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, c
14500
15048
  }
14501
15049
  } catch (copyErr) {
14502
15050
  if (requiredLibs.has(libFile)) {
14503
- rmSync2(targetDir, { recursive: true, force: true });
15051
+ rmSync3(targetDir, { recursive: true, force: true });
14504
15052
  throw copyErr;
14505
15053
  }
14506
15054
  log(`ORT extract: failed to copy optional ${libFile}: ${copyErr}`);
@@ -14510,14 +15058,14 @@ function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, c
14510
15058
  for (const link of symlinks) {
14511
15059
  const dst = join8(targetDir, link.name);
14512
15060
  try {
14513
- unlinkSync3(dst);
15061
+ unlinkSync4(dst);
14514
15062
  } catch {}
14515
15063
  const dstForContainment = join8(targetRoot, link.name);
14516
- const resolvedTarget = resolve2(dirname4(dstForContainment), link.target);
15064
+ const resolvedTarget = resolve5(dirname4(dstForContainment), link.target);
14517
15065
  if (!isPathInsideRoot(targetRoot, resolvedTarget)) {
14518
15066
  const message = `ONNX Runtime symlink ${link.name} points outside install dir: ${link.target}`;
14519
15067
  if (requiredLibs.has(link.name)) {
14520
- rmSync2(targetDir, { recursive: true, force: true });
15068
+ rmSync3(targetDir, { recursive: true, force: true });
14521
15069
  throw new Error(message);
14522
15070
  }
14523
15071
  log(`ORT extract: skipping optional symlink ${link.name}: ${message}`);
@@ -14527,15 +15075,15 @@ function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, c
14527
15075
  symlinkSync(link.target, dst);
14528
15076
  } catch (symlinkErr) {
14529
15077
  if (requiredLibs.has(link.name)) {
14530
- rmSync2(targetDir, { recursive: true, force: true });
15078
+ rmSync3(targetDir, { recursive: true, force: true });
14531
15079
  throw symlinkErr;
14532
15080
  }
14533
15081
  log(`ORT extract: failed to symlink optional ${link.name}: ${symlinkErr}`);
14534
15082
  }
14535
15083
  }
14536
15084
  const requiredPath = join8(targetDir, info.libName);
14537
- if (!existsSync5(requiredPath)) {
14538
- rmSync2(targetDir, { recursive: true, force: true });
15085
+ if (!existsSync6(requiredPath)) {
15086
+ rmSync3(targetDir, { recursive: true, force: true });
14539
15087
  throw new Error(`Required ONNX Runtime library missing after install: ${requiredPath}`);
14540
15088
  }
14541
15089
  }
@@ -14560,7 +15108,7 @@ function writeOnnxInstalledMeta(installDir, version, sha256, archiveSha256) {
14560
15108
  ...sha256 ? { sha256 } : {},
14561
15109
  archiveSha256
14562
15110
  };
14563
- writeFileSync2(join8(installDir, ONNX_INSTALLED_META_FILE), JSON.stringify(meta), "utf8");
15111
+ writeFileSync3(join8(installDir, ONNX_INSTALLED_META_FILE), JSON.stringify(meta), "utf8");
14564
15112
  } catch (err) {
14565
15113
  log(`[onnx] failed to write installed-meta in ${installDir}: ${err}`);
14566
15114
  }
@@ -14568,9 +15116,9 @@ function writeOnnxInstalledMeta(installDir, version, sha256, archiveSha256) {
14568
15116
  function readOnnxInstalledMeta(installDir) {
14569
15117
  const path2 = join8(installDir, ONNX_INSTALLED_META_FILE);
14570
15118
  try {
14571
- if (!statSync3(path2).isFile())
15119
+ if (!statSync4(path2).isFile())
14572
15120
  return null;
14573
- const raw = readFileSync3(path2, "utf8");
15121
+ const raw = readFileSync5(path2, "utf8");
14574
15122
  const parsed = JSON.parse(raw);
14575
15123
  if (typeof parsed.version !== "string" || parsed.version.length === 0)
14576
15124
  return null;
@@ -14586,19 +15134,19 @@ function readOnnxInstalledMeta(installDir) {
14586
15134
  }
14587
15135
  function sha256File(path2) {
14588
15136
  const hash = createHash2("sha256");
14589
- hash.update(readFileSync3(path2));
15137
+ hash.update(readFileSync5(path2));
14590
15138
  return hash.digest("hex");
14591
15139
  }
14592
15140
  function acquireLock(lockPath) {
14593
15141
  const tryClaim = () => {
14594
15142
  try {
14595
- const fd = openSync3(lockPath, "wx");
15143
+ const fd = openSync4(lockPath, "wx");
14596
15144
  try {
14597
- writeFileSync2(fd, `${process.pid}
15145
+ writeFileSync3(fd, `${process.pid}
14598
15146
  ${new Date().toISOString()}
14599
15147
  `);
14600
15148
  } finally {
14601
- closeSync3(fd);
15149
+ closeSync4(fd);
14602
15150
  }
14603
15151
  return true;
14604
15152
  } catch (err) {
@@ -14614,12 +15162,12 @@ ${new Date().toISOString()}
14614
15162
  let owningPid = null;
14615
15163
  let lockMtimeMs = 0;
14616
15164
  try {
14617
- const raw = readFileSync3(lockPath, "utf8");
15165
+ const raw = readFileSync5(lockPath, "utf8");
14618
15166
  const firstLine = raw.split(/\r?\n/, 1)[0]?.trim() ?? "";
14619
15167
  const parsed = Number.parseInt(firstLine, 10);
14620
15168
  if (Number.isFinite(parsed) && parsed > 0)
14621
15169
  owningPid = parsed;
14622
- lockMtimeMs = statSync3(lockPath).mtimeMs;
15170
+ lockMtimeMs = statSync4(lockPath).mtimeMs;
14623
15171
  } catch {
14624
15172
  return tryClaim();
14625
15173
  }
@@ -14629,637 +15177,123 @@ ${new Date().toISOString()}
14629
15177
  if (ownerAlive && ageWithinFresh) {
14630
15178
  return false;
14631
15179
  }
14632
- log(`[onnx] reclaiming install lock (owner_pid=${owningPid ?? "unknown"}, alive=${ownerAlive}, age_ms=${age})`);
14633
- try {
14634
- unlinkSync3(lockPath);
14635
- } catch {}
14636
- return tryClaim();
14637
- }
14638
- function releaseLock(lockPath) {
14639
- try {
14640
- let owningPid = null;
14641
- try {
14642
- const raw = readFileSync3(lockPath, "utf8");
14643
- const firstLine = raw.split(/\r?\n/, 1)[0]?.trim() ?? "";
14644
- const parsed = Number.parseInt(firstLine, 10);
14645
- if (Number.isFinite(parsed) && parsed > 0)
14646
- owningPid = parsed;
14647
- } catch (readErr) {
14648
- const code = readErr.code;
14649
- if (code === "ENOENT")
14650
- return;
14651
- warn(`[onnx] could not read lock ${lockPath} during release: ${readErr}`);
14652
- return;
14653
- }
14654
- if (owningPid !== process.pid) {
14655
- log(`[onnx] not releasing lock ${lockPath}: owned by pid ${owningPid ?? "unknown"} (we are ${process.pid})`);
14656
- return;
14657
- }
14658
- try {
14659
- unlinkSync3(lockPath);
14660
- } catch (unlinkErr) {
14661
- const code = unlinkErr.code;
14662
- if (code !== "ENOENT") {
14663
- warn(`[onnx] failed to release lock ${lockPath}: ${unlinkErr}`);
14664
- }
14665
- }
14666
- } catch (err) {
14667
- warn(`[onnx] unexpected error releasing lock ${lockPath}: ${err}`);
14668
- }
14669
- }
14670
- function tasklistPidFromCsvLine(line) {
14671
- const quoted = line.match(/"([^"]*)"/g);
14672
- if (quoted && quoted.length >= 2)
14673
- return quoted[1].slice(1, -1);
14674
- const cells = line.split(",").map((cell) => cell.trim().replace(/^"|"$/g, ""));
14675
- return cells[1] ?? null;
14676
- }
14677
- function isWindowsProcessAlive(pid) {
14678
- if (!Number.isInteger(pid) || pid <= 0)
14679
- return false;
14680
- try {
14681
- const output = execFileSync("tasklist.exe", ["/FI", `PID eq ${pid}`, "/FO", "CSV", "/NH"], {
14682
- encoding: "utf8",
14683
- timeout: 2000,
14684
- windowsHide: true
14685
- });
14686
- const expected = String(pid);
14687
- return output.split(/\r?\n/).some((line) => tasklistPidFromCsvLine(line) === expected);
14688
- } catch {
14689
- return false;
14690
- }
14691
- }
14692
- function isProcessAlive(pid) {
14693
- if (process.platform === "win32")
14694
- return isWindowsProcessAlive(pid);
14695
- try {
14696
- process.kill(pid, 0);
14697
- return true;
14698
- } catch (err) {
14699
- const code = err.code;
14700
- if (code === "ESRCH")
14701
- return false;
14702
- return true;
14703
- }
14704
- }
14705
- // ../aft-bridge/dist/pipe-strip.js
14706
- var NOISE_FILTERS = new Set([
14707
- "grep",
14708
- "rg",
14709
- "head",
14710
- "tail",
14711
- "cat",
14712
- "less",
14713
- "more",
14714
- "sed",
14715
- "awk",
14716
- "cut",
14717
- "sort",
14718
- "uniq",
14719
- "tr",
14720
- "column",
14721
- "fold"
14722
- ]);
14723
- var GREP_GUARD_FLAGS = new Set([
14724
- "c",
14725
- "count",
14726
- "q",
14727
- "quiet",
14728
- "o",
14729
- "only-matching",
14730
- "l",
14731
- "files-with-matches"
14732
- ]);
14733
- function maybeStripCompressorPipe(command, compressionEnabled) {
14734
- if (!compressionEnabled)
14735
- return { command, stripped: false };
14736
- const chain = splitTopLevelCommandChain(command);
14737
- if (chain === null)
14738
- return { command, stripped: false };
14739
- let stripped = false;
14740
- const droppedFilterChains = [];
14741
- const rebuilt = chain.map(({ segment, separator }) => {
14742
- const result = stripSinglePipelineSegment(segment);
14743
- if (result.stripped) {
14744
- stripped = true;
14745
- droppedFilterChains.push(result.filters);
14746
- }
14747
- return `${result.segment}${separator}`;
14748
- }).join("");
14749
- if (!stripped)
14750
- return { command, stripped: false };
14751
- return {
14752
- command: rebuilt,
14753
- stripped: true,
14754
- note: formatStripNote(droppedFilterChains)
14755
- };
14756
- }
14757
- function stripSinglePipelineSegment(segment) {
14758
- const leading = /^\s*/.exec(segment)?.[0] ?? "";
14759
- const trailing = /\s*$/.exec(segment)?.[0] ?? "";
14760
- const coreStart = leading.length;
14761
- const coreEnd = segment.length - trailing.length;
14762
- if (coreEnd <= coreStart)
14763
- return { segment, stripped: false };
14764
- const core = segment.slice(coreStart, coreEnd);
14765
- if (containsUnsplittableConstruct(core))
14766
- return { segment, stripped: false };
14767
- if (hasUnquotedBackground(core))
14768
- return { segment, stripped: false };
14769
- const stages = splitTopLevelPipeline(core);
14770
- if (stages.length < 2)
14771
- return { segment, stripped: false };
14772
- const firstStage = stages[0]?.trim() ?? "";
14773
- if (!isCompressorHandledRunner(firstStage))
14774
- return { segment, stripped: false };
14775
- const filterStages = stages.slice(1).map((stage) => stage.trim());
14776
- for (const stage of filterStages) {
14777
- if (!filterStageIsSafeToDrop(stage))
14778
- return { segment, stripped: false };
14779
- }
14780
- return {
14781
- segment: `${leading}${firstStage}${trailing}`,
14782
- stripped: true,
14783
- filters: filterStages.join(" | ")
14784
- };
14785
- }
14786
- function formatStripNote(droppedFilterChains) {
14787
- const filters = droppedFilterChains.map((filter) => `\`| ${filter}\``).join(", ");
14788
- return `[AFT dropped ${filters} (compressed:false to keep)]`;
14789
- }
14790
- function splitTopLevelCommandChain(command) {
14791
- const segments = [];
14792
- let start = 0;
14793
- let quote = null;
14794
- let escaped = false;
14795
- let inBacktick = false;
14796
- let parenDepth = 0;
14797
- for (let index = 0;index < command.length; index++) {
14798
- const char = command[index];
14799
- const next = command[index + 1];
14800
- if (escaped) {
14801
- escaped = false;
14802
- continue;
14803
- }
14804
- if (inBacktick) {
14805
- if (char === "\\")
14806
- escaped = true;
14807
- else if (char === "`")
14808
- inBacktick = false;
14809
- continue;
14810
- }
14811
- if (char === "\\" && quote !== "'") {
14812
- escaped = true;
14813
- continue;
14814
- }
14815
- if (quote) {
14816
- if (char === quote)
14817
- quote = null;
14818
- continue;
14819
- }
14820
- if (char === "'" || char === '"') {
14821
- quote = char;
14822
- continue;
14823
- }
14824
- if (char === "`") {
14825
- inBacktick = true;
14826
- continue;
14827
- }
14828
- if (char === "(") {
14829
- parenDepth++;
14830
- continue;
14831
- }
14832
- if (char === ")") {
14833
- if (parenDepth === 0)
14834
- return null;
14835
- parenDepth--;
14836
- continue;
14837
- }
14838
- if (parenDepth > 0)
14839
- continue;
14840
- if (char === `
14841
- ` || char === "\r")
14842
- return null;
14843
- if (char === "#" && (index === 0 || command[index - 1] === " " || command[index - 1] === "\t"))
14844
- return null;
14845
- if (char === "&" && next === "&") {
14846
- segments.push({ segment: command.slice(start, index), separator: "&&" });
14847
- start = index + 2;
14848
- index++;
14849
- continue;
14850
- }
14851
- if (char === "|" && next === "|") {
14852
- segments.push({ segment: command.slice(start, index), separator: "||" });
14853
- start = index + 2;
14854
- index++;
14855
- continue;
14856
- }
14857
- if (char === ";") {
14858
- segments.push({ segment: command.slice(start, index), separator: ";" });
14859
- start = index + 1;
14860
- }
14861
- }
14862
- if (escaped || quote || inBacktick || parenDepth !== 0)
14863
- return null;
14864
- segments.push({ segment: command.slice(start), separator: "" });
14865
- return segments;
14866
- }
14867
- function splitTopLevelPipeline(command) {
14868
- const stages = [];
14869
- let start = 0;
14870
- let quote = null;
14871
- let escaped = false;
14872
- for (let index = 0;index < command.length; index++) {
14873
- const char = command[index];
14874
- const next = command[index + 1];
14875
- const previous = command[index - 1];
14876
- if (escaped) {
14877
- escaped = false;
14878
- continue;
14879
- }
14880
- if (char === "\\" && quote !== "'") {
14881
- escaped = true;
14882
- continue;
14883
- }
14884
- if (quote) {
14885
- if (char === quote)
14886
- quote = null;
14887
- continue;
14888
- }
14889
- if (char === "'" || char === '"') {
14890
- quote = char;
14891
- continue;
14892
- }
14893
- if (char === "|" && previous !== "|" && next !== "|") {
14894
- stages.push(command.slice(start, index));
14895
- start = index + 1;
14896
- }
14897
- }
14898
- stages.push(command.slice(start));
14899
- return stages;
14900
- }
14901
- function isCompressorHandledRunner(stage) {
14902
- const tokens = tokenizeStage(stage);
14903
- if (tokens.length === 0)
14904
- return false;
14905
- if (tokens.some((token) => token === "&&" || token === "||" || token.includes(";"))) {
14906
- return false;
14907
- }
14908
- let tokenOffset = 0;
14909
- while (tokenOffset < tokens.length && isEnvAssignment(tokens[tokenOffset])) {
14910
- tokenOffset++;
14911
- }
14912
- const first = runnerName(tokens[tokenOffset]);
14913
- const runnerArgs = tokens.slice(tokenOffset + 1);
14914
- const second = runnerArgs[0];
14915
- const third = runnerArgs[1];
14916
- const rest = runnerArgs;
14917
- if (!first)
14918
- return false;
14919
- if (first === "bun") {
14920
- let args = rest;
14921
- if (args[0] === "--cwd")
14922
- args = args.slice(2);
14923
- else if (args[0]?.startsWith("--cwd="))
14924
- args = args.slice(1);
14925
- const sub = args[0];
14926
- const subNext = args[1];
14927
- return sub === "test" || sub === "run" && isJsVerificationScript(subNext);
14928
- }
14929
- if (first === "npm" || first === "pnpm") {
14930
- return second === "test" || second === "run" && isJsVerificationScript(third);
14931
- }
14932
- if (first === "yarn") {
14933
- return isJsVerificationScript(second) || second === "run" && isJsVerificationScript(third);
14934
- }
14935
- if (first === "deno")
14936
- return ["test", "lint", "check", "bench"].includes(second ?? "");
14937
- if (first === "npx") {
14938
- return ["tsc", "eslint", "vitest", "jest", "playwright", "biome"].includes(second ?? "");
14939
- }
14940
- if (first === "playwright")
14941
- return second === "test";
14942
- if (first === "cargo") {
14943
- return ["test", "build", "check", "clippy", "nextest"].includes(second ?? "");
14944
- }
14945
- if (first === "go")
14946
- return ["test", "build", "vet"].includes(second ?? "");
14947
- if (first === "gradle" || first === "gradlew") {
14948
- return hasBuildTask(rest, ["test", "check", "build", "assemble", "clean"]);
14949
- }
14950
- if (first === "mvn" || first === "mvnw") {
14951
- return hasBuildTask(rest, ["test", "verify", "package", "install", "clean"]);
14952
- }
14953
- if (first === "dotnet")
14954
- return ["test", "build"].includes(second ?? "");
14955
- if (first === "rspec")
14956
- return true;
14957
- if (first === "rake") {
14958
- const positionals = rest.filter((a) => !a.startsWith("-"));
14959
- if (positionals.length === 0)
14960
- return false;
14961
- if (positionals.some((a) => a.includes("/") || a.includes(".") || a.includes("=")))
14962
- return false;
14963
- return positionals.some((a) => a === "test" || a === "spec");
14964
- }
14965
- if (first === "phpunit" || first === "pest")
14966
- return true;
14967
- if (first === "xcodebuild")
14968
- return xcodebuildHasBuildAction(rest);
14969
- if (first === "swift")
14970
- return second === "test" || second === "build";
14971
- if (first === "make" || first === "gmake") {
14972
- return hasBuildTask(rest, ["test", "check", "lint", "clean"]);
14973
- }
14974
- return [
14975
- "vitest",
14976
- "jest",
14977
- "pytest",
14978
- "tsc",
14979
- "eslint",
14980
- "biome",
14981
- "ruff",
14982
- "mypy",
14983
- "tox",
14984
- "nox"
14985
- ].includes(first);
14986
- }
14987
- function isEnvAssignment(token) {
14988
- if (!token)
14989
- return false;
14990
- return /^[a-zA-Z_][a-zA-Z0-9_]*=/.test(token);
14991
- }
14992
- function runnerName(token) {
14993
- if (!token)
14994
- return "";
14995
- const slash = token.lastIndexOf("/");
14996
- return slash === -1 ? token : token.slice(slash + 1);
14997
- }
14998
- function hasBuildTask(args, tasks) {
14999
- const isAllowedTask = (arg) => tasks.some((task) => arg === task || arg.endsWith(`:${task}`));
15000
- const isFlagOrProperty = (arg) => arg.startsWith("-") || arg.includes("=");
15001
- let sawAllowed = false;
15002
- for (const arg of args) {
15003
- if (isFlagOrProperty(arg))
15004
- continue;
15005
- if (!isAllowedTask(arg))
15006
- return false;
15007
- sawAllowed = true;
15008
- }
15009
- return sawAllowed;
15010
- }
15011
- function isJsVerificationScript(token) {
15012
- if (!token)
15013
- return false;
15014
- return token.startsWith("test") || token === "typecheck" || token.startsWith("typecheck:");
15015
- }
15016
- var XCODEBUILD_VALUE_FLAGS = new Set([
15017
- "-scheme",
15018
- "-target",
15019
- "-project",
15020
- "-workspace",
15021
- "-configuration",
15022
- "-sdk",
15023
- "-destination",
15024
- "-arch",
15025
- "-derivedDataPath",
15026
- "-resultBundlePath",
15027
- "-xcconfig",
15028
- "-toolchain"
15029
- ]);
15030
- var XCODEBUILD_BUILD_ACTIONS = new Set([
15031
- "build",
15032
- "test",
15033
- "build-for-testing",
15034
- "test-without-building",
15035
- "analyze"
15036
- ]);
15037
- function xcodebuildHasBuildAction(args) {
15038
- for (let i = 0;i < args.length; i++) {
15039
- const arg = args[i];
15040
- if (arg.startsWith("-")) {
15041
- if (XCODEBUILD_VALUE_FLAGS.has(arg))
15042
- i++;
15043
- continue;
15044
- }
15045
- if (XCODEBUILD_BUILD_ACTIONS.has(arg))
15046
- return true;
15047
- }
15048
- return false;
15180
+ log(`[onnx] reclaiming install lock (owner_pid=${owningPid ?? "unknown"}, alive=${ownerAlive}, age_ms=${age})`);
15181
+ try {
15182
+ unlinkSync4(lockPath);
15183
+ } catch {}
15184
+ return tryClaim();
15049
15185
  }
15050
- function containsUnsplittableConstruct(command) {
15051
- let quote = null;
15052
- let escaped = false;
15053
- for (let i = 0;i < command.length; i++) {
15054
- const char = command[i];
15055
- if (escaped) {
15056
- escaped = false;
15057
- continue;
15058
- }
15059
- if (char === "\\" && quote !== "'") {
15060
- escaped = true;
15061
- continue;
15062
- }
15063
- if (quote === "'") {
15064
- if (char === "'")
15065
- quote = null;
15066
- continue;
15186
+ function releaseLock(lockPath) {
15187
+ try {
15188
+ let owningPid = null;
15189
+ try {
15190
+ const raw = readFileSync5(lockPath, "utf8");
15191
+ const firstLine = raw.split(/\r?\n/, 1)[0]?.trim() ?? "";
15192
+ const parsed = Number.parseInt(firstLine, 10);
15193
+ if (Number.isFinite(parsed) && parsed > 0)
15194
+ owningPid = parsed;
15195
+ } catch (readErr) {
15196
+ const code = readErr.code;
15197
+ if (code === "ENOENT")
15198
+ return;
15199
+ warn(`[onnx] could not read lock ${lockPath} during release: ${readErr}`);
15200
+ return;
15067
15201
  }
15068
- if (quote === '"') {
15069
- if (char === '"')
15070
- quote = null;
15071
- else if (char === "`")
15072
- return true;
15073
- else if (char === "$" && command[i + 1] === "(")
15074
- return true;
15075
- continue;
15202
+ if (owningPid !== process.pid) {
15203
+ log(`[onnx] not releasing lock ${lockPath}: owned by pid ${owningPid ?? "unknown"} (we are ${process.pid})`);
15204
+ return;
15076
15205
  }
15077
- if (char === "'" || char === '"') {
15078
- quote = char;
15079
- continue;
15206
+ try {
15207
+ unlinkSync4(lockPath);
15208
+ } catch (unlinkErr) {
15209
+ const code = unlinkErr.code;
15210
+ if (code !== "ENOENT") {
15211
+ warn(`[onnx] failed to release lock ${lockPath}: ${unlinkErr}`);
15212
+ }
15080
15213
  }
15081
- if (char === "`")
15082
- return true;
15083
- if (char === "(" || char === ")")
15084
- return true;
15214
+ } catch (err) {
15215
+ warn(`[onnx] unexpected error releasing lock ${lockPath}: ${err}`);
15085
15216
  }
15086
- return false;
15087
15217
  }
15088
- var READS_FILE_OPERAND = new Set(["cat", "tac", "nl", "less", "more"]);
15089
- function filterStageIsSafeToDrop(stage) {
15090
- const head = tokenizeStage(stage)[0];
15091
- if (!head)
15092
- return false;
15093
- if (head === "wc")
15094
- return false;
15095
- if (!NOISE_FILTERS.has(head))
15096
- return false;
15097
- if (/[<>]/.test(stage))
15218
+ function tasklistPidFromCsvLine(line) {
15219
+ const quoted = line.match(/"([^"]*)"/g);
15220
+ if (quoted && quoted.length >= 2)
15221
+ return quoted[1].slice(1, -1);
15222
+ const cells = line.split(",").map((cell) => cell.trim().replace(/^"|"$/g, ""));
15223
+ return cells[1] ?? null;
15224
+ }
15225
+ function isWindowsProcessAlive(pid) {
15226
+ if (!Number.isInteger(pid) || pid <= 0)
15098
15227
  return false;
15099
- if (hasUnquotedBackground(stage))
15228
+ try {
15229
+ const output = execFileSync("tasklist.exe", ["/FI", `PID eq ${pid}`, "/FO", "CSV", "/NH"], {
15230
+ encoding: "utf8",
15231
+ timeout: 2000,
15232
+ windowsHide: true
15233
+ });
15234
+ const expected = String(pid);
15235
+ return output.split(/\r?\n/).some((line) => tasklistPidFromCsvLine(line) === expected);
15236
+ } catch {
15100
15237
  return false;
15101
- const args = tokenizeStage(stage).slice(1);
15102
- const hasFlag = (...names) => args.some((a) => names.some((n) => a === n || a.startsWith(`${n}=`)));
15103
- if (head === "grep" || head === "rg") {
15104
- if (hasIntentChangingGrepFlag(args))
15105
- return false;
15106
- if (countBareOperands(args) > 1)
15107
- return false;
15108
- return true;
15109
- }
15110
- if (head === "head" || head === "tail") {
15111
- if (bareOperands(args).some((op) => !/^\d+$/.test(op)))
15112
- return false;
15113
- return true;
15114
15238
  }
15115
- if (READS_FILE_OPERAND.has(head)) {
15116
- if (countBareOperands(args) > 0)
15117
- return false;
15118
- return true;
15119
- }
15120
- if (head === "sed") {
15121
- if (hasFlag("-i", "--in-place"))
15122
- return false;
15123
- if (countBareOperands(args) > 1)
15124
- return false;
15239
+ }
15240
+ function isProcessAlive(pid) {
15241
+ if (process.platform === "win32")
15242
+ return isWindowsProcessAlive(pid);
15243
+ try {
15244
+ process.kill(pid, 0);
15125
15245
  return true;
15126
- }
15127
- if (head === "awk") {
15128
- if (countBareOperands(args) > 1)
15246
+ } catch (err) {
15247
+ const code = err.code;
15248
+ if (code === "ESRCH")
15129
15249
  return false;
15130
15250
  return true;
15131
15251
  }
15132
- if (head === "sort" && hasFlag("-o", "--output"))
15133
- return false;
15134
- if (bareOperands(args).some((op) => op.includes("/") || op.includes(".")))
15135
- return false;
15136
- return true;
15137
- }
15138
- function bareOperands(args) {
15139
- const out = [];
15140
- let afterDoubleDash = false;
15141
- for (const arg of args) {
15142
- if (!afterDoubleDash && arg === "--") {
15143
- afterDoubleDash = true;
15144
- continue;
15145
- }
15146
- if (!afterDoubleDash && arg.startsWith("-") && arg !== "-")
15147
- continue;
15148
- out.push(arg);
15149
- }
15150
- return out;
15151
- }
15152
- function countBareOperands(args) {
15153
- return bareOperands(args).length;
15154
15252
  }
15155
- function hasUnquotedBackground(stage) {
15156
- let quote = null;
15157
- let escaped = false;
15158
- for (let i = 0;i < stage.length; i++) {
15159
- const char = stage[i];
15160
- if (escaped) {
15161
- escaped = false;
15162
- continue;
15163
- }
15164
- if (char === "\\" && quote !== "'") {
15165
- escaped = true;
15166
- continue;
15167
- }
15168
- if (quote) {
15169
- if (char === quote)
15170
- quote = null;
15171
- continue;
15172
- }
15173
- if (char === "'" || char === '"') {
15174
- quote = char;
15175
- continue;
15176
- }
15177
- if (char === "&") {
15178
- const prev = stage[i - 1];
15179
- const next = stage[i + 1];
15180
- if (prev === "&" || next === "&" || prev === ">" || next === ">")
15181
- continue;
15182
- return true;
15183
- }
15253
+ // ../aft-bridge/dist/pool.js
15254
+ import { homedir as homedir9 } from "node:os";
15255
+
15256
+ // ../aft-bridge/dist/project-identity.js
15257
+ import { realpathSync as realpathSync2 } from "node:fs";
15258
+ import { resolve as resolve6 } from "node:path";
15259
+ function canonicalizeProjectRoot(dir) {
15260
+ const trimmed = dir.replace(/[/\\]+$/, "");
15261
+ let canonical;
15262
+ try {
15263
+ canonical = realpathSync2(trimmed);
15264
+ } catch {
15265
+ canonical = resolve6(trimmed);
15184
15266
  }
15185
- return false;
15267
+ return normalizeWindowsRoot(canonical);
15186
15268
  }
15187
- function hasIntentChangingGrepFlag(args) {
15188
- for (const arg of args) {
15189
- if (arg === "--")
15190
- return false;
15191
- if (!arg.startsWith("-") || arg === "-")
15192
- continue;
15193
- if (arg.startsWith("--")) {
15194
- const flag = arg.slice(2).split("=", 1)[0];
15195
- if (GREP_GUARD_FLAGS.has(flag))
15196
- return true;
15197
- continue;
15198
- }
15199
- for (const flag of arg.slice(1)) {
15200
- if (GREP_GUARD_FLAGS.has(flag))
15201
- return true;
15202
- }
15269
+ function normalizeWindowsRoot(p) {
15270
+ if (process.platform !== "win32")
15271
+ return p;
15272
+ let s = p;
15273
+ if (s.startsWith("\\\\?\\UNC\\")) {
15274
+ s = `\\\\${s.slice("\\\\?\\UNC\\".length)}`;
15275
+ } else if (s.startsWith("\\\\?\\")) {
15276
+ s = s.slice("\\\\?\\".length);
15203
15277
  }
15204
- return false;
15205
- }
15206
- function tokenizeStage(stage) {
15207
- const tokens = [];
15208
- let current = "";
15209
- let quote = null;
15210
- let escaped = false;
15211
- for (let index = 0;index < stage.length; index++) {
15212
- const char = stage[index];
15213
- if (escaped) {
15214
- current += char;
15215
- escaped = false;
15216
- continue;
15217
- }
15218
- if (char === "\\" && quote !== "'") {
15219
- escaped = true;
15220
- continue;
15221
- }
15222
- if (quote) {
15223
- if (char === quote) {
15224
- quote = null;
15225
- } else {
15226
- current += char;
15227
- }
15228
- continue;
15229
- }
15230
- if (char === "'" || char === '"') {
15231
- quote = char;
15232
- continue;
15233
- }
15234
- if (/\s/.test(char)) {
15235
- if (current.length > 0) {
15236
- tokens.push(current);
15237
- current = "";
15238
- }
15239
- continue;
15278
+ if (s.length >= 2 && s[1] === ":") {
15279
+ const drive = s.charCodeAt(0);
15280
+ if (drive >= 97 && drive <= 122) {
15281
+ s = s[0].toUpperCase() + s.slice(1);
15240
15282
  }
15241
- current += char;
15242
15283
  }
15243
- if (current.length > 0)
15244
- tokens.push(current);
15245
- return tokens;
15284
+ return s;
15246
15285
  }
15286
+
15247
15287
  // ../aft-bridge/dist/pool.js
15248
- import { realpathSync as realpathSync2 } from "node:fs";
15249
- import { homedir as homedir8 } from "node:os";
15250
15288
  var DEFAULT_IDLE_TIMEOUT_MS = 30 * 60 * 1000;
15251
15289
  var DEFAULT_MAX_POOL_SIZE = 8;
15252
15290
  var CLEANUP_INTERVAL_MS = 60 * 1000;
15253
15291
  function canonicalHomeDir() {
15254
15292
  try {
15255
- const home = homedir8();
15293
+ const home = homedir9();
15256
15294
  if (!home)
15257
15295
  return null;
15258
- try {
15259
- return realpathSync2(home);
15260
- } catch {
15261
- return home.replace(/[/\\]+$/, "");
15262
- }
15296
+ return canonicalizeProjectRoot(home);
15263
15297
  } catch {
15264
15298
  return null;
15265
15299
  }
@@ -15438,12 +15472,7 @@ class BridgePool {
15438
15472
  }
15439
15473
  }
15440
15474
  function normalizeKey(projectRoot) {
15441
- const stripped = projectRoot.replace(/[/\\]+$/, "");
15442
- try {
15443
- return realpathSync2(stripped);
15444
- } catch {
15445
- return stripped;
15446
- }
15475
+ return canonicalizeProjectRoot(projectRoot);
15447
15476
  }
15448
15477
  // ../aft-bridge/dist/tool-format.js
15449
15478
  function asPlainObject(value) {
@@ -16275,7 +16304,7 @@ import {
16275
16304
  // package.json
16276
16305
  var package_default = {
16277
16306
  name: "@cortexkit/aft-pi",
16278
- version: "0.39.3",
16307
+ version: "0.40.0",
16279
16308
  type: "module",
16280
16309
  description: "Pi coding agent extension for Agent File Tools (AFT) — tree-sitter and LSP-powered code analysis",
16281
16310
  main: "dist/index.js",
@@ -16298,7 +16327,7 @@ var package_default = {
16298
16327
  "test:unit": "bun test src/__tests__ --path-ignore-patterns 'src/__tests__/e2e/**'"
16299
16328
  },
16300
16329
  dependencies: {
16301
- "@cortexkit/aft-bridge": "0.39.3",
16330
+ "@cortexkit/aft-bridge": "0.40.0",
16302
16331
  "@xterm/headless": "^5.5.0",
16303
16332
  "comment-json": "^5.0.0",
16304
16333
  diff: "^8.0.4",
@@ -16306,12 +16335,12 @@ var package_default = {
16306
16335
  zod: "^4.1.8"
16307
16336
  },
16308
16337
  optionalDependencies: {
16309
- "@cortexkit/aft-darwin-arm64": "0.39.3",
16310
- "@cortexkit/aft-darwin-x64": "0.39.3",
16311
- "@cortexkit/aft-linux-arm64": "0.39.3",
16312
- "@cortexkit/aft-linux-x64": "0.39.3",
16313
- "@cortexkit/aft-win32-arm64": "0.39.3",
16314
- "@cortexkit/aft-win32-x64": "0.39.3"
16338
+ "@cortexkit/aft-darwin-arm64": "0.40.0",
16339
+ "@cortexkit/aft-darwin-x64": "0.40.0",
16340
+ "@cortexkit/aft-linux-arm64": "0.40.0",
16341
+ "@cortexkit/aft-linux-x64": "0.40.0",
16342
+ "@cortexkit/aft-win32-arm64": "0.40.0",
16343
+ "@cortexkit/aft-win32-x64": "0.40.0"
16315
16344
  },
16316
16345
  devDependencies: {
16317
16346
  "@earendil-works/pi-coding-agent": "*",
@@ -16885,9 +16914,7 @@ function registerStatusCommand(pi, ctx) {
16885
16914
  }
16886
16915
 
16887
16916
  // src/config.ts
16888
- import { existsSync as existsSync6, readFileSync as readFileSync4, renameSync as renameSync4, unlinkSync as unlinkSync4, writeFileSync as writeFileSync3 } from "node:fs";
16889
- import { homedir as homedir9 } from "node:os";
16890
- import { join as join10 } from "node:path";
16917
+ import { existsSync as existsSync7, readFileSync as readFileSync6, renameSync as renameSync5, unlinkSync as unlinkSync5, writeFileSync as writeFileSync4 } from "node:fs";
16891
16918
  var import_comment_json = __toESM(require_src2(), 1);
16892
16919
 
16893
16920
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/external.js
@@ -30423,7 +30450,7 @@ function date4(params) {
30423
30450
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/external.js
30424
30451
  config(en_default());
30425
30452
  // src/config.ts
30426
- var FOREGROUND_WAIT_WINDOW_DEFAULT_MS = 8000;
30453
+ var FOREGROUND_WAIT_WINDOW_DEFAULT_MS = 15000;
30427
30454
  var FOREGROUND_WAIT_WINDOW_MIN_MS = 5000;
30428
30455
  function resolveBashConfig(config2) {
30429
30456
  const top = config2.bash;
@@ -30594,105 +30621,6 @@ var AftConfigSchema = exports_external.object({
30594
30621
  max_callgraph_files: exports_external.number().int().positive().optional(),
30595
30622
  bridge: BridgeConfigSchema.optional()
30596
30623
  }).strict();
30597
- function normalizeLspExtension(extension) {
30598
- return extension.trim().replace(/^\.+/, "");
30599
- }
30600
- function resolveLspConfigForConfigure(config2) {
30601
- const overrides = {};
30602
- const disabled = new Set(config2.lsp?.disabled ?? []);
30603
- let experimentalTy = config2.experimental?.lsp_ty;
30604
- switch (config2.lsp?.python ?? "auto") {
30605
- case "ty":
30606
- experimentalTy = true;
30607
- disabled.add("python");
30608
- break;
30609
- case "pyright":
30610
- experimentalTy = false;
30611
- disabled.add("ty");
30612
- break;
30613
- case "auto":
30614
- break;
30615
- }
30616
- if (experimentalTy !== undefined) {
30617
- overrides.experimental_lsp_ty = experimentalTy;
30618
- }
30619
- const servers = Object.entries(config2.lsp?.servers ?? {}).map(([id, server]) => {
30620
- const entry = {
30621
- id,
30622
- args: server.args,
30623
- root_markers: server.root_markers,
30624
- disabled: server.disabled
30625
- };
30626
- if (server.extensions && server.extensions.length > 0) {
30627
- entry.extensions = server.extensions.map(normalizeLspExtension);
30628
- }
30629
- if (server.binary) {
30630
- entry.binary = server.binary;
30631
- }
30632
- if (server.env && Object.keys(server.env).length > 0) {
30633
- entry.env = server.env;
30634
- }
30635
- if (server.initialization_options !== undefined) {
30636
- entry.initialization_options = server.initialization_options;
30637
- }
30638
- return entry;
30639
- });
30640
- if (servers.length > 0) {
30641
- overrides.lsp_servers = servers;
30642
- }
30643
- if (disabled.size > 0) {
30644
- overrides.disabled_lsp = [...disabled];
30645
- }
30646
- return overrides;
30647
- }
30648
- function resolveProjectOverridesForConfigure(config2) {
30649
- const overrides = {};
30650
- if (config2.format_on_edit !== undefined)
30651
- overrides.format_on_edit = config2.format_on_edit;
30652
- if (config2.formatter_timeout_secs !== undefined)
30653
- overrides.formatter_timeout_secs = config2.formatter_timeout_secs;
30654
- if (config2.validate_on_edit !== undefined)
30655
- overrides.validate_on_edit = config2.validate_on_edit;
30656
- if (config2.formatter !== undefined)
30657
- overrides.formatter = config2.formatter;
30658
- if (config2.checker !== undefined)
30659
- overrides.checker = config2.checker;
30660
- overrides.restrict_to_project_root = config2.restrict_to_project_root ?? false;
30661
- if (config2.search_index !== undefined)
30662
- overrides.search_index = config2.search_index;
30663
- if (config2.semantic_search !== undefined)
30664
- overrides.semantic_search = config2.semantic_search;
30665
- if (config2.callgraph_store !== undefined)
30666
- overrides.callgraph_store = config2.callgraph_store;
30667
- if (config2.callgraph_chunk_size !== undefined)
30668
- overrides.callgraph_chunk_size = config2.callgraph_chunk_size;
30669
- Object.assign(overrides, resolveExperimentalConfigForConfigure(config2));
30670
- Object.assign(overrides, resolveLspConfigForConfigure(config2));
30671
- if (config2.semantic !== undefined)
30672
- overrides.semantic = config2.semantic;
30673
- if (config2.inspect !== undefined)
30674
- overrides.inspect = config2.inspect;
30675
- if (config2.max_callgraph_files !== undefined)
30676
- overrides.max_callgraph_files = config2.max_callgraph_files;
30677
- return overrides;
30678
- }
30679
- function resolveExperimentalConfigForConfigure(config2) {
30680
- const overrides = {};
30681
- const bash = resolveBashConfig(config2);
30682
- overrides.experimental_bash_rewrite = bash.rewrite;
30683
- overrides.experimental_bash_compress = bash.compress;
30684
- overrides.experimental_bash_background = bash.background;
30685
- if (bash.long_running_reminder_enabled !== undefined) {
30686
- overrides.bash_long_running_reminder_enabled = bash.long_running_reminder_enabled;
30687
- }
30688
- if (bash.long_running_reminder_interval_ms !== undefined) {
30689
- overrides.bash_long_running_reminder_interval_ms = bash.long_running_reminder_interval_ms;
30690
- }
30691
- if (config2.experimental?.lsp_ty !== undefined) {
30692
- overrides.experimental_lsp_ty = config2.experimental.lsp_ty;
30693
- }
30694
- return overrides;
30695
- }
30696
30624
  var CONFIG_MIGRATIONS = [
30697
30625
  { oldKey: "experimental_search_index", newPath: ["search_index"] },
30698
30626
  { oldKey: "experimental_semantic_search", newPath: ["semantic_search"] },
@@ -30802,14 +30730,14 @@ function migrateExperimentalBashBlock(rawConfig, configPath, logger) {
30802
30730
  }
30803
30731
  return movedKeys;
30804
30732
  }
30805
- function migrateAftConfigFile(configPath, logger = { log: log2, warn: warn2 }) {
30806
- if (!existsSync6(configPath)) {
30733
+ function migrateAftConfigFile2(configPath, logger = { log: log2, warn: warn2 }) {
30734
+ if (!existsSync7(configPath)) {
30807
30735
  return { migrated: false, oldKeys: [] };
30808
30736
  }
30809
30737
  let tmpPath = null;
30810
30738
  let oldKeys = [];
30811
30739
  try {
30812
- const content = readFileSync4(configPath, "utf-8");
30740
+ const content = readFileSync6(configPath, "utf-8");
30813
30741
  const rawConfig = import_comment_json.parse(content);
30814
30742
  if (!rawConfig || typeof rawConfig !== "object" || Array.isArray(rawConfig)) {
30815
30743
  return { migrated: false, oldKeys: [] };
@@ -30825,14 +30753,14 @@ function migrateAftConfigFile(configPath, logger = { log: log2, warn: warn2 }) {
30825
30753
  `)}
30826
30754
  ${serialized}` : serialized;
30827
30755
  tmpPath = `${configPath}.tmp.${process.pid}`;
30828
- writeFileSync3(tmpPath, nextContent, "utf-8");
30829
- renameSync4(tmpPath, configPath);
30756
+ writeFileSync4(tmpPath, nextContent, "utf-8");
30757
+ renameSync5(tmpPath, configPath);
30830
30758
  logger.log(`Migrated config at ${configPath}: removed ${oldKeys.join(", ")}`);
30831
30759
  return { migrated: true, oldKeys };
30832
30760
  } catch (err) {
30833
30761
  if (tmpPath) {
30834
30762
  try {
30835
- unlinkSync4(tmpPath);
30763
+ unlinkSync5(tmpPath);
30836
30764
  } catch {}
30837
30765
  }
30838
30766
  if (isWritableMigrationError(err)) {
@@ -30843,15 +30771,6 @@ ${serialized}` : serialized;
30843
30771
  return { migrated: false, oldKeys: [] };
30844
30772
  }
30845
30773
  }
30846
- function detectConfigFile(basePath) {
30847
- const jsoncPath = `${basePath}.jsonc`;
30848
- const jsonPath = `${basePath}.json`;
30849
- if (existsSync6(jsoncPath))
30850
- return { format: "jsonc", path: jsoncPath };
30851
- if (existsSync6(jsonPath))
30852
- return { format: "json", path: jsonPath };
30853
- return { format: "none", path: jsonPath };
30854
- }
30855
30774
  var configLoadErrors = [];
30856
30775
  function getConfigLoadErrors() {
30857
30776
  return configLoadErrors;
@@ -30865,9 +30784,9 @@ function recordConfigParseFailure(configPath, errorMessage) {
30865
30784
  }
30866
30785
  function loadConfigFromPath(configPath) {
30867
30786
  try {
30868
- if (!existsSync6(configPath))
30787
+ if (!existsSync7(configPath))
30869
30788
  return null;
30870
- const content = readFileSync4(configPath, "utf-8");
30789
+ const content = readFileSync6(configPath, "utf-8");
30871
30790
  const rawConfig = import_comment_json.parse(content);
30872
30791
  if (!rawConfig || typeof rawConfig !== "object" || Array.isArray(rawConfig)) {
30873
30792
  recordConfigParseFailure(configPath, "root must be an object");
@@ -31084,21 +31003,43 @@ function resolveBridgePoolTransportOptions(config2) {
31084
31003
  hangThreshold: config2.bridge?.hang_threshold ?? DEFAULT_BRIDGE_HANG_THRESHOLD
31085
31004
  };
31086
31005
  }
31087
- function getGlobalPiDir() {
31088
- return join10(homedir9(), ".pi", "agent");
31006
+ function migrateAftConfigLocations(projectDirectory, logger = { log: log2, warn: warn2 }) {
31007
+ const paths = resolveCortexKitConfigPaths(projectDirectory);
31008
+ const legacy = resolveLegacyAftConfigSources(projectDirectory);
31009
+ return [
31010
+ migrateAftConfigFile({
31011
+ scope: "user",
31012
+ targetPath: paths.userConfigPath,
31013
+ legacySources: legacy.user,
31014
+ operatingHarness: "pi",
31015
+ logger
31016
+ }),
31017
+ migrateAftConfigFile({
31018
+ scope: "project",
31019
+ targetPath: paths.projectConfigPath,
31020
+ legacySources: legacy.project,
31021
+ operatingHarness: "pi",
31022
+ logger
31023
+ })
31024
+ ];
31025
+ }
31026
+ function resolveAftConfigPaths(projectDirectory) {
31027
+ const paths = resolveCortexKitConfigPaths(projectDirectory);
31028
+ migrateAftConfigFile2(paths.userConfigPath);
31029
+ migrateAftConfigFile2(paths.projectConfigPath);
31030
+ return paths;
31031
+ }
31032
+ function buildConfigTierConfigureParams(projectDirectory, processState = {}) {
31033
+ const paths = resolveAftConfigPaths(projectDirectory);
31034
+ return {
31035
+ ...processState,
31036
+ cortexkit_user_config_path: paths.userConfigPath,
31037
+ config: readConfigTiers(paths)
31038
+ };
31089
31039
  }
31090
31040
  function loadAftConfig(projectDirectory) {
31091
31041
  configLoadErrors = [];
31092
- const userBasePath = join10(getGlobalPiDir(), "aft");
31093
- migrateAftConfigFile(`${userBasePath}.jsonc`);
31094
- migrateAftConfigFile(`${userBasePath}.json`);
31095
- const userDetected = detectConfigFile(userBasePath);
31096
- const userConfigPath = userDetected.format !== "none" ? userDetected.path : `${userBasePath}.json`;
31097
- const projectBasePath = join10(projectDirectory, ".pi", "aft");
31098
- migrateAftConfigFile(`${projectBasePath}.jsonc`);
31099
- migrateAftConfigFile(`${projectBasePath}.json`);
31100
- const projectDetected = detectConfigFile(projectBasePath);
31101
- const projectConfigPath = projectDetected.format !== "none" ? projectDetected.path : `${projectBasePath}.json`;
31042
+ const { userConfigPath, projectConfigPath } = resolveAftConfigPaths(projectDirectory);
31102
31043
  let config2 = loadConfigFromPath(userConfigPath) ?? {};
31103
31044
  const projectConfig = loadConfigFromPath(projectConfigPath);
31104
31045
  if (projectConfig) {
@@ -31123,56 +31064,56 @@ import { spawn as spawn2 } from "node:child_process";
31123
31064
  import { createHash as createHash3 } from "node:crypto";
31124
31065
  import {
31125
31066
  createReadStream,
31126
- existsSync as existsSync8,
31067
+ existsSync as existsSync9,
31127
31068
  mkdirSync as mkdirSync7,
31128
- readFileSync as readFileSync7,
31129
- renameSync as renameSync5,
31130
- rmSync as rmSync3,
31131
- statSync as statSync5,
31132
- writeFileSync as writeFileSync5
31069
+ readFileSync as readFileSync9,
31070
+ renameSync as renameSync6,
31071
+ rmSync as rmSync4,
31072
+ statSync as statSync6,
31073
+ writeFileSync as writeFileSync6
31133
31074
  } from "node:fs";
31134
- import { join as join13 } from "node:path";
31075
+ import { join as join12 } from "node:path";
31135
31076
 
31136
31077
  // src/lsp-cache.ts
31137
31078
  import {
31138
- closeSync as closeSync4,
31079
+ closeSync as closeSync5,
31139
31080
  mkdirSync as mkdirSync6,
31140
- openSync as openSync4,
31141
- readFileSync as readFileSync5,
31142
- statSync as statSync4,
31143
- unlinkSync as unlinkSync5,
31144
- writeFileSync as writeFileSync4
31081
+ openSync as openSync5,
31082
+ readFileSync as readFileSync7,
31083
+ statSync as statSync5,
31084
+ unlinkSync as unlinkSync6,
31085
+ writeFileSync as writeFileSync5
31145
31086
  } from "node:fs";
31146
31087
  import { homedir as homedir10 } from "node:os";
31147
- import { join as join11 } from "node:path";
31088
+ import { join as join10 } from "node:path";
31148
31089
  function aftCacheBase() {
31149
31090
  const override = process.env.AFT_CACHE_DIR;
31150
31091
  if (override && override.length > 0)
31151
31092
  return override;
31152
31093
  if (process.platform === "win32") {
31153
31094
  const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
31154
- const base2 = localAppData || join11(homedir10(), "AppData", "Local");
31155
- return join11(base2, "aft");
31095
+ const base2 = localAppData || join10(homedir10(), "AppData", "Local");
31096
+ return join10(base2, "aft");
31156
31097
  }
31157
- const base = process.env.XDG_CACHE_HOME || join11(homedir10(), ".cache");
31158
- return join11(base, "aft");
31098
+ const base = process.env.XDG_CACHE_HOME || join10(homedir10(), ".cache");
31099
+ return join10(base, "aft");
31159
31100
  }
31160
31101
  function lspCacheRoot() {
31161
- return join11(aftCacheBase(), "lsp-packages");
31102
+ return join10(aftCacheBase(), "lsp-packages");
31162
31103
  }
31163
31104
  function lspPackageDir(npmPackage) {
31164
- return join11(lspCacheRoot(), encodeURIComponent(npmPackage));
31105
+ return join10(lspCacheRoot(), encodeURIComponent(npmPackage));
31165
31106
  }
31166
31107
  function lspBinaryPath(npmPackage, binary) {
31167
- return join11(lspPackageDir(npmPackage), "node_modules", ".bin", binary);
31108
+ return join10(lspPackageDir(npmPackage), "node_modules", ".bin", binary);
31168
31109
  }
31169
31110
  function lspBinDir(npmPackage) {
31170
- return join11(lspPackageDir(npmPackage), "node_modules", ".bin");
31111
+ return join10(lspPackageDir(npmPackage), "node_modules", ".bin");
31171
31112
  }
31172
31113
  function isInstalled(npmPackage, binary) {
31173
31114
  for (const candidate of lspBinaryCandidates(binary)) {
31174
31115
  try {
31175
- if (statSync4(join11(lspBinDir(npmPackage), candidate)).isFile())
31116
+ if (statSync5(join10(lspBinDir(npmPackage), candidate)).isFile())
31176
31117
  return true;
31177
31118
  } catch {}
31178
31119
  }
@@ -31192,17 +31133,17 @@ function writeInstalledMetaIn(installDir, version2, sha256) {
31192
31133
  installedAt: new Date().toISOString(),
31193
31134
  ...sha256 ? { sha256 } : {}
31194
31135
  };
31195
- writeFileSync4(join11(installDir, INSTALLED_META_FILE), JSON.stringify(meta3), "utf8");
31136
+ writeFileSync5(join10(installDir, INSTALLED_META_FILE), JSON.stringify(meta3), "utf8");
31196
31137
  } catch (err) {
31197
31138
  log2(`[lsp-cache] failed to write installed-meta in ${installDir}: ${err}`);
31198
31139
  }
31199
31140
  }
31200
31141
  function readInstalledMetaIn(installDir) {
31201
- const path3 = join11(installDir, INSTALLED_META_FILE);
31142
+ const path3 = join10(installDir, INSTALLED_META_FILE);
31202
31143
  try {
31203
- if (!statSync4(path3).isFile())
31144
+ if (!statSync5(path3).isFile())
31204
31145
  return null;
31205
- const raw = readFileSync5(path3, "utf8");
31146
+ const raw = readFileSync7(path3, "utf8");
31206
31147
  const parsed = JSON.parse(raw);
31207
31148
  if (typeof parsed.version !== "string" || parsed.version.length === 0)
31208
31149
  return null;
@@ -31222,7 +31163,7 @@ function readInstalledMeta(packageKey) {
31222
31163
  return readInstalledMetaIn(lspPackageDir(packageKey));
31223
31164
  }
31224
31165
  function lockPath(npmPackage) {
31225
- return join11(lspPackageDir(npmPackage), ".aft-installing");
31166
+ return join10(lspPackageDir(npmPackage), ".aft-installing");
31226
31167
  }
31227
31168
  var STALE_LOCK_MS2 = 30 * 60 * 1000;
31228
31169
  function acquireInstallLock(lockKey) {
@@ -31230,13 +31171,13 @@ function acquireInstallLock(lockKey) {
31230
31171
  const lock = lockPath(lockKey);
31231
31172
  const tryClaim = () => {
31232
31173
  try {
31233
- const fd = openSync4(lock, "wx");
31174
+ const fd = openSync5(lock, "wx");
31234
31175
  try {
31235
- writeFileSync4(fd, `${process.pid}
31176
+ writeFileSync5(fd, `${process.pid}
31236
31177
  ${new Date().toISOString()}
31237
31178
  `);
31238
31179
  } finally {
31239
- closeSync4(fd);
31180
+ closeSync5(fd);
31240
31181
  }
31241
31182
  return true;
31242
31183
  } catch (err) {
@@ -31252,12 +31193,12 @@ ${new Date().toISOString()}
31252
31193
  let owningPid = null;
31253
31194
  let lockMtimeMs = 0;
31254
31195
  try {
31255
- const raw = readFileSync5(lock, "utf8");
31196
+ const raw = readFileSync7(lock, "utf8");
31256
31197
  const firstLine = raw.split(/\r?\n/, 1)[0]?.trim() ?? "";
31257
31198
  const parsed = Number.parseInt(firstLine, 10);
31258
31199
  if (Number.isFinite(parsed) && parsed > 0)
31259
31200
  owningPid = parsed;
31260
- lockMtimeMs = statSync4(lock).mtimeMs;
31201
+ lockMtimeMs = statSync5(lock).mtimeMs;
31261
31202
  } catch {
31262
31203
  return tryClaim();
31263
31204
  }
@@ -31270,7 +31211,7 @@ ${new Date().toISOString()}
31270
31211
  }
31271
31212
  log2(`[lsp] reclaiming install lock for ${lockKey} (owner_pid=${owningPid ?? "unknown"}, alive=${ownerAlive}, age_ms=${age})`);
31272
31213
  try {
31273
- unlinkSync5(lock);
31214
+ unlinkSync6(lock);
31274
31215
  } catch {}
31275
31216
  return tryClaim();
31276
31217
  }
@@ -31290,7 +31231,7 @@ function releaseInstallLock(lockKey) {
31290
31231
  try {
31291
31232
  let owningPid = null;
31292
31233
  try {
31293
- const raw = readFileSync5(lock, "utf8");
31234
+ const raw = readFileSync7(lock, "utf8");
31294
31235
  const firstLine = raw.split(/\r?\n/, 1)[0]?.trim() ?? "";
31295
31236
  const parsed = Number.parseInt(firstLine, 10);
31296
31237
  if (Number.isFinite(parsed) && parsed > 0)
@@ -31307,7 +31248,7 @@ function releaseInstallLock(lockKey) {
31307
31248
  return;
31308
31249
  }
31309
31250
  try {
31310
- unlinkSync5(lock);
31251
+ unlinkSync6(lock);
31311
31252
  } catch (unlinkErr) {
31312
31253
  const code = unlinkErr.code;
31313
31254
  if (code !== "ENOENT") {
@@ -31329,9 +31270,9 @@ async function withInstallLock(lockKey, task) {
31329
31270
  }
31330
31271
  var VERSION_CHECK_FILE = ".aft-version-check";
31331
31272
  function readVersionCheck(npmPackage) {
31332
- const file2 = join11(lspPackageDir(npmPackage), VERSION_CHECK_FILE);
31273
+ const file2 = join10(lspPackageDir(npmPackage), VERSION_CHECK_FILE);
31333
31274
  try {
31334
- const raw = readFileSync5(file2, "utf8");
31275
+ const raw = readFileSync7(file2, "utf8");
31335
31276
  const parsed = JSON.parse(raw);
31336
31277
  if (typeof parsed.last_checked === "string") {
31337
31278
  return {
@@ -31346,12 +31287,12 @@ function readVersionCheck(npmPackage) {
31346
31287
  }
31347
31288
  function writeVersionCheck(npmPackage, latest) {
31348
31289
  mkdirSync6(lspPackageDir(npmPackage), { recursive: true });
31349
- const file2 = join11(lspPackageDir(npmPackage), VERSION_CHECK_FILE);
31290
+ const file2 = join10(lspPackageDir(npmPackage), VERSION_CHECK_FILE);
31350
31291
  const record2 = {
31351
31292
  last_checked: new Date().toISOString(),
31352
31293
  latest_eligible: latest
31353
31294
  };
31354
- writeFileSync4(file2, JSON.stringify(record2, null, 2));
31295
+ writeFileSync5(file2, JSON.stringify(record2, null, 2));
31355
31296
  }
31356
31297
  function shouldRecheckVersion(record2, weeklyCheckIntervalMs = 7 * 24 * 60 * 60 * 1000) {
31357
31298
  if (!record2)
@@ -31508,8 +31449,8 @@ var NPM_LSP_TABLE = [
31508
31449
  ];
31509
31450
 
31510
31451
  // src/lsp-project-relevance.ts
31511
- import { existsSync as existsSync7, readdirSync as readdirSync3, readFileSync as readFileSync6 } from "node:fs";
31512
- import { join as join12 } from "node:path";
31452
+ import { existsSync as existsSync8, readdirSync as readdirSync3, readFileSync as readFileSync8 } from "node:fs";
31453
+ import { join as join11 } from "node:path";
31513
31454
  var MAX_WALK_DIRS = 200;
31514
31455
  var MAX_WALK_DEPTH = 4;
31515
31456
  var NOISE_DIRS = new Set([
@@ -31526,7 +31467,7 @@ function hasRootMarker(projectRoot, rootMarkers) {
31526
31467
  if (!rootMarkers)
31527
31468
  return false;
31528
31469
  for (const marker of rootMarkers) {
31529
- if (existsSync7(join12(projectRoot, marker)))
31470
+ if (existsSync8(join11(projectRoot, marker)))
31530
31471
  return true;
31531
31472
  }
31532
31473
  return false;
@@ -31550,7 +31491,7 @@ function hasPackageJsonDep(projectRoot, depNames) {
31550
31491
  }
31551
31492
  function readPackageJson(projectRoot) {
31552
31493
  try {
31553
- const raw = readFileSync6(join12(projectRoot, "package.json"), "utf8");
31494
+ const raw = readFileSync8(join11(projectRoot, "package.json"), "utf8");
31554
31495
  const parsed = JSON.parse(raw);
31555
31496
  if (typeof parsed !== "object" || parsed === null)
31556
31497
  return null;
@@ -31580,7 +31521,7 @@ function relevantExtensionsInProject(projectRoot, extToServer) {
31580
31521
  for (const entry of entries) {
31581
31522
  if (entry.isDirectory()) {
31582
31523
  if (current.depth < MAX_WALK_DEPTH && !NOISE_DIRS.has(entry.name.toLowerCase())) {
31583
- queue.push({ dir: join12(current.dir, entry.name), depth: current.depth + 1 });
31524
+ queue.push({ dir: join11(current.dir, entry.name), depth: current.depth + 1 });
31584
31525
  }
31585
31526
  continue;
31586
31527
  }
@@ -31707,9 +31648,9 @@ async function resolveTargetVersion(spec, config2, fetchImpl = fetch) {
31707
31648
  }
31708
31649
  function ensureInstallAnchor(cwd) {
31709
31650
  try {
31710
- const stub = join13(cwd, "package.json");
31711
- if (!existsSync8(stub)) {
31712
- writeFileSync5(stub, `${JSON.stringify({ name: "aft-lsp-cache", version: "0.0.0", private: true })}
31651
+ const stub = join12(cwd, "package.json");
31652
+ if (!existsSync9(stub)) {
31653
+ writeFileSync6(stub, `${JSON.stringify({ name: "aft-lsp-cache", version: "0.0.0", private: true })}
31713
31654
  `);
31714
31655
  }
31715
31656
  } catch (err) {
@@ -31717,18 +31658,18 @@ function ensureInstallAnchor(cwd) {
31717
31658
  }
31718
31659
  }
31719
31660
  function runInstall(spec, version2, cwd, signal) {
31720
- return new Promise((resolve3) => {
31661
+ return new Promise((resolve7) => {
31721
31662
  const target = `${spec.npm}@${version2}`;
31722
31663
  log2(`[lsp] installing ${target} to ${cwd}`);
31723
31664
  if (signal?.aborted) {
31724
31665
  warn2(`[lsp] install ${target} aborted before spawn`);
31725
- resolve3(false);
31666
+ resolve7(false);
31726
31667
  return;
31727
31668
  }
31728
31669
  const npm = resolveNpm();
31729
31670
  if (!npm) {
31730
31671
  warn2(`[lsp] npm not found on PATH or known locations; cannot install ${target}`);
31731
- resolve3(false);
31672
+ resolve7(false);
31732
31673
  return;
31733
31674
  }
31734
31675
  ensureInstallAnchor(cwd);
@@ -31751,7 +31692,7 @@ function runInstall(spec, version2, cwd, signal) {
31751
31692
  return;
31752
31693
  settled = true;
31753
31694
  cleanup();
31754
- resolve3(ok);
31695
+ resolve7(ok);
31755
31696
  };
31756
31697
  const onAbort = () => {
31757
31698
  warn2(`[lsp] install ${target} aborted during shutdown`);
@@ -31852,7 +31793,7 @@ function cachedPackageDir(npmPackage) {
31852
31793
  return lspBinDir(npmPackage).replace(/[\\/]node_modules[\\/]\.bin[\\/]?$/, "");
31853
31794
  }
31854
31795
  function hashInstalledBinary(spec) {
31855
- return new Promise((resolve3, reject) => {
31796
+ return new Promise((resolve7, reject) => {
31856
31797
  const candidates = process.platform === "win32" ? [
31857
31798
  lspBinaryPath(spec.npm, spec.binary),
31858
31799
  lspBinaryPath(spec.npm, `${spec.binary}.cmd`),
@@ -31862,7 +31803,7 @@ function hashInstalledBinary(spec) {
31862
31803
  let pathToHash = null;
31863
31804
  for (const p of candidates) {
31864
31805
  try {
31865
- if (statSync5(p).isFile()) {
31806
+ if (statSync6(p).isFile()) {
31866
31807
  pathToHash = p;
31867
31808
  break;
31868
31809
  }
@@ -31876,7 +31817,7 @@ function hashInstalledBinary(spec) {
31876
31817
  const stream = createReadStream(pathToHash);
31877
31818
  stream.on("error", reject);
31878
31819
  stream.on("data", (chunk) => hash2.update(chunk));
31879
- stream.on("end", () => resolve3(hash2.digest("hex")));
31820
+ stream.on("end", () => resolve7(hash2.digest("hex")));
31880
31821
  });
31881
31822
  }
31882
31823
  function installedBinaryPath(spec) {
@@ -31888,23 +31829,23 @@ function installedBinaryPath(spec) {
31888
31829
  ] : [lspBinaryPath(spec.npm, spec.binary)];
31889
31830
  for (const candidate of candidates) {
31890
31831
  try {
31891
- if (statSync5(candidate).isFile())
31832
+ if (statSync6(candidate).isFile())
31892
31833
  return candidate;
31893
31834
  } catch {}
31894
31835
  }
31895
31836
  return null;
31896
31837
  }
31897
31838
  function sha256OfFileSync(path3) {
31898
- return createHash3("sha256").update(readFileSync7(path3)).digest("hex");
31839
+ return createHash3("sha256").update(readFileSync9(path3)).digest("hex");
31899
31840
  }
31900
31841
  function quarantineCachedNpmInstall(spec, reason) {
31901
31842
  const packageDir = lspPackageDir(spec.npm);
31902
- const dest = join13(packageDir, "..", ".quarantine", encodeURIComponent(spec.npm), `${Date.now()}`);
31843
+ const dest = join12(packageDir, "..", ".quarantine", encodeURIComponent(spec.npm), `${Date.now()}`);
31903
31844
  warn2(`[lsp] tofu_mismatch ${spec.npm}: ${reason}; quarantining ${packageDir} -> ${dest}`);
31904
31845
  try {
31905
- mkdirSync7(join13(dest, ".."), { recursive: true });
31906
- rmSync3(dest, { recursive: true, force: true });
31907
- renameSync5(packageDir, dest);
31846
+ mkdirSync7(join12(dest, ".."), { recursive: true });
31847
+ rmSync4(dest, { recursive: true, force: true });
31848
+ renameSync6(packageDir, dest);
31908
31849
  } catch (err) {
31909
31850
  warn2(`[lsp] tofu_mismatch ${spec.npm}: failed to quarantine cache entry: ${err}`);
31910
31851
  }
@@ -31984,20 +31925,20 @@ import {
31984
31925
  copyFileSync as copyFileSync4,
31985
31926
  createReadStream as createReadStream2,
31986
31927
  createWriteStream as createWriteStream3,
31987
- existsSync as existsSync9,
31928
+ existsSync as existsSync10,
31988
31929
  lstatSync as lstatSync2,
31989
31930
  mkdirSync as mkdirSync8,
31990
31931
  readdirSync as readdirSync4,
31991
- readFileSync as readFileSync8,
31932
+ readFileSync as readFileSync10,
31992
31933
  readlinkSync as readlinkSync2,
31993
31934
  realpathSync as realpathSync3,
31994
- renameSync as renameSync6,
31995
- rmSync as rmSync4,
31996
- statSync as statSync6,
31997
- unlinkSync as unlinkSync6,
31998
- writeFileSync as writeFileSync6
31935
+ renameSync as renameSync7,
31936
+ rmSync as rmSync5,
31937
+ statSync as statSync7,
31938
+ unlinkSync as unlinkSync7,
31939
+ writeFileSync as writeFileSync7
31999
31940
  } from "node:fs";
32000
- import { dirname as dirname5, join as join14, relative as relative3, resolve as resolve3 } from "node:path";
31941
+ import { dirname as dirname5, join as join13, relative as relative3, resolve as resolve7 } from "node:path";
32001
31942
  import { Readable as Readable3 } from "node:stream";
32002
31943
  import { pipeline as pipeline3 } from "node:stream/promises";
32003
31944
 
@@ -32087,26 +32028,26 @@ function detectHostPlatform() {
32087
32028
 
32088
32029
  // src/lsp-github-install.ts
32089
32030
  function ghCacheRoot() {
32090
- return join14(aftCacheBase(), "lsp-binaries");
32031
+ return join13(aftCacheBase(), "lsp-binaries");
32091
32032
  }
32092
32033
  function ghPackageDir(spec) {
32093
- return join14(ghCacheRoot(), spec.id);
32034
+ return join13(ghCacheRoot(), spec.id);
32094
32035
  }
32095
32036
  function ghBinDir(spec) {
32096
- return join14(ghPackageDir(spec), "bin");
32037
+ return join13(ghPackageDir(spec), "bin");
32097
32038
  }
32098
32039
  function ghExtractDir(spec) {
32099
- return join14(ghPackageDir(spec), "extracted");
32040
+ return join13(ghPackageDir(spec), "extracted");
32100
32041
  }
32101
32042
  var INSTALLED_META_FILE2 = ".aft-installed";
32102
32043
  function ghBinaryPath(spec, platform) {
32103
32044
  const ext = platform === "win32" ? ".exe" : "";
32104
- return join14(ghBinDir(spec), `${spec.binary}${ext}`);
32045
+ return join13(ghBinDir(spec), `${spec.binary}${ext}`);
32105
32046
  }
32106
32047
  function isGithubInstalled(spec, platform) {
32107
32048
  for (const candidate of ghBinaryCandidates(spec, platform)) {
32108
32049
  try {
32109
- if (statSync6(join14(ghBinDir(spec), candidate)).isFile())
32050
+ if (statSync7(join13(ghBinDir(spec), candidate)).isFile())
32110
32051
  return true;
32111
32052
  } catch {}
32112
32053
  }
@@ -32119,10 +32060,10 @@ function ghBinaryCandidates(spec, platform) {
32119
32060
  }
32120
32061
  function readGithubInstalledMetaIn(installDir) {
32121
32062
  try {
32122
- const path3 = join14(installDir, INSTALLED_META_FILE2);
32123
- if (!statSync6(path3).isFile())
32063
+ const path3 = join13(installDir, INSTALLED_META_FILE2);
32064
+ if (!statSync7(path3).isFile())
32124
32065
  return null;
32125
- const parsed = JSON.parse(readFileSync8(path3, "utf8"));
32066
+ const parsed = JSON.parse(readFileSync10(path3, "utf8"));
32126
32067
  if (typeof parsed.version !== "string" || parsed.version.length === 0)
32127
32068
  return null;
32128
32069
  return {
@@ -32146,7 +32087,7 @@ function writeGithubInstalledMetaIn(installDir, version2, binarySha256, archiveS
32146
32087
  binarySha256,
32147
32088
  ...archiveSha256 ? { archiveSha256 } : {}
32148
32089
  };
32149
- writeFileSync6(join14(installDir, INSTALLED_META_FILE2), JSON.stringify(meta3), "utf8");
32090
+ writeFileSync7(join13(installDir, INSTALLED_META_FILE2), JSON.stringify(meta3), "utf8");
32150
32091
  } catch (err) {
32151
32092
  warn2(`[lsp] failed to write github installed metadata in ${installDir}: ${err}`);
32152
32093
  }
@@ -32154,16 +32095,16 @@ function writeGithubInstalledMetaIn(installDir, version2, binarySha256, archiveS
32154
32095
  var MAX_DOWNLOAD_BYTES3 = 256 * 1024 * 1024;
32155
32096
  var MAX_EXTRACT_BYTES2 = 1024 * 1024 * 1024;
32156
32097
  function sha256OfFile(path3) {
32157
- return new Promise((resolve4, reject) => {
32098
+ return new Promise((resolve8, reject) => {
32158
32099
  const hash2 = createHash4("sha256");
32159
32100
  const stream = createReadStream2(path3);
32160
32101
  stream.on("error", reject);
32161
32102
  stream.on("data", (chunk) => hash2.update(chunk));
32162
- stream.on("end", () => resolve4(hash2.digest("hex")));
32103
+ stream.on("end", () => resolve8(hash2.digest("hex")));
32163
32104
  });
32164
32105
  }
32165
32106
  function sha256OfFileSync2(path3) {
32166
- return createHash4("sha256").update(readFileSync8(path3)).digest("hex");
32107
+ return createHash4("sha256").update(readFileSync10(path3)).digest("hex");
32167
32108
  }
32168
32109
  async function fetchReleaseByTag(githubRepo, tag, fetchImpl, signal) {
32169
32110
  const candidates = [];
@@ -32356,7 +32297,7 @@ async function downloadFile(url2, destPath, fetchImpl, assetSize, signal) {
32356
32297
  await pipeline3(nodeStream, createWriteStream3(destPath), { signal: timeout.signal });
32357
32298
  } catch (err) {
32358
32299
  try {
32359
- unlinkSync6(destPath);
32300
+ unlinkSync7(destPath);
32360
32301
  } catch {}
32361
32302
  throw err;
32362
32303
  } finally {
@@ -32395,7 +32336,7 @@ function validateExtraction(stagingRoot) {
32395
32336
  throw new Error(`failed to read staging dir ${dir}: ${err}`);
32396
32337
  }
32397
32338
  for (const entry of entries) {
32398
- const full = join14(dir, entry);
32339
+ const full = join13(dir, entry);
32399
32340
  let lst;
32400
32341
  try {
32401
32342
  lst = lstatSync2(full);
@@ -32416,7 +32357,7 @@ function validateExtraction(stagingRoot) {
32416
32357
  throw new Error(`failed to realpath ${full}: ${err}`);
32417
32358
  }
32418
32359
  const rel = relative3(realStagingRoot, realFull);
32419
- if (rel.startsWith("..") || resolve3(realStagingRoot, rel) !== realFull) {
32360
+ if (rel.startsWith("..") || resolve7(realStagingRoot, rel) !== realFull) {
32420
32361
  throw new Error(`archive entry escapes staging root: ${full} → ${realFull} (zip-slip defense)`);
32421
32362
  }
32422
32363
  if (lst.isDirectory()) {
@@ -32463,7 +32404,7 @@ function extractArchiveSafely(archivePath, destDir, archiveType) {
32463
32404
  const suffix = randomBytes(8).toString("hex");
32464
32405
  const stagingDir = `${destDir}.staging-${suffix}`;
32465
32406
  try {
32466
- rmSync4(stagingDir, { recursive: true, force: true });
32407
+ rmSync5(stagingDir, { recursive: true, force: true });
32467
32408
  } catch {}
32468
32409
  mkdirSync8(stagingDir, { recursive: true });
32469
32410
  try {
@@ -32471,24 +32412,24 @@ function extractArchiveSafely(archivePath, destDir, archiveType) {
32471
32412
  runPlatformExtractor(archivePath, stagingDir, archiveType);
32472
32413
  validateExtraction(stagingDir);
32473
32414
  try {
32474
- rmSync4(destDir, { recursive: true, force: true });
32415
+ rmSync5(destDir, { recursive: true, force: true });
32475
32416
  } catch {}
32476
- renameSync6(stagingDir, destDir);
32417
+ renameSync7(stagingDir, destDir);
32477
32418
  } catch (err) {
32478
32419
  try {
32479
- rmSync4(stagingDir, { recursive: true, force: true });
32420
+ rmSync5(stagingDir, { recursive: true, force: true });
32480
32421
  } catch {}
32481
32422
  throw err;
32482
32423
  }
32483
32424
  }
32484
32425
  function quarantineCachedGithubInstall(spec, reason) {
32485
32426
  const packageDir = ghPackageDir(spec);
32486
- const dest = join14(ghCacheRoot(), ".quarantine", encodeURIComponent(spec.id), `${Date.now()}`);
32427
+ const dest = join13(ghCacheRoot(), ".quarantine", encodeURIComponent(spec.id), `${Date.now()}`);
32487
32428
  warn2(`[lsp] tofu_mismatch ${spec.id}: ${reason}; quarantining ${packageDir} -> ${dest}`);
32488
32429
  try {
32489
32430
  mkdirSync8(dirname5(dest), { recursive: true });
32490
- rmSync4(dest, { recursive: true, force: true });
32491
- renameSync6(packageDir, dest);
32431
+ rmSync5(dest, { recursive: true, force: true });
32432
+ renameSync7(packageDir, dest);
32492
32433
  } catch (err) {
32493
32434
  warn2(`[lsp] tofu_mismatch ${spec.id}: failed to quarantine cache entry: ${err}`);
32494
32435
  }
@@ -32496,9 +32437,9 @@ function quarantineCachedGithubInstall(spec, reason) {
32496
32437
  function validateCachedGithubInstall(spec, platform) {
32497
32438
  const packageDir = ghPackageDir(spec);
32498
32439
  const meta3 = readGithubInstalledMetaIn(packageDir);
32499
- const binaryPath = ghBinaryCandidates(spec, platform).map((candidate) => join14(ghBinDir(spec), candidate)).find((candidate) => {
32440
+ const binaryPath = ghBinaryCandidates(spec, platform).map((candidate) => join13(ghBinDir(spec), candidate)).find((candidate) => {
32500
32441
  try {
32501
- return statSync6(candidate).isFile();
32442
+ return statSync7(candidate).isFile();
32502
32443
  } catch {
32503
32444
  return false;
32504
32445
  }
@@ -32574,7 +32515,7 @@ async function downloadAndInstall(spec, tag, assets, platform, arch, fetchImpl,
32574
32515
  }
32575
32516
  const pkgDir = ghPackageDir(spec);
32576
32517
  const extractDir = ghExtractDir(spec);
32577
- const archivePath = join14(pkgDir, expected.name);
32518
+ const archivePath = join13(pkgDir, expected.name);
32578
32519
  log2(`[lsp] downloading ${spec.id} ${tag} → ${matchingAsset.url}`);
32579
32520
  try {
32580
32521
  await downloadFile(matchingAsset.url, archivePath, fetchImpl, matchingAsset.size, signal);
@@ -32588,7 +32529,7 @@ async function downloadAndInstall(spec, tag, assets, platform, arch, fetchImpl,
32588
32529
  } catch (err) {
32589
32530
  error2(`[lsp] hash ${spec.id} failed: ${err}`);
32590
32531
  try {
32591
- unlinkSync6(archivePath);
32532
+ unlinkSync7(archivePath);
32592
32533
  } catch {}
32593
32534
  return null;
32594
32535
  }
@@ -32599,7 +32540,7 @@ async function downloadAndInstall(spec, tag, assets, platform, arch, fetchImpl,
32599
32540
  if (previousArchiveSha256 !== archiveSha256) {
32600
32541
  error2(`[lsp] ${spec.id} ${tag}: TOFU sha256 mismatch — refusing install. ` + `Previously installed archive sha256=${previousArchiveSha256}, downloaded sha256=${archiveSha256}. ` + `This means the published release for tag ${tag} changed. Investigate before proceeding.`);
32601
32542
  try {
32602
- unlinkSync6(archivePath);
32543
+ unlinkSync7(archivePath);
32603
32544
  } catch {}
32604
32545
  return null;
32605
32546
  }
@@ -32611,11 +32552,11 @@ async function downloadAndInstall(spec, tag, assets, platform, arch, fetchImpl,
32611
32552
  return null;
32612
32553
  } finally {
32613
32554
  try {
32614
- unlinkSync6(archivePath);
32555
+ unlinkSync7(archivePath);
32615
32556
  } catch {}
32616
32557
  }
32617
- const innerBinaryPath = join14(extractDir, spec.binaryPathInArchive(platform, arch, version2));
32618
- if (!existsSync9(innerBinaryPath)) {
32558
+ const innerBinaryPath = join13(extractDir, spec.binaryPathInArchive(platform, arch, version2));
32559
+ if (!existsSync10(innerBinaryPath)) {
32619
32560
  error2(`[lsp] ${spec.id}: extracted binary not found at ${innerBinaryPath}`);
32620
32561
  return null;
32621
32562
  }
@@ -32699,7 +32640,7 @@ function runGithubAutoInstall(relevantServers, config2, fetchImpl = fetch) {
32699
32640
  if (!host) {
32700
32641
  for (const spec of GITHUB_LSP_TABLE) {
32701
32642
  try {
32702
- if (existsSync9(ghBinDir(spec))) {
32643
+ if (existsSync10(ghBinDir(spec))) {
32703
32644
  cachedBinDirs.push(ghBinDir(spec));
32704
32645
  }
32705
32646
  } catch {}
@@ -32869,9 +32810,13 @@ function warningTitle(warning) {
32869
32810
  return "LSP binary is missing";
32870
32811
  case "config_parse_failed":
32871
32812
  return "Config failed to parse";
32813
+ case "config_key_dropped":
32814
+ return "Config key ignored";
32872
32815
  }
32873
32816
  }
32874
32817
  function formatConfigureWarning(warning) {
32818
+ if (warning.kind === "config_key_dropped")
32819
+ return `${WARNING_MARKER} ${warning.hint}`;
32875
32820
  const details = [];
32876
32821
  if (warning.language)
32877
32822
  details.push(`language: ${warning.language}`);
@@ -33014,7 +32959,7 @@ function renderScreen(state, rows = state.rows, cols = state.cols) {
33014
32959
  `);
33015
32960
  }
33016
32961
  function writeTerminal(terminal, data) {
33017
- return new Promise((resolve4) => terminal.write(data, resolve4));
32962
+ return new Promise((resolve8) => terminal.write(data, resolve8));
33018
32963
  }
33019
32964
 
33020
32965
  // src/shutdown-hooks.ts
@@ -33071,8 +33016,8 @@ function installProcessHandlers() {
33071
33016
  }
33072
33017
  signalShutdownStarted = true;
33073
33018
  process.exitCode = SIGNAL_EXIT_CODES[sig];
33074
- const timeout = new Promise((resolve4) => {
33075
- setTimeout(resolve4, SIGNAL_CLEANUP_TIMEOUT_MS);
33019
+ const timeout = new Promise((resolve8) => {
33020
+ setTimeout(resolve8, SIGNAL_CLEANUP_TIMEOUT_MS);
33076
33021
  });
33077
33022
  Promise.race([runCleanups(sig), timeout]).finally(() => {
33078
33023
  process.exit(SIGNAL_EXIT_CODES[sig]);
@@ -33115,7 +33060,7 @@ import { Type as Type3 } from "typebox";
33115
33060
  // src/tools/hoisted.ts
33116
33061
  import { stat } from "node:fs/promises";
33117
33062
  import { homedir as homedir11 } from "node:os";
33118
- import { isAbsolute as isAbsolute4, relative as relative4, resolve as resolve4, sep } from "node:path";
33063
+ import { isAbsolute as isAbsolute5, relative as relative4, resolve as resolve8, sep } from "node:path";
33119
33064
  import {
33120
33065
  renderDiff
33121
33066
  } from "@earendil-works/pi-coding-agent";
@@ -33230,7 +33175,7 @@ function diagnosticsOnEditDefault(ctx) {
33230
33175
  }
33231
33176
  function containsPath(parent, child) {
33232
33177
  const rel = relative4(parent, child);
33233
- return rel === "" || !rel.startsWith("..") && !isAbsolute4(rel);
33178
+ return rel === "" || !rel.startsWith("..") && !isAbsolute5(rel);
33234
33179
  }
33235
33180
  function expandTilde2(path3) {
33236
33181
  if (!path3 || !path3.startsWith("~"))
@@ -33238,13 +33183,13 @@ function expandTilde2(path3) {
33238
33183
  if (path3 === "~")
33239
33184
  return homedir11();
33240
33185
  if (path3.startsWith(`~${sep}`) || path3.startsWith("~/")) {
33241
- return resolve4(homedir11(), path3.slice(2));
33186
+ return resolve8(homedir11(), path3.slice(2));
33242
33187
  }
33243
33188
  return path3;
33244
33189
  }
33245
33190
  function absoluteSearchPath(cwd, target) {
33246
33191
  const expanded = expandTilde2(target);
33247
- return isAbsolute4(expanded) ? expanded : resolve4(cwd, expanded);
33192
+ return isAbsolute5(expanded) ? expanded : resolve8(cwd, expanded);
33248
33193
  }
33249
33194
  async function searchPathExists(cwd, target) {
33250
33195
  try {
@@ -33296,46 +33241,16 @@ function appendSkippedSearchPaths(text, missing) {
33296
33241
 
33297
33242
  ${note}` : note;
33298
33243
  }
33299
- function externalDirectoryPromptTimeoutMs() {
33300
- const raw = process.env.AFT_PI_EXTERNAL_PROMPT_TIMEOUT_MS;
33301
- if (raw === undefined)
33302
- return 30000;
33303
- const parsed = Number.parseInt(raw, 10);
33304
- return Number.isFinite(parsed) && parsed > 0 ? parsed : 30000;
33305
- }
33306
- async function assertExternalDirectoryPermission(extCtx, target, action = "modify", options = {}) {
33244
+ async function assertExternalDirectoryPermission(extCtx, target, options = {}) {
33307
33245
  if (!target)
33308
33246
  return;
33309
33247
  const expanded = expandTilde2(target);
33310
- const absoluteTarget = isAbsolute4(expanded) ? expanded : resolve4(extCtx.cwd, expanded);
33248
+ const absoluteTarget = isAbsolute5(expanded) ? expanded : resolve8(extCtx.cwd, expanded);
33311
33249
  if (containsPath(extCtx.cwd, absoluteTarget))
33312
33250
  return;
33313
33251
  if (options.restrictToProjectRoot === false)
33314
33252
  return;
33315
- const confirmFn = extCtx.ui?.confirm;
33316
- if (extCtx.hasUI === false || !confirmFn) {
33317
- throw new Error(`Permission denied: cannot prompt for ${action} outside the project (${absoluteTarget}).`);
33318
- }
33319
- const timeoutMs = externalDirectoryPromptTimeoutMs();
33320
- let timer;
33321
- const timeoutPromise = new Promise((resolve5) => {
33322
- timer = setTimeout(() => resolve5("timeout"), timeoutMs);
33323
- });
33324
- try {
33325
- const result = await Promise.race([
33326
- confirmFn("Allow external directory access?", `AFT wants to ${action} outside the project: ${absoluteTarget}`),
33327
- timeoutPromise
33328
- ]);
33329
- if (result === true)
33330
- return;
33331
- if (result === "timeout") {
33332
- throw new Error(`Permission denied: external directory prompt timed out after ${timeoutMs}ms.`);
33333
- }
33334
- throw new Error("Permission denied: external directory access was cancelled.");
33335
- } finally {
33336
- if (timer !== undefined)
33337
- clearTimeout(timer);
33338
- }
33253
+ throw new Error(`Blocked: '${absoluteTarget}' is outside the project root and restrict_to_project_root is ` + "enabled (AFT full isolation). Not overridable per-call; set restrict_to_project_root: false " + "in aft.jsonc to allow external paths.");
33339
33254
  }
33340
33255
  var ReadParams = Type2.Object({
33341
33256
  path: Type2.String({
@@ -33384,7 +33299,7 @@ function registerHoistedTools(pi, ctx, surface) {
33384
33299
  const offset = coerceOptionalInt(params.offset, "offset", 1, Number.MAX_SAFE_INTEGER);
33385
33300
  const limit = coerceOptionalInt(params.limit, "limit", 1, Number.MAX_SAFE_INTEGER);
33386
33301
  const filePath = await resolvePathArg(extCtx.cwd, params.path);
33387
- await assertExternalDirectoryPermission(extCtx, filePath, "read", {
33302
+ await assertExternalDirectoryPermission(extCtx, filePath, {
33388
33303
  restrictToProjectRoot: surface.restrictToProjectRoot
33389
33304
  });
33390
33305
  const req = {
@@ -33422,7 +33337,7 @@ function registerHoistedTools(pi, ctx, surface) {
33422
33337
  parameters: WriteParams,
33423
33338
  async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
33424
33339
  const filePath = await resolvePathArg(extCtx.cwd, params.filePath);
33425
- await assertExternalDirectoryPermission(extCtx, filePath, "modify", {
33340
+ await assertExternalDirectoryPermission(extCtx, filePath, {
33426
33341
  restrictToProjectRoot: surface.restrictToProjectRoot
33427
33342
  });
33428
33343
  const bridge = bridgeFor(ctx, extCtx.cwd);
@@ -33456,7 +33371,7 @@ function registerHoistedTools(pi, ctx, surface) {
33456
33371
  parameters: EditParams,
33457
33372
  async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
33458
33373
  const filePath = await resolvePathArg(extCtx.cwd, params.filePath);
33459
- await assertExternalDirectoryPermission(extCtx, filePath, "modify", {
33374
+ await assertExternalDirectoryPermission(extCtx, filePath, {
33460
33375
  restrictToProjectRoot: surface.restrictToProjectRoot
33461
33376
  });
33462
33377
  const bridge = bridgeFor(ctx, extCtx.cwd);
@@ -33478,7 +33393,7 @@ function registerHoistedTools(pi, ctx, surface) {
33478
33393
  diagnostics: diagnosticsOnEditDefault(ctx),
33479
33394
  include_diff_content: true
33480
33395
  };
33481
- if (params.replaceAll === true)
33396
+ if (coerceBoolean(params.replaceAll))
33482
33397
  req.replace_all = true;
33483
33398
  const occurrence = coerceOptionalInt(params.occurrence, "occurrence", 0, Number.MAX_SAFE_INTEGER);
33484
33399
  if (occurrence !== undefined)
@@ -33509,7 +33424,7 @@ function registerHoistedTools(pi, ctx, surface) {
33509
33424
  if (params.path) {
33510
33425
  pathSplit = await splitSearchPathArg(extCtx.cwd, params.path);
33511
33426
  for (const target of pathSplit.paths) {
33512
- await assertExternalDirectoryPermission(extCtx, absoluteSearchPath(extCtx.cwd, target), "search", {
33427
+ await assertExternalDirectoryPermission(extCtx, absoluteSearchPath(extCtx.cwd, target), {
33513
33428
  restrictToProjectRoot: surface.restrictToProjectRoot
33514
33429
  });
33515
33430
  }
@@ -33930,7 +33845,7 @@ async function resolveAstPaths(extCtx, paths) {
33930
33845
  return;
33931
33846
  return Promise.all(paths.filter((path3) => typeof path3 === "string").map((path3) => resolvePathArg(extCtx.cwd, path3)));
33932
33847
  }
33933
- async function assertAstPathsPermission(extCtx, paths, action, restrictToProjectRoot) {
33848
+ async function assertAstPathsPermission(extCtx, paths, restrictToProjectRoot) {
33934
33849
  if (paths === undefined || paths.length === 0)
33935
33850
  return;
33936
33851
  const checked = new Set;
@@ -33938,7 +33853,7 @@ async function assertAstPathsPermission(extCtx, paths, action, restrictToProject
33938
33853
  if (checked.has(path3))
33939
33854
  continue;
33940
33855
  checked.add(path3);
33941
- await assertExternalDirectoryPermission(extCtx, path3, action, { restrictToProjectRoot });
33856
+ await assertExternalDirectoryPermission(extCtx, path3, { restrictToProjectRoot });
33942
33857
  }
33943
33858
  }
33944
33859
  function buildAstSearchSections(payload, theme) {
@@ -34057,7 +33972,7 @@ function registerAstTools(pi, ctx, surface) {
34057
33972
  parameters: SearchParams,
34058
33973
  async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
34059
33974
  const paths = await resolveAstPaths(extCtx, params.paths);
34060
- await assertAstPathsPermission(extCtx, paths, "search", ctx.config.restrict_to_project_root ?? false);
33975
+ await assertAstPathsPermission(extCtx, paths, ctx.config.restrict_to_project_root ?? false);
34061
33976
  const bridge = bridgeFor(ctx, extCtx.cwd);
34062
33977
  const req = {
34063
33978
  pattern: params.pattern,
@@ -34088,7 +34003,7 @@ function registerAstTools(pi, ctx, surface) {
34088
34003
  parameters: ReplaceParams,
34089
34004
  async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
34090
34005
  const paths = await resolveAstPaths(extCtx, params.paths);
34091
- await assertAstPathsPermission(extCtx, paths, params.dryRun === true ? "search" : "modify", ctx.config.restrict_to_project_root ?? false);
34006
+ await assertAstPathsPermission(extCtx, paths, ctx.config.restrict_to_project_root ?? false);
34092
34007
  const bridge = bridgeFor(ctx, extCtx.cwd);
34093
34008
  const req = {
34094
34009
  pattern: params.pattern,
@@ -34099,7 +34014,7 @@ function registerAstTools(pi, ctx, surface) {
34099
34014
  req.paths = paths;
34100
34015
  if (!isEmptyParam(params.globs))
34101
34016
  req.globs = params.globs;
34102
- req.dry_run = params.dryRun === true;
34017
+ req.dry_run = coerceBoolean(params.dryRun);
34103
34018
  const response = await callBridge(bridge, "ast_replace", req, extCtx);
34104
34019
  return textResult(response.text ?? JSON.stringify(response));
34105
34020
  },
@@ -34215,6 +34130,9 @@ var BashWriteParams = Type4.Object({
34215
34130
  description: "Either a string of verbatim bytes (e.g. 'print(1)\\n') OR an array mixing strings " + "and { key: '<name>' } objects for atomic text+key sequences. " + "Example: [ 'iHello', { key: 'esc' }, ':wq', { key: 'enter' } ]. " + "Allowed key names: enter, return, tab, space, backspace, esc, escape, up, down, " + "left, right, home, end, page-up, page-down, delete, insert, f1..f12, ctrl-a..ctrl-z."
34216
34131
  })
34217
34132
  });
34133
+ function unavailableSnapshot() {
34134
+ return { status: "unknown" };
34135
+ }
34218
34136
  async function callBashBridge(bridge, command, params = {}, extCtx, options) {
34219
34137
  return await callBridge(bridge, command, params, extCtx, {
34220
34138
  transportTimeoutMs: BASH_TRANSPORT_TIMEOUT_MS,
@@ -34247,8 +34165,8 @@ function registerBashTool(pi, ctx, aftSearchRegistered = false) {
34247
34165
  const spawnHook = getBashSpawnHook(pi);
34248
34166
  const searchSteer = aftSearchRegistered ? "use `aft_search` (concepts, identifiers, regex, literals), `read`, `aft_outline`, or `aft_zoom` instead" : "use the `grep` tool, `read`, `aft_outline`, or `aft_zoom` instead";
34249
34167
  const bashCfg = resolveBashConfig(ctx.config);
34250
- const compressionSentence = bashCfg.compress ? " Output is compressed by default; pass `compressed: false` for raw output." : "";
34251
- const tasksSentence = bashCfg.background ? ' Pass `background: true` to run in the background and get a task_id for `bash_status`/`bash_watch`/`bash_kill`. Pass `pty: true` for interactive programs (REPLs, TUIs) and drive them with `bash_status({ output_mode: "screen" })` plus `bash_write`. Use `bash_watch` to wait for exit or output patterns (sync blocks, async notifies). Do not loop `bash_status` to wait.' : " Commands run in the foreground to completion; `timeout` is the hard kill cap (default 30 minutes).";
34168
+ const compressionSentence = bashCfg.compress ? " Output is compressed by default; pass `compressed: false` for raw output. Piped commands run verbatim and show the pipeline's output; for AFT's test/build summary, run the runner without `| head`, `| tail`, or `| grep`." : "";
34169
+ const tasksSentence = bashCfg.background ? ' Commands run in the foreground and return inline; a long-running one auto-promotes to background and delivers a completion reminder when it finishes — so for the common "I am waiting on this result" case, just run it and wait, no flags needed. Use `background: true` yourself ONLY when you have other useful work to do while it runs; then `bash_watch` waits on the task (sync blocks until exit/pattern, async notifies) and `bash_status` peeks at it — never background a command and immediately `bash_watch` it (that wastes a turn for what foreground returns in one), and never loop `bash_status` to wait. `pty: true` runs interactive programs (REPLs, TUIs), implies background, and is driven with `bash_status({ output_mode: "screen" })` plus `bash_write`.' : " Commands run in the foreground to completion; `timeout` is the hard kill cap (default 30 minutes).";
34252
34170
  pi.registerTool({
34253
34171
  name: "bash",
34254
34172
  label: "bash",
@@ -34258,7 +34176,8 @@ DO NOT use bash for code search or code exploration. If you are about to run gre
34258
34176
  promptSnippet: bashCfg.background ? "Run shell commands (timeout in milliseconds; supports workdir, background tasks, compressed output, PTY mode)" : "Run shell commands (timeout in milliseconds; supports workdir and compressed output)",
34259
34177
  promptGuidelines: [
34260
34178
  `DO NOT use bash for code search or exploration — ${searchSteer}.`,
34261
- "Set compressed: false when you need ANSI color codes in the output."
34179
+ "Set compressed: false when you need ANSI color codes in the output.",
34180
+ "Piped commands run verbatim and show the pipeline's output; run test/build tools without pipes when you need AFT's summary."
34262
34181
  ],
34263
34182
  parameters: bashParamsForConfig(bashCfg.background),
34264
34183
  async execute(_toolCallId, params, _signal, onUpdate, extCtx) {
@@ -34269,8 +34188,9 @@ DO NOT use bash for code search or code exploration. If you are about to run gre
34269
34188
  const timeout = coerceOptionalInt(params.timeout, "timeout", 1, Number.MAX_SAFE_INTEGER);
34270
34189
  const ptyRows = backgroundDisabled ? undefined : coerceOptionalInt(params.ptyRows, "ptyRows", 1, 60);
34271
34190
  const ptyCols = backgroundDisabled ? undefined : coerceOptionalInt(params.ptyCols, "ptyCols", 1, 140);
34272
- const requestedPty = !backgroundDisabled && params.pty === true;
34273
- const effectiveBackground = !backgroundDisabled && (params.background === true || requestedPty);
34191
+ const compressed = coerceBoolean(params.compressed, true);
34192
+ const requestedPty = !backgroundDisabled && coerceBoolean(params.pty);
34193
+ const effectiveBackground = !backgroundDisabled && (coerceBoolean(params.background) || requestedPty);
34274
34194
  const effectiveTimeout = effectiveBackground || backgroundDisabled ? timeout : resolveBashKillTimeout(timeout, foregroundWaitMs);
34275
34195
  let spawnContext = {
34276
34196
  command: params.command,
@@ -34283,9 +34203,7 @@ DO NOT use bash for code search or code exploration. If you are about to run gre
34283
34203
  throw new Error(`BashSpawnHook failed: ${hookErr instanceof Error ? hookErr.message : String(hookErr)}`);
34284
34204
  }
34285
34205
  }
34286
- const compressionEnabled = bashCfg2.compress && params.compressed !== false;
34287
- const pipeStrip = maybeStripCompressorPipe(spawnContext.command, compressionEnabled);
34288
- const bridgeCommand = pipeStrip.command;
34206
+ const bridgeCommand = spawnContext.command;
34289
34207
  let streamed = "";
34290
34208
  const response = await callBashBridge(bridge, "bash", {
34291
34209
  command: bridgeCommand,
@@ -34295,7 +34213,7 @@ DO NOT use bash for code search or code exploration. If you are about to run gre
34295
34213
  description: params.description,
34296
34214
  background: effectiveBackground,
34297
34215
  notify_on_completion: effectiveBackground,
34298
- compressed: params.compressed,
34216
+ compressed,
34299
34217
  pty: requestedPty,
34300
34218
  pty_rows: ptyRows,
34301
34219
  pty_cols: ptyCols
@@ -34318,7 +34236,7 @@ DO NOT use bash for code search or code exploration. If you are about to run gre
34318
34236
  if (response.status === "running" && taskId) {
34319
34237
  if (effectiveBackground) {
34320
34238
  trackBgTask(resolveSessionId(extCtx), taskId);
34321
- return bashResult(appendPipeStripNote(formatBackgroundLaunch(taskId, requestedPty), pipeStrip.note), { task_id: taskId });
34239
+ return bashResult(formatBackgroundLaunch(taskId, requestedPty), { task_id: taskId });
34322
34240
  }
34323
34241
  const waitTimeoutMs = backgroundDisabled ? effectiveTimeout ?? DEFAULT_HARD_TIMEOUT_MS : effectiveTimeout !== undefined ? Math.min(effectiveTimeout, foregroundWaitMs) : foregroundWaitMs;
34324
34242
  const startedAt = Date.now();
@@ -34328,7 +34246,7 @@ DO NOT use bash for code search or code exploration. If you are about to run gre
34328
34246
  throw new Error(status.message ?? "bash_status failed");
34329
34247
  }
34330
34248
  if (isTerminalStatus(status.status)) {
34331
- return bashResult(appendPipeStripNote(withBashHints(formatForegroundResult(status), bridgeCommand, aftSearchRegistered, extCtx.cwd), pipeStrip.note), {
34249
+ return bashResult(withBashHints(formatForegroundResult(status), bridgeCommand, aftSearchRegistered, extCtx.cwd), {
34332
34250
  exit_code: status.exit_code,
34333
34251
  duration_ms: status.duration_ms,
34334
34252
  truncated: status.output_truncated,
@@ -34346,7 +34264,9 @@ DO NOT use bash for code search or code exploration. If you are about to run gre
34346
34264
  throw new Error(promoted.message ?? "bash_promote failed");
34347
34265
  }
34348
34266
  trackBgTask(resolveSessionId(extCtx), taskId);
34349
- return bashResult(appendPipeStripNote(formatPromotionMessage(taskId, effectiveTimeout, foregroundWaitMs), pipeStrip.note), { task_id: taskId });
34267
+ return bashResult(formatPromotionMessage(taskId, effectiveTimeout, foregroundWaitMs), {
34268
+ task_id: taskId
34269
+ });
34350
34270
  }
34351
34271
  await sleep(FOREGROUND_POLL_INTERVAL_MS);
34352
34272
  }
@@ -34359,7 +34279,7 @@ DO NOT use bash for code search or code exploration. If you are about to run gre
34359
34279
  task_id: taskId
34360
34280
  };
34361
34281
  const output = response.output ?? "";
34362
- return bashResult(appendPipeStripNote(withBashHints(output, bridgeCommand, aftSearchRegistered, extCtx.cwd), pipeStrip.note), details);
34282
+ return bashResult(withBashHints(output, bridgeCommand, aftSearchRegistered, extCtx.cwd), details);
34363
34283
  },
34364
34284
  renderCall(args, theme, context) {
34365
34285
  return renderBashCall(args?.command, args?.description, theme, context);
@@ -34413,13 +34333,13 @@ function createBashWatchTool(ctx) {
34413
34333
  async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
34414
34334
  const bridge = bridgeFor(ctx, extCtx.cwd);
34415
34335
  const waitFor = parseWaitPattern(params.pattern);
34416
- if (params.background === true) {
34336
+ if (coerceBoolean(params.background)) {
34417
34337
  if (!waitFor) {
34418
34338
  throw new Error("invalid_request: Use auto-reminder; bash_watch without pattern in async mode is redundant");
34419
34339
  }
34420
34340
  const notifyParams = {
34421
34341
  task_id: params.task_id,
34422
- once: params.once !== false
34342
+ once: coerceBoolean(params.once, true)
34423
34343
  };
34424
34344
  if (waitFor.kind === "regex")
34425
34345
  notifyParams.regex = waitFor.source;
@@ -34445,7 +34365,7 @@ A notification will fire when the pattern matches or the task exits.`, watchDeta
34445
34365
  }
34446
34366
  const data = await waitForBashStatus(ctx, bridge, extCtx, params.task_id, undefined, waitFor, true, Math.min(coerceOptionalInt(params.timeout_ms, "timeout_ms", 1, MAX_BASH_STATUS_WAIT_TIMEOUT_MS) ?? DEFAULT_BASH_STATUS_WAIT_TIMEOUT_MS, MAX_BASH_STATUS_WAIT_TIMEOUT_MS));
34447
34367
  if (data.waited?.reason === "user_message") {
34448
- const convertedText = await convertToAsyncWatchOnAbort(bridge, extCtx, params.task_id, waitFor, params.once !== false);
34368
+ const convertedText = await convertToAsyncWatchOnAbort(bridge, extCtx, params.task_id, waitFor, coerceBoolean(params.once, true));
34449
34369
  return textResult(convertedText, { waited: data.waited });
34450
34370
  }
34451
34371
  const text = await formatBashStatus(extCtx, params.task_id, data, undefined);
@@ -34561,9 +34481,31 @@ async function waitForBashStatus(ctx, bridge, extCtx, taskId, outputMode, waitFo
34561
34481
  if (waitForExit)
34562
34482
  markTaskWaiting(sessionId, taskId);
34563
34483
  let sawTerminal = false;
34484
+ let lastData;
34564
34485
  try {
34565
34486
  while (true) {
34566
- const data = await bashStatusSnapshot(bridge, extCtx, taskId, outputMode, bridgeOptions);
34487
+ let data;
34488
+ try {
34489
+ data = await bashStatusSnapshot(bridge, extCtx, taskId, outputMode, bridgeOptions);
34490
+ } catch (err) {
34491
+ if (!isBridgeTransportTimeout(err))
34492
+ throw err;
34493
+ if (isSyncWatchAborted(sessionId)) {
34494
+ return withWaited(lastData ?? unavailableSnapshot(), {
34495
+ reason: "user_message",
34496
+ elapsed_ms: Date.now() - startedAt
34497
+ });
34498
+ }
34499
+ if (Date.now() >= deadline) {
34500
+ return withWaited(lastData ?? unavailableSnapshot(), {
34501
+ reason: "unavailable",
34502
+ elapsed_ms: Date.now() - startedAt
34503
+ });
34504
+ }
34505
+ await sleep(Math.min(BASH_WAIT_POLL_INTERVAL_MS, Math.max(0, deadline - Date.now())));
34506
+ continue;
34507
+ }
34508
+ lastData = data;
34567
34509
  const terminal = isTerminalStatus(data.status);
34568
34510
  if (waitFor) {
34569
34511
  const scan = await readNewTaskOutput(extCtx, taskId, data, spillCursor);
@@ -34755,6 +34697,9 @@ function formatWaitSummary(waited, details) {
34755
34697
  if (waited.reason === "timeout") {
34756
34698
  return `Waited ${waited.elapsed_ms}ms; timeout reached without match.`;
34757
34699
  }
34700
+ if (waited.reason === "unavailable") {
34701
+ return `Waited ${waited.elapsed_ms}ms; the bridge stayed busy and status couldn't be read. The task may still be running — check with bash_status({ taskId }).`;
34702
+ }
34758
34703
  const exit = typeof details.exit_code === "number" ? `, exit ${details.exit_code}` : "";
34759
34704
  return `Waited ${waited.elapsed_ms}ms; task exited (${details.status}${exit}).`;
34760
34705
  }
@@ -34924,7 +34869,7 @@ function registerConflictsTool(pi, ctx) {
34924
34869
  const reqParams = {};
34925
34870
  const path3 = params?.path;
34926
34871
  if (typeof path3 === "string" && path3.trim() !== "") {
34927
- await assertExternalDirectoryPermission(extCtx, path3, "inspect", {
34872
+ await assertExternalDirectoryPermission(extCtx, path3, {
34928
34873
  restrictToProjectRoot: ctx.config.restrict_to_project_root ?? false
34929
34874
  });
34930
34875
  reqParams.path = await resolvePathArg(extCtx.cwd, path3);
@@ -35015,14 +34960,14 @@ function registerFsTools(pi, ctx, surface) {
35015
34960
  if (checked.has(file2))
35016
34961
  continue;
35017
34962
  checked.add(file2);
35018
- await assertExternalDirectoryPermission(extCtx, file2, "modify", {
34963
+ await assertExternalDirectoryPermission(extCtx, file2, {
35019
34964
  restrictToProjectRoot: ctx.config.restrict_to_project_root ?? false
35020
34965
  });
35021
34966
  }
35022
34967
  const bridge = bridgeFor(ctx, extCtx.cwd);
35023
34968
  const response = await callBridge(bridge, "delete_file", {
35024
34969
  files,
35025
- recursive: params.recursive === true
34970
+ recursive: coerceBoolean(params.recursive)
35026
34971
  }, extCtx);
35027
34972
  const deletedEntries = response.deleted ?? [];
35028
34973
  const skipped = response.skipped_files ?? [];
@@ -35059,7 +35004,7 @@ function registerFsTools(pi, ctx, surface) {
35059
35004
  const destination = await resolvePathArg(extCtx.cwd, params.destination);
35060
35005
  const checked = new Set([filePath, destination]);
35061
35006
  for (const file2 of checked) {
35062
- await assertExternalDirectoryPermission(extCtx, file2, "modify", {
35007
+ await assertExternalDirectoryPermission(extCtx, file2, {
35063
35008
  restrictToProjectRoot: ctx.config.restrict_to_project_root ?? false
35064
35009
  });
35065
35010
  }
@@ -35163,7 +35108,7 @@ function registerImportTools(pi, ctx) {
35163
35108
  throw new Error(`op='${params.op}' requires 'module'`);
35164
35109
  }
35165
35110
  const filePath = await resolvePathArg(extCtx.cwd, params.filePath);
35166
- await assertExternalDirectoryPermission(extCtx, filePath, "modify", {
35111
+ await assertExternalDirectoryPermission(extCtx, filePath, {
35167
35112
  restrictToProjectRoot: ctx.config.restrict_to_project_root ?? false
35168
35113
  });
35169
35114
  const bridge = bridgeFor(ctx, extCtx.cwd);
@@ -35239,7 +35184,7 @@ async function resolveAndGateScope(extCtx, ctx, scope) {
35239
35184
  if (checked.has(target))
35240
35185
  continue;
35241
35186
  checked.add(target);
35242
- await assertExternalDirectoryPermission(extCtx, target, "read", {
35187
+ await assertExternalDirectoryPermission(extCtx, target, {
35243
35188
  restrictToProjectRoot: ctx.config.restrict_to_project_root ?? false
35244
35189
  });
35245
35190
  }
@@ -35463,7 +35408,7 @@ function registerInspectTool(pi, ctx) {
35463
35408
  pi.registerTool({
35464
35409
  name: "aft_inspect",
35465
35410
  label: "inspect",
35466
- description: "Codebase health snapshot. One call returns summary stats for: TODOs, diagnostics, file/symbol metrics, dead code, unused exports, code duplicates. Pass `sections` for per-category drill-down details.\n\n" + "Categories run in tiers — Tier 1 (todos, metrics) return synchronously from cache. Tier 2 (dead_code, unused_exports, duplicates) run asynchronously on demand: when a call sees cold `pending_categories: [...]` or stale `stale_categories: [...]`, Pi quietly starts a background Tier 2 warmup (deduped while in-flight and rate-limited per category to at most once every 4 minutes, matching OpenCode's default idle window). The current call may still return pending results while the cache warms; a later call can use cached data.\n\n" + `Use when: starting work on unfamiliar code, after multi-edit batches to check diagnostics, before a refactor, before review, or to verify cleanup completeness.
35411
+ description: "Codebase health snapshot. One call returns summary stats for: TODOs, diagnostics, file/symbol metrics, dead code, unused exports, code duplicates. Pass `sections` for per-category drill-down details.\n\n" + "Categories run in tiers — Tier 1 (todos, metrics) return synchronously from cache. Tier 2 (dead_code, unused_exports, duplicates) waits for a fresh reuse scan up to a short deadline; if a category is still scanning the response reports `complete: false` with `pending_categories: [...]` rather than a fabricated clean count. Pi may still trigger a deduped background warmup for categories that remain pending.\n\n" + `Use when: starting work on unfamiliar code, after multi-edit batches to check diagnostics, before a refactor, before review, or to verify cleanup completeness.
35467
35412
 
35468
35413
  ` + "Treat `dead_code` as a hint, not proof: reachability is call-based, so symbols reached only via method dispatch or referenced only in type position may be false positives — verify before deleting.",
35469
35414
  parameters: InspectParams,
@@ -35472,11 +35417,16 @@ function registerInspectTool(pi, ctx) {
35472
35417
  const sections = normalizeStringOrArray(params.sections);
35473
35418
  const scope = await resolveAndGateScope(extCtx, ctx, normalizeStringOrArray(params.scope));
35474
35419
  const topK = validateOptionalTopK(params.topK);
35475
- const response = await callBridge(bridge, "inspect", { sections, scope, topK }, extCtx);
35420
+ const response = await callBridge(bridge, "inspect", { sections, scope, topK }, extCtx, {
35421
+ keepBridgeOnTimeout: true
35422
+ });
35476
35423
  runPendingTier2Categories(bridge, tier2RefreshCategories(response), extCtx);
35477
35424
  const body = response.text;
35478
35425
  if (typeof body === "string") {
35479
- const diagnostics = diagnosticsSummaryPart(asRecord3(response.summary));
35426
+ const diagnosticsSummary = diagnosticsSummaryPart(asRecord3(response.summary));
35427
+ const diagnosticsDetail = diagnosticsDetailSection(asRecord3(response.details));
35428
+ const diagnostics = [diagnosticsSummary, diagnosticsDetail].filter(Boolean).join(`
35429
+ `);
35480
35430
  const text = diagnostics ? body ? `${body}
35481
35431
 
35482
35432
  ${diagnostics}` : diagnostics : body;
@@ -35540,7 +35490,7 @@ function registerNavigateTool(pi, ctx) {
35540
35490
  pi.registerTool({
35541
35491
  name: "aft_callgraph",
35542
35492
  label: "callgraph",
35543
- description: "Answer code-relationship questions from a real call graph — instead of grep + read chains. Reach for this whenever the question is about how symbols connect. All ops require both `filePath` and `symbol`. Use `callers` for call sites (before renaming/signature changes), `impact` for blast radius (what breaks if a symbol changes), `call_tree` for what a function calls, `trace_to` for how execution reaches a symbol from entry points, `trace_to_symbol` for the shortest path from one symbol to another (requires `toSymbol`; if ambiguous, the error returns candidate files — retry with `toFile`), `trace_data` to follow a value across assignments/params.",
35493
+ description: "Answer code-relationship questions from a real call graph — instead of grep + read chains. Reach for this whenever the question is about how symbols connect. All ops require both `filePath` and `symbol`. Use `callers` for call sites (before renaming/signature changes), `impact` for blast radius (what breaks if a symbol changes), `call_tree` for what a function calls, `trace_to` for how execution reaches a symbol from entry points, `trace_to_symbol` for the shortest path from one symbol to another (requires `toSymbol`; if ambiguous, the error returns candidate files — retry with `toFile`), `trace_data` to follow a value across assignments/params. Markers: ~ = edge resolved by name only (may point at the wrong same-named symbol); [unresolved] = callee not resolved to a definition, so the location shown is the call site. Unmarked edges are resolved exactly.",
35544
35494
  parameters: navigateParamsSchema(),
35545
35495
  async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
35546
35496
  if (isEmptyParam(params.filePath)) {
@@ -35562,7 +35512,7 @@ function registerNavigateTool(pi, ctx) {
35562
35512
  if (checked.has(target))
35563
35513
  continue;
35564
35514
  checked.add(target);
35565
- await assertExternalDirectoryPermission(extCtx, target, "read", {
35515
+ await assertExternalDirectoryPermission(extCtx, target, {
35566
35516
  restrictToProjectRoot: ctx.config.restrict_to_project_root ?? false
35567
35517
  });
35568
35518
  }
@@ -35642,7 +35592,7 @@ async function assertReadPathPermissions(extCtx, ctx, paths) {
35642
35592
  if (!target || checked.has(target))
35643
35593
  continue;
35644
35594
  checked.add(target);
35645
- await assertExternalDirectoryPermission(extCtx, target, "read", {
35595
+ await assertExternalDirectoryPermission(extCtx, target, {
35646
35596
  restrictToProjectRoot: ctx.config.restrict_to_project_root ?? false
35647
35597
  });
35648
35598
  }
@@ -35769,8 +35719,8 @@ Pass a single \`target\`:
35769
35719
  parameters: OutlineParams,
35770
35720
  async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
35771
35721
  const bridge = bridgeFor(ctx, extCtx.cwd);
35772
- const target = params.target;
35773
- const filesMode = params.files === true;
35722
+ const target = coerceTargetParam(params.target);
35723
+ const filesMode = coerceBoolean(params.files);
35774
35724
  const isArray = Array.isArray(target) && target.length > 0;
35775
35725
  if (filesMode) {
35776
35726
  if (Array.isArray(target)) {
@@ -35868,7 +35818,7 @@ Pass a single \`target\`:
35868
35818
  const hasUrl = !isEmptyParam(params.url);
35869
35819
  const hasTargets = hasTargetsProvided(params.targets);
35870
35820
  const hasSymbols = !isEmptyParam(params.symbols);
35871
- const wantCallgraph = params.callgraph === true;
35821
+ const wantCallgraph = coerceBoolean(params.callgraph);
35872
35822
  if (hasTargets) {
35873
35823
  if (hasFilePath || hasUrl || hasSymbols) {
35874
35824
  throw new Error("'targets' is mutually exclusive with 'filePath', 'url', and 'symbols'");
@@ -36137,7 +36087,7 @@ function registerRefactorTool(pi, ctx) {
36137
36087
  if (checked.has(target))
36138
36088
  continue;
36139
36089
  checked.add(target);
36140
- await assertExternalDirectoryPermission(extCtx, target, "modify", {
36090
+ await assertExternalDirectoryPermission(extCtx, target, {
36141
36091
  restrictToProjectRoot: ctx.config.restrict_to_project_root ?? false
36142
36092
  });
36143
36093
  }
@@ -36290,7 +36240,7 @@ function registerSafetyTool(pi, ctx) {
36290
36240
  previewReq.file = filePath;
36291
36241
  const preview = await callBridge(bridge, "undo_preview", previewReq, extCtx);
36292
36242
  for (const file2 of new Set(responsePaths(preview))) {
36293
- await assertExternalDirectoryPermission(extCtx, file2, "modify", {
36243
+ await assertExternalDirectoryPermission(extCtx, file2, {
36294
36244
  restrictToProjectRoot
36295
36245
  });
36296
36246
  }
@@ -36303,7 +36253,7 @@ function registerSafetyTool(pi, ctx) {
36303
36253
  if (checked.has(file2))
36304
36254
  continue;
36305
36255
  checked.add(file2);
36306
- await assertExternalDirectoryPermission(extCtx, file2, "modify", {
36256
+ await assertExternalDirectoryPermission(extCtx, file2, {
36307
36257
  restrictToProjectRoot
36308
36258
  });
36309
36259
  }
@@ -36312,7 +36262,7 @@ function registerSafetyTool(pi, ctx) {
36312
36262
  if (params.op === "restore" && params.name) {
36313
36263
  const preview = await callBridge(bridge, "checkpoint_paths", { name: params.name }, extCtx);
36314
36264
  for (const file2 of new Set(responsePaths(preview))) {
36315
- await assertExternalDirectoryPermission(extCtx, file2, "modify", {
36265
+ await assertExternalDirectoryPermission(extCtx, file2, {
36316
36266
  restrictToProjectRoot
36317
36267
  });
36318
36268
  }
@@ -36390,6 +36340,10 @@ var SearchParams2 = Type13.Object({
36390
36340
  Type13.Literal("auto")
36391
36341
  ], {
36392
36342
  description: "Optional routing hint. Defaults to 'auto'."
36343
+ })),
36344
+ includeTests: Type13.Optional(Type13.Boolean({
36345
+ default: false,
36346
+ description: "Include test files (*.test.*, *_test.rs, __tests__/, …) plus test-support, fixture, mock, snapshot, and corpus files. Defaults to false."
36393
36347
  }))
36394
36348
  });
36395
36349
  function buildSemanticSections(args, payload, theme) {
@@ -36495,6 +36449,8 @@ function registerSemanticTool(pi, ctx) {
36495
36449
  req.top_k = params.topK;
36496
36450
  if (params.hint !== undefined)
36497
36451
  req.hint = params.hint;
36452
+ if (params.includeTests !== undefined)
36453
+ req.include_tests = params.includeTests;
36498
36454
  const response = await callBridge(bridge, "semantic_search", req, extCtx);
36499
36455
  let agentText = response.text ?? "No results.";
36500
36456
  const extra = extraAgentHonestyNote(response);
@@ -36528,7 +36484,7 @@ function buildWorkflowHints(opts) {
36528
36484
  const hasBgBash = opts.bashBackgroundEnabled && hasBash && !opts.absentTools.has("bash_status");
36529
36485
  if (hasBash && opts.bashCompressionEnabled) {
36530
36486
  sections.push([
36531
- "**Test/build output**: bash output is auto-compressed failures and the summary are always kept. DO NOT pipe test/build commands through filters to summarize; that hides failures:",
36487
+ "**Test/build output**: bash output is auto-compressed for non-piped commands. Piped commands run verbatim and show the pipeline's output. For AFT's test/build summary, run the runner without filters:",
36532
36488
  "- `bun test | grep fail` → run `bun test`",
36533
36489
  "- `cargo test 2>&1 | tail -20` → run `cargo test`",
36534
36490
  "- `npm run build | head -50` → run `npm run build`"
@@ -36567,11 +36523,9 @@ function buildWorkflowHints(opts) {
36567
36523
  }
36568
36524
  if (hasBgBash) {
36569
36525
  sections.push([
36570
- `**Long-running commands** (builds, installs, full test suites): \`${bashName}({ background: true })\` returns a \`task_id\` immediately.`,
36571
- "1. Nothing else useful to do (a build/test/validation whose result is the next thing you need) sync `bash_watch` to block until it exits (pass a longer timeout_ms for long commands; the user can interrupt).",
36572
- "2. Useful parallel work available end your turn; the completion reminder delivers the result. (Or spawn a subagent for the side work.)",
36573
- "3. Want to react to a specific early output line → async `bash_watch` (background:true + pattern).",
36574
- "Never loop `bash_status` to wait — it's a one-shot inspector, not a wait primitive."
36526
+ `**Long-running commands** (builds, installs, full test suites): run them in the FOREGROUND — \`${bashName}({ command })\` waits and returns the result in ONE step, and if it outlives the short wait window it auto-promotes to background and delivers a completion reminder when it finishes. Don't reach for \`background: true\` for the common "I'm waiting on this result" case — that costs an extra turn.`,
36527
+ "- `background: true` is ONLY for when you have OTHER useful work to do while it runs: start it, do the other work, and the completion reminder delivers the result (or spawn a subagent for the side work). Do NOT background a command and then immediately `bash_watch` it that spends a whole extra turn waiting for something foreground returns in one.",
36528
+ "- `bash_watch` is for blocking on an ALREADY-backgrounded task once you've run out of parallel work (sync the user can interrupt), or reacting to a specific early output line (async: background:true + pattern). Never loop `bash_status` to wait — it's a one-shot inspector."
36575
36529
  ].join(`
36576
36530
  `));
36577
36531
  sections.push(`**PTY / interactive commands**: PTY mode is for interactive REPLs and terminal apps (python, node, bash itself, vim). Start with \`${bashName}({ command: "python", pty: true, background: true })\`, read the screen with \`bash_status({ task_id, output_mode: "screen" })\`, and send input with \`bash_write({ task_id, input: "..." })\`.`);
@@ -36673,11 +36627,12 @@ var PLUGIN_VERSION = (() => {
36673
36627
  return "0.0.0";
36674
36628
  }
36675
36629
  })();
36676
- var ANNOUNCEMENT_VERSION = "0.39.0";
36630
+ var ANNOUNCEMENT_VERSION = "0.40.0";
36677
36631
  var ANNOUNCEMENT_FEATURES = [
36678
- "Accurate Rust dead code: constructors and associated functions reached through receiver-type inference are no longer mis-reported as dead.",
36679
- "Code Health scans reparse only the file you changed instead of the whole project on every edit a single-file edit dropped from ~3s of scan work to ~130ms on this repo, byte-identical results.",
36680
- "R language support in outline, zoom, and the AST tools."
36632
+ "Groundwork for Subconscious, the CortexKit daemon: configuration now lives in one place under ~/.config/cortexkit/ and <project>/.cortexkit/ (auto-migrated). The daemon integration is dormant nothing changes in how you run AFT today.",
36633
+ "Bash output never hides a failure: piped commands run exactly as written with their real exit status, cancelled tasks report as failed, and a failing run is never compressed to a clean-looking summary.",
36634
+ "The top aft_search result is now the complete, ready-to-edit symbol (no re-read needed); test files are hidden from results by default.",
36635
+ "format_on_edit is now off by default, and bash commands default to the foreground."
36681
36636
  ];
36682
36637
  var ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/DSa65w8wuf";
36683
36638
  var ALL_ONLY_TOOLS = new Set(["aft_callgraph", "aft_delete", "aft_move", "aft_refactor"]);
@@ -36686,11 +36641,25 @@ function isConfigureWarning(value) {
36686
36641
  if (!value || typeof value !== "object" || Array.isArray(value))
36687
36642
  return false;
36688
36643
  const warning = value;
36689
- return (warning.kind === "formatter_not_installed" || warning.kind === "checker_not_installed" || warning.kind === "lsp_binary_missing" || warning.kind === "config_parse_failed") && typeof warning.hint === "string";
36644
+ return (warning.kind === "formatter_not_installed" || warning.kind === "checker_not_installed" || warning.kind === "lsp_binary_missing" || warning.kind === "config_parse_failed" || warning.kind === "config_key_dropped") && typeof warning.hint === "string";
36690
36645
  }
36691
36646
  function coerceConfigureWarnings(warnings) {
36692
36647
  return warnings.filter(isConfigureWarning);
36693
36648
  }
36649
+ function isDroppedConfigKey(value) {
36650
+ if (!value || typeof value !== "object" || Array.isArray(value))
36651
+ return false;
36652
+ const dropped = value;
36653
+ return typeof dropped.key === "string" && typeof dropped.tier === "string" && typeof dropped.reason === "string";
36654
+ }
36655
+ function coerceDroppedKeyWarnings(droppedKeys) {
36656
+ if (!Array.isArray(droppedKeys))
36657
+ return [];
36658
+ return formatDroppedKeyWarnings(droppedKeys.filter(isDroppedConfigKey)).map((hint) => ({
36659
+ kind: "config_key_dropped",
36660
+ hint
36661
+ }));
36662
+ }
36694
36663
  function drainPendingEagerWarnings(projectRoot) {
36695
36664
  const pending = pendingEagerWarnings.get(projectRoot) ?? [];
36696
36665
  pendingEagerWarnings.delete(projectRoot);
@@ -36717,7 +36686,10 @@ function bridgeDirectoryFromCallback(bridge, fallback) {
36717
36686
  return typeof cwd === "string" && cwd.length > 0 ? cwd : fallback;
36718
36687
  }
36719
36688
  async function handleConfigureWarningsForSession(context) {
36720
- const validWarnings = coerceConfigureWarnings(context.warnings);
36689
+ const validWarnings = [
36690
+ ...coerceConfigureWarnings(context.warnings),
36691
+ ...coerceDroppedKeyWarnings(context.configDroppedKeys)
36692
+ ];
36721
36693
  if (!context.sessionId) {
36722
36694
  if (validWarnings.length === 0)
36723
36695
  return;
@@ -36815,6 +36787,25 @@ async function src_default(pi) {
36815
36787
  return;
36816
36788
  }
36817
36789
  await ensureStorageMigrated({ harness: "pi", binaryPath, logger: bridgeLogger });
36790
+ const deliverConfigMigrationWarnings = (messages) => {
36791
+ for (const message of messages) {
36792
+ const notify = pi.ui?.notify;
36793
+ if (typeof notify === "function") {
36794
+ try {
36795
+ notify(message, "warning");
36796
+ continue;
36797
+ } catch (err) {
36798
+ warn2(`[config] failed to deliver migration notification: ${err}`);
36799
+ }
36800
+ }
36801
+ try {
36802
+ process.stderr.write(`
36803
+ [AFT] ${message}
36804
+ `);
36805
+ } catch {}
36806
+ }
36807
+ };
36808
+ deliverConfigMigrationWarnings(migrateAftConfigLocations(process.cwd(), bridgeLogger).flatMap((result) => result.warnings));
36818
36809
  const config2 = loadAftConfig(process.cwd());
36819
36810
  enqueueConfigParseWarnings(process.cwd(), getConfigLoadErrors());
36820
36811
  const storageDir = resolveCortexKitStorageRoot();
@@ -36825,10 +36816,9 @@ async function src_default(pi) {
36825
36816
  return null;
36826
36817
  });
36827
36818
  }
36828
- const configOverrides = resolveProjectOverridesForConfigure(config2);
36829
- if (config2.url_fetch_allow_private !== undefined)
36830
- configOverrides.url_fetch_allow_private = config2.url_fetch_allow_private;
36831
- configOverrides.storage_dir = storageDir;
36819
+ const configOverrides = buildConfigTierConfigureParams(process.cwd(), {
36820
+ storage_dir: storageDir
36821
+ });
36832
36822
  try {
36833
36823
  const lspAutoInstall = config2.lsp?.auto_install ?? true;
36834
36824
  const lspGraceDays = config2.lsp?.grace_days ?? 7;
@@ -36896,7 +36886,7 @@ ${lines}
36896
36886
  errorPrefix: "[aft-pi]",
36897
36887
  minVersion: PLUGIN_VERSION,
36898
36888
  onVersionMismatch: createVersionMismatchHandler(() => pool),
36899
- onConfigureWarnings: ({ projectRoot, sessionId, client, warnings }) => {
36889
+ onConfigureWarnings: ({ projectRoot, sessionId, client, warnings, configDroppedKeys }) => {
36900
36890
  const bridge = pool.getActiveBridgeForRoot(projectRoot);
36901
36891
  if (!bridge)
36902
36892
  return;
@@ -36908,6 +36898,7 @@ ${lines}
36908
36898
  client,
36909
36899
  bridge,
36910
36900
  warnings: [...pendingWarnings, ...warnings],
36901
+ configDroppedKeys,
36911
36902
  storageDir,
36912
36903
  pluginVersion: PLUGIN_VERSION
36913
36904
  });
@@ -36967,7 +36958,7 @@ ${lines}
36967
36958
  if (onnxRuntimePromise) {
36968
36959
  await Promise.race([
36969
36960
  onnxRuntimePromise,
36970
- new Promise((resolve5) => setTimeout(() => resolve5(null), 60000))
36961
+ new Promise((resolve9) => setTimeout(() => resolve9(null), 60000))
36971
36962
  ]);
36972
36963
  }
36973
36964
  const bridge = pool.getBridge(cwd);