smart-context-mcp 1.2.0 → 1.3.1

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** - Enabled by default (disable with `export DEVCTX_SHOW_USAGE=false`)
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,129 @@ 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 (Enabled by Default)
109
+
110
+ Feedback is **enabled by default** and shows after every devctx tool call.
111
+
112
+ **To disable:**
113
+ ```bash
114
+ export DEVCTX_SHOW_USAGE=false
115
+ ```
116
+
117
+ You'll see at the end of agent responses:
118
+
119
+ ```markdown
120
+ ---
121
+
122
+ 📊 **devctx usage this session:**
123
+ - **smart_read**: 3 calls | ~45.0K tokens saved (file1.js, file2.js, file3.js)
124
+ - **smart_search**: 1 call | ~12.0K tokens saved (query)
125
+
126
+ **Total saved:** ~57.0K tokens
127
+
128
+ *To disable this message: `export DEVCTX_SHOW_USAGE=false`*
129
+ ```
130
+
131
+ **Why this is useful:**
132
+ - ✅ Verify agent is following rules
133
+ - ✅ See token savings in real-time
134
+ - ✅ Debug adoption issues instantly
135
+ - ✅ Validate forcing prompts worked
136
+
137
+ ### Historical Metrics
138
+
139
+ ```bash
140
+ npm run report:metrics
141
+ ```
142
+
143
+ Shows adoption analysis + token savings over time.
144
+
145
+ ### Decision Explanations (Optional)
146
+
147
+ Understand **why** the agent chose devctx tools:
148
+
149
+ ```bash
150
+ export DEVCTX_EXPLAIN=true
151
+ ```
152
+
153
+ You'll see explanations like:
154
+
155
+ ```markdown
156
+ 🤖 **Decision explanations:**
157
+
158
+ **smart_read** (read server.js (outline mode))
159
+ - **Why:** File is large (2500 lines), outline mode extracts structure only
160
+ - **Instead of:** Read (full file)
161
+ - **Expected benefit:** ~45.0K tokens saved
162
+
163
+ **smart_search** (search "bug" (intent: debug))
164
+ - **Why:** Intent-aware search prioritizes relevant results
165
+ - **Expected benefit:** Better result ranking
166
+ ```
167
+
168
+ **When to use:**
169
+ - Learning how devctx works
170
+ - Debugging tool selection
171
+ - Understanding best practices
172
+
173
+ ### Missed Opportunities Detection (Optional)
174
+
175
+ Detect when devctx **should have been used but wasn't**:
176
+
177
+ ```bash
178
+ export DEVCTX_DETECT_MISSED=true
179
+ ```
180
+
181
+ You'll see warnings like:
182
+
183
+ ```markdown
184
+ ⚠️ **Missed devctx opportunities detected:**
185
+
186
+ **Session stats:**
187
+ - devctx operations: 2
188
+ - Estimated total: 25
189
+ - Adoption: 8%
190
+
191
+ 🟡 **low devctx adoption**
192
+ - **Issue:** Low adoption (8%). Target: >50%
193
+ - **Potential savings:** ~184.0K tokens
194
+ ```
195
+
196
+ **Detects:**
197
+ - No devctx usage in long sessions
198
+ - Low adoption (<30%)
199
+ - Usage dropped mid-session
200
+
201
+ **All features enabled by default.** To disable:
202
+
203
+ ```bash
204
+ export DEVCTX_SHOW_USAGE=false
205
+ export DEVCTX_EXPLAIN=false
206
+ export DEVCTX_DETECT_MISSED=false
207
+ ```
208
+
209
+ ## MCP Prompts
210
+
211
+ The MCP server provides **prompts** for automatic forcing:
212
+
213
+ ```
214
+ /prompt use-devctx
215
+ ```
216
+
217
+ **Available prompts:**
218
+ - `use-devctx` - Ultra-short forcing prompt
219
+ - `devctx-workflow` - Complete workflow template
220
+ - `devctx-preflight` - Preflight checklist
221
+
222
+ **Benefits:**
223
+ - No manual typing
224
+ - Centrally managed
225
+ - No typos
226
+
227
+ See [MCP Prompts Documentation](../../docs/mcp-prompts.md).
228
+
105
229
  ## Core Tools
