@4da/mcp-server 6.0.2 → 6.2.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 (119) hide show
  1. package/CHANGELOG.md +433 -0
  2. package/README.md +26 -18
  3. package/dist/db.d.ts +73 -8
  4. package/dist/db.js +180 -43
  5. package/dist/doctor.d.ts +1 -1
  6. package/dist/doctor.js +15 -9
  7. package/dist/embeddings.js +19 -7
  8. package/dist/gitignore.js +9 -3
  9. package/dist/index.js +43 -4
  10. package/dist/live/cache.d.ts +31 -2
  11. package/dist/live/cache.js +113 -1
  12. package/dist/live/changelog-classify.d.ts +9 -3
  13. package/dist/live/changelog-classify.js +125 -12
  14. package/dist/live/changelog.d.ts +22 -3
  15. package/dist/live/changelog.js +132 -38
  16. package/dist/live/dev-scope.d.ts +2 -2
  17. package/dist/live/fix-paths.d.ts +118 -0
  18. package/dist/live/fix-paths.js +498 -0
  19. package/dist/live/github-releases.d.ts +69 -0
  20. package/dist/live/github-releases.js +252 -0
  21. package/dist/live/go-registry.d.ts +5 -0
  22. package/dist/live/go-registry.js +1 -1
  23. package/dist/live/go-releases.d.ts +48 -0
  24. package/dist/live/go-releases.js +196 -0
  25. package/dist/live/index.d.ts +34 -8
  26. package/dist/live/index.js +66 -7
  27. package/dist/live/lockfile-parents.d.ts +61 -0
  28. package/dist/live/lockfile-parents.js +360 -0
  29. package/dist/live/lockfile-parsers-pygo.d.ts +31 -0
  30. package/dist/live/lockfile-parsers-pygo.js +178 -28
  31. package/dist/live/lockfile-parsers.d.ts +4 -1
  32. package/dist/live/lockfile-parsers.js +57 -24
  33. package/dist/live/lockfile-types.d.ts +12 -3
  34. package/dist/live/lockfile-types.js +2 -1
  35. package/dist/live/osv-ranges.d.ts +85 -0
  36. package/dist/live/osv-ranges.js +136 -0
  37. package/dist/live/osv-scanner.d.ts +1 -0
  38. package/dist/live/osv-scanner.js +85 -78
  39. package/dist/live/package-archive.d.ts +2 -0
  40. package/dist/live/package-archive.js +7 -4
  41. package/dist/live/pypi-releases.d.ts +44 -0
  42. package/dist/live/pypi-releases.js +103 -0
  43. package/dist/live/range-satisfies.d.ts +21 -0
  44. package/dist/live/range-satisfies.js +190 -0
  45. package/dist/live/rate-limiter.d.ts +6 -0
  46. package/dist/live/rate-limiter.js +5 -0
  47. package/dist/live/registry-health.js +2 -0
  48. package/dist/live/release-metadata.d.ts +17 -8
  49. package/dist/live/release-metadata.js +6 -3
  50. package/dist/live/resolution.d.ts +11 -1
  51. package/dist/live/resolution.js +16 -6
  52. package/dist/live/semver-utils.d.ts +8 -0
  53. package/dist/live/semver-utils.js +10 -2
  54. package/dist/live/types.d.ts +17 -1
  55. package/dist/live/upgrade-go.d.ts +62 -0
  56. package/dist/live/upgrade-go.js +130 -0
  57. package/dist/live/upgrade-pypi.d.ts +42 -0
  58. package/dist/live/upgrade-pypi.js +85 -0
  59. package/dist/live/upgrade-sources.d.ts +30 -11
  60. package/dist/live/upgrade-sources.js +93 -21
  61. package/dist/live/zip-archive.d.ts +22 -0
  62. package/dist/live/zip-archive.js +98 -0
  63. package/dist/memory-store.d.ts +19 -0
  64. package/dist/memory-store.js +8 -0
  65. package/dist/native-bindings.d.ts +7 -2
  66. package/dist/native-bindings.js +4 -0
  67. package/dist/project-tree.d.ts +50 -8
  68. package/dist/project-tree.js +121 -29
  69. package/dist/schema-registry.js +5 -5
  70. package/dist/server-instructions.d.ts +6 -0
  71. package/dist/server-instructions.js +20 -15
  72. package/dist/sqlite-driver.d.ts +134 -0
  73. package/dist/sqlite-driver.js +359 -0
  74. package/dist/tool-dispatch.d.ts +8 -1
  75. package/dist/tool-dispatch.js +17 -2
  76. package/dist/tools/agent-memory.js +10 -2
  77. package/dist/tools/app-plan.d.ts +11 -1
  78. package/dist/tools/app-plan.js +32 -6
  79. package/dist/tools/briefing-task-scope.d.ts +21 -2
  80. package/dist/tools/briefing-task-scope.js +97 -4
  81. package/dist/tools/decision-memory.js +9 -3
  82. package/dist/tools/dependency-check-signals.d.ts +23 -5
  83. package/dist/tools/dependency-check-signals.js +62 -19
  84. package/dist/tools/dependency-check-versions.d.ts +94 -0
  85. package/dist/tools/dependency-check-versions.js +213 -0
  86. package/dist/tools/dependency-check.d.ts +27 -11
  87. package/dist/tools/dependency-check.js +321 -74
  88. package/dist/tools/dependency-health.d.ts +1 -1
  89. package/dist/tools/dependency-health.js +5 -0
  90. package/dist/tools/knowledge-gap-ranges.d.ts +1 -1
  91. package/dist/tools/knowledge-gap-ranges.js +1 -1
  92. package/dist/tools/knowledge-gap-scope.d.ts +2 -2
  93. package/dist/tools/project-scope.d.ts +45 -0
  94. package/dist/tools/project-scope.js +119 -0
  95. package/dist/tools/record-feedback.js +1 -1
  96. package/dist/tools/upgrade-impact-callsites.d.ts +1 -0
  97. package/dist/tools/upgrade-impact-callsites.js +23 -5
  98. package/dist/tools/upgrade-impact-changelog.d.ts +41 -0
  99. package/dist/tools/upgrade-impact-changelog.js +79 -0
  100. package/dist/tools/upgrade-impact-imports.d.ts +42 -0
  101. package/dist/tools/upgrade-impact-imports.js +260 -0
  102. package/dist/tools/upgrade-impact-literals.js +1 -1
  103. package/dist/tools/upgrade-impact-report.d.ts +23 -0
  104. package/dist/tools/upgrade-impact-report.js +45 -6
  105. package/dist/tools/upgrade-impact.d.ts +13 -4
  106. package/dist/tools/upgrade-impact.js +236 -85
  107. package/dist/tools/upgrade-planner-fixpaths.d.ts +40 -0
  108. package/dist/tools/upgrade-planner-fixpaths.js +0 -0
  109. package/dist/tools/upgrade-planner.d.ts +31 -3
  110. package/dist/tools/upgrade-planner.js +74 -19
  111. package/dist/tools/vulnerability-scan-concise.d.ts +13 -1
  112. package/dist/tools/vulnerability-scan-concise.js +15 -3
  113. package/dist/tools/vulnerability-scan-format.d.ts +58 -1
  114. package/dist/tools/vulnerability-scan-format.js +107 -11
  115. package/dist/tools/vulnerability-scan.d.ts +104 -13
  116. package/dist/tools/vulnerability-scan.js +27 -3
  117. package/dist/tools/what-should-i-know.d.ts +22 -2
  118. package/dist/tools/what-should-i-know.js +122 -12
  119. package/package.json +10 -32
