sparda-mcp 0.69.1 → 0.70.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/package.json +5 -4
- package/src/ubg/extract.js +30 -0
- package/src/ubg/resolve.js +18 -3
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sparda-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.70.0",
|
|
4
4
|
"mcpName": "io.github.zyx77550/sparda-mcp",
|
|
5
|
-
"description": "AI writes. SPARDA proves. A deterministic, offline gate that catches when an AI edit removes a guard, exposes a route, or breaks an invariant
|
|
5
|
+
"description": "AI writes. SPARDA proves. A deterministic, offline gate that catches when an AI edit removes a guard, exposes a route, or breaks an invariant \u2014 no API key, right in the agent edit loop.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"scripts": {
|
|
19
19
|
"test": "vitest run",
|
|
20
|
-
"prepublishOnly": "
|
|
20
|
+
"prepublishOnly": "node scripts/release-gate.mjs",
|
|
21
21
|
"corpus": "node scripts/corpus-oracle.mjs",
|
|
22
22
|
"corpus:update": "node scripts/corpus-oracle.mjs --update",
|
|
23
23
|
"test:router": "node tests/router-selftest.cjs",
|
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
"format:check": "prettier --check \"**/*.{js,cjs,mjs}\"",
|
|
30
30
|
"bench:check": "node bench/check-readme.mjs",
|
|
31
31
|
"mutation": "node tests/mutation/run.mjs",
|
|
32
|
-
"wedge": "node bench/wedge.mjs"
|
|
32
|
+
"wedge": "node bench/wedge.mjs",
|
|
33
|
+
"release:check": "node scripts/release-gate.mjs"
|
|
33
34
|
},
|
|
34
35
|
"files": [
|
|
35
36
|
"src",
|
package/src/ubg/extract.js
CHANGED
|
@@ -799,6 +799,36 @@ export function resolveExportedFunction(mod, name, seen = new Set()) {
|
|
|
799
799
|
return null;
|
|
800
800
|
}
|
|
801
801
|
|
|
802
|
+
// The CLASS twin of `resolveExportedFunction`, and it exists for the same reason one
|
|
803
|
+
// level up. A monorepo package (`@novu/dal`, `@novu/application-generic`) resolves to its
|
|
804
|
+
// entry file, and that entry file is a BARREL — sixty `export * from './repositories/…'`
|
|
805
|
+
// lines and not one class declaration. `classInModule` looks for a class DECLARED in the
|
|
806
|
+
// module it was handed, so every DI hop into a workspace package died on the barrel:
|
|
807
|
+
// measured on novu, 1479 of 2039 constructor-DI hops resolved to nothing, `PinoLogger`
|
|
808
|
+
// and the repository classes at the top of the list. The route's real behavior — every
|
|
809
|
+
// db_write the repository performs — was simply absent from the graph.
|
|
810
|
+
//
|
|
811
|
+
// Bounded by `seen` (a barrel cycle terminates) and memoized by the caller, because a
|
|
812
|
+
// wide barrel is walked once per hop otherwise.
|
|
813
|
+
export function resolveExportedClass(mod, name, seen = new Set()) {
|
|
814
|
+
if (!mod || mod.error) return null;
|
|
815
|
+
const direct = classInModule(mod, name);
|
|
816
|
+
if (direct) return { cls: direct, mod };
|
|
817
|
+
const named = mod.reexports.get(name); // export { X } from './x'
|
|
818
|
+
if (named && !seen.has(named)) {
|
|
819
|
+
seen.add(named);
|
|
820
|
+
const hit = resolveExportedClass(parseModule(named), name, seen);
|
|
821
|
+
if (hit) return hit;
|
|
822
|
+
}
|
|
823
|
+
for (const file of mod.starReexports ?? []) {
|
|
824
|
+
if (seen.has(file)) continue;
|
|
825
|
+
seen.add(file);
|
|
826
|
+
const hit = resolveExportedClass(parseModule(file), name, seen);
|
|
827
|
+
if (hit) return hit;
|
|
828
|
+
}
|
|
829
|
+
return null;
|
|
830
|
+
}
|
|
831
|
+
|
|
802
832
|
function collectTopLevel(node, facts, absFile, exported) {
|
|
803
833
|
if (node.type === 'ExportNamedDeclaration' && node.declaration) {
|
|
804
834
|
collectTopLevel(node.declaration, facts, absFile, true);
|
package/src/ubg/resolve.js
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
methodInClassChain,
|
|
27
27
|
computeThisSymbols,
|
|
28
28
|
resolveExportedFunction,
|
|
29
|
+
resolveExportedClass,
|
|
29
30
|
collectRepoFields,
|
|
30
31
|
collectReqDerived,
|
|
31
32
|
reqParamName,
|
|
@@ -156,6 +157,7 @@ function seedTaint(fn, args, callerReq) {
|
|
|
156
157
|
// live here, scoped to the run.
|
|
157
158
|
export function createResolver({ cwd, scannedFiles, helpers }) {
|
|
158
159
|
const classBundles = new Map(); // memo: per (class file, class.method[, symbols])
|
|
160
|
+
const classLookups = new Map(); // memo: per (module, class name) — barrel walks
|
|
159
161
|
const rel = (abs) => relOf(cwd, abs);
|
|
160
162
|
|
|
161
163
|
// ---- member-call following (imports, instantiated services, this/super) ----
|
|
@@ -411,9 +413,22 @@ export function createResolver({ cwd, scannedFiles, helpers }) {
|
|
|
411
413
|
const file = mod.imports.get(className);
|
|
412
414
|
const clsMod = file ? parseModule(file) : mod;
|
|
413
415
|
if (clsMod.error) return null;
|
|
414
|
-
const
|
|
415
|
-
if (!
|
|
416
|
-
return classMethodBundle(cls,
|
|
416
|
+
const hit = classThroughBarrels(clsMod, className);
|
|
417
|
+
if (!hit) return null;
|
|
418
|
+
return classMethodBundle(hit.cls, hit.mod, methodName, depth, stack, thisSymbols);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// A class is looked up THROUGH barrels: declared here, or re-exported from a module
|
|
422
|
+
// this one re-exports. A monorepo package entry point is a barrel and nothing else, so
|
|
423
|
+
// without this every DI hop into a workspace package resolves to nothing at all —
|
|
424
|
+
// silently, since an unresolved hop leaves no trace of its own. Memoized per (module,
|
|
425
|
+
// class): a sixty-line barrel is otherwise re-walked once per hop.
|
|
426
|
+
function classThroughBarrels(clsMod, className) {
|
|
427
|
+
const key = `cls:${clsMod._file ?? ''}#${className}`;
|
|
428
|
+
if (classLookups.has(key)) return classLookups.get(key);
|
|
429
|
+
const hit = resolveExportedClass(clsMod, className);
|
|
430
|
+
classLookups.set(key, hit);
|
|
431
|
+
return hit;
|
|
417
432
|
}
|
|
418
433
|
|
|
419
434
|
// The fully-resolved scan of `<topCls>.<methodName>` — its body plus everything
|