wolverine-ai 1.7.0 → 1.8.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 +90 -28
- package/package.json +1 -1
- package/src/agent/agent-engine.js +80 -1
- package/src/agent/sub-agents.js +3 -3
- package/src/brain/brain.js +5 -1
- package/src/core/wolverine.js +11 -24
- package/src/index.js +4 -0
- package/src/skills/deps.js +449 -0
package/README.md
CHANGED
|
@@ -185,24 +185,26 @@ Most production bugs don't crash the process — Fastify/Express catch them and
|
|
|
185
185
|
```
|
|
186
186
|
Route returns 500 (process still alive)
|
|
187
187
|
→ Error hook reports to parent via IPC (auto-injected, zero user code changes)
|
|
188
|
-
→ ErrorMonitor tracks
|
|
189
|
-
→
|
|
188
|
+
→ ErrorMonitor tracks errors per normalized route (/api/users/:id)
|
|
189
|
+
→ Single error triggers heal pipeline immediately (configurable threshold)
|
|
190
190
|
→ Fix applied → server restarted → route prober verifies fix
|
|
191
191
|
```
|
|
192
192
|
|
|
193
193
|
| Setting | Default | Env Variable |
|
|
194
194
|
|---------|---------|-------------|
|
|
195
|
-
| Failure threshold |
|
|
195
|
+
| Failure threshold | 1 | `WOLVERINE_ERROR_THRESHOLD` |
|
|
196
196
|
| Time window | 30s | `WOLVERINE_ERROR_WINDOW_MS` |
|
|
197
197
|
| Cooldown per route | 60s | `WOLVERINE_ERROR_COOLDOWN_MS` |
|
|
198
198
|
|
|
199
|
+
Routes are auto-normalized: `/api/users/123` and `/api/users/456` aggregate as `/api/users/:id`.
|
|
200
|
+
|
|
199
201
|
The error hook auto-patches Fastify and Express via `--require` preload. No middleware, no code changes to your server.
|
|
200
202
|
|
|
201
203
|
---
|
|
202
204
|
|
|
203
205
|
## Agent Tool Harness
|
|
204
206
|
|
|
205
|
-
The AI agent has
|
|
207
|
+
The AI agent has 18 built-in tools (inspired by [claw-code](https://github.com/ultraworkers/claw-code)):
|
|
206
208
|
|
|
207
209
|
| Tool | Category | Description |
|
|
208
210
|
|------|----------|-------------|
|
|
@@ -220,6 +222,8 @@ The AI agent has 16 built-in tools (inspired by [claw-code](https://github.com/u
|
|
|
220
222
|
| `run_db_fix` | Database | UPDATE/DELETE/INSERT/ALTER on SQLite (auto-backup before write) |
|
|
221
223
|
| `check_port` | Diagnostic | Check if a port is in use and by what process |
|
|
222
224
|
| `check_env` | Diagnostic | Check environment variables (values auto-redacted) |
|
|
225
|
+
| `audit_deps` | Deps | Full health check: vulnerabilities, outdated, peer conflicts, unused |
|
|
226
|
+
| `check_migration` | Deps | Known upgrade paths (express→fastify, moment→dayjs, etc.) |
|
|
223
227
|
| `web_fetch` | Research | Fetch URL content for documentation/research |
|
|
224
228
|
| `done` | Control | Signal task completion with summary |
|
|
225
229
|
|
|
@@ -315,7 +319,7 @@ Reasoning models (`o-series`, `gpt-5-nano`) automatically get 4x token limits to
|
|
|
315
319
|
| **Admin Auth** | Dashboard requires key + IP allowlist. Localhost always allowed. Remote IPs via `WOLVERINE_ADMIN_IPS` env var or `POST /api/admin/add-ip` at runtime. Timing-safe comparison, lockout after 10 failures |
|
|
316
320
|
| **Rate Limiter** | Sliding window, min gap, hourly budget, exponential backoff on error loops |
|
|
317
321
|
| **MCP Security** | Per-server tool allowlists, arg sanitization, result injection scanning |
|
|
318
|
-
| **SQL Skill** | `sqlGuard()`
|
|
322
|
+
| **SQL Skill** | `sqlGuard()` blocks 15 injection pattern families; `idempotencyGuard()` prevents double-fire in cluster mode |
|
|
319
323
|
|
|
320
324
|
---
|
|
321
325
|
|
|
@@ -354,17 +358,35 @@ The `📊 Analytics` dashboard panel shows memory/CPU charts, route health statu
|
|
|
354
358
|
|
|
355
359
|
---
|
|
356
360
|
|
|
357
|
-
##
|
|
361
|
+
## Cluster Mode
|
|
358
362
|
|
|
359
|
-
|
|
363
|
+
The server handles its own clustering. Wolverine is the single process manager — it spawns your server, which forks workers internally.
|
|
360
364
|
|
|
361
365
|
```bash
|
|
362
|
-
|
|
363
|
-
wolverine server/index.js
|
|
364
|
-
|
|
365
|
-
|
|
366
|
+
# Enable cluster mode
|
|
367
|
+
WOLVERINE_CLUSTER=true wolverine server/index.js
|
|
368
|
+
|
|
369
|
+
# System info (cores, RAM, recommended workers)
|
|
370
|
+
wolverine --info
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
**How it works:**
|
|
374
|
+
```
|
|
375
|
+
Wolverine (single process manager)
|
|
376
|
+
└── spawns server/index.js
|
|
377
|
+
├── WOLVERINE_CLUSTER=false → single server (default)
|
|
378
|
+
└── WOLVERINE_CLUSTER=true → master forks N workers
|
|
379
|
+
├── Worker 1 (port 3000, reusePort)
|
|
380
|
+
├── Worker 2 (port 3000, reusePort)
|
|
381
|
+
└── Worker N (port 3000, reusePort)
|
|
366
382
|
```
|
|
367
383
|
|
|
384
|
+
- **`WOLVERINE_RECOMMENDED_WORKERS`** auto-set based on CPU cores/RAM
|
|
385
|
+
- Workers share port 3000 via `reusePort` — OS handles load balancing
|
|
386
|
+
- Dead workers auto-respawn by the master process
|
|
387
|
+
- Wolverine kills the **entire process tree** on restart (no orphaned workers)
|
|
388
|
+
- **Idempotency protection** prevents double-fire across workers (see below)
|
|
389
|
+
|
|
368
390
|
**System detection:**
|
|
369
391
|
- CPU cores, model, speed
|
|
370
392
|
- Total/free RAM, disk space
|
|
@@ -372,18 +394,6 @@ wolverine --info # show system capabilities
|
|
|
372
394
|
- Container environment (Docker, Kubernetes)
|
|
373
395
|
- Cloud provider (AWS, GCP, Azure, Railway, Fly, Render, Heroku)
|
|
374
396
|
|
|
375
|
-
**Scaling rules:**
|
|
376
|
-
|
|
377
|
-
| Cores | Workers |
|
|
378
|
-
|-------|---------|
|
|
379
|
-
| 1 | 1 (no clustering) |
|
|
380
|
-
| 2 | 2 |
|
|
381
|
-
| 3-4 | cores - 1 |
|
|
382
|
-
| 5-8 | cores - 1, cap 6 |
|
|
383
|
-
| 9+ | cores / 2, cap 16 |
|
|
384
|
-
|
|
385
|
-
Workers auto-respawn on crash with exponential backoff (1s → 30s). Max 5 restarts per worker.
|
|
386
|
-
|
|
387
397
|
---
|
|
388
398
|
|
|
389
399
|
## Configuration
|
|
@@ -412,8 +422,8 @@ Startup:
|
|
|
412
422
|
- Auto-registers on first run, retries every 60s until platform responds
|
|
413
423
|
- Saves key to `.wolverine/platform-key` (survives restarts)
|
|
414
424
|
- Sends one ~2KB JSON POST every 60 seconds (5s timeout, non-blocking)
|
|
415
|
-
- Payload
|
|
416
|
-
- Platform
|
|
425
|
+
- Payload: `instanceId`, `server`, `process`, `routes`, `repairs`, `usage` (tokens/cost/calls + `byCategory` + `byModel` + `byTool`), `brain`, `backups`
|
|
426
|
+
- Platform aggregates across all servers: total tokens/cost by category, model, tool
|
|
417
427
|
- Secrets redacted before sending
|
|
418
428
|
- Offline-resilient: queues up to 1440 heartbeats locally, drains on reconnect
|
|
419
429
|
|
|
@@ -422,7 +432,7 @@ Startup:
|
|
|
422
432
|
**Override:** `WOLVERINE_PLATFORM_URL=https://your-own-platform.com`
|
|
423
433
|
**Opt out:** `WOLVERINE_TELEMETRY=false`
|
|
424
434
|
|
|
425
|
-
|
|
435
|
+
Telemetry payload includes: `instanceId`, `server`, `process`, `routes`, `repairs`, `usage` (by category/model/tool), `brain`, `backups`.
|
|
426
436
|
|
|
427
437
|
---
|
|
428
438
|
|
|
@@ -478,14 +488,66 @@ Full `server/` directory snapshots with lifecycle management:
|
|
|
478
488
|
Auto-discovered from `src/skills/`. Each skill exports metadata for the registry:
|
|
479
489
|
|
|
480
490
|
### SQL Skill (`src/skills/sql.js`)
|
|
481
|
-
- **sqlGuard()** — Express middleware blocking SQL injection (UNION, stacked queries, tautologies, timing attacks,
|
|
482
|
-
- **SafeDB** —
|
|
491
|
+
- **sqlGuard()** — Fastify/Express middleware blocking SQL injection (UNION, stacked queries, tautologies, timing attacks, 15 pattern families)
|
|
492
|
+
- **SafeDB** — Cluster-safe database with split read/write connections, FIFO write queue, WAL mode
|
|
493
|
+
- **idempotencyGuard()** — Prevents double-fire of write requests in cluster mode (see below)
|
|
494
|
+
- **db.idempotent(key, fn)** — Database-level dedup for critical writes (payments, orders)
|
|
483
495
|
- Auto-injected into agent prompts when building database features
|
|
484
496
|
|
|
497
|
+
### Dependency Manager (`src/skills/deps.js`)
|
|
498
|
+
- **diagnose()** — structured diagnosis of dependency errors before AI runs (zero tokens)
|
|
499
|
+
- **healthReport()** — full audit: vulnerabilities, outdated, peer conflicts, unused packages, lock file, health score
|
|
500
|
+
- **getMigration()** — known upgrade paths with code transformation patterns:
|
|
501
|
+
|
|
502
|
+
| From | To | Why |
|
|
503
|
+
|------|----|-----|
|
|
504
|
+
| `express` | `fastify` | 5.6x faster, async-first, built-in validation |
|
|
505
|
+
| `moment` | `dayjs` | Maintenance mode, 70KB → 2KB |
|
|
506
|
+
| `request` | `node-fetch` | Deprecated since 2020 |
|
|
507
|
+
| `body-parser` | built-in | Included in Express 4.16+ / Fastify |
|
|
508
|
+
| callbacks | `async/await` | Cleaner error handling, no callback hell |
|
|
509
|
+
|
|
485
510
|
Add new skills by creating a file in `src/skills/` with `SKILL_NAME`, `SKILL_DESCRIPTION`, `SKILL_KEYWORDS`, `SKILL_USAGE` exports.
|
|
486
511
|
|
|
487
512
|
---
|
|
488
513
|
|
|
514
|
+
## Idempotency (Double-Fire Protection)
|
|
515
|
+
|
|
516
|
+
In cluster mode, a retry or duplicate request can land on a different worker and execute twice. Two layers prevent this:
|
|
517
|
+
|
|
518
|
+
### Layer 1: HTTP Middleware
|
|
519
|
+
|
|
520
|
+
```javascript
|
|
521
|
+
const { idempotencyGuard, idempotencyAfterHook } = require("wolverine-ai");
|
|
522
|
+
|
|
523
|
+
fastify.addHook("preHandler", idempotencyGuard({ db, logger }));
|
|
524
|
+
fastify.addHook("onSend", idempotencyAfterHook(db));
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
- Client sends `X-Idempotency-Key: order-abc-123` header
|
|
528
|
+
- Without header: auto-generates key from `sha256(method + url + body)`
|
|
529
|
+
- First request: executes handler, caches response in shared SQLite table
|
|
530
|
+
- Duplicate: returns cached response with `X-Idempotency-Cached: true` header
|
|
531
|
+
- Safe methods (GET/HEAD/OPTIONS) always pass through
|
|
532
|
+
- Keys expire after 24h (configurable)
|
|
533
|
+
|
|
534
|
+
### Layer 2: Database-Level
|
|
535
|
+
|
|
536
|
+
```javascript
|
|
537
|
+
const result = await db.idempotent("charge-abc-123", (tx) => {
|
|
538
|
+
tx.run("INSERT INTO charges (id, amount) VALUES (?, ?)", ["abc-123", 99.99]);
|
|
539
|
+
tx.run("UPDATE balance SET amount = amount - ? WHERE user_id = ?", [99.99, 1]);
|
|
540
|
+
return { charged: true };
|
|
541
|
+
});
|
|
542
|
+
// result.executed = true (first time) or false (duplicate)
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
- Wraps `fn` in a transaction with idempotency key check
|
|
546
|
+
- All workers share the `_idempotency` table via WAL mode — globally consistent
|
|
547
|
+
- Auto-created on `db.connect()`, pruned via `db.pruneIdempotency()`
|
|
548
|
+
|
|
549
|
+
---
|
|
550
|
+
|
|
489
551
|
## MCP Integration
|
|
490
552
|
|
|
491
553
|
Connect external tools via [Model Context Protocol](https://modelcontextprotocol.io):
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wolverine-ai",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.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": {
|
|
@@ -261,6 +261,33 @@ const TOOL_DEFINITIONS = [
|
|
|
261
261
|
},
|
|
262
262
|
},
|
|
263
263
|
},
|
|
264
|
+
// ── DEPENDENCY MANAGEMENT ──
|
|
265
|
+
{
|
|
266
|
+
type: "function",
|
|
267
|
+
function: {
|
|
268
|
+
name: "audit_deps",
|
|
269
|
+
description: "Run a full dependency health check: npm audit (vulnerabilities), outdated packages, peer dep conflicts, unused packages, lock file status. Returns a health score and actionable fixes. Use BEFORE editing code when the error might be a dependency issue.",
|
|
270
|
+
parameters: {
|
|
271
|
+
type: "object",
|
|
272
|
+
properties: {},
|
|
273
|
+
required: [],
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
type: "function",
|
|
279
|
+
function: {
|
|
280
|
+
name: "check_migration",
|
|
281
|
+
description: "Check if a package has a known migration/upgrade path. Use when a deprecated API or old package is causing errors. Returns the recommended replacement and code transformation patterns.",
|
|
282
|
+
parameters: {
|
|
283
|
+
type: "object",
|
|
284
|
+
properties: {
|
|
285
|
+
package: { type: "string", description: "Package name to check (e.g. 'express', 'moment', 'request')" },
|
|
286
|
+
},
|
|
287
|
+
required: ["package"],
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
},
|
|
264
291
|
// ── COMPLETION ──
|
|
265
292
|
{
|
|
266
293
|
type: "function",
|
|
@@ -352,6 +379,10 @@ DIAGNOSTICS:
|
|
|
352
379
|
- check_port: Check if a port is in use and by what process
|
|
353
380
|
- check_env: Check environment variables (values auto-redacted for security)
|
|
354
381
|
|
|
382
|
+
DEPENDENCY MANAGEMENT:
|
|
383
|
+
- audit_deps: Full health check (vulnerabilities, outdated, peer conflicts, unused). Use FIRST for dependency errors.
|
|
384
|
+
- check_migration: Check if a package has a known upgrade path (express→fastify, moment→dayjs, etc.)
|
|
385
|
+
|
|
355
386
|
RESEARCH:
|
|
356
387
|
- web_fetch: Fetch a URL (docs, npm packages, error solutions)
|
|
357
388
|
|
|
@@ -365,7 +396,7 @@ RESEARCH:
|
|
|
365
396
|
|
|
366
397
|
| Error Pattern | Category | Diagnostic Steps | Fix |
|
|
367
398
|
|---|---|---|---|
|
|
368
|
-
| Cannot find module 'X' | DEPENDENCY | check package.json | bash_exec: npm install X |
|
|
399
|
+
| Cannot find module 'X' | DEPENDENCY | audit_deps first, check package.json | bash_exec: npm install X |
|
|
369
400
|
| Cannot find module './X' | IMPORT | glob_files to find real path | edit_file: fix require path |
|
|
370
401
|
| ENOENT: no such file | FILE MISSING | list_dir to check structure | write_file or move_file |
|
|
371
402
|
| EACCES/EPERM | PERMISSION | bash_exec: ls -la | bash_exec: chmod 755 |
|
|
@@ -530,6 +561,8 @@ Project root: ${this.cwd}${primaryFile ? `\nPrimary crash file: ${primaryFile}`
|
|
|
530
561
|
case "check_env": return this._checkEnv(args);
|
|
531
562
|
case "inspect_db": return this._inspectDb(args);
|
|
532
563
|
case "run_db_fix": return this._runDbFix(args);
|
|
564
|
+
case "audit_deps": return this._auditDeps(args);
|
|
565
|
+
case "check_migration": return this._checkMigration(args);
|
|
533
566
|
case "done": return this._done(args);
|
|
534
567
|
// Legacy aliases
|
|
535
568
|
case "list_files": return this._globFiles({ pattern: (args.dir || ".") + "/*" + (args.pattern || "") });
|
|
@@ -932,6 +965,52 @@ Project root: ${this.cwd}${primaryFile ? `\nPrimary crash file: ${primaryFile}`
|
|
|
932
965
|
} catch (e) { return { content: `DB error: ${e.message}` }; }
|
|
933
966
|
}
|
|
934
967
|
|
|
968
|
+
_auditDeps() {
|
|
969
|
+
try {
|
|
970
|
+
const { healthReport } = require("../skills/deps");
|
|
971
|
+
const report = healthReport(this.cwd);
|
|
972
|
+
const { redact } = require("../security/secret-redactor");
|
|
973
|
+
const lines = [
|
|
974
|
+
`Dependency Health Score: ${report.score}/100 (${report.summary})`,
|
|
975
|
+
"",
|
|
976
|
+
`Vulnerabilities: ${report.audit.vulnerabilities} (${report.audit.critical} critical, ${report.audit.high} high, ${report.audit.moderate} moderate)`,
|
|
977
|
+
report.audit.fixes.length > 0 ? `Fix: ${report.audit.fixes.join(", ")}` : "",
|
|
978
|
+
"",
|
|
979
|
+
`Outdated: ${report.outdated.length} packages`,
|
|
980
|
+
...report.outdated.slice(0, 10).map(p => ` ${p.name}: ${p.current} → ${p.latest}`),
|
|
981
|
+
report.outdated.length > 10 ? ` ... and ${report.outdated.length - 10} more` : "",
|
|
982
|
+
"",
|
|
983
|
+
`Peer Dependency Issues: ${report.peerDeps.length}`,
|
|
984
|
+
...report.peerDeps.slice(0, 5).map(p => ` ${p.package}: ${p.requires}`),
|
|
985
|
+
"",
|
|
986
|
+
`Unused Packages: ${report.unused.length}`,
|
|
987
|
+
report.unused.length > 0 ? ` ${report.unused.join(", ")}` : "",
|
|
988
|
+
"",
|
|
989
|
+
`Lock File: ${report.lockFile.healthy ? "OK" : report.lockFile.issue}`,
|
|
990
|
+
report.lockFile.fix ? ` Fix: ${report.lockFile.fix}` : "",
|
|
991
|
+
].filter(l => l !== undefined);
|
|
992
|
+
console.log(chalk.gray(` 📦 Deps audit: score ${report.score}/100, ${report.audit.vulnerabilities} vulns, ${report.outdated.length} outdated`));
|
|
993
|
+
return { content: redact(lines.join("\n")) };
|
|
994
|
+
} catch (e) { return { content: `Deps audit error: ${e.message}` }; }
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
_checkMigration(args) {
|
|
998
|
+
try {
|
|
999
|
+
const { getMigration } = require("../skills/deps");
|
|
1000
|
+
const migration = getMigration(args.package);
|
|
1001
|
+
if (!migration) return { content: `No known migration path for '${args.package}'.` };
|
|
1002
|
+
const lines = [
|
|
1003
|
+
`Migration: ${args.package} → ${migration.to}`,
|
|
1004
|
+
`Reason: ${migration.reason}`,
|
|
1005
|
+
"",
|
|
1006
|
+
"Code patterns:",
|
|
1007
|
+
...migration.patterns.map(p => ` ${p.from}\n → ${p.to}`),
|
|
1008
|
+
];
|
|
1009
|
+
console.log(chalk.gray(` 📦 Migration: ${args.package} → ${migration.to}`));
|
|
1010
|
+
return { content: lines.join("\n") };
|
|
1011
|
+
} catch (e) { return { content: `Migration check error: ${e.message}` }; }
|
|
1012
|
+
}
|
|
1013
|
+
|
|
935
1014
|
_done(args) {
|
|
936
1015
|
console.log(chalk.green(` ✅ Agent done: ${args.summary}`));
|
|
937
1016
|
if (this.logger) {
|
package/src/agent/sub-agents.js
CHANGED
|
@@ -23,9 +23,9 @@ 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", "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"],
|
|
26
|
+
explore: ["read_file", "glob_files", "grep_code", "git_log", "git_diff", "list_dir", "check_env", "check_port", "inspect_db", "audit_deps", "done"],
|
|
27
|
+
plan: ["read_file", "glob_files", "grep_code", "list_dir", "inspect_db", "check_env", "audit_deps", "check_migration", "search_brain", "done"],
|
|
28
|
+
fix: ["read_file", "write_file", "edit_file", "glob_files", "grep_code", "bash_exec", "move_file", "run_db_fix", "audit_deps", "done"],
|
|
29
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
31
|
security: ["read_file", "glob_files", "grep_code", "inspect_db", "done"],
|
package/src/brain/brain.js
CHANGED
|
@@ -228,9 +228,13 @@ const SEED_DOCS = [
|
|
|
228
228
|
metadata: { topic: "idempotency" },
|
|
229
229
|
},
|
|
230
230
|
{
|
|
231
|
-
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.",
|
|
231
|
+
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, audit_deps) 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 + route probe as before.",
|
|
232
232
|
metadata: { topic: "fileless-heal" },
|
|
233
233
|
},
|
|
234
|
+
{
|
|
235
|
+
text: "Dependency manager skill (src/skills/deps.js): structured npm dependency analysis + repair. diagnose(errorMessage, cwd) returns {diagnosed, category, summary, fixes} — categories: missing_install, missing_package, version_conflict, outdated_api, corrupted_modules. healthReport(cwd) returns full health check: npm audit (vulnerabilities), outdated packages, peer dep conflicts, unused packages, lock file status, health score 0-100. getMigration(packageName) returns known upgrade paths: express→fastify (5.6x faster), moment→dayjs (2KB vs 70KB), request→node-fetch (deprecated), body-parser→built-in, callbacks→async/await. Agent tools: audit_deps (full health check), check_migration (upgrade paths). Heal pipeline uses diagnose() in tryOperationalFix before AI — zero tokens for dependency issues.",
|
|
236
|
+
metadata: { topic: "skill-deps" },
|
|
237
|
+
},
|
|
234
238
|
];
|
|
235
239
|
|
|
236
240
|
class Brain {
|
package/src/core/wolverine.js
CHANGED
|
@@ -13,6 +13,7 @@ const { ResearchAgent } = require("../agent/research-agent");
|
|
|
13
13
|
const { GoalLoop } = require("../agent/goal-loop");
|
|
14
14
|
const { exploreAndFix, spawnParallel } = require("../agent/sub-agents");
|
|
15
15
|
const { EVENT_TYPES } = require("../logger/event-logger");
|
|
16
|
+
const { diagnose: diagnoseDeps } = require("../skills/deps");
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* The Wolverine healing engine — v3.
|
|
@@ -400,33 +401,19 @@ async function tryOperationalFix(parsed, cwd, logger) {
|
|
|
400
401
|
const { execSync } = require("child_process");
|
|
401
402
|
const msg = parsed.errorMessage || "";
|
|
402
403
|
|
|
403
|
-
// Pattern 1:
|
|
404
|
-
const
|
|
405
|
-
if (
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
// Only npm install if it's a package name (not a relative/absolute path)
|
|
409
|
-
if (!moduleName.startsWith(".") && !moduleName.startsWith("/") && !moduleName.startsWith("\\")) {
|
|
410
|
-
// Check if it's already in package.json but not installed
|
|
411
|
-
const fs = require("fs");
|
|
412
|
-
const path = require("path");
|
|
413
|
-
const pkgPath = path.join(cwd, "package.json");
|
|
414
|
-
let isInPkg = false;
|
|
415
|
-
try {
|
|
416
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
417
|
-
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
418
|
-
isInPkg = !!allDeps[moduleName];
|
|
419
|
-
} catch {}
|
|
404
|
+
// Pattern 1: Dependency issues — use deps skill for structured diagnosis
|
|
405
|
+
const depsDiag = diagnoseDeps(msg, cwd);
|
|
406
|
+
if (depsDiag.diagnosed) {
|
|
407
|
+
console.log(chalk.blue(` 📦 Deps diagnosis: ${depsDiag.category} — ${depsDiag.summary}`));
|
|
408
|
+
if (logger) logger.info("heal.ops.deps", depsDiag.summary, { category: depsDiag.category });
|
|
420
409
|
|
|
410
|
+
for (const fix of (depsDiag.fixes || [])) {
|
|
421
411
|
try {
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
execSync(cmd, { cwd, stdio: "pipe", timeout: 60000 });
|
|
426
|
-
return { fixed: true, action: `Installed missing module '${moduleName}' via: ${cmd}` };
|
|
412
|
+
console.log(chalk.blue(` 📦 Running: ${fix}`));
|
|
413
|
+
execSync(fix, { cwd, stdio: "pipe", timeout: 60000 });
|
|
414
|
+
return { fixed: true, action: `${depsDiag.summary}. Fixed via: ${fix}` };
|
|
427
415
|
} catch (e) {
|
|
428
|
-
console.log(chalk.yellow(` ⚠️
|
|
429
|
-
// Fall through to AI repair
|
|
416
|
+
console.log(chalk.yellow(` ⚠️ ${fix} failed: ${e.message?.slice(0, 80)}`));
|
|
430
417
|
}
|
|
431
418
|
}
|
|
432
419
|
}
|
package/src/index.js
CHANGED
|
@@ -34,6 +34,7 @@ const { detect: detectSystem } = require("./core/system-info");
|
|
|
34
34
|
const { ClusterManager } = require("./core/cluster-manager");
|
|
35
35
|
const { loadConfig, getConfig } = require("./core/config");
|
|
36
36
|
const { sqlGuard, SafeDB, scanForInjection, idempotencyGuard, idempotencyAfterHook } = require("./skills/sql");
|
|
37
|
+
const { diagnose: diagnoseDeps, healthReport: depsHealthReport, getMigration } = require("./skills/deps");
|
|
37
38
|
|
|
38
39
|
module.exports = {
|
|
39
40
|
// Core
|
|
@@ -95,4 +96,7 @@ module.exports = {
|
|
|
95
96
|
scanForInjection,
|
|
96
97
|
idempotencyGuard,
|
|
97
98
|
idempotencyAfterHook,
|
|
99
|
+
diagnoseDeps,
|
|
100
|
+
depsHealthReport,
|
|
101
|
+
getMigration,
|
|
98
102
|
};
|
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dependency Manager Skill — structured npm dependency analysis + repair.
|
|
3
|
+
*
|
|
4
|
+
* The agent often sees errors caused by dependency issues (version conflicts,
|
|
5
|
+
* missing peer deps, deprecated APIs, vulnerabilities) and wastes tokens trying
|
|
6
|
+
* to "fix" code when the real problem is in package.json or node_modules.
|
|
7
|
+
*
|
|
8
|
+
* This skill provides structured analysis BEFORE the AI runs, so the agent
|
|
9
|
+
* gets actionable context like "express@4.21 has a known vulnerability,
|
|
10
|
+
* upgrade to 4.22" instead of guessing from a stack trace.
|
|
11
|
+
*
|
|
12
|
+
* Capabilities:
|
|
13
|
+
* 1. Audit: npm audit wrapper — finds vulnerabilities with fix commands
|
|
14
|
+
* 2. Outdated: detects packages with available updates
|
|
15
|
+
* 3. Peer deps: finds missing/conflicting peer dependencies
|
|
16
|
+
* 4. Unused: detects packages in package.json not imported anywhere
|
|
17
|
+
* 5. Lock repair: detects and fixes corrupted package-lock.json
|
|
18
|
+
* 6. Version conflicts: finds packages requiring incompatible versions
|
|
19
|
+
* 7. Migration knowledge: knows common upgrade paths (express→fastify, etc.)
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const { execSync } = require("child_process");
|
|
23
|
+
const fs = require("fs");
|
|
24
|
+
const path = require("path");
|
|
25
|
+
const chalk = require("chalk");
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Run npm audit and return structured vulnerability data.
|
|
29
|
+
* @param {string} cwd — project root
|
|
30
|
+
* @returns {{ vulnerabilities: number, critical: number, high: number, fixes: string[] }}
|
|
31
|
+
*/
|
|
32
|
+
function audit(cwd) {
|
|
33
|
+
try {
|
|
34
|
+
const raw = execSync("npm audit --json 2>/dev/null", { cwd, encoding: "utf-8", timeout: 30000 });
|
|
35
|
+
const data = JSON.parse(raw);
|
|
36
|
+
const meta = data.metadata || {};
|
|
37
|
+
const vulns = meta.vulnerabilities || {};
|
|
38
|
+
const total = (vulns.critical || 0) + (vulns.high || 0) + (vulns.moderate || 0) + (vulns.low || 0);
|
|
39
|
+
|
|
40
|
+
const fixes = [];
|
|
41
|
+
if (total > 0) {
|
|
42
|
+
try {
|
|
43
|
+
const fixOutput = execSync("npm audit fix --dry-run --json 2>/dev/null", { cwd, encoding: "utf-8", timeout: 30000 });
|
|
44
|
+
const fixData = JSON.parse(fixOutput);
|
|
45
|
+
if (fixData.added || fixData.updated || fixData.removed) {
|
|
46
|
+
fixes.push("npm audit fix");
|
|
47
|
+
}
|
|
48
|
+
} catch { fixes.push("npm audit fix (may require --force for breaking changes)"); }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
vulnerabilities: total,
|
|
53
|
+
critical: vulns.critical || 0,
|
|
54
|
+
high: vulns.high || 0,
|
|
55
|
+
moderate: vulns.moderate || 0,
|
|
56
|
+
low: vulns.low || 0,
|
|
57
|
+
fixes,
|
|
58
|
+
};
|
|
59
|
+
} catch (e) {
|
|
60
|
+
// npm audit exits non-zero when vulnerabilities exist
|
|
61
|
+
try {
|
|
62
|
+
const data = JSON.parse(e.stdout || "{}");
|
|
63
|
+
const vulns = (data.metadata || {}).vulnerabilities || {};
|
|
64
|
+
return {
|
|
65
|
+
vulnerabilities: (vulns.critical || 0) + (vulns.high || 0) + (vulns.moderate || 0) + (vulns.low || 0),
|
|
66
|
+
critical: vulns.critical || 0,
|
|
67
|
+
high: vulns.high || 0,
|
|
68
|
+
moderate: vulns.moderate || 0,
|
|
69
|
+
low: vulns.low || 0,
|
|
70
|
+
fixes: ["npm audit fix"],
|
|
71
|
+
};
|
|
72
|
+
} catch { return { vulnerabilities: 0, critical: 0, high: 0, moderate: 0, low: 0, fixes: [], error: e.message?.slice(0, 100) }; }
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Find outdated packages.
|
|
78
|
+
* @param {string} cwd
|
|
79
|
+
* @returns {Array<{ name, current, wanted, latest, type }>}
|
|
80
|
+
*/
|
|
81
|
+
function outdated(cwd) {
|
|
82
|
+
try {
|
|
83
|
+
const raw = execSync("npm outdated --json 2>/dev/null", { cwd, encoding: "utf-8", timeout: 30000 });
|
|
84
|
+
const data = JSON.parse(raw || "{}");
|
|
85
|
+
return Object.entries(data).map(([name, info]) => ({
|
|
86
|
+
name,
|
|
87
|
+
current: info.current,
|
|
88
|
+
wanted: info.wanted,
|
|
89
|
+
latest: info.latest,
|
|
90
|
+
type: info.type || "dependencies",
|
|
91
|
+
}));
|
|
92
|
+
} catch (e) {
|
|
93
|
+
// npm outdated exits non-zero when outdated packages exist
|
|
94
|
+
try {
|
|
95
|
+
const data = JSON.parse(e.stdout || "{}");
|
|
96
|
+
return Object.entries(data).map(([name, info]) => ({
|
|
97
|
+
name,
|
|
98
|
+
current: info.current,
|
|
99
|
+
wanted: info.wanted,
|
|
100
|
+
latest: info.latest,
|
|
101
|
+
type: info.type || "dependencies",
|
|
102
|
+
}));
|
|
103
|
+
} catch { return []; }
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Find missing or conflicting peer dependencies.
|
|
109
|
+
* @param {string} cwd
|
|
110
|
+
* @returns {Array<{ package, requires, missing }>}
|
|
111
|
+
*/
|
|
112
|
+
function checkPeerDeps(cwd) {
|
|
113
|
+
const issues = [];
|
|
114
|
+
try {
|
|
115
|
+
const raw = execSync("npm ls --json --depth=1 2>/dev/null", { cwd, encoding: "utf-8", timeout: 30000 });
|
|
116
|
+
const data = JSON.parse(raw || "{}");
|
|
117
|
+
|
|
118
|
+
const walk = (deps, parentName = "root") => {
|
|
119
|
+
if (!deps) return;
|
|
120
|
+
for (const [name, info] of Object.entries(deps)) {
|
|
121
|
+
if (info.peerMissing) {
|
|
122
|
+
for (const peer of info.peerMissing) {
|
|
123
|
+
issues.push({ package: name, requires: peer.requires, missing: peer.requiredBy || name });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (info.problems) {
|
|
127
|
+
for (const problem of info.problems) {
|
|
128
|
+
if (problem.includes("peer dep")) {
|
|
129
|
+
issues.push({ package: name, requires: problem, missing: parentName });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (info.dependencies) walk(info.dependencies, name);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
walk(data.dependencies);
|
|
137
|
+
} catch (e) {
|
|
138
|
+
try {
|
|
139
|
+
const data = JSON.parse(e.stdout || "{}");
|
|
140
|
+
if (data.problems) {
|
|
141
|
+
for (const p of data.problems) {
|
|
142
|
+
issues.push({ package: "root", requires: p, missing: "project" });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
} catch {}
|
|
146
|
+
}
|
|
147
|
+
return issues;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Find packages in package.json that aren't imported anywhere in the project.
|
|
152
|
+
* @param {string} cwd
|
|
153
|
+
* @returns {string[]} — unused package names
|
|
154
|
+
*/
|
|
155
|
+
function findUnused(cwd) {
|
|
156
|
+
const pkgPath = path.join(cwd, "package.json");
|
|
157
|
+
if (!fs.existsSync(pkgPath)) return [];
|
|
158
|
+
|
|
159
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
160
|
+
const allDeps = Object.keys({ ...pkg.dependencies, ...pkg.devDependencies });
|
|
161
|
+
|
|
162
|
+
// Scan all .js/.ts/.mjs/.cjs files for require/import statements
|
|
163
|
+
const usedPackages = new Set();
|
|
164
|
+
const scanDir = (dir) => {
|
|
165
|
+
let entries;
|
|
166
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
|
167
|
+
for (const entry of entries) {
|
|
168
|
+
if (entry.name === "node_modules" || entry.name === ".git" || entry.name === ".wolverine") continue;
|
|
169
|
+
const fullPath = path.join(dir, entry.name);
|
|
170
|
+
if (entry.isDirectory()) { scanDir(fullPath); continue; }
|
|
171
|
+
if (!/\.(js|ts|mjs|cjs|jsx|tsx)$/.test(entry.name)) continue;
|
|
172
|
+
try {
|
|
173
|
+
const content = fs.readFileSync(fullPath, "utf-8");
|
|
174
|
+
// Match require("X") and import ... from "X"
|
|
175
|
+
const requires = content.match(/require\s*\(\s*["']([^"'./][^"']*)["']\s*\)/g) || [];
|
|
176
|
+
const imports = content.match(/from\s+["']([^"'./][^"']*)["']/g) || [];
|
|
177
|
+
for (const r of requires) {
|
|
178
|
+
const m = r.match(/["']([^"']+)["']/);
|
|
179
|
+
if (m) usedPackages.add(m[1].split("/")[0]);
|
|
180
|
+
}
|
|
181
|
+
for (const i of imports) {
|
|
182
|
+
const m = i.match(/["']([^"']+)["']/);
|
|
183
|
+
if (m) usedPackages.add(m[1].split("/")[0]);
|
|
184
|
+
}
|
|
185
|
+
} catch {}
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
scanDir(cwd);
|
|
189
|
+
|
|
190
|
+
// Filter: only report deps that are truly unused
|
|
191
|
+
// Exclude common false positives (types, plugins, bin-only packages)
|
|
192
|
+
const falsePositives = new Set(["typescript", "@types", "eslint", "prettier", "nodemon", "ts-node"]);
|
|
193
|
+
return allDeps.filter(dep => {
|
|
194
|
+
const baseName = dep.startsWith("@") ? dep : dep.split("/")[0];
|
|
195
|
+
return !usedPackages.has(baseName) && !falsePositives.has(baseName) && !baseName.startsWith("@types/");
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Check if package-lock.json is healthy.
|
|
201
|
+
* @param {string} cwd
|
|
202
|
+
* @returns {{ healthy: boolean, issue?: string, fix?: string }}
|
|
203
|
+
*/
|
|
204
|
+
function checkLockFile(cwd) {
|
|
205
|
+
const lockPath = path.join(cwd, "package-lock.json");
|
|
206
|
+
const pkgPath = path.join(cwd, "package.json");
|
|
207
|
+
|
|
208
|
+
if (!fs.existsSync(lockPath)) {
|
|
209
|
+
return { healthy: false, issue: "No package-lock.json found", fix: "npm install" };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
try {
|
|
213
|
+
const lock = JSON.parse(fs.readFileSync(lockPath, "utf-8"));
|
|
214
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
215
|
+
|
|
216
|
+
// Check lock version
|
|
217
|
+
if (!lock.lockfileVersion || lock.lockfileVersion < 2) {
|
|
218
|
+
return { healthy: false, issue: `Outdated lockfile version ${lock.lockfileVersion || 1}`, fix: "rm package-lock.json && npm install" };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Check if lock matches package.json
|
|
222
|
+
const pkgDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
223
|
+
const lockDeps = lock.packages?.[""]?.dependencies || {};
|
|
224
|
+
const lockDevDeps = lock.packages?.[""]?.devDependencies || {};
|
|
225
|
+
const allLockDeps = { ...lockDeps, ...lockDevDeps };
|
|
226
|
+
|
|
227
|
+
for (const [name] of Object.entries(pkgDeps)) {
|
|
228
|
+
if (!allLockDeps[name] && !lock.dependencies?.[name]) {
|
|
229
|
+
return { healthy: false, issue: `${name} in package.json but missing from lock`, fix: "npm install" };
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return { healthy: true };
|
|
234
|
+
} catch (e) {
|
|
235
|
+
return { healthy: false, issue: `Corrupted lock file: ${e.message?.slice(0, 60)}`, fix: "rm package-lock.json && npm install" };
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Diagnose a dependency-related error and return structured fix recommendations.
|
|
241
|
+
* This is the main entry point — call it from the heal pipeline.
|
|
242
|
+
*
|
|
243
|
+
* @param {string} errorMessage
|
|
244
|
+
* @param {string} cwd
|
|
245
|
+
* @returns {{ diagnosed: boolean, category: string, summary: string, fixes: string[] }}
|
|
246
|
+
*/
|
|
247
|
+
function diagnose(errorMessage, cwd) {
|
|
248
|
+
const msg = (errorMessage || "").toLowerCase();
|
|
249
|
+
|
|
250
|
+
// Pattern: Cannot find module 'X'
|
|
251
|
+
const missingMatch = errorMessage.match(/Cannot find module '([^']+)'/);
|
|
252
|
+
if (missingMatch) {
|
|
253
|
+
const mod = missingMatch[1];
|
|
254
|
+
if (!mod.startsWith(".") && !mod.startsWith("/")) {
|
|
255
|
+
// Check if it's in package.json
|
|
256
|
+
const pkgPath = path.join(cwd, "package.json");
|
|
257
|
+
let inPkg = false;
|
|
258
|
+
try {
|
|
259
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
260
|
+
inPkg = !!(pkg.dependencies?.[mod] || pkg.devDependencies?.[mod]);
|
|
261
|
+
} catch {}
|
|
262
|
+
|
|
263
|
+
if (inPkg) {
|
|
264
|
+
return {
|
|
265
|
+
diagnosed: true,
|
|
266
|
+
category: "missing_install",
|
|
267
|
+
summary: `${mod} is in package.json but not installed`,
|
|
268
|
+
fixes: ["npm install"],
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
return {
|
|
272
|
+
diagnosed: true,
|
|
273
|
+
category: "missing_package",
|
|
274
|
+
summary: `${mod} is not in package.json and not installed`,
|
|
275
|
+
fixes: [`npm install ${mod}`],
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Pattern: version/compatibility errors
|
|
281
|
+
if (/version mismatch|incompatible|peer dep|ERESOLVE/i.test(msg)) {
|
|
282
|
+
const peerIssues = checkPeerDeps(cwd);
|
|
283
|
+
return {
|
|
284
|
+
diagnosed: true,
|
|
285
|
+
category: "version_conflict",
|
|
286
|
+
summary: `Dependency version conflict (${peerIssues.length} peer dep issues)`,
|
|
287
|
+
fixes: peerIssues.length > 0
|
|
288
|
+
? peerIssues.map(p => `npm install ${p.package}@latest`).concat(["npm install --legacy-peer-deps"])
|
|
289
|
+
: ["npm install --legacy-peer-deps", "rm -rf node_modules && npm install"],
|
|
290
|
+
details: peerIssues,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Pattern: deprecated/removed API
|
|
295
|
+
if (/is not a function|is not a constructor|has no method|deprecated/i.test(msg)) {
|
|
296
|
+
const outdatedPkgs = outdated(cwd);
|
|
297
|
+
if (outdatedPkgs.length > 0) {
|
|
298
|
+
// Find the package mentioned in the error
|
|
299
|
+
const relevantPkgs = outdatedPkgs.filter(p =>
|
|
300
|
+
msg.includes(p.name.toLowerCase()) || errorMessage.includes(p.name)
|
|
301
|
+
);
|
|
302
|
+
if (relevantPkgs.length > 0) {
|
|
303
|
+
return {
|
|
304
|
+
diagnosed: true,
|
|
305
|
+
category: "outdated_api",
|
|
306
|
+
summary: `API may have changed in newer version of ${relevantPkgs.map(p => p.name).join(", ")}`,
|
|
307
|
+
fixes: relevantPkgs.map(p => `npm install ${p.name}@${p.latest}`),
|
|
308
|
+
details: relevantPkgs,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Pattern: corrupted node_modules
|
|
315
|
+
if (/unexpected token|cannot find module.*node_modules|EISDIR|ENOTDIR/i.test(msg) && msg.includes("node_modules")) {
|
|
316
|
+
return {
|
|
317
|
+
diagnosed: true,
|
|
318
|
+
category: "corrupted_modules",
|
|
319
|
+
summary: "node_modules appears corrupted",
|
|
320
|
+
fixes: ["rm -rf node_modules package-lock.json && npm install"],
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return { diagnosed: false };
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Full dependency health report — used by dashboard and agent context.
|
|
329
|
+
* @param {string} cwd
|
|
330
|
+
* @returns {object}
|
|
331
|
+
*/
|
|
332
|
+
function healthReport(cwd) {
|
|
333
|
+
const auditResult = audit(cwd);
|
|
334
|
+
const outdatedPkgs = outdated(cwd);
|
|
335
|
+
const peerIssues = checkPeerDeps(cwd);
|
|
336
|
+
const unused = findUnused(cwd);
|
|
337
|
+
const lockStatus = checkLockFile(cwd);
|
|
338
|
+
|
|
339
|
+
const score = Math.max(0, 100
|
|
340
|
+
- (auditResult.critical * 20)
|
|
341
|
+
- (auditResult.high * 10)
|
|
342
|
+
- (auditResult.moderate * 3)
|
|
343
|
+
- (peerIssues.length * 5)
|
|
344
|
+
- (lockStatus.healthy ? 0 : 15)
|
|
345
|
+
- Math.min(outdatedPkgs.length * 2, 20)
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
return {
|
|
349
|
+
score,
|
|
350
|
+
audit: auditResult,
|
|
351
|
+
outdated: outdatedPkgs,
|
|
352
|
+
peerDeps: peerIssues,
|
|
353
|
+
unused,
|
|
354
|
+
lockFile: lockStatus,
|
|
355
|
+
summary: score >= 80 ? "Healthy" : score >= 50 ? "Needs attention" : "Critical issues",
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// ── Migration Knowledge ──
|
|
360
|
+
|
|
361
|
+
const MIGRATION_PATTERNS = {
|
|
362
|
+
"express": {
|
|
363
|
+
to: "fastify",
|
|
364
|
+
reason: "5.6x faster routing, built-in validation, async-first",
|
|
365
|
+
patterns: [
|
|
366
|
+
{ from: "app.get(path, handler)", to: "fastify.get(path, handler)" },
|
|
367
|
+
{ from: "app.use(middleware)", to: "fastify.addHook('preHandler', middleware)" },
|
|
368
|
+
{ from: "res.json(data)", to: "reply.send(data)" },
|
|
369
|
+
{ from: "res.status(code)", to: "reply.code(code)" },
|
|
370
|
+
{ from: "app.listen(port)", to: "fastify.listen({ port, host: '0.0.0.0' })" },
|
|
371
|
+
],
|
|
372
|
+
},
|
|
373
|
+
"request": {
|
|
374
|
+
to: "node-fetch or undici",
|
|
375
|
+
reason: "request is deprecated since 2020",
|
|
376
|
+
patterns: [
|
|
377
|
+
{ from: "request(url, callback)", to: "const res = await fetch(url)" },
|
|
378
|
+
{ from: "request.post(url, { body })", to: "await fetch(url, { method: 'POST', body })" },
|
|
379
|
+
],
|
|
380
|
+
},
|
|
381
|
+
"moment": {
|
|
382
|
+
to: "dayjs or date-fns",
|
|
383
|
+
reason: "moment is in maintenance mode, 70KB vs 2KB",
|
|
384
|
+
patterns: [
|
|
385
|
+
{ from: "moment().format('YYYY-MM-DD')", to: "dayjs().format('YYYY-MM-DD')" },
|
|
386
|
+
{ from: "moment(date).diff(other)", to: "dayjs(date).diff(other)" },
|
|
387
|
+
],
|
|
388
|
+
},
|
|
389
|
+
"body-parser": {
|
|
390
|
+
to: "built-in (Express 4.16+ / Fastify)",
|
|
391
|
+
reason: "Included by default in modern frameworks",
|
|
392
|
+
patterns: [
|
|
393
|
+
{ from: "app.use(bodyParser.json())", to: "app.use(express.json())" },
|
|
394
|
+
],
|
|
395
|
+
},
|
|
396
|
+
"callback-style": {
|
|
397
|
+
to: "async/await with util.promisify",
|
|
398
|
+
reason: "Cleaner error handling, no callback hell",
|
|
399
|
+
patterns: [
|
|
400
|
+
{ from: "fs.readFile(path, (err, data) => {})", to: "const data = await fs.promises.readFile(path)" },
|
|
401
|
+
{ from: "db.query(sql, (err, rows) => {})", to: "const rows = await db.query(sql)" },
|
|
402
|
+
],
|
|
403
|
+
},
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Check if a package has a known migration path.
|
|
408
|
+
* @param {string} packageName
|
|
409
|
+
* @returns {object|null}
|
|
410
|
+
*/
|
|
411
|
+
function getMigration(packageName) {
|
|
412
|
+
return MIGRATION_PATTERNS[packageName] || null;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// ── Skill Metadata ──
|
|
416
|
+
|
|
417
|
+
const SKILL_NAME = "deps";
|
|
418
|
+
const SKILL_DESCRIPTION = "Dependency manager: npm audit, version conflicts, peer deps, unused packages, lock file repair, migration knowledge. Diagnoses dependency-related errors before AI runs, providing actionable fix commands instead of code edits.";
|
|
419
|
+
const SKILL_KEYWORDS = ["dependency", "dependencies", "npm", "package", "install", "audit", "vulnerability", "outdated", "peer", "version", "conflict", "lock", "node_modules", "upgrade", "migrate", "deprecated"];
|
|
420
|
+
const SKILL_USAGE = `// Diagnose a dependency error (returns structured fix)
|
|
421
|
+
const { diagnose } = require("../src/skills/deps");
|
|
422
|
+
const result = diagnose("Cannot find module 'cors'", process.cwd());
|
|
423
|
+
// { diagnosed: true, category: "missing_package", fixes: ["npm install cors"] }
|
|
424
|
+
|
|
425
|
+
// Full health report
|
|
426
|
+
const { healthReport } = require("../src/skills/deps");
|
|
427
|
+
const report = healthReport(process.cwd());
|
|
428
|
+
// { score: 85, audit: {...}, outdated: [...], peerDeps: [...], unused: [...] }
|
|
429
|
+
|
|
430
|
+
// Check migration paths
|
|
431
|
+
const { getMigration } = require("../src/skills/deps");
|
|
432
|
+
const migration = getMigration("moment");
|
|
433
|
+
// { to: "dayjs", reason: "maintenance mode, 70KB vs 2KB", patterns: [...] }`;
|
|
434
|
+
|
|
435
|
+
module.exports = {
|
|
436
|
+
SKILL_NAME,
|
|
437
|
+
SKILL_DESCRIPTION,
|
|
438
|
+
SKILL_KEYWORDS,
|
|
439
|
+
SKILL_USAGE,
|
|
440
|
+
audit,
|
|
441
|
+
outdated,
|
|
442
|
+
checkPeerDeps,
|
|
443
|
+
findUnused,
|
|
444
|
+
checkLockFile,
|
|
445
|
+
diagnose,
|
|
446
|
+
healthReport,
|
|
447
|
+
getMigration,
|
|
448
|
+
MIGRATION_PATTERNS,
|
|
449
|
+
};
|