@@ -8,6 +8,8 @@
8
8
  * conventional `## [7.0.0](https://…/compare/…) (2025-06-24)`
9
9
  * plain / prefixed `## 1.2.3`, `# v1.2.3 (2024-01-01)`, `### Version 1.2.3`, `## fastembed 5.0.0`
10
10
  * setext `1.2.3` underlined with `===` / `---`
11
+ * reStructuredText `Version 3.0.0` underlined (or over- and underlined)
12
+ * with any of `= - ~ ^ * + # " '`, then `Released 2023-09-30`
11
13
  * History.md (express) `1.2.3 / 2020-01-01`
12
14
  * A heading only opens a section when the rest of it looks like a trailer
13
15
  * (date, link, "yanked") — `## Upgrading from 1.x` or `#### Rust 1.70 support`
@@ -19,6 +21,13 @@
19
21
  */
20
22
  import { compareVersionPrecedence } from "./semver-precedence.js";
21
23
  import { classifyEntry, classifyHeading, sanitizeEntry } from "./changelog-classify.js";
24
+ /**
25
+ * Version of the parser AND classifier output. A parsed changelog is cached
26
+ * for 7 days per package version, so a fix to either one stays invisible
27
+ * for a week unless this changes: bump it with every change to parseChangelog
28
+ * or changelog-classify.ts that can alter a section or an entry's kind.
29
+ */
30
+ export const CHANGELOG_PARSER_VERSION = 4;
22
31
  const NAME_STEMS = ["changelog", "changes", "history", "releases", "release-notes", "release_notes", "releasenotes", "news"];
