smart-context-mcp 1.3.0 → 1.4.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
@@ -42,6 +42,62 @@ npx smart-context-init --target .
42
42
  ```
43
43
  Restart your AI client. Done.
44
44
 
45
+ ---
46
+
47
+ ## šŸš€ How to Invoke the MCP
48
+
49
+ The MCP doesn't intercept prompts automatically. **You need to tell the agent to use it.**
50
+
51
+ ### Option 1: Use MCP Prompts (Easiest)
52
+
53
+ In Cursor, type in the chat:
54
+
55
+ ```
56
+ /prompt use-devctx
57
+
58
+ [Your task here]
59
+ ```
60
+
61
+ **Available prompts:**
62
+ - `/prompt use-devctx` - Force devctx tools for current task
63
+ - `/prompt devctx-workflow` - Full workflow (start → context → work → end)
64
+ - `/prompt devctx-preflight` - Preflight only (build_index + smart_turn start)
65
+
66
+ ### Option 2: Explicit Instruction
67
+
68
+ Just tell the agent directly:
69
+
70
+ ```
71
+ Use smart_turn(start) to recover context, then [your task]
72
+ ```
73
+
74
+ Or:
75
+
76
+ ```
77
+ Use the MCP to review this code
78
+ ```
79
+
80
+ ### Option 3: Automatic (via Rules)
81
+
82
+ The agent *should* use devctx automatically for complex tasks because:
83
+ - āœ… `.cursorrules` is active in Cursor
84
+ - āœ… `CLAUDE.md` is active in Claude Desktop (if you created it)
85
+ - āœ… `AGENTS.md` is active in other clients (if you created it)
86
+
87
+ **But it's not guaranteed** - the agent decides based on task complexity.
88
+
89
+ ### ⚔ Quick Reference
90
+
91
+ | Scenario | Command |
92
+ |----------|---------|
93
+ | Start new task | `/prompt devctx-workflow` |
94
+ | Continue previous task | `smart_turn(start) and continue` |
95
+ | Force MCP usage | `/prompt use-devctx` |
96
+ | First time in project | `/prompt devctx-preflight` |
97
+ | Trust automatic rules | Just describe your task normally |
98
+
99
+ ---
100
+
45
101
  ## How it Works in Practice
46
102
 
47
103
  **The reality:** This MCP does not intercept prompts automatically. Here's the actual flow:
@@ -62,7 +118,7 @@ Restart your AI client. Done.
62
118
  - āœ… Token savings: 85-90% on complex tasks
63
119
 
64
120
  Check actual usage:
65
- - **Real-time feedback** - See usage immediately (enable with `export DEVCTX_SHOW_USAGE=true`)
121
+ - **Real-time feedback** - Enabled by default (disable with `export DEVCTX_SHOW_USAGE=false`)
66
122
  - `npm run report:metrics` - Tool-level savings + adoption analysis
67
123
  - `npm run report:workflows` - Workflow-level savings (requires `DEVCTX_WORKFLOW_TRACKING=true`)
68
124
 
@@ -105,16 +161,11 @@ Production usage: **14.5M tokens → 1.6M tokens** (89.87% reduction)
105
161
 
106
162
  ## Verify It's Working
107
163
 
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.
164
+ ### Real-Time Feedback (Enabled by Default)
111
165
 
112
- **To keep it enabled permanently:**
113
- ```bash
114
- export DEVCTX_SHOW_USAGE=true
115
- ```
166
+ Feedback is **enabled by default** and shows after every devctx tool call.
116
167
 
117
- **To disable immediately:**
168
+ **To disable:**
118
169
  ```bash
119
170
  export DEVCTX_SHOW_USAGE=false
120
171
  ```
@@ -130,7 +181,7 @@ You'll see at the end of agent responses:
130
181
 
131
182
  **Total saved:** ~57.0K tokens
132
183
 
133
- *Onboarding mode: showing for 3 more tool calls. To keep: `export DEVCTX_SHOW_USAGE=true`*
184
+ *To disable this message: `export DEVCTX_SHOW_USAGE=false`*
134
185
  ```
135
186
 
136
187
  **Why this is useful:**
@@ -203,12 +254,12 @@ You'll see warnings like:
203
254
  - Low adoption (<30%)
204
255
  - Usage dropped mid-session
205
256
 
206
- **Combine all features:**
257
+ **All features enabled by default.** To disable:
207
258
 
208
259
  ```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
260
+ export DEVCTX_SHOW_USAGE=false
261
+ export DEVCTX_EXPLAIN=false
262
+ export DEVCTX_DETECT_MISSED=false
212
263
  ```
213
264
 
214
265
  ## MCP Prompts
@@ -272,19 +323,50 @@ Get everything for a task in one call:
272
323
 
273
324
  Returns: relevant files + compressed content + symbol details + graph relationships
274
325
 
326
+ **Smart pattern detection:** Automatically detects literal patterns (TODO, FIXME, /**, console.log, debugger) and prioritizes them in search.
327
+
275
328
  ### smart_summary
276
329
 
277
330
  Maintain task checkpoint:
278
331
 
279
332
  ```javascript
280
- // Save checkpoint
333
+ // Save checkpoint (flat API - recommended)
334
+ { action: 'update', goal: 'Implement OAuth', status: 'in_progress', nextStep: '...' }
335
+
336
+ // Or nested format (backward compatible)
281
337
  { action: 'update', update: { goal: 'Implement OAuth', status: 'in_progress', nextStep: '...' }}
282
338
 
283
339
  // Resume task
284
340
  { action: 'get' }
285
341
  ```
286
342
 
287
- Stores compressed task state (~100 tokens: goal, status, decisions, blockers), not full conversation.
343
+ Stores compressed task state (~100 tokens: goal, status, decisions, blockers), not full conversation. Supports both flat and nested parameter formats.
344
+
345
+ ### smart_status
346
+
347
+ Display current session context:
348
+
349
+ ```javascript
350
+ { format: 'detailed' } // Full output with progress stats
351
+ { format: 'compact' } // Minimal JSON
352
+ ```
353
+
354
+ Shows goal, status, recent decisions, touched files, and progress. Updates automatically with each MCP operation.
355
+
356
+ ### smart_edit
357
+
358
+ Batch edit multiple files:
359
+
360
+ ```javascript
361
+ {
362
+ pattern: 'console.log',
363
+ replacement: 'logger.info',
364
+ files: ['src/a.js', 'src/b.js'],
365
+ mode: 'literal' // or 'regex'
366
+ }
367
+ ```
368
+
369
+ Use `dryRun: true` for preview. Max 50 files per call.
288
370
 
289
371
  ## New Features
290
372
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smart-context-mcp",
3
- "version": "1.3.0",
3
+ "version": "1.4.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,21 @@
1
+ export const IGNORED_DIRS = [
2
+ 'node_modules',
3
+ '.git',
4
+ '.next',
5
+ 'dist',
6
+ 'build',
7
+ 'coverage',
8
+ '.venv',
9
+ 'venv',
10
+ '__pycache__',
11
+ '.terraform',
12
+ '.devctx',
13
+ ];
14
+
15
+ export const IGNORED_FILE_NAMES = [
16
+ 'pnpm-lock.yaml',
17
+ 'package-lock.json',
18
+ 'yarn.lock',
19
+ 'bun.lockb',
20
+ 'npm-shrinkwrap.json',
21
+ ];
@@ -1,24 +1,12 @@
1
- /**
2
- * Cross-project context for monorepos and related projects.
3
- *
4
- * Enables context sharing across multiple related codebases.
5
- */
6
-
7
1
  import fs from 'node:fs';
8
2
  import path from 'node:path';
9
3
  import { loadIndex } from './index.js';
10
4
  import { smartSearch } from './tools/smart-search.js';
11
5
  import { smartRead } from './tools/smart-read.js';
12
- import { projectRoot, setProjectRoot } from './utils/paths.js';
6
+ import { projectRoot } from './utils/paths.js';
13
7
 
14
8
  const CROSS_PROJECT_CONFIG_FILE = '.devctx-projects.json';
15
9
 
16
- /**
17
- * Load cross-project configuration.
18
- *
19
- * @param {string} root - Project root
20
- * @returns {object|null} Configuration or null if not found
21
- */
22
10
  export const loadCrossProjectConfig = (root = projectRoot) => {
23
11
  const configPath = path.join(root, CROSS_PROJECT_CONFIG_FILE);
24
12
 
@@ -34,12 +22,6 @@ export const loadCrossProjectConfig = (root = projectRoot) => {
34
22
  }
35
23
  };
36
24
 
37
- /**
38
- * Discover related projects from config.
39
- *
40
- * @param {string} root - Project root
41
- * @returns {Array<object>} Array of { name, path, type, description }
42
- */
43
25
  export const discoverRelatedProjects = (root = projectRoot) => {
44
26
  const config = loadCrossProjectConfig(root);
45
27
  if (!config?.projects) return [];
@@ -68,13 +50,6 @@ export const discoverRelatedProjects = (root = projectRoot) => {
68
50
  return projects;
69
51
  };
70
52
 
71
- /**
72
- * Search across multiple related projects.
73
- *
74
- * @param {string} query - Search query
75
- * @param {object} options - Search options
76
- * @returns {Promise<Array>} Results from all projects
77
- */
78
53
  export const searchAcrossProjects = async (query, options = {}) => {
79
54
  const {
80
55
  root = projectRoot,
@@ -125,18 +100,10 @@ export const searchAcrossProjects = async (query, options = {}) => {
125
100
  return results;
126
101
  };
127
102
 
128
- /**
129
- * Read files from multiple projects.
130
- *
131
- * @param {Array<object>} fileRefs - Array of { project, file, mode }
132
- * @param {string} root - Project root
133
- * @returns {Promise<Array>} Read results
134
- */
135
103
  export const readAcrossProjects = async (fileRefs, root = projectRoot) => {
136
104
  const relatedProjects = discoverRelatedProjects(root);
137
105
  const projectMap = new Map(relatedProjects.map(p => [p.name, p]));
138
106
 
139
- const originalRoot = projectRoot;
140
107
  const results = [];
141
108
 
142
109
  for (const ref of fileRefs) {
@@ -151,11 +118,10 @@ export const readAcrossProjects = async (fileRefs, root = projectRoot) => {
151
118
  }
152
119
 
153
120
  try {
154
- setProjectRoot(project.path);
155
-
156
121
  const readResult = await smartRead({
157
122
  filePath: ref.file,
158
123
  mode: ref.mode || 'outline',
124
+ cwd: project.path,
159
125
  });
160
126
 
161
127
  results.push({
@@ -175,17 +141,9 @@ export const readAcrossProjects = async (fileRefs, root = projectRoot) => {
175
141
  }
176
142
  }
177
143
 
178
- setProjectRoot(originalRoot);
179
144
  return results;
180
145
  };
181
146
 
182
- /**
183
- * Find symbol definitions across projects.
184
- *
185
- * @param {string} symbolName - Symbol to find
186
- * @param {string} root - Project root
187
- * @returns {Promise<Array>} Symbol locations across projects
188
- */
189
147
  export const findSymbolAcrossProjects = async (symbolName, root = projectRoot) => {
190
148
  const relatedProjects = discoverRelatedProjects(root).filter(p => p.hasIndex);
191
149
  const results = [];
@@ -223,12 +181,6 @@ export const findSymbolAcrossProjects = async (symbolName, root = projectRoot) =
223
181
  return results;
224
182
  };
225
183
 
226
- /**
227
- * Get dependency graph across projects.
228
- *
229
- * @param {string} root - Project root
230
- * @returns {object} Cross-project dependency graph
231
- */
232
184
  export const getCrossProjectDependencies = (root = projectRoot) => {
233
185
  const relatedProjects = discoverRelatedProjects(root).filter(p => p.hasIndex);
234
186
  const dependencies = {
@@ -276,12 +228,6 @@ export const getCrossProjectDependencies = (root = projectRoot) => {
276
228
  return dependencies;
277
229
  };
278
230
 
279
- /**
280
- * Get statistics about cross-project usage.
281
- *
282
- * @param {string} root - Project root
283
- * @returns {object} Usage statistics
284
- */
285
231
  export const getCrossProjectStats = (root = projectRoot) => {
286
232
  const relatedProjects = discoverRelatedProjects(root);
287
233
  const deps = getCrossProjectDependencies(root);
@@ -306,12 +252,6 @@ export const getCrossProjectStats = (root = projectRoot) => {
306
252
  return stats;
307
253
  };
308
254
 
309
- /**
310
- * Create a sample cross-project configuration.
311
- *
312
- * @param {string} root - Project root
313
- * @returns {object} Sample configuration
314
- */
315
255
  export const createSampleConfig = (root = projectRoot) => {
316
256
  return {
317
257
  version: '1.0',
@@ -1,32 +1,25 @@
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
1
  const sessionDecisions = {
13
2
  decisions: [],
14
- enabled: false,
3
+ enabled: true,
15
4
  };
16
5
 
17
- /**
18
- * Check if explanations are enabled
19
- */
20
6
  export const isExplainEnabled = () => {
21
7
  const envValue = process.env.DEVCTX_EXPLAIN?.toLowerCase();
22
- const enabled = envValue === 'true' || envValue === '1' || envValue === 'yes';
23
- sessionDecisions.enabled = enabled;
24
- return enabled;
8
+
9
+ if (envValue === 'true' || envValue === '1' || envValue === 'yes') {
10
+ sessionDecisions.enabled = true;
11
+ return true;
12
+ }
13
+
14
+ if (envValue === 'false' || envValue === '0' || envValue === 'no') {
15
+ sessionDecisions.enabled = false;
16
+ return false;
17
+ }
18
+
19
+ sessionDecisions.enabled = true;
20
+ return true;
25
21
  };
26
22
 
27
- /**
28
- * Record a decision with explanation
29
- */
30
23
  export const recordDecision = ({
31
24
  tool,
32
25
  action,
@@ -48,16 +41,10 @@ export const recordDecision = ({
48
41
  });
49
42
  };
50
43
 
51
- /**
52
- * Get all decisions for current session
53
- */
54
44
  export const getSessionDecisions = () => {
55
45
  return sessionDecisions.decisions;
56
46
  };
57
47
 
58
- /**
59
- * Format decisions as markdown for display
60
- */
61
48
  export const formatDecisionExplanations = () => {
62
49
  if (!isExplainEnabled()) return '';
63
50
 
@@ -95,46 +82,30 @@ export const formatDecisionExplanations = () => {
95
82
  return lines.join('\n');
96
83
  };
97
84
 
98
- /**
99
- * Reset session decisions (for testing or manual reset)
100
- */
101
85
  export const resetSessionDecisions = () => {
102
86
  sessionDecisions.decisions = [];
87
+ sessionDecisions.enabled = true;
103
88
  };
104
89
 
105
- /**
106
- * Common decision reasons (for consistency)
107
- */
108
90
  export const DECISION_REASONS = {
109
- // smart_read reasons
110
91
  LARGE_FILE: 'File is large (>500 lines), outline mode extracts structure only',
111
92
  SYMBOL_EXTRACTION: 'Extracting specific symbol, smart_read can locate and extract it efficiently',
112
93
  TOKEN_BUDGET: 'Token budget constraint, cascading to more compressed mode',
113
94
  MULTIPLE_SYMBOLS: 'Reading multiple symbols, smart_read can batch them',
114
-
115
- // smart_search reasons
116
95
  MULTIPLE_FILES: 'Query spans 50+ files, smart_search ranks by relevance',
117
96
  INTENT_AWARE: 'Intent-aware search prioritizes relevant results (debug/implementation/tests)',
118
97
  INDEX_BOOST: 'Symbol index available, boosting relevant matches',
119
98
  PATTERN_SEARCH: 'Complex pattern search, smart_search handles regex efficiently',
120
-
121
- // smart_context reasons
122
99
  TASK_CONTEXT: 'Building complete context for task, smart_context orchestrates multiple reads',
123
100
  RELATED_FILES: 'Need related files (callers, tests, types), smart_context finds them',
124
101
  ONE_CALL: 'Single call to get all context, more efficient than multiple reads',
125
102
  DIFF_ANALYSIS: 'Analyzing git diff, smart_context expands changed symbols',
126
-
127
- // smart_shell reasons
128
103
  COMMAND_OUTPUT: 'Command output needs compression (git log, npm test, etc.)',
129
104
  RELEVANT_LINES: 'Extracting relevant lines from command output',
130
105
  SAFE_EXECUTION: 'Using allowlist-validated command execution',
131
-
132
- // smart_summary reasons
133
106
  CHECKPOINT: 'Saving task checkpoint for session recovery',
134
107
  RESUME: 'Recovering previous task context',
135
108
  PERSISTENCE: 'Maintaining task state across agent restarts',
136
-
137
- // Native tool reasons
138
109
  SIMPLE_TASK: 'Task is simple, native tool is more direct',
139
110
  ALREADY_CACHED: 'Content already in context, no need for compression',
140
111
  SINGLE_LINE: 'Reading single line, native Read is sufficient',
@@ -142,9 +113,6 @@ export const DECISION_REASONS = {
142
113
  NO_INDEX: 'No symbol index available, native search is equivalent',
143
114
  };
144
115
 
145
- /**
146
- * Common expected benefits (for consistency)
147
- */
148
116
  export const EXPECTED_BENEFITS = {
149
117
  TOKEN_SAVINGS: (tokens) => `~${formatTokens(tokens)} saved`,
150
118
  FASTER_RESPONSE: 'Faster response due to less data to process',
@@ -154,9 +122,6 @@ export const EXPECTED_BENEFITS = {
154
122
  FOCUSED_RESULTS: 'Focused on relevant code only',
155
123
  };
156
124
 
157
- /**
158
- * Format token count for display
159
- */
160
125
  const formatTokens = (tokens) => {
161
126
  if (tokens >= 1000000) {
162
127
  return `${(tokens / 1000000).toFixed(1)}M tokens`;
package/src/index.js CHANGED
@@ -3,6 +3,7 @@ import fsp from 'node:fs/promises';
3
3
  import path from 'node:path';
4
4
  import ts from 'typescript';
5
5
  import { isBinaryBuffer } from './utils/fs.js';
6
+ import { IGNORED_DIRS } from './config/ignored-paths.js';
6
7
 
7
8
  const INDEX_VERSION = 4;
8
9
 
@@ -61,10 +62,7 @@ const indexableExtensions = new Set([
61
62
  '.cs', '.kt', '.php', '.swift',
62
63
  ]);
63
64
 
64
- const ignoredDirs = new Set([
65
- 'node_modules', '.git', '.next', 'dist', 'build', 'coverage',
66
- '.venv', 'venv', '__pycache__', '.terraform', '.devctx',
67
- ]);
65
+ const ignoredDirs = new Set(IGNORED_DIRS);
68
66
 
69
67
  const scriptKindByExtension = {
70
68
  '.js': ts.ScriptKind.JS,
@@ -1,25 +1,9 @@
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
1
  const sessionActivity = {
18
2
  devctxOperations: 0,
19
- totalOperations: 0, // Estimated from devctx calls + time-based heuristic
3
+ totalOperations: 0,
20
4
  lastDevctxCall: 0,
21
5
  sessionStart: Date.now(),
22
- enabled: false,
6
+ enabled: true,
23
7
  warnings: [],
24
8
  };
25
9
 
@@ -34,19 +18,23 @@ const DEVCTX_TOOLS = new Set([
34
18
  'build_index',
35
19
  ]);
36
20
 
37
- /**
38
- * Check if missed opportunity detection is enabled
39
- */
40
21
  export const isMissedDetectionEnabled = () => {
41
22
  const envValue = process.env.DEVCTX_DETECT_MISSED?.toLowerCase();
42
- const enabled = envValue === 'true' || envValue === '1' || envValue === 'yes';
43
- sessionActivity.enabled = enabled;
44
- return enabled;
23
+
24
+ if (envValue === 'true' || envValue === '1' || envValue === 'yes') {
25
+ sessionActivity.enabled = true;
26
+ return true;
27
+ }
28
+
29
+ if (envValue === 'false' || envValue === '0' || envValue === 'no') {
30
+ sessionActivity.enabled = false;
31
+ return false;
32
+ }
33
+
34
+ sessionActivity.enabled = true;
35
+ return true;
45
36
  };
46
37
 
47
- /**
48
- * Record devctx tool usage
49
- */
50
38
  export const recordDevctxOperation = () => {
51
39
  if (!isMissedDetectionEnabled()) return;
52
40
 
@@ -55,29 +43,19 @@ export const recordDevctxOperation = () => {
55
43
  sessionActivity.lastDevctxCall = Date.now();
56
44
  };
57
45
 
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
46
  const estimateTotalOperations = () => {
63
47
  const now = Date.now();
64
48
  const sessionDuration = now - sessionActivity.sessionStart;
65
49
  const timeSinceLastDevctx = now - sessionActivity.lastDevctxCall;
66
50
 
67
- // If session is active (recent devctx calls), estimate conservatively
68
51
  if (timeSinceLastDevctx < 2 * 60 * 1000) {
69
52
  return sessionActivity.totalOperations;
70
53
  }
71
54
 
72
- // If long gap without devctx, estimate agent is using native tools
73
- // Heuristic: ~1 operation per 10 seconds of activity
74
55
  const estimatedNativeOps = Math.floor(timeSinceLastDevctx / 10000);
75
56
  return sessionActivity.totalOperations + estimatedNativeOps;
76
57
  };
77
58
 
78
- /**
79
- * Analyze session and detect missed opportunities
80
- */
81
59
  export const analyzeMissedOpportunities = () => {
82
60
  if (!isMissedDetectionEnabled()) return null;
83
61
 
@@ -85,21 +63,18 @@ export const analyzeMissedOpportunities = () => {
85
63
  const sessionDuration = now - sessionActivity.sessionStart;
86
64
  const timeSinceLastDevctx = now - sessionActivity.lastDevctxCall;
87
65
  const estimatedTotal = estimateTotalOperations();
88
-
89
66
  const opportunities = [];
90
67
 
91
- // Detection 1: Long session with no devctx usage
92
68
  if (sessionDuration > 5 * 60 * 1000 && sessionActivity.devctxOperations === 0) {
93
69
  opportunities.push({
94
70
  type: 'no_devctx_usage',
95
71
  severity: 'high',
96
72
  reason: 'Session active for >5 minutes with 0 devctx calls. Agent may not be using devctx.',
97
73
  suggestion: 'Use forcing prompt or check if MCP is active',
98
- estimatedSavings: estimatedTotal * 10000, // Estimate ~10K tokens per operation
74
+ estimatedSavings: estimatedTotal * 10000,
99
75
  });
100
76
  }
101
77
 
102
- // Detection 2: Low devctx adoption in active session
103
78
  const devctxRatio = estimatedTotal > 0 ? sessionActivity.devctxOperations / estimatedTotal : 0;
104
79
  if (estimatedTotal >= 10 && devctxRatio < 0.3) {
105
80
  opportunities.push({
@@ -111,7 +86,6 @@ export const analyzeMissedOpportunities = () => {
111
86
  });
112
87
  }
113
88
 
114
- // Detection 3: Long gap without devctx (agent switched to native tools)
115
89
  if (sessionActivity.devctxOperations > 0 && timeSinceLastDevctx > 3 * 60 * 1000) {
116
90
  const minutesSince = Math.round(timeSinceLastDevctx / 60000);
117
91
  opportunities.push({
@@ -123,7 +97,6 @@ export const analyzeMissedOpportunities = () => {
123
97
  });
124
98
  }
125
99
 
126
- // Detection 4: Session too short to analyze
127
100
  if (sessionDuration < 60 * 1000 && opportunities.length === 0) {
128
101
  return {
129
102
  opportunities: [],
@@ -143,17 +116,21 @@ export const analyzeMissedOpportunities = () => {
143
116
  };
144
117
  };
145
118
 
146
- /**
147
- * Format missed opportunities as markdown
148
- */
149
119
  export const formatMissedOpportunities = () => {
150
120
  if (!isMissedDetectionEnabled()) return '';
151
121
 
152
122
  const analysis = analyzeMissedOpportunities();
153
123
  if (!analysis) return '';
154
124
 
155
- // Don't show if session is too short or no opportunities
156
- if (analysis.message || analysis.opportunities.length === 0) {
125
+ if (analysis.message) {
126
+ return '';
127
+ }
128
+
129
+ if (analysis.opportunities.length === 0 && analysis.devctxOperations > 0) {
130
+ return `\n\nāœ… **devctx adoption: ${analysis.devctxRatio}%** (${analysis.devctxOperations}/${analysis.estimatedTotal} operations)\n`;
131
+ }
132
+
133
+ if (analysis.opportunities.length === 0) {
157
134
  return '';
158
135
  }
159
136
 
@@ -200,9 +177,6 @@ export const formatMissedOpportunities = () => {
200
177
  return lines.join('\n');
201
178
  };
202
179
 
203
- /**
204
- * Get session activity summary
205
- */
206
180
  export const getSessionActivity = () => {
207
181
  return {
208
182
  devctxOperations: sessionActivity.devctxOperations,
@@ -213,20 +187,15 @@ export const getSessionActivity = () => {
213
187
  };
214
188
  };
215
189
 
216
- /**
217
- * Reset session activity (for testing or manual reset)
218
- */
219
190
  export const resetSessionActivity = () => {
220
191
  sessionActivity.devctxOperations = 0;
221
192
  sessionActivity.totalOperations = 0;
222
193
  sessionActivity.lastDevctxCall = 0;
223
194
  sessionActivity.sessionStart = Date.now();
224
195
  sessionActivity.warnings = [];
196
+ sessionActivity.enabled = true;
225
197
  };
226
198
 
227
- /**
228
- * Testing helpers - expose internal state for test scenarios
229
- */
230
199
  export const __testing__ = {
231
200
  setSessionStart: (timestamp) => {
232
201
  sessionActivity.sessionStart = timestamp;
@@ -240,9 +209,6 @@ export const __testing__ = {
240
209
  getSessionActivity: () => sessionActivity,
241
210
  };
242
211
 
243
- /**
244
- * Format token count for display
245
- */
246
212
  const formatTokens = (tokens) => {
247
213
  if (tokens >= 1000000) {
248
214
  return `${(tokens / 1000000).toFixed(1)}M tokens`;