Navegador

navegador / docs / api / analysis.md
1
# Analysis API Reference
2
3
```python
4
from navegador.analysis import (
5
ImpactAnalyzer,
6
FlowTracer,
7
DeadCodeDetector,
8
CycleDetector,
9
TestMapper,
10
)
11
from navegador.graph import GraphStore
12
```
13
14
---
15
16
## ImpactAnalyzer
17
18
Traces downstream dependents of a function, class, or file by following `CALLS`, `INHERITS`, and `IMPORTS` edges.
19
20
```python
21
class ImpactAnalyzer:
22
def __init__(self, store: GraphStore) -> None: ...
23
```
24
25
### `analyze`
26
27
```python
28
def analyze(
29
self,
30
name: str,
31
*,
32
node_type: str = "",
33
file: str = "",
34
depth: int = 0,
35
include_tests: bool = False,
36
include_knowledge: bool = False,
37
) -> ImpactResult
38
```
39
40
Compute the impact set for a given node.
41
42
**Parameters:**
43
44
| Name | Type | Default | Description |
45
|---|---|---|---|
46
| `name` | `str` | — | Name of the function, class, or file to analyze |
47
| `node_type` | `str` | `""` | Node type hint: `"Function"`, `"Class"`, `"File"` |
48
| `file` | `str` | `""` | Optional file path to disambiguate |
49
| `depth` | `int` | `0` | Maximum hops to follow (0 = unlimited) |
50
| `include_tests` | `bool` | `False` | Include test files in the impact set |
51
| `include_knowledge` | `bool` | `False` | Include linked knowledge nodes (rules, concepts, decisions) |
52
53
**Returns:** `ImpactResult`
54
55
---
56
57
### ImpactResult
58
59
```python
60
@dataclass
61
class ImpactResult:
62
root: ContextNode
63
direct_dependents: list[ContextNode]
64
transitive_dependents: list[ContextNode]
65
affected_files: list[str]
66
knowledge_nodes: list[ContextNode] # empty unless include_knowledge=True
67
depth_reached: int
68
```
69
70
| Field | Type | Description |
71
|---|---|---|
72
| `root` | `ContextNode` | The analyzed node |
73
| `direct_dependents` | `list[ContextNode]` | Nodes one hop away |
74
| `transitive_dependents` | `list[ContextNode]` | All nodes reachable beyond one hop |
75
| `affected_files` | `list[str]` | Unique file paths in the full dependent set |
76
| `knowledge_nodes` | `list[ContextNode]` | Linked concepts, rules, decisions |
77
| `depth_reached` | `int` | Actual maximum depth traversed |
78
79
**Example:**
80
81
```python
82
store = GraphStore.sqlite(".navegador/navegador.db")
83
analyzer = ImpactAnalyzer(store)
84
85
result = analyzer.analyze("validate_token", depth=3, include_knowledge=True)
86
print(f"{len(result.direct_dependents)} direct dependents")
87
print(f"{len(result.transitive_dependents)} transitive dependents")
88
print(f"Affects {len(result.affected_files)} files")
89
for rule in [n for n in result.knowledge_nodes if n.label == "Rule"]:
90
print(f" Governed by: {rule.name} ({rule.properties.get('severity')})")
91
```
92
93
---
94
95
## FlowTracer
96
97
Finds call paths between two functions.
98
99
```python
100
class FlowTracer:
101
def __init__(self, store: GraphStore) -> None: ...
102
```
103
104
### `trace`
105
106
```python
107
def trace(
108
self,
109
from_name: str,
110
to_name: str,
111
*,
112
from_file: str = "",
113
to_file: str = "",
114
max_paths: int = 3,
115
max_depth: int = 10,
116
) -> list[FlowPath]
117
```
118
119
Find call chains from `from_name` to `to_name`.
120
121
**Parameters:**
122
123
| Name | Type | Default | Description |
124
|---|---|---|---|
125
| `from_name` | `str` | — | Starting function name |
126
| `to_name` | `str` | — | Target function name |
127
| `from_file` | `str` | `""` | File path to disambiguate start |
128
| `to_file` | `str` | `""` | File path to disambiguate target |
129
| `max_paths` | `int` | `3` | Maximum number of paths to return |
130
| `max_depth` | `int` | `10` | Maximum call chain length |
131
132
**Returns:** `list[FlowPath]`
133
134
---
135
136
### FlowPath
137
138
```python
139
@dataclass
140
class FlowPath:
141
nodes: list[str] # function names in order
142
node_details: list[ContextNode]
143
length: int
144
```
145
146
**Example:**
147
148
```python
149
tracer = FlowTracer(store)
150
paths = tracer.trace("create_order", "process_payment", max_paths=5)
151
for i, path in enumerate(paths, 1):
152
print(f"Path {i}: {' -> '.join(path.nodes)}")
153
```
154
155
---
156
157
## DeadCodeDetector
158
159
Identifies functions and classes that are never called, never imported, and not entry points.
160
161
```python
162
class DeadCodeDetector:
163
def __init__(self, store: GraphStore) -> None: ...
164
```
165
166
### `find`
167
168
```python
169
def find(
170
self,
171
path: str | Path,
172
*,
173
exclude_tests: bool = False,
174
min_confidence: int = 80,
175
) -> list[DeadCodeCandidate]
176
```
177
178
Find potentially dead code within a path.
179
180
**Parameters:**
181
182
| Name | Type | Default | Description |
183
|---|---|---|---|
184
| `path` | `str \| Path` | — | Directory or file to analyze |
185
| `exclude_tests` | `bool` | `False` | Skip test files |
186
| `min_confidence` | `int` | `80` | Minimum confidence score to include (0–100) |
187
188
**Returns:** `list[DeadCodeCandidate]`
189
190
---
191
192
### DeadCodeCandidate
193
194
```python
195
@dataclass
196
class DeadCodeCandidate:
197
node: ContextNode
198
confidence: int # 0–100
199
reasons: list[str] # e.g. ["no callers", "no imports", "no decorator entry point"]
200
```
201
202
| Field | Type | Description |
203
|---|---|---|
204
| `node` | `ContextNode` | The potentially dead node |
205
| `confidence` | `int` | Confidence that this is truly unreachable (higher = more confident) |
206
| `reasons` | `list[str]` | Reasons for the classification |
207
208
**Example:**
209
210
```python
211
detector = DeadCodeDetector(store)
212
candidates = detector.find("./src", exclude_tests=True, min_confidence=90)
213
for c in candidates:
214
print(f"[{c.confidence}%] {c.node.label}: {c.node.name} {c.node.properties['file']}")
215
print(f" Reasons: {', '.join(c.reasons)}")
216
```
217
218
---
219
220
## CycleDetector
221
222
Finds circular dependency chains in call and import graphs.
223
224
```python
225
class CycleDetector:
226
def __init__(self, store: GraphStore) -> None: ...
227
```
228
229
### `find_import_cycles`
230
231
```python
232
def find_import_cycles(
233
self,
234
path: str | Path,
235
*,
236
min_length: int = 2,
237
) -> list[Cycle]
238
```
239
240
Find circular import chains within a path.
241
242
---
243
244
### `find_call_cycles`
245
246
```python
247
def find_call_cycles(
248
self,
249
path: str | Path,
250
*,
251
min_length: int = 2,
252
) -> list[Cycle]
253
```
254
255
Find circular call chains within a path.
256
257
---
258
259
### `find_all`
260
261
```python
262
def find_all(
263
self,
264
path: str | Path,
265
*,
266
min_length: int = 2,
267
) -> CycleReport
268
```
269
270
Find both import and call cycles.
271
272
**Parameters (all methods):**
273
274
| Name | Type | Default | Description |
275
|---|---|---|---|
276
| `path` | `str \| Path` | — | Directory or file to analyze |
277
| `min_length` | `int` | `2` | Minimum cycle length to report |
278
279
**Returns:** `list[Cycle]` or `CycleReport`
280
281
---
282
283
### Cycle
284
285
```python
286
@dataclass
287
class Cycle:
288
path: list[str] # node names (files or functions) forming the cycle
289
cycle_type: str # "import" or "call"
290
length: int
291
```
292
293
### CycleReport
294
295
```python
296
@dataclass
297
class CycleReport:
298
import_cycles: list[Cycle]
299
call_cycles: list[Cycle]
300
total: int
301
```
302
303
**Example:**
304
305
```python
306
detector = CycleDetector(store)
307
report = detector.find_all("./src")
308
print(f"{report.total} cycles found")
309
for cycle in report.import_cycles:
310
print(f" Import cycle: {' -> '.join(cycle.path)}")
311
```
312
313
---
314
315
## TestMapper
316
317
Maps test functions to the production code they exercise via call graph analysis.
318
319
```python
320
class TestMapper:
321
def __init__(self, store: GraphStore) -> None: ...
322
```
323
324
### `map`
325
326
```python
327
def map(
328
self,
329
src_path: str | Path,
330
test_path: str | Path,
331
*,
332
target: str = "",
333
) -> TestCoverageMap
334
```
335
336
Build a mapping of production functions to their covering tests.
337
338
**Parameters:**
339
340
| Name | Type | Default | Description |
341
|---|---|---|---|
342
| `src_path` | `str \| Path` | — | Production code directory |
343
| `test_path` | `str \| Path` | — | Test directory |
344
| `target` | `str` | `""` | If set, only map coverage for this specific function |
345
346
**Returns:** `TestCoverageMap`
347
348
---
349
350
### `uncovered`
351
352
```python
353
def uncovered(
354
self,
355
src_path: str | Path,
356
test_path: str | Path,
357
) -> list[ContextNode]
358
```
359
360
Return production functions and classes with no covering tests.
361
362
---
363
364
### TestCoverageMap
365
366
```python
367
@dataclass
368
class TestCoverageMap:
369
coverage: dict[str, list[ContextNode]] # function name -> list of test nodes
370
uncovered: list[ContextNode]
371
coverage_percent: float
372
```
373
374
| Field | Type | Description |
375
|---|---|---|
376
| `coverage` | `dict[str, list[ContextNode]]` | Maps each production function to its test nodes |
377
| `uncovered` | `list[ContextNode]` | Production functions with no tests |
378
| `coverage_percent` | `float` | Percentage of functions with at least one test |
379
380
**Example:**
381
382
```python
383
mapper = TestMapper(store)
384
coverage_map = mapper.map("./src", "./tests")
385
386
print(f"Coverage: {coverage_map.coverage_percent:.1f}%")
387
print(f"Uncovered: {len(coverage_map.uncovered)} functions")
388
389
for fn_name, tests in coverage_map.coverage.items():
390
print(f" {fn_name}: {len(tests)} tests")
391
for test in tests:
392
print(f" {test.properties['file']}::{test.name}")
393
```
394

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button