smart-context-mcp 1.2.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.
package/README.md CHANGED
@@ -62,7 +62,8 @@ Restart your AI client. Done.
62
62
  - ✅ Token savings: 85-90% on complex tasks
63
63
 
64
64
  Check actual usage:
65
- - `npm run report:metrics` - Tool-level savings
65
+ - **Real-time feedback** - See usage immediately (enable with `export DEVCTX_SHOW_USAGE=true`)
66
+ - `npm run report:metrics` - Tool-level savings + adoption analysis
66
67
  - `npm run report:workflows` - Workflow-level savings (requires `DEVCTX_WORKFLOW_TRACKING=true`)
67
68
 
68
69
  ## What it does
@@ -102,6 +103,134 @@ Installation generates rules that teach agents optimal workflows:
102
103
 
103
104
  Production usage: **14.5M tokens → 1.6M tokens** (89.87% reduction)
104
105
 
106
+ ## Verify It's Working
107
+
108
+ ### Real-Time Feedback (Auto-enabled for First 10 Calls)
109
+
110
+ Feedback is **automatically enabled** for your first 10 tool calls (onboarding mode), then auto-disables.
111
+
112
+ **To keep it enabled permanently:**
113
+ ```bash
114
+ export DEVCTX_SHOW_USAGE=true
115
+ ```
116
+
117
+ **To disable immediately:**
118
+ ```bash
119
+ export DEVCTX_SHOW_USAGE=false
120
+ ```
121
+
122
+ You'll see at the end of agent responses:
123
+
124
+ ```markdown
125
+ ---
126
+
127
+ 📊 **devctx usage this session:**
128
+ - **smart_read**: 3 calls | ~45.0K tokens saved (file1.js, file2.js, file3.js)
129
+ - **smart_search**: 1 call | ~12.0K tokens saved (query)
130
+
131
+ **Total saved:** ~57.0K tokens
132
+
133
+ *Onboarding mode: showing for 3 more tool calls. To keep: `export DEVCTX_SHOW_USAGE=true`*
134
+ ```
135
+
136
+ **Why this is useful:**
137
+ - ✅ Verify agent is following rules
138
+ - ✅ See token savings in real-time
139
+ - ✅ Debug adoption issues instantly
140
+ - ✅ Validate forcing prompts worked
141
+
142
+ ### Historical Metrics
143
+
144
+ ```bash
145
+ npm run report:metrics
146
+ ```
147
+
148
+ Shows adoption analysis + token savings over time.
149
+
150
+ ### Decision Explanations (Optional)
151
+
152
+ Understand **why** the agent chose devctx tools:
153
+
154
+ ```bash
155
+ export DEVCTX_EXPLAIN=true
156
+ ```
157
+
158
+ You'll see explanations like:
159
+
160
+ ```markdown
161
+ 🤖 **Decision explanations:**
162
+
163
+ **smart_read** (read server.js (outline mode))
164
+ - **Why:** File is large (2500 lines), outline mode extracts structure only
165
+ - **Instead of:** Read (full file)
166
+ - **Expected benefit:** ~45.0K tokens saved
167
+
168
+ **smart_search** (search "bug" (intent: debug))
169
+ - **Why:** Intent-aware search prioritizes relevant results
170
+ - **Expected benefit:** Better result ranking
171
+ ```
172
+
173
+ **When to use:**
174
+ - Learning how devctx works
175
+ - Debugging tool selection
176
+ - Understanding best practices
177
+
178
+ ### Missed Opportunities Detection (Optional)
179
+
180
+ Detect when devctx **should have been used but wasn't**:
181
+
182
+ ```bash
183
+ export DEVCTX_DETECT_MISSED=true
184
+ ```
185
+
186
+ You'll see warnings like:
187
+
188
+ ```markdown
189
+ ⚠️ **Missed devctx opportunities detected:**
190
+
191
+ **Session stats:**
192
+ - devctx operations: 2
193
+ - Estimated total: 25
194
+ - Adoption: 8%
195
+
196
+ 🟡 **low devctx adoption**
197
+ - **Issue:** Low adoption (8%). Target: >50%
198
+ - **Potential savings:** ~184.0K tokens
199
+ ```
200
+
201
+ **Detects:**
202
+ - No devctx usage in long sessions
203
+ - Low adoption (<30%)
204
+ - Usage dropped mid-session
205
+
206
+ **Combine all features:**
207
+
208
+ ```bash
209
+ export DEVCTX_SHOW_USAGE=true # See what's used
210
+ export DEVCTX_EXPLAIN=true # Understand why
211
+ export DEVCTX_DETECT_MISSED=true # Detect gaps
212
+ ```
213
+
214
+ ## MCP Prompts
215
+
216
+ The MCP server provides **prompts** for automatic forcing:
217
+
218
+ ```
219
+ /prompt use-devctx
220
+ ```
221
+
222
+ **Available prompts:**
223
+ - `use-devctx` - Ultra-short forcing prompt
224
+ - `devctx-workflow` - Complete workflow template
225
+ - `devctx-preflight` - Preflight checklist
226
+
227
+ **Benefits:**
228
+ - No manual typing
229
+ - Centrally managed
230
+ - No typos
231
+
232
+ See [MCP Prompts Documentation](../../docs/mcp-prompts.md).
233
+
105
234
  ## Core Tools
