@chemx/starter-kit 26.9.25-943 → 26.9.26-253

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.
@@ -1,4 +1,23 @@
1
+ import fs from 'node:fs';
1
2
  import path from 'node:path';
3
+ import { createRequire } from 'node:module';
4
+ import { extractSymbolBlock } from './reader.js';
5
+
6
+ const _require = createRequire(import.meta.url);
7
+ let _parse = null;
8
+ let _traverse = null;
9
+ const loadBabel = () => {
10
+ if (_parse) return { parse: _parse, traverse: _traverse };
11
+ try {
12
+ _parse = _require('@babel/parser').parse;
13
+ const t = _require('@babel/traverse');
14
+ _traverse = t.default?.default || t.default || t;
15
+ } catch {
16
+ _parse = null;
17
+ _traverse = null;
18
+ }
19
+ return { parse: _parse, traverse: _traverse };
20
+ };
2
21
 
3
22
  const isSpecOrTest = (filePath) => {
4
23
  if (!filePath || typeof filePath !== 'string') return false;
@@ -107,3 +126,278 @@ export const calculateBlastRadius = (db, targetPathOrSymbol, options = {}) => {
107
126
  tiers
108
127
  };
109
128
  };
129
+
130
+ /**
131
+ * Extracts called function and method symbols from a code block via AST parsing.
132
+ * Falls back to regex extraction if @babel/parser is unavailable.
133
+ *
134
+ * @param {string} code Source code snippet.
135
+ * @returns {string[]} List of unique called symbol names.
136
+ */
137
+ export const extractCalleesFromCode = (code) => {
138
+ const callees = new Set();
139
+ const { parse, traverse } = loadBabel();
140
+ try {
141
+ if (!parse || !traverse) throw new Error('babel unavailable');
142
+ const ast = parse(code, {
143
+ sourceType: 'module',
144
+ plugins: ['typescript', 'jsx', 'decorators-legacy', 'topLevelAwait'],
145
+ errorRecovery: true,
146
+ });
147
+ traverse(ast, {
148
+ CallExpression(nodePath) {
149
+ const callee = nodePath.node.callee;
150
+ if (callee.type === 'Identifier') {
151
+ callees.add(callee.name);
152
+ } else if (callee.type === 'MemberExpression') {
153
+ const obj = callee.object?.name || callee.object?.property?.name || '';
154
+ const prop = callee.property?.name || '';
155
+ if (obj && prop) callees.add(`${obj}.${prop}`);
156
+ else if (prop) callees.add(prop);
157
+ }
158
+ },
159
+ OptionalCallExpression(nodePath) {
160
+ const callee = nodePath.node.callee;
161
+ if (callee.type === 'Identifier') {
162
+ callees.add(callee.name);
163
+ } else if (callee.type === 'MemberExpression') {
164
+ const obj = callee.object?.name || '';
165
+ const prop = callee.property?.name || '';
166
+ if (obj && prop) callees.add(`${obj}.${prop}`);
167
+ else if (prop) callees.add(prop);
168
+ }
169
+ }
170
+ });
171
+ } catch {
172
+ const matches = code.matchAll(/\b([A-Za-z0-9_$]+)\s*\(/g);
173
+ for (const m of matches) {
174
+ if (!['if', 'for', 'while', 'switch', 'catch', 'function'].includes(m[1])) {
175
+ callees.add(m[1]);
176
+ }
177
+ }
178
+ }
179
+ return Array.from(callees);
180
+ };
181
+
182
+ /**
183
+ * Calculates forward call trace: downstream functions invoked by target symbol.
184
+ *
185
+ * @param {object} db SQLite index database.
186
+ * @param {string} targetSymbolOrPath Target symbol or file path.
187
+ * @param {object} options Traversal options.
188
+ * @returns {object} Call trace tree payload.
189
+ */
190
+ export const calculateCallTrace = (db, targetSymbolOrPath, options = {}) => {
191
+ if (!db || !targetSymbolOrPath) {
192
+ return { target: targetSymbolOrPath || '', totalCallees: 0, depth: 0, callees: [] };
193
+ }
194
+
195
+ const cleanTarget = targetSymbolOrPath.trim();
196
+ const maxDepth = typeof options.maxDepth === 'number' ? options.maxDepth : 3;
197
+
198
+ const symRow = db.prepare(`
199
+ SELECT s.name, s.kind, s.start_line, s.end_line, s.file_path, f.tier
200
+ FROM symbols s
201
+ JOIN files f ON s.file_path = f.path
202
+ WHERE s.name = ?
203
+ LIMIT 1
204
+ `).get(cleanTarget);
205
+
206
+ let targetPath = symRow ? symRow.file_path : cleanTarget;
207
+ let symbolName = symRow ? symRow.name : cleanTarget;
208
+ let startLine = symRow ? Number(symRow.start_line) : 1;
209
+ let endLine = symRow ? Number(symRow.end_line) : 1;
210
+ let tier = symRow ? symRow.tier : 'utility';
211
+
212
+ const visited = new Set([symbolName]);
213
+
214
+ const traceCallees = (symbol, filePath, currentDepth) => {
215
+ if (currentDepth > maxDepth) return [];
216
+
217
+ let codeSlice = '';
218
+ let fullText = '';
219
+ const absPath = path.isAbsolute(filePath) ? filePath : path.resolve(process.cwd(), filePath);
220
+ if (fs.existsSync(absPath)) {
221
+ fullText = fs.readFileSync(absPath, 'utf-8');
222
+ const block = extractSymbolBlock(fullText, symbol);
223
+ if (block) {
224
+ codeSlice = block.code;
225
+ } else {
226
+ const fileLines = fullText.split('\n');
227
+ const start = Math.max(0, startLine - 1);
228
+ const end = Math.min(fileLines.length, endLine > start ? endLine : start + 100);
229
+ codeSlice = fileLines.slice(start, end).join('\n');
230
+ }
231
+ }
232
+
233
+ if (!codeSlice) return [];
234
+
235
+ const foundCallees = extractCalleesFromCode(codeSlice);
236
+ const results = [];
237
+
238
+ for (const c of foundCallees) {
239
+ if (visited.has(c)) continue;
240
+ visited.add(c);
241
+
242
+ const calleeSym = db.prepare(`
243
+ SELECT s.name, s.kind, s.start_line, s.end_line, s.file_path, f.tier
244
+ FROM symbols s
245
+ JOIN files f ON s.file_path = f.path
246
+ WHERE s.name = ?
247
+ LIMIT 1
248
+ `).get(c);
249
+
250
+ if (calleeSym) {
251
+ const subCallees = traceCallees(calleeSym.name, calleeSym.file_path, currentDepth + 1);
252
+ results.push({
253
+ symbol: calleeSym.name,
254
+ file: calleeSym.file_path,
255
+ line: Number(calleeSym.start_line),
256
+ tier: calleeSym.tier,
257
+ depth: currentDepth,
258
+ isExternal: false,
259
+ callees: subCallees
260
+ });
261
+ } else {
262
+ const baseName = c.includes('.') ? c.split('.')[0] : c;
263
+ const importRow = db.prepare(`
264
+ SELECT source_module
265
+ FROM imports
266
+ WHERE (importer_path = ? OR importer_path LIKE ?)
267
+ AND (imported_symbol = ? OR imported_symbol = ? OR imported_symbol = '*')
268
+ LIMIT 1
269
+ `).get(filePath, `%${path.basename(filePath)}%`, c, baseName);
270
+
271
+ if (importRow) {
272
+ results.push({
273
+ symbol: c,
274
+ file: importRow.source_module,
275
+ line: 1,
276
+ tier: 'external',
277
+ depth: currentDepth,
278
+ isExternal: true,
279
+ callees: []
280
+ });
281
+ } else {
282
+ const isLocal = new RegExp(`(?:const|function|let|var|class)\\s+${baseName}\\b`).test(fullText);
283
+ if (isLocal && baseName !== symbol) {
284
+ const subCallees = traceCallees(baseName, filePath, currentDepth + 1);
285
+ results.push({
286
+ symbol: c,
287
+ file: filePath,
288
+ line: 1,
289
+ tier: 'local',
290
+ depth: currentDepth,
291
+ isExternal: false,
292
+ callees: subCallees
293
+ });
294
+ }
295
+ }
296
+ }
297
+ }
298
+
299
+ return results;
300
+ };
301
+
302
+ const calleeTree = traceCallees(symbolName, targetPath, 1);
303
+ const totalCount = visited.size - 1;
304
+
305
+ return {
306
+ target: symbolName,
307
+ filePath: targetPath,
308
+ startLine,
309
+ tier,
310
+ totalCallees: totalCount,
311
+ depth: calleeTree.reduce((acc, c) => Math.max(acc, c.depth), 0),
312
+ callees: calleeTree
313
+ };
314
+ };
315
+
316
+ /**
317
+ * Calculates reverse backtrace: upstream callers leading to target symbol.
318
+ *
319
+ * @param {object} db SQLite index database.
320
+ * @param {string} targetSymbolOrPath Target symbol or file path.
321
+ * @param {object} options Traversal options.
322
+ * @returns {object} Backtrace causal path payload.
323
+ */
324
+ export const calculateBacktrace = (db, targetSymbolOrPath, options = {}) => {
325
+ if (!db || !targetSymbolOrPath) {
326
+ return { target: targetSymbolOrPath || '', totalCallers: 0, depth: 0, chains: [], callers: [] };
327
+ }
328
+
329
+ const cleanTarget = targetSymbolOrPath.trim();
330
+ const maxDepth = typeof options.maxDepth === 'number' ? options.maxDepth : 5;
331
+
332
+ let seedPath = '';
333
+ let targetSymbolName = cleanTarget;
334
+ const symRow = db.prepare('SELECT file_path, name FROM symbols WHERE name = ? LIMIT 1').get(cleanTarget);
335
+ if (symRow) {
336
+ seedPath = symRow.file_path;
337
+ targetSymbolName = symRow.name;
338
+ } else {
339
+ const fileRow = db.prepare('SELECT path FROM files WHERE path = ? OR path LIKE ? LIMIT 1').get(cleanTarget, `%${cleanTarget}%`);
340
+ if (fileRow) seedPath = fileRow.path;
341
+ }
342
+
343
+ const seedBaseName = seedPath ? path.basename(seedPath).replace(/\.[^.]+$/, '') : cleanTarget;
344
+
345
+ const querySql = `
346
+ WITH RECURSIVE backtrace_tree(caller_path, caller_symbol, depth, chain) AS (
347
+ SELECT DISTINCT i.importer_path, i.imported_symbol, 1, i.importer_path || ' (' || i.imported_symbol || ')'
348
+ FROM imports i
349
+ WHERE (? != '' AND i.imported_symbol = ?)
350
+ OR (i.resolved_path = ? AND i.resolved_path != '')
351
+ OR (? != '' AND i.source_module LIKE '%' || ? || '%')
352
+
353
+ UNION
354
+
355
+ SELECT DISTINCT i.importer_path, i.imported_symbol, bt.depth + 1, i.importer_path || ' -> ' || bt.chain
356
+ FROM imports i
357
+ JOIN backtrace_tree bt ON (
358
+ (i.resolved_path = bt.caller_path AND i.resolved_path != '')
359
+ OR i.source_module LIKE '%' || bt.caller_path || '%'
360
+ )
361
+ WHERE bt.depth < ?
362
+ AND instr(bt.chain, i.importer_path) = 0
363
+ )
364
+ SELECT bt.caller_path, bt.caller_symbol, min(bt.depth) as depth, bt.chain, COALESCE(f.tier, 'utility') as tier
365
+ FROM backtrace_tree bt
366
+ LEFT JOIN files f ON bt.caller_path = f.path
367
+ WHERE bt.caller_path != ?
368
+ GROUP BY bt.caller_path, bt.caller_symbol
369
+ ORDER BY depth DESC, bt.caller_path ASC;
370
+ `;
371
+
372
+ const rows = db.prepare(querySql).all(
373
+ targetSymbolName,
374
+ targetSymbolName,
375
+ seedPath,
376
+ seedBaseName,
377
+ seedBaseName,
378
+ maxDepth,
379
+ seedPath
380
+ );
381
+
382
+ const callers = rows.map((r) => ({
383
+ path: r.caller_path,
384
+ symbol: r.caller_symbol,
385
+ depth: Number(r.depth),
386
+ chain: r.chain,
387
+ tier: r.tier,
388
+ isEntry: ['view', 'page', 'route', 'template'].includes(r.tier) || isSpecOrTest(r.caller_path)
389
+ }));
390
+
391
+ const chains = callers.map((c) => `${c.chain} -> ${cleanTarget}`);
392
+ const maxReachedDepth = callers.reduce((acc, c) => Math.max(acc, c.depth), 0);
393
+
394
+ return {
395
+ target: cleanTarget,
396
+ seedPath,
397
+ totalCallers: callers.length,
398
+ depth: maxReachedDepth,
399
+ chains,
400
+ callers,
401
+ rootCallers: callers.filter((c) => c.isEntry || c.depth === maxReachedDepth)
402
+ };
403
+ };
@@ -239,7 +239,12 @@ export const queryFilesByHealth = (db, { status = 'all', limit = 50 } = {}) => {
239
239
  }));
