@4da/mcp-server 5.0.3 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +667 -0
- package/README.md +66 -35
- package/dist/build-staleness.d.ts +35 -0
- package/dist/build-staleness.js +95 -0
- package/dist/db.d.ts +53 -2
- package/dist/db.js +242 -36
- package/dist/doctor.js +27 -7
- package/dist/embeddings.js +7 -1
- package/dist/freshness-markers.d.ts +34 -0
- package/dist/freshness-markers.js +74 -0
- package/dist/gitignore.d.ts +37 -0
- package/dist/gitignore.js +130 -0
- package/dist/index.js +127 -44
- package/dist/live/bun-lockfile.d.ts +26 -0
- package/dist/live/bun-lockfile.js +123 -0
- package/dist/live/cargo-platform.d.ts +38 -0
- package/dist/live/cargo-platform.js +178 -0
- package/dist/live/changelog-classify.d.ts +41 -0
- package/dist/live/changelog-classify.js +207 -0
- package/dist/live/changelog.d.ts +64 -0
- package/dist/live/changelog.js +318 -0
- package/dist/live/crates-registry.js +3 -1
- package/dist/live/crates-versions.d.ts +47 -0
- package/dist/live/crates-versions.js +201 -0
- package/dist/live/dependency-set.d.ts +13 -0
- package/dist/live/dependency-set.js +95 -0
- package/dist/live/dev-scope.d.ts +32 -0
- package/dist/live/dev-scope.js +102 -0
- package/dist/live/file-signature.d.ts +18 -0
- package/dist/live/file-signature.js +43 -0
- package/dist/live/go-registry.js +3 -1
- package/dist/live/hn-fetcher.js +9 -3
- package/dist/live/index.d.ts +105 -2
- package/dist/live/index.js +266 -139
- package/dist/live/install-state.d.ts +43 -0
- package/dist/live/install-state.js +150 -0
- package/dist/live/js-lockfile-readers.d.ts +46 -0
- package/dist/live/js-lockfile-readers.js +181 -0
- package/dist/live/lockfile-parsers-pygo.d.ts +60 -0
- package/dist/live/lockfile-parsers-pygo.js +274 -0
- package/dist/live/lockfile-parsers.d.ts +69 -0
- package/dist/live/lockfile-parsers.js +311 -0
- package/dist/live/lockfile-types.d.ts +34 -0
- package/dist/live/lockfile-types.js +42 -0
- package/dist/live/maintenance.d.ts +33 -0
- package/dist/live/maintenance.js +37 -0
- package/dist/live/npm-packument.d.ts +47 -0
- package/dist/live/npm-packument.js +150 -0
- package/dist/live/npm-registry.js +67 -53
- package/dist/live/osv-scanner.d.ts +31 -1
- package/dist/live/osv-scanner.js +119 -10
- package/dist/live/package-archive.d.ts +49 -0
- package/dist/live/package-archive.js +186 -0
- package/dist/live/pypi-registry.js +3 -1
- package/dist/live/registry-health.d.ts +16 -0
- package/dist/live/registry-health.js +74 -0
- package/dist/live/release-metadata.d.ts +67 -0
- package/dist/live/release-metadata.js +16 -0
- package/dist/live/resolution.d.ts +43 -0
- package/dist/live/resolution.js +62 -0
- package/dist/live/semver-precedence.d.ts +30 -0
- package/dist/live/semver-precedence.js +83 -0
- package/dist/live/severity-scope.d.ts +57 -0
- package/dist/live/severity-scope.js +89 -0
- package/dist/live/types.d.ts +83 -0
- package/dist/live/upgrade-sources.d.ts +71 -0
- package/dist/live/upgrade-sources.js +198 -0
- package/dist/live/version-compare.d.ts +39 -0
- package/dist/live/version-compare.js +130 -0
- package/dist/live/version-resolver.d.ts +14 -1
- package/dist/live/version-resolver.js +93 -303
- package/dist/native-bindings.d.ts +19 -0
- package/dist/native-bindings.js +39 -0
- package/dist/project-manifests.d.ts +40 -0
- package/dist/project-manifests.js +193 -0
- package/dist/project-scanner.js +38 -48
- package/dist/project-tree.d.ts +41 -0
- package/dist/project-tree.js +110 -0
- package/dist/prompts.d.ts +28 -0
- package/dist/prompts.js +54 -0
- package/dist/schema-registry.d.ts +41 -29
- package/dist/schema-registry.js +98 -105
- package/dist/server-instructions.d.ts +11 -0
- package/dist/server-instructions.js +36 -0
- package/dist/tool-args.d.ts +28 -0
- package/dist/tool-args.js +93 -0
- package/dist/tool-dispatch.d.ts +1 -1
- package/dist/tool-dispatch.js +42 -8
- package/dist/tools/app-plan.d.ts +92 -0
- package/dist/tools/app-plan.js +155 -0
- package/dist/tools/briefing-stored-advisories.d.ts +20 -0
- package/dist/tools/briefing-stored-advisories.js +58 -0
- package/dist/tools/briefing-task-scope.d.ts +73 -0
- package/dist/tools/briefing-task-scope.js +197 -0
- package/dist/tools/briefing-wisdom.d.ts +31 -0
- package/dist/tools/briefing-wisdom.js +148 -0
- package/dist/tools/decision-enforcement.d.ts +0 -5
- package/dist/tools/decision-enforcement.js +0 -4
- package/dist/tools/decision-memory.js +18 -66
- package/dist/tools/dependency-check-signals.d.ts +74 -0
- package/dist/tools/dependency-check-signals.js +264 -0
- package/dist/tools/dependency-check.d.ts +98 -0
- package/dist/tools/dependency-check.js +295 -0
- package/dist/tools/dependency-health.js +74 -21
- package/dist/tools/developer-dna.d.ts +3 -0
- package/dist/tools/developer-dna.js +16 -15
- package/dist/tools/ecosystem-pulse.d.ts +6 -2
- package/dist/tools/ecosystem-pulse.js +53 -14
- package/dist/tools/get-actionable-signals.d.ts +26 -4
- package/dist/tools/get-actionable-signals.js +214 -251
- package/dist/tools/get-context.d.ts +2 -0
- package/dist/tools/get-context.js +9 -1
- package/dist/tools/get-relevant-content.d.ts +3 -1
- package/dist/tools/get-relevant-content.js +34 -3
- package/dist/tools/index.d.ts +4 -2
- package/dist/tools/index.js +4 -2
- package/dist/tools/install-drift-notes.d.ts +51 -0
- package/dist/tools/install-drift-notes.js +172 -0
- package/dist/tools/knowledge-gap-citations.d.ts +55 -0
- package/dist/tools/knowledge-gap-citations.js +132 -0
- package/dist/tools/knowledge-gap-exposure.d.ts +72 -0
- package/dist/tools/knowledge-gap-exposure.js +160 -0
- package/dist/tools/knowledge-gap-grading.d.ts +91 -0
- package/dist/tools/knowledge-gap-grading.js +157 -0
- package/dist/tools/knowledge-gap-installs.d.ts +65 -0
- package/dist/tools/knowledge-gap-installs.js +134 -0
- package/dist/tools/knowledge-gap-ranges.d.ts +51 -0
- package/dist/tools/knowledge-gap-ranges.js +146 -0
- package/dist/tools/knowledge-gap-scope.d.ts +63 -0
- package/dist/tools/knowledge-gap-scope.js +118 -0
- package/dist/tools/knowledge-gaps.d.ts +12 -3
- package/dist/tools/knowledge-gaps.js +172 -121
- package/dist/tools/package-presence.d.ts +24 -0
- package/dist/tools/package-presence.js +35 -0
- package/dist/tools/record-feedback.d.ts +0 -1
- package/dist/tools/record-feedback.js +7 -8
- package/dist/tools/signal-classifier.d.ts +25 -0
- package/dist/tools/signal-classifier.js +40 -0
- package/dist/tools/upgrade-impact-callsites.d.ts +52 -0
- package/dist/tools/upgrade-impact-callsites.js +226 -0
- package/dist/tools/upgrade-impact-literals.d.ts +45 -0
- package/dist/tools/upgrade-impact-literals.js +118 -0
- package/dist/tools/upgrade-impact-report.d.ts +80 -0
- package/dist/tools/upgrade-impact-report.js +178 -0
- package/dist/tools/upgrade-impact.d.ts +84 -0
- package/dist/tools/upgrade-impact.js +305 -0
- package/dist/tools/upgrade-planner.d.ts +41 -8
- package/dist/tools/upgrade-planner.js +201 -53
- package/dist/tools/vulnerability-scan-concise.d.ts +72 -0
- package/dist/tools/vulnerability-scan-concise.js +97 -0
- package/dist/tools/vulnerability-scan-format.d.ts +137 -0
- package/dist/tools/vulnerability-scan-format.js +276 -0
- package/dist/tools/vulnerability-scan.d.ts +338 -18
- package/dist/tools/vulnerability-scan.js +84 -125
- package/dist/tools/what-should-i-know.d.ts +86 -19
- package/dist/tools/what-should-i-know.js +277 -283
- package/dist/types.d.ts +4 -1
- package/package.json +27 -30
- package/dist/schemas/agent-memory.json +0 -49
- package/dist/schemas/decision-enforcement.json +0 -22
- package/dist/schemas/decision-memory.json +0 -54
- package/dist/schemas/dependency-health.json +0 -27
- package/dist/schemas/developer-dna.json +0 -219
- package/dist/schemas/ecosystem-pulse.json +0 -17
- package/dist/schemas/get-actionable-signals.json +0 -29
- package/dist/schemas/get-context.json +0 -17
- package/dist/schemas/get-relevant-content.json +0 -26
- package/dist/schemas/knowledge-gaps.json +0 -14
- package/dist/schemas/record-feedback.json +0 -24
- package/dist/schemas/upgrade-planner.json +0 -22
- package/dist/schemas/vulnerability-scan.json +0 -26
- package/dist/schemas/what-should-i-know.json +0 -19
|
@@ -4,11 +4,14 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Extracts exact dependency versions from lock files.
|
|
6
6
|
* Priority: lock file (exact) > manifest (range/specifier).
|
|
7
|
-
* Uses only Node.js built-ins — no external parsers.
|
|
7
|
+
* Uses only Node.js built-ins — no external parsers. The per-ecosystem
|
|
8
|
+
* parsers live in lockfile-parsers.ts, which also reports which file each
|
|
9
|
+
* resolution read from.
|
|
8
10
|
*/
|
|
9
|
-
import * as fs from "node:fs";
|
|
10
|
-
import * as path from "node:path";
|
|
11
11
|
import { targetActiveOnHost } from "./platform.js";
|
|
12
|
+
import { activeCratesForHost, hostTriple } from "./cargo-platform.js";
|
|
13
|
+
import { resolveVersionSource } from "./lockfile-parsers.js";
|
|
14
|
+
import { normalizePythonName } from "./lockfile-parsers-pygo.js";
|
|
12
15
|
// Alias -> OSV ecosystem. Mirrors the desktop app's canonical
|
|
13
16
|
// `Ecosystem::parse` (src-tauri/src/ecosystem.rs) so both sides recognize the
|
|
14
17
|
// SAME aliases — they previously drifted (the Rust side knew csharp/dart/php
|
|
@@ -62,12 +65,35 @@ const ECOSYSTEM_MAP = {
|
|
|
62
65
|
export function mapEcosystem(language) {
|
|
63
66
|
return ECOSYSTEM_MAP[language.trim().toLowerCase()] || "npm";
|
|
64
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* crates.io treats `-` and `_` as one namespace (you cannot publish both
|
|
70
|
+
* `http-body-util` and `http_body_util`), but the two spellings reach this
|
|
71
|
+
* resolver from different sources: Cargo.lock records the publisher's canonical
|
|
72
|
+
* name while ACE's import scraper reports the IMPORT identifier, which is
|
|
73
|
+
* always underscored. An exact-match lookup then fails for the import spelling
|
|
74
|
+
* and the same crate surfaces twice — once versioned, once `version: null`
|
|
75
|
+
* (observed live 2026-08-30: http_body_util, async_trait, tower_http,
|
|
76
|
+
* ed25519_dalek, ts_rs, proc_macro2, victauri_test, all null beside their
|
|
77
|
+
* hyphenated versioned twins). Resolve via the spelling the lock file actually
|
|
78
|
+
* uses and RETURN that spelling, so downstream dedupe merges the variants.
|
|
79
|
+
*/
|
|
80
|
+
function canonicalCrateName(name, versionMap) {
|
|
81
|
+
if (versionMap.has(name))
|
|
82
|
+
return name;
|
|
83
|
+
const swapped = name.includes("_") ? name.replace(/_/g, "-") : name.replace(/-/g, "_");
|
|
84
|
+
return versionMap.has(swapped) ? swapped : name;
|
|
85
|
+
}
|
|
65
86
|
export function resolveVersions(cwd, deps, devDeps, language, targets = {}) {
|
|
66
87
|
const ecosystem = mapEcosystem(language);
|
|
88
|
+
const versionMap = resolveVersionSource(cwd, ecosystem).versions;
|
|
89
|
+
return resolveVersionsFrom(cwd, deps, devDeps, ecosystem, versionMap, targets);
|
|
90
|
+
}
|
|
91
|
+
/** `resolveVersions` over a version map the caller already read from `cwd`. */
|
|
92
|
+
export function resolveVersionsFrom(cwd, deps, devDeps, ecosystem, versionMap, targets = {}) {
|
|
67
93
|
const results = [];
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
const target = targets[name] ?? null;
|
|
94
|
+
const build = (rawName, isDev) => {
|
|
95
|
+
const name = ecosystem === "crates.io" ? canonicalCrateName(rawName, versionMap) : rawName;
|
|
96
|
+
const target = targets[rawName] ?? targets[name] ?? null;
|
|
71
97
|
return {
|
|
72
98
|
name,
|
|
73
99
|
version: versionMap.get(normalizePackageName(name, ecosystem)) || null,
|
|
@@ -77,6 +103,9 @@ export function resolveVersions(cwd, deps, devDeps, language, targets = {}) {
|
|
|
77
103
|
devScopeKnown: true,
|
|
78
104
|
target,
|
|
79
105
|
platformActive: targetActiveOnHost(target),
|
|
106
|
+
// Provenance recorded at the point of resolution: this version came from
|
|
107
|
+
// THIS manifest directory's lock file, not from "the project" generally.
|
|
108
|
+
sourceDirs: [cwd],
|
|
80
109
|
};
|
|
81
110
|
};
|
|
82
111
|
for (const name of deps)
|
|
@@ -91,27 +120,71 @@ export function resolveVersions(cwd, deps, devDeps, language, targets = {}) {
|
|
|
91
120
|
*/
|
|
92
121
|
export function resolveAuditVersions(cwd, deps, devDeps, language, targets = {}) {
|
|
93
122
|
const ecosystem = mapEcosystem(language);
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
123
|
+
const read = resolveVersionSource(cwd, ecosystem);
|
|
124
|
+
return resolveAuditVersionsFrom(cwd, deps, devDeps, ecosystem, read.versions, targets, undefined, read.instances);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* `resolveAuditVersions` over a version map the caller already read. `direct`
|
|
128
|
+
* lets the caller pass direct entries it has already enriched (install state)
|
|
129
|
+
* so the audit set carries the same objects' context. `instances` is every
|
|
130
|
+
* installed copy the lockfile holds; without it the version map (one copy per
|
|
131
|
+
* name) is all there is.
|
|
132
|
+
*/
|
|
133
|
+
export function resolveAuditVersionsFrom(cwd, deps, devDeps, ecosystem, versionMap, targets = {}, direct = resolveVersionsFrom(cwd, deps, devDeps, ecosystem, versionMap, targets), instances = [...versionMap].map(([name, version]) => ({ name, version }))) {
|
|
134
|
+
// Canonicalize the same way resolveVersions does, so a dep declared under its
|
|
135
|
+
// import spelling still marks the lock file's canonical entry as direct.
|
|
136
|
+
const canonical = (name) => normalizePackageName(ecosystem === "crates.io" ? canonicalCrateName(name, versionMap) : name, ecosystem);
|
|
137
|
+
const directNames = new Set(deps.map(canonical));
|
|
138
|
+
const devNames = new Set(devDeps.map(canonical));
|
|
98
139
|
const seen = new Set(direct.map((dep) => dependencyKey(dep)));
|
|
99
140
|
const results = [...direct];
|
|
100
|
-
|
|
141
|
+
// Cargo.lock is target-agnostic, so a Windows lockfile still lists the whole
|
|
142
|
+
// Linux GTK3 stack Tauri pulls in. `[target.'cfg(...)']` parsing only ever
|
|
143
|
+
// covered DIRECT gated deps, and that cluster is entirely transitive — which
|
|
144
|
+
// is why nine unreachable crates were reported while the scan claimed to be
|
|
145
|
+
// platform-filtered. Ask cargo to resolve the graph for this host instead.
|
|
146
|
+
// `null` means cargo could not answer; every crate then stays active, because
|
|
147
|
+
// "unknown" must never silently hide a real advisory.
|
|
148
|
+
//
|
|
149
|
+
// THE SHARED PREDICATE — keep in step with the app's
|
|
150
|
+
// `src-tauri/src/platform_filter.rs`, which documents it in full:
|
|
151
|
+
//
|
|
152
|
+
// a crate that is not RESOLVED FOR THE HOST is platform-inactive
|
|
153
|
+
//
|
|
154
|
+
// Two independent facts can make that true, and both are recorded because
|
|
155
|
+
// the copy differs: (1) the manifest gates it behind a `cfg(...)` for a
|
|
156
|
+
// target this machine is not — `targetActiveOnHost(declaredTarget)`, the
|
|
157
|
+
// app's `project_dependencies.target_cfg`; (2) cargo builds it for no
|
|
158
|
+
// target/feature combination here — `builtOnHost`, the app's
|
|
159
|
+
// `user_dependencies.target_cfg = 'lockfile-only'` (schema 122). Neither
|
|
160
|
+
// fact alone is the whole picture: (1) covers only DIRECT deps, and (2) is
|
|
161
|
+
// the only one that can see a transitive.
|
|
162
|
+
const hostCrates = ecosystem === "crates.io" ? activeCratesForHost(cwd) : null;
|
|
163
|
+
const triple = hostCrates ? hostTriple() : null;
|
|
164
|
+
for (const { name, version, dev } of instances) {
|
|
101
165
|
const normalized = normalizePackageName(name, ecosystem);
|
|
102
|
-
|
|
103
|
-
//
|
|
104
|
-
|
|
105
|
-
const
|
|
166
|
+
// Direct only for the copy a manifest dependency resolves to; another
|
|
167
|
+
// installed version of the same name is somebody else's transitive.
|
|
168
|
+
const isDirect = (directNames.has(normalized) || devNames.has(normalized)) && versionMap.get(name) === version;
|
|
169
|
+
const declaredTarget = targets[name] ?? null;
|
|
170
|
+
const builtOnHost = hostCrates ? hostCrates.has(name) : true;
|
|
171
|
+
// Prefer the manifest's own cfg() spec when it has one — it is the more
|
|
172
|
+
// precise, human-readable explanation. Fall back to naming the triple the
|
|
173
|
+
// crate is absent from.
|
|
174
|
+
const target = declaredTarget ?? (builtOnHost || !triple ? null : `not built for ${triple}`);
|
|
106
175
|
const candidate = {
|
|
107
176
|
name,
|
|
108
177
|
version,
|
|
109
178
|
ecosystem,
|
|
110
|
-
|
|
179
|
+
// A direct dependency's scope is its manifest section; a transitive's is
|
|
180
|
+
// whatever the lockfile recorded for that copy (npm `dev`, pnpm 5/6
|
|
181
|
+
// `dev`, Pipfile `develop`, poetry `category`), else unknown.
|
|
182
|
+
isDev: isDirect ? devNames.has(normalized) : dev === true,
|
|
111
183
|
isDirect,
|
|
112
|
-
devScopeKnown: isDirect,
|
|
184
|
+
devScopeKnown: isDirect || dev !== undefined,
|
|
113
185
|
target,
|
|
114
|
-
platformActive: targetActiveOnHost(
|
|
186
|
+
platformActive: targetActiveOnHost(declaredTarget) && builtOnHost,
|
|
187
|
+
sourceDirs: [cwd],
|
|
115
188
|
};
|
|
116
189
|
const key = dependencyKey(candidate);
|
|
117
190
|
if (!seen.has(key)) {
|
|
@@ -121,293 +194,10 @@ export function resolveAuditVersions(cwd, deps, devDeps, language, targets = {})
|
|
|
121
194
|
}
|
|
122
195
|
return results;
|
|
123
196
|
}
|
|
124
|
-
function resolveVersionMap(cwd, ecosystem) {
|
|
125
|
-
const resolvers = {
|
|
126
|
-
npm: () => resolveNpm(cwd),
|
|
127
|
-
"crates.io": () => resolveRust(cwd),
|
|
128
|
-
PyPI: () => resolvePython(cwd),
|
|
129
|
-
Go: () => resolveGo(cwd),
|
|
130
|
-
};
|
|
131
|
-
return resolvers[ecosystem]?.() || new Map();
|
|
132
|
-
}
|
|
133
197
|
function normalizePackageName(name, ecosystem) {
|
|
134
|
-
|
|
198
|
+
// PEP 503: the lockfile readers store Python names normalised the same way.
|
|
199
|
+
return ecosystem === "PyPI" ? normalizePythonName(name) : name;
|
|
135
200
|
}
|
|
136
201
|
function dependencyKey(dep) {
|
|
137
202
|
return `${dep.ecosystem}\0${normalizePackageName(dep.name, dep.ecosystem)}\0${dep.version ?? ""}`;
|
|
138
203
|
}
|
|
139
|
-
// =============================================================================
|
|
140
|
-
// npm: package-lock.json > pnpm-lock.yaml > yarn.lock > package.json ranges
|
|
141
|
-
// =============================================================================
|
|
142
|
-
function resolveNpm(cwd) {
|
|
143
|
-
const versions = new Map();
|
|
144
|
-
// Try package-lock.json first (npm)
|
|
145
|
-
const lockPath = path.join(cwd, "package-lock.json");
|
|
146
|
-
if (fs.existsSync(lockPath)) {
|
|
147
|
-
try {
|
|
148
|
-
const lock = JSON.parse(fs.readFileSync(lockPath, "utf-8"));
|
|
149
|
-
// v2/v3 format: packages["node_modules/name"].version
|
|
150
|
-
if (lock.packages) {
|
|
151
|
-
for (const [key, value] of Object.entries(lock.packages)) {
|
|
152
|
-
const name = packageNameFromNodeModulesPath(key);
|
|
153
|
-
if (name && value.version) {
|
|
154
|
-
versions.set(name, value.version);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
// v1 format fallback: dependencies.name.version
|
|
159
|
-
if (versions.size === 0 && lock.dependencies) {
|
|
160
|
-
for (const [name, value] of Object.entries(lock.dependencies)) {
|
|
161
|
-
if (value.version) {
|
|
162
|
-
versions.set(name, value.version);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
if (versions.size > 0)
|
|
167
|
-
return versions;
|
|
168
|
-
}
|
|
169
|
-
catch { /* fall through */ }
|
|
170
|
-
}
|
|
171
|
-
// Try pnpm-lock.yaml (line-by-line, no YAML parser)
|
|
172
|
-
const pnpmLockPath = path.join(cwd, "pnpm-lock.yaml");
|
|
173
|
-
if (fs.existsSync(pnpmLockPath)) {
|
|
174
|
-
try {
|
|
175
|
-
const content = fs.readFileSync(pnpmLockPath, "utf-8");
|
|
176
|
-
// pnpm v9 importers format: dependency name on one line, version on the next
|
|
177
|
-
// Example:
|
|
178
|
-
// '@modelcontextprotocol/sdk':
|
|
179
|
-
// specifier: ^1.10.0
|
|
180
|
-
// version: 1.26.0(zod@4.3.6)
|
|
181
|
-
const lines = content.split("\n");
|
|
182
|
-
let currentPkg = null;
|
|
183
|
-
for (const line of lines) {
|
|
184
|
-
// Match dependency name (with or without quotes, scoped packages)
|
|
185
|
-
const pkgMatch = line.match(/^\s{4,8}'?(@?[^':]+)'?:\s*$/);
|
|
186
|
-
if (pkgMatch) {
|
|
187
|
-
currentPkg = pkgMatch[1].trim();
|
|
188
|
-
continue;
|
|
189
|
-
}
|
|
190
|
-
// Match version line under a dependency
|
|
191
|
-
if (currentPkg) {
|
|
192
|
-
const versionMatch = line.match(/^\s+version:\s+['"]?(\d+\.\d+[^('"\s]*)['"]?/);
|
|
193
|
-
if (versionMatch) {
|
|
194
|
-
versions.set(currentPkg, versionMatch[1]);
|
|
195
|
-
currentPkg = null;
|
|
196
|
-
continue;
|
|
197
|
-
}
|
|
198
|
-
// specifier line — skip, wait for version
|
|
199
|
-
if (line.match(/^\s+specifier:/))
|
|
200
|
-
continue;
|
|
201
|
-
// Any other line resets current package context
|
|
202
|
-
if (!line.match(/^\s/))
|
|
203
|
-
currentPkg = null;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
// Also parse package/snapshot keys. Requiring a numeric version after
|
|
207
|
-
// the separator handles scoped packages without splitting at their
|
|
208
|
-
// leading `@` (for example '@scope/pkg@1.2.3').
|
|
209
|
-
const pkgRegex = /^\s{2}'?(.+?)@(\d+[^:('"]*)[^:]*'?:\s*$/gm;
|
|
210
|
-
let match;
|
|
211
|
-
while ((match = pkgRegex.exec(content)) !== null) {
|
|
212
|
-
const name = match[1].replace(/^\//, "").trim();
|
|
213
|
-
const version = match[2].trim().replace(/['":]/g, "");
|
|
214
|
-
if (name && version && /^\d/.test(version) && !versions.has(name)) {
|
|
215
|
-
versions.set(name, version);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
if (versions.size > 0)
|
|
219
|
-
return versions;
|
|
220
|
-
}
|
|
221
|
-
catch { /* fall through */ }
|
|
222
|
-
}
|
|
223
|
-
// Try yarn.lock
|
|
224
|
-
const yarnLockPath = path.join(cwd, "yarn.lock");
|
|
225
|
-
if (fs.existsSync(yarnLockPath)) {
|
|
226
|
-
try {
|
|
227
|
-
const content = fs.readFileSync(yarnLockPath, "utf-8");
|
|
228
|
-
// Format: "name@range":\n version "x.y.z"
|
|
229
|
-
const blocks = content.split(/\n(?=\S)/);
|
|
230
|
-
for (const block of blocks) {
|
|
231
|
-
const headerMatch = block.match(/^"?([^@\s"]+)@/);
|
|
232
|
-
const versionMatch = block.match(/^\s+version\s+"([^"]+)"/m);
|
|
233
|
-
if (headerMatch && versionMatch) {
|
|
234
|
-
versions.set(headerMatch[1], versionMatch[1]);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
if (versions.size > 0)
|
|
238
|
-
return versions;
|
|
239
|
-
}
|
|
240
|
-
catch { /* fall through */ }
|
|
241
|
-
}
|
|
242
|
-
// Fallback: extract version ranges from package.json
|
|
243
|
-
const pkgPath = path.join(cwd, "package.json");
|
|
244
|
-
if (fs.existsSync(pkgPath)) {
|
|
245
|
-
try {
|
|
246
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
247
|
-
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
248
|
-
for (const [name, spec] of Object.entries(allDeps)) {
|
|
249
|
-
const version = extractVersionFromSpec(spec);
|
|
250
|
-
if (version)
|
|
251
|
-
versions.set(name, version);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
catch { /* skip */ }
|
|
255
|
-
}
|
|
256
|
-
return versions;
|
|
257
|
-
}
|
|
258
|
-
function packageNameFromNodeModulesPath(lockPath) {
|
|
259
|
-
const marker = "node_modules/";
|
|
260
|
-
const index = lockPath.lastIndexOf(marker);
|
|
261
|
-
return index === -1 ? "" : lockPath.slice(index + marker.length);
|
|
262
|
-
}
|
|
263
|
-
// =============================================================================
|
|
264
|
-
// Rust: Cargo.lock
|
|
265
|
-
// =============================================================================
|
|
266
|
-
function resolveRust(cwd) {
|
|
267
|
-
const versions = new Map();
|
|
268
|
-
const lockPath = path.join(cwd, "Cargo.lock");
|
|
269
|
-
if (!fs.existsSync(lockPath)) {
|
|
270
|
-
// Fallback: parse Cargo.toml version strings
|
|
271
|
-
return resolveRustFromManifest(cwd);
|
|
272
|
-
}
|
|
273
|
-
try {
|
|
274
|
-
const content = fs.readFileSync(lockPath, "utf-8");
|
|
275
|
-
// [[package]]\nname = "serde"\nversion = "1.0.193"
|
|
276
|
-
const packageRegex = /\[\[package\]\]\s*\nname\s*=\s*"([^"]+)"\s*\nversion\s*=\s*"([^"]+)"/g;
|
|
277
|
-
let match;
|
|
278
|
-
while ((match = packageRegex.exec(content)) !== null) {
|
|
279
|
-
versions.set(match[1], match[2]);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
catch { /* skip */ }
|
|
283
|
-
return versions;
|
|
284
|
-
}
|
|
285
|
-
function resolveRustFromManifest(cwd) {
|
|
286
|
-
const versions = new Map();
|
|
287
|
-
const cargoPath = path.join(cwd, "Cargo.toml");
|
|
288
|
-
if (!fs.existsSync(cargoPath))
|
|
289
|
-
return versions;
|
|
290
|
-
try {
|
|
291
|
-
const content = fs.readFileSync(cargoPath, "utf-8");
|
|
292
|
-
// name = "1.0" or name = { version = "1.0", ... }
|
|
293
|
-
const depRegex = /^([a-zA-Z_][\w-]*)\s*=\s*(?:"([^"]+)"|.*?version\s*=\s*"([^"]+)")/gm;
|
|
294
|
-
let match;
|
|
295
|
-
while ((match = depRegex.exec(content)) !== null) {
|
|
296
|
-
const name = match[1];
|
|
297
|
-
const version = match[2] || match[3];
|
|
298
|
-
if (version && /^\d/.test(version)) {
|
|
299
|
-
versions.set(name, version);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
catch { /* skip */ }
|
|
304
|
-
return versions;
|
|
305
|
-
}
|
|
306
|
-
// =============================================================================
|
|
307
|
-
// Python: poetry.lock > Pipfile.lock > requirements.txt
|
|
308
|
-
// =============================================================================
|
|
309
|
-
function resolvePython(cwd) {
|
|
310
|
-
const versions = new Map();
|
|
311
|
-
// poetry.lock
|
|
312
|
-
const poetryLock = path.join(cwd, "poetry.lock");
|
|
313
|
-
if (fs.existsSync(poetryLock)) {
|
|
314
|
-
try {
|
|
315
|
-
const content = fs.readFileSync(poetryLock, "utf-8");
|
|
316
|
-
const packageRegex = /\[\[package\]\]\s*\nname\s*=\s*"([^"]+)"\s*\nversion\s*=\s*"([^"]+)"/g;
|
|
317
|
-
let match;
|
|
318
|
-
while ((match = packageRegex.exec(content)) !== null) {
|
|
319
|
-
versions.set(match[1].toLowerCase(), match[2]);
|
|
320
|
-
}
|
|
321
|
-
if (versions.size > 0)
|
|
322
|
-
return versions;
|
|
323
|
-
}
|
|
324
|
-
catch { /* fall through */ }
|
|
325
|
-
}
|
|
326
|
-
// Pipfile.lock
|
|
327
|
-
const pipfileLock = path.join(cwd, "Pipfile.lock");
|
|
328
|
-
if (fs.existsSync(pipfileLock)) {
|
|
329
|
-
try {
|
|
330
|
-
const lock = JSON.parse(fs.readFileSync(pipfileLock, "utf-8"));
|
|
331
|
-
const sections = [lock.default, lock.develop].filter(Boolean);
|
|
332
|
-
for (const section of sections) {
|
|
333
|
-
for (const [name, info] of Object.entries(section)) {
|
|
334
|
-
const version = info.version;
|
|
335
|
-
if (version) {
|
|
336
|
-
versions.set(name.toLowerCase(), version.replace(/^==/, ""));
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
if (versions.size > 0)
|
|
341
|
-
return versions;
|
|
342
|
-
}
|
|
343
|
-
catch { /* fall through */ }
|
|
344
|
-
}
|
|
345
|
-
// requirements.txt pinned versions
|
|
346
|
-
const reqPath = path.join(cwd, "requirements.txt");
|
|
347
|
-
if (fs.existsSync(reqPath)) {
|
|
348
|
-
try {
|
|
349
|
-
const content = fs.readFileSync(reqPath, "utf-8");
|
|
350
|
-
for (const line of content.split("\n")) {
|
|
351
|
-
const match = line.trim().match(/^([a-zA-Z0-9_-]+)\s*==\s*(.+)/);
|
|
352
|
-
if (match) {
|
|
353
|
-
versions.set(match[1].toLowerCase(), match[2].trim());
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
catch { /* skip */ }
|
|
358
|
-
}
|
|
359
|
-
return versions;
|
|
360
|
-
}
|
|
361
|
-
// =============================================================================
|
|
362
|
-
// Go: go.sum > go.mod
|
|
363
|
-
// =============================================================================
|
|
364
|
-
function resolveGo(cwd) {
|
|
365
|
-
const versions = new Map();
|
|
366
|
-
// go.sum has exact versions
|
|
367
|
-
const goSumPath = path.join(cwd, "go.sum");
|
|
368
|
-
if (fs.existsSync(goSumPath)) {
|
|
369
|
-
try {
|
|
370
|
-
const content = fs.readFileSync(goSumPath, "utf-8");
|
|
371
|
-
for (const line of content.split("\n")) {
|
|
372
|
-
const match = line.match(/^(\S+)\s+(v[\d.]+)/);
|
|
373
|
-
if (match) {
|
|
374
|
-
versions.set(match[1], match[2]);
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
if (versions.size > 0)
|
|
378
|
-
return versions;
|
|
379
|
-
}
|
|
380
|
-
catch { /* fall through */ }
|
|
381
|
-
}
|
|
382
|
-
// Fallback: go.mod require block
|
|
383
|
-
const goModPath = path.join(cwd, "go.mod");
|
|
384
|
-
if (fs.existsSync(goModPath)) {
|
|
385
|
-
try {
|
|
386
|
-
const content = fs.readFileSync(goModPath, "utf-8");
|
|
387
|
-
const requireMatch = content.match(/require\s*\(([\s\S]*?)\)/);
|
|
388
|
-
if (requireMatch) {
|
|
389
|
-
for (const line of requireMatch[1].split("\n")) {
|
|
390
|
-
const match = line.trim().match(/^(\S+)\s+(v[\d.]+)/);
|
|
391
|
-
if (match) {
|
|
392
|
-
versions.set(match[1], match[2]);
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
catch { /* skip */ }
|
|
398
|
-
}
|
|
399
|
-
return versions;
|
|
400
|
-
}
|
|
401
|
-
// =============================================================================
|
|
402
|
-
// Helpers
|
|
403
|
-
// =============================================================================
|
|
404
|
-
/**
|
|
405
|
-
* Extract a usable version from an npm version specifier.
|
|
406
|
-
* "^4.18.2" -> "4.18.2", "~1.0.0" -> "1.0.0", ">=2.0.0" -> "2.0.0"
|
|
407
|
-
*/
|
|
408
|
-
function extractVersionFromSpec(spec) {
|
|
409
|
-
if (!spec)
|
|
410
|
-
return null;
|
|
411
|
-
const match = spec.match(/(\d+\.\d+\.\d+(?:-[\w.]+)?)/);
|
|
412
|
-
return match ? match[1] : null;
|
|
413
|
-
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* better-sqlite3's native module: a real check and an actionable fix.
|
|
3
|
+
*
|
|
4
|
+
* `import("better-sqlite3")` succeeds even when the compiled module is
|
|
5
|
+
* missing; the failure comes at the first `new Database()`. Measured
|
|
6
|
+
* 2026-10-03 on node:24-bookworm-slim with npm 12.2.0, which blocks
|
|
7
|
+
* dependency install scripts not covered by `allowScripts`: `npm install`
|
|
8
|
+
* (local or -g) left no better_sqlite3.node, `--doctor` still reported the
|
|
9
|
+
* bindings as loaded, and every tool failed with "Could not locate the
|
|
10
|
+
* bindings file" and no hint why. `npx @4da/mcp-server` was unaffected.
|
|
11
|
+
*/
|
|
12
|
+
import type BetterSqlite3 from "better-sqlite3";
|
|
13
|
+
export declare const NATIVE_BINDING_FIX: string;
|
|
14
|
+
/** Errors that mean the compiled module is absent or built for another Node/platform. */
|
|
15
|
+
export declare function isNativeBindingError(err: unknown): boolean;
|
|
16
|
+
/** The first line of a native-binding error, with the fix appended. */
|
|
17
|
+
export declare function nativeBindingMessage(err: unknown): string;
|
|
18
|
+
/** Null when an in-memory database opens; otherwise why not, with the fix. */
|
|
19
|
+
export declare function checkNativeBindings(Database: typeof BetterSqlite3): string | null;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/**
|
|
3
|
+
* better-sqlite3's native module: a real check and an actionable fix.
|
|
4
|
+
*
|
|
5
|
+
* `import("better-sqlite3")` succeeds even when the compiled module is
|
|
6
|
+
* missing; the failure comes at the first `new Database()`. Measured
|
|
7
|
+
* 2026-10-03 on node:24-bookworm-slim with npm 12.2.0, which blocks
|
|
8
|
+
* dependency install scripts not covered by `allowScripts`: `npm install`
|
|
9
|
+
* (local or -g) left no better_sqlite3.node, `--doctor` still reported the
|
|
10
|
+
* bindings as loaded, and every tool failed with "Could not locate the
|
|
11
|
+
* bindings file" and no hint why. `npx @4da/mcp-server` was unaffected.
|
|
12
|
+
*/
|
|
13
|
+
// Each remedy measured on npm 12.2.0 (node:24-bookworm-slim), 2026-10-03.
|
|
14
|
+
export const NATIVE_BINDING_FIX = "npm 12+ blocks dependency install scripts unless allowed. With npx: allow it once with " +
|
|
15
|
+
"`npm config set allow-scripts=better-sqlite3 --location=user` (or run `npx --allow-scripts=better-sqlite3 @4da/mcp-server`), " +
|
|
16
|
+
"then clear the npx cache (`npx clear-npx-cache`, or delete the _npx folder in your npm cache). " +
|
|
17
|
+
"In an npm install: `npm install-scripts approve better-sqlite3`, then `npm rebuild better-sqlite3`. " +
|
|
18
|
+
"Without a prebuilt binary for your platform the rebuild needs a C++ toolchain " +
|
|
19
|
+
"(macOS: xcode-select --install; Debian/Ubuntu: apt install build-essential python3; Windows: Visual Studio Build Tools).";
|
|
20
|
+
/** Errors that mean the compiled module is absent or built for another Node/platform. */
|
|
21
|
+
export function isNativeBindingError(err) {
|
|
22
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
23
|
+
return /bindings file|better_sqlite3\.node|NODE_MODULE_VERSION|compiled against a different Node\.js version|invalid ELF header|not a valid Win32 application/i.test(msg);
|
|
24
|
+
}
|
|
25
|
+
/** The first line of a native-binding error, with the fix appended. */
|
|
26
|
+
export function nativeBindingMessage(err) {
|
|
27
|
+
const first = (err instanceof Error ? err.message : String(err)).split("\n")[0].replace(/\s*Tried:\s*$/, "").trim();
|
|
28
|
+
return `better-sqlite3's native module is not usable (${first}). ${NATIVE_BINDING_FIX}`;
|
|
29
|
+
}
|
|
30
|
+
/** Null when an in-memory database opens; otherwise why not, with the fix. */
|
|
31
|
+
export function checkNativeBindings(Database) {
|
|
32
|
+
try {
|
|
33
|
+
new Database(":memory:").close();
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
return nativeBindingMessage(err);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Direct-dependency names from Python, Go and Cargo-workspace manifests, for
|
|
3
|
+
* the project scanner (project-scanner.ts keeps npm and single-crate Cargo).
|
|
4
|
+
*
|
|
5
|
+
* Measured 2026-10-02, each gap below hid a whole project from the scan:
|
|
6
|
+
* - Poetry projects declare dependencies under `[tool.poetry.dependencies]`;
|
|
7
|
+
* the scanner only looked for `dependencies = [`, found nothing, and never
|
|
8
|
+
* created a Python group — poetry 1.0's own lockfile (131 advisories per
|
|
9
|
+
* osv-scanner) scanned as zero dependencies.
|
|
10
|
+
* - A Cargo virtual workspace root (`[workspace]`, no `[package]`) declares
|
|
11
|
+
* its dependencies in `[workspace.dependencies]` and in member manifests;
|
|
12
|
+
* victauri's whole Cargo.lock (10 advisories per cargo audit) was ignored.
|
|
13
|
+
* - go.mod `// indirect` requirements are transitive, not direct.
|
|
14
|
+
*/
|
|
15
|
+
/** The body of a TOML table (`[name]`) up to the next table header, or null. */
|
|
16
|
+
export declare function tomlTable(content: string, name: string): string | null;
|
|
17
|
+
/**
|
|
18
|
+
* Direct Python dependencies from pyproject.toml: PEP 621 `[project]`
|
|
19
|
+
* `dependencies`, `[project.optional-dependencies]` and PEP 735
|
|
20
|
+
* `[dependency-groups]` (dev), Poetry's `[tool.poetry.dependencies]` and its
|
|
21
|
+
* dev groups, and uv's `[tool.uv] dev-dependencies`. Names are PEP 503 normalised.
|
|
22
|
+
*/
|
|
23
|
+
export declare function parsePyprojectDependencies(content: string): {
|
|
24
|
+
deps: string[];
|
|
25
|
+
devDeps: string[];
|
|
26
|
+
};
|
|
27
|
+
/** Requirement names (pins and ranges alike) from a requirements file, PEP 503 normalised. */
|
|
28
|
+
export declare function parseRequirementNames(content: string): string[];
|
|
29
|
+
/** Direct Go dependencies: go.mod requirements not marked `// indirect`. */
|
|
30
|
+
export declare function parseGoModDirectDeps(content: string): string[];
|
|
31
|
+
/**
|
|
32
|
+
* Dependencies of a Cargo workspace root: `[workspace.dependencies]` plus the
|
|
33
|
+
* `[dependencies]`/`[dev-dependencies]`/`[build-dependencies]` of every member
|
|
34
|
+
* (`members = ["crates/*", "app"]`, one-level `*` globs). Member crates
|
|
35
|
+
* themselves are excluded: they are this project's own code.
|
|
36
|
+
*/
|
|
37
|
+
export declare function cargoWorkspaceDeps(root: string, content: string): {
|
|
38
|
+
deps: string[];
|
|
39
|
+
devDeps: string[];
|
|
40
|
+
};
|