depwire-cli 1.1.0 → 1.1.1

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,9 @@ 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");
40
41
  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) {
42
+ if (isTypeScript || isJavaScript || isPython || isGo || isRust || isC || isCpp || isCSharp || isJava || isKotlin || isPhp || isSwift || isCppBuild) {
42
43
  files.push(relative(rootDir, fullPath));
43
44
  }
44
45
  }
@@ -84,6 +85,8 @@ function findProjectRoot(startDir = process.cwd()) {
84
85
  // Kotlin (Gradle KTS)
85
86
  "composer.json",
86
87
  // PHP
88
+ "Package.swift",
89
+ // Swift (SPM)
87
90
  ".git"
88
91
  // Any git repo
89
92
  ];
@@ -117,11 +120,11 @@ function findProjectRoot(startDir = process.cwd()) {
117
120
  }
118
121
 
119
122
  // src/parser/index.ts
120
- import { readFileSync as readFileSync10, statSync as statSync7 } from "fs";
121
- import { join as join13, resolve as resolve7 } from "path";
123
+ import { readFileSync as readFileSync11, statSync as statSync8 } from "fs";
124
+ import { join as join14, resolve as resolve8 } from "path";
122
125
 
123
126
  // src/parser/detect.ts
124
- import { extname as extname8, basename as basename6 } from "path";
127
+ import { extname as extname9, basename as basename7 } from "path";
125
128
 
126
129
  // src/parser/wasm-init.ts
127
130
  import { Parser, Language } from "web-tree-sitter";
@@ -153,7 +156,8 @@ async function initParser() {
153
156
  "java": "tree-sitter-java.wasm",
154
157
  "cpp": "tree-sitter-cpp.wasm",
155
158
  "kotlin": "tree-sitter-kotlin.wasm",
156
- "php": "tree-sitter-php.wasm"
159
+ "php": "tree-sitter-php.wasm",
160
+ "swift": "tree-sitter-swift.wasm"
157
161
  };
