@cortexkit/aft-opencode 0.35.2 → 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.
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +58 -21
- package/dist/lsp-auto-install.d.ts +30 -0
- package/dist/lsp-auto-install.d.ts.map +1 -1
- package/dist/shared/status.d.ts +23 -0
- package/dist/shared/status.d.ts.map +1 -1
- package/dist/tui.js +116 -1
- package/package.json +8 -8
- package/src/shared/status.ts +72 -0
- package/src/tui/index.tsx +51 -0
- package/src/tui/sidebar.tsx +98 -0
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AA2MlD;;;;;;;;;;;;;;;;;GAiBG;AAQH,QAAA,MAAM,MAAM,EAAE,MAA6D,CAAC;AA80B5E,eAAe,MAAM,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -12064,7 +12064,6 @@ class BinaryBridge {
|
|
|
12064
12064
|
if (!snapshot || typeof snapshot !== "object" || Array.isArray(snapshot))
|
|
12065
12065
|
return;
|
|
12066
12066
|
this.cachedStatus = snapshot;
|
|
12067
|
-
this.logVia("Received status_changed push frame; cached AFT status snapshot");
|
|
12068
12067
|
for (const listener of this.statusListeners) {
|
|
12069
12068
|
this.deliverStatusSnapshot(listener, this.cachedStatus);
|
|
12070
12069
|
}
|
|
@@ -30598,7 +30597,16 @@ function showToast(ctx, title, message, variant = "info", duration3 = 3000) {
|
|
|
30598
30597
|
// src/lsp-auto-install.ts
|
|
30599
30598
|
import { spawn as spawn3 } from "node:child_process";
|
|
30600
30599
|
import { createHash as createHash4 } from "node:crypto";
|
|
30601
|
-
import {
|
|
30600
|
+
import {
|
|
30601
|
+
createReadStream,
|
|
30602
|
+
existsSync as existsSync12,
|
|
30603
|
+
mkdirSync as mkdirSync8,
|
|
30604
|
+
readFileSync as readFileSync11,
|
|
30605
|
+
renameSync as renameSync6,
|
|
30606
|
+
rmSync as rmSync5,
|
|
30607
|
+
statSync as statSync6,
|
|
30608
|
+
writeFileSync as writeFileSync8
|
|
30609
|
+
} from "node:fs";
|
|
30602
30610
|
import { join as join16 } from "node:path";
|
|
30603
30611
|
|
|
30604
30612
|
// src/lsp-cache.ts
|
|
@@ -31173,6 +31181,17 @@ async function resolveTargetVersion(spec, config2, fetchImpl = fetch) {
|
|
|
31173
31181
|
writeVersionCheck(spec.npm, probe.version);
|
|
31174
31182
|
return { version: probe.version, pinned: false, probe };
|
|
31175
31183
|
}
|
|
31184
|
+
function ensureInstallAnchor(cwd) {
|
|
31185
|
+
try {
|
|
31186
|
+
const stub = join16(cwd, "package.json");
|
|
31187
|
+
if (!existsSync12(stub)) {
|
|
31188
|
+
writeFileSync8(stub, `${JSON.stringify({ name: "aft-lsp-cache", version: "0.0.0", private: true })}
|
|
31189
|
+
`);
|
|
31190
|
+
}
|
|
31191
|
+
} catch (err) {
|
|
31192
|
+
warn2(`[lsp] could not write package.json stub in ${cwd}: ${err}`);
|
|
31193
|
+
}
|
|
31194
|
+
}
|
|
31176
31195
|
function runInstall(spec, version2, cwd, signal) {
|
|
31177
31196
|
return new Promise((resolve3) => {
|
|
31178
31197
|
const target = `${spec.npm}@${version2}`;
|
|
@@ -31188,6 +31207,7 @@ function runInstall(spec, version2, cwd, signal) {
|
|
|
31188
31207
|
resolve3(false);
|
|
31189
31208
|
return;
|
|
31190
31209
|
}
|
|
31210
|
+
ensureInstallAnchor(cwd);
|
|
31191
31211
|
const child = spawn3(npm.command, ["install", "--no-save", "--ignore-scripts", "--silent", target], {
|
|
31192
31212
|
stdio: ["ignore", "pipe", "pipe"],
|
|
31193
31213
|
cwd,
|
|
@@ -31441,7 +31461,7 @@ import {
|
|
|
31441
31461
|
copyFileSync as copyFileSync4,
|
|
31442
31462
|
createReadStream as createReadStream2,
|
|
31443
31463
|
createWriteStream as createWriteStream3,
|
|
31444
|
-
existsSync as
|
|
31464
|
+
existsSync as existsSync13,
|
|
31445
31465
|
lstatSync as lstatSync2,
|
|
31446
31466
|
mkdirSync as mkdirSync9,
|
|
31447
31467
|
readdirSync as readdirSync4,
|
|
@@ -31452,7 +31472,7 @@ import {
|
|
|
31452
31472
|
rmSync as rmSync6,
|
|
31453
31473
|
statSync as statSync7,
|
|
31454
31474
|
unlinkSync as unlinkSync6,
|
|
31455
|
-
writeFileSync as
|
|
31475
|
+
writeFileSync as writeFileSync9
|
|
31456
31476
|
} from "node:fs";
|
|
31457
31477
|
import { dirname as dirname8, join as join17, relative as relative2, resolve as resolve3 } from "node:path";
|
|
31458
31478
|
import { Readable as Readable3 } from "node:stream";
|
|
@@ -31603,7 +31623,7 @@ function writeGithubInstalledMetaIn(installDir, version2, binarySha256, archiveS
|
|
|
31603
31623
|
binarySha256,
|
|
31604
31624
|
...archiveSha256 ? { archiveSha256 } : {}
|
|
31605
31625
|
};
|
|
31606
|
-
|
|
31626
|
+
writeFileSync9(join17(installDir, INSTALLED_META_FILE2), JSON.stringify(meta3), "utf8");
|
|
31607
31627
|
} catch (err) {
|
|
31608
31628
|
warn2(`[lsp] failed to write github installed metadata in ${installDir}: ${err}`);
|
|
31609
31629
|
}
|
|
@@ -32072,7 +32092,7 @@ async function downloadAndInstall(spec, tag, assets, platform3, arch, fetchImpl,
|
|
|
32072
32092
|
} catch {}
|
|
32073
32093
|
}
|
|
32074
32094
|
const innerBinaryPath = join17(extractDir, spec.binaryPathInArchive(platform3, arch, version2));
|
|
32075
|
-
if (!
|
|
32095
|
+
if (!existsSync13(innerBinaryPath)) {
|
|
32076
32096
|
error2(`[lsp] ${spec.id}: extracted binary not found at ${innerBinaryPath}`);
|
|
32077
32097
|
return null;
|
|
32078
32098
|
}
|
|
@@ -32156,7 +32176,7 @@ function runGithubAutoInstall(relevantServers, config2, fetchImpl = fetch) {
|
|
|
32156
32176
|
if (!host) {
|
|
32157
32177
|
for (const spec of GITHUB_LSP_TABLE) {
|
|
32158
32178
|
try {
|
|
32159
|
-
if (
|
|
32179
|
+
if (existsSync13(ghBinDir(spec))) {
|
|
32160
32180
|
cachedBinDirs.push(ghBinDir(spec));
|
|
32161
32181
|
}
|
|
32162
32182
|
} catch {}
|
|
@@ -32403,7 +32423,7 @@ function writeTerminal(terminal, data) {
|
|
|
32403
32423
|
|
|
32404
32424
|
// src/shared/rpc-server.ts
|
|
32405
32425
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
32406
|
-
import { mkdirSync as mkdirSync10, renameSync as renameSync8, unlinkSync as unlinkSync7, writeFileSync as
|
|
32426
|
+
import { mkdirSync as mkdirSync10, renameSync as renameSync8, unlinkSync as unlinkSync7, writeFileSync as writeFileSync10 } from "node:fs";
|
|
32407
32427
|
import { createServer } from "node:http";
|
|
32408
32428
|
import { dirname as dirname9, join as join19 } from "node:path";
|
|
32409
32429
|
|
|
@@ -32456,7 +32476,7 @@ class AftRpcServer {
|
|
|
32456
32476
|
const dir = dirname9(this.portFilePath);
|
|
32457
32477
|
mkdirSync10(dir, { recursive: true, mode: 448 });
|
|
32458
32478
|
const tmpPath = `${this.portFilePath}.tmp`;
|
|
32459
|
-
|
|
32479
|
+
writeFileSync10(tmpPath, JSON.stringify({ port: this.port, token: this.token }), {
|
|
32460
32480
|
encoding: "utf-8",
|
|
32461
32481
|
mode: 384
|
|
32462
32482
|
});
|
|
@@ -32632,6 +32652,20 @@ function readCompression(value) {
|
|
|
32632
32652
|
session: readCompressionAggregate(compression.session)
|
|
32633
32653
|
};
|
|
32634
32654
|
}
|
|
32655
|
+
function readStatusBar(value) {
|
|
32656
|
+
if (typeof value !== "object" || value === null)
|
|
32657
|
+
return;
|
|
32658
|
+
const bar = asRecord(value);
|
|
32659
|
+
return {
|
|
32660
|
+
errors: readNumber(bar.errors),
|
|
32661
|
+
warnings: readNumber(bar.warnings),
|
|
32662
|
+
dead_code: readNumber(bar.dead_code),
|
|
32663
|
+
unused_exports: readNumber(bar.unused_exports),
|
|
32664
|
+
duplicates: readNumber(bar.duplicates),
|
|
32665
|
+
todos: readNumber(bar.todos),
|
|
32666
|
+
tier2_stale: readBoolean(bar.tier2_stale)
|
|
32667
|
+
};
|
|
32668
|
+
}
|
|
32635
32669
|
function formatFlag(enabled) {
|
|
32636
32670
|
return enabled ? "enabled" : "disabled";
|
|
32637
32671
|
}
|
|
@@ -32725,6 +32759,7 @@ function coerceAftStatus(response) {
|
|
|
32725
32759
|
checkpoints: readNumber(session.checkpoints)
|
|
32726
32760
|
},
|
|
32727
32761
|
compression: readCompression(response.compression),
|
|
32762
|
+
status_bar: readStatusBar(response.status_bar),
|
|
32728
32763
|
message: readString(response.message, "")
|
|
32729
32764
|
};
|
|
32730
32765
|
}
|
|
@@ -32780,6 +32815,10 @@ function formatStatusMarkdown(status) {
|
|
|
32780
32815
|
if (status.storage_dir ?? status.disk.storage_dir) {
|
|
32781
32816
|
lines.push(`- **Storage dir:** \`${status.storage_dir ?? status.disk.storage_dir}\``);
|
|
32782
32817
|
}
|
|
32818
|
+
if (status.status_bar) {
|
|
32819
|
+
const sb = status.status_bar;
|
|
32820
|
+
lines.push("", `### Code Health${sb.tier2_stale ? " (~ stale)" : ""}`, `- **Errors:** ${formatCount(sb.errors)}`, `- **Warnings:** ${formatCount(sb.warnings)}`, `- **Duplicates:** ${formatCount(sb.duplicates)}`, `- **TODOs:** ${formatCount(sb.todos)}`);
|
|
32821
|
+
}
|
|
32783
32822
|
lines.push("", "### Current session", `- **ID:** \`${status.session.id}\``, `- **Tracked files:** ${formatCount(status.session.tracked_files)}`, `- **Checkpoints:** ${formatCount(status.session.checkpoints)}`, `- **Project checkpoints (all sessions):** ${formatCount(status.checkpoints_total)}`);
|
|
32784
32823
|
return lines.join(`
|
|
32785
32824
|
`);
|
|
@@ -32787,7 +32826,7 @@ function formatStatusMarkdown(status) {
|
|
|
32787
32826
|
|
|
32788
32827
|
// src/shared/tui-config.ts
|
|
32789
32828
|
var import_comment_json4 = __toESM(require_src2(), 1);
|
|
32790
|
-
import { existsSync as
|
|
32829
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync11, readFileSync as readFileSync13, writeFileSync as writeFileSync11 } from "node:fs";
|
|
32791
32830
|
import { dirname as dirname10, join as join21 } from "node:path";
|
|
32792
32831
|
|
|
32793
32832
|
// src/shared/opencode-config-dir.ts
|
|
@@ -32824,9 +32863,9 @@ function resolveTuiConfigPath() {
|
|
|
32824
32863
|
const configDir = getOpenCodeConfigPaths({ binary: "opencode" }).configDir;
|
|
32825
32864
|
const jsoncPath = join21(configDir, "tui.jsonc");
|
|
32826
32865
|
const jsonPath = join21(configDir, "tui.json");
|
|
32827
|
-
if (
|
|
32866
|
+
if (existsSync14(jsoncPath))
|
|
32828
32867
|
return jsoncPath;
|
|
32829
|
-
if (
|
|
32868
|
+
if (existsSync14(jsonPath))
|
|
32830
32869
|
return jsonPath;
|
|
32831
32870
|
return jsonPath;
|
|
32832
32871
|
}
|
|
@@ -32834,7 +32873,7 @@ function ensureTuiPluginEntry() {
|
|
|
32834
32873
|
try {
|
|
32835
32874
|
const configPath = resolveTuiConfigPath();
|
|
32836
32875
|
let config2 = {};
|
|
32837
|
-
if (
|
|
32876
|
+
if (existsSync14(configPath)) {
|
|
32838
32877
|
config2 = import_comment_json4.parse(readFileSync13(configPath, "utf-8")) ?? {};
|
|
32839
32878
|
}
|
|
32840
32879
|
const plugins = Array.isArray(config2.plugin) ? config2.plugin.filter((value) => typeof value === "string") : [];
|
|
@@ -32844,7 +32883,7 @@ function ensureTuiPluginEntry() {
|
|
|
32844
32883
|
plugins.push(PLUGIN_ENTRY);
|
|
32845
32884
|
config2.plugin = plugins;
|
|
32846
32885
|
mkdirSync11(dirname10(configPath), { recursive: true });
|
|
32847
|
-
|
|
32886
|
+
writeFileSync11(configPath, `${import_comment_json4.stringify(config2, null, 2)}
|
|
32848
32887
|
`);
|
|
32849
32888
|
log2(`[aft-plugin] added TUI plugin entry to ${configPath}`);
|
|
32850
32889
|
return true;
|
|
@@ -37272,14 +37311,12 @@ var PLUGIN_VERSION = (() => {
|
|
|
37272
37311
|
return "0.0.0";
|
|
37273
37312
|
}
|
|
37274
37313
|
})();
|
|
37275
|
-
var ANNOUNCEMENT_VERSION = "0.35.
|
|
37314
|
+
var ANNOUNCEMENT_VERSION = "0.35.3";
|
|
37276
37315
|
var ANNOUNCEMENT_FEATURES = [
|
|
37277
|
-
"
|
|
37278
|
-
"
|
|
37279
|
-
"
|
|
37280
|
-
"
|
|
37281
|
-
"`npx @cortexkit/aft --version` reports CLI, binary, and per-harness versions; `doctor --issue` can scope its report to a single session.",
|
|
37282
|
-
"New SCSS support and `.inc` files parsed as PHP."
|
|
37316
|
+
"Code Health in the TUI sidebar and `/aft-status`: live LSP errors and warnings plus duplicate and TODO counts, shown as at-a-glance traffic lights when the sidebar is collapsed.",
|
|
37317
|
+
"The semantic index now recovers on its own from a transient embedding-backend blip (a restarted local server, or a model still loading) instead of getting stuck on `failed`.",
|
|
37318
|
+
"Fixed a background codebase-scan crash on very deep or minified files.",
|
|
37319
|
+
"More reliable LSP auto-install when a parent directory has its own `package.json`."
|
|
37283
37320
|
];
|
|
37284
37321
|
var ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/DSa65w8wuf";
|
|
37285
37322
|
var plugin = async (input) => initializePluginForDirectory(input);
|
|
@@ -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;
|
|
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"}
|
package/dist/shared/status.d.ts
CHANGED
|
@@ -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":"AAAA,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,0BAA0B,CAAC;IACpC,OAAO,EAAE,0BAA0B,CAAC;CACrC;AAED,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,gBAAgB,EAAE,MAAM,CAAC;QACzB,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;IACF,wEAAwE;IACxE,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAChC;;;;;;OAMG;IACH,OAAO,EAAE,MAAM,CAAC;CACjB;
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/shared/status.ts"],"names":[],"mappings":"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"}
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
41
|
-
"@cortexkit/aft-darwin-x64": "0.35.
|
|
42
|
-
"@cortexkit/aft-linux-arm64": "0.35.
|
|
43
|
-
"@cortexkit/aft-linux-x64": "0.35.
|
|
44
|
-
"@cortexkit/aft-win32-arm64": "0.35.
|
|
45
|
-
"@cortexkit/aft-win32-x64": "0.35.
|
|
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",
|
package/src/shared/status.ts
CHANGED
|
@@ -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>
|
package/src/tui/sidebar.tsx
CHANGED
|
@@ -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
|