dev-mcp-server 0.0.2 → 1.0.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.
Files changed (58) hide show
  1. package/.env.example +23 -55
  2. package/README.md +609 -219
  3. package/cli.js +486 -160
  4. package/package.json +2 -2
  5. package/src/agents/BaseAgent.js +113 -0
  6. package/src/agents/dreamer.js +165 -0
  7. package/src/agents/improver.js +175 -0
  8. package/src/agents/specialists.js +202 -0
  9. package/src/agents/taskDecomposer.js +176 -0
  10. package/src/agents/teamCoordinator.js +153 -0
  11. package/src/api/routes/agents.js +172 -0
  12. package/src/api/routes/extras.js +115 -0
  13. package/src/api/routes/git.js +72 -0
  14. package/src/api/routes/ingest.js +60 -40
  15. package/src/api/routes/knowledge.js +59 -41
  16. package/src/api/routes/memory.js +41 -0
  17. package/src/api/routes/newRoutes.js +168 -0
  18. package/src/api/routes/pipelines.js +41 -0
  19. package/src/api/routes/planner.js +54 -0
  20. package/src/api/routes/query.js +24 -0
  21. package/src/api/routes/sessions.js +54 -0
  22. package/src/api/routes/tasks.js +67 -0
  23. package/src/api/routes/tools.js +85 -0
  24. package/src/api/routes/v5routes.js +196 -0
  25. package/src/api/server.js +133 -5
  26. package/src/context/compactor.js +151 -0
  27. package/src/context/contextEngineer.js +181 -0
  28. package/src/context/contextVisualizer.js +140 -0
  29. package/src/core/conversationEngine.js +231 -0
  30. package/src/core/indexer.js +169 -143
  31. package/src/core/ingester.js +141 -126
  32. package/src/core/queryEngine.js +286 -236
  33. package/src/cron/cronScheduler.js +260 -0
  34. package/src/dashboard/index.html +1181 -0
  35. package/src/lsp/symbolNavigator.js +220 -0
  36. package/src/memory/memoryManager.js +186 -0
  37. package/src/memory/teamMemory.js +111 -0
  38. package/src/messaging/messageBus.js +177 -0
  39. package/src/monitor/proactiveMonitor.js +337 -0
  40. package/src/pipelines/pipelineEngine.js +230 -0
  41. package/src/planner/plannerEngine.js +202 -0
  42. package/src/plugins/builtin/stats-plugin.js +29 -0
  43. package/src/plugins/pluginManager.js +144 -0
  44. package/src/prompts/promptEngineer.js +289 -0
  45. package/src/sessions/sessionManager.js +166 -0
  46. package/src/skills/skillsManager.js +263 -0
  47. package/src/storage/store.js +127 -105
  48. package/src/tasks/taskManager.js +151 -0
  49. package/src/tools/BashTool.js +154 -0
  50. package/src/tools/FileEditTool.js +280 -0
  51. package/src/tools/GitTool.js +212 -0
  52. package/src/tools/GrepTool.js +199 -0
  53. package/src/tools/registry.js +1380 -0
  54. package/src/utils/costTracker.js +69 -0
  55. package/src/utils/fileParser.js +176 -153
  56. package/src/utils/llmClient.js +355 -206
  57. package/src/watcher/fileWatcher.js +137 -0
  58. package/src/worktrees/worktreeManager.js +176 -0
