@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
@@ -0,0 +1,213 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ /**
3
+ * dependency_check version handling across npm, crates.io, PyPI and Go:
4
+ * reading `to` as an exact version, "latest", or a constraint, and finding
5
+ * the safe releases a constraint admits.
6
+ *
7
+ * Why a constraint: an agent told "pin ws 7.x" writes the 7.x release it
8
+ * remembers. In the G7 agent evaluation (2026-10-11) that was ws 7.5.10 and
9
+ * body-parser 1.20.3, both affected by an advisory their next patch fixes.
10
+ * Asked with `to: "7.x"`, the check answers with the smallest and newest
11
+ * release in the constraint that no known advisory affects.
12
+ *
13
+ * Constraint syntax is the ecosystem's own (npm ranges, Cargo requirements,
14
+ * PEP 440 specifiers), plus the shorthand every agent writes: "7.x", "3.1.*",
15
+ * "v1" for a Go major. Pure functions, no I/O.
16
+ */
17
+ import { cargoReqAdmits, npmRangeAdmits } from "../live/range-satisfies.js";
18
+ import { advisoryAffects, isPrereleaseVersion } from "../live/osv-ranges.js";
19
+ import { compareVersions, parsePep440 } from "../live/version-compare.js";
20
+ import { parseSemverPrecedence } from "../live/semver-precedence.js";
21
+ import { GO_VERSION, goVersion } from "../live/go-releases.js";
22
+ const FULL_SEMVER = /^v?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
23
+ /** Read a `to` value; null when it is neither a version nor a constraint this ecosystem understands. */
24
+ export function parseTarget(ecosystem, raw) {
25
+ const text = raw.trim();
26
+ if (!text)
27
+ return null;
28
+ if (text.toLowerCase() === "latest")
29
+ return { kind: "latest" };
30
+ switch (ecosystem) {
31
+ case "npm":
32
+ case "crates.io": {
33
+ // "1.2.3", and the exact-pin spellings "=1.2.3" / "==1.2.3" / "v1.2.3".
34
+ const bare = text.replace(/^={1,2}\s*/, "");
35
+ if (FULL_SEMVER.test(bare))
36
+ return { kind: "exact", version: bare.replace(/^v(?=\d)/, "") };
37
+ return rangeAdmits(ecosystem, text, "1.0.0") === null ? null : { kind: "range", range: text };
38
+ }
39
+ case "PyPI": {
40
+ const pinned = /^={2,3}\s*([^,*\s]+)$/.exec(text);
41
+ if (pinned && parsePep440(pinned[1]))
42
+ return { kind: "exact", version: pinned[1] };
43
+ const p = parsePep440(text);
44
+ if (p) {
45
+ // "3.1.4" is a release; "3.1" may be a release (PyPI has "2.0"s) or mean the 3.1 series.
46
+ const plain = p.release.length < 3 && p.pre === null && p.post === null && p.dev === null && !text.includes("+");
47
+ return plain ? { kind: "exact_or_series", version: text, range: `==${text}.*` } : { kind: "exact", version: text };
48
+ }
49
+ const range = pypiShorthand(text);
50
+ return pep440SpecAdmits(range, "1.0") === null ? null : { kind: "range", range };
51
+ }
52
+ case "Go": {
53
+ const v = goVersion(text);
54
+ if (GO_VERSION.test(v))
55
+ return { kind: "exact", version: v };
56
+ return rangeAdmits("Go", text, "v1.0.0") === null ? null : { kind: "range", range: text };
57
+ }
58
+ }
59
+ }
60
+ /** "3.1.x" / "3.x" / "3.1.*" (no operator) -> "==3.1.*". */
61
+ function pypiShorthand(text) {
62
+ const m = /^v?(\d+(?:\.\d+)*)\.[xX*]$/.exec(text);
63
+ return m ? `==${m[1]}.*` : text;
64
+ }
65
+ /** Whether a constraint admits a version, in the ecosystem's own syntax. Null = unreadable constraint or version. */
66
+ export function rangeAdmits(ecosystem, range, version) {
67
+ switch (ecosystem) {
68
+ case "npm":
69
+ return npmRangeAdmits(range, version);
70
+ case "crates.io":
71
+ // Cargo's own syntax first ("0.17" means ^0.17); then npm-style spellings agents also write ("0.17.x").
72
+ return cargoReqAdmits(range, version) ?? npmRangeAdmits(range, version);
73
+ case "PyPI":
74
+ return pep440SpecAdmits(pypiShorthand(range.trim()), version);
75
+ case "Go": {
76
+ // Go has no range syntax of its own; read semver ranges over `v`-less versions ("v1" = the v1 major).
77
+ const unv = range.replace(/(^|[\s,<>=~^|-])v(?=\d)/g, "$1");
78
+ return npmRangeAdmits(unv.replace(/,\s*/g, " "), version.replace(/^v(?=\d)/, "").replace(/\+incompatible$/, ""));
79
+ }
80
+ }
81
+ }
82
+ /**
83
+ * PEP 440 version specifiers: comma-separated clauses of `==`, `!=` (both
84
+ * with optional `.*` prefix matching), `>=`, `<=`, `>`, `<`, `~=` and `===`.
85
+ * Null when a clause or the version is unreadable.
86
+ */
87
+ export function pep440SpecAdmits(spec, version) {
88
+ const v = parsePep440(version);
89
+ if (!v)
90
+ return null;
91
+ const clauses = spec.split(",").map((c) => c.trim()).filter(Boolean);
92
+ if (clauses.length === 0)
93
+ return null;
94
+ for (const clause of clauses) {
95
+ const m = /^(===|~=|==|!=|>=|<=|>|<)\s*(\S+)$/.exec(clause);
96
+ if (!m)
97
+ return null;
98
+ const [, op, rawTarget] = m;
99
+ if (op === "===") {
100
+ if (rawTarget !== version)
101
+ return false;
102
+ continue;
103
+ }
104
+ const prefix = rawTarget.endsWith(".*");
105
+ const target = parsePep440(prefix ? rawTarget.slice(0, -2) : rawTarget);
106
+ if (!target)
107
+ return null;
108
+ let ok;
109
+ if (prefix) {
110
+ if (op !== "==" && op !== "!=")
111
+ return null;
112
+ ok = matchesPrefix(v, target) === (op === "==");
113
+ }
114
+ else if (op === "~=") {
115
+ if (target.release.length < 2)
116
+ return null;
117
+ const series = { ...target, release: target.release.slice(0, -1), pre: null, post: null, dev: null };
118
+ ok = cmp(v, target) >= 0 && matchesPrefix(v, series);
119
+ }
120
+ else {
121
+ const d = cmp(v, target);
122
+ ok = op === "==" ? d === 0 : op === "!=" ? d !== 0 : op === ">=" ? d >= 0 : op === "<=" ? d <= 0 : op === ">" ? d > 0 : d < 0;
123
+ }
124
+ if (!ok)
125
+ return false;
126
+ }
127
+ return true;
128
+ }
129
+ function cmp(a, b) {
130
+ return compareVersions(fmtPep440(a), fmtPep440(b), "PyPI");
131
+ }
132
+ function fmtPep440(v) {
133
+ const pre = v.pre ? `${["a", "b", "rc"][v.pre[0]]}${v.pre[1]}` : "";
134
+ return `${v.epoch ? `${v.epoch}!` : ""}${v.release.join(".")}${pre}${v.post !== null ? `.post${v.post}` : ""}${v.dev !== null ? `.dev${v.dev}` : ""}`;
135
+ }
136
+ /** `==X.Y.*`: same epoch and the release starts with X.Y (missing parts read as 0). */
137
+ function matchesPrefix(v, prefix) {
138
+ if (v.epoch !== prefix.epoch)
139
+ return false;
140
+ return prefix.release.every((n, i) => (v.release[i] ?? 0) === n);
141
+ }
142
+ /** Order two versions of one ecosystem. */
143
+ export function compareIn(ecosystem, a, b) {
144
+ return compareVersions(a, b, ecosystem);
145
+ }
146
+ export function isPrereleaseIn(ecosystem, version) {
147
+ if (ecosystem === "Go" && /\d{14}-[0-9a-f]{12}/.test(version))
148
+ return true; // pseudo-version
149
+ return isPrereleaseVersion(version, ecosystem);
150
+ }
151
+ /** MAJOR.MINOR.PATCH of a version in any of the four ecosystems (PEP 440 release parts; missing ones 0). */
152
+ export function versionParts(ecosystem, version) {
153
+ if (ecosystem === "PyPI") {
154
+ const p = parsePep440(version);
155
+ return p ? { major: p.release[0] ?? 0, minor: p.release[1] ?? 0, patch: p.release[2] ?? 0 } : null;
156
+ }
157
+ const p = parseSemverPrecedence(version.replace(/\+incompatible$/, ""));
158
+ return p ? { major: p.major, minor: p.minor, patch: p.patch } : null;
159
+ }
160
+ /**
161
+ * The release line a version belongs to, as a constraint: the same major (the
162
+ * same 0.minor below 1.0) on npm and crates.io; the same module major on Go,
163
+ * where v0 is one line (`go get -u` moves freely within a module path); the
164
+ * same X.Y series on PyPI, where many projects do not follow semver (Django
165
+ * 4.2 LTS, Jinja2 3.1).
166
+ */
167
+ export function lineOf(ecosystem, version) {
168
+ const p = versionParts(ecosystem, version);
169
+ if (!p)
170
+ return null;
171
+ if (ecosystem === "PyPI")
172
+ return `==${p.major}.${p.minor}.*`;
173
+ if (ecosystem === "Go")
174
+ return `v${p.major}`;
175
+ return p.major === 0 ? `0.${p.minor}.x` : `${p.major}.x`;
176
+ }
177
+ /**
178
+ * The releases a constraint admits, judged against every advisory OSV.dev
179
+ * lists for the package. Withdrawn (yanked / unpublished / retracted) and
180
+ * deprecated releases are not candidates; neither are prereleases, unless
181
+ * the constraint admits nothing else. Maintenance notices do not make a
182
+ * release unsafe.
183
+ */
184
+ export function checkRange(input) {
185
+ const { ecosystem, index, range, basis } = input;
186
+ const installable = Object.values(index.releases).filter((r) => !r.withdrawn && !r.deprecated).map((r) => r.version);
187
+ const admitted = installable.filter((v) => rangeAdmits(ecosystem, range, v) === true);
188
+ const stable = admitted.filter((v) => !isPrereleaseIn(ecosystem, v));
189
+ const candidates = (stable.length > 0 ? stable : admitted).sort((a, b) => compareIn(ecosystem, a, b));
190
+ const real = input.advisories.filter((a) => !a.maintenance);
191
+ const affecting = (v) => real.filter((a) => advisoryAffects(a, v, ecosystem)).map((a) => a.id);
192
+ const safe = candidates.filter((v) => affecting(v).length === 0);
193
+ const out = {
194
+ range,
195
+ basis,
196
+ releases_in_range: candidates.length,
197
+ vulnerable_in_range: candidates.length - safe.length,
198
+ smallest_safe_in_range: safe[0] ?? null,
199
+ newest_safe_in_range: safe[safe.length - 1] ?? null,
200
+ safe_versions: safe.slice(-8),
201
+ };
202
+ if (candidates.length > 0 && safe.length === 0) {
203
+ out.affecting_all = [...new Set(candidates.flatMap(affecting))].filter((id) => candidates.every((v) => affecting(v).includes(id))).slice(0, 10);
204
+ const outside = installable
205
+ .filter((v) => !isPrereleaseIn(ecosystem, v) && affecting(v).length === 0)
206
+ .sort((a, b) => compareIn(ecosystem, a, b));
207
+ out.newest_safe_outside_range = outside[outside.length - 1] ?? null;
208
+ }
209
+ if (input.from) {
210
+ out.smallest_safe_upgrade = safe.find((v) => compareIn(ecosystem, v, input.from) > 0) ?? null;
211
+ }
212
+ return out;
213
+ }
@@ -2,28 +2,43 @@
2
2
  * dependency_check tool
