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,202 @@
1
+ /**
2
+ * Plan Mode: Before executing a complex change, generate a step-by-step plan
3
+ * and get user approval before proceeding.
4
+ *
5
+ * Compact: Compress long conversation history into a dense summary
6
+ * to stay within context limits.
7
+ */
8
+
9
+ const llm = require('../utils/llmClient');
10
+ const costTracker = require('../utils/costTracker');
11
+ const logger = require('../utils/logger');
12
+
13
+ class PlannerEngine {
14
+ /**
15
+ * Generate an execution plan for a complex task.
16
+ * This is the "think before you act" pattern.
17
+ *
18
+ * @param {string} task - What the user wants to do
19
+ * @param {Array} context - Retrieved codebase chunks
20
+ * @param {string} sessionId
21
+ */
22
+ async generatePlan(task, context = [], sessionId = 'default') {
23
+ const contextStr = context.length > 0
24
+ ? `\n\n## Relevant Codebase Context\n${context.map(c => `**${c.filename}**:\n${c.content.slice(0, 800)}`).join('\n\n---\n\n')}`
25
+ : '';
26
+
27
+ const response = await llm.chat({
28
+ model: llm.model('smart'),
29
+ max_tokens: 1500,
30
+ system: `You are a senior developer creating an execution plan. Be specific, step-by-step, and honest about risks.
31
+ Format the plan as:
32
+
33
+ ## Understanding
34
+ (What you understand about the task and codebase context)
35
+
36
+ ## Plan
37
+ Step 1: [action] — [file/component affected] — [risk: low|medium|high]
38
+ Step 2: ...
39
+ ...
40
+
41
+ ## Prerequisites
42
+ (Anything that needs to happen before starting)
43
+
44
+ ## Risks & Rollback
45
+ (What could go wrong and how to undo it)
46
+
47
+ ## Estimated Effort
48
+ (Quick estimate: minutes/hours)`,
49
+ messages: [{
50
+ role: 'user',
51
+ content: `Create an execution plan for this task:\n\n${task}${contextStr}`,
52
+ }],
53
+ });
54
+
55
+ costTracker.record({
56
+ model: llm.model('smart'),
57
+ inputTokens: response.usage.input_tokens,
58
+ outputTokens: response.usage.output_tokens,
59
+ sessionId,
60
+ queryType: 'plan',
61
+ });
62
+
63
+ return {
64
+ task,
65
+ plan: response.content[0].text,
66
+ generatedAt: new Date().toISOString(),
67
+ approved: false,
68
+ };
69
+ }
70
+
71
+ /**
72
+ * Compact a conversation history into a dense summary.
73
+ * Reduces token usage by replacing old messages with a compressed summary.
74
+ *
75
+ * @param {Array} messages - Array of { role, content } message objects
76
+ * @param {string} sessionId
77
+ */
78
+ async compact(messages, sessionId = 'default') {
79
+ if (!messages || messages.length < 4) {
80
+ return { compacted: false, reason: 'Too few messages to compact', messages };
81
+ }
82
+
83
+ // Keep last 2 messages verbatim, compact the rest
84
+ const toCompact = messages.slice(0, -2);
85
+ const toKeep = messages.slice(-2);
86
+
87
+ const historyText = toCompact.map(m =>
88
+ `${m.role.toUpperCase()}: ${m.content?.slice(0, 500) || ''}`
89
+ ).join('\n\n');
90
+
91
+ const response = await llm.chat({
92
+ model: llm.model('fast'),
93
+ max_tokens: 800,
94
+ messages: [{
95
+ role: 'user',
96
+ content: `Compress this conversation history into a dense, information-preserving summary that captures:
97
+ 1. What was asked and answered
98
+ 2. Key facts discovered about the codebase
99
+ 3. Any decisions made or actions taken
100
+ 4. Open questions or unresolved issues
101
+
102
+ Keep it under 400 words. Be specific (include file names, function names, error types).
103
+
104
+ History to compress:
105
+ ${historyText}`,
106
+ }],
107
+ });
108
+
109
+ costTracker.record({
110
+ model: llm.model('fast'),
111
+ inputTokens: response.usage.input_tokens,
112
+ outputTokens: response.usage.output_tokens,
113
+ sessionId,
114
+ queryType: 'compact',
115
+ });
116
+
117
+ const summary = response.content[0].text;
118
+ const compactedMessage = {
119
+ role: 'system',
120
+ content: `[Compacted conversation summary]\n${summary}`,
121
+ compacted: true,
122
+ originalCount: toCompact.length,
123
+ compactedAt: new Date().toISOString(),
124
+ };
125
+
126
+ const compactedMessages = [compactedMessage, ...toKeep];
127
+
128
+ logger.info(`[Compact] ${messages.length} messages → ${compactedMessages.length} (${toCompact.length} compacted)`);
129
+
130
+ return {
131
+ compacted: true,
132
+ originalCount: messages.length,
133
+ newCount: compactedMessages.length,
134
+ savedMessages: messages.length - compactedMessages.length,
135
+ messages: compactedMessages,
136
+ summary,
137
+ };
138
+ }
139
+
140
+ /**
141
+ * Doctor — check environment health
142
+ */
143
+ async doctor() {
144
+ const checks = [];
145
+ const BashTool = require('../tools/BashTool');
146
+
147
+ const run = async (name, cmd, parse) => {
148
+ try {
149
+ const r = await BashTool.executeOrThrow(cmd, {});
150
+ checks.push({ name, status: 'ok', detail: parse ? parse(r.stdout) : r.stdout.trim() });
151
+ } catch (err) {
152
+ checks.push({ name, status: 'fail', detail: err.message.slice(0, 100) });
153
+ }
154
+ };
155
+
156
+ await run('Node.js', 'node --version', v => v.trim());
157
+ await run('npm', 'npm --version', v => `v${v.trim()}`);
158
+ await run('git', 'git --version', v => v.trim());
159
+ await run('ripgrep', 'rg --version', v => v.split('\n')[0]);
160
+
161
+ // Check API key
162
+ // checks.push({
163
+ // name: 'ANTHROPIC_API_KEY',
164
+ // status: process.env.ANTHROPIC_API_KEY ? 'ok' : 'fail',
165
+ // detail: process.env.ANTHROPIC_API_KEY
166
+ // ? `Set (${process.env.ANTHROPIC_API_KEY.slice(0, 10)}...)`
167
+ // : 'Not set — add to .env',
168
+ // });
169
+
170
+ // Check data directory
171
+ const fs = require('fs');
172
+ const dataDir = require('path').join(process.cwd(), 'data');
173
+ checks.push({
174
+ name: 'data/ directory',
175
+ status: fs.existsSync(dataDir) ? 'ok' : 'warn',
176
+ detail: fs.existsSync(dataDir) ? 'Exists' : 'Will be created on first ingest',
177
+ });
178
+
179
+ // Knowledge base
180
+ const store = require('../storage/store');
181
+ const stats = store.getStats();
182
+ checks.push({
183
+ name: 'Knowledge base',
184
+ status: stats.totalDocs > 0 ? 'ok' : 'warn',
185
+ detail: stats.totalDocs > 0
186
+ ? `${stats.totalDocs} docs from ${stats.totalFiles} files`
187
+ : 'Empty — run: node cli.js ingest <path>',
188
+ });
189
+
190
+ const passed = checks.filter(c => c.status === 'ok').length;
191
+ const failed = checks.filter(c => c.status === 'fail').length;
192
+ const warned = checks.filter(c => c.status === 'warn').length;
193
+
194
+ return {
195
+ checks,
196
+ summary: { passed, failed, warned, total: checks.length },
197
+ healthy: failed === 0,
198
+ };
199
+ }
200
+ }
201
+
202
+ module.exports = new PlannerEngine();
@@ -0,0 +1,29 @@
1
+ 'use strict';
2
+ /**
3
+ * Built-in plugin: stats-plugin
4
+ * Adds /api/stats/overview — a single endpoint that aggregates everything.
5
+ */
6
+ module.exports = {
7
+ name: 'stats-plugin',
8
+ version: '1.0.0',
9
+ description: 'Adds /api/stats/overview endpoint aggregating all system stats',
10
+ async register(app, { store, toolRegistry }) {
11
+ app.get('/api/stats/overview', async (req, res) => {
12
+ try {
13
+ const kbStats = store ? store.getStats() : {};
14
+ const { MemoryManager } = require('../../memory/memoryManager');
15
+ const { TaskManager } = require('../../tasks/taskManager');
16
+ const costTracker = require('../../utils/costTracker');
17
+ res.json({
18
+ version: '1.0.0',
19
+ knowledgeBase: kbStats,
20
+ memory: MemoryManager.getStats(),
21
+ tasks: TaskManager.getStats(),
22
+ cost: costTracker.getSummary(),
23
+ tools: toolRegistry ? toolRegistry.count : 0,
24
+ uptime: process.uptime(),
25
+ });
26
+ } catch (e) { res.status(500).json({ error: e.message }); }
27
+ });
28
+ },
29
+ };
@@ -0,0 +1,144 @@
1
+ 'use strict';
2
+ /**
3
+ * A plugin is a plain Node module that exports:
4
+ * { name, version, description, register(app, registry) }
5
+ *
6
+ * register() receives:
7
+ * - app : Express app (add routes)
8
+ * - toolRegistry : add custom tools
9
+ * - app._mcpCtx : shared context (store, indexer, memory, etc.)
10
+ *
11
+ * Built-in plugins ship in src/plugins/builtin/.
12
+ * User plugins drop into <cwd>/plugins/.
13
+ */
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+ const logger = require('../utils/logger');
18
+
19
+ const BUILTIN_DIR = path.join(__dirname, 'builtin');
20
+ const USER_DIR = path.join(process.cwd(), 'plugins');
21
+ const STATE_FILE = path.join(process.cwd(), 'data', 'plugins-state.json');
22
+
23
+ class PluginManager {
24
+ constructor() {
25
+ this._plugins = new Map(); // name → { meta, enabled, instance }
26
+ this._state = this._loadState();
27
+ this._app = null;
28
+ this._ctx = null;
29
+ }
30
+
31
+ _loadState() {
32
+ try { if (fs.existsSync(STATE_FILE)) return JSON.parse(fs.readFileSync(STATE_FILE, 'utf-8')); } catch { }
33
+ return { disabled: [] };
34
+ }
35
+ _saveState() { fs.writeFileSync(STATE_FILE, JSON.stringify(this._state, null, 2)); }
36
+
37
+ /**
38
+ * Load and register all plugins. Called once during server boot.
39
+ * @param {Express} app
40
+ * @param {object} ctx — { toolRegistry, store, indexer, memoryManager, ... }
41
+ */
42
+ async loadAll(app, ctx = {}) {
43
+ this._app = app;
44
+ this._ctx = ctx;
45
+
46
+ // Load built-in plugins first
47
+ if (fs.existsSync(BUILTIN_DIR)) {
48
+ for (const file of fs.readdirSync(BUILTIN_DIR).filter(f => f.endsWith('.js'))) {
49
+ await this._load(path.join(BUILTIN_DIR, file), true);
50
+ }
51
+ }
52
+
53
+ // Load user plugins
54
+ if (fs.existsSync(USER_DIR)) {
55
+ for (const file of fs.readdirSync(USER_DIR).filter(f => f.endsWith('.js'))) {
56
+ await this._load(path.join(USER_DIR, file), false);
57
+ }
58
+ }
59
+
60
+ logger.info(`[Plugins] Loaded ${this._plugins.size} plugin(s): ${[...this._plugins.keys()].join(', ') || 'none'}`);
61
+ }
62
+
63
+ async _load(filePath, builtIn = false) {
64
+ try {
65
+ const mod = require(filePath);
66
+ const name = mod.name || path.basename(filePath, '.js');
67
+ const enabled = !this._state.disabled.includes(name);
68
+
69
+ const entry = {
70
+ name,
71
+ version: mod.version || '1.0.0',
72
+ description: mod.description || '',
73
+ filePath,
74
+ builtIn,
75
+ enabled,
76
+ loadedAt: new Date().toISOString(),
77
+ error: null,
78
+ };
79
+
80
+ if (enabled && typeof mod.register === 'function') {
81
+ try {
82
+ await mod.register(this._app, {
83
+ toolRegistry: this._ctx.toolRegistry,
84
+ ...this._ctx,
85
+ });
86
+ logger.info(`[Plugins] ✓ ${name}${builtIn ? ' (built-in)' : ''}`);
87
+ } catch (err) {
88
+ entry.error = err.message;
89
+ logger.error(`[Plugins] ✗ ${name}: ${err.message}`);
90
+ }
91
+ }
92
+
93
+ this._plugins.set(name, entry);
94
+ } catch (err) {
95
+ logger.warn(`[Plugins] Could not load ${filePath}: ${err.message}`);
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Load a single plugin by file path (hot-load at runtime)
101
+ */
102
+ async loadPlugin(filePath) {
103
+ delete require.cache[require.resolve(filePath)];
104
+ await this._load(filePath);
105
+ return this.get(path.basename(filePath, '.js'));
106
+ }
107
+
108
+ enable(name) {
109
+ const p = this._plugins.get(name);
110
+ if (!p) throw new Error(`Plugin not found: ${name}`);
111
+ this._state.disabled = this._state.disabled.filter(n => n !== name);
112
+ p.enabled = true;
113
+ this._saveState();
114
+ return p;
115
+ }
116
+
117
+ disable(name) {
118
+ const p = this._plugins.get(name);
119
+ if (!p) throw new Error(`Plugin not found: ${name}`);
120
+ if (!this._state.disabled.includes(name)) this._state.disabled.push(name);
121
+ p.enabled = false;
122
+ this._saveState();
123
+ logger.info(`[Plugins] Disabled: ${name} (restart to fully unload)`);
124
+ return p;
125
+ }
126
+
127
+ list() {
128
+ return [...this._plugins.values()].map(({ filePath, ...rest }) => rest);
129
+ }
130
+
131
+ get(name) {
132
+ const p = this._plugins.get(name);
133
+ if (!p) return null;
134
+ const { filePath, ...rest } = p;
135
+ return rest;
136
+ }
137
+
138
+ getStats() {
139
+ const all = this.list();
140
+ return { total: all.length, enabled: all.filter(p => p.enabled).length, disabled: all.filter(p => !p.enabled).length, errors: all.filter(p => p.error).length };
141
+ }
142
+ }
143
+
144
+ module.exports = new PluginManager();
@@ -0,0 +1,289 @@
1
+ 'use strict';
2
+ /**
3
+ * A dedicated prompt engineering system.
4
+ *
5
+ * Features:
6
+ * - Analyse a prompt for weaknesses
7
+ * - Improve a prompt automatically
8
+ * - Generate prompts from a task description
9
+ * - A/B test two prompts against a benchmark
10
+ * - Prompt template library with variables
11
+ * - Chain-of-thought injection
12
+ */
13
+
14
+ const llm = require('../utils/llmClient');
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+ const costTracker = require('../utils/costTracker');
18
+ const logger = require('../utils/logger');
19
+
20
+ const LIBRARY_FILE = path.join(process.cwd(), 'data', 'prompt-library.json');
21
+
22
+ // ── Built-in prompt templates ─────────────────────────────────────────────────
23
+ const BUILTIN_TEMPLATES = {
24
+ 'chain-of-thought': {
25
+ name: 'chain-of-thought',
26
+ description: 'Adds structured reasoning steps before the answer',
27
+ template: `{prompt}
28
+
29
+ Think through this step by step:
30
+ 1. What is being asked?
31
+ 2. What information do I have?
32
+ 3. What approach should I take?
33
+ 4. Work through the solution
34
+ 5. Verify the answer
35
+
36
+ Answer:`,
37
+ variables: ['prompt'],
38
+ },
39
+ 'role-expert': {
40
+ name: 'role-expert',
41
+ description: 'Frame Anu as a domain expert',
42
+ template: `You are a world-class {domain} expert with 20+ years of experience.
43
+ You give precise, evidence-based answers grounded in real-world practice.
44
+ You never guess — if you are uncertain, you say so clearly.
45
+
46
+ Task: {task}`,
47
+ variables: ['domain', 'task'],
48
+ },
49
+ 'few-shot': {
50
+ name: 'few-shot',
51
+ description: 'Provide examples before the actual task',
52
+ template: `Here are examples of the task:
53
+
54
+ Example 1:
55
+ Input: {example1_input}
56
+ Output: {example1_output}
57
+
58
+ Example 2:
59
+ Input: {example2_input}
60
+ Output: {example2_output}
61
+
62
+ Now do the same for:
63
+ Input: {actual_input}
64
+ Output:`,
65
+ variables: ['example1_input', 'example1_output', 'example2_input', 'example2_output', 'actual_input'],
66
+ },
67
+ 'structured-output': {
68
+ name: 'structured-output',
69
+ description: 'Force structured JSON output',
70
+ template: `{task}
71
+
72
+ Respond ONLY with a JSON object in this exact format (no markdown, no explanation):
73
+ {schema}`,
74
+ variables: ['task', 'schema'],
75
+ },
76
+ 'critique-and-revise': {
77
+ name: 'critique-and-revise',
78
+ description: 'Self-critique and improve an answer',
79
+ template: `{task}
80
+
81
+ First, provide your initial answer.
82
+ Then, critique it: what's missing, what could be wrong, what could be clearer?
83
+ Finally, provide a revised, improved answer incorporating your critique.
84
+
85
+ Initial answer:
86
+ [your first attempt]
87
+
88
+ Critique:
89
+ [what you'd improve]
90
+
91
+ Revised answer:
92
+ [your improved answer]`,
93
+ variables: ['task'],
94
+ },
95
+ 'least-to-most': {
96
+ name: 'least-to-most',
97
+ description: 'Break down into simpler sub-problems first',
98
+ template: `To solve: {problem}
99
+
100
+ First, identify the simpler sub-problems that lead to the solution.
101
+ Solve each sub-problem in order, using each answer to help with the next.
102
+
103
+ Sub-problems:
104
+ 1. [simplest piece]
105
+ 2. [next piece]
106
+ ...
107
+
108
+ Now solve each:`,
109
+ variables: ['problem'],
110
+ },
111
+ 'react-agent': {
112
+ name: 'react-agent',
113
+ description: 'ReAct (Reason + Act) prompting pattern',
114
+ template: `Task: {task}
115
+
116
+ Use this format:
117
+ Thought: [your reasoning about the current situation]
118
+ Action: [action to take from: {available_actions}]
119
+ Observation: [result of the action]
120
+ ... (repeat until done)
121
+ Thought: I now have enough information to answer
122
+ Answer: [final answer]`,
123
+ variables: ['task', 'available_actions'],
124
+ },
125
+ 'socratic': {
126
+ name: 'socratic',
127
+ description: 'Lead the user to the answer through questions',
128
+ template: `Instead of directly answering "{question}", help the user discover the answer themselves.
129
+
130
+ Ask 2-3 probing questions that guide them toward understanding.
131
+ After they respond, lead them closer to the answer.
132
+ Only reveal the full answer if they are completely stuck.`,
133
+ variables: ['question'],
134
+ },
135
+ };
136
+
137
+ class PromptEngineer {
138
+ constructor() {
139
+ this._library = this._loadLibrary();
140
+ }
141
+
142
+ _loadLibrary() {
143
+ try { if (fs.existsSync(LIBRARY_FILE)) return JSON.parse(fs.readFileSync(LIBRARY_FILE, 'utf-8')); } catch { }
144
+ return {};
145
+ }
146
+ _saveLibrary() { fs.writeFileSync(LIBRARY_FILE, JSON.stringify(this._library, null, 2)); }
147
+
148
+ /**
149
+ * Analyse a prompt and identify its weaknesses
150
+ */
151
+ async analyse(prompt, opts = {}) {
152
+ logger.info(`[PromptEngineer] Analysing prompt (${prompt.length} chars)`);
153
+ const response = await llm.chat({
154
+ model: llm.model('smart'), max_tokens: 800,
155
+ system: `You are a prompt engineering expert. Analyse prompts with precision and suggest concrete improvements.`,
156
+ messages: [{ role: 'user', content: `Analyse this prompt and score it 1-10 on each dimension:\n\n---\n${prompt}\n---\n\nReturn JSON:\n{"scores":{"clarity":0,"specificity":0,"context":0,"output_format":0,"role_framing":0,"examples":0},"weaknesses":["..."],"strengths":["..."],"overall_score":0}` }],
157
+ });
158
+ costTracker.record({ model: llm.model('smart'), inputTokens: response.usage.input_tokens, outputTokens: response.usage.output_tokens, sessionId: opts.sessionId || 'prompt-engineer', queryType: 'prompt-analyse' });
159
+ try { return JSON.parse(response.content[0].text.replace(/```json\n?|\n?```/g, '').trim()); }
160
+ catch { return { raw: response.content[0].text }; }
161
+ }
162
+
163
+ /**
164
+ * Automatically improve a prompt
165
+ */
166
+ async improve(prompt, opts = {}) {
167
+ const { goal = '', style = 'concise' } = opts;
168
+ logger.info(`[PromptEngineer] Improving prompt`);
169
+
170
+ const analysis = await this.analyse(prompt, opts);
171
+
172
+ const response = await llm.chat({
173
+ model: llm.model('smart'), max_tokens: 1200,
174
+ system: `You are an expert prompt engineer. Rewrite prompts to be clearer, more specific, and more effective.`,
175
+ messages: [{ role: 'user', content: `Original prompt:\n---\n${prompt}\n---\n\nWeaknesses identified: ${JSON.stringify(analysis.weaknesses || [])}\n\n${goal ? `Goal: ${goal}` : ''}\nStyle: ${style}\n\nRewrite the prompt to fix all weaknesses. Return:\n{"improved_prompt":"...","changes_made":["..."],"expected_improvement":"..."}` }],
176
+ });
177
+ costTracker.record({ model: llm.model('smart'), inputTokens: response.usage.input_tokens, outputTokens: response.usage.output_tokens, sessionId: opts.sessionId || 'prompt-engineer', queryType: 'prompt-improve' });
178
+
179
+ try {
180
+ const result = JSON.parse(response.content[0].text.replace(/```json\n?|\n?```/g, '').trim());
181
+ return { ...result, original: prompt, analysis };
182
+ } catch {
183
+ return { improved_prompt: response.content[0].text, original: prompt };
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Generate a prompt from a task description
189
+ */
190
+ async generate(taskDescription, opts = {}) {
191
+ const { type = 'instruction', model = 'smart', includeExamples = false } = opts;
192
+ logger.info(`[PromptEngineer] Generating prompt for: ${taskDescription.slice(0, 60)}`);
193
+
194
+ const response = await llm.chat({
195
+ model: llm.model('smart'), max_tokens: 1000,
196
+ messages: [{ role: 'user', content: `Generate an optimal ${type} prompt for this task:\n"${taskDescription}"\n\nTarget model: ${model}\nInclude examples: ${includeExamples}\n\nReturn:\n{"system_prompt":"...","user_prompt_template":"...","key_techniques":["..."],"variables":["..."]}` }],
197
+ });
198
+ costTracker.record({ model: llm.model('smart'), inputTokens: response.usage.input_tokens, outputTokens: response.usage.output_tokens, sessionId: opts.sessionId || 'prompt-engineer', queryType: 'prompt-generate' });
199
+ try { return JSON.parse(response.content[0].text.replace(/```json\n?|\n?```/g, '').trim()); }
200
+ catch { return { user_prompt_template: response.content[0].text }; }
201
+ }
202
+
203
+ /**
204
+ * A/B test two prompts against a test case
205
+ */
206
+ async abTest(promptA, promptB, testInput, opts = {}) {
207
+ logger.info(`[PromptEngineer] A/B testing two prompts`);
208
+
209
+ const [resA, resB] = await Promise.all([
210
+ llm.chat({ model: llm.model('fast'), max_tokens: 500, messages: [{ role: 'user', content: `${promptA}\n\nInput: ${testInput}` }] }),
211
+ llm.chat({ model: llm.model('fast'), max_tokens: 500, messages: [{ role: 'user', content: `${promptB}\n\nInput: ${testInput}` }] }),
212
+ ]);
213
+
214
+ const outputA = resA.content[0].text;
215
+ const outputB = resB.content[0].text;
216
+
217
+ // Judge which is better
218
+ const judge = await llm.chat({
219
+ model: llm.model('fast'), max_tokens: 300,
220
+ messages: [{ role: 'user', content: `Compare these two AI outputs for the task: "${testInput}"\n\nOutput A:\n${outputA}\n\nOutput B:\n${outputB}\n\nWhich is better and why? Return JSON:\n{"winner":"A|B|tie","score_a":0,"score_b":0,"reasoning":"...","key_difference":"..."}` }],
221
+ });
222
+
223
+ for (const r of [resA, resB, judge]) {
224
+ costTracker.record({ model: llm.model('fast'), inputTokens: r.usage.input_tokens, outputTokens: r.usage.output_tokens, sessionId: opts.sessionId || 'prompt-engineer', queryType: 'prompt-abtest' });
225
+ }
226
+
227
+ try {
228
+ const verdict = JSON.parse(judge.content[0].text.replace(/```json\n?|\n?```/g, '').trim());
229
+ return { promptA, promptB, testInput, outputA, outputB, verdict };
230
+ } catch {
231
+ return { promptA, promptB, outputA, outputB, verdict: { raw: judge.content[0].text } };
232
+ }
233
+ }
234
+
235
+ /**
236
+ * Apply a prompt template with variable substitution
237
+ */
238
+ applyTemplate(templateName, variables = {}) {
239
+ const t = this._library[templateName] || BUILTIN_TEMPLATES[templateName];
240
+ if (!t) throw new Error(`Template not found: ${templateName}. Available: ${this.listTemplates().map(t => t.name).join(', ')}`);
241
+ let result = t.template;
242
+ for (const [k, v] of Object.entries(variables)) {
243
+ result = result.replace(new RegExp(`\\{${k}\\}`, 'g'), v);
244
+ }
245
+ const missing = (result.match(/\{(\w+)\}/g) || []);
246
+ if (missing.length) logger.warn(`[PromptEngineer] Unfilled variables: ${missing.join(', ')}`);
247
+ return result;
248
+ }
249
+
250
+ /**
251
+ * Inject chain-of-thought reasoning into any prompt
252
+ */
253
+ injectCoT(prompt, style = 'standard') {
254
+ const injections = {
255
+ standard: `\n\nThink through this carefully, step by step, before giving your final answer.`,
256
+ detailed: `\n\nBefore answering:\n1. Understand what's being asked\n2. Identify relevant information\n3. Consider edge cases\n4. Work through the solution\n5. Verify your answer\n\nNow answer:`,
257
+ socratic: `\n\nAsk yourself: What do I know? What don't I know? What's the simplest path? Then answer:`,
258
+ zero_shot: `\n\nLet's think about this step by step:`,
259
+ };
260
+ return prompt + (injections[style] || injections.standard);
261
+ }
262
+
263
+ /** Save a custom template to the library */
264
+ saveTemplate(name, description, template, variables = []) {
265
+ if (BUILTIN_TEMPLATES[name]) throw new Error(`Cannot override built-in template: ${name}`);
266
+ this._library[name] = { name, description, template, variables, custom: true, createdAt: new Date().toISOString() };
267
+ this._saveLibrary();
268
+ return this._library[name];
269
+ }
270
+
271
+ deleteTemplate(name) {
272
+ if (BUILTIN_TEMPLATES[name]) throw new Error('Cannot delete built-in templates');
273
+ if (!this._library[name]) throw new Error(`Template not found: ${name}`);
274
+ delete this._library[name]; this._saveLibrary(); return true;
275
+ }
276
+
277
+ listTemplates() {
278
+ return [
279
+ ...Object.values(BUILTIN_TEMPLATES).map(t => ({ ...t, builtIn: true })),
280
+ ...Object.values(this._library).map(t => ({ ...t, builtIn: false })),
281
+ ];
282
+ }
283
+
284
+ getTemplate(name) {
285
+ return this._library[name] || BUILTIN_TEMPLATES[name] || null;
286
+ }
287
+ }
288
+
289
+ module.exports = new PromptEngineer();