106
235
 
107
236
  ### smart_read
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smart-context-mcp",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "MCP server that reduces agent token usage by 90% with intelligent context compression, task checkpoint persistence, and workflow-aware agent guidance.",
5
5
  "author": "Francisco Caballero Portero <fcp1978@hotmail.com>",
6
6
  "type": "module",
@@ -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
+ };
@@ -0,0 +1,255 @@
1
+ /**
2
+ * Missed opportunities detector - identifies when devctx should have been used but wasn't
3
+ *
4
+ * Analyzes session metrics to detect patterns where devctx would have helped.
5
+ *
6
+ * Enable with environment variable: DEVCTX_DETECT_MISSED=true
7
+ *
8
+ * Detection heuristics (based on session metrics):
9
+ * - Low devctx adoption in complex sessions (many operations, few devctx calls)
10
+ * - Sessions with 0 devctx usage but high operation count
11
+ * - Inferred complexity vs actual devctx usage
12
+ *
13
+ * Note: We can't intercept native tool calls in real-time, so we analyze
14
+ * patterns from metrics after the fact.
15
+ */
16
+
17
+ const sessionActivity = {
18
+ devctxOperations: 0,
19
+ totalOperations: 0, // Estimated from devctx calls + time-based heuristic
20
+ lastDevctxCall: 0,
21
+ sessionStart: Date.now(),
22
+ enabled: false,
23
+ warnings: [],
24
+ };
25
+
26
+ const DEVCTX_TOOLS = new Set([
27
+ 'smart_read',
28
+ 'smart_search',
29
+ 'smart_context',
30
+ 'smart_shell',
31
+ 'smart_summary',
32
+ 'smart_turn',
33
+ 'smart_read_batch',
34
+ 'build_index',
35
+ ]);
36
+
37
+ /**
38
+ * Check if missed opportunity detection is enabled
39
+ */
40
+ export const isMissedDetectionEnabled = () => {
41
+ const envValue = process.env.DEVCTX_DETECT_MISSED?.toLowerCase();
42
+ const enabled = envValue === 'true' || envValue === '1' || envValue === 'yes';
43
+ sessionActivity.enabled = enabled;
44
+ return enabled;
45
+ };
46
+
47
+ /**
48
+ * Record devctx tool usage
49
+ */
50
+ export const recordDevctxOperation = () => {
51
+ if (!isMissedDetectionEnabled()) return;
52
+
53
+ sessionActivity.devctxOperations += 1;
54
+ sessionActivity.totalOperations += 1;
55
+ sessionActivity.lastDevctxCall = Date.now();
56
+ };
57
+
58
+ /**
59
+ * Estimate total operations based on time and activity
60
+ * Heuristic: If no devctx calls for >2 minutes, likely agent is using native tools
61
+ */
62
+ const estimateTotalOperations = () => {
63
+ const now = Date.now();
64
+ const sessionDuration = now - sessionActivity.sessionStart;
65
+ const timeSinceLastDevctx = now - sessionActivity.lastDevctxCall;
66
+
67
+ // If session is active (recent devctx calls), estimate conservatively
68
+ if (timeSinceLastDevctx < 2 * 60 * 1000) {
69
+ return sessionActivity.totalOperations;
70
+ }
71
+
72
+ // If long gap without devctx, estimate agent is using native tools
73
+ // Heuristic: ~1 operation per 10 seconds of activity
74
+ const estimatedNativeOps = Math.floor(timeSinceLastDevctx / 10000);
75
+ return sessionActivity.totalOperations + estimatedNativeOps;
76
+ };
77
+
78
+ /**
79
+ * Analyze session and detect missed opportunities
80
+ */
81
+ export const analyzeMissedOpportunities = () => {
82
+ if (!isMissedDetectionEnabled()) return null;
83
+
84
+ const now = Date.now();
85
+ const sessionDuration = now - sessionActivity.sessionStart;
86
+ const timeSinceLastDevctx = now - sessionActivity.lastDevctxCall;
87
+ const estimatedTotal = estimateTotalOperations();
88
+
89
+ const opportunities = [];
90
+
91
+ // Detection 1: Long session with no devctx usage
92
+ if (sessionDuration > 5 * 60 * 1000 && sessionActivity.devctxOperations === 0) {
93
+ opportunities.push({
94
+ type: 'no_devctx_usage',
95
+ severity: 'high',
96
+ reason: 'Session active for >5 minutes with 0 devctx calls. Agent may not be using devctx.',
97
+ suggestion: 'Use forcing prompt or check if MCP is active',
98
+ estimatedSavings: estimatedTotal * 10000, // Estimate ~10K tokens per operation
99
+ });
100
+ }
101
+
102
+ // Detection 2: Low devctx adoption in active session
103
+ const devctxRatio = estimatedTotal > 0 ? sessionActivity.devctxOperations / estimatedTotal : 0;
104
+ if (estimatedTotal >= 10 && devctxRatio < 0.3) {
105
+ opportunities.push({
106
+ type: 'low_devctx_adoption',
107
+ severity: 'medium',
108
+ reason: `Low devctx adoption: ${sessionActivity.devctxOperations}/${estimatedTotal} operations (${Math.round(devctxRatio * 100)}%). Target: >50%.`,
109
+ suggestion: 'Agent may be using native tools. Consider forcing prompt.',
110
+ estimatedSavings: (estimatedTotal - sessionActivity.devctxOperations) * 8000,
111
+ });
112
+ }
113
+
114
+ // Detection 3: Long gap without devctx (agent switched to native tools)
115
+ if (sessionActivity.devctxOperations > 0 && timeSinceLastDevctx > 3 * 60 * 1000) {
116
+ const minutesSince = Math.round(timeSinceLastDevctx / 60000);
117
+ opportunities.push({
118
+ type: 'devctx_usage_dropped',
119
+ severity: 'medium',
120
+ reason: `No devctx calls for ${minutesSince} minutes. Agent may have switched to native tools.`,
121
+ suggestion: 'Re-apply forcing prompt if task is still complex',
122
+ estimatedSavings: Math.floor(timeSinceLastDevctx / 10000) * 5000,
123
+ });
124
+ }
125
+
126
+ // Detection 4: Session too short to analyze
127
+ if (sessionDuration < 60 * 1000 && opportunities.length === 0) {
128
+ return {
129
+ opportunities: [],
130
+ message: 'Session too short to analyze (<1 minute)',
131
+ devctxOperations: sessionActivity.devctxOperations,
132
+ estimatedTotal,
133
+ };
134
+ }
135
+
136
+ return {
137
+ opportunities,
138
+ devctxOperations: sessionActivity.devctxOperations,
139
+ estimatedTotal,
140
+ devctxRatio: Math.round(devctxRatio * 100),
141
+ sessionDuration: Math.round(sessionDuration / 1000),
142
+ totalEstimatedSavings: opportunities.reduce((sum, opp) => sum + (opp.estimatedSavings || 0), 0),
143
+ };
144
+ };
145
+
146
+ /**
147
+ * Format missed opportunities as markdown
148
+ */
149
+ export const formatMissedOpportunities = () => {
150
+ if (!isMissedDetectionEnabled()) return '';
151
+
152
+ const analysis = analyzeMissedOpportunities();
153
+ if (!analysis) return '';
154
+
155
+ // Don't show if session is too short or no opportunities
156
+ if (analysis.message || analysis.opportunities.length === 0) {
157
+ return '';
158
+ }
159
+
160
+ const lines = [];
161
+ lines.push('');
162
+ lines.push('---');
163
+ lines.push('');
164
+ lines.push('⚠️ **Missed devctx opportunities detected:**');
165
+ lines.push('');
166
+
167
+ // Show session stats
168
+ lines.push(`**Session stats:**`);
169
+ lines.push(`- Duration: ${analysis.sessionDuration}s`);
170
+ lines.push(`- devctx operations: ${analysis.devctxOperations}`);
171
+ lines.push(`- Estimated total operations: ${analysis.estimatedTotal}`);
172
+ lines.push(`- devctx adoption: ${analysis.devctxRatio}%`);
173
+ lines.push('');
174
+
175
+ for (const opp of analysis.opportunities) {
176
+ const severityIcon = opp.severity === 'high' ? '🔴' : '🟡';
177
+ lines.push(`${severityIcon} **${opp.type.replace(/_/g, ' ')}**`);
178
+ lines.push(`- **Issue:** ${opp.reason}`);
179
+ lines.push(`- **Suggestion:** ${opp.suggestion}`);
180
+
181
+ if (opp.estimatedSavings) {
182
+ lines.push(`- **Potential savings:** ~${formatTokens(opp.estimatedSavings)}`);
183
+ }
184
+
185
+ lines.push('');
186
+ }
187
+
188
+ if (analysis.totalEstimatedSavings > 0) {
189
+ lines.push(`**Total potential savings:** ~${formatTokens(analysis.totalEstimatedSavings)}`);
190
+ lines.push('');
191
+ }
192
+
193
+ lines.push('**How to fix:**');
194
+ lines.push('1. Use forcing prompt: `Use devctx: smart_turn(start) → smart_context/smart_search → smart_read → smart_turn(end)`');
195
+ lines.push('2. Check if index is built: `ls .devctx/index.json`');
196
+ lines.push('3. Verify MCP is active in Cursor settings');
197
+ lines.push('');
198
+ lines.push('*To disable: `export DEVCTX_DETECT_MISSED=false`*');
199
+
200
+ return lines.join('\n');
201
+ };
202
+
203
+ /**
204
+ * Get session activity summary
205
+ */
206
+ export const getSessionActivity = () => {
207
+ return {
208
+ devctxOperations: sessionActivity.devctxOperations,
209
+ totalOperations: sessionActivity.totalOperations,
210
+ estimatedTotal: estimateTotalOperations(),
211
+ sessionDuration: Date.now() - sessionActivity.sessionStart,
212
+ timeSinceLastDevctx: Date.now() - sessionActivity.lastDevctxCall,
213
+ };
214
+ };
215
+
216
+ /**
217
+ * Reset session activity (for testing or manual reset)
218
+ */
219
+ export const resetSessionActivity = () => {
220
+ sessionActivity.devctxOperations = 0;
221
+ sessionActivity.totalOperations = 0;
222
+ sessionActivity.lastDevctxCall = 0;
223
+ sessionActivity.sessionStart = Date.now();
224
+ sessionActivity.warnings = [];
225
+ };
226
+
227
+ /**
228
+ * Testing helpers - expose internal state for test scenarios
229
+ */
230
+ export const __testing__ = {
231
+ setSessionStart: (timestamp) => {
232
+ sessionActivity.sessionStart = timestamp;
233
+ },
234
+ setLastDevctxCall: (timestamp) => {
235
+ sessionActivity.lastDevctxCall = timestamp;
236
+ },
237
+ setTotalOperations: (count) => {
238
+ sessionActivity.totalOperations = count;
239
+ },
240
+ getSessionActivity: () => sessionActivity,
241
+ };
242
+
243
+ /**
244
+ * Format token count for display
245
+ */
246
+ const formatTokens = (tokens) => {
247
+ if (tokens >= 1000000) {
248
+ return `${(tokens / 1000000).toFixed(1)}M tokens`;
249
+ }
250
+ if (tokens >= 1000) {
251
+ return `${(tokens / 1000).toFixed(1)}K tokens`;
252
+ }
253
+ return `${tokens} tokens`;
254
+ };
255
+
package/src/server.js CHANGED
@@ -49,6 +49,72 @@ export const createDevctxServer = () => {
49
49
  // Enable streaming progress notifications
50
50
  setServerForStreaming(server);
51
51
 
52
+ // Register prompts
53
+ server.prompt(
54
+ 'use-devctx',
55
+ 'Force the agent to use devctx tools for the current task. Use this prompt at the start of your message to ensure devctx is used instead of native tools.',
56
+ {},
57
+ async () => ({
58
+ messages: [
59
+ {
60
+ role: 'user',
61
+ content: {
62
+ type: 'text',
63
+ text: 'Use devctx: smart_turn(start) → smart_context/smart_search → smart_read → smart_turn(end)',
64
+ },
65
+ },
66
+ ],
67
+ })
68
+ );
69
+
70
+ server.prompt(
71
+ 'devctx-workflow',
72
+ 'Complete devctx workflow template with all recommended steps. Includes session start, context building, file reading, and session end.',
73
+ {},
74
+ async () => ({
75
+ messages: [
76
+ {
77
+ role: 'user',
78
+ content: {
79
+ type: 'text',
80
+ text: `Follow this devctx workflow:
81
+
82
+ 1. smart_turn(start) - Start session and recover previous context
83
+ 2. smart_context(task) - Build complete context for the task
84
+ 3. smart_search(query) - Search for specific patterns if needed
85
+ 4. smart_read(file) - Read files with appropriate mode (outline/signatures/symbol)
86
+ 5. smart_turn(end) - Save checkpoint for next session
87
+
88
+ Use devctx tools instead of native Read/Grep/Shell when possible.`,
89
+ },
90
+ },
91
+ ],
92
+ })
93
+ );
94
+
95
+ server.prompt(
96
+ 'devctx-preflight',
97
+ 'Preflight checklist before starting work. Ensures index is built and session is initialized.',
98
+ {},
99
+ async () => ({
100
+ messages: [
101
+ {
102
+ role: 'user',
103
+ content: {
104
+ type: 'text',
105
+ text: `Preflight checklist:
106
+
107
+ 1. build_index(incremental=true) - Build/update symbol index
108
+ 2. smart_turn(start) - Initialize session and recover context
109
+ 3. Proceed with your task using devctx tools
110
+
111
+ This ensures optimal performance and context recovery.`,
112
+ },
113
+ },
114
+ ],
115
+ })
116
+ );
117
+
52
118
  server.tool(
53
119
  'smart_read',
54
120
  'Read a file with token-efficient modes. outline/signatures: compact structure (~90% savings). range: specific line range with line numbers. symbol: extract function/class/method by name (string or array for batch). full: file content capped at 12k chars. maxTokens: token budget — auto-selects the most detailed mode that fits (full -> outline -> signatures -> truncated). context=true (symbol mode only): includes callers, tests, and referenced types from the dependency graph; returns graphCoverage (imports/tests: full|partial|none) so the agent knows how reliable the cross-file context is. Responses are cached in memory per session and invalidated by file mtime; cached=true when served from cache. Every response includes a unified confidence block: { parser, truncated, cached, graphCoverage? }. Supports JS/TS, Python, Go, Rust, Java, C#, Kotlin, PHP, Swift, shell, Terraform, Dockerfile, SQL, JSON, TOML, YAML.',
@@ -11,6 +11,9 @@ import { resolveSafePath } from '../utils/fs.js';
11
11
  import { countTokens } from '../tokenCounter.js';
12
12
  import { persistMetrics } from '../metrics.js';
13
13
  import { predictContextFiles, recordContextAccess } from '../context-patterns.js';
14
+ import { recordToolUsage } from '../usage-feedback.js';
15
+ import { recordDecision, DECISION_REASONS, EXPECTED_BENEFITS } from '../decision-explainer.js';
16
+ import { recordDevctxOperation } from '../missed-opportunities.js';
14
17
  import {
15
18
  getDetailedDiff,
16
19
  analyzeChangeImpact,
@@ -1219,15 +1222,44 @@ export const smartContext = async ({
1219
1222
  const contentItems = context.filter((item) => typeof item.content === 'string' && item.content.length > 0).length;
1220
1223
  const primaryItem = context.find((item) => item.role === 'primary');
1221
1224
 
1225
+ const savedTokens = Math.max(0, totalRawTokens - totalCompressedTokens);
1226
+
1222
1227
  await persistMetrics({
1223
1228
  tool: 'smart_context',
1224
1229
  target: `${root} :: ${task}`,
1225
1230
  rawTokens: totalRawTokens,
1226
1231
  compressedTokens: totalCompressedTokens,
1227
- savedTokens: Math.max(0, totalRawTokens - totalCompressedTokens),
1232
+ savedTokens,
1228
1233
  savingsPct,
1229
1234
  timestamp: new Date().toISOString(),
1230
1235
  });
1236
+
1237
+ // Record usage for feedback
1238
+ recordToolUsage({
1239
+ tool: 'smart_context',
1240
+ savedTokens,
1241
+ target: task,
1242
+ });
1243
+
1244
+ // Record devctx operation for missed opportunity detection
1245
+ recordDevctxOperation();
1246
+
1247
+ // Record decision explanation
1248
+ let reason = DECISION_REASONS.TASK_CONTEXT;
1249
+ if (diff) {
1250
+ reason = DECISION_REASONS.DIFF_ANALYSIS;
1251
+ } else if (context.some(c => c.role === 'caller' || c.role === 'test')) {
1252
+ reason = DECISION_REASONS.RELATED_FILES;
1253
+ }
1254
+
1255
+ recordDecision({
1256
+ tool: 'smart_context',
1257
+ action: `build context for "${task}"`,
1258
+ reason,
1259
+ alternative: 'Multiple smart_read + smart_search calls',
1260
+ expectedBenefit: `${EXPECTED_BENEFITS.TOKEN_SAVINGS(savedTokens)}, ${EXPECTED_BENEFITS.COMPLETE_CONTEXT}`,
1261
+ context: `${context.length} files, ${totalCompressedTokens} tokens (${savingsPct}% compression)`,
1262
+ });
1231
1263
 
1232
1264
  if (prefetch && context.length > 0) {
1233
1265
  try {
@@ -9,6 +9,9 @@ import { isDockerfile, readTextFile } from '../utils/fs.js';
9
9
  import { projectRoot } from '../utils/paths.js';
10
10
  import { truncate } from '../utils/text.js';
11
11
  import { countTokens } from '../tokenCounter.js';
12
+ import { recordToolUsage } from '../usage-feedback.js';
13
+ import { recordDecision, DECISION_REASONS, EXPECTED_BENEFITS } from '../decision-explainer.js';
14
+ import { recordDevctxOperation } from '../missed-opportunities.js';
12
15
 
13
16
  const execFile = promisify(execFileCb);
14
17
  import { summarizeGo, summarizeRust, summarizeJava, summarizeShell, summarizeTerraform, summarizeDockerfile, summarizeSql, extractGoSymbol, extractRustSymbol, extractJavaSymbol, summarizeCsharp, extractCsharpSymbol, summarizeKotlin, extractKotlinSymbol, summarizePhp, extractPhpSymbol, summarizeSwift, extractSwiftSymbol } from './smart-read/additional-languages.js';
@@ -453,6 +456,38 @@ export const smartRead = async ({ filePath, mode = 'outline', startLine, endLine
453
456
  });
454
457
 
455
458
  await persistMetrics(metrics);
459
+
460
+ // Record usage for feedback
461
+ recordToolUsage({
462
+ tool: 'smart_read',
463
+ savedTokens: metrics.savedTokens,
464
+ target: path.relative(projectRoot, fullPath),
465
+ });
466
+
467
+ // Record devctx operation for missed opportunity detection
468
+ recordDevctxOperation();
469
+
470
+ // Record decision explanation
471
+ const lineCount = content.split('\n').length;
472
+ let reason = DECISION_REASONS.LARGE_FILE;
473
+ let expectedBenefit = EXPECTED_BENEFITS.TOKEN_SAVINGS(metrics.savedTokens);
474
+
475
+ if (mode === 'symbol') {
476
+ reason = DECISION_REASONS.SYMBOL_EXTRACTION;
477
+ } else if (validBudget && effectiveMode !== mode) {
478
+ reason = DECISION_REASONS.TOKEN_BUDGET;
479
+ } else if (lineCount < 100) {
480
+ reason = `File is small (${lineCount} lines), but using ${effectiveMode} mode for consistency`;
481
+ }
482
+
483
+ recordDecision({
484
+ tool: 'smart_read',
485
+ action: `read ${path.relative(projectRoot, fullPath)} (${effectiveMode} mode)`,
486
+ reason,
487
+ alternative: 'Read (full file)',
488
+ expectedBenefit,
489
+ context: `${lineCount} lines, ${metrics.rawTokens} tokens → ${metrics.compressedTokens} tokens`,
490
+ });
456
491
 
457
492
  const confidence = { parser, truncated, cached: cacheHit && !contextResult };
458
493
  if (contextResult) confidence.graphCoverage = contextResult.graphCoverage;
@@ -8,6 +8,9 @@ import { loadIndex, queryIndex, queryRelated } from '../index.js';
8
8
  import { projectRoot } from '../utils/paths.js';
9
9
  import { isBinaryBuffer, isDockerfile, resolveSafePath } from '../utils/fs.js';
10
10
  import { truncate } from '../utils/text.js';
11
+ import { recordToolUsage } from '../usage-feedback.js';
12
+ import { recordDecision, DECISION_REASONS, EXPECTED_BENEFITS } from '../decision-explainer.js';
13
+ import { recordDevctxOperation } from '../missed-opportunities.js';
11
14
 
12
15
  const execFile = promisify(execFileCallback);
13
16
  const supportedGlobs = [
@@ -381,6 +384,34 @@ export const smartSearch = async ({ query, cwd = '.', intent, _testForceWalk = f
381
384
  });
382
385
 
383
386
  await persistMetrics(metrics);
387
+
388
+ // Record usage for feedback
389
+ recordToolUsage({
390
+ tool: 'smart_search',
391
+ savedTokens: metrics.savedTokens,
392
+ target: query,
393
+ });
394
+
395
+ // Record devctx operation for missed opportunity detection
396
+ recordDevctxOperation();
397
+
398
+ // Record decision explanation
399
+ let reason = DECISION_REASONS.MULTIPLE_FILES;
400
+ if (validIntent) {
401
+ reason = DECISION_REASONS.INTENT_AWARE;
402
+ }
403
+ if (indexHits && indexHits.size > 0) {
404
+ reason = DECISION_REASONS.INDEX_BOOST;
405
+ }
406
+
407
+ recordDecision({
408
+ tool: 'smart_search',
409
+ action: `search "${query}"${validIntent ? ` (intent: ${validIntent})` : ''}`,
410
+ reason,
411
+ alternative: 'Grep (unranked results)',
412
+ expectedBenefit: `${EXPECTED_BENEFITS.TOKEN_SAVINGS(metrics.savedTokens)}, ${EXPECTED_BENEFITS.BETTER_RANKING}`,
413
+ context: `${dedupedMatches.length} matches in ${groups.length} files, ranked by relevance`,
414
+ });
384
415
 
385
416
  let retrievalConfidence = 'high';
386
417
  if (provenance) {
@@ -4,6 +4,9 @@ import { rgPath } from '@vscode/ripgrep';
4
4
  import { buildMetrics, persistMetrics } from '../metrics.js';
5
5
  import { projectRoot } from '../utils/paths.js';
6
6
  import { pickRelevantLines, truncate, uniqueLines } from '../utils/text.js';
7
+ import { recordToolUsage } from '../usage-feedback.js';
8
+ import { recordDecision, DECISION_REASONS, EXPECTED_BENEFITS } from '../decision-explainer.js';
9
+ import { recordDevctxOperation } from '../missed-opportunities.js';
7
10
 
8
11
  const execFile = promisify(execFileCallback);
9
12
  const isShellDisabled = () => process.env.DEVCTX_SHELL_DISABLED === 'true';
@@ -221,6 +224,32 @@ export const smartShell = async ({ command }) => {
221
224
  });
222
225
 
223
226
  await persistMetrics(metrics);
227
+
228
+ // Record usage for feedback
229
+ recordToolUsage({
230
+ tool: 'smart_shell',
231
+ savedTokens: metrics.savedTokens,
232
+ target: command,
233
+ });
234
+
235
+ // Record devctx operation for missed opportunity detection
236
+ recordDevctxOperation();
237
+
238
+ // Record decision explanation
239
+ const outputLines = rawText.split('\n').length;
240
+ let reason = DECISION_REASONS.COMMAND_OUTPUT;
241
+ if (shouldPrioritizeRelevant && relevant) {
242
+ reason = DECISION_REASONS.RELEVANT_LINES;
243
+ }
244
+
245
+ recordDecision({
246
+ tool: 'smart_shell',
247
+ action: `execute "${command}"`,
248
+ reason,
249
+ alternative: 'Shell (uncompressed output)',
250
+ expectedBenefit: EXPECTED_BENEFITS.TOKEN_SAVINGS(metrics.savedTokens),
251
+ context: `${outputLines} lines → ${compressedText.split('\n').length} lines (relevant only)`,
252
+ });
224
253
 
225
254
  const result = {
226
255
  command,
@@ -1,6 +1,9 @@
1
1
  import { countTokens } from '../tokenCounter.js';
2
2
  import { persistMetrics } from '../metrics.js';
3
3
  import { enforceRepoSafety, getRepoSafety } from '../repo-safety.js';
4
+ import { recordToolUsage } from '../usage-feedback.js';
5
+ import { recordDecision, DECISION_REASONS, EXPECTED_BENEFITS } from '../decision-explainer.js';
6
+ import { recordDevctxOperation } from '../missed-opportunities.js';
4
7
  import {
5
8
  ACTIVE_SESSION_SCOPE,
6
9
  SQLITE_SCHEMA_VERSION,
@@ -1212,6 +1215,23 @@ export const smartSummary = async ({
1212
1215
  ...summaryMetrics,
1213
1216
  latencyMs: Date.now() - startTime,
1214
1217
  });
1218
+
1219
+ recordToolUsage({
1220
+ tool: 'smart_summary',
1221
+ savedTokens: summaryMetrics.savedTokens || 0,
1222
+ target: targetSessionId,
1223
+ });
1224
+
1225
+ recordDevctxOperation();
1226
+
1227
+ recordDecision({
1228
+ tool: 'smart_summary',
1229
+ action: `get checkpoint "${targetSessionId}"`,
1230
+ reason: DECISION_REASONS.RESUME,
1231
+ alternative: 'Start from scratch (lose context)',
1232
+ expectedBenefit: EXPECTED_BENEFITS.SESSION_RECOVERY,
1233
+ context: `Recovered ${compressed.goal ? 'goal' : 'state'}, ${compressed.status || 'unknown'} status`,
1234
+ });
1215
1235
  }
1216
1236
 
1217
1237
  return addRepoSafety({
@@ -1356,14 +1376,21 @@ export const smartSummary = async ({
1356
1376
  const { compressed, tokens, truncated, omitted, compressionLevel } = compressSummary(currentSession, maxTokens);
1357
1377
  const rawTokens = countTokens(JSON.stringify(currentSession));
1358
1378
 
1379
+ const metrics = buildSummaryMetrics(rawTokens, tokens);
1359
1380
  persistMetrics({
1360
1381
  tool: 'smart_summary',
1361
1382
  action,
1362
1383
  sessionId: targetSessionId,
1363
- ...buildSummaryMetrics(rawTokens, tokens),
1384
+ ...metrics,
1364
1385
  latencyMs: Date.now() - startTime,
1365
1386
  skipped: true,
1366
1387
  });
1388
+
1389
+ recordToolUsage({
1390
+ tool: 'smart_summary',
1391
+ savedTokens: metrics.savedTokens || 0,
1392
+ target: targetSessionId,
1393
+ });
1367
1394
 
1368
1395
  return addRepoSafety({
1369
1396
  action,
@@ -1386,15 +1413,24 @@ export const smartSummary = async ({
1386
1413
  const { compressed, tokens, truncated, omitted, compressionLevel } = compressSummary(currentSession, maxTokens);
1387
1414
  const rawTokens = countTokens(JSON.stringify(currentSession));
1388
1415
 
1416
+ const metrics2 = buildSummaryMetrics(rawTokens, tokens);
1389
1417
  persistMetrics({
1390
1418
  tool: 'smart_summary',
1391
1419
  action,
1392
1420
  sessionId: targetSessionId,
1393
- ...buildSummaryMetrics(rawTokens, tokens),
1421
+ ...metrics2,
1394
1422
  latencyMs: Date.now() - startTime,
1395
1423
  skipped: true,
1396
1424
  checkpointEvent: checkpointDecision.event,
1397
1425
  });
1426
+
1427
+ recordToolUsage({
1428
+ tool: 'smart_summary',
1429
+ savedTokens: metrics2.savedTokens || 0,
1430
+ target: targetSessionId,
1431
+ });
1432
+
1433
+ recordDevctxOperation();
1398
1434
 
1399
1435
  return addRepoSafety({
1400
1436
  action,
@@ -1455,6 +1491,14 @@ export const smartSummary = async ({
1455
1491
  latencyMs: Date.now() - startTime,
1456
1492
  ...(action === 'checkpoint' ? { checkpointEvent: checkpointDecision.event } : {}),
1457
1493
  });
1494
+
1495
+ recordToolUsage({
1496
+ tool: 'smart_summary',
1497
+ savedTokens: summaryMetrics.savedTokens || 0,
1498
+ target: targetSessionId,
1499
+ });
1500
+
1501
+ recordDevctxOperation();
1458
1502
 
1459
1503
  return addRepoSafety({
1460
1504
  action,
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Usage feedback system - tracks devctx tool usage in current session
3
+ * and provides visible feedback to users about what tools were used and tokens saved.
4
+ *
5
+ * Enable with environment variable: DEVCTX_SHOW_USAGE=true
6
+ *
7
+ * Auto-enabled for first 10 tool calls (onboarding mode), then auto-disables.
8
+ * User can explicitly enable/disable at any time.
9
+ */
10
+
11
+ const sessionUsage = {
12
+ tools: new Map(), // toolName -> { count, savedTokens }
13
+ totalSavedTokens: 0,
14
+ enabled: false,
15
+ totalToolCalls: 0,
16
+ onboardingMode: true,
17
+ ONBOARDING_THRESHOLD: 10, // Auto-disable after 10 tool calls
18
+ };
19
+
20
+ /**
21
+ * Check if usage feedback is enabled
22
+ *
23
+ * Priority:
24
+ * 1. Explicit env var (DEVCTX_SHOW_USAGE=true/false)
25
+ * 2. Onboarding mode (first 10 tool calls)
26
+ * 3. Default: disabled
27
+ */
28
+ export const isFeedbackEnabled = () => {
29
+ const envValue = process.env.DEVCTX_SHOW_USAGE?.toLowerCase();
30
+
31
+ // Explicit enable
32
+ if (envValue === 'true' || envValue === '1' || envValue === 'yes') {
33
+ sessionUsage.enabled = true;
34
+ sessionUsage.onboardingMode = false;
35
+ return true;
36
+ }
37
+
38
+ // Explicit disable
39
+ if (envValue === 'false' || envValue === '0' || envValue === 'no') {
40
+ sessionUsage.enabled = false;
41
+ sessionUsage.onboardingMode = false;
42
+ return false;
43
+ }
44
+
45
+ // Onboarding mode: auto-enable for first N tool calls
46
+ if (sessionUsage.onboardingMode && sessionUsage.totalToolCalls < sessionUsage.ONBOARDING_THRESHOLD) {
47
+ sessionUsage.enabled = true;
48
+ return true;
49
+ }
50
+
51
+ // After onboarding threshold, auto-disable
52
+ if (sessionUsage.onboardingMode && sessionUsage.totalToolCalls >= sessionUsage.ONBOARDING_THRESHOLD) {
53
+ sessionUsage.enabled = false;
54
+ sessionUsage.onboardingMode = false;
55
+ }
56
+
57
+ return sessionUsage.enabled;
58
+ };
59
+
60
+ /**
61
+ * Record tool usage for feedback
62
+ */
63
+ export const recordToolUsage = ({ tool, savedTokens = 0, target = null }) => {
64
+ // Increment total tool calls (for onboarding mode)
65
+ sessionUsage.totalToolCalls += 1;
66
+
67
+ if (!isFeedbackEnabled()) return;
68
+
69
+ const current = sessionUsage.tools.get(tool) || { count: 0, savedTokens: 0, targets: [] };
70
+ current.count += 1;
71
+ current.savedTokens += savedTokens;
72
+ if (target) current.targets.push(target);
73
+
74
+ sessionUsage.tools.set(tool, current);
75
+ sessionUsage.totalSavedTokens += savedTokens;
76
+ };
77
+
78
+ /**
79
+ * Get current session usage stats
80
+ */
81
+ export const getSessionUsage = () => {
82
+ return {
83
+ tools: Array.from(sessionUsage.tools.entries()).map(([tool, stats]) => ({
84
+ tool,
85
+ count: stats.count,
86
+ savedTokens: stats.savedTokens,
87
+ targets: stats.targets.slice(-3), // Last 3 targets only
88
+ })),
89
+ totalSavedTokens: sessionUsage.totalSavedTokens,
90
+ };
91
+ };
92
+
93
+ /**
94
+ * Format usage feedback as markdown
95
+ */
96
+ export const formatUsageFeedback = () => {
97
+ if (!isFeedbackEnabled()) return '';
98
+
99
+ const usage = getSessionUsage();
100
+ if (usage.tools.length === 0) return '';
101
+
102
+ const lines = [];
103
+ lines.push('');
104
+ lines.push('---');
105
+ lines.push('');
106
+ lines.push('📊 **devctx usage this session:**');
107
+
108
+ // Sort by count descending
109
+ const sorted = usage.tools.sort((a, b) => b.count - a.count);
110
+
111
+ for (const { tool, count, savedTokens, targets } of sorted) {
112
+ const countStr = count === 1 ? '1 call' : `${count} calls`;
113
+ const tokensStr = savedTokens > 0 ? ` | ~${formatTokens(savedTokens)} saved` : '';
114
+
115
+ if (targets.length > 0) {
116
+ const targetsPreview = targets.length === 1
117
+ ? ` (${truncateTarget(targets[0])})`
118
+ : ` (${targets.length} files)`;
119
+ lines.push(`- **${tool}**: ${countStr}${tokensStr}${targetsPreview}`);
120
+ } else {
121
+ lines.push(`- **${tool}**: ${countStr}${tokensStr}`);
122
+ }
123
+ }
124
+
125
+ if (usage.totalSavedTokens > 0) {
126
+ lines.push('');
127
+ lines.push(`**Total saved:** ~${formatTokens(usage.totalSavedTokens)}`);
128
+ }
129
+
130
+ lines.push('');
131
+
132
+ // Show onboarding message if in onboarding mode
133
+ if (sessionUsage.onboardingMode && sessionUsage.totalToolCalls < sessionUsage.ONBOARDING_THRESHOLD) {
134
+ const remaining = sessionUsage.ONBOARDING_THRESHOLD - sessionUsage.totalToolCalls;
135
+ lines.push(`*Onboarding mode: showing for ${remaining} more tool calls. To keep: \`export DEVCTX_SHOW_USAGE=true\`*`);
136
+ } else {
137
+ lines.push('*To disable this message: `export DEVCTX_SHOW_USAGE=false`*');
138
+ }
139
+
140
+ return lines.join('\n');
141
+ };
142
+
143
+ /**
144
+ * Reset session usage (for testing or manual reset)
145
+ */
146
+ export const resetSessionUsage = () => {
147
+ sessionUsage.tools.clear();
148
+ sessionUsage.totalSavedTokens = 0;
149
+ sessionUsage.totalToolCalls = 0;
150
+ sessionUsage.onboardingMode = true;
151
+ };
152
+
153
+ /**
154
+ * Format token count for display
155
+ */
156
+ const formatTokens = (tokens) => {
157
+ if (tokens >= 1000000) {
158
+ return `${(tokens / 1000000).toFixed(1)}M tokens`;
159
+ }
160
+ if (tokens >= 1000) {
161
+ return `${(tokens / 1000).toFixed(1)}K tokens`;
162
+ }
163
+ return `${tokens} tokens`;
164
+ };
165
+
166
+ /**
167
+ * Truncate target path for display
168
+ */
169
+ const truncateTarget = (target) => {
170
+ if (!target) return '';
171
+ if (target.length <= 40) return target;
172
+
173
+ // Try to show filename
174
+ const parts = target.split('/');
175
+ const filename = parts[parts.length - 1];
176
+ if (filename.length <= 40) return `.../${filename}`;
177
+
178
+ return target.slice(0, 37) + '...';
179
+ };