@shipsafe/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +167 -0
- package/dist/bin/shipsafe.d.ts +3 -0
- package/dist/bin/shipsafe.d.ts.map +1 -0
- package/dist/bin/shipsafe.js +33 -0
- package/dist/bin/shipsafe.js.map +1 -0
- package/dist/src/autofix/pr-generator.d.ts +48 -0
- package/dist/src/autofix/pr-generator.d.ts.map +1 -0
- package/dist/src/autofix/pr-generator.js +359 -0
- package/dist/src/autofix/pr-generator.js.map +1 -0
- package/dist/src/autofix/scaffolding.d.ts +26 -0
- package/dist/src/autofix/scaffolding.d.ts.map +1 -0
- package/dist/src/autofix/scaffolding.js +249 -0
- package/dist/src/autofix/scaffolding.js.map +1 -0
- package/dist/src/autofix/secret-fixer.d.ts +27 -0
- package/dist/src/autofix/secret-fixer.d.ts.map +1 -0
- package/dist/src/autofix/secret-fixer.js +138 -0
- package/dist/src/autofix/secret-fixer.js.map +1 -0
- package/dist/src/claude-md/manager.d.ts +17 -0
- package/dist/src/claude-md/manager.d.ts.map +1 -0
- package/dist/src/claude-md/manager.js +143 -0
- package/dist/src/claude-md/manager.js.map +1 -0
- package/dist/src/cli/activate.d.ts +4 -0
- package/dist/src/cli/activate.d.ts.map +1 -0
- package/dist/src/cli/activate.js +53 -0
- package/dist/src/cli/activate.js.map +1 -0
- package/dist/src/cli/config.d.ts +21 -0
- package/dist/src/cli/config.d.ts.map +1 -0
- package/dist/src/cli/config.js +128 -0
- package/dist/src/cli/config.js.map +1 -0
- package/dist/src/cli/connect.d.ts +36 -0
- package/dist/src/cli/connect.d.ts.map +1 -0
- package/dist/src/cli/connect.js +107 -0
- package/dist/src/cli/connect.js.map +1 -0
- package/dist/src/cli/init.d.ts +12 -0
- package/dist/src/cli/init.d.ts.map +1 -0
- package/dist/src/cli/init.js +45 -0
- package/dist/src/cli/init.js.map +1 -0
- package/dist/src/cli/license-check.d.ts +7 -0
- package/dist/src/cli/license-check.d.ts.map +1 -0
- package/dist/src/cli/license-check.js +69 -0
- package/dist/src/cli/license-check.js.map +1 -0
- package/dist/src/cli/license-gate.d.ts +9 -0
- package/dist/src/cli/license-gate.d.ts.map +1 -0
- package/dist/src/cli/license-gate.js +25 -0
- package/dist/src/cli/license-gate.js.map +1 -0
- package/dist/src/cli/scan.d.ts +9 -0
- package/dist/src/cli/scan.d.ts.map +1 -0
- package/dist/src/cli/scan.js +75 -0
- package/dist/src/cli/scan.js.map +1 -0
- package/dist/src/cli/setup.d.ts +27 -0
- package/dist/src/cli/setup.d.ts.map +1 -0
- package/dist/src/cli/setup.js +134 -0
- package/dist/src/cli/setup.js.map +1 -0
- package/dist/src/cli/status.d.ts +4 -0
- package/dist/src/cli/status.d.ts.map +1 -0
- package/dist/src/cli/status.js +52 -0
- package/dist/src/cli/status.js.map +1 -0
- package/dist/src/cli/upload-sourcemaps.d.ts +13 -0
- package/dist/src/cli/upload-sourcemaps.d.ts.map +1 -0
- package/dist/src/cli/upload-sourcemaps.js +157 -0
- package/dist/src/cli/upload-sourcemaps.js.map +1 -0
- package/dist/src/config/manager.d.ts +37 -0
- package/dist/src/config/manager.d.ts.map +1 -0
- package/dist/src/config/manager.js +131 -0
- package/dist/src/config/manager.js.map +1 -0
- package/dist/src/constants.d.ts +28 -0
- package/dist/src/constants.d.ts.map +1 -0
- package/dist/src/constants.js +34 -0
- package/dist/src/constants.js.map +1 -0
- package/dist/src/engines/graph/data-flow.d.ts +36 -0
- package/dist/src/engines/graph/data-flow.d.ts.map +1 -0
- package/dist/src/engines/graph/data-flow.js +189 -0
- package/dist/src/engines/graph/data-flow.js.map +1 -0
- package/dist/src/engines/graph/index.d.ts +20 -0
- package/dist/src/engines/graph/index.d.ts.map +1 -0
- package/dist/src/engines/graph/index.js +100 -0
- package/dist/src/engines/graph/index.js.map +1 -0
- package/dist/src/engines/graph/parser.d.ts +13 -0
- package/dist/src/engines/graph/parser.d.ts.map +1 -0
- package/dist/src/engines/graph/parser.js +620 -0
- package/dist/src/engines/graph/parser.js.map +1 -0
- package/dist/src/engines/graph/queries.d.ts +11 -0
- package/dist/src/engines/graph/queries.d.ts.map +1 -0
- package/dist/src/engines/graph/queries.js +196 -0
- package/dist/src/engines/graph/queries.js.map +1 -0
- package/dist/src/engines/graph/store.d.ts +35 -0
- package/dist/src/engines/graph/store.d.ts.map +1 -0
- package/dist/src/engines/graph/store.js +284 -0
- package/dist/src/engines/graph/store.js.map +1 -0
- package/dist/src/engines/pattern/gitleaks.d.ts +4 -0
- package/dist/src/engines/pattern/gitleaks.d.ts.map +1 -0
- package/dist/src/engines/pattern/gitleaks.js +78 -0
- package/dist/src/engines/pattern/gitleaks.js.map +1 -0
- package/dist/src/engines/pattern/index.d.ts +11 -0
- package/dist/src/engines/pattern/index.d.ts.map +1 -0
- package/dist/src/engines/pattern/index.js +111 -0
- package/dist/src/engines/pattern/index.js.map +1 -0
- package/dist/src/engines/pattern/semgrep.d.ts +4 -0
- package/dist/src/engines/pattern/semgrep.d.ts.map +1 -0
- package/dist/src/engines/pattern/semgrep.js +83 -0
- package/dist/src/engines/pattern/semgrep.js.map +1 -0
- package/dist/src/engines/pattern/trivy.d.ts +4 -0
- package/dist/src/engines/pattern/trivy.d.ts.map +1 -0
- package/dist/src/engines/pattern/trivy.js +90 -0
- package/dist/src/engines/pattern/trivy.js.map +1 -0
- package/dist/src/github/api.d.ts +19 -0
- package/dist/src/github/api.d.ts.map +1 -0
- package/dist/src/github/api.js +75 -0
- package/dist/src/github/api.js.map +1 -0
- package/dist/src/github/app-manifest.d.ts +28 -0
- package/dist/src/github/app-manifest.d.ts.map +1 -0
- package/dist/src/github/app-manifest.js +27 -0
- package/dist/src/github/app-manifest.js.map +1 -0
- package/dist/src/github/checks.d.ts +36 -0
- package/dist/src/github/checks.d.ts.map +1 -0
- package/dist/src/github/checks.js +90 -0
- package/dist/src/github/checks.js.map +1 -0
- package/dist/src/github/scanner.d.ts +20 -0
- package/dist/src/github/scanner.d.ts.map +1 -0
- package/dist/src/github/scanner.js +78 -0
- package/dist/src/github/scanner.js.map +1 -0
- package/dist/src/github/webhook.d.ts +39 -0
- package/dist/src/github/webhook.d.ts.map +1 -0
- package/dist/src/github/webhook.js +80 -0
- package/dist/src/github/webhook.js.map +1 -0
- package/dist/src/hooks/installer.d.ts +4 -0
- package/dist/src/hooks/installer.d.ts.map +1 -0
- package/dist/src/hooks/installer.js +146 -0
- package/dist/src/hooks/installer.js.map +1 -0
- package/dist/src/mcp/server.d.ts +2 -0
- package/dist/src/mcp/server.d.ts.map +1 -0
- package/dist/src/mcp/server.js +96 -0
- package/dist/src/mcp/server.js.map +1 -0
- package/dist/src/mcp/tools/check-package.d.ts +30 -0
- package/dist/src/mcp/tools/check-package.d.ts.map +1 -0
- package/dist/src/mcp/tools/check-package.js +196 -0
- package/dist/src/mcp/tools/check-package.js.map +1 -0
- package/dist/src/mcp/tools/fix.d.ts +41 -0
- package/dist/src/mcp/tools/fix.d.ts.map +1 -0
- package/dist/src/mcp/tools/fix.js +98 -0
- package/dist/src/mcp/tools/fix.js.map +1 -0
- package/dist/src/mcp/tools/graph-query.d.ts +7 -0
- package/dist/src/mcp/tools/graph-query.d.ts.map +1 -0
- package/dist/src/mcp/tools/graph-query.js +139 -0
- package/dist/src/mcp/tools/graph-query.js.map +1 -0
- package/dist/src/mcp/tools/production-errors.d.ts +23 -0
- package/dist/src/mcp/tools/production-errors.d.ts.map +1 -0
- package/dist/src/mcp/tools/production-errors.js +46 -0
- package/dist/src/mcp/tools/production-errors.js.map +1 -0
- package/dist/src/mcp/tools/scan.d.ts +7 -0
- package/dist/src/mcp/tools/scan.d.ts.map +1 -0
- package/dist/src/mcp/tools/scan.js +9 -0
- package/dist/src/mcp/tools/scan.js.map +1 -0
- package/dist/src/mcp/tools/status.d.ts +9 -0
- package/dist/src/mcp/tools/status.d.ts.map +1 -0
- package/dist/src/mcp/tools/status.js +18 -0
- package/dist/src/mcp/tools/status.js.map +1 -0
- package/dist/src/mcp/tools/verify-resolution.d.ts +12 -0
- package/dist/src/mcp/tools/verify-resolution.d.ts.map +1 -0
- package/dist/src/mcp/tools/verify-resolution.js +45 -0
- package/dist/src/mcp/tools/verify-resolution.js.map +1 -0
- package/dist/src/types.d.ts +136 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/package.json +53 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
// ── Constants ──
|
|
2
|
+
const ENTRY_POINT_PATTERNS = ['handle', 'route', 'controller', 'api', 'endpoint'];
|
|
3
|
+
const SINK_PATTERNS = {
|
|
4
|
+
database: ['query', 'execute', 'exec', 'find', 'insert', 'update', 'delete', 'raw'],
|
|
5
|
+
filesystem: ['readFile', 'writeFile', 'unlink', 'createReadStream', 'open'],
|
|
6
|
+
shell: ['exec', 'execSync', 'spawn', 'execFile'],
|
|
7
|
+
network: ['fetch', 'axios', 'request', 'http.get'],
|
|
8
|
+
};
|
|
9
|
+
const VALIDATOR_PATTERNS = ['valid', 'sanitiz', 'escape', 'clean', 'check', 'verify', 'auth'];
|
|
10
|
+
// ── Helpers ──
|
|
11
|
+
function isEntryPoint(fn) {
|
|
12
|
+
const nameLower = fn.name.toLowerCase();
|
|
13
|
+
return (fn.isExported &&
|
|
14
|
+
(ENTRY_POINT_PATTERNS.some((p) => nameLower.includes(p)) || fn.isAsync));
|
|
15
|
+
}
|
|
16
|
+
function classifySink(name) {
|
|
17
|
+
const nameLower = name.toLowerCase();
|
|
18
|
+
for (const [type, patterns] of Object.entries(SINK_PATTERNS)) {
|
|
19
|
+
for (const pattern of patterns) {
|
|
20
|
+
if (nameLower === pattern.toLowerCase()) {
|
|
21
|
+
return type;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
function isValidator(name) {
|
|
28
|
+
const nameLower = name.toLowerCase();
|
|
29
|
+
return VALIDATOR_PATTERNS.some((p) => nameLower.includes(p));
|
|
30
|
+
}
|
|
31
|
+
function isSink(name) {
|
|
32
|
+
return classifySink(name) !== null;
|
|
33
|
+
}
|
|
34
|
+
function isHandlerLike(name) {
|
|
35
|
+
const nameLower = name.toLowerCase();
|
|
36
|
+
return ENTRY_POINT_PATTERNS.some((p) => nameLower.includes(p));
|
|
37
|
+
}
|
|
38
|
+
// ── findAttackPaths ──
|
|
39
|
+
/**
|
|
40
|
+
* Find paths from entry point functions to dangerous sink functions.
|
|
41
|
+
* Uses BFS via getCallees to trace call chains without native path queries.
|
|
42
|
+
*/
|
|
43
|
+
export async function findAttackPaths(store) {
|
|
44
|
+
// Step 1: Get all functions to identify entry points
|
|
45
|
+
const allFunctions = (await store.query('MATCH (fn:Function) RETURN fn.name AS name, fn.filePath AS filePath, fn.startLine AS line, fn.isAsync AS isAsync, fn.isExported AS isExported'));
|
|
46
|
+
const entryPoints = allFunctions.filter((fn) => {
|
|
47
|
+
const name = fn['name'];
|
|
48
|
+
const isExported = fn['isExported'];
|
|
49
|
+
const isAsync = fn['isAsync'];
|
|
50
|
+
return isExported && (isHandlerLike(name) || isAsync);
|
|
51
|
+
});
|
|
52
|
+
const attackPaths = [];
|
|
53
|
+
// Step 2: For each entry point, trace call chains to find sinks
|
|
54
|
+
for (const entry of entryPoints) {
|
|
55
|
+
const entryName = entry['name'];
|
|
56
|
+
// Get all direct callees (depth 1 only to build specific paths)
|
|
57
|
+
const directCallees = await store.getCallees(entryName, 1);
|
|
58
|
+
for (const callee of directCallees) {
|
|
59
|
+
if (isSink(callee.name)) {
|
|
60
|
+
// Direct path: entry -> sink
|
|
61
|
+
const pathNames = [entryName, callee.name];
|
|
62
|
+
attackPaths.push({
|
|
63
|
+
entryPoint: {
|
|
64
|
+
name: entryName,
|
|
65
|
+
filePath: entry['filePath'],
|
|
66
|
+
line: entry['line'],
|
|
67
|
+
},
|
|
68
|
+
sink: {
|
|
69
|
+
name: callee.name,
|
|
70
|
+
filePath: callee.filePath,
|
|
71
|
+
line: callee.startLine,
|
|
72
|
+
type: classifySink(callee.name),
|
|
73
|
+
},
|
|
74
|
+
path: pathNames,
|
|
75
|
+
hasValidation: pathNames.some((n) => isValidator(n)),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
// Check if this intermediate callee leads to a sink (depth 2-5)
|
|
80
|
+
const deepCallees = await store.getCallees(callee.name, 4);
|
|
81
|
+
for (const deepCallee of deepCallees) {
|
|
82
|
+
if (isSink(deepCallee.name)) {
|
|
83
|
+
const pathNames = [entryName, callee.name, deepCallee.name];
|
|
84
|
+
attackPaths.push({
|
|
85
|
+
entryPoint: {
|
|
86
|
+
name: entryName,
|
|
87
|
+
filePath: entry['filePath'],
|
|
88
|
+
line: entry['line'],
|
|
89
|
+
},
|
|
90
|
+
sink: {
|
|
91
|
+
name: deepCallee.name,
|
|
92
|
+
filePath: deepCallee.filePath,
|
|
93
|
+
line: deepCallee.startLine,
|
|
94
|
+
type: classifySink(deepCallee.name),
|
|
95
|
+
},
|
|
96
|
+
path: pathNames,
|
|
97
|
+
hasValidation: pathNames.some((n) => isValidator(n)),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return attackPaths;
|
|
105
|
+
}
|
|
106
|
+
// ── findBlastRadius ──
|
|
107
|
+
export async function findBlastRadius(store, functionName) {
|
|
108
|
+
// Find all transitive callers of the target function
|
|
109
|
+
const callers = await store.getCallers(functionName, 10);
|
|
110
|
+
const affectedFunctions = callers.map((fn) => ({
|
|
111
|
+
name: fn.name,
|
|
112
|
+
filePath: fn.filePath,
|
|
113
|
+
line: fn.startLine,
|
|
114
|
+
}));
|
|
115
|
+
// Identify which callers are endpoints (exported + handler-like or async)
|
|
116
|
+
const affectedEndpoints = callers
|
|
117
|
+
.filter((fn) => isEntryPoint(fn))
|
|
118
|
+
.map((fn) => ({
|
|
119
|
+
name: fn.name,
|
|
120
|
+
filePath: fn.filePath,
|
|
121
|
+
line: fn.startLine,
|
|
122
|
+
}));
|
|
123
|
+
return {
|
|
124
|
+
targetFunction: functionName,
|
|
125
|
+
affectedFunctions,
|
|
126
|
+
affectedEndpoints,
|
|
127
|
+
totalAffected: affectedFunctions.length,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
// ── findMissingAuth ──
|
|
131
|
+
export async function findMissingAuth(store) {
|
|
132
|
+
// Find all exported endpoint-like functions
|
|
133
|
+
const allFunctions = (await store.query('MATCH (fn:Function) RETURN fn.name AS name, fn.filePath AS filePath, fn.startLine AS line, fn.isAsync AS isAsync, fn.isExported AS isExported'));
|
|
134
|
+
const results = [];
|
|
135
|
+
for (const fn of allFunctions) {
|
|
136
|
+
const name = fn['name'];
|
|
137
|
+
const isExported = fn['isExported'];
|
|
138
|
+
const isAsync = fn['isAsync'];
|
|
139
|
+
// Only check endpoint-like functions (handler/route/controller/api pattern AND async)
|
|
140
|
+
if (!isExported || !isAsync || !isHandlerLike(name))
|
|
141
|
+
continue;
|
|
142
|
+
// Check if any function in the call chain (callees) has 'auth' in the name
|
|
143
|
+
const callees = await store.getCallees(name, 10);
|
|
144
|
+
const hasAuth = callees.some((callee) => callee.name.toLowerCase().includes('auth'));
|
|
145
|
+
if (!hasAuth) {
|
|
146
|
+
results.push({
|
|
147
|
+
endpoint: {
|
|
148
|
+
name,
|
|
149
|
+
filePath: fn['filePath'],
|
|
150
|
+
line: fn['line'],
|
|
151
|
+
},
|
|
152
|
+
reason: 'No auth middleware in call chain',
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return results;
|
|
157
|
+
}
|
|
158
|
+
// ── queryResultsToFindings ──
|
|
159
|
+
export function queryResultsToFindings(attackPaths, blastRadius, missingAuth) {
|
|
160
|
+
const findings = [];
|
|
161
|
+
let idCounter = 0;
|
|
162
|
+
// Attack paths without validation are findings
|
|
163
|
+
for (const ap of attackPaths) {
|
|
164
|
+
if (!ap.hasValidation) {
|
|
165
|
+
idCounter++;
|
|
166
|
+
findings.push({
|
|
167
|
+
id: `kg-attack-path-${idCounter}`,
|
|
168
|
+
engine: 'knowledge_graph',
|
|
169
|
+
severity: ap.sink.type === 'shell' ? 'critical' : 'high',
|
|
170
|
+
type: 'attack_path',
|
|
171
|
+
file: ap.entryPoint.filePath,
|
|
172
|
+
line: ap.entryPoint.line,
|
|
173
|
+
description: `Unvalidated path from ${ap.entryPoint.name} to ${ap.sink.name} (${ap.sink.type} sink): ${ap.path.join(' -> ')}`,
|
|
174
|
+
fix_suggestion: `Add input validation or sanitization in the call chain between ${ap.entryPoint.name} and ${ap.sink.name}`,
|
|
175
|
+
auto_fixable: false,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// Missing auth findings
|
|
180
|
+
for (const ma of missingAuth) {
|
|
181
|
+
idCounter++;
|
|
182
|
+
findings.push({
|
|
183
|
+
id: `kg-missing-auth-${idCounter}`,
|
|
184
|
+
engine: 'knowledge_graph',
|
|
185
|
+
severity: 'high',
|
|
186
|
+
type: 'missing_auth',
|
|
187
|
+
file: ma.endpoint.filePath,
|
|
188
|
+
line: ma.endpoint.line,
|
|
189
|
+
description: `Endpoint ${ma.endpoint.name} has no auth middleware in call chain`,
|
|
190
|
+
fix_suggestion: `Add authentication middleware (e.g., checkAuth) to the call chain for ${ma.endpoint.name}`,
|
|
191
|
+
auto_fixable: false,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
return findings;
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=queries.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queries.js","sourceRoot":"","sources":["../../../../src/engines/graph/queries.ts"],"names":[],"mappings":"AAGA,kBAAkB;AAElB,MAAM,oBAAoB,GAAG,CAAC,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC;AAElF,MAAM,aAAa,GAA6B;IAC9C,QAAQ,EAAE,CAAC,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,CAAC;IACnF,UAAU,EAAE,CAAC,UAAU,EAAE,WAAW,EAAE,QAAQ,EAAE,kBAAkB,EAAE,MAAM,CAAC;IAC3E,KAAK,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU,CAAC;IAChD,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,CAAC;CACnD,CAAC;AAEF,MAAM,kBAAkB,GAAG,CAAC,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;AAE9F,gBAAgB;AAEhB,SAAS,YAAY,CAAC,EAAiB;IACrC,MAAM,SAAS,GAAG,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;IACxC,OAAO,CACL,EAAE,CAAC,UAAU;QACb,CAAC,oBAAoB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,CACxE,CAAC;AACJ,CAAC;AAED,SAAS,YAAY,CAAC,IAAY;IAChC,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IACrC,KAAK,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,EAAE,CAAC;QAC7D,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;YAC/B,IAAI,SAAS,KAAK,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC;gBACxC,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,WAAW,CAAC,IAAY;IAC/B,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IACrC,OAAO,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;AAC/D,CAAC;AAED,SAAS,MAAM,CAAC,IAAY;IAC1B,OAAO,YAAY,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC;AACrC,CAAC;AAED,SAAS,aAAa,CAAC,IAAY;IACjC,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IACrC,OAAO,oBAAoB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;AACjE,CAAC;AAED,wBAAwB;AAExB;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,KAAiB;IACrD,qDAAqD;IACrD,MAAM,YAAY,GAAG,CAAC,MAAM,KAAK,CAAC,KAAK,CACrC,+IAA+I,CAChJ,CAAmC,CAAC;IAErC,MAAM,WAAW,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE;QAC7C,MAAM,IAAI,GAAG,EAAE,CAAC,MAAM,CAAW,CAAC;QAClC,MAAM,UAAU,GAAG,EAAE,CAAC,YAAY,CAAY,CAAC;QAC/C,MAAM,OAAO,GAAG,EAAE,CAAC,SAAS,CAAY,CAAC;QACzC,OAAO,UAAU,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IAEH,MAAM,WAAW,GAAiB,EAAE,CAAC;IAErC,gEAAgE;IAChE,KAAK,MAAM,KAAK,IAAI,WAAW,EAAE,CAAC;QAChC,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,CAAW,CAAC;QAE1C,gEAAgE;QAChE,MAAM,aAAa,GAAG,MAAM,KAAK,CAAC,UAAU,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;QAE3D,KAAK,MAAM,MAAM,IAAI,aAAa,EAAE,CAAC;YACnC,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;gBACxB,6BAA6B;gBAC7B,MAAM,SAAS,GAAG,CAAC,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;gBAC3C,WAAW,CAAC,IAAI,CAAC;oBACf,UAAU,EAAE;wBACV,IAAI,EAAE,SAAS;wBACf,QAAQ,EAAE,KAAK,CAAC,UAAU,CAAW;wBACrC,IAAI,EAAE,KAAK,CAAC,MAAM,CAAW;qBAC9B;oBACD,IAAI,EAAE;wBACJ,IAAI,EAAE,MAAM,CAAC,IAAI;wBACjB,QAAQ,EAAE,MAAM,CAAC,QAAQ;wBACzB,IAAI,EAAE,MAAM,CAAC,SAAS;wBACtB,IAAI,EAAE,YAAY,CAAC,MAAM,CAAC,IAAI,CAAE;qBACjC;oBACD,IAAI,EAAE,SAAS;oBACf,aAAa,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;iBACrD,CAAC,CAAC;YACL,CAAC;iBAAM,CAAC;gBACN,gEAAgE;gBAChE,MAAM,WAAW,GAAG,MAAM,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;gBAC3D,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;oBACrC,IAAI,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;wBAC5B,MAAM,SAAS,GAAG,CAAC,SAAS,EAAE,MAAM,CAAC,IAAI,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC;wBAC5D,WAAW,CAAC,IAAI,CAAC;4BACf,UAAU,EAAE;gCACV,IAAI,EAAE,SAAS;gCACf,QAAQ,EAAE,KAAK,CAAC,UAAU,CAAW;gCACrC,IAAI,EAAE,KAAK,CAAC,MAAM,CAAW;6BAC9B;4BACD,IAAI,EAAE;gCACJ,IAAI,EAAE,UAAU,CAAC,IAAI;gCACrB,QAAQ,EAAE,UAAU,CAAC,QAAQ;gCAC7B,IAAI,EAAE,UAAU,CAAC,SAAS;gCAC1B,IAAI,EAAE,YAAY,CAAC,UAAU,CAAC,IAAI,CAAE;6BACrC;4BACD,IAAI,EAAE,SAAS;4BACf,aAAa,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;yBACrD,CAAC,CAAC;oBACL,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,wBAAwB;AAExB,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,KAAiB,EACjB,YAAoB;IAEpB,qDAAqD;IACrD,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,UAAU,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;IAEzD,MAAM,iBAAiB,GAA2C,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QACrF,IAAI,EAAE,EAAE,CAAC,IAAI;QACb,QAAQ,EAAE,EAAE,CAAC,QAAQ;QACrB,IAAI,EAAE,EAAE,CAAC,SAAS;KACnB,CAAC,CAAC,CAAC;IAEJ,0EAA0E;IAC1E,MAAM,iBAAiB,GAA2C,OAAO;SACtE,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;SAChC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QACZ,IAAI,EAAE,EAAE,CAAC,IAAI;QACb,QAAQ,EAAE,EAAE,CAAC,QAAQ;QACrB,IAAI,EAAE,EAAE,CAAC,SAAS;KACnB,CAAC,CAAC,CAAC;IAEN,OAAO;QACL,cAAc,EAAE,YAAY;QAC5B,iBAAiB;QACjB,iBAAiB;QACjB,aAAa,EAAE,iBAAiB,CAAC,MAAM;KACxC,CAAC;AACJ,CAAC;AAED,wBAAwB;AAExB,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,KAAiB;IACrD,4CAA4C;IAC5C,MAAM,YAAY,GAAG,CAAC,MAAM,KAAK,CAAC,KAAK,CACrC,+IAA+I,CAChJ,CAAmC,CAAC;IAErC,MAAM,OAAO,GAAwB,EAAE,CAAC;IAExC,KAAK,MAAM,EAAE,IAAI,YAAY,EAAE,CAAC;QAC9B,MAAM,IAAI,GAAG,EAAE,CAAC,MAAM,CAAW,CAAC;QAClC,MAAM,UAAU,GAAG,EAAE,CAAC,YAAY,CAAY,CAAC;QAC/C,MAAM,OAAO,GAAG,EAAE,CAAC,SAAS,CAAY,CAAC;QAEzC,sFAAsF;QACtF,IAAI,CAAC,UAAU,IAAI,CAAC,OAAO,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC;YAAE,SAAS;QAE9D,2EAA2E;QAC3E,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,UAAU,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACjD,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;QAErF,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CAAC;gBACX,QAAQ,EAAE;oBACR,IAAI;oBACJ,QAAQ,EAAE,EAAE,CAAC,UAAU,CAAW;oBAClC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAW;iBAC3B;gBACD,MAAM,EAAE,kCAAkC;aAC3C,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,+BAA+B;AAE/B,MAAM,UAAU,sBAAsB,CACpC,WAAyB,EACzB,WAAgC,EAChC,WAAgC;IAEhC,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,IAAI,SAAS,GAAG,CAAC,CAAC;IAElB,+CAA+C;IAC/C,KAAK,MAAM,EAAE,IAAI,WAAW,EAAE,CAAC;QAC7B,IAAI,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC;YACtB,SAAS,EAAE,CAAC;YACZ,QAAQ,CAAC,IAAI,CAAC;gBACZ,EAAE,EAAE,kBAAkB,SAAS,EAAE;gBACjC,MAAM,EAAE,iBAAiB;gBACzB,QAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM;gBACxD,IAAI,EAAE,aAAa;gBACnB,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,QAAQ;gBAC5B,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI;gBACxB,WAAW,EAAE,yBAAyB,EAAE,CAAC,UAAU,CAAC,IAAI,OAAO,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,EAAE,CAAC,IAAI,CAAC,IAAI,WAAW,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE;gBAC7H,cAAc,EAAE,kEAAkE,EAAE,CAAC,UAAU,CAAC,IAAI,QAAQ,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE;gBAC1H,YAAY,EAAE,KAAK;aACpB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,wBAAwB;IACxB,KAAK,MAAM,EAAE,IAAI,WAAW,EAAE,CAAC;QAC7B,SAAS,EAAE,CAAC;QACZ,QAAQ,CAAC,IAAI,CAAC;YACZ,EAAE,EAAE,mBAAmB,SAAS,EAAE;YAClC,MAAM,EAAE,iBAAiB;YACzB,QAAQ,EAAE,MAAM;YAChB,IAAI,EAAE,cAAc;YACpB,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,QAAQ;YAC1B,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI;YACtB,WAAW,EAAE,YAAY,EAAE,CAAC,QAAQ,CAAC,IAAI,uCAAuC;YAChF,cAAc,EAAE,yEAAyE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE;YAC3G,YAAY,EAAE,KAAK;SACpB,CAAC,CAAC;IACL,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { ParsedFile } from '../../types.js';
|
|
2
|
+
export interface GraphFunction {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
filePath: string;
|
|
6
|
+
startLine: number;
|
|
7
|
+
endLine: number;
|
|
8
|
+
isAsync: boolean;
|
|
9
|
+
isExported: boolean;
|
|
10
|
+
className: string;
|
|
11
|
+
}
|
|
12
|
+
export interface GraphImport {
|
|
13
|
+
source: string;
|
|
14
|
+
filePath: string;
|
|
15
|
+
line: number;
|
|
16
|
+
specifiers: string[];
|
|
17
|
+
}
|
|
18
|
+
export interface GraphStore {
|
|
19
|
+
/** Populate the graph from parsed files. */
|
|
20
|
+
buildGraph(parsedFiles: ParsedFile[]): Promise<void>;
|
|
21
|
+
/** Retrieve a function node by name. Returns the first match. */
|
|
22
|
+
getFunction(name: string): Promise<GraphFunction | null>;
|
|
23
|
+
/** Find functions that call the target (transitive up to depth). */
|
|
24
|
+
getCallers(functionName: string, depth?: number): Promise<GraphFunction[]>;
|
|
25
|
+
/** Find functions called by the target (transitive up to depth). */
|
|
26
|
+
getCallees(functionName: string, depth?: number): Promise<GraphFunction[]>;
|
|
27
|
+
/** Get all files that import a given module path. */
|
|
28
|
+
getImportsOf(modulePath: string): Promise<GraphImport[]>;
|
|
29
|
+
/** Execute a raw Cypher query. */
|
|
30
|
+
query(cypher: string, params?: Record<string, unknown>): Promise<unknown[]>;
|
|
31
|
+
/** Close the database and release resources. */
|
|
32
|
+
close(): Promise<void>;
|
|
33
|
+
}
|
|
34
|
+
export declare function createGraphStore(dbPath: string): Promise<GraphStore>;
|
|
35
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../../src/engines/graph/store.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,UAAU,EAKX,MAAM,gBAAgB,CAAC;AAIxB,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,4CAA4C;IAC5C,UAAU,CAAC,WAAW,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAErD,iEAAiE;IACjE,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC;IAEzD,oEAAoE;IACpE,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;IAE3E,oEAAoE;IACpE,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;IAE3E,qDAAqD;IACrD,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;IAEzD,kCAAkC;IAClC,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IAE5E,gDAAgD;IAChD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAkED,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CA+R1E"}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import kuzu from 'kuzu';
|
|
2
|
+
// ── Helpers ──
|
|
3
|
+
function functionId(fn) {
|
|
4
|
+
if (fn.className) {
|
|
5
|
+
return `${fn.filePath}::${fn.className}.${fn.name}`;
|
|
6
|
+
}
|
|
7
|
+
return `${fn.filePath}::${fn.name}`;
|
|
8
|
+
}
|
|
9
|
+
function classId(cls) {
|
|
10
|
+
return `${cls.filePath}::${cls.name}`;
|
|
11
|
+
}
|
|
12
|
+
/** Normalise a QueryResult (which may be a single result or array) into a single result. */
|
|
13
|
+
function unwrapResult(result) {
|
|
14
|
+
if (Array.isArray(result)) {
|
|
15
|
+
return result[0];
|
|
16
|
+
}
|
|
17
|
+
return result;
|
|
18
|
+
}
|
|
19
|
+
// ── Schema creation ──
|
|
20
|
+
const SCHEMA_STATEMENTS = [
|
|
21
|
+
// Node tables
|
|
22
|
+
`CREATE NODE TABLE IF NOT EXISTS Function(
|
|
23
|
+
id STRING,
|
|
24
|
+
name STRING,
|
|
25
|
+
filePath STRING,
|
|
26
|
+
startLine INT64,
|
|
27
|
+
endLine INT64,
|
|
28
|
+
isAsync BOOLEAN,
|
|
29
|
+
isExported BOOLEAN,
|
|
30
|
+
className STRING,
|
|
31
|
+
PRIMARY KEY(id)
|
|
32
|
+
)`,
|
|
33
|
+
`CREATE NODE TABLE IF NOT EXISTS Class(
|
|
34
|
+
id STRING,
|
|
35
|
+
name STRING,
|
|
36
|
+
filePath STRING,
|
|
37
|
+
startLine INT64,
|
|
38
|
+
endLine INT64,
|
|
39
|
+
isExported BOOLEAN,
|
|
40
|
+
PRIMARY KEY(id)
|
|
41
|
+
)`,
|
|
42
|
+
`CREATE NODE TABLE IF NOT EXISTS File(
|
|
43
|
+
path STRING,
|
|
44
|
+
language STRING,
|
|
45
|
+
PRIMARY KEY(path)
|
|
46
|
+
)`,
|
|
47
|
+
`CREATE NODE TABLE IF NOT EXISTS Module(
|
|
48
|
+
name STRING,
|
|
49
|
+
PRIMARY KEY(name)
|
|
50
|
+
)`,
|
|
51
|
+
// Relationship tables
|
|
52
|
+
`CREATE REL TABLE IF NOT EXISTS CALLS(FROM Function TO Function)`,
|
|
53
|
+
`CREATE REL TABLE IF NOT EXISTS CONTAINS(FROM File TO Function)`,
|
|
54
|
+
`CREATE REL TABLE IF NOT EXISTS CONTAINS_CLASS(FROM File TO Class)`,
|
|
55
|
+
`CREATE REL TABLE IF NOT EXISTS HAS_METHOD(FROM Class TO Function)`,
|
|
56
|
+
`CREATE REL TABLE IF NOT EXISTS IMPORTS(FROM File TO Module, line INT64, specifiers STRING)`,
|
|
57
|
+
];
|
|
58
|
+
// ── Implementation ──
|
|
59
|
+
export async function createGraphStore(dbPath) {
|
|
60
|
+
const db = new kuzu.Database(dbPath);
|
|
61
|
+
const conn = new kuzu.Connection(db);
|
|
62
|
+
// Ensure schema exists
|
|
63
|
+
for (const stmt of SCHEMA_STATEMENTS) {
|
|
64
|
+
await conn.query(stmt);
|
|
65
|
+
}
|
|
66
|
+
// Pre-prepare reusable statements
|
|
67
|
+
const insertFunctionStmt = await conn.prepare('CREATE (:Function {id: $id, name: $name, filePath: $filePath, startLine: $startLine, endLine: $endLine, isAsync: $isAsync, isExported: $isExported, className: $className})');
|
|
68
|
+
const insertClassStmt = await conn.prepare('CREATE (:Class {id: $id, name: $name, filePath: $filePath, startLine: $startLine, endLine: $endLine, isExported: $isExported})');
|
|
69
|
+
const insertFileStmt = await conn.prepare('CREATE (:File {path: $path, language: $language})');
|
|
70
|
+
const insertContainsStmt = await conn.prepare('MATCH (f:File), (fn:Function) WHERE f.path = $filePath AND fn.id = $fnId CREATE (f)-[:CONTAINS]->(fn)');
|
|
71
|
+
const insertContainsClassStmt = await conn.prepare('MATCH (f:File), (c:Class) WHERE f.path = $filePath AND c.id = $classId CREATE (f)-[:CONTAINS_CLASS]->(c)');
|
|
72
|
+
const insertHasMethodStmt = await conn.prepare('MATCH (c:Class), (fn:Function) WHERE c.id = $classId AND fn.id = $fnId CREATE (c)-[:HAS_METHOD]->(fn)');
|
|
73
|
+
const insertCallsStmt = await conn.prepare('MATCH (a:Function), (b:Function) WHERE a.id = $callerId AND b.id = $calleeId CREATE (a)-[:CALLS]->(b)');
|
|
74
|
+
// ── buildGraph ──
|
|
75
|
+
async function buildGraph(parsedFiles) {
|
|
76
|
+
// Collect all function IDs for call-site resolution
|
|
77
|
+
const functionIndex = new Map(); // name -> id (first match)
|
|
78
|
+
// For methods with receivers: "receiver.method" -> id
|
|
79
|
+
const methodIndex = new Map();
|
|
80
|
+
// Pass 1: Insert nodes
|
|
81
|
+
for (const file of parsedFiles) {
|
|
82
|
+
// Insert File node
|
|
83
|
+
await conn.execute(insertFileStmt, {
|
|
84
|
+
path: file.filePath,
|
|
85
|
+
language: file.language,
|
|
86
|
+
});
|
|
87
|
+
// Insert Function nodes
|
|
88
|
+
for (const fn of file.functions) {
|
|
89
|
+
const id = functionId(fn);
|
|
90
|
+
await conn.execute(insertFunctionStmt, {
|
|
91
|
+
id,
|
|
92
|
+
name: fn.name,
|
|
93
|
+
filePath: fn.filePath,
|
|
94
|
+
startLine: fn.startLine,
|
|
95
|
+
endLine: fn.endLine,
|
|
96
|
+
isAsync: fn.isAsync,
|
|
97
|
+
isExported: fn.isExported,
|
|
98
|
+
className: fn.className ?? '',
|
|
99
|
+
});
|
|
100
|
+
// Index for call resolution
|
|
101
|
+
if (!functionIndex.has(fn.name)) {
|
|
102
|
+
functionIndex.set(fn.name, id);
|
|
103
|
+
}
|
|
104
|
+
// Index methods by className.methodName
|
|
105
|
+
if (fn.className) {
|
|
106
|
+
const qualifiedName = `${fn.className}.${fn.name}`;
|
|
107
|
+
if (!methodIndex.has(qualifiedName)) {
|
|
108
|
+
methodIndex.set(qualifiedName, id);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// Create CONTAINS edge (File -> Function)
|
|
112
|
+
await conn.execute(insertContainsStmt, {
|
|
113
|
+
filePath: file.filePath,
|
|
114
|
+
fnId: id,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
// Insert Class nodes
|
|
118
|
+
for (const cls of file.classes) {
|
|
119
|
+
const id = classId(cls);
|
|
120
|
+
await conn.execute(insertClassStmt, {
|
|
121
|
+
id,
|
|
122
|
+
name: cls.name,
|
|
123
|
+
filePath: cls.filePath,
|
|
124
|
+
startLine: cls.startLine,
|
|
125
|
+
endLine: cls.endLine,
|
|
126
|
+
isExported: cls.isExported,
|
|
127
|
+
});
|
|
128
|
+
// Create CONTAINS_CLASS edge (File -> Class)
|
|
129
|
+
await conn.execute(insertContainsClassStmt, {
|
|
130
|
+
filePath: file.filePath,
|
|
131
|
+
classId: id,
|
|
132
|
+
});
|
|
133
|
+
// Create HAS_METHOD edges (Class -> Function)
|
|
134
|
+
for (const methodName of cls.methods) {
|
|
135
|
+
const methodFnId = `${cls.filePath}::${cls.name}.${methodName}`;
|
|
136
|
+
await conn.execute(insertHasMethodStmt, {
|
|
137
|
+
classId: id,
|
|
138
|
+
fnId: methodFnId,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// Insert Module nodes and IMPORTS edges
|
|
143
|
+
for (const imp of file.imports) {
|
|
144
|
+
// MERGE Module node (idempotent)
|
|
145
|
+
await conn.query(`MERGE (:Module {name: '${escapeCypher(imp.source)}'})`);
|
|
146
|
+
// Create IMPORTS edge with metadata
|
|
147
|
+
const importStmt = await conn.prepare('MATCH (f:File), (m:Module) WHERE f.path = $filePath AND m.name = $moduleName CREATE (f)-[:IMPORTS {line: $line, specifiers: $specifiers}]->(m)');
|
|
148
|
+
await conn.execute(importStmt, {
|
|
149
|
+
filePath: file.filePath,
|
|
150
|
+
moduleName: imp.source,
|
|
151
|
+
line: imp.line,
|
|
152
|
+
specifiers: imp.specifiers.join(','),
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// Pass 2: Resolve call sites into CALLS edges
|
|
157
|
+
for (const file of parsedFiles) {
|
|
158
|
+
for (const call of file.callSites) {
|
|
159
|
+
// Find the caller function ID
|
|
160
|
+
const callerFn = file.functions.find((fn) => {
|
|
161
|
+
if (fn.className) {
|
|
162
|
+
return fn.name === call.callerName;
|
|
163
|
+
}
|
|
164
|
+
return fn.name === call.callerName;
|
|
165
|
+
});
|
|
166
|
+
if (!callerFn)
|
|
167
|
+
continue;
|
|
168
|
+
const callerId = functionId(callerFn);
|
|
169
|
+
// Find the callee function ID
|
|
170
|
+
let calleeId;
|
|
171
|
+
if (call.receiver) {
|
|
172
|
+
// Method call: try receiver.calleeName
|
|
173
|
+
const qualifiedName = `${call.receiver}.${call.calleeName}`;
|
|
174
|
+
calleeId = methodIndex.get(qualifiedName);
|
|
175
|
+
}
|
|
176
|
+
// Fallback to plain name lookup
|
|
177
|
+
if (!calleeId) {
|
|
178
|
+
calleeId = functionIndex.get(call.calleeName);
|
|
179
|
+
}
|
|
180
|
+
if (!calleeId)
|
|
181
|
+
continue; // External or unresolved call
|
|
182
|
+
await conn.execute(insertCallsStmt, {
|
|
183
|
+
callerId,
|
|
184
|
+
calleeId,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
// ── getFunction ──
|
|
190
|
+
async function getFunction(name) {
|
|
191
|
+
const stmt = await conn.prepare('MATCH (f:Function) WHERE f.name = $name RETURN f.id, f.name, f.filePath, f.startLine, f.endLine, f.isAsync, f.isExported, f.className LIMIT 1');
|
|
192
|
+
const result = unwrapResult(await conn.execute(stmt, { name }));
|
|
193
|
+
const rows = await result.getAll();
|
|
194
|
+
if (rows.length === 0)
|
|
195
|
+
return null;
|
|
196
|
+
const row = rows[0];
|
|
197
|
+
return {
|
|
198
|
+
id: row['f.id'],
|
|
199
|
+
name: row['f.name'],
|
|
200
|
+
filePath: row['f.filePath'],
|
|
201
|
+
startLine: row['f.startLine'],
|
|
202
|
+
endLine: row['f.endLine'],
|
|
203
|
+
isAsync: row['f.isAsync'],
|
|
204
|
+
isExported: row['f.isExported'],
|
|
205
|
+
className: row['f.className'],
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
// ── getCallers ──
|
|
209
|
+
async function getCallers(functionName, depth = 1) {
|
|
210
|
+
const stmt = await conn.prepare(`MATCH (caller:Function)-[:CALLS*1..${depth}]->(target:Function) WHERE target.name = $name RETURN DISTINCT caller.id, caller.name, caller.filePath, caller.startLine, caller.endLine, caller.isAsync, caller.isExported, caller.className`);
|
|
211
|
+
const result = unwrapResult(await conn.execute(stmt, { name: functionName }));
|
|
212
|
+
const rows = await result.getAll();
|
|
213
|
+
return rows.map((row) => ({
|
|
214
|
+
id: row['caller.id'],
|
|
215
|
+
name: row['caller.name'],
|
|
216
|
+
filePath: row['caller.filePath'],
|
|
217
|
+
startLine: row['caller.startLine'],
|
|
218
|
+
endLine: row['caller.endLine'],
|
|
219
|
+
isAsync: row['caller.isAsync'],
|
|
220
|
+
isExported: row['caller.isExported'],
|
|
221
|
+
className: row['caller.className'],
|
|
222
|
+
}));
|
|
223
|
+
}
|
|
224
|
+
// ── getCallees ──
|
|
225
|
+
async function getCallees(functionName, depth = 1) {
|
|
226
|
+
const stmt = await conn.prepare(`MATCH (target:Function)-[:CALLS*1..${depth}]->(callee:Function) WHERE target.name = $name RETURN DISTINCT callee.id, callee.name, callee.filePath, callee.startLine, callee.endLine, callee.isAsync, callee.isExported, callee.className`);
|
|
227
|
+
const result = unwrapResult(await conn.execute(stmt, { name: functionName }));
|
|
228
|
+
const rows = await result.getAll();
|
|
229
|
+
return rows.map((row) => ({
|
|
230
|
+
id: row['callee.id'],
|
|
231
|
+
name: row['callee.name'],
|
|
232
|
+
filePath: row['callee.filePath'],
|
|
233
|
+
startLine: row['callee.startLine'],
|
|
234
|
+
endLine: row['callee.endLine'],
|
|
235
|
+
isAsync: row['callee.isAsync'],
|
|
236
|
+
isExported: row['callee.isExported'],
|
|
237
|
+
className: row['callee.className'],
|
|
238
|
+
}));
|
|
239
|
+
}
|
|
240
|
+
// ── getImportsOf ──
|
|
241
|
+
async function getImportsOf(modulePath) {
|
|
242
|
+
const stmt = await conn.prepare('MATCH (f:File)-[r:IMPORTS]->(m:Module) WHERE m.name = $name RETURN f.path, r.line, r.specifiers, m.name');
|
|
243
|
+
const result = unwrapResult(await conn.execute(stmt, { name: modulePath }));
|
|
244
|
+
const rows = await result.getAll();
|
|
245
|
+
return rows.map((row) => ({
|
|
246
|
+
source: row['m.name'],
|
|
247
|
+
filePath: row['f.path'],
|
|
248
|
+
line: row['r.line'],
|
|
249
|
+
specifiers: row['r.specifiers'].split(',').filter(Boolean),
|
|
250
|
+
}));
|
|
251
|
+
}
|
|
252
|
+
// ── query (raw Cypher) ──
|
|
253
|
+
async function rawQuery(cypher, params) {
|
|
254
|
+
let result;
|
|
255
|
+
if (params && Object.keys(params).length > 0) {
|
|
256
|
+
const stmt = await conn.prepare(cypher);
|
|
257
|
+
result = await conn.execute(stmt, params);
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
result = await conn.query(cypher);
|
|
261
|
+
}
|
|
262
|
+
const qr = unwrapResult(result);
|
|
263
|
+
return await qr.getAll();
|
|
264
|
+
}
|
|
265
|
+
// ── close ──
|
|
266
|
+
async function close() {
|
|
267
|
+
await conn.close();
|
|
268
|
+
await db.close();
|
|
269
|
+
}
|
|
270
|
+
return {
|
|
271
|
+
buildGraph,
|
|
272
|
+
getFunction,
|
|
273
|
+
getCallers,
|
|
274
|
+
getCallees,
|
|
275
|
+
getImportsOf,
|
|
276
|
+
query: rawQuery,
|
|
277
|
+
close,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
// ── Internal helpers ──
|
|
281
|
+
function escapeCypher(value) {
|
|
282
|
+
return value.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
|
|
283
|
+
}
|
|
284
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../../../src/engines/graph/store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gitleaks.d.ts","sourceRoot":"","sources":["../../../../src/engines/pattern/gitleaks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAyD9C,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,OAAO,CAAC,CAO/D;AAED,wBAAsB,WAAW,CAC/B,UAAU,EAAE,MAAM,EAClB,YAAY,CAAC,EAAE,MAAM,EAAE,GACtB,OAAO,CAAC,OAAO,EAAE,CAAC,CAmCpB"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
function execFilePromise(cmd, args) {
|
|
3
|
+
return new Promise((resolve, reject) => {
|
|
4
|
+
execFile(cmd, args, (error, stdout, stderr) => {
|
|
5
|
+
if (error) {
|
|
6
|
+
// Attach stdout/stderr to the error so callers can still read output
|
|
7
|
+
const enrichedError = error;
|
|
8
|
+
enrichedError.stdout = typeof stdout === 'string' ? stdout : '';
|
|
9
|
+
enrichedError.stderr = typeof stderr === 'string' ? stderr : '';
|
|
10
|
+
reject(enrichedError);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
resolve({
|
|
14
|
+
stdout: typeof stdout === 'string' ? stdout : '',
|
|
15
|
+
stderr: typeof stderr === 'string' ? stderr : '',
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
function parseGitleaksOutput(jsonString) {
|
|
21
|
+
const results = JSON.parse(jsonString);
|
|
22
|
+
return results.map((result) => ({
|
|
23
|
+
id: `gitleaks_${result.RuleID}_${result.StartLine}`,
|
|
24
|
+
engine: 'pattern',
|
|
25
|
+
severity: 'critical',
|
|
26
|
+
type: 'hardcoded_secret',
|
|
27
|
+
file: result.File,
|
|
28
|
+
line: result.StartLine,
|
|
29
|
+
description: result.Description,
|
|
30
|
+
fix_suggestion: 'Move this secret to a .env file or environment variable',
|
|
31
|
+
auto_fixable: true,
|
|
32
|
+
}));
|
|
33
|
+
}
|
|
34
|
+
export async function checkGitleaksInstalled() {
|
|
35
|
+
try {
|
|
36
|
+
await execFilePromise('which', ['gitleaks']);
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export async function runGitleaks(targetPath, _stagedFiles) {
|
|
44
|
+
const installed = await checkGitleaksInstalled();
|
|
45
|
+
if (!installed) {
|
|
46
|
+
console.warn('ShipSafe: gitleaks is not installed, skipping secret scan');
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
const args = [
|
|
51
|
+
'detect',
|
|
52
|
+
'--source',
|
|
53
|
+
targetPath,
|
|
54
|
+
'--report-format',
|
|
55
|
+
'json',
|
|
56
|
+
'--report-path',
|
|
57
|
+
'/dev/stdout',
|
|
58
|
+
'--no-git',
|
|
59
|
+
];
|
|
60
|
+
const { stdout } = await execFilePromise('gitleaks', args);
|
|
61
|
+
return parseGitleaksOutput(stdout);
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
// gitleaks exits non-zero when it finds secrets — still try to parse output
|
|
65
|
+
const execError = error;
|
|
66
|
+
if (execError.stdout) {
|
|
67
|
+
try {
|
|
68
|
+
return parseGitleaksOutput(execError.stdout);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// JSON parse failed on the output — fall through to warn
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
console.warn('ShipSafe: gitleaks scan failed', execError.message);
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=gitleaks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gitleaks.js","sourceRoot":"","sources":["../../../../src/engines/pattern/gitleaks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Finding, ScanResult, ScanScope, SecurityScore, ScannerAvailability } from '../../types.js';
|
|
2
|
+
export interface PatternEngineOptions {
|
|
3
|
+
targetPath: string;
|
|
4
|
+
scope: ScanScope;
|
|
5
|
+
stagedFiles?: string[];
|
|
6
|
+
}
|
|
7
|
+
export declare function computeScore(findings: Finding[]): SecurityScore;
|
|
8
|
+
export declare function getAvailableScanners(): Promise<ScannerAvailability>;
|
|
9
|
+
export declare function getStagedFiles(projectDir: string): Promise<string[]>;
|
|
10
|
+
export declare function runPatternEngine(options: PatternEngineOptions): Promise<ScanResult>;
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|