@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.
Files changed (172) hide show
  1. package/CHANGELOG.md +667 -0
  2. package/README.md +66 -35
  3. package/dist/build-staleness.d.ts +35 -0
  4. package/dist/build-staleness.js +95 -0
  5. package/dist/db.d.ts +53 -2
  6. package/dist/db.js +233 -37
  7. package/dist/doctor.js +27 -7
  8. package/dist/embeddings.js +7 -1
  9. package/dist/freshness-markers.d.ts +34 -0
  10. package/dist/freshness-markers.js +74 -0
  11. package/dist/gitignore.d.ts +37 -0
  12. package/dist/gitignore.js +130 -0
  13. package/dist/index.js +127 -44
  14. package/dist/live/bun-lockfile.d.ts +26 -0
  15. package/dist/live/bun-lockfile.js +123 -0
  16. package/dist/live/cargo-platform.d.ts +38 -0
  17. package/dist/live/cargo-platform.js +178 -0
  18. package/dist/live/changelog-classify.d.ts +41 -0
  19. package/dist/live/changelog-classify.js +207 -0
  20. package/dist/live/changelog.d.ts +64 -0
  21. package/dist/live/changelog.js +318 -0
  22. package/dist/live/crates-registry.js +3 -1
  23. package/dist/live/crates-versions.d.ts +47 -0
  24. package/dist/live/crates-versions.js +201 -0
  25. package/dist/live/dependency-set.d.ts +13 -0
  26. package/dist/live/dependency-set.js +95 -0
  27. package/dist/live/dev-scope.d.ts +32 -0
  28. package/dist/live/dev-scope.js +102 -0
  29. package/dist/live/file-signature.d.ts +18 -0
  30. package/dist/live/file-signature.js +43 -0
  31. package/dist/live/go-registry.js +3 -1
  32. package/dist/live/hn-fetcher.js +9 -3
  33. package/dist/live/index.d.ts +105 -2
  34. package/dist/live/index.js +266 -139
  35. package/dist/live/install-state.d.ts +43 -0
  36. package/dist/live/install-state.js +150 -0
  37. package/dist/live/js-lockfile-readers.d.ts +46 -0
  38. package/dist/live/js-lockfile-readers.js +181 -0
  39. package/dist/live/lockfile-parsers-pygo.d.ts +60 -0
  40. package/dist/live/lockfile-parsers-pygo.js +274 -0
  41. package/dist/live/lockfile-parsers.d.ts +69 -0
  42. package/dist/live/lockfile-parsers.js +311 -0
  43. package/dist/live/lockfile-types.d.ts +34 -0
  44. package/dist/live/lockfile-types.js +42 -0
  45. package/dist/live/maintenance.d.ts +33 -0
  46. package/dist/live/maintenance.js +37 -0
  47. package/dist/live/npm-packument.d.ts +47 -0
  48. package/dist/live/npm-packument.js +150 -0
  49. package/dist/live/npm-registry.js +67 -53
  50. package/dist/live/osv-scanner.d.ts +12 -1
  51. package/dist/live/osv-scanner.js +50 -15
  52. package/dist/live/package-archive.d.ts +49 -0
  53. package/dist/live/package-archive.js +186 -0
  54. package/dist/live/pypi-registry.js +3 -1
  55. package/dist/live/registry-health.d.ts +16 -0
  56. package/dist/live/registry-health.js +74 -0
  57. package/dist/live/release-metadata.d.ts +67 -0
  58. package/dist/live/release-metadata.js +16 -0
  59. package/dist/live/resolution.d.ts +43 -0
  60. package/dist/live/resolution.js +62 -0
  61. package/dist/live/semver-precedence.d.ts +30 -0
  62. package/dist/live/semver-precedence.js +83 -0
  63. package/dist/live/severity-scope.d.ts +57 -0
  64. package/dist/live/severity-scope.js +89 -0
  65. package/dist/live/types.d.ts +83 -0
  66. package/dist/live/upgrade-sources.d.ts +71 -0
  67. package/dist/live/upgrade-sources.js +198 -0
  68. package/dist/live/version-compare.d.ts +39 -0
  69. package/dist/live/version-compare.js +130 -0
  70. package/dist/live/version-resolver.d.ts +14 -1
  71. package/dist/live/version-resolver.js +93 -303
  72. package/dist/native-bindings.d.ts +19 -0
  73. package/dist/native-bindings.js +39 -0
  74. package/dist/project-manifests.d.ts +40 -0
  75. package/dist/project-manifests.js +193 -0
  76. package/dist/project-scanner.js +38 -48
  77. package/dist/project-tree.d.ts +41 -0
  78. package/dist/project-tree.js +110 -0
  79. package/dist/prompts.d.ts +28 -0
  80. package/dist/prompts.js +54 -0
  81. package/dist/schema-registry.d.ts +41 -29
  82. package/dist/schema-registry.js +98 -105
  83. package/dist/server-instructions.d.ts +11 -0
  84. package/dist/server-instructions.js +36 -0
  85. package/dist/tool-args.d.ts +28 -0
  86. package/dist/tool-args.js +93 -0
  87. package/dist/tool-dispatch.d.ts +1 -1
  88. package/dist/tool-dispatch.js +42 -8
  89. package/dist/tools/app-plan.d.ts +92 -0
  90. package/dist/tools/app-plan.js +155 -0
  91. package/dist/tools/briefing-stored-advisories.d.ts +20 -0
  92. package/dist/tools/briefing-stored-advisories.js +58 -0
  93. package/dist/tools/briefing-task-scope.d.ts +73 -0
  94. package/dist/tools/briefing-task-scope.js +197 -0
  95. package/dist/tools/briefing-wisdom.d.ts +31 -0
  96. package/dist/tools/briefing-wisdom.js +148 -0
  97. package/dist/tools/decision-enforcement.d.ts +0 -5
  98. package/dist/tools/decision-enforcement.js +0 -4
  99. package/dist/tools/decision-memory.js +18 -66
  100. package/dist/tools/dependency-check-signals.d.ts +74 -0
  101. package/dist/tools/dependency-check-signals.js +264 -0
  102. package/dist/tools/dependency-check.d.ts +98 -0
  103. package/dist/tools/dependency-check.js +295 -0
  104. package/dist/tools/dependency-health.js +74 -21
  105. package/dist/tools/developer-dna.d.ts +3 -0
  106. package/dist/tools/developer-dna.js +16 -15
  107. package/dist/tools/ecosystem-pulse.d.ts +6 -2
  108. package/dist/tools/ecosystem-pulse.js +53 -14
  109. package/dist/tools/get-actionable-signals.d.ts +26 -4
  110. package/dist/tools/get-actionable-signals.js +214 -251
  111. package/dist/tools/get-context.d.ts +2 -0
  112. package/dist/tools/get-context.js +9 -1
  113. package/dist/tools/get-relevant-content.d.ts +3 -1
  114. package/dist/tools/get-relevant-content.js +34 -3
  115. package/dist/tools/index.d.ts +4 -2
  116. package/dist/tools/index.js +4 -2
  117. package/dist/tools/install-drift-notes.d.ts +51 -0
  118. package/dist/tools/install-drift-notes.js +172 -0
  119. package/dist/tools/knowledge-gap-citations.d.ts +55 -0
  120. package/dist/tools/knowledge-gap-citations.js +132 -0
  121. package/dist/tools/knowledge-gap-exposure.d.ts +72 -0
  122. package/dist/tools/knowledge-gap-exposure.js +160 -0
  123. package/dist/tools/knowledge-gap-grading.d.ts +91 -0
  124. package/dist/tools/knowledge-gap-grading.js +157 -0
  125. package/dist/tools/knowledge-gap-installs.d.ts +65 -0
  126. package/dist/tools/knowledge-gap-installs.js +134 -0
  127. package/dist/tools/knowledge-gap-ranges.d.ts +51 -0
  128. package/dist/tools/knowledge-gap-ranges.js +146 -0
  129. package/dist/tools/knowledge-gap-scope.d.ts +63 -0
  130. package/dist/tools/knowledge-gap-scope.js +118 -0
  131. package/dist/tools/knowledge-gaps.d.ts +12 -3
  132. package/dist/tools/knowledge-gaps.js +172 -121
  133. package/dist/tools/package-presence.d.ts +24 -0
  134. package/dist/tools/package-presence.js +35 -0
  135. package/dist/tools/record-feedback.d.ts +0 -1
  136. package/dist/tools/record-feedback.js +7 -8
  137. package/dist/tools/signal-classifier.d.ts +25 -0
  138. package/dist/tools/signal-classifier.js +40 -0
  139. package/dist/tools/upgrade-impact-callsites.d.ts +52 -0
  140. package/dist/tools/upgrade-impact-callsites.js +226 -0
  141. package/dist/tools/upgrade-impact-literals.d.ts +45 -0
  142. package/dist/tools/upgrade-impact-literals.js +118 -0
  143. package/dist/tools/upgrade-impact-report.d.ts +80 -0
  144. package/dist/tools/upgrade-impact-report.js +178 -0
  145. package/dist/tools/upgrade-impact.d.ts +84 -0
  146. package/dist/tools/upgrade-impact.js +305 -0
  147. package/dist/tools/upgrade-planner.d.ts +41 -8
  148. package/dist/tools/upgrade-planner.js +201 -53
  149. package/dist/tools/vulnerability-scan-concise.d.ts +72 -0
  150. package/dist/tools/vulnerability-scan-concise.js +97 -0
  151. package/dist/tools/vulnerability-scan-format.d.ts +137 -0
  152. package/dist/tools/vulnerability-scan-format.js +276 -0
  153. package/dist/tools/vulnerability-scan.d.ts +338 -18
  154. package/dist/tools/vulnerability-scan.js +84 -125
  155. package/dist/tools/what-should-i-know.d.ts +86 -19
  156. package/dist/tools/what-should-i-know.js +277 -283
  157. package/dist/types.d.ts +4 -1
  158. package/package.json +27 -30
  159. package/dist/schemas/agent-memory.json +0 -49
  160. package/dist/schemas/decision-enforcement.json +0 -22
  161. package/dist/schemas/decision-memory.json +0 -54
  162. package/dist/schemas/dependency-health.json +0 -27
  163. package/dist/schemas/developer-dna.json +0 -219
  164. package/dist/schemas/ecosystem-pulse.json +0 -17
  165. package/dist/schemas/get-actionable-signals.json +0 -29
  166. package/dist/schemas/get-context.json +0 -17
  167. package/dist/schemas/get-relevant-content.json +0 -26
  168. package/dist/schemas/knowledge-gaps.json +0 -14
  169. package/dist/schemas/record-feedback.json +0 -24
  170. package/dist/schemas/upgrade-planner.json +0 -22
  171. package/dist/schemas/vulnerability-scan.json +0 -26
  172. 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 { scanCurrentProject } from "../project-scanner.js";
