depwire-cli 1.1.0 → 1.1.2

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.
@@ -37,8 +37,10 @@ function scanDirectory(rootDir, baseDir = rootDir) {
37
37
  const isJava = entry.endsWith(".java") || entry === "pom.xml" || entry === "build.gradle" || entry === "build.gradle.kts";
38
38
  const isKotlin = entry.endsWith(".kt") || entry.endsWith(".kts") || entry === "settings.gradle.kts" || entry === "settings.gradle";
39
39
  const isPhp = entry.endsWith(".php");
40
+ const isSwift = entry.endsWith(".swift");
41
+ const isMojo = entry.endsWith(".mojo") || entry.endsWith(".\u{1F525}");
40
42
  const isCppBuild = entry === "CMakeLists.txt" || entry === "conanfile.txt" || entry === "vcpkg.json";
41
- if (isTypeScript || isJavaScript || isPython || isGo || isRust || isC || isCpp || isCSharp || isJava || isKotlin || isPhp || isCppBuild) {
43
+ if (isTypeScript || isJavaScript || isPython || isGo || isRust || isC || isCpp || isCSharp || isJava || isKotlin || isPhp || isSwift || isMojo || isCppBuild) {
42
44
  files.push(relative(rootDir, fullPath));
43
45
  }
44
46
  }
@@ -84,6 +86,10 @@ function findProjectRoot(startDir = process.cwd()) {
84
86
  // Kotlin (Gradle KTS)
85
87
  "composer.json",
86
88
  // PHP
89
+ "Package.swift",
90
+ // Swift (SPM)
91
+ "mojoproject.toml",
92
+ // Mojo
87
93
  ".git"
88
94
  // Any git repo
89
95
  ];
@@ -117,11 +123,11 @@ function findProjectRoot(startDir = process.cwd()) {
117
123
  }
118
124
 
119
125
  // src/parser/index.ts
120
- import { readFileSync as readFileSync10, statSync as statSync7 } from "fs";
121
- import { join as join13, resolve as resolve7 } from "path";
126
+ import { readFileSync as readFileSync11, statSync as statSync8 } from "fs";
127
+ import { join as join15, resolve as resolve9 } from "path";
122
128
 
123
129
  // src/parser/detect.ts
124
- import { extname as extname8, basename as basename6 } from "path";
130
+ import { extname as extname10, basename as basename8 } from "path";
125
131
 
126
132
  // src/parser/wasm-init.ts
127
133
  import { Parser, Language } from "web-tree-sitter";
@@ -153,7 +159,9 @@ async function initParser() {
153
159
  "java": "tree-sitter-java.wasm",
154
160
  "cpp": "tree-sitter-cpp.wasm",
155
161
  "kotlin": "tree-sitter-kotlin.wasm",
156
- "php": "tree-sitter-php.wasm"
162
+ "php": "tree-sitter-php.wasm",
163
+ "swift": "tree-sitter-swift.wasm"
164
+ // Note: Mojo uses a pattern-based parser (no tree-sitter-mojo WASM available)
157
165
  };
158
166
  for (const [name, file] of Object.entries(grammarFiles)) {
159
167
  const wasmPath = path.join(grammarsDir, file);
@@ -6231,6 +6239,921 @@ var phpParser = {
6231
6239
  parseFile: parsePhpFile
6232
6240
  };
6233
6241
 
6242
+ // src/parser/swift.ts
6243
+ import { dirname as dirname12, join as join13, basename as basename6 } from "path";
6244
+ import { existsSync as existsSync13, readdirSync as readdirSync10, statSync as statSync7 } from "fs";
6245
+ function parseSwiftFile(filePath, sourceCode, projectRoot) {
6246
+ if (filePath.endsWith("Package.swift")) {
6247
+ return parsePackageSwift(filePath, sourceCode, projectRoot);
6248
+ }
6249
+ const parser = getParser("swift");
6250
+ const tree = parser.parse(sourceCode, null, { bufferSize: 1024 * 1024 });
6251
+ const context = {
6252
+ filePath,
6253
+ projectRoot,
6254
+ sourceCode,
6255
+ symbols: [],
6256
+ edges: [],
6257
+ currentScope: [],
6258
+ currentClass: null,
6259
+ currentModule: null,
6260
+ imports: /* @__PURE__ */ new Map(),
6261
+ isPackageFile: false
6262
+ };
6263
+ walkNode12(tree.rootNode, context);
6264
+ return {
6265
+ filePath,
6266
+ symbols: context.symbols,
6267
+ edges: context.edges
6268
+ };
6269
+ }
6270
+ function walkNode12(node, context) {
6271
+ const handled = processNode12(node, context);
6272
+ if (handled) return;
6273
+ for (let i = 0; i < node.childCount; i++) {
6274
+ const child = node.child(i);
6275
+ if (child) {
6276
+ walkNode12(child, context);
6277
+ }
6278
+ }
6279
+ }
6280
+ function processNode12(node, context) {
6281
+ switch (node.type) {
6282
+ case "import_declaration":
6283
+ processImportDeclaration3(node, context);
6284
+ return false;
6285
+ case "class_declaration":
6286
+ processClassDeclaration7(node, context);
6287
+ return true;
6288
+ // handles its own children
6289
+ case "protocol_declaration":
6290
+ processProtocolDeclaration(node, context);
6291
+ return true;
6292
+ // handles its own children
6293
+ case "function_declaration":
6294
+ processFunctionDeclaration5(node, context);
6295
+ return true;
6296
+ // handles its own children
6297
+ case "init_declaration":
6298
+ processInitDeclaration(node, context);
6299
+ return true;
6300
+ case "deinit_declaration":
6301
+ processDeinitDeclaration(node, context);
6302
+ return true;
6303
+ case "property_declaration":
6304
+ case "variable_declaration":
6305
+ processPropertyDeclaration4(node, context);
6306
+ return false;
6307
+ case "typealias_declaration":
6308
+ processTypealiasDeclaration(node, context);
6309
+ return false;
6310
+ case "associatedtype_declaration":
6311
+ processAssociatedTypeDeclaration(node, context);
6312
+ return false;
6313
+ case "call_expression":
6314
+ processCallExpression12(node, context);
6315
+ return false;
6316
+ default:
6317
+ return false;
6318
+ }
6319
+ }
6320
+ function processImportDeclaration3(node, context) {
6321
+ const text = nodeText11(node, context).trim();
6322
+ const match = text.match(/^import\s+(?:(?:typealias|struct|class|enum|protocol|let|var|func)\s+)?(.+)$/);
6323
+ if (!match) return;
6324
+ const importPath = match[1].trim();
6325
+ const resolvedPath = resolveSwiftImport(importPath, context.filePath, context.projectRoot);
6326
+ if (resolvedPath) {
6327
+ const sourceId = `${context.filePath}::__file__`;
6328
+ const targetId = `${resolvedPath}::__file__`;
6329
+ context.edges.push({
6330
+ source: sourceId,
6331
+ target: targetId,
6332
+ kind: "imports",
6333
+ filePath: context.filePath,
6334
+ line: node.startPosition.row + 1
6335
+ });
6336
+ const parts = importPath.split(".");
6337
+ const simpleName = parts[parts.length - 1];
6338
+ context.imports.set(simpleName, `${resolvedPath}::${simpleName}`);
6339
+ }
6340
+ const symbolId = `${context.filePath}::import:${importPath}`;
6341
+ context.symbols.push({
6342
+ id: symbolId,
6343
+ name: importPath,
6344
+ kind: "import",
6345
+ filePath: context.filePath,
6346
+ startLine: node.startPosition.row + 1,
6347
+ endLine: node.endPosition.row + 1,
6348
+ exported: false
6349
+ });
6350
+ }
6351
+ function processClassDeclaration7(node, context) {
6352
+ let keyword = "class";
6353
+ for (let i = 0; i < node.childCount; i++) {
6354
+ const child = node.child(i);
6355
+ if (child && ["class", "struct", "actor", "enum", "extension"].includes(child.type)) {
6356
+ keyword = child.type;
6357
+ break;
6358
+ }
6359
+ }
6360
+ if (keyword === "extension") {
6361
+ const typeNode = findChildByType12(node, "user_type") || findChildByType12(node, "type_identifier");
6362
+ const extName = typeNode ? nodeText11(typeNode, context).trim() : "Unknown";
6363
+ const name2 = `${extName}+ext`;
6364
+ const symbolId2 = `${context.filePath}::${name2}`;
6365
+ context.symbols.push({
6366
+ id: symbolId2,
6367
+ name: name2,
6368
+ kind: "class",
6369
+ filePath: context.filePath,
6370
+ startLine: node.startPosition.row + 1,
6371
+ endLine: node.endPosition.row + 1,
6372
+ exported: true
6373
+ });
6374
+ const oldClass2 = context.currentClass;
6375
+ context.currentClass = extName;
6376
+ context.currentScope.push(extName);
6377
+ const body2 = findChildByType12(node, "class_body");
6378
+ if (body2) {
6379
+ walkNode12(body2, context);
6380
+ }
6381
+ context.currentScope.pop();
6382
+ context.currentClass = oldClass2;
6383
+ return;
6384
+ }
6385
+ const nameNode = findChildByType12(node, "type_identifier") || findChildByType12(node, "simple_identifier");
6386
+ if (!nameNode) return;
6387
+ const name = nodeText11(nameNode, context);
6388
+ const modifiers = getModifiers3(node, context);
6389
+ const exported = !modifiers.includes("private") && !modifiers.includes("fileprivate");
6390
+ const scope = context.currentClass || void 0;
6391
+ const symbolId = `${context.filePath}::${name}`;
6392
+ let kind = "class";
6393
+ if (keyword === "enum") kind = "enum";
6394
+ else if (keyword === "struct" || keyword === "actor") kind = "class";
6395
+ context.symbols.push({
6396
+ id: symbolId,
6397
+ name,
6398
+ kind,
6399
+ filePath: context.filePath,
6400
+ startLine: node.startPosition.row + 1,
6401
+ endLine: node.endPosition.row + 1,
6402
+ exported,
6403
+ scope
6404
+ });
6405
+ processInheritance(node, symbolId, context);
6406
+ if (keyword === "enum") {
6407
+ processEnumCases(node, name, context);
6408
+ }
6409
+ const oldClass = context.currentClass;
6410
+ context.currentClass = name;
6411
+ context.currentScope.push(name);
6412
+ const body = findChildByType12(node, "class_body");
6413
+ if (body) {
6414
+ walkNode12(body, context);
6415
+ }
6416
+ context.currentScope.pop();
6417
+ context.currentClass = oldClass;
6418
+ }
6419
+ function processProtocolDeclaration(node, context) {
6420
+ const nameNode = findChildByType12(node, "type_identifier") || findChildByType12(node, "simple_identifier");
6421
+ if (!nameNode) return;
6422
+ const name = nodeText11(nameNode, context);
6423
+ const modifiers = getModifiers3(node, context);
6424
+ const exported = !modifiers.includes("private") && !modifiers.includes("fileprivate");
6425
+ const symbolId = `${context.filePath}::${name}`;
6426
+ context.symbols.push({
6427
+ id: symbolId,
6428
+ name,
6429
+ kind: "interface",
6430
+ filePath: context.filePath,
6431
+ startLine: node.startPosition.row + 1,
6432
+ endLine: node.endPosition.row + 1,
6433
+ exported
6434
+ });
6435
+ processInheritance(node, symbolId, context);
6436
+ const oldClass = context.currentClass;
6437
+ context.currentClass = name;
6438
+ context.currentScope.push(name);
6439
+ const body = findChildByType12(node, "protocol_body");
6440
+ if (body) {
6441
+ walkNode12(body, context);
6442
+ }
6443
+ context.currentScope.pop();
6444
+ context.currentClass = oldClass;
6445
+ }
6446
+ function processFunctionDeclaration5(node, context) {
6447
+ const nameNode = findChildByType12(node, "simple_identifier");
6448
+ if (!nameNode) return;
6449
+ const name = nodeText11(nameNode, context);
6450
+ const modifiers = getModifiers3(node, context);
6451
+ const exported = !modifiers.includes("private") && !modifiers.includes("fileprivate");
6452
+ const scope = context.currentClass || void 0;
6453
+ const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
6454
+ context.symbols.push({
6455
+ id: symbolId,
6456
+ name,
6457
+ kind: context.currentClass ? "method" : "function",
6458
+ filePath: context.filePath,
6459
+ startLine: node.startPosition.row + 1,
6460
+ endLine: node.endPosition.row + 1,
6461
+ exported,
6462
+ scope
6463
+ });
6464
+ const scopeName = scope ? `${scope}.${name}` : name;
6465
+ context.currentScope.push(scopeName);
6466
+ const body = findChildByType12(node, "function_body") || findChildByType12(node, "code_block");
6467
+ if (body) {
6468
+ walkNode12(body, context);
6469
+ }
6470
+ context.currentScope.pop();
6471
+ }
6472
+ function processInitDeclaration(node, context) {
6473
+ const scope = context.currentClass || void 0;
6474
+ if (!scope) return;
6475
+ const name = "init";
6476
+ const symbolId = `${context.filePath}::${scope}.${name}:${node.startPosition.row + 1}`;
6477
+ context.symbols.push({
6478
+ id: symbolId,
6479
+ name,
6480
+ kind: "method",
6481
+ filePath: context.filePath,
6482
+ startLine: node.startPosition.row + 1,
6483
+ endLine: node.endPosition.row + 1,
6484
+ exported: true,
6485
+ scope
6486
+ });
6487
+ const scopeName = `${scope}.${name}`;
6488
+ context.currentScope.push(scopeName);
6489
+ const body = findChildByType12(node, "function_body") || findChildByType12(node, "code_block");
6490
+ if (body) {
6491
+ walkNode12(body, context);
6492
+ }
6493
+ context.currentScope.pop();
6494
+ }
6495
+ function processDeinitDeclaration(node, context) {
6496
+ const scope = context.currentClass || void 0;
6497
+ if (!scope) return;
6498
+ const name = "deinit";
6499
+ const symbolId = `${context.filePath}::${scope}.${name}`;
6500
+ context.symbols.push({
6501
+ id: symbolId,
6502
+ name,
6503
+ kind: "method",
6504
+ filePath: context.filePath,
6505
+ startLine: node.startPosition.row + 1,
6506
+ endLine: node.endPosition.row + 1,
6507
+ exported: true,
6508
+ scope
6509
+ });
6510
+ }
6511
+ function processPropertyDeclaration4(node, context) {
6512
+ const nameNode = findChildByType12(node, "simple_identifier") || findChildByType12(node, "pattern");
6513
+ if (!nameNode) return;
6514
+ const name = nodeText11(nameNode, context).trim();
6515
+ if (!name || name.includes(" ")) return;
6516
+ const modifiers = getModifiers3(node, context);
6517
+ const exported = !modifiers.includes("private") && !modifiers.includes("fileprivate");
6518
+ const scope = context.currentClass || void 0;
6519
+ const text = nodeText11(node, context);
6520
+ const isConst = text.trimStart().startsWith("let");
6521
+ const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
6522
+ context.symbols.push({
6523
+ id: symbolId,
6524
+ name,
6525
+ kind: isConst ? "constant" : "property",
6526
+ filePath: context.filePath,
6527
+ startLine: node.startPosition.row + 1,
6528
+ endLine: node.endPosition.row + 1,
6529
+ exported,
6530
+ scope
6531
+ });
6532
+ }
6533
+ function processTypealiasDeclaration(node, context) {
6534
+ const nameNode = findChildByType12(node, "type_identifier") || findChildByType12(node, "simple_identifier");
6535
+ if (!nameNode) return;
6536
+ const name = nodeText11(nameNode, context);
6537
+ const symbolId = `${context.filePath}::${name}`;
6538
+ context.symbols.push({
6539
+ id: symbolId,
6540
+ name,
6541
+ kind: "type_alias",
6542
+ filePath: context.filePath,
6543
+ startLine: node.startPosition.row + 1,
6544
+ endLine: node.endPosition.row + 1,
6545
+ exported: true
6546
+ });
6547
+ }
6548
+ function processAssociatedTypeDeclaration(node, context) {
6549
+ const nameNode = findChildByType12(node, "type_identifier") || findChildByType12(node, "simple_identifier");
6550
+ if (!nameNode) return;
6551
+ const name = nodeText11(nameNode, context);
6552
+ const scope = context.currentClass || void 0;
6553
+ const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
6554
+ context.symbols.push({
6555
+ id: symbolId,
6556
+ name,
6557
+ kind: "type_alias",
6558
+ filePath: context.filePath,
6559
+ startLine: node.startPosition.row + 1,
6560
+ endLine: node.endPosition.row + 1,
6561
+ exported: true,
6562
+ scope
6563
+ });
6564
+ }
6565
+ function processCallExpression12(node, context) {
6566
+ if (context.currentScope.length === 0) return;
6567
+ const firstChild = node.child(0);
6568
+ if (!firstChild) return;
6569
+ let calleeName = null;
6570
+ if (firstChild.type === "simple_identifier") {
6571
+ calleeName = nodeText11(firstChild, context);
6572
+ } else if (firstChild.type === "navigation_expression" || firstChild.type === "member_access") {
6573
+ for (let i = firstChild.childCount - 1; i >= 0; i--) {
6574
+ const child = firstChild.child(i);
6575
+ if (child && (child.type === "simple_identifier" || child.type === "navigation_suffix")) {
6576
+ calleeName = nodeText11(child, context).replace(/^\./, "");
6577
+ break;
6578
+ }
6579
+ }
6580
+ }
6581
+ if (!calleeName) return;
6582
+ const builtins = /* @__PURE__ */ new Set([
6583
+ "print",
6584
+ "debugPrint",
6585
+ "dump",
6586
+ "fatalError",
6587
+ "precondition",
6588
+ "assert",
6589
+ "preconditionFailure",
6590
+ "assertionFailure",
6591
+ "map",
6592
+ "filter",
6593
+ "reduce",
6594
+ "forEach",
6595
+ "flatMap",
6596
+ "compactMap",
6597
+ "sorted",
6598
+ "contains",
6599
+ "first",
6600
+ "last",
6601
+ "count",
6602
+ "isEmpty",
6603
+ "append",
6604
+ "remove",
6605
+ "insert",
6606
+ "removeAll",
6607
+ "String",
6608
+ "Int",
6609
+ "Double",
6610
+ "Float",
6611
+ "Bool",
6612
+ "Array",
6613
+ "Dictionary",
6614
+ "Set",
6615
+ "DispatchQueue",
6616
+ "Task",
6617
+ "withCheckedContinuation",
6618
+ "withCheckedThrowingContinuation"
6619
+ ]);
6620
+ if (builtins.has(calleeName)) return;
6621
+ const callerId = getCurrentSymbolId12(context);
6622
+ if (!callerId) return;
6623
+ const calleeId = resolveSymbol11(calleeName, context);
6624
+ if (calleeId) {
6625
+ context.edges.push({
6626
+ source: callerId,
6627
+ target: calleeId,
6628
+ kind: "calls",
6629
+ filePath: context.filePath,
6630
+ line: node.startPosition.row + 1
6631
+ });
6632
+ }
6633
+ }
6634
+ function processInheritance(node, sourceId, context) {
6635
+ const inheritanceClause = findChildByType12(node, "inheritance_specifier") || findChildByType12(node, "type_inheritance_clause");
6636
+ if (!inheritanceClause) return;
6637
+ const text = nodeText11(node, context);
6638
+ const colonMatch = text.match(/:\s*([^{]+)/);
6639
+ if (!colonMatch) return;
6640
+ const types = colonMatch[1].split(",").map((t) => t.trim().split("<")[0].trim());
6641
+ for (const typeName of types) {
6642
+ if (!typeName || typeName.includes("{") || typeName.includes("where")) break;
6643
+ const baseId = resolveSymbol11(typeName, context);
6644
+ if (baseId) {
6645
+ context.edges.push({
6646
+ source: sourceId,
6647
+ target: baseId,
6648
+ kind: "implements",
6649
+ filePath: context.filePath,
6650
+ line: node.startPosition.row + 1
6651
+ });
6652
+ }
6653
+ }
6654
+ }
6655
+ function processEnumCases(node, enumName, context) {
6656
+ const text = nodeText11(node, context);
6657
+ const caseMatches = text.matchAll(/\bcase\s+(\w+)/g);
6658
+ for (const match of caseMatches) {
6659
+ const caseName = match[1];
6660
+ if (caseName === enumName) continue;
6661
+ const constId = `${context.filePath}::${enumName}.${caseName}`;
6662
+ context.symbols.push({
6663
+ id: constId,
6664
+ name: caseName,
6665
+ kind: "constant",
6666
+ filePath: context.filePath,
6667
+ startLine: node.startPosition.row + 1,
6668
+ endLine: node.endPosition.row + 1,
6669
+ exported: true,
6670
+ scope: enumName
6671
+ });
6672
+ }
6673
+ }
6674
+ function parsePackageSwift(filePath, sourceCode, projectRoot) {
6675
+ const symbols = [];
6676
+ const edges = [];
6677
+ const lines = sourceCode.split("\n");
6678
+ const nameMatch = sourceCode.match(/name\s*:\s*["']([^"']+)["']/);
6679
+ const packageName = nameMatch ? nameMatch[1] : basename6(dirname12(join13(projectRoot, filePath)));
6680
+ symbols.push({
6681
+ id: `${filePath}::${packageName}`,
6682
+ name: packageName,
6683
+ kind: "module",
6684
+ filePath,
6685
+ startLine: 1,
6686
+ endLine: lines.length,
6687
+ exported: true
6688
+ });
6689
+ for (let i = 0; i < lines.length; i++) {
6690
+ const line = lines[i].trim();
6691
+ const lineNum = i + 1;
6692
+ const depMatch = line.match(/\.package\s*\(\s*(?:url\s*:\s*)?["']([^"']+)["']/);
6693
+ if (depMatch) {
6694
+ const depUrl = depMatch[1];
6695
+ const depName = depUrl.split("/").pop()?.replace(/\.git$/, "") || depUrl;
6696
+ symbols.push({
6697
+ id: `${filePath}::dep:${depName}`,
6698
+ name: depName,
6699
+ kind: "import",
6700
+ filePath,
6701
+ startLine: lineNum,
6702
+ endLine: lineNum,
6703
+ exported: false
6704
+ });
6705
+ }
6706
+ const targetMatch = line.match(/\.(?:target|executableTarget|testTarget)\s*\(\s*name\s*:\s*["']([^"']+)["']/);
6707
+ if (targetMatch) {
6708
+ symbols.push({
6709
+ id: `${filePath}::target:${targetMatch[1]}`,
6710
+ name: targetMatch[1],
6711
+ kind: "module",
6712
+ filePath,
6713
+ startLine: lineNum,
6714
+ endLine: lineNum,
6715
+ exported: true
6716
+ });
6717
+ }
6718
+ }
6719
+ return { filePath, symbols, edges };
6720
+ }
6721
+ function resolveSwiftImport(importPath, currentFile, projectRoot) {
6722
+ const parts = importPath.split(".");
6723
+ const moduleName = parts[0];
6724
+ const sourceRoots = [
6725
+ "",
6726
+ "Sources",
6727
+ `Sources/${moduleName}`,
6728
+ "src",
6729
+ `src/${moduleName}`
6730
+ ];
6731
+ for (const root of sourceRoots) {
6732
+ const candidate = root ? join13(root, moduleName + ".swift") : moduleName + ".swift";
6733
+ const fullPath = join13(projectRoot, candidate);
6734
+ if (existsSync13(fullPath)) {
6735
+ return candidate;
6736
+ }
6737
+ }
6738
+ for (const root of sourceRoots) {
6739
+ const dirCandidate = root || moduleName;
6740
+ const fullDir = join13(projectRoot, dirCandidate);
6741
+ if (existsSync13(fullDir)) {
6742
+ try {
6743
+ const stats = statSync7(fullDir);
6744
+ if (stats.isDirectory()) {
6745
+ const swiftFiles = readdirSync10(fullDir).filter((f) => f.endsWith(".swift"));
6746
+ if (swiftFiles.length > 0) {
6747
+ return join13(dirCandidate, swiftFiles[0]);
6748
+ }
6749
+ }
6750
+ } catch {
6751
+ }
6752
+ }
6753
+ }
6754
+ return null;
6755
+ }
6756
+ function resolveSymbol11(name, context) {
6757
+ if (context.imports.has(name)) {
6758
+ return context.imports.get(name) || null;
6759
+ }
6760
+ const currentFileId = `${context.filePath}::${name}`;
6761
+ if (context.symbols.find((s) => s.id === currentFileId)) {
6762
+ return currentFileId;
6763
+ }
6764
+ if (context.currentClass) {
6765
+ const classMethodId = `${context.filePath}::${context.currentClass}.${name}`;
6766
+ if (context.symbols.find((s) => s.id === classMethodId)) {
6767
+ return classMethodId;
6768
+ }
6769
+ }
6770
+ return null;
6771
+ }
6772
+ function getModifiers3(node, context) {
6773
+ const modifiers = [];
6774
+ const modList = findChildByType12(node, "modifiers") || findChildByType12(node, "modifier");
6775
+ if (modList) {
6776
+ for (let i = 0; i < modList.childCount; i++) {
6777
+ const child = modList.child(i);
6778
+ if (child) {
6779
+ const text2 = nodeText11(child, context).trim();
6780
+ if (text2) modifiers.push(text2);
6781
+ }
6782
+ }
6783
+ }
6784
+ const text = nodeText11(node, context);
6785
+ if (text.match(/\bprivate\b/)) modifiers.push("private");
6786
+ if (text.match(/\bfileprivate\b/)) modifiers.push("fileprivate");
6787
+ if (text.match(/\binternal\b/)) modifiers.push("internal");
6788
+ if (text.match(/\bpublic\b/)) modifiers.push("public");
6789
+ if (text.match(/\bopen\b/)) modifiers.push("open");
6790
+ return modifiers;
6791
+ }
6792
+ function findChildByType12(node, type) {
6793
+ for (let i = 0; i < node.childCount; i++) {
6794
+ const child = node.child(i);
6795
+ if (child && child.type === type) return child;
6796
+ }
6797
+ return null;
6798
+ }
6799
+ function nodeText11(node, context) {
6800
+ return context.sourceCode.substring(node.startIndex, node.endIndex);
6801
+ }
6802
+ function getCurrentSymbolId12(context) {
6803
+ if (context.currentScope.length === 0) return null;
6804
+ return `${context.filePath}::${context.currentScope[context.currentScope.length - 1]}`;
6805
+ }
6806
+ var swiftParser = {
6807
+ name: "swift",
6808
+ extensions: [".swift", "Package.swift"],
6809
+ parseFile: parseSwiftFile
6810
+ };
6811
+
6812
+ // src/parser/mojo.ts
6813
+ import { dirname as dirname13, join as join14, basename as basename7 } from "path";
6814
+ function parseMojoFile(filePath, sourceCode, projectRoot) {
6815
+ if (filePath.endsWith("mojoproject.toml")) {
6816
+ return parseMojoProject(filePath, sourceCode, projectRoot);
6817
+ }
6818
+ const context = {
6819
+ filePath,
6820
+ projectRoot,
6821
+ sourceCode,
6822
+ symbols: [],
6823
+ edges: [],
6824
+ currentScope: [],
6825
+ currentClass: null,
6826
+ imports: /* @__PURE__ */ new Map()
6827
+ };
6828
+ const lines = sourceCode.split("\n");
6829
+ parseLines(lines, context);
6830
+ return {
6831
+ filePath,
6832
+ symbols: context.symbols,
6833
+ edges: context.edges
6834
+ };
6835
+ }
6836
+ function parseLines(lines, context) {
6837
+ let i = 0;
6838
+ while (i < lines.length) {
6839
+ const line = lines[i];
6840
+ const trimmed = line.trimStart();
6841
+ const indent = line.length - trimmed.length;
6842
+ const lineNum = i + 1;
6843
+ if (!trimmed || trimmed.startsWith("#")) {
6844
+ i++;
6845
+ continue;
6846
+ }
6847
+ if (trimmed.startsWith("@")) {
6848
+ i++;
6849
+ continue;
6850
+ }
6851
+ if (trimmed.startsWith("import ") || trimmed.startsWith("from ")) {
6852
+ processImport(trimmed, lineNum, context);
6853
+ i++;
6854
+ continue;
6855
+ }
6856
+ const fnMatch = trimmed.match(/^fn\s+(\w+)\s*[\[(]/);
6857
+ if (fnMatch) {
6858
+ const name = fnMatch[1];
6859
+ const endLine = findBlockEnd(lines, i, indent);
6860
+ addFunction(name, lineNum, endLine, context);
6861
+ i = endLine;
6862
+ continue;
6863
+ }
6864
+ const defMatch = trimmed.match(/^def\s+(\w+)\s*[\[(]/);
6865
+ if (defMatch) {
6866
+ const name = defMatch[1];
6867
+ const endLine = findBlockEnd(lines, i, indent);
6868
+ addFunction(name, lineNum, endLine, context);
6869
+ i = endLine;
6870
+ continue;
6871
+ }
6872
+ const structMatch = trimmed.match(/^struct\s+(\w+)/);
6873
+ if (structMatch) {
6874
+ const name = structMatch[1];
6875
+ const endLine = findBlockEnd(lines, i, indent);
6876
+ addType(name, "class", lineNum, endLine, context);
6877
+ parseStructBody(lines, i + 1, endLine, indent, name, context);
6878
+ i = endLine;
6879
+ continue;
6880
+ }
6881
+ const classMatch = trimmed.match(/^class\s+(\w+)/);
6882
+ if (classMatch) {
6883
+ const name = classMatch[1];
6884
+ const endLine = findBlockEnd(lines, i, indent);
6885
+ addType(name, "class", lineNum, endLine, context);
6886
+ parseStructBody(lines, i + 1, endLine, indent, name, context);
6887
+ i = endLine;
6888
+ continue;
6889
+ }
6890
+ const traitMatch = trimmed.match(/^trait\s+(\w+)/);
6891
+ if (traitMatch) {
6892
+ const name = traitMatch[1];
6893
+ const endLine = findBlockEnd(lines, i, indent);
6894
+ addType(name, "interface", lineNum, endLine, context);
6895
+ parseStructBody(lines, i + 1, endLine, indent, name, context);
6896
+ i = endLine;
6897
+ continue;
6898
+ }
6899
+ const aliasMatch = trimmed.match(/^alias\s+(\w+)\s*[=:]/);
6900
+ if (aliasMatch) {
6901
+ const name = aliasMatch[1];
6902
+ context.symbols.push({
6903
+ id: `${context.filePath}::${name}`,
6904
+ name,
6905
+ kind: "type_alias",
6906
+ filePath: context.filePath,
6907
+ startLine: lineNum,
6908
+ endLine: lineNum,
6909
+ exported: true
6910
+ });
6911
+ i++;
6912
+ continue;
6913
+ }
6914
+ const varMatch = trimmed.match(/^(var|let)\s+(\w+)/);
6915
+ if (varMatch && indent === 0) {
6916
+ const name = varMatch[2];
6917
+ const kind = varMatch[1] === "let" ? "constant" : "var";
6918
+ context.symbols.push({
6919
+ id: `${context.filePath}::${name}`,
6920
+ name,
6921
+ kind,
6922
+ filePath: context.filePath,
6923
+ startLine: lineNum,
6924
+ endLine: lineNum,
6925
+ exported: true
6926
+ });
6927
+ i++;
6928
+ continue;
6929
+ }
6930
+ processCallsInLine(trimmed, lineNum, context);
6931
+ i++;
6932
+ }
6933
+ }
6934
+ function processImport(line, lineNum, context) {
6935
+ let importName = null;
6936
+ const fromMatch = line.match(/^from\s+([\w.]+)\s+import\s+(.+)/);
6937
+ if (fromMatch) {
6938
+ const module = fromMatch[1];
6939
+ const symbols = fromMatch[2].split(",").map((s) => s.trim());
6940
+ importName = module;
6941
+ for (const sym of symbols) {
6942
+ const cleanSym = sym.split(" as ")[0].trim();
6943
+ if (cleanSym && cleanSym !== "*") {
6944
+ context.imports.set(cleanSym, `${module}::${cleanSym}`);
6945
+ }
6946
+ }
6947
+ } else {
6948
+ const importMatch = line.match(/^import\s+([\w.]+)(?:\s+as\s+(\w+))?/);
6949
+ if (importMatch) {
6950
+ importName = importMatch[1];
6951
+ const alias = importMatch[2] || importMatch[1].split(".").pop();
6952
+ context.imports.set(alias, `${importMatch[1]}::__module__`);
6953
+ }
6954
+ }
6955
+ if (importName) {
6956
+ context.symbols.push({
6957
+ id: `${context.filePath}::import:${importName}`,
6958
+ name: importName,
6959
+ kind: "import",
6960
+ filePath: context.filePath,
6961
+ startLine: lineNum,
6962
+ endLine: lineNum,
6963
+ exported: false
6964
+ });
6965
+ }
6966
+ }
6967
+ function addFunction(name, startLine, endLine, context) {
6968
+ const scope = context.currentClass || void 0;
6969
+ const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
6970
+ context.symbols.push({
6971
+ id: symbolId,
6972
+ name,
6973
+ kind: context.currentClass ? "method" : "function",
6974
+ filePath: context.filePath,
6975
+ startLine,
6976
+ endLine,
6977
+ exported: true,
6978
+ scope
6979
+ });
6980
+ }
6981
+ function addType(name, kind, startLine, endLine, context) {
6982
+ context.symbols.push({
6983
+ id: `${context.filePath}::${name}`,
6984
+ name,
6985
+ kind,
6986
+ filePath: context.filePath,
6987
+ startLine,
6988
+ endLine,
6989
+ exported: true
6990
+ });
6991
+ }
6992
+ function parseStructBody(lines, start, end, baseIndent, className, context) {
6993
+ const oldClass = context.currentClass;
6994
+ context.currentClass = className;
6995
+ let i = start;
6996
+ while (i < end && i < lines.length) {
6997
+ const line = lines[i];
6998
+ const trimmed = line.trimStart();
6999
+ const indent = line.length - trimmed.length;
7000
+ const lineNum = i + 1;
7001
+ if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("@")) {
7002
+ i++;
7003
+ continue;
7004
+ }
7005
+ if (indent <= baseIndent) break;
7006
+ const fnMatch = trimmed.match(/^(fn|def)\s+(\w+)\s*[\[(]/);
7007
+ if (fnMatch) {
7008
+ const name = fnMatch[2];
7009
+ const fnEnd = findBlockEnd(lines, i, indent);
7010
+ addFunction(name, lineNum, fnEnd, context);
7011
+ i = fnEnd;
7012
+ continue;
7013
+ }
7014
+ const varMatch = trimmed.match(/^(var|let)\s+(\w+)/);
7015
+ if (varMatch) {
7016
+ const name = varMatch[2];
7017
+ context.symbols.push({
7018
+ id: `${context.filePath}::${className}.${name}`,
7019
+ name,
7020
+ kind: "property",
7021
+ filePath: context.filePath,
7022
+ startLine: lineNum,
7023
+ endLine: lineNum,
7024
+ exported: true,
7025
+ scope: className
7026
+ });
7027
+ i++;
7028
+ continue;
7029
+ }
7030
+ const aliasMatch = trimmed.match(/^alias\s+(\w+)\s*[=:]/);
7031
+ if (aliasMatch) {
7032
+ const name = aliasMatch[1];
7033
+ context.symbols.push({
7034
+ id: `${context.filePath}::${className}.${name}`,
7035
+ name,
7036
+ kind: "type_alias",
7037
+ filePath: context.filePath,
7038
+ startLine: lineNum,
7039
+ endLine: lineNum,
7040
+ exported: true,
7041
+ scope: className
7042
+ });
7043
+ i++;
7044
+ continue;
7045
+ }
7046
+ i++;
7047
+ }
7048
+ context.currentClass = oldClass;
7049
+ }
7050
+ function processCallsInLine(line, lineNum, context) {
7051
+ const callRegex = /\b(\w+)\s*(?:\[[^\]]*\]\s*)?\(/g;
7052
+ let match;
7053
+ const builtins = /* @__PURE__ */ new Set([
7054
+ "print",
7055
+ "len",
7056
+ "range",
7057
+ "int",
7058
+ "str",
7059
+ "float",
7060
+ "bool",
7061
+ "type",
7062
+ "if",
7063
+ "elif",
7064
+ "while",
7065
+ "for",
7066
+ "return",
7067
+ "raise",
7068
+ "assert",
7069
+ "fn",
7070
+ "def",
7071
+ "struct",
7072
+ "class",
7073
+ "trait",
7074
+ "alias",
7075
+ "var",
7076
+ "let",
7077
+ "from",
7078
+ "import",
7079
+ "inout",
7080
+ "owned",
7081
+ "borrowed"
7082
+ ]);
7083
+ while ((match = callRegex.exec(line)) !== null) {
7084
+ const name = match[1];
7085
+ if (builtins.has(name)) continue;
7086
+ if (name.startsWith("_") && name !== "__init__") continue;
7087
+ if (context.currentScope.length > 0 || context.currentClass) {
7088
+ const callerId = context.currentClass ? `${context.filePath}::${context.currentClass}` : `${context.filePath}::__file__`;
7089
+ const targetId = context.imports.get(name) || `${context.filePath}::${name}`;
7090
+ context.edges.push({
7091
+ source: callerId,
7092
+ target: targetId,
7093
+ kind: "calls",
7094
+ filePath: context.filePath,
7095
+ line: lineNum
7096
+ });
7097
+ }
7098
+ }
7099
+ }
7100
+ function findBlockEnd(lines, startIdx, baseIndent) {
7101
+ let i = startIdx + 1;
7102
+ while (i < lines.length) {
7103
+ const line = lines[i];
7104
+ if (line.trim() === "") {
7105
+ i++;
7106
+ continue;
7107
+ }
7108
+ const indent = line.length - line.trimStart().length;
7109
+ if (indent <= baseIndent) {
7110
+ return i;
7111
+ }
7112
+ i++;
7113
+ }
7114
+ return i;
7115
+ }
7116
+ function parseMojoProject(filePath, sourceCode, projectRoot) {
7117
+ const symbols = [];
7118
+ const edges = [];
7119
+ const lines = sourceCode.split("\n");
7120
+ let projectName = basename7(dirname13(join14(projectRoot, filePath)));
7121
+ const nameMatch = sourceCode.match(/name\s*=\s*["']([^"']+)["']/);
7122
+ if (nameMatch) {
7123
+ projectName = nameMatch[1];
7124
+ }
7125
+ symbols.push({
7126
+ id: `${filePath}::${projectName}`,
7127
+ name: projectName,
7128
+ kind: "module",
7129
+ filePath,
7130
+ startLine: 1,
7131
+ endLine: lines.length,
7132
+ exported: true
7133
+ });
7134
+ for (let i = 0; i < lines.length; i++) {
7135
+ const line = lines[i].trim();
7136
+ const depMatch = line.match(/^(\w[\w-]*)\s*=\s*["']([^"']+)["']/);
7137
+ if (depMatch) {
7138
+ symbols.push({
7139
+ id: `${filePath}::dep:${depMatch[1]}`,
7140
+ name: depMatch[1],
7141
+ kind: "import",
7142
+ filePath,
7143
+ startLine: i + 1,
7144
+ endLine: i + 1,
7145
+ exported: false
7146
+ });
7147
+ }
7148
+ }
7149
+ return { filePath, symbols, edges };
7150
+ }
7151
+ var mojoParser = {
7152
+ name: "mojo",
7153
+ extensions: [".mojo", ".\u{1F525}", "mojoproject.toml"],
7154
+ parseFile: parseMojoFile
7155
+ };
7156
+
6234
7157
  // src/parser/detect.ts
6235
7158
  var parsers = [
6236
7159
  typescriptParser,
@@ -6243,12 +7166,14 @@ var parsers = [
6243
7166
  javaParser,
6244
7167
  cppParser,
6245
7168
  kotlinParser,
6246
- phpParser
7169
+ phpParser,
7170
+ swiftParser,
7171
+ mojoParser
6247
7172
  ];
6248
7173
  var CPP_KEYWORDS = /\b(?:class|namespace|template|public:|private:|protected:|virtual|nullptr|constexpr|auto\s+\w+\s*=|using\s+\w+\s*=|static_cast|dynamic_cast|reinterpret_cast|const_cast|noexcept|override|final|decltype|concept|requires|co_await|co_yield|co_return|std::)\b/;
6249
7174
  function getParserForFile(filePath, content) {
6250
- const ext = extname8(filePath).toLowerCase();
6251
- const fileName = basename6(filePath);
7175
+ const ext = extname10(filePath).toLowerCase();
7176
+ const fileName = basename8(filePath);
6252
7177
  if (ext === ".h" && content) {
6253
7178
  if (CPP_KEYWORDS.test(content)) {
6254
7179
  return cppParser;
@@ -6266,7 +7191,7 @@ import { minimatch } from "minimatch";
6266
7191
  var MAX_FILE_SIZE = 1e6;
6267
7192
  function shouldParseFile(fullPath) {
6268
7193
  try {
6269
- const stats = statSync7(fullPath);
7194
+ const stats = statSync8(fullPath);
6270
7195
  if (stats.size > MAX_FILE_SIZE) {
6271
7196
  console.error(`[Parser] Skipping ${fullPath} \u2014 file too large (${(stats.size / 1024).toFixed(0)}KB)`);
6272
7197
  return false;
@@ -6284,8 +7209,8 @@ async function parseProject(projectRoot, options) {
6284
7209
  let errorFiles = 0;
6285
7210
  for (const file of files) {
6286
7211
  try {
6287
- const fullPath = join13(projectRoot, file);
6288
- if (!resolve7(fullPath).startsWith(resolve7(projectRoot))) {
7212
+ const fullPath = join15(projectRoot, file);
7213
+ if (!resolve9(fullPath).startsWith(resolve9(projectRoot))) {
6289
7214
  skippedFiles++;
6290
7215
  continue;
6291
7216
  }
@@ -6308,7 +7233,7 @@ async function parseProject(projectRoot, options) {
6308
7233
  if (options?.verbose) {
6309
7234
  console.error(`[Parser] Parsing: ${file}`);
6310
7235
  }
6311
- const sourceCode = readFileSync10(fullPath, "utf-8");
7236
+ const sourceCode = readFileSync11(fullPath, "utf-8");
6312
7237
  const parser = getParserForFile(file, sourceCode);
6313
7238
  if (!parser) {
6314
7239
  console.error(`No parser found for file: ${file}`);
@@ -6337,8 +7262,8 @@ async function parseProject(projectRoot, options) {
6337
7262
  }
6338
7263
 
6339
7264
  // src/cross-language/detectors/rest-api.ts
6340
- import { readFileSync as readFileSync11 } from "fs";
6341
- import { join as join14, resolve as resolve8 } from "path";
7265
+ import { readFileSync as readFileSync12 } from "fs";
7266
+ import { join as join16, resolve as resolve10 } from "path";
6342
7267
  function getLanguage(filePath) {
6343
7268
  if (filePath.endsWith(".ts") || filePath.endsWith(".tsx")) return "typescript";
6344
7269
  if (filePath.endsWith(".js") || filePath.endsWith(".jsx") || filePath.endsWith(".mjs") || filePath.endsWith(".cjs")) return "javascript";
@@ -6348,6 +7273,8 @@ function getLanguage(filePath) {
6348
7273
  if (filePath.endsWith(".java")) return "java";
6349
7274
  if (filePath.endsWith(".kt") || filePath.endsWith(".kts")) return "kotlin";
6350
7275
  if (filePath.endsWith(".php")) return "php";
7276
+ if (filePath.endsWith(".swift")) return "swift";
7277
+ if (filePath.endsWith(".mojo") || filePath.endsWith(".\u{1F525}")) return "mojo";
6351
7278
  if (filePath.endsWith(".cpp") || filePath.endsWith(".cc") || filePath.endsWith(".cxx") || filePath.endsWith(".c++") || filePath.endsWith(".hpp") || filePath.endsWith(".hh") || filePath.endsWith(".hxx") || filePath.endsWith(".h++") || filePath.endsWith(".h") || filePath.endsWith(".inl") || filePath.endsWith(".ipp")) return "cpp";
6352
7279
  return "unknown";
6353
7280
  }
@@ -6767,6 +7694,73 @@ function extractRouteDefinitions(source, filePath) {
6767
7694
  });
6768
7695
  }
6769
7696
  }
7697
+ if (lang === "swift") {
7698
+ const vaporMatch = line.match(/(?:app|router|routes)\s*\.\s*(get|post|put|delete|patch)\s*\(\s*["']([^"']+)["']/i);
7699
+ if (vaporMatch) {
7700
+ let path6 = vaporMatch[2];
7701
+ if (!path6.startsWith("/")) path6 = "/" + path6;
7702
+ routes.push({
7703
+ method: vaporMatch[1].toUpperCase(),
7704
+ path: path6,
7705
+ normalizedPath: normalizePath(path6),
7706
+ file: filePath,
7707
+ line: i + 1
7708
+ });
7709
+ }
7710
+ const hbMatch = line.match(/router\s*\.\s*(get|post|put|delete|patch)\s*\(\s*["']([^"']+)["']/i);
7711
+ if (hbMatch && !vaporMatch) {
7712
+ let path6 = hbMatch[2];
7713
+ if (!path6.startsWith("/")) path6 = "/" + path6;
7714
+ routes.push({
7715
+ method: hbMatch[1].toUpperCase(),
7716
+ path: path6,
7717
+ normalizedPath: normalizePath(path6),
7718
+ file: filePath,
7719
+ line: i + 1
7720
+ });
7721
+ }
7722
+ const perfectMatch = line.match(/routes\s*\.\s*add\s*\([^)]*uri\s*:\s*["']([^"']+)["']/);
7723
+ if (perfectMatch) {
7724
+ const path6 = perfectMatch[1].startsWith("/") ? perfectMatch[1] : "/" + perfectMatch[1];
7725
+ const methodMatch = line.match(/method\s*:\s*\.(\w+)/);
7726
+ const method = methodMatch ? methodMatch[1].toUpperCase() : "ANY";
7727
+ routes.push({
7728
+ method,
7729
+ path: path6,
7730
+ normalizedPath: normalizePath(path6),
7731
+ file: filePath,
7732
+ line: i + 1
7733
+ });
7734
+ }
7735
+ }
7736
+ if (lang === "mojo") {
7737
+ const pythonMatch = line.match(/@(?:app|router)\s*\.\s*(get|post|put|delete|patch)\s*\(\s*(['"])([^'"]+)\2/i);
7738
+ if (pythonMatch) {
7739
+ const path6 = pythonMatch[3];
7740
+ if (path6.startsWith("/")) {
7741
+ routes.push({
7742
+ method: pythonMatch[1].toUpperCase(),
7743
+ path: path6,
7744
+ normalizedPath: normalizePath(path6),
7745
+ file: filePath,
7746
+ line: i + 1
7747
+ });
7748
+ }
7749
+ }
7750
+ const mojoHttpMatch = line.match(/(?:server|app)\s*\.\s*(?:route|handle)\s*\(\s*['"]([^'"]+)['"]\s*,\s*['"]?(GET|POST|PUT|DELETE|PATCH)['"]?/i);
7751
+ if (mojoHttpMatch) {
7752
+ const path6 = mojoHttpMatch[1];
7753
+ if (path6.startsWith("/")) {
7754
+ routes.push({
7755
+ method: mojoHttpMatch[2].toUpperCase(),
7756
+ path: path6,
7757
+ normalizedPath: normalizePath(path6),
7758
+ file: filePath,
7759
+ line: i + 1
7760
+ });
7761
+ }
7762
+ }
7763
+ }
6770
7764
  if (lang === "cpp") {
6771
7765
  const crowMatch = line.match(/CROW_ROUTE\s*\(\s*\w+\s*,\s*"([^"]+)"/);
6772
7766
  if (crowMatch) {
@@ -6881,11 +7875,11 @@ function detectRestApiEdges(files, projectRoot) {
6881
7875
  const allCalls = [];
6882
7876
  const allRoutes = [];
6883
7877
  for (const file of files) {
6884
- const fullPath = join14(projectRoot, file.filePath);
6885
- if (!resolve8(fullPath).startsWith(resolve8(projectRoot))) continue;
7878
+ const fullPath = join16(projectRoot, file.filePath);
7879
+ if (!resolve10(fullPath).startsWith(resolve10(projectRoot))) continue;
6886
7880
  let source;
6887
7881
  try {
6888
- source = readFileSync11(fullPath, "utf-8");
7882
+ source = readFileSync12(fullPath, "utf-8");
6889
7883
  } catch {
6890
7884
  continue;
6891
7885
  }
@@ -6905,6 +7899,30 @@ function detectRestApiEdges(files, projectRoot) {
6905
7899
  }
6906
7900
  }
6907
7901
  }
7902
+ if (lang === "swift") {
7903
+ const swiftLines = source.split("\n");
7904
+ for (let i = 0; i < swiftLines.length; i++) {
7905
+ const line = swiftLines[i];
7906
+ const urlMatch = line.match(/URL\s*\(\s*string\s*:\s*["']([^"']+)["']/);
7907
+ if (urlMatch) {
7908
+ const path6 = urlMatch[1];
7909
+ if (isLocalApiPath(path6)) {
7910
+ const methodMatch = line.match(/httpMethod\s*=\s*["'](\w+)["']/);
7911
+ const method = methodMatch ? methodMatch[1].toUpperCase() : "GET";
7912
+ allCalls.push({ method, path: cleanPath(path6), file: file.filePath, line: i + 1 });
7913
+ }
7914
+ }
7915
+ const afMatch = line.match(/AF\s*\.\s*(?:request|upload|download)\s*\(\s*["']([^"']+)["']/);
7916
+ if (afMatch) {
7917
+ const path6 = afMatch[1];
7918
+ if (isLocalApiPath(path6)) {
7919
+ const methodMatch = line.match(/method\s*:\s*\.(\w+)/);
7920
+ const method = methodMatch ? methodMatch[1].toUpperCase() : "GET";
7921
+ allCalls.push({ method, path: cleanPath(path6), file: file.filePath, line: i + 1 });
7922
+ }
7923
+ }
7924
+ }
7925
+ }
6908
7926
  if (lang === "php") {
6909
7927
  const phpLines = source.split("\n");
6910
7928
  for (let i = 0; i < phpLines.length; i++) {
@@ -6961,8 +7979,8 @@ function detectRestApiEdges(files, projectRoot) {
6961
7979
  }
6962
7980
 
6963
7981
  // src/cross-language/detectors/subprocess.ts
6964
- import { readFileSync as readFileSync12 } from "fs";
6965
- import { join as join15, resolve as resolve9, basename as basename7 } from "path";
7982
+ import { readFileSync as readFileSync13 } from "fs";
7983
+ import { join as join17, resolve as resolve11, basename as basename9 } from "path";
6966
7984
  var SCRIPT_EXTENSIONS = [".py", ".js", ".ts", ".go", ".rs"];
6967
7985
  function getLanguage2(filePath) {
6968
7986
  if (filePath.endsWith(".ts") || filePath.endsWith(".tsx")) return "typescript";
@@ -7061,16 +8079,16 @@ function detectSubprocessEdges(files, projectRoot) {
7061
8079
  const knownFiles = new Set(files.map((f) => f.filePath));
7062
8080
  const basenameMap = /* @__PURE__ */ new Map();
7063
8081
  for (const f of files) {
7064
- const base = basename7(f.filePath);
8082
+ const base = basename9(f.filePath);
7065
8083
  if (!basenameMap.has(base)) basenameMap.set(base, []);
7066
8084
  basenameMap.get(base).push(f.filePath);
7067
8085
  }
7068
8086
  for (const file of files) {
7069
- const fullPath = join15(projectRoot, file.filePath);
7070
- if (!resolve9(fullPath).startsWith(resolve9(projectRoot))) continue;
8087
+ const fullPath = join17(projectRoot, file.filePath);
8088
+ if (!resolve11(fullPath).startsWith(resolve11(projectRoot))) continue;
7071
8089
  let source;
7072
8090
  try {
7073
- source = readFileSync12(fullPath, "utf-8");
8091
+ source = readFileSync13(fullPath, "utf-8");
7074
8092
  } catch {
7075
8093
  continue;
7076
8094
  }
@@ -7082,7 +8100,7 @@ function detectSubprocessEdges(files, projectRoot) {
7082
8100
  targetFile = call.calledFile;
7083
8101
  confidence = "high";
7084
8102
  } else {
7085
- const base = basename7(call.calledFile);
8103
+ const base = basename9(call.calledFile);
7086
8104
  const candidates = basenameMap.get(base);
7087
8105
  if (candidates && candidates.length > 0) {
7088
8106
  const exactCandidate = candidates.find((c) => c.endsWith(call.calledFile));
@@ -7461,7 +8479,7 @@ function getArchitectureSummary(graph) {
7461
8479
  }
7462
8480
 
7463
8481
  // src/health/metrics.ts
7464
- import { dirname as dirname12 } from "path";
8482
+ import { dirname as dirname14 } from "path";
7465
8483
  function scoreToGrade(score) {
7466
8484
  if (score >= 90) return "A";
7467
8485
  if (score >= 80) return "B";
@@ -7494,8 +8512,8 @@ function calculateCouplingScore(graph) {
7494
8512
  totalEdges++;
7495
8513
  fileConnections.set(sourceAttrs.filePath, (fileConnections.get(sourceAttrs.filePath) || 0) + 1);
7496
8514
  fileConnections.set(targetAttrs.filePath, (fileConnections.get(targetAttrs.filePath) || 0) + 1);
7497
- const sourceDir = dirname12(sourceAttrs.filePath).split("/")[0];
7498
- const targetDir = dirname12(targetAttrs.filePath).split("/")[0];
8515
+ const sourceDir = dirname14(sourceAttrs.filePath).split("/")[0];
8516
+ const targetDir = dirname14(targetAttrs.filePath).split("/")[0];
7499
8517
  if (sourceDir !== targetDir) {
7500
8518
  crossDirEdges++;
7501
8519
  }
@@ -7542,8 +8560,8 @@ function calculateCohesionScore(graph) {
7542
8560
  const sourceAttrs = graph.getNodeAttributes(source);
7543
8561
  const targetAttrs = graph.getNodeAttributes(target);
7544
8562
  if (sourceAttrs.filePath !== targetAttrs.filePath) {
7545
- const sourceDir = dirname12(sourceAttrs.filePath);
7546
- const targetDir = dirname12(targetAttrs.filePath);
8563
+ const sourceDir = dirname14(sourceAttrs.filePath);
8564
+ const targetDir = dirname14(targetAttrs.filePath);
7547
8565
  if (!dirEdges.has(sourceDir)) {
7548
8566
  dirEdges.set(sourceDir, { internal: 0, total: 0 });
7549
8567
  }
@@ -7825,8 +8843,8 @@ function calculateDepthScore(graph) {
7825
8843
  }
7826
8844
 
7827
8845
  // src/health/index.ts
7828
- import { readFileSync as readFileSync13, writeFileSync, existsSync as existsSync13, mkdirSync } from "fs";
7829
- import { dirname as dirname13, resolve as resolve10 } from "path";
8846
+ import { readFileSync as readFileSync14, writeFileSync, existsSync as existsSync14, mkdirSync } from "fs";
8847
+ import { dirname as dirname15, resolve as resolve12 } from "path";
7830
8848
  function calculateHealthScore(graph, projectRoot) {
7831
8849
  const coupling = calculateCouplingScore(graph);
7832
8850
  const cohesion = calculateCohesionScore(graph);
@@ -7925,8 +8943,8 @@ function getHealthTrend(projectRoot, currentScore) {
7925
8943
  }
7926
8944
  }
7927
8945
  function saveHealthHistory(projectRoot, report) {
7928
- const resolvedRoot = resolve10(projectRoot);
7929
- const historyFile = resolve10(resolvedRoot, ".depwire", "health-history.json");
8946
+ const resolvedRoot = resolve12(projectRoot);
8947
+ const historyFile = resolve12(resolvedRoot, ".depwire", "health-history.json");
7930
8948
  if (!historyFile.startsWith(resolvedRoot)) {
7931
8949
  return;
7932
8950
  }
@@ -7941,10 +8959,10 @@ function saveHealthHistory(projectRoot, report) {
7941
8959
  }))
7942
8960
  };
7943
8961
  let history = [];
7944
- if (existsSync13(historyFile)) {
8962
+ if (existsSync14(historyFile)) {
7945
8963
  try {
7946
8964
  if (!historyFile.startsWith(resolvedRoot)) return;
7947
- const content = readFileSync13(historyFile, "utf-8");
8965
+ const content = readFileSync14(historyFile, "utf-8");
7948
8966
  history = JSON.parse(content);
7949
8967
  } catch {
7950
8968
  }
@@ -7953,19 +8971,19 @@ function saveHealthHistory(projectRoot, report) {
7953
8971
  if (history.length > 50) {
7954
8972
  history = history.slice(-50);
7955
8973
  }
7956
- mkdirSync(dirname13(historyFile), { recursive: true });
8974
+ mkdirSync(dirname15(historyFile), { recursive: true });
7957
8975
  if (!historyFile.startsWith(resolvedRoot)) return;
7958
8976
  writeFileSync(historyFile, JSON.stringify(history, null, 2), "utf-8");
7959
8977
  }
7960
8978
  function loadHealthHistory(projectRoot) {
7961
- const resolvedRoot = resolve10(projectRoot);
7962
- const historyFile = resolve10(resolvedRoot, ".depwire", "health-history.json");
7963
- if (!historyFile.startsWith(resolvedRoot) || !existsSync13(historyFile)) {
8979
+ const resolvedRoot = resolve12(projectRoot);
8980
+ const historyFile = resolve12(resolvedRoot, ".depwire", "health-history.json");
8981
+ if (!historyFile.startsWith(resolvedRoot) || !existsSync14(historyFile)) {
7964
8982
  return [];
7965
8983
  }
7966
8984
  try {
7967
8985
  if (!historyFile.startsWith(resolvedRoot)) return [];
7968
- const content = readFileSync13(historyFile, "utf-8");
8986
+ const content = readFileSync14(historyFile, "utf-8");
7969
8987
  return JSON.parse(content);
7970
8988
  } catch {
7971
8989
  return [];
@@ -7974,7 +8992,7 @@ function loadHealthHistory(projectRoot) {
7974
8992
 
7975
8993
  // src/dead-code/detector.ts
7976
8994
  import path2 from "path";
7977
- import { readFileSync as readFileSync14, existsSync as existsSync14 } from "fs";
8995
+ import { readFileSync as readFileSync15, existsSync as existsSync15 } from "fs";
7978
8996
  function findDeadSymbols(graph, projectRoot, includeTests = false, debug = false) {
7979
8997
  const deadSymbols = [];
7980
8998
  const context = { graph, projectRoot };
@@ -8107,11 +9125,11 @@ function getPackageEntryPoints(projectRoot) {
8107
9125
  const entryPoints = /* @__PURE__ */ new Set();
8108
9126
  const resolvedRoot = path2.resolve(projectRoot);
8109
9127
  const packageJsonPath = path2.resolve(resolvedRoot, "package.json");
8110
- if (!packageJsonPath.startsWith(resolvedRoot) || !existsSync14(packageJsonPath)) {
9128
+ if (!packageJsonPath.startsWith(resolvedRoot) || !existsSync15(packageJsonPath)) {
8111
9129
  return entryPoints;
8112
9130
  }
8113
9131
  try {
8114
- const packageJson = JSON.parse(readFileSync14(packageJsonPath, "utf-8"));
9132
+ const packageJson = JSON.parse(readFileSync15(packageJsonPath, "utf-8"));
8115
9133
  if (packageJson.main) {
8116
9134
  entryPoints.add(path2.resolve(projectRoot, packageJson.main));
8117
9135
  }
@@ -8171,6 +9189,12 @@ function shouldExclude(attrs, context, includeTests, packageEntryPoints) {
8171
9189
  if (isPhpExcluded(attrs)) {
8172
9190
  return "framework";
8173
9191
  }
9192
+ if (isSwiftExcluded(attrs)) {
9193
+ return "framework";
9194
+ }
9195
+ if (isMojoExcluded(attrs)) {
9196
+ return "framework";
9197
+ }
8174
9198
  return null;
8175
9199
  }
8176
9200
  function isRealPackageEntryPoint(filePath, packageEntryPoints) {
@@ -8301,6 +9325,88 @@ function isPhpExcluded(attrs) {
8301
9325
  if (name.startsWith("test") || name === "setUp" || name === "tearDown" || name === "setUpBeforeClass" || name === "tearDownAfterClass") return true;
8302
9326
  return false;
8303
9327
  }
9328
+ function isSwiftExcluded(attrs) {
9329
+ const filePath = attrs.file || attrs.filePath || "";
9330
+ const name = attrs.name || "";
9331
+ if (!filePath.endsWith(".swift")) return false;
9332
+ if (name === "main") return true;
9333
+ const appLifecycle = [
9334
+ "application",
9335
+ "applicationDidFinishLaunching",
9336
+ "applicationWillTerminate",
9337
+ "applicationDidBecomeActive",
9338
+ "applicationWillResignActive",
9339
+ "applicationDidEnterBackground",
9340
+ "applicationWillEnterForeground",
9341
+ "scene",
9342
+ "sceneDidDisconnect",
9343
+ "sceneDidBecomeActive",
9344
+ "sceneWillResignActive",
9345
+ "sceneWillEnterForeground",
9346
+ "sceneDidEnterBackground"
9347
+ ];
9348
+ if (appLifecycle.includes(name)) return true;
9349
+ if (name === "body" || name === "previews") return true;
9350
+ const protocolMethods = [
9351
+ "hash",
9352
+ "encode",
9353
+ "init",
9354
+ "deinit",
9355
+ "tableView",
9356
+ "collectionView",
9357
+ "numberOfSections",
9358
+ "numberOfRowsInSection",
9359
+ "cellForRowAt",
9360
+ "didSelectRowAt"
9361
+ ];
9362
+ if (protocolMethods.includes(name)) return true;
9363
+ if (["encode", "decode", "init(from:)"].includes(name)) return true;
9364
+ if (name.startsWith("test") || name === "setUp" || name === "tearDown" || name === "setUpWithError" || name === "tearDownWithError") return true;
9365
+ return false;
9366
+ }
9367
+ function isMojoExcluded(attrs) {
9368
+ const filePath = attrs.file || attrs.filePath || "";
9369
+ const name = attrs.name || "";
9370
+ if (!filePath.endsWith(".mojo") && !filePath.endsWith(".\u{1F525}")) return false;
9371
+ const lifecycleMethods = [
9372
+ "__init__",
9373
+ "__copyinit__",
9374
+ "__moveinit__",
9375
+ "__del__",
9376
+ "__enter__",
9377
+ "__exit__"
9378
+ ];
9379
+ if (lifecycleMethods.includes(name)) return true;
9380
+ const traitMethods = [
9381
+ "__str__",
9382
+ "__repr__",
9383
+ "__len__",
9384
+ "__getitem__",
9385
+ "__setitem__",
9386
+ "__eq__",
9387
+ "__ne__",
9388
+ "__lt__",
9389
+ "__le__",
9390
+ "__gt__",
9391
+ "__ge__",
9392
+ "__add__",
9393
+ "__sub__",
9394
+ "__mul__",
9395
+ "__truediv__",
9396
+ "__floordiv__",
9397
+ "__hash__",
9398
+ "__bool__",
9399
+ "__int__",
9400
+ "__float__",
9401
+ "__iter__",
9402
+ "__next__",
9403
+ "__contains__"
9404
+ ];
9405
+ if (traitMethods.includes(name)) return true;
9406
+ if (name.startsWith("__mlir_") || name.startsWith("_mlir_")) return true;
9407
+ if (name === "main") return true;
9408
+ return false;
9409
+ }
8304
9410
 
8305
9411
  // src/dead-code/classifier.ts
8306
9412
  import path3 from "path";
@@ -8367,8 +9473,8 @@ function generateReason(symbol, confidence) {
8367
9473
  return "Potentially unused";
8368
9474
  }
8369
9475
  function isBarrelFile(filePath) {
8370
- const basename11 = path3.basename(filePath);
8371
- return basename11 === "index.ts" || basename11 === "index.js";
9476
+ const basename13 = path3.basename(filePath);
9477
+ return basename13 === "index.ts" || basename13 === "index.js";
8372
9478
  }
8373
9479
  function isTestFile2(filePath) {
8374
9480
  return filePath.includes("__tests__/") || filePath.includes(".test.") || filePath.includes(".spec.") || filePath.includes("/test/") || filePath.includes("/tests/");
@@ -8547,11 +9653,11 @@ function filterByConfidence(symbols, minConfidence) {
8547
9653
  }
8548
9654
 
8549
9655
  // src/docs/generator.ts
8550
- import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync17 } from "fs";
8551
- import { join as join19 } from "path";
9656
+ import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync18 } from "fs";
9657
+ import { join as join21 } from "path";
8552
9658
 
8553
9659
  // src/docs/architecture.ts
8554
- import { dirname as dirname14 } from "path";
9660
+ import { dirname as dirname16 } from "path";
8555
9661
 
8556
9662
  // src/docs/templates.ts
8557
9663
  function header(text, level = 1) {
@@ -8702,7 +9808,7 @@ function generateModuleStructure(graph) {
8702
9808
  function getDirectoryStats(graph) {
8703
9809
  const dirMap = /* @__PURE__ */ new Map();
8704
9810
  graph.forEachNode((node, attrs) => {
8705
- const dir = dirname14(attrs.filePath);
9811
+ const dir = dirname16(attrs.filePath);
8706
9812
  if (dir === ".") return;
8707
9813
  if (!dirMap.has(dir)) {
8708
9814
  dirMap.set(dir, {
@@ -8727,7 +9833,7 @@ function getDirectoryStats(graph) {
8727
9833
  });
8728
9834
  const filesPerDir = /* @__PURE__ */ new Map();
8729
9835
  graph.forEachNode((node, attrs) => {
8730
- const dir = dirname14(attrs.filePath);
9836
+ const dir = dirname16(attrs.filePath);
8731
9837
  if (!filesPerDir.has(dir)) {
8732
9838
  filesPerDir.set(dir, /* @__PURE__ */ new Set());
8733
9839
  }
@@ -8742,8 +9848,8 @@ function getDirectoryStats(graph) {
8742
9848
  graph.forEachEdge((edge, attrs, source, target) => {
8743
9849
  const sourceAttrs = graph.getNodeAttributes(source);
8744
9850
  const targetAttrs = graph.getNodeAttributes(target);
8745
- const sourceDir = dirname14(sourceAttrs.filePath);
8746
- const targetDir = dirname14(targetAttrs.filePath);
9851
+ const sourceDir = dirname16(sourceAttrs.filePath);
9852
+ const targetDir = dirname16(targetAttrs.filePath);
8747
9853
  if (sourceDir !== targetDir) {
8748
9854
  if (!dirEdges.has(sourceDir)) {
8749
9855
  dirEdges.set(sourceDir, { in: 0, out: 0 });
@@ -8950,7 +10056,7 @@ function detectCycles(graph) {
8950
10056
  }
8951
10057
 
8952
10058
  // src/docs/conventions.ts
8953
- import { basename as basename8, extname as extname9 } from "path";
10059
+ import { basename as basename10, extname as extname11 } from "path";
8954
10060
  function generateConventions(graph, projectRoot, version) {
8955
10061
  let output = "";
8956
10062
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -8988,7 +10094,7 @@ function generateFileOrganization(graph) {
8988
10094
  graph.forEachNode((node, attrs) => {
8989
10095
  if (!files.has(attrs.filePath)) {
8990
10096
  files.add(attrs.filePath);
8991
- const fileName = basename8(attrs.filePath);
10097
+ const fileName = basename10(attrs.filePath);
8992
10098
  if (fileName === "index.ts" || fileName === "index.js" || fileName === "index.tsx" || fileName === "index.jsx") {
8993
10099
  barrelFileCount++;
8994
10100
  }
@@ -9047,7 +10153,7 @@ function generateNamingPatterns(graph) {
9047
10153
  graph.forEachNode((node, attrs) => {
9048
10154
  if (!files.has(attrs.filePath)) {
9049
10155
  files.add(attrs.filePath);
9050
- const fileName = basename8(attrs.filePath, extname9(attrs.filePath));
10156
+ const fileName = basename10(attrs.filePath, extname11(attrs.filePath));
9051
10157
  if (isCamelCase(fileName)) patterns.files.camelCase++;
9052
10158
  else if (isPascalCase(fileName)) patterns.files.PascalCase++;
9053
10159
  else if (isKebabCase(fileName)) patterns.files.kebabCase++;
@@ -9724,7 +10830,7 @@ function detectCyclesDetailed(graph) {
9724
10830
  }
9725
10831
 
9726
10832
  // src/docs/onboarding.ts
9727
- import { dirname as dirname15 } from "path";
10833
+ import { dirname as dirname17 } from "path";
9728
10834
  function generateOnboarding(graph, projectRoot, version) {
9729
10835
  let output = "";
9730
10836
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -9783,7 +10889,7 @@ function generateQuickOrientation(graph) {
9783
10889
  const primaryLang = Object.entries(languages2).sort((a, b) => b[1] - a[1])[0];
9784
10890
  const dirs = /* @__PURE__ */ new Set();
9785
10891
  graph.forEachNode((node, attrs) => {
9786
- const dir = dirname15(attrs.filePath);
10892
+ const dir = dirname17(attrs.filePath);
9787
10893
  if (dir !== ".") {
9788
10894
  const topLevel = dir.split("/")[0];
9789
10895
  dirs.add(topLevel);
@@ -9887,7 +10993,7 @@ function generateModuleMap(graph) {
9887
10993
  function getDirectoryStats2(graph) {
9888
10994
  const dirMap = /* @__PURE__ */ new Map();
9889
10995
  graph.forEachNode((node, attrs) => {
9890
- const dir = dirname15(attrs.filePath);
10996
+ const dir = dirname17(attrs.filePath);
9891
10997
  if (dir === ".") return;
9892
10998
  if (!dirMap.has(dir)) {
9893
10999
  dirMap.set(dir, {
@@ -9902,7 +11008,7 @@ function getDirectoryStats2(graph) {
9902
11008
  });
9903
11009
  const filesPerDir = /* @__PURE__ */ new Map();
9904
11010
  graph.forEachNode((node, attrs) => {
9905
- const dir = dirname15(attrs.filePath);
11011
+ const dir = dirname17(attrs.filePath);
9906
11012
  if (!filesPerDir.has(dir)) {
9907
11013
  filesPerDir.set(dir, /* @__PURE__ */ new Set());
9908
11014
  }
@@ -9917,8 +11023,8 @@ function getDirectoryStats2(graph) {
9917
11023
  graph.forEachEdge((edge, attrs, source, target) => {
9918
11024
  const sourceAttrs = graph.getNodeAttributes(source);
9919
11025
  const targetAttrs = graph.getNodeAttributes(target);
9920
- const sourceDir = dirname15(sourceAttrs.filePath);
9921
- const targetDir = dirname15(targetAttrs.filePath);
11026
+ const sourceDir = dirname17(sourceAttrs.filePath);
11027
+ const targetDir = dirname17(targetAttrs.filePath);
9922
11028
  if (sourceDir !== targetDir) {
9923
11029
  if (!dirEdges.has(sourceDir)) {
9924
11030
  dirEdges.set(sourceDir, { in: 0, out: 0 });
@@ -9999,7 +11105,7 @@ function detectClusters(graph) {
9999
11105
  const dirFiles = /* @__PURE__ */ new Map();
10000
11106
  const fileEdges = /* @__PURE__ */ new Map();
10001
11107
  graph.forEachNode((node, attrs) => {
10002
- const dir = dirname15(attrs.filePath);
11108
+ const dir = dirname17(attrs.filePath);
10003
11109
  if (!dirFiles.has(dir)) {
10004
11110
  dirFiles.set(dir, /* @__PURE__ */ new Set());
10005
11111
  }
@@ -10053,8 +11159,8 @@ function inferClusterName(files) {
10053
11159
  if (sortedWords.length > 0 && sortedWords[0][1] > 1) {
10054
11160
  return capitalizeFirst2(sortedWords[0][0]);
10055
11161
  }
10056
- const commonDir = dirname15(files[0]);
10057
- if (files.every((f) => dirname15(f) === commonDir)) {
11162
+ const commonDir = dirname17(files[0]);
11163
+ if (files.every((f) => dirname17(f) === commonDir)) {
10058
11164
  return capitalizeFirst2(commonDir.split("/").pop() || "Core");
10059
11165
  }
10060
11166
  return "Core";
@@ -10112,7 +11218,7 @@ function generateDepwireUsage(projectRoot) {
10112
11218
  }
10113
11219
 
10114
11220
  // src/docs/files.ts
10115
- import { dirname as dirname16, basename as basename9 } from "path";
11221
+ import { dirname as dirname18, basename as basename11 } from "path";
10116
11222
  function generateFiles(graph, projectRoot, version) {
10117
11223
  let output = "";
10118
11224
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -10216,7 +11322,7 @@ function generateDirectoryBreakdown(graph) {
10216
11322
  const fileStats = getFileStats2(graph);
10217
11323
  const dirMap = /* @__PURE__ */ new Map();
10218
11324
  for (const file of fileStats) {
10219
- const dir = dirname16(file.filePath);
11325
+ const dir = dirname18(file.filePath);
10220
11326
  const topDir = dir === "." ? "." : dir.split("/")[0];
10221
11327
  if (!dirMap.has(topDir)) {
10222
11328
  dirMap.set(topDir, {
@@ -10231,7 +11337,7 @@ function generateDirectoryBreakdown(graph) {
10231
11337
  dirStats.symbolCount += file.symbolCount;
10232
11338
  if (file.totalConnections > dirStats.maxConnections) {
10233
11339
  dirStats.maxConnections = file.totalConnections;
10234
- dirStats.mostConnectedFile = basename9(file.filePath);
11340
+ dirStats.mostConnectedFile = basename11(file.filePath);
10235
11341
  }
10236
11342
  }
10237
11343
  if (dirMap.size === 0) {
@@ -10859,7 +11965,7 @@ function generateRecommendations(graph) {
10859
11965
  }
10860
11966
 
10861
11967
  // src/docs/tests.ts
10862
- import { basename as basename10, dirname as dirname17 } from "path";
11968
+ import { basename as basename12, dirname as dirname19 } from "path";
10863
11969
  function generateTests(graph, projectRoot, version) {
10864
11970
  let output = "";
10865
11971
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -10887,8 +11993,8 @@ function getFileCount8(graph) {
10887
11993
  return files.size;
10888
11994
  }
10889
11995
  function isTestFile3(filePath) {
10890
- const fileName = basename10(filePath).toLowerCase();
10891
- const dirPath = dirname17(filePath).toLowerCase();
11996
+ const fileName = basename12(filePath).toLowerCase();
11997
+ const dirPath = dirname19(filePath).toLowerCase();
10892
11998
  if (dirPath.includes("test") || dirPath.includes("spec") || dirPath.includes("__tests__")) {
10893
11999
  return true;
10894
12000
  }
@@ -10946,13 +12052,13 @@ function generateTestFileInventory(graph) {
10946
12052
  return output;
10947
12053
  }
10948
12054
  function matchTestToSource(testFile) {
10949
- const testFileName = basename10(testFile);
10950
- const testDir = dirname17(testFile);
12055
+ const testFileName = basename12(testFile);
12056
+ const testDir = dirname19(testFile);
10951
12057
  let sourceFileName = testFileName.replace(/\.test\./g, ".").replace(/\.spec\./g, ".").replace(/_test\./g, ".").replace(/_spec\./g, ".");
10952
12058
  const possiblePaths = [];
10953
12059
  possiblePaths.push(testDir + "/" + sourceFileName);
10954
12060
  if (testDir.endsWith("/test") || testDir.endsWith("/tests") || testDir.endsWith("/__tests__")) {
10955
- const parentDir = dirname17(testDir);
12061
+ const parentDir = dirname19(testDir);
10956
12062
  possiblePaths.push(parentDir + "/" + sourceFileName);
10957
12063
  }
10958
12064
  if (testDir.includes("test")) {
@@ -11108,7 +12214,7 @@ function generateTestCoverageMap(graph) {
11108
12214
  const rows = mappings.slice(0, 30).map((m) => [
11109
12215
  `\`${m.sourceFile}\``,
11110
12216
  m.hasTest ? "\u2705" : "\u274C",
11111
- m.testFile ? `\`${basename10(m.testFile)}\`` : "-",
12217
+ m.testFile ? `\`${basename12(m.testFile)}\`` : "-",
11112
12218
  formatNumber(m.symbolCount)
11113
12219
  ]);
11114
12220
  let output = table(headers, rows);
@@ -11149,7 +12255,7 @@ function generateTestStatistics(graph) {
11149
12255
  `;
11150
12256
  const dirTestCoverage = /* @__PURE__ */ new Map();
11151
12257
  for (const sourceFile of sourceFiles) {
11152
- const dir = dirname17(sourceFile).split("/")[0];
12258
+ const dir = dirname19(sourceFile).split("/")[0];
11153
12259
  if (!dirTestCoverage.has(dir)) {
11154
12260
  dirTestCoverage.set(dir, { total: 0, tested: 0 });
11155
12261
  }
@@ -11172,7 +12278,7 @@ function generateTestStatistics(graph) {
11172
12278
  }
11173
12279
 
11174
12280
  // src/docs/history.ts
11175
- import { dirname as dirname18 } from "path";
12281
+ import { dirname as dirname20 } from "path";
11176
12282
  import { execSync } from "child_process";
11177
12283
  function generateHistory(graph, projectRoot, version) {
11178
12284
  let output = "";
@@ -11453,7 +12559,7 @@ function generateFeatureClusters(graph) {
11453
12559
  const dirFiles = /* @__PURE__ */ new Map();
11454
12560
  const fileEdges = /* @__PURE__ */ new Map();
11455
12561
  graph.forEachNode((node, attrs) => {
11456
- const dir = dirname18(attrs.filePath);
12562
+ const dir = dirname20(attrs.filePath);
11457
12563
  if (!dirFiles.has(dir)) {
11458
12564
  dirFiles.set(dir, /* @__PURE__ */ new Set());
11459
12565
  }
@@ -11535,7 +12641,7 @@ function capitalizeFirst3(str) {
11535
12641
  }
11536
12642
 
11537
12643
  // src/docs/current.ts
11538
- import { dirname as dirname19 } from "path";
12644
+ import { dirname as dirname21 } from "path";
11539
12645
  function generateCurrent(graph, projectRoot, version) {
11540
12646
  let output = "";
11541
12647
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -11673,7 +12779,7 @@ function generateCompleteFileIndex(graph) {
11673
12779
  fileInfos.sort((a, b) => a.filePath.localeCompare(b.filePath));
11674
12780
  const dirGroups = /* @__PURE__ */ new Map();
11675
12781
  for (const info of fileInfos) {
11676
- const dir = dirname19(info.filePath);
12782
+ const dir = dirname21(info.filePath);
11677
12783
  const topDir = dir === "." ? "root" : dir.split("/")[0];
11678
12784
  if (!dirGroups.has(topDir)) {
11679
12785
  dirGroups.set(topDir, []);
@@ -11884,8 +12990,8 @@ function getTopLevelDir2(filePath) {
11884
12990
  }
11885
12991
 
11886
12992
  // src/docs/status.ts
11887
- import { readFileSync as readFileSync15, existsSync as existsSync15 } from "fs";
11888
- import { resolve as resolve11 } from "path";
12993
+ import { readFileSync as readFileSync16, existsSync as existsSync16 } from "fs";
12994
+ import { resolve as resolve13 } from "path";
11889
12995
  function generateStatus(graph, projectRoot, version) {
11890
12996
  let output = "";
11891
12997
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -11918,16 +13024,16 @@ function getFileCount11(graph) {
11918
13024
  }
11919
13025
  function extractComments(projectRoot, filePath) {
11920
13026
  const comments = [];
11921
- const resolvedRoot = resolve11(projectRoot);
11922
- const fullPath = resolve11(resolvedRoot, filePath);
13027
+ const resolvedRoot = resolve13(projectRoot);
13028
+ const fullPath = resolve13(resolvedRoot, filePath);
11923
13029
  if (!fullPath.startsWith(resolvedRoot)) {
11924
13030
  return comments;
11925
13031
  }
11926
- if (!existsSync15(fullPath)) {
13032
+ if (!existsSync16(fullPath)) {
11927
13033
  return comments;
11928
13034
  }
11929
13035
  try {
11930
- const content = readFileSync15(fullPath, "utf-8");
13036
+ const content = readFileSync16(fullPath, "utf-8");
11931
13037
  const lines = content.split("\n");
11932
13038
  const patterns = [
11933
13039
  { type: "TODO", regex: /(?:\/\/|#|\/\*)\s*TODO:?\s*(.+)/i },
@@ -12506,16 +13612,16 @@ function generateConfidenceSection(title, description, symbols, projectRoot) {
12506
13612
  }
12507
13613
 
12508
13614
  // src/docs/metadata.ts
12509
- import { existsSync as existsSync16, readFileSync as readFileSync16, writeFileSync as writeFileSync2 } from "fs";
12510
- import { resolve as resolve12 } from "path";
13615
+ import { existsSync as existsSync17, readFileSync as readFileSync17, writeFileSync as writeFileSync2 } from "fs";
13616
+ import { resolve as resolve14 } from "path";
12511
13617
  function loadMetadata(outputDir) {
12512
- const resolvedDir = resolve12(outputDir);
12513
- const metadataPath = resolve12(resolvedDir, "metadata.json");
12514
- if (!metadataPath.startsWith(resolvedDir) || !existsSync16(metadataPath)) {
13618
+ const resolvedDir = resolve14(outputDir);
13619
+ const metadataPath = resolve14(resolvedDir, "metadata.json");
13620
+ if (!metadataPath.startsWith(resolvedDir) || !existsSync17(metadataPath)) {
12515
13621
  return null;
12516
13622
  }
12517
13623
  try {
12518
- const content = readFileSync16(metadataPath, "utf-8");
13624
+ const content = readFileSync17(metadataPath, "utf-8");
12519
13625
  return JSON.parse(content);
12520
13626
  } catch (err) {
12521
13627
  console.error("Failed to load metadata:", err);
@@ -12523,8 +13629,8 @@ function loadMetadata(outputDir) {
12523
13629
  }
12524
13630
  }
12525
13631
  function saveMetadata(outputDir, metadata) {
12526
- const resolvedDir = resolve12(outputDir);
12527
- const metadataPath = resolve12(resolvedDir, "metadata.json");
13632
+ const resolvedDir = resolve14(outputDir);
13633
+ const metadataPath = resolve14(resolvedDir, "metadata.json");
12528
13634
  if (!metadataPath.startsWith(resolvedDir)) {
12529
13635
  throw new Error(`Path traversal attempt blocked: ${metadataPath}`);
12530
13636
  }
@@ -12570,7 +13676,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
12570
13676
  const generated = [];
12571
13677
  const errors = [];
12572
13678
  try {
12573
- if (!existsSync17(options.outputDir)) {
13679
+ if (!existsSync18(options.outputDir)) {
12574
13680
  mkdirSync2(options.outputDir, { recursive: true });
12575
13681
  if (options.verbose) {
12576
13682
  console.log(`Created output directory: ${options.outputDir}`);
@@ -12609,7 +13715,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
12609
13715
  try {
12610
13716
  if (options.verbose) console.log("Generating ARCHITECTURE.md...");
12611
13717
  const content = generateArchitecture(graph, projectRoot, version, parseTime);
12612
- const filePath = join19(options.outputDir, "ARCHITECTURE.md");
13718
+ const filePath = join21(options.outputDir, "ARCHITECTURE.md");
12613
13719
  writeFileSync3(filePath, content, "utf-8");
12614
13720
  generated.push("ARCHITECTURE.md");
12615
13721
  } catch (err) {
@@ -12620,7 +13726,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
12620
13726
  try {
12621
13727
  if (options.verbose) console.log("Generating CONVENTIONS.md...");
12622
13728
  const content = generateConventions(graph, projectRoot, version);
12623
- const filePath = join19(options.outputDir, "CONVENTIONS.md");
13729
+ const filePath = join21(options.outputDir, "CONVENTIONS.md");
12624
13730
  writeFileSync3(filePath, content, "utf-8");
12625
13731
  generated.push("CONVENTIONS.md");
12626
13732
  } catch (err) {
@@ -12631,7 +13737,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
12631
13737
  try {
12632
13738
  if (options.verbose) console.log("Generating DEPENDENCIES.md...");
12633
13739
  const content = generateDependencies(graph, projectRoot, version);
12634
- const filePath = join19(options.outputDir, "DEPENDENCIES.md");
13740
+ const filePath = join21(options.outputDir, "DEPENDENCIES.md");
12635
13741
  writeFileSync3(filePath, content, "utf-8");
12636
13742
  generated.push("DEPENDENCIES.md");
12637
13743
  } catch (err) {
@@ -12642,7 +13748,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
12642
13748
  try {
12643
13749
  if (options.verbose) console.log("Generating ONBOARDING.md...");
12644
13750
  const content = generateOnboarding(graph, projectRoot, version);
12645
- const filePath = join19(options.outputDir, "ONBOARDING.md");
13751
+ const filePath = join21(options.outputDir, "ONBOARDING.md");
12646
13752
  writeFileSync3(filePath, content, "utf-8");
12647
13753
  generated.push("ONBOARDING.md");
12648
13754
  } catch (err) {
@@ -12653,7 +13759,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
12653
13759
  try {
12654
13760
  if (options.verbose) console.log("Generating FILES.md...");
12655
13761
  const content = generateFiles(graph, projectRoot, version);
12656
- const filePath = join19(options.outputDir, "FILES.md");
13762
+ const filePath = join21(options.outputDir, "FILES.md");
12657
13763
  writeFileSync3(filePath, content, "utf-8");
12658
13764
  generated.push("FILES.md");
12659
13765
  } catch (err) {
@@ -12664,7 +13770,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
12664
13770
  try {
12665
13771
  if (options.verbose) console.log("Generating API_SURFACE.md...");
12666
13772
  const content = generateApiSurface(graph, projectRoot, version);
12667
- const filePath = join19(options.outputDir, "API_SURFACE.md");
13773
+ const filePath = join21(options.outputDir, "API_SURFACE.md");
12668
13774
  writeFileSync3(filePath, content, "utf-8");
12669
13775
  generated.push("API_SURFACE.md");
12670
13776
  } catch (err) {
@@ -12675,7 +13781,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
12675
13781
  try {
12676
13782
  if (options.verbose) console.log("Generating ERRORS.md...");
12677
13783
  const content = generateErrors(graph, projectRoot, version);
12678
- const filePath = join19(options.outputDir, "ERRORS.md");
13784
+ const filePath = join21(options.outputDir, "ERRORS.md");
12679
13785
  writeFileSync3(filePath, content, "utf-8");
12680
13786
  generated.push("ERRORS.md");
12681
13787
  } catch (err) {
@@ -12686,7 +13792,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
12686
13792
  try {
12687
13793
  if (options.verbose) console.log("Generating TESTS.md...");
12688
13794
  const content = generateTests(graph, projectRoot, version);
12689
- const filePath = join19(options.outputDir, "TESTS.md");
13795
+ const filePath = join21(options.outputDir, "TESTS.md");
12690
13796
  writeFileSync3(filePath, content, "utf-8");
12691
13797
  generated.push("TESTS.md");
12692
13798
  } catch (err) {
@@ -12697,7 +13803,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
12697
13803
  try {
12698
13804
  if (options.verbose) console.log("Generating HISTORY.md...");
12699
13805
  const content = generateHistory(graph, projectRoot, version);
12700
- const filePath = join19(options.outputDir, "HISTORY.md");
13806
+ const filePath = join21(options.outputDir, "HISTORY.md");
12701
13807
  writeFileSync3(filePath, content, "utf-8");
12702
13808
  generated.push("HISTORY.md");
12703
13809
  } catch (err) {
@@ -12708,7 +13814,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
12708
13814
  try {
12709
13815
  if (options.verbose) console.log("Generating CURRENT.md...");
12710
13816
  const content = generateCurrent(graph, projectRoot, version);
12711
- const filePath = join19(options.outputDir, "CURRENT.md");
13817
+ const filePath = join21(options.outputDir, "CURRENT.md");
12712
13818
  writeFileSync3(filePath, content, "utf-8");
12713
13819
  generated.push("CURRENT.md");
12714
13820
  } catch (err) {
@@ -12719,7 +13825,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
12719
13825
  try {
12720
13826
  if (options.verbose) console.log("Generating STATUS.md...");
12721
13827
  const content = generateStatus(graph, projectRoot, version);
12722
- const filePath = join19(options.outputDir, "STATUS.md");
13828
+ const filePath = join21(options.outputDir, "STATUS.md");
12723
13829
  writeFileSync3(filePath, content, "utf-8");
12724
13830
  generated.push("STATUS.md");
12725
13831
  } catch (err) {
@@ -12730,7 +13836,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
12730
13836
  try {
12731
13837
  if (options.verbose) console.log("Generating HEALTH.md...");
12732
13838
  const content = generateHealth(graph, projectRoot, version);
12733
- const filePath = join19(options.outputDir, "HEALTH.md");
13839
+ const filePath = join21(options.outputDir, "HEALTH.md");
12734
13840
  writeFileSync3(filePath, content, "utf-8");
12735
13841
  generated.push("HEALTH.md");
12736
13842
  } catch (err) {
@@ -12741,7 +13847,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
12741
13847
  try {
12742
13848
  if (options.verbose) console.log("Generating DEAD_CODE.md...");
12743
13849
  const content = generateDeadCode(graph, projectRoot, version);
12744
- const filePath = join19(options.outputDir, "DEAD_CODE.md");
13850
+ const filePath = join21(options.outputDir, "DEAD_CODE.md");
12745
13851
  writeFileSync3(filePath, content, "utf-8");
12746
13852
  generated.push("DEAD_CODE.md");
12747
13853
  } catch (err) {
@@ -12785,7 +13891,7 @@ function getFileCount13(graph) {
12785
13891
  }
12786
13892
 
12787
13893
  // src/simulation/engine.ts
12788
- import { dirname as dirname20, join as join20 } from "path";
13894
+ import { dirname as dirname22, join as join22 } from "path";
12789
13895
  function normalizePath2(p) {
12790
13896
  return p.replace(/^\.\//, "").replace(/\/+$/, "");
12791
13897
  }
@@ -12917,7 +14023,7 @@ var SimulationEngine = class {
12917
14023
  }
12918
14024
  }
12919
14025
  applyRename(clone, target, newName, brokenImports) {
12920
- const destination = join20(dirname20(target), newName);
14026
+ const destination = join22(dirname22(target), newName);
12921
14027
  this.applyMove(clone, target, destination, brokenImports);
12922
14028
  }
12923
14029
  applySplit(clone, target, newFile, symbols, brokenImports) {
@@ -13117,13 +14223,13 @@ var SimulationEngine = class {
13117
14223
  };
13118
14224
 
13119
14225
  // src/security/scanner.ts
13120
- import { existsSync as existsSync19 } from "fs";
13121
- import { join as join30 } from "path";
14226
+ import { existsSync as existsSync20 } from "fs";
14227
+ import { join as join32 } from "path";
13122
14228
 
13123
14229
  // src/security/checks/dependencies.ts
13124
14230
  import { execSync as execSync2 } from "child_process";
13125
- import { existsSync as existsSync18, readFileSync as readFileSync17, readdirSync as readdirSync10 } from "fs";
13126
- import { join as join21 } from "path";
14231
+ import { existsSync as existsSync19, readFileSync as readFileSync18, readdirSync as readdirSync11 } from "fs";
14232
+ import { join as join23 } from "path";
13127
14233
  function cvssToSeverity(score) {
13128
14234
  if (score >= 9) return "critical";
13129
14235
  if (score >= 7) return "high";
@@ -13133,18 +14239,18 @@ function cvssToSeverity(score) {
13133
14239
  async function checkDependencies(_files, projectRoot) {
13134
14240
  const findings = [];
13135
14241
  try {
13136
- if (existsSync18(join21(projectRoot, "package.json"))) {
14242
+ if (existsSync19(join23(projectRoot, "package.json"))) {
13137
14243
  findings.push(...checkNpmAudit(projectRoot));
13138
14244
  findings.push(...checkPackageJsonPatterns(projectRoot));
13139
14245
  findings.push(...checkPostinstallScripts(projectRoot));
13140
14246
  }
13141
- if (existsSync18(join21(projectRoot, "requirements.txt")) || existsSync18(join21(projectRoot, "pyproject.toml"))) {
14247
+ if (existsSync19(join23(projectRoot, "requirements.txt")) || existsSync19(join23(projectRoot, "pyproject.toml"))) {
13142
14248
  findings.push(...checkPipAudit(projectRoot));
13143
14249
  }
13144
- if (existsSync18(join21(projectRoot, "Cargo.toml"))) {
14250
+ if (existsSync19(join23(projectRoot, "Cargo.toml"))) {
13145
14251
  findings.push(...checkCargoAudit(projectRoot));
13146
14252
  }
13147
- if (existsSync18(join21(projectRoot, "go.mod"))) {
14253
+ if (existsSync19(join23(projectRoot, "go.mod"))) {
13148
14254
  findings.push(...checkGoVerify(projectRoot));
13149
14255
  }
13150
14256
  } catch (err) {
@@ -13233,8 +14339,8 @@ function checkNpmAudit(projectRoot) {
13233
14339
  function checkPackageJsonPatterns(projectRoot) {
13234
14340
  const findings = [];
13235
14341
  try {
13236
- const pkgPath = join21(projectRoot, "package.json");
13237
- const pkg = JSON.parse(readFileSync17(pkgPath, "utf-8"));
14342
+ const pkgPath = join23(projectRoot, "package.json");
14343
+ const pkg = JSON.parse(readFileSync18(pkgPath, "utf-8"));
13238
14344
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
13239
14345
  for (const [name, version] of Object.entries(allDeps)) {
13240
14346
  if (version.startsWith("^") || version.startsWith("~")) {
@@ -13256,15 +14362,15 @@ function checkPackageJsonPatterns(projectRoot) {
13256
14362
  }
13257
14363
  function checkPostinstallScripts(projectRoot) {
13258
14364
  const findings = [];
13259
- const nodeModules = join21(projectRoot, "node_modules");
13260
- if (!existsSync18(nodeModules)) return findings;
14365
+ const nodeModules = join23(projectRoot, "node_modules");
14366
+ if (!existsSync19(nodeModules)) return findings;
13261
14367
  try {
13262
- const topLevelDeps = readdirSync10(nodeModules).filter((d) => !d.startsWith("."));
14368
+ const topLevelDeps = readdirSync11(nodeModules).filter((d) => !d.startsWith("."));
13263
14369
  for (const dep of topLevelDeps) {
13264
- const depPkgPath = join21(nodeModules, dep, "package.json");
13265
- if (!existsSync18(depPkgPath)) continue;
14370
+ const depPkgPath = join23(nodeModules, dep, "package.json");
14371
+ if (!existsSync19(depPkgPath)) continue;
13266
14372
  try {
13267
- const depPkg = JSON.parse(readFileSync17(depPkgPath, "utf-8"));
14373
+ const depPkg = JSON.parse(readFileSync18(depPkgPath, "utf-8"));
13268
14374
  const scripts = depPkg.scripts || {};
13269
14375
  if (scripts.postinstall || scripts.preinstall || scripts.install) {
13270
14376
  const scriptName = scripts.postinstall ? "postinstall" : scripts.preinstall ? "preinstall" : "install";
@@ -13302,7 +14408,7 @@ function checkPipAudit(projectRoot) {
13302
14408
  id: "",
13303
14409
  severity: cvssToSeverity(vuln.cvss?.score || 5),
13304
14410
  vulnerabilityClass: "dependency-cve",
13305
- file: existsSync18(join21(projectRoot, "requirements.txt")) ? "requirements.txt" : "pyproject.toml",
14411
+ file: existsSync19(join23(projectRoot, "requirements.txt")) ? "requirements.txt" : "pyproject.toml",
13306
14412
  title: `Vulnerable Python dependency: ${vuln.name}`,
13307
14413
  description: `${vuln.name}@${vuln.version} \u2014 ${vuln.id}: ${vuln.description || "Known vulnerability"}`,
13308
14414
  attackScenario: `An attacker could exploit the vulnerability in ${vuln.name}.`,
@@ -13399,8 +14505,8 @@ function checkGoVerify(projectRoot) {
13399
14505
  }
13400
14506
 
13401
14507
  // src/security/checks/injection.ts
13402
- import { readFileSync as readFileSync18 } from "fs";
13403
- import { join as join22 } from "path";
14508
+ import { readFileSync as readFileSync19 } from "fs";
14509
+ import { join as join24 } from "path";
13404
14510
  var SKIP_DIRS = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
13405
14511
  var TEST_PATTERNS = ["test", "spec", "fixture", "mock", "__tests__", "__mocks__"];
13406
14512
  var USER_INPUT_NAMES = /(?:input|user|name|path|query|branch|hash|cmd|command|req\.|params|body|args|url|dir|file|subdirectory)/i;
@@ -13687,6 +14793,89 @@ var PATTERNS = [
13687
14793
  description: "extract() on $_GET/$_POST/$_REQUEST overwrites local variables \u2014 can bypass security checks.",
13688
14794
  attackScenario: "An attacker could set arbitrary variables by crafting request parameters, potentially overwriting auth flags or config values.",
13689
14795
  suggestedFix: "Avoid extract() on user input. Access superglobals directly or use a whitelist of expected keys."
14796
+ },
14797
+ // Swift injection patterns
14798
+ {
14799
+ regex: /["'](?:SELECT|INSERT|UPDATE|DELETE)\b[^"']*\\\(/,
14800
+ title: "Swift SQL injection via string interpolation",
14801
+ vulnClass: "code-injection",
14802
+ baseSeverity: "high",
14803
+ description: "SQL query built using Swift string interpolation \u2014 vulnerable to SQL injection.",
14804
+ attackScenario: "An attacker could inject SQL through interpolated variables to read, modify, or delete database data.",
14805
+ suggestedFix: "Use parameterized queries with your database library (e.g., Fluent ORM, SQLite.swift bindings)."
14806
+ },
14807
+ {
14808
+ regex: /Process\s*\(\s*\)/,
14809
+ title: "Swift command injection via Process class",
14810
+ vulnClass: "shell-injection",
14811
+ baseSeverity: "medium",
14812
+ description: "Process() class executes external commands \u2014 vulnerable if user input reaches arguments.",
14813
+ attackScenario: "An attacker could inject shell metacharacters to execute arbitrary commands on the server.",
14814
+ suggestedFix: "Validate all arguments against a strict allowlist before passing to Process. Avoid shell execution."
14815
+ },
14816
+ {
14817
+ regex: /Unsafe(?:Raw|Mutable|Buffer)?Pointer/,
14818
+ title: "Swift unsafe pointer usage",
14819
+ vulnClass: "code-injection",
14820
+ baseSeverity: "medium",
14821
+ description: "Unsafe pointer usage bypasses Swift memory safety \u2014 potential for memory corruption.",
14822
+ attackScenario: "An attacker could exploit unsafe pointer operations to corrupt memory or execute arbitrary code.",
14823
+ suggestedFix: "Prefer safe Swift alternatives. If unsafe pointers are necessary, validate all bounds and lifetimes."
14824
+ },
14825
+ {
14826
+ regex: /UserDefaults\s*\.\s*(?:standard\s*\.\s*)?set\s*\([^)]*(?:password|secret|token|apiKey|api_key)/i,
14827
+ title: "Swift sensitive data in UserDefaults (unencrypted)",
14828
+ vulnClass: "code-injection",
14829
+ baseSeverity: "high",
14830
+ description: "Sensitive data stored in UserDefaults without encryption \u2014 easily accessible on jailbroken devices.",
14831
+ attackScenario: "An attacker with device access could read UserDefaults plist to extract credentials.",
14832
+ suggestedFix: "Use Keychain Services for storing sensitive data. Never store passwords or tokens in UserDefaults."
14833
+ },
14834
+ // Mojo injection patterns
14835
+ {
14836
+ regex: /\bPointer\s*\[\s*\w+\s*\]/,
14837
+ title: "Mojo unsafe Pointer[T] usage",
14838
+ vulnClass: "code-injection",
14839
+ baseSeverity: "medium",
14840
+ description: "Mojo Pointer[T] bypasses memory safety \u2014 potential for memory corruption or buffer overflow.",
14841
+ attackScenario: "An attacker could exploit unsafe pointer operations to corrupt memory or execute arbitrary code.",
14842
+ suggestedFix: "Use safe Mojo abstractions (SIMD, Tensor) instead of raw pointers where possible. Validate bounds."
14843
+ },
14844
+ {
14845
+ regex: /\bDTypePointer\b/,
14846
+ title: "Mojo unsafe DTypePointer usage",
14847
+ vulnClass: "code-injection",
14848
+ baseSeverity: "medium",
14849
+ description: "DTypePointer provides raw memory access without bounds checking.",
14850
+ attackScenario: "An attacker could exploit unvalidated pointer operations for buffer overflow or memory corruption.",
14851
+ suggestedFix: "Use Tensor or SIMD types with bounds checking instead of raw DTypePointer."
14852
+ },
14853
+ {
14854
+ regex: /from\s+python\s+import.*\beval\b/,
14855
+ title: "Mojo Python interop: eval() imported",
14856
+ vulnClass: "code-injection",
14857
+ baseSeverity: "high",
14858
+ description: "Python eval() imported via Mojo Python interop \u2014 can execute arbitrary code.",
14859
+ attackScenario: "An attacker could inject malicious code strings executed through the Python bridge.",
14860
+ suggestedFix: "Avoid importing eval. Use safe parsing alternatives or validate all input strictly."
14861
+ },
14862
+ {
14863
+ regex: /\b__get_address_as_lvalue\b/,
14864
+ title: "Mojo uninitialized memory access pattern",
14865
+ vulnClass: "code-injection",
14866
+ baseSeverity: "medium",
14867
+ description: "Low-level memory access without initialization \u2014 potential for use of uninitialized data.",
14868
+ attackScenario: "An attacker could exploit uninitialized memory to leak data or corrupt program state.",
14869
+ suggestedFix: "Always initialize memory before use. Use safe constructors and value semantics."
14870
+ },
14871
+ {
14872
+ regex: /SIMD\s*\[[^\]]*\]\s*\.\s*(?:store|load)\s*\(/,
14873
+ title: "Mojo SIMD store/load without bounds check",
14874
+ vulnClass: "code-injection",
14875
+ baseSeverity: "medium",
14876
+ description: "SIMD store/load operations without explicit bounds checking \u2014 buffer overflow risk.",
14877
+ attackScenario: "An attacker could trigger out-of-bounds SIMD operations to corrupt memory.",
14878
+ suggestedFix: "Validate buffer size against SIMD width before store/load operations."
13690
14879
  }
13691
14880
  ];
13692
14881
  function shouldSkip(filePath) {
@@ -13703,7 +14892,7 @@ async function checkInjection(files, projectRoot) {
13703
14892
  if (shouldSkip(file.filePath) || isTestFile4(file.filePath)) continue;
13704
14893
  let content;
13705
14894
  try {
13706
- content = readFileSync18(join22(projectRoot, file.filePath), "utf-8");
14895
+ content = readFileSync19(join24(projectRoot, file.filePath), "utf-8");
13707
14896
  } catch {
13708
14897
  continue;
13709
14898
  }
@@ -13741,8 +14930,8 @@ async function checkInjection(files, projectRoot) {
13741
14930
  }
13742
14931
 
13743
14932
  // src/security/checks/secrets.ts
13744
- import { readFileSync as readFileSync19 } from "fs";
13745
- import { join as join23 } from "path";
14933
+ import { readFileSync as readFileSync20 } from "fs";
14934
+ import { join as join25 } from "path";
13746
14935
  var SKIP_DIRS2 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
13747
14936
  var TEST_PATTERNS2 = ["test", "spec", "fixture", "mock", "__tests__", "__mocks__", ".example", ".sample"];
13748
14937
  var SECRET_PATTERNS = [
@@ -13775,7 +14964,7 @@ async function checkSecrets(files, projectRoot) {
13775
14964
  if (shouldSkip2(file.filePath) || isTestFile5(file.filePath)) continue;
13776
14965
  let content;
13777
14966
  try {
13778
- content = readFileSync19(join23(projectRoot, file.filePath), "utf-8");
14967
+ content = readFileSync20(join25(projectRoot, file.filePath), "utf-8");
13779
14968
  } catch {
13780
14969
  continue;
13781
14970
  }
@@ -13808,8 +14997,8 @@ async function checkSecrets(files, projectRoot) {
13808
14997
  }
13809
14998
 
13810
14999
  // src/security/checks/path-traversal.ts
13811
- import { readFileSync as readFileSync20 } from "fs";
13812
- import { join as join24 } from "path";
15000
+ import { readFileSync as readFileSync21 } from "fs";
15001
+ import { join as join26 } from "path";
13813
15002
  var SKIP_DIRS3 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
13814
15003
  var USER_INPUT_VARS = /(?:req\.|params|query|body|input|path|dir|subdirectory|file|userInput|fileName|filePath)/i;
13815
15004
  var PATTERNS2 = [
@@ -13856,7 +15045,7 @@ async function checkPathTraversal(files, projectRoot) {
13856
15045
  if (shouldSkip3(file.filePath)) continue;
13857
15046
  let content;
13858
15047
  try {
13859
- content = readFileSync20(join24(projectRoot, file.filePath), "utf-8");
15048
+ content = readFileSync21(join26(projectRoot, file.filePath), "utf-8");
13860
15049
  } catch {
13861
15050
  continue;
13862
15051
  }
@@ -13898,8 +15087,8 @@ async function checkPathTraversal(files, projectRoot) {
13898
15087
  }
13899
15088
 
13900
15089
  // src/security/checks/auth.ts
13901
- import { readFileSync as readFileSync21 } from "fs";
13902
- import { join as join25 } from "path";
15090
+ import { readFileSync as readFileSync22 } from "fs";
15091
+ import { join as join27 } from "path";
13903
15092
  var SKIP_DIRS4 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
13904
15093
  function shouldSkip4(filePath) {
13905
15094
  return SKIP_DIRS4.some((d) => filePath.includes(d));
@@ -13915,7 +15104,7 @@ async function checkAuth(files, projectRoot) {
13915
15104
  if (shouldSkip4(file.filePath)) continue;
13916
15105
  let content;
13917
15106
  try {
13918
- content = readFileSync21(join25(projectRoot, file.filePath), "utf-8");
15107
+ content = readFileSync22(join27(projectRoot, file.filePath), "utf-8");
13919
15108
  } catch {
13920
15109
  continue;
13921
15110
  }
@@ -14006,8 +15195,8 @@ async function checkAuth(files, projectRoot) {
14006
15195
  }
14007
15196
 
14008
15197
  // src/security/checks/input-validation.ts
14009
- import { readFileSync as readFileSync22 } from "fs";
14010
- import { join as join26 } from "path";
15198
+ import { readFileSync as readFileSync23 } from "fs";
15199
+ import { join as join28 } from "path";
14011
15200
  var SKIP_DIRS5 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
14012
15201
  function shouldSkip5(filePath) {
14013
15202
  return SKIP_DIRS5.some((d) => filePath.includes(d));
@@ -14019,7 +15208,7 @@ async function checkInputValidation(files, projectRoot) {
14019
15208
  if (shouldSkip5(file.filePath)) continue;
14020
15209
  let content;
14021
15210
  try {
14022
- content = readFileSync22(join26(projectRoot, file.filePath), "utf-8");
15211
+ content = readFileSync23(join28(projectRoot, file.filePath), "utf-8");
14023
15212
  } catch {
14024
15213
  continue;
14025
15214
  }
@@ -14096,8 +15285,8 @@ async function checkInputValidation(files, projectRoot) {
14096
15285
  }
14097
15286
 
14098
15287
  // src/security/checks/information-disclosure.ts
14099
- import { readFileSync as readFileSync23 } from "fs";
14100
- import { join as join27 } from "path";
15288
+ import { readFileSync as readFileSync24 } from "fs";
15289
+ import { join as join29 } from "path";
14101
15290
  var SKIP_DIRS6 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
14102
15291
  function shouldSkip6(filePath) {
14103
15292
  return SKIP_DIRS6.some((d) => filePath.includes(d));
@@ -14109,7 +15298,7 @@ async function checkInformationDisclosure(files, projectRoot) {
14109
15298
  if (shouldSkip6(file.filePath)) continue;
14110
15299
  let content;
14111
15300
  try {
14112
- content = readFileSync23(join27(projectRoot, file.filePath), "utf-8");
15301
+ content = readFileSync24(join29(projectRoot, file.filePath), "utf-8");
14113
15302
  } catch {
14114
15303
  continue;
14115
15304
  }
@@ -14179,8 +15368,8 @@ async function checkInformationDisclosure(files, projectRoot) {
14179
15368
  }
14180
15369
 
14181
15370
  // src/security/checks/cryptography.ts
14182
- import { readFileSync as readFileSync24 } from "fs";
14183
- import { join as join28 } from "path";
15371
+ import { readFileSync as readFileSync25 } from "fs";
15372
+ import { join as join30 } from "path";
14184
15373
  var SKIP_DIRS7 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
14185
15374
  var USER_INPUT_NAMES2 = /(?:input|user|name|path|query|param|request|body|args|url)/i;
14186
15375
  function shouldSkip7(filePath) {
@@ -14197,7 +15386,7 @@ async function checkCryptography(files, projectRoot) {
14197
15386
  if (shouldSkip7(file.filePath)) continue;
14198
15387
  let content;
14199
15388
  try {
14200
- content = readFileSync24(join28(projectRoot, file.filePath), "utf-8");
15389
+ content = readFileSync25(join30(projectRoot, file.filePath), "utf-8");
14201
15390
  } catch {
14202
15391
  continue;
14203
15392
  }
@@ -14455,6 +15644,125 @@ async function checkCryptography(files, projectRoot) {
14455
15644
  suggestedFix: "Load credentials from environment variables using getenv() or $_ENV."
14456
15645
  });
14457
15646
  }
15647
+ if (/\bCC_MD5\b/.test(line) || /Insecure\s*\.\s*MD5/.test(line)) {
15648
+ findings.push({
15649
+ id: "",
15650
+ severity: isCryptoFile ? "high" : "medium",
15651
+ vulnerabilityClass: "cryptography",
15652
+ file: file.filePath,
15653
+ line: i + 1,
15654
+ title: "Weak hash algorithm: MD5 in Swift",
15655
+ description: "MD5 is cryptographically broken \u2014 collisions can be generated in seconds.",
15656
+ attackScenario: "An attacker could generate MD5 collisions to bypass integrity checks or forge hashes.",
15657
+ suggestedFix: "Use SHA256 from CryptoKit: SHA256.hash(data: data)"
15658
+ });
15659
+ }
15660
+ if (/\bCC_SHA1\b/.test(line) || /Insecure\s*\.\s*SHA1/.test(line)) {
15661
+ findings.push({
15662
+ id: "",
15663
+ severity: isCryptoFile ? "high" : "medium",
15664
+ vulnerabilityClass: "cryptography",
15665
+ file: file.filePath,
15666
+ line: i + 1,
15667
+ title: "Weak hash algorithm: SHA-1 in Swift",
15668
+ description: "SHA-1 has known collision attacks \u2014 should not be used for security purposes.",
15669
+ attackScenario: "An attacker could generate SHA-1 collisions to bypass integrity checks.",
15670
+ suggestedFix: "Use SHA256 from CryptoKit: SHA256.hash(data: data)"
15671
+ });
15672
+ }
15673
+ if (/(?:let|var)\s+(?:password|secret|apiKey|api_key|token)\s*(?::\s*String\s*)?=\s*["']/i.test(line)) {
15674
+ findings.push({
15675
+ id: "",
15676
+ severity: "high",
15677
+ vulnerabilityClass: "cryptography",
15678
+ file: file.filePath,
15679
+ line: i + 1,
15680
+ title: "Hardcoded credentials in Swift source",
15681
+ description: "A password, secret, or API key is hardcoded as a string literal.",
15682
+ attackScenario: "An attacker with access to the binary or source could extract the credential.",
15683
+ suggestedFix: "Load credentials from Keychain, environment variables, or a secure configuration service."
15684
+ });
15685
+ }
15686
+ if (/\barc4random\b/.test(line) && isCryptoFile) {
15687
+ findings.push({
15688
+ id: "",
15689
+ severity: "medium",
15690
+ vulnerabilityClass: "cryptography",
15691
+ file: file.filePath,
15692
+ line: i + 1,
15693
+ title: "arc4random in Swift security context",
15694
+ description: "arc4random is not suitable for cryptographic key generation.",
15695
+ attackScenario: "An attacker could predict random values if used for cryptographic purposes.",
15696
+ suggestedFix: "Use SecRandomCopyBytes or SystemRandomNumberGenerator for security-sensitive randomness."
15697
+ });
15698
+ }
15699
+ if (/allowsArbitraryLoads\s*:\s*true/.test(line) || /NSAllowsArbitraryLoads.*true/.test(line)) {
15700
+ findings.push({
15701
+ id: "",
15702
+ severity: "medium",
15703
+ vulnerabilityClass: "cryptography",
15704
+ file: file.filePath,
15705
+ line: i + 1,
15706
+ title: "Swift App Transport Security disabled",
15707
+ description: "allowsArbitraryLoads is set to true \u2014 all HTTP traffic is permitted without encryption.",
15708
+ attackScenario: "An attacker on the network path could intercept, read, or modify data in transit.",
15709
+ suggestedFix: "Remove allowsArbitraryLoads or set to false. Add specific exceptions only for domains that require HTTP."
15710
+ });
15711
+ }
15712
+ if (/(?:let|var)\s+\w*[Uu]rl\w*\s*=\s*["']http:\/\/(?!(?:localhost|127\.))/.test(line)) {
15713
+ findings.push({
15714
+ id: "",
15715
+ severity: "medium",
15716
+ vulnerabilityClass: "cryptography",
15717
+ file: file.filePath,
15718
+ line: i + 1,
15719
+ title: "Hardcoded HTTP URL in Swift source",
15720
+ description: "An HTTP (not HTTPS) URL is hardcoded \u2014 data is transmitted unencrypted.",
15721
+ attackScenario: "An attacker on the network path could intercept, read, or modify data in transit.",
15722
+ suggestedFix: "Use HTTPS for all external URLs to ensure data confidentiality and integrity."
15723
+ });
15724
+ }
15725
+ if (/from\s+python\s+import\s+random/.test(line)) {
15726
+ findings.push({
15727
+ id: "",
15728
+ severity: "medium",
15729
+ vulnerabilityClass: "cryptography",
15730
+ file: file.filePath,
15731
+ line: i + 1,
15732
+ title: "Mojo weak random via Python random module",
15733
+ description: "Python random module imported via Mojo interop \u2014 not cryptographically secure.",
15734
+ attackScenario: "An attacker could predict random values to forge tokens or bypass security checks.",
15735
+ suggestedFix: "Use Python secrets module through interop, or implement CSPRNG natively in Mojo."
15736
+ });
15737
+ }
15738
+ if (/\balias\s+(?:key|secret|password|token|api_key)\s*[=:]\s*["'][^"']{4,}["']/i.test(line)) {
15739
+ findings.push({
15740
+ id: "",
15741
+ severity: "high",
15742
+ vulnerabilityClass: "cryptography",
15743
+ file: file.filePath,
15744
+ line: i + 1,
15745
+ title: "Hardcoded credentials in Mojo alias declaration",
15746
+ description: "A secret, key, or password is hardcoded in a compile-time alias \u2014 visible in source.",
15747
+ attackScenario: "An attacker with access to source or compiled binary could extract the credential.",
15748
+ suggestedFix: "Load credentials from environment variables at runtime instead of alias declarations."
15749
+ });
15750
+ }
15751
+ if (/from\s+python\s+import\s+hashlib/.test(line)) {
15752
+ if (isCryptoFile) {
15753
+ findings.push({
15754
+ id: "",
15755
+ severity: "medium",
15756
+ vulnerabilityClass: "cryptography",
15757
+ file: file.filePath,
15758
+ line: i + 1,
15759
+ title: "Mojo Python hashlib imported in security context",
15760
+ description: "Python hashlib imported via interop \u2014 ensure only strong algorithms (SHA-256+) are used.",
15761
+ attackScenario: "If MD5 or SHA-1 from hashlib is used, an attacker could exploit weak hash collisions.",
15762
+ suggestedFix: "Only use hashlib.sha256() or stronger. Avoid md5() and sha1() for security purposes."
15763
+ });
15764
+ }
15765
+ }
14458
15766
  }
14459
15767
  }
14460
15768
  } catch {
@@ -14463,8 +15771,8 @@ async function checkCryptography(files, projectRoot) {
14463
15771
  }
14464
15772
 
14465
15773
  // src/security/checks/frontend.ts
14466
- import { readFileSync as readFileSync25 } from "fs";
14467
- import { join as join29 } from "path";
15774
+ import { readFileSync as readFileSync26 } from "fs";
15775
+ import { join as join31 } from "path";
14468
15776
  var SKIP_DIRS8 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
14469
15777
  function shouldSkip8(filePath) {
14470
15778
  return SKIP_DIRS8.some((d) => filePath.includes(d));
@@ -14480,7 +15788,7 @@ async function checkFrontend(files, projectRoot) {
14480
15788
  if (!isFrontendFile(file.filePath)) continue;
14481
15789
  let content;
14482
15790
  try {
14483
- content = readFileSync25(join29(projectRoot, file.filePath), "utf-8");
15791
+ content = readFileSync26(join31(projectRoot, file.filePath), "utf-8");
14484
15792
  } catch {
14485
15793
  continue;
14486
15794
  }
@@ -14940,13 +16248,13 @@ async function scanSecurity(projectRoot, graph, options = {}) {
14940
16248
  };
14941
16249
  }
14942
16250
  function detectPackageManager(projectRoot) {
14943
- if (existsSync19(join30(projectRoot, "package.json"))) return "npm";
14944
- if (existsSync19(join30(projectRoot, "requirements.txt"))) return "pip";
14945
- if (existsSync19(join30(projectRoot, "pyproject.toml"))) return "pip";
14946
- if (existsSync19(join30(projectRoot, "Cargo.toml"))) return "cargo";
14947
- if (existsSync19(join30(projectRoot, "go.mod"))) return "go";
14948
- if (existsSync19(join30(projectRoot, "pom.xml"))) return "maven";
14949
- if (existsSync19(join30(projectRoot, "build.gradle")) || existsSync19(join30(projectRoot, "build.gradle.kts"))) return "gradle";
16251
+ if (existsSync20(join32(projectRoot, "package.json"))) return "npm";
16252
+ if (existsSync20(join32(projectRoot, "requirements.txt"))) return "pip";
16253
+ if (existsSync20(join32(projectRoot, "pyproject.toml"))) return "pip";
16254
+ if (existsSync20(join32(projectRoot, "Cargo.toml"))) return "cargo";
16255
+ if (existsSync20(join32(projectRoot, "go.mod"))) return "go";
16256
+ if (existsSync20(join32(projectRoot, "pom.xml"))) return "maven";
16257
+ if (existsSync20(join32(projectRoot, "build.gradle")) || existsSync20(join32(projectRoot, "build.gradle.kts"))) return "gradle";
14950
16258
  return "unknown";
14951
16259
  }
14952
16260