240
240
  };
241
241
 
242
- export { calculateBlastRadius } from './search-queries-graph.js';
242
+ export {
243
+ calculateBlastRadius,
244
+ calculateCallTrace,
245
+ calculateBacktrace,
246
+ extractCalleesFromCode
247
+ } from './search-queries-graph.js';
243
248
  export { querySemanticIndex, queryHybridIndex } from './search-queries-semantic.js';
244
249
 
245
250
 
package/cli/search.js CHANGED
@@ -28,6 +28,8 @@ import {
28
28
  handleHealthFilterCommand,
29
29
  handleCheckCommand,
30
30
  handleBlastRadiusCommand,
31
+ handleCallTraceCommand,
32
+ handleBacktraceCommand,
31
33
  handleSemanticCommand,
32
34
  handleHybridCommand
33
35
  } from './search-commands.js';
@@ -39,41 +41,21 @@ import { ANSI } from './theme.js';
39
41
  export { toColumnar, fromColumnar } from './columnar.js';
40
42
 
41
43
  export {
42
- openIndexDb,
43
- upsertFileIndex,
44
- getIndexStats,
45
- findSymbolDefinition,
46
- findSymbolReferences,
47
- findFileDependencies,
48
- findFileDependents,
49
- calculateBlastRadius,
50
- querySemanticIndex,
51
- queryHybridIndex,
52
- syncViolationsIndex,
53
- queryViolations,
54
- recordAuditSnapshot,
55
- getAuditProgression,
56
- queryFilesByHealth
44
+ openIndexDb, upsertFileIndex, getIndexStats,
45
+ findSymbolDefinition, findSymbolReferences,
46
+ findFileDependencies, findFileDependents,
47
+ calculateBlastRadius, querySemanticIndex, queryHybridIndex,
48
+ syncViolationsIndex, queryViolations,
49
+ recordAuditSnapshot, getAuditProgression, queryFilesByHealth
57
50
  } from './search-db.js';
