@cortexkit/aft 0.63.5 → 0.64.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
@@ -498,12 +498,13 @@ function resolvePluginLogPath(context = {}) {
498
498
  var init_storage_paths = () => {};
499
499
 
500
500
  // ../aft-bridge/dist/bash-host-fallback.js
501
- var BASH_HOST_FALLBACK_MAX_OUTPUT_BYTES, BASH_HOST_FALLBACK_MAX_TIMEOUT_MS;
501
+ 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
502
  var init_bash_host_fallback = __esm(() => {
503
503
  init_child_process();
504
504
  init_storage_paths();
505
505
  BASH_HOST_FALLBACK_MAX_OUTPUT_BYTES = 100 * 1024;
506
506
  BASH_HOST_FALLBACK_MAX_TIMEOUT_MS = 10 * 60 * 1000;
507
+ 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
508
  });
508
509
  // ../aft-bridge/dist/bash-permissions.js
509
510
  var init_bash_permissions = __esm(() => {
@@ -2289,7 +2290,10 @@ function readConfigTiers(opts) {
2289
2290
  var init_config_tiers = () => {};
2290
2291
 
2291
2292
  // ../aft-bridge/dist/config-watch.js
2292
- var init_config_watch = () => {};
2293
+ var CONFIG_READ_LIMIT;
2294
+ var init_config_watch = __esm(() => {
2295
+ CONFIG_READ_LIMIT = 1024 * 1024;
2296
+ });
2293
2297
 
2294
2298
  // ../aft-bridge/dist/platform.js
2295
2299
  var PLATFORM_ARCH_MAP, PLATFORM_ASSET_MAP;
@@ -3539,6 +3543,44 @@ function dropNoEffectPaths(map, prefix, out) {
3539
3543
  }
3540
3544
  }
3541
3545
  }
3546
+ function retiredIdleCheckoutHours(value) {
3547
+ const raw = typeof value === "number" && Number.isSafeInteger(value) ? value : IDLE_CHECKOUT_HOURS_DEFAULT;
3548
+ return Math.min(IDLE_CHECKOUT_HOURS_MAX, Math.max(IDLE_CHECKOUT_HOURS_MIN, raw));
3549
+ }
3550
+ function translateDataLayerPaths(map, prefix, out) {
3551
+ const [retired, replacement] = RETIRED_IDLE_CHECKOUT_HOURS;
3552
+ const hours = takeNested(map, "views", "idle_checkout_hours");
3553
+ if (hours.found) {
3554
+ recordRetired(out, prefix, retired);
3555
+ if (isRecord(map.idle) && map.idle.checkout_hours !== undefined) {
3556
+ superseded(out, `${prefix}${retired}`, `${retired} is ignored because ${replacement} is set`);
3557
+ } else {
3558
+ if (!Object.hasOwn(map, "idle"))
3559
+ map.idle = {};
3560
+ if (isRecord(map.idle))
3561
+ map.idle.checkout_hours = retiredIdleCheckoutHours(hours.value);
3562
+ }
3563
+ }
3564
+ for (const path of REMOVED_DATA_LAYER_PATHS) {
3565
+ if (hasPath(map, path))
3566
+ out.removedKeys.push(`${prefix}${path}`);
3567
+ }
3568
+ }
3569
+ function dropRemovedDataLayerKeys(map) {
3570
+ const blocks = [map];
3571
+ if (isRecord(map.harnesses)) {
3572
+ for (const block of Object.values(map.harnesses))
3573
+ if (isRecord(block))
3574
+ blocks.push(block);
3575
+ }
3576
+ for (const block of blocks) {
3577
+ for (const path of REMOVED_DATA_LAYER_PATHS) {
3578
+ const [container, leaf] = path.split(".");
3579
+ if (container && leaf)
3580
+ takeNested(block, container, leaf);
3581
+ }
3582
+ }
3583
+ }
3542
3584
  function translateExperimentalPaths(map, prefix, out) {
3543
3585
  for (const [oldKey, leaf] of [
3544
3586
  ["experimental_lsp_ty", "lsp_ty"],
@@ -3571,23 +3613,25 @@ function translateExperimentalPaths(map, prefix, out) {
3571
3613
  const experimental = map.experimental;
3572
3614
  const legacy = experimental.bash;
3573
3615
  if (isRecord(legacy)) {
3574
- if (!["rewrite", "compress", "background"].some((leaf) => Object.hasOwn(legacy, leaf)))
3575
- return;
3616
+ if (Object.keys(legacy).length === 0)
3617
+ recordRetired(out, prefix, "experimental.bash");
3576
3618
  for (const leaf of Object.keys(legacy))
3577
3619
  recordRetired(out, prefix, `experimental.bash.${leaf}`);
3578
3620
  if (Object.hasOwn(map, "bash")) {
3579
3621
  superseded(out, "experimental.bash", 'experimental.bash is ignored because top-level "bash" is already set');
3580
3622
  } else {
3581
- const bash = {
3582
- rewrite: legacy.rewrite === true,
3583
- compress: legacy.compress === true,
3584
- background: legacy.background === true
3585
- };
3623
+ const bash = {};
3624
+ if (["rewrite", "compress", "background"].some((leaf) => Object.hasOwn(legacy, leaf))) {
3625
+ bash.rewrite = legacy.rewrite === true;
3626
+ bash.compress = legacy.compress === true;
3627
+ bash.background = legacy.background === true;
3628
+ }
3586
3629
  for (const leaf of ["long_running_reminder_enabled", "long_running_reminder_interval_ms"]) {
3587
3630
  if (Object.hasOwn(legacy, leaf))
3588
3631
  bash[leaf] = legacy[leaf];
3589
3632
  }
3590
- map.bash = bash;
3633
+ if (Object.keys(bash).length > 0)
3634
+ map.bash = bash;
3591
3635
  }
3592
3636
  } else {
3593
3637
  recordRetired(out, prefix, "experimental.bash");
@@ -3600,6 +3644,7 @@ function translateBlock(map, userBase, blockLabel, out) {
3600
3644
  const prefix = blockLabel === "base" ? "" : `${blockLabel}.`;
3601
3645
  translateGithubAliases(map, prefix, out);
3602
3646
  translateInspectLspPaths(map, prefix, out);
3647
+ translateDataLayerPaths(map, prefix, out);
3603
3648
  dropNoEffectPaths(map, prefix, out);
3604
3649
  translateExperimentalPaths(map, prefix, out);
3605
3650
  if (isRecord(map.sandbox) && Object.hasOwn(map.sandbox, "enabled")) {
@@ -3638,8 +3683,6 @@ function translateBlock(map, userBase, blockLabel, out) {
3638
3683
  });
3639
3684
  }
3640
3685
  for (const [path] of RETIRED_PATHS) {
3641
- if (path === "experimental.bash")
3642
- continue;
3643
3686
  if (hasPath(map, path))
3644
3687
  recordRetired(out, prefix, path);
3645
3688
  }
@@ -3772,6 +3815,7 @@ function translateConfigDocument(map, tier) {
3772
3815
  warnings: [],
3773
3816
  legacyInput: false,
3774
3817
  retiredKeys: [],
3818
+ removedKeys: [],
3775
3819
  retiredEnabledFalse: false
3776
3820
  };
3777
3821
  translateBlock(map, tier === "user", "base", out);
@@ -3782,6 +3826,7 @@ function translateConfigDocument(map, tier) {
3782
3826
  }
3783
3827
  }
3784
3828
  out.retiredKeys = sortedUnique(out.retiredKeys);
3829
+ out.removedKeys = sortedUnique(out.removedKeys);
3785
3830
  return out;
3786
3831
  }
3787
3832
  function canonicalJson(value) {
@@ -3808,7 +3853,7 @@ function mergeIndexes(base, override, restricted) {
3808
3853
  }
3809
3854
  return merged;
3810
3855
  }
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;
3856
+ 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
3857
  var init_feature_config = __esm(() => {
3813
3858
  CANONICAL_TOOLS = [
3814
3859
  "aft_callgraph",
@@ -3854,6 +3899,22 @@ var init_feature_config = __esm(() => {
3854
3899
  aft_glob: "glob",
3855
3900
  aft_bash: "bash"
3856
3901
  };
3902
+ REMOVED_DATA_LAYER_PATHS = [
3903
+ "views.callgraph_copies",
3904
+ "views.enabled",
3905
+ "worktree.ram_overlay"
3906
+ ];
3907
+ RETIRED_IDLE_CHECKOUT_HOURS = [
3908
+ "views.idle_checkout_hours",
3909
+ "idle.checkout_hours"
3910
+ ];
3911
+ IDLE_CHECKOUT_HOURS_MAX = 24 * 365;
3912
+ AFT_PACKAGE_NAMES = new Set([
3913
+ "@cortexkit/aft-bridge",
3914
+ "@cortexkit/aft-opencode",
3915
+ "@cortexkit/aft-pi",
3916
+ "@cortexkit/aft"
3917
+ ]);
3857
3918
  RETIRED_PATHS = [
3858
3919
  ["sandbox.enabled", "sandbox.profile"],
3859
3920
  ["tool_surface", "disabled_tools"],
@@ -3876,7 +3937,10 @@ var init_feature_config = __esm(() => {
3876
3937
  ["semantic", "semantic_search", "experimental_semantic_search"],
3877
3938
  ["callgraph", "callgraph_store", undefined]
3878
3939
  ];
3879
- REMOVED_NO_EFFECT_PATHS = ["bridge.hang_threshold"];
3940
+ REMOVED_NO_EFFECT_PATHS = [
3941
+ "bridge.hang_threshold",
3942
+ "inspect.tier2_idle_minutes"
3943
+ ];
3880
3944
  SEMANTIC_COST_NOTICE_DIGEST = noticeDigest({ notice: "semantic_default_cost_v1" });
3881
3945
  SECURITY_CONFIG_SECTIONS = new Set([
3882
3946
  "restrict_to_project_root",
@@ -4127,6 +4191,8 @@ function quoteCmdArgument(value) {
4127
4191
  return `"${value}"`;
4128
4192
  }
4129
4193
  function npmInvocation(resolved, npmArgs, platform = process.platform, env = process.env) {
4194
+ if (resolved.cliPath)
4195
+ return { command: resolved.command, args: [resolved.cliPath, ...npmArgs] };
4130
4196
  if (platform !== "win32" || !/\.(?:cmd|bat)$/i.test(resolved.command)) {
4131
4197
  return { command: resolved.command, args: [...npmArgs] };
4132
4198
  }
@@ -4319,6 +4385,12 @@ var init_lsp_npm_policy = __esm(() => {
4319
4385
  NPM_PROBE_RETRY_MS = 5 * 60000;
4320
4386
  });
4321
4387
 
4388
+ // ../aft-bridge/dist/lsp-npm-selection.js
4389
+ var init_lsp_npm_selection = __esm(() => {
4390
+ init_lsp_npm_policy();
4391
+ init_npm_resolver();
4392
+ });
4393
+
4322
4394
  // ../aft-bridge/dist/message-detach.js
4323
4395
  var BASH_WAIT_DETACH_MAGIC_KEYWORD = "&detach", STANDALONE_DETACH_KEYWORD_SOURCE, STANDALONE_DETACH_KEYWORD_PATTERN, STANDALONE_DETACH_KEYWORDS_PATTERN;
4324
4396
  var init_message_detach = __esm(() => {
@@ -5489,6 +5561,7 @@ var init_dist2 = __esm(() => {
5489
5561
  init_lsp_github_probe();
5490
5562
  init_lsp_github_table();
5491
5563
  init_lsp_npm_policy();
5564
+ init_lsp_npm_selection();
5492
5565
  init_message_detach();
5493
5566
  init_migration();
5494
5567
  init_migration_notices();
@@ -14177,27 +14250,54 @@ var init_jsonc = __esm(() => {
14177
14250
  import_comment_json = __toESM(require_src2(), 1);
14178
14251
  });
14179
14252
 
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
14253
  // ../../.github/opencode-version.txt
14197
14254
  var opencode_version_default = `1.19.3
14198
14255
  `;
14199
14256
  var init_opencode_version = () => {};
14200
14257
 
14258
+ // src/lib/semver.ts
14259
+ function parse2(version) {
14260
+ 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);
14261
+ if (!match)
14262
+ return;
14263
+ const prerelease = match[4]?.split(".") ?? [];
14264
+ if (prerelease.some((part) => /^\d+$/.test(part) && part.length > 1 && part[0] === "0")) {
14265
+ return;
14266
+ }
14267
+ return { core: match.slice(1, 4).map(BigInt), prerelease };
14268
+ }
14269
+ function compareSemver2(left, right) {
14270
+ const a = parse2(left);
14271
+ const b = parse2(right);
14272
+ if (!a || !b)
14273
+ return;
14274
+ for (let i = 0;i < 3; i++) {
14275
+ if (a.core[i] !== b.core[i])
14276
+ return a.core[i] > b.core[i] ? 1 : -1;
14277
+ }
14278
+ if (!a.prerelease.length || !b.prerelease.length) {
14279
+ return a.prerelease.length === b.prerelease.length ? 0 : a.prerelease.length ? -1 : 1;
14280
+ }
14281
+ for (let i = 0;i < Math.max(a.prerelease.length, b.prerelease.length); i++) {
14282
+ const x = a.prerelease[i];
14283
+ const y = b.prerelease[i];
14284
+ if (x === y)
14285
+ continue;
14286
+ if (x === undefined)
14287
+ return -1;
14288
+ if (y === undefined)
14289
+ return 1;
14290
+ const xn = /^\d+$/.test(x);
14291
+ const yn = /^\d+$/.test(y);
14292
+ if (xn && yn)
14293
+ return BigInt(x) > BigInt(y) ? 1 : -1;
14294
+ if (xn !== yn)
14295
+ return xn ? -1 : 1;
14296
+ return x > y ? 1 : -1;
14297
+ }
14298
+ return 0;
14299
+ }
14300
+
14201
14301
  // src/setup/opencode-config.ts
14202
14302
  function openCodePluginKey(generation) {
14203
14303
  return generation === "v2" ? "plugins" : "plugin";
@@ -14215,6 +14315,24 @@ function openCodePluginReadKeys(generation) {
14215
14315
  function pinnedPluginEntry(version) {
14216
14316
  return `${AFT_OPENCODE_PACKAGE}@${version}`;
14217
14317
  }
14318
+ function preserveDoctorPluginEntry(cli, entry, installed) {
14319
+ const configured = entry ? exactPinnedVersion(entry) : null;
14320
+ const configuredOrder = configured ? compareSemver2(configured, cli) : undefined;
14321
+ const installedOrder = installed ? compareSemver2(installed, cli) : undefined;
14322
+ if (installed && (installedOrder === undefined || installedOrder > 0))
14323
+ return true;
14324
+ if (configured && (configuredOrder === undefined || configuredOrder > 0))
14325
+ return true;
14326
+ if (entry === AFT_OPENCODE_PACKAGE)
14327
+ return false;
14328
+ return Boolean(entry && configuredOrder === undefined && installedOrder === undefined);
14329
+ }
14330
+ function doctorNoDowngradeMessage(cli, entry, installed) {
14331
+ const configured = entry ? exactPinnedVersion(entry) : null;
14332
+ const newerInstalled = installed && (compareSemver2(installed, cli) ?? 1) > 0;
14333
+ const plugin = newerInstalled ? installed : (configured ?? entry?.slice(AFT_OPENCODE_PACKAGE.length + 1)) || "unknown";
14334
+ return DOCTOR_NO_DOWNGRADE.replace("<cli>", cli).replace("<plugin>", plugin);
14335
+ }
14218
14336
  function describePluginEntry(entry) {
14219
14337
  if (entry === AFT_OPENCODE_PACKAGE) {
14220
14338
  return "has no version, so OpenCode may load any release of the plugin";
@@ -14287,8 +14405,8 @@ function findLocalAftEntry(list, hasLocalAftEntry) {
14287
14405
  }
14288
14406
  return null;
14289
14407
  }
14290
- function ensurePinnedPluginConfig(value, version, hasLocalAftEntry = () => false, generation = "unknown") {
14291
- const entry = pinnedPluginEntry(version);
14408
+ function ensurePinnedPluginConfig(value, version, hasLocalAftEntry = () => false, generation = "unknown", policy = {}) {
14409
+ const entry = policy.fallbackEntry ?? pinnedPluginEntry(version);
14292
14410
  const key = openCodePluginKey(generation);
14293
14411
  const hadKey = Array.isArray(value[key]);
14294
14412
  const list = hadKey ? value[key] : [];
@@ -14302,6 +14420,21 @@ function ensurePinnedPluginConfig(value, version, hasLocalAftEntry = () => false
14302
14420
  }
14303
14421
  if (npmIndexes.length > 0) {
14304
14422
  const firstIndex = npmIndexes[0];
14423
+ if (policy.doctor && npmIndexes.some((index) => preserveDoctorPluginEntry(version, pluginEntryPackage(list[index]) ?? undefined, policy.installedVersion))) {
14424
+ let changed = false;
14425
+ for (const index of npmIndexes) {
14426
+ const spec = pluginEntryPackage(list[index]);
14427
+ const shaped = shapePluginEntry(key, spec, pluginEntryOptions(list[index]));
14428
+ changed ||= !sameEntry(list[index], shaped);
14429
+ list[index] = shaped;
14430
+ }
14431
+ return {
14432
+ action: changed ? "updated" : "already_present",
14433
+ changed,
14434
+ entry: pluginEntryPackage(list[firstIndex]),
14435
+ key
14436
+ };
14437
+ }
14305
14438
  const shaped = shapePluginEntry(key, entry, pluginEntryOptions(list[firstIndex]));
14306
14439
  const wasExact = sameEntry(list[firstIndex], shaped);
14307
14440
  list[firstIndex] = shaped;
@@ -14320,13 +14453,24 @@ function ensurePinnedPluginConfig(value, version, hasLocalAftEntry = () => false
14320
14453
  }
14321
14454
  const sibling = value[otherOpenCodePluginKey(key)];
14322
14455
  const siblingList = Array.isArray(sibling) ? sibling : null;
14456
+ if (policy.doctor && siblingList) {
14457
+ const preserved = siblingList.find((candidate) => {
14458
+ const spec = pluginEntryPackage(candidate);
14459
+ return spec !== null && isAftNpmEntry(spec) && preserveDoctorPluginEntry(version, spec, policy.installedVersion);
14460
+ });
14461
+ const spec = pluginEntryPackage(preserved);
14462
+ if (spec) {
14463
+ list.push(shapePluginEntry(key, spec, pluginEntryOptions(preserved)));
14464
+ return { action: "added", changed: true, entry: spec, key };
14465
+ }
14466
+ }
14323
14467
  const carried = siblingList ? findLocalAftEntry(siblingList, hasLocalAftEntry) : null;
14324
14468
  list.push(carried ? shapePluginEntry(key, carried.packageSpec, carried.options) : entry);
14325
14469
  if (carried && siblingList)
14326
14470
  siblingList.splice(carried.index, 1);
14327
14471
  return { action: "added", changed: true, entry, key };
14328
14472
  }
14329
- function pluginConfigNeedsUpdate(value, version, hasLocalAftEntry = () => false, generation = "unknown") {
14473
+ function pluginConfigNeedsUpdate(value, version, hasLocalAftEntry = () => false, generation = "unknown", policy = {}) {
14330
14474
  const key = openCodePluginKey(generation);
14331
14475
  const list = value[key];
14332
14476
  if (!Array.isArray(list))
@@ -14336,7 +14480,9 @@ function pluginConfigNeedsUpdate(value, version, hasLocalAftEntry = () => false,
14336
14480
  return packageSpec !== null && isAftNpmEntry(packageSpec);
14337
14481
  });
14338
14482
  if (npmEntries.length > 0) {
14339
- return npmEntries.length !== 1 || pluginEntryPackage(npmEntries[0]) !== pinnedPluginEntry(version) || !pluginEntryFitsKey(npmEntries[0], key);
14483
+ if (policy.doctor && npmEntries.some((candidate) => preserveDoctorPluginEntry(version, pluginEntryPackage(candidate) ?? undefined, policy.installedVersion)))
14484
+ return npmEntries.some((candidate) => !pluginEntryFitsKey(candidate, key));
14485
+ return npmEntries.length !== 1 || pluginEntryPackage(npmEntries[0]) !== (policy.fallbackEntry ?? pinnedPluginEntry(version)) || !pluginEntryFitsKey(npmEntries[0], key);
14340
14486
  }
14341
14487
  const local = findLocalAftEntry(list, hasLocalAftEntry);
14342
14488
  if (local)
@@ -14386,7 +14532,7 @@ function aftHostReplacementEnabled(userConfig, hostTool) {
14386
14532
  }
14387
14533
  return true;
14388
14534
  }
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;
14535
+ 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
14536
  var init_opencode_config = __esm(() => {
14391
14537
  init_opencode_version();
14392
14538
  MODERN_V1_VERSION = opencode_version_default.trim();
@@ -14398,29 +14544,138 @@ var init_opencode_config = __esm(() => {
14398
14544
  OPENCODE_HOST_TOOL_NAMES = Object.keys(OPENCODE_HOST_TOOLS);
14399
14545
  });
14400
14546
 
14547
+ // src/lib/opencode-plugin-cache.ts
14548
+ import { existsSync as existsSync14, readdirSync as readdirSync6, readFileSync as readFileSync10 } from "node:fs";
14549
+ import { dirname as dirname8, join as join18 } from "node:path";
14550
+ function discoverLocalOpenCodePlugin(path, cliVersion) {
14551
+ let current = path;
14552
+ for (;; ) {
14553
+ const manifestPath = join18(current, "package.json");
14554
+ try {
14555
+ const pkg = JSON.parse(readFileSync10(manifestPath, "utf8"));
14556
+ if (pkg.name === AFT_OPENCODE_PACKAGE && typeof pkg.version === "string") {
14557
+ return {
14558
+ path: current,
14559
+ manifestPath,
14560
+ cached: pkg.version,
14561
+ version: pkg.version,
14562
+ versionSource: "installed",
14563
+ exists: true,
14564
+ cliVersion,
14565
+ local: true
14566
+ };
14567
+ }
14568
+ } catch {}
14569
+ const parent = dirname8(current);
14570
+ if (parent === current)
14571
+ return;
14572
+ current = parent;
14573
+ }
14574
+ }
14575
+ function discoverOpenCodePluginCache(root, entry, generation, cliVersion, configuredEntry) {
14576
+ const v1 = join18(root, "packages", entry);
14577
+ const v2 = join18(root, "npm", entry === AFT_OPENCODE_PACKAGE ? `${entry}@latest` : entry);
14578
+ const paths = [];
14579
+ if (generation !== "v1") {
14580
+ try {
14581
+ const timestamps = readdirSync6(v2).filter((name) => /^\d+$/.test(name));
14582
+ timestamps.sort((a, b) => BigInt(a) > BigInt(b) ? -1 : BigInt(a) < BigInt(b) ? 1 : 0);
14583
+ if (timestamps[0])
14584
+ paths.push(join18(v2, timestamps[0]));
14585
+ } catch {}
14586
+ }
14587
+ if (generation === "v1" || generation !== "v2" && paths.length === 0)
14588
+ paths.push(v1);
14589
+ let readError;
14590
+ for (const path of paths) {
14591
+ const manifestPath = join18(path, "node_modules", AFT_OPENCODE_PACKAGE, "package.json");
14592
+ try {
14593
+ const pkg = JSON.parse(readFileSync10(manifestPath, "utf8"));
14594
+ if (pkg.name !== AFT_OPENCODE_PACKAGE || typeof pkg.version !== "string" || compareSemver2(pkg.version, pkg.version) !== 0) {
14595
+ readError = `Invalid plugin identity or version at ${manifestPath}`;
14596
+ continue;
14597
+ }
14598
+ if (typeof pkg.main === "string" && !existsSync14(join18(path, "node_modules", AFT_OPENCODE_PACKAGE, pkg.main))) {
14599
+ readError = `Incomplete plugin installation at ${manifestPath}`;
14600
+ continue;
14601
+ }
14602
+ return {
14603
+ path,
14604
+ manifestPath,
14605
+ cached: pkg.version,
14606
+ version: pkg.version,
14607
+ versionSource: "installed",
14608
+ configuredEntry,
14609
+ exists: true,
14610
+ cliVersion
14611
+ };
14612
+ } catch (error) {
14613
+ if (error.code !== "ENOENT" || existsSync14(path))
14614
+ readError = String(error);
14615
+ }
14616
+ }
14617
+ return {
14618
+ path: paths[0] ?? (generation === "v2" ? v2 : v1),
14619
+ cached: undefined,
14620
+ version: configuredEntry ? exactPinnedVersion(configuredEntry) ?? undefined : undefined,
14621
+ versionSource: configuredEntry && exactPinnedVersion(configuredEntry) ? "configured" : "unknown",
14622
+ readError,
14623
+ exists: false,
14624
+ configuredEntry,
14625
+ cliVersion
14626
+ };
14627
+ }
14628
+ function doctorInstalledVersion(cache) {
14629
+ return cache.cached ?? (cache.readError ? "unknown" : undefined);
14630
+ }
14631
+ function describePluginVersion(cache) {
14632
+ const version = cache.version ?? cache.cached;
14633
+ const source = cache.versionSource ?? (cache.cached ? "installed" : "unknown");
14634
+ return `${version ?? "unknown"} (${source}${cache.manifestPath ? `: ${cache.manifestPath}` : source === "configured" && cache.configuredEntry ? `: ${cache.configuredEntry}` : ""})`;
14635
+ }
14636
+ var init_opencode_plugin_cache = __esm(() => {
14637
+ init_opencode_config();
14638
+ });
14639
+
14640
+ // src/lib/self-version.ts
14641
+ import { createRequire as createRequire2 } from "node:module";
14642
+ function getSelfVersion() {
14643
+ const require2 = createRequire2(import.meta.url);
14644
+ for (const relPath of ["../../package.json", "../package.json"]) {
14645
+ try {
14646
+ const version = require2(relPath).version;
14647
+ if (typeof version === "string" && version.length > 0) {
14648
+ return version;
14649
+ }
14650
+ } catch {}
14651
+ }
14652
+ return "unknown";
14653
+ }
14654
+ var init_self_version = () => {};
14655
+
14401
14656
  // src/setup/host-generation.ts
14402
14657
  import { createHash as createHash4 } from "node:crypto";
14403
14658
  import {
14404
14659
  closeSync as closeSync7,
14405
- existsSync as existsSync14,
14660
+ existsSync as existsSync15,
14406
14661
  mkdirSync as mkdirSync5,
14407
14662
  mkdtempSync,
14408
14663
  openSync as openSync7,
14409
- readdirSync as readdirSync6,
14410
- readFileSync as readFileSync10,
14664
+ readdirSync as readdirSync7,
14665
+ readFileSync as readFileSync11,
14411
14666
  readSync as readSync5,
14412
14667
  realpathSync as realpathSync5,
14413
14668
  rmSync as rmSync4,
14414
14669
  statSync as statSync10
14415
14670
  } from "node:fs";
14416
14671
  import { homedir as homedir14, tmpdir as tmpdir3 } from "node:os";
14417
- import { dirname as dirname8, join as join18, parse as parse2 } from "node:path";
14672
+ import { dirname as dirname9, join as join19, parse as parse3 } from "node:path";
14418
14673
  function findExecutableOnPath2(name, pathValue = process.env.PATH ?? "", platform = process.platform) {
14419
14674
  return findExecutableOnPath(name, { pathValue, platform });
14420
14675
  }
14421
14676
  function packageMetadataAt(path, expectedName) {
14422
14677
  try {
14423
- const metadata = JSON.parse(readFileSync10(path, "utf8"));
14678
+ const metadata = JSON.parse(readFileSync11(path, "utf8"));
14424
14679
  return metadata.name === expectedName ? metadata : null;
14425
14680
  } catch {
14426
14681
  return null;
@@ -14429,16 +14684,16 @@ function packageMetadataAt(path, expectedName) {
14429
14684
  function packageMetadataNearExecutable(executable, expectedName) {
14430
14685
  let current;
14431
14686
  try {
14432
- current = dirname8(realpathSync5(executable));
14687
+ current = dirname9(realpathSync5(executable));
14433
14688
  } catch {
14434
- current = dirname8(executable);
14689
+ current = dirname9(executable);
14435
14690
  }
14436
14691
  while (true) {
14437
- const direct = packageMetadataAt(join18(current, "package.json"), expectedName);
14692
+ const direct = packageMetadataAt(join19(current, "package.json"), expectedName);
14438
14693
  if (direct)
14439
14694
  return direct;
14440
- const parent = dirname8(current);
14441
- if (parent === current || current === parse2(current).root)
14695
+ const parent = dirname9(current);
14696
+ if (parent === current || current === parse3(current).root)
14442
14697
  return null;
14443
14698
  current = parent;
14444
14699
  }
@@ -14448,7 +14703,7 @@ function npmLauncherTarget(launcher) {
14448
14703
  try {
14449
14704
  if (statSync10(launcher).size > LAUNCHER_MAX_BYTES)
14450
14705
  return null;
14451
- source = readFileSync10(launcher, "utf8");
14706
+ source = readFileSync11(launcher, "utf8");
14452
14707
  } catch {
14453
14708
  return null;
14454
14709
  }
@@ -14458,7 +14713,7 @@ function npmLauncherTarget(launcher) {
14458
14713
  const relativeTarget = operands.at(-1)?.[1];
14459
14714
  if (!relativeTarget)
14460
14715
  return null;
14461
- const target = join18(dirname8(launcher), ...relativeTarget.split(/[\\/]+/).filter(Boolean));
14716
+ const target = join19(dirname9(launcher), ...relativeTarget.split(/[\\/]+/).filter(Boolean));
14462
14717
  try {
14463
14718
  return statSync10(target).isFile() ? target : null;
14464
14719
  } catch {
@@ -14504,7 +14759,7 @@ function hashFileBytes(hash, path, size) {
14504
14759
  }
14505
14760
  }
14506
14761
  function snapshotTree(path) {
14507
- if (!existsSync14(path))
14762
+ if (!existsSync15(path))
14508
14763
  return "missing";
14509
14764
  const hash = createHash4("sha256");
14510
14765
  const visit = (current, relativePath) => {
@@ -14517,8 +14772,8 @@ function snapshotTree(path) {
14517
14772
  }
14518
14773
  hash.update(`${relativePath}\x00${info.mode}\x00${info.size}\x00${info.mtimeMs}\x00`);
14519
14774
  if (info.isDirectory()) {
14520
- for (const entry of readdirSync6(current).sort()) {
14521
- visit(join18(current, entry), join18(relativePath, entry));
14775
+ for (const entry of readdirSync7(current).sort()) {
14776
+ visit(join19(current, entry), join19(relativePath, entry));
14522
14777
  }
14523
14778
  return;
14524
14779
  }
@@ -14534,19 +14789,19 @@ function snapshotTree(path) {
14534
14789
  return hash.digest("hex");
14535
14790
  }
14536
14791
  function snapshotOperatorState(operatorHome) {
14537
- const database = join18(operatorHome, ".local", "share", "opencode", "opencode.db");
14792
+ const database = join19(operatorHome, ".local", "share", "opencode", "opencode.db");
14538
14793
  return JSON.stringify({
14539
14794
  database: snapshotTree(database),
14540
14795
  databaseWal: snapshotTree(`${database}-wal`),
14541
14796
  databaseShm: snapshotTree(`${database}-shm`),
14542
- logs: snapshotTree(join18(operatorHome, ".local", "share", "opencode", "log"))
14797
+ logs: snapshotTree(join19(operatorHome, ".local", "share", "opencode", "log"))
14543
14798
  });
14544
14799
  }
14545
14800
  function probeCreatedIsolatedState(dataRoot, stateRoot) {
14546
14801
  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);
14802
+ const opencodeRoot = join19(root, "opencode");
14803
+ const database = join19(opencodeRoot, "opencode.db");
14804
+ return [database, `${database}-wal`, `${database}-shm`, join19(opencodeRoot, "log")].some(existsSync15);
14550
14805
  });
14551
14806
  }
14552
14807
  function probeOpenCodeV1Version(executable, options = {}) {
@@ -14555,13 +14810,13 @@ function probeOpenCodeV1Version(executable, options = {}) {
14555
14810
  return null;
14556
14811
  const operatorHome = options.operatorHome ?? homedir14();
14557
14812
  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");
14813
+ const root = mkdtempSync(join19(options.tempParent ?? tmpdir3(), "aft-opencode-probe-"));
14814
+ const home = join19(root, "home");
14815
+ const config = join19(root, "config");
14816
+ const data = join19(root, "data");
14817
+ const state = join19(root, "state");
14818
+ const cache = join19(root, "cache");
14819
+ const project = join19(root, "project");
14565
14820
  for (const directory of [home, config, data, state, cache, project]) {
14566
14821
  mkdirSync5(directory, { recursive: true });
14567
14822
  }
@@ -14572,7 +14827,7 @@ function probeOpenCodeV1Version(executable, options = {}) {
14572
14827
  XDG_DATA_HOME: data,
14573
14828
  XDG_STATE_HOME: state,
14574
14829
  XDG_CACHE_HOME: cache,
14575
- TMPDIR: join18(root, "tmp")
14830
+ TMPDIR: join19(root, "tmp")
14576
14831
  };
14577
14832
  mkdirSync5(env.TMPDIR, { recursive: true });
14578
14833
  delete env.OPENCODE_CONFIG;
@@ -14713,9 +14968,9 @@ var init_host_generation = __esm(() => {
14713
14968
  });
14714
14969
 
14715
14970
  // src/adapters/opencode.ts
14716
- import { existsSync as existsSync15, readFileSync as readFileSync11, rmSync as rmSync5, statSync as statSync11 } from "node:fs";
14971
+ import { existsSync as existsSync16, readFileSync as readFileSync12, rmSync as rmSync5, statSync as statSync11 } from "node:fs";
14717
14972
  import { homedir as homedir15 } from "node:os";
14718
- import { dirname as dirname9, join as join19, parse as parse3, resolve as resolve12 } from "node:path";
14973
+ import { dirname as dirname10, join as join20, parse as parse4, resolve as resolve12 } from "node:path";
14719
14974
  import { fileURLToPath } from "node:url";
14720
14975
  function getOpenCodeConfigDir() {
14721
14976
  const envDir = process.env.OPENCODE_CONFIG_DIR?.trim();
@@ -14726,13 +14981,13 @@ function getOpenCodeConfigDir() {
14726
14981
  function getLegacyOpenCodePluginCachePath(primaryPath) {
14727
14982
  if (process.platform !== "win32")
14728
14983
  return null;
14729
- const localAppData = process.env.LOCALAPPDATA ?? join19(homedir15(), "AppData", "Local");
14730
- const legacyPath = join19(localAppData, "opencode", "packages", LEGACY_PLUGIN_ENTRY);
14984
+ const localAppData = process.env.LOCALAPPDATA ?? join20(homedir15(), "AppData", "Local");
14985
+ const legacyPath = join20(localAppData, "opencode", "packages", LEGACY_PLUGIN_ENTRY);
14731
14986
  return resolve12(legacyPath) === resolve12(primaryPath) ? null : legacyPath;
14732
14987
  }
14733
14988
  function clearLegacyOpenCodePluginCache(primaryPath) {
14734
14989
  const legacyPath = getLegacyOpenCodePluginCachePath(primaryPath);
14735
- if (!legacyPath || !existsSync15(legacyPath))
14990
+ if (!legacyPath || !existsSync16(legacyPath))
14736
14991
  return { clearedPath: null };
14737
14992
  try {
14738
14993
  rmSync5(legacyPath, { recursive: true, force: true });
@@ -14750,16 +15005,16 @@ function hasOpenCodeCli() {
14750
15005
  function openCodeDesktopAppExists() {
14751
15006
  const candidates = [];
14752
15007
  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"));
15008
+ candidates.push("/Applications/OpenCode.app", "/Applications/OpenCode Beta.app", join20(homedir15(), "Applications", "OpenCode.app"), join20(homedir15(), "Applications", "OpenCode Beta.app"));
14754
15009
  } 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"));
15010
+ const localAppData = process.env.LOCALAPPDATA ?? join20(homedir15(), "AppData", "Local");
15011
+ candidates.push(join20(localAppData, "Programs", "opencode"), join20(localAppData, "opencode"));
14757
15012
  } else {
14758
- candidates.push("/opt/OpenCode", "/usr/lib/opencode", join19(homedir15(), ".local", "share", "applications", "opencode.desktop"));
15013
+ candidates.push("/opt/OpenCode", "/usr/lib/opencode", join20(homedir15(), ".local", "share", "applications", "opencode.desktop"));
14759
15014
  }
14760
15015
  return candidates.some((p) => {
14761
15016
  try {
14762
- return existsSync15(p);
15017
+ return existsSync16(p);
14763
15018
  } catch {
14764
15019
  return false;
14765
15020
  }
@@ -14782,24 +15037,24 @@ function pathPointsToOurPlugin(entry) {
14782
15037
  if (!fsPath)
14783
15038
  return false;
14784
15039
  try {
14785
- if (!existsSync15(fsPath))
15040
+ if (!existsSync16(fsPath))
14786
15041
  return false;
14787
- let searchDir = statSync11(fsPath).isDirectory() ? fsPath : dirname9(fsPath);
15042
+ let searchDir = statSync11(fsPath).isDirectory() ? fsPath : dirname10(fsPath);
14788
15043
  let pkgJsonPath = null;
14789
15044
  while (true) {
14790
- const candidate = join19(searchDir, "package.json");
14791
- if (existsSync15(candidate)) {
15045
+ const candidate = join20(searchDir, "package.json");
15046
+ if (existsSync16(candidate)) {
14792
15047
  pkgJsonPath = candidate;
14793
15048
  break;
14794
15049
  }
14795
- const parent = dirname9(searchDir);
14796
- if (parent === searchDir || searchDir === parse3(searchDir).root)
15050
+ const parent = dirname10(searchDir);
15051
+ if (parent === searchDir || searchDir === parse4(searchDir).root)
14797
15052
  break;
14798
15053
  searchDir = parent;
14799
15054
  }
14800
15055
  if (!pkgJsonPath)
14801
15056
  return false;
14802
- const parsed = JSON.parse(readFileSync11(pkgJsonPath, "utf-8"));
15057
+ const parsed = JSON.parse(readFileSync12(pkgJsonPath, "utf-8"));
14803
15058
  return parsed.name === PLUGIN_NAME;
14804
15059
  } catch {
14805
15060
  return false;
@@ -14833,7 +15088,7 @@ class OpenCodeAdapter {
14833
15088
  pluginEntryWithVersion = PLUGIN_ENTRY;
14834
15089
  hostDetection;
14835
15090
  isInstalled() {
14836
- if (existsSync15(getOpenCodeConfigDir()))
15091
+ if (existsSync16(getOpenCodeConfigDir()))
14837
15092
  return true;
14838
15093
  if (openCodeDesktopAppExists())
14839
15094
  return true;
@@ -14857,7 +15112,7 @@ class OpenCodeAdapter {
14857
15112
  const configDir = getOpenCodeConfigDir();
14858
15113
  const harness = detectJsoncFile(configDir, "opencode");
14859
15114
  const aftConfigPath = resolveCortexKitUserConfigPath();
14860
- const aftConfigExists = existsSync15(aftConfigPath);
15115
+ const aftConfigExists = existsSync16(aftConfigPath);
14861
15116
  const tui = detectJsoncFile(configDir, "tui");
14862
15117
  return {
14863
15118
  configDir,
@@ -14874,13 +15129,32 @@ class OpenCodeAdapter {
14874
15129
  const { value } = readJsoncFile(paths.harnessConfig);
14875
15130
  return configRegistersAft(value, this.configGeneration());
14876
15131
  }
14877
- async ensurePluginEntry() {
15132
+ async ensurePluginEntry(options = {}) {
14878
15133
  const paths = this.detectConfigPaths();
14879
- return this.ensureConfigEntry(paths.harnessConfig, paths.harnessConfigFormat, "server config");
15134
+ return this.ensureConfigEntry(paths.harnessConfig, paths.harnessConfigFormat, "server config", options);
14880
15135
  }
14881
- needsPluginEntryUpdate() {
15136
+ needsPluginEntryUpdate(options = {}) {
14882
15137
  const paths = this.detectConfigPaths();
14883
- return this.configEntryNeedsUpdate(paths.harnessConfig, paths.harnessConfigFormat);
15138
+ return this.configEntryNeedsUpdate(paths.harnessConfig, paths.harnessConfigFormat, options);
15139
+ }
15140
+ doctorPluginEntry(tui = false) {
15141
+ const paths = this.detectConfigPaths();
15142
+ const path = tui ? paths.tuiConfig : paths.harnessConfig;
15143
+ const value = path ? readJsoncFile(path).value : null;
15144
+ const policy = this.doctorVersionPolicy({ doctor: true });
15145
+ if (!value)
15146
+ return policy.fallbackEntry ?? PLUGIN_ENTRY;
15147
+ return ensurePinnedPluginConfig(JSON.parse(JSON.stringify(value)), getSelfVersion(), pathPointsToOurPlugin, this.configGeneration(), policy).entry;
15148
+ }
15149
+ doctorVersionPolicy(policy) {
15150
+ if (!policy.doctor)
15151
+ return policy;
15152
+ const cache = this.getPluginCacheInfo();
15153
+ const installedVersion = doctorInstalledVersion(cache);
15154
+ const configuredVersion = cache.configuredEntry ? exactPinnedVersion(cache.configuredEntry) : null;
15155
+ const newer = configuredVersion && (compareSemver2(configuredVersion, getSelfVersion()) ?? 0) > 0 || cache.cached && (compareSemver2(cache.cached, getSelfVersion()) ?? 0) > 0;
15156
+ const fallbackEntry = newer ? cache.configuredEntry ?? (cache.cached ? pinnedPluginEntry(cache.cached) : undefined) : undefined;
15157
+ return { ...policy, installedVersion, fallbackEntry };
14884
15158
  }
14885
15159
  hasTuiPluginEntry() {
14886
15160
  const paths = this.detectConfigPaths();
@@ -14889,7 +15163,7 @@ class OpenCodeAdapter {
14889
15163
  const { value } = readJsoncFile(paths.tuiConfig);
14890
15164
  return configRegistersAft(value, this.configGeneration());
14891
15165
  }
14892
- async ensureTuiPluginEntry() {
15166
+ async ensureTuiPluginEntry(options = {}) {
14893
15167
  const paths = this.detectConfigPaths();
14894
15168
  if (this.configGeneration() === "v2") {
14895
15169
  return {
@@ -14907,28 +15181,30 @@ class OpenCodeAdapter {
14907
15181
  configPath: paths.configDir
14908
15182
  };
14909
15183
  }
14910
- return this.ensureConfigEntry(paths.tuiConfig, paths.tuiConfigFormat ?? "none", "TUI config");
15184
+ return this.ensureConfigEntry(paths.tuiConfig, paths.tuiConfigFormat ?? "none", "TUI config", options);
14911
15185
  }
14912
15186
  tuiPluginEntryApplies() {
14913
15187
  return this.configGeneration() !== "v2";
14914
15188
  }
14915
- needsTuiPluginEntryUpdate() {
15189
+ needsTuiPluginEntryUpdate(options = {}) {
14916
15190
  if (this.configGeneration() === "v2")
14917
15191
  return false;
14918
15192
  const paths = this.detectConfigPaths();
14919
- return paths.tuiConfig ? this.configEntryNeedsUpdate(paths.tuiConfig, paths.tuiConfigFormat ?? "none") : true;
15193
+ return paths.tuiConfig ? this.configEntryNeedsUpdate(paths.tuiConfig, paths.tuiConfigFormat ?? "none", options) : true;
14920
15194
  }
14921
- ensureConfigEntry(configPath, format, label) {
15195
+ ensureConfigEntry(configPath, format, label, policy = {}) {
14922
15196
  const generation = this.configGeneration();
14923
15197
  const key = openCodePluginKey(generation);
15198
+ policy = this.doctorVersionPolicy(policy);
15199
+ const initialEntry = policy.fallbackEntry ?? PLUGIN_ENTRY;
14924
15200
  if (format === "none") {
14925
- const failed = this.tryWrite(configPath, () => writeJsoncFile(configPath, { [key]: [PLUGIN_ENTRY] }, "json"));
15201
+ const failed = this.tryWrite(configPath, () => writeJsoncFile(configPath, { [key]: [initialEntry] }, "json"));
14926
15202
  if (failed)
14927
15203
  return failed;
14928
15204
  return {
14929
15205
  ok: true,
14930
15206
  action: "added",
14931
- message: `Created ${configPath} and added ${PLUGIN_ENTRY} under \`${key}\` (${label})`,
15207
+ message: `Created ${configPath} and added ${initialEntry} under \`${key}\` (${label})`,
14932
15208
  configPath
14933
15209
  };
14934
15210
  }
@@ -14941,12 +15217,14 @@ class OpenCodeAdapter {
14941
15217
  configPath
14942
15218
  };
14943
15219
  }
14944
- const update = ensurePinnedPluginConfig(value, getSelfVersion(), pathPointsToOurPlugin, generation);
15220
+ const update = ensurePinnedPluginConfig(value, getSelfVersion(), pathPointsToOurPlugin, generation, policy);
15221
+ const preserved = policy.doctor && preserveDoctorPluginEntry(getSelfVersion(), update.entry, doctorInstalledVersion(this.getPluginCacheInfo()));
15222
+ const notice = preserved ? doctorNoDowngradeMessage(getSelfVersion(), update.entry, doctorInstalledVersion(this.getPluginCacheInfo())) : "";
14945
15223
  if (!update.changed) {
14946
15224
  return {
14947
15225
  ok: true,
14948
15226
  action: "already_present",
14949
- message: `${PLUGIN_ENTRY} is already registered under \`${update.key}\` in ${configPath}`,
15227
+ message: notice || `${update.entry} is already registered under \`${update.key}\` in ${configPath}`,
14950
15228
  configPath
14951
15229
  };
14952
15230
  }
@@ -14956,7 +15234,7 @@ class OpenCodeAdapter {
14956
15234
  return {
14957
15235
  ok: true,
14958
15236
  action: update.action,
14959
- message: `${update.action === "added" ? "Added" : "Updated"} ${PLUGIN_ENTRY} under \`${update.key}\` in ${configPath} (${label})`,
15237
+ message: `${update.action === "added" ? "Added" : "Updated"} ${update.entry} under \`${update.key}\` in ${configPath} (${label})${notice ? `. ${notice}` : ""}`,
14960
15238
  configPath
14961
15239
  };
14962
15240
  }
@@ -15041,38 +15319,27 @@ class OpenCodeAdapter {
15041
15319
  return { ok: false, action: "error", message: formatFsError(error, configPath), configPath };
15042
15320
  }
15043
15321
  }
15044
- configEntryNeedsUpdate(configPath, format) {
15322
+ configEntryNeedsUpdate(configPath, format, policy = {}) {
15045
15323
  if (format === "none")
15046
15324
  return true;
15047
15325
  const { value, error } = readJsoncFile(configPath);
15048
15326
  if (error || !value)
15049
15327
  return false;
15050
- return pluginConfigNeedsUpdate(value, getSelfVersion(), pathPointsToOurPlugin, this.configGeneration());
15328
+ return pluginConfigNeedsUpdate(value, getSelfVersion(), pathPointsToOurPlugin, this.configGeneration(), this.doctorVersionPolicy(policy));
15051
15329
  }
15052
15330
  getPluginCacheInfo() {
15053
15331
  const configPath = this.detectConfigPaths().harnessConfig;
15054
15332
  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
- }
15333
+ const entries = openCodePluginReadKeys(this.configGeneration()).flatMap((key) => Array.isArray(value?.[key]) ? value[key] : []).map(pluginEntryPackage);
15334
+ const configuredEntry = entries.find(isAftNpmEntry);
15335
+ if (!configuredEntry) {
15336
+ const localEntry = entries.find((entry) => entry !== null && pathPointsToOurPlugin(entry));
15337
+ const localPath = localEntry ? pathFromEntry(localEntry) : null;
15338
+ const local = localPath ? discoverLocalOpenCodePlugin(localPath, getSelfVersion()) : undefined;
15339
+ if (local)
15340
+ return local;
15067
15341
  }
15068
- return {
15069
- path,
15070
- cached,
15071
- readError,
15072
- latest: getSelfVersion(),
15073
- exists: existsSync15(path),
15074
- configuredEntry
15075
- };
15342
+ return discoverOpenCodePluginCache(getOpenCodeCacheDir(), configuredEntry ?? PLUGIN_ENTRY, this.configGeneration(), getSelfVersion(), configuredEntry);
15076
15343
  }
15077
15344
  getStorageDir() {
15078
15345
  return getCortexKitStorageRoot();
@@ -15085,6 +15352,8 @@ class OpenCodeAdapter {
15085
15352
  }
15086
15353
  async clearPluginCache(force) {
15087
15354
  const info = this.getPluginCacheInfo();
15355
+ if (info.local)
15356
+ return { action: "not_applicable", path: info.path };
15088
15357
  const clearLegacy = force ? clearLegacyOpenCodePluginCache(info.path) : { clearedPath: null };
15089
15358
  if (clearLegacy.error) {
15090
15359
  return {
@@ -15101,12 +15370,12 @@ class OpenCodeAdapter {
15101
15370
  legacy_path_cleared: clearLegacy.clearedPath
15102
15371
  } : { action: "not_found", path: info.path };
15103
15372
  }
15104
- if (!force && info.cached && info.cached === info.latest) {
15373
+ if (!force && info.cached && info.cached === info.cliVersion) {
15105
15374
  return {
15106
15375
  action: "up_to_date",
15107
15376
  path: info.path,
15108
15377
  cached: info.cached,
15109
- latest: info.latest
15378
+ latest: info.cliVersion
15110
15379
  };
15111
15380
  }
15112
15381
  try {
@@ -15115,7 +15384,7 @@ class OpenCodeAdapter {
15115
15384
  action: "cleared",
15116
15385
  path: info.path,
15117
15386
  cached: info.cached,
15118
- latest: info.latest,
15387
+ latest: info.cliVersion,
15119
15388
  ...clearLegacy.clearedPath ? { legacy_path_cleared: clearLegacy.clearedPath } : {}
15120
15389
  };
15121
15390
  } catch (error) {
@@ -15123,7 +15392,7 @@ class OpenCodeAdapter {
15123
15392
  action: "error",
15124
15393
  path: info.path,
15125
15394
  cached: info.cached,
15126
- latest: info.latest,
15395
+ latest: info.cliVersion,
15127
15396
  error: error instanceof Error ? error.message : String(error)
15128
15397
  };
15129
15398
  }
@@ -15134,12 +15403,12 @@ class OpenCodeAdapter {
15134
15403
  describeStorageSubtrees() {
15135
15404
  const storage = this.getStorageDir();
15136
15405
  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"))
15406
+ index: dirSize(join20(storage, "index")),
15407
+ semantic: dirSize(join20(storage, "semantic")),
15408
+ backups: dirSize(join20(storage, "backups")),
15409
+ url_cache: dirSize(join20(storage, "url_cache")),
15410
+ onnxruntime: dirSize(join20(storage, "onnxruntime")),
15411
+ logs: dirSize(join20(storage, "logs"))
15143
15412
  };
15144
15413
  }
15145
15414
  }
@@ -15149,6 +15418,7 @@ var init_opencode = __esm(() => {
15149
15418
  init_fs_errors();
15150
15419
  init_fs_util();
15151
15420
  init_jsonc();
15421
+ init_opencode_plugin_cache();
15152
15422
  init_paths2();
15153
15423
  init_self_version();
15154
15424
  init_host_generation();
@@ -15159,9 +15429,9 @@ var init_opencode = __esm(() => {
15159
15429
  });
15160
15430
 
15161
15431
  // src/adapters/pi.ts
15162
- import { existsSync as existsSync16, readFileSync as readFileSync12 } from "node:fs";
15432
+ import { existsSync as existsSync17, readFileSync as readFileSync13 } from "node:fs";
15163
15433
  import { homedir as homedir16 } from "node:os";
15164
- import { isAbsolute as isAbsolute7, join as join20, resolve as resolve13 } from "node:path";
15434
+ import { isAbsolute as isAbsolute7, join as join21, resolve as resolve13 } from "node:path";
15165
15435
  import { fileURLToPath as fileURLToPath2 } from "node:url";
15166
15436
  function getPiAgentDir() {
15167
15437
  const configuredDir = process.env.PI_CODING_AGENT_DIR?.trim();
@@ -15169,13 +15439,13 @@ function getPiAgentDir() {
15169
15439
  return resolve13(configuredDir);
15170
15440
  const envHome = process.platform === "win32" ? process.env.USERPROFILE : process.env.HOME;
15171
15441
  const home = envHome && envHome.length > 0 ? envHome : homedir16();
15172
- return join20(home, ".pi", "agent");
15442
+ return join21(home, ".pi", "agent");
15173
15443
  }
15174
15444
  function readPiExtensionIndex() {
15175
- const settingsPath = join20(getPiAgentDir(), "settings.json");
15176
- if (existsSync16(settingsPath)) {
15445
+ const settingsPath = join21(getPiAgentDir(), "settings.json");
15446
+ if (existsSync17(settingsPath)) {
15177
15447
  try {
15178
- const raw = readFileSync12(settingsPath, "utf-8");
15448
+ const raw = readFileSync13(settingsPath, "utf-8");
15179
15449
  const trimmed = raw.replace(/^\uFEFF/, "");
15180
15450
  const value = JSON.parse(trimmed);
15181
15451
  const packages = value.packages;
@@ -15193,13 +15463,13 @@ function readPiExtensionIndex() {
15193
15463
  } catch {}
15194
15464
  }
15195
15465
  const candidates = [
15196
- join20(getPiAgentDir(), "extensions.json"),
15197
- join20(getPiAgentDir(), "extensions.jsonc"),
15198
- join20(getPiAgentDir(), "config.json"),
15199
- join20(getPiAgentDir(), "config.jsonc")
15466
+ join21(getPiAgentDir(), "extensions.json"),
15467
+ join21(getPiAgentDir(), "extensions.jsonc"),
15468
+ join21(getPiAgentDir(), "config.json"),
15469
+ join21(getPiAgentDir(), "config.jsonc")
15200
15470
  ];
15201
15471
  for (const path of candidates) {
15202
- if (!existsSync16(path))
15472
+ if (!existsSync17(path))
15203
15473
  continue;
15204
15474
  try {
15205
15475
  const { value } = readJsoncFile(path);
@@ -15231,17 +15501,17 @@ function piEntryMatchesAft(entry) {
15231
15501
  } else if (isAbsolute7(entry)) {
15232
15502
  resolved = entry;
15233
15503
  } else if (entry.length > 0) {
15234
- resolved = join20(getPiAgentDir(), entry);
15504
+ resolved = join21(getPiAgentDir(), entry);
15235
15505
  }
15236
15506
  if (!resolved)
15237
15507
  return false;
15238
15508
  try {
15239
- if (!existsSync16(resolved))
15509
+ if (!existsSync17(resolved))
15240
15510
  return false;
15241
- const pkgPath = join20(resolved, "package.json");
15242
- if (!existsSync16(pkgPath))
15511
+ const pkgPath = join21(resolved, "package.json");
15512
+ if (!existsSync17(pkgPath))
15243
15513
  return false;
15244
- const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
15514
+ const pkg = JSON.parse(readFileSync13(pkgPath, "utf-8"));
15245
15515
  return pkg.name === PLUGIN_NAME2;
15246
15516
  } catch {
15247
15517
  return false;
@@ -15287,10 +15557,10 @@ class PiAdapter {
15287
15557
  const configDir = getPiAgentDir();
15288
15558
  const index = readPiExtensionIndex();
15289
15559
  const aftConfigPath = resolveCortexKitUserConfigPath();
15290
- const aftConfigExists = existsSync16(aftConfigPath);
15560
+ const aftConfigExists = existsSync17(aftConfigPath);
15291
15561
  return {
15292
15562
  configDir,
15293
- harnessConfig: index.path ?? join20(configDir, "extensions.json"),
15563
+ harnessConfig: index.path ?? join21(configDir, "extensions.json"),
15294
15564
  harnessConfigFormat: index.path ? "json" : "none",
15295
15565
  aftConfig: aftConfigPath,
15296
15566
  aftConfigFormat: aftConfigExists ? "jsonc" : "none"
@@ -15338,14 +15608,14 @@ class PiAdapter {
15338
15608
  }
15339
15609
  getPluginCacheInfo() {
15340
15610
  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")
15611
+ join21(getPiAgentDir(), "npm", "node_modules", "@cortexkit", "aft-pi", "package.json"),
15612
+ join21(getPiAgentDir(), "node_modules", "@cortexkit", "aft-pi", "package.json"),
15613
+ join21(getPiAgentDir(), "extensions", "node_modules", "@cortexkit", "aft-pi", "package.json")
15344
15614
  ];
15345
15615
  let readError;
15346
15616
  for (const candidate of candidates) {
15347
15617
  try {
15348
- const pkg = JSON.parse(readFileSync12(candidate, "utf-8"));
15618
+ const pkg = JSON.parse(readFileSync13(candidate, "utf-8"));
15349
15619
  const cached = typeof pkg.version === "string" ? pkg.version : undefined;
15350
15620
  return {
15351
15621
  path: candidate,
@@ -15361,7 +15631,7 @@ class PiAdapter {
15361
15631
  }
15362
15632
  }
15363
15633
  return {
15364
- path: join20(getPiAgentDir(), "npm", "node_modules", "@cortexkit", "aft-pi", "package.json"),
15634
+ path: join21(getPiAgentDir(), "npm", "node_modules", "@cortexkit", "aft-pi", "package.json"),
15365
15635
  exists: readError !== undefined,
15366
15636
  readError
15367
15637
  };
@@ -15384,12 +15654,12 @@ class PiAdapter {
15384
15654
  describeStorageSubtrees() {
15385
15655
  const storage = this.getStorageDir();
15386
15656
  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"))
15657
+ index: dirSize(join21(storage, "index")),
15658
+ semantic: dirSize(join21(storage, "semantic")),
15659
+ backups: dirSize(join21(storage, "backups")),
15660
+ url_cache: dirSize(join21(storage, "url_cache")),
15661
+ onnxruntime: dirSize(join21(storage, "onnxruntime")),
15662
+ logs: dirSize(join21(storage, "logs"))
15393
15663
  };
15394
15664
  }
15395
15665
  }
@@ -16811,10 +17081,10 @@ var init_version = __esm(async () => {
16811
17081
  // src/lib/cli-logger.ts
16812
17082
  import { appendFileSync, mkdirSync as mkdirSync6 } from "node:fs";
16813
17083
  import { tmpdir as tmpdir4 } from "node:os";
16814
- import { dirname as dirname10, join as join21 } from "node:path";
17084
+ import { dirname as dirname11, join as join22 } from "node:path";
16815
17085
  function cliLogPath() {
16816
17086
  if (isTestEnvironment())
16817
- return join21(tmpdir4(), "aft-cli-test.log");
17087
+ return join22(tmpdir4(), "aft-cli-test.log");
16818
17088
  return resolveAftLogPath("aft-cli.log");
16819
17089
  }
16820
17090
  function write(level, message) {
@@ -16829,7 +17099,7 @@ function write(level, message) {
16829
17099
  }
16830
17100
  try {
16831
17101
  const path = cliLogPath();
16832
- mkdirSync6(dirname10(path), { recursive: true });
17102
+ mkdirSync6(dirname11(path), { recursive: true });
16833
17103
  appendFileSync(path, `${new Date().toISOString()} ${level.toUpperCase()} ${message}
16834
17104
  `);
16835
17105
  } catch {}
@@ -16866,13 +17136,13 @@ var init_cli_logger = __esm(() => {
16866
17136
  });
16867
17137
 
16868
17138
  // src/lib/binary-install.ts
16869
- import { existsSync as existsSync17 } from "node:fs";
16870
- import { join as join22 } from "node:path";
17139
+ import { existsSync as existsSync18 } from "node:fs";
17140
+ import { join as join23 } from "node:path";
16871
17141
  function cachedBinaryFor(version) {
16872
17142
  const tag = version.startsWith("v") ? version : `v${version}`;
16873
- const path = join22(getAftBinaryCacheDir(), tag, getAftBinaryName());
17143
+ const path = join23(getAftBinaryCacheDir(), tag, getAftBinaryName());
16874
17144
  try {
16875
- return existsSync17(path) ? path : null;
17145
+ return existsSync18(path) ? path : null;
16876
17146
  } catch {
16877
17147
  return null;
16878
17148
  }
@@ -17818,8 +18088,8 @@ var init_features = __esm(() => {
17818
18088
  });
17819
18089
 
17820
18090
  // 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";
18091
+ import { existsSync as existsSync19, readFileSync as readFileSync14 } from "node:fs";
18092
+ import { dirname as dirname12, join as join24 } from "node:path";
17823
18093
  import { fileURLToPath as fileURLToPath3 } from "node:url";
17824
18094
  function isLoadPath(value) {
17825
18095
  return OPENCODE_LOAD_PATHS.includes(value);
@@ -17882,7 +18152,7 @@ function configuredVersion(entry) {
17882
18152
  }
17883
18153
  function readManifest(path) {
17884
18154
  try {
17885
- const parsed = JSON.parse(readFileSync13(path, "utf8"));
18155
+ const parsed = JSON.parse(readFileSync14(path, "utf8"));
17886
18156
  return parsed.name === AFT_OPENCODE_PACKAGE ? parsed : null;
17887
18157
  } catch {
17888
18158
  return null;
@@ -17890,13 +18160,13 @@ function readManifest(path) {
17890
18160
  }
17891
18161
  function manifestFromLocalPath(path) {
17892
18162
  let current = path;
17893
- if (!existsSync18(join23(current, "package.json")))
17894
- current = dirname11(current);
18163
+ if (!existsSync19(join24(current, "package.json")))
18164
+ current = dirname12(current);
17895
18165
  while (true) {
17896
- const manifest = readManifest(join23(current, "package.json"));
18166
+ const manifest = readManifest(join24(current, "package.json"));
17897
18167
  if (manifest)
17898
18168
  return manifest;
17899
- const parent = dirname11(current);
18169
+ const parent = dirname12(current);
17900
18170
  if (parent === current)
17901
18171
  return null;
17902
18172
  current = parent;
@@ -17906,16 +18176,16 @@ function pluginManifest(entry, cachePath) {
17906
18176
  const localRoot = localPluginRoot(entry);
17907
18177
  if (localRoot)
17908
18178
  return manifestFromLocalPath(localRoot);
17909
- return readManifest(join23(cachePath, "node_modules", "@cortexkit", "aft-opencode", "package.json"));
18179
+ return readManifest(join24(cachePath, "node_modules", "@cortexkit", "aft-opencode", "package.json"));
17910
18180
  }
17911
18181
  function pinProblem(entry, pinned) {
17912
18182
  return `the plugin entry ${entry} ${describePluginEntry(entry)}; run \`${CLI} doctor --fix\` to pin it to ${pinned}`;
17913
18183
  }
17914
18184
  function latestLoggedLoadPath(logPath) {
17915
- if (!existsSync18(logPath))
18185
+ if (!existsSync19(logPath))
17916
18186
  return null;
17917
18187
  try {
17918
- const text = readFileSync13(logPath, "utf8");
18188
+ const text = readFileSync14(logPath, "utf8");
17919
18189
  const pattern = /(?:load path|load_path)\s*[:=]\s*(root-default|export-server-effect-bun|export-server-effect-node)/gi;
17920
18190
  let latest = null;
17921
18191
  for (const match of text.matchAll(pattern)) {
@@ -17955,7 +18225,12 @@ function diagnoseOpenCodeLoad(input) {
17955
18225
  const pluginVersion = (typeof manifest?.version === "string" ? manifest.version : null) ?? input.cachedPluginVersion ?? configuredVersion(entry) ?? null;
17956
18226
  const problems = [...describePluginKeyProblems(config, generation)];
17957
18227
  let entryPinProblem = null;
17958
- if (entry && isAftNpmEntry(entry) && input.expectedPluginEntry && entry !== input.expectedPluginEntry && !(input.acceptExplicitPluginVersion && acceptV1Entry(entry))) {
18228
+ let versionNotice;
18229
+ const cli = input.expectedPluginEntry ? exactPinnedVersion(input.expectedPluginEntry) : null;
18230
+ const installed = input.cachedPluginVersion ?? (typeof manifest?.version === "string" ? manifest.version : undefined);
18231
+ if (cli && entry && isAftNpmEntry(entry) && preserveDoctorPluginEntry(cli, entry, installed)) {
18232
+ versionNotice = doctorNoDowngradeMessage(cli, entry, installed);
18233
+ } else if (entry && isAftNpmEntry(entry) && input.expectedPluginEntry && entry !== input.expectedPluginEntry && !(input.acceptExplicitPluginVersion && acceptV1Entry(entry))) {
17959
18234
  entryPinProblem = pinProblem(entry, input.expectedPluginEntry);
17960
18235
  problems.push(entryPinProblem);
17961
18236
  }
@@ -17967,7 +18242,14 @@ function diagnoseOpenCodeLoad(input) {
17967
18242
  if (expectedLoadPath && takenLoadPath && expectedLoadPath !== takenLoadPath) {
17968
18243
  problems.push(`load path ${takenLoadPath} does not match ${expectedLoadPath} expected for the detected host`);
17969
18244
  }
17970
- return { expectedLoadPath, takenLoadPath, pluginVersion, problems, pinProblem: entryPinProblem };
18245
+ return {
18246
+ expectedLoadPath,
18247
+ takenLoadPath,
18248
+ pluginVersion,
18249
+ problems,
18250
+ pinProblem: entryPinProblem,
18251
+ versionNotice
18252
+ };
17971
18253
  }
17972
18254
  function describePluginKeyProblems(config, generation) {
17973
18255
  if (!config || generation === "ambiguous" || generation === "unknown")
@@ -18167,11 +18449,11 @@ var init_aft_bridge = __esm(() => {
18167
18449
  });
18168
18450
 
18169
18451
  // 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";
18452
+ import { existsSync as existsSync20, readdirSync as readdirSync8, statSync as statSync12 } from "node:fs";
18453
+ import { join as join25 } from "node:path";
18172
18454
  function getBinaryCacheInfo(activeVersion) {
18173
18455
  const path = getAftBinaryCacheDir();
18174
- if (!existsSync19(path)) {
18456
+ if (!existsSync20(path)) {
18175
18457
  return {
18176
18458
  versions: [],
18177
18459
  activeVersion: null,
@@ -18179,9 +18461,9 @@ function getBinaryCacheInfo(activeVersion) {
18179
18461
  path
18180
18462
  };
18181
18463
  }
18182
- const versions = readdirSync7(path).filter((entry) => {
18464
+ const versions = readdirSync8(path).filter((entry) => {
18183
18465
  try {
18184
- return statSync12(join24(path, entry)).isDirectory();
18466
+ return statSync12(join25(path, entry)).isDirectory();
18185
18467
  } catch {
18186
18468
  return false;
18187
18469
  }
@@ -18291,12 +18573,12 @@ var init_sanitize = __esm(() => {
18291
18573
  });
18292
18574
 
18293
18575
  // 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";
18576
+ import { closeSync as closeSync8, existsSync as existsSync21, openSync as openSync8, readSync as readSync6, statSync as statSync13 } from "node:fs";
18295
18577
  function resolveBridgePluginLogPath() {
18296
18578
  return resolvePluginLogPath();
18297
18579
  }
18298
18580
  function tailLogFileBytes(path, maxBytes) {
18299
- if (!existsSync20(path) || maxBytes <= 0)
18581
+ if (!existsSync21(path) || maxBytes <= 0)
18300
18582
  return "";
18301
18583
  let fd = null;
18302
18584
  try {
@@ -18432,9 +18714,9 @@ var init_bridge_tool_failures = __esm(() => {
18432
18714
  });
18433
18715
 
18434
18716
  // src/lib/build-breaker.ts
18435
- import { existsSync as existsSync21, readdirSync as readdirSync8 } from "node:fs";
18717
+ import { existsSync as existsSync22, readdirSync as readdirSync9 } from "node:fs";
18436
18718
  import { createRequire as createRequire3 } from "node:module";
18437
- import { join as join25 } from "node:path";
18719
+ import { join as join26 } from "node:path";
18438
18720
  function loadDatabaseSync() {
18439
18721
  const require2 = createRequire3(import.meta.url);
18440
18722
  try {
@@ -18448,11 +18730,11 @@ function loadDatabaseSync() {
18448
18730
  }
18449
18731
  }
18450
18732
  function buildBreakerDatabases(storageRoot) {
18451
- const callgraphRoot = join25(storageRoot, "callgraph");
18452
- if (!existsSync21(callgraphRoot))
18733
+ const callgraphRoot = join26(storageRoot, "callgraph");
18734
+ if (!existsSync22(callgraphRoot))
18453
18735
  return [];
18454
18736
  try {
18455
- return readdirSync8(callgraphRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join25(callgraphRoot, entry.name, "build-breaker.sqlite")).filter((path) => existsSync21(path));
18737
+ return readdirSync9(callgraphRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join26(callgraphRoot, entry.name, "build-breaker.sqlite")).filter((path) => existsSync22(path));
18456
18738
  } catch {
18457
18739
  return [];
18458
18740
  }
@@ -18528,15 +18810,15 @@ var init_build_breaker = __esm(() => {
18528
18810
  });
18529
18811
 
18530
18812
  // src/lib/config-migration.ts
18531
- import { existsSync as existsSync22, readFileSync as readFileSync14 } from "node:fs";
18813
+ import { existsSync as existsSync23, readFileSync as readFileSync15 } from "node:fs";
18532
18814
  function isRecord2(value) {
18533
18815
  return typeof value === "object" && value !== null && !Array.isArray(value);
18534
18816
  }
18535
18817
  function readPlainConfig(path) {
18536
- if (!existsSync22(path))
18818
+ if (!existsSync23(path))
18537
18819
  return null;
18538
18820
  try {
18539
- const plain = JSON.parse(JSON.stringify(import_comment_json2.parse(readFileSync14(path, "utf8"))));
18821
+ const plain = JSON.parse(JSON.stringify(import_comment_json2.parse(readFileSync15(path, "utf8"))));
18540
18822
  return isRecord2(plain) ? plain : null;
18541
18823
  } catch {
18542
18824
  return null;
@@ -18544,10 +18826,10 @@ function readPlainConfig(path) {
18544
18826
  }
18545
18827
  function configMigrationTargets(userConfigPath, cwd = process.cwd()) {
18546
18828
  const targets = [];
18547
- if (existsSync22(userConfigPath))
18829
+ if (existsSync23(userConfigPath))
18548
18830
  targets.push({ path: userConfigPath, tier: "user" });
18549
18831
  const project = resolveCortexKitProjectConfigPath(cwd);
18550
- if (project !== userConfigPath && existsSync22(project)) {
18832
+ if (project !== userConfigPath && existsSync23(project)) {
18551
18833
  targets.push({ path: project, tier: "project" });
18552
18834
  }
18553
18835
  return targets;
@@ -18589,8 +18871,9 @@ function previewConfigMigration(targets) {
18589
18871
  continue;
18590
18872
  const translated = structuredClone(raw);
18591
18873
  const translation = translateConfigDocument(translated, target.tier);
18592
- if (!translation.legacyInput)
18874
+ if (!translation.legacyInput && translation.removedKeys.length === 0)
18593
18875
  continue;
18876
+ dropRemovedDataLayerKeys(translated);
18594
18877
  const changes = diffConfig(raw, translated);
18595
18878
  previews.push({ ...target, changes });
18596
18879
  }
@@ -18612,21 +18895,27 @@ var init_config_migration = __esm(() => {
18612
18895
  import_comment_json2 = __toESM(require_src2(), 1);
18613
18896
  });
18614
18897
 
18898
+ // src/lib/doctor-labels.ts
18899
+ function describeCliBinary(version, path) {
18900
+ return CLI_BINARY_LABEL.replace("<version>", version ?? "unknown").replace("<path>", path ?? "unknown");
18901
+ }
18902
+ 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";
18903
+
18615
18904
  // 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";
18905
+ import { existsSync as existsSync24, readdirSync as readdirSync10, statSync as statSync14 } from "node:fs";
18906
+ import { join as join27 } from "node:path";
18618
18907
  function summarizeLegacyPartitionDuplication(storageRoot) {
18619
- if (!existsSync23(storageRoot)) {
18908
+ if (!existsSync24(storageRoot)) {
18620
18909
  return { totalPartitions: 0, totalBytes: 0, byHarness: [] };
18621
18910
  }
18622
18911
  const byHarness = [];
18623
18912
  for (const harness of safeReadDir(storageRoot)) {
18624
- const harnessPath = join26(storageRoot, harness);
18913
+ const harnessPath = join27(storageRoot, harness);
18625
18914
  if (!isDirectory(harnessPath))
18626
18915
  continue;
18627
18916
  const partitions = new Map;
18628
- collectCallgraphPartitions(join26(harnessPath, "callgraph"), partitions);
18629
- collectInspectPartitions(join26(harnessPath, "inspect"), partitions);
18917
+ collectCallgraphPartitions(join27(harnessPath, "callgraph"), partitions);
18918
+ collectInspectPartitions(join27(harnessPath, "inspect"), partitions);
18630
18919
  if (partitions.size === 0)
18631
18920
  continue;
18632
18921
  let bytes = 0;
@@ -18645,7 +18934,7 @@ function collectCallgraphPartitions(domainPath, partitions) {
18645
18934
  if (!isDirectory(domainPath))
18646
18935
  return;
18647
18936
  for (const name of safeReadDir(domainPath)) {
18648
- const path = join26(domainPath, name);
18937
+ const path = join27(domainPath, name);
18649
18938
  if (isDirectory(path)) {
18650
18939
  if (!looksLikePartitionKey(name))
18651
18940
  continue;
@@ -18662,7 +18951,7 @@ function collectInspectPartitions(domainPath, partitions) {
18662
18951
  if (!isDirectory(domainPath))
18663
18952
  return;
18664
18953
  for (const name of safeReadDir(domainPath)) {
18665
- const path = join26(domainPath, name);
18954
+ const path = join27(domainPath, name);
18666
18955
  if (isDirectory(path)) {
18667
18956
  if (!looksLikePartitionKey(name))
18668
18957
  continue;
@@ -18711,7 +19000,7 @@ function looksLikePartitionKey(value) {
18711
19000
  }
18712
19001
  function safeReadDir(path) {
18713
19002
  try {
18714
- return readdirSync9(path).sort((left, right) => left.localeCompare(right));
19003
+ return readdirSync10(path).sort((left, right) => left.localeCompare(right));
18715
19004
  } catch {
18716
19005
  return [];
18717
19006
  }
@@ -18737,22 +19026,22 @@ var init_legacy_storage = __esm(() => {
18737
19026
  });
18738
19027
 
18739
19028
  // 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";
19029
+ import { existsSync as existsSync25, readdirSync as readdirSync11, rmSync as rmSync6, statSync as statSync15 } from "node:fs";
19030
+ import { join as join28 } from "node:path";
18742
19031
  function inspectDir(path) {
18743
- if (!existsSync24(path)) {
19032
+ if (!existsSync25(path)) {
18744
19033
  return { entries: [], totalSize: 0 };
18745
19034
  }
18746
19035
  const entries = [];
18747
19036
  let totalSize = 0;
18748
19037
  let names;
18749
19038
  try {
18750
- names = readdirSync10(path);
19039
+ names = readdirSync11(path);
18751
19040
  } catch {
18752
19041
  return { entries: [], totalSize: 0 };
18753
19042
  }
18754
19043
  for (const name of names) {
18755
- const full = join27(path, name);
19044
+ const full = join28(path, name);
18756
19045
  try {
18757
19046
  if (!statSync15(full).isDirectory())
18758
19047
  continue;
@@ -18814,8 +19103,8 @@ var init_lsp_cache = __esm(() => {
18814
19103
  });
18815
19104
 
18816
19105
  // 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";
19106
+ import { existsSync as existsSync26, readdirSync as readdirSync12, readlinkSync as readlinkSync2, realpathSync as realpathSync7 } from "node:fs";
19107
+ import { basename as basename2, isAbsolute as isAbsolute8, join as join29, resolve as resolve14, win32 as win323 } from "node:path";
18819
19108
  function getOnnxLibraryName() {
18820
19109
  if (process.platform === "darwin")
18821
19110
  return "libonnxruntime.dylib";
@@ -18867,7 +19156,7 @@ function isWindowsSystem32Directory2(dir) {
18867
19156
  }
18868
19157
  function directoryContainsLibrary2(dir, libName) {
18869
19158
  try {
18870
- const entries = readdirSync11(dir);
19159
+ const entries = readdirSync12(dir);
18871
19160
  if (process.platform === "win32") {
18872
19161
  const expected = libName.toLowerCase();
18873
19162
  return entries.some((entry) => entry.toLowerCase() === expected);
@@ -18885,7 +19174,7 @@ function findIgnoredWindowsSystemOnnxRuntime() {
18885
19174
  for (const root of windowsRoots) {
18886
19175
  if (!root)
18887
19176
  continue;
18888
- const systemDir = join28(root, "System32");
19177
+ const systemDir = join29(root, "System32");
18889
19178
  const key = win323.resolve(systemDir).toLowerCase();
18890
19179
  if (seen.has(key))
18891
19180
  continue;
@@ -18907,21 +19196,21 @@ function inspectSystemOnnxRuntime(searchPathsOverride) {
18907
19196
  searchPaths.push(...pathEntriesForPlatform2());
18908
19197
  const programFiles = process.env.ProgramFiles ?? "C:\\Program Files";
18909
19198
  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"), ...(() => {
19199
+ searchPaths.push(join29(programFiles, "onnxruntime", "lib"), join29(programFiles, "Microsoft ONNX Runtime", "lib"), join29(programFiles, "Microsoft Machine Learning", "lib"), join29(programFilesX86, "onnxruntime", "lib"), ...(() => {
18911
19200
  const nugetPaths = [];
18912
19201
  const userProfile = process.env.USERPROFILE ?? "";
18913
19202
  if (!userProfile)
18914
19203
  return nugetPaths;
18915
- const nugetPackageDir = join28(userProfile, ".nuget", "packages", "microsoft.ml.onnxruntime");
18916
- if (!existsSync25(nugetPackageDir))
19204
+ const nugetPackageDir = join29(userProfile, ".nuget", "packages", "microsoft.ml.onnxruntime");
19205
+ if (!existsSync26(nugetPackageDir))
18917
19206
  return nugetPaths;
18918
19207
  try {
18919
- for (const entry of readdirSync11(nugetPackageDir, { withFileTypes: true })) {
19208
+ for (const entry of readdirSync12(nugetPackageDir, { withFileTypes: true })) {
18920
19209
  if (!entry.isDirectory())
18921
19210
  continue;
18922
19211
  if (entry.name === "__globalPackagesFolder" || entry.name.startsWith("."))
18923
19212
  continue;
18924
- nugetPaths.push(join28(nugetPackageDir, entry.name, "runtimes", "win-x64", "native"), join28(nugetPackageDir, entry.name, "runtimes", "win-arm64", "native"));
19213
+ nugetPaths.push(join29(nugetPackageDir, entry.name, "runtimes", "win-x64", "native"), join29(nugetPackageDir, entry.name, "runtimes", "win-arm64", "native"));
18925
19214
  }
18926
19215
  } catch {}
18927
19216
  return nugetPaths;
@@ -18940,7 +19229,7 @@ function inspectSystemOnnxRuntime(searchPathsOverride) {
18940
19229
  if (!directoryContainsLibrary2(dir, libName))
18941
19230
  continue;
18942
19231
  if (process.platform !== "win32") {
18943
- const probe = probeOnnxRuntimeLoadable(join28(dir, libName));
19232
+ const probe = probeOnnxRuntimeLoadable(join29(dir, libName));
18944
19233
  if (!probe.loadable) {
18945
19234
  ignored ??= { path: dir, reason: `unloadable: ${probe.reason} — ignored` };
18946
19235
  continue;
@@ -18960,12 +19249,12 @@ function inspectSystemOnnxRuntime(searchPathsOverride) {
18960
19249
  return { path: unknownVersionPaths[0] ?? null, ignored };
18961
19250
  }
18962
19251
  function findCachedOnnxRuntime2(storageDir) {
18963
- const ortDir = join28(storageDir, "onnxruntime", ONNX_RUNTIME_VERSION);
19252
+ const ortDir = join29(storageDir, "onnxruntime", ONNX_RUNTIME_VERSION);
18964
19253
  const libName = getOnnxLibraryName();
18965
- if (existsSync25(join28(ortDir, libName)))
19254
+ if (existsSync26(join29(ortDir, libName)))
18966
19255
  return ortDir;
18967
- const libSubdir = join28(ortDir, "lib");
18968
- if (existsSync25(join28(libSubdir, libName)))
19256
+ const libSubdir = join29(ortDir, "lib");
19257
+ if (existsSync26(join29(libSubdir, libName)))
18969
19258
  return libSubdir;
18970
19259
  return null;
18971
19260
  }
@@ -18986,16 +19275,16 @@ function parseOrtVersionFromDirectoryPath(value) {
18986
19275
  return null;
18987
19276
  }
18988
19277
  function detectOrtVersion(libDir) {
18989
- if (!existsSync25(libDir))
19278
+ if (!existsSync26(libDir))
18990
19279
  return null;
18991
19280
  const libName = getOnnxLibraryName();
18992
19281
  try {
18993
- const resolved = parseOrtVersionFromPath(realpathSync7(join28(libDir, libName)));
19282
+ const resolved = parseOrtVersionFromPath(realpathSync7(join29(libDir, libName)));
18994
19283
  if (resolved && resolved !== INVALID_ORT_VERSION2)
18995
19284
  return resolved;
18996
19285
  } catch {}
18997
19286
  try {
18998
- const entries = readdirSync11(libDir);
19287
+ const entries = readdirSync12(libDir);
18999
19288
  const barePrefix = libName.replace(/\.(so|dylib|dll)$/, "");
19000
19289
  const expectedPrefix = process.platform === "win32" ? barePrefix.toLowerCase() : barePrefix;
19001
19290
  for (const entry of entries) {
@@ -19006,8 +19295,8 @@ function detectOrtVersion(libDir) {
19006
19295
  if (version)
19007
19296
  return version;
19008
19297
  }
19009
- const base = join28(libDir, libName);
19010
- if (existsSync25(base)) {
19298
+ const base = join29(libDir, libName);
19299
+ if (existsSync26(base)) {
19011
19300
  try {
19012
19301
  const real = realpathSync7(base);
19013
19302
  const version = parseOrtVersionFromPath(real) ?? parseOrtVersionFromDirectoryPath(real);
@@ -19040,17 +19329,17 @@ var init_onnx = __esm(() => {
19040
19329
  });
19041
19330
 
19042
19331
  // src/lib/plugin-load.ts
19043
- import { existsSync as existsSync26, readFileSync as readFileSync15 } from "node:fs";
19332
+ import { existsSync as existsSync27, readFileSync as readFileSync16 } from "node:fs";
19044
19333
  import { homedir as homedir18 } from "node:os";
19045
- import { isAbsolute as isAbsolute9, join as join29, normalize } from "node:path";
19334
+ import { isAbsolute as isAbsolute9, join as join30, normalize } from "node:path";
19046
19335
  function isRecord3(value) {
19047
19336
  return typeof value === "object" && value !== null && !Array.isArray(value);
19048
19337
  }
19049
19338
  function readConfig(path) {
19050
- if (!existsSync26(path))
19339
+ if (!existsSync27(path))
19051
19340
  return { value: null, error: null };
19052
19341
  try {
19053
- const parsed = import_comment_json3.parse(readFileSync15(path, "utf8"));
19342
+ const parsed = import_comment_json3.parse(readFileSync16(path, "utf8"));
19054
19343
  const plain = JSON.parse(JSON.stringify(parsed));
19055
19344
  return isRecord3(plain) ? { value: plain, error: null } : { value: null, error: "the file is not a JSON object" };
19056
19345
  } catch (error) {
@@ -19106,7 +19395,7 @@ function evaluatePluginLoad(input) {
19106
19395
  const raw = typeof subc?.connection_file === "string" ? subc.connection_file.trim() : "";
19107
19396
  if (raw.length > 0) {
19108
19397
  const resolved = resolveSubcConnectionPath(raw, input.home);
19109
- if (!existsSync26(resolved)) {
19398
+ if (!existsSync27(resolved)) {
19110
19399
  const toolCallError = formatConfigErrorMessage(formatSubcConnectionMissingMessage(raw, resolved));
19111
19400
  blockers.push({
19112
19401
  code: "subc_connection_missing",
@@ -19125,7 +19414,7 @@ function evaluatePluginLoad(input) {
19125
19414
  const storage = loaded.user && isRecord3(loaded.user.storage) ? loaded.user.storage : null;
19126
19415
  const requested = typeof storage?.cache_dir === "string" ? storage.cache_dir : "";
19127
19416
  const home = input.home ?? homedir18();
19128
- const expanded = requested === "~" ? home : requested.startsWith("~/") || requested.startsWith("~\\") ? join29(home, requested.slice(2)) : requested;
19417
+ const expanded = requested === "~" ? home : requested.startsWith("~/") || requested.startsWith("~\\") ? join30(home, requested.slice(2)) : requested;
19129
19418
  return {
19130
19419
  ...isAbsolute9(expanded) ? { cacheDir: normalize(expanded) } : {},
19131
19420
  blockers,
@@ -19145,15 +19434,16 @@ import {
19145
19434
  accessSync as accessSync3,
19146
19435
  closeSync as closeSync9,
19147
19436
  constants as constants5,
19148
- existsSync as existsSync27,
19437
+ existsSync as existsSync28,
19149
19438
  openSync as openSync9,
19150
19439
  readSync as readSync7,
19151
19440
  statSync as statSync16
19152
19441
  } from "node:fs";
19153
- import { join as join30 } from "node:path";
19442
+ import { join as join31 } from "node:path";
19154
19443
  async function collectDiagnostics(adapters) {
19155
19444
  const cliVersion = getSelfVersion();
19156
- const binaryVersion = probeBinaryVersion(cliVersion);
19445
+ const binaryProbe = probeAftBinary(cliVersion);
19446
+ const binaryVersion = binaryProbe.version;
19157
19447
  const harnesses = [];
19158
19448
  for (const adapter of adapters) {
19159
19449
  harnesses.push(await diagnoseHarness(adapter));
@@ -19165,6 +19455,7 @@ async function collectDiagnostics(adapters) {
19165
19455
  nodeVersion: process.version,
19166
19456
  cliVersion,
19167
19457
  binaryVersion,
19458
+ binaryPath: binaryProbe.path,
19168
19459
  harnesses,
19169
19460
  binaryCache: getBinaryCacheInfo(cliVersion),
19170
19461
  lspCache: getLspCacheReport(),
@@ -19185,7 +19476,7 @@ async function diagnoseHarness(adapter) {
19185
19476
  const logPath = adapter.getLogFile();
19186
19477
  const pluginCache = adapter.getPluginCacheInfo();
19187
19478
  const storageAccessible = (() => {
19188
- if (!existsSync27(storage))
19479
+ if (!existsSync28(storage))
19189
19480
  return false;
19190
19481
  try {
19191
19482
  accessSync3(storage, constants5.R_OK | constants5.W_OK);
@@ -19220,7 +19511,7 @@ async function diagnoseHarness(adapter) {
19220
19511
  pluginRegistered: adapter.hasPluginEntry(),
19221
19512
  configPaths,
19222
19513
  aftConfig: {
19223
- exists: existsSync27(configPaths.aftConfig),
19514
+ exists: existsSync28(configPaths.aftConfig),
19224
19515
  ...aftConfigRead.error ? { parseError: aftConfigRead.error } : {},
19225
19516
  enabled: aftEnabled,
19226
19517
  ...aftEnabledSource ? { enabledSource: aftEnabledSource } : {},
@@ -19229,10 +19520,10 @@ async function diagnoseHarness(adapter) {
19229
19520
  pluginCache,
19230
19521
  storageDir: {
19231
19522
  path: storage,
19232
- exists: existsSync27(storage),
19523
+ exists: existsSync28(storage),
19233
19524
  accessible: storageAccessible,
19234
19525
  sizesByKey: describeStorage,
19235
- ...pluginLoad.cacheDir ? { cacheRoot: join30(pluginLoad.cacheDir, "aft") } : {},
19526
+ ...pluginLoad.cacheDir ? { cacheRoot: join31(pluginLoad.cacheDir, "aft") } : {},
19236
19527
  ...legacyDuplication.totalPartitions > 0 ? { legacyDuplication } : {}
19237
19528
  },
19238
19529
  onnxRuntime: {
@@ -19252,8 +19543,8 @@ async function diagnoseHarness(adapter) {
19252
19543
  },
19253
19544
  logFile: {
19254
19545
  path: logPath,
19255
- exists: existsSync27(logPath),
19256
- sizeKb: existsSync27(logPath) ? Math.round(statSync16(logPath).size / 1024) : 0
19546
+ exists: existsSync28(logPath),
19547
+ sizeKb: existsSync28(logPath) ? Math.round(statSync16(logPath).size / 1024) : 0
19257
19548
  },
19258
19549
  pluginLoad
19259
19550
  };
@@ -19262,7 +19553,7 @@ function renderDiagnosticsMarkdown(report) {
19262
19553
  const lines = [];
19263
19554
  lines.push(`- Timestamp: ${report.timestamp}`);
19264
19555
  lines.push(`- AFT CLI: v${report.cliVersion}`);
19265
- lines.push(`- AFT binary: ${report.binaryVersion ?? "unknown"}`);
19556
+ lines.push(`- ${describeCliBinary(report.binaryVersion, report.binaryPath)}`);
19266
19557
  lines.push(`- OS: ${report.platform} ${report.arch}`);
19267
19558
  lines.push(`- Node: ${report.nodeVersion}`);
19268
19559
  const issues = collectDiagnosticIssues(report);
@@ -19279,7 +19570,7 @@ function renderDiagnosticsMarkdown(report) {
19279
19570
  lines.push(`- Host installed: ${h.hostInstalled}`);
19280
19571
  lines.push(`- Host version: ${h.hostVersion ?? "unknown"}`);
19281
19572
  lines.push(`- Plugin registered: ${h.pluginRegistered}`);
19282
- lines.push(`- Plugin version: ${h.pluginCache.cached ?? "not installed"}`);
19573
+ lines.push(`- Plugin version: ${describePluginVersion(h.pluginCache)}`);
19283
19574
  lines.push(`- AFT enabled: ${h.aftConfig.enabled}${h.aftConfig.enabledSource ? ` (from ${h.aftConfig.enabledSource})` : ""}`);
19284
19575
  lines.push(`- AFT config parse error: ${h.aftConfig.parseError ?? "none"}`);
19285
19576
  lines.push("");
@@ -19314,7 +19605,8 @@ function renderDiagnosticsMarkdown(report) {
19314
19605
  lines.push("");
19315
19606
  lines.push("### Binary cache");
19316
19607
  lines.push("```json");
19317
- lines.push(JSON.stringify(report.binaryCache, null, 2));
19608
+ const { activeVersion, ...cacheInventory } = report.binaryCache;
19609
+ lines.push(JSON.stringify({ ...cacheInventory, [CLI_VERSION_CACHED_LABEL]: activeVersion }, null, 2));
19318
19610
  lines.push("```");
19319
19611
  lines.push("");
19320
19612
  lines.push("### LSP cache");
@@ -19347,6 +19639,8 @@ function openCodeEntryToPin(harness, cliVersion) {
19347
19639
  if (harness.kind !== "opencode")
19348
19640
  return null;
19349
19641
  const entry = harness.pluginCache.configuredEntry;
19642
+ if (preserveDoctorPluginEntry(normalizeVersion(cliVersion), entry, doctorInstalledVersion(harness.pluginCache)))
19643
+ return null;
19350
19644
  return entry && entry !== pinnedPluginEntry(normalizeVersion(cliVersion)) ? entry : null;
19351
19645
  }
19352
19646
  function pluginVersionSkewRemediation(harness, cliVersion, older) {
@@ -19362,12 +19656,23 @@ function pluginVersionSkewRemediation(harness, cliVersion, older) {
19362
19656
  return entry ? `Run \`${CLI} doctor --fix\` to pin the plugin entry to ${pinnedPluginEntry(version)} and ${change}.` : `Run \`${CLI} doctor --fix\` to ${change}.`;
19363
19657
  }
19364
19658
  function pluginVersionSkewIssue(harness, cliVersion) {
19659
+ if (harness.pluginCache.local)
19660
+ return null;
19661
+ if (harness.kind === "opencode" && preserveDoctorPluginEntry(normalizeVersion(cliVersion), harness.pluginCache.configuredEntry, doctorInstalledVersion(harness.pluginCache))) {
19662
+ return {
19663
+ code: "plugin_cli_version_skew",
19664
+ severity: "info",
19665
+ scope: harness.displayName,
19666
+ message: doctorNoDowngradeMessage(normalizeVersion(cliVersion), harness.pluginCache.configuredEntry, doctorInstalledVersion(harness.pluginCache)),
19667
+ remediation: ""
19668
+ };
19669
+ }
19365
19670
  const pluginVersion = harness.pluginCache.cached;
19366
19671
  if (!pluginVersion)
19367
19672
  return null;
19368
19673
  if (normalizeVersion(pluginVersion) === normalizeVersion(cliVersion))
19369
19674
  return null;
19370
- const older = compareLooseSemver(pluginVersion, cliVersion) < 0;
19675
+ const older = (compareSemver2(normalizeVersion(pluginVersion), normalizeVersion(cliVersion)) ?? compareLooseSemver(pluginVersion, cliVersion)) < 0;
19371
19676
  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
19677
  const entry = openCodeEntryToPin(harness, cliVersion);
19373
19678
  return {
@@ -19484,7 +19789,7 @@ function formatDiagnosticIssuesSection(report) {
19484
19789
  return lines;
19485
19790
  }
19486
19791
  function tailLogFile(path, lines) {
19487
- if (!existsSync27(path))
19792
+ if (!existsSync28(path))
19488
19793
  return "";
19489
19794
  if (lines <= 0)
19490
19795
  return "";
@@ -19529,6 +19834,7 @@ var init_diagnostics = __esm(async () => {
19529
19834
  init_legacy_storage();
19530
19835
  init_lsp_cache();
19531
19836
  init_onnx();
19837
+ init_opencode_plugin_cache();
19532
19838
  init_plugin_load();
19533
19839
  init_sanitize();
19534
19840
  init_self_version();
@@ -19684,14 +19990,14 @@ var init_issue_body = __esm(() => {
19684
19990
 
19685
19991
  // src/lib/onnx-fix.ts
19686
19992
  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";
19993
+ import { existsSync as existsSync29, mkdirSync as mkdirSync7, renameSync as renameSync6, rmSync as rmSync7 } from "node:fs";
19994
+ import { isAbsolute as isAbsolute10, join as join32, relative as relative4 } from "node:path";
19689
19995
  function findOnnxFixCandidates(report) {
19690
19996
  const candidates = [];
19691
19997
  for (const harness of report.harnesses) {
19692
19998
  if (!harness.onnxRuntime.required)
19693
19999
  continue;
19694
- const storageOnnxDir = join31(harness.storageDir.path, "onnxruntime");
20000
+ const storageOnnxDir = join32(harness.storageDir.path, "onnxruntime");
19695
20001
  const systemTooOld = harness.onnxRuntime.systemPath !== null && harness.onnxRuntime.systemCompatible === false;
19696
20002
  const cachedTooOld = harness.onnxRuntime.cachedPath !== null && harness.onnxRuntime.cachedCompatible === false;
19697
20003
  const hasCompatibleCached = harness.onnxRuntime.cachedCompatible === true;
@@ -19700,7 +20006,7 @@ function findOnnxFixCandidates(report) {
19700
20006
  harness,
19701
20007
  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
20008
  storageOnnxDir,
19703
- storageOnnxBytes: existsSync28(storageOnnxDir) ? dirSize(storageOnnxDir) : 0
20009
+ storageOnnxBytes: existsSync29(storageOnnxDir) ? dirSize(storageOnnxDir) : 0
19704
20010
  });
19705
20011
  continue;
19706
20012
  }
@@ -19709,7 +20015,7 @@ function findOnnxFixCandidates(report) {
19709
20015
  harness,
19710
20016
  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
20017
  storageOnnxDir,
19712
- storageOnnxBytes: existsSync28(storageOnnxDir) ? dirSize(storageOnnxDir) : 0
20018
+ storageOnnxBytes: existsSync29(storageOnnxDir) ? dirSize(storageOnnxDir) : 0
19713
20019
  });
19714
20020
  continue;
19715
20021
  }
@@ -19721,7 +20027,7 @@ function findOnnxFixCandidates(report) {
19721
20027
  harness,
19722
20028
  reason: `no compatible ONNX Runtime is installed.${ignoredCopy} AFT will download v1.24 into managed storage.`,
19723
20029
  storageOnnxDir,
19724
- storageOnnxBytes: existsSync28(storageOnnxDir) ? dirSize(storageOnnxDir) : 0
20030
+ storageOnnxBytes: existsSync29(storageOnnxDir) ? dirSize(storageOnnxDir) : 0
19725
20031
  });
19726
20032
  }
19727
20033
  }
@@ -19730,11 +20036,11 @@ function findOnnxFixCandidates(report) {
19730
20036
  async function installManagedReplacement(candidate, ensureFn, rmFn) {
19731
20037
  const suffix = `${process.pid}.${Date.now().toString(36)}.${randomBytes2(6).toString("hex")}`;
19732
20038
  const stagingStorageDir = `${candidate.harness.storageDir.path}.onnx-fix.tmp.${suffix}`;
19733
- const stagedOnnxDir = join31(stagingStorageDir, "onnxruntime");
20039
+ const stagedOnnxDir = join32(stagingStorageDir, "onnxruntime");
19734
20040
  const backupDir = `${candidate.storageOnnxDir}.backup.${suffix}`;
19735
20041
  try {
19736
20042
  const stagedInstalledPath = await ensureFn(stagingStorageDir);
19737
- if (!stagedInstalledPath || !existsSync28(stagedInstalledPath)) {
20043
+ if (!stagedInstalledPath || !existsSync29(stagedInstalledPath)) {
19738
20044
  throw new Error("managed ONNX Runtime download was unavailable");
19739
20045
  }
19740
20046
  const installedRelativePath = relative4(stagedOnnxDir, stagedInstalledPath);
@@ -19742,7 +20048,7 @@ async function installManagedReplacement(candidate, ensureFn, rmFn) {
19742
20048
  throw new Error(`managed ONNX Runtime was installed outside staging: ${stagedInstalledPath}`);
19743
20049
  }
19744
20050
  mkdirSync7(candidate.harness.storageDir.path, { recursive: true });
19745
- const hadPrevious = existsSync28(candidate.storageOnnxDir);
20051
+ const hadPrevious = existsSync29(candidate.storageOnnxDir);
19746
20052
  if (hadPrevious)
19747
20053
  renameSync6(candidate.storageOnnxDir, backupDir);
19748
20054
  try {
@@ -19766,7 +20072,7 @@ async function installManagedReplacement(candidate, ensureFn, rmFn) {
19766
20072
  }
19767
20073
  }
19768
20074
  return {
19769
- installedPath: join31(candidate.storageOnnxDir, installedRelativePath),
20075
+ installedPath: join32(candidate.storageOnnxDir, installedRelativePath),
19770
20076
  clearedPrevious: hadPrevious && cleanupError === undefined,
19771
20077
  cleanupError
19772
20078
  };
@@ -19915,10 +20221,10 @@ var init_session_log = __esm(() => {
19915
20221
  });
19916
20222
 
19917
20223
  // src/lib/sessions.ts
19918
- import { existsSync as existsSync29, readdirSync as readdirSync12, readFileSync as readFileSync16, statSync as statSync18 } from "node:fs";
20224
+ import { existsSync as existsSync30, readdirSync as readdirSync13, readFileSync as readFileSync17, statSync as statSync18 } from "node:fs";
19919
20225
  import { createRequire as createRequire4 } from "node:module";
19920
20226
  import { homedir as homedir19 } from "node:os";
19921
- import { basename as basename3, join as join32 } from "node:path";
20227
+ import { basename as basename3, join as join33 } from "node:path";
19922
20228
  function listRecentSessions(adapter) {
19923
20229
  try {
19924
20230
  if (adapter.kind === "opencode")
@@ -19952,8 +20258,8 @@ function mapOpenCodeSessionRows(rows) {
19952
20258
  }).filter((session) => session !== null).sort((a, b) => b.lastActivity - a.lastActivity).slice(0, MAX_RECENT_SESSIONS);
19953
20259
  }
19954
20260
  function listRecentOpenCodeSessions() {
19955
- const dbPath = join32(getXdgDataHome(), "opencode", "opencode.db");
19956
- if (!existsSync29(dbPath))
20261
+ const dbPath = join33(getXdgDataHome(), "opencode", "opencode.db");
20262
+ if (!existsSync30(dbPath))
19957
20263
  return [];
19958
20264
  let db = null;
19959
20265
  try {
@@ -19972,10 +20278,10 @@ function listRecentOpenCodeSessions() {
19972
20278
  }
19973
20279
  function getXdgDataHome() {
19974
20280
  const xdgDataHome = process.env.XDG_DATA_HOME;
19975
- return xdgDataHome && xdgDataHome.length > 0 ? xdgDataHome : join32(homedir19(), ".local", "share");
20281
+ return xdgDataHome && xdgDataHome.length > 0 ? xdgDataHome : join33(homedir19(), ".local", "share");
19976
20282
  }
19977
20283
  function listRecentPiSessions() {
19978
- return listPiSessionsFromDir(join32(getHomeDir(), ".pi", "agent", "sessions"));
20284
+ return listPiSessionsFromDir(join33(getHomeDir(), ".pi", "agent", "sessions"));
19979
20285
  }
19980
20286
  function getHomeDir() {
19981
20287
  const envHome = process.platform === "win32" ? process.env.USERPROFILE : process.env.HOME;
@@ -19983,7 +20289,7 @@ function getHomeDir() {
19983
20289
  }
19984
20290
  function listPiSessionsFromDir(sessionsDir) {
19985
20291
  try {
19986
- if (!existsSync29(sessionsDir))
20292
+ if (!existsSync30(sessionsDir))
19987
20293
  return [];
19988
20294
  const files = collectJsonlFiles(sessionsDir).map((filePath) => {
19989
20295
  try {
@@ -19995,7 +20301,7 @@ function listPiSessionsFromDir(sessionsDir) {
19995
20301
  }).filter((entry) => entry !== null).sort((a, b) => b.mtimeMs - a.mtimeMs).slice(0, MAX_RECENT_SESSIONS * 4);
19996
20302
  const sessions = [];
19997
20303
  for (const file of files) {
19998
- const parsed = parsePiSessionJsonl(readFileSync16(file.filePath, "utf8"), basename3(file.filePath));
20304
+ const parsed = parsePiSessionJsonl(readFileSync17(file.filePath, "utf8"), basename3(file.filePath));
19999
20305
  if (!parsed)
20000
20306
  continue;
20001
20307
  sessions.push({ ...parsed, lastActivity: file.mtimeMs });
@@ -20016,12 +20322,12 @@ function collectJsonlFiles(root) {
20016
20322
  continue;
20017
20323
  let entries;
20018
20324
  try {
20019
- entries = readdirSync12(dir, { withFileTypes: true });
20325
+ entries = readdirSync13(dir, { withFileTypes: true });
20020
20326
  } catch {
20021
20327
  continue;
20022
20328
  }
20023
20329
  for (const entry of entries) {
20024
- const path = join32(dir, entry.name);
20330
+ const path = join33(dir, entry.name);
20025
20331
  if (entry.isDirectory()) {
20026
20332
  stack.push(path);
20027
20333
  } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
@@ -20122,19 +20428,19 @@ var init_sessions = __esm(() => {
20122
20428
  // src/commands/doctor.ts
20123
20429
  import {
20124
20430
  chmodSync as chmodSync4,
20125
- existsSync as existsSync30,
20431
+ existsSync as existsSync31,
20126
20432
  lstatSync as lstatSync5,
20127
20433
  mkdirSync as mkdirSync8,
20128
20434
  mkdtempSync as mkdtempSync2,
20129
- readdirSync as readdirSync13,
20130
- readFileSync as readFileSync17,
20435
+ readdirSync as readdirSync14,
20436
+ readFileSync as readFileSync18,
20131
20437
  realpathSync as realpathSync8,
20132
20438
  rmSync as rmSync8,
20133
20439
  statSync as statSync19,
20134
20440
  writeFileSync as writeFileSync5
20135
20441
  } from "node:fs";
20136
20442
  import { tmpdir as tmpdir5 } from "node:os";
20137
- import { dirname as dirname12, join as join33 } from "node:path";
20443
+ import { dirname as dirname13, join as join34 } from "node:path";
20138
20444
  function openCodeAdapter(adapters) {
20139
20445
  return adapters.find((adapter) => adapter.kind === "opencode");
20140
20446
  }
@@ -20157,7 +20463,7 @@ function refuseAmbiguousOpenCodeWrites(detection) {
20157
20463
  function adapterConfigNeedsUpdate(adapter, tui = false) {
20158
20464
  const method = tui ? "needsTuiPluginEntryUpdate" : "needsPluginEntryUpdate";
20159
20465
  const candidate = adapter;
20160
- return candidate[method]?.() ?? false;
20466
+ return candidate[method]?.({ doctor: true }) ?? false;
20161
20467
  }
20162
20468
  function buildDoctorProfileArgs(argv) {
20163
20469
  const profileIndex = argv.indexOf("--profile");
@@ -20214,12 +20520,12 @@ async function runDoctor(options) {
20214
20520
  configPath: opencodeHarness.configPaths.harnessConfig,
20215
20521
  logPath: opencodeHarness.logFile.path,
20216
20522
  pluginCachePath: opencodeHarness.pluginCache.path,
20217
- cachedPluginVersion: opencodeHarness.pluginCache.cached,
20523
+ cachedPluginVersion: doctorInstalledVersion(opencodeHarness.pluginCache),
20218
20524
  expectedPluginEntry: opencodeAdapter.pluginEntryWithVersion,
20219
20525
  acceptExplicitPluginVersion: hostDetection.status === "v1"
20220
20526
  }) : null;
20221
20527
  const opencodeSkew = opencodeHarness ? pluginVersionSkewFor(report, opencodeHarness) : null;
20222
- log2.info(`AFT CLI v${report.cliVersion}, AFT binary ${report.binaryVersion ?? "unknown"}`);
20528
+ log2.info(`AFT CLI v${report.cliVersion}, ${describeCliBinary(report.binaryVersion, report.binaryPath)}`);
20223
20529
  if (!report.binaryVersion) {
20224
20530
  const hasEnabledRegisteredHarness = report.harnesses.some((h) => h.pluginRegistered && h.aftConfig.enabled);
20225
20531
  const hasRegisteredHarness = report.harnesses.some((h) => h.pluginRegistered);
@@ -20235,7 +20541,7 @@ async function runDoctor(options) {
20235
20541
  }
20236
20542
  logUnmatchedBinaryCandidates(report.cliVersion);
20237
20543
  }
20238
- log2.info(`Binary cache: ${report.binaryCache.versions.length} version(s), ${formatBytes(report.binaryCache.totalSize)} at ${report.binaryCache.path}`);
20544
+ 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
20545
  logBuildBreakerSuspensions(report);
20240
20546
  const features = logFeatureStatus(options.argv, options.runNative, {
20241
20547
  checkGh: options.checkGh ?? checkGhStatus,
@@ -20274,6 +20580,8 @@ async function runDoctor(options) {
20274
20580
  }
20275
20581
  if (opencodeSkew)
20276
20582
  log2.error(` ${opencodeSkew.message} ${opencodeSkew.remediation}`);
20583
+ else if (opencodeDoctor?.versionNotice)
20584
+ log2.info(` ${opencodeDoctor.versionNotice}`);
20277
20585
  for (const hostTool of OPENCODE_HOST_TOOL_NAMES) {
20278
20586
  const line = hostToolDoctorLine(opencodeAdapter, hostTool);
20279
20587
  if (line)
@@ -20286,7 +20594,7 @@ async function runDoctor(options) {
20286
20594
  } else {
20287
20595
  log2.info(` plugin registered: ${h.pluginRegistered ? "yes" : "no"}`);
20288
20596
  }
20289
- log2.info(` plugin version: ${h.kind === "opencode" ? opencodeDoctor?.pluginVersion ?? h.pluginCache.cached ?? "not installed" : h.pluginCache.cached ?? "not installed"}`);
20597
+ log2.info(` plugin version: ${describePluginVersion(h.pluginCache)}`);
20290
20598
  if (!h.aftConfig.enabled) {
20291
20599
  log2.info(` AFT disabled by config${h.aftConfig.enabledSource ? ` (${h.aftConfig.enabledSource})` : ""}; plugin will stay inert`);
20292
20600
  }
@@ -20545,7 +20853,7 @@ function clearOldBinaries(cliVersion = getSelfVersion(), plugins = getAllAdapter
20545
20853
  errors: [],
20546
20854
  keptVersion: keepTag
20547
20855
  };
20548
- if (!existsSync30(info.path)) {
20856
+ if (!existsSync31(info.path)) {
20549
20857
  log2.info(`Binary cache: nothing to clear at ${info.path}`);
20550
20858
  return result;
20551
20859
  }
@@ -20576,7 +20884,7 @@ function clearOldBinaries(cliVersion = getSelfVersion(), plugins = getAllAdapter
20576
20884
  return result;
20577
20885
  }
20578
20886
  for (const version of stale) {
20579
- const dir = join33(info.path, version);
20887
+ const dir = join34(info.path, version);
20580
20888
  let bytes = 0;
20581
20889
  try {
20582
20890
  bytes = statSync19(dir).isDirectory() ? dirSize(dir) : 0;
@@ -20640,9 +20948,14 @@ function findPluginUpdateTargets(adapters, report) {
20640
20948
  if (!harness.hostInstalled || !harness.pluginRegistered)
20641
20949
  continue;
20642
20950
  const cache = harness.pluginCache;
20643
- if (!cache?.exists || !cache.cached || !cache.latest)
20951
+ if (cache.local)
20952
+ continue;
20953
+ const cliVersion = cache.cliVersion ?? cache.latest ?? report.cliVersion;
20954
+ if (!cache?.exists || !cache.cached)
20644
20955
  continue;
20645
- if (cache.cached === cache.latest)
20956
+ if (preserveDoctorPluginEntry(cliVersion, cache.configuredEntry, doctorInstalledVersion(cache)))
20957
+ continue;
20958
+ if ((compareSemver2(cache.cached, cliVersion) ?? 0) >= 0)
20646
20959
  continue;
20647
20960
  const adapter = adaptersByKind.get(harness.kind);
20648
20961
  if (!adapter)
@@ -20652,8 +20965,8 @@ function findPluginUpdateTargets(adapters, report) {
20652
20965
  adapter,
20653
20966
  installDir: cache.path,
20654
20967
  cached: cache.cached,
20655
- latest: cache.latest,
20656
- oldPin: pin !== null && pin !== cache.latest ? pin : null
20968
+ latest: cliVersion,
20969
+ oldPin: pin !== null && pin !== cliVersion ? pin : null
20657
20970
  });
20658
20971
  }
20659
20972
  return targets;
@@ -20736,9 +21049,9 @@ async function runDoctorNpmInstall(npm, installDir) {
20736
21049
  });
20737
21050
  }
20738
21051
  function installedPluginVersion(installDir) {
20739
- const manifest = join33(installDir, "node_modules", ...AFT_OPENCODE_PACKAGE.split("/"), "package.json");
21052
+ const manifest = join34(installDir, "node_modules", ...AFT_OPENCODE_PACKAGE.split("/"), "package.json");
20740
21053
  try {
20741
- const pkg = JSON.parse(readFileSync17(manifest, "utf-8"));
21054
+ const pkg = JSON.parse(readFileSync18(manifest, "utf-8"));
20742
21055
  return typeof pkg.version === "string" ? pkg.version : null;
20743
21056
  } catch {
20744
21057
  return null;
@@ -20838,7 +21151,7 @@ function buildDoctorFixPlan(adapters, report) {
20838
21151
  } else {
20839
21152
  items.push({
20840
21153
  kind: "plugin",
20841
- message: `Will ${harness.pluginRegistered ? "update" : "add"} ${adapter.pluginEntryWithVersion} ${harness.pluginRegistered ? "in" : "to"} ${harness.configPaths.harnessConfig}`
21154
+ message: `Will ${harness.pluginRegistered ? "update" : "add"} ${adapter instanceof OpenCodeAdapter ? adapter.doctorPluginEntry() : adapter.pluginEntryWithVersion} ${harness.pluginRegistered ? "in" : "to"} ${harness.configPaths.harnessConfig}`
20842
21155
  });
20843
21156
  }
20844
21157
  }
@@ -20855,7 +21168,7 @@ function buildDoctorFixPlan(adapters, report) {
20855
21168
  continue;
20856
21169
  items.push({
20857
21170
  kind: "plugin",
20858
- message: `Will ${hasTuiEntry ? "update" : "add"} ${adapter.pluginEntryWithVersion} ${hasTuiEntry ? "in" : "to"} ${harness.configPaths.tuiConfig} (TUI sidebar)`
21171
+ message: `Will ${hasTuiEntry ? "update" : "add"} ${adapter instanceof OpenCodeAdapter ? adapter.doctorPluginEntry(true) : adapter.pluginEntryWithVersion} ${hasTuiEntry ? "in" : "to"} ${harness.configPaths.tuiConfig} (TUI sidebar)`
20859
21172
  });
20860
21173
  }
20861
21174
  for (const { adapter, hostTool } of hostToolFixTargets(adapters)) {
@@ -20994,6 +21307,12 @@ async function runFixFlow(argv, detectOpenCodeHost, resolveAdapters = resolveAda
20994
21307
  }
20995
21308
  log2.info("Running diagnostics to identify auto-fixable issues…");
20996
21309
  const report = await collect(adapters);
21310
+ for (const harness of report.harnesses) {
21311
+ const cache = harness.pluginCache;
21312
+ if (harness.kind === "opencode" && preserveDoctorPluginEntry(report.cliVersion, cache.configuredEntry, doctorInstalledVersion(cache))) {
21313
+ log2.info(doctorNoDowngradeMessage(report.cliVersion, cache.configuredEntry, doctorInstalledVersion(cache)));
21314
+ }
21315
+ }
20997
21316
  if (!report.binaryVersion) {
20998
21317
  logUnmatchedBinaryCandidates(report.cliVersion);
20999
21318
  }
@@ -21197,14 +21516,14 @@ function formatDoctorStorageStatus(h) {
21197
21516
  let cacheWarning = "";
21198
21517
  if (h.storageDir.cacheRoot) {
21199
21518
  try {
21200
- const parent = realpathSync8(dirname12(h.storageDir.cacheRoot));
21519
+ const parent = realpathSync8(dirname13(h.storageDir.cacheRoot));
21201
21520
  let durable = h.storageDir.path;
21202
- while (!existsSync30(durable) && dirname12(durable) !== durable)
21203
- durable = dirname12(durable);
21521
+ while (!existsSync31(durable) && dirname13(durable) !== durable)
21522
+ durable = dirname13(durable);
21204
21523
  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"));
21524
+ const history = join34(h.storageDir.path, "retention/cache-parents.json");
21525
+ if (existsSync31(history)) {
21526
+ const saved = JSON.parse(readFileSync18(history, "utf8"));
21208
21527
  registered = saved.parents?.some((entry) => entry.path === parent) === true;
21209
21528
  }
21210
21529
  if (!registered && statSync19(parent).dev === statSync19(durable).dev) {
@@ -21246,7 +21565,7 @@ function ensureStorageDirsForRegisteredPlugins(adapters) {
21246
21565
  if (!adapter.isInstalled() || !adapter.hasPluginEntry())
21247
21566
  continue;
21248
21567
  storageDir = adapter.getStorageDir();
21249
- if (existsSync30(storageDir))
21568
+ if (existsSync31(storageDir))
21250
21569
  continue;
21251
21570
  mkdirSync8(storageDir, { recursive: true });
21252
21571
  summary.created += 1;
@@ -21309,7 +21628,7 @@ async function maybeFixPlugin(adapter) {
21309
21628
  return summary;
21310
21629
  if (!adapter.hasPluginEntry() || adapterConfigNeedsUpdate(adapter)) {
21311
21630
  log2.info(`${adapter.displayName}: attempting to register or update plugin config…`);
21312
- const result = await adapter.ensurePluginEntry();
21631
+ const result = await adapter.ensurePluginEntry({ doctor: true });
21313
21632
  if (result.ok) {
21314
21633
  if (result.action === "added" || result.action === "updated")
21315
21634
  summary.changed += 1;
@@ -21322,7 +21641,7 @@ async function maybeFixPlugin(adapter) {
21322
21641
  if (adapter.ensureTuiPluginEntry && adapter.hasTuiPluginEntry && adapter.tuiPluginEntryApplies?.() !== false) {
21323
21642
  const hasTuiEntry = adapter.hasTuiPluginEntry();
21324
21643
  if (!hasTuiEntry || adapterConfigNeedsUpdate(adapter, true)) {
21325
- const result = await adapter.ensureTuiPluginEntry();
21644
+ const result = await adapter.ensureTuiPluginEntry({ doctor: true });
21326
21645
  if (result.ok && (result.action === "added" || result.action === "updated")) {
21327
21646
  summary.changed += 1;
21328
21647
  log2.success(`${adapter.displayName}: ${result.message}`);
@@ -21407,11 +21726,11 @@ function deriveIssueTitleFromBody(body) {
21407
21726
  function writeIssueReviewFile(body) {
21408
21727
  let reviewDir = null;
21409
21728
  try {
21410
- reviewDir = mkdtempSync2(join33(tmpdir5(), "aft-issue-"));
21729
+ reviewDir = mkdtempSync2(join34(tmpdir5(), "aft-issue-"));
21411
21730
  if (process.platform !== "win32") {
21412
21731
  chmodSync4(reviewDir, 448);
21413
21732
  }
21414
- const outPath = join33(reviewDir, "issue.md");
21733
+ const outPath = join34(reviewDir, "issue.md");
21415
21734
  writeFileSync5(outPath, `${body}
21416
21735
  `, { encoding: "utf8", mode: 384, flag: "wx" });
21417
21736
  return { path: outPath, realPath: realpathSync8(outPath) };
@@ -21432,7 +21751,7 @@ function readReviewedIssueFile(reviewFile) {
21432
21751
  log2.error(`Review file path changed before filing; refusing to read ${reviewFile.path}.`);
21433
21752
  return null;
21434
21753
  }
21435
- return readFileSync17(reviewFile.path, "utf8");
21754
+ return readFileSync18(reviewFile.path, "utf8");
21436
21755
  } catch (err) {
21437
21756
  log2.error(`Failed to read reviewed issue report: ${err instanceof Error ? err.message : String(err)}`);
21438
21757
  return null;
@@ -21477,14 +21796,14 @@ function collectIssueLog(path, selectedSession) {
21477
21796
  };
21478
21797
  }
21479
21798
  function collectIssueEngineLog(path, selectedSession) {
21480
- const directory = dirname12(path);
21799
+ const directory = dirname13(path);
21481
21800
  const files = [];
21482
21801
  try {
21483
- for (const name of readdirSync13(directory)) {
21802
+ for (const name of readdirSync14(directory)) {
21484
21803
  if (!/^aft-\d+\.log(?:\.1)?$/.test(name))
21485
21804
  continue;
21486
21805
  try {
21487
- const candidate = join33(directory, name);
21806
+ const candidate = join34(directory, name);
21488
21807
  const stat = lstatSync5(candidate);
21489
21808
  if (stat.isFile())
21490
21809
  files.push({ path: candidate, modified: stat.mtimeMs });
@@ -21535,7 +21854,7 @@ ${logText || "<no log output>"}
21535
21854
  errors
21536
21855
  },
21537
21856
  {
21538
- section: `#### Engine logs (${dirname12(path)})
21857
+ section: `#### Engine logs (${dirname13(path)})
21539
21858
 
21540
21859
  ${engine.notice}
21541
21860
  \`\`\`
@@ -21645,6 +21964,7 @@ var init_doctor = __esm(async () => {
21645
21964
  init_jsonc();
21646
21965
  init_lsp_cache();
21647
21966
  init_onnx_fix();
21967
+ init_opencode_plugin_cache();
21648
21968
  init_paths2();
21649
21969
  init_prompts();
21650
21970
  init_sanitize();
@@ -21677,24 +21997,24 @@ var init_doctor = __esm(async () => {
21677
21997
  });
21678
21998
 
21679
21999
  // src/commands/lsp.ts
21680
- import { existsSync as existsSync31, readdirSync as readdirSync14, statSync as statSync20 } from "node:fs";
22000
+ import { existsSync as existsSync32, readdirSync as readdirSync15, statSync as statSync20 } from "node:fs";
21681
22001
  import { createRequire as createRequire5 } from "node:module";
21682
- import { dirname as dirname13, join as join34, resolve as resolve15 } from "node:path";
22002
+ import { dirname as dirname14, join as join35, resolve as resolve15 } from "node:path";
21683
22003
  function findProjectRootForFile(filePath, fallbackCwd = process.cwd()) {
21684
22004
  const resolvedFile = resolve15(fallbackCwd, filePath);
21685
- let dir = dirname13(resolvedFile);
22005
+ let dir = dirname14(resolvedFile);
21686
22006
  try {
21687
- if (existsSync31(resolvedFile) && statSync20(resolvedFile).isDirectory()) {
22007
+ if (existsSync32(resolvedFile) && statSync20(resolvedFile).isDirectory()) {
21688
22008
  dir = resolvedFile;
21689
22009
  }
21690
22010
  } catch {
21691
- dir = dirname13(resolvedFile);
22011
+ dir = dirname14(resolvedFile);
21692
22012
  }
21693
22013
  while (true) {
21694
- if (PROJECT_ROOT_MARKERS.some((marker) => existsSync31(join34(dir, marker)))) {
22014
+ if (PROJECT_ROOT_MARKERS.some((marker) => existsSync32(join35(dir, marker)))) {
21695
22015
  return dir;
21696
22016
  }
21697
- const parent = dirname13(dir);
22017
+ const parent = dirname14(dir);
21698
22018
  if (parent === dir)
21699
22019
  return resolve15(fallbackCwd);
21700
22020
  dir = parent;
@@ -21840,18 +22160,18 @@ function buildConfigureParams(adapter, projectRoot) {
21840
22160
  function inferLspPathsExtra(_lsp) {
21841
22161
  const paths = new Set;
21842
22162
  for (const entry of childDirs(getAftLspPackagesDir())) {
21843
- paths.add(join34(entry, "node_modules", ".bin"));
22163
+ paths.add(join35(entry, "node_modules", ".bin"));
21844
22164
  }
21845
22165
  for (const entry of childDirs(getAftLspBinariesDir())) {
21846
- paths.add(join34(entry, "bin"));
22166
+ paths.add(join35(entry, "bin"));
21847
22167
  }
21848
22168
  return [...paths];
21849
22169
  }
21850
22170
  function childDirs(path) {
21851
- if (!existsSync31(path))
22171
+ if (!existsSync32(path))
21852
22172
  return [];
21853
22173
  try {
21854
- return readdirSync14(path).map((entry) => join34(path, entry)).filter((entry) => {
22174
+ return readdirSync15(path).map((entry) => join35(path, entry)).filter((entry) => {
21855
22175
  try {
21856
22176
  return statSync20(entry).isDirectory();
21857
22177
  } catch {
@@ -21895,7 +22215,7 @@ function typescriptPackageWarning(response) {
21895
22215
  if (!typescriptServerSpawned || !response.project_root || diagnosticsCount > 0)
21896
22216
  return null;
21897
22217
  try {
21898
- createRequire5(join34(response.project_root, "package.json")).resolve("typescript");
22218
+ createRequire5(join35(response.project_root, "package.json")).resolve("typescript");
21899
22219
  return null;
21900
22220
  } catch {
21901
22221
  return "typescript package not resolvable from project — server will produce no diagnostics";
@@ -21935,7 +22255,7 @@ var init_lsp = __esm(async () => {
21935
22255
  });
21936
22256
 
21937
22257
  // src/commands/doctor-filters.ts
21938
- import { existsSync as existsSync32 } from "node:fs";
22258
+ import { existsSync as existsSync33 } from "node:fs";
21939
22259
  import { relative as relative5, resolve as resolve16 } from "node:path";
21940
22260
  function printDoctorFiltersHelp() {
21941
22261
  console.log(`Usage: ${CLI} doctor filters [--show <name>] [trust|untrust]`);
@@ -21977,7 +22297,7 @@ async function runDoctorFilters(options) {
21977
22297
  log2.error(list.message ?? list.code ?? "list_filters failed");
21978
22298
  return 1;
21979
22299
  }
21980
- list.project_dir_exists = list.project_dir ? existsSync32(list.project_dir) : false;
22300
+ list.project_dir_exists = list.project_dir ? existsSync33(list.project_dir) : false;
21981
22301
  if (mode.kind === "list") {
21982
22302
  console.log(renderFilterList(list, projectRoot));
21983
22303
  return 0;
@@ -22191,7 +22511,7 @@ function printHelp() {
22191
22511
  console.log(" setup --answers <file> Save feature choices from an answers file");
22192
22512
  console.log(" index Build one configured index snapshot (no scheduler)");
22193
22513
  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)");
22514
+ console.log(" cache prune-legacy Delete the index folders AFT used before 0.64 (dry run unless --yes)");
22195
22515
  console.log(" doctor Check and fix configuration issues");
22196
22516
  console.log(" doctor --profile [seconds] Profile a running AFT daemon");
22197
22517
  console.log(" doctor lsp <file> Inspect LSP setup for one file");