@cortexkit/aft-pi 0.48.1 → 0.49.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -9623,14 +9623,6 @@ function coerceStringArray(value) {
9623
9623
  }
9624
9624
  return [];
9625
9625
  }
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
9626
  function coerceTargetParam(value) {
9635
9627
  if (Array.isArray(value)) {
9636
9628
  return value.filter((entry) => typeof entry === "string" && entry.length > 0);
@@ -10185,6 +10177,23 @@ ${reflowText}`;
10185
10177
  }
10186
10178
  return " Auto-formatted.";
10187
10179
  }
10180
+ // ../aft-bridge/dist/error-contract.js
10181
+ class AftToolError extends Error {
10182
+ code;
10183
+ response;
10184
+ constructor(message, code, response) {
10185
+ const cause = { code, message, response };
10186
+ super(message, { cause });
10187
+ this.name = "AftToolError";
10188
+ this.code = code;
10189
+ this.response = response;
10190
+ }
10191
+ }
10192
+ function toolErrorFromResponse(command, response) {
10193
+ const code = typeof response.code === "string" && response.code.length > 0 ? response.code : "unknown_error";
10194
+ const message = typeof response.message === "string" && response.message.length > 0 ? response.message : `${command} failed`;
10195
+ return new AftToolError(message, code, response);
10196
+ }
10188
10197
  // ../aft-bridge/dist/format.js
10189
10198
  function compressionSavingsPercent(original, compressed) {
10190
10199
  if (original <= 0)
@@ -11807,6 +11816,420 @@ function isProcessAlive(pid) {
11807
11816
  return true;
11808
11817
  }
11809
11818
  }
11819
+ // ../aft-bridge/dist/path-aliases.js
11820
+ class InvalidRequestError extends AftToolError {
11821
+ constructor(message) {
11822
+ super(message, "invalid_request", {
11823
+ success: false,
11824
+ code: "invalid_request",
11825
+ message
11826
+ });
11827
+ this.name = "InvalidRequestError";
11828
+ }
11829
+ }
11830
+ function isWellFormedUnicodeString(value) {
11831
+ for (let index = 0;index < value.length; index++) {
11832
+ const codeUnit = value.charCodeAt(index);
11833
+ if (codeUnit >= 55296 && codeUnit <= 56319) {
11834
+ const next = value.charCodeAt(index + 1);
11835
+ if (next < 56320 || next > 57343 || Number.isNaN(next))
11836
+ return false;
11837
+ index++;
11838
+ } else if (codeUnit >= 56320 && codeUnit <= 57343) {
11839
+ return false;
11840
+ }
11841
+ }
11842
+ return true;
11843
+ }
11844
+ function hasOwn(record, key) {
11845
+ return Object.hasOwn(record, key);
11846
+ }
11847
+ function invalidPathValue(property) {
11848
+ throw new InvalidRequestError(`'${property}' must be a non-empty well-formed Unicode string`);
11849
+ }
11850
+ function pathValue(record, property) {
11851
+ const value = record[property];
11852
+ if (typeof value !== "string" || value.length === 0 || !isWellFormedUnicodeString(value)) {
11853
+ invalidPathValue(property);
11854
+ }
11855
+ return value;
11856
+ }
11857
+ function normalizeAliasPair(record, canonical, legacy, required) {
11858
+ const hasCanonical = hasOwn(record, canonical);
11859
+ const hasLegacy = hasOwn(record, legacy);
11860
+ if (!hasCanonical && !hasLegacy) {
11861
+ if (required) {
11862
+ throw new InvalidRequestError(`'${canonical}' is required`);
11863
+ }
11864
+ return;
11865
+ }
11866
+ if (hasCanonical && hasLegacy) {
11867
+ let canonicalValue;
11868
+ let legacyValue;
11869
+ try {
11870
+ canonicalValue = pathValue(record, canonical);
11871
+ legacyValue = pathValue(record, legacy);
11872
+ } catch {
11873
+ throw new InvalidRequestError(`Invalid request: '${canonical}' and '${legacy}' must both be non-empty well-formed Unicode strings`);
11874
+ }
11875
+ if (canonicalValue !== legacyValue) {
11876
+ throw new InvalidRequestError(`Invalid request: '${canonical}' and '${legacy}' must contain equal decoded strings`);
11877
+ }
11878
+ delete record[legacy];
11879
+ return;
11880
+ }
11881
+ if (hasCanonical) {
11882
+ pathValue(record, canonical);
11883
+ return;
11884
+ }
11885
+ record[canonical] = pathValue(record, legacy);
11886
+ delete record[legacy];
11887
+ }
11888
+ function validateOptionalCanonicalPath(record, property) {
11889
+ if (hasOwn(record, property))
11890
+ pathValue(record, property);
11891
+ }
11892
+ function normalizeZoomTargets(record) {
11893
+ if (!hasOwn(record, "targets"))
11894
+ return;
11895
+ const targets = record.targets;
11896
+ const normalizeTarget = (target, index) => {
11897
+ if (!target || typeof target !== "object" || Array.isArray(target)) {
11898
+ throw new InvalidRequestError(`'targets[${index}].path' must be a non-empty string`);
11899
+ }
11900
+ const source = target;
11901
+ const emptyTarget = source.symbol === "" && (hasOwn(source, "path") && source.path === "" || hasOwn(source, "filePath") && source.filePath === "");
11902
+ if (emptyTarget)
11903
+ return { ...source };
11904
+ const normalized = { ...source };
11905
+ try {
11906
+ normalizeAliasPair(normalized, "path", "filePath", true);
11907
+ } catch (error2) {
11908
+ if (error2 instanceof InvalidRequestError) {
11909
+ throw new InvalidRequestError(error2.message.replace("'filePath'", `'targets[${index}].filePath'`).replace("'path'", `'targets[${index}].path'`));
11910
+ }
11911
+ throw error2;
11912
+ }
11913
+ return normalized;
11914
+ };
11915
+ if (Array.isArray(targets)) {
11916
+ if (targets.length === 0)
11917
+ return;
11918
+ record.targets = targets.map(normalizeTarget);
11919
+ return;
11920
+ }
11921
+ if (targets && typeof targets === "object") {
11922
+ record.targets = normalizeTarget(targets, 0);
11923
+ }
11924
+ }
11925
+ function bareToolName(toolName) {
11926
+ const bare = toolName.startsWith("aft_") ? toolName.slice(4) : toolName;
11927
+ 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") {
11928
+ return bare;
11929
+ }
11930
+ return;
11931
+ }
11932
+ function prepareCanonicalPathArguments(toolName, rawArguments) {
11933
+ if (!rawArguments || typeof rawArguments !== "object" || Array.isArray(rawArguments)) {
11934
+ throw new InvalidRequestError("tool arguments must be an object");
11935
+ }
11936
+ const tool = bareToolName(toolName);
11937
+ const record = { ...rawArguments };
11938
+ if (!tool)
11939
+ return record;
11940
+ switch (tool) {
11941
+ case "read":
11942
+ case "write":
11943
+ case "edit":
11944
+ case "move":
11945
+ case "import":
11946
+ case "refactor":
11947
+ normalizeAliasPair(record, "path", "filePath", true);
11948
+ break;
11949
+ case "zoom":
11950
+ normalizeAliasPair(record, "path", "filePath", false);
11951
+ normalizeZoomTargets(record);
11952
+ break;
11953
+ case "callgraph":
11954
+ normalizeAliasPair(record, "path", "filePath", true);
11955
+ normalizeAliasPair(record, "toPath", "toFile", false);
11956
+ break;
11957
+ case "safety":
11958
+ normalizeAliasPair(record, "path", "filePath", false);
11959
+ break;
11960
+ case "grep":
11961
+ case "search":
11962
+ case "conflicts":
11963
+ validateOptionalCanonicalPath(record, "path");
11964
+ break;
11965
+ }
11966
+ return record;
11967
+ }
11968
+ var EDIT_ROOT_COMPATIBILITY_KEYS = new Set([
11969
+ "oldString",
11970
+ "newString",
11971
+ "replaceAll",
11972
+ "occurrence"
11973
+ ]);
11974
+ var EDIT_ROOT_CANONICAL_KEYS = new Set(["path", "appendContent", "edits", "symbol", "content"]);
11975
+ var EDIT_ITEM_KEYS = new Set([
11976
+ "oldString",
11977
+ "newString",
11978
+ "replaceAll",
11979
+ "occurrence",
11980
+ "startLine",
11981
+ "endLine",
11982
+ "content"
11983
+ ]);
11984
+ var ASCII_WHITESPACE = /^[\t\n\v\f\r ]+$/;
11985
+ var ASCII_TRIM = /^[\t\n\v\f\r ]+|[\t\n\v\f\r ]+$/g;
11986
+ var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER;
11987
+ function prepareCanonicalEditArguments(toolName, rawArguments) {
11988
+ if (!rawArguments || typeof rawArguments !== "object" || Array.isArray(rawArguments)) {
11989
+ throw new InvalidRequestError("tool arguments must be an object");
11990
+ }
11991
+ const raw = rawArguments;
11992
+ const record = copyOwnProperties(raw);
11993
+ normalizeEditPathAlias(record);
11994
+ const suppliedLineFields = ["startLine", "endLine"].filter((key) => hasOwn(record, key));
11995
+ if (suppliedLineFields.length > 0) {
11996
+ 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 }].");
11997
+ }
11998
+ const isOpenCodeRetiredBoundary = toolName === "aft_edit";
11999
+ const retiredFields = ["file", "mode"].filter((key) => hasOwn(record, key));
12000
+ if (retiredFields.length > 0 && isOpenCodeRetiredBoundary) {
12001
+ 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`.");
12002
+ }
12003
+ const unknownRootKeys = Object.getOwnPropertyNames(record).filter((key) => !EDIT_ROOT_CANONICAL_KEYS.has(key) && !EDIT_ROOT_COMPATIBILITY_KEYS.has(key) && key !== "filePath").sort();
12004
+ if (unknownRootKeys.length > 0) {
12005
+ throw new InvalidRequestError(formatUnknownKeys(unknownRootKeys));
12006
+ }
12007
+ const modes = editModesPresent(record);
12008
+ if (modes.length > 1) {
12009
+ throw new InvalidRequestError(`edit: conflicting modes: ${modes.join(", ")}`);
12010
+ }
12011
+ if (modes.length === 0) {
12012
+ throw new InvalidRequestError("edit: exactly one of `appendContent`, `edits`, or `symbol` plus `content` is required");
12013
+ }
12014
+ const mode = modes[0];
12015
+ if (mode === "appendContent") {
12016
+ if (typeof record.appendContent !== "string") {
12017
+ throw new InvalidRequestError("edit: 'appendContent' must be a string");
12018
+ }
12019
+ } else if (mode === "edits") {
12020
+ const parsedEdits = parseEditArray(record.edits);
12021
+ record.edits = parsedEdits.map((item, index) => normalizeEditItem(item, index));
12022
+ } else if (mode === "symbol/content") {
12023
+ if (!hasOwn(record, "symbol") || typeof record.symbol !== "string") {
12024
+ throw new InvalidRequestError("edit: 'symbol' must be a string when symbol mode is selected");
12025
+ }
12026
+ if (!hasOwn(record, "content") || typeof record.content !== "string") {
12027
+ throw new InvalidRequestError("edit: symbol mode requires both 'symbol' and 'content' string properties");
12028
+ }
12029
+ } else {
12030
+ const item = {};
12031
+ for (const key of EDIT_ROOT_COMPATIBILITY_KEYS) {
12032
+ if (hasOwn(record, key))
12033
+ item[key] = record[key];
12034
+ }
12035
+ record.edits = [normalizeEditItem(item, 0)];
12036
+ for (const key of EDIT_ROOT_COMPATIBILITY_KEYS)
12037
+ delete record[key];
12038
+ }
12039
+ validateEditPath(record);
12040
+ return record;
12041
+ }
12042
+ function normalizeEditPathAlias(record) {
12043
+ const hasCanonical = hasOwn(record, "path");
12044
+ const hasLegacy = hasOwn(record, "filePath");
12045
+ if (!hasCanonical && !hasLegacy)
12046
+ return;
12047
+ if (hasCanonical && hasLegacy) {
12048
+ let canonical;
12049
+ let legacy;
12050
+ try {
12051
+ canonical = pathValue(record, "path");
12052
+ legacy = pathValue(record, "filePath");
12053
+ } catch {
12054
+ throw new InvalidRequestError("Invalid request: 'path' and 'filePath' must both be non-empty well-formed Unicode strings");
12055
+ }
12056
+ if (canonical !== legacy) {
12057
+ throw new InvalidRequestError("Invalid request: 'path' and 'filePath' must contain equal decoded strings");
12058
+ }
12059
+ delete record.filePath;
12060
+ return;
12061
+ }
12062
+ if (!hasCanonical) {
12063
+ record.path = pathValue(record, "filePath");
12064
+ delete record.filePath;
12065
+ }
12066
+ }
12067
+ function validateEditPath(record) {
12068
+ if (!hasOwn(record, "path")) {
12069
+ throw new InvalidRequestError("'path' is required");
12070
+ }
12071
+ pathValue(record, "path");
12072
+ }
12073
+ function formatUnknownKeys(keys) {
12074
+ return `Unrecognized keys: ${keys.map((key) => `"${key}"`).join(", ")}`;
12075
+ }
12076
+ function editModesPresent(record) {
12077
+ const modes = [];
12078
+ if (hasOwn(record, "appendContent"))
12079
+ modes.push("appendContent");
12080
+ if (hasOwn(record, "edits"))
12081
+ modes.push("edits");
12082
+ if (hasOwn(record, "symbol") || hasOwn(record, "content"))
12083
+ modes.push("symbol/content");
12084
+ if (["oldString", "newString", "replaceAll", "occurrence"].some((key) => hasOwn(record, key))) {
12085
+ modes.push("oldString/newString");
12086
+ }
12087
+ return modes;
12088
+ }
12089
+ function parseEditArray(value) {
12090
+ if (typeof value === "string") {
12091
+ let parsed;
12092
+ try {
12093
+ parsed = JSON.parse(value);
12094
+ } catch {
12095
+ throw new InvalidRequestError("edit: 'edits' must contain valid JSON representing an array");
12096
+ }
12097
+ if (!Array.isArray(parsed)) {
12098
+ throw new InvalidRequestError("edit: 'edits' JSON must have an array root");
12099
+ }
12100
+ if (parsed.length === 0) {
12101
+ throw new InvalidRequestError("edit: 'edits' array must not be empty");
12102
+ }
12103
+ return parsed;
12104
+ }
12105
+ if (!Array.isArray(value)) {
12106
+ throw new InvalidRequestError("edit: 'edits' must be a non-empty array");
12107
+ }
12108
+ if (value.length === 0) {
12109
+ throw new InvalidRequestError("edit: 'edits' array must not be empty");
12110
+ }
12111
+ return value;
12112
+ }
12113
+ function normalizeEditItem(value, index) {
12114
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
12115
+ throw new InvalidRequestError(`edit: edits[${index}] must be an object`);
12116
+ }
12117
+ const source = value;
12118
+ const item = copyOwnProperties(source);
12119
+ normalizeItemAlias(item, "oldString", "oldText");
12120
+ normalizeItemAlias(item, "newString", "newText");
12121
+ const hasFindField = ["oldString", "newString", "replaceAll", "occurrence"].some((key) => hasOwn(item, key));
12122
+ const hasRangeField = ["startLine", "endLine", "content"].some((key) => hasOwn(item, key));
12123
+ if (hasFindField && hasRangeField) {
12124
+ throw new InvalidRequestError(`edit: edits[${index}] mixes find/replace and line-range fields`);
12125
+ }
12126
+ if (hasFindField) {
12127
+ if (!hasOwn(item, "oldString") || typeof item.oldString !== "string") {
12128
+ throw new InvalidRequestError(`edit: edits[${index}] requires string 'oldString'`);
12129
+ }
12130
+ if (hasOwn(item, "newString") && typeof item.newString !== "string") {
12131
+ throw new InvalidRequestError(`edit: edits[${index}].newString must be a string`);
12132
+ }
12133
+ coerceEditScalars(item, index);
12134
+ validateEditItemKeys(item, index);
12135
+ return item;
12136
+ }
12137
+ if (hasRangeField) {
12138
+ for (const key of ["startLine", "endLine"]) {
12139
+ const value2 = item[key];
12140
+ if (typeof value2 === "string" && /^[0-9]+$/.test(value2.trim())) {
12141
+ item[key] = Number(value2.trim());
12142
+ }
12143
+ if (!hasOwn(item, key) || !isPositiveSafeInteger(item[key])) {
12144
+ throw new InvalidRequestError(`edit: edits[${index}].${key} must be a positive integer`);
12145
+ }
12146
+ }
12147
+ if (item.startLine > item.endLine) {
12148
+ throw new InvalidRequestError(`edit: edits[${index}] requires startLine <= endLine`);
12149
+ }
12150
+ if (!hasOwn(item, "content") || typeof item.content !== "string") {
12151
+ throw new InvalidRequestError(`edit: edits[${index}] requires string 'content'`);
12152
+ }
12153
+ validateEditItemKeys(item, index);
12154
+ return item;
12155
+ }
12156
+ throw new InvalidRequestError(`edit: edits[${index}] must be a find/replace or line-range item`);
12157
+ }
12158
+ function normalizeItemAlias(item, canonical, legacy) {
12159
+ if (hasOwn(item, legacy)) {
12160
+ if (!hasOwn(item, canonical))
12161
+ item[canonical] = item[legacy];
12162
+ delete item[legacy];
12163
+ }
12164
+ }
12165
+ function validateEditItemKeys(item, index) {
12166
+ const unknown = Object.getOwnPropertyNames(item).filter((key) => !EDIT_ITEM_KEYS.has(key)).sort();
12167
+ if (unknown.length > 0) {
12168
+ throw new InvalidRequestError(`edit: edits[${index}] contains ${formatUnknownKeys(unknown)}`);
12169
+ }
12170
+ }
12171
+ function coerceEditScalars(item, index) {
12172
+ if (hasOwn(item, "replaceAll") && hasOwn(item, "occurrence")) {
12173
+ throw new InvalidRequestError(`edit: edits[${index}] cannot contain both 'replaceAll' and 'occurrence'`);
12174
+ }
12175
+ if (hasOwn(item, "replaceAll"))
12176
+ item.replaceAll = coerceEditBoolean(item.replaceAll, index);
12177
+ if (hasOwn(item, "occurrence")) {
12178
+ const occurrence = coerceEditOccurrence(item.occurrence, index);
12179
+ if (occurrence === undefined)
12180
+ delete item.occurrence;
12181
+ else
12182
+ item.occurrence = occurrence;
12183
+ }
12184
+ }
12185
+ function coerceEditBoolean(value, index) {
12186
+ if (typeof value === "boolean")
12187
+ return value;
12188
+ if (typeof value === "number" && Number.isFinite(value) && (value === 0 || value === 1)) {
12189
+ return value === 1;
12190
+ }
12191
+ if (typeof value === "string") {
12192
+ if (value === "1")
12193
+ return true;
12194
+ if (value === "0")
12195
+ return false;
12196
+ if (/^(?:true|false)$/i.test(value))
12197
+ return value.toLowerCase() === "true";
12198
+ }
12199
+ throw new InvalidRequestError(`edit: edits[${index}].replaceAll must be a boolean, true/false string, or 0/1`);
12200
+ }
12201
+ function coerceEditOccurrence(value, index) {
12202
+ if (value === null)
12203
+ return;
12204
+ if (typeof value === "string") {
12205
+ const trimmed = value.replace(ASCII_TRIM, "");
12206
+ if (trimmed.length === 0 || ASCII_WHITESPACE.test(trimmed))
12207
+ return;
12208
+ if (!/^[+]?[0-9]+$/.test(trimmed)) {
12209
+ throw new InvalidRequestError(`edit: edits[${index}].occurrence must be a positive integer`);
12210
+ }
12211
+ try {
12212
+ const parsed = BigInt(trimmed);
12213
+ if (parsed < 1n || parsed > BigInt(MAX_SAFE_INTEGER))
12214
+ throw new Error("out of range");
12215
+ return Number(parsed);
12216
+ } catch {
12217
+ throw new InvalidRequestError(`edit: edits[${index}].occurrence must be a positive integer`);
12218
+ }
12219
+ }
12220
+ if (typeof value === "number" && isPositiveSafeInteger(value))
12221
+ return value;
12222
+ throw new InvalidRequestError(`edit: edits[${index}].occurrence must be a positive integer`);
12223
+ }
12224
+ function isPositiveSafeInteger(value) {
12225
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 1;
12226
+ }
12227
+ function copyOwnProperties(source) {
12228
+ const copy = Object.create(null);
12229
+ for (const key of Object.getOwnPropertyNames(source))
12230
+ copy[key] = source[key];
12231
+ return copy;
12232
+ }
11810
12233
  // ../aft-bridge/dist/pool.js
