@toolbaux/guardian 0.1.4 → 0.1.5

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/dist/config.js CHANGED
@@ -7,6 +7,7 @@ const DEFAULT_CONFIG = {
7
7
  root: "",
8
8
  backendRoot: "",
9
9
  frontendRoot: "",
10
+ roots: [],
10
11
  discovery: {
11
12
  enabled: true
12
13
  },
@@ -137,6 +138,11 @@ function normalizeConfig(input, configDir) {
137
138
  project.root = resolveMaybe(project.root) ?? "";
138
139
  project.backendRoot = resolveMaybe(project.backendRoot) ?? "";
139
140
  project.frontendRoot = resolveMaybe(project.frontendRoot) ?? "";
141
+ if (Array.isArray(project.roots) && configDir) {
142
+ project.roots = project.roots
143
+ .filter((r) => typeof r === "string" && r.trim().length > 0)
144
+ .map(r => path.resolve(configDir, r));
145
+ }
140
146
  normalized.project = project;
141
147
  }
142
148
  if (input.python) {
@@ -271,6 +277,7 @@ function mergeConfig(base, override) {
271
277
  root: override.project?.root ?? base.project?.root ?? "",
272
278
  backendRoot: override.project?.backendRoot ?? base.project?.backendRoot ?? "",
273
279
  frontendRoot: override.project?.frontendRoot ?? base.project?.frontendRoot ?? "",
280
+ roots: mergeArrays(base.project?.roots, override.project?.roots),
274
281
  discovery: {
275
282
  enabled: override.project?.discovery?.enabled ??
276
283
  base.project?.discovery?.enabled ??
@@ -28,8 +28,20 @@ export async function buildSnapshots(options) {
28
28
  const reportedFrontendRoot = formatOutputPath(resolvedFrontendRoot);
29
29
  const reportedWorkspaceRoot = formatOutputPath(workspaceRoot);
30
30
  const config = resolvedProject.config;
31
- const backend = await analyzeBackend(resolvedBackendRoot, config, workspaceRoot);
32
- const frontend = await analyzeFrontend(resolvedFrontendRoot, config);
31
+ // Analyze all roots run both analyzers on each root, then merge
32
+ const allRoots = resolvedProject.roots;
33
+ const backendResults = [];
34
+ const frontendResults = [];
35
+ for (const root of allRoots) {
36
+ const [be, fe] = await Promise.all([
37
+ analyzeBackend(root, config, workspaceRoot),
38
+ analyzeFrontend(root, config)
39
+ ]);
40
+ backendResults.push(be);
41
+ frontendResults.push(fe);
42
+ }
43
+ const backend = mergeBackendAnalyses(backendResults, allRoots, workspaceRoot);
44
+ const frontend = mergeFrontendAnalyses(frontendResults, allRoots, workspaceRoot);
33
45
  const projectRoot = workspaceRoot;
34
46
  const runtime = await analyzeRuntime(workspaceRoot, config);
35
47
  const projectName = deriveProjectName(resolvedBackendRoot);
@@ -82,6 +94,7 @@ export async function buildSnapshots(options) {
82
94
  workspace_root: reportedWorkspaceRoot,
83
95
  backend_root: reportedBackendRoot,
84
96
  frontend_root: reportedFrontendRoot,
97
+ roots: resolvedProject.roots.map(formatOutputPath),
85
98
  resolution_source: resolvedProject.resolutionSource,
86
99
  entrypoints: backend.entrypoints
87
100
  },
@@ -305,6 +318,97 @@ async function buildFunctionTestCoverage(params) {
305
318
  };
306
319
  });
307
320
  }
321
+ function mergeBackendAnalyses(results, roots, workspaceRoot) {
322
+ if (results.length === 1)
323
+ return results[0];
324
+ // Prefix module IDs with root-relative path so they're globally unique
325
+ for (let i = 0; i < results.length; i++) {
326
+ const rootLabel = path.relative(workspaceRoot, roots[i]).replace(/\\/g, "/");
327
+ const idMap = new Map();
328
+ for (const mod of results[i].modules) {
329
+ const newId = `${rootLabel}/${mod.id}`;
330
+ idMap.set(mod.id, newId);
331
+ mod.id = newId;
332
+ mod.path = `${rootLabel}/${mod.path}`;
333
+ }
334
+ // Remap references in graph edges, endpoints, etc.
335
+ for (const edge of results[i].moduleGraph) {
336
+ edge.from = idMap.get(edge.from) ?? edge.from;
337
+ edge.to = idMap.get(edge.to) ?? edge.to;
338
+ }
339
+ for (const ep of results[i].endpoints) {
340
+ if (ep.module && idMap.has(ep.module)) {
341
+ ep.module = idMap.get(ep.module);
342
+ }
343
+ }
344
+ for (const cycle of results[i].circularDependencies) {
345
+ for (let j = 0; j < cycle.length; j++) {
346
+ cycle[j] = idMap.get(cycle[j]) ?? cycle[j];
347
+ }
348
+ }
349
+ results[i].orphanModules = results[i].orphanModules.map(m => idMap.get(m) ?? m);
350
+ const newUsage = {};
351
+ for (const [key, value] of Object.entries(results[i].moduleUsage)) {
352
+ newUsage[idMap.get(key) ?? key] = value;
353
+ }
354
+ results[i].moduleUsage = newUsage;
355
+ }
356
+ const moduleUsage = {};
357
+ for (const r of results) {
358
+ for (const [key, value] of Object.entries(r.moduleUsage)) {
359
+ moduleUsage[key] = (moduleUsage[key] ?? 0) + value;
360
+ }
361
+ }
362
+ // Merge testCoverage: combine arrays across all roots
363
+ const mergedCoverage = { ...results[0].testCoverage };
364
+ mergedCoverage.untested_source_files = [...mergedCoverage.untested_source_files];
365
+ mergedCoverage.test_files_missing_source = [...mergedCoverage.test_files_missing_source];
366
+ mergedCoverage.coverage_map = [...mergedCoverage.coverage_map];
367
+ for (let i = 1; i < results.length; i++) {
368
+ const tc = results[i].testCoverage;
369
+ mergedCoverage.untested_source_files.push(...tc.untested_source_files);
370
+ mergedCoverage.test_files_missing_source.push(...tc.test_files_missing_source);
371
+ mergedCoverage.coverage_map.push(...tc.coverage_map);
372
+ }
373
+ return {
374
+ modules: results.flatMap(r => r.modules),
375
+ moduleGraph: results.flatMap(r => r.moduleGraph),
376
+ fileGraph: results.flatMap(r => r.fileGraph),
377
+ endpoints: results.flatMap(r => r.endpoints),
378
+ dataModels: results.flatMap(r => r.dataModels),
379
+ enums: results.flatMap(r => r.enums),
380
+ constants: results.flatMap(r => r.constants),
381
+ endpointModelUsage: results.flatMap(r => r.endpointModelUsage),
382
+ tasks: results.flatMap(r => r.tasks),
383
+ circularDependencies: results.flatMap(r => r.circularDependencies),
384
+ orphanModules: results.flatMap(r => r.orphanModules),
385
+ orphanFiles: results.flatMap(r => r.orphanFiles),
386
+ moduleUsage,
387
+ unusedExports: results.flatMap(r => r.unusedExports),
388
+ unusedEndpoints: results.flatMap(r => r.unusedEndpoints),
389
+ entrypoints: results.flatMap(r => r.entrypoints),
390
+ duplicateFunctions: results.flatMap(r => r.duplicateFunctions),
391
+ similarFunctions: results.flatMap(r => r.similarFunctions),
392
+ testCoverage: mergedCoverage,
393
+ tests: results.flatMap(r => r.tests)
394
+ };
395
+ }
396
+ function mergeFrontendAnalyses(results, _roots, _workspaceRoot) {
397
+ if (results.length === 1)
398
+ return results[0];
399
+ return {
400
+ files: results.flatMap(r => r.files),
401
+ pages: results.flatMap(r => r.pages),
402
+ apiCalls: results.flatMap(r => r.apiCalls),
403
+ uxPages: results.flatMap(r => r.uxPages),
404
+ components: results.flatMap(r => r.components),
405
+ componentGraph: results.flatMap(r => r.componentGraph),
406
+ fileGraph: results.flatMap(r => r.fileGraph),
407
+ orphanFiles: results.flatMap(r => r.orphanFiles),
408
+ unusedExports: results.flatMap(r => r.unusedExports),
409
+ tests: results.flatMap(r => r.tests)
410
+ };
411
+ }
308
412
  function findCommonRoot(paths) {
309
413
  if (paths.length === 0) {
310
414
  return process.cwd();
@@ -53,16 +53,22 @@ export async function resolveProjectPaths(options) {
53
53
  (await chooseBackendRoot(workspaceRoot));
54
54
  const frontendRoot = explicitFrontend ??
55
55
  (await chooseFrontendRoot(workspaceRoot));
56
+ // Build unified roots list: backendRoot + frontendRoot + any extra from config
57
+ const configRoots = (config.project?.roots ?? []).map(r => path.resolve(r));
58
+ const allRoots = [...new Set([backendRoot, frontendRoot, ...configRoots].filter(Boolean))];
56
59
  return {
57
60
  workspaceRoot,
58
61
  backendRoot,
59
62
  frontendRoot,
63
+ roots: allRoots,
60
64
  resolutionSource,
61
65
  config
62
66
  };
63
67
  }
64
68
  export function logResolvedProjectPaths(resolved) {
65
- console.log(`Guardian roots (${resolved.resolutionSource}): workspace=${resolved.workspaceRoot} backend=${resolved.backendRoot} frontend=${resolved.frontendRoot}`);
69
+ const extra = resolved.roots.filter(r => r !== resolved.backendRoot && r !== resolved.frontendRoot);
70
+ const extraMsg = extra.length > 0 ? ` +${extra.length} roots` : "";
71
+ console.log(`Guardian roots (${resolved.resolutionSource}): workspace=${resolved.workspaceRoot} backend=${resolved.backendRoot} frontend=${resolved.frontendRoot}${extraMsg}`);
66
72
  }
67
73
  async function chooseBackendRoot(workspaceRoot) {
68
74
  const candidates = await discoverBackendCandidates(workspaceRoot);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toolbaux/guardian",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "type": "module",
5
5
  "description": "Architectural intelligence for codebases. Verify that AI-generated code matches your architectural intent.",
6
6
  "keywords": [