@cortexkit/aft-opencode 0.35.1 → 0.35.3

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.
@@ -71,6 +71,36 @@ export interface AutoInstallResult {
71
71
  installsComplete: Promise<void>;
72
72
  }
73
73
  export declare function abortInFlightAutoInstalls(): Promise<void>;
74
+ /**
75
+ * Spawn `npm install --no-save <pkg>@<version>` in the cache dir.
76
+ *
77
+ * Uses `--ignore-scripts` to neutralize lifecycle hooks (the v0.16 audit
78
+ * hardening). Output goes to plugin log.
79
+ *
80
+ * Previously this used `bun add`, but OpenCode runs under whichever runtime
81
+ * the user has installed (Node 22 is common), and bun is not guaranteed to
82
+ * be on PATH. Every install would fail silently with ENOENT, so users
83
+ * without bun saw recurring `lsp_binary_missing` warnings for newer servers
84
+ * like `@vue/language-server` even though `lsp.auto_install` was true.
85
+ * GitHub #46 reported this exact symptom against v0.27.0.
86
+ *
87
+ * npm is guaranteed to be present whenever the plugin runs through OpenCode's
88
+ * normal CLI distribution path, matches Pi's auto-install behavior, and is
89
+ * what OpenCode itself uses for its built-in LSP auto-install.
90
+ */
91
+ /**
92
+ * Anchor an `npm install --no-save` to `cwd` by writing a minimal package.json.
93
+ *
94
+ * Without a package.json in `cwd`, npm walks UP the directory tree; if any
95
+ * ancestor (e.g. ~/package.json) has one, npm installs into THAT package's
96
+ * node_modules instead, leaving our cache dir's node_modules/<server> missing
97
+ * while still exiting 0. The result is a silent install failure and a recurring
98
+ * lsp_binary_missing warning. The old `bun add` flow created this package.json
99
+ * implicitly; npm needs it written explicitly. GitHub #92.
100
+ *
101
+ * Idempotent: only writes when absent. Failures are non-fatal (logged).
102
+ */
103
+ export declare function ensureInstallAnchor(cwd: string): void;
74
104
  /**
75
105
  * Top-level entry point. Returns the list of bin directories that already
76
106
  * have an installed binary AND kicks off background installs for missing
@@ -1 +1 @@
1
- {"version":3,"file":"lsp-auto-install.d.ts","sourceRoot":"","sources":["../src/lsp-auto-install.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AA4BH,+DAA+D;AAC/D,MAAM,WAAW,iBAAiB;IAChC,oCAAoC;IACpC,WAAW,EAAE,OAAO,CAAC;IACrB,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB,8EAA8E;IAC9E,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC3C,kGAAkG;IAClG,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CAC/B;AAED,qCAAqC;AACrC,MAAM,WAAW,iBAAiB;IAChC,mFAAmF;IACnF,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,gDAAgD;IAChD,eAAe,EAAE,MAAM,CAAC;IACxB,yEAAyE;IACzE,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B;;;;;;;OAOG;IACH,OAAO,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/C;;;;;;;;;OASG;IACH,gBAAgB,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;CACjC;AA8DD,wBAAsB,yBAAyB,IAAI,OAAO,CAAC,IAAI,CAAC,CAM/D;AA4XD;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,iBAAiB,EACzB,SAAS,GAAE,OAAO,KAAa,GAC9B,iBAAiB,CAwEnB"}
1
+ {"version":3,"file":"lsp-auto-install.d.ts","sourceRoot":"","sources":["../src/lsp-auto-install.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAsCH,+DAA+D;AAC/D,MAAM,WAAW,iBAAiB;IAChC,oCAAoC;IACpC,WAAW,EAAE,OAAO,CAAC;IACrB,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB,8EAA8E;IAC9E,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC3C,kGAAkG;IAClG,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CAC/B;AAED,qCAAqC;AACrC,MAAM,WAAW,iBAAiB;IAChC,mFAAmF;IACnF,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,gDAAgD;IAChD,eAAe,EAAE,MAAM,CAAC;IACxB,yEAAyE;IACzE,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B;;;;;;;OAOG;IACH,OAAO,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/C;;;;;;;;;OASG;IACH,gBAAgB,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;CACjC;AA8DD,wBAAsB,yBAAyB,IAAI,OAAO,CAAC,IAAI,CAAC,CAM/D;AA4DD;;;;;;;;;;;;;;;;GAgBG;AACH;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAYrD;AA8TD;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,iBAAiB,EACzB,SAAS,GAAE,OAAO,KAAa,GAC9B,iBAAiB,CAwEnB"}
@@ -8,6 +8,23 @@ export interface StatusCompression {
8
8
  project: StatusCompressionAggregate;
9
9
  session: StatusCompressionAggregate;
10
10
  }
11
+ /**
12
+ * The agent status-bar glance — `[AFT E· W· | D· U· C· | T·]`. `errors`/
13
+ * `warnings` are live LSP diagnostics for files touched this session; the
14
+ * Tier-2 trio (`dead_code`/`unused_exports`/`duplicates`) plus `todos` come
15
+ * from the last completed background scan. `tier2_stale` marks those Tier-2
16
+ * counts as predating the latest edit. Null in the snapshot until the Tier-2
17
+ * cache is populated at least once (no fabricated zeros).
18
+ */
19
+ export interface StatusBar {
20
+ errors: number;
21
+ warnings: number;
22
+ dead_code: number;
23
+ unused_exports: number;
24
+ duplicates: number;
25
+ todos: number;
26
+ tier2_stale: boolean;
27
+ }
11
28
  export interface AftStatusSnapshot {
12
29
  version: string;
13
30
  project_root: string | null;
@@ -70,6 +87,12 @@ export interface AftStatusSnapshot {
70
87
  };
71
88
  /** Compression aggregate passthrough; rendering is added separately. */
72
89
  compression?: StatusCompression;
90
+ /**
91
+ * Agent status-bar counts (E/W/D/U/C/T). Undefined until the Tier-2 cache
92
+ * is populated at least once — the sidebar hides the Code Health section
93
+ * until then so it never shows fabricated zeros.
94
+ */
95
+ status_bar?: StatusBar;
73
96
  /**
74
97
  * Human-readable explanation for a synthetic snapshot (e.g.
75
98
  * `cache_role === "not_initialized"`). When the plugin returns a placeholder
@@ -1 +1 @@
1
- {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/shared/status.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/shared/status.ts"],"names":[],"mappings":"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"}
@@ -120,8 +120,17 @@ export declare function projectRootFor(runtime: ToolRuntime): string;
120
120
  * approves is byte-for-byte the path the Rust bridge acts on.
121
121
  */
122
122
  export declare function resolveProjectRoot(ctx: PluginContext, runtime: ToolRuntime): Promise<string>;
123
- /** Resolve a user path exactly as a bridge request will: absolute paths are
124
- * preserved; relative paths are rooted at the session/project root. */
123
+ /**
124
+ * Expand a leading `~` to the user's home directory. Node's `path.resolve`
125
+ * treats `~` as a literal segment, so `~/foo` would otherwise resolve to
126
+ * `<projectRoot>/~/foo`. Applied before any absolute/relative decision so all
127
+ * file tools (read/write/edit/outline/zoom/delete/refactor/imports/safety)
128
+ * accept `~/...` the same way the search tools already do.
129
+ */
130
+ export declare function expandTilde(input: string): string;
131
+ /** Resolve a user path exactly as a bridge request will: `~` expands to home,
132
+ * absolute paths are preserved; relative paths are rooted at the
133
+ * session/project root. */
125
134
  export declare function resolvePathFromProjectRoot(projectRoot: string, target: string): string;
126
135
  export declare function resolvePathArg(ctx: PluginContext, runtime: ToolRuntime, target: string): Promise<string>;
127
136
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"_shared.d.ts","sourceRoot":"","sources":["../../src/tools/_shared.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAQhF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAIjD;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,eAAO,MAAM,WAAW,GAAI,KAAK,MAAM,EAAE,KAAK,MAAM,KAAG,GACR,CAAC;AAEhD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAC/B,CAAC,EAAE,OAAO,EACV,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,GACV,MAAM,GAAG,SAAS,CAWpB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAMpD;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,+BAA+B,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CASlE,CAAC;AAEF,wFAAwF;AACxF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAErE;AA2BD,8EAA8E;AAC9E,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACnC,MAAM,CAsDR;AAsBD;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oDAAoD;IACpD,SAAS,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAkBD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAW3D;AAED;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,MAAM,CAAC,CAKjB;AAED;uEACuE;AACvE,wBAAgB,0BAA0B,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEtF;AAED,wBAAsB,cAAc,CAClC,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,CAEjB;AAED;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,aAAa,EAAE,OAAO,EAAE,WAAW,GAAG,YAAY,CAEhF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,UAAU,CAC9B,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,MAAM,EACf,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACpC,OAAO,CAAC,EAAE,oBAAoB,GAC7B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CA0BlC;AAED;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAAC,GAAG,EAAE,aAAa,EAAE,OAAO,EAAE,WAAW,GAAG,IAAI,CAE9F"}
1
+ {"version":3,"file":"_shared.d.ts","sourceRoot":"","sources":["../../src/tools/_shared.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH,OAAO,KAAK,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAQhF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAIjD;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,eAAO,MAAM,WAAW,GAAI,KAAK,MAAM,EAAE,KAAK,MAAM,KAAG,GACR,CAAC;AAEhD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAC/B,CAAC,EAAE,OAAO,EACV,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,GACV,MAAM,GAAG,SAAS,CAWpB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAMpD;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,+BAA+B,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CASlE,CAAC;AAEF,wFAAwF;AACxF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAErE;AA2BD,8EAA8E;AAC9E,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACnC,MAAM,CAsDR;AAsBD;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oDAAoD;IACpD,SAAS,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAkBD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAW3D;AAED;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,MAAM,CAAC,CAKjB;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQjD;AAED;;2BAE2B;AAC3B,wBAAgB,0BAA0B,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAGtF;AAED,wBAAsB,cAAc,CAClC,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,CAEjB;AAED;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,aAAa,EAAE,OAAO,EAAE,WAAW,GAAG,YAAY,CAEhF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,UAAU,CAC9B,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,MAAM,EACf,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACpC,OAAO,CAAC,EAAE,oBAAoB,GAC7B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CA0BlC;AAED;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAAC,GAAG,EAAE,aAAa,EAAE,OAAO,EAAE,WAAW,GAAG,IAAI,CAE9F"}
@@ -1 +1 @@
1
- {"version":3,"file":"hoisted.d.ts","sourceRoot":"","sources":["../../src/tools/hoisted.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAK1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AA8EjD,wEAAwE;AACxE,eAAO,MAAM,wBAAwB,GAAI,IAAI,MAAM,EAAE,QAAQ,MAAM,EAAE,OAAO,MAAM,KAAG,MAChD,CAAC;AA0RtC;;GAEG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,CAmKjE;AAmnCD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CA0B/E;AAMD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CA+FnF"}
1
+ {"version":3,"file":"hoisted.d.ts","sourceRoot":"","sources":["../../src/tools/hoisted.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAK1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AA8EjD,wEAAwE;AACxE,eAAO,MAAM,wBAAwB,GAAI,IAAI,MAAM,EAAE,QAAQ,MAAM,EAAE,OAAO,MAAM,KAAG,MAChD,CAAC;AA0RtC;;GAEG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,CAmKjE;AAknCD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CA0B/E;AAMD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CA+FnF"}
@@ -1 +1 @@
1
- {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/tools/search.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAsIjD;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CA0BrD;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CA4I9E"}
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/tools/search.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AA2HjD;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CA0BrD;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CA4I9E"}
@@ -1 +1 @@
1
- {"version":3,"file":"semantic.d.ts","sourceRoot":"","sources":["../../src/tools/semantic.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAqBjD,wBAAgB,aAAa,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CA6FhF"}
1
+ {"version":3,"file":"semantic.d.ts","sourceRoot":"","sources":["../../src/tools/semantic.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAiCjD,wBAAgB,aAAa,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAyFhF"}
package/dist/tui.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // src/tui/index.tsx
2
2
  import { createMemo as createMemo2, createSignal as createSignal2, onCleanup as onCleanup2 } from "solid-js";
3
3
  // package.json
4
- var version = "0.35.1";
4
+ var version = "0.35.3";
5
5
 
6
6
  // src/shared/rpc-client.ts
7
7
  import { existsSync, readdirSync, readFileSync, unlinkSync } from "node:fs";
@@ -343,6 +343,20 @@ function readCompression(value) {
343
343
  session: readCompressionAggregate(compression.session)
344
344
  };
345
345
  }
346
+ function readStatusBar(value) {
347
+ if (typeof value !== "object" || value === null)
348
+ return;
349
+ const bar = asRecord(value);
350
+ return {
351
+ errors: readNumber(bar.errors),
352
+ warnings: readNumber(bar.warnings),
353
+ dead_code: readNumber(bar.dead_code),
354
+ unused_exports: readNumber(bar.unused_exports),
355
+ duplicates: readNumber(bar.duplicates),
356
+ todos: readNumber(bar.todos),
357
+ tier2_stale: readBoolean(bar.tier2_stale)
358
+ };
359
+ }
346
360
  function formatSemanticIndexStatus(status, stage) {
347
361
  if ((status === "loading" || status === "building") && stage === "fingerprint_change") {
348
362
  return "Rebuilding (model changed)";
@@ -430,6 +444,7 @@ function coerceAftStatus(response) {
430
444
  checkpoints: readNumber(session.checkpoints)
431
445
  },
432
446
  compression: readCompression(response.compression),
447
+ status_bar: readStatusBar(response.status_bar),
433
448
  message: readString(response.message, "")
434
449
  };
435
450
  }
@@ -591,6 +606,14 @@ function collapsedCompressionValue(compression) {
591
606
  const pct = original_tokens > 0 ? Math.round(savings_tokens / original_tokens * 100) : 0;
592
607
  return `${formatCount(savings_tokens)} / ${pct}%`;
593
608
  }
609
+ function collapsedHealthLights(statusBar) {
610
+ if (!statusBar)
611
+ return null;
612
+ const diagnostics = statusBar.errors > 0 ? "err" : statusBar.warnings > 0 ? "warn" : "ok";
613
+ const code = statusBar.duplicates > 0 ? "warn" : "ok";
614
+ const todos = statusBar.todos > 0 ? "warn" : "ok";
615
+ return { diagnostics, code, todos };
616
+ }
594
617
  function resolveTuiStorageDir() {
595
618
  return resolveCortexKitStorageRoot();
596
619
  }
@@ -755,6 +778,7 @@ var SidebarContent = (props) => {
755
778
  const trigramBytes = () => s()?.disk?.trigram_disk_bytes ?? 0;
756
779
  const semanticBytes = () => s()?.disk?.semantic_disk_bytes ?? 0;
757
780
  const compressionRows = () => formatCompressionSidebarRows(s()?.compression);
781
+ const statusBar = () => s()?.status_bar;
758
782
  const degradedReasonLabel = (reason) => {
759
783
  if (reason === "home_root") {
760
784
  return "project root is your home directory";
@@ -873,6 +897,28 @@ var SidebarContent = (props) => {
873
897
  children: "●"
874
898
  }, undefined, false, undefined, this)
875
899
  }, undefined, false, undefined, this),
900
+ collapsedHealthLights(statusBar()) && /* @__PURE__ */ jsxDEV(CollapsedRow, {
901
+ theme: props.theme,
902
+ label: "Code Health",
903
+ children: /* @__PURE__ */ jsxDEV("box", {
904
+ flexDirection: "row",
905
+ gap: 1,
906
+ children: [
907
+ /* @__PURE__ */ jsxDEV("text", {
908
+ fg: toneColor(props.theme, collapsedHealthLights(statusBar()).diagnostics),
909
+ children: "●"
910
+ }, undefined, false, undefined, this),
911
+ /* @__PURE__ */ jsxDEV("text", {
912
+ fg: toneColor(props.theme, collapsedHealthLights(statusBar()).code),
913
+ children: "●"
914
+ }, undefined, false, undefined, this),
915
+ /* @__PURE__ */ jsxDEV("text", {
916
+ fg: toneColor(props.theme, collapsedHealthLights(statusBar()).todos),
917
+ children: "●"
918
+ }, undefined, false, undefined, this)
919
+ ]
920
+ }, undefined, true, undefined, this)
921
+ }, undefined, false, undefined, this),
876
922
  collapsedCompressionValue(s()?.compression) && /* @__PURE__ */ jsxDEV(CollapsedRow, {
877
923
  theme: props.theme,
878
924
  label: "Compression",
@@ -944,6 +990,38 @@ var SidebarContent = (props) => {
944
990
  value: formatBytes2(semanticBytes()),
945
991
  tone: "muted"
946
992
  }, undefined, false, undefined, this),
993
+ statusBar() && /* @__PURE__ */ jsxDEV(Fragment, {
994
+ children: [
995
+ /* @__PURE__ */ jsxDEV(SectionHeader, {
996
+ theme: props.theme,
997
+ title: statusBar().tier2_stale ? "Code Health ~" : "Code Health"
998
+ }, undefined, false, undefined, this),
999
+ /* @__PURE__ */ jsxDEV(StatRow, {
1000
+ theme: props.theme,
1001
+ label: "Errors",
1002
+ value: formatCount(statusBar().errors),
1003
+ tone: statusBar().errors > 0 ? "err" : "muted"
1004
+ }, undefined, false, undefined, this),
1005
+ /* @__PURE__ */ jsxDEV(StatRow, {
1006
+ theme: props.theme,
1007
+ label: "Warnings",
1008
+ value: formatCount(statusBar().warnings),
1009
+ tone: statusBar().warnings > 0 ? "warn" : "muted"
1010
+ }, undefined, false, undefined, this),
1011
+ /* @__PURE__ */ jsxDEV(StatRow, {
1012
+ theme: props.theme,
1013
+ label: "Duplicates",
1014
+ value: formatCount(statusBar().duplicates),
1015
+ tone: "muted"
1016
+ }, undefined, false, undefined, this),
1017
+ /* @__PURE__ */ jsxDEV(StatRow, {
1018
+ theme: props.theme,
1019
+ label: "TODOs",
1020
+ value: formatCount(statusBar().todos),
1021
+ tone: "muted"
1022
+ }, undefined, false, undefined, this)
1023
+ ]
1024
+ }, undefined, true, undefined, this),
947
1025
  compressionRows().length > 0 && /* @__PURE__ */ jsxDEV(Fragment, {
948
1026
  children: [
949
1027
  /* @__PURE__ */ jsxDEV(SectionHeader, {
@@ -1433,6 +1511,43 @@ var StatusDialog = (props) => {
1433
1511
  }, undefined, false, undefined, this))
1434
1512
  ]
1435
1513
  }, undefined, true, undefined, this) : null,
1514
+ status()?.status_bar ? /* @__PURE__ */ jsxDEV2("box", {
1515
+ flexDirection: "column",
1516
+ width: "100%",
1517
+ marginTop: 1,
1518
+ children: [
1519
+ /* @__PURE__ */ jsxDEV2("text", {
1520
+ fg: t().text,
1521
+ children: /* @__PURE__ */ jsxDEV2("b", {
1522
+ children: status().status_bar.tier2_stale ? "Code Health ~" : "Code Health"
1523
+ }, undefined, false, undefined, this)
1524
+ }, undefined, false, undefined, this),
1525
+ /* @__PURE__ */ jsxDEV2(R, {
1526
+ theme: t(),
1527
+ label: "Errors",
1528
+ value: formatCountShort(status().status_bar.errors),
1529
+ tone: status().status_bar.errors > 0 ? "err" : "muted"
1530
+ }, undefined, false, undefined, this),
1531
+ /* @__PURE__ */ jsxDEV2(R, {
1532
+ theme: t(),
1533
+ label: "Warnings",
1534
+ value: formatCountShort(status().status_bar.warnings),
1535
+ tone: status().status_bar.warnings > 0 ? "warn" : "muted"
1536
+ }, undefined, false, undefined, this),
1537
+ /* @__PURE__ */ jsxDEV2(R, {
1538
+ theme: t(),
1539
+ label: "Duplicates",
1540
+ value: formatCountShort(status().status_bar.duplicates),
1541
+ tone: "muted"
1542
+ }, undefined, false, undefined, this),
1543
+ /* @__PURE__ */ jsxDEV2(R, {
1544
+ theme: t(),
1545
+ label: "TODOs",
1546
+ value: formatCountShort(status().status_bar.todos),
1547
+ tone: "muted"
1548
+ }, undefined, false, undefined, this)
1549
+ ]
1550
+ }, undefined, true, undefined, this) : null,
1436
1551
  /* @__PURE__ */ jsxDEV2("box", {
1437
1552
  marginTop: 1,
1438
1553
  justifyContent: "flex-end",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/aft-opencode",
3
- "version": "0.35.1",
3
+ "version": "0.35.3",
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",
@@ -30,19 +30,19 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "@clack/prompts": "^1.2.0",
33
- "@cortexkit/aft-bridge": "0.35.1",
33
+ "@cortexkit/aft-bridge": "0.35.3",
34
34
  "@xterm/headless": "^5.5.0",
35
35
  "comment-json": "^4.6.2",
36
36
  "undici": "^7.25.0",
37
37
  "zod": "^4.1.8"
38
38
  },
39
39
  "optionalDependencies": {
40
- "@cortexkit/aft-darwin-arm64": "0.35.1",
41
- "@cortexkit/aft-darwin-x64": "0.35.1",
42
- "@cortexkit/aft-linux-arm64": "0.35.1",
43
- "@cortexkit/aft-linux-x64": "0.35.1",
44
- "@cortexkit/aft-win32-arm64": "0.35.1",
45
- "@cortexkit/aft-win32-x64": "0.35.1"
40
+ "@cortexkit/aft-darwin-arm64": "0.35.3",
41
+ "@cortexkit/aft-darwin-x64": "0.35.3",
42
+ "@cortexkit/aft-linux-arm64": "0.35.3",
43
+ "@cortexkit/aft-linux-x64": "0.35.3",
44
+ "@cortexkit/aft-win32-arm64": "0.35.3",
45
+ "@cortexkit/aft-win32-x64": "0.35.3"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@opencode-ai/plugin": "^1.15.11",
@@ -10,6 +10,24 @@ export interface StatusCompression {
10
10
  session: StatusCompressionAggregate;
11
11
  }
12
12
 
13
+ /**
14
+ * The agent status-bar glance — `[AFT E· W· | D· U· C· | T·]`. `errors`/
15
+ * `warnings` are live LSP diagnostics for files touched this session; the
16
+ * Tier-2 trio (`dead_code`/`unused_exports`/`duplicates`) plus `todos` come
17
+ * from the last completed background scan. `tier2_stale` marks those Tier-2
18
+ * counts as predating the latest edit. Null in the snapshot until the Tier-2
19
+ * cache is populated at least once (no fabricated zeros).
20
+ */
21
+ export interface StatusBar {
22
+ errors: number;
23
+ warnings: number;
24
+ dead_code: number;
25
+ unused_exports: number;
26
+ duplicates: number;
27
+ todos: number;
28
+ tier2_stale: boolean;
29
+ }
30
+
13
31
  export interface AftStatusSnapshot {
14
32
  version: string;
15
33
  project_root: string | null;
@@ -72,6 +90,12 @@ export interface AftStatusSnapshot {
72
90
  };
73
91
  /** Compression aggregate passthrough; rendering is added separately. */
74
92
  compression?: StatusCompression;
93
+ /**
94
+ * Agent status-bar counts (E/W/D/U/C/T). Undefined until the Tier-2 cache
95
+ * is populated at least once — the sidebar hides the Code Health section
96
+ * until then so it never shows fabricated zeros.
97
+ */
98
+ status_bar?: StatusBar;
75
99
  /**
76
100
  * Human-readable explanation for a synthetic snapshot (e.g.
77
101
  * `cache_role === "not_initialized"`). When the plugin returns a placeholder
@@ -125,6 +149,23 @@ function readCompression(value: unknown): StatusCompression | undefined {
125
149
  };
126
150
  }
127
151
 
152
+ function readStatusBar(value: unknown): StatusBar | undefined {
153
+ // Null until Tier-2 is populated — the Rust snapshot emits JSON null, which
154
+ // is not an object, so this returns undefined and the sidebar hides the
155
+ // Code Health section (no fabricated zeros).
156
+ if (typeof value !== "object" || value === null) return undefined;
157
+ const bar = asRecord(value);
158
+ return {
159
+ errors: readNumber(bar.errors),
160
+ warnings: readNumber(bar.warnings),
161
+ dead_code: readNumber(bar.dead_code),
162
+ unused_exports: readNumber(bar.unused_exports),
163
+ duplicates: readNumber(bar.duplicates),
164
+ todos: readNumber(bar.todos),
165
+ tier2_stale: readBoolean(bar.tier2_stale),
166
+ };
167
+ }
168
+
128
169
  function formatFlag(enabled: boolean): string {
129
170
  return enabled ? "enabled" : "disabled";
130
171
  }
@@ -227,6 +268,7 @@ export function coerceAftStatus(response: Record<string, unknown>): AftStatusSna
227
268
  checkpoints: readNumber(session.checkpoints),
228
269
  },
229
270
  compression: readCompression(response.compression),
271
+ status_bar: readStatusBar(response.status_bar),
230
272
  message: readString(response.message, ""),
231
273
  };
232
274
  }
@@ -288,6 +330,21 @@ export function formatStatusDialogMessage(status: AftStatusSnapshot): string {
288
330
  lines.push(`- storage dir: ${status.storage_dir ?? status.disk.storage_dir}`);
289
331
  }
290
332
 
333
+ if (status.status_bar) {
334
+ const sb = status.status_bar;
335
+ lines.push(
336
+ "",
337
+ `Code Health${sb.tier2_stale ? " (~ stale)" : ""}`,
338
+ `- errors: ${formatCount(sb.errors)}`,
339
+ `- warnings: ${formatCount(sb.warnings)}`,
340
+ // dead code / unused exports hidden until oxc resolver lands (restore both)
341
+ // `- dead code: ${formatCount(sb.dead_code)}`,
342
+ // `- unused exports: ${formatCount(sb.unused_exports)}`,
343
+ `- duplicates: ${formatCount(sb.duplicates)}`,
344
+ `- todos: ${formatCount(sb.todos)}`,
345
+ );
346
+ }
347
+
291
348
  lines.push(
292
349
  "",
293
350
  "Current session",
@@ -383,6 +440,21 @@ export function formatStatusMarkdown(status: AftStatusSnapshot): string {
383
440
  lines.push(`- **Storage dir:** \`${status.storage_dir ?? status.disk.storage_dir}\``);
384
441
  }
385
442
 
443
+ if (status.status_bar) {
444
+ const sb = status.status_bar;
445
+ lines.push(
446
+ "",
447
+ `### Code Health${sb.tier2_stale ? " (~ stale)" : ""}`,
448
+ `- **Errors:** ${formatCount(sb.errors)}`,
449
+ `- **Warnings:** ${formatCount(sb.warnings)}`,
450
+ // dead code / unused exports hidden until oxc resolver lands (restore both)
451
+ // `- **Dead code:** ${formatCount(sb.dead_code)}`,
452
+ // `- **Unused exports:** ${formatCount(sb.unused_exports)}`,
453
+ `- **Duplicates:** ${formatCount(sb.duplicates)}`,
454
+ `- **TODOs:** ${formatCount(sb.todos)}`,
455
+ );
456
+ }
457
+
386
458
  lines.push(
387
459
  "",
388
460
  "### Current session",
package/src/tui/index.tsx CHANGED
@@ -466,6 +466,57 @@ const StatusDialog = (props: StatusDialogProps) => {
466
466
  </box>
467
467
  ) : null}
468
468
 
469
+ {/* Code Health — the agent status-bar glance (E/W/D/U/C/T), surfaced so
470
+ users see the same view agents get. Hidden until the Tier-2 cache is
471
+ populated (status_bar undefined) so it never shows fabricated zeros.
472
+ A `~` on the header flags the Tier-2 counts as predating the latest
473
+ edit. */}
474
+ {status()?.status_bar ? (
475
+ <box flexDirection="column" width="100%" marginTop={1}>
476
+ <text fg={t().text}>
477
+ <b>{status()!.status_bar!.tier2_stale ? "Code Health ~" : "Code Health"}</b>
478
+ </text>
479
+ <R
480
+ theme={t()}
481
+ label="Errors"
482
+ value={formatCountShort(status()!.status_bar!.errors)}
483
+ tone={status()!.status_bar!.errors > 0 ? "err" : "muted"}
484
+ />
485
+ <R
486
+ theme={t()}
487
+ label="Warnings"
488
+ value={formatCountShort(status()!.status_bar!.warnings)}
489
+ tone={status()!.status_bar!.warnings > 0 ? "warn" : "muted"}
490
+ />
491
+ {/* Dead Code / Unused Exports hidden until the oxc resolver lands
492
+ (current scanner over-reports on barrel re-exports). Restore both. */}
493
+ {/* <R
494
+ theme={t()}
495
+ label="Dead Code"
496
+ value={formatCountShort(status()!.status_bar!.dead_code)}
497
+ tone="muted"
498
+ />
499
+ <R
500
+ theme={t()}
501
+ label="Unused Exports"
502
+ value={formatCountShort(status()!.status_bar!.unused_exports)}
503
+ tone="muted"
504
+ /> */}
505
+ <R
506
+ theme={t()}
507
+ label="Duplicates"
508
+ value={formatCountShort(status()!.status_bar!.duplicates)}
509
+ tone="muted"
510
+ />
511
+ <R
512
+ theme={t()}
513
+ label="TODOs"
514
+ value={formatCountShort(status()!.status_bar!.todos)}
515
+ tone="muted"
516
+ />
517
+ </box>
518
+ ) : null}
519
+
469
520
  {/* Footer */}
470
521
  <box marginTop={1} justifyContent="flex-end" width="100%">
471
522
  <text fg={t().textMuted}>Enter or Esc to close</text>
@@ -16,6 +16,7 @@ import {
16
16
  coerceAftStatus,
17
17
  formatSemanticIndexStatus,
18
18
  formatSemanticRefreshing,
19
+ type StatusBar,
19
20
  type StatusCompression,
20
21
  } from "../shared/status";
21
22
  import { resolveCortexKitStorageRoot } from "../shared/storage-paths";
@@ -179,6 +180,34 @@ export function collapsedCompressionValue(
179
180
  return `${formatCount(savings_tokens)} / ${pct}%`;
180
181
  }
181
182
 
183
+ export type HealthLightTone = "ok" | "warn" | "err";
184
+
185
+ export interface HealthLights {
186
+ // Diagnostics: red if any errors, yellow if any warnings, else green.
187
+ diagnostics: HealthLightTone;
188
+ // Code cruft: yellow if there is any duplicate, green when zero. Never red —
189
+ // cruft is not a build failure. NOTE: dead_code / unused_exports are
190
+ // temporarily excluded from this signal (and the expanded rows below) until
191
+ // the oxc-based resolver lands and makes those counts trustworthy on real
192
+ // TS/JS codebases. Restore them here when that engine ships.
193
+ code: HealthLightTone;
194
+ // TODOs: yellow if any, else green.
195
+ todos: HealthLightTone;
196
+ }
197
+
198
+ // Three traffic lights for the collapsed view, replacing the cramped
199
+ // `E· W· | D· U· C· | T·` string. Returns null until populated.
200
+ export function collapsedHealthLights(statusBar: StatusBar | undefined): HealthLights | null {
201
+ if (!statusBar) return null;
202
+ const diagnostics: HealthLightTone =
203
+ statusBar.errors > 0 ? "err" : statusBar.warnings > 0 ? "warn" : "ok";
204
+ const code: HealthLightTone =
205
+ // statusBar.dead_code > 0 || statusBar.unused_exports > 0 || // restore with oxc engine
206
+ statusBar.duplicates > 0 ? "warn" : "ok";
207
+ const todos: HealthLightTone = statusBar.todos > 0 ? "warn" : "ok";
208
+ return { diagnostics, code, todos };
209
+ }
210
+
182
211
  // v0.27 moved AFT storage to the CortexKit root. TUI code must use a
183
212
  // lightweight local path helper rather than the shared bridge barrel, which
184
213
  // also exports URL-fetch helpers unsuitable for Bun's TUI runtime.
@@ -426,6 +455,7 @@ const SidebarContent = (props: {
426
455
  const trigramBytes = () => s()?.disk?.trigram_disk_bytes ?? 0;
427
456
  const semanticBytes = () => s()?.disk?.semantic_disk_bytes ?? 0;
428
457
  const compressionRows = () => formatCompressionSidebarRows(s()?.compression);
458
+ const statusBar = () => s()?.status_bar;
429
459
 
430
460
  // Degraded-mode reason → human-readable hint. Distinct strings per reason
431
461
  // because the UX direction is different: "home_root" tells the user to
@@ -537,6 +567,19 @@ const SidebarContent = (props: {
537
567
  <CollapsedRow theme={props.theme} label="Semantic Index">
538
568
  <text fg={toneColor(props.theme, semanticStatus().tone)}>●</text>
539
569
  </CollapsedRow>
570
+ {collapsedHealthLights(statusBar()) && (
571
+ <CollapsedRow theme={props.theme} label="Code Health">
572
+ <box flexDirection="row" gap={1}>
573
+ <text fg={toneColor(props.theme, collapsedHealthLights(statusBar())!.diagnostics)}>
574
+
575
+ </text>
576
+ <text fg={toneColor(props.theme, collapsedHealthLights(statusBar())!.code)}>●</text>
577
+ <text fg={toneColor(props.theme, collapsedHealthLights(statusBar())!.todos)}>
578
+
579
+ </text>
580
+ </box>
581
+ </CollapsedRow>
582
+ )}
540
583
  {collapsedCompressionValue(s()?.compression) && (
541
584
  <CollapsedRow theme={props.theme} label="Compression">
542
585
  <text fg={props.theme.textMuted}>
@@ -614,6 +657,61 @@ const SidebarContent = (props: {
614
657
  tone="muted"
615
658
  />
616
659
 
660
+ {/* Code Health — the agent status-bar glance, surfaced for users.
661
+ Hidden until the Tier-2 cache is populated (status_bar undefined),
662
+ so it never shows fabricated zeros. Errors/warnings are live LSP
663
+ diagnostics; D/U/C/T come from the last background scan. A `~` on
664
+ the section header flags the Tier-2 counts as predating the latest
665
+ edit. */}
666
+ {statusBar() && (
667
+ <>
668
+ <SectionHeader
669
+ theme={props.theme}
670
+ title={statusBar()!.tier2_stale ? "Code Health ~" : "Code Health"}
671
+ />
672
+ <StatRow
673
+ theme={props.theme}
674
+ label="Errors"
675
+ value={formatCount(statusBar()!.errors)}
676
+ tone={statusBar()!.errors > 0 ? "err" : "muted"}
677
+ />
678
+ <StatRow
679
+ theme={props.theme}
680
+ label="Warnings"
681
+ value={formatCount(statusBar()!.warnings)}
682
+ tone={statusBar()!.warnings > 0 ? "warn" : "muted"}
683
+ />
684
+ {/* Dead Code / Unused Exports temporarily hidden until the
685
+ oxc-based resolver lands and makes these trustworthy on real
686
+ TS/JS codebases (current tree-sitter scanner over-reports via
687
+ barrel re-export gaps). Restore both rows when that ships. */}
688
+ {/* <StatRow
689
+ theme={props.theme}
690
+ label="Dead Code"
691
+ value={formatCount(statusBar()!.dead_code)}
692
+ tone="muted"
693
+ />
694
+ <StatRow
695
+ theme={props.theme}
696
+ label="Unused Exports"
697
+ value={formatCount(statusBar()!.unused_exports)}
698
+ tone="muted"
699
+ /> */}
700
+ <StatRow
701
+ theme={props.theme}
702
+ label="Duplicates"
703
+ value={formatCount(statusBar()!.duplicates)}
704
+ tone="muted"
705
+ />
706
+ <StatRow
707
+ theme={props.theme}
708
+ label="TODOs"
709
+ value={formatCount(statusBar()!.todos)}
710
+ tone="muted"
711
+ />
712
+ </>
713
+ )}
714
+
617
715
  {/* Compression aggregates. Tabular layout matching Search/Semantic
618
716
  Index above: each scope ("Session", "Project") renders as a
619
717
  subheader followed by two StatRows (Tokens Saved, Compression