@cortexkit/aft 0.63.5 → 0.65.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
@@ -263,10 +263,87 @@ function relativePathEscapesRoot(rel, platform = process.platform) {
263
263
  }
264
264
  var init_path_display = () => {};
265
265
 
266
+ // ../aft-bridge/dist/wait-clock.js
267
+ class WaitClock {
268
+ wallNow;
269
+ awakeNow;
270
+ wall;
271
+ awake;
272
+ skew = 0;
273
+ elapsed;
274
+ suspendCount = 0;
275
+ lastSuspendEnd = Number.NEGATIVE_INFINITY;
276
+ constructor(wallNow = Date.now, awakeNow = () => performance.now()) {
277
+ this.wallNow = wallNow;
278
+ this.awakeNow = awakeNow;
279
+ this.wall = wallNow();
280
+ this.awake = awakeNow();
281
+ this.elapsed = this.awake;
282
+ }
283
+ now() {
284
+ const wall = this.wallNow();
285
+ const awake = this.awakeNow();
286
+ const active = Math.max(0, awake - this.awake);
287
+ this.skew = Math.max(0, this.skew + wall - this.wall - active);
288
+ this.elapsed += active;
289
+ if (this.skew >= SUSPEND_SKEW_MS) {
290
+ this.elapsed += this.skew;
291
+ this.lastSuspendEnd = this.elapsed;
292
+ this.skew = 0;
293
+ this.suspendCount++;
294
+ }
295
+ this.wall = wall;
296
+ this.awake = awake;
297
+ return this.elapsed;
298
+ }
299
+ }
300
+
301
+ class RequestDeadline {
302
+ clock;
303
+ allowResume;
304
+ deadline;
305
+ suspendCount;
306
+ resumed = false;
307
+ constructor(clock, timeoutMs, allowResume = true) {
308
+ this.clock = clock;
309
+ this.allowResume = allowResume;
310
+ this.deadline = clock.now() + timeoutMs;
311
+ this.suspendCount = clock.suspendCount;
312
+ }
313
+ remaining() {
314
+ const now = this.clock.now();
315
+ if (now >= this.deadline && this.allowResume && !this.resumed && this.clock.suspendCount > this.suspendCount && this.clock.lastSuspendEnd >= this.deadline) {
316
+ this.resumed = true;
317
+ this.deadline = now + RESUME_ALLOWANCE_MS;
318
+ }
319
+ return Math.max(0, this.deadline - now);
320
+ }
321
+ }
322
+ var SUSPEND_SKEW_MS = 1000, RESUME_ALLOWANCE_MS = 1e4, DEADLINE_POLL_MS = 250;
323
+
266
324
  // ../aft-bridge/dist/bash-hints.js
325
+ function abortableSleep(ms, signal) {
326
+ if (signal?.aborted)
327
+ return Promise.resolve();
328
+ return new Promise((resolve) => {
329
+ const done = () => {
330
+ clearTimeout(timer);
331
+ signal?.removeEventListener("abort", done);
332
+ resolve();
333
+ };
334
+ const timer = setTimeout(done, ms);
335
+ signal?.addEventListener("abort", done, { once: true });
336
+ });
337
+ }
338
+ var watchClock;
267
339
  var init_bash_hints = __esm(() => {
268
340
  init_bash_format();
269
341
  init_path_display();
342
+ watchClock = {
343
+ source: new WaitClock,
344
+ now: () => watchClock.source.now(),
345
+ sleep: abortableSleep
346
+ };
270
347
  });
271
348
 
272
349
  // ../aft-bridge/dist/test-child-environment.js
