@cortexkit/aft-opencode 0.26.1 → 0.26.2

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
@@ -9382,15 +9382,6 @@ import { homedir as homedir3 } from "os";
9382
9382
  var DEFAULT_IDLE_TIMEOUT_MS = Infinity;
9383
9383
  var DEFAULT_MAX_POOL_SIZE = 8;
9384
9384
  var CLEANUP_INTERVAL_MS = 60 * 1000;
9385
-
9386
- class HomeProjectRootError extends Error {
9387
- projectRoot;
9388
- constructor(projectRoot) {
9389
- super(`aft refuses to spawn a bridge with project_root=${projectRoot} (user home directory). ` + `Open OpenCode/Pi from a project subdirectory instead, or set the session's ` + `directory to a real project root.`);
9390
- this.projectRoot = projectRoot;
9391
- this.name = "HomeProjectRootError";
9392
- }
9393
- }
9394
9385
  function canonicalHomeDir() {
9395
9386
  try {
9396
9387
  const home = homedir3();
@@ -9453,9 +9444,6 @@ class BridgePool {
9453
9444
  }
9454
9445
  getBridge(projectRoot) {
9455
9446
  const key = normalizeKey(projectRoot);
9456
- if (isHomeDirectoryRoot(key)) {
9457
- throw new HomeProjectRootError(key);
9458
- }
9459
9447
  const existing = this.bridges.get(key);
9460
9448
  if (existing) {
9461
9449
  existing.lastUsed = Date.now();
@@ -9613,6 +9601,21 @@ function copyToVersionedCache(npmBinaryPath, knownVersion) {
9613
9601
  function normalizeBareVersion(version) {
9614
9602
  return version.startsWith("v") ? version.slice(1) : version;
9615
9603
  }
9604
+ function homeDirFromEnv(env) {
9605
+ return (process.platform === "win32" ? env.USERPROFILE || env.HOME : env.HOME) || homedir4();
9606
+ }
9607
+ function cacheDirFromEnv(env) {
9608
+ if (process.platform === "win32") {
9609
+ const base2 = env.LOCALAPPDATA || env.APPDATA || join4(homeDirFromEnv(env), "AppData", "Local");
9610
+ return join4(base2, "aft", "bin");
9611
+ }
9612
+ const base = env.XDG_CACHE_HOME || join4(homeDirFromEnv(env), ".cache");
9613
+ return join4(base, "aft", "bin");
9614
+ }
9615
+ function cachedBinaryPathFromEnv(version, env, ext) {
9616
+ const binaryPath = join4(cacheDirFromEnv(env), version, `aft${ext}`);
9617
+ return existsSync3(binaryPath) ? binaryPath : null;
9618
+ }
9616
9619
  function isExpectedCachedBinary(binaryPath, expectedVersion) {
9617
9620
  const expected = normalizeBareVersion(expectedVersion);
9618
9621
  const actual = readBinaryVersion(binaryPath);
@@ -9634,6 +9637,7 @@ function platformKey(platform = process.platform, arch = process.arch) {
9634
9637
  }
9635
9638
  function findBinarySync(expectedVersion) {
9636
9639
  const ext = process.platform === "win32" ? ".exe" : "";
9640
+ const env = { ...process.env };
9637
9641
  const pluginVersion = expectedVersion ?? (() => {
9638
9642
  try {
9639
9643
  const req = createRequire(import.meta.url);
@@ -9644,7 +9648,7 @@ function findBinarySync(expectedVersion) {
9644
9648
  })();
9645
9649
  if (pluginVersion) {
9646
9650
  const tag = pluginVersion.startsWith("v") ? pluginVersion : `v${pluginVersion}`;
9647
- const versionCached = getCachedBinaryPath(tag);
9651
+ const versionCached = cachedBinaryPathFromEnv(tag, env, ext);
9648
9652
  if (versionCached && isExpectedCachedBinary(versionCached, pluginVersion))
9649
9653
  return versionCached;
9650
9654
  }
@@ -9669,12 +9673,13 @@ function findBinarySync(expectedVersion) {
9669
9673
  const whichCmd = process.platform === "win32" ? "where aft" : "which aft";
9670
9674
  const result = execSync(whichCmd, {
9671
9675
  encoding: "utf-8",
9676
+ env,
9672
9677
  stdio: ["pipe", "pipe", "pipe"]
9673
9678
  }).trim();
9674
9679
  if (result)
9675
9680
  return result;
9676
9681
  } catch {}
9677
- const cargoPath = join4(homedir4(), ".cargo", "bin", `aft${ext}`);
9682
+ const cargoPath = join4(homeDirFromEnv(env), ".cargo", "bin", `aft${ext}`);
9678
9683
  if (existsSync3(cargoPath))
9679
9684
  return cargoPath;
9680
9685
  return null;
@@ -27490,6 +27495,8 @@ function coerceAftStatus(response) {
27490
27495
  project_root: readNullableString(response.project_root),
27491
27496
  canonical_root: readNullableString(response.canonical_root),
27492
27497
  cache_role: readString(response.cache_role, "not_initialized"),
27498
+ degraded: readBoolean(response.degraded),
27499
+ degraded_reasons: Array.isArray(response.degraded_reasons) ? response.degraded_reasons.filter((r) => typeof r === "string") : [],
27493
27500
  features: {
27494
27501
  format_on_edit: readBoolean(features.format_on_edit),
27495
27502
  validate_on_edit: readString(features.validate_on_edit, "off"),
@@ -3,6 +3,17 @@ export interface AftStatusSnapshot {
3
3
  project_root: string | null;
4
4
  canonical_root: string | null;
5
5
  cache_role: string;
6
+ /**
7
+ * True when at least one heavy AFT subsystem has been auto-disabled for
8
+ * the current project root. `degraded_reasons` enumerates why (e.g.
9
+ * `["home_root"]`, `["search_too_many_files:20000"]`). The sidebar / TUI
10
+ * dialog surface this so users know `aft_search`, `aft_navigate` etc.
11
+ * won't return results from this session and can choose to open a
12
+ * project subdirectory instead.
13
+ */
14
+ degraded: boolean;
15
+ /** Machine-readable degraded-mode reasons. Empty when `degraded === false`. */
16
+ degraded_reasons: string[];
6
17
  features: {
7
18
  format_on_edit: boolean;
8
19
  validate_on_edit: string;
@@ -1 +1 @@
1
- {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/shared/status.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE;QACR,cAAc,EAAE,OAAO,CAAC;QACxB,gBAAgB,EAAE,MAAM,CAAC;QACzB,wBAAwB,EAAE,OAAO,CAAC;QAClC,YAAY,EAAE,OAAO,CAAC;QACtB,eAAe,EAAE,OAAO,CAAC;KAC1B,CAAC;IACF,YAAY,EAAE;QACZ,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;QACrB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;KACzB,CAAC;IACF,cAAc,EAAE;QACd,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACxB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC7B,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;QACzB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KACvB,CAAC;IACF,IAAI,EAAE;QACJ,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3B,kBAAkB,EAAE,MAAM,CAAC;QAC3B,mBAAmB,EAAE,MAAM,CAAC;KAC7B,CAAC;IACF,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE;QACZ,aAAa,EAAE,MAAM,CAAC;QACtB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;IACF,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,iEAAiE;IACjE,iBAAiB,EAAE,MAAM,CAAC;IAC1B,4DAA4D;IAC5D,OAAO,EAAE;QACP,EAAE,EAAE,MAAM,CAAC;QACX,aAAa,EAAE,MAAM,CAAC;QACtB,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;CACH;AAkCD,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAajD;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,iBAAiB,CA6DpF;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,CAuE3E;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,CA0EtE"}
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/shared/status.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;;OAOG;IACH,QAAQ,EAAE,OAAO,CAAC;IAClB,+EAA+E;IAC/E,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,QAAQ,EAAE;QACR,cAAc,EAAE,OAAO,CAAC;QACxB,gBAAgB,EAAE,MAAM,CAAC;QACzB,wBAAwB,EAAE,OAAO,CAAC;QAClC,YAAY,EAAE,OAAO,CAAC;QACtB,eAAe,EAAE,OAAO,CAAC;KAC1B,CAAC;IACF,YAAY,EAAE;QACZ,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;QACrB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;KACzB,CAAC;IACF,cAAc,EAAE;QACd,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACxB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC7B,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;QACzB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KACvB,CAAC;IACF,IAAI,EAAE;QACJ,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3B,kBAAkB,EAAE,MAAM,CAAC;QAC3B,mBAAmB,EAAE,MAAM,CAAC;KAC7B,CAAC;IACF,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE;QACZ,aAAa,EAAE,MAAM,CAAC;QACtB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;IACF,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,iEAAiE;IACjE,iBAAiB,EAAE,MAAM,CAAC;IAC1B,4DAA4D;IAC5D,OAAO,EAAE;QACP,EAAE,EAAE,MAAM,CAAC;QACX,aAAa,EAAE,MAAM,CAAC;QACtB,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;CACH;AAkCD,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAajD;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,iBAAiB,CAiEpF;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,CAuE3E;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,CA0EtE"}
package/dist/tui.js CHANGED
@@ -4,7 +4,7 @@ import { createMemo as createMemo2, createSignal as createSignal2, onCleanup as
4
4
  // package.json
5
5
  var package_default = {
6
6
  name: "@cortexkit/aft-opencode",
7
- version: "0.26.1",
7
+ version: "0.26.2",
8
8
  type: "module",
9
9
  description: "OpenCode plugin for Agent File Tools (AFT) \u2014 tree-sitter and lsp powered code analysis",
10
10
  main: "dist/index.js",
@@ -32,7 +32,7 @@ var package_default = {
32
32
  },
33
33
  dependencies: {
34
34
  "@clack/prompts": "^1.2.0",
35
- "@cortexkit/aft-bridge": "0.26.1",
35
+ "@cortexkit/aft-bridge": "0.26.2",
36
36
  "@opencode-ai/plugin": "^1.14.39",
37
37
  "@opencode-ai/sdk": "^1.14.39",
38
38
  "comment-json": "^4.6.2",
@@ -40,11 +40,11 @@ var package_default = {
40
40
  zod: "^4.1.8"
41
41
  },
42
42
  optionalDependencies: {
43
- "@cortexkit/aft-darwin-arm64": "0.26.1",
44
- "@cortexkit/aft-darwin-x64": "0.26.1",
45
- "@cortexkit/aft-linux-arm64": "0.26.1",
46
- "@cortexkit/aft-linux-x64": "0.26.1",
47
- "@cortexkit/aft-win32-x64": "0.26.1"
43
+ "@cortexkit/aft-darwin-arm64": "0.26.2",
44
+ "@cortexkit/aft-darwin-x64": "0.26.2",
45
+ "@cortexkit/aft-linux-arm64": "0.26.2",
46
+ "@cortexkit/aft-linux-x64": "0.26.2",
47
+ "@cortexkit/aft-win32-x64": "0.26.2"
48
48
  },
49
49
  devDependencies: {
50
50
  "@types/node": "^22.0.0",
@@ -334,6 +334,8 @@ function coerceAftStatus(response) {
334
334
  project_root: readNullableString(response.project_root),
335
335
  canonical_root: readNullableString(response.canonical_root),
336
336
  cache_role: readString(response.cache_role, "not_initialized"),
337
+ degraded: readBoolean(response.degraded),
338
+ degraded_reasons: Array.isArray(response.degraded_reasons) ? response.degraded_reasons.filter((r) => typeof r === "string") : [],
337
339
  features: {
338
340
  format_on_edit: readBoolean(features.format_on_edit),
339
341
  validate_on_edit: readString(features.validate_on_edit, "off"),
@@ -560,6 +562,25 @@ var SidebarContent = (props) => {
560
562
  const semanticStatus = () => statusDisplay(s()?.semantic_index?.status ?? "disabled");
561
563
  const trigramBytes = () => s()?.disk?.trigram_disk_bytes ?? 0;
562
564
  const semanticBytes = () => s()?.disk?.semantic_disk_bytes ?? 0;
565
+ const degradedReasonLabel = (reason) => {
566
+ if (reason === "home_root") {
567
+ return "project root is your home directory";
568
+ }
569
+ if (reason.startsWith("search_too_many_files:")) {
570
+ const threshold = reason.split(":")[1] ?? "20000";
571
+ return `project exceeds ${threshold} files`;
572
+ }
573
+ return reason;
574
+ };
575
+ const degradedSummary = () => {
576
+ const snap = s();
577
+ if (!snap?.degraded)
578
+ return null;
579
+ const reasons = snap.degraded_reasons ?? [];
580
+ if (reasons.length === 0)
581
+ return null;
582
+ return reasons.map(degradedReasonLabel).join("; ");
583
+ };
563
584
  return /* @__PURE__ */ jsxDEV("box", {
564
585
  width: "100%",
565
586
  flexDirection: "column",
@@ -576,16 +597,34 @@ var SidebarContent = (props) => {
576
597
  alignItems: "center",
577
598
  children: [
578
599
  /* @__PURE__ */ jsxDEV("box", {
579
- paddingLeft: 1,
580
- paddingRight: 1,
581
- backgroundColor: props.theme.accent,
582
- children: /* @__PURE__ */ jsxDEV("text", {
583
- fg: props.theme.background,
584
- children: /* @__PURE__ */ jsxDEV("b", {
585
- children: "AFT"
600
+ flexDirection: "row",
601
+ alignItems: "center",
602
+ children: [
603
+ /* @__PURE__ */ jsxDEV("box", {
604
+ paddingLeft: 1,
605
+ paddingRight: 1,
606
+ backgroundColor: props.theme.accent,
607
+ children: /* @__PURE__ */ jsxDEV("text", {
608
+ fg: props.theme.background,
609
+ children: /* @__PURE__ */ jsxDEV("b", {
610
+ children: "AFT"
611
+ }, undefined, false, undefined, this)
612
+ }, undefined, false, undefined, this)
613
+ }, undefined, false, undefined, this),
614
+ s()?.degraded && /* @__PURE__ */ jsxDEV("box", {
615
+ paddingLeft: 1,
616
+ paddingRight: 1,
617
+ marginLeft: 1,
618
+ backgroundColor: props.theme.warning,
619
+ children: /* @__PURE__ */ jsxDEV("text", {
620
+ fg: props.theme.background,
621
+ children: /* @__PURE__ */ jsxDEV("b", {
622
+ children: "DEGRADED"
623
+ }, undefined, false, undefined, this)
624
+ }, undefined, false, undefined, this)
586
625
  }, undefined, false, undefined, this)
587
- }, undefined, false, undefined, this)
588
- }, undefined, false, undefined, this),
626
+ ]
627
+ }, undefined, true, undefined, this),
589
628
  /* @__PURE__ */ jsxDEV("text", {
590
629
  fg: props.theme.textMuted,
591
630
  children: [
@@ -595,6 +634,17 @@ var SidebarContent = (props) => {
595
634
  }, undefined, true, undefined, this)
596
635
  ]
597
636
  }, undefined, true, undefined, this),
637
+ s()?.degraded && degradedSummary() && /* @__PURE__ */ jsxDEV("box", {
638
+ marginTop: 1,
639
+ width: "100%",
640
+ children: /* @__PURE__ */ jsxDEV("text", {
641
+ fg: props.theme.warning,
642
+ children: [
643
+ "\u26A0 ",
644
+ degradedSummary()
645
+ ]
646
+ }, undefined, true, undefined, this)
647
+ }, undefined, false, undefined, this),
598
648
  /* @__PURE__ */ jsxDEV(SectionHeader, {
599
649
  theme: props.theme,
600
650
  title: "Search Index"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/aft-opencode",
3
- "version": "0.26.1",
3
+ "version": "0.26.2",
4
4
  "type": "module",
5
5
  "description": "OpenCode plugin for Agent File Tools (AFT) — tree-sitter and lsp powered code analysis",
6
6
  "main": "dist/index.js",
@@ -28,7 +28,7 @@
28
28
  },
29
29
  "dependencies": {
30
30
  "@clack/prompts": "^1.2.0",
31
- "@cortexkit/aft-bridge": "0.26.1",
31
+ "@cortexkit/aft-bridge": "0.26.2",
32
32
  "@opencode-ai/plugin": "^1.14.39",
33
33
  "@opencode-ai/sdk": "^1.14.39",
34
34
  "comment-json": "^4.6.2",
@@ -36,11 +36,11 @@
36
36
  "zod": "^4.1.8"
37
37
  },
38
38
  "optionalDependencies": {
39
- "@cortexkit/aft-darwin-arm64": "0.26.1",
40
- "@cortexkit/aft-darwin-x64": "0.26.1",
41
- "@cortexkit/aft-linux-arm64": "0.26.1",
42
- "@cortexkit/aft-linux-x64": "0.26.1",
43
- "@cortexkit/aft-win32-x64": "0.26.1"
39
+ "@cortexkit/aft-darwin-arm64": "0.26.2",
40
+ "@cortexkit/aft-darwin-x64": "0.26.2",
41
+ "@cortexkit/aft-linux-arm64": "0.26.2",
42
+ "@cortexkit/aft-linux-x64": "0.26.2",
43
+ "@cortexkit/aft-win32-x64": "0.26.2"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@types/node": "^22.0.0",
@@ -3,6 +3,17 @@ export interface AftStatusSnapshot {
3
3
  project_root: string | null;
4
4
  canonical_root: string | null;
5
5
  cache_role: string;
6
+ /**
7
+ * True when at least one heavy AFT subsystem has been auto-disabled for
8
+ * the current project root. `degraded_reasons` enumerates why (e.g.
9
+ * `["home_root"]`, `["search_too_many_files:20000"]`). The sidebar / TUI
10
+ * dialog surface this so users know `aft_search`, `aft_navigate` etc.
11
+ * won't return results from this session and can choose to open a
12
+ * project subdirectory instead.
13
+ */
14
+ degraded: boolean;
15
+ /** Machine-readable degraded-mode reasons. Empty when `degraded === false`. */
16
+ degraded_reasons: string[];
6
17
  features: {
7
18
  format_on_edit: boolean;
8
19
  validate_on_edit: string;
@@ -112,6 +123,10 @@ export function coerceAftStatus(response: Record<string, unknown>): AftStatusSna
112
123
  project_root: readNullableString(response.project_root),
113
124
  canonical_root: readNullableString(response.canonical_root),
114
125
  cache_role: readString(response.cache_role, "not_initialized"),
126
+ degraded: readBoolean(response.degraded),
127
+ degraded_reasons: Array.isArray(response.degraded_reasons)
128
+ ? response.degraded_reasons.filter((r): r is string => typeof r === "string")
129
+ : [],
115
130
  features: {
116
131
  format_on_edit: readBoolean(features.format_on_edit),
117
132
  validate_on_edit: readString(features.validate_on_edit, "off"),
@@ -226,6 +226,28 @@ const SidebarContent = (props: {
226
226
  const trigramBytes = () => s()?.disk?.trigram_disk_bytes ?? 0;
227
227
  const semanticBytes = () => s()?.disk?.semantic_disk_bytes ?? 0;
228
228
 
229
+ // Degraded-mode reason → human-readable hint. Distinct strings per reason
230
+ // because the UX direction is different: "home_root" tells the user to
231
+ // open a real project subdirectory, "search_too_many_files" tells them the
232
+ // tree is too big for full indexing.
233
+ const degradedReasonLabel = (reason: string): string => {
234
+ if (reason === "home_root") {
235
+ return "project root is your home directory";
236
+ }
237
+ if (reason.startsWith("search_too_many_files:")) {
238
+ const threshold = reason.split(":")[1] ?? "20000";
239
+ return `project exceeds ${threshold} files`;
240
+ }
241
+ return reason; // unknown reason — surface verbatim so users can grep logs
242
+ };
243
+ const degradedSummary = () => {
244
+ const snap = s();
245
+ if (!snap?.degraded) return null;
246
+ const reasons = snap.degraded_reasons ?? [];
247
+ if (reasons.length === 0) return null;
248
+ return reasons.map(degradedReasonLabel).join("; ");
249
+ };
250
+
229
251
  return (
230
252
  <box
231
253
  width="100%"
@@ -237,16 +259,39 @@ const SidebarContent = (props: {
237
259
  paddingLeft={1}
238
260
  paddingRight={1}
239
261
  >
240
- {/* Header: AFT badge + binary version */}
262
+ {/* Header: AFT badge + binary version + degraded badge (when active) */}
241
263
  <box flexDirection="row" justifyContent="space-between" alignItems="center">
242
- <box paddingLeft={1} paddingRight={1} backgroundColor={props.theme.accent}>
243
- <text fg={props.theme.background}>
244
- <b>AFT</b>
245
- </text>
264
+ <box flexDirection="row" alignItems="center">
265
+ <box paddingLeft={1} paddingRight={1} backgroundColor={props.theme.accent}>
266
+ <text fg={props.theme.background}>
267
+ <b>AFT</b>
268
+ </text>
269
+ </box>
270
+ {s()?.degraded && (
271
+ <box
272
+ paddingLeft={1}
273
+ paddingRight={1}
274
+ marginLeft={1}
275
+ backgroundColor={props.theme.warning}
276
+ >
277
+ <text fg={props.theme.background}>
278
+ <b>DEGRADED</b>
279
+ </text>
280
+ </box>
281
+ )}
246
282
  </box>
247
283
  <text fg={props.theme.textMuted}>v{s()?.version ?? props.pluginVersion}</text>
248
284
  </box>
249
285
 
286
+ {/* Degraded reason — explains why heavy tools (aft_search, aft_navigate)
287
+ are disabled. Surface this prominently so users know to open a real
288
+ project subdirectory if they want full features. */}
289
+ {s()?.degraded && degradedSummary() && (
290
+ <box marginTop={1} width="100%">
291
+ <text fg={props.theme.warning}>⚠ {degradedSummary()}</text>
292
+ </box>
293
+ )}
294
+
250
295
  {/* Search index */}
251
296
  <SectionHeader theme={props.theme} title="Search Index" />
252
297
  <StatRow