@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,221 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { analyzeBackend } from "../extract/analyzers/backend.js";
4
+ import { analyzeFrontend } from "../extract/analyzers/frontend.js";
5
+ import { analyzeRuntime } from "../extract/runtime.js";
6
+ import { computeDriftReport } from "../extract/drift.js";
7
+ import { buildArchitectureSummary } from "../extract/compress.js";
8
+ import { logResolvedProjectPaths, resolveProjectPaths } from "../project-discovery.js";
9
+ export async function runConstraints(options) {
10
+ const resolved = await resolveProjectPaths({
11
+ projectRoot: options.projectRoot,
12
+ backendRoot: options.backendRoot,
13
+ frontendRoot: options.frontendRoot,
14
+ configPath: options.configPath
15
+ });
16
+ const resolvedBackendRoot = resolved.backendRoot;
17
+ const resolvedFrontendRoot = resolved.frontendRoot;
18
+ const config = resolved.config;
19
+ logResolvedProjectPaths(resolved);
20
+ const backend = await analyzeBackend(resolvedBackendRoot, config);
21
+ const frontend = await analyzeFrontend(resolvedFrontendRoot, config);
22
+ const projectRoot = resolved.workspaceRoot;
23
+ const runtime = await analyzeRuntime(projectRoot, config);
24
+ const drift = await computeDriftReport({
25
+ backendRoot: resolvedBackendRoot,
26
+ modules: backend.modules,
27
+ moduleGraph: backend.moduleGraph,
28
+ fileGraph: backend.fileGraph,
29
+ circularDependencies: backend.circularDependencies,
30
+ config,
31
+ projectRoot
32
+ });
33
+ const architectureSummary = buildArchitectureSummary({
34
+ version: "1.0",
35
+ metadata: {
36
+ generated_at: new Date().toISOString(),
37
+ duration_ms: 0,
38
+ target_backend: resolvedBackendRoot,
39
+ target_frontend: resolvedFrontendRoot
40
+ },
41
+ project: {
42
+ name: deriveProjectName(resolvedBackendRoot),
43
+ workspace_root: resolved.workspaceRoot,
44
+ backend_root: resolvedBackendRoot,
45
+ frontend_root: resolvedFrontendRoot,
46
+ resolution_source: resolved.resolutionSource,
47
+ entrypoints: backend.entrypoints
48
+ },
49
+ modules: backend.modules,
50
+ frontend_files: frontend.files,
51
+ frontend: {
52
+ pages: frontend.pages,
53
+ api_calls: frontend.apiCalls
54
+ },
55
+ endpoints: backend.endpoints,
56
+ data_models: backend.dataModels,
57
+ enums: backend.enums,
58
+ constants: backend.constants,
59
+ endpoint_model_usage: backend.endpointModelUsage,
60
+ cross_stack_contracts: [],
61
+ tasks: backend.tasks,
62
+ runtime,
63
+ data_flows: [],
64
+ tests: [...backend.tests, ...frontend.tests],
65
+ dependencies: {
66
+ module_graph: backend.moduleGraph,
67
+ file_graph: []
68
+ },
69
+ drift,
70
+ analysis: {
71
+ circular_dependencies: backend.circularDependencies,
72
+ orphan_modules: backend.orphanModules,
73
+ orphan_files: backend.orphanFiles,
74
+ frontend_orphan_files: frontend.orphanFiles,
75
+ module_usage: backend.moduleUsage,
76
+ unused_exports: backend.unusedExports,
77
+ frontend_unused_exports: frontend.unusedExports,
78
+ unused_endpoints: [],
79
+ frontend_unused_api_calls: [],
80
+ duplicate_functions: backend.duplicateFunctions,
81
+ similar_functions: backend.similarFunctions,
82
+ test_coverage: {
83
+ untested_source_files: [],
84
+ test_files_missing_source: [],
85
+ coverage_map: []
86
+ },
87
+ endpoint_test_coverage: [],
88
+ function_test_coverage: []
89
+ }
90
+ }, {
91
+ version: "0.2",
92
+ components: frontend.components,
93
+ component_graph: frontend.componentGraph,
94
+ pages: frontend.uxPages
95
+ });
96
+ const cycleModules = Array.from(new Set(backend.circularDependencies.flatMap((cycle) => cycle)));
97
+ const constraints = {
98
+ version: "0.1",
99
+ generated_at: new Date().toISOString(),
100
+ project: {
101
+ name: deriveProjectName(resolvedBackendRoot)
102
+ },
103
+ drift: {
104
+ delta: drift.delta,
105
+ D_t: drift.D_t,
106
+ K_t: drift.K_t,
107
+ status: drift.status,
108
+ graph_level: drift.graph_level,
109
+ alerts: drift.alerts
110
+ },
111
+ capacity: drift.capacity,
112
+ growth: drift.growth,
113
+ cross_layer_edges: drift.details.cross_layer_edges,
114
+ allowed_dependencies: config.drift?.layers ?? {},
115
+ capacity_budgets: config.drift?.capacity ?? {},
116
+ cycle_risk_modules: cycleModules,
117
+ duplicate_functions: backend.duplicateFunctions.slice(0, 20),
118
+ similar_functions: backend.similarFunctions.slice(0, 20),
119
+ related_endpoints: findRelatedEndpoints(backend.endpoints.map((endpoint) => `${endpoint.method} ${endpoint.path}`)),
120
+ modules: backend.modules.map((module) => module.id),
121
+ endpoints: backend.endpoints.map((endpoint) => `${endpoint.method} ${endpoint.path}`),
122
+ models: backend.dataModels.map((model) => model.name),
123
+ architecture_fingerprint: architectureSummary.fingerprint,
124
+ shape_fingerprint: architectureSummary.shape_fingerprint,
125
+ prompt: renderPrompt({
126
+ drift,
127
+ cycleModules,
128
+ allowed: config.drift?.layers ?? {},
129
+ capacity: drift.capacity
130
+ })
131
+ };
132
+ const outputPath = path.resolve(options.output ?? "specs-out/constraints.json");
133
+ await fs.mkdir(path.dirname(outputPath), { recursive: true });
134
+ await fs.writeFile(outputPath, JSON.stringify(constraints, null, 2));
135
+ console.log(`Wrote ${outputPath}`);
136
+ }
137
+ function renderPrompt(params) {
138
+ const lines = [];
139
+ lines.push("SYSTEM:");
140
+ lines.push("You are operating inside a bounded architectural system.");
141
+ lines.push("");
142
+ lines.push("Constraints:");
143
+ lines.push(`- Current delta: ${params.drift.delta.toFixed(4)} (${params.drift.status})`);
144
+ if (params.capacity.total?.budget) {
145
+ lines.push(`- Total capacity: ${params.capacity.total.used}/${params.capacity.total.budget}`);
146
+ }
147
+ if (params.cycleModules.length > 0) {
148
+ lines.push(`- Avoid cycle-risk modules: ${params.cycleModules.join(", ")}`);
149
+ }
150
+ if (Object.keys(params.allowed).length > 0) {
151
+ lines.push("- Allowed dependency flow:");
152
+ for (const [layer, allowed] of Object.entries(params.allowed)) {
153
+ lines.push(` - ${layer} -> ${allowed.join(", ") || "none"}`);
154
+ }
155
+ }
156
+ lines.push("");
157
+ lines.push("Your task:");
158
+ lines.push("- Implement the requested change without increasing cross-layer coupling.");
159
+ lines.push("- Prefer refactoring or reuse over new module creation.");
160
+ lines.push("- Do not introduce new cycles.");
161
+ lines.push("- Return a patch only.");
162
+ return lines.join("\n");
163
+ }
164
+ function findRelatedEndpoints(endpoints) {
165
+ const pairs = [];
166
+ const tokenized = endpoints.map((endpoint) => ({
167
+ endpoint,
168
+ tokens: tokenizeEndpoint(endpoint)
169
+ }));
170
+ const threshold = 0.8;
171
+ const maxPairs = 20;
172
+ for (let i = 0; i < tokenized.length; i += 1) {
173
+ for (let j = i + 1; j < tokenized.length; j += 1) {
174
+ const sim = jaccard(tokenized[i].tokens, tokenized[j].tokens);
175
+ if (sim >= threshold) {
176
+ pairs.push({
177
+ similarity: round(sim, 2),
178
+ endpoints: [tokenized[i].endpoint, tokenized[j].endpoint]
179
+ });
180
+ if (pairs.length >= maxPairs) {
181
+ return pairs;
182
+ }
183
+ }
184
+ }
185
+ }
186
+ return pairs;
187
+ }
188
+ function tokenizeEndpoint(endpoint) {
189
+ return endpoint
190
+ .toLowerCase()
191
+ .replace(/[^a-z0-9/]+/g, " ")
192
+ .split(/[\\/\\s]+/)
193
+ .map((token) => token.trim())
194
+ .filter(Boolean);
195
+ }
196
+ function jaccard(a, b) {
197
+ const setA = new Set(a);
198
+ const setB = new Set(b);
199
+ if (setA.size === 0 || setB.size === 0) {
200
+ return 0;
201
+ }
202
+ let intersection = 0;
203
+ for (const entry of setA) {
204
+ if (setB.has(entry)) {
205
+ intersection += 1;
206
+ }
207
+ }
208
+ const union = setA.size + setB.size - intersection;
209
+ return union === 0 ? 0 : intersection / union;
210
+ }
211
+ function round(value, precision) {
212
+ const factor = 10 ** precision;
213
+ return Math.round(value * factor) / factor;
214
+ }
215
+ function deriveProjectName(backendRoot) {
216
+ const base = path.basename(path.resolve(backendRoot));
217
+ if (base.toLowerCase() === "backend" || base.toLowerCase() === "src") {
218
+ return path.basename(path.dirname(path.resolve(backendRoot)));
219
+ }
220
+ return base || "unknown";
221
+ }
@@ -0,0 +1,101 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import yaml from "js-yaml";
4
+ import { loadArchitectureDiff, loadHeatmap } from "../extract/compress.js";
5
+ import { renderContextBlock } from "../extract/context-block.js";
6
+ import { resolveMachineInputDir } from "../output-layout.js";
7
+ export async function runContext(options) {
8
+ const inputDir = await resolveMachineInputDir(options.input || "specs-out");
9
+ const { architecture, ux } = await loadSnapshots(inputDir);
10
+ const [diff, heatmap] = await Promise.all([
11
+ loadArchitectureDiff(inputDir),
12
+ loadHeatmap(inputDir)
13
+ ]);
14
+ const content = renderContextBlock(architecture, ux, {
15
+ focusQuery: options.focus,
16
+ maxLines: normalizeMaxLines(options.maxLines),
17
+ diff,
18
+ heatmap
19
+ });
20
+ if (!options.output) {
21
+ console.log(content);
22
+ return;
23
+ }
24
+ const outputPath = path.resolve(options.output);
25
+ await fs.mkdir(path.dirname(outputPath), { recursive: true });
26
+ const existing = await readIfExists(outputPath);
27
+ const next = injectIntoAutoContext(existing, content);
28
+ await fs.writeFile(outputPath, next, "utf8");
29
+ console.log(`Wrote ${outputPath}`);
30
+ }
31
+ async function loadSnapshots(inputDir) {
32
+ const architecturePath = path.join(inputDir, "architecture.snapshot.yaml");
33
+ const uxPath = path.join(inputDir, "ux.snapshot.yaml");
34
+ let architectureRaw;
35
+ let uxRaw;
36
+ try {
37
+ [architectureRaw, uxRaw] = await Promise.all([
38
+ fs.readFile(architecturePath, "utf8"),
39
+ fs.readFile(uxPath, "utf8")
40
+ ]);
41
+ }
42
+ catch (error) {
43
+ if (error.code === "ENOENT") {
44
+ throw new Error(`Could not find snapshots in ${inputDir}. Run \`specguard extract\` first.`);
45
+ }
46
+ throw error;
47
+ }
48
+ return {
49
+ architecture: yaml.load(architectureRaw),
50
+ ux: yaml.load(uxRaw)
51
+ };
52
+ }
53
+ async function readIfExists(filePath) {
54
+ try {
55
+ return await fs.readFile(filePath, "utf8");
56
+ }
57
+ catch {
58
+ return "";
59
+ }
60
+ }
61
+ function stripExistingSpecGuardBlocks(content) {
62
+ return content
63
+ .replace(/\n?<!-- guardian:ai-context -->[\s\S]*?<!-- \/guardian:ai-context -->\n?/g, "\n")
64
+ .replace(/\n?<!-- guardian:context generated=.*?-->[\s\S]*?<!-- \/guardian:context -->\n?/g, "\n")
65
+ .replace(/<!-- guardian:auto-context -->[\s\S]*?<!-- \/guardian:auto-context -->/g, "<!-- guardian:auto-context -->\n<!-- /guardian:auto-context -->")
66
+ .replace(/\n{3,}/g, "\n\n");
67
+ }
68
+ /**
69
+ * Inject context into a file that has <!-- guardian:auto-context --> markers.
70
+ * Replaces content between the markers instead of appending.
71
+ */
72
+ function injectIntoAutoContext(existing, contextBlock) {
73
+ const marker = "<!-- guardian:auto-context -->";
74
+ const endMarker = "<!-- /guardian:auto-context -->";
75
+ if (!existing.includes(marker)) {
76
+ // No auto-context markers — fall back to append behavior
77
+ const cleaned = stripExistingSpecGuardBlocks(existing).trim();
78
+ return cleaned.length > 0 ? `${cleaned}\n\n${contextBlock}\n` : `${contextBlock}\n`;
79
+ }
80
+ // Replace content between markers
81
+ const startIdx = existing.indexOf(marker);
82
+ const endIdx = existing.indexOf(endMarker);
83
+ if (startIdx === -1 || endIdx === -1) {
84
+ return existing;
85
+ }
86
+ const before = existing.slice(0, startIdx + marker.length);
87
+ const after = existing.slice(endIdx);
88
+ return `${before}\n${contextBlock}\n${after}`;
89
+ }
90
+ function normalizeMaxLines(value) {
91
+ if (typeof value === "number" && Number.isFinite(value)) {
92
+ return value;
93
+ }
94
+ if (typeof value === "string" && value.trim().length > 0) {
95
+ const parsed = Number.parseInt(value, 10);
96
+ if (Number.isFinite(parsed) && parsed > 0) {
97
+ return parsed;
98
+ }
99
+ }
100
+ return undefined;
101
+ }
@@ -0,0 +1,291 @@
1
+ /**
2
+ * FEATURE 5: DATA FLOW TRACER
3
+ *
4
+ * Follow a single data entity from user input → storage → retrieval → response
5
+ * Visualize complete flow across all layers
6
+ * Identify race conditions, lost updates, missing validation
7
+ *
8
+ * Benchmarking: High complexity
9
+ * Problem Domain: Debugging, Data Integrity, Performance Analysis
10
+ */
11
+ import fs from "node:fs/promises";
12
+ import path from "node:path";
13
+ /**
14
+ * Main function: Trace data flow through codebase
15
+ */
16
+ export async function traceDataFlow(options) {
17
+ const { srcRoot, startPattern, dataEntityName, maxDepth = 10, output } = options;
18
+ // Scan all source files
19
+ const sourceFiles = await scanAllSourceFiles(srcRoot);
20
+ // Find entry points matching pattern
21
+ const entryPoints = findEntryPoints(sourceFiles, startPattern);
22
+ if (entryPoints.length === 0) {
23
+ throw new Error(`No entry points found matching pattern: ${startPattern}`);
24
+ }
25
+ // Build data flow graph from first entry point
26
+ const trace = buildDataFlowGraph(entryPoints[0], dataEntityName, sourceFiles, maxDepth);
27
+ // Analyze for issues
28
+ const issues = analyzeForIssues(trace, sourceFiles);
29
+ trace.issues = issues;
30
+ if (output) {
31
+ await writeTraceReport(trace, output);
32
+ }
33
+ return trace;
34
+ }
35
+ /**
36
+ * Helper: Scan all source files
37
+ */
38
+ async function scanAllSourceFiles(srcRoot) {
39
+ const files = new Map();
40
+ async function walkDir(dir) {
41
+ try {
42
+ const entries = await fs.readdir(dir, { withFileTypes: true });
43
+ for (const entry of entries) {
44
+ const fullPath = path.join(dir, entry.name);
45
+ if (entry.isDirectory() &&
46
+ [".git", "node_modules", "dist", "build", "coverage"].includes(entry.name)) {
47
+ continue;
48
+ }
49
+ if (entry.isDirectory()) {
50
+ await walkDir(fullPath);
51
+ }
52
+ else if (entry.isFile() &&
53
+ [".ts", ".tsx", ".js", ".jsx"].some((ext) => entry.name.endsWith(ext))) {
54
+ const content = await fs.readFile(fullPath, "utf8");
55
+ const relPath = path.relative(srcRoot, fullPath);
56
+ files.set(relPath, content);
57
+ }
58
+ }
59
+ }
60
+ catch {
61
+ // Skip inaccessible directories
62
+ }
63
+ }
64
+ await walkDir(srcRoot);
65
+ return files;
66
+ }
67
+ /**
68
+ * Helper: Find entry points in code
69
+ */
70
+ function findEntryPoints(sourceFiles, pattern) {
71
+ const entryPoints = [];
72
+ const regex = new RegExp(pattern, "gmi");
73
+ for (const [file, content] of sourceFiles.entries()) {
74
+ const lines = content.split("\n");
75
+ lines.forEach((line, idx) => {
76
+ if (regex.test(line)) {
77
+ entryPoints.push({ file, line: idx + 1 });
78
+ }
79
+ });
80
+ }
81
+ return entryPoints;
82
+ }
83
+ /**
84
+ * Helper: Build complete data flow graph
85
+ */
86
+ function buildDataFlowGraph(entryPoint, dataEntity, sourceFiles, maxDepth) {
87
+ const nodes = [];
88
+ const edges = [];
89
+ const visited = new Set();
90
+ // Start node
91
+ nodes.push({
92
+ id: "entry_0",
93
+ type: "input",
94
+ name: `${dataEntity} Input`,
95
+ file: entryPoint.file,
96
+ lineNumber: entryPoint.line,
97
+ operation: "Receive request",
98
+ });
99
+ // Build graph by following data references
100
+ let depth = 0;
101
+ const queue = [
102
+ { nodeId: "entry_0", file: entryPoint.file, entity: dataEntity },
103
+ ];
104
+ while (queue.length > 0 && depth < maxDepth) {
105
+ const { nodeId, file, entity } = queue.shift();
106
+ const key = `${file}:${entity}`;
107
+ if (visited.has(key))
108
+ continue;
109
+ visited.add(key);
110
+ const content = sourceFiles.get(file);
111
+ if (!content)
112
+ continue;
113
+ // Find references to this data entity in this file
114
+ const references = findDataReferences(content, entity);
115
+ for (const ref of references) {
116
+ const newNodeId = `node_${nodes.length}`;
117
+ nodes.push({
118
+ id: newNodeId,
119
+ type: ref.type,
120
+ name: ref.name,
121
+ file,
122
+ lineNumber: ref.line,
123
+ operation: ref.operation,
124
+ });
125
+ edges.push({
126
+ from: nodeId,
127
+ to: newNodeId,
128
+ dataType: ref.dataType || entity,
129
+ transformation: ref.transformation,
130
+ });
131
+ // Find next file to follow
132
+ const nextFile = findNextFile(content, ref.operation, sourceFiles);
133
+ if (nextFile && !visited.has(`${nextFile}:${entity}`)) {
134
+ queue.push({ nodeId: newNodeId, file: nextFile, entity });
135
+ }
136
+ }
137
+ depth++;
138
+ }
139
+ // Calculate complexity
140
+ const complexity = Math.min(10, Math.ceil((nodes.length + edges.length) / 5));
141
+ return {
142
+ startPoint: entryPoint.file,
143
+ endPoint: nodes[nodes.length - 1]?.file || entryPoint.file,
144
+ nodes,
145
+ edges,
146
+ issues: [],
147
+ depth,
148
+ complexity,
149
+ };
150
+ }
151
+ /**
152
+ * Helper: Find references to data entity
153
+ */
154
+ function findDataReferences(content, entity) {
155
+ const references = [];
156
+ const lines = content.split("\n");
157
+ const entityRegex = new RegExp(`\\b${entity}\\b`, "gi");
158
+ lines.forEach((line, idx) => {
159
+ if (!entityRegex.test(line))
160
+ return;
161
+ let type = "processor";
162
+ let operation = "Process";
163
+ if (line.includes("await") || line.includes(".then")) {
164
+ type = "processor";
165
+ operation = "Async operation";
166
+ }
167
+ if (line.match(/(?:insert|save|store|create)\s*\(/)) {
168
+ type = "storage";
169
+ operation = "Save to storage";
170
+ }
171
+ if (line.match(/(?:find|get|select|fetch|retrieve)\s*\(/)) {
172
+ type = "retrieval";
173
+ operation = "Retrieve from storage";
174
+ }
175
+ if (line.match(/return\s+.*\b${entity}\b/)) {
176
+ type = "output";
177
+ operation = "Return response";
178
+ }
179
+ references.push({
180
+ type,
181
+ name: `${type}: ${line.trim().substring(0, 40)}...`,
182
+ line: idx + 1,
183
+ operation,
184
+ });
185
+ });
186
+ return references;
187
+ }
188
+ /**
189
+ * Helper: Find next file to follow
190
+ */
191
+ function findNextFile(content, operation, sourceFiles) {
192
+ // Look for imports of related modules
193
+ const importRegex = /import\s+.*\s+from\s+['"](.+?)['"]/g;
194
+ let match;
195
+ while ((match = importRegex.exec(content)) !== null) {
196
+ const modulePath = match[1];
197
+ // Try to find matching file
198
+ for (const file of sourceFiles.keys()) {
199
+ if (file.includes(modulePath)) {
200
+ return file;
201
+ }
202
+ }
203
+ }
204
+ return null;
205
+ }
206
+ /**
207
+ * Helper: Analyze data flow for issues
208
+ */
209
+ function analyzeForIssues(trace, sourceFiles) {
210
+ const issues = [];
211
+ // Check for missing validation
212
+ const hasValidation = trace.nodes.some((n) => n.operation.toLowerCase().includes("validat")) ||
213
+ trace.edges.some((e) => e.transformation?.toLowerCase().includes("validat"));
214
+ if (!hasValidation) {
215
+ issues.push({
216
+ type: "missing_validation",
217
+ severity: "high",
218
+ location: trace.startPoint,
219
+ description: "Data flow lacks explicit validation step",
220
+ suggestion: "Add validation at input boundary",
221
+ });
222
+ }
223
+ // Check for error handling
224
+ const storageNodes = trace.nodes.filter((n) => n.type.includes("storage"));
225
+ for (const node of storageNodes) {
226
+ const file = sourceFiles.get(node.file);
227
+ if (file && !file.includes("catch") && !file.includes("try")) {
228
+ issues.push({
229
+ type: "unhandled_error",
230
+ severity: "high",
231
+ location: `${node.file}:${node.lineNumber}`,
232
+ description: `Storage operation lacks error handling: ${node.operation}`,
233
+ suggestion: "Wrap storage operation in try-catch block",
234
+ });
235
+ }
236
+ }
237
+ // Check for race conditions in asynchronous flow
238
+ const asyncNodes = trace.nodes.filter((n) => n.operation.toLowerCase().includes("async"));
239
+ if (asyncNodes.length > 1) {
240
+ issues.push({
241
+ type: "race_condition",
242
+ severity: "medium",
243
+ location: trace.nodes[0].file,
244
+ description: "Multiple async operations on same data entity",
245
+ suggestion: "Use locks or atomic operations to prevent race conditions",
246
+ });
247
+ }
248
+ // Check for data mutations
249
+ const edges = trace.edges.filter((e) => e.transformation?.toLowerCase().includes("mutate"));
250
+ if (edges.length > 0) {
251
+ issues.push({
252
+ type: "data_mutation",
253
+ severity: "medium",
254
+ location: trace.nodes[0].file,
255
+ description: "Direct data mutation detected in flow",
256
+ suggestion: "Use immutable patterns (shallow copy, Object.assign)",
257
+ });
258
+ }
259
+ return issues;
260
+ }
261
+ /**
262
+ * Helper: Write trace report to markdown
263
+ */
264
+ async function writeTraceReport(trace, outputPath) {
265
+ let md = `# Data Flow Trace Report\n\n`;
266
+ md += `Start: ${trace.startPoint}\n`;
267
+ md += `End: ${trace.endPoint}\n`;
268
+ md += `Nodes: ${trace.nodes.length} | Edges: ${trace.edges.length}\n`;
269
+ md += `Depth: ${trace.depth} | Complexity: ${trace.complexity}/10\n\n`;
270
+ // Flow diagram
271
+ md += `## Data Flow\n`;
272
+ md += "```\n";
273
+ for (let i = 0; i < trace.nodes.length; i++) {
274
+ const node = trace.nodes[i];
275
+ md += `${i > 0 ? " ↓\n" : ""}${node.type.toUpperCase()}: ${node.operation}`;
276
+ md += ` (${path.basename(node.file)}:${node.lineNumber})\n`;
277
+ }
278
+ md += "```\n\n";
279
+ // Issues
280
+ if (trace.issues.length > 0) {
281
+ md += `## Issues Found (${trace.issues.length})\n`;
282
+ for (const issue of trace.issues) {
283
+ md += `### ${issue.type} - ${issue.severity}\n`;
284
+ md += `${issue.description}\n`;
285
+ md += `**Suggestion:** ${issue.suggestion}\n\n`;
286
+ }
287
+ }
288
+ await fs.mkdir(path.dirname(outputPath), { recursive: true });
289
+ await fs.writeFile(outputPath, md, "utf8");
290
+ }
291
+ export default traceDataFlow;
@@ -0,0 +1,27 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ export async function run() {
4
+ const root = process.cwd();
5
+ const pkgPath = path.join(root, 'package.json');
6
+ let pkg = {};
7
+ try {
8
+ pkg = JSON.parse(await fs.promises.readFile(pkgPath, 'utf8'));
9
+ }
10
+ catch (err) {
11
+ console.warn('No package.json found; using empty');
12
+ }
13
+ const deps = Object.assign({}, pkg.dependencies || {}, pkg.devDependencies || {});
14
+ const result = {
15
+ totalDependencies: Object.keys(deps).length,
16
+ dependencies: Object.keys(deps).map(name => ({ name, version: deps[name], impactScore: Math.round(Math.random() * 100) }))
17
+ };
18
+ const out = path.join(root, 'out', 'dependency-impact.json');
19
+ await fs.promises.mkdir(path.dirname(out), { recursive: true });
20
+ await fs.promises.writeFile(out, JSON.stringify(result, null, 2), 'utf8');
21
+ console.log('Wrote dependency impact to', out);
22
+ return result;
23
+ }
24
+ const _isMain = (typeof require !== 'undefined' && (require.main === module)) || (process.argv[1] && process.argv[1].endsWith('dependency-impact-analyzer.ts'));
25
+ if (_isMain) {
26
+ run().catch(err => { console.error(err); process.exit(1); });
27
+ }