dev-mcp-server 0.0.3 → 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.
- package/.env.example +23 -55
- package/README.md +609 -219
- package/cli.js +486 -160
- package/package.json +2 -2
- package/src/agents/BaseAgent.js +113 -0
- package/src/agents/dreamer.js +165 -0
- package/src/agents/improver.js +175 -0
- package/src/agents/specialists.js +202 -0
- package/src/agents/taskDecomposer.js +176 -0
- package/src/agents/teamCoordinator.js +153 -0
- package/src/api/routes/agents.js +172 -0
- package/src/api/routes/extras.js +115 -0
- package/src/api/routes/git.js +72 -0
- package/src/api/routes/ingest.js +60 -40
- package/src/api/routes/knowledge.js +59 -41
- package/src/api/routes/memory.js +41 -0
- package/src/api/routes/newRoutes.js +168 -0
- package/src/api/routes/pipelines.js +41 -0
- package/src/api/routes/planner.js +54 -0
- package/src/api/routes/query.js +24 -0
- package/src/api/routes/sessions.js +54 -0
- package/src/api/routes/tasks.js +67 -0
- package/src/api/routes/tools.js +85 -0
- package/src/api/routes/v5routes.js +196 -0
- package/src/api/server.js +134 -6
- package/src/context/compactor.js +151 -0
- package/src/context/contextEngineer.js +181 -0
- package/src/context/contextVisualizer.js +140 -0
- package/src/core/conversationEngine.js +231 -0
- package/src/core/indexer.js +169 -143
- package/src/core/ingester.js +141 -126
- package/src/core/queryEngine.js +286 -236
- package/src/cron/cronScheduler.js +260 -0
- package/src/dashboard/index.html +1181 -0
- package/src/lsp/symbolNavigator.js +220 -0
- package/src/memory/memoryManager.js +186 -0
- package/src/memory/teamMemory.js +111 -0
- package/src/messaging/messageBus.js +177 -0
- package/src/monitor/proactiveMonitor.js +337 -0
- package/src/pipelines/pipelineEngine.js +230 -0
- package/src/planner/plannerEngine.js +202 -0
- package/src/plugins/builtin/stats-plugin.js +29 -0
- package/src/plugins/pluginManager.js +144 -0
- package/src/prompts/promptEngineer.js +289 -0
- package/src/sessions/sessionManager.js +166 -0
- package/src/skills/skillsManager.js +263 -0
- package/src/storage/store.js +127 -105
- package/src/tasks/taskManager.js +151 -0
- package/src/tools/BashTool.js +154 -0
- package/src/tools/FileEditTool.js +280 -0
- package/src/tools/GitTool.js +212 -0
- package/src/tools/GrepTool.js +199 -0
- package/src/tools/registry.js +1380 -0
- package/src/utils/costTracker.js +69 -0
- package/src/utils/fileParser.js +176 -153
- package/src/utils/llmClient.js +355 -206
- package/src/watcher/fileWatcher.js +137 -0
- package/src/worktrees/worktreeManager.js +176 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dev-mcp-server",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Model Context Platform — AI that understands YOUR codebase",
|
|
5
5
|
"main": "src/api/server.js",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"glob": "^10.4.1",
|
|
32
32
|
"morgan": "^1.10.0",
|
|
33
33
|
"natural": "^8.0.1",
|
|
34
|
-
"openai": "^
|
|
34
|
+
"openai": "^6.33.0",
|
|
35
35
|
"ora": "^5.4.1",
|
|
36
36
|
"winston": "^3.13.0"
|
|
37
37
|
},
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
|
|
2
|
+
'use strict';
|
|
3
|
+
/**
|
|
4
|
+
* Foundation for all agents — tool-use loop, memory injection, cost tracking.
|
|
5
|
+
* Provider-agnostic: uses llmClient which routes to Anthropic / Azure / Ollama.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const llm = require('../utils/llmClient');
|
|
9
|
+
const toolRegistry = require('../tools/registry');
|
|
10
|
+
const contextEngineer = require('../context/contextEngineer');
|
|
11
|
+
const { MemoryManager } = require('../memory/memoryManager');
|
|
12
|
+
const costTracker = require('../utils/costTracker');
|
|
13
|
+
const logger = require('../utils/logger');
|
|
14
|
+
|
|
15
|
+
class BaseAgent {
|
|
16
|
+
constructor(cfg = {}) {
|
|
17
|
+
this.name = cfg.name || 'Agent';
|
|
18
|
+
this.role = cfg.role || 'General assistant';
|
|
19
|
+
this.model = cfg.model || 'smart'; // alias: fast | default | smart
|
|
20
|
+
this.toolNames = cfg.toolNames || [];
|
|
21
|
+
this.maxTokens = cfg.maxTokens || 2000;
|
|
22
|
+
this.maxLoops = cfg.maxLoops || 8;
|
|
23
|
+
this.sessionId = cfg.sessionId || `agent_${this.name.toLowerCase()}`;
|
|
24
|
+
|
|
25
|
+
this._history = [];
|
|
26
|
+
this._callCount = 0;
|
|
27
|
+
this._tokenCount = 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async run(task, opts = {}) {
|
|
31
|
+
const { context = [], extraSystem = '' } = opts;
|
|
32
|
+
|
|
33
|
+
const engineered = contextEngineer.engineer(context, task, opts.mode || 'general');
|
|
34
|
+
const memories = MemoryManager.getRelevant(task, 4);
|
|
35
|
+
const memContext = MemoryManager.formatAsContext(memories);
|
|
36
|
+
const systemPrompt = this._buildSystem(memContext, extraSystem);
|
|
37
|
+
const userMessage = this._buildUserMessage(task, engineered.chunks);
|
|
38
|
+
|
|
39
|
+
const history = [];
|
|
40
|
+
history.push({ role: 'user', content: userMessage });
|
|
41
|
+
|
|
42
|
+
logger.info(`[${this.name}] task="${task.slice(0, 60)}" tools=${this.toolNames.length} ctx=${engineered.chunks.length} provider=${llm.provider}`);
|
|
43
|
+
|
|
44
|
+
const toolSchemas = llm.supportsTools() && this.toolNames.length
|
|
45
|
+
? toolRegistry.schemas(this.toolNames)
|
|
46
|
+
: [];
|
|
47
|
+
|
|
48
|
+
const toolResults = [];
|
|
49
|
+
let loops = 0;
|
|
50
|
+
let response;
|
|
51
|
+
|
|
52
|
+
while (loops < this.maxLoops) {
|
|
53
|
+
loops++;
|
|
54
|
+
|
|
55
|
+
response = await llm.chat({
|
|
56
|
+
model: this.model,
|
|
57
|
+
system: systemPrompt,
|
|
58
|
+
messages: history,
|
|
59
|
+
max_tokens: this.maxTokens,
|
|
60
|
+
tools: toolSchemas.length ? toolSchemas : undefined,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
this._callCount++;
|
|
64
|
+
this._tokenCount += (response.usage.input_tokens + response.usage.output_tokens);
|
|
65
|
+
costTracker.record({
|
|
66
|
+
model: llm.model(this.model),
|
|
67
|
+
inputTokens: response.usage.input_tokens,
|
|
68
|
+
outputTokens: response.usage.output_tokens,
|
|
69
|
+
sessionId: opts.sessionId || this.sessionId,
|
|
70
|
+
queryType: `agent_${this.name.toLowerCase()}`,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
if (response.stop_reason !== 'tool_use') break;
|
|
74
|
+
|
|
75
|
+
const toolUseBlocks = response.content.filter(b => b.type === 'tool_use');
|
|
76
|
+
if (!toolUseBlocks.length) break;
|
|
77
|
+
|
|
78
|
+
history.push({ role: 'assistant', content: response.content });
|
|
79
|
+
|
|
80
|
+
const toolResultContent = [];
|
|
81
|
+
for (const tu of toolUseBlocks) {
|
|
82
|
+
logger.info(`[${this.name}] → tool: ${tu.name}(${JSON.stringify(tu.input).slice(0, 80)})`);
|
|
83
|
+
const output = await toolRegistry.execute(tu.name, tu.input);
|
|
84
|
+
toolResults.push({ tool: tu.name, input: tu.input, output: output.slice(0, 500) });
|
|
85
|
+
toolResultContent.push({ type: 'tool_result', tool_use_id: tu.id, content: output });
|
|
86
|
+
}
|
|
87
|
+
history.push({ role: 'user', content: toolResultContent });
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const answer = (response?.content || [])
|
|
91
|
+
.filter(b => b.type === 'text').map(b => b.text).join('\n').trim();
|
|
92
|
+
|
|
93
|
+
MemoryManager.extractFromExchange(task, answer, opts.sessionId || this.sessionId).catch(() => { });
|
|
94
|
+
|
|
95
|
+
return { agent: this.name, answer, toolResults, loops, contextChunks: engineered.chunks.length, memoriesUsed: memories.length };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
reset() { this._history = []; }
|
|
99
|
+
getStats() { return { name: this.name, calls: this._callCount, tokens: this._tokenCount, historyLen: this._history.length }; }
|
|
100
|
+
|
|
101
|
+
_buildSystem(memContext, extra) {
|
|
102
|
+
return [`You are ${this.name}. ${this.role}`, `Ground every statement in evidence from tools or provided context.`, extra || '', memContext || ''].filter(Boolean).join('\n\n');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
_buildUserMessage(task, chunks) {
|
|
106
|
+
const ctx = chunks.length
|
|
107
|
+
? '\n\n## Codebase Context\n' + chunks.map((c, i) => `[${i + 1}] **${c.filename}** (${c.kind})\n\`\`\`\n${c.content.slice(0, 700)}\n\`\`\``).join('\n\n')
|
|
108
|
+
: '';
|
|
109
|
+
return `## Task\n${task}${ctx}`;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
module.exports = BaseAgent;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* Background cognitive agent. Runs on a timer.
|
|
4
|
+
* Five phases per dream cycle:
|
|
5
|
+
* 1. Consolidate memories (merge duplicates, resolve contradictions)
|
|
6
|
+
* 2. Pattern discovery (scan codebase for architectural patterns)
|
|
7
|
+
* 3. Proactive suggestions (surface improvements nobody asked for)
|
|
8
|
+
* 4. Knowledge graph building (connect related facts)
|
|
9
|
+
* 5. Pruning (remove stale/low-quality memories)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const llm = require('../utils/llmClient');
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const { MemoryManager, MEMORY_TYPES } = require('../memory/memoryManager');
|
|
16
|
+
const store = require('../storage/store');
|
|
17
|
+
const costTracker = require('../utils/costTracker');
|
|
18
|
+
const logger = require('../utils/logger');
|
|
19
|
+
|
|
20
|
+
const LOG_FILE = path.join(process.cwd(), 'data', 'dream-log.json');
|
|
21
|
+
|
|
22
|
+
class Dreamer {
|
|
23
|
+
constructor() {
|
|
24
|
+
this._running = false;
|
|
25
|
+
this._handle = null;
|
|
26
|
+
this._count = 0;
|
|
27
|
+
this._log = this._loadLog();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
_loadLog() {
|
|
31
|
+
try { if (fs.existsSync(LOG_FILE)) return JSON.parse(fs.readFileSync(LOG_FILE, 'utf-8')); } catch { }
|
|
32
|
+
return { dreams: [], lastDream: null };
|
|
33
|
+
}
|
|
34
|
+
_saveLog() { fs.writeFileSync(LOG_FILE, JSON.stringify(this._log, null, 2)); }
|
|
35
|
+
|
|
36
|
+
start(intervalMinutes = 30) {
|
|
37
|
+
if (this._running) return;
|
|
38
|
+
this._running = true;
|
|
39
|
+
logger.info(`[Dreamer] 💤 Started (every ${intervalMinutes}min)`);
|
|
40
|
+
setTimeout(() => this.dream(), 15000);
|
|
41
|
+
this._handle = setInterval(() => this.dream(), intervalMinutes * 60000);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
stop() {
|
|
45
|
+
if (this._handle) clearInterval(this._handle);
|
|
46
|
+
this._running = false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async dream() {
|
|
50
|
+
const t0 = Date.now();
|
|
51
|
+
this._count++;
|
|
52
|
+
logger.info(`[Dreamer] 💭 Dream #${this._count} starting...`);
|
|
53
|
+
|
|
54
|
+
const phases = {};
|
|
55
|
+
try { phases.consolidate = await this._consolidate(); } catch (e) { logger.warn(`[Dreamer] consolidate: ${e.message}`); }
|
|
56
|
+
try { phases.patterns = await this._discoverPatterns(); } catch (e) { logger.warn(`[Dreamer] patterns: ${e.message}`); }
|
|
57
|
+
try { phases.suggestions = await this._proactiveSuggestions(); } catch (e) { logger.warn(`[Dreamer] suggestions: ${e.message}`); }
|
|
58
|
+
try { phases.graph = await this._buildKnowledgeGraph(); } catch (e) { logger.warn(`[Dreamer] graph: ${e.message}`); }
|
|
59
|
+
phases.pruned = this._prune();
|
|
60
|
+
|
|
61
|
+
const record = {
|
|
62
|
+
id: this._count, ts: new Date().toISOString(),
|
|
63
|
+
ms: Date.now() - t0,
|
|
64
|
+
insights: (phases.patterns?.length || 0) + (phases.suggestions?.length || 0) + (phases.consolidate?.length || 0),
|
|
65
|
+
pruned: phases.pruned, phases: Object.keys(phases),
|
|
66
|
+
};
|
|
67
|
+
this._log.dreams.push(record);
|
|
68
|
+
this._log.lastDream = record.ts;
|
|
69
|
+
if (this._log.dreams.length > 50) this._log.dreams = this._log.dreams.slice(-50);
|
|
70
|
+
this._saveLog();
|
|
71
|
+
logger.info(`[Dreamer] ✨ Dream #${this._count} done: ${record.insights} insights, ${record.pruned} pruned in ${record.ms}ms`);
|
|
72
|
+
return record;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Phase 1 — consolidate duplicate / contradicting memories
|
|
76
|
+
async _consolidate() {
|
|
77
|
+
const mems = MemoryManager.list();
|
|
78
|
+
if (mems.length < 6) return [];
|
|
79
|
+
const byType = {};
|
|
80
|
+
for (const m of mems) (byType[m.type] = byType[m.type] || []).push(m);
|
|
81
|
+
const merged = [];
|
|
82
|
+
for (const [type, group] of Object.entries(byType)) {
|
|
83
|
+
if (group.length < 3) continue;
|
|
84
|
+
const snippet = group.slice(0, 8).map((m, i) => `[${i}] ${m.content}`).join('\n');
|
|
85
|
+
const r = await this._ask(`These are "${type}" memories. Find duplicates or contradictions worth merging/removing.\nReturn JSON: [{"action":"merge|remove","indices":[0,1],"merged":"new text if merging"}]. Return [] if nothing to do.\n${snippet}`);
|
|
86
|
+
const actions = this._parseJSON(r, []);
|
|
87
|
+
for (const a of actions) {
|
|
88
|
+
if (a.action === 'merge' && a.merged) {
|
|
89
|
+
for (const i of a.indices) { if (group[i]) MemoryManager.delete(group[i].id); }
|
|
90
|
+
merged.push(MemoryManager.add(a.merged, type, ['dream-consolidated']));
|
|
91
|
+
} else if (a.action === 'remove') {
|
|
92
|
+
for (const i of a.indices) { if (group[i]) MemoryManager.delete(group[i].id); }
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return merged;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Phase 2 — discover architectural / code patterns
|
|
100
|
+
async _discoverPatterns() {
|
|
101
|
+
const docs = store.getAll().filter(d => d.kind === 'code').slice(0, 20);
|
|
102
|
+
if (docs.length < 3) return [];
|
|
103
|
+
const sample = docs.map(d => `// ${d.filename}\n${d.content.slice(0, 250)}`).join('\n\n---\n\n');
|
|
104
|
+
const r = await this._ask(`Analyse these code samples. Find 2-3 important patterns (conventions, risks, anti-patterns).\nReturn JSON: [{"pattern":"...","type":"pattern|fact|decision","importance":"high|med|low"}]\nReturn [] if nothing notable.\n${sample}`);
|
|
105
|
+
const patterns = this._parseJSON(r, []);
|
|
106
|
+
const added = [];
|
|
107
|
+
for (const p of patterns.filter(p => p.pattern && p.importance !== 'low')) {
|
|
108
|
+
const exists = MemoryManager.getRelevant(p.pattern, 1);
|
|
109
|
+
if (!exists.length) added.push(MemoryManager.add(p.pattern, p.type || 'pattern', ['dream-pattern']));
|
|
110
|
+
}
|
|
111
|
+
return added;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Phase 3 — proactive suggestions nobody asked for
|
|
115
|
+
async _proactiveSuggestions() {
|
|
116
|
+
const recent = MemoryManager.list().filter(m => !m.tags?.includes('dream-')).sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)).slice(0, 12);
|
|
117
|
+
if (recent.length < 4) return [];
|
|
118
|
+
const summary = recent.map(m => m.content.slice(0, 80)).join('\n');
|
|
119
|
+
const r = await this._ask(`Based on recent developer activity, generate 1-2 proactive improvements NOT yet mentioned.\nReturn JSON: [{"suggestion":"...","rationale":"...","priority":"high|med"}]\nReturn [] if nothing to add.\nRecent activity:\n${summary}`);
|
|
120
|
+
const sug = this._parseJSON(r, []);
|
|
121
|
+
return sug.filter(s => s.suggestion && s.priority !== 'low')
|
|
122
|
+
.map(s => MemoryManager.add(`[Proactive] ${s.suggestion} — ${s.rationale}`, MEMORY_TYPES.DECISION, ['dream-suggestion', s.priority]));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Phase 4 — build connections between related memories
|
|
126
|
+
async _buildKnowledgeGraph() {
|
|
127
|
+
const mems = MemoryManager.list().slice(0, 20);
|
|
128
|
+
if (mems.length < 5) return [];
|
|
129
|
+
const list = mems.map((m, i) => `[${i}] (${m.type}) ${m.content.slice(0, 70)}`).join('\n');
|
|
130
|
+
const r = await this._ask(`Find 2-3 non-obvious connections between these facts that would help a developer.\nReturn JSON: [{"connection":"...","indices":[0,1]}]\nReturn [].\n${list}`);
|
|
131
|
+
const links = this._parseJSON(r, []);
|
|
132
|
+
return links.filter(l => l.connection)
|
|
133
|
+
.map(l => MemoryManager.add(`[Connection] ${l.connection}`, MEMORY_TYPES.PATTERN, ['dream-graph']));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Phase 5 — prune stale/low-quality memories
|
|
137
|
+
_prune() {
|
|
138
|
+
const mems = MemoryManager.list();
|
|
139
|
+
let pruned = 0;
|
|
140
|
+
for (const m of mems) {
|
|
141
|
+
const ageDays = (Date.now() - new Date(m.createdAt).getTime()) / 86400000;
|
|
142
|
+
const stale = ageDays > 30 && m.useCount === 0;
|
|
143
|
+
const short = m.content.length < 12;
|
|
144
|
+
const oldAuto = m.tags?.includes('dream-') && ageDays > 14 && m.useCount === 0;
|
|
145
|
+
if (stale || short || oldAuto) { MemoryManager.delete(m.id); pruned++; }
|
|
146
|
+
}
|
|
147
|
+
if (pruned) logger.info(`[Dreamer] Pruned ${pruned} stale memories`);
|
|
148
|
+
return pruned;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async _ask(prompt) {
|
|
152
|
+
const r = await llm.chat({ model: llm.model('fast'), max_tokens: 400, messages: [{ role: 'user', content: prompt }] });
|
|
153
|
+
costTracker.record({ model: llm.model('fast'), inputTokens: r.usage.input_tokens, outputTokens: r.usage.output_tokens, sessionId: 'dreamer', queryType: 'dream' });
|
|
154
|
+
return r.content[0].text;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
_parseJSON(text, fallback) {
|
|
158
|
+
try { return JSON.parse(text.replace(/```json\n?|\n?```/g, '').trim()); } catch { return fallback; }
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async dreamNow() { return this.dream(); }
|
|
162
|
+
getStatus() { return { running: this._running, count: this._count, lastDream: this._log.lastDream, recent: this._log.dreams.slice(-5) }; }
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
module.exports = new Dreamer();
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Continuous improvement without explicit prompts.
|
|
3
|
+
* Tracks which answers were helpful, learns from patterns in queries,
|
|
4
|
+
* and adapts retrieval weights over time.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const logger = require('../utils/logger');
|
|
10
|
+
const { MemoryManager } = require('../memory/memoryManager');
|
|
11
|
+
|
|
12
|
+
const FEEDBACK_FILE = path.join(process.cwd(), 'data', 'feedback.json');
|
|
13
|
+
const QUERY_STATS_FILE = path.join(process.cwd(), 'data', 'query-stats.json');
|
|
14
|
+
|
|
15
|
+
class Improver {
|
|
16
|
+
constructor() {
|
|
17
|
+
this._feedback = this._load(FEEDBACK_FILE, { entries: [], stats: {} });
|
|
18
|
+
this._queryStats = this._load(QUERY_STATS_FILE, {
|
|
19
|
+
totalQueries: 0,
|
|
20
|
+
byMode: {},
|
|
21
|
+
commonTerms: {},
|
|
22
|
+
sourceEffectiveness: {}, // filePath → helpfulness score
|
|
23
|
+
retrieval: { avgChunks: 0, avgScore: 0 },
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
_load(file, def) {
|
|
28
|
+
try { if (fs.existsSync(file)) return JSON.parse(fs.readFileSync(file, 'utf-8')); } catch { }
|
|
29
|
+
return def;
|
|
30
|
+
}
|
|
31
|
+
_save(file, data) { fs.writeFileSync(file, JSON.stringify(data, null, 2)); }
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Record feedback on an answer (thumbs up/down + optional text)
|
|
35
|
+
*/
|
|
36
|
+
recordFeedback(queryId, rating, comment = '') {
|
|
37
|
+
const entry = {
|
|
38
|
+
queryId,
|
|
39
|
+
rating, // 1 = helpful, -1 = not helpful, 0 = neutral
|
|
40
|
+
comment,
|
|
41
|
+
timestamp: new Date().toISOString(),
|
|
42
|
+
};
|
|
43
|
+
this._feedback.entries.push(entry);
|
|
44
|
+
|
|
45
|
+
// Update stats
|
|
46
|
+
this._feedback.stats.total = (this._feedback.stats.total || 0) + 1;
|
|
47
|
+
this._feedback.stats.positive = (this._feedback.stats.positive || 0) + (rating > 0 ? 1 : 0);
|
|
48
|
+
this._feedback.stats.negative = (this._feedback.stats.negative || 0) + (rating < 0 ? 1 : 0);
|
|
49
|
+
|
|
50
|
+
this._save(FEEDBACK_FILE, this._feedback);
|
|
51
|
+
logger.info(`[Improver] Feedback recorded: ${rating > 0 ? '👍' : rating < 0 ? '👎' : '😐'} for ${queryId}`);
|
|
52
|
+
|
|
53
|
+
// Trigger learning if we have enough feedback
|
|
54
|
+
if (this._feedback.entries.length % 10 === 0) {
|
|
55
|
+
this._learnFromFeedback().catch(() => { });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return entry;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Record a query event for pattern learning
|
|
63
|
+
*/
|
|
64
|
+
recordQuery({ queryId, question, mode, sources, answer, durationMs }) {
|
|
65
|
+
this._queryStats.totalQueries++;
|
|
66
|
+
|
|
67
|
+
// Track mode frequency
|
|
68
|
+
this._queryStats.byMode[mode] = (this._queryStats.byMode[mode] || 0) + 1;
|
|
69
|
+
|
|
70
|
+
// Track term frequency (for improving retrieval)
|
|
71
|
+
const terms = question.toLowerCase().split(/\W+/).filter(t => t.length > 3);
|
|
72
|
+
for (const term of terms) {
|
|
73
|
+
this._queryStats.commonTerms[term] = (this._queryStats.commonTerms[term] || 0) + 1;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Track which sources are frequently retrieved (= probably important files)
|
|
77
|
+
if (sources) {
|
|
78
|
+
for (const src of sources) {
|
|
79
|
+
if (!this._queryStats.sourceEffectiveness[src.path]) {
|
|
80
|
+
this._queryStats.sourceEffectiveness[src.path] = { retrievals: 0, helpfulCount: 0 };
|
|
81
|
+
}
|
|
82
|
+
this._queryStats.sourceEffectiveness[src.path].retrievals++;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Running average for chunk stats
|
|
87
|
+
const n = this._queryStats.totalQueries;
|
|
88
|
+
const srcCount = sources?.length || 0;
|
|
89
|
+
this._queryStats.retrieval.avgChunks =
|
|
90
|
+
((this._queryStats.retrieval.avgChunks * (n - 1)) + srcCount) / n;
|
|
91
|
+
|
|
92
|
+
this._save(QUERY_STATS_FILE, this._queryStats);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get the most frequently retrieved (therefore most important) files
|
|
97
|
+
* Used to boost their ingestion priority
|
|
98
|
+
*/
|
|
99
|
+
getHotFiles(limit = 10) {
|
|
100
|
+
return Object.entries(this._queryStats.sourceEffectiveness)
|
|
101
|
+
.sort(([, a], [, b]) => b.retrievals - a.retrievals)
|
|
102
|
+
.slice(0, limit)
|
|
103
|
+
.map(([path, stats]) => ({ path, ...stats }));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Get the most common query terms — useful for seeing what devs struggle with
|
|
108
|
+
*/
|
|
109
|
+
getTopTerms(limit = 20) {
|
|
110
|
+
return Object.entries(this._queryStats.commonTerms)
|
|
111
|
+
.sort(([, a], [, b]) => b - a)
|
|
112
|
+
.slice(0, limit)
|
|
113
|
+
.map(([term, count]) => ({ term, count }));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Learn from accumulated feedback — update memory weights and system config
|
|
118
|
+
*/
|
|
119
|
+
async _learnFromFeedback() {
|
|
120
|
+
const recent = this._feedback.entries.slice(-20);
|
|
121
|
+
const negatives = recent.filter(e => e.rating < 0);
|
|
122
|
+
|
|
123
|
+
if (negatives.length < 3) return;
|
|
124
|
+
|
|
125
|
+
logger.info(`[Improver] Learning from ${negatives.length} negative feedbacks`);
|
|
126
|
+
|
|
127
|
+
// Add a memory about what kinds of answers aren't working
|
|
128
|
+
if (negatives.length >= 3) {
|
|
129
|
+
const comments = negatives
|
|
130
|
+
.filter(e => e.comment)
|
|
131
|
+
.map(e => e.comment)
|
|
132
|
+
.join('; ');
|
|
133
|
+
|
|
134
|
+
if (comments) {
|
|
135
|
+
MemoryManager.add(
|
|
136
|
+
`Improvement needed: users found answers unhelpful in these areas: ${comments}`,
|
|
137
|
+
'preference',
|
|
138
|
+
['improver-feedback']
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Get a dashboard summary of system health & improvement metrics
|
|
146
|
+
*/
|
|
147
|
+
getSummary() {
|
|
148
|
+
const total = this._feedback.stats.total || 0;
|
|
149
|
+
const positive = this._feedback.stats.positive || 0;
|
|
150
|
+
const negative = this._feedback.stats.negative || 0;
|
|
151
|
+
const satisfactionRate = total > 0 ? ((positive / total) * 100).toFixed(1) : 'N/A';
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
queries: {
|
|
155
|
+
total: this._queryStats.totalQueries,
|
|
156
|
+
byMode: this._queryStats.byMode,
|
|
157
|
+
avgContextChunks: this._queryStats.retrieval.avgChunks.toFixed(1),
|
|
158
|
+
},
|
|
159
|
+
feedback: {
|
|
160
|
+
total,
|
|
161
|
+
positive,
|
|
162
|
+
negative,
|
|
163
|
+
satisfactionRate: total > 0 ? `${satisfactionRate}%` : 'No feedback yet',
|
|
164
|
+
},
|
|
165
|
+
topTerms: this.getTopTerms(10),
|
|
166
|
+
hotFiles: this.getHotFiles(5),
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
getFeedbackStats() {
|
|
171
|
+
return this._feedback.stats;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
module.exports = new Improver();
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* 10 specialist agents, each with a curated toolset and domain-expert system prompt.
|
|
4
|
+
* Built on BaseAgent's real Anthropic tool-use loop.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const BaseAgent = require('./BaseAgent');
|
|
8
|
+
|
|
9
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
10
|
+
class DebugAgent extends BaseAgent {
|
|
11
|
+
constructor() {
|
|
12
|
+
super({
|
|
13
|
+
name: 'DebugAgent',
|
|
14
|
+
role: 'Expert debugger. Finds exact root causes, traces execution flows, provides verified fixes.',
|
|
15
|
+
toolNames: ['bash', 'file_read', 'grep', 'kb_search', 'memory_search', 'git_diff', 'log_analyze', 'symbol_navigate', 'think', 'token_count'],
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
_buildSystem(m, e) {
|
|
19
|
+
return [`You are DebugAgent, a world-class debugger.`,
|
|
20
|
+
`PROCESS: 1) think() first 2) kb_search + grep to find relevant code 3) trace the exact flow 4) pinpoint root cause 5) provide fix with code`,
|
|
21
|
+
`Never guess. Cite exact file:line. Format: Root Cause → Affected Flow → Fix → Verify`,
|
|
22
|
+
m, e].filter(Boolean).join('\n\n');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
27
|
+
class ArchitectureAgent extends BaseAgent {
|
|
28
|
+
constructor() {
|
|
29
|
+
super({
|
|
30
|
+
name: 'ArchitectureAgent',
|
|
31
|
+
role: 'System design expert. Analyses module relationships, coupling, and recommends structural improvements.',
|
|
32
|
+
toolNames: ['file_read', 'dir_list', 'grep', 'kb_search', 'find_files', 'symbol_navigate', 'generate_diagram', 'code_complexity', 'dependency_analysis', 'think'],
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
_buildSystem(m, e) {
|
|
36
|
+
return [`You are ArchitectureAgent, a senior system architect.`,
|
|
37
|
+
`Analyse: module coupling, separation of concerns, scalability, testability, maintainability.`,
|
|
38
|
+
`Use generate_diagram to produce Mermaid diagrams. Use dependency_analysis for deps.`,
|
|
39
|
+
`Format: ## Current Structure → ## Coupling Issues → ## Diagram → ## Recommendations → ## Migration Path`,
|
|
40
|
+
m, e].filter(Boolean).join('\n\n');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
45
|
+
class SecurityAgent extends BaseAgent {
|
|
46
|
+
constructor() {
|
|
47
|
+
super({
|
|
48
|
+
name: 'SecurityAgent',
|
|
49
|
+
role: 'Security auditor. Finds vulnerabilities, injection risks, auth flaws, and insecure patterns.',
|
|
50
|
+
toolNames: ['bash', 'file_read', 'grep', 'kb_search', 'api_test', 'env_read', 'regex_test', 'log_analyze', 'think', 'memory_search'],
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
_buildSystem(m, e) {
|
|
54
|
+
return [`You are SecurityAgent, an OWASP-certified security auditor.`,
|
|
55
|
+
`Scan for: SQLi, NoSQLi, XSS, CSRF, IDOR, path traversal, hardcoded secrets, weak auth, insecure deserialization, RCE vectors.`,
|
|
56
|
+
`Use grep to find patterns. Use env_read to check secret handling.`,
|
|
57
|
+
`For each finding: CRITICAL/HIGH/MEDIUM/LOW | File:Line | Exploit scenario | Fix code`,
|
|
58
|
+
m, e].filter(Boolean).join('\n\n');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
63
|
+
class DocumentationAgent extends BaseAgent {
|
|
64
|
+
constructor() {
|
|
65
|
+
super({
|
|
66
|
+
name: 'DocumentationAgent',
|
|
67
|
+
role: 'Technical writer. Generates accurate JSDoc, READMEs, API docs, and architecture diagrams.',
|
|
68
|
+
toolNames: ['file_read', 'kb_search', 'symbol_navigate', 'grep', 'generate_diagram', 'generate_changelog', 'find_files', 'memory_search', 'think'],
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
_buildSystem(m, e) {
|
|
72
|
+
return [`You are DocumentationAgent, a technical writer.`,
|
|
73
|
+
`Generate documentation that is: accurate (from real code), concise (no padding), developer-first.`,
|
|
74
|
+
`For functions: JSDoc @param @returns @throws @example. For APIs: method/endpoint/schema/errors/curl.`,
|
|
75
|
+
`Use generate_diagram for architecture diagrams. Only document what you can verify in code.`,
|
|
76
|
+
m, e].filter(Boolean).join('\n\n');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
81
|
+
class RefactorAgent extends BaseAgent {
|
|
82
|
+
constructor() {
|
|
83
|
+
super({
|
|
84
|
+
name: 'RefactorAgent',
|
|
85
|
+
role: 'Code quality engineer. Eliminates duplication, improves readability, applies modern patterns.',
|
|
86
|
+
toolNames: ['file_read', 'file_edit', 'grep', 'kb_search', 'code_complexity', 'lint', 'format_code', 'symbol_navigate', 'think', 'text_diff'],
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
_buildSystem(m, e) {
|
|
90
|
+
return [`You are RefactorAgent, a code quality expert.`,
|
|
91
|
+
`Use code_complexity + lint to find issues. Use grep to find duplication.`,
|
|
92
|
+
`Show before/after diffs. Explain WHY each change improves the code.`,
|
|
93
|
+
`Never change behaviour — only structure, readability, maintainability.`,
|
|
94
|
+
`Format: ## Issues Found → ## Refactored Code → ## What Changed & Why`,
|
|
95
|
+
m, e].filter(Boolean).join('\n\n');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
100
|
+
class PerformanceAgent extends BaseAgent {
|
|
101
|
+
constructor() {
|
|
102
|
+
super({
|
|
103
|
+
name: 'PerformanceAgent',
|
|
104
|
+
role: 'Performance engineer. Finds N+1 queries, memory leaks, blocking I/O, and bottlenecks.',
|
|
105
|
+
toolNames: ['file_read', 'grep', 'kb_search', 'code_complexity', 'log_analyze', 'system_info', 'run_tests', 'bash', 'think', 'memory_search'],
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
_buildSystem(m, e) {
|
|
109
|
+
return [`You are PerformanceAgent, a performance engineering specialist.`,
|
|
110
|
+
`Hunt for: N+1 DB queries, sync ops that should be async, unnecessary data loading, memory leaks, blocking I/O.`,
|
|
111
|
+
`Use log_analyze to find slow queries. Use system_info for runtime metrics.`,
|
|
112
|
+
`For each issue: impact (HIGH/MED/LOW) | root cause | specific fix with code | expected gain`,
|
|
113
|
+
m, e].filter(Boolean).join('\n\n');
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
118
|
+
class TestAgent extends BaseAgent {
|
|
119
|
+
constructor() {
|
|
120
|
+
super({
|
|
121
|
+
name: 'TestAgent',
|
|
122
|
+
role: 'QA engineer. Writes tests, runs test suites, analyses coverage, finds untested code paths.',
|
|
123
|
+
toolNames: ['file_read', 'file_write', 'run_tests', 'grep', 'kb_search', 'mock_generate', 'code_complexity', 'find_files', 'symbol_navigate', 'think'],
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
_buildSystem(m, e) {
|
|
127
|
+
return [`You are TestAgent, an expert QA engineer.`,
|
|
128
|
+
`Use run_tests to check current state. Use grep to find untested code. Use mock_generate for fixtures.`,
|
|
129
|
+
`Write tests that are: deterministic, isolated, fast, readable.`,
|
|
130
|
+
`Generate Jest tests. Cover: happy path, edge cases (null/empty/boundary), error cases, mocked deps.`,
|
|
131
|
+
m, e].filter(Boolean).join('\n\n');
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
136
|
+
class DevOpsAgent extends BaseAgent {
|
|
137
|
+
constructor() {
|
|
138
|
+
super({
|
|
139
|
+
name: 'DevOpsAgent',
|
|
140
|
+
role: 'DevOps engineer. Manages CI/CD, Docker, deployments, infrastructure, and environment config.',
|
|
141
|
+
toolNames: ['bash', 'docker', 'system_info', 'process_info', 'network_check', 'env_read', 'npm_info', 'log_analyze', 'file_read', 'git_status'],
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
_buildSystem(m, e) {
|
|
145
|
+
return [`You are DevOpsAgent, a DevOps/SRE engineer.`,
|
|
146
|
+
`Use docker for container management. Use system_info for resource monitoring.`,
|
|
147
|
+
`Diagnose infrastructure issues. Suggest CI/CD improvements. Review Dockerfiles and config.`,
|
|
148
|
+
`Format: ## Current State → ## Issues → ## Fixes → ## Prevention`,
|
|
149
|
+
m, e].filter(Boolean).join('\n\n');
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
154
|
+
class DataAgent extends BaseAgent {
|
|
155
|
+
constructor() {
|
|
156
|
+
super({
|
|
157
|
+
name: 'DataAgent',
|
|
158
|
+
role: 'Data engineer. Analyses data structures, queries, schemas, and data pipelines.',
|
|
159
|
+
toolNames: ['file_read', 'json_query', 'json_transform', 'schema_validate', 'grep', 'kb_search', 'regex_test', 'crypto_hash', 'mock_generate', 'think'],
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
_buildSystem(m, e) {
|
|
163
|
+
return [`You are DataAgent, a data engineering specialist.`,
|
|
164
|
+
`Analyse: data models, JSON structures, API payloads, DB schemas, transformations.`,
|
|
165
|
+
`Use json_query + json_transform to explore data. Use schema_validate to check contracts.`,
|
|
166
|
+
`Spot: missing validation, unsafe transformations, data inconsistencies, schema drift.`,
|
|
167
|
+
m, e].filter(Boolean).join('\n\n');
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
172
|
+
class PlannerAgent extends BaseAgent {
|
|
173
|
+
constructor() {
|
|
174
|
+
super({
|
|
175
|
+
name: 'PlannerAgent',
|
|
176
|
+
role: 'Project planner. Breaks down complex tasks, estimates effort, identifies risks and dependencies.',
|
|
177
|
+
toolNames: ['kb_search', 'memory_search', 'task_manage', 'git_log', 'grep', 'symbol_navigate', 'generate_diagram', 'think', 'token_count', 'dependency_analysis'],
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
_buildSystem(m, e) {
|
|
181
|
+
return [`You are PlannerAgent, an expert project planner.`,
|
|
182
|
+
`Use task_manage to track action items. Use kb_search to understand the codebase before planning.`,
|
|
183
|
+
`Produce: numbered steps, effort estimate, dependencies, risk rating, rollback strategy.`,
|
|
184
|
+
`Format: ## Understanding → ## Plan (numbered) → ## Dependencies → ## Risks → ## Rollback`,
|
|
185
|
+
m, e].filter(Boolean).join('\n\n');
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
190
|
+
|
|
191
|
+
module.exports = {
|
|
192
|
+
DebugAgent: new DebugAgent(),
|
|
193
|
+
ArchitectureAgent: new ArchitectureAgent(),
|
|
194
|
+
SecurityAgent: new SecurityAgent(),
|
|
195
|
+
DocumentationAgent: new DocumentationAgent(),
|
|
196
|
+
RefactorAgent: new RefactorAgent(),
|
|
197
|
+
PerformanceAgent: new PerformanceAgent(),
|
|
198
|
+
TestAgent: new TestAgent(),
|
|
199
|
+
DevOpsAgent: new DevOpsAgent(),
|
|
200
|
+
DataAgent: new DataAgent(),
|
|
201
|
+
PlannerAgent: new PlannerAgent(),
|
|
202
|
+
};
|