3
3
  *
4
4
  * A pre-flight verdict for ONE proposed change per item: adding a dependency
5
- * at a version, or bumping an installed one. An agent calls this before it
6
- * edits a manifest and gets `proceed | wait | review | avoid | unknown`, a
7
- * one-line reason, and the signals behind it, each with evidence.
5
+ * at a version, pinning one, or moving an installed one to another version.
6
+ * An agent calls this before it writes a version into a manifest and gets
7
+ * `proceed | wait | review | avoid | unknown`, a one-line reason, and the
8
+ * signals behind it, each with evidence. npm, crates.io, PyPI and Go.
9
+ *
10
+ * `to` is an exact version, "latest", or a constraint ("7.x", "^1.20",
11
+ * "~=3.1", "v1"). For a constraint the item also returns `range_check`: every
12
+ * release it admits judged against every advisory OSV.dev lists for the
13
+ * package, with the smallest and newest safe one, and the verdict is given
14
+ * for the version it recommends (the smallest safe upgrade above `from`, or
15
+ * for a new dependency the newest safe release). An exact `to` that an
16
+ * advisory affects gets the same `range_check` over its own release line, so
17
+ * the answer to "is 7.5.10 safe?" names 7.5.11.
8
18
  *
9
19
  * Signals (see dependency-check-signals.ts): known advisories on the target