11811
12234
  import { homedir as homedir10 } from "node:os";
11812
12235
 
@@ -15795,7 +16218,7 @@ import {
15795
16218
  // package.json
15796
16219
  var package_default = {
15797
16220
  name: "@cortexkit/aft-pi",
15798
- version: "0.48.1",
16221
+ version: "0.49.0",
15799
16222
  type: "module",
15800
16223
  description: "Pi coding agent extension for Agent File Tools (AFT) — tree-sitter and LSP-powered code analysis",
15801
16224
  main: "dist/index.js",
@@ -15818,19 +16241,19 @@ var package_default = {
15818
16241
  "test:unit": "bun test src/__tests__ --path-ignore-patterns 'src/__tests__/e2e/**'"
15819
16242
  },
15820
16243
  dependencies: {
15821
- "@cortexkit/aft-bridge": "0.48.1",
16244
+ "@cortexkit/aft-bridge": "0.49.0",
15822
16245
  "comment-json": "^5.0.0",
15823
16246
  diff: "^8.0.4",
15824
16247
  typebox: "1.1.38",
15825
16248
  zod: "^4.4.3"
15826
16249
  },
15827
16250
  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"
16251
+ "@cortexkit/aft-darwin-arm64": "0.49.0",
16252
+ "@cortexkit/aft-darwin-x64": "0.49.0",
16253
+ "@cortexkit/aft-linux-arm64": "0.49.0",
16254
+ "@cortexkit/aft-linux-x64": "0.49.0",
16255
+ "@cortexkit/aft-win32-arm64": "0.49.0",
16256
+ "@cortexkit/aft-win32-x64": "0.49.0"
15834
16257
  },
15835
16258
  devDependencies: {
15836
16259
  "@earendil-works/pi-coding-agent": "^0.80.2",
@@ -16072,6 +16495,20 @@ import { Type } from "typebox";
16072
16495
  var optionalInt = (min, max, description = "(integer)") => Type.Optional(Type.Union([Type.Integer({ minimum: min, maximum: max }), Type.String()], {
16073
16496
  description
16074
16497
  }));
16498
+ function withPathAliasPreparation(tool) {
16499
+ const existing = tool.prepareArguments;
16500
+ const prepare = (args) => {
16501
+ const prepared = tool.name === "edit" ? prepareCanonicalEditArguments(tool.name, args) : prepareCanonicalPathArguments(tool.name, args);
16502
+ return existing ? existing(prepared) : prepared;
16503
+ };
16504
+ return {
16505
+ ...tool,
16506
+ prepareArguments: prepare,
16507
+ execute(toolCallId, params, signal, onUpdate, context) {
16508
+ return tool.execute(toolCallId, prepare(params), signal, onUpdate, context);
16509
+ }
16510
+ };
16511
+ }
16075
16512
  function bridgeFor(ctx, cwd) {
16076
16513
  if (!existsSync7(cwd)) {
16077
16514
  throw new Error(`project directory no longer exists: ${cwd} (stale restored session?)`);
@@ -32037,7 +32474,7 @@ async function ensureServerInstalled(spec, config2, fetchImpl, signal) {
32037
32474
  return null;
32038
32475
  });
32039
32476
  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.`);
32477
+ 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
32478
  return {
32042
32479
  started: false,
32043
32480
  reason: `TOFU sha256 mismatch on ${spec.npm}@${version2} — see plugin log`
@@ -33517,29 +33954,25 @@ async function assertExternalDirectoryPermission(extCtx, target, options = {}) {
33517
33954
  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
33955
  }
33519
33956
  var ReadParams = Type2.Object({
33520
- path: Type2.Optional(Type2.String({
33957
+ path: Type2.String({
33521
33958
  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")
33959
+ }),
33960
+ startLine: optionalInt(1, Number.MAX_SAFE_INTEGER, "1-based line to start reading from"),
33961
+ endLine: optionalInt(1, Number.MAX_SAFE_INTEGER, "1-based line to stop reading at (inclusive)"),
33962
+ limit: optionalInt(1, Number.MAX_SAFE_INTEGER, "Maximum number of lines to return"),
33963
+ offset: optionalInt(1, Number.MAX_SAFE_INTEGER, "1-based line number to start reading from (use with limit)")
33528
33964
  });
33529
33965
  var WriteParams = Type2.Object({
33530
- filePath: Type2.Optional(Type2.String({
33966
+ path: Type2.String({
33531
33967
  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
- })),
33968
+ }),
33536
33969
  content: Type2.String({ description: "Full file contents to write" })
33537
33970
  });
33538
33971
  var BatchEditParams = Type2.Object({
33539
33972
  oldString: Type2.Optional(Type2.String({ description: "Text to find for a batch find/replace edit" })),
33540
33973
  newString: Type2.Optional(Type2.String({ description: "Replacement text for a batch find/replace edit" })),
33541
33974
  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"),
33975
+ occurrence: optionalInt(1, Number.MAX_SAFE_INTEGER, "1-based occurrence for this batch item (1 = first match)"),
33543
33976
  startLine: optionalInt(1, Number.MAX_SAFE_INTEGER, "1-based start line for a batch line-range edit"),
33544
33977
  endLine: optionalInt(1, Number.MAX_SAFE_INTEGER, "1-based end line for a batch line-range edit"),
33545
33978
  content: Type2.Optional(Type2.String({
@@ -33547,21 +33980,19 @@ var BatchEditParams = Type2.Object({
33547
33980
  }))
33548
33981
  });
33549
33982
  var EditParams = Type2.Object({
33550
- filePath: Type2.Optional(Type2.String({
33983
+ path: Type2.String({
33551
33984
  description: "Path to the file to edit (absolute or relative to project root)"
33985
+ }),
33986
+ symbol: Type2.Optional(Type2.String({ description: "Named symbol to replace (function, class, type)" })),
33987
+ content: Type2.Optional(Type2.String({
33988
+ description: "Replacement content for symbol mode. For whole-file writes, use the `write` tool."
33552
33989
  })),
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
33990
  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."
33991
+ 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
33992
  })),
33563
33993
  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."
33994
+ minItems: 1,
33995
+ description: "Batch edits — non-empty array of { oldString, newString }, { oldString, newString, replaceAll: true }, or { startLine, endLine, content } objects applied atomically."
33565
33996
  }))
33566
33997
  });
33567
33998
  var GrepParams = Type2.Object({
@@ -33569,16 +34000,15 @@ var GrepParams = Type2.Object({
33569
34000
  path: Type2.Optional(Type2.String({
33570
34001
  description: "Path scope (file or directory; absolute or relative to project root)"
33571
34002
  })),
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" }))
34003
+ include: Type2.Optional(Type2.String({ description: "Glob filter for included files (e.g. '*.ts,*.tsx')" }))
33574
34004
  });
33575
34005
  function readPathArg(args) {
33576
- return coerceAliasedStringParam(args.path, args.filePath);
34006
+ return typeof args.path === "string" ? args.path : undefined;
33577
34007
  }
33578
34008
  function mutationFilePathArg(args) {
33579
- return coerceAliasedStringParam(args.filePath, args.path);
34009
+ return typeof args.path === "string" ? args.path : undefined;
33580
34010
  }
33581
- function hasOwn(record2, key) {
34011
+ function hasOwn2(record2, key) {
33582
34012
  return Object.hasOwn(record2, key);
33583
34013
  }
33584
34014
  function validateBatchEdit(edit, index) {
@@ -33589,7 +34019,7 @@ function validateBatchEdit(edit, index) {
33589
34019
  if (typeof record2.oldString === "string") {
33590
34020
  return;
33591
34021
  }
33592
- if (hasOwn(record2, "startLine")) {
34022
+ if (hasOwn2(record2, "startLine")) {
33593
34023
  if (typeof record2.startLine !== "number" || !Number.isInteger(record2.startLine) || record2.startLine < 0) {
33594
34024
  throw new Error(`batch: edit[${index}] 'startLine' must be a positive integer (1-based)`);
33595
34025
  }
@@ -33628,7 +34058,7 @@ function renderReadCall(args, theme, context) {
33628
34058
  }
33629
34059
  function registerHoistedTools(pi, ctx, surface) {
33630
34060
  if (surface.hoistRead) {
33631
- pi.registerTool({
34061
+ pi.registerTool(withPathAliasPreparation({
33632
34062
  name: "read",
33633
34063
  label: "read",
33634
34064
  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 +34071,8 @@ function registerHoistedTools(pi, ctx, surface) {
33641
34071
  if (typeof pathArg !== "string") {
33642
34072
  throw new Error("read: missing required parameter `path`");
33643
34073
  }
34074
+ const startLine = coerceOptionalInt(params.startLine, "startLine", 1, Number.MAX_SAFE_INTEGER);
34075
+ const endLine = coerceOptionalInt(params.endLine, "endLine", 1, Number.MAX_SAFE_INTEGER);
33644
34076
  const offset = coerceOptionalInt(params.offset, "offset", 1, Number.MAX_SAFE_INTEGER);
33645
34077
  const limit = coerceOptionalInt(params.limit, "limit", 1, Number.MAX_SAFE_INTEGER);
33646
34078
  const filePath = await resolvePathArg(extCtx.cwd, pathArg);
@@ -33649,6 +34081,10 @@ function registerHoistedTools(pi, ctx, surface) {
33649
34081
  serverValidatedRead: true
33650
34082
  });
33651
34083
  const rawArgs = { filePath: pathArg };
34084
+ if (startLine !== undefined)
34085
+ rawArgs.startLine = startLine;
34086
+ if (endLine !== undefined)
34087
+ rawArgs.endLine = endLine;
33652
34088
  if (offset !== undefined)
33653
34089
  rawArgs.offset = offset;
33654
34090
  if (limit !== undefined)
@@ -33684,21 +34120,21 @@ PDFs aren't supported on the Pi harness yet.`, response);
33684
34120
  renderCall(args, theme, context) {
33685
34121
  return renderReadCall(args, theme, context);
33686
34122
  }
33687
- });
34123
+ }));
33688
34124
  }