@@ -1,236 +1,286 @@
1
- 'use strict';
2
-
3
- const llmClient = require('../utils/llmClient');
4
- const indexer = require('./indexer');
5
- const logger = require('../utils/logger');
6
-
7
- const QUERY_MODES = {
8
- DEBUG: 'debug', // "Why is this failing?"
9
- USAGE: 'usage', // "Where is this used?"
10
- IMPACT: 'impact', // "If I change this, what breaks?"
11
- GENERAL: 'general', // Open-ended question
12
- };
13
-
14
- function detectMode(question) {
15
- const q = question.toLowerCase();
16
-
17
- if (/why.*(fail|error|crash|break|throw|exception|not work)/i.test(q) ||
18
- /what.*(error|exception|wrong|cause|happen)/i.test(q) ||
19
- /debug|trace|stack|exception|ClassCastException|NullPointer|TypeError/i.test(q)) {
20
- return QUERY_MODES.DEBUG;
21
- }
22
-
23
- if (/where.*(use|call|reference|import|depend)|who.*(use|call)|find.*(usage|reference)/i.test(q)) {
24
- return QUERY_MODES.USAGE;
25
- }
26
-
27
- if (/if.*change|what.*break|impact|affect|depend|side effect|ripple|downstream/i.test(q)) {
28
- return QUERY_MODES.IMPACT;
29
- }
30
-
31
- return QUERY_MODES.GENERAL;
32
- }
33
-
34
- function buildSystemPrompt(mode) {
35
- const base = `You are an expert developer assistant with deep knowledge of the codebase.
36
- You answer questions ONLY based on the code context provided — never guess or hallucinate.
37
- If the context doesn't contain enough information, say so clearly.
38
- Be concise, direct, and developer-friendly. Use code examples from the context when relevant.`;
39
-
40
- const modeInstructions = {
41
- [QUERY_MODES.DEBUG]: `
42
- Your job: DIAGNOSE the root cause of bugs and errors.
43
- - Identify the exact type mismatch, null reference, or logic flaw
44
- - Trace the execution flow that leads to the error
45
- - Point to the specific file, function, and line range where the issue originates
46
- - Provide a concrete fix with code
47
- Format: Root Cause → Affected Flow → Fix`,
48
-
49
- [QUERY_MODES.USAGE]: `
50
- Your job: FIND all usages of a function, class, module, or variable.
51
- - List every file where it is imported or referenced
52
- - Explain HOW it is used in each context (called directly, passed as callback, extended, etc.)
53
- - Note any patterns or inconsistencies in usage
54
- Format: SummaryFile-by-file breakdown`,
55
-
56
- [QUERY_MODES.IMPACT]: `
57
- Your job: ANALYSE what would break or change if the target is modified.
58
- - List all files/modules that directly depend on it
59
- - Identify indirect dependencies (things that use things that use it)
60
- - Flag any risky or tightly-coupled areas
61
- - Suggest safe modification strategies
62
- Format: Direct Impact → Indirect Impact → Risk Level → Safe change strategy`,
63
-
64
- [QUERY_MODES.GENERAL]: `
65
- Your job: Answer the developer's question using the codebase context.
66
- - Be specific and cite the relevant files/functions
67
- - If the answer spans multiple files, connect the dots
68
- - If something is unclear in the code, flag it`,
69
- };
70
-
71
- return base + (modeInstructions[mode] || modeInstructions[QUERY_MODES.GENERAL]);
72
- }
73
-
74
- function formatContext(docs) {
75
- if (!docs || docs.length === 0) {
76
- return 'No relevant context found in the codebase index.';
77
- }
78
-
79
- return docs
80
- .map((doc, i) => {
81
- const meta = doc.metadata || {};
82
- const metaSummary = [
83
- meta.functions?.length ? `Functions: ${meta.functions.slice(0, 5).join(', ')}` : null,
84
- meta.classes?.length ? `Classes: ${meta.classes.join(', ')}` : null,
85
- meta.imports?.length ? `Imports: ${meta.imports.slice(0, 3).join(', ')}` : null,
86
- meta.errors?.length ? `Errors: ${meta.errors.slice(0, 3).join(', ')}` : null,
87
- meta.patterns?.length ? `Patterns: ${meta.patterns.join(', ')}` : null,
88
- ]
89
- .filter(Boolean)
90
- .join(' | ');
91
-
92
- return `--- [${i + 1}] ${doc.filename} (${doc.kind}) | Score: ${doc.relevanceScore} ---
93
- Path: ${doc.filePath}
94
- ${metaSummary ? `Meta: ${metaSummary}` : ''}
95
- \`\`\`
96
- ${doc.content}
97
- \`\`\``;
98
- })
99
- .join('\n\n');
100
- }
101
-
102
- class QueryEngine {
103
- async query(question, options = {}) {
104
- const {
105
- mode: forcedMode,
106
- topK = 8,
107
- stream = false,
108
- filter = {},
109
- } = options;
110
-
111
- if (!question || question.trim().length === 0) {
112
- throw new Error('Question cannot be empty');
113
- }
114
-
115
- const mode = forcedMode || detectMode(question);
116
- logger.info(`Query mode: ${mode} | Q: "${question.slice(0, 80)}..."`);
117
-
118
- let docs;
119
- switch (mode) {
120
- case QUERY_MODES.DEBUG:
121
- docs = indexer.searchForErrors(question, topK);
122
- break;
123
- case QUERY_MODES.USAGE: {
124
- const usageMatch = question.match(/(?:where|how|who).*?(?:is|are|does)?\s+[`"']?(\w+)[`"']?\s+(?:used|called|import|reference)/i);
125
- const symbol = usageMatch?.[1] || question;
126
- docs = indexer.searchForUsages(symbol, topK);
127
- break;
128
- }
129
- case QUERY_MODES.IMPACT: {
130
- const impactMatch = question.match(/(?:change|modify|update|refactor)\s+[`"']?(\w+)[`"']?/i);
131
- const target = impactMatch?.[1] || question;
132
- docs = indexer.searchForImpact(target, topK);
133
- break;
134
- }
135
- default:
136
- docs = indexer.search(question, topK, filter);
137
- }
138
-
139
- const contextText = formatContext(docs);
140
- const systemPrompt = buildSystemPrompt(mode);
141
-
142
- const userMessage = `## Developer Question
143
- ${question}
144
-
145
- ## Codebase Context (retrieved from your system)
146
- ${contextText}
147
-
148
- ## Answer`;
149
-
150
- logger.info(`Sending to ${llmClient.label()}: ${docs.length} context chunks`);
151
-
152
- if (stream) {
153
- return this._streamQuery(systemPrompt, userMessage, docs, mode);
154
- }
155
-
156
- const response = await llmClient.createMessage({
157
- maxTokens: 2000,
158
- system: systemPrompt,
159
- messages: [{ role: 'user', content: userMessage }],
160
- });
161
-
162
- const answer = response.content[0].text;
163
-
164
- return {
165
- answer,
166
- mode,
167
- provider: llmClient.label(),
168
- sources: docs.map(d => ({
169
- file: d.filename,
170
- path: d.filePath,
171
- kind: d.kind,
172
- relevanceScore: d.relevanceScore,
173
- functions: d.metadata?.functions?.slice(0, 5) || [],
174
- })),
175
- usage: {
176
- inputTokens: response.usage.input_tokens,
177
- outputTokens: response.usage.output_tokens,
178
- },
179
- };
180
- }
181
-
182
- async *_streamQuery(systemPrompt, userMessage, docs, mode) {
183
- const stream = await llmClient.createMessage({
184
- maxTokens: 2000,
185
- system: systemPrompt,
186
- messages: [{ role: 'user', content: userMessage }],
187
- stream: true,
188
- });
189
-
190
- yield {
191
- type: 'metadata',
192
- mode,
193
- provider: llmClient.label(),
194
- sources: docs.map(d => ({
195
- file: d.filename,
196
- path: d.filePath,
197
- kind: d.kind,
198
- relevanceScore: d.relevanceScore,
199
- })),
200
- };
201
-
202
- for await (const event of stream) {
203
- if (event.type === 'content_block_delta' && event.delta.type === 'text_delta') {
204
- yield { type: 'text', text: event.delta.text };
205
- }
206
- if (event.type === 'message_stop') {
207
- yield { type: 'done' };
208
- }
209
- }
210
- }
211
-
212
- async debugError(errorMessage, stackTrace = '', options = {}) {
213
- const question = `Why is this error happening and how do I fix it?
214
- Error: ${errorMessage}
215
- ${stackTrace ? `Stack trace:\n${stackTrace}` : ''}`;
216
-
217
- return this.query(question, { ...options, mode: QUERY_MODES.DEBUG });
218
- }
219
-
220
- async findUsages(symbol, options = {}) {
221
- return this.query(`Where is ${symbol} used across the codebase?`, {
222
- ...options,
223
- mode: QUERY_MODES.USAGE,
224
- });
225
- }
226
-
227
- async analyzeImpact(target, changeDescription = '', options = {}) {
228
- const question = changeDescription
229
- ? `If I change ${target} to ${changeDescription}, what would break or be affected?`
230
- : `If I change or remove ${target}, what would break?`;
231
-
232
- return this.query(question, { ...options, mode: QUERY_MODES.IMPACT });
233
- }
234
- }
235
-
236
- module.exports = { QueryEngine: new QueryEngine(), QUERY_MODES, detectMode };
1
+ const llm = require('../utils/llmClient');
2
+ const indexer = require('./indexer');
3
+ const logger = require('../utils/logger');
4
+ const costTracker = require('../utils/costTracker');
5
+ const { MemoryManager } = require('../memory/memoryManager');
6
+
7
+ // The 3 core query modes from the article
8
+ const QUERY_MODES = {
9
+ DEBUG: 'debug', // "Why is this failing?"
10
+ USAGE: 'usage', // "Where is this used?"
11
+ IMPACT: 'impact', // "If I change this, what breaks?"
12
+ GENERAL: 'general', // Open-ended question
13
+ };
14
+
15
+ /**
16
+ * Detect the most appropriate query mode from the question text
17
+ */
18
+ function detectMode(question) {
19
+ const q = question.toLowerCase();
20
+
21
+ if (/why.*(fail|error|crash|break|throw|exception|not work)/i.test(q) ||
22
+ /what.*(error|exception|wrong|cause|happen)/i.test(q) ||
23
+ /debug|trace|stack|exception|ClassCastException|NullPointer|TypeError/i.test(q)) {
24
+ return QUERY_MODES.DEBUG;
25
+ }
26
+
27
+ if (/where.*(use|call|reference|import|depend)|who.*(use|call)|find.*(usage|reference)/i.test(q)) {
28
+ return QUERY_MODES.USAGE;
29
+ }
30
+
31
+ if (/if.*change|what.*break|impact|affect|depend|side effect|ripple|downstream/i.test(q)) {
32
+ return QUERY_MODES.IMPACT;
33
+ }
34
+
35
+ return QUERY_MODES.GENERAL;
36
+ }
37
+
38
+ /**
39
+ * Build a mode-specific system prompt
40
+ */
41
+ function buildSystemPrompt(mode) {
42
+ const base = `You are an expert developer assistant with deep knowledge of the codebase.
43
+ You answer questions ONLY based on the code context provided never guess or hallucinate.
44
+ If the context doesn't contain enough information, say so clearly.
45
+ Be concise, direct, and developer-friendly. Use code examples from the context when relevant.`;
46
+
47
+ const modeInstructions = {
48
+ [QUERY_MODES.DEBUG]: `
49
+ Your job: DIAGNOSE the root cause of bugs and errors.
50
+ - Identify the exact type mismatch, null reference, or logic flaw
51
+ - Trace the execution flow that leads to the error
52
+ - Point to the specific file, function, and line range where the issue originates
53
+ - Provide a concrete fix with code
54
+ Format: Root Cause Affected Flow → Fix`,
55
+
56
+ [QUERY_MODES.USAGE]: `
57
+ Your job: FIND all usages of a function, class, module, or variable.
58
+ - List every file where it is imported or referenced
59
+ - Explain HOW it is used in each context (called directly, passed as callback, extended, etc.)
60
+ - Note any patterns or inconsistencies in usage
61
+ Format: Summary File-by-file breakdown`,
62
+
63
+ [QUERY_MODES.IMPACT]: `
64
+ Your job: ANALYSE what would break or change if the target is modified.
65
+ - List all files/modules that directly depend on it
66
+ - Identify indirect dependencies (things that use things that use it)
67
+ - Flag any risky or tightly-coupled areas
68
+ - Suggest safe modification strategies
69
+ Format: Direct Impact → Indirect Impact → Risk Level → Safe change strategy`,
70
+
71
+ [QUERY_MODES.GENERAL]: `
72
+ Your job: Answer the developer's question using the codebase context.
73
+ - Be specific and cite the relevant files/functions
74
+ - If the answer spans multiple files, connect the dots
75
+ - If something is unclear in the code, flag it`,
76
+ };
77
+
78
+ return base + (modeInstructions[mode] || modeInstructions[QUERY_MODES.GENERAL]);
79
+ }
80
+
81
+ /**
82
+ * Format retrieved context chunks into a readable prompt section
83
+ */
84
+ function formatContext(docs) {
85
+ if (!docs || docs.length === 0) {
86
+ return 'No relevant context found in the codebase index.';
87
+ }
88
+
89
+ return docs
90
+ .map((doc, i) => {
91
+ const meta = doc.metadata || {};
92
+ const metaSummary = [
93
+ meta.functions?.length ? `Functions: ${meta.functions.slice(0, 5).join(', ')}` : null,
94
+ meta.classes?.length ? `Classes: ${meta.classes.join(', ')}` : null,
95
+ meta.imports?.length ? `Imports: ${meta.imports.slice(0, 3).join(', ')}` : null,
96
+ meta.errors?.length ? `Errors: ${meta.errors.slice(0, 3).join(', ')}` : null,
97
+ meta.patterns?.length ? `Patterns: ${meta.patterns.join(', ')}` : null,
98
+ ]
99
+ .filter(Boolean)
100
+ .join(' | ');
101
+
102
+ return `--- [${i + 1}] ${doc.filename} (${doc.kind}) | Score: ${doc.relevanceScore} ---
103
+ Path: ${doc.filePath}
104
+ ${metaSummary ? `Meta: ${metaSummary}` : ''}
105
+ \`\`\`
106
+ ${doc.content}
107
+ \`\`\``;
108
+ })
109
+ .join('\n\n');
110
+ }
111
+
112
+ class QueryEngine {
113
+ /**
114
+ * Main query method
115
+ */
116
+ async query(question, options = {}) {
117
+ const {
118
+ mode: forcedMode,
119
+ topK = 8,
120
+ stream = false,
121
+ filter = {},
122
+ } = options;
123
+
124
+ if (!question || question.trim().length === 0) {
125
+ throw new Error('Question cannot be empty');
126
+ }
127
+
128
+ const mode = forcedMode || detectMode(question);
129
+ logger.info(`Query mode: ${mode} | Q: "${question.slice(0, 80)}..."`);
130
+
131
+ // Retrieve relevant context
132
+ let docs;
133
+ switch (mode) {
134
+ case QUERY_MODES.DEBUG:
135
+ docs = indexer.searchForErrors(question, topK);
136
+ break;
137
+ case QUERY_MODES.USAGE:
138
+ // Extract the symbol being searched
139
+ const usageMatch = question.match(/(?:where|how|who).*?(?:is|are|does)?\s+[`"']?(\w+)[`"']?\s+(?:used|called|import|reference)/i);
140
+ const symbol = usageMatch?.[1] || question;
141
+ docs = indexer.searchForUsages(symbol, topK);
142
+ break;
143
+ case QUERY_MODES.IMPACT:
144
+ const impactMatch = question.match(/(?:change|modify|update|refactor)\s+[`"']?(\w+)[`"']?/i);
145
+ const target = impactMatch?.[1] || question;
146
+ docs = indexer.searchForImpact(target, topK);
147
+ break;
148
+ default:
149
+ docs = indexer.search(question, topK, filter);
150
+ }
151
+
152
+ const contextText = formatContext(docs);
153
+ const systemPrompt = buildSystemPrompt(mode);
154
+
155
+ const userMessage = `## Developer Question
156
+ ${question}
157
+
158
+ ## Codebase Context (retrieved from your system)
159
+ ${contextText}
160
+
161
+ ## Answer`;
162
+
163
+ const { provider, model } = llm.getInfo();
164
+ logger.info(`Sending to ${provider} (${model}): ${docs.length} context chunks`);
165
+
166
+ // Inject relevant memories into context
167
+ const memories = MemoryManager.getRelevant(question, 5);
168
+ const memoryContext = MemoryManager.formatAsContext(memories);
169
+ const fullSystem = memoryContext ? `${systemPrompt}\n\n${memoryContext}` : systemPrompt;
170
+
171
+ if (stream) {
172
+ return this._streamQuery(fullSystem, userMessage, docs, mode);
173
+ }
174
+
175
+ const response = await llm.chat({
176
+ model: llm.model('smart'),
177
+ max_tokens: 2000,
178
+ system: fullSystem,
179
+ messages: [{ role: 'user', content: userMessage }],
180
+ });
181
+
182
+ const answer = response.content[0].text;
183
+
184
+ // Track cost
185
+ const sessionId = options.sessionId || 'default';
186
+ const cost = costTracker.record({
187
+ model: llm.model('smart'),
188
+ inputTokens: response.usage.input_tokens,
189
+ outputTokens: response.usage.output_tokens,
190
+ sessionId,
191
+ queryType: mode,
192
+ });
193
+
194
+ // Auto-extract memories from this exchange (async, fire-and-forget)
195
+ if (options.extractMemories !== false) {
196
+ MemoryManager.extractFromExchange(question, answer, sessionId).catch(() => { });
197
+ }
198
+
199
+ return {
200
+ answer,
201
+ mode,
202
+ memoriesUsed: memories.length,
203
+ sources: docs.map(d => ({
204
+ file: d.filename,
205
+ path: d.filePath,
206
+ kind: d.kind,
207
+ relevanceScore: d.relevanceScore,
208
+ functions: d.metadata?.functions?.slice(0, 5) || [],
209
+ })),
210
+ usage: {
211
+ inputTokens: response.usage.input_tokens,
212
+ outputTokens: response.usage.output_tokens,
213
+ costUsd: cost.costUsd,
214
+ },
215
+ };
216
+ }
217
+
218
+ /**
219
+ * Streaming version of query
220
+ */
221
+ async *_streamQuery(systemPrompt, userMessage, docs, mode) {
222
+ const stream = await llm.chat({
223
+ model: llm.model('smart'),
224
+ max_tokens: 2000,
225
+ system: systemPrompt,
226
+ messages: [{ role: 'user', content: userMessage }],
227
+ stream: true,
228
+ });
229
+
230
+ // First yield metadata
231
+ yield {
232
+ type: 'metadata',
233
+ mode,
234
+ sources: docs.map(d => ({
235
+ file: d.filename,
236
+ path: d.filePath,
237
+ kind: d.kind,
238
+ relevanceScore: d.relevanceScore,
239
+ })),
240
+ };
241
+
242
+ // Then stream the answer
243
+ for await (const event of stream) {
244
+ if (event.type === 'content_block_delta' && event.delta.type === 'text_delta') {
245
+ yield { type: 'text', text: event.delta.text };
246
+ }
247
+ if (event.type === 'message_stop') {
248
+ yield { type: 'done' };
249
+ }
250
+ }
251
+ }
252
+
253
+ /**
254
+ * Quick debug helper - answer a specific error without full context retrieval
255
+ */
256
+ async debugError(errorMessage, stackTrace = '', options = {}) {
257
+ const question = `Why is this error happening and how do I fix it?
258
+ Error: ${errorMessage}
259
+ ${stackTrace ? `Stack trace:\n${stackTrace}` : ''}`;
260
+
261
+ return this.query(question, { ...options, mode: QUERY_MODES.DEBUG });
262
+ }
263
+
264
+ /**
265
+ * Find all usages of a symbol across the codebase
266
+ */
267
+ async findUsages(symbol, options = {}) {
268
+ return this.query(`Where is ${symbol} used across the codebase?`, {
269
+ ...options,
270
+ mode: QUERY_MODES.USAGE,
271
+ });
272
+ }
273
+
274
+ /**
275
+ * Impact analysis for a proposed change
276
+ */
277
+ async analyzeImpact(target, changeDescription = '', options = {}) {
278
+ const question = changeDescription
279
+ ? `If I change ${target} to ${changeDescription}, what would break or be affected?`
280
+ : `If I change or remove ${target}, what would break?`;
281
+
282
+ return this.query(question, { ...options, mode: QUERY_MODES.IMPACT });
283
+ }
284
+ }
285
+
286
+ module.exports = { QueryEngine: new QueryEngine(), QUERY_MODES, detectMode };