Navegador

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

Keyboard Shortcuts

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