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
|
@@ -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();
|