@4da/mcp-server 4.6.1 → 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/README.md +3 -1
- package/dist/db.d.ts +21 -2
- package/dist/db.js +66 -16
- package/dist/doctor.js +5 -3
- package/dist/embeddings.d.ts +88 -0
- package/dist/embeddings.js +292 -0
- package/dist/index.d.ts +9 -9
- package/dist/index.js +21 -15
- package/dist/live/cvss.d.ts +16 -0
- package/dist/live/cvss.js +62 -0
- package/dist/live/index.d.ts +9 -1
- package/dist/live/index.js +40 -3
- package/dist/live/npm-registry.js +29 -6
- package/dist/live/osv-scanner.d.ts +8 -0
- package/dist/live/osv-scanner.js +115 -30
- package/dist/live/platform.d.ts +32 -0
- package/dist/live/platform.js +156 -0
- package/dist/live/semver-utils.d.ts +8 -0
- package/dist/live/semver-utils.js +24 -0
- package/dist/live/types.d.ts +31 -9
- package/dist/live/version-resolver.d.ts +12 -2
- package/dist/live/version-resolver.js +63 -20
- package/dist/llm.js +1 -1
- package/dist/project-scanner.d.ts +2 -0
- package/dist/project-scanner.js +34 -0
- package/dist/scheduler/core.d.ts +25 -0
- package/dist/scheduler/core.js +61 -0
- package/dist/scheduler/freshness.d.ts +17 -0
- package/dist/scheduler/freshness.js +41 -0
- package/dist/scheduler/index.d.ts +21 -0
- package/dist/scheduler/index.js +32 -0
- package/dist/scheduler/refreshers.d.ts +21 -0
- package/dist/scheduler/refreshers.js +21 -0
- 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/agent-memory.d.ts +11 -1
- package/dist/tools/agent-memory.js +120 -14
- package/dist/tools/decision-enforcement.d.ts +17 -1
- package/dist/tools/decision-enforcement.js +109 -81
- package/dist/tools/decision-memory.d.ts +1 -1
- package/dist/tools/decision-memory.js +58 -28
- package/dist/tools/decision-recall.d.ts +59 -0
- package/dist/tools/decision-recall.js +87 -0
- package/dist/tools/dependency-health.js +6 -4
- package/dist/tools/developer-dna.js +4 -2
- package/dist/tools/knowledge-gaps.js +16 -4
- package/dist/tools/recall.d.ts +33 -0
- package/dist/tools/recall.js +134 -0
- package/dist/tools/upgrade-planner.d.ts +25 -1
- package/dist/tools/upgrade-planner.js +111 -30
- package/dist/tools/vulnerability-scan.d.ts +46 -0
- package/dist/tools/vulnerability-scan.js +64 -24
- package/dist/tools/what-should-i-know.d.ts +3 -1
- package/dist/tools/what-should-i-know.js +210 -106
- package/dist/types.d.ts +9 -0
- package/package.json +8 -3
- package/dist/auth.d.ts.map +0 -1
- package/dist/auth.js.map +0 -1
- package/dist/db.d.ts.map +0 -1
- package/dist/db.js.map +0 -1
- package/dist/doctor.d.ts.map +0 -1
- package/dist/doctor.js.map +0 -1
- package/dist/http-transport.d.ts.map +0 -1
- package/dist/http-transport.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/live/cache.d.ts.map +0 -1
- package/dist/live/cache.js.map +0 -1
- package/dist/live/crates-registry.d.ts.map +0 -1
- package/dist/live/crates-registry.js.map +0 -1
- package/dist/live/go-registry.d.ts.map +0 -1
- package/dist/live/go-registry.js.map +0 -1
- package/dist/live/hn-fetcher.d.ts.map +0 -1
- package/dist/live/hn-fetcher.js.map +0 -1
- package/dist/live/http-utils.d.ts.map +0 -1
- package/dist/live/http-utils.js.map +0 -1
- package/dist/live/index.d.ts.map +0 -1
- package/dist/live/index.js.map +0 -1
- package/dist/live/npm-registry.d.ts.map +0 -1
- package/dist/live/npm-registry.js.map +0 -1
- package/dist/live/osv-scanner.d.ts.map +0 -1
- package/dist/live/osv-scanner.js.map +0 -1
- package/dist/live/pypi-registry.d.ts.map +0 -1
- package/dist/live/pypi-registry.js.map +0 -1
- package/dist/live/rate-limiter.d.ts.map +0 -1
- package/dist/live/rate-limiter.js.map +0 -1
- package/dist/live/semver-utils.d.ts.map +0 -1
- package/dist/live/semver-utils.js.map +0 -1
- package/dist/live/types.d.ts.map +0 -1
- package/dist/live/types.js.map +0 -1
- package/dist/live/version-resolver.d.ts.map +0 -1
- package/dist/live/version-resolver.js.map +0 -1
- package/dist/live-singleton.d.ts.map +0 -1
- package/dist/live-singleton.js.map +0 -1
- package/dist/llm.d.ts.map +0 -1
- package/dist/llm.js.map +0 -1
- package/dist/output-manager.d.ts.map +0 -1
- package/dist/output-manager.js.map +0 -1
- package/dist/project-scanner.d.ts.map +0 -1
- package/dist/project-scanner.js.map +0 -1
- package/dist/schema-registry.d.ts.map +0 -1
- package/dist/schema-registry.js.map +0 -1
- package/dist/setup.d.ts.map +0 -1
- package/dist/setup.js.map +0 -1
- package/dist/tool-dispatch.d.ts.map +0 -1
- package/dist/tool-dispatch.js.map +0 -1
- package/dist/tools/agent-memory.d.ts.map +0 -1
- package/dist/tools/agent-memory.js.map +0 -1
- package/dist/tools/decision-enforcement.d.ts.map +0 -1
- package/dist/tools/decision-enforcement.js.map +0 -1
- package/dist/tools/decision-memory.d.ts.map +0 -1
- package/dist/tools/decision-memory.js.map +0 -1
- package/dist/tools/dependency-health.d.ts.map +0 -1
- package/dist/tools/dependency-health.js.map +0 -1
- package/dist/tools/developer-dna.d.ts.map +0 -1
- package/dist/tools/developer-dna.js.map +0 -1
- package/dist/tools/ecosystem-pulse.d.ts.map +0 -1
- package/dist/tools/ecosystem-pulse.js.map +0 -1
- package/dist/tools/get-actionable-signals.d.ts.map +0 -1
- package/dist/tools/get-actionable-signals.js.map +0 -1
- package/dist/tools/get-context.d.ts.map +0 -1
- package/dist/tools/get-context.js.map +0 -1
- package/dist/tools/get-relevant-content.d.ts.map +0 -1
- package/dist/tools/get-relevant-content.js.map +0 -1
- package/dist/tools/index.d.ts.map +0 -1
- package/dist/tools/index.js.map +0 -1
- package/dist/tools/knowledge-gaps.d.ts.map +0 -1
- package/dist/tools/knowledge-gaps.js.map +0 -1
- package/dist/tools/record-feedback.d.ts.map +0 -1
- package/dist/tools/record-feedback.js.map +0 -1
- package/dist/tools/upgrade-planner.d.ts.map +0 -1
- package/dist/tools/upgrade-planner.js.map +0 -1
- package/dist/tools/vulnerability-scan.d.ts.map +0 -1
- package/dist/tools/vulnerability-scan.js.map +0 -1
- package/dist/tools/what-should-i-know.d.ts.map +0 -1
- package/dist/tools/what-should-i-know.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
|
@@ -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
|
|
@@ -106,11 +133,9 @@ export async function executeUpgradePlanner(_db, params, liveIntel) {
|
|
|
106
133
|
reasons.push(`${d.patch} patch${d.patch !== 1 ? "es" : ""} behind`);
|
|
107
134
|
}
|
|
108
135
|
}
|
|
109
|
-
// Skip if no upgrade needed
|
|
136
|
+
// Skip if no upgrade needed (no vuln, not deprecated, not behind => no reasons)
|
|
110
137
|
if (reasons.length === 0)
|
|
111
138
|
continue;
|
|
112
|
-
if (dep.versionsBehind?.label === "up-to-date" && !dep.deprecated && !vulns)
|
|
113
|
-
continue;
|
|
114
139
|
const label = dep.versionsBehind?.label;
|
|
115
140
|
const upgradeType = !label || label === "up-to-date" ? "patch" : label;
|
|
116
141
|
recommendations.push({
|
|
@@ -123,45 +148,101 @@ export async function executeUpgradePlanner(_db, params, liveIntel) {
|
|
|
123
148
|
reasons,
|
|
124
149
|
breaking: dep.versionsBehind?.label === "major",
|
|
125
150
|
isDev: dep.isDev,
|
|
151
|
+
scope: "direct",
|
|
152
|
+
action: "upgrade_direct",
|
|
153
|
+
platformActive,
|
|
126
154
|
});
|
|
127
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
|
+
}
|
|
128
199
|
// Filter by risk threshold
|
|
129
|
-
const riskLevels = { low: 0, medium: 1, high: 2, critical: 3 };
|
|
130
200
|
const threshold = params.risk_threshold ?? "all";
|
|
131
201
|
const filtered = threshold === "all"
|
|
132
202
|
? recommendations
|
|
133
|
-
: recommendations.filter((r) =>
|
|
134
|
-
// 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).
|
|
135
206
|
filtered.sort((a, b) => {
|
|
136
|
-
const riskDiff =
|
|
207
|
+
const riskDiff = RISK_LEVELS[b.risk] - RISK_LEVELS[a.risk];
|
|
137
208
|
if (riskDiff !== 0)
|
|
138
209
|
return riskDiff;
|
|
210
|
+
if (a.scope !== b.scope)
|
|
211
|
+
return a.scope === "direct" ? -1 : 1;
|
|
139
212
|
if (a.breaking !== b.breaking)
|
|
140
213
|
return a.breaking ? 1 : -1;
|
|
141
214
|
return 0;
|
|
142
215
|
});
|
|
143
216
|
const maxRecs = params.max_recommendations ?? 20;
|
|
144
217
|
const limited = filtered.slice(0, maxRecs);
|
|
145
|
-
const
|
|
146
|
-
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;
|
|
147
222
|
// Summary
|
|
148
223
|
const parts = [];
|
|
149
|
-
parts.push(`${limited.length}
|
|
150
|
-
if (
|
|
151
|
-
parts.push(`${quickWins} quick win${quickWins !== 1 ? "s" : ""}
|
|
152
|
-
if (
|
|
153
|
-
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`);
|
|
154
229
|
const criticalCount = limited.filter((r) => r.risk === "critical").length;
|
|
155
230
|
if (criticalCount > 0)
|
|
156
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
|
+
}
|
|
157
235
|
return {
|
|
158
236
|
generatedAt: new Date().toISOString(),
|
|
159
|
-
projectPath: process.cwd(),
|
|
237
|
+
projectPath: liveIntel.getProjectRoot() ?? vulnResult?.projectPath ?? process.cwd(),
|
|
160
238
|
totalDeps: deps.length,
|
|
161
239
|
recommendations: limited,
|
|
162
240
|
summary: parts.join(". ") + ".",
|
|
163
241
|
quickWins,
|
|
164
242
|
breakingChanges,
|
|
243
|
+
waitingOnUpstream,
|
|
244
|
+
vulnerabilityDataAvailable,
|
|
245
|
+
provenance: { mode: "standalone_heuristic", note: PROVENANCE_NOTE },
|
|
165
246
|
};
|
|
166
247
|
}
|
|
167
248
|
//# sourceMappingURL=upgrade-planner.js.map
|
|
@@ -42,9 +42,11 @@ export declare const vulnerabilityScanTool: {
|
|
|
42
42
|
export declare function executeVulnerabilityScan(db: FourDADatabase, params: VulnerabilityScanParams, liveIntelligence: LiveIntelligence | null): Promise<{
|
|
43
43
|
scan_time: string;
|
|
44
44
|
project: string;
|
|
45
|
+
host_platform: string;
|
|
45
46
|
ecosystems_scanned: string[];
|
|
46
47
|
total_dependencies_scanned: number;
|
|
47
48
|
total_vulnerable_packages: number;
|
|
49
|
+
platform_inactive_packages: number;
|
|
48
50
|
by_severity: {
|
|
49
51
|
critical: number;
|
|
50
52
|
high: number;
|
|
@@ -59,6 +61,26 @@ export declare function executeVulnerabilityScan(db: FourDADatabase, params: Vul
|
|
|
59
61
|
is_dev: boolean;
|
|
60
62
|
is_direct: boolean;
|
|
61
63
|
dev_scope_known: boolean;
|
|
64
|
+
target: string | null;
|
|
65
|
+
platform_active: boolean;
|
|
66
|
+
vuln_id: string;
|
|
67
|
+
aliases: string[];
|
|
68
|
+
severity: "critical" | "high" | "medium" | "low" | "unknown";
|
|
69
|
+
cvss_score: number | null;
|
|
70
|
+
summary: string;
|
|
71
|
+
fixed_version: string | null;
|
|
72
|
+
published: string;
|
|
73
|
+
references: string[];
|
|
74
|
+
}[];
|
|
75
|
+
platform_inactive_vulnerabilities: {
|
|
76
|
+
package: string;
|
|
77
|
+
current_version: string;
|
|
78
|
+
ecosystem: import("../live/types.js").OsvEcosystem;
|
|
79
|
+
is_dev: boolean;
|
|
80
|
+
is_direct: boolean;
|
|
81
|
+
dev_scope_known: boolean;
|
|
82
|
+
target: string | null;
|
|
83
|
+
platform_active: boolean;
|
|
62
84
|
vuln_id: string;
|
|
63
85
|
aliases: string[];
|
|
64
86
|
severity: "critical" | "high" | "medium" | "low" | "unknown";
|
|
@@ -76,6 +98,7 @@ export declare function executeVulnerabilityScan(db: FourDADatabase, params: Vul
|
|
|
76
98
|
cached: boolean;
|
|
77
99
|
offline: boolean;
|
|
78
100
|
privacy: string;
|
|
101
|
+
relevance: string;
|
|
79
102
|
};
|
|
80
103
|
} | {
|
|
81
104
|
error: null;
|
|
@@ -91,9 +114,11 @@ export declare function executeVulnerabilityScan(db: FourDADatabase, params: Vul
|
|
|
91
114
|
note: string;
|
|
92
115
|
scan_time: string;
|
|
93
116
|
project: string;
|
|
117
|
+
host_platform: string;
|
|
94
118
|
ecosystems_scanned: string[];
|
|
95
119
|
total_dependencies_scanned: number;
|
|
96
120
|
total_vulnerable_packages: number;
|
|
121
|
+
platform_inactive_packages: number;
|
|
97
122
|
by_severity: {
|
|
98
123
|
critical: number;
|
|
99
124
|
high: number;
|
|
@@ -108,6 +133,26 @@ export declare function executeVulnerabilityScan(db: FourDADatabase, params: Vul
|
|
|
108
133
|
is_dev: boolean;
|
|
109
134
|
is_direct: boolean;
|
|
110
135
|
dev_scope_known: boolean;
|
|
136
|
+
target: string | null;
|
|
137
|
+
platform_active: boolean;
|
|
138
|
+
vuln_id: string;
|
|
139
|
+
aliases: string[];
|
|
140
|
+
severity: "critical" | "high" | "medium" | "low" | "unknown";
|
|
141
|
+
cvss_score: number | null;
|
|
142
|
+
summary: string;
|
|
143
|
+
fixed_version: string | null;
|
|
144
|
+
published: string;
|
|
145
|
+
references: string[];
|
|
146
|
+
}[];
|
|
147
|
+
platform_inactive_vulnerabilities: {
|
|
148
|
+
package: string;
|
|
149
|
+
current_version: string;
|
|
150
|
+
ecosystem: import("../live/types.js").OsvEcosystem;
|
|
151
|
+
is_dev: boolean;
|
|
152
|
+
is_direct: boolean;
|
|
153
|
+
dev_scope_known: boolean;
|
|
154
|
+
target: string | null;
|
|
155
|
+
platform_active: boolean;
|
|
111
156
|
vuln_id: string;
|
|
112
157
|
aliases: string[];
|
|
113
158
|
severity: "critical" | "high" | "medium" | "low" | "unknown";
|
|
@@ -125,6 +170,7 @@ export declare function executeVulnerabilityScan(db: FourDADatabase, params: Vul
|
|
|
125
170
|
cached: boolean;
|
|
126
171
|
offline: boolean;
|
|
127
172
|
privacy: string;
|
|
173
|
+
relevance: string;
|
|
128
174
|
};
|
|
129
175
|
error?: undefined;
|
|
130
176
|
offline?: undefined;
|
|
@@ -10,6 +10,8 @@ import * as fs from "node:fs";
|
|
|
10
10
|
import * as path from "node:path";
|
|
11
11
|
import { LiveIntelligence } from "../live/index.js";
|
|
12
12
|
import { scanCurrentProject } from "../project-scanner.js";
|
|
13
|
+
import { hostTarget } from "../live/platform.js";
|
|
14
|
+
import { maxSemver } from "../live/semver-utils.js";
|
|
13
15
|
export const vulnerabilityScanTool = {
|
|
14
16
|
name: "vulnerability_scan",
|
|
15
17
|
description: "Scan project dependencies for known vulnerabilities (CVEs) using OSV.dev. " +
|
|
@@ -60,7 +62,7 @@ export async function executeVulnerabilityScan(db, params, liveIntelligence) {
|
|
|
60
62
|
if (normalizedPath(projectPath) !== normalizedPath(process.cwd())) {
|
|
61
63
|
const scan = scanCurrentProject(projectPath);
|
|
62
64
|
scanner = new LiveIntelligence(db.getRawDb());
|
|
63
|
-
scanner.initFromMultiEcosystem(projectPath, scan.depsByEcosystem);
|
|
65
|
+
scanner.initFromMultiEcosystem(projectPath, scan.depsByEcosystem, scan.depTargets);
|
|
64
66
|
}
|
|
65
67
|
if (!scanner.isInitialized()) {
|
|
66
68
|
return {
|
|
@@ -121,47 +123,54 @@ function formatResult(result, severityFilter) {
|
|
|
121
123
|
}
|
|
122
124
|
// Sort: critical first, then high, etc.
|
|
123
125
|
vulns.sort((a, b) => (SEVERITY_ORDER[b.severity] || 0) - (SEVERITY_ORDER[a.severity] || 0));
|
|
124
|
-
//
|
|
126
|
+
// Relevance split: advisories on deps NOT active on the host platform are not
|
|
127
|
+
// actionable for this user (e.g. a Linux-only crate's CVE shown to a Windows
|
|
128
|
+
// dev). Keep them, but separated and labeled so they don't pollute the signal.
|
|
129
|
+
const active = vulns.filter((v) => v.platformActive !== false);
|
|
130
|
+
const inactive = vulns.filter((v) => v.platformActive === false);
|
|
131
|
+
// Recommendations are derived from platform-active vulns only.
|
|
125
132
|
const recommendations = [];
|
|
126
133
|
const byPackage = new Map();
|
|
127
|
-
for (const v of
|
|
134
|
+
for (const v of active) {
|
|
128
135
|
if (!byPackage.has(v.package))
|
|
129
136
|
byPackage.set(v.package, []);
|
|
130
137
|
byPackage.get(v.package).push(v);
|
|
131
138
|
}
|
|
132
139
|
for (const [pkg, pkgVulns] of byPackage) {
|
|
133
140
|
const highest = pkgVulns[0];
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
141
|
+
const breakdown = severityBreakdown(pkgVulns);
|
|
142
|
+
// Target the HIGHEST published fix so the upgrade resolves every fixable CVE,
|
|
143
|
+
// not just the first one. (The old code picked the first fix version, which
|
|
144
|
+
// could leave higher-version-gated CVEs unfixed while claiming to fix them.)
|
|
145
|
+
const target = maxSemver(pkgVulns.map((v) => v.fixedVersion).filter((v) => Boolean(v)));
|
|
146
|
+
const total = pkgVulns.length;
|
|
147
|
+
const fixable = pkgVulns.filter((v) => v.fixedVersion).length;
|
|
148
|
+
if (target) {
|
|
149
|
+
const scope = fixable === total
|
|
150
|
+
? `${total} CVE${total > 1 ? "s" : ""}`
|
|
151
|
+
: `${fixable} of ${total} CVEs (${total - fixable} with no published fix)`;
|
|
152
|
+
recommendations.push(`Upgrade ${pkg} ${highest.currentVersion} \u2192 ${target} \u2014 resolves ${scope} [${breakdown}]`);
|
|
137
153
|
}
|
|
138
154
|
else {
|
|
139
|
-
recommendations.push(`Review ${pkg} ${highest.currentVersion} \u2014 ${
|
|
155
|
+
recommendations.push(`Review ${pkg} ${highest.currentVersion} \u2014 ${total} known vulnerabilit${total > 1 ? "ies" : "y"} [${breakdown}], no fix version published`);
|
|
140
156
|
}
|
|
141
157
|
}
|
|
158
|
+
const host = hostTarget();
|
|
159
|
+
const activePackages = new Set(active.map((v) => v.package)).size;
|
|
142
160
|
return {
|
|
143
161
|
scan_time: result.scannedAt,
|
|
144
162
|
project: result.projectPath.split(/[/\\]/).pop() || result.projectPath,
|
|
163
|
+
host_platform: `${host.os}/${host.arch}`,
|
|
145
164
|
ecosystems_scanned: result.ecosystemsScanned,
|
|
146
165
|
total_dependencies_scanned: result.totalScanned,
|
|
147
|
-
total_vulnerable_packages:
|
|
166
|
+
total_vulnerable_packages: activePackages,
|
|
167
|
+
platform_inactive_packages: result.platformInactiveVulnerable,
|
|
148
168
|
by_severity: result.bySeverity,
|
|
149
|
-
vulnerabilities:
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
is_direct: v.isDirect ?? null,
|
|
155
|
-
dev_scope_known: v.devScopeKnown ?? false,
|
|
156
|
-
vuln_id: v.vulnId,
|
|
157
|
-
aliases: v.aliases,
|
|
158
|
-
severity: v.severity,
|
|
159
|
-
cvss_score: v.cvssScore,
|
|
160
|
-
summary: v.summary,
|
|
161
|
-
fixed_version: v.fixedVersion,
|
|
162
|
-
published: v.published,
|
|
163
|
-
references: v.references,
|
|
164
|
-
})),
|
|
169
|
+
vulnerabilities: active.map(formatVuln),
|
|
170
|
+
// Advisories suppressed from the main list because the affected crate is not
|
|
171
|
+
// built on this host (e.g. cfg(not(windows)) deps on Windows). Surfaced for
|
|
172
|
+
// cross-platform builds, clearly labeled with the gating target.
|
|
173
|
+
platform_inactive_vulnerabilities: inactive.map(formatVuln),
|
|
165
174
|
recommendations,
|
|
166
175
|
clean_dependencies: result.cleanCount,
|
|
167
176
|
scan_duration_ms: result.scanDurationMs,
|
|
@@ -170,7 +179,38 @@ function formatResult(result, severityFilter) {
|
|
|
170
179
|
cached: result.cached,
|
|
171
180
|
offline: result.offline,
|
|
172
181
|
privacy: "Only package names and versions were sent (public manifest data)",
|
|
182
|
+
relevance: "Advisories are filtered to the host platform; inactive-target advisories are listed separately.",
|
|
173
183
|
},
|
|
174
184
|
};
|
|
175
185
|
}
|
|
186
|
+
/** "1 critical, 3 high, 2 medium" — severity-ordered, omitting empty buckets. */
|
|
187
|
+
function severityBreakdown(vulns) {
|
|
188
|
+
const counts = {};
|
|
189
|
+
for (const v of vulns)
|
|
190
|
+
counts[v.severity] = (counts[v.severity] || 0) + 1;
|
|
191
|
+
return ["critical", "high", "medium", "low", "unknown"]
|
|
192
|
+
.filter((s) => counts[s])
|
|
193
|
+
.map((s) => `${counts[s]} ${s}`)
|
|
194
|
+
.join(", ");
|
|
195
|
+
}
|
|
196
|
+
function formatVuln(v) {
|
|
197
|
+
return {
|
|
198
|
+
package: v.package,
|
|
199
|
+
current_version: v.currentVersion,
|
|
200
|
+
ecosystem: v.ecosystem,
|
|
201
|
+
is_dev: v.isDev,
|
|
202
|
+
is_direct: v.isDirect ?? null,
|
|
203
|
+
dev_scope_known: v.devScopeKnown ?? false,
|
|
204
|
+
target: v.target,
|
|
205
|
+
platform_active: v.platformActive,
|
|
206
|
+
vuln_id: v.vulnId,
|
|
207
|
+
aliases: v.aliases,
|
|
208
|
+
severity: v.severity,
|
|
209
|
+
cvss_score: v.cvssScore,
|
|
210
|
+
summary: v.summary,
|
|
211
|
+
fixed_version: v.fixedVersion,
|
|
212
|
+
published: v.published,
|
|
213
|
+
references: v.references,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
176
216
|
//# sourceMappingURL=vulnerability-scan.js.map
|
|
@@ -50,6 +50,8 @@ interface WhatShouldIKnowResult {
|
|
|
50
50
|
reason: string;
|
|
51
51
|
};
|
|
52
52
|
summary: string;
|
|
53
|
+
/** How relevant_wisdom was retrieved: "hybrid" when an embedding provider is active. */
|
|
54
|
+
wisdom_recall_mode: "hybrid" | "ranked_lexical";
|
|
53
55
|
}
|
|
54
56
|
export declare const whatShouldIKnowTool: {
|
|
55
57
|
name: string;
|
|
@@ -72,6 +74,6 @@ export declare const whatShouldIKnowTool: {
|
|
|
72
74
|
required: string[];
|
|
73
75
|
};
|
|
74
76
|
};
|
|
75
|
-
export declare function executeWhatShouldIKnow(db: FourDADatabase, params: WhatShouldIKnowParams): WhatShouldIKnowResult
|
|
77
|
+
export declare function executeWhatShouldIKnow(db: FourDADatabase, params: WhatShouldIKnowParams): WhatShouldIKnowResult | Promise<WhatShouldIKnowResult>;
|
|
76
78
|
export {};
|
|
77
79
|
//# sourceMappingURL=what-should-i-know.d.ts.map
|