33689
34125
  if (surface.hoistWrite) {
33690
34126
  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({
34127
+ pi.registerTool(withPathAliasPreparation({
33692
34128
  name: "write",
33693
34129
  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)",
34130
+ 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.`,
34131
+ promptSnippet: "Create or overwrite files (uses path; auto-formats)",
33696
34132
  promptGuidelines: ["Use write only for new files or complete rewrites."],
33697
34133
  parameters: WriteParams,
33698
34134
  async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
33699
34135
  const filePathArg = mutationFilePathArg(params);
33700
34136
  if (typeof filePathArg !== "string") {
33701
- throw new Error("write: missing required parameter `filePath`");
34137
+ throw new Error("write: missing required parameter `path`");
33702
34138
  }
33703
34139
  const filePath = await resolvePathArg(extCtx.cwd, filePathArg);
33704
34140
  await assertExternalDirectoryPermission(extCtx, filePath, {
@@ -33711,7 +34147,7 @@ PDFs aren't supported on the Pi harness yet.`, response);
33711
34147
  };
33712
34148
  const response = await callToolCall(bridge, "write", rawArgs, extCtx);
33713
34149
  if (response.success === false) {
33714
- throw new Error(response.text || response.message || "write failed");
34150
+ throw toolErrorFromResponse("write", response);
33715
34151
  }
33716
34152
  return buildMutationResult(response);
33717
34153
  },
@@ -33721,60 +34157,45 @@ PDFs aren't supported on the Pi harness yet.`, response);
33721
34157
  renderResult(result, _options, theme, context) {
33722
34158
  return renderMutationResult(result, theme, context);
33723
34159
  }
33724
- });
34160
+ }));
33725
34161
  }
