Skip to content
Cockpit0.29.0
A snapshot: read at commit 707a04c7ce8 on 2026-10-18T00:06:01Z by majordomus 0.29.0. Nothing on this page is read again. See it live

devcontext.compile

Compile the context for a piece of work

What it is

The objects a session working on this issue, milestone, intent or set of paths should be given: each with its canonical identifier, the index's own provenance, every discovery path that reached it with the reason and the confidence, its version, its tier and its cost in estimated tokens — followed by everything reached and not given with the reason for each, everything that collapsed into one entry, every pair that does not agree, and the per-tier spend against the budget. Deterministic for a given tree and request; the index fingerprint it was compiled from is in the answer.

Descriptor

Canonical id
devcontext.compile
Module
devcontext
Kind
query
Stability
behaviorallyverified
Provenance
declaredcached
Source
apps/majordomus-cli/src/capability/builtin/devcontext.rs
Cache
process, 16 entries, no expiry
Benchmark
required
Tags
contextsessionplanningintrospection

Run

query
A query. It reads and changes nothing.

An issue id (`I0301`) or its canonical identifier. Its milestone, its dependencies, its declared scope and the decisions over that scope follow from it.

A milestone id or slug, or its canonical identifier.

What the session is trying to do, in words. The only input the compiler infers from, and every entry it produces says so and carries a confidence below one.

Repository-relative paths the work touches, added to whatever the seeds declare.

Canonical identifiers to seed with directly, for a request about something that is neither an issue nor a milestone.

The ceiling in estimated tokens; `DEFAULT_BUDGET_TOKENS` when absent.

How far from a seed the walk goes; `DEFAULT_MAX_DEPTH` when absent.

Relevance below which an entry is reported rather than given; `DEFAULT_FLOOR` when absent.

Every blocking rule of the layer, not only the ones the work reaches. Honest and usually over budget: the answer then says `over_budget` rather than dropping what it may not drop.

GET /api/v1/devcontext

Projections

InterfaceAsWhere
MCP toolmajordomus_devcontext-
MCP resourcenot exposed-
HTTPGET /api/v1/devcontextSwagger
Command linemajordomus devcontext compile-

Schemas

