@4da/mcp-server 5.0.4 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +667 -0
- package/README.md +66 -35
- package/dist/build-staleness.d.ts +35 -0
- package/dist/build-staleness.js +95 -0
- package/dist/db.d.ts +53 -2
- package/dist/db.js +233 -37
- package/dist/doctor.js +27 -7
- package/dist/embeddings.js +7 -1
- package/dist/freshness-markers.d.ts +34 -0
- package/dist/freshness-markers.js +74 -0
- package/dist/gitignore.d.ts +37 -0
- package/dist/gitignore.js +130 -0
- package/dist/index.js +127 -44
- package/dist/live/bun-lockfile.d.ts +26 -0
- package/dist/live/bun-lockfile.js +123 -0
- package/dist/live/cargo-platform.d.ts +38 -0
- package/dist/live/cargo-platform.js +178 -0
- package/dist/live/changelog-classify.d.ts +41 -0
- package/dist/live/changelog-classify.js +207 -0
- package/dist/live/changelog.d.ts +64 -0
- package/dist/live/changelog.js +318 -0
- package/dist/live/crates-registry.js +3 -1
- package/dist/live/crates-versions.d.ts +47 -0
- package/dist/live/crates-versions.js +201 -0
- package/dist/live/dependency-set.d.ts +13 -0
- package/dist/live/dependency-set.js +95 -0
- package/dist/live/dev-scope.d.ts +32 -0
- package/dist/live/dev-scope.js +102 -0
- package/dist/live/file-signature.d.ts +18 -0
- package/dist/live/file-signature.js +43 -0
- package/dist/live/go-registry.js +3 -1
- package/dist/live/hn-fetcher.js +9 -3
- package/dist/live/index.d.ts +105 -2
- package/dist/live/index.js +266 -139
- package/dist/live/install-state.d.ts +43 -0
- package/dist/live/install-state.js +150 -0
- package/dist/live/js-lockfile-readers.d.ts +46 -0
- package/dist/live/js-lockfile-readers.js +181 -0
- package/dist/live/lockfile-parsers-pygo.d.ts +60 -0
- package/dist/live/lockfile-parsers-pygo.js +274 -0
- package/dist/live/lockfile-parsers.d.ts +69 -0
- package/dist/live/lockfile-parsers.js +311 -0
- package/dist/live/lockfile-types.d.ts +34 -0
- package/dist/live/lockfile-types.js +42 -0
- package/dist/live/maintenance.d.ts +33 -0
- package/dist/live/maintenance.js +37 -0
- package/dist/live/npm-packument.d.ts +47 -0
- package/dist/live/npm-packument.js +150 -0
- package/dist/live/npm-registry.js +67 -53
- package/dist/live/osv-scanner.d.ts +12 -1
- package/dist/live/osv-scanner.js +50 -15
- package/dist/live/package-archive.d.ts +49 -0
- package/dist/live/package-archive.js +186 -0
- package/dist/live/pypi-registry.js +3 -1
- package/dist/live/registry-health.d.ts +16 -0
- package/dist/live/registry-health.js +74 -0
- package/dist/live/release-metadata.d.ts +67 -0
- package/dist/live/release-metadata.js +16 -0
- package/dist/live/resolution.d.ts +43 -0
- package/dist/live/resolution.js +62 -0
- package/dist/live/semver-precedence.d.ts +30 -0
- package/dist/live/semver-precedence.js +83 -0
- package/dist/live/severity-scope.d.ts +57 -0
- package/dist/live/severity-scope.js +89 -0
- package/dist/live/types.d.ts +83 -0
- package/dist/live/upgrade-sources.d.ts +71 -0
- package/dist/live/upgrade-sources.js +198 -0
- package/dist/live/version-compare.d.ts +39 -0
- package/dist/live/version-compare.js +130 -0
- package/dist/live/version-resolver.d.ts +14 -1
- package/dist/live/version-resolver.js +93 -303
- package/dist/native-bindings.d.ts +19 -0
- package/dist/native-bindings.js +39 -0
- package/dist/project-manifests.d.ts +40 -0
- package/dist/project-manifests.js +193 -0
- package/dist/project-scanner.js +38 -48
- package/dist/project-tree.d.ts +41 -0
- package/dist/project-tree.js +110 -0
- package/dist/prompts.d.ts +28 -0
- package/dist/prompts.js +54 -0
- package/dist/schema-registry.d.ts +41 -29
- package/dist/schema-registry.js +98 -105
- package/dist/server-instructions.d.ts +11 -0
- package/dist/server-instructions.js +36 -0
- package/dist/tool-args.d.ts +28 -0
- package/dist/tool-args.js +93 -0
- package/dist/tool-dispatch.d.ts +1 -1
- package/dist/tool-dispatch.js +42 -8
- package/dist/tools/app-plan.d.ts +92 -0
- package/dist/tools/app-plan.js +155 -0
- package/dist/tools/briefing-stored-advisories.d.ts +20 -0
- package/dist/tools/briefing-stored-advisories.js +58 -0
- package/dist/tools/briefing-task-scope.d.ts +73 -0
- package/dist/tools/briefing-task-scope.js +197 -0
- package/dist/tools/briefing-wisdom.d.ts +31 -0
- package/dist/tools/briefing-wisdom.js +148 -0
- package/dist/tools/decision-enforcement.d.ts +0 -5
- package/dist/tools/decision-enforcement.js +0 -4
- package/dist/tools/decision-memory.js +18 -66
- package/dist/tools/dependency-check-signals.d.ts +74 -0
- package/dist/tools/dependency-check-signals.js +264 -0
- package/dist/tools/dependency-check.d.ts +98 -0
- package/dist/tools/dependency-check.js +295 -0
- package/dist/tools/dependency-health.js +74 -21
- package/dist/tools/developer-dna.d.ts +3 -0
- package/dist/tools/developer-dna.js +16 -15
- package/dist/tools/ecosystem-pulse.d.ts +6 -2
- package/dist/tools/ecosystem-pulse.js +53 -14
- package/dist/tools/get-actionable-signals.d.ts +26 -4
- package/dist/tools/get-actionable-signals.js +214 -251
- package/dist/tools/get-context.d.ts +2 -0
- package/dist/tools/get-context.js +9 -1
- package/dist/tools/get-relevant-content.d.ts +3 -1
- package/dist/tools/get-relevant-content.js +34 -3
- package/dist/tools/index.d.ts +4 -2
- package/dist/tools/index.js +4 -2
- package/dist/tools/install-drift-notes.d.ts +51 -0
- package/dist/tools/install-drift-notes.js +172 -0
- package/dist/tools/knowledge-gap-citations.d.ts +55 -0
- package/dist/tools/knowledge-gap-citations.js +132 -0
- package/dist/tools/knowledge-gap-exposure.d.ts +72 -0
- package/dist/tools/knowledge-gap-exposure.js +160 -0
- package/dist/tools/knowledge-gap-grading.d.ts +91 -0
- package/dist/tools/knowledge-gap-grading.js +157 -0
- package/dist/tools/knowledge-gap-installs.d.ts +65 -0
- package/dist/tools/knowledge-gap-installs.js +134 -0
- package/dist/tools/knowledge-gap-ranges.d.ts +51 -0
- package/dist/tools/knowledge-gap-ranges.js +146 -0
- package/dist/tools/knowledge-gap-scope.d.ts +63 -0
- package/dist/tools/knowledge-gap-scope.js +118 -0
- package/dist/tools/knowledge-gaps.d.ts +12 -3
- package/dist/tools/knowledge-gaps.js +172 -121
- package/dist/tools/package-presence.d.ts +24 -0
- package/dist/tools/package-presence.js +35 -0
- package/dist/tools/record-feedback.d.ts +0 -1
- package/dist/tools/record-feedback.js +7 -8
- package/dist/tools/signal-classifier.d.ts +25 -0
- package/dist/tools/signal-classifier.js +40 -0
- package/dist/tools/upgrade-impact-callsites.d.ts +52 -0
- package/dist/tools/upgrade-impact-callsites.js +226 -0
- package/dist/tools/upgrade-impact-literals.d.ts +45 -0
- package/dist/tools/upgrade-impact-literals.js +118 -0
- package/dist/tools/upgrade-impact-report.d.ts +80 -0
- package/dist/tools/upgrade-impact-report.js +178 -0
- package/dist/tools/upgrade-impact.d.ts +84 -0
- package/dist/tools/upgrade-impact.js +305 -0
- package/dist/tools/upgrade-planner.d.ts +41 -8
- package/dist/tools/upgrade-planner.js +201 -53
- package/dist/tools/vulnerability-scan-concise.d.ts +72 -0
- package/dist/tools/vulnerability-scan-concise.js +97 -0
- package/dist/tools/vulnerability-scan-format.d.ts +137 -0
- package/dist/tools/vulnerability-scan-format.js +276 -0
- package/dist/tools/vulnerability-scan.d.ts +338 -18
- package/dist/tools/vulnerability-scan.js +84 -125
- package/dist/tools/what-should-i-know.d.ts +86 -19
- package/dist/tools/what-should-i-know.js +277 -283
- package/dist/types.d.ts +4 -1
- package/package.json +27 -30
- package/dist/schemas/agent-memory.json +0 -49
- package/dist/schemas/decision-enforcement.json +0 -22
- package/dist/schemas/decision-memory.json +0 -54
- package/dist/schemas/dependency-health.json +0 -27
- package/dist/schemas/developer-dna.json +0 -219
- package/dist/schemas/ecosystem-pulse.json +0 -17
- package/dist/schemas/get-actionable-signals.json +0 -29
- package/dist/schemas/get-context.json +0 -17
- package/dist/schemas/get-relevant-content.json +0 -26
- package/dist/schemas/knowledge-gaps.json +0 -14
- package/dist/schemas/record-feedback.json +0 -24
- package/dist/schemas/upgrade-planner.json +0 -22
- package/dist/schemas/vulnerability-scan.json +0 -26
- package/dist/schemas/what-should-i-know.json +0 -19
|
@@ -5,18 +5,28 @@
|
|
|
5
5
|
* Scans project dependencies for known vulnerabilities using OSV.dev.
|
|
6
6
|
* Zero configuration — auto-detects stack from manifest files.
|
|
7
7
|
* Returns CVEs, severity, fix versions across npm, Rust, Python, Go.
|
|
8
|
+
* Output shaping lives in vulnerability-scan-format.ts.
|
|
8
9
|
*/
|
|
9
10
|
import * as fs from "node:fs";
|
|
10
11
|
import * as path from "node:path";
|
|
11
12
|
import { LiveIntelligence } from "../live/index.js";
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
13
|
+
import { scanProjectTree, treeResolutionGroups } from "../project-tree.js";
|
|
14
|
+
import { isMaintenanceNotice } from "../live/maintenance.js";
|
|
15
|
+
import { namesPackage } from "../live/version-compare.js";
|
|
16
|
+
import { emptyAnswerNote, packagePresence } from "./package-presence.js";
|
|
17
|
+
import { conciseScanResult } from "./vulnerability-scan-concise.js";
|
|
18
|
+
import { formatScanResult } from "./vulnerability-scan-format.js";
|
|
19
|
+
// The predicate lives in live/maintenance.ts so every scan consumer shares
|
|
20
|
+
// one definition; re-exported here, with the formatters, for existing importers.
|
|
21
|
+
export { isMaintenanceNotice };
|
|
22
|
+
export { collapseAliases, platformFilterNote, relativeDir } from "./vulnerability-scan-format.js";
|
|
15
23
|
export const vulnerabilityScanTool = {
|
|
16
24
|
name: "vulnerability_scan",
|
|
17
25
|
description: "Scan project dependencies for known vulnerabilities (CVEs) using OSV.dev. " +
|
|
18
26
|
"Returns severity, fix versions, and upgrade recommendations for npm, Rust, Python, and Go. " +
|
|
19
|
-
"Zero config — scans the complete resolvable lockfile package set, including transitives. " +
|
|
27
|
+
"Zero config — scans the complete resolvable lockfile package set, including transitives, and for npm also the version node_modules actually holds when it differs from the lockfile (install_drift). " +
|
|
28
|
+
"`severity` is the scope-adjusted grade the 4DA app presents; `advisory_severity` keeps the advisory's own tier. " +
|
|
29
|
+
"`_meta.cached` (= `_meta.osv_cached`) refers to the OSV advisory lookup, not to dependency resolution: versions are re-read whenever a lockfile changes, and `_meta.resolution` says from which files and when. " +
|
|
20
30
|
"Privacy: only sends package names and versions (public manifest data).",
|
|
21
31
|
inputSchema: {
|
|
22
32
|
type: "object",
|
|
@@ -28,7 +38,7 @@ export const vulnerabilityScanTool = {
|
|
|
28
38
|
severity_filter: {
|
|
29
39
|
type: "string",
|
|
30
40
|
enum: ["critical", "high", "medium", "low"],
|
|
31
|
-
description: "Only show vulnerabilities at or above this
|
|
41
|
+
description: "Only show vulnerabilities whose presented severity is at or above this level.",
|
|
32
42
|
},
|
|
33
43
|
include_dev: {
|
|
34
44
|
type: "boolean",
|
|
@@ -36,18 +46,20 @@ export const vulnerabilityScanTool = {
|
|
|
36
46
|
},
|
|
37
47
|
force_refresh: {
|
|
38
48
|
type: "boolean",
|
|
39
|
-
description: "Ignore cache and fetch fresh data from OSV.dev. Default: false.",
|
|
49
|
+
description: "Ignore the OSV advisory cache and fetch fresh advisory data from OSV.dev (dependency versions are re-read on every lockfile change regardless). Default: false.",
|
|
50
|
+
},
|
|
51
|
+
package: {
|
|
52
|
+
type: "string",
|
|
53
|
+
description: "Only this package (any ecosystem, every installed copy, transitives included), with a `package_note` saying what the lockfiles hold for it. Use it to ask about one dependency instead of reading the whole report.",
|
|
54
|
+
},
|
|
55
|
+
response_format: {
|
|
56
|
+
type: "string",
|
|
57
|
+
enum: ["concise", "detailed"],
|
|
58
|
+
description: "concise (default): one row per vulnerable package version (worst severity, the version that fixes all its advisories, advisory count and first ids, where it is pinned), the 40 most severe and 25 recommendations, with counts of anything left out. detailed: one row per advisory with references, plus platform-inactive advisories, maintenance notices, install drift and resolution provenance.",
|
|
40
59
|
},
|
|
41
60
|
},
|
|
42
61
|
},
|
|
43
62
|
};
|
|
44
|
-
const SEVERITY_ORDER = {
|
|
45
|
-
critical: 4,
|
|
46
|
-
high: 3,
|
|
47
|
-
medium: 2,
|
|
48
|
-
low: 1,
|
|
49
|
-
unknown: 0,
|
|
50
|
-
};
|
|
51
63
|
export async function executeVulnerabilityScan(db, params, liveIntelligence) {
|
|
52
64
|
if (!liveIntelligence || !liveIntelligence.isEnabled()) {
|
|
53
65
|
return {
|
|
@@ -57,12 +69,27 @@ export async function executeVulnerabilityScan(db, params, liveIntelligence) {
|
|
|
57
69
|
hint: "4DA queries OSV.dev with your dependency names and versions (public manifest data). No personal data is transmitted.",
|
|
58
70
|
};
|
|
59
71
|
}
|
|
60
|
-
|
|
72
|
+
// The project the server was started for (FOURDA_PROJECT_DIR, else cwd) —
|
|
73
|
+
// not process.cwd(), which under Claude Desktop is the app's own directory.
|
|
74
|
+
const defaultRoot = liveIntelligence.getProjectRoot() ?? process.cwd();
|
|
75
|
+
const projectPath = params.project_path ? path.resolve(params.project_path) : defaultRoot;
|
|
76
|
+
if (params.project_path && !isDirectory(projectPath)) {
|
|
77
|
+
return {
|
|
78
|
+
error: `project_path "${params.project_path}" is not a directory on this machine. Pass the absolute path of a project root, or omit it to scan ${defaultRoot}.`,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
61
81
|
let scanner = liveIntelligence;
|
|
62
|
-
|
|
63
|
-
|
|
82
|
+
let reResolvedThisCall = false;
|
|
83
|
+
if (normalizedPath(projectPath) !== normalizedPath(defaultRoot)) {
|
|
84
|
+
// Every independently-locked project under the requested root, as at startup.
|
|
64
85
|
scanner = new LiveIntelligence(db.getRawDb());
|
|
65
|
-
scanner.
|
|
86
|
+
scanner.initFromProjectTree(projectPath, treeResolutionGroups(scanProjectTree(projectPath)));
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
// A long-lived server must not answer from the versions it read at
|
|
90
|
+
// startup (2026-09-10: one reported hono 4.13.3 after the lockfile said
|
|
91
|
+
// 4.13.5). Stat calls only; re-resolves when a lockfile changed.
|
|
92
|
+
reResolvedThisCall = liveIntelligence.refreshIfLockfilesChanged();
|
|
66
93
|
}
|
|
67
94
|
if (!scanner.isInitialized()) {
|
|
68
95
|
return {
|
|
@@ -71,27 +98,58 @@ export async function executeVulnerabilityScan(db, params, liveIntelligence) {
|
|
|
71
98
|
message: "No project dependencies detected. Ensure manifest files (package.json, Cargo.toml, etc.) exist in your project.",
|
|
72
99
|
};
|
|
73
100
|
}
|
|
74
|
-
const
|
|
75
|
-
|
|
101
|
+
const includeDev = params.include_dev || false;
|
|
102
|
+
const scanned = await scanner.scanVulnerabilities(projectPath, {
|
|
103
|
+
includeDev,
|
|
76
104
|
forceRefresh: params.force_refresh || false,
|
|
77
105
|
});
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
106
|
+
// One package's findings without the whole report. In the 2026-10-03 agent
|
|
107
|
+
// eval, asking about one transitive meant `response_format: "detailed"` on
|
|
108
|
+
// nodegoat: 225k characters, refused by the host and grepped from a file.
|
|
109
|
+
const only = params.package?.trim();
|
|
110
|
+
const result = only
|
|
111
|
+
? { ...scanned, vulnerabilities: scanned.vulnerabilities.filter((v) => namesPackage(only, v.package, v.ecosystem)) }
|
|
112
|
+
: scanned;
|
|
113
|
+
const drift = scanner.getInstallDrift();
|
|
114
|
+
const full = formatScanResult(result, params.severity_filter, {
|
|
115
|
+
resolution: scanner.getResolutionProvenance(),
|
|
116
|
+
reResolvedThisCall,
|
|
117
|
+
drift: only ? drift.filter((r) => namesPackage(only, r.package, "npm")) : drift,
|
|
118
|
+
includeDev,
|
|
119
|
+
});
|
|
120
|
+
const formatted = params.response_format === "detailed" ? full : conciseScanResult(full);
|
|
121
|
+
const withNote = only ? { ...formatted, package_note: packageNote(only, scanner, result, includeDev) } : formatted;
|
|
122
|
+
if (scanned.totalScanned === 0 && !scanned.offline) {
|
|
123
|
+
return { ...withNote, note: diagnoseMissingVersions(projectPath) };
|
|
124
|
+
}
|
|
125
|
+
return withNote;
|
|
126
|
+
}
|
|
127
|
+
/** What the lockfiles hold for `query`, so "no findings" is never confused with "not a dependency". */
|
|
128
|
+
function packageNote(query, scanner, result, includeDev) {
|
|
129
|
+
if (result.vulnerabilities.length > 0) {
|
|
130
|
+
const p = packagePresence(query, scanner);
|
|
131
|
+
return `${p.name ?? query} ${p.versions.join(", ")} (${p.scope ?? "in the scan"}): findings below.`;
|
|
84
132
|
}
|
|
85
|
-
|
|
133
|
+
const checked = result.offline ? "OSV was unreachable, so its advisories are unverified" : "no known advisories";
|
|
134
|
+
return `${emptyAnswerNote(query, scanner, includeDev, checked)}.`;
|
|
86
135
|
}
|
|
87
136
|
function normalizedPath(value) {
|
|
88
137
|
return path.resolve(value).replace(/\\/g, "/").toLowerCase().replace(/\/+$/, "");
|
|
89
138
|
}
|
|
139
|
+
function isDirectory(p) {
|
|
140
|
+
try {
|
|
141
|
+
return fs.statSync(p).isDirectory();
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
90
147
|
function diagnoseMissingVersions(projectPath) {
|
|
91
148
|
const lockFiles = {
|
|
92
149
|
"package-lock.json": "npm install",
|
|
93
150
|
"pnpm-lock.yaml": "pnpm install",
|
|
94
151
|
"yarn.lock": "yarn install",
|
|
152
|
+
"bun.lock": "bun install",
|
|
95
153
|
"Cargo.lock": "cargo build",
|
|
96
154
|
"poetry.lock": "poetry install",
|
|
97
155
|
"Pipfile.lock": "pipenv install",
|
|
@@ -114,102 +172,3 @@ function diagnoseMissingVersions(projectPath) {
|
|
|
114
172
|
}
|
|
115
173
|
return "Lock file found but no dependency versions could be resolved. The lock file format may be unsupported or corrupted.";
|
|
116
174
|
}
|
|
117
|
-
function formatResult(result, severityFilter) {
|
|
118
|
-
let vulns = result.vulnerabilities;
|
|
119
|
-
// Apply severity filter
|
|
120
|
-
if (severityFilter) {
|
|
121
|
-
const minOrder = SEVERITY_ORDER[severityFilter] || 0;
|
|
122
|
-
vulns = vulns.filter((v) => (SEVERITY_ORDER[v.severity] || 0) >= minOrder);
|
|
123
|
-
}
|
|
124
|
-
// Sort: critical first, then high, etc.
|
|
125
|
-
vulns.sort((a, b) => (SEVERITY_ORDER[b.severity] || 0) - (SEVERITY_ORDER[a.severity] || 0));
|
|
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.
|
|
132
|
-
const recommendations = [];
|
|
133
|
-
const byPackage = new Map();
|
|
134
|
-
for (const v of active) {
|
|
135
|
-
if (!byPackage.has(v.package))
|
|
136
|
-
byPackage.set(v.package, []);
|
|
137
|
-
byPackage.get(v.package).push(v);
|
|
138
|
-
}
|
|
139
|
-
for (const [pkg, pkgVulns] of byPackage) {
|
|
140
|
-
const highest = pkgVulns[0];
|
|
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}]`);
|
|
153
|
-
}
|
|
154
|
-
else {
|
|
155
|
-
recommendations.push(`Review ${pkg} ${highest.currentVersion} \u2014 ${total} known vulnerabilit${total > 1 ? "ies" : "y"} [${breakdown}], no fix version published`);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
const host = hostTarget();
|
|
159
|
-
const activePackages = new Set(active.map((v) => v.package)).size;
|
|
160
|
-
return {
|
|
161
|
-
scan_time: result.scannedAt,
|
|
162
|
-
project: result.projectPath.split(/[/\\]/).pop() || result.projectPath,
|
|
163
|
-
host_platform: `${host.os}/${host.arch}`,
|
|
164
|
-
ecosystems_scanned: result.ecosystemsScanned,
|
|
165
|
-
total_dependencies_scanned: result.totalScanned,
|
|
166
|
-
total_vulnerable_packages: activePackages,
|
|
167
|
-
platform_inactive_packages: result.platformInactiveVulnerable,
|
|
168
|
-
by_severity: result.bySeverity,
|
|
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),
|
|
174
|
-
recommendations,
|
|
175
|
-
clean_dependencies: result.cleanCount,
|
|
176
|
-
scan_duration_ms: result.scanDurationMs,
|
|
177
|
-
_meta: {
|
|
178
|
-
source: "osv.dev",
|
|
179
|
-
cached: result.cached,
|
|
180
|
-
offline: result.offline,
|
|
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.",
|
|
183
|
-
},
|
|
184
|
-
};
|
|
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
|
-
}
|
|
@@ -1,14 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* what_should_i_know tool
|
|
3
3
|
*
|
|
4
|
-
* Pre-task
|
|
5
|
-
* -
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Pre-task briefing for AI coding agents, built from the task outward:
|
|
5
|
+
* - the dependencies the task names, with their installed versions, the
|
|
6
|
+
* version-confirmed vulnerabilities on those versions, releases since, and
|
|
7
|
+
* how far the requested upgrade jumps;
|
|
8
|
+
* - this project's other version-confirmed findings, counted separately;
|
|
9
|
+
* - recorded decisions and decision windows relevant to the task;
|
|
10
|
+
* - a delegation verdict that ONLY version-confirmed evidence about the task
|
|
11
|
+
* (or install drift actually running) can raise.
|
|
8
12
|
*
|
|
9
|
-
*
|
|
13
|
+
* Measured 2026-10-01 (live), the old briefing for "bump fastembed 5 -> 7":
|
|
14
|
+
* eleven advisories, none about fastembed — an arXiv dataset paper, three
|
|
15
|
+
* OpenAI-company news items matched to the `openai` npm package of another
|
|
16
|
+
* project, Angular and russh CVEs no project uses — and `human_only`, because
|
|
17
|
+
* any high "security" keyword match anywhere forced it. Feed classifications
|
|
18
|
+
* were ~5-15% precise as security alerts (154 in 14 days; the relevance judge
|
|
19
|
+
* accepted 10). They no longer decide anything here.
|
|
10
20
|
*/
|
|
11
21
|
import type { FourDADatabase } from "../db.js";
|
|
22
|
+
import type { LiveIntelligence } from "../live/index.js";
|
|
23
|
+
import { advisoriesFor, judgedReadingFor, releasesFor } from "./briefing-task-scope.js";
|
|
24
|
+
import { type WisdomEntry, type WisdomRecallMode } from "./briefing-wisdom.js";
|
|
12
25
|
export interface WhatShouldIKnowParams {
|
|
13
26
|
task: string;
|
|
14
27
|
files?: string[];
|
|
@@ -19,6 +32,10 @@ interface Advisory {
|
|
|
19
32
|
priority: string;
|
|
20
33
|
action: string;
|
|
21
34
|
url: string | null;
|
|
35
|
+
/** `task`: about a dependency this task names. `project`: elsewhere in this project. */
|
|
36
|
+
scope: "task" | "project";
|
|
37
|
+
/** True when matched against the installed version (OSV scan); false for an advisory feed row naming the package. */
|
|
38
|
+
version_confirmed: boolean;
|
|
22
39
|
}
|
|
23
40
|
interface DecisionWindow {
|
|
24
41
|
id: number;
|
|
@@ -26,33 +43,83 @@ interface DecisionWindow {
|
|
|
26
43
|
description: string | null;
|
|
27
44
|
urgency: number;
|
|
28
45
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
46
|
+
/** What the briefing knows about one dependency the task names. */
|
|
47
|
+
interface TaskDependency {
|
|
48
|
+
package: string;
|
|
49
|
+
ecosystem: string;
|
|
50
|
+
installed: string[];
|
|
51
|
+
direct: boolean;
|
|
52
|
+
dev_only: boolean;
|
|
53
|
+
requested: {
|
|
54
|
+
from: string | null;
|
|
55
|
+
to: string | null;
|
|
56
|
+
};
|
|
57
|
+
/** Major versions the requested upgrade crosses, when both ends are known. */
|
|
58
|
+
majors_crossed: number | null;
|
|
59
|
+
vulnerabilities: ReturnType<typeof advisoriesFor>;
|
|
60
|
+
/** Registry releases newer than the installed version that the 4DA feed recorded (desktop app only). */
|
|
61
|
+
newer_releases: ReturnType<typeof releasesFor>;
|
|
62
|
+
next_step: string;
|
|
33
63
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
64
|
+
/**
|
|
65
|
+
* "unknown" is the verdict when the vulnerability scan could not be consulted:
|
|
66
|
+
* the briefing has not reviewed the task, which is a different claim from
|
|
67
|
+
* "reviewed and found nothing". "safe_to_delegate" is only ever emitted over a
|
|
68
|
+
* ready scan.
|
|
69
|
+
*/
|
|
70
|
+
export type DelegationLevel = "safe_to_delegate" | "review_needed" | "human_only" | "unknown";
|
|
71
|
+
/**
|
|
72
|
+
* Whether the live vulnerability scan backed this briefing.
|
|
73
|
+
* - ready: a completed, online scan was consulted
|
|
74
|
+
* - unavailable: live intelligence is on but the scan is still running past
|
|
75
|
+
* the wait budget, finished offline, or covered no resolvable
|
|
76
|
+
* dependency versions
|
|
77
|
+
* - disabled: no live intelligence (FOURDA_OFFLINE, or not initialised)
|
|
78
|
+
*/
|
|
79
|
+
export type ScanStatus = "ready" | "unavailable" | "disabled";
|
|
80
|
+
/** What backed the briefing's vulnerability claims, and how current it is. */
|
|
81
|
+
export interface BriefingScan {
|
|
82
|
+
status: ScanStatus;
|
|
83
|
+
/** When the scan the briefing read ran, or null when none backed it. */
|
|
84
|
+
scanned_at: string | null;
|
|
85
|
+
/** When the dependency versions it covers were read from the lockfiles. */
|
|
86
|
+
resolved_at: string | null;
|
|
87
|
+
/** True when this call noticed a changed lockfile and re-resolved first. */
|
|
88
|
+
re_resolved_this_call: boolean;
|
|
39
89
|
}
|
|
40
|
-
|
|
41
|
-
interface WhatShouldIKnowResult {
|
|
90
|
+
export interface WhatShouldIKnowResult {
|
|
42
91
|
task: string;
|
|
43
92
|
files: string[];
|
|
93
|
+
task_dependencies: TaskDependency[];
|
|
44
94
|
advisories: Advisory[];
|
|
45
95
|
decision_windows: DecisionWindow[];
|
|
46
96
|
relevant_wisdom: WisdomEntry[];
|
|
47
|
-
|
|
97
|
+
decision_conflicts: Array<{
|
|
98
|
+
technology: string;
|
|
99
|
+
decision_id: number;
|
|
100
|
+
reason: string;
|
|
101
|
+
}>;
|
|
102
|
+
related_reading: ReturnType<typeof judgedReadingFor>;
|
|
48
103
|
delegation_assessment: {
|
|
49
104
|
level: DelegationLevel;
|
|
50
105
|
reason: string;
|
|
51
106
|
};
|
|
107
|
+
/** Kept for existing callers; `scan.status` carries the same value. */
|
|
108
|
+
scan_status: ScanStatus;
|
|
109
|
+
scan: BriefingScan;
|
|
52
110
|
summary: string;
|
|
53
111
|
/** How relevant_wisdom was retrieved: "hybrid" when an embedding provider is active. */
|
|
54
|
-
wisdom_recall_mode:
|
|
112
|
+
wisdom_recall_mode: WisdomRecallMode;
|
|
113
|
+
_meta: {
|
|
114
|
+
untrusted_text: string;
|
|
115
|
+
};
|
|
55
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* The slice of the live layer the briefing reads; a stub suffices in tests.
|
|
119
|
+
* Re-resolution, provenance and the dependency lists are optional so a
|
|
120
|
+
* minimal stub still works (no dependencies means no task packages).
|
|
121
|
+
*/
|
|
122
|
+
export type BriefingLiveIntel = Pick<LiveIntelligence, "ensureVulnerabilities" | "isEnabled" | "getProjectRoot"> & Partial<Pick<LiveIntelligence, "refreshIfLockfilesChanged" | "getResolutionProvenance" | "getResolvedDeps" | "getAuditDeps" | "getVulnerabilities" | "getHeadlines">>;
|
|
56
123
|
export declare const whatShouldIKnowTool: {
|
|
57
124
|
name: string;
|
|
58
125
|
description: string;
|
|
@@ -74,5 +141,5 @@ export declare const whatShouldIKnowTool: {
|
|
|
74
141
|
required: string[];
|
|
75
142
|
};
|
|
76
143
|
};
|
|
77
|
-
export declare function executeWhatShouldIKnow(db: FourDADatabase, params: WhatShouldIKnowParams
|
|
144
|
+
export declare function executeWhatShouldIKnow(db: FourDADatabase, params: WhatShouldIKnowParams, liveIntel?: BriefingLiveIntel | null): Promise<WhatShouldIKnowResult>;
|
|
78
145
|
export {};
|