58
51
  export {
59
- handleCheckCommand,
60
- handleBlastRadiusCommand,
61
- handleSemanticCommand,
62
- handleHybridCommand
52
+ handleCheckCommand, handleBlastRadiusCommand,
53
+ handleSemanticCommand, handleHybridCommand
63
54
  } from './search-commands.js';
64
55
 
65
56
  const IGNORED_DIRS = new Set([
66
- 'node_modules',
67
- 'dist',
68
- 'build',
69
- 'vendor',
70
- '.git',
71
- '.next',
72
- '.turbo',
73
- '.output',
74
- '.nuxt',
75
- '.cache',
76
- 'out'
57
+ 'node_modules', 'dist', 'build', 'vendor', '.git',
58
+ '.next', '.turbo', '.output', '.nuxt', '.cache', 'out'
77
59
  ]);
78
60
 
79
61
  const EXCLUDED_NAME_PATTERNS = ['.test.', '.spec.', '.min.'];
@@ -370,6 +352,22 @@ export const runSearch = async (rawArgs = [], isCli = true) => {
370
352
  return handleBlastRadiusCommand(db, target, { isJson, isCli, isColumnar, maxDepth });
371
353
  }
372
354
 
355
+ const isTraceCommand = firstArg === 'trace' || rawArgs.includes('--trace');
356
+ if (isTraceCommand) {
357
+ const target = firstArg === 'trace' ? secondArg : firstArg;
358
+ const maxDepthFlag = rawArgs.find((a) => a.startsWith('--max-depth='));
359
+ const maxDepth = maxDepthFlag ? parseInt(maxDepthFlag.split('=')[1], 10) : 3;
360
+ return handleCallTraceCommand(db, target, { isJson, isCli, maxDepth });
361
+ }
362
+
363
+ const isBacktraceCommand = firstArg === 'backtrace' || rawArgs.includes('--backtrace');
364
+ if (isBacktraceCommand) {
365
+ const target = firstArg === 'backtrace' ? secondArg : firstArg;
366
+ const maxDepthFlag = rawArgs.find((a) => a.startsWith('--max-depth='));
367
+ const maxDepth = maxDepthFlag ? parseInt(maxDepthFlag.split('=')[1], 10) : 5;
368
+ return handleBacktraceCommand(db, target, { isJson, isCli, maxDepth });
369
+ }
370
+
373
371
  const isSemanticCommand = firstArg === 'semantic' || rawArgs.includes('--semantic');
