@snapback/cli 1.0.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.
@@ -0,0 +1,1380 @@
1
+ import { __name } from './chunk-WCQVDF3K.js';
2
+ import * as eslintParser from '@typescript-eslint/parser';
3
+ import { parse } from '@babel/parser';
4
+ import traverse from '@babel/traverse';
5
+ import { dirname, relative, basename } from 'path';
6
+
7
+ var SyntaxAnalyzer = class {
8
+ static {
9
+ __name(this, "SyntaxAnalyzer");
10
+ }
11
+ id = "syntax";
12
+ name = "Syntax Analysis";
13
+ filePatterns = [
14
+ "*.ts",
15
+ "*.tsx",
16
+ "*.js",
17
+ "*.jsx"
18
+ ];
19
+ async analyze(context) {
20
+ const startTime = performance.now();
21
+ const issues = [];
22
+ let filesAnalyzed = 0;
23
+ let nodesVisited = 0;
24
+ const parseErrors = [];
25
+ for (const [file, content] of context.contents) {
26
+ if (!this.shouldAnalyzeFile(file)) continue;
27
+ filesAnalyzed++;
28
+ try {
29
+ const ast = eslintParser.parse(content, {
30
+ sourceType: "module",
31
+ ecmaFeatures: {
32
+ jsx: file.endsWith(".tsx") || file.endsWith(".jsx")
33
+ },
34
+ ecmaVersion: "latest",
35
+ // Error recovery mode to get partial AST even with errors
36
+ errorOnUnknownASTType: false
37
+ });
38
+ nodesVisited += this.countNodes(ast);
39
+ this.checkSyntaxPatterns(content, file, issues);
40
+ } catch (error) {
41
+ const parseError = this.extractParseError(error);
42
+ parseErrors.push(`${file}: ${parseError.message}`);
43
+ issues.push({
44
+ id: `syntax/parse-error/${file}/${parseError.line}`,
45
+ severity: "critical",
46
+ type: "SYNTAX_ERROR",
47
+ message: parseError.message,
48
+ file,
49
+ line: parseError.line,
50
+ column: parseError.column,
51
+ fix: "Fix the syntax error to allow parsing"
52
+ });
53
+ }
54
+ }
55
+ return {
56
+ analyzer: this.id,
57
+ success: true,
58
+ issues,
59
+ coverage: filesAnalyzed / Math.max(context.files.length, 1),
60
+ duration: performance.now() - startTime,
61
+ metadata: {
62
+ filesAnalyzed,
63
+ nodesVisited,
64
+ parseErrors
65
+ }
66
+ };
67
+ }
68
+ shouldRun(context) {
69
+ return context.files.some((f) => this.shouldAnalyzeFile(f));
70
+ }
71
+ shouldAnalyzeFile(file) {
72
+ const ext = file.split(".").pop()?.toLowerCase();
73
+ return [
74
+ "ts",
75
+ "tsx",
76
+ "js",
77
+ "jsx"
78
+ ].includes(ext || "");
79
+ }
80
+ /**
81
+ * Extract parse error information from parser exception
82
+ */
83
+ extractParseError(error) {
84
+ if (error instanceof Error) {
85
+ const match = error.message.match(/\((\d+):(\d+)\)/);
86
+ if (match) {
87
+ return {
88
+ message: error.message,
89
+ line: Number.parseInt(match[1], 10),
90
+ column: Number.parseInt(match[2], 10)
91
+ };
92
+ }
93
+ return {
94
+ message: error.message,
95
+ line: 1,
96
+ column: 1
97
+ };
98
+ }
99
+ return {
100
+ message: String(error),
101
+ line: 1,
102
+ column: 1
103
+ };
104
+ }
105
+ /**
106
+ * Count AST nodes for coverage metrics
107
+ */
108
+ countNodes(node) {
109
+ if (!node || typeof node !== "object") return 0;
110
+ let count = 1;
111
+ for (const key of Object.keys(node)) {
112
+ const value = node[key];
113
+ if (Array.isArray(value)) {
114
+ for (const item of value) {
115
+ count += this.countNodes(item);
116
+ }
117
+ } else if (value && typeof value === "object" && "type" in value) {
118
+ count += this.countNodes(value);
119
+ }
120
+ }
121
+ return count;
122
+ }
123
+ /**
124
+ * Check for additional syntax patterns that may indicate issues
125
+ */
126
+ checkSyntaxPatterns(content, file, issues) {
127
+ const lines = content.split("\n");
128
+ for (let i = 0; i < lines.length; i++) {
129
+ const line = lines[i];
130
+ const lineNum = i + 1;
131
+ if (line.includes(";;")) {
132
+ issues.push({
133
+ id: `syntax/double-semicolon/${file}/${lineNum}`,
134
+ severity: "low",
135
+ type: "SYNTAX_WARNING",
136
+ message: "Double semicolon detected",
137
+ file,
138
+ line: lineNum,
139
+ column: line.indexOf(";;") + 1,
140
+ fix: "Remove extra semicolon",
141
+ snippet: line.trim()
142
+ });
143
+ }
144
+ if (/console\.assert\([^,]+,\s*\)/.test(line)) {
145
+ issues.push({
146
+ id: `syntax/empty-assert/${file}/${lineNum}`,
147
+ severity: "medium",
148
+ type: "SYNTAX_WARNING",
149
+ message: "console.assert with empty message",
150
+ file,
151
+ line: lineNum,
152
+ fix: "Add assertion message for debugging",
153
+ snippet: line.trim()
154
+ });
155
+ }
156
+ if (/if\s*\([^=]*=\s*[^=]/.test(line) && !/if\s*\([^=]*[=!]==/.test(line)) {
157
+ const assignMatch = line.match(/if\s*\(\s*(\w+)\s*=\s*[^=]/);
158
+ if (assignMatch) {
159
+ issues.push({
160
+ id: `syntax/assignment-in-condition/${file}/${lineNum}`,
161
+ severity: "medium",
162
+ type: "SYNTAX_WARNING",
163
+ message: "Possible assignment in condition (did you mean ===?)",
164
+ file,
165
+ line: lineNum,
166
+ fix: "Use === for comparison, or wrap in extra parentheses if intentional",
167
+ snippet: line.trim()
168
+ });
169
+ }
170
+ }
171
+ }
172
+ }
173
+ };
174
+ var CompletenessAnalyzer = class {
175
+ static {
176
+ __name(this, "CompletenessAnalyzer");
177
+ }
178
+ id = "completeness";
179
+ name = "Completeness Detection";
180
+ filePatterns = [
181
+ "*.ts",
182
+ "*.tsx",
183
+ "*.js",
184
+ "*.jsx"
185
+ ];
186
+ todoPatterns = [
187
+ /\/\/\s*TODO\b/gi,
188
+ /\/\/\s*FIXME\b/gi,
189
+ /\/\/\s*XXX\b/gi,
190
+ /\/\/\s*HACK\b/gi,
191
+ /\/\*\s*TODO\b/gi,
192
+ /\/\*\s*FIXME\b/gi
193
+ ];
194
+ placeholderPatterns = [
195
+ /throw\s+new\s+Error\s*\(\s*['"`].*not\s*implemented.*['"`]\s*\)/gi,
196
+ /throw\s+new\s+Error\s*\(\s*['"`]TODO.*['"`]\s*\)/gi,
197
+ /NotImplementedError/gi,
198
+ /throw\s+new\s+Error\s*\(\s*['"`]STUB['"`]\s*\)/gi
199
+ ];
200
+ parserOptions = {
201
+ sourceType: "module",
202
+ plugins: [
203
+ "typescript",
204
+ "jsx"
205
+ ],
206
+ errorRecovery: true
207
+ };
208
+ async analyze(context) {
209
+ const startTime = performance.now();
210
+ const issues = [];
211
+ let filesAnalyzed = 0;
212
+ let nodesVisited = 0;
213
+ const parseErrors = [];
214
+ for (const [file, content] of context.contents) {
215
+ if (!this.shouldAnalyzeFile(file)) continue;
216
+ filesAnalyzed++;
217
+ this.checkTodoComments(content, file, issues);
218
+ this.checkPlaceholderPatterns(content, file, issues);
219
+ try {
220
+ const ast = parse(content, {
221
+ ...this.parserOptions,
222
+ plugins: this.getPluginsForFile(file)
223
+ });
224
+ const result = this.analyzeAST(ast, content, file);
225
+ issues.push(...result.issues);
226
+ nodesVisited += result.nodesVisited;
227
+ } catch (error) {
228
+ parseErrors.push(`${file}: ${error instanceof Error ? error.message : String(error)}`);
229
+ }
230
+ }
231
+ return {
232
+ analyzer: this.id,
233
+ success: true,
234
+ issues,
235
+ coverage: filesAnalyzed / Math.max(context.files.length, 1),
236
+ duration: performance.now() - startTime,
237
+ metadata: {
238
+ filesAnalyzed,
239
+ nodesVisited,
240
+ patternsChecked: [
241
+ "TODO",
242
+ "FIXME",
243
+ "EMPTY_CATCH",
244
+ "EMPTY_FUNCTION",
245
+ "NOT_IMPLEMENTED",
246
+ "PLACEHOLDER"
247
+ ],
248
+ parseErrors
249
+ }
250
+ };
251
+ }
252
+ shouldRun(context) {
253
+ return context.files.some((f) => this.shouldAnalyzeFile(f));
254
+ }
255
+ shouldAnalyzeFile(file) {
256
+ const ext = file.split(".").pop()?.toLowerCase();
257
+ return [
258
+ "ts",
259
+ "tsx",
260
+ "js",
261
+ "jsx"
262
+ ].includes(ext || "");
263
+ }
264
+ getPluginsForFile(file) {
265
+ const plugins = [
266
+ "typescript"
267
+ ];
268
+ if (file.endsWith(".tsx") || file.endsWith(".jsx")) {
269
+ plugins.push("jsx");
270
+ }
271
+ return plugins;
272
+ }
273
+ /**
274
+ * Check for TODO/FIXME comments
275
+ */
276
+ checkTodoComments(content, file, issues) {
277
+ const lines = content.split("\n");
278
+ for (let i = 0; i < lines.length; i++) {
279
+ const line = lines[i];
280
+ const lineNum = i + 1;
281
+ for (const pattern of this.todoPatterns) {
282
+ pattern.lastIndex = 0;
283
+ if (pattern.test(line)) {
284
+ const todoContent = line.trim().slice(0, 100);
285
+ issues.push({
286
+ id: `completeness/todo/${file}/${lineNum}`,
287
+ severity: "medium",
288
+ type: "INCOMPLETE_IMPLEMENTATION",
289
+ message: `TODO/FIXME: ${todoContent}`,
290
+ file,
291
+ line: lineNum,
292
+ snippet: todoContent
293
+ });
294
+ break;
295
+ }
296
+ }
297
+ }
298
+ }
299
+ /**
300
+ * Check for placeholder/stub patterns
301
+ */
302
+ checkPlaceholderPatterns(content, file, issues) {
303
+ const lines = content.split("\n");
304
+ for (let i = 0; i < lines.length; i++) {
305
+ const line = lines[i];
306
+ const lineNum = i + 1;
307
+ for (const pattern of this.placeholderPatterns) {
308
+ pattern.lastIndex = 0;
309
+ if (pattern.test(line)) {
310
+ issues.push({
311
+ id: `completeness/placeholder/${file}/${lineNum}`,
312
+ severity: "high",
313
+ type: "INCOMPLETE_IMPLEMENTATION",
314
+ message: 'Placeholder implementation: "not implemented" or similar',
315
+ file,
316
+ line: lineNum,
317
+ fix: "Implement the functionality or remove the placeholder",
318
+ snippet: line.trim().slice(0, 100)
319
+ });
320
+ break;
321
+ }
322
+ }
323
+ }
324
+ }
325
+ /**
326
+ * AST-based detection of empty/incomplete code
327
+ */
328
+ analyzeAST(ast, _content, file) {
329
+ const issues = [];
330
+ let nodesVisited = 0;
331
+ traverse(ast, {
332
+ enter() {
333
+ nodesVisited++;
334
+ },
335
+ // Empty catch blocks
336
+ CatchClause: /* @__PURE__ */ __name((path) => {
337
+ const body = path.node.body;
338
+ if (body.body.length === 0) {
339
+ issues.push({
340
+ id: `completeness/empty-catch/${file}/${path.node.loc?.start.line}`,
341
+ severity: "medium",
342
+ type: "INCOMPLETE_IMPLEMENTATION",
343
+ message: "Empty catch block - errors silently swallowed",
344
+ file,
345
+ line: path.node.loc?.start.line,
346
+ fix: "Add error handling, rethrow, or log the error"
347
+ });
348
+ } else if (body.body.length === 1) {
349
+ const stmt = body.body[0];
350
+ if (stmt.type === "EmptyStatement") {
351
+ issues.push({
352
+ id: `completeness/empty-catch/${file}/${path.node.loc?.start.line}`,
353
+ severity: "medium",
354
+ type: "INCOMPLETE_IMPLEMENTATION",
355
+ message: "Catch block contains only empty statement",
356
+ file,
357
+ line: path.node.loc?.start.line,
358
+ fix: "Add proper error handling"
359
+ });
360
+ }
361
+ }
362
+ }, "CatchClause"),
363
+ // Empty function bodies (excluding type declarations and interface methods)
364
+ FunctionDeclaration: /* @__PURE__ */ __name((path) => {
365
+ if (path.node.body.body.length === 0) {
366
+ const funcName = path.node.id?.name || "anonymous";
367
+ {
368
+ issues.push({
369
+ id: `completeness/empty-fn/${file}/${path.node.loc?.start.line}`,
370
+ severity: "medium",
371
+ type: "INCOMPLETE_IMPLEMENTATION",
372
+ message: `Empty function body: ${funcName}()`,
373
+ file,
374
+ line: path.node.loc?.start.line,
375
+ fix: "Implement the function or mark as abstract/stub if intentional"
376
+ });
377
+ }
378
+ }
379
+ }, "FunctionDeclaration"),
380
+ // Empty method bodies
381
+ ClassMethod: /* @__PURE__ */ __name((path) => {
382
+ if (path.node.abstract) return;
383
+ if (path.node.kind === "get" || path.node.kind === "set") return;
384
+ const body = path.node.body;
385
+ if (body && body.body.length === 0) {
386
+ const methodName = path.node.key.type === "Identifier" ? path.node.key.name : "anonymous";
387
+ if (methodName === "constructor") return;
388
+ issues.push({
389
+ id: `completeness/empty-method/${file}/${path.node.loc?.start.line}`,
390
+ severity: "medium",
391
+ type: "INCOMPLETE_IMPLEMENTATION",
392
+ message: `Empty method body: ${methodName}()`,
393
+ file,
394
+ line: path.node.loc?.start.line,
395
+ fix: "Implement the method or mark as abstract if intentional"
396
+ });
397
+ }
398
+ }, "ClassMethod"),
399
+ // Arrow functions that just throw or are empty (might be intentional)
400
+ ArrowFunctionExpression: /* @__PURE__ */ __name((path) => {
401
+ const body = path.node.body;
402
+ if (body.type === "BlockStatement" && body.body.length === 0) {
403
+ const parent = path.parent;
404
+ if (parent.type === "VariableDeclarator") {
405
+ const varName = parent.id.type === "Identifier" ? parent.id.name : "anonymous";
406
+ issues.push({
407
+ id: `completeness/empty-arrow/${file}/${path.node.loc?.start.line}`,
408
+ severity: "low",
409
+ type: "INCOMPLETE_IMPLEMENTATION",
410
+ message: `Empty arrow function: ${varName}`,
411
+ file,
412
+ line: path.node.loc?.start.line,
413
+ fix: "Implement the function or use () => {} if intentionally empty"
414
+ });
415
+ }
416
+ }
417
+ }, "ArrowFunctionExpression"),
418
+ // Check for console.log that might be debug code
419
+ CallExpression: /* @__PURE__ */ __name((path) => {
420
+ const callee = path.node.callee;
421
+ if (callee.type === "MemberExpression" && callee.object.type === "Identifier" && callee.object.name === "console" && callee.property.type === "Identifier" && callee.property.name === "log") {
422
+ const firstArg = path.node.arguments[0];
423
+ if (firstArg && firstArg.type === "StringLiteral") {
424
+ const msg = firstArg.value.toLowerCase();
425
+ if (msg.includes("debug") || msg.includes("test") || msg.includes("todo") || msg.includes("remove")) {
426
+ issues.push({
427
+ id: `completeness/debug-log/${file}/${path.node.loc?.start.line}`,
428
+ severity: "low",
429
+ type: "DEBUG_CODE",
430
+ message: `Debug console.log left in code: "${firstArg.value.slice(0, 50)}"`,
431
+ file,
432
+ line: path.node.loc?.start.line,
433
+ fix: "Remove debug logging before commit"
434
+ });
435
+ }
436
+ }
437
+ }
438
+ }, "CallExpression")
439
+ });
440
+ return {
441
+ issues,
442
+ nodesVisited
443
+ };
444
+ }
445
+ };
446
+ var EXPORT_PATTERNS = [
447
+ /export\s+(const|function|class|interface|type|enum)\s+(\w+)/g,
448
+ /export\s+default\s+(function|class)?\s*(\w+)?/g,
449
+ /export\s+\{([^}]+)\}/g
450
+ ];
451
+ var PERFORMANCE_PATTERNS = [
452
+ {
453
+ pattern: /\.forEach\s*\(/g,
454
+ type: "computation",
455
+ risk: "low"
456
+ },
457
+ {
458
+ pattern: /for\s*\(\s*let\s+\w+\s*=\s*0/g,
459
+ type: "computation",
460
+ risk: "low"
461
+ },
462
+ {
463
+ pattern: /while\s*\(/g,
464
+ type: "computation",
465
+ risk: "medium"
466
+ },
467
+ {
468
+ pattern: /async\s+function|await\s+/g,
469
+ type: "io",
470
+ risk: "medium"
471
+ },
472
+ {
473
+ pattern: /new\s+(Map|Set|Array)\s*\(/g,
474
+ type: "memory",
475
+ risk: "low"
476
+ },
477
+ {
478
+ pattern: /JSON\.(parse|stringify)/g,
479
+ type: "computation",
480
+ risk: "medium"
481
+ },
482
+ {
483
+ pattern: /readFileSync|writeFileSync/g,
484
+ type: "io",
485
+ risk: "high"
486
+ },
487
+ {
488
+ pattern: /spawn|exec\s*\(/g,
489
+ type: "io",
490
+ risk: "high"
491
+ },
492
+ {
493
+ pattern: /import\s*\(/g,
494
+ type: "bundle",
495
+ risk: "low"
496
+ },
497
+ {
498
+ pattern: /require\s*\(/g,
499
+ type: "bundle",
500
+ risk: "medium"
501
+ }
502
+ ];
503
+ var TEST_FILE_PATTERNS = [
504
+ /\.test\.[tj]sx?$/,
505
+ /\.spec\.[tj]sx?$/,
506
+ /__tests__\//,
507
+ /test\//,
508
+ /tests\//
509
+ ];
510
+ var ChangeImpactAnalyzer = class {
511
+ static {
512
+ __name(this, "ChangeImpactAnalyzer");
513
+ }
514
+ id = "change-impact";
515
+ name = "Change Impact Analyzer";
516
+ filePatterns = [
517
+ "**/*.ts",
518
+ "**/*.tsx",
519
+ "**/*.js",
520
+ "**/*.jsx"
521
+ ];
522
+ workspaceRoot;
523
+ dependencyGraph = /* @__PURE__ */ new Map();
524
+ reverseDependencyGraph = /* @__PURE__ */ new Map();
525
+ constructor(workspaceRoot) {
526
+ this.workspaceRoot = workspaceRoot;
527
+ }
528
+ /**
529
+ * Check if this analyzer should run
530
+ */
531
+ shouldRun(context) {
532
+ return context.files.some((f) => this.filePatterns.some((p) => new RegExp(p.replace(/\*/g, ".*")).test(f)));
533
+ }
534
+ /**
535
+ * Run impact analysis
536
+ */
537
+ async analyze(context) {
538
+ const start = Date.now();
539
+ const issues = [];
540
+ try {
541
+ await this.buildDependencyGraph(context);
542
+ for (const file of context.files) {
543
+ const content = context.contents.get(file);
544
+ if (!content) {
545
+ continue;
546
+ }
547
+ const breakingChanges = this.detectBreakingChanges(content, file);
548
+ for (const bc of breakingChanges) {
549
+ issues.push({
550
+ id: `impact/breaking/${bc.type}/${file}/${bc.symbol}`,
551
+ severity: bc.severity,
552
+ type: `BREAKING_${bc.type.toUpperCase()}`,
553
+ message: bc.description,
554
+ file,
555
+ fix: bc.migration
556
+ });
557
+ }
558
+ const perfImpacts = this.detectPerformanceImpacts(content, file);
559
+ for (const pi of perfImpacts) {
560
+ if (pi.risk === "high" || pi.risk === "critical") {
561
+ issues.push({
562
+ id: `impact/perf/${pi.type}/${file}/${pi.component}`,
563
+ severity: pi.risk === "critical" ? "critical" : "high",
564
+ type: `PERF_${pi.type.toUpperCase()}`,
565
+ message: pi.description,
566
+ file,
567
+ fix: pi.recommendation
568
+ });
569
+ }
570
+ }
571
+ const affectedTests = this.findAffectedTests(file);
572
+ if (affectedTests.length > 5) {
573
+ issues.push({
574
+ id: `impact/tests/${file}`,
575
+ severity: "medium",
576
+ type: "HIGH_TEST_IMPACT",
577
+ message: `Change affects ${affectedTests.length} test files - consider running full test suite`,
578
+ file
579
+ });
580
+ }
581
+ }
582
+ return {
583
+ analyzer: this.id,
584
+ success: true,
585
+ issues,
586
+ coverage: 1,
587
+ duration: Date.now() - start,
588
+ metadata: {
589
+ filesAnalyzed: context.files.length
590
+ }
591
+ };
592
+ } catch (error) {
593
+ return {
594
+ analyzer: this.id,
595
+ success: false,
596
+ issues: [
597
+ {
598
+ id: "impact/error",
599
+ severity: "high",
600
+ type: "ANALYSIS_ERROR",
601
+ message: error instanceof Error ? error.message : String(error)
602
+ }
603
+ ],
604
+ coverage: 0,
605
+ duration: Date.now() - start
606
+ };
607
+ }
608
+ }
609
+ /**
610
+ * Get full impact analysis (more detailed than standard analyze)
611
+ */
612
+ async getFullImpact(files, contents) {
613
+ const start = Date.now();
614
+ const context = {
615
+ workspaceRoot: this.workspaceRoot,
616
+ files,
617
+ contents
618
+ };
619
+ await this.buildDependencyGraph(context);
620
+ const affectedTests = [];
621
+ const breakingChanges = [];
622
+ const performanceImpacts = [];
623
+ const dependentFiles = [];
624
+ const recommendations = [];
625
+ for (const file of files) {
626
+ const content = contents.get(file) || "";
627
+ const tests = this.findAffectedTests(file);
628
+ affectedTests.push(...tests);
629
+ const breaks = this.detectBreakingChanges(content, file);
630
+ breakingChanges.push(...breaks);
631
+ const perfs = this.detectPerformanceImpacts(content, file);
632
+ performanceImpacts.push(...perfs);
633
+ const deps = this.findDependentFiles(file);
634
+ dependentFiles.push(...deps);
635
+ }
636
+ const impactScore = this.calculateImpactScore(affectedTests, breakingChanges, performanceImpacts, dependentFiles);
637
+ if (breakingChanges.length > 0) {
638
+ recommendations.push(`\u26A0\uFE0F ${breakingChanges.length} breaking change(s) detected - update dependent code`);
639
+ }
640
+ if (affectedTests.length > 10) {
641
+ recommendations.push(`\u{1F9EA} Run full test suite - ${affectedTests.length} tests potentially affected`);
642
+ }
643
+ if (performanceImpacts.some((p) => p.risk === "high" || p.risk === "critical")) {
644
+ recommendations.push("\u26A1 Performance-sensitive code modified - run benchmarks");
645
+ }
646
+ if (dependentFiles.length > 20) {
647
+ recommendations.push("\u{1F517} High ripple effect - consider incremental rollout");
648
+ }
649
+ return {
650
+ filesAnalyzed: files.length,
651
+ affectedTests: this.dedupeItems(affectedTests),
652
+ breakingChanges,
653
+ performanceImpacts,
654
+ dependentFiles: this.dedupeItems(dependentFiles),
655
+ impactScore,
656
+ recommendations,
657
+ duration: Date.now() - start
658
+ };
659
+ }
660
+ // =========================================================================
661
+ // Private Methods
662
+ // =========================================================================
663
+ /**
664
+ * Build dependency graph from file contents
665
+ */
666
+ async buildDependencyGraph(context) {
667
+ this.dependencyGraph.clear();
668
+ this.reverseDependencyGraph.clear();
669
+ for (const file of context.files) {
670
+ const content = context.contents.get(file);
671
+ if (!content) {
672
+ continue;
673
+ }
674
+ const imports = this.extractImports(content, file);
675
+ this.dependencyGraph.set(file, imports);
676
+ for (const imp of imports) {
677
+ const existing = this.reverseDependencyGraph.get(imp) || [];
678
+ existing.push(file);
679
+ this.reverseDependencyGraph.set(imp, existing);
680
+ }
681
+ }
682
+ }
683
+ /**
684
+ * Extract import statements from file content
685
+ */
686
+ extractImports(content, fromFile) {
687
+ const imports = [];
688
+ const importRegex = /import\s+(?:.*?\s+from\s+)?['"]([^'"]+)['"]/g;
689
+ const requireRegex = /require\s*\(['"]([^'"]+)['"]\)/g;
690
+ let match;
691
+ while ((match = importRegex.exec(content)) !== null) {
692
+ const importPath = this.resolveImportPath(match[1], fromFile);
693
+ if (importPath) {
694
+ imports.push(importPath);
695
+ }
696
+ }
697
+ while ((match = requireRegex.exec(content)) !== null) {
698
+ const importPath = this.resolveImportPath(match[1], fromFile);
699
+ if (importPath) {
700
+ imports.push(importPath);
701
+ }
702
+ }
703
+ return imports;
704
+ }
705
+ /**
706
+ * Resolve import path to absolute file path
707
+ */
708
+ resolveImportPath(importPath, fromFile) {
709
+ if (!importPath.startsWith(".") && !importPath.startsWith("/")) {
710
+ return null;
711
+ }
712
+ const dir = dirname(fromFile);
713
+ const extensions = [
714
+ ".ts",
715
+ ".tsx",
716
+ ".js",
717
+ ".jsx",
718
+ "/index.ts",
719
+ "/index.tsx",
720
+ "/index.js"
721
+ ];
722
+ for (const ext of extensions) {
723
+ const resolved = `${dir}/${importPath}${ext}`.replace(/\/\.\//g, "/");
724
+ return resolved;
725
+ }
726
+ return null;
727
+ }
728
+ /**
729
+ * Find test files that might be affected by a change
730
+ */
731
+ findAffectedTests(file) {
732
+ const tests = [];
733
+ const relPath = relative(this.workspaceRoot, file);
734
+ const fileName = basename(file).replace(/\.[tj]sx?$/, "");
735
+ const directTestPatterns = [
736
+ `${fileName}.test.ts`,
737
+ `${fileName}.test.tsx`,
738
+ `${fileName}.spec.ts`,
739
+ `${fileName}.spec.tsx`,
740
+ `__tests__/${fileName}.test.ts`,
741
+ `__tests__/${fileName}.test.tsx`
742
+ ];
743
+ for (const pattern of directTestPatterns) {
744
+ tests.push({
745
+ path: pattern,
746
+ reason: "Direct test file for changed source",
747
+ level: "high"
748
+ });
749
+ }
750
+ const importers = this.reverseDependencyGraph.get(file) || [];
751
+ for (const importer of importers) {
752
+ if (this.isTestFile(importer)) {
753
+ tests.push({
754
+ path: relative(this.workspaceRoot, importer),
755
+ reason: "Test file imports changed module",
756
+ level: "medium"
757
+ });
758
+ }
759
+ }
760
+ if (relPath.includes("/core/") || relPath.includes("/services/")) {
761
+ tests.push({
762
+ path: "**/*.integration.test.ts",
763
+ reason: "Core module change may affect integration tests",
764
+ level: "low"
765
+ });
766
+ }
767
+ return tests;
768
+ }
769
+ /**
770
+ * Check if a file is a test file
771
+ */
772
+ isTestFile(file) {
773
+ return TEST_FILE_PATTERNS.some((p) => p.test(file));
774
+ }
775
+ /**
776
+ * Detect breaking changes in content
777
+ */
778
+ detectBreakingChanges(content, file) {
779
+ const breaks = [];
780
+ for (const pattern of EXPORT_PATTERNS) {
781
+ const regex = new RegExp(pattern.source, pattern.flags);
782
+ let match2;
783
+ while ((match2 = regex.exec(content)) !== null) {
784
+ const symbolName = match2[2] || match2[1];
785
+ if (symbolName) {
786
+ breaks.push({
787
+ type: "export",
788
+ symbol: symbolName,
789
+ file,
790
+ description: `Exported symbol '${symbolName}' may have changed`,
791
+ severity: "medium",
792
+ migration: `Verify consumers of '${symbolName}' are updated`
793
+ });
794
+ }
795
+ }
796
+ }
797
+ const interfaceRegex = /(?:export\s+)?interface\s+(\w+)\s*\{([^}]+)\}/g;
798
+ let match;
799
+ while ((match = interfaceRegex.exec(content)) !== null) {
800
+ const interfaceName = match[1];
801
+ const body = match[2];
802
+ if (body.includes("?:") || body.includes(": ")) {
803
+ breaks.push({
804
+ type: "type",
805
+ symbol: interfaceName,
806
+ file,
807
+ description: `Interface '${interfaceName}' definition changed`,
808
+ severity: "medium"
809
+ });
810
+ }
811
+ }
812
+ return breaks;
813
+ }
814
+ /**
815
+ * Detect performance-sensitive code changes
816
+ */
817
+ detectPerformanceImpacts(content, file) {
818
+ const impacts = [];
819
+ for (const { pattern, type, risk } of PERFORMANCE_PATTERNS) {
820
+ const regex = new RegExp(pattern.source, pattern.flags);
821
+ let match;
822
+ while ((match = regex.exec(content)) !== null) {
823
+ impacts.push({
824
+ type,
825
+ description: `${type} operation detected: ${match[0]}`,
826
+ risk,
827
+ component: basename(file),
828
+ recommendation: this.getPerformanceRecommendation(type)
829
+ });
830
+ }
831
+ }
832
+ return impacts;
833
+ }
834
+ /**
835
+ * Get recommendation for performance issue type
836
+ */
837
+ getPerformanceRecommendation(type) {
838
+ switch (type) {
839
+ case "hotpath":
840
+ return "Consider memoization or caching for hot paths";
841
+ case "memory":
842
+ return "Monitor memory usage, consider object pooling";
843
+ case "io":
844
+ return "Use async operations, consider batching";
845
+ case "computation":
846
+ return "Profile for bottlenecks, consider Web Workers";
847
+ case "bundle":
848
+ return "Use dynamic imports for code splitting";
849
+ default:
850
+ return "Profile before optimizing";
851
+ }
852
+ }
853
+ /**
854
+ * Find files that depend on changed file
855
+ */
856
+ findDependentFiles(file) {
857
+ const dependents = [];
858
+ const visited = /* @__PURE__ */ new Set();
859
+ const traverse3 = /* @__PURE__ */ __name((current, depth) => {
860
+ if (visited.has(current) || depth > 3) {
861
+ return;
862
+ }
863
+ visited.add(current);
864
+ const importers = this.reverseDependencyGraph.get(current) || [];
865
+ for (const importer of importers) {
866
+ dependents.push({
867
+ path: relative(this.workspaceRoot, importer),
868
+ reason: depth === 0 ? "Directly imports changed file" : `Transitive dependency (depth ${depth})`,
869
+ level: depth === 0 ? "high" : depth === 1 ? "medium" : "low"
870
+ });
871
+ traverse3(importer, depth + 1);
872
+ }
873
+ }, "traverse");
874
+ traverse3(file, 0);
875
+ return dependents;
876
+ }
877
+ /**
878
+ * Calculate overall impact score
879
+ */
880
+ calculateImpactScore(tests, breaks, perfs, deps) {
881
+ let score = 0;
882
+ score += Math.min(tests.length * 0.05, 0.25);
883
+ score += Math.min(breaks.length * 0.15, 0.35);
884
+ score += Math.min(perfs.filter((p) => p.risk === "high").length * 0.1, 0.2);
885
+ score += Math.min(deps.length * 0.02, 0.2);
886
+ return Math.min(score, 1);
887
+ }
888
+ /**
889
+ * Deduplicate impact items
890
+ */
891
+ dedupeItems(items) {
892
+ const seen = /* @__PURE__ */ new Set();
893
+ return items.filter((item) => {
894
+ if (seen.has(item.path)) {
895
+ return false;
896
+ }
897
+ seen.add(item.path);
898
+ return true;
899
+ });
900
+ }
901
+ };
902
+ function createChangeImpactAnalyzer(workspaceRoot) {
903
+ return new ChangeImpactAnalyzer(workspaceRoot);
904
+ }
905
+ __name(createChangeImpactAnalyzer, "createChangeImpactAnalyzer");
906
+ var SecurityAnalyzer = class {
907
+ static {
908
+ __name(this, "SecurityAnalyzer");
909
+ }
910
+ id = "security";
911
+ name = "Security Analysis";
912
+ filePatterns = [
913
+ "*.ts",
914
+ "*.tsx",
915
+ "*.js",
916
+ "*.jsx"
917
+ ];
918
+ parserOptions = {
919
+ sourceType: "module",
920
+ plugins: [
921
+ "typescript",
922
+ "jsx"
923
+ ],
924
+ errorRecovery: true
925
+ };
926
+ async analyze(context) {
927
+ const startTime = performance.now();
928
+ const issues = [];
929
+ let filesAnalyzed = 0;
930
+ let nodesVisited = 0;
931
+ const parseErrors = [];
932
+ for (const [file, content] of context.contents) {
933
+ if (!this.shouldAnalyzeFile(file)) continue;
934
+ filesAnalyzed++;
935
+ try {
936
+ const ast = parse(content, {
937
+ ...this.parserOptions,
938
+ plugins: this.getPluginsForFile(file)
939
+ });
940
+ const fileIssues = this.analyzeAST(ast, content, file);
941
+ issues.push(...fileIssues.issues);
942
+ nodesVisited += fileIssues.nodesVisited;
943
+ } catch (error) {
944
+ parseErrors.push(`${file}: ${error instanceof Error ? error.message : String(error)}`);
945
+ issues.push({
946
+ id: `security/parse-error/${file}`,
947
+ severity: "info",
948
+ type: "PARSE_ERROR",
949
+ message: `Could not parse for security analysis: ${error instanceof Error ? error.message : String(error)}`,
950
+ file
951
+ });
952
+ }
953
+ }
954
+ return {
955
+ analyzer: this.id,
956
+ success: true,
957
+ issues,
958
+ coverage: filesAnalyzed / Math.max(context.files.length, 1),
959
+ duration: performance.now() - startTime,
960
+ metadata: {
961
+ filesAnalyzed,
962
+ nodesVisited,
963
+ patternsChecked: [
964
+ "UNSAFE_EVAL",
965
+ "PATH_TRAVERSAL",
966
+ "MISSING_SIGNAL_HANDLER",
967
+ "COMMAND_INJECTION",
968
+ "SQL_INJECTION",
969
+ "XSS_RISK",
970
+ "HARDCODED_SECRET",
971
+ "UNSAFE_REGEX"
972
+ ],
973
+ parseErrors
974
+ }
975
+ };
976
+ }
977
+ shouldRun(context) {
978
+ return context.files.some((f) => this.shouldAnalyzeFile(f));
979
+ }
980
+ shouldAnalyzeFile(file) {
981
+ const ext = file.split(".").pop()?.toLowerCase();
982
+ return [
983
+ "ts",
984
+ "tsx",
985
+ "js",
986
+ "jsx"
987
+ ].includes(ext || "");
988
+ }
989
+ getPluginsForFile(file) {
990
+ const plugins = [
991
+ "typescript"
992
+ ];
993
+ if (file.endsWith(".tsx") || file.endsWith(".jsx")) {
994
+ plugins.push("jsx");
995
+ }
996
+ return plugins;
997
+ }
998
+ /**
999
+ * Analyze AST for security issues
1000
+ */
1001
+ analyzeAST(ast, content, file) {
1002
+ const issues = [];
1003
+ let nodesVisited = 0;
1004
+ const fileContext = {
1005
+ isDaemon: false,
1006
+ hasSignalHandler: false};
1007
+ fileContext.isDaemon = content.includes(".listen(") || file.includes("daemon") || file.includes("server") || file.includes("worker");
1008
+ traverse(ast, {
1009
+ enter() {
1010
+ nodesVisited++;
1011
+ },
1012
+ // Detect eval()
1013
+ CallExpression: /* @__PURE__ */ __name((path) => {
1014
+ const callee = path.node.callee;
1015
+ if (callee.type === "Identifier" && callee.name === "eval") {
1016
+ issues.push({
1017
+ id: `security/eval/${file}/${path.node.loc?.start.line}`,
1018
+ severity: "critical",
1019
+ type: "UNSAFE_EVAL",
1020
+ message: "eval() allows arbitrary code execution",
1021
+ file,
1022
+ line: path.node.loc?.start.line,
1023
+ column: path.node.loc?.start.column,
1024
+ fix: "Use JSON.parse() for data or refactor logic to avoid eval"
1025
+ });
1026
+ }
1027
+ if (callee.type === "Identifier" && callee.name === "Function") {
1028
+ issues.push({
1029
+ id: `security/function-constructor/${file}/${path.node.loc?.start.line}`,
1030
+ severity: "critical",
1031
+ type: "UNSAFE_EVAL",
1032
+ message: "new Function() is equivalent to eval() and allows arbitrary code execution",
1033
+ file,
1034
+ line: path.node.loc?.start.line,
1035
+ column: path.node.loc?.start.column,
1036
+ fix: "Refactor to avoid dynamic code generation"
1037
+ });
1038
+ }
1039
+ if (callee.type === "Identifier" && (callee.name === "setTimeout" || callee.name === "setInterval")) {
1040
+ const firstArg = path.node.arguments[0];
1041
+ if (firstArg && firstArg.type === "StringLiteral") {
1042
+ issues.push({
1043
+ id: `security/string-timer/${file}/${path.node.loc?.start.line}`,
1044
+ severity: "high",
1045
+ type: "UNSAFE_EVAL",
1046
+ message: `${callee.name} with string argument executes code like eval()`,
1047
+ file,
1048
+ line: path.node.loc?.start.line,
1049
+ fix: "Pass a function instead of a string"
1050
+ });
1051
+ }
1052
+ }
1053
+ if (callee.type === "Identifier" && (callee.name === "exec" || callee.name === "execSync")) {
1054
+ const firstArg = path.node.arguments[0];
1055
+ if (firstArg && !this.isStaticString(firstArg)) {
1056
+ issues.push({
1057
+ id: `security/command-injection/${file}/${path.node.loc?.start.line}`,
1058
+ severity: "high",
1059
+ type: "COMMAND_INJECTION",
1060
+ message: "exec with dynamic command - potential command injection",
1061
+ file,
1062
+ line: path.node.loc?.start.line,
1063
+ fix: "Validate/sanitize input or use execFile with explicit arguments"
1064
+ });
1065
+ }
1066
+ }
1067
+ if (callee.type === "MemberExpression" && callee.object.type === "Identifier" && callee.object.name === "process" && callee.property.type === "Identifier" && callee.property.name === "on") {
1068
+ const firstArg = path.node.arguments[0];
1069
+ if (firstArg && firstArg.type === "StringLiteral") {
1070
+ if (firstArg.value === "SIGTERM" || firstArg.value === "SIGINT") {
1071
+ fileContext.hasSignalHandler = true;
1072
+ }
1073
+ }
1074
+ }
1075
+ }, "CallExpression"),
1076
+ // Detect fs operations with dynamic paths
1077
+ MemberExpression: /* @__PURE__ */ __name((path) => {
1078
+ const node = path.node;
1079
+ if (node.object.type === "Identifier" && (node.object.name === "fs" || node.object.name === "fsp")) {
1080
+ const parent = path.parentPath;
1081
+ if (parent.isCallExpression()) {
1082
+ const methodName = node.property.type === "Identifier" ? node.property.name : node.property.value;
1083
+ const pathMethods = [
1084
+ "readFile",
1085
+ "readFileSync",
1086
+ "writeFile",
1087
+ "writeFileSync",
1088
+ "readdir",
1089
+ "readdirSync",
1090
+ "stat",
1091
+ "statSync",
1092
+ "unlink",
1093
+ "unlinkSync",
1094
+ "mkdir",
1095
+ "mkdirSync",
1096
+ "rmdir",
1097
+ "rmdirSync",
1098
+ "access",
1099
+ "accessSync"
1100
+ ];
1101
+ if (pathMethods.includes(methodName)) {
1102
+ const firstArg = parent.node.arguments[0];
1103
+ if (firstArg && !this.isStaticPath(firstArg)) {
1104
+ issues.push({
1105
+ id: `security/path-traversal/${file}/${path.node.loc?.start.line}`,
1106
+ severity: "high",
1107
+ type: "PATH_TRAVERSAL",
1108
+ message: `fs.${methodName} with dynamic path - potential path traversal`,
1109
+ file,
1110
+ line: path.node.loc?.start.line,
1111
+ fix: "Validate paths against workspace root before use"
1112
+ });
1113
+ }
1114
+ }
1115
+ }
1116
+ }
1117
+ }, "MemberExpression"),
1118
+ // Check for dangerous regex patterns
1119
+ NewExpression: /* @__PURE__ */ __name((path) => {
1120
+ if (path.node.callee.type === "Identifier" && path.node.callee.name === "RegExp") {
1121
+ const firstArg = path.node.arguments[0];
1122
+ if (firstArg && !this.isStaticString(firstArg)) {
1123
+ issues.push({
1124
+ id: `security/unsafe-regex/${file}/${path.node.loc?.start.line}`,
1125
+ severity: "medium",
1126
+ type: "UNSAFE_REGEX",
1127
+ message: "Dynamic RegExp - potential ReDoS or injection vulnerability",
1128
+ file,
1129
+ line: path.node.loc?.start.line,
1130
+ fix: "Use static regex patterns or validate input"
1131
+ });
1132
+ }
1133
+ }
1134
+ }, "NewExpression"),
1135
+ // Check for innerHTML/dangerouslySetInnerHTML (XSS)
1136
+ JSXAttribute: /* @__PURE__ */ __name((path) => {
1137
+ const name = path.node.name;
1138
+ if (name.type === "JSXIdentifier" && name.name === "dangerouslySetInnerHTML") {
1139
+ issues.push({
1140
+ id: `security/xss-risk/${file}/${path.node.loc?.start.line}`,
1141
+ severity: "high",
1142
+ type: "XSS_RISK",
1143
+ message: "dangerouslySetInnerHTML can lead to XSS if content is not sanitized",
1144
+ file,
1145
+ line: path.node.loc?.start.line,
1146
+ fix: "Sanitize HTML content before rendering or avoid using dangerouslySetInnerHTML"
1147
+ });
1148
+ }
1149
+ }, "JSXAttribute"),
1150
+ // Check for hardcoded secrets in variable declarations
1151
+ VariableDeclarator: /* @__PURE__ */ __name((path) => {
1152
+ const id = path.node.id;
1153
+ const init = path.node.init;
1154
+ if (id.type === "Identifier" && init) {
1155
+ this.checkForHardcodedSecret(id.name, init, file, path.node.loc?.start.line, issues);
1156
+ }
1157
+ }, "VariableDeclarator"),
1158
+ // Check for hardcoded secrets in class properties
1159
+ ClassProperty: /* @__PURE__ */ __name((path) => {
1160
+ const key = path.node.key;
1161
+ const value = path.node.value;
1162
+ if (key.type === "Identifier" && value) {
1163
+ this.checkForHardcodedSecret(key.name, value, file, path.node.loc?.start.line, issues);
1164
+ }
1165
+ }, "ClassProperty"),
1166
+ // After traversal is complete, check daemon-specific patterns
1167
+ Program: {
1168
+ exit: /* @__PURE__ */ __name(() => {
1169
+ if (fileContext.isDaemon && !fileContext.hasSignalHandler) {
1170
+ issues.push({
1171
+ id: `security/signal-handler/${file}`,
1172
+ severity: "high",
1173
+ type: "MISSING_SIGNAL_HANDLER",
1174
+ message: "Daemon/server missing signal handlers (SIGTERM/SIGINT)",
1175
+ file,
1176
+ fix: "Add process.on('SIGTERM', gracefulShutdown) for clean shutdown"
1177
+ });
1178
+ }
1179
+ }, "exit")
1180
+ }
1181
+ });
1182
+ return {
1183
+ issues,
1184
+ nodesVisited
1185
+ };
1186
+ }
1187
+ /**
1188
+ * Check if expression is a static string (safe)
1189
+ */
1190
+ isStaticString(node) {
1191
+ if (node.type === "StringLiteral") return true;
1192
+ if (node.type === "TemplateLiteral" && node.expressions.length === 0) return true;
1193
+ return false;
1194
+ }
1195
+ /**
1196
+ * Check if expression is a static path (safe)
1197
+ */
1198
+ isStaticPath(node) {
1199
+ if (node.type === "StringLiteral") return true;
1200
+ if (node.type === "TemplateLiteral" && node.expressions.length === 0) return true;
1201
+ if (node.type === "CallExpression") {
1202
+ const callee = node.callee;
1203
+ if (callee.type === "MemberExpression" && callee.object.type === "Identifier" && callee.object.name === "path" && callee.property.type === "Identifier" && callee.property.name === "join") {
1204
+ return node.arguments.every((arg) => {
1205
+ if (arg.type === "StringLiteral") return true;
1206
+ if (arg.type === "Identifier" && (arg.name === "__dirname" || arg.name === "__filename")) return true;
1207
+ return false;
1208
+ });
1209
+ }
1210
+ }
1211
+ return false;
1212
+ }
1213
+ /**
1214
+ * Check if a value looks like a hardcoded secret
1215
+ */
1216
+ checkForHardcodedSecret(name, value, file, line, issues) {
1217
+ if (!value) {
1218
+ return;
1219
+ }
1220
+ const varName = name.toLowerCase();
1221
+ const secretIndicators = [
1222
+ "apikey",
1223
+ "api_key",
1224
+ "secret",
1225
+ "password",
1226
+ "token",
1227
+ "credential",
1228
+ "auth",
1229
+ "key"
1230
+ ];
1231
+ if (secretIndicators.some((s) => varName.includes(s))) {
1232
+ if (value.type === "StringLiteral" && value.value.length > 8) {
1233
+ const valueStr = value.value.toLowerCase();
1234
+ if (!valueStr.includes("placeholder") && !valueStr.includes("example") && !valueStr.includes("xxx") && !valueStr.includes("todo") && !valueStr.includes("your_") && !valueStr.includes("env.")) {
1235
+ issues.push({
1236
+ id: `security/hardcoded-secret/${file}/${line}`,
1237
+ severity: "critical",
1238
+ type: "HARDCODED_SECRET",
1239
+ message: `Possible hardcoded secret in "${name}"`,
1240
+ file,
1241
+ line,
1242
+ fix: "Use environment variables for secrets"
1243
+ });
1244
+ }
1245
+ }
1246
+ }
1247
+ }
1248
+ };
1249
+
1250
+ // ../../packages/core/dist/analysis/static/OrphanDetector.js
1251
+ var DEFAULT_OPTIONS = {
1252
+ fileExtensions: [
1253
+ "ts",
1254
+ "tsx",
1255
+ "js",
1256
+ "jsx"
1257
+ ],
1258
+ excludePatterns: [
1259
+ "node_modules",
1260
+ "dist",
1261
+ ".next",
1262
+ "coverage",
1263
+ "**/*.test.*",
1264
+ "**/*.spec.*",
1265
+ "**/__tests__/**",
1266
+ "**/__mocks__/**"
1267
+ ]
1268
+ };
1269
+ async function detectOrphans(entryPoint, options = {}) {
1270
+ const startTime = Date.now();
1271
+ const mergedOptions = {
1272
+ ...DEFAULT_OPTIONS,
1273
+ ...options
1274
+ };
1275
+ try {
1276
+ const madgeModule = await import('madge');
1277
+ const madge = madgeModule.default || madgeModule;
1278
+ const result = await madge(entryPoint, {
1279
+ fileExtensions: mergedOptions.fileExtensions,
1280
+ excludeRegExp: mergedOptions.excludePatterns.map((p) => {
1281
+ const regexPattern = p.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*").replace(/\./g, "\\.");
1282
+ return new RegExp(regexPattern);
1283
+ }),
1284
+ tsConfig: mergedOptions.tsConfigPath,
1285
+ detectiveOptions: {
1286
+ ts: {
1287
+ skipTypeImports: true
1288
+ }
1289
+ }
1290
+ });
1291
+ const orphans = result.orphans();
1292
+ const allFiles = Object.keys(result.obj());
1293
+ return {
1294
+ orphans,
1295
+ totalFiles: allFiles.length,
1296
+ success: true,
1297
+ duration: Date.now() - startTime
1298
+ };
1299
+ } catch (error) {
1300
+ return {
1301
+ orphans: [],
1302
+ totalFiles: 0,
1303
+ success: false,
1304
+ error: error instanceof Error ? error.message : String(error),
1305
+ duration: Date.now() - startTime
1306
+ };
1307
+ }
1308
+ }
1309
+ __name(detectOrphans, "detectOrphans");
1310
+ function filterOrphansToFiles(orphanResult, targetFiles) {
1311
+ if (!orphanResult.success) {
1312
+ return [];
1313
+ }
1314
+ const targetSet = new Set(targetFiles.map((f) => f.replace(/\\/g, "/")));
1315
+ return orphanResult.orphans.filter((orphan) => {
1316
+ const normalizedOrphan = orphan.replace(/\\/g, "/");
1317
+ return targetSet.has(normalizedOrphan) || targetFiles.some((t) => normalizedOrphan.endsWith(t));
1318
+ });
1319
+ }
1320
+ __name(filterOrphansToFiles, "filterOrphansToFiles");
1321
+ async function checkFilesForOrphanStatus(files, workspaceRoot) {
1322
+ const result = await detectOrphans(workspaceRoot, {
1323
+ baseDir: workspaceRoot
1324
+ });
1325
+ if (!result.success) {
1326
+ return {
1327
+ orphans: [],
1328
+ success: false,
1329
+ error: result.error
1330
+ };
1331
+ }
1332
+ const orphans = filterOrphansToFiles(result, files);
1333
+ return {
1334
+ orphans,
1335
+ success: true
1336
+ };
1337
+ }
1338
+ __name(checkFilesForOrphanStatus, "checkFilesForOrphanStatus");
1339
+
1340
+ // ../../packages/core/dist/analysis/static/index.js
1341
+ async function runStaticAnalysis(files, _workspaceRoot, options = {}) {
1342
+ const startTime = Date.now();
1343
+ const result = {
1344
+ skippedTests: [],
1345
+ orphanedFiles: [],
1346
+ duration: 0,
1347
+ success: true,
1348
+ errors: []
1349
+ };
1350
+ if (!options.skipTestDetection) {
1351
+ try {
1352
+ const { analyzeSkippedTests: analyzeSkippedTests2 } = await import('./SkippedTestDetector-JY4EF5BN.js');
1353
+ const testResults = analyzeSkippedTests2(files);
1354
+ for (const testResult of testResults) {
1355
+ if (!testResult.parsed && testResult.error) {
1356
+ result.errors.push(`Parse error in ${testResult.file}: ${testResult.error}`);
1357
+ }
1358
+ for (const skipped of testResult.skipped) {
1359
+ result.skippedTests.push({
1360
+ file: skipped.file,
1361
+ type: skipped.type,
1362
+ name: skipped.name,
1363
+ line: skipped.line
1364
+ });
1365
+ }
1366
+ }
1367
+ } catch (error) {
1368
+ result.errors.push(`Skipped test detection failed: ${error instanceof Error ? error.message : String(error)}`);
1369
+ }
1370
+ }
1371
+ if (!options.skipOrphanDetection) ;
1372
+ result.duration = Date.now() - startTime;
1373
+ result.success = result.errors.length === 0;
1374
+ return result;
1375
+ }
1376
+ __name(runStaticAnalysis, "runStaticAnalysis");
1377
+
1378
+ export { ChangeImpactAnalyzer, CompletenessAnalyzer, SecurityAnalyzer, SyntaxAnalyzer, checkFilesForOrphanStatus, createChangeImpactAnalyzer, detectOrphans, filterOrphansToFiles, runStaticAnalysis };
1379
+ //# sourceMappingURL=chunk-VSJ33PLA.js.map
1380
+ //# sourceMappingURL=chunk-VSJ33PLA.js.map