@grafema/cli 0.2.4-beta → 0.2.6-beta
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 +85 -0
- package/dist/cli.js +7 -2
- package/dist/cli.js.map +1 -0
- package/dist/commands/analyze.d.ts +3 -1
- package/dist/commands/analyze.d.ts.map +1 -1
- package/dist/commands/analyze.js +8 -266
- package/dist/commands/analyze.js.map +1 -0
- package/dist/commands/analyzeAction.d.ts +28 -0
- package/dist/commands/analyzeAction.d.ts.map +1 -0
- package/dist/commands/analyzeAction.js +243 -0
- package/dist/commands/analyzeAction.js.map +1 -0
- package/dist/commands/check.d.ts +2 -6
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +34 -48
- package/dist/commands/check.js.map +1 -0
- package/dist/commands/context.d.ts +16 -0
- package/dist/commands/context.d.ts.map +1 -0
- package/dist/commands/context.js +238 -0
- package/dist/commands/context.js.map +1 -0
- package/dist/commands/coverage.js +1 -0
- package/dist/commands/coverage.js.map +1 -0
- package/dist/commands/doctor/checks.d.ts.map +1 -1
- package/dist/commands/doctor/checks.js +10 -6
- package/dist/commands/doctor/checks.js.map +1 -0
- package/dist/commands/doctor/output.js +1 -0
- package/dist/commands/doctor/output.js.map +1 -0
- package/dist/commands/doctor/types.js +1 -0
- package/dist/commands/doctor/types.js.map +1 -0
- package/dist/commands/doctor.js +1 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/explain.d.ts.map +1 -1
- package/dist/commands/explain.js +5 -3
- package/dist/commands/explain.js.map +1 -0
- package/dist/commands/explore.d.ts.map +1 -1
- package/dist/commands/explore.js +9 -4
- package/dist/commands/explore.js.map +1 -0
- package/dist/commands/file.d.ts +15 -0
- package/dist/commands/file.d.ts.map +1 -0
- package/dist/commands/file.js +144 -0
- package/dist/commands/file.js.map +1 -0
- package/dist/commands/get.d.ts.map +1 -1
- package/dist/commands/get.js +7 -0
- package/dist/commands/get.js.map +1 -0
- package/dist/commands/impact.d.ts.map +1 -1
- package/dist/commands/impact.js +3 -3
- package/dist/commands/impact.js.map +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +20 -2
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/ls.d.ts.map +1 -1
- package/dist/commands/ls.js +10 -2
- package/dist/commands/ls.js.map +1 -0
- package/dist/commands/overview.d.ts.map +1 -1
- package/dist/commands/overview.js +1 -0
- package/dist/commands/overview.js.map +1 -0
- package/dist/commands/query.d.ts +8 -0
- package/dist/commands/query.d.ts.map +1 -1
- package/dist/commands/query.js +217 -43
- package/dist/commands/query.js.map +1 -0
- package/dist/commands/schema.d.ts.map +1 -1
- package/dist/commands/schema.js +4 -2
- package/dist/commands/schema.js.map +1 -0
- package/dist/commands/server.d.ts +2 -1
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +76 -14
- package/dist/commands/server.js.map +1 -0
- package/dist/commands/setup-skill.d.ts +17 -0
- package/dist/commands/setup-skill.d.ts.map +1 -0
- package/dist/commands/setup-skill.js +131 -0
- package/dist/commands/setup-skill.js.map +1 -0
- package/dist/commands/stats.js +1 -0
- package/dist/commands/stats.js.map +1 -0
- package/dist/commands/trace.d.ts.map +1 -1
- package/dist/commands/trace.js +21 -10
- package/dist/commands/trace.js.map +1 -0
- package/dist/commands/types.js +1 -0
- package/dist/commands/types.js.map +1 -0
- package/dist/plugins/builtinPlugins.d.ts +10 -0
- package/dist/plugins/builtinPlugins.d.ts.map +1 -0
- package/dist/plugins/builtinPlugins.js +68 -0
- package/dist/plugins/builtinPlugins.js.map +1 -0
- package/dist/plugins/pluginLoader.d.ts +16 -0
- package/dist/plugins/pluginLoader.d.ts.map +1 -0
- package/dist/plugins/pluginLoader.js +101 -0
- package/dist/plugins/pluginLoader.js.map +1 -0
- package/dist/plugins/pluginResolver.js +38 -0
- package/dist/utils/codePreview.d.ts +1 -0
- package/dist/utils/codePreview.d.ts.map +1 -1
- package/dist/utils/codePreview.js +6 -3
- package/dist/utils/codePreview.js.map +1 -0
- package/dist/utils/errorFormatter.js +1 -0
- package/dist/utils/errorFormatter.js.map +1 -0
- package/dist/utils/formatNode.d.ts +1 -1
- package/dist/utils/formatNode.d.ts.map +1 -1
- package/dist/utils/formatNode.js +3 -2
- package/dist/utils/formatNode.js.map +1 -0
- package/dist/utils/pathUtils.d.ts +2 -0
- package/dist/utils/pathUtils.d.ts.map +1 -0
- package/dist/utils/pathUtils.js +9 -0
- package/dist/utils/pathUtils.js.map +1 -0
- package/dist/utils/progressRenderer.d.ts +119 -0
- package/dist/utils/progressRenderer.d.ts.map +1 -0
- package/dist/utils/progressRenderer.js +245 -0
- package/dist/utils/progressRenderer.js.map +1 -0
- package/dist/utils/spinner.d.ts +39 -0
- package/dist/utils/spinner.d.ts.map +1 -0
- package/dist/utils/spinner.js +84 -0
- package/dist/utils/spinner.js.map +1 -0
- package/package.json +8 -9
- package/skills/grafema-codebase-analysis/SKILL.md +295 -0
- package/skills/grafema-codebase-analysis/references/node-edge-types.md +123 -0
- package/skills/grafema-codebase-analysis/references/query-patterns.md +205 -0
- package/src/cli.ts +8 -2
- package/src/commands/analyze.ts +7 -342
- package/src/commands/analyzeAction.ts +284 -0
- package/src/commands/check.ts +38 -70
- package/src/commands/context.ts +309 -0
- package/src/commands/doctor/checks.ts +9 -6
- package/src/commands/explain.ts +4 -3
- package/src/commands/explore.tsx +15 -9
- package/src/commands/file.ts +179 -0
- package/src/commands/get.ts +8 -0
- package/src/commands/impact.ts +3 -4
- package/src/commands/init.ts +19 -3
- package/src/commands/ls.ts +11 -2
- package/src/commands/overview.ts +0 -4
- package/src/commands/query.ts +235 -44
- package/src/commands/schema.ts +3 -2
- package/src/commands/server.ts +85 -15
- package/src/commands/setup-skill.ts +162 -0
- package/src/commands/trace.ts +18 -9
- package/src/plugins/builtinPlugins.ts +108 -0
- package/src/plugins/pluginLoader.ts +123 -0
- package/src/plugins/pluginResolver.js +38 -0
- package/src/utils/codePreview.ts +7 -3
- package/src/utils/formatNode.ts +3 -3
- package/src/utils/pathUtils.ts +9 -0
- package/src/utils/progressRenderer.ts +288 -0
- package/src/utils/spinner.ts +94 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# Grafema Datalog Query Patterns
|
|
2
|
+
|
|
3
|
+
All queries use `query_graph` tool. Every query must define a `violation/1` predicate —
|
|
4
|
+
matching nodes are returned as results.
|
|
5
|
+
|
|
6
|
+
## Syntax Quick Reference
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
violation(X) :- <body>. # Rule: X is a result if body is true
|
|
10
|
+
node(X, "TYPE") # Match node by type
|
|
11
|
+
edge(X, Y, "TYPE") # Match edge: X -> Y of given type
|
|
12
|
+
attr(X, "name", "value") # Match node attribute
|
|
13
|
+
\+ <condition> # Negation: condition is NOT true
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Basic Patterns
|
|
17
|
+
|
|
18
|
+
### Find nodes by type
|
|
19
|
+
|
|
20
|
+
```datalog
|
|
21
|
+
violation(X) :- node(X, "FUNCTION").
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Find nodes by type and name
|
|
25
|
+
|
|
26
|
+
```datalog
|
|
27
|
+
violation(X) :- node(X, "FUNCTION"), attr(X, "name", "processPayment").
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Find nodes by type and file
|
|
31
|
+
|
|
32
|
+
```datalog
|
|
33
|
+
violation(X) :- node(X, "FUNCTION"), attr(X, "file", "src/api.ts").
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Find nodes matching multiple criteria
|
|
37
|
+
|
|
38
|
+
```datalog
|
|
39
|
+
violation(X) :- node(X, "CALL"), attr(X, "name", "eval"), attr(X, "file", "src/handler.ts").
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Edge Traversal
|
|
43
|
+
|
|
44
|
+
### One-hop: Find all calls from a function
|
|
45
|
+
|
|
46
|
+
```datalog
|
|
47
|
+
violation(Call) :-
|
|
48
|
+
node(F, "FUNCTION"), attr(F, "name", "main"),
|
|
49
|
+
edge(F, S, "HAS_SCOPE"), edge(S, Call, "CONTAINS"),
|
|
50
|
+
node(Call, "CALL").
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### One-hop: Find all callers of a function
|
|
54
|
+
|
|
55
|
+
```datalog
|
|
56
|
+
violation(Caller) :-
|
|
57
|
+
node(Target, "FUNCTION"), attr(Target, "name", "validate"),
|
|
58
|
+
edge(Call, Target, "CALLS"),
|
|
59
|
+
edge(Scope, Call, "CONTAINS"),
|
|
60
|
+
edge(Caller, Scope, "HAS_SCOPE"),
|
|
61
|
+
node(Caller, "FUNCTION").
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Find module dependencies
|
|
65
|
+
|
|
66
|
+
```datalog
|
|
67
|
+
violation(Dep) :-
|
|
68
|
+
node(M, "MODULE"), attr(M, "name", "api"),
|
|
69
|
+
edge(M, Dep, "DEPENDS_ON"), node(Dep, "MODULE").
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Find what a variable is assigned from
|
|
73
|
+
|
|
74
|
+
```datalog
|
|
75
|
+
violation(Source) :-
|
|
76
|
+
node(V, "VARIABLE"), attr(V, "name", "config"),
|
|
77
|
+
edge(V, Source, "ASSIGNED_FROM").
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Negation Patterns
|
|
81
|
+
|
|
82
|
+
### Functions with no callers (potential dead code)
|
|
83
|
+
|
|
84
|
+
```datalog
|
|
85
|
+
violation(X) :-
|
|
86
|
+
node(X, "FUNCTION"),
|
|
87
|
+
\+ edge(_, X, "CALLS").
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Modules with no dependents (unused modules)
|
|
91
|
+
|
|
92
|
+
```datalog
|
|
93
|
+
violation(X) :-
|
|
94
|
+
node(X, "MODULE"),
|
|
95
|
+
\+ edge(_, X, "DEPENDS_ON").
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Unresolved calls (external/dynamic targets)
|
|
99
|
+
|
|
100
|
+
```datalog
|
|
101
|
+
violation(X) :-
|
|
102
|
+
node(X, "CALL"),
|
|
103
|
+
attr(X, "resolved", "false").
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Invariant Patterns
|
|
107
|
+
|
|
108
|
+
### No eval() usage
|
|
109
|
+
|
|
110
|
+
```datalog
|
|
111
|
+
violation(X) :- node(X, "CALL"), attr(X, "name", "eval").
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### No direct database queries outside service layer
|
|
115
|
+
|
|
116
|
+
```datalog
|
|
117
|
+
violation(X) :-
|
|
118
|
+
node(X, "db:query"),
|
|
119
|
+
attr(X, "file", File),
|
|
120
|
+
\+ attr(X, "file", "src/services/").
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Note: File matching is exact. For pattern matching, use the `find_nodes` tool instead.
|
|
124
|
+
|
|
125
|
+
### All HTTP endpoints must have handlers
|
|
126
|
+
|
|
127
|
+
```datalog
|
|
128
|
+
violation(X) :-
|
|
129
|
+
node(X, "http:request"),
|
|
130
|
+
\+ edge(_, X, "CALLS").
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Join Patterns
|
|
134
|
+
|
|
135
|
+
### Find functions that call both X and Y
|
|
136
|
+
|
|
137
|
+
```datalog
|
|
138
|
+
violation(F) :-
|
|
139
|
+
node(F, "FUNCTION"),
|
|
140
|
+
edge(F, S, "HAS_SCOPE"),
|
|
141
|
+
edge(S, C1, "CONTAINS"), node(C1, "CALL"), attr(C1, "name", "readFile"),
|
|
142
|
+
edge(S, C2, "CONTAINS"), node(C2, "CALL"), attr(C2, "name", "writeFile").
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Find classes that extend a specific base class
|
|
146
|
+
|
|
147
|
+
```datalog
|
|
148
|
+
violation(Child) :-
|
|
149
|
+
node(Base, "CLASS"), attr(Base, "name", "BaseService"),
|
|
150
|
+
edge(Child, Base, "EXTENDS"),
|
|
151
|
+
node(Child, "CLASS").
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Performance Tips
|
|
155
|
+
|
|
156
|
+
1. **Put most selective filters first.** `attr(X, "name", "specific")` before `node(X, "FUNCTION")`.
|
|
157
|
+
|
|
158
|
+
2. **Avoid unconstrained joins.** Every variable should be bounded by at least one specific condition.
|
|
159
|
+
|
|
160
|
+
3. **Use high-level tools when possible.** `find_calls` is faster than writing a Datalog query for the same pattern — it uses optimized indexes.
|
|
161
|
+
|
|
162
|
+
4. **Use `explain: true` to debug.** If a query returns nothing, add `explain: true` to see step-by-step execution.
|
|
163
|
+
|
|
164
|
+
5. **Use `limit` and `offset` for large result sets.** Default limit applies, but you can paginate through results.
|
|
165
|
+
|
|
166
|
+
## Common Mistakes
|
|
167
|
+
|
|
168
|
+
### Wrong: Unbound variable
|
|
169
|
+
|
|
170
|
+
```datalog
|
|
171
|
+
# BAD: Y is never constrained
|
|
172
|
+
violation(X) :- node(X, "FUNCTION"), edge(X, Y, "CALLS").
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
```datalog
|
|
176
|
+
# GOOD: Constrain Y
|
|
177
|
+
violation(X) :- node(X, "FUNCTION"), edge(X, Y, "CALLS"), node(Y, "FUNCTION").
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Wrong: Using wrong edge direction
|
|
181
|
+
|
|
182
|
+
```datalog
|
|
183
|
+
# BAD: CALLS edge goes Caller -> Callee, not reverse
|
|
184
|
+
violation(X) :- node(X, "FUNCTION"), edge(X, Target, "CALLS").
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
The CALLS edge typically goes from CALL/CALL_SITE node to target FUNCTION,
|
|
188
|
+
not directly from FUNCTION to FUNCTION. Check [node-edge-types.md](node-edge-types.md)
|
|
189
|
+
for correct edge directions.
|
|
190
|
+
|
|
191
|
+
### Wrong: Missing scope traversal
|
|
192
|
+
|
|
193
|
+
Functions don't directly CONTAIN calls — they have scopes that contain calls:
|
|
194
|
+
|
|
195
|
+
```datalog
|
|
196
|
+
# BAD: No direct CONTAINS edge from FUNCTION to CALL
|
|
197
|
+
violation(C) :- node(F, "FUNCTION"), edge(F, C, "CONTAINS"), node(C, "CALL").
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
```datalog
|
|
201
|
+
# GOOD: Go through HAS_SCOPE
|
|
202
|
+
violation(C) :-
|
|
203
|
+
node(F, "FUNCTION"), edge(F, S, "HAS_SCOPE"),
|
|
204
|
+
edge(S, C, "CONTAINS"), node(C, "CALL").
|
|
205
|
+
```
|
package/src/cli.ts
CHANGED
|
@@ -16,7 +16,8 @@ import { lsCommand } from './commands/ls.js';
|
|
|
16
16
|
import { getCommand } from './commands/get.js';
|
|
17
17
|
import { traceCommand } from './commands/trace.js';
|
|
18
18
|
import { impactCommand } from './commands/impact.js';
|
|
19
|
-
import {
|
|
19
|
+
import { contextCommand } from './commands/context.js';
|
|
20
|
+
|
|
20
21
|
import { statsCommand } from './commands/stats.js';
|
|
21
22
|
import { checkCommand } from './commands/check.js';
|
|
22
23
|
import { serverCommand } from './commands/server.js';
|
|
@@ -24,6 +25,8 @@ import { coverageCommand } from './commands/coverage.js';
|
|
|
24
25
|
import { doctorCommand } from './commands/doctor.js';
|
|
25
26
|
import { schemaCommand } from './commands/schema.js';
|
|
26
27
|
import { explainCommand } from './commands/explain.js';
|
|
28
|
+
import { fileCommand } from './commands/file.js';
|
|
29
|
+
import { setupSkillCommand } from './commands/setup-skill.js';
|
|
27
30
|
|
|
28
31
|
// Read version from package.json
|
|
29
32
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -41,12 +44,13 @@ program.addCommand(initCommand);
|
|
|
41
44
|
program.addCommand(analyzeCommand);
|
|
42
45
|
program.addCommand(overviewCommand);
|
|
43
46
|
program.addCommand(queryCommand);
|
|
47
|
+
program.addCommand(contextCommand);
|
|
44
48
|
program.addCommand(typesCommand);
|
|
45
49
|
program.addCommand(lsCommand);
|
|
46
50
|
program.addCommand(getCommand);
|
|
47
51
|
program.addCommand(traceCommand);
|
|
48
52
|
program.addCommand(impactCommand);
|
|
49
|
-
|
|
53
|
+
|
|
50
54
|
program.addCommand(statsCommand); // Keep for backwards compat
|
|
51
55
|
program.addCommand(coverageCommand);
|
|
52
56
|
program.addCommand(checkCommand);
|
|
@@ -54,5 +58,7 @@ program.addCommand(serverCommand);
|
|
|
54
58
|
program.addCommand(doctorCommand);
|
|
55
59
|
program.addCommand(schemaCommand);
|
|
56
60
|
program.addCommand(explainCommand);
|
|
61
|
+
program.addCommand(fileCommand);
|
|
62
|
+
program.addCommand(setupSkillCommand);
|
|
57
63
|
|
|
58
64
|
program.parse();
|
package/src/commands/analyze.ts
CHANGED
|
@@ -1,182 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Analyze command
|
|
2
|
+
* Analyze command — Run project analysis via Orchestrator.
|
|
3
|
+
*
|
|
4
|
+
* Command definition only. Execution logic is in analyzeAction.ts.
|
|
3
5
|
*/
|
|
4
6
|
|
|
5
7
|
import { Command } from 'commander';
|
|
6
|
-
import {
|
|
7
|
-
import { existsSync, mkdirSync, readdirSync } from 'fs';
|
|
8
|
-
import { pathToFileURL } from 'url';
|
|
9
|
-
import {
|
|
10
|
-
Orchestrator,
|
|
11
|
-
RFDBServerBackend,
|
|
12
|
-
Plugin,
|
|
13
|
-
DiagnosticReporter,
|
|
14
|
-
DiagnosticWriter,
|
|
15
|
-
createLogger,
|
|
16
|
-
loadConfig,
|
|
17
|
-
type GrafemaConfig,
|
|
18
|
-
// Discovery
|
|
19
|
-
SimpleProjectDiscovery,
|
|
20
|
-
MonorepoServiceDiscovery,
|
|
21
|
-
WorkspaceDiscovery,
|
|
22
|
-
// Indexing
|
|
23
|
-
JSModuleIndexer,
|
|
24
|
-
RustModuleIndexer,
|
|
25
|
-
// Analysis
|
|
26
|
-
JSASTAnalyzer,
|
|
27
|
-
ExpressRouteAnalyzer,
|
|
28
|
-
ExpressResponseAnalyzer,
|
|
29
|
-
SocketIOAnalyzer,
|
|
30
|
-
DatabaseAnalyzer,
|
|
31
|
-
FetchAnalyzer,
|
|
32
|
-
ServiceLayerAnalyzer,
|
|
33
|
-
ReactAnalyzer,
|
|
34
|
-
RustAnalyzer,
|
|
35
|
-
// Enrichment
|
|
36
|
-
MethodCallResolver,
|
|
37
|
-
ArgumentParameterLinker,
|
|
38
|
-
AliasTracker,
|
|
39
|
-
ValueDomainAnalyzer,
|
|
40
|
-
MountPointResolver,
|
|
41
|
-
PrefixEvaluator,
|
|
42
|
-
InstanceOfResolver,
|
|
43
|
-
ImportExportLinker,
|
|
44
|
-
FunctionCallResolver,
|
|
45
|
-
HTTPConnectionEnricher,
|
|
46
|
-
RustFFIEnricher,
|
|
47
|
-
// Validation
|
|
48
|
-
CallResolverValidator,
|
|
49
|
-
EvalBanValidator,
|
|
50
|
-
SQLInjectionValidator,
|
|
51
|
-
ShadowingDetector,
|
|
52
|
-
GraphConnectivityValidator,
|
|
53
|
-
DataFlowValidator,
|
|
54
|
-
TypeScriptDeadCodeValidator,
|
|
55
|
-
BrokenImportValidator,
|
|
56
|
-
} from '@grafema/core';
|
|
57
|
-
import type { LogLevel } from '@grafema/types';
|
|
8
|
+
import { analyzeAction } from './analyzeAction.js';
|
|
58
9
|
|
|
59
|
-
const BUILTIN_PLUGINS: Record<string, () => Plugin> = {
|
|
60
|
-
// Discovery
|
|
61
|
-
SimpleProjectDiscovery: () => new SimpleProjectDiscovery() as Plugin,
|
|
62
|
-
MonorepoServiceDiscovery: () => new MonorepoServiceDiscovery() as Plugin,
|
|
63
|
-
WorkspaceDiscovery: () => new WorkspaceDiscovery() as Plugin,
|
|
64
|
-
// Indexing
|
|
65
|
-
JSModuleIndexer: () => new JSModuleIndexer() as Plugin,
|
|
66
|
-
RustModuleIndexer: () => new RustModuleIndexer() as Plugin,
|
|
67
|
-
// Analysis
|
|
68
|
-
JSASTAnalyzer: () => new JSASTAnalyzer() as Plugin,
|
|
69
|
-
ExpressRouteAnalyzer: () => new ExpressRouteAnalyzer() as Plugin,
|
|
70
|
-
ExpressResponseAnalyzer: () => new ExpressResponseAnalyzer() as Plugin,
|
|
71
|
-
SocketIOAnalyzer: () => new SocketIOAnalyzer() as Plugin,
|
|
72
|
-
DatabaseAnalyzer: () => new DatabaseAnalyzer() as Plugin,
|
|
73
|
-
FetchAnalyzer: () => new FetchAnalyzer() as Plugin,
|
|
74
|
-
ServiceLayerAnalyzer: () => new ServiceLayerAnalyzer() as Plugin,
|
|
75
|
-
ReactAnalyzer: () => new ReactAnalyzer() as Plugin,
|
|
76
|
-
RustAnalyzer: () => new RustAnalyzer() as Plugin,
|
|
77
|
-
// Enrichment
|
|
78
|
-
MethodCallResolver: () => new MethodCallResolver() as Plugin,
|
|
79
|
-
ArgumentParameterLinker: () => new ArgumentParameterLinker() as Plugin,
|
|
80
|
-
AliasTracker: () => new AliasTracker() as Plugin,
|
|
81
|
-
ValueDomainAnalyzer: () => new ValueDomainAnalyzer() as Plugin,
|
|
82
|
-
MountPointResolver: () => new MountPointResolver() as Plugin,
|
|
83
|
-
PrefixEvaluator: () => new PrefixEvaluator() as Plugin,
|
|
84
|
-
InstanceOfResolver: () => new InstanceOfResolver() as Plugin,
|
|
85
|
-
ImportExportLinker: () => new ImportExportLinker() as Plugin,
|
|
86
|
-
FunctionCallResolver: () => new FunctionCallResolver() as Plugin,
|
|
87
|
-
HTTPConnectionEnricher: () => new HTTPConnectionEnricher() as Plugin,
|
|
88
|
-
RustFFIEnricher: () => new RustFFIEnricher() as Plugin,
|
|
89
|
-
// Validation
|
|
90
|
-
CallResolverValidator: () => new CallResolverValidator() as Plugin,
|
|
91
|
-
EvalBanValidator: () => new EvalBanValidator() as Plugin,
|
|
92
|
-
SQLInjectionValidator: () => new SQLInjectionValidator() as Plugin,
|
|
93
|
-
ShadowingDetector: () => new ShadowingDetector() as Plugin,
|
|
94
|
-
GraphConnectivityValidator: () => new GraphConnectivityValidator() as Plugin,
|
|
95
|
-
DataFlowValidator: () => new DataFlowValidator() as Plugin,
|
|
96
|
-
TypeScriptDeadCodeValidator: () => new TypeScriptDeadCodeValidator() as Plugin,
|
|
97
|
-
BrokenImportValidator: () => new BrokenImportValidator() as Plugin,
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Load custom plugins from .grafema/plugins/ directory
|
|
102
|
-
*/
|
|
103
|
-
async function loadCustomPlugins(
|
|
104
|
-
projectPath: string,
|
|
105
|
-
log: (msg: string) => void
|
|
106
|
-
): Promise<Record<string, () => Plugin>> {
|
|
107
|
-
const pluginsDir = join(projectPath, '.grafema', 'plugins');
|
|
108
|
-
if (!existsSync(pluginsDir)) {
|
|
109
|
-
return {};
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const customPlugins: Record<string, () => Plugin> = {};
|
|
113
|
-
|
|
114
|
-
try {
|
|
115
|
-
const files = readdirSync(pluginsDir).filter(
|
|
116
|
-
(f) => f.endsWith('.js') || f.endsWith('.mjs')
|
|
117
|
-
);
|
|
118
|
-
|
|
119
|
-
for (const file of files) {
|
|
120
|
-
try {
|
|
121
|
-
const pluginPath = join(pluginsDir, file);
|
|
122
|
-
const pluginUrl = pathToFileURL(pluginPath).href;
|
|
123
|
-
const module = await import(pluginUrl);
|
|
124
|
-
|
|
125
|
-
const PluginClass = module.default || module[file.replace(/\.(m?js)$/, '')];
|
|
126
|
-
if (PluginClass && typeof PluginClass === 'function') {
|
|
127
|
-
const pluginName = PluginClass.name || file.replace(/\.(m?js)$/, '');
|
|
128
|
-
customPlugins[pluginName] = () => new PluginClass() as Plugin;
|
|
129
|
-
log(`Loaded custom plugin: ${pluginName}`);
|
|
130
|
-
}
|
|
131
|
-
} catch (err) {
|
|
132
|
-
console.warn(`Failed to load plugin ${file}: ${(err as Error).message}`);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
} catch (err) {
|
|
136
|
-
console.warn(`Error loading custom plugins: ${(err as Error).message}`);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
return customPlugins;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function createPlugins(
|
|
143
|
-
config: GrafemaConfig['plugins'],
|
|
144
|
-
customPlugins: Record<string, () => Plugin> = {}
|
|
145
|
-
): Plugin[] {
|
|
146
|
-
const plugins: Plugin[] = [];
|
|
147
|
-
const phases: (keyof GrafemaConfig['plugins'])[] = ['discovery', 'indexing', 'analysis', 'enrichment', 'validation'];
|
|
148
|
-
|
|
149
|
-
for (const phase of phases) {
|
|
150
|
-
const names = config[phase] || [];
|
|
151
|
-
for (const name of names) {
|
|
152
|
-
// Check built-in first, then custom
|
|
153
|
-
const factory = BUILTIN_PLUGINS[name] || customPlugins[name];
|
|
154
|
-
if (factory) {
|
|
155
|
-
plugins.push(factory());
|
|
156
|
-
} else {
|
|
157
|
-
console.warn(`Unknown plugin: ${name}`);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
return plugins;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Determine log level from CLI options.
|
|
167
|
-
* Priority: --log-level > --quiet > --verbose > default ('info')
|
|
168
|
-
*/
|
|
169
|
-
function getLogLevel(options: { quiet?: boolean; verbose?: boolean; logLevel?: string }): LogLevel {
|
|
170
|
-
if (options.logLevel) {
|
|
171
|
-
const validLevels: LogLevel[] = ['silent', 'errors', 'warnings', 'info', 'debug'];
|
|
172
|
-
if (validLevels.includes(options.logLevel as LogLevel)) {
|
|
173
|
-
return options.logLevel as LogLevel;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
if (options.quiet) return 'silent';
|
|
177
|
-
if (options.verbose) return 'debug';
|
|
178
|
-
return 'info';
|
|
179
|
-
}
|
|
180
10
|
|
|
181
11
|
export const analyzeCommand = new Command('analyze')
|
|
182
12
|
.description('Run project analysis')
|
|
@@ -188,6 +18,7 @@ export const analyzeCommand = new Command('analyze')
|
|
|
188
18
|
.option('-v, --verbose', 'Show verbose logging')
|
|
189
19
|
.option('--debug', 'Enable debug mode (writes diagnostics.log)')
|
|
190
20
|
.option('--log-level <level>', 'Set log level (silent, errors, warnings, info, debug)')
|
|
21
|
+
.option('--log-file <path>', 'Write all log output to a file')
|
|
191
22
|
.option('--strict', 'Enable strict mode (fail on unresolved references)')
|
|
192
23
|
.option('--auto-start', 'Auto-start RFDB server if not running')
|
|
193
24
|
.addHelpText('after', `
|
|
@@ -198,176 +29,10 @@ Examples:
|
|
|
198
29
|
grafema analyze -s api Analyze only "api" service (monorepo)
|
|
199
30
|
grafema analyze -v Verbose output with progress details
|
|
200
31
|
grafema analyze --debug Write diagnostics.log for debugging
|
|
32
|
+
grafema analyze --log-file out.log Write all logs to a file
|
|
201
33
|
grafema analyze --strict Fail on unresolved references (debugging)
|
|
202
34
|
grafema analyze --auto-start Auto-start server (useful for CI)
|
|
203
35
|
|
|
204
36
|
Note: Start the server first with: grafema server start
|
|
205
37
|
`)
|
|
206
|
-
.action(
|
|
207
|
-
const projectPath = resolve(path);
|
|
208
|
-
const grafemaDir = join(projectPath, '.grafema');
|
|
209
|
-
const dbPath = join(grafemaDir, 'graph.rfdb');
|
|
210
|
-
|
|
211
|
-
if (!existsSync(grafemaDir)) {
|
|
212
|
-
mkdirSync(grafemaDir, { recursive: true });
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const log = options.quiet ? () => {} : console.log;
|
|
216
|
-
|
|
217
|
-
// Create logger based on CLI flags
|
|
218
|
-
const logLevel = getLogLevel(options);
|
|
219
|
-
const logger = createLogger(logLevel);
|
|
220
|
-
|
|
221
|
-
log(`Analyzing project: ${projectPath}`);
|
|
222
|
-
|
|
223
|
-
// Connect to RFDB server
|
|
224
|
-
// Default: require explicit `grafema server start`
|
|
225
|
-
// Use --auto-start for CI or backwards compatibility
|
|
226
|
-
const backend = new RFDBServerBackend({
|
|
227
|
-
dbPath,
|
|
228
|
-
autoStart: options.autoStart ?? false
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
try {
|
|
232
|
-
await backend.connect();
|
|
233
|
-
} catch (err) {
|
|
234
|
-
if (!options.autoStart && err instanceof Error && err.message.includes('not running')) {
|
|
235
|
-
console.error('');
|
|
236
|
-
console.error('RFDB server is not running.');
|
|
237
|
-
console.error('');
|
|
238
|
-
console.error('Start the server first:');
|
|
239
|
-
console.error(' grafema server start');
|
|
240
|
-
console.error('');
|
|
241
|
-
console.error('Or use --auto-start flag:');
|
|
242
|
-
console.error(' grafema analyze --auto-start');
|
|
243
|
-
console.error('');
|
|
244
|
-
process.exit(1);
|
|
245
|
-
}
|
|
246
|
-
throw err;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
if (options.clear) {
|
|
250
|
-
log('Clearing existing database...');
|
|
251
|
-
await backend.clear();
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
const config = loadConfig(projectPath, logger);
|
|
255
|
-
|
|
256
|
-
// Extract services from config (REG-174)
|
|
257
|
-
if (config.services.length > 0) {
|
|
258
|
-
log(`Loaded ${config.services.length} service(s) from config`);
|
|
259
|
-
for (const svc of config.services) {
|
|
260
|
-
const entry = svc.entryPoint ? ` (entry: ${svc.entryPoint})` : '';
|
|
261
|
-
log(` - ${svc.name}: ${svc.path}${entry}`);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Load custom plugins from .grafema/plugins/
|
|
266
|
-
const customPlugins = await loadCustomPlugins(projectPath, log);
|
|
267
|
-
const plugins = createPlugins(config.plugins, customPlugins);
|
|
268
|
-
|
|
269
|
-
log(`Loaded ${plugins.length} plugins`);
|
|
270
|
-
|
|
271
|
-
// Resolve strict mode: CLI flag overrides config
|
|
272
|
-
const strictMode = options.strict ?? config.strict ?? false;
|
|
273
|
-
if (strictMode) {
|
|
274
|
-
log('Strict mode enabled - analysis will fail on unresolved references');
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
const startTime = Date.now();
|
|
278
|
-
|
|
279
|
-
const orchestrator = new Orchestrator({
|
|
280
|
-
graph: backend as unknown as import('@grafema/types').GraphBackend,
|
|
281
|
-
plugins,
|
|
282
|
-
serviceFilter: options.service || null,
|
|
283
|
-
entrypoint: options.entrypoint,
|
|
284
|
-
forceAnalysis: options.clear || false,
|
|
285
|
-
logger,
|
|
286
|
-
services: config.services.length > 0 ? config.services : undefined, // Pass config services (REG-174)
|
|
287
|
-
strictMode, // REG-330: Pass strict mode flag
|
|
288
|
-
onProgress: (progress) => {
|
|
289
|
-
if (options.verbose) {
|
|
290
|
-
log(`[${progress.phase}] ${progress.message}`);
|
|
291
|
-
}
|
|
292
|
-
},
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
let exitCode = 0;
|
|
296
|
-
|
|
297
|
-
try {
|
|
298
|
-
await orchestrator.run(projectPath);
|
|
299
|
-
await backend.flush();
|
|
300
|
-
|
|
301
|
-
const elapsed = ((Date.now() - startTime) / 1000).toFixed(2);
|
|
302
|
-
const stats = await backend.getStats();
|
|
303
|
-
|
|
304
|
-
log('');
|
|
305
|
-
log(`Analysis complete in ${elapsed}s`);
|
|
306
|
-
log(` Nodes: ${stats.nodeCount}`);
|
|
307
|
-
log(` Edges: ${stats.edgeCount}`);
|
|
308
|
-
|
|
309
|
-
// Get diagnostics and report summary
|
|
310
|
-
const diagnostics = orchestrator.getDiagnostics();
|
|
311
|
-
const reporter = new DiagnosticReporter(diagnostics);
|
|
312
|
-
|
|
313
|
-
// Print summary if there are any issues
|
|
314
|
-
if (diagnostics.count() > 0) {
|
|
315
|
-
log('');
|
|
316
|
-
log(reporter.categorizedSummary());
|
|
317
|
-
|
|
318
|
-
// In verbose mode, print full report
|
|
319
|
-
if (options.verbose) {
|
|
320
|
-
log('');
|
|
321
|
-
log(reporter.report({ format: 'text', includeSummary: false }));
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// Always write diagnostics.log (required for `grafema check` command)
|
|
326
|
-
const writer = new DiagnosticWriter();
|
|
327
|
-
await writer.write(diagnostics, grafemaDir);
|
|
328
|
-
if (options.debug) {
|
|
329
|
-
log(`Diagnostics written to ${writer.getLogPath(grafemaDir)}`);
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
// Determine exit code based on severity
|
|
333
|
-
if (diagnostics.hasFatal()) {
|
|
334
|
-
exitCode = 1;
|
|
335
|
-
} else if (diagnostics.hasErrors()) {
|
|
336
|
-
exitCode = 2; // Completed with errors
|
|
337
|
-
} else {
|
|
338
|
-
exitCode = 0; // Success (maybe warnings)
|
|
339
|
-
}
|
|
340
|
-
} catch (e) {
|
|
341
|
-
// Orchestrator threw (fatal error stopped analysis)
|
|
342
|
-
const error = e instanceof Error ? e : new Error(String(e));
|
|
343
|
-
const diagnostics = orchestrator.getDiagnostics();
|
|
344
|
-
const reporter = new DiagnosticReporter(diagnostics);
|
|
345
|
-
|
|
346
|
-
console.error('');
|
|
347
|
-
console.error(`✗ Analysis failed: ${error.message}`);
|
|
348
|
-
console.error('');
|
|
349
|
-
console.error('→ Run with --debug for detailed diagnostics');
|
|
350
|
-
|
|
351
|
-
if (diagnostics.count() > 0) {
|
|
352
|
-
console.error('');
|
|
353
|
-
console.error(reporter.report({ format: 'text', includeSummary: true }));
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
// Write diagnostics.log in debug mode even on failure
|
|
357
|
-
if (options.debug) {
|
|
358
|
-
const writer = new DiagnosticWriter();
|
|
359
|
-
await writer.write(diagnostics, grafemaDir);
|
|
360
|
-
console.error(`Diagnostics written to ${writer.getLogPath(grafemaDir)}`);
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
exitCode = 1;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
await backend.close();
|
|
367
|
-
|
|
368
|
-
// Exit with appropriate code
|
|
369
|
-
// 0 = success, 1 = fatal, 2 = errors
|
|
370
|
-
if (exitCode !== 0) {
|
|
371
|
-
process.exit(exitCode);
|
|
372
|
-
}
|
|
373
|
-
});
|
|
38
|
+
.action(analyzeAction);
|