@cortexkit/aft-pi 0.50.3 → 0.51.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
@@ -8325,6 +8325,120 @@ function skipSpaces(input, start) {
8325
8325
  index++;
8326
8326
  return index;
8327
8327
  }
8328
+ // ../aft-bridge/dist/bash-host-fallback.js
8329
+ import { spawn } from "node:child_process";
8330
+ var BASH_HOST_FALLBACK_BANNER = "[AFT host fallback - module transport down; no rewrites/compression/background]";
8331
+ var BASH_HOST_FALLBACK_MAX_OUTPUT_BYTES = 100 * 1024;
8332
+ var BASH_HOST_FALLBACK_MAX_TIMEOUT_MS = 10 * 60 * 1000;
8333
+ var BASH_HOST_FALLBACK_REFUSAL = "AFT transport is down; only foreground execution is available in host fallback";
8334
+ function bashHostFallbackAskPattern(command, cwd) {
8335
+ return `AFT UNAVAILABLE - host fallback execution:
8336
+
8337
+ Exact command:
8338
+ ${command}
8339
+
8340
+ Working directory:
8341
+ ${cwd}`;
8342
+ }
8343
+ function appendTail(chunks, chunk) {
8344
+ const combined = Buffer.concat([...chunks, chunk]);
8345
+ if (combined.byteLength <= BASH_HOST_FALLBACK_MAX_OUTPUT_BYTES) {
8346
+ return { chunks: [combined], truncated: false };
8347
+ }
8348
+ return {
8349
+ chunks: [combined.subarray(combined.byteLength - BASH_HOST_FALLBACK_MAX_OUTPUT_BYTES)],
8350
+ truncated: true
8351
+ };
8352
+ }
8353
+ function renderOutput(output, exitCode) {
8354
+ const body = output.toString("utf8");
8355
+ const separator = body.length === 0 || body.endsWith(`
8356
+ `) ? "" : `
8357
+ `;
8358
+ return `${BASH_HOST_FALLBACK_BANNER}
8359
+ ${body}${separator}[exit code: ${exitCode}]`;
8360
+ }
8361
+ async function runBashHostFallback(options) {
8362
+ const timeoutMs = Math.min(Math.max(1, options.timeoutMs ?? BASH_HOST_FALLBACK_MAX_TIMEOUT_MS), BASH_HOST_FALLBACK_MAX_TIMEOUT_MS);
8363
+ if (options.signal?.aborted) {
8364
+ throw new DOMException("The host fallback command was aborted", "AbortError");
8365
+ }
8366
+ return await new Promise((resolve2, reject) => {
8367
+ const child = spawn(options.command, {
8368
+ cwd: options.projectRoot,
8369
+ shell: true,
8370
+ env: { ...process.env, ...options.env },
8371
+ stdio: ["ignore", "pipe", "pipe"],
8372
+ detached: process.platform !== "win32",
8373
+ windowsHide: true
8374
+ });
8375
+ let chunks = [];
8376
+ let truncated = false;
8377
+ let timedOut = false;
8378
+ let aborted = false;
8379
+ let settled = false;
8380
+ let abortForceTimer;
8381
+ const capture = (chunk) => {
8382
+ const next = appendTail(chunks, Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
8383
+ chunks = next.chunks;
8384
+ truncated ||= next.truncated;
8385
+ };
8386
+ child.stdout?.on("data", capture);
8387
+ child.stderr?.on("data", capture);
8388
+ const kill = (signal) => {
8389
+ if (child.exitCode !== null || child.signalCode !== null)
8390
+ return;
8391
+ if (process.platform !== "win32" && child.pid !== undefined) {
8392
+ try {
8393
+ process.kill(-child.pid, signal);
8394
+ return;
8395
+ } catch {}
8396
+ }
8397
+ child.kill(signal);
8398
+ };
8399
+ const onAbort = () => {
8400
+ aborted = true;
8401
+ kill("SIGTERM");
8402
+ abortForceTimer = setTimeout(() => kill("SIGKILL"), 250);
8403
+ abortForceTimer.unref?.();
8404
+ };
8405
+ options.signal?.addEventListener("abort", onAbort, { once: true });
8406
+ const timer = setTimeout(() => {
8407
+ timedOut = true;
8408
+ kill("SIGKILL");
8409
+ }, timeoutMs);
8410
+ const cleanup = () => {
8411
+ clearTimeout(timer);
8412
+ if (abortForceTimer !== undefined)
8413
+ clearTimeout(abortForceTimer);
8414
+ options.signal?.removeEventListener("abort", onAbort);
8415
+ };
8416
+ child.once("error", (error2) => {
8417
+ if (settled)
8418
+ return;
8419
+ settled = true;
8420
+ cleanup();
8421
+ reject(error2);
8422
+ });
8423
+ child.once("close", (code) => {
8424
+ if (settled)
8425
+ return;
8426
+ settled = true;
8427
+ cleanup();
8428
+ if (aborted) {
8429
+ reject(new DOMException("The host fallback command was aborted", "AbortError"));
8430
+ return;
8431
+ }
8432
+ const exitCode = timedOut ? 124 : code ?? 1;
8433
+ resolve2({
8434
+ success: true,
8435
+ output: renderOutput(Buffer.concat(chunks), exitCode),
8436
+ exit_code: exitCode,
8437
+ truncated
8438
+ });
8439
+ });
8440
+ });
8441
+ }
8328
8442
  // ../aft-bridge/dist/bash-timeout.js
8329
8443
  function resolveBashKillTimeout(modelTimeout, foregroundWaitMs) {
8330
8444
  if (modelTimeout !== undefined && modelTimeout >= foregroundWaitMs) {
@@ -8333,7 +8447,7 @@ function resolveBashKillTimeout(modelTimeout, foregroundWaitMs) {
8333
8447
  return;
8334
8448
  }
8335
8449
  // ../aft-bridge/dist/bridge.js
8336
- import { spawn } from "node:child_process";
8450
+ import { spawn as spawn2 } from "node:child_process";
8337
8451
  import { createHash } from "node:crypto";
8338
8452
  import { readFileSync } from "node:fs";
8339
8453
  import { homedir as homedir2 } from "node:os";
@@ -8363,51 +8477,6 @@ function isPassiveCommand(command) {
8363
8477
  return PASSIVE_COMMANDS.has(command);
8364
8478
  }
8365
8479
 
8366
- // ../aft-bridge/dist/status-bar.js
8367
- var STATUS_BAR_HEARTBEAT_CALLS = 15;
8368
- function createStatusBarEmitState() {
8369
- return { callsSinceEmit: 0 };
8370
- }
8371
- function parseStatusBarCounts(value) {
8372
- if (!value || typeof value !== "object")
8373
- return;
8374
- const record = value;
8375
- const num = (key) => {
8376
- const raw = record[key];
8377
- return typeof raw === "number" && Number.isFinite(raw) ? raw : 0;
8378
- };
8379
- return {
8380
- errors: num("errors"),
8381
- warnings: num("warnings"),
8382
- dead_code: num("dead_code"),
8383
- unused_exports: num("unused_exports"),
8384
- duplicates: num("duplicates"),
8385
- todos: num("todos"),
8386
- tier2_stale: record.tier2_stale === true,
8387
- ...typeof record.line === "string" ? { line: record.line } : {}
8388
- };
8389
- }
8390
- function countsEqual(a, b) {
8391
- return a.errors === b.errors && a.warnings === b.warnings && a.dead_code === b.dead_code && a.unused_exports === b.unused_exports && a.duplicates === b.duplicates && a.todos === b.todos && a.tier2_stale === b.tier2_stale && a.line === b.line;
8392
- }
8393
- function shouldEmitStatusBar(state, next) {
8394
- const changed = state.last === undefined || !countsEqual(state.last, next);
8395
- state.callsSinceEmit += 1;
8396
- const heartbeat = state.callsSinceEmit >= STATUS_BAR_HEARTBEAT_CALLS;
8397
- if (changed || heartbeat) {
8398
- state.last = next;
8399
- state.callsSinceEmit = 0;
8400
- return true;
8401
- }
8402
- return false;
8403
- }
8404
- function formatStatusBar(counts) {
8405
- if (counts.line !== undefined)
8406
- return counts.line;
8407
- const staleMark = counts.tier2_stale ? "~" : "";
8408
- return `[AFT E${counts.errors} W${counts.warnings} | ` + `${staleMark}D${counts.dead_code} U${counts.unused_exports} C${counts.duplicates} | ` + `T${counts.todos}]`;
8409
- }
8410
-
8411
8480
  // ../aft-bridge/dist/bridge.js
8412
8481
  var DEFAULT_BRIDGE_TIMEOUT_MS = 30000;
8413
8482
  var BRIDGE_HANG_TIMEOUT_THRESHOLD = 2;
@@ -8536,6 +8605,14 @@ class BridgeTransportTimeoutError extends Error {
8536
8605
  this.name = "BridgeTransportTimeoutError";
8537
8606
  }
8538
8607
  }
8608
+
8609
+ class BridgeTransportUnavailableError extends Error {
8610
+ code = "bridge_transport_unavailable";
8611
+ constructor(message, options) {
8612
+ super(message, options);
8613
+ this.name = "BridgeTransportUnavailableError";
8614
+ }
8615
+ }
8539
8616
  function isBridgeTransportTimeout(err) {
8540
8617
  return err instanceof Error && err.code === "transport_timeout";
8541
8618
  }
@@ -8578,7 +8655,6 @@ class BinaryBridge {
8578
8655
  configureWarningClients = new Map;
8579
8656
  restartResetTimer = null;
8580
8657
  lastChildActivityAt = 0;
8581
- lastStatusBar;
8582
8658
  consecutiveRequestTimeouts = 0;
8583
8659
  errorPrefix;
8584
8660
  logger;
@@ -8779,7 +8855,7 @@ class BinaryBridge {
8779
8855
  throw new Error(`${this.errorPrefix} Bridge is retiring after the on-disk binary changed; retry to respawn on the updated binary`);
8780
8856
  }
8781
8857
  if (this._shuttingDown) {
8782
- throw new Error(`${this.errorPrefix} Bridge is shutting down, cannot send "${command}"`);
8858
+ throw new BridgeTransportUnavailableError(`${this.errorPrefix} Bridge is shutting down, cannot send "${command}"`);
8783
8859
  }
8784
8860
  if (Object.hasOwn(params, "id")) {
8785
8861
  throw new Error("params cannot contain reserved key 'id'");
@@ -8813,7 +8889,7 @@ class BinaryBridge {
8813
8889
  await this.deliverConfigureWarnings(configResult, params, options);
8814
8890
  await this.checkVersion(implicitTransportOptions);
8815
8891
  if (!this.isAlive()) {
8816
- throw new Error(`${this.errorPrefix} Bridge died during version check. Check logs: ${this.getLogFilePathVia()}`);
8892
+ throw new BridgeTransportUnavailableError(`${this.errorPrefix} Bridge died during version check. Check logs: ${this.getLogFilePathVia()}`);
8817
8893
  }
8818
8894
  this.configured = true;
8819
8895
  } finally {
@@ -8882,7 +8958,7 @@ class BinaryBridge {
8882
8958
  if (!this.process?.stdin?.writable) {
8883
8959
  this.pending.delete(id);
8884
8960
  clearTimeout(timer);
8885
- reject(new Error(`${this.errorPrefix} stdin not writable for command "${command}"`));
8961
+ reject(new BridgeTransportUnavailableError(`${this.errorPrefix} stdin not writable for command "${command}"`));
8886
8962
  return;
8887
8963
  }
8888
8964
  requestSentAt = Date.now();
@@ -8892,7 +8968,7 @@ class BinaryBridge {
8892
8968
  if (entry) {
8893
8969
  this.pending.delete(id);
8894
8970
  clearTimeout(entry.timer);
8895
- entry.reject(new Error(`${this.errorPrefix} Failed to write to stdin: ${err.message}`));
8971
+ entry.reject(new BridgeTransportUnavailableError(`${this.errorPrefix} Failed to write to stdin: ${err.message}`, { cause: err }));
8896
8972
  }
8897
8973
  }
8898
8974
  });
@@ -9059,7 +9135,6 @@ class BinaryBridge {
9059
9135
  }
9060
9136
  spawnProcess(triggeringSessionId) {
9061
9137
  this._retiringDueToBinaryChange = false;
9062
- this.lastStatusBar = undefined;
9063
9138
  if (triggeringSessionId) {
9064
9139
  this.sessionLogVia(triggeringSessionId, `Spawning binary: ${this.binaryPath} (cwd: ${this.cwd})`);
9065
9140
  } else {
@@ -9100,7 +9175,7 @@ class BinaryBridge {
9100
9175
  }
9101
9176
  }
9102
9177
  }
9103
- const child = spawn(this.binaryPath, [], {
9178
+ const child = spawn2(this.binaryPath, [], {
9104
9179
  cwd: this.cwd,
9105
9180
  stdio: ["pipe", "pipe", "pipe"],
9106
9181
  env
@@ -9130,7 +9205,7 @@ class BinaryBridge {
9130
9205
  if (this.process !== currentChild)
9131
9206
  return;
9132
9207
  this.errorVia(`Process error: ${err.message}${this.formatStderrTail()}`);
9133
- this.handleCrash();
9208
+ this.handleCrash(err);
9134
9209
  });
9135
9210
  child.on("exit", (code, signal) => {
9136
9211
  if (this.process !== currentChild)
@@ -9143,7 +9218,7 @@ class BinaryBridge {
9143
9218
  this.process = null;
9144
9219
  this.configured = false;
9145
9220
  this.clearRestartResetTimer();
9146
- this.rejectAllPending(new Error(`${this.errorPrefix} Binary killed by ${signal}`));
9221
+ this.rejectAllPending(new BridgeTransportUnavailableError(`${this.errorPrefix} Binary killed by ${signal}`));
9147
9222
  return;
9148
9223
  }
9149
9224
  this.handleCrash();
@@ -9298,7 +9373,6 @@ class BinaryBridge {
9298
9373
  this.consecutiveRequestTimeouts = 0;
9299
9374
  this.scheduleRestartCountReset();
9300
9375
  this.accountForBashTaskResponse(entry.command, response);
9301
- this.captureStatusBar(response);
9302
9376
  entry.resolve(response);
9303
9377
  } else if (typeof response.type === "string") {
9304
9378
  this.logVia(`Ignoring unknown stdout push frame type: ${response.type}`);
@@ -9319,14 +9393,6 @@ class BinaryBridge {
9319
9393
  this.outstandingBackgroundTaskIds.add(taskId);
9320
9394
  }
9321
9395
  }
9322
- captureStatusBar(response) {
9323
- const parsed = parseStatusBarCounts(response.status_bar);
9324
- if (parsed)
9325
- this.lastStatusBar = parsed;
9326
- }
9327
- getStatusBar() {
9328
- return this.lastStatusBar;
9329
- }
9330
9396
  handleTimeout(triggeringSessionId) {
9331
9397
  this.consecutiveRequestTimeouts = 0;
9332
9398
  this.spawnedBinaryFingerprint = null;
@@ -9367,7 +9433,7 @@ class BinaryBridge {
9367
9433
  if (tail) {
9368
9434
  this.errorVia(`Binary crashed (restarts: ${this._restartCount})${cause ? `: ${cause.message}` : ""}.${tail}`);
9369
9435
  }
9370
- this.rejectAllPending(new Error(`${this.errorPrefix} Binary crashed (restarts: ${this._restartCount})${cause ? `: ${cause.message}` : ""} (see ${this.getLogFilePathVia()})`));
9436
+ this.rejectAllPending(new BridgeTransportUnavailableError(`${this.errorPrefix} Binary crashed (restarts: ${this._restartCount})${cause ? `: ${cause.message}` : ""} (see ${this.getLogFilePathVia()})`, { cause }));
9371
9437
  if (this._retiringDueToBinaryChange) {
9372
9438
  this.logVia("Binary exited while retiring after an on-disk update; skipping auto-restart");
9373
9439
  return;
@@ -13030,6 +13096,12 @@ function normalizeWindowsRoot(p) {
13030
13096
  }
13031
13097
 
13032
13098
  // ../aft-bridge/dist/subc-transport.js
13099
+ class SubcTransportShuttingDownError extends SubcCallError {
13100
+ constructor() {
13101
+ super("terminal", "subc transport is shutting down", "transport_shutting_down");
13102
+ this.name = "SubcTransportShuttingDownError";
13103
+ }
13104
+ }
13033
13105
  var AFT_MODULE_ID = "aft";
13034
13106
  var MAX_CONSECUTIVE_TRANSPORT_FAILURES = 3;
13035
13107
  var BG_STABLE_MS = 5000;
@@ -13371,7 +13443,6 @@ class SubcTransport {
13371
13443
  pool;
13372
13444
  projectRoot;
13373
13445
  generation;
13374
- lastStatusBar;
13375
13446
  cachedStatus = null;
13376
13447
  constructor(pool, projectRoot, generation) {
13377
13448
  this.pool = pool;
@@ -13387,20 +13458,12 @@ class SubcTransport {
13387
13458
  getConcretePoolId() {
13388
13459
  return this.pool.getConcretePoolId();
13389
13460
  }
13390
- getStatusBar() {
13391
- return this.lastStatusBar;
13392
- }
13393
13461
  getCachedStatus() {
13394
13462
  return this.cachedStatus;
13395
13463
  }
13396
13464
  cacheStatusSnapshot(snapshot) {
13397
13465
  this.cachedStatus = snapshot;
13398
13466
  }
13399
- captureStatusBar(response) {
13400
- const parsed = parseStatusBarCounts(response.status_bar);
13401
- if (parsed)
13402
- this.lastStatusBar = parsed;
13403
- }
13404
13467
  identityFor(session) {
13405
13468
  return {
13406
13469
  project_root: this.projectRoot,
@@ -13421,9 +13484,7 @@ class SubcTransport {
13421
13484
  if (preview === true)
13422
13485
  body.preview = true;
13423
13486
  const reply = await this.pool.routeRequest(this.identityFor(sessionId), body, timeoutMs, onProgress, this.generation);
13424
- const result = reliftReply(reply);
13425
- this.captureStatusBar(result);
13426
- return result;
13487
+ return reliftReply(reply);
13427
13488
  }
13428
13489
  async send(command, params = {}, options) {
13429
13490
  this.assertCurrent();
@@ -13437,9 +13498,7 @@ class SubcTransport {
13437
13498
  if (editSlotSurvives !== undefined)
13438
13499
  body.edit_slot_survives = editSlotSurvives;
13439
13500
  const reply = await this.pool.routeRequest(this.identityFor(session), body, timeoutMs, onProgress, this.generation);
13440
- const response = reliftReply(reply);
13441
- this.captureStatusBar(response);
13442
- return response;
13501
+ return reliftReply(reply);
13443
13502
  }
13444
13503
  splitOptions(options) {
13445
13504
  if (!options)
@@ -13710,7 +13769,7 @@ class SubcTransportPool {
13710
13769
  getBridge(projectRoot) {
13711
13770
  const root = this.canonicalRoot(projectRoot);
13712
13771
  if (this.shuttingDown && this.lifecycleEnabled()) {
13713
- throw new SubcCallError("terminal", "subc transport is shutting down");
13772
+ throw new SubcTransportShuttingDownError;
13714
13773
  }
13715
13774
  if (!this.lifecycleEnabled())
13716
13775
  return this.makeFacade(root);
@@ -14010,7 +14069,7 @@ class SubcTransportPool {
14010
14069
  }
14011
14070
  async ensureClient() {
14012
14071
  if (this.shuttingDown)
14013
- throw new SubcCallError("terminal", "subc transport is shutting down");
14072
+ throw new SubcTransportShuttingDownError;
14014
14073
  if (this.client)
14015
14074
  return this.client;
14016
14075
  if (this.connecting)
@@ -14024,7 +14083,7 @@ class SubcTransportPool {
14024
14083
  try {
14025
14084
  client.close();
14026
14085
  } catch {}
14027
- throw new SubcCallError("terminal", "subc transport is shutting down");
14086
+ throw new SubcTransportShuttingDownError;
14028
14087
  }
14029
14088
  this.client = client;
14030
14089
  this.transportFailures = 0;
@@ -14289,8 +14348,24 @@ function isRouteGoodbyeError(error2) {
14289
14348
  }
14290
14349
  return error2.code === "route_closed" && error2.message.includes("route closed by subc");
14291
14350
  }
14351
+ function hasEngineResponse(error2) {
14352
+ const response = error2.response;
14353
+ if (response !== null && typeof response === "object")
14354
+ return true;
14355
+ const cause = error2.cause;
14356
+ if (cause === null || typeof cause !== "object")
14357
+ return false;
14358
+ const causeResponse = cause.response;
14359
+ return causeResponse !== null && typeof causeResponse === "object";
14360
+ }
14361
+ function isBashTransportDeadError(error2) {
14362
+ if (!(error2 instanceof Error) || hasEngineResponse(error2) || isRouteGoodbyeError(error2)) {
14363
+ return false;
14364
+ }
14365
+ return error2 instanceof BridgeTransportUnavailableError || error2 instanceof SubcTransportShuttingDownError || isConsumerReconnectTransient(error2) || error2 instanceof StaleRouteHandleError || error2 instanceof SubcRootGenerationExpiredError || error2 instanceof SubcRootReapedError;
14366
+ }
14292
14367
  function isTransportClassError(error2) {
14293
- return isBridgeTransportTimeout(error2) || isConsumerReconnectTransient(error2) || error2 instanceof StaleRouteHandleError || error2 instanceof SubcRootGenerationExpiredError || error2 instanceof SubcRootReapedError;
14368
+ return isBridgeTransportTimeout(error2) || isBashTransportDeadError(error2);
14294
14369
  }
14295
14370
  function adaptToolError(command, error2) {
14296
14371
  if (!(error2 instanceof Error))
@@ -16860,9 +16935,6 @@ class RevivableProjectTransport {
16860
16935
  getCwd() {
16861
16936
  return this.projectRoot;
16862
16937
  }
16863
- getStatusBar() {
16864
- return this.owner.currentBridge(this.projectRoot)?.getStatusBar();
16865
- }
16866
16938
  getCachedStatus() {
16867
16939
  return this.owner.currentBridge(this.projectRoot)?.getCachedStatus() ?? null;
16868
16940
  }
@@ -17881,7 +17953,7 @@ import {
17881
17953
  // package.json
17882
17954
  var package_default = {
17883
17955
  name: "@cortexkit/aft-pi",
17884
- version: "0.50.3",
17956
+ version: "0.51.0",
17885
17957
  type: "module",
17886
17958
  description: "Pi coding agent extension for Agent File Tools (AFT) — tree-sitter and LSP-powered code analysis",
17887
17959
  main: "dist/index.js",
@@ -17904,19 +17976,19 @@ var package_default = {
17904
17976
  "test:unit": "bun test src/__tests__ --path-ignore-patterns 'src/__tests__/e2e/**'"
17905
17977
  },
17906
17978
  dependencies: {
17907
- "@cortexkit/aft-bridge": "0.50.3",
17979
+ "@cortexkit/aft-bridge": "0.51.0",
17908
17980
  "comment-json": "^5.0.0",
17909
17981
  diff: "^8.0.4",
17910
17982
  typebox: "1.1.38",
17911
17983
  zod: "^4.4.3"
17912
17984
  },
17913
17985
  optionalDependencies: {
17914
- "@cortexkit/aft-darwin-arm64": "0.50.3",
17915
- "@cortexkit/aft-darwin-x64": "0.50.3",
17916
- "@cortexkit/aft-linux-arm64": "0.50.3",
17917
- "@cortexkit/aft-linux-x64": "0.50.3",
17918
- "@cortexkit/aft-win32-arm64": "0.50.3",
17919
- "@cortexkit/aft-win32-x64": "0.50.3"
17986
+ "@cortexkit/aft-darwin-arm64": "0.51.0",
17987
+ "@cortexkit/aft-darwin-x64": "0.51.0",
17988
+ "@cortexkit/aft-linux-arm64": "0.51.0",
17989
+ "@cortexkit/aft-linux-x64": "0.51.0",
17990
+ "@cortexkit/aft-win32-arm64": "0.51.0",
17991
+ "@cortexkit/aft-win32-x64": "0.51.0"
17920
17992
  },
17921
17993
  devDependencies: {
17922
17994
  "@earendil-works/pi-coding-agent": "^0.80.2",
@@ -17983,14 +18055,23 @@ function readStatusBar(value) {
17983
18055
  if (typeof value !== "object" || value === null)
17984
18056
  return;
17985
18057
  const bar = asRecord2(value);
18058
+ const errors = readOptionalNumber(bar.errors);
18059
+ const warnings = readOptionalNumber(bar.warnings);
18060
+ const deadCode = readOptionalNumber(bar.dead_code);
18061
+ const unusedExports = readOptionalNumber(bar.unused_exports);
18062
+ const duplicates = readOptionalNumber(bar.duplicates);
18063
+ const todos = readOptionalNumber(bar.todos);
18064
+ if (errors === null && warnings === null && deadCode === null && unusedExports === null && duplicates === null && todos === null) {
18065
+ return;
18066
+ }
17986
18067
  return {
17987
- errors: readNumber(bar.errors),
17988
- warnings: readNumber(bar.warnings),
17989
- dead_code: readNumber(bar.dead_code),
17990
- unused_exports: readNumber(bar.unused_exports),
17991
- duplicates: readNumber(bar.duplicates),
17992
- todos: readNumber(bar.todos),
17993
- tier2_stale: readBoolean(bar.tier2_stale)
18068
+ ...errors !== null ? { errors } : {},
18069
+ ...warnings !== null ? { warnings } : {},
18070
+ ...deadCode !== null ? { dead_code: deadCode } : {},
18071
+ ...unusedExports !== null ? { unused_exports: unusedExports } : {},
18072
+ ...duplicates !== null ? { duplicates } : {},
18073
+ ...todos !== null ? { todos } : {},
18074
+ ...bar.tier2_stale === true ? { tier2_stale: true } : {}
17994
18075
  };
17995
18076
  }
17996
18077
  function formatFlag(enabled) {
@@ -17999,6 +18080,15 @@ function formatFlag(enabled) {
17999
18080
  function formatCount(value) {
18000
18081
  return value == null ? "—" : value.toLocaleString("en-US");
18001
18082
  }
18083
+ function formatCacheRoleLabel(role) {
18084
+ if (role === "worktree") {
18085
+ return "worktree — shared repo index (built by the main checkout)";
18086
+ }
18087
+ if (role === "read_only") {
18088
+ return "read_only — sharing the repo index family (read-only borrow)";
18089
+ }
18090
+ return role;
18091
+ }
18002
18092
  function formatSemanticIndexStatus(status, stage) {
18003
18093
  if ((status === "loading" || status === "building") && stage === "fingerprint_change") {
18004
18094
  return "Rebuilding (model changed)";
@@ -18099,7 +18189,7 @@ function formatStatusDialogMessage(status) {
18099
18189
  `AFT version: ${status.version}`,
18100
18190
  `Project root: ${status.project_root ?? "(not configured)"}`,
18101
18191
  `Canonical root: ${status.canonical_root ?? "(not configured)"}`,
18102
- `Cache role: ${status.cache_role}`,
18192
+ `Cache role: ${formatCacheRoleLabel(status.cache_role)}`,
18103
18193
  "",
18104
18194
  "Enabled features",
18105
18195
  `- format_on_edit: ${formatFlag(status.features.format_on_edit)}`,
@@ -18131,7 +18221,19 @@ function formatStatusDialogMessage(status) {
18131
18221
  lines.push("", "Disk usage", `- trigram index: ${formatBytes(status.disk.trigram_disk_bytes)}`, `- semantic index: ${formatBytes(status.disk.semantic_disk_bytes)}`, "", "Runtime", `- LSP servers: ${formatCount(status.lsp_servers)}`, `- symbol cache: ${formatCount(status.symbol_cache.local_entries)} local / ${formatCount(status.symbol_cache.warm_entries)} warm`);
18132
18222
  if (status.status_bar) {
18133
18223
  const sb = status.status_bar;
18134
- lines.push("", `Code Health${sb.tier2_stale ? " (~ stale)" : ""}`, `- errors: ${formatCount(sb.errors)}`, `- warnings: ${formatCount(sb.warnings)}`, `- dead code: ${formatCount(sb.dead_code)}`, `- unused exports: ${formatCount(sb.unused_exports)}`, `- duplicates: ${formatCount(sb.duplicates)}`, `- todos: ${formatCount(sb.todos)}`);
18224
+ const rows = [
18225
+ ["errors", sb.errors],
18226
+ ["warnings", sb.warnings],
18227
+ ["dead code", sb.dead_code],
18228
+ ["unused exports", sb.unused_exports],
18229
+ ["duplicates", sb.duplicates],
18230
+ ["todos", sb.todos]
18231
+ ].filter((row) => typeof row[1] === "number");
18232
+ if (rows.length > 0) {
18233
+ lines.push("", `Code Health${sb.tier2_stale ? " (~ stale)" : ""}`);
18234
+ for (const [label, value] of rows)
18235
+ lines.push(`- ${label}: ${formatCount(value)}`);
18236
+ }
18135
18237
  }
18136
18238
  if (status.storage_dir ?? status.disk.storage_dir) {
18137
18239
  lines.push(`- storage dir: ${status.storage_dir ?? status.disk.storage_dir}`);
@@ -18210,6 +18312,8 @@ async function callBridge(bridge, command, params = {}, extCtx, options) {
18210
18312
  try {
18211
18313
  response = await bridge.send(command, merged, Object.keys(sendOptions).length > 0 ? sendOptions : undefined);
18212
18314
  } catch (error3) {
18315
+ if (command === "bash" && isBashTransportDeadError(error3))
18316
+ throw error3;
18213
18317
  throw adaptToolError(command, error3);
18214
18318
  }
18215
18319
  if (response.success === false) {
@@ -18345,8 +18449,8 @@ function renderInner(s, error3, theme, innerWidth) {
18345
18449
  }
18346
18450
  lines.push(rowFull("Project root", s.project_root ?? "(not configured)", theme, innerWidth));
18347
18451
  lines.push(rowFull("Canonical root", s.canonical_root ?? "(not configured)", theme, innerWidth));
18348
- const cacheTone = s.cache_role === "main" ? "accent" : s.cache_role === "worktree" ? "warning" : "muted";
18349
- lines.push(rowFull("Cache role", theme.fg(cacheTone, s.cache_role), theme, innerWidth));
18452
+ const cacheTone = s.cache_role === "main" ? "accent" : "muted";
18453
+ lines.push(rowFull("Cache role", theme.fg(cacheTone, formatCacheRoleLabel(s.cache_role)), theme, innerWidth));
18350
18454
  lines.push("");
18351
18455
  const colWidth = Math.floor((innerWidth - 2) / 2);
18352
18456
  const left = [];
@@ -32839,6 +32943,7 @@ function resolveBashConfig(config2) {
32839
32943
  rewrite: false,
32840
32944
  compress: false,
32841
32945
  background: false,
32946
+ host_fallback: false,
32842
32947
  long_running_reminder_enabled: reminderEnabled,
32843
32948
  long_running_reminder_interval_ms: reminderInterval,
32844
32949
  foreground_wait_window_ms: foregroundWaitWindowMs
@@ -32854,7 +32959,8 @@ function resolveBashConfig(config2) {
32854
32959
  enabled: true,
32855
32960
  rewrite: top.rewrite ?? true,
32856
32961
  compress: top.compress ?? true,
32857
- background: top.background ?? true
32962
+ background: top.background ?? true,
32963
+ host_fallback: top.host_fallback ?? false
32858
32964
  };
32859
32965
  }
32860
32966
  const hasLegacyFeatureFlag = legacy && (legacy.rewrite !== undefined || legacy.compress !== undefined || legacy.background !== undefined);
@@ -32941,6 +33047,7 @@ var BashFeaturesSchema = exports_external.object({
32941
33047
  rewrite: exports_external.boolean().optional(),
32942
33048
  compress: exports_external.boolean().optional(),
32943
33049
  background: exports_external.boolean().optional(),
33050
+ host_fallback: exports_external.boolean().optional(),
32944
33051
  long_running_reminder_enabled: exports_external.boolean().optional(),
32945
33052
  long_running_reminder_interval_ms: exports_external.number().int().positive().optional(),
32946
33053
  foreground_wait_window_ms: exports_external.number().int().positive().optional()
@@ -32969,6 +33076,9 @@ var InspectConfigSchema = exports_external.object({
32969
33076
  expected_mirrors: exports_external.array(exports_external.tuple([exports_external.string().trim().min(1), exports_external.string().trim().min(1)])).optional()
32970
33077
  }).optional()
32971
33078
  });
33079
+ var WorktreeConfigSchema = exports_external.object({
33080
+ ram_overlay: exports_external.boolean().optional()
33081
+ });
32972
33082
  var BackupConfigSchema = exports_external.object({
32973
33083
  enabled: exports_external.boolean().optional(),
32974
33084
  max_depth: exports_external.number().int().positive().optional(),
@@ -32993,6 +33103,7 @@ var AftConfigSchema = exports_external.preprocess((value) => stripHarnessSpecifi
32993
33103
  callgraph_chunk_size: exports_external.number().optional(),
32994
33104
  inspect: InspectConfigSchema.optional(),
32995
33105
  backup: BackupConfigSchema.optional(),
33106
+ worktree: WorktreeConfigSchema.optional(),
32996
33107
  sandbox: SandboxConfigSchema.optional(),
32997
33108
  bash: BashConfigSchema.optional(),
32998
33109
  experimental: ExperimentalConfigSchema.optional(),
@@ -33249,6 +33360,15 @@ function mergeLspConfig(base, override) {
33249
33360
  return;
33250
33361
  return Object.fromEntries(Object.entries(lsp).filter(([, v]) => v !== undefined));
33251
33362
  }
33363
+ function mergeWorktreeConfig(baseWorktree, overrideWorktree) {
33364
+ if (baseWorktree === undefined && overrideWorktree === undefined)
33365
+ return;
33366
+ if (overrideWorktree === undefined)
33367
+ return baseWorktree;
33368
+ if (baseWorktree === undefined)
33369
+ return overrideWorktree;
33370
+ return { ...baseWorktree, ...overrideWorktree };
33371
+ }
33252
33372
  function mergeInspectConfig(baseInspect, overrideInspect) {
33253
33373
  const inspect = {
33254
33374
  ...baseInspect,
@@ -33334,6 +33454,7 @@ var PROJECT_SAFE_TOP_LEVEL_FIELDS = new Set([
33334
33454
  "callgraph_store",
33335
33455
  "callgraph_chunk_size",
33336
33456
  "inspect",
33457
+ "worktree",
33337
33458
  "experimental",
33338
33459
  "bash"
33339
33460
  ]);
@@ -33378,11 +33499,13 @@ function mergeConfigs(base, override) {
33378
33499
  const experimental = mergeExperimentalConfig(base.experimental, override.experimental);
33379
33500
  const bash = mergeBashConfig(base.bash, override.bash);
33380
33501
  const inspect = mergeInspectConfig(base.inspect, override.inspect);
33502
+ const worktree = mergeWorktreeConfig(base.worktree, override.worktree);
33381
33503
  const sandbox = mergeSandboxConfig(base.sandbox, override.sandbox);
33382
33504
  const bridge = base.bridge;
33383
33505
  const safeOverride = pickProjectSafeFields(override);
33384
33506
  delete safeOverride.bash;
33385
33507
  delete safeOverride.inspect;
33508
+ delete safeOverride.worktree;
33386
33509
  return {
33387
33510
  ...base,
33388
33511
  ...safeOverride,
@@ -33391,6 +33514,7 @@ function mergeConfigs(base, override) {
33391
33514
  ...lsp ? { lsp } : {},
33392
33515
  ...bash !== undefined ? { bash } : {},
33393
33516
  ...inspect !== undefined ? { inspect } : {},
33517
+ ...worktree !== undefined ? { worktree } : {},
33394
33518
  ...sandbox !== undefined ? { sandbox } : {},
33395
33519
  experimental,
33396
33520
  semantic,
@@ -33472,7 +33596,7 @@ function loadAftConfig(projectDirectory) {
33472
33596
  }
33473
33597
 
33474
33598
  // src/lsp-auto-install.ts
33475
- import { spawn as spawn2 } from "node:child_process";
33599
+ import { spawn as spawn3 } from "node:child_process";
33476
33600
  import { createHash as createHash4 } from "node:crypto";
33477
33601
  import {
33478
33602
  createReadStream,
@@ -34072,7 +34196,7 @@ function runInstall(spec, version2, cwd, signal) {
34072
34196
  return;
34073
34197
  }
34074
34198
  ensureInstallAnchor(cwd);
34075
- const child = spawn2(npm.command, ["install", "--no-save", "--ignore-scripts", "--silent", target], {
34199
+ const child = spawn3(npm.command, ["install", "--no-save", "--ignore-scripts", "--silent", target], {
34076
34200
  stdio: ["ignore", "pipe", "pipe"],
34077
34201
  cwd,
34078
34202
  env: npmSpawnEnv(npm)
@@ -35270,26 +35394,6 @@ function sendFeatureAnnouncement(version2, features, footer, storageDir) {
35270
35394
  markAnnouncementSeen(storageDir, "pi", version2);
35271
35395
  }
35272
35396
 
35273
- // src/status-bar-inject.ts
35274
- var DEFAULT_SESSION = "__default__";
35275
- var emitStateBySession = new Map;
35276
- function statusBarBlockForSession(sessionID, counts, force = false) {
35277
- if (!counts)
35278
- return;
35279
- const key = sessionID ?? DEFAULT_SESSION;
35280
- let state = emitStateBySession.get(key);
35281
- if (!state) {
35282
- state = createStatusBarEmitState();
35283
- emitStateBySession.set(key, state);
35284
- }
35285
- if (force) {
35286
- state.last = counts;
35287
- state.callsSinceEmit = 0;
35288
- return formatStatusBar(counts);
35289
- }
35290
- return shouldEmitStatusBar(state, counts) ? formatStatusBar(counts) : undefined;
35291
- }
35292
-
35293
35397
  // src/bash-wait-detach.ts
35294
35398
  var BASH_TRANSPORT_TIMEOUT_MS = 30000;
35295
35399
  async function sendBashWaitDetach(bridge, sessionID) {
@@ -36730,7 +36834,7 @@ DO NOT use bash for code search or code exploration. If you are about to run gre
36730
36834
  "Piped commands run verbatim and show the pipeline's output; run test/build tools without pipes when you need AFT's summary."
36731
36835
  ],
36732
36836
  parameters: bashParamsForConfig(bashCfg.background),
36733
- async execute(_toolCallId, params, _signal, onUpdate, extCtx) {
36837
+ async execute(_toolCallId, params, signal, onUpdate, extCtx) {
36734
36838
  const bridge = bridgeFor(ctx, extCtx.cwd);
36735
36839
  const bashCfg2 = resolveBashConfig(ctx.config);
36736
36840
  const foregroundWaitMs = resolveForegroundWaitMs(bashCfg2.foreground_wait_window_ms);
@@ -36765,30 +36869,62 @@ DO NOT use bash for code search or code exploration. If you are about to run gre
36765
36869
  }
36766
36870
  const bridgeCommand = spawnContext.command;
36767
36871
  let streamed = "";
36768
- const response = await callBashWithPermissionLoop(bridge, {
36769
- command: bridgeCommand,
36770
- timeout: effectiveTimeout,
36771
- workdir: spawnContext.cwd ?? params.workdir,
36772
- env: spawnContext.env,
36773
- description: params.description,
36774
- background: effectiveBackground,
36775
- notify_on_completion: effectiveBackground,
36776
- compressed,
36777
- pty: requestedPty,
36778
- pty_rows: ptyRows,
36779
- pty_cols: ptyCols,
36780
- foreground_orchestrate: true,
36781
- block_to_completion: blockToCompletion,
36782
- wait: requestedWait,
36783
- sandbox: params.sandbox
36784
- }, extCtx, {
36785
- transportTimeoutMs: orchestratedTransportTimeoutMs(blockToCompletion, requestedWait, effectiveTimeout, foregroundWaitMs),
36786
- onProgress: ({ text }) => {
36787
- streamed += text;
36788
- const displayText = truncateToVisualLines(streamed, 100);
36789
- onUpdate?.(bashResult(displayText, { streaming: true }));
36790
- }
36791
- });
36872
+ let usedHostFallback = false;
36873
+ let response;
36874
+ try {
36875
+ response = await callBashWithPermissionLoop(bridge, {
36876
+ command: bridgeCommand,
36877
+ timeout: effectiveTimeout,
36878
+ workdir: spawnContext.cwd ?? params.workdir,
36879
+ env: spawnContext.env,
36880
+ description: params.description,
36881
+ background: effectiveBackground,
36882
+ notify_on_completion: effectiveBackground,
36883
+ compressed,
36884
+ pty: requestedPty,
36885
+ pty_rows: ptyRows,
36886
+ pty_cols: ptyCols,
36887
+ foreground_orchestrate: true,
36888
+ block_to_completion: blockToCompletion,
36889
+ wait: requestedWait,
36890
+ sandbox: params.sandbox
36891
+ }, extCtx, {
36892
+ transportTimeoutMs: orchestratedTransportTimeoutMs(blockToCompletion, requestedWait, effectiveTimeout, foregroundWaitMs),
36893
+ onProgress: ({ text }) => {
36894
+ streamed += text;
36895
+ const displayText = truncateToVisualLines(streamed, 100);
36896
+ onUpdate?.(bashResult(displayText, { streaming: true }));
36897
+ }
36898
+ });
36899
+ } catch (error53) {
36900
+ if (!bashCfg2.host_fallback || !isBashTransportDeadError(error53))
36901
+ throw error53;
36902
+ if (rawRequestedBackground) {
36903
+ throw new Error(`${BASH_HOST_FALLBACK_REFUSAL}; background:true is unsupported.`);
36904
+ }
36905
+ if (rawRequestedPty) {
36906
+ throw new Error(`${BASH_HOST_FALLBACK_REFUSAL}; pty:true is unsupported.`);
36907
+ }
36908
+ if (!extCtx.hasUI || typeof extCtx.ui?.confirm !== "function") {
36909
+ throw new BridgeError("Permission denied: host fallback execution requires an interactive UI.", "permission_denied");
36910
+ }
36911
+ const projectRoot = extCtx.cwd;
36912
+ const pattern = bashHostFallbackAskPattern(bridgeCommand, projectRoot);
36913
+ const approved = await extCtx.ui.confirm("AFT unavailable — run command on host?", pattern, {
36914
+ signal: signal ?? extCtx.signal
36915
+ });
36916
+ if (!approved) {
36917
+ throw new BridgeError("Permission denied: AFT host fallback execution was denied.", "permission_denied");
36918
+ }
36919
+ response = await runBashHostFallback({
36920
+ command: bridgeCommand,
36921
+ projectRoot,
36922
+ timeoutMs: timeout,
36923
+ signal: signal ?? extCtx.signal,
36924
+ env: spawnContext.env
36925
+ });
36926
+ usedHostFallback = true;
36927
+ }
36792
36928
  if (response.success === false) {
36793
36929
  throw new Error(response.message ?? "bash failed");
36794
36930
  }
@@ -36805,7 +36941,7 @@ DO NOT use bash for code search or code exploration. If you are about to run gre
36805
36941
  task_id: taskId
36806
36942
  };
36807
36943
  const output = response.output ?? "";
36808
- return bashResult(withBashHints(output, bridgeCommand, aftSearchRegistered, extCtx.cwd), details);
36944
+ return bashResult(usedHostFallback ? output : withBashHints(output, bridgeCommand, aftSearchRegistered, extCtx.cwd), details);
36809
36945
  },
36810
36946
  renderCall(args, theme, context) {
36811
36947
  return renderBashCall(args?.command, args?.description, theme, context);
@@ -37647,10 +37783,10 @@ function registerImportTools(pi, ctx) {
37647
37783
  import { Type as Type8 } from "typebox";
37648
37784
  var InspectParams = Type8.Object({
37649
37785
  sections: Type8.Optional(Type8.Union([Type8.String(), Type8.Array(Type8.String())], {
37650
- description: "Categories to include in detailed drill-down (e.g. 'todos' or ['todos', 'dead_code', 'cycles']). Use 'all' for every active category. Omit for summary-only mode."
37786
+ description: "Categories to include in detailed drill-down (e.g. 'todos' or ['todos', 'dead_code', 'cycles']). Use 'all' for every active category. Omit for summary-only mode. `sections` changes detail, not the categories verified."
37651
37787
  })),
37652
37788
  scope: Type8.Optional(Type8.Union([Type8.String(), Type8.Array(Type8.String())], {
37653
- 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."
37789
+ description: "Restrict returned results to paths under this scope (file or directory, absolute or relative to project root). `scope=` narrows results; it does not reduce the blocking-fresh verification work."
37654
37790
  })),
37655
37791
  topK: Type8.Optional(Type8.Integer({
37656
37792
  minimum: 1,
@@ -37659,11 +37795,6 @@ var InspectParams = Type8.Object({
37659
37795
  description: "Max drill-down items per category. Default 20, max 100."
37660
37796
  }))
37661
37797
  });
37662
- var TIER2_INSPECT_CATEGORIES = new Set(["dead_code", "unused_exports", "duplicates", "cycles"]);
37663
- var INSPECT_TIER2_RUN_TIMEOUT_MS = 5 * 60000;
37664
- var INSPECT_TIER2_MIN_TRIGGER_INTERVAL_MS = 4 * 60000;
37665
- var runningTier2Categories = new WeakMap;
37666
- var lastTier2TriggerAtByBridge = new WeakMap;
37667
37798
  function normalizeStringOrArray(value) {
37668
37799
  return isEmptyParam(value) ? undefined : value;
37669
37800
  }
@@ -37694,15 +37825,119 @@ function validateOptionalTopK(value) {
37694
37825
  }
37695
37826
  return value;
37696
37827
  }
37697
- function diagnosticsServerSummary(section) {
37698
- const pending = Array.isArray(section.servers_pending) ? section.servers_pending.filter((item) => typeof item === "string") : [];
37699
- const notInstalled = Array.isArray(section.servers_not_installed) ? section.servers_not_installed.filter((item) => typeof item === "string") : [];
37700
- const parts = [];
37701
- if (pending.length > 0)
37702
- parts.push(`pending: ${pending.join(", ")}`);
37703
- if (notInstalled.length > 0)
37704
- parts.push(`not installed: ${notInstalled.join(", ")}`);
37705
- return parts.length > 0 ? parts.join("; ") : "none reported";
37828
+ function terminalKind(response) {
37829
+ for (const value of [
37830
+ response.terminal,
37831
+ response.outcome,
37832
+ response.inspect_outcome,
37833
+ response.inspect_terminal,
37834
+ response.status
37835
+ ]) {
37836
+ if (typeof value !== "string")
37837
+ continue;
37838
+ const normalized = value.toUpperCase().replaceAll("_", "-");
37839
+ if (normalized === "FRESH" || normalized === "INTERRUPTED" || normalized === "PHASE-FAILED") {
37840
+ return normalized;
37841
+ }
37842
+ }
37843
+ return;
37844
+ }
37845
+ function firstRecord(...values) {
37846
+ for (const value of values) {
37847
+ const record2 = asRecord3(value);
37848
+ if (record2)
37849
+ return record2;
37850
+ }
37851
+ return;
37852
+ }
37853
+ function firstString(...values) {
37854
+ for (const value of values) {
37855
+ const text = asString2(value);
37856
+ if (text !== undefined)
37857
+ return text;
37858
+ }
37859
+ return;
37860
+ }
37861
+ function parseInspectPhaseEntries(value) {
37862
+ if (!Array.isArray(value))
37863
+ return [];
37864
+ return value.flatMap((candidate) => {
37865
+ const entry = asRecord3(candidate);
37866
+ const id = asString2(entry?.id);
37867
+ if (!id)
37868
+ return [];
37869
+ const alsoSatisfied = Array.isArray(entry?.also_satisfied) ? entry.also_satisfied.filter((category) => typeof category === "string") : [];
37870
+ return [
37871
+ {
37872
+ id,
37873
+ producer: asString2(entry?.producer),
37874
+ category: asString2(entry?.category),
37875
+ alsoSatisfied
37876
+ }
37877
+ ];
37878
+ });
37879
+ }
37880
+ function parseInspectTerminal(payload) {
37881
+ const response = asRecord3(payload);
37882
+ if (!response)
37883
+ return;
37884
+ const kind = terminalKind(response);
37885
+ if (!kind)
37886
+ return;
37887
+ const waitStamp = firstRecord(response.wait_stamp, response.waitStamp, response.blocking_wait_stamp);
37888
+ const phaseSource = kind === "FRESH" ? waitStamp ?? response : response;
37889
+ const phases = parseInspectPhaseEntries(phaseSource.phases ?? response.completed_phases ?? response.completedPhases);
37890
+ if (kind !== "PHASE-FAILED") {
37891
+ return {
37892
+ kind,
37893
+ phases,
37894
+ waitStampText: kind === "FRESH" ? firstString(waitStamp?.text, waitStamp?.human_text) : undefined
37895
+ };
37896
+ }
37897
+ const failedPhaseRecord = asRecord3(response.failed_phase);
37898
+ const failedPhaseId = firstString(failedPhaseRecord?.id, response.failed_phase);
37899
+ const failedPhase = failedPhaseId ? parseInspectPhaseEntries([
37900
+ {
37901
+ id: failedPhaseId,
37902
+ producer: firstString(failedPhaseRecord?.producer, response.failed_phase_producer, response.producer),
37903
+ category: firstString(failedPhaseRecord?.category, response.failed_phase_category, response.category)
37904
+ }
37905
+ ])[0] : undefined;
37906
+ return {
37907
+ kind,
37908
+ phases,
37909
+ failedPhase,
37910
+ failureReason: asString2(response.failure_reason),
37911
+ failureDetail: asString2(response.failure_detail)
37912
+ };
37913
+ }
37914
+ function formatPhase(entry) {
37915
+ const details = [
37916
+ entry.producer ? `producer: ${entry.producer}` : undefined,
37917
+ entry.category ? `category: ${entry.category}` : undefined,
37918
+ entry.alsoSatisfied.length > 0 ? `also satisfied: ${entry.alsoSatisfied.join(", ")}` : undefined
37919
+ ].filter((detail) => Boolean(detail));
37920
+ return details.length > 0 ? `${entry.id} (${details.join("; ")})` : entry.id;
37921
+ }
37922
+ function renderInspectTerminal(terminal, serverText) {
37923
+ const lines = [terminal.kind];
37924
+ if (terminal.kind === "FRESH") {
37925
+ lines.push(`wait-stamp: ${terminal.waitStampText ?? "not supplied"}`);
37926
+ }
37927
+ if (terminal.kind === "PHASE-FAILED") {
37928
+ if (terminal.failedPhase)
37929
+ lines.push(`failed phase: ${formatPhase(terminal.failedPhase)}`);
37930
+ lines.push(`failure reason: ${terminal.failureReason ?? "not supplied"}`);
37931
+ if (terminal.failureDetail)
37932
+ lines.push(`failure detail: ${terminal.failureDetail}`);
37933
+ }
37934
+ lines.push(terminal.phases.length > 0 ? `completed phases:
37935
+ ${terminal.phases.map((phase) => `- ${formatPhase(phase)}`).join(`
37936
+ `)}` : "completed phases: none");
37937
+ if (serverText?.trim())
37938
+ lines.push(serverText);
37939
+ return lines.join(`
37940
+ `);
37706
37941
  }
37707
37942
  function diagnosticsSummaryPart(summary) {
37708
37943
  const section = asRecord3(summary?.diagnostics);
@@ -37712,31 +37947,9 @@ function diagnosticsSummaryPart(summary) {
37712
37947
  const warnings = asNumber2(section.warnings);
37713
37948
  const info = asNumber2(section.info);
37714
37949
  const hints = asNumber2(section.hints);
37715
- const hasCounts = [errors3, warnings, info, hints].some((value) => value !== undefined);
37716
- const counts = `${errors3 ?? 0} errors/${warnings ?? 0} warnings/${info ?? 0} info/${hints ?? 0} hints`;
37717
- const status = asString2(section.status);
37718
- const provisionalCounts = asRecord3(section.provisional_counts);
37719
- const provisionalValues = provisionalCounts ? [
37720
- asNumber2(provisionalCounts.errors),
37721
- asNumber2(provisionalCounts.warnings),
37722
- asNumber2(provisionalCounts.info),
37723
- asNumber2(provisionalCounts.hints)
37724
- ] : [];
37725
- const provisionalText = provisionalCounts ? ` (${provisionalValues[0] ?? 0} errors/${provisionalValues[1] ?? 0} warnings/${provisionalValues[2] ?? 0} info/${provisionalValues[3] ?? 0} hints)` : "";
37726
- const provisionalFraming = `provisional — analyzer not ready; counts excluded from E/W${provisionalText}`;
37727
- if (status === "pending") {
37728
- return `diagnostics ${provisionalFraming} — still pending (servers: ${diagnosticsServerSummary(section)}); wait for the LSP update and use the next normal aft_inspect, not repeated polling`;
37729
- }
37730
- if (status === "incomplete") {
37731
- return `diagnostics ${provisionalFraming} (incomplete — servers: ${diagnosticsServerSummary(section)})`;
37732
- }
37733
- if (provisionalCounts) {
37734
- return `diagnostics ${provisionalFraming}`;
37735
- }
37736
- if (hasCounts) {
37737
- return `diagnostics ${counts}`;
37738
- }
37739
- return;
37950
+ if (![errors3, warnings, info, hints].some((value) => value !== undefined))
37951
+ return;
37952
+ return `diagnostics ${errors3 ?? 0} errors/${warnings ?? 0} warnings/${info ?? 0} info/${hints ?? 0} hints`;
37740
37953
  }
37741
37954
  function diagnosticLocation(diagnostic) {
37742
37955
  const file2 = asString2(diagnostic.file) ?? "(unknown file)";
@@ -37748,36 +37961,28 @@ function diagnosticLocation(diagnostic) {
37748
37961
  return `${file2}:${line}`;
37749
37962
  return `${file2}:${line}:${column}`;
37750
37963
  }
37751
- function diagnosticsDetailSection(details, summary) {
37964
+ function diagnosticsDetailSection(details) {
37752
37965
  const diagnostics = asRecords2(details?.diagnostics);
37753
37966
  if (diagnostics.length === 0)
37754
37967
  return;
37755
- const diagnosticsSummary = asRecord3(summary?.diagnostics);
37756
- const provisional = asString2(diagnosticsSummary?.status) === "pending" || asString2(diagnosticsSummary?.status) === "incomplete" || asRecord3(diagnosticsSummary?.provisional_counts) !== undefined;
37757
- const lines = [
37758
- provisional ? "diagnostics (provisional — analyzer not ready; counts excluded from E/W)" : "diagnostics"
37759
- ];
37760
- for (const diagnostic of diagnostics) {
37761
- const severity = asString2(diagnostic.severity) ?? "information";
37762
- const message = asString2(diagnostic.message) ?? "(no message)";
37763
- const source = asString2(diagnostic.source);
37764
- const suffix = source ? ` [${source}]` : "";
37765
- lines.push(`- ${diagnosticLocation(diagnostic)} ${severity} ${message}${suffix}`);
37766
- }
37767
- return lines.join(`
37968
+ return [
37969
+ "diagnostics",
37970
+ ...diagnostics.map((diagnostic) => {
37971
+ const severity = asString2(diagnostic.severity) ?? "information";
37972
+ const message = asString2(diagnostic.message) ?? "(no message)";
37973
+ const source = asString2(diagnostic.source);
37974
+ return `- ${diagnosticLocation(diagnostic)} ${severity} ${message}${source ? ` [${source}]` : ""}`;
37975
+ })
37976
+ ].join(`
37768
37977
  `);
37769
37978
  }
37770
37979
  function countFrom(summary, key) {
37771
- const section = asRecord3(summary?.[key]);
37772
- return asNumber2(section?.count);
37980
+ return asNumber2(asRecord3(summary?.[key])?.count);
37773
37981
  }
37774
37982
  function tier2SummaryPart(summary, key, label) {
37775
37983
  const section = asRecord3(summary?.[key]);
37776
37984
  const count = asNumber2(section?.count);
37777
- if (count !== undefined)
37778
- return `${label} ${count}`;
37779
- const status = asString2(section?.status);
37780
- return `${label} ${status ?? "unavailable"}`;
37985
+ return count !== undefined ? `${label} ${count}` : `${label} unavailable`;
37781
37986
  }
37782
37987
  function shortDupOccurrence(entry) {
37783
37988
  const [path2] = entry.split(":");
@@ -37785,95 +37990,41 @@ function shortDupOccurrence(entry) {
37785
37990
  }
37786
37991
  function tier2TopPreview(summary, theme) {
37787
37992
  const lines = [];
37788
- const dup = asRecord3(summary?.duplicates);
37789
- const dupTop = Array.isArray(dup?.top) ? dup.top : [];
37993
+ const dupTop = Array.isArray(asRecord3(summary?.duplicates)?.top) ? asRecord3(summary?.duplicates)?.top : [];
37790
37994
  for (const group of dupTop) {
37791
37995
  const record2 = asRecord3(group);
37792
37996
  const files = Array.isArray(record2?.files) ? record2.files : [];
37793
37997
  const cost = asNumber2(record2?.cost);
37794
37998
  if (files.length < 2)
37795
37999
  continue;
37796
- const a = shortDupOccurrence(String(files[0]));
37797
- const b = shortDupOccurrence(String(files[1]));
37798
- lines.push(` dup ${a} ↔ ${b}${cost !== undefined ? ` (${cost})` : ""}`);
38000
+ lines.push(` dup ${shortDupOccurrence(String(files[0]))} ↔ ${shortDupOccurrence(String(files[1]))}${cost !== undefined ? ` (${cost})` : ""}`);
37799
38001
  }
37800
38002
  for (const [key, label] of [
37801
38003
  ["dead_code", "dead"],
37802
38004
  ["unused_exports", "unused"]
37803
38005
  ]) {
37804
- const section = asRecord3(summary?.[key]);
37805
- const top = Array.isArray(section?.top) ? section.top : [];
38006
+ const top = Array.isArray(asRecord3(summary?.[key])?.top) ? asRecord3(summary?.[key])?.top : [];
37806
38007
  for (const item of top) {
37807
38008
  const record2 = asRecord3(item);
37808
38009
  const file2 = asString2(record2?.file);
37809
38010
  const symbol2 = asString2(record2?.symbol);
37810
- if (!file2 || !symbol2)
37811
- continue;
37812
- lines.push(` ${label} ${symbol2} (${file2.split("/").pop()})`);
38011
+ if (file2 && symbol2)
38012
+ lines.push(` ${label} ${symbol2} (${file2.split("/").pop()})`);
37813
38013
  }
37814
38014
  }
37815
- if (lines.length === 0)
37816
- return;
37817
- return `${theme.fg("muted", "top findings:")}
38015
+ return lines.length > 0 ? `${theme.fg("muted", "top findings:")}
37818
38016
  ${lines.join(`
37819
- `)}`;
37820
- }
37821
- function tier2RefreshCategories(response) {
37822
- const scannerState = asRecord3(response.scanner_state);
37823
- const categories = new Set;
37824
- for (const key of ["pending_categories", "stale_categories"]) {
37825
- const values = scannerState?.[key];
37826
- if (!Array.isArray(values))
37827
- continue;
37828
- for (const category of values) {
37829
- if (typeof category === "string" && TIER2_INSPECT_CATEGORIES.has(category)) {
37830
- categories.add(category);
37831
- }
37832
- }
37833
- }
37834
- return [...categories];
37835
- }
37836
- function runPendingTier2Categories(bridge, categories, extCtx) {
37837
- const now = Date.now();
37838
- const running = runningTier2Categories.get(bridge) ?? new Set;
37839
- const lastTriggerAt = lastTier2TriggerAtByBridge.get(bridge) ?? new Map;
37840
- const toRun = categories.filter((category) => {
37841
- if (running.has(category))
37842
- return false;
37843
- const previousTriggerAt = lastTriggerAt.get(category);
37844
- return previousTriggerAt === undefined || previousTriggerAt + INSPECT_TIER2_MIN_TRIGGER_INTERVAL_MS <= now;
37845
- });
37846
- if (toRun.length === 0)
37847
- return;
37848
- for (const category of toRun) {
37849
- running.add(category);
37850
- lastTriggerAt.set(category, now);
37851
- }
37852
- runningTier2Categories.set(bridge, running);
37853
- lastTier2TriggerAtByBridge.set(bridge, lastTriggerAt);
37854
- callBridge(bridge, "inspect_tier2_run", { categories: toRun }, extCtx, {
37855
- transportTimeoutMs: INSPECT_TIER2_RUN_TIMEOUT_MS
37856
- }).catch(() => {}).finally(() => {
37857
- const active = runningTier2Categories.get(bridge);
37858
- if (!active)
37859
- return;
37860
- for (const category of toRun) {
37861
- active.delete(category);
37862
- }
37863
- if (active.size === 0) {
37864
- runningTier2Categories.delete(bridge);
37865
- }
37866
- });
38017
+ `)}` : undefined;
37867
38018
  }
37868
38019
  function buildInspectSections(payload, theme) {
38020
+ const terminal = parseInspectTerminal(payload);
38021
+ if (terminal)
38022
+ return [renderInspectTerminal(terminal, asString2(asRecord3(payload)?.text))];
37869
38023
  const response = asRecord3(payload);
37870
38024
  if (!response)
37871
38025
  return [theme.fg("muted", "No inspect snapshot available.")];
37872
38026
  const summary = asRecord3(response.summary);
37873
38027
  const metrics = asRecord3(summary?.metrics);
37874
- const scannerState = asRecord3(response.scanner_state);
37875
- const stale = Array.isArray(scannerState?.stale_categories) ? scannerState.stale_categories.length : 0;
37876
- const pending = Array.isArray(scannerState?.pending_categories) ? scannerState.pending_categories.length : 0;
37877
38028
  const parts = [
37878
38029
  `todos ${countFrom(summary, "todos") ?? 0}`,
37879
38030
  diagnosticsSummaryPart(summary),
@@ -37884,9 +38035,6 @@ function buildInspectSections(payload, theme) {
37884
38035
  tier2SummaryPart(summary, "cycles", "cycles")
37885
38036
  ].filter((part) => Boolean(part));
37886
38037
  const sections = [theme.fg("accent", parts.join(" · "))];
37887
- if (stale > 0 || pending > 0) {
37888
- 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`));
37889
- }
37890
38038
  const topPreview = tier2TopPreview(summary, theme);
37891
38039
  if (topPreview)
37892
38040
  sections.push(topPreview);
@@ -37894,7 +38042,7 @@ function buildInspectSections(payload, theme) {
37894
38042
  if (details) {
37895
38043
  const names = Object.keys(details);
37896
38044
  sections.push(names.length > 0 ? `details: ${names.join(", ")}` : theme.fg("muted", "No drill-down details returned."));
37897
- const diagnosticsDetails = diagnosticsDetailSection(details, summary);
38045
+ const diagnosticsDetails = diagnosticsDetailSection(details);
37898
38046
  if (diagnosticsDetails)
37899
38047
  sections.push(diagnosticsDetails);
37900
38048
  }
@@ -37910,15 +38058,19 @@ function renderInspectCall(args, theme, context) {
37910
38058
  return renderToolCall("inspect", summary ? theme.fg("toolOutput", summary) : undefined, theme, context);
37911
38059
  }
37912
38060
  function renderInspectResult(result, theme, context) {
38061
+ const payload = extractStructuredPayload(result);
38062
+ const terminal = parseInspectTerminal(payload);
38063
+ if (terminal)
38064
+ return renderSections([renderInspectTerminal(terminal, asString2(asRecord3(payload)?.text))], context);
37913
38065
  if (context.isError)
37914
38066
  return renderErrorResult(result, "inspect failed", theme, context);
37915
- return renderSections(buildInspectSections(extractStructuredPayload(result), theme), context);
38067
+ return renderSections(buildInspectSections(payload, theme), context);
37916
38068
  }
37917
38069
  function registerInspectTool(pi, ctx) {
37918
38070
  pi.registerTool({
37919
38071
  name: "aft_inspect",
37920
38072
  label: "inspect",
37921
- description: "Codebase health snapshot. One call returns summary stats for: TODOs, diagnostics, file/symbol metrics, dead code, unused exports, code duplicates, and TS/JS import cycles. 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, cycles) waits for a fresh reuse scan up to a short deadline; if a category is still scanning the response reports `complete: false` with `pending_categories: [...]` rather than a fabricated clean count. Pi may still trigger a deduped background warmup for categories that remain pending. Rust module cycles are out of scope for `cycles`.\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.
38073
+ description: "Blocking-fresh codebase health inspection. Each call completes current analysis and produces exactly one terminal result: FRESH includes a wait-stamp and completed phases; INTERRUPTED and PHASE-FAILED retain completed phases, with PHASE-FAILED also reporting its phase attribution and failure reason. `sections` selects drill-down detail, not the categories verified.\n\n" + "Use `scope=` to narrow returned results. It does not reduce the fresh verification work. Passive health changes use the alert channel; do not infer inspect completion from that channel.\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.
37922
38074
 
37923
38075
  ` + "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.",
37924
38076
  parameters: InspectParams,
@@ -37934,13 +38086,12 @@ function registerInspectTool(pi, ctx) {
37934
38086
  rawArgs.scope = scope;
37935
38087
  if (topK !== undefined)
37936
38088
  rawArgs.topK = topK;
37937
- const response = await callToolCall(bridge, "inspect", rawArgs, extCtx, {
37938
- keepBridgeOnTimeout: true
37939
- });
37940
- if (response.success === false) {
38089
+ const response = await callToolCall(bridge, "inspect", rawArgs, extCtx);
38090
+ const terminal = parseInspectTerminal(response);
38091
+ if (terminal)
38092
+ return textResult(renderInspectTerminal(terminal, response.text), response);
38093
+ if (response.success === false)
37941
38094
  throw new Error(response.text || response.message || "inspect failed");
37942
- }
37943
- runPendingTier2Categories(bridge, tier2RefreshCategories(response), extCtx);
37944
38095
  return textResult(response.text, response);
37945
38096
  },
37946
38097
  renderCall(args, theme, context) {
@@ -39100,7 +39251,7 @@ var PLUGIN_VERSION = (() => {
39100
39251
  return "0.0.0";
39101
39252
  }
39102
39253
  })();
39103
- var ANNOUNCEMENT_VERSION = "0.50.3";
39254
+ var ANNOUNCEMENT_VERSION = "0.51.0";
39104
39255
  var ANNOUNCEMENT_FEATURES = [
39105
39256
  "Callgraph stays consistent under load: edits arriving mid-rebuild can no longer be silently lost from navigation results.",
39106
39257
  "aft_zoom handles real-world .jsonc: files with comment banners above the opening brace now resolve, and not-found errors point at the segment that actually failed (thanks @iceteaSA).",
@@ -39463,12 +39614,7 @@ ${lines}
39463
39614
  pi.on("tool_result", async (event, extCtx) => {
39464
39615
  const sessionID = resolveSessionId(extCtx);
39465
39616
  const bgContent = await appendToolResultBgCompletions({ ctx, directory: extCtx.cwd, sessionID }, event.content);
39466
- let content = bgContent ?? event.content;
39467
- const isInspect = typeof event.details === "object" && event.details !== null && "scanner_state" in event.details;
39468
- const activeBridge = pool.getActiveBridgeForRoot(extCtx.cwd);
39469
- const bar = statusBarBlockForSession(sessionID, activeBridge?.getStatusBar(), isInspect);
39470
- if (bar)
39471
- content = [...content, { type: "text", text: bar }];
39617
+ const content = bgContent ?? event.content;
39472
39618
  if (content === event.content)
39473
39619
  return;
39474
39620
  return { content, details: event.details, isError: event.isError };