158
162
  for (const [name, file] of Object.entries(grammarFiles)) {
159
163
  const wasmPath = path.join(grammarsDir, file);
@@ -6151,13 +6155,601 @@ function resolvePhpInclude(includePath, currentFile, projectRoot) {
6151
6155
  if (existsSync12(relativePath)) {
6152
6156
  return relativeToRoot;
6153
6157
  }
6154
- const fromRoot = join12(projectRoot, includePath);
6155
- if (existsSync12(fromRoot)) {
6156
- return includePath;
6158
+ const fromRoot = join12(projectRoot, includePath);
6159
+ if (existsSync12(fromRoot)) {
6160
+ return includePath;
6161
+ }
6162
+ return null;
6163
+ }
6164
+ function resolveSymbol10(name, context) {
6165
+ if (context.imports.has(name)) {
6166
+ return context.imports.get(name) || null;
6167
+ }
6168
+ const currentFileId = `${context.filePath}::${name}`;
6169
+ if (context.symbols.find((s) => s.id === currentFileId)) {
6170
+ return currentFileId;
6171
+ }
6172
+ if (context.currentClass) {
6173
+ const classMethodId = `${context.filePath}::${context.currentClass}.${name}`;
6174
+ if (context.symbols.find((s) => s.id === classMethodId)) {
6175
+ return classMethodId;
6176
+ }
6177
+ }
6178
+ return null;
6179
+ }
6180
+ function getModifiers2(node, context) {
6181
+ const modifiers = [];
6182
+ for (let i = 0; i < node.childCount; i++) {
6183
+ const child = node.child(i);
6184
+ if (!child) continue;
6185
+ const type = child.type;
6186
+ if (type === "visibility_modifier" || type === "static_modifier" || type === "abstract_modifier" || type === "final_modifier" || type === "readonly_modifier") {
6187
+ modifiers.push(nodeText10(child, context).trim());
6188
+ }
6189
+ }
6190
+ return modifiers;
6191
+ }
6192
+ function extractQualifiedName(node, context) {
6193
+ const nameNode = findDescendantByTypes3(node, ["name", "qualified_name", "namespace_name"]);
6194
+ if (!nameNode) return null;
6195
+ const text = nodeText10(nameNode, context).trim();
6196
+ if (!text) return null;
6197
+ const parts = text.split("\\");
6198
+ return parts[parts.length - 1];
6199
+ }
6200
+ function findChildByType11(node, type) {
6201
+ for (let i = 0; i < node.childCount; i++) {
6202
+ const child = node.child(i);
6203
+ if (child && child.type === type) return child;
6204
+ }
6205
+ return null;
6206
+ }
6207
+ function findChildrenByType5(node, type) {
6208
+ const results = [];
6209
+ for (let i = 0; i < node.childCount; i++) {
6210
+ const child = node.child(i);
6211
+ if (child && child.type === type) results.push(child);
6212
+ }
6213
+ return results;
6214
+ }
6215
+ function findDescendantByTypes3(node, types) {
6216
+ for (let i = 0; i < node.childCount; i++) {
6217
+ const child = node.child(i);
6218
+ if (!child) continue;
6219
+ if (types.includes(child.type)) return child;
6220
+ const found = findDescendantByTypes3(child, types);
6221
+ if (found) return found;
6222
+ }
6223
+ return null;
6224
+ }
6225
+ function nodeText10(node, context) {
6226
+ return context.sourceCode.substring(node.startIndex, node.endIndex);
6227
+ }
6228
+ function getCurrentSymbolId11(context) {
6229
+ if (context.currentScope.length === 0) return null;
6230
+ return `${context.filePath}::${context.currentScope[context.currentScope.length - 1]}`;
6231
+ }
6232
+ var phpParser = {
6233
+ name: "php",
6234
+ extensions: [".php"],
6235
+ parseFile: parsePhpFile
6236
+ };
6237
+
6238
+ // src/parser/swift.ts
6239
+ import { dirname as dirname12, join as join13, basename as basename6 } from "path";
6240
+ import { existsSync as existsSync13, readdirSync as readdirSync10, statSync as statSync7 } from "fs";
6241
+ function parseSwiftFile(filePath, sourceCode, projectRoot) {
6242
+ if (filePath.endsWith("Package.swift")) {
6243
+ return parsePackageSwift(filePath, sourceCode, projectRoot);
6244
+ }
6245
+ const parser = getParser("swift");
6246
+ const tree = parser.parse(sourceCode, null, { bufferSize: 1024 * 1024 });
6247
+ const context = {
6248
+ filePath,
6249
+ projectRoot,
6250
+ sourceCode,
6251
+ symbols: [],
6252
+ edges: [],
6253
+ currentScope: [],
6254
+ currentClass: null,
6255
+ currentModule: null,
6256
+ imports: /* @__PURE__ */ new Map(),
6257
+ isPackageFile: false
6258
+ };
6259
+ walkNode12(tree.rootNode, context);
6260
+ return {
6261
+ filePath,
6262
+ symbols: context.symbols,
6263
+ edges: context.edges
6264
+ };
6265
+ }
6266
+ function walkNode12(node, context) {
6267
+ const handled = processNode12(node, context);
6268
+ if (handled) return;
6269
+ for (let i = 0; i < node.childCount; i++) {
6270
+ const child = node.child(i);
6271
+ if (child) {
6272
+ walkNode12(child, context);
6273
+ }
6274
+ }
6275
+ }
6276
+ function processNode12(node, context) {
6277
+ switch (node.type) {
6278
+ case "import_declaration":
6279
+ processImportDeclaration3(node, context);
6280
+ return false;
6281
+ case "class_declaration":
6282
+ processClassDeclaration7(node, context);
6283
+ return true;
6284
+ // handles its own children
6285
+ case "protocol_declaration":
6286
+ processProtocolDeclaration(node, context);
6287
+ return true;
6288
+ // handles its own children
6289
+ case "function_declaration":
6290
+ processFunctionDeclaration5(node, context);
6291
+ return true;
6292
+ // handles its own children
6293
+ case "init_declaration":
6294
+ processInitDeclaration(node, context);
6295
+ return true;
6296
+ case "deinit_declaration":
6297
+ processDeinitDeclaration(node, context);
6298
+ return true;
6299
+ case "property_declaration":
6300
+ case "variable_declaration":
6301
+ processPropertyDeclaration4(node, context);
6302
+ return false;
6303
+ case "typealias_declaration":
6304
+ processTypealiasDeclaration(node, context);
6305
+ return false;
6306
+ case "associatedtype_declaration":
6307
+ processAssociatedTypeDeclaration(node, context);
6308
+ return false;
6309
+ case "call_expression":
6310
+ processCallExpression12(node, context);
6311
+ return false;
6312
+ default:
6313
+ return false;
6314
+ }
6315
+ }
6316
+ function processImportDeclaration3(node, context) {
6317
+ const text = nodeText11(node, context).trim();
6318
+ const match = text.match(/^import\s+(?:(?:typealias|struct|class|enum|protocol|let|var|func)\s+)?(.+)$/);
6319
+ if (!match) return;
6320
+ const importPath = match[1].trim();
6321
+ const resolvedPath = resolveSwiftImport(importPath, context.filePath, context.projectRoot);
6322
+ if (resolvedPath) {
6323
+ const sourceId = `${context.filePath}::__file__`;
6324
+ const targetId = `${resolvedPath}::__file__`;
6325
+ context.edges.push({
6326
+ source: sourceId,
6327
+ target: targetId,
6328
+ kind: "imports",
6329
+ filePath: context.filePath,
6330
+ line: node.startPosition.row + 1
6331
+ });
6332
+ const parts = importPath.split(".");
6333
+ const simpleName = parts[parts.length - 1];
6334
+ context.imports.set(simpleName, `${resolvedPath}::${simpleName}`);
6335
+ }
6336
+ const symbolId = `${context.filePath}::import:${importPath}`;
6337
+ context.symbols.push({
6338
+ id: symbolId,
6339
+ name: importPath,
6340
+ kind: "import",
6341
+ filePath: context.filePath,
6342
+ startLine: node.startPosition.row + 1,
6343
+ endLine: node.endPosition.row + 1,
6344
+ exported: false
6345
+ });
6346
+ }
6347
+ function processClassDeclaration7(node, context) {
6348
+ let keyword = "class";
6349
+ for (let i = 0; i < node.childCount; i++) {
6350
+ const child = node.child(i);
6351
+ if (child && ["class", "struct", "actor", "enum", "extension"].includes(child.type)) {
6352
+ keyword = child.type;
6353
+ break;
6354
+ }
6355
+ }
6356
+ if (keyword === "extension") {
6357
+ const typeNode = findChildByType12(node, "user_type") || findChildByType12(node, "type_identifier");
6358
+ const extName = typeNode ? nodeText11(typeNode, context).trim() : "Unknown";
6359
+ const name2 = `${extName}+ext`;
6360
+ const symbolId2 = `${context.filePath}::${name2}`;
6361
+ context.symbols.push({
6362
+ id: symbolId2,
6363
+ name: name2,
6364
+ kind: "class",
6365
+ filePath: context.filePath,
6366
+ startLine: node.startPosition.row + 1,
6367
+ endLine: node.endPosition.row + 1,
6368
+ exported: true
6369
+ });
6370
+ const oldClass2 = context.currentClass;
6371
+ context.currentClass = extName;
6372
+ context.currentScope.push(extName);
6373
+ const body2 = findChildByType12(node, "class_body");
6374
+ if (body2) {
6375
+ walkNode12(body2, context);
6376
+ }
6377
+ context.currentScope.pop();
6378
+ context.currentClass = oldClass2;
6379
+ return;
6380
+ }
6381
+ const nameNode = findChildByType12(node, "type_identifier") || findChildByType12(node, "simple_identifier");
6382
+ if (!nameNode) return;
6383
+ const name = nodeText11(nameNode, context);
6384
+ const modifiers = getModifiers3(node, context);
6385
+ const exported = !modifiers.includes("private") && !modifiers.includes("fileprivate");
6386
+ const scope = context.currentClass || void 0;
6387
+ const symbolId = `${context.filePath}::${name}`;
6388
+ let kind = "class";
6389
+ if (keyword === "enum") kind = "enum";
6390
+ else if (keyword === "struct" || keyword === "actor") kind = "class";
6391
+ context.symbols.push({
6392
+ id: symbolId,
6393
+ name,
6394
+ kind,
6395
+ filePath: context.filePath,
6396
+ startLine: node.startPosition.row + 1,
6397
+ endLine: node.endPosition.row + 1,
6398
+ exported,
6399
+ scope
6400
+ });
6401
+ processInheritance(node, symbolId, context);
6402
+ if (keyword === "enum") {
6403
+ processEnumCases(node, name, context);
6404
+ }
6405
+ const oldClass = context.currentClass;
6406
+ context.currentClass = name;
6407
+ context.currentScope.push(name);
6408
+ const body = findChildByType12(node, "class_body");
6409
+ if (body) {
6410
+ walkNode12(body, context);
6411
+ }
6412
+ context.currentScope.pop();
6413
+ context.currentClass = oldClass;
6414
+ }
6415
+ function processProtocolDeclaration(node, context) {
6416
+ const nameNode = findChildByType12(node, "type_identifier") || findChildByType12(node, "simple_identifier");
6417
+ if (!nameNode) return;
6418
+ const name = nodeText11(nameNode, context);
6419
+ const modifiers = getModifiers3(node, context);
6420
+ const exported = !modifiers.includes("private") && !modifiers.includes("fileprivate");
6421
+ const symbolId = `${context.filePath}::${name}`;
6422
+ context.symbols.push({
6423
+ id: symbolId,
6424
+ name,
6425
+ kind: "interface",
6426
+ filePath: context.filePath,
6427
+ startLine: node.startPosition.row + 1,
6428
+ endLine: node.endPosition.row + 1,
6429
+ exported
6430
+ });
6431
+ processInheritance(node, symbolId, context);
6432
+ const oldClass = context.currentClass;
6433
+ context.currentClass = name;
6434
+ context.currentScope.push(name);
6435
+ const body = findChildByType12(node, "protocol_body");
6436
+ if (body) {
6437
+ walkNode12(body, context);
6438
+ }
6439
+ context.currentScope.pop();
6440
+ context.currentClass = oldClass;
6441
+ }
6442
+ function processFunctionDeclaration5(node, context) {
6443
+ const nameNode = findChildByType12(node, "simple_identifier");
6444
+ if (!nameNode) return;
6445
+ const name = nodeText11(nameNode, context);
6446
+ const modifiers = getModifiers3(node, context);
6447
+ const exported = !modifiers.includes("private") && !modifiers.includes("fileprivate");
6448
+ const scope = context.currentClass || void 0;
6449
+ const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
6450
+ context.symbols.push({
6451
+ id: symbolId,
6452
+ name,
6453
+ kind: context.currentClass ? "method" : "function",
6454
+ filePath: context.filePath,
6455
+ startLine: node.startPosition.row + 1,
6456
+ endLine: node.endPosition.row + 1,
6457
+ exported,
6458
+ scope
6459
+ });
6460
+ const scopeName = scope ? `${scope}.${name}` : name;
6461
+ context.currentScope.push(scopeName);
6462
+ const body = findChildByType12(node, "function_body") || findChildByType12(node, "code_block");
6463
+ if (body) {
6464
+ walkNode12(body, context);
6465
+ }
6466
+ context.currentScope.pop();
6467
+ }
6468
+ function processInitDeclaration(node, context) {
6469
+ const scope = context.currentClass || void 0;
6470
+ if (!scope) return;
6471
+ const name = "init";
6472
+ const symbolId = `${context.filePath}::${scope}.${name}:${node.startPosition.row + 1}`;
6473
+ context.symbols.push({
6474
+ id: symbolId,
6475
+ name,
6476
+ kind: "method",
6477
+ filePath: context.filePath,
6478
+ startLine: node.startPosition.row + 1,
6479
+ endLine: node.endPosition.row + 1,
6480
+ exported: true,
6481
+ scope
6482
+ });
6483
+ const scopeName = `${scope}.${name}`;
6484
+ context.currentScope.push(scopeName);
6485
+ const body = findChildByType12(node, "function_body") || findChildByType12(node, "code_block");
6486
+ if (body) {
6487
+ walkNode12(body, context);
6488
+ }
6489
+ context.currentScope.pop();
6490
+ }
6491
+ function processDeinitDeclaration(node, context) {
6492
+ const scope = context.currentClass || void 0;
6493
+ if (!scope) return;
6494
+ const name = "deinit";
6495
+ const symbolId = `${context.filePath}::${scope}.${name}`;
6496
+ context.symbols.push({
6497
+ id: symbolId,
6498
+ name,
6499
+ kind: "method",
6500
+ filePath: context.filePath,
6501
+ startLine: node.startPosition.row + 1,
6502
+ endLine: node.endPosition.row + 1,
6503
+ exported: true,
6504
+ scope
6505
+ });
6506
+ }
6507
+ function processPropertyDeclaration4(node, context) {
6508
+ const nameNode = findChildByType12(node, "simple_identifier") || findChildByType12(node, "pattern");
6509
+ if (!nameNode) return;
6510
+ const name = nodeText11(nameNode, context).trim();
6511
+ if (!name || name.includes(" ")) return;
6512
+ const modifiers = getModifiers3(node, context);
6513
+ const exported = !modifiers.includes("private") && !modifiers.includes("fileprivate");
6514
+ const scope = context.currentClass || void 0;
6515
+ const text = nodeText11(node, context);
6516
+ const isConst = text.trimStart().startsWith("let");
6517
+ const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
6518
+ context.symbols.push({
6519
+ id: symbolId,
6520
+ name,
6521
+ kind: isConst ? "constant" : "property",
6522
+ filePath: context.filePath,
6523
+ startLine: node.startPosition.row + 1,
6524
+ endLine: node.endPosition.row + 1,
6525
+ exported,
6526
+ scope
6527
+ });
6528
+ }
6529
+ function processTypealiasDeclaration(node, context) {
6530
+ const nameNode = findChildByType12(node, "type_identifier") || findChildByType12(node, "simple_identifier");
6531
+ if (!nameNode) return;
6532
+ const name = nodeText11(nameNode, context);
6533
+ const symbolId = `${context.filePath}::${name}`;
6534
+ context.symbols.push({
6535
+ id: symbolId,
6536
+ name,
6537
+ kind: "type_alias",
6538
+ filePath: context.filePath,
6539
+ startLine: node.startPosition.row + 1,
6540
+ endLine: node.endPosition.row + 1,
6541
+ exported: true
6542
+ });
6543
+ }
6544
+ function processAssociatedTypeDeclaration(node, context) {
6545
+ const nameNode = findChildByType12(node, "type_identifier") || findChildByType12(node, "simple_identifier");
6546
+ if (!nameNode) return;
6547
+ const name = nodeText11(nameNode, context);
6548
+ const scope = context.currentClass || void 0;
6549
+ const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
6550
+ context.symbols.push({
6551
+ id: symbolId,
6552
+ name,
6553
+ kind: "type_alias",
6554
+ filePath: context.filePath,
6555
+ startLine: node.startPosition.row + 1,
6556
+ endLine: node.endPosition.row + 1,
6557
+ exported: true,
6558
+ scope
6559
+ });
6560
+ }
6561
+ function processCallExpression12(node, context) {
6562
+ if (context.currentScope.length === 0) return;
6563
+ const firstChild = node.child(0);
6564
+ if (!firstChild) return;
6565
+ let calleeName = null;
6566
+ if (firstChild.type === "simple_identifier") {
6567
+ calleeName = nodeText11(firstChild, context);
6568
+ } else if (firstChild.type === "navigation_expression" || firstChild.type === "member_access") {
6569
+ for (let i = firstChild.childCount - 1; i >= 0; i--) {
6570
+ const child = firstChild.child(i);
6571
+ if (child && (child.type === "simple_identifier" || child.type === "navigation_suffix")) {
6572
+ calleeName = nodeText11(child, context).replace(/^\./, "");
6573
+ break;
6574
+ }
6575
+ }
6576
+ }
6577
+ if (!calleeName) return;
6578
+ const builtins = /* @__PURE__ */ new Set([
6579
+ "print",
6580
+ "debugPrint",
6581
+ "dump",
6582
+ "fatalError",
6583
+ "precondition",
6584
+ "assert",
6585
+ "preconditionFailure",
6586
+ "assertionFailure",
6587
+ "map",
6588
+ "filter",
6589
+ "reduce",
6590
+ "forEach",
6591
+ "flatMap",
6592
+ "compactMap",
6593
+ "sorted",
6594
+ "contains",
6595
+ "first",
6596
+ "last",
6597
+ "count",
6598
+ "isEmpty",
6599
+ "append",
6600
+ "remove",
6601
+ "insert",
6602
+ "removeAll",
6603
+ "String",
6604
+ "Int",
6605
+ "Double",
6606
+ "Float",
6607
+ "Bool",
6608
+ "Array",
6609
+ "Dictionary",
6610
+ "Set",
6611
+ "DispatchQueue",
6612
+ "Task",
6613
+ "withCheckedContinuation",
6614
+ "withCheckedThrowingContinuation"
6615
+ ]);
6616
+ if (builtins.has(calleeName)) return;
6617
+ const callerId = getCurrentSymbolId12(context);
6618
+ if (!callerId) return;
6619
+ const calleeId = resolveSymbol11(calleeName, context);
6620
+ if (calleeId) {
6621
+ context.edges.push({
6622
+ source: callerId,
6623
+ target: calleeId,
6624
+ kind: "calls",
6625
+ filePath: context.filePath,
6626
+ line: node.startPosition.row + 1
6627
+ });
6628
+ }
6629
+ }
6630
+ function processInheritance(node, sourceId, context) {
6631
+ const inheritanceClause = findChildByType12(node, "inheritance_specifier") || findChildByType12(node, "type_inheritance_clause");
6632
+ if (!inheritanceClause) return;
6633
+ const text = nodeText11(node, context);
6634
+ const colonMatch = text.match(/:\s*([^{]+)/);
6635
+ if (!colonMatch) return;
6636
+ const types = colonMatch[1].split(",").map((t) => t.trim().split("<")[0].trim());
6637
+ for (const typeName of types) {
6638
+ if (!typeName || typeName.includes("{") || typeName.includes("where")) break;
6639
+ const baseId = resolveSymbol11(typeName, context);
6640
+ if (baseId) {
6641
+ context.edges.push({
6642
+ source: sourceId,
6643
+ target: baseId,
6644
+ kind: "implements",
6645
+ filePath: context.filePath,
6646
+ line: node.startPosition.row + 1
6647
+ });
6648
+ }
6649
+ }
6650
+ }
6651
+ function processEnumCases(node, enumName, context) {
6652
+ const text = nodeText11(node, context);
6653
+ const caseMatches = text.matchAll(/\bcase\s+(\w+)/g);
6654
+ for (const match of caseMatches) {
6655
+ const caseName = match[1];
6656
+ if (caseName === enumName) continue;
6657
+ const constId = `${context.filePath}::${enumName}.${caseName}`;
6658
+ context.symbols.push({
6659
+ id: constId,
6660
+ name: caseName,
6661
+ kind: "constant",
6662
+ filePath: context.filePath,
6663
+ startLine: node.startPosition.row + 1,
6664
+ endLine: node.endPosition.row + 1,
6665
+ exported: true,
6666
+ scope: enumName
6667
+ });
6668
+ }
6669
+ }
6670
+ function parsePackageSwift(filePath, sourceCode, projectRoot) {
6671
+ const symbols = [];
6672
+ const edges = [];
6673
+ const lines = sourceCode.split("\n");
6674
+ const nameMatch = sourceCode.match(/name\s*:\s*["']([^"']+)["']/);
6675
+ const packageName = nameMatch ? nameMatch[1] : basename6(dirname12(join13(projectRoot, filePath)));
6676
+ symbols.push({
6677
+ id: `${filePath}::${packageName}`,
6678
+ name: packageName,
6679
+ kind: "module",
6680
+ filePath,
6681
+ startLine: 1,
6682
+ endLine: lines.length,
6683
+ exported: true
6684
+ });
6685
+ for (let i = 0; i < lines.length; i++) {
6686
+ const line = lines[i].trim();
6687
+ const lineNum = i + 1;
6688
+ const depMatch = line.match(/\.package\s*\(\s*(?:url\s*:\s*)?["']([^"']+)["']/);
6689
+ if (depMatch) {
6690
+ const depUrl = depMatch[1];
6691
+ const depName = depUrl.split("/").pop()?.replace(/\.git$/, "") || depUrl;
6692
+ symbols.push({
6693
+ id: `${filePath}::dep:${depName}`,
6694
+ name: depName,
6695
+ kind: "import",
6696
+ filePath,
6697
+ startLine: lineNum,
6698
+ endLine: lineNum,
6699
+ exported: false
6700
+ });
6701
+ }
6702
+ const targetMatch = line.match(/\.(?:target|executableTarget|testTarget)\s*\(\s*name\s*:\s*["']([^"']+)["']/);
6703
+ if (targetMatch) {
6704
+ symbols.push({
6705
+ id: `${filePath}::target:${targetMatch[1]}`,
6706
+ name: targetMatch[1],
6707
+ kind: "module",
6708
+ filePath,
6709
+ startLine: lineNum,
6710
+ endLine: lineNum,
6711
+ exported: true
6712
+ });
6713
+ }
6714
+ }
6715
+ return { filePath, symbols, edges };
6716
+ }
6717
+ function resolveSwiftImport(importPath, currentFile, projectRoot) {
6718
+ const parts = importPath.split(".");
6719
+ const moduleName = parts[0];
6720
+ const sourceRoots = [
6721
+ "",
6722
+ "Sources",
6723
+ `Sources/${moduleName}`,
6724
+ "src",
6725
+ `src/${moduleName}`
6726
+ ];
6727
+ for (const root of sourceRoots) {
6728
+ const candidate = root ? join13(root, moduleName + ".swift") : moduleName + ".swift";
6729
+ const fullPath = join13(projectRoot, candidate);
6730
+ if (existsSync13(fullPath)) {
6731
+ return candidate;
6732
+ }
6733
+ }
6734
+ for (const root of sourceRoots) {
6735
+ const dirCandidate = root || moduleName;
6736
+ const fullDir = join13(projectRoot, dirCandidate);
6737
+ if (existsSync13(fullDir)) {
6738
+ try {
6739
+ const stats = statSync7(fullDir);
6740
+ if (stats.isDirectory()) {
6741
+ const swiftFiles = readdirSync10(fullDir).filter((f) => f.endsWith(".swift"));
6742
+ if (swiftFiles.length > 0) {
6743
+ return join13(dirCandidate, swiftFiles[0]);
6744
+ }
6745
+ }
6746
+ } catch {
6747
+ }
6748
+ }
6157
6749
  }
6158
6750
  return null;
6159
6751
  }
6160
- function resolveSymbol10(name, context) {
6752
+ function resolveSymbol11(name, context) {
6161
6753
  if (context.imports.has(name)) {
6162
6754
  return context.imports.get(name) || null;
6163
6755
  }
@@ -6173,62 +6765,44 @@ function resolveSymbol10(name, context) {
6173
6765
  }
6174
6766
  return null;
6175
6767
  }
6176
- function getModifiers2(node, context) {
6768
+ function getModifiers3(node, context) {
6177
6769
  const modifiers = [];
6178
- for (let i = 0; i < node.childCount; i++) {
6179
- const child = node.child(i);
6180
- if (!child) continue;
6181
- const type = child.type;
6182
- if (type === "visibility_modifier" || type === "static_modifier" || type === "abstract_modifier" || type === "final_modifier" || type === "readonly_modifier") {
6183
- modifiers.push(nodeText10(child, context).trim());
6770
+ const modList = findChildByType12(node, "modifiers") || findChildByType12(node, "modifier");
6771
+ if (modList) {
6772
+ for (let i = 0; i < modList.childCount; i++) {
6773
+ const child = modList.child(i);
6774
+ if (child) {
6775
+ const text2 = nodeText11(child, context).trim();
6776
+ if (text2) modifiers.push(text2);
6777
+ }
6184
6778
  }
6185
6779
  }
6780
+ const text = nodeText11(node, context);
6781
+ if (text.match(/\bprivate\b/)) modifiers.push("private");
6782
+ if (text.match(/\bfileprivate\b/)) modifiers.push("fileprivate");
6783
+ if (text.match(/\binternal\b/)) modifiers.push("internal");
6784
+ if (text.match(/\bpublic\b/)) modifiers.push("public");
6785
+ if (text.match(/\bopen\b/)) modifiers.push("open");
6186
6786
  return modifiers;
6187
6787
  }
6188
- function extractQualifiedName(node, context) {
6189
- const nameNode = findDescendantByTypes3(node, ["name", "qualified_name", "namespace_name"]);
6190
- if (!nameNode) return null;
6191
- const text = nodeText10(nameNode, context).trim();
6192
- if (!text) return null;
6193
- const parts = text.split("\\");
6194
- return parts[parts.length - 1];
6195
- }
6196
- function findChildByType11(node, type) {
6788
+ function findChildByType12(node, type) {
6197
6789
  for (let i = 0; i < node.childCount; i++) {
6198
6790
  const child = node.child(i);
6199
6791
  if (child && child.type === type) return child;
6200
6792
  }
6201
6793
  return null;
6202
6794
  }
6203
- function findChildrenByType5(node, type) {
6204
- const results = [];
6205
- for (let i = 0; i < node.childCount; i++) {
6206
- const child = node.child(i);
6207
- if (child && child.type === type) results.push(child);
6208
- }
6209
- return results;
6210
- }
6211
- function findDescendantByTypes3(node, types) {
6212
- for (let i = 0; i < node.childCount; i++) {
6213
- const child = node.child(i);
6214
- if (!child) continue;
6215
- if (types.includes(child.type)) return child;
6216
- const found = findDescendantByTypes3(child, types);
6217
- if (found) return found;
6218
- }
6219
- return null;
6220
- }
6221
- function nodeText10(node, context) {
6795
+ function nodeText11(node, context) {
6222
6796
  return context.sourceCode.substring(node.startIndex, node.endIndex);
6223
6797
  }
6224
- function getCurrentSymbolId11(context) {
6798
+ function getCurrentSymbolId12(context) {
6225
6799
  if (context.currentScope.length === 0) return null;
6226
6800
  return `${context.filePath}::${context.currentScope[context.currentScope.length - 1]}`;
6227
6801
  }
6228
- var phpParser = {
6229
- name: "php",
6230
- extensions: [".php"],
6231
- parseFile: parsePhpFile
6802
+ var swiftParser = {
6803
+ name: "swift",
6804
+ extensions: [".swift", "Package.swift"],
6805
+ parseFile: parseSwiftFile
6232
6806
  };
6233
6807
 
6234
6808
  // src/parser/detect.ts
@@ -6243,12 +6817,13 @@ var parsers = [
6243
6817
  javaParser,
6244
6818
  cppParser,
6245
6819
  kotlinParser,
6246
- phpParser
6820
+ phpParser,
6821
+ swiftParser
6247
6822
  ];
6248
6823
  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
6824
  function getParserForFile(filePath, content) {
6250
- const ext = extname8(filePath).toLowerCase();
6251
- const fileName = basename6(filePath);
6825
+ const ext = extname9(filePath).toLowerCase();
6826
+ const fileName = basename7(filePath);
6252
6827
  if (ext === ".h" && content) {
6253
6828
  if (CPP_KEYWORDS.test(content)) {
6254
6829
  return cppParser;
@@ -6266,7 +6841,7 @@ import { minimatch } from "minimatch";
6266
6841
  var MAX_FILE_SIZE = 1e6;
6267
6842
  function shouldParseFile(fullPath) {
6268
6843
  try {
6269
- const stats = statSync7(fullPath);
6844
+ const stats = statSync8(fullPath);
6270
6845
  if (stats.size > MAX_FILE_SIZE) {
6271
6846
  console.error(`[Parser] Skipping ${fullPath} \u2014 file too large (${(stats.size / 1024).toFixed(0)}KB)`);
6272
6847
  return false;
@@ -6284,8 +6859,8 @@ async function parseProject(projectRoot, options) {
6284
6859
  let errorFiles = 0;
6285
6860
  for (const file of files) {
6286
6861
  try {
6287
- const fullPath = join13(projectRoot, file);
6288
- if (!resolve7(fullPath).startsWith(resolve7(projectRoot))) {
6862
+ const fullPath = join14(projectRoot, file);
6863
+ if (!resolve8(fullPath).startsWith(resolve8(projectRoot))) {
6289
6864
  skippedFiles++;
6290
6865
  continue;
6291
6866
  }
@@ -6308,7 +6883,7 @@ async function parseProject(projectRoot, options) {
6308
6883
  if (options?.verbose) {
6309
6884
  console.error(`[Parser] Parsing: ${file}`);
6310
6885
  }
6311
- const sourceCode = readFileSync10(fullPath, "utf-8");
6886
+ const sourceCode = readFileSync11(fullPath, "utf-8");
6312
6887
  const parser = getParserForFile(file, sourceCode);
6313
6888
  if (!parser) {
6314
6889
  console.error(`No parser found for file: ${file}`);
@@ -6337,8 +6912,8 @@ async function parseProject(projectRoot, options) {
6337
6912
  }
6338
6913
 
6339
6914
  // src/cross-language/detectors/rest-api.ts
6340
- import { readFileSync as readFileSync11 } from "fs";
6341
- import { join as join14, resolve as resolve8 } from "path";
6915
+ import { readFileSync as readFileSync12 } from "fs";
6916
+ import { join as join15, resolve as resolve9 } from "path";
6342
6917
  function getLanguage(filePath) {
6343
6918
  if (filePath.endsWith(".ts") || filePath.endsWith(".tsx")) return "typescript";
6344
6919
  if (filePath.endsWith(".js") || filePath.endsWith(".jsx") || filePath.endsWith(".mjs") || filePath.endsWith(".cjs")) return "javascript";
@@ -6348,6 +6923,7 @@ function getLanguage(filePath) {
6348
6923
  if (filePath.endsWith(".java")) return "java";
6349
6924
  if (filePath.endsWith(".kt") || filePath.endsWith(".kts")) return "kotlin";
6350
6925
  if (filePath.endsWith(".php")) return "php";
6926
+ if (filePath.endsWith(".swift")) return "swift";
6351
6927
  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
6928
  return "unknown";
6353
6929
  }
@@ -6767,6 +7343,45 @@ function extractRouteDefinitions(source, filePath) {
6767
7343
  });
6768
7344
  }
6769
7345
  }
7346
+ if (lang === "swift") {
7347
+ const vaporMatch = line.match(/(?:app|router|routes)\s*\.\s*(get|post|put|delete|patch)\s*\(\s*["']([^"']+)["']/i);
7348
+ if (vaporMatch) {
7349
+ let path6 = vaporMatch[2];
7350
+ if (!path6.startsWith("/")) path6 = "/" + path6;
7351
+ routes.push({
7352
+ method: vaporMatch[1].toUpperCase(),
7353
+ path: path6,
7354
+ normalizedPath: normalizePath(path6),
7355
+ file: filePath,
7356
+ line: i + 1
7357
+ });
7358
+ }
7359
+ const hbMatch = line.match(/router\s*\.\s*(get|post|put|delete|patch)\s*\(\s*["']([^"']+)["']/i);
7360
+ if (hbMatch && !vaporMatch) {
7361
+ let path6 = hbMatch[2];
7362
+ if (!path6.startsWith("/")) path6 = "/" + path6;
7363
+ routes.push({
7364
+ method: hbMatch[1].toUpperCase(),
7365
+ path: path6,
7366
+ normalizedPath: normalizePath(path6),
7367
+ file: filePath,
7368
+ line: i + 1
7369
+ });
7370
+ }
7371
+ const perfectMatch = line.match(/routes\s*\.\s*add\s*\([^)]*uri\s*:\s*["']([^"']+)["']/);
7372
+ if (perfectMatch) {
7373
+ const path6 = perfectMatch[1].startsWith("/") ? perfectMatch[1] : "/" + perfectMatch[1];
7374
+ const methodMatch = line.match(/method\s*:\s*\.(\w+)/);
7375
+ const method = methodMatch ? methodMatch[1].toUpperCase() : "ANY";
7376
+ routes.push({
7377
+ method,
7378
+ path: path6,
7379
+ normalizedPath: normalizePath(path6),
7380
+ file: filePath,
7381
+ line: i + 1
7382
+ });
7383
+ }
7384
+ }
6770
7385
  if (lang === "cpp") {
6771
7386
  const crowMatch = line.match(/CROW_ROUTE\s*\(\s*\w+\s*,\s*"([^"]+)"/);
6772
7387
  if (crowMatch) {
@@ -6881,11 +7496,11 @@ function detectRestApiEdges(files, projectRoot) {
6881
7496
  const allCalls = [];
6882
7497
  const allRoutes = [];
6883
7498
  for (const file of files) {
6884
- const fullPath = join14(projectRoot, file.filePath);
6885
- if (!resolve8(fullPath).startsWith(resolve8(projectRoot))) continue;
7499
+ const fullPath = join15(projectRoot, file.filePath);
7500
+ if (!resolve9(fullPath).startsWith(resolve9(projectRoot))) continue;
6886
7501
  let source;
6887
7502
  try {
6888
- source = readFileSync11(fullPath, "utf-8");
7503
+ source = readFileSync12(fullPath, "utf-8");
6889
7504
  } catch {
6890
7505
  continue;
6891
7506
  }
@@ -6905,6 +7520,30 @@ function detectRestApiEdges(files, projectRoot) {
6905
7520
  }
6906
7521
  }
6907
7522
  }
7523
+ if (lang === "swift") {
7524
+ const swiftLines = source.split("\n");
7525
+ for (let i = 0; i < swiftLines.length; i++) {
7526
+ const line = swiftLines[i];
7527
+ const urlMatch = line.match(/URL\s*\(\s*string\s*:\s*["']([^"']+)["']/);
7528
+ if (urlMatch) {
7529
+ const path6 = urlMatch[1];
7530
+ if (isLocalApiPath(path6)) {
7531
+ const methodMatch = line.match(/httpMethod\s*=\s*["'](\w+)["']/);
7532
+ const method = methodMatch ? methodMatch[1].toUpperCase() : "GET";
7533
+ allCalls.push({ method, path: cleanPath(path6), file: file.filePath, line: i + 1 });
7534
+ }
7535
+ }
7536
+ const afMatch = line.match(/AF\s*\.\s*(?:request|upload|download)\s*\(\s*["']([^"']+)["']/);
7537
+ if (afMatch) {
7538
+ const path6 = afMatch[1];
7539
+ if (isLocalApiPath(path6)) {
7540
+ const methodMatch = line.match(/method\s*:\s*\.(\w+)/);
7541
+ const method = methodMatch ? methodMatch[1].toUpperCase() : "GET";
7542
+ allCalls.push({ method, path: cleanPath(path6), file: file.filePath, line: i + 1 });
7543
+ }
7544
+ }
7545
+ }
7546
+ }
6908
7547
  if (lang === "php") {
6909
7548
  const phpLines = source.split("\n");
6910
7549
  for (let i = 0; i < phpLines.length; i++) {
@@ -6961,8 +7600,8 @@ function detectRestApiEdges(files, projectRoot) {
6961
7600
  }
6962
7601
 
6963
7602
  // 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";
7603
+ import { readFileSync as readFileSync13 } from "fs";
7604
+ import { join as join16, resolve as resolve10, basename as basename8 } from "path";
6966
7605
  var SCRIPT_EXTENSIONS = [".py", ".js", ".ts", ".go", ".rs"];
6967
7606
  function getLanguage2(filePath) {
6968
7607
  if (filePath.endsWith(".ts") || filePath.endsWith(".tsx")) return "typescript";
@@ -7061,16 +7700,16 @@ function detectSubprocessEdges(files, projectRoot) {
7061
7700
  const knownFiles = new Set(files.map((f) => f.filePath));
7062
7701
  const basenameMap = /* @__PURE__ */ new Map();
7063
7702
  for (const f of files) {
7064
- const base = basename7(f.filePath);
7703
+ const base = basename8(f.filePath);
7065
7704
  if (!basenameMap.has(base)) basenameMap.set(base, []);
7066
7705
  basenameMap.get(base).push(f.filePath);
7067
7706
  }
7068
7707
  for (const file of files) {
7069
- const fullPath = join15(projectRoot, file.filePath);
7070
- if (!resolve9(fullPath).startsWith(resolve9(projectRoot))) continue;
7708
+ const fullPath = join16(projectRoot, file.filePath);
7709
+ if (!resolve10(fullPath).startsWith(resolve10(projectRoot))) continue;
7071
7710
  let source;
7072
7711
  try {
7073
- source = readFileSync12(fullPath, "utf-8");
7712
+ source = readFileSync13(fullPath, "utf-8");
7074
7713
  } catch {
7075
7714
  continue;
7076
7715
  }
@@ -7082,7 +7721,7 @@ function detectSubprocessEdges(files, projectRoot) {
7082
7721
  targetFile = call.calledFile;
7083
7722
  confidence = "high";
7084
7723
  } else {
7085
- const base = basename7(call.calledFile);
7724
+ const base = basename8(call.calledFile);
7086
7725
  const candidates = basenameMap.get(base);
7087
7726
  if (candidates && candidates.length > 0) {
7088
7727
  const exactCandidate = candidates.find((c) => c.endsWith(call.calledFile));
@@ -7461,7 +8100,7 @@ function getArchitectureSummary(graph) {
7461
8100
  }
7462
8101
 
7463
8102
  // src/health/metrics.ts
7464
- import { dirname as dirname12 } from "path";
8103
+ import { dirname as dirname13 } from "path";
7465
8104
  function scoreToGrade(score) {
7466
8105
  if (score >= 90) return "A";
7467
8106
  if (score >= 80) return "B";
@@ -7494,8 +8133,8 @@ function calculateCouplingScore(graph) {
7494
8133
  totalEdges++;
7495
8134
  fileConnections.set(sourceAttrs.filePath, (fileConnections.get(sourceAttrs.filePath) || 0) + 1);
7496
8135
  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];
8136
+ const sourceDir = dirname13(sourceAttrs.filePath).split("/")[0];
8137
+ const targetDir = dirname13(targetAttrs.filePath).split("/")[0];
7499
8138
  if (sourceDir !== targetDir) {
7500
8139
  crossDirEdges++;
7501
8140
  }
@@ -7542,8 +8181,8 @@ function calculateCohesionScore(graph) {
7542
8181
  const sourceAttrs = graph.getNodeAttributes(source);
7543
8182
  const targetAttrs = graph.getNodeAttributes(target);
7544
8183
  if (sourceAttrs.filePath !== targetAttrs.filePath) {
7545
- const sourceDir = dirname12(sourceAttrs.filePath);
7546
- const targetDir = dirname12(targetAttrs.filePath);
8184
+ const sourceDir = dirname13(sourceAttrs.filePath);
8185
+ const targetDir = dirname13(targetAttrs.filePath);
7547
8186
  if (!dirEdges.has(sourceDir)) {
7548
8187
  dirEdges.set(sourceDir, { internal: 0, total: 0 });
7549
8188
  }
@@ -7825,8 +8464,8 @@ function calculateDepthScore(graph) {
7825
8464
  }
7826
8465
 
7827
8466
  // 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";
8467
+ import { readFileSync as readFileSync14, writeFileSync, existsSync as existsSync14, mkdirSync } from "fs";
8468
+ import { dirname as dirname14, resolve as resolve11 } from "path";
7830
8469
  function calculateHealthScore(graph, projectRoot) {
7831
8470
  const coupling = calculateCouplingScore(graph);
7832
8471
  const cohesion = calculateCohesionScore(graph);
@@ -7925,8 +8564,8 @@ function getHealthTrend(projectRoot, currentScore) {
7925
8564
  }
7926
8565
  }
7927
8566
  function saveHealthHistory(projectRoot, report) {
7928
- const resolvedRoot = resolve10(projectRoot);
7929
- const historyFile = resolve10(resolvedRoot, ".depwire", "health-history.json");
8567
+ const resolvedRoot = resolve11(projectRoot);
8568
+ const historyFile = resolve11(resolvedRoot, ".depwire", "health-history.json");
7930
8569
  if (!historyFile.startsWith(resolvedRoot)) {
7931
8570
  return;
7932
8571
  }
@@ -7941,10 +8580,10 @@ function saveHealthHistory(projectRoot, report) {
7941
8580
  }))
7942
8581
  };
7943
8582
  let history = [];
7944
- if (existsSync13(historyFile)) {
8583
+ if (existsSync14(historyFile)) {
7945
8584
  try {
7946
8585
  if (!historyFile.startsWith(resolvedRoot)) return;
7947
- const content = readFileSync13(historyFile, "utf-8");
8586
+ const content = readFileSync14(historyFile, "utf-8");
7948
8587
  history = JSON.parse(content);
7949
8588
  } catch {
7950
8589
  }
@@ -7953,19 +8592,19 @@ function saveHealthHistory(projectRoot, report) {
7953
8592
  if (history.length > 50) {
7954
8593
  history = history.slice(-50);
7955
8594
  }
7956
- mkdirSync(dirname13(historyFile), { recursive: true });
8595
+ mkdirSync(dirname14(historyFile), { recursive: true });
7957
8596
  if (!historyFile.startsWith(resolvedRoot)) return;
7958
8597
  writeFileSync(historyFile, JSON.stringify(history, null, 2), "utf-8");
7959
8598
  }
7960
8599
  function loadHealthHistory(projectRoot) {
7961
- const resolvedRoot = resolve10(projectRoot);
7962
- const historyFile = resolve10(resolvedRoot, ".depwire", "health-history.json");
7963
- if (!historyFile.startsWith(resolvedRoot) || !existsSync13(historyFile)) {
8600
+ const resolvedRoot = resolve11(projectRoot);
8601
+ const historyFile = resolve11(resolvedRoot, ".depwire", "health-history.json");
8602
+ if (!historyFile.startsWith(resolvedRoot) || !existsSync14(historyFile)) {
7964
8603
  return [];
7965
8604
  }
7966
8605
  try {
7967
8606
  if (!historyFile.startsWith(resolvedRoot)) return [];
7968
- const content = readFileSync13(historyFile, "utf-8");
8607
+ const content = readFileSync14(historyFile, "utf-8");
7969
8608
  return JSON.parse(content);
7970
8609
  } catch {
7971
8610
  return [];
@@ -7974,7 +8613,7 @@ function loadHealthHistory(projectRoot) {
7974
8613
 
7975
8614
  // src/dead-code/detector.ts
7976
8615
  import path2 from "path";
7977
- import { readFileSync as readFileSync14, existsSync as existsSync14 } from "fs";
8616
+ import { readFileSync as readFileSync15, existsSync as existsSync15 } from "fs";
7978
8617
  function findDeadSymbols(graph, projectRoot, includeTests = false, debug = false) {
7979
8618
  const deadSymbols = [];
7980
8619
  const context = { graph, projectRoot };
@@ -8107,11 +8746,11 @@ function getPackageEntryPoints(projectRoot) {
8107
8746
  const entryPoints = /* @__PURE__ */ new Set();
8108
8747
  const resolvedRoot = path2.resolve(projectRoot);
8109
8748
  const packageJsonPath = path2.resolve(resolvedRoot, "package.json");
8110
- if (!packageJsonPath.startsWith(resolvedRoot) || !existsSync14(packageJsonPath)) {
8749
+ if (!packageJsonPath.startsWith(resolvedRoot) || !existsSync15(packageJsonPath)) {
8111
8750
  return entryPoints;
8112
8751
  }
8113
8752
  try {
8114
- const packageJson = JSON.parse(readFileSync14(packageJsonPath, "utf-8"));
8753
+ const packageJson = JSON.parse(readFileSync15(packageJsonPath, "utf-8"));
8115
8754
  if (packageJson.main) {
8116
8755
  entryPoints.add(path2.resolve(projectRoot, packageJson.main));
8117
8756
  }
@@ -8171,6 +8810,9 @@ function shouldExclude(attrs, context, includeTests, packageEntryPoints) {
8171
8810
  if (isPhpExcluded(attrs)) {
8172
8811
  return "framework";
8173
8812
  }
8813
+ if (isSwiftExcluded(attrs)) {
8814
+ return "framework";
8815
+ }
8174
8816
  return null;
8175
8817
  }
8176
8818
  function isRealPackageEntryPoint(filePath, packageEntryPoints) {
@@ -8301,6 +8943,45 @@ function isPhpExcluded(attrs) {
8301
8943
  if (name.startsWith("test") || name === "setUp" || name === "tearDown" || name === "setUpBeforeClass" || name === "tearDownAfterClass") return true;
8302
8944
  return false;
8303
8945
  }
8946
+ function isSwiftExcluded(attrs) {
8947
+ const filePath = attrs.file || attrs.filePath || "";
8948
+ const name = attrs.name || "";
8949
+ if (!filePath.endsWith(".swift")) return false;
8950
+ if (name === "main") return true;
8951
+ const appLifecycle = [
8952
+ "application",
8953
+ "applicationDidFinishLaunching",
8954
+ "applicationWillTerminate",
8955
+ "applicationDidBecomeActive",
8956
+ "applicationWillResignActive",
8957
+ "applicationDidEnterBackground",
8958
+ "applicationWillEnterForeground",
8959
+ "scene",
8960
+ "sceneDidDisconnect",
8961
+ "sceneDidBecomeActive",
8962
+ "sceneWillResignActive",
8963
+ "sceneWillEnterForeground",
8964
+ "sceneDidEnterBackground"
8965
+ ];
8966
+ if (appLifecycle.includes(name)) return true;
8967
+ if (name === "body" || name === "previews") return true;
8968
+ const protocolMethods = [
8969
+ "hash",
8970
+ "encode",
8971
+ "init",
8972
+ "deinit",
8973
+ "tableView",
8974
+ "collectionView",
8975
+ "numberOfSections",
8976
+ "numberOfRowsInSection",
8977
+ "cellForRowAt",
8978
+ "didSelectRowAt"
8979
+ ];
8980
+ if (protocolMethods.includes(name)) return true;
8981
+ if (["encode", "decode", "init(from:)"].includes(name)) return true;
8982
+ if (name.startsWith("test") || name === "setUp" || name === "tearDown" || name === "setUpWithError" || name === "tearDownWithError") return true;
8983
+ return false;
8984
+ }
8304
8985
 
8305
8986
  // src/dead-code/classifier.ts
8306
8987
  import path3 from "path";
@@ -8367,8 +9048,8 @@ function generateReason(symbol, confidence) {
8367
9048
  return "Potentially unused";
8368
9049
  }
8369
9050
  function isBarrelFile(filePath) {
8370
- const basename11 = path3.basename(filePath);
8371
- return basename11 === "index.ts" || basename11 === "index.js";
9051
+ const basename12 = path3.basename(filePath);
9052
+ return basename12 === "index.ts" || basename12 === "index.js";
8372
9053
  }
8373
9054
  function isTestFile2(filePath) {
8374
9055
  return filePath.includes("__tests__/") || filePath.includes(".test.") || filePath.includes(".spec.") || filePath.includes("/test/") || filePath.includes("/tests/");
@@ -8547,11 +9228,11 @@ function filterByConfidence(symbols, minConfidence) {
8547
9228
  }
8548
9229
 
8549
9230
  // src/docs/generator.ts
8550
- import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync17 } from "fs";
8551
- import { join as join19 } from "path";
9231
+ import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync18 } from "fs";
9232
+ import { join as join20 } from "path";
8552
9233
 
8553
9234
  // src/docs/architecture.ts
8554
- import { dirname as dirname14 } from "path";
9235
+ import { dirname as dirname15 } from "path";
8555
9236
 
8556
9237
  // src/docs/templates.ts
8557
9238
  function header(text, level = 1) {
@@ -8702,7 +9383,7 @@ function generateModuleStructure(graph) {
8702
9383
  function getDirectoryStats(graph) {
8703
9384
  const dirMap = /* @__PURE__ */ new Map();
8704
9385
  graph.forEachNode((node, attrs) => {
8705
- const dir = dirname14(attrs.filePath);
9386
+ const dir = dirname15(attrs.filePath);
8706
9387
  if (dir === ".") return;
8707
9388
  if (!dirMap.has(dir)) {
8708
9389
  dirMap.set(dir, {
@@ -8727,7 +9408,7 @@ function getDirectoryStats(graph) {
8727
9408
  });
8728
9409
  const filesPerDir = /* @__PURE__ */ new Map();
8729
9410
  graph.forEachNode((node, attrs) => {
8730
- const dir = dirname14(attrs.filePath);
9411
+ const dir = dirname15(attrs.filePath);
8731
9412
  if (!filesPerDir.has(dir)) {
8732
9413
  filesPerDir.set(dir, /* @__PURE__ */ new Set());
8733
9414
  }
@@ -8742,8 +9423,8 @@ function getDirectoryStats(graph) {
8742
9423
  graph.forEachEdge((edge, attrs, source, target) => {
8743
9424
  const sourceAttrs = graph.getNodeAttributes(source);
8744
9425
  const targetAttrs = graph.getNodeAttributes(target);
8745
- const sourceDir = dirname14(sourceAttrs.filePath);
8746
- const targetDir = dirname14(targetAttrs.filePath);
9426
+ const sourceDir = dirname15(sourceAttrs.filePath);
9427
+ const targetDir = dirname15(targetAttrs.filePath);
8747
9428
  if (sourceDir !== targetDir) {
8748
9429
  if (!dirEdges.has(sourceDir)) {
8749
9430
  dirEdges.set(sourceDir, { in: 0, out: 0 });
@@ -8950,7 +9631,7 @@ function detectCycles(graph) {
8950
9631
  }
8951
9632
 
8952
9633
  // src/docs/conventions.ts
8953
- import { basename as basename8, extname as extname9 } from "path";
9634
+ import { basename as basename9, extname as extname10 } from "path";
8954
9635
  function generateConventions(graph, projectRoot, version) {
8955
9636
  let output = "";
8956
9637
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -8988,7 +9669,7 @@ function generateFileOrganization(graph) {
8988
9669
  graph.forEachNode((node, attrs) => {
8989
9670
  if (!files.has(attrs.filePath)) {
8990
9671
  files.add(attrs.filePath);
8991
- const fileName = basename8(attrs.filePath);
9672
+ const fileName = basename9(attrs.filePath);
8992
9673
  if (fileName === "index.ts" || fileName === "index.js" || fileName === "index.tsx" || fileName === "index.jsx") {
8993
9674
  barrelFileCount++;
8994
9675
  }
@@ -9047,7 +9728,7 @@ function generateNamingPatterns(graph) {
9047
9728
  graph.forEachNode((node, attrs) => {
9048
9729
  if (!files.has(attrs.filePath)) {
9049
9730
  files.add(attrs.filePath);
9050
- const fileName = basename8(attrs.filePath, extname9(attrs.filePath));
9731
+ const fileName = basename9(attrs.filePath, extname10(attrs.filePath));
9051
9732
  if (isCamelCase(fileName)) patterns.files.camelCase++;
9052
9733
  else if (isPascalCase(fileName)) patterns.files.PascalCase++;
9053
9734
  else if (isKebabCase(fileName)) patterns.files.kebabCase++;
@@ -9724,7 +10405,7 @@ function detectCyclesDetailed(graph) {
9724
10405
  }
9725
10406
 
9726
10407
  // src/docs/onboarding.ts
9727
- import { dirname as dirname15 } from "path";
10408
+ import { dirname as dirname16 } from "path";
9728
10409
  function generateOnboarding(graph, projectRoot, version) {
9729
10410
  let output = "";
9730
10411
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -9783,7 +10464,7 @@ function generateQuickOrientation(graph) {
9783
10464
  const primaryLang = Object.entries(languages2).sort((a, b) => b[1] - a[1])[0];
9784
10465
  const dirs = /* @__PURE__ */ new Set();
9785
10466
  graph.forEachNode((node, attrs) => {
9786
- const dir = dirname15(attrs.filePath);
10467
+ const dir = dirname16(attrs.filePath);
9787
10468
  if (dir !== ".") {
9788
10469
  const topLevel = dir.split("/")[0];
9789
10470
  dirs.add(topLevel);
@@ -9887,7 +10568,7 @@ function generateModuleMap(graph) {
9887
10568
  function getDirectoryStats2(graph) {
9888
10569
  const dirMap = /* @__PURE__ */ new Map();
9889
10570
  graph.forEachNode((node, attrs) => {
9890
- const dir = dirname15(attrs.filePath);
10571
+ const dir = dirname16(attrs.filePath);
9891
10572
  if (dir === ".") return;
9892
10573
  if (!dirMap.has(dir)) {
9893
10574
  dirMap.set(dir, {
@@ -9902,7 +10583,7 @@ function getDirectoryStats2(graph) {
9902
10583
  });
9903
10584
  const filesPerDir = /* @__PURE__ */ new Map();
9904
10585
  graph.forEachNode((node, attrs) => {
9905
- const dir = dirname15(attrs.filePath);
10586
+ const dir = dirname16(attrs.filePath);
9906
10587
  if (!filesPerDir.has(dir)) {
9907
10588
  filesPerDir.set(dir, /* @__PURE__ */ new Set());
9908
10589
  }
@@ -9917,8 +10598,8 @@ function getDirectoryStats2(graph) {
9917
10598
  graph.forEachEdge((edge, attrs, source, target) => {
9918
10599
  const sourceAttrs = graph.getNodeAttributes(source);
9919
10600
  const targetAttrs = graph.getNodeAttributes(target);
9920
- const sourceDir = dirname15(sourceAttrs.filePath);
9921
- const targetDir = dirname15(targetAttrs.filePath);
10601
+ const sourceDir = dirname16(sourceAttrs.filePath);
10602
+ const targetDir = dirname16(targetAttrs.filePath);
9922
10603
  if (sourceDir !== targetDir) {
9923
10604
  if (!dirEdges.has(sourceDir)) {
9924
10605
  dirEdges.set(sourceDir, { in: 0, out: 0 });
@@ -9999,7 +10680,7 @@ function detectClusters(graph) {
9999
10680
  const dirFiles = /* @__PURE__ */ new Map();
10000
10681
  const fileEdges = /* @__PURE__ */ new Map();
10001
10682
  graph.forEachNode((node, attrs) => {
10002
- const dir = dirname15(attrs.filePath);
10683
+ const dir = dirname16(attrs.filePath);
10003
10684
  if (!dirFiles.has(dir)) {
10004
10685
  dirFiles.set(dir, /* @__PURE__ */ new Set());
10005
10686
  }
@@ -10053,8 +10734,8 @@ function inferClusterName(files) {
10053
10734
  if (sortedWords.length > 0 && sortedWords[0][1] > 1) {
10054
10735
  return capitalizeFirst2(sortedWords[0][0]);
10055
10736
  }
10056
- const commonDir = dirname15(files[0]);
10057
- if (files.every((f) => dirname15(f) === commonDir)) {
10737
+ const commonDir = dirname16(files[0]);
10738
+ if (files.every((f) => dirname16(f) === commonDir)) {
10058
10739
  return capitalizeFirst2(commonDir.split("/").pop() || "Core");
10059
10740
  }
10060
10741
  return "Core";
@@ -10112,7 +10793,7 @@ function generateDepwireUsage(projectRoot) {
10112
10793
  }
10113
10794
 
10114
10795
  // src/docs/files.ts
10115
- import { dirname as dirname16, basename as basename9 } from "path";
10796
+ import { dirname as dirname17, basename as basename10 } from "path";
10116
10797
  function generateFiles(graph, projectRoot, version) {
10117
10798
  let output = "";
10118
10799
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -10216,7 +10897,7 @@ function generateDirectoryBreakdown(graph) {
10216
10897
  const fileStats = getFileStats2(graph);
10217
10898
  const dirMap = /* @__PURE__ */ new Map();
10218
10899
  for (const file of fileStats) {
10219
- const dir = dirname16(file.filePath);
10900
+ const dir = dirname17(file.filePath);
10220
10901
  const topDir = dir === "." ? "." : dir.split("/")[0];
10221
10902
  if (!dirMap.has(topDir)) {
10222
10903
  dirMap.set(topDir, {
@@ -10231,7 +10912,7 @@ function generateDirectoryBreakdown(graph) {
10231
10912
  dirStats.symbolCount += file.symbolCount;
10232
10913
  if (file.totalConnections > dirStats.maxConnections) {
10233
10914
  dirStats.maxConnections = file.totalConnections;
10234
- dirStats.mostConnectedFile = basename9(file.filePath);
10915
+ dirStats.mostConnectedFile = basename10(file.filePath);
10235
10916
  }
10236
10917
  }
10237
10918
  if (dirMap.size === 0) {
@@ -10859,7 +11540,7 @@ function generateRecommendations(graph) {
10859
11540
  }
10860
11541
 
10861
11542
  // src/docs/tests.ts
10862
- import { basename as basename10, dirname as dirname17 } from "path";
11543
+ import { basename as basename11, dirname as dirname18 } from "path";
10863
11544
  function generateTests(graph, projectRoot, version) {
10864
11545
  let output = "";
10865
11546
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -10887,8 +11568,8 @@ function getFileCount8(graph) {
10887
11568
  return files.size;
10888
11569
  }
10889
11570
  function isTestFile3(filePath) {
10890
- const fileName = basename10(filePath).toLowerCase();
10891
- const dirPath = dirname17(filePath).toLowerCase();
11571
+ const fileName = basename11(filePath).toLowerCase();
11572
+ const dirPath = dirname18(filePath).toLowerCase();
10892
11573
  if (dirPath.includes("test") || dirPath.includes("spec") || dirPath.includes("__tests__")) {
10893
11574
  return true;
10894
11575
  }
@@ -10946,13 +11627,13 @@ function generateTestFileInventory(graph) {
10946
11627
  return output;
10947
11628
  }
10948
11629
  function matchTestToSource(testFile) {
10949
- const testFileName = basename10(testFile);
10950
- const testDir = dirname17(testFile);
11630
+ const testFileName = basename11(testFile);
11631
+ const testDir = dirname18(testFile);
10951
11632
  let sourceFileName = testFileName.replace(/\.test\./g, ".").replace(/\.spec\./g, ".").replace(/_test\./g, ".").replace(/_spec\./g, ".");
10952
11633
  const possiblePaths = [];
10953
11634
  possiblePaths.push(testDir + "/" + sourceFileName);
10954
11635
  if (testDir.endsWith("/test") || testDir.endsWith("/tests") || testDir.endsWith("/__tests__")) {
10955
- const parentDir = dirname17(testDir);
11636
+ const parentDir = dirname18(testDir);
10956
11637
  possiblePaths.push(parentDir + "/" + sourceFileName);
10957
11638
  }
10958
11639
  if (testDir.includes("test")) {
@@ -11108,7 +11789,7 @@ function generateTestCoverageMap(graph) {
11108
11789
  const rows = mappings.slice(0, 30).map((m) => [
11109
11790
  `\`${m.sourceFile}\``,
11110
11791
  m.hasTest ? "\u2705" : "\u274C",
11111
- m.testFile ? `\`${basename10(m.testFile)}\`` : "-",
11792
+ m.testFile ? `\`${basename11(m.testFile)}\`` : "-",
11112
11793
  formatNumber(m.symbolCount)
11113
11794
  ]);
11114
11795
  let output = table(headers, rows);
@@ -11149,7 +11830,7 @@ function generateTestStatistics(graph) {
11149
11830
  `;
11150
11831
  const dirTestCoverage = /* @__PURE__ */ new Map();
11151
11832
  for (const sourceFile of sourceFiles) {
11152
- const dir = dirname17(sourceFile).split("/")[0];
11833
+ const dir = dirname18(sourceFile).split("/")[0];
11153
11834
  if (!dirTestCoverage.has(dir)) {
11154
11835
  dirTestCoverage.set(dir, { total: 0, tested: 0 });
11155
11836
  }
@@ -11172,7 +11853,7 @@ function generateTestStatistics(graph) {
11172
11853
  }
11173
11854
 
11174
11855
  // src/docs/history.ts
11175
- import { dirname as dirname18 } from "path";
11856
+ import { dirname as dirname19 } from "path";
11176
11857
  import { execSync } from "child_process";
11177
11858
  function generateHistory(graph, projectRoot, version) {
11178
11859
  let output = "";
@@ -11453,7 +12134,7 @@ function generateFeatureClusters(graph) {
11453
12134
  const dirFiles = /* @__PURE__ */ new Map();
11454
12135
  const fileEdges = /* @__PURE__ */ new Map();
11455
12136
  graph.forEachNode((node, attrs) => {
11456
- const dir = dirname18(attrs.filePath);
12137
+ const dir = dirname19(attrs.filePath);
11457
12138
  if (!dirFiles.has(dir)) {
11458
12139
  dirFiles.set(dir, /* @__PURE__ */ new Set());
11459
12140
  }
@@ -11535,7 +12216,7 @@ function capitalizeFirst3(str) {
11535
12216
  }
11536
12217
 
11537
12218
  // src/docs/current.ts
11538
- import { dirname as dirname19 } from "path";
12219
+ import { dirname as dirname20 } from "path";
11539
12220
  function generateCurrent(graph, projectRoot, version) {
11540
12221
  let output = "";
11541
12222
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -11673,7 +12354,7 @@ function generateCompleteFileIndex(graph) {
11673
12354
  fileInfos.sort((a, b) => a.filePath.localeCompare(b.filePath));
11674
12355
  const dirGroups = /* @__PURE__ */ new Map();
11675
12356
  for (const info of fileInfos) {
11676
- const dir = dirname19(info.filePath);
12357
+ const dir = dirname20(info.filePath);
11677
12358
  const topDir = dir === "." ? "root" : dir.split("/")[0];
11678
12359
  if (!dirGroups.has(topDir)) {
11679
12360
  dirGroups.set(topDir, []);
@@ -11884,8 +12565,8 @@ function getTopLevelDir2(filePath) {
11884
12565
  }
11885
12566
 
11886
12567
  // src/docs/status.ts
11887
- import { readFileSync as readFileSync15, existsSync as existsSync15 } from "fs";
11888
- import { resolve as resolve11 } from "path";
12568
+ import { readFileSync as readFileSync16, existsSync as existsSync16 } from "fs";
12569
+ import { resolve as resolve12 } from "path";
11889
12570
  function generateStatus(graph, projectRoot, version) {
11890
12571
  let output = "";
11891
12572
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -11918,16 +12599,16 @@ function getFileCount11(graph) {
11918
12599
  }
11919
12600
  function extractComments(projectRoot, filePath) {
11920
12601
  const comments = [];
11921
- const resolvedRoot = resolve11(projectRoot);
11922
- const fullPath = resolve11(resolvedRoot, filePath);
12602
+ const resolvedRoot = resolve12(projectRoot);
12603
+ const fullPath = resolve12(resolvedRoot, filePath);
11923
12604
  if (!fullPath.startsWith(resolvedRoot)) {
11924
12605
  return comments;
11925
12606
  }
11926
- if (!existsSync15(fullPath)) {
12607
+ if (!existsSync16(fullPath)) {
11927
12608
  return comments;
11928
12609
  }
11929
12610
  try {
11930
- const content = readFileSync15(fullPath, "utf-8");
12611
+ const content = readFileSync16(fullPath, "utf-8");
11931
12612
  const lines = content.split("\n");
11932
12613
  const patterns = [
11933
12614
  { type: "TODO", regex: /(?:\/\/|#|\/\*)\s*TODO:?\s*(.+)/i },
@@ -12506,16 +13187,16 @@ function generateConfidenceSection(title, description, symbols, projectRoot) {
12506
13187
  }
12507
13188
 
12508
13189
  // src/docs/metadata.ts
12509
- import { existsSync as existsSync16, readFileSync as readFileSync16, writeFileSync as writeFileSync2 } from "fs";
12510
- import { resolve as resolve12 } from "path";
13190
+ import { existsSync as existsSync17, readFileSync as readFileSync17, writeFileSync as writeFileSync2 } from "fs";
13191
+ import { resolve as resolve13 } from "path";
12511
13192
  function loadMetadata(outputDir) {
12512
- const resolvedDir = resolve12(outputDir);
12513
- const metadataPath = resolve12(resolvedDir, "metadata.json");
12514
- if (!metadataPath.startsWith(resolvedDir) || !existsSync16(metadataPath)) {
13193
+ const resolvedDir = resolve13(outputDir);
13194
+ const metadataPath = resolve13(resolvedDir, "metadata.json");
13195
+ if (!metadataPath.startsWith(resolvedDir) || !existsSync17(metadataPath)) {
12515
13196
  return null;
12516
13197
  }
12517
13198
  try {
12518
- const content = readFileSync16(metadataPath, "utf-8");
13199
+ const content = readFileSync17(metadataPath, "utf-8");
12519
13200
  return JSON.parse(content);
12520
13201
  } catch (err) {
12521
13202
  console.error("Failed to load metadata:", err);
@@ -12523,8 +13204,8 @@ function loadMetadata(outputDir) {
12523
13204
  }
12524
13205
  }
12525
13206
  function saveMetadata(outputDir, metadata) {
12526
- const resolvedDir = resolve12(outputDir);
12527
- const metadataPath = resolve12(resolvedDir, "metadata.json");
13207
+ const resolvedDir = resolve13(outputDir);
13208
+ const metadataPath = resolve13(resolvedDir, "metadata.json");
12528
13209
  if (!metadataPath.startsWith(resolvedDir)) {
12529
13210
  throw new Error(`Path traversal attempt blocked: ${metadataPath}`);
12530
13211
  }
@@ -12570,7 +13251,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
12570
13251
  const generated = [];
12571
13252
  const errors = [];
12572
13253
  try {
12573
- if (!existsSync17(options.outputDir)) {
13254
+ if (!existsSync18(options.outputDir)) {
12574
13255
  mkdirSync2(options.outputDir, { recursive: true });
12575
13256
  if (options.verbose) {
12576
13257
  console.log(`Created output directory: ${options.outputDir}`);
@@ -12609,7 +13290,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
12609
13290
  try {
12610
13291
  if (options.verbose) console.log("Generating ARCHITECTURE.md...");
12611
13292
  const content = generateArchitecture(graph, projectRoot, version, parseTime);
12612
- const filePath = join19(options.outputDir, "ARCHITECTURE.md");
13293
+ const filePath = join20(options.outputDir, "ARCHITECTURE.md");
12613
13294
  writeFileSync3(filePath, content, "utf-8");
12614
13295
  generated.push("ARCHITECTURE.md");
12615
13296
  } catch (err) {
@@ -12620,7 +13301,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
12620
13301
  try {
12621
13302
  if (options.verbose) console.log("Generating CONVENTIONS.md...");
12622
13303
  const content = generateConventions(graph, projectRoot, version);
12623
- const filePath = join19(options.outputDir, "CONVENTIONS.md");
13304
+ const filePath = join20(options.outputDir, "CONVENTIONS.md");
12624
13305
  writeFileSync3(filePath, content, "utf-8");
12625
13306
  generated.push("CONVENTIONS.md");
12626
13307
  } catch (err) {
@@ -12631,7 +13312,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
12631
13312
  try {
12632
13313
  if (options.verbose) console.log("Generating DEPENDENCIES.md...");
12633
13314
  const content = generateDependencies(graph, projectRoot, version);
12634
- const filePath = join19(options.outputDir, "DEPENDENCIES.md");
13315
+ const filePath = join20(options.outputDir, "DEPENDENCIES.md");
12635
13316
  writeFileSync3(filePath, content, "utf-8");
12636
13317
  generated.push("DEPENDENCIES.md");
12637
13318
  } catch (err) {
@@ -12642,7 +13323,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
12642
13323
  try {
12643
13324
  if (options.verbose) console.log("Generating ONBOARDING.md...");
12644
13325
  const content = generateOnboarding(graph, projectRoot, version);
12645
- const filePath = join19(options.outputDir, "ONBOARDING.md");
13326
+ const filePath = join20(options.outputDir, "ONBOARDING.md");
12646
13327
  writeFileSync3(filePath, content, "utf-8");
12647
13328
  generated.push("ONBOARDING.md");
12648
13329
  } catch (err) {
@@ -12653,7 +13334,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
12653
13334
  try {
12654
13335
  if (options.verbose) console.log("Generating FILES.md...");
12655
13336
  const content = generateFiles(graph, projectRoot, version);
12656
- const filePath = join19(options.outputDir, "FILES.md");
13337
+ const filePath = join20(options.outputDir, "FILES.md");
12657
13338
  writeFileSync3(filePath, content, "utf-8");
12658
13339
  generated.push("FILES.md");
12659
13340
  } catch (err) {
@@ -12664,7 +13345,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
12664
13345
  try {
12665
13346
  if (options.verbose) console.log("Generating API_SURFACE.md...");
12666
13347
  const content = generateApiSurface(graph, projectRoot, version);
12667
- const filePath = join19(options.outputDir, "API_SURFACE.md");
13348
+ const filePath = join20(options.outputDir, "API_SURFACE.md");
12668
13349
  writeFileSync3(filePath, content, "utf-8");
12669
13350
  generated.push("API_SURFACE.md");
12670
13351
  } catch (err) {
@@ -12675,7 +13356,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
12675
13356
  try {
12676
13357
  if (options.verbose) console.log("Generating ERRORS.md...");
12677
13358
  const content = generateErrors(graph, projectRoot, version);
12678
- const filePath = join19(options.outputDir, "ERRORS.md");
13359
+ const filePath = join20(options.outputDir, "ERRORS.md");
12679
13360
  writeFileSync3(filePath, content, "utf-8");
12680
13361
  generated.push("ERRORS.md");
12681
13362
  } catch (err) {
@@ -12686,7 +13367,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
12686
13367
  try {
12687
13368
  if (options.verbose) console.log("Generating TESTS.md...");
12688
13369
  const content = generateTests(graph, projectRoot, version);
12689
- const filePath = join19(options.outputDir, "TESTS.md");
13370
+ const filePath = join20(options.outputDir, "TESTS.md");
12690
13371
  writeFileSync3(filePath, content, "utf-8");
12691
13372
  generated.push("TESTS.md");
12692
13373
  } catch (err) {
@@ -12697,7 +13378,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
12697
13378
  try {
12698
13379
  if (options.verbose) console.log("Generating HISTORY.md...");
12699
13380
  const content = generateHistory(graph, projectRoot, version);
12700
- const filePath = join19(options.outputDir, "HISTORY.md");
13381
+ const filePath = join20(options.outputDir, "HISTORY.md");
12701
13382
  writeFileSync3(filePath, content, "utf-8");
12702
13383
  generated.push("HISTORY.md");
12703
13384
  } catch (err) {
@@ -12708,7 +13389,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
12708
13389
  try {
12709
13390
  if (options.verbose) console.log("Generating CURRENT.md...");
12710
13391
  const content = generateCurrent(graph, projectRoot, version);
12711
- const filePath = join19(options.outputDir, "CURRENT.md");
13392
+ const filePath = join20(options.outputDir, "CURRENT.md");
12712
13393
  writeFileSync3(filePath, content, "utf-8");
12713
13394
  generated.push("CURRENT.md");
12714
13395
  } catch (err) {
@@ -12719,7 +13400,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
12719
13400
  try {
12720
13401
  if (options.verbose) console.log("Generating STATUS.md...");
12721
13402
  const content = generateStatus(graph, projectRoot, version);
12722
- const filePath = join19(options.outputDir, "STATUS.md");
13403
+ const filePath = join20(options.outputDir, "STATUS.md");
12723
13404
  writeFileSync3(filePath, content, "utf-8");
12724
13405
  generated.push("STATUS.md");
12725
13406
  } catch (err) {
@@ -12730,7 +13411,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
12730
13411
  try {
12731
13412
  if (options.verbose) console.log("Generating HEALTH.md...");
12732
13413
  const content = generateHealth(graph, projectRoot, version);
12733
- const filePath = join19(options.outputDir, "HEALTH.md");
13414
+ const filePath = join20(options.outputDir, "HEALTH.md");
12734
13415
  writeFileSync3(filePath, content, "utf-8");
12735
13416
  generated.push("HEALTH.md");
12736
13417
  } catch (err) {
@@ -12741,7 +13422,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
12741
13422
  try {
12742
13423
  if (options.verbose) console.log("Generating DEAD_CODE.md...");
12743
13424
  const content = generateDeadCode(graph, projectRoot, version);
12744
- const filePath = join19(options.outputDir, "DEAD_CODE.md");
13425
+ const filePath = join20(options.outputDir, "DEAD_CODE.md");
12745
13426
  writeFileSync3(filePath, content, "utf-8");
12746
13427
  generated.push("DEAD_CODE.md");
12747
13428
  } catch (err) {
@@ -12785,7 +13466,7 @@ function getFileCount13(graph) {
12785
13466
  }
12786
13467
 
12787
13468
  // src/simulation/engine.ts
12788
- import { dirname as dirname20, join as join20 } from "path";
13469
+ import { dirname as dirname21, join as join21 } from "path";
12789
13470
  function normalizePath2(p) {
12790
13471
  return p.replace(/^\.\//, "").replace(/\/+$/, "");
12791
13472
  }
@@ -12917,7 +13598,7 @@ var SimulationEngine = class {
12917
13598
  }
12918
13599
  }
12919
13600
  applyRename(clone, target, newName, brokenImports) {
12920
- const destination = join20(dirname20(target), newName);
13601
+ const destination = join21(dirname21(target), newName);
12921
13602
  this.applyMove(clone, target, destination, brokenImports);
12922
13603
  }
12923
13604
  applySplit(clone, target, newFile, symbols, brokenImports) {
@@ -13117,13 +13798,13 @@ var SimulationEngine = class {
13117
13798
  };
13118
13799
 
13119
13800
  // src/security/scanner.ts
13120
- import { existsSync as existsSync19 } from "fs";
13121
- import { join as join30 } from "path";
13801
+ import { existsSync as existsSync20 } from "fs";
13802
+ import { join as join31 } from "path";
13122
13803
 
13123
13804
  // src/security/checks/dependencies.ts
13124
13805
  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";
13806
+ import { existsSync as existsSync19, readFileSync as readFileSync18, readdirSync as readdirSync11 } from "fs";
13807
+ import { join as join22 } from "path";
13127
13808
  function cvssToSeverity(score) {
13128
13809
  if (score >= 9) return "critical";
13129
13810
  if (score >= 7) return "high";
@@ -13133,18 +13814,18 @@ function cvssToSeverity(score) {
13133
13814
  async function checkDependencies(_files, projectRoot) {
13134
13815
  const findings = [];
13135
13816
  try {
13136
- if (existsSync18(join21(projectRoot, "package.json"))) {
13817
+ if (existsSync19(join22(projectRoot, "package.json"))) {
13137
13818
  findings.push(...checkNpmAudit(projectRoot));
13138
13819
  findings.push(...checkPackageJsonPatterns(projectRoot));
13139
13820
  findings.push(...checkPostinstallScripts(projectRoot));
13140
13821
  }
13141
- if (existsSync18(join21(projectRoot, "requirements.txt")) || existsSync18(join21(projectRoot, "pyproject.toml"))) {
13822
+ if (existsSync19(join22(projectRoot, "requirements.txt")) || existsSync19(join22(projectRoot, "pyproject.toml"))) {
13142
13823
  findings.push(...checkPipAudit(projectRoot));
13143
13824
  }
13144
- if (existsSync18(join21(projectRoot, "Cargo.toml"))) {
13825
+ if (existsSync19(join22(projectRoot, "Cargo.toml"))) {
13145
13826
  findings.push(...checkCargoAudit(projectRoot));
13146
13827
  }
13147
- if (existsSync18(join21(projectRoot, "go.mod"))) {
13828
+ if (existsSync19(join22(projectRoot, "go.mod"))) {
13148
13829
  findings.push(...checkGoVerify(projectRoot));
13149
13830
  }
13150
13831
  } catch (err) {
@@ -13233,8 +13914,8 @@ function checkNpmAudit(projectRoot) {
13233
13914
  function checkPackageJsonPatterns(projectRoot) {
13234
13915
  const findings = [];
13235
13916
  try {
13236
- const pkgPath = join21(projectRoot, "package.json");
13237
- const pkg = JSON.parse(readFileSync17(pkgPath, "utf-8"));
13917
+ const pkgPath = join22(projectRoot, "package.json");
13918
+ const pkg = JSON.parse(readFileSync18(pkgPath, "utf-8"));
13238
13919
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
13239
13920
  for (const [name, version] of Object.entries(allDeps)) {
13240
13921
  if (version.startsWith("^") || version.startsWith("~")) {
@@ -13256,15 +13937,15 @@ function checkPackageJsonPatterns(projectRoot) {
13256
13937
  }
13257
13938
  function checkPostinstallScripts(projectRoot) {
13258
13939
  const findings = [];
13259
- const nodeModules = join21(projectRoot, "node_modules");
13260
- if (!existsSync18(nodeModules)) return findings;
13940
+ const nodeModules = join22(projectRoot, "node_modules");
13941
+ if (!existsSync19(nodeModules)) return findings;
13261
13942
  try {
13262
- const topLevelDeps = readdirSync10(nodeModules).filter((d) => !d.startsWith("."));
13943
+ const topLevelDeps = readdirSync11(nodeModules).filter((d) => !d.startsWith("."));
13263
13944
  for (const dep of topLevelDeps) {
13264
- const depPkgPath = join21(nodeModules, dep, "package.json");
13265
- if (!existsSync18(depPkgPath)) continue;
13945
+ const depPkgPath = join22(nodeModules, dep, "package.json");
13946
+ if (!existsSync19(depPkgPath)) continue;
13266
13947
  try {
13267
- const depPkg = JSON.parse(readFileSync17(depPkgPath, "utf-8"));
13948
+ const depPkg = JSON.parse(readFileSync18(depPkgPath, "utf-8"));
13268
13949
  const scripts = depPkg.scripts || {};
13269
13950
  if (scripts.postinstall || scripts.preinstall || scripts.install) {
13270
13951
  const scriptName = scripts.postinstall ? "postinstall" : scripts.preinstall ? "preinstall" : "install";
@@ -13302,7 +13983,7 @@ function checkPipAudit(projectRoot) {
13302
13983
  id: "",
13303
13984
  severity: cvssToSeverity(vuln.cvss?.score || 5),
13304
13985
  vulnerabilityClass: "dependency-cve",
13305
- file: existsSync18(join21(projectRoot, "requirements.txt")) ? "requirements.txt" : "pyproject.toml",
13986
+ file: existsSync19(join22(projectRoot, "requirements.txt")) ? "requirements.txt" : "pyproject.toml",
13306
13987
  title: `Vulnerable Python dependency: ${vuln.name}`,
13307
13988
  description: `${vuln.name}@${vuln.version} \u2014 ${vuln.id}: ${vuln.description || "Known vulnerability"}`,
13308
13989
  attackScenario: `An attacker could exploit the vulnerability in ${vuln.name}.`,
@@ -13399,8 +14080,8 @@ function checkGoVerify(projectRoot) {
13399
14080
  }
13400
14081
 
13401
14082
  // src/security/checks/injection.ts
13402
- import { readFileSync as readFileSync18 } from "fs";
13403
- import { join as join22 } from "path";
14083
+ import { readFileSync as readFileSync19 } from "fs";
14084
+ import { join as join23 } from "path";
13404
14085
  var SKIP_DIRS = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
13405
14086
  var TEST_PATTERNS = ["test", "spec", "fixture", "mock", "__tests__", "__mocks__"];
13406
14087
  var USER_INPUT_NAMES = /(?:input|user|name|path|query|branch|hash|cmd|command|req\.|params|body|args|url|dir|file|subdirectory)/i;
@@ -13687,6 +14368,43 @@ var PATTERNS = [
13687
14368
  description: "extract() on $_GET/$_POST/$_REQUEST overwrites local variables \u2014 can bypass security checks.",
13688
14369
  attackScenario: "An attacker could set arbitrary variables by crafting request parameters, potentially overwriting auth flags or config values.",
13689
14370
  suggestedFix: "Avoid extract() on user input. Access superglobals directly or use a whitelist of expected keys."
14371
+ },
14372
+ // Swift injection patterns
14373
+ {
14374
+ regex: /["'](?:SELECT|INSERT|UPDATE|DELETE)\b[^"']*\\\(/,
14375
+ title: "Swift SQL injection via string interpolation",
14376
+ vulnClass: "code-injection",
14377
+ baseSeverity: "high",
14378
+ description: "SQL query built using Swift string interpolation \u2014 vulnerable to SQL injection.",
14379
+ attackScenario: "An attacker could inject SQL through interpolated variables to read, modify, or delete database data.",
14380
+ suggestedFix: "Use parameterized queries with your database library (e.g., Fluent ORM, SQLite.swift bindings)."
14381
+ },
14382
+ {
14383
+ regex: /Process\s*\(\s*\)/,
14384
+ title: "Swift command injection via Process class",
14385
+ vulnClass: "shell-injection",
14386
+ baseSeverity: "medium",
14387
+ description: "Process() class executes external commands \u2014 vulnerable if user input reaches arguments.",
14388
+ attackScenario: "An attacker could inject shell metacharacters to execute arbitrary commands on the server.",
14389
+ suggestedFix: "Validate all arguments against a strict allowlist before passing to Process. Avoid shell execution."
14390
+ },
14391
+ {
14392
+ regex: /Unsafe(?:Raw|Mutable|Buffer)?Pointer/,
14393
+ title: "Swift unsafe pointer usage",
14394
+ vulnClass: "code-injection",
14395
+ baseSeverity: "medium",
14396
+ description: "Unsafe pointer usage bypasses Swift memory safety \u2014 potential for memory corruption.",
14397
+ attackScenario: "An attacker could exploit unsafe pointer operations to corrupt memory or execute arbitrary code.",
14398
+ suggestedFix: "Prefer safe Swift alternatives. If unsafe pointers are necessary, validate all bounds and lifetimes."
14399
+ },
14400
+ {
14401
+ regex: /UserDefaults\s*\.\s*(?:standard\s*\.\s*)?set\s*\([^)]*(?:password|secret|token|apiKey|api_key)/i,
14402
+ title: "Swift sensitive data in UserDefaults (unencrypted)",
14403
+ vulnClass: "code-injection",
14404
+ baseSeverity: "high",
14405
+ description: "Sensitive data stored in UserDefaults without encryption \u2014 easily accessible on jailbroken devices.",
14406
+ attackScenario: "An attacker with device access could read UserDefaults plist to extract credentials.",
14407
+ suggestedFix: "Use Keychain Services for storing sensitive data. Never store passwords or tokens in UserDefaults."
13690
14408
  }
13691
14409
  ];
13692
14410
  function shouldSkip(filePath) {
@@ -13703,7 +14421,7 @@ async function checkInjection(files, projectRoot) {
13703
14421
  if (shouldSkip(file.filePath) || isTestFile4(file.filePath)) continue;
13704
14422
  let content;
13705
14423
  try {
13706
- content = readFileSync18(join22(projectRoot, file.filePath), "utf-8");
14424
+ content = readFileSync19(join23(projectRoot, file.filePath), "utf-8");
13707
14425
  } catch {
13708
14426
  continue;
13709
14427
  }
@@ -13741,8 +14459,8 @@ async function checkInjection(files, projectRoot) {
13741
14459
  }
13742
14460
 
13743
14461
  // src/security/checks/secrets.ts
13744
- import { readFileSync as readFileSync19 } from "fs";
13745
- import { join as join23 } from "path";
14462
+ import { readFileSync as readFileSync20 } from "fs";
14463
+ import { join as join24 } from "path";
13746
14464
  var SKIP_DIRS2 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
13747
14465
  var TEST_PATTERNS2 = ["test", "spec", "fixture", "mock", "__tests__", "__mocks__", ".example", ".sample"];
13748
14466
  var SECRET_PATTERNS = [
@@ -13775,7 +14493,7 @@ async function checkSecrets(files, projectRoot) {
13775
14493
  if (shouldSkip2(file.filePath) || isTestFile5(file.filePath)) continue;
13776
14494
  let content;
13777
14495
  try {
13778
- content = readFileSync19(join23(projectRoot, file.filePath), "utf-8");
14496
+ content = readFileSync20(join24(projectRoot, file.filePath), "utf-8");
13779
14497
  } catch {
13780
14498
  continue;
13781
14499
  }
@@ -13808,8 +14526,8 @@ async function checkSecrets(files, projectRoot) {
13808
14526
  }
13809
14527
 
13810
14528
  // src/security/checks/path-traversal.ts
13811
- import { readFileSync as readFileSync20 } from "fs";
13812
- import { join as join24 } from "path";
14529
+ import { readFileSync as readFileSync21 } from "fs";
14530
+ import { join as join25 } from "path";
13813
14531
  var SKIP_DIRS3 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
13814
14532
  var USER_INPUT_VARS = /(?:req\.|params|query|body|input|path|dir|subdirectory|file|userInput|fileName|filePath)/i;
13815
14533
  var PATTERNS2 = [
@@ -13856,7 +14574,7 @@ async function checkPathTraversal(files, projectRoot) {
13856
14574
  if (shouldSkip3(file.filePath)) continue;
13857
14575
  let content;
13858
14576
  try {
13859
- content = readFileSync20(join24(projectRoot, file.filePath), "utf-8");
14577
+ content = readFileSync21(join25(projectRoot, file.filePath), "utf-8");
13860
14578
  } catch {
13861
14579
  continue;
13862
14580
  }
@@ -13898,8 +14616,8 @@ async function checkPathTraversal(files, projectRoot) {
13898
14616
  }
13899
14617
 
13900
14618
  // src/security/checks/auth.ts
13901
- import { readFileSync as readFileSync21 } from "fs";
13902
- import { join as join25 } from "path";
14619
+ import { readFileSync as readFileSync22 } from "fs";
14620
+ import { join as join26 } from "path";
13903
14621
  var SKIP_DIRS4 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
13904
14622
  function shouldSkip4(filePath) {
13905
14623
  return SKIP_DIRS4.some((d) => filePath.includes(d));
@@ -13915,7 +14633,7 @@ async function checkAuth(files, projectRoot) {
13915
14633
  if (shouldSkip4(file.filePath)) continue;
13916
14634
  let content;
13917
14635
  try {
13918
- content = readFileSync21(join25(projectRoot, file.filePath), "utf-8");
14636
+ content = readFileSync22(join26(projectRoot, file.filePath), "utf-8");
13919
14637
  } catch {
13920
14638
  continue;
13921
14639
  }
@@ -14006,8 +14724,8 @@ async function checkAuth(files, projectRoot) {
14006
14724
  }
14007
14725
 
14008
14726
  // src/security/checks/input-validation.ts
14009
- import { readFileSync as readFileSync22 } from "fs";
14010
- import { join as join26 } from "path";
14727
+ import { readFileSync as readFileSync23 } from "fs";
14728
+ import { join as join27 } from "path";
14011
14729
  var SKIP_DIRS5 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
14012
14730
  function shouldSkip5(filePath) {
14013
14731
  return SKIP_DIRS5.some((d) => filePath.includes(d));
@@ -14019,7 +14737,7 @@ async function checkInputValidation(files, projectRoot) {
14019
14737
  if (shouldSkip5(file.filePath)) continue;
14020
14738
  let content;
14021
14739
  try {
14022
- content = readFileSync22(join26(projectRoot, file.filePath), "utf-8");
14740
+ content = readFileSync23(join27(projectRoot, file.filePath), "utf-8");
14023
14741
  } catch {
14024
14742
  continue;
14025
14743
  }
@@ -14096,8 +14814,8 @@ async function checkInputValidation(files, projectRoot) {
14096
14814
  }
14097
14815
 
14098
14816
  // src/security/checks/information-disclosure.ts
14099
- import { readFileSync as readFileSync23 } from "fs";
14100
- import { join as join27 } from "path";
14817
+ import { readFileSync as readFileSync24 } from "fs";
14818
+ import { join as join28 } from "path";
14101
14819
  var SKIP_DIRS6 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
14102
14820
  function shouldSkip6(filePath) {
14103
14821
  return SKIP_DIRS6.some((d) => filePath.includes(d));
@@ -14109,7 +14827,7 @@ async function checkInformationDisclosure(files, projectRoot) {
14109
14827
  if (shouldSkip6(file.filePath)) continue;
14110
14828
  let content;
14111
14829
  try {
14112
- content = readFileSync23(join27(projectRoot, file.filePath), "utf-8");
14830
+ content = readFileSync24(join28(projectRoot, file.filePath), "utf-8");
14113
14831
  } catch {
14114
14832
  continue;
14115
14833
  }
@@ -14179,8 +14897,8 @@ async function checkInformationDisclosure(files, projectRoot) {
14179
14897
  }
14180
14898
 
14181
14899
  // src/security/checks/cryptography.ts
14182
- import { readFileSync as readFileSync24 } from "fs";
14183
- import { join as join28 } from "path";
14900
+ import { readFileSync as readFileSync25 } from "fs";
14901
+ import { join as join29 } from "path";
14184
14902
  var SKIP_DIRS7 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
14185
14903
  var USER_INPUT_NAMES2 = /(?:input|user|name|path|query|param|request|body|args|url)/i;
14186
14904
  function shouldSkip7(filePath) {
@@ -14197,7 +14915,7 @@ async function checkCryptography(files, projectRoot) {
14197
14915
  if (shouldSkip7(file.filePath)) continue;
14198
14916
  let content;
14199
14917
  try {
14200
- content = readFileSync24(join28(projectRoot, file.filePath), "utf-8");
14918
+ content = readFileSync25(join29(projectRoot, file.filePath), "utf-8");
14201
14919
  } catch {
14202
14920
  continue;
14203
14921
  }
@@ -14455,6 +15173,84 @@ async function checkCryptography(files, projectRoot) {
14455
15173
  suggestedFix: "Load credentials from environment variables using getenv() or $_ENV."
14456
15174
  });
14457
15175
  }
15176
+ if (/\bCC_MD5\b/.test(line) || /Insecure\s*\.\s*MD5/.test(line)) {
15177
+ findings.push({
15178
+ id: "",
15179
+ severity: isCryptoFile ? "high" : "medium",
15180
+ vulnerabilityClass: "cryptography",
15181
+ file: file.filePath,
15182
+ line: i + 1,
15183
+ title: "Weak hash algorithm: MD5 in Swift",
15184
+ description: "MD5 is cryptographically broken \u2014 collisions can be generated in seconds.",
15185
+ attackScenario: "An attacker could generate MD5 collisions to bypass integrity checks or forge hashes.",
15186
+ suggestedFix: "Use SHA256 from CryptoKit: SHA256.hash(data: data)"
15187
+ });
15188
+ }
15189
+ if (/\bCC_SHA1\b/.test(line) || /Insecure\s*\.\s*SHA1/.test(line)) {
15190
+ findings.push({
15191
+ id: "",
15192
+ severity: isCryptoFile ? "high" : "medium",
15193
+ vulnerabilityClass: "cryptography",
15194
+ file: file.filePath,
15195
+ line: i + 1,
15196
+ title: "Weak hash algorithm: SHA-1 in Swift",
15197
+ description: "SHA-1 has known collision attacks \u2014 should not be used for security purposes.",
15198
+ attackScenario: "An attacker could generate SHA-1 collisions to bypass integrity checks.",
15199
+ suggestedFix: "Use SHA256 from CryptoKit: SHA256.hash(data: data)"
15200
+ });
15201
+ }
15202
+ if (/(?:let|var)\s+(?:password|secret|apiKey|api_key|token)\s*(?::\s*String\s*)?=\s*["']/i.test(line)) {
15203
+ findings.push({
15204
+ id: "",
15205
+ severity: "high",
15206
+ vulnerabilityClass: "cryptography",
15207
+ file: file.filePath,
15208
+ line: i + 1,
15209
+ title: "Hardcoded credentials in Swift source",
15210
+ description: "A password, secret, or API key is hardcoded as a string literal.",
15211
+ attackScenario: "An attacker with access to the binary or source could extract the credential.",
15212
+ suggestedFix: "Load credentials from Keychain, environment variables, or a secure configuration service."
15213
+ });
15214
+ }
15215
+ if (/\barc4random\b/.test(line) && isCryptoFile) {
15216
+ findings.push({
15217
+ id: "",
15218
+ severity: "medium",
15219
+ vulnerabilityClass: "cryptography",
15220
+ file: file.filePath,
15221
+ line: i + 1,
15222
+ title: "arc4random in Swift security context",
15223
+ description: "arc4random is not suitable for cryptographic key generation.",
15224
+ attackScenario: "An attacker could predict random values if used for cryptographic purposes.",
15225
+ suggestedFix: "Use SecRandomCopyBytes or SystemRandomNumberGenerator for security-sensitive randomness."
15226
+ });
15227
+ }
15228
+ if (/allowsArbitraryLoads\s*:\s*true/.test(line) || /NSAllowsArbitraryLoads.*true/.test(line)) {
15229
+ findings.push({
15230
+ id: "",
15231
+ severity: "medium",
15232
+ vulnerabilityClass: "cryptography",
15233
+ file: file.filePath,
15234
+ line: i + 1,
15235
+ title: "Swift App Transport Security disabled",
15236
+ description: "allowsArbitraryLoads is set to true \u2014 all HTTP traffic is permitted without encryption.",
15237
+ attackScenario: "An attacker on the network path could intercept, read, or modify data in transit.",
15238
+ suggestedFix: "Remove allowsArbitraryLoads or set to false. Add specific exceptions only for domains that require HTTP."
15239
+ });
15240
+ }
15241
+ if (/(?:let|var)\s+\w*[Uu]rl\w*\s*=\s*["']http:\/\/(?!(?:localhost|127\.))/.test(line)) {
15242
+ findings.push({
15243
+ id: "",
15244
+ severity: "medium",
15245
+ vulnerabilityClass: "cryptography",
15246
+ file: file.filePath,
15247
+ line: i + 1,
15248
+ title: "Hardcoded HTTP URL in Swift source",
15249
+ description: "An HTTP (not HTTPS) URL is hardcoded \u2014 data is transmitted unencrypted.",
15250
+ attackScenario: "An attacker on the network path could intercept, read, or modify data in transit.",
15251
+ suggestedFix: "Use HTTPS for all external URLs to ensure data confidentiality and integrity."
15252
+ });
15253
+ }
14458
15254
  }
14459
15255
  }
14460
15256
  } catch {
@@ -14463,8 +15259,8 @@ async function checkCryptography(files, projectRoot) {
14463
15259
  }
14464
15260
 
14465
15261
  // src/security/checks/frontend.ts
14466
- import { readFileSync as readFileSync25 } from "fs";
14467
- import { join as join29 } from "path";
15262
+ import { readFileSync as readFileSync26 } from "fs";
15263
+ import { join as join30 } from "path";
14468
15264
  var SKIP_DIRS8 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
14469
15265
  function shouldSkip8(filePath) {
14470
15266
  return SKIP_DIRS8.some((d) => filePath.includes(d));
@@ -14480,7 +15276,7 @@ async function checkFrontend(files, projectRoot) {
14480
15276
  if (!isFrontendFile(file.filePath)) continue;
14481
15277
  let content;
14482
15278
  try {
14483
- content = readFileSync25(join29(projectRoot, file.filePath), "utf-8");
15279
+ content = readFileSync26(join30(projectRoot, file.filePath), "utf-8");
14484
15280
  } catch {
14485
15281
  continue;
14486
15282
  }
@@ -14940,13 +15736,13 @@ async function scanSecurity(projectRoot, graph, options = {}) {
14940
15736
  };
14941
15737
  }
14942
15738
  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";
15739
+ if (existsSync20(join31(projectRoot, "package.json"))) return "npm";
15740
+ if (existsSync20(join31(projectRoot, "requirements.txt"))) return "pip";
15741
+ if (existsSync20(join31(projectRoot, "pyproject.toml"))) return "pip";
15742
+ if (existsSync20(join31(projectRoot, "Cargo.toml"))) return "cargo";
15743
+ if (existsSync20(join31(projectRoot, "go.mod"))) return "go";
15744
+ if (existsSync20(join31(projectRoot, "pom.xml"))) return "maven";
15745
+ if (existsSync20(join31(projectRoot, "build.gradle")) || existsSync20(join31(projectRoot, "build.gradle.kts"))) return "gradle";
14950
15746
  return "unknown";
14951
15747
  }
14952
15748