@toolbaux/guardian 0.1.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 (78) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +366 -0
  3. package/dist/adapters/csharp-adapter.js +149 -0
  4. package/dist/adapters/go-adapter.js +96 -0
  5. package/dist/adapters/index.js +16 -0
  6. package/dist/adapters/java-adapter.js +122 -0
  7. package/dist/adapters/python-adapter.js +183 -0
  8. package/dist/adapters/runner.js +69 -0
  9. package/dist/adapters/types.js +1 -0
  10. package/dist/adapters/typescript-adapter.js +179 -0
  11. package/dist/benchmarking/framework.js +91 -0
  12. package/dist/cli.js +343 -0
  13. package/dist/commands/analyze-depth.js +43 -0
  14. package/dist/commands/api-spec-extractor.js +52 -0
  15. package/dist/commands/breaking-change-analyzer.js +334 -0
  16. package/dist/commands/config-compliance.js +219 -0
  17. package/dist/commands/constraints.js +221 -0
  18. package/dist/commands/context.js +101 -0
  19. package/dist/commands/data-flow-tracer.js +291 -0
  20. package/dist/commands/dependency-impact-analyzer.js +27 -0
  21. package/dist/commands/diff.js +146 -0
  22. package/dist/commands/discrepancy.js +71 -0
  23. package/dist/commands/doc-generate.js +163 -0
  24. package/dist/commands/doc-html.js +120 -0
  25. package/dist/commands/drift.js +88 -0
  26. package/dist/commands/extract.js +16 -0
  27. package/dist/commands/feature-context.js +116 -0
  28. package/dist/commands/generate.js +339 -0
  29. package/dist/commands/guard.js +182 -0
  30. package/dist/commands/init.js +209 -0
  31. package/dist/commands/intel.js +20 -0
  32. package/dist/commands/license-dependency-auditor.js +33 -0
  33. package/dist/commands/performance-hotspot-profiler.js +42 -0
  34. package/dist/commands/search.js +314 -0
  35. package/dist/commands/security-boundary-auditor.js +359 -0
  36. package/dist/commands/simulate.js +294 -0
  37. package/dist/commands/summary.js +27 -0
  38. package/dist/commands/test-coverage-mapper.js +264 -0
  39. package/dist/commands/verify-drift.js +62 -0
  40. package/dist/config.js +441 -0
  41. package/dist/extract/ai-context-hints.js +107 -0
  42. package/dist/extract/analyzers/backend.js +1704 -0
  43. package/dist/extract/analyzers/depth.js +264 -0
  44. package/dist/extract/analyzers/frontend.js +2221 -0
  45. package/dist/extract/api-usage-tracker.js +19 -0
  46. package/dist/extract/cache.js +53 -0
  47. package/dist/extract/codebase-intel.js +190 -0
  48. package/dist/extract/compress.js +452 -0
  49. package/dist/extract/context-block.js +356 -0
  50. package/dist/extract/contracts.js +183 -0
  51. package/dist/extract/discrepancies.js +233 -0
  52. package/dist/extract/docs-loader.js +110 -0
  53. package/dist/extract/docs.js +2379 -0
  54. package/dist/extract/drift.js +1578 -0
  55. package/dist/extract/duplicates.js +435 -0
  56. package/dist/extract/feature-arcs.js +138 -0
  57. package/dist/extract/graph.js +76 -0
  58. package/dist/extract/html-doc.js +1409 -0
  59. package/dist/extract/ignore.js +45 -0
  60. package/dist/extract/index.js +455 -0
  61. package/dist/extract/llm-client.js +159 -0
  62. package/dist/extract/pattern-registry.js +141 -0
  63. package/dist/extract/product-doc.js +497 -0
  64. package/dist/extract/python.js +1202 -0
  65. package/dist/extract/runtime.js +193 -0
  66. package/dist/extract/schema-evolution-validator.js +35 -0
  67. package/dist/extract/test-gap-analyzer.js +20 -0
  68. package/dist/extract/tests.js +74 -0
  69. package/dist/extract/types.js +1 -0
  70. package/dist/extract/validate-backend.js +30 -0
  71. package/dist/extract/writer.js +11 -0
  72. package/dist/output-layout.js +37 -0
  73. package/dist/project-discovery.js +309 -0
  74. package/dist/schema/architecture.js +350 -0
  75. package/dist/schema/feature-spec.js +89 -0
  76. package/dist/schema/index.js +8 -0
  77. package/dist/schema/ux.js +46 -0
  78. package/package.json +75 -0
