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,72 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const GitTool = require('../../tools/GitTool');
4
+ const logger = require('../../utils/logger');
5
+
6
+ /** GET /api/git/status */
7
+ router.get('/status', async (req, res) => {
8
+ const { cwd } = req.query;
9
+ try {
10
+ const status = await GitTool.status(cwd);
11
+ res.json(status);
12
+ } catch (err) {
13
+ res.status(500).json({ error: err.message });
14
+ }
15
+ });
16
+
17
+ /** GET /api/git/diff */
18
+ router.get('/diff', async (req, res) => {
19
+ const { cwd, staged, file, stat } = req.query;
20
+ try {
21
+ const result = await GitTool.diff({ cwd, staged: staged === 'true', file, stat: stat === 'true' });
22
+ res.json(result);
23
+ } catch (err) {
24
+ res.status(500).json({ error: err.message });
25
+ }
26
+ });
27
+
28
+ /** POST /api/git/commit */
29
+ router.post('/commit', async (req, res) => {
30
+ const { cwd, files, message, autoMessage } = req.body;
31
+ try {
32
+ const result = await GitTool.commit({ cwd, files, message, autoMessage });
33
+ res.json(result);
34
+ } catch (err) {
35
+ res.status(500).json({ error: err.message });
36
+ }
37
+ });
38
+
39
+ /** POST /api/git/review */
40
+ router.post('/review', async (req, res) => {
41
+ const { cwd, staged, file, focus } = req.body;
42
+ try {
43
+ const result = await GitTool.review({ cwd, staged, file, focus });
44
+ res.json(result);
45
+ } catch (err) {
46
+ res.status(500).json({ error: err.message });
47
+ }
48
+ });
49
+
50
+ /** GET /api/git/log */
51
+ router.get('/log', async (req, res) => {
52
+ const { cwd, limit, file, oneline } = req.query;
53
+ try {
54
+ const result = await GitTool.log({ cwd, limit: parseInt(limit) || 10, file, oneline: oneline === 'true' });
55
+ res.json({ commits: result });
56
+ } catch (err) {
57
+ res.status(500).json({ error: err.message });
58
+ }
59
+ });
60
+
61
+ /** GET /api/git/branches */
62
+ router.get('/branches', async (req, res) => {
63
+ const { cwd } = req.query;
64
+ try {
65
+ const result = await GitTool.branches(cwd);
66
+ res.json({ branches: result });
67
+ } catch (err) {
68
+ res.status(500).json({ error: err.message });
69
+ }
70
+ });
71
+
72
+ module.exports = router;
@@ -5,65 +5,85 @@ const indexer = require('../../core/indexer');
5
5
  const store = require('../../storage/store');
6
6
  const logger = require('../../utils/logger');
7
7
 
8
+ /**
9
+ * POST /api/ingest/file
10
+ * Ingest a single file by path
11
+ */
8
12
  router.post('/file', async (req, res) => {
9
- const { filePath } = req.body;
13
+ const { filePath } = req.body;
10
14
 
11
- if (!filePath) {
12
- return res.status(400).json({ error: 'filePath is required' });
13
- }
15
+ if (!filePath) {
16
+ return res.status(400).json({ error: 'filePath is required' });
17
+ }
14
18
 
15
- try {
16
- const result = await ingester.ingestFile(filePath);
17
- indexer.build();
18
- res.json({ success: true, result });
19
- } catch (err) {
20
- logger.error(`Ingest file error: ${err.message}`);
21
- res.status(500).json({ error: err.message });
22
- }
19
+ try {
20
+ const result = await ingester.ingestFile(filePath);
21
+ indexer.build();
22
+ res.json({ success: true, result });
23
+ } catch (err) {
24
+ logger.error(`Ingest file error: ${err.message}`);
25
+ res.status(500).json({ error: err.message });
26
+ }
23
27
  });
24
28
 