10
20
  * (OSV), release age (hold releases < 3 days unless they fix an advisory on
11
- * the installed version), publish-trust drop, newly added install scripts
12
- * (npm), newly introduced runtime dependencies that are themselves < 30 days
13
- * old, yanked/deprecated/unpublished targets, and the upgrade type.
21
+ * the installed version), publish-trust drop and newly added install scripts
22
+ * (npm; trust also crates.io), newly introduced runtime dependencies that are
23
+ * themselves < 30 days old (npm, crates.io), yanked / deprecated / unpublished
24
+ * / retracted targets, and the upgrade type. A signal a registry cannot
25
+ * supply says "not applicable", never a made-up value.
14
26
  *
15
27
  * Honesty contract: a registry or OSV that cannot be reached makes the item
16
28
  * `unknown`, never `proceed`. A package missing from the public registry is
17
29
  * `unknown` with that said (private package, or a misspelled/hallucinated
18
30
  * name), never treated as clean.
19
31
  *
20
- * Privacy: registry requests carry the package name only — the installed and
21
- * target versions are picked out of the full release list locally. Only OSV
22
- * receives name + version, exactly as vulnerability_scan already does.
32
+ * Privacy: npm, crates.io and PyPI requests carry the package name only — the
33
+ * installed and target versions are picked out of the full release list
34
+ * locally. The Go proxy lists versions without dates, so the judged versions'
35
+ * `.info` is read from it. OSV receives name + version, as vulnerability_scan
36
+ * already does, and the package name for its advisory list.
23
37
  */
