wolverine-ai 1.4.0 → 1.5.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/README.md CHANGED
@@ -202,20 +202,26 @@ The error hook auto-patches Fastify and Express via `--require` preload. No midd
202
202
 
203
203
  ## Agent Tool Harness
204
204
 
205
- The AI agent has 10 built-in tools (ported from [claw-code](https://github.com/instructkr/claw-code)):
206
-
207
- | Tool | Source | Description |
208
- |------|--------|-------------|
209
- | `read_file` | FileReadTool | Read any file with optional offset/limit for large files |
210
- | `write_file` | FileWriteTool | Write complete file content, creates parent dirs |
211
- | `edit_file` | FileEditTool | Surgical find-and-replace without rewriting entire file |
212
- | `glob_files` | GlobTool | Pattern-based file discovery (`**/*.js`, `src/**/*.json`) |
213
- | `grep_code` | GrepTool | Regex search across codebase with context lines |
214
- | `bash_exec` | BashTool | Sandboxed shell execution with blocked dangerous commands |
215
- | `git_log` | gitOperationTracking | View recent commit history |
216
- | `git_diff` | gitOperationTracking | View uncommitted changes |
217
- | `web_fetch` | WebFetchTool | Fetch URL content for documentation/research |
218
- | `done` | | Signal task completion with summary |
205
+ The AI agent has 16 built-in tools (inspired by [claw-code](https://github.com/ultraworkers/claw-code)):
206
+
207
+ | Tool | Category | Description |
208
+ |------|----------|-------------|
209
+ | `read_file` | File | Read any file with optional offset/limit for large files |
210
+ | `write_file` | File | Write complete file content, creates parent dirs |
211
+ | `edit_file` | File | Surgical find-and-replace without rewriting entire file |
212
+ | `glob_files` | File | Pattern-based file discovery (`**/*.js`, `src/**/*.json`) |
213
+ | `grep_code` | File | Regex search across codebase with context lines |
214
+ | `list_dir` | File | List directory contents with sizes (find misplaced files) |
215
+ | `move_file` | File | Move or rename files (fix structure problems) |
216
+ | `bash_exec` | Shell | Sandboxed shell execution (npm install, chmod, kill, etc.) |
217
+ | `git_log` | Shell | View recent commit history |
218
+ | `git_diff` | Shell | View uncommitted changes |
219
+ | `inspect_db` | Database | List tables, show schema, run SELECT on SQLite databases |
220
+ | `run_db_fix` | Database | UPDATE/DELETE/INSERT/ALTER on SQLite (auto-backup before write) |
221
+ | `check_port` | Diagnostic | Check if a port is in use and by what process |
222
+ | `check_env` | Diagnostic | Check environment variables (values auto-redacted) |
223
+ | `web_fetch` | Research | Fetch URL content for documentation/research |
224
+ | `done` | Control | Signal task completion with summary |
219
225
 
220
226
  **Blocked commands** (from claw-code's `destructiveCommandWarning`):
221
227
  `rm -rf /`, `git push --force`, `git reset --hard`, `npm publish`, `curl | bash`, `eval()`
@@ -231,15 +237,15 @@ For complex repairs, wolverine spawns specialized sub-agents that run in sequenc
231
237
 
232
238
  | Agent | Access | Model | Role |
233
239
  |-------|--------|-------|------|
234
- | `explore` | Read-only | REASONING | Investigate codebase, find relevant files |
240
+ | `explore` | Read+diagnostics | REASONING | Investigate codebase, check env/ports/databases |
235
241
  | `plan` | Read-only | REASONING | Analyze problem, propose fix strategy |
236
242
  | `fix` | Read+write+shell | CODING | Execute targeted fix — code edits AND npm install/chmod |
237
243
  | `verify` | Read-only | REASONING | Check if fix actually works |
238
244
  | `research` | Read-only | RESEARCH | Search brain + web for solutions |
239
245
  | `security` | Read-only | AUDIT | Audit code for vulnerabilities |
240
- | `database` | Read+write | CODING | Database-specific fixes (SQL skill) |
246
+ | `database` | Read+write+SQL | CODING | Database fixes: inspect_db + run_db_fix + SQL skill |
241
247
 
242
- Each sub-agent gets **restricted tools** — the explorer can't write files, the fixer can't search the web. This prevents agents from overstepping their role.
248
+ Each sub-agent gets **restricted tools** — the explorer can't write files, the fixer can't search the web. This prevents agents from overstepping their role. Diagnostic tools (check_port, check_env, inspect_db, list_dir) are available to explorers and planners for investigation.
243
249
 
244
250
  **Workflows:**
245
251
  - `exploreAndFix()` — explore → plan → fix (sequential, 3 agents)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wolverine-ai",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Self-healing Node.js server framework powered by AI. Catches crashes, diagnoses errors, generates fixes, verifies, and restarts — automatically.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -172,6 +172,95 @@ const TOOL_DEFINITIONS = [
172
172
  },
173
173
  },
174
174
  },
175
+ // ── DIAGNOSTICS (investigate non-code problems) ──
176
+ {
177
+ type: "function",
178
+ function: {
179
+ name: "list_dir",
180
+ description: "List directory contents with file sizes. Use to check if files exist, find misplaced files, or verify directory structure.",
181
+ parameters: {
182
+ type: "object",
183
+ properties: {
184
+ path: { type: "string", description: "Relative directory path (default: project root)" },
185
+ },
186
+ required: [],
187
+ },
188
+ },
189
+ },
190
+ {
191
+ type: "function",
192
+ function: {
193
+ name: "move_file",
194
+ description: "Move or rename a file. Use to fix misplaced files, reorganize structure, or rename incorrectly named files.",
195
+ parameters: {
196
+ type: "object",
197
+ properties: {
198
+ from: { type: "string", description: "Source relative path" },
199
+ to: { type: "string", description: "Destination relative path" },
200
+ },
201
+ required: ["from", "to"],
202
+ },
203
+ },
204
+ },
205
+ {
206
+ type: "function",
207
+ function: {
208
+ name: "check_port",
209
+ description: "Check if a port is in use and what process is using it. Use for EADDRINUSE errors.",
210
+ parameters: {
211
+ type: "object",
212
+ properties: {
213
+ port: { type: "number", description: "Port number to check" },
214
+ },
215
+ required: ["port"],
216
+ },
217
+ },
218
+ },
219
+ {
220
+ type: "function",
221
+ function: {
222
+ name: "check_env",
223
+ description: "Check environment variables. Lists all env vars (values redacted) or checks if a specific var is set. Use to diagnose missing config.",
224
+ parameters: {
225
+ type: "object",
226
+ properties: {
227
+ variable: { type: "string", description: "Specific env var to check (optional — omit to list all)" },
228
+ },
229
+ required: [],
230
+ },
231
+ },
232
+ },
233
+ {
234
+ type: "function",
235
+ function: {
236
+ name: "inspect_db",
237
+ description: "Inspect a SQLite database: list tables, describe schema, or run a read-only query. Use for database errors, invalid entries, schema mismatches.",
238
+ parameters: {
239
+ type: "object",
240
+ properties: {
241
+ db_path: { type: "string", description: "Relative path to .db or .sqlite file" },
242
+ action: { type: "string", description: "Action: 'tables' (list tables), 'schema' (show CREATE statements), 'query' (run read-only SQL)" },
243
+ sql: { type: "string", description: "SQL query (required if action is 'query', must be SELECT/PRAGMA only)" },
244
+ },
245
+ required: ["db_path", "action"],
246
+ },
247
+ },
248
+ },
249
+ {
250
+ type: "function",
251
+ function: {
252
+ name: "run_db_fix",
253
+ description: "Run a write query on a SQLite database to fix data issues: UPDATE invalid entries, DELETE corrupt rows, ALTER schema. Creates a backup first.",
254
+ parameters: {
255
+ type: "object",
256
+ properties: {
257
+ db_path: { type: "string", description: "Relative path to .db or .sqlite file" },
258
+ sql: { type: "string", description: "SQL statement (UPDATE, DELETE, INSERT, ALTER, CREATE)" },
259
+ },
260
+ required: ["db_path", "sql"],
261
+ },
262
+ },
263
+ },
175
264
  // ── COMPLETION ──
176
265
  {
177
266
  type: "function",
@@ -235,68 +324,92 @@ class AgentEngine {
235
324
  async run({ errorMessage, stackTrace, primaryFile, sourceCode, brainContext }) {
236
325
  const model = getModel("reasoning");
237
326
 
238
- const systemPrompt = `You are Wolverine, an autonomous Node.js server repair agent. A server crashed and you must fix it.
327
+ const systemPrompt = `You are Wolverine, an autonomous Node.js server repair agent. A server has an error and you must diagnose and fix it.
328
+
329
+ You are NOT just a code editor — you are a full server doctor. Errors can be code bugs, missing dependencies, database problems, misplaced files, configuration issues, port conflicts, permission errors, corrupted state, or environment problems. Use your tools to investigate the ACTUAL root cause before attempting a fix.
239
330
 
240
- You have a full tool harness for investigating and fixing issues:
331
+ ## YOUR TOOLS
241
332
 
242
333
  FILE TOOLS:
243
334
  - read_file: Read any file (with optional offset/limit for large files)
244
- - write_file: Write a complete file
335
+ - write_file: Write a complete file (creates parent dirs)
245
336
  - edit_file: Surgical find-and-replace (preferred for small fixes)
246
- - glob_files: Find files by pattern (e.g. "**/*.js", "src/**/*.config.*")
337
+ - glob_files: Find files by pattern (e.g. "**/*.js", "server/**/*.json")
247
338
  - grep_code: Search code with regex across the project
339
+ - list_dir: List directory contents (check structure, find misplaced files)
340
+ - move_file: Move or rename files (fix misplaced files)
248
341
 
249
342
  SHELL TOOLS:
250
- - bash_exec: Run any shell command (sandboxed to project dir)
251
- - git_log: View recent commits
343
+ - bash_exec: Run any shell command (npm install, chmod, kill, etc.)
344
+ - git_log: View recent commits (what changed recently?)
252
345
  - git_diff: View uncommitted changes
253
346
 
347
+ DATABASE TOOLS:
348
+ - inspect_db: List tables, show schema, or run SELECT on SQLite databases
349
+ - run_db_fix: Run UPDATE/DELETE/INSERT/ALTER on SQLite databases (backs up first)
350
+
351
+ DIAGNOSTICS:
352
+ - check_port: Check if a port is in use and by what process
353
+ - check_env: Check environment variables (values auto-redacted for security)
354
+
254
355
  RESEARCH:
255
- - web_fetch: Fetch a URL (docs, npm packages, Stack Overflow)
256
-
257
- Use these tools systematically:
258
- 1. Understand the error and its root cause
259
- 2. Explore related files (imports, configs, dependencies, schemas)
260
- 3. Check git history if relevant
261
- 4. Fix the issue across ALL affected files
262
- 5. You can edit ANY file type: .js, .json, .sql, .yaml, .env, .dockerfile, .sh, etc.
263
- 6. Prefer edit_file for small targeted fixes, write_file for major changes
264
- 7. Use grep_code to find all usages before renaming something
265
- 8. Use bash_exec to run tests, install packages, or check dependencies
266
-
267
- CRITICAL Not every crash is a code bug. Choose the right fix:
268
-
269
- | Error Pattern | Root Cause | Correct Fix |
270
- |---|---|---|
271
- | Cannot find module 'X' | Missing npm package | bash_exec: npm install X |
272
- | Cannot find module './X' | Wrong import path | edit_file: fix the require/import path |
273
- | ENOENT: no such file | Missing config/data file | write_file: create the missing file |
274
- | EACCES/EPERM | Permission denied | bash_exec: chmod or fix ownership |
275
- | EADDRINUSE | Port conflict | bash_exec: kill process on port, or edit config |
276
- | SyntaxError | Bad code | edit_file: fix the syntax |
277
- | TypeError/ReferenceError | Logic bug | edit_file: fix the code |
278
- | MODULE_NOT_FOUND + node_modules | Corrupted install | bash_exec: rm -rf node_modules && npm install |
279
-
280
- ALWAYS check package.json before editing imports. If a module isn't a local file, use bash_exec to install it.
281
-
282
- Rules:
283
- - Read files before modifying them
284
- - Make minimal, targeted changes
285
- - Use bash_exec for operational fixes (npm install, chmod, config creation)
286
- - When done, call the "done" tool with a summary
287
-
288
- Project root: ${this.cwd}
289
- Primary crash file: ${primaryFile}`;
356
+ - web_fetch: Fetch a URL (docs, npm packages, error solutions)
357
+
358
+ ## DIAGNOSIS FLOWCHART — follow this order:
359
+
360
+ 1. READ THE ERROR CAREFULLY what type of problem is this?
361
+ 2. If no file path: use glob_files, grep_code, list_dir to investigate
362
+ 3. If file path: read_file to see the code, then investigate related files
363
+
364
+ ## ERROR FIX STRATEGY TABLE
365
+
366
+ | Error Pattern | Category | Diagnostic Steps | Fix |
367
+ |---|---|---|---|
368
+ | Cannot find module 'X' | DEPENDENCY | check package.json | bash_exec: npm install X |
369
+ | Cannot find module './X' | IMPORT | glob_files to find real path | edit_file: fix require path |
370
+ | ENOENT: no such file | FILE MISSING | list_dir to check structure | write_file or move_file |
371
+ | EACCES/EPERM | PERMISSION | bash_exec: ls -la | bash_exec: chmod 755 |
372
+ | EADDRINUSE | PORT | check_port to find blocker | bash_exec: kill PID, or edit config |
373
+ | ECONNREFUSED | SERVICE DOWN | check if DB/service is running | bash_exec: start service |
374
+ | SyntaxError | CODE | read_file to see context | edit_file: fix syntax |
375
+ | TypeError/ReferenceError | CODE | read_file + grep_code | edit_file: fix logic |
376
+ | ER_NO_SUCH_TABLE | DATABASE | inspect_db: tables | run_db_fix: CREATE TABLE or bash_exec migration |
377
+ | SQLITE_ERROR/CONSTRAINT | DATABASE | inspect_db: schema + query | run_db_fix: UPDATE/ALTER |
378
+ | Invalid JSON | CONFIG | read_file the JSON | edit_file: fix JSON syntax |
379
+ | ENOMEM / heap out of memory | RESOURCE | check_env for NODE_OPTIONS | edit config or bash_exec: increase limit |
380
+ | Missing env variable | CONFIG | check_env | write_file .env or edit config |
381
+ | Wrong file location | STRUCTURE | list_dir + glob_files | move_file to correct location |
382
+ | Corrupted node_modules | DEPENDENCY | bash_exec: ls node_modules | bash_exec: rm -rf node_modules && npm install |
383
+ | Git conflict markers | CODE | grep_code: <<<<<<< | edit_file: resolve conflicts |
384
+
385
+ ## RULES
386
+
387
+ 1. INVESTIGATE FIRST — never guess. Read files, check directories, inspect databases before fixing.
388
+ 2. Read files before modifying them. Check package.json before editing imports.
389
+ 3. Make minimal, targeted changes — fix the root cause, not symptoms.
390
+ 4. Use the right tool: bash_exec for operational fixes, edit_file for code, run_db_fix for data.
391
+ 5. You can edit ANY file type: .js, .json, .sql, .yaml, .env, .toml, .sh, .dockerfile, etc.
392
+ 6. If the error has no file path, USE YOUR TOOLS to find the problem (glob, grep, list_dir, inspect_db).
393
+ 7. When done, call the "done" tool with a summary of what you found and fixed.
394
+
395
+ Project root: ${this.cwd}${primaryFile ? `\nPrimary crash file: ${primaryFile}` : ""}`;
396
+
397
+ // Build user message — handle cases with and without a specific file
398
+ let userContent = `The server has an error:\n\n**Error:** ${errorMessage}\n\n**Stack Trace:**\n\`\`\`\n${stackTrace}\n\`\`\``;
399
+ if (primaryFile && sourceCode) {
400
+ userContent += `\n\n**Primary file (${primaryFile}):**\n\`\`\`\n${sourceCode}\n\`\`\``;
401
+ } else if (!primaryFile) {
402
+ userContent += `\n\n**No specific file identified.** Use your investigation tools (glob_files, grep_code, list_dir, inspect_db, check_env, check_port) to find the root cause.`;
403
+ }
404
+ if (brainContext) userContent += `\n\n**Context from Wolverine Brain:**\n${brainContext}`;
405
+ userContent += `\n\nDiagnose the root cause, investigate with your tools, and fix the issue.`;
290
406
 
291
407
  this.messages = [
292
408
  { role: "system", content: systemPrompt },
293
- {
294
- role: "user",
295
- content: `The server crashed with this error:\n\n**Error:** ${errorMessage}\n\n**Stack Trace:**\n\`\`\`\n${stackTrace}\n\`\`\`\n\n**Primary file (${primaryFile}):**\n\`\`\`\n${sourceCode}\n\`\`\`${brainContext ? `\n\n**Context from Wolverine Brain:**\n${brainContext}` : ""}\n\nAnalyze the error, explore any related files you need, and fix the issue. Use your tools.`,
296
- },
409
+ { role: "user", content: userContent },
297
410
  ];
298
411
 
299
- this.filesRead.add(primaryFile);
412
+ if (primaryFile) this.filesRead.add(primaryFile);
300
413
 
301
414
  // Merge MCP tools with built-in tools
302
415
  const allTools = [...TOOL_DEFINITIONS];
@@ -411,6 +524,12 @@ Primary crash file: ${primaryFile}`;
411
524
  case "git_log": return this._gitLog(args);
412
525
  case "git_diff": return this._gitDiff(args);
413
526
  case "web_fetch": return this._webFetch(args);
527
+ case "list_dir": return this._listDir(args);
528
+ case "move_file": return this._moveFile(args);
529
+ case "check_port": return this._checkPort(args);
530
+ case "check_env": return this._checkEnv(args);
531
+ case "inspect_db": return this._inspectDb(args);
532
+ case "run_db_fix": return this._runDbFix(args);
414
533
  case "done": return this._done(args);
415
534
  // Legacy aliases
416
535
  case "list_files": return this._globFiles({ pattern: (args.dir || ".") + "/*" + (args.pattern || "") });
@@ -690,6 +809,129 @@ Primary crash file: ${primaryFile}`;
690
809
 
691
810
  // ── COMPLETION ──
692
811
 
812
+ // ── DIAGNOSTIC TOOLS ──
813
+
814
+ _listDir(args) {
815
+ const dirPath = path.resolve(this.cwd, args.path || ".");
816
+ try {
817
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
818
+ const lines = entries.map(e => {
819
+ try {
820
+ const stat = fs.statSync(path.join(dirPath, e.name));
821
+ const size = e.isDirectory() ? "DIR" : `${Math.round(stat.size / 1024)}KB`;
822
+ return `${e.isDirectory() ? "📁" : "📄"} ${e.name} (${size})`;
823
+ } catch { return `${e.name} (?)` ; }
824
+ });
825
+ console.log(chalk.gray(` 📁 Listed ${lines.length} entries in ${args.path || "."}`));
826
+ return { content: lines.join("\n") || "(empty directory)" };
827
+ } catch (e) { return { content: `Error: ${e.message}` }; }
828
+ }
829
+
830
+ _moveFile(args) {
831
+ if (this._isProtectedPath(args.from) || this._isProtectedPath(args.to)) {
832
+ return { content: "BLOCKED: Cannot move protected files" };
833
+ }
834
+ const from = path.resolve(this.cwd, args.from);
835
+ const to = path.resolve(this.cwd, args.to);
836
+ try {
837
+ fs.mkdirSync(path.dirname(to), { recursive: true });
838
+ fs.renameSync(from, to);
839
+ this.filesModified.push(args.to);
840
+ console.log(chalk.green(` 📦 Moved: ${args.from} → ${args.to}`));
841
+ return { content: `Moved ${args.from} → ${args.to}` };
842
+ } catch (e) { return { content: `Error moving: ${e.message}` }; }
843
+ }
844
+
845
+ _checkPort(args) {
846
+ const port = args.port;
847
+ try {
848
+ const platform = process.platform;
849
+ let cmd;
850
+ if (platform === "win32") {
851
+ cmd = `netstat -ano | findstr :${port}`;
852
+ } else {
853
+ cmd = `lsof -i :${port} 2>/dev/null || ss -tlnp 2>/dev/null | grep :${port}`;
854
+ }
855
+ const result = execSync(cmd, { timeout: 5000, stdio: "pipe" }).toString().trim();
856
+ console.log(chalk.gray(` 🔌 Port ${port}: ${result ? "IN USE" : "free"}`));
857
+ return { content: result || `Port ${port} is free` };
858
+ } catch { return { content: `Port ${port} appears free (no listeners found)` }; }
859
+ }
860
+
861
+ _checkEnv(args) {
862
+ const { redact } = require("../security/secret-redactor");
863
+ if (args.variable) {
864
+ const val = process.env[args.variable];
865
+ const display = val ? redact(val) : "(not set)";
866
+ return { content: `${args.variable}=${display}` };
867
+ }
868
+ // List all env vars with redacted values
869
+ const keys = Object.keys(process.env).sort();
870
+ const lines = keys.map(k => {
871
+ const val = process.env[k];
872
+ return `${k}=${val && val.length > 50 ? "(set, " + val.length + " chars)" : redact(val || "")}`;
873
+ });
874
+ return { content: lines.join("\n") };
875
+ }
876
+
877
+ _inspectDb(args) {
878
+ const dbPath = path.resolve(this.cwd, args.db_path);
879
+ try {
880
+ let Database;
881
+ try { Database = require("better-sqlite3"); } catch {
882
+ return { content: "better-sqlite3 not installed. Run: npm install better-sqlite3" };
883
+ }
884
+ const db = new Database(dbPath, { readonly: true });
885
+ let result;
886
+ if (args.action === "tables") {
887
+ const tables = db.prepare("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name").all();
888
+ result = tables.map(t => t.name).join("\n") || "(no tables)";
889
+ } else if (args.action === "schema") {
890
+ const schemas = db.prepare("SELECT sql FROM sqlite_master WHERE type='table' AND sql IS NOT NULL").all();
891
+ result = schemas.map(s => s.sql).join("\n\n") || "(no tables)";
892
+ } else if (args.action === "query") {
893
+ if (!args.sql) return { content: "Error: sql required for query action" };
894
+ const upper = args.sql.trim().toUpperCase();
895
+ if (!upper.startsWith("SELECT") && !upper.startsWith("PRAGMA")) {
896
+ return { content: "BLOCKED: inspect_db only allows SELECT/PRAGMA. Use run_db_fix for writes." };
897
+ }
898
+ const rows = db.prepare(args.sql).all();
899
+ result = JSON.stringify(rows.slice(0, 50), null, 2);
900
+ if (rows.length > 50) result += `\n... (${rows.length} total rows, showing first 50)`;
901
+ } else {
902
+ result = "Unknown action. Use: tables, schema, or query";
903
+ }
904
+ db.close();
905
+ const { redact } = require("../security/secret-redactor");
906
+ console.log(chalk.gray(` 🗃️ DB ${args.action}: ${args.db_path}`));
907
+ return { content: redact(result) };
908
+ } catch (e) { return { content: `DB error: ${e.message}` }; }
909
+ }
910
+
911
+ _runDbFix(args) {
912
+ const dbPath = path.resolve(this.cwd, args.db_path);
913
+ try {
914
+ let Database;
915
+ try { Database = require("better-sqlite3"); } catch {
916
+ return { content: "better-sqlite3 not installed. Run: npm install better-sqlite3" };
917
+ }
918
+ // Block dangerous operations
919
+ const upper = args.sql.trim().toUpperCase();
920
+ if (upper.startsWith("DROP DATABASE") || upper.includes("DROP TABLE sqlite_")) {
921
+ return { content: "BLOCKED: Cannot drop system tables" };
922
+ }
923
+ // Backup the DB file first
924
+ const backupPath = dbPath + ".wolverine-backup";
925
+ fs.copyFileSync(dbPath, backupPath);
926
+ const db = new Database(dbPath);
927
+ const result = db.prepare(args.sql).run();
928
+ db.close();
929
+ this.filesModified.push(args.db_path);
930
+ console.log(chalk.green(` 🗃️ DB fix applied: ${args.sql.slice(0, 60)} (changes: ${result.changes})`));
931
+ return { content: `SQL executed. Changes: ${result.changes}. Backup at: ${backupPath}` };
932
+ } catch (e) { return { content: `DB error: ${e.message}` }; }
933
+ }
934
+
693
935
  _done(args) {
694
936
  console.log(chalk.green(` ✅ Agent done: ${args.summary}`));
695
937
  if (this.logger) {
@@ -23,13 +23,13 @@ const { getModel } = require("../core/models");
23
23
 
24
24
  // Tool restrictions per agent type (claw-code: allowed_tools_for_subagent)
25
25
  const AGENT_TOOL_SETS = {
26
- explore: ["read_file", "glob_files", "grep_code", "git_log", "git_diff", "done"],
27
- plan: ["read_file", "glob_files", "grep_code", "search_brain", "done"],
28
- fix: ["read_file", "write_file", "edit_file", "glob_files", "grep_code", "bash_exec", "done"],
29
- verify: ["read_file", "glob_files", "grep_code", "bash_exec", "done"],
26
+ explore: ["read_file", "glob_files", "grep_code", "git_log", "git_diff", "list_dir", "check_env", "check_port", "inspect_db", "done"],
27
+ plan: ["read_file", "glob_files", "grep_code", "list_dir", "inspect_db", "check_env", "search_brain", "done"],
28
+ fix: ["read_file", "write_file", "edit_file", "glob_files", "grep_code", "bash_exec", "move_file", "run_db_fix", "done"],
29
+ verify: ["read_file", "glob_files", "grep_code", "bash_exec", "inspect_db", "check_port", "done"],
30
30
  research: ["read_file", "grep_code", "web_fetch", "search_brain", "done"],
31
- security: ["read_file", "glob_files", "grep_code", "done"],
32
- database: ["read_file", "write_file", "edit_file", "glob_files", "grep_code", "bash_exec", "done"],
31
+ security: ["read_file", "glob_files", "grep_code", "inspect_db", "done"],
32
+ database: ["read_file", "write_file", "edit_file", "glob_files", "grep_code", "bash_exec", "inspect_db", "run_db_fix", "done"],
33
33
  };
34
34
 
35
35
  // Default model + budget per agent type
@@ -215,6 +215,18 @@ const SEED_DOCS = [
215
215
  text: "Error Monitor: detects caught 500 errors that don't crash the process. Most production bugs are caught by Fastify/Express error handlers — the server stays alive but routes return 500. Wolverine's crash-based heal pipeline never triggers for these. ErrorMonitor tracks 5xx errors per route via IPC from child process. After N consecutive 500s within a time window (default: 3 failures in 30s), triggers the heal pipeline without killing the server. Error hook auto-injected via --require preload (no user code changes). Cooldown prevents heal spam (default: 60s per route). Stats available in dashboard and telemetry. Config: WOLVERINE_ERROR_THRESHOLD, WOLVERINE_ERROR_WINDOW_MS, WOLVERINE_ERROR_COOLDOWN_MS.",
216
216
  metadata: { topic: "error-monitor" },
217
217
  },
218
+ {
219
+ text: "Agent tool harness v2: 16 built-in tools. FILE: read_file, write_file, edit_file, glob_files, grep_code, list_dir, move_file. SHELL: bash_exec, git_log, git_diff. DATABASE: inspect_db (list tables, show schema, run SELECT), run_db_fix (UPDATE/DELETE/INSERT/ALTER with auto-backup). DIAGNOSTICS: check_port (find what's using a port), check_env (list/check env vars, values redacted). RESEARCH: web_fetch. COMPLETION: done. Sub-agents get restricted sets: explorer gets diagnostics (list_dir, check_env, check_port, inspect_db), fixer gets action tools (bash_exec, move_file, run_db_fix), verifier gets inspection tools.",
220
+ metadata: { topic: "agent-tools-v2" },
221
+ },
222
+ {
223
+ text: "Server problem categories the agent can fix: CODE BUGS (SyntaxError, TypeError, ReferenceError → edit_file), DEPENDENCIES (Cannot find module → npm install, corrupted node_modules → rm + reinstall), DATABASE (invalid entries → run_db_fix UPDATE, missing table → CREATE TABLE, schema mismatch → ALTER TABLE, constraint violation → fix data or schema), CONFIG (invalid JSON → edit_file, missing env vars → write .env, wrong port → edit config), FILESYSTEM (misplaced files → move_file, missing directories → bash_exec mkdir, wrong permissions → chmod), NETWORK (port conflict → check_port + kill, service down → restart, connection refused → check config), STATE (corrupted cache → delete + restart, stale locks → remove lock file, git conflicts → resolve markers). The agent investigates before fixing — reads files, checks directories, inspects databases, never guesses.",
224
+ metadata: { topic: "server-problems" },
225
+ },
226
+ {
227
+ text: "Heal pipeline no longer requires a file path. When no file is identified from the error (database errors, config problems, port conflicts), the pipeline skips fast path and goes straight to the agent, which uses investigation tools (glob_files, grep_code, list_dir, inspect_db, check_env, check_port) to find the root cause. Agent verification for no-file errors: if agent made changes or ran commands, trust the agent's assessment. For file-based errors, verification uses syntax check + boot probe as before.",
228
+ metadata: { topic: "fileless-heal" },
229
+ },
218
230
  ];
219
231
 
220
232
  class Brain {
@@ -47,30 +47,25 @@ async function heal({ stderr, cwd, sandbox, notifier, rateLimiter, backupManager
47
47
 
48
48
  if (logger) logger.debug(EVENT_TYPES.HEAL_PARSE, `Parsed: ${parsed.errorMessage}`, { file: parsed.filePath, line: parsed.line });
49
49
 
50
- if (!parsed.filePath) {
51
- console.log(chalk.red(" Could not identify the source file from the error. Skipping repair."));
52
- if (logger) logger.error(EVENT_TYPES.HEAL_FAILED, "Could not parse file path from error");
53
- return { healed: false, explanation: "Could not parse file path from error" };
54
- }
55
-
56
- // 2. Sandbox check
57
- try {
58
- sandbox.resolve(parsed.filePath);
59
- } catch (e) {
60
- if (e instanceof SandboxViolationError) {
61
- console.log(chalk.red(` 🔒 SANDBOX: ${e.message}`));
62
- if (logger) logger.error(EVENT_TYPES.SECURITY_SANDBOX_VIOLATION, e.message, { file: parsed.filePath });
63
- return { healed: false, explanation: "File outside sandbox — access denied" };
50
+ // File path is optional — some errors (database, config, port) don't trace to a file.
51
+ // When no file is found, skip fast path and go straight to agent investigation.
52
+ let hasFile = false;
53
+ if (parsed.filePath) {
54
+ // 2. Sandbox check
55
+ try {
56
+ sandbox.resolve(parsed.filePath);
57
+ hasFile = sandbox.exists(parsed.filePath);
58
+ } catch (e) {
59
+ if (e instanceof SandboxViolationError) {
60
+ console.log(chalk.red(` 🔒 SANDBOX: ${e.message}`));
61
+ if (logger) logger.error(EVENT_TYPES.SECURITY_SANDBOX_VIOLATION, e.message, { file: parsed.filePath });
62
+ return { healed: false, explanation: "File outside sandbox — access denied" };
63
+ }
64
+ throw e;
64
65
  }
65
- throw e;
66
- }
67
-
68
- if (!sandbox.exists(parsed.filePath)) {
69
- console.log(chalk.red(` Source file not found: ${parsed.filePath}`));
70
- return { healed: false, explanation: "Source file not found" };
71
66
  }
72
67
 
73
- console.log(chalk.cyan(` File: ${parsed.filePath}`));
68
+ console.log(chalk.cyan(` File: ${parsed.filePath || "(no file — agent will investigate)"}`));
74
69
  console.log(chalk.cyan(` Line: ${parsed.line || "unknown"}`));
75
70
  console.log(chalk.cyan(` Error: ${parsed.errorMessage}`));
76
71
  console.log(chalk.cyan(` Type: ${parsed.errorType || "unknown"}`));
@@ -130,8 +125,8 @@ async function heal({ stderr, cwd, sandbox, notifier, rateLimiter, backupManager
130
125
  return { healed: true, explanation: opsFix.action, mode: "operational" };
131
126
  }
132
127
 
133
- // 5. Read the source file + get brain context
134
- const sourceCode = sandbox.readFile(parsed.filePath);
128
+ // 5. Read the source file (if available) + get brain context
129
+ const sourceCode = hasFile ? sandbox.readFile(parsed.filePath) : "";
135
130
 
136
131
  let brainContext = "";
137
132
  // Inject relevant skill context (claw-code: pre-enrich prompt with matched tools)
@@ -175,15 +170,16 @@ async function heal({ stderr, cwd, sandbox, notifier, rateLimiter, backupManager
175
170
  onAttempt: async (iteration, researchCtx) => {
176
171
  // Create backup for this attempt
177
172
  // Full server/ backup — includes all files, configs, databases
178
- const bid = backupManager.createBackup(null);
173
+ const bid = backupManager.createBackup(`heal attempt ${iteration}: ${parsed.errorMessage.slice(0, 60)}`);
179
174
  backupManager.setErrorSignature(bid, errorSignature);
180
175
  if (logger) logger.info(EVENT_TYPES.BACKUP_CREATED, `Backup ${bid} (iteration ${iteration})`, { backupId: bid });
181
176
 
182
177
  const fullContext = [brainContext, researchContext, researchCtx].filter(Boolean).join("\n");
183
178
 
184
179
  let result;
185
- if (iteration === 1) {
180
+ if (iteration === 1 && hasFile) {
186
181
  // Fast path — CODING_MODEL, single file + optional commands
182
+ // Only available when we have a specific file to fix
187
183
  console.log(chalk.yellow(` 🧠 Fast path (${getModel("coding")})...`));
188
184
  try {
189
185
  const repair = await requestRepair({
@@ -235,8 +231,8 @@ async function heal({ stderr, cwd, sandbox, notifier, rateLimiter, backupManager
235
231
  backupManager.rollbackTo(bid);
236
232
  return { healed: false, explanation: `Fast path error: ${err.message}` };
237
233
  }
238
- } else if (iteration === 2) {
239
- // Iteration 2: Single agent REASONING_MODEL
234
+ } else if (iteration <= 2) {
235
+ // Agent path REASONING_MODEL (also handles iteration 1 when no file)
240
236
  console.log(chalk.magenta(` 🤖 Agent path (${getModel("reasoning")})...`));
241
237
  const agent = new AgentEngine({
242
238
  sandbox, logger, cwd, mcp,
@@ -251,9 +247,17 @@ async function heal({ stderr, cwd, sandbox, notifier, rateLimiter, backupManager
251
247
  });
252
248
  rateLimiter.record(errorSignature, agentResult.totalTokens);
253
249
 
254
- if (agentResult.success && agentResult.filesModified.length > 0) {
255
- const verification = await verifyFix(parsed.filePath, cwd, errorSignature);
256
- if (verification.verified) {
250
+ if (agentResult.success) {
251
+ // Verify: if we have a file, do syntax + boot check. Otherwise just boot probe.
252
+ if (hasFile) {
253
+ const verification = await verifyFix(parsed.filePath, cwd, errorSignature);
254
+ if (verification.verified) {
255
+ backupManager.markVerified(bid);
256
+ rateLimiter.clearSignature(errorSignature);
257
+ return { healed: true, explanation: agentResult.summary, backupId: bid, mode: "agent", agentStats: agentResult };
258
+ }
259
+ } else if (agentResult.filesModified.length > 0 || agentResult.toolCalls?.some(t => t.name === "bash_exec")) {
260
+ // No specific file but agent made changes or ran commands — trust it
257
261
  backupManager.markVerified(bid);
258
262
  rateLimiter.clearSignature(errorSignature);
259
263
  return { healed: true, explanation: agentResult.summary, backupId: bid, mode: "agent", agentStats: agentResult };
@@ -267,14 +271,20 @@ async function heal({ stderr, cwd, sandbox, notifier, rateLimiter, backupManager
267
271
  console.log(chalk.magenta(` 🤖 Sub-agent path (explore → plan → fix)...`));
268
272
 
269
273
  const subResult = await exploreAndFix(
270
- `Error: ${parsed.errorMessage}\nFile: ${parsed.filePath}\nStack: ${parsed.stackTrace?.slice(0, 300)}`,
274
+ `Error: ${parsed.errorMessage}\n${parsed.filePath ? "File: " + parsed.filePath + "\n" : ""}Stack: ${parsed.stackTrace?.slice(0, 300)}`,
271
275
  { sandbox, logger, cwd, mcp, brainContext: fullContext }
272
276
  );
273
277
  rateLimiter.record(errorSignature, subResult.totalTokens);
274
278
 
275
- if (subResult.success && subResult.filesModified.length > 0) {
276
- const verification = await verifyFix(parsed.filePath, cwd, errorSignature);
277
- if (verification.verified) {
279
+ if (subResult.success) {
280
+ if (hasFile) {
281
+ const verification = await verifyFix(parsed.filePath, cwd, errorSignature);
282
+ if (verification.verified) {
283
+ backupManager.markVerified(bid);
284
+ rateLimiter.clearSignature(errorSignature);
285
+ return { healed: true, explanation: subResult.summary, backupId: bid, mode: "sub-agents", agentStats: subResult };
286
+ }
287
+ } else {
278
288
  backupManager.markVerified(bid);
279
289
  rateLimiter.clearSignature(errorSignature);
280
290
  return { healed: true, explanation: subResult.summary, backupId: bid, mode: "sub-agents", agentStats: subResult };