@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.
- package/CHANGELOG.md +433 -0
- package/README.md +26 -18
- package/dist/db.d.ts +73 -8
- package/dist/db.js +180 -43
- package/dist/doctor.d.ts +1 -1
- package/dist/doctor.js +15 -9
- package/dist/embeddings.js +19 -7
- package/dist/gitignore.js +9 -3
- package/dist/index.js +43 -4
- package/dist/live/cache.d.ts +31 -2
- package/dist/live/cache.js +113 -1
- package/dist/live/changelog-classify.d.ts +9 -3
- package/dist/live/changelog-classify.js +125 -12
- package/dist/live/changelog.d.ts +22 -3
- package/dist/live/changelog.js +132 -38
- package/dist/live/dev-scope.d.ts +2 -2
- package/dist/live/fix-paths.d.ts +118 -0
- package/dist/live/fix-paths.js +498 -0
- package/dist/live/github-releases.d.ts +69 -0
- package/dist/live/github-releases.js +252 -0
- package/dist/live/go-registry.d.ts +5 -0
- package/dist/live/go-registry.js +1 -1
- package/dist/live/go-releases.d.ts +48 -0
- package/dist/live/go-releases.js +196 -0
- package/dist/live/index.d.ts +34 -8
- package/dist/live/index.js +66 -7
- package/dist/live/lockfile-parents.d.ts +61 -0
- package/dist/live/lockfile-parents.js +360 -0
- package/dist/live/lockfile-parsers-pygo.d.ts +31 -0
- package/dist/live/lockfile-parsers-pygo.js +178 -28
- package/dist/live/lockfile-parsers.d.ts +4 -1
- package/dist/live/lockfile-parsers.js +57 -24
- package/dist/live/lockfile-types.d.ts +12 -3
- package/dist/live/lockfile-types.js +2 -1
- package/dist/live/osv-ranges.d.ts +85 -0
- package/dist/live/osv-ranges.js +136 -0
- package/dist/live/osv-scanner.d.ts +1 -0
- package/dist/live/osv-scanner.js +85 -78
- package/dist/live/package-archive.d.ts +2 -0
- package/dist/live/package-archive.js +7 -4
- package/dist/live/pypi-releases.d.ts +44 -0
- package/dist/live/pypi-releases.js +103 -0
- package/dist/live/range-satisfies.d.ts +21 -0
- package/dist/live/range-satisfies.js +190 -0
- package/dist/live/rate-limiter.d.ts +6 -0
- package/dist/live/rate-limiter.js +5 -0
- package/dist/live/registry-health.js +2 -0
- package/dist/live/release-metadata.d.ts +17 -8
- package/dist/live/release-metadata.js +6 -3
- package/dist/live/resolution.d.ts +11 -1
- package/dist/live/resolution.js +16 -6
- package/dist/live/semver-utils.d.ts +8 -0
- package/dist/live/semver-utils.js +10 -2
- package/dist/live/types.d.ts +17 -1
- package/dist/live/upgrade-go.d.ts +62 -0
- package/dist/live/upgrade-go.js +130 -0
- package/dist/live/upgrade-pypi.d.ts +42 -0
- package/dist/live/upgrade-pypi.js +85 -0
- package/dist/live/upgrade-sources.d.ts +30 -11
- package/dist/live/upgrade-sources.js +93 -21
- package/dist/live/zip-archive.d.ts +22 -0
- package/dist/live/zip-archive.js +98 -0
- package/dist/memory-store.d.ts +19 -0
- package/dist/memory-store.js +8 -0
- package/dist/native-bindings.d.ts +7 -2
- package/dist/native-bindings.js +4 -0
- package/dist/project-tree.d.ts +50 -8
- package/dist/project-tree.js +121 -29
- package/dist/schema-registry.js +5 -5
- package/dist/server-instructions.d.ts +6 -0
- package/dist/server-instructions.js +20 -15
- package/dist/sqlite-driver.d.ts +134 -0
- package/dist/sqlite-driver.js +359 -0
- package/dist/tool-dispatch.d.ts +8 -1
- package/dist/tool-dispatch.js +17 -2
- package/dist/tools/agent-memory.js +10 -2
- package/dist/tools/app-plan.d.ts +11 -1
- package/dist/tools/app-plan.js +32 -6
- package/dist/tools/briefing-task-scope.d.ts +21 -2
- package/dist/tools/briefing-task-scope.js +97 -4
- package/dist/tools/decision-memory.js +9 -3
- package/dist/tools/dependency-check-signals.d.ts +23 -5
- package/dist/tools/dependency-check-signals.js +62 -19
- package/dist/tools/dependency-check-versions.d.ts +94 -0
- package/dist/tools/dependency-check-versions.js +213 -0
- package/dist/tools/dependency-check.d.ts +27 -11
- package/dist/tools/dependency-check.js +321 -74
- package/dist/tools/dependency-health.d.ts +1 -1
- package/dist/tools/dependency-health.js +5 -0
- package/dist/tools/knowledge-gap-ranges.d.ts +1 -1
- package/dist/tools/knowledge-gap-ranges.js +1 -1
- package/dist/tools/knowledge-gap-scope.d.ts +2 -2
- package/dist/tools/project-scope.d.ts +45 -0
- package/dist/tools/project-scope.js +119 -0
- package/dist/tools/record-feedback.js +1 -1
- package/dist/tools/upgrade-impact-callsites.d.ts +1 -0
- package/dist/tools/upgrade-impact-callsites.js +23 -5
- package/dist/tools/upgrade-impact-changelog.d.ts +41 -0
- package/dist/tools/upgrade-impact-changelog.js +79 -0
- package/dist/tools/upgrade-impact-imports.d.ts +42 -0
- package/dist/tools/upgrade-impact-imports.js +260 -0
- package/dist/tools/upgrade-impact-literals.js +1 -1
- package/dist/tools/upgrade-impact-report.d.ts +23 -0
- package/dist/tools/upgrade-impact-report.js +45 -6
- package/dist/tools/upgrade-impact.d.ts +13 -4
- package/dist/tools/upgrade-impact.js +236 -85
- package/dist/tools/upgrade-planner-fixpaths.d.ts +40 -0
- package/dist/tools/upgrade-planner-fixpaths.js +0 -0
- package/dist/tools/upgrade-planner.d.ts +31 -3
- package/dist/tools/upgrade-planner.js +74 -19
- package/dist/tools/vulnerability-scan-concise.d.ts +13 -1
- package/dist/tools/vulnerability-scan-concise.js +15 -3
- package/dist/tools/vulnerability-scan-format.d.ts +58 -1
- package/dist/tools/vulnerability-scan-format.js +107 -11
- package/dist/tools/vulnerability-scan.d.ts +104 -13
- package/dist/tools/vulnerability-scan.js +27 -3
- package/dist/tools/what-should-i-know.d.ts +22 -2
- package/dist/tools/what-should-i-know.js +122 -12
- package/package.json +10 -32
package/dist/live/changelog.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
300
|
-
export function inRange(version, from, to) {
|
|
301
|
-
const lower =
|
|
302
|
-
const upper =
|
|
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) =>
|
|
314
|
-
const selected = readable.filter((s) => inRange(s.version, from, to));
|
|
315
|
-
const reachesTop = readable.some((s) => (
|
|
316
|
-
const reachesBottom = readable.some((s) => (
|
|
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
|
}
|
package/dist/live/dev-scope.d.ts
CHANGED
|
@@ -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
|
|
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:
|
|
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>;
|