@@ -498,12 +575,13 @@ function resolvePluginLogPath(context = {}) {
498
575
  var init_storage_paths = () => {};
499
576
 
500
577
  // ../aft-bridge/dist/bash-host-fallback.js
501
- var BASH_HOST_FALLBACK_MAX_OUTPUT_BYTES, BASH_HOST_FALLBACK_MAX_TIMEOUT_MS;
578
+ var BASH_HOST_FALLBACK_MAX_OUTPUT_BYTES, BASH_HOST_FALLBACK_MAX_TIMEOUT_MS, BASH_RUNON_MEMORY_DESCRIPTION = "On Linux, add `,Ngb` to ask for N GiB of memory, for example `linux,2c,16gb` for a Rust build.", BASH_RUNON_DESCRIPTION;
502
579
  var init_bash_host_fallback = __esm(() => {
503
580
  init_child_process();
504
581
  init_storage_paths();
505
582
  BASH_HOST_FALLBACK_MAX_OUTPUT_BYTES = 100 * 1024;
506
583
  BASH_HOST_FALLBACK_MAX_TIMEOUT_MS = 10 * 60 * 1000;
584
+ BASH_RUNON_DESCRIPTION = 'Run on a remote build server: `linux` (2 vCPUs), or `linux,Nc` for N vCPUs such as `linux,8c`; add ",net" for outbound internet (offline by default). `windows` runs the command in Git Bash on a Windows build machine (no vCPU or network options). `macos` sends the command to a macOS machine for builds and tests that need macOS (no vCPU or network options).' + ` ${BASH_RUNON_MEMORY_DESCRIPTION}`;
507
585
  });
508
586
  // ../aft-bridge/dist/bash-permissions.js
509
587
  var init_bash_permissions = __esm(() => {
@@ -881,6 +959,7 @@ var init_bridge = __esm(() => {
881
959
  binaryFingerprintAsyncReader = binaryContentHash;
882
960
  SYSTEM_CLOCK = {
883
961
  now: () => Date.now(),
962
+ awakeNow: () => performance.now(),
884
963
  setTimeout: (callback, ms) => setTimeout(callback, ms),
885
964
  clearTimeout: (timer) => clearTimeout(timer)
886
965
  };
@@ -960,6 +1039,7 @@ var init_bridge = __esm(() => {
960
1039
  hangWindowMinMs;
961
1040
  hangProbeTimeoutMs;
962
1041
  clock;
1042
+ waitClock;
963
1043
  lateShellReplyGraceMs;
964
1044
  maxRestarts;
965
1045
  configured = false;
@@ -996,6 +1076,7 @@ var init_bridge = __esm(() => {
996
1076
  this.hangWindowMinMs = options?.hangWindowMinMs ?? DEFAULT_MIN_HANG_WINDOW_MS;
997
1077
  this.hangProbeTimeoutMs = options?.hangProbeTimeoutMs ?? DEFAULT_HANG_PROBE_TIMEOUT_MS;
998
1078
  this.clock = options?.clock ?? SYSTEM_CLOCK;
1079
+ this.waitClock = new WaitClock(() => this.clock.now(), () => this.clock.awakeNow?.() ?? this.clock.now());
999
1080
  this.lateShellReplyGraceMs = options?.lateShellReplyGraceMs ?? DEFAULT_LATE_SHELL_REPLY_GRACE_MS;
1000
1081
  this.maxRestarts = options?.maxRestarts ?? 3;
1001
1082
  this.errorPrefix = options?.errorPrefix ?? "[aft-bridge]";
@@ -1362,11 +1443,17 @@ var init_bridge = __esm(() => {
1362
1443
  const keepBridgeOnTimeout = passive || command === "bash_drain_completions" || options?.keepBridgeOnTimeout === true;
1363
1444
  const child = this.process;
1364
1445
  const response = await new Promise((resolve, reject) => {
1365
- const timer = this.clock.setTimeout(() => {
1446
+ const deadline = new RequestDeadline(this.waitClock, effectiveTimeoutMs);
1447
+ const expire = () => {
1366
1448
  const entry = this.pending.get(id);
1367
1449
  if (!entry)
1368
1450
  return;
1369
- if (keepBridgeOnTimeout && this.holdForLateShellReply(id, entry, effectiveTimeoutMs, requestSessionId)) {
1451
+ const remaining = deadline.remaining();
1452
+ if (remaining > 0) {
1453
+ entry.timer = this.clock.setTimeout(expire, Math.min(remaining, DEADLINE_POLL_MS));
1454
+ return;
1455
+ }
1456
+ if (keepBridgeOnTimeout && !deadline.resumed && this.holdForLateShellReply(id, entry, effectiveTimeoutMs, requestSessionId)) {
1370
1457
  return;
1371
1458
  }
1372
1459
  this.pending.delete(id);
@@ -1379,11 +1466,12 @@ var init_bridge = __esm(() => {
1379
1466
  this.warnVia(timeoutMsg);
1380
1467
  }
1381
1468
  if (keepBridgeOnTimeout) {
1382
- entry.reject(new BridgeTransportTimeoutError(command, effectiveTimeoutMs, `${this.errorPrefix} Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms`));
1469
+ entry.reject(new BridgeTransportTimeoutError(command, effectiveTimeoutMs, `${this.errorPrefix} Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms`, deadline.resumed && SHELL_TASK_COMMANDS.has(command) ? { kind: "unknown" } : undefined));
1383
1470
  return;
1384
1471
  }
1385
1472
  entry.reject(new Error(`${this.errorPrefix} request "${command}" timed out after ${effectiveTimeoutMs}ms (bridge busy/under load); bridge kept warm — retry. A timed-out mutation may still complete; inspect before repeating it.`));
1386
- }, effectiveTimeoutMs);
1473
+ };
1474
+ const timer = this.clock.setTimeout(expire, Math.min(effectiveTimeoutMs, DEADLINE_POLL_MS));
1387
1475
  const entry = {
1388
1476
  resolve,
1389
1477
  reject,
@@ -1939,14 +2027,21 @@ var init_bridge = __esm(() => {
1939
2027
  };
1940
2028
  entry.resolve = (response) => settle(shellOutcomeFromLateReply(response));
1941
2029
  this.clock.clearTimeout(entry.timer);
1942
- entry.timer = this.clock.setTimeout(() => {
2030
+ const deadline = new RequestDeadline(this.waitClock, graceMs, false);
2031
+ const expire = () => {
1943
2032
  if (this.pending.get(id) !== entry)
1944
2033
  return;
2034
+ const remaining = deadline.remaining();
2035
+ if (remaining > 0) {
2036
+ entry.timer = this.clock.setTimeout(expire, Math.min(remaining, DEADLINE_POLL_MS));
2037
+ return;
2038
+ }
1945
2039
  this.pending.delete(id);
1946
2040
  entry.onSettled?.();
1947
2041
  this.warnVia(`Request "${entry.command}" (id=${id}) got no reply within ${graceMs}ms after its deadline`);
1948
2042
  settle({ kind: "unknown" });
1949
- }, graceMs);
2043
+ };
2044
+ entry.timer = this.clock.setTimeout(expire, Math.min(graceMs, DEADLINE_POLL_MS));
1950
2045
  return true;
1951
2046
  }
1952
2047
  accountForBashTaskResponse(command, response) {
@@ -2289,7 +2384,10 @@ function readConfigTiers(opts) {
2289
2384
  var init_config_tiers = () => {};
2290
2385
 
2291
2386
  // ../aft-bridge/dist/config-watch.js
2292
- var init_config_watch = () => {};
2387
+ var CONFIG_READ_LIMIT;
2388
+ var init_config_watch = __esm(() => {
2389
+ CONFIG_READ_LIMIT = 1024 * 1024;
2390
+ });
2293
2391
 
2294
2392
  // ../aft-bridge/dist/platform.js
2295
2393
  var PLATFORM_ARCH_MAP, PLATFORM_ASSET_MAP;
@@ -2836,10 +2934,11 @@ var init_durable_log = __esm(() => {
2836
2934
  init_storage_paths();
2837
2935
  DEFAULT_LOG_BYTES = 32 * 1024 * 1024;
2838
2936
  });
2839
- // ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/envelope.js
2840
- var MAX_FRAME_BODY_LEN, FrameType, FRAME_TYPE_MAX, Priority, AdmissionClass;
2937
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.24.5/node_modules/@cortexkit/subc-client/dist/envelope.js
2938
+ var MAX_FRAME_BODY_LEN, MAX_DELIVERED_FRAME_LEN, FrameType, FRAME_TYPE_MAX, Priority, AdmissionClass;
2841
2939
  var init_envelope = __esm(() => {
2842
2940
  MAX_FRAME_BODY_LEN = 64 * 1024 * 1024;
2941
+ MAX_DELIVERED_FRAME_LEN = 4 * 1024 * 1024;
2843
2942
  (function(FrameType) {
2844
2943
  FrameType[FrameType["Request"] = 0] = "Request";
2845
2944
  FrameType[FrameType["Response"] = 1] = "Response";
@@ -2867,7 +2966,7 @@ var init_envelope = __esm(() => {
2867
2966
  })(AdmissionClass || (AdmissionClass = {}));
2868
2967
  });
2869
2968
 
2870
- // ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/socket.js
2969
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.24.5/node_modules/@cortexkit/subc-client/dist/socket.js
2871
2970
  var WRITE_BORROWED, WRITE_TRACKED_BORROWED;
2872
2971
  var init_socket = __esm(() => {
2873
2972
  init_envelope();
@@ -2875,17 +2974,17 @@ var init_socket = __esm(() => {
2875
2974
  WRITE_TRACKED_BORROWED = Symbol("subc.socket.writeTrackedBorrowed");
2876
2975
  });
2877
2976
 
2878
- // ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/auth.js
2977
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.24.5/node_modules/@cortexkit/subc-client/dist/auth.js
2879
2978
  var init_auth = __esm(() => {
2880
2979
  init_socket();
2881
2980
  });
2882
2981
 
2883
- // ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/connection-file.js
2982
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.24.5/node_modules/@cortexkit/subc-client/dist/connection-file.js
2884
2983
  var init_connection_file = __esm(() => {
2885
2984
  init_envelope();
2886
2985
  });
2887
2986
 
2888
- // ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/launch-nonce.js
2987
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.24.5/node_modules/@cortexkit/subc-client/dist/launch-nonce.js
2889
2988
  var PROCESS_CELL_KEY, U64_MAX, I32_MAX, RedactedLaunchNonce;
2890
2989
  var init_launch_nonce = __esm(() => {
2891
2990
  PROCESS_CELL_KEY = Symbol.for("@cortexkit/subc-client/launch-nonce/v1");
@@ -2910,15 +3009,14 @@ var init_launch_nonce = __esm(() => {
2910
3009
  }
2911
3010
  };
2912
3011
  });
2913
-
2914
- // ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/route-handle.js
3012
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.24.5/node_modules/@cortexkit/subc-client/dist/route-handle.js
2915
3013
  var connectionToken, reverseRequests;
2916
3014
  var init_route_handle = __esm(() => {
2917
3015
  connectionToken = new WeakMap;
2918
3016
  reverseRequests = new WeakMap;
2919
3017
  });
2920
3018
 
2921
- // ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/client.js
3019
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.24.5/node_modules/@cortexkit/subc-client/dist/client.js
2922
3020
  import { debuglog } from "node:util";
2923
3021
  var debug, EMPTY_BODY, errorFrameFlags, SCOPE_ROUTE_CLOSE_REASONS, ROUTE_OPEN_ADMISSION_PRESSURE_REASONS;
2924
3022
  var init_client = __esm(() => {
@@ -2940,8 +3038,7 @@ var init_client = __esm(() => {
2940
3038
  ]);
2941
3039
  ROUTE_OPEN_ADMISSION_PRESSURE_REASONS = new Set(["open_admission_full", "target_binds_full"]);
2942
3040
  });
2943
-
2944
- // ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/provider.js
3041
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.24.5/node_modules/@cortexkit/subc-client/dist/provider.js
2945
3042
  var init_provider = __esm(() => {
2946
3043
  init_auth();
2947
3044
  init_client();
@@ -2952,7 +3049,7 @@ var init_provider = __esm(() => {
2952
3049
  init_socket();
2953
3050
  });
2954
3051
 
2955
- // ../../node_modules/.bun/@cortexkit+subc-client@0.21.2/node_modules/@cortexkit/subc-client/dist/index.js
3052
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.24.5/node_modules/@cortexkit/subc-client/dist/index.js
2956
3053
  var init_dist = __esm(() => {
2957
3054
  init_client();
2958
3055
  init_route_handle();
@@ -3539,6 +3636,45 @@ function dropNoEffectPaths(map, prefix, out) {
3539
3636
  }
3540
3637
  }
3541
3638
  }
3639
+ function retiredIdleCheckoutHours(value) {
3640
+ const raw = typeof value === "number" && Number.isSafeInteger(value) ? value : IDLE_CHECKOUT_HOURS_DEFAULT;
3641
+ return Math.min(IDLE_CHECKOUT_HOURS_MAX, Math.max(IDLE_CHECKOUT_HOURS_MIN, raw));
3642
+ }
3643
+ function translateDataLayerPaths(map, prefix, out) {
3644
+ const [retired, replacement] = RETIRED_IDLE_CHECKOUT_HOURS;
3645
+ const hours = takeNested(map, "views", "idle_checkout_hours");
3646
+ if (hours.found) {
3647
+ recordRetired(out, prefix, retired);
3648
+ if (isRecord(map.idle) && map.idle.checkout_hours !== undefined) {
3649
+ superseded(out, `${prefix}${retired}`, `${retired} is ignored because ${replacement} is set`);
3650
+ } else {
3651
+ if (!Object.hasOwn(map, "idle"))
3652
+ map.idle = {};
3653
+ if (isRecord(map.idle))
3654
+ map.idle.checkout_hours = retiredIdleCheckoutHours(hours.value);
3655
+ }
3656
+ }
3657
+ for (const path of REMOVED_DATA_LAYER_PATHS) {
3658
+ const [container, leaf] = path.split(".");
3659
+ if (container && leaf && takeNested(map, container, leaf).found)
3660
+ recordRetired(out, prefix, path);
3661
+ }
3662
+ }
3663
+ function dropRemovedDataLayerKeys(map) {
3664
+ const blocks = [map];
3665
+ if (isRecord(map.harnesses)) {
3666
+ for (const block of Object.values(map.harnesses))
3667
+ if (isRecord(block))
3668
+ blocks.push(block);
3669
+ }
3670
+ for (const block of blocks) {
3671
+ for (const path of REMOVED_DATA_LAYER_PATHS) {
3672
+ const [container, leaf] = path.split(".");
3673
+ if (container && leaf)
3674
+ takeNested(block, container, leaf);
3675
+ }
3676
+ }
3677
+ }
3542
3678
  function translateExperimentalPaths(map, prefix, out) {
3543
3679
  for (const [oldKey, leaf] of [
3544
3680
  ["experimental_lsp_ty", "lsp_ty"],
@@ -3571,23 +3707,25 @@ function translateExperimentalPaths(map, prefix, out) {
3571
3707
  const experimental = map.experimental;
3572
3708
  const legacy = experimental.bash;
3573
3709
  if (isRecord(legacy)) {
3574
- if (!["rewrite", "compress", "background"].some((leaf) => Object.hasOwn(legacy, leaf)))
3575
- return;
3710
+ if (Object.keys(legacy).length === 0)
3711
+ recordRetired(out, prefix, "experimental.bash");
3576
3712
  for (const leaf of Object.keys(legacy))
3577
3713
  recordRetired(out, prefix, `experimental.bash.${leaf}`);
3578
3714
  if (Object.hasOwn(map, "bash")) {
3579
3715
  superseded(out, "experimental.bash", 'experimental.bash is ignored because top-level "bash" is already set');
3580
3716
  } else {
3581
- const bash = {
3582
- rewrite: legacy.rewrite === true,
3583
- compress: legacy.compress === true,
3584
- background: legacy.background === true
3585
- };
3717
+ const bash = {};
3718
+ if (["rewrite", "compress", "background"].some((leaf) => Object.hasOwn(legacy, leaf))) {
3719
+ bash.rewrite = legacy.rewrite === true;
3720
+ bash.compress = legacy.compress === true;
3721
+ bash.background = legacy.background === true;
3722
+ }
3586
3723
  for (const leaf of ["long_running_reminder_enabled", "long_running_reminder_interval_ms"]) {
3587
3724
  if (Object.hasOwn(legacy, leaf))
3588
3725
  bash[leaf] = legacy[leaf];
3589
3726
  }
3590
- map.bash = bash;
3727
+ if (Object.keys(bash).length > 0)
3728
+ map.bash = bash;
3591
3729
  }
3592
3730
  } else {
3593
3731
  recordRetired(out, prefix, "experimental.bash");
@@ -3600,6 +3738,7 @@ function translateBlock(map, userBase, blockLabel, out) {
3600
3738
  const prefix = blockLabel === "base" ? "" : `${blockLabel}.`;
3601
3739
  translateGithubAliases(map, prefix, out);
3602
3740
  translateInspectLspPaths(map, prefix, out);
3741
+ translateDataLayerPaths(map, prefix, out);
3603
3742
  dropNoEffectPaths(map, prefix, out);
3604
3743
  translateExperimentalPaths(map, prefix, out);
3605
3744
  if (isRecord(map.sandbox) && Object.hasOwn(map.sandbox, "enabled")) {
@@ -3638,8 +3777,6 @@ function translateBlock(map, userBase, blockLabel, out) {
3638
3777
  });
3639
3778
  }
3640
3779
  for (const [path] of RETIRED_PATHS) {
3641
- if (path === "experimental.bash")
3642
- continue;
3643
3780
  if (hasPath(map, path))
3644
3781
  recordRetired(out, prefix, path);
3645
3782
  }
@@ -3808,7 +3945,7 @@ function mergeIndexes(base, override, restricted) {
3808
3945
  }
3809
3946
  return merged;
3810
3947
  }
3811
- var CANONICAL_TOOLS, HOST_TOOL_NAMES, DEFAULT_DISABLED_TOOLS, LEGACY_TOOL_ALIASES, RETIRED_PATHS, INDEX_INPUTS, LSP_IDLE_MINUTES_DEFAULT = 60, LSP_IDLE_MINUTES_MIN = 5, LSP_IDLE_MINUTES_MAX = 1440, REMOVED_NO_EFFECT_PATHS, RETIRED_ENABLED_FALSE_INDEXES_NOTE = "enabled: false no longer turns AFT off: it is translated to disabling every tool, but the trigram, semantic and callgraph indexes still build. To keep AFT from indexing this repository, also set indexes.trigram, indexes.semantic and indexes.callgraph to false.", SEMANTIC_COST_NOTICE_DIGEST, SECURITY_CONFIG_SECTIONS;
3948
+ var CANONICAL_TOOLS, HOST_TOOL_NAMES, DEFAULT_DISABLED_TOOLS, LEGACY_TOOL_ALIASES, REMOVED_DATA_LAYER_PATHS, RETIRED_IDLE_CHECKOUT_HOURS, IDLE_CHECKOUT_HOURS_DEFAULT = 168, IDLE_CHECKOUT_HOURS_MIN = 1, IDLE_CHECKOUT_HOURS_MAX, AFT_PACKAGE_NAMES, RETIRED_PATHS, INDEX_INPUTS, LSP_IDLE_MINUTES_DEFAULT = 60, LSP_IDLE_MINUTES_MIN = 5, LSP_IDLE_MINUTES_MAX = 1440, REMOVED_NO_EFFECT_PATHS, RETIRED_ENABLED_FALSE_INDEXES_NOTE = "enabled: false no longer turns AFT off: it is translated to disabling every tool, but the trigram, semantic and callgraph indexes still build. To keep AFT from indexing this repository, also set indexes.trigram, indexes.semantic and indexes.callgraph to false.", SEMANTIC_COST_NOTICE_DIGEST, SECURITY_CONFIG_SECTIONS;
3812
3949
  var init_feature_config = __esm(() => {
3813
3950
  CANONICAL_TOOLS = [
3814
3951
  "aft_callgraph",
@@ -3854,6 +3991,22 @@ var init_feature_config = __esm(() => {
3854
3991
  aft_glob: "glob",
3855
3992
  aft_bash: "bash"
3856
3993
  };
3994
+ REMOVED_DATA_LAYER_PATHS = [
3995
+ "views.callgraph_copies",
3996
+ "views.enabled",
3997
+ "worktree.ram_overlay"
3998
+ ];
3999
+ RETIRED_IDLE_CHECKOUT_HOURS = [
4000
+ "views.idle_checkout_hours",
4001
+ "idle.checkout_hours"
4002
+ ];
4003
+ IDLE_CHECKOUT_HOURS_MAX = 24 * 365;
4004
+ AFT_PACKAGE_NAMES = new Set([
4005
+ "@cortexkit/aft-bridge",
4006
+ "@cortexkit/aft-opencode",
4007
+ "@cortexkit/aft-pi",
4008
+ "@cortexkit/aft"
4009
+ ]);
3857
4010
  RETIRED_PATHS = [
3858
4011
  ["sandbox.enabled", "sandbox.profile"],
3859
4012
  ["tool_surface", "disabled_tools"],
@@ -3876,7 +4029,10 @@ var init_feature_config = __esm(() => {
3876
4029
  ["semantic", "semantic_search", "experimental_semantic_search"],
3877
4030
  ["callgraph", "callgraph_store", undefined]
3878
4031
  ];
3879
- REMOVED_NO_EFFECT_PATHS = ["bridge.hang_threshold"];
4032
+ REMOVED_NO_EFFECT_PATHS = [
4033
+ "bridge.hang_threshold",
4034
+ "inspect.tier2_idle_minutes"
4035
+ ];
3880
4036
  SEMANTIC_COST_NOTICE_DIGEST = noticeDigest({ notice: "semantic_default_cost_v1" });
3881
4037
  SECURITY_CONFIG_SECTIONS = new Set([
3882
4038
  "restrict_to_project_root",
@@ -4127,6 +4283,8 @@ function quoteCmdArgument(value) {
4127
4283
  return `"${value}"`;
4128
4284
  }
4129
4285
  function npmInvocation(resolved, npmArgs, platform = process.platform, env = process.env) {
4286
+ if (resolved.cliPath)
4287
+ return { command: resolved.command, args: [resolved.cliPath, ...npmArgs] };
4130
4288
  if (platform !== "win32" || !/\.(?:cmd|bat)$/i.test(resolved.command)) {
4131
4289
  return { command: resolved.command, args: [...npmArgs] };
4132
4290
  }
@@ -4319,6 +4477,12 @@ var init_lsp_npm_policy = __esm(() => {
4319
4477
  NPM_PROBE_RETRY_MS = 5 * 60000;
4320
4478
  });
4321
4479
 
4480
+ // ../aft-bridge/dist/lsp-npm-selection.js
4481
+ var init_lsp_npm_selection = __esm(() => {
4482
+ init_lsp_npm_policy();
4483
+ init_npm_resolver();
4484
+ });
4485
+
4322
4486
  // ../aft-bridge/dist/message-detach.js
4323
4487
  var BASH_WAIT_DETACH_MAGIC_KEYWORD = "&detach", STANDALONE_DETACH_KEYWORD_SOURCE, STANDALONE_DETACH_KEYWORD_PATTERN, STANDALONE_DETACH_KEYWORDS_PATTERN;
4324
4488
  var init_message_detach = __esm(() => {
@@ -5489,6 +5653,7 @@ var init_dist2 = __esm(() => {
5489
5653
  init_lsp_github_probe();
5490
5654
  init_lsp_github_table();
5491
5655
  init_lsp_npm_policy();
5656
+ init_lsp_npm_selection();
5492
5657
  init_message_detach();
5493
5658
  init_migration();
5494
5659
  init_migration_notices();
@@ -14177,27 +14342,54 @@ var init_jsonc = __esm(() => {
14177
14342
  import_comment_json = __toESM(require_src2(), 1);
14178
14343
  });
14179
14344
 
14180
- // src/lib/self-version.ts
14181
- import { createRequire as createRequire2 } from "node:module";
14182
- function getSelfVersion() {
14183
- const require2 = createRequire2(import.meta.url);
14184
- for (const relPath of ["../../package.json", "../package.json"]) {
14185
- try {
14186
- const version = require2(relPath).version;
14187
- if (typeof version === "string" && version.length > 0) {
14188
- return version;
14189
- }
14190
- } catch {}
14191
- }
14192
- return "unknown";
14193
- }
14194
- var init_self_version = () => {};
14195
-
14196
14345
  // ../../.github/opencode-version.txt
14197
14346
  var opencode_version_default = `1.19.3
14198
14347
  `;
14199
14348
  var init_opencode_version = () => {};
14200
14349
 
14350
+ // src/lib/semver.ts
14351
+ function parse2(version) {
14352
+ const match = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/.exec(version);
14353
+ if (!match)
14354
+ return;
14355
+ const prerelease = match[4]?.split(".") ?? [];
14356
+ if (prerelease.some((part) => /^\d+$/.test(part) && part.length > 1 && part[0] === "0")) {
14357
+ return;
14358
+ }
14359
+ return { core: match.slice(1, 4).map(BigInt), prerelease };
14360
+ }
14361
+ function compareSemver2(left, right) {
14362
+ const a = parse2(left);
14363
+ const b = parse2(right);
14364
+ if (!a || !b)
14365
+ return;
14366
+ for (let i = 0;i < 3; i++) {
14367
+ if (a.core[i] !== b.core[i])
14368
+ return a.core[i] > b.core[i] ? 1 : -1;
14369
+ }
14370
+ if (!a.prerelease.length || !b.prerelease.length) {
14371
+ return a.prerelease.length === b.prerelease.length ? 0 : a.prerelease.length ? -1 : 1;
14372
+ }
14373
+ for (let i = 0;i < Math.max(a.prerelease.length, b.prerelease.length); i++) {
14374
+ const x = a.prerelease[i];
14375
+ const y = b.prerelease[i];
14376
+ if (x === y)
14377
+ continue;
14378
+ if (x === undefined)
14379
+ return -1;
14380
+ if (y === undefined)
14381
+ return 1;
14382
+ const xn = /^\d+$/.test(x);
14383
+ const yn = /^\d+$/.test(y);
14384
+ if (xn && yn)
14385
+ return BigInt(x) > BigInt(y) ? 1 : -1;
14386
+ if (xn !== yn)
14387
+ return xn ? -1 : 1;
14388
+ return x > y ? 1 : -1;
14389
+ }
14390
+ return 0;
14391
+ }
14392
+
14201
14393
  // src/setup/opencode-config.ts
14202
14394
  function openCodePluginKey(generation) {
14203
14395
  return generation === "v2" ? "plugins" : "plugin";
@@ -14215,6 +14407,24 @@ function openCodePluginReadKeys(generation) {
14215
14407
  function pinnedPluginEntry(version) {
14216
14408
  return `${AFT_OPENCODE_PACKAGE}@${version}`;
14217
14409
  }
14410
+ function preserveDoctorPluginEntry(cli, entry, installed) {
14411
+ const configured = entry ? exactPinnedVersion(entry) : null;
14412
+ const configuredOrder = configured ? compareSemver2(configured, cli) : undefined;
14413
+ const installedOrder = installed ? compareSemver2(installed, cli) : undefined;
14414
+ if (installed && (installedOrder === undefined || installedOrder > 0))
14415
+ return true;
14416
+ if (configured && (configuredOrder === undefined || configuredOrder > 0))
14417
+ return true;
14418
+ if (entry === AFT_OPENCODE_PACKAGE)
14419
+ return false;
14420
+ return Boolean(entry && configuredOrder === undefined && installedOrder === undefined);
14421
+ }
14422
+ function doctorNoDowngradeMessage(cli, entry, installed) {
14423
+ const configured = entry ? exactPinnedVersion(entry) : null;
14424
+ const newerInstalled = installed && (compareSemver2(installed, cli) ?? 1) > 0;
14425
+ const plugin = newerInstalled ? installed : (configured ?? entry?.slice(AFT_OPENCODE_PACKAGE.length + 1)) || "unknown";
14426
+ return DOCTOR_NO_DOWNGRADE.replace("<cli>", cli).replace("<plugin>", plugin);
14427
+ }
14218
14428
  function describePluginEntry(entry) {
14219
14429
  if (entry === AFT_OPENCODE_PACKAGE) {
14220
14430
  return "has no version, so OpenCode may load any release of the plugin";
@@ -14287,8 +14497,8 @@ function findLocalAftEntry(list, hasLocalAftEntry) {
14287
14497
  }
14288
14498
  return null;
14289
14499
  }
14290
- function ensurePinnedPluginConfig(value, version, hasLocalAftEntry = () => false, generation = "unknown") {
14291
- const entry = pinnedPluginEntry(version);
14500
+ function ensurePinnedPluginConfig(value, version, hasLocalAftEntry = () => false, generation = "unknown", policy = {}) {
14501
+ const entry = policy.fallbackEntry ?? pinnedPluginEntry(version);
14292
14502
  const key = openCodePluginKey(generation);
14293
14503
  const hadKey = Array.isArray(value[key]);
14294
14504
  const list = hadKey ? value[key] : [];
@@ -14302,6 +14512,21 @@ function ensurePinnedPluginConfig(value, version, hasLocalAftEntry = () => false
14302
14512
  }
14303
14513
  if (npmIndexes.length > 0) {
14304
14514
  const firstIndex = npmIndexes[0];
14515
+ if (policy.doctor && npmIndexes.some((index) => preserveDoctorPluginEntry(version, pluginEntryPackage(list[index]) ?? undefined, policy.installedVersion))) {
14516
+ let changed = false;
14517
+ for (const index of npmIndexes) {
14518
+ const spec = pluginEntryPackage(list[index]);
14519
+ const shaped = shapePluginEntry(key, spec, pluginEntryOptions(list[index]));
14520
+ changed ||= !sameEntry(list[index], shaped);
14521
+ list[index] = shaped;
14522
+ }
14523
+ return {
14524
+ action: changed ? "updated" : "already_present",
14525
+ changed,
14526
+ entry: pluginEntryPackage(list[firstIndex]),
14527
+ key
14528
+ };
14529
+ }
14305
14530
  const shaped = shapePluginEntry(key, entry, pluginEntryOptions(list[firstIndex]));
14306
14531
  const wasExact = sameEntry(list[firstIndex], shaped);
14307
14532
  list[firstIndex] = shaped;
@@ -14320,13 +14545,24 @@ function ensurePinnedPluginConfig(value, version, hasLocalAftEntry = () => false
14320
14545
  }
14321
14546
  const sibling = value[otherOpenCodePluginKey(key)];
14322
14547
  const siblingList = Array.isArray(sibling) ? sibling : null;
14548
+ if (policy.doctor && siblingList) {
14549
+ const preserved = siblingList.find((candidate) => {
14550
+ const spec = pluginEntryPackage(candidate);
14551
+ return spec !== null && isAftNpmEntry(spec) && preserveDoctorPluginEntry(version, spec, policy.installedVersion);
14552
+ });
14553
+ const spec = pluginEntryPackage(preserved);
14554
+ if (spec) {
14555
+ list.push(shapePluginEntry(key, spec, pluginEntryOptions(preserved)));
14556
+ return { action: "added", changed: true, entry: spec, key };
14557
+ }
14558
+ }
14323
14559
  const carried = siblingList ? findLocalAftEntry(siblingList, hasLocalAftEntry) : null;
14324
14560
  list.push(carried ? shapePluginEntry(key, carried.packageSpec, carried.options) : entry);
14325
14561
  if (carried && siblingList)
14326
14562
  siblingList.splice(carried.index, 1);
14327
14563
  return { action: "added", changed: true, entry, key };
14328
14564
  }
14329
- function pluginConfigNeedsUpdate(value, version, hasLocalAftEntry = () => false, generation = "unknown") {
14565
+ function pluginConfigNeedsUpdate(value, version, hasLocalAftEntry = () => false, generation = "unknown", policy = {}) {
14330
14566
  const key = openCodePluginKey(generation);
14331
14567
  const list = value[key];
14332
14568
  if (!Array.isArray(list))
@@ -14336,7 +14572,9 @@ function pluginConfigNeedsUpdate(value, version, hasLocalAftEntry = () => false,
14336
14572
  return packageSpec !== null && isAftNpmEntry(packageSpec);
14337
14573
  });
14338
14574
  if (npmEntries.length > 0) {
14339
- return npmEntries.length !== 1 || pluginEntryPackage(npmEntries[0]) !== pinnedPluginEntry(version) || !pluginEntryFitsKey(npmEntries[0], key);
14575
+ if (policy.doctor && npmEntries.some((candidate) => preserveDoctorPluginEntry(version, pluginEntryPackage(candidate) ?? undefined, policy.installedVersion)))
14576
+ return npmEntries.some((candidate) => !pluginEntryFitsKey(candidate, key));
14577
+ return npmEntries.length !== 1 || pluginEntryPackage(npmEntries[0]) !== (policy.fallbackEntry ?? pinnedPluginEntry(version)) || !pluginEntryFitsKey(npmEntries[0], key);
14340
14578
  }
14341
14579
  const local = findLocalAftEntry(list, hasLocalAftEntry);
14342
14580
  if (local)
@@ -14386,7 +14624,7 @@ function aftHostReplacementEnabled(userConfig, hostTool) {
14386
14624
  }
14387
14625
  return true;
14388
14626
  }
14389
- var AFT_OPENCODE_PACKAGE = "@cortexkit/aft-opencode", MODERN_V1_VERSION, EXACT_VERSION, OPENCODE_HOST_SHELL_DISABLE_ENTRY = "-opencode.tool.shell", OPENCODE_HOST_PATCH_DISABLE_ENTRY = "-opencode.tool.patch", OPENCODE_HOST_TOOLS, OPENCODE_HOST_TOOL_NAMES;
14627
+ var AFT_OPENCODE_PACKAGE = "@cortexkit/aft-opencode", MODERN_V1_VERSION, DOCTOR_NO_DOWNGRADE = "This doctor is AFT <cli>; OpenCode is configured for AFT plugin <plugin>. Doctor will not downgrade that entry. Run doctor with a matching or newer CLI.", EXACT_VERSION, OPENCODE_HOST_SHELL_DISABLE_ENTRY = "-opencode.tool.shell", OPENCODE_HOST_PATCH_DISABLE_ENTRY = "-opencode.tool.patch", OPENCODE_HOST_TOOLS, OPENCODE_HOST_TOOL_NAMES;
14390
14628
  var init_opencode_config = __esm(() => {
14391
14629
  init_opencode_version();
14392
14630
  MODERN_V1_VERSION = opencode_version_default.trim();
@@ -14398,29 +14636,138 @@ var init_opencode_config = __esm(() => {
14398
14636
  OPENCODE_HOST_TOOL_NAMES = Object.keys(OPENCODE_HOST_TOOLS);
14399
14637
  });
14400
14638
 
14639
+ // src/lib/opencode-plugin-cache.ts
14640
+ import { existsSync as existsSync14, readdirSync as readdirSync6, readFileSync as readFileSync10 } from "node:fs";
14641
+ import { dirname as dirname8, join as join18 } from "node:path";
14642
+ function discoverLocalOpenCodePlugin(path, cliVersion) {
14643
+ let current = path;
14644
+ for (;; ) {
14645
+ const manifestPath = join18(current, "package.json");
14646
+ try {
14647
+ const pkg = JSON.parse(readFileSync10(manifestPath, "utf8"));
14648
+ if (pkg.name === AFT_OPENCODE_PACKAGE && typeof pkg.version === "string") {
14649
+ return {
14650
+ path: current,
14651
+ manifestPath,
14652
+ cached: pkg.version,
14653
+ version: pkg.version,
14654
+ versionSource: "installed",
14655
+ exists: true,
14656
+ cliVersion,
14657
+ local: true
14658
+ };
14659
+ }
14660
+ } catch {}
14661
+ const parent = dirname8(current);
14662
+ if (parent === current)
14663
+ return;
14664
+ current = parent;
14665
+ }
14666
+ }
14667
+ function discoverOpenCodePluginCache(root, entry, generation, cliVersion, configuredEntry) {
14668
+ const v1 = join18(root, "packages", entry);
14669
+ const v2 = join18(root, "npm", entry === AFT_OPENCODE_PACKAGE ? `${entry}@latest` : entry);
14670
+ const paths = [];
14671
+ if (generation !== "v1") {
14672
+ try {
14673
+ const timestamps = readdirSync6(v2).filter((name) => /^\d+$/.test(name));
14674
+ timestamps.sort((a, b) => BigInt(a) > BigInt(b) ? -1 : BigInt(a) < BigInt(b) ? 1 : 0);
14675
+ if (timestamps[0])
14676
+ paths.push(join18(v2, timestamps[0]));
14677
+ } catch {}
14678
+ }
14679
+ if (generation === "v1" || generation !== "v2" && paths.length === 0)
14680
+ paths.push(v1);
14681
+ let readError;
14682
+ for (const path of paths) {
14683
+ const manifestPath = join18(path, "node_modules", AFT_OPENCODE_PACKAGE, "package.json");
14684
+ try {
14685
+ const pkg = JSON.parse(readFileSync10(manifestPath, "utf8"));
14686
+ if (pkg.name !== AFT_OPENCODE_PACKAGE || typeof pkg.version !== "string" || compareSemver2(pkg.version, pkg.version) !== 0) {
14687
+ readError = `Invalid plugin identity or version at ${manifestPath}`;
14688
+ continue;
14689
+ }
14690
+ if (typeof pkg.main === "string" && !existsSync14(join18(path, "node_modules", AFT_OPENCODE_PACKAGE, pkg.main))) {
14691
+ readError = `Incomplete plugin installation at ${manifestPath}`;
14692
+ continue;
14693
+ }
14694
+ return {
14695
+ path,
14696
+ manifestPath,
14697
+ cached: pkg.version,
14698
+ version: pkg.version,
14699
+ versionSource: "installed",
14700
+ configuredEntry,
14701
+ exists: true,
14702
+ cliVersion
14703
+ };
14704
+ } catch (error) {
14705
+ if (error.code !== "ENOENT" || existsSync14(path))
14706
+ readError = String(error);
14707
+ }
14708
+ }
14709
+ return {
14710
+ path: paths[0] ?? (generation === "v2" ? v2 : v1),
14711
+ cached: undefined,
14712
+ version: configuredEntry ? exactPinnedVersion(configuredEntry) ?? undefined : undefined,
14713
+ versionSource: configuredEntry && exactPinnedVersion(configuredEntry) ? "configured" : "unknown",
14714
+ readError,
14715
+ exists: false,
14716
+ configuredEntry,
14717
+ cliVersion
14718
+ };
14719
+ }
14720
+ function doctorInstalledVersion(cache) {
14721
+ return cache.cached ?? (cache.readError ? "unknown" : undefined);
14722
+ }
14723
+ function describePluginVersion(cache) {
14724
+ const version = cache.version ?? cache.cached;
14725
+ const source = cache.versionSource ?? (cache.cached ? "installed" : "unknown");
14726
+ return `${version ?? "unknown"} (${source}${cache.manifestPath ? `: ${cache.manifestPath}` : source === "configured" && cache.configuredEntry ? `: ${cache.configuredEntry}` : ""})`;
14727
+ }
14728
+ var init_opencode_plugin_cache = __esm(() => {
14729
+ init_opencode_config();
14730
+ });
14731
+
14732
+ // src/lib/self-version.ts
14733
+ import { createRequire as createRequire2 } from "node:module";
14734
+ function getSelfVersion() {
14735
+ const require2 = createRequire2(import.meta.url);
14736
+ for (const relPath of ["../../package.json", "../package.json"]) {
14737
+ try {
14738
+ const version = require2(relPath).version;
14739
+ if (typeof version === "string" && version.length > 0) {
14740
+ return version;
14741
+ }
14742
+ } catch {}
14743
+ }
14744
+ return "unknown";
14745
+ }
14746
+ var init_self_version = () => {};
14747
+
14401
14748
  // src/setup/host-generation.ts
14402
14749
  import { createHash as createHash4 } from "node:crypto";
14403
14750
  import {
14404
14751
  closeSync as closeSync7,
14405
- existsSync as existsSync14,
14752
+ existsSync as existsSync15,
14406
14753
  mkdirSync as mkdirSync5,
14407
14754
  mkdtempSync,
14408
14755
  openSync as openSync7,
14409
- readdirSync as readdirSync6,
14410
- readFileSync as readFileSync10,
14756
+ readdirSync as readdirSync7,
14757
+ readFileSync as readFileSync11,
14411
14758
  readSync as readSync5,
14412
14759
  realpathSync as realpathSync5,
14413
14760
  rmSync as rmSync4,
14414
14761
  statSync as statSync10
14415
14762
  } from "node:fs";
14416
14763
  import { homedir as homedir14, tmpdir as tmpdir3 } from "node:os";
14417
- import { dirname as dirname8, join as join18, parse as parse2 } from "node:path";
14764
+ import { dirname as dirname9, join as join19, parse as parse3 } from "node:path";
14418
14765
  function findExecutableOnPath2(name, pathValue = process.env.PATH ?? "", platform = process.platform) {
14419
14766
  return findExecutableOnPath(name, { pathValue, platform });
14420
14767
  }
14421
14768
  function packageMetadataAt(path, expectedName) {
14422
14769
  try {
14423
- const metadata = JSON.parse(readFileSync10(path, "utf8"));
14770
+ const metadata = JSON.parse(readFileSync11(path, "utf8"));
14424
14771
  return metadata.name === expectedName ? metadata : null;
14425
14772
  } catch {
14426
14773
  return null;
@@ -14429,16 +14776,16 @@ function packageMetadataAt(path, expectedName) {
14429
14776
  function packageMetadataNearExecutable(executable, expectedName) {
14430
14777
  let current;
14431
14778
  try {
14432
- current = dirname8(realpathSync5(executable));
14779
+ current = dirname9(realpathSync5(executable));
14433
14780
  } catch {
14434
- current = dirname8(executable);
14781
+ current = dirname9(executable);
14435
14782
  }
14436
14783
  while (true) {
14437
- const direct = packageMetadataAt(join18(current, "package.json"), expectedName);
14784
+ const direct = packageMetadataAt(join19(current, "package.json"), expectedName);
14438
14785
  if (direct)
14439
14786
  return direct;
14440
- const parent = dirname8(current);
14441
- if (parent === current || current === parse2(current).root)
14787
+ const parent = dirname9(current);
14788
+ if (parent === current || current === parse3(current).root)
14442
14789
  return null;
14443
14790
  current = parent;
14444
14791
  }
@@ -14448,7 +14795,7 @@ function npmLauncherTarget(launcher) {
14448
14795
  try {
14449
14796
  if (statSync10(launcher).size > LAUNCHER_MAX_BYTES)
14450
14797
  return null;
14451
- source = readFileSync10(launcher, "utf8");
14798
+ source = readFileSync11(launcher, "utf8");
14452
14799
  } catch {
14453
14800
  return null;
14454
14801
  }
@@ -14458,7 +14805,7 @@ function npmLauncherTarget(launcher) {
14458
14805
  const relativeTarget = operands.at(-1)?.[1];
14459
14806
  if (!relativeTarget)
14460
14807
  return null;
14461
- const target = join18(dirname8(launcher), ...relativeTarget.split(/[\\/]+/).filter(Boolean));
14808
+ const target = join19(dirname9(launcher), ...relativeTarget.split(/[\\/]+/).filter(Boolean));
14462
14809
  try {
14463
14810
  return statSync10(target).isFile() ? target : null;
14464
14811
  } catch {
@@ -14504,7 +14851,7 @@ function hashFileBytes(hash, path, size) {
14504
14851
  }
14505
14852
  }
14506
14853
  function snapshotTree(path) {
14507
- if (!existsSync14(path))
14854
+ if (!existsSync15(path))
14508
14855
  return "missing";
14509
14856
  const hash = createHash4("sha256");
14510
14857
  const visit = (current, relativePath) => {
@@ -14517,8 +14864,8 @@ function snapshotTree(path) {
14517
14864
  }
14518
14865
  hash.update(`${relativePath}\x00${info.mode}\x00${info.size}\x00${info.mtimeMs}\x00`);
14519
14866
  if (info.isDirectory()) {
14520
- for (const entry of readdirSync6(current).sort()) {
14521
- visit(join18(current, entry), join18(relativePath, entry));
14867
+ for (const entry of readdirSync7(current).sort()) {
14868
+ visit(join19(current, entry), join19(relativePath, entry));
14522
14869
  }
14523
14870
  return;
14524
14871
  }
@@ -14534,19 +14881,19 @@ function snapshotTree(path) {
14534
14881
  return hash.digest("hex");
14535
14882
  }
14536
14883
  function snapshotOperatorState(operatorHome) {
14537
- const database = join18(operatorHome, ".local", "share", "opencode", "opencode.db");
14884
+ const database = join19(operatorHome, ".local", "share", "opencode", "opencode.db");
14538
14885
  return JSON.stringify({
14539
14886
  database: snapshotTree(database),
14540
14887
  databaseWal: snapshotTree(`${database}-wal`),
14541
14888
  databaseShm: snapshotTree(`${database}-shm`),
14542
- logs: snapshotTree(join18(operatorHome, ".local", "share", "opencode", "log"))
14889
+ logs: snapshotTree(join19(operatorHome, ".local", "share", "opencode", "log"))
14543
14890
  });
14544
14891
  }
14545
14892
  function probeCreatedIsolatedState(dataRoot, stateRoot) {
14546
14893
  return [dataRoot, stateRoot].some((root) => {
14547
- const opencodeRoot = join18(root, "opencode");
14548
- const database = join18(opencodeRoot, "opencode.db");
14549
- return [database, `${database}-wal`, `${database}-shm`, join18(opencodeRoot, "log")].some(existsSync14);
14894
+ const opencodeRoot = join19(root, "opencode");
14895
+ const database = join19(opencodeRoot, "opencode.db");
14896
+ return [database, `${database}-wal`, `${database}-shm`, join19(opencodeRoot, "log")].some(existsSync15);
14550
14897
  });
14551
14898
  }
14552
14899
  function probeOpenCodeV1Version(executable, options = {}) {
@@ -14555,13 +14902,13 @@ function probeOpenCodeV1Version(executable, options = {}) {
14555
14902
  return null;
14556
14903
  const operatorHome = options.operatorHome ?? homedir14();
14557
14904
  const before = snapshotOperatorState(operatorHome);
14558
- const root = mkdtempSync(join18(options.tempParent ?? tmpdir3(), "aft-opencode-probe-"));
14559
- const home = join18(root, "home");
14560
- const config = join18(root, "config");
14561
- const data = join18(root, "data");
14562
- const state = join18(root, "state");
14563
- const cache = join18(root, "cache");
14564
- const project = join18(root, "project");
14905
+ const root = mkdtempSync(join19(options.tempParent ?? tmpdir3(), "aft-opencode-probe-"));
14906
+ const home = join19(root, "home");
14907
+ const config = join19(root, "config");
14908
+ const data = join19(root, "data");
14909
+ const state = join19(root, "state");
14910
+ const cache = join19(root, "cache");
14911
+ const project = join19(root, "project");
14565
14912
  for (const directory of [home, config, data, state, cache, project]) {
14566
14913
  mkdirSync5(directory, { recursive: true });
14567
14914
  }
@@ -14572,7 +14919,7 @@ function probeOpenCodeV1Version(executable, options = {}) {
14572
14919
  XDG_DATA_HOME: data,
14573
14920
  XDG_STATE_HOME: state,
14574
14921
  XDG_CACHE_HOME: cache,
14575
- TMPDIR: join18(root, "tmp")
14922
+ TMPDIR: join19(root, "tmp")
14576
14923
  };
14577
14924
  mkdirSync5(env.TMPDIR, { recursive: true });
14578
14925
  delete env.OPENCODE_CONFIG;
@@ -14713,9 +15060,9 @@ var init_host_generation = __esm(() => {
14713
15060
  });
14714
15061
 
14715
15062
  // src/adapters/opencode.ts
14716
- import { existsSync as existsSync15, readFileSync as readFileSync11, rmSync as rmSync5, statSync as statSync11 } from "node:fs";
15063
+ import { existsSync as existsSync16, readFileSync as readFileSync12, rmSync as rmSync5, statSync as statSync11 } from "node:fs";
14717
15064
  import { homedir as homedir15 } from "node:os";
14718
- import { dirname as dirname9, join as join19, parse as parse3, resolve as resolve12 } from "node:path";
15065
+ import { dirname as dirname10, join as join20, parse as parse4, resolve as resolve12 } from "node:path";
14719
15066
  import { fileURLToPath } from "node:url";
14720
15067
  function getOpenCodeConfigDir() {
14721
15068
  const envDir = process.env.OPENCODE_CONFIG_DIR?.trim();
@@ -14726,13 +15073,13 @@ function getOpenCodeConfigDir() {
14726
15073
  function getLegacyOpenCodePluginCachePath(primaryPath) {
14727
15074
  if (process.platform !== "win32")
14728
15075
  return null;
14729
- const localAppData = process.env.LOCALAPPDATA ?? join19(homedir15(), "AppData", "Local");
14730
- const legacyPath = join19(localAppData, "opencode", "packages", LEGACY_PLUGIN_ENTRY);
15076
+ const localAppData = process.env.LOCALAPPDATA ?? join20(homedir15(), "AppData", "Local");
15077
+ const legacyPath = join20(localAppData, "opencode", "packages", LEGACY_PLUGIN_ENTRY);
14731
15078
  return resolve12(legacyPath) === resolve12(primaryPath) ? null : legacyPath;
14732
15079
  }
14733
15080
  function clearLegacyOpenCodePluginCache(primaryPath) {
14734
15081
  const legacyPath = getLegacyOpenCodePluginCachePath(primaryPath);
14735
- if (!legacyPath || !existsSync15(legacyPath))
15082
+ if (!legacyPath || !existsSync16(legacyPath))
14736
15083
  return { clearedPath: null };
14737
15084
  try {
14738
15085
  rmSync5(legacyPath, { recursive: true, force: true });
@@ -14750,16 +15097,16 @@ function hasOpenCodeCli() {
14750
15097
  function openCodeDesktopAppExists() {
14751
15098
  const candidates = [];
14752
15099
  if (process.platform === "darwin") {
14753
- candidates.push("/Applications/OpenCode.app", "/Applications/OpenCode Beta.app", join19(homedir15(), "Applications", "OpenCode.app"), join19(homedir15(), "Applications", "OpenCode Beta.app"));
15100
+ candidates.push("/Applications/OpenCode.app", "/Applications/OpenCode Beta.app", join20(homedir15(), "Applications", "OpenCode.app"), join20(homedir15(), "Applications", "OpenCode Beta.app"));
14754
15101
  } else if (process.platform === "win32") {
14755
- const localAppData = process.env.LOCALAPPDATA ?? join19(homedir15(), "AppData", "Local");
14756
- candidates.push(join19(localAppData, "Programs", "opencode"), join19(localAppData, "opencode"));
15102
+ const localAppData = process.env.LOCALAPPDATA ?? join20(homedir15(), "AppData", "Local");
15103
+ candidates.push(join20(localAppData, "Programs", "opencode"), join20(localAppData, "opencode"));
14757
15104
  } else {
14758
- candidates.push("/opt/OpenCode", "/usr/lib/opencode", join19(homedir15(), ".local", "share", "applications", "opencode.desktop"));
15105
+ candidates.push("/opt/OpenCode", "/usr/lib/opencode", join20(homedir15(), ".local", "share", "applications", "opencode.desktop"));
14759
15106
  }
14760
15107
  return candidates.some((p) => {
14761
15108
  try {
14762
- return existsSync15(p);
15109
+ return existsSync16(p);
14763
15110
  } catch {
14764
15111
  return false;
14765
15112
  }
@@ -14782,24 +15129,24 @@ function pathPointsToOurPlugin(entry) {
14782
15129
  if (!fsPath)
14783
15130
  return false;
14784
15131
  try {
14785
- if (!existsSync15(fsPath))
15132
+ if (!existsSync16(fsPath))
14786
15133
  return false;
14787
- let searchDir = statSync11(fsPath).isDirectory() ? fsPath : dirname9(fsPath);
15134
+ let searchDir = statSync11(fsPath).isDirectory() ? fsPath : dirname10(fsPath);
14788
15135
  let pkgJsonPath = null;
14789
15136
  while (true) {
14790
- const candidate = join19(searchDir, "package.json");
14791
- if (existsSync15(candidate)) {
15137
+ const candidate = join20(searchDir, "package.json");
15138
+ if (existsSync16(candidate)) {
14792
15139
  pkgJsonPath = candidate;
14793
15140
  break;
14794
15141
  }
14795
- const parent = dirname9(searchDir);
14796
- if (parent === searchDir || searchDir === parse3(searchDir).root)
15142
+ const parent = dirname10(searchDir);
15143
+ if (parent === searchDir || searchDir === parse4(searchDir).root)
14797
15144
  break;
14798
15145
  searchDir = parent;
14799
15146
  }
14800
15147
  if (!pkgJsonPath)
14801
15148
  return false;
14802
- const parsed = JSON.parse(readFileSync11(pkgJsonPath, "utf-8"));
15149
+ const parsed = JSON.parse(readFileSync12(pkgJsonPath, "utf-8"));
14803
15150
  return parsed.name === PLUGIN_NAME;
14804
15151
  } catch {
14805
15152
  return false;
@@ -14833,7 +15180,7 @@ class OpenCodeAdapter {
14833
15180
  pluginEntryWithVersion = PLUGIN_ENTRY;
14834
15181
  hostDetection;
14835
15182
  isInstalled() {
14836
- if (existsSync15(getOpenCodeConfigDir()))
15183
+ if (existsSync16(getOpenCodeConfigDir()))
14837
15184
  return true;
14838
15185
  if (openCodeDesktopAppExists())
14839
15186
  return true;
@@ -14857,7 +15204,7 @@ class OpenCodeAdapter {
14857
15204
  const configDir = getOpenCodeConfigDir();
14858
15205
  const harness = detectJsoncFile(configDir, "opencode");
14859
15206
  const aftConfigPath = resolveCortexKitUserConfigPath();
14860
- const aftConfigExists = existsSync15(aftConfigPath);
15207
+ const aftConfigExists = existsSync16(aftConfigPath);
14861
15208
  const tui = detectJsoncFile(configDir, "tui");
14862
15209
  return {
14863
15210
  configDir,
@@ -14874,13 +15221,32 @@ class OpenCodeAdapter {
14874
15221
  const { value } = readJsoncFile(paths.harnessConfig);
14875
15222
  return configRegistersAft(value, this.configGeneration());
14876
15223
  }
14877
- async ensurePluginEntry() {
15224
+ async ensurePluginEntry(options = {}) {
15225
+ const paths = this.detectConfigPaths();
15226
+ return this.ensureConfigEntry(paths.harnessConfig, paths.harnessConfigFormat, "server config", options);
15227
+ }
15228
+ needsPluginEntryUpdate(options = {}) {
14878
15229
  const paths = this.detectConfigPaths();
14879
- return this.ensureConfigEntry(paths.harnessConfig, paths.harnessConfigFormat, "server config");
15230
+ return this.configEntryNeedsUpdate(paths.harnessConfig, paths.harnessConfigFormat, options);
14880
15231
  }
14881
- needsPluginEntryUpdate() {
15232
+ doctorPluginEntry(tui = false) {
14882
15233
  const paths = this.detectConfigPaths();
14883
- return this.configEntryNeedsUpdate(paths.harnessConfig, paths.harnessConfigFormat);
15234
+ const path = tui ? paths.tuiConfig : paths.harnessConfig;
15235
+ const value = path ? readJsoncFile(path).value : null;
15236
+ const policy = this.doctorVersionPolicy({ doctor: true });
15237
+ if (!value)
15238
+ return policy.fallbackEntry ?? PLUGIN_ENTRY;
15239
+ return ensurePinnedPluginConfig(JSON.parse(JSON.stringify(value)), getSelfVersion(), pathPointsToOurPlugin, this.configGeneration(), policy).entry;
15240
+ }
15241
+ doctorVersionPolicy(policy) {
15242
+ if (!policy.doctor)
15243
+ return policy;
15244
+ const cache = this.getPluginCacheInfo();
15245
+ const installedVersion = doctorInstalledVersion(cache);
15246
+ const configuredVersion = cache.configuredEntry ? exactPinnedVersion(cache.configuredEntry) : null;
15247
+ const newer = configuredVersion && (compareSemver2(configuredVersion, getSelfVersion()) ?? 0) > 0 || cache.cached && (compareSemver2(cache.cached, getSelfVersion()) ?? 0) > 0;
15248
+ const fallbackEntry = newer ? cache.configuredEntry ?? (cache.cached ? pinnedPluginEntry(cache.cached) : undefined) : undefined;
15249
+ return { ...policy, installedVersion, fallbackEntry };
14884
15250
  }
14885
15251
  hasTuiPluginEntry() {
14886
15252
  const paths = this.detectConfigPaths();
@@ -14889,7 +15255,7 @@ class OpenCodeAdapter {
14889
15255
  const { value } = readJsoncFile(paths.tuiConfig);
14890
15256
  return configRegistersAft(value, this.configGeneration());
14891
15257
  }
14892
- async ensureTuiPluginEntry() {
15258
+ async ensureTuiPluginEntry(options = {}) {
14893
15259
  const paths = this.detectConfigPaths();
14894
15260
  if (this.configGeneration() === "v2") {
14895
15261
  return {
@@ -14907,28 +15273,30 @@ class OpenCodeAdapter {
14907
15273
  configPath: paths.configDir
14908
15274
  };
14909
15275
  }
14910
- return this.ensureConfigEntry(paths.tuiConfig, paths.tuiConfigFormat ?? "none", "TUI config");
15276
+ return this.ensureConfigEntry(paths.tuiConfig, paths.tuiConfigFormat ?? "none", "TUI config", options);
14911
15277
  }
14912
15278
  tuiPluginEntryApplies() {
14913
15279
  return this.configGeneration() !== "v2";
14914
15280
  }
14915
- needsTuiPluginEntryUpdate() {
15281
+ needsTuiPluginEntryUpdate(options = {}) {
14916
15282
  if (this.configGeneration() === "v2")
14917
15283
  return false;
14918
15284
  const paths = this.detectConfigPaths();
14919
- return paths.tuiConfig ? this.configEntryNeedsUpdate(paths.tuiConfig, paths.tuiConfigFormat ?? "none") : true;
15285
+ return paths.tuiConfig ? this.configEntryNeedsUpdate(paths.tuiConfig, paths.tuiConfigFormat ?? "none", options) : true;
14920
15286
  }
14921
- ensureConfigEntry(configPath, format, label) {
15287
+ ensureConfigEntry(configPath, format, label, policy = {}) {
14922
15288
  const generation = this.configGeneration();
14923
15289
  const key = openCodePluginKey(generation);
15290
+ policy = this.doctorVersionPolicy(policy);
15291
+ const initialEntry = policy.fallbackEntry ?? PLUGIN_ENTRY;
14924
15292
  if (format === "none") {
14925
- const failed = this.tryWrite(configPath, () => writeJsoncFile(configPath, { [key]: [PLUGIN_ENTRY] }, "json"));
15293
+ const failed = this.tryWrite(configPath, () => writeJsoncFile(configPath, { [key]: [initialEntry] }, "json"));
14926
15294
  if (failed)
14927
15295
  return failed;
14928
15296
  return {
14929
15297
  ok: true,
14930
15298
  action: "added",
14931
- message: `Created ${configPath} and added ${PLUGIN_ENTRY} under \`${key}\` (${label})`,
15299
+ message: `Created ${configPath} and added ${initialEntry} under \`${key}\` (${label})`,
14932
15300
  configPath
14933
15301
  };
14934
15302
  }
@@ -14941,12 +15309,14 @@ class OpenCodeAdapter {
14941
15309
  configPath
14942
15310
  };
14943
15311
  }
14944
- const update = ensurePinnedPluginConfig(value, getSelfVersion(), pathPointsToOurPlugin, generation);
15312
+ const update = ensurePinnedPluginConfig(value, getSelfVersion(), pathPointsToOurPlugin, generation, policy);
15313
+ const preserved = policy.doctor && preserveDoctorPluginEntry(getSelfVersion(), update.entry, doctorInstalledVersion(this.getPluginCacheInfo()));
15314
+ const notice = preserved ? doctorNoDowngradeMessage(getSelfVersion(), update.entry, doctorInstalledVersion(this.getPluginCacheInfo())) : "";
14945
15315
  if (!update.changed) {
14946
15316
  return {
14947
15317
  ok: true,
14948
15318
  action: "already_present",
14949
- message: `${PLUGIN_ENTRY} is already registered under \`${update.key}\` in ${configPath}`,
15319
+ message: notice || `${update.entry} is already registered under \`${update.key}\` in ${configPath}`,
14950
15320
  configPath
14951
15321
  };
14952
15322
  }
@@ -14956,7 +15326,7 @@ class OpenCodeAdapter {
14956
15326
  return {
14957
15327
  ok: true,
14958
15328
  action: update.action,
14959
- message: `${update.action === "added" ? "Added" : "Updated"} ${PLUGIN_ENTRY} under \`${update.key}\` in ${configPath} (${label})`,
15329
+ message: `${update.action === "added" ? "Added" : "Updated"} ${update.entry} under \`${update.key}\` in ${configPath} (${label})${notice ? `. ${notice}` : ""}`,
14960
15330
  configPath
14961
15331
  };
14962
15332
  }
@@ -15041,38 +15411,27 @@ class OpenCodeAdapter {
15041
15411
  return { ok: false, action: "error", message: formatFsError(error, configPath), configPath };
15042
15412
  }
15043
15413
  }
15044
- configEntryNeedsUpdate(configPath, format) {
15414
+ configEntryNeedsUpdate(configPath, format, policy = {}) {
15045
15415
  if (format === "none")
15046
15416
  return true;
15047
15417
  const { value, error } = readJsoncFile(configPath);
15048
15418
  if (error || !value)
15049
15419
  return false;
15050
- return pluginConfigNeedsUpdate(value, getSelfVersion(), pathPointsToOurPlugin, this.configGeneration());
15420
+ return pluginConfigNeedsUpdate(value, getSelfVersion(), pathPointsToOurPlugin, this.configGeneration(), this.doctorVersionPolicy(policy));
15051
15421
  }
15052
15422
  getPluginCacheInfo() {
15053
15423
  const configPath = this.detectConfigPaths().harnessConfig;
15054
15424
  const { value } = readJsoncFile(configPath);
15055
- const configuredEntry = openCodePluginReadKeys(this.configGeneration()).flatMap((key) => Array.isArray(value?.[key]) ? value[key] : []).map(pluginEntryPackage).find(isAftNpmEntry);
15056
- const path = join19(getOpenCodeCacheDir(), "packages", configuredEntry ?? PLUGIN_ENTRY);
15057
- let cached;
15058
- let readError;
15059
- try {
15060
- const installedPkgPath = join19(path, "node_modules", "@cortexkit", "aft-opencode", "package.json");
15061
- const pkg = JSON.parse(readFileSync11(installedPkgPath, "utf-8"));
15062
- cached = typeof pkg.version === "string" ? pkg.version : undefined;
15063
- } catch (error) {
15064
- if (error.code !== "ENOENT") {
15065
- readError = error instanceof Error ? error.message : String(error);
15066
- }
15425
+ const entries = openCodePluginReadKeys(this.configGeneration()).flatMap((key) => Array.isArray(value?.[key]) ? value[key] : []).map(pluginEntryPackage);
15426
+ const configuredEntry = entries.find(isAftNpmEntry);
15427
+ if (!configuredEntry) {
15428
+ const localEntry = entries.find((entry) => entry !== null && pathPointsToOurPlugin(entry));
15429
+ const localPath = localEntry ? pathFromEntry(localEntry) : null;
15430
+ const local = localPath ? discoverLocalOpenCodePlugin(localPath, getSelfVersion()) : undefined;
15431
+ if (local)
15432
+ return local;
15067
15433
  }
15068
- return {
15069
- path,
15070
- cached,
15071
- readError,
15072
- latest: getSelfVersion(),
15073
- exists: existsSync15(path),
15074
- configuredEntry
15075
- };
15434
+ return discoverOpenCodePluginCache(getOpenCodeCacheDir(), configuredEntry ?? PLUGIN_ENTRY, this.configGeneration(), getSelfVersion(), configuredEntry);
15076
15435
  }
15077
15436
  getStorageDir() {
15078
15437
  return getCortexKitStorageRoot();
@@ -15085,6 +15444,8 @@ class OpenCodeAdapter {
15085
15444
  }
15086
15445
  async clearPluginCache(force) {
15087
15446
  const info = this.getPluginCacheInfo();
15447
+ if (info.local)
15448
+ return { action: "not_applicable", path: info.path };
15088
15449
  const clearLegacy = force ? clearLegacyOpenCodePluginCache(info.path) : { clearedPath: null };
15089
15450
  if (clearLegacy.error) {
15090
15451
  return {
@@ -15101,12 +15462,12 @@ class OpenCodeAdapter {
15101
15462
  legacy_path_cleared: clearLegacy.clearedPath
15102
15463
  } : { action: "not_found", path: info.path };
15103
15464
  }
15104
- if (!force && info.cached && info.cached === info.latest) {
15465
+ if (!force && info.cached && info.cached === info.cliVersion) {
15105
15466
  return {
15106
15467
  action: "up_to_date",
15107
15468
  path: info.path,
15108
15469
  cached: info.cached,
15109
- latest: info.latest
15470
+ latest: info.cliVersion
15110
15471
  };
15111
15472
  }
15112
15473
  try {
@@ -15115,7 +15476,7 @@ class OpenCodeAdapter {
15115
15476
  action: "cleared",
15116
15477
  path: info.path,
15117
15478
  cached: info.cached,
15118
- latest: info.latest,
15479
+ latest: info.cliVersion,
15119
15480
  ...clearLegacy.clearedPath ? { legacy_path_cleared: clearLegacy.clearedPath } : {}
15120
15481
  };
15121
15482
  } catch (error) {
@@ -15123,7 +15484,7 @@ class OpenCodeAdapter {
15123
15484
  action: "error",
15124
15485
  path: info.path,
15125
15486
  cached: info.cached,
15126
- latest: info.latest,
15487
+ latest: info.cliVersion,
15127
15488
  error: error instanceof Error ? error.message : String(error)
15128
15489
  };
15129
15490
  }
@@ -15134,12 +15495,12 @@ class OpenCodeAdapter {
15134
15495
  describeStorageSubtrees() {
15135
15496
  const storage = this.getStorageDir();
15136
15497
  return {
15137
- index: dirSize(join19(storage, "index")),
15138
- semantic: dirSize(join19(storage, "semantic")),
15139
- backups: dirSize(join19(storage, "backups")),
15140
- url_cache: dirSize(join19(storage, "url_cache")),
15141
- onnxruntime: dirSize(join19(storage, "onnxruntime")),
15142
- logs: dirSize(join19(storage, "logs"))
15498
+ index: dirSize(join20(storage, "index")),
15499
+ semantic: dirSize(join20(storage, "semantic")),
15500
+ backups: dirSize(join20(storage, "backups")),
15501
+ url_cache: dirSize(join20(storage, "url_cache")),
15502
+ onnxruntime: dirSize(join20(storage, "onnxruntime")),
15503
+ logs: dirSize(join20(storage, "logs"))
15143
15504
  };
15144
15505
  }
15145
15506
  }
@@ -15149,6 +15510,7 @@ var init_opencode = __esm(() => {
15149
15510
  init_fs_errors();
15150
15511
  init_fs_util();
15151
15512
  init_jsonc();
15513
+ init_opencode_plugin_cache();
15152
15514
  init_paths2();
15153
15515
  init_self_version();
15154
15516
  init_host_generation();
@@ -15159,9 +15521,9 @@ var init_opencode = __esm(() => {
15159
15521
  });
15160
15522
 
15161
15523
  // src/adapters/pi.ts
15162
- import { existsSync as existsSync16, readFileSync as readFileSync12 } from "node:fs";
15524
+ import { existsSync as existsSync17, readFileSync as readFileSync13 } from "node:fs";
15163
15525
  import { homedir as homedir16 } from "node:os";
15164
- import { isAbsolute as isAbsolute7, join as join20, resolve as resolve13 } from "node:path";
15526
+ import { isAbsolute as isAbsolute7, join as join21, resolve as resolve13 } from "node:path";
15165
15527
  import { fileURLToPath as fileURLToPath2 } from "node:url";
15166
15528
  function getPiAgentDir() {
15167
15529
  const configuredDir = process.env.PI_CODING_AGENT_DIR?.trim();
@@ -15169,13 +15531,13 @@ function getPiAgentDir() {
15169
15531
  return resolve13(configuredDir);
15170
15532
  const envHome = process.platform === "win32" ? process.env.USERPROFILE : process.env.HOME;
15171
15533
  const home = envHome && envHome.length > 0 ? envHome : homedir16();
15172
- return join20(home, ".pi", "agent");
15534
+ return join21(home, ".pi", "agent");
15173
15535
  }
15174
15536
  function readPiExtensionIndex() {
15175
- const settingsPath = join20(getPiAgentDir(), "settings.json");
15176
- if (existsSync16(settingsPath)) {
15537
+ const settingsPath = join21(getPiAgentDir(), "settings.json");
15538
+ if (existsSync17(settingsPath)) {
15177
15539
  try {
15178
- const raw = readFileSync12(settingsPath, "utf-8");
15540
+ const raw = readFileSync13(settingsPath, "utf-8");
15179
15541
  const trimmed = raw.replace(/^\uFEFF/, "");
15180
15542
  const value = JSON.parse(trimmed);
15181
15543
  const packages = value.packages;
@@ -15193,13 +15555,13 @@ function readPiExtensionIndex() {
15193
15555
  } catch {}
15194
15556
  }
15195
15557
  const candidates = [
15196
- join20(getPiAgentDir(), "extensions.json"),
15197
- join20(getPiAgentDir(), "extensions.jsonc"),
15198
- join20(getPiAgentDir(), "config.json"),
15199
- join20(getPiAgentDir(), "config.jsonc")
15558
+ join21(getPiAgentDir(), "extensions.json"),
15559
+ join21(getPiAgentDir(), "extensions.jsonc"),
15560
+ join21(getPiAgentDir(), "config.json"),
15561
+ join21(getPiAgentDir(), "config.jsonc")
15200
15562
  ];
15201
15563
  for (const path of candidates) {
15202
- if (!existsSync16(path))
15564
+ if (!existsSync17(path))
15203
15565
  continue;
15204
15566
  try {
15205
15567
  const { value } = readJsoncFile(path);
@@ -15231,17 +15593,17 @@ function piEntryMatchesAft(entry) {
15231
15593
  } else if (isAbsolute7(entry)) {
15232
15594
  resolved = entry;
15233
15595
  } else if (entry.length > 0) {
15234
- resolved = join20(getPiAgentDir(), entry);
15596
+ resolved = join21(getPiAgentDir(), entry);
15235
15597
  }
15236
15598
  if (!resolved)
15237
15599
  return false;
15238
15600
  try {
15239
- if (!existsSync16(resolved))
15601
+ if (!existsSync17(resolved))
15240
15602
  return false;
15241
- const pkgPath = join20(resolved, "package.json");
15242
- if (!existsSync16(pkgPath))
15603
+ const pkgPath = join21(resolved, "package.json");
15604
+ if (!existsSync17(pkgPath))
15243
15605
  return false;
15244
- const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
15606
+ const pkg = JSON.parse(readFileSync13(pkgPath, "utf-8"));
15245
15607
  return pkg.name === PLUGIN_NAME2;
15246
15608
  } catch {
15247
15609
  return false;
@@ -15287,10 +15649,10 @@ class PiAdapter {
15287
15649
  const configDir = getPiAgentDir();
15288
15650
  const index = readPiExtensionIndex();
15289
15651
  const aftConfigPath = resolveCortexKitUserConfigPath();
15290
- const aftConfigExists = existsSync16(aftConfigPath);
15652
+ const aftConfigExists = existsSync17(aftConfigPath);
15291
15653
  return {
15292
15654
  configDir,
15293
- harnessConfig: index.path ?? join20(configDir, "extensions.json"),
15655
+ harnessConfig: index.path ?? join21(configDir, "extensions.json"),
15294
15656
  harnessConfigFormat: index.path ? "json" : "none",
15295
15657
  aftConfig: aftConfigPath,
15296
15658
  aftConfigFormat: aftConfigExists ? "jsonc" : "none"
@@ -15338,14 +15700,14 @@ class PiAdapter {
15338
15700
  }
15339
15701
  getPluginCacheInfo() {
15340
15702
  const candidates = [
15341
- join20(getPiAgentDir(), "npm", "node_modules", "@cortexkit", "aft-pi", "package.json"),
15342
- join20(getPiAgentDir(), "node_modules", "@cortexkit", "aft-pi", "package.json"),
15343
- join20(getPiAgentDir(), "extensions", "node_modules", "@cortexkit", "aft-pi", "package.json")
15703
+ join21(getPiAgentDir(), "npm", "node_modules", "@cortexkit", "aft-pi", "package.json"),
15704
+ join21(getPiAgentDir(), "node_modules", "@cortexkit", "aft-pi", "package.json"),
15705
+ join21(getPiAgentDir(), "extensions", "node_modules", "@cortexkit", "aft-pi", "package.json")
15344
15706
  ];
15345
15707
  let readError;
15346
15708
  for (const candidate of candidates) {
15347
15709
  try {
15348
- const pkg = JSON.parse(readFileSync12(candidate, "utf-8"));
15710
+ const pkg = JSON.parse(readFileSync13(candidate, "utf-8"));
15349
15711
  const cached = typeof pkg.version === "string" ? pkg.version : undefined;
15350
15712
  return {
15351
15713
  path: candidate,
@@ -15361,7 +15723,7 @@ class PiAdapter {
15361
15723
  }
15362
15724
  }
15363
15725
  return {
15364
- path: join20(getPiAgentDir(), "npm", "node_modules", "@cortexkit", "aft-pi", "package.json"),
15726
+ path: join21(getPiAgentDir(), "npm", "node_modules", "@cortexkit", "aft-pi", "package.json"),
15365
15727
  exists: readError !== undefined,
15366
15728
  readError
15367
15729
  };
@@ -15384,12 +15746,12 @@ class PiAdapter {
15384
15746
  describeStorageSubtrees() {
15385
15747
  const storage = this.getStorageDir();
15386
15748
  return {
15387
- index: dirSize(join20(storage, "index")),
15388
- semantic: dirSize(join20(storage, "semantic")),
15389
- backups: dirSize(join20(storage, "backups")),
15390
- url_cache: dirSize(join20(storage, "url_cache")),
15391
- onnxruntime: dirSize(join20(storage, "onnxruntime")),
15392
- logs: dirSize(join20(storage, "logs"))
15749
+ index: dirSize(join21(storage, "index")),
15750
+ semantic: dirSize(join21(storage, "semantic")),
15751
+ backups: dirSize(join21(storage, "backups")),
15752
+ url_cache: dirSize(join21(storage, "url_cache")),
15753
+ onnxruntime: dirSize(join21(storage, "onnxruntime")),
15754
+ logs: dirSize(join21(storage, "logs"))
15393
15755
  };
15394
15756
  }
15395
15757
  }
@@ -16811,10 +17173,10 @@ var init_version = __esm(async () => {
16811
17173
  // src/lib/cli-logger.ts
16812
17174
  import { appendFileSync, mkdirSync as mkdirSync6 } from "node:fs";
16813
17175
  import { tmpdir as tmpdir4 } from "node:os";
16814
- import { dirname as dirname10, join as join21 } from "node:path";
17176
+ import { dirname as dirname11, join as join22 } from "node:path";
16815
17177
  function cliLogPath() {
16816
17178
  if (isTestEnvironment())
16817
- return join21(tmpdir4(), "aft-cli-test.log");
17179
+ return join22(tmpdir4(), "aft-cli-test.log");
16818
17180
  return resolveAftLogPath("aft-cli.log");
16819
17181
  }
16820
17182
  function write(level, message) {
@@ -16829,7 +17191,7 @@ function write(level, message) {
16829
17191
  }
16830
17192
  try {
16831
17193
  const path = cliLogPath();
16832
- mkdirSync6(dirname10(path), { recursive: true });
17194
+ mkdirSync6(dirname11(path), { recursive: true });
16833
17195
  appendFileSync(path, `${new Date().toISOString()} ${level.toUpperCase()} ${message}
16834
17196
  `);
16835
17197
  } catch {}
@@ -16866,13 +17228,13 @@ var init_cli_logger = __esm(() => {
16866
17228
  });
16867
17229
 
16868
17230
  // src/lib/binary-install.ts
16869
- import { existsSync as existsSync17 } from "node:fs";
16870
- import { join as join22 } from "node:path";
17231
+ import { existsSync as existsSync18 } from "node:fs";
17232
+ import { join as join23 } from "node:path";
16871
17233
  function cachedBinaryFor(version) {
16872
17234
  const tag = version.startsWith("v") ? version : `v${version}`;
16873
- const path = join22(getAftBinaryCacheDir(), tag, getAftBinaryName());
17235
+ const path = join23(getAftBinaryCacheDir(), tag, getAftBinaryName());
16874
17236
  try {
16875
- return existsSync17(path) ? path : null;
17237
+ return existsSync18(path) ? path : null;
16876
17238
  } catch {
16877
17239
  return null;
16878
17240
  }
@@ -17818,8 +18180,8 @@ var init_features = __esm(() => {
17818
18180
  });
17819
18181
 
17820
18182
  // src/doctor/opencode.ts
17821
- import { existsSync as existsSync18, readFileSync as readFileSync13 } from "node:fs";
17822
- import { dirname as dirname11, join as join23 } from "node:path";
18183
+ import { existsSync as existsSync19, readFileSync as readFileSync14 } from "node:fs";
18184
+ import { dirname as dirname12, join as join24 } from "node:path";
17823
18185
  import { fileURLToPath as fileURLToPath3 } from "node:url";
17824
18186
  function isLoadPath(value) {
17825
18187
  return OPENCODE_LOAD_PATHS.includes(value);
@@ -17882,7 +18244,7 @@ function configuredVersion(entry) {
17882
18244
  }
17883
18245
  function readManifest(path) {
17884
18246
  try {
17885
- const parsed = JSON.parse(readFileSync13(path, "utf8"));
18247
+ const parsed = JSON.parse(readFileSync14(path, "utf8"));
17886
18248
  return parsed.name === AFT_OPENCODE_PACKAGE ? parsed : null;
17887
18249
  } catch {
17888
18250
  return null;
@@ -17890,13 +18252,13 @@ function readManifest(path) {
17890
18252
  }
17891
18253
  function manifestFromLocalPath(path) {
17892
18254
  let current = path;
17893
- if (!existsSync18(join23(current, "package.json")))
17894
- current = dirname11(current);
18255
+ if (!existsSync19(join24(current, "package.json")))
18256
+ current = dirname12(current);
17895
18257
  while (true) {
17896
- const manifest = readManifest(join23(current, "package.json"));
18258
+ const manifest = readManifest(join24(current, "package.json"));
17897
18259
  if (manifest)
17898
18260
  return manifest;
17899
- const parent = dirname11(current);
18261
+ const parent = dirname12(current);
17900
18262
  if (parent === current)
17901
18263
  return null;
17902
18264
  current = parent;
@@ -17906,16 +18268,16 @@ function pluginManifest(entry, cachePath) {
17906
18268
  const localRoot = localPluginRoot(entry);
17907
18269
  if (localRoot)
17908
18270
  return manifestFromLocalPath(localRoot);
17909
- return readManifest(join23(cachePath, "node_modules", "@cortexkit", "aft-opencode", "package.json"));
18271
+ return readManifest(join24(cachePath, "node_modules", "@cortexkit", "aft-opencode", "package.json"));
17910
18272
  }
17911
18273
  function pinProblem(entry, pinned) {
17912
18274
  return `the plugin entry ${entry} ${describePluginEntry(entry)}; run \`${CLI} doctor --fix\` to pin it to ${pinned}`;
17913
18275
  }
17914
18276
  function latestLoggedLoadPath(logPath) {
17915
- if (!existsSync18(logPath))
18277
+ if (!existsSync19(logPath))
17916
18278
  return null;
17917
18279
  try {
17918
- const text = readFileSync13(logPath, "utf8");
18280
+ const text = readFileSync14(logPath, "utf8");
17919
18281
  const pattern = /(?:load path|load_path)\s*[:=]\s*(root-default|export-server-effect-bun|export-server-effect-node)/gi;
17920
18282
  let latest = null;
17921
18283
  for (const match of text.matchAll(pattern)) {
@@ -17955,7 +18317,12 @@ function diagnoseOpenCodeLoad(input) {
17955
18317
  const pluginVersion = (typeof manifest?.version === "string" ? manifest.version : null) ?? input.cachedPluginVersion ?? configuredVersion(entry) ?? null;
17956
18318
  const problems = [...describePluginKeyProblems(config, generation)];
17957
18319
  let entryPinProblem = null;
17958
- if (entry && isAftNpmEntry(entry) && input.expectedPluginEntry && entry !== input.expectedPluginEntry && !(input.acceptExplicitPluginVersion && acceptV1Entry(entry))) {
18320
+ let versionNotice;
18321
+ const cli = input.expectedPluginEntry ? exactPinnedVersion(input.expectedPluginEntry) : null;
18322
+ const installed = input.cachedPluginVersion ?? (typeof manifest?.version === "string" ? manifest.version : undefined);
18323
+ if (cli && entry && isAftNpmEntry(entry) && preserveDoctorPluginEntry(cli, entry, installed)) {
18324
+ versionNotice = doctorNoDowngradeMessage(cli, entry, installed);
18325
+ } else if (entry && isAftNpmEntry(entry) && input.expectedPluginEntry && entry !== input.expectedPluginEntry && !(input.acceptExplicitPluginVersion && acceptV1Entry(entry))) {
17959
18326
  entryPinProblem = pinProblem(entry, input.expectedPluginEntry);
17960
18327
  problems.push(entryPinProblem);
17961
18328
  }
@@ -17967,7 +18334,14 @@ function diagnoseOpenCodeLoad(input) {
17967
18334
  if (expectedLoadPath && takenLoadPath && expectedLoadPath !== takenLoadPath) {
17968
18335
  problems.push(`load path ${takenLoadPath} does not match ${expectedLoadPath} expected for the detected host`);
17969
18336
  }
17970
- return { expectedLoadPath, takenLoadPath, pluginVersion, problems, pinProblem: entryPinProblem };
18337
+ return {
18338
+ expectedLoadPath,
18339
+ takenLoadPath,
18340
+ pluginVersion,
18341
+ problems,
18342
+ pinProblem: entryPinProblem,
18343
+ versionNotice
18344
+ };
17971
18345
  }
17972
18346
  function describePluginKeyProblems(config, generation) {
17973
18347
  if (!config || generation === "ambiguous" || generation === "unknown")
@@ -18167,11 +18541,11 @@ var init_aft_bridge = __esm(() => {
18167
18541
  });
18168
18542
 
18169
18543
  // src/lib/binary-cache.ts
18170
- import { existsSync as existsSync19, readdirSync as readdirSync7, statSync as statSync12 } from "node:fs";
18171
- import { join as join24 } from "node:path";
18544
+ import { existsSync as existsSync20, readdirSync as readdirSync8, statSync as statSync12 } from "node:fs";
18545
+ import { join as join25 } from "node:path";
18172
18546
  function getBinaryCacheInfo(activeVersion) {
18173
18547
  const path = getAftBinaryCacheDir();
18174
- if (!existsSync19(path)) {
18548
+ if (!existsSync20(path)) {
18175
18549
  return {
18176
18550
  versions: [],
18177
18551
  activeVersion: null,
@@ -18179,9 +18553,9 @@ function getBinaryCacheInfo(activeVersion) {
18179
18553
  path
18180
18554
  };
18181
18555
  }
18182
- const versions = readdirSync7(path).filter((entry) => {
18556
+ const versions = readdirSync8(path).filter((entry) => {
18183
18557
  try {
18184
- return statSync12(join24(path, entry)).isDirectory();
18558
+ return statSync12(join25(path, entry)).isDirectory();
18185
18559
  } catch {
18186
18560
  return false;
18187
18561
  }
@@ -18216,8 +18590,22 @@ function safeRealpath(p) {
18216
18590
  function looksLikeSecret(value) {
18217
18591
  return value.length >= 16 && /^[A-Za-z0-9_\-.~+/=]+$/.test(value) && /[0-9]/.test(value) && /[A-Z]/.test(value) && /[a-z]/.test(value);
18218
18592
  }
18593
+ function looksLikeAssignmentSecret(value) {
18594
+ return !["/", "~/", "./", "../"].some((prefix) => value.startsWith(prefix)) && !value.includes("://") && looksLikeSecret(value);
18595
+ }
18219
18596
  function isSensitiveKeyName(keyName) {
18220
- return SENSITIVE_KEY_WORD.test(keyName) || SEGMENTED_KEY_WORD.test(keyName) || CAMEL_CASE_KEY_WORD.test(keyName);
18597
+ return keyName !== "PWD" && (SENSITIVE_KEY_WORD.test(keyName) || SEGMENTED_KEY_WORD.test(keyName) || CAMEL_CASE_KEY_WORD.test(keyName) || SHORT_PASSWORD_NAME.test(keyName));
18598
+ }
18599
+ function shouldRedactAssignment(prefix, keyName, value, offset, content, commandContext) {
18600
+ if (keyName.toLowerCase() === "key" && /[?&]/.test(content[offset - 1] ?? "")) {
18601
+ return false;
18602
+ }
18603
+ return isSensitiveKeyName(keyName) || commandContext && prefix.trimEnd().endsWith("=") && looksLikeAssignmentSecret(value);
18604
+ }
18605
+ function redactAssignments(content, commandContext) {
18606
+ let sanitized = content.replace(unquotedSensitiveKeyValuePattern, (match, prefix, keyName, valueQuote, value, offset, input) => shouldRedactAssignment(prefix, keyName, value, offset, input, commandContext) ? `${prefix}${valueQuote}${SECRET_PLACEHOLDER}${valueQuote}` : match);
18607
+ sanitized = sanitized.replace(bareSensitiveKeyValuePattern, (match, prefix, keyName, value, offset, input) => shouldRedactAssignment(prefix, keyName, value, offset, input, commandContext) ? `${prefix}${SECRET_PLACEHOLDER}` : match);
18608
+ return sanitized;
18221
18609
  }
18222
18610
  function redactSecrets(content) {
18223
18611
  let sanitized = content;
@@ -18230,73 +18618,188 @@ function redactSecrets(content) {
18230
18618
  sanitized = sanitized.replace(CREDENTIAL_QUERY_PATTERN, `$1${SECRET_PLACEHOLDER}`);
18231
18619
  sanitized = sanitized.replace(KEY_QUERY_PATTERN, (match, prefix, value) => looksLikeSecret(value) ? `${prefix}${SECRET_PLACEHOLDER}` : match);
18232
18620
  sanitized = sanitized.replace(quotedSensitiveKeyValuePattern, (match, prefix, _keyQuote, keyName, valueQuote) => isSensitiveKeyName(keyName) ? `${prefix}${valueQuote}${SECRET_PLACEHOLDER}${valueQuote}` : match);
18233
- sanitized = sanitized.replace(unquotedSensitiveKeyValuePattern, (match, prefix, keyName, valueQuote) => isSensitiveKeyName(keyName) ? `${prefix}${valueQuote}${SECRET_PLACEHOLDER}${valueQuote}` : match);
18234
- sanitized = sanitized.replace(bareSensitiveKeyValuePattern, (match, prefix, keyName) => isSensitiveKeyName(keyName) ? `${prefix}${SECRET_PLACEHOLDER}` : match);
18621
+ sanitized = redactAssignments(sanitized, false);
18235
18622
  sanitized = sanitized.replace(/\b([a-z][a-z0-9+.-]*:\/\/)[^@\s/?#]+@/gi, `$1${URL_CREDENTIAL_PLACEHOLDER}@`);
18236
18623
  sanitized = sanitized.replace(/\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi, "<EMAIL>");
18624
+ sanitized = sanitized.replace(/^([^\r\n]*\bbash spawned task=[^\r\n]*? cmd=)([^\r\n]*)/gm, (_match, prefix, command) => `${prefix}${redactAssignments(command, true)}`);
18237
18625
  return sanitized;
18238
18626
  }
18239
- function sanitizeContent(content) {
18240
- const username = userInfo().username;
18241
- const home = homedir17();
18627
+ function sanitizeContent(content, context = {}) {
18628
+ const username = context.username ?? userInfo().username;
18629
+ const home = context.home ?? homedir17();
18242
18630
  let sanitized = redactSecrets(content);
18243
18631
  const cwd = process.cwd();
18244
- for (const candidate of new Set([cwd, safeRealpath(cwd)])) {
18245
- if (candidate && candidate !== "/" && candidate !== home) {
18246
- sanitized = sanitized.replace(new RegExp(escapeRegex(candidate), "g"), "<PROJECT>");
18632
+ const roots = new Set([
18633
+ cwd,
18634
+ safeRealpath(cwd),
18635
+ ...(context.projectRoots ?? []).flatMap((root) => root.startsWith("~/") ? [root, home + root.slice(1)] : [root]),
18636
+ ...reportProjectRoots(decodedView(content).text)
18637
+ ]);
18638
+ const candidates = [...roots].filter((root) => Boolean(root));
18639
+ for (const root of candidates) {
18640
+ const real = safeRealpath(root);
18641
+ if (real)
18642
+ roots.add(real);
18643
+ }
18644
+ for (const root of [...roots].filter((root) => Boolean(root)).sort((a, b) => b.length - a.length)) {
18645
+ if (/^(?:\/|[A-Za-z]:[/\\]|~\/)/.test(root) && root !== home && !/^(?:[/\\]|[A-Za-z]:[/\\])$/.test(root)) {
18646
+ sanitized = replacePath(sanitized, root, "<PROJECT>");
18647
+ }
18648
+ }
18649
+ if (home && home !== "/")
18650
+ sanitized = replacePath(sanitized, home, "~");
18651
+ sanitized = replaceDecoded(sanitized, /(?:(?<![\p{L}\p{N}_/.-])|(?<=file:\/\/)|(?<=file:\/\/\/))(?:\/(?:home|Users)\/|[A-Za-z]:\/Users\/)([^/\s"'<>`,;:?#&()[\]{}=]+)/gu, (match) => ({ offset: match[0].length - match[1].length, value: "<USER>" }));
18652
+ return redactUsername(sanitized, username);
18653
+ }
18654
+ function decodedView(content) {
18655
+ let units = Array.from({ length: content.length }, (_, i) => ({
18656
+ char: content[i],
18657
+ start: i,
18658
+ end: i + 1
18659
+ }));
18660
+ const decoded = [];
18661
+ for (let i = 0;i < units.length; ) {
18662
+ const byte = content.slice(i, i + 3);
18663
+ const first = /^%[\da-f]{2}$/i.test(byte) ? Number.parseInt(byte.slice(1), 16) : -1;
18664
+ const width = first < 128 ? 1 : first >= 194 && first <= 223 ? 2 : first <= 239 ? 3 : 4;
18665
+ const spelling = content.slice(i, i + width * 3);
18666
+ if (first >= 0 && /^(?:%[\da-f]{2})+$/i.test(spelling)) {
18667
+ try {
18668
+ const value = decodeURIComponent(spelling);
18669
+ for (const char of value.split(""))
18670
+ decoded.push({ char, start: i, end: i + spelling.length });
18671
+ i += spelling.length;
18672
+ continue;
18673
+ } catch {}
18247
18674
  }
18675
+ decoded.push(units[i++]);
18248
18676
  }
18249
- if (home) {
18250
- sanitized = sanitized.replace(new RegExp(escapeRegex(home), "g"), "~");
18677
+ units = [];
18678
+ for (let i = 0;i < decoded.length; i++) {
18679
+ const unit = decoded[i];
18680
+ const next = decoded[i + 1];
18681
+ if (unit.char === "\\" && next && (next.char === "/" || next.char === "\\")) {
18682
+ units.push({ char: "/", start: unit.start, end: next.end });
18683
+ i++;
18684
+ } else
18685
+ units.push({ ...unit, char: unit.char === "\\" ? "/" : unit.char });
18251
18686
  }
18252
- sanitized = sanitized.replace(/\/Users\/[^/\s"']+/g, "/Users/<USER>");
18253
- sanitized = sanitized.replace(/\/home\/[^/\s"']+/g, "/home/<USER>");
18254
- sanitized = sanitized.replace(/([A-Za-z]:\\\\Users\\\\)[^\\\\"'\s]+/g, "$1<USER>");
18255
- sanitized = sanitized.replace(/([A-Za-z]:\\Users\\)[^\\"'\s]+/g, "$1<USER>");
18256
- sanitized = sanitized.replace(/([A-Za-z]:\/Users\/)[^/\s"']+/g, "$1<USER>");
18257
- if (username) {
18258
- sanitized = redactUsername(sanitized, username);
18687
+ return { text: units.map((unit) => unit.char).join(""), units };
18688
+ }
18689
+ function replaceDecoded(content, pattern, replacement) {
18690
+ const view = decodedView(content);
18691
+ let result = "";
18692
+ let cursor = 0;
18693
+ for (const match of view.text.matchAll(pattern)) {
18694
+ const change = replacement(match);
18695
+ if (!change)
18696
+ continue;
18697
+ const { offset, value } = change;
18698
+ const start = view.units[match.index + offset].start;
18699
+ const end = view.units[match.index + match[0].length - 1].end;
18700
+ result += content.slice(cursor, start) + value;
18701
+ cursor = end;
18259
18702
  }
18260
- return sanitized;
18703
+ return result + content.slice(cursor);
18704
+ }
18705
+ function replacePath(content, path, value) {
18706
+ const normalized = path.replace(/\\/g, "/").replace(/\/+$/, "");
18707
+ if (!normalized)
18708
+ return content;
18709
+ const pattern = new RegExp(`(?:(?<![\\p{L}\\p{N}_/.-])|(?<=file://)|(?<=file:///))${escapeRegex(normalized)}(?=$|[/\\s"'<>\`,;:)\\]?#&])`, /^[A-Za-z]:/.test(path) ? "giu" : "gu");
18710
+ return replaceDecoded(content, pattern, () => ({ offset: 0, value }));
18711
+ }
18712
+ function reportProjectRoots(content) {
18713
+ return [
18714
+ ...content.matchAll(/(?:project root set: |watcher started: )([^\r\n]+)/g),
18715
+ ...content.matchAll(/(?:\broot=|"(?:projectRoot|project_root|canonical_root)"\s*:\s*)("[^"]+"|'[^']+'|[^\s"'<>`,;]+)/g)
18716
+ ].map((match) => match[1].trim().replace(/^["']|["']$/g, "")).filter((root) => /^(?:\/|[A-Za-z]:\/)/.test(root));
18717
+ }
18718
+ function sanitizeValue(value, context = {}) {
18719
+ const scoped = {
18720
+ ...context,
18721
+ projectRoots: [...context.projectRoots ?? [], ...projectRootsFromValue(value)]
18722
+ };
18723
+ return sanitizeNestedValue(value, scoped);
18261
18724
  }
18262
- function sanitizeValue(value) {
18725
+ function sanitizeNestedValue(value, context) {
18263
18726
  if (typeof value === "string") {
18264
- return sanitizeContent(value);
18727
+ return sanitizeContent(value, context);
18265
18728
  }
18266
18729
  if (Array.isArray(value)) {
18267
- return value.map((entry) => sanitizeValue(entry));
18730
+ return value.map((entry) => sanitizeNestedValue(entry, context));
18268
18731
  }
18269
18732
  if (value && typeof value === "object") {
18270
- return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, sanitizeValue(entry)]));
18733
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, sanitizeNestedValue(entry, context)]));
18271
18734
  }
18272
18735
  return value;
18273
18736
  }
18737
+ function projectRootsFromValue(value) {
18738
+ if (!value || typeof value !== "object")
18739
+ return [];
18740
+ if (Array.isArray(value))
18741
+ return value.flatMap(projectRootsFromValue);
18742
+ const object = value;
18743
+ const roots = [object.projectRoot, object.project_root, object.canonical_root].filter((root) => typeof root === "string");
18744
+ const index = object.index;
18745
+ if (index && Array.isArray(index.roots)) {
18746
+ for (const root of index.roots)
18747
+ if (root && typeof root.path === "string")
18748
+ roots.push(root.path);
18749
+ }
18750
+ return [...roots, ...Object.values(object).flatMap(projectRootsFromValue)];
18751
+ }
18274
18752
  function redactUsername(content, username) {
18275
18753
  if (!username)
18276
18754
  return content;
18277
- return content.replace(new RegExp(`(?<![\\p{L}\\p{N}_])${escapeRegex(username)}(?![\\p{L}\\p{N}_])`, "gu"), "<USER>");
18755
+ if (!COMMON_ACCOUNT_NAMES.has(username.toLowerCase())) {
18756
+ return replaceDecoded(content, new RegExp(`(?<![\\p{L}\\p{N}_])${escapeRegex(username)}(?![\\p{L}\\p{N}_])`, "gu"), () => ({ offset: 0, value: "<USER>" }));
18757
+ }
18758
+ return replaceDecoded(content, new RegExp(`(?<![\\p{L}\\p{N}_/.-])~${escapeRegex(username)}(?![\\p{L}\\p{N}_])|(?<![\\p{L}\\p{N}_/.-])${escapeRegex(username)}(?=@[\\p{L}\\p{N}])`, "gu"), (match) => ({ offset: match[0].startsWith("~") ? 1 : 0, value: "<USER>" }));
18278
18759
  }
18279
- var SECRET_PLACEHOLDER = "<REDACTED_SECRET>", URL_CREDENTIAL_PLACEHOLDER = "***", KEY_NAME = "[A-Za-z_][A-Za-z0-9_.-]*", SENSITIVE_KEY_WORD, SEGMENTED_KEY_WORD, CAMEL_CASE_KEY_WORD, JWT_PATTERN, AWS_ACCESS_KEY_ID_PATTERN, CREDENTIAL_QUERY_PATTERN, KEY_QUERY_PATTERN, quotedSensitiveKeyValuePattern, unquotedSensitiveKeyValuePattern, bareSensitiveKeyValuePattern;
18760
+ var SECRET_PLACEHOLDER = "<REDACTED_SECRET>", URL_CREDENTIAL_PLACEHOLDER = "***", KEY_NAME = "[A-Za-z_][A-Za-z0-9_.-]*", SENSITIVE_KEY_WORD, SEGMENTED_KEY_WORD, CAMEL_CASE_KEY_WORD, SHORT_PASSWORD_NAME, JWT_PATTERN, AWS_ACCESS_KEY_ID_PATTERN, CREDENTIAL_QUERY_PATTERN, KEY_QUERY_PATTERN, quotedSensitiveKeyValuePattern, unquotedSensitiveKeyValuePattern, bareSensitiveKeyValuePattern, COMMON_ACCOUNT_NAMES;
18280
18761
  var init_sanitize = __esm(() => {
18281
18762
  SENSITIVE_KEY_WORD = /(?:token|password|secret|api[_-]?key|passwd|pwd|credential)/i;
18282
18763
  SEGMENTED_KEY_WORD = /(?:^|[_.-])key(?:$|[_.-])/i;
18283
18764
  CAMEL_CASE_KEY_WORD = /[a-z0-9]Key(?:$|[A-Z_.-])/;
18765
+ SHORT_PASSWORD_NAME = /(?:^|_)(?:pw|pass)$/i;
18284
18766
  JWT_PATTERN = /\beyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g;
18285
18767
  AWS_ACCESS_KEY_ID_PATTERN = /\b(?:AKIA|ASIA|AGPA|AIDA|AROA)[A-Z0-9]{16}\b/g;
18286
18768
  CREDENTIAL_QUERY_PATTERN = /([?&](?:[a-z0-9_-]*token|api[_-]?key|apikey|[a-z0-9_-]*secret|password|passwd|pwd)=)[^&\s#"'<>]+/gi;
18287
18769
  KEY_QUERY_PATTERN = /([?&]key=)([^&\s#"'<>]+)/gi;
18288
18770
  quotedSensitiveKeyValuePattern = new RegExp(String.raw`((['"])(${KEY_NAME})\2[^\S\r\n]*:[^\S\r\n]*)(['"])([^'"\r\n]+)\4`, "gi");
18289
18771
  unquotedSensitiveKeyValuePattern = new RegExp(String.raw`\b((${KEY_NAME})[^\S\r\n]*[=:][^\S\r\n]*)(['"])([^'"\r\n]+)\3`, "gi");
18290
- bareSensitiveKeyValuePattern = new RegExp(String.raw`\b((${KEY_NAME})[^\S\r\n]*[=:][^\S\r\n]*)([^\s,;&'"]+)`, "gi");
18772
+ bareSensitiveKeyValuePattern = new RegExp(String.raw`\b((${KEY_NAME})[^\S\r\n]*[=:][^\S\r\n]*)([^\s,;&|<>()'"]+)`, "gi");
18773
+ COMMON_ACCOUNT_NAMES = new Set([
18774
+ "root",
18775
+ "admin",
18776
+ "administrator",
18777
+ "user",
18778
+ "dev",
18779
+ "developer",
18780
+ "test",
18781
+ "guest",
18782
+ "ubuntu",
18783
+ "debian",
18784
+ "pi",
18785
+ "runner",
18786
+ "vagrant",
18787
+ "ec2-user",
18788
+ "centos",
18789
+ "git",
18790
+ "docker",
18791
+ "build",
18792
+ "ci"
18793
+ ]);
18291
18794
  });
18292
18795
 
18293
18796
  // src/lib/bridge-tool-failures.ts
18294
- import { closeSync as closeSync8, existsSync as existsSync20, openSync as openSync8, readSync as readSync6, statSync as statSync13 } from "node:fs";
18797
+ import { closeSync as closeSync8, existsSync as existsSync21, openSync as openSync8, readSync as readSync6, statSync as statSync13 } from "node:fs";
18295
18798
  function resolveBridgePluginLogPath() {
18296
18799
  return resolvePluginLogPath();
18297
18800
  }
18298
18801
  function tailLogFileBytes(path, maxBytes) {
18299
- if (!existsSync20(path) || maxBytes <= 0)
18802
+ if (!existsSync21(path) || maxBytes <= 0)
18300
18803
  return "";
18301
18804
  let fd = null;
18302
18805
  try {
@@ -18432,9 +18935,9 @@ var init_bridge_tool_failures = __esm(() => {
18432
18935
  });
18433
18936
 
18434
18937
  // src/lib/build-breaker.ts
18435
- import { existsSync as existsSync21, readdirSync as readdirSync8 } from "node:fs";
18938
+ import { existsSync as existsSync22, readdirSync as readdirSync9 } from "node:fs";
18436
18939
  import { createRequire as createRequire3 } from "node:module";
18437
- import { join as join25 } from "node:path";
18940
+ import { join as join26 } from "node:path";
18438
18941
  function loadDatabaseSync() {
18439
18942
  const require2 = createRequire3(import.meta.url);
18440
18943
  try {
@@ -18448,11 +18951,11 @@ function loadDatabaseSync() {
18448
18951
  }
18449
18952
  }
18450
18953
  function buildBreakerDatabases(storageRoot) {
18451
- const callgraphRoot = join25(storageRoot, "callgraph");
18452
- if (!existsSync21(callgraphRoot))
18954
+ const callgraphRoot = join26(storageRoot, "callgraph");
18955
+ if (!existsSync22(callgraphRoot))
18453
18956
  return [];
18454
18957
  try {
18455
- return readdirSync8(callgraphRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join25(callgraphRoot, entry.name, "build-breaker.sqlite")).filter((path) => existsSync21(path));
18958
+ return readdirSync9(callgraphRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join26(callgraphRoot, entry.name, "build-breaker.sqlite")).filter((path) => existsSync22(path));
18456
18959
  } catch {
18457
18960
  return [];
18458
18961
  }
@@ -18528,15 +19031,15 @@ var init_build_breaker = __esm(() => {
18528
19031
  });
18529
19032
 
18530
19033
  // src/lib/config-migration.ts
18531
- import { existsSync as existsSync22, readFileSync as readFileSync14 } from "node:fs";
19034
+ import { existsSync as existsSync23, readFileSync as readFileSync15 } from "node:fs";
18532
19035
  function isRecord2(value) {
18533
19036
  return typeof value === "object" && value !== null && !Array.isArray(value);
18534
19037
  }
18535
19038
  function readPlainConfig(path) {
18536
- if (!existsSync22(path))
19039
+ if (!existsSync23(path))
18537
19040
  return null;
18538
19041
  try {
18539
- const plain = JSON.parse(JSON.stringify(import_comment_json2.parse(readFileSync14(path, "utf8"))));
19042
+ const plain = JSON.parse(JSON.stringify(import_comment_json2.parse(readFileSync15(path, "utf8"))));
18540
19043
  return isRecord2(plain) ? plain : null;
18541
19044
  } catch {
18542
19045
  return null;
@@ -18544,10 +19047,10 @@ function readPlainConfig(path) {
18544
19047
  }
18545
19048
  function configMigrationTargets(userConfigPath, cwd = process.cwd()) {
18546
19049
  const targets = [];
18547
- if (existsSync22(userConfigPath))
19050
+ if (existsSync23(userConfigPath))
18548
19051
  targets.push({ path: userConfigPath, tier: "user" });
18549
19052
  const project = resolveCortexKitProjectConfigPath(cwd);
18550
- if (project !== userConfigPath && existsSync22(project)) {
19053
+ if (project !== userConfigPath && existsSync23(project)) {
18551
19054
  targets.push({ path: project, tier: "project" });
18552
19055
  }
18553
19056
  return targets;
@@ -18591,6 +19094,7 @@ function previewConfigMigration(targets) {
18591
19094
  const translation = translateConfigDocument(translated, target.tier);
18592
19095
  if (!translation.legacyInput)
18593
19096
  continue;
19097
+ dropRemovedDataLayerKeys(translated);
18594
19098
  const changes = diffConfig(raw, translated);
18595
19099
  previews.push({ ...target, changes });
18596
19100
  }
@@ -18612,21 +19116,27 @@ var init_config_migration = __esm(() => {
18612
19116
  import_comment_json2 = __toESM(require_src2(), 1);
18613
19117
  });
18614
19118
 
19119
+ // src/lib/doctor-labels.ts
19120
+ function describeCliBinary(version, path) {
19121
+ return CLI_BINARY_LABEL.replace("<version>", version ?? "unknown").replace("<path>", path ?? "unknown");
19122
+ }
19123
+ var CLI_BINARY_LABEL = "AFT binary this CLI found: <version> at <path> (the plugin may run a different one)", CLI_VERSION_CACHED_LABEL = "Cached binary for this CLI";
19124
+
18615
19125
  // src/lib/legacy-storage.ts
18616
- import { existsSync as existsSync23, readdirSync as readdirSync9, statSync as statSync14 } from "node:fs";
18617
- import { join as join26 } from "node:path";
19126
+ import { existsSync as existsSync24, readdirSync as readdirSync10, statSync as statSync14 } from "node:fs";
19127
+ import { join as join27 } from "node:path";
18618
19128
  function summarizeLegacyPartitionDuplication(storageRoot) {
18619
- if (!existsSync23(storageRoot)) {
19129
+ if (!existsSync24(storageRoot)) {
18620
19130
  return { totalPartitions: 0, totalBytes: 0, byHarness: [] };
18621
19131
  }
18622
19132
  const byHarness = [];
18623
19133
  for (const harness of safeReadDir(storageRoot)) {
18624
- const harnessPath = join26(storageRoot, harness);
19134
+ const harnessPath = join27(storageRoot, harness);
18625
19135
  if (!isDirectory(harnessPath))
18626
19136
  continue;
18627
19137
  const partitions = new Map;
18628
- collectCallgraphPartitions(join26(harnessPath, "callgraph"), partitions);
18629
- collectInspectPartitions(join26(harnessPath, "inspect"), partitions);
19138
+ collectCallgraphPartitions(join27(harnessPath, "callgraph"), partitions);
19139
+ collectInspectPartitions(join27(harnessPath, "inspect"), partitions);
18630
19140
  if (partitions.size === 0)
18631
19141
  continue;
18632
19142
  let bytes = 0;
@@ -18645,7 +19155,7 @@ function collectCallgraphPartitions(domainPath, partitions) {
18645
19155
  if (!isDirectory(domainPath))
18646
19156
  return;
18647
19157
  for (const name of safeReadDir(domainPath)) {
18648
- const path = join26(domainPath, name);
19158
+ const path = join27(domainPath, name);
18649
19159
  if (isDirectory(path)) {
18650
19160
  if (!looksLikePartitionKey(name))
18651
19161
  continue;
@@ -18662,7 +19172,7 @@ function collectInspectPartitions(domainPath, partitions) {
18662
19172
  if (!isDirectory(domainPath))
18663
19173
  return;
18664
19174
  for (const name of safeReadDir(domainPath)) {
18665
- const path = join26(domainPath, name);
19175
+ const path = join27(domainPath, name);
18666
19176
  if (isDirectory(path)) {
18667
19177
  if (!looksLikePartitionKey(name))
18668
19178
  continue;
@@ -18711,7 +19221,7 @@ function looksLikePartitionKey(value) {
18711
19221
  }
18712
19222
  function safeReadDir(path) {
18713
19223
  try {
18714
- return readdirSync9(path).sort((left, right) => left.localeCompare(right));
19224
+ return readdirSync10(path).sort((left, right) => left.localeCompare(right));
18715
19225
  } catch {
18716
19226
  return [];
18717
19227
  }
@@ -18737,22 +19247,22 @@ var init_legacy_storage = __esm(() => {
18737
19247
  });
18738
19248
 
18739
19249
  // src/lib/lsp-cache.ts
18740
- import { existsSync as existsSync24, readdirSync as readdirSync10, rmSync as rmSync6, statSync as statSync15 } from "node:fs";
18741
- import { join as join27 } from "node:path";
19250
+ import { existsSync as existsSync25, readdirSync as readdirSync11, rmSync as rmSync6, statSync as statSync15 } from "node:fs";
19251
+ import { join as join28 } from "node:path";
18742
19252
  function inspectDir(path) {
18743
- if (!existsSync24(path)) {
19253
+ if (!existsSync25(path)) {
18744
19254
  return { entries: [], totalSize: 0 };
18745
19255
  }
18746
19256
  const entries = [];
18747
19257
  let totalSize = 0;
18748
19258
  let names;
18749
19259
  try {
18750
- names = readdirSync10(path);
19260
+ names = readdirSync11(path);
18751
19261
  } catch {
18752
19262
  return { entries: [], totalSize: 0 };
18753
19263
  }
18754
19264
  for (const name of names) {
18755
- const full = join27(path, name);
19265
+ const full = join28(path, name);
18756
19266
  try {
18757
19267
  if (!statSync15(full).isDirectory())
18758
19268
  continue;
@@ -18814,8 +19324,8 @@ var init_lsp_cache = __esm(() => {
18814
19324
  });
18815
19325
 
18816
19326
  // src/lib/onnx.ts
18817
- import { existsSync as existsSync25, readdirSync as readdirSync11, readlinkSync as readlinkSync2, realpathSync as realpathSync7 } from "node:fs";
18818
- import { basename as basename2, isAbsolute as isAbsolute8, join as join28, resolve as resolve14, win32 as win323 } from "node:path";
19327
+ import { existsSync as existsSync26, readdirSync as readdirSync12, readlinkSync as readlinkSync2, realpathSync as realpathSync7 } from "node:fs";
19328
+ import { basename as basename2, isAbsolute as isAbsolute8, join as join29, resolve as resolve14, win32 as win323 } from "node:path";
18819
19329
  function getOnnxLibraryName() {
18820
19330
  if (process.platform === "darwin")
18821
19331
  return "libonnxruntime.dylib";
@@ -18867,7 +19377,7 @@ function isWindowsSystem32Directory2(dir) {
18867
19377
  }
18868
19378
  function directoryContainsLibrary2(dir, libName) {
18869
19379
  try {
18870
- const entries = readdirSync11(dir);
19380
+ const entries = readdirSync12(dir);
18871
19381
  if (process.platform === "win32") {
18872
19382
  const expected = libName.toLowerCase();
18873
19383
  return entries.some((entry) => entry.toLowerCase() === expected);
@@ -18885,7 +19395,7 @@ function findIgnoredWindowsSystemOnnxRuntime() {
18885
19395
  for (const root of windowsRoots) {
18886
19396
  if (!root)
18887
19397
  continue;
18888
- const systemDir = join28(root, "System32");
19398
+ const systemDir = join29(root, "System32");
18889
19399
  const key = win323.resolve(systemDir).toLowerCase();
18890
19400
  if (seen.has(key))
18891
19401
  continue;
@@ -18907,21 +19417,21 @@ function inspectSystemOnnxRuntime(searchPathsOverride) {
18907
19417
  searchPaths.push(...pathEntriesForPlatform2());
18908
19418
  const programFiles = process.env.ProgramFiles ?? "C:\\Program Files";
18909
19419
  const programFilesX86 = process.env["ProgramFiles(x86)"] ?? "C:\\Program Files (x86)";
18910
- searchPaths.push(join28(programFiles, "onnxruntime", "lib"), join28(programFiles, "Microsoft ONNX Runtime", "lib"), join28(programFiles, "Microsoft Machine Learning", "lib"), join28(programFilesX86, "onnxruntime", "lib"), ...(() => {
19420
+ searchPaths.push(join29(programFiles, "onnxruntime", "lib"), join29(programFiles, "Microsoft ONNX Runtime", "lib"), join29(programFiles, "Microsoft Machine Learning", "lib"), join29(programFilesX86, "onnxruntime", "lib"), ...(() => {
18911
19421
  const nugetPaths = [];
18912
19422
  const userProfile = process.env.USERPROFILE ?? "";
18913
19423
  if (!userProfile)
18914
19424
  return nugetPaths;
18915
- const nugetPackageDir = join28(userProfile, ".nuget", "packages", "microsoft.ml.onnxruntime");
18916
- if (!existsSync25(nugetPackageDir))
19425
+ const nugetPackageDir = join29(userProfile, ".nuget", "packages", "microsoft.ml.onnxruntime");
19426
+ if (!existsSync26(nugetPackageDir))
18917
19427
  return nugetPaths;
18918
19428
  try {
18919
- for (const entry of readdirSync11(nugetPackageDir, { withFileTypes: true })) {
19429
+ for (const entry of readdirSync12(nugetPackageDir, { withFileTypes: true })) {
18920
19430
  if (!entry.isDirectory())
18921
19431
  continue;
18922
19432
  if (entry.name === "__globalPackagesFolder" || entry.name.startsWith("."))
18923
19433
  continue;
18924
- nugetPaths.push(join28(nugetPackageDir, entry.name, "runtimes", "win-x64", "native"), join28(nugetPackageDir, entry.name, "runtimes", "win-arm64", "native"));
19434
+ nugetPaths.push(join29(nugetPackageDir, entry.name, "runtimes", "win-x64", "native"), join29(nugetPackageDir, entry.name, "runtimes", "win-arm64", "native"));
18925
19435
  }
18926
19436
  } catch {}
18927
19437
  return nugetPaths;
@@ -18940,7 +19450,7 @@ function inspectSystemOnnxRuntime(searchPathsOverride) {
18940
19450
  if (!directoryContainsLibrary2(dir, libName))
18941
19451
  continue;
18942
19452
  if (process.platform !== "win32") {
18943
- const probe = probeOnnxRuntimeLoadable(join28(dir, libName));
19453
+ const probe = probeOnnxRuntimeLoadable(join29(dir, libName));
18944
19454
  if (!probe.loadable) {
18945
19455
  ignored ??= { path: dir, reason: `unloadable: ${probe.reason} — ignored` };
18946
19456
  continue;
@@ -18960,12 +19470,12 @@ function inspectSystemOnnxRuntime(searchPathsOverride) {
18960
19470
  return { path: unknownVersionPaths[0] ?? null, ignored };
18961
19471
  }
18962
19472
  function findCachedOnnxRuntime2(storageDir) {
18963
- const ortDir = join28(storageDir, "onnxruntime", ONNX_RUNTIME_VERSION);
19473
+ const ortDir = join29(storageDir, "onnxruntime", ONNX_RUNTIME_VERSION);
18964
19474
  const libName = getOnnxLibraryName();
18965
- if (existsSync25(join28(ortDir, libName)))
19475
+ if (existsSync26(join29(ortDir, libName)))
18966
19476
  return ortDir;
18967
- const libSubdir = join28(ortDir, "lib");
18968
- if (existsSync25(join28(libSubdir, libName)))
19477
+ const libSubdir = join29(ortDir, "lib");
19478
+ if (existsSync26(join29(libSubdir, libName)))
18969
19479
  return libSubdir;
18970
19480
  return null;
18971
19481
  }
@@ -18986,16 +19496,16 @@ function parseOrtVersionFromDirectoryPath(value) {
18986
19496
  return null;
18987
19497
  }
18988
19498
  function detectOrtVersion(libDir) {
18989
- if (!existsSync25(libDir))
19499
+ if (!existsSync26(libDir))
18990
19500
  return null;
18991
19501
  const libName = getOnnxLibraryName();
18992
19502
  try {
18993
- const resolved = parseOrtVersionFromPath(realpathSync7(join28(libDir, libName)));
19503
+ const resolved = parseOrtVersionFromPath(realpathSync7(join29(libDir, libName)));
18994
19504
  if (resolved && resolved !== INVALID_ORT_VERSION2)
18995
19505
  return resolved;
18996
19506
  } catch {}
18997
19507
  try {
18998
- const entries = readdirSync11(libDir);
19508
+ const entries = readdirSync12(libDir);
18999
19509
  const barePrefix = libName.replace(/\.(so|dylib|dll)$/, "");
19000
19510
  const expectedPrefix = process.platform === "win32" ? barePrefix.toLowerCase() : barePrefix;
19001
19511
  for (const entry of entries) {
@@ -19006,8 +19516,8 @@ function detectOrtVersion(libDir) {
19006
19516
  if (version)
19007
19517
  return version;
19008
19518
  }
19009
- const base = join28(libDir, libName);
19010
- if (existsSync25(base)) {
19519
+ const base = join29(libDir, libName);
19520
+ if (existsSync26(base)) {
19011
19521
  try {
19012
19522
  const real = realpathSync7(base);
19013
19523
  const version = parseOrtVersionFromPath(real) ?? parseOrtVersionFromDirectoryPath(real);
@@ -19040,17 +19550,17 @@ var init_onnx = __esm(() => {
19040
19550
  });
19041
19551
 
19042
19552
  // src/lib/plugin-load.ts
19043
- import { existsSync as existsSync26, readFileSync as readFileSync15 } from "node:fs";
19553
+ import { existsSync as existsSync27, readFileSync as readFileSync16 } from "node:fs";
19044
19554
  import { homedir as homedir18 } from "node:os";
19045
- import { isAbsolute as isAbsolute9, join as join29, normalize } from "node:path";
19555
+ import { isAbsolute as isAbsolute9, join as join30, normalize } from "node:path";
19046
19556
  function isRecord3(value) {
19047
19557
  return typeof value === "object" && value !== null && !Array.isArray(value);
19048
19558
  }
19049
19559
  function readConfig(path) {
19050
- if (!existsSync26(path))
19560
+ if (!existsSync27(path))
19051
19561
  return { value: null, error: null };
19052
19562
  try {
19053
- const parsed = import_comment_json3.parse(readFileSync15(path, "utf8"));
19563
+ const parsed = import_comment_json3.parse(readFileSync16(path, "utf8"));
19054
19564
  const plain = JSON.parse(JSON.stringify(parsed));
19055
19565
  return isRecord3(plain) ? { value: plain, error: null } : { value: null, error: "the file is not a JSON object" };
19056
19566
  } catch (error) {
@@ -19106,7 +19616,7 @@ function evaluatePluginLoad(input) {
19106
19616
  const raw = typeof subc?.connection_file === "string" ? subc.connection_file.trim() : "";
19107
19617
  if (raw.length > 0) {
19108
19618
  const resolved = resolveSubcConnectionPath(raw, input.home);
19109
- if (!existsSync26(resolved)) {
19619
+ if (!existsSync27(resolved)) {
19110
19620
  const toolCallError = formatConfigErrorMessage(formatSubcConnectionMissingMessage(raw, resolved));
19111
19621
  blockers.push({
19112
19622
  code: "subc_connection_missing",
@@ -19125,7 +19635,7 @@ function evaluatePluginLoad(input) {
19125
19635
  const storage = loaded.user && isRecord3(loaded.user.storage) ? loaded.user.storage : null;
19126
19636
  const requested = typeof storage?.cache_dir === "string" ? storage.cache_dir : "";
19127
19637
  const home = input.home ?? homedir18();
19128
- const expanded = requested === "~" ? home : requested.startsWith("~/") || requested.startsWith("~\\") ? join29(home, requested.slice(2)) : requested;
19638
+ const expanded = requested === "~" ? home : requested.startsWith("~/") || requested.startsWith("~\\") ? join30(home, requested.slice(2)) : requested;
19129
19639
  return {
19130
19640
  ...isAbsolute9(expanded) ? { cacheDir: normalize(expanded) } : {},
19131
19641
  blockers,
@@ -19145,15 +19655,16 @@ import {
19145
19655
  accessSync as accessSync3,
19146
19656
  closeSync as closeSync9,
19147
19657
  constants as constants5,
19148
- existsSync as existsSync27,
19658
+ existsSync as existsSync28,
19149
19659
  openSync as openSync9,
19150
19660
  readSync as readSync7,
19151
19661
  statSync as statSync16
19152
19662
  } from "node:fs";
19153
- import { join as join30 } from "node:path";
19663
+ import { join as join31 } from "node:path";
19154
19664
  async function collectDiagnostics(adapters) {
19155
19665
  const cliVersion = getSelfVersion();
19156
- const binaryVersion = probeBinaryVersion(cliVersion);
19666
+ const binaryProbe = probeAftBinary(cliVersion);
19667
+ const binaryVersion = binaryProbe.version;
19157
19668
  const harnesses = [];
19158
19669
  for (const adapter of adapters) {
19159
19670
  harnesses.push(await diagnoseHarness(adapter));
@@ -19165,6 +19676,7 @@ async function collectDiagnostics(adapters) {
19165
19676
  nodeVersion: process.version,
19166
19677
  cliVersion,
19167
19678
  binaryVersion,
19679
+ binaryPath: binaryProbe.path,
19168
19680
  harnesses,
19169
19681
  binaryCache: getBinaryCacheInfo(cliVersion),
19170
19682
  lspCache: getLspCacheReport(),
@@ -19185,7 +19697,7 @@ async function diagnoseHarness(adapter) {
19185
19697
  const logPath = adapter.getLogFile();
19186
19698
  const pluginCache = adapter.getPluginCacheInfo();
19187
19699
  const storageAccessible = (() => {
19188
- if (!existsSync27(storage))
19700
+ if (!existsSync28(storage))
19189
19701
  return false;
19190
19702
  try {
19191
19703
  accessSync3(storage, constants5.R_OK | constants5.W_OK);
@@ -19220,7 +19732,7 @@ async function diagnoseHarness(adapter) {
19220
19732
  pluginRegistered: adapter.hasPluginEntry(),
19221
19733
  configPaths,
19222
19734
  aftConfig: {
19223
- exists: existsSync27(configPaths.aftConfig),
19735
+ exists: existsSync28(configPaths.aftConfig),
19224
19736
  ...aftConfigRead.error ? { parseError: aftConfigRead.error } : {},
19225
19737
  enabled: aftEnabled,
19226
19738
  ...aftEnabledSource ? { enabledSource: aftEnabledSource } : {},
@@ -19229,10 +19741,10 @@ async function diagnoseHarness(adapter) {
19229
19741
  pluginCache,
19230
19742
  storageDir: {
19231
19743
  path: storage,
19232
- exists: existsSync27(storage),
19744
+ exists: existsSync28(storage),
19233
19745
  accessible: storageAccessible,
19234
19746
  sizesByKey: describeStorage,
19235
- ...pluginLoad.cacheDir ? { cacheRoot: join30(pluginLoad.cacheDir, "aft") } : {},
19747
+ ...pluginLoad.cacheDir ? { cacheRoot: join31(pluginLoad.cacheDir, "aft") } : {},
19236
19748
  ...legacyDuplication.totalPartitions > 0 ? { legacyDuplication } : {}
19237
19749
  },
19238
19750
  onnxRuntime: {
@@ -19252,8 +19764,8 @@ async function diagnoseHarness(adapter) {
19252
19764
  },
19253
19765
  logFile: {
19254
19766
  path: logPath,
19255
- exists: existsSync27(logPath),
19256
- sizeKb: existsSync27(logPath) ? Math.round(statSync16(logPath).size / 1024) : 0
19767
+ exists: existsSync28(logPath),
19768
+ sizeKb: existsSync28(logPath) ? Math.round(statSync16(logPath).size / 1024) : 0
19257
19769
  },
19258
19770
  pluginLoad
19259
19771
  };
@@ -19262,7 +19774,7 @@ function renderDiagnosticsMarkdown(report) {
19262
19774
  const lines = [];
19263
19775
  lines.push(`- Timestamp: ${report.timestamp}`);
19264
19776
  lines.push(`- AFT CLI: v${report.cliVersion}`);
19265
- lines.push(`- AFT binary: ${report.binaryVersion ?? "unknown"}`);
19777
+ lines.push(`- ${describeCliBinary(report.binaryVersion, report.binaryPath)}`);
19266
19778
  lines.push(`- OS: ${report.platform} ${report.arch}`);
19267
19779
  lines.push(`- Node: ${report.nodeVersion}`);
19268
19780
  const issues = collectDiagnosticIssues(report);
@@ -19279,7 +19791,7 @@ function renderDiagnosticsMarkdown(report) {
19279
19791
  lines.push(`- Host installed: ${h.hostInstalled}`);
19280
19792
  lines.push(`- Host version: ${h.hostVersion ?? "unknown"}`);
19281
19793
  lines.push(`- Plugin registered: ${h.pluginRegistered}`);
19282
- lines.push(`- Plugin version: ${h.pluginCache.cached ?? "not installed"}`);
19794
+ lines.push(`- Plugin version: ${describePluginVersion(h.pluginCache)}`);
19283
19795
  lines.push(`- AFT enabled: ${h.aftConfig.enabled}${h.aftConfig.enabledSource ? ` (from ${h.aftConfig.enabledSource})` : ""}`);
19284
19796
  lines.push(`- AFT config parse error: ${h.aftConfig.parseError ?? "none"}`);
19285
19797
  lines.push("");
@@ -19314,7 +19826,8 @@ function renderDiagnosticsMarkdown(report) {
19314
19826
  lines.push("");
19315
19827
  lines.push("### Binary cache");
19316
19828
  lines.push("```json");
19317
- lines.push(JSON.stringify(report.binaryCache, null, 2));
19829
+ const { activeVersion, ...cacheInventory } = report.binaryCache;
19830
+ lines.push(JSON.stringify({ ...cacheInventory, [CLI_VERSION_CACHED_LABEL]: activeVersion }, null, 2));
19318
19831
  lines.push("```");
19319
19832
  lines.push("");
19320
19833
  lines.push("### LSP cache");
@@ -19347,6 +19860,8 @@ function openCodeEntryToPin(harness, cliVersion) {
19347
19860
  if (harness.kind !== "opencode")
19348
19861
  return null;
19349
19862
  const entry = harness.pluginCache.configuredEntry;
19863
+ if (preserveDoctorPluginEntry(normalizeVersion(cliVersion), entry, doctorInstalledVersion(harness.pluginCache)))
19864
+ return null;
19350
19865
  return entry && entry !== pinnedPluginEntry(normalizeVersion(cliVersion)) ? entry : null;
19351
19866
  }
19352
19867
  function pluginVersionSkewRemediation(harness, cliVersion, older) {
@@ -19362,12 +19877,23 @@ function pluginVersionSkewRemediation(harness, cliVersion, older) {
19362
19877
  return entry ? `Run \`${CLI} doctor --fix\` to pin the plugin entry to ${pinnedPluginEntry(version)} and ${change}.` : `Run \`${CLI} doctor --fix\` to ${change}.`;
19363
19878
  }
19364
19879
  function pluginVersionSkewIssue(harness, cliVersion) {
19880
+ if (harness.pluginCache.local)
19881
+ return null;
19882
+ if (harness.kind === "opencode" && preserveDoctorPluginEntry(normalizeVersion(cliVersion), harness.pluginCache.configuredEntry, doctorInstalledVersion(harness.pluginCache))) {
19883
+ return {
19884
+ code: "plugin_cli_version_skew",
19885
+ severity: "info",
19886
+ scope: harness.displayName,
19887
+ message: doctorNoDowngradeMessage(normalizeVersion(cliVersion), harness.pluginCache.configuredEntry, doctorInstalledVersion(harness.pluginCache)),
19888
+ remediation: ""
19889
+ };
19890
+ }
19365
19891
  const pluginVersion = harness.pluginCache.cached;
19366
19892
  if (!pluginVersion)
19367
19893
  return null;
19368
19894
  if (normalizeVersion(pluginVersion) === normalizeVersion(cliVersion))
19369
19895
  return null;
19370
- const older = compareLooseSemver(pluginVersion, cliVersion) < 0;
19896
+ const older = (compareSemver2(normalizeVersion(pluginVersion), normalizeVersion(cliVersion)) ?? compareLooseSemver(pluginVersion, cliVersion)) < 0;
19371
19897
  const skew = older ? `Plugin version (${pluginVersion}) is older than CLI (${cliVersion}). New binary cache won't be used until you update the plugin.` : `Plugin version (${pluginVersion}) does not match CLI (${cliVersion}). New binary cache may not be used until the plugin and CLI match.`;
19372
19898
  const entry = openCodeEntryToPin(harness, cliVersion);
19373
19899
  return {
@@ -19484,7 +20010,7 @@ function formatDiagnosticIssuesSection(report) {
19484
20010
  return lines;
19485
20011
  }
19486
20012
  function tailLogFile(path, lines) {
19487
- if (!existsSync27(path))
20013
+ if (!existsSync28(path))
19488
20014
  return "";
19489
20015
  if (lines <= 0)
19490
20016
  return "";
@@ -19529,6 +20055,7 @@ var init_diagnostics = __esm(async () => {
19529
20055
  init_legacy_storage();
19530
20056
  init_lsp_cache();
19531
20057
  init_onnx();
20058
+ init_opencode_plugin_cache();
19532
20059
  init_plugin_load();
19533
20060
  init_sanitize();
19534
20061
  init_self_version();
@@ -19684,14 +20211,14 @@ var init_issue_body = __esm(() => {
19684
20211
 
19685
20212
  // src/lib/onnx-fix.ts
19686
20213
  import { randomBytes as randomBytes2 } from "node:crypto";
19687
- import { existsSync as existsSync28, mkdirSync as mkdirSync7, renameSync as renameSync6, rmSync as rmSync7 } from "node:fs";
19688
- import { isAbsolute as isAbsolute10, join as join31, relative as relative4 } from "node:path";
20214
+ import { existsSync as existsSync29, mkdirSync as mkdirSync7, renameSync as renameSync6, rmSync as rmSync7 } from "node:fs";
20215
+ import { isAbsolute as isAbsolute10, join as join32, relative as relative4 } from "node:path";
19689
20216
  function findOnnxFixCandidates(report) {
19690
20217
  const candidates = [];
19691
20218
  for (const harness of report.harnesses) {
19692
20219
  if (!harness.onnxRuntime.required)
19693
20220
  continue;
19694
- const storageOnnxDir = join31(harness.storageDir.path, "onnxruntime");
20221
+ const storageOnnxDir = join32(harness.storageDir.path, "onnxruntime");
19695
20222
  const systemTooOld = harness.onnxRuntime.systemPath !== null && harness.onnxRuntime.systemCompatible === false;
19696
20223
  const cachedTooOld = harness.onnxRuntime.cachedPath !== null && harness.onnxRuntime.cachedCompatible === false;
19697
20224
  const hasCompatibleCached = harness.onnxRuntime.cachedCompatible === true;
@@ -19700,7 +20227,7 @@ function findOnnxFixCandidates(report) {
19700
20227
  harness,
19701
20228
  reason: `cached ONNX Runtime at ${harness.onnxRuntime.cachedPath} is v${harness.onnxRuntime.cachedVersion}, but AFT requires ${harness.onnxRuntime.requirement}. AFT will stage and verify a managed replacement before removing it.`,
19702
20229
  storageOnnxDir,
19703
- storageOnnxBytes: existsSync28(storageOnnxDir) ? dirSize(storageOnnxDir) : 0
20230
+ storageOnnxBytes: existsSync29(storageOnnxDir) ? dirSize(storageOnnxDir) : 0
19704
20231
  });
19705
20232
  continue;
19706
20233
  }
@@ -19709,7 +20236,7 @@ function findOnnxFixCandidates(report) {
19709
20236
  harness,
19710
20237
  reason: `system ONNX Runtime at ${harness.onnxRuntime.systemPath} is v${harness.onnxRuntime.systemVersion}, but AFT requires ${harness.onnxRuntime.requirement}, and no AFT-managed install is present. AFT will leave the system copy untouched and download v1.24 into managed storage.`,
19711
20238
  storageOnnxDir,
19712
- storageOnnxBytes: existsSync28(storageOnnxDir) ? dirSize(storageOnnxDir) : 0
20239
+ storageOnnxBytes: existsSync29(storageOnnxDir) ? dirSize(storageOnnxDir) : 0
19713
20240
  });
19714
20241
  continue;
19715
20242
  }
@@ -19721,7 +20248,7 @@ function findOnnxFixCandidates(report) {
19721
20248
  harness,
19722
20249
  reason: `no compatible ONNX Runtime is installed.${ignoredCopy} AFT will download v1.24 into managed storage.`,
19723
20250
  storageOnnxDir,
19724
- storageOnnxBytes: existsSync28(storageOnnxDir) ? dirSize(storageOnnxDir) : 0
20251
+ storageOnnxBytes: existsSync29(storageOnnxDir) ? dirSize(storageOnnxDir) : 0
19725
20252
  });
19726
20253
  }
19727
20254
  }
@@ -19730,11 +20257,11 @@ function findOnnxFixCandidates(report) {
19730
20257
  async function installManagedReplacement(candidate, ensureFn, rmFn) {
19731
20258
  const suffix = `${process.pid}.${Date.now().toString(36)}.${randomBytes2(6).toString("hex")}`;
19732
20259
  const stagingStorageDir = `${candidate.harness.storageDir.path}.onnx-fix.tmp.${suffix}`;
19733
- const stagedOnnxDir = join31(stagingStorageDir, "onnxruntime");
20260
+ const stagedOnnxDir = join32(stagingStorageDir, "onnxruntime");
19734
20261
  const backupDir = `${candidate.storageOnnxDir}.backup.${suffix}`;
19735
20262
  try {
19736
20263
  const stagedInstalledPath = await ensureFn(stagingStorageDir);
19737
- if (!stagedInstalledPath || !existsSync28(stagedInstalledPath)) {
20264
+ if (!stagedInstalledPath || !existsSync29(stagedInstalledPath)) {
19738
20265
  throw new Error("managed ONNX Runtime download was unavailable");
19739
20266
  }
19740
20267
  const installedRelativePath = relative4(stagedOnnxDir, stagedInstalledPath);
@@ -19742,7 +20269,7 @@ async function installManagedReplacement(candidate, ensureFn, rmFn) {
19742
20269
  throw new Error(`managed ONNX Runtime was installed outside staging: ${stagedInstalledPath}`);
19743
20270
  }
19744
20271
  mkdirSync7(candidate.harness.storageDir.path, { recursive: true });
19745
- const hadPrevious = existsSync28(candidate.storageOnnxDir);
20272
+ const hadPrevious = existsSync29(candidate.storageOnnxDir);
19746
20273
  if (hadPrevious)
19747
20274
  renameSync6(candidate.storageOnnxDir, backupDir);
19748
20275
  try {
@@ -19766,7 +20293,7 @@ async function installManagedReplacement(candidate, ensureFn, rmFn) {
19766
20293
  }
19767
20294
  }
19768
20295
  return {
19769
- installedPath: join31(candidate.storageOnnxDir, installedRelativePath),
20296
+ installedPath: join32(candidate.storageOnnxDir, installedRelativePath),
19770
20297
  clearedPrevious: hadPrevious && cleanupError === undefined,
19771
20298
  cleanupError
19772
20299
  };
@@ -19915,10 +20442,10 @@ var init_session_log = __esm(() => {
19915
20442
  });
19916
20443
 
19917
20444
  // src/lib/sessions.ts
19918
- import { existsSync as existsSync29, readdirSync as readdirSync12, readFileSync as readFileSync16, statSync as statSync18 } from "node:fs";
20445
+ import { existsSync as existsSync30, readdirSync as readdirSync13, readFileSync as readFileSync17, statSync as statSync18 } from "node:fs";
19919
20446
  import { createRequire as createRequire4 } from "node:module";
19920
20447
  import { homedir as homedir19 } from "node:os";
19921
- import { basename as basename3, join as join32 } from "node:path";
20448
+ import { basename as basename3, join as join33 } from "node:path";
19922
20449
  function listRecentSessions(adapter) {
19923
20450
  try {
19924
20451
  if (adapter.kind === "opencode")
@@ -19952,8 +20479,8 @@ function mapOpenCodeSessionRows(rows) {
19952
20479
  }).filter((session) => session !== null).sort((a, b) => b.lastActivity - a.lastActivity).slice(0, MAX_RECENT_SESSIONS);
19953
20480
  }
19954
20481
  function listRecentOpenCodeSessions() {
19955
- const dbPath = join32(getXdgDataHome(), "opencode", "opencode.db");
19956
- if (!existsSync29(dbPath))
20482
+ const dbPath = join33(getXdgDataHome(), "opencode", "opencode.db");
20483
+ if (!existsSync30(dbPath))
19957
20484
  return [];
19958
20485
  let db = null;
19959
20486
  try {
@@ -19972,10 +20499,10 @@ function listRecentOpenCodeSessions() {
19972
20499
  }
19973
20500
  function getXdgDataHome() {
19974
20501
  const xdgDataHome = process.env.XDG_DATA_HOME;
19975
- return xdgDataHome && xdgDataHome.length > 0 ? xdgDataHome : join32(homedir19(), ".local", "share");
20502
+ return xdgDataHome && xdgDataHome.length > 0 ? xdgDataHome : join33(homedir19(), ".local", "share");
19976
20503
  }
19977
20504
  function listRecentPiSessions() {
19978
- return listPiSessionsFromDir(join32(getHomeDir(), ".pi", "agent", "sessions"));
20505
+ return listPiSessionsFromDir(join33(getHomeDir(), ".pi", "agent", "sessions"));
19979
20506
  }
19980
20507
  function getHomeDir() {
19981
20508
  const envHome = process.platform === "win32" ? process.env.USERPROFILE : process.env.HOME;
@@ -19983,7 +20510,7 @@ function getHomeDir() {
19983
20510
  }
19984
20511
  function listPiSessionsFromDir(sessionsDir) {
19985
20512
  try {
19986
- if (!existsSync29(sessionsDir))
20513
+ if (!existsSync30(sessionsDir))
19987
20514
  return [];
19988
20515
  const files = collectJsonlFiles(sessionsDir).map((filePath) => {
19989
20516
  try {
@@ -19995,7 +20522,7 @@ function listPiSessionsFromDir(sessionsDir) {
19995
20522
  }).filter((entry) => entry !== null).sort((a, b) => b.mtimeMs - a.mtimeMs).slice(0, MAX_RECENT_SESSIONS * 4);
19996
20523
  const sessions = [];
19997
20524
  for (const file of files) {
19998
- const parsed = parsePiSessionJsonl(readFileSync16(file.filePath, "utf8"), basename3(file.filePath));
20525
+ const parsed = parsePiSessionJsonl(readFileSync17(file.filePath, "utf8"), basename3(file.filePath));
19999
20526
  if (!parsed)
20000
20527
  continue;
20001
20528
  sessions.push({ ...parsed, lastActivity: file.mtimeMs });
@@ -20016,12 +20543,12 @@ function collectJsonlFiles(root) {
20016
20543
  continue;
20017
20544
  let entries;
20018
20545
  try {
20019
- entries = readdirSync12(dir, { withFileTypes: true });
20546
+ entries = readdirSync13(dir, { withFileTypes: true });
20020
20547
  } catch {
20021
20548
  continue;
20022
20549
  }
20023
20550
  for (const entry of entries) {
20024
- const path = join32(dir, entry.name);
20551
+ const path = join33(dir, entry.name);
20025
20552
  if (entry.isDirectory()) {
20026
20553
  stack.push(path);
20027
20554
  } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
@@ -20122,19 +20649,19 @@ var init_sessions = __esm(() => {
20122
20649
  // src/commands/doctor.ts
20123
20650
  import {
20124
20651
  chmodSync as chmodSync4,
20125
- existsSync as existsSync30,
20652
+ existsSync as existsSync31,
20126
20653
  lstatSync as lstatSync5,
20127
20654
  mkdirSync as mkdirSync8,
20128
20655
  mkdtempSync as mkdtempSync2,
20129
- readdirSync as readdirSync13,
20130
- readFileSync as readFileSync17,
20656
+ readdirSync as readdirSync14,
20657
+ readFileSync as readFileSync18,
20131
20658
  realpathSync as realpathSync8,
20132
20659
  rmSync as rmSync8,
20133
20660
  statSync as statSync19,
20134
20661
  writeFileSync as writeFileSync5
20135
20662
  } from "node:fs";
20136
20663
  import { tmpdir as tmpdir5 } from "node:os";
20137
- import { dirname as dirname12, join as join33 } from "node:path";
20664
+ import { dirname as dirname13, join as join34 } from "node:path";
20138
20665
  function openCodeAdapter(adapters) {
20139
20666
  return adapters.find((adapter) => adapter.kind === "opencode");
20140
20667
  }
@@ -20157,7 +20684,7 @@ function refuseAmbiguousOpenCodeWrites(detection) {
20157
20684
  function adapterConfigNeedsUpdate(adapter, tui = false) {
20158
20685
  const method = tui ? "needsTuiPluginEntryUpdate" : "needsPluginEntryUpdate";
20159
20686
  const candidate = adapter;
20160
- return candidate[method]?.() ?? false;
20687
+ return candidate[method]?.({ doctor: true }) ?? false;
20161
20688
  }
20162
20689
  function buildDoctorProfileArgs(argv) {
20163
20690
  const profileIndex = argv.indexOf("--profile");
@@ -20214,12 +20741,12 @@ async function runDoctor(options) {
20214
20741
  configPath: opencodeHarness.configPaths.harnessConfig,
20215
20742
  logPath: opencodeHarness.logFile.path,
20216
20743
  pluginCachePath: opencodeHarness.pluginCache.path,
20217
- cachedPluginVersion: opencodeHarness.pluginCache.cached,
20744
+ cachedPluginVersion: doctorInstalledVersion(opencodeHarness.pluginCache),
20218
20745
  expectedPluginEntry: opencodeAdapter.pluginEntryWithVersion,
20219
20746
  acceptExplicitPluginVersion: hostDetection.status === "v1"
20220
20747
  }) : null;
20221
20748
  const opencodeSkew = opencodeHarness ? pluginVersionSkewFor(report, opencodeHarness) : null;
20222
- log2.info(`AFT CLI v${report.cliVersion}, AFT binary ${report.binaryVersion ?? "unknown"}`);
20749
+ log2.info(`AFT CLI v${report.cliVersion}, ${describeCliBinary(report.binaryVersion, report.binaryPath)}`);
20223
20750
  if (!report.binaryVersion) {
20224
20751
  const hasEnabledRegisteredHarness = report.harnesses.some((h) => h.pluginRegistered && h.aftConfig.enabled);
20225
20752
  const hasRegisteredHarness = report.harnesses.some((h) => h.pluginRegistered);
@@ -20235,7 +20762,7 @@ async function runDoctor(options) {
20235
20762
  }
20236
20763
  logUnmatchedBinaryCandidates(report.cliVersion);
20237
20764
  }
20238
- log2.info(`Binary cache: ${report.binaryCache.versions.length} version(s), ${formatBytes(report.binaryCache.totalSize)} at ${report.binaryCache.path}`);
20765
+ log2.info(`Binary cache: ${report.binaryCache.versions.length} version(s), ${formatBytes(report.binaryCache.totalSize)} at ${report.binaryCache.path}; ${CLI_VERSION_CACHED_LABEL}: ${report.binaryCache.activeVersion ?? "no"}`);
20239
20766
  logBuildBreakerSuspensions(report);
20240
20767
  const features = logFeatureStatus(options.argv, options.runNative, {
20241
20768
  checkGh: options.checkGh ?? checkGhStatus,
@@ -20274,6 +20801,8 @@ async function runDoctor(options) {
20274
20801
  }
20275
20802
  if (opencodeSkew)
20276
20803
  log2.error(` ${opencodeSkew.message} ${opencodeSkew.remediation}`);
20804
+ else if (opencodeDoctor?.versionNotice)
20805
+ log2.info(` ${opencodeDoctor.versionNotice}`);
20277
20806
  for (const hostTool of OPENCODE_HOST_TOOL_NAMES) {
20278
20807
  const line = hostToolDoctorLine(opencodeAdapter, hostTool);
20279
20808
  if (line)
@@ -20286,7 +20815,7 @@ async function runDoctor(options) {
20286
20815
  } else {
20287
20816
  log2.info(` plugin registered: ${h.pluginRegistered ? "yes" : "no"}`);
20288
20817
  }
20289
- log2.info(` plugin version: ${h.kind === "opencode" ? opencodeDoctor?.pluginVersion ?? h.pluginCache.cached ?? "not installed" : h.pluginCache.cached ?? "not installed"}`);
20818
+ log2.info(` plugin version: ${describePluginVersion(h.pluginCache)}`);
20290
20819
  if (!h.aftConfig.enabled) {
20291
20820
  log2.info(` AFT disabled by config${h.aftConfig.enabledSource ? ` (${h.aftConfig.enabledSource})` : ""}; plugin will stay inert`);
20292
20821
  }
@@ -20545,7 +21074,7 @@ function clearOldBinaries(cliVersion = getSelfVersion(), plugins = getAllAdapter
20545
21074
  errors: [],
20546
21075
  keptVersion: keepTag
20547
21076
  };
20548
- if (!existsSync30(info.path)) {
21077
+ if (!existsSync31(info.path)) {
20549
21078
  log2.info(`Binary cache: nothing to clear at ${info.path}`);
20550
21079
  return result;
20551
21080
  }
@@ -20576,7 +21105,7 @@ function clearOldBinaries(cliVersion = getSelfVersion(), plugins = getAllAdapter
20576
21105
  return result;
20577
21106
  }
20578
21107
  for (const version of stale) {
20579
- const dir = join33(info.path, version);
21108
+ const dir = join34(info.path, version);
20580
21109
  let bytes = 0;
20581
21110
  try {
20582
21111
  bytes = statSync19(dir).isDirectory() ? dirSize(dir) : 0;
@@ -20640,9 +21169,14 @@ function findPluginUpdateTargets(adapters, report) {
20640
21169
  if (!harness.hostInstalled || !harness.pluginRegistered)
20641
21170
  continue;
20642
21171
  const cache = harness.pluginCache;
20643
- if (!cache?.exists || !cache.cached || !cache.latest)
21172
+ if (cache.local)
21173
+ continue;
21174
+ const cliVersion = cache.cliVersion ?? cache.latest ?? report.cliVersion;
21175
+ if (!cache?.exists || !cache.cached)
20644
21176
  continue;
20645
- if (cache.cached === cache.latest)
21177
+ if (preserveDoctorPluginEntry(cliVersion, cache.configuredEntry, doctorInstalledVersion(cache)))
21178
+ continue;
21179
+ if ((compareSemver2(cache.cached, cliVersion) ?? 0) >= 0)
20646
21180
  continue;
20647
21181
  const adapter = adaptersByKind.get(harness.kind);
20648
21182
  if (!adapter)
@@ -20652,8 +21186,8 @@ function findPluginUpdateTargets(adapters, report) {
20652
21186
  adapter,
20653
21187
  installDir: cache.path,
20654
21188
  cached: cache.cached,
20655
- latest: cache.latest,
20656
- oldPin: pin !== null && pin !== cache.latest ? pin : null
21189
+ latest: cliVersion,
21190
+ oldPin: pin !== null && pin !== cliVersion ? pin : null
20657
21191
  });
20658
21192
  }
20659
21193
  return targets;
@@ -20736,9 +21270,9 @@ async function runDoctorNpmInstall(npm, installDir) {
20736
21270
  });
20737
21271
  }
20738
21272
  function installedPluginVersion(installDir) {
20739
- const manifest = join33(installDir, "node_modules", ...AFT_OPENCODE_PACKAGE.split("/"), "package.json");
21273
+ const manifest = join34(installDir, "node_modules", ...AFT_OPENCODE_PACKAGE.split("/"), "package.json");
20740
21274
  try {
20741
- const pkg = JSON.parse(readFileSync17(manifest, "utf-8"));
21275
+ const pkg = JSON.parse(readFileSync18(manifest, "utf-8"));
20742
21276
  return typeof pkg.version === "string" ? pkg.version : null;
20743
21277
  } catch {
20744
21278
  return null;
@@ -20838,7 +21372,7 @@ function buildDoctorFixPlan(adapters, report) {
20838
21372
  } else {
20839
21373
  items.push({
20840
21374
  kind: "plugin",
20841
- message: `Will ${harness.pluginRegistered ? "update" : "add"} ${adapter.pluginEntryWithVersion} ${harness.pluginRegistered ? "in" : "to"} ${harness.configPaths.harnessConfig}`
21375
+ message: `Will ${harness.pluginRegistered ? "update" : "add"} ${adapter instanceof OpenCodeAdapter ? adapter.doctorPluginEntry() : adapter.pluginEntryWithVersion} ${harness.pluginRegistered ? "in" : "to"} ${harness.configPaths.harnessConfig}`
20842
21376
  });
20843
21377
  }
20844
21378
  }
@@ -20855,7 +21389,7 @@ function buildDoctorFixPlan(adapters, report) {
20855
21389
  continue;
20856
21390
  items.push({
20857
21391
  kind: "plugin",
20858
- message: `Will ${hasTuiEntry ? "update" : "add"} ${adapter.pluginEntryWithVersion} ${hasTuiEntry ? "in" : "to"} ${harness.configPaths.tuiConfig} (TUI sidebar)`
21392
+ message: `Will ${hasTuiEntry ? "update" : "add"} ${adapter instanceof OpenCodeAdapter ? adapter.doctorPluginEntry(true) : adapter.pluginEntryWithVersion} ${hasTuiEntry ? "in" : "to"} ${harness.configPaths.tuiConfig} (TUI sidebar)`
20859
21393
  });
20860
21394
  }
20861
21395
  for (const { adapter, hostTool } of hostToolFixTargets(adapters)) {
@@ -20994,6 +21528,12 @@ async function runFixFlow(argv, detectOpenCodeHost, resolveAdapters = resolveAda
20994
21528
  }
20995
21529
  log2.info("Running diagnostics to identify auto-fixable issues…");
20996
21530
  const report = await collect(adapters);
21531
+ for (const harness of report.harnesses) {
21532
+ const cache = harness.pluginCache;
21533
+ if (harness.kind === "opencode" && preserveDoctorPluginEntry(report.cliVersion, cache.configuredEntry, doctorInstalledVersion(cache))) {
21534
+ log2.info(doctorNoDowngradeMessage(report.cliVersion, cache.configuredEntry, doctorInstalledVersion(cache)));
21535
+ }
21536
+ }
20997
21537
  if (!report.binaryVersion) {
20998
21538
  logUnmatchedBinaryCandidates(report.cliVersion);
20999
21539
  }
@@ -21197,14 +21737,14 @@ function formatDoctorStorageStatus(h) {
21197
21737
  let cacheWarning = "";
21198
21738
  if (h.storageDir.cacheRoot) {
21199
21739
  try {
21200
- const parent = realpathSync8(dirname12(h.storageDir.cacheRoot));
21740
+ const parent = realpathSync8(dirname13(h.storageDir.cacheRoot));
21201
21741
  let durable = h.storageDir.path;
21202
- while (!existsSync30(durable) && dirname12(durable) !== durable)
21203
- durable = dirname12(durable);
21742
+ while (!existsSync31(durable) && dirname13(durable) !== durable)
21743
+ durable = dirname13(durable);
21204
21744
  let registered = false;
21205
- const history = join33(h.storageDir.path, "retention/cache-parents.json");
21206
- if (existsSync30(history)) {
21207
- const saved = JSON.parse(readFileSync17(history, "utf8"));
21745
+ const history = join34(h.storageDir.path, "retention/cache-parents.json");
21746
+ if (existsSync31(history)) {
21747
+ const saved = JSON.parse(readFileSync18(history, "utf8"));
21208
21748
  registered = saved.parents?.some((entry) => entry.path === parent) === true;
21209
21749
  }
21210
21750
  if (!registered && statSync19(parent).dev === statSync19(durable).dev) {
@@ -21246,7 +21786,7 @@ function ensureStorageDirsForRegisteredPlugins(adapters) {
21246
21786
  if (!adapter.isInstalled() || !adapter.hasPluginEntry())
21247
21787
  continue;
21248
21788
  storageDir = adapter.getStorageDir();
21249
- if (existsSync30(storageDir))
21789
+ if (existsSync31(storageDir))
21250
21790
  continue;
21251
21791
  mkdirSync8(storageDir, { recursive: true });
21252
21792
  summary.created += 1;
@@ -21309,7 +21849,7 @@ async function maybeFixPlugin(adapter) {
21309
21849
  return summary;
21310
21850
  if (!adapter.hasPluginEntry() || adapterConfigNeedsUpdate(adapter)) {
21311
21851
  log2.info(`${adapter.displayName}: attempting to register or update plugin config…`);
21312
- const result = await adapter.ensurePluginEntry();
21852
+ const result = await adapter.ensurePluginEntry({ doctor: true });
21313
21853
  if (result.ok) {
21314
21854
  if (result.action === "added" || result.action === "updated")
21315
21855
  summary.changed += 1;
@@ -21322,7 +21862,7 @@ async function maybeFixPlugin(adapter) {
21322
21862
  if (adapter.ensureTuiPluginEntry && adapter.hasTuiPluginEntry && adapter.tuiPluginEntryApplies?.() !== false) {
21323
21863
  const hasTuiEntry = adapter.hasTuiPluginEntry();
21324
21864
  if (!hasTuiEntry || adapterConfigNeedsUpdate(adapter, true)) {
21325
- const result = await adapter.ensureTuiPluginEntry();
21865
+ const result = await adapter.ensureTuiPluginEntry({ doctor: true });
21326
21866
  if (result.ok && (result.action === "added" || result.action === "updated")) {
21327
21867
  summary.changed += 1;
21328
21868
  log2.success(`${adapter.displayName}: ${result.message}`);
@@ -21407,11 +21947,11 @@ function deriveIssueTitleFromBody(body) {
21407
21947
  function writeIssueReviewFile(body) {
21408
21948
  let reviewDir = null;
21409
21949
  try {
21410
- reviewDir = mkdtempSync2(join33(tmpdir5(), "aft-issue-"));
21950
+ reviewDir = mkdtempSync2(join34(tmpdir5(), "aft-issue-"));
21411
21951
  if (process.platform !== "win32") {
21412
21952
  chmodSync4(reviewDir, 448);
21413
21953
  }
21414
- const outPath = join33(reviewDir, "issue.md");
21954
+ const outPath = join34(reviewDir, "issue.md");
21415
21955
  writeFileSync5(outPath, `${body}
21416
21956
  `, { encoding: "utf8", mode: 384, flag: "wx" });
21417
21957
  return { path: outPath, realPath: realpathSync8(outPath) };
@@ -21432,7 +21972,7 @@ function readReviewedIssueFile(reviewFile) {
21432
21972
  log2.error(`Review file path changed before filing; refusing to read ${reviewFile.path}.`);
21433
21973
  return null;
21434
21974
  }
21435
- return readFileSync17(reviewFile.path, "utf8");
21975
+ return readFileSync18(reviewFile.path, "utf8");
21436
21976
  } catch (err) {
21437
21977
  log2.error(`Failed to read reviewed issue report: ${err instanceof Error ? err.message : String(err)}`);
21438
21978
  return null;
@@ -21477,14 +22017,14 @@ function collectIssueLog(path, selectedSession) {
21477
22017
  };
21478
22018
  }
21479
22019
  function collectIssueEngineLog(path, selectedSession) {
21480
- const directory = dirname12(path);
22020
+ const directory = dirname13(path);
21481
22021
  const files = [];
21482
22022
  try {
21483
- for (const name of readdirSync13(directory)) {
22023
+ for (const name of readdirSync14(directory)) {
21484
22024
  if (!/^aft-\d+\.log(?:\.1)?$/.test(name))
21485
22025
  continue;
21486
22026
  try {
21487
- const candidate = join33(directory, name);
22027
+ const candidate = join34(directory, name);
21488
22028
  const stat = lstatSync5(candidate);
21489
22029
  if (stat.isFile())
21490
22030
  files.push({ path: candidate, modified: stat.mtimeMs });
@@ -21519,6 +22059,13 @@ async function runIssueFlow(argv) {
21519
22059
  const selectedSession = await promptForIssueSession(adapters[0]);
21520
22060
  const selectedBareSessionId = selectedSession?.id.replace(/^ses_/, "") ?? null;
21521
22061
  const report = await collectDiagnostics(adapters);
22062
+ const sanitizeContext = {
22063
+ projectRoots: [
22064
+ ...adapters.flatMap((adapter) => projectRootsFromValue(readJsoncFile(adapter.detectConfigPaths().aftConfig).value)),
22065
+ ...adapters.flatMap((adapter) => projectRootsFromValue(listRecentSessions(adapter))),
22066
+ ...projectRootsFromValue(report)
22067
+ ]
22068
+ };
21522
22069
  const logResults = adapters.flatMap((adapter) => {
21523
22070
  const path = adapter.getLogFile();
21524
22071
  const { logText, errors, notice } = collectIssueLog(path, selectedSession);
@@ -21535,7 +22082,7 @@ ${logText || "<no log output>"}
21535
22082
  errors
21536
22083
  },
21537
22084
  {
21538
- section: `#### Engine logs (${dirname12(path)})
22085
+ section: `#### Engine logs (${dirname13(path)})
21539
22086
 
21540
22087
  ${engine.notice}
21541
22088
  \`\`\`
@@ -21548,7 +22095,7 @@ ${engine.logText || "<no log output>"}
21548
22095
  });
21549
22096
  const logSections = logResults.map((result) => result.section).join(`
21550
22097
  `);
21551
- const recentErrorLines = extractRecentErrors(logResults.map((result) => sanitizeContent(result.errors)).join(`
22098
+ const recentErrorLines = extractRecentErrors(logResults.map((result) => sanitizeContent(result.errors, sanitizeContext)).join(`
21552
22099
  `), 20);
21553
22100
  const recentErrorsSection = recentErrorLines.length === 0 ? "_No error-shaped log lines found in recent history._" : ["```", recentErrorLines.join(`
21554
22101
  `), "```"].join(`
@@ -21578,7 +22125,7 @@ ${engine.logText || "<no log output>"}
21578
22125
  "_Usernames and home paths have been stripped from this report._"
21579
22126
  ].join(`
21580
22127
  `);
21581
- const body = capBodyToGithubLimit(sanitizeContent(rawBody));
22128
+ const body = capBodyToGithubLimit(sanitizeContent(rawBody, sanitizeContext));
21582
22129
  const reviewFile = writeIssueReviewFile(body);
21583
22130
  if (!reviewFile) {
21584
22131
  outro("Done — could not write the issue report.");
@@ -21603,7 +22150,7 @@ https://github.com/cortexkit/aft/issues/new and paste the contents of ${outPath}
21603
22150
  outro("Done.");
21604
22151
  return 1;
21605
22152
  }
21606
- const finalBody = capBodyToGithubLimit(sanitizeContent(reviewedBody));
22153
+ const finalBody = capBodyToGithubLimit(sanitizeContent(reviewedBody, sanitizeContext));
21607
22154
  const finalTitle = deriveIssueTitleFromBody(finalBody);
21608
22155
  if (isGhInstalled()) {
21609
22156
  log2.info("Opening GitHub issue via `gh`…");
@@ -21645,6 +22192,7 @@ var init_doctor = __esm(async () => {
21645
22192
  init_jsonc();
21646
22193
  init_lsp_cache();
21647
22194
  init_onnx_fix();
22195
+ init_opencode_plugin_cache();
21648
22196
  init_paths2();
21649
22197
  init_prompts();
21650
22198
  init_sanitize();
@@ -21677,24 +22225,24 @@ var init_doctor = __esm(async () => {
21677
22225
  });
21678
22226
 
21679
22227
  // src/commands/lsp.ts
21680
- import { existsSync as existsSync31, readdirSync as readdirSync14, statSync as statSync20 } from "node:fs";
22228
+ import { existsSync as existsSync32, readdirSync as readdirSync15, statSync as statSync20 } from "node:fs";
21681
22229
  import { createRequire as createRequire5 } from "node:module";
21682
- import { dirname as dirname13, join as join34, resolve as resolve15 } from "node:path";
22230
+ import { dirname as dirname14, join as join35, resolve as resolve15 } from "node:path";
21683
22231
  function findProjectRootForFile(filePath, fallbackCwd = process.cwd()) {
21684
22232
  const resolvedFile = resolve15(fallbackCwd, filePath);
21685
- let dir = dirname13(resolvedFile);
22233
+ let dir = dirname14(resolvedFile);
21686
22234
  try {
21687
- if (existsSync31(resolvedFile) && statSync20(resolvedFile).isDirectory()) {
22235
+ if (existsSync32(resolvedFile) && statSync20(resolvedFile).isDirectory()) {
21688
22236
  dir = resolvedFile;
21689
22237
  }
21690
22238
  } catch {
21691
- dir = dirname13(resolvedFile);
22239
+ dir = dirname14(resolvedFile);
21692
22240
  }
21693
22241
  while (true) {
21694
- if (PROJECT_ROOT_MARKERS.some((marker) => existsSync31(join34(dir, marker)))) {
22242
+ if (PROJECT_ROOT_MARKERS.some((marker) => existsSync32(join35(dir, marker)))) {
21695
22243
  return dir;
21696
22244
  }
21697
- const parent = dirname13(dir);
22245
+ const parent = dirname14(dir);
21698
22246
  if (parent === dir)
21699
22247
  return resolve15(fallbackCwd);
21700
22248
  dir = parent;
@@ -21840,18 +22388,18 @@ function buildConfigureParams(adapter, projectRoot) {
21840
22388
  function inferLspPathsExtra(_lsp) {
21841
22389
  const paths = new Set;
21842
22390
  for (const entry of childDirs(getAftLspPackagesDir())) {
21843
- paths.add(join34(entry, "node_modules", ".bin"));
22391
+ paths.add(join35(entry, "node_modules", ".bin"));
21844
22392
  }
21845
22393
  for (const entry of childDirs(getAftLspBinariesDir())) {
21846
- paths.add(join34(entry, "bin"));
22394
+ paths.add(join35(entry, "bin"));
21847
22395
  }
21848
22396
  return [...paths];
21849
22397
  }
21850
22398
  function childDirs(path) {
21851
- if (!existsSync31(path))
22399
+ if (!existsSync32(path))
21852
22400
  return [];
21853
22401
  try {
21854
- return readdirSync14(path).map((entry) => join34(path, entry)).filter((entry) => {
22402
+ return readdirSync15(path).map((entry) => join35(path, entry)).filter((entry) => {
21855
22403
  try {
21856
22404
  return statSync20(entry).isDirectory();
21857
22405
  } catch {
@@ -21895,7 +22443,7 @@ function typescriptPackageWarning(response) {
21895
22443
  if (!typescriptServerSpawned || !response.project_root || diagnosticsCount > 0)
21896
22444
  return null;
21897
22445
  try {
21898
- createRequire5(join34(response.project_root, "package.json")).resolve("typescript");
22446
+ createRequire5(join35(response.project_root, "package.json")).resolve("typescript");
21899
22447
  return null;
21900
22448
  } catch {
21901
22449
  return "typescript package not resolvable from project — server will produce no diagnostics";
@@ -21935,7 +22483,7 @@ var init_lsp = __esm(async () => {
21935
22483
  });
21936
22484
 
21937
22485
  // src/commands/doctor-filters.ts
21938
- import { existsSync as existsSync32 } from "node:fs";
22486
+ import { existsSync as existsSync33 } from "node:fs";
21939
22487
  import { relative as relative5, resolve as resolve16 } from "node:path";
21940
22488
  function printDoctorFiltersHelp() {
21941
22489
  console.log(`Usage: ${CLI} doctor filters [--show <name>] [trust|untrust]`);
@@ -21977,7 +22525,7 @@ async function runDoctorFilters(options) {
21977
22525
  log2.error(list.message ?? list.code ?? "list_filters failed");
21978
22526
  return 1;
21979
22527
  }
21980
- list.project_dir_exists = list.project_dir ? existsSync32(list.project_dir) : false;
22528
+ list.project_dir_exists = list.project_dir ? existsSync33(list.project_dir) : false;
21981
22529
  if (mode.kind === "list") {
21982
22530
  console.log(renderFilterList(list, projectRoot));
21983
22531
  return 0;
@@ -22191,7 +22739,7 @@ function printHelp() {
22191
22739
  console.log(" setup --answers <file> Save feature choices from an answers file");
22192
22740
  console.log(" index Build one configured index snapshot (no scheduler)");
22193
22741
  console.log(" backups purge Remove undo backups by --path and/or --session (dry run unless --yes)");
22194
- console.log(" cache prune-legacy Delete legacy index caches past their rollback window (dry run unless --yes)");
22742
+ console.log(" cache prune-legacy Delete the index folders AFT used before 0.64 (dry run unless --yes)");
22195
22743
  console.log(" doctor Check and fix configuration issues");
22196
22744
  console.log(" doctor --profile [seconds] Profile a running AFT daemon");
22197
22745
  console.log(" doctor lsp <file> Inspect LSP setup for one file");