24
38
  import type { FourDADatabase } from "../db.js";
25
39
  import type { LiveIntelligence } from "../live/index.js";
26
40
  import { type Signal, type Verdict } from "./dependency-check-signals.js";
41
+ import { type RangeCheck } from "./dependency-check-versions.js";
27
42
  export interface DependencyCheckParams {
28
43
  items?: unknown;
29
44
  }
@@ -71,13 +86,14 @@ interface ItemResult {
71
86
  package: string;
72
87
  from: string | null;
73
88
  to: string;
74
- /** "latest" when the caller asked for it and `to` is what it resolved to. */
89
+ /** "latest" or the constraint, when the caller asked for one and `to` is what it resolved to. */
75
90
  to_requested?: string;
76
91
  verdict: Verdict;
77
92
  reason: string;
78
93
  signals: Signal[];
94
+ range_check?: RangeCheck;
79
95
  }
80
- export declare function executeDependencyCheck(_db: FourDADatabase, params: DependencyCheckParams, liveIntel: LiveIntelligence | null, opts?: {
96
+ export declare function executeDependencyCheck(_db: FourDADatabase | null, params: DependencyCheckParams, liveIntel: LiveIntelligence | null, opts?: {
81
97
  now?: Date;
82
98
  }): Promise<{
83
99
  checked_at: string;