@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,19 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { analyzeFrontend } from "./analyzers/frontend.js";
4
+ export async function trackApiUsage(frontendRoot) {
5
+ const frontendAnalysis = await analyzeFrontend(frontendRoot, {});
6
+ const apiCalls = frontendAnalysis.apiCalls;
7
+ const usageSummary = apiCalls.reduce((summary, call) => {
8
+ const key = `${call.method} ${call.path}`;
9
+ if (!summary[key]) {
10
+ summary[key] = { count: 0, sources: [] };
11
+ }
12
+ summary[key].count += 1;
13
+ summary[key].sources.push(call.source);
14
+ return summary;
15
+ }, {});
16
+ const reportPath = path.join(frontendRoot, "api-usage-report.json");
17
+ await fs.writeFile(reportPath, JSON.stringify(usageSummary, null, 2));
18
+ console.log(`API usage report generated at: ${reportPath}`);
19
+ }
@@ -0,0 +1,53 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ const BACKEND_CACHE_VERSION = "specguard-backend-cache-v4";
5
+ export async function loadBackendExtractionCache(params) {
6
+ const cachePath = path.join(params.projectRoot, "specs-out", ".cache", "file-hashes.json");
7
+ const configHash = hashObject(params.config);
8
+ try {
9
+ const raw = await fs.readFile(cachePath, "utf8");
10
+ const parsed = JSON.parse(raw);
11
+ if (parsed &&
12
+ parsed.version === BACKEND_CACHE_VERSION &&
13
+ parsed.configHash === configHash &&
14
+ parsed.files &&
15
+ typeof parsed.files === "object") {
16
+ return { cachePath, cache: parsed };
17
+ }
18
+ }
19
+ catch {
20
+ // ignore
21
+ }
22
+ return {
23
+ cachePath,
24
+ cache: {
25
+ version: BACKEND_CACHE_VERSION,
26
+ configHash,
27
+ files: {}
28
+ }
29
+ };
30
+ }
31
+ export async function saveBackendExtractionCache(cachePath, cache) {
32
+ await fs.mkdir(path.dirname(cachePath), { recursive: true });
33
+ await fs.writeFile(cachePath, JSON.stringify(cache, null, 2), "utf8");
34
+ }
35
+ export function hashContent(content) {
36
+ return crypto.createHash("sha256").update(content).digest("hex");
37
+ }
38
+ function hashObject(value) {
39
+ return crypto.createHash("sha256").update(stableStringify(value)).digest("hex");
40
+ }
41
+ function stableStringify(value) {
42
+ if (value === null || typeof value !== "object") {
43
+ return JSON.stringify(value);
44
+ }
45
+ if (Array.isArray(value)) {
46
+ return `[${value.map((entry) => stableStringify(entry)).join(",")}]`;
47
+ }
48
+ const record = value;
49
+ return `{${Object.keys(record)
50
+ .sort((a, b) => a.localeCompare(b))
51
+ .map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`)
52
+ .join(",")}}`;
53
+ }
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Codebase Intelligence — assembles a frozen cross-feature packet from architecture snapshots.
3
+ *
4
+ * Analogous to series-intelligence.json in the book workflow:
5
+ * a single self-contained JSON file that any downstream command can read without
6
+ * touching source code. Contains:
7
+ *
8
+ * api_registry — every endpoint keyed by "METHOD /path"
9
+ * model_registry — every ORM model with fields and relationships
10
+ * enum_registry — every enum with its values
11
+ * pattern_registry — detected implementation patterns (P1–P8)
12
+ * background_tasks — all background/celery tasks
13
+ * service_map — modules with their endpoint counts and dependencies
14
+ * frontend_pages — pages with their component trees and API calls
15
+ * meta — project name, counts, generated_at
16
+ */
17
+ import fs from "node:fs/promises";
18
+ import path from "node:path";
19
+ import yaml from "js-yaml";
20
+ import { buildPatternRegistry } from "./pattern-registry.js";
21
+ import { resolveMachineInputDir } from "../output-layout.js";
22
+ export function buildCodebaseIntelligence(architecture, ux) {
23
+ const patternRegistry = buildPatternRegistry(architecture);
24
+ // Build a per-endpoint pattern index
25
+ const endpointPatterns = new Map();
26
+ for (const pattern of patternRegistry.patterns) {
27
+ for (const epStr of pattern.example_endpoints) {
28
+ // example_endpoints are "METHOD /path" — match by id
29
+ }
30
+ }
31
+ // More precise: recompute per-endpoint patterns from the architecture
32
+ const epPatternMap = buildEndpointPatternMap(architecture);
33
+ // api_registry
34
+ const apiRegistry = {};
35
+ for (const ep of architecture.endpoints) {
36
+ const key = `${ep.method} ${ep.path}`;
37
+ apiRegistry[key] = {
38
+ method: ep.method,
39
+ path: ep.path,
40
+ handler: ep.handler,
41
+ file: ep.file,
42
+ module: ep.module,
43
+ request_schema: ep.request_schema ?? null,
44
+ response_schema: ep.response_schema ?? null,
45
+ service_calls: ep.service_calls,
46
+ ai_operations: ep.ai_operations,
47
+ patterns: epPatternMap.get(ep.id) ?? [],
48
+ };
49
+ }
50
+ // model_registry
51
+ const modelRegistry = {};
52
+ for (const model of architecture.data_models) {
53
+ modelRegistry[model.name] = {
54
+ name: model.name,
55
+ file: model.file,
56
+ framework: model.framework,
57
+ fields: model.fields,
58
+ relationships: model.relationships,
59
+ field_details: model.field_details ?? [],
60
+ };
61
+ }
62
+ // enum_registry
63
+ const enumRegistry = {};
64
+ for (const en of architecture.enums) {
65
+ enumRegistry[en.name] = {
66
+ name: en.name,
67
+ file: en.file,
68
+ values: en.values,
69
+ };
70
+ }
71
+ // service_map
72
+ const serviceMap = architecture.modules.map((m) => ({
73
+ id: m.id,
74
+ path: m.path,
75
+ type: m.type,
76
+ layer: m.layer,
77
+ file_count: m.files.length,
78
+ endpoint_count: m.endpoints.length,
79
+ imports: m.imports,
80
+ }));
81
+ // frontend_pages
82
+ const frontendPages = ux.pages.map((p) => ({
83
+ path: p.path,
84
+ component: p.component,
85
+ api_calls: p.api_calls,
86
+ direct_components: p.components_direct,
87
+ }));
88
+ return {
89
+ meta: {
90
+ project: architecture.project.name,
91
+ generated_at: new Date().toISOString(),
92
+ counts: {
93
+ endpoints: architecture.endpoints.length,
94
+ models: architecture.data_models.length,
95
+ enums: architecture.enums.length,
96
+ tasks: architecture.tasks.length,
97
+ modules: architecture.modules.length,
98
+ pages: ux.pages.length,
99
+ patterns_detected: patternRegistry.patterns.filter((p) => p.occurrences > 0).length,
100
+ },
101
+ },
102
+ api_registry: apiRegistry,
103
+ model_registry: modelRegistry,
104
+ enum_registry: enumRegistry,
105
+ pattern_registry: patternRegistry,
106
+ background_tasks: architecture.tasks,
107
+ service_map: serviceMap,
108
+ frontend_pages: frontendPages,
109
+ };
110
+ }
111
+ /**
112
+ * Build a map from endpoint id → list of pattern IDs that apply.
113
+ * This mirrors the pattern detection logic in pattern-registry.ts but keyed by id.
114
+ */
115
+ function buildEndpointPatternMap(architecture) {
116
+ const result = new Map();
117
+ const modelWriteMap = new Map();
118
+ for (const usage of architecture.endpoint_model_usage) {
119
+ const writes = usage.models.filter((m) => m.access === "write" || m.access === "read_write").length;
120
+ modelWriteMap.set(usage.endpoint_id, writes);
121
+ }
122
+ const crossStackVerified = new Set((architecture.cross_stack_contracts ?? [])
123
+ .filter((c) => c.status === "ok")
124
+ .map((c) => c.endpoint_id));
125
+ const resourceMethods = new Map();
126
+ for (const ep of architecture.endpoints) {
127
+ const resource = ep.path.replace(/\/\{[^}]+\}$/, "").replace(/\/:[^/]+$/, "");
128
+ const entry = resourceMethods.get(resource) ?? new Set();
129
+ entry.add(ep.method.toUpperCase());
130
+ resourceMethods.set(resource, entry);
131
+ }
132
+ const crudResources = new Set();
133
+ for (const [resource, methods] of resourceMethods) {
134
+ if (methods.has("GET") && methods.has("POST") && (methods.has("PATCH") || methods.has("PUT"))) {
135
+ crudResources.add(resource);
136
+ }
137
+ }
138
+ for (const ep of architecture.endpoints) {
139
+ const patterns = [];
140
+ const lower = (ep.file + ep.handler).toLowerCase();
141
+ if (ep.service_calls.length > 0)
142
+ patterns.push("P1");
143
+ if (lower.includes("auth") ||
144
+ lower.includes("permission") ||
145
+ lower.includes("require_") ||
146
+ lower.includes("depends(get_current"))
147
+ patterns.push("P2");
148
+ if (ep.ai_operations && ep.ai_operations.length > 0)
149
+ patterns.push("P3");
150
+ if (ep.service_calls.some((s) => {
151
+ const sl = s.toLowerCase();
152
+ return sl.includes("task") || sl.includes(".delay(") || sl.includes("background");
153
+ }))
154
+ patterns.push("P4");
155
+ const resource = ep.path.replace(/\/\{[^}]+\}$/, "").replace(/\/:[^/]+$/, "");
156
+ if (crudResources.has(resource))
157
+ patterns.push("P5");
158
+ if ((modelWriteMap.get(ep.id) ?? 0) >= 3)
159
+ patterns.push("P6");
160
+ if (crossStackVerified.has(ep.id))
161
+ patterns.push("P7");
162
+ if (ep.method.toUpperCase() === "GET" &&
163
+ (ep.path + ep.handler).toLowerCase().match(/list|page|paginate/))
164
+ patterns.push("P8");
165
+ result.set(ep.id, patterns);
166
+ }
167
+ return result;
168
+ }
169
+ /**
170
+ * Load snapshots and build CodebaseIntelligence, then write to disk.
171
+ */
172
+ export async function writeCodebaseIntelligence(specsDir, outputPath) {
173
+ const machineDir = await resolveMachineInputDir(specsDir);
174
+ const [archRaw, uxRaw] = await Promise.all([
175
+ fs.readFile(path.join(machineDir, "architecture.snapshot.yaml"), "utf8"),
176
+ fs.readFile(path.join(machineDir, "ux.snapshot.yaml"), "utf8"),
177
+ ]);
178
+ const architecture = yaml.load(archRaw);
179
+ const ux = yaml.load(uxRaw);
180
+ const intel = buildCodebaseIntelligence(architecture, ux);
181
+ await fs.mkdir(path.dirname(outputPath), { recursive: true });
182
+ await fs.writeFile(outputPath, JSON.stringify(intel, null, 2), "utf8");
183
+ }
184
+ /**
185
+ * Load an existing codebase-intelligence.json from disk.
186
+ */
187
+ export async function loadCodebaseIntelligence(intelPath) {
188
+ const raw = await fs.readFile(intelPath, "utf8");
189
+ return JSON.parse(raw);
190
+ }