Input · DevContextInput
{
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "issue": {
      "type": [
        "string",
        "null"
      ],
      "description": "An issue id (`I0301`) or its canonical identifier. Its milestone, its dependencies,\nits declared scope and the decisions over that scope follow from it."
    },
    "milestone": {
      "type": [
        "string",
        "null"
      ],
      "description": "A milestone id or slug, or its canonical identifier."
    },
    "intent": {
      "type": [
        "string",
        "null"
      ],
      "description": "What the session is trying to do, in words. The only input the compiler infers\nfrom, and every entry it produces says so and carries a confidence below one."
    },
    "paths": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "Repository-relative paths the work touches, added to whatever the seeds declare."
    },
    "uris": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "Canonical identifiers to seed with directly, for a request about something that is\nneither an issue nor a milestone."
    },
    "budget_tokens": {
      "type": [
        "integer",
        "null"
      ],
      "format": "uint64",
      "minimum": 0,
      "description": "The ceiling in estimated tokens; `DEFAULT_BUDGET_TOKENS` when absent."
    },
    "max_depth": {
      "type": [
        "integer",
        "null"
      ],
      "format": "uint",
      "minimum": 0,
      "description": "How far from a seed the walk goes; `DEFAULT_MAX_DEPTH` when absent."
    },
    "floor": {
      "type": [
        "number",
        "null"
      ],
      "format": "double",
      "description": "Relevance below which an entry is reported rather than given; `DEFAULT_FLOOR`\nwhen absent."
    },
    "all_blocking_rules": {
      "type": [
        "boolean",
        "null"
      ],
      "description": "Every blocking rule of the layer, not only the ones the work reaches. Honest and\nusually over budget: the answer then says `over_budget` rather than dropping what it\nmay not drop."
    }
  },
  "description": "The input of `devcontext.compile`: what to compile a context about.",
  "title": "DevContextInput"
}
Output · CompiledContext
{
  "type": "object",
  "properties": {
    "schema": {
      "type": "integer",
      "format": "uint64",
      "minimum": 0,
      "description": "The format of this answer."
    },
    "fingerprint": {
      "type": "string",
      "description": "The index this was compiled from, over every object's path and content. Two answers\nwith the same fingerprint and the same request are the same answer; a different\nfingerprint is a different tree."
    },
    "git": {
      "$ref": "#/$defs/GitContext",
      "description": "The head the answer is true of."
    },
    "seeds": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/Seed"
      },
      "description": "What was asked for."
    },
    "selected": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/ContextEntry"
      },
      "description": "What to give the session, in the order the budget spent: tier, then relevance, then\ncanonical identifier."
    },
    "excluded": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/Excluded"
      },
      "description": "What was reached and not given, with the reason for each."
    },
    "deduplicated": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/Deduplicated"
      },
      "description": "What collapsed into what."
    },
    "conflicts": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/Conflict"
      },
      "description": "What does not agree with what."
    },
    "budget": {
      "$ref": "#/$defs/Budget",
      "description": "The budget and its per-tier spend."
    },
    "diagnostics": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/Diagnostic"
      },
      "description": "What the compiler could not read: a knowledge source the layer names and does not\nhold, a session record that no longer describes this checkout. Never an error: an\nabsent source is an answer."
    }
  },
  "required": [
    "schema",
    "fingerprint",
    "git",
    "seeds",
    "selected",
    "excluded",
    "deduplicated",
    "conflicts",
    "budget",
    "diagnostics"
  ],
  "description": "A compiled development context: what to give a session, what was left out, what\ncollapsed into what, and the budget it was all decided under.\n\nThis is the canonical form. Rendering it as a prompt is a downstream projection of this\nvalue and is not performed here — the structure is what lets a caller re-budget, re-order\nor explain the selection without recompiling it.\n\nTwo invariants hold over every answer, and they are what make it safe to cache and to\ncompare. `fingerprint` is over the index this was compiled from, so the same request\nagainst the same fingerprint is the same answer and a different fingerprint is a\ndifferent tree — a caller can tell two trees apart without asking. And\n`budget.used_tokens` is exactly the sum of `selected`'s costs, so the arithmetic in the\nbudget describes the list beside it rather than a walk that has since been filtered.\n\n```\nuse majordomus_cli::devcontext::{compile, CompileInput, CompiledContext};\nuse majordomus_cli::devcontext::{Tier, DEFAULT_BUDGET_TOKENS};\nuse majordomus_cli::synthetic::SyntheticRepository;\n\nlet repo = SyntheticRepository::small().unwrap();\nlet ctx = repo.context().unwrap();\n\nlet c: CompiledContext = compile(&ctx, CompileInput::default()).unwrap();\n\n// the answer says which tree it is true of, and nothing else has to be asked\nassert_eq!(c.schema, 1);\nassert!(!c.fingerprint.is_empty(), \"an answer that names no tree is not one\");\n\n// the budget describes the list beside it\nlet spent: u64 = c.selected.iter().map(|e| e.cost_tokens).sum();\nassert_eq!(c.budget.used_tokens, spent);\nassert_eq!(c.budget.limit_tokens, DEFAULT_BUDGET_TOKENS);\n\n// every tier is reported, so an empty tier is visibly empty rather than missing\nassert_eq!(c.budget.tiers.len(), Tier::ORDER.len());\n\n// a request that named nothing has no seeds and is still an answer\nassert!(c.seeds.is_empty());\n\n// and recompiling the same request over the same tree gives the same answer\nlet again: CompiledContext = compile(&ctx, CompileInput::default()).unwrap();\nassert_eq!(again.fingerprint, c.fingerprint);\nassert_eq!(again.selected.len(), c.selected.len());\n```",
  "title": "CompiledContext",
  "$defs": {
    "GitContext": {
      "type": "object",
      "properties": {
        "branch": {
          "type": [
            "string",
            "null"
          ],
          "description": "The branch, when the checkout is on one."
        },
        "head": {
          "type": [
            "string",
            "null"
          ],
          "description": "The head commit."
        },
        "working_tree": {
          "type": "string",
          "description": "`clean`, `dirty`, or what git said."
        },
        "commits": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/CommitReference"
          },
          "description": "Commits the selected entries name as their own evidence, newest record first.\nDerived from the layer's records, never from a log walk."
        }
      },
      "required": [
        "working_tree"
      ],
      "description": "The head this answer was compiled against. A compiled context is only true of one tree,\nand these three fields plus the index fingerprint are what say which.\n\n`branch` and `head` are optional because a checkout can legitimately have neither — a\ndetached head, or a tree that is not a git repository at all — and the absence is carried\nrather than faked. `working_tree` is not optional, because there is always something to\nsay: when git cannot be reached it carries `unavailable: <reason>` instead of a status,\nso a reader is never left unable to tell \"clean\" from \"not asked\".\n\n`commits` is derived from the selected entries' own records and never from a log walk.\nThat is the difference between \"the commits this work says it produced\" and \"what\nhappened to be committed recently\", and only the first is context.\n\n```\nuse majordomus_cli::devcontext::GitContext;\nuse serde_json::json;\n\n// a tree the compiler could read\nlet known: GitContext = serde_json::from_value(json!({\n    \"branch\": \"master\",\n    \"head\": \"cd04012f1b1dc21fd493e2be14938ffdfaccc8fa\",\n    \"working_tree\": \"clean\",\n})).unwrap();\nassert_eq!(known.branch.as_deref(), Some(\"master\"));\nassert!(known.commits.is_empty(), \"no selected record named a commit\");\n\n// and one it could not: the status field still says something a reader can act on\nlet unknown: GitContext = serde_json::from_value(json!({\n    \"working_tree\": \"unavailable: not a git repository\",\n})).unwrap();\nassert!(unknown.head.is_none() && unknown.branch.is_none());\nassert!(\n    unknown.working_tree.starts_with(\"unavailable: \"),\n    \"never silently indistinguishable from clean\",\n);\n```"
    },
    "CommitReference": {
      "type": "object",
      "properties": {
        "commit": {
          "type": "string",
          "description": "The commit as the record wrote it."
        },
        "named_by": {
          "type": "string",
          "description": "The entry that named it."
        },
        "covers": {
          "type": [
            "string",
            "null"
          ],
          "description": "What the record says the commit was for."
        }
      },
      "required": [
        "commit",
        "named_by"
      ],
      "description": "One commit the layer itself names, and the record that named it.\n\nThe pair is the fact, not the commit alone. One commit named by three records is three\nreferences, because each record is a separate claim about what that commit was for, and\nfolding them would lose the attribution — which is the only reason to carry a commit here\nat all. So `named_by` ends the ordering key, and two references to one commit both\nsurvive.\n\n```\nuse majordomus_cli::devcontext::CommitReference;\nuse majordomus_cli::order::canonical;\nuse serde_json::json;\n\nlet by = |commit: &str, record: &str| -> CommitReference {\n    serde_json::from_value(json!({\"commit\": commit, \"named_by\": record})).unwrap()\n};\n\nlet mut commits = vec![\n    by(\"cd04012\", \"majordomus://session/b\"),\n    by(\"cd04012\", \"majordomus://session/a\"),\n    by(\"5e31dd4\", \"majordomus://issue/I0301\"),\n];\ncanonical(&mut commits);\n\n// filed under the commit, which is what a reader looks for\nlet order: Vec<&str> = commits.iter().map(|c| c.commit.as_str()).collect();\nassert_eq!(order, [\"5e31dd4\", \"cd04012\", \"cd04012\"]);\n\n// and the record ends the key, so one commit named twice stays two facts\nassert_eq!(commits[1].named_by, \"majordomus://session/a\");\nassert_eq!(commits[2].named_by, \"majordomus://session/b\");\n\n// `covers` is what the record said the commit was for, when it said\nlet with_reason = by(\"cd04012\", \"majordomus://session/a\");\nassert!(with_reason.covers.is_none(), \"absent rather than an empty sentence\");\n```"
    },
    "Seed": {
      "type": "object",
      "properties": {
        "uri": {
          "type": "string",
          "description": "The canonical identifier of the seed, or the literal the request gave for an\nintent or a path."
        },
        "kind": {
          "type": "string",
          "description": "`issue`, `milestone`, `path`, `intent`, or the kind of the object named."
        },
        "from": {
          "type": "string",
          "description": "Where it came from: which field of the request."
        },
        "resolved": {
          "type": [
            "string",
            "null"
          ],
          "description": "What the compiler resolved it to, when the request named something by a short name."
        }
      },
      "required": [
        "uri",
        "kind",
        "from"
      ],
      "description": "What the request asked to compile a context about.\n\nA seed is the request echoed back after resolution, and it is in the answer so that a\ncaller can see what their short name turned into. `from` names the request field it came\nfrom — `issue`, `milestone`, `uris`, `paths`, `intent` — which is how the same identifier\narriving two ways stays two seeds rather than one.\n\n`resolved` is populated only when resolution actually changed something: name a\nmilestone by its slug and it carries the canonical identity, name it by that identity and\nthere was nothing to resolve, so the field is absent. A path and an intent are not\nresolved against the index at all — their `uri` is the literal the request gave.\n\n```\nuse majordomus_cli::devcontext::Seed;\nuse serde_json::json;\n\n// named by a slug: the answer says what it became\nlet by_slug: Seed = serde_json::from_value(json!({\n    \"uri\": \"majordomus://milestone/M04\",\n    \"kind\": \"milestone\",\n    \"from\": \"milestone\",\n    \"resolved\": \"M04\",\n})).unwrap();\nassert_eq!(by_slug.resolved.as_deref(), Some(\"M04\"));\n\n// named canonically: nothing was resolved, so nothing is reported\nlet canonical: Seed = serde_json::from_value(json!({\n    \"uri\": \"majordomus://issue/I0301\",\n    \"kind\": \"issue\",\n    \"from\": \"issue\",\n})).unwrap();\nassert!(canonical.resolved.is_none());\n\n// and a path is carried verbatim: its `uri` is not a canonical identifier at all\nlet path: Seed = serde_json::from_value(json!({\n    \"uri\": \"apps/majordomus-cli/src\",\n    \"kind\": \"path\",\n    \"from\": \"paths\",\n})).unwrap();\nassert!(!path.uri.starts_with(\"majordomus://\"));\nassert_eq!(path.from, \"paths\", \"which request field it came from\");\n```"
    },
    "ContextEntry": {
      "type": "object",
      "properties": {
        "uri": {
          "type": "string",
          "description": "The canonical identifier: `majordomus://<kind>/<identity>`, unique in the answer."
        },
        "kind": {
          "type": "string",
          "description": "The kind the layer declared for it."
        },
        "title": {
          "type": [
            "string",
            "null"
          ],
          "description": "Its title, when it has one."
        },
        "provenance": {
          "$ref": "#/$defs/EntryProvenance",
          "description": "Where it lives."
        },
        "tier": {
          "$ref": "#/$defs/Tier",
          "description": "Which tier's budget it is spent from."
        },
        "relevance": {
          "type": "number",
          "format": "double",
          "description": "0.0 to 1.0, deterministic for a given tree and request. The order within a tier."
        },
        "confidence": {
          "type": "number",
          "format": "double",
          "description": "The highest confidence of any path that reached it."
        },
        "discovered_by": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/Discovery"
          },
          "description": "Every path that reached it, best first. Length above one is the evidence that\ndeduplication happened."
        },
        "depth": {
          "type": "integer",
          "format": "uint",
          "minimum": 0,
          "description": "The shortest number of hops from a seed."
        },
        "cost_tokens": {
          "type": "integer",
          "format": "uint64",
          "minimum": 0,
          "description": "Estimated tokens: the file's bytes divided by `BYTES_PER_TOKEN`, never fewer\nthan one. An estimate, said to be one."
        },
        "cost_bytes": {
          "type": "integer",
          "format": "uint64",
          "minimum": 0,
          "description": "The file's bytes, the measured half of the cost."
        },
        "version": {
          "type": [
            "string",
            "null"
          ],
          "description": "The version or date the object states about itself, when it states one: a rule's\nversion, an ADR's date, an issue's `updated_at`."
        },
        "status": {
          "type": [
            "string",
            "null"
          ],
          "description": "The status word the object declares, when it declares one."
        },
        "required": {
          "type": "boolean",
          "description": "True when a blocking rule, a policy or the scope: never dropped for budget."
        },
        "facts": {
          "type": "object",
          "additionalProperties": {
            "type": "string"
          },
          "description": "Facts worth carrying about this entry that are not the object's prose — a rule's\nclass, an issue's milestone, the commits an issue's evidence names."
        }
      },
      "required": [
        "uri",
        "kind",
        "provenance",
        "tier",
        "relevance",
        "confidence",
        "discovered_by",
        "depth",
        "cost_tokens",
        "cost_bytes",
        "required"
      ],
      "description": "One thing the compiler decided a session should be given: what it is, how it was\nreached, what it costs, and how far the compiler trusts it.\n\nNotably, it does **not** carry the prose. The identifier is here and the body is not,\nbecause the body belongs to the object and `objects.get` serves it whole; copying it\nwould put the layer in the answer twice and flatten the structure into text before\nanything had decided what to keep. An entry is a decision about an object, not a copy of\none.\n\nThree of the scalars are derived from `discovered_by` rather than independent of it, and\na reader can check them: `confidence` is the highest of any path's, `depth` the shortest,\nand a `discovered_by` of length above one is the evidence that deduplication happened.\n`required` is the veto — a blocking rule, the policy, the scope, or a seed — and it is\nthe one field that overrides the budget.\n\n```\nuse majordomus_cli::devcontext::{tokens_for, ContextEntry, Selector, Tier};\nuse serde_json::json;\n\n// one ADR, reached twice: once along a declared edge and once by matching the intent\nlet e: ContextEntry = serde_json::from_value(json!({\n    \"uri\": \"majordomus://adr/0052\",\n    \"kind\": \"adr\",\n    \"title\": \"The episode is the boundary\",\n    \"provenance\": {\n        \"path\": \".ai/repo/adrs/0052.md\", \"directory\": \".ai/repo/adrs\",\n        \"source_class\": \"adr\", \"section\": \"adrs\", \"bytes\": 8_192,\n    },\n    \"tier\": \"decision\",\n    \"relevance\": 0.82,\n    \"confidence\": 1.0,\n    \"discovered_by\": [\n        {\"selector\": \"relation\", \"reason\": \"in force over this scope\",\n         \"via\": \"majordomus://issue/I0301\", \"edge\": \"governed_by\",\n         \"depth\": 1, \"confidence\": 1.0, \"weight\": 0.9},\n        {\"selector\": \"intent_match\", \"reason\": \"the intent's words occur in it\",\n         \"depth\": 2, \"confidence\": 0.4, \"weight\": 0.4},\n    ],\n    \"depth\": 1,\n    \"cost_tokens\": 2_048,\n    \"cost_bytes\": 8_192,\n    \"version\": \"2026-09-11\",\n    \"status\": \"accepted\",\n    \"required\": false,\n})).unwrap();\n\nassert_eq!(e.tier, Tier::Decision);\n\n// the cost is the estimate over the measured bytes, and the answer carries both halves\nassert_eq!(e.cost_tokens, tokens_for(e.cost_bytes));\n\n// the derived scalars agree with the paths they are derived from\nlet best = e.discovered_by.iter().map(|d| d.confidence).fold(0.0_f64, f64::max);\nassert_eq!(e.confidence, best);\nassert_eq!(e.depth, e.discovered_by.iter().map(|d| d.depth).min().unwrap());\n\n// two paths for one entry: the record that four things pointing here is one entry\nassert_eq!(e.discovered_by.len(), 2);\nassert_eq!(e.discovered_by[0].selector, Selector::Relation, \"best path first\");\n\n// and the prose is not here: the identifier is how a caller gets the body\nassert!(e.uri.starts_with(\"majordomus://\"));\n```"
    },
    "EntryProvenance": {
      "type": "object",
      "properties": {
        "path": {
          "type": "string",
          "description": "Repository-relative path, forward slashes."
        },
        "directory": {
          "type": "string",
          "description": "The directory it sits in."
        },
        "source_class": {
          "type": "string",
          "description": "The `sources.yaml` class that discovered it."
        },
        "section": {
          "type": [
            "string",
            "null"
          ],
          "description": "The manifest section it falls under, when it falls under one."
        },
        "bytes": {
          "type": "integer",
          "format": "uint64",
          "minimum": 0,
          "description": "Size of the file in bytes."
        }
      },
      "required": [
        "path",
        "directory",
        "source_class",
        "bytes"
      ],
      "description": "Where the selected thing lives, as the index knows it. Nothing here is authored: every\nfield is the index's own `crate::model::Provenance`, carried rather than restated.\n\nIt is a copy of the index's provenance and not a re-derivation of it, which is the\nproperty worth relying on: no path here was constructed by this module, so a path in an\nanswer is a path the index discovered, and the two cannot disagree about where something\nlives.\n\n`bytes` is the measured half of an entry's cost — the estimate on top of it is\n`tokens_for` — so this is also where the budget's arithmetic starts.\n\n```\nuse majordomus_cli::devcontext::{tokens_for, EntryProvenance};\nuse serde_json::json;\n\nlet p: EntryProvenance = serde_json::from_value(json!({\n    \"path\": \".ai/repo/adrs/0052-the-episode-is-the-boundary.md\",\n    \"directory\": \".ai/repo/adrs\",\n    \"source_class\": \"adr\",\n    \"section\": \"adrs\",\n    \"bytes\": 8_192,\n})).unwrap();\n\n// the size is what the cost is computed from, and the estimate says so of itself\nassert_eq!(tokens_for(p.bytes), 2_048);\n\n// a file under no manifest section — a root README, say — carries none rather than \"\"\nlet rootish: EntryProvenance = serde_json::from_value(json!({\n    \"path\": \"README.md\",\n    \"directory\": \".\",\n    \"source_class\": \"readme\",\n    \"bytes\": 4_000,\n})).unwrap();\nassert!(rootish.section.is_none());\nassert_eq!(rootish.directory, \".\", \"the root is `.`, not the empty string\");\n```"
    },
    "Tier": {
      "oneOf": [
        {
          "type": "string",
          "const": "task",
          "description": "What was asked for: the issue, its milestone, the paths named, the intent. Never\ndropped — a context that lost the thing it was compiled about is not a smaller\nanswer, it is the wrong one."
        },
        {
          "type": "string",
          "const": "governance",
          "description": "What the work will be judged against: the policy, the scope, the directory\ncontracts that reach the named paths, and the rules. A blocking rule is never\ndropped."
        },
        {
          "type": "string",
          "const": "source",
          "description": "The code and the cases the work touches."
        },
        {
          "type": "string",
          "const": "decision",
          "description": "The architectural decisions already in force over it."
        },
        {
          "type": "string",
          "const": "knowledge",
          "description": "What a previous session left: handovers, session records, curated knowledge."
        },
        {
          "type": "string",
          "const": "history",
          "description": "Supporting evidence: use cases, claims, features, and the rest of the layer."
        }
      ],
      "description": "Where an entry sits in the order the budget spends in.\n\nThe order is this repository's own: `lib/context.sh` assembles its sections \"in\nauthority order, highest first, because a worker that runs out of budget must lose the\nleast reliable evidence rather than the most: git, then the task and its policy, then\nblockers, then authored records, then event history.\" These six tiers are that\nsentence, applied to objects instead of to sections.\n\nThe derived `Ord` is load-bearing: the variants are declared in authority order, so\n`<` means \"spent before\" and \"harder to drop\", and the budget's ordering is a plain\n`sort` rather than a table mapping tiers to ranks. A variant added in the wrong place\nwould silently re-rank the whole answer, which is why the declaration order is the one\nthing about this enum to be careful with.\n\n```\nuse majordomus_cli::devcontext::Tier;\n\n// the declaration order *is* the order the budget spends in\nlet mut sorted = Tier::ORDER.to_vec();\nsorted.sort();\nassert_eq!(sorted, Tier::ORDER.to_vec());\n\n// so the strongest authority is the smallest, and it is the one never dropped\nassert_eq!(Tier::ORDER.iter().copied().min(), Some(Tier::Task));\nassert!(Tier::Task < Tier::Governance && Tier::Governance < Tier::History);\n\n// and the serialised word is the lowercase one the report is read under\nassert_eq!(\n    serde_json::to_value(Tier::Governance).unwrap(),\n    serde_json::json!(Tier::Governance.as_str()),\n);\n```"
    },
    "Discovery": {
      "type": "object",
      "properties": {
        "selector": {
          "$ref": "#/$defs/Selector",
          "description": "What reached it."
        },
        "reason": {
          "type": "string",
          "description": "Why it was included, in words a person reads without this file."
        },
        "via": {
          "type": [
            "string",
            "null"
          ],
          "description": "The entry this one was reached from, when it was reached from one."
        },
        "edge": {
          "type": [
            "string",
            "null"
          ],
          "description": "The edge kind followed, for `Relation`."
        },
        "depth": {
          "type": "integer",
          "format": "uint",
          "minimum": 0,
          "description": "Hops from a seed; a seed is 0."
        },
        "confidence": {
          "type": "number",
          "format": "double",
          "description": "1.0 when the repository declared this path, less when it was inferred."
        },
        "weight": {
          "type": "number",
          "format": "double",
          "description": "What this path contributes to the entry's relevance."
        }
      },
      "required": [
        "selector",
        "reason",
        "depth",
        "confidence",
        "weight"
      ],
      "description": "One way an entry was reached: which selector, why, how far from a seed, and how much\nthe compiler trusts it. An entry keeps every one of these, because \"five discovery\npaths found the same ADR\" is the interesting fact, not a thing to hide.\n\nA `Discovery` is a *kind* of way in, not one traversal. Thirty-three issues of one\nmilestone all reach that milestone along `belongs_to`, and recording that thirty-three\ntimes would say the answer had thirty-three reasons when it has one — so a path already\nrecorded for the same selector, edge and reason is kept rather than repeated.\n\n`via` and `edge` are present exactly when they mean something: a seed was not reached\nfrom anything and followed no edge, so both are absent and are skipped on the wire\nrather than serialised as null. `confidence` is 1.0 for every declared selector and less\nonly for `Selector::IntentMatch`; `weight` is what this particular path contributes to\nrelevance, which is a different quantity — how much it counts, against how sure it is.\n\n```\nuse majordomus_cli::devcontext::{Discovery, Selector};\nuse serde_json::json;\n\n// the request named it: nothing to come via, no edge followed, depth zero\nlet seed: Discovery = serde_json::from_value(json!({\n    \"selector\": \"seed\",\n    \"reason\": \"named in the request\",\n    \"depth\": 0,\n    \"confidence\": 1.0,\n    \"weight\": 1.0,\n})).unwrap();\nassert_eq!(seed.selector, Selector::Seed);\nassert!(seed.via.is_none() && seed.edge.is_none());\nassert!(seed.selector.declared() && seed.confidence == 1.0);\n\n// the wire form omits them rather than carrying nulls a reader must skip\nlet wire = serde_json::to_value(&seed).unwrap();\nassert!(wire.get(\"via\").is_none() && wire.get(\"edge\").is_none());\n\n// a relation says both where it came from and which edge it followed\nlet walked: Discovery = serde_json::from_value(json!({\n    \"selector\": \"relation\",\n    \"reason\": \"the decision in force over this scope\",\n    \"via\": \"majordomus://issue/I0301\",\n    \"edge\": \"governed_by\",\n    \"depth\": 1,\n    \"confidence\": 1.0,\n    \"weight\": 0.9,\n})).unwrap();\nassert_eq!(walked.edge.as_deref(), Some(\"governed_by\"));\nassert_eq!(walked.via.as_deref(), Some(\"majordomus://issue/I0301\"));\n\n// and a guess says so, in the one field that distinguishes the two halves of an answer\nlet guessed: Discovery = serde_json::from_value(json!({\n    \"selector\": \"intent_match\",\n    \"reason\": \"the intent's words occur in it\",\n    \"depth\": 2,\n    \"confidence\": 0.4,\n    \"weight\": 0.4,\n})).unwrap();\nassert!(!guessed.selector.declared() && guessed.confidence < 1.0);\n```"
    },
    "Selector": {
      "oneOf": [
        {
          "type": "string",
          "const": "seed",
          "description": "Named in the request."
        },
        {
          "type": "string",
          "const": "relation",
          "description": "Reached along a typed edge of the composed graph."
        },
        {
          "type": "string",
          "const": "scope_path",
          "description": "Its path lies under a path the request or a seed declares."
        },
        {
          "type": "string",
          "const": "contract",
          "description": "A directory contract that reaches one of those paths."
        },
        {
          "type": "string",
          "const": "governance",
          "description": "A rule or policy the layer applies to everything."
        },
        {
          "type": "string",
          "const": "session_state",
          "description": "A recorded session of this branch or of these paths, the handover the resolver\nchose, the newest checkpoint."
        },
        {
          "type": "string",
          "const": "intent_match",
          "description": "Inferred: the words of the intent occur in the object's own words."
        }
      ],
      "description": "What reached an entry. This is the provenance of the *selection*, and it is a different\nfact from the provenance of the file: `Relation` says the compiler followed an edge the\nlayer declares, `IntentMatch` says it guessed from words. An answer that did not keep\nthem apart could not say which half of itself to trust.\n\nThe division that matters is `Selector::declared`, and it is deliberately lopsided:\nsix of the seven selectors follow something the repository states, and exactly one\nguesses. That ratio is the design. Inference is confined to a single selector so that\nthe inferred part of any answer can be found, weighed and ignored, rather than being\nmixed through the rest at unmarked strength.\n\n```\nuse majordomus_cli::devcontext::Selector;\n\n// exactly one selector infers, and the answer can always be split on it\nlet inferred: Vec<&str> = Selector::ALL\n    .iter()\n    .filter(|s| !s.declared())\n    .map(|s| s.as_str())\n    .collect();\nassert_eq!(inferred, [\"intent_match\"]);\n\n// every selector has its own word, so a reason is never ambiguous\nlet mut words: Vec<&str> = Selector::ALL.iter().map(|s| s.as_str()).collect();\nlet before = words.len();\nwords.sort();\nwords.dedup();\nassert_eq!(words.len(), before);\n\n// and the word is the serialised form\nassert_eq!(\n    serde_json::to_value(Selector::ScopePath).unwrap(),\n    serde_json::json!(\"scope_path\"),\n);\n```"
    },
    "Excluded": {
      "type": "object",
      "properties": {
        "uri": {
          "type": "string",
          "description": "The canonical identifier, or the name the layer used when nothing resolves."
        },
        "kind": {
          "type": [
            "string",
            "null"
          ],
          "description": "Its kind, when it resolved to something."
        },
        "tier": {
          "$ref": "#/$defs/Tier",
          "description": "The tier it would have been spent from."
        },
        "reason": {
          "$ref": "#/$defs/ExclusionReason",
          "description": "Why it is not in the answer."
        },
        "detail": {
          "type": "string",
          "description": "The same, in words."
        },
        "cost_tokens": {
          "type": "integer",
          "format": "uint64",
          "minimum": 0,
          "description": "What it would have cost, so that a reader can decide whether to raise the budget."
        },
        "relevance": {
          "type": "number",
          "format": "double",
          "description": "What its relevance was."
        }
      },
      "required": [
        "uri",
        "tier",
        "reason",
        "detail",
        "cost_tokens",
        "relevance"
      ],
      "description": "One thing the compiler reached and did not give. Excluded is not the same as absent:\nan under-filled context is debugged from this list, not by guessing.\n\n`cost_tokens` is why this type exists in the shape it does. A budget that reported only\n*that* it dropped things would leave a caller guessing at a new ceiling; one that reports\nwhat each omission would have cost turns \"raise the budget\" into arithmetic. `detail`\ncarries the same in words, so the row is legible without the reader doing that\narithmetic themselves.\n\nThe ordering puts the tier first as an explicit rank, because the tier is the precedence\nthe budget spent in; inside a tier a reader is looking for a reason, and the identifier\nends the key so the list is total and the same tree always prints it the same way.\n\n```\nuse majordomus_cli::devcontext::{Excluded, ExclusionReason, Tier};\nuse majordomus_cli::order::canonical;\nuse serde_json::json;\n\nlet row = |uri: &str, tier: &str, reason: &str, cost: u64| -> Excluded {\n    serde_json::from_value(json!({\n        \"uri\": uri, \"tier\": tier, \"reason\": reason,\n        \"detail\": \"as measured\", \"cost_tokens\": cost, \"relevance\": 0.4,\n    })).unwrap()\n};\n\nlet mut excluded = vec![\n    row(\"majordomus://knowledge/b\", \"history\", \"budget\", 900),\n    row(\"majordomus://adr/0001\", \"decision\", \"superseded\", 300),\n    row(\"majordomus://knowledge/a\", \"history\", \"budget\", 1_200),\n];\ncanonical(&mut excluded);\n\n// the tier ranks first, so the decision tier precedes history whatever the reason says\nassert_eq!(excluded[0].tier, Tier::Decision);\nassert_eq!(excluded[0].reason, ExclusionReason::Superseded);\n// then the reason, then the identifier — which makes the order total\nlet rest: Vec<&str> = excluded[1..].iter().map(|e| e.uri.as_str()).collect();\nassert_eq!(rest, [\"majordomus://knowledge/a\", \"majordomus://knowledge/b\"]);\n\n// and the row a caller acts on says what it would have cost to keep\nlet biggest = excluded.iter().max_by_key(|e| e.cost_tokens).unwrap();\nassert_eq!(biggest.cost_tokens, 1_200);\n```"
    },
    "ExclusionReason": {
      "oneOf": [
        {
          "type": "string",
          "const": "budget",
          "description": "The budget ran out before its tier was filled."
        },
        {
          "type": "string",
          "const": "relevance",
          "description": "Its relevance fell below the floor the request set."
        },
        {
          "type": "string",
          "const": "depth",
          "description": "It is beyond the depth the request allows."
        },
        {
          "type": "string",
          "const": "stale",
          "description": "The object says it is deprecated or superseded."
        },
        {
          "type": "string",
          "const": "superseded",
          "description": "A newer version of the same thing is in the answer."
        },
        {
          "type": "string",
          "const": "unresolved",
          "description": "The layer names it and holds nothing under that name."
        }
      ],
      "description": "Why something the compiler selected did not survive.\n\nThe six divide into three that a caller can act on by asking again — `Budget`,\n`Relevance` and `Depth` are all consequences of the request's own limits, so raising the\nlimit brings the entry back — and three that are facts about the repository:\n`Stale` and `Superseded` mean the layer itself says this is not the current thing, and\n`Unresolved` means the layer names something it does not hold, which is a dangling\nreference somebody should fix.\n\n```\nuse majordomus_cli::devcontext::ExclusionReason as Why;\n\n// the ones a different request would change, against the ones it would not\nlet request_bound = [Why::Budget, Why::Relevance, Why::Depth];\nlet repository_bound = [Why::Stale, Why::Superseded, Why::Unresolved];\nfor r in repository_bound {\n    assert!(!request_bound.contains(&r), \"{} is not the caller's to fix\", r.as_str());\n}\n\n// and the word is the serialised form, snake_case for the two-word one\nassert_eq!(Why::Superseded.as_str(), \"superseded\");\nassert_eq!(\n    serde_json::to_value(Why::Unresolved).unwrap(),\n    serde_json::json!(Why::Unresolved.as_str()),\n);\n```"
    },
    "Deduplicated": {
      "type": "object",
      "properties": {
        "kept": {
          "type": "string",
          "description": "The entry that survived."
        },
        "dropped": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "What was folded into it, or dropped for it."
        },
        "key": {
          "type": "string",
          "description": "What made them the same thing: `uri`, `path` or `identity`."
        },
        "detail": {
          "type": "string",
          "description": "The same, in words."
        },
        "paths": {
          "type": "integer",
          "format": "uint",
          "minimum": 0,
          "description": "How many discovery paths reached the surviving entry."
        }
      },
      "required": [
        "kept",
        "dropped",
        "key",
        "detail",
        "paths"
      ],
      "description": "Two names, one thing. The compiler keeps one entry and records the collapse here, so\nthat \"why is this ADR listed once when four things point at it\" has an answer that is\nstate rather than a log line.\n\nOne type covers two different collapses, and `dropped` is how a reader tells them apart.\n**Structural** collapse is the same identifier reached several ways: nothing was\ndiscarded, so `dropped` is empty and `paths` is the count that makes the row worth\nprinting — this is the record that four things pointing at one ADR produced one entry.\n**Semantic** collapse is two different identifiers standing for one thing, one file\nindexed under two kinds or one rule at two versions: there `dropped` names what lost,\nbecause something really was discarded and a reader is entitled to know what.\n\n```\nuse majordomus_cli::devcontext::Deduplicated;\nuse serde_json::json;\n\n// structural: one identifier, four ways in, nothing discarded\nlet structural: Deduplicated = serde_json::from_value(json!({\n    \"kept\": \"majordomus://adr/0052\",\n    \"dropped\": [],\n    \"key\": \"uri\",\n    \"detail\": \"one object reached by 4 discovery path(s)\",\n    \"paths\": 4,\n})).unwrap();\nassert!(structural.dropped.is_empty(), \"nothing lost; this is a record, not a decision\");\nassert!(structural.paths > 1, \"which is what makes the row worth printing\");\nassert_eq!(structural.key, \"uri\");\n\n// semantic: two identifiers for one rule, and the older version lost\nlet semantic: Deduplicated = serde_json::from_value(json!({\n    \"kept\": \"majordomus://rule/project.x@2\",\n    \"dropped\": [\"majordomus://rule/project.x@1\"],\n    \"key\": \"identity\",\n    \"detail\": \"one rule at two versions; the newer is in the answer\",\n    \"paths\": 1,\n})).unwrap();\nassert_eq!(semantic.dropped, [\"majordomus://rule/project.x@1\"]);\nassert_ne!(semantic.kept, semantic.dropped[0], \"two names, and one of them lost\");\n```"
    },
    "Conflict": {
      "type": "object",
      "properties": {
        "current": {
          "type": "string",
          "description": "The entry the compiler treats as current."
        },
        "against": {
          "type": "string",
          "description": "The entry it stands against."
        },
        "kind": {
          "type": "string",
          "description": "What kind of disagreement: `supersedes` or `version`."
        },
        "detail": {
          "type": "string",
          "description": "The same, in words."
        }
      },
      "required": [
        "current",
        "against",
        "kind",
        "detail"
      ],
      "description": "Two things in the answer that do not agree, named rather than silently ordered.\n\nThe compiler could have resolved every disagreement by keeping one side, and for\ndeduplication it does. A conflict is what is left when both sides are legitimately in\nthe answer and they still do not agree — so the resolution belongs to the session\nreading it, and the compiler's job is to make sure the disagreement is visible rather\nthan implied by the order of two rows.\n\n`current` is the side the compiler treats as current, which is a statement about\nrecency, not correctness: `supersedes` means the layer declared one to replace the other,\n`version` means two versions of one thing are both here.\n\n```\nuse majordomus_cli::devcontext::Conflict;\nuse majordomus_cli::order::canonical;\nuse serde_json::json;\n\nlet row = |current: &str, against: &str, kind: &str| -> Conflict {\n    serde_json::from_value(json!({\n        \"current\": current, \"against\": against, \"kind\": kind,\n        \"detail\": \"both are in the answer and they disagree\",\n    })).unwrap()\n};\n\nlet mut conflicts = vec![\n    row(\"majordomus://rule/project.x@2\", \"majordomus://rule/project.x@1\", \"version\"),\n    row(\"majordomus://adr/0052\", \"majordomus://adr/0031\", \"supersedes\"),\n];\ncanonical(&mut conflicts);\n\n// filed under the kind of disagreement, so like reads with like\nlet kinds: Vec<&str> = conflicts.iter().map(|c| c.kind.as_str()).collect();\nassert_eq!(kinds, [\"supersedes\", \"version\"]);\n\n// and both sides are always named: the pair is the fact, not the winner alone\nfor c in &conflicts {\n    assert_ne!(c.current, c.against);\n    assert!(!c.detail.is_empty());\n}\n```"
    },
    "Budget": {
      "type": "object",
      "properties": {
        "limit_tokens": {
          "type": "integer",
          "format": "uint64",
          "minimum": 0,
          "description": "The ceiling this answer was compiled under, in estimated tokens."
        },
        "used_tokens": {
          "type": "integer",
          "format": "uint64",
          "minimum": 0,
          "description": "What the survivors cost."
        },
        "required_tokens": {
          "type": "integer",
          "format": "uint64",
          "minimum": 0,
          "description": "What the entries that cannot be dropped cost. Above the limit, the answer is\nover-budget and says so rather than dropping what it must keep."
        },
        "remaining_tokens": {
          "type": "integer",
          "format": "uint64",
          "minimum": 0,
          "description": "`limit - used`, floored at zero."
        },
        "over_budget": {
          "type": "boolean",
          "description": "True when the required entries alone exceed the limit."
        },
        "bytes_per_token": {
          "type": "integer",
          "format": "uint64",
          "minimum": 0,
          "description": "Bytes per token, the divisor the estimate uses."
        },
        "tiers": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/TierSpend"
          },
          "description": "Per tier, in the order the budget spends in."
        }
      },
      "required": [
        "limit_tokens",
        "used_tokens",
        "required_tokens",
        "remaining_tokens",
        "over_budget",
        "bytes_per_token",
        "tiers"
      ],
      "description": "The budget, and what became of it.\n\n`over_budget` is the field to read and the one most likely to be misread. It does not\nmean the compiler overspent by accident: every entry the budget has a say over is refused\nthe moment it would not fit, so the only way past the ceiling is through the entries that\nmay not be dropped — the seeds, the policy, the scope, a blocking rule. So\n`over_budget` means *the things this context cannot do without already cost more than you\nallowed*, and `required_tokens` is the explanation of it. The honest answer to that is to\nsay so, not to return a context missing the thing it was compiled about.\n\n`remaining_tokens` is floored at zero rather than going negative, so it is safe to\ndisplay; the overspend is read from `used_tokens` against `limit_tokens`, which is why\nboth are carried.\n\n```\nuse majordomus_cli::devcontext::{Budget, Tier, BYTES_PER_TOKEN};\nuse serde_json::json;\n\nlet tier = |name: &str, tokens: u64, required: usize| json!({\n    \"tier\": name, \"meaning\": \"as this tier is for\", \"selected\": 1,\n    \"excluded\": 0, \"tokens\": tokens, \"required\": required,\n});\n\n// the undroppable entries alone cost more than the caller allowed\nlet b: Budget = serde_json::from_value(json!({\n    \"limit_tokens\": 1_000,\n    \"used_tokens\": 2_500,\n    \"required_tokens\": 2_500,\n    \"remaining_tokens\": 0,\n    \"over_budget\": true,\n    \"bytes_per_token\": BYTES_PER_TOKEN,\n    \"tiers\": [tier(\"task\", 2_000, 1), tier(\"governance\", 500, 1)],\n})).unwrap();\n\nassert!(b.over_budget);\nassert!(b.used_tokens > b.limit_tokens, \"the overspend is read from these two\");\nassert_eq!(b.required_tokens, b.used_tokens, \"all of it was undroppable\");\nassert_eq!(b.remaining_tokens, 0, \"floored, so it is safe to display\");\n\n// the divisor is carried so a caller measuring tokens differently can convert\n// rather than guess what the estimate meant\nassert_eq!(b.bytes_per_token, BYTES_PER_TOKEN);\n\n// and the tiers are reported in the order the budget spends in\nlet order: Vec<Tier> = b.tiers.iter().map(|t| t.tier).collect();\nassert_eq!(order, [Tier::Task, Tier::Governance]);\nassert!(order.windows(2).all(|w| w[0] < w[1]));\n```"
    },
    "TierSpend": {
      "type": "object",
      "properties": {
        "tier": {
          "$ref": "#/$defs/Tier",
          "description": "The tier."
        },
        "meaning": {
          "type": "string",
          "description": "What it is for."
        },
        "selected": {
          "type": "integer",
          "format": "uint",
          "minimum": 0,
          "description": "How many entries survived in it."
        },
        "excluded": {
          "type": "integer",
          "format": "uint",
          "minimum": 0,
          "description": "How many were dropped from it for budget."
        },
        "tokens": {
          "type": "integer",
          "format": "uint64",
          "minimum": 0,
          "description": "Tokens the survivors cost."
        },
        "required": {
          "type": "integer",
          "format": "uint",
          "minimum": 0,
          "description": "Entries in it that cannot be dropped."
        }
      },
      "required": [
        "tier",
        "meaning",
        "selected",
        "excluded",
        "tokens",
        "required"
      ],
      "description": "What one tier cost, how much of it survived, and how much of that could not have been\ndropped.\n\nThere is no per-tier ceiling, which is worth saying because the name suggests one: the\nbudget is a single pot spent in tier order, and this is the *report* of what each tier\ntook from it, not an allowance it was given. A tier with many exclusions is therefore\nnot a tier that hit its own limit — it is a tier that was reached after the pot had been\nspent by the tiers above it.\n\n`meaning` is carried so that a reader deciding what to raise the budget for does not\nhave to look up what `knowledge` holds; `required` is the part of `selected` the budget\nhad no say over.\n\n```\nuse majordomus_cli::devcontext::{Tier, TierSpend};\nuse serde_json::json;\n\nlet t: TierSpend = serde_json::from_value(json!({\n    \"tier\": \"governance\",\n    \"meaning\": Tier::Governance.meaning(),\n    \"selected\": 9,\n    \"excluded\": 2,\n    \"tokens\": 6_400,\n    \"required\": 3,\n})).unwrap();\n\nassert_eq!(t.tier, Tier::Governance);\n// the undroppable part is a part of what survived, never a separate count\nassert!(t.required <= t.selected);\n// and the row explains its own tier, so \"2 excluded\" is actionable\nassert_eq!(t.meaning, Tier::Governance.meaning());\nassert!(t.meaning.contains(\"policy\"));\n```"
    },
    "Diagnostic": {
      "type": "object",
      "properties": {
        "severity": {
          "$ref": "#/$defs/Severity",
          "description": "How bad."
        },
        "code": {
          "type": "string",
          "description": "A stable machine-readable code, e.g. `unknown_key`, `duplicate_identity`."
        },
        "path": {
          "type": [
            "string",
            "null"
          ],
          "description": "Repository-relative path of the file concerned, when there is one."
        },
        "message": {
          "type": "string",
          "description": "What is wrong, for a person; the code is for a program."
        }
      },
      "required": [
        "severity",
        "code",
        "message"
      ],
      "description": "One finding about the declarative state, named by a stable code, tied to a path where\nthere is one, and carrying the command that reproduces it where there is one."
    },
    "Severity": {
      "oneOf": [
        {
          "type": "string",
          "const": "info",
          "description": "Worth knowing; changes nothing."
        },
        {
          "type": "string",
          "const": "warning",
          "description": "Something to look at; the object is still served."
        },
        {
          "type": "string",
          "const": "error",
          "description": "The file is excluded and the index is degraded."
        }
      ],
      "description": "How bad a diagnostic is. `Error` excludes the file it concerns from the index and puts\nthe index into the degraded state; `Warning` and `Info` do neither."
    }
  }
}

Examples

These are the capability's own benchmark cases: the same inputs the benchmark runs and the OpenAPI document shows.

no-seed
{}
issue
{
  "issue": "I0001"
}
milestone
{
  "milestone": "M000"
}
intent
{
  "intent": "the context a session is given and the budget it costs"
}