{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"],"fields":{"title":{"boost":1000.0},"text":{"boost":1.0},"tags":{"boost":1000000.0}}},"docs":[{"location":"","title":"Navegador v0.7","text":"<p>The project knowledge graph for AI coding agents.</p> <p>Navegador builds and maintains a queryable graph of your software project \u2014 combining static code analysis with human-curated business knowledge \u2014 so that AI coding agents always have precise, structured context instead of raw file dumps.</p> <p>navegador \u2014 Spanish for navigator / sailor. It helps agents navigate your code.</p> <p>Current version: 0.7.0</p>"},{"location":"#two-layers-one-graph","title":"Two layers, one graph","text":"<pre><code>\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 KNOWLEDGE LAYER \u2502\n\u2502 Concepts \u00b7 Rules \u00b7 Decisions \u00b7 WikiPages \u00b7 People \u00b7 Domains \u2502\n\u2502 \u2502\n\u2502 \u2195 GOVERNS / IMPLEMENTS / DOCUMENTS / ANNOTATES \u2502\n\u2502 \u2502\n\u2502 CODE LAYER \u2502\n\u2502 Repository \u00b7 File \u00b7 Module \u00b7 Class \u00b7 Function \u00b7 Method \u2502\n\u2502 Variable \u00b7 Import \u00b7 Decorator \u00b7 (call graphs, hierarchies) \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n stored in FalkorDB (SQLite local / Redis prod)\n</code></pre> <p>The code layer is populated automatically by ingesting source trees. 13 languages are supported via tree-sitter (Python, TypeScript, JavaScript, Go, Rust, Java, Kotlin, C#, PHP, Ruby, Swift, C, C++). The knowledge layer is populated by manual curation (<code>navegador add</code>), GitHub wiki ingestion, and Planopticon output (meeting and video knowledge graphs).</p>"},{"location":"#quick-start","title":"Quick start","text":"<pre><code>pip install navegador # Python 3.12+ required\nnavegador ingest ./my-repo # parse + index the codebase\nnavegador explain AuthService # what is this thing?\nnavegador search \"rate limit\" --all # search code + knowledge together\n</code></pre> <p>Or use the Python SDK:</p> <pre><code>from navegador import Navegador\n\nnav = Navegador(\".navegador/navegador.db\")\nnav.ingest(\"./my-repo\")\nbundle = nav.explain(\"AuthService\")\nprint(bundle.to_markdown())\n</code></pre>"},{"location":"#what-goes-in-the-graph","title":"What goes in the graph","text":"Layer Node type Populated by Code Repository, File, Module <code>navegador ingest</code> Code Class, Function, Method <code>navegador ingest</code> (tree-sitter AST) Code Decorator, Import, Variable <code>navegador ingest</code> Code CALLS / INHERITS edges <code>navegador ingest</code> (call graph analysis) Knowledge Concept, Domain <code>navegador add concept</code> / <code>add domain</code> Knowledge Rule <code>navegador add rule</code> Knowledge Decision <code>navegador add decision</code> Knowledge Person <code>navegador add person</code> Knowledge WikiPage <code>navegador wiki ingest</code> Knowledge (any) <code>navegador planopticon ingest</code> Cross-layer ANNOTATES, GOVERNS, IMPLEMENTS <code>navegador annotate</code> Analysis TESTS, COUPLED_WITH edges <code>navegador testmap</code>, <code>navegador cycles</code>"},{"location":"#agent-integration","title":"Agent integration","text":"CLIMCPBootstrapEditor integrationCI/CD <p>The simplest integration: call <code>navegador explain</code> or <code>navegador context</code> from any shell script or agent tool definition.</p> <pre><code># get context for the file the agent just edited\nnavegador context src/auth/service.py --format json\n\n# look up a function before editing it\nnavegador function validate_token --depth 2 --format json\n\n# find everything annotated with a business concept\nnavegador concept PaymentProcessing --format json\n</code></pre> <p>Run navegador as a Model Context Protocol server. Configure it once in your agent settings and all navegador commands become callable tools with structured input/output.</p> <pre><code>{\n \"mcpServers\": {\n \"navegador\": {\n \"command\": \"navegador\",\n \"args\": [\"mcp\"]\n }\n }\n}\n</code></pre> <p>11 tools available. See MCP Integration for the full tool list and per-agent config snippets. Use <code>--read-only</code> mode to restrict agents to query-only access.</p> <p>One command to install navegador, ingest a repo, and wire the agent hook for your preferred AI coding assistant.</p> <pre><code>./bootstrap.sh --repo owner/repo --wiki --agent claude\n</code></pre> <p>Supports <code>--agent claude</code>, <code>--agent gemini</code>, and <code>--agent openai</code>. See Agent Hooks for what the hook does and how to configure it manually.</p> <p>Wire navegador into your editor with one command:</p> <pre><code>navegador editor setup claude-code\nnavegador editor setup cursor\nnavegador editor setup codex\nnavegador editor setup windsurf\n</code></pre> <p>Run navegador in CI pipelines for automated context checks:</p> <pre><code>navegador ci ingest\nnavegador ci stats\nnavegador ci check\n</code></pre>"},{"location":"#whats-new-in-070","title":"What's new in 0.7.0","text":"Feature Command / API 13 languages (added Kotlin, C#, PHP, Ruby, Swift, C, C++) <code>pip install \"navegador[languages]\"</code> Python SDK <code>from navegador import Navegador</code> Incremental ingestion <code>navegador ingest --incremental</code>, <code>--watch</code> Schema migrations <code>navegador migrate</code> Export / import <code>navegador export</code>, <code>navegador import</code> (JSONL) Editor integrations <code>navegador editor setup <editor></code> Analysis commands <code>navegador diff</code>, <code>navegador churn</code>, <code>navegador impact</code>, <code>navegador trace</code>, <code>navegador deadcode</code>, <code>navegador cycles</code>, <code>navegador testmap</code> Multi-repo <code>navegador repo add/list/ingest-all/search</code> Semantic search <code>navegador semantic-search</code>, <code>navegador ask</code> Framework enrichment Django, FastAPI, React, Rails, Spring Boot, Laravel, and more Monorepo support Turborepo, Nx, Yarn, pnpm, Cargo, Go workspaces Cluster mode Shared Redis graph, pub/sub, task queue, sessions 11 MCP tools (was 7) <code>get_rationale</code>, <code>find_owners</code>, <code>search_knowledge</code>, <code>blast_radius</code> added Sensitive content redaction <code>navegador ingest --redact</code> Shell completions <code>navegador completions bash/zsh/fish</code>"},{"location":"#license","title":"License","text":"<p>Navegador is open source under the MIT License. Copyright 2026 CONFLICT LLC.</p>"},{"location":"api/analysis/","title":"Analysis API Reference","text":"<pre><code>from navegador.analysis import (\n ImpactAnalyzer,\n FlowTracer,\n DeadCodeDetector,\n CycleDetector,\n TestMapper,\n)\nfrom navegador.graph import GraphStore\n</code></pre>"},{"location":"api/analysis/#impactanalyzer","title":"ImpactAnalyzer","text":"<p>Traces downstream dependents of a function, class, or file by following <code>CALLS</code>, <code>INHERITS</code>, and <code>IMPORTS</code> edges.</p> <pre><code>class ImpactAnalyzer:\n def __init__(self, store: GraphStore) -> None: ...\n</code></pre>"},{"location":"api/analysis/#analyze","title":"<code>analyze</code>","text":"<pre><code>def analyze(\n self,\n name: str,\n *,\n node_type: str = \"\",\n file: str = \"\",\n depth: int = 0,\n include_tests: bool = False,\n include_knowledge: bool = False,\n) -> ImpactResult\n</code></pre> <p>Compute the impact set for a given node.</p> <p>Parameters:</p> Name Type Default Description <code>name</code> <code>str</code> \u2014 Name of the function, class, or file to analyze <code>node_type</code> <code>str</code> <code>\"\"</code> Node type hint: <code>\"Function\"</code>, <code>\"Class\"</code>, <code>\"File\"</code> <code>file</code> <code>str</code> <code>\"\"</code> Optional file path to disambiguate <code>depth</code> <code>int</code> <code>0</code> Maximum hops to follow (0 = unlimited) <code>include_tests</code> <code>bool</code> <code>False</code> Include test files in the impact set <code>include_knowledge</code> <code>bool</code> <code>False</code> Include linked knowledge nodes (rules, concepts, decisions) <p>Returns: <code>ImpactResult</code></p>"},{"location":"api/analysis/#impactresult","title":"ImpactResult","text":"<pre><code>@dataclass\nclass ImpactResult:\n root: ContextNode\n direct_dependents: list[ContextNode]\n transitive_dependents: list[ContextNode]\n affected_files: list[str]\n knowledge_nodes: list[ContextNode] # empty unless include_knowledge=True\n depth_reached: int\n</code></pre> Field Type Description <code>root</code> <code>ContextNode</code> The analyzed node <code>direct_dependents</code> <code>list[ContextNode]</code> Nodes one hop away <code>transitive_dependents</code> <code>list[ContextNode]</code> All nodes reachable beyond one hop <code>affected_files</code> <code>list[str]</code> Unique file paths in the full dependent set <code>knowledge_nodes</code> <code>list[ContextNode]</code> Linked concepts, rules, decisions <code>depth_reached</code> <code>int</code> Actual maximum depth traversed <p>Example:</p> <pre><code>store = GraphStore.sqlite(\".navegador/navegador.db\")\nanalyzer = ImpactAnalyzer(store)\n\nresult = analyzer.analyze(\"validate_token\", depth=3, include_knowledge=True)\nprint(f\"{len(result.direct_dependents)} direct dependents\")\nprint(f\"{len(result.transitive_dependents)} transitive dependents\")\nprint(f\"Affects {len(result.affected_files)} files\")\nfor rule in [n for n in result.knowledge_nodes if n.label == \"Rule\"]:\n print(f\" Governed by: {rule.name} ({rule.properties.get('severity')})\")\n</code></pre>"},{"location":"api/analysis/#flowtracer","title":"FlowTracer","text":"<p>Finds call paths between two functions.</p> <pre><code>class FlowTracer:\n def __init__(self, store: GraphStore) -> None: ...\n</code></pre>"},{"location":"api/analysis/#trace","title":"<code>trace</code>","text":"<pre><code>def trace(\n self,\n from_name: str,\n to_name: str,\n *,\n from_file: str = \"\",\n to_file: str = \"\",\n max_paths: int = 3,\n max_depth: int = 10,\n) -> list[FlowPath]\n</code></pre> <p>Find call chains from <code>from_name</code> to <code>to_name</code>.</p> <p>Parameters:</p> Name Type Default Description <code>from_name</code> <code>str</code> \u2014 Starting function name <code>to_name</code> <code>str</code> \u2014 Target function name <code>from_file</code> <code>str</code> <code>\"\"</code> File path to disambiguate start <code>to_file</code> <code>str</code> <code>\"\"</code> File path to disambiguate target <code>max_paths</code> <code>int</code> <code>3</code> Maximum number of paths to return <code>max_depth</code> <code>int</code> <code>10</code> Maximum call chain length <p>Returns: <code>list[FlowPath]</code></p>"},{"location":"api/analysis/#flowpath","title":"FlowPath","text":"<pre><code>@dataclass\nclass FlowPath:\n nodes: list[str] # function names in order\n node_details: list[ContextNode]\n length: int\n</code></pre> <p>Example:</p> <pre><code>tracer = FlowTracer(store)\npaths = tracer.trace(\"create_order\", \"process_payment\", max_paths=5)\nfor i, path in enumerate(paths, 1):\n print(f\"Path {i}: {' -> '.join(path.nodes)}\")\n</code></pre>"},{"location":"api/analysis/#deadcodedetector","title":"DeadCodeDetector","text":"<p>Identifies functions and classes that are never called, never imported, and not entry points.</p> <pre><code>class DeadCodeDetector:\n def __init__(self, store: GraphStore) -> None: ...\n</code></pre>"},{"location":"api/analysis/#find","title":"<code>find</code>","text":"<pre><code>def find(\n self,\n path: str | Path,\n *,\n exclude_tests: bool = False,\n min_confidence: int = 80,\n) -> list[DeadCodeCandidate]\n</code></pre> <p>Find potentially dead code within a path.</p> <p>Parameters:</p> Name Type Default Description <code>path</code> <code>str \\| Path</code> \u2014 Directory or file to analyze <code>exclude_tests</code> <code>bool</code> <code>False</code> Skip test files <code>min_confidence</code> <code>int</code> <code>80</code> Minimum confidence score to include (0\u2013100) <p>Returns: <code>list[DeadCodeCandidate]</code></p>"},{"location":"api/analysis/#deadcodecandidate","title":"DeadCodeCandidate","text":"<pre><code>@dataclass\nclass DeadCodeCandidate:\n node: ContextNode\n confidence: int # 0\u2013100\n reasons: list[str] # e.g. [\"no callers\", \"no imports\", \"no decorator entry point\"]\n</code></pre> Field Type Description <code>node</code> <code>ContextNode</code> The potentially dead node <code>confidence</code> <code>int</code> Confidence that this is truly unreachable (higher = more confident) <code>reasons</code> <code>list[str]</code> Reasons for the classification <p>Example:</p> <pre><code>detector = DeadCodeDetector(store)\ncandidates = detector.find(\"./src\", exclude_tests=True, min_confidence=90)\nfor c in candidates:\n print(f\"[{c.confidence}%] {c.node.label}: {c.node.name} {c.node.properties['file']}\")\n print(f\" Reasons: {', '.join(c.reasons)}\")\n</code></pre>"},{"location":"api/analysis/#cycledetector","title":"CycleDetector","text":"<p>Finds circular dependency chains in call and import graphs.</p> <pre><code>class CycleDetector:\n def __init__(self, store: GraphStore) -> None: ...\n</code></pre>"},{"location":"api/analysis/#find_import_cycles","title":"<code>find_import_cycles</code>","text":"<pre><code>def find_import_cycles(\n self,\n path: str | Path,\n *,\n min_length: int = 2,\n) -> list[Cycle]\n</code></pre> <p>Find circular import chains within a path.</p>"},{"location":"api/analysis/#find_call_cycles","title":"<code>find_call_cycles</code>","text":"<pre><code>def find_call_cycles(\n self,\n path: str | Path,\n *,\n min_length: int = 2,\n) -> list[Cycle]\n</code></pre> <p>Find circular call chains within a path.</p>"},{"location":"api/analysis/#find_all","title":"<code>find_all</code>","text":"<pre><code>def find_all(\n self,\n path: str | Path,\n *,\n min_length: int = 2,\n) -> CycleReport\n</code></pre> <p>Find both import and call cycles.</p> <p>Parameters (all methods):</p> Name Type Default Description <code>path</code> <code>str \\| Path</code> \u2014 Directory or file to analyze <code>min_length</code> <code>int</code> <code>2</code> Minimum cycle length to report <p>Returns: <code>list[Cycle]</code> or <code>CycleReport</code></p>"},{"location":"api/analysis/#cycle","title":"Cycle","text":"<pre><code>@dataclass\nclass Cycle:\n path: list[str] # node names (files or functions) forming the cycle\n cycle_type: str # \"import\" or \"call\"\n length: int\n</code></pre>"},{"location":"api/analysis/#cyclereport","title":"CycleReport","text":"<pre><code>@dataclass\nclass CycleReport:\n import_cycles: list[Cycle]\n call_cycles: list[Cycle]\n total: int\n</code></pre> <p>Example:</p> <pre><code>detector = CycleDetector(store)\nreport = detector.find_all(\"./src\")\nprint(f\"{report.total} cycles found\")\nfor cycle in report.import_cycles:\n print(f\" Import cycle: {' -> '.join(cycle.path)}\")\n</code></pre>"},{"location":"api/analysis/#testmapper","title":"TestMapper","text":"<p>Maps test functions to the production code they exercise via call graph analysis.</p> <pre><code>class TestMapper:\n def __init__(self, store: GraphStore) -> None: ...\n</code></pre>"},{"location":"api/analysis/#map","title":"<code>map</code>","text":"<pre><code>def map(\n self,\n src_path: str | Path,\n test_path: str | Path,\n *,\n target: str = \"\",\n) -> TestCoverageMap\n</code></pre> <p>Build a mapping of production functions to their covering tests.</p> <p>Parameters:</p> Name Type Default Description <code>src_path</code> <code>str \\| Path</code> \u2014 Production code directory <code>test_path</code> <code>str \\| Path</code> \u2014 Test directory <code>target</code> <code>str</code> <code>\"\"</code> If set, only map coverage for this specific function <p>Returns: <code>TestCoverageMap</code></p>"},{"location":"api/analysis/#uncovered","title":"<code>uncovered</code>","text":"<pre><code>def uncovered(\n self,\n src_path: str | Path,\n test_path: str | Path,\n) -> list[ContextNode]\n</code></pre> <p>Return production functions and classes with no covering tests.</p>"},{"location":"api/analysis/#testcoveragemap","title":"TestCoverageMap","text":"<pre><code>@dataclass\nclass TestCoverageMap:\n coverage: dict[str, list[ContextNode]] # function name -> list of test nodes\n uncovered: list[ContextNode]\n coverage_percent: float\n</code></pre> Field Type Description <code>coverage</code> <code>dict[str, list[ContextNode]]</code> Maps each production function to its test nodes <code>uncovered</code> <code>list[ContextNode]</code> Production functions with no tests <code>coverage_percent</code> <code>float</code> Percentage of functions with at least one test <p>Example:</p> <pre><code>mapper = TestMapper(store)\ncoverage_map = mapper.map(\"./src\", \"./tests\")\n\nprint(f\"Coverage: {coverage_map.coverage_percent:.1f}%\")\nprint(f\"Uncovered: {len(coverage_map.uncovered)} functions\")\n\nfor fn_name, tests in coverage_map.coverage.items():\n print(f\" {fn_name}: {len(tests)} tests\")\n for test in tests:\n print(f\" {test.properties['file']}::{test.name}\")\n</code></pre>"},{"location":"api/graph/","title":"Graph API","text":"<pre><code>from navegador.graph import GraphStore\nfrom navegador.context import ContextLoader, ContextBundle, ContextNode\n</code></pre>"},{"location":"api/graph/#graphstore","title":"GraphStore","text":"<p>The database abstraction layer. Both SQLite and Redis backends implement this interface.</p> <pre><code>class GraphStore:\n @classmethod\n def sqlite(cls, path: str | Path = \"navegador.db\") -> \"GraphStore\": ...\n\n @classmethod\n def redis(cls, url: str = \"redis://localhost:6379\") -> \"GraphStore\": ...\n\n def query(\n self,\n cypher: str,\n params: dict | None = None,\n ) -> list[dict]: ...\n\n def create_node(\n self,\n label: str,\n properties: dict,\n ) -> str: ... # returns node ID\n\n def merge_node(\n self,\n label: str,\n match_properties: dict,\n set_properties: dict | None = None,\n ) -> str: ... # upsert by match_properties, returns node ID\n\n def create_edge(\n self,\n from_id: str,\n to_id: str,\n edge_type: str,\n properties: dict | None = None,\n ) -> None: ...\n\n def merge_edge(\n self,\n from_label: str,\n from_match: dict,\n to_label: str,\n to_match: dict,\n edge_type: str,\n properties: dict | None = None,\n ) -> None: ...\n\n def clear(self) -> None: ...\n\n def close(self) -> None: ...\n\n def export_jsonl(self, fp: IO[str]) -> None: ...\n \"\"\"Write all nodes and edges to a JSONL stream.\"\"\"\n\n def import_jsonl(self, fp: IO[str]) -> None: ...\n \"\"\"Read nodes and edges from a JSONL stream and merge into the graph.\"\"\"\n</code></pre>"},{"location":"api/graph/#usage","title":"Usage","text":"<pre><code># SQLite (local dev)\nstore = GraphStore.sqlite(\".navegador/navegador.db\")\n\n# Redis (production)\nstore = GraphStore.redis(\"redis://localhost:6379\")\n\n# raw Cypher query\nresults = store.query(\n \"MATCH (f:Function {name: $name}) RETURN f\",\n params={\"name\": \"validate_token\"}\n)\n\n# create a node\nnode_id = store.create_node(\"Concept\", {\n \"name\": \"Idempotency\",\n \"description\": \"Operations safe to retry\"\n})\n\n# upsert a node (match by name, update description)\nnode_id = store.merge_node(\n \"Concept\",\n match_properties={\"name\": \"Idempotency\"},\n set_properties={\"description\": \"Updated description\"}\n)\n\n# create an edge\nstore.create_edge(from_id, to_id, \"ANNOTATES\")\n\n# wipe the graph\nstore.clear()\n</code></pre>"},{"location":"api/graph/#context-manager","title":"Context manager","text":"<p><code>GraphStore</code> implements the context manager protocol:</p> <pre><code>with GraphStore.sqlite(\".navegador/navegador.db\") as store:\n results = store.query(\"MATCH (n) RETURN count(n) AS total\")\n</code></pre>"},{"location":"api/graph/#contextloader","title":"ContextLoader","text":"<p>Builds structured context bundles from graph queries. Each method corresponds to a CLI command.</p> <pre><code>class ContextLoader:\n def __init__(self, store: GraphStore) -> None: ...\n\n def load_file(self, path: str) -> ContextBundle: ...\n\n def load_function(\n self,\n name: str,\n *,\n file: str = \"\",\n depth: int = 1,\n ) -> ContextBundle: ...\n\n def load_class(\n self,\n name: str,\n *,\n file: str = \"\",\n ) -> ContextBundle: ...\n\n def explain(\n self,\n name: str,\n *,\n file: str = \"\",\n ) -> ContextBundle: ...\n\n def load_concept(self, name: str) -> ContextBundle: ...\n\n def load_domain(self, name: str) -> ContextBundle: ...\n\n def search(\n self,\n query: str,\n *,\n all_layers: bool = False,\n docs_only: bool = False,\n limit: int = 20,\n ) -> list[ContextNode]: ...\n\n def search_by_docstring(\n self,\n query: str,\n *,\n limit: int = 20,\n ) -> list[ContextNode]: ...\n\n def decorated_by(\n self,\n decorator: str,\n ) -> list[ContextNode]: ...\n</code></pre>"},{"location":"api/graph/#usage_1","title":"Usage","text":"<pre><code>store = GraphStore.sqlite(\".navegador/navegador.db\")\nloader = ContextLoader(store)\n\n# file context\nbundle = loader.load_file(\"src/auth/service.py\")\n\n# function with 2-hop call graph\nbundle = loader.load_function(\"validate_token\", depth=2)\n\n# class hierarchy\nbundle = loader.load_class(\"PaymentProcessor\", file=\"src/payments/processor.py\")\n\n# universal explain\nbundle = loader.explain(\"AuthService\")\n\n# concept with annotated code\nbundle = loader.load_concept(\"Idempotency\")\n\n# domain overview\nbundle = loader.load_domain(\"Payments\")\n\n# search\nnodes = loader.search(\"rate limit\", all_layers=True, limit=10)\n\n# all @login_required functions\nnodes = loader.decorated_by(\"login_required\")\n</code></pre>"},{"location":"api/graph/#contextbundle","title":"ContextBundle","text":"<p>The structured result type returned by <code>ContextLoader</code> methods.</p> <pre><code>@dataclass\nclass ContextBundle:\n root: ContextNode\n nodes: list[ContextNode]\n edges: list[ContextEdge]\n metadata: dict\n\n def to_json(self) -> str: ...\n def to_markdown(self) -> str: ...\n def to_dict(self) -> dict: ...\n</code></pre>"},{"location":"api/graph/#contextnode","title":"ContextNode","text":"<p>A single node in a context bundle.</p> <pre><code>@dataclass\nclass ContextNode:\n id: str\n label: str # e.g. \"Function\", \"Concept\"\n name: str\n properties: dict # all node properties\n layer: str # \"code\" or \"knowledge\"\n</code></pre>"},{"location":"api/graph/#contextedge","title":"ContextEdge","text":"<pre><code>@dataclass\nclass ContextEdge:\n from_id: str\n to_id: str\n edge_type: str # e.g. \"CALLS\", \"ANNOTATES\"\n properties: dict\n</code></pre>"},{"location":"api/graph/#schema-migrations","title":"Schema migrations","text":"<pre><code>from navegador.graph import migrate\n\nstore = GraphStore.sqlite(\".navegador/navegador.db\")\nmigrate(store) # applies any pending schema migrations; idempotent\n</code></pre> <p>The <code>migrate()</code> function is safe to call on every startup. It compares the stored migration version against the current schema and applies only missing migrations.</p>"},{"location":"api/graph/#formatting-output","title":"Formatting output","text":"<pre><code>bundle = loader.load_function(\"validate_token\")\n\n# JSON string\nprint(bundle.to_json())\n\n# Markdown string (for agent consumption)\nprint(bundle.to_markdown())\n\n# Python dict (for further processing)\ndata = bundle.to_dict()\n</code></pre>"},{"location":"api/ingestion/","title":"Ingestion API","text":"<p>All ingesters accept a <code>GraphStore</code> instance and return an <code>IngestionResult</code> dataclass.</p> <pre><code>from navegador.graph import GraphStore\nfrom navegador.ingest import RepoIngester, KnowledgeIngester, WikiIngester, PlanopticonIngester\n</code></pre>"},{"location":"api/ingestion/#ingestionresult","title":"IngestionResult","text":"<pre><code>@dataclass\nclass IngestionResult:\n nodes_created: int\n nodes_updated: int\n edges_created: int\n files_processed: int\n errors: list[str]\n duration_seconds: float\n</code></pre>"},{"location":"api/ingestion/#repoingester","title":"RepoIngester","text":"<p>Parses a source tree and writes code layer nodes and edges.</p> <pre><code>class RepoIngester:\n def __init__(self, store: GraphStore) -> None: ...\n\n def ingest(\n self,\n path: str | Path,\n *,\n clear: bool = False,\n incremental: bool = False,\n redact: bool = False,\n monorepo: bool = False,\n ) -> IngestionResult: ...\n\n def ingest_file(\n self,\n path: str | Path,\n *,\n redact: bool = False,\n ) -> IngestionResult: ...\n</code></pre>"},{"location":"api/ingestion/#usage","title":"Usage","text":"<pre><code>store = GraphStore.sqlite(\".navegador/navegador.db\")\ningester = RepoIngester(store)\n\n# full repo ingest\nresult = ingester.ingest(\"./src\")\nprint(f\"{result.nodes_created} nodes, {result.edges_created} edges\")\n\n# incremental ingest \u2014 only reprocesses files whose content hash has changed\nresult = ingester.ingest(\"./src\", incremental=True)\n\n# incremental: single file\nresult = ingester.ingest_file(\"./src/auth/service.py\")\n\n# wipe + rebuild\nresult = ingester.ingest(\"./src\", clear=True)\n\n# redact sensitive content (strips tokens, passwords, keys from string literals)\nresult = ingester.ingest(\"./src\", redact=True)\n\n# monorepo \u2014 traverse workspace sub-packages\nresult = ingester.ingest(\"./monorepo\", monorepo=True)\n</code></pre>"},{"location":"api/ingestion/#supported-languages","title":"Supported languages","text":"Language File extensions Parser Extra required Python <code>.py</code> tree-sitter-python \u2014 (included) TypeScript <code>.ts</code>, <code>.tsx</code> tree-sitter-typescript \u2014 (included) JavaScript <code>.js</code>, <code>.jsx</code> tree-sitter-javascript \u2014 (included) Go <code>.go</code> tree-sitter-go \u2014 (included) Rust <code>.rs</code> tree-sitter-rust \u2014 (included) Java <code>.java</code> tree-sitter-java \u2014 (included) Kotlin <code>.kt</code>, <code>.kts</code> tree-sitter-kotlin <code>navegador[languages]</code> C# <code>.cs</code> tree-sitter-c-sharp <code>navegador[languages]</code> PHP <code>.php</code> tree-sitter-php <code>navegador[languages]</code> Ruby <code>.rb</code> tree-sitter-ruby <code>navegador[languages]</code> Swift <code>.swift</code> tree-sitter-swift <code>navegador[languages]</code> C <code>.c</code>, <code>.h</code> tree-sitter-c <code>navegador[languages]</code> C++ <code>.cpp</code>, <code>.cc</code>, <code>.cxx</code>, <code>.hpp</code> tree-sitter-cpp <code>navegador[languages]</code>"},{"location":"api/ingestion/#adding-a-new-language-parser","title":"Adding a new language parser","text":"<ol> <li>Install the tree-sitter grammar: <code>pip install tree-sitter-<lang></code></li> <li>Subclass <code>navegador.ingest.base.LanguageParser</code>:</li> </ol> <pre><code>from navegador.ingest.base import LanguageParser, ParseResult\n\nclass RubyParser(LanguageParser):\n language = \"ruby\"\n extensions = [\".rb\"]\n\n def parse(self, source: str, file_path: str) -> ParseResult:\n # use self.tree_sitter_language to build the tree\n # return ParseResult with nodes and edges\n ...\n</code></pre> <ol> <li>Register in <code>navegador/ingest/registry.py</code>:</li> </ol> <pre><code>from .ruby import RubyParser\nPARSERS[\"ruby\"] = RubyParser\n</code></pre> <p><code>RepoIngester</code> dispatches to registered parsers by file extension.</p>"},{"location":"api/ingestion/#framework-enrichers","title":"Framework enrichers","text":"<p>After parsing, <code>RepoIngester</code> runs framework-specific enrichers that annotate nodes with framework context. Enrichers are discovered automatically based on what frameworks are detected in the repo.</p> Framework What gets enriched Django Models, views, URL patterns, admin registrations FastAPI Route handlers, dependency injections, Pydantic schemas React Components, hooks, prop types Express Route handlers, middleware chains React Native Screens, navigators Rails Controllers, models, routes Spring Boot Beans, controllers, repositories Laravel Controllers, models, routes"},{"location":"api/ingestion/#knowledgeingester","title":"KnowledgeIngester","text":"<p>Writes knowledge layer nodes. Wraps the <code>navegador add</code> commands programmatically.</p> <pre><code>class KnowledgeIngester:\n def __init__(self, store: GraphStore) -> None: ...\n\n def add_concept(\n self,\n name: str,\n *,\n description: str = \"\",\n domain: str = \"\",\n status: str = \"\",\n ) -> str: ... # returns node ID\n\n def add_rule(\n self,\n name: str,\n *,\n description: str = \"\",\n domain: str = \"\",\n severity: str = \"info\",\n rationale: str = \"\",\n ) -> str: ...\n\n def add_decision(\n self,\n name: str,\n *,\n description: str = \"\",\n domain: str = \"\",\n rationale: str = \"\",\n alternatives: str = \"\",\n date: str = \"\",\n status: str = \"proposed\",\n ) -> str: ...\n\n def add_person(\n self,\n name: str,\n *,\n email: str = \"\",\n role: str = \"\",\n team: str = \"\",\n ) -> str: ...\n\n def add_domain(\n self,\n name: str,\n *,\n description: str = \"\",\n ) -> str: ...\n\n def annotate(\n self,\n code_name: str,\n *,\n node_type: str = \"Function\",\n concept: str = \"\",\n rule: str = \"\",\n ) -> None: ...\n</code></pre>"},{"location":"api/ingestion/#usage_1","title":"Usage","text":"<pre><code>store = GraphStore.sqlite(\".navegador/navegador.db\")\ningester = KnowledgeIngester(store)\n\ningester.add_domain(\"Payments\", description=\"Payment processing and billing\")\ningester.add_concept(\"Idempotency\", domain=\"Payments\",\n description=\"Operations safe to retry without side effects\")\ningester.add_rule(\"RequireIdempotencyKey\",\n domain=\"Payments\", severity=\"critical\",\n rationale=\"Card networks retry on timeout\")\ningester.annotate(\"process_payment\", node_type=\"Function\",\n concept=\"Idempotency\", rule=\"RequireIdempotencyKey\")\n</code></pre>"},{"location":"api/ingestion/#wikiingester","title":"WikiIngester","text":"<p>Fetches GitHub wiki pages and writes <code>WikiPage</code> nodes.</p> <pre><code>class WikiIngester:\n def __init__(self, store: GraphStore) -> None: ...\n\n def ingest_repo(\n self,\n repo: str,\n *,\n token: str = \"\",\n use_api: bool = False,\n ) -> IngestionResult: ...\n\n def ingest_dir(\n self,\n path: str | Path,\n ) -> IngestionResult: ...\n</code></pre>"},{"location":"api/ingestion/#usage_2","title":"Usage","text":"<pre><code>import os\nstore = GraphStore.sqlite(\".navegador/navegador.db\")\ningester = WikiIngester(store)\n\n# from GitHub API\nresult = ingester.ingest_repo(\"myorg/myrepo\", token=os.environ[\"GITHUB_TOKEN\"])\n\n# from local clone\nresult = ingester.ingest_dir(\"./myrepo.wiki\")\n</code></pre>"},{"location":"api/ingestion/#planopticoningester","title":"PlanopticonIngester","text":"<p>Ingests Planopticon knowledge graph output into the knowledge layer.</p> <pre><code>class PlanopticonIngester:\n def __init__(self, store: GraphStore) -> None: ...\n\n def ingest(\n self,\n path: str | Path,\n *,\n input_type: str = \"auto\",\n source: str = \"\",\n ) -> IngestionResult: ...\n\n def ingest_manifest(\n self,\n path: str | Path,\n *,\n source: str = \"\",\n ) -> IngestionResult: ...\n\n def ingest_kg(\n self,\n path: str | Path,\n *,\n source: str = \"\",\n ) -> IngestionResult: ...\n\n def ingest_interchange(\n self,\n path: str | Path,\n *,\n source: str = \"\",\n ) -> IngestionResult: ...\n\n def ingest_batch(\n self,\n path: str | Path,\n *,\n source: str = \"\",\n ) -> IngestionResult: ...\n</code></pre> <p><code>input_type</code> values: <code>\"auto\"</code>, <code>\"manifest\"</code>, <code>\"kg\"</code>, <code>\"interchange\"</code>, <code>\"batch\"</code>.</p> <p>See Planopticon guide for format details and entity mapping.</p>"},{"location":"api/ingestion/#export-and-import","title":"Export and import","text":"<p>Navegador can export the full graph (or a subset) to JSONL for backup, migration, or sharing. The JSONL format is one JSON object per line, where each object is either a node or an edge.</p> <pre><code>navegador export > graph.jsonl\nnavegador export --nodes-only > nodes.jsonl\nnavegador import graph.jsonl\n</code></pre> <p>Python API:</p> <pre><code>from navegador.graph import GraphStore\n\nstore = GraphStore.sqlite(\".navegador/navegador.db\")\n\n# export\nwith open(\"graph.jsonl\", \"w\") as f:\n store.export_jsonl(f)\n\n# import into a new store\nnew_store = GraphStore.sqlite(\".navegador/new.db\")\nwith open(\"graph.jsonl\") as f:\n new_store.import_jsonl(f)\n</code></pre>"},{"location":"api/ingestion/#schema-migrations","title":"Schema migrations","text":"<p>When upgrading navegador, run <code>navegador migrate</code> before re-ingesting to apply schema changes (new node properties, new edge types, index updates):</p> <pre><code>navegador migrate\n</code></pre> <p>Migrations are idempotent \u2014 safe to run multiple times. The migration state is stored in the graph itself under a <code>_MigrationState</code> node.</p> <p>Python API:</p> <pre><code>from navegador.graph import GraphStore, migrate\n\nstore = GraphStore.sqlite(\".navegador/navegador.db\")\nmigrate(store) # applies any pending migrations\n</code></pre>"},{"location":"api/mcp/","title":"MCP Server API","text":"<p>Navegador exposes all context-loading commands as an MCP server. The server is created with <code>create_mcp_server()</code> and speaks the Model Context Protocol over stdio.</p> <pre><code>from navegador.mcp import create_mcp_server\n</code></pre>"},{"location":"api/mcp/#create_mcp_server","title":"create_mcp_server","text":"<pre><code>def create_mcp_server(\n store: GraphStore | None = None,\n db_path: str = \"\",\n read_only: bool = False,\n max_query_complexity: int = 0,\n) -> MCPServer: ...\n</code></pre> <p>Creates and returns an MCP server instance. If <code>store</code> is not provided, opens a <code>GraphStore.sqlite()</code> at <code>db_path</code> (or <code>NAVEGADOR_DB</code> env var, or <code>./navegador.db</code>).</p> <ul> <li><code>read_only</code>: when <code>True</code>, disables the <code>ingest</code> tool and restricts the <code>query</code> tool to <code>MATCH</code>/<code>RETURN</code> only. Corresponds to <code>navegador mcp --read-only</code>.</li> <li><code>max_query_complexity</code>: if non-zero, rejects Cypher queries whose estimated complexity exceeds this value. Prevents runaway traversals in multi-agent environments.</li> </ul> <p>The CLI command <code>navegador mcp [--db path] [--read-only]</code> calls this function and runs the server loop.</p>"},{"location":"api/mcp/#usage","title":"Usage","text":"<pre><code>from navegador.graph import GraphStore\nfrom navegador.mcp import create_mcp_server\n\nstore = GraphStore.sqlite(\".navegador/navegador.db\")\nserver = create_mcp_server(store=store)\nserver.run() # blocks; serves over stdio\n</code></pre> <p>To embed in a larger MCP server:</p> <pre><code>server = create_mcp_server(db_path=\".navegador/navegador.db\")\n# register additional tools on server before running\nserver.run()\n</code></pre>"},{"location":"api/mcp/#available-mcp-tools","title":"Available MCP tools","text":"<p>All tools accept JSON input and return JSON output. There are 11 tools in total.</p>"},{"location":"api/mcp/#ingest","title":"<code>ingest</code>","text":"<p>Ingest a repository or file into the graph.</p> <p>Input schema: <pre><code>{\n \"type\": \"object\",\n \"properties\": {\n \"path\": {\n \"type\": \"string\",\n \"description\": \"Path to the repo or file to ingest\"\n },\n \"clear\": {\n \"type\": \"boolean\",\n \"description\": \"Wipe the graph before ingesting\",\n \"default\": false\n }\n },\n \"required\": [\"path\"]\n}\n</code></pre></p> <p>Output: <code>IngestionResult</code> serialized to JSON.</p>"},{"location":"api/mcp/#context","title":"<code>context</code>","text":"<p>Return the full context bundle for a source file.</p> <p>Input schema: <pre><code>{\n \"type\": \"object\",\n \"properties\": {\n \"file\": {\n \"type\": \"string\",\n \"description\": \"Path to the source file\"\n },\n \"format\": {\n \"type\": \"string\",\n \"enum\": [\"json\", \"markdown\"],\n \"default\": \"json\"\n }\n },\n \"required\": [\"file\"]\n}\n</code></pre></p> <p>Output: <code>ContextBundle</code> as JSON or markdown string.</p>"},{"location":"api/mcp/#function","title":"<code>function</code>","text":"<p>Return a function's context bundle including callers, callees, and decorators.</p> <p>Input schema: <pre><code>{\n \"type\": \"object\",\n \"properties\": {\n \"name\": {\n \"type\": \"string\",\n \"description\": \"Function name\"\n },\n \"file\": {\n \"type\": \"string\",\n \"description\": \"Optional file path to disambiguate\"\n },\n \"depth\": {\n \"type\": \"integer\",\n \"description\": \"Call graph traversal depth\",\n \"default\": 1\n }\n },\n \"required\": [\"name\"]\n}\n</code></pre></p> <p>Output: <code>ContextBundle</code> as JSON.</p>"},{"location":"api/mcp/#class","title":"<code>class</code>","text":"<p>Return a class context bundle including hierarchy, methods, and references.</p> <p>Input schema: <pre><code>{\n \"type\": \"object\",\n \"properties\": {\n \"name\": {\n \"type\": \"string\",\n \"description\": \"Class name\"\n },\n \"file\": {\n \"type\": \"string\",\n \"description\": \"Optional file path to disambiguate\"\n }\n },\n \"required\": [\"name\"]\n}\n</code></pre></p> <p>Output: <code>ContextBundle</code> as JSON.</p>"},{"location":"api/mcp/#explain","title":"<code>explain</code>","text":"<p>Universal lookup: explain any node (function, class, file, concept, rule, decision) by name.</p> <p>Input schema: <pre><code>{\n \"type\": \"object\",\n \"properties\": {\n \"name\": {\n \"type\": \"string\",\n \"description\": \"Node name to explain\"\n },\n \"file\": {\n \"type\": \"string\",\n \"description\": \"Optional file path to disambiguate code nodes\"\n }\n },\n \"required\": [\"name\"]\n}\n</code></pre></p> <p>Output: <code>ContextBundle</code> as JSON.</p>"},{"location":"api/mcp/#search","title":"<code>search</code>","text":"<p>Search the graph by text query.</p> <p>Input schema: <pre><code>{\n \"type\": \"object\",\n \"properties\": {\n \"query\": {\n \"type\": \"string\",\n \"description\": \"Search query\"\n },\n \"all\": {\n \"type\": \"boolean\",\n \"description\": \"Search all layers including knowledge layer\",\n \"default\": false\n },\n \"docs\": {\n \"type\": \"boolean\",\n \"description\": \"Search docstrings and wiki content only\",\n \"default\": false\n },\n \"limit\": {\n \"type\": \"integer\",\n \"description\": \"Maximum results to return\",\n \"default\": 20\n }\n },\n \"required\": [\"query\"]\n}\n</code></pre></p> <p>Output: Array of <code>ContextNode</code> objects as JSON.</p>"},{"location":"api/mcp/#query","title":"<code>query</code>","text":"<p>Execute a raw Cypher query against the graph.</p> <p>Input schema: <pre><code>{\n \"type\": \"object\",\n \"properties\": {\n \"cypher\": {\n \"type\": \"string\",\n \"description\": \"Cypher query string\"\n },\n \"params\": {\n \"type\": \"object\",\n \"description\": \"Optional query parameters\",\n \"default\": {}\n }\n },\n \"required\": [\"cypher\"]\n}\n</code></pre></p> <p>Output: Array of result rows as JSON.</p> <p>Warning</p> <p>This tool executes writes as well as reads. Agents should use read-only queries (<code>MATCH</code> / <code>RETURN</code>) unless explicitly performing graph updates. Use <code>--read-only</code> mode or <code>read_only=True</code> in <code>create_mcp_server()</code> to enforce this at the server level.</p>"},{"location":"api/mcp/#get_rationale","title":"<code>get_rationale</code>","text":"<p>Return the rationale, decisions, and rules that govern a named code node.</p> <p>Input schema: <pre><code>{\n \"type\": \"object\",\n \"properties\": {\n \"name\": {\n \"type\": \"string\",\n \"description\": \"Function, class, or file name\"\n },\n \"file\": {\n \"type\": \"string\",\n \"description\": \"Optional file path to disambiguate\"\n }\n },\n \"required\": [\"name\"]\n}\n</code></pre></p> <p>Output: Array of <code>Decision</code> and <code>Rule</code> nodes with <code>rationale</code> fields, as JSON.</p>"},{"location":"api/mcp/#find_owners","title":"<code>find_owners</code>","text":"<p>Return the people and domains that own a code node, based on CODEOWNERS, annotation, and domain membership.</p> <p>Input schema: <pre><code>{\n \"type\": \"object\",\n \"properties\": {\n \"name\": {\n \"type\": \"string\",\n \"description\": \"Function, class, or file name\"\n },\n \"file\": {\n \"type\": \"string\",\n \"description\": \"Optional file path\"\n }\n },\n \"required\": [\"name\"]\n}\n</code></pre></p> <p>Output: Array of <code>Person</code> and <code>Domain</code> nodes as JSON.</p>"},{"location":"api/mcp/#search_knowledge","title":"<code>search_knowledge</code>","text":"<p>Search the knowledge layer only (concepts, rules, decisions, wiki pages, domains).</p> <p>Input schema: <pre><code>{\n \"type\": \"object\",\n \"properties\": {\n \"query\": {\n \"type\": \"string\",\n \"description\": \"Search query\"\n },\n \"limit\": {\n \"type\": \"integer\",\n \"description\": \"Maximum results to return\",\n \"default\": 20\n }\n },\n \"required\": [\"query\"]\n}\n</code></pre></p> <p>Output: Array of knowledge layer <code>ContextNode</code> objects as JSON.</p>"},{"location":"api/mcp/#blast_radius","title":"<code>blast_radius</code>","text":"<p>Return all code nodes transitively reachable from a given node via CALLS, IMPORTS, and INHERITS edges \u2014 the set of things that could break if this node changes.</p> <p>Input schema: <pre><code>{\n \"type\": \"object\",\n \"properties\": {\n \"name\": {\n \"type\": \"string\",\n \"description\": \"Starting node name\"\n },\n \"file\": {\n \"type\": \"string\",\n \"description\": \"Optional file path to disambiguate\"\n },\n \"depth\": {\n \"type\": \"integer\",\n \"description\": \"Maximum traversal depth\",\n \"default\": 3\n }\n },\n \"required\": [\"name\"]\n}\n</code></pre></p> <p>Output: Array of <code>ContextNode</code> objects ordered by distance from the starting node.</p>"},{"location":"api/mcp/#security","title":"Security","text":""},{"location":"api/mcp/#read-only-mode","title":"Read-only mode","text":"<p>Start the server in read-only mode to prevent agents from modifying the graph:</p> <pre><code>navegador mcp --read-only\n</code></pre> <p>In read-only mode: - The <code>ingest</code> tool is disabled (returns an error if called) - The <code>query</code> tool validates that queries contain only <code>MATCH</code>, <code>RETURN</code>, <code>WITH</code>, <code>WHERE</code>, <code>ORDER BY</code>, <code>LIMIT</code>, and <code>SKIP</code> clauses - Write Cypher keywords (<code>CREATE</code>, <code>MERGE</code>, <code>SET</code>, <code>DELETE</code>, <code>DETACH</code>) are rejected</p>"},{"location":"api/mcp/#query-complexity-limits","title":"Query complexity limits","text":"<p>Set a maximum query complexity to prevent runaway traversals:</p> <pre><code>navegador mcp --max-query-complexity 100\n</code></pre> <p>Or in <code>.navegador/config.toml</code>:</p> <pre><code>[mcp]\nmax_query_complexity = 100\n</code></pre> <p>Queries that exceed the complexity threshold return an error rather than executing.</p>"},{"location":"api/sdk/","title":"Python SDK Reference","text":"<p>Full API reference for the navegador Python SDK.</p> <pre><code>from navegador.graph import GraphStore\nfrom navegador.context import ContextLoader, ContextBundle, ContextNode, ContextEdge\nfrom navegador.ingest import RepoIngester, KnowledgeIngester, WikiIngester, PlanopticonIngester\n</code></pre>"},{"location":"api/sdk/#graphstore","title":"GraphStore","text":"<p>Database abstraction layer. Both SQLite and Redis backends implement this interface.</p> <pre><code>class GraphStore:\n @classmethod\n def sqlite(cls, path: str | Path = \"navegador.db\") -> \"GraphStore\": ...\n\n @classmethod\n def redis(cls, url: str = \"redis://localhost:6379\") -> \"GraphStore\": ...\n</code></pre>"},{"location":"api/sdk/#class-methods","title":"Class methods","text":""},{"location":"api/sdk/#graphstoresqlite","title":"<code>GraphStore.sqlite</code>","text":"<pre><code>@classmethod\ndef sqlite(cls, path: str | Path = \"navegador.db\") -> \"GraphStore\"\n</code></pre> <p>Open a local SQLite-backed graph. The file is created if it does not exist. Uses <code>falkordblite</code> (embedded engine \u2014 no daemon required).</p> <p>Parameters:</p> Name Type Default Description <code>path</code> <code>str \\| Path</code> <code>\"navegador.db\"</code> Path to the <code>.db</code> file <p>Returns: <code>GraphStore</code></p>"},{"location":"api/sdk/#graphstoreredis","title":"<code>GraphStore.redis</code>","text":"<pre><code>@classmethod\ndef redis(cls, url: str = \"redis://localhost:6379\") -> \"GraphStore\"\n</code></pre> <p>Connect to a Redis-backed FalkorDB instance.</p> <p>Parameters:</p> Name Type Default Description <code>url</code> <code>str</code> <code>\"redis://localhost:6379\"</code> Redis connection URL <p>Returns: <code>GraphStore</code></p>"},{"location":"api/sdk/#instance-methods","title":"Instance methods","text":""},{"location":"api/sdk/#query","title":"<code>query</code>","text":"<pre><code>def query(self, cypher: str, params: dict | None = None) -> list[dict]\n</code></pre> <p>Execute a Cypher query and return all result rows.</p> <p>Parameters:</p> Name Type Default Description <code>cypher</code> <code>str</code> \u2014 Cypher query string <code>params</code> <code>dict \\| None</code> <code>None</code> Optional query parameters <p>Returns: <code>list[dict]</code> \u2014 one dict per result row, keyed by return variable name.</p>"},{"location":"api/sdk/#create_node","title":"<code>create_node</code>","text":"<pre><code>def create_node(self, label: str, properties: dict) -> str\n</code></pre> <p>Create a new node and return its ID.</p> <p>Parameters:</p> Name Type Description <code>label</code> <code>str</code> Node label (e.g., <code>\"Function\"</code>, <code>\"Concept\"</code>) <code>properties</code> <code>dict</code> Node properties <p>Returns: <code>str</code> \u2014 node ID</p>"},{"location":"api/sdk/#merge_node","title":"<code>merge_node</code>","text":"<pre><code>def merge_node(\n self,\n label: str,\n match_properties: dict,\n set_properties: dict | None = None,\n) -> str\n</code></pre> <p>Upsert a node: create it if no node with <code>match_properties</code> exists; otherwise update <code>set_properties</code> on the existing node.</p> <p>Parameters:</p> Name Type Default Description <code>label</code> <code>str</code> \u2014 Node label <code>match_properties</code> <code>dict</code> \u2014 Properties to match on (identity key) <code>set_properties</code> <code>dict \\| None</code> <code>None</code> Properties to set on create or update <p>Returns: <code>str</code> \u2014 node ID</p>"},{"location":"api/sdk/#create_edge","title":"<code>create_edge</code>","text":"<pre><code>def create_edge(\n self,\n from_id: str,\n to_id: str,\n edge_type: str,\n properties: dict | None = None,\n) -> None\n</code></pre> <p>Create a directed edge between two nodes.</p> <p>Parameters:</p> Name Type Default Description <code>from_id</code> <code>str</code> \u2014 Source node ID <code>to_id</code> <code>str</code> \u2014 Target node ID <code>edge_type</code> <code>str</code> \u2014 Relationship type (e.g., <code>\"CALLS\"</code>, <code>\"ANNOTATES\"</code>) <code>properties</code> <code>dict \\| None</code> <code>None</code> Optional edge properties"},{"location":"api/sdk/#merge_edge","title":"<code>merge_edge</code>","text":"<pre><code>def merge_edge(\n self,\n from_label: str,\n from_match: dict,\n to_label: str,\n to_match: dict,\n edge_type: str,\n properties: dict | None = None,\n) -> None\n</code></pre> <p>Upsert an edge between two nodes matched by label and properties.</p>"},{"location":"api/sdk/#clear","title":"<code>clear</code>","text":"<pre><code>def clear(self) -> None\n</code></pre> <p>Delete all nodes and edges from the graph.</p>"},{"location":"api/sdk/#close","title":"<code>close</code>","text":"<pre><code>def close(self) -> None\n</code></pre> <p>Release the database connection. Called automatically when used as a context manager.</p>"},{"location":"api/sdk/#context-manager","title":"Context manager","text":"<p><code>GraphStore</code> implements <code>__enter__</code> / <code>__exit__</code>. Use with <code>with</code> to ensure the connection is closed:</p> <pre><code>with GraphStore.sqlite(\".navegador/navegador.db\") as store:\n results = store.query(\"MATCH (n) RETURN count(n) AS total\")\n</code></pre>"},{"location":"api/sdk/#contextloader","title":"ContextLoader","text":"<p>Builds structured context bundles from graph queries.</p> <pre><code>class ContextLoader:\n def __init__(self, store: GraphStore) -> None: ...\n</code></pre>"},{"location":"api/sdk/#methods","title":"Methods","text":""},{"location":"api/sdk/#load_file","title":"<code>load_file</code>","text":"<pre><code>def load_file(self, path: str) -> ContextBundle\n</code></pre> <p>Return the full context bundle for a source file: the file node, its modules, classes, functions, imports, and their relationships.</p> <p>Parameters:</p> Name Type Description <code>path</code> <code>str</code> Relative or absolute path to the source file <p>Returns: <code>ContextBundle</code></p>"},{"location":"api/sdk/#load_function","title":"<code>load_function</code>","text":"<pre><code>def load_function(\n self,\n name: str,\n *,\n file: str = \"\",\n depth: int = 1,\n) -> ContextBundle\n</code></pre> <p>Return a function node with its callers, callees, decorators, containing class, and source.</p> <p>Parameters:</p> Name Type Default Description <code>name</code> <code>str</code> \u2014 Function name <code>file</code> <code>str</code> <code>\"\"</code> Optional file path to disambiguate <code>depth</code> <code>int</code> <code>1</code> Call graph traversal depth (1 = direct callers/callees only) <p>Returns: <code>ContextBundle</code></p>"},{"location":"api/sdk/#load_class","title":"<code>load_class</code>","text":"<pre><code>def load_class(\n self,\n name: str,\n *,\n file: str = \"\",\n) -> ContextBundle\n</code></pre> <p>Return a class node with its methods, base classes, subclasses, and references from other files.</p> <p>Parameters:</p> Name Type Default Description <code>name</code> <code>str</code> \u2014 Class name <code>file</code> <code>str</code> <code>\"\"</code> Optional file path to disambiguate <p>Returns: <code>ContextBundle</code></p>"},{"location":"api/sdk/#explain","title":"<code>explain</code>","text":"<pre><code>def explain(\n self,\n name: str,\n *,\n file: str = \"\",\n) -> ContextBundle\n</code></pre> <p>Universal lookup: explain any node (function, class, file, concept, rule, decision) by name.</p> <p>Parameters:</p> Name Type Default Description <code>name</code> <code>str</code> \u2014 Node name or file path <code>file</code> <code>str</code> <code>\"\"</code> Optional file path to disambiguate code nodes <p>Returns: <code>ContextBundle</code></p>"},{"location":"api/sdk/#load_concept","title":"<code>load_concept</code>","text":"<pre><code>def load_concept(self, name: str) -> ContextBundle\n</code></pre> <p>Return a concept node with its rules, linked wiki pages, and annotated code nodes.</p>"},{"location":"api/sdk/#load_domain","title":"<code>load_domain</code>","text":"<pre><code>def load_domain(self, name: str) -> ContextBundle\n</code></pre> <p>Return a domain and all nodes belonging to it: concepts, rules, decisions, people, and annotated code.</p>"},{"location":"api/sdk/#search","title":"<code>search</code>","text":"<pre><code>def search(\n self,\n query: str,\n *,\n all_layers: bool = False,\n docs_only: bool = False,\n limit: int = 20,\n) -> list[ContextNode]\n</code></pre> <p>Search the graph by text query.</p> <p>Parameters:</p> Name Type Default Description <code>query</code> <code>str</code> \u2014 Search string <code>all_layers</code> <code>bool</code> <code>False</code> Search all layers including knowledge and docs <code>docs_only</code> <code>bool</code> <code>False</code> Search docstrings and wiki content only <code>limit</code> <code>int</code> <code>20</code> Maximum number of results <p>Returns: <code>list[ContextNode]</code></p>"},{"location":"api/sdk/#search_by_docstring","title":"<code>search_by_docstring</code>","text":"<pre><code>def search_by_docstring(self, query: str, *, limit: int = 20) -> list[ContextNode]\n</code></pre> <p>Search docstrings and wiki page content. Equivalent to <code>search(query, docs_only=True)</code>.</p>"},{"location":"api/sdk/#decorated_by","title":"<code>decorated_by</code>","text":"<pre><code>def decorated_by(self, decorator: str) -> list[ContextNode]\n</code></pre> <p>Find all functions and classes that use a specific decorator.</p> <p>Parameters:</p> Name Type Description <code>decorator</code> <code>str</code> Decorator name (e.g., <code>\"login_required\"</code>, <code>\"pytest.mark.parametrize\"</code>) <p>Returns: <code>list[ContextNode]</code></p>"},{"location":"api/sdk/#contextbundle","title":"ContextBundle","text":"<p>Structured result returned by <code>ContextLoader</code> methods.</p> <pre><code>@dataclass\nclass ContextBundle:\n root: ContextNode\n nodes: list[ContextNode]\n edges: list[ContextEdge]\n metadata: dict\n</code></pre>"},{"location":"api/sdk/#fields","title":"Fields","text":"Field Type Description <code>root</code> <code>ContextNode</code> The primary node (function, class, file, etc.) <code>nodes</code> <code>list[ContextNode]</code> All nodes in the bundle, including <code>root</code> <code>edges</code> <code>list[ContextEdge]</code> All edges between nodes in the bundle <code>metadata</code> <code>dict</code> Query metadata (depth, timing, node counts)"},{"location":"api/sdk/#methods_1","title":"Methods","text":""},{"location":"api/sdk/#to_json","title":"<code>to_json</code>","text":"<pre><code>def to_json(self) -> str\n</code></pre> <p>Serialize the bundle to a JSON string.</p>"},{"location":"api/sdk/#to_markdown","title":"<code>to_markdown</code>","text":"<pre><code>def to_markdown(self) -> str\n</code></pre> <p>Render the bundle as a Markdown string. Suitable for pasting into agent context.</p>"},{"location":"api/sdk/#to_dict","title":"<code>to_dict</code>","text":"<pre><code>def to_dict(self) -> dict\n</code></pre> <p>Return the bundle as a plain Python dict.</p>"},{"location":"api/sdk/#contextnode","title":"ContextNode","text":"<p>A single node in a context bundle.</p> <pre><code>@dataclass\nclass ContextNode:\n id: str\n label: str # e.g. \"Function\", \"Concept\"\n name: str\n properties: dict # all node properties from the graph\n layer: str # \"code\" or \"knowledge\"\n score: float # relevance score (search results only)\n</code></pre>"},{"location":"api/sdk/#contextedge","title":"ContextEdge","text":"<p>A relationship between two nodes in a context bundle.</p> <pre><code>@dataclass\nclass ContextEdge:\n from_id: str\n to_id: str\n edge_type: str # e.g. \"CALLS\", \"ANNOTATES\", \"INHERITS\"\n properties: dict\n</code></pre>"},{"location":"api/sdk/#ingestionresult","title":"IngestionResult","text":"<p>Returned by all ingest methods.</p> <pre><code>@dataclass\nclass IngestionResult:\n nodes_created: int\n nodes_updated: int\n edges_created: int\n files_processed: int\n errors: list[str]\n duration_seconds: float\n</code></pre> Field Type Description <code>nodes_created</code> <code>int</code> New nodes written to the graph <code>nodes_updated</code> <code>int</code> Existing nodes updated <code>edges_created</code> <code>int</code> New edges written <code>files_processed</code> <code>int</code> Source files walked <code>errors</code> <code>list[str]</code> Per-file parse errors (non-fatal) <code>duration_seconds</code> <code>float</code> Wall time for the ingest operation"},{"location":"architecture/graph-schema/","title":"Graph Schema","text":""},{"location":"architecture/graph-schema/#node-labels","title":"Node labels","text":""},{"location":"architecture/graph-schema/#code-layer","title":"Code layer","text":"Label Properties Description <code>Repository</code> <code>name</code>, <code>path</code>, <code>url</code>, <code>ingested_at</code> Top-level repo node <code>File</code> <code>path</code>, <code>language</code>, <code>lines</code>, <code>size</code> Source file <code>Module</code> <code>name</code>, <code>file</code>, <code>path</code> Python module or TS namespace <code>Class</code> <code>name</code>, <code>file</code>, <code>line</code>, <code>end_line</code>, <code>docstring</code>, <code>is_abstract</code> Class definition <code>Function</code> <code>name</code>, <code>file</code>, <code>line</code>, <code>end_line</code>, <code>signature</code>, <code>docstring</code>, <code>is_async</code> Top-level function <code>Method</code> <code>name</code>, <code>file</code>, <code>line</code>, <code>end_line</code>, <code>signature</code>, <code>docstring</code>, <code>is_async</code>, <code>is_classmethod</code>, <code>is_staticmethod</code> Class method <code>Variable</code> <code>name</code>, <code>file</code>, <code>line</code>, <code>type_annotation</code> Module-level variable <code>Import</code> <code>name</code>, <code>alias</code>, <code>file</code>, <code>line</code>, <code>from_module</code> Import statement <code>Decorator</code> <code>name</code>, <code>expression</code>, <code>file</code>, <code>line</code> Decorator applied to function/class"},{"location":"architecture/graph-schema/#knowledge-layer","title":"Knowledge layer","text":"Label Properties Description <code>Domain</code> <code>name</code>, <code>description</code>, <code>created_at</code> Top-level grouping for concepts, rules, decisions <code>Concept</code> <code>name</code>, <code>description</code>, <code>domain</code>, <code>status</code>, <code>created_at</code> Named domain concept or design pattern <code>Rule</code> <code>name</code>, <code>description</code>, <code>domain</code>, <code>severity</code>, <code>rationale</code>, <code>created_at</code> Enforceable constraint; severity: <code>info</code>, <code>warning</code>, <code>critical</code> <code>Decision</code> <code>name</code>, <code>description</code>, <code>domain</code>, <code>rationale</code>, <code>alternatives</code>, <code>date</code>, <code>status</code>, <code>created_at</code> Architectural decision record; status: <code>proposed</code>, <code>accepted</code>, <code>deprecated</code>, <code>superseded</code> <code>WikiPage</code> <code>title</code>, <code>content</code>, <code>url</code>, <code>source</code>, <code>updated_at</code> Wiki page or document <code>Person</code> <code>name</code>, <code>email</code>, <code>role</code>, <code>team</code>, <code>created_at</code> Team member or contributor"},{"location":"architecture/graph-schema/#edge-types","title":"Edge types","text":"Edge From To Meaning <code>CONTAINS</code> Repository, File, Module, Class File, Class, Function, Method, Variable, Import Structural containment <code>DEFINES</code> File, Class Class, Function, Method Definition site <code>IMPORTS</code> File Import File imports a module or symbol <code>DEPENDS_ON</code> File, Module File, Module Module-level dependency <code>CALLS</code> Function, Method Function, Method Direct function call (static analysis) <code>REFERENCES</code> Function, Method, File Variable, Class, Function Name reference (not a call) <code>INHERITS</code> Class Class Class inherits from parent <code>IMPLEMENTS</code> Class, Function Concept Code implements a concept or interface <code>DECORATES</code> Decorator Function, Method, Class Decorator applied to target <code>BELONGS_TO</code> Concept, Rule, Decision, Person Domain Membership in a domain <code>RELATED_TO</code> Any Any General semantic relationship <code>GOVERNS</code> Rule Function, Method, Class, File Rule applies to code node <code>DOCUMENTS</code> WikiPage Concept, Function, Class, File Documentation relationship <code>ANNOTATES</code> Concept, Rule Function, Method, Class, File, Module Knowledge node annotates code node <code>ASSIGNED_TO</code> Rule, Decision Person Work item or decision assigned to person <code>DECIDED_BY</code> Decision Person Decision was made by person <code>TESTS</code> Function, Method Function, Method, Class Test function exercises a source function or class <code>COUPLED_WITH</code> File, Module File, Module Files that change together frequently (from churn analysis) <code>OWNS</code> Person File, Module, Class Ownership from CODEOWNERS or annotation"},{"location":"architecture/graph-schema/#schema-diagram","title":"Schema diagram","text":"<pre><code>graph LR\n subgraph Knowledge[\"Knowledge Layer\"]\n Domain\n Concept\n Rule\n Decision\n WikiPage\n Person\n end\n\n subgraph Code[\"Code Layer\"]\n Repository\n File\n Module\n Class\n Function\n Method\n Decorator\n Import\n Variable\n end\n\n Repository -->|CONTAINS| File\n File -->|CONTAINS| Class\n File -->|CONTAINS| Function\n File -->|IMPORTS| Import\n Class -->|DEFINES| Method\n Function -->|CALLS| Function\n Method -->|CALLS| Function\n Method -->|CALLS| Method\n Class -->|INHERITS| Class\n Decorator -->|DECORATES| Function\n Decorator -->|DECORATES| Class\n Decorator -->|DECORATES| Method\n\n Domain -->|\"(inverse) BELONGS_TO\"| Concept\n Domain -->|\"(inverse) BELONGS_TO\"| Rule\n Domain -->|\"(inverse) BELONGS_TO\"| Decision\n WikiPage -->|DOCUMENTS| Concept\n WikiPage -->|DOCUMENTS| Function\n\n Rule -->|GOVERNS| Function\n Rule -->|GOVERNS| Class\n Concept -->|ANNOTATES| Function\n Concept -->|ANNOTATES| Class\n Class -->|IMPLEMENTS| Concept\n Decision -->|DECIDED_BY| Person\n Rule -->|ASSIGNED_TO| Person\n Function -->|TESTS| Function\n Function -->|TESTS| Class\n File -->|COUPLED_WITH| File\n Person -->|OWNS| File</code></pre>"},{"location":"architecture/graph-schema/#code-vs-knowledge-layer-distinction","title":"Code vs knowledge layer distinction","text":"<p>The two layers have different lifecycle and provenance:</p> Dimension Code layer Knowledge layer Source tree-sitter AST parsing Manual curation, wiki, Planopticon Refresh On every <code>navegador ingest</code> On demand (<code>navegador add</code>, <code>wiki ingest</code>, <code>planopticon ingest</code>) Change rate Fast (changes with every commit) Slow (changes with design decisions) Authorship Derived from code Human or meeting-authored Queryability Names, signatures, call graphs Domain semantics, rationale, history <p>Cross-layer edges (<code>ANNOTATES</code>, <code>GOVERNS</code>, <code>IMPLEMENTS</code>, <code>DOCUMENTS</code>) are the join points between the two layers. They are created explicitly by humans via <code>navegador annotate</code> or inferred during wiki/Planopticon ingestion.</p> <p>Analysis edges (<code>TESTS</code>, <code>COUPLED_WITH</code>) are created by analysis commands (<code>navegador testmap</code>, <code>navegador churn</code>) and are derived rather than curated. They are refreshed whenever those commands are re-run.</p> <p><code>OWNS</code> edges are populated from CODEOWNERS file parsing (<code>navegador codeowners</code>) and from explicit <code>navegador annotate --owner</code> assignments.</p>"},{"location":"architecture/overview/","title":"Architecture Overview","text":""},{"location":"architecture/overview/#design-philosophy","title":"Design philosophy","text":"<p>Navegador is built around a single observation: code structure and business knowledge are both graph-shaped, and they belong in the same graph.</p> <p>Most context tools for AI agents handle one or the other \u2014 either they parse code (AST tools, code search) or they surface docs (RAG over wikis, ADR files). Navegador stores both in the same property graph and connects them with typed edges. An agent asking \"what does <code>process_payment</code> do?\" gets back not just the function signature and call graph, but the business rules that govern it and the architectural decision that shaped its design \u2014 in a single structured query.</p>"},{"location":"architecture/overview/#architecture-layers","title":"Architecture layers","text":"<pre><code>\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 INTELLIGENCE LAYER \u2502\n\u2502 LLM providers (Anthropic \u00b7 OpenAI \u00b7 Ollama) \u2502\n\u2502 Semantic search \u00b7 NLP queries \u00b7 Doc generation \u2502\n\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n\u2502 ANALYSIS LAYER \u2502\n\u2502 Impact \u00b7 Trace \u00b7 Churn \u00b7 Deadcode \u00b7 Cycles \u00b7 Testmap \u2502\n\u2502 Diff \u00b7 Rename \u00b7 Communities \u00b7 Blast radius \u2502\n\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n\u2502 KNOWLEDGE LAYER \u2502\n\u2502 \u2502\n\u2502 Domain \u2500\u2500BELONGS_TO\u2500\u2500 Concept \u2500\u2500RELATED_TO\u2500\u2500 Rule \u2502\n\u2502 \u2502 \u2502 \u2502 \u2502\n\u2502 \u2514\u2500\u2500BELONGS_TO\u2500\u2500 Decision GOVERNS \u2502 \u2502\n\u2502 \u2502 \u2193 \u2502\n\u2502 DECIDED_BY\u2500\u2500 Person (code nodes) \u2502\n\u2502 \u2502\n\u2502 WikiPage \u2500\u2500DOCUMENTS\u2500\u2500 Concept \u2502\n\u2502 \u2502\n\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 ANNOTATES / GOVERNS / IMPLEMENTS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n\u2502 \u2502\n\u2502 CODE LAYER \u2502\n\u2502 \u2502\n\u2502 Repository \u2500\u2500CONTAINS\u2500\u2500 File \u2500\u2500CONTAINS\u2500\u2500 Class \u2502\n\u2502 \u2502 \u2502 \u2502\n\u2502 CONTAINS DEFINES \u2502\n\u2502 \u2193 \u2193 \u2502\n\u2502 Function \u2500\u2500CALLS\u2500\u2500 Function \u2502\n\u2502 \u2502 \u2502 \u2502\n\u2502 DECORATES\u2500Decorator TESTS\u2500\u2500TestFn \u2502\n\u2502 \u2502 \u2502\n\u2502 IMPORTS\u2500\u2500 Import \u2502\n\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n\u2502 ENRICHMENT LAYER \u2502\n\u2502 Framework metadata (Django \u00b7 FastAPI \u00b7 React \u00b7 Rails \u00b7 Spring) \u2502\n\u2502 VCS (Git \u00b7 Fossil) \u00b7 CODEOWNERS \u00b7 ADRs \u00b7 OpenAPI \u00b7 GraphQL \u2502\n\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n\u2502 STORE LAYER \u2502\n\u2502 FalkorDB (falkordblite SQLite local / Redis cluster) \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n</code></pre>"},{"location":"architecture/overview/#code-layer","title":"Code layer","text":"<p>Populated automatically by <code>navegador ingest</code>. Contains the structural facts extracted from source code across 13 languages: which functions exist, what they call, which classes inherit from which, what decorators are applied. Supports incremental ingestion (content hashing), watch mode, and parallel processing. This layer changes whenever code changes; re-ingest is the refresh mechanism.</p>"},{"location":"architecture/overview/#knowledge-layer","title":"Knowledge layer","text":"<p>Populated by humans (via <code>navegador add</code>) or semi-automatically (via wiki, Planopticon, ADR, OpenAPI, and PM ingestion). Contains the why: business concepts, architectural rules, recorded decisions, domain ownership, and documentation. This layer changes slowly and deliberately.</p>"},{"location":"architecture/overview/#enrichment-layer","title":"Enrichment layer","text":"<p>Framework enrichers run after AST parsing to add framework-specific metadata \u2014 Django model field types, FastAPI route paths, React component display names, etc. VCS adapters (Git, Fossil) provide blame, history, and churn data. CODEOWNERS files are parsed to populate <code>Person</code>\u2192<code>File</code> ownership edges.</p>"},{"location":"architecture/overview/#analysis-layer","title":"Analysis layer","text":"<p>Graph analysis commands operate over the populated code and knowledge layers without additional ingestion. They run Cypher traversals for impact analysis, cycle detection, dead code detection, test mapping, and community detection.</p>"},{"location":"architecture/overview/#intelligence-layer","title":"Intelligence layer","text":"<p>NLP and LLM commands (<code>navegador ask</code>, <code>navegador semantic-search</code>, <code>navegador docs</code>) use configurable LLM providers (Anthropic, OpenAI, Ollama) to answer natural language queries grounded in graph data.</p>"},{"location":"architecture/overview/#cross-layer-edges","title":"Cross-layer edges","text":"Edge Meaning Direction <code>ANNOTATES</code> A knowledge node describes a code node Concept/Rule \u2192 Function/Class/File <code>GOVERNS</code> A rule applies to a code node Rule \u2192 Function/Class <code>IMPLEMENTS</code> A code node implements a concept or interface Function/Class \u2192 Concept/Interface <code>DOCUMENTS</code> A wiki page documents a concept or code node WikiPage \u2192 Concept/Function/Class <p>These edges are created explicitly via <code>navegador annotate</code> or inferred during wiki/Planopticon ingestion when names match.</p>"},{"location":"architecture/overview/#falkordb-as-the-store","title":"FalkorDB as the store","text":"<p>Navegador uses FalkorDB \u2014 a property graph database with a Cypher query interface.</p> Environment Backend Install Local dev <code>falkordblite</code> (SQLite) Included in <code>pip install navegador</code> Production / team FalkorDB on Redis <code>pip install \"navegador[redis]\"</code> <p>Both backends implement the same <code>GraphStore</code> interface. The query path is identical; only the connection setup differs. The SQLite backend uses an embedded engine \u2014 no daemon, no port, just a <code>.db</code> file.</p>"},{"location":"architecture/overview/#ingestion-pipeline","title":"Ingestion pipeline","text":"<pre><code>Source code (13 languages via tree-sitter)\n \u2502\n \u25bc\n tree-sitter parser (per-language grammar)\n + incremental parsing (LRU cache, content hashing)\n + parallel ingestion (worker pool)\n \u2502\n \u25bc\n AST visitor (extract nodes + relationships)\n \u2502\n \u25bc\n Framework enrichers (Django \u00b7 FastAPI \u00b7 React \u00b7 Rails \u00b7 Spring \u00b7 Laravel \u00b7 \u2026)\n \u2502\n \u25bc\n Graph diffing (only write changed nodes/edges)\n \u2502\n \u25bc\n GraphStore.merge_node / create_edge\n \u2502\n \u25bc\n FalkorDB (SQLite or Redis)\n</code></pre> <pre><code>Human curation (navegador add)\nWiki pages (navegador wiki ingest)\nPlanopticon output (navegador planopticon ingest)\nADRs (navegador adr ingest)\nOpenAPI / GraphQL schemas (navegador api ingest)\nPM issues (navegador pm ingest --github)\nExternal deps (navegador deps ingest)\nSubmodules (navegador submodules ingest)\n \u2502\n \u25bc\n KnowledgeIngester / WikiIngester / PlanopticonIngester / \u2026\n \u2502\n \u25bc\n GraphStore.merge_node / create_edge\n \u2502\n \u25bc\n FalkorDB (same database)\n</code></pre> <p>All ingesters write to the same graph. There is no separate code database and knowledge database.</p>"},{"location":"architecture/overview/#query-and-analysis-path","title":"Query and analysis path","text":"<pre><code>User / agent\n \u2502\n \u25bc CLI command, MCP tool call, or Python SDK\nnavegador context / function / explain / search / impact / trace / ...\n \u2502\n \u25bc\nContextLoader / AnalysisEngine (builds Cypher query)\n \u2502\n \u25bc\nGraphStore.query(cypher) \u2190 MCP: query validation + complexity check\n \u2502\n \u25bc\nFalkorDB (SQLite or Redis)\n \u2502\n \u25bc\nContextBundle / AnalysisResult (structured result)\n \u2502\n \u25bc\nJSON / Markdown / rich terminal output\n</code></pre> <p><code>ContextLoader</code> handles context retrieval commands (<code>function</code>, <code>class</code>, <code>explain</code>, etc.). <code>AnalysisEngine</code> handles graph analysis commands (<code>impact</code>, <code>trace</code>, <code>deadcode</code>, <code>cycles</code>, <code>churn</code>, <code>testmap</code>). Both construct Cypher queries, fetch results from <code>GraphStore</code>, and return structured output. The CLI formats output for the terminal; the MCP server returns JSON; the Python SDK returns typed Python objects.</p>"},{"location":"architecture/overview/#cluster-architecture","title":"Cluster architecture","text":"<p>For team and CI environments, navegador supports a cluster mode backed by a shared Redis graph:</p> <pre><code>Multiple agents / CI runners\n \u2502\n \u25bc\n navegador (each instance)\n \u2502\n \u25bc\n \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u2502 Shared Redis \u2502\n \u2502 \u251c\u2500\u2500 FalkorDB graph (shared state) \u2502\n \u2502 \u251c\u2500\u2500 Pub/sub (event broadcast) \u2502\n \u2502 \u251c\u2500\u2500 Task queue (ingest jobs) \u2502\n \u2502 \u251c\u2500\u2500 Sessions (agent state) \u2502\n \u2502 \u251c\u2500\u2500 Checkpoints (long-running tasks) \u2502\n \u2502 \u2514\u2500\u2500 Observability (metrics, traces) \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n</code></pre> <p>Configure with <code>[cluster]</code> in <code>.navegador/config.toml</code>. See Configuration for details.</p>"},{"location":"getting-started/configuration/","title":"Configuration","text":"<p>Navegador has minimal required configuration. The only thing you typically need to set is where the graph database lives.</p>"},{"location":"getting-started/configuration/#database-path","title":"Database path","text":""},{"location":"getting-started/configuration/#sqlite-default","title":"SQLite (default)","text":"<p>By default navegador writes a <code>navegador.db</code> file in the current working directory. Override with the <code>--db</code> flag or the <code>NAVEGADOR_DB</code> environment variable:</p> <pre><code># flag (takes precedence)\nnavegador ingest ./repo --db ~/.navegador/myproject.db\n\n# environment variable\nexport NAVEGADOR_DB=~/.navegador/myproject.db\nnavegador ingest ./repo\n</code></pre> <p>The <code>.navegador/</code> directory convention keeps the database alongside your project:</p> <pre><code>my-project/\n .navegador/\n navegador.db \u2190 graph database\n src/\n ...\n</code></pre> <p>Add <code>.navegador/</code> to <code>.gitignore</code> \u2014 the database is a build artifact, not source.</p>"},{"location":"getting-started/configuration/#redis-production","title":"Redis (production)","text":"<p>For team or CI environments, point <code>NAVEGADOR_DB</code> at a Redis instance running FalkorDB:</p> <pre><code>export NAVEGADOR_DB=redis://localhost:6379\n</code></pre> <p>With authentication:</p> <pre><code>export NAVEGADOR_DB=redis://:[email protected]:6379\n</code></pre> <p>Install the Redis extra if you haven't already:</p> <pre><code>pip install \"navegador[redis]\"\n</code></pre>"},{"location":"getting-started/configuration/#sqlite-vs-redis-when-to-use-which","title":"SQLite vs Redis: when to use which","text":"SQLite (falkordblite) Redis (FalkorDB) Setup Zero config Requires a Redis server Use case Local dev, single developer Team, CI, shared context Persistence Local file Redis persistence config Performance Fast for single-user workloads Scales to large codebases Extra required None (included) <code>navegador[redis]</code> <p>Both backends implement the same <code>GraphStore</code> interface. You can migrate by re-ingesting against the new backend.</p>"},{"location":"getting-started/configuration/#github-token","title":"GitHub token","text":"<p>Required for <code>navegador wiki ingest --repo owner/repo</code> to access private wikis or to avoid rate limits on public repos.</p> <pre><code>export GITHUB_TOKEN=ghp_...\nnavegador wiki ingest --repo myorg/myrepo\n</code></pre> <p>For public repos, wiki ingestion works without a token but will hit GitHub's unauthenticated rate limit (60 req/hr).</p>"},{"location":"getting-started/configuration/#project-local-config","title":"Project-local config","text":"<p>Drop a <code>.navegador/config.toml</code> in your project root for project-specific defaults:</p> <pre><code>[database]\npath = \".navegador/navegador.db\"\n\n[ingest]\nexclude = [\"node_modules\", \"dist\", \".venv\", \"migrations\"]\nincremental = true # use content hashing by default\nredact = false # strip secrets from ingested content\n\n[mcp]\nread_only = false # set true to prevent agents from writing to the graph\nmax_query_complexity = 100 # Cypher query complexity limit\n</code></pre>"},{"location":"getting-started/configuration/#llm-provider-config","title":"LLM provider config","text":"<p>Configure LLM providers used by <code>navegador ask</code>, <code>navegador docs</code>, and <code>navegador semantic-search</code>. Requires <code>pip install \"navegador[llm]\"</code>.</p> <pre><code>[llm]\nprovider = \"anthropic\" # \"anthropic\", \"openai\", or \"ollama\"\nmodel = \"claude-3-5-haiku-20241022\"\n\n[llm.anthropic]\napi_key_env = \"ANTHROPIC_API_KEY\" # env var name (not the key itself)\n\n[llm.openai]\napi_key_env = \"OPENAI_API_KEY\"\nmodel = \"gpt-4o-mini\"\n\n[llm.ollama]\nbase_url = \"http://localhost:11434\"\nmodel = \"llama3\"\n</code></pre>"},{"location":"getting-started/configuration/#cluster-config","title":"Cluster config","text":"<p>For team deployments using a shared Redis graph with pub/sub, task queue, and session coordination:</p> <pre><code>[cluster]\nenabled = true\nredis_url = \"redis://redis.internal:6379\"\ngraph_name = \"navegador-team\"\n\n[cluster.pubsub]\nchannel = \"navegador:events\"\n\n[cluster.queue]\nname = \"navegador:tasks\"\n\n[cluster.sessions]\nttl_seconds = 3600\n</code></pre> <p>See the Cluster mode guide for full setup instructions.</p>"},{"location":"getting-started/configuration/#environment-variable-reference","title":"Environment variable reference","text":"Variable Default Description <code>NAVEGADOR_DB</code> <code>./navegador.db</code> Path to SQLite file or <code>redis://</code> URL <code>GITHUB_TOKEN</code> \u2014 GitHub personal access token for wiki ingestion <code>ANTHROPIC_API_KEY</code> \u2014 Anthropic API key for LLM features <code>OPENAI_API_KEY</code> \u2014 OpenAI API key for LLM features <code>NAVEGADOR_CONFIG</code> <code>.navegador/config.toml</code> Override config file path"},{"location":"getting-started/installation/","title":"Installation","text":""},{"location":"getting-started/installation/#requirements","title":"Requirements","text":"<ul> <li>Python 3.12 or later \u2014 required by <code>falkordblite</code>, the embedded SQLite backend</li> <li>pip 23+</li> </ul>"},{"location":"getting-started/installation/#install","title":"Install","text":"<pre><code>pip install navegador\n</code></pre> <p>This installs the core package with the SQLite backend (<code>falkordblite</code>) included. No external services are required for local use.</p>"},{"location":"getting-started/installation/#optional-extras","title":"Optional extras","text":"[sqlite][redis][languages][iac][llm]all extras <p>The default. <code>falkordblite</code> is bundled and requires no configuration. This is what <code>pip install navegador</code> already gives you.</p> <pre><code>pip install \"navegador[sqlite]\" # explicit, same as above\n</code></pre> <p>Note</p> <p><code>falkordblite</code> requires Python 3.12+. Its embedded SQLite graph engine uses features not available in earlier Python versions.</p> <p>For production deployments backed by a Redis instance running FalkorDB.</p> <pre><code>pip install \"navegador[redis]\"\n</code></pre> <p>Then point navegador at your Redis instance:</p> <pre><code>export NAVEGADOR_DB=redis://localhost:6379\nnavegador ingest ./repo\n</code></pre> <p>See Configuration for full Redis setup details.</p> <p>Additional tree-sitter grammars for Kotlin, C#, PHP, Ruby, Swift, C, and C++. The default install includes Python, TypeScript, JavaScript, Go, Rust, and Java.</p> <pre><code>pip install \"navegador[languages]\"\n</code></pre> <p>After installing, all 13 languages are parsed automatically by <code>navegador ingest</code>. No additional configuration is required.</p> <p>Infrastructure-as-Code parsers for HCL/Terraform, Puppet, Bash/Shell, Ansible, and Chef.</p> <pre><code>pip install \"navegador[iac]\"\n</code></pre> <p>After installing, <code>.tf</code>, <code>.hcl</code>, <code>.pp</code>, <code>.sh</code>, <code>.bash</code>, and <code>.zsh</code> files are parsed automatically. Ansible YAML files are detected heuristically by directory structure. Chef cookbooks are enriched via the Chef enricher on top of the Ruby parser.</p> <p>LLM provider integrations for Anthropic, OpenAI, and Ollama. Required for <code>navegador ask</code>, <code>navegador docs</code>, and <code>navegador semantic-search</code>.</p> <pre><code>pip install \"navegador[llm]\"\n</code></pre> <p>Configure the provider in <code>.navegador/config.toml</code> or via environment variables. See Configuration for details.</p> <p>Install everything at once:</p> <pre><code>pip install \"navegador[sqlite,redis,languages,iac,llm]\"\n</code></pre>"},{"location":"getting-started/installation/#verify","title":"Verify","text":"<pre><code>navegador --version\n</code></pre> <p>Expected output:</p> <pre><code>navegador, version 0.8.0\n</code></pre>"},{"location":"getting-started/installation/#shell-completions","title":"Shell completions","text":"<p>Install shell completions for tab-completion of commands and flags:</p> <pre><code>navegador completions bash >> ~/.bashrc\nnavegador completions zsh >> ~/.zshrc\nnavegador completions fish > ~/.config/fish/completions/navegador.fish\n</code></pre>"},{"location":"getting-started/installation/#python-sdk","title":"Python SDK","text":"<p>The Python SDK wraps all CLI functionality in a single <code>Navegador</code> class:</p> <pre><code>from navegador import Navegador\n\nnav = Navegador(\".navegador/navegador.db\")\nnav.ingest(\"./src\")\nbundle = nav.explain(\"AuthService\")\nprint(bundle.to_markdown())\n</code></pre> <p>The SDK is included in the base install \u2014 no extra is required.</p>"},{"location":"getting-started/installation/#development-install","title":"Development install","text":"<pre><code>git clone https://github.com/ConflictHQ/navegador\ncd navegador\npip install -e \".[sqlite,redis,languages,llm,dev]\"\n</code></pre>"},{"location":"getting-started/installation/#upgrading","title":"Upgrading","text":"<pre><code>pip install --upgrade navegador\n</code></pre> <p>After upgrading, run schema migrations first, then re-ingest to pick up new parser features:</p> <pre><code>navegador migrate # apply any schema changes from the new version\nnavegador ingest ./repo # re-ingest with incremental updates (preferred)\nnavegador ingest ./repo --clear # full rebuild if you prefer a clean slate\n</code></pre>"},{"location":"getting-started/quickstart/","title":"Quick Start","text":"<p>This guide walks from a fresh install to a fully wired agent integration in five steps.</p>"},{"location":"getting-started/quickstart/#step-1-install","title":"Step 1: Install","text":"<pre><code>pip install navegador\nnavegador --version\n</code></pre> <p>Python 3.12+ is required. For additional languages (Kotlin, C#, PHP, Ruby, Swift, C, C++) install the <code>[languages]</code> extra. See Installation for all extras and Redis setup.</p>"},{"location":"getting-started/quickstart/#step-2-ingest-a-repo","title":"Step 2: Ingest a repo","text":"<p>Point navegador at any local source tree:</p> <pre><code>navegador ingest ./my-repo\n</code></pre> <p>On first run this builds the graph from scratch. Re-run anytime to pick up changes. Use <code>--incremental</code> to skip files that haven't changed (based on content hashing \u2014 much faster on large repos):</p> <pre><code>navegador ingest ./my-repo --incremental\n</code></pre> <p>Use <code>--watch</code> to keep the graph in sync as files change:</p> <pre><code>navegador ingest ./my-repo --watch\n</code></pre> <p>Use <code>--clear</code> to wipe and rebuild from scratch:</p> <pre><code>navegador ingest ./my-repo --clear\n</code></pre> <p>Use <code>--json</code> to get a machine-readable summary of what was indexed:</p> <pre><code>navegador ingest ./my-repo --json\n</code></pre> <p>Navegador walks the tree, parses source files in 13 languages with tree-sitter, and writes nodes and edges for: files, modules, classes, functions, methods, imports, decorators, and call relationships. Framework enrichers automatically detect and annotate Django models, FastAPI routes, React components, Rails controllers, Spring Boot beans, and more.</p>"},{"location":"getting-started/quickstart/#step-3-query-the-graph","title":"Step 3: Query the graph","text":"<p>Explain anything by name \u2014 works for functions, classes, files, concepts, rules, and decisions:</p> <pre><code>navegador explain AuthService\nnavegador explain validate_token\nnavegador explain src/payments/processor.py\n</code></pre> <p>Search across code and knowledge together:</p> <pre><code>navegador search \"rate limit\" --all\nnavegador search \"authentication\" --docs --limit 10\n</code></pre> <p>Inspect a function (callers, callees, decorators, source):</p> <pre><code>navegador function validate_token\nnavegador function validate_token --depth 2 --format json\n</code></pre> <p>Inspect a class (hierarchy, methods, references):</p> <pre><code>navegador class PaymentProcessor\nnavegador class PaymentProcessor --format json\n</code></pre>"},{"location":"getting-started/quickstart/#step-4-add-business-knowledge","title":"Step 4: Add business knowledge","text":"<p>Code alone doesn't capture why. Add concepts, rules, and decisions and link them to code.</p> <p>Add a concept:</p> <pre><code>navegador add concept \"Idempotency\" \\\n --desc \"Operations that can be retried safely without side effects\" \\\n --domain Payments\n</code></pre> <p>Add a rule:</p> <pre><code>navegador add rule \"PaymentsMustBeIdempotent\" \\\n --desc \"All payment endpoints must handle duplicate submissions\" \\\n --domain Payments \\\n --severity critical \\\n --rationale \"Card networks retry on timeout; double-charging causes chargebacks\"\n</code></pre> <p>Annotate code with a concept or rule:</p> <pre><code>navegador annotate process_payment \\\n --type Function \\\n --concept Idempotency \\\n --rule PaymentsMustBeIdempotent\n</code></pre> <p>Add a decision:</p> <pre><code>navegador add decision \"UseStripeForPayments\" \\\n --desc \"Stripe is the primary payment processor\" \\\n --domain Payments \\\n --rationale \"Best fraud tooling for SaaS\" \\\n --alternatives \"Braintree, Adyen\" \\\n --date 2025-01-15 \\\n --status accepted\n</code></pre> <p>Now <code>navegador explain process_payment</code> returns code structure and the rules that govern it.</p>"},{"location":"getting-started/quickstart/#step-5-wire-an-agent-hook","title":"Step 5: Wire an agent hook","text":"<p>Use the bootstrap script to ingest your repo and install the hook for your AI coding assistant in one command:</p> <pre><code>./bootstrap.sh --repo owner/repo --wiki --agent claude\n</code></pre> <p>Options:</p> Flag Effect <code>--repo owner/repo</code> GitHub repo to clone + ingest <code>--wiki</code> Also ingest the GitHub wiki <code>--agent claude</code> Install <code>.claude/hooks/claude-hook.py</code> <code>--agent gemini</code> Install <code>.gemini/hooks/gemini-hook.py</code> <code>--agent openai</code> Install <code>openai-hook.py</code> + <code>openai-tools.json</code> <p>After bootstrap, every file the agent edits triggers a re-ingest so the graph stays in sync. See Agent Hooks for manual setup and the <code>NAVEGADOR.md</code> template.</p>"},{"location":"getting-started/quickstart/#step-6-optional-sdk-quick-start","title":"Step 6 (optional): SDK quick start","text":"<p>All CLI functionality is available through the Python SDK:</p> <pre><code>from navegador import Navegador\n\nnav = Navegador(\".navegador/navegador.db\")\n\n# ingest (incremental by default in SDK)\nnav.ingest(\"./my-repo\", incremental=True)\n\n# query\nbundle = nav.explain(\"AuthService\")\nprint(bundle.to_markdown())\n\n# analysis\nimpact = nav.impact(\"validate_token\")\nchurn = nav.churn(days=30)\ncycles = nav.cycles()\n</code></pre> <p>The <code>Navegador</code> class wraps <code>GraphStore</code>, <code>ContextLoader</code>, all ingesters, and the analysis commands into one interface.</p>"},{"location":"getting-started/quickstart/#step-7-optional-code-analysis","title":"Step 7 (optional): Code analysis","text":"<p>Once the graph is populated, use the analysis commands to understand your codebase:</p> <pre><code># impact of changing a function\nnavegador impact validate_token\n\n# trace execution flow\nnavegador trace process_payment --depth 3\n\n# find dead code\nnavegador deadcode\n\n# detect dependency cycles\nnavegador cycles\n\n# map tests to source files\nnavegador testmap\n\n# code churn (files that change most)\nnavegador churn --days 30\n\n# diff \u2014 what changed between two refs\nnavegador diff HEAD~1 HEAD\n</code></pre>"},{"location":"guide/agent-hooks/","title":"Agent Hooks","text":"<p>Agent hooks keep the navegador graph in sync as AI coding agents work. Without hooks, the graph goes stale the moment an agent edits a file. With hooks, the graph is re-ingested automatically after every file write, and architectural decisions in <code>DECISIONS.md</code> are synced into the knowledge layer.</p>"},{"location":"guide/agent-hooks/#why-hooks","title":"Why hooks","text":"<p>A stale graph gives wrong answers. If an agent adds a new function and then asks <code>navegador function</code> about a caller, the graph needs to reflect the edit. Hooks solve this by triggering <code>navegador ingest</code> on the modified files immediately after the agent writes them.</p> <p>Hooks also enforce the habit: every agent session starts with the graph as ground truth, not a stale snapshot.</p>"},{"location":"guide/agent-hooks/#claude-code","title":"Claude Code","text":""},{"location":"guide/agent-hooks/#install","title":"Install","text":"<p>The hook file lives at <code>.claude/hooks/claude-hook.py</code>. Bootstrap installs it automatically:</p> <pre><code>./bootstrap.sh --repo owner/repo --agent claude\n</code></pre> <p>To install manually, copy <code>hooks/claude-hook.py</code> from the navegador repo into your project's <code>.claude/hooks/</code> directory.</p>"},{"location":"guide/agent-hooks/#settingsjson-config","title":"settings.json config","text":"<p>In your project's <code>.claude/settings.json</code>, register the hook:</p> <pre><code>{\n \"hooks\": {\n \"PostToolUse\": [\n {\n \"matcher\": \"Write|Edit|MultiEdit\",\n \"hooks\": [\n {\n \"type\": \"command\",\n \"command\": \"python .claude/hooks/claude-hook.py\"\n }\n ]\n }\n ]\n }\n}\n</code></pre>"},{"location":"guide/agent-hooks/#what-the-hook-does","title":"What the hook does","text":"<p>On every <code>Write</code>, <code>Edit</code>, or <code>MultiEdit</code> tool call:</p> <ol> <li>Reads the list of modified file paths from the tool result</li> <li>Runs <code>navegador ingest</code> scoped to those files (fast incremental update)</li> <li>Checks for changes to <code>DECISIONS.md</code> \u2014 if found, syncs any new ADR entries into the graph as <code>Decision</code> nodes</li> <li>Logs a one-line summary to stderr (visible in Claude's tool output)</li> </ol>"},{"location":"guide/agent-hooks/#gemini-cli","title":"Gemini CLI","text":""},{"location":"guide/agent-hooks/#install_1","title":"Install","text":"<p>The hook file lives at <code>.gemini/hooks/gemini-hook.py</code>. Bootstrap installs it automatically:</p> <pre><code>./bootstrap.sh --repo owner/repo --agent gemini\n</code></pre> <p>To install manually, copy <code>hooks/gemini-hook.py</code> from the navegador repo into your project's <code>.gemini/hooks/</code> directory.</p>"},{"location":"guide/agent-hooks/#geminimd-config","title":"GEMINI.md config","text":"<p>Add to your project's <code>GEMINI.md</code>:</p> <p><pre><code>## Tool Hooks\n\nAfter writing or editing any source file, run:\n</code></pre> python .gemini/hooks/gemini-hook.py <pre><code>This keeps the navegador knowledge graph in sync. The graph is your source of truth for code structure and project decisions.\n</code></pre> <p>The Gemini CLI does not have a declarative hook registry like Claude. The <code>GEMINI.md</code> instruction tells the model to call the hook script explicitly as a tool after file writes.</p>"},{"location":"guide/agent-hooks/#openai","title":"OpenAI","text":"<p>OpenAI agents use a dispatcher script and a tool definition JSON file.</p>"},{"location":"guide/agent-hooks/#install_2","title":"Install","text":"<pre><code>./bootstrap.sh --repo owner/repo --agent openai\n</code></pre> <p>This places: - <code>openai-hook.py</code> \u2014 dispatcher script - <code>openai-tools.json</code> \u2014 tool schema for the OpenAI function-calling API</p>"},{"location":"guide/agent-hooks/#openai-toolsjson","title":"openai-tools.json","text":"<p>The tool schema exposes navegador commands as callable functions:</p> <pre><code>[\n {\n \"type\": \"function\",\n \"function\": {\n \"name\": \"navegador_explain\",\n \"description\": \"Look up any code or knowledge node by name\",\n \"parameters\": {\n \"type\": \"object\",\n \"properties\": {\n \"name\": { \"type\": \"string\", \"description\": \"Node name to explain\" },\n \"file\": { \"type\": \"string\", \"description\": \"Optional file path to disambiguate\" }\n },\n \"required\": [\"name\"]\n }\n }\n },\n {\n \"type\": \"function\",\n \"function\": {\n \"name\": \"navegador_ingest\",\n \"description\": \"Re-ingest a file or directory into the knowledge graph\",\n \"parameters\": {\n \"type\": \"object\",\n \"properties\": {\n \"path\": { \"type\": \"string\", \"description\": \"File or directory path to ingest\" }\n },\n \"required\": [\"path\"]\n }\n }\n }\n]\n</code></pre>"},{"location":"guide/agent-hooks/#dispatcher-script","title":"Dispatcher script","text":"<p><code>openai-hook.py</code> receives function call JSON on stdin and dispatches to <code>navegador</code> CLI commands:</p> <pre><code># openai-hook.py dispatches tool calls to the navegador CLI\n# usage: echo '{\"name\": \"navegador_explain\", \"arguments\": {\"name\": \"AuthService\"}}' | python openai-hook.py\n</code></pre> <p>Register <code>openai-tools.json</code> in your OpenAI assistant configuration and point function call handling at <code>openai-hook.py</code>.</p>"},{"location":"guide/agent-hooks/#navegadormd-template","title":"NAVEGADOR.md template","text":"<p>Drop a <code>NAVEGADOR.md</code> in your project root so agents know the graph exists and how to use it. Example template:</p> <pre><code># Navegador Knowledge Graph\n\nThis project has a navegador knowledge graph at `.navegador/navegador.db`.\n\n## Before editing code\n\nRun the relevant context command first:\n\n```bash\nnavegador context <file> # full file context\nnavegador function <name> # function + call graph\nnavegador class <name> # class + hierarchy\nnavegador explain <name> # anything by name\n</code></pre>"},{"location":"guide/agent-hooks/#before-adding-new-patterns","title":"Before adding new patterns","text":"<p>Check if a concept or rule already exists:</p> <pre><code>navegador search \"<topic>\" --all\nnavegador domain <domain-name>\n</code></pre>"},{"location":"guide/agent-hooks/#after-editing-code","title":"After editing code","text":"<p>The agent hook re-ingests automatically. If you disabled hooks, run:</p> <pre><code>navegador ingest ./src --clear\n</code></pre>"},{"location":"guide/agent-hooks/#key-domains","title":"Key domains","text":"<ul> <li>Payments \u2014 payment processing, billing, idempotency rules</li> <li>Auth \u2014 authentication, session management, permissions</li> <li>Infrastructure \u2014 deployment, database, caching decisions <pre><code>---\n\n## Bootstrap reference\n\n```bash\n./bootstrap.sh [options]\n</code></pre></li> </ul> Option Description <code>--repo owner/repo</code> GitHub repo to clone and ingest <code>--wiki</code> Also ingest the GitHub wiki <code>--agent claude</code> Install Claude Code hook + settings.json config <code>--agent gemini</code> Install Gemini CLI hook + GEMINI.md instruction <code>--agent openai</code> Install openai-hook.py + openai-tools.json <code>--db <path></code> Custom database path (default: <code>.navegador/navegador.db</code>)"},{"location":"guide/analysis/","title":"Structural Analysis","text":"<p>Navegador's analysis commands answer questions about how code fits together: what breaks if this function changes, where does data flow, which code is never called, and which tests cover what.</p> <p>All analysis commands work against the live graph. Run <code>navegador ingest</code> first to populate it.</p>"},{"location":"guide/analysis/#impact-analysis","title":"Impact analysis","text":"<p><code>navegador impact</code> traces the downstream effect of changing a function, class, or file. It follows <code>CALLS</code>, <code>INHERITS</code>, and <code>IMPORTS</code> edges to find everything that depends on the target \u2014 directly or transitively.</p> <pre><code>navegador impact validate_token\nnavegador impact PaymentProcessor --depth 3\nnavegador impact src/auth/service.py --format json\n</code></pre>"},{"location":"guide/analysis/#options","title":"Options","text":"Flag Effect <code>--depth N</code> How many hops to follow (default: unlimited) <code>--format json</code> Machine-readable output <code>--include-tests</code> Include test files in the impact set"},{"location":"guide/analysis/#output","title":"Output","text":"<pre><code>validate_token (Function \u2014 src/auth/service.py:42)\n Direct dependents (3):\n check_permissions src/auth/permissions.py:18\n require_auth src/auth/decorators.py:7\n middleware_auth src/middleware/auth.py:31\n\n Transitive dependents (11):\n process_payment src/payments/processor.py:56\n create_order src/orders/service.py:23\n ... (8 more)\n\n Affected files (5):\n src/auth/permissions.py\n src/auth/decorators.py\n src/middleware/auth.py\n src/payments/processor.py\n src/orders/service.py\n</code></pre>"},{"location":"guide/analysis/#use-cases","title":"Use cases","text":"<ul> <li>Before refactoring: understand the blast radius before changing a shared utility</li> <li>Code review: verify a PR's changes are limited to the expected scope</li> <li>Dependency triage: identify high-fan-out functions that deserve extra test coverage</li> </ul>"},{"location":"guide/analysis/#flow-tracing","title":"Flow tracing","text":"<p><code>navegador flow</code> traces the execution path from one function to another, returning every call chain that connects them.</p> <pre><code>navegador flow create_order process_payment\nnavegador flow handle_request save_to_db --max-paths 5\n</code></pre>"},{"location":"guide/analysis/#options_1","title":"Options","text":"Flag Effect <code>--max-paths N</code> Maximum number of paths to return (default: 3) <code>--format json</code> Machine-readable output"},{"location":"guide/analysis/#output_1","title":"Output","text":"<pre><code>Paths from create_order to process_payment:\n\nPath 1 (3 hops):\n create_order \u2192 validate_cart \u2192 charge_card \u2192 process_payment\n\nPath 2 (4 hops):\n create_order \u2192 apply_discount \u2192 charge_card \u2192 process_payment\n</code></pre>"},{"location":"guide/analysis/#use-cases_1","title":"Use cases","text":"<ul> <li>Debugging: find all code paths that reach a problematic function</li> <li>Security review: trace every path to a sensitive operation (e.g., <code>delete_user</code>, <code>transfer_funds</code>)</li> <li>Onboarding: understand how a high-level action maps to low-level implementation</li> </ul>"},{"location":"guide/analysis/#dead-code-detection","title":"Dead code detection","text":"<p><code>navegador dead-code</code> finds functions and classes that are never called, never imported, and not decorated as entry points.</p> <pre><code>navegador dead-code ./src\nnavegador dead-code ./src --exclude-tests --format json\n</code></pre>"},{"location":"guide/analysis/#options_2","title":"Options","text":"Flag Effect <code>--exclude-tests</code> Skip test files <code>--min-age-days N</code> Only report code not called in the last N days (requires git history) <code>--format json</code> Machine-readable output <code>--threshold N</code> Minimum confidence score to report (0\u2013100, default: 80)"},{"location":"guide/analysis/#output_2","title":"Output","text":"<pre><code>Potentially dead code (12 items):\n\n [Function] legacy_hash_password src/auth/legacy.py:14\n [Function] _format_receipt_v1 src/payments/receipt.py:88\n [Class] OldPaymentAdapter src/payments/adapters.py:201\n ...\n</code></pre> <p>Note</p> <p>Navegador performs static call graph analysis. Dynamic dispatch, <code>getattr</code>, and string-based imports are not traced. Review candidates before deleting them.</p>"},{"location":"guide/analysis/#use-cases_2","title":"Use cases","text":"<ul> <li>Codebase cleanup: identify safe-to-delete code before a release</li> <li>Migration audits: find old adapter classes after a library upgrade</li> </ul>"},{"location":"guide/analysis/#cycle-detection","title":"Cycle detection","text":"<p><code>navegador cycles</code> finds circular dependency chains in the call graph and import graph.</p> <pre><code>navegador cycles ./src\nnavegador cycles ./src --type imports\nnavegador cycles ./src --type calls --format json\n</code></pre>"},{"location":"guide/analysis/#options_3","title":"Options","text":"Flag Effect <code>--type calls</code> Find circular call chains (default) <code>--type imports</code> Find circular import chains <code>--type both</code> Find both <code>--min-length N</code> Only report cycles with at least N nodes (default: 2) <code>--format json</code> Machine-readable output"},{"location":"guide/analysis/#output_3","title":"Output","text":"<pre><code>Import cycles (2 found):\n\n Cycle 1 (length 3):\n src/payments/processor.py\n \u2192 src/payments/validators.py\n \u2192 src/payments/utils.py\n \u2192 src/payments/processor.py\n\n Cycle 2 (length 2):\n src/auth/service.py\n \u2192 src/auth/models.py\n \u2192 src/auth/service.py\n</code></pre>"},{"location":"guide/analysis/#use-cases_3","title":"Use cases","text":"<ul> <li>CI gate: fail builds that introduce new circular imports</li> <li>Refactoring prep: identify modules to split before a large restructure</li> </ul>"},{"location":"guide/analysis/#test-mapping","title":"Test mapping","text":"<p><code>navegador test-map</code> maps test functions to the production code they exercise, using call graph analysis.</p> <pre><code>navegador test-map ./src ./tests\nnavegador test-map ./src ./tests --target process_payment\nnavegador test-map ./src ./tests --format json\n</code></pre>"},{"location":"guide/analysis/#options_4","title":"Options","text":"Flag Effect <code>--target <name></code> Only show tests that cover a specific function <code>--uncovered</code> Show production functions with no covering tests <code>--format json</code> Machine-readable output"},{"location":"guide/analysis/#output_4","title":"Output","text":"<pre><code>Test coverage map:\n\n process_payment (src/payments/processor.py:56)\n tests/payments/test_processor.py::test_process_payment_success\n tests/payments/test_processor.py::test_process_payment_duplicate\n tests/integration/test_checkout.py::test_full_checkout_flow\n\n validate_token (src/auth/service.py:42)\n tests/auth/test_service.py::test_validate_token_valid\n tests/auth/test_service.py::test_validate_token_expired\n\nUncovered functions (4):\n legacy_hash_password src/auth/legacy.py:14\n _format_receipt_v1 src/payments/receipt.py:88\n ...\n</code></pre>"},{"location":"guide/analysis/#use-cases_4","title":"Use cases","text":"<ul> <li>Coverage by semantics, not just lines: see which tests actually call a function</li> <li>Regression targeting: when a function changes, which tests should run?</li> <li>Review prep: check that new code has corresponding tests before merging</li> </ul>"},{"location":"guide/analysis/#combining-analysis-with-knowledge","title":"Combining analysis with knowledge","text":"<p>All analysis commands understand the knowledge layer. Add <code>--include-knowledge</code> to see rules, concepts, and decisions linked to the affected nodes:</p> <pre><code>navegador impact process_payment --include-knowledge\n</code></pre> <p>Output will include knowledge nodes like:</p> <pre><code> Governed by:\n Rule: RequireIdempotencyKey (critical)\n Concept: Idempotency\n Decisions:\n UseStripeForPayments (accepted, 2025-01-15)\n</code></pre>"},{"location":"guide/analysis/#python-api","title":"Python API","text":"<pre><code>from navegador.graph import GraphStore\nfrom navegador.analysis import (\n ImpactAnalyzer,\n FlowTracer,\n DeadCodeDetector,\n CycleDetector,\n TestMapper,\n)\n\nstore = GraphStore.sqlite(\".navegador/navegador.db\")\n\n# impact analysis\nanalyzer = ImpactAnalyzer(store)\nresult = analyzer.analyze(\"validate_token\", depth=3)\nprint(result.direct_dependents)\nprint(result.transitive_dependents)\n\n# flow tracing\ntracer = FlowTracer(store)\npaths = tracer.trace(\"create_order\", \"process_payment\", max_paths=5)\nfor path in paths:\n print(\" -> \".join(path.nodes))\n\n# dead code\ndetector = DeadCodeDetector(store)\ncandidates = detector.find(\"./src\", exclude_tests=True)\nfor item in candidates:\n print(f\"{item.label}: {item.name} {item.file}:{item.line}\")\n\n# cycle detection\ncycle_detector = CycleDetector(store)\ncycles = cycle_detector.find_import_cycles(\"./src\")\nfor cycle in cycles:\n print(\" -> \".join(cycle.path))\n\n# test mapping\nmapper = TestMapper(store)\ncoverage = mapper.map(\"./src\", \"./tests\")\nfor fn, tests in coverage.items():\n print(f\"{fn}: {len(tests)} tests\")\n</code></pre> <p>See the Analysis API reference for full method signatures.</p>"},{"location":"guide/ci-cd/","title":"CI/CD Integration","text":"<p>Navegador's <code>ci</code> subcommand is designed for non-interactive use in pipelines. All CI commands emit structured output and use exit codes that CI systems understand.</p>"},{"location":"guide/ci-cd/#ci-commands","title":"CI commands","text":""},{"location":"guide/ci-cd/#navegador-ci-ingest","title":"<code>navegador ci ingest</code>","text":"<p>Ingest the repo and output a machine-readable summary. Exits non-zero on errors.</p> <pre><code>navegador ci ingest ./src\n</code></pre> <p>JSON output (always on in CI mode):</p> <pre><code>{\n \"status\": \"ok\",\n \"nodes_created\": 1240,\n \"nodes_updated\": 38,\n \"edges_created\": 4821,\n \"files_processed\": 87,\n \"errors\": [],\n \"duration_seconds\": 4.2\n}\n</code></pre>"},{"location":"guide/ci-cd/#navegador-ci-stats","title":"<code>navegador ci stats</code>","text":"<p>Print graph statistics as JSON. Use to track graph growth over time or assert a minimum coverage threshold.</p> <pre><code>navegador ci stats\n</code></pre> <pre><code>{\n \"repositories\": 1,\n \"files\": 87,\n \"classes\": 143,\n \"functions\": 891,\n \"methods\": 412,\n \"concepts\": 14,\n \"rules\": 9,\n \"decisions\": 6,\n \"total_edges\": 4821\n}\n</code></pre>"},{"location":"guide/ci-cd/#navegador-ci-check","title":"<code>navegador ci check</code>","text":"<p>Run assertion checks against the graph. Exits non-zero if any check fails.</p> <pre><code>navegador ci check\n</code></pre> <p>Checks run by default:</p> Check Condition for failure <code>no-cycles</code> Circular import chains detected <code>min-coverage</code> Functions with no tests below threshold <code>critical-rules</code> Code violates a <code>critical</code>-severity rule <code>dead-code</code> High-confidence dead code above threshold <p>Configure checks in <code>navegador.toml</code>:</p> <pre><code>[ci.checks]\nno-cycles = true\nmin-coverage = 60 # percent of functions with tests\ncritical-rules = true\ndead-code = false # disable dead-code check\n\n[ci.thresholds]\ndead_code_max = 10 # fail if more than 10 dead-code candidates\nuncovered_max_percent = 40 # fail if more than 40% of functions lack tests\n</code></pre>"},{"location":"guide/ci-cd/#running-specific-checks","title":"Running specific checks","text":"<pre><code>navegador ci check --only no-cycles\nnavegador ci check --only critical-rules,min-coverage\nnavegador ci check --skip dead-code\n</code></pre>"},{"location":"guide/ci-cd/#exit-codes","title":"Exit codes","text":"Code Meaning <code>0</code> Success \u2014 all checks passed <code>1</code> Check failure \u2014 one or more assertions failed <code>2</code> Ingest error \u2014 files could not be parsed (partial result) <code>3</code> Configuration error \u2014 bad flags or missing config <code>4</code> Connection error \u2014 cannot reach database or Redis"},{"location":"guide/ci-cd/#github-actions","title":"GitHub Actions","text":""},{"location":"guide/ci-cd/#basic-ingest-on-push","title":"Basic: ingest on push","text":"<pre><code># .github/workflows/navegador.yml\nname: navegador\n\non:\n push:\n branches: [main]\n pull_request:\n\njobs:\n graph:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n\n - name: Install navegador\n run: pip install navegador\n\n - name: Ingest\n run: navegador ci ingest ./src\n\n - name: Check\n run: navegador ci check\n</code></pre>"},{"location":"guide/ci-cd/#with-graph-caching","title":"With graph caching","text":"<p>Cache the SQLite database between runs to speed up incremental ingestion:</p> <pre><code> - name: Cache navegador graph\n uses: actions/cache@v4\n with:\n path: .navegador/navegador.db\n key: navegador-${{ runner.os }}-${{ hashFiles('src/**') }}\n restore-keys: navegador-${{ runner.os }}-\n\n - name: Ingest\n run: navegador ci ingest ./src\n\n - name: Check\n run: navegador ci check\n</code></pre>"},{"location":"guide/ci-cd/#shared-graph-via-redis-cluster-mode","title":"Shared graph via Redis (cluster mode)","text":"<p>Use a shared Redis instance for team-wide graph persistence across branches and PRs:</p> <pre><code> - name: Ingest to shared graph\n env:\n NAVEGADOR_REDIS_URL: ${{ secrets.NAVEGADOR_REDIS_URL }}\n run: |\n navegador ci ingest ./src --cluster\n navegador ci check --cluster\n</code></pre>"},{"location":"guide/ci-cd/#pr-impact-report","title":"PR impact report","text":"<p>Post an impact analysis comment on pull requests:</p> <pre><code> - name: Impact analysis\n if: github.event_name == 'pull_request'\n run: |\n CHANGED=$(git diff --name-only origin/main...HEAD | grep '\\.py$' | head -20)\n for f in $CHANGED; do\n navegador ci ingest \"$f\"\n done\n navegador impact --changed-since origin/main --format json > impact.json\n\n - name: Comment impact\n if: github.event_name == 'pull_request'\n uses: actions/github-script@v7\n with:\n script: |\n const impact = require('./impact.json')\n const body = `## Navegador impact analysis\\n\\n${impact.summary}`\n github.rest.issues.createComment({\n issue_number: context.issue.number,\n owner: context.repo.owner,\n repo: context.repo.repo,\n body\n })\n</code></pre>"},{"location":"guide/ci-cd/#editor-integration","title":"Editor integration","text":""},{"location":"guide/ci-cd/#vs-code","title":"VS Code","text":"<p>Install the Navegador VS Code extension for inline context overlays and on-save re-ingest.</p> <p>Or configure a task in <code>.vscode/tasks.json</code> to run on save:</p> <pre><code>{\n \"version\": \"2.0.0\",\n \"tasks\": [\n {\n \"label\": \"Navegador: re-ingest on save\",\n \"type\": \"shell\",\n \"command\": \"navegador ingest ${file}\",\n \"group\": \"build\",\n \"presentation\": {\n \"reveal\": \"silent\",\n \"panel\": \"shared\"\n },\n \"runOptions\": {\n \"runOn\": \"folderOpen\"\n }\n }\n ]\n}\n</code></pre>"},{"location":"guide/ci-cd/#neovim","title":"Neovim","text":"<p>Add a post-write autocmd to trigger incremental ingest:</p> <pre><code>-- in your init.lua or a plugin config\nvim.api.nvim_create_autocmd(\"BufWritePost\", {\n pattern = { \"*.py\", \"*.ts\", \"*.tsx\", \"*.js\" },\n callback = function(ev)\n local file = ev.file\n vim.fn.jobstart({ \"navegador\", \"ingest\", file }, { detach = true })\n end,\n})\n</code></pre>"},{"location":"guide/ci-cd/#pre-commit-hook","title":"Pre-commit hook","text":"<p>Run checks before committing:</p> <pre><code># .pre-commit-config.yaml\nrepos:\n - repo: local\n hooks:\n - id: navegador-check\n name: Navegador graph checks\n entry: navegador ci check --only no-cycles,critical-rules\n language: system\n pass_filenames: false\n stages: [commit]\n</code></pre>"},{"location":"guide/ci-cd/#secrets-and-auth","title":"Secrets and auth","text":"<p>The graph database path and Redis URL should come from environment variables in CI, not from committed config:</p> <pre><code># CI environment variables\nNAVEGADOR_DB=.navegador/navegador.db # SQLite path\nNAVEGADOR_REDIS_URL=redis://... # Redis URL (cluster mode)\nGITHUB_TOKEN=ghp_... # for wiki ingestion\n</code></pre> <p>Set these as GitHub Actions secrets and reference them in your workflow with <code>${{ secrets.NAME }}</code>.</p>"},{"location":"guide/cluster/","title":"Cluster Mode","text":"<p>Cluster mode lets multiple machines share a single navegador graph over Redis. Use it when your team runs large ingestion jobs, want shared context across agents and CI, or need partitioned work processing.</p>"},{"location":"guide/cluster/#prerequisites","title":"Prerequisites","text":"<ul> <li>Redis 7+ (or a managed Redis service \u2014 Upstash, Redis Cloud, etc.)</li> <li><code>pip install \"navegador[redis]\"</code></li> </ul>"},{"location":"guide/cluster/#setup","title":"Setup","text":""},{"location":"guide/cluster/#1-initialize-cluster-mode","title":"1. Initialize cluster mode","text":"<p>Point navegador at your Redis instance and run init:</p> <pre><code>navegador init --cluster --redis redis://your-redis-host:6379\n</code></pre> <p>This writes cluster config to <code>navegador.toml</code>:</p> <pre><code>[cluster]\nenabled = true\nredis_url = \"redis://your-redis-host:6379\"\ngraph_name = \"navegador\"\nnode_id = \"worker-1\" # auto-generated; override with --node-id\n</code></pre>"},{"location":"guide/cluster/#2-verify-connectivity","title":"2. Verify connectivity","text":"<pre><code>navegador cluster status\n</code></pre> <p>Output:</p> <pre><code>Cluster: connected\nRedis: redis://your-redis-host:6379\nGraph: navegador (47,231 nodes, 189,043 edges)\nWorkers: 3 online (worker-1, worker-2, ci-runner-7)\nQueue: 0 tasks pending\n</code></pre>"},{"location":"guide/cluster/#shared-graph","title":"Shared graph","text":"<p>All cluster members read from and write to the same FalkorDB graph stored in Redis. Any ingestion or annotation from any node is immediately visible to all other nodes.</p> <pre><code># on any machine in the cluster\nnavegador ingest ./src\n\n# on any other machine \u2014 sees the result immediately\nnavegador explain AuthService\n</code></pre>"},{"location":"guide/cluster/#local-snapshots","title":"Local snapshots","text":"<p>To work offline or reduce Redis round-trips, snapshot the graph to a local SQLite file:</p> <pre><code># pull a snapshot from the shared graph\nnavegador cluster snapshot --pull .navegador/local.db\n\n# use the snapshot for queries\nnavegador --db .navegador/local.db explain AuthService\n\n# push local changes back to the shared graph\nnavegador cluster snapshot --push .navegador/local.db\n</code></pre> <p>Snapshots are point-in-time copies. They do not auto-sync. Use <code>--pull</code> to refresh and <code>--push</code> to merge back.</p>"},{"location":"guide/cluster/#task-queue","title":"Task queue","text":"<p>The cluster task queue distributes ingestion and analysis jobs across workers. Instead of running <code>navegador ingest</code> directly, submit a task:</p> <pre><code># submit an ingestion task\nnavegador cluster enqueue ingest ./src --clear\n\n# submit an analysis task\nnavegador cluster enqueue analyze impact validate_token\n\n# list pending and active tasks\nnavegador cluster queue\n</code></pre> <p>Workers pick up tasks from the queue automatically. See work partitioning for multi-worker ingestion.</p>"},{"location":"guide/cluster/#starting-a-worker","title":"Starting a worker","text":"<pre><code>navegador cluster worker start\n</code></pre> <p>The worker polls the queue and processes tasks. Run one worker per machine.</p> <pre><code># run in background\nnavegador cluster worker start --daemon\n\n# stop the worker\nnavegador cluster worker stop\n</code></pre>"},{"location":"guide/cluster/#work-partitioning","title":"Work partitioning","text":"<p>For large monorepos, split ingestion across multiple workers:</p> <pre><code># partition a directory across N workers\nnavegador cluster partition ./src --workers 4\n\n# each worker then runs its assigned slice\nnavegador cluster worker start --partition 0 --of 4 # worker 0\nnavegador cluster worker start --partition 1 --of 4 # worker 1\n# ...\n</code></pre> <p>Partitioning splits the file list across workers by file count. All workers write to the same shared graph. The final graph is the union of all partitions.</p>"},{"location":"guide/cluster/#in-ci","title":"In CI","text":"<pre><code># .github/workflows/ingest.yml\njobs:\n ingest:\n strategy:\n matrix:\n partition: [0, 1, 2, 3]\n steps:\n - run: navegador cluster worker start --partition ${{ matrix.partition }} --of 4 --run-once\n</code></pre> <p><code>--run-once</code> processes the current queue and exits rather than running as a daemon.</p>"},{"location":"guide/cluster/#sessions","title":"Sessions","text":"<p>Sessions let multiple agents coordinate on the same task without interfering with each other.</p> <pre><code># start a session (returns a session ID)\nSESSION=$(navegador cluster session start --name \"feature/auth-refactor\")\necho \"Session: $SESSION\"\n\n# run commands scoped to the session\nnavegador --session $SESSION ingest ./src/auth\nnavegador --session $SESSION explain AuthService\n\n# end the session\nnavegador cluster session end $SESSION\n</code></pre> <p>Sessions create a namespaced view of the graph. Writes within a session are visible to other session members but isolated from the main graph until committed.</p> <pre><code># commit session changes to the main graph\nnavegador cluster session commit $SESSION\n\n# discard session changes\nnavegador cluster session discard $SESSION\n</code></pre>"},{"location":"guide/cluster/#locking","title":"Locking","text":"<p>For writes that must not overlap (e.g., <code>--clear</code> ingest), navegador acquires a distributed lock:</p> <pre><code>navegador ingest ./src --clear\n# automatically acquires the graph write lock; other writers block until it releases\n</code></pre> <p>You can also acquire locks explicitly:</p> <pre><code># acquire a named lock\nLOCK=$(navegador cluster lock acquire \"ingest-lock\" --ttl 300)\n\n# ... run your operations ...\n\n# release the lock\nnavegador cluster lock release $LOCK\n</code></pre> <p>Locks have a TTL (seconds) and release automatically if the holder crashes.</p>"},{"location":"guide/cluster/#messaging","title":"Messaging","text":"<p>Workers and agents can exchange messages via the cluster bus:</p> <pre><code># publish a message to a channel\nnavegador cluster publish \"ingest.complete\" '{\"repo\": \"myorg/myrepo\", \"nodes\": 12450}'\n\n# subscribe to a channel (blocks; prints messages as they arrive)\nnavegador cluster subscribe \"ingest.complete\"\n</code></pre> <p>Useful for triggering downstream steps (e.g., notify agents that a fresh ingest is ready) without polling.</p>"},{"location":"guide/cluster/#observability","title":"Observability","text":""},{"location":"guide/cluster/#cluster-metrics","title":"Cluster metrics","text":"<pre><code>navegador cluster metrics\n</code></pre> <p>Output:</p> <pre><code>Graph\n Nodes: 47,231\n Edges: 189,043\n Last ingest: 2026-03-23T14:22:11Z (worker-2)\n\nWorkers (3 online)\n worker-1 idle last seen 4s ago\n worker-2 idle last seen 2s ago\n ci-runner-7 processing task: ingest ./src/payments\n\nQueue\n Pending: 0\n Active: 1\n Completed: 847 (last 24h)\n Failed: 2 (last 24h)\n</code></pre>"},{"location":"guide/cluster/#logs","title":"Logs","text":"<p>Workers emit structured JSON logs. Stream them:</p> <pre><code>navegador cluster logs --follow\nnavegador cluster logs --worker worker-2 --since 1h\n</code></pre>"},{"location":"guide/cluster/#health-check","title":"Health check","text":"<pre><code>navegador cluster health\n# exits 0 if healthy, 1 if degraded, 2 if unavailable\n</code></pre> <p>Suitable for use in load balancer health checks and PagerDuty integrations.</p>"},{"location":"guide/cluster/#configuration-reference","title":"Configuration reference","text":"<p>All cluster settings can be set in <code>navegador.toml</code> or as environment variables:</p> Setting Env var Default Description <code>redis_url</code> <code>NAVEGADOR_REDIS_URL</code> <code>redis://localhost:6379</code> Redis connection URL <code>graph_name</code> <code>NAVEGADOR_GRAPH_NAME</code> <code>navegador</code> FalkorDB graph name <code>node_id</code> <code>NAVEGADOR_NODE_ID</code> auto Unique identifier for this worker <code>lock_ttl</code> <code>NAVEGADOR_LOCK_TTL</code> <code>300</code> Default lock TTL in seconds <code>worker_poll_interval</code> <code>NAVEGADOR_POLL_INTERVAL</code> <code>2</code> Queue poll interval in seconds <code>snapshot_dir</code> <code>NAVEGADOR_SNAPSHOT_DIR</code> <code>.navegador/snapshots</code> Local snapshot directory"},{"location":"guide/context-loading/","title":"Loading Context","text":"<p>These commands retrieve structured context from the graph. All commands support <code>--format json</code> for machine-readable output (useful in agent tool definitions) and default to rich terminal output.</p>"},{"location":"guide/context-loading/#explain-universal-lookup","title":"explain \u2014 universal lookup","text":"<p><code>explain</code> is the single command for \"what is this thing?\" It works for any node type: functions, classes, files, concepts, rules, decisions, and domains.</p> <pre><code>navegador explain AuthService\nnavegador explain validate_token\nnavegador explain src/auth/service.py\nnavegador explain PaymentsMustBeIdempotent\n</code></pre> <p>Output includes: - Node type, name, and properties - Source location and docstring (for code nodes) - Related knowledge (concepts, rules, decisions) via ANNOTATES edges - Related code (for knowledge nodes) that implements or is governed by the node</p> <pre><code>navegador explain AuthService --format json\nnavegador explain AuthService --file src/auth/service.py # disambiguate by file\n</code></pre>"},{"location":"guide/context-loading/#context-file-contents","title":"context \u2014 file contents","text":"<p>Returns everything navegador knows about a file: the file node, its modules, classes, functions, imports, and their relationships.</p> <pre><code>navegador context src/auth/service.py\nnavegador context src/auth/service.py --format json\nnavegador context src/auth/service.py --format markdown\n</code></pre> <p>Useful as a pre-edit context load: give the agent the full graph context for a file before it starts editing.</p>"},{"location":"guide/context-loading/#function-call-graph-view","title":"function \u2014 call graph view","text":"<p>Returns a function node with its callers, callees, decorators, containing class, and source.</p> <pre><code>navegador function validate_token\nnavegador function validate_token --file src/auth/service.py\nnavegador function validate_token --depth 2\nnavegador function validate_token --format json\n</code></pre> <p><code>--depth</code> controls how many hops of the call graph to traverse (default: 1). At depth 2, you get callers-of-callers and callees-of-callees.</p>"},{"location":"guide/context-loading/#class-hierarchy-and-references","title":"class \u2014 hierarchy and references","text":"<p>Returns a class node with its methods, base classes, subclasses, and references from other files.</p> <pre><code>navegador class PaymentProcessor\nnavegador class PaymentProcessor --file src/payments/processor.py\nnavegador class PaymentProcessor --format json\n</code></pre> <p>Output includes: - Class properties (file, line, docstring) - Methods with signatures - INHERITS chain (parents and children) - IMPLEMENTS edges (for abstract base classes / interfaces) - Files that import or reference this class</p>"},{"location":"guide/context-loading/#concept-knowledge-implementing-code","title":"concept \u2014 knowledge + implementing code","text":"<p>Returns a concept node with its rules, linked wiki pages, and annotated code nodes.</p> <pre><code>navegador concept Idempotency\nnavegador concept Idempotency --format json\n</code></pre> <p>Output includes: - Concept description and domain - Rules in the same domain that reference this concept - WikiPage nodes linked via DOCUMENTS - All code nodes (functions, classes, files) annotated with this concept via ANNOTATES edges</p>"},{"location":"guide/context-loading/#domain-everything-in-a-domain","title":"domain \u2014 everything in a domain","text":"<p>Returns a domain and all nodes belonging to it: concepts, rules, decisions, people, and code annotated via those knowledge nodes.</p> <pre><code>navegador domain Payments\nnavegador domain Payments --format json\n</code></pre> <p>Useful for onboarding: a new contributor can run <code>navegador domain Payments</code> to get the full business context before reading any code.</p>"},{"location":"guide/context-loading/#search-text-search-across-the-graph","title":"search \u2014 text search across the graph","text":"<pre><code>navegador search \"rate limit\"\n</code></pre> <p>By default, searches function and class names. Flags expand the scope:</p> Flag What it searches (default) Function, class, method names <code>--all</code> Names + docstrings + knowledge layer (concepts, rules, decisions, wiki) <code>--docs</code> Docstrings and wiki page content only <code>--limit N</code> Max results (default: 20) <code>--format json</code> JSON output <p>Examples:</p> <pre><code># find anything about rate limiting, anywhere\nnavegador search \"rate limit\" --all\n\n# find code with docstrings mentioning retry logic\nnavegador search \"retry\" --docs\n\n# search with a higher limit\nnavegador search \"auth\" --all --limit 50 --format json\n</code></pre>"},{"location":"guide/context-loading/#decorated-find-by-decorator","title":"decorated \u2014 find by decorator","text":"<p>Find all functions and classes that use a specific decorator:</p> <pre><code>navegador decorated login_required\nnavegador decorated pytest.mark.parametrize\nnavegador decorated --format json login_required\n</code></pre> <p>Returns function/class nodes with their file paths, line numbers, and the full decorator expression.</p>"},{"location":"guide/context-loading/#impact-blast-radius-analysis","title":"impact \u2014 blast radius analysis","text":"<p>Return the set of code nodes that could be affected if a given node changes, traversing CALLS, IMPORTS, and INHERITS edges transitively.</p> <pre><code>navegador impact validate_token\nnavegador impact validate_token --depth 3\nnavegador impact validate_token --format json\n</code></pre> <p>Useful before a refactor to understand the blast radius.</p>"},{"location":"guide/context-loading/#trace-execution-flow","title":"trace \u2014 execution flow","text":"<p>Trace the execution path through the call graph from a starting function:</p> <pre><code>navegador trace process_payment\nnavegador trace process_payment --depth 4 --format json\n</code></pre> <p>Output shows the call chain as a tree, with each node annotated by file and line.</p>"},{"location":"guide/context-loading/#diff-graph-diff-between-refs","title":"diff \u2014 graph diff between refs","text":"<p>Show what changed in the graph between two Git refs:</p> <pre><code>navegador diff HEAD~1 HEAD\nnavegador diff main feature-branch\n</code></pre> <p>Reports added, removed, and changed nodes and edges.</p>"},{"location":"guide/context-loading/#churn-code-churn-analysis","title":"churn \u2014 code churn analysis","text":"<p>Identify files and functions that change most frequently, based on Git history:</p> <pre><code>navegador churn\nnavegador churn --days 30\nnavegador churn --format json\n</code></pre> <p>High-churn nodes are often candidates for stabilization or better test coverage.</p>"},{"location":"guide/context-loading/#deadcode-find-unreachable-code","title":"deadcode \u2014 find unreachable code","text":"<p>Find functions and classes with no callers and no references from outside their defining file:</p> <pre><code>navegador deadcode\nnavegador deadcode --format json\n</code></pre>"},{"location":"guide/context-loading/#cycles-dependency-cycle-detection","title":"cycles \u2014 dependency cycle detection","text":"<p>Detect cycles in the IMPORTS and CALLS graphs:</p> <pre><code>navegador cycles\nnavegador cycles --format json\n</code></pre> <p>Reports each cycle as an ordered list of node names.</p>"},{"location":"guide/context-loading/#testmap-test-to-source-mapping","title":"testmap \u2014 test-to-source mapping","text":"<p>Map test functions to the source functions they exercise (based on naming conventions and import analysis):</p> <pre><code>navegador testmap\nnavegador testmap src/auth/service.py\nnavegador testmap --format json\n</code></pre> <p>Creates <code>TESTS</code> edges between test functions and their targets.</p>"},{"location":"guide/context-loading/#semantic-search-vector-similarity-search","title":"semantic-search \u2014 vector similarity search","text":"<p>Search using natural language against embeddings of docstrings and code. Requires <code>pip install \"navegador[llm]\"</code>.</p> <pre><code>navegador semantic-search \"functions that validate user input\"\nnavegador semantic-search \"payment retry logic\" --limit 10\n</code></pre>"},{"location":"guide/context-loading/#ask-nlp-query-interface","title":"ask \u2014 NLP query interface","text":"<p>Ask a natural language question about the codebase. Requires <code>pip install \"navegador[llm]\"</code>.</p> <pre><code>navegador ask \"What handles authentication in this codebase?\"\nnavegador ask \"Which functions touch the database?\"\n</code></pre> <p>The answer is grounded in graph queries \u2014 not hallucinated from code text.</p>"},{"location":"guide/context-loading/#rename-coordinated-rename","title":"rename \u2014 coordinated rename","text":"<p>Rename a function or class across the graph and get a list of all files that reference the old name:</p> <pre><code>navegador rename validate_token validate_access_token\n</code></pre> <p>Output is a structured change plan. The command does not modify source files \u2014 it produces the list of locations to update.</p>"},{"location":"guide/context-loading/#codeowners-ownership-queries","title":"codeowners \u2014 ownership queries","text":"<p>Query CODEOWNERS assignments and domain ownership:</p> <pre><code>navegador codeowners src/auth/service.py\nnavegador codeowners AuthService\n</code></pre> <p>Returns owning teams and people from CODEOWNERS file and from <code>Person</code> nodes annotated to the matching code nodes.</p>"},{"location":"guide/context-loading/#communities-module-cluster-detection","title":"communities \u2014 module cluster detection","text":"<p>Detect communities of highly-coupled modules using graph clustering:</p> <pre><code>navegador communities\nnavegador communities --format json\n</code></pre>"},{"location":"guide/context-loading/#explore-interactive-graph-explorer","title":"explore \u2014 interactive graph explorer","text":"<p>Open an interactive graph explorer in the terminal:</p> <pre><code>navegador explore\nnavegador explore --start AuthService\n</code></pre>"},{"location":"guide/framework-enrichment/","title":"Framework Enrichment","text":""},{"location":"guide/framework-enrichment/#what-enrichment-does","title":"What enrichment does","text":"<p>After ingestion, navegador's graph contains generic structural nodes: <code>Function</code>, <code>Class</code>, <code>File</code>, <code>Import</code>. Enrichment promotes those generic nodes to semantic types that reflect how the code is actually used.</p> <p>For example, a Django view function becomes a <code>View</code> node. A pytest function becomes a <code>Test</code> node. A Flask route decorator triggers creation of a <code>Route</code> node with the URL pattern extracted.</p> <p>This lets you ask questions that wouldn't be possible from structure alone:</p> <pre><code># without enrichment: grep for \"def test_\"\n# with enrichment: query the graph by semantic type\nnavegador query \"MATCH (t:Test) RETURN t.name, t.file ORDER BY t.file\"\n\n# find all API routes\nnavegador query \"MATCH (r:Route) RETURN r.method, r.path, r.handler ORDER BY r.path\"\n</code></pre>"},{"location":"guide/framework-enrichment/#how-it-works","title":"How it works","text":"<p>Enrichment runs as a post-ingest pass. It reads existing nodes and edges, applies framework-specific pattern matching (decorator names, base class names, naming conventions), and:</p> <ol> <li>Adds semantic labels to matched nodes (e.g., adds <code>View</code> label to Django view functions)</li> <li>Creates typed edges where the framework implies relationships (e.g., <code>HANDLES</code> from a <code>Route</code> to its handler function)</li> <li>Extracts framework-specific properties (e.g., HTTP method and URL pattern from route decorators)</li> </ol> <p>Enrichment is non-destructive \u2014 it never removes or modifies existing nodes, only adds labels and edges.</p>"},{"location":"guide/framework-enrichment/#supported-frameworks","title":"Supported frameworks","text":"Framework Language Detected patterns Semantic types added Django Python <code>View</code> subclasses, <code>urlpatterns</code>, <code>@login_required</code> <code>View</code>, <code>Route</code>, <code>Model</code>, <code>Form</code>, <code>Middleware</code> Flask Python <code>@app.route</code>, <code>@blueprint.route</code>, <code>MethodView</code> <code>Route</code>, <code>View</code>, <code>Blueprint</code> FastAPI Python <code>@router.get/post/put/delete/patch</code>, <code>APIRouter</code> <code>Route</code>, <code>Schema</code>, <code>Dependency</code> pytest Python <code>def test_*</code>, <code>@pytest.mark.*</code>, <code>conftest.py</code> <code>Test</code>, <code>Fixture</code>, <code>TestSuite</code> SQLAlchemy Python <code>Base</code> subclasses, <code>Column</code>, <code>relationship()</code> <code>Model</code>, <code>Column</code>, <code>Relation</code> Next.js TypeScript <code>pages/</code>, <code>app/</code>, <code>getServerSideProps</code> <code>Page</code>, <code>Route</code>, <code>ServerComponent</code> Express JavaScript <code>app.get/post/put/delete</code>, <code>Router</code> <code>Route</code>, <code>Middleware</code> NestJS TypeScript <code>@Controller</code>, <code>@Injectable</code>, <code>@Module</code> <code>Controller</code>, <code>Service</code>, <code>Module</code> Terraform HCL <code>main.tf</code>, <code>variables.tf</code>, <code>outputs.tf</code> Cross-file module resolution, provider grouping Chef Ruby <code>metadata.rb</code>, <code>Berksfile</code> <code>chef_recipe</code>, <code>chef_resource</code>, <code>chef_cookbook</code>, <code>chef_include</code> <p>Note</p> <p>Framework detection is automatic when <code>--framework auto</code> is used (the default). Navegador inspects imports and decorator patterns to identify which frameworks are present.</p>"},{"location":"guide/framework-enrichment/#usage","title":"Usage","text":""},{"location":"guide/framework-enrichment/#auto-detect-and-enrich-all-frameworks","title":"Auto-detect and enrich all frameworks","text":"<pre><code>navegador enrich ./src\n</code></pre> <p>This runs after ingestion and enriches everything it can detect automatically.</p>"},{"location":"guide/framework-enrichment/#enrich-immediately-after-ingestion","title":"Enrich immediately after ingestion","text":"<pre><code>navegador ingest ./src && navegador enrich ./src\n</code></pre> <p>Or use the <code>--enrich</code> flag on ingest:</p> <pre><code>navegador ingest ./src --enrich\n</code></pre>"},{"location":"guide/framework-enrichment/#target-a-specific-framework","title":"Target a specific framework","text":"<pre><code>navegador enrich ./src --framework django\nnavegador enrich ./src --framework fastapi\nnavegador enrich ./src --framework pytest\n</code></pre> <p>Valid values: <code>django</code>, <code>flask</code>, <code>fastapi</code>, <code>pytest</code>, <code>sqlalchemy</code>, <code>nextjs</code>, <code>express</code>, <code>nestjs</code>, <code>terraform</code>, <code>chef</code>, <code>auto</code> (default).</p>"},{"location":"guide/framework-enrichment/#json-output","title":"JSON output","text":"<pre><code>navegador enrich ./src --json\n</code></pre> <p>Returns a summary of labels and edges added per framework.</p>"},{"location":"guide/framework-enrichment/#querying-enriched-nodes","title":"Querying enriched nodes","text":"<p>Once enriched, the semantic types are queryable via Cypher:</p> <pre><code># all FastAPI routes with their HTTP methods\nnavegador query \"MATCH (r:Route) RETURN r.method, r.path, r.handler ORDER BY r.path\"\n\n# all SQLAlchemy models and their columns\nnavegador query \"MATCH (m:Model)-[:HAS_COLUMN]->(c:Column) RETURN m.name, c.name, c.type ORDER BY m.name\"\n\n# all pytest tests that reference a specific function\nnavegador query \"MATCH (t:Test)-[:CALLS]->(f:Function {name: 'process_payment'}) RETURN t.name, t.file\"\n\n# all Django views governed by a rule\nnavegador query \"MATCH (r:Rule)-[:GOVERNS]->(v:View) RETURN r.name, v.name, v.file\"\n</code></pre>"},{"location":"guide/framework-enrichment/#adding-custom-enrichers","title":"Adding custom enrichers","text":"<p>Enrichers are subclasses of <code>FrameworkEnricher</code>. Create one to add support for an internal framework or library.</p>"},{"location":"guide/framework-enrichment/#1-create-the-enricher","title":"1. Create the enricher","text":"<pre><code># myproject/enrichers/celery.py\nfrom navegador.enrichment.base import FrameworkEnricher, EnrichmentResult\nfrom navegador.graph import GraphStore\n\nclass CeleryEnricher(FrameworkEnricher):\n name = \"celery\"\n\n def detect(self, store: GraphStore) -> bool:\n \"\"\"Return True if this framework is present in the graph.\"\"\"\n results = store.query(\n \"MATCH (i:Import {name: 'celery'}) RETURN count(i) AS n\"\n )\n return results[0][\"n\"] > 0\n\n def enrich(self, store: GraphStore) -> EnrichmentResult:\n \"\"\"Add semantic labels and edges for Celery tasks.\"\"\"\n # Find functions decorated with @shared_task or @app.task\n tasks = store.query(\n \"MATCH (d:Decorator)-[:DECORATES]->(f:Function) \"\n \"WHERE d.name IN ['shared_task', 'task'] \"\n \"RETURN f.id, f.name\"\n )\n labels_added = 0\n for row in tasks:\n store.query(\n \"MATCH (f:Function) WHERE id(f) = $id \"\n \"SET f:Task\",\n params={\"id\": row[\"f.id\"]}\n )\n labels_added += 1\n\n return EnrichmentResult(labels_added=labels_added, edges_added=0)\n</code></pre>"},{"location":"guide/framework-enrichment/#2-register-the-enricher","title":"2. Register the enricher","text":"<pre><code># myproject/enrichers/__init__.py\nfrom navegador.enrichment.registry import register_enricher\nfrom .celery import CeleryEnricher\n\nregister_enricher(CeleryEnricher())\n</code></pre>"},{"location":"guide/framework-enrichment/#3-load-at-startup","title":"3. Load at startup","text":"<p>Import the registration module before running enrichment. In a CLI wrapper or agent hook:</p> <pre><code>import myproject.enrichers # registers the enricher\nfrom navegador.enrichment import run_enrichment\nfrom navegador.graph import GraphStore\n\nstore = GraphStore.sqlite(\".navegador/navegador.db\")\nresult = run_enrichment(store, framework=\"celery\")\nprint(f\"Added {result.labels_added} labels\")\n</code></pre> <p>Or pass the module path to the CLI via <code>NAVEGADOR_ENRICHERS</code>:</p> <pre><code>NAVEGADOR_ENRICHERS=myproject.enrichers navegador enrich ./src --framework celery\n</code></pre>"},{"location":"guide/graph-queries/","title":"Graph Queries","text":""},{"location":"guide/graph-queries/#raw-cypher-passthrough","title":"Raw Cypher passthrough","text":"<p>Every high-level command is built on Cypher queries against FalkorDB. You can drop to raw Cypher for anything the built-in commands don't cover:</p> <pre><code>navegador query \"MATCH (f:Function) RETURN f.name, f.file LIMIT 10\"\n</code></pre> <p>Results are printed as a table to stdout. Pipe with <code>--format json</code> if you need machine-readable output:</p> <pre><code>navegador query \"MATCH (f:Function) RETURN f.name, f.file\" --format json\n</code></pre> <p>Warning</p> <p><code>navegador query</code> executes writes as well as reads. Use <code>MATCH</code> / <code>RETURN</code> for inspection. Use <code>CREATE</code> / <code>MERGE</code> / <code>DELETE</code> only if you know what you're doing \u2014 there is no undo.</p>"},{"location":"guide/graph-queries/#useful-example-queries","title":"Useful example queries","text":""},{"location":"guide/graph-queries/#find-all-functions-decorated-with-login_required","title":"Find all functions decorated with <code>@login_required</code>","text":"<pre><code>MATCH (d:Decorator {name: \"login_required\"})-[:DECORATES]->(f:Function)\nRETURN f.name, f.file, f.line\nORDER BY f.file\n</code></pre>"},{"location":"guide/graph-queries/#find-all-functions-in-a-specific-file","title":"Find all functions in a specific file","text":"<pre><code>MATCH (file:File {path: \"src/auth/service.py\"})-[:CONTAINS]->(f:Function)\nRETURN f.name, f.line, f.signature\n</code></pre>"},{"location":"guide/graph-queries/#find-everything-a-function-calls-two-hops","title":"Find everything a function calls (two hops)","text":"<pre><code>MATCH (f:Function {name: \"process_payment\"})-[:CALLS*1..2]->(callee:Function)\nRETURN DISTINCT callee.name, callee.file\n</code></pre>"},{"location":"guide/graph-queries/#find-all-callers-of-a-function","title":"Find all callers of a function","text":"<pre><code>MATCH (caller:Function)-[:CALLS]->(f:Function {name: \"validate_token\"})\nRETURN caller.name, caller.file\n</code></pre>"},{"location":"guide/graph-queries/#find-all-rules-in-a-domain","title":"Find all rules in a domain","text":"<pre><code>MATCH (d:Domain {name: \"Payments\"})<-[:BELONGS_TO]-(r:Rule)\nRETURN r.name, r.severity, r.description\nORDER BY r.severity\n</code></pre>"},{"location":"guide/graph-queries/#find-all-concepts-implemented-by-code-in-a-file","title":"Find all concepts implemented by code in a file","text":"<pre><code>MATCH (file:File {path: \"src/payments/processor.py\"})-[:CONTAINS]->(f)\n -[:ANNOTATES]-(c:Concept)\nRETURN DISTINCT c.name, c.description\n</code></pre>"},{"location":"guide/graph-queries/#find-all-decisions-that-relate-to-a-domain","title":"Find all decisions that relate to a domain","text":"<pre><code>MATCH (d:Domain {name: \"Infrastructure\"})<-[:BELONGS_TO]-(dec:Decision)\nRETURN dec.name, dec.status, dec.date, dec.rationale\nORDER BY dec.date DESC\n</code></pre>"},{"location":"guide/graph-queries/#find-classes-that-inherit-from-a-base-class","title":"Find classes that inherit from a base class","text":"<pre><code>MATCH (child:Class)-[:INHERITS]->(parent:Class {name: \"BaseProcessor\"})\nRETURN child.name, child.file\n</code></pre>"},{"location":"guide/graph-queries/#find-the-full-inheritance-chain-for-a-class","title":"Find the full inheritance chain for a class","text":"<pre><code>MATCH path = (c:Class {name: \"StripeProcessor\"})-[:INHERITS*]->(ancestor)\nRETURN [node IN nodes(path) | node.name] AS hierarchy\n</code></pre>"},{"location":"guide/graph-queries/#find-wiki-pages-that-document-a-concept","title":"Find wiki pages that document a concept","text":"<pre><code>MATCH (wp:WikiPage)-[:DOCUMENTS]->(c:Concept {name: \"Idempotency\"})\nRETURN wp.title, wp.url\n</code></pre>"},{"location":"guide/graph-queries/#find-all-functions-annotated-with-a-specific-rule","title":"Find all functions annotated with a specific rule","text":"<pre><code>MATCH (r:Rule {name: \"RequireIdempotencyKey\"})-[:GOVERNS]->(f:Function)\nRETURN f.name, f.file, f.line\n</code></pre>"},{"location":"guide/graph-queries/#find-what-a-person-is-assigned-to","title":"Find what a person is assigned to","text":"<pre><code>MATCH (p:Person {name: \"Alice Chen\"})<-[:ASSIGNED_TO]-(item)\nRETURN labels(item)[0] AS type, item.name\n</code></pre>"},{"location":"guide/graph-queries/#navegador-stats","title":"navegador stats","text":"<p>Get a high-level count of everything in the graph:</p> <pre><code>navegador stats\nnavegador stats --json\n</code></pre> <p>Output breakdown:</p> Metric What it counts Repositories <code>Repository</code> nodes Files <code>File</code> nodes Classes <code>Class</code> nodes Functions + Methods <code>Function</code> + <code>Method</code> nodes combined Decorators <code>Decorator</code> nodes Imports <code>Import</code> nodes Domains <code>Domain</code> nodes Concepts <code>Concept</code> nodes Rules <code>Rule</code> nodes Decisions <code>Decision</code> nodes People <code>Person</code> nodes WikiPages <code>WikiPage</code> nodes Total edges All relationship edges <p>Use <code>--json</code> to feed stats into CI dashboards or coverage checks.</p>"},{"location":"guide/ingestion/","title":"Ingesting a Repo","text":"<p>Navegador builds the graph from four sources: code, manual knowledge curation, GitHub wikis, and Planopticon knowledge graph output.</p>"},{"location":"guide/ingestion/#code-ingestion","title":"Code ingestion","text":"<pre><code>navegador ingest ./repo\n</code></pre>"},{"location":"guide/ingestion/#what-gets-extracted","title":"What gets extracted","text":"<p>Navegador walks all source files in the repo and uses tree-sitter to extract structure. Supported languages:</p> Extension(s) Language Extra <code>.py</code> Python \u2014 <code>.ts</code>, <code>.tsx</code> TypeScript \u2014 <code>.js</code>, <code>.jsx</code> JavaScript \u2014 <code>.go</code> Go \u2014 <code>.rs</code> Rust \u2014 <code>.java</code> Java \u2014 <code>.kt</code>, <code>.kts</code> Kotlin <code>[languages]</code> <code>.cs</code> C# <code>[languages]</code> <code>.php</code> PHP <code>[languages]</code> <code>.rb</code> Ruby <code>[languages]</code> <code>.swift</code> Swift <code>[languages]</code> <code>.c</code>, <code>.h</code> C <code>[languages]</code> <code>.cpp</code>, <code>.cc</code>, <code>.cxx</code>, <code>.hpp</code> C++ <code>[languages]</code> <p>Infrastructure-as-Code:</p> Extension(s) Language Extra <code>.tf</code>, <code>.hcl</code> HCL / Terraform <code>[iac]</code> <code>.pp</code> Puppet <code>[iac]</code> <code>.sh</code>, <code>.bash</code>, <code>.zsh</code> Bash / Shell <code>[iac]</code> <code>.yml</code>, <code>.yaml</code> Ansible <code>[iac]</code> (heuristic detection) <code>.rb</code> (in Chef cookbooks) Chef <code>[iac]</code> (enricher on Ruby parser) <p>Ansible files are not matched by extension \u2014 navegador detects them by directory structure (<code>roles/</code>, <code>playbooks/</code>, <code>group_vars/</code>, <code>host_vars/</code>) or content (<code>hosts:</code> + <code>tasks:</code> keys). Chef uses the existing Ruby parser; the Chef enricher promotes nodes with Chef-specific semantic types.</p> <p>Install language and IaC support:</p> <pre><code>pip install \"navegador[languages]\"\npip install \"navegador[iac]\"\n</code></pre> <p>The following directories are always skipped: <code>.git</code>, <code>.venv</code>, <code>venv</code>, <code>node_modules</code>, <code>__pycache__</code>, <code>dist</code>, <code>build</code>, <code>.next</code>, <code>target</code> (Rust/Java builds), <code>vendor</code> (Go modules), <code>.gradle</code>.</p>"},{"location":"guide/ingestion/#what-gets-extracted_1","title":"What gets extracted","text":"What Graph nodes / edges created Files <code>File</code> node; <code>CONTAINS</code> edge from <code>Repository</code> Classes, structs, interfaces <code>Class</code> node with <code>name</code>, <code>file</code>, <code>line</code>, <code>docstring</code> Functions and methods <code>Function</code> / <code>Method</code> nodes with <code>name</code>, <code>docstring</code>, <code>line</code> Imports / use declarations <code>Import</code> node; <code>IMPORTS</code> edge from the importing file Call relationships <code>CALLS</code> edges between functions based on static call analysis Inheritance <code>INHERITS</code> edges from subclass to parent <p>Doc comment formats supported per language: Python docstrings, JSDoc (<code>/** */</code>), Rust <code>///</code>, Java Javadoc.</p>"},{"location":"guide/ingestion/#iac-extraction","title":"IaC extraction","text":"<p>IaC parsers map infrastructure constructs to the standard node labels with a <code>semantic_type</code> property for specificity:</p> Language Construct Node label <code>semantic_type</code> Terraform <code>resource</code> <code>Class</code> <code>terraform_resource</code> Terraform <code>variable</code> / <code>output</code> / <code>locals</code> <code>Variable</code> <code>terraform_variable</code> / <code>terraform_output</code> / <code>terraform_local</code> Terraform <code>module</code> <code>Module</code> <code>terraform_module</code> Terraform <code>data</code> / <code>provider</code> <code>Class</code> <code>terraform_data</code> / <code>terraform_provider</code> Puppet <code>class</code> / <code>define</code> / <code>node</code> <code>Class</code> <code>puppet_class</code> / <code>puppet_defined_type</code> / <code>puppet_node</code> Puppet resource declaration <code>Function</code> <code>puppet_resource</code> Puppet <code>include</code> <code>Import</code> <code>puppet_include</code> Ansible playbook file <code>Module</code> <code>ansible_playbook</code> Ansible play <code>Class</code> <code>ansible_play</code> Ansible task / handler <code>Function</code> <code>ansible_task</code> / <code>ansible_handler</code> Ansible role <code>Import</code> <code>ansible_role</code> Bash function <code>Function</code> <code>shell_function</code> Bash variable <code>Variable</code> <code>shell_variable</code> Bash <code>source</code> / <code>.</code> <code>Import</code> <code>shell_source</code> <p>Cross-references are extracted where possible: Terraform <code>var.x</code>, <code>module.x</code>, and resource-to-resource dependencies become <code>REFERENCES</code> / <code>DEPENDS_ON</code> edges. Ansible <code>notify:</code> keys create <code>CALLS</code> edges to handlers. Puppet <code>include</code> creates <code>IMPORTS</code> edges.</p>"},{"location":"guide/ingestion/#options","title":"Options","text":"Flag Effect <code>--clear</code> Wipe the graph before ingesting (full rebuild) <code>--incremental</code> Only reprocess files whose content hash has changed <code>--watch</code> Keep running and re-ingest on file changes <code>--redact</code> Strip secrets (tokens, passwords, keys) from string literals <code>--monorepo</code> Traverse workspace sub-packages (Turborepo, Nx, Yarn, pnpm, Cargo, Go) <code>--json</code> Output a JSON summary of nodes and edges created <code>--db <path></code> Use a specific database file"},{"location":"guide/ingestion/#re-ingesting","title":"Re-ingesting","text":"<p>Re-run <code>navegador ingest</code> anytime to pick up changes. Nodes are upserted by identity (file path + name), so repeated ingestion is idempotent for unchanged nodes. Use <code>--incremental</code> for large repos to skip unchanged files. Use <code>--clear</code> when you need a clean slate (e.g., after a large rename refactor).</p>"},{"location":"guide/ingestion/#incremental-ingestion","title":"Incremental ingestion","text":"<p><code>--incremental</code> uses SHA-256 content hashing to skip files that haven't changed since the last ingest. The hash is stored on each <code>File</code> node. On large repos this can reduce ingest time by 90%+ after the initial run.</p> <pre><code>navegador ingest ./repo --incremental\n</code></pre>"},{"location":"guide/ingestion/#watch-mode","title":"Watch mode","text":"<p><code>--watch</code> starts a file-system watcher and automatically re-ingests any file that changes:</p> <pre><code>navegador ingest ./repo --watch\n</code></pre> <p>Press <code>Ctrl-C</code> to stop. Watch mode uses <code>--incremental</code> automatically.</p>"},{"location":"guide/ingestion/#sensitive-content-redaction","title":"Sensitive content redaction","text":"<p><code>--redact</code> scans string literals for patterns that look like API keys, tokens, and passwords, and replaces their values with <code>[REDACTED]</code> in the graph. Source files are never modified.</p> <pre><code>navegador ingest ./repo --redact\n</code></pre>"},{"location":"guide/ingestion/#monorepo-support","title":"Monorepo support","text":"<p><code>--monorepo</code> detects the workspace type and traverses all sub-packages:</p> <pre><code>navegador ingest ./monorepo --monorepo\n</code></pre> <p>Supported workspace formats: Turborepo, Nx, Yarn workspaces, pnpm workspaces, Cargo workspaces, Go workspaces.</p>"},{"location":"guide/ingestion/#knowledge-curation","title":"Knowledge curation","text":"<p>Manual knowledge is added with <code>navegador add</code> commands and linked to code with <code>navegador annotate</code>.</p>"},{"location":"guide/ingestion/#concepts","title":"Concepts","text":"<p>A concept is a named idea or design pattern relevant to the codebase.</p> <pre><code>navegador add concept \"Idempotency\" \\\n --desc \"Operations safe to retry without side effects\" \\\n --domain Payments\n</code></pre>"},{"location":"guide/ingestion/#rules","title":"Rules","text":"<p>A rule is an enforceable constraint on code behaviour.</p> <pre><code>navegador add rule \"RequireIdempotencyKey\" \\\n --desc \"All write endpoints must accept an idempotency key header\" \\\n --domain Payments \\\n --severity critical \\\n --rationale \"Prevents double-processing on client retries\"\n</code></pre> <p>Severity values: <code>info</code>, <code>warning</code>, <code>critical</code>.</p>"},{"location":"guide/ingestion/#decisions","title":"Decisions","text":"<p>An architectural decision record (ADR) stored in the graph.</p> <pre><code>navegador add decision \"UsePostgresForTransactions\" \\\n --desc \"PostgreSQL is the primary datastore for transactional data\" \\\n --domain Infrastructure \\\n --rationale \"ACID guarantees required for financial data\" \\\n --alternatives \"MySQL, CockroachDB\" \\\n --date 2025-03-01 \\\n --status accepted\n</code></pre> <p>Status values: <code>proposed</code>, <code>accepted</code>, <code>deprecated</code>, <code>superseded</code>.</p>"},{"location":"guide/ingestion/#people","title":"People","text":"<pre><code>navegador add person \"Alice Chen\" \\\n --email [email protected] \\\n --role \"Lead Engineer\" \\\n --team Payments\n</code></pre>"},{"location":"guide/ingestion/#domains","title":"Domains","text":"<p>Domains are top-level groupings for concepts, rules, and decisions.</p> <pre><code>navegador add domain \"Payments\" \\\n --desc \"Everything related to payment processing and billing\"\n</code></pre>"},{"location":"guide/ingestion/#annotating-code","title":"Annotating code","text":"<p>Link a code node to a concept or rule:</p> <pre><code>navegador annotate process_payment \\\n --type Function \\\n --concept Idempotency \\\n --rule RequireIdempotencyKey\n</code></pre> <p><code>--type</code> accepts: <code>Function</code>, <code>Class</code>, <code>Method</code>, <code>File</code>, <code>Module</code>.</p> <p>This creates <code>ANNOTATES</code> edges between the knowledge nodes and the code node. The code node then appears in results for <code>navegador concept Idempotency</code> and <code>navegador explain process_payment</code>.</p>"},{"location":"guide/ingestion/#wiki-ingestion","title":"Wiki ingestion","text":"<p>Pull a GitHub wiki into the graph as <code>WikiPage</code> nodes.</p> <pre><code># ingest from GitHub API\nnavegador wiki ingest --repo myorg/myrepo --token $GITHUB_TOKEN\n\n# ingest from a locally cloned wiki directory\nnavegador wiki ingest --dir ./myrepo.wiki\n\n# force API mode (bypass auto-detection)\nnavegador wiki ingest --repo myorg/myrepo --api\n</code></pre> <p>Each wiki page becomes a <code>WikiPage</code> node with <code>title</code>, <code>content</code>, <code>url</code>, and <code>updated_at</code> properties. Pages are linked to relevant <code>Concept</code>, <code>Domain</code>, or <code>Function</code> nodes with <code>DOCUMENTS</code> edges where names match.</p> <p>Set <code>GITHUB_TOKEN</code> in your environment to avoid rate limits and to access private wikis.</p>"},{"location":"guide/ingestion/#planopticon-ingestion","title":"Planopticon ingestion","text":"<p>Planopticon is a video/meeting knowledge extraction tool. It produces structured knowledge graph output that navegador can ingest directly.</p> <pre><code>navegador planopticon ingest ./meeting-output/ --type auto\n</code></pre> <p>See the Planopticon guide for the full input format reference and entity mapping details.</p>"},{"location":"guide/intelligence/","title":"Intelligence Layer","text":"<p>The intelligence layer adds capabilities that go beyond structural graph queries: semantic similarity search, community detection, natural language queries, and documentation generation. These features require an LLM provider or embedding model.</p>"},{"location":"guide/intelligence/#setup","title":"Setup","text":"<p>Install the intelligence extras:</p> <pre><code>pip install \"navegador[intelligence]\"\n</code></pre> <p>Configure your LLM provider:</p> OpenAIAnthropicLocal (Ollama) <pre><code>export NAVEGADOR_LLM_PROVIDER=openai\nexport OPENAI_API_KEY=sk-...\n</code></pre> <pre><code>export NAVEGADOR_LLM_PROVIDER=anthropic\nexport ANTHROPIC_API_KEY=sk-ant-...\n</code></pre> <pre><code>export NAVEGADOR_LLM_PROVIDER=ollama\nexport NAVEGADOR_LLM_MODEL=llama3.2\n# no API key required; Ollama must be running on localhost:11434\n</code></pre> <p>Or set via <code>navegador.toml</code>:</p> <pre><code>[intelligence]\nprovider = \"openai\"\nmodel = \"text-embedding-3-small\" # embedding model\nllm_model = \"gpt-4o-mini\" # generation model\n</code></pre>"},{"location":"guide/intelligence/#semantic-search","title":"Semantic search","text":"<p>Standard <code>navegador search</code> matches on names and text. Semantic search matches on meaning: a query for \"billing\" finds code about \"invoices\", \"subscriptions\", and \"charges\" even when those words don't appear in the query.</p> <pre><code>navegador search \"handle payment failure\" --semantic\nnavegador search \"retry with backoff\" --semantic --limit 10\nnavegador search \"authentication middleware\" --semantic --all\n</code></pre> <p>Note</p> <p>First run after ingestion builds the embedding index. This may take a minute on large codebases. The index is cached in the database and updated incrementally on re-ingest.</p>"},{"location":"guide/intelligence/#combining-semantic-and-text-search","title":"Combining semantic and text search","text":"<pre><code># semantic search scoped to a domain\nnavegador search \"billing failure\" --semantic --domain Payments\n\n# hybrid: semantic ranking with text pre-filter\nnavegador search \"rate limit\" --semantic --all\n</code></pre>"},{"location":"guide/intelligence/#python-api","title":"Python API","text":"<pre><code>from navegador.graph import GraphStore\nfrom navegador.intelligence import SemanticSearch\n\nstore = GraphStore.sqlite(\".navegador/navegador.db\")\nsearch = SemanticSearch(store)\n\nresults = search.query(\"handle payment failure\", limit=10)\nfor node in results:\n print(f\"[{node.label}] {node.name} score={node.score:.3f}\")\n</code></pre>"},{"location":"guide/intelligence/#community-detection","title":"Community detection","text":"<p>Community detection groups nodes in the graph into clusters based on structural connectivity. Clusters typically correspond to logical modules or subsystems, even when the code doesn't explicitly organize itself that way.</p> <pre><code>navegador intelligence communities\nnavegador intelligence communities --algorithm louvain\nnavegador intelligence communities --format json\n</code></pre>"},{"location":"guide/intelligence/#algorithms","title":"Algorithms","text":"Algorithm Best for <code>louvain</code> (default) Large codebases; fast and produces stable clusters <code>leiden</code> Higher modularity; slower <code>label-propagation</code> Very large graphs"},{"location":"guide/intelligence/#output","title":"Output","text":"<pre><code>Detected 6 communities:\n\n Community 1 (47 nodes) \u2014 suggested name: Auth\n Core: AuthService, validate_token, JWTManager\n Files: src/auth/ (12 files)\n\n Community 2 (38 nodes) \u2014 suggested name: Payments\n Core: PaymentProcessor, charge_card, StripeClient\n Files: src/payments/ (9 files)\n\n Community 3 (22 nodes) \u2014 suggested name: Orders\n Core: OrderService, create_order, OrderRepository\n Files: src/orders/ (6 files)\n ...\n</code></pre> <p>Suggested names are generated by the LLM by inspecting the core nodes of each cluster.</p>"},{"location":"guide/intelligence/#persisting-communities","title":"Persisting communities","text":"<pre><code>navegador intelligence communities --save\n</code></pre> <p>This writes <code>Community</code> nodes and <code>MEMBER_OF</code> edges into the graph, making communities queryable:</p> <pre><code>navegador query \"MATCH (f:Function)-[:MEMBER_OF]->(c:Community) WHERE c.name = 'Payments' RETURN f.name, f.file\"\n</code></pre>"},{"location":"guide/intelligence/#natural-language-queries","title":"Natural language queries","text":"<p><code>navegador ask</code> lets you query the graph in plain English. The LLM translates your question into Cypher, executes it, and returns a natural language answer with citations.</p> <pre><code>navegador ask \"Which functions call process_payment?\"\nnavegador ask \"What rules govern the Payments domain?\"\nnavegador ask \"Show me all classes that inherit from BaseProcessor\"\nnavegador ask \"What decisions have been made about the database?\"\n</code></pre>"},{"location":"guide/intelligence/#how-it-works","title":"How it works","text":"<ol> <li>Your question is embedded and matched against graph schema context</li> <li>The LLM generates a Cypher query based on the question and schema</li> <li>Navegador executes the query against the graph</li> <li>The LLM formats the results as a natural language answer with source references</li> </ol>"},{"location":"guide/intelligence/#safety","title":"Safety","text":"<p>Generated queries are run in read-only mode. Write operations (<code>CREATE</code>, <code>MERGE</code>, <code>DELETE</code>, <code>SET</code>) in generated queries are blocked.</p> <pre><code># show the generated Cypher without executing\nnavegador ask \"Which functions have no tests?\" --show-query\n\n# execute and show both Cypher and answer\nnavegador ask \"Which functions have no tests?\" --verbose\n</code></pre>"},{"location":"guide/intelligence/#documentation-generation","title":"Documentation generation","text":"<p><code>navegador docgen</code> generates or improves docstrings for undocumented functions and classes, using graph context to produce accurate, context-aware descriptions.</p> <pre><code># generate docs for all undocumented functions in a file\nnavegador docgen src/payments/processor.py\n\n# generate docs for a specific function\nnavegador docgen --target process_payment\n\n# dry run: show what would be generated without writing\nnavegador docgen src/payments/processor.py --dry-run\n\n# write directly to source files\nnavegador docgen src/payments/processor.py --write\n</code></pre>"},{"location":"guide/intelligence/#what-it-includes","title":"What it includes","text":"<p>The generated docstring draws on:</p> <ul> <li>The function's call graph (what it calls and what calls it)</li> <li>Related concepts and rules from the knowledge layer</li> <li>The class or module context</li> <li>Existing docstrings in the same file as style examples</li> </ul>"},{"location":"guide/intelligence/#output_1","title":"Output","text":"<pre><code>def process_payment(order_id: str, amount: Decimal, idempotency_key: str) -> PaymentResult:\n \"\"\"Process a payment for an order.\n\n Charges the card on file for the given order using the Stripe payment\n processor. Requires an idempotency key to prevent double-charging on\n client retries (see: RequireIdempotencyKey rule, UseStripeForPayments\n decision).\n\n Args:\n order_id: The unique identifier of the order to charge.\n amount: The amount to charge in the order's currency.\n idempotency_key: Client-supplied key for idempotent retries.\n\n Returns:\n PaymentResult with charge_id and status.\n\n Raises:\n PaymentError: If the charge fails or the card is declined.\n DuplicatePaymentError: If a payment with this idempotency_key\n already exists with a different amount.\n \"\"\"\n</code></pre>"},{"location":"guide/intelligence/#batch-generation","title":"Batch generation","text":"<pre><code># generate docs for all undocumented functions in the repo\nnavegador docgen ./src --write --format google\n\n# formats: google (default), numpy, sphinx\n</code></pre> <p>Warning</p> <p><code>--write</code> modifies source files in place. Review changes with <code>--dry-run</code> first and commit before running with <code>--write</code> so you can diff and revert.</p>"},{"location":"guide/intelligence/#python-api_1","title":"Python API","text":"<pre><code>from navegador.graph import GraphStore\nfrom navegador.intelligence import SemanticSearch, CommunityDetector, NaturalLanguageQuery, DocGenerator\n\nstore = GraphStore.sqlite(\".navegador/navegador.db\")\n\n# semantic search\nsearch = SemanticSearch(store)\nresults = search.query(\"retry logic\", limit=5)\n\n# community detection\ndetector = CommunityDetector(store)\ncommunities = detector.detect(algorithm=\"louvain\", save=True)\n\n# natural language query\nnlq = NaturalLanguageQuery(store)\nanswer = nlq.ask(\"What functions call process_payment?\")\nprint(answer.text)\nprint(answer.cypher) # generated Cypher\n\n# doc generation\ndocgen = DocGenerator(store)\ndraft = docgen.generate_for_function(\"process_payment\")\nprint(draft.docstring)\n</code></pre>"},{"location":"guide/mcp-integration/","title":"MCP Integration","text":"<p>Navegador ships a built-in Model Context Protocol server. When running in MCP mode, all navegador commands become callable tools that agents can invoke with structured input and receive structured output.</p>"},{"location":"guide/mcp-integration/#cli-vs-mcp-when-to-use-which","title":"CLI vs MCP: when to use which","text":"<p>The primary interface for agents is the CLI, not MCP. Here's why:</p> CLI MCP Token cost Low \u2014 agent calls a shell tool, gets back only what it asked for Higher \u2014 MCP tool calls involve protocol overhead Setup None beyond installing navegador Requires MCP config in agent settings Best for Agent hooks, shell scripts, CI Interactive sessions in Claude / Cursor Output formats JSON, markdown, rich terminal Structured JSON always <p>Use MCP when you want navegador tools available as first-class tool calls in an interactive Claude or Cursor session. Use the CLI (via agent hooks) for automated background sync and pre-edit context loading.</p>"},{"location":"guide/mcp-integration/#starting-the-mcp-server","title":"Starting the MCP server","text":"<pre><code>navegador mcp\n</code></pre> <p>With a custom database path:</p> <pre><code>navegador mcp --db .navegador/navegador.db\n</code></pre> <p>The server speaks MCP over stdio. It does not bind a port.</p>"},{"location":"guide/mcp-integration/#agent-configuration","title":"Agent configuration","text":"Claude CodeClaude DesktopCursor <p>Add to your project's <code>.claude/settings.json</code>:</p> <pre><code>{\n \"mcpServers\": {\n \"navegador\": {\n \"command\": \"navegador\",\n \"args\": [\"mcp\"],\n \"env\": {\n \"NAVEGADOR_DB\": \".navegador/navegador.db\"\n }\n }\n }\n}\n</code></pre> <p>Add to <code>~/Library/Application Support/Claude/claude_desktop_config.json</code>:</p> <pre><code>{\n \"mcpServers\": {\n \"navegador\": {\n \"command\": \"navegador\",\n \"args\": [\"mcp\", \"--db\", \"/path/to/project/.navegador/navegador.db\"]\n }\n }\n}\n</code></pre> <p>Add to <code>.cursor/mcp.json</code> in your project root:</p> <pre><code>{\n \"mcpServers\": {\n \"navegador\": {\n \"command\": \"navegador\",\n \"args\": [\"mcp\"],\n \"env\": {\n \"NAVEGADOR_DB\": \".navegador/navegador.db\"\n }\n }\n }\n}\n</code></pre>"},{"location":"guide/mcp-integration/#available-mcp-tools","title":"Available MCP tools","text":"<p>All tools accept and return JSON. There are 11 tools in total.</p> Tool Equivalent CLI Description <code>ingest</code> <code>navegador ingest</code> Ingest a repo into the graph <code>context</code> <code>navegador context</code> File-level context bundle <code>function</code> <code>navegador function</code> Function with call graph <code>class</code> <code>navegador class</code> Class with hierarchy <code>explain</code> <code>navegador explain</code> Universal node lookup <code>search</code> <code>navegador search</code> Text search across graph <code>query</code> <code>navegador query</code> Raw Cypher passthrough <code>get_rationale</code> <code>navegador explain --rationale</code> Decisions and rules governing a node <code>find_owners</code> <code>navegador codeowners</code> People and domains that own a node <code>search_knowledge</code> <code>navegador search --knowledge</code> Search knowledge layer only <code>blast_radius</code> <code>navegador impact</code> Transitive impact set for a node"},{"location":"guide/mcp-integration/#tool-input-schemas","title":"Tool input schemas","text":"<p>ingest <pre><code>{ \"path\": \"./repo\", \"clear\": false }\n</code></pre></p> <p>context <pre><code>{ \"file\": \"src/auth/service.py\", \"format\": \"json\" }\n</code></pre></p> <p>function <pre><code>{ \"name\": \"validate_token\", \"file\": \"src/auth/service.py\", \"depth\": 2 }\n</code></pre></p> <p>class <pre><code>{ \"name\": \"PaymentProcessor\", \"file\": \"src/payments/processor.py\" }\n</code></pre></p> <p>explain <pre><code>{ \"name\": \"AuthService\", \"file\": \"src/auth/service.py\" }\n</code></pre></p> <p>search <pre><code>{ \"query\": \"rate limit\", \"all\": true, \"docs\": false, \"limit\": 20 }\n</code></pre></p> <p>query <pre><code>{ \"cypher\": \"MATCH (f:Function) RETURN f.name LIMIT 10\" }\n</code></pre></p> <p>get_rationale <pre><code>{ \"name\": \"process_payment\", \"file\": \"src/payments/processor.py\" }\n</code></pre></p> <p>find_owners <pre><code>{ \"name\": \"AuthService\", \"file\": \"src/auth/service.py\" }\n</code></pre></p> <p>search_knowledge <pre><code>{ \"query\": \"idempotency\", \"limit\": 10 }\n</code></pre></p> <p>blast_radius <pre><code>{ \"name\": \"validate_token\", \"depth\": 3 }\n</code></pre></p>"},{"location":"guide/mcp-integration/#read-only-mode","title":"Read-only mode","text":"<p>Start the MCP server in read-only mode to prevent agents from modifying the graph. This is recommended for shared or production environments.</p> <pre><code>navegador mcp --read-only\n</code></pre> <p>In read-only mode, the <code>ingest</code> tool is disabled and the <code>query</code> tool only accepts <code>MATCH</code>/<code>RETURN</code> queries. Write keywords (<code>CREATE</code>, <code>MERGE</code>, <code>SET</code>, <code>DELETE</code>) are rejected.</p> <p>Set in agent config:</p> <pre><code>{\n \"mcpServers\": {\n \"navegador\": {\n \"command\": \"navegador\",\n \"args\": [\"mcp\", \"--read-only\"],\n \"env\": {\n \"NAVEGADOR_DB\": \".navegador/navegador.db\"\n }\n }\n }\n}\n</code></pre> <p>Or set <code>read_only = true</code> in <code>.navegador/config.toml</code> under <code>[mcp]</code>.</p>"},{"location":"guide/mcp-integration/#editor-integration","title":"Editor integration","text":"<p>Instead of configuring MCP manually, use the editor setup command:</p> <pre><code>navegador editor setup claude-code\nnavegador editor setup cursor\nnavegador editor setup codex\nnavegador editor setup windsurf\n</code></pre> <p>This writes the correct MCP config file for the selected editor and prompts for the database path.</p>"},{"location":"guide/mcp-integration/#when-mcp-makes-sense","title":"When MCP makes sense","text":"<ul> <li>You are in an interactive Claude or Cursor session and want to call <code>explain</code>, <code>search</code>, or <code>function</code> without dropping to a terminal</li> <li>You want navegador tools auto-discovered by the agent without writing custom tool definitions</li> <li>You are building an agent workflow that dynamically queries the graph mid-task</li> </ul> <p>For automated background tasks (re-ingest on file save, sync on pull), use the CLI via agent hooks instead.</p>"},{"location":"guide/planopticon/","title":"Planopticon Integration","text":""},{"location":"guide/planopticon/#what-is-planopticon","title":"What is Planopticon","text":"<p>Planopticon is a video and meeting knowledge extraction tool. It ingests recordings, transcripts, and meeting notes and produces structured knowledge graphs: entities (people, concepts, decisions), relationships, action items, and diagrams extracted from the meeting content.</p> <p>Navegador treats Planopticon output as a first-class knowledge source. Where <code>navegador add concept</code> requires manual entry, Planopticon extracts concepts, rules, and decisions from meeting recordings automatically and navegador stores them alongside your code graph.</p>"},{"location":"guide/planopticon/#how-they-connect","title":"How they connect","text":"<pre><code>Video / transcript\n \u2193\n Planopticon\n \u2193 produces\n knowledge_graph.json / interchange.json / manifest.json\n \u2193\n navegador planopticon ingest\n \u2193 creates\n Concept / Rule / Decision / Person / WikiPage nodes\n in the same FalkorDB graph as your code\n</code></pre> <p>The result: \"the team decided to use Redis for session storage in the March architecture review\" becomes a <code>Decision</code> node linked to the <code>Infrastructure</code> domain and, via <code>GOVERNS</code>, to the <code>SessionManager</code> class in your code.</p>"},{"location":"guide/planopticon/#input-formats","title":"Input formats","text":"<p>Planopticon produces several output formats. Navegador accepts all of them and auto-detects by default.</p>"},{"location":"guide/planopticon/#manifestjson","title":"manifest.json","text":"<p>Top-level manifest for a multi-file Planopticon output package. Points to the knowledge graph, interchange, and supporting files.</p> <pre><code>{\n \"version\": \"1.0\",\n \"source\": \"zoom-meeting-2026-03-15\",\n \"knowledge_graph\": \"knowledge_graph.json\",\n \"interchange\": \"interchange.json\",\n \"diagrams\": [\"arch-diagram.png\"]\n}\n</code></pre>"},{"location":"guide/planopticon/#knowledge_graphjson","title":"knowledge_graph.json","text":"<p>Planopticon's native graph format. Contains typed entities and relationships:</p> <pre><code>{\n \"entities\": [\n { \"id\": \"e1\", \"type\": \"Decision\", \"name\": \"UseRedisForSessions\", \"description\": \"...\", \"rationale\": \"...\" },\n { \"id\": \"e2\", \"type\": \"Person\", \"name\": \"Alice Chen\", \"role\": \"Lead Engineer\" },\n { \"id\": \"e3\", \"type\": \"Concept\", \"name\": \"SessionAffinity\", \"description\": \"...\" }\n ],\n \"relationships\": [\n { \"from\": \"e2\", \"to\": \"e1\", \"type\": \"DECIDED_BY\" },\n { \"from\": \"e1\", \"to\": \"e3\", \"type\": \"RELATED_TO\" }\n ]\n}\n</code></pre>"},{"location":"guide/planopticon/#interchangejson","title":"interchange.json","text":"<p>A normalized interchange format, flatter than the native graph. Used when exporting from Planopticon for consumption by downstream tools.</p> <pre><code>{\n \"concepts\": [...],\n \"rules\": [...],\n \"decisions\": [...],\n \"people\": [...],\n \"action_items\": [...],\n \"diagrams\": [...]\n}\n</code></pre>"},{"location":"guide/planopticon/#batch-manifest","title":"Batch manifest","text":"<p>A JSON file listing multiple Planopticon output directories or archive paths for bulk ingestion:</p> <pre><code>{\n \"batch\": [\n { \"path\": \"./meetings/2026-03-15/\", \"source\": \"arch-review\" },\n { \"path\": \"./meetings/2026-02-20/\", \"source\": \"sprint-planning\" }\n ]\n}\n</code></pre>"},{"location":"guide/planopticon/#what-maps-to-what","title":"What maps to what","text":"Planopticon entity Navegador node Notes <code>Concept</code> <code>Concept</code> Direct mapping; domain preserved if present <code>Rule</code> <code>Rule</code> Severity set to <code>info</code> if not specified <code>Decision</code> <code>Decision</code> <code>rationale</code>, <code>alternatives</code>, <code>date</code>, <code>status</code> preserved <code>Person</code> <code>Person</code> <code>name</code>, <code>email</code>, <code>role</code>, <code>team</code> preserved Action item <code>Rule</code> + <code>ASSIGNED_TO</code> Creates a <code>Rule</code> with severity <code>info</code>; creates <code>ASSIGNED_TO</code> edge to the <code>Person</code> Diagram / image <code>WikiPage</code> Title from filename; content set to alt-text or caption <code>Relationship: DECIDED_BY</code> <code>DECIDED_BY</code> edge Person \u2192 Decision <code>Relationship: RELATED_TO</code> <code>RELATED_TO</code> edge Between any two knowledge nodes Entity domain field <code>BELONGS_TO</code> edge Links node to named <code>Domain</code> (created if not exists)"},{"location":"guide/planopticon/#cli-examples","title":"CLI examples","text":""},{"location":"guide/planopticon/#auto-detect-format-recommended","title":"Auto-detect format (recommended)","text":"<pre><code>navegador planopticon ingest ./meeting-output/ --type auto\n</code></pre>"},{"location":"guide/planopticon/#explicit-format","title":"Explicit format","text":"<pre><code>navegador planopticon ingest ./meeting-output/knowledge_graph.json --type kg\nnavegador planopticon ingest ./meeting-output/interchange.json --type interchange\nnavegador planopticon ingest ./manifest.json --type manifest\nnavegador planopticon ingest ./batch.json --type batch\n</code></pre>"},{"location":"guide/planopticon/#label-the-source","title":"Label the source","text":"<p>Use <code>--source</code> to tag all nodes from this ingestion with a source label (useful for auditing where knowledge came from):</p> <pre><code>navegador planopticon ingest ./meeting-output/ \\\n --type auto \\\n --source \"arch-review-2026-03-15\"\n</code></pre>"},{"location":"guide/planopticon/#json-output","title":"JSON output","text":"<pre><code>navegador planopticon ingest ./meeting-output/ --json\n</code></pre> <p>Returns a summary of nodes and edges created.</p>"},{"location":"guide/planopticon/#python-api","title":"Python API","text":"<pre><code>from navegador.ingest import PlanopticonIngester\nfrom navegador.graph import GraphStore\n\nstore = GraphStore.sqlite(\".navegador/navegador.db\")\ningester = PlanopticonIngester(store)\n\n# auto-detect format\nresult = ingester.ingest(\"./meeting-output/\", input_type=\"auto\", source=\"arch-review\")\n\nprint(f\"Created {result.nodes_created} nodes, {result.edges_created} edges\")\n\n# ingest a specific interchange file\nresult = ingester.ingest_interchange(\"./interchange.json\", source=\"sprint-planning\")\n</code></pre>"},{"location":"guide/planopticon/#planopticoningester-methods","title":"PlanopticonIngester methods","text":"Method Description <code>ingest(path, input_type, source)</code> Auto or explicit ingest from path <code>ingest_manifest(path, source)</code> Ingest a manifest.json package <code>ingest_kg(path, source)</code> Ingest a knowledge_graph.json file <code>ingest_interchange(path, source)</code> Ingest an interchange.json file <code>ingest_batch(path, source)</code> Ingest a batch manifest"},{"location":"guide/sdk/","title":"Python SDK","text":"<p>The navegador Python SDK lets you drive ingestion, query the graph, and load context from your own scripts and tools \u2014 without going through the CLI.</p>"},{"location":"guide/sdk/#installation","title":"Installation","text":"<pre><code>pip install navegador\n</code></pre> <p>For Redis (production/team) support:</p> <pre><code>pip install \"navegador[redis]\"\n</code></pre>"},{"location":"guide/sdk/#connecting-to-the-graph","title":"Connecting to the graph","text":"SQLite (local)Redis (production) <pre><code>from navegador.graph import GraphStore\n\nstore = GraphStore.sqlite(\".navegador/navegador.db\")\n</code></pre> <pre><code>from navegador.graph import GraphStore\n\nstore = GraphStore.redis(\"redis://localhost:6379\")\n</code></pre> <p>Both backends implement the same interface. All examples below work with either.</p> <p>Use the context manager to ensure the connection is closed:</p> <pre><code>with GraphStore.sqlite(\".navegador/navegador.db\") as store:\n results = store.query(\"MATCH (n) RETURN count(n) AS total\")\n print(results[0][\"total\"])\n</code></pre>"},{"location":"guide/sdk/#ingestion","title":"Ingestion","text":""},{"location":"guide/sdk/#ingest-a-repo","title":"Ingest a repo","text":"<pre><code>from navegador.graph import GraphStore\nfrom navegador.ingest import RepoIngester\n\nstore = GraphStore.sqlite(\".navegador/navegador.db\")\ningester = RepoIngester(store)\n\nresult = ingester.ingest(\"./src\")\nprint(f\"{result.nodes_created} nodes, {result.edges_created} edges in {result.duration_seconds:.2f}s\")\n</code></pre>"},{"location":"guide/sdk/#incremental-ingest-single-file","title":"Incremental ingest (single file)","text":"<pre><code>result = ingester.ingest_file(\"./src/auth/service.py\")\n</code></pre>"},{"location":"guide/sdk/#wipe-and-rebuild","title":"Wipe and rebuild","text":"<pre><code>result = ingester.ingest(\"./src\", clear=True)\n</code></pre>"},{"location":"guide/sdk/#add-knowledge-programmatically","title":"Add knowledge programmatically","text":"<pre><code>from navegador.ingest import KnowledgeIngester\n\nki = KnowledgeIngester(store)\n\nki.add_domain(\"Payments\", description=\"Payment processing and billing\")\nki.add_concept(\"Idempotency\", domain=\"Payments\",\n description=\"Operations safe to retry without side effects\")\nki.add_rule(\"RequireIdempotencyKey\",\n domain=\"Payments\", severity=\"critical\",\n rationale=\"Card networks retry on timeout\")\nki.annotate(\"process_payment\", node_type=\"Function\",\n concept=\"Idempotency\", rule=\"RequireIdempotencyKey\")\n</code></pre>"},{"location":"guide/sdk/#loading-context","title":"Loading context","text":"<p><code>ContextLoader</code> builds structured context bundles from the graph. Each method corresponds to a CLI command.</p> <pre><code>from navegador.graph import GraphStore\nfrom navegador.context import ContextLoader\n\nstore = GraphStore.sqlite(\".navegador/navegador.db\")\nloader = ContextLoader(store)\n</code></pre>"},{"location":"guide/sdk/#file-context","title":"File context","text":"<pre><code>bundle = loader.load_file(\"src/auth/service.py\")\nprint(bundle.to_markdown())\n</code></pre>"},{"location":"guide/sdk/#function-with-call-graph","title":"Function with call graph","text":"<pre><code># depth controls how many hops of callers/callees to include\nbundle = loader.load_function(\"validate_token\", depth=2)\nprint(bundle.to_json())\n</code></pre>"},{"location":"guide/sdk/#class-hierarchy","title":"Class hierarchy","text":"<pre><code>bundle = loader.load_class(\"PaymentProcessor\", file=\"src/payments/processor.py\")\ndata = bundle.to_dict()\n</code></pre>"},{"location":"guide/sdk/#universal-explain","title":"Universal explain","text":"<pre><code># works for any node type: function, class, file, concept, rule, decision\nbundle = loader.explain(\"AuthService\")\nbundle = loader.explain(\"PaymentsMustBeIdempotent\")\n</code></pre>"},{"location":"guide/sdk/#concept-and-domain","title":"Concept and domain","text":"<pre><code>bundle = loader.load_concept(\"Idempotency\")\nbundle = loader.load_domain(\"Payments\")\n</code></pre>"},{"location":"guide/sdk/#search","title":"Search","text":"<pre><code># search function and class names (default)\nnodes = loader.search(\"rate limit\")\n\n# search all layers including knowledge and docs\nnodes = loader.search(\"rate limit\", all_layers=True, limit=50)\n\n# search docstrings and wiki content only\nnodes = loader.search_by_docstring(\"retry logic\")\n\n# find all functions using a specific decorator\nnodes = loader.decorated_by(\"login_required\")\n\nfor node in nodes:\n print(f\"{node.label}: {node.name} ({node.properties.get('file', '')})\")\n</code></pre>"},{"location":"guide/sdk/#knowledge-queries","title":"Knowledge queries","text":"<pre><code># everything in the Payments domain\nbundle = loader.load_domain(\"Payments\")\nfor node in bundle.nodes:\n print(f\" [{node.label}] {node.name}\")\n\n# all code annotated with a concept\nbundle = loader.load_concept(\"Idempotency\")\nfor node in bundle.nodes:\n if node.layer == \"code\":\n print(f\" {node.name} {node.properties.get('file', '')}\")\n</code></pre>"},{"location":"guide/sdk/#exporting-output","title":"Exporting output","text":"<p>Every <code>ContextBundle</code> supports three output formats:</p> <pre><code>bundle = loader.load_function(\"process_payment\")\n\n# JSON string \u2014 for agents, APIs, CI\njson_str = bundle.to_json()\n\n# Markdown \u2014 readable by humans and LLMs\nmd_str = bundle.to_markdown()\n\n# Python dict \u2014 for further processing\ndata = bundle.to_dict()\nprint(data[\"root\"][\"name\"])\nprint(len(data[\"nodes\"]))\n</code></pre>"},{"location":"guide/sdk/#raw-cypher-queries","title":"Raw Cypher queries","text":"<p>Drop to raw Cypher for anything the built-in methods don't cover:</p> <pre><code>results = store.query(\n \"MATCH (f:Function)-[:CALLS]->(g:Function) \"\n \"WHERE f.file = $file \"\n \"RETURN f.name, g.name\",\n params={\"file\": \"src/payments/processor.py\"}\n)\nfor row in results:\n print(f\"{row['f.name']} -> {row['g.name']}\")\n</code></pre> <p>Warning</p> <p><code>store.query()</code> executes writes as well as reads. Stick to <code>MATCH</code> / <code>RETURN</code> for inspection queries.</p>"},{"location":"guide/sdk/#wiki-ingestion","title":"Wiki ingestion","text":"<pre><code>import os\nfrom navegador.ingest import WikiIngester\n\ningester = WikiIngester(store)\n\n# from GitHub API\nresult = ingester.ingest_repo(\"myorg/myrepo\", token=os.environ[\"GITHUB_TOKEN\"])\n\n# from a locally cloned wiki directory\nresult = ingester.ingest_dir(\"./myrepo.wiki\")\n</code></pre>"},{"location":"guide/sdk/#error-handling","title":"Error handling","text":"<p>All ingesters return an <code>IngestionResult</code> dataclass. Check <code>errors</code> for per-file failures without crashing the whole run:</p> <pre><code>result = ingester.ingest(\"./src\")\nif result.errors:\n for err in result.errors:\n print(f\"Warning: {err}\")\nprint(f\"Processed {result.files_processed} files, {result.nodes_created} nodes created\")\n</code></pre>"}]}