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 +98 -16
- package/package.json +1 -1
- package/src/config/ignored-paths.js +21 -0
- package/src/cross-project.js +2 -62
- package/src/decision-explainer.js +15 -50
- package/src/index.js +2 -4
- package/src/missed-opportunities.js +26 -60
- package/src/server.js +49 -1
- package/src/storage/sqlite.js +1 -1
- package/src/tools/smart-context.js +33 -527
- package/src/tools/smart-edit.js +105 -0
- package/src/tools/smart-read.js +63 -10
- package/src/tools/smart-search.js +4 -2
- package/src/tools/smart-status.js +201 -0
- package/src/tools/smart-summary.js +29 -0
- package/src/usage-feedback.js +7 -68
- package/src/utils/context-scoring.js +400 -0
- package/src/utils/fs.js +13 -7
- package/src/utils/query-extraction.js +180 -0
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** -
|
|
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 (
|
|
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
|
-
**
|
|
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
|
|
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
|
-
*
|
|
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
|
-
**
|
|
257
|
+
**All features enabled by default.** To disable:
|
|
207
258
|
|
|
208
259
|
```bash
|
|
209
|
-
export DEVCTX_SHOW_USAGE=
|
|
210
|
-
export DEVCTX_EXPLAIN=
|
|
211
|
-
export DEVCTX_DETECT_MISSED=
|
|
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
|
+
"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
|
+
];
|
package/src/cross-project.js
CHANGED
|
@@ -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
|
|
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:
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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,
|
|
3
|
+
totalOperations: 0,
|
|
20
4
|
lastDevctxCall: 0,
|
|
21
5
|
sessionStart: Date.now(),
|
|
22
|
-
enabled:
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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,
|
|
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
|
-
|
|
156
|
-
|
|
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`;
|