@@ -0,0 +1,452 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { buildFunctionGraph } from "./drift.js";
5
+ export async function writeCompressionOutputs(params) {
6
+ const { outputDir, architecture, ux, context } = params;
7
+ await fs.mkdir(outputDir, { recursive: true });
8
+ const previous = await loadArchitectureSummary(outputDir);
9
+ const summary = buildArchitectureSummary(architecture, ux);
10
+ const diff = previous ? buildArchitectureDiff(previous, summary) : null;
11
+ const heatmap = await buildHeatmapBundle(architecture, context);
12
+ await fs.writeFile(path.join(outputDir, "architecture.summary.json"), JSON.stringify(summary, null, 2));
13
+ if (diff) {
14
+ await fs.writeFile(path.join(outputDir, "architecture.diff.summary.json"), JSON.stringify(diff, null, 2));
15
+ }
16
+ await fs.writeFile(path.join(outputDir, "drift.heatmap.json"), JSON.stringify(heatmap, null, 2));
17
+ return { summary, diff, heatmap };
18
+ }
19
+ export async function loadArchitectureSummary(outputDir) {
20
+ try {
21
+ const raw = await fs.readFile(path.join(outputDir, "architecture.summary.json"), "utf8");
22
+ return JSON.parse(raw);
23
+ }
24
+ catch {
25
+ return null;
26
+ }
27
+ }
28
+ export async function loadArchitectureDiff(outputDir) {
29
+ try {
30
+ const raw = await fs.readFile(path.join(outputDir, "architecture.diff.summary.json"), "utf8");
31
+ return JSON.parse(raw);
32
+ }
33
+ catch {
34
+ return null;
35
+ }
36
+ }
37
+ export async function loadHeatmap(outputDir) {
38
+ try {
39
+ const raw = await fs.readFile(path.join(outputDir, "drift.heatmap.json"), "utf8");
40
+ const parsed = JSON.parse(raw);
41
+ if ("levels" in parsed) {
42
+ return parsed;
43
+ }
44
+ if ("entries" in parsed) {
45
+ return {
46
+ version: "0.2",
47
+ generated_at: new Date().toISOString(),
48
+ levels: [
49
+ {
50
+ level: "module",
51
+ entries: parsed.entries
52
+ }
53
+ ]
54
+ };
55
+ }
56
+ return null;
57
+ }
58
+ catch {
59
+ return null;
60
+ }
61
+ }
62
+ export function buildArchitectureSummary(architecture, ux) {
63
+ const modules = architecture.modules.map((module) => module.id).sort();
64
+ const moduleEdges = architecture.dependencies.module_graph
65
+ .map((edge) => `${edge.from}→${edge.to}`)
66
+ .sort();
67
+ const endpoints = architecture.endpoints
68
+ .map((endpoint) => `${endpoint.method} ${endpoint.path}`)
69
+ .sort();
70
+ const models = architecture.data_models.map((model) => model.name).sort();
71
+ const pages = ux.pages.map((page) => page.path).sort();
72
+ const components = ux.components.map((component) => component.id).sort();
73
+ const tasks = architecture.tasks.map((task) => task.name).sort();
74
+ const services = architecture.runtime.services.map((service) => service.name).sort();
75
+ const fileEdges = architecture.dependencies.file_graph ?? [];
76
+ const normalized = {
77
+ modules,
78
+ module_edges: moduleEdges,
79
+ endpoints,
80
+ models,
81
+ pages,
82
+ components,
83
+ tasks,
84
+ runtime_services: services
85
+ };
86
+ const fingerprint = hashObject(normalized);
87
+ const shapeFingerprint = computeShapeFingerprint(architecture);
88
+ const moduleCounts = new Map();
89
+ for (const module of architecture.modules) {
90
+ moduleCounts.set(module.id, { inbound: 0, outbound: 0, layer: module.layer });
91
+ }
92
+ for (const edge of architecture.dependencies.module_graph) {
93
+ const from = moduleCounts.get(edge.from);
94
+ if (from) {
95
+ from.outbound += 1;
96
+ }
97
+ const to = moduleCounts.get(edge.to);
98
+ if (to) {
99
+ to.inbound += 1;
100
+ }
101
+ }
102
+ const topModules = Array.from(moduleCounts.entries())
103
+ .map(([id, stats]) => ({
104
+ id,
105
+ inbound: stats.inbound,
106
+ outbound: stats.outbound,
107
+ total: stats.inbound + stats.outbound,
108
+ layer: stats.layer
109
+ }))
110
+ .sort((a, b) => b.total - a.total)
111
+ .slice(0, 10);
112
+ return {
113
+ version: "0.1",
114
+ generated_at: new Date().toISOString(),
115
+ fingerprint,
116
+ shape_fingerprint: shapeFingerprint,
117
+ normalized,
118
+ counts: {
119
+ modules: modules.length,
120
+ module_edges: moduleEdges.length,
121
+ file_edges: fileEdges.length,
122
+ files: countUniqueFiles(architecture),
123
+ endpoints: endpoints.length,
124
+ models: models.length,
125
+ pages: pages.length,
126
+ components: components.length,
127
+ tasks: tasks.length,
128
+ services: services.length
129
+ },
130
+ top_modules: topModules
131
+ };
132
+ }
133
+ export function buildArchitectureDiff(previous, next) {
134
+ const diff = {
135
+ version: "0.1",
136
+ generated_at: new Date().toISOString(),
137
+ from_fingerprint: previous.fingerprint,
138
+ to_fingerprint: next.fingerprint,
139
+ structural_change: previous.fingerprint !== next.fingerprint,
140
+ shape_equivalent: previous.shape_fingerprint === next.shape_fingerprint,
141
+ added: {},
142
+ removed: {},
143
+ counts_delta: {}
144
+ };
145
+ const keys = [
146
+ "modules",
147
+ "module_edges",
148
+ "endpoints",
149
+ "models",
150
+ "pages",
151
+ "components",
152
+ "tasks",
153
+ "runtime_services"
154
+ ];
155
+ for (const key of keys) {
156
+ const prev = new Set(previous.normalized[key]);
157
+ const nextSet = new Set(next.normalized[key]);
158
+ diff.added[key] = Array.from(nextSet).filter((value) => !prev.has(value)).sort();
159
+ diff.removed[key] = Array.from(prev).filter((value) => !nextSet.has(value)).sort();
160
+ }
161
+ const countKeys = Object.keys(next.counts);
162
+ for (const key of countKeys) {
163
+ diff.counts_delta[key] = (next.counts[key] ?? 0) - (previous.counts[key] ?? 0);
164
+ }
165
+ return diff;
166
+ }
167
+ function computeShapeFingerprint(architecture) {
168
+ const inbound = new Map();
169
+ const outbound = new Map();
170
+ for (const module of architecture.modules) {
171
+ inbound.set(module.id, 0);
172
+ outbound.set(module.id, 0);
173
+ }
174
+ for (const edge of architecture.dependencies.module_graph) {
175
+ outbound.set(edge.from, (outbound.get(edge.from) ?? 0) + 1);
176
+ inbound.set(edge.to, (inbound.get(edge.to) ?? 0) + 1);
177
+ }
178
+ const degrees = architecture.modules
179
+ .map((module) => ({
180
+ in: inbound.get(module.id) ?? 0,
181
+ out: outbound.get(module.id) ?? 0
182
+ }))
183
+ .sort((a, b) => (a.in + a.out) - (b.in + b.out));
184
+ const degreeSignature = degrees.map((entry) => `${entry.in}:${entry.out}`).join("|");
185
+ const edgeSignature = architecture.dependencies.module_graph
186
+ .map((edge) => {
187
+ const from = `${inbound.get(edge.from) ?? 0}:${outbound.get(edge.from) ?? 0}`;
188
+ const to = `${inbound.get(edge.to) ?? 0}:${outbound.get(edge.to) ?? 0}`;
189
+ return `${from}->${to}`;
190
+ })
191
+ .sort()
192
+ .join("|");
193
+ return hashObject({
194
+ degreeSignature,
195
+ edgeSignature
196
+ });
197
+ }
198
+ async function buildHeatmapBundle(architecture, context) {
199
+ const levels = [];
200
+ const moduleNodes = architecture.modules.map((module) => module.id);
201
+ const moduleLayers = new Map(architecture.modules.map((module) => [module.id, module.layer]));
202
+ const moduleEdges = architecture.dependencies.module_graph.map((edge) => ({
203
+ from: edge.from,
204
+ to: edge.to
205
+ }));
206
+ levels.push(buildHeatmapFromGraph("module", moduleNodes, moduleEdges, moduleLayers));
207
+ if (architecture.dependencies.file_graph && architecture.dependencies.file_graph.length > 0) {
208
+ const fileNodes = new Set();
209
+ const fileLayers = new Map();
210
+ for (const module of architecture.modules) {
211
+ for (const file of module.files) {
212
+ fileNodes.add(file);
213
+ fileLayers.set(file, module.layer);
214
+ }
215
+ }
216
+ for (const edge of architecture.dependencies.file_graph) {
217
+ fileNodes.add(edge.from);
218
+ fileNodes.add(edge.to);
219
+ }
220
+ const fileEdges = architecture.dependencies.file_graph.map((edge) => ({
221
+ from: edge.from,
222
+ to: edge.to
223
+ }));
224
+ levels.push(buildHeatmapFromGraph("file", Array.from(fileNodes), fileEdges, fileLayers));
225
+ }
226
+ const domainMap = context?.config?.drift?.domains ?? {};
227
+ if (Object.keys(domainMap).length > 0) {
228
+ const moduleToDomain = new Map();
229
+ for (const module of architecture.modules) {
230
+ const domain = resolveDomainForModule(module.id, domainMap);
231
+ if (domain) {
232
+ moduleToDomain.set(module.id, domain);
233
+ }
234
+ }
235
+ const domainNodes = new Set();
236
+ const domainLayers = new Map();
237
+ for (const domain of moduleToDomain.values()) {
238
+ domainNodes.add(domain);
239
+ domainLayers.set(domain, domain);
240
+ }
241
+ const domainEdges = [];
242
+ const seen = new Set();
243
+ for (const edge of architecture.dependencies.module_graph) {
244
+ const fromDomain = moduleToDomain.get(edge.from) ?? "unassigned";
245
+ const toDomain = moduleToDomain.get(edge.to) ?? "unassigned";
246
+ const key = `${fromDomain}::${toDomain}`;
247
+ if (seen.has(key)) {
248
+ continue;
249
+ }
250
+ seen.add(key);
251
+ domainNodes.add(fromDomain);
252
+ domainNodes.add(toDomain);
253
+ domainLayers.set(fromDomain, fromDomain);
254
+ domainLayers.set(toDomain, toDomain);
255
+ domainEdges.push({ from: fromDomain, to: toDomain });
256
+ }
257
+ levels.push(buildHeatmapFromGraph("domain", Array.from(domainNodes), domainEdges, domainLayers));
258
+ }
259
+ if (context?.backendRoot && context?.projectRoot) {
260
+ const hasFunctionScale = context.config?.drift?.scales?.includes("function") ?? false;
261
+ const shouldFunction = hasFunctionScale ||
262
+ context.config?.drift?.graphLevel === "function" ||
263
+ context.config?.drift?.graphLevel === "auto";
264
+ if (shouldFunction) {
265
+ const fnGraph = await buildFunctionGraph({
266
+ backendRoot: context.backendRoot,
267
+ modules: architecture.modules,
268
+ projectRoot: context.projectRoot
269
+ });
270
+ if (fnGraph) {
271
+ levels.push(buildHeatmapFromGraph("function", fnGraph.nodes, fnGraph.edges, fnGraph.nodeLayers));
272
+ }
273
+ }
274
+ }
275
+ return {
276
+ version: "0.2",
277
+ generated_at: new Date().toISOString(),
278
+ levels
279
+ };
280
+ }
281
+ function buildHeatmapFromGraph(level, nodes, edges, nodeLayers) {
282
+ const adjacency = new Map();
283
+ const reverse = new Map();
284
+ const outbound = new Map();
285
+ const inbound = new Map();
286
+ const crossLayerOut = new Map();
287
+ for (const node of nodes) {
288
+ adjacency.set(node, []);
289
+ reverse.set(node, []);
290
+ outbound.set(node, 0);
291
+ inbound.set(node, 0);
292
+ crossLayerOut.set(node, 0);
293
+ }
294
+ for (const edge of edges) {
295
+ if (!adjacency.has(edge.from)) {
296
+ adjacency.set(edge.from, []);
297
+ reverse.set(edge.from, []);
298
+ outbound.set(edge.from, 0);
299
+ inbound.set(edge.from, 0);
300
+ crossLayerOut.set(edge.from, 0);
301
+ nodes.push(edge.from);
302
+ }
303
+ if (!adjacency.has(edge.to)) {
304
+ adjacency.set(edge.to, []);
305
+ reverse.set(edge.to, []);
306
+ outbound.set(edge.to, 0);
307
+ inbound.set(edge.to, 0);
308
+ crossLayerOut.set(edge.to, 0);
309
+ nodes.push(edge.to);
310
+ }
311
+ adjacency.get(edge.from)?.push(edge.to);
312
+ reverse.get(edge.to)?.push(edge.from);
313
+ outbound.set(edge.from, (outbound.get(edge.from) ?? 0) + 1);
314
+ inbound.set(edge.to, (inbound.get(edge.to) ?? 0) + 1);
315
+ const fromLayer = nodeLayers.get(edge.from);
316
+ const toLayer = nodeLayers.get(edge.to);
317
+ if (fromLayer && toLayer && fromLayer !== toLayer) {
318
+ crossLayerOut.set(edge.from, (crossLayerOut.get(edge.from) ?? 0) + 1);
319
+ }
320
+ }
321
+ const cycleNodes = findCycleNodes(nodes, adjacency, reverse);
322
+ const degreeValues = nodes.map((node) => (outbound.get(node) ?? 0) + (inbound.get(node) ?? 0));
323
+ const maxDegree = Math.max(1, ...degreeValues);
324
+ const maxCrossRatio = Math.max(1, ...nodes.map((node) => {
325
+ const out = outbound.get(node) ?? 0;
326
+ const cross = crossLayerOut.get(node) ?? 0;
327
+ return out === 0 ? 0 : cross / out;
328
+ }));
329
+ const entries = nodes.map((node) => {
330
+ const degree = (outbound.get(node) ?? 0) + (inbound.get(node) ?? 0);
331
+ const crossOut = crossLayerOut.get(node) ?? 0;
332
+ const out = outbound.get(node) ?? 0;
333
+ const crossRatio = out === 0 ? 0 : crossOut / out;
334
+ const cycleFlag = cycleNodes.has(node) ? 1 : 0;
335
+ const score = 0.5 * (degree / maxDegree) +
336
+ 0.3 * (crossRatio / maxCrossRatio) +
337
+ 0.2 * cycleFlag;
338
+ return {
339
+ id: node,
340
+ layer: nodeLayers.get(node) ?? "unknown",
341
+ score: round(score, 4),
342
+ components: {
343
+ degree,
344
+ cross_layer_ratio: round(crossRatio, 4),
345
+ cycle: cycleFlag
346
+ }
347
+ };
348
+ });
349
+ entries.sort((a, b) => b.score - a.score);
350
+ return {
351
+ level,
352
+ entries
353
+ };
354
+ }
355
+ function resolveDomainForModule(moduleId, domainMap) {
356
+ for (const [domain, patterns] of Object.entries(domainMap)) {
357
+ for (const pattern of patterns) {
358
+ if (pattern === moduleId) {
359
+ return domain;
360
+ }
361
+ if (pattern.endsWith("*")) {
362
+ const prefix = pattern.slice(0, -1);
363
+ if (moduleId.startsWith(prefix)) {
364
+ return domain;
365
+ }
366
+ }
367
+ }
368
+ }
369
+ return null;
370
+ }
371
+ function findCycleNodes(nodes, adjacency, reverse) {
372
+ const visited = new Set();
373
+ const order = [];
374
+ const dfs1 = (node) => {
375
+ visited.add(node);
376
+ for (const next of adjacency.get(node) ?? []) {
377
+ if (!visited.has(next)) {
378
+ dfs1(next);
379
+ }
380
+ }
381
+ order.push(node);
382
+ };
383
+ for (const node of nodes) {
384
+ if (!visited.has(node)) {
385
+ dfs1(node);
386
+ }
387
+ }
388
+ const visited2 = new Set();
389
+ const cycleNodes = new Set();
390
+ const dfs2 = (node, component) => {
391
+ visited2.add(node);
392
+ component.push(node);
393
+ for (const next of reverse.get(node) ?? []) {
394
+ if (!visited2.has(next)) {
395
+ dfs2(next, component);
396
+ }
397
+ }
398
+ };
399
+ for (let i = order.length - 1; i >= 0; i -= 1) {
400
+ const node = order[i];
401
+ if (visited2.has(node)) {
402
+ continue;
403
+ }
404
+ const component = [];
405
+ dfs2(node, component);
406
+ if (component.length > 1) {
407
+ for (const entry of component) {
408
+ cycleNodes.add(entry);
409
+ }
410
+ }
411
+ else {
412
+ const neighbors = adjacency.get(node) ?? [];
413
+ if (neighbors.includes(node)) {
414
+ cycleNodes.add(node);
415
+ }
416
+ }
417
+ }
418
+ return cycleNodes;
419
+ }
420
+ function countUniqueFiles(snapshot) {
421
+ const files = new Set();
422
+ for (const module of snapshot.modules) {
423
+ for (const file of module.files) {
424
+ files.add(file);
425
+ }
426
+ }
427
+ for (const file of snapshot.frontend_files) {
428
+ files.add(file);
429
+ }
430
+ return files.size;
431
+ }
432
+ function hashObject(value) {
433
+ const payload = stableStringify(value);
434
+ return crypto.createHash("sha256").update(payload).digest("hex");
435
+ }
436
+ function stableStringify(value) {
437
+ if (Array.isArray(value)) {
438
+ return `[${value.map((entry) => stableStringify(entry)).join(",")}]`;
439
+ }
440
+ if (value && typeof value === "object") {
441
+ const obj = value;
442
+ const keys = Object.keys(obj).sort();
443
+ return `{${keys
444
+ .map((key) => `${JSON.stringify(key)}:${stableStringify(obj[key])}`)
445
+ .join(",")}}`;
446
+ }
447
+ return JSON.stringify(value);
448
+ }
449
+ function round(value, precision) {
450
+ const factor = 10 ** precision;
451
+ return Math.round(value * factor) / factor;
452
+ }