# Project descriptor A generic DocForge project is selected by one fixed file: `.docforge/project.toml` beneath an explicit project root. The descriptor is schema version 1. DocForge validates both the JSON-schema shape in `schemas/project.schema.json` and runtime invariants that schema alone cannot prove. Reference integrations use a different fixed descriptor, `.docforge/reference-adapter.toml`; see [Reference adapters](REFERENCE_ADAPTERS.md). Project-owned adapters construct the same runtime `ProjectDescriptor` contract through the public adapter SDK. ## Complete generic example ```toml schema_version = 1 project_id = "my-project" title = "My Project" adapter = "generic" [sources] content_roots = ["docs/docforge/content"] authority_files = [] [derived] cache_root = ".docforge/cache" index = ".docforge/cache/index.sqlite3" [changesets] root = ".docforge/changesets" [[changesets.writers]] id = "project-editor" families = ["architecture", "operations", "system"] operations = ["create", "update", "move", "delete"] [render] template_root = ".docforge/templates" preview_root = ".docforge/previews" [[render.views]] id = "manual" renderer = "generic_html" template = "manual.html" output = ".docforge/rendered/manual.html" title = "My Project Manual" families = ["architecture", "operations", "system"] [graph_render] output_root = ".docforge/portable-graph" [[graph_render.views]] id = "architecture" renderer = "portable_graph_html" output = "architecture.html" title = "Architecture" root = "architecture.overview" initial_mode = "web" depth = 3 max_nodes = 250 max_edges = 1000 max_work = 100000 families = ["architecture", "system"] relations = ["depends_on", "owns", "calls", "reads", "writes", "tested_by", "relates_to"] authorities = [] statuses = ["current", "active", "verified"] tags = [] include_logic = false [graph] allowed_relations = [ "calls", "depends_on", "owns", "reads", "relates_to", "tested_by", "writes", ] [limits] max_source_bytes = 500000 max_nodes = 10000 max_query_chars = 500 max_results = 100 max_traversal_depth = 6 max_context_tokens = 12000 max_tool_output_chars = 200000 max_changesets = 100 max_changeset_operations = 100 max_changeset_bytes = 1000000 max_render_views = 20 max_template_bytes = 1000000 max_render_bytes = 1000000 [[profiles]] id = "development" families = ["architecture", "operations", "system"] statuses = ["current", "active", "verified"] required_nodes = ["architecture.overview"] token_budget = 8000 dependency_depth = 3 ``` Rendering sections are optional. Profiles and limits may also be omitted; runtime defaults then apply. The required top-level fields are `schema_version`, `project_id`, `title`, `adapter`, `sources`, `derived`, `changesets`, and `graph`. ## Identity fields `schema_version` must be `1`. `project_id` is the stable machine identity. It is lowercase and may contain digits, dots, underscores, and hyphens after its first character. Do not derive it from a mutable display title. `title` is the human-readable project name. `adapter` is `generic` for this file format. Project-owned adapters publish a validated `adapter_id@adapter_version` identity through their runtime descriptor; changing adapter identity invalidates incompatible derived state. The descriptor's byte content contributes to a descriptor hash. Client fragments, launchers, index evidence, and policy results use that hash to detect drift. ## Canonical sources `sources.content_roots` lists the project-relative directories containing generic Markdown and TOML nodes. Each path must resolve beneath the project root. Canonical content roots may not overlap the derived cache. `sources.authority_files` lists additional project-relative regular files whose content belongs to the canonical project identity. They are not automatically parsed as nodes. A Markdown node contains one TOML metadata block followed by Markdown content: ```markdown +++ schema_version = 1 id = "architecture.overview" title = "Architecture overview" family = "architecture" authority = "authoritative" status = "current" tags = ["architecture"] summary = "Defines the top-level architecture and ownership." +++ # Architecture overview Describe systems, ownership, runtime flow, failure behavior, and proof. ``` Every node ID is project-wide and stable. Every relationship target must resolve. A TOML source may contain multiple `[[nodes]]` records; proposal-enabled multi-node files need stable `source_anchor` values where creation or movement requires an exact record boundary. ## Derived state `derived.cache_root` owns disposable indexes, attestations, extraction caches, render receipts, projection artifacts, and viewer registry state. `derived.index` must be inside `derived.cache_root`. Canonical content and cache paths must not overlap. Derived state is not a backup. If it is deleted or rejected as corrupt, DocForge rebuilds it from validated canonical sources. ## Changesets and writers `changesets.root` is the confined proposal store. It must not overlap canonical content or the derived cache. Each `changesets.writers` entry declares: - a stable writer `id`; - the node `families` that writer may change; - allowed `operations`: `create`, `update`, `move`, and/or `delete`. The descriptor grant is necessary but not sufficient. A process must also select that writer at startup, and canonical application requires a separately bound matching applier. Capability mode does not broaden the descriptor grant. See [Policy precedence](POLICY_PRECEDENCE.md). ## Allowed relationships `graph.allowed_relations` is the exact project vocabulary accepted on edges. It must be nonempty. Relationship names are stable IDs. DocForge rejects relationships that are not declared and gives `depends_on` additional cycle validation. The core does not reinterpret a custom relationship just because its spelling resembles a known term. Task-context retrieval classifies only the documented versioned aliases and preserves unknown allowed relationships as `unclassified`. ## Context profiles Each `profiles` entry defines one bounded context compilation: - `id` selects the profile; - `families` and `statuses` filter eligible nodes; - `required_nodes` names stable nodes that must be present; - `token_budget` limits compiled content; - `dependency_depth` bounds relationship expansion. Profiles choose derived retrieval scope. They do not change node authority or writer permissions. ## Limits Positive limits bound input, graph, retrieval, proposal, and rendering work. Current defaults are: - `max_source_bytes = 1000000` - `max_nodes = 10000` - `max_query_chars = 500` - `max_results = 100` - `max_traversal_depth = 8` - `max_context_tokens = 32000` - `max_tool_output_chars = 200000` - `max_changesets = 1000` - `max_changeset_operations = 100` - `max_changeset_bytes = 1000000` - `max_render_views = 100` - `max_template_bytes = 1000000` - `max_render_bytes = 1000000` Smaller project limits are useful policy. They cannot widen fixed internal worker, package, response, or cache ceilings. In particular, detached renderer transfer has its own fixed boundary even if a compatibility descriptor retains a larger `max_render_bytes`. ## Manual rendering The optional `render` section declares: - one confined `template_root`; - one isolated `preview_root`; - one or more stable views. Each view uses the built-in `generic_html` renderer, a template beneath `template_root`, one declared output path, a title, and a family filter. Paths may not overlap canonical content, authority files, changesets, cache, templates, or previews in unsafe ways. Templates are inert UTF-8 files with a fixed token vocabulary. They cannot select executable renderers or commands. See [Rendering and visualization](RENDERING_AND_VISUALIZATION.md). ## Portable graph rendering The optional `graph_render` section declares an output root and one or more `portable_graph_html` views. Each view selects exactly one: - `root`, an exact stable node ID; or - `query`, a bounded metadata-only lexical seed. It may then restrict families, relations, authorities, statuses, and tags, plus depth, node, edge, and work limits. `initial_mode` is `nodes`, `flow`, or `web`. Portable graph contract version 1 requires `include_logic = false`. The declared `output` is relative to `graph_render.output_root`. ## Paths and confinement Descriptor paths are project-relative. Absolute paths and parent traversal are rejected. Runtime validation also rejects symlink escapes, unexpected file types, unsafe overlap, changing path identity during sensitive reads or publication, and derived outputs outside their declared roots. The explicit CLI `--project-root` is the only project selector. The MCP process binds it at startup and exposes no project-switching tool. ## Validate changes safely After editing the descriptor: ```bash docforge --project-root /absolute/path/MyProject validate docforge --project-root /absolute/path/MyProject reindex docforge --project-root /absolute/path/MyProject check ``` Descriptor or adapter implementation drift makes a project-owned running process fail closed; start a fresh process after changing those boundaries. For first-time creation, prefer the create-only [New-project quickstart](NEW_PROJECT_QUICKSTART.md). For full invariants, read the [Core contract](CONTRACT.md).