@snapback/cli 1.1.12 → 1.1.15

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 (59) hide show
  1. package/README.md +79 -18
  2. package/dist/SkippedTestDetector-AXTMWWHC.js +5 -0
  3. package/dist/SkippedTestDetector-QLSQV7K7.js +5 -0
  4. package/dist/analysis-6WTBZJH3.js +6 -0
  5. package/dist/analysis-C472LUGW.js +2475 -0
  6. package/dist/auth-HFJRXXG2.js +1446 -0
  7. package/dist/auto-provision-organization-SF6XM7X4.js +161 -0
  8. package/dist/chunk-23G5VYA3.js +4259 -0
  9. package/dist/{chunk-QAKFE3NE.js → chunk-4YTE4JEW.js} +3 -4
  10. package/dist/chunk-5EOPYJ4Y.js +12 -0
  11. package/dist/{chunk-G7QXHNGB.js → chunk-5SQA44V7.js} +1125 -32
  12. package/dist/{chunk-BW7RALUZ.js → chunk-7ADPL4Q3.js} +11 -4
  13. package/dist/chunk-CBGOC6RV.js +293 -0
  14. package/dist/chunk-DNEADD2G.js +3499 -0
  15. package/dist/{chunk-NKBZIXCN.js → chunk-DPWFZNMY.js} +122 -15
  16. package/dist/chunk-GQ73B37K.js +314 -0
  17. package/dist/chunk-HR34NJP7.js +6133 -0
  18. package/dist/chunk-ICKSHS3A.js +2264 -0
  19. package/dist/{chunk-KPETDXQO.js → chunk-OI2HNNT6.js} +565 -50
  20. package/dist/chunk-PL4HF4M2.js +593 -0
  21. package/dist/chunk-WS36HDEU.js +3735 -0
  22. package/dist/chunk-XYU5FFE3.js +111 -0
  23. package/dist/chunk-ZBQDE6WJ.js +108 -0
  24. package/dist/client-WIO6W447.js +8 -0
  25. package/dist/dist-E7E2T3DQ.js +9 -0
  26. package/dist/dist-TEWNOZYS.js +5 -0
  27. package/dist/dist-YZBJAYEJ.js +12 -0
  28. package/dist/index.js +65215 -26627
  29. package/dist/local-service-adapter-3JHN6G4O.js +6 -0
  30. package/dist/pioneer-oauth-hook-V2JKEXM7.js +12 -0
  31. package/dist/{secure-credentials-6UMEU22H.js → secure-credentials-UEPG7GWW.js} +15 -8
  32. package/dist/snapback-dir-MG7DTRMF.js +6 -0
  33. package/package.json +8 -42
  34. package/scripts/postinstall.mjs +2 -3
  35. package/dist/SkippedTestDetector-B3JZUE5G.js +0 -5
  36. package/dist/SkippedTestDetector-B3JZUE5G.js.map +0 -1
  37. package/dist/analysis-Z53F5FT2.js +0 -6
  38. package/dist/analysis-Z53F5FT2.js.map +0 -1
  39. package/dist/chunk-6MR2TINI.js +0 -27
  40. package/dist/chunk-6MR2TINI.js.map +0 -1
  41. package/dist/chunk-BW7RALUZ.js.map +0 -1
  42. package/dist/chunk-G7QXHNGB.js.map +0 -1
  43. package/dist/chunk-ISVRGBWT.js +0 -16223
  44. package/dist/chunk-ISVRGBWT.js.map +0 -1
  45. package/dist/chunk-KPETDXQO.js.map +0 -1
  46. package/dist/chunk-NKBZIXCN.js.map +0 -1
  47. package/dist/chunk-QAKFE3NE.js.map +0 -1
  48. package/dist/chunk-YOVA65PS.js +0 -12745
  49. package/dist/chunk-YOVA65PS.js.map +0 -1
  50. package/dist/dist-7UKXVKH3.js +0 -5
  51. package/dist/dist-7UKXVKH3.js.map +0 -1
  52. package/dist/dist-VDK7WEF4.js +0 -5
  53. package/dist/dist-VDK7WEF4.js.map +0 -1
  54. package/dist/dist-WKLJSPJT.js +0 -8
  55. package/dist/dist-WKLJSPJT.js.map +0 -1
  56. package/dist/index.js.map +0 -1
  57. package/dist/secure-credentials-6UMEU22H.js.map +0 -1
  58. package/dist/snapback-dir-T3CRQRY6.js +0 -6
  59. package/dist/snapback-dir-T3CRQRY6.js.map +0 -1
