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.
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 +134 -6
  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,137 @@
1
+ /**
2
+ * Watches the filesystem for changes and automatically re-ingests
3
+ * modified files into the knowledge base, keeping the index fresh.
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const logger = require('../utils/logger');
9
+ const ingester = require('../core/ingester');
10
+ const indexer = require('../core/indexer');
11
+ const { FILE_TYPE_MAP } = require('../utils/fileParser');
12
+
13
+ const SUPPORTED_EXTS = new Set(Object.keys(FILE_TYPE_MAP));
14
+
15
+ // Debounce time — wait this long after last change before re-ingesting
16
+ const DEBOUNCE_MS = 1500;
17
+
18
+ class FileWatcher {
19
+ constructor() {
20
+ this._watchers = new Map(); // dirPath → fs.FSWatcher
21
+ this._pending = new Map(); // filePath → timeout handle
22
+ this._stats = { ingested: 0, errors: 0, events: 0 };
23
+ this._isActive = false;
24
+ }
25
+
26
+ /**
27
+ * Watch a directory for changes
28
+ */
29
+ watch(dirPath, opts = {}) {
30
+ const abs = path.resolve(dirPath);
31
+ if (!fs.existsSync(abs)) throw new Error(`Directory not found: ${abs}`);
32
+ if (this._watchers.has(abs)) return { alreadyWatching: true, path: abs };
33
+
34
+ logger.info(`[Watcher] 👁️ Watching: ${abs}`);
35
+
36
+ const watcher = fs.watch(abs, { recursive: true }, (event, filename) => {
37
+ if (!filename) return;
38
+ const filePath = path.join(abs, filename);
39
+ const ext = path.extname(filename).toLowerCase();
40
+
41
+ // Skip unsupported files and build artifacts
42
+ if (!SUPPORTED_EXTS.has(ext)) return;
43
+ if (/node_modules|\.git|dist|build|\.bak/.test(filePath)) return;
44
+
45
+ this._stats.events++;
46
+ this._scheduleIngest(filePath);
47
+ });
48
+
49
+ watcher.on('error', (err) => {
50
+ logger.error(`[Watcher] Error watching ${abs}: ${err.message}`);
51
+ });
52
+
53
+ this._watchers.set(abs, watcher);
54
+ this._isActive = true;
55
+
56
+ return { watching: true, path: abs };
57
+ }
58
+
59
+ /**
60
+ * Stop watching a directory
61
+ */
62
+ unwatch(dirPath) {
63
+ const abs = path.resolve(dirPath);
64
+ const watcher = this._watchers.get(abs);
65
+ if (!watcher) return false;
66
+ watcher.close();
67
+ this._watchers.delete(abs);
68
+ logger.info(`[Watcher] Stopped watching: ${abs}`);
69
+ if (this._watchers.size === 0) this._isActive = false;
70
+ return true;
71
+ }
72
+
73
+ /**
74
+ * Stop all watchers
75
+ */
76
+ stopAll() {
77
+ for (const [dir, watcher] of this._watchers.entries()) {
78
+ watcher.close();
79
+ logger.info(`[Watcher] Stopped: ${dir}`);
80
+ }
81
+ this._watchers.clear();
82
+ for (const handle of this._pending.values()) clearTimeout(handle);
83
+ this._pending.clear();
84
+ this._isActive = false;
85
+ }
86
+
87
+ /**
88
+ * Schedule a debounced ingest for a file
89
+ */
90
+ _scheduleIngest(filePath) {
91
+ // Clear any pending ingest for this file
92
+ if (this._pending.has(filePath)) {
93
+ clearTimeout(this._pending.get(filePath));
94
+ }
95
+
96
+ const handle = setTimeout(async () => {
97
+ this._pending.delete(filePath);
98
+ await this._ingestFile(filePath);
99
+ }, DEBOUNCE_MS);
100
+
101
+ this._pending.set(filePath, handle);
102
+ }
103
+
104
+ async _ingestFile(filePath) {
105
+ if (!fs.existsSync(filePath)) {
106
+ // File deleted — remove from index
107
+ const store = require('../storage/store');
108
+ const removed = store.removeByPath(filePath);
109
+ if (removed > 0) {
110
+ indexer.invalidate();
111
+ logger.info(`[Watcher] Removed from index (deleted): ${path.basename(filePath)}`);
112
+ }
113
+ return;
114
+ }
115
+
116
+ try {
117
+ await ingester.ingestFile(filePath);
118
+ indexer.build();
119
+ this._stats.ingested++;
120
+ logger.info(`[Watcher] ♻️ Re-ingested: ${path.basename(filePath)}`);
121
+ } catch (err) {
122
+ this._stats.errors++;
123
+ logger.warn(`[Watcher] Failed to re-ingest ${path.basename(filePath)}: ${err.message}`);
124
+ }
125
+ }
126
+
127
+ getStatus() {
128
+ return {
129
+ isActive: this._isActive,
130
+ watchedDirs: [...this._watchers.keys()],
131
+ pendingIngests: this._pending.size,
132
+ stats: { ...this._stats },
133
+ };
134
+ }
135
+ }
136
+
137
+ module.exports = new FileWatcher();
@@ -0,0 +1,176 @@
1
+ 'use strict';
2
+ /**
3
+ * Git worktrees let you check out a branch into a separate directory,
4
+ * letting agents work on risky changes without touching your main working tree.
5
+ *
6
+ * Workflow:
7
+ * 1. create(branch, path) → checkout branch in isolated dir
8
+ * 2. run agents with cwd = worktree path
9
+ * 3. diff(name) → see what changed
10
+ * 4. merge(name) → merge back to current branch
11
+ * 5. remove(name) → clean up
12
+ */
13
+
14
+ const { execSync } = require('child_process');
15
+ const { promisify } = require('util');
16
+ const { exec } = require('child_process');
17
+ const execAsync = promisify(exec);
18
+ const fs = require('fs');
19
+ const path = require('path');
20
+ const logger = require('../utils/logger');
21
+
22
+ const META_FILE = path.join(process.cwd(), 'data', 'worktrees.json');
23
+
24
+ const run = async (cmd, cwd) => {
25
+ const { stdout, stderr } = await execAsync(cmd, { cwd: cwd || process.cwd(), timeout: 30000 });
26
+ return (stdout || '').trim();
27
+ };
28
+
29
+ class WorktreeManager {
30
+ constructor() {
31
+ this._worktrees = this._load();
32
+ }
33
+
34
+ _load() {
35
+ try { if (fs.existsSync(META_FILE)) return JSON.parse(fs.readFileSync(META_FILE, 'utf-8')); } catch { }
36
+ return {};
37
+ }
38
+ _save() { fs.writeFileSync(META_FILE, JSON.stringify(this._worktrees, null, 2)); }
39
+
40
+ /**
41
+ * Create a new worktree from a branch (creates branch if it doesn't exist)
42
+ */
43
+ async create(name, branch, opts = {}) {
44
+ const { cwd = process.cwd(), createBranch = true } = opts;
45
+
46
+ if (this._worktrees[name]) throw new Error(`Worktree "${name}" already exists`);
47
+
48
+ const worktreePath = path.join(os_tmpdir(), 'mcp-worktrees', name);
49
+ fs.mkdirSync(path.dirname(worktreePath), { recursive: true });
50
+
51
+ // Check if branch exists
52
+ const branchExists = await run(`git branch --list "${branch}"`, cwd);
53
+ let addCmd;
54
+ if (!branchExists && createBranch) {
55
+ addCmd = `git worktree add -b "${branch}" "${worktreePath}"`;
56
+ } else {
57
+ addCmd = `git worktree add "${worktreePath}" "${branch}"`;
58
+ }
59
+
60
+ logger.info(`[Worktree] Creating: ${name} → ${worktreePath} (${branch})`);
61
+ await run(addCmd, cwd);
62
+
63
+ const entry = {
64
+ name,
65
+ branch,
66
+ path: worktreePath,
67
+ sourceCwd: cwd,
68
+ createdAt: new Date().toISOString(),
69
+ status: 'active',
70
+ };
71
+ this._worktrees[name] = entry;
72
+ this._save();
73
+
74
+ return entry;
75
+ }
76
+
77
+ /**
78
+ * List all worktrees
79
+ */
80
+ async list(cwd = process.cwd()) {
81
+ // Merge local metadata with git's actual worktree list
82
+ let gitList = [];
83
+ try {
84
+ const out = await run('git worktree list --porcelain', cwd);
85
+ const entries = out.split('\n\n').filter(Boolean);
86
+ gitList = entries.map(e => {
87
+ const lines = e.split('\n');
88
+ const p = lines.find(l => l.startsWith('worktree '))?.slice(9);
89
+ const b = lines.find(l => l.startsWith('branch '))?.slice(7);
90
+ return { path: p, branch: b };
91
+ });
92
+ } catch { }
93
+
94
+ return Object.values(this._worktrees).map(wt => ({
95
+ ...wt,
96
+ exists: fs.existsSync(wt.path),
97
+ gitTracked: gitList.some(g => g.path === wt.path),
98
+ }));
99
+ }
100
+
101
+ /**
102
+ * Get a worktree by name
103
+ */
104
+ get(name) {
105
+ return this._worktrees[name] || null;
106
+ }
107
+
108
+ /**
109
+ * Show diff between worktree and its base branch
110
+ */
111
+ async diff(name) {
112
+ const wt = this._worktrees[name];
113
+ if (!wt) throw new Error(`Worktree not found: ${name}`);
114
+ const diff = await run('git diff HEAD', wt.path);
115
+ const status = await run('git status --short', wt.path);
116
+ return { name, branch: wt.branch, diff, status, path: wt.path };
117
+ }
118
+
119
+ /**
120
+ * Get the git log for a worktree (commits made in it)
121
+ */
122
+ async log(name, limit = 5) {
123
+ const wt = this._worktrees[name];
124
+ if (!wt) throw new Error(`Worktree not found: ${name}`);
125
+ return run(`git log --oneline -${limit}`, wt.path);
126
+ }
127
+
128
+ /**
129
+ * Remove a worktree (prune from git + delete dir)
130
+ */
131
+ async remove(name, opts = {}) {
132
+ const { force = false } = opts;
133
+ const wt = this._worktrees[name];
134
+ if (!wt) throw new Error(`Worktree not found: ${name}`);
135
+
136
+ logger.info(`[Worktree] Removing: ${name}`);
137
+
138
+ try {
139
+ const forceFlag = force ? '--force' : '';
140
+ await run(`git worktree remove ${forceFlag} "${wt.path}"`, wt.sourceCwd);
141
+ } catch (e) {
142
+ if (force && fs.existsSync(wt.path)) {
143
+ fs.rmSync(wt.path, { recursive: true, force: true });
144
+ }
145
+ }
146
+
147
+ // Clean up branch if it was auto-created and not merged
148
+ delete this._worktrees[name];
149
+ this._save();
150
+
151
+ return { success: true, name, path: wt.path };
152
+ }
153
+
154
+ /**
155
+ * Stage and commit all changes in a worktree
156
+ */
157
+ async commit(name, message) {
158
+ const wt = this._worktrees[name];
159
+ if (!wt) throw new Error(`Worktree not found: ${name}`);
160
+ await run('git add .', wt.path);
161
+ const result = await run(`git commit -m "${message || 'MCP agent changes'}"`, wt.path);
162
+ return { name, result };
163
+ }
164
+
165
+ getStats() {
166
+ const wts = Object.values(this._worktrees);
167
+ return { total: wts.length, active: wts.filter(w => fs.existsSync(w.path)).length };
168
+ }
169
+ }
170
+
171
+ // Simple os tmpdir helper
172
+ function os_tmpdir() {
173
+ return require('os').tmpdir();
174
+ }
175
+
176
+ module.exports = new WorktreeManager();