codemini-cli 0.3.3 → 0.3.5

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/src/core/tools.js CHANGED
@@ -1843,7 +1843,7 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
1843
1843
  function: {
1844
1844
  name: 'read',
1845
1845
  description:
1846
- 'Inspect a file and return content directly by default. Demo-style aliases like file_path, offset, and limit are accepted. Use metadata_only=true only when you want file metadata without content. Do not use run with cat, head, or tail for file reads.',
1846
+ 'Inspect code or text files. Use read(path) for normal file or line-window reads, read(ast_target=...) for a node-scoped AST read, and read(path, query=..., capture_name=...) to run an inline Tree-sitter query before returning the first matched node. Prefer the AST forms when targeting a function, class, or method and you want tighter context. Demo-style aliases like file_path, offset, and limit are accepted. Use metadata_only=true only when you want file metadata without content. Do not use run with cat, head, or tail for file reads.',
1847
1847
  parameters: {
1848
1848
  type: 'object',
1849
1849
  properties: {
@@ -1856,7 +1856,11 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
1856
1856
  max_chars: { type: 'number', description: 'Max chars to return' },
1857
1857
  include_content: { type: 'boolean', description: 'Legacy compatibility flag. Content is returned by default.' },
1858
1858
  read_token: { type: 'string', description: 'Legacy compatibility token. No longer required for content reads.' },
1859
- metadata_only: { type: 'boolean', description: 'Set true to return metadata without content.' }
1859
+ metadata_only: { type: 'boolean', description: 'Set true to return metadata without content.' },
1860
+ ast_target: { type: 'object', description: 'AST target from ast_query or a prior AST selection. When provided, read returns that node instead of a line window.' },
1861
+ query: { type: 'string', description: 'Optional Tree-sitter query to run inline before reading the first matched AST node. Use with path for one-shot function/class/method reads.' },
1862
+ capture_name: { type: 'string', description: 'Optional capture name to select when query is provided.' },
1863
+ language: { type: 'string', description: 'Optional Tree-sitter language override for AST reads or inline queries.' }
1860
1864
  },
1861
1865
  required: []
1862
1866
  }
@@ -1923,7 +1927,7 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
1923
1927
  function: {
1924
1928
  name: 'edit',
1925
1929
  description:
1926
- 'Edit existing files. Prefer one of these shapes: 1) {file, old_text, new_text} for exact text replacement, 2) {file, symbol, edit:{kind:"replace_block", new_content:"..."}} for block replacement, 3) {file, anchor_text, position:"before"|"after", content:"..."} for inserts. Demo-style aliases {file_path, old_string, new_string} are also accepted. Read first unless the exact target is already known. Prefer this over write for existing code changes.',
1930
+ 'Edit existing files. Prefer one of these shapes: 1) {file, old_text, new_text} for exact text replacement, 2) {file, symbol, edit:{kind:"replace_block", new_content:"..."}} for block replacement, 3) {file, anchor_text, position:"before"|"after", content:"..."} for inserts. Demo-style aliases {file_path, old_string, new_string} are also accepted. Read first unless the exact target is already known, and prefer read(ast_target=...) or read(path, query=...) before symbol- or block-level edits when you want tighter context. Prefer this over write for existing code changes.',
1927
1931
  parameters: {
1928
1932
  type: 'object',
1929
1933
  properties: {
@@ -2053,7 +2057,7 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
2053
2057
  function: {
2054
2058
  name: 'ast_query',
2055
2059
  description:
2056
- 'Run a Tree-sitter query on a code file and return ast_target objects. Use this when you need node-scoped reads or edits for functions, classes, or methods.',
2060
+ 'Run a Tree-sitter query on a code file and return ast_target objects. Use this for advanced AST workflows such as multi-match selection, explicit node caching, or when you plan to reuse ast_target across follow-up reads or edits. For a common one-shot function, class, or method read, prefer read(path, query=...) or read(ast_target=...).',
2057
2061
  parameters: {
2058
2062
  type: 'object',
2059
2063
  properties: {
@@ -2092,7 +2096,7 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
2092
2096
  function: {
2093
2097
  name: 'read_ast_node',
2094
2098
  description:
2095
- 'Read a previously selected AST node with compact structural context. Use this after ast_query before a scoped structural edit.',
2099
+ 'Read a previously selected AST node with compact structural context. Use this after ast_query when you want an explicit follow-up read of a cached node before a scoped structural edit. For common one-shot AST reads, prefer read(ast_target=...) or read(path, query=...).',
2096
2100
  parameters: {
2097
2101
  type: 'object',
2098
2102
  properties: {
@@ -2274,19 +2278,63 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
2274
2278
  const definitions = [...primaryDefinitions];
2275
2279
 
2276
2280
  const handlers = {
2277
- read: (args) =>
2278
- readFile(workspaceRoot, {
2281
+ read: async (args) => {
2282
+ const inlineQuery = String(args?.query || '').trim();
2283
+ const directAstTarget = args?.ast_target;
2284
+
2285
+ if (directAstTarget) {
2286
+ const result = await readAstNode(workspaceRoot, {
2287
+ ...args,
2288
+ path: args?.path || directAstTarget?.path,
2289
+ ast_target: directAstTarget
2290
+ });
2291
+ if (directAstTarget?.path) rememberAstSelection(directAstTarget.path, directAstTarget);
2292
+ const readPath = String(result?.path || directAstTarget?.path || '').trim();
2293
+ if (readPath) lastReadPath = readPath;
2294
+ return result;
2295
+ }
2296
+
2297
+ if (inlineQuery) {
2298
+ const queryResult = await queryAst(workspaceRoot, args);
2299
+ const firstTarget = queryResult?.matches?.[0]?.ast_target;
2300
+ if (!firstTarget) {
2301
+ return {
2302
+ path: String(args?.path || '').trim(),
2303
+ language: queryResult?.language,
2304
+ query: inlineQuery,
2305
+ capture_name: String(args?.capture_name || '').trim() || undefined,
2306
+ matches: 0,
2307
+ content: ''
2308
+ };
2309
+ }
2310
+ rememberAstSelection(firstTarget.path, firstTarget);
2311
+ const result = await readAstNode(workspaceRoot, {
2312
+ ...args,
2313
+ path: firstTarget.path,
2314
+ ast_target: firstTarget
2315
+ });
2316
+ const readPath = String(result?.path || firstTarget?.path || '').trim();
2317
+ if (readPath) lastReadPath = readPath;
2318
+ return {
2319
+ ...result,
2320
+ query: inlineQuery,
2321
+ capture_name: String(args?.capture_name || '').trim() || undefined,
2322
+ matches: queryResult.matches.length
2323
+ };
2324
+ }
2325
+
2326
+ const result = await readFile(workspaceRoot, {
2279
2327
  ...args,
2280
2328
  default_lines: config.context?.read_file_default_lines ?? 220,
2281
2329
  max_chars:
2282
2330
  typeof args?.max_chars === 'number'
2283
2331
  ? args.max_chars
2284
2332
  : config.context?.read_file_max_chars ?? 24000
2285
- }).then((result) => {
2286
- const readPath = String(result?.path || args?.path || '').trim();
2287
- if (readPath) lastReadPath = readPath;
2288
- return result;
2289
- }),
2333
+ });
2334
+ const readPath = String(result?.path || args?.path || '').trim();
2335
+ if (readPath) lastReadPath = readPath;
2336
+ return result;
2337
+ },
2290
2338
  query_project_index: async (args) => {
2291
2339
  await ensureProjectIndex();
2292
2340
  return queryProjectIndex(workspaceRoot, args);
@@ -2427,6 +2475,10 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
2427
2475
  read(result) {
2428
2476
  if (typeof result === 'string') return result;
2429
2477
  if (!result || typeof result !== 'object') return String(result);
2478
+ if (result.node && typeof result.content === 'string') {
2479
+ const header = `[AST: ${result.path || '?'} ${result.node.node_type || 'node'} ${result.node.start_line || '?'}-${result.node.end_line || '?'}${result.matches ? `, matches ${result.matches}` : ''}]`;
2480
+ return `${header}\n${result.content}`;
2481
+ }
2430
2482
  // Phase 1 metadata: small, return as-is
2431
2483
  if (result.phase === 'metadata') {
2432
2484
  return JSON.stringify(result);
@@ -961,6 +961,17 @@ function normalizeRuntimeStatus(status, copy) {
961
961
  };
962
962
  }
963
963
 
964
+ export function shouldRefreshRuntimeStateForEvent(event) {
965
+ const type = String(event?.type || '');
966
+ return (
967
+ type === 'assistant:start' ||
968
+ type === 'assistant:delta' ||
969
+ type === 'assistant:response' ||
970
+ type === 'tool:result' ||
971
+ type === 'compact:auto'
972
+ );
973
+ }
974
+
964
975
  function stageDescriptor(inputStage, busy, runtimeStatus, copy) {
965
976
  const normalized = normalizeRuntimeStatus(runtimeStatus, copy);
966
977
  const tag =
@@ -1829,6 +1840,10 @@ export function normalizeActivitySpacingRows(inputRows) {
1829
1840
  }
1830
1841
  }
1831
1842
 
1843
+ if (isTodoRow(row) && !isTodoRow(next) && next && !isBlankTextRow(next) && next.kind !== 'status') {
1844
+ normalized.push({ kind: 'todo-gap' });
1845
+ }
1846
+
1832
1847
  if (isBlankTextRow(row) && isBlankTextRow(prev)) {
1833
1848
  normalized.pop();
1834
1849
  }
@@ -1954,7 +1969,7 @@ export function collapseActivityChainRows(inputRows, showToolDetails, copy, maxV
1954
1969
  return collapsed;
1955
1970
  }
1956
1971
 
1957
- function buildMessageRows(msg, showToolDetails, contentWidth = 72, copy) {
1972
+ export function buildMessageRows(msg, showToolDetails, contentWidth = 72, copy) {
1958
1973
  const rows = [];
1959
1974
  const pushTextRows = (text) => {
1960
1975
  const lines = String(text || '').split('\n');
@@ -2071,26 +2086,26 @@ function buildMessageRows(msg, showToolDetails, contentWidth = 72, copy) {
2071
2086
 
2072
2087
  const codeGenerationRows = getCodeGenerationActivityRows(msg);
2073
2088
  const generatingCodeRows = getGeneratingCodePlaceholderRows(msg, copy, contentWidth);
2074
- const syntheticRows = [...codeGenerationRows, ...generatingCodeRows];
2089
+ const trailingRows = [];
2075
2090
  if (msg?.loading && (msg?.liveStatus || msg?.phase)) {
2076
- const statusRows = [];
2077
2091
  pushWrappedRow(
2078
- statusRows,
2092
+ trailingRows,
2079
2093
  {
2080
2094
  kind: 'status',
2081
2095
  text: trimText(msg.liveStatus || msg.phase, 144)
2082
2096
  },
2083
2097
  Math.max(8, contentWidth - 2)
2084
2098
  );
2085
- syntheticRows.push(...statusRows);
2086
2099
  }
2087
2100
 
2088
- return normalizeActivitySpacingRows(
2089
- insertRowsAfterLastCodeRow(collapseActivityChainRows(rows, showToolDetails, copy), syntheticRows)
2101
+ const rowsWithCodePreview = insertRowsAfterLastCodeRow(
2102
+ collapseActivityChainRows(rows, showToolDetails, copy),
2103
+ [...codeGenerationRows, ...generatingCodeRows]
2090
2104
  );
2105
+ return normalizeActivitySpacingRows([...rowsWithCodePreview, ...trailingRows]);
2091
2106
  }
2092
2107
 
2093
- function renderMessageRow(msg, row, idx, loaderTick) {
2108
+ export function renderMessageRow(msg, row, idx, loaderTick) {
2094
2109
  if (row.kind === 'activity') {
2095
2110
  const activity = { type: row.activityType, name: row.name, status: row.status };
2096
2111
  const display = getActivityDisplayParts(activity);
@@ -2139,13 +2154,16 @@ function renderMessageRow(msg, row, idx, loaderTick) {
2139
2154
  const dimColor = row.status === 'completed';
2140
2155
  return h(
2141
2156
  Box,
2142
- { key: `row-todo-${msg.id}-${idx}`, marginLeft: 2, marginBottom: 1 },
2157
+ { key: `row-todo-${msg.id}-${idx}`, marginLeft: 2 },
2143
2158
  h(Text, { color: 'gray' }, ' '),
2144
2159
  h(Text, { color }, marker),
2145
2160
  h(Text, { color: 'gray' }, ' '),
2146
2161
  h(Text, { color, dimColor }, row.text || row.activeForm || ' ')
2147
2162
  );
2148
2163
  }
2164
+ if (row.kind === 'todo-gap') {
2165
+ return h(Box, { key: `row-todo-gap-${msg.id}-${idx}`, marginTop: 1 }, h(Text, { color: 'gray' }, ' '));
2166
+ }
2149
2167
  if (row.kind === 'table') {
2150
2168
  return h(
2151
2169
  Box,
@@ -2203,22 +2221,11 @@ function renderMessageRow(msg, row, idx, loaderTick) {
2203
2221
  }
2204
2222
  if (row.kind === 'status') {
2205
2223
  const dots = '.'.repeat((loaderTick % 3) + 1);
2206
- const phase = msg.phase;
2207
- const color =
2208
- phase === 'sending'
2209
- ? 'yellowBright'
2210
- : phase === 'queued'
2211
- ? 'cyanBright'
2212
- : phase === 'tooling'
2213
- ? 'magentaBright'
2214
- : phase === 'generating'
2215
- ? 'greenBright'
2216
- : 'cyanBright';
2217
2224
  return h(
2218
2225
  Box,
2219
2226
  { key: `row-status-${msg.id}-${idx}`, marginTop: 1 },
2220
2227
  h(Text, { color: 'gray' }, ' '),
2221
- h(Text, { color }, `${row.text}${dots}`)
2228
+ h(Text, { color: 'gray', dimColor: true }, `${row.text}${dots}`)
2222
2229
  );
2223
2230
  }
2224
2231
  if (row.kind === 'quote') {
@@ -2762,6 +2769,15 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
2762
2769
  setRuntimeStatus(makeIdleStatus(copy, snapshot, variant));
2763
2770
  };
2764
2771
 
2772
+ const refreshRuntimeSnapshot = () => {
2773
+ const snapshot = runtime.getRuntimeState?.();
2774
+ if (!snapshot) return;
2775
+ setDisplaySessionId(snapshot.sessionId || sessionId);
2776
+ setDisplayModel(snapshot.model || model);
2777
+ setDisplaySdkProvider(snapshot.sdkProvider || sdkProvider);
2778
+ setRuntimeState(snapshot);
2779
+ };
2780
+
2765
2781
  useEffect(() => {
2766
2782
  syncRuntimeVisualState('ready');
2767
2783
  }, []);
@@ -3137,6 +3153,9 @@ export function ChatApp({ runtime, sessionId, model, sdkProvider = 'openai-compa
3137
3153
 
3138
3154
  runtime
3139
3155
  .submit(line, (event) => {
3156
+ if (shouldRefreshRuntimeStateForEvent(event)) {
3157
+ refreshRuntimeSnapshot();
3158
+ }
3140
3159
  if (event?.type === 'assistant:start') {
3141
3160
  streamedAssistantHandledRef.current = true;
3142
3161
  setRuntimeStatus(makeStatus(copy.runtime.modelThinking, copy.runtime.requestDelivered, 'cyanBright'));