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
@@ -0,0 +1,69 @@
1
+ 'use strict';
2
+ /**
3
+ * Provider-agnostic cost tracking. Uses llmClient for price lookup.
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ const COST_FILE = path.join(process.cwd(), 'data', 'cost-tracker.json');
10
+
11
+ class CostTracker {
12
+ constructor() {
13
+ this._data = this._load();
14
+ }
15
+
16
+ _load() {
17
+ try { if (fs.existsSync(COST_FILE)) return JSON.parse(fs.readFileSync(COST_FILE, 'utf-8')); } catch { }
18
+ return { sessions: {}, allTime: { inputTokens: 0, outputTokens: 0, totalCostUsd: 0, calls: 0 } };
19
+ }
20
+ _save() { fs.writeFileSync(COST_FILE, JSON.stringify(this._data, null, 2)); }
21
+
22
+ record({ model, inputTokens, outputTokens, sessionId = 'default', queryType = 'general' }) {
23
+ // Lazy-load llmClient to avoid circular dep at startup
24
+ let costUsd = 0;
25
+ try {
26
+ const llm = require('./llmClient');
27
+ costUsd = llm.costUsd(model, inputTokens, outputTokens);
28
+ } catch { }
29
+
30
+ this._data.allTime.inputTokens += inputTokens;
31
+ this._data.allTime.outputTokens += outputTokens;
32
+ this._data.allTime.totalCostUsd += costUsd;
33
+ this._data.allTime.calls += 1;
34
+
35
+ if (!this._data.sessions[sessionId]) {
36
+ this._data.sessions[sessionId] = { startedAt: new Date().toISOString(), inputTokens: 0, outputTokens: 0, totalCostUsd: 0, calls: 0, byQueryType: {} };
37
+ }
38
+ const sess = this._data.sessions[sessionId];
39
+ sess.inputTokens += inputTokens;
40
+ sess.outputTokens += outputTokens;
41
+ sess.totalCostUsd += costUsd;
42
+ sess.calls += 1;
43
+ sess.lastUsedAt = new Date().toISOString();
44
+ sess.byQueryType[queryType] = (sess.byQueryType[queryType] || 0) + 1;
45
+
46
+ this._save();
47
+ return { costUsd, inputTokens, outputTokens };
48
+ }
49
+
50
+ getSession(id = 'default') { return this._data.sessions[id] || null; }
51
+ getAllTime() { return this._data.allTime; }
52
+
53
+ getSummary(sessionId = 'default') {
54
+ const sess = this.getSession(sessionId);
55
+ const all = this.getAllTime();
56
+ return {
57
+ session: sess ? { calls: sess.calls, inputTokens: sess.inputTokens, outputTokens: sess.outputTokens, costUsd: parseFloat(sess.totalCostUsd.toFixed(6)), startedAt: sess.startedAt } : null,
58
+ allTime: { calls: all.calls, inputTokens: all.inputTokens, outputTokens: all.outputTokens, costUsd: parseFloat(all.totalCostUsd.toFixed(6)) },
59
+ };
60
+ }
61
+
62
+ formatCost(usd) {
63
+ if (usd === 0) return '$0.0000 (local)';
64
+ if (usd < 0.001) return `$${(usd * 1000).toFixed(4)}m`;
65
+ return `$${usd.toFixed(4)}`;
66
+ }
67
+ }
68
+
69
+ module.exports = new CostTracker();
@@ -1,183 +1,206 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
3
 
4
+ // Supported file types and their "kind" labels
4
5
  const FILE_TYPE_MAP = {
5
- '.js': 'code',
6
- '.ts': 'code',
7
- '.jsx': 'code',
8
- '.tsx': 'code',
9
- '.mjs': 'code',
10
- '.cjs': 'code',
11
- '.py': 'code',
12
- '.java': 'code',
13
- '.go': 'code',
14
- '.rb': 'code',
15
- '.php': 'code',
16
- '.cs': 'code',
17
- '.cpp': 'code',
18
- '.c': 'code',
19
- '.rs': 'code',
20
- '.json': 'config',
21
- '.yaml': 'config',
22
- '.yml': 'config',
23
- '.env': 'config',
24
- '.toml': 'config',
25
- '.xml': 'config',
26
- '.md': 'documentation',
27
- '.txt': 'documentation',
28
- '.log': 'log',
29
- '.sql': 'schema',
30
- '.graphql': 'schema',
31
- '.gql': 'schema',
32
- '.sh': 'script',
33
- '.bash': 'script',
6
+ '.js': 'code',
7
+ '.ts': 'code',
8
+ '.jsx': 'code',
9
+ '.tsx': 'code',
10
+ '.mjs': 'code',
11
+ '.cjs': 'code',
12
+ '.py': 'code',
13
+ '.java': 'code',
14
+ '.go': 'code',
15
+ '.rb': 'code',
16
+ '.php': 'code',
17
+ '.cs': 'code',
18
+ '.cpp': 'code',
19
+ '.c': 'code',
20
+ '.rs': 'code',
21
+ '.json': 'config',
22
+ '.yaml': 'config',
23
+ '.yml': 'config',
24
+ '.env': 'config',
25
+ '.toml': 'config',
26
+ '.xml': 'config',
27
+ '.md': 'documentation',
28
+ '.txt': 'documentation',
29
+ '.log': 'log',
30
+ '.sql': 'schema',
31
+ '.graphql': 'schema',
32
+ '.gql': 'schema',
33
+ '.sh': 'script',
34
+ '.bash': 'script',
34
35
  };
35
36
 
36
37
  const CHUNK_SIZE = 1500; // characters per chunk
37
38
  const CHUNK_OVERLAP = 200; // overlap between chunks
38
39
 
40
+ /**
41
+ * Parse a file and return structured chunks for indexing
42
+ */
39
43
  function parseFile(filePath) {
40
- const ext = path.extname(filePath).toLowerCase();
41
- const kind = FILE_TYPE_MAP[ext] || 'unknown';
42
- const filename = path.basename(filePath);
43
- const content = fs.readFileSync(filePath, 'utf-8');
44
-
45
- if (!content.trim()) return [];
46
-
47
- const chunks = chunkContent(content, filePath);
48
-
49
- return chunks.map((chunk, idx) => ({
50
- id: `${filePath}::chunk${idx}`,
51
- filePath,
52
- filename,
53
- ext,
54
- kind,
55
- chunkIndex: idx,
56
- totalChunks: chunks.length,
57
- content: chunk,
58
- lines: countLines(chunk),
59
- ingestedAt: new Date().toISOString(),
60
- metadata: extractMetadata(chunk, kind, filename),
61
- }));
44
+ const ext = path.extname(filePath).toLowerCase();
45
+ const kind = FILE_TYPE_MAP[ext] || 'unknown';
46
+ const filename = path.basename(filePath);
47
+ const content = fs.readFileSync(filePath, 'utf-8');
48
+
49
+ if (!content.trim()) return [];
50
+
51
+ const chunks = chunkContent(content, filePath);
52
+
53
+ return chunks.map((chunk, idx) => ({
54
+ id: `${filePath}::chunk${idx}`,
55
+ filePath,
56
+ filename,
57
+ ext,
58
+ kind,
59
+ chunkIndex: idx,
60
+ totalChunks: chunks.length,
61
+ content: chunk,
62
+ lines: countLines(chunk),
63
+ ingestedAt: new Date().toISOString(),
64
+ // Extract code-specific metadata
65
+ metadata: extractMetadata(chunk, kind, filename),
66
+ }));
62
67
  }
63
68
 
69
+ /**
70
+ * Split content into overlapping chunks
71
+ */
64
72
  function chunkContent(content, filePath) {
65
- const lines = content.split('\n');
66
-
67
- if (content.length <= CHUNK_SIZE) return [content];
68
-
69
- const chunks = [];
70
- let current = [];
71
- let currentLen = 0;
72
-
73
- for (const line of lines) {
74
- current.push(line);
75
- currentLen += line.length + 1;
76
-
77
- if (currentLen >= CHUNK_SIZE) {
78
- chunks.push(current.join('\n'));
79
- const overlapLines = [];
80
- let overlapLen = 0;
81
- for (let i = current.length - 1; i >= 0 && overlapLen < CHUNK_OVERLAP; i--) {
82
- overlapLines.unshift(current[i]);
83
- overlapLen += current[i].length + 1;
84
- }
85
- current = overlapLines;
86
- currentLen = overlapLen;
87
- }
73
+ const lines = content.split('\n');
74
+
75
+ // For small files, keep as single chunk
76
+ if (content.length <= CHUNK_SIZE) return [content];
77
+
78
+ const chunks = [];
79
+ let current = [];
80
+ let currentLen = 0;
81
+
82
+ for (const line of lines) {
83
+ current.push(line);
84
+ currentLen += line.length + 1;
85
+
86
+ if (currentLen >= CHUNK_SIZE) {
87
+ chunks.push(current.join('\n'));
88
+ // Keep last N chars for overlap
89
+ const overlapLines = [];
90
+ let overlapLen = 0;
91
+ for (let i = current.length - 1; i >= 0 && overlapLen < CHUNK_OVERLAP; i--) {
92
+ overlapLines.unshift(current[i]);
93
+ overlapLen += current[i].length + 1;
94
+ }
95
+ current = overlapLines;
96
+ currentLen = overlapLen;
88
97
  }
98
+ }
89
99
 
90
- if (current.length > 0) {
91
- chunks.push(current.join('\n'));
92
- }
100
+ if (current.length > 0) {
101
+ chunks.push(current.join('\n'));
102
+ }
93
103
 
94
- return chunks;
104
+ return chunks;
95
105
  }
96
106
 
107
+ /**
108
+ * Extract metadata from file content based on its type
109
+ */
97
110
  function extractMetadata(content, kind, filename) {
98
- const meta = {};
99
-
100
- if (kind === 'code') {
101
- const functions = [];
102
- const classes = [];
103
- const imports = [];
104
- const exports = [];
105
-
106
- const fnMatches = content.matchAll(/(?:function\s+(\w+)|const\s+(\w+)\s*=\s*(?:async\s*)?\(|(\w+)\s*\([^)]*\)\s*{)/g);
107
- for (const m of fnMatches) {
108
- const name = m[1] || m[2] || m[3];
109
- if (name && !['if', 'for', 'while', 'switch', 'catch'].includes(name)) {
110
- functions.push(name);
111
- }
112
- }
113
-
114
- const classMatches = content.matchAll(/class\s+(\w+)/g);
115
- for (const m of classMatches) classes.push(m[1]);
116
-
117
- const importMatches = content.matchAll(/(?:import|require)\s*(?:\{[^}]+\}|[\w*]+)?\s*(?:from)?\s*['"]([^'"]+)['"]/g);
118
- for (const m of importMatches) imports.push(m[1]);
119
-
120
- const exportMatches = content.matchAll(/export\s+(?:default\s+)?(?:function|class|const|let|var)?\s*(\w+)/g);
121
- for (const m of exportMatches) exports.push(m[1]);
122
-
123
- const errors = [];
124
- const errorMatches = content.matchAll(/(?:catch|throw|Error|Exception)\s*[(\s]*([A-Z]\w+(?:Error|Exception))/g);
125
- for (const m of errorMatches) errors.push(m[1]);
126
-
127
- meta.functions = [...new Set(functions)].slice(0, 20);
128
- meta.classes = [...new Set(classes)].slice(0, 10);
129
- meta.imports = [...new Set(imports)].slice(0, 20);
130
- meta.exports = [...new Set(exports)].slice(0, 10);
131
- meta.errors = [...new Set(errors)].slice(0, 10);
132
-
133
- const apiPatterns = [];
134
- if (/express|router\.(get|post|put|delete|patch)/i.test(content)) apiPatterns.push('REST API');
135
- if (/graphql|resolver|schema/i.test(content)) apiPatterns.push('GraphQL');
136
- if (/mongoose|sequelize|prisma|typeorm/i.test(content)) apiPatterns.push('ORM');
137
- if (/redis|ioredis|bull/i.test(content)) apiPatterns.push('Cache/Queue');
138
- if (/jwt|passport|bcrypt/i.test(content)) apiPatterns.push('Auth');
139
- meta.patterns = apiPatterns;
140
-
141
- } else if (kind === 'log') {
142
- const errorLines = content
143
- .split('\n')
144
- .filter(l => /error|exception|warn|fail|crash/i.test(l))
145
- .slice(0, 10);
146
- meta.errors = errorLines;
147
-
148
- const hasTimestamps = /\d{4}-\d{2}-\d{2}|\d{2}\/\d{2}\/\d{4}/.test(content);
149
- meta.hasTimestamps = hasTimestamps;
150
-
151
- } else if (kind === 'schema') {
152
- const tables = [];
153
- const tableMatches = content.matchAll(/(?:CREATE\s+TABLE|model\s+|type\s+)["'`]?(\w+)["'`]?/gi);
154
- for (const m of tableMatches) tables.push(m[1]);
155
- meta.tables = [...new Set(tables)].slice(0, 20);
111
+ const meta = {};
112
+
113
+ if (kind === 'code') {
114
+ // Extract function/class names
115
+ const functions = [];
116
+ const classes = [];
117
+ const imports = [];
118
+ const exports = [];
119
+
120
+ const fnMatches = content.matchAll(/(?:function\s+(\w+)|const\s+(\w+)\s*=\s*(?:async\s*)?\(|(\w+)\s*\([^)]*\)\s*{)/g);
121
+ for (const m of fnMatches) {
122
+ const name = m[1] || m[2] || m[3];
123
+ if (name && !['if', 'for', 'while', 'switch', 'catch'].includes(name)) {
124
+ functions.push(name);
125
+ }
156
126
  }
157
127
 
158
- meta.isBugFix = /fix|bug|patch|resolve|hotfix|issue/i.test(filename) ||
159
- /TODO|FIXME|HACK|BUG|XXX/.test(content);
160
-
161
- return meta;
128
+ const classMatches = content.matchAll(/class\s+(\w+)/g);
129
+ for (const m of classMatches) classes.push(m[1]);
130
+
131
+ const importMatches = content.matchAll(/(?:import|require)\s*(?:\{[^}]+\}|[\w*]+)?\s*(?:from)?\s*['"]([^'"]+)['"]/g);
132
+ for (const m of importMatches) imports.push(m[1]);
133
+
134
+ const exportMatches = content.matchAll(/export\s+(?:default\s+)?(?:function|class|const|let|var)?\s*(\w+)/g);
135
+ for (const m of exportMatches) exports.push(m[1]);
136
+
137
+ // Extract error types mentioned
138
+ const errors = [];
139
+ const errorMatches = content.matchAll(/(?:catch|throw|Error|Exception)\s*[(\s]*([A-Z]\w+(?:Error|Exception))/g);
140
+ for (const m of errorMatches) errors.push(m[1]);
141
+
142
+ meta.functions = [...new Set(functions)].slice(0, 20);
143
+ meta.classes = [...new Set(classes)].slice(0, 10);
144
+ meta.imports = [...new Set(imports)].slice(0, 20);
145
+ meta.exports = [...new Set(exports)].slice(0, 10);
146
+ meta.errors = [...new Set(errors)].slice(0, 10);
147
+
148
+ // Detect API patterns
149
+ const apiPatterns = [];
150
+ if (/express|router\.(get|post|put|delete|patch)/i.test(content)) apiPatterns.push('REST API');
151
+ if (/graphql|resolver|schema/i.test(content)) apiPatterns.push('GraphQL');
152
+ if (/mongoose|sequelize|prisma|typeorm/i.test(content)) apiPatterns.push('ORM');
153
+ if (/redis|ioredis|bull/i.test(content)) apiPatterns.push('Cache/Queue');
154
+ if (/jwt|passport|bcrypt/i.test(content)) apiPatterns.push('Auth');
155
+ meta.patterns = apiPatterns;
156
+
157
+ } else if (kind === 'log') {
158
+ // Extract error/warning lines
159
+ const errorLines = content
160
+ .split('\n')
161
+ .filter(l => /error|exception|warn|fail|crash/i.test(l))
162
+ .slice(0, 10);
163
+ meta.errors = errorLines;
164
+
165
+ // Extract timestamps if present
166
+ const hasTimestamps = /\d{4}-\d{2}-\d{2}|\d{2}\/\d{2}\/\d{4}/.test(content);
167
+ meta.hasTimestamps = hasTimestamps;
168
+
169
+ } else if (kind === 'schema') {
170
+ // Extract table/model names
171
+ const tables = [];
172
+ const tableMatches = content.matchAll(/(?:CREATE\s+TABLE|model\s+|type\s+)["'`]?(\w+)["'`]?/gi);
173
+ for (const m of tableMatches) tables.push(m[1]);
174
+ meta.tables = [...new Set(tables)].slice(0, 20);
175
+ }
176
+
177
+ // Detect if this looks like a bug fix / patch
178
+ meta.isBugFix = /fix|bug|patch|resolve|hotfix|issue/i.test(filename) ||
179
+ /TODO|FIXME|HACK|BUG|XXX/.test(content);
180
+
181
+ return meta;
162
182
  }
163
183
 
164
184
  function countLines(text) {
165
- return text.split('\n').length;
185
+ return text.split('\n').length;
166
186
  }
167
187
 
188
+ /**
189
+ * Check if a file should be skipped
190
+ */
168
191
  function shouldSkip(filePath) {
169
- const skipPatterns = [
170
- /node_modules/,
171
- /\.git\//,
172
- /dist\//,
173
- /build\//,
174
- /coverage\//,
175
- /\.min\.(js|css)$/,
176
- /package-lock\.json$/,
177
- /yarn\.lock$/,
178
- /\.map$/,
179
- ];
180
- return skipPatterns.some(p => p.test(filePath));
192
+ const skipPatterns = [
193
+ /node_modules/,
194
+ /\.git\//,
195
+ /dist\//,
196
+ /build\//,
197
+ /coverage\//,
198
+ /\.min\.(js|css)$/,
199
+ /package-lock\.json$/,
200
+ /yarn\.lock$/,
201
+ /\.map$/,
202
+ ];
203
+ return skipPatterns.some(p => p.test(filePath));
181
204
  }
182
205
 
183
206
  module.exports = { parseFile, shouldSkip, FILE_TYPE_MAP };