@4da/mcp-server 4.6.2 → 4.6.3
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/dist/db.d.ts +14 -2
- package/dist/db.js +45 -14
- package/dist/doctor.js +4 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +4 -4
- package/dist/live/index.d.ts +8 -0
- package/dist/live/index.js +33 -0
- package/dist/live/types.d.ts +1 -1
- package/dist/live/version-resolver.d.ts +10 -0
- package/dist/live/version-resolver.js +42 -2
- package/dist/schemas/get-relevant-content.json +1 -1
- package/dist/schemas/upgrade-planner.json +1 -1
- package/dist/schemas/what-should-i-know.json +1 -1
- package/dist/tools/dependency-health.js +1 -1
- package/dist/tools/upgrade-planner.d.ts +25 -1
- package/dist/tools/upgrade-planner.js +110 -27
- package/dist/tools/what-should-i-know.js +1 -1
- package/dist/types.d.ts +9 -0
- package/package.json +6 -3
package/dist/db.d.ts
CHANGED
|
@@ -53,14 +53,26 @@ export declare class FourDADatabase {
|
|
|
53
53
|
*/
|
|
54
54
|
get isStandalone(): boolean;
|
|
55
55
|
/**
|
|
56
|
-
* Validate that a database file exists
|
|
56
|
+
* Validate that a database file exists and is a readable SQLite database.
|
|
57
57
|
*
|
|
58
58
|
* Use this before accepting tool calls to ensure the database is in a good state.
|
|
59
59
|
*
|
|
60
|
+
* By default this runs a CHEAP probe (open + list tables, ~0.1s) which surfaces
|
|
61
|
+
* a missing, locked, or corrupt-header file. It deliberately does NOT run a full
|
|
62
|
+
* `PRAGMA integrity_check`: that is an O(database-size) scan (~11s on a 1.7 GB DB,
|
|
63
|
+
* and growing) and, because better-sqlite3 is synchronous, it blocks the event
|
|
64
|
+
* loop — running it on the startup path stalls the MCP stdio handshake until it
|
|
65
|
+
* finishes, so the host's connect timeout can fire and the server looks like it
|
|
66
|
+
* "failed to start". Pass `{ deep: true }` for the full integrity_check on the
|
|
67
|
+
* explicit `--doctor` diagnostic path, where a slow, thorough scan is expected.
|
|
68
|
+
*
|
|
60
69
|
* @param dbPath - Path to the database file. If omitted, uses the default resolution.
|
|
70
|
+
* @param opts.deep - Run the full `PRAGMA integrity_check` (slow, blocking). Default false.
|
|
61
71
|
* @returns Validation result with table list on success, or error details on failure.
|
|
62
72
|
*/
|
|
63
|
-
static validateDatabase(dbPath?: string
|
|
73
|
+
static validateDatabase(dbPath?: string, opts?: {
|
|
74
|
+
deep?: boolean;
|
|
75
|
+
}): DatabaseValidationResult;
|
|
64
76
|
/**
|
|
65
77
|
* Get the raw better-sqlite3 database instance for custom queries
|
|
66
78
|
*/
|
package/dist/db.js
CHANGED
|
@@ -121,14 +121,24 @@ export class FourDADatabase {
|
|
|
121
121
|
return this._isStandalone;
|
|
122
122
|
}
|
|
123
123
|
/**
|
|
124
|
-
* Validate that a database file exists
|
|
124
|
+
* Validate that a database file exists and is a readable SQLite database.
|
|
125
125
|
*
|
|
126
126
|
* Use this before accepting tool calls to ensure the database is in a good state.
|
|
127
127
|
*
|
|
128
|
+
* By default this runs a CHEAP probe (open + list tables, ~0.1s) which surfaces
|
|
129
|
+
* a missing, locked, or corrupt-header file. It deliberately does NOT run a full
|
|
130
|
+
* `PRAGMA integrity_check`: that is an O(database-size) scan (~11s on a 1.7 GB DB,
|
|
131
|
+
* and growing) and, because better-sqlite3 is synchronous, it blocks the event
|
|
132
|
+
* loop — running it on the startup path stalls the MCP stdio handshake until it
|
|
133
|
+
* finishes, so the host's connect timeout can fire and the server looks like it
|
|
134
|
+
* "failed to start". Pass `{ deep: true }` for the full integrity_check on the
|
|
135
|
+
* explicit `--doctor` diagnostic path, where a slow, thorough scan is expected.
|
|
136
|
+
*
|
|
128
137
|
* @param dbPath - Path to the database file. If omitted, uses the default resolution.
|
|
138
|
+
* @param opts.deep - Run the full `PRAGMA integrity_check` (slow, blocking). Default false.
|
|
129
139
|
* @returns Validation result with table list on success, or error details on failure.
|
|
130
140
|
*/
|
|
131
|
-
static validateDatabase(dbPath) {
|
|
141
|
+
static validateDatabase(dbPath, opts) {
|
|
132
142
|
const resolvedPath = dbPath || getDefaultDbPath();
|
|
133
143
|
const absolutePath = path.isAbsolute(resolvedPath)
|
|
134
144
|
? resolvedPath
|
|
@@ -141,21 +151,24 @@ export class FourDADatabase {
|
|
|
141
151
|
error: `No existing database at ${absolutePath}. Standalone mode will create one on startup.`,
|
|
142
152
|
};
|
|
143
153
|
}
|
|
144
|
-
// Try to open and run integrity check
|
|
145
154
|
let testDb = null;
|
|
146
155
|
try {
|
|
147
156
|
testDb = new Database(absolutePath, { readonly: true });
|
|
148
|
-
//
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
+
// Deep, opt-in integrity check (slow: full-database scan). Only on --doctor.
|
|
158
|
+
if (opts?.deep) {
|
|
159
|
+
const integrityResult = testDb.pragma("integrity_check");
|
|
160
|
+
const integrityStatus = integrityResult[0]?.integrity_check;
|
|
161
|
+
if (integrityStatus !== "ok") {
|
|
162
|
+
return {
|
|
163
|
+
valid: false,
|
|
164
|
+
error: `Database integrity check failed: ${integrityStatus}. `
|
|
165
|
+
+ "The database file may be corrupt. Try deleting data/4da.db and restarting 4DA.",
|
|
166
|
+
};
|
|
167
|
+
}
|
|
157
168
|
}
|
|
158
|
-
// List tables
|
|
169
|
+
// List tables. This reads the schema, so a truncated/corrupt-header file
|
|
170
|
+
// (not a valid SQLite DB) throws here and is reported below — giving the
|
|
171
|
+
// cheap path meaningful corruption detection without a full scan.
|
|
159
172
|
const tables = testDb
|
|
160
173
|
.prepare("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name")
|
|
161
174
|
.all();
|
|
@@ -710,10 +723,17 @@ export class FourDADatabase {
|
|
|
710
723
|
* These scores are from the full 5-axis PASIFA engine — much more accurate.
|
|
711
724
|
*/
|
|
712
725
|
getRelevantContentFromRust(minScore, sourceType, limit, sinceDate) {
|
|
726
|
+
// Grounding subquery — guarded: older/partial 4DA DBs may have relevance_score
|
|
727
|
+
// (so the Rust path runs) but lack source_item_dependencies. Fall back to 0
|
|
728
|
+
// (→ evidence_class "semantic_only") rather than throwing.
|
|
729
|
+
const depCount = this.hasColumn("source_item_dependencies", "source_item_id")
|
|
730
|
+
? `(SELECT COUNT(*) FROM source_item_dependencies d WHERE d.source_item_id = source_items.id)`
|
|
731
|
+
: `0`;
|
|
713
732
|
let query = `
|
|
714
733
|
SELECT id, source_type, source_id, url, title, content, content_hash,
|
|
715
734
|
created_at, last_seen, relevance_score, content_type,
|
|
716
|
-
signal_type, signal_priority
|
|
735
|
+
signal_type, signal_priority,
|
|
736
|
+
${depCount} AS dep_match_count
|
|
717
737
|
FROM source_items
|
|
718
738
|
WHERE relevance_score >= ?
|
|
719
739
|
AND datetime(created_at) >= datetime(?)
|
|
@@ -737,6 +757,14 @@ export class FourDADatabase {
|
|
|
737
757
|
? `${hoursAgo} hours ago`
|
|
738
758
|
: `${Math.round(hoursAgo / 24)} days ago`;
|
|
739
759
|
const necessity = this.getNecessityForItem(item.id);
|
|
760
|
+
// Provenance: grounded (matched one of the user's installed deps) vs
|
|
761
|
+
// semantic-only. A grounded security advisory is the OSV-verified gold tier.
|
|
762
|
+
const grounded = (item.dep_match_count ?? 0) > 0;
|
|
763
|
+
const evidence_class = grounded
|
|
764
|
+
? item.content_type === "security_advisory"
|
|
765
|
+
? "osv_verified"
|
|
766
|
+
: "dependency_grounded"
|
|
767
|
+
: "semantic_only";
|
|
740
768
|
return {
|
|
741
769
|
id: item.id,
|
|
742
770
|
source_type: item.source_type,
|
|
@@ -753,6 +781,7 @@ export class FourDADatabase {
|
|
|
753
781
|
necessity_urgency: necessity.urgency,
|
|
754
782
|
signal_type: item.signal_type ?? null,
|
|
755
783
|
signal_priority: item.signal_priority ?? null,
|
|
784
|
+
evidence_class,
|
|
756
785
|
};
|
|
757
786
|
});
|
|
758
787
|
}
|
|
@@ -804,6 +833,8 @@ export class FourDADatabase {
|
|
|
804
833
|
necessity_urgency: necessity.urgency,
|
|
805
834
|
signal_type: null,
|
|
806
835
|
signal_priority: null,
|
|
836
|
+
// Standalone keyword scorer — no Rust pipeline, no dependency grounding.
|
|
837
|
+
evidence_class: "keyword_heuristic",
|
|
807
838
|
});
|
|
808
839
|
}
|
|
809
840
|
}
|
package/dist/doctor.js
CHANGED
|
@@ -34,8 +34,10 @@ export function runDoctor() {
|
|
|
34
34
|
status: "pass",
|
|
35
35
|
detail: "better-sqlite3 loaded successfully",
|
|
36
36
|
});
|
|
37
|
-
// 3. Database discovery
|
|
38
|
-
|
|
37
|
+
// 3. Database discovery — deep integrity_check is appropriate here: --doctor is
|
|
38
|
+
// an explicit, user-invoked diagnostic where a slow full scan is expected. The
|
|
39
|
+
// startup path uses the cheap default to keep the MCP handshake responsive.
|
|
40
|
+
const validation = FourDADatabase.validateDatabase(undefined, { deep: true });
|
|
39
41
|
if (validation.valid) {
|
|
40
42
|
checks.push({
|
|
41
43
|
name: "4DA database",
|
package/dist/index.d.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* do NOT expose it over a network without putting your own auth in front.
|
|
13
13
|
*/
|
|
14
14
|
/**
|
|
15
|
-
* 4DA MCP Server v4.6.
|
|
15
|
+
* 4DA MCP Server v4.6.3 — Dependency Intelligence for AI Coding Agents
|
|
16
16
|
*
|
|
17
17
|
* 14 tools across 5 categories. Live vulnerability scanning (OSV.dev),
|
|
18
18
|
* ecosystem news, persistent memory, and tech stack awareness for any MCP host.
|
package/dist/index.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* do NOT expose it over a network without putting your own auth in front.
|
|
14
14
|
*/
|
|
15
15
|
/**
|
|
16
|
-
* 4DA MCP Server v4.6.
|
|
16
|
+
* 4DA MCP Server v4.6.3 — Dependency Intelligence for AI Coding Agents
|
|
17
17
|
*
|
|
18
18
|
* 14 tools across 5 categories. Live vulnerability scanning (OSV.dev),
|
|
19
19
|
* ecosystem news, persistent memory, and tech stack awareness for any MCP host.
|
|
@@ -51,7 +51,7 @@ import { createDatabase, FourDADatabase } from "./db.js";
|
|
|
51
51
|
// =============================================================================
|
|
52
52
|
const server = new Server({
|
|
53
53
|
name: "4da-server",
|
|
54
|
-
version: "4.6.
|
|
54
|
+
version: "4.6.3",
|
|
55
55
|
}, {
|
|
56
56
|
capabilities: {
|
|
57
57
|
tools: { listChanged: true },
|
|
@@ -293,7 +293,7 @@ async function main() {
|
|
|
293
293
|
const args = process.argv.slice(2);
|
|
294
294
|
// Version
|
|
295
295
|
if (args.includes("--version") || args.includes("-v")) {
|
|
296
|
-
console.log("@4da/mcp-server 4.6.
|
|
296
|
+
console.log("@4da/mcp-server 4.6.3");
|
|
297
297
|
return;
|
|
298
298
|
}
|
|
299
299
|
// Help
|
|
@@ -382,7 +382,7 @@ async function main() {
|
|
|
382
382
|
process.exit(0);
|
|
383
383
|
});
|
|
384
384
|
const toolCount = getSlimToolList().length;
|
|
385
|
-
console.error(`4DA MCP Server v4.6.
|
|
385
|
+
console.error(`4DA MCP Server v4.6.3 started — ${toolCount} tools, stdio transport`);
|
|
386
386
|
console.error(" Use --http for Streamable HTTP, --setup to configure editors, --doctor to check health");
|
|
387
387
|
}
|
|
388
388
|
main().catch((error) => {
|
package/dist/live/index.d.ts
CHANGED
|
@@ -25,6 +25,7 @@ export declare class LiveIntelligence {
|
|
|
25
25
|
private resolvedDeps;
|
|
26
26
|
private auditDeps;
|
|
27
27
|
private initialized;
|
|
28
|
+
private projectRoot;
|
|
28
29
|
constructor(db: Database.Database);
|
|
29
30
|
/**
|
|
30
31
|
* Initialize with project data. Call once after project scan.
|
|
@@ -56,6 +57,13 @@ export declare class LiveIntelligence {
|
|
|
56
57
|
deps: string[];
|
|
57
58
|
devDeps: string[];
|
|
58
59
|
}>): void;
|
|
60
|
+
/**
|
|
61
|
+
* Root directory the dependency set was resolved from, or null before init.
|
|
62
|
+
* For multi-manifest (grouped) init this is the deepest common ancestor of
|
|
63
|
+
* the group directories — honest scope reporting for tool output, instead
|
|
64
|
+
* of whatever `process.cwd()` happens to be.
|
|
65
|
+
*/
|
|
66
|
+
getProjectRoot(): string | null;
|
|
59
67
|
/**
|
|
60
68
|
* Run vulnerability scan (returns cached if fresh, fetches otherwise).
|
|
61
69
|
*/
|
package/dist/live/index.js
CHANGED
|
@@ -32,6 +32,7 @@ export class LiveIntelligence {
|
|
|
32
32
|
resolvedDeps = [];
|
|
33
33
|
auditDeps = [];
|
|
34
34
|
initialized = false;
|
|
35
|
+
projectRoot = null;
|
|
35
36
|
constructor(db) {
|
|
36
37
|
this.enabled = process.env.FOURDA_OFFLINE !== "true";
|
|
37
38
|
this.cache = new LiveCache(db);
|
|
@@ -50,6 +51,7 @@ export class LiveIntelligence {
|
|
|
50
51
|
initFromProject(projectPath, deps, devDeps, language) {
|
|
51
52
|
this.resolvedDeps = resolveVersions(projectPath, deps, devDeps, language);
|
|
52
53
|
this.auditDeps = resolveAuditVersions(projectPath, deps, devDeps, language);
|
|
54
|
+
this.projectRoot = projectPath;
|
|
53
55
|
this.initialized = true;
|
|
54
56
|
}
|
|
55
57
|
/**
|
|
@@ -66,6 +68,7 @@ export class LiveIntelligence {
|
|
|
66
68
|
}
|
|
67
69
|
this.resolvedDeps = dedupeDependencies(allResolved);
|
|
68
70
|
this.auditDeps = dedupeDependencies(allAudit);
|
|
71
|
+
this.projectRoot = projectPath;
|
|
69
72
|
this.initialized = true;
|
|
70
73
|
}
|
|
71
74
|
/**
|
|
@@ -87,8 +90,18 @@ export class LiveIntelligence {
|
|
|
87
90
|
}
|
|
88
91
|
this.resolvedDeps = dedupeDependencies(allResolved);
|
|
89
92
|
this.auditDeps = dedupeDependencies(allAudit);
|
|
93
|
+
this.projectRoot = commonPathRoot(groups.map((g) => g.dir));
|
|
90
94
|
this.initialized = true;
|
|
91
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Root directory the dependency set was resolved from, or null before init.
|
|
98
|
+
* For multi-manifest (grouped) init this is the deepest common ancestor of
|
|
99
|
+
* the group directories — honest scope reporting for tool output, instead
|
|
100
|
+
* of whatever `process.cwd()` happens to be.
|
|
101
|
+
*/
|
|
102
|
+
getProjectRoot() {
|
|
103
|
+
return this.projectRoot;
|
|
104
|
+
}
|
|
92
105
|
/**
|
|
93
106
|
* Run vulnerability scan (returns cached if fresh, fetches otherwise).
|
|
94
107
|
*/
|
|
@@ -227,6 +240,26 @@ export class LiveIntelligence {
|
|
|
227
240
|
};
|
|
228
241
|
}
|
|
229
242
|
}
|
|
243
|
+
/**
|
|
244
|
+
* Deepest common ancestor of a set of directories (segment-wise, both slash
|
|
245
|
+
* styles). Null for an empty set; a single dir is its own root.
|
|
246
|
+
*/
|
|
247
|
+
function commonPathRoot(dirs) {
|
|
248
|
+
if (dirs.length === 0)
|
|
249
|
+
return null;
|
|
250
|
+
const split = (p) => p.replace(/\\/g, "/").replace(/\/+$/, "").split("/");
|
|
251
|
+
let common = split(dirs[0]);
|
|
252
|
+
for (const dir of dirs.slice(1)) {
|
|
253
|
+
const parts = split(dir);
|
|
254
|
+
let i = 0;
|
|
255
|
+
while (i < common.length && i < parts.length && common[i].toLowerCase() === parts[i].toLowerCase())
|
|
256
|
+
i++;
|
|
257
|
+
common = common.slice(0, i);
|
|
258
|
+
if (common.length === 0)
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
return common.length > 0 ? common.join("/") : null;
|
|
262
|
+
}
|
|
230
263
|
function dedupeDependencies(deps) {
|
|
231
264
|
const unique = new Map();
|
|
232
265
|
for (const dep of deps) {
|
package/dist/live/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type OsvEcosystem = "npm" | "crates.io" | "PyPI" | "Go" | "Maven" | "NuGet" | "RubyGems" | "Packagist";
|
|
1
|
+
export type OsvEcosystem = "npm" | "crates.io" | "PyPI" | "Go" | "Maven" | "NuGet" | "RubyGems" | "Packagist" | "Pub";
|
|
2
2
|
export interface ResolvedDependency {
|
|
3
3
|
name: string;
|
|
4
4
|
version: string | null;
|
|
@@ -6,6 +6,16 @@
|
|
|
6
6
|
* Uses only Node.js built-ins — no external parsers.
|
|
7
7
|
*/
|
|
8
8
|
import type { OsvEcosystem, ResolvedDependency } from "./types.js";
|
|
9
|
+
/**
|
|
10
|
+
* Map a language/ecosystem alias to its OSV ecosystem id (case-insensitive).
|
|
11
|
+
*
|
|
12
|
+
* Falls back to "npm" ONLY for genuinely unrecognized input — a last resort, not
|
|
13
|
+
* a real default. OSV-unindexed languages (Swift, C/C++) and unknowns produce no
|
|
14
|
+
* OSV matches anyway (their package names don't collide with the npm namespace),
|
|
15
|
+
* so this never fabricates a vulnerability. Ecosystems that lack a version
|
|
16
|
+
* resolver here (NuGet/Maven/Packagist/RubyGems/Pub) still get the CORRECT label,
|
|
17
|
+
* so the moment a resolver is added the scan is right by construction.
|
|
18
|
+
*/
|
|
9
19
|
export declare function mapEcosystem(language: string): OsvEcosystem;
|
|
10
20
|
export declare function resolveVersions(cwd: string, deps: string[], devDeps: string[], language: string, targets?: Record<string, string>): ResolvedDependency[];
|
|
11
21
|
/**
|
|
@@ -9,18 +9,58 @@
|
|
|
9
9
|
import * as fs from "node:fs";
|
|
10
10
|
import * as path from "node:path";
|
|
11
11
|
import { targetActiveOnHost } from "./platform.js";
|
|
12
|
+
// Alias -> OSV ecosystem. Mirrors the desktop app's canonical
|
|
13
|
+
// `Ecosystem::parse` (src-tauri/src/ecosystem.rs) so both sides recognize the
|
|
14
|
+
// SAME aliases — they previously drifted (the Rust side knew csharp/dart/php
|
|
15
|
+
// while this map keyed C# as "dotnet" and omitted Dart entirely, mislabeling
|
|
16
|
+
// those deps). Keep this in sync with that enum.
|
|
12
17
|
const ECOSYSTEM_MAP = {
|
|
13
18
|
npm: "npm",
|
|
19
|
+
javascript: "npm",
|
|
20
|
+
typescript: "npm",
|
|
21
|
+
node: "npm",
|
|
22
|
+
js: "npm",
|
|
23
|
+
ts: "npm",
|
|
14
24
|
rust: "crates.io",
|
|
25
|
+
cargo: "crates.io",
|
|
26
|
+
"crates.io": "crates.io",
|
|
27
|
+
crates: "crates.io",
|
|
15
28
|
python: "PyPI",
|
|
29
|
+
pypi: "PyPI",
|
|
30
|
+
pip: "PyPI",
|
|
31
|
+
py: "PyPI",
|
|
16
32
|
go: "Go",
|
|
33
|
+
golang: "Go",
|
|
17
34
|
java: "Maven",
|
|
35
|
+
maven: "Maven",
|
|
36
|
+
kotlin: "Maven",
|
|
37
|
+
gradle: "Maven",
|
|
38
|
+
csharp: "NuGet",
|
|
39
|
+
"c#": "NuGet",
|
|
18
40
|
dotnet: "NuGet",
|
|
19
|
-
|
|
41
|
+
nuget: "NuGet",
|
|
20
42
|
php: "Packagist",
|
|
43
|
+
composer: "Packagist",
|
|
44
|
+
packagist: "Packagist",
|
|
45
|
+
ruby: "RubyGems",
|
|
46
|
+
rubygems: "RubyGems",
|
|
47
|
+
gem: "RubyGems",
|
|
48
|
+
dart: "Pub",
|
|
49
|
+
flutter: "Pub",
|
|
50
|
+
pub: "Pub",
|
|
21
51
|
};
|
|
52
|
+
/**
|
|
53
|
+
* Map a language/ecosystem alias to its OSV ecosystem id (case-insensitive).
|
|
54
|
+
*
|
|
55
|
+
* Falls back to "npm" ONLY for genuinely unrecognized input — a last resort, not
|
|
56
|
+
* a real default. OSV-unindexed languages (Swift, C/C++) and unknowns produce no
|
|
57
|
+
* OSV matches anyway (their package names don't collide with the npm namespace),
|
|
58
|
+
* so this never fabricates a vulnerability. Ecosystems that lack a version
|
|
59
|
+
* resolver here (NuGet/Maven/Packagist/RubyGems/Pub) still get the CORRECT label,
|
|
60
|
+
* so the moment a resolver is added the scan is right by construction.
|
|
61
|
+
*/
|
|
22
62
|
export function mapEcosystem(language) {
|
|
23
|
-
return ECOSYSTEM_MAP[language] || "npm";
|
|
63
|
+
return ECOSYSTEM_MAP[language.trim().toLowerCase()] || "npm";
|
|
24
64
|
}
|
|
25
65
|
export function resolveVersions(cwd, deps, devDeps, language, targets = {}) {
|
|
26
66
|
const ecosystem = mapEcosystem(language);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "get_relevant_content",
|
|
3
|
-
"description": "Get filtered relevant content from 4DA's personalized feed.\n\nReturns content items that match the user's interests, tech stack, and learned preferences.\nItems are scored based on:\n- Explicit interests declared by the user\n- Tech stack and domains\n- ACE-detected context (recent files, projects, git activity)\n- Learned preferences from past interactions\n\nCall when the user asks what to read, what is relevant to them, or for content on a topic.",
|
|
3
|
+
"description": "Get filtered relevant content from 4DA's personalized feed.\n\nReturns content items that match the user's interests, tech stack, and learned preferences.\nItems are scored based on:\n- Explicit interests declared by the user\n- Tech stack and domains\n- ACE-detected context (recent files, projects, git activity)\n- Learned preferences from past interactions\n\nIMPORTANT for autonomous use: each item carries an `evidence_class` describing how it is grounded. The relevance_score float is NOT comparable across classes — gate actions on evidence_class, not the number:\n- \"osv_verified\": a security advisory matching one of the user's INSTALLED dependencies — safe to act on\n- \"dependency_grounded\": matched one of the user's declared dependencies — trustworthy\n- \"semantic_only\": topical/embedding relevance with NO dependency edge — surface to the human, verify before acting\n- \"keyword_heuristic\": standalone-mode keyword score (no Rust pipeline) — weakest signal\n\nCall when the user asks what to read, what is relevant to them, or for content on a topic.",
|
|
4
4
|
"inputSchema": {
|
|
5
5
|
"type": "object",
|
|
6
6
|
"properties": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "upgrade_planner",
|
|
3
|
-
"description": "Ranked upgrade recommendations for project dependencies. Combines vulnerability severity, deprecation status, and version distance
|
|
3
|
+
"description": "Ranked standalone upgrade recommendations for project dependencies — call before upgrading, adding, or auditing any dependency to pick the safest order. Combines vulnerability severity, deprecation status, and version distance into a prioritized plan.\n\nSplits recommendations three ways: quick wins (safe patch/minor upgrades), breaking changes (major version bumps), and transitive CVEs marked waiting_on_upstream (not declared in your manifest — the fix arrives via a parent package update or a lockfile refresh). Call dependency_health first for registry context and vulnerability_scan first for a CVE-aware plan; without a scan the output says it is CVE-blind.\n\nProvenance: standalone heuristic — a point-in-time plan from local lockfiles plus live registry/OSV lookups, not the 4DA desktop app's cross-project, version-confirmed plan.\n\nPrivacy: live mode may query public registries and OSV with package names and versions. It never sends source code.",
|
|
4
4
|
"inputSchema": {
|
|
5
5
|
"type": "object",
|
|
6
6
|
"properties": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "what_should_i_know",
|
|
3
|
-
"description": "Pre-task intelligence briefing. Given a task description and optional file paths, returns filtered advisories (security alerts, breaking changes), active decision windows, signal chains, relevant past decisions/memories, and a delegation assessment. Call this before starting any non-trivial task to get caught up on what matters.",
|
|
3
|
+
"description": "Pre-task intelligence briefing. Given a task description and optional file paths, returns filtered advisories (security alerts, breaking changes), active decision windows, signal chains, relevant past decisions/memories, and a delegation assessment. Call this before starting any non-trivial task to get caught up on what matters. If the task involves upgrading, adding, or auditing dependencies, follow with upgrade_planner for the ranked plan.",
|
|
4
4
|
"inputSchema": {
|
|
5
5
|
"type": "object",
|
|
6
6
|
"properties": {
|
|
@@ -137,7 +137,7 @@ export async function executeDependencyHealth(_db, params, liveIntel) {
|
|
|
137
137
|
parts.push("all healthy");
|
|
138
138
|
return {
|
|
139
139
|
scannedAt: new Date().toISOString(),
|
|
140
|
-
projectPath: process.cwd(),
|
|
140
|
+
projectPath: liveIntel.getProjectRoot() ?? process.cwd(),
|
|
141
141
|
ecosystemsScanned: ecosystems,
|
|
142
142
|
totalDeps: total,
|
|
143
143
|
outdatedCount: outdated,
|
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* upgrade_planner tool
|
|
3
3
|
*
|
|
4
|
-
* Ranked upgrade recommendations for project dependencies.
|
|
4
|
+
* Ranked standalone upgrade recommendations for project dependencies.
|
|
5
5
|
* Combines version freshness, vulnerability data, and deprecation status
|
|
6
6
|
* to produce a prioritized upgrade plan.
|
|
7
|
+
*
|
|
8
|
+
* Honesty contract (Phase 0 hardening):
|
|
9
|
+
* - Transitive CVEs are surfaced as `waiting_on_upstream` steps instead of
|
|
10
|
+
* being counted-but-never-recommended. No dependency-graph parsing happens
|
|
11
|
+
* here — parent mapping is the desktop core's job; the honest label is ours.
|
|
12
|
+
* - Output carries explicit heuristic provenance: this is a point-in-time
|
|
13
|
+
* standalone plan, not the 4DA app's cross-project, version-confirmed plan.
|
|
14
|
+
* - `projectPath` reports the directory set the dependencies were actually
|
|
15
|
+
* resolved from, not whatever `process.cwd()` happens to be.
|
|
16
|
+
* - When no vulnerability scan has run, the plan says so instead of silently
|
|
17
|
+
* producing a CVE-blind ranking.
|
|
7
18
|
*/
|
|
8
19
|
import type { FourDADatabase } from "../db.js";
|
|
9
20
|
import type { LiveIntelligence } from "../live/index.js";
|
|
@@ -22,6 +33,12 @@ interface UpgradeRecommendation {
|
|
|
22
33
|
reasons: string[];
|
|
23
34
|
breaking: boolean;
|
|
24
35
|
isDev: boolean;
|
|
36
|
+
/** Whether this package is declared in a manifest (direct) or only present via the lockfile (transitive). */
|
|
37
|
+
scope: "direct" | "transitive";
|
|
38
|
+
/** Direct deps you can bump yourself; transitive fixes arrive via a parent update or lockfile refresh. */
|
|
39
|
+
action: "upgrade_direct" | "waiting_on_upstream";
|
|
40
|
+
/** False when the dependency is gated behind a target spec not active on this host (label, never suppressed). */
|
|
41
|
+
platformActive: boolean;
|
|
25
42
|
}
|
|
26
43
|
interface UpgradePlanResult {
|
|
27
44
|
generatedAt: string;
|
|
@@ -31,6 +48,13 @@ interface UpgradePlanResult {
|
|
|
31
48
|
summary: string;
|
|
32
49
|
quickWins: number;
|
|
33
50
|
breakingChanges: number;
|
|
51
|
+
waitingOnUpstream: number;
|
|
52
|
+
/** False when no vulnerability scan has run this session — the ranking is then CVE-blind and says so. */
|
|
53
|
+
vulnerabilityDataAvailable: boolean;
|
|
54
|
+
provenance: {
|
|
55
|
+
mode: "standalone_heuristic";
|
|
56
|
+
note: string;
|
|
57
|
+
};
|
|
34
58
|
}
|
|
35
59
|
export declare const upgradePlannerTool: {
|
|
36
60
|
name: string;
|
|
@@ -2,13 +2,27 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* upgrade_planner tool
|
|
4
4
|
*
|
|
5
|
-
* Ranked upgrade recommendations for project dependencies.
|
|
5
|
+
* Ranked standalone upgrade recommendations for project dependencies.
|
|
6
6
|
* Combines version freshness, vulnerability data, and deprecation status
|
|
7
7
|
* to produce a prioritized upgrade plan.
|
|
8
|
+
*
|
|
9
|
+
* Honesty contract (Phase 0 hardening):
|
|
10
|
+
* - Transitive CVEs are surfaced as `waiting_on_upstream` steps instead of
|
|
11
|
+
* being counted-but-never-recommended. No dependency-graph parsing happens
|
|
12
|
+
* here — parent mapping is the desktop core's job; the honest label is ours.
|
|
13
|
+
* - Output carries explicit heuristic provenance: this is a point-in-time
|
|
14
|
+
* standalone plan, not the 4DA app's cross-project, version-confirmed plan.
|
|
15
|
+
* - `projectPath` reports the directory set the dependencies were actually
|
|
16
|
+
* resolved from, not whatever `process.cwd()` happens to be.
|
|
17
|
+
* - When no vulnerability scan has run, the plan says so instead of silently
|
|
18
|
+
* producing a CVE-blind ranking.
|
|
8
19
|
*/
|
|
20
|
+
import { maxSemver } from "../live/semver-utils.js";
|
|
21
|
+
const PROVENANCE_NOTE = "Point-in-time heuristic from local lockfiles plus live registry/OSV lookups. " +
|
|
22
|
+
"Not the 4DA desktop app's cross-project, version-confirmed plan.";
|
|
9
23
|
export const upgradePlannerTool = {
|
|
10
24
|
name: "upgrade_planner",
|
|
11
|
-
description: "Ranked upgrade recommendations for dependencies. Prioritizes by vulnerability severity, deprecation, and version distance.
|
|
25
|
+
description: "Ranked standalone upgrade recommendations for dependencies — call before upgrading, adding, or auditing any dependency to pick the safest order. Prioritizes by vulnerability severity (direct AND transitive), deprecation, and version distance. Splits quick wins (patch/minor) from breaking changes (major) and from transitive CVEs waiting on upstream. Run vulnerability_scan first for a CVE-aware plan. Privacy: live mode may query public registries and OSV with package names and versions; it never sends source code.",
|
|
12
26
|
inputSchema: {
|
|
13
27
|
type: "object",
|
|
14
28
|
properties: {
|
|
@@ -28,6 +42,14 @@ export const upgradePlannerTool = {
|
|
|
28
42
|
},
|
|
29
43
|
},
|
|
30
44
|
};
|
|
45
|
+
const RISK_LEVELS = { low: 0, medium: 1, high: 2, critical: 3 };
|
|
46
|
+
function severityToRisk(severities) {
|
|
47
|
+
if (severities.includes("critical"))
|
|
48
|
+
return "critical";
|
|
49
|
+
if (severities.includes("high"))
|
|
50
|
+
return "high";
|
|
51
|
+
return "medium";
|
|
52
|
+
}
|
|
31
53
|
export async function executeUpgradePlanner(_db, params, liveIntel) {
|
|
32
54
|
if (!liveIntel || !liveIntel.isInitialized()) {
|
|
33
55
|
return {
|
|
@@ -38,16 +60,20 @@ export async function executeUpgradePlanner(_db, params, liveIntel) {
|
|
|
38
60
|
summary: "No project detected. Run from a directory with package.json, Cargo.toml, pyproject.toml, or go.mod.",
|
|
39
61
|
quickWins: 0,
|
|
40
62
|
breakingChanges: 0,
|
|
63
|
+
waitingOnUpstream: 0,
|
|
64
|
+
vulnerabilityDataAvailable: false,
|
|
65
|
+
provenance: { mode: "standalone_heuristic", note: PROVENANCE_NOTE },
|
|
41
66
|
};
|
|
42
67
|
}
|
|
43
68
|
const includeDev = params.include_dev ?? false;
|
|
44
69
|
let deps = liveIntel.getResolvedDeps();
|
|
45
70
|
if (!includeDev)
|
|
46
71
|
deps = deps.filter((d) => !d.isDev);
|
|
47
|
-
// Fetch registry data
|
|
72
|
+
// Fetch registry data (live lookups for direct deps; cached where fresh)
|
|
48
73
|
const registryData = await liveIntel.fetchRegistryHealth(deps);
|
|
49
|
-
//
|
|
74
|
+
// Vulnerability data from the last scan this session (no network here).
|
|
50
75
|
const vulnResult = liveIntel.getVulnerabilities();
|
|
76
|
+
const vulnerabilityDataAvailable = vulnResult !== null;
|
|
51
77
|
const vulnsByPackage = new Map();
|
|
52
78
|
if (vulnResult) {
|
|
53
79
|
for (const v of vulnResult.vulnerabilities) {
|
|
@@ -58,31 +84,32 @@ export async function executeUpgradePlanner(_db, params, liveIntel) {
|
|
|
58
84
|
vulnId: v.vulnId,
|
|
59
85
|
summary: v.summary,
|
|
60
86
|
fixedVersion: v.fixedVersion,
|
|
87
|
+
platformActive: v.platformActive,
|
|
61
88
|
});
|
|
62
89
|
}
|
|
63
90
|
}
|
|
64
|
-
// Build recommendations
|
|
91
|
+
// Build recommendations — direct deps (registry-informed)
|
|
65
92
|
const recommendations = [];
|
|
93
|
+
const directPackages = new Set();
|
|
66
94
|
for (const dep of registryData) {
|
|
95
|
+
directPackages.add(`${dep.ecosystem}\0${dep.name}`);
|
|
67
96
|
const reasons = [];
|
|
68
97
|
let risk = "low";
|
|
69
98
|
let targetVersion = dep.latestStableVersion || dep.latestVersion;
|
|
99
|
+
let platformActive = true;
|
|
70
100
|
// Check vulnerabilities
|
|
71
101
|
const vulns = vulnsByPackage.get(dep.name);
|
|
72
102
|
if (vulns && vulns.length > 0) {
|
|
73
|
-
|
|
74
|
-
const hasHigh = vulns.some((v) => v.severity === "high");
|
|
75
|
-
if (hasCritical)
|
|
76
|
-
risk = "critical";
|
|
77
|
-
else if (hasHigh)
|
|
78
|
-
risk = "high";
|
|
79
|
-
else
|
|
80
|
-
risk = "medium";
|
|
103
|
+
risk = severityToRisk(vulns.map((v) => v.severity));
|
|
81
104
|
reasons.push(`${vulns.length} known CVE${vulns.length !== 1 ? "s" : ""} (${vulns.map((v) => v.vulnId).join(", ")})`);
|
|
105
|
+
if (vulns.every((v) => !v.platformActive)) {
|
|
106
|
+
platformActive = false;
|
|
107
|
+
reasons.push("Affected code is gated behind a target not active on this platform");
|
|
108
|
+
}
|
|
82
109
|
// Use fixed version as target if available
|
|
83
|
-
const fixedVersions = vulns.map((v) => v.fixedVersion).filter(Boolean);
|
|
110
|
+
const fixedVersions = vulns.map((v) => v.fixedVersion).filter((f) => Boolean(f));
|
|
84
111
|
if (fixedVersions.length > 0 && !targetVersion) {
|
|
85
|
-
targetVersion = fixedVersions
|
|
112
|
+
targetVersion = maxSemver(fixedVersions);
|
|
86
113
|
}
|
|
87
114
|
}
|
|
88
115
|
// Check deprecation
|
|
@@ -121,45 +148,101 @@ export async function executeUpgradePlanner(_db, params, liveIntel) {
|
|
|
121
148
|
reasons,
|
|
122
149
|
breaking: dep.versionsBehind?.label === "major",
|
|
123
150
|
isDev: dep.isDev,
|
|
151
|
+
scope: "direct",
|
|
152
|
+
action: "upgrade_direct",
|
|
153
|
+
platformActive,
|
|
124
154
|
});
|
|
125
155
|
}
|
|
156
|
+
// Transitive vulnerable packages — previously counted in scan totals but
|
|
157
|
+
// never recommended. Surfaced as waiting_on_upstream with honest labels.
|
|
158
|
+
if (vulnResult) {
|
|
159
|
+
const transitive = new Map();
|
|
160
|
+
for (const v of vulnResult.vulnerabilities) {
|
|
161
|
+
if (v.isDirect)
|
|
162
|
+
continue;
|
|
163
|
+
if (directPackages.has(`${v.ecosystem}\0${v.package}`))
|
|
164
|
+
continue;
|
|
165
|
+
if (!includeDev && v.isDev && v.devScopeKnown)
|
|
166
|
+
continue;
|
|
167
|
+
const key = `${v.ecosystem}\0${v.package}`;
|
|
168
|
+
if (!transitive.has(key))
|
|
169
|
+
transitive.set(key, []);
|
|
170
|
+
transitive.get(key).push(v);
|
|
171
|
+
}
|
|
172
|
+
for (const entries of transitive.values()) {
|
|
173
|
+
const first = entries[0];
|
|
174
|
+
const fixedVersions = entries.map((e) => e.fixedVersion).filter((f) => Boolean(f));
|
|
175
|
+
const platformActive = !entries.every((e) => !e.platformActive);
|
|
176
|
+
const reasons = [
|
|
177
|
+
`${entries.length} known CVE${entries.length !== 1 ? "s" : ""} (${entries.map((e) => e.vulnId).join(", ")})`,
|
|
178
|
+
"Transitive dependency — not declared in your manifest; the fix arrives via a parent package update or a lockfile refresh",
|
|
179
|
+
];
|
|
180
|
+
if (!platformActive) {
|
|
181
|
+
reasons.push("Affected code is gated behind a target not active on this platform");
|
|
182
|
+
}
|
|
183
|
+
recommendations.push({
|
|
184
|
+
package: first.package,
|
|
185
|
+
ecosystem: first.ecosystem,
|
|
186
|
+
currentVersion: first.currentVersion,
|
|
187
|
+
targetVersion: fixedVersions.length > 0 ? maxSemver(fixedVersions) : null,
|
|
188
|
+
upgradeType: "unknown",
|
|
189
|
+
risk: severityToRisk(entries.map((e) => e.severity)),
|
|
190
|
+
reasons,
|
|
191
|
+
breaking: false,
|
|
192
|
+
isDev: entries.every((e) => e.isDev),
|
|
193
|
+
scope: "transitive",
|
|
194
|
+
action: "waiting_on_upstream",
|
|
195
|
+
platformActive,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
126
199
|
// Filter by risk threshold
|
|
127
|
-
const riskLevels = { low: 0, medium: 1, high: 2, critical: 3 };
|
|
128
200
|
const threshold = params.risk_threshold ?? "all";
|
|
129
201
|
const filtered = threshold === "all"
|
|
130
202
|
? recommendations
|
|
131
|
-
: recommendations.filter((r) =>
|
|
132
|
-
// Sort: critical > high > medium > low, then
|
|
203
|
+
: recommendations.filter((r) => RISK_LEVELS[r.risk] >= RISK_LEVELS[threshold]);
|
|
204
|
+
// Sort: critical > high > medium > low, then fixable-now (direct) before
|
|
205
|
+
// waiting-on-upstream (transitive), then non-breaking first (quick wins).
|
|
133
206
|
filtered.sort((a, b) => {
|
|
134
|
-
const riskDiff =
|
|
207
|
+
const riskDiff = RISK_LEVELS[b.risk] - RISK_LEVELS[a.risk];
|
|
135
208
|
if (riskDiff !== 0)
|
|
136
209
|
return riskDiff;
|
|
210
|
+
if (a.scope !== b.scope)
|
|
211
|
+
return a.scope === "direct" ? -1 : 1;
|
|
137
212
|
if (a.breaking !== b.breaking)
|
|
138
213
|
return a.breaking ? 1 : -1;
|
|
139
214
|
return 0;
|
|
140
215
|
});
|
|
141
216
|
const maxRecs = params.max_recommendations ?? 20;
|
|
142
217
|
const limited = filtered.slice(0, maxRecs);
|
|
143
|
-
const
|
|
144
|
-
const
|
|
218
|
+
const direct = limited.filter((r) => r.scope === "direct");
|
|
219
|
+
const quickWins = direct.filter((r) => !r.breaking).length;
|
|
220
|
+
const breakingChanges = direct.filter((r) => r.breaking).length;
|
|
221
|
+
const waitingOnUpstream = limited.filter((r) => r.scope === "transitive").length;
|
|
145
222
|
// Summary
|
|
146
223
|
const parts = [];
|
|
147
|
-
parts.push(`${limited.length}
|
|
148
|
-
if (
|
|
149
|
-
parts.push(`${quickWins} quick win${quickWins !== 1 ? "s" : ""}
|
|
150
|
-
if (
|
|
151
|
-
parts.push(`${
|
|
224
|
+
parts.push(`${limited.length} recommendation${limited.length !== 1 ? "s" : ""}`);
|
|
225
|
+
if (direct.length > 0)
|
|
226
|
+
parts.push(`${direct.length} fixable directly (${quickWins} quick win${quickWins !== 1 ? "s" : ""}, ${breakingChanges} breaking)`);
|
|
227
|
+
if (waitingOnUpstream > 0)
|
|
228
|
+
parts.push(`${waitingOnUpstream} transitive, waiting on upstream`);
|
|
152
229
|
const criticalCount = limited.filter((r) => r.risk === "critical").length;
|
|
153
230
|
if (criticalCount > 0)
|
|
154
231
|
parts.push(`${criticalCount} critical`);
|
|
232
|
+
if (!vulnerabilityDataAvailable) {
|
|
233
|
+
parts.push("CVE data not loaded — run vulnerability_scan first for a security-aware plan");
|
|
234
|
+
}
|
|
155
235
|
return {
|
|
156
236
|
generatedAt: new Date().toISOString(),
|
|
157
|
-
projectPath: process.cwd(),
|
|
237
|
+
projectPath: liveIntel.getProjectRoot() ?? vulnResult?.projectPath ?? process.cwd(),
|
|
158
238
|
totalDeps: deps.length,
|
|
159
239
|
recommendations: limited,
|
|
160
240
|
summary: parts.join(". ") + ".",
|
|
161
241
|
quickWins,
|
|
162
242
|
breakingChanges,
|
|
243
|
+
waitingOnUpstream,
|
|
244
|
+
vulnerabilityDataAvailable,
|
|
245
|
+
provenance: { mode: "standalone_heuristic", note: PROVENANCE_NOTE },
|
|
163
246
|
};
|
|
164
247
|
}
|
|
165
248
|
//# sourceMappingURL=upgrade-planner.js.map
|
|
@@ -20,7 +20,7 @@ import { memoryEmbedText } from "./agent-memory.js";
|
|
|
20
20
|
// ============================================================================
|
|
21
21
|
export const whatShouldIKnowTool = {
|
|
22
22
|
name: "what_should_i_know",
|
|
23
|
-
description: "Pre-task intelligence briefing. Given a task description and optional file paths, returns filtered advisories, decision windows, signal chains, relevant wisdom, and a delegation assessment. Call before starting any non-trivial task.",
|
|
23
|
+
description: "Pre-task intelligence briefing. Given a task description and optional file paths, returns filtered advisories, decision windows, signal chains, relevant wisdom, and a delegation assessment. Call before starting any non-trivial task. If the task involves upgrading, adding, or auditing dependencies, follow with upgrade_planner for the ranked plan.",
|
|
24
24
|
inputSchema: {
|
|
25
25
|
type: "object",
|
|
26
26
|
properties: {
|
package/dist/types.d.ts
CHANGED
|
@@ -42,6 +42,15 @@ export interface RelevantItem {
|
|
|
42
42
|
signal_type: string | null;
|
|
43
43
|
/** Pipeline-computed signal priority (critical, high, medium, low) */
|
|
44
44
|
signal_priority: string | null;
|
|
45
|
+
/**
|
|
46
|
+
* Provenance of this item's relevance — how an autonomous agent should weight it.
|
|
47
|
+
* Scores alone are NOT comparable across classes; gate actions on this, not the float:
|
|
48
|
+
* - "osv_verified": a security advisory matching one of YOUR installed dependencies — safe to act on
|
|
49
|
+
* - "dependency_grounded": matched one of your declared dependencies — trustworthy
|
|
50
|
+
* - "semantic_only": topical/embedding relevance with NO dependency edge — verify before acting
|
|
51
|
+
* - "keyword_heuristic": standalone-mode keyword score (no Rust pipeline) — weakest signal
|
|
52
|
+
*/
|
|
53
|
+
evidence_class: "osv_verified" | "dependency_grounded" | "semantic_only" | "keyword_heuristic";
|
|
45
54
|
}
|
|
46
55
|
/**
|
|
47
56
|
* Explicit user interest
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@4da/mcp-server",
|
|
3
|
-
"version": "4.6.
|
|
3
|
+
"version": "4.6.3",
|
|
4
4
|
"mcpName": "io.github.runyourempire/4da-mcp-server",
|
|
5
5
|
"description": "Dependency intelligence for AI coding agents. Live CVE scanning, dependency health, upgrade planning, ecosystem news, decision memory — zero config, privacy-first. 9 tools standalone, 14 total with desktop app.",
|
|
6
6
|
"type": "module",
|
|
@@ -85,8 +85,9 @@
|
|
|
85
85
|
},
|
|
86
86
|
"devDependencies": {
|
|
87
87
|
"@types/better-sqlite3": "^7.6.11",
|
|
88
|
-
"@types/node": "^
|
|
88
|
+
"@types/node": "^26.0.1",
|
|
89
89
|
"typescript": "^5.3.0",
|
|
90
|
+
"vite": "~7.3.5",
|
|
90
91
|
"vitest": "^4.1.5"
|
|
91
92
|
},
|
|
92
93
|
"engines": {
|
|
@@ -99,7 +100,9 @@
|
|
|
99
100
|
],
|
|
100
101
|
"overrides": {
|
|
101
102
|
"fast-uri": ">=3.1.2",
|
|
102
|
-
"ip-address": "^10.1.1"
|
|
103
|
+
"ip-address": "^10.1.1",
|
|
104
|
+
"vite": ">=7.3.5 <8",
|
|
105
|
+
"esbuild": ">=0.28.1"
|
|
103
106
|
}
|
|
104
107
|
}
|
|
105
108
|
}
|