106
230
 
107
231
  ### 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.1",
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,189 @@
1
+ /**
2
+ * Decision explainer - tracks and explains why devctx tools were used or not used
3
+ *
4
+ * ENABLED BY DEFAULT - disable with: DEVCTX_EXPLAIN=false
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: true, // Changed: enabled by default
15
+ };
16
+
17
+ /**
18
+ * Check if explanations are enabled
19
+ */
20
+ /**
21
+ * Check if decision explanations are enabled
22
+ *
23
+ * Priority:
24
+ * 1. Explicit env var (DEVCTX_EXPLAIN=true/false)
25
+ * 2. Default: ENABLED (changed from disabled)
26
+ */
27
+ export const isExplainEnabled = () => {
28
+ const envValue = process.env.DEVCTX_EXPLAIN?.toLowerCase();
29
+
30
+ // Explicit enable
31
+ if (envValue === 'true' || envValue === '1' || envValue === 'yes') {
32
+ sessionDecisions.enabled = true;
33
+ return true;
34
+ }
35
+
36
+ // Explicit disable
37
+ if (envValue === 'false' || envValue === '0' || envValue === 'no') {
38
+ sessionDecisions.enabled = false;
39
+ return false;
40
+ }
41
+
42
+ // Default: ENABLED (changed)
43
+ sessionDecisions.enabled = true;
44
+ return true;
45
+ };
46
+
47
+ /**
48
+ * Record a decision with explanation
49
+ */
50
+ export const recordDecision = ({
51
+ tool,
52
+ action,
53
+ reason,
54
+ alternative = null,
55
+ expectedBenefit = null,
56
+ context = null,
57
+ }) => {
58
+ if (!isExplainEnabled()) return;
59
+
60
+ sessionDecisions.decisions.push({
61
+ tool,
62
+ action,
63
+ reason,
64
+ alternative,
65
+ expectedBenefit,
66
+ context,
67
+ timestamp: new Date().toISOString(),
68
+ });
69
+ };
70
+
71
+ /**
72
+ * Get all decisions for current session
73
+ */
74
+ export const getSessionDecisions = () => {
75
+ return sessionDecisions.decisions;
76
+ };
77
+
78
+ /**
79
+ * Format decisions as markdown for display
80
+ */
81
+ export const formatDecisionExplanations = () => {
82
+ if (!isExplainEnabled()) return '';
83
+
84
+ const decisions = getSessionDecisions();
85
+ if (decisions.length === 0) return '';
86
+
87
+ const lines = [];
88
+ lines.push('');
89
+ lines.push('---');
90
+ lines.push('');
91
+ lines.push('🤖 **Decision explanations:**');
92
+ lines.push('');
93
+
94
+ for (const decision of decisions) {
95
+ lines.push(`**${decision.tool}** (${decision.action})`);
96
+ lines.push(`- **Why:** ${decision.reason}`);
97
+
98
+ if (decision.alternative) {
99
+ lines.push(`- **Instead of:** ${decision.alternative}`);
100
+ }
101
+
102
+ if (decision.expectedBenefit) {
103
+ lines.push(`- **Expected benefit:** ${decision.expectedBenefit}`);
104
+ }
105
+
106
+ if (decision.context) {
107
+ lines.push(`- **Context:** ${decision.context}`);
108
+ }
109
+
110
+ lines.push('');
111
+ }
112
+
113
+ lines.push('*To disable: `export DEVCTX_EXPLAIN=false`*');
114
+
115
+ return lines.join('\n');
116
+ };
117
+
118
+ /**
119
+ * Reset session decisions (for testing or manual reset)
120
+ */
121
+ export const resetSessionDecisions = () => {
122
+ sessionDecisions.decisions = [];
123
+ sessionDecisions.enabled = true; // Reset to default (enabled)
124
+ };
125
+
126
+ /**
127
+ * Common decision reasons (for consistency)
128
+ */
129
+ export const DECISION_REASONS = {
130
+ // smart_read reasons
131
+ LARGE_FILE: 'File is large (>500 lines), outline mode extracts structure only',
132
+ SYMBOL_EXTRACTION: 'Extracting specific symbol, smart_read can locate and extract it efficiently',
133
+ TOKEN_BUDGET: 'Token budget constraint, cascading to more compressed mode',
134
+ MULTIPLE_SYMBOLS: 'Reading multiple symbols, smart_read can batch them',
135
+
136
+ // smart_search reasons
137
+ MULTIPLE_FILES: 'Query spans 50+ files, smart_search ranks by relevance',
138
+ INTENT_AWARE: 'Intent-aware search prioritizes relevant results (debug/implementation/tests)',
139
+ INDEX_BOOST: 'Symbol index available, boosting relevant matches',
140
+ PATTERN_SEARCH: 'Complex pattern search, smart_search handles regex efficiently',
141
+
142
+ // smart_context reasons
143
+ TASK_CONTEXT: 'Building complete context for task, smart_context orchestrates multiple reads',
144
+ RELATED_FILES: 'Need related files (callers, tests, types), smart_context finds them',
145
+ ONE_CALL: 'Single call to get all context, more efficient than multiple reads',
146
+ DIFF_ANALYSIS: 'Analyzing git diff, smart_context expands changed symbols',
147
+
148
+ // smart_shell reasons
149
+ COMMAND_OUTPUT: 'Command output needs compression (git log, npm test, etc.)',
150
+ RELEVANT_LINES: 'Extracting relevant lines from command output',
151
+ SAFE_EXECUTION: 'Using allowlist-validated command execution',
152
+
153
+ // smart_summary reasons
154
+ CHECKPOINT: 'Saving task checkpoint for session recovery',
155
+ RESUME: 'Recovering previous task context',
156
+ PERSISTENCE: 'Maintaining task state across agent restarts',
157
+
158
+ // Native tool reasons
159
+ SIMPLE_TASK: 'Task is simple, native tool is more direct',
160
+ ALREADY_CACHED: 'Content already in context, no need for compression',
161
+ SINGLE_LINE: 'Reading single line, native Read is sufficient',
162
+ SMALL_FILE: 'File is small (<100 lines), compression not needed',
163
+ NO_INDEX: 'No symbol index available, native search is equivalent',
164
+ };
165
+
166
+ /**
167
+ * Common expected benefits (for consistency)
168
+ */
169
+ export const EXPECTED_BENEFITS = {
170
+ TOKEN_SAVINGS: (tokens) => `~${formatTokens(tokens)} saved`,
171
+ FASTER_RESPONSE: 'Faster response due to less data to process',
172
+ BETTER_RANKING: 'Better result ranking, relevant items first',
173
+ COMPLETE_CONTEXT: 'Complete context in single call',
174
+ SESSION_RECOVERY: 'Can recover task state if agent restarts',
175
+ FOCUSED_RESULTS: 'Focused on relevant code only',
176
+ };
177
+
178
+ /**
179
+ * Format token count for display
180
+ */
181
+ const formatTokens = (tokens) => {
182
+ if (tokens >= 1000000) {
183
+ return `${(tokens / 1000000).toFixed(1)}M tokens`;
184
+ }
185
+ if (tokens >= 1000) {
186
+ return `${(tokens / 1000).toFixed(1)}K tokens`;
187
+ }
188
+ return `${tokens} tokens`;
189
+ };
@@ -0,0 +1,283 @@
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
+ * ENABLED BY DEFAULT - disable with: DEVCTX_DETECT_MISSED=false
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: true, // Changed: enabled by default
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
+ * Priority:
41
+ * 1. Explicit env var (DEVCTX_DETECT_MISSED=true/false)
42
+ * 2. Default: ENABLED (changed from disabled)
43
+ */
44
+ export const isMissedDetectionEnabled = () => {
45
+ const envValue = process.env.DEVCTX_DETECT_MISSED?.toLowerCase();
46
+
47
+ // Explicit enable
48
+ if (envValue === 'true' || envValue === '1' || envValue === 'yes') {
49
+ sessionActivity.enabled = true;
50
+ return true;
51
+ }
52
+
53
+ // Explicit disable
54
+ if (envValue === 'false' || envValue === '0' || envValue === 'no') {
55
+ sessionActivity.enabled = false;
56
+ return false;
57
+ }
58
+
59
+ // Default: ENABLED (changed)
60
+ sessionActivity.enabled = true;
61
+ return true;
62
+ };
63
+
64
+ /**
65
+ * Record devctx tool usage
66
+ */
67
+ export const recordDevctxOperation = () => {
68
+ if (!isMissedDetectionEnabled()) return;
69
+
70
+ sessionActivity.devctxOperations += 1;
71
+ sessionActivity.totalOperations += 1;
72
+ sessionActivity.lastDevctxCall = Date.now();
73
+ };
74
+
75
+ /**
76
+ * Estimate total operations based on time and activity
77
+ * Heuristic: If no devctx calls for >2 minutes, likely agent is using native tools
78
+ */
79
+ const estimateTotalOperations = () => {
80
+ const now = Date.now();
81
+ const sessionDuration = now - sessionActivity.sessionStart;
82
+ const timeSinceLastDevctx = now - sessionActivity.lastDevctxCall;
83
+
84
+ // If session is active (recent devctx calls), estimate conservatively
85
+ if (timeSinceLastDevctx < 2 * 60 * 1000) {
86
+ return sessionActivity.totalOperations;
87
+ }
88
+
89
+ // If long gap without devctx, estimate agent is using native tools
90
+ // Heuristic: ~1 operation per 10 seconds of activity
91
+ const estimatedNativeOps = Math.floor(timeSinceLastDevctx / 10000);
92
+ return sessionActivity.totalOperations + estimatedNativeOps;
93
+ };
94
+
95
+ /**
96
+ * Analyze session and detect missed opportunities
97
+ */
98
+ export const analyzeMissedOpportunities = () => {
99
+ if (!isMissedDetectionEnabled()) return null;
100
+
101
+ const now = Date.now();
102
+ const sessionDuration = now - sessionActivity.sessionStart;
103
+ const timeSinceLastDevctx = now - sessionActivity.lastDevctxCall;
104
+ const estimatedTotal = estimateTotalOperations();
105
+
106
+ const opportunities = [];
107
+
108
+ // Detection 1: Long session with no devctx usage
109
+ if (sessionDuration > 5 * 60 * 1000 && sessionActivity.devctxOperations === 0) {
110
+ opportunities.push({
111
+ type: 'no_devctx_usage',
112
+ severity: 'high',
113
+ reason: 'Session active for >5 minutes with 0 devctx calls. Agent may not be using devctx.',
114
+ suggestion: 'Use forcing prompt or check if MCP is active',
115
+ estimatedSavings: estimatedTotal * 10000, // Estimate ~10K tokens per operation
116
+ });
117
+ }
118
+
119
+ // Detection 2: Low devctx adoption in active session
120
+ const devctxRatio = estimatedTotal > 0 ? sessionActivity.devctxOperations / estimatedTotal : 0;
121
+ if (estimatedTotal >= 10 && devctxRatio < 0.3) {
122
+ opportunities.push({
123
+ type: 'low_devctx_adoption',
124
+ severity: 'medium',
125
+ reason: `Low devctx adoption: ${sessionActivity.devctxOperations}/${estimatedTotal} operations (${Math.round(devctxRatio * 100)}%). Target: >50%.`,
126
+ suggestion: 'Agent may be using native tools. Consider forcing prompt.',
127
+ estimatedSavings: (estimatedTotal - sessionActivity.devctxOperations) * 8000,
128
+ });
129
+ }
130
+
131
+ // Detection 3: Long gap without devctx (agent switched to native tools)
132
+ if (sessionActivity.devctxOperations > 0 && timeSinceLastDevctx > 3 * 60 * 1000) {
133
+ const minutesSince = Math.round(timeSinceLastDevctx / 60000);
134
+ opportunities.push({
135
+ type: 'devctx_usage_dropped',
136
+ severity: 'medium',
137
+ reason: `No devctx calls for ${minutesSince} minutes. Agent may have switched to native tools.`,
138
+ suggestion: 'Re-apply forcing prompt if task is still complex',
139
+ estimatedSavings: Math.floor(timeSinceLastDevctx / 10000) * 5000,
140
+ });
141
+ }
142
+
143
+ // Detection 4: Session too short to analyze
144
+ if (sessionDuration < 60 * 1000 && opportunities.length === 0) {
145
+ return {
146
+ opportunities: [],
147
+ message: 'Session too short to analyze (<1 minute)',
148
+ devctxOperations: sessionActivity.devctxOperations,
149
+ estimatedTotal,
150
+ };
151
+ }
152
+
153
+ return {
154
+ opportunities,
155
+ devctxOperations: sessionActivity.devctxOperations,
156
+ estimatedTotal,
157
+ devctxRatio: Math.round(devctxRatio * 100),
158
+ sessionDuration: Math.round(sessionDuration / 1000),
159
+ totalEstimatedSavings: opportunities.reduce((sum, opp) => sum + (opp.estimatedSavings || 0), 0),
160
+ };
161
+ };
162
+
163
+ /**
164
+ * Format missed opportunities as markdown
165
+ */
166
+ export const formatMissedOpportunities = () => {
167
+ if (!isMissedDetectionEnabled()) return '';
168
+
169
+ const analysis = analyzeMissedOpportunities();
170
+ if (!analysis) return '';
171
+
172
+ // Show session too short message (but don't show full warning)
173
+ if (analysis.message) {
174
+ return '';
175
+ }
176
+
177
+ // If no opportunities but session is active, show positive feedback
178
+ if (analysis.opportunities.length === 0 && analysis.devctxOperations > 0) {
179
+ return `\n\n✅ **devctx adoption: ${analysis.devctxRatio}%** (${analysis.devctxOperations}/${analysis.estimatedTotal} operations)\n`;
180
+ }
181
+
182
+ // No opportunities and no devctx usage - don't show yet (wait for longer session)
183
+ if (analysis.opportunities.length === 0) {
184
+ return '';
185
+ }
186
+
187
+ const lines = [];
188
+ lines.push('');
189
+ lines.push('---');
190
+ lines.push('');
191
+ lines.push('⚠️ **Missed devctx opportunities detected:**');
192
+ lines.push('');
193
+
194
+ // Show session stats
195
+ lines.push(`**Session stats:**`);
196
+ lines.push(`- Duration: ${analysis.sessionDuration}s`);
197
+ lines.push(`- devctx operations: ${analysis.devctxOperations}`);
198
+ lines.push(`- Estimated total operations: ${analysis.estimatedTotal}`);
199
+ lines.push(`- devctx adoption: ${analysis.devctxRatio}%`);
200
+ lines.push('');
201
+
202
+ for (const opp of analysis.opportunities) {
203
+ const severityIcon = opp.severity === 'high' ? '🔴' : '🟡';
204
+ lines.push(`${severityIcon} **${opp.type.replace(/_/g, ' ')}**`);
205
+ lines.push(`- **Issue:** ${opp.reason}`);
206
+ lines.push(`- **Suggestion:** ${opp.suggestion}`);
207
+
208
+ if (opp.estimatedSavings) {
209
+ lines.push(`- **Potential savings:** ~${formatTokens(opp.estimatedSavings)}`);
210
+ }
211
+
212
+ lines.push('');
213
+ }
214
+
215
+ if (analysis.totalEstimatedSavings > 0) {
216
+ lines.push(`**Total potential savings:** ~${formatTokens(analysis.totalEstimatedSavings)}`);
217
+ lines.push('');
218
+ }
219
+
220
+ lines.push('**How to fix:**');
221
+ lines.push('1. Use forcing prompt: `Use devctx: smart_turn(start) → smart_context/smart_search → smart_read → smart_turn(end)`');
222
+ lines.push('2. Check if index is built: `ls .devctx/index.json`');
223
+ lines.push('3. Verify MCP is active in Cursor settings');
224
+ lines.push('');
225
+ lines.push('*To disable: `export DEVCTX_DETECT_MISSED=false`*');
226
+
227
+ return lines.join('\n');
228
+ };
229
+
230
+ /**
231
+ * Get session activity summary
232
+ */
233
+ export const getSessionActivity = () => {
234
+ return {
235
+ devctxOperations: sessionActivity.devctxOperations,
236
+ totalOperations: sessionActivity.totalOperations,
237
+ estimatedTotal: estimateTotalOperations(),
238
+ sessionDuration: Date.now() - sessionActivity.sessionStart,
239
+ timeSinceLastDevctx: Date.now() - sessionActivity.lastDevctxCall,
240
+ };
241
+ };
242
+
243
+ /**
244
+ * Reset session activity (for testing or manual reset)
245
+ */
246
+ export const resetSessionActivity = () => {
247
+ sessionActivity.devctxOperations = 0;
248
+ sessionActivity.totalOperations = 0;
249
+ sessionActivity.lastDevctxCall = 0;
250
+ sessionActivity.sessionStart = Date.now();
251
+ sessionActivity.warnings = [];
252
+ sessionActivity.enabled = true; // Reset to default (enabled)
253
+ };
254
+
255
+ /**
256
+ * Testing helpers - expose internal state for test scenarios
257
+ */
258
+ export const __testing__ = {
259
+ setSessionStart: (timestamp) => {
260
+ sessionActivity.sessionStart = timestamp;
261
+ },
262
+ setLastDevctxCall: (timestamp) => {
263
+ sessionActivity.lastDevctxCall = timestamp;
264
+ },
265
+ setTotalOperations: (count) => {
266
+ sessionActivity.totalOperations = count;
267
+ },
268
+ getSessionActivity: () => sessionActivity,
269
+ };
270
+
271
+ /**
272
+ * Format token count for display
273
+ */
274
+ const formatTokens = (tokens) => {
275
+ if (tokens >= 1000000) {
276
+ return `${(tokens / 1000000).toFixed(1)}M tokens`;
277
+ }
278
+ if (tokens >= 1000) {
279
+ return `${(tokens / 1000).toFixed(1)}K tokens`;
280
+ }
281
+ return `${tokens} tokens`;
282
+ };
283
+
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,157 @@
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
+ * ENABLED BY DEFAULT - disable with: DEVCTX_SHOW_USAGE=false
6
+ *
7
+ * Shows feedback after every devctx tool call to ensure visibility.
8
+ * User can explicitly disable if they find it too verbose.
9
+ */
10
+
11
+ const sessionUsage = {
12
+ tools: new Map(), // toolName -> { count, savedTokens }
13
+ totalSavedTokens: 0,
14
+ enabled: true, // Changed: enabled by default
15
+ totalToolCalls: 0,
16
+ };
17
+
18
+ /**
19
+ * Check if usage feedback is enabled
20
+ *
21
+ * Priority:
22
+ * 1. Explicit env var (DEVCTX_SHOW_USAGE=true/false)
23
+ * 2. Default: ENABLED (changed from disabled)
24
+ */
25
+ export const isFeedbackEnabled = () => {
26
+ const envValue = process.env.DEVCTX_SHOW_USAGE?.toLowerCase();
27
+
28
+ // Explicit enable
29
+ if (envValue === 'true' || envValue === '1' || envValue === 'yes') {
30
+ sessionUsage.enabled = true;
31
+ return true;
32
+ }
33
+
34
+ // Explicit disable
35
+ if (envValue === 'false' || envValue === '0' || envValue === 'no') {
36
+ sessionUsage.enabled = false;
37
+ return false;
38
+ }
39
+
40
+ // Default: ENABLED (changed)
41
+ sessionUsage.enabled = true;
42
+ return true;
43
+ };
44
+
45
+ /**
46
+ * Record tool usage for feedback
47
+ */
48
+ export const recordToolUsage = ({ tool, savedTokens = 0, target = null }) => {
49
+ // Increment total tool calls (for onboarding mode)
50
+ sessionUsage.totalToolCalls += 1;
51
+
52
+ if (!isFeedbackEnabled()) return;
53
+
54
+ const current = sessionUsage.tools.get(tool) || { count: 0, savedTokens: 0, targets: [] };
55
+ current.count += 1;
56
+ current.savedTokens += savedTokens;
57
+ if (target) current.targets.push(target);
58
+
59
+ sessionUsage.tools.set(tool, current);
60
+ sessionUsage.totalSavedTokens += savedTokens;
61
+ };
62
+
63
+ /**
64
+ * Get current session usage stats
65
+ */
66
+ export const getSessionUsage = () => {
67
+ return {
68
+ tools: Array.from(sessionUsage.tools.entries()).map(([tool, stats]) => ({
69
+ tool,
70
+ count: stats.count,
71
+ savedTokens: stats.savedTokens,
72
+ targets: stats.targets.slice(-3), // Last 3 targets only
73
+ })),
74
+ totalSavedTokens: sessionUsage.totalSavedTokens,
75
+ };
76
+ };
77
+
78
+ /**
79
+ * Format usage feedback as markdown
80
+ */
81
+ export const formatUsageFeedback = () => {
82
+ if (!isFeedbackEnabled()) return '';
83
+
84
+ const usage = getSessionUsage();
85
+ if (usage.tools.length === 0) return '';
86
+
87
+ const lines = [];
88
+ lines.push('');
89
+ lines.push('---');
90
+ lines.push('');
91
+ lines.push('📊 **devctx usage this session:**');
92
+
93
+ // Sort by count descending
94
+ const sorted = usage.tools.sort((a, b) => b.count - a.count);
95
+
96
+ for (const { tool, count, savedTokens, targets } of sorted) {
97
+ const countStr = count === 1 ? '1 call' : `${count} calls`;
98
+ const tokensStr = savedTokens > 0 ? ` | ~${formatTokens(savedTokens)} saved` : '';
99
+
100
+ if (targets.length > 0) {
101
+ const targetsPreview = targets.length === 1
102
+ ? ` (${truncateTarget(targets[0])})`
103
+ : ` (${targets.length} files)`;
104
+ lines.push(`- **${tool}**: ${countStr}${tokensStr}${targetsPreview}`);
105
+ } else {
106
+ lines.push(`- **${tool}**: ${countStr}${tokensStr}`);
107
+ }
108
+ }
109
+
110
+ if (usage.totalSavedTokens > 0) {
111
+ lines.push('');
112
+ lines.push(`**Total saved:** ~${formatTokens(usage.totalSavedTokens)}`);
113
+ }
114
+
115
+ lines.push('');
116
+ lines.push('*To disable this message: `export DEVCTX_SHOW_USAGE=false`*');
117
+
118
+ return lines.join('\n');
119
+ };
120
+
121
+ /**
122
+ * Reset session usage (for testing or manual reset)
123
+ */
124
+ export const resetSessionUsage = () => {
125
+ sessionUsage.tools.clear();
126
+ sessionUsage.totalSavedTokens = 0;
127
+ sessionUsage.totalToolCalls = 0;
128
+ sessionUsage.enabled = true; // Reset to default (enabled)
129
+ };
130
+
131
+ /**
132
+ * Format token count for display
133
+ */
134
+ const formatTokens = (tokens) => {
135
+ if (tokens >= 1000000) {
136
+ return `${(tokens / 1000000).toFixed(1)}M tokens`;
137
+ }
138
+ if (tokens >= 1000) {
139
+ return `${(tokens / 1000).toFixed(1)}K tokens`;
140
+ }
141
+ return `${tokens} tokens`;
142
+ };
143
+
144
+ /**
145
+ * Truncate target path for display
146
+ */
147
+ const truncateTarget = (target) => {
148
+ if (!target) return '';
149
+ if (target.length <= 40) return target;
150
+
151
+ // Try to show filename
152
+ const parts = target.split('/');
153
+ const filename = parts[parts.length - 1];
154
+ if (filename.length <= 40) return `.../${filename}`;
155
+
156
+ return target.slice(0, 37) + '...';
157
+ };