smart-context-mcp 1.1.0 → 1.3.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.
@@ -0,0 +1,255 @@
1
+ #!/usr/bin/env node
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ // Dynamic import to handle SQLite availability
6
+ let workflowTracker;
7
+ try {
8
+ workflowTracker = await import('../src/workflow-tracker.js');
9
+ } catch {
10
+ workflowTracker = await import('../src/workflow-tracker-stub.js');
11
+ }
12
+
13
+ const { getWorkflowMetrics, getWorkflowSummaryByType, WORKFLOW_DEFINITIONS } = workflowTracker;
14
+
15
+ const parseArgs = (argv) => {
16
+ const options = {
17
+ type: null,
18
+ sessionId: null,
19
+ limit: 10,
20
+ json: false,
21
+ summary: false,
22
+ };
23
+
24
+ for (let index = 0; index < argv.length; index += 1) {
25
+ const token = argv[index];
26
+
27
+ if (token === '--type') {
28
+ options.type = argv[index + 1];
29
+ index += 1;
30
+ continue;
31
+ }
32
+
33
+ if (token === '--session') {
34
+ options.sessionId = argv[index + 1];
35
+ index += 1;
36
+ continue;
37
+ }
38
+
39
+ if (token === '--limit') {
40
+ options.limit = parseInt(argv[index + 1], 10);
41
+ index += 1;
42
+ continue;
43
+ }
44
+
45
+ if (token === '--json') {
46
+ options.json = true;
47
+ continue;
48
+ }
49
+
50
+ if (token === '--summary') {
51
+ options.summary = true;
52
+ continue;
53
+ }
54
+
55
+ if (token === '--help' || token === '-h') {
56
+ console.log(`
57
+ Usage: report-workflow-metrics [options]
58
+
59
+ Options:
60
+ --type <type> Filter by workflow type (debugging, code-review, refactoring, testing, architecture)
61
+ --session <id> Filter by session ID
62
+ --limit <n> Limit number of workflows (default: 10)
63
+ --summary Show summary by workflow type
64
+ --json Output as JSON
65
+ --help, -h Show this help
66
+
67
+ Examples:
68
+ # Show summary by workflow type
69
+ npm run report:workflows -- --summary
70
+
71
+ # Show last 10 debugging workflows
72
+ npm run report:workflows -- --type debugging
73
+
74
+ # Show workflows for specific session
75
+ npm run report:workflows -- --session abc123
76
+
77
+ # Output as JSON
78
+ npm run report:workflows -- --json
79
+ `);
80
+ process.exit(0);
81
+ }
82
+
83
+ throw new Error(`Unknown argument: ${token}`);
84
+ }
85
+
86
+ return options;
87
+ };
88
+
89
+ const formatNumber = (value) => new Intl.NumberFormat('en-US').format(value);
90
+
91
+ const formatDuration = (ms) => {
92
+ if (ms < 1000) return `${ms}ms`;
93
+ if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
94
+ if (ms < 3600000) return `${(ms / 60000).toFixed(1)}m`;
95
+ return `${(ms / 3600000).toFixed(1)}h`;
96
+ };
97
+
98
+ const printSummary = (summary) => {
99
+ console.log('');
100
+ console.log('Workflow Metrics Summary');
101
+ console.log('═'.repeat(120));
102
+ console.log('');
103
+
104
+ if (!Array.isArray(summary) || summary.length === 0) {
105
+ console.log('No completed workflows found.');
106
+ console.log('');
107
+ console.log('Workflows are tracked when:');
108
+ console.log(' 1. Agent calls smart_turn(start) with a task description');
109
+ console.log(' 2. Task matches a workflow pattern (debugging, review, refactor, testing, architecture)');
110
+ console.log(' 3. Agent calls smart_turn(end) to complete the workflow');
111
+ console.log('');
112
+ return;
113
+ }
114
+
115
+ const totalWorkflows = summary.reduce((sum, s) => sum + s.count, 0);
116
+ const totalRaw = summary.reduce((sum, s) => sum + s.total_raw_tokens, 0);
117
+ const totalCompressed = summary.reduce((sum, s) => sum + s.total_compressed_tokens, 0);
118
+ const totalSaved = summary.reduce((sum, s) => sum + s.total_saved_tokens, 0);
119
+ const totalBaseline = summary.reduce((sum, s) => sum + s.total_baseline_tokens, 0);
120
+
121
+ console.log(`Total Workflows: ${formatNumber(totalWorkflows)}`);
122
+ console.log(`Total Raw Tokens: ${formatNumber(totalRaw)}`);
123
+ console.log(`Total Compressed Tokens: ${formatNumber(totalCompressed)}`);
124
+ console.log(`Total Saved Tokens: ${formatNumber(totalSaved)} (${((totalSaved / totalRaw) * 100).toFixed(2)}%)`);
125
+ console.log(`Total Baseline Tokens: ${formatNumber(totalBaseline)}`);
126
+ console.log(`Savings vs Baseline: ${formatNumber(totalBaseline - totalCompressed)} (${(((totalBaseline - totalCompressed) / totalBaseline) * 100).toFixed(2)}%)`);
127
+ console.log('');
128
+ console.log('By Workflow Type:');
129
+ console.log('─'.repeat(120));
130
+ console.log(
131
+ 'Type'.padEnd(20) +
132
+ 'Count'.padStart(8) +
133
+ 'Avg Steps'.padStart(12) +
134
+ 'Avg Duration'.padStart(15) +
135
+ 'Avg Savings'.padStart(15) +
136
+ 'vs Baseline'.padStart(15),
137
+ );
138
+ console.log('─'.repeat(120));
139
+
140
+ for (const s of summary) {
141
+ const def = WORKFLOW_DEFINITIONS[s.workflow_type];
142
+ const name = def ? def.name : s.workflow_type;
143
+
144
+ console.log(
145
+ name.padEnd(20) +
146
+ formatNumber(s.count).padStart(8) +
147
+ s.avgStepsCount.toString().padStart(12) +
148
+ formatDuration(s.avgDurationMs).padStart(15) +
149
+ `${s.avgSavingsPct}%`.padStart(15) +
150
+ `${s.avgVsBaselinePct}%`.padStart(15),
151
+ );
152
+ }
153
+
154
+ console.log('─'.repeat(120));
155
+ console.log('');
156
+
157
+ // Show detailed breakdown for each workflow type
158
+ console.log('Detailed Breakdown:');
159
+ console.log('');
160
+
161
+ for (const s of summary) {
162
+ const def = WORKFLOW_DEFINITIONS[s.workflow_type];
163
+ const name = def ? def.name : s.workflow_type;
164
+
165
+ console.log(`${name}:`);
166
+ console.log(` Workflows: ${formatNumber(s.count)}`);
167
+ console.log(` Avg Steps: ${s.avgStepsCount}`);
168
+ console.log(` Avg Duration: ${formatDuration(s.avgDurationMs)}`);
169
+ console.log(` Total Raw Tokens: ${formatNumber(s.total_raw_tokens)}`);
170
+ console.log(` Total Compressed Tokens: ${formatNumber(s.total_compressed_tokens)}`);
171
+ console.log(` Total Saved Tokens: ${formatNumber(s.total_saved_tokens)} (${s.avgSavingsPct}%)`);
172
+ console.log(` Baseline Tokens: ${formatNumber(s.total_baseline_tokens)}`);
173
+ console.log(` Savings vs Baseline: ${formatNumber(s.total_baseline_tokens - s.total_compressed_tokens)} (${s.avgVsBaselinePct}%)`);
174
+ console.log('');
175
+ }
176
+ };
177
+
178
+ const printWorkflows = (workflows) => {
179
+ console.log('');
180
+ console.log('Recent Workflows');
181
+ console.log('═'.repeat(120));
182
+ console.log('');
183
+
184
+ if (workflows.length === 0) {
185
+ console.log('No workflows found.');
186
+ console.log('');
187
+ return;
188
+ }
189
+
190
+ for (const w of workflows) {
191
+ const def = WORKFLOW_DEFINITIONS[w.workflow_type];
192
+ const name = def ? def.name : w.workflow_type;
193
+ const status = w.end_time ? '✓ Completed' : '⏳ In Progress';
194
+
195
+ console.log(`${name} (${status})`);
196
+ console.log(` Workflow ID: ${w.workflow_id}`);
197
+ console.log(` Session ID: ${w.session_id || 'N/A'}`);
198
+ console.log(` Started: ${new Date(w.start_time).toLocaleString()}`);
199
+
200
+ if (w.end_time) {
201
+ console.log(` Ended: ${new Date(w.end_time).toLocaleString()}`);
202
+ console.log(` Duration: ${formatDuration(w.duration_ms)}`);
203
+ console.log(` Steps: ${w.steps_count}`);
204
+ console.log(` Tools Used: ${w.toolsUsed.join(', ')}`);
205
+ console.log(` Raw Tokens: ${formatNumber(w.raw_tokens)}`);
206
+ console.log(` Compressed Tokens: ${formatNumber(w.compressed_tokens)}`);
207
+ console.log(` Saved Tokens: ${formatNumber(w.saved_tokens)} (${w.savings_pct}%)`);
208
+ console.log(` Baseline Tokens: ${formatNumber(w.baseline_tokens)}`);
209
+ console.log(` Savings vs Baseline: ${formatNumber(w.baseline_tokens - w.compressed_tokens)} (${w.vs_baseline_pct}%)`);
210
+ }
211
+
212
+ console.log('');
213
+ }
214
+ };
215
+
216
+ const main = async () => {
217
+ try {
218
+ const options = parseArgs(process.argv.slice(2));
219
+
220
+ if (options.summary) {
221
+ const summary = getWorkflowSummaryByType();
222
+
223
+ if (options.json) {
224
+ console.log(JSON.stringify(summary, null, 2));
225
+ return;
226
+ }
227
+
228
+ printSummary(summary);
229
+ return;
230
+ }
231
+
232
+ const workflows = getWorkflowMetrics({
233
+ workflowType: options.type,
234
+ sessionId: options.sessionId,
235
+ limit: options.limit,
236
+ completed: true,
237
+ });
238
+
239
+ if (options.json) {
240
+ console.log(JSON.stringify(workflows, null, 2));
241
+ return;
242
+ }
243
+
244
+ printWorkflows(workflows);
245
+ } catch (error) {
246
+ console.error('Error:', error.message);
247
+ process.exit(1);
248
+ }
249
+ };
250
+
251
+ const isDirectExecution = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
252
+
253
+ if (isDirectExecution) {
254
+ main();
255
+ }
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Adoption analytics - measures how often agents use devctx tools in practice
3
+ *
4
+ * Limitations:
5
+ * - Complexity is inferred from operation count, not actual task complexity
6
+ * - Can't detect feedback shown (requires agent cooperation)
7
+ * - Can't detect forcing prompts (requires prompt analysis)
8
+ * - Can only measure when devctx IS used, not when it's ignored
9
+ */
10
+
11
+ const DEVCTX_TOOLS = new Set([
12
+ 'smart_read',
13
+ 'smart_search',
14
+ 'smart_context',
15
+ 'smart_shell',
16
+ 'smart_summary',
17
+ 'smart_turn',
18
+ 'smart_read_batch',
19
+ 'smart_metrics',
20
+ 'build_index',
21
+ 'warm_cache',
22
+ 'git_blame',
23
+ 'cross_project',
24
+ ]);
25
+
26
+ const inferComplexity = (opCount, fileCount) => {
27
+ if (opCount <= 2 && fileCount <= 1) return 'trivial';
28
+ if (opCount <= 5 && fileCount <= 3) return 'simple';
29
+ if (opCount <= 15 && fileCount <= 10) return 'moderate';
30
+ return 'complex';
31
+ };
32
+
33
+ const groupBySession = (entries) => {
34
+ const sessions = new Map();
35
+
36
+ for (const entry of entries) {
37
+ const sessionId = entry.sessionId || entry.session_id || 'unknown';
38
+
39
+ if (!sessions.has(sessionId)) {
40
+ sessions.set(sessionId, {
41
+ sessionId,
42
+ operations: [],
43
+ devctxTools: new Set(),
44
+ nativeTools: new Set(),
45
+ filesAccessed: new Set(),
46
+ totalRawTokens: 0,
47
+ totalCompressedTokens: 0,
48
+ totalSavedTokens: 0,
49
+ });
50
+ }
51
+
52
+ const session = sessions.get(sessionId);
53
+ session.operations.push(entry);
54
+
55
+ const tool = entry.tool;
56
+ if (DEVCTX_TOOLS.has(tool)) {
57
+ session.devctxTools.add(tool);
58
+ } else if (tool) {
59
+ session.nativeTools.add(tool);
60
+ }
61
+
62
+ if (entry.target) {
63
+ session.filesAccessed.add(entry.target);
64
+ }
65
+
66
+ session.totalRawTokens += Number(entry.rawTokens || entry.raw_tokens || 0);
67
+ session.totalCompressedTokens += Number(entry.compressedTokens || entry.compressed_tokens || 0);
68
+ session.totalSavedTokens += Number(entry.savedTokens || entry.saved_tokens || 0);
69
+ }
70
+
71
+ return Array.from(sessions.values());
72
+ };
73
+
74
+ export const analyzeAdoption = (entries) => {
75
+ const sessions = groupBySession(entries);
76
+
77
+ const sessionsWithDevctx = sessions.filter(s => s.devctxTools.size > 0);
78
+ const sessionsWithoutDevctx = sessions.filter(s => s.devctxTools.size === 0 && s.operations.length > 0);
79
+
80
+ const byComplexity = {
81
+ trivial: { total: 0, withDevctx: 0 },
82
+ simple: { total: 0, withDevctx: 0 },
83
+ moderate: { total: 0, withDevctx: 0 },
84
+ complex: { total: 0, withDevctx: 0 },
85
+ };
86
+
87
+ for (const session of sessions) {
88
+ const complexity = inferComplexity(session.operations.length, session.filesAccessed.size);
89
+ byComplexity[complexity].total += 1;
90
+ if (session.devctxTools.size > 0) {
91
+ byComplexity[complexity].withDevctx += 1;
92
+ }
93
+ }
94
+
95
+ const nonTrivialSessions = sessions.filter(s => {
96
+ const complexity = inferComplexity(s.operations.length, s.filesAccessed.size);
97
+ return complexity !== 'trivial';
98
+ });
99
+
100
+ const nonTrivialWithDevctx = nonTrivialSessions.filter(s => s.devctxTools.size > 0);
101
+
102
+ const toolUsageCount = {};
103
+ for (const session of sessionsWithDevctx) {
104
+ for (const tool of session.devctxTools) {
105
+ toolUsageCount[tool] = (toolUsageCount[tool] || 0) + 1;
106
+ }
107
+ }
108
+
109
+ return {
110
+ totalSessions: sessions.length,
111
+ sessionsWithDevctx: sessionsWithDevctx.length,
112
+ sessionsWithoutDevctx: sessionsWithoutDevctx.length,
113
+ adoptionRate: sessions.length > 0
114
+ ? Number(((sessionsWithDevctx.length / sessions.length) * 100).toFixed(1))
115
+ : 0,
116
+
117
+ nonTrivial: {
118
+ total: nonTrivialSessions.length,
119
+ withDevctx: nonTrivialWithDevctx.length,
120
+ adoptionRate: nonTrivialSessions.length > 0
121
+ ? Number(((nonTrivialWithDevctx.length / nonTrivialSessions.length) * 100).toFixed(1))
122
+ : 0,
123
+ },
124
+
125
+ byComplexity: Object.fromEntries(
126
+ Object.entries(byComplexity).map(([level, stats]) => [
127
+ level,
128
+ {
129
+ ...stats,
130
+ adoptionRate: stats.total > 0
131
+ ? Number(((stats.withDevctx / stats.total) * 100).toFixed(1))
132
+ : 0,
133
+ },
134
+ ])
135
+ ),
136
+
137
+ toolUsageCount,
138
+
139
+ avgToolsPerSession: sessionsWithDevctx.length > 0
140
+ ? Number((sessionsWithDevctx.reduce((sum, s) => sum + s.devctxTools.size, 0) / sessionsWithDevctx.length).toFixed(1))
141
+ : 0,
142
+
143
+ avgTokenSavingsWhenUsed: sessionsWithDevctx.length > 0
144
+ ? Number((sessionsWithDevctx.reduce((sum, s) => sum + s.totalSavedTokens, 0) / sessionsWithDevctx.length).toFixed(0))
145
+ : 0,
146
+ };
147
+ };
148
+
149
+ export const formatAdoptionReport = (stats) => {
150
+ const lines = [];
151
+
152
+ lines.push('');
153
+ lines.push('Adoption Analysis (Inferred from Tool Usage)');
154
+ lines.push('');
155
+ lines.push(`Total sessions: ${stats.totalSessions}`);
156
+ lines.push(`Sessions with devctx: ${stats.sessionsWithDevctx} (${stats.adoptionRate}%)`);
157
+ lines.push(`Sessions without: ${stats.sessionsWithoutDevctx} (${100 - stats.adoptionRate}%)`);
158
+ lines.push('');
159
+
160
+ lines.push('Non-Trivial Tasks Only:');
161
+ lines.push(`Total: ${stats.nonTrivial.total}`);
162
+ lines.push(`With devctx: ${stats.nonTrivial.withDevctx} (${stats.nonTrivial.adoptionRate}%)`);
163
+ lines.push(`Without devctx: ${stats.nonTrivial.total - stats.nonTrivial.withDevctx} (${100 - stats.nonTrivial.adoptionRate}%)`);
164
+ lines.push('');
165
+
166
+ lines.push('By Inferred Complexity:');
167
+ for (const [level, data] of Object.entries(stats.byComplexity)) {
168
+ if (data.total === 0) continue;
169
+ lines.push(`- ${level.padEnd(10)} ${data.withDevctx}/${data.total} (${data.adoptionRate}%)`);
170
+ }
171
+ lines.push('');
172
+
173
+ if (stats.sessionsWithDevctx > 0) {
174
+ lines.push('When devctx IS used:');
175
+ lines.push(`Avg tools/session: ${stats.avgToolsPerSession}`);
176
+ lines.push(`Avg token savings: ${stats.avgTokenSavingsWhenUsed.toLocaleString()} tokens`);
177
+ lines.push('');
178
+ }
179
+
180
+ lines.push('Top Tools Used:');
181
+ const sortedTools = Object.entries(stats.toolUsageCount)
182
+ .sort((a, b) => b[1] - a[1])
183
+ .slice(0, 5);
184
+ for (const [tool, count] of sortedTools) {
185
+ lines.push(`- ${tool.padEnd(20)} ${count} sessions`);
186
+ }
187
+ lines.push('');
188
+
189
+ lines.push('Limitations:');
190
+ lines.push('- Complexity inferred from operation count (not actual task complexity)');
191
+ lines.push('- Can only measure when devctx IS used (tool calls visible)');
192
+ lines.push('- Cannot measure feedback shown or forcing prompts (requires agent cooperation)');
193
+ lines.push('- Sessions without devctx may be simple tasks (not adoption failures)');
194
+ lines.push('');
195
+
196
+ return lines.join('\n');
197
+ };
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Decision explainer - tracks and explains why devctx tools were used or not used
3
+ *
4
+ * Enable with environment variable: DEVCTX_EXPLAIN=true
5
+ *
6
+ * Provides transparency into agent decision-making:
7
+ * - Why was smart_read used instead of Read?
8
+ * - Why was smart_search chosen?
9
+ * - What are the expected benefits?
10
+ */
11
+
12
+ const sessionDecisions = {
13
+ decisions: [],
14
+ enabled: false,
15
+ };
16
+
17
+ /**
18
+ * Check if explanations are enabled
19
+ */
20
+ export const isExplainEnabled = () => {
21
+ const envValue = process.env.DEVCTX_EXPLAIN?.toLowerCase();
22
+ const enabled = envValue === 'true' || envValue === '1' || envValue === 'yes';
23
+ sessionDecisions.enabled = enabled;
24
+ return enabled;
25
+ };
26
+
27
+ /**
28
+ * Record a decision with explanation
29
+ */
30
+ export const recordDecision = ({
31
+ tool,
32
+ action,
33
+ reason,
34
+ alternative = null,
35
+ expectedBenefit = null,
36
+ context = null,
37
+ }) => {
38
+ if (!isExplainEnabled()) return;
39
+
40
+ sessionDecisions.decisions.push({
41
+ tool,
42
+ action,
43
+ reason,
44
+ alternative,
45
+ expectedBenefit,
46
+ context,
47
+ timestamp: new Date().toISOString(),
48
+ });
49
+ };
50
+
51
+ /**
52
+ * Get all decisions for current session
53
+ */
54
+ export const getSessionDecisions = () => {
55
+ return sessionDecisions.decisions;
56
+ };
57
+
58
+ /**
59
+ * Format decisions as markdown for display
60
+ */
61
+ export const formatDecisionExplanations = () => {
62
+ if (!isExplainEnabled()) return '';
63
+
64
+ const decisions = getSessionDecisions();
65
+ if (decisions.length === 0) return '';
66
+
67
+ const lines = [];
68
+ lines.push('');
69
+ lines.push('---');
70
+ lines.push('');
71
+ lines.push('🤖 **Decision explanations:**');
72
+ lines.push('');
73
+
74
+ for (const decision of decisions) {
75
+ lines.push(`**${decision.tool}** (${decision.action})`);
76
+ lines.push(`- **Why:** ${decision.reason}`);
77
+
78
+ if (decision.alternative) {
79
+ lines.push(`- **Instead of:** ${decision.alternative}`);
80
+ }
81
+
82
+ if (decision.expectedBenefit) {
83
+ lines.push(`- **Expected benefit:** ${decision.expectedBenefit}`);
84
+ }
85
+
86
+ if (decision.context) {
87
+ lines.push(`- **Context:** ${decision.context}`);
88
+ }
89
+
90
+ lines.push('');
91
+ }
92
+
93
+ lines.push('*To disable: `export DEVCTX_EXPLAIN=false`*');
94
+
95
+ return lines.join('\n');
96
+ };
97
+
98
+ /**
99
+ * Reset session decisions (for testing or manual reset)
100
+ */
101
+ export const resetSessionDecisions = () => {
102
+ sessionDecisions.decisions = [];
103
+ };
104
+
105
+ /**
106
+ * Common decision reasons (for consistency)
107
+ */
108
+ export const DECISION_REASONS = {
109
+ // smart_read reasons
110
+ LARGE_FILE: 'File is large (>500 lines), outline mode extracts structure only',
111
+ SYMBOL_EXTRACTION: 'Extracting specific symbol, smart_read can locate and extract it efficiently',
112
+ TOKEN_BUDGET: 'Token budget constraint, cascading to more compressed mode',
113
+ MULTIPLE_SYMBOLS: 'Reading multiple symbols, smart_read can batch them',
114
+
115
+ // smart_search reasons
116
+ MULTIPLE_FILES: 'Query spans 50+ files, smart_search ranks by relevance',
117
+ INTENT_AWARE: 'Intent-aware search prioritizes relevant results (debug/implementation/tests)',
118
+ INDEX_BOOST: 'Symbol index available, boosting relevant matches',
119
+ PATTERN_SEARCH: 'Complex pattern search, smart_search handles regex efficiently',
120
+
121
+ // smart_context reasons
122
+ TASK_CONTEXT: 'Building complete context for task, smart_context orchestrates multiple reads',
123
+ RELATED_FILES: 'Need related files (callers, tests, types), smart_context finds them',
124
+ ONE_CALL: 'Single call to get all context, more efficient than multiple reads',
125
+ DIFF_ANALYSIS: 'Analyzing git diff, smart_context expands changed symbols',
126
+
127
+ // smart_shell reasons
128
+ COMMAND_OUTPUT: 'Command output needs compression (git log, npm test, etc.)',
129
+ RELEVANT_LINES: 'Extracting relevant lines from command output',
130
+ SAFE_EXECUTION: 'Using allowlist-validated command execution',
131
+
132
+ // smart_summary reasons
133
+ CHECKPOINT: 'Saving task checkpoint for session recovery',
134
+ RESUME: 'Recovering previous task context',
135
+ PERSISTENCE: 'Maintaining task state across agent restarts',
136
+
137
+ // Native tool reasons
138
+ SIMPLE_TASK: 'Task is simple, native tool is more direct',
139
+ ALREADY_CACHED: 'Content already in context, no need for compression',
140
+ SINGLE_LINE: 'Reading single line, native Read is sufficient',
141
+ SMALL_FILE: 'File is small (<100 lines), compression not needed',
142
+ NO_INDEX: 'No symbol index available, native search is equivalent',
143
+ };
144
+
145
+ /**
146
+ * Common expected benefits (for consistency)
147
+ */
148
+ export const EXPECTED_BENEFITS = {
149
+ TOKEN_SAVINGS: (tokens) => `~${formatTokens(tokens)} saved`,
150
+ FASTER_RESPONSE: 'Faster response due to less data to process',
151
+ BETTER_RANKING: 'Better result ranking, relevant items first',
152
+ COMPLETE_CONTEXT: 'Complete context in single call',
153
+ SESSION_RECOVERY: 'Can recover task state if agent restarts',
154
+ FOCUSED_RESULTS: 'Focused on relevant code only',
155
+ };
156
+
157
+ /**
158
+ * Format token count for display
159
+ */
160
+ const formatTokens = (tokens) => {
161
+ if (tokens >= 1000000) {
162
+ return `${(tokens / 1000000).toFixed(1)}M tokens`;
163
+ }
164
+ if (tokens >= 1000) {
165
+ return `${(tokens / 1000).toFixed(1)}K tokens`;
166
+ }
167
+ return `${tokens} tokens`;
168
+ };