374
372
  if (isSemanticCommand) {
375
373
  const query = firstArg === 'semantic' ? secondArg : firstArg;
@@ -84,17 +84,26 @@ export const runAblationComparison = (db) => {
84
84
  const precisionGain = (100 / 12.0).toFixed(1) + 'x';
85
85
  return {
86
86
  hasRealData: false,
87
+ hasMeasuredTokens: false,
87
88
  isSimulated: true,
88
89
  workloadTurns: turns,
89
90
  memoryEnabled: { mode: 'SQLite AST & Event Memory', tokensUsed: memTokens, staleRetryRatePct: 0.0, retrievalPrecisionPct: 100, astQualityScore: 100 },
90
91
  memoryDisabled: { mode: 'Stateless Monolithic File Re-read', tokensUsed: baseTokens, staleRetryRatePct: 18.5, retrievalPrecisionPct: 12.0, astQualityScore: 84 },
91
- delta: { tokenReductionPct: Math.round((tokensSaved / baseTokens) * 100), costAvoidedUsd: Number(((tokensSaved / 1000000) * 10.0).toFixed(4)), retrievalPrecisionGainRatio: precisionGain, staleRetriesEliminated: Math.round(turns * 0.185) }
92
+ delta: {
93
+ tokenReductionPct: Math.round((tokensSaved / baseTokens) * 100),
94
+ tokenReductionBasis: 'estimated_typical_usage',
95
+ isEstimated: true,
96
+ costAvoidedUsd: Number(((tokensSaved / 1000000) * 10.0).toFixed(4)),
97
+ retrievalPrecisionGainRatio: precisionGain,
98
+ staleRetriesEliminated: Math.round(turns * 0.185)
99
+ }
92
100
  };
93
101
  }
94
102
 
95
103
  const turns = taskStats.totalTasks + m.totalCount;
96
104
  const liveTokens = taskStats.totalPromptTokens + taskStats.totalCompletionTokens;
97
- const memTokens = liveTokens > 0 ? liveTokens : Math.max(turns * 70, m.totalInjectedTokens || 70);
105
+ const hasMeasuredTokens = liveTokens > 0;
106
+ const memTokens = hasMeasuredTokens ? liveTokens : Math.max(turns * 70, m.totalInjectedTokens || 70);
98
107
  const baseTokens = turns * 2500 + Math.round(memTokens * 0.5);
99
108
  const tokensSaved = Math.max(0, baseTokens - memTokens);
100
109
  const tokenReductionPct = baseTokens > 0 ? Math.round((tokensSaved / baseTokens) * 100) : 0;
@@ -105,11 +114,19 @@ export const runAblationComparison = (db) => {
105
114
 
106
115
  return {
107
116
  hasRealData: true,
117
+ hasMeasuredTokens,
108
118
  isSimulated: false,
109
119
  workloadTurns: turns,
110
120
  memoryEnabled: { mode: 'SQLite AST & Event Memory', tokensUsed: memTokens, staleRetryRatePct: retryRatePct, retrievalPrecisionPct: m.precisionPct, astQualityScore: 100 },
111
121
  memoryDisabled: { mode: 'Stateless Monolithic File Re-read', tokensUsed: baseTokens, staleRetryRatePct: 18.5, retrievalPrecisionPct: 12.0, astQualityScore: 84 },
112
- delta: { tokenReductionPct, costAvoidedUsd, retrievalPrecisionGainRatio: precisionGainRatio, staleRetriesEliminated }
122
+ delta: {
123
+ tokenReductionPct,
124
+ tokenReductionBasis: hasMeasuredTokens ? 'measured' : 'estimated_typical_usage',
125
+ isEstimated: !hasMeasuredTokens,
126
+ costAvoidedUsd,
127
+ retrievalPrecisionGainRatio: precisionGainRatio,
128
+ staleRetriesEliminated
129
+ }
113
130
  };
114
131
  };
115
132
 
@@ -132,13 +149,19 @@ export const formatAblationCard = (a) => {
132
149
  );
133
150
  }
