# DocForge user manual DocForge turns project-owned documentation and source projections into a validated graph that people and AI agents can search, inspect, visualize, and change through reviewable proposals. Canonical project files remain authoritative. The SQLite graph, previews, rendered manuals, and viewer processes are derived and can be rebuilt. DocForge 1.0.0 is the first stable product release. It includes the project-scoped graph, CLI and MCP query surfaces, hash-approved proposal application, generic and project-owned adapters, declared rendering, and the Nodes/Flow/Web visualization model documented below. Later incremental-compiler capabilities are additive. A Release 1 adapter with only `load_projection()` remains valid and follows the same complete-rebuild path. No existing project descriptor, canonical document, changeset, or adapter must be rewritten. Source-scoped caching and lazy logic projections activate only for adapters that explicitly implement the optional incremental methods while retaining the full loader as a fallback. ## Features - Project-bound Markdown and TOML documentation graphs with stable node IDs. - Deterministic validation for metadata, relationships, dependency cycles, paths, and limits. - Disposable SQLite indexing with lexical search, filters, backlinks, dependencies, and impact. - Bounded context profiles for AI agents, including source paths and content hashes. - Isolated, optimistic changesets with create, update, move, delete, validation, diffs, and previews. - Relationship-only changeset operations that do not rewrite node content. - Hash-bound canonical application through both CLI and an explicitly enabled MCP tool. - Opt-in incremental adapter extraction with reverse-dependency invalidation. - Lazy function-scoped logic projections that do not densify the primary graph. - Declared HTML render views. Arbitrary templates, render commands, and output paths are rejected. - A loopback-only graph browser with Nodes, semantic Flow, and convergence Web views, relationship keys, source inspection, branch-aware node hiding, panel resizing, zooming, and managed idle shutdown. - A generic Markdown/TOML adapter plus contracts for deterministic project-owned adapters. DocForge does not run shell commands from documentation, mutate Git, build an application, deploy, publish, choose a project globally, or cross project boundaries. ## Mental model One `.docforge/project.toml` binds DocForge to one project root. The descriptor declares canonical content roots, authority files, derived paths, proposal writers, relationship types, limits, context profiles, and optional render views. Canonical files own facts: ```text canonical Markdown/TOML or adapter sources ↓ validate disposable SQLite graph ↓ query / visualize / compile context people and agents ↓ propose isolated changeset + preview ↓ exact hash approval canonical apply ↓ reindexed graph + declared renders ``` An apply operation is deliberately narrower than a general file editor. It accepts one validated changeset ID and the exact SHA-256 changeset hash that was reviewed. It rejects stale canonical sources, changed proposals, overlapping proposals, unauthorized families or operations, unsafe paths, symlink escapes, and projections that do not round-trip through the project loader. The generic adapter can serialize its Markdown and TOML nodes directly. A custom adapter must provide its own canonical applier because only that project knows how a graph node maps back to its source format. ## Setup ### Requirements - Python 3.12 or newer. - [`uv`](https://docs.astral.sh/uv/) for the development environment. - Node.js and npm for browser asset validation and strict Pyright checking. Clone and verify DocForge: ```bash git clone forgejo@repo.andraxion.net:administrator/DocForge.git /absolute/path/DocForge cd /absolute/path/DocForge uv sync --group dev npm ci npx pyright npm run lint:web uv run ruff check src tests tools uv run ruff format --check src tests tools uv run pytest -q ``` Use the executables under `/absolute/path/DocForge/.venv/bin/` when DocForge is not installed into the active shell environment. ### Assess and onboard an unconfigured project Run a read-only assessment before writing configuration: ```bash docforge --project-root /absolute/path/MyProject onboard ``` The result reports detected languages, build evidence, likely documentation, existing configuration, and capability status. Detection does not claim that a language frontend exists. Limit the assessment with one or more `--language` options when needed. Create, index, and render a generic starter manual explicitly: ```bash docforge --project-root /absolute/path/MyProject onboard \ --language rust \ --scaffold \ --project-id my-project \ --title "My Project" ``` Scaffolding refuses to replace existing target files. It leaves source-graph status at `adapter_required` until a project integration implements and proves the adapter contract. See [Project onboarding](PROJECT_ONBOARDING.md) for the complete language-neutral checklist. Use the [Language Adapter Authoring Guide](ADAPTER_AUTHORING_GUIDE.md) when implementing that frontend. It covers stable identities, overlapping compiler evidence, deterministic ownership, normalization, incremental equivalence, failure recovery, and the required proof matrix. ### Configure a generic project Create `/absolute/path/MyProject/.docforge/project.toml`: ```toml schema_version = 1 project_id = "my-project" title = "My Project" adapter = "generic" [sources] content_roots = ["Docs/Manual"] authority_files = [] [derived] cache_root = ".docforge/cache" index = ".docforge/cache/index.sqlite3" [changesets] root = ".docforge/changesets" [[changesets.writers]] id = "project-editor" families = ["architecture", "system", "operations", "roadmap"] operations = ["create", "update", "move", "delete"] [render] template_root = "Docs/Templates" preview_root = ".docforge/previews" [[render.views]] id = "manual" renderer = "generic_html" template = "manual.html" output = "Docs/Rendered/Manual.html" title = "My Project Manual" families = ["architecture", "system", "operations", "roadmap"] [graph] allowed_relations = ["depends_on", "owns", "calls", "reads", "writes", "tested_by", "relates_to"] [limits] max_source_bytes = 500000 max_nodes = 10000 max_query_chars = 500 max_results = 100 max_traversal_depth = 6 max_context_tokens = 12000 max_changesets = 100 max_changeset_operations = 100 max_changeset_bytes = 1000000 [[profiles]] id = "development" families = ["architecture", "system", "operations", "roadmap"] statuses = ["current", "active", "verified"] required_nodes = ["architecture.overview"] token_budget = 8000 dependency_depth = 3 ``` Every path is resolved against the explicit project root. Canonical content, derived cache, and changeset roots must not overlap. ### Add a Markdown node Create `Docs/Manual/architecture-overview.md`: ```markdown +++ schema_version = 1 id = "architecture.overview" title = "Architecture overview" family = "architecture" authority = "authoritative" status = "current" tags = ["architecture", "ownership"] summary = "Defines the top-level systems and ownership boundaries." depends_on = ["system.persistence"] +++ # Architecture overview Describe the project’s systems, authorities, persistence owners, runtime flow, failure behavior, tests, and operational entry points. ``` Each Markdown file contains one node. A TOML source may contain multiple `[[nodes]]` records. TOML nodes need stable `source_anchor` values when proposals may create or move records within the file. Every relationship target must exist. ### Build the graph ```bash PROJECT=/absolute/path/MyProject DOCFORGE=/absolute/path/DocForge/.venv/bin/docforge "$DOCFORGE" --project-root "$PROJECT" validate "$DOCFORGE" --project-root "$PROJECT" reindex "$DOCFORGE" --project-root "$PROJECT" context development ``` `reindex` builds the SQLite graph and immediately checks its identity. Run it after canonical documentation or adapter sources change. ### Install the viewer manager Install the native per-user supervisor once: ```bash docforge-viewer-manager install-user-service ``` Linux uses `systemd --user`, macOS uses a LaunchAgent, and Windows uses Task Scheduler. If the virtual environment moves, reinstall the service so it points at the current Python interpreter. For a temporary foreground manager: ```bash docforge-viewer-manager serve ``` Open a project graph without Codex: ```bash docforge --project-root "$PROJECT" visualize docforge --project-root "$PROJECT" visualize --node architecture.overview docforge --project-root "$PROJECT" visualize --query persistence ``` The command opens the default browser. Add `--no-open` when a script only needs the returned JSON URL. Use `visualization-status` and `visualization-stop` to inspect or stop the project viewer. ## Visualization usage - Left-click a node for its compact descriptor. - Right-click a node for the full inspector. - Use **Open source** to read the node’s project-confined source at its anchor. - Use **Explore neighborhood** to make a node the new focus. - Use the mouse wheel or viewport buttons to zoom. Drag the canvas to pan. Press Space to center the selected node. ### Nodes: bounded neighborhood **Nodes** answers: “What is immediately related to this thing?” DocForge starts at the focus and traverses every stored incoming and outgoing relationship up to the selected depth and fixed edge limit. The graph preserves the relationships exactly as the index stores them. It does not reinterpret direction or exclude contextual relationships. The focus appears at the center. Every other card is categorized by the relationship that explains its contribution to the focus: Structure, Behavior, Dependency, Execution, Data, Evidence, Context, or Related. This is the broadest view and is useful for inspecting raw adapter output, discovering nearby nodes, and choosing a better focus. It can also be the noisiest view because containment, documentation, dependencies, calls, imports, and other relationship types may all appear together. In Nodes, **Hide node** removes that node and its incident edges from the presentation. It does not remove other nodes merely because they become disconnected. ### Flow: semantic paths into the focus **Flow** answers: “What origins and prerequisites lead to this thing?” Flow builds bounded semantic paths whose destination is the focus. Structural and execution relationships already aimed at the consumer keep their stored direction. Prerequisite-style relationships are reversed for presentation so arrows consistently point toward the thing being explained: - `defined_in`, `inherits`, and `imports` become definition, base-class, and imported-module contributions. - `depends_on` and `reads` become dependency and data-source contributions. - `tested_by` becomes a test path into the exercised node. For example, a method can appear as: ```text tests package → test module → test class → test method ``` The displayed reversal is a visualization rule only. It does not mutate the canonical relationship or derived index. Context-only relationships such as `documents`, `governs`, and `relates_to` are omitted so Flow remains a focused origin-to-destination explanation. ### Web: convergence and makeup **Web** answers: “What makes up this thing, and what paths converge on it?” Web starts with the same semantic contributor direction as Flow, then includes contextual relationships that Flow intentionally omits. It can show callers, containers, imports, dependencies, evidence, documentation context, and other contributors converging on the focus. It also presents direct relationships owned by the focus as adjacent contributor branches, including supported `calls`, `contains`, `defines`, `dispatches_to`, `implemented_by`, `launches`, `writes`, and `activates` relationships. This makes classes and methods useful graph nodes rather than labels attached to a file. A class can show its containing module, base class, callers, tests, and methods. A method can show the package and class path that contains it alongside imported helpers, dependencies, callers, and evidence. Every displayed path is oriented toward the focused node. Adjacent traversal is deliberately bounded. After DocForge includes a direct member or execution dependency owned by the focus, it continues toward that branch rather than fanning back out through unrelated siblings. Depth and edge limits provide a second guard against an unbounded web. ### Logic: possible control paths **Logic** answers: “What decisions and actions can occur inside this function or method?” Logic appears when the focused node owns a function-scoped `LogicProjection`. It loads that projection on demand instead of adding statements and conditions to the primary architecture graph. The view presents: - **Entry** and **Exit** terminals. - **Decision** cards for `if`, `elif`, compound booleans, loop conditions, `match` cases, and assertions. - **Action** cards for executable statement blocks and calls. - **Control** cards for loops, `break`, and `continue`. - **Convergence** cards where alternate paths rejoin, including decision, case, loop-exit, and exception convergence. - **Terminal** cards for returns and raised exceptions. Edges use explicit labels and independent colors for `TRUE`, `FALSE`, `NEXT`, `CASE`, `LOOP`, `EXCEPTION`, `RETURN`, `RAISE`, `BREAK`, and `CONTINUE`. Long predicates wrap on the card. The full expression and source anchor remain available through inspection and source navigation. The built-in analyzers cover Python, JavaScript, and C++. Python uses the standard-library AST. JavaScript and C++ use pinned Tree-sitter grammars behind the same language-neutral `LogicProjection` contract. Tree-sitter handles concrete syntax; DocForge keeps a thin language-specific control-flow profile for constructs such as conditions, loops, cases, exceptions, returns, and short-circuit operators. Adding a language therefore requires a grammar and a semantic profile, not a new visualization or database design. Logic is static analysis. It shows paths the indexed source permits, not the branch that ran for a particular request or the runtime value of a boolean. Dynamic dispatch, reflection, generated behavior, and values returned by other processes may require runtime tracing to resolve. ### Finding the right node The left panel combines independent filters rather than forcing users to scan the complete node list: - **Text** searches indexed titles, summaries, and content. - **Family** selects the project-defined family. - **Node type** selects callables or an exact indexed kind such as function, method, class, route, test, module, or document. - **Language** selects an indexed language tag such as Python, JavaScript, or C++. - **Capability** selects nodes with source navigation or an available Logic projection. Quick presets select common combinations for Logic-ready nodes, Python callables, tests, routes, and documentation. Filters compose, so `JavaScript` plus `Logic available` lists only JavaScript functions that can open Logic. Result cards show the readable leaf name, kind, language, path, and source anchor. Full identities remain in the tooltip and inspector. Selecting any canvas node highlights its directly connected nodes and the exact edges between them. Other nodes and edges remain visible at reduced opacity. This local trace works in Nodes, Flow, Web, and Logic without changing the root or querying a different graph. ### Reading graph cards The canvas presents nodes as compact semantic cards rather than anonymous circles: - **Focus** identifies the node being explained. - **Structure** identifies packages, modules, classes, methods, definitions, and other containment paths that establish where the focus exists. - **Behavior** identifies base classes, derived classes, and implementation relationships. - **Dependency** identifies imported modules and required services or helpers. - **Execution** identifies callers, dispatchers, launchers, activators, and focus-owned execution branches. - **Data** identifies values or resources read and written. - **Evidence** identifies tests, verification, governing rules, and documentation. - **Context** identifies descriptive relationships that do not imply execution or ownership. - **Related** is the deterministic fallback for adapter-specific relationships that do not fit a built-in category. The colored rail, category badge, edge style, and relationship label provide separate visual cues. Color is not the only signal. Cards also display the node kind, such as `Test class` or `Test method`. Canvas cards use the readable leaf name. For example, `tests.test_settings.SettingsTests.test_default_settings_load` appears as `test_default_settings_load`, while `tests.test_settings` appears as `test_settings`. Long leaf names wrap at identifier boundaries instead of being truncated. The complete qualified title and stable node ID remain available in the pointer tooltip, compact descriptor, and full inspector, so the shorter canvas label never changes identity or loses information. ### Hiding nodes and pruning ancestors Hidden nodes are browser presentation state. Hiding never changes canonical files, the derived index, or future graph queries. The focus cannot be hidden; focus another node first. - In **Nodes**, hiding removes only the selected node and its incident edges. - In **Flow** and **Web**, hiding removes the selected node, then prunes every upstream ancestor whose only remaining route to the focus passed through it. - In **Logic**, hiding removes the selected control-flow step and inserts an `omitted` bridge between its visible predecessors and successors. This preserves the readable path without pretending the hidden code disappeared from the indexed source. - Descendant nodes between the hidden node and the focus remain visible. - Ancestors with another valid path to the focus remain visible through that alternate path. - The status line reports how many nodes were hidden or isolated. - **Restore hidden** clears the hidden-node set and rebuilds the complete current view. This behavior lets a user cut away a noisy or irrelevant branch without losing the useful downstream chain that explains how the remaining nodes reach the focus. Source navigation depends on adapter evidence. Numeric anchors, line-style anchors such as `L120`, TOML `node-N` anchors, heading slugs, and searchable text anchors are recognized. If a custom adapter supplies only a path or a vague symbol, the source viewer opens the file and falls back to the closest match or first line. ## CLI usage Every command emits deterministic JSON: ```bash docforge --project-root /absolute/path/MyProject ``` ### Project and index commands ```text info validate build reindex sync check validate-index ``` - `info` reports the project binding and index health. - `validate` validates current canonical sources without requiring an index. - `build` rebuilds the disposable index. - `reindex` rebuilds and checks the index in one operation. - `sync` checks the index and rebuilds it only when it is missing, stale, or invalid. - `check` and `validate-index` verify that the existing index matches current sources. ### Query commands ```text show NODE_ID search QUERY [--limit N] filter [--family X] [--authority X] [--status X] [--tag X] [--limit N] backlinks NODE_ID [--relation RELATION] dependencies NODE_ID [--depth N] impact NODE_ID [--depth N] context PROFILE [--budget N] ``` ### Render and proposal commands ```text render-status [VIEW_ID] render VIEW_ID preview CHANGESET_ID VIEW_ID apply CHANGESET_ID --changeset-hash SHA256 --applier WRITER_ID ``` The CLI apply command supports the generic adapter. It verifies that the configured writer owns the changeset, applies the exact reviewed hash, rebuilds the index, checks it, and regenerates every declared render. It does not commit or push the result. ### Viewer commands ```text visualize [--node NODE_ID | --query QUERY] [--depth N] [--no-open] visualization-status visualization-stop ``` ## MCP usage Run one MCP server per project with absolute paths: ```bash docforge-mcp \ --project-root /absolute/path/MyProject \ --proposal-writer project-editor ``` Omit `--proposal-writer` when the MCP client should not create or append proposals. To expose canonical application, add a separate explicit startup gate: ```bash docforge-mcp \ --project-root /absolute/path/MyProject \ --proposal-writer project-editor \ --canonical-applier project-editor ``` Without `--canonical-applier`, `docforge_apply_changeset` is not registered. The flag is an identity, not a command. The changeset creator, configured writer, and canonical applier must agree. Example MCP client configuration: ```json { "mcpServers": { "my-project-docforge": { "command": "/absolute/path/DocForge/.venv/bin/docforge-mcp", "args": [ "--project-root", "/absolute/path/MyProject", "--proposal-writer", "project-editor", "--canonical-applier", "project-editor" ] } } } ``` ### Read tools - `docforge_bootstrap` - `docforge_sync` - `docforge_project_info` - `docforge_get_contract` - `docforge_get_node` - `docforge_get_logic` - `docforge_search` - `docforge_filter_nodes` - `docforge_backlinks` - `docforge_dependencies` - `docforge_impact` - `docforge_get_context` - `docforge_validate_project` - `docforge_render_status` - `docforge_visualize` - `docforge_visualization_status` - `docforge_stop_visualization` ### Proposal tools - `docforge_create_changeset` - `docforge_register_changes` - `docforge_list_changesets` - `docforge_get_changeset` - `docforge_rebase_changeset` - `docforge_abandon_changeset` - `docforge_propose_node_create` - `docforge_propose_node_update` - `docforge_propose_node_move` - `docforge_propose_relationship_update` - `docforge_propose_node_delete` - `docforge_validate_changeset` - `docforge_get_changeset_diff` - `docforge_preview_changeset` ### Application tool - `docforge_apply_changeset` The application call requires `changeset_id` and `expected_changeset_hash`. Always retrieve and inspect the final diff after the last proposal mutation. Apply that exact hash. A proposal mutation creates a new hash, so an earlier approval cannot silently apply later content. Recommended release-candidate sequence: 1. Call `docforge_bootstrap`. It synchronizes derived state and reports the exact fixed binding. 2. Read only the relevant canonical context, implementation, configuration, tests, and release rules. 3. Record the expected documentation impact in the working plan. Do not create or apply a changeset yet. 4. Implement and run focused checks iteratively. Canonical documentation remains read-only during this loop. 5. Freeze one release candidate after implementation stops changing. 6. Run the complete project gate, deployment preflight, candidate deployment, live checks, data integrity checks, and release-identity checks. 7. If candidate validation fails, return to implementation. Do not document the failed candidate. 8. Call `docforge_sync` once after the candidate is green. 9. Call `docforge_register_changes` once with the complete operation list for every affected canonical node. 10. Inspect the structured diff and every required preview. 11. Obtain human approval for the final changeset hash when required by the client workflow. 12. Call `docforge_apply_changeset` with that exact hash. 13. Run documentation-only validation and render checks. 14. Call `docforge_bootstrap` to verify the new canonical and derived identity. 15. Commit, tag, and publish the final revision containing both the verified implementation and canonical documentation. This cadence separates documentation intake from documentation publication. It avoids repeatedly rewriting the manual around intermediate implementation states. One second documentation write is allowed only for a narrow evidence correction that could not exist before deployment. If a late check exposes an implementation defect, abandon or rebase the pending proposal and return to the implementation loop. The older create-and-append tools remain supported for interactive proposal construction. `docforge_register_changes` avoids intermediate empty changesets and caller-managed hash chaining. For update, move, and delete operations it captures the synchronized current node hash when `expected_content_hash` is omitted. Active changeset listing includes draft and ready proposals. Stale work remains available through an explicit `status="stale"` query for rebase decisions. Applied and abandoned proposals are terminal history, remain available by status or history request, and no longer block new proposals against the same canonical base. Canonical application records its terminal receipt immediately after the project-owned serializer verifies the new canonical state. A later index or render refresh failure is reported as degraded derived state with remediation, not as permission to apply the same canonical change again. Use `docforge_propose_relationship_update` when the intended change is only an edge addition or removal. It uses the same underlying validated update contract, but rejects empty relationship lists and makes it explicit that node content will remain unchanged. Custom adapters may expose the application tool only when they supply a project-owned `CanonicalApplier`. Core DocForge will not guess how adapter nodes map back to canonical sources. ## Incremental adapter compilation Release 1 complete-projection adapters remain supported. Adapters with large source trees can implement the optional source-scoped manifest and extraction contract. DocForge then fingerprints sources, reuses unchanged facts, reparses changed sources and their reverse dependents, validates a complete candidate graph, and publishes the index atomically. DocForge detects this capability structurally. An adapter without both `load_manifest()` and `extract_source()` remains on the Release 1 path. Its behavior and query results are unchanged, but it does not receive incremental performance until it opts in. Build results report cache hits, reparsed sources, invalidated sources, deleted sources, and total sources. A full projection remains the fallback and equivalence oracle. Manual proposals remain separate from compilation. Applying an approved changeset updates canonical sources first. Incremental compilation then notices those changed source fingerprints; it never treats an unapplied proposal as canonical. Function-scoped `LogicProjection` data is cached alongside its owning source but remains separate from the primary Nodes, Flow, and Web graph. The Logic tab and `docforge_get_logic` load one function or method on demand without adding every condition and basic block to ordinary graph traversal. See [Incremental Adapter Indexing](INCREMENTAL_INDEXING.md) for the complete contract, cache invalidation rules, manual-application lifecycle, and lazy Logic boundary. ## Troubleshooting ### `adapter_restart_required` The project-local adapter code, its declared descriptor, or another implementation file changed after the project-bound MCP process started. DocForge rejects every further operation before synchronization because the live Python objects still represent the prior implementation. Restart the MCP server or start a fresh client session. Do not stage files merely to change the adapter's source manifest, and do not attempt in-process module reloading. The error includes bounded added, changed, and deleted path evidence to identify the changed implementation boundary. ### `stale_index` or `visualization_stale` Normal MCP operations automatically repair a missing, stale, or invalid disposable index under a project lock. `docforge_sync` can be called explicitly to inspect whether synchronization was a no-op or rebuild. The CLI equivalent is: ```bash docforge --project-root "$PROJECT" sync docforge --project-root "$PROJECT" visualize ``` An existing graph browser intentionally stays pinned to its original index identity. Reopen it after synchronization or reindexing. Every complete index build also writes a disposable whole-file SHA-256 attestation. A new MCP process verifies the unchanged database against that receipt instead of reconstructing every graph row. Missing or mismatched receipts fall back to complete verification and are recreated only after the full check succeeds. ### `visualization_manager_unavailable` The per-user manager is not installed, is stopped, or points to an old virtual environment. ```bash docforge-viewer-manager install-user-service ``` For diagnosis, run `docforge-viewer-manager serve` in a terminal and retry `docforge visualize`. ### The browser did not open The command still returns the loopback URL as JSON. Open that URL manually. Desktop-less sessions should use `--no-open`. Confirm a local browser is registered as the default URL handler. ### `docforge_apply_changeset` is missing The MCP server was started without `--canonical-applier`, or a custom adapter did not supply a canonical applier. Restart the MCP server with the explicit gate after deciding that canonical application is appropriate for that project. ### `canonical_application_disabled` The CLI/MCP process has no matching configured applier identity. Confirm the ID exists under `[[changesets.writers]]`, owns the changeset, and is passed exactly to `--applier` or `--canonical-applier`. ### `changeset_conflict` The changeset changed after the caller read it. Retrieve the changeset and diff again. Review the new hash rather than retrying with the old approval. ### `base_conflict`, `content_conflict`, or `proposal_conflict` - `base_conflict`: canonical sources changed after changeset creation. - `content_conflict`: a target node no longer has the expected content hash. - `proposal_conflict`: another active proposal from the same base touches the same node or source. Do not force apply. Call `docforge_rebase_changeset` with the exact current changeset hash. DocForge will rebind it only when every touched fact is unchanged and the proposal still validates. A content or relationship conflict remains fail-closed and requires a newly reviewed proposal. ### `application_mismatch` The written sources did not reproduce the validated projection. DocForge rolls the generic canonical files back. For a custom adapter, fix its serializer or node-to-source mapping before retrying. ### `path_escape`, `unsafe_template`, or missing source DocForge rejects absolute paths, parent traversal, symlink escapes, overlapping canonical and derived roots, unsafe render outputs, and source files outside the project root. Fix the descriptor or adapter projection. Do not weaken confinement to make the error disappear. ### Source opens at the wrong place The source path comes from the node. The anchor comes from the generic source or custom adapter. Improve the adapter’s `source_anchor` to a line, stable heading, TOML `node-N` anchor, or distinctive symbol. DocForge can open the file safely, but it cannot infer a perfect code location from ambiguous adapter evidence. ### Full inspector content does not fit DocForge 1.0 uses a fixed header and footer with a scrollable inspector body. If an older page is still open, stop and reopen the visualization so it loads the current `graph-browser@17` template. ### Render output is stale ```bash docforge --project-root "$PROJECT" render-status docforge --project-root "$PROJECT" render VIEW_ID ``` Successful canonical apply regenerates all declared views automatically. A manual canonical edit requires reindexing and rendering. ### Descriptor changed after startup Long-lived CLI/MCP bindings fail closed if `.docforge/project.toml` changes underneath them. Restart the process so it binds the new descriptor deliberately. ## Development and verification Run the complete release gate from the DocForge repository: ```bash npx pyright npm run lint:web uv run ruff check src tests tools uv run ruff format --check src tests tools uv run python -m compileall -q src tests tools uv run pytest -q ``` Project-specific vocabulary, extraction rules, and serialization belong in the project adapter. Generic core behavior must remain deterministic, project-bound, and recoverable.