33726
34162
  if (surface.hoistEdit) {
33727
- pi.registerTool({
34163
+ pi.registerTool(withPathAliasPreparation({
33728
34164
  name: "edit",
33729
34165
  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).",
34166
+ 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).",
34167
+ promptSnippet: "Partial file edits via appendContent, edits[], or symbol plus content (exactly one mode per call).",
33732
34168
  promptGuidelines: [
33733
34169
  "Prefer edit over write when changing part of an existing file.",
33734
34170
  "Use appendContent when adding text to the end of a file.",
33735
34171
  "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."
34172
+ "Include enough surrounding context in an edits[] find/replace item to make the match unique, or set replaceAll/occurrence explicitly."
33737
34173
  ],
33738
34174
  parameters: EditParams,
33739
34175
  async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
33740
34176
  const argsRecord = params;
33741
34177
  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.");
34178
+ 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
34179
  }
33744
34180
  const filePathArg = mutationFilePathArg(params);
33745
34181
  if (typeof filePathArg !== "string") {
33746
- throw new Error("edit: missing required parameter `filePath`");
34182
+ throw new Error("edit: missing required parameter `path`");
33747
34183
  }
33748
- if (params.appendContent === undefined)
34184
+ if (params.edits !== undefined)
33749
34185
  validateBatchEdits(params.edits);
33750
34186
  const filePath = await resolvePathArg(extCtx.cwd, filePathArg);
33751
34187
  await assertExternalDirectoryPermission(extCtx, filePath, {
33752
34188
  restrictToProjectRoot: surface.restrictToProjectRoot
33753
34189
  });
33754
34190
  const bridge = bridgeFor(ctx, extCtx.cwd);
33755
- const rawArgs = { filePath: filePathArg };
33756
- for (const key of ["appendContent", "oldString", "newString"]) {
34191
+ const rawArgs = { path: filePathArg };
34192
+ for (const key of ["appendContent", "edits", "symbol", "content"]) {
33757
34193
  if (argsRecord[key] !== undefined)
33758
34194
  rawArgs[key] = argsRecord[key];
33759
34195
  }
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
34196
  const response = await callToolCall(bridge, "edit", rawArgs, extCtx);
33776
34197
  if (response.success === false) {
33777
- throw new Error(response.text || response.message || "edit failed");
34198
+ throw toolErrorFromResponse("edit", response);
33778
34199
  }
33779
34200
  return buildMutationResult(response);
33780
34201
  },
@@ -33784,10 +34205,10 @@ PDFs aren't supported on the Pi harness yet.`, response);
33784
34205
  renderResult(result, _options, theme, context) {
33785
34206
  return renderMutationResult(result, theme, context);
33786
34207
  }
33787
- });
34208
+ }));
33788
34209
  }
33789
34210
  if (surface.hoistGrep) {
33790
- pi.registerTool({
34211
+ pi.registerTool(withPathAliasPreparation({
33791
34212
  name: "grep",
33792
34213
  label: "grep",
33793
34214
  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 +34230,6 @@ PDFs aren't supported on the Pi harness yet.`, response);
33809
34230
  }
33810
34231
  if (params.include)
33811
34232
  req.include = params.include;
33812
- if (params.caseSensitive !== undefined)
33813
- req.caseSensitive = params.caseSensitive;
33814
34233
  const response = await callToolCall(bridge, "grep", req, extCtx);
33815
34234
  if (response.success === false) {
33816
34235
  throw new Error(response.text || response.message || "grep failed");
@@ -33821,7 +34240,7 @@ PDFs aren't supported on the Pi harness yet.`, response);
33821
34240
  const text = appendSkippedSearchPaths(response.text ?? "", pathSplit?.missing ?? []);
33822
34241
  return textResult(text, response);
33823
34242
  }
33824
- });
34243
+ }));
33825
34244
  }
33826
34245
  }