134
151
 
152
+ const tokenReductionLabel = a.hasMeasuredTokens
153
+ ? `${delta.tokenReductionPct}% fewer tokens consumed (measured vs modeled baseline)`
154
+ : `${delta.tokenReductionPct}% fewer tokens consumed (estimated based on typical usage)`;
155
+ const costAvoidedSuffix = a.hasMeasuredTokens ? '' : ' (projected)';
156
+ const memoryTokensSuffix = a.hasMeasuredTokens ? '' : ' (est.)';
157
+
135
158
  lines.push(
136
159
  ` \x1b[1mWorkload:\x1b[0m ${a.workloadTurns} agent turns / tasks`,
137
- ` \x1b[1mMemory Mode (SQLite):\x1b[0m ${m.tokensUsed.toLocaleString()} tokens │ Precision: ${m.retrievalPrecisionPct}% │ Retry: ${m.staleRetryRatePct}%`,
138
- ` \x1b[1mBaseline (Stateless):\x1b[0m ${d.tokensUsed.toLocaleString()} tokens │ Precision: ${d.retrievalPrecisionPct}% │ Retry: ${d.staleRetryRatePct}%`,
160
+ ` \x1b[1mMemory Mode (SQLite):\x1b[0m ${m.tokensUsed.toLocaleString()} tokens${memoryTokensSuffix} │ Precision: ${m.retrievalPrecisionPct}% │ Retry: ${m.staleRetryRatePct}%`,
161
+ ` \x1b[1mBaseline (Stateless):\x1b[0m ${d.tokensUsed.toLocaleString()} tokens (modeled) │ Precision: ${d.retrievalPrecisionPct}% │ Retry: ${d.staleRetryRatePct}%`,
139
162
  `\x1b[90m${'─'.repeat(58)}\x1b[0m`,
140
- ` \x1b[32m✔ Token Reduction:\x1b[0m ${delta.tokenReductionPct}% fewer tokens consumed`,
141
- ` \x1b[32m✔ Cost Avoided:\x1b[0m ${delta.costAvoidedUsd.toFixed(4)} USD`,
163
+ ` \x1b[32m✔ Token Reduction:\x1b[0m ${tokenReductionLabel}`,
164
+ ` \x1b[32m✔ Cost Avoided:\x1b[0m ${delta.costAvoidedUsd.toFixed(4)} USD${costAvoidedSuffix}`,
142
165
  ` \x1b[32m✔ Precision Gain:\x1b[0m ${delta.retrievalPrecisionGainRatio} higher retrieval precision per dollar`,
143
166
  ` \x1b[32m✔ Stale Retries:\x1b[0m ${delta.staleRetriesEliminated} retries eliminated`,
144
167
  `\x1b[90m${'─'.repeat(58)}\x1b[0m\n`
@@ -95,5 +95,29 @@ test("runAblationComparison computes dynamic metrics from live task and memory r
95
95
 
96
96
  const card = formatAblationCard(ablation);
97
97
  assert.ok(card.includes("Live Project Telemetry"));
98
+ assert.ok(card.includes("measured vs modeled baseline"));
99
+ assert.strictEqual(ablation.hasMeasuredTokens, true);
100
+ assert.strictEqual(ablation.delta.isEstimated, false);
101
+ assert.strictEqual(ablation.delta.tokenReductionBasis, "measured");
98
102
  assert.strictEqual(card.includes("—"), false);
99
103
  }));