@@ -0,0 +1,2475 @@
1
+ #!/usr/bin/env node --no-warnings=ExperimentalWarning
2
+ export { analyzeSkippedTests, detectSkippedTests, getSkippedTestSummary } from './chunk-ZBQDE6WJ.js';
3
+ import { __name } from './chunk-7ADPL4Q3.js';
4
+ import { parseSync } from 'oxc-parser';
5
+ import { dirname, resolve, relative, basename } from 'path';
6
+ import * as eslintParser from '@typescript-eslint/parser';
7
+ import { parse } from '@babel/parser';
8
+ import traverse from '@babel/traverse';
9
+
10
+ process.env.SNAPBACK_CLI='true';
11
+ var TS_EXTENSIONS = /* @__PURE__ */ new Set([
12
+ ".ts",
13
+ ".tsx",
14
+ ".mts",
15
+ ".cts"
16
+ ]);
17
+ var JSX_EXTENSIONS = /* @__PURE__ */ new Set([
18
+ ".tsx",
19
+ ".jsx"
20
+ ]);
21
+ var ALL_EXTENSIONS = /* @__PURE__ */ new Set([
22
+ ".ts",
23
+ ".tsx",
24
+ ".js",
25
+ ".jsx",
26
+ ".mts",
27
+ ".cts",
28
+ ".mjs",
29
+ ".cjs"
30
+ ]);
31
+ function isSupportedFile(filePath) {
32
+ const ext = getExtension(filePath);
33
+ return ALL_EXTENSIONS.has(ext);
34
+ }
35
+ __name(isSupportedFile, "isSupportedFile");
36
+ function parseSource(content, filePath) {
37
+ const ext = getExtension(filePath);
38
+ try {
39
+ const lang = JSX_EXTENSIONS.has(ext) ? TS_EXTENSIONS.has(ext) ? "tsx" : "jsx" : TS_EXTENSIONS.has(ext) ? "ts" : "js";
40
+ const result = parseSync(filePath, content, {
41
+ sourceType: "module",
42
+ lang
43
+ });
44
+ const errors = Array.isArray(result.errors) ? result.errors.map((e) => normalizeError(e)) : [];
45
+ return {
46
+ program: result.program,
47
+ errors,
48
+ success: errors.length === 0
49
+ };
50
+ } catch (error) {
51
+ return {
52
+ program: {
53
+ type: "Program",
54
+ body: [],
55
+ sourceType: "module"
56
+ },
57
+ errors: [
58
+ {
59
+ message: error instanceof Error ? error.message : String(error),
60
+ severity: "error"
61
+ }
62
+ ],
63
+ success: false
64
+ };
65
+ }
66
+ }
67
+ __name(parseSource, "parseSource");
68
+ function walkAST(node, visitor, parent) {
69
+ if (!node || typeof node !== "object") {
70
+ return;
71
+ }
72
+ const n = node;
73
+ if (typeof n.type === "string") {
74
+ visitor(n, parent);
75
+ }
76
+ for (const key of Object.keys(n)) {
77
+ if (key === "type" || key === "start" || key === "end" || key === "loc") {
78
+ continue;
79
+ }
80
+ const value = n[key];
81
+ if (Array.isArray(value)) {
82
+ for (const item of value) {
83
+ if (item && typeof item === "object" && typeof item.type === "string") {
84
+ walkAST(item, visitor, n);
85
+ }
86
+ }
87
+ } else if (value && typeof value === "object" && typeof value.type === "string") {
88
+ walkAST(value, visitor, n);
89
+ }
90
+ }
91
+ }
92
+ __name(walkAST, "walkAST");
93
+ function countASTNodes(program) {
94
+ let count = 0;
95
+ walkAST(program, () => {
96
+ count++;
97
+ });
98
+ return count;
99
+ }
100
+ __name(countASTNodes, "countASTNodes");
101
+ function offsetToLine(source, offset) {
102
+ if (offset < 0 || offset > source.length) {
103
+ return 1;
104
+ }
105
+ let line = 1;
106
+ for (let i = 0; i < offset; i++) {
107
+ if (source[i] === "\n") {
108
+ line++;
109
+ }
110
+ }
111
+ return line;
112
+ }
113
+ __name(offsetToLine, "offsetToLine");
114
+ function getExtension(filePath) {
115
+ const lastDot = filePath.lastIndexOf(".");
116
+ return lastDot >= 0 ? filePath.substring(lastDot).toLowerCase() : "";
117
+ }
118
+ __name(getExtension, "getExtension");
119
+ function normalizeError(e) {
120
+ if (typeof e === "object" && e !== null) {
121
+ const err = e;
122
+ return {
123
+ message: String(err.message ?? err),
124
+ severity: String(err.severity ?? "error"),
125
+ labels: Array.isArray(err.labels) ? err.labels : void 0
126
+ };
127
+ }
128
+ return {
129
+ message: String(e),
130
+ severity: "error"
131
+ };
132
+ }
133
+ __name(normalizeError, "normalizeError");
134
+
135
+ // ../../node_modules/@snapback/core/dist/analysis/ast/ComplexityAnalyzer.js
136
+ var THRESHOLDS = {
137
+ /** Cyclomatic complexity per function */
138
+ maxCyclomaticPerFunction: 15,
139
+ /** Maximum nesting depth per function */
140
+ maxNestingDepth: 5,
141
+ /** Maximum parameters per function */
142
+ maxParameters: 5,
143
+ /** Maximum functions per file */
144
+ maxFunctionsPerFile: 30,
145
+ /** File-level aggregate cyclomatic complexity */
146
+ maxCyclomaticPerFile: 50
147
+ };
148
+ var BRANCH_NODES = /* @__PURE__ */ new Set([
149
+ "IfStatement",
150
+ "ConditionalExpression",
151
+ "SwitchCase",
152
+ "ForStatement",
153
+ "ForInStatement",
154
+ "ForOfStatement",
155
+ "WhileStatement",
156
+ "DoWhileStatement",
157
+ "CatchClause"
158
+ ]);
159
+ var LOGICAL_OPERATORS = /* @__PURE__ */ new Set([
160
+ "&&",
161
+ "||",
162
+ "??"
163
+ ]);
164
+ var FUNCTION_NODES = /* @__PURE__ */ new Set([
165
+ "FunctionDeclaration",
166
+ "FunctionExpression",
167
+ "ArrowFunctionExpression",
168
+ "MethodDefinition"
169
+ ]);
170
+ var ComplexityAnalyzer = class {
171
+ static {
172
+ __name(this, "ComplexityAnalyzer");
173
+ }
174
+ id = "complexity";
175
+ name = "Complexity Analysis";
176
+ filePatterns = [
177
+ "*.ts",
178
+ "*.tsx",
179
+ "*.js",
180
+ "*.jsx"
181
+ ];
182
+ async analyze(context) {
183
+ const startTime = performance.now();
184
+ const issues = [];
185
+ let filesAnalyzed = 0;
186
+ let totalNodesVisited = 0;
187
+ const parseErrors = [];
188
+ for (const [file, content] of context.contents) {
189
+ if (!this.shouldAnalyzeFile(file)) {
190
+ continue;
191
+ }
192
+ filesAnalyzed++;
193
+ const fileComplexity = this.analyzeFile(content, file);
194
+ if (!fileComplexity) {
195
+ parseErrors.push(`${file}: Failed to parse`);
196
+ continue;
197
+ }
198
+ totalNodesVisited += fileComplexity.functions.length;
199
+ for (const fn of fileComplexity.functions) {
200
+ if (fn.cyclomatic > THRESHOLDS.maxCyclomaticPerFunction) {
201
+ issues.push({
202
+ id: `complexity/cyclomatic/${file}/${fn.line}`,
203
+ severity: fn.cyclomatic > THRESHOLDS.maxCyclomaticPerFunction * 2 ? "high" : "medium",
204
+ type: "HIGH_CYCLOMATIC_COMPLEXITY",
205
+ message: `Function "${fn.name}" has cyclomatic complexity ${fn.cyclomatic} (max: ${THRESHOLDS.maxCyclomaticPerFunction})`,
206
+ file,
207
+ line: fn.line,
208
+ fix: "Extract helper functions or simplify branching logic"
209
+ });
210
+ }
211
+ if (fn.maxNesting > THRESHOLDS.maxNestingDepth) {
212
+ issues.push({
213
+ id: `complexity/nesting/${file}/${fn.line}`,
214
+ severity: "medium",
215
+ type: "DEEP_NESTING",
216
+ message: `Function "${fn.name}" has nesting depth ${fn.maxNesting} (max: ${THRESHOLDS.maxNestingDepth})`,
217
+ file,
218
+ line: fn.line,
219
+ fix: "Use early returns or extract nested logic into helper functions"
220
+ });
221
+ }
222
+ if (fn.parameters > THRESHOLDS.maxParameters) {
223
+ issues.push({
224
+ id: `complexity/parameters/${file}/${fn.line}`,
225
+ severity: "low",
226
+ type: "TOO_MANY_PARAMETERS",
227
+ message: `Function "${fn.name}" has ${fn.parameters} parameters (max: ${THRESHOLDS.maxParameters})`,
228
+ file,
229
+ line: fn.line,
230
+ fix: "Use an options object pattern to reduce parameter count"
231
+ });
232
+ }
233
+ }
234
+ if (fileComplexity.functionCount > THRESHOLDS.maxFunctionsPerFile) {
235
+ issues.push({
236
+ id: `complexity/function-count/${file}`,
237
+ severity: "low",
238
+ type: "TOO_MANY_FUNCTIONS",
239
+ message: `${file} has ${fileComplexity.functionCount} functions (max: ${THRESHOLDS.maxFunctionsPerFile})`,
240
+ file,
241
+ fix: "Split into multiple focused modules"
242
+ });
243
+ }
244
+ if (fileComplexity.totalCyclomatic > THRESHOLDS.maxCyclomaticPerFile) {
245
+ issues.push({
246
+ id: `complexity/file-complexity/${file}`,
247
+ severity: "medium",
248
+ type: "HIGH_FILE_COMPLEXITY",
249
+ message: `${file} has total cyclomatic complexity ${fileComplexity.totalCyclomatic} (max: ${THRESHOLDS.maxCyclomaticPerFile})`,
250
+ file,
251
+ fix: "Consider splitting this file into smaller modules"
252
+ });
253
+ }
254
+ }
255
+ return {
256
+ analyzer: this.id,
257
+ success: true,
258
+ issues,
259
+ coverage: filesAnalyzed / Math.max(context.files.length, 1),
260
+ duration: performance.now() - startTime,
261
+ metadata: {
262
+ filesAnalyzed,
263
+ nodesVisited: totalNodesVisited,
264
+ patternsChecked: [
265
+ "HIGH_CYCLOMATIC_COMPLEXITY",
266
+ "DEEP_NESTING",
267
+ "TOO_MANY_PARAMETERS",
268
+ "TOO_MANY_FUNCTIONS",
269
+ "HIGH_FILE_COMPLEXITY"
270
+ ],
271
+ parseErrors
272
+ }
273
+ };
274
+ }
275
+ shouldRun(context) {
276
+ return context.files.some((f) => this.shouldAnalyzeFile(f));
277
+ }
278
+ /**
279
+ * Analyze a single file and return complexity metrics.
280
+ * Useful for external callers that just want metrics, not issues.
281
+ */
282
+ analyzeFile(content, filePath) {
283
+ if (!isSupportedFile(filePath)) {
284
+ return null;
285
+ }
286
+ const { program, success } = parseSource(content, filePath);
287
+ if (!success && program.body.length === 0) {
288
+ return null;
289
+ }
290
+ const functions = [];
291
+ let fileMaxNesting = 0;
292
+ walkAST(program, (node) => {
293
+ if (!FUNCTION_NODES.has(node.type)) {
294
+ return;
295
+ }
296
+ const fn = this.analyzeFunctionNode(node);
297
+ functions.push(fn);
298
+ if (fn.maxNesting > fileMaxNesting) {
299
+ fileMaxNesting = fn.maxNesting;
300
+ }
301
+ });
302
+ const totalCyclomatic = functions.reduce((sum, fn) => sum + fn.cyclomatic, 0);
303
+ return {
304
+ filePath,
305
+ functions,
306
+ totalCyclomatic,
307
+ maxNesting: fileMaxNesting,
308
+ functionCount: functions.length,
309
+ averageCyclomatic: functions.length > 0 ? totalCyclomatic / functions.length : 0
310
+ };
311
+ }
312
+ // -----------------------------------------------------------------------
313
+ // Per-function analysis
314
+ // -----------------------------------------------------------------------
315
+ analyzeFunctionNode(node) {
316
+ const name = this.getFunctionName(node);
317
+ const line = node.start ?? 0;
318
+ const params = this.getParameterCount(node);
319
+ let cyclomatic = 1;
320
+ let maxNesting = 0;
321
+ const body = this.getFunctionBody(node);
322
+ if (body) {
323
+ this.walkForComplexity(body, (n, depth) => {
324
+ if (BRANCH_NODES.has(n.type)) {
325
+ cyclomatic++;
326
+ }
327
+ if (n.type === "LogicalExpression") {
328
+ const operator = n.operator;
329
+ if (LOGICAL_OPERATORS.has(operator)) {
330
+ cyclomatic++;
331
+ }
332
+ }
333
+ if (depth > maxNesting) {
334
+ maxNesting = depth;
335
+ }
336
+ });
337
+ }
338
+ return {
339
+ name,
340
+ line,
341
+ cyclomatic,
342
+ maxNesting,
343
+ parameters: params
344
+ };
345
+ }
346
+ /**
347
+ * Walk AST nodes counting nesting depth for complexity metrics
348
+ */
349
+ walkForComplexity(node, callback, depth = 0) {
350
+ if (!node || typeof node !== "object") {
351
+ return;
352
+ }
353
+ const n = node;
354
+ if (typeof n.type !== "string") {
355
+ return;
356
+ }
357
+ const nestingNodes = /* @__PURE__ */ new Set([
358
+ "IfStatement",
359
+ "ForStatement",
360
+ "ForInStatement",
361
+ "ForOfStatement",
362
+ "WhileStatement",
363
+ "DoWhileStatement",
364
+ "SwitchStatement",
365
+ "TryStatement"
366
+ ]);
367
+ const newDepth = nestingNodes.has(n.type) ? depth + 1 : depth;
368
+ callback(n, newDepth);
369
+ for (const key of Object.keys(n)) {
370
+ if (key === "type" || key === "start" || key === "end" || key === "loc") {
371
+ continue;
372
+ }
373
+ const value = n[key];
374
+ if (Array.isArray(value)) {
375
+ for (const item of value) {
376
+ this.walkForComplexity(item, callback, newDepth);
377
+ }
378
+ } else if (value && typeof value === "object" && typeof value.type === "string") {
379
+ this.walkForComplexity(value, callback, newDepth);
380
+ }
381
+ }
382
+ }
383
+ // -----------------------------------------------------------------------
384
+ // Helpers
385
+ // -----------------------------------------------------------------------
386
+ getFunctionName(node) {
387
+ if (node.id && typeof node.id === "object") {
388
+ const id = node.id;
389
+ if (typeof id.name === "string") {
390
+ return id.name;
391
+ }
392
+ }
393
+ if (node.key && typeof node.key === "object") {
394
+ const key = node.key;
395
+ if (typeof key.name === "string") {
396
+ return key.name;
397
+ }
398
+ if (typeof key.value === "string") {
399
+ return key.value;
400
+ }
401
+ }
402
+ return "<anonymous>";
403
+ }
404
+ getParameterCount(node) {
405
+ if (node.type === "MethodDefinition") {
406
+ const value = node.value;
407
+ if (value && Array.isArray(value.params)) {
408
+ return value.params.length;
409
+ }
410
+ }
411
+ if (Array.isArray(node.params)) {
412
+ return node.params.length;
413
+ }
414
+ return 0;
415
+ }
416
+ getFunctionBody(node) {
417
+ if (node.type === "MethodDefinition") {
418
+ const value = node.value;
419
+ if (value?.body) {
420
+ return value.body;
421
+ }
422
+ }
423
+ if (node.body) {
424
+ return node.body;
425
+ }
426
+ return null;
427
+ }
428
+ shouldAnalyzeFile(file) {
429
+ const ext = file.split(".").pop()?.toLowerCase();
430
+ return [
431
+ "ts",
432
+ "tsx",
433
+ "js",
434
+ "jsx",
435
+ "mts",
436
+ "cts"
437
+ ].includes(ext ?? "");
438
+ }
439
+ };
440
+
441
+ // ../../node_modules/@snapback/core/dist/analysis/ast/import-extractor.js
442
+ function extractImports(content, filePath) {
443
+ if (!isSupportedFile(filePath)) {
444
+ return {
445
+ filePath,
446
+ imports: [],
447
+ parseSuccess: false,
448
+ parseErrors: [
449
+ `Unsupported file type: ${filePath}`
450
+ ]
451
+ };
452
+ }
453
+ const { program, errors, success } = parseSource(content, filePath);
454
+ const imports = [];
455
+ walkAST(program, (node) => {
456
+ switch (node.type) {
457
+ // import ... from 'source'
458
+ case "ImportDeclaration":
459
+ handleImportDeclaration(node, imports);
460
+ break;
461
+ // export { ... } from 'source' OR export * from 'source'
462
+ case "ExportNamedDeclaration":
463
+ case "ExportAllDeclaration":
464
+ handleExportDeclaration(node, imports);
465
+ break;
466
+ // import('source') — ESTree ImportExpression
467
+ case "ImportExpression":
468
+ handleImportExpression(node, imports);
469
+ break;
470
+ // require('source') OR legacy import('source') as CallExpression
471
+ case "CallExpression":
472
+ handleCallExpression(node, imports);
473
+ break;
474
+ }
475
+ });
476
+ return {
477
+ filePath,
478
+ imports,
479
+ parseSuccess: success,
480
+ parseErrors: errors.map((e) => e.message)
481
+ };
482
+ }
483
+ __name(extractImports, "extractImports");
484
+ function extractImportSources(content, filePath) {
485
+ const result = extractImports(content, filePath);
486
+ const sources = /* @__PURE__ */ new Set();
487
+ for (const imp of result.imports) {
488
+ sources.add(imp.source);
489
+ }
490
+ return [
491
+ ...sources
492
+ ];
493
+ }
494
+ __name(extractImportSources, "extractImportSources");
495
+ function extractImportsBatch(files) {
496
+ const results = /* @__PURE__ */ new Map();
497
+ for (const [filePath, content] of files) {
498
+ results.set(filePath, extractImports(content, filePath));
499
+ }
500
+ return results;
501
+ }
502
+ __name(extractImportsBatch, "extractImportsBatch");
503
+ function handleImportDeclaration(node, imports) {
504
+ const source = getStringValue(node.source);
505
+ if (!source) {
506
+ return;
507
+ }
508
+ const specifiers = [];
509
+ if (Array.isArray(node.specifiers)) {
510
+ for (const spec of node.specifiers) {
511
+ const s = spec;
512
+ if (s.type === "ImportSpecifier") {
513
+ const imported = s.imported;
514
+ specifiers.push(getIdentifierName(imported) ?? "unknown");
515
+ } else if (s.type === "ImportDefaultSpecifier") {
516
+ specifiers.push("default");
517
+ } else if (s.type === "ImportNamespaceSpecifier") {
518
+ specifiers.push("*");
519
+ }
520
+ }
521
+ }
522
+ imports.push({
523
+ source,
524
+ kind: "static",
525
+ typeOnly: node.importKind === "type" || Boolean(node.isTypeOnly),
526
+ line: node.start != null ? node.start : void 0,
527
+ specifiers
528
+ });
529
+ }
530
+ __name(handleImportDeclaration, "handleImportDeclaration");
531
+ function handleExportDeclaration(node, imports) {
532
+ const source = getStringValue(node.source);
533
+ if (!source) {
534
+ return;
535
+ }
536
+ const specifiers = [];
537
+ if (node.type === "ExportAllDeclaration") {
538
+ specifiers.push("*");
539
+ } else if (Array.isArray(node.specifiers)) {
540
+ for (const spec of node.specifiers) {
541
+ const s = spec;
542
+ const local = s.local;
543
+ specifiers.push(getIdentifierName(local) ?? "unknown");
544
+ }
545
+ }
546
+ imports.push({
547
+ source,
548
+ kind: "re-export",
549
+ typeOnly: node.exportKind === "type" || Boolean(node.isTypeOnly),
550
+ line: node.start != null ? node.start : void 0,
551
+ specifiers
552
+ });
553
+ }
554
+ __name(handleExportDeclaration, "handleExportDeclaration");
555
+ function handleImportExpression(node, imports) {
556
+ const source = getStringValue(node.source);
557
+ if (source) {
558
+ imports.push({
559
+ source,
560
+ kind: "dynamic",
561
+ typeOnly: false,
562
+ line: node.start != null ? node.start : void 0,
563
+ specifiers: []
564
+ });
565
+ }
566
+ }
567
+ __name(handleImportExpression, "handleImportExpression");
568
+ function handleCallExpression(node, imports) {
569
+ const callee = node.callee;
570
+ if (!callee) {
571
+ return;
572
+ }
573
+ if (node.type === "CallExpression" && callee.type === "Import") {
574
+ const args = node.arguments;
575
+ if (args && args.length > 0) {
576
+ const source = getStringValue(args[0]);
577
+ if (source) {
578
+ imports.push({
579
+ source,
580
+ kind: "dynamic",
581
+ typeOnly: false,
582
+ line: node.start != null ? node.start : void 0,
583
+ specifiers: []
584
+ });
585
+ }
586
+ }
587
+ return;
588
+ }
589
+ if (callee.type === "Identifier" && callee.name === "require") {
590
+ const args = node.arguments;
591
+ if (args && args.length > 0) {
592
+ const source = getStringValue(args[0]);
593
+ if (source) {
594
+ imports.push({
595
+ source,
596
+ kind: "require",
597
+ typeOnly: false,
598
+ line: node.start != null ? node.start : void 0,
599
+ specifiers: []
600
+ });
601
+ }
602
+ }
603
+ }
604
+ }
605
+ __name(handleCallExpression, "handleCallExpression");
606
+ function getStringValue(node) {
607
+ if (!node || typeof node !== "object") {
608
+ return void 0;
609
+ }
610
+ const n = node;
611
+ if (n.type === "StringLiteral" || n.type === "Literal") {
612
+ return typeof n.value === "string" ? n.value : void 0;
613
+ }
614
+ return void 0;
615
+ }
616
+ __name(getStringValue, "getStringValue");
617
+ function getIdentifierName(node) {
618
+ if (!node || typeof node !== "object") {
619
+ return void 0;
620
+ }
621
+ const n = node;
622
+ if (n.type === "Identifier" || n.type === "IdentifierName" || n.type === "IdentifierReference") {
623
+ return typeof n.name === "string" ? n.name : void 0;
624
+ }
625
+ return void 0;
626
+ }
627
+ __name(getIdentifierName, "getIdentifierName");
628
+
629
+ // ../../node_modules/@snapback/core/dist/analysis/ast/ImportGraphAnalyzer.js
630
+ var THRESHOLDS2 = {
631
+ /** Max files that can import a single file before it's flagged as high fan-in */
632
+ highFanIn: 15,
633
+ /** Max imports a single file can have before it's flagged as high fan-out */
634
+ highFanOut: 20,
635
+ /** Minimum cycle length to report (avoids noise from self-imports) */
636
+ minCycleLength: 2
637
+ };
638
+ var ImportGraphAnalyzer = class {
639
+ static {
640
+ __name(this, "ImportGraphAnalyzer");
641
+ }
642
+ id = "import-graph";
643
+ name = "Import Graph Analysis";
644
+ filePatterns = [
645
+ "*.ts",
646
+ "*.tsx",
647
+ "*.js",
648
+ "*.jsx"
649
+ ];
650
+ async analyze(context) {
651
+ const startTime = performance.now();
652
+ const issues = [];
653
+ let filesAnalyzed = 0;
654
+ const parseErrors = [];
655
+ const extractions = /* @__PURE__ */ new Map();
656
+ for (const [file, content] of context.contents) {
657
+ if (!this.shouldAnalyzeFile(file)) {
658
+ continue;
659
+ }
660
+ filesAnalyzed++;
661
+ const result = extractImports(content, file);
662
+ extractions.set(file, result);
663
+ if (!result.parseSuccess) {
664
+ parseErrors.push(...result.parseErrors);
665
+ }
666
+ }
667
+ const graph = this.buildGraph(extractions, context.workspaceRoot);
668
+ const cycles = this.detectCycles(graph.edges);
669
+ graph.cycles = cycles;
670
+ for (const cycle of cycles) {
671
+ issues.push({
672
+ id: `import-graph/circular/${cycle.join("->")}`,
673
+ severity: "high",
674
+ type: "CIRCULAR_DEPENDENCY",
675
+ message: `Circular dependency: ${cycle.join(" \u2192 ")}`,
676
+ file: cycle[0],
677
+ fix: "Break the cycle by extracting shared code or using dependency injection"
678
+ });
679
+ }
680
+ for (const [file, node] of graph.nodes) {
681
+ if (node.importedBy.length > THRESHOLDS2.highFanIn) {
682
+ issues.push({
683
+ id: `import-graph/high-fan-in/${file}`,
684
+ severity: "medium",
685
+ type: "HIGH_FAN_IN",
686
+ message: `${file} is imported by ${node.importedBy.length} files \u2014 changes here have high blast radius`,
687
+ file,
688
+ fix: "Consider splitting into smaller, more focused modules"
689
+ });
690
+ }
691
+ }
692
+ for (const [file, node] of graph.nodes) {
693
+ const runtimeImports = node.imports.filter((imp) => !node.typeOnlyImports.includes(imp));
694
+ if (runtimeImports.length > THRESHOLDS2.highFanOut) {
695
+ issues.push({
696
+ id: `import-graph/high-fan-out/${file}`,
697
+ severity: "low",
698
+ type: "HIGH_FAN_OUT",
699
+ message: `${file} imports ${runtimeImports.length} modules \u2014 high coupling`,
700
+ file,
701
+ fix: "Consider using a facade or consolidating related imports"
702
+ });
703
+ }
704
+ }
705
+ for (const [file, node] of graph.nodes) {
706
+ if (node.importedBy.length === 0 && !this.isEntryPoint(file)) {
707
+ issues.push({
708
+ id: `import-graph/orphan/${file}`,
709
+ severity: "info",
710
+ type: "ORPHAN_FILE",
711
+ message: `${file} is not imported by any other analyzed file`,
712
+ file,
713
+ fix: "Verify this file is needed \u2014 it may be dead code"
714
+ });
715
+ }
716
+ }
717
+ return {
718
+ analyzer: this.id,
719
+ success: true,
720
+ issues,
721
+ coverage: filesAnalyzed / Math.max(context.files.length, 1),
722
+ duration: performance.now() - startTime,
723
+ metadata: {
724
+ filesAnalyzed,
725
+ nodesVisited: graph.nodes.size,
726
+ patternsChecked: [
727
+ "CIRCULAR_DEPENDENCY",
728
+ "HIGH_FAN_IN",
729
+ "HIGH_FAN_OUT",
730
+ "ORPHAN_FILE"
731
+ ],
732
+ parseErrors
733
+ }
734
+ };
735
+ }
736
+ shouldRun(context) {
737
+ return context.files.some((f) => this.shouldAnalyzeFile(f));
738
+ }
739
+ /**
740
+ * Build the import graph and return it for external consumption.
741
+ * Useful for other tools (momentum scoring, risk propagation, etc.).
742
+ */
743
+ buildGraphFromContext(context) {
744
+ const extractions = /* @__PURE__ */ new Map();
745
+ for (const [file, content] of context.contents) {
746
+ if (this.shouldAnalyzeFile(file)) {
747
+ extractions.set(file, extractImports(content, file));
748
+ }
749
+ }
750
+ const graph = this.buildGraph(extractions, context.workspaceRoot);
751
+ graph.cycles = this.detectCycles(graph.edges);
752
+ return graph;
753
+ }
754
+ // -----------------------------------------------------------------------
755
+ // Graph construction
756
+ // -----------------------------------------------------------------------
757
+ buildGraph(extractions, workspaceRoot) {
758
+ const nodes = /* @__PURE__ */ new Map();
759
+ const edges = /* @__PURE__ */ new Map();
760
+ const reverseEdges = /* @__PURE__ */ new Map();
761
+ for (const filePath of extractions.keys()) {
762
+ const normalized = this.normalizePath(filePath);
763
+ nodes.set(normalized, {
764
+ filePath: normalized,
765
+ imports: [],
766
+ importedBy: [],
767
+ typeOnlyImports: []
768
+ });
769
+ edges.set(normalized, /* @__PURE__ */ new Set());
770
+ }
771
+ for (const [filePath, extraction] of extractions) {
772
+ const normalized = this.normalizePath(filePath);
773
+ for (const imp of extraction.imports) {
774
+ const resolved = this.resolveImport(imp.source, filePath, workspaceRoot);
775
+ if (!resolved) {
776
+ continue;
777
+ }
778
+ const resolvedNorm = this.normalizePath(resolved);
779
+ edges.get(normalized)?.add(resolvedNorm);
780
+ const node = nodes.get(normalized);
781
+ if (node && !node.imports.includes(resolvedNorm)) {
782
+ node.imports.push(resolvedNorm);
783
+ if (imp.typeOnly) {
784
+ node.typeOnlyImports.push(resolvedNorm);
785
+ }
786
+ }
787
+ if (!reverseEdges.has(resolvedNorm)) {
788
+ reverseEdges.set(resolvedNorm, /* @__PURE__ */ new Set());
789
+ }
790
+ reverseEdges.get(resolvedNorm)?.add(normalized);
791
+ if (!nodes.has(resolvedNorm)) {
792
+ nodes.set(resolvedNorm, {
793
+ filePath: resolvedNorm,
794
+ imports: [],
795
+ importedBy: [],
796
+ typeOnlyImports: []
797
+ });
798
+ }
799
+ }
800
+ }
801
+ for (const [file, importers] of reverseEdges) {
802
+ const node = nodes.get(file);
803
+ if (node) {
804
+ node.importedBy = [
805
+ ...importers
806
+ ];
807
+ }
808
+ }
809
+ return {
810
+ nodes,
811
+ edges,
812
+ reverseEdges,
813
+ cycles: []
814
+ };
815
+ }
816
+ // -----------------------------------------------------------------------
817
+ // Cycle detection (Tarjan's SCC adapted for cycles)
818
+ // -----------------------------------------------------------------------
819
+ detectCycles(edges) {
820
+ const cycles = [];
821
+ const visited = /* @__PURE__ */ new Set();
822
+ const inStack = /* @__PURE__ */ new Set();
823
+ const stack = [];
824
+ const dfs = /* @__PURE__ */ __name((node) => {
825
+ if (inStack.has(node)) {
826
+ const cycleStart = stack.indexOf(node);
827
+ if (cycleStart >= 0) {
828
+ const cycle = stack.slice(cycleStart);
829
+ if (cycle.length >= THRESHOLDS2.minCycleLength) {
830
+ cycles.push([
831
+ ...cycle,
832
+ node
833
+ ]);
834
+ }
835
+ }
836
+ return;
837
+ }
838
+ if (visited.has(node)) {
839
+ return;
840
+ }
841
+ visited.add(node);
842
+ inStack.add(node);
843
+ stack.push(node);
844
+ const neighbors = edges.get(node) ?? /* @__PURE__ */ new Set();
845
+ for (const neighbor of neighbors) {
846
+ dfs(neighbor);
847
+ }
848
+ stack.pop();
849
+ inStack.delete(node);
850
+ }, "dfs");
851
+ for (const node of edges.keys()) {
852
+ dfs(node);
853
+ }
854
+ return cycles;
855
+ }
856
+ // -----------------------------------------------------------------------
857
+ // Import resolution
858
+ // -----------------------------------------------------------------------
859
+ resolveImport(importSource, fromFile, _workspaceRoot) {
860
+ if (!importSource.startsWith(".") && !importSource.startsWith("/")) {
861
+ if (importSource.startsWith("@")) {
862
+ const parts = importSource.split("/");
863
+ if (parts.length >= 2) {
864
+ const pkg = parts[1];
865
+ return `packages/${pkg}/src/index.ts`;
866
+ }
867
+ }
868
+ return null;
869
+ }
870
+ const fromDir = dirname(fromFile);
871
+ let resolved = resolve(fromDir, importSource);
872
+ if (!resolved.match(/\.(ts|tsx|js|jsx|mts|cts|mjs|cjs)$/)) {
873
+ resolved += ".ts";
874
+ }
875
+ resolved = resolved.replace(/\.js$/, ".ts").replace(/\.jsx$/, ".tsx");
876
+ return resolved;
877
+ }
878
+ // -----------------------------------------------------------------------
879
+ // Helpers
880
+ // -----------------------------------------------------------------------
881
+ shouldAnalyzeFile(file) {
882
+ const ext = file.split(".").pop()?.toLowerCase();
883
+ return [
884
+ "ts",
885
+ "tsx",
886
+ "js",
887
+ "jsx",
888
+ "mts",
889
+ "cts"
890
+ ].includes(ext ?? "");
891
+ }
892
+ isEntryPoint(file) {
893
+ return file.includes("index.") || file.includes("main.") || file.includes("entry.") || file.includes("server.") || file.includes("app.") || file.endsWith("/page.tsx") || file.endsWith("/layout.tsx") || file.endsWith("/route.ts") || file.includes("__tests__") || file.includes(".test.") || file.includes(".spec.");
894
+ }
895
+ normalizePath(filePath) {
896
+ return filePath.replace(/\\/g, "/").replace(/^\.\//, "");
897
+ }
898
+ };
899
+ var SyntaxAnalyzer = class {
900
+ static {
901
+ __name(this, "SyntaxAnalyzer");
902
+ }
903
+ id = "syntax";
904
+ name = "Syntax Analysis";
905
+ filePatterns = [
906
+ "*.ts",
907
+ "*.tsx",
908
+ "*.js",
909
+ "*.jsx"
910
+ ];
911
+ async analyze(context) {
912
+ const startTime = performance.now();
913
+ const issues = [];
914
+ let filesAnalyzed = 0;
915
+ let nodesVisited = 0;
916
+ const parseErrors = [];
917
+ for (const [file, content] of context.contents) {
918
+ if (!this.shouldAnalyzeFile(file)) {
919
+ continue;
920
+ }
921
+ filesAnalyzed++;
922
+ try {
923
+ const ast = eslintParser.parse(content, {
924
+ sourceType: "module",
925
+ ecmaFeatures: {
926
+ jsx: file.endsWith(".tsx") || file.endsWith(".jsx")
927
+ },
928
+ ecmaVersion: "latest",
929
+ // Error recovery mode to get partial AST even with errors
930
+ errorOnUnknownASTType: false
931
+ });
932
+ nodesVisited += this.countNodes(ast);
933
+ this.checkSyntaxPatterns(content, file, issues);
934
+ } catch (error) {
935
+ const parseError = this.extractParseError(error);
936
+ parseErrors.push(`${file}: ${parseError.message}`);
937
+ issues.push({
938
+ id: `syntax/parse-error/${file}/${parseError.line}`,
939
+ severity: "critical",
940
+ type: "SYNTAX_ERROR",
941
+ message: parseError.message,
942
+ file,
943
+ line: parseError.line,
944
+ column: parseError.column,
945
+ fix: "Fix the syntax error to allow parsing"
946
+ });
947
+ }
948
+ }
949
+ return {
950
+ analyzer: this.id,
951
+ success: true,
952
+ issues,
953
+ coverage: filesAnalyzed / Math.max(context.files.length, 1),
954
+ duration: performance.now() - startTime,
955
+ metadata: {
956
+ filesAnalyzed,
957
+ nodesVisited,
958
+ parseErrors
959
+ }
960
+ };
961
+ }
962
+ shouldRun(context) {
963
+ return context.files.some((f) => this.shouldAnalyzeFile(f));
964
+ }
965
+ shouldAnalyzeFile(file) {
966
+ const ext = file.split(".").pop()?.toLowerCase();
967
+ return [
968
+ "ts",
969
+ "tsx",
970
+ "js",
971
+ "jsx"
972
+ ].includes(ext || "");
973
+ }
974
+ /**
975
+ * Extract parse error information from parser exception
976
+ */
977
+ extractParseError(error) {
978
+ if (error instanceof Error) {
979
+ const match = error.message.match(/\((\d+):(\d+)\)/);
980
+ if (match) {
981
+ return {
982
+ message: error.message,
983
+ line: Number.parseInt(match[1], 10),
984
+ column: Number.parseInt(match[2], 10)
985
+ };
986
+ }
987
+ return {
988
+ message: error.message,
989
+ line: 1,
990
+ column: 1
991
+ };
992
+ }
993
+ return {
994
+ message: String(error),
995
+ line: 1,
996
+ column: 1
997
+ };
998
+ }
999
+ /**
1000
+ * Count AST nodes for coverage metrics
1001
+ */
1002
+ countNodes(node) {
1003
+ if (!node || typeof node !== "object") {
1004
+ return 0;
1005
+ }
1006
+ let count = 1;
1007
+ for (const key of Object.keys(node)) {
1008
+ const value = node[key];
1009
+ if (Array.isArray(value)) {
1010
+ for (const item of value) {
1011
+ count += this.countNodes(item);
1012
+ }
1013
+ } else if (value && typeof value === "object" && "type" in value) {
1014
+ count += this.countNodes(value);
1015
+ }
1016
+ }
1017
+ return count;
1018
+ }
1019
+ /**
1020
+ * Check for additional syntax patterns that may indicate issues
1021
+ */
1022
+ checkSyntaxPatterns(content, file, issues) {
1023
+ const lines = content.split("\n");
1024
+ for (let i = 0; i < lines.length; i++) {
1025
+ const line = lines[i];
1026
+ const lineNum = i + 1;
1027
+ if (line.includes(";;")) {
1028
+ issues.push({
1029
+ id: `syntax/double-semicolon/${file}/${lineNum}`,
1030
+ severity: "low",
1031
+ type: "SYNTAX_WARNING",
1032
+ message: "Double semicolon detected",
1033
+ file,
1034
+ line: lineNum,
1035
+ column: line.indexOf(";;") + 1,
1036
+ fix: "Remove extra semicolon",
1037
+ snippet: line.trim()
1038
+ });
1039
+ }
1040
+ if (/console\.assert\([^,]+,\s*\)/.test(line)) {
1041
+ issues.push({
1042
+ id: `syntax/empty-assert/${file}/${lineNum}`,
1043
+ severity: "medium",
1044
+ type: "SYNTAX_WARNING",
1045
+ message: "console.assert with empty message",
1046
+ file,
1047
+ line: lineNum,
1048
+ fix: "Add assertion message for debugging",
1049
+ snippet: line.trim()
1050
+ });
1051
+ }
1052
+ if (/if\s*\([^=]*=\s*[^=]/.test(line) && !/if\s*\([^=]*[=!]==/.test(line)) {
1053
+ const assignMatch = line.match(/if\s*\(\s*(\w+)\s*=\s*[^=]/);
1054
+ if (assignMatch) {
1055
+ issues.push({
1056
+ id: `syntax/assignment-in-condition/${file}/${lineNum}`,
1057
+ severity: "medium",
1058
+ type: "SYNTAX_WARNING",
1059
+ message: "Possible assignment in condition (did you mean ===?)",
1060
+ file,
1061
+ line: lineNum,
1062
+ fix: "Use === for comparison, or wrap in extra parentheses if intentional",
1063
+ snippet: line.trim()
1064
+ });
1065
+ }
1066
+ }
1067
+ }
1068
+ }
1069
+ };
1070
+ var CompletenessAnalyzer = class {
1071
+ static {
1072
+ __name(this, "CompletenessAnalyzer");
1073
+ }
1074
+ id = "completeness";
1075
+ name = "Completeness Detection";
1076
+ filePatterns = [
1077
+ "*.ts",
1078
+ "*.tsx",
1079
+ "*.js",
1080
+ "*.jsx"
1081
+ ];
1082
+ todoPatterns = [
1083
+ /\/\/\s*TODO\b/gi,
1084
+ /\/\/\s*FIXME\b/gi,
1085
+ /\/\/\s*XXX\b/gi,
1086
+ /\/\/\s*HACK\b/gi,
1087
+ /\/\*\s*TODO\b/gi,
1088
+ /\/\*\s*FIXME\b/gi
1089
+ ];
1090
+ placeholderPatterns = [
1091
+ /throw\s+new\s+Error\s*\(\s*['"`].*not\s*implemented.*['"`]\s*\)/gi,
1092
+ /throw\s+new\s+Error\s*\(\s*['"`]TODO.*['"`]\s*\)/gi,
1093
+ /NotImplementedError/gi,
1094
+ /throw\s+new\s+Error\s*\(\s*['"`]STUB['"`]\s*\)/gi
1095
+ ];
1096
+ parserOptions = {
1097
+ sourceType: "module",
1098
+ plugins: [
1099
+ "typescript",
1100
+ "jsx"
1101
+ ],
1102
+ errorRecovery: true
1103
+ };
1104
+ async analyze(context) {
1105
+ const startTime = performance.now();
1106
+ const issues = [];
1107
+ let filesAnalyzed = 0;
1108
+ let nodesVisited = 0;
1109
+ const parseErrors = [];
1110
+ for (const [file, content] of context.contents) {
1111
+ if (!this.shouldAnalyzeFile(file)) {
1112
+ continue;
1113
+ }
1114
+ filesAnalyzed++;
1115
+ this.checkTodoComments(content, file, issues);
1116
+ this.checkPlaceholderPatterns(content, file, issues);
1117
+ try {
1118
+ const ast = parse(content, {
1119
+ ...this.parserOptions,
1120
+ plugins: this.getPluginsForFile(file)
1121
+ });
1122
+ const result = this.analyzeAST(ast, content, file);
1123
+ issues.push(...result.issues);
1124
+ nodesVisited += result.nodesVisited;
1125
+ } catch (error) {
1126
+ parseErrors.push(`${file}: ${error instanceof Error ? error.message : String(error)}`);
1127
+ }
1128
+ }
1129
+ return {
1130
+ analyzer: this.id,
1131
+ success: true,
1132
+ issues,
1133
+ coverage: filesAnalyzed / Math.max(context.files.length, 1),
1134
+ duration: performance.now() - startTime,
1135
+ metadata: {
1136
+ filesAnalyzed,
1137
+ nodesVisited,
1138
+ patternsChecked: [
1139
+ "TODO",
1140
+ "FIXME",
1141
+ "EMPTY_CATCH",
1142
+ "EMPTY_FUNCTION",
1143
+ "NOT_IMPLEMENTED",
1144
+ "PLACEHOLDER"
1145
+ ],
1146
+ parseErrors
1147
+ }
1148
+ };
1149
+ }
1150
+ shouldRun(context) {
1151
+ return context.files.some((f) => this.shouldAnalyzeFile(f));
1152
+ }
1153
+ shouldAnalyzeFile(file) {
1154
+ const ext = file.split(".").pop()?.toLowerCase();
1155
+ return [
1156
+ "ts",
1157
+ "tsx",
1158
+ "js",
1159
+ "jsx"
1160
+ ].includes(ext || "");
1161
+ }
1162
+ getPluginsForFile(file) {
1163
+ const plugins = [
1164
+ "typescript"
1165
+ ];
1166
+ if (file.endsWith(".tsx") || file.endsWith(".jsx")) {
1167
+ plugins.push("jsx");
1168
+ }
1169
+ return plugins;
1170
+ }
1171
+ /**
1172
+ * Check for TODO/FIXME comments
1173
+ */
1174
+ checkTodoComments(content, file, issues) {
1175
+ const lines = content.split("\n");
1176
+ for (let i = 0; i < lines.length; i++) {
1177
+ const line = lines[i];
1178
+ const lineNum = i + 1;
1179
+ for (const pattern of this.todoPatterns) {
1180
+ pattern.lastIndex = 0;
1181
+ if (pattern.test(line)) {
1182
+ const todoContent = line.trim().slice(0, 100);
1183
+ issues.push({
1184
+ id: `completeness/todo/${file}/${lineNum}`,
1185
+ severity: "medium",
1186
+ type: "INCOMPLETE_IMPLEMENTATION",
1187
+ message: `TODO/FIXME: ${todoContent}`,
1188
+ file,
1189
+ line: lineNum,
1190
+ snippet: todoContent
1191
+ });
1192
+ break;
1193
+ }
1194
+ }
1195
+ }
1196
+ }
1197
+ /**
1198
+ * Check for placeholder/stub patterns
1199
+ */
1200
+ checkPlaceholderPatterns(content, file, issues) {
1201
+ const lines = content.split("\n");
1202
+ for (let i = 0; i < lines.length; i++) {
1203
+ const line = lines[i];
1204
+ const lineNum = i + 1;
1205
+ for (const pattern of this.placeholderPatterns) {
1206
+ pattern.lastIndex = 0;
1207
+ if (pattern.test(line)) {
1208
+ issues.push({
1209
+ id: `completeness/placeholder/${file}/${lineNum}`,
1210
+ severity: "high",
1211
+ type: "INCOMPLETE_IMPLEMENTATION",
1212
+ message: 'Placeholder implementation: "not implemented" or similar',
1213
+ file,
1214
+ line: lineNum,
1215
+ fix: "Implement the functionality or remove the placeholder",
1216
+ snippet: line.trim().slice(0, 100)
1217
+ });
1218
+ break;
1219
+ }
1220
+ }
1221
+ }
1222
+ }
1223
+ /**
1224
+ * AST-based detection of empty/incomplete code
1225
+ */
1226
+ analyzeAST(ast, _content, file) {
1227
+ const issues = [];
1228
+ let nodesVisited = 0;
1229
+ traverse(ast, {
1230
+ enter() {
1231
+ nodesVisited++;
1232
+ },
1233
+ // Empty catch blocks
1234
+ CatchClause: /* @__PURE__ */ __name((path) => {
1235
+ const body = path.node.body;
1236
+ if (body.body.length === 0) {
1237
+ issues.push({
1238
+ id: `completeness/empty-catch/${file}/${path.node.loc?.start.line}`,
1239
+ severity: "medium",
1240
+ type: "INCOMPLETE_IMPLEMENTATION",
1241
+ message: "Empty catch block - errors silently swallowed",
1242
+ file,
1243
+ line: path.node.loc?.start.line,
1244
+ fix: "Add error handling, rethrow, or log the error"
1245
+ });
1246
+ } else if (body.body.length === 1) {
1247
+ const stmt = body.body[0];
1248
+ if (stmt.type === "EmptyStatement") {
1249
+ issues.push({
1250
+ id: `completeness/empty-catch/${file}/${path.node.loc?.start.line}`,
1251
+ severity: "medium",
1252
+ type: "INCOMPLETE_IMPLEMENTATION",
1253
+ message: "Catch block contains only empty statement",
1254
+ file,
1255
+ line: path.node.loc?.start.line,
1256
+ fix: "Add proper error handling"
1257
+ });
1258
+ }
1259
+ }
1260
+ }, "CatchClause"),
1261
+ // Empty function bodies (excluding type declarations and interface methods)
1262
+ FunctionDeclaration: /* @__PURE__ */ __name((path) => {
1263
+ if (path.node.body.body.length === 0) {
1264
+ const funcName = path.node.id?.name || "anonymous";
1265
+ {
1266
+ issues.push({
1267
+ id: `completeness/empty-fn/${file}/${path.node.loc?.start.line}`,
1268
+ severity: "medium",
1269
+ type: "INCOMPLETE_IMPLEMENTATION",
1270
+ message: `Empty function body: ${funcName}()`,
1271
+ file,
1272
+ line: path.node.loc?.start.line,
1273
+ fix: "Implement the function or mark as abstract/stub if intentional"
1274
+ });
1275
+ }
1276
+ }
1277
+ }, "FunctionDeclaration"),
1278
+ // Empty method bodies
1279
+ ClassMethod: /* @__PURE__ */ __name((path) => {
1280
+ if (path.node.abstract) {
1281
+ return;
1282
+ }
1283
+ if (path.node.kind === "get" || path.node.kind === "set") {
1284
+ return;
1285
+ }
1286
+ const body = path.node.body;
1287
+ if (body && body.body.length === 0) {
1288
+ const methodName = path.node.key.type === "Identifier" ? path.node.key.name : "anonymous";
1289
+ if (methodName === "constructor") {
1290
+ return;
1291
+ }
1292
+ issues.push({
1293
+ id: `completeness/empty-method/${file}/${path.node.loc?.start.line}`,
1294
+ severity: "medium",
1295
+ type: "INCOMPLETE_IMPLEMENTATION",
1296
+ message: `Empty method body: ${methodName}()`,
1297
+ file,
1298
+ line: path.node.loc?.start.line,
1299
+ fix: "Implement the method or mark as abstract if intentional"
1300
+ });
1301
+ }
1302
+ }, "ClassMethod"),
1303
+ // Arrow functions that just throw or are empty (might be intentional)
1304
+ ArrowFunctionExpression: /* @__PURE__ */ __name((path) => {
1305
+ const body = path.node.body;
1306
+ if (body.type === "BlockStatement" && body.body.length === 0) {
1307
+ const parent = path.parent;
1308
+ if (parent.type === "VariableDeclarator") {
1309
+ const varName = parent.id.type === "Identifier" ? parent.id.name : "anonymous";
1310
+ issues.push({
1311
+ id: `completeness/empty-arrow/${file}/${path.node.loc?.start.line}`,
1312
+ severity: "low",
1313
+ type: "INCOMPLETE_IMPLEMENTATION",
1314
+ message: `Empty arrow function: ${varName}`,
1315
+ file,
1316
+ line: path.node.loc?.start.line,
1317
+ fix: "Implement the function or use () => {} if intentionally empty"
1318
+ });
1319
+ }
1320
+ }
1321
+ }, "ArrowFunctionExpression"),
1322
+ // Check for console.log that might be debug code
1323
+ CallExpression: /* @__PURE__ */ __name((path) => {
1324
+ const callee = path.node.callee;
1325
+ if (callee.type === "MemberExpression" && callee.object.type === "Identifier" && callee.object.name === "console" && callee.property.type === "Identifier" && callee.property.name === "log") {
1326
+ const firstArg = path.node.arguments[0];
1327
+ if (firstArg && firstArg.type === "StringLiteral") {
1328
+ const msg = firstArg.value.toLowerCase();
1329
+ if (msg.includes("debug") || msg.includes("test") || msg.includes("todo") || msg.includes("remove")) {
1330
+ issues.push({
1331
+ id: `completeness/debug-log/${file}/${path.node.loc?.start.line}`,
1332
+ severity: "low",
1333
+ type: "DEBUG_CODE",
1334
+ message: `Debug console.log left in code: "${firstArg.value.slice(0, 50)}"`,
1335
+ file,
1336
+ line: path.node.loc?.start.line,
1337
+ fix: "Remove debug logging before commit"
1338
+ });
1339
+ }
1340
+ }
1341
+ }
1342
+ }, "CallExpression")
1343
+ });
1344
+ return {
1345
+ issues,
1346
+ nodesVisited
1347
+ };
1348
+ }
1349
+ };
1350
+ var EXPORT_PATTERNS = [
1351
+ /export\s+(const|function|class|interface|type|enum)\s+(\w+)/g,
1352
+ /export\s+default\s+(function|class)?\s*(\w+)?/g,
1353
+ /export\s+\{([^}]+)\}/g
1354
+ ];
1355
+ var PERFORMANCE_PATTERNS = [
1356
+ {
1357
+ pattern: /\.forEach\s*\(/g,
1358
+ type: "computation",
1359
+ risk: "low"
1360
+ },
1361
+ {
1362
+ pattern: /for\s*\(\s*let\s+\w+\s*=\s*0/g,
1363
+ type: "computation",
1364
+ risk: "low"
1365
+ },
1366
+ {
1367
+ pattern: /while\s*\(/g,
1368
+ type: "computation",
1369
+ risk: "medium"
1370
+ },
1371
+ {
1372
+ pattern: /async\s+function|await\s+/g,
1373
+ type: "io",
1374
+ risk: "medium"
1375
+ },
1376
+ {
1377
+ pattern: /new\s+(Map|Set|Array)\s*\(/g,
1378
+ type: "memory",
1379
+ risk: "low"
1380
+ },
1381
+ {
1382
+ pattern: /JSON\.(parse|stringify)/g,
1383
+ type: "computation",
1384
+ risk: "medium"
1385
+ },
1386
+ {
1387
+ pattern: /readFileSync|writeFileSync/g,
1388
+ type: "io",
1389
+ risk: "high"
1390
+ },
1391
+ {
1392
+ pattern: /spawn|exec\s*\(/g,
1393
+ type: "io",
1394
+ risk: "high"
1395
+ },
1396
+ {
1397
+ pattern: /import\s*\(/g,
1398
+ type: "bundle",
1399
+ risk: "low"
1400
+ },
1401
+ {
1402
+ pattern: /require\s*\(/g,
1403
+ type: "bundle",
1404
+ risk: "medium"
1405
+ }
1406
+ ];
1407
+ var TEST_FILE_PATTERNS = [
1408
+ /\.test\.[tj]sx?$/,
1409
+ /\.spec\.[tj]sx?$/,
1410
+ /__tests__\//,
1411
+ /test\//,
1412
+ /tests\//
1413
+ ];
1414
+ var ChangeImpactAnalyzer = class {
1415
+ static {
1416
+ __name(this, "ChangeImpactAnalyzer");
1417
+ }
1418
+ id = "change-impact";
1419
+ name = "Change Impact Analyzer";
1420
+ filePatterns = [
1421
+ "**/*.ts",
1422
+ "**/*.tsx",
1423
+ "**/*.js",
1424
+ "**/*.jsx"
1425
+ ];
1426
+ workspaceRoot;
1427
+ dependencyGraph = /* @__PURE__ */ new Map();
1428
+ reverseDependencyGraph = /* @__PURE__ */ new Map();
1429
+ constructor(workspaceRoot) {
1430
+ this.workspaceRoot = workspaceRoot;
1431
+ }
1432
+ /**
1433
+ * Check if this analyzer should run
1434
+ */
1435
+ shouldRun(context) {
1436
+ return context.files.some((f) => this.filePatterns.some((p) => new RegExp(p.replace(/\*/g, ".*")).test(f)));
1437
+ }
1438
+ /**
1439
+ * Run impact analysis
1440
+ */
1441
+ async analyze(context) {
1442
+ const start = Date.now();
1443
+ const issues = [];
1444
+ try {
1445
+ await this.buildDependencyGraph(context);
1446
+ for (const file of context.files) {
1447
+ const content = context.contents.get(file);
1448
+ if (!content) {
1449
+ continue;
1450
+ }
1451
+ const breakingChanges = this.detectBreakingChanges(content, file);
1452
+ for (const bc of breakingChanges) {
1453
+ issues.push({
1454
+ id: `impact/breaking/${bc.type}/${file}/${bc.symbol}`,
1455
+ severity: bc.severity,
1456
+ type: `BREAKING_${bc.type.toUpperCase()}`,
1457
+ message: bc.description,
1458
+ file,
1459
+ fix: bc.migration
1460
+ });
1461
+ }
1462
+ const perfImpacts = this.detectPerformanceImpacts(content, file);
1463
+ for (const pi of perfImpacts) {
1464
+ if (pi.risk === "high" || pi.risk === "critical") {
1465
+ issues.push({
1466
+ id: `impact/perf/${pi.type}/${file}/${pi.component}`,
1467
+ severity: pi.risk === "critical" ? "critical" : "high",
1468
+ type: `PERF_${pi.type.toUpperCase()}`,
1469
+ message: pi.description,
1470
+ file,
1471
+ fix: pi.recommendation
1472
+ });
1473
+ }
1474
+ }
1475
+ const affectedTests = this.findAffectedTests(file);
1476
+ if (affectedTests.length > 5) {
1477
+ issues.push({
1478
+ id: `impact/tests/${file}`,
1479
+ severity: "medium",
1480
+ type: "HIGH_TEST_IMPACT",
1481
+ message: `Change affects ${affectedTests.length} test files - consider running full test suite`,
1482
+ file
1483
+ });
1484
+ }
1485
+ }
1486
+ return {
1487
+ analyzer: this.id,
1488
+ success: true,
1489
+ issues,
1490
+ coverage: 1,
1491
+ duration: Date.now() - start,
1492
+ metadata: {
1493
+ filesAnalyzed: context.files.length
1494
+ }
1495
+ };
1496
+ } catch (error) {
1497
+ return {
1498
+ analyzer: this.id,
1499
+ success: false,
1500
+ issues: [
1501
+ {
1502
+ id: "impact/error",
1503
+ severity: "high",
1504
+ type: "ANALYSIS_ERROR",
1505
+ message: error instanceof Error ? error.message : String(error)
1506
+ }
1507
+ ],
1508
+ coverage: 0,
1509
+ duration: Date.now() - start
1510
+ };
1511
+ }
1512
+ }
1513
+ /**
1514
+ * Get full impact analysis (more detailed than standard analyze)
1515
+ */
1516
+ async getFullImpact(files, contents) {
1517
+ const start = Date.now();
1518
+ const context = {
1519
+ workspaceRoot: this.workspaceRoot,
1520
+ files,
1521
+ contents
1522
+ };
1523
+ await this.buildDependencyGraph(context);
1524
+ const affectedTests = [];
1525
+ const breakingChanges = [];
1526
+ const performanceImpacts = [];
1527
+ const dependentFiles = [];
1528
+ const recommendations = [];
1529
+ for (const file of files) {
1530
+ const content = contents.get(file) || "";
1531
+ const tests = this.findAffectedTests(file);
1532
+ affectedTests.push(...tests);
1533
+ const breaks = this.detectBreakingChanges(content, file);
1534
+ breakingChanges.push(...breaks);
1535
+ const perfs = this.detectPerformanceImpacts(content, file);
1536
+ performanceImpacts.push(...perfs);
1537
+ const deps = this.findDependentFiles(file);
1538
+ dependentFiles.push(...deps);
1539
+ }
1540
+ const impactScore = this.calculateImpactScore(affectedTests, breakingChanges, performanceImpacts, dependentFiles);
1541
+ if (breakingChanges.length > 0) {
1542
+ recommendations.push(`\u26A0\uFE0F ${breakingChanges.length} breaking change(s) detected - update dependent code`);
1543
+ }
1544
+ if (affectedTests.length > 10) {
1545
+ recommendations.push(`\u{1F9EA} Run full test suite - ${affectedTests.length} tests potentially affected`);
1546
+ }
1547
+ if (performanceImpacts.some((p) => p.risk === "high" || p.risk === "critical")) {
1548
+ recommendations.push("\u26A1 Performance-sensitive code modified - run benchmarks");
1549
+ }
1550
+ if (dependentFiles.length > 20) {
1551
+ recommendations.push("\u{1F517} High ripple effect - consider incremental rollout");
1552
+ }
1553
+ return {
1554
+ filesAnalyzed: files.length,
1555
+ affectedTests: this.dedupeItems(affectedTests),
1556
+ breakingChanges,
1557
+ performanceImpacts,
1558
+ dependentFiles: this.dedupeItems(dependentFiles),
1559
+ impactScore,
1560
+ recommendations,
1561
+ duration: Date.now() - start
1562
+ };
1563
+ }
1564
+ // =========================================================================
1565
+ // Private Methods
1566
+ // =========================================================================
1567
+ /**
1568
+ * Build dependency graph from file contents
1569
+ */
1570
+ async buildDependencyGraph(context) {
1571
+ this.dependencyGraph.clear();
1572
+ this.reverseDependencyGraph.clear();
1573
+ for (const file of context.files) {
1574
+ const content = context.contents.get(file);
1575
+ if (!content) {
1576
+ continue;
1577
+ }
1578
+ const imports = this.extractImports(content, file);
1579
+ this.dependencyGraph.set(file, imports);
1580
+ for (const imp of imports) {
1581
+ const existing = this.reverseDependencyGraph.get(imp) || [];
1582
+ existing.push(file);
1583
+ this.reverseDependencyGraph.set(imp, existing);
1584
+ }
1585
+ }
1586
+ }
1587
+ /**
1588
+ * Extract import statements from file content using AST analysis.
1589
+ *
1590
+ * UPGRADED (11b): Replaces regex-based import extraction with proper AST walking
1591
+ * via packages/core/src/analysis/ast/import-extractor.ts (oxc-parser based).
1592
+ * This eliminates false positives from imports in strings/comments and correctly
1593
+ * handles dynamic imports, re-exports, and type-only imports.
1594
+ */
1595
+ extractImports(content, fromFile) {
1596
+ const rawSources = extractImportSources(content, fromFile);
1597
+ const imports = [];
1598
+ for (const source of rawSources) {
1599
+ const importPath = this.resolveImportPath(source, fromFile);
1600
+ if (importPath) {
1601
+ imports.push(importPath);
1602
+ }
1603
+ }
1604
+ return imports;
1605
+ }
1606
+ /**
1607
+ * Resolve import path to absolute file path
1608
+ */
1609
+ resolveImportPath(importPath, fromFile) {
1610
+ if (!importPath.startsWith(".") && !importPath.startsWith("/")) {
1611
+ return null;
1612
+ }
1613
+ const dir = dirname(fromFile);
1614
+ const extensions = [
1615
+ ".ts",
1616
+ ".tsx",
1617
+ ".js",
1618
+ ".jsx",
1619
+ "/index.ts",
1620
+ "/index.tsx",
1621
+ "/index.js"
1622
+ ];
1623
+ for (const ext of extensions) {
1624
+ const resolved = `${dir}/${importPath}${ext}`.replace(/\/\.\//g, "/");
1625
+ return resolved;
1626
+ }
1627
+ return null;
1628
+ }
1629
+ /**
1630
+ * Find test files that might be affected by a change
1631
+ */
1632
+ findAffectedTests(file) {
1633
+ const tests = [];
1634
+ const relPath = relative(this.workspaceRoot, file);
1635
+ const fileName = basename(file).replace(/\.[tj]sx?$/, "");
1636
+ const directTestPatterns = [
1637
+ `${fileName}.test.ts`,
1638
+ `${fileName}.test.tsx`,
1639
+ `${fileName}.spec.ts`,
1640
+ `${fileName}.spec.tsx`,
1641
+ `__tests__/${fileName}.test.ts`,
1642
+ `__tests__/${fileName}.test.tsx`
1643
+ ];
1644
+ for (const pattern of directTestPatterns) {
1645
+ tests.push({
1646
+ path: pattern,
1647
+ reason: "Direct test file for changed source",
1648
+ level: "high"
1649
+ });
1650
+ }
1651
+ const importers = this.reverseDependencyGraph.get(file) || [];
1652
+ for (const importer of importers) {
1653
+ if (this.isTestFile(importer)) {
1654
+ tests.push({
1655
+ path: relative(this.workspaceRoot, importer),
1656
+ reason: "Test file imports changed module",
1657
+ level: "medium"
1658
+ });
1659
+ }
1660
+ }
1661
+ if (relPath.includes("/core/") || relPath.includes("/services/")) {
1662
+ tests.push({
1663
+ path: "**/*.integration.test.ts",
1664
+ reason: "Core module change may affect integration tests",
1665
+ level: "low"
1666
+ });
1667
+ }
1668
+ return tests;
1669
+ }
1670
+ /**
1671
+ * Check if a file is a test file
1672
+ */
1673
+ isTestFile(file) {
1674
+ return TEST_FILE_PATTERNS.some((p) => p.test(file));
1675
+ }
1676
+ /**
1677
+ * Detect breaking changes in content
1678
+ */
1679
+ detectBreakingChanges(content, file) {
1680
+ const breaks = [];
1681
+ for (const pattern of EXPORT_PATTERNS) {
1682
+ const regex = new RegExp(pattern.source, pattern.flags);
1683
+ let match2;
1684
+ while ((match2 = regex.exec(content)) !== null) {
1685
+ const symbolName = match2[2] || match2[1];
1686
+ if (symbolName) {
1687
+ breaks.push({
1688
+ type: "export",
1689
+ symbol: symbolName,
1690
+ file,
1691
+ description: `Exported symbol '${symbolName}' may have changed`,
1692
+ severity: "medium",
1693
+ migration: `Verify consumers of '${symbolName}' are updated`
1694
+ });
1695
+ }
1696
+ }
1697
+ }
1698
+ const interfaceRegex = /(?:export\s+)?interface\s+(\w+)\s*\{([^}]+)\}/g;
1699
+ let match;
1700
+ while ((match = interfaceRegex.exec(content)) !== null) {
1701
+ const interfaceName = match[1];
1702
+ const body = match[2];
1703
+ if (body.includes("?:") || body.includes(": ")) {
1704
+ breaks.push({
1705
+ type: "type",
1706
+ symbol: interfaceName,
1707
+ file,
1708
+ description: `Interface '${interfaceName}' definition changed`,
1709
+ severity: "medium"
1710
+ });
1711
+ }
1712
+ }
1713
+ return breaks;
1714
+ }
1715
+ /**
1716
+ * Detect performance-sensitive code changes
1717
+ */
1718
+ detectPerformanceImpacts(content, file) {
1719
+ const impacts = [];
1720
+ for (const { pattern, type, risk } of PERFORMANCE_PATTERNS) {
1721
+ const regex = new RegExp(pattern.source, pattern.flags);
1722
+ let match;
1723
+ while ((match = regex.exec(content)) !== null) {
1724
+ impacts.push({
1725
+ type,
1726
+ description: `${type} operation detected: ${match[0]}`,
1727
+ risk,
1728
+ component: basename(file),
1729
+ recommendation: this.getPerformanceRecommendation(type)
1730
+ });
1731
+ }
1732
+ }
1733
+ return impacts;
1734
+ }
1735
+ /**
1736
+ * Get recommendation for performance issue type
1737
+ */
1738
+ getPerformanceRecommendation(type) {
1739
+ switch (type) {
1740
+ case "hotpath":
1741
+ return "Consider memoization or caching for hot paths";
1742
+ case "memory":
1743
+ return "Monitor memory usage, consider object pooling";
1744
+ case "io":
1745
+ return "Use async operations, consider batching";
1746
+ case "computation":
1747
+ return "Profile for bottlenecks, consider Web Workers";
1748
+ case "bundle":
1749
+ return "Use dynamic imports for code splitting";
1750
+ default:
1751
+ return "Profile before optimizing";
1752
+ }
1753
+ }
1754
+ /**
1755
+ * Find files that depend on changed file
1756
+ */
1757
+ findDependentFiles(file) {
1758
+ const dependents = [];
1759
+ const visited = /* @__PURE__ */ new Set();
1760
+ const traverse3 = /* @__PURE__ */ __name((current, depth) => {
1761
+ if (visited.has(current) || depth > 3) {
1762
+ return;
1763
+ }
1764
+ visited.add(current);
1765
+ const importers = this.reverseDependencyGraph.get(current) || [];
1766
+ for (const importer of importers) {
1767
+ dependents.push({
1768
+ path: relative(this.workspaceRoot, importer),
1769
+ reason: depth === 0 ? "Directly imports changed file" : `Transitive dependency (depth ${depth})`,
1770
+ level: depth === 0 ? "high" : depth === 1 ? "medium" : "low"
1771
+ });
1772
+ traverse3(importer, depth + 1);
1773
+ }
1774
+ }, "traverse");
1775
+ traverse3(file, 0);
1776
+ return dependents;
1777
+ }
1778
+ /**
1779
+ * Calculate overall impact score
1780
+ */
1781
+ calculateImpactScore(tests, breaks, perfs, deps) {
1782
+ let score = 0;
1783
+ score += Math.min(tests.length * 0.05, 0.25);
1784
+ score += Math.min(breaks.length * 0.15, 0.35);
1785
+ score += Math.min(perfs.filter((p) => p.risk === "high").length * 0.1, 0.2);
1786
+ score += Math.min(deps.length * 0.02, 0.2);
1787
+ return Math.min(score, 1);
1788
+ }
1789
+ /**
1790
+ * Deduplicate impact items
1791
+ */
1792
+ dedupeItems(items) {
1793
+ const seen = /* @__PURE__ */ new Set();
1794
+ return items.filter((item) => {
1795
+ if (seen.has(item.path)) {
1796
+ return false;
1797
+ }
1798
+ seen.add(item.path);
1799
+ return true;
1800
+ });
1801
+ }
1802
+ };
1803
+ function createChangeImpactAnalyzer(workspaceRoot) {
1804
+ return new ChangeImpactAnalyzer(workspaceRoot);
1805
+ }
1806
+ __name(createChangeImpactAnalyzer, "createChangeImpactAnalyzer");
1807
+ var SecurityAnalyzer = class {
1808
+ static {
1809
+ __name(this, "SecurityAnalyzer");
1810
+ }
1811
+ id = "security";
1812
+ name = "Security Analysis";
1813
+ filePatterns = [
1814
+ "*.ts",
1815
+ "*.tsx",
1816
+ "*.js",
1817
+ "*.jsx"
1818
+ ];
1819
+ parserOptions = {
1820
+ sourceType: "module",
1821
+ plugins: [
1822
+ "typescript",
1823
+ "jsx"
1824
+ ],
1825
+ errorRecovery: true
1826
+ };
1827
+ async analyze(context) {
1828
+ const startTime = performance.now();
1829
+ const issues = [];
1830
+ let filesAnalyzed = 0;
1831
+ let nodesVisited = 0;
1832
+ const parseErrors = [];
1833
+ for (const [file, content] of context.contents) {
1834
+ if (!this.shouldAnalyzeFile(file)) {
1835
+ continue;
1836
+ }
1837
+ filesAnalyzed++;
1838
+ try {
1839
+ const ast = parse(content, {
1840
+ ...this.parserOptions,
1841
+ plugins: this.getPluginsForFile(file)
1842
+ });
1843
+ const fileIssues = this.analyzeAST(ast, content, file);
1844
+ issues.push(...fileIssues.issues);
1845
+ nodesVisited += fileIssues.nodesVisited;
1846
+ } catch (error) {
1847
+ parseErrors.push(`${file}: ${error instanceof Error ? error.message : String(error)}`);
1848
+ issues.push({
1849
+ id: `security/parse-error/${file}`,
1850
+ severity: "info",
1851
+ type: "PARSE_ERROR",
1852
+ message: `Could not parse for security analysis: ${error instanceof Error ? error.message : String(error)}`,
1853
+ file
1854
+ });
1855
+ }
1856
+ }
1857
+ return {
1858
+ analyzer: this.id,
1859
+ success: true,
1860
+ issues,
1861
+ coverage: filesAnalyzed / Math.max(context.files.length, 1),
1862
+ duration: performance.now() - startTime,
1863
+ metadata: {
1864
+ filesAnalyzed,
1865
+ nodesVisited,
1866
+ patternsChecked: [
1867
+ "UNSAFE_EVAL",
1868
+ "PATH_TRAVERSAL",
1869
+ "MISSING_SIGNAL_HANDLER",
1870
+ "COMMAND_INJECTION",
1871
+ "SQL_INJECTION",
1872
+ "XSS_RISK",
1873
+ "HARDCODED_SECRET",
1874
+ "UNSAFE_REGEX"
1875
+ ],
1876
+ parseErrors
1877
+ }
1878
+ };
1879
+ }
1880
+ shouldRun(context) {
1881
+ return context.files.some((f) => this.shouldAnalyzeFile(f));
1882
+ }
1883
+ shouldAnalyzeFile(file) {
1884
+ const ext = file.split(".").pop()?.toLowerCase();
1885
+ return [
1886
+ "ts",
1887
+ "tsx",
1888
+ "js",
1889
+ "jsx"
1890
+ ].includes(ext || "");
1891
+ }
1892
+ getPluginsForFile(file) {
1893
+ const plugins = [
1894
+ "typescript"
1895
+ ];
1896
+ if (file.endsWith(".tsx") || file.endsWith(".jsx")) {
1897
+ plugins.push("jsx");
1898
+ }
1899
+ return plugins;
1900
+ }
1901
+ /**
1902
+ * Analyze AST for security issues
1903
+ */
1904
+ analyzeAST(ast, content, file) {
1905
+ const issues = [];
1906
+ let nodesVisited = 0;
1907
+ const fileContext = {
1908
+ isDaemon: false,
1909
+ hasSignalHandler: false};
1910
+ fileContext.isDaemon = content.includes(".listen(") || file.includes("daemon") || file.includes("server") || file.includes("worker");
1911
+ traverse(ast, {
1912
+ enter() {
1913
+ nodesVisited++;
1914
+ },
1915
+ // Detect eval()
1916
+ CallExpression: /* @__PURE__ */ __name((path) => {
1917
+ const callee = path.node.callee;
1918
+ if (callee.type === "Identifier" && callee.name === "eval") {
1919
+ issues.push({
1920
+ id: `security/eval/${file}/${path.node.loc?.start.line}`,
1921
+ severity: "critical",
1922
+ type: "UNSAFE_EVAL",
1923
+ message: "eval() allows arbitrary code execution",
1924
+ file,
1925
+ line: path.node.loc?.start.line,
1926
+ column: path.node.loc?.start.column,
1927
+ fix: "Use JSON.parse() for data or refactor logic to avoid eval"
1928
+ });
1929
+ }
1930
+ if (callee.type === "Identifier" && callee.name === "Function") {
1931
+ issues.push({
1932
+ id: `security/function-constructor/${file}/${path.node.loc?.start.line}`,
1933
+ severity: "critical",
1934
+ type: "UNSAFE_EVAL",
1935
+ message: "new Function() is equivalent to eval() and allows arbitrary code execution",
1936
+ file,
1937
+ line: path.node.loc?.start.line,
1938
+ column: path.node.loc?.start.column,
1939
+ fix: "Refactor to avoid dynamic code generation"
1940
+ });
1941
+ }
1942
+ if (callee.type === "Identifier" && (callee.name === "setTimeout" || callee.name === "setInterval")) {
1943
+ const firstArg = path.node.arguments[0];
1944
+ if (firstArg && firstArg.type === "StringLiteral") {
1945
+ issues.push({
1946
+ id: `security/string-timer/${file}/${path.node.loc?.start.line}`,
1947
+ severity: "high",
1948
+ type: "UNSAFE_EVAL",
1949
+ message: `${callee.name} with string argument executes code like eval()`,
1950
+ file,
1951
+ line: path.node.loc?.start.line,
1952
+ fix: "Pass a function instead of a string"
1953
+ });
1954
+ }
1955
+ }
1956
+ if (callee.type === "Identifier" && (callee.name === "exec" || callee.name === "execSync")) {
1957
+ const firstArg = path.node.arguments[0];
1958
+ if (firstArg && !this.isStaticString(firstArg)) {
1959
+ issues.push({
1960
+ id: `security/command-injection/${file}/${path.node.loc?.start.line}`,
1961
+ severity: "high",
1962
+ type: "COMMAND_INJECTION",
1963
+ message: "exec with dynamic command - potential command injection",
1964
+ file,
1965
+ line: path.node.loc?.start.line,
1966
+ fix: "Validate/sanitize input or use execFile with explicit arguments"
1967
+ });
1968
+ }
1969
+ }
1970
+ if (callee.type === "MemberExpression" && callee.object.type === "Identifier" && callee.object.name === "process" && callee.property.type === "Identifier" && callee.property.name === "on") {
1971
+ const firstArg = path.node.arguments[0];
1972
+ if (firstArg && firstArg.type === "StringLiteral") {
1973
+ if (firstArg.value === "SIGTERM" || firstArg.value === "SIGINT") {
1974
+ fileContext.hasSignalHandler = true;
1975
+ }
1976
+ }
1977
+ }
1978
+ }, "CallExpression"),
1979
+ // Detect fs operations with dynamic paths
1980
+ MemberExpression: /* @__PURE__ */ __name((path) => {
1981
+ const node = path.node;
1982
+ if (node.object.type === "Identifier" && (node.object.name === "fs" || node.object.name === "fsp")) {
1983
+ const parent = path.parentPath;
1984
+ if (parent.isCallExpression()) {
1985
+ const methodName = node.property.type === "Identifier" ? node.property.name : node.property.value;
1986
+ const pathMethods = [
1987
+ "readFile",
1988
+ "readFileSync",
1989
+ "writeFile",
1990
+ "writeFileSync",
1991
+ "readdir",
1992
+ "readdirSync",
1993
+ "stat",
1994
+ "statSync",
1995
+ "unlink",
1996
+ "unlinkSync",
1997
+ "mkdir",
1998
+ "mkdirSync",
1999
+ "rmdir",
2000
+ "rmdirSync",
2001
+ "access",
2002
+ "accessSync"
2003
+ ];
2004
+ if (pathMethods.includes(methodName)) {
2005
+ const firstArg = parent.node.arguments[0];
2006
+ if (firstArg && !this.isStaticPath(firstArg)) {
2007
+ issues.push({
2008
+ id: `security/path-traversal/${file}/${path.node.loc?.start.line}`,
2009
+ severity: "high",
2010
+ type: "PATH_TRAVERSAL",
2011
+ message: `fs.${methodName} with dynamic path - potential path traversal`,
2012
+ file,
2013
+ line: path.node.loc?.start.line,
2014
+ fix: "Validate paths against workspace root before use"
2015
+ });
2016
+ }
2017
+ }
2018
+ }
2019
+ }
2020
+ }, "MemberExpression"),
2021
+ // Check for dangerous regex patterns
2022
+ NewExpression: /* @__PURE__ */ __name((path) => {
2023
+ if (path.node.callee.type === "Identifier" && path.node.callee.name === "RegExp") {
2024
+ const firstArg = path.node.arguments[0];
2025
+ if (firstArg && !this.isStaticString(firstArg)) {
2026
+ issues.push({
2027
+ id: `security/unsafe-regex/${file}/${path.node.loc?.start.line}`,
2028
+ severity: "medium",
2029
+ type: "UNSAFE_REGEX",
2030
+ message: "Dynamic RegExp - potential ReDoS or injection vulnerability",
2031
+ file,
2032
+ line: path.node.loc?.start.line,
2033
+ fix: "Use static regex patterns or validate input"
2034
+ });
2035
+ }
2036
+ }
2037
+ }, "NewExpression"),
2038
+ // Check for innerHTML/dangerouslySetInnerHTML (XSS)
2039
+ JSXAttribute: /* @__PURE__ */ __name((path) => {
2040
+ const name = path.node.name;
2041
+ if (name.type === "JSXIdentifier" && name.name === "dangerouslySetInnerHTML") {
2042
+ issues.push({
2043
+ id: `security/xss-risk/${file}/${path.node.loc?.start.line}`,
2044
+ severity: "high",
2045
+ type: "XSS_RISK",
2046
+ message: "dangerouslySetInnerHTML can lead to XSS if content is not sanitized",
2047
+ file,
2048
+ line: path.node.loc?.start.line,
2049
+ fix: "Sanitize HTML content before rendering or avoid using dangerouslySetInnerHTML"
2050
+ });
2051
+ }
2052
+ }, "JSXAttribute"),
2053
+ // Check for hardcoded secrets in variable declarations
2054
+ VariableDeclarator: /* @__PURE__ */ __name((path) => {
2055
+ const id = path.node.id;
2056
+ const init = path.node.init;
2057
+ if (id.type === "Identifier" && init) {
2058
+ this.checkForHardcodedSecret(id.name, init, file, path.node.loc?.start.line, issues);
2059
+ }
2060
+ }, "VariableDeclarator"),
2061
+ // Check for hardcoded secrets in class properties
2062
+ ClassProperty: /* @__PURE__ */ __name((path) => {
2063
+ const key = path.node.key;
2064
+ const value = path.node.value;
2065
+ if (key.type === "Identifier" && value) {
2066
+ this.checkForHardcodedSecret(key.name, value, file, path.node.loc?.start.line, issues);
2067
+ }
2068
+ }, "ClassProperty"),
2069
+ // After traversal is complete, check daemon-specific patterns
2070
+ Program: {
2071
+ exit: /* @__PURE__ */ __name(() => {
2072
+ if (fileContext.isDaemon && !fileContext.hasSignalHandler) {
2073
+ issues.push({
2074
+ id: `security/signal-handler/${file}`,
2075
+ severity: "high",
2076
+ type: "MISSING_SIGNAL_HANDLER",
2077
+ message: "Daemon/server missing signal handlers (SIGTERM/SIGINT)",
2078
+ file,
2079
+ fix: "Add process.on('SIGTERM', gracefulShutdown) for clean shutdown"
2080
+ });
2081
+ }
2082
+ }, "exit")
2083
+ }
2084
+ });
2085
+ return {
2086
+ issues,
2087
+ nodesVisited
2088
+ };
2089
+ }
2090
+ /**
2091
+ * Check if expression is a static string (safe)
2092
+ */
2093
+ isStaticString(node) {
2094
+ if (node.type === "StringLiteral") {
2095
+ return true;
2096
+ }
2097
+ if (node.type === "TemplateLiteral" && node.expressions.length === 0) {
2098
+ return true;
2099
+ }
2100
+ return false;
2101
+ }
2102
+ /**
2103
+ * Check if expression is a static path (safe)
2104
+ */
2105
+ isStaticPath(node) {
2106
+ if (node.type === "StringLiteral") {
2107
+ return true;
2108
+ }
2109
+ if (node.type === "TemplateLiteral" && node.expressions.length === 0) {
2110
+ return true;
2111
+ }
2112
+ if (node.type === "CallExpression") {
2113
+ const callee = node.callee;
2114
+ if (callee.type === "MemberExpression" && callee.object.type === "Identifier" && callee.object.name === "path" && callee.property.type === "Identifier" && callee.property.name === "join") {
2115
+ return node.arguments.every((arg) => {
2116
+ if (arg.type === "StringLiteral") {
2117
+ return true;
2118
+ }
2119
+ if (arg.type === "Identifier" && (arg.name === "__dirname" || arg.name === "__filename")) {
2120
+ return true;
2121
+ }
2122
+ return false;
2123
+ });
2124
+ }
2125
+ }
2126
+ return false;
2127
+ }
2128
+ /**
2129
+ * Check if a value looks like a hardcoded secret
2130
+ */
2131
+ checkForHardcodedSecret(name, value, file, line, issues) {
2132
+ if (!value) {
2133
+ return;
2134
+ }
2135
+ const varName = name.toLowerCase();
2136
+ const secretIndicators = [
2137
+ "apikey",
2138
+ "api_key",
2139
+ "secret",
2140
+ "password",
2141
+ "token",
2142
+ "credential",
2143
+ "auth",
2144
+ "key"
2145
+ ];
2146
+ if (secretIndicators.some((s) => varName.includes(s))) {
2147
+ if (value.type === "StringLiteral" && value.value.length > 8) {
2148
+ const valueStr = value.value.toLowerCase();
2149
+ if (!valueStr.includes("placeholder") && !valueStr.includes("example") && !valueStr.includes("xxx") && !valueStr.includes("todo") && !valueStr.includes("your_") && !valueStr.includes("env.")) {
2150
+ issues.push({
2151
+ id: `security/hardcoded-secret/${file}/${line}`,
2152
+ severity: "critical",
2153
+ type: "HARDCODED_SECRET",
2154
+ message: `Possible hardcoded secret in "${name}"`,
2155
+ file,
2156
+ line,
2157
+ fix: "Use environment variables for secrets"
2158
+ });
2159
+ }
2160
+ }
2161
+ }
2162
+ }
2163
+ };
2164
+
2165
+ // ../../node_modules/@snapback/core/dist/analysis/pipeline.js
2166
+ var ANALYZER_COVERAGE_MAP = {
2167
+ syntax: "astParsed",
2168
+ security: "securityChecked",
2169
+ completeness: "completenessChecked",
2170
+ "change-impact": "architectureChecked",
2171
+ "import-graph": "importGraphChecked",
2172
+ complexity: "complexityChecked"
2173
+ };
2174
+ var CONFIDENCE_WEIGHTS = {
2175
+ syntax: 0.2,
2176
+ security: 0.25,
2177
+ completeness: 0.15,
2178
+ "change-impact": 0.1,
2179
+ "import-graph": 0.15,
2180
+ complexity: 0.15
2181
+ };
2182
+ async function runAnalysisPipeline(context, config) {
2183
+ const start = Date.now();
2184
+ const parallel = config?.parallel ?? true;
2185
+ const timeout = config?.timeout ?? 3e4;
2186
+ const allAnalyzers = createAnalyzers(context.workspaceRoot);
2187
+ const selectedAnalyzers = filterAnalyzers(allAnalyzers, context, config?.analyzers);
2188
+ const results = parallel ? await runParallel(selectedAnalyzers, context, timeout) : await runSequential(selectedAnalyzers, context, timeout);
2189
+ const coverage = buildCoverageInfo(results, context);
2190
+ const confidence = calculateConfidence(results, coverage);
2191
+ const allIssues = results.flatMap((r) => r.issues);
2192
+ const issuesBySeverity = groupBySeverity(allIssues);
2193
+ return {
2194
+ results,
2195
+ totalIssues: allIssues.length,
2196
+ issuesBySeverity,
2197
+ coverage,
2198
+ confidence,
2199
+ duration: Date.now() - start
2200
+ };
2201
+ }
2202
+ __name(runAnalysisPipeline, "runAnalysisPipeline");
2203
+ function createAnalyzers(workspaceRoot) {
2204
+ return [
2205
+ new SyntaxAnalyzer(),
2206
+ new SecurityAnalyzer(),
2207
+ new CompletenessAnalyzer(),
2208
+ new ComplexityAnalyzer(),
2209
+ new ImportGraphAnalyzer(),
2210
+ new ChangeImpactAnalyzer(workspaceRoot)
2211
+ ];
2212
+ }
2213
+ __name(createAnalyzers, "createAnalyzers");
2214
+ function filterAnalyzers(analyzers, context, selectedIds) {
2215
+ let filtered = analyzers;
2216
+ if (selectedIds && selectedIds.length > 0) {
2217
+ const idSet = new Set(selectedIds);
2218
+ filtered = filtered.filter((a) => idSet.has(a.id));
2219
+ }
2220
+ return filtered.filter((a) => a.shouldRun(context));
2221
+ }
2222
+ __name(filterAnalyzers, "filterAnalyzers");
2223
+ async function runParallel(analyzers, context, timeout) {
2224
+ const promises = analyzers.map((analyzer) => runWithTimeout(analyzer, context, timeout));
2225
+ return Promise.all(promises);
2226
+ }
2227
+ __name(runParallel, "runParallel");
2228
+ async function runSequential(analyzers, context, timeout) {
2229
+ const results = [];
2230
+ for (const analyzer of analyzers) {
2231
+ const result = await runWithTimeout(analyzer, context, timeout);
2232
+ results.push(result);
2233
+ }
2234
+ return results;
2235
+ }
2236
+ __name(runSequential, "runSequential");
2237
+ async function runWithTimeout(analyzer, context, timeout) {
2238
+ const start = Date.now();
2239
+ try {
2240
+ const result = await Promise.race([
2241
+ analyzer.analyze(context),
2242
+ new Promise((_, reject) => {
2243
+ setTimeout(() => reject(new Error(`Analyzer '${analyzer.id}' timed out after ${timeout}ms`)), timeout);
2244
+ })
2245
+ ]);
2246
+ return result;
2247
+ } catch (error) {
2248
+ return {
2249
+ analyzer: analyzer.id,
2250
+ success: false,
2251
+ issues: [
2252
+ {
2253
+ id: `pipeline/${analyzer.id}/error`,
2254
+ severity: "high",
2255
+ type: "ANALYZER_ERROR",
2256
+ message: error instanceof Error ? error.message : String(error)
2257
+ }
2258
+ ],
2259
+ coverage: 0,
2260
+ duration: Date.now() - start
2261
+ };
2262
+ }
2263
+ }
2264
+ __name(runWithTimeout, "runWithTimeout");
2265
+ function buildCoverageInfo(results, context) {
2266
+ const coverage = {
2267
+ astParsed: false,
2268
+ securityChecked: false,
2269
+ completenessChecked: false,
2270
+ architectureChecked: false,
2271
+ importGraphChecked: false,
2272
+ complexityChecked: false,
2273
+ filesCoverage: 0
2274
+ };
2275
+ for (const result of results) {
2276
+ const field = ANALYZER_COVERAGE_MAP[result.analyzer];
2277
+ if (field && field !== "filesCoverage" && result.success) {
2278
+ coverage[field] = true;
2279
+ }
2280
+ }
2281
+ const totalFiles = context.files.length;
2282
+ if (totalFiles > 0) {
2283
+ const successfulResults = results.filter((r) => r.success);
2284
+ const avgCoverage = successfulResults.length > 0 ? successfulResults.reduce((sum, r) => sum + r.coverage, 0) / successfulResults.length : 0;
2285
+ coverage.filesCoverage = avgCoverage;
2286
+ }
2287
+ return coverage;
2288
+ }
2289
+ __name(buildCoverageInfo, "buildCoverageInfo");
2290
+ function calculateConfidence(results, coverage) {
2291
+ const breakdown = {};
2292
+ let weightedSum = 0;
2293
+ let totalWeight = 0;
2294
+ let maxPossible = 0;
2295
+ for (const [id, weight] of Object.entries(CONFIDENCE_WEIGHTS)) {
2296
+ const result = results.find((r) => r.analyzer === id);
2297
+ totalWeight += weight;
2298
+ if (result) {
2299
+ const analyzerConfidence = result.success ? result.coverage : 0;
2300
+ breakdown[id] = analyzerConfidence;
2301
+ weightedSum += weight * analyzerConfidence;
2302
+ maxPossible += weight;
2303
+ } else {
2304
+ breakdown[id] = 0;
2305
+ }
2306
+ }
2307
+ const confidence = totalWeight > 0 ? weightedSum / totalWeight : 0;
2308
+ const maxPossibleConfidence = totalWeight > 0 ? maxPossible / totalWeight : 0;
2309
+ const ranCount = results.filter((r) => r.success).length;
2310
+ const totalAnalyzers = Object.keys(CONFIDENCE_WEIGHTS).length;
2311
+ const explanationParts = [
2312
+ `${ranCount}/${totalAnalyzers} analyzers ran successfully`,
2313
+ `Files coverage: ${(coverage.filesCoverage * 100).toFixed(0)}%`
2314
+ ];
2315
+ const failedAnalyzers = results.filter((r) => !r.success);
2316
+ if (failedAnalyzers.length > 0) {
2317
+ explanationParts.push(`Failed: ${failedAnalyzers.map((r) => r.analyzer).join(", ")}`);
2318
+ }
2319
+ return {
2320
+ confidence: Math.round(confidence * 100) / 100,
2321
+ breakdown,
2322
+ explanation: explanationParts.join(". "),
2323
+ maxPossibleConfidence: Math.round(maxPossibleConfidence * 100) / 100
2324
+ };
2325
+ }
2326
+ __name(calculateConfidence, "calculateConfidence");
2327
+ function groupBySeverity(issues) {
2328
+ const grouped = {
2329
+ critical: [],
2330
+ high: [],
2331
+ medium: [],
2332
+ low: [],
2333
+ info: []
2334
+ };
2335
+ for (const issue of issues) {
2336
+ const severity = issue.severity || "info";
2337
+ if (severity in grouped) {
2338
+ grouped[severity].push(issue);
2339
+ } else {
2340
+ grouped.info.push(issue);
2341
+ }
2342
+ }
2343
+ return grouped;
2344
+ }
2345
+ __name(groupBySeverity, "groupBySeverity");
2346
+
2347
+ // ../../node_modules/@snapback/core/dist/analysis/static/OrphanDetector.js
2348
+ var DEFAULT_OPTIONS = {
2349
+ fileExtensions: [
2350
+ "ts",
2351
+ "tsx",
2352
+ "js",
2353
+ "jsx"
2354
+ ],
2355
+ excludePatterns: [
2356
+ "node_modules",
2357
+ "dist",
2358
+ ".next",
2359
+ "coverage",
2360
+ "**/*.test.*",
2361
+ "**/*.spec.*",
2362
+ "**/__tests__/**",
2363
+ "**/__mocks__/**"
2364
+ ]
2365
+ };
2366
+ async function detectOrphans(entryPoint, options = {}) {
2367
+ const startTime = Date.now();
2368
+ const mergedOptions = {
2369
+ ...DEFAULT_OPTIONS,
2370
+ ...options
2371
+ };
2372
+ try {
2373
+ const madgeModule = await import('madge');
2374
+ const madge = madgeModule.default || madgeModule;
2375
+ const result = await madge(entryPoint, {
2376
+ fileExtensions: mergedOptions.fileExtensions,
2377
+ excludeRegExp: mergedOptions.excludePatterns.map((p) => {
2378
+ const regexPattern = p.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*").replace(/\./g, "\\.");
2379
+ return new RegExp(regexPattern);
2380
+ }),
2381
+ tsConfig: mergedOptions.tsConfigPath,
2382
+ detectiveOptions: {
2383
+ ts: {
2384
+ skipTypeImports: true
2385
+ }
2386
+ }
2387
+ });
2388
+ const orphans = result.orphans();
2389
+ const allFiles = Object.keys(result.obj());
2390
+ return {
2391
+ orphans,
2392
+ totalFiles: allFiles.length,
2393
+ success: true,
2394
+ duration: Date.now() - startTime
2395
+ };
2396
+ } catch (error) {
2397
+ return {
2398
+ orphans: [],
2399
+ totalFiles: 0,
2400
+ success: false,
2401
+ error: error instanceof Error ? error.message : String(error),
2402
+ duration: Date.now() - startTime
2403
+ };
2404
+ }
2405
+ }
2406
+ __name(detectOrphans, "detectOrphans");
2407
+ function filterOrphansToFiles(orphanResult, targetFiles) {
2408
+ if (!orphanResult.success) {
2409
+ return [];
2410
+ }
2411
+ const targetSet = new Set(targetFiles.map((f) => f.replace(/\\/g, "/")));
2412
+ return orphanResult.orphans.filter((orphan) => {
2413
+ const normalizedOrphan = orphan.replace(/\\/g, "/");
2414
+ return targetSet.has(normalizedOrphan) || targetFiles.some((t) => normalizedOrphan.endsWith(t));
2415
+ });
2416
+ }
2417
+ __name(filterOrphansToFiles, "filterOrphansToFiles");
2418
+ async function checkFilesForOrphanStatus(files, workspaceRoot) {
2419
+ const result = await detectOrphans(workspaceRoot, {
2420
+ baseDir: workspaceRoot
2421
+ });
2422
+ if (!result.success) {
2423
+ return {
2424
+ orphans: [],
2425
+ success: false,
2426
+ error: result.error
2427
+ };
2428
+ }
2429
+ const orphans = filterOrphansToFiles(result, files);
2430
+ return {
2431
+ orphans,
2432
+ success: true
2433
+ };
2434
+ }
2435
+ __name(checkFilesForOrphanStatus, "checkFilesForOrphanStatus");
2436
+
2437
+ // ../../node_modules/@snapback/core/dist/analysis/static/index.js
2438
+ async function runStaticAnalysis(files, _workspaceRoot, options = {}) {
2439
+ const startTime = Date.now();
2440
+ const result = {
2441
+ skippedTests: [],
2442
+ orphanedFiles: [],
2443
+ duration: 0,
2444
+ success: true,
2445
+ errors: []
2446
+ };
2447
+ if (!options.skipTestDetection) {
2448
+ try {
2449
+ const { analyzeSkippedTests: analyzeSkippedTests2 } = await import('./SkippedTestDetector-QLSQV7K7.js');
2450
+ const testResults = analyzeSkippedTests2(files);
2451
+ for (const testResult of testResults) {
2452
+ if (!testResult.parsed && testResult.error) {
2453
+ result.errors.push(`Parse error in ${testResult.file}: ${testResult.error}`);
2454
+ }
2455
+ for (const skipped of testResult.skipped) {
2456
+ result.skippedTests.push({
2457
+ file: skipped.file,
2458
+ type: skipped.type,
2459
+ name: skipped.name,
2460
+ line: skipped.line
2461
+ });
2462
+ }
2463
+ }
2464
+ } catch (error) {
2465
+ result.errors.push(`Skipped test detection failed: ${error instanceof Error ? error.message : String(error)}`);
2466
+ }
2467
+ }
2468
+ if (!options.skipOrphanDetection) ;
2469
+ result.duration = Date.now() - startTime;
2470
+ result.success = result.errors.length === 0;
2471
+ return result;
2472
+ }
2473
+ __name(runStaticAnalysis, "runStaticAnalysis");
2474
+
2475
+ export { ChangeImpactAnalyzer, CompletenessAnalyzer, ComplexityAnalyzer, ImportGraphAnalyzer, SecurityAnalyzer, SyntaxAnalyzer, checkFilesForOrphanStatus, countASTNodes, createChangeImpactAnalyzer, detectOrphans, extractImportSources, extractImports, extractImportsBatch, filterOrphansToFiles, isSupportedFile, offsetToLine, parseSource, runAnalysisPipeline, runStaticAnalysis, walkAST };