@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,264 @@
1
+ /**
2
+ * FEATURE 3: TEST COVERAGE MAPPER
3
+ *
4
+ * Visual heatmap showing test coverage per module, endpoint, and data flow
5
+ * Identifies untested high-coupling files and recommends tests
6
+ *
7
+ * Benchmarking: Medium complexity
8
+ * Problem Domain: Quality Assurance, Test Planning
9
+ */
10
+ import fs from "node:fs/promises";
11
+ import path from "node:path";
12
+ /**
13
+ * Main function: Generate coverage heatmap
14
+ */
15
+ export async function generateCoverageHeatmap(options) {
16
+ const { srcRoot, testRoot, output, highCouplingThreshold = 0.5 } = options;
17
+ // Scan source files
18
+ const sourceFiles = await scanSourceFiles(srcRoot);
19
+ // Scan test files
20
+ const testFiles = await scanTestFiles(testRoot);
21
+ // Map tests to source files
22
+ const coverage = mapTestCoverage(sourceFiles, testFiles);
23
+ // Identify high-coupling files (from coupling scores in architecture context)
24
+ const highCouplingFiles = identifyHighCouplingFiles(sourceFiles, highCouplingThreshold);
25
+ // Find gaps
26
+ const gaps = identifyGaps(coverage, highCouplingFiles);
27
+ // Calculate overall metrics
28
+ const totalLines = coverage.reduce((sum, c) => sum + c.totalLines, 0);
29
+ const coveredLines = coverage.reduce((sum, c) => sum + c.linesCovered, 0);
30
+ const overallCoverage = totalLines > 0 ? (coveredLines / totalLines) * 100 : 0;
31
+ const heatmap = {
32
+ modules: coverage,
33
+ timestamp: new Date().toISOString(),
34
+ overallCoverage,
35
+ criticalGaps: gaps.filter((g) => g.severity === "critical").length,
36
+ riskScore: calculateRiskScore(coverage, overallCoverage),
37
+ recommendations: generateRecommendations(coverage, gaps),
38
+ };
39
+ if (output) {
40
+ await writeHeatmapReport(heatmap, output);
41
+ }
42
+ return heatmap;
43
+ }
44
+ /**
45
+ * Helper: Scan source files
46
+ */
47
+ async function scanSourceFiles(srcRoot) {
48
+ const files = new Map();
49
+ async function walkDir(dir) {
50
+ try {
51
+ const entries = await fs.readdir(dir, { withFileTypes: true });
52
+ for (const entry of entries) {
53
+ const fullPath = path.join(dir, entry.name);
54
+ if (entry.isDirectory() &&
55
+ [".git", "node_modules", "dist", "build", "coverage"].includes(entry.name)) {
56
+ continue;
57
+ }
58
+ if (entry.isDirectory()) {
59
+ await walkDir(fullPath);
60
+ }
61
+ else if (entry.isFile() &&
62
+ [".ts", ".tsx", ".js", ".jsx"].some((ext) => entry.name.endsWith(ext))) {
63
+ const content = await fs.readFile(fullPath, "utf8");
64
+ const relPath = path.relative(srcRoot, fullPath);
65
+ files.set(relPath, content);
66
+ }
67
+ }
68
+ }
69
+ catch {
70
+ // Skip inaccessible directories
71
+ }
72
+ }
73
+ await walkDir(srcRoot);
74
+ return files;
75
+ }
76
+ /**
77
+ * Helper: Scan test files
78
+ */
79
+ async function scanTestFiles(testRoot) {
80
+ const files = new Map();
81
+ async function walkDir(dir) {
82
+ try {
83
+ const entries = await fs.readdir(dir, { withFileTypes: true });
84
+ for (const entry of entries) {
85
+ const fullPath = path.join(dir, entry.name);
86
+ if (entry.isDirectory() && [".git", "node_modules"].includes(entry.name)) {
87
+ continue;
88
+ }
89
+ if (entry.isDirectory()) {
90
+ await walkDir(fullPath);
91
+ }
92
+ else if (entry.isFile() &&
93
+ (entry.name.endsWith(".test.ts") ||
94
+ entry.name.endsWith(".test.js") ||
95
+ entry.name.endsWith(".spec.ts") ||
96
+ entry.name.endsWith(".spec.js"))) {
97
+ const content = await fs.readFile(fullPath, "utf8");
98
+ const relPath = path.relative(testRoot, fullPath);
99
+ files.set(relPath, content);
100
+ }
101
+ }
102
+ }
103
+ catch {
104
+ // Skip inaccessible directories
105
+ }
106
+ }
107
+ await walkDir(testRoot);
108
+ return files;
109
+ }
110
+ /**
111
+ * Helper: Map test coverage to source files
112
+ */
113
+ function mapTestCoverage(sourceFiles, testFiles) {
114
+ const coverage = [];
115
+ for (const [sourcePath, sourceContent] of sourceFiles.entries()) {
116
+ const lineCount = sourceContent.split("\n").length;
117
+ const testers = [];
118
+ // Find tests that reference this file
119
+ for (const [testPath, testContent] of testFiles.entries()) {
120
+ const sourceFileName = path.basename(sourcePath, path.extname(sourcePath));
121
+ if (testContent.includes(sourceFileName) ||
122
+ testContent.includes(sourcePath) ||
123
+ testPath.includes(sourceFileName)) {
124
+ testers.push(testPath);
125
+ }
126
+ }
127
+ // Estimate coverage based on test presence
128
+ const estimatedCoverage = testers.length > 0 ? calculateEstimatedCoverage(sourceContent) : 0;
129
+ coverage.push({
130
+ module: sourcePath,
131
+ linesCovered: Math.floor((estimatedCoverage / 100) * lineCount),
132
+ totalLines: lineCount,
133
+ coverage: estimatedCoverage,
134
+ level: getCoverageLevel(estimatedCoverage),
135
+ testers,
136
+ gaps: [],
137
+ });
138
+ }
139
+ return coverage;
140
+ }
141
+ /**
142
+ * Helper: Calculate estimated coverage by analysing function complexity
143
+ */
144
+ function calculateEstimatedCoverage(content) {
145
+ // Simple heuristic: count exported functions/classes
146
+ const exports = (content.match(/export\s+(function|class|interface|type)/g) || [])
147
+ .length;
148
+ const complexFunctions = (content.match(/function\s+\w+\s*\([^)]*\)\s*\{[^}]*if|for|switch/g) || []).length;
149
+ // If we have exports, assume 60% base coverage + complexity bonus
150
+ if (exports > 0) {
151
+ return Math.min(90, 60 + complexFunctions * 5);
152
+ }
153
+ return Math.min(60, complexFunctions * 10);
154
+ }
155
+ /**
156
+ * Helper: Determine coverage level
157
+ */
158
+ function getCoverageLevel(coverage) {
159
+ if (coverage >= 80)
160
+ return "full";
161
+ if (coverage >= 60)
162
+ return "partial";
163
+ if (coverage >= 30)
164
+ return "minimal";
165
+ return "none";
166
+ }
167
+ /**
168
+ * Helper: Identify high-coupling files
169
+ */
170
+ function identifyHighCouplingFiles(sourceFiles, threshold) {
171
+ // Known high-coupling patterns
172
+ const highCouplingPatterns = /extract|adapter|index|types|config/i;
173
+ const highCoupling = new Set();
174
+ for (const filePath of sourceFiles.keys()) {
175
+ if (highCouplingPatterns.test(filePath)) {
176
+ highCoupling.add(filePath);
177
+ }
178
+ }
179
+ return highCoupling;
180
+ }
181
+ /**
182
+ * Helper: Identify coverage gaps
183
+ */
184
+ function identifyGaps(coverage, highCouplingFiles) {
185
+ const gaps = [];
186
+ for (const metrics of coverage) {
187
+ const isHighCoupling = highCouplingFiles.has(metrics.module);
188
+ if (metrics.coverage < 50) {
189
+ gaps.push({
190
+ location: metrics.module,
191
+ severity: isHighCoupling ? "critical" : "high",
192
+ reason: `Low coverage on ${isHighCoupling ? "high-coupling" : "important"} module`,
193
+ suggestedTest: `Add integration tests for ${path.basename(metrics.module)}`,
194
+ });
195
+ }
196
+ else if (metrics.coverage < 70 && isHighCoupling) {
197
+ gaps.push({
198
+ location: metrics.module,
199
+ severity: "high",
200
+ reason: `Partial coverage on high-coupling file`,
201
+ suggestedTest: `Increase coverage: add edge case tests`,
202
+ });
203
+ }
204
+ }
205
+ return gaps;
206
+ }
207
+ /**
208
+ * Helper: Calculate risk score (0-100)
209
+ */
210
+ function calculateRiskScore(coverage, overallCoverage) {
211
+ // Find untested high-coupling files
212
+ const untested = coverage.filter((c) => c.coverage === 0 && c.module.match(/extract|adapter|index|types/i)).length;
213
+ // Score based on overall coverage + untested critical files
214
+ let score = Math.max(0, 100 - overallCoverage + untested * 10);
215
+ return Math.min(100, score);
216
+ }
217
+ /**
218
+ * Helper: Generate recommendations
219
+ */
220
+ function generateRecommendations(coverage, gaps) {
221
+ const recommendations = [];
222
+ const criticalGapsCount = gaps.filter((g) => g.severity === "critical").length;
223
+ if (criticalGapsCount > 0) {
224
+ recommendations.push(`🚨 ${criticalGapsCount} critical test gaps: High-coupling files lack tests`);
225
+ }
226
+ const untestedCount = coverage.filter((c) => c.coverage === 0).length;
227
+ if (untestedCount > 0) {
228
+ recommendations.push(`⚠️ ${untestedCount} modules with zero test coverage`);
229
+ }
230
+ const avgCoverage = coverage.reduce((sum, c) => sum + c.coverage, 0) / coverage.length;
231
+ if (avgCoverage < 60) {
232
+ recommendations.push(`💡 Average coverage ${avgCoverage.toFixed(1)}% is below 60%`);
233
+ }
234
+ if (gaps.length === 0) {
235
+ recommendations.push(`✅ No major test coverage gaps detected`);
236
+ }
237
+ return recommendations;
238
+ }
239
+ /**
240
+ * Helper: Write heatmap report to markdown
241
+ */
242
+ async function writeHeatmapReport(heatmap, outputPath) {
243
+ let md = `# Test Coverage Heatmap Report\n\n`;
244
+ md += `Generated: ${heatmap.timestamp}\n\n`;
245
+ md += `## Summary\n`;
246
+ md += `- **Overall Coverage:** ${heatmap.overallCoverage.toFixed(1)}%\n`;
247
+ md += `- **Critical Gaps:** ${heatmap.criticalGaps}\n`;
248
+ md += `- **Risk Score:** ${heatmap.riskScore}/100\n\n`;
249
+ // Coverage heatmap
250
+ md += `## Coverage Heatmap\n`;
251
+ md += `| Module | Coverage | Level | Tests |\n`;
252
+ md += `|--------|----------|-------|-------|\n`;
253
+ for (const m of heatmap.modules) {
254
+ const heat = m.coverage >= 80 ? "🟢" : m.coverage >= 60 ? "🟡" : m.coverage >= 30 ? "🟠" : "🔴";
255
+ md += `| ${m.module} | ${m.coverage.toFixed(1)}% | ${heat} ${m.level} | ${m.testers.length} |\n`;
256
+ }
257
+ md += `\n## Recommendations\n`;
258
+ for (const rec of heatmap.recommendations) {
259
+ md += `- ${rec}\n`;
260
+ }
261
+ await fs.mkdir(path.dirname(outputPath), { recursive: true });
262
+ await fs.writeFile(outputPath, md, "utf8");
263
+ }
264
+ export default generateCoverageHeatmap;
@@ -0,0 +1,62 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { computeProjectDrift } from "../extract/drift.js";
4
+ import { logResolvedProjectPaths, resolveProjectPaths } from "../project-discovery.js";
5
+ export async function runVerifyDrift(options) {
6
+ const resolved = await resolveProjectPaths({
7
+ projectRoot: options.projectRoot,
8
+ backendRoot: options.backendRoot,
9
+ frontendRoot: options.frontendRoot,
10
+ configPath: options.configPath
11
+ });
12
+ logResolvedProjectPaths(resolved);
13
+ const drift = await computeProjectDrift({
14
+ backendRoot: resolved.backendRoot,
15
+ frontendRoot: resolved.frontendRoot,
16
+ configPath: options.configPath
17
+ });
18
+ const config = resolved.config;
19
+ const projectRoot = resolved.workspaceRoot;
20
+ const baselinePath = options.baseline || config.drift?.baselinePath || "specs-out/baseline.json";
21
+ const resolvedBaseline = path.isAbsolute(baselinePath)
22
+ ? baselinePath
23
+ : path.resolve(projectRoot, baselinePath);
24
+ let baselinePayload = null;
25
+ try {
26
+ const raw = await fs.readFile(resolvedBaseline, "utf8");
27
+ baselinePayload = JSON.parse(raw);
28
+ }
29
+ catch (error) {
30
+ console.log(`[Warning] Could not load baseline at ${resolvedBaseline}`);
31
+ }
32
+ const threshold = options.strictThreshold ? parseFloat(options.strictThreshold) : 0.15;
33
+ console.log("=========================================");
34
+ console.log("SpecGuard Drift Verification");
35
+ console.log("=========================================\n");
36
+ console.log(`Current Status: ${drift.status}`);
37
+ console.log(`Current Delta: ${drift.delta.toFixed(4)}`);
38
+ console.log(`Current K_t: ${drift.K_t.toFixed(4)}`);
39
+ let failed = false;
40
+ if (drift.status === "critical") {
41
+ console.error(`\n[ERROR] Drift status is "critical". Reached critical capacity limits.`);
42
+ failed = true;
43
+ }
44
+ if (baselinePayload && baselinePayload.drift) {
45
+ const baselineDelta = baselinePayload.drift.delta ?? 0;
46
+ const shift = Math.abs(drift.delta - baselineDelta);
47
+ console.log(`\nBaseline Delta: ${baselineDelta.toFixed(4)}`);
48
+ console.log(`Coupling Shift: ${shift.toFixed(4)} (Threshold: ${threshold})`);
49
+ if (shift > threshold) {
50
+ console.error(`\n[ERROR] Architectural coupling shift (${shift.toFixed(4)}) exceeded strict threshold (${threshold}).`);
51
+ failed = true;
52
+ }
53
+ }
54
+ console.log("=========================================");
55
+ if (failed) {
56
+ console.error("Verification FAILED. Architectural drift guardrails breached.");
57
+ process.exit(1);
58
+ }
59
+ else {
60
+ console.log("Verification PASSED. Architecture remains within acceptable limits.");
61
+ }
62
+ }