@cortexkit/aft-pi 0.48.1 → 0.49.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -7952,6 +7952,7 @@ function sleep(ms) {
7952
7952
  return new Promise((resolve) => setTimeout(resolve, ms));
7953
7953
  }
7954
7954
  // ../aft-bridge/dist/bash-hints.js
7955
+ import * as fs from "node:fs";
7955
7956
  import * as os from "node:os";
7956
7957
  import * as path from "node:path";
7957
7958
  var CONFLICT_HINT = `
@@ -7960,6 +7961,7 @@ var CONFLICT_HINT = `
7960
7961
  var GREP_SEARCH_AFT_SEARCH_HINT = "DO NOT search code by running grep/rg in bash — it is unindexed, unranked, and serial. Use the `aft_search` tool instead (it auto-routes concepts, identifiers, regex, and literals).";
7961
7962
  var GREP_SEARCH_GREP_HINT = "DO NOT search code by running grep/rg in bash — it is unindexed, unranked, and serial. Use the `grep` tool instead (indexed and ranked).";
7962
7963
  var GREP_SEARCH_HINT_PREFIX = "DO NOT search code by running grep/rg in bash —";
7964
+ var GREP_SEARCH_FRESHNESS_WINDOW_MS = 60000;
7963
7965
  function maybeAppendConflictsHint(output) {
7964
7966
  if (!output.includes("Automatic merge failed; fix conflicts"))
7965
7967
  return output;
@@ -8031,12 +8033,19 @@ function shouldSuppressGrepSearchHint(command, projectRoot) {
8031
8033
  const operands = collectPathOperands(firstStage, firstToken.end);
8032
8034
  if (operands.length === 0)
8033
8035
  return false;
8036
+ let sawInProjectOperand = false;
8034
8037
  for (const operand of operands) {
8035
8038
  if (isDynamicPathOperand(operand))
8036
8039
  continue;
8037
- if (isPathInsideProject(resolvedRoot, effectiveCwd, operand))
8038
- return false;
8040
+ const resolvedOperand = resolvePathOperand(effectiveCwd, operand);
8041
+ if (!isPathInsideProject(resolvedRoot, effectiveCwd, operand))
8042
+ continue;
8043
+ sawInProjectOperand = true;
8044
+ if (shouldSuppressResolvedPath(resolvedOperand))
8045
+ return true;
8039
8046
  }
8047
+ if (sawInProjectOperand)
8048
+ return false;
8040
8049
  }
8041
8050
  return sawCodeSearchStatement;
8042
8051
  }
@@ -8135,6 +8144,17 @@ function isPathInsideProject(resolvedRoot, baseCwd, operand) {
8135
8144
  const rel = path.relative(resolvedRoot, resolved);
8136
8145
  return rel === "" || !rel.startsWith("..") && !path.isAbsolute(rel);
8137
8146
  }
8147
+ function shouldSuppressResolvedPath(resolved) {
8148
+ try {
8149
+ const stats = fs.statSync(resolved);
8150
+ if (stats.isFile())
8151
+ return true;
8152
+ const ageMs = Date.now() - stats.mtimeMs;
8153
+ return ageMs >= 0 && ageMs < GREP_SEARCH_FRESHNESS_WINDOW_MS;
8154
+ } catch {
8155
+ return false;
8156
+ }
8157
+ }
8138
8158
  function splitTopLevelStatements(command) {
8139
8159
  const statements = [];
8140
8160
  let start = 0;
@@ -9623,14 +9643,6 @@ function coerceStringArray(value) {
9623
9643
  }
9624
9644
  return [];
9625
9645
  }