23
32
  const NAME_EXTS = ["", ".md", ".markdown", ".txt", ".rst"];
24
33
  /** True for a root-level basename that names a changelog (case-insensitive). */
@@ -71,7 +80,14 @@ function extractDate(text) {
71
80
  * heading. Exported for tests.
72
81
  */
73
82
  export function parseVersionHeading(raw) {
74
- const text = raw.trim().replace(/^\*\*|\*\*$/g, "");
83
+ // Inline HTML is markup, not heading text: stripe writes
84
+ // `## <a id="23-0-0"></a>23.0.0 - 2026-09-30`, and until 6.0.2 no stripe
85
+ // release was found (changelog.found: false, 2026-10-07).
86
+ const text = raw
87
+ .replace(/<\/?[A-Za-z][^<>]*>/g, " ")
88
+ .trim()
89
+ .replace(/^\*\*|\*\*$/g, "")
90
+ .trim();
75
91
  const m = HEADING.exec(text);
76
92
  if (!m)
77
93
  return null;
@@ -92,7 +108,13 @@ export function parseVersionHeading(raw) {
92
108
  return { version, date: extractDate(rest) };
93
109
  }
94
110
  const ATX = /^(#{1,6})\s+(.*?)\s*#*\s*$/;
95
- const SETEXT_RULE = /^\s*(=+|-+)\s*$/;
111
+ /**
112
+ * A setext / reStructuredText underline. `=` and `-` at any length (Markdown);
113
+ * the other RST adornments need three, so a stray `**` is not a heading rule.
114
+ */
115
+ const SETEXT_RULE = /^\s*(=+|-+|~{3,}|\^{3,}|\*{3,}|\+{3,}|#{3,}|"{3,}|'{3,})\s*$/;
116
+ /** `Released 2023-09-30`, `Released on 2024-01-01`, `Release date: ...`, `Unreleased`: a date line under an RST heading. */
117
+ const RELEASED_LINE = /^\s*(?:\*{0,2}(?:released|release date|date)\*{0,2}(?:\s+on)?\s*:?\s*(.*)|unreleased|\(unreleased\))\s*$/i;
96
118
  const BULLET = /^(\s*)(?:[-*+•]|\d+[.)])\s+(.*)$/;
97
119
  const LINK_DEF = /^\s*\[[^\]]+\]:\s*\S+/;
98
120
  const FENCE = /^\s*(```|~~~)/;
@@ -105,17 +127,37 @@ const PLAIN_LABEL = /^\s{0,3}([A-Za-z⚠][\w /&,'()⚠️-]{1,58}?)\s*:\s*$/;
105
127
  const HTML_LAYOUT = /^<\/?(?:details|summary|div|p|br|hr|img|picture|source|table|tr|td|th|thead|tbody|center|sup|sub)\b/i;
106
128
  /** A bullet that is only a label: "remove:", "**Breaking**:", "deps:". */
107
129
  const LABEL_BULLET = /^\W*([A-Za-z][\w -]{0,30}?)\W*:\W*$/;
130
+ const KIND_SEVERITY = { breaking: 3, security: 2, deprecation: 1, change: 0 };
131
+ /** Nested lines kept per entry, and the length of each. */
132
+ const MAX_DETAILS = 12;
133
+ const MAX_DETAIL_CHARS = 200;
108
134
  function flush(state) {
109
- if (state.pending && state.current) {
110
- const text = sanitizeEntry(state.pending.text);
111
- const under = state.pending.under;
112
- // Layout markup is not a change: actix-web 4 wraps pre-release notes in
113
- // `<details> <summary>...</summary>`, and both lines were counted as removals.
114
- if (text && !HTML_LAYOUT.test(text)) {
115
- state.current.entries.push({ kind: classifyEntry(text, state.pending.context), text, ...(under ? { under } : {}) });
116
- }
117
- }
135
+ const pending = state.pending;
118
136
  state.pending = null;
137
+ if (!pending || !state.current)
138
+ return;
139
+ const text = sanitizeEntry(pending.text);
140
+ // Layout markup is not a change: actix-web 4 wraps pre-release notes in
141
+ // `<details> <summary>...</summary>`, and both lines were counted as removals.
142
+ if (!text || HTML_LAYOUT.test(text))
143
+ return;
144
+ const kind = classifyEntry(text, pending.context);
145
+ if (pending.into) {
146
+ const parent = pending.into;
147
+ const details = (parent.details ??= []);
148
+ if (details.length < MAX_DETAILS)
149
+ details.push(sanitizeEntry(pending.text, MAX_DETAIL_CHARS));
150
+ // "Breaking change: adds a DB type parameter" under a plain "fix: RawSql
151
+ // lifetime issues" bullet makes the entry breaking.
152
+ if (KIND_SEVERITY[kind] > KIND_SEVERITY[parent.kind])
153
+ parent.kind = kind;
154
+ return;
155
+ }
156
+ const under = pending.under;
157
+ const entry = { kind, text, ...(under ? { under } : {}) };
158
+ state.current.entries.push(entry);
159
+ if (pending.outer)
160
+ state.parentEntry = entry;
119
161
  }
120
162
  /**
121
163
  * Headings that name a kind of change (keep-a-changelog and conventional-commit
@@ -135,6 +177,7 @@ function openSection(state, heading, level) {
135
177
  state.parentIndent = null;
136
178
  state.headingText = null;
137
179
  state.parentText = null;
180
+ state.parentEntry = null;
138
181
  }
139
182
  /** A heading or parent bullet as context text: emphasis and code marks dropped, one line, at most 80 characters. */
140
183
  function contextText(raw) {
@@ -156,6 +199,8 @@ function addLine(state, line) {
156
199
  if (label) {
157
200
  state.parentKind = classifyHeading(label[1]) ?? state.headingKind;
158
201
  state.parentText = contextText(label[1]);
202
+ // A label's children are entries of their own, under the label.
203
+ state.parentEntry = null;
159
204
  return;
160
205
  }
161
206
  // A breaking bullet lends "breaking" to its sub-points (date-fns 3.0: "**BREAKING**: Functions
@@ -167,7 +212,9 @@ function addLine(state, line) {
167
212
  }
168
213
  const context = !outer && state.parentKind ? state.parentKind : state.headingKind;
169
214
  const under = !outer && state.parentText ? state.parentText : state.headingText;
170
- state.pending = { text, context, indent, under };
215
+ if (outer)
216
+ state.parentEntry = null;
217
+ state.pending = { text, context, indent, under, outer, into: outer ? null : state.parentEntry };
171
218
  return;
172
219
  }
173
220
  if (line.trim() === "") {
@@ -182,9 +229,54 @@ function addLine(state, line) {
182
229
  state.pending.text += ` ${line.trim()}`; // wrapped paragraph line
183
230
  return;
184
231
  }
232
+ // An indented paragraph after a blank line continues the list item above it
233
+ // (stripe 23.0.0: "* ⚠️ Remove ErrorType export", blank, " Remove the ErrorType
234
+ // interface ..."): a detail of that entry, not an entry of its own.
235
+ if (!state.pending && state.parentEntry && /^\s{2,}\S/.test(line)) {
236
+ state.pending = {
237
+ text: line.trim(),
238
+ context: state.parentKind ?? state.headingKind,
239
+ indent: -2,
240
+ under: state.headingText,
241
+ into: state.parentEntry,
242
+ };
243
+ return;
244
+ }
185
245
  flush(state);
246
+ state.parentEntry = null;
186
247
  state.pending = { text: line, context: state.headingKind, indent: -1, under: state.headingText };
187
248
  }
249
+ /** A heading that is not a release: a category inside the current release, or the end of it. */
250
+ function subHeading(state, raw, level, setext = false) {
251
+ flush(state);
252
+ // A setext title ends the release only from a strictly higher level: "text" over
253
+ // "---" is also how a paragraph followed by a rule reads.
254
+ if (setext && level >= state.sectionLevel)
255
+ level = state.sectionLevel + 1;
256
+ // "## Migration guide" after the last release must not be read as part of it.
257
+ // A change-category heading ("## Changed", "## Breaking Changes") at the
258
+ // release's own level is still inside it: date-fns 3.0.0 writes
259
+ // "## v3.0.0" then "## Changed", and its ten BREAKING entries were dropped.
260
+ if (state.current && level <= state.sectionLevel && !isCategoryHeading(raw))
261
+ state.current = null;
262
+ state.headingKind = classifyHeading(raw);
263
+ state.headingText = contextText(raw);
264
+ state.parentKind = null;
265
+ state.parentIndent = null;
266
+ state.parentText = null;
267
+ state.parentEntry = null;
268
+ }
269
+ /**
270
+ * Setext / RST title level by adornment. Markdown fixes `=` at 1 and `-` at 2;
271
+ * reStructuredText ranks adornments in the order a document first uses them,
272
+ * so the rest rank below both, in a fixed order: close enough to compare a
273
+ * sub-heading with the release title above it.
274
+ */
275
+ function adornmentLevel(ch) {
276
+ const order = "=-~^*+#\"'";
277
+ const i = order.indexOf(ch);
278
+ return i === -1 ? 3 : i + 1;
279
+ }
188
280
  /** Parse a changelog into version sections, in document order (usually newest first). */
189
281
  export function parseChangelog(text) {
190
282
  const lines = text.replace(/\r\n?/g, "\n").split("\n");
@@ -198,6 +290,7 @@ export function parseChangelog(text) {
198
290
  headingText: null,
199
291
  parentText: null,
200
292
  pending: null,
293
+ parentEntry: null,
201
294
  };
202
295
  let inFence = false;
203
296
  let inComment = false;
@@ -231,30 +324,30 @@ export function parseChangelog(text) {
231
324
  const level = atx[1].length;
232
325
  if (heading)
233
326
  openSection(state, heading, level);
234
- else {
235
- flush(state);
236
- // "## Migration guide" after the last release must not be read as part of it.
237
- // A change-category heading ("## Changed", "## Breaking Changes") at the
238
- // release's own level is still inside it: date-fns 3.0.0 writes
239
- // "## v3.0.0" then "## Changed", and its ten BREAKING entries were dropped.
240
- if (state.current && level <= state.sectionLevel && !isCategoryHeading(atx[2]))
241
- state.current = null;
242
- state.headingKind = classifyHeading(atx[2]);
243
- state.headingText = contextText(atx[2]);
244
- state.parentKind = null;
245
- state.parentIndent = null;
246
- state.parentText = null;
247
- }
327
+ else
328
+ subHeading(state, atx[2], level);
248
329
  continue;
249
330
  }
250
331
  const next = lines[i + 1];
251
- if (next !== undefined && SETEXT_RULE.test(next) && line.trim() !== "" && !BULLET.test(line)) {
332
+ if (next !== undefined && SETEXT_RULE.test(next) && line.trim() !== "" && !BULLET.test(line) && !SETEXT_RULE.test(line)) {
333
+ const level = adornmentLevel(next.trim()[0]);
252
334
  const heading = parseVersionHeading(line);
335
+ i++;
253
336
  if (heading) {
254
- openSection(state, heading, next.trim().startsWith("=") ? 1 : 2);
255
- i++;
337
+ openSection(state, heading, level);
256
338
  continue;
257
339
  }
340
+ // A non-release setext / RST title is a sub-heading ("Breaking Changes" over
341
+ // `~~~~`), as an ATX one is: it was an entry until parser version 3.
342
+ subHeading(state, line, level, true);
343
+ continue;
344
+ }
345
+ // `Released 2023-09-30` right under an RST release title is its date, not a change (flask, werkzeug, click).
346
+ const released = state.current && state.current.entries.length === 0 && !state.pending ? RELEASED_LINE.exec(line) : null;
347
+ if (released && (released[1] === undefined || extractDate(released[1]) || /^(?:unreleased|tbd|-)?$/i.test(released[1].trim()))) {
348
+ if (released[1])
349
+ state.current.date ??= extractDate(released[1]);
350
+ continue;
258
351
  }
259
352
  // History.md style without an underline: `1.2.3 / 2020-01-01` alone on a line.
260
353
  const history = new RegExp(String.raw `^v?(${VERSION})\s+/\s+(.+)$`).exec(line.trim());
@@ -288,6 +381,7 @@ export function parseChangelog(text) {
288
381
  state.parentKind = null;
289
382
  state.parentIndent = null;
290
383
  state.parentText = null;
384
+ state.parentEntry = null;
291
385
  continue;
292
386
  }
293
387
  if (state.current)
@@ -296,10 +390,10 @@ export function parseChangelog(text) {
296
390
  flush(state);
297
391
  return state.sections;
298
392
  }
299
- /** True when `version` lies in (from, to] by semver precedence; unreadable versions are out. */
300
- export function inRange(version, from, to) {
301
- const lower = compareVersionPrecedence(version, from);
302
- const upper = compareVersionPrecedence(version, to);
393
+ /** True when `version` lies in (from, to] by `order`; unreadable versions are out. */
394
+ export function inRange(version, from, to, order = compareVersionPrecedence) {
395
+ const lower = order(version, from);
396
+ const upper = order(version, to);
303
397
  return lower !== null && upper !== null && lower > 0 && upper <= 0;
304
398
  }
305
399
  /**
@@ -309,10 +403,10 @@ export function inRange(version, from, to) {
309
403
  * stopped being maintained two majors ago is reported as not covering the
310
404
  * range instead of silently presenting a partial history as the whole one.
311
405
  */
312
- export function selectRange(sections, from, to, firstInRange) {
313
- const readable = sections.filter((s) => compareVersionPrecedence(s.version, s.version) !== null);
314
- const selected = readable.filter((s) => inRange(s.version, from, to));
315
- const reachesTop = readable.some((s) => (compareVersionPrecedence(s.version, to) ?? -1) >= 0);
316
- const reachesBottom = readable.some((s) => (compareVersionPrecedence(s.version, firstInRange) ?? 1) <= 0);
406
+ export function selectRange(sections, from, to, firstInRange, order = compareVersionPrecedence) {
407
+ const readable = sections.filter((s) => order(s.version, s.version) !== null);
408
+ const selected = readable.filter((s) => inRange(s.version, from, to, order));
409
+ const reachesTop = readable.some((s) => (order(s.version, to) ?? -1) >= 0);
410
+ const reachesBottom = readable.some((s) => (order(s.version, firstInRange) ?? 1) <= 0);
317
411
  return { sections: selected, coversRange: selected.length > 0 && reachesTop && reachesBottom };
318
412
  }
@@ -13,7 +13,7 @@
13
13
  * claim a certainty the app itself does not have. The grade would not change
14
14
  * (unknown and runtime both get no discount), but `dev_scope_known` would lie.
15
15
  */
16
- import type Database from "better-sqlite3";
16
+ import type { SqliteDatabase } from "../sqlite-driver.js";
17
17
  import type { ResolvedDependency } from "./types.js";
18
18
  /**
19
19
  * A project path in the app's storage form: forward slashes, lowercased on
@@ -29,4 +29,4 @@ export declare function canonicalStoragePath(p: string): string;
29
29
  * dependencies are untouched: their manifest already says. Returns new
30
30
  * objects; never throws; a database without the table changes nothing.
31
31
  */
32
- export declare function applyInstanceDevScope(db: Database.Database | null, deps: ResolvedDependency[]): ResolvedDependency[];
32
+ export declare function applyInstanceDevScope(db: SqliteDatabase | null, deps: ResolvedDependency[]): ResolvedDependency[];
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Fix paths for vulnerable dependencies: WHICH version to move to, and HOW.
3
+ *
4
+ * Two defects found by the fix-path oracle (2026-10-10, 19 public repos;
5
+ * each recommendation applied in a copy, re-resolved, re-scanned):
6
+ *
7
+ * 1. The target could itself be vulnerable. It was the highest fix among the
8
+ * advisories that affect the installed version, so an advisory the
9
+ * installed version predates was never consulted: openssl 0.10.38 was sent
10
+ * to 0.10.79, which GHSA-phqj-4mhp-q6mq affects (0.10.50 up to 0.10.80).
11
+ * Now: the smallest published version at or above every per-advisory fix
12
+ * that NO advisory of the package affects (osv-ranges.ts).
13
+ * 2. Every transitive finding was "waiting on upstream", 3 wrong out of 3:
14
+ * minimist 1.2.5 (npm), braces 3.0.2 (pnpm) and mio 0.8.0 (Cargo) all had
15
+ * parents whose declared requirement already admitted the fix, and a
16
+ * lockfile refresh fixed each one. Now: the parents' requirements are
17
+ * read (from the lockfile, or from the parent's registry metadata for
18
+ * pnpm and Cargo) and a refresh is recommended when they all admit a
19
+ * clean version.
20
+ *
21
+ * Network: api.osv.dev (one /v1/query per vulnerable package) and the
22
+ * package's own registry; package names and versions only. Every lookup is
23
+ * cached, and any failure degrades to the old answer, never to a guess.
24
+ */
25
+ import type { LiveCache } from "./cache.js";
26
+ import type { RateLimiter } from "./rate-limiter.js";
27
+ import { type AdvisoryRanges, type CleanTarget } from "./osv-ranges.js";
28
+ import { type LockfileFormat } from "./lockfile-parents.js";
29
+ import type { OsvEcosystem } from "./types.js";
30
+ export interface CrateDepReq {
31
+ req: string;
32
+ optional: boolean;
33
+ }
34
+ export interface CratesSparse {
35
+ /** Versions not yanked. */
36
+ versions: string[];
37
+ /** version -> dependency crate name -> requirements (one per target/kind entry; dev-dependencies left out). */
38
+ reqs: Record<string, Record<string, CrateDepReq[]>>;
39
+ }
40
+ export declare class FixPathSources {
41
+ private cache;
42
+ private rateLimiter;
43
+ constructor(cache: LiveCache, rateLimiter: RateLimiter);
44
+ /** Every non-withdrawn OSV advisory for a package, as ranges; null when OSV could not be read. */
45
+ packageAdvisories(ecosystem: OsvEcosystem, name: string): Promise<AdvisoryRanges[] | null>;
46
+ /** Published, installable versions (not yanked); null when the registry could not be read or is not supported. */
47
+ publishedVersions(ecosystem: OsvEcosystem, name: string): Promise<string[] | null>;
48
+ /**
49
+ * The requirements `parent@parentVersion` declares on `dep`, from the
50
+ * registry: npm (pnpm lockfiles record none) and crates.io (Cargo.lock
51
+ * records none). Null when unknown; [] when the parent declares none.
52
+ */
53
+ parentRequirements(ecosystem: OsvEcosystem, parent: string, parentVersion: string, dep: string): Promise<string[] | null>;
54
+ private npmVersions;
55
+ /** A crate's sparse-index record: versions and per-version dependency requirements. */
56
+ cratesSparse(name: string): Promise<CratesSparse | null>;
57
+ private pypiVersions;
58
+ private goVersions;
59
+ }
60
+ export type FixTargetResult = CleanTarget;
61
+ /** The minimal clean target for one installed copy, or null when the advisories could not be read. */
62
+ export declare function cleanFixTarget(sources: FixPathSources, ecosystem: OsvEcosystem, name: string, perAdvisoryFixes: string[]): Promise<FixTargetResult | null>;
63
+ /**
64
+ * Verified targets for many installed copies at once, within a time budget:
65
+ * the copies that could not be checked in time (or offline) are simply
66
+ * absent from the result, and the caller says so. Keyed by `key`.
67
+ */
68
+ export declare function verifyFixTargets(sources: FixPathSources | null, items: Array<{
69
+ key: string;
70
+ ecosystem: OsvEcosystem;
71
+ name: string;
72
+ fixes: string[];
73
+ }>, budgetMs: number, concurrency?: number): Promise<Map<string, CleanTarget>>;
74
+ /** One clause for a target that is not the highest per-advisory fix, or null when it is. */
75
+ export declare function targetNote(clean: CleanTarget): string | null;
76
+ export interface RefreshVerdict {
77
+ /** True when every parent's requirement already admits a clean version. */
78
+ refreshFixes: boolean;
79
+ format: LockfileFormat;
80
+ /** The command, run in the lockfile's directory. */
81
+ command: string;
82
+ /** Parent requirements that do NOT admit a clean version (why it is waiting on upstream). */
83
+ blocking: Array<{
84
+ parent: string;
85
+ parentVersion: string | null;
86
+ requirement: string;
87
+ }>;
88
+ /** The version a refresh resolves to, when the registry's version list was read. */
89
+ resolvesTo: string | null;
90
+ }
91
+ /** The refresh command for one lockfile format. */
92
+ export declare function refreshCommand(format: LockfileFormat, name: string, installed: string, target: string): string;
93
+ /**
94
+ * Whether refreshing `lockfile` moves `name@installed` to a clean version
95
+ * without any parent changing its declared requirement. Null when the
96
+ * lockfile or a requirement cannot be read (the caller keeps "waiting on
97
+ * upstream", the conservative answer).
98
+ */
99
+ export declare function refreshVerdict(sources: FixPathSources, ecosystem: OsvEcosystem, lockfile: string, name: string, installed: string, target: string, advisories: AdvisoryRanges[] | null, published: string[] | null): Promise<RefreshVerdict | null>;
100
+ /**
101
+ * The locked crates `cargo update -p name@installed --precise target` cannot
102
+ * move by itself, as `crate@lockedVersion` specs to update first; null when it
103
+ * cannot be worked out (a registry read failed, or the walk is too large).
104
+ *
105
+ * Measured on nushell (2026-10-10): `cargo update -p openssl@0.10.38
106
+ * --precise 0.10.80` fails. 0.10.80 needs openssl-macros 0.1.1 -> syn 2 ->
107
+ * quote ^1.0.25, and quote 1.0.15 is locked by rstest, outside openssl's own
108
+ * dependency tree, so cargo keeps it and reports a conflict. `cargo update -p
109
+ * quote` first, then the precise update, works. Crates in the installed
110
+ * version's own tree may move with it (openssl-sys did) and are not listed.
111
+ *
112
+ * The walk follows the target's normal and build dependencies (optional ones
113
+ * only when the crate is already in the lockfile): a requirement a locked copy
114
+ * satisfies is reused; one whose compatibility line holds a locked copy that
115
+ * does not satisfy it names that copy; anything else is a new crate, walked
116
+ * at its newest matching version.
117
+ */
118
+ export declare function cargoPrecisePrerequisites(sources: FixPathSources, lockContent: string, name: string, installed: string, target: string): Promise<string[] | null>;