33827
34246
  function buildMutationResult(response) {
@@ -34165,7 +34584,7 @@ var SearchParams = Type3.Object({
34165
34584
  lang: AstLang,
34166
34585
  paths: Type3.Optional(Type3.Array(Type3.String(), { description: "Paths to search (default: ['.'])" })),
34167
34586
  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" }))
34587
+ contextLines: optionalInt(1, Number.MAX_SAFE_INTEGER, "Number of context lines around each match")
34169
34588
  });
34170
34589
  var ReplaceParams = Type3.Object({
34171
34590
  pattern: Type3.String({ description: "AST pattern with meta-variables" }),
@@ -35056,7 +35475,7 @@ function formatWaitSummary(waited, details) {
35056
35475
  return `Waited ${waited.elapsed_ms}ms; timeout reached without match.`;
35057
35476
  }
35058
35477
  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 }).`;
35478
+ 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
35479
  }
35061
35480
  const exit = typeof details.exit_code === "number" ? `, exit ${details.exit_code}` : "";
35062
35481
  return `Waited ${waited.elapsed_ms}ms; task exited (${details.status}${exit}).`;
@@ -35197,7 +35616,7 @@ function renderConflictToolResult(result, theme, context) {
35197
35616
  return renderConflictResult(collectTextContent(result), theme, context);
35198
35617
  }
35199
35618
  function registerConflictsTool(pi, ctx) {
35200
- pi.registerTool({
35619
+ pi.registerTool(withPathAliasPreparation({
35201
35620
  name: "aft_conflicts",
35202
35621
  label: "conflicts",
35203
35622
  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 +35643,7 @@ function registerConflictsTool(pi, ctx) {
35224
35643
  renderResult(result, _options, theme, context) {
35225
35644
  return renderConflictToolResult(result, theme, context);
35226
35645
  }
35227
- });
35646
+ }));
35228
35647
  }
35229
35648
 
35230
35649
  // src/tools/fs.ts
@@ -35239,7 +35658,7 @@ var DeleteParams = Type6.Object({
35239
35658
  }))
35240
35659
  });
35241
35660
  var MoveParams = Type6.Object({
35242
- filePath: Type6.String({
35661
+ path: Type6.String({
35243
35662
  description: "Source file path to move (absolute or relative to project root)"
35244
35663
  }),
35245
35664
  destination: Type6.String({
@@ -35263,7 +35682,7 @@ function renderFsCall(toolName, args, theme, context) {
35263
35682
  return renderToolCall("delete", summary, theme, context);
35264
35683
  }
35265
35684
  const moveArgs = args;
35266
- return renderToolCall("move", `${accentPath(theme, moveArgs.filePath)} ${theme.fg("muted", "→")} ${accentPath(theme, moveArgs.destination)}`, theme, context);
35685
+ return renderToolCall("move", `${accentPath(theme, moveArgs.path)} ${theme.fg("muted", "→")} ${accentPath(theme, moveArgs.destination)}`, theme, context);
35267
35686
  }
35268
35687
  function renderFsResult(toolName, args, result, theme, context) {
35269
35688
  if (context.isError) {
@@ -35289,14 +35708,14 @@ function renderFsResult(toolName, args, result, theme, context) {
35289
35708
  }
35290
35709
  const moveArgs = args;
35291
35710
  return renderSections([
35292
- `${theme.fg("success", "✓ moved")} ${theme.fg("accent", shortenPath3(moveArgs.filePath))}`,
35711
+ `${theme.fg("success", "✓ moved")} ${theme.fg("accent", shortenPath3(moveArgs.path ?? ""))}`,
35293
35712
  `${theme.fg("muted", "to")} ${theme.fg("accent", shortenPath3(moveArgs.destination))}`
35294
35713
  ], context);
35295
35714
  }
35296
35715
  function registerFsTools(pi, ctx, surface) {
35297
35716
  const backupsDisabled = ctx.config.backup?.enabled === false;
35298
35717
  if (surface.delete) {
35299
- pi.registerTool({
35718
+ pi.registerTool(withPathAliasPreparation({
35300
35719
  name: "aft_delete",
35301
35720
  label: "delete",
35302
35721
  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 +35759,16 @@ function registerFsTools(pi, ctx, surface) {
35340
35759
  renderResult(result, _options, theme, context) {
35341
35760
  return renderFsResult("aft_delete", context.args, result, theme, context);
35342
35761
  }
35343
- });
35762
+ }));
35344
35763
  }
35345
35764
  if (surface.move) {
35346
- pi.registerTool({
35765
+ pi.registerTool(withPathAliasPreparation({
35347
35766
  name: "aft_move",
35348
35767
  label: "move",
35349
35768
  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
35769
  parameters: MoveParams,
35351
35770
  async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
35352
- const filePath = await resolvePathArg(extCtx.cwd, params.filePath);
35771
+ const filePath = await resolvePathArg(extCtx.cwd, params.path);
35353
35772
  const destination = await resolvePathArg(extCtx.cwd, params.destination);
35354
35773
  const checked = new Set([filePath, destination]);
35355
35774
  for (const file2 of checked) {
@@ -35359,7 +35778,7 @@ function registerFsTools(pi, ctx, surface) {
35359
35778
  }
35360
35779
  const bridge = bridgeFor(ctx, extCtx.cwd);
35361
35780
  const response = await callToolCall(bridge, "move", {
35362
- filePath: params.filePath,
35781
+ filePath: params.path,
35363
35782
  destination: params.destination
35364
35783
  }, extCtx);
35365
35784
  if (response.success === false) {
@@ -35373,7 +35792,7 @@ function registerFsTools(pi, ctx, surface) {
35373
35792
  renderResult(result, _options, theme, context) {
35374
35793
  return renderFsResult("aft_move", context.args, result, theme, context);
35375
35794
  }
35376
- });
35795
+ }));
35377
35796
  }
35378
35797
  }
35379
35798
 
@@ -35382,7 +35801,9 @@ import { StringEnum as StringEnum2 } from "@earendil-works/pi-ai";
35382
35801
  import { Type as Type7 } from "typebox";
35383
35802
  var ImportParams = Type7.Object({
35384
35803
  op: StringEnum2(["add", "remove", "organize"], { description: "Import operation" }),
35385
- filePath: Type7.String({ description: "Path to the file (absolute or relative to project root)" }),
35804
+ path: Type7.String({
35805
+ description: "Path to the file (absolute or relative to project root)"
35806
+ }),
35386
35807
  module: Type7.Optional(Type7.String({ description: "Module path (required for add/remove), e.g. 'react', './utils'" })),
35387
35808
  names: Type7.Optional(Type7.Array(Type7.String(), {
35388
35809
  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 +35833,7 @@ function buildImportSections(args, payload, theme) {
35412
35833
  const groups = asRecords2(response.groups);
35413
35834
  const groupText = groups.length > 0 ? groups.map((group) => `${asString2(group.name) ?? "unknown"}: ${asNumber2(group.count) ?? 0}`).join(" · ") : "No imports found";
35414
35835
  return [
35415
- `${theme.fg("success", "organized")} ${theme.fg("accent", asString2(response.file) ?? args.filePath)}`,
35836
+ `${theme.fg("success", "organized")} ${theme.fg("accent", asString2(response.file) ?? args.path ?? "")}`,
35416
35837
  `${theme.fg("muted", "groups")} ${groupText}`,
35417
35838
  `${theme.fg("muted", "duplicates removed")} ${asNumber2(response.removed_duplicates) ?? 0}`
35418
35839
  ];
@@ -35422,7 +35843,7 @@ function buildImportSections(args, payload, theme) {
35422
35843
  const status = response.already_present === true ? theme.fg("warning", "already present") : theme.fg("success", "added");
35423
35844
  return [
35424
35845
  `${status} ${theme.fg("accent", moduleName2)}`,
35425
- `${theme.fg("muted", "file")} ${theme.fg("accent", asString2(response.file) ?? args.filePath)}`,
35846
+ `${theme.fg("muted", "file")} ${theme.fg("accent", asString2(response.file) ?? args.path ?? "")}`,
35426
35847
  `${theme.fg("muted", "group")} ${asString2(response.group) ?? "—"}`
35427
35848
  ];
35428
35849
  }
@@ -35431,14 +35852,14 @@ function buildImportSections(args, payload, theme) {
35431
35852
  const removeStatus = didRemove ? `${theme.fg("success", "removed")} ${theme.fg("accent", moduleName)}` : `${theme.fg("warning", "not present")} ${theme.fg("accent", moduleName)}`;
35432
35853
  return [
35433
35854
  removeStatus,
35434
- `${theme.fg("muted", "file")} ${theme.fg("accent", asString2(response.file) ?? args.filePath)}`,
35855
+ `${theme.fg("muted", "file")} ${theme.fg("accent", asString2(response.file) ?? args.path ?? "")}`,
35435
35856
  args.removeName ? `${theme.fg("muted", "name")} ${args.removeName}` : `${theme.fg("muted", "scope")} entire import`
35436
35857
  ];
35437
35858
  }
35438
35859
  function renderImportCall(args, theme, context) {
35439
35860
  const summary = [
35440
35861
  theme.fg("accent", args.op),
35441
- accentPath(theme, args.filePath),
35862
+ accentPath(theme, args.path ?? ""),
35442
35863
  args.module ? theme.fg("toolOutput", args.module) : undefined
35443
35864
  ].filter(Boolean).join(" ");
35444
35865
  return renderToolCall("import", summary, theme, context);
@@ -35451,7 +35872,7 @@ function renderImportResult(result, args, theme, context) {
35451
35872
  }
35452
35873
  function registerImportTools(pi, ctx) {
35453
35874
  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({
35875
+ pi.registerTool(withPathAliasPreparation({
35455
35876
  name: "aft_import",
35456
35877
  label: "import",
35457
35878
  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 +35881,7 @@ function registerImportTools(pi, ctx) {
35460
35881
  if ((params.op === "add" || params.op === "remove") && isEmptyParam(params.module)) {
35461
35882
  throw new Error(`op='${params.op}' requires 'module'`);
35462
35883
  }
35463
- const filePath = await resolvePathArg(extCtx.cwd, params.filePath);
35884
+ const filePath = await resolvePathArg(extCtx.cwd, params.path);
35464
35885
  await assertExternalDirectoryPermission(extCtx, filePath, {
35465
35886
  restrictToProjectRoot: ctx.config.restrict_to_project_root ?? false
35466
35887
  });
@@ -35498,7 +35919,7 @@ function registerImportTools(pi, ctx) {
35498
35919
  renderResult(result, _options, theme, context) {
35499
35920
  return renderImportResult(result, context.args, theme, context);
35500
35921
  }
35501
- });
35922
+ }));
35502
35923
  }
35503
35924
 
35504
35925
  // src/tools/inspect.ts
@@ -35574,7 +35995,7 @@ function diagnosticsSummaryPart(summary) {
35574
35995
  const counts = `${errors3 ?? 0} errors/${warnings ?? 0} warnings/${info ?? 0} info/${hints ?? 0} hints`;
35575
35996
  const status = asString2(section.status);
35576
35997
  if (status === "pending") {
35577
- return hasCounts ? `diagnostics ${counts} so far — still pending (servers: ${diagnosticsServerSummary(section)})` : `diagnostics pending (servers: ${diagnosticsServerSummary(section)})`;
35998
+ 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
35999
  }
35579
36000
  if (status === "incomplete") {
35580
36001
  return hasCounts ? `diagnostics ${counts} (incomplete — servers: ${diagnosticsServerSummary(section)})` : `diagnostics unavailable (status incomplete; servers: ${diagnosticsServerSummary(section)})`;
@@ -35727,7 +36148,7 @@ function buildInspectSections(payload, theme) {
35727
36148
  ].filter((part) => Boolean(part));
35728
36149
  const sections = [theme.fg("accent", parts.join(" · "))];
35729
36150
  if (stale > 0 || pending > 0) {
35730
- sections.push(theme.fg("warning", `scanner state: ${stale} stale · ${pending} pending`));
36151
+ 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
36152
  }
35732
36153
  const topPreview = tier2TopPreview(summary, theme);
35733
36154
  if (topPreview)
@@ -35803,7 +36224,7 @@ function navigateParamsSchema() {
35803
36224
  op: StringEnum3(["call_tree", "callers", "trace_to", "trace_to_symbol", "impact", "trace_data"], {
35804
36225
  description: "Navigation operation"
35805
36226
  }),
35806
- filePath: Type9.String({
36227
+ path: Type9.String({
35807
36228
  description: "Source file containing the symbol (absolute or relative to project root)"
35808
36229
  }),
35809
36230
  symbol: Type9.String({ description: "Name of the symbol to analyze" }),
@@ -35812,7 +36233,7 @@ function navigateParamsSchema() {
35812
36233
  toSymbol: Type9.Optional(Type9.String({
35813
36234
  description: "Target symbol for trace_to_symbol; the returned path ends here"
35814
36235
  })),
35815
- toFile: Type9.Optional(Type9.String({
36236
+ toPath: Type9.Optional(Type9.String({
35816
36237
  description: "Optional target file for trace_to_symbol; required when toSymbol exists in multiple files"
35817
36238
  })),
35818
36239
  includeTests: Type9.Optional(Type9.Boolean({
@@ -35834,7 +36255,7 @@ function buildNavigateSections(args, payload, theme) {
35834
36255
  function renderNavigateCall(args, theme, context) {
35835
36256
  const summary = [
35836
36257
  theme.fg("accent", args.op),
35837
- accentPath(theme, args.filePath),
36258
+ accentPath(theme, args.path),
35838
36259
  theme.fg("toolOutput", args.symbol),
35839
36260
  args.toSymbol ? theme.fg("toolOutput", `→ ${args.toSymbol}`) : undefined
35840
36261
  ].filter(Boolean).join(" ");
@@ -35846,14 +36267,14 @@ function renderNavigateResult(result, args, theme, context) {
35846
36267
  return renderSections(buildNavigateSections(args, extractStructuredPayload(result), theme), context);
35847
36268
  }
35848
36269
  function registerNavigateTool(pi, ctx) {
35849
- pi.registerTool({
36270
+ pi.registerTool(withPathAliasPreparation({
35850
36271
  name: "aft_callgraph",
35851
36272
  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.",
36273
+ 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
36274
  parameters: navigateParamsSchema(),
35854
36275
  async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
35855
- if (isEmptyParam(params.filePath)) {
35856
- throw new Error(`op='${params.op}' requires a \`filePath\``);
36276
+ if (isEmptyParam(params.path)) {
36277
+ throw new Error(`op='${params.op}' requires a \`path\``);
35857
36278
  }