13
- import { hostTarget } from "../live/platform.js";
14
- import { maxSemver } from "../live/semver-utils.js";
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 severity level.",
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
- const projectPath = params.project_path || process.cwd();
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
- if (normalizedPath(projectPath) !== normalizedPath(process.cwd())) {
63
- const scan = scanCurrentProject(projectPath);
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.initFromMultiEcosystem(projectPath, scan.depsByEcosystem, scan.depTargets);
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 result = await scanner.scanVulnerabilities(projectPath, {
75
- includeDev: params.include_dev || false,
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
- if (result.totalScanned === 0 && !result.offline) {
79
- const hint = diagnoseMissingVersions(projectPath);
80
- return {
81
- ...formatResult(result, params.severity_filter),
82
- note: hint,
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
- return formatResult(result, params.severity_filter);
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 intelligence briefing for AI coding agents. Synthesizes:
5
- * - Live vulnerability data + actionable signals
6
- * - Decision windows (time-bounded opportunities)
7
- * - Ecosystem news (HN headlines relevant to tech stack)
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
- * Filters everything for relevance to the described task and involved files.
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
- interface WisdomEntry {
30
- type: string;
31
- subject: string;
32
- detail: string;
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
- interface EcosystemNewsItem {
35
- title: string;
36
- url: string | null;
37
- points: number;
38
- relevance_reason: string;
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
- type DelegationLevel = "safe_to_delegate" | "review_needed" | "human_only";
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
- ecosystem_news: EcosystemNewsItem[];
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: "hybrid" | "ranked_lexical";
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): WhatShouldIKnowResult | Promise<WhatShouldIKnowResult>;
144
+ export declare function executeWhatShouldIKnow(db: FourDADatabase, params: WhatShouldIKnowParams, liveIntel?: BriefingLiveIntel | null): Promise<WhatShouldIKnowResult>;
78
145
  export {};