9626
- function coerceAliasedStringParam(value, aliasValue) {
9627
- if (value !== undefined) {
9628
- return typeof value === "string" ? value : undefined;
9629
- }
9630
- if (typeof aliasValue !== "string")
9631
- return;
9632
- return aliasValue.trim().length > 0 ? aliasValue : undefined;
9633
- }
9634
9646
  function coerceTargetParam(value) {
9635
9647
  if (Array.isArray(value)) {
9636
9648
  return value.filter((entry) => typeof entry === "string" && entry.length > 0);
@@ -9744,7 +9756,7 @@ function formatDroppedKeyWarnings(dropped) {
9744
9756
  // ../aft-bridge/dist/downloader.js
9745
9757
  import { spawnSync } from "node:child_process";
9746
9758
  import { createHash as createHash2, randomUUID } from "node:crypto";
9747
- import { chmodSync, closeSync, copyFileSync, createWriteStream, existsSync as existsSync2, mkdirSync, openSync, readFileSync as readFileSync3, renameSync, rmSync, statSync, unlinkSync, writeSync } from "node:fs";
9759
+ import { chmodSync, closeSync, copyFileSync, createWriteStream, existsSync as existsSync2, mkdirSync, openSync, readFileSync as readFileSync3, renameSync, rmSync, statSync as statSync2, unlinkSync, writeSync } from "node:fs";
9748
9760
  import { homedir as homedir4 } from "node:os";
9749
9761
  import { join as join3 } from "node:path";
9750
9762
  import { Readable } from "node:stream";
@@ -9777,7 +9789,7 @@ function readBinaryVersion(binaryPath) {
9777
9789
  const result = spawnSync(binaryPath, ["--version"], {
9778
9790
  encoding: "utf-8",
9779
9791
  stdio: ["pipe", "pipe", "pipe"],
9780
- timeout: 5000
9792
+ timeout: 60000
9781
9793
  });
9782
9794
  const stdoutVersion = result.stdout?.trim();
9783
9795
  const stderrVersion = result.stderr?.trim();
@@ -9982,7 +9994,7 @@ async function acquireDownloadLock(lockPath) {
9982
9994
  if (code !== "EEXIST")
9983
9995
  throw err;
9984
9996
  try {
9985
- const ageMs = Date.now() - statSync(lockPath).mtimeMs;
9997
+ const ageMs = Date.now() - statSync2(lockPath).mtimeMs;
9986
9998
  if (ageMs > DOWNLOAD_LOCK_STALE_MS) {
9987
9999
  rmSync(lockPath, { force: true });
9988
10000
  continue;
@@ -10185,6 +10197,23 @@ ${reflowText}`;
10185
10197
  }
10186
10198
  return " Auto-formatted.";
10187
10199
  }
10200
+ // ../aft-bridge/dist/error-contract.js
10201
+ class AftToolError extends Error {
10202
+ code;
10203
+ response;
10204
+ constructor(message, code, response) {
10205
+ const cause = { code, message, response };
10206
+ super(message, { cause });
10207
+ this.name = "AftToolError";
10208
+ this.code = code;
10209
+ this.response = response;
10210
+ }
10211
+ }
10212
+ function toolErrorFromResponse(command, response) {
10213
+ const code = typeof response.code === "string" && response.code.length > 0 ? response.code : "unknown_error";
10214
+ const message = typeof response.message === "string" && response.message.length > 0 ? response.message : `${command} failed`;
10215
+ return new AftToolError(message, code, response);
10216
+ }
10188
10217
  // ../aft-bridge/dist/format.js
10189
10218
  function compressionSavingsPercent(original, compressed) {
10190
10219
  if (original <= 0)
@@ -10208,7 +10237,7 @@ function stripJsoncSymbols(value) {
10208
10237
  }
10209
10238
  // ../aft-bridge/dist/migration.js
10210
10239
  import { spawnSync as spawnSync2 } from "node:child_process";
10211
- import { closeSync as closeSync3, existsSync as existsSync5, mkdirSync as mkdirSync4, openSync as openSync3, readFileSync as readFileSync5, renameSync as renameSync4, rmSync as rmSync2, statSync as statSync2, unlinkSync as unlinkSync3, writeFileSync as writeFileSync2 } from "node:fs";
10240
+ import { closeSync as closeSync3, existsSync as existsSync5, mkdirSync as mkdirSync4, openSync as openSync3, readFileSync as readFileSync5, renameSync as renameSync4, rmSync as rmSync2, statSync as statSync3, unlinkSync as unlinkSync3, writeFileSync as writeFileSync2 } from "node:fs";
10212
10241
  import { homedir as homedir8, tmpdir } from "node:os";
10213
10242
  import { basename, dirname as dirname3, join as join7, resolve as resolve4 } from "node:path";
10214
10243
 
@@ -10708,7 +10737,7 @@ function acquireConfigMigrationLock(lockDir) {
10708
10737
  if (code !== "EEXIST")
10709
10738
  throw err;
10710
10739
  try {
10711
- const ageMs = Date.now() - statSync2(lockDir).mtimeMs;
10740
+ const ageMs = Date.now() - statSync3(lockDir).mtimeMs;
10712
10741
  if (ageMs > 60000) {
10713
10742
  rmSync2(lockDir, { recursive: true, force: true });
10714
10743
  continue;
@@ -10994,7 +11023,7 @@ async function ensureStorageMigrated(opts) {
10994
11023
  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}` : ""));
10995
11024
  }
10996
11025
  // ../aft-bridge/dist/npm-resolver.js
10997
- import { readdirSync, statSync as statSync3 } from "node:fs";
11026
+ import { readdirSync, statSync as statSync4 } from "node:fs";
10998
11027
  import { homedir as homedir9 } from "node:os";
10999
11028
  import { delimiter, dirname as dirname4, isAbsolute as isAbsolute3, join as join8 } from "node:path";
11000
11029
  function defaultDeps() {
@@ -11010,7 +11039,7 @@ function npmBinaryName(platform) {
11010
11039
  }
11011
11040
  function isFile(p) {
11012
11041
  try {
11013
- return statSync3(p).isFile();
11042
+ return statSync4(p).isFile();
11014
11043
  } catch {
11015
11044
  return false;
11016
11045
  }
@@ -11110,7 +11139,7 @@ function npmSpawnEnv(resolved, baseEnv = process.env) {
11110
11139
  // ../aft-bridge/dist/onnx-runtime.js
11111
11140
  import { execFileSync } from "node:child_process";
11112
11141
  import { createHash as createHash3 } from "node:crypto";
11113
- 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 readFileSync6, readlinkSync, realpathSync, rmSync as rmSync3, statSync as statSync4, symlinkSync, unlinkSync as unlinkSync4, writeFileSync as writeFileSync3 } from "node:fs";
11142
+ 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 readFileSync6, readlinkSync, realpathSync, rmSync as rmSync3, statSync as statSync5, symlinkSync, unlinkSync as unlinkSync4, writeFileSync as writeFileSync3 } from "node:fs";
11114
11143
  import { basename as basename2, dirname as dirname5, isAbsolute as isAbsolute4, join as join9, relative as relative2, resolve as resolve5, win32 } from "node:path";
11115
11144
  import { Readable as Readable2 } from "node:stream";
11116
11145
  import { pipeline as pipeline2 } from "node:stream/promises";
@@ -11240,7 +11269,7 @@ function cleanupAbandonedStagingDirs(onnxBaseDir) {
11240
11269
  abandoned = true;
11241
11270
  } else {
11242
11271
  try {
11243
- const ageMs = Date.now() - statSync4(stagingDir).mtimeMs;
11272
+ const ageMs = Date.now() - statSync5(stagingDir).mtimeMs;
11244
11273
  abandoned = ageMs > STALE_LOCK_MS;
11245
11274
  } catch {
11246
11275
  abandoned = true;
@@ -11673,7 +11702,7 @@ function writeOnnxInstalledMeta(installDir, version, sha256, archiveSha256) {
11673
11702
  function readOnnxInstalledMeta(installDir) {
11674
11703
  const path2 = join9(installDir, ONNX_INSTALLED_META_FILE);
11675
11704
  try {
11676
- if (!statSync4(path2).isFile())
11705
+ if (!statSync5(path2).isFile())
11677
11706
  return null;
11678
11707
  const raw = readFileSync6(path2, "utf8");
11679
11708
  const parsed = JSON.parse(raw);
@@ -11724,7 +11753,7 @@ ${new Date().toISOString()}
11724
11753
  const parsed = Number.parseInt(firstLine, 10);
11725
11754
  if (Number.isFinite(parsed) && parsed > 0)
11726
11755
  owningPid = parsed;
11727
- lockMtimeMs = statSync4(lockPath).mtimeMs;
11756
+ lockMtimeMs = statSync5(lockPath).mtimeMs;
11728
11757
  } catch {
11729
11758
  return tryClaim();
11730
11759
  }
@@ -11807,6 +11836,488 @@ function isProcessAlive(pid) {
11807
11836
  return true;
11808
11837
  }
11809
11838
  }
11839
+ // ../aft-bridge/dist/path-aliases.js
11840
+ class InvalidRequestError extends AftToolError {
11841
+ constructor(message) {
11842
+ super(message, "invalid_request", {
11843
+ success: false,
11844
+ code: "invalid_request",
11845
+ message
11846
+ });
11847
+ this.name = "InvalidRequestError";
11848
+ }
11849
+ }
11850
+ function isWellFormedUnicodeString(value) {
11851
+ for (let index = 0;index < value.length; index++) {
11852
+ const codeUnit = value.charCodeAt(index);
11853
+ if (codeUnit >= 55296 && codeUnit <= 56319) {
11854
+ const next = value.charCodeAt(index + 1);
11855
+ if (next < 56320 || next > 57343 || Number.isNaN(next))
11856
+ return false;
11857
+ index++;
11858
+ } else if (codeUnit >= 56320 && codeUnit <= 57343) {
11859
+ return false;
11860
+ }
11861
+ }
11862
+ return true;
11863
+ }
11864
+ function hasOwn(record, key) {
11865
+ return Object.hasOwn(record, key);
11866
+ }
11867
+ function invalidPathValue(property) {
11868
+ throw new InvalidRequestError(`'${property}' must be a non-empty well-formed Unicode string`);
11869
+ }
11870
+ function pathValue(record, property) {
11871
+ const value = record[property];
11872
+ if (typeof value !== "string" || value.length === 0 || !isWellFormedUnicodeString(value)) {
11873
+ invalidPathValue(property);
11874
+ }
11875
+ return value;
11876
+ }
11877
+ function normalizeAliasPair(record, canonical, legacy, required) {
11878
+ const hasCanonical = hasOwn(record, canonical);
11879
+ const hasLegacy = hasOwn(record, legacy);
11880
+ if (!hasCanonical && !hasLegacy) {
11881
+ if (required) {
11882
+ throw new InvalidRequestError(`'${canonical}' is required`);
11883
+ }
11884
+ return;
11885
+ }
11886
+ if (hasCanonical && hasLegacy) {
11887
+ let canonicalValue;
11888
+ let legacyValue;
11889
+ try {
11890
+ canonicalValue = pathValue(record, canonical);
11891
+ legacyValue = pathValue(record, legacy);
11892
+ } catch {
11893
+ throw new InvalidRequestError(`Invalid request: '${canonical}' and '${legacy}' must both be non-empty well-formed Unicode strings`);
11894
+ }
11895
+ if (canonicalValue !== legacyValue) {
11896
+ throw new InvalidRequestError(`Invalid request: '${canonical}' and '${legacy}' must contain equal decoded strings`);
11897
+ }
11898
+ delete record[legacy];
11899
+ return;
11900
+ }
11901
+ if (hasCanonical) {
11902
+ pathValue(record, canonical);
11903
+ return;
11904
+ }
11905
+ record[canonical] = pathValue(record, legacy);
11906
+ delete record[legacy];
11907
+ }
11908
+ function validateOptionalCanonicalPath(record, property) {
11909
+ if (hasOwn(record, property))
11910
+ pathValue(record, property);
11911
+ }
11912
+ function normalizeZoomTargets(record) {
11913
+ if (!hasOwn(record, "targets"))
11914
+ return;
11915
+ const targets = record.targets;
11916
+ const normalizeTarget = (target, index) => {
11917
+ if (!target || typeof target !== "object" || Array.isArray(target)) {
11918
+ throw new InvalidRequestError(`'targets[${index}].path' must be a non-empty string`);
11919
+ }
11920
+ const source = target;
11921
+ const emptyTarget = source.symbol === "" && (hasOwn(source, "path") && source.path === "" || hasOwn(source, "filePath") && source.filePath === "");
11922
+ if (emptyTarget)
11923
+ return { ...source };
11924
+ const normalized = { ...source };
11925
+ try {
11926
+ normalizeAliasPair(normalized, "path", "filePath", true);
11927
+ } catch (error2) {
11928
+ if (error2 instanceof InvalidRequestError) {
11929
+ throw new InvalidRequestError(error2.message.replace("'filePath'", `'targets[${index}].filePath'`).replace("'path'", `'targets[${index}].path'`));
11930
+ }
11931
+ throw error2;
11932
+ }
11933
+ return normalized;
11934
+ };
11935
+ if (Array.isArray(targets)) {
11936
+ if (targets.length === 0)
11937
+ return;
11938
+ record.targets = targets.map(normalizeTarget);
11939
+ return;
11940
+ }
11941
+ if (targets && typeof targets === "object") {
11942
+ record.targets = normalizeTarget(targets, 0);
11943
+ }
11944
+ }
11945
+ function bareToolName(toolName) {
11946
+ const bare = toolName.startsWith("aft_") ? toolName.slice(4) : toolName;
11947
+ if (bare === "read" || bare === "write" || bare === "edit" || bare === "zoom" || bare === "callgraph" || bare === "safety" || bare === "move" || bare === "import" || bare === "refactor" || bare === "grep" || bare === "search" || bare === "conflicts") {
11948
+ return bare;
11949
+ }
11950
+ return;
11951
+ }
11952
+ function prepareCanonicalPathArguments(toolName, rawArguments) {
11953
+ if (!rawArguments || typeof rawArguments !== "object" || Array.isArray(rawArguments)) {
11954
+ throw new InvalidRequestError("tool arguments must be an object");
11955
+ }
11956
+ const tool = bareToolName(toolName);
11957
+ const record = { ...rawArguments };
11958
+ if (!tool)
11959
+ return record;
11960
+ switch (tool) {
11961
+ case "read":
11962
+ case "write":
11963
+ case "edit":
11964
+ case "move":
11965
+ case "import":
11966
+ case "refactor":
11967
+ normalizeAliasPair(record, "path", "filePath", true);
11968
+ break;
11969
+ case "zoom":
11970
+ normalizeAliasPair(record, "path", "filePath", false);
11971
+ normalizeZoomTargets(record);
11972
+ break;
11973
+ case "callgraph":
11974
+ normalizeAliasPair(record, "path", "filePath", true);
11975
+ normalizeAliasPair(record, "toPath", "toFile", false);
11976
+ break;
11977
+ case "safety":
11978
+ normalizeAliasPair(record, "path", "filePath", false);
11979
+ break;
11980
+ case "grep":
11981
+ case "search":
11982
+ case "conflicts":
11983
+ validateOptionalCanonicalPath(record, "path");
11984
+ break;
11985
+ }
11986
+ return record;
11987
+ }
11988
+ var EDIT_ROOT_COMPATIBILITY_KEYS = new Set([
11989
+ "oldString",
11990
+ "newString",
11991
+ "replaceAll",
11992
+ "occurrence"
11993
+ ]);
11994
+ var EDIT_ROOT_CANONICAL_KEYS = new Set(["path", "appendContent", "edits", "symbol", "content"]);
11995
+ var EDIT_ITEM_KEYS = new Set([
11996
+ "oldString",
11997
+ "newString",
11998
+ "replaceAll",
11999
+ "occurrence",
12000
+ "startLine",
12001
+ "endLine",
12002
+ "content"
12003
+ ]);
12004
+ var ASCII_WHITESPACE = /^[\t\n\v\f\r ]+$/;
12005
+ var ASCII_TRIM = /^[\t\n\v\f\r ]+|[\t\n\v\f\r ]+$/g;
12006
+ var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER;
12007
+ function prepareCanonicalEditArguments(toolName, rawArguments) {
12008
+ if (!rawArguments || typeof rawArguments !== "object" || Array.isArray(rawArguments)) {
12009
+ throw new InvalidRequestError("tool arguments must be an object");
12010
+ }
12011
+ const raw = rawArguments;
12012
+ const record = copyOwnProperties(raw);
12013
+ normalizeEditPathAlias(record);
12014
+ const suppliedLineFields = ["startLine", "endLine"].filter((key) => hasOwn(record, key));
12015
+ if (suppliedLineFields.length > 0) {
12016
+ throw new InvalidRequestError(`edit: top-level ${suppliedLineFields.map((key) => `'${key}'`).join(" and ")} are invalid; ` + "line-range fields are valid only inside 'edits[]'. " + "Use edits: [{ startLine, endLine, content }].");
12017
+ }
12018
+ const isOpenCodeRetiredBoundary = toolName === "aft_edit";
12019
+ const retiredFields = ["file", "mode"].filter((key) => hasOwn(record, key));
12020
+ if (retiredFields.length > 0 && isOpenCodeRetiredBoundary) {
12021
+ throw new InvalidRequestError("aft_edit: the retired `mode`/`file` edit form is no longer supported; use `path` with " + "exactly one of `appendContent`, `edits`, or `symbol` plus `content`.");
12022
+ }
12023
+ const unknownRootKeys = Object.getOwnPropertyNames(record).filter((key) => !EDIT_ROOT_CANONICAL_KEYS.has(key) && !EDIT_ROOT_COMPATIBILITY_KEYS.has(key) && key !== "filePath").sort();
12024
+ if (unknownRootKeys.length > 0) {
12025
+ throw new InvalidRequestError(formatUnknownKeys(unknownRootKeys));
12026
+ }
12027
+ const modes = editModesPresent(record);
12028
+ if (hasOrphanedSymbolContent(record)) {
12029
+ throw new InvalidRequestError("edit: 'content' requires a non-empty string 'symbol' when symbol mode is selected");
12030
+ }
12031
+ if (modes.length > 1) {
12032
+ throw new InvalidRequestError(`edit: conflicting modes: ${modes.join(", ")}. ` + OMIT_OPTIONAL_FIELDS_STEERING);
12033
+ }
12034
+ if (modes.length === 0) {
12035
+ throw new InvalidRequestError("edit: exactly one of `appendContent`, `edits`, or `symbol` plus `content` is required. " + OMIT_OPTIONAL_FIELDS_STEERING);
12036
+ }
12037
+ const mode = modes[0];
12038
+ if (mode === "appendContent") {
12039
+ if (typeof record.appendContent !== "string") {
12040
+ throw new InvalidRequestError("edit: 'appendContent' must be a string");
12041
+ }
12042
+ } else if (mode === "edits") {
12043
+ const parsedEdits = parseEditArray(record.edits);
12044
+ record.edits = parsedEdits.map((item, index) => normalizeEditItem(item, index));
12045
+ } else if (mode === "symbol/content") {
12046
+ if (!hasOwn(record, "symbol") || typeof record.symbol !== "string") {
12047
+ throw new InvalidRequestError("edit: 'symbol' must be a string when symbol mode is selected");
12048
+ }
12049
+ if (!hasOwn(record, "content") || typeof record.content !== "string") {
12050
+ throw new InvalidRequestError("edit: symbol mode requires both 'symbol' and 'content' string properties");
12051
+ }
12052
+ } else {
12053
+ const item = {};
12054
+ for (const key of EDIT_ROOT_COMPATIBILITY_KEYS) {
12055
+ if (hasOwn(record, key))
12056
+ item[key] = record[key];
12057
+ }
12058
+ record.edits = [normalizeEditItem(item, 0)];
12059
+ for (const key of EDIT_ROOT_COMPATIBILITY_KEYS)
12060
+ delete record[key];
12061
+ }
12062
+ validateEditPath(record);
12063
+ return record;
12064
+ }
12065
+ function normalizeEditPathAlias(record) {
12066
+ const hasCanonical = hasOwn(record, "path");
12067
+ const hasLegacy = hasOwn(record, "filePath");
12068
+ if (!hasCanonical && !hasLegacy)
12069
+ return;
12070
+ if (hasCanonical && hasLegacy) {
12071
+ let canonical;
12072
+ let legacy;
12073
+ try {
12074
+ canonical = pathValue(record, "path");
12075
+ legacy = pathValue(record, "filePath");
12076
+ } catch {
12077
+ throw new InvalidRequestError("Invalid request: 'path' and 'filePath' must both be non-empty well-formed Unicode strings");
12078
+ }
12079
+ if (canonical !== legacy) {
12080
+ throw new InvalidRequestError("Invalid request: 'path' and 'filePath' must contain equal decoded strings");
12081
+ }
12082
+ delete record.filePath;
12083
+ return;
12084
+ }
12085
+ if (!hasCanonical) {
12086
+ record.path = pathValue(record, "filePath");
12087
+ delete record.filePath;
12088
+ }
12089
+ }
12090
+ function validateEditPath(record) {
12091
+ if (!hasOwn(record, "path")) {
12092
+ throw new InvalidRequestError("'path' is required");
12093
+ }
12094
+ pathValue(record, "path");
12095
+ }
12096
+ function formatUnknownKeys(keys) {
12097
+ return `Unrecognized keys: ${keys.map((key) => `"${key}"`).join(", ")}`;
12098
+ }
12099
+ function editModesPresent(record) {
12100
+ const hasAppendContent = isNonEmptyString(record.appendContent);
12101
+ if (!hasAppendContent)
12102
+ delete record.appendContent;
12103
+ const hasEdits = normalizeEditArraySentinels(record);
12104
+ if (!hasEdits)
12105
+ delete record.edits;
12106
+ const hasSymbol = isNonEmptyString(record.symbol);
12107
+ if (!hasSymbol) {
12108
+ delete record.symbol;
12109
+ if (record.content === null || record.content === "")
12110
+ delete record.content;
12111
+ } else if (record.content === null) {
12112
+ delete record.content;
12113
+ }
12114
+ const hasSingleEdit = isNonEmptyString(record.oldString);
12115
+ if (!hasSingleEdit) {
12116
+ for (const key of EDIT_ROOT_COMPATIBILITY_KEYS)
12117
+ delete record[key];
12118
+ } else {
12119
+ for (const key of ["newString", "replaceAll", "occurrence"]) {
12120
+ if (record[key] === null)
12121
+ delete record[key];
12122
+ }
12123
+ }
12124
+ const modes = [];
12125
+ if (hasAppendContent)
12126
+ modes.push("appendContent");
12127
+ if (hasEdits)
12128
+ modes.push("edits");
12129
+ if (hasSymbol)
12130
+ modes.push("symbol/content");
12131
+ if (hasSingleEdit)
12132
+ modes.push("oldString/newString");
12133
+ return modes;
12134
+ }
12135
+ function isNonEmptyString(value) {
12136
+ return typeof value === "string" && value.length > 0;
12137
+ }
12138
+ var OMIT_OPTIONAL_FIELDS_STEERING = "Omit unused optional fields entirely; do not send empty strings or empty arrays for them.";
12139
+ function isEditSentinelItem(item) {
12140
+ if (!item || typeof item !== "object" || Array.isArray(item))
12141
+ return false;
12142
+ const record = item;
12143
+ if (record.oldString !== "")
12144
+ return false;
12145
+ const newStringEmpty = !hasOwn(record, "newString") || record.newString === "" || record.newString === null;
12146
+ if (!newStringEmpty)
12147
+ return false;
12148
+ return !hasOwn(record, "content") || record.content === "" || record.content === null;
12149
+ }
12150
+ function normalizeEditArraySentinels(record) {
12151
+ const value = record.edits;
12152
+ if (Array.isArray(value)) {
12153
+ const survivors = value.filter((item) => !isEditSentinelItem(item));
12154
+ if (survivors.length === 0)
12155
+ return false;
12156
+ record.edits = survivors;
12157
+ return true;
12158
+ }
12159
+ if (typeof value !== "string" || value.length === 0)
12160
+ return false;
12161
+ try {
12162
+ const parsed = JSON.parse(value);
12163
+ if (!Array.isArray(parsed))
12164
+ return true;
12165
+ const survivors = parsed.filter((item) => !isEditSentinelItem(item));
12166
+ if (survivors.length === 0)
12167
+ return false;
12168
+ record.edits = survivors;
12169
+ return true;
12170
+ } catch {
12171
+ return true;
12172
+ }
12173
+ }
12174
+ function hasOrphanedSymbolContent(record) {
12175
+ return isNonEmptyString(record.content) && !isNonEmptyString(record.symbol);
12176
+ }
12177
+ function parseEditArray(value) {
12178
+ if (typeof value === "string") {
12179
+ let parsed;
12180
+ try {
12181
+ parsed = JSON.parse(value);
12182
+ } catch {
12183
+ throw new InvalidRequestError("edit: 'edits' must contain valid JSON representing an array");
12184
+ }
12185
+ if (!Array.isArray(parsed)) {
12186
+ throw new InvalidRequestError("edit: 'edits' JSON must have an array root");
12187
+ }
12188
+ if (parsed.length === 0) {
12189
+ throw new InvalidRequestError("edit: 'edits' array must not be empty");
12190
+ }
12191
+ return parsed;
12192
+ }
12193
+ if (!Array.isArray(value)) {
12194
+ throw new InvalidRequestError("edit: 'edits' must be a non-empty array");
12195
+ }
12196
+ if (value.length === 0) {
12197
+ throw new InvalidRequestError("edit: 'edits' array must not be empty");
12198
+ }
12199
+ return value;
12200
+ }
12201
+ function normalizeEditItem(value, index) {
12202
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
12203
+ throw new InvalidRequestError(`edit: edits[${index}] must be an object`);
12204
+ }
12205
+ const source = value;
12206
+ const item = copyOwnProperties(source);
12207
+ normalizeItemAlias(item, "oldString", "oldText");
12208
+ normalizeItemAlias(item, "newString", "newText");
12209
+ const hasFindField = ["oldString", "newString", "replaceAll", "occurrence"].some((key) => hasOwn(item, key));
12210
+ const hasRangeField = ["startLine", "endLine", "content"].some((key) => hasOwn(item, key));
12211
+ if (hasFindField && hasRangeField) {
12212
+ throw new InvalidRequestError(`edit: edits[${index}] mixes find/replace and line-range fields`);
12213
+ }
12214
+ if (hasFindField) {
12215
+ if (!hasOwn(item, "oldString") || typeof item.oldString !== "string") {
12216
+ throw new InvalidRequestError(`edit: edits[${index}] requires string 'oldString'`);
12217
+ }
12218
+ if (hasOwn(item, "newString") && typeof item.newString !== "string") {
12219
+ throw new InvalidRequestError(`edit: edits[${index}].newString must be a string`);
12220
+ }
12221
+ coerceEditScalars(item, index);
12222
+ validateEditItemKeys(item, index);
12223
+ return item;
12224
+ }
12225
+ if (hasRangeField) {
12226
+ for (const key of ["startLine", "endLine"]) {
12227
+ const value2 = item[key];
12228
+ if (typeof value2 === "string" && /^[0-9]+$/.test(value2.trim())) {
12229
+ item[key] = Number(value2.trim());
12230
+ }
12231
+ if (!hasOwn(item, key) || !isPositiveSafeInteger(item[key])) {
12232
+ throw new InvalidRequestError(`edit: edits[${index}].${key} must be a positive integer`);
12233
+ }
12234
+ }
12235
+ if (item.startLine > item.endLine) {
12236
+ throw new InvalidRequestError(`edit: edits[${index}] requires startLine <= endLine`);
12237
+ }
12238
+ if (!hasOwn(item, "content") || typeof item.content !== "string") {
12239
+ throw new InvalidRequestError(`edit: edits[${index}] requires string 'content'`);
12240
+ }
12241
+ validateEditItemKeys(item, index);
12242
+ return item;
12243
+ }
12244
+ throw new InvalidRequestError(`edit: edits[${index}] must be a find/replace or line-range item`);
12245
+ }
12246
+ function normalizeItemAlias(item, canonical, legacy) {
12247
+ if (hasOwn(item, legacy)) {
12248
+ if (!hasOwn(item, canonical))
12249
+ item[canonical] = item[legacy];
12250
+ delete item[legacy];
12251
+ }
12252
+ }
12253
+ function validateEditItemKeys(item, index) {
12254
+ const unknown = Object.getOwnPropertyNames(item).filter((key) => !EDIT_ITEM_KEYS.has(key)).sort();
12255
+ if (unknown.length > 0) {
12256
+ throw new InvalidRequestError(`edit: edits[${index}] contains ${formatUnknownKeys(unknown)}`);
12257
+ }
12258
+ }
12259
+ function coerceEditScalars(item, index) {
12260
+ if (hasOwn(item, "replaceAll") && hasOwn(item, "occurrence")) {
12261
+ throw new InvalidRequestError(`edit: edits[${index}] cannot contain both 'replaceAll' and 'occurrence'`);
12262
+ }
12263
+ if (hasOwn(item, "replaceAll"))
12264
+ item.replaceAll = coerceEditBoolean(item.replaceAll, index);
12265
+ if (hasOwn(item, "occurrence")) {
12266
+ const occurrence = coerceEditOccurrence(item.occurrence, index);
12267
+ if (occurrence === undefined)
12268
+ delete item.occurrence;
12269
+ else
12270
+ item.occurrence = occurrence;
12271
+ }
12272
+ }
12273
+ function coerceEditBoolean(value, index) {
12274
+ if (typeof value === "boolean")
12275
+ return value;
12276
+ if (typeof value === "number" && Number.isFinite(value) && (value === 0 || value === 1)) {
12277
+ return value === 1;
12278
+ }
12279
+ if (typeof value === "string") {
12280
+ if (value === "1")
12281
+ return true;
12282
+ if (value === "0")
12283
+ return false;
12284
+ if (/^(?:true|false)$/i.test(value))
12285
+ return value.toLowerCase() === "true";
12286
+ }
12287
+ throw new InvalidRequestError(`edit: edits[${index}].replaceAll must be a boolean, true/false string, or 0/1`);
12288
+ }
12289
+ function coerceEditOccurrence(value, index) {
12290
+ if (value === null)
12291
+ return;
12292
+ if (typeof value === "string") {
12293
+ const trimmed = value.replace(ASCII_TRIM, "");
12294
+ if (trimmed.length === 0 || ASCII_WHITESPACE.test(trimmed))
12295
+ return;
12296
+ if (!/^[+]?[0-9]+$/.test(trimmed)) {
12297
+ throw new InvalidRequestError(`edit: edits[${index}].occurrence must be a positive integer`);
12298
+ }
12299
+ try {
12300
+ const parsed = BigInt(trimmed);
12301
+ if (parsed < 1n || parsed > BigInt(MAX_SAFE_INTEGER))
12302
+ throw new Error("out of range");
12303
+ return Number(parsed);
12304
+ } catch {
12305
+ throw new InvalidRequestError(`edit: edits[${index}].occurrence must be a positive integer`);
12306
+ }
12307
+ }
12308
+ if (typeof value === "number" && isPositiveSafeInteger(value))
12309
+ return value;
12310
+ throw new InvalidRequestError(`edit: edits[${index}].occurrence must be a positive integer`);
12311
+ }
12312
+ function isPositiveSafeInteger(value) {
12313
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 1;
12314
+ }
12315
+ function copyOwnProperties(source) {
12316
+ const copy = Object.create(null);
12317
+ for (const key of Object.getOwnPropertyNames(source))
12318
+ copy[key] = source[key];
12319
+ return copy;
12320
+ }
11810
12321
  // ../aft-bridge/dist/pool.js
11811
12322
  import { homedir as homedir10 } from "node:os";
11812
12323
 
@@ -12261,7 +12772,7 @@ class RevivableProjectTransport {
12261
12772
  }
12262
12773
  }
12263
12774
  // ../../node_modules/.bun/@cortexkit+subc-client@0.5.0/node_modules/@cortexkit/subc-client/dist/client.js
12264
- import { promises as fs2 } from "node:fs";
12775
+ import { promises as fs3 } from "node:fs";
12265
12776
  import { debuglog } from "node:util";
12266
12777
 
12267
12778
  // ../../node_modules/.bun/@cortexkit+subc-client@0.5.0/node_modules/@cortexkit/subc-client/dist/auth.js
@@ -12329,7 +12840,7 @@ async function authenticateClient(sock, conn, deadlineMs) {
12329
12840
  }
12330
12841
 
12331
12842
  // ../../node_modules/.bun/@cortexkit+subc-client@0.5.0/node_modules/@cortexkit/subc-client/dist/connection-file.js
12332
- import { promises as fs } from "node:fs";
12843
+ import { promises as fs2 } from "node:fs";
12333
12844
 
12334
12845
  // ../../node_modules/.bun/@cortexkit+subc-client@0.5.0/node_modules/@cortexkit/subc-client/dist/envelope.js
12335
12846
  var PROTOCOL_VERSION = 2;
@@ -12497,7 +13008,7 @@ function validate(info) {
12497
13008
  async function verifyOwnerOnly(path2) {
12498
13009
  if (process.platform === "win32")
12499
13010
  return;
12500
- const stat2 = await fs.stat(path2);
13011
+ const stat2 = await fs2.stat(path2);
12501
13012
  const mode = stat2.mode & 511;
12502
13013
  if ((mode & 63) !== 0) {
12503
13014
  throw new ConnectionFileError(`connection file ${path2} has insecure permissions 0o${mode.toString(8)}; expected owner-only 0600`);
@@ -12505,7 +13016,7 @@ async function verifyOwnerOnly(path2) {
12505
13016
  }
12506
13017
  async function readConnectionFile(path2) {
12507
13018
  await verifyOwnerOnly(path2);
12508
- const raw = await fs.readFile(path2, "utf8");
13019
+ const raw = await fs2.readFile(path2, "utf8");
12509
13020
  let parsed;
12510
13021
  try {
12511
13022
  parsed = JSON.parse(raw);
@@ -13568,7 +14079,7 @@ function isRetryableRouteOpenCode(code) {
13568
14079
  }
13569
14080
  async function connectionFileExists(path2) {
13570
14081
  try {
13571
- await fs2.access(path2);
14082
+ await fs3.access(path2);
13572
14083
  return true;
13573
14084
  } catch {
13574
14085
  return false;
@@ -15795,7 +16306,7 @@ import {
15795
16306
  // package.json
15796
16307
  var package_default = {
15797
16308
  name: "@cortexkit/aft-pi",
15798
- version: "0.48.1",
16309
+ version: "0.49.1",
15799
16310
  type: "module",
15800
16311
  description: "Pi coding agent extension for Agent File Tools (AFT) — tree-sitter and LSP-powered code analysis",
15801
16312
  main: "dist/index.js",
@@ -15818,19 +16329,19 @@ var package_default = {
15818
16329
  "test:unit": "bun test src/__tests__ --path-ignore-patterns 'src/__tests__/e2e/**'"
15819
16330
  },
15820
16331
  dependencies: {
15821
- "@cortexkit/aft-bridge": "0.48.1",
16332
+ "@cortexkit/aft-bridge": "0.49.1",
15822
16333
  "comment-json": "^5.0.0",
15823
16334
  diff: "^8.0.4",
15824
16335
  typebox: "1.1.38",
15825
16336
  zod: "^4.4.3"
15826
16337
  },
15827
16338
  optionalDependencies: {
15828
- "@cortexkit/aft-darwin-arm64": "0.48.1",
15829
- "@cortexkit/aft-darwin-x64": "0.48.1",
15830
- "@cortexkit/aft-linux-arm64": "0.48.1",
15831
- "@cortexkit/aft-linux-x64": "0.48.1",
15832
- "@cortexkit/aft-win32-arm64": "0.48.1",
15833
- "@cortexkit/aft-win32-x64": "0.48.1"
16339
+ "@cortexkit/aft-darwin-arm64": "0.49.1",
16340
+ "@cortexkit/aft-darwin-x64": "0.49.1",
16341
+ "@cortexkit/aft-linux-arm64": "0.49.1",
16342
+ "@cortexkit/aft-linux-x64": "0.49.1",
16343
+ "@cortexkit/aft-win32-arm64": "0.49.1",
16344
+ "@cortexkit/aft-win32-x64": "0.49.1"
15834
16345
  },
15835
16346
  devDependencies: {
15836
16347
  "@earendil-works/pi-coding-agent": "^0.80.2",
@@ -16072,6 +16583,20 @@ import { Type } from "typebox";
16072
16583
  var optionalInt = (min, max, description = "(integer)") => Type.Optional(Type.Union([Type.Integer({ minimum: min, maximum: max }), Type.String()], {
16073
16584
  description
16074
16585
  }));
16586
+ function withPathAliasPreparation(tool) {
16587
+ const existing = tool.prepareArguments;
16588
+ const prepare = (args) => {
16589
+ const prepared = tool.name === "edit" ? prepareCanonicalEditArguments(tool.name, args) : prepareCanonicalPathArguments(tool.name, args);
16590
+ return existing ? existing(prepared) : prepared;
16591
+ };
16592
+ return {
16593
+ ...tool,
16594
+ prepareArguments: prepare,
16595
+ execute(toolCallId, params, signal, onUpdate, context) {
16596
+ return tool.execute(toolCallId, prepare(params), signal, onUpdate, context);
16597
+ }
16598
+ };
16599
+ }
16075
16600
  function bridgeFor(ctx, cwd) {
16076
16601
  if (!existsSync7(cwd)) {
16077
16602
  throw new Error(`project directory no longer exists: ${cwd} (stale restored session?)`);
@@ -31354,7 +31879,7 @@ import {
31354
31879
  readFileSync as readFileSync10,
31355
31880
  renameSync as renameSync6,
31356
31881
  rmSync as rmSync4,
31357
- statSync as statSync6,
31882
+ statSync as statSync7,
31358
31883
  writeFileSync as writeFileSync6
31359
31884
  } from "node:fs";
31360
31885
  import { join as join13 } from "node:path";
@@ -31365,7 +31890,7 @@ import {
31365
31890
  mkdirSync as mkdirSync6,
31366
31891
  openSync as openSync5,
31367
31892
  readFileSync as readFileSync8,
31368
- statSync as statSync5,
31893
+ statSync as statSync6,
31369
31894
  unlinkSync as unlinkSync6,
31370
31895
  writeFileSync as writeFileSync5
31371
31896
  } from "node:fs";
@@ -31398,7 +31923,7 @@ function lspBinDir(npmPackage) {
31398
31923
  function isInstalled(npmPackage, binary) {
31399
31924
  for (const candidate of lspBinaryCandidates(binary)) {
31400
31925
  try {
31401
- if (statSync5(join11(lspBinDir(npmPackage), candidate)).isFile())
31926
+ if (statSync6(join11(lspBinDir(npmPackage), candidate)).isFile())
31402
31927
  return true;
31403
31928
  } catch {}
31404
31929
  }
@@ -31426,7 +31951,7 @@ function writeInstalledMetaIn(installDir, version2, sha256) {
31426
31951
  function readInstalledMetaIn(installDir) {
31427
31952
  const path2 = join11(installDir, INSTALLED_META_FILE);
31428
31953
  try {
31429
- if (!statSync5(path2).isFile())
31954
+ if (!statSync6(path2).isFile())
31430
31955
  return null;
31431
31956
  const raw = readFileSync8(path2, "utf8");
31432
31957
  const parsed = JSON.parse(raw);
@@ -31483,7 +32008,7 @@ ${new Date().toISOString()}
31483
32008
  const parsed = Number.parseInt(firstLine, 10);
31484
32009
  if (Number.isFinite(parsed) && parsed > 0)
31485
32010
  owningPid = parsed;
31486
- lockMtimeMs = statSync5(lock).mtimeMs;
32011
+ lockMtimeMs = statSync6(lock).mtimeMs;
31487
32012
  } catch {
31488
32013
  return tryClaim();
31489
32014
  }
@@ -32037,7 +32562,7 @@ async function ensureServerInstalled(spec, config2, fetchImpl, signal) {
32037
32562
  return null;
32038
32563
  });
32039
32564
  if (currentHash && currentHash !== installedMeta.sha256) {
32040
- error2(`[lsp] ${spec.npm}@${version2}: TOFU sha256 mismatch — refusing to use ` + `tampered binary. Recorded ${installedMeta.sha256}, current ${currentHash}. ` + `Run \`aft doctor --clear\` to re-install from scratch.`);
32565
+ error2(`[lsp] ${spec.npm}@${version2}: TOFU sha256 mismatch — refusing to use ` + `tampered binary. Recorded ${installedMeta.sha256}, current ${currentHash}. ` + `Run \`npx @cortexkit/aft doctor --clear\` to re-install from scratch.`);
32041
32566
  return {
32042
32567
  started: false,
32043
32568
  reason: `TOFU sha256 mismatch on ${spec.npm}@${version2} — see plugin log`
@@ -32088,7 +32613,7 @@ function hashInstalledBinary(spec) {
32088
32613
  let pathToHash = null;
32089
32614
  for (const p of candidates) {
32090
32615
  try {
32091
- if (statSync6(p).isFile()) {
32616
+ if (statSync7(p).isFile()) {
32092
32617
  pathToHash = p;
32093
32618
  break;
32094
32619
  }
@@ -32114,7 +32639,7 @@ function installedBinaryPath(spec) {
32114
32639
  ] : [lspBinaryPath(spec.npm, spec.binary)];
32115
32640
  for (const candidate of candidates) {
32116
32641
  try {
32117
- if (statSync6(candidate).isFile())
32642
+ if (statSync7(candidate).isFile())
32118
32643
  return candidate;
32119
32644
  } catch {}
32120
32645
  }
@@ -32225,7 +32750,7 @@ import {
32225
32750
  realpathSync as realpathSync3,
32226
32751
  renameSync as renameSync7,
32227
32752
  rmSync as rmSync5,
32228
- statSync as statSync7,
32753
+ statSync as statSync8,
32229
32754
  unlinkSync as unlinkSync7,
32230
32755
  writeFileSync as writeFileSync7
32231
32756
  } from "node:fs";
@@ -32338,7 +32863,7 @@ function ghBinaryPath(spec, platform) {
32338
32863
  function isGithubInstalled(spec, platform) {
32339
32864
  for (const candidate of ghBinaryCandidates(spec, platform)) {
32340
32865
  try {
32341
- if (statSync7(join14(ghBinDir(spec), candidate)).isFile())
32866
+ if (statSync8(join14(ghBinDir(spec), candidate)).isFile())
32342
32867
  return true;
32343
32868
  } catch {}
32344
32869
  }
@@ -32352,7 +32877,7 @@ function ghBinaryCandidates(spec, platform) {
32352
32877
  function readGithubInstalledMetaIn(installDir) {
32353
32878
  try {
32354
32879
  const path2 = join14(installDir, INSTALLED_META_FILE2);
32355
- if (!statSync7(path2).isFile())
32880
+ if (!statSync8(path2).isFile())
32356
32881
  return null;
32357
32882
  const parsed = JSON.parse(readFileSync11(path2, "utf8"));
32358
32883
  if (typeof parsed.version !== "string" || parsed.version.length === 0)
@@ -32730,7 +33255,7 @@ function validateCachedGithubInstall(spec, platform) {
32730
33255
  const meta3 = readGithubInstalledMetaIn(packageDir);
32731
33256
  const binaryPath = ghBinaryCandidates(spec, platform).map((candidate) => join14(ghBinDir(spec), candidate)).find((candidate) => {
32732
33257
  try {
32733
- return statSync7(candidate).isFile();
33258
+ return statSync8(candidate).isFile();
32734
33259
  } catch {
32735
33260
  return false;
32736
33261
  }
@@ -33517,29 +34042,25 @@ async function assertExternalDirectoryPermission(extCtx, target, options = {}) {
33517
34042
  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.");
33518
34043
  }
33519
34044
  var ReadParams = Type2.Object({
33520
- path: Type2.Optional(Type2.String({
34045
+ path: Type2.String({
33521
34046
  description: "Path to the file to read (absolute or relative to project root)"
33522
- })),
33523
- filePath: Type2.Optional(Type2.String({
33524
- description: "Alias for `path` — provide one of the two."
33525
- })),
33526
- offset: optionalInt(1, Number.MAX_SAFE_INTEGER, "1-based line number to start reading from (use with limit)"),
33527
- limit: optionalInt(1, Number.MAX_SAFE_INTEGER, "Maximum number of lines to return")
34047
+ }),
34048
+ startLine: optionalInt(1, Number.MAX_SAFE_INTEGER, "1-based line to start reading from"),
34049
+ endLine: optionalInt(1, Number.MAX_SAFE_INTEGER, "1-based line to stop reading at (inclusive)"),
34050
+ limit: optionalInt(1, Number.MAX_SAFE_INTEGER, "Maximum number of lines to return"),
34051
+ offset: optionalInt(1, Number.MAX_SAFE_INTEGER, "1-based line number to start reading from (use with limit)")
33528
34052
  });
33529
34053
  var WriteParams = Type2.Object({
33530
- filePath: Type2.Optional(Type2.String({
34054
+ path: Type2.String({
33531
34055
  description: "Path to the file to write (absolute or relative to project root)"
33532
- })),
33533
- path: Type2.Optional(Type2.String({
33534
- description: "Alias for `filePath` — provide one of the two."
33535
- })),
34056
+ }),
33536
34057
  content: Type2.String({ description: "Full file contents to write" })
33537
34058
  });
33538
34059
  var BatchEditParams = Type2.Object({
33539
34060
  oldString: Type2.Optional(Type2.String({ description: "Text to find for a batch find/replace edit" })),
33540
34061
  newString: Type2.Optional(Type2.String({ description: "Replacement text for a batch find/replace edit" })),
33541
34062
  replaceAll: Type2.Optional(Type2.Boolean({ description: "Replace every occurrence for this batch item" })),
33542
- occurrence: optionalInt(0, Number.MAX_SAFE_INTEGER, "0-based occurrence for this batch item"),
34063
+ occurrence: optionalInt(1, Number.MAX_SAFE_INTEGER, "1-based occurrence for this batch item (1 = first match)"),
33543
34064
  startLine: optionalInt(1, Number.MAX_SAFE_INTEGER, "1-based start line for a batch line-range edit"),
33544
34065
  endLine: optionalInt(1, Number.MAX_SAFE_INTEGER, "1-based end line for a batch line-range edit"),
33545
34066
  content: Type2.Optional(Type2.String({
@@ -33547,21 +34068,19 @@ var BatchEditParams = Type2.Object({
33547
34068
  }))
33548
34069
  });
33549
34070
  var EditParams = Type2.Object({
33550
- filePath: Type2.Optional(Type2.String({
34071
+ path: Type2.String({
33551
34072
  description: "Path to the file to edit (absolute or relative to project root)"
34073
+ }),
34074
+ symbol: Type2.Optional(Type2.String({ description: "Named symbol to replace (function, class, type)" })),
34075
+ content: Type2.Optional(Type2.String({
34076
+ description: "Replacement content for symbol mode. For whole-file writes, use the `write` tool."
33552
34077
  })),
33553
- path: Type2.Optional(Type2.String({
33554
- description: "Alias for `filePath` — provide one of the two."
33555
- })),
33556
- oldString: Type2.Optional(Type2.String({ description: "Text to find (exact match, fuzzy fallback)" })),
33557
- newString: Type2.Optional(Type2.String({ description: "Replacement text (omit to delete match)" })),
33558
- replaceAll: Type2.Optional(Type2.Boolean({ description: "Replace every occurrence" })),
33559
- occurrence: optionalInt(0, Number.MAX_SAFE_INTEGER, "0-based occurrence to replace when multiple matches exist"),
33560
34078
  appendContent: Type2.Optional(Type2.String({
33561
- description: "Append text to the end of the file (creates the file if missing, parent dirs auto-created). When set, edits/oldString/newString are ignored."
34079
+ description: "Append text to the end of the file (creates the file if missing, parent dirs auto-created). When set, other edit modes are ignored."
33562
34080
  })),
33563
34081
  edits: Type2.Optional(Type2.Array(BatchEditParams, {
33564
- description: "Batch edits — array of { oldString, newString }, { oldString, newString, replaceAll: true }, or { startLine, endLine, content } objects applied atomically."
34082
+ minItems: 1,
34083
+ description: "Batch edits — non-empty array of { oldString, newString }, { oldString, newString, replaceAll: true }, or { startLine, endLine, content } objects applied atomically."
33565
34084
  }))
33566
34085
  });
33567
34086
  var GrepParams = Type2.Object({
@@ -33569,16 +34088,15 @@ var GrepParams = Type2.Object({
33569
34088
  path: Type2.Optional(Type2.String({
33570
34089
  description: "Path scope (file or directory; absolute or relative to project root)"
33571
34090
  })),
33572
- include: Type2.Optional(Type2.String({ description: "Glob filter for included files (e.g. '*.ts,*.tsx')" })),
33573
- caseSensitive: Type2.Optional(Type2.Boolean({ description: "Case-sensitive matching" }))
34091
+ include: Type2.Optional(Type2.String({ description: "Glob filter for included files (e.g. '*.ts,*.tsx')" }))
33574
34092
  });
33575
34093
  function readPathArg(args) {
33576
- return coerceAliasedStringParam(args.path, args.filePath);
34094
+ return typeof args.path === "string" ? args.path : undefined;
33577
34095
  }
33578
34096
  function mutationFilePathArg(args) {
33579
- return coerceAliasedStringParam(args.filePath, args.path);
34097
+ return typeof args.path === "string" ? args.path : undefined;
33580
34098
  }
33581
- function hasOwn(record2, key) {
34099
+ function hasOwn2(record2, key) {
33582
34100
  return Object.hasOwn(record2, key);
33583
34101
  }
33584
34102
  function validateBatchEdit(edit, index) {
@@ -33589,7 +34107,7 @@ function validateBatchEdit(edit, index) {
33589
34107
  if (typeof record2.oldString === "string") {
33590
34108
  return;
33591
34109
  }
33592
- if (hasOwn(record2, "startLine")) {
34110
+ if (hasOwn2(record2, "startLine")) {
33593
34111
  if (typeof record2.startLine !== "number" || !Number.isInteger(record2.startLine) || record2.startLine < 0) {
33594
34112
  throw new Error(`batch: edit[${index}] 'startLine' must be a positive integer (1-based)`);
33595
34113
  }
@@ -33628,7 +34146,7 @@ function renderReadCall(args, theme, context) {
33628
34146
  }
33629
34147
  function registerHoistedTools(pi, ctx, surface) {
33630
34148
  if (surface.hoistRead) {
33631
- pi.registerTool({
34149
+ pi.registerTool(withPathAliasPreparation({
33632
34150
  name: "read",
33633
34151
  label: "read",
33634
34152
  description: "Read file contents with line numbers. Backed by AFT's indexed Rust reader — faster than the built-in `read` on large repos. Images are returned as attachments on vision-capable models; PDFs and non-vision models are not yet supported.",
@@ -33641,6 +34159,8 @@ function registerHoistedTools(pi, ctx, surface) {
33641
34159
  if (typeof pathArg !== "string") {
33642
34160
  throw new Error("read: missing required parameter `path`");
33643
34161
  }
34162
+ const startLine = coerceOptionalInt(params.startLine, "startLine", 1, Number.MAX_SAFE_INTEGER);
34163
+ const endLine = coerceOptionalInt(params.endLine, "endLine", 1, Number.MAX_SAFE_INTEGER);
33644
34164
  const offset = coerceOptionalInt(params.offset, "offset", 1, Number.MAX_SAFE_INTEGER);
33645
34165
  const limit = coerceOptionalInt(params.limit, "limit", 1, Number.MAX_SAFE_INTEGER);
33646
34166
  const filePath = await resolvePathArg(extCtx.cwd, pathArg);
@@ -33649,6 +34169,10 @@ function registerHoistedTools(pi, ctx, surface) {
33649
34169
  serverValidatedRead: true
33650
34170
  });
33651
34171
  const rawArgs = { filePath: pathArg };
34172
+ if (startLine !== undefined)
34173
+ rawArgs.startLine = startLine;
34174
+ if (endLine !== undefined)
34175
+ rawArgs.endLine = endLine;
33652
34176
  if (offset !== undefined)
33653
34177
  rawArgs.offset = offset;
33654
34178
  if (limit !== undefined)
@@ -33684,21 +34208,21 @@ PDFs aren't supported on the Pi harness yet.`, response);
33684
34208
  renderCall(args, theme, context) {
33685
34209
  return renderReadCall(args, theme, context);
33686
34210
  }
33687
- });
34211
+ }));
33688
34212
  }
33689
34213
  if (surface.hoistWrite) {
33690
34214
  const writeBackupText = ctx.config.backup?.enabled === false ? "Backup capture is disabled by user config." : "Existing files are backed up before overwriting (undo via aft_safety).";
33691
- pi.registerTool({
34215
+ pi.registerTool(withPathAliasPreparation({
33692
34216
  name: "write",
33693
34217
  label: "write",
33694
- description: `Write content to a file, creating it and parent directories automatically. ${writeBackupText} Auto-formats when the project has a formatter configured. Uses \`filePath\` (not \`path\`). For partial edits, use the \`edit\` tool.`,
33695
- promptSnippet: "Create or overwrite files (uses filePath; auto-formats)",
34218
+ description: `Write content to a file, creating it and parent directories automatically. ${writeBackupText} Auto-formats when the project has a formatter configured. Uses \`path\`. For partial edits, use the \`edit\` tool.`,
34219
+ promptSnippet: "Create or overwrite files (uses path; auto-formats)",
33696
34220
  promptGuidelines: ["Use write only for new files or complete rewrites."],
33697
34221
  parameters: WriteParams,
33698
34222
  async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
33699
34223
  const filePathArg = mutationFilePathArg(params);
33700
34224
  if (typeof filePathArg !== "string") {
33701
- throw new Error("write: missing required parameter `filePath`");
34225
+ throw new Error("write: missing required parameter `path`");
33702
34226
  }
33703
34227
  const filePath = await resolvePathArg(extCtx.cwd, filePathArg);
33704
34228
  await assertExternalDirectoryPermission(extCtx, filePath, {
@@ -33711,7 +34235,7 @@ PDFs aren't supported on the Pi harness yet.`, response);
33711
34235
  };
33712
34236
  const response = await callToolCall(bridge, "write", rawArgs, extCtx);
33713
34237
  if (response.success === false) {
33714
- throw new Error(response.text || response.message || "write failed");
34238
+ throw toolErrorFromResponse("write", response);
33715
34239
  }
33716
34240
  return buildMutationResult(response);
33717
34241
  },
@@ -33721,60 +34245,45 @@ PDFs aren't supported on the Pi harness yet.`, response);
33721
34245
  renderResult(result, _options, theme, context) {
33722
34246
  return renderMutationResult(result, theme, context);
33723
34247
  }
33724
- });
34248
+ }));
33725
34249
  }
33726
34250
  if (surface.hoistEdit) {
33727
- pi.registerTool({
34251
+ pi.registerTool(withPathAliasPreparation({
33728
34252
  name: "edit",
33729
34253
  label: "edit",
33730
- description: "Edit part of a file via `appendContent`, batch `edits[]`, or `oldString`/`newString` find-and-replace. Batch `{ oldString, newString, replaceAll: true }` replaces every match. Mode priority: appendContent > edits > oldString.",
33731
- promptSnippet: "Partial file edits via appendContent, edits[], or oldString/newString (mode priority: appendContent > edits > oldString).",
34254
+ description: "Edit part of a file via `appendContent`, batch `edits[]`, or symbol plus `content`. Batch `{ oldString, newString, replaceAll: true }` replaces every match. Provide exactly one mode per call: appendContent, edits[], or symbol plus content (mixing modes is rejected).",
34255
+ promptSnippet: "Partial file edits via appendContent, edits[], or symbol plus content (exactly one mode per call).",
33732
34256
  promptGuidelines: [
33733
34257
  "Prefer edit over write when changing part of an existing file.",
33734
34258
  "Use appendContent when adding text to the end of a file.",
33735
34259
  "Use edits[] for multiple atomic changes in one file.",
33736
- "Include enough surrounding context in oldString to make the match unique, or set replaceAll/occurrence explicitly."
34260
+ "Include enough surrounding context in an edits[] find/replace item to make the match unique, or set replaceAll/occurrence explicitly."
33737
34261
  ],
33738
34262
  parameters: EditParams,
33739
34263
  async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
33740
34264
  const argsRecord = params;
33741
34265
  if (argsRecord.startLine !== undefined || argsRecord.endLine !== undefined) {
33742
- throw new Error("edit: 'startLine'/'endLine' are not top-level parameters. " + "For line-range edits, nest them inside the `edits` array: " + '`edits: [{ startLine: N, endLine: M, content: "..." }]`. ' + "For find/replace, use `oldString`/`newString` instead.");
34266
+ throw new Error("edit: 'startLine'/'endLine' are not top-level parameters. " + "For line-range edits, nest them inside the `edits` array: " + '`edits: [{ startLine: N, endLine: M, content: "..." }]`. ' + "For find/replace, use an item in `edits[]` instead.");
33743
34267
  }
33744
34268
  const filePathArg = mutationFilePathArg(params);
33745
34269
  if (typeof filePathArg !== "string") {
33746
- throw new Error("edit: missing required parameter `filePath`");
34270
+ throw new Error("edit: missing required parameter `path`");
33747
34271
  }
33748
- if (params.appendContent === undefined)
34272
+ if (params.edits !== undefined)
33749
34273
  validateBatchEdits(params.edits);
33750
34274
  const filePath = await resolvePathArg(extCtx.cwd, filePathArg);
33751
34275
  await assertExternalDirectoryPermission(extCtx, filePath, {
33752
34276
  restrictToProjectRoot: surface.restrictToProjectRoot
33753
34277
  });
33754
34278
  const bridge = bridgeFor(ctx, extCtx.cwd);
33755
- const rawArgs = { filePath: filePathArg };
33756
- for (const key of ["appendContent", "oldString", "newString"]) {
34279
+ const rawArgs = { path: filePathArg };
34280
+ for (const key of ["appendContent", "edits", "symbol", "content"]) {
33757
34281
  if (argsRecord[key] !== undefined)
33758
34282
  rawArgs[key] = argsRecord[key];
33759
34283
  }
33760
- if (Array.isArray(argsRecord.edits)) {
33761
- rawArgs.edits = argsRecord.edits.map((item) => {
33762
- if (!item || typeof item !== "object" || Array.isArray(item))
33763
- return item;
33764
- const batchItem = item;
33765
- return batchItem.replaceAll === undefined ? batchItem : { ...batchItem, replaceAll: coerceBoolean(batchItem.replaceAll) };
33766
- });
33767
- } else if (argsRecord.edits !== undefined) {
33768
- rawArgs.edits = argsRecord.edits;
33769
- }
33770
- if (params.replaceAll !== undefined)
33771
- rawArgs.replaceAll = coerceBoolean(params.replaceAll);
33772
- const occurrence = coerceOptionalInt(params.occurrence, "occurrence", 0, Number.MAX_SAFE_INTEGER);
33773
- if (occurrence !== undefined)
33774
- rawArgs.occurrence = occurrence;
33775
34284
  const response = await callToolCall(bridge, "edit", rawArgs, extCtx);
33776
34285
  if (response.success === false) {
33777
- throw new Error(response.text || response.message || "edit failed");
34286
+ throw toolErrorFromResponse("edit", response);
33778
34287
  }
33779
34288
  return buildMutationResult(response);
33780
34289
  },
@@ -33784,10 +34293,10 @@ PDFs aren't supported on the Pi harness yet.`, response);
33784
34293
  renderResult(result, _options, theme, context) {
33785
34294
  return renderMutationResult(result, theme, context);
33786
34295
  }
33787
- });
34296
+ }));
33788
34297
  }
33789
34298
  if (surface.hoistGrep) {
33790
- pi.registerTool({
34299
+ pi.registerTool(withPathAliasPreparation({
33791
34300
  name: "grep",
33792
34301
  label: "grep",
33793
34302
  description: "Search for a regex pattern across files. Uses AFT's trigram index inside the project root for fast repeated queries, and falls back to ripgrep for paths outside the project root.",
@@ -33809,8 +34318,6 @@ PDFs aren't supported on the Pi harness yet.`, response);
33809
34318
  }
33810
34319
  if (params.include)
33811
34320
  req.include = params.include;
33812
- if (params.caseSensitive !== undefined)
33813
- req.caseSensitive = params.caseSensitive;
33814
34321
  const response = await callToolCall(bridge, "grep", req, extCtx);
33815
34322
  if (response.success === false) {
33816
34323
  throw new Error(response.text || response.message || "grep failed");
@@ -33821,7 +34328,7 @@ PDFs aren't supported on the Pi harness yet.`, response);
33821
34328
  const text = appendSkippedSearchPaths(response.text ?? "", pathSplit?.missing ?? []);
33822
34329
  return textResult(text, response);
33823
34330
  }
33824
- });
34331
+ }));
33825
34332
  }
33826
34333
  }
33827
34334
  function buildMutationResult(response) {
@@ -34165,7 +34672,7 @@ var SearchParams = Type3.Object({
34165
34672
  lang: AstLang,
34166
34673
  paths: Type3.Optional(Type3.Array(Type3.String(), { description: "Paths to search (default: ['.'])" })),
34167
34674
  globs: Type3.Optional(Type3.Array(Type3.String(), { description: "Include/exclude globs (prefix `!` to exclude)" })),
34168
- contextLines: Type3.Optional(Type3.Number({ description: "Number of context lines around each match" }))
34675
+ contextLines: optionalInt(1, Number.MAX_SAFE_INTEGER, "Number of context lines around each match")
34169
34676
  });
34170
34677
  var ReplaceParams = Type3.Object({
34171
34678
  pattern: Type3.String({ description: "AST pattern with meta-variables" }),
@@ -35056,7 +35563,7 @@ function formatWaitSummary(waited, details) {
35056
35563
  return `Waited ${waited.elapsed_ms}ms; timeout reached without match.`;
35057
35564
  }
35058
35565
  if (waited.reason === "unavailable") {
35059
- 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 }).`;
35566
+ return `Waited ${waited.elapsed_ms}ms; the bridge was busy, so task state is unknown. Do not poll; let the task's completion notification wake the session, or use one bash_status snapshot on the next normal tool call.`;
35060
35567
  }
35061
35568
  const exit = typeof details.exit_code === "number" ? `, exit ${details.exit_code}` : "";
35062
35569
  return `Waited ${waited.elapsed_ms}ms; task exited (${details.status}${exit}).`;
@@ -35197,7 +35704,7 @@ function renderConflictToolResult(result, theme, context) {
35197
35704
  return renderConflictResult(collectTextContent(result), theme, context);
35198
35705
  }
35199
35706
  function registerConflictsTool(pi, ctx) {
35200
- pi.registerTool({
35707
+ pi.registerTool(withPathAliasPreparation({
35201
35708
  name: "aft_conflicts",
35202
35709
  label: "conflicts",
35203
35710
  description: "Show all git merge conflicts across the repository — returns line-numbered conflict regions with context for every conflicted file in a single call.",
@@ -35224,7 +35731,7 @@ function registerConflictsTool(pi, ctx) {
35224
35731
  renderResult(result, _options, theme, context) {
35225
35732
  return renderConflictToolResult(result, theme, context);
35226
35733
  }
35227
- });
35734
+ }));
35228
35735
  }
35229
35736
 
35230
35737
  // src/tools/fs.ts
@@ -35239,7 +35746,7 @@ var DeleteParams = Type6.Object({
35239
35746
  }))
35240
35747
  });
35241
35748
  var MoveParams = Type6.Object({
35242
- filePath: Type6.String({
35749
+ path: Type6.String({
35243
35750
  description: "Source file path to move (absolute or relative to project root)"
35244
35751
  }),
35245
35752
  destination: Type6.String({
@@ -35263,7 +35770,7 @@ function renderFsCall(toolName, args, theme, context) {
35263
35770
  return renderToolCall("delete", summary, theme, context);
35264
35771
  }
35265
35772
  const moveArgs = args;
35266
- return renderToolCall("move", `${accentPath(theme, moveArgs.filePath)} ${theme.fg("muted", "→")} ${accentPath(theme, moveArgs.destination)}`, theme, context);
35773
+ return renderToolCall("move", `${accentPath(theme, moveArgs.path)} ${theme.fg("muted", "→")} ${accentPath(theme, moveArgs.destination)}`, theme, context);
35267
35774
  }
35268
35775
  function renderFsResult(toolName, args, result, theme, context) {
35269
35776
  if (context.isError) {
@@ -35289,14 +35796,14 @@ function renderFsResult(toolName, args, result, theme, context) {
35289
35796
  }
35290
35797
  const moveArgs = args;
35291
35798
  return renderSections([
35292
- `${theme.fg("success", "✓ moved")} ${theme.fg("accent", shortenPath3(moveArgs.filePath))}`,
35799
+ `${theme.fg("success", "✓ moved")} ${theme.fg("accent", shortenPath3(moveArgs.path ?? ""))}`,
35293
35800
  `${theme.fg("muted", "to")} ${theme.fg("accent", shortenPath3(moveArgs.destination))}`
35294
35801
  ], context);
35295
35802
  }
35296
35803
  function registerFsTools(pi, ctx, surface) {
35297
35804
  const backupsDisabled = ctx.config.backup?.enabled === false;
35298
35805
  if (surface.delete) {
35299
- pi.registerTool({
35806
+ pi.registerTool(withPathAliasPreparation({
35300
35807
  name: "aft_delete",
35301
35808
  label: "delete",
35302
35809
  description: "Delete one or more files (or directories). " + (backupsDisabled ? "Backup capture is disabled by user config, so this tool does not create undo snapshots. " : "Each file is backed up before deletion — use `aft_safety undo` to recover any of them. For directories, every file inside is individually backed up before removal. ") + "Directory deletion requires recursive: true. " + "Returns { success, complete, deleted, skipped_files }: partial success is allowed; files that fail are reported in skipped_files.",
@@ -35340,16 +35847,16 @@ function registerFsTools(pi, ctx, surface) {
35340
35847
  renderResult(result, _options, theme, context) {
35341
35848
  return renderFsResult("aft_delete", context.args, result, theme, context);
35342
35849
  }
35343
- });
35850
+ }));
35344
35851
  }
35345
35852
  if (surface.move) {
35346
- pi.registerTool({
35853
+ pi.registerTool(withPathAliasPreparation({
35347
35854
  name: "aft_move",
35348
35855
  label: "move",
35349
35856
  description: "Move or rename a file. " + (backupsDisabled ? "Backup capture is disabled by user config. " : "Creates an undo backup before moving. ") + "Creates parent directories for the destination automatically. This operates on files at the OS level — to relocate a code symbol between files, use `aft_refactor` with op='move' instead.",
35350
35857
  parameters: MoveParams,
35351
35858
  async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
35352
- const filePath = await resolvePathArg(extCtx.cwd, params.filePath);
35859
+ const filePath = await resolvePathArg(extCtx.cwd, params.path);
35353
35860
  const destination = await resolvePathArg(extCtx.cwd, params.destination);
35354
35861
  const checked = new Set([filePath, destination]);
35355
35862
  for (const file2 of checked) {
@@ -35359,7 +35866,7 @@ function registerFsTools(pi, ctx, surface) {
35359
35866
  }
35360
35867
  const bridge = bridgeFor(ctx, extCtx.cwd);
35361
35868
  const response = await callToolCall(bridge, "move", {
35362
- filePath: params.filePath,
35869
+ filePath: params.path,
35363
35870
  destination: params.destination
35364
35871
  }, extCtx);
35365
35872
  if (response.success === false) {
@@ -35373,7 +35880,7 @@ function registerFsTools(pi, ctx, surface) {
35373
35880
  renderResult(result, _options, theme, context) {
35374
35881
  return renderFsResult("aft_move", context.args, result, theme, context);
35375
35882
  }
35376
- });
35883
+ }));
35377
35884
  }
35378
35885
  }
35379
35886
 
@@ -35382,7 +35889,9 @@ import { StringEnum as StringEnum2 } from "@earendil-works/pi-ai";
35382
35889
  import { Type as Type7 } from "typebox";
35383
35890
  var ImportParams = Type7.Object({
35384
35891
  op: StringEnum2(["add", "remove", "organize"], { description: "Import operation" }),
35385
- filePath: Type7.String({ description: "Path to the file (absolute or relative to project root)" }),
35892
+ path: Type7.String({
35893
+ description: "Path to the file (absolute or relative to project root)"
35894
+ }),
35386
35895
  module: Type7.Optional(Type7.String({ description: "Module path (required for add/remove), e.g. 'react', './utils'" })),
35387
35896
  names: Type7.Optional(Type7.Array(Type7.String(), {
35388
35897
  description: "Named imports to add, using native named-import text with per-name `as` aliasing where supported, e.g. ['useState'], Solidity ['ERC20', 'IERC20 as IToken']"
@@ -35412,7 +35921,7 @@ function buildImportSections(args, payload, theme) {
35412
35921
  const groups = asRecords2(response.groups);
35413
35922
  const groupText = groups.length > 0 ? groups.map((group) => `${asString2(group.name) ?? "unknown"}: ${asNumber2(group.count) ?? 0}`).join(" · ") : "No imports found";
35414
35923
  return [
35415
- `${theme.fg("success", "organized")} ${theme.fg("accent", asString2(response.file) ?? args.filePath)}`,
35924
+ `${theme.fg("success", "organized")} ${theme.fg("accent", asString2(response.file) ?? args.path ?? "")}`,
35416
35925
  `${theme.fg("muted", "groups")} ${groupText}`,
35417
35926
  `${theme.fg("muted", "duplicates removed")} ${asNumber2(response.removed_duplicates) ?? 0}`
35418
35927
  ];
@@ -35422,7 +35931,7 @@ function buildImportSections(args, payload, theme) {
35422
35931
  const status = response.already_present === true ? theme.fg("warning", "already present") : theme.fg("success", "added");
35423
35932
  return [
35424
35933
  `${status} ${theme.fg("accent", moduleName2)}`,
35425
- `${theme.fg("muted", "file")} ${theme.fg("accent", asString2(response.file) ?? args.filePath)}`,
35934
+ `${theme.fg("muted", "file")} ${theme.fg("accent", asString2(response.file) ?? args.path ?? "")}`,
35426
35935
  `${theme.fg("muted", "group")} ${asString2(response.group) ?? "—"}`
35427
35936
  ];
35428
35937
  }
@@ -35431,14 +35940,14 @@ function buildImportSections(args, payload, theme) {
35431
35940
  const removeStatus = didRemove ? `${theme.fg("success", "removed")} ${theme.fg("accent", moduleName)}` : `${theme.fg("warning", "not present")} ${theme.fg("accent", moduleName)}`;
35432
35941
  return [
35433
35942
  removeStatus,
35434
- `${theme.fg("muted", "file")} ${theme.fg("accent", asString2(response.file) ?? args.filePath)}`,
35943
+ `${theme.fg("muted", "file")} ${theme.fg("accent", asString2(response.file) ?? args.path ?? "")}`,
35435
35944
  args.removeName ? `${theme.fg("muted", "name")} ${args.removeName}` : `${theme.fg("muted", "scope")} entire import`
35436
35945
  ];
35437
35946
  }
35438
35947
  function renderImportCall(args, theme, context) {
35439
35948
  const summary = [
35440
35949
  theme.fg("accent", args.op),
35441
- accentPath(theme, args.filePath),
35950
+ accentPath(theme, args.path ?? ""),
35442
35951
  args.module ? theme.fg("toolOutput", args.module) : undefined
35443
35952
  ].filter(Boolean).join(" ");
35444
35953
  return renderToolCall("import", summary, theme, context);
@@ -35451,7 +35960,7 @@ function renderImportResult(result, args, theme, context) {
35451
35960
  }
35452
35961
  function registerImportTools(pi, ctx) {
35453
35962
  const organizeRecovery = ctx.config.backup?.enabled === false ? "Backup capture is disabled by user config; review broad cleanup changes before proceeding." : "Use aft_safety checkpoint/undo before broad cleanup.";
35454
- pi.registerTool({
35963
+ pi.registerTool(withPathAliasPreparation({
35455
35964
  name: "aft_import",
35456
35965
  label: "import",
35457
35966
  description: `Language-aware import management. Supports TS, JS, TSX, Python, Rust, Go, Solidity, Java, C#, PHP, Kotlin, Scala, Swift, Ruby, Lua, C, C++, Perl, Vue. Ops: \`add\`, \`remove\`, \`organize\`. ${organizeRecovery}`,
@@ -35460,7 +35969,7 @@ function registerImportTools(pi, ctx) {
35460
35969
  if ((params.op === "add" || params.op === "remove") && isEmptyParam(params.module)) {
35461
35970
  throw new Error(`op='${params.op}' requires 'module'`);
35462
35971
  }
35463
- const filePath = await resolvePathArg(extCtx.cwd, params.filePath);
35972
+ const filePath = await resolvePathArg(extCtx.cwd, params.path);
35464
35973
  await assertExternalDirectoryPermission(extCtx, filePath, {
35465
35974
  restrictToProjectRoot: ctx.config.restrict_to_project_root ?? false
35466
35975
  });
@@ -35498,7 +36007,7 @@ function registerImportTools(pi, ctx) {
35498
36007
  renderResult(result, _options, theme, context) {
35499
36008
  return renderImportResult(result, context.args, theme, context);
35500
36009
  }
35501
- });
36010
+ }));
35502
36011
  }
35503
36012
 
35504
36013
  // src/tools/inspect.ts
@@ -35574,7 +36083,7 @@ function diagnosticsSummaryPart(summary) {
35574
36083
  const counts = `${errors3 ?? 0} errors/${warnings ?? 0} warnings/${info ?? 0} info/${hints ?? 0} hints`;
35575
36084
  const status = asString2(section.status);
35576
36085
  if (status === "pending") {
35577
- return hasCounts ? `diagnostics ${counts} so far — still pending (servers: ${diagnosticsServerSummary(section)})` : `diagnostics pending (servers: ${diagnosticsServerSummary(section)})`;
36086
+ return hasCounts ? `diagnostics ${counts} so far — still pending (servers: ${diagnosticsServerSummary(section)}); wait for the LSP update and use the next normal aft_inspect, not repeated polling` : `diagnostics pending (servers: ${diagnosticsServerSummary(section)}); wait for the LSP update and use the next normal aft_inspect, not repeated polling`;
35578
36087
  }
35579
36088
  if (status === "incomplete") {
35580
36089
  return hasCounts ? `diagnostics ${counts} (incomplete — servers: ${diagnosticsServerSummary(section)})` : `diagnostics unavailable (status incomplete; servers: ${diagnosticsServerSummary(section)})`;
@@ -35727,7 +36236,7 @@ function buildInspectSections(payload, theme) {
35727
36236
  ].filter((part) => Boolean(part));
35728
36237
  const sections = [theme.fg("accent", parts.join(" · "))];
35729
36238
  if (stale > 0 || pending > 0) {
35730
- sections.push(theme.fg("warning", `scanner state: ${stale} stale · ${pending} pending`));
36239
+ sections.push(theme.fg("warning", `scanner state: ${stale} stale · ${pending} pending. Treat stale_categories/pending_categories as stale or incomplete cache state. AFT schedules a Tier-2 refresh after its next idle or inspect-triggered background run; use one later normal aft_inspect after that refresh, not a polling loop`));
35731
36240
  }
35732
36241
  const topPreview = tier2TopPreview(summary, theme);
35733
36242
  if (topPreview)
@@ -35803,7 +36312,7 @@ function navigateParamsSchema() {
35803
36312
  op: StringEnum3(["call_tree", "callers", "trace_to", "trace_to_symbol", "impact", "trace_data"], {
35804
36313
  description: "Navigation operation"
35805
36314
  }),
35806
- filePath: Type9.String({
36315
+ path: Type9.String({
35807
36316
  description: "Source file containing the symbol (absolute or relative to project root)"
35808
36317
  }),
35809
36318
  symbol: Type9.String({ description: "Name of the symbol to analyze" }),
@@ -35812,7 +36321,7 @@ function navigateParamsSchema() {
35812
36321
  toSymbol: Type9.Optional(Type9.String({
35813
36322
  description: "Target symbol for trace_to_symbol; the returned path ends here"
35814
36323
  })),
35815
- toFile: Type9.Optional(Type9.String({
36324
+ toPath: Type9.Optional(Type9.String({
35816
36325
  description: "Optional target file for trace_to_symbol; required when toSymbol exists in multiple files"
35817
36326
  })),
35818
36327
  includeTests: Type9.Optional(Type9.Boolean({
@@ -35834,7 +36343,7 @@ function buildNavigateSections(args, payload, theme) {
35834
36343
  function renderNavigateCall(args, theme, context) {
35835
36344
  const summary = [
35836
36345
  theme.fg("accent", args.op),
35837
- accentPath(theme, args.filePath),
36346
+ accentPath(theme, args.path),
35838
36347
  theme.fg("toolOutput", args.symbol),
35839
36348
  args.toSymbol ? theme.fg("toolOutput", `→ ${args.toSymbol}`) : undefined
35840
36349
  ].filter(Boolean).join(" ");
@@ -35846,14 +36355,14 @@ function renderNavigateResult(result, args, theme, context) {
35846
36355
  return renderSections(buildNavigateSections(args, extractStructuredPayload(result), theme), context);
35847
36356
  }
35848
36357
  function registerNavigateTool(pi, ctx) {
35849
- pi.registerTool({
36358
+ pi.registerTool(withPathAliasPreparation({
35850
36359
  name: "aft_callgraph",
35851
36360
  label: "callgraph",
35852
- 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. Use aft_zoom with `callgraph:true` for one-level forward calls-out while reading source; use aft_callgraph only for reverse callers or multi-level traces so you do not double-fetch the same relationships. 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. By default, unresolved external/stdlib leaf calls in call_tree are collapsed into one summary per parent; pass includeUnresolved=true to show every unresolved edge individually.",
36361
+ 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. Use aft_zoom with `callgraph:true` for one-level forward calls-out while reading source; use aft_callgraph only for reverse callers or multi-level traces so you do not double-fetch the same relationships. All ops require both `path` 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 `toPath`), `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. By default, unresolved external/stdlib leaf calls in call_tree are collapsed into one summary per parent; pass includeUnresolved=true to show every unresolved edge individually.",
35853
36362
  parameters: navigateParamsSchema(),
35854
36363
  async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
35855
- if (isEmptyParam(params.filePath)) {
35856
- throw new Error(`op='${params.op}' requires a \`filePath\``);
36364
+ if (isEmptyParam(params.path)) {
36365
+ throw new Error(`op='${params.op}' requires a \`path\``);
35857
36366
  }
35858
36367
  if (isEmptyParam(params.symbol)) {
35859
36368
  throw new Error(`op='${params.op}' requires a \`symbol\``);
@@ -35864,8 +36373,8 @@ function registerNavigateTool(pi, ctx) {
35864
36373
  if (params.op === "trace_to_symbol" && isEmptyParam(params.toSymbol)) {
35865
36374
  throw new Error("op='trace_to_symbol' requires a `toSymbol`");
35866
36375
  }
35867
- const filePath = await resolvePathArg(extCtx.cwd, params.filePath);
35868
- const toFile = !isEmptyParam(params.toFile) ? await resolvePathArg(extCtx.cwd, params.toFile) : undefined;
36376
+ const filePath = await resolvePathArg(extCtx.cwd, params.path);
36377
+ const toFile = !isEmptyParam(params.toPath) ? await resolvePathArg(extCtx.cwd, params.toPath) : undefined;
35869
36378
  const checked = new Set;
35870
36379
  for (const target of [filePath, ...toFile !== undefined ? [toFile] : []]) {
35871
36380
  if (checked.has(target))
@@ -35878,7 +36387,7 @@ function registerNavigateTool(pi, ctx) {
35878
36387
  const bridge = bridgeFor(ctx, extCtx.cwd);
35879
36388
  const rawArgs = {
35880
36389
  op: params.op,
35881
- filePath: params.filePath,
36390
+ filePath: params.path,
35882
36391
  symbol: params.symbol
35883
36392
  };
35884
36393
  const depth = coerceOptionalInt(params.depth, "depth", 1, Number.MAX_SAFE_INTEGER);
@@ -35888,8 +36397,8 @@ function registerNavigateTool(pi, ctx) {
35888
36397
  rawArgs.expression = params.expression;
35889
36398
  if (!isEmptyParam(params.toSymbol))
35890
36399
  rawArgs.toSymbol = params.toSymbol;
35891
- if (!isEmptyParam(params.toFile))
35892
- rawArgs.toFile = params.toFile;
36400
+ if (!isEmptyParam(params.toPath))
36401
+ rawArgs.toFile = params.toPath;
35893
36402
  if (!isEmptyParam(params.includeTests))
35894
36403
  rawArgs.includeTests = coerceBoolean(params.includeTests);
35895
36404
  if (!isEmptyParam(params.includeUnresolved))
@@ -35914,7 +36423,7 @@ function registerNavigateTool(pi, ctx) {
35914
36423
  renderResult(result, _options, theme, context) {
35915
36424
  return renderNavigateResult(result, context.args, theme, context);
35916
36425
  }
35917
- });
36426
+ }));
35918
36427
  }
35919
36428
 
35920
36429
  // src/tools/reading.ts
@@ -35931,21 +36440,21 @@ var OutlineParams = Type10.Object({
35931
36440
  }))
35932
36441
  });
35933
36442
  var ZoomTarget = Type10.Object({
35934
- filePath: Type10.String({ description: "Path to file (absolute or project-relative)" }),
36443
+ path: Type10.String({ description: "Path to file (absolute or project-relative)" }),
35935
36444
  symbol: Type10.String({ description: "Symbol name in that file" })
35936
36445
  });
35937
36446
  var ZoomParams = Type10.Object({
35938
- filePath: Type10.Optional(Type10.String({ description: "Path to file (absolute or project-relative)" })),
36447
+ path: Type10.Optional(Type10.String({ description: "Path to file (absolute or project-relative)" })),
35939
36448
  url: Type10.Optional(Type10.String({
35940
36449
  description: "HTTP/HTTPS URL of an HTML or Markdown document to fetch and zoom into"
35941
36450
  })),
35942
36451
  symbols: Type10.Optional(Type10.Union([Type10.String(), Type10.Array(Type10.String())], {
35943
- description: "Symbol name for code, or heading text for Markdown/HTML. Pass a string for one lookup or an array for batched lookups in the same file/URL."
36452
+ description: "Symbol name for code, or heading text for Markdown/HTML. Pass a string or array for batched lookups in the same file/URL."
35944
36453
  })),
35945
36454
  targets: Type10.Optional(Type10.Union([ZoomTarget, Type10.Array(ZoomTarget)], {
35946
- description: "Cross-file batch: `{ filePath, symbol }` or an array of them. Mutually exclusive with filePath/url/symbols."
36455
+ description: "Cross-file batch: `{ path, symbol }` or an array of them. Mutually exclusive with path/url/symbols."
35947
36456
  })),
35948
- contextLines: Type10.Optional(Type10.Number({ description: "Lines of context before/after (default: 3)" })),
36457
+ contextLines: optionalInt(1, Number.MAX_SAFE_INTEGER, "Lines of context before/after (default: 3)"),
35949
36458
  callgraph: Type10.Optional(Type10.Boolean({
35950
36459
  description: "Include call-graph annotations (calls-out / called-by within the same file). Default false; off keeps zoom output minimal."
35951
36460
  }))
@@ -35966,7 +36475,7 @@ async function assertReadPathPermissions(extCtx, ctx, paths) {
35966
36475
  }
35967
36476
  }
35968
36477
  function zoomTargetLabel(args) {
35969
- return args.filePath ?? args.url ?? "(no target)";
36478
+ return args.path ?? args.url ?? "(no target)";
35970
36479
  }
35971
36480
  function zoomExtraCallSites(call) {
35972
36481
  const extraCount = call.extra_count;
@@ -36087,7 +36596,7 @@ function renderZoomResult(result, args, theme, context) {
36087
36596
  }
36088
36597
  function registerReadingTools(pi, ctx, surface) {
36089
36598
  if (surface.outline) {
36090
- pi.registerTool({
36599
+ pi.registerTool(withPathAliasPreparation({
36091
36600
  name: "aft_outline",
36092
36601
  label: "outline",
36093
36602
  description: `Structural outline of source code, documentation files, or remote URLs. For code, returns symbols (functions, classes, types) with line ranges. For Markdown and HTML, returns heading hierarchy. Use this to explore structure before reading specific sections with aft_zoom. Set \`files: true\` with a directory target for a flat indexed file tree with language, symbol count, and byte metadata.
@@ -36130,7 +36639,17 @@ Pass a single \`target\`:
36130
36639
  if (response.success === false) {
36131
36640
  throw new Error(response.text || response.message || "outline failed");
36132
36641
  }
36133
- return textResult(response.text, response);
36642
+ let text = typeof response.text === "string" ? response.text : "";
36643
+ const trimmed = text.trim();
36644
+ if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
36645
+ try {
36646
+ const parsed = JSON.parse(text);
36647
+ if (typeof parsed.text === "string") {
36648
+ text = parsed.text;
36649
+ }
36650
+ } catch {}
36651
+ }
36652
+ return textResult(text, response);
36134
36653
  },
36135
36654
  renderCall(args, theme, context) {
36136
36655
  return renderOutlineCall(args, theme, context);
@@ -36138,13 +36657,13 @@ Pass a single \`target\`:
36138
36657
  renderResult(result, _options, theme, context) {
36139
36658
  return renderOutlineResult(result, theme, context);
36140
36659
  }
36141
- });
36660
+ }));
36142
36661
  }
36143
36662
  if (surface.zoom) {
36144
- pi.registerTool({
36663
+ pi.registerTool(withPathAliasPreparation({
36145
36664
  name: "aft_zoom",
36146
36665
  label: "zoom",
36147
- description: "Inspect code symbols or documentation sections. For code, returns the full source of a symbol. Pass `callgraph: true` to also include call-graph annotations (calls-out / called-by within the same file). For Markdown and HTML, returns the section content under the given heading.\n\nUse exactly ONE mode: `{ filePath, symbols }`, `{ url, symbols }`, or `{ targets }`. `symbols` can be a string or array (one or many lookups in the same file/URL). Use `targets` for cross-file batches: `{ filePath, symbol }` or an array of them.",
36666
+ description: "Inspect code symbols or documentation sections. For code, returns the full source of a symbol. Pass `callgraph: true` to also include call-graph annotations (calls-out / called-by within the same file). For Markdown and HTML, returns the section content under the given heading.\n\nUse exactly ONE mode: `{ path, symbols }`, `{ url, symbols }`, or `{ targets }`. `symbols` can be a string or array (one or many lookups in the same file/URL). Use `targets` for cross-file batches: `{ path, symbol }` or an array of them.",
36148
36667
  parameters: ZoomParams,
36149
36668
  async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
36150
36669
  const bridge = bridgeFor(ctx, extCtx.cwd);
@@ -36154,7 +36673,7 @@ Pass a single \`target\`:
36154
36673
  const entryEmpty = (entry) => {
36155
36674
  if (!entry || typeof entry !== "object")
36156
36675
  return true;
36157
- const fp = entry.filePath;
36676
+ const fp = entry.path;
36158
36677
  const sym = entry.symbol;
36159
36678
  const fpEmpty = typeof fp !== "string" || fp.length === 0;
36160
36679
  const symEmpty = typeof sym !== "string" || sym.length === 0;
@@ -36164,31 +36683,33 @@ Pass a single \`target\`:
36164
36683
  return !t.every(entryEmpty);
36165
36684
  return !entryEmpty(t);
36166
36685
  };
36167
- const hasFilePath = !isEmptyParam(params.filePath);
36686
+ const hasPath2 = !isEmptyParam(params.path);
36168
36687
  const hasUrl = !isEmptyParam(params.url);
36169
36688
  const hasTargets = hasTargetsProvided(params.targets);
36170
36689
  const hasSymbols = !isEmptyParam(params.symbols);
36171
36690
  const wantCallgraph = coerceBoolean(params.callgraph);
36172
36691
  const contextLines = coerceOptionalInt(params.contextLines, "contextLines", 1, Number.MAX_SAFE_INTEGER);
36173
36692
  if (hasTargets) {
36174
- if (hasFilePath || hasUrl || hasSymbols) {
36175
- throw new Error("'targets' is mutually exclusive with 'filePath', 'url', and 'symbols'");
36693
+ if (hasPath2 || hasUrl || hasSymbols) {
36694
+ throw new Error("'targets' is mutually exclusive with 'path', 'url', and 'symbols'");
36176
36695
  }
36177
36696
  const targets = Array.isArray(params.targets) ? params.targets : [params.targets];
36178
36697
  if (targets.length === 0) {
36179
36698
  throw new Error("'targets' must be a non-empty object or array");
36180
36699
  }
36181
36700
  for (const [i, entry] of targets.entries()) {
36182
- if (!entry || typeof entry.filePath !== "string" || entry.filePath.length === 0) {
36183
- throw new Error(`targets[${i}].filePath must be a non-empty string`);
36701
+ if (!entry || typeof entry.path !== "string" || entry.path.length === 0) {
36702
+ throw new Error(`targets[${i}].path must be a non-empty string`);
36184
36703
  }
36185
36704
  if (typeof entry.symbol !== "string" || entry.symbol.length === 0) {
36186
36705
  throw new Error(`targets[${i}].symbol must be a non-empty string`);
36187
36706
  }
36188
36707
  }
36189
- const resolvedTargets = await Promise.all(targets.map((t) => resolvePathArg(extCtx.cwd, t.filePath)));
36708
+ const resolvedTargets = await Promise.all(targets.map((t) => resolvePathArg(extCtx.cwd, t.path)));
36190
36709
  await assertReadPathPermissions(extCtx, ctx, resolvedTargets);
36191
- const rawArgs2 = { targets };
36710
+ const rawArgs2 = {
36711
+ targets: targets.map((target) => ({ filePath: target.path, symbol: target.symbol }))
36712
+ };
36192
36713
  if (contextLines !== undefined)
36193
36714
  rawArgs2.contextLines = contextLines;
36194
36715
  if (wantCallgraph)
@@ -36199,17 +36720,17 @@ Pass a single \`target\`:
36199
36720
  }
36200
36721
  return textResult(response2.text, response2);
36201
36722
  }
36202
- if (!hasFilePath && !hasUrl) {
36203
- throw new Error("Provide exactly one of 'filePath', 'url', or 'targets'");
36723
+ if (!hasPath2 && !hasUrl) {
36724
+ throw new Error("Provide exactly one of 'path', 'url', or 'targets'");
36204
36725
  }
36205
- if (hasFilePath && hasUrl) {
36206
- throw new Error("Provide exactly ONE of 'filePath' or 'url' — not both");
36726
+ if (hasPath2 && hasUrl) {
36727
+ throw new Error("Provide exactly ONE of 'path' or 'url' — not both");
36207
36728
  }
36208
36729
  if (!hasUrl) {
36209
- const file2 = await resolvePathArg(extCtx.cwd, params.filePath);
36730
+ const file2 = await resolvePathArg(extCtx.cwd, params.path);
36210
36731
  await assertReadPathPermissions(extCtx, ctx, file2);
36211
36732
  }
36212
- const rawArgs = hasUrl ? { url: params.url } : { filePath: params.filePath };
36733
+ const rawArgs = hasUrl ? { url: params.url } : { filePath: params.path };
36213
36734
  if (hasSymbols)
36214
36735
  rawArgs.symbols = params.symbols;
36215
36736
  if (contextLines !== undefined)
@@ -36228,7 +36749,7 @@ Pass a single \`target\`:
36228
36749
  renderResult(result, _options, theme, context) {
36229
36750
  return renderZoomResult(result, context.args, theme, context);
36230
36751
  }
36231
- });
36752
+ }));
36232
36753
  }
36233
36754
  }
36234
36755
 
@@ -36237,7 +36758,7 @@ import { StringEnum as StringEnum4 } from "@earendil-works/pi-ai";
36237
36758
  import { Type as Type11 } from "typebox";
36238
36759
  var RefactorParams = Type11.Object({
36239
36760
  op: StringEnum4(["move", "extract", "inline"], { description: "Refactoring operation" }),
36240
- filePath: Type11.String({
36761
+ path: Type11.String({
36241
36762
  description: "Source file (absolute or relative to project root)"
36242
36763
  }),
36243
36764
  symbol: Type11.Optional(Type11.String({ description: "Symbol name (for move, inline)" })),
@@ -36265,14 +36786,14 @@ function buildRefactorSections(args, payload, theme) {
36265
36786
  if (args.op === "extract") {
36266
36787
  return [
36267
36788
  `${theme.fg("success", "extracted")} ${theme.fg("toolOutput", asString2(response.name) ?? args.name ?? "(function)")}`,
36268
- `${theme.fg("muted", "file")} ${theme.fg("accent", shortenPath3(asString2(response.file) ?? args.filePath))}`,
36789
+ `${theme.fg("muted", "file")} ${theme.fg("accent", shortenPath3(asString2(response.file) ?? args.path ?? ""))}`,
36269
36790
  `${theme.fg("muted", "params")} ${Array.isArray(response.parameters) ? response.parameters.join(", ") || "none" : "none"}`,
36270
36791
  `${theme.fg("muted", "return type")} ${asString2(response.return_type) ?? "unknown"}`
36271
36792
  ];
36272
36793
  }
36273
36794
  return [
36274
36795
  `${theme.fg("success", "inlined")} ${theme.fg("toolOutput", asString2(response.symbol) ?? args.symbol ?? "(symbol)")}`,
36275
- `${theme.fg("muted", "file")} ${theme.fg("accent", shortenPath3(asString2(response.file) ?? args.filePath))}`,
36796
+ `${theme.fg("muted", "file")} ${theme.fg("accent", shortenPath3(asString2(response.file) ?? args.path ?? ""))}`,
36276
36797
  `${theme.fg("muted", "context")} ${asString2(response.call_context) ?? "unknown"}`,
36277
36798
  `${theme.fg("muted", "substitutions")} ${asNumber2(response.substitutions) ?? 0}`
36278
36799
  ];
@@ -36280,7 +36801,7 @@ function buildRefactorSections(args, payload, theme) {
36280
36801
  function renderRefactorCall(args, theme, context) {
36281
36802
  const summary = [
36282
36803
  theme.fg("accent", args.op),
36283
- accentPath(theme, args.filePath),
36804
+ accentPath(theme, args.path),
36284
36805
  args.symbol ? theme.fg("toolOutput", args.symbol) : undefined
36285
36806
  ].filter(Boolean).join(" ");
36286
36807
  return renderToolCall("refactor", summary, theme, context);
@@ -36291,7 +36812,7 @@ function renderRefactorResult(result, args, theme, context) {
36291
36812
  return renderSections(buildRefactorSections(args, extractStructuredPayload(result), theme), context);
36292
36813
  }
36293
36814
  function registerRefactorTool(pi, ctx) {
36294
- pi.registerTool({
36815
+ pi.registerTool(withPathAliasPreparation({
36295
36816
  name: "aft_refactor",
36296
36817
  label: "refactor",
36297
36818
  description: "Workspace-wide refactoring that updates imports and references across files. `move` relocates a top-level symbol (not nested functions or class methods) to another file, rewriting imports workspace-wide; a checkpoint is created first. To move/rename a whole file, use aft_move. `extract` pulls a line range into a new function (TS/JS/TSX, Python). `inline` replaces a call with the function's body.",
@@ -36318,7 +36839,7 @@ function registerRefactorTool(pi, ctx) {
36318
36839
  if (params.op === "inline" && callSiteLine === undefined) {
36319
36840
  throw new Error("'callSiteLine' is required for 'inline' op");
36320
36841
  }
36321
- const filePath = await resolvePathArg(extCtx.cwd, params.filePath);
36842
+ const filePath = await resolvePathArg(extCtx.cwd, params.path);
36322
36843
  const destination = !isEmptyParam(params.destination) ? await resolvePathArg(extCtx.cwd, params.destination) : undefined;
36323
36844
  const permissionTargets = params.op === "move" && destination !== undefined ? [filePath, destination] : [filePath];
36324
36845
  const checked = new Set;
@@ -36358,7 +36879,7 @@ function registerRefactorTool(pi, ctx) {
36358
36879
  renderResult(result, _options, theme, context) {
36359
36880
  return renderRefactorResult(result, context.args, theme, context);
36360
36881
  }
36361
- });
36882
+ }));
36362
36883
  }
36363
36884
 
36364
36885
  // src/tools/safety.ts
@@ -36371,7 +36892,7 @@ var SafetyParams = Type12.Object({
36371
36892
  op: StringEnum5(["undo", "history", "checkpoint", "restore", "list"], {
36372
36893
  description: "Safety operation"
36373
36894
  }),
36374
- filePath: Type12.Optional(Type12.String({
36895
+ path: Type12.Optional(Type12.String({
36375
36896
  description: "File path (required for history, optional for undo). Absolute or relative to project root."
36376
36897
  })),
36377
36898
  name: Type12.Optional(Type12.String({ description: "Checkpoint name (required for checkpoint, restore)" })),
@@ -36391,14 +36912,14 @@ function buildSafetySections(args, payload, theme) {
36391
36912
  ];
36392
36913
  }
36393
36914
  return [
36394
- `${theme.fg("success", "restored")} ${theme.fg("accent", shortenPath3(asString2(response.path) ?? args.filePath ?? "(file)"))}`,
36915
+ `${theme.fg("success", "restored")} ${theme.fg("accent", shortenPath3(asString2(response.path) ?? args.path ?? "(file)"))}`,
36395
36916
  `${theme.fg("muted", "backup")} ${asString2(response.backup_id) ?? "—"}`
36396
36917
  ];
36397
36918
  }
36398
36919
  if (args.op === "history") {
36399
36920
  const entries = asRecords2(response.entries);
36400
36921
  const sections2 = [
36401
- theme.fg("accent", shortenPath3(asString2(response.file) ?? args.filePath ?? "(file)"))
36922
+ theme.fg("accent", shortenPath3(asString2(response.file) ?? args.path ?? "(file)"))
36402
36923
  ];
36403
36924
  if (entries.length === 0) {
36404
36925
  sections2.push(theme.fg("muted", "No history entries."));
@@ -36448,7 +36969,7 @@ ${skipped.map((entry) => ` ↳ ${shortenPath3(asString2(entry.file) ?? "(file)"
36448
36969
  return sections;
36449
36970
  }
36450
36971
  function renderSafetyCall(args, theme, context) {
36451
- const target = args.filePath ?? args.name;
36972
+ const target = args.path ?? args.name;
36452
36973
  const summary = [theme.fg("accent", args.op), target ? accentPath(theme, target) : undefined].filter(Boolean).join(" ");
36453
36974
  return renderToolCall("safety", summary, theme, context);
36454
36975
  }
@@ -36458,19 +36979,19 @@ function renderSafetyResult(result, args, theme, context) {
36458
36979
  return renderSections(buildSafetySections(args, extractStructuredPayload(result), theme), context);
36459
36980
  }
36460
36981
  function registerSafetyTool(pi, ctx) {
36461
- pi.registerTool({
36982
+ pi.registerTool(withPathAliasPreparation({
36462
36983
  name: "aft_safety",
36463
36984
  label: "safety",
36464
- description: "File safety and recovery operations. Ops: `undo` (omit filePath to undo the entire last tool call; pass filePath to pop latest snapshot for one file — irreversible), `history` (list snapshots for a file), `checkpoint` (save named snapshot), `restore` (restore named checkpoint), `list` (list checkpoints). Per-file undo stack is capped at 20.",
36985
+ description: "File safety and recovery operations. Ops: `undo` (omit path to undo the entire last tool call; pass path to pop latest snapshot for one file — irreversible), `history` (list snapshots for a file), `checkpoint` (save named snapshot), `restore` (restore named checkpoint), `list` (list checkpoints). Per-file undo stack is capped at 20.",
36465
36986
  parameters: SafetyParams,
36466
36987
  async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
36467
- if (params.op === "history" && !params.filePath) {
36468
- throw new Error(`op='${params.op}' requires 'filePath'`);
36988
+ if (params.op === "history" && !params.path) {
36989
+ throw new Error(`op='${params.op}' requires 'path'`);
36469
36990
  }
36470
36991
  if ((params.op === "checkpoint" || params.op === "restore") && !params.name) {
36471
36992
  throw new Error(`op='${params.op}' requires 'name'`);
36472
36993
  }
36473
- const filePath = params.filePath ? await resolvePathArg(extCtx.cwd, params.filePath) : undefined;
36994
+ const filePath = params.path ? await resolvePathArg(extCtx.cwd, params.path) : undefined;
36474
36995
  const fileInputs = coerceStringArray(params.files);
36475
36996
  const files = fileInputs.length > 0 ? await Promise.all(fileInputs.map((file2) => resolvePathArg(extCtx.cwd, file2))) : undefined;
36476
36997
  const bridge = bridgeFor(ctx, extCtx.cwd);
@@ -36527,7 +37048,7 @@ function registerSafetyTool(pi, ctx) {
36527
37048
  renderResult(result, _options, theme, context) {
36528
37049
  return renderSafetyResult(result, context.args, theme, context);
36529
37050
  }
36530
- });
37051
+ }));
36531
37052
  }
36532
37053
 
36533
37054
  // src/tools/semantic.ts
@@ -36554,14 +37075,6 @@ var SearchParams2 = Type13.Object({
36554
37075
  default: 10,
36555
37076
  description: "Maximum number of results (default: 10, max: 100)"
36556
37077
  })),
36557
- hint: Type13.Optional(Type13.Union([
36558
- Type13.Literal("regex"),
36559
- Type13.Literal("literal"),
36560
- Type13.Literal("semantic"),
36561
- Type13.Literal("auto")
36562
- ], {
36563
- description: "Optional routing hint. Defaults to 'auto'."
36564
- })),
36565
37078
  includeTests: Type13.Optional(Type13.Boolean({
36566
37079
  default: false,
36567
37080
  description: "Include test files (*.test.*, *_test.rs, __tests__/, …) plus test-support, fixture, mock, snapshot, and corpus files. Defaults to false."
@@ -36653,13 +37166,11 @@ function renderSemanticResult(result, args, theme, context) {
36653
37166
  return renderSections(buildSemanticSections(args, extractStructuredPayload(result), theme), context);
36654
37167
  }
36655
37168
  function registerSemanticTool(pi, ctx) {
36656
- pi.registerTool({
37169
+ pi.registerTool(withPathAliasPreparation({
36657
37170
  name: "aft_search",
36658
37171
  label: "search",
36659
37172
  description: [
36660
- "Search code with one tool: concepts, identifiers, error strings, regex, literals, and filenames are auto-routed to the right engine and returned ranked. For conceptual 'how does X work' queries, phrase a full natural-language sentence — the semantic lane is NL-aware and matches intent against docstrings and comments ('how does the ORM build and execute a query', 'where is rate limiting handled'), not just keywords. Exact names, strings, and regex stay terse ('^export', 'Cargo.lock').",
36661
- "",
36662
- "Set hint to 'regex', 'literal', or 'semantic' to force a lane."
37173
+ "Search code with one tool: concepts, identifiers, error strings, regex, literals, and filenames are auto-routed to the right engine and returned ranked. For conceptual 'how does X work' queries, phrase a full natural-language sentence — the semantic lane is NL-aware and matches intent against docstrings and comments ('how does the ORM build and execute a query', 'where is rate limiting handled'), not just keywords. Exact names, strings, and regex stay terse ('^export', 'Cargo.lock')."
36663
37174
  ].join(`
36664
37175
  `),
36665
37176
  parameters: SearchParams2,
@@ -36671,8 +37182,6 @@ function registerSemanticTool(pi, ctx) {
36671
37182
  const req = { query: params.query };
36672
37183
  if (params.topK !== undefined)
36673
37184
  req.topK = params.topK;
36674
- if (params.hint !== undefined)
36675
- req.hint = params.hint;
36676
37185
  if (params.includeTests !== undefined)
36677
37186
  req.includeTests = params.includeTests;
36678
37187
  if (params.path !== undefined)
@@ -36689,7 +37198,98 @@ function registerSemanticTool(pi, ctx) {
36689
37198
  renderResult(result, _options, theme, context) {
36690
37199
  return renderSemanticResult(result, context.args, theme, context);
36691
37200
  }
36692
- });
37201
+ }));
37202
+ }
37203
+
37204
+ // src/tool-registration.ts
37205
+ var ALL_ONLY_TOOLS = new Set(["aft_callgraph", "aft_delete", "aft_move", "aft_refactor"]);
37206
+ function resolvePiToolSurface(config2) {
37207
+ const surface = config2.tool_surface ?? "recommended";
37208
+ const disabled = new Set(config2.disabled_tools ?? []);
37209
+ const ok = (name) => !disabled.has(name);
37210
+ const allOnly = (name) => ALL_ONLY_TOOLS.has(name) && ok(name);
37211
+ const restrictToProjectRoot = config2.restrict_to_project_root ?? false;
37212
+ if (surface === "minimal") {
37213
+ return {
37214
+ hoistBash: ok("bash"),
37215
+ hoistRead: false,
37216
+ hoistWrite: false,
37217
+ hoistEdit: false,
37218
+ hoistGrep: false,
37219
+ restrictToProjectRoot,
37220
+ outline: ok("aft_outline"),
37221
+ zoom: ok("aft_zoom"),
37222
+ semantic: false,
37223
+ inspect: false,
37224
+ navigate: false,
37225
+ conflicts: false,
37226
+ importTool: false,
37227
+ safety: ok("aft_safety"),
37228
+ delete: false,
37229
+ move: false,
37230
+ astSearch: false,
37231
+ astReplace: false,
37232
+ refactor: false
37233
+ };
37234
+ }
37235
+ const base = {
37236
+ hoistBash: ok("bash"),
37237
+ hoistRead: ok("read"),
37238
+ hoistWrite: ok("write"),
37239
+ hoistEdit: ok("edit"),
37240
+ hoistGrep: ok("grep") && config2.search_index === true,
37241
+ restrictToProjectRoot,
37242
+ outline: ok("aft_outline"),
37243
+ zoom: ok("aft_zoom"),
37244
+ semantic: ok("aft_search") && config2.semantic_search === true,
37245
+ inspect: ok("aft_inspect") && config2.inspect?.enabled !== false,
37246
+ navigate: false,
37247
+ conflicts: ok("aft_conflicts"),
37248
+ importTool: ok("aft_import"),
37249
+ safety: ok("aft_safety"),
37250
+ delete: false,
37251
+ move: false,
37252
+ astSearch: ok("ast_grep_search"),
37253
+ astReplace: ok("ast_grep_replace"),
37254
+ refactor: false
37255
+ };
37256
+ if (surface === "all") {
37257
+ return {
37258
+ ...base,
37259
+ navigate: allOnly("aft_callgraph"),
37260
+ delete: allOnly("aft_delete"),
37261
+ move: allOnly("aft_move"),
37262
+ refactor: allOnly("aft_refactor")
37263
+ };
37264
+ }
37265
+ return base;
37266
+ }
37267
+ function registerPiToolSurface(pi, ctx, surface) {
37268
+ if (surface.hoistBash && resolveBashConfig(ctx.config).enabled) {
37269
+ registerBashTool(pi, ctx, surface.semantic);
37270
+ }
37271
+ registerHoistedTools(pi, ctx, surface);
37272
+ if (surface.outline || surface.zoom) {
37273
+ registerReadingTools(pi, ctx, surface);
37274
+ }
37275
+ if (surface.semantic)
37276
+ registerSemanticTool(pi, ctx);
37277
+ if (surface.inspect)
37278
+ registerInspectTool(pi, ctx);
37279
+ if (surface.navigate)
37280
+ registerNavigateTool(pi, ctx);
37281
+ if (surface.conflicts)
37282
+ registerConflictsTool(pi, ctx);
37283
+ if (surface.importTool)
37284
+ registerImportTools(pi, ctx);
37285
+ if (surface.safety && ctx.config.backup?.enabled !== false)
37286
+ registerSafetyTool(pi, ctx);
37287
+ if (surface.astSearch || surface.astReplace)
37288
+ registerAstTools(pi, ctx, surface);
37289
+ if (surface.delete || surface.move)
37290
+ registerFsTools(pi, ctx, surface);
37291
+ if (surface.refactor)
37292
+ registerRefactorTool(pi, ctx);
36693
37293
  }
36694
37294
 
36695
37295
  // src/workflow-hints.ts
@@ -36720,18 +37320,18 @@ function buildWorkflowHints(opts) {
36720
37320
  }
36721
37321
  if (hasOutline && hasZoom && (hasGrep || hasSearch)) {
36722
37322
  const searchName = hasSearch ? "aft_search" : grepName;
36723
- const locate = hasSearch ? '`aft_search` is the primary code-search tool: one call auto-routes concepts, identifiers, regex, error strings, and literals (pass `hint: "regex"`/`"literal"`/`"semantic"` to force a lane).' : `\`${grepName}\` (the tool — indexed and ranked) locates code.`;
37323
+ const locate = hasSearch ? "`aft_search` is the primary code-search tool: one call auto-routes concepts, identifiers, regex, error strings, and literals." : `\`${grepName}\` (the tool — indexed and ranked) locates code.`;
36724
37324
  const readName = opts.hoistBuiltins ? "read" : "aft_read";
36725
37325
  sections.push([
36726
37326
  `**Code exploration**: ${locate} Then \`aft_outline\` for structure → \`aft_zoom\` for symbol(s). DO NOT run \`grep\`/\`rg\`/\`find\`/\`sed\`/\`cat\` through \`bash\` to locate or read code — the bash path is unindexed, unranked, serial, and routinely surfaces the wrong hit. Keep \`bash\` for shell facts (git state, file metadata, processes). Reflex translations:`,
36727
37327
  `- \`grep -rn "handleAuth" src/\` in bash → \`${searchName}({ query: "handleAuth" })\``,
36728
37328
  `- \`find . -name "*.ts" | xargs grep watcher\` in bash → \`${searchName}({ query: "watcher invalidation" })\` (concepts work too)`,
36729
- `- \`sed -n '100,160p' app.ts\` / \`cat app.ts\` in bash → \`${readName}({ filePath: "app.ts", startLine: 100, endLine: 160 })\``
37329
+ `- \`sed -n '100,160p' app.ts\` / \`cat app.ts\` in bash → \`${readName}({ path: "app.ts", startLine: 100, endLine: 160 })\``
36730
37330
  ].join(`
36731
37331
  `));
36732
37332
  }
36733
37333
  if (hasInspect) {
36734
- sections.push("**Codebase health & diagnostics**: AFT does not surface compile/type errors automatically after edits — pull them with `aft_inspect`. Run it after a batch of edits and before you run tests or commit, when starting in unfamiliar code, or before a refactor/review. One call summarizes diagnostics (compile/type errors), TODOs, metrics, dead code, unused exports, and duplicates; pass `sections` for focused drill-down and `scope` to actively pull diagnostics for a specific file or directory. Its diagnostics are a fast checkpoint, not the authority — a clean `tsc` / `cargo check` / `pyright` run is the real gate. Treat `stale_categories` as a genuine stale-cache signal while an async Tier 2 refresh catches up.");
37334
+ sections.push("**Codebase health & diagnostics**: AFT does not surface compile/type errors automatically after edits — pull them with `aft_inspect`. Run it after a batch of edits and before you run tests or commit, when starting in unfamiliar code, or before a refactor/review. One call summarizes diagnostics (compile/type errors), TODOs, metrics, dead code, unused exports, and duplicates; pass `sections` for focused drill-down and `scope` to actively pull diagnostics for a specific file or directory. Its diagnostics are a fast checkpoint, not the authority — a clean `tsc` / `cargo check` / `pyright` run is the real gate. Treat stale_categories/pending_categories as stale or incomplete cache state. AFT schedules a Tier-2 refresh after its next idle or inspect-triggered background run; use one later normal aft_inspect after that refresh, not a polling loop.");
36735
37335
  sections.push("**AFT status bar**: tool results may end with a one-line health bar `[AFT E<errors> W<warnings> | D<dead-code> U<unused-exports> C<clone/dup-groups> | T<todos>]` — an IDE-style glance that appears when a count changes. `E`/`W` are live LSP diagnostics for files touched this session (your universal compile-error signal across every language with an LSP). A `~` before `D` means the dead-code/unused/dup counts predate your latest edit — run `aft_inspect` for current numbers and detail. When `E>0`, you likely just introduced errors; investigate before moving on.");
36736
37336
  }
36737
37337
  if (hasNavigate) {
@@ -36851,14 +37451,13 @@ var PLUGIN_VERSION = (() => {
36851
37451
  return "0.0.0";
36852
37452
  }
36853
37453
  })();
36854
- var ANNOUNCEMENT_VERSION = "0.48.0";
37454
+ var ANNOUNCEMENT_VERSION = "0.49.1";
36855
37455
  var ANNOUNCEMENT_FEATURES = [
36856
- "New (beta, off by default): an optional native sandbox for hoisted bash. Confines commands to your project and blocks reads of your credentials (~/.ssh, ~/.aws, cloud configs). Enable with sandbox.enabled in aft.jsonc — macOS and Linux; see docs/config.md for the platform matrix.",
36857
- "Fixed: Windows background bash tasks could report failed with no exit code after a detach/rebind — completion is now recorded reliably.",
36858
- "Fixed: search no longer rebuilds its index unnecessarily for repositories with grafted history."
37456
+ "Windows: language servers spawn again under extended-length paths, config edits reach running servers, and dead-code/diagnostics results no longer silently drop — one canonical path form across the LSP and inspect surface.",
37457
+ "Faster answers under load: completed bash commands respond immediately instead of waiting behind background maintenance (thanks @hheei), and chained commands like `cargo test && ...` keep every command's output.",
37458
+ "Permission rules with absolute paths outside the project root now match (thanks @iceteaSA), and slow embedding providers get a clear timeout message naming the setting that raises the budget."
36859
37459
  ];
36860
37460
  var ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/DSa65w8wuf";
36861
- var ALL_ONLY_TOOLS = new Set(["aft_callgraph", "aft_delete", "aft_move", "aft_refactor"]);
36862
37461
  var pendingEagerWarnings = new Map;
36863
37462
  function isConfigureWarning(value) {
36864
37463
  if (!value || typeof value !== "object" || Array.isArray(value))
@@ -36939,67 +37538,6 @@ async function handleConfigureWarningsForSession(context) {
36939
37538
  projectRoot: context.projectRoot
36940
37539
  }, combinedWarnings);
36941
37540
  }
36942
- function resolveToolSurface(config2) {
36943
- const surface = config2.tool_surface ?? "recommended";
36944
- const disabled = new Set(config2.disabled_tools ?? []);
36945
- const ok = (name) => !disabled.has(name);
36946
- const allOnly = (name) => ALL_ONLY_TOOLS.has(name) && ok(name);
36947
- const restrictToProjectRoot = config2.restrict_to_project_root ?? false;
36948
- if (surface === "minimal") {
36949
- return {
36950
- hoistBash: ok("bash"),
36951
- hoistRead: false,
36952
- hoistWrite: false,
36953
- hoistEdit: false,
36954
- hoistGrep: false,
36955
- restrictToProjectRoot,
36956
- outline: ok("aft_outline"),
36957
- zoom: ok("aft_zoom"),
36958
- semantic: false,
36959
- inspect: false,
36960
- navigate: false,
36961
- conflicts: false,
36962
- importTool: false,
36963
- safety: ok("aft_safety"),
36964
- delete: false,
36965
- move: false,
36966
- astSearch: false,
36967
- astReplace: false,
36968
- refactor: false
36969
- };
36970
- }
36971
- const base = {
36972
- hoistBash: ok("bash"),
36973
- hoistRead: ok("read"),
36974
- hoistWrite: ok("write"),
36975
- hoistEdit: ok("edit"),
36976
- hoistGrep: ok("grep") && config2.search_index === true,
36977
- restrictToProjectRoot,
36978
- outline: ok("aft_outline"),
36979
- zoom: ok("aft_zoom"),
36980
- semantic: ok("aft_search") && config2.semantic_search === true,
36981
- inspect: ok("aft_inspect") && config2.inspect?.enabled !== false,
36982
- navigate: false,
36983
- conflicts: ok("aft_conflicts"),
36984
- importTool: ok("aft_import"),
36985
- safety: ok("aft_safety"),
36986
- delete: false,
36987
- move: false,
36988
- astSearch: ok("ast_grep_search"),
36989
- astReplace: ok("ast_grep_replace"),
36990
- refactor: false
36991
- };
36992
- if (surface === "all") {
36993
- return {
36994
- ...base,
36995
- navigate: allOnly("aft_callgraph"),
36996
- delete: allOnly("aft_delete"),
36997
- move: allOnly("aft_move"),
36998
- refactor: allOnly("aft_refactor")
36999
- };
37000
- }
37001
- return base;
37002
- }
37003
37541
  async function src_default(pi) {
37004
37542
  const deliverConfigMigrationWarnings = (messages) => {
37005
37543
  for (const message of messages) {
@@ -37210,7 +37748,7 @@ ${lines}
37210
37748
  });
37211
37749
  }
37212
37750
  pool.setConfigureOverride("harness", "pi");
37213
- pool.setConfigureOverride("aft_search_registered", resolveToolSurface(config2).semantic);
37751
+ pool.setConfigureOverride("aft_search_registered", resolvePiToolSurface(config2).semantic);
37214
37752
  const ctx = { pool, config: config2, storageDir };
37215
37753
  if (onnxRuntimePromise) {
37216
37754
  onnxRuntimePromise.then((ortDylibDir) => {
@@ -37249,41 +37787,8 @@ ${lines}
37249
37787
  if (ANNOUNCEMENT_VERSION && ANNOUNCEMENT_FEATURES.length > 0) {
37250
37788
  sendFeatureAnnouncement(ANNOUNCEMENT_VERSION, ANNOUNCEMENT_FEATURES, ANNOUNCEMENT_FOOTER, storageDir);
37251
37789
  }
37252
- const surface = resolveToolSurface(config2);
37253
- if (surface.hoistBash && resolveBashConfig(config2).enabled) {
37254
- registerBashTool(pi, ctx, surface.semantic);
37255
- }
37256
- registerHoistedTools(pi, ctx, surface);
37257
- if (surface.outline || surface.zoom) {
37258
- registerReadingTools(pi, ctx, surface);
37259
- }
37260
- if (surface.semantic) {
37261
- registerSemanticTool(pi, ctx);
37262
- }
37263
- if (surface.inspect) {
37264
- registerInspectTool(pi, ctx);
37265
- }
37266
- if (surface.navigate) {
37267
- registerNavigateTool(pi, ctx);
37268
- }
37269
- if (surface.conflicts) {
37270
- registerConflictsTool(pi, ctx);
37271
- }
37272
- if (surface.importTool) {
37273
- registerImportTools(pi, ctx);
37274
- }
37275
- if (surface.safety && config2.backup?.enabled !== false) {
37276
- registerSafetyTool(pi, ctx);
37277
- }
37278
- if (surface.astSearch || surface.astReplace) {
37279
- registerAstTools(pi, ctx, surface);
37280
- }
37281
- if (surface.delete || surface.move) {
37282
- registerFsTools(pi, ctx, surface);
37283
- }
37284
- if (surface.refactor) {
37285
- registerRefactorTool(pi, ctx);
37286
- }
37790
+ const surface = resolvePiToolSurface(config2);
37791
+ registerPiToolSurface(pi, ctx, surface);
37287
37792
  registerWorkflowHints(pi, config2, surface);
37288
37793
  registerStatusCommand(pi, ctx);
37289
37794
  pi.on("tool_result", async (event, extCtx) => {
@@ -37349,7 +37854,7 @@ ${lines}
37349
37854
  var __test__2 = {
37350
37855
  enqueueConfigParseWarnings,
37351
37856
  bridgeDirectoryFromCallback,
37352
- resolveToolSurface,
37857
+ resolveToolSurface: resolvePiToolSurface,
37353
37858
  handleConfigureWarningsForSession,
37354
37859
  shouldPrepareOnnxRuntime,
37355
37860
  createVersionMismatchHandler