35858
36279
  if (isEmptyParam(params.symbol)) {
35859
36280
  throw new Error(`op='${params.op}' requires a \`symbol\``);
@@ -35864,8 +36285,8 @@ function registerNavigateTool(pi, ctx) {
35864
36285
  if (params.op === "trace_to_symbol" && isEmptyParam(params.toSymbol)) {
35865
36286
  throw new Error("op='trace_to_symbol' requires a `toSymbol`");
35866
36287
  }
35867
- const filePath = await resolvePathArg(extCtx.cwd, params.filePath);
35868
- const toFile = !isEmptyParam(params.toFile) ? await resolvePathArg(extCtx.cwd, params.toFile) : undefined;
36288
+ const filePath = await resolvePathArg(extCtx.cwd, params.path);
36289
+ const toFile = !isEmptyParam(params.toPath) ? await resolvePathArg(extCtx.cwd, params.toPath) : undefined;
35869
36290
  const checked = new Set;
35870
36291
  for (const target of [filePath, ...toFile !== undefined ? [toFile] : []]) {
35871
36292
  if (checked.has(target))
@@ -35878,7 +36299,7 @@ function registerNavigateTool(pi, ctx) {
35878
36299
  const bridge = bridgeFor(ctx, extCtx.cwd);
35879
36300
  const rawArgs = {
35880
36301
  op: params.op,
35881
- filePath: params.filePath,
36302
+ filePath: params.path,
35882
36303
  symbol: params.symbol
35883
36304
  };
35884
36305
  const depth = coerceOptionalInt(params.depth, "depth", 1, Number.MAX_SAFE_INTEGER);
@@ -35888,8 +36309,8 @@ function registerNavigateTool(pi, ctx) {
35888
36309
  rawArgs.expression = params.expression;
35889
36310
  if (!isEmptyParam(params.toSymbol))
35890
36311
  rawArgs.toSymbol = params.toSymbol;
35891
- if (!isEmptyParam(params.toFile))
35892
- rawArgs.toFile = params.toFile;
36312
+ if (!isEmptyParam(params.toPath))
36313
+ rawArgs.toFile = params.toPath;
35893
36314
  if (!isEmptyParam(params.includeTests))
35894
36315
  rawArgs.includeTests = coerceBoolean(params.includeTests);
35895
36316
  if (!isEmptyParam(params.includeUnresolved))
@@ -35914,7 +36335,7 @@ function registerNavigateTool(pi, ctx) {
35914
36335
  renderResult(result, _options, theme, context) {
35915
36336
  return renderNavigateResult(result, context.args, theme, context);
35916
36337
  }
35917
- });
36338
+ }));
35918
36339
  }
35919
36340
 
35920
36341
  // src/tools/reading.ts
@@ -35931,21 +36352,21 @@ var OutlineParams = Type10.Object({
35931
36352
  }))
35932
36353
  });
35933
36354
  var ZoomTarget = Type10.Object({
35934
- filePath: Type10.String({ description: "Path to file (absolute or project-relative)" }),
36355
+ path: Type10.String({ description: "Path to file (absolute or project-relative)" }),
35935
36356
  symbol: Type10.String({ description: "Symbol name in that file" })
35936
36357
  });
35937
36358
  var ZoomParams = Type10.Object({
35938
- filePath: Type10.Optional(Type10.String({ description: "Path to file (absolute or project-relative)" })),
36359
+ path: Type10.Optional(Type10.String({ description: "Path to file (absolute or project-relative)" })),
35939
36360
  url: Type10.Optional(Type10.String({
35940
36361
  description: "HTTP/HTTPS URL of an HTML or Markdown document to fetch and zoom into"
35941
36362
  })),
35942
36363
  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."
36364
+ 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
36365
  })),
35945
36366
  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."
36367
+ description: "Cross-file batch: `{ path, symbol }` or an array of them. Mutually exclusive with path/url/symbols."
35947
36368
  })),
35948
- contextLines: Type10.Optional(Type10.Number({ description: "Lines of context before/after (default: 3)" })),
36369
+ contextLines: optionalInt(1, Number.MAX_SAFE_INTEGER, "Lines of context before/after (default: 3)"),
35949
36370
  callgraph: Type10.Optional(Type10.Boolean({
35950
36371
  description: "Include call-graph annotations (calls-out / called-by within the same file). Default false; off keeps zoom output minimal."
35951
36372
  }))
@@ -35966,7 +36387,7 @@ async function assertReadPathPermissions(extCtx, ctx, paths) {
35966
36387
  }
35967
36388
  }
35968
36389
  function zoomTargetLabel(args) {
35969
- return args.filePath ?? args.url ?? "(no target)";
36390
+ return args.path ?? args.url ?? "(no target)";
35970
36391
  }
35971
36392
  function zoomExtraCallSites(call) {
35972
36393
  const extraCount = call.extra_count;
@@ -36087,7 +36508,7 @@ function renderZoomResult(result, args, theme, context) {
36087
36508
  }
36088
36509
  function registerReadingTools(pi, ctx, surface) {
36089
36510
  if (surface.outline) {
36090
- pi.registerTool({
36511
+ pi.registerTool(withPathAliasPreparation({
36091
36512
  name: "aft_outline",
36092
36513
  label: "outline",
36093
36514
  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 +36551,17 @@ Pass a single \`target\`:
36130
36551
  if (response.success === false) {
36131
36552
  throw new Error(response.text || response.message || "outline failed");
36132
36553
  }
36133
- return textResult(response.text, response);
36554
+ let text = typeof response.text === "string" ? response.text : "";
36555
+ const trimmed = text.trim();
36556
+ if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
36557
+ try {
36558
+ const parsed = JSON.parse(text);
36559
+ if (typeof parsed.text === "string") {
36560
+ text = parsed.text;
36561
+ }
36562
+ } catch {}
36563
+ }
36564
+ return textResult(text, response);
36134
36565
  },
36135
36566
  renderCall(args, theme, context) {
36136
36567
  return renderOutlineCall(args, theme, context);
@@ -36138,13 +36569,13 @@ Pass a single \`target\`:
36138
36569
  renderResult(result, _options, theme, context) {
36139
36570
  return renderOutlineResult(result, theme, context);
36140
36571
  }
36141
- });
36572
+ }));
36142
36573
  }
36143
36574
  if (surface.zoom) {
36144
- pi.registerTool({
36575
+ pi.registerTool(withPathAliasPreparation({
36145
36576
  name: "aft_zoom",
36146
36577
  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.",
36578
+ 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
36579
  parameters: ZoomParams,
36149
36580
  async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
36150
36581
  const bridge = bridgeFor(ctx, extCtx.cwd);
@@ -36154,7 +36585,7 @@ Pass a single \`target\`:
36154
36585
  const entryEmpty = (entry) => {
36155
36586
  if (!entry || typeof entry !== "object")
36156
36587
  return true;
36157
- const fp = entry.filePath;
36588
+ const fp = entry.path;
36158
36589
  const sym = entry.symbol;
36159
36590
  const fpEmpty = typeof fp !== "string" || fp.length === 0;
36160
36591
  const symEmpty = typeof sym !== "string" || sym.length === 0;
@@ -36164,31 +36595,33 @@ Pass a single \`target\`:
36164
36595
  return !t.every(entryEmpty);
36165
36596
  return !entryEmpty(t);
36166
36597
  };
36167
- const hasFilePath = !isEmptyParam(params.filePath);
36598
+ const hasPath2 = !isEmptyParam(params.path);
36168
36599
  const hasUrl = !isEmptyParam(params.url);
36169
36600
  const hasTargets = hasTargetsProvided(params.targets);
36170
36601
  const hasSymbols = !isEmptyParam(params.symbols);
36171
36602
  const wantCallgraph = coerceBoolean(params.callgraph);
36172
36603
  const contextLines = coerceOptionalInt(params.contextLines, "contextLines", 1, Number.MAX_SAFE_INTEGER);
36173
36604
  if (hasTargets) {
36174
- if (hasFilePath || hasUrl || hasSymbols) {
36175
- throw new Error("'targets' is mutually exclusive with 'filePath', 'url', and 'symbols'");
36605
+ if (hasPath2 || hasUrl || hasSymbols) {
36606
+ throw new Error("'targets' is mutually exclusive with 'path', 'url', and 'symbols'");
36176
36607
  }
36177
36608
  const targets = Array.isArray(params.targets) ? params.targets : [params.targets];
36178
36609
  if (targets.length === 0) {
36179
36610
  throw new Error("'targets' must be a non-empty object or array");
36180
36611
  }
36181
36612
  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`);
36613
+ if (!entry || typeof entry.path !== "string" || entry.path.length === 0) {
36614
+ throw new Error(`targets[${i}].path must be a non-empty string`);
36184
36615
  }
36185
36616
  if (typeof entry.symbol !== "string" || entry.symbol.length === 0) {
36186
36617
  throw new Error(`targets[${i}].symbol must be a non-empty string`);
36187
36618
  }
36188
36619
  }
36189
- const resolvedTargets = await Promise.all(targets.map((t) => resolvePathArg(extCtx.cwd, t.filePath)));
36620
+ const resolvedTargets = await Promise.all(targets.map((t) => resolvePathArg(extCtx.cwd, t.path)));
36190
36621
  await assertReadPathPermissions(extCtx, ctx, resolvedTargets);
36191
- const rawArgs2 = { targets };
36622
+ const rawArgs2 = {
36623
+ targets: targets.map((target) => ({ filePath: target.path, symbol: target.symbol }))
36624
+ };
36192
36625
  if (contextLines !== undefined)
36193
36626
  rawArgs2.contextLines = contextLines;
36194
36627
  if (wantCallgraph)
@@ -36199,17 +36632,17 @@ Pass a single \`target\`:
36199
36632
  }
36200
36633
  return textResult(response2.text, response2);
36201
36634
  }
36202
- if (!hasFilePath && !hasUrl) {
36203
- throw new Error("Provide exactly one of 'filePath', 'url', or 'targets'");
36635
+ if (!hasPath2 && !hasUrl) {
36636
+ throw new Error("Provide exactly one of 'path', 'url', or 'targets'");
36204
36637
  }
36205
- if (hasFilePath && hasUrl) {
36206
- throw new Error("Provide exactly ONE of 'filePath' or 'url' — not both");
36638
+ if (hasPath2 && hasUrl) {
36639
+ throw new Error("Provide exactly ONE of 'path' or 'url' — not both");
36207
36640
  }
36208
36641
  if (!hasUrl) {
36209
- const file2 = await resolvePathArg(extCtx.cwd, params.filePath);
36642
+ const file2 = await resolvePathArg(extCtx.cwd, params.path);
36210
36643
  await assertReadPathPermissions(extCtx, ctx, file2);
36211
36644
  }
36212
- const rawArgs = hasUrl ? { url: params.url } : { filePath: params.filePath };
36645
+ const rawArgs = hasUrl ? { url: params.url } : { filePath: params.path };
36213
36646
  if (hasSymbols)
36214
36647
  rawArgs.symbols = params.symbols;
36215
36648
  if (contextLines !== undefined)
@@ -36228,7 +36661,7 @@ Pass a single \`target\`:
36228
36661
  renderResult(result, _options, theme, context) {
36229
36662
  return renderZoomResult(result, context.args, theme, context);
36230
36663
  }
36231
- });
36664
+ }));
36232
36665
  }
36233
36666
  }
36234
36667
 
@@ -36237,7 +36670,7 @@ import { StringEnum as StringEnum4 } from "@earendil-works/pi-ai";
36237
36670
  import { Type as Type11 } from "typebox";
36238
36671
  var RefactorParams = Type11.Object({
36239
36672
  op: StringEnum4(["move", "extract", "inline"], { description: "Refactoring operation" }),
36240
- filePath: Type11.String({
36673
+ path: Type11.String({
36241
36674
  description: "Source file (absolute or relative to project root)"
36242
36675
  }),
36243
36676
  symbol: Type11.Optional(Type11.String({ description: "Symbol name (for move, inline)" })),
@@ -36265,14 +36698,14 @@ function buildRefactorSections(args, payload, theme) {
36265
36698
  if (args.op === "extract") {
36266
36699
  return [
36267
36700
  `${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))}`,
36701
+ `${theme.fg("muted", "file")} ${theme.fg("accent", shortenPath3(asString2(response.file) ?? args.path ?? ""))}`,
36269
36702
  `${theme.fg("muted", "params")} ${Array.isArray(response.parameters) ? response.parameters.join(", ") || "none" : "none"}`,
36270
36703
  `${theme.fg("muted", "return type")} ${asString2(response.return_type) ?? "unknown"}`
36271
36704
  ];
36272
36705
  }
36273
36706
  return [
36274
36707
  `${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))}`,
36708
+ `${theme.fg("muted", "file")} ${theme.fg("accent", shortenPath3(asString2(response.file) ?? args.path ?? ""))}`,
36276
36709
  `${theme.fg("muted", "context")} ${asString2(response.call_context) ?? "unknown"}`,
36277
36710
  `${theme.fg("muted", "substitutions")} ${asNumber2(response.substitutions) ?? 0}`
36278
36711
  ];
@@ -36280,7 +36713,7 @@ function buildRefactorSections(args, payload, theme) {
36280
36713
  function renderRefactorCall(args, theme, context) {
36281
36714
  const summary = [
36282
36715
  theme.fg("accent", args.op),
36283
- accentPath(theme, args.filePath),
36716
+ accentPath(theme, args.path),
36284
36717
  args.symbol ? theme.fg("toolOutput", args.symbol) : undefined
36285
36718
  ].filter(Boolean).join(" ");
36286
36719
  return renderToolCall("refactor", summary, theme, context);
@@ -36291,7 +36724,7 @@ function renderRefactorResult(result, args, theme, context) {
36291
36724
  return renderSections(buildRefactorSections(args, extractStructuredPayload(result), theme), context);
36292
36725
  }
36293
36726
  function registerRefactorTool(pi, ctx) {
36294
- pi.registerTool({
36727
+ pi.registerTool(withPathAliasPreparation({
36295
36728
  name: "aft_refactor",
36296
36729
  label: "refactor",
36297
36730
  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 +36751,7 @@ function registerRefactorTool(pi, ctx) {
36318
36751
  if (params.op === "inline" && callSiteLine === undefined) {
36319
36752
  throw new Error("'callSiteLine' is required for 'inline' op");
36320
36753
  }
36321
- const filePath = await resolvePathArg(extCtx.cwd, params.filePath);
36754
+ const filePath = await resolvePathArg(extCtx.cwd, params.path);
36322
36755
  const destination = !isEmptyParam(params.destination) ? await resolvePathArg(extCtx.cwd, params.destination) : undefined;
36323
36756
  const permissionTargets = params.op === "move" && destination !== undefined ? [filePath, destination] : [filePath];
36324
36757
  const checked = new Set;
@@ -36358,7 +36791,7 @@ function registerRefactorTool(pi, ctx) {
36358
36791
  renderResult(result, _options, theme, context) {
36359
36792
  return renderRefactorResult(result, context.args, theme, context);
36360
36793
  }
36361
- });
36794
+ }));
36362
36795
  }
36363
36796
 
36364
36797
  // src/tools/safety.ts
@@ -36371,7 +36804,7 @@ var SafetyParams = Type12.Object({
36371
36804
  op: StringEnum5(["undo", "history", "checkpoint", "restore", "list"], {
36372
36805
  description: "Safety operation"
36373
36806
  }),
36374
- filePath: Type12.Optional(Type12.String({
36807
+ path: Type12.Optional(Type12.String({
36375
36808
  description: "File path (required for history, optional for undo). Absolute or relative to project root."
36376
36809
  })),
36377
36810
  name: Type12.Optional(Type12.String({ description: "Checkpoint name (required for checkpoint, restore)" })),
@@ -36391,14 +36824,14 @@ function buildSafetySections(args, payload, theme) {
36391
36824
  ];
36392
36825
  }
36393
36826
  return [
36394
- `${theme.fg("success", "restored")} ${theme.fg("accent", shortenPath3(asString2(response.path) ?? args.filePath ?? "(file)"))}`,
36827
+ `${theme.fg("success", "restored")} ${theme.fg("accent", shortenPath3(asString2(response.path) ?? args.path ?? "(file)"))}`,
36395
36828
  `${theme.fg("muted", "backup")} ${asString2(response.backup_id) ?? "—"}`
36396
36829
  ];
36397
36830
  }
36398
36831
  if (args.op === "history") {
36399
36832
  const entries = asRecords2(response.entries);
36400
36833
  const sections2 = [
36401
- theme.fg("accent", shortenPath3(asString2(response.file) ?? args.filePath ?? "(file)"))
36834
+ theme.fg("accent", shortenPath3(asString2(response.file) ?? args.path ?? "(file)"))
36402
36835
  ];
36403
36836
  if (entries.length === 0) {
36404
36837
  sections2.push(theme.fg("muted", "No history entries."));
@@ -36448,7 +36881,7 @@ ${skipped.map((entry) => ` ↳ ${shortenPath3(asString2(entry.file) ?? "(file)"
36448
36881
  return sections;
36449
36882
  }
36450
36883
  function renderSafetyCall(args, theme, context) {
36451
- const target = args.filePath ?? args.name;
36884
+ const target = args.path ?? args.name;
36452
36885
  const summary = [theme.fg("accent", args.op), target ? accentPath(theme, target) : undefined].filter(Boolean).join(" ");
36453
36886
  return renderToolCall("safety", summary, theme, context);
36454
36887
  }
@@ -36458,19 +36891,19 @@ function renderSafetyResult(result, args, theme, context) {
36458
36891
  return renderSections(buildSafetySections(args, extractStructuredPayload(result), theme), context);
36459
36892
  }
36460
36893
  function registerSafetyTool(pi, ctx) {
36461
- pi.registerTool({
36894
+ pi.registerTool(withPathAliasPreparation({
36462
36895
  name: "aft_safety",
36463
36896
  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.",
36897
+ 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
36898
  parameters: SafetyParams,
36466
36899
  async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
36467
- if (params.op === "history" && !params.filePath) {
36468
- throw new Error(`op='${params.op}' requires 'filePath'`);
36900
+ if (params.op === "history" && !params.path) {
36901
+ throw new Error(`op='${params.op}' requires 'path'`);
36469
36902
  }
36470
36903
  if ((params.op === "checkpoint" || params.op === "restore") && !params.name) {
36471
36904
  throw new Error(`op='${params.op}' requires 'name'`);
36472
36905
  }
36473
- const filePath = params.filePath ? await resolvePathArg(extCtx.cwd, params.filePath) : undefined;
36906
+ const filePath = params.path ? await resolvePathArg(extCtx.cwd, params.path) : undefined;
36474
36907
  const fileInputs = coerceStringArray(params.files);
36475
36908
  const files = fileInputs.length > 0 ? await Promise.all(fileInputs.map((file2) => resolvePathArg(extCtx.cwd, file2))) : undefined;
36476
36909
  const bridge = bridgeFor(ctx, extCtx.cwd);
@@ -36527,7 +36960,7 @@ function registerSafetyTool(pi, ctx) {
36527
36960
  renderResult(result, _options, theme, context) {
36528
36961
  return renderSafetyResult(result, context.args, theme, context);
36529
36962
  }
36530
- });
36963
+ }));
36531
36964
  }
36532
36965
 
36533
36966
  // src/tools/semantic.ts
@@ -36554,14 +36987,6 @@ var SearchParams2 = Type13.Object({
36554
36987
  default: 10,
36555
36988
  description: "Maximum number of results (default: 10, max: 100)"
36556
36989
  })),
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
36990
  includeTests: Type13.Optional(Type13.Boolean({
36566
36991
  default: false,
36567
36992
  description: "Include test files (*.test.*, *_test.rs, __tests__/, …) plus test-support, fixture, mock, snapshot, and corpus files. Defaults to false."
@@ -36653,13 +37078,11 @@ function renderSemanticResult(result, args, theme, context) {
36653
37078
  return renderSections(buildSemanticSections(args, extractStructuredPayload(result), theme), context);
36654
37079
  }
36655
37080
  function registerSemanticTool(pi, ctx) {
36656
- pi.registerTool({
37081
+ pi.registerTool(withPathAliasPreparation({
36657
37082
  name: "aft_search",
36658
37083
  label: "search",
36659
37084
  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."
37085
+ "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
37086
  ].join(`
36664
37087
  `),
36665
37088
  parameters: SearchParams2,
@@ -36671,8 +37094,6 @@ function registerSemanticTool(pi, ctx) {
36671
37094
  const req = { query: params.query };
36672
37095
  if (params.topK !== undefined)
36673
37096
  req.topK = params.topK;
36674
- if (params.hint !== undefined)
36675
- req.hint = params.hint;
36676
37097
  if (params.includeTests !== undefined)
36677
37098
  req.includeTests = params.includeTests;
36678
37099
  if (params.path !== undefined)
@@ -36689,7 +37110,98 @@ function registerSemanticTool(pi, ctx) {
36689
37110
  renderResult(result, _options, theme, context) {
36690
37111
  return renderSemanticResult(result, context.args, theme, context);
36691
37112
  }
36692
- });
37113
+ }));
37114
+ }
37115
+
37116
+ // src/tool-registration.ts
37117
+ var ALL_ONLY_TOOLS = new Set(["aft_callgraph", "aft_delete", "aft_move", "aft_refactor"]);
37118
+ function resolvePiToolSurface(config2) {
37119
+ const surface = config2.tool_surface ?? "recommended";
37120
+ const disabled = new Set(config2.disabled_tools ?? []);
37121
+ const ok = (name) => !disabled.has(name);
37122
+ const allOnly = (name) => ALL_ONLY_TOOLS.has(name) && ok(name);
37123
+ const restrictToProjectRoot = config2.restrict_to_project_root ?? false;
37124
+ if (surface === "minimal") {
37125
+ return {
37126
+ hoistBash: ok("bash"),
37127
+ hoistRead: false,
37128
+ hoistWrite: false,
37129
+ hoistEdit: false,
37130
+ hoistGrep: false,
37131
+ restrictToProjectRoot,
37132
+ outline: ok("aft_outline"),
37133
+ zoom: ok("aft_zoom"),
37134
+ semantic: false,
37135
+ inspect: false,
37136
+ navigate: false,
37137
+ conflicts: false,
37138
+ importTool: false,
37139
+ safety: ok("aft_safety"),
37140
+ delete: false,
37141
+ move: false,
37142
+ astSearch: false,
37143
+ astReplace: false,
37144
+ refactor: false
37145
+ };
37146
+ }
37147
+ const base = {
37148
+ hoistBash: ok("bash"),
37149
+ hoistRead: ok("read"),
37150
+ hoistWrite: ok("write"),
37151
+ hoistEdit: ok("edit"),
37152
+ hoistGrep: ok("grep") && config2.search_index === true,
37153
+ restrictToProjectRoot,
37154
+ outline: ok("aft_outline"),
37155
+ zoom: ok("aft_zoom"),
37156
+ semantic: ok("aft_search") && config2.semantic_search === true,
37157
+ inspect: ok("aft_inspect") && config2.inspect?.enabled !== false,
37158
+ navigate: false,
37159
+ conflicts: ok("aft_conflicts"),
37160
+ importTool: ok("aft_import"),
37161
+ safety: ok("aft_safety"),
37162
+ delete: false,
37163
+ move: false,
37164
+ astSearch: ok("ast_grep_search"),
37165
+ astReplace: ok("ast_grep_replace"),
37166
+ refactor: false
37167
+ };
37168
+ if (surface === "all") {
37169
+ return {
37170
+ ...base,
37171
+ navigate: allOnly("aft_callgraph"),
37172
+ delete: allOnly("aft_delete"),
37173
+ move: allOnly("aft_move"),
37174
+ refactor: allOnly("aft_refactor")
37175
+ };
37176
+ }
37177
+ return base;
37178
+ }
37179
+ function registerPiToolSurface(pi, ctx, surface) {
37180
+ if (surface.hoistBash && resolveBashConfig(ctx.config).enabled) {
37181
+ registerBashTool(pi, ctx, surface.semantic);
37182
+ }
37183
+ registerHoistedTools(pi, ctx, surface);
37184
+ if (surface.outline || surface.zoom) {
37185
+ registerReadingTools(pi, ctx, surface);
37186
+ }
37187
+ if (surface.semantic)
37188
+ registerSemanticTool(pi, ctx);
37189
+ if (surface.inspect)
37190
+ registerInspectTool(pi, ctx);
37191
+ if (surface.navigate)
37192
+ registerNavigateTool(pi, ctx);
37193
+ if (surface.conflicts)
37194
+ registerConflictsTool(pi, ctx);
37195
+ if (surface.importTool)
37196
+ registerImportTools(pi, ctx);
37197
+ if (surface.safety && ctx.config.backup?.enabled !== false)
37198
+ registerSafetyTool(pi, ctx);
37199
+ if (surface.astSearch || surface.astReplace)
37200
+ registerAstTools(pi, ctx, surface);
37201
+ if (surface.delete || surface.move)
37202
+ registerFsTools(pi, ctx, surface);
37203
+ if (surface.refactor)
37204
+ registerRefactorTool(pi, ctx);
36693
37205
  }
36694
37206
 
36695
37207
  // src/workflow-hints.ts
@@ -36720,18 +37232,18 @@ function buildWorkflowHints(opts) {
36720
37232
  }
36721
37233
  if (hasOutline && hasZoom && (hasGrep || hasSearch)) {
36722
37234
  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.`;
37235
+ 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
37236
  const readName = opts.hoistBuiltins ? "read" : "aft_read";
36725
37237
  sections.push([
36726
37238
  `**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
37239
  `- \`grep -rn "handleAuth" src/\` in bash → \`${searchName}({ query: "handleAuth" })\``,
36728
37240
  `- \`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 })\``
37241
+ `- \`sed -n '100,160p' app.ts\` / \`cat app.ts\` in bash → \`${readName}({ path: "app.ts", startLine: 100, endLine: 160 })\``
36730
37242
  ].join(`
36731
37243
  `));
36732
37244
  }
36733
37245
  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.");
37246
+ 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
37247
  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
37248
  }
36737
37249
  if (hasNavigate) {
@@ -36851,14 +37363,13 @@ var PLUGIN_VERSION = (() => {
36851
37363
  return "0.0.0";
36852
37364
  }
36853
37365
  })();
36854
- var ANNOUNCEMENT_VERSION = "0.48.0";
37366
+ var ANNOUNCEMENT_VERSION = "0.49.0";
36855
37367
  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."
37368
+ "One unified tool surface: every tool now takes `path` (old spellings keep working), `edit` has one calling shape, and `occurrence` is 1-based. Fewer ways for models to get a call wrong; prompt caches re-warm once after this upgrade.",
37369
+ "aft_search now routes queries itself — the hint parameter is gone, and searches that find no exact match are re-ranked by their terms instead of returning nothing.",
37370
+ "Corporate and custom certificate authorities now work: HTTPS requests verify through the OS trust store (Keychain, Windows certificate store, SSL_CERT_FILE on Linux)."
36859
37371
  ];
36860
37372
  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
37373
  var pendingEagerWarnings = new Map;
36863
37374
  function isConfigureWarning(value) {
36864
37375
  if (!value || typeof value !== "object" || Array.isArray(value))
@@ -36939,67 +37450,6 @@ async function handleConfigureWarningsForSession(context) {
36939
37450
  projectRoot: context.projectRoot
36940
37451
  }, combinedWarnings);
36941
37452
  }
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
37453
  async function src_default(pi) {
37004
37454
  const deliverConfigMigrationWarnings = (messages) => {
37005
37455
  for (const message of messages) {
@@ -37210,7 +37660,7 @@ ${lines}
37210
37660
  });
37211
37661
  }
37212
37662
  pool.setConfigureOverride("harness", "pi");
37213
- pool.setConfigureOverride("aft_search_registered", resolveToolSurface(config2).semantic);
37663
+ pool.setConfigureOverride("aft_search_registered", resolvePiToolSurface(config2).semantic);
37214
37664
  const ctx = { pool, config: config2, storageDir };
37215
37665
  if (onnxRuntimePromise) {
37216
37666
  onnxRuntimePromise.then((ortDylibDir) => {
@@ -37249,41 +37699,8 @@ ${lines}
37249
37699
  if (ANNOUNCEMENT_VERSION && ANNOUNCEMENT_FEATURES.length > 0) {
37250
37700
  sendFeatureAnnouncement(ANNOUNCEMENT_VERSION, ANNOUNCEMENT_FEATURES, ANNOUNCEMENT_FOOTER, storageDir);
37251
37701
  }
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
- }
37702
+ const surface = resolvePiToolSurface(config2);
37703
+ registerPiToolSurface(pi, ctx, surface);
37287
37704
  registerWorkflowHints(pi, config2, surface);
37288
37705
  registerStatusCommand(pi, ctx);
37289
37706
  pi.on("tool_result", async (event, extCtx) => {
@@ -37349,7 +37766,7 @@ ${lines}
37349
37766
  var __test__2 = {
37350
37767
  enqueueConfigParseWarnings,
37351
37768
  bridgeDirectoryFromCallback,
37352
- resolveToolSurface,
37769
+ resolveToolSurface: resolvePiToolSurface,
37353
37770
  handleConfigureWarningsForSession,
37354
37771
  shouldPrepareOnnxRuntime,
37355
37772
  createVersionMismatchHandler