@cortexkit/aft-pi 0.31.1 → 0.33.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/README.md +2 -2
- package/dist/config.d.ts +41 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/dialogs/status-dialog.d.ts.map +1 -1
- package/dist/index.d.ts +2 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +622 -236
- package/dist/notifications.d.ts.map +1 -1
- package/dist/shared/status.d.ts +3 -0
- package/dist/shared/status.d.ts.map +1 -1
- package/dist/tools/_shared.d.ts +13 -0
- package/dist/tools/_shared.d.ts.map +1 -1
- package/dist/tools/ast.d.ts.map +1 -1
- package/dist/tools/hoisted.d.ts.map +1 -1
- package/dist/tools/inspect.d.ts +21 -0
- package/dist/tools/inspect.d.ts.map +1 -0
- package/dist/tools/navigate.d.ts +1 -1
- package/dist/tools/reading.d.ts.map +1 -1
- package/dist/tools/refactor.d.ts.map +1 -1
- package/dist/tools/semantic.d.ts +3 -2
- package/dist/tools/semantic.d.ts.map +1 -1
- package/dist/workflow-hints.d.ts +1 -0
- package/dist/workflow-hints.d.ts.map +1 -1
- package/package.json +9 -9
- package/dist/tools/lsp.d.ts +0 -24
- package/dist/tools/lsp.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -11680,6 +11680,7 @@ import { homedir } from "node:os";
|
|
|
11680
11680
|
import { join } from "node:path";
|
|
11681
11681
|
import { StringDecoder } from "node:string_decoder";
|
|
11682
11682
|
var DEFAULT_BRIDGE_TIMEOUT_MS = 30000;
|
|
11683
|
+
var BRIDGE_HANG_TIMEOUT_THRESHOLD = 2;
|
|
11683
11684
|
var SEMANTIC_TIMEOUT_SAFETY_MARGIN_MS = 5000;
|
|
11684
11685
|
var MAX_STDOUT_BUFFER = 64 * 1024 * 1024;
|
|
11685
11686
|
function tagStderrLine(line) {
|
|
@@ -11787,8 +11788,11 @@ class BinaryBridge {
|
|
|
11787
11788
|
statusListeners = new Set;
|
|
11788
11789
|
configureWarningClients = new Map;
|
|
11789
11790
|
restartResetTimer = null;
|
|
11791
|
+
lastChildActivityAt = 0;
|
|
11792
|
+
consecutiveRequestTimeouts = 0;
|
|
11790
11793
|
errorPrefix;
|
|
11791
11794
|
logger;
|
|
11795
|
+
childEnv;
|
|
11792
11796
|
constructor(binaryPath, cwd, options, configOverrides) {
|
|
11793
11797
|
this.binaryPath = binaryPath;
|
|
11794
11798
|
this.cwd = cwd;
|
|
@@ -11803,6 +11807,7 @@ class BinaryBridge {
|
|
|
11803
11807
|
this.onBashPatternMatch = options?.onBashPatternMatch;
|
|
11804
11808
|
this.errorPrefix = options?.errorPrefix ?? "[aft-bridge]";
|
|
11805
11809
|
this.logger = options?.logger;
|
|
11810
|
+
this.childEnv = options?.childEnv;
|
|
11806
11811
|
}
|
|
11807
11812
|
logVia(message, meta) {
|
|
11808
11813
|
const logger = this.logger ?? getActiveLogger();
|
|
@@ -11954,20 +11959,41 @@ class BinaryBridge {
|
|
|
11954
11959
|
const line = `${JSON.stringify(request)}
|
|
11955
11960
|
`;
|
|
11956
11961
|
const keepBridgeOnTimeout = options?.keepBridgeOnTimeout === true;
|
|
11962
|
+
let requestSentAt = Date.now();
|
|
11957
11963
|
const response = await new Promise((resolve, reject) => {
|
|
11958
11964
|
const timer = setTimeout(() => {
|
|
11965
|
+
const entry = this.pending.get(id);
|
|
11966
|
+
if (!entry)
|
|
11967
|
+
return;
|
|
11959
11968
|
this.pending.delete(id);
|
|
11960
|
-
|
|
11969
|
+
clearTimeout(entry.timer);
|
|
11970
|
+
if (keepBridgeOnTimeout) {
|
|
11971
|
+
const timeoutMsg2 = `Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms`;
|
|
11972
|
+
if (requestSessionId) {
|
|
11973
|
+
this.sessionWarnVia(requestSessionId, timeoutMsg2);
|
|
11974
|
+
} else {
|
|
11975
|
+
this.warnVia(timeoutMsg2);
|
|
11976
|
+
}
|
|
11977
|
+
entry.reject(new Error(`${this.errorPrefix} Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms`));
|
|
11978
|
+
return;
|
|
11979
|
+
}
|
|
11980
|
+
const childActiveSinceRequest = this.lastChildActivityAt > requestSentAt;
|
|
11981
|
+
const consecutiveTimeouts = this.consecutiveRequestTimeouts + 1;
|
|
11982
|
+
this.consecutiveRequestTimeouts = consecutiveTimeouts;
|
|
11983
|
+
const keepWarm = childActiveSinceRequest || consecutiveTimeouts < BRIDGE_HANG_TIMEOUT_THRESHOLD;
|
|
11984
|
+
const restartSuffix = keepWarm ? " — bridge kept warm" : " — restarting bridge";
|
|
11961
11985
|
const timeoutMsg = `Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms${restartSuffix}`;
|
|
11962
11986
|
if (requestSessionId) {
|
|
11963
11987
|
this.sessionWarnVia(requestSessionId, timeoutMsg);
|
|
11964
11988
|
} else {
|
|
11965
11989
|
this.warnVia(timeoutMsg);
|
|
11966
11990
|
}
|
|
11967
|
-
|
|
11968
|
-
|
|
11969
|
-
|
|
11991
|
+
if (keepWarm) {
|
|
11992
|
+
entry.reject(new Error(`${this.errorPrefix} request "${command}" timed out after ${effectiveTimeoutMs}ms (bridge busy/under load); bridge kept warm — retry`));
|
|
11993
|
+
return;
|
|
11970
11994
|
}
|
|
11995
|
+
entry.reject(new Error(`${this.errorPrefix} Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms`));
|
|
11996
|
+
this.handleTimeout(requestSessionId);
|
|
11971
11997
|
}, effectiveTimeoutMs);
|
|
11972
11998
|
this.pending.set(id, { resolve, reject, timer, onProgress: options?.onProgress });
|
|
11973
11999
|
if (!this.process?.stdin?.writable) {
|
|
@@ -11976,6 +12002,7 @@ class BinaryBridge {
|
|
|
11976
12002
|
reject(new Error(`${this.errorPrefix} stdin not writable for command "${command}"`));
|
|
11977
12003
|
return;
|
|
11978
12004
|
}
|
|
12005
|
+
requestSentAt = Date.now();
|
|
11979
12006
|
this.process.stdin.write(line, (err) => {
|
|
11980
12007
|
if (err) {
|
|
11981
12008
|
const entry = this.pending.get(id);
|
|
@@ -12170,6 +12197,15 @@ class BinaryBridge {
|
|
|
12170
12197
|
this.logVia(`ORT_DYLIB_PATH set from managed ONNX Runtime: ${ortLibraryPath}`);
|
|
12171
12198
|
}
|
|
12172
12199
|
}
|
|
12200
|
+
if (this.childEnv) {
|
|
12201
|
+
for (const [key, value] of Object.entries(this.childEnv)) {
|
|
12202
|
+
if (value === undefined) {
|
|
12203
|
+
delete env[key];
|
|
12204
|
+
} else {
|
|
12205
|
+
env[key] = value;
|
|
12206
|
+
}
|
|
12207
|
+
}
|
|
12208
|
+
}
|
|
12173
12209
|
const child = spawn(this.binaryPath, [], {
|
|
12174
12210
|
cwd: this.cwd,
|
|
12175
12211
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -12219,6 +12255,8 @@ class BinaryBridge {
|
|
|
12219
12255
|
this.process = child;
|
|
12220
12256
|
this.stdoutBuffer = "";
|
|
12221
12257
|
this.stderrBuffer = "";
|
|
12258
|
+
this.lastChildActivityAt = 0;
|
|
12259
|
+
this.consecutiveRequestTimeouts = 0;
|
|
12222
12260
|
this.stderrTail = [];
|
|
12223
12261
|
}
|
|
12224
12262
|
pushStderrLine(line) {
|
|
@@ -12274,6 +12312,7 @@ class BinaryBridge {
|
|
|
12274
12312
|
continue;
|
|
12275
12313
|
try {
|
|
12276
12314
|
const response = JSON.parse(line);
|
|
12315
|
+
this.lastChildActivityAt = Date.now();
|
|
12277
12316
|
if (response.type === "progress") {
|
|
12278
12317
|
const requestId = response.request_id;
|
|
12279
12318
|
const entry = requestId ? this.pending.get(requestId) : undefined;
|
|
@@ -12326,6 +12365,7 @@ class BinaryBridge {
|
|
|
12326
12365
|
continue;
|
|
12327
12366
|
this.pending.delete(id);
|
|
12328
12367
|
clearTimeout(entry.timer);
|
|
12368
|
+
this.consecutiveRequestTimeouts = 0;
|
|
12329
12369
|
this.scheduleRestartCountReset();
|
|
12330
12370
|
entry.resolve(response);
|
|
12331
12371
|
} else if (typeof response.type === "string") {
|
|
@@ -12337,6 +12377,7 @@ class BinaryBridge {
|
|
|
12337
12377
|
}
|
|
12338
12378
|
}
|
|
12339
12379
|
handleTimeout(triggeringSessionId) {
|
|
12380
|
+
this.consecutiveRequestTimeouts = 0;
|
|
12340
12381
|
this.rejectAllPending(new Error(`${this.errorPrefix} bridge killed during sibling timeout — request aborted`));
|
|
12341
12382
|
if (this.process) {
|
|
12342
12383
|
this.process.kill("SIGKILL");
|
|
@@ -12415,7 +12456,7 @@ class BinaryBridge {
|
|
|
12415
12456
|
// ../aft-bridge/dist/downloader.js
|
|
12416
12457
|
import { spawnSync } from "node:child_process";
|
|
12417
12458
|
import { createHash, randomUUID } from "node:crypto";
|
|
12418
|
-
import { chmodSync, closeSync, createWriteStream, existsSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, statSync, unlinkSync, writeSync } from "node:fs";
|
|
12459
|
+
import { chmodSync, closeSync, copyFileSync, createWriteStream, existsSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, statSync, unlinkSync, writeSync } from "node:fs";
|
|
12419
12460
|
import { homedir as homedir2 } from "node:os";
|
|
12420
12461
|
import { join as join2 } from "node:path";
|
|
12421
12462
|
import { Readable } from "node:stream";
|
|
@@ -12582,10 +12623,18 @@ async function downloadBinary(version) {
|
|
|
12582
12623
|
throw new Error(`Checksum mismatch for ${assetName}: expected ${expectedHash}, got ${actualHash}. ` + "The binary may have been tampered with.");
|
|
12583
12624
|
}
|
|
12584
12625
|
log(`Checksum verified (SHA-256: ${actualHash.slice(0, 16)}...)`);
|
|
12585
|
-
if (process.platform
|
|
12626
|
+
if (process.platform === "win32") {
|
|
12627
|
+
copyFileSync(tmpPath, binaryPath);
|
|
12628
|
+
} else {
|
|
12586
12629
|
chmodSync(tmpPath, 493);
|
|
12630
|
+
renameSync(tmpPath, binaryPath);
|
|
12631
|
+
}
|
|
12632
|
+
try {
|
|
12633
|
+
if (existsSync(tmpPath))
|
|
12634
|
+
unlinkSync(tmpPath);
|
|
12635
|
+
} catch {
|
|
12636
|
+
warn(`Could not clean up temporary download file ${tmpPath} — it can be removed manually.`);
|
|
12587
12637
|
}
|
|
12588
|
-
renameSync(tmpPath, binaryPath);
|
|
12589
12638
|
log(`AFT binary ready at ${binaryPath}`);
|
|
12590
12639
|
return binaryPath;
|
|
12591
12640
|
} catch (err) {
|
|
@@ -12756,7 +12805,7 @@ function markAnnouncementSeen(storageRoot, harness, currentVersion) {
|
|
|
12756
12805
|
|
|
12757
12806
|
// ../aft-bridge/dist/resolver.js
|
|
12758
12807
|
import { execSync } from "node:child_process";
|
|
12759
|
-
import { chmodSync as chmodSync2, copyFileSync, existsSync as existsSync3, mkdirSync as mkdirSync3, renameSync as renameSync3 } from "node:fs";
|
|
12808
|
+
import { chmodSync as chmodSync2, copyFileSync as copyFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync3, renameSync as renameSync3, unlinkSync as unlinkSync2 } from "node:fs";
|
|
12760
12809
|
import { createRequire as createRequire2 } from "node:module";
|
|
12761
12810
|
import { homedir as homedir3 } from "node:os";
|
|
12762
12811
|
import { join as join4 } from "node:path";
|
|
@@ -12779,10 +12828,15 @@ function copyToVersionedCache(npmBinaryPath, knownVersion) {
|
|
|
12779
12828
|
}
|
|
12780
12829
|
mkdirSync3(versionedDir, { recursive: true });
|
|
12781
12830
|
const tmpPath = `${cachedPath}.${process.pid}.${Date.now()}.tmp`;
|
|
12782
|
-
|
|
12831
|
+
copyFileSync2(npmBinaryPath, tmpPath);
|
|
12783
12832
|
if (process.platform !== "win32") {
|
|
12784
12833
|
chmodSync2(tmpPath, 493);
|
|
12785
12834
|
}
|
|
12835
|
+
if (process.platform === "win32" && existsSync3(cachedPath)) {
|
|
12836
|
+
try {
|
|
12837
|
+
unlinkSync2(cachedPath);
|
|
12838
|
+
} catch {}
|
|
12839
|
+
}
|
|
12786
12840
|
renameSync3(tmpPath, cachedPath);
|
|
12787
12841
|
log(`Copied npm binary to versioned cache: ${cachedPath}`);
|
|
12788
12842
|
return cachedPath;
|
|
@@ -13034,7 +13088,7 @@ async function ensureStorageMigrated(opts) {
|
|
|
13034
13088
|
// ../aft-bridge/dist/onnx-runtime.js
|
|
13035
13089
|
import { execFileSync } from "node:child_process";
|
|
13036
13090
|
import { createHash as createHash2 } from "node:crypto";
|
|
13037
|
-
import { chmodSync as chmodSync3, closeSync as closeSync2, copyFileSync as
|
|
13091
|
+
import { chmodSync as chmodSync3, closeSync as closeSync2, copyFileSync as copyFileSync3, createWriteStream as createWriteStream2, existsSync as existsSync5, lstatSync, mkdirSync as mkdirSync5, openSync as openSync2, readdirSync, readFileSync as readFileSync3, readlinkSync, realpathSync, rmSync as rmSync2, statSync as statSync2, symlinkSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
13038
13092
|
import { basename, dirname as dirname3, isAbsolute, join as join6, relative, resolve, win32 } from "node:path";
|
|
13039
13093
|
import { Readable as Readable2 } from "node:stream";
|
|
13040
13094
|
import { pipeline as pipeline2 } from "node:stream/promises";
|
|
@@ -13270,20 +13324,64 @@ function findSystemOnnxRuntime(libName) {
|
|
|
13270
13324
|
} else if (process.platform === "linux") {
|
|
13271
13325
|
searchPaths.push("/usr/lib", "/usr/lib/x86_64-linux-gnu", "/usr/lib/aarch64-linux-gnu", "/usr/local/lib");
|
|
13272
13326
|
} else if (process.platform === "win32") {
|
|
13327
|
+
const programFiles = process.env.ProgramFiles ?? "C:\\Program Files";
|
|
13328
|
+
const programFilesX86 = process.env["ProgramFiles(x86)"] ?? "C:\\Program Files (x86)";
|
|
13329
|
+
searchPaths.push(join6(programFiles, "onnxruntime", "lib"), join6(programFiles, "Microsoft ONNX Runtime", "lib"), join6(programFiles, "Microsoft Machine Learning", "lib"), join6(programFilesX86, "onnxruntime", "lib"), ...(() => {
|
|
13330
|
+
const nugetPaths = [];
|
|
13331
|
+
const userProfile = process.env.USERPROFILE ?? "";
|
|
13332
|
+
if (!userProfile)
|
|
13333
|
+
return nugetPaths;
|
|
13334
|
+
const nugetPackageDir = join6(userProfile, ".nuget", "packages", "microsoft.ml.onnxruntime");
|
|
13335
|
+
if (!existsSync5(nugetPackageDir))
|
|
13336
|
+
return nugetPaths;
|
|
13337
|
+
try {
|
|
13338
|
+
for (const entry of readdirSync(nugetPackageDir, { withFileTypes: true })) {
|
|
13339
|
+
if (!entry.isDirectory())
|
|
13340
|
+
continue;
|
|
13341
|
+
if (entry.name === "__globalPackagesFolder" || entry.name.startsWith("."))
|
|
13342
|
+
continue;
|
|
13343
|
+
nugetPaths.push(join6(nugetPackageDir, entry.name, "runtimes", "win-x64", "native"), join6(nugetPackageDir, entry.name, "runtimes", "win-arm64", "native"));
|
|
13344
|
+
}
|
|
13345
|
+
} catch (err) {
|
|
13346
|
+
warn(`Failed to scan NuGet ONNX Runtime cache ${nugetPackageDir}: ${err instanceof Error ? err.message : String(err)}`);
|
|
13347
|
+
}
|
|
13348
|
+
return nugetPaths;
|
|
13349
|
+
})());
|
|
13273
13350
|
searchPaths.push(...pathEntriesForPlatform());
|
|
13274
13351
|
}
|
|
13352
|
+
const normalizeCase = process.platform === "win32" || process.platform === "darwin";
|
|
13275
13353
|
const seen = new Set;
|
|
13276
|
-
|
|
13277
|
-
|
|
13278
|
-
|
|
13279
|
-
|
|
13280
|
-
if (
|
|
13281
|
-
|
|
13282
|
-
|
|
13283
|
-
|
|
13284
|
-
|
|
13354
|
+
const uniquePaths = searchPaths.filter((p) => {
|
|
13355
|
+
let key = resolve(p).replace(/[/\\]+$/, "");
|
|
13356
|
+
if (normalizeCase)
|
|
13357
|
+
key = key.toLowerCase();
|
|
13358
|
+
if (seen.has(key))
|
|
13359
|
+
return false;
|
|
13360
|
+
seen.add(key);
|
|
13361
|
+
return true;
|
|
13362
|
+
});
|
|
13363
|
+
for (const dir of uniquePaths) {
|
|
13364
|
+
const libPath = join6(dir, libName);
|
|
13365
|
+
if (process.platform === "win32") {
|
|
13366
|
+
if (!directoryContainsLibrary(dir, libName))
|
|
13367
|
+
continue;
|
|
13368
|
+
} else if (!existsSync5(libPath)) {
|
|
13285
13369
|
continue;
|
|
13286
13370
|
}
|
|
13371
|
+
let skipVersionCheck = false;
|
|
13372
|
+
if (process.platform === "win32") {
|
|
13373
|
+
const dirLower = dir.toLowerCase();
|
|
13374
|
+
const isCommonPath = dirLower.includes("program files") || dirLower.includes("onnxruntime");
|
|
13375
|
+
if (!isCommonPath)
|
|
13376
|
+
skipVersionCheck = true;
|
|
13377
|
+
}
|
|
13378
|
+
if (!skipVersionCheck) {
|
|
13379
|
+
const version = detectOnnxVersion(dir, libName);
|
|
13380
|
+
if (version && !isOnnxVersionCompatible(version)) {
|
|
13381
|
+
warn(`Skipping system ONNX Runtime at ${dir} (v${version}); AFT requires ` + `v${REQUIRED_ORT_MAJOR}.${REQUIRED_ORT_MIN_MINOR}+. Falling through to AFT-managed download.`);
|
|
13382
|
+
continue;
|
|
13383
|
+
}
|
|
13384
|
+
}
|
|
13287
13385
|
return dir;
|
|
13288
13386
|
}
|
|
13289
13387
|
return null;
|
|
@@ -13321,7 +13419,7 @@ async function downloadFileWithCap(url, destPath) {
|
|
|
13321
13419
|
await pipeline2(nodeStream, createWriteStream2(destPath), { signal: controller.signal });
|
|
13322
13420
|
} catch (err) {
|
|
13323
13421
|
try {
|
|
13324
|
-
|
|
13422
|
+
unlinkSync3(destPath);
|
|
13325
13423
|
} catch {}
|
|
13326
13424
|
throw err;
|
|
13327
13425
|
} finally {
|
|
@@ -13382,7 +13480,7 @@ async function downloadOnnxRuntime(info, targetDir) {
|
|
|
13382
13480
|
await extractZipArchive(archivePath, tmpDir);
|
|
13383
13481
|
}
|
|
13384
13482
|
try {
|
|
13385
|
-
|
|
13483
|
+
unlinkSync3(archivePath);
|
|
13386
13484
|
} catch {}
|
|
13387
13485
|
validateExtractedTree(tmpDir);
|
|
13388
13486
|
const extractedDir = join6(tmpDir, info.assetName, "lib");
|
|
@@ -13431,7 +13529,7 @@ async function downloadOnnxRuntime(info, targetDir) {
|
|
|
13431
13529
|
return null;
|
|
13432
13530
|
}
|
|
13433
13531
|
}
|
|
13434
|
-
function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, copyFile =
|
|
13532
|
+
function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, copyFile = copyFileSync3) {
|
|
13435
13533
|
const requiredLibs = new Set([info.libName]);
|
|
13436
13534
|
for (const libFile of realFiles) {
|
|
13437
13535
|
const src = join6(extractedDir, libFile);
|
|
@@ -13452,7 +13550,7 @@ function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, c
|
|
|
13452
13550
|
for (const link of symlinks) {
|
|
13453
13551
|
const dst = join6(targetDir, link.name);
|
|
13454
13552
|
try {
|
|
13455
|
-
|
|
13553
|
+
unlinkSync3(dst);
|
|
13456
13554
|
} catch {}
|
|
13457
13555
|
try {
|
|
13458
13556
|
symlinkSync(link.target, dst);
|
|
@@ -13563,7 +13661,7 @@ ${new Date().toISOString()}
|
|
|
13563
13661
|
}
|
|
13564
13662
|
log(`[onnx] reclaiming install lock (owner_pid=${owningPid ?? "unknown"}, alive=${ownerAlive}, age_ms=${age})`);
|
|
13565
13663
|
try {
|
|
13566
|
-
|
|
13664
|
+
unlinkSync3(lockPath);
|
|
13567
13665
|
} catch {}
|
|
13568
13666
|
return tryClaim();
|
|
13569
13667
|
}
|
|
@@ -13588,7 +13686,7 @@ function releaseLock(lockPath) {
|
|
|
13588
13686
|
return;
|
|
13589
13687
|
}
|
|
13590
13688
|
try {
|
|
13591
|
-
|
|
13689
|
+
unlinkSync3(lockPath);
|
|
13592
13690
|
} catch (unlinkErr) {
|
|
13593
13691
|
const code = unlinkErr.code;
|
|
13594
13692
|
if (code !== "ENOENT") {
|
|
@@ -14534,7 +14632,7 @@ import {
|
|
|
14534
14632
|
// package.json
|
|
14535
14633
|
var package_default = {
|
|
14536
14634
|
name: "@cortexkit/aft-pi",
|
|
14537
|
-
version: "0.
|
|
14635
|
+
version: "0.33.0",
|
|
14538
14636
|
type: "module",
|
|
14539
14637
|
description: "Pi coding agent extension for Agent File Tools (AFT) — tree-sitter and LSP-powered code analysis",
|
|
14540
14638
|
main: "dist/index.js",
|
|
@@ -14542,7 +14640,7 @@ var package_default = {
|
|
|
14542
14640
|
license: "MIT",
|
|
14543
14641
|
repository: {
|
|
14544
14642
|
type: "git",
|
|
14545
|
-
url: "https://github.com/cortexkit/aft"
|
|
14643
|
+
url: "git+https://github.com/cortexkit/aft.git"
|
|
14546
14644
|
},
|
|
14547
14645
|
files: [
|
|
14548
14646
|
"dist",
|
|
@@ -14556,7 +14654,7 @@ var package_default = {
|
|
|
14556
14654
|
prepublishOnly: "bun run build"
|
|
14557
14655
|
},
|
|
14558
14656
|
dependencies: {
|
|
14559
|
-
"@cortexkit/aft-bridge": "0.
|
|
14657
|
+
"@cortexkit/aft-bridge": "0.33.0",
|
|
14560
14658
|
"@xterm/headless": "^5.5.0",
|
|
14561
14659
|
"comment-json": "^5.0.0",
|
|
14562
14660
|
diff: "^8.0.4",
|
|
@@ -14564,12 +14662,12 @@ var package_default = {
|
|
|
14564
14662
|
zod: "^4.1.8"
|
|
14565
14663
|
},
|
|
14566
14664
|
optionalDependencies: {
|
|
14567
|
-
"@cortexkit/aft-darwin-arm64": "0.
|
|
14568
|
-
"@cortexkit/aft-darwin-x64": "0.
|
|
14569
|
-
"@cortexkit/aft-linux-arm64": "0.
|
|
14570
|
-
"@cortexkit/aft-linux-x64": "0.
|
|
14571
|
-
"@cortexkit/aft-win32-arm64": "0.
|
|
14572
|
-
"@cortexkit/aft-win32-x64": "0.
|
|
14665
|
+
"@cortexkit/aft-darwin-arm64": "0.33.0",
|
|
14666
|
+
"@cortexkit/aft-darwin-x64": "0.33.0",
|
|
14667
|
+
"@cortexkit/aft-linux-arm64": "0.33.0",
|
|
14668
|
+
"@cortexkit/aft-linux-x64": "0.33.0",
|
|
14669
|
+
"@cortexkit/aft-win32-arm64": "0.33.0",
|
|
14670
|
+
"@cortexkit/aft-win32-x64": "0.33.0"
|
|
14573
14671
|
},
|
|
14574
14672
|
devDependencies: {
|
|
14575
14673
|
"@earendil-works/pi-coding-agent": "*",
|
|
@@ -14638,6 +14736,19 @@ function formatFlag(enabled) {
|
|
|
14638
14736
|
function formatCount(value) {
|
|
14639
14737
|
return value == null ? "—" : value.toLocaleString("en-US");
|
|
14640
14738
|
}
|
|
14739
|
+
function formatSemanticIndexStatus(status, stage) {
|
|
14740
|
+
if ((status === "loading" || status === "building") && stage === "fingerprint_change") {
|
|
14741
|
+
return "Rebuilding (model changed)";
|
|
14742
|
+
}
|
|
14743
|
+
return status;
|
|
14744
|
+
}
|
|
14745
|
+
function formatSemanticRefreshing(refreshingCount) {
|
|
14746
|
+
if (!Number.isFinite(refreshingCount) || refreshingCount <= 0)
|
|
14747
|
+
return null;
|
|
14748
|
+
if (refreshingCount > 20)
|
|
14749
|
+
return "Ready (many files refreshing)";
|
|
14750
|
+
return `Ready (${refreshingCount} file(s) refreshing)`;
|
|
14751
|
+
}
|
|
14641
14752
|
function formatBytes(bytes) {
|
|
14642
14753
|
if (!Number.isFinite(bytes) || bytes <= 0)
|
|
14643
14754
|
return "0 B";
|
|
@@ -14688,6 +14799,7 @@ function coerceAftStatus(response) {
|
|
|
14688
14799
|
files: readOptionalNumber(semanticIndex.files),
|
|
14689
14800
|
entries_done: readOptionalNumber(semanticIndex.entries_done),
|
|
14690
14801
|
entries_total: readOptionalNumber(semanticIndex.entries_total),
|
|
14802
|
+
refreshing_count: readNumber(semanticIndex.refreshing_count),
|
|
14691
14803
|
entries: readOptionalNumber(semanticIndex.entries),
|
|
14692
14804
|
dimension: readOptionalNumber(semanticIndex.dimension),
|
|
14693
14805
|
error: readNullableString(semanticIndex.error)
|
|
@@ -14730,9 +14842,13 @@ function formatStatusDialogMessage(status) {
|
|
|
14730
14842
|
`- trigrams: ${formatCount(status.search_index.trigrams)}`,
|
|
14731
14843
|
"",
|
|
14732
14844
|
"Semantic index",
|
|
14733
|
-
`- status: ${status.semantic_index.status}
|
|
14734
|
-
`- entries: ${formatCount(status.semantic_index.entries)}`
|
|
14845
|
+
`- status: ${formatSemanticIndexStatus(status.semantic_index.status, status.semantic_index.stage)}`
|
|
14735
14846
|
];
|
|
14847
|
+
const refreshing = formatSemanticRefreshing(status.semantic_index.refreshing_count);
|
|
14848
|
+
if (refreshing) {
|
|
14849
|
+
lines.push(`- ${refreshing}`);
|
|
14850
|
+
}
|
|
14851
|
+
lines.push(`- entries: ${formatCount(status.semantic_index.entries)}`);
|
|
14736
14852
|
if (status.semantic_index.backend) {
|
|
14737
14853
|
lines.push(`- backend: ${status.semantic_index.backend}`);
|
|
14738
14854
|
}
|
|
@@ -14779,6 +14895,17 @@ function coerceOptionalInt(v, paramName, min, max) {
|
|
|
14779
14895
|
}
|
|
14780
14896
|
return n;
|
|
14781
14897
|
}
|
|
14898
|
+
function isEmptyParam(value) {
|
|
14899
|
+
if (value === undefined || value === null)
|
|
14900
|
+
return true;
|
|
14901
|
+
if (typeof value === "string")
|
|
14902
|
+
return value.length === 0;
|
|
14903
|
+
if (Array.isArray(value))
|
|
14904
|
+
return value.length === 0;
|
|
14905
|
+
if (typeof value === "object")
|
|
14906
|
+
return Object.keys(value).length === 0;
|
|
14907
|
+
return false;
|
|
14908
|
+
}
|
|
14782
14909
|
var LONG_RUNNING_COMMAND_TIMEOUT_MS = {
|
|
14783
14910
|
callers: 60000,
|
|
14784
14911
|
trace_to: 60000,
|
|
@@ -14787,7 +14914,7 @@ var LONG_RUNNING_COMMAND_TIMEOUT_MS = {
|
|
|
14787
14914
|
impact: 60000,
|
|
14788
14915
|
grep: 60000,
|
|
14789
14916
|
glob: 60000,
|
|
14790
|
-
semantic_search:
|
|
14917
|
+
semantic_search: 60000
|
|
14791
14918
|
};
|
|
14792
14919
|
function timeoutForCommand(command) {
|
|
14793
14920
|
return LONG_RUNNING_COMMAND_TIMEOUT_MS[command];
|
|
@@ -15000,7 +15127,10 @@ function renderInner(s, error3, theme, innerWidth) {
|
|
|
15000
15127
|
left.push(kv("trigrams", formatCountShort(s.search_index.trigrams), theme));
|
|
15001
15128
|
left.push(kv("disk", formatBytes(s.disk.trigram_disk_bytes), theme));
|
|
15002
15129
|
right.push(theme.fg("muted", "Semantic index"));
|
|
15003
|
-
right.push(kv("status", colorStatus(s.semantic_index.status, theme), theme));
|
|
15130
|
+
right.push(kv("status", colorStatus(s.semantic_index.status, theme, formatSemanticIndexStatus(s.semantic_index.status, s.semantic_index.stage)), theme));
|
|
15131
|
+
const semanticRefreshing = formatSemanticRefreshing(s.semantic_index.refreshing_count);
|
|
15132
|
+
if (semanticRefreshing)
|
|
15133
|
+
right.push(theme.fg("muted", ` ${semanticRefreshing}`));
|
|
15004
15134
|
right.push(kv("entries", formatCountShort(s.semantic_index.entries), theme));
|
|
15005
15135
|
if (s.semantic_index.backend)
|
|
15006
15136
|
right.push(kv("backend", s.semantic_index.backend, theme));
|
|
@@ -15074,24 +15204,24 @@ function formatCompressionStatusRows(compression) {
|
|
|
15074
15204
|
appendCompressionScope(rows, "Project", compression.project);
|
|
15075
15205
|
return rows;
|
|
15076
15206
|
}
|
|
15077
|
-
function colorStatus(status, theme) {
|
|
15207
|
+
function colorStatus(status, theme, label = status) {
|
|
15078
15208
|
switch (status) {
|
|
15079
15209
|
case "ready":
|
|
15080
15210
|
try {
|
|
15081
|
-
return theme.fg("success",
|
|
15211
|
+
return theme.fg("success", label);
|
|
15082
15212
|
} catch {
|
|
15083
|
-
return theme.fg("accent",
|
|
15213
|
+
return theme.fg("accent", label);
|
|
15084
15214
|
}
|
|
15085
15215
|
case "loading":
|
|
15086
15216
|
case "building":
|
|
15087
|
-
return theme.fg("warning",
|
|
15217
|
+
return theme.fg("warning", label);
|
|
15088
15218
|
case "failed":
|
|
15089
15219
|
case "error":
|
|
15090
|
-
return theme.fg("error",
|
|
15220
|
+
return theme.fg("error", label);
|
|
15091
15221
|
case "disabled":
|
|
15092
|
-
return theme.fg("muted",
|
|
15222
|
+
return theme.fg("muted", label);
|
|
15093
15223
|
default:
|
|
15094
|
-
return
|
|
15224
|
+
return label;
|
|
15095
15225
|
}
|
|
15096
15226
|
}
|
|
15097
15227
|
function featureBadge(name, enabled, theme) {
|
|
@@ -15184,7 +15314,7 @@ function registerStatusCommand(pi, ctx) {
|
|
|
15184
15314
|
|
|
15185
15315
|
// src/config.ts
|
|
15186
15316
|
var import_comment_json = __toESM(require_src2(), 1);
|
|
15187
|
-
import { existsSync as existsSync6, readFileSync as readFileSync4, renameSync as renameSync4, unlinkSync as
|
|
15317
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4, renameSync as renameSync4, unlinkSync as unlinkSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
15188
15318
|
import { homedir as homedir6 } from "node:os";
|
|
15189
15319
|
import { join as join8 } from "node:path";
|
|
15190
15320
|
|
|
@@ -28815,6 +28945,7 @@ var LspConfigSchema = exports_external.object({
|
|
|
28815
28945
|
servers: exports_external.record(exports_external.string().trim().min(1), LspServerEntrySchema).optional(),
|
|
28816
28946
|
disabled: exports_external.array(exports_external.string().trim().min(1)).optional(),
|
|
28817
28947
|
python: exports_external.enum(["pyright", "ty", "auto"]).optional(),
|
|
28948
|
+
diagnostics_on_edit: exports_external.boolean().optional(),
|
|
28818
28949
|
auto_install: exports_external.boolean().optional(),
|
|
28819
28950
|
grace_days: exports_external.number().int().positive().optional(),
|
|
28820
28951
|
versions: exports_external.record(exports_external.string().trim().min(1), exports_external.string().trim().min(1)).optional()
|
|
@@ -28837,6 +28968,24 @@ var BashFeaturesSchema = exports_external.object({
|
|
|
28837
28968
|
long_running_reminder_interval_ms: exports_external.number().int().positive().optional()
|
|
28838
28969
|
});
|
|
28839
28970
|
var BashConfigSchema = exports_external.union([exports_external.boolean(), BashFeaturesSchema]);
|
|
28971
|
+
var InspectConfigSchema = exports_external.object({
|
|
28972
|
+
enabled: exports_external.boolean().optional(),
|
|
28973
|
+
tier2_idle_minutes: exports_external.number().min(0).optional(),
|
|
28974
|
+
categories: exports_external.record(exports_external.string(), exports_external.boolean()).optional(),
|
|
28975
|
+
tier2_soft_deadline_ms: exports_external.number().int().positive().optional(),
|
|
28976
|
+
max_drill_down_items: exports_external.number().int().positive().max(100).optional(),
|
|
28977
|
+
duplicates: exports_external.object({
|
|
28978
|
+
lower_bound: exports_external.number().int().positive().optional(),
|
|
28979
|
+
discard_cost: exports_external.number().int().min(0).optional(),
|
|
28980
|
+
anonymize: exports_external.object({
|
|
28981
|
+
variables: exports_external.boolean().optional(),
|
|
28982
|
+
fields: exports_external.boolean().optional(),
|
|
28983
|
+
methods: exports_external.boolean().optional(),
|
|
28984
|
+
types: exports_external.boolean().optional(),
|
|
28985
|
+
literals: exports_external.boolean().optional()
|
|
28986
|
+
}).optional()
|
|
28987
|
+
}).optional()
|
|
28988
|
+
});
|
|
28840
28989
|
var AftConfigSchema = exports_external.object({
|
|
28841
28990
|
$schema: exports_external.string().optional(),
|
|
28842
28991
|
format_on_edit: exports_external.boolean().optional(),
|
|
@@ -28849,6 +28998,7 @@ var AftConfigSchema = exports_external.object({
|
|
|
28849
28998
|
restrict_to_project_root: exports_external.boolean().optional(),
|
|
28850
28999
|
search_index: exports_external.boolean().optional(),
|
|
28851
29000
|
semantic_search: exports_external.boolean().optional(),
|
|
29001
|
+
inspect: InspectConfigSchema.optional(),
|
|
28852
29002
|
bash: BashConfigSchema.optional(),
|
|
28853
29003
|
experimental: ExperimentalConfigSchema.optional(),
|
|
28854
29004
|
lsp: LspConfigSchema.optional(),
|
|
@@ -29059,7 +29209,7 @@ ${serialized}` : serialized;
|
|
|
29059
29209
|
} catch (err) {
|
|
29060
29210
|
if (tmpPath) {
|
|
29061
29211
|
try {
|
|
29062
|
-
|
|
29212
|
+
unlinkSync4(tmpPath);
|
|
29063
29213
|
} catch {}
|
|
29064
29214
|
}
|
|
29065
29215
|
if (isWritableMigrationError(err)) {
|
|
@@ -29143,6 +29293,9 @@ function mergeLspConfig(base, override) {
|
|
|
29143
29293
|
const projectSafe = {};
|
|
29144
29294
|
if (override?.python !== undefined)
|
|
29145
29295
|
projectSafe.python = override.python;
|
|
29296
|
+
if (override?.diagnostics_on_edit !== undefined) {
|
|
29297
|
+
projectSafe.diagnostics_on_edit = override.diagnostics_on_edit;
|
|
29298
|
+
}
|
|
29146
29299
|
const userDisabled = base?.disabled ?? [];
|
|
29147
29300
|
const lsp = {
|
|
29148
29301
|
...base,
|
|
@@ -29153,6 +29306,27 @@ function mergeLspConfig(base, override) {
|
|
|
29153
29306
|
return;
|
|
29154
29307
|
return Object.fromEntries(Object.entries(lsp).filter(([, v]) => v !== undefined));
|
|
29155
29308
|
}
|
|
29309
|
+
function mergeInspectConfig(baseInspect, overrideInspect) {
|
|
29310
|
+
const inspect = {
|
|
29311
|
+
...baseInspect,
|
|
29312
|
+
...overrideInspect,
|
|
29313
|
+
duplicates: baseInspect?.duplicates || overrideInspect?.duplicates ? {
|
|
29314
|
+
...baseInspect?.duplicates,
|
|
29315
|
+
...overrideInspect?.duplicates,
|
|
29316
|
+
anonymize: baseInspect?.duplicates?.anonymize || overrideInspect?.duplicates?.anonymize ? {
|
|
29317
|
+
...baseInspect?.duplicates?.anonymize,
|
|
29318
|
+
...overrideInspect?.duplicates?.anonymize
|
|
29319
|
+
} : undefined
|
|
29320
|
+
} : undefined
|
|
29321
|
+
};
|
|
29322
|
+
if (inspect.duplicates && inspect.duplicates.anonymize === undefined) {
|
|
29323
|
+
delete inspect.duplicates.anonymize;
|
|
29324
|
+
}
|
|
29325
|
+
if (Object.values(inspect).every((value) => value === undefined)) {
|
|
29326
|
+
return;
|
|
29327
|
+
}
|
|
29328
|
+
return Object.fromEntries(Object.entries(inspect).filter(([, value]) => value !== undefined));
|
|
29329
|
+
}
|
|
29156
29330
|
function mergeBashConfig(baseBash, overrideBash) {
|
|
29157
29331
|
if (baseBash === undefined && overrideBash === undefined)
|
|
29158
29332
|
return;
|
|
@@ -29209,6 +29383,7 @@ var PROJECT_SAFE_TOP_LEVEL_FIELDS = new Set([
|
|
|
29209
29383
|
"validate_on_edit",
|
|
29210
29384
|
"search_index",
|
|
29211
29385
|
"semantic_search",
|
|
29386
|
+
"inspect",
|
|
29212
29387
|
"experimental",
|
|
29213
29388
|
"bash"
|
|
29214
29389
|
]);
|
|
@@ -29239,8 +29414,10 @@ function mergeConfigs(base, override) {
|
|
|
29239
29414
|
const lsp = mergeLspConfig(base.lsp, override.lsp);
|
|
29240
29415
|
const experimental = mergeExperimentalConfig(base.experimental, override.experimental);
|
|
29241
29416
|
const bash = mergeBashConfig(base.bash, override.bash);
|
|
29417
|
+
const inspect = mergeInspectConfig(base.inspect, override.inspect);
|
|
29242
29418
|
const safeOverride = pickProjectSafeFields(override);
|
|
29243
29419
|
delete safeOverride.bash;
|
|
29420
|
+
delete safeOverride.inspect;
|
|
29244
29421
|
return {
|
|
29245
29422
|
...base,
|
|
29246
29423
|
...safeOverride,
|
|
@@ -29248,6 +29425,7 @@ function mergeConfigs(base, override) {
|
|
|
29248
29425
|
...Object.keys(checker).length > 0 ? { checker } : {},
|
|
29249
29426
|
...lsp ? { lsp } : {},
|
|
29250
29427
|
...bash !== undefined ? { bash } : {},
|
|
29428
|
+
...inspect !== undefined ? { inspect } : {},
|
|
29251
29429
|
experimental,
|
|
29252
29430
|
semantic,
|
|
29253
29431
|
...disabledTools.length > 0 ? { disabled_tools: [...new Set(disabledTools)] } : {}
|
|
@@ -29299,7 +29477,7 @@ import {
|
|
|
29299
29477
|
openSync as openSync3,
|
|
29300
29478
|
readFileSync as readFileSync5,
|
|
29301
29479
|
statSync as statSync3,
|
|
29302
|
-
unlinkSync as
|
|
29480
|
+
unlinkSync as unlinkSync5,
|
|
29303
29481
|
writeFileSync as writeFileSync4
|
|
29304
29482
|
} from "node:fs";
|
|
29305
29483
|
import { homedir as homedir7 } from "node:os";
|
|
@@ -29429,7 +29607,7 @@ ${new Date().toISOString()}
|
|
|
29429
29607
|
}
|
|
29430
29608
|
log2(`[lsp] reclaiming install lock for ${lockKey} (owner_pid=${owningPid ?? "unknown"}, alive=${ownerAlive}, age_ms=${age})`);
|
|
29431
29609
|
try {
|
|
29432
|
-
|
|
29610
|
+
unlinkSync5(lock);
|
|
29433
29611
|
} catch {}
|
|
29434
29612
|
return tryClaim();
|
|
29435
29613
|
}
|
|
@@ -29466,7 +29644,7 @@ function releaseInstallLock(lockKey) {
|
|
|
29466
29644
|
return;
|
|
29467
29645
|
}
|
|
29468
29646
|
try {
|
|
29469
|
-
|
|
29647
|
+
unlinkSync5(lock);
|
|
29470
29648
|
} catch (unlinkErr) {
|
|
29471
29649
|
const code = unlinkErr.code;
|
|
29472
29650
|
if (code !== "ENOENT") {
|
|
@@ -30122,7 +30300,7 @@ function runAutoInstall(projectRoot, config2, fetchImpl = fetch) {
|
|
|
30122
30300
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
30123
30301
|
import { createHash as createHash4, randomBytes } from "node:crypto";
|
|
30124
30302
|
import {
|
|
30125
|
-
copyFileSync as
|
|
30303
|
+
copyFileSync as copyFileSync4,
|
|
30126
30304
|
createReadStream as createReadStream2,
|
|
30127
30305
|
createWriteStream as createWriteStream3,
|
|
30128
30306
|
existsSync as existsSync8,
|
|
@@ -30135,7 +30313,7 @@ import {
|
|
|
30135
30313
|
renameSync as renameSync6,
|
|
30136
30314
|
rmSync as rmSync4,
|
|
30137
30315
|
statSync as statSync5,
|
|
30138
|
-
unlinkSync as
|
|
30316
|
+
unlinkSync as unlinkSync6,
|
|
30139
30317
|
writeFileSync as writeFileSync5
|
|
30140
30318
|
} from "node:fs";
|
|
30141
30319
|
import { dirname as dirname4, join as join12, relative as relative2, resolve as resolve2 } from "node:path";
|
|
@@ -30497,7 +30675,7 @@ async function downloadFile(url2, destPath, fetchImpl, assetSize, signal) {
|
|
|
30497
30675
|
await pipeline3(nodeStream, createWriteStream3(destPath), { signal: timeout.signal });
|
|
30498
30676
|
} catch (err) {
|
|
30499
30677
|
try {
|
|
30500
|
-
|
|
30678
|
+
unlinkSync6(destPath);
|
|
30501
30679
|
} catch {}
|
|
30502
30680
|
throw err;
|
|
30503
30681
|
} finally {
|
|
@@ -30729,7 +30907,7 @@ async function downloadAndInstall(spec, tag, assets, platform, arch, fetchImpl,
|
|
|
30729
30907
|
} catch (err) {
|
|
30730
30908
|
error2(`[lsp] hash ${spec.id} failed: ${err}`);
|
|
30731
30909
|
try {
|
|
30732
|
-
|
|
30910
|
+
unlinkSync6(archivePath);
|
|
30733
30911
|
} catch {}
|
|
30734
30912
|
return null;
|
|
30735
30913
|
}
|
|
@@ -30740,7 +30918,7 @@ async function downloadAndInstall(spec, tag, assets, platform, arch, fetchImpl,
|
|
|
30740
30918
|
if (previousArchiveSha256 !== archiveSha256) {
|
|
30741
30919
|
error2(`[lsp] ${spec.id} ${tag}: TOFU sha256 mismatch — refusing install. ` + `Previously installed archive sha256=${previousArchiveSha256}, downloaded sha256=${archiveSha256}. ` + `This means the published release for tag ${tag} changed. Investigate before proceeding.`);
|
|
30742
30920
|
try {
|
|
30743
|
-
|
|
30921
|
+
unlinkSync6(archivePath);
|
|
30744
30922
|
} catch {}
|
|
30745
30923
|
return null;
|
|
30746
30924
|
}
|
|
@@ -30752,7 +30930,7 @@ async function downloadAndInstall(spec, tag, assets, platform, arch, fetchImpl,
|
|
|
30752
30930
|
return null;
|
|
30753
30931
|
} finally {
|
|
30754
30932
|
try {
|
|
30755
|
-
|
|
30933
|
+
unlinkSync6(archivePath);
|
|
30756
30934
|
} catch {}
|
|
30757
30935
|
}
|
|
30758
30936
|
const innerBinaryPath = join12(extractDir, spec.binaryPathInArchive(platform, arch, version2));
|
|
@@ -30763,7 +30941,7 @@ async function downloadAndInstall(spec, tag, assets, platform, arch, fetchImpl,
|
|
|
30763
30941
|
const targetBinary = ghBinaryPath(spec, platform);
|
|
30764
30942
|
mkdirSync8(dirname4(targetBinary), { recursive: true });
|
|
30765
30943
|
try {
|
|
30766
|
-
|
|
30944
|
+
copyFileSync4(innerBinaryPath, targetBinary);
|
|
30767
30945
|
if (platform !== "win32") {
|
|
30768
30946
|
const { chmodSync: chmodSync4 } = await import("node:fs");
|
|
30769
30947
|
chmodSync4(targetBinary, 493);
|
|
@@ -30952,13 +31130,18 @@ function sendIgnoredMessage(client, sessionId, text) {
|
|
|
30952
31130
|
}
|
|
30953
31131
|
}
|
|
30954
31132
|
async function readWarnedTools(bridge) {
|
|
31133
|
+
let resp;
|
|
31134
|
+
try {
|
|
31135
|
+
resp = await bridge.send("db_get_state", { key: "warned_tools" });
|
|
31136
|
+
} catch {
|
|
31137
|
+
return null;
|
|
31138
|
+
}
|
|
31139
|
+
if (resp.success === false)
|
|
31140
|
+
return null;
|
|
31141
|
+
const value = resp.data?.value;
|
|
31142
|
+
if (typeof value !== "string")
|
|
31143
|
+
return {};
|
|
30955
31144
|
try {
|
|
30956
|
-
const resp = await bridge.send("db_get_state", { key: "warned_tools" });
|
|
30957
|
-
if (resp.success === false)
|
|
30958
|
-
return {};
|
|
30959
|
-
const value = resp.data?.value;
|
|
30960
|
-
if (typeof value !== "string")
|
|
30961
|
-
return {};
|
|
30962
31145
|
const parsed = JSON.parse(value);
|
|
30963
31146
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
30964
31147
|
return {};
|
|
@@ -30967,12 +31150,16 @@ async function readWarnedTools(bridge) {
|
|
|
30967
31150
|
return {};
|
|
30968
31151
|
}
|
|
30969
31152
|
}
|
|
30970
|
-
async function
|
|
31153
|
+
async function warnedStatus(bridge, key) {
|
|
30971
31154
|
const warned = await readWarnedTools(bridge);
|
|
30972
|
-
|
|
31155
|
+
if (warned === null)
|
|
31156
|
+
return "unknown";
|
|
31157
|
+
return warned[key] === true || typeof warned[key] === "string" ? "warned" : "fresh";
|
|
30973
31158
|
}
|
|
30974
31159
|
async function recordWarning(bridge, key) {
|
|
30975
31160
|
const warned = await readWarnedTools(bridge);
|
|
31161
|
+
if (warned === null)
|
|
31162
|
+
return;
|
|
30976
31163
|
warned[key] = true;
|
|
30977
31164
|
try {
|
|
30978
31165
|
await bridge.send("db_set_state", {
|
|
@@ -31021,7 +31208,7 @@ async function deliverConfigureWarnings(opts, warnings) {
|
|
|
31021
31208
|
return;
|
|
31022
31209
|
for (const warning of warnings) {
|
|
31023
31210
|
const key = warningKey(warning, opts.projectRoot);
|
|
31024
|
-
if (await
|
|
31211
|
+
if (await warnedStatus(opts.bridge, key) !== "fresh")
|
|
31025
31212
|
continue;
|
|
31026
31213
|
if (!sendIgnoredMessage(opts.client, opts.sessionId, formatConfigureWarning(warning)))
|
|
31027
31214
|
continue;
|
|
@@ -31289,24 +31476,6 @@ function groupByFile(items, getFile) {
|
|
|
31289
31476
|
});
|
|
31290
31477
|
return groups;
|
|
31291
31478
|
}
|
|
31292
|
-
function distinctCount(values) {
|
|
31293
|
-
return new Set(values.filter((value) => Boolean(value))).size;
|
|
31294
|
-
}
|
|
31295
|
-
function severityBadge(theme, severity) {
|
|
31296
|
-
const label = severity === "information" ? "info" : severity;
|
|
31297
|
-
switch (severity) {
|
|
31298
|
-
case "error":
|
|
31299
|
-
return theme.fg("error", `[${label}]`);
|
|
31300
|
-
case "warning":
|
|
31301
|
-
return theme.fg("warning", `[${label}]`);
|
|
31302
|
-
case "information":
|
|
31303
|
-
return theme.fg("accent", `[${label}]`);
|
|
31304
|
-
case "hint":
|
|
31305
|
-
return theme.fg("muted", `[${label}]`);
|
|
31306
|
-
default:
|
|
31307
|
-
return theme.fg("muted", `[${label}]`);
|
|
31308
|
-
}
|
|
31309
|
-
}
|
|
31310
31479
|
function formatTimestamp(value) {
|
|
31311
31480
|
if (typeof value === "string" && value.length > 0)
|
|
31312
31481
|
return value;
|
|
@@ -31531,9 +31700,9 @@ function registerAstTools(pi, ctx, surface) {
|
|
|
31531
31700
|
pattern: params.pattern,
|
|
31532
31701
|
lang: params.lang
|
|
31533
31702
|
};
|
|
31534
|
-
if (params.paths
|
|
31703
|
+
if (!isEmptyParam(params.paths))
|
|
31535
31704
|
req.paths = params.paths;
|
|
31536
|
-
if (params.globs
|
|
31705
|
+
if (!isEmptyParam(params.globs))
|
|
31537
31706
|
req.globs = params.globs;
|
|
31538
31707
|
if (params.contextLines !== undefined)
|
|
31539
31708
|
req.context_lines = params.contextLines;
|
|
@@ -31561,9 +31730,9 @@ function registerAstTools(pi, ctx, surface) {
|
|
|
31561
31730
|
rewrite: params.rewrite,
|
|
31562
31731
|
lang: params.lang
|
|
31563
31732
|
};
|
|
31564
|
-
if (params.paths
|
|
31733
|
+
if (!isEmptyParam(params.paths))
|
|
31565
31734
|
req.paths = params.paths;
|
|
31566
|
-
if (params.globs
|
|
31735
|
+
if (!isEmptyParam(params.globs))
|
|
31567
31736
|
req.globs = params.globs;
|
|
31568
31737
|
req.dry_run = params.dryRun === true;
|
|
31569
31738
|
const response = await callBridge(bridge, "ast_replace", req, extCtx);
|
|
@@ -32509,6 +32678,10 @@ function formatDiffForPi(oldContent, newContent, contextLines = DEFAULT_CONTEXT_
|
|
|
32509
32678
|
}
|
|
32510
32679
|
|
|
32511
32680
|
// src/tools/hoisted.ts
|
|
32681
|
+
var DIAGNOSTICS_PARAM_DESCRIPTION = "When true, wait up to 3 seconds for fresh LSP diagnostics on the edited file and include them in the result. Defaults to the configured `lsp.diagnostics_on_edit` value (false unless configured); per-call true/false overrides. Use aft_inspect to check diagnostics across a batch of edits or before tests/commits.";
|
|
32682
|
+
function diagnosticsOnEditDefault(ctx) {
|
|
32683
|
+
return ctx.config.lsp?.diagnostics_on_edit ?? false;
|
|
32684
|
+
}
|
|
32512
32685
|
function containsPath(parent, child) {
|
|
32513
32686
|
const rel = relative3(parent, child);
|
|
32514
32687
|
return rel === "" || !rel.startsWith("..") && !isAbsolute2(rel);
|
|
@@ -32575,7 +32748,8 @@ var WriteParams = Type6.Object({
|
|
|
32575
32748
|
filePath: Type6.String({
|
|
32576
32749
|
description: "Path to the file to write (absolute or relative to project root)"
|
|
32577
32750
|
}),
|
|
32578
|
-
content: Type6.String({ description: "Full file contents to write" })
|
|
32751
|
+
content: Type6.String({ description: "Full file contents to write" }),
|
|
32752
|
+
diagnostics: Type6.Optional(Type6.Boolean({ description: DIAGNOSTICS_PARAM_DESCRIPTION }))
|
|
32579
32753
|
});
|
|
32580
32754
|
var EditParams = Type6.Object({
|
|
32581
32755
|
filePath: Type6.String({
|
|
@@ -32587,7 +32761,8 @@ var EditParams = Type6.Object({
|
|
|
32587
32761
|
occurrence: optionalInt(0, Number.MAX_SAFE_INTEGER),
|
|
32588
32762
|
appendContent: Type6.Optional(Type6.String({
|
|
32589
32763
|
description: "Append text to the end of the file (creates the file if missing, parent dirs auto-created). When set, oldString/newString are ignored."
|
|
32590
|
-
}))
|
|
32764
|
+
})),
|
|
32765
|
+
diagnostics: Type6.Optional(Type6.Boolean({ description: DIAGNOSTICS_PARAM_DESCRIPTION }))
|
|
32591
32766
|
});
|
|
32592
32767
|
var GrepParams = Type6.Object({
|
|
32593
32768
|
pattern: Type6.String({ description: "Regex pattern to search for" }),
|
|
@@ -32638,8 +32813,8 @@ function registerHoistedTools(pi, ctx, surface) {
|
|
|
32638
32813
|
pi.registerTool({
|
|
32639
32814
|
name: "write",
|
|
32640
32815
|
label: "write",
|
|
32641
|
-
description: "Write a file atomically with per-file backup
|
|
32642
|
-
promptSnippet: "Create or overwrite files (uses filePath; auto-formats;
|
|
32816
|
+
description: "Write a file atomically with per-file backup and optional auto-format. Parent directories are created automatically. Overwrites existing files. Uses `filePath` (not `path`). Edits return as soon as the write completes unless `lsp.diagnostics_on_edit` or a per-call `diagnostics: true` requests legacy sync-wait behavior. Call `aft_inspect` afterward to check diagnostics across a batch of edits.",
|
|
32817
|
+
promptSnippet: "Create or overwrite files (uses filePath; auto-formats; diagnostics follow lsp.diagnostics_on_edit unless overridden)",
|
|
32643
32818
|
promptGuidelines: ["Use write only for new files or complete rewrites."],
|
|
32644
32819
|
parameters: WriteParams,
|
|
32645
32820
|
async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
|
|
@@ -32650,8 +32825,8 @@ function registerHoistedTools(pi, ctx, surface) {
|
|
|
32650
32825
|
const response = await callBridge(bridge, "write", {
|
|
32651
32826
|
file: params.filePath,
|
|
32652
32827
|
content: params.content,
|
|
32653
|
-
diagnostics:
|
|
32654
|
-
|
|
32828
|
+
diagnostics: params.diagnostics ?? diagnosticsOnEditDefault(ctx),
|
|
32829
|
+
include_diff_content: true
|
|
32655
32830
|
}, extCtx);
|
|
32656
32831
|
return buildMutationResult(params.filePath, response);
|
|
32657
32832
|
},
|
|
@@ -32667,8 +32842,8 @@ function registerHoistedTools(pi, ctx, surface) {
|
|
|
32667
32842
|
pi.registerTool({
|
|
32668
32843
|
name: "edit",
|
|
32669
32844
|
label: "edit",
|
|
32670
|
-
description: "Find-and-replace edit with progressive fuzzy matching (handles whitespace and Unicode drift). Uses `filePath`, `oldString`, `newString`. Errors on multiple matches — use `occurrence` to pick one, or `replaceAll: true`.
|
|
32671
|
-
promptSnippet: "Targeted find-and-replace (uses filePath/oldString/newString; occurrence or replaceAll for disambiguation; fuzzy whitespace matching). Pass appendContent to append to a file (creates if missing).",
|
|
32845
|
+
description: "Find-and-replace edit with progressive fuzzy matching (handles whitespace and Unicode drift). Uses `filePath`, `oldString`, `newString`. Errors on multiple matches — use `occurrence` to pick one, or `replaceAll: true`. Edits return as soon as the write completes unless `lsp.diagnostics_on_edit` or a per-call `diagnostics: true` requests legacy sync-wait behavior. Call `aft_inspect` afterward to check diagnostics across a batch of edits.",
|
|
32846
|
+
promptSnippet: "Targeted find-and-replace (uses filePath/oldString/newString; occurrence or replaceAll for disambiguation; fuzzy whitespace matching). Pass appendContent to append to a file (creates if missing). Diagnostics follow lsp.diagnostics_on_edit unless overridden.",
|
|
32672
32847
|
promptGuidelines: [
|
|
32673
32848
|
"Prefer edit over write when changing part of an existing file.",
|
|
32674
32849
|
"Include enough surrounding context in oldString to make the match unique, or set replaceAll/occurrence explicitly.",
|
|
@@ -32685,8 +32860,8 @@ function registerHoistedTools(pi, ctx, surface) {
|
|
|
32685
32860
|
op: "append",
|
|
32686
32861
|
file: params.filePath,
|
|
32687
32862
|
append_content: params.appendContent,
|
|
32688
|
-
diagnostics:
|
|
32689
|
-
|
|
32863
|
+
diagnostics: params.diagnostics ?? diagnosticsOnEditDefault(ctx),
|
|
32864
|
+
include_diff_content: true
|
|
32690
32865
|
};
|
|
32691
32866
|
const response2 = await callBridge(bridge, "edit_match", req2, extCtx);
|
|
32692
32867
|
return buildMutationResult(params.filePath, response2);
|
|
@@ -32695,8 +32870,8 @@ function registerHoistedTools(pi, ctx, surface) {
|
|
|
32695
32870
|
file: params.filePath,
|
|
32696
32871
|
match: params.oldString ?? "",
|
|
32697
32872
|
replacement: params.newString ?? "",
|
|
32698
|
-
diagnostics:
|
|
32699
|
-
|
|
32873
|
+
diagnostics: params.diagnostics ?? diagnosticsOnEditDefault(ctx),
|
|
32874
|
+
include_diff_content: true
|
|
32700
32875
|
};
|
|
32701
32876
|
if (params.replaceAll === true)
|
|
32702
32877
|
req.replace_all = true;
|
|
@@ -32765,15 +32940,6 @@ function buildMutationResult(filePath, response) {
|
|
|
32765
32940
|
}
|
|
32766
32941
|
const summaryHeader = replacements !== undefined ? `Edited ${filePath} (+${additions}/-${deletions}, ${replacements} replacement${replacements === 1 ? "" : "s"})` : `Wrote ${filePath} (+${additions}/-${deletions})`;
|
|
32767
32942
|
let text = summaryHeader;
|
|
32768
|
-
if (diffText)
|
|
32769
|
-
text += `
|
|
32770
|
-
|
|
32771
|
-
${diffText}`;
|
|
32772
|
-
if (truncated) {
|
|
32773
|
-
text += `
|
|
32774
|
-
|
|
32775
|
-
(diff truncated — file too large to include before/after content)`;
|
|
32776
|
-
}
|
|
32777
32943
|
if (noOp) {
|
|
32778
32944
|
text += `
|
|
32779
32945
|
|
|
@@ -33053,106 +33219,234 @@ function registerImportTools(pi, ctx) {
|
|
|
33053
33219
|
});
|
|
33054
33220
|
}
|
|
33055
33221
|
|
|
33056
|
-
// src/tools/
|
|
33057
|
-
import { StringEnum as StringEnum3 } from "@earendil-works/pi-ai";
|
|
33222
|
+
// src/tools/inspect.ts
|
|
33058
33223
|
import { Type as Type8 } from "typebox";
|
|
33059
|
-
var
|
|
33060
|
-
|
|
33061
|
-
|
|
33062
|
-
description: "Directory to get diagnostics for (mutually exclusive with filePath)"
|
|
33224
|
+
var InspectParams = Type8.Object({
|
|
33225
|
+
sections: Type8.Optional(Type8.Union([Type8.String(), Type8.Array(Type8.String())], {
|
|
33226
|
+
description: "Categories to include in detailed drill-down (e.g. 'todos' or ['todos', 'dead_code']). Use 'all' for every active category. Omit for summary-only mode."
|
|
33063
33227
|
})),
|
|
33064
|
-
|
|
33065
|
-
description: "
|
|
33228
|
+
scope: Type8.Optional(Type8.Union([Type8.String(), Type8.Array(Type8.String())], {
|
|
33229
|
+
description: "Restrict scan/results to paths under this scope (file or directory, absolute or relative to project root). Tier 1 scopes the scan; Tier 2 scans project-wide and applies scope as a result filter."
|
|
33066
33230
|
})),
|
|
33067
|
-
|
|
33068
|
-
|
|
33231
|
+
topK: Type8.Optional(Type8.Integer({
|
|
33232
|
+
minimum: 1,
|
|
33233
|
+
maximum: 100,
|
|
33234
|
+
default: 20,
|
|
33235
|
+
description: "Max drill-down items per category. Default 20, max 100."
|
|
33069
33236
|
}))
|
|
33070
33237
|
});
|
|
33071
|
-
|
|
33072
|
-
|
|
33073
|
-
|
|
33074
|
-
|
|
33075
|
-
|
|
33076
|
-
|
|
33077
|
-
|
|
33078
|
-
|
|
33079
|
-
|
|
33080
|
-
|
|
33081
|
-
|
|
33082
|
-
if (
|
|
33083
|
-
|
|
33084
|
-
return sections;
|
|
33238
|
+
var TIER2_INSPECT_CATEGORIES = new Set(["dead_code", "unused_exports", "duplicates"]);
|
|
33239
|
+
var INSPECT_TIER2_RUN_TIMEOUT_MS = 5 * 60000;
|
|
33240
|
+
var INSPECT_TIER2_MIN_TRIGGER_INTERVAL_MS = 4 * 60000;
|
|
33241
|
+
var runningTier2Categories = new WeakMap;
|
|
33242
|
+
var lastTier2TriggerAtByBridge = new WeakMap;
|
|
33243
|
+
function normalizeStringOrArray(value) {
|
|
33244
|
+
return isEmptyParam(value) ? undefined : value;
|
|
33245
|
+
}
|
|
33246
|
+
function validateOptionalTopK(value) {
|
|
33247
|
+
if (value === undefined || value === null || value === "")
|
|
33248
|
+
return;
|
|
33249
|
+
if (typeof value !== "number" || !Number.isInteger(value)) {
|
|
33250
|
+
throw new Error("topK must be an integer between 1 and 100");
|
|
33085
33251
|
}
|
|
33086
|
-
|
|
33087
|
-
|
|
33088
|
-
|
|
33089
|
-
|
|
33090
|
-
|
|
33091
|
-
|
|
33092
|
-
|
|
33093
|
-
|
|
33094
|
-
|
|
33095
|
-
|
|
33096
|
-
|
|
33097
|
-
|
|
33098
|
-
|
|
33099
|
-
|
|
33252
|
+
if (value < 1 || value > 100) {
|
|
33253
|
+
throw new Error("topK must be between 1 and 100");
|
|
33254
|
+
}
|
|
33255
|
+
return value;
|
|
33256
|
+
}
|
|
33257
|
+
function diagnosticsServerSummary(section) {
|
|
33258
|
+
const pending = Array.isArray(section.servers_pending) ? section.servers_pending.filter((item) => typeof item === "string") : [];
|
|
33259
|
+
const notInstalled = Array.isArray(section.servers_not_installed) ? section.servers_not_installed.filter((item) => typeof item === "string") : [];
|
|
33260
|
+
const parts = [];
|
|
33261
|
+
if (pending.length > 0)
|
|
33262
|
+
parts.push(`pending: ${pending.join(", ")}`);
|
|
33263
|
+
if (notInstalled.length > 0)
|
|
33264
|
+
parts.push(`not installed: ${notInstalled.join(", ")}`);
|
|
33265
|
+
return parts.length > 0 ? parts.join("; ") : "none reported";
|
|
33266
|
+
}
|
|
33267
|
+
function diagnosticsSummaryPart(summary) {
|
|
33268
|
+
const section = asRecord2(summary?.diagnostics);
|
|
33269
|
+
if (!section)
|
|
33270
|
+
return;
|
|
33271
|
+
const errors3 = asNumber(section.errors);
|
|
33272
|
+
const warnings = asNumber(section.warnings);
|
|
33273
|
+
const info = asNumber(section.info);
|
|
33274
|
+
const hints = asNumber(section.hints);
|
|
33275
|
+
const hasCounts = [errors3, warnings, info, hints].some((value) => value !== undefined);
|
|
33276
|
+
const counts = `${errors3 ?? 0} errors/${warnings ?? 0} warnings/${info ?? 0} info/${hints ?? 0} hints`;
|
|
33277
|
+
const status = asString(section.status);
|
|
33278
|
+
if (status === "pending") {
|
|
33279
|
+
return hasCounts ? `diagnostics ${counts} so far — still pending (servers: ${diagnosticsServerSummary(section)})` : `diagnostics pending (servers: ${diagnosticsServerSummary(section)})`;
|
|
33280
|
+
}
|
|
33281
|
+
if (status === "incomplete") {
|
|
33282
|
+
return hasCounts ? `diagnostics ${counts} (incomplete — servers: ${diagnosticsServerSummary(section)})` : `diagnostics unavailable (status incomplete; servers: ${diagnosticsServerSummary(section)})`;
|
|
33283
|
+
}
|
|
33284
|
+
if (hasCounts) {
|
|
33285
|
+
return `diagnostics ${counts}`;
|
|
33286
|
+
}
|
|
33287
|
+
return;
|
|
33288
|
+
}
|
|
33289
|
+
function diagnosticLocation(diagnostic) {
|
|
33290
|
+
const file2 = asString(diagnostic.file) ?? "(unknown file)";
|
|
33291
|
+
const line = asNumber(diagnostic.line);
|
|
33292
|
+
const column = asNumber(diagnostic.column);
|
|
33293
|
+
if (line === undefined)
|
|
33294
|
+
return file2;
|
|
33295
|
+
if (column === undefined)
|
|
33296
|
+
return `${file2}:${line}`;
|
|
33297
|
+
return `${file2}:${line}:${column}`;
|
|
33298
|
+
}
|
|
33299
|
+
function diagnosticsDetailSection(details) {
|
|
33300
|
+
const diagnostics = asRecords(details?.diagnostics);
|
|
33301
|
+
if (diagnostics.length === 0)
|
|
33302
|
+
return;
|
|
33303
|
+
const lines = ["diagnostics"];
|
|
33304
|
+
for (const diagnostic of diagnostics) {
|
|
33305
|
+
const severity = asString(diagnostic.severity) ?? "information";
|
|
33306
|
+
const message = asString(diagnostic.message) ?? "(no message)";
|
|
33307
|
+
const source = asString(diagnostic.source);
|
|
33308
|
+
const suffix = source ? ` [${source}]` : "";
|
|
33309
|
+
lines.push(`- ${diagnosticLocation(diagnostic)} ${severity} ${message}${suffix}`);
|
|
33310
|
+
}
|
|
33311
|
+
return lines.join(`
|
|
33312
|
+
`);
|
|
33313
|
+
}
|
|
33314
|
+
function countFrom(summary, key) {
|
|
33315
|
+
const section = asRecord2(summary?.[key]);
|
|
33316
|
+
return asNumber(section?.count);
|
|
33317
|
+
}
|
|
33318
|
+
function tier2SummaryPart(summary, key, label) {
|
|
33319
|
+
const section = asRecord2(summary?.[key]);
|
|
33320
|
+
const count = asNumber(section?.count);
|
|
33321
|
+
if (count !== undefined)
|
|
33322
|
+
return `${label} ${count}`;
|
|
33323
|
+
const status = asString(section?.status);
|
|
33324
|
+
return `${label} ${status ?? "unavailable"}`;
|
|
33325
|
+
}
|
|
33326
|
+
function tier2RefreshCategories(response) {
|
|
33327
|
+
const scannerState = asRecord2(response.scanner_state);
|
|
33328
|
+
const categories = new Set;
|
|
33329
|
+
for (const key of ["pending_categories", "stale_categories"]) {
|
|
33330
|
+
const values = scannerState?.[key];
|
|
33331
|
+
if (!Array.isArray(values))
|
|
33332
|
+
continue;
|
|
33333
|
+
for (const category of values) {
|
|
33334
|
+
if (typeof category === "string" && TIER2_INSPECT_CATEGORIES.has(category)) {
|
|
33335
|
+
categories.add(category);
|
|
33336
|
+
}
|
|
33337
|
+
}
|
|
33100
33338
|
}
|
|
33339
|
+
return [...categories];
|
|
33340
|
+
}
|
|
33341
|
+
function runPendingTier2Categories(bridge, categories, extCtx) {
|
|
33342
|
+
const now = Date.now();
|
|
33343
|
+
const running = runningTier2Categories.get(bridge) ?? new Set;
|
|
33344
|
+
const lastTriggerAt = lastTier2TriggerAtByBridge.get(bridge) ?? new Map;
|
|
33345
|
+
const toRun = categories.filter((category) => {
|
|
33346
|
+
if (running.has(category))
|
|
33347
|
+
return false;
|
|
33348
|
+
const previousTriggerAt = lastTriggerAt.get(category);
|
|
33349
|
+
return previousTriggerAt === undefined || previousTriggerAt + INSPECT_TIER2_MIN_TRIGGER_INTERVAL_MS <= now;
|
|
33350
|
+
});
|
|
33351
|
+
if (toRun.length === 0)
|
|
33352
|
+
return;
|
|
33353
|
+
for (const category of toRun) {
|
|
33354
|
+
running.add(category);
|
|
33355
|
+
lastTriggerAt.set(category, now);
|
|
33356
|
+
}
|
|
33357
|
+
runningTier2Categories.set(bridge, running);
|
|
33358
|
+
lastTier2TriggerAtByBridge.set(bridge, lastTriggerAt);
|
|
33359
|
+
callBridge(bridge, "inspect_tier2_run", { categories: toRun }, extCtx, {
|
|
33360
|
+
transportTimeoutMs: INSPECT_TIER2_RUN_TIMEOUT_MS
|
|
33361
|
+
}).catch(() => {}).finally(() => {
|
|
33362
|
+
const active = runningTier2Categories.get(bridge);
|
|
33363
|
+
if (!active)
|
|
33364
|
+
return;
|
|
33365
|
+
for (const category of toRun) {
|
|
33366
|
+
active.delete(category);
|
|
33367
|
+
}
|
|
33368
|
+
if (active.size === 0) {
|
|
33369
|
+
runningTier2Categories.delete(bridge);
|
|
33370
|
+
}
|
|
33371
|
+
});
|
|
33372
|
+
}
|
|
33373
|
+
function buildInspectSections(payload, theme) {
|
|
33374
|
+
const response = asRecord2(payload);
|
|
33375
|
+
if (!response)
|
|
33376
|
+
return [theme.fg("muted", "No inspect snapshot available.")];
|
|
33377
|
+
const summary = asRecord2(response.summary);
|
|
33378
|
+
const metrics = asRecord2(summary?.metrics);
|
|
33379
|
+
const scannerState = asRecord2(response.scanner_state);
|
|
33380
|
+
const stale = Array.isArray(scannerState?.stale_categories) ? scannerState.stale_categories.length : 0;
|
|
33381
|
+
const pending = Array.isArray(scannerState?.pending_categories) ? scannerState.pending_categories.length : 0;
|
|
33382
|
+
const parts = [
|
|
33383
|
+
`todos ${countFrom(summary, "todos") ?? 0}`,
|
|
33384
|
+
diagnosticsSummaryPart(summary),
|
|
33385
|
+
`metrics ${asNumber(metrics?.files) ?? 0} files/${asNumber(metrics?.symbols) ?? 0} symbols`,
|
|
33386
|
+
tier2SummaryPart(summary, "dead_code", "dead code"),
|
|
33387
|
+
tier2SummaryPart(summary, "unused_exports", "unused exports"),
|
|
33388
|
+
tier2SummaryPart(summary, "duplicates", "duplicates")
|
|
33389
|
+
].filter((part) => Boolean(part));
|
|
33390
|
+
const sections = [theme.fg("accent", parts.join(" · "))];
|
|
33391
|
+
if (stale > 0 || pending > 0) {
|
|
33392
|
+
sections.push(theme.fg("warning", `scanner state: ${stale} stale · ${pending} pending`));
|
|
33393
|
+
}
|
|
33394
|
+
const details = asRecord2(response.details);
|
|
33395
|
+
if (details) {
|
|
33396
|
+
const names = Object.keys(details);
|
|
33397
|
+
sections.push(names.length > 0 ? `details: ${names.join(", ")}` : theme.fg("muted", "No drill-down details returned."));
|
|
33398
|
+
const diagnosticsDetails = diagnosticsDetailSection(details);
|
|
33399
|
+
if (diagnosticsDetails)
|
|
33400
|
+
sections.push(diagnosticsDetails);
|
|
33401
|
+
}
|
|
33402
|
+
const text = asString(response.text);
|
|
33403
|
+
if (text)
|
|
33404
|
+
sections.push(text);
|
|
33101
33405
|
return sections;
|
|
33102
33406
|
}
|
|
33103
|
-
function
|
|
33104
|
-
const
|
|
33105
|
-
const
|
|
33106
|
-
|
|
33107
|
-
|
|
33108
|
-
].filter(Boolean).join(" ");
|
|
33109
|
-
return renderToolCall("lsp diagnostics", summary, theme, context);
|
|
33407
|
+
function renderInspectCall(args, theme, context) {
|
|
33408
|
+
const sections = Array.isArray(args.sections) ? `${args.sections.length} sections` : args.sections;
|
|
33409
|
+
const scope = Array.isArray(args.scope) ? `${args.scope.length} scopes` : args.scope;
|
|
33410
|
+
const summary = [sections, scope, args.topK ? `topK=${args.topK}` : undefined].filter(Boolean).join(" ");
|
|
33411
|
+
return renderToolCall("inspect", summary ? theme.fg("toolOutput", summary) : undefined, theme, context);
|
|
33110
33412
|
}
|
|
33111
|
-
function
|
|
33413
|
+
function renderInspectResult(result, theme, context) {
|
|
33112
33414
|
if (context.isError)
|
|
33113
|
-
return renderErrorResult(result, "
|
|
33114
|
-
return renderSections(
|
|
33415
|
+
return renderErrorResult(result, "inspect failed", theme, context);
|
|
33416
|
+
return renderSections(buildInspectSections(extractStructuredPayload(result), theme), context);
|
|
33115
33417
|
}
|
|
33116
|
-
function
|
|
33418
|
+
function registerInspectTool(pi, ctx) {
|
|
33117
33419
|
pi.registerTool({
|
|
33118
|
-
name: "
|
|
33119
|
-
label: "
|
|
33120
|
-
description: "
|
|
33121
|
-
|
|
33420
|
+
name: "aft_inspect",
|
|
33421
|
+
label: "inspect",
|
|
33422
|
+
description: "Codebase health snapshot. One call returns summary stats for: TODOs, diagnostics, file/symbol metrics, dead code, unused exports, code duplicates. Pass `sections` for per-category drill-down details.\n\n" + "Categories run in tiers — Tier 1 (todos, metrics) return synchronously from cache. Tier 2 (dead_code, unused_exports, duplicates) run asynchronously on demand: when a call sees cold `pending_categories: [...]` or stale `stale_categories: [...]`, Pi quietly starts a background Tier 2 warmup (deduped while in-flight and rate-limited per category to at most once every 4 minutes, matching OpenCode's default idle window). The current call may still return pending results while the cache warms; a later call can use cached data.\n\n" + `Use when: starting work on unfamiliar code, after multi-edit batches to check diagnostics, before a refactor, before review, or to verify cleanup completeness.
|
|
33423
|
+
|
|
33424
|
+
` + "Treat `dead_code` as a hint, not proof: reachability is call-based, so symbols reached only via method dispatch or referenced only in type position may be false positives — verify before deleting.",
|
|
33425
|
+
parameters: InspectParams,
|
|
33122
33426
|
async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
|
|
33123
|
-
const hasFile = typeof params.filePath === "string" && params.filePath.length > 0;
|
|
33124
|
-
const hasDir = typeof params.directory === "string" && params.directory.length > 0;
|
|
33125
|
-
if (hasFile && hasDir) {
|
|
33126
|
-
throw new Error("'filePath' and 'directory' are mutually exclusive — provide one or neither");
|
|
33127
|
-
}
|
|
33128
33427
|
const bridge = bridgeFor(ctx, extCtx.cwd);
|
|
33129
|
-
const
|
|
33130
|
-
|
|
33131
|
-
|
|
33132
|
-
|
|
33133
|
-
|
|
33134
|
-
|
|
33135
|
-
req.severity = params.severity;
|
|
33136
|
-
if (params.waitMs !== undefined)
|
|
33137
|
-
req.wait_ms = params.waitMs;
|
|
33138
|
-
const response = await callBridge(bridge, "lsp_diagnostics", req, extCtx);
|
|
33139
|
-
return textResult(JSON.stringify(response, null, 2));
|
|
33428
|
+
const sections = normalizeStringOrArray(params.sections);
|
|
33429
|
+
const scope = normalizeStringOrArray(params.scope);
|
|
33430
|
+
const topK = validateOptionalTopK(params.topK);
|
|
33431
|
+
const response = await callBridge(bridge, "inspect", { sections, scope, topK }, extCtx);
|
|
33432
|
+
runPendingTier2Categories(bridge, tier2RefreshCategories(response), extCtx);
|
|
33433
|
+
return textResult(response.text ?? JSON.stringify(response, null, 2), response);
|
|
33140
33434
|
},
|
|
33141
33435
|
renderCall(args, theme, context) {
|
|
33142
|
-
return
|
|
33436
|
+
return renderInspectCall(args, theme, context);
|
|
33143
33437
|
},
|
|
33144
33438
|
renderResult(result, _options, theme, context) {
|
|
33145
|
-
return
|
|
33439
|
+
return renderInspectResult(result, theme, context);
|
|
33146
33440
|
}
|
|
33147
33441
|
});
|
|
33148
33442
|
}
|
|
33149
33443
|
|
|
33150
33444
|
// src/tools/navigate.ts
|
|
33151
|
-
import { StringEnum as
|
|
33445
|
+
import { StringEnum as StringEnum3 } from "@earendil-works/pi-ai";
|
|
33152
33446
|
import { Type as Type9 } from "typebox";
|
|
33153
33447
|
function navigateParamsSchema() {
|
|
33154
33448
|
return Type9.Object({
|
|
33155
|
-
op:
|
|
33449
|
+
op: StringEnum3(["call_tree", "callers", "trace_to", "trace_to_symbol", "impact", "trace_data"], {
|
|
33156
33450
|
description: "Navigation operation"
|
|
33157
33451
|
}),
|
|
33158
33452
|
filePath: Type9.String({
|
|
@@ -33309,7 +33603,7 @@ function renderNavigateCall(args, theme, context) {
|
|
|
33309
33603
|
theme.fg("toolOutput", args.symbol),
|
|
33310
33604
|
args.toSymbol ? theme.fg("toolOutput", `→ ${args.toSymbol}`) : undefined
|
|
33311
33605
|
].filter(Boolean).join(" ");
|
|
33312
|
-
return renderToolCall("
|
|
33606
|
+
return renderToolCall("callgraph", summary, theme, context);
|
|
33313
33607
|
}
|
|
33314
33608
|
function renderNavigateResult(result, args, theme, context) {
|
|
33315
33609
|
if (context.isError)
|
|
@@ -33318,9 +33612,9 @@ function renderNavigateResult(result, args, theme, context) {
|
|
|
33318
33612
|
}
|
|
33319
33613
|
function registerNavigateTool(pi, ctx) {
|
|
33320
33614
|
pi.registerTool({
|
|
33321
|
-
name: "
|
|
33322
|
-
label: "
|
|
33323
|
-
description: "
|
|
33615
|
+
name: "aft_callgraph",
|
|
33616
|
+
label: "callgraph",
|
|
33617
|
+
description: "Answer code-relationship questions from a real call graph — instead of grep + read chains. Reach for this whenever the question is about how symbols connect. All ops require both `filePath` and `symbol`. Use `callers` for call sites (before renaming/signature changes), `impact` for blast radius (what breaks if a symbol changes), `call_tree` for what a function calls, `trace_to` for how execution reaches a symbol from entry points, `trace_to_symbol` for the shortest path from one symbol to another (requires `toSymbol`; if ambiguous, the error returns candidate files — retry with `toFile`), `trace_data` to follow a value across assignments/params.",
|
|
33324
33618
|
parameters: navigateParamsSchema(),
|
|
33325
33619
|
async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
|
|
33326
33620
|
if (params.op === "trace_data" && !params.expression) {
|
|
@@ -33582,10 +33876,26 @@ Pass a single \`target\`:
|
|
|
33582
33876
|
parameters: ZoomParams,
|
|
33583
33877
|
async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
|
|
33584
33878
|
const bridge = bridgeFor(ctx, extCtx.cwd);
|
|
33585
|
-
const
|
|
33586
|
-
|
|
33587
|
-
|
|
33588
|
-
|
|
33879
|
+
const hasTargetsProvided = (t) => {
|
|
33880
|
+
if (isEmptyParam(t))
|
|
33881
|
+
return false;
|
|
33882
|
+
const entryEmpty = (entry) => {
|
|
33883
|
+
if (!entry || typeof entry !== "object")
|
|
33884
|
+
return true;
|
|
33885
|
+
const fp = entry.filePath;
|
|
33886
|
+
const sym = entry.symbol;
|
|
33887
|
+
const fpEmpty = typeof fp !== "string" || fp.length === 0;
|
|
33888
|
+
const symEmpty = typeof sym !== "string" || sym.length === 0;
|
|
33889
|
+
return fpEmpty && symEmpty;
|
|
33890
|
+
};
|
|
33891
|
+
if (Array.isArray(t))
|
|
33892
|
+
return !t.every(entryEmpty);
|
|
33893
|
+
return !entryEmpty(t);
|
|
33894
|
+
};
|
|
33895
|
+
const hasFilePath = !isEmptyParam(params.filePath);
|
|
33896
|
+
const hasUrl = !isEmptyParam(params.url);
|
|
33897
|
+
const hasTargets = hasTargetsProvided(params.targets);
|
|
33898
|
+
const hasSymbols = !isEmptyParam(params.symbols);
|
|
33589
33899
|
if (hasTargets) {
|
|
33590
33900
|
if (hasFilePath || hasUrl || hasSymbols) {
|
|
33591
33901
|
throw new Error("'targets' is mutually exclusive with 'filePath', 'url', and 'symbols'");
|
|
@@ -33738,10 +34048,10 @@ function formatOutlineFilesText(response) {
|
|
|
33738
34048
|
}
|
|
33739
34049
|
|
|
33740
34050
|
// src/tools/refactor.ts
|
|
33741
|
-
import { StringEnum as
|
|
34051
|
+
import { StringEnum as StringEnum4 } from "@earendil-works/pi-ai";
|
|
33742
34052
|
import { Type as Type11 } from "typebox";
|
|
33743
34053
|
var RefactorParams = Type11.Object({
|
|
33744
|
-
op:
|
|
34054
|
+
op: StringEnum4(["move", "extract", "inline"], { description: "Refactoring operation" }),
|
|
33745
34055
|
filePath: Type11.String({
|
|
33746
34056
|
description: "Source file (absolute or relative to project root)"
|
|
33747
34057
|
}),
|
|
@@ -33808,14 +34118,23 @@ function registerRefactorTool(pi, ctx) {
|
|
|
33808
34118
|
extract: "extract_function",
|
|
33809
34119
|
inline: "inline_symbol"
|
|
33810
34120
|
};
|
|
34121
|
+
if ((params.op === "move" || params.op === "inline") && isEmptyParam(params.symbol)) {
|
|
34122
|
+
throw new Error(`'symbol' is required for '${params.op}' op`);
|
|
34123
|
+
}
|
|
34124
|
+
if (params.op === "move" && isEmptyParam(params.destination)) {
|
|
34125
|
+
throw new Error("'destination' is required for 'move' op");
|
|
34126
|
+
}
|
|
34127
|
+
if (params.op === "extract" && isEmptyParam(params.name)) {
|
|
34128
|
+
throw new Error("'name' is required for 'extract' op");
|
|
34129
|
+
}
|
|
33811
34130
|
const req = { file: params.filePath };
|
|
33812
|
-
if (params.symbol
|
|
34131
|
+
if (!isEmptyParam(params.symbol))
|
|
33813
34132
|
req.symbol = params.symbol;
|
|
33814
|
-
if (params.destination
|
|
34133
|
+
if (!isEmptyParam(params.destination))
|
|
33815
34134
|
req.destination = params.destination;
|
|
33816
|
-
if (params.scope
|
|
34135
|
+
if (!isEmptyParam(params.scope))
|
|
33817
34136
|
req.scope = params.scope;
|
|
33818
|
-
if (params.name
|
|
34137
|
+
if (!isEmptyParam(params.name))
|
|
33819
34138
|
req.name = params.name;
|
|
33820
34139
|
const startLine = coerceOptionalInt(params.startLine, "startLine", 1, Number.MAX_SAFE_INTEGER);
|
|
33821
34140
|
const endLine = coerceOptionalInt(params.endLine, "endLine", 1, Number.MAX_SAFE_INTEGER);
|
|
@@ -33840,10 +34159,10 @@ function registerRefactorTool(pi, ctx) {
|
|
|
33840
34159
|
}
|
|
33841
34160
|
|
|
33842
34161
|
// src/tools/safety.ts
|
|
33843
|
-
import { StringEnum as
|
|
34162
|
+
import { StringEnum as StringEnum5 } from "@earendil-works/pi-ai";
|
|
33844
34163
|
import { Type as Type12 } from "typebox";
|
|
33845
34164
|
var SafetyParams = Type12.Object({
|
|
33846
|
-
op:
|
|
34165
|
+
op: StringEnum5(["undo", "history", "checkpoint", "restore", "list"], {
|
|
33847
34166
|
description: "Safety operation"
|
|
33848
34167
|
}),
|
|
33849
34168
|
filePath: Type12.Optional(Type12.String({
|
|
@@ -33982,35 +34301,81 @@ function registerSafetyTool(pi, ctx) {
|
|
|
33982
34301
|
|
|
33983
34302
|
// src/tools/semantic.ts
|
|
33984
34303
|
import { Type as Type13 } from "typebox";
|
|
34304
|
+
function semanticHonestyNote(response, theme) {
|
|
34305
|
+
const notes = [];
|
|
34306
|
+
if (response.more_available === true)
|
|
34307
|
+
notes.push("more results available");
|
|
34308
|
+
if (response.engine_capped === true)
|
|
34309
|
+
notes.push("enumeration capped");
|
|
34310
|
+
if (response.fully_degraded === true)
|
|
34311
|
+
notes.push("fully degraded");
|
|
34312
|
+
if (response.complete === false)
|
|
34313
|
+
notes.push("partial/incomplete");
|
|
34314
|
+
return notes.length > 0 ? theme.fg("warning", `Search status: ${notes.join("; ")}.`) : undefined;
|
|
34315
|
+
}
|
|
33985
34316
|
var SearchParams2 = Type13.Object({
|
|
33986
34317
|
query: Type13.String({
|
|
33987
|
-
description: "Concept
|
|
34318
|
+
description: "Concept, regex, literal text, filename, or capability to find. Examples: 'fuzzy match with whitespace tolerance', '^export', 'Cargo.lock'."
|
|
33988
34319
|
}),
|
|
33989
|
-
topK: Type13.Optional(Type13.
|
|
34320
|
+
topK: Type13.Optional(Type13.Integer({
|
|
34321
|
+
minimum: 1,
|
|
34322
|
+
maximum: 100,
|
|
34323
|
+
default: 10,
|
|
34324
|
+
description: "Maximum number of results (default: 10, max: 100)"
|
|
34325
|
+
})),
|
|
34326
|
+
hint: Type13.Optional(Type13.Union([
|
|
34327
|
+
Type13.Literal("regex"),
|
|
34328
|
+
Type13.Literal("literal"),
|
|
34329
|
+
Type13.Literal("semantic"),
|
|
34330
|
+
Type13.Literal("auto")
|
|
34331
|
+
], {
|
|
34332
|
+
description: "Optional routing hint. Defaults to 'auto'."
|
|
34333
|
+
}))
|
|
33990
34334
|
});
|
|
33991
34335
|
function buildSemanticSections(args, payload, theme) {
|
|
33992
34336
|
const response = asRecord2(payload);
|
|
33993
34337
|
if (!response)
|
|
33994
|
-
return [theme.fg("muted", "No
|
|
34338
|
+
return [theme.fg("muted", "No search result.")];
|
|
33995
34339
|
const status = asString(response.status) ?? "unknown";
|
|
34340
|
+
const semanticStatus = asString(response.semantic_status) ?? status;
|
|
34341
|
+
const interpretedAs = asString(response.interpreted_as) ?? "unknown";
|
|
34342
|
+
const queryKind = asString(response.query_kind);
|
|
33996
34343
|
const sections = [
|
|
33997
|
-
`${theme.fg(
|
|
34344
|
+
`${theme.fg(semanticStatus === "ready" ? "success" : "warning", `semantic: ${semanticStatus}`)} ${theme.fg("muted", `mode=${interpretedAs}${queryKind ? ` kind=${queryKind}` : ""} query=${JSON.stringify(args.query)} topK=${args.topK ?? 10}`)}`
|
|
33998
34345
|
];
|
|
33999
|
-
|
|
34346
|
+
const warnings = Array.isArray(response.warnings) ? response.warnings.filter((warning) => typeof warning === "string") : [];
|
|
34347
|
+
if (warnings.length > 0) {
|
|
34348
|
+
sections.push(warnings.map((warning) => theme.fg("warning", `⚠ ${warning}`)).join(`
|
|
34349
|
+
`));
|
|
34350
|
+
}
|
|
34351
|
+
const honestyNote = semanticHonestyNote(response, theme);
|
|
34352
|
+
if (honestyNote)
|
|
34353
|
+
sections.push(honestyNote);
|
|
34354
|
+
const results = asRecords(response.results);
|
|
34355
|
+
if (status !== "ready" && results.length === 0) {
|
|
34000
34356
|
sections.push(asString(response.text) ?? theme.fg("muted", "Semantic index is not ready."));
|
|
34001
34357
|
return sections;
|
|
34002
34358
|
}
|
|
34003
|
-
const results = asRecords(response.results);
|
|
34004
34359
|
if (results.length === 0) {
|
|
34005
|
-
sections.push(theme.fg("muted", "No
|
|
34360
|
+
sections.push(theme.fg("muted", "No matches found."));
|
|
34006
34361
|
return sections;
|
|
34007
34362
|
}
|
|
34008
34363
|
const grouped = groupByFile(results, (result) => asString(result.file));
|
|
34009
34364
|
for (const [file2, fileResults] of grouped.entries()) {
|
|
34010
34365
|
const lines = [theme.fg("accent", shortenPath(file2))];
|
|
34011
34366
|
fileResults.forEach((result) => {
|
|
34367
|
+
if (asString(result.kind) === "GrepLine") {
|
|
34368
|
+
const line = asNumber(result.line);
|
|
34369
|
+
const column = asNumber(result.column);
|
|
34370
|
+
const lineText = asString(result.line_text) ?? "";
|
|
34371
|
+
const location2 = line !== undefined ? `${line}${column !== undefined ? `:${column}` : ""}` : "?";
|
|
34372
|
+
lines.push(` ↳ ${theme.fg("muted", `line ${location2}`)} ${lineText}`);
|
|
34373
|
+
return;
|
|
34374
|
+
}
|
|
34012
34375
|
const score = asNumber(result.score);
|
|
34013
34376
|
const source = asString(result.source);
|
|
34377
|
+
const kind = asString(result.kind) ?? "symbol";
|
|
34378
|
+
const location = asString(result.location);
|
|
34014
34379
|
if (source === "lexical") {
|
|
34015
34380
|
lines.push(` ↳ ${theme.fg("muted", `[lexical match${score !== undefined ? ` — score ${score.toFixed(3)}` : ""}]`)}`);
|
|
34016
34381
|
const snippet2 = asString(result.snippet);
|
|
@@ -34020,10 +34385,14 @@ function buildSemanticSections(args, payload, theme) {
|
|
|
34020
34385
|
}
|
|
34021
34386
|
return;
|
|
34022
34387
|
}
|
|
34388
|
+
if (kind === "file_summary" || location === "[file summary]") {
|
|
34389
|
+
const summary = asString(result.snippet) ?? asString(result.name) ?? "(no summary)";
|
|
34390
|
+
lines.push(` ↳ ${summary} ${theme.fg("muted", `[file summary${score !== undefined ? ` score ${score.toFixed(3)}` : ""}]`)}`);
|
|
34391
|
+
return;
|
|
34392
|
+
}
|
|
34023
34393
|
const startLine = asNumber(result.start_line);
|
|
34024
34394
|
const endLine = asNumber(result.end_line);
|
|
34025
34395
|
const range = startLine !== undefined ? `${startLine}${endLine && endLine !== startLine ? `-${endLine}` : ""}` : "?";
|
|
34026
|
-
const kind = asString(result.kind) ?? "symbol";
|
|
34027
34396
|
const name = asString(result.name) ?? "(unknown)";
|
|
34028
34397
|
lines.push(` ↳ ${name} ${theme.fg("muted", `[${kind}] lines ${range}${score !== undefined ? ` score ${score.toFixed(3)}` : ""}`)}`);
|
|
34029
34398
|
const snippet = asString(result.snippet);
|
|
@@ -34038,43 +34407,48 @@ function buildSemanticSections(args, payload, theme) {
|
|
|
34038
34407
|
return sections;
|
|
34039
34408
|
}
|
|
34040
34409
|
function renderSemanticCall(args, theme, context) {
|
|
34041
|
-
return renderToolCall("
|
|
34410
|
+
return renderToolCall("search", theme.fg("toolOutput", args.query), theme, context);
|
|
34042
34411
|
}
|
|
34043
34412
|
function renderSemanticResult(result, args, theme, context) {
|
|
34044
34413
|
if (context.isError)
|
|
34045
|
-
return renderErrorResult(result, "
|
|
34414
|
+
return renderErrorResult(result, "search failed", theme, context);
|
|
34046
34415
|
return renderSections(buildSemanticSections(args, extractStructuredPayload(result), theme), context);
|
|
34047
34416
|
}
|
|
34048
34417
|
function registerSemanticTool(pi, ctx) {
|
|
34049
34418
|
pi.registerTool({
|
|
34050
34419
|
name: "aft_search",
|
|
34051
|
-
label: "
|
|
34420
|
+
label: "search",
|
|
34052
34421
|
description: [
|
|
34053
|
-
"Find
|
|
34422
|
+
"Find code with unified semantic, lexical, literal, and regex search. Returns ranked symbol/file results or exact matching lines, with routing metadata.",
|
|
34054
34423
|
"",
|
|
34055
34424
|
"When to reach for it:",
|
|
34056
34425
|
"- Exploring an unfamiliar area: 'where is rate limiting handled', 'how does auth flow work'",
|
|
34057
34426
|
"- Concept doesn't appear as a literal string: 'retry logic', 'cache invalidation', 'graceful shutdown'",
|
|
34058
34427
|
"- Filename-shaped concepts: 'the bridge spawn helper', 'the session detection module'",
|
|
34059
|
-
"-
|
|
34428
|
+
"- Regex-shaped or exact text queries when you want AFT to classify and route automatically",
|
|
34060
34429
|
"- You know roughly what the function does but not what it's named",
|
|
34061
34430
|
"",
|
|
34062
34431
|
"When NOT to use:",
|
|
34063
|
-
"- You
|
|
34432
|
+
"- You need exhaustive literal enumeration → use grep directly",
|
|
34064
34433
|
"- You want the file/module structure → use aft_outline",
|
|
34065
|
-
"- You're following a call chain → use
|
|
34434
|
+
"- You're following a call chain → use aft_callgraph",
|
|
34066
34435
|
"",
|
|
34067
|
-
"
|
|
34436
|
+
"Set hint to 'regex', 'literal', 'semantic', or 'auto' to override or document routing intent."
|
|
34068
34437
|
].join(`
|
|
34069
34438
|
`),
|
|
34070
34439
|
parameters: SearchParams2,
|
|
34071
34440
|
async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
|
|
34441
|
+
if (isEmptyParam(params.query) || typeof params.query !== "string" || params.query.trim().length === 0) {
|
|
34442
|
+
throw new Error("semantic_search: invalid params: `query` must be a non-empty string");
|
|
34443
|
+
}
|
|
34072
34444
|
const bridge = bridgeFor(ctx, extCtx.cwd);
|
|
34073
34445
|
const req = { query: params.query };
|
|
34074
34446
|
if (params.topK !== undefined)
|
|
34075
34447
|
req.top_k = params.topK;
|
|
34448
|
+
if (params.hint !== undefined)
|
|
34449
|
+
req.hint = params.hint;
|
|
34076
34450
|
const response = await callBridge(bridge, "semantic_search", req, extCtx);
|
|
34077
|
-
return textResult(response.text ?? JSON.stringify(response, null, 2));
|
|
34451
|
+
return textResult(response.text ?? JSON.stringify(response, null, 2), response);
|
|
34078
34452
|
},
|
|
34079
34453
|
renderCall(args, theme, context) {
|
|
34080
34454
|
return renderSemanticCall(args, theme, context);
|
|
@@ -34086,10 +34460,10 @@ function registerSemanticTool(pi, ctx) {
|
|
|
34086
34460
|
}
|
|
34087
34461
|
|
|
34088
34462
|
// src/tools/structure.ts
|
|
34089
|
-
import { StringEnum as
|
|
34463
|
+
import { StringEnum as StringEnum6 } from "@earendil-works/pi-ai";
|
|
34090
34464
|
import { Type as Type14 } from "typebox";
|
|
34091
34465
|
var TransformParams = Type14.Object({
|
|
34092
|
-
op:
|
|
34466
|
+
op: StringEnum6(["add_member", "add_derive", "wrap_try_catch", "add_decorator", "add_struct_tags"], { description: "Transformation operation" }),
|
|
34093
34467
|
filePath: Type14.String({
|
|
34094
34468
|
description: "Path to the source file (absolute or relative to project root)"
|
|
34095
34469
|
}),
|
|
@@ -34105,7 +34479,7 @@ var TransformParams = Type14.Object({
|
|
|
34105
34479
|
position: Type14.Optional(Type14.String({
|
|
34106
34480
|
description: "Position hint: 'first', 'last' (default), 'before:name', 'after:name' for add_member"
|
|
34107
34481
|
})),
|
|
34108
|
-
validate: Type14.Optional(
|
|
34482
|
+
validate: Type14.Optional(StringEnum6(["syntax", "full"], {
|
|
34109
34483
|
description: "Validation level (default: syntax)"
|
|
34110
34484
|
}))
|
|
34111
34485
|
});
|
|
@@ -34221,18 +34595,26 @@ function buildWorkflowHints(opts) {
|
|
|
34221
34595
|
const hasZoom = !opts.absentTools.has("aft_zoom");
|
|
34222
34596
|
const hasGrep = opts.toolSurface !== "minimal" && !opts.absentTools.has(grepName);
|
|
34223
34597
|
const hasSearch = opts.toolSurface !== "minimal" && opts.semanticEnabled && !opts.absentTools.has("aft_search");
|
|
34224
|
-
const hasNavigate = opts.toolSurface === "all" && !opts.absentTools.has("
|
|
34598
|
+
const hasNavigate = opts.toolSurface === "all" && !opts.absentTools.has("aft_callgraph");
|
|
34599
|
+
const hasInspect = opts.toolSurface !== "minimal" && !opts.absentTools.has("aft_inspect");
|
|
34225
34600
|
const hasBgBash = opts.bashBackgroundEnabled && !opts.absentTools.has(bashName) && !opts.absentTools.has("bash_status");
|
|
34226
34601
|
if (hasOutline && hasZoom) {
|
|
34227
34602
|
sections.push(`**Web/URL access**: \`aft_outline({ target: "<url>" })\` first for structure, then \`aft_zoom({ url: "<url>", symbols: "<heading>" })\` for the specific section.`);
|
|
34228
34603
|
}
|
|
34229
34604
|
if (hasOutline && hasZoom && (hasGrep || hasSearch)) {
|
|
34230
|
-
|
|
34231
|
-
|
|
34605
|
+
if (hasSearch) {
|
|
34606
|
+
const grepFallback = hasGrep ? ` Use \`${grepName}\` directly only when you need exhaustive enumeration of literal text (every TODO, every import of X) without ranking.` : "";
|
|
34607
|
+
sections.push(`**Code exploration**: \`aft_search\` is the primary code-search tool. It auto-routes by query shape — exact identifiers, regex, error messages, natural language all use the same call. Very short queries fall back to literal scans; pass \`hint: "regex"\` / \`hint: "literal"\` / \`hint: "semantic"\` to override routing if needed. Then \`aft_outline\` for structure → \`aft_zoom\` for symbol(s).${grepFallback}`);
|
|
34608
|
+
} else {
|
|
34609
|
+
sections.push(`**Code exploration**: \`${grepName}\` to locate → \`aft_outline\` for structure → \`aft_zoom\` for symbol(s).`);
|
|
34610
|
+
}
|
|
34611
|
+
}
|
|
34612
|
+
if (hasInspect) {
|
|
34613
|
+
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.");
|
|
34232
34614
|
}
|
|
34233
34615
|
if (hasNavigate) {
|
|
34234
34616
|
sections.push([
|
|
34235
|
-
"Use `
|
|
34617
|
+
"Use `aft_callgraph` for code-relationship questions instead of grep + read chains:",
|
|
34236
34618
|
"- `callers` — find all call sites before changing a function signature",
|
|
34237
34619
|
"- `impact` — blast radius (which functions/files will need updates)",
|
|
34238
34620
|
"- `trace_to` — how execution reaches this code from entry points (routes, exports, main)",
|
|
@@ -34272,7 +34654,9 @@ function registerWorkflowHints(pi, config2, surface) {
|
|
|
34272
34654
|
if (!surface.semantic)
|
|
34273
34655
|
absent.add("aft_search");
|
|
34274
34656
|
if (!surface.navigate)
|
|
34275
|
-
absent.add("
|
|
34657
|
+
absent.add("aft_callgraph");
|
|
34658
|
+
if (!surface.inspect)
|
|
34659
|
+
absent.add("aft_inspect");
|
|
34276
34660
|
if (!surface.hoistGrep)
|
|
34277
34661
|
absent.add("grep");
|
|
34278
34662
|
if (!surface.hoistBash) {
|
|
@@ -34336,17 +34720,17 @@ var PLUGIN_VERSION = (() => {
|
|
|
34336
34720
|
return "0.0.0";
|
|
34337
34721
|
}
|
|
34338
34722
|
})();
|
|
34339
|
-
var ANNOUNCEMENT_VERSION = "0.
|
|
34723
|
+
var ANNOUNCEMENT_VERSION = "0.33.0";
|
|
34340
34724
|
var ANNOUNCEMENT_FEATURES = [
|
|
34341
|
-
"
|
|
34342
|
-
|
|
34343
|
-
|
|
34344
|
-
"
|
|
34345
|
-
"
|
|
34725
|
+
"New `aft_inspect` — one call for codebase health: diagnostics, metrics, TODOs, dead code, unused exports, and duplicates.",
|
|
34726
|
+
"Diagnostics now flow through `aft_inspect` (run it after a batch of edits) instead of arriving automatically on every edit.",
|
|
34727
|
+
"`aft_navigate` is renamed to `aft_callgraph`; the Rust call graph now resolves cross-file callers.",
|
|
34728
|
+
"Edits no longer echo the whole file back to the agent — much lower token cost per edit.",
|
|
34729
|
+
"Batch of `aft_search` correctness fixes and undo-history/SSRF/Windows hardening."
|
|
34346
34730
|
];
|
|
34347
34731
|
var ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/F2uWxjGnU";
|
|
34348
34732
|
var ALL_ONLY_TOOLS = new Set([
|
|
34349
|
-
"
|
|
34733
|
+
"aft_callgraph",
|
|
34350
34734
|
"aft_delete",
|
|
34351
34735
|
"aft_move",
|
|
34352
34736
|
"aft_transform",
|
|
@@ -34420,6 +34804,7 @@ function resolveToolSurface(config2) {
|
|
|
34420
34804
|
outline: ok("aft_outline"),
|
|
34421
34805
|
zoom: ok("aft_zoom"),
|
|
34422
34806
|
semantic: false,
|
|
34807
|
+
inspect: false,
|
|
34423
34808
|
navigate: false,
|
|
34424
34809
|
conflicts: false,
|
|
34425
34810
|
importTool: false,
|
|
@@ -34428,7 +34813,6 @@ function resolveToolSurface(config2) {
|
|
|
34428
34813
|
move: false,
|
|
34429
34814
|
astSearch: false,
|
|
34430
34815
|
astReplace: false,
|
|
34431
|
-
lspDiagnostics: false,
|
|
34432
34816
|
structure: false,
|
|
34433
34817
|
refactor: false
|
|
34434
34818
|
};
|
|
@@ -34443,6 +34827,7 @@ function resolveToolSurface(config2) {
|
|
|
34443
34827
|
outline: ok("aft_outline"),
|
|
34444
34828
|
zoom: ok("aft_zoom"),
|
|
34445
34829
|
semantic: ok("aft_search") && config2.semantic_search === true,
|
|
34830
|
+
inspect: ok("aft_inspect") && config2.inspect?.enabled !== false,
|
|
34446
34831
|
navigate: false,
|
|
34447
34832
|
conflicts: ok("aft_conflicts"),
|
|
34448
34833
|
importTool: ok("aft_import"),
|
|
@@ -34451,14 +34836,13 @@ function resolveToolSurface(config2) {
|
|
|
34451
34836
|
move: false,
|
|
34452
34837
|
astSearch: ok("ast_grep_search"),
|
|
34453
34838
|
astReplace: ok("ast_grep_replace"),
|
|
34454
|
-
lspDiagnostics: ok("lsp_diagnostics"),
|
|
34455
34839
|
structure: false,
|
|
34456
34840
|
refactor: false
|
|
34457
34841
|
};
|
|
34458
34842
|
if (surface === "all") {
|
|
34459
34843
|
return {
|
|
34460
34844
|
...base,
|
|
34461
|
-
navigate: allOnly("
|
|
34845
|
+
navigate: allOnly("aft_callgraph"),
|
|
34462
34846
|
delete: allOnly("aft_delete"),
|
|
34463
34847
|
move: allOnly("aft_move"),
|
|
34464
34848
|
structure: allOnly("aft_transform"),
|
|
@@ -34506,6 +34890,8 @@ async function src_default(pi) {
|
|
|
34506
34890
|
Object.assign(configOverrides, resolveLspConfigForConfigure(config2));
|
|
34507
34891
|
if (config2.semantic !== undefined)
|
|
34508
34892
|
configOverrides.semantic = config2.semantic;
|
|
34893
|
+
if (config2.inspect !== undefined)
|
|
34894
|
+
configOverrides.inspect = config2.inspect;
|
|
34509
34895
|
if (config2.max_callgraph_files !== undefined)
|
|
34510
34896
|
configOverrides.max_callgraph_files = config2.max_callgraph_files;
|
|
34511
34897
|
if (config2.url_fetch_allow_private !== undefined)
|
|
@@ -34673,6 +35059,9 @@ ${lines}
|
|
|
34673
35059
|
if (surface.semantic) {
|
|
34674
35060
|
registerSemanticTool(pi, ctx);
|
|
34675
35061
|
}
|
|
35062
|
+
if (surface.inspect) {
|
|
35063
|
+
registerInspectTool(pi, ctx);
|
|
35064
|
+
}
|
|
34676
35065
|
if (surface.navigate) {
|
|
34677
35066
|
registerNavigateTool(pi, ctx);
|
|
34678
35067
|
}
|
|
@@ -34691,9 +35080,6 @@ ${lines}
|
|
|
34691
35080
|
if (surface.delete || surface.move) {
|
|
34692
35081
|
registerFsTools(pi, ctx, surface);
|
|
34693
35082
|
}
|
|
34694
|
-
if (surface.lspDiagnostics) {
|
|
34695
|
-
registerLspTools(pi, ctx);
|
|
34696
|
-
}
|
|
34697
35083
|
if (surface.structure) {
|
|
34698
35084
|
registerStructureTool(pi, ctx);
|
|
34699
35085
|
}
|