104
+
105
+ test("runAblationComparison marks token reduction as estimated when live tasks lack token counts", () => withTmpCwd((db) => {
106
+ db.prepare(`
107
+ INSERT INTO agent_tasks (title, status, created_at, updated_at)
108
+ VALUES ('Task without token telemetry', 'done', 1000, 1000)
109
+ `).run();
110
+
111
+ const ablation = runAblationComparison(db);
112
+ assert.strictEqual(ablation.hasRealData, true);
113
+ assert.strictEqual(ablation.hasMeasuredTokens, false);
114
+ assert.strictEqual(ablation.delta.isEstimated, true);
115
+ assert.strictEqual(ablation.delta.tokenReductionBasis, "estimated_typical_usage");
116
+ assert.strictEqual(ablation.delta.tokenReductionPct, 97);
117
+
118
+ const card = formatAblationCard(ablation);
119
+ assert.ok(card.includes("estimated based on typical usage"));
120
+ assert.ok(card.includes("(projected)"));
121
+ assert.strictEqual(card.includes("—"), false);
122
+ }));
123
+
@@ -27,7 +27,7 @@ test('tesseract: cli command execution succeeds with exit code 0', () => {
27
27
  const cliPath = path.resolve('cli/index.js');
28
28
  const res = spawnSync(process.execPath, [cliPath, 'tesseract'], {
29
29
  encoding: 'utf8',
30
- timeout: 5000
30
+ timeout: 15000
31
31
  });
32
32
 
33
33
  assert.strictEqual(res.status, 0, 'CLI tesseract should exit with 0');
@@ -40,7 +40,7 @@ test('tesseract: cli aliases cube and matrix work identically', () => {
40
40
  for (const alias of ['cube', 'matrix']) {
41
41
  const res = spawnSync(process.execPath, [cliPath, alias], {
42
42
  encoding: 'utf8',
43
- timeout: 5000
43
+ timeout: 15000
44
44
  });
45
45
  assert.strictEqual(res.status, 0, `CLI ${alias} should exit with 0`);
46
46
  assert.ok(res.stdout.includes('TESSERACT'), `CLI ${alias} should render Tesseract`);
@@ -0,0 +1,92 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {
4
+ extractCalleesFromCode,
5
+ calculateCallTrace,
6
+ calculateBacktrace
7
+ } from './search-queries-graph.js';
8
+ import {
9
+ handleCallTraceCommand,
10
+ handleBacktraceCommand
11
+ } from './search-commands-graph.js';
12
+ import { openIndexDb, upsertFileIndex } from './search-db.js';
13
+
14
+ describe('Call Trace & Backtrace Engine', () => {
15
+ it('extracts call expressions from code snippet', () => {
16
+ const code = `
17
+ const handleAction = async () => {
18
+ if (!canProceed.value) return;
19
+ state.value = { status: 'loading' };
20
+ await api.postOrder(items.value);
21
+ emit('success');
22
+ };
23
+ `;
24
+ const callees = extractCalleesFromCode(code);
25
+ assert.ok(callees.includes('api.postOrder'), 'Should extract api.postOrder');
26
+ assert.ok(callees.includes('emit'), 'Should extract emit');
27
+ });
28
+
29
+ it('calculates forward call trace gracefully on unknown or empty target', () => {
30
+ const db = openIndexDb();
31
+ const trace = calculateCallTrace(db, 'nonExistentSymbol', { maxDepth: 2 });
32
+ assert.equal(trace.target, 'nonExistentSymbol');
33
+ assert.equal(trace.totalCallees, 0);
34
+ assert.deepEqual(trace.callees, []);
35
+ });
36
+
37
+ it('calculates reverse backtrace using recursive imports CTE', () => {
38
+ const db = openIndexDb();
39
+
40
+ upsertFileIndex(db, {
41
+ path: 'src/controllers/trace-cart.ts',
42
+ mtime: Date.now(),
43
+ size: 400,
44
+ tier: 'hook',
45
+ lines: 40,
46
+ chars: 400,
47
+ symbols: [{ name: 'useTraceCartController', kind: 'const', isExport: true, startLine: 1, endLine: 40 }],
48
+ imports: []
49
+ });
50
+
51
+ upsertFileIndex(db, {
52
+ path: 'src/organisms/trace-cart.vue',
53
+ mtime: Date.now(),
54
+ size: 800,
55
+ tier: 'organism',
56
+ lines: 80,
57
+ chars: 800,
58
+ symbols: [{ name: 'TraceCartOrganism', kind: 'const', isExport: true, startLine: 1, endLine: 80 }],
59
+ imports: [
60
+ { importedSymbol: 'useTraceCartController', sourceModule: '@/controllers/trace-cart.ts', resolvedPath: 'src/controllers/trace-cart.ts', line: 3 }
61
+ ]
62
+ });
63
+
64
+ upsertFileIndex(db, {
65
+ path: 'src/views/trace-checkout.vue',
66
+ mtime: Date.now(),
67
+ size: 500,
68
+ tier: 'view',
69
+ lines: 50,
70
+ chars: 500,
71
+ symbols: [{ name: 'TraceCheckoutView', kind: 'const', isExport: true, startLine: 1, endLine: 50 }],
72
+ imports: [
73
+ { importedSymbol: 'TraceCartOrganism', sourceModule: '@/organisms/trace-cart.vue', resolvedPath: 'src/organisms/trace-cart.vue', line: 5 }
74
+ ]
75
+ });
76
+
77
+ const backtrace = calculateBacktrace(db, 'useTraceCartController', { maxDepth: 5 });
78
+ assert.equal(backtrace.target, 'useTraceCartController');
79
+ assert.ok(backtrace.totalCallers >= 2, `Expected at least 2 callers, got ${backtrace.totalCallers}`);
80
+ assert.ok(backtrace.chains.some((c) => c.includes('trace-checkout.vue')), 'Causal chain should reach root view');
81
+ assert.ok(backtrace.rootCallers.some((r) => r.tier === 'view'), 'Root callers should include view tier');
82
+ });
83
+
84
+ it('formats call trace and backtrace as JSON payloads', () => {
85
+ const db = openIndexDb();
86
+ const traceRes = handleCallTraceCommand(db, 'foo', { isJson: true, isCli: false });
87
+ assert.equal(traceRes.target, 'foo');
88
+
89
+ const backtraceRes = handleBacktraceCommand(db, 'foo', { isJson: true, isCli: false });
90
+ assert.equal(backtraceRes.target, 'foo');
91
+ });
92
+ });
@@ -30,16 +30,59 @@ export const detectTypecheckCommand = (customCmd, cwd = process.cwd()) => {
30
30
  return `${pm} run typecheck`;
31
31
  };
32
32
 
33
- export const detectTestCommand = (customCmd, cwd = process.cwd()) => {
34
- if (customCmd && customCmd.trim().length > 0) return customCmd.trim();
33
+ export const detectTestCommand = (customCmd, cwd = process.cwd(), options = {}) => {
34
+ const target = options.target ? String(options.target).trim() : null;
35
+ const filter = options.filter ? String(options.filter).trim() : null;
36
+
37
+ if (customCmd && customCmd.trim().length > 0) {
38
+ let cmd = customCmd.trim();
39
+ if (target && !cmd.includes(target)) cmd += ` ${target}`;
40
+ if (filter && !cmd.includes(filter)) cmd += ` -t "${filter}"`;
41
+ return cmd;
42
+ }
43
+
35
44
  const pkg = loadLocalPackageJson(cwd);
36
45
  const scripts = (pkg && pkg.scripts) || {};
46
+ const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
37
47
  const pm = resolvePackageManager(cwd);
38
48
 
49
+ const hasVitest = Boolean(
50
+ deps.vitest ||
51
+ (scripts.test && scripts.test.includes('vitest')) ||
52
+ fs.existsSync(path.join(cwd, 'vitest.config.ts')) ||
53
+ fs.existsSync(path.join(cwd, 'vitest.config.js'))
54
+ );
55
+
56
+ const hasNodeTest = Boolean(
57
+ scripts.test && (scripts.test.includes('node --test') || scripts.test.includes('node:test'))
58
+ );
59
+
60
+ if (target || filter) {
61
+ if (hasVitest) {
62
+ let vitestCmd = 'npx vitest run';
63
+ if (target) vitestCmd += ` ${target}`;
64
+ if (filter) vitestCmd += ` -t "${filter}"`;
65
+ return vitestCmd;
66
+ }
67
+
68
+ if (hasNodeTest || target?.endsWith('.spec.js') || target?.endsWith('.test.js')) {
69
+ let nodeCmd = 'node --test';
70
+ if (target) nodeCmd += ` ${target}`;
71
+ if (filter) nodeCmd += ` --test-name-pattern="${filter}"`;
72
+ return nodeCmd;
73
+ }
74
+
75
+ const baseTest = pm === 'yarn' ? 'yarn test' : `${pm} test`;
76
+ let args = '';
77
+ if (target) args += ` ${target}`;
78
+ if (filter) args += ` -t "${filter}"`;
79
+ return `${baseTest} --${args}`;
80
+ }
81
+
39
82
  const isLegitTest = scripts.test && !scripts.test.includes('no test specified');
40
83
  if (isLegitTest) return pm === 'yarn' ? 'yarn test' : `${pm} run test`;
41
84
 
42
- if (fs.existsSync(path.join(cwd, 'vitest.config.ts')) || fs.existsSync(path.join(cwd, 'vitest.config.js'))) {
85
+ if (hasVitest) {
43
86
  return 'npx vitest run';
44
87
  }
45
88