@cortexkit/aft-opencode 0.31.1 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -31990,6 +31990,19 @@ function formatFlag(enabled) {
31990
31990
  function formatCount(value) {
31991
31991
  return value == null ? "—" : value.toLocaleString("en-US");
31992
31992
  }
31993
+ function formatSemanticIndexStatus(status, stage) {
31994
+ if ((status === "loading" || status === "building") && stage === "fingerprint_change") {
31995
+ return "Rebuilding (model changed)";
31996
+ }
31997
+ return status;
31998
+ }
31999
+ function formatSemanticRefreshing(refreshingCount) {
32000
+ if (!Number.isFinite(refreshingCount) || refreshingCount <= 0)
32001
+ return null;
32002
+ if (refreshingCount > 20)
32003
+ return "Ready (many files refreshing)";
32004
+ return `Ready (${refreshingCount} file(s) refreshing)`;
32005
+ }
31993
32006
  function formatBytes(bytes) {
31994
32007
  if (!Number.isFinite(bytes) || bytes <= 0)
31995
32008
  return "0 B";
@@ -32041,6 +32054,7 @@ function coerceAftStatus(response) {
32041
32054
  files: readOptionalNumber(semanticIndex.files),
32042
32055
  entries_done: readOptionalNumber(semanticIndex.entries_done),
32043
32056
  entries_total: readOptionalNumber(semanticIndex.entries_total),
32057
+ refreshing_count: readNumber(semanticIndex.refreshing_count),
32044
32058
  entries: readOptionalNumber(semanticIndex.entries),
32045
32059
  dimension: readOptionalNumber(semanticIndex.dimension),
32046
32060
  error: readNullableString(semanticIndex.error)
@@ -32086,9 +32100,13 @@ function formatStatusMarkdown(status) {
32086
32100
  `- **Trigrams:** ${formatCount(status.search_index.trigrams)}`,
32087
32101
  "",
32088
32102
  "### Semantic index",
32089
- `- **Status:** \`${status.semantic_index.status}\``,
32090
- `- **Entries:** ${formatCount(status.semantic_index.entries)}`
32103
+ `- **Status:** \`${formatSemanticIndexStatus(status.semantic_index.status, status.semantic_index.stage)}\``
32091
32104
  ];
32105
+ const refreshing = formatSemanticRefreshing(status.semantic_index.refreshing_count);
32106
+ if (refreshing) {
32107
+ lines.push(`- **Refresh:** ${refreshing}`);
32108
+ }
32109
+ lines.push(`- **Entries:** ${formatCount(status.semantic_index.entries)}`);
32092
32110
  if (status.semantic_index.backend) {
32093
32111
  lines.push(`- **Backend:** ${status.semantic_index.backend}`);
32094
32112
  }
@@ -32249,6 +32267,17 @@ import * as path2 from "node:path";
32249
32267
  import { tool as tool2 } from "@opencode-ai/plugin";
32250
32268
  var z2 = tool2.schema;
32251
32269
  var optionalInt = (min, max) => z2.number().int().min(min).max(max).optional();
32270
+ function isEmptyParam(value) {
32271
+ if (value === undefined || value === null)
32272
+ return true;
32273
+ if (typeof value === "string")
32274
+ return value.length === 0;
32275
+ if (Array.isArray(value))
32276
+ return value.length === 0;
32277
+ if (typeof value === "object")
32278
+ return Object.keys(value).length === 0;
32279
+ return false;
32280
+ }
32252
32281
  var LONG_RUNNING_COMMAND_TIMEOUT_MS = {
32253
32282
  callers: 60000,
32254
32283
  trace_to: 60000,
@@ -32257,7 +32286,7 @@ var LONG_RUNNING_COMMAND_TIMEOUT_MS = {
32257
32286
  impact: 60000,
32258
32287
  grep: 60000,
32259
32288
  glob: 60000,
32260
- semantic_search: 45000
32289
+ semantic_search: 60000
32261
32290
  };
32262
32291
  function timeoutForCommand(command) {
32263
32292
  return LONG_RUNNING_COMMAND_TIMEOUT_MS[command];
@@ -32569,9 +32598,9 @@ function astTools(ctx) {
32569
32598
  pattern: args.pattern,
32570
32599
  lang: args.lang
32571
32600
  };
32572
- if (args.paths)
32601
+ if (!isEmptyParam(args.paths))
32573
32602
  params.paths = args.paths;
32574
- if (args.globs)
32603
+ if (!isEmptyParam(args.globs))
32575
32604
  params.globs = args.globs;
32576
32605
  if (args.contextLines !== undefined)
32577
32606
  params.context = Number(args.contextLines);
@@ -32689,9 +32718,9 @@ ${hint}`;
32689
32718
  rewrite: args.rewrite,
32690
32719
  lang: args.lang
32691
32720
  };
32692
- if (args.paths)
32721
+ if (!isEmptyParam(args.paths))
32693
32722
  params.paths = args.paths;
32694
- if (args.globs)
32723
+ if (!isEmptyParam(args.globs))
32695
32724
  params.globs = args.globs;
32696
32725
  params.dry_run = args.dryRun === true;
32697
32726
  const response = await callBridge(ctx, context, "ast_replace", params);
@@ -35174,7 +35203,7 @@ function getCallID3(ctx) {
35174
35203
  return c.callID ?? c.callId ?? c.call_id;
35175
35204
  }
35176
35205
  function buildZoomTitle(args) {
35177
- if (args.targets !== undefined && args.targets !== null) {
35206
+ if (!isEmptyParam(args.targets)) {
35178
35207
  if (Array.isArray(args.targets)) {
35179
35208
  if (args.targets.length === 1 && args.targets[0]) {
35180
35209
  return `${args.targets[0].filePath}#${args.targets[0].symbol}`;
@@ -35303,14 +35332,26 @@ function readingTools(ctx) {
35303
35332
  contextLines: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("Lines of context before/after the symbol (default: 3)")
35304
35333
  },
35305
35334
  execute: async (args, context) => {
35306
- const hasFilePath = typeof args.filePath === "string" && args.filePath.length > 0;
35307
- const hasUrl = typeof args.url === "string" && args.url.length > 0;
35308
- const hasTargets = args.targets !== undefined && args.targets !== null;
35309
- const hasSymbols = args.symbols !== undefined && args.symbols !== null && (typeof args.symbols === "string" ? args.symbols.length > 0 : Array.isArray(args.symbols) && args.symbols.length > 0);
35335
+ const hasFilePath = !isEmptyParam(args.filePath);
35336
+ const hasUrl = !isEmptyParam(args.url);
35337
+ const hasTargets = !isEmptyParam(args.targets);
35338
+ const hasSymbols = !isEmptyParam(args.symbols);
35310
35339
  const zoomCallID = getCallID3(context);
35311
35340
  if (zoomCallID) {
35312
35341
  const title = buildZoomTitle(args);
35313
- storeToolMetadata(context.sessionID, zoomCallID, { title, metadata: { title } });
35342
+ const display = { title };
35343
+ if (hasFilePath)
35344
+ display.filePath = args.filePath;
35345
+ if (hasUrl)
35346
+ display.url = args.url;
35347
+ if (hasSymbols) {
35348
+ display.symbols = typeof args.symbols === "string" ? args.symbols : JSON.stringify(args.symbols);
35349
+ }
35350
+ if (hasTargets)
35351
+ display.targets = JSON.stringify(args.targets);
35352
+ if (args.contextLines !== undefined)
35353
+ display.contextLines = args.contextLines;
35354
+ storeToolMetadata(context.sessionID, zoomCallID, { title, metadata: display });
35314
35355
  }
35315
35356
  if (hasTargets) {
35316
35357
  if (hasFilePath || hasUrl || hasSymbols) {
@@ -35560,14 +35601,14 @@ function refactoringTools(ctx) {
35560
35601
  },
35561
35602
  execute: async (args, context) => {
35562
35603
  const op = args.op;
35563
- if ((op === "move" || op === "inline") && typeof args.symbol !== "string") {
35604
+ if ((op === "move" || op === "inline") && isEmptyParam(args.symbol)) {
35564
35605
  throw new Error(`'symbol' is required for '${op}' op`);
35565
35606
  }
35566
- if (op === "move" && typeof args.destination !== "string") {
35607
+ if (op === "move" && isEmptyParam(args.destination)) {
35567
35608
  throw new Error("'destination' is required for 'move' op");
35568
35609
  }
35569
35610
  if (op === "extract") {
35570
- if (typeof args.name !== "string")
35611
+ if (isEmptyParam(args.name))
35571
35612
  throw new Error("'name' is required for 'extract' op");
35572
35613
  if (args.startLine === undefined)
35573
35614
  throw new Error("'startLine' is required for 'extract' op");
@@ -35950,32 +35991,46 @@ function arg2(schema) {
35950
35991
  function semanticTools(ctx) {
35951
35992
  const searchTool = {
35952
35993
  description: [
35953
- "Find symbols by concept using hybrid semantic + lexical search. Returns ranked code matches with similarity scores and provenance tags.",
35994
+ "Find code with unified semantic, lexical, literal, and regex search. Returns ranked symbol/file results or exact matching lines, with routing metadata.",
35954
35995
  "",
35955
35996
  "When to reach for it:",
35956
35997
  "- Exploring an unfamiliar area: 'where is rate limiting handled', 'how does auth flow work'",
35957
35998
  "- Concept doesn't appear as a literal string: 'retry logic', 'cache invalidation', 'graceful shutdown'",
35958
35999
  "- Filename-shaped concepts: 'the bridge spawn helper', 'the session detection module'",
35959
- "- After 2+ grep attempts that came back empty or noisy",
36000
+ "- Regex-shaped or exact text queries when you want AFT to classify and route automatically",
35960
36001
  "- You know roughly what the function does but not what it's named",
35961
36002
  "",
35962
36003
  "When NOT to use:",
35963
- "- You have an error message or stack trace → use grep",
36004
+ "- You need exhaustive literal enumeration → use grep directly",
35964
36005
  "- You want the file/module structure → use aft_outline",
35965
36006
  "- You're following a call chain → use aft_navigate",
35966
36007
  "",
35967
- "Each result tags `source` as one of: 'semantic' (embedding match only), 'lexical' (trigram exact-token match the embedding lane missed), or 'hybrid' (both lanes agreed strongest signal)."
36008
+ "Set hint to 'regex', 'literal', 'semantic', or 'auto' to override or document routing intent."
35968
36009
  ].join(`
35969
36010
  `),
35970
36011
  args: {
35971
- query: arg2(z14.string().describe("Concept or capability to find, phrased as a programmer would describe the code. Examples: 'fuzzy match with whitespace tolerance', 'undo backup before edit', 'retry failed network request'.")),
35972
- topK: arg2(optionalInt(1, 100).describe("Number of results (default: 10, max: 100)"))
36012
+ query: arg2(z14.string().describe("Concept, regex, literal text, filename, or capability to find. Examples: 'fuzzy match with whitespace tolerance', '^export', 'Cargo.lock'.")),
36013
+ topK: arg2(optionalInt(1, 100).describe("Number of results (default: 10, max: 100)")),
36014
+ hint: arg2(z14.enum(["regex", "literal", "semantic", "auto"]).optional().describe("Optional routing hint. Defaults to 'auto'."))
35973
36015
  },
35974
36016
  execute: async (args, context) => {
35975
- const response = await callBridge(ctx, context, "semantic_search", {
35976
- query: args.query,
36017
+ if (typeof args.query !== "string" || args.query.length === 0) {
36018
+ throw new Error("semantic_search: invalid params: missing field `query`");
36019
+ }
36020
+ const query = args.query;
36021
+ const hint = typeof args.hint === "string" ? args.hint : undefined;
36022
+ if (hint !== "semantic") {
36023
+ const denied = await askGrepPermission(context, query);
36024
+ if (denied)
36025
+ return permissionDeniedResponse(denied);
36026
+ }
36027
+ const bridgeParams = {
36028
+ query,
35977
36029
  top_k: args.topK ?? 10
35978
- });
36030
+ };
36031
+ if (hint)
36032
+ bridgeParams.hint = hint;
36033
+ const response = await callBridge(ctx, context, "semantic_search", bridgeParams);
35979
36034
  if (response.success === false) {
35980
36035
  if (response.code === "semantic_search_unavailable" && typeof response.message === "string") {
35981
36036
  return response.message;
@@ -36122,8 +36177,12 @@ function buildWorkflowHints(opts) {
36122
36177
  sections.push(`**Web/URL access**: \`aft_outline({ target: url })\` first for structure, then \`aft_zoom({ url, symbols: "<heading>" })\` for the specific section.`);
36123
36178
  }
36124
36179
  if (hasOutline && hasZoom && (hasGrep || hasSearch)) {
36125
- const locator = hasGrep ? `\`${grepName}\`` : "`aft_search`";
36126
- sections.push(hasGrep && hasSearch ? `**Code exploration**: For exact identifiers (\`useState\`, function names, env vars), error messages, or path-shaped queries → \`${grepName}\` first. For broad concepts ('where is X handled', 'how does Y work') \`aft_search\`. Then use \`aft_outline\` for structure → \`aft_zoom\` for symbol(s).` : `**Code exploration**: ${locator} to locate → \`aft_outline\` for structure → \`aft_zoom\` for symbol(s).`);
36180
+ if (hasSearch) {
36181
+ const grepFallback = hasGrep ? ` Use \`${grepName}\` directly only when you need exhaustive enumeration of literal text (every TODO, every import of X) without ranking.` : "";
36182
+ sections.push(`**Code exploration**: \`aft_search\` is the primary code-search tool. It auto-routes by query shape — exact identifiers, regex, error messages, natural language all use the same call. Pass \`hint: "regex"\` / \`hint: "literal"\` / \`hint: "semantic"\` to override routing if needed. Then \`aft_outline\` for structure → \`aft_zoom\` for symbol(s).${grepFallback}`);
36183
+ } else {
36184
+ sections.push(`**Code exploration**: \`${grepName}\` to locate → \`aft_outline\` for structure → \`aft_zoom\` for symbol(s).`);
36185
+ }
36127
36186
  }
36128
36187
  if (hasNavigate) {
36129
36188
  sections.push([
@@ -36244,13 +36303,13 @@ var PLUGIN_VERSION = (() => {
36244
36303
  return "0.0.0";
36245
36304
  }
36246
36305
  })();
36247
- var ANNOUNCEMENT_VERSION = "0.31.0";
36306
+ var ANNOUNCEMENT_VERSION = "0.32.0";
36248
36307
  var ANNOUNCEMENT_FEATURES = [
36249
- "`aft_navigate` has a new `trace_to_symbol` op for path-between-symbols queries. One call returns the shortest call path from one function to another with file + line for every hop. Honest errors for ambiguous, missing, or unreachable targets — including a candidate list to disambiguate.",
36250
- '`aft_outline target: "<dir>", files: true` returns an indexed file tree with language, symbol count, and byte size per file. Lets agents pick which files to actually open without first reading symbol bodies. Honest truncation (`complete: false` + `walk_truncated`/`unchecked_files`) when the directory exceeds the walk cap.',
36251
- '`aft_zoom` adds `targets` for cross-file batches — pull bodies from different files in one call. Schema breaking change: `symbol` is removed; pass `symbols: "name"` (string) or `symbols: ["a", "b"]` (array). Same shape works for `url` mode so you can grab multiple sections from one URL fetch.',
36252
- "Bun/npm/pnpm test compressors now match on output shape, not just on the head token. Wrapper invocations like `bun run --cwd packages/foo test`, `npm test`, and `pnpm test` keep failing-test bodies on the first run instead of dropping them to a generic summary.",
36253
- "Tool descriptions trimmed: ~1.2K agent-facing tokens removed (-19% on OpenCode, -2% on Pi) by dropping redundant `Returns:` blocks and compressing `lsp_diagnostics` while preserving its load-bearing honesty guidance."
36308
+ "`aft_search` is now the primary code-search tool auto-routes regex / literal / semantic / hybrid by query shape, with a `hint` override.",
36309
+ 'Semantic search stays queryable through edits no more "rebuilding" fallback after every save.',
36310
+ "Workflow hints promote `aft_search` as primary; `grep` is positioned as the specialized fallback.",
36311
+ "Bare `\\n`, `\\t`, `\\r` queries correctly route to regex mode.",
36312
+ 'Empty params (`targets: []`, `url: ""`) no longer trigger misleading mutual-exclusion errors.'
36254
36313
  ];
36255
36314
  var ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/F2uWxjGnU";
36256
36315
  var plugin = async (input) => initializePluginForDirectory(input);
@@ -44,6 +44,7 @@ export interface AftStatusSnapshot {
44
44
  files?: number | null;
45
45
  entries_done?: number | null;
46
46
  entries_total?: number | null;
47
+ refreshing_count: number;
47
48
  entries: number | null;
48
49
  dimension: number | null;
49
50
  error?: string | null;
@@ -78,6 +79,8 @@ export interface AftStatusSnapshot {
78
79
  */
79
80
  message: string;
80
81
  }
82
+ export declare function formatSemanticIndexStatus(status: string, stage?: string | null): string;
83
+ export declare function formatSemanticRefreshing(refreshingCount: number): string | null;
81
84
  export declare function formatBytes(bytes: number): string;
82
85
  export declare function coerceAftStatus(response: Record<string, unknown>): AftStatusSnapshot;
83
86
  /**
@@ -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,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;AAqDD,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAajD;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,iBAAiB,CAmEpF;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":"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"}
@@ -56,6 +56,19 @@ export declare const optionalInt: (min: number, max: number) => any;
56
56
  * declared more permissively.
57
57
  */
58
58
  export declare function coerceOptionalInt(v: unknown, paramName: string, min: number, max: number): number | undefined;
59
+ /**
60
+ * True when a value represents "agent did not provide this param".
61
+ *
62
+ * GPT-family models send empty strings / empty arrays / null instead of
63
+ * omitting optional params entirely. Use this BEFORE mutual-exclusion
64
+ * checks so an empty `targets: []` or `url: ""` doesn't get counted as
65
+ * present and trigger a misleading "X is mutually exclusive with Y" error.
66
+ *
67
+ * Treats undefined / null / "" / [] / {} as empty. Booleans and numbers
68
+ * (including 0 and false) are NOT empty by themselves — only string and
69
+ * collection sentinels qualify.
70
+ */
71
+ export declare function isEmptyParam(value: unknown): boolean;
59
72
  /**
60
73
  * Per-command timeout overrides (milliseconds).
61
74
  *
@@ -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;;;;;;;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;;;;;;;;;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;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;;;;;;;;;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":"ast.d.ts","sourceRoot":"","sources":["../../src/tools/ast.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAkDjD,wBAAgB,QAAQ,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CA0S3E"}
1
+ {"version":3,"file":"ast.d.ts","sourceRoot":"","sources":["../../src/tools/ast.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAkDjD,wBAAgB,QAAQ,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CA8S3E"}
@@ -1 +1 @@
1
- {"version":3,"file":"reading.d.ts","sourceRoot":"","sources":["../../src/tools/reading.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAe,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAGvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAsCjD,UAAU,qBAAqB;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,eAAe;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,qBAAqB,EAAE,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAmS/E;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EAAE,EACjB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GACnC,eAAe,CAyBjB;AA+CD,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CA2ChF"}
1
+ {"version":3,"file":"reading.d.ts","sourceRoot":"","sources":["../../src/tools/reading.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAe,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAGvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AA0CjD,UAAU,qBAAqB;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,eAAe;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,qBAAqB,EAAE,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CA2S/E;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EAAE,EACjB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GACnC,eAAe,CAyBjB;AA+CD,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CA2ChF"}
@@ -1 +1 @@
1
- {"version":3,"file":"refactoring.d.ts","sourceRoot":"","sources":["../../src/tools/refactoring.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAG1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAcjD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAyInF"}
1
+ {"version":3,"file":"refactoring.d.ts","sourceRoot":"","sources":["../../src/tools/refactoring.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAG1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAcjD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CA4InF"}
@@ -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;AAWjD,wBAAgB,aAAa,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAyDhF"}
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;AAYjD,wBAAgB,aAAa,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CA8EhF"}
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.31.1";
4
+ var version = "0.32.0";
5
5
 
6
6
  // src/shared/rpc-client.ts
7
7
  import { existsSync, readdirSync, readFileSync, unlinkSync } from "node:fs";
@@ -343,6 +343,19 @@ function readCompression(value) {
343
343
  session: readCompressionAggregate(compression.session)
344
344
  };
345
345
  }
346
+ function formatSemanticIndexStatus(status, stage) {
347
+ if ((status === "loading" || status === "building") && stage === "fingerprint_change") {
348
+ return "Rebuilding (model changed)";
349
+ }
350
+ return status;
351
+ }
352
+ function formatSemanticRefreshing(refreshingCount) {
353
+ if (!Number.isFinite(refreshingCount) || refreshingCount <= 0)
354
+ return null;
355
+ if (refreshingCount > 20)
356
+ return "Ready (many files refreshing)";
357
+ return `Ready (${refreshingCount} file(s) refreshing)`;
358
+ }
346
359
  function formatBytes(bytes) {
347
360
  if (!Number.isFinite(bytes) || bytes <= 0)
348
361
  return "0 B";
@@ -394,6 +407,7 @@ function coerceAftStatus(response) {
394
407
  files: readOptionalNumber(semanticIndex.files),
395
408
  entries_done: readOptionalNumber(semanticIndex.entries_done),
396
409
  entries_total: readOptionalNumber(semanticIndex.entries_total),
410
+ refreshing_count: readNumber(semanticIndex.refreshing_count),
397
411
  entries: readOptionalNumber(semanticIndex.entries),
398
412
  dimension: readOptionalNumber(semanticIndex.dimension),
399
413
  error: readNullableString(semanticIndex.error)
@@ -690,7 +704,15 @@ var SidebarContent = (props) => {
690
704
  const s = () => scopedSidebarSnapshot(status(), currentDirectory(), props.sessionID());
691
705
  const notInitialized = () => s()?.cache_role === "not_initialized";
692
706
  const searchStatus = () => statusDisplay(s()?.search_index?.status ?? "disabled");
693
- const semanticStatus = () => statusDisplay(s()?.semantic_index?.status ?? "disabled");
707
+ const semanticStatus = () => {
708
+ const rawStatus = s()?.semantic_index?.status ?? "disabled";
709
+ const display = statusDisplay(rawStatus);
710
+ return {
711
+ ...display,
712
+ label: formatSemanticIndexStatus(rawStatus, s()?.semantic_index?.stage)
713
+ };
714
+ };
715
+ const semanticRefreshing = () => formatSemanticRefreshing(s()?.semantic_index?.refreshing_count ?? 0);
694
716
  const trigramBytes = () => s()?.disk?.trigram_disk_bytes ?? 0;
695
717
  const semanticBytes = () => s()?.disk?.semantic_disk_bytes ?? 0;
696
718
  const compressionRows = () => formatCompressionSidebarRows(s()?.compression);
@@ -819,6 +841,13 @@ var SidebarContent = (props) => {
819
841
  value: semanticStatus().label,
820
842
  tone: semanticStatus().tone
821
843
  }, undefined, false, undefined, this),
844
+ semanticRefreshing() && /* @__PURE__ */ jsxDEV("box", {
845
+ width: "100%",
846
+ children: /* @__PURE__ */ jsxDEV("text", {
847
+ fg: props.theme.textMuted,
848
+ children: semanticRefreshing()
849
+ }, undefined, false, undefined, this)
850
+ }, undefined, false, undefined, this),
822
851
  s()?.semantic_index?.status === "loading" && s()?.semantic_index?.entries_total != null && s().semantic_index.entries_total > 0 && /* @__PURE__ */ jsxDEV(StatRow, {
823
852
  theme: props.theme,
824
853
  label: "Progress",
@@ -1189,9 +1218,16 @@ var StatusDialog = (props) => {
1189
1218
  /* @__PURE__ */ jsxDEV2(R, {
1190
1219
  theme: t(),
1191
1220
  label: "Status",
1192
- value: status().semantic_index.status,
1221
+ value: formatSemanticIndexStatus(status().semantic_index.status, status().semantic_index.stage),
1193
1222
  tone: statusTone(status().semantic_index.status)
1194
1223
  }, undefined, false, undefined, this),
1224
+ formatSemanticRefreshing(status().semantic_index.refreshing_count) ? /* @__PURE__ */ jsxDEV2("box", {
1225
+ width: "100%",
1226
+ children: /* @__PURE__ */ jsxDEV2("text", {
1227
+ fg: t().textMuted,
1228
+ children: formatSemanticRefreshing(status().semantic_index.refreshing_count)
1229
+ }, undefined, false, undefined, this)
1230
+ }, undefined, false, undefined, this) : null,
1195
1231
  /* @__PURE__ */ jsxDEV2(R, {
1196
1232
  theme: t(),
1197
1233
  label: "Entries",
@@ -1 +1 @@
1
- {"version":3,"file":"workflow-hints.d.ts","sourceRoot":"","sources":["../src/workflow-hints.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,KAAK,SAAS,EAAqB,MAAM,aAAa,CAAC;AAEhE,MAAM,WAAW,iBAAiB;IAChC,oEAAoE;IACpE,WAAW,EAAE,SAAS,GAAG,aAAa,GAAG,KAAK,CAAC;IAC/C,4EAA4E;IAC5E,aAAa,EAAE,OAAO,CAAC;IACvB,mEAAmE;IACnE,eAAe,EAAE,OAAO,CAAC;IACzB,wEAAwE;IACxE,qBAAqB,EAAE,OAAO,CAAC;IAC/B,4DAA4D;IAC5D,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAC5B;AAID;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,iBAAiB,GAAG,MAAM,GAAG,IAAI,CAwEzE;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,GAAG,IAAI,CAQjG"}
1
+ {"version":3,"file":"workflow-hints.d.ts","sourceRoot":"","sources":["../src/workflow-hints.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,KAAK,SAAS,EAAqB,MAAM,aAAa,CAAC;AAEhE,MAAM,WAAW,iBAAiB;IAChC,oEAAoE;IACpE,WAAW,EAAE,SAAS,GAAG,aAAa,GAAG,KAAK,CAAC;IAC/C,4EAA4E;IAC5E,aAAa,EAAE,OAAO,CAAC;IACvB,mEAAmE;IACnE,eAAe,EAAE,OAAO,CAAC;IACzB,wEAAwE;IACxE,qBAAqB,EAAE,OAAO,CAAC;IAC/B,4DAA4D;IAC5D,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAC5B;AAID;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,iBAAiB,GAAG,MAAM,GAAG,IAAI,CA8EzE;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,GAAG,IAAI,CAQjG"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/aft-opencode",
3
- "version": "0.31.1",
3
+ "version": "0.32.0",
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",
@@ -29,7 +29,7 @@
29
29
  },
30
30
  "dependencies": {
31
31
  "@clack/prompts": "^1.2.0",
32
- "@cortexkit/aft-bridge": "0.31.1",
32
+ "@cortexkit/aft-bridge": "0.32.0",
33
33
  "@opencode-ai/plugin": "^1.15.5",
34
34
  "@opencode-ai/sdk": "^1.15.5",
35
35
  "@xterm/headless": "^5.5.0",
@@ -38,12 +38,12 @@
38
38
  "zod": "^4.1.8"
39
39
  },
40
40
  "optionalDependencies": {
41
- "@cortexkit/aft-darwin-arm64": "0.31.1",
42
- "@cortexkit/aft-darwin-x64": "0.31.1",
43
- "@cortexkit/aft-linux-arm64": "0.31.1",
44
- "@cortexkit/aft-linux-x64": "0.31.1",
45
- "@cortexkit/aft-win32-arm64": "0.31.1",
46
- "@cortexkit/aft-win32-x64": "0.31.1"
41
+ "@cortexkit/aft-darwin-arm64": "0.32.0",
42
+ "@cortexkit/aft-darwin-x64": "0.32.0",
43
+ "@cortexkit/aft-linux-arm64": "0.32.0",
44
+ "@cortexkit/aft-linux-x64": "0.32.0",
45
+ "@cortexkit/aft-win32-arm64": "0.32.0",
46
+ "@cortexkit/aft-win32-x64": "0.32.0"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@types/node": "^22.0.0",
@@ -46,6 +46,7 @@ export interface AftStatusSnapshot {
46
46
  files?: number | null;
47
47
  entries_done?: number | null;
48
48
  entries_total?: number | null;
49
+ refreshing_count: number;
49
50
  entries: number | null;
50
51
  dimension: number | null;
51
52
  error?: string | null;
@@ -132,6 +133,19 @@ function formatCount(value: number | null): string {
132
133
  return value == null ? "—" : value.toLocaleString("en-US");
133
134
  }
134
135
 
136
+ export function formatSemanticIndexStatus(status: string, stage?: string | null): string {
137
+ if ((status === "loading" || status === "building") && stage === "fingerprint_change") {
138
+ return "Rebuilding (model changed)";
139
+ }
140
+ return status;
141
+ }
142
+
143
+ export function formatSemanticRefreshing(refreshingCount: number): string | null {
144
+ if (!Number.isFinite(refreshingCount) || refreshingCount <= 0) return null;
145
+ if (refreshingCount > 20) return "Ready (many files refreshing)";
146
+ return `Ready (${refreshingCount} file(s) refreshing)`;
147
+ }
148
+
135
149
  export function formatBytes(bytes: number): string {
136
150
  if (!Number.isFinite(bytes) || bytes <= 0) return "0 B";
137
151
  const units = ["B", "KB", "MB", "GB", "TB"];
@@ -190,6 +204,7 @@ export function coerceAftStatus(response: Record<string, unknown>): AftStatusSna
190
204
  files: readOptionalNumber(semanticIndex.files),
191
205
  entries_done: readOptionalNumber(semanticIndex.entries_done),
192
206
  entries_total: readOptionalNumber(semanticIndex.entries_total),
207
+ refreshing_count: readNumber(semanticIndex.refreshing_count),
193
208
  entries: readOptionalNumber(semanticIndex.entries),
194
209
  dimension: readOptionalNumber(semanticIndex.dimension),
195
210
  error: readNullableString(semanticIndex.error),
@@ -241,9 +256,13 @@ export function formatStatusDialogMessage(status: AftStatusSnapshot): string {
241
256
  `- trigrams: ${formatCount(status.search_index.trigrams)}`,
242
257
  "",
243
258
  "Semantic index",
244
- `- status: ${status.semantic_index.status}`,
245
- `- entries: ${formatCount(status.semantic_index.entries)}`,
259
+ `- status: ${formatSemanticIndexStatus(status.semantic_index.status, status.semantic_index.stage)}`,
246
260
  ];
261
+ const refreshing = formatSemanticRefreshing(status.semantic_index.refreshing_count);
262
+ if (refreshing) {
263
+ lines.push(`- ${refreshing}`);
264
+ }
265
+ lines.push(`- entries: ${formatCount(status.semantic_index.entries)}`);
247
266
  if (status.semantic_index.backend) {
248
267
  lines.push(`- backend: ${status.semantic_index.backend}`);
249
268
  }
@@ -316,9 +335,13 @@ export function formatStatusMarkdown(status: AftStatusSnapshot): string {
316
335
  `- **Trigrams:** ${formatCount(status.search_index.trigrams)}`,
317
336
  "",
318
337
  "### Semantic index",
319
- `- **Status:** \`${status.semantic_index.status}\``,
320
- `- **Entries:** ${formatCount(status.semantic_index.entries)}`,
338
+ `- **Status:** \`${formatSemanticIndexStatus(status.semantic_index.status, status.semantic_index.stage)}\``,
321
339
  ];
340
+ const refreshing = formatSemanticRefreshing(status.semantic_index.refreshing_count);
341
+ if (refreshing) {
342
+ lines.push(`- **Refresh:** ${refreshing}`);
343
+ }
344
+ lines.push(`- **Entries:** ${formatCount(status.semantic_index.entries)}`);
322
345
  if (status.semantic_index.backend) {
323
346
  lines.push(`- **Backend:** ${status.semantic_index.backend}`);
324
347
  }
package/src/tui/index.tsx CHANGED
@@ -6,7 +6,13 @@ import { createMemo, createSignal, onCleanup } from "solid-js";
6
6
 
7
7
  import { version as packageVersion } from "../../package.json";
8
8
  import { AftRpcClient } from "../shared/rpc-client";
9
- import { type AftStatusSnapshot, coerceAftStatus, formatBytes } from "../shared/status";
9
+ import {
10
+ type AftStatusSnapshot,
11
+ coerceAftStatus,
12
+ formatBytes,
13
+ formatSemanticIndexStatus,
14
+ formatSemanticRefreshing,
15
+ } from "../shared/status";
10
16
  import {
11
17
  createAftSidebarSlot,
12
18
  formatCompressionSidebarRows,
@@ -331,9 +337,19 @@ const StatusDialog = (props: StatusDialogProps) => {
331
337
  <R
332
338
  theme={t()}
333
339
  label="Status"
334
- value={status()!.semantic_index.status}
340
+ value={formatSemanticIndexStatus(
341
+ status()!.semantic_index.status,
342
+ status()!.semantic_index.stage,
343
+ )}
335
344
  tone={statusTone(status()!.semantic_index.status)}
336
345
  />
346
+ {formatSemanticRefreshing(status()!.semantic_index.refreshing_count) ? (
347
+ <box width="100%">
348
+ <text fg={t().textMuted}>
349
+ {formatSemanticRefreshing(status()!.semantic_index.refreshing_count)}
350
+ </text>
351
+ </box>
352
+ ) : null}
337
353
  <R
338
354
  theme={t()}
339
355
  label="Entries"
@@ -11,7 +11,13 @@ import type { TuiPluginApi, TuiSlotPlugin, TuiThemeCurrent } from "@opencode-ai/
11
11
  import { createEffect, createMemo, createSignal, on, onCleanup } from "solid-js";
12
12
 
13
13
  import { AftRpcClient } from "../shared/rpc-client";
14
- import { type AftStatusSnapshot, coerceAftStatus, type StatusCompression } from "../shared/status";
14
+ import {
15
+ type AftStatusSnapshot,
16
+ coerceAftStatus,
17
+ formatSemanticIndexStatus,
18
+ formatSemanticRefreshing,
19
+ type StatusCompression,
20
+ } from "../shared/status";
15
21
  import { resolveCortexKitStorageRoot } from "../shared/storage-paths";
16
22
 
17
23
  const SINGLE_BORDER = { type: "single" } as any;
@@ -341,7 +347,16 @@ const SidebarContent = (props: {
341
347
  // Pre-compute display values so the JSX stays readable. createMemo for
342
348
  // each derived field would be overkill — these are cheap derivations.
343
349
  const searchStatus = () => statusDisplay(s()?.search_index?.status ?? "disabled");
344
- const semanticStatus = () => statusDisplay(s()?.semantic_index?.status ?? "disabled");
350
+ const semanticStatus = () => {
351
+ const rawStatus = s()?.semantic_index?.status ?? "disabled";
352
+ const display = statusDisplay(rawStatus);
353
+ return {
354
+ ...display,
355
+ label: formatSemanticIndexStatus(rawStatus, s()?.semantic_index?.stage),
356
+ };
357
+ };
358
+ const semanticRefreshing = () =>
359
+ formatSemanticRefreshing(s()?.semantic_index?.refreshing_count ?? 0);
345
360
  const trigramBytes = () => s()?.disk?.trigram_disk_bytes ?? 0;
346
361
  const semanticBytes = () => s()?.disk?.semantic_disk_bytes ?? 0;
347
362
  const compressionRows = () => formatCompressionSidebarRows(s()?.compression);
@@ -463,6 +478,11 @@ const SidebarContent = (props: {
463
478
  value={semanticStatus().label}
464
479
  tone={semanticStatus().tone}
465
480
  />
481
+ {semanticRefreshing() && (
482
+ <box width="100%">
483
+ <text fg={props.theme.textMuted}>{semanticRefreshing()}</text>
484
+ </box>
485
+ )}
466
486
  {/* When loading, magic-context-style progress hint helps users see
467
487
  background work is making progress instead of stuck. */}
468
488
  {s()?.semantic_index?.status === "loading" &&