depwire-cli 1.0.7 → 1.0.8
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.
- package/README.md +5 -2
- package/dist/{chunk-IOJJTKI4.js → chunk-W3ZVSDFL.js} +6 -6
- package/dist/{chunk-SLGC72RW.js → chunk-WLKW7X7G.js} +1082 -188
- package/dist/index.js +2 -2
- package/dist/mcpb-entry.js +2 -2
- package/dist/parser/grammars/tree-sitter-php.wasm +0 -0
- package/dist/sdk.js +1 -1
- package/package.json +4 -2
|
@@ -36,8 +36,9 @@ function scanDirectory(rootDir, baseDir = rootDir) {
|
|
|
36
36
|
const isCSharp = entry.endsWith(".cs") || entry.endsWith(".csx") || entry.endsWith(".csproj");
|
|
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
|
+
const isPhp = entry.endsWith(".php");
|
|
39
40
|
const isCppBuild = entry === "CMakeLists.txt" || entry === "conanfile.txt" || entry === "vcpkg.json";
|
|
40
|
-
if (isTypeScript || isJavaScript || isPython || isGo || isRust || isC || isCpp || isCSharp || isJava || isKotlin || isCppBuild) {
|
|
41
|
+
if (isTypeScript || isJavaScript || isPython || isGo || isRust || isC || isCpp || isCSharp || isJava || isKotlin || isPhp || isCppBuild) {
|
|
41
42
|
files.push(relative(rootDir, fullPath));
|
|
42
43
|
}
|
|
43
44
|
}
|
|
@@ -81,6 +82,8 @@ function findProjectRoot(startDir = process.cwd()) {
|
|
|
81
82
|
// Java (Gradle)
|
|
82
83
|
"build.gradle.kts",
|
|
83
84
|
// Kotlin (Gradle KTS)
|
|
85
|
+
"composer.json",
|
|
86
|
+
// PHP
|
|
84
87
|
".git"
|
|
85
88
|
// Any git repo
|
|
86
89
|
];
|
|
@@ -114,11 +117,11 @@ function findProjectRoot(startDir = process.cwd()) {
|
|
|
114
117
|
}
|
|
115
118
|
|
|
116
119
|
// src/parser/index.ts
|
|
117
|
-
import { readFileSync as
|
|
118
|
-
import { join as
|
|
120
|
+
import { readFileSync as readFileSync10, statSync as statSync7 } from "fs";
|
|
121
|
+
import { join as join13, resolve as resolve7 } from "path";
|
|
119
122
|
|
|
120
123
|
// src/parser/detect.ts
|
|
121
|
-
import { extname as
|
|
124
|
+
import { extname as extname8, basename as basename6 } from "path";
|
|
122
125
|
|
|
123
126
|
// src/parser/wasm-init.ts
|
|
124
127
|
import { Parser, Language } from "web-tree-sitter";
|
|
@@ -149,7 +152,8 @@ async function initParser() {
|
|
|
149
152
|
"c_sharp": "tree-sitter-c_sharp.wasm",
|
|
150
153
|
"java": "tree-sitter-java.wasm",
|
|
151
154
|
"cpp": "tree-sitter-cpp.wasm",
|
|
152
|
-
"kotlin": "tree-sitter-kotlin.wasm"
|
|
155
|
+
"kotlin": "tree-sitter-kotlin.wasm",
|
|
156
|
+
"php": "tree-sitter-php.wasm"
|
|
153
157
|
};
|
|
154
158
|
for (const [name, file] of Object.entries(grammarFiles)) {
|
|
155
159
|
const wasmPath = path.join(grammarsDir, file);
|
|
@@ -5549,7 +5553,611 @@ function resolveKotlinImport(importPath, currentFile, projectRoot) {
|
|
|
5549
5553
|
}
|
|
5550
5554
|
return null;
|
|
5551
5555
|
}
|
|
5552
|
-
function resolveSymbol9(name, context) {
|
|
5556
|
+
function resolveSymbol9(name, context) {
|
|
5557
|
+
if (context.imports.has(name)) {
|
|
5558
|
+
return context.imports.get(name) || null;
|
|
5559
|
+
}
|
|
5560
|
+
const currentFileId = `${context.filePath}::${name}`;
|
|
5561
|
+
if (context.symbols.find((s) => s.id === currentFileId)) {
|
|
5562
|
+
return currentFileId;
|
|
5563
|
+
}
|
|
5564
|
+
if (context.currentClass) {
|
|
5565
|
+
const classMethodId = `${context.filePath}::${context.currentClass}.${name}`;
|
|
5566
|
+
if (context.symbols.find((s) => s.id === classMethodId)) {
|
|
5567
|
+
return classMethodId;
|
|
5568
|
+
}
|
|
5569
|
+
}
|
|
5570
|
+
return null;
|
|
5571
|
+
}
|
|
5572
|
+
function getModifiers(node, context) {
|
|
5573
|
+
const modifiers = [];
|
|
5574
|
+
const modList = findChildByType10(node, "modifiers");
|
|
5575
|
+
if (modList) {
|
|
5576
|
+
for (let i = 0; i < modList.childCount; i++) {
|
|
5577
|
+
const child = modList.child(i);
|
|
5578
|
+
if (child) {
|
|
5579
|
+
const text = nodeText9(child, context).trim();
|
|
5580
|
+
if (text) modifiers.push(text);
|
|
5581
|
+
}
|
|
5582
|
+
}
|
|
5583
|
+
}
|
|
5584
|
+
return modifiers;
|
|
5585
|
+
}
|
|
5586
|
+
function extractTypeName5(node, context) {
|
|
5587
|
+
const text = nodeText9(node, context).trim();
|
|
5588
|
+
if (!text || text === "," || text === ":") return null;
|
|
5589
|
+
let name = text;
|
|
5590
|
+
const angleBracketIdx = name.indexOf("<");
|
|
5591
|
+
if (angleBracketIdx > 0) name = name.substring(0, angleBracketIdx);
|
|
5592
|
+
const parenIdx = name.indexOf("(");
|
|
5593
|
+
if (parenIdx > 0) name = name.substring(0, parenIdx);
|
|
5594
|
+
const dotIdx = name.lastIndexOf(".");
|
|
5595
|
+
name = dotIdx >= 0 ? name.substring(dotIdx + 1) : name;
|
|
5596
|
+
return name.trim() || null;
|
|
5597
|
+
}
|
|
5598
|
+
function findChildByType10(node, type) {
|
|
5599
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
5600
|
+
const child = node.child(i);
|
|
5601
|
+
if (child && child.type === type) return child;
|
|
5602
|
+
}
|
|
5603
|
+
return null;
|
|
5604
|
+
}
|
|
5605
|
+
function findChildrenByType4(node, type) {
|
|
5606
|
+
const results = [];
|
|
5607
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
5608
|
+
const child = node.child(i);
|
|
5609
|
+
if (child && child.type === type) results.push(child);
|
|
5610
|
+
}
|
|
5611
|
+
return results;
|
|
5612
|
+
}
|
|
5613
|
+
function findDescendantByTypes2(node, types) {
|
|
5614
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
5615
|
+
const child = node.child(i);
|
|
5616
|
+
if (!child) continue;
|
|
5617
|
+
if (types.includes(child.type)) return child;
|
|
5618
|
+
const found = findDescendantByTypes2(child, types);
|
|
5619
|
+
if (found) return found;
|
|
5620
|
+
}
|
|
5621
|
+
return null;
|
|
5622
|
+
}
|
|
5623
|
+
function nodeText9(node, context) {
|
|
5624
|
+
return context.sourceCode.substring(node.startIndex, node.endIndex);
|
|
5625
|
+
}
|
|
5626
|
+
function getCurrentSymbolId10(context) {
|
|
5627
|
+
if (context.currentScope.length === 0) return null;
|
|
5628
|
+
return `${context.filePath}::${context.currentScope[context.currentScope.length - 1]}`;
|
|
5629
|
+
}
|
|
5630
|
+
var kotlinParser = {
|
|
5631
|
+
name: "kotlin",
|
|
5632
|
+
extensions: [".kt", ".kts", "build.gradle.kts", "settings.gradle.kts", "settings.gradle"],
|
|
5633
|
+
parseFile: parseKotlinFile
|
|
5634
|
+
};
|
|
5635
|
+
|
|
5636
|
+
// src/parser/php.ts
|
|
5637
|
+
import { dirname as dirname11, join as join12 } from "path";
|
|
5638
|
+
import { existsSync as existsSync12 } from "fs";
|
|
5639
|
+
function parsePhpFile(filePath, sourceCode, projectRoot) {
|
|
5640
|
+
const parser = getParser("php");
|
|
5641
|
+
const tree = parser.parse(sourceCode, null, { bufferSize: 1024 * 1024 });
|
|
5642
|
+
const context = {
|
|
5643
|
+
filePath,
|
|
5644
|
+
projectRoot,
|
|
5645
|
+
sourceCode,
|
|
5646
|
+
symbols: [],
|
|
5647
|
+
edges: [],
|
|
5648
|
+
currentScope: [],
|
|
5649
|
+
currentClass: null,
|
|
5650
|
+
currentNamespace: null,
|
|
5651
|
+
imports: /* @__PURE__ */ new Map()
|
|
5652
|
+
};
|
|
5653
|
+
walkNode11(tree.rootNode, context);
|
|
5654
|
+
return {
|
|
5655
|
+
filePath,
|
|
5656
|
+
symbols: context.symbols,
|
|
5657
|
+
edges: context.edges
|
|
5658
|
+
};
|
|
5659
|
+
}
|
|
5660
|
+
var SCOPE_TYPES = /* @__PURE__ */ new Set([
|
|
5661
|
+
"class_declaration",
|
|
5662
|
+
"interface_declaration",
|
|
5663
|
+
"trait_declaration",
|
|
5664
|
+
"enum_declaration",
|
|
5665
|
+
"function_definition",
|
|
5666
|
+
"method_declaration"
|
|
5667
|
+
]);
|
|
5668
|
+
function walkNode11(node, context) {
|
|
5669
|
+
processNode11(node, context);
|
|
5670
|
+
if (SCOPE_TYPES.has(node.type)) return;
|
|
5671
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
5672
|
+
const child = node.child(i);
|
|
5673
|
+
if (child) {
|
|
5674
|
+
walkNode11(child, context);
|
|
5675
|
+
}
|
|
5676
|
+
}
|
|
5677
|
+
}
|
|
5678
|
+
function processNode11(node, context) {
|
|
5679
|
+
switch (node.type) {
|
|
5680
|
+
case "namespace_definition":
|
|
5681
|
+
processNamespaceDefinition2(node, context);
|
|
5682
|
+
break;
|
|
5683
|
+
case "namespace_use_declaration":
|
|
5684
|
+
processUseDeclaration2(node, context);
|
|
5685
|
+
break;
|
|
5686
|
+
case "class_declaration":
|
|
5687
|
+
processClassDeclaration6(node, context);
|
|
5688
|
+
break;
|
|
5689
|
+
case "interface_declaration":
|
|
5690
|
+
processInterfaceDeclaration4(node, context);
|
|
5691
|
+
break;
|
|
5692
|
+
case "trait_declaration":
|
|
5693
|
+
processTraitDeclaration(node, context);
|
|
5694
|
+
break;
|
|
5695
|
+
case "enum_declaration":
|
|
5696
|
+
processEnumDeclaration4(node, context);
|
|
5697
|
+
break;
|
|
5698
|
+
case "function_definition":
|
|
5699
|
+
processFunctionDefinition4(node, context);
|
|
5700
|
+
break;
|
|
5701
|
+
case "method_declaration":
|
|
5702
|
+
processMethodDeclaration4(node, context);
|
|
5703
|
+
break;
|
|
5704
|
+
case "property_declaration":
|
|
5705
|
+
processPropertyDeclaration3(node, context);
|
|
5706
|
+
break;
|
|
5707
|
+
case "const_declaration":
|
|
5708
|
+
processConstDeclaration2(node, context);
|
|
5709
|
+
break;
|
|
5710
|
+
case "function_call_expression":
|
|
5711
|
+
processCallExpression11(node, context);
|
|
5712
|
+
break;
|
|
5713
|
+
case "member_call_expression":
|
|
5714
|
+
processMemberCallExpression(node, context);
|
|
5715
|
+
break;
|
|
5716
|
+
case "scoped_call_expression":
|
|
5717
|
+
processScopedCallExpression(node, context);
|
|
5718
|
+
break;
|
|
5719
|
+
case "include_expression":
|
|
5720
|
+
case "include_once_expression":
|
|
5721
|
+
case "require_expression":
|
|
5722
|
+
case "require_once_expression":
|
|
5723
|
+
processIncludeRequire(node, context);
|
|
5724
|
+
break;
|
|
5725
|
+
}
|
|
5726
|
+
}
|
|
5727
|
+
function processNamespaceDefinition2(node, context) {
|
|
5728
|
+
const nameNode = findChildByType11(node, "namespace_name");
|
|
5729
|
+
if (!nameNode) return;
|
|
5730
|
+
const name = nodeText10(nameNode, context);
|
|
5731
|
+
const symbolId = `${context.filePath}::${name}`;
|
|
5732
|
+
context.symbols.push({
|
|
5733
|
+
id: symbolId,
|
|
5734
|
+
name,
|
|
5735
|
+
kind: "module",
|
|
5736
|
+
filePath: context.filePath,
|
|
5737
|
+
startLine: node.startPosition.row + 1,
|
|
5738
|
+
endLine: node.endPosition.row + 1,
|
|
5739
|
+
exported: true
|
|
5740
|
+
});
|
|
5741
|
+
context.currentNamespace = name;
|
|
5742
|
+
}
|
|
5743
|
+
function processUseDeclaration2(node, context) {
|
|
5744
|
+
const clauses = findChildrenByType5(node, "namespace_use_clause");
|
|
5745
|
+
for (const clause of clauses) {
|
|
5746
|
+
const nameNode = findChildByType11(clause, "namespace_name") || findChildByType11(clause, "qualified_name");
|
|
5747
|
+
if (!nameNode) continue;
|
|
5748
|
+
const importPath = nodeText10(nameNode, context);
|
|
5749
|
+
const aliasNode = findChildByType11(clause, "namespace_aliasing_clause");
|
|
5750
|
+
const alias = aliasNode ? nodeText10(findChildByType11(aliasNode, "name") || aliasNode, context).trim() : null;
|
|
5751
|
+
const parts = importPath.split("\\");
|
|
5752
|
+
const simpleName = alias || parts[parts.length - 1];
|
|
5753
|
+
const resolvedPath = resolvePhpImport(importPath, context.filePath, context.projectRoot);
|
|
5754
|
+
if (resolvedPath) {
|
|
5755
|
+
const sourceId = `${context.filePath}::__file__`;
|
|
5756
|
+
const targetId = `${resolvedPath}::__file__`;
|
|
5757
|
+
context.edges.push({
|
|
5758
|
+
source: sourceId,
|
|
5759
|
+
target: targetId,
|
|
5760
|
+
kind: "imports",
|
|
5761
|
+
filePath: context.filePath,
|
|
5762
|
+
line: node.startPosition.row + 1
|
|
5763
|
+
});
|
|
5764
|
+
context.imports.set(simpleName, `${resolvedPath}::${parts[parts.length - 1]}`);
|
|
5765
|
+
}
|
|
5766
|
+
const symbolId = `${context.filePath}::import:${importPath}`;
|
|
5767
|
+
context.symbols.push({
|
|
5768
|
+
id: symbolId,
|
|
5769
|
+
name: importPath,
|
|
5770
|
+
kind: "import",
|
|
5771
|
+
filePath: context.filePath,
|
|
5772
|
+
startLine: node.startPosition.row + 1,
|
|
5773
|
+
endLine: node.endPosition.row + 1,
|
|
5774
|
+
exported: false
|
|
5775
|
+
});
|
|
5776
|
+
}
|
|
5777
|
+
}
|
|
5778
|
+
function processClassDeclaration6(node, context) {
|
|
5779
|
+
const nameNode = findChildByType11(node, "name");
|
|
5780
|
+
if (!nameNode) return;
|
|
5781
|
+
const name = nodeText10(nameNode, context);
|
|
5782
|
+
const symbolId = `${context.filePath}::${name}`;
|
|
5783
|
+
const text = nodeText10(node, context);
|
|
5784
|
+
const isAbstract = text.trimStart().startsWith("abstract");
|
|
5785
|
+
context.symbols.push({
|
|
5786
|
+
id: symbolId,
|
|
5787
|
+
name,
|
|
5788
|
+
kind: "class",
|
|
5789
|
+
filePath: context.filePath,
|
|
5790
|
+
startLine: node.startPosition.row + 1,
|
|
5791
|
+
endLine: node.endPosition.row + 1,
|
|
5792
|
+
exported: true,
|
|
5793
|
+
scope: context.currentClass || void 0
|
|
5794
|
+
});
|
|
5795
|
+
const baseClause = findChildByType11(node, "base_clause");
|
|
5796
|
+
if (baseClause) {
|
|
5797
|
+
const baseName = extractQualifiedName(baseClause, context);
|
|
5798
|
+
if (baseName) {
|
|
5799
|
+
const baseId = resolveSymbol10(baseName, context);
|
|
5800
|
+
if (baseId) {
|
|
5801
|
+
context.edges.push({
|
|
5802
|
+
source: symbolId,
|
|
5803
|
+
target: baseId,
|
|
5804
|
+
kind: "inherits",
|
|
5805
|
+
filePath: context.filePath,
|
|
5806
|
+
line: node.startPosition.row + 1
|
|
5807
|
+
});
|
|
5808
|
+
}
|
|
5809
|
+
}
|
|
5810
|
+
}
|
|
5811
|
+
const interfaceClause = findChildByType11(node, "class_interface_clause");
|
|
5812
|
+
if (interfaceClause) {
|
|
5813
|
+
const names = findChildrenByType5(interfaceClause, "name");
|
|
5814
|
+
const qualifiedNames = findChildrenByType5(interfaceClause, "qualified_name");
|
|
5815
|
+
for (const n of [...names, ...qualifiedNames]) {
|
|
5816
|
+
const ifaceName = nodeText10(n, context).trim();
|
|
5817
|
+
if (ifaceName && ifaceName !== ",") {
|
|
5818
|
+
const ifaceId = resolveSymbol10(ifaceName, context);
|
|
5819
|
+
if (ifaceId) {
|
|
5820
|
+
context.edges.push({
|
|
5821
|
+
source: symbolId,
|
|
5822
|
+
target: ifaceId,
|
|
5823
|
+
kind: "implements",
|
|
5824
|
+
filePath: context.filePath,
|
|
5825
|
+
line: node.startPosition.row + 1
|
|
5826
|
+
});
|
|
5827
|
+
}
|
|
5828
|
+
}
|
|
5829
|
+
}
|
|
5830
|
+
}
|
|
5831
|
+
const oldClass = context.currentClass;
|
|
5832
|
+
context.currentClass = name;
|
|
5833
|
+
context.currentScope.push(name);
|
|
5834
|
+
const body = findChildByType11(node, "declaration_list");
|
|
5835
|
+
if (body) {
|
|
5836
|
+
walkNode11(body, context);
|
|
5837
|
+
}
|
|
5838
|
+
context.currentScope.pop();
|
|
5839
|
+
context.currentClass = oldClass;
|
|
5840
|
+
}
|
|
5841
|
+
function processInterfaceDeclaration4(node, context) {
|
|
5842
|
+
const nameNode = findChildByType11(node, "name");
|
|
5843
|
+
if (!nameNode) return;
|
|
5844
|
+
const name = nodeText10(nameNode, context);
|
|
5845
|
+
const symbolId = `${context.filePath}::${name}`;
|
|
5846
|
+
context.symbols.push({
|
|
5847
|
+
id: symbolId,
|
|
5848
|
+
name,
|
|
5849
|
+
kind: "interface",
|
|
5850
|
+
filePath: context.filePath,
|
|
5851
|
+
startLine: node.startPosition.row + 1,
|
|
5852
|
+
endLine: node.endPosition.row + 1,
|
|
5853
|
+
exported: true,
|
|
5854
|
+
scope: context.currentClass || void 0
|
|
5855
|
+
});
|
|
5856
|
+
const oldClass = context.currentClass;
|
|
5857
|
+
context.currentClass = name;
|
|
5858
|
+
context.currentScope.push(name);
|
|
5859
|
+
const body = findChildByType11(node, "declaration_list");
|
|
5860
|
+
if (body) {
|
|
5861
|
+
walkNode11(body, context);
|
|
5862
|
+
}
|
|
5863
|
+
context.currentScope.pop();
|
|
5864
|
+
context.currentClass = oldClass;
|
|
5865
|
+
}
|
|
5866
|
+
function processTraitDeclaration(node, context) {
|
|
5867
|
+
const nameNode = findChildByType11(node, "name");
|
|
5868
|
+
if (!nameNode) return;
|
|
5869
|
+
const name = nodeText10(nameNode, context);
|
|
5870
|
+
const symbolId = `${context.filePath}::${name}`;
|
|
5871
|
+
context.symbols.push({
|
|
5872
|
+
id: symbolId,
|
|
5873
|
+
name,
|
|
5874
|
+
kind: "class",
|
|
5875
|
+
// Traits map to class kind
|
|
5876
|
+
filePath: context.filePath,
|
|
5877
|
+
startLine: node.startPosition.row + 1,
|
|
5878
|
+
endLine: node.endPosition.row + 1,
|
|
5879
|
+
exported: true,
|
|
5880
|
+
scope: context.currentClass || void 0
|
|
5881
|
+
});
|
|
5882
|
+
const oldClass = context.currentClass;
|
|
5883
|
+
context.currentClass = name;
|
|
5884
|
+
context.currentScope.push(name);
|
|
5885
|
+
const body = findChildByType11(node, "declaration_list");
|
|
5886
|
+
if (body) {
|
|
5887
|
+
walkNode11(body, context);
|
|
5888
|
+
}
|
|
5889
|
+
context.currentScope.pop();
|
|
5890
|
+
context.currentClass = oldClass;
|
|
5891
|
+
}
|
|
5892
|
+
function processEnumDeclaration4(node, context) {
|
|
5893
|
+
const nameNode = findChildByType11(node, "name");
|
|
5894
|
+
if (!nameNode) return;
|
|
5895
|
+
const name = nodeText10(nameNode, context);
|
|
5896
|
+
const symbolId = `${context.filePath}::${name}`;
|
|
5897
|
+
context.symbols.push({
|
|
5898
|
+
id: symbolId,
|
|
5899
|
+
name,
|
|
5900
|
+
kind: "enum",
|
|
5901
|
+
filePath: context.filePath,
|
|
5902
|
+
startLine: node.startPosition.row + 1,
|
|
5903
|
+
endLine: node.endPosition.row + 1,
|
|
5904
|
+
exported: true
|
|
5905
|
+
});
|
|
5906
|
+
const oldClass = context.currentClass;
|
|
5907
|
+
context.currentClass = name;
|
|
5908
|
+
context.currentScope.push(name);
|
|
5909
|
+
const body = findChildByType11(node, "declaration_list");
|
|
5910
|
+
if (body) {
|
|
5911
|
+
walkNode11(body, context);
|
|
5912
|
+
}
|
|
5913
|
+
context.currentScope.pop();
|
|
5914
|
+
context.currentClass = oldClass;
|
|
5915
|
+
}
|
|
5916
|
+
function processFunctionDefinition4(node, context) {
|
|
5917
|
+
const nameNode = findChildByType11(node, "name");
|
|
5918
|
+
if (!nameNode) return;
|
|
5919
|
+
const name = nodeText10(nameNode, context);
|
|
5920
|
+
const scope = context.currentClass || void 0;
|
|
5921
|
+
const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
|
|
5922
|
+
context.symbols.push({
|
|
5923
|
+
id: symbolId,
|
|
5924
|
+
name,
|
|
5925
|
+
kind: "function",
|
|
5926
|
+
filePath: context.filePath,
|
|
5927
|
+
startLine: node.startPosition.row + 1,
|
|
5928
|
+
endLine: node.endPosition.row + 1,
|
|
5929
|
+
exported: true,
|
|
5930
|
+
scope
|
|
5931
|
+
});
|
|
5932
|
+
const scopeName = scope ? `${scope}.${name}` : name;
|
|
5933
|
+
context.currentScope.push(scopeName);
|
|
5934
|
+
const body = findChildByType11(node, "compound_statement");
|
|
5935
|
+
if (body) {
|
|
5936
|
+
walkNode11(body, context);
|
|
5937
|
+
}
|
|
5938
|
+
context.currentScope.pop();
|
|
5939
|
+
}
|
|
5940
|
+
function processMethodDeclaration4(node, context) {
|
|
5941
|
+
const nameNode = findChildByType11(node, "name");
|
|
5942
|
+
if (!nameNode) return;
|
|
5943
|
+
const name = nodeText10(nameNode, context);
|
|
5944
|
+
const modifiers = getModifiers2(node, context);
|
|
5945
|
+
const exported = !modifiers.includes("private");
|
|
5946
|
+
const scope = context.currentClass || void 0;
|
|
5947
|
+
const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
|
|
5948
|
+
context.symbols.push({
|
|
5949
|
+
id: symbolId,
|
|
5950
|
+
name,
|
|
5951
|
+
kind: "method",
|
|
5952
|
+
filePath: context.filePath,
|
|
5953
|
+
startLine: node.startPosition.row + 1,
|
|
5954
|
+
endLine: node.endPosition.row + 1,
|
|
5955
|
+
exported,
|
|
5956
|
+
scope
|
|
5957
|
+
});
|
|
5958
|
+
const scopeName = scope ? `${scope}.${name}` : name;
|
|
5959
|
+
context.currentScope.push(scopeName);
|
|
5960
|
+
const body = findChildByType11(node, "compound_statement");
|
|
5961
|
+
if (body) {
|
|
5962
|
+
walkNode11(body, context);
|
|
5963
|
+
}
|
|
5964
|
+
context.currentScope.pop();
|
|
5965
|
+
}
|
|
5966
|
+
function processPropertyDeclaration3(node, context) {
|
|
5967
|
+
const varNode = findDescendantByTypes3(node, ["variable_name"]);
|
|
5968
|
+
if (!varNode) return;
|
|
5969
|
+
const name = nodeText10(varNode, context).replace(/^\$/, "");
|
|
5970
|
+
const modifiers = getModifiers2(node, context);
|
|
5971
|
+
const exported = !modifiers.includes("private");
|
|
5972
|
+
const scope = context.currentClass || void 0;
|
|
5973
|
+
const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
|
|
5974
|
+
context.symbols.push({
|
|
5975
|
+
id: symbolId,
|
|
5976
|
+
name,
|
|
5977
|
+
kind: "property",
|
|
5978
|
+
filePath: context.filePath,
|
|
5979
|
+
startLine: node.startPosition.row + 1,
|
|
5980
|
+
endLine: node.endPosition.row + 1,
|
|
5981
|
+
exported,
|
|
5982
|
+
scope
|
|
5983
|
+
});
|
|
5984
|
+
}
|
|
5985
|
+
function processConstDeclaration2(node, context) {
|
|
5986
|
+
const elements = findChildrenByType5(node, "const_element");
|
|
5987
|
+
for (const elem of elements) {
|
|
5988
|
+
const nameNode = findChildByType11(elem, "name");
|
|
5989
|
+
if (!nameNode) continue;
|
|
5990
|
+
const name = nodeText10(nameNode, context);
|
|
5991
|
+
const scope = context.currentClass || void 0;
|
|
5992
|
+
const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
|
|
5993
|
+
context.symbols.push({
|
|
5994
|
+
id: symbolId,
|
|
5995
|
+
name,
|
|
5996
|
+
kind: "constant",
|
|
5997
|
+
filePath: context.filePath,
|
|
5998
|
+
startLine: elem.startPosition.row + 1,
|
|
5999
|
+
endLine: elem.endPosition.row + 1,
|
|
6000
|
+
exported: true,
|
|
6001
|
+
scope
|
|
6002
|
+
});
|
|
6003
|
+
}
|
|
6004
|
+
}
|
|
6005
|
+
function processCallExpression11(node, context) {
|
|
6006
|
+
if (context.currentScope.length === 0) return;
|
|
6007
|
+
const firstChild = node.child(0);
|
|
6008
|
+
if (!firstChild) return;
|
|
6009
|
+
let calleeName = null;
|
|
6010
|
+
if (firstChild.type === "name") {
|
|
6011
|
+
calleeName = nodeText10(firstChild, context);
|
|
6012
|
+
} else if (firstChild.type === "qualified_name") {
|
|
6013
|
+
const parts = nodeText10(firstChild, context).split("\\");
|
|
6014
|
+
calleeName = parts[parts.length - 1];
|
|
6015
|
+
}
|
|
6016
|
+
if (!calleeName) return;
|
|
6017
|
+
const builtins = /* @__PURE__ */ new Set([
|
|
6018
|
+
"echo",
|
|
6019
|
+
"print",
|
|
6020
|
+
"var_dump",
|
|
6021
|
+
"print_r",
|
|
6022
|
+
"isset",
|
|
6023
|
+
"unset",
|
|
6024
|
+
"empty",
|
|
6025
|
+
"array",
|
|
6026
|
+
"list",
|
|
6027
|
+
"count",
|
|
6028
|
+
"strlen",
|
|
6029
|
+
"strpos",
|
|
6030
|
+
"substr",
|
|
6031
|
+
"explode",
|
|
6032
|
+
"implode",
|
|
6033
|
+
"array_map",
|
|
6034
|
+
"array_filter",
|
|
6035
|
+
"array_merge",
|
|
6036
|
+
"array_push",
|
|
6037
|
+
"array_pop",
|
|
6038
|
+
"array_shift",
|
|
6039
|
+
"array_unshift",
|
|
6040
|
+
"array_keys",
|
|
6041
|
+
"array_values",
|
|
6042
|
+
"in_array",
|
|
6043
|
+
"json_encode",
|
|
6044
|
+
"json_decode",
|
|
6045
|
+
"sprintf",
|
|
6046
|
+
"printf",
|
|
6047
|
+
"is_array",
|
|
6048
|
+
"is_string",
|
|
6049
|
+
"is_int",
|
|
6050
|
+
"is_null",
|
|
6051
|
+
"is_bool",
|
|
6052
|
+
"intval",
|
|
6053
|
+
"floatval",
|
|
6054
|
+
"strval",
|
|
6055
|
+
"boolval",
|
|
6056
|
+
"trim",
|
|
6057
|
+
"ltrim",
|
|
6058
|
+
"rtrim",
|
|
6059
|
+
"strtolower",
|
|
6060
|
+
"strtoupper",
|
|
6061
|
+
"str_replace",
|
|
6062
|
+
"preg_match",
|
|
6063
|
+
"preg_replace",
|
|
6064
|
+
"file_exists",
|
|
6065
|
+
"is_file",
|
|
6066
|
+
"is_dir",
|
|
6067
|
+
"dirname",
|
|
6068
|
+
"basename",
|
|
6069
|
+
"date",
|
|
6070
|
+
"time",
|
|
6071
|
+
"strtotime",
|
|
6072
|
+
"compact",
|
|
6073
|
+
"extract",
|
|
6074
|
+
"defined",
|
|
6075
|
+
"define",
|
|
6076
|
+
"class_exists",
|
|
6077
|
+
"function_exists",
|
|
6078
|
+
"throw",
|
|
6079
|
+
"die",
|
|
6080
|
+
"exit"
|
|
6081
|
+
]);
|
|
6082
|
+
if (builtins.has(calleeName)) return;
|
|
6083
|
+
const callerId = getCurrentSymbolId11(context);
|
|
6084
|
+
if (!callerId) return;
|
|
6085
|
+
const calleeId = resolveSymbol10(calleeName, context);
|
|
6086
|
+
if (calleeId) {
|
|
6087
|
+
context.edges.push({
|
|
6088
|
+
source: callerId,
|
|
6089
|
+
target: calleeId,
|
|
6090
|
+
kind: "calls",
|
|
6091
|
+
filePath: context.filePath,
|
|
6092
|
+
line: node.startPosition.row + 1
|
|
6093
|
+
});
|
|
6094
|
+
}
|
|
6095
|
+
}
|
|
6096
|
+
function processMemberCallExpression(node, context) {
|
|
6097
|
+
}
|
|
6098
|
+
function processScopedCallExpression(node, context) {
|
|
6099
|
+
}
|
|
6100
|
+
function processIncludeRequire(node, context) {
|
|
6101
|
+
const text = nodeText10(node, context);
|
|
6102
|
+
const pathMatch = text.match(/['"]([^'"]+)['"]/);
|
|
6103
|
+
if (!pathMatch) return;
|
|
6104
|
+
const includePath = pathMatch[1];
|
|
6105
|
+
const resolvedPath = resolvePhpInclude(includePath, context.filePath, context.projectRoot);
|
|
6106
|
+
if (resolvedPath) {
|
|
6107
|
+
const sourceId = `${context.filePath}::__file__`;
|
|
6108
|
+
const targetId = `${resolvedPath}::__file__`;
|
|
6109
|
+
context.edges.push({
|
|
6110
|
+
source: sourceId,
|
|
6111
|
+
target: targetId,
|
|
6112
|
+
kind: "imports",
|
|
6113
|
+
filePath: context.filePath,
|
|
6114
|
+
line: node.startPosition.row + 1
|
|
6115
|
+
});
|
|
6116
|
+
}
|
|
6117
|
+
}
|
|
6118
|
+
function resolvePhpImport(importPath, currentFile, projectRoot) {
|
|
6119
|
+
const parts = importPath.split("\\");
|
|
6120
|
+
const filePath = parts.join("/") + ".php";
|
|
6121
|
+
const sourceRoots = [
|
|
6122
|
+
"",
|
|
6123
|
+
"src",
|
|
6124
|
+
"app",
|
|
6125
|
+
"lib",
|
|
6126
|
+
"includes",
|
|
6127
|
+
"wp-content/plugins",
|
|
6128
|
+
"wp-content/themes"
|
|
6129
|
+
];
|
|
6130
|
+
for (const root of sourceRoots) {
|
|
6131
|
+
const candidate = root ? join12(root, filePath) : filePath;
|
|
6132
|
+
const fullPath = join12(projectRoot, candidate);
|
|
6133
|
+
if (existsSync12(fullPath)) {
|
|
6134
|
+
return candidate;
|
|
6135
|
+
}
|
|
6136
|
+
const loweredParts = [...parts];
|
|
6137
|
+
loweredParts[0] = loweredParts[0].toLowerCase();
|
|
6138
|
+
const loweredFilePath = loweredParts.join("/") + ".php";
|
|
6139
|
+
const loweredCandidate = root ? join12(root, loweredFilePath) : loweredFilePath;
|
|
6140
|
+
const loweredFullPath = join12(projectRoot, loweredCandidate);
|
|
6141
|
+
if (existsSync12(loweredFullPath)) {
|
|
6142
|
+
return loweredCandidate;
|
|
6143
|
+
}
|
|
6144
|
+
}
|
|
6145
|
+
return null;
|
|
6146
|
+
}
|
|
6147
|
+
function resolvePhpInclude(includePath, currentFile, projectRoot) {
|
|
6148
|
+
const currentDir = dirname11(join12(projectRoot, currentFile));
|
|
6149
|
+
const relativePath = join12(currentDir, includePath);
|
|
6150
|
+
const relativeToRoot = relativePath.replace(projectRoot + "/", "");
|
|
6151
|
+
if (existsSync12(relativePath)) {
|
|
6152
|
+
return relativeToRoot;
|
|
6153
|
+
}
|
|
6154
|
+
const fromRoot = join12(projectRoot, includePath);
|
|
6155
|
+
if (existsSync12(fromRoot)) {
|
|
6156
|
+
return includePath;
|
|
6157
|
+
}
|
|
6158
|
+
return null;
|
|
6159
|
+
}
|
|
6160
|
+
function resolveSymbol10(name, context) {
|
|
5553
6161
|
if (context.imports.has(name)) {
|
|
5554
6162
|
return context.imports.get(name) || null;
|
|
5555
6163
|
}
|
|
@@ -5565,40 +6173,34 @@ function resolveSymbol9(name, context) {
|
|
|
5565
6173
|
}
|
|
5566
6174
|
return null;
|
|
5567
6175
|
}
|
|
5568
|
-
function
|
|
6176
|
+
function getModifiers2(node, context) {
|
|
5569
6177
|
const modifiers = [];
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
if (text) modifiers.push(text);
|
|
5577
|
-
}
|
|
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());
|
|
5578
6184
|
}
|
|
5579
6185
|
}
|
|
5580
6186
|
return modifiers;
|
|
5581
6187
|
}
|
|
5582
|
-
function
|
|
5583
|
-
const
|
|
5584
|
-
if (!
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
if (parenIdx > 0) name = name.substring(0, parenIdx);
|
|
5590
|
-
const dotIdx = name.lastIndexOf(".");
|
|
5591
|
-
name = dotIdx >= 0 ? name.substring(dotIdx + 1) : name;
|
|
5592
|
-
return name.trim() || null;
|
|
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];
|
|
5593
6195
|
}
|
|
5594
|
-
function
|
|
6196
|
+
function findChildByType11(node, type) {
|
|
5595
6197
|
for (let i = 0; i < node.childCount; i++) {
|
|
5596
6198
|
const child = node.child(i);
|
|
5597
6199
|
if (child && child.type === type) return child;
|
|
5598
6200
|
}
|
|
5599
6201
|
return null;
|
|
5600
6202
|
}
|
|
5601
|
-
function
|
|
6203
|
+
function findChildrenByType5(node, type) {
|
|
5602
6204
|
const results = [];
|
|
5603
6205
|
for (let i = 0; i < node.childCount; i++) {
|
|
5604
6206
|
const child = node.child(i);
|
|
@@ -5606,27 +6208,27 @@ function findChildrenByType4(node, type) {
|
|
|
5606
6208
|
}
|
|
5607
6209
|
return results;
|
|
5608
6210
|
}
|
|
5609
|
-
function
|
|
6211
|
+
function findDescendantByTypes3(node, types) {
|
|
5610
6212
|
for (let i = 0; i < node.childCount; i++) {
|
|
5611
6213
|
const child = node.child(i);
|
|
5612
6214
|
if (!child) continue;
|
|
5613
6215
|
if (types.includes(child.type)) return child;
|
|
5614
|
-
const found =
|
|
6216
|
+
const found = findDescendantByTypes3(child, types);
|
|
5615
6217
|
if (found) return found;
|
|
5616
6218
|
}
|
|
5617
6219
|
return null;
|
|
5618
6220
|
}
|
|
5619
|
-
function
|
|
6221
|
+
function nodeText10(node, context) {
|
|
5620
6222
|
return context.sourceCode.substring(node.startIndex, node.endIndex);
|
|
5621
6223
|
}
|
|
5622
|
-
function
|
|
6224
|
+
function getCurrentSymbolId11(context) {
|
|
5623
6225
|
if (context.currentScope.length === 0) return null;
|
|
5624
6226
|
return `${context.filePath}::${context.currentScope[context.currentScope.length - 1]}`;
|
|
5625
6227
|
}
|
|
5626
|
-
var
|
|
5627
|
-
name: "
|
|
5628
|
-
extensions: [".
|
|
5629
|
-
parseFile:
|
|
6228
|
+
var phpParser = {
|
|
6229
|
+
name: "php",
|
|
6230
|
+
extensions: [".php"],
|
|
6231
|
+
parseFile: parsePhpFile
|
|
5630
6232
|
};
|
|
5631
6233
|
|
|
5632
6234
|
// src/parser/detect.ts
|
|
@@ -5640,12 +6242,13 @@ var parsers = [
|
|
|
5640
6242
|
csharpParser,
|
|
5641
6243
|
javaParser,
|
|
5642
6244
|
cppParser,
|
|
5643
|
-
kotlinParser
|
|
6245
|
+
kotlinParser,
|
|
6246
|
+
phpParser
|
|
5644
6247
|
];
|
|
5645
6248
|
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/;
|
|
5646
6249
|
function getParserForFile(filePath, content) {
|
|
5647
|
-
const ext =
|
|
5648
|
-
const fileName =
|
|
6250
|
+
const ext = extname8(filePath).toLowerCase();
|
|
6251
|
+
const fileName = basename6(filePath);
|
|
5649
6252
|
if (ext === ".h" && content) {
|
|
5650
6253
|
if (CPP_KEYWORDS.test(content)) {
|
|
5651
6254
|
return cppParser;
|
|
@@ -5663,7 +6266,7 @@ import { minimatch } from "minimatch";
|
|
|
5663
6266
|
var MAX_FILE_SIZE = 1e6;
|
|
5664
6267
|
function shouldParseFile(fullPath) {
|
|
5665
6268
|
try {
|
|
5666
|
-
const stats =
|
|
6269
|
+
const stats = statSync7(fullPath);
|
|
5667
6270
|
if (stats.size > MAX_FILE_SIZE) {
|
|
5668
6271
|
console.error(`[Parser] Skipping ${fullPath} \u2014 file too large (${(stats.size / 1024).toFixed(0)}KB)`);
|
|
5669
6272
|
return false;
|
|
@@ -5681,8 +6284,8 @@ async function parseProject(projectRoot, options) {
|
|
|
5681
6284
|
let errorFiles = 0;
|
|
5682
6285
|
for (const file of files) {
|
|
5683
6286
|
try {
|
|
5684
|
-
const fullPath =
|
|
5685
|
-
if (!
|
|
6287
|
+
const fullPath = join13(projectRoot, file);
|
|
6288
|
+
if (!resolve7(fullPath).startsWith(resolve7(projectRoot))) {
|
|
5686
6289
|
skippedFiles++;
|
|
5687
6290
|
continue;
|
|
5688
6291
|
}
|
|
@@ -5705,7 +6308,7 @@ async function parseProject(projectRoot, options) {
|
|
|
5705
6308
|
if (options?.verbose) {
|
|
5706
6309
|
console.error(`[Parser] Parsing: ${file}`);
|
|
5707
6310
|
}
|
|
5708
|
-
const sourceCode =
|
|
6311
|
+
const sourceCode = readFileSync10(fullPath, "utf-8");
|
|
5709
6312
|
const parser = getParserForFile(file, sourceCode);
|
|
5710
6313
|
if (!parser) {
|
|
5711
6314
|
console.error(`No parser found for file: ${file}`);
|
|
@@ -5734,8 +6337,8 @@ async function parseProject(projectRoot, options) {
|
|
|
5734
6337
|
}
|
|
5735
6338
|
|
|
5736
6339
|
// src/cross-language/detectors/rest-api.ts
|
|
5737
|
-
import { readFileSync as
|
|
5738
|
-
import { join as
|
|
6340
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
6341
|
+
import { join as join14, resolve as resolve8 } from "path";
|
|
5739
6342
|
function getLanguage(filePath) {
|
|
5740
6343
|
if (filePath.endsWith(".ts") || filePath.endsWith(".tsx")) return "typescript";
|
|
5741
6344
|
if (filePath.endsWith(".js") || filePath.endsWith(".jsx") || filePath.endsWith(".mjs") || filePath.endsWith(".cjs")) return "javascript";
|
|
@@ -5744,6 +6347,7 @@ function getLanguage(filePath) {
|
|
|
5744
6347
|
if (filePath.endsWith(".cs") || filePath.endsWith(".csx")) return "csharp";
|
|
5745
6348
|
if (filePath.endsWith(".java")) return "java";
|
|
5746
6349
|
if (filePath.endsWith(".kt") || filePath.endsWith(".kts")) return "kotlin";
|
|
6350
|
+
if (filePath.endsWith(".php")) return "php";
|
|
5747
6351
|
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";
|
|
5748
6352
|
return "unknown";
|
|
5749
6353
|
}
|
|
@@ -6102,6 +6706,67 @@ function extractRouteDefinitions(source, filePath) {
|
|
|
6102
6706
|
if (!path6.startsWith("/")) path6 = "/" + path6;
|
|
6103
6707
|
}
|
|
6104
6708
|
}
|
|
6709
|
+
if (lang === "php") {
|
|
6710
|
+
const laravelRouteMatch = line.match(/Route\s*::\s*(get|post|put|delete|patch)\s*\(\s*['"]([^'"]+)['"]/i);
|
|
6711
|
+
if (laravelRouteMatch) {
|
|
6712
|
+
const path6 = laravelRouteMatch[2];
|
|
6713
|
+
if (path6.startsWith("/")) {
|
|
6714
|
+
routes.push({
|
|
6715
|
+
method: laravelRouteMatch[1].toUpperCase(),
|
|
6716
|
+
path: path6,
|
|
6717
|
+
normalizedPath: normalizePath(path6),
|
|
6718
|
+
file: filePath,
|
|
6719
|
+
line: i + 1
|
|
6720
|
+
});
|
|
6721
|
+
}
|
|
6722
|
+
}
|
|
6723
|
+
const symfonyRouteMatch = line.match(/#\[Route\s*\(\s*['"]([^'"]+)['"]/);
|
|
6724
|
+
if (symfonyRouteMatch) {
|
|
6725
|
+
const path6 = symfonyRouteMatch[1];
|
|
6726
|
+
if (path6.startsWith("/")) {
|
|
6727
|
+
const methodsMatch = line.match(/methods\s*:\s*\[([^\]]+)\]/);
|
|
6728
|
+
const methods = methodsMatch ? methodsMatch[1].match(/['"](\w+)['"]/g)?.map((m) => m.replace(/['"]/g, "").toUpperCase()) || ["ANY"] : ["ANY"];
|
|
6729
|
+
for (const method of methods) {
|
|
6730
|
+
routes.push({
|
|
6731
|
+
method,
|
|
6732
|
+
path: path6,
|
|
6733
|
+
normalizedPath: normalizePath(path6),
|
|
6734
|
+
file: filePath,
|
|
6735
|
+
line: i + 1
|
|
6736
|
+
});
|
|
6737
|
+
}
|
|
6738
|
+
}
|
|
6739
|
+
}
|
|
6740
|
+
const slimMatch = line.match(/\$(?:app|group)\s*->\s*(get|post|put|delete|patch)\s*\(\s*['"]([^'"]+)['"]/i);
|
|
6741
|
+
if (slimMatch) {
|
|
6742
|
+
const path6 = slimMatch[2];
|
|
6743
|
+
if (path6.startsWith("/")) {
|
|
6744
|
+
routes.push({
|
|
6745
|
+
method: slimMatch[1].toUpperCase(),
|
|
6746
|
+
path: path6,
|
|
6747
|
+
normalizedPath: normalizePath(path6),
|
|
6748
|
+
file: filePath,
|
|
6749
|
+
line: i + 1
|
|
6750
|
+
});
|
|
6751
|
+
}
|
|
6752
|
+
}
|
|
6753
|
+
const wpRestMatch = line.match(/register_rest_route\s*\(\s*['"]([^'"]+)['"]\s*,\s*['"]([^'"]+)['"]/);
|
|
6754
|
+
if (wpRestMatch) {
|
|
6755
|
+
const namespace = wpRestMatch[1];
|
|
6756
|
+
let path6 = wpRestMatch[2];
|
|
6757
|
+
if (!path6.startsWith("/")) path6 = "/" + path6;
|
|
6758
|
+
const fullPath = `/wp-json/${namespace}${path6}`;
|
|
6759
|
+
const methodMatch = line.match(/methods\s*['"=>\s]+['"](\w+)['"]/i);
|
|
6760
|
+
const method = methodMatch ? methodMatch[1].toUpperCase() : "ANY";
|
|
6761
|
+
routes.push({
|
|
6762
|
+
method,
|
|
6763
|
+
path: fullPath,
|
|
6764
|
+
normalizedPath: normalizePath(fullPath),
|
|
6765
|
+
file: filePath,
|
|
6766
|
+
line: i + 1
|
|
6767
|
+
});
|
|
6768
|
+
}
|
|
6769
|
+
}
|
|
6105
6770
|
if (lang === "cpp") {
|
|
6106
6771
|
const crowMatch = line.match(/CROW_ROUTE\s*\(\s*\w+\s*,\s*"([^"]+)"/);
|
|
6107
6772
|
if (crowMatch) {
|
|
@@ -6216,11 +6881,11 @@ function detectRestApiEdges(files, projectRoot) {
|
|
|
6216
6881
|
const allCalls = [];
|
|
6217
6882
|
const allRoutes = [];
|
|
6218
6883
|
for (const file of files) {
|
|
6219
|
-
const fullPath =
|
|
6220
|
-
if (!
|
|
6884
|
+
const fullPath = join14(projectRoot, file.filePath);
|
|
6885
|
+
if (!resolve8(fullPath).startsWith(resolve8(projectRoot))) continue;
|
|
6221
6886
|
let source;
|
|
6222
6887
|
try {
|
|
6223
|
-
source =
|
|
6888
|
+
source = readFileSync11(fullPath, "utf-8");
|
|
6224
6889
|
} catch {
|
|
6225
6890
|
continue;
|
|
6226
6891
|
}
|
|
@@ -6240,6 +6905,34 @@ function detectRestApiEdges(files, projectRoot) {
|
|
|
6240
6905
|
}
|
|
6241
6906
|
}
|
|
6242
6907
|
}
|
|
6908
|
+
if (lang === "php") {
|
|
6909
|
+
const phpLines = source.split("\n");
|
|
6910
|
+
for (let i = 0; i < phpLines.length; i++) {
|
|
6911
|
+
const line = phpLines[i];
|
|
6912
|
+
const guzzleMatch = line.match(/\$\w+\s*->\s*(get|post|put|delete|patch|request)\s*\(\s*['"]([^'"]+)['"]/i);
|
|
6913
|
+
if (guzzleMatch) {
|
|
6914
|
+
let method = guzzleMatch[1].toUpperCase();
|
|
6915
|
+
let path6 = guzzleMatch[2];
|
|
6916
|
+
if (method === "REQUEST") {
|
|
6917
|
+
const reqMethodMatch = line.match(/request\s*\(\s*['"](\w+)['"]\s*,\s*['"]([^'"]+)['"]/i);
|
|
6918
|
+
if (reqMethodMatch) {
|
|
6919
|
+
method = reqMethodMatch[1].toUpperCase();
|
|
6920
|
+
path6 = reqMethodMatch[2];
|
|
6921
|
+
}
|
|
6922
|
+
}
|
|
6923
|
+
if (isLocalApiPath(path6)) {
|
|
6924
|
+
allCalls.push({ method, path: cleanPath(path6), file: file.filePath, line: i + 1 });
|
|
6925
|
+
}
|
|
6926
|
+
}
|
|
6927
|
+
const fgcMatch = line.match(/file_get_contents\s*\(\s*['"]([^'"]+)['"]/);
|
|
6928
|
+
if (fgcMatch) {
|
|
6929
|
+
const path6 = fgcMatch[1];
|
|
6930
|
+
if (isLocalApiPath(path6)) {
|
|
6931
|
+
allCalls.push({ method: "GET", path: cleanPath(path6), file: file.filePath, line: i + 1 });
|
|
6932
|
+
}
|
|
6933
|
+
}
|
|
6934
|
+
}
|
|
6935
|
+
}
|
|
6243
6936
|
allRoutes.push(...extractRouteDefinitions(source, file.filePath));
|
|
6244
6937
|
}
|
|
6245
6938
|
for (const call of allCalls) {
|
|
@@ -6268,8 +6961,8 @@ function detectRestApiEdges(files, projectRoot) {
|
|
|
6268
6961
|
}
|
|
6269
6962
|
|
|
6270
6963
|
// src/cross-language/detectors/subprocess.ts
|
|
6271
|
-
import { readFileSync as
|
|
6272
|
-
import { join as
|
|
6964
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
6965
|
+
import { join as join15, resolve as resolve9, basename as basename7 } from "path";
|
|
6273
6966
|
var SCRIPT_EXTENSIONS = [".py", ".js", ".ts", ".go", ".rs"];
|
|
6274
6967
|
function getLanguage2(filePath) {
|
|
6275
6968
|
if (filePath.endsWith(".ts") || filePath.endsWith(".tsx")) return "typescript";
|
|
@@ -6368,16 +7061,16 @@ function detectSubprocessEdges(files, projectRoot) {
|
|
|
6368
7061
|
const knownFiles = new Set(files.map((f) => f.filePath));
|
|
6369
7062
|
const basenameMap = /* @__PURE__ */ new Map();
|
|
6370
7063
|
for (const f of files) {
|
|
6371
|
-
const base =
|
|
7064
|
+
const base = basename7(f.filePath);
|
|
6372
7065
|
if (!basenameMap.has(base)) basenameMap.set(base, []);
|
|
6373
7066
|
basenameMap.get(base).push(f.filePath);
|
|
6374
7067
|
}
|
|
6375
7068
|
for (const file of files) {
|
|
6376
|
-
const fullPath =
|
|
6377
|
-
if (!
|
|
7069
|
+
const fullPath = join15(projectRoot, file.filePath);
|
|
7070
|
+
if (!resolve9(fullPath).startsWith(resolve9(projectRoot))) continue;
|
|
6378
7071
|
let source;
|
|
6379
7072
|
try {
|
|
6380
|
-
source =
|
|
7073
|
+
source = readFileSync12(fullPath, "utf-8");
|
|
6381
7074
|
} catch {
|
|
6382
7075
|
continue;
|
|
6383
7076
|
}
|
|
@@ -6389,7 +7082,7 @@ function detectSubprocessEdges(files, projectRoot) {
|
|
|
6389
7082
|
targetFile = call.calledFile;
|
|
6390
7083
|
confidence = "high";
|
|
6391
7084
|
} else {
|
|
6392
|
-
const base =
|
|
7085
|
+
const base = basename7(call.calledFile);
|
|
6393
7086
|
const candidates = basenameMap.get(base);
|
|
6394
7087
|
if (candidates && candidates.length > 0) {
|
|
6395
7088
|
const exactCandidate = candidates.find((c) => c.endsWith(call.calledFile));
|
|
@@ -6768,7 +7461,7 @@ function getArchitectureSummary(graph) {
|
|
|
6768
7461
|
}
|
|
6769
7462
|
|
|
6770
7463
|
// src/health/metrics.ts
|
|
6771
|
-
import { dirname as
|
|
7464
|
+
import { dirname as dirname12 } from "path";
|
|
6772
7465
|
function scoreToGrade(score) {
|
|
6773
7466
|
if (score >= 90) return "A";
|
|
6774
7467
|
if (score >= 80) return "B";
|
|
@@ -6801,8 +7494,8 @@ function calculateCouplingScore(graph) {
|
|
|
6801
7494
|
totalEdges++;
|
|
6802
7495
|
fileConnections.set(sourceAttrs.filePath, (fileConnections.get(sourceAttrs.filePath) || 0) + 1);
|
|
6803
7496
|
fileConnections.set(targetAttrs.filePath, (fileConnections.get(targetAttrs.filePath) || 0) + 1);
|
|
6804
|
-
const sourceDir =
|
|
6805
|
-
const targetDir =
|
|
7497
|
+
const sourceDir = dirname12(sourceAttrs.filePath).split("/")[0];
|
|
7498
|
+
const targetDir = dirname12(targetAttrs.filePath).split("/")[0];
|
|
6806
7499
|
if (sourceDir !== targetDir) {
|
|
6807
7500
|
crossDirEdges++;
|
|
6808
7501
|
}
|
|
@@ -6849,8 +7542,8 @@ function calculateCohesionScore(graph) {
|
|
|
6849
7542
|
const sourceAttrs = graph.getNodeAttributes(source);
|
|
6850
7543
|
const targetAttrs = graph.getNodeAttributes(target);
|
|
6851
7544
|
if (sourceAttrs.filePath !== targetAttrs.filePath) {
|
|
6852
|
-
const sourceDir =
|
|
6853
|
-
const targetDir =
|
|
7545
|
+
const sourceDir = dirname12(sourceAttrs.filePath);
|
|
7546
|
+
const targetDir = dirname12(targetAttrs.filePath);
|
|
6854
7547
|
if (!dirEdges.has(sourceDir)) {
|
|
6855
7548
|
dirEdges.set(sourceDir, { internal: 0, total: 0 });
|
|
6856
7549
|
}
|
|
@@ -7132,8 +7825,8 @@ function calculateDepthScore(graph) {
|
|
|
7132
7825
|
}
|
|
7133
7826
|
|
|
7134
7827
|
// src/health/index.ts
|
|
7135
|
-
import { readFileSync as
|
|
7136
|
-
import { dirname as
|
|
7828
|
+
import { readFileSync as readFileSync13, writeFileSync, existsSync as existsSync13, mkdirSync } from "fs";
|
|
7829
|
+
import { dirname as dirname13, resolve as resolve10 } from "path";
|
|
7137
7830
|
function calculateHealthScore(graph, projectRoot) {
|
|
7138
7831
|
const coupling = calculateCouplingScore(graph);
|
|
7139
7832
|
const cohesion = calculateCohesionScore(graph);
|
|
@@ -7232,8 +7925,8 @@ function getHealthTrend(projectRoot, currentScore) {
|
|
|
7232
7925
|
}
|
|
7233
7926
|
}
|
|
7234
7927
|
function saveHealthHistory(projectRoot, report) {
|
|
7235
|
-
const resolvedRoot =
|
|
7236
|
-
const historyFile =
|
|
7928
|
+
const resolvedRoot = resolve10(projectRoot);
|
|
7929
|
+
const historyFile = resolve10(resolvedRoot, ".depwire", "health-history.json");
|
|
7237
7930
|
if (!historyFile.startsWith(resolvedRoot)) {
|
|
7238
7931
|
return;
|
|
7239
7932
|
}
|
|
@@ -7248,10 +7941,10 @@ function saveHealthHistory(projectRoot, report) {
|
|
|
7248
7941
|
}))
|
|
7249
7942
|
};
|
|
7250
7943
|
let history = [];
|
|
7251
|
-
if (
|
|
7944
|
+
if (existsSync13(historyFile)) {
|
|
7252
7945
|
try {
|
|
7253
7946
|
if (!historyFile.startsWith(resolvedRoot)) return;
|
|
7254
|
-
const content =
|
|
7947
|
+
const content = readFileSync13(historyFile, "utf-8");
|
|
7255
7948
|
history = JSON.parse(content);
|
|
7256
7949
|
} catch {
|
|
7257
7950
|
}
|
|
@@ -7260,19 +7953,19 @@ function saveHealthHistory(projectRoot, report) {
|
|
|
7260
7953
|
if (history.length > 50) {
|
|
7261
7954
|
history = history.slice(-50);
|
|
7262
7955
|
}
|
|
7263
|
-
mkdirSync(
|
|
7956
|
+
mkdirSync(dirname13(historyFile), { recursive: true });
|
|
7264
7957
|
if (!historyFile.startsWith(resolvedRoot)) return;
|
|
7265
7958
|
writeFileSync(historyFile, JSON.stringify(history, null, 2), "utf-8");
|
|
7266
7959
|
}
|
|
7267
7960
|
function loadHealthHistory(projectRoot) {
|
|
7268
|
-
const resolvedRoot =
|
|
7269
|
-
const historyFile =
|
|
7270
|
-
if (!historyFile.startsWith(resolvedRoot) || !
|
|
7961
|
+
const resolvedRoot = resolve10(projectRoot);
|
|
7962
|
+
const historyFile = resolve10(resolvedRoot, ".depwire", "health-history.json");
|
|
7963
|
+
if (!historyFile.startsWith(resolvedRoot) || !existsSync13(historyFile)) {
|
|
7271
7964
|
return [];
|
|
7272
7965
|
}
|
|
7273
7966
|
try {
|
|
7274
7967
|
if (!historyFile.startsWith(resolvedRoot)) return [];
|
|
7275
|
-
const content =
|
|
7968
|
+
const content = readFileSync13(historyFile, "utf-8");
|
|
7276
7969
|
return JSON.parse(content);
|
|
7277
7970
|
} catch {
|
|
7278
7971
|
return [];
|
|
@@ -7281,7 +7974,7 @@ function loadHealthHistory(projectRoot) {
|
|
|
7281
7974
|
|
|
7282
7975
|
// src/dead-code/detector.ts
|
|
7283
7976
|
import path2 from "path";
|
|
7284
|
-
import { readFileSync as
|
|
7977
|
+
import { readFileSync as readFileSync14, existsSync as existsSync14 } from "fs";
|
|
7285
7978
|
function findDeadSymbols(graph, projectRoot, includeTests = false, debug = false) {
|
|
7286
7979
|
const deadSymbols = [];
|
|
7287
7980
|
const context = { graph, projectRoot };
|
|
@@ -7414,11 +8107,11 @@ function getPackageEntryPoints(projectRoot) {
|
|
|
7414
8107
|
const entryPoints = /* @__PURE__ */ new Set();
|
|
7415
8108
|
const resolvedRoot = path2.resolve(projectRoot);
|
|
7416
8109
|
const packageJsonPath = path2.resolve(resolvedRoot, "package.json");
|
|
7417
|
-
if (!packageJsonPath.startsWith(resolvedRoot) || !
|
|
8110
|
+
if (!packageJsonPath.startsWith(resolvedRoot) || !existsSync14(packageJsonPath)) {
|
|
7418
8111
|
return entryPoints;
|
|
7419
8112
|
}
|
|
7420
8113
|
try {
|
|
7421
|
-
const packageJson = JSON.parse(
|
|
8114
|
+
const packageJson = JSON.parse(readFileSync14(packageJsonPath, "utf-8"));
|
|
7422
8115
|
if (packageJson.main) {
|
|
7423
8116
|
entryPoints.add(path2.resolve(projectRoot, packageJson.main));
|
|
7424
8117
|
}
|
|
@@ -7475,6 +8168,9 @@ function shouldExclude(attrs, context, includeTests, packageEntryPoints) {
|
|
|
7475
8168
|
if (isKotlinExcluded(attrs)) {
|
|
7476
8169
|
return "framework";
|
|
7477
8170
|
}
|
|
8171
|
+
if (isPhpExcluded(attrs)) {
|
|
8172
|
+
return "framework";
|
|
8173
|
+
}
|
|
7478
8174
|
return null;
|
|
7479
8175
|
}
|
|
7480
8176
|
function isRealPackageEntryPoint(filePath, packageEntryPoints) {
|
|
@@ -7540,6 +8236,71 @@ function isKotlinExcluded(attrs) {
|
|
|
7540
8236
|
if (name.startsWith("operator")) return true;
|
|
7541
8237
|
return false;
|
|
7542
8238
|
}
|
|
8239
|
+
function isPhpExcluded(attrs) {
|
|
8240
|
+
const filePath = attrs.file || attrs.filePath || "";
|
|
8241
|
+
const name = attrs.name || "";
|
|
8242
|
+
if (!filePath.endsWith(".php")) return false;
|
|
8243
|
+
const magicMethods = [
|
|
8244
|
+
"__construct",
|
|
8245
|
+
"__destruct",
|
|
8246
|
+
"__call",
|
|
8247
|
+
"__callStatic",
|
|
8248
|
+
"__get",
|
|
8249
|
+
"__set",
|
|
8250
|
+
"__isset",
|
|
8251
|
+
"__unset",
|
|
8252
|
+
"__sleep",
|
|
8253
|
+
"__wakeup",
|
|
8254
|
+
"__serialize",
|
|
8255
|
+
"__unserialize",
|
|
8256
|
+
"__toString",
|
|
8257
|
+
"__invoke",
|
|
8258
|
+
"__set_state",
|
|
8259
|
+
"__clone",
|
|
8260
|
+
"__debugInfo"
|
|
8261
|
+
];
|
|
8262
|
+
if (magicMethods.includes(name)) return true;
|
|
8263
|
+
const wpHooks = [
|
|
8264
|
+
"init",
|
|
8265
|
+
"admin_init",
|
|
8266
|
+
"wp_enqueue_scripts",
|
|
8267
|
+
"admin_enqueue_scripts",
|
|
8268
|
+
"widgets_init",
|
|
8269
|
+
"register_activation_hook",
|
|
8270
|
+
"register_deactivation_hook",
|
|
8271
|
+
"add_action",
|
|
8272
|
+
"add_filter",
|
|
8273
|
+
"activate",
|
|
8274
|
+
"deactivate"
|
|
8275
|
+
];
|
|
8276
|
+
if (wpHooks.includes(name)) return true;
|
|
8277
|
+
const laravelMethods = [
|
|
8278
|
+
"register",
|
|
8279
|
+
"boot",
|
|
8280
|
+
"handle",
|
|
8281
|
+
"authorize",
|
|
8282
|
+
"rules",
|
|
8283
|
+
"messages",
|
|
8284
|
+
"prepareForValidation",
|
|
8285
|
+
"failed",
|
|
8286
|
+
"broadcastOn",
|
|
8287
|
+
"broadcastAs",
|
|
8288
|
+
"broadcastWith"
|
|
8289
|
+
];
|
|
8290
|
+
if (laravelMethods.includes(name)) return true;
|
|
8291
|
+
const symfonyMethods = [
|
|
8292
|
+
"__invoke",
|
|
8293
|
+
"getSubscribedEvents",
|
|
8294
|
+
"getSubscribedServices",
|
|
8295
|
+
"configureOptions",
|
|
8296
|
+
"buildForm",
|
|
8297
|
+
"load",
|
|
8298
|
+
"getConfigTreeBuilder"
|
|
8299
|
+
];
|
|
8300
|
+
if (symfonyMethods.includes(name)) return true;
|
|
8301
|
+
if (name.startsWith("test") || name === "setUp" || name === "tearDown" || name === "setUpBeforeClass" || name === "tearDownAfterClass") return true;
|
|
8302
|
+
return false;
|
|
8303
|
+
}
|
|
7543
8304
|
|
|
7544
8305
|
// src/dead-code/classifier.ts
|
|
7545
8306
|
import path3 from "path";
|
|
@@ -7606,8 +8367,8 @@ function generateReason(symbol, confidence) {
|
|
|
7606
8367
|
return "Potentially unused";
|
|
7607
8368
|
}
|
|
7608
8369
|
function isBarrelFile(filePath) {
|
|
7609
|
-
const
|
|
7610
|
-
return
|
|
8370
|
+
const basename11 = path3.basename(filePath);
|
|
8371
|
+
return basename11 === "index.ts" || basename11 === "index.js";
|
|
7611
8372
|
}
|
|
7612
8373
|
function isTestFile2(filePath) {
|
|
7613
8374
|
return filePath.includes("__tests__/") || filePath.includes(".test.") || filePath.includes(".spec.") || filePath.includes("/test/") || filePath.includes("/tests/");
|
|
@@ -7786,11 +8547,11 @@ function filterByConfidence(symbols, minConfidence) {
|
|
|
7786
8547
|
}
|
|
7787
8548
|
|
|
7788
8549
|
// src/docs/generator.ts
|
|
7789
|
-
import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as
|
|
7790
|
-
import { join as
|
|
8550
|
+
import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync17 } from "fs";
|
|
8551
|
+
import { join as join19 } from "path";
|
|
7791
8552
|
|
|
7792
8553
|
// src/docs/architecture.ts
|
|
7793
|
-
import { dirname as
|
|
8554
|
+
import { dirname as dirname14 } from "path";
|
|
7794
8555
|
|
|
7795
8556
|
// src/docs/templates.ts
|
|
7796
8557
|
function header(text, level = 1) {
|
|
@@ -7941,7 +8702,7 @@ function generateModuleStructure(graph) {
|
|
|
7941
8702
|
function getDirectoryStats(graph) {
|
|
7942
8703
|
const dirMap = /* @__PURE__ */ new Map();
|
|
7943
8704
|
graph.forEachNode((node, attrs) => {
|
|
7944
|
-
const dir =
|
|
8705
|
+
const dir = dirname14(attrs.filePath);
|
|
7945
8706
|
if (dir === ".") return;
|
|
7946
8707
|
if (!dirMap.has(dir)) {
|
|
7947
8708
|
dirMap.set(dir, {
|
|
@@ -7966,7 +8727,7 @@ function getDirectoryStats(graph) {
|
|
|
7966
8727
|
});
|
|
7967
8728
|
const filesPerDir = /* @__PURE__ */ new Map();
|
|
7968
8729
|
graph.forEachNode((node, attrs) => {
|
|
7969
|
-
const dir =
|
|
8730
|
+
const dir = dirname14(attrs.filePath);
|
|
7970
8731
|
if (!filesPerDir.has(dir)) {
|
|
7971
8732
|
filesPerDir.set(dir, /* @__PURE__ */ new Set());
|
|
7972
8733
|
}
|
|
@@ -7981,8 +8742,8 @@ function getDirectoryStats(graph) {
|
|
|
7981
8742
|
graph.forEachEdge((edge, attrs, source, target) => {
|
|
7982
8743
|
const sourceAttrs = graph.getNodeAttributes(source);
|
|
7983
8744
|
const targetAttrs = graph.getNodeAttributes(target);
|
|
7984
|
-
const sourceDir =
|
|
7985
|
-
const targetDir =
|
|
8745
|
+
const sourceDir = dirname14(sourceAttrs.filePath);
|
|
8746
|
+
const targetDir = dirname14(targetAttrs.filePath);
|
|
7986
8747
|
if (sourceDir !== targetDir) {
|
|
7987
8748
|
if (!dirEdges.has(sourceDir)) {
|
|
7988
8749
|
dirEdges.set(sourceDir, { in: 0, out: 0 });
|
|
@@ -8189,7 +8950,7 @@ function detectCycles(graph) {
|
|
|
8189
8950
|
}
|
|
8190
8951
|
|
|
8191
8952
|
// src/docs/conventions.ts
|
|
8192
|
-
import { basename as
|
|
8953
|
+
import { basename as basename8, extname as extname9 } from "path";
|
|
8193
8954
|
function generateConventions(graph, projectRoot, version) {
|
|
8194
8955
|
let output = "";
|
|
8195
8956
|
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
@@ -8227,7 +8988,7 @@ function generateFileOrganization(graph) {
|
|
|
8227
8988
|
graph.forEachNode((node, attrs) => {
|
|
8228
8989
|
if (!files.has(attrs.filePath)) {
|
|
8229
8990
|
files.add(attrs.filePath);
|
|
8230
|
-
const fileName =
|
|
8991
|
+
const fileName = basename8(attrs.filePath);
|
|
8231
8992
|
if (fileName === "index.ts" || fileName === "index.js" || fileName === "index.tsx" || fileName === "index.jsx") {
|
|
8232
8993
|
barrelFileCount++;
|
|
8233
8994
|
}
|
|
@@ -8286,7 +9047,7 @@ function generateNamingPatterns(graph) {
|
|
|
8286
9047
|
graph.forEachNode((node, attrs) => {
|
|
8287
9048
|
if (!files.has(attrs.filePath)) {
|
|
8288
9049
|
files.add(attrs.filePath);
|
|
8289
|
-
const fileName =
|
|
9050
|
+
const fileName = basename8(attrs.filePath, extname9(attrs.filePath));
|
|
8290
9051
|
if (isCamelCase(fileName)) patterns.files.camelCase++;
|
|
8291
9052
|
else if (isPascalCase(fileName)) patterns.files.PascalCase++;
|
|
8292
9053
|
else if (isKebabCase(fileName)) patterns.files.kebabCase++;
|
|
@@ -8963,7 +9724,7 @@ function detectCyclesDetailed(graph) {
|
|
|
8963
9724
|
}
|
|
8964
9725
|
|
|
8965
9726
|
// src/docs/onboarding.ts
|
|
8966
|
-
import { dirname as
|
|
9727
|
+
import { dirname as dirname15 } from "path";
|
|
8967
9728
|
function generateOnboarding(graph, projectRoot, version) {
|
|
8968
9729
|
let output = "";
|
|
8969
9730
|
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
@@ -9022,7 +9783,7 @@ function generateQuickOrientation(graph) {
|
|
|
9022
9783
|
const primaryLang = Object.entries(languages2).sort((a, b) => b[1] - a[1])[0];
|
|
9023
9784
|
const dirs = /* @__PURE__ */ new Set();
|
|
9024
9785
|
graph.forEachNode((node, attrs) => {
|
|
9025
|
-
const dir =
|
|
9786
|
+
const dir = dirname15(attrs.filePath);
|
|
9026
9787
|
if (dir !== ".") {
|
|
9027
9788
|
const topLevel = dir.split("/")[0];
|
|
9028
9789
|
dirs.add(topLevel);
|
|
@@ -9126,7 +9887,7 @@ function generateModuleMap(graph) {
|
|
|
9126
9887
|
function getDirectoryStats2(graph) {
|
|
9127
9888
|
const dirMap = /* @__PURE__ */ new Map();
|
|
9128
9889
|
graph.forEachNode((node, attrs) => {
|
|
9129
|
-
const dir =
|
|
9890
|
+
const dir = dirname15(attrs.filePath);
|
|
9130
9891
|
if (dir === ".") return;
|
|
9131
9892
|
if (!dirMap.has(dir)) {
|
|
9132
9893
|
dirMap.set(dir, {
|
|
@@ -9141,7 +9902,7 @@ function getDirectoryStats2(graph) {
|
|
|
9141
9902
|
});
|
|
9142
9903
|
const filesPerDir = /* @__PURE__ */ new Map();
|
|
9143
9904
|
graph.forEachNode((node, attrs) => {
|
|
9144
|
-
const dir =
|
|
9905
|
+
const dir = dirname15(attrs.filePath);
|
|
9145
9906
|
if (!filesPerDir.has(dir)) {
|
|
9146
9907
|
filesPerDir.set(dir, /* @__PURE__ */ new Set());
|
|
9147
9908
|
}
|
|
@@ -9156,8 +9917,8 @@ function getDirectoryStats2(graph) {
|
|
|
9156
9917
|
graph.forEachEdge((edge, attrs, source, target) => {
|
|
9157
9918
|
const sourceAttrs = graph.getNodeAttributes(source);
|
|
9158
9919
|
const targetAttrs = graph.getNodeAttributes(target);
|
|
9159
|
-
const sourceDir =
|
|
9160
|
-
const targetDir =
|
|
9920
|
+
const sourceDir = dirname15(sourceAttrs.filePath);
|
|
9921
|
+
const targetDir = dirname15(targetAttrs.filePath);
|
|
9161
9922
|
if (sourceDir !== targetDir) {
|
|
9162
9923
|
if (!dirEdges.has(sourceDir)) {
|
|
9163
9924
|
dirEdges.set(sourceDir, { in: 0, out: 0 });
|
|
@@ -9238,7 +9999,7 @@ function detectClusters(graph) {
|
|
|
9238
9999
|
const dirFiles = /* @__PURE__ */ new Map();
|
|
9239
10000
|
const fileEdges = /* @__PURE__ */ new Map();
|
|
9240
10001
|
graph.forEachNode((node, attrs) => {
|
|
9241
|
-
const dir =
|
|
10002
|
+
const dir = dirname15(attrs.filePath);
|
|
9242
10003
|
if (!dirFiles.has(dir)) {
|
|
9243
10004
|
dirFiles.set(dir, /* @__PURE__ */ new Set());
|
|
9244
10005
|
}
|
|
@@ -9292,8 +10053,8 @@ function inferClusterName(files) {
|
|
|
9292
10053
|
if (sortedWords.length > 0 && sortedWords[0][1] > 1) {
|
|
9293
10054
|
return capitalizeFirst2(sortedWords[0][0]);
|
|
9294
10055
|
}
|
|
9295
|
-
const commonDir =
|
|
9296
|
-
if (files.every((f) =>
|
|
10056
|
+
const commonDir = dirname15(files[0]);
|
|
10057
|
+
if (files.every((f) => dirname15(f) === commonDir)) {
|
|
9297
10058
|
return capitalizeFirst2(commonDir.split("/").pop() || "Core");
|
|
9298
10059
|
}
|
|
9299
10060
|
return "Core";
|
|
@@ -9351,7 +10112,7 @@ function generateDepwireUsage(projectRoot) {
|
|
|
9351
10112
|
}
|
|
9352
10113
|
|
|
9353
10114
|
// src/docs/files.ts
|
|
9354
|
-
import { dirname as
|
|
10115
|
+
import { dirname as dirname16, basename as basename9 } from "path";
|
|
9355
10116
|
function generateFiles(graph, projectRoot, version) {
|
|
9356
10117
|
let output = "";
|
|
9357
10118
|
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
@@ -9455,7 +10216,7 @@ function generateDirectoryBreakdown(graph) {
|
|
|
9455
10216
|
const fileStats = getFileStats2(graph);
|
|
9456
10217
|
const dirMap = /* @__PURE__ */ new Map();
|
|
9457
10218
|
for (const file of fileStats) {
|
|
9458
|
-
const dir =
|
|
10219
|
+
const dir = dirname16(file.filePath);
|
|
9459
10220
|
const topDir = dir === "." ? "." : dir.split("/")[0];
|
|
9460
10221
|
if (!dirMap.has(topDir)) {
|
|
9461
10222
|
dirMap.set(topDir, {
|
|
@@ -9470,7 +10231,7 @@ function generateDirectoryBreakdown(graph) {
|
|
|
9470
10231
|
dirStats.symbolCount += file.symbolCount;
|
|
9471
10232
|
if (file.totalConnections > dirStats.maxConnections) {
|
|
9472
10233
|
dirStats.maxConnections = file.totalConnections;
|
|
9473
|
-
dirStats.mostConnectedFile =
|
|
10234
|
+
dirStats.mostConnectedFile = basename9(file.filePath);
|
|
9474
10235
|
}
|
|
9475
10236
|
}
|
|
9476
10237
|
if (dirMap.size === 0) {
|
|
@@ -10098,7 +10859,7 @@ function generateRecommendations(graph) {
|
|
|
10098
10859
|
}
|
|
10099
10860
|
|
|
10100
10861
|
// src/docs/tests.ts
|
|
10101
|
-
import { basename as
|
|
10862
|
+
import { basename as basename10, dirname as dirname17 } from "path";
|
|
10102
10863
|
function generateTests(graph, projectRoot, version) {
|
|
10103
10864
|
let output = "";
|
|
10104
10865
|
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
@@ -10126,8 +10887,8 @@ function getFileCount8(graph) {
|
|
|
10126
10887
|
return files.size;
|
|
10127
10888
|
}
|
|
10128
10889
|
function isTestFile3(filePath) {
|
|
10129
|
-
const fileName =
|
|
10130
|
-
const dirPath =
|
|
10890
|
+
const fileName = basename10(filePath).toLowerCase();
|
|
10891
|
+
const dirPath = dirname17(filePath).toLowerCase();
|
|
10131
10892
|
if (dirPath.includes("test") || dirPath.includes("spec") || dirPath.includes("__tests__")) {
|
|
10132
10893
|
return true;
|
|
10133
10894
|
}
|
|
@@ -10185,13 +10946,13 @@ function generateTestFileInventory(graph) {
|
|
|
10185
10946
|
return output;
|
|
10186
10947
|
}
|
|
10187
10948
|
function matchTestToSource(testFile) {
|
|
10188
|
-
const testFileName =
|
|
10189
|
-
const testDir =
|
|
10949
|
+
const testFileName = basename10(testFile);
|
|
10950
|
+
const testDir = dirname17(testFile);
|
|
10190
10951
|
let sourceFileName = testFileName.replace(/\.test\./g, ".").replace(/\.spec\./g, ".").replace(/_test\./g, ".").replace(/_spec\./g, ".");
|
|
10191
10952
|
const possiblePaths = [];
|
|
10192
10953
|
possiblePaths.push(testDir + "/" + sourceFileName);
|
|
10193
10954
|
if (testDir.endsWith("/test") || testDir.endsWith("/tests") || testDir.endsWith("/__tests__")) {
|
|
10194
|
-
const parentDir =
|
|
10955
|
+
const parentDir = dirname17(testDir);
|
|
10195
10956
|
possiblePaths.push(parentDir + "/" + sourceFileName);
|
|
10196
10957
|
}
|
|
10197
10958
|
if (testDir.includes("test")) {
|
|
@@ -10347,7 +11108,7 @@ function generateTestCoverageMap(graph) {
|
|
|
10347
11108
|
const rows = mappings.slice(0, 30).map((m) => [
|
|
10348
11109
|
`\`${m.sourceFile}\``,
|
|
10349
11110
|
m.hasTest ? "\u2705" : "\u274C",
|
|
10350
|
-
m.testFile ? `\`${
|
|
11111
|
+
m.testFile ? `\`${basename10(m.testFile)}\`` : "-",
|
|
10351
11112
|
formatNumber(m.symbolCount)
|
|
10352
11113
|
]);
|
|
10353
11114
|
let output = table(headers, rows);
|
|
@@ -10388,7 +11149,7 @@ function generateTestStatistics(graph) {
|
|
|
10388
11149
|
`;
|
|
10389
11150
|
const dirTestCoverage = /* @__PURE__ */ new Map();
|
|
10390
11151
|
for (const sourceFile of sourceFiles) {
|
|
10391
|
-
const dir =
|
|
11152
|
+
const dir = dirname17(sourceFile).split("/")[0];
|
|
10392
11153
|
if (!dirTestCoverage.has(dir)) {
|
|
10393
11154
|
dirTestCoverage.set(dir, { total: 0, tested: 0 });
|
|
10394
11155
|
}
|
|
@@ -10411,7 +11172,7 @@ function generateTestStatistics(graph) {
|
|
|
10411
11172
|
}
|
|
10412
11173
|
|
|
10413
11174
|
// src/docs/history.ts
|
|
10414
|
-
import { dirname as
|
|
11175
|
+
import { dirname as dirname18 } from "path";
|
|
10415
11176
|
import { execSync } from "child_process";
|
|
10416
11177
|
function generateHistory(graph, projectRoot, version) {
|
|
10417
11178
|
let output = "";
|
|
@@ -10692,7 +11453,7 @@ function generateFeatureClusters(graph) {
|
|
|
10692
11453
|
const dirFiles = /* @__PURE__ */ new Map();
|
|
10693
11454
|
const fileEdges = /* @__PURE__ */ new Map();
|
|
10694
11455
|
graph.forEachNode((node, attrs) => {
|
|
10695
|
-
const dir =
|
|
11456
|
+
const dir = dirname18(attrs.filePath);
|
|
10696
11457
|
if (!dirFiles.has(dir)) {
|
|
10697
11458
|
dirFiles.set(dir, /* @__PURE__ */ new Set());
|
|
10698
11459
|
}
|
|
@@ -10774,7 +11535,7 @@ function capitalizeFirst3(str) {
|
|
|
10774
11535
|
}
|
|
10775
11536
|
|
|
10776
11537
|
// src/docs/current.ts
|
|
10777
|
-
import { dirname as
|
|
11538
|
+
import { dirname as dirname19 } from "path";
|
|
10778
11539
|
function generateCurrent(graph, projectRoot, version) {
|
|
10779
11540
|
let output = "";
|
|
10780
11541
|
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
@@ -10912,7 +11673,7 @@ function generateCompleteFileIndex(graph) {
|
|
|
10912
11673
|
fileInfos.sort((a, b) => a.filePath.localeCompare(b.filePath));
|
|
10913
11674
|
const dirGroups = /* @__PURE__ */ new Map();
|
|
10914
11675
|
for (const info of fileInfos) {
|
|
10915
|
-
const dir =
|
|
11676
|
+
const dir = dirname19(info.filePath);
|
|
10916
11677
|
const topDir = dir === "." ? "root" : dir.split("/")[0];
|
|
10917
11678
|
if (!dirGroups.has(topDir)) {
|
|
10918
11679
|
dirGroups.set(topDir, []);
|
|
@@ -11123,8 +11884,8 @@ function getTopLevelDir2(filePath) {
|
|
|
11123
11884
|
}
|
|
11124
11885
|
|
|
11125
11886
|
// src/docs/status.ts
|
|
11126
|
-
import { readFileSync as
|
|
11127
|
-
import { resolve as
|
|
11887
|
+
import { readFileSync as readFileSync15, existsSync as existsSync15 } from "fs";
|
|
11888
|
+
import { resolve as resolve11 } from "path";
|
|
11128
11889
|
function generateStatus(graph, projectRoot, version) {
|
|
11129
11890
|
let output = "";
|
|
11130
11891
|
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
@@ -11157,16 +11918,16 @@ function getFileCount11(graph) {
|
|
|
11157
11918
|
}
|
|
11158
11919
|
function extractComments(projectRoot, filePath) {
|
|
11159
11920
|
const comments = [];
|
|
11160
|
-
const resolvedRoot =
|
|
11161
|
-
const fullPath =
|
|
11921
|
+
const resolvedRoot = resolve11(projectRoot);
|
|
11922
|
+
const fullPath = resolve11(resolvedRoot, filePath);
|
|
11162
11923
|
if (!fullPath.startsWith(resolvedRoot)) {
|
|
11163
11924
|
return comments;
|
|
11164
11925
|
}
|
|
11165
|
-
if (!
|
|
11926
|
+
if (!existsSync15(fullPath)) {
|
|
11166
11927
|
return comments;
|
|
11167
11928
|
}
|
|
11168
11929
|
try {
|
|
11169
|
-
const content =
|
|
11930
|
+
const content = readFileSync15(fullPath, "utf-8");
|
|
11170
11931
|
const lines = content.split("\n");
|
|
11171
11932
|
const patterns = [
|
|
11172
11933
|
{ type: "TODO", regex: /(?:\/\/|#|\/\*)\s*TODO:?\s*(.+)/i },
|
|
@@ -11745,16 +12506,16 @@ function generateConfidenceSection(title, description, symbols, projectRoot) {
|
|
|
11745
12506
|
}
|
|
11746
12507
|
|
|
11747
12508
|
// src/docs/metadata.ts
|
|
11748
|
-
import { existsSync as
|
|
11749
|
-
import { resolve as
|
|
12509
|
+
import { existsSync as existsSync16, readFileSync as readFileSync16, writeFileSync as writeFileSync2 } from "fs";
|
|
12510
|
+
import { resolve as resolve12 } from "path";
|
|
11750
12511
|
function loadMetadata(outputDir) {
|
|
11751
|
-
const resolvedDir =
|
|
11752
|
-
const metadataPath =
|
|
11753
|
-
if (!metadataPath.startsWith(resolvedDir) || !
|
|
12512
|
+
const resolvedDir = resolve12(outputDir);
|
|
12513
|
+
const metadataPath = resolve12(resolvedDir, "metadata.json");
|
|
12514
|
+
if (!metadataPath.startsWith(resolvedDir) || !existsSync16(metadataPath)) {
|
|
11754
12515
|
return null;
|
|
11755
12516
|
}
|
|
11756
12517
|
try {
|
|
11757
|
-
const content =
|
|
12518
|
+
const content = readFileSync16(metadataPath, "utf-8");
|
|
11758
12519
|
return JSON.parse(content);
|
|
11759
12520
|
} catch (err) {
|
|
11760
12521
|
console.error("Failed to load metadata:", err);
|
|
@@ -11762,8 +12523,8 @@ function loadMetadata(outputDir) {
|
|
|
11762
12523
|
}
|
|
11763
12524
|
}
|
|
11764
12525
|
function saveMetadata(outputDir, metadata) {
|
|
11765
|
-
const resolvedDir =
|
|
11766
|
-
const metadataPath =
|
|
12526
|
+
const resolvedDir = resolve12(outputDir);
|
|
12527
|
+
const metadataPath = resolve12(resolvedDir, "metadata.json");
|
|
11767
12528
|
if (!metadataPath.startsWith(resolvedDir)) {
|
|
11768
12529
|
throw new Error(`Path traversal attempt blocked: ${metadataPath}`);
|
|
11769
12530
|
}
|
|
@@ -11809,7 +12570,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
11809
12570
|
const generated = [];
|
|
11810
12571
|
const errors = [];
|
|
11811
12572
|
try {
|
|
11812
|
-
if (!
|
|
12573
|
+
if (!existsSync17(options.outputDir)) {
|
|
11813
12574
|
mkdirSync2(options.outputDir, { recursive: true });
|
|
11814
12575
|
if (options.verbose) {
|
|
11815
12576
|
console.log(`Created output directory: ${options.outputDir}`);
|
|
@@ -11848,7 +12609,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
11848
12609
|
try {
|
|
11849
12610
|
if (options.verbose) console.log("Generating ARCHITECTURE.md...");
|
|
11850
12611
|
const content = generateArchitecture(graph, projectRoot, version, parseTime);
|
|
11851
|
-
const filePath =
|
|
12612
|
+
const filePath = join19(options.outputDir, "ARCHITECTURE.md");
|
|
11852
12613
|
writeFileSync3(filePath, content, "utf-8");
|
|
11853
12614
|
generated.push("ARCHITECTURE.md");
|
|
11854
12615
|
} catch (err) {
|
|
@@ -11859,7 +12620,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
11859
12620
|
try {
|
|
11860
12621
|
if (options.verbose) console.log("Generating CONVENTIONS.md...");
|
|
11861
12622
|
const content = generateConventions(graph, projectRoot, version);
|
|
11862
|
-
const filePath =
|
|
12623
|
+
const filePath = join19(options.outputDir, "CONVENTIONS.md");
|
|
11863
12624
|
writeFileSync3(filePath, content, "utf-8");
|
|
11864
12625
|
generated.push("CONVENTIONS.md");
|
|
11865
12626
|
} catch (err) {
|
|
@@ -11870,7 +12631,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
11870
12631
|
try {
|
|
11871
12632
|
if (options.verbose) console.log("Generating DEPENDENCIES.md...");
|
|
11872
12633
|
const content = generateDependencies(graph, projectRoot, version);
|
|
11873
|
-
const filePath =
|
|
12634
|
+
const filePath = join19(options.outputDir, "DEPENDENCIES.md");
|
|
11874
12635
|
writeFileSync3(filePath, content, "utf-8");
|
|
11875
12636
|
generated.push("DEPENDENCIES.md");
|
|
11876
12637
|
} catch (err) {
|
|
@@ -11881,7 +12642,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
11881
12642
|
try {
|
|
11882
12643
|
if (options.verbose) console.log("Generating ONBOARDING.md...");
|
|
11883
12644
|
const content = generateOnboarding(graph, projectRoot, version);
|
|
11884
|
-
const filePath =
|
|
12645
|
+
const filePath = join19(options.outputDir, "ONBOARDING.md");
|
|
11885
12646
|
writeFileSync3(filePath, content, "utf-8");
|
|
11886
12647
|
generated.push("ONBOARDING.md");
|
|
11887
12648
|
} catch (err) {
|
|
@@ -11892,7 +12653,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
11892
12653
|
try {
|
|
11893
12654
|
if (options.verbose) console.log("Generating FILES.md...");
|
|
11894
12655
|
const content = generateFiles(graph, projectRoot, version);
|
|
11895
|
-
const filePath =
|
|
12656
|
+
const filePath = join19(options.outputDir, "FILES.md");
|
|
11896
12657
|
writeFileSync3(filePath, content, "utf-8");
|
|
11897
12658
|
generated.push("FILES.md");
|
|
11898
12659
|
} catch (err) {
|
|
@@ -11903,7 +12664,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
11903
12664
|
try {
|
|
11904
12665
|
if (options.verbose) console.log("Generating API_SURFACE.md...");
|
|
11905
12666
|
const content = generateApiSurface(graph, projectRoot, version);
|
|
11906
|
-
const filePath =
|
|
12667
|
+
const filePath = join19(options.outputDir, "API_SURFACE.md");
|
|
11907
12668
|
writeFileSync3(filePath, content, "utf-8");
|
|
11908
12669
|
generated.push("API_SURFACE.md");
|
|
11909
12670
|
} catch (err) {
|
|
@@ -11914,7 +12675,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
11914
12675
|
try {
|
|
11915
12676
|
if (options.verbose) console.log("Generating ERRORS.md...");
|
|
11916
12677
|
const content = generateErrors(graph, projectRoot, version);
|
|
11917
|
-
const filePath =
|
|
12678
|
+
const filePath = join19(options.outputDir, "ERRORS.md");
|
|
11918
12679
|
writeFileSync3(filePath, content, "utf-8");
|
|
11919
12680
|
generated.push("ERRORS.md");
|
|
11920
12681
|
} catch (err) {
|
|
@@ -11925,7 +12686,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
11925
12686
|
try {
|
|
11926
12687
|
if (options.verbose) console.log("Generating TESTS.md...");
|
|
11927
12688
|
const content = generateTests(graph, projectRoot, version);
|
|
11928
|
-
const filePath =
|
|
12689
|
+
const filePath = join19(options.outputDir, "TESTS.md");
|
|
11929
12690
|
writeFileSync3(filePath, content, "utf-8");
|
|
11930
12691
|
generated.push("TESTS.md");
|
|
11931
12692
|
} catch (err) {
|
|
@@ -11936,7 +12697,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
11936
12697
|
try {
|
|
11937
12698
|
if (options.verbose) console.log("Generating HISTORY.md...");
|
|
11938
12699
|
const content = generateHistory(graph, projectRoot, version);
|
|
11939
|
-
const filePath =
|
|
12700
|
+
const filePath = join19(options.outputDir, "HISTORY.md");
|
|
11940
12701
|
writeFileSync3(filePath, content, "utf-8");
|
|
11941
12702
|
generated.push("HISTORY.md");
|
|
11942
12703
|
} catch (err) {
|
|
@@ -11947,7 +12708,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
11947
12708
|
try {
|
|
11948
12709
|
if (options.verbose) console.log("Generating CURRENT.md...");
|
|
11949
12710
|
const content = generateCurrent(graph, projectRoot, version);
|
|
11950
|
-
const filePath =
|
|
12711
|
+
const filePath = join19(options.outputDir, "CURRENT.md");
|
|
11951
12712
|
writeFileSync3(filePath, content, "utf-8");
|
|
11952
12713
|
generated.push("CURRENT.md");
|
|
11953
12714
|
} catch (err) {
|
|
@@ -11958,7 +12719,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
11958
12719
|
try {
|
|
11959
12720
|
if (options.verbose) console.log("Generating STATUS.md...");
|
|
11960
12721
|
const content = generateStatus(graph, projectRoot, version);
|
|
11961
|
-
const filePath =
|
|
12722
|
+
const filePath = join19(options.outputDir, "STATUS.md");
|
|
11962
12723
|
writeFileSync3(filePath, content, "utf-8");
|
|
11963
12724
|
generated.push("STATUS.md");
|
|
11964
12725
|
} catch (err) {
|
|
@@ -11969,7 +12730,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
11969
12730
|
try {
|
|
11970
12731
|
if (options.verbose) console.log("Generating HEALTH.md...");
|
|
11971
12732
|
const content = generateHealth(graph, projectRoot, version);
|
|
11972
|
-
const filePath =
|
|
12733
|
+
const filePath = join19(options.outputDir, "HEALTH.md");
|
|
11973
12734
|
writeFileSync3(filePath, content, "utf-8");
|
|
11974
12735
|
generated.push("HEALTH.md");
|
|
11975
12736
|
} catch (err) {
|
|
@@ -11980,7 +12741,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
11980
12741
|
try {
|
|
11981
12742
|
if (options.verbose) console.log("Generating DEAD_CODE.md...");
|
|
11982
12743
|
const content = generateDeadCode(graph, projectRoot, version);
|
|
11983
|
-
const filePath =
|
|
12744
|
+
const filePath = join19(options.outputDir, "DEAD_CODE.md");
|
|
11984
12745
|
writeFileSync3(filePath, content, "utf-8");
|
|
11985
12746
|
generated.push("DEAD_CODE.md");
|
|
11986
12747
|
} catch (err) {
|
|
@@ -12024,7 +12785,7 @@ function getFileCount13(graph) {
|
|
|
12024
12785
|
}
|
|
12025
12786
|
|
|
12026
12787
|
// src/simulation/engine.ts
|
|
12027
|
-
import { dirname as
|
|
12788
|
+
import { dirname as dirname20, join as join20 } from "path";
|
|
12028
12789
|
function normalizePath2(p) {
|
|
12029
12790
|
return p.replace(/^\.\//, "").replace(/\/+$/, "");
|
|
12030
12791
|
}
|
|
@@ -12156,7 +12917,7 @@ var SimulationEngine = class {
|
|
|
12156
12917
|
}
|
|
12157
12918
|
}
|
|
12158
12919
|
applyRename(clone, target, newName, brokenImports) {
|
|
12159
|
-
const destination =
|
|
12920
|
+
const destination = join20(dirname20(target), newName);
|
|
12160
12921
|
this.applyMove(clone, target, destination, brokenImports);
|
|
12161
12922
|
}
|
|
12162
12923
|
applySplit(clone, target, newFile, symbols, brokenImports) {
|
|
@@ -12356,13 +13117,13 @@ var SimulationEngine = class {
|
|
|
12356
13117
|
};
|
|
12357
13118
|
|
|
12358
13119
|
// src/security/scanner.ts
|
|
12359
|
-
import { existsSync as
|
|
12360
|
-
import { join as
|
|
13120
|
+
import { existsSync as existsSync19 } from "fs";
|
|
13121
|
+
import { join as join30 } from "path";
|
|
12361
13122
|
|
|
12362
13123
|
// src/security/checks/dependencies.ts
|
|
12363
13124
|
import { execSync as execSync2 } from "child_process";
|
|
12364
|
-
import { existsSync as
|
|
12365
|
-
import { join as
|
|
13125
|
+
import { existsSync as existsSync18, readFileSync as readFileSync17, readdirSync as readdirSync10 } from "fs";
|
|
13126
|
+
import { join as join21 } from "path";
|
|
12366
13127
|
function cvssToSeverity(score) {
|
|
12367
13128
|
if (score >= 9) return "critical";
|
|
12368
13129
|
if (score >= 7) return "high";
|
|
@@ -12372,18 +13133,18 @@ function cvssToSeverity(score) {
|
|
|
12372
13133
|
async function checkDependencies(_files, projectRoot) {
|
|
12373
13134
|
const findings = [];
|
|
12374
13135
|
try {
|
|
12375
|
-
if (
|
|
13136
|
+
if (existsSync18(join21(projectRoot, "package.json"))) {
|
|
12376
13137
|
findings.push(...checkNpmAudit(projectRoot));
|
|
12377
13138
|
findings.push(...checkPackageJsonPatterns(projectRoot));
|
|
12378
13139
|
findings.push(...checkPostinstallScripts(projectRoot));
|
|
12379
13140
|
}
|
|
12380
|
-
if (
|
|
13141
|
+
if (existsSync18(join21(projectRoot, "requirements.txt")) || existsSync18(join21(projectRoot, "pyproject.toml"))) {
|
|
12381
13142
|
findings.push(...checkPipAudit(projectRoot));
|
|
12382
13143
|
}
|
|
12383
|
-
if (
|
|
13144
|
+
if (existsSync18(join21(projectRoot, "Cargo.toml"))) {
|
|
12384
13145
|
findings.push(...checkCargoAudit(projectRoot));
|
|
12385
13146
|
}
|
|
12386
|
-
if (
|
|
13147
|
+
if (existsSync18(join21(projectRoot, "go.mod"))) {
|
|
12387
13148
|
findings.push(...checkGoVerify(projectRoot));
|
|
12388
13149
|
}
|
|
12389
13150
|
} catch (err) {
|
|
@@ -12472,8 +13233,8 @@ function checkNpmAudit(projectRoot) {
|
|
|
12472
13233
|
function checkPackageJsonPatterns(projectRoot) {
|
|
12473
13234
|
const findings = [];
|
|
12474
13235
|
try {
|
|
12475
|
-
const pkgPath =
|
|
12476
|
-
const pkg = JSON.parse(
|
|
13236
|
+
const pkgPath = join21(projectRoot, "package.json");
|
|
13237
|
+
const pkg = JSON.parse(readFileSync17(pkgPath, "utf-8"));
|
|
12477
13238
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
12478
13239
|
for (const [name, version] of Object.entries(allDeps)) {
|
|
12479
13240
|
if (version.startsWith("^") || version.startsWith("~")) {
|
|
@@ -12495,15 +13256,15 @@ function checkPackageJsonPatterns(projectRoot) {
|
|
|
12495
13256
|
}
|
|
12496
13257
|
function checkPostinstallScripts(projectRoot) {
|
|
12497
13258
|
const findings = [];
|
|
12498
|
-
const nodeModules =
|
|
12499
|
-
if (!
|
|
13259
|
+
const nodeModules = join21(projectRoot, "node_modules");
|
|
13260
|
+
if (!existsSync18(nodeModules)) return findings;
|
|
12500
13261
|
try {
|
|
12501
|
-
const topLevelDeps =
|
|
13262
|
+
const topLevelDeps = readdirSync10(nodeModules).filter((d) => !d.startsWith("."));
|
|
12502
13263
|
for (const dep of topLevelDeps) {
|
|
12503
|
-
const depPkgPath =
|
|
12504
|
-
if (!
|
|
13264
|
+
const depPkgPath = join21(nodeModules, dep, "package.json");
|
|
13265
|
+
if (!existsSync18(depPkgPath)) continue;
|
|
12505
13266
|
try {
|
|
12506
|
-
const depPkg = JSON.parse(
|
|
13267
|
+
const depPkg = JSON.parse(readFileSync17(depPkgPath, "utf-8"));
|
|
12507
13268
|
const scripts = depPkg.scripts || {};
|
|
12508
13269
|
if (scripts.postinstall || scripts.preinstall || scripts.install) {
|
|
12509
13270
|
const scriptName = scripts.postinstall ? "postinstall" : scripts.preinstall ? "preinstall" : "install";
|
|
@@ -12541,7 +13302,7 @@ function checkPipAudit(projectRoot) {
|
|
|
12541
13302
|
id: "",
|
|
12542
13303
|
severity: cvssToSeverity(vuln.cvss?.score || 5),
|
|
12543
13304
|
vulnerabilityClass: "dependency-cve",
|
|
12544
|
-
file:
|
|
13305
|
+
file: existsSync18(join21(projectRoot, "requirements.txt")) ? "requirements.txt" : "pyproject.toml",
|
|
12545
13306
|
title: `Vulnerable Python dependency: ${vuln.name}`,
|
|
12546
13307
|
description: `${vuln.name}@${vuln.version} \u2014 ${vuln.id}: ${vuln.description || "Known vulnerability"}`,
|
|
12547
13308
|
attackScenario: `An attacker could exploit the vulnerability in ${vuln.name}.`,
|
|
@@ -12638,8 +13399,8 @@ function checkGoVerify(projectRoot) {
|
|
|
12638
13399
|
}
|
|
12639
13400
|
|
|
12640
13401
|
// src/security/checks/injection.ts
|
|
12641
|
-
import { readFileSync as
|
|
12642
|
-
import { join as
|
|
13402
|
+
import { readFileSync as readFileSync18 } from "fs";
|
|
13403
|
+
import { join as join22 } from "path";
|
|
12643
13404
|
var SKIP_DIRS = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
|
|
12644
13405
|
var TEST_PATTERNS = ["test", "spec", "fixture", "mock", "__tests__", "__mocks__"];
|
|
12645
13406
|
var USER_INPUT_NAMES = /(?:input|user|name|path|query|branch|hash|cmd|command|req\.|params|body|args|url|dir|file|subdirectory)/i;
|
|
@@ -12871,6 +13632,61 @@ var PATTERNS = [
|
|
|
12871
13632
|
description: "permitAll() applied to a path that appears security-sensitive.",
|
|
12872
13633
|
attackScenario: "An attacker could access administrative or destructive endpoints without authentication.",
|
|
12873
13634
|
suggestedFix: 'Use .hasRole("ADMIN") or .authenticated() for sensitive endpoints.'
|
|
13635
|
+
},
|
|
13636
|
+
// PHP injection patterns
|
|
13637
|
+
{
|
|
13638
|
+
regex: /\$wpdb\s*->\s*query\s*\(\s*["'][^"']*\$|.*\$wpdb\s*->\s*query\s*\(\s*[^"']*\.\s*\$/,
|
|
13639
|
+
title: "PHP SQL injection via $wpdb->query with string concatenation",
|
|
13640
|
+
vulnClass: "code-injection",
|
|
13641
|
+
baseSeverity: "high",
|
|
13642
|
+
description: "WordPress $wpdb->query() called with direct variable interpolation \u2014 vulnerable to SQL injection.",
|
|
13643
|
+
attackScenario: "An attacker could inject SQL through unescaped user input to read, modify, or delete database data.",
|
|
13644
|
+
suggestedFix: 'Use $wpdb->prepare() with placeholders: $wpdb->query($wpdb->prepare("SELECT * FROM table WHERE id = %d", $id))'
|
|
13645
|
+
},
|
|
13646
|
+
{
|
|
13647
|
+
regex: /\beval\s*\(\s*\$/,
|
|
13648
|
+
title: "PHP eval() with variable input",
|
|
13649
|
+
vulnClass: "code-injection",
|
|
13650
|
+
baseSeverity: "high",
|
|
13651
|
+
description: "eval() executes arbitrary PHP code from a variable \u2014 potential RCE.",
|
|
13652
|
+
attackScenario: "An attacker could inject malicious PHP code if user input reaches eval().",
|
|
13653
|
+
suggestedFix: "Remove eval() entirely. Use safe alternatives like json_decode() for data or specific parsers."
|
|
13654
|
+
},
|
|
13655
|
+
{
|
|
13656
|
+
regex: /\b(?:system|exec|shell_exec|passthru)\s*\(\s*\$/,
|
|
13657
|
+
title: "PHP command injection via system/exec/shell_exec/passthru",
|
|
13658
|
+
vulnClass: "shell-injection",
|
|
13659
|
+
baseSeverity: "high",
|
|
13660
|
+
description: "Shell command execution with a variable argument \u2014 potential command injection.",
|
|
13661
|
+
attackScenario: "An attacker could inject shell metacharacters to execute arbitrary commands on the server.",
|
|
13662
|
+
suggestedFix: "Use escapeshellarg() and escapeshellcmd() to sanitize input, or avoid shell commands entirely."
|
|
13663
|
+
},
|
|
13664
|
+
{
|
|
13665
|
+
regex: /preg_replace\s*\(\s*['"]\/[^'"]*\/e['"]/,
|
|
13666
|
+
title: "PHP preg_replace with /e modifier (code execution)",
|
|
13667
|
+
vulnClass: "code-injection",
|
|
13668
|
+
baseSeverity: "high",
|
|
13669
|
+
description: "preg_replace() with the /e modifier evaluates the replacement as PHP code \u2014 deprecated and dangerous.",
|
|
13670
|
+
attackScenario: "An attacker could inject PHP code through the matched string to achieve remote code execution.",
|
|
13671
|
+
suggestedFix: "Use preg_replace_callback() instead of the /e modifier."
|
|
13672
|
+
},
|
|
13673
|
+
{
|
|
13674
|
+
regex: /\bunserialize\s*\(\s*\$(?:_GET|_POST|_REQUEST|_COOKIE)/,
|
|
13675
|
+
title: "PHP insecure deserialization of user input",
|
|
13676
|
+
vulnClass: "code-injection",
|
|
13677
|
+
baseSeverity: "high",
|
|
13678
|
+
description: "unserialize() called on user-controlled superglobal input \u2014 potential RCE via PHP object injection.",
|
|
13679
|
+
attackScenario: "An attacker could craft a malicious serialized PHP object to achieve remote code execution.",
|
|
13680
|
+
suggestedFix: "Use json_decode() instead of unserialize() for user input. If unserialize is necessary, use the allowed_classes option."
|
|
13681
|
+
},
|
|
13682
|
+
{
|
|
13683
|
+
regex: /\bextract\s*\(\s*\$(?:_GET|_POST|_REQUEST)/,
|
|
13684
|
+
title: "PHP extract() on superglobal input",
|
|
13685
|
+
vulnClass: "code-injection",
|
|
13686
|
+
baseSeverity: "high",
|
|
13687
|
+
description: "extract() on $_GET/$_POST/$_REQUEST overwrites local variables \u2014 can bypass security checks.",
|
|
13688
|
+
attackScenario: "An attacker could set arbitrary variables by crafting request parameters, potentially overwriting auth flags or config values.",
|
|
13689
|
+
suggestedFix: "Avoid extract() on user input. Access superglobals directly or use a whitelist of expected keys."
|
|
12874
13690
|
}
|
|
12875
13691
|
];
|
|
12876
13692
|
function shouldSkip(filePath) {
|
|
@@ -12887,7 +13703,7 @@ async function checkInjection(files, projectRoot) {
|
|
|
12887
13703
|
if (shouldSkip(file.filePath) || isTestFile4(file.filePath)) continue;
|
|
12888
13704
|
let content;
|
|
12889
13705
|
try {
|
|
12890
|
-
content =
|
|
13706
|
+
content = readFileSync18(join22(projectRoot, file.filePath), "utf-8");
|
|
12891
13707
|
} catch {
|
|
12892
13708
|
continue;
|
|
12893
13709
|
}
|
|
@@ -12925,8 +13741,8 @@ async function checkInjection(files, projectRoot) {
|
|
|
12925
13741
|
}
|
|
12926
13742
|
|
|
12927
13743
|
// src/security/checks/secrets.ts
|
|
12928
|
-
import { readFileSync as
|
|
12929
|
-
import { join as
|
|
13744
|
+
import { readFileSync as readFileSync19 } from "fs";
|
|
13745
|
+
import { join as join23 } from "path";
|
|
12930
13746
|
var SKIP_DIRS2 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
|
|
12931
13747
|
var TEST_PATTERNS2 = ["test", "spec", "fixture", "mock", "__tests__", "__mocks__", ".example", ".sample"];
|
|
12932
13748
|
var SECRET_PATTERNS = [
|
|
@@ -12959,7 +13775,7 @@ async function checkSecrets(files, projectRoot) {
|
|
|
12959
13775
|
if (shouldSkip2(file.filePath) || isTestFile5(file.filePath)) continue;
|
|
12960
13776
|
let content;
|
|
12961
13777
|
try {
|
|
12962
|
-
content =
|
|
13778
|
+
content = readFileSync19(join23(projectRoot, file.filePath), "utf-8");
|
|
12963
13779
|
} catch {
|
|
12964
13780
|
continue;
|
|
12965
13781
|
}
|
|
@@ -12992,8 +13808,8 @@ async function checkSecrets(files, projectRoot) {
|
|
|
12992
13808
|
}
|
|
12993
13809
|
|
|
12994
13810
|
// src/security/checks/path-traversal.ts
|
|
12995
|
-
import { readFileSync as
|
|
12996
|
-
import { join as
|
|
13811
|
+
import { readFileSync as readFileSync20 } from "fs";
|
|
13812
|
+
import { join as join24 } from "path";
|
|
12997
13813
|
var SKIP_DIRS3 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
|
|
12998
13814
|
var USER_INPUT_VARS = /(?:req\.|params|query|body|input|path|dir|subdirectory|file|userInput|fileName|filePath)/i;
|
|
12999
13815
|
var PATTERNS2 = [
|
|
@@ -13040,7 +13856,7 @@ async function checkPathTraversal(files, projectRoot) {
|
|
|
13040
13856
|
if (shouldSkip3(file.filePath)) continue;
|
|
13041
13857
|
let content;
|
|
13042
13858
|
try {
|
|
13043
|
-
content =
|
|
13859
|
+
content = readFileSync20(join24(projectRoot, file.filePath), "utf-8");
|
|
13044
13860
|
} catch {
|
|
13045
13861
|
continue;
|
|
13046
13862
|
}
|
|
@@ -13082,8 +13898,8 @@ async function checkPathTraversal(files, projectRoot) {
|
|
|
13082
13898
|
}
|
|
13083
13899
|
|
|
13084
13900
|
// src/security/checks/auth.ts
|
|
13085
|
-
import { readFileSync as
|
|
13086
|
-
import { join as
|
|
13901
|
+
import { readFileSync as readFileSync21 } from "fs";
|
|
13902
|
+
import { join as join25 } from "path";
|
|
13087
13903
|
var SKIP_DIRS4 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
|
|
13088
13904
|
function shouldSkip4(filePath) {
|
|
13089
13905
|
return SKIP_DIRS4.some((d) => filePath.includes(d));
|
|
@@ -13099,7 +13915,7 @@ async function checkAuth(files, projectRoot) {
|
|
|
13099
13915
|
if (shouldSkip4(file.filePath)) continue;
|
|
13100
13916
|
let content;
|
|
13101
13917
|
try {
|
|
13102
|
-
content =
|
|
13918
|
+
content = readFileSync21(join25(projectRoot, file.filePath), "utf-8");
|
|
13103
13919
|
} catch {
|
|
13104
13920
|
continue;
|
|
13105
13921
|
}
|
|
@@ -13190,8 +14006,8 @@ async function checkAuth(files, projectRoot) {
|
|
|
13190
14006
|
}
|
|
13191
14007
|
|
|
13192
14008
|
// src/security/checks/input-validation.ts
|
|
13193
|
-
import { readFileSync as
|
|
13194
|
-
import { join as
|
|
14009
|
+
import { readFileSync as readFileSync22 } from "fs";
|
|
14010
|
+
import { join as join26 } from "path";
|
|
13195
14011
|
var SKIP_DIRS5 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
|
|
13196
14012
|
function shouldSkip5(filePath) {
|
|
13197
14013
|
return SKIP_DIRS5.some((d) => filePath.includes(d));
|
|
@@ -13203,7 +14019,7 @@ async function checkInputValidation(files, projectRoot) {
|
|
|
13203
14019
|
if (shouldSkip5(file.filePath)) continue;
|
|
13204
14020
|
let content;
|
|
13205
14021
|
try {
|
|
13206
|
-
content =
|
|
14022
|
+
content = readFileSync22(join26(projectRoot, file.filePath), "utf-8");
|
|
13207
14023
|
} catch {
|
|
13208
14024
|
continue;
|
|
13209
14025
|
}
|
|
@@ -13280,8 +14096,8 @@ async function checkInputValidation(files, projectRoot) {
|
|
|
13280
14096
|
}
|
|
13281
14097
|
|
|
13282
14098
|
// src/security/checks/information-disclosure.ts
|
|
13283
|
-
import { readFileSync as
|
|
13284
|
-
import { join as
|
|
14099
|
+
import { readFileSync as readFileSync23 } from "fs";
|
|
14100
|
+
import { join as join27 } from "path";
|
|
13285
14101
|
var SKIP_DIRS6 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
|
|
13286
14102
|
function shouldSkip6(filePath) {
|
|
13287
14103
|
return SKIP_DIRS6.some((d) => filePath.includes(d));
|
|
@@ -13293,7 +14109,7 @@ async function checkInformationDisclosure(files, projectRoot) {
|
|
|
13293
14109
|
if (shouldSkip6(file.filePath)) continue;
|
|
13294
14110
|
let content;
|
|
13295
14111
|
try {
|
|
13296
|
-
content =
|
|
14112
|
+
content = readFileSync23(join27(projectRoot, file.filePath), "utf-8");
|
|
13297
14113
|
} catch {
|
|
13298
14114
|
continue;
|
|
13299
14115
|
}
|
|
@@ -13363,8 +14179,8 @@ async function checkInformationDisclosure(files, projectRoot) {
|
|
|
13363
14179
|
}
|
|
13364
14180
|
|
|
13365
14181
|
// src/security/checks/cryptography.ts
|
|
13366
|
-
import { readFileSync as
|
|
13367
|
-
import { join as
|
|
14182
|
+
import { readFileSync as readFileSync24 } from "fs";
|
|
14183
|
+
import { join as join28 } from "path";
|
|
13368
14184
|
var SKIP_DIRS7 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
|
|
13369
14185
|
var USER_INPUT_NAMES2 = /(?:input|user|name|path|query|param|request|body|args|url)/i;
|
|
13370
14186
|
function shouldSkip7(filePath) {
|
|
@@ -13381,7 +14197,7 @@ async function checkCryptography(files, projectRoot) {
|
|
|
13381
14197
|
if (shouldSkip7(file.filePath)) continue;
|
|
13382
14198
|
let content;
|
|
13383
14199
|
try {
|
|
13384
|
-
content =
|
|
14200
|
+
content = readFileSync24(join28(projectRoot, file.filePath), "utf-8");
|
|
13385
14201
|
} catch {
|
|
13386
14202
|
continue;
|
|
13387
14203
|
}
|
|
@@ -13561,6 +14377,84 @@ async function checkCryptography(files, projectRoot) {
|
|
|
13561
14377
|
suggestedFix: "Use HTTPS for all external URLs to ensure data confidentiality and integrity."
|
|
13562
14378
|
});
|
|
13563
14379
|
}
|
|
14380
|
+
if (/\bmd5\s*\(/.test(line) && /password|passwd|pass|pwd/i.test(line)) {
|
|
14381
|
+
findings.push({
|
|
14382
|
+
id: "",
|
|
14383
|
+
severity: "high",
|
|
14384
|
+
vulnerabilityClass: "cryptography",
|
|
14385
|
+
file: file.filePath,
|
|
14386
|
+
line: i + 1,
|
|
14387
|
+
title: "PHP md5() used for password hashing",
|
|
14388
|
+
description: "md5() is cryptographically broken and should never be used for password hashing.",
|
|
14389
|
+
attackScenario: "An attacker could crack MD5 password hashes in seconds using rainbow tables or GPU brute force.",
|
|
14390
|
+
suggestedFix: "Use password_hash() with PASSWORD_BCRYPT or PASSWORD_ARGON2ID."
|
|
14391
|
+
});
|
|
14392
|
+
}
|
|
14393
|
+
if (/\bsha1\s*\(/.test(line) && /password|passwd|pass|pwd/i.test(line)) {
|
|
14394
|
+
findings.push({
|
|
14395
|
+
id: "",
|
|
14396
|
+
severity: "high",
|
|
14397
|
+
vulnerabilityClass: "cryptography",
|
|
14398
|
+
file: file.filePath,
|
|
14399
|
+
line: i + 1,
|
|
14400
|
+
title: "PHP sha1() used for password hashing",
|
|
14401
|
+
description: "SHA-1 has known collision attacks and should not be used for password hashing.",
|
|
14402
|
+
attackScenario: "An attacker could crack SHA-1 password hashes using precomputed tables.",
|
|
14403
|
+
suggestedFix: "Use password_hash() with PASSWORD_BCRYPT or PASSWORD_ARGON2ID."
|
|
14404
|
+
});
|
|
14405
|
+
}
|
|
14406
|
+
if (/\bcrypt\s*\(\s*[^,]+,\s*['"][\$]?[12a-zA-Z]{0,3}['"]/.test(line)) {
|
|
14407
|
+
findings.push({
|
|
14408
|
+
id: "",
|
|
14409
|
+
severity: "medium",
|
|
14410
|
+
vulnerabilityClass: "cryptography",
|
|
14411
|
+
file: file.filePath,
|
|
14412
|
+
line: i + 1,
|
|
14413
|
+
title: "PHP crypt() with potentially weak salt",
|
|
14414
|
+
description: "crypt() with a short or weak salt may use DES or MD5 algorithm.",
|
|
14415
|
+
attackScenario: "An attacker could crack weakly-salted crypt() hashes using brute force.",
|
|
14416
|
+
suggestedFix: "Use password_hash() instead of crypt(). It automatically uses a strong algorithm and salt."
|
|
14417
|
+
});
|
|
14418
|
+
}
|
|
14419
|
+
if (/\bmcrypt_/.test(line)) {
|
|
14420
|
+
findings.push({
|
|
14421
|
+
id: "",
|
|
14422
|
+
severity: "high",
|
|
14423
|
+
vulnerabilityClass: "cryptography",
|
|
14424
|
+
file: file.filePath,
|
|
14425
|
+
line: i + 1,
|
|
14426
|
+
title: "PHP deprecated mcrypt_* function",
|
|
14427
|
+
description: "mcrypt extension was deprecated in PHP 7.1 and removed in PHP 7.2. It has known vulnerabilities.",
|
|
14428
|
+
attackScenario: "An attacker could exploit known weaknesses in mcrypt implementations.",
|
|
14429
|
+
suggestedFix: "Use openssl_encrypt()/openssl_decrypt() or the sodium extension (sodium_crypto_*)."
|
|
14430
|
+
});
|
|
14431
|
+
}
|
|
14432
|
+
if (/\b(?:rand|mt_rand)\s*\(/.test(line) && isCryptoFile) {
|
|
14433
|
+
findings.push({
|
|
14434
|
+
id: "",
|
|
14435
|
+
severity: "medium",
|
|
14436
|
+
vulnerabilityClass: "cryptography",
|
|
14437
|
+
file: file.filePath,
|
|
14438
|
+
line: i + 1,
|
|
14439
|
+
title: "PHP rand()/mt_rand() in security context",
|
|
14440
|
+
description: "rand() and mt_rand() are not cryptographically secure \u2014 their output can be predicted.",
|
|
14441
|
+
attackScenario: "An attacker could predict random values to forge tokens or bypass security checks.",
|
|
14442
|
+
suggestedFix: "Use random_bytes() or random_int() for cryptographic purposes."
|
|
14443
|
+
});
|
|
14444
|
+
}
|
|
14445
|
+
if (/\$(?:password|secret|api_?key|token)\s*=\s*['"][^'"]{4,}['"]/i.test(line)) {
|
|
14446
|
+
findings.push({
|
|
14447
|
+
id: "",
|
|
14448
|
+
severity: "high",
|
|
14449
|
+
vulnerabilityClass: "cryptography",
|
|
14450
|
+
file: file.filePath,
|
|
14451
|
+
line: i + 1,
|
|
14452
|
+
title: "Hardcoded credentials in PHP source",
|
|
14453
|
+
description: "A password, secret, or API key is hardcoded as a string literal.",
|
|
14454
|
+
attackScenario: "An attacker with access to the source could extract the credential.",
|
|
14455
|
+
suggestedFix: "Load credentials from environment variables using getenv() or $_ENV."
|
|
14456
|
+
});
|
|
14457
|
+
}
|
|
13564
14458
|
}
|
|
13565
14459
|
}
|
|
13566
14460
|
} catch {
|
|
@@ -13569,8 +14463,8 @@ async function checkCryptography(files, projectRoot) {
|
|
|
13569
14463
|
}
|
|
13570
14464
|
|
|
13571
14465
|
// src/security/checks/frontend.ts
|
|
13572
|
-
import { readFileSync as
|
|
13573
|
-
import { join as
|
|
14466
|
+
import { readFileSync as readFileSync25 } from "fs";
|
|
14467
|
+
import { join as join29 } from "path";
|
|
13574
14468
|
var SKIP_DIRS8 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
|
|
13575
14469
|
function shouldSkip8(filePath) {
|
|
13576
14470
|
return SKIP_DIRS8.some((d) => filePath.includes(d));
|
|
@@ -13586,7 +14480,7 @@ async function checkFrontend(files, projectRoot) {
|
|
|
13586
14480
|
if (!isFrontendFile(file.filePath)) continue;
|
|
13587
14481
|
let content;
|
|
13588
14482
|
try {
|
|
13589
|
-
content =
|
|
14483
|
+
content = readFileSync25(join29(projectRoot, file.filePath), "utf-8");
|
|
13590
14484
|
} catch {
|
|
13591
14485
|
continue;
|
|
13592
14486
|
}
|
|
@@ -14046,13 +14940,13 @@ async function scanSecurity(projectRoot, graph, options = {}) {
|
|
|
14046
14940
|
};
|
|
14047
14941
|
}
|
|
14048
14942
|
function detectPackageManager(projectRoot) {
|
|
14049
|
-
if (
|
|
14050
|
-
if (
|
|
14051
|
-
if (
|
|
14052
|
-
if (
|
|
14053
|
-
if (
|
|
14054
|
-
if (
|
|
14055
|
-
if (
|
|
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";
|
|
14056
14950
|
return "unknown";
|
|
14057
14951
|
}
|
|
14058
14952
|
|