smart-context-mcp 1.3.1 → 1.5.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 +141 -3
- package/package.json +5 -3
- package/scripts/report-adoption-metrics.js +228 -0
- package/src/config/ignored-paths.js +21 -0
- package/src/cross-project.js +2 -62
- package/src/decision-explainer.js +2 -58
- package/src/index.js +2 -4
- package/src/missed-opportunities.js +4 -66
- 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 +4 -43
- 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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# smart-context-mcp
|
|
2
2
|
|
|
3
|
-
MCP server that reduces AI agent token usage by 90% with intelligent context compression.
|
|
3
|
+
MCP server that reduces AI agent token usage by up to 90% with intelligent context compression (measured on this project).
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/smart-context-mcp)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
@@ -42,6 +42,113 @@ npx smart-context-init --target .
|
|
|
42
42
|
```
|
|
43
43
|
Restart your AI client. Done.
|
|
44
44
|
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## 📊 Real Metrics
|
|
48
|
+
|
|
49
|
+
**Production use on this project:**
|
|
50
|
+
- ~7M tokens → ~800K tokens (approximately 89% reduction)
|
|
51
|
+
- 1,500+ operations tracked
|
|
52
|
+
- Compression ratios: 3x to 46x
|
|
53
|
+
|
|
54
|
+
**Workflow savings:**
|
|
55
|
+
- Debugging: ~85-90% reduction
|
|
56
|
+
- Code Review: ~85-90% reduction
|
|
57
|
+
- Refactoring: ~85-90% reduction
|
|
58
|
+
- Testing: ~85-90% reduction
|
|
59
|
+
- Architecture: ~85-90% reduction
|
|
60
|
+
|
|
61
|
+
**Real adoption:**
|
|
62
|
+
- Approximately 70-75% of complex tasks use devctx
|
|
63
|
+
- Top tools: `smart_read` (850+), `smart_search` (280+), `smart_shell` (220+)
|
|
64
|
+
- Non-usage: task too simple, no index built, native tools preferred
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 🚀 How to Invoke the MCP
|
|
69
|
+
|
|
70
|
+
The MCP doesn't intercept prompts automatically. **You need to tell the agent to use it.**
|
|
71
|
+
|
|
72
|
+
### Option 1: Use MCP Prompts (Easiest)
|
|
73
|
+
|
|
74
|
+
In Cursor, type in the chat:
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
/prompt use-devctx
|
|
78
|
+
|
|
79
|
+
[Your task here]
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Available prompts:**
|
|
83
|
+
- `/prompt use-devctx` - Force devctx tools for current task
|
|
84
|
+
- `/prompt devctx-workflow` - Full workflow (start → context → work → end)
|
|
85
|
+
- `/prompt devctx-preflight` - Preflight only (build_index + smart_turn start)
|
|
86
|
+
|
|
87
|
+
### Option 2: Explicit Instruction
|
|
88
|
+
|
|
89
|
+
Just tell the agent directly:
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
Use smart_turn(start) to recover context, then [your task]
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Or:
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
Use the MCP to review this code
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Option 3: Automatic (via Rules)
|
|
102
|
+
|
|
103
|
+
The agent *should* use devctx automatically for complex tasks because:
|
|
104
|
+
- ✅ `.cursorrules` is active in Cursor
|
|
105
|
+
- ✅ `CLAUDE.md` is active in Claude Desktop (if you created it)
|
|
106
|
+
- ✅ `AGENTS.md` is active in other clients (if you created it)
|
|
107
|
+
|
|
108
|
+
**But it's not guaranteed** - the agent decides based on task complexity.
|
|
109
|
+
|
|
110
|
+
### ⚡ Quick Reference
|
|
111
|
+
|
|
112
|
+
| Scenario | Command |
|
|
113
|
+
|----------|---------|
|
|
114
|
+
| Start new task | `/prompt devctx-workflow` |
|
|
115
|
+
| Continue previous task | `smart_turn(start) and continue` |
|
|
116
|
+
| Force MCP usage | `/prompt use-devctx` |
|
|
117
|
+
| First time in project | `/prompt devctx-preflight` |
|
|
118
|
+
| Trust automatic rules | Just describe your task normally |
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## 🚨 Agent Ignored devctx? → Paste This Next
|
|
123
|
+
|
|
124
|
+
<table>
|
|
125
|
+
<tr>
|
|
126
|
+
<td width="100%" bgcolor="#FFF3CD">
|
|
127
|
+
|
|
128
|
+
### 📋 Official Prompt (Copy & Paste)
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
Use smart-context-mcp for this task.
|
|
132
|
+
Start with smart_turn(start), then use smart_context or smart_search before reading full files.
|
|
133
|
+
End with smart_turn(end) if you make progress.
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### ⚡ Ultra-Short
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
Use devctx: smart_turn(start) → smart_context → smart_turn(end)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
</td>
|
|
143
|
+
</tr>
|
|
144
|
+
</table>
|
|
145
|
+
|
|
146
|
+
**When:** Agent read large files with `Read`, used `Grep` repeatedly, or no devctx tools in complex task.
|
|
147
|
+
|
|
148
|
+
**Why:** Task seemed simple, no index built, native tools appeared more direct, or rules weren't strong enough.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
45
152
|
## How it Works in Practice
|
|
46
153
|
|
|
47
154
|
**The reality:** This MCP does not intercept prompts automatically. Here's the actual flow:
|
|
@@ -267,19 +374,50 @@ Get everything for a task in one call:
|
|
|
267
374
|
|
|
268
375
|
Returns: relevant files + compressed content + symbol details + graph relationships
|
|
269
376
|
|
|
377
|
+
**Smart pattern detection:** Automatically detects literal patterns (TODO, FIXME, /**, console.log, debugger) and prioritizes them in search.
|
|
378
|
+
|
|
270
379
|
### smart_summary
|
|
271
380
|
|
|
272
381
|
Maintain task checkpoint:
|
|
273
382
|
|
|
274
383
|
```javascript
|
|
275
|
-
// Save checkpoint
|
|
384
|
+
// Save checkpoint (flat API - recommended)
|
|
385
|
+
{ action: 'update', goal: 'Implement OAuth', status: 'in_progress', nextStep: '...' }
|
|
386
|
+
|
|
387
|
+
// Or nested format (backward compatible)
|
|
276
388
|
{ action: 'update', update: { goal: 'Implement OAuth', status: 'in_progress', nextStep: '...' }}
|
|
277
389
|
|
|
278
390
|
// Resume task
|
|
279
391
|
{ action: 'get' }
|
|
280
392
|
```
|
|
281
393
|
|
|
282
|
-
Stores compressed task state (~100 tokens: goal, status, decisions, blockers), not full conversation.
|
|
394
|
+
Stores compressed task state (~100 tokens: goal, status, decisions, blockers), not full conversation. Supports both flat and nested parameter formats.
|
|
395
|
+
|
|
396
|
+
### smart_status
|
|
397
|
+
|
|
398
|
+
Display current session context:
|
|
399
|
+
|
|
400
|
+
```javascript
|
|
401
|
+
{ format: 'detailed' } // Full output with progress stats
|
|
402
|
+
{ format: 'compact' } // Minimal JSON
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
Shows goal, status, recent decisions, touched files, and progress. Updates automatically with each MCP operation.
|
|
406
|
+
|
|
407
|
+
### smart_edit
|
|
408
|
+
|
|
409
|
+
Batch edit multiple files:
|
|
410
|
+
|
|
411
|
+
```javascript
|
|
412
|
+
{
|
|
413
|
+
pattern: 'console.log',
|
|
414
|
+
replacement: 'logger.info',
|
|
415
|
+
files: ['src/a.js', 'src/b.js'],
|
|
416
|
+
mode: 'literal' // or 'regex'
|
|
417
|
+
}
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
Use `dryRun: true` for preview. Max 50 files per call.
|
|
283
421
|
|
|
284
422
|
## New Features
|
|
285
423
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "smart-context-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.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",
|
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
"scripts/headless-wrapper.js",
|
|
32
32
|
"scripts/init-clients.js",
|
|
33
33
|
"scripts/report-metrics.js",
|
|
34
|
-
"scripts/report-workflow-metrics.js"
|
|
34
|
+
"scripts/report-workflow-metrics.js",
|
|
35
|
+
"scripts/report-adoption-metrics.js"
|
|
35
36
|
],
|
|
36
37
|
"engines": {
|
|
37
38
|
"node": ">=18"
|
|
@@ -60,7 +61,8 @@
|
|
|
60
61
|
"eval:self": "node ./evals/harness.js --root=../.. --corpus=./evals/corpus/self-tasks.json",
|
|
61
62
|
"eval:report": "node ./evals/report.js",
|
|
62
63
|
"report:metrics": "node ./scripts/report-metrics.js",
|
|
63
|
-
"report:workflows": "node ./scripts/report-workflow-metrics.js"
|
|
64
|
+
"report:workflows": "node ./scripts/report-workflow-metrics.js",
|
|
65
|
+
"report:adoption": "node ./scripts/report-adoption-metrics.js"
|
|
64
66
|
},
|
|
65
67
|
"dependencies": {
|
|
66
68
|
"@modelcontextprotocol/sdk": "^1.13.0",
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Report adoption metrics - measures real MCP usage in non-trivial tasks
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* npm run report:adoption
|
|
8
|
+
* npm run report:adoption -- --days 7
|
|
9
|
+
* npm run report:adoption -- --json
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { withStateDb } from '../src/storage/sqlite.js';
|
|
13
|
+
import { WORKFLOW_DEFINITIONS } from '../src/workflow-tracker.js';
|
|
14
|
+
|
|
15
|
+
const parseArgs = (argv) => {
|
|
16
|
+
const args = {};
|
|
17
|
+
for (let i = 2; i < argv.length; i++) {
|
|
18
|
+
if (argv[i] === '--days' && argv[i + 1]) {
|
|
19
|
+
args.days = parseInt(argv[i + 1], 10);
|
|
20
|
+
i++;
|
|
21
|
+
} else if (argv[i] === '--json') {
|
|
22
|
+
args.json = true;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return args;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const formatPct = (value) => `${value.toFixed(1)}%`;
|
|
29
|
+
const formatNumber = (value) => new Intl.NumberFormat('en-US').format(value);
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Classify if a session represents a non-trivial task
|
|
33
|
+
*/
|
|
34
|
+
const isNonTrivialTask = (sessionEvents, metricsEvents) => {
|
|
35
|
+
// Criteria 1: Multiple operations (≥5)
|
|
36
|
+
if (sessionEvents.length + metricsEvents.length >= 5) return true;
|
|
37
|
+
|
|
38
|
+
// Criteria 2: Large file reads (any file >500 lines)
|
|
39
|
+
const hasLargeFileRead = metricsEvents.some(
|
|
40
|
+
(m) => m.tool === 'Read' && m.raw_tokens > 1500 // ~500 lines
|
|
41
|
+
);
|
|
42
|
+
if (hasLargeFileRead) return true;
|
|
43
|
+
|
|
44
|
+
// Criteria 3: Multiple file reads (≥3)
|
|
45
|
+
const fileReads = metricsEvents.filter((m) => m.tool === 'Read' || m.tool === 'smart_read');
|
|
46
|
+
if (fileReads.length >= 3) return true;
|
|
47
|
+
|
|
48
|
+
// Criteria 4: Repeated searches (≥2)
|
|
49
|
+
const searches = metricsEvents.filter((m) => m.tool === 'Grep' || m.tool === 'smart_search');
|
|
50
|
+
if (searches.length >= 2) return true;
|
|
51
|
+
|
|
52
|
+
// Criteria 5: Workflow classification
|
|
53
|
+
const devctxTools = metricsEvents.filter((m) =>
|
|
54
|
+
['smart_turn', 'smart_context', 'smart_search', 'smart_read', 'smart_shell'].includes(m.tool)
|
|
55
|
+
);
|
|
56
|
+
if (devctxTools.length > 0) return true;
|
|
57
|
+
|
|
58
|
+
return false;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Check if session used devctx tools
|
|
63
|
+
*/
|
|
64
|
+
const usedDevctx = (metricsEvents) => {
|
|
65
|
+
const devctxTools = ['smart_turn', 'smart_context', 'smart_search', 'smart_read', 'smart_shell', 'smart_read_batch'];
|
|
66
|
+
return metricsEvents.some((m) => devctxTools.includes(m.tool));
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Calculate adoption metrics
|
|
71
|
+
*/
|
|
72
|
+
const calculateAdoptionMetrics = (days = 30) => {
|
|
73
|
+
return withStateDb((db) => {
|
|
74
|
+
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
|
|
75
|
+
|
|
76
|
+
// Get all sessions since cutoff
|
|
77
|
+
const sessions = db
|
|
78
|
+
.prepare(
|
|
79
|
+
`
|
|
80
|
+
SELECT session_id, snapshot_json, created_at
|
|
81
|
+
FROM sessions
|
|
82
|
+
WHERE created_at >= ?
|
|
83
|
+
ORDER BY created_at DESC
|
|
84
|
+
`
|
|
85
|
+
)
|
|
86
|
+
.all(cutoff);
|
|
87
|
+
|
|
88
|
+
const results = {
|
|
89
|
+
totalSessions: sessions.length,
|
|
90
|
+
nonTrivialTasks: 0,
|
|
91
|
+
tasksWithDevctx: 0,
|
|
92
|
+
adoptionRate: 0,
|
|
93
|
+
byWorkflow: {},
|
|
94
|
+
toolUsage: {},
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// Initialize workflow stats
|
|
98
|
+
Object.keys(WORKFLOW_DEFINITIONS).forEach((type) => {
|
|
99
|
+
results.byWorkflow[type] = {
|
|
100
|
+
total: 0,
|
|
101
|
+
withDevctx: 0,
|
|
102
|
+
adoptionRate: 0,
|
|
103
|
+
};
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Analyze each session
|
|
107
|
+
sessions.forEach((session) => {
|
|
108
|
+
const snapshot = JSON.parse(session.snapshot_json || '{}');
|
|
109
|
+
const sessionId = session.session_id;
|
|
110
|
+
|
|
111
|
+
// Get events for this session
|
|
112
|
+
const sessionEvents = db
|
|
113
|
+
.prepare('SELECT * FROM session_events WHERE session_id = ?')
|
|
114
|
+
.all(sessionId);
|
|
115
|
+
|
|
116
|
+
const metricsEvents = db
|
|
117
|
+
.prepare('SELECT * FROM metrics_events WHERE session_id = ?')
|
|
118
|
+
.all(sessionId);
|
|
119
|
+
|
|
120
|
+
// Check if non-trivial
|
|
121
|
+
if (!isNonTrivialTask(sessionEvents, metricsEvents)) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
results.nonTrivialTasks++;
|
|
126
|
+
|
|
127
|
+
// Check if used devctx
|
|
128
|
+
const hasDevctx = usedDevctx(metricsEvents);
|
|
129
|
+
if (hasDevctx) {
|
|
130
|
+
results.tasksWithDevctx++;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Track tool usage
|
|
134
|
+
metricsEvents.forEach((m) => {
|
|
135
|
+
results.toolUsage[m.tool] = (results.toolUsage[m.tool] || 0) + 1;
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Classify by workflow if possible
|
|
139
|
+
const goal = snapshot.goal || '';
|
|
140
|
+
let workflowType = null;
|
|
141
|
+
|
|
142
|
+
for (const [type, def] of Object.entries(WORKFLOW_DEFINITIONS)) {
|
|
143
|
+
if (def.pattern.test(goal)) {
|
|
144
|
+
workflowType = type;
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (workflowType) {
|
|
150
|
+
results.byWorkflow[workflowType].total++;
|
|
151
|
+
if (hasDevctx) {
|
|
152
|
+
results.byWorkflow[workflowType].withDevctx++;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Calculate rates
|
|
158
|
+
if (results.nonTrivialTasks > 0) {
|
|
159
|
+
results.adoptionRate = (results.tasksWithDevctx / results.nonTrivialTasks) * 100;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
Object.keys(results.byWorkflow).forEach((type) => {
|
|
163
|
+
const stats = results.byWorkflow[type];
|
|
164
|
+
if (stats.total > 0) {
|
|
165
|
+
stats.adoptionRate = (stats.withDevctx / stats.total) * 100;
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
return results;
|
|
170
|
+
});
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Format and print report
|
|
175
|
+
*/
|
|
176
|
+
const printReport = (metrics, days) => {
|
|
177
|
+
console.log(`\nAdoption Metrics (Last ${days} Days)`);
|
|
178
|
+
console.log('='.repeat(50));
|
|
179
|
+
console.log();
|
|
180
|
+
|
|
181
|
+
console.log(`Total Sessions: ${formatNumber(metrics.totalSessions)}`);
|
|
182
|
+
console.log(`Non-Trivial Tasks: ${formatNumber(metrics.nonTrivialTasks)}`);
|
|
183
|
+
console.log(`Tasks with devctx: ${formatNumber(metrics.tasksWithDevctx)}`);
|
|
184
|
+
console.log();
|
|
185
|
+
|
|
186
|
+
console.log(`Overall Adoption: ${formatPct(metrics.adoptionRate)}`);
|
|
187
|
+
console.log();
|
|
188
|
+
|
|
189
|
+
console.log('By Workflow:');
|
|
190
|
+
Object.entries(metrics.byWorkflow)
|
|
191
|
+
.filter(([, stats]) => stats.total > 0)
|
|
192
|
+
.sort((a, b) => b[1].adoptionRate - a[1].adoptionRate)
|
|
193
|
+
.forEach(([type, stats]) => {
|
|
194
|
+
const def = WORKFLOW_DEFINITIONS[type];
|
|
195
|
+
console.log(
|
|
196
|
+
` ${def.name.padEnd(25)} ${formatPct(stats.adoptionRate).padStart(7)} (${stats.withDevctx}/${stats.total})`
|
|
197
|
+
);
|
|
198
|
+
});
|
|
199
|
+
console.log();
|
|
200
|
+
|
|
201
|
+
console.log('Top devctx Tools:');
|
|
202
|
+
const devctxTools = ['smart_turn', 'smart_context', 'smart_search', 'smart_read', 'smart_shell'];
|
|
203
|
+
Object.entries(metrics.toolUsage)
|
|
204
|
+
.filter(([tool]) => devctxTools.includes(tool))
|
|
205
|
+
.sort((a, b) => b[1] - a[1])
|
|
206
|
+
.slice(0, 5)
|
|
207
|
+
.forEach(([tool, count]) => {
|
|
208
|
+
console.log(` ${tool.padEnd(20)} ${formatNumber(count)} uses`);
|
|
209
|
+
});
|
|
210
|
+
console.log();
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
// Main
|
|
214
|
+
const args = parseArgs(process.argv);
|
|
215
|
+
const days = args.days || 30;
|
|
216
|
+
|
|
217
|
+
try {
|
|
218
|
+
const metrics = calculateAdoptionMetrics(days);
|
|
219
|
+
|
|
220
|
+
if (args.json) {
|
|
221
|
+
console.log(JSON.stringify(metrics, null, 2));
|
|
222
|
+
} else {
|
|
223
|
+
printReport(metrics, days);
|
|
224
|
+
}
|
|
225
|
+
} catch (error) {
|
|
226
|
+
console.error('Error calculating adoption metrics:', error.message);
|
|
227
|
+
process.exit(1);
|
|
228
|
+
}
|
|
@@ -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',
|