29
+ /**
30
+ * POST /api/ingest/directory
31
+ * Ingest all supported files in a directory
32
+ */
25
33
  router.post('/directory', async (req, res) => {
26
- const { dirPath } = req.body;
34
+ const { dirPath } = req.body;
27
35
 
28
- if (!dirPath) {
29
- return res.status(400).json({ error: 'dirPath is required' });
30
- }
36
+ if (!dirPath) {
37
+ return res.status(400).json({ error: 'dirPath is required' });
38
+ }
31
39
 
32
- try {
33
- const result = await ingester.ingestDirectory(dirPath);
34
- res.json({ success: true, result });
35
- } catch (err) {
36
- logger.error(`Ingest directory error: ${err.message}`);
37
- res.status(500).json({ error: err.message });
38
- }
40
+ try {
41
+ const result = await ingester.ingestDirectory(dirPath);
42
+ res.json({ success: true, result });
43
+ } catch (err) {
44
+ logger.error(`Ingest directory error: ${err.message}`);
45
+ res.status(500).json({ error: err.message });
46
+ }
39
47
  });
40
48
 
49
+ /**
50
+ * POST /api/ingest/raw
51
+ * Ingest raw text (error logs, bug descriptions, API responses)
52
+ */
41
53
  router.post('/raw', async (req, res) => {
42
- const { content, kind, label, tags } = req.body;
54
+ const { content, kind, label, tags } = req.body;
43
55
 
44
- if (!content) {
45
- return res.status(400).json({ error: 'content is required' });
46
- }
56
+ if (!content) {
57
+ return res.status(400).json({ error: 'content is required' });
58
+ }
47
59
 
48
- try {
49
- const result = await ingester.ingestRawText(content, { kind, label, tags });
50
- indexer.build();
51
- res.json({ success: true, result });
52
- } catch (err) {
53
- logger.error(`Ingest raw error: ${err.message}`);
54
- res.status(500).json({ error: err.message });
55
- }
60
+ try {
61
+ const result = await ingester.ingestRawText(content, { kind, label, tags });
62
+ indexer.build();
63
+ res.json({ success: true, result });
64
+ } catch (err) {
65
+ logger.error(`Ingest raw error: ${err.message}`);
66
+ res.status(500).json({ error: err.message });
67
+ }
56
68
  });
57
69
 
70
+ /**
71
+ * DELETE /api/ingest/clear
72
+ * Clear the entire knowledge base
73
+ */
58
74
  router.delete('/clear', (req, res) => {
59
- store.clear();
60
- indexer.invalidate();
61
- res.json({ success: true, message: 'Knowledge base cleared' });
75
+ store.clear();
76
+ indexer.invalidate();
77
+ res.json({ success: true, message: 'Knowledge base cleared' });
62
78
  });
63
79
 
80
+ /**
81
+ * GET /api/ingest/files
82
+ * List all ingested files
83
+ */
64
84
  router.get('/files', (req, res) => {
65
- const files = store.getIngestedFiles();
66
- res.json({ files, count: files.length });
85
+ const files = store.getIngestedFiles();
86
+ res.json({ files, count: files.length });
67
87
  });
68
88
 
69
89
  module.exports = router;
@@ -3,63 +3,81 @@ const router = express.Router();
3
3
  const store = require('../../storage/store');
4
4
  const indexer = require('../../core/indexer');
5
5
 
6
+ /**
7
+ * GET /api/knowledge/stats
8
+ * Get knowledge base statistics
9
+ */
6
10
  router.get('/stats', (req, res) => {
7
- const stats = store.getStats();
8
- res.json(stats);
11
+ const stats = store.getStats();
12
+ res.json(stats);
9
13
  });
10
14
 
15
+ /**
16
+ * GET /api/knowledge/search
17
+ * Raw TF-IDF search (no AI, just retrieval)
18
+ * Query params: q, topK, kind
19
+ */
11
20
  router.get('/search', (req, res) => {
12
- const { q, topK = '8', kind } = req.query;
21
+ const { q, topK = '8', kind } = req.query;
13
22
 
14
- if (!q) {
15
- return res.status(400).json({ error: 'q query parameter is required' });
16
- }
23
+ if (!q) {
24
+ return res.status(400).json({ error: 'q query parameter is required' });
25
+ }
17
26
 
18
- const filter = kind ? { kind } : {};
19
- const results = indexer.search(q, parseInt(topK), filter);
27
+ const filter = kind ? { kind } : {};
28
+ const results = indexer.search(q, parseInt(topK), filter);
20
29
 
21
- res.json({
22
- query: q,
23
- count: results.length,
24
- results: results.map(r => ({
25
- file: r.filename,
26
- path: r.filePath,
27
- kind: r.kind,
28
- relevanceScore: r.relevanceScore,
29
- snippet: r.content.slice(0, 300) + (r.content.length > 300 ? '...' : ''),
30
- metadata: r.metadata,
31
- })),
32
- });
30
+ res.json({
31
+ query: q,
32
+ count: results.length,
33
+ results: results.map(r => ({
34
+ file: r.filename,
35
+ path: r.filePath,
36
+ kind: r.kind,
37
+ relevanceScore: r.relevanceScore,
38
+ snippet: r.content.slice(0, 300) + (r.content.length > 300 ? '...' : ''),
39
+ metadata: r.metadata,
40
+ })),
41
+ });
33
42
  });
34
43
 
44
+ /**
45
+ * GET /api/knowledge/files
46
+ * List all ingested files with metadata
47
+ */
35
48
  router.get('/files', (req, res) => {
36
- const { kind } = req.query;
37
- const docs = kind ? store.getByKind(kind) : store.getAll();
49
+ const { kind } = req.query;
50
+ const docs = kind ? store.getByKind(kind) : store.getAll();
38
51
 
39
- const grouped = {};
40
- for (const doc of docs) {
41
- if (!grouped[doc.filePath]) {
42
- grouped[doc.filePath] = {
43
- filePath: doc.filePath,
44
- filename: doc.filename,
45
- kind: doc.kind,
46
- chunks: 0,
47
- ingestedAt: doc.ingestedAt,
48
- metadata: doc.metadata,
49
- };
50
- }
51
- grouped[doc.filePath].chunks++;
52
+ // Group by file path
53
+ const grouped = {};
54
+ for (const doc of docs) {
55
+ if (!grouped[doc.filePath]) {
56
+ grouped[doc.filePath] = {
57
+ filePath: doc.filePath,
58
+ filename: doc.filename,
59
+ kind: doc.kind,
60
+ chunks: 0,
61
+ ingestedAt: doc.ingestedAt,
62
+ metadata: doc.metadata,
63
+ };
52
64
  }
65
+ grouped[doc.filePath].chunks++;
66
+ }
53
67
 
54
- res.json({
55
- count: Object.keys(grouped).length,
56
- files: Object.values(grouped),
57
- });
68
+ res.json({
69
+ count: Object.keys(grouped).length,
70
+ files: Object.values(grouped),
71
+ });
58
72
  });
59
73
 
74
+ /**
75
+ * POST /api/knowledge/rebuild
76
+ * Force rebuild of the search index
77
+ */
60
78
  router.post('/rebuild', (req, res) => {
61
- const count = indexer.build();
62
- res.json({ success: true, documentsIndexed: count });
79
+ const count = indexer.build();
80
+ res.json({ success: true, documentsIndexed: count });
63
81
  });
64
82
 
65
83
  module.exports = router;
@@ -0,0 +1,41 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const { MemoryManager, MEMORY_TYPES } = require('../../memory/memoryManager');
4
+
5
+ /** GET /api/memory — list all memories */
6
+ router.get('/', (req, res) => {
7
+ const { type } = req.query;
8
+ const memories = MemoryManager.list(type || null);
9
+ res.json({ memories, stats: MemoryManager.getStats() });
10
+ });
11
+
12
+ /** POST /api/memory — add a memory manually */
13
+ router.post('/', (req, res) => {
14
+ const { content, type, tags } = req.body;
15
+ if (!content) return res.status(400).json({ error: 'content is required' });
16
+ try {
17
+ const entry = MemoryManager.add(content, type || MEMORY_TYPES.FACT, tags || []);
18
+ res.json({ success: true, entry });
19
+ } catch (err) {
20
+ res.status(500).json({ error: err.message });
21
+ }
22
+ });
23
+
24
+ /** DELETE /api/memory/:id — delete a memory */
25
+ router.delete('/:id', (req, res) => {
26
+ const deleted = MemoryManager.delete(req.params.id);
27
+ res.json({ success: deleted });
28
+ });
29
+
30
+ /** DELETE /api/memory — clear all memories */
31
+ router.delete('/', (req, res) => {
32
+ MemoryManager.clear();
33
+ res.json({ success: true, message: 'All memories cleared' });
34
+ });
35
+
36
+ /** GET /api/memory/types — list valid memory types */
37
+ router.get('/types', (req, res) => {
38
+ res.json({ types: Object.values(MEMORY_TYPES) });
39
+ });
40
+
41
+ module.exports = router;
@@ -0,0 +1,168 @@
1
+ // ── skills.js ──────────────────────────────────────────────────────────────────
2
+ const express = require('express');
3
+ const skillsRouter = express.Router();
4
+ const skillsManager = require('../../skills/skillsManager');
5
+
6
+ skillsRouter.get('/', (req, res) => res.json({ skills: skillsManager.list(req.query) }));
7
+ skillsRouter.post('/', (req, res) => {
8
+ try { res.json({ skill: skillsManager.create(req.body.name, req.body.description, req.body.prompt, req.body.tags) }); }
9
+ catch (e) { res.status(400).json({ error: e.message }); }
10
+ });
11
+ skillsRouter.post('/:name', async (req, res) => {
12
+ const { target, sessionId, extraContext } = req.body;
13
+ if (!target) return res.status(400).json({ error: 'target is required' });
14
+ try { res.json(await skillsManager.run(req.params.name, target, { sessionId, extraContext })); }
15
+ catch (e) { res.status(500).json({ error: e.message }); }
16
+ });
17
+ skillsRouter.delete('/:name', (req, res) => {
18
+ try { res.json({ success: skillsManager.delete(req.params.name) }); }
19
+ catch (e) { res.status(400).json({ error: e.message }); }
20
+ });
21
+ module.exports.skillsRouter = skillsRouter;
22
+
23
+ // ── lsp.js ─────────────────────────────────────────────────────────────────────
24
+ const lspRouter = express.Router();
25
+ const symbolNavigator = require('../../lsp/symbolNavigator');
26
+
27
+ lspRouter.get('/definition/:symbol', async (req, res) => {
28
+ try { res.json(await symbolNavigator.goToDefinition(req.params.symbol, req.query.cwd)); }
29
+ catch (e) { res.status(500).json({ error: e.message }); }
30
+ });
31
+ lspRouter.get('/references/:symbol', async (req, res) => {
32
+ try { res.json(await symbolNavigator.findReferences(req.params.symbol, req.query.cwd)); }
33
+ catch (e) { res.status(500).json({ error: e.message }); }
34
+ });
35
+ lspRouter.post('/hover', async (req, res) => {
36
+ try { res.json(await symbolNavigator.hover(req.body.symbol, req.body.filePath, req.body.sessionId)); }
37
+ catch (e) { res.status(500).json({ error: e.message }); }
38
+ });
39
+ lspRouter.get('/outline', async (req, res) => {
40
+ if (!req.query.path) return res.status(400).json({ error: 'path is required' });
41
+ try { res.json(await symbolNavigator.outline(req.query.path)); }
42
+ catch (e) { res.status(500).json({ error: e.message }); }
43
+ });
44
+ lspRouter.get('/symbols', async (req, res) => {
45
+ if (!req.query.q) return res.status(400).json({ error: 'q is required' });
46
+ try { res.json(await symbolNavigator.workspaceSymbols(req.query.q, req.query.cwd)); }
47
+ catch (e) { res.status(500).json({ error: e.message }); }
48
+ });
49
+ lspRouter.post('/rename', async (req, res) => {
50
+ const { oldName, newName, cwd } = req.body;
51
+ if (!oldName || !newName) return res.status(400).json({ error: 'oldName and newName required' });
52
+ try { res.json(await symbolNavigator.renameSymbol(oldName, newName, cwd)); }
53
+ catch (e) { res.status(500).json({ error: e.message }); }
54
+ });
55
+ module.exports.lspRouter = lspRouter;
56
+
57
+ // ── files.js ───────────────────────────────────────────────────────────────────
58
+ const filesRouter = express.Router();
59
+ const FileEditTool = require('../../tools/FileEditTool');
60
+
61
+ filesRouter.get('/read', (req, res) => {
62
+ if (!req.query.path) return res.status(400).json({ error: 'path is required' });
63
+ try { res.json(FileEditTool.read(req.query.path, { startLine: req.query.startLine, endLine: req.query.endLine })); }
64
+ catch (e) { res.status(500).json({ error: e.message }); }
65
+ });
66
+ filesRouter.post('/str-replace', async (req, res) => {
67
+ const { filePath, oldStr, newStr, dryRun, backup } = req.body;
68
+ if (!filePath || !oldStr) return res.status(400).json({ error: 'filePath and oldStr required' });
69
+ try { res.json(await FileEditTool.strReplace(filePath, oldStr, newStr || '', { dryRun, backup })); }
70
+ catch (e) { res.status(400).json({ error: e.message }); }
71
+ });
72
+ filesRouter.post('/insert-after', async (req, res) => {
73
+ const { filePath, afterStr, insertText, dryRun } = req.body;
74
+ if (!filePath || !afterStr || !insertText) return res.status(400).json({ error: 'filePath, afterStr, insertText required' });
75
+ try { res.json(await FileEditTool.insertAfter(filePath, afterStr, insertText, { dryRun })); }
76
+ catch (e) { res.status(400).json({ error: e.message }); }
77
+ });
78
+ filesRouter.post('/rewrite', async (req, res) => {
79
+ const { filePath, content, dryRun, backup } = req.body;
80
+ if (!filePath || !content) return res.status(400).json({ error: 'filePath and content required' });
81
+ try { res.json(await FileEditTool.rewrite(filePath, content, { dryRun, backup })); }
82
+ catch (e) { res.status(400).json({ error: e.message }); }
83
+ });
84
+ filesRouter.post('/ai-edit', async (req, res) => {
85
+ const { filePath, instruction, dryRun, sessionId } = req.body;
86
+ if (!filePath || !instruction) return res.status(400).json({ error: 'filePath and instruction required' });
87
+ try { res.json(await FileEditTool.aiEdit(filePath, instruction, { dryRun, sessionId })); }
88
+ catch (e) { res.status(500).json({ error: e.message }); }
89
+ });
90
+ filesRouter.post('/undo', (req, res) => {
91
+ if (!req.body.filePath) return res.status(400).json({ error: 'filePath required' });
92
+ try { res.json(FileEditTool.undo(req.body.filePath)); }
93
+ catch (e) { res.status(400).json({ error: e.message }); }
94
+ });
95
+ module.exports.filesRouter = filesRouter;
96
+
97
+ // ── monitor.js ─────────────────────────────────────────────────────────────────
98
+ const monitorRouter = express.Router();
99
+ const { ProactiveMonitor } = require('../../monitor/proactiveMonitor');
100
+
101
+ monitorRouter.get('/status', (req, res) => res.json(ProactiveMonitor.getStatus()));
102
+ monitorRouter.get('/alerts', (req, res) => {
103
+ const { severity, unacknowledged, limit } = req.query;
104
+ res.json({ alerts: ProactiveMonitor.getAlerts({ severity, unacknowledged: unacknowledged === 'true', limit: parseInt(limit) || 50 }) });
105
+ });
106
+ monitorRouter.post('/run-all', async (req, res) => {
107
+ try { res.json({ results: await ProactiveMonitor.runAll() }); }
108
+ catch (e) { res.status(500).json({ error: e.message }); }
109
+ });
110
+ monitorRouter.post('/run/:checkId', async (req, res) => {
111
+ try { res.json(await ProactiveMonitor.runCheck(req.params.checkId)); }
112
+ catch (e) { res.status(500).json({ error: e.message }); }
113
+ });
114
+ monitorRouter.post('/alerts/:id/acknowledge', (req, res) => {
115
+ try { res.json(ProactiveMonitor.acknowledge(req.params.id)); }
116
+ catch (e) { res.status(404).json({ error: e.message }); }
117
+ });
118
+ monitorRouter.post('/acknowledge-all', (req, res) => {
119
+ const count = ProactiveMonitor.acknowledgeAll();
120
+ res.json({ success: true, acknowledged: count });
121
+ });
122
+ monitorRouter.post('/start', (req, res) => {
123
+ ProactiveMonitor.start(req.body.cwd);
124
+ res.json({ success: true, message: 'Monitor started' });
125
+ });
126
+ monitorRouter.post('/stop', (req, res) => {
127
+ ProactiveMonitor.stop();
128
+ res.json({ success: true });
129
+ });
130
+ module.exports.monitorRouter = monitorRouter;
131
+
132
+ // ── conversation.js ────────────────────────────────────────────────────────────
133
+ const convRouter = express.Router();
134
+ const conversationEngine = require('../../core/conversationEngine');
135
+
136
+ convRouter.post('/', async (req, res) => {
137
+ const { message, convId, sessionId, topK } = req.body;
138
+ if (!message) return res.status(400).json({ error: 'message is required' });
139
+ try { res.json(await conversationEngine.chat(message, convId || 'default', { sessionId, topK })); }
140
+ catch (e) { res.status(500).json({ error: e.message }); }
141
+ });
142
+ convRouter.post('/follow-up', async (req, res) => {
143
+ const { message, convId, sessionId } = req.body;
144
+ if (!message) return res.status(400).json({ error: 'message is required' });
145
+ try { res.json(await conversationEngine.followUp(message, convId || 'default', { sessionId })); }
146
+ catch (e) { res.status(500).json({ error: e.message }); }
147
+ });
148
+ convRouter.get('/', (req, res) => res.json({ conversations: conversationEngine.list() }));
149
+ convRouter.get('/:id/history', (req, res) => res.json({ history: conversationEngine.getHistory(req.params.id) }));
150
+ convRouter.delete('/:id', (req, res) => { conversationEngine.reset(req.params.id); res.json({ success: true }); });
151
+ module.exports.convRouter = convRouter;
152
+
153
+ // ── watcher.js ─────────────────────────────────────────────────────────────────
154
+ const watcherRouter = express.Router();
155
+ const fileWatcher = require('../../watcher/fileWatcher');
156
+
157
+ watcherRouter.get('/status', (req, res) => res.json(fileWatcher.getStatus()));
158
+ watcherRouter.post('/watch', (req, res) => {
159
+ if (!req.body.path) return res.status(400).json({ error: 'path is required' });
160
+ try { res.json(fileWatcher.watch(req.body.path)); }
161
+ catch (e) { res.status(400).json({ error: e.message }); }
162
+ });
163
+ watcherRouter.post('/unwatch', (req, res) => {
164
+ if (!req.body.path) return res.status(400).json({ error: 'path is required' });
165
+ res.json({ success: fileWatcher.unwatch(req.body.path) });
166
+ });
167
+ watcherRouter.post('/stop', (req, res) => { fileWatcher.stopAll(); res.json({ success: true }); });
168
+ module.exports.watcherRouter = watcherRouter;
@@ -0,0 +1,41 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const pipelineEngine = require('../../pipelines/pipelineEngine');
4
+ const logger = require('../../utils/logger');
5
+
6
+ /** GET /api/pipelines — list all available pipelines and steps */
7
+ router.get('/', (req, res) => {
8
+ res.json({
9
+ pipelines: pipelineEngine.getAvailablePipelines(),
10
+ availableSteps: pipelineEngine.getAvailableSteps(),
11
+ });
12
+ });
13
+
14
+ /** POST /api/pipelines/:name — run a named pipeline */
15
+ router.post('/:name', async (req, res) => {
16
+ const { task, sessionId, topK, ...rest } = req.body;
17
+ if (!task) return res.status(400).json({ error: 'task is required' });
18
+
19
+ try {
20
+ const result = await pipelineEngine.run(req.params.name, { task, ...rest }, { sessionId, topK });
21
+ res.json(result);
22
+ } catch (err) {
23
+ logger.error(`Pipeline error: ${err.message}`);
24
+ res.status(500).json({ error: err.message });
25
+ }
26
+ });
27
+
28
+ /** POST /api/pipelines/custom/run — run a custom steps pipeline */
29
+ router.post('/custom/run', async (req, res) => {
30
+ const { steps, task, sessionId, topK, ...rest } = req.body;
31
+ if (!task || !steps?.length) return res.status(400).json({ error: 'task and steps[] are required' });
32
+
33
+ try {
34
+ const result = await pipelineEngine.runCustom(steps, { task, ...rest }, { sessionId, topK });
35
+ res.json(result);
36
+ } catch (err) {
37
+ res.status(500).json({ error: err.message });
38
+ }
39
+ });
40
+
41
+ module.exports = router;
@@ -0,0 +1,54 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const plannerEngine = require('../../planner/plannerEngine');
4
+ const costTracker = require('../../utils/costTracker');
5
+ const indexer = require('../../core/indexer');
6
+ const logger = require('../../utils/logger');
7
+
8
+ /** POST /api/plan — generate an execution plan for a task */
9
+ router.post('/', async (req, res) => {
10
+ const { task, sessionId } = req.body;
11
+ if (!task) return res.status(400).json({ error: 'task is required' });
12
+
13
+ try {
14
+ // Retrieve relevant context chunks for the plan
15
+ const context = indexer.search(task, 6);
16
+ const plan = await plannerEngine.generatePlan(task, context, sessionId);
17
+ res.json(plan);
18
+ } catch (err) {
19
+ logger.error(`Plan error: ${err.message}`);
20
+ res.status(500).json({ error: err.message });
21
+ }
22
+ });
23
+
24
+ /** POST /api/plan/compact — compact a conversation history */
25
+ router.post('/compact', async (req, res) => {
26
+ const { messages, sessionId } = req.body;
27
+ if (!messages || !Array.isArray(messages)) {
28
+ return res.status(400).json({ error: 'messages array is required' });
29
+ }
30
+ try {
31
+ const result = await plannerEngine.compact(messages, sessionId);
32
+ res.json(result);
33
+ } catch (err) {
34
+ res.status(500).json({ error: err.message });
35
+ }
36
+ });
37
+
38
+ /** GET /api/plan/doctor — environment health check */
39
+ router.get('/doctor', async (req, res) => {
40
+ try {
41
+ const result = await plannerEngine.doctor();
42
+ res.json(result);
43
+ } catch (err) {
44
+ res.status(500).json({ error: err.message });
45
+ }
46
+ });
47
+
48
+ /** GET /api/cost — get cost summary */
49
+ router.get('/cost', (req, res) => {
50
+ const { sessionId } = req.query;
51
+ res.json(costTracker.getSummary(sessionId || 'default'));
52
+ });
53
+
54
+ module.exports = router;