Přeskočit na obsah

Tato stránka byla automaticky přeložena z angličtiny, jediného jazyka, ve kterém se tento web píše. Číst originál.

HTTP API: devcontext

Kontext, který má dostat vývojová relace, odvozený z repozitáře, a ne složený ručně: pro daný úkol, milník, záměr (intent) nebo sadu cest vrátí kanonické objekty, které se práce týkají, proč je každý z nich v odpovědi, co bylo vynecháno a proč, co se do čeho sloučilo a celkové náklady vůči rozpočtu. Výběr je strukturovaný — každá položka si drží svůj identifikátor, původ, selektor, který ji zasáhl, a míru jistoty — protože vykreslení promptu je projekcí výběru, a ne výběrem samotným.

Vykresleno z docs/generated/openapi.json, který spustitelný soubor generuje ze svého registru schopností a CI jej odmítne, je-li zastaralý. Surový dokument je na /openapi.json; běžící server servíruje stejný dokument na vlastní /openapi.json a Swagger UI na /swagger.

devcontext

Kontext, který má dostat vývojová relace, odvozený z repozitáře, a ne složený ručně: pro daný úkol, milník, záměr (intent) nebo sadu cest vrátí kanonické objekty, které se práce týkají, proč je každý z nich v odpovědi, co bylo vynecháno a proč, co se do čeho sloučilo a celkové náklady vůči rozpočtu. Výběr je strukturovaný — každá položka si drží svůj identifikátor, původ, selektor, který ji zasáhl, a míru jistoty — protože vykreslení promptu je projekcí výběru, a ne výběrem samotným.

GET /api/v1/devcontext devcontext.compile

Zkompilovat kontext pro kus práce

Objekty, které by měla dostat relace pracující na tomto úkolu, milníku, záměru nebo sadě cest: každý s kanonickým identifikátorem, původem z indexu, každou cestou objevení, která k němu vedla, s důvodem a jistotou, jeho verzí, vrstvou a cenou v odhadovaných tokenech — následované vším, k čemu se došlo a co nebylo dáno, s důvodem u každého, vším, co se sloučilo do jednoho záznamu, každou dvojicí, která si neodpovídá, a výdaji po vrstvách proti rozpočtu. Deterministické pro daný strom a požadavek; otisk indexu, z něhož bylo sestaveno, je v odpovědi.

  • query
  • behaviorally_verified
  • vyžaduje se měření výkonu
  • cache procesu (max_entries=16)
  • MCP nástroj majordomus_devcontext
  • $ devcontext compile

Parametry dotazu

názevtyppovinnépopispříklady
issue string ne Id úkolu (`I0301`) nebo jeho kanonický identifikátor. Z něj vyplývá jeho milník, jeho závislosti, jeho deklarovaný rozsah a rozhodnutí nad tímto rozsahem. issue: "I0001"
milestone string ne Id nebo slug milníku, případně jeho kanonický identifikátor. milestone: "M000"
intent string ne Co se relace snaží udělat, slovy. Jediný vstup, ze kterého kompilátor usuzuje, a každý záznam, který vytvoří, to uvádí a nese jistotu pod jedna. intent: "the context a session is given and the budget it costs"
cesty array of string ne Cesty relativní k repozitáři, kterých se práce dotýká, přidané k tomu, co deklarují seedy.
uris array of string ne Kanonické identifikátory, ze kterých se přímo vychází, pro požadavek na něco, co není úkol ani milník.
budget_tokens integer (uint64) ne Strop v odhadovaných tokenech; `DEFAULT_BUDGET_TOKENS`, když chybí.
max_depth integer (uint) ne Jak daleko od výchozího bodu průchod sahá; při absenci `DEFAULT_MAX_DEPTH`.
spodní mez číslo (double) ne Relevance, pod kterou se záznam hlásí a nevydává; bez zadání `DEFAULT_FLOOR`.
all_blocking_rules boolean ne Každé blokující pravidlo vrstvy, nejen ta, kterých se práce dotkne. Poctivé a obvykle nad rozpočtem: odpověď pak říká `over_budget`, místo aby zahodila, co zahodit nesmí.

Vyzkoušejte

Port je ten, který server zalogoval; 8741 je ten, o který žádá jako první. Hodnoty jsou první příklad operace.

curl -s "http://127.0.0.1:8741/api/v1/devcontext?issue=I0001&milestone=M000&intent=the%20context%20a%20session%20is%20given%20and%20the%20budget%20it%20costs"

Testováno v apps/majordomus-cli/tests/http_serve.rs, který přehrává routu s těmito hodnotami proti serveru na skutečném socketu.

Odpovědi

statustělovýznam
200 CompiledContext Sestavit kontext pro kus práce: výsledek.
400 ErrorBody invalid_input: Vstup neodpovídá schématu: parametr špatného typu, neznámý parametr nebo hodnota, kterou schopnost odmítne.
404 ErrorBody not_found: Vstup pojmenovává něco, co repozitář nemá.
500 ErrorBody internal: Schopnost selhala z vlastního důvodu; zpráva ho pojmenovává.
výchozí ErrorBody Jakékoli jiné selhání, které transport hlásí: 405 method_not_allowed pro jinou metodu na této cestě, 400 invalid_input pro tělo, které není objektem JSON.
GET /api/v1/devcontext/explain devcontext.explain

Proč něco je nebo není ve zkompilovaném kontextu

Jeden kanonický identifikátor posouzený pod požadavkem: zda byl vybrán, dosažen a vyloučen, sloučen do jiného identifikátoru, držen indexem a nikdy nedosažen, nebo neznámý — se samotným záznamem, vyloučením nebo sloučením a rozpočtem, pod nímž bylo posouzení provedeno, aby se na `excluded for budget` dalo reagovat bez druhého volání.

  • query
  • behaviorally_verified
  • vyžaduje se měření výkonu
  • cache procesu (max_entries=8)
  • Nástroj MCP majordomus_devcontext_explain
  • $ devcontext explain

Parametry dotazu

názevtyppovinnépopispříklady
uri string ano Kanonický identifikátor k vysvětlení. chybí: "majordomus://rule/nothing-like-this@1"policy: "majordomus://policy/.ai/repo/policy.yaml"
issue string ne Id úkolu (`I0301`) nebo jeho kanonický identifikátor. Z něj vyplývá jeho milník, jeho závislosti, jeho deklarovaný rozsah a rozhodnutí nad tímto rozsahem.
milestone string ne Id nebo slug milníku, případně jeho kanonický identifikátor.
intent string ne Co se relace snaží udělat, slovy. Jediný vstup, ze kterého kompilátor usuzuje, a každý záznam, který vytvoří, to uvádí a nese jistotu pod jedna.
cesty array of string ne Cesty relativní k repozitáři, kterých se práce dotýká, přidané k tomu, co deklarují seedy.
uris array of string ne Kanonické identifikátory, ze kterých se přímo vychází, pro požadavek na něco, co není úkol ani milník.
budget_tokens integer (uint64) ne Strop v odhadovaných tokenech; `DEFAULT_BUDGET_TOKENS`, když chybí.
max_depth integer (uint) ne Jak daleko od výchozího bodu průchod sahá; při absenci `DEFAULT_MAX_DEPTH`.
spodní mez číslo (double) ne Relevance, pod kterou se záznam hlásí a nevydává; bez zadání `DEFAULT_FLOOR`.
all_blocking_rules boolean ne Každé blokující pravidlo vrstvy, nejen ta, kterých se práce dotkne. Poctivé a obvykle nad rozpočtem: odpověď pak říká `over_budget`, místo aby zahodila, co zahodit nesmí.

Vyzkoušejte

Port je ten, který server zalogoval; 8741 je ten, o který žádá jako první. Hodnoty jsou první příklad operace.

curl -s "http://127.0.0.1:8741/api/v1/devcontext/explain?uri=majordomus%3A%2F%2Frule%2Fnothing-like-this%401"

Testováno v apps/majordomus-cli/tests/http_serve.rs, který přehrává routu s těmito hodnotami proti serveru na skutečném socketu.

Odpovědi

statustělovýznam
200 Vysvětlení Proč je jedna věc nebo není ve zkompilovaném kontextu: výsledek.
400 ErrorBody invalid_input: Vstup neodpovídá schématu: parametr špatného typu, neznámý parametr nebo hodnota, kterou schopnost odmítne.
404 ErrorBody not_found: Vstup pojmenovává něco, co repozitář nemá.
500 ErrorBody internal: Schopnost selhala z vlastního důvodu; zpráva ho pojmenovává.
výchozí ErrorBody Jakékoli jiné selhání, které transport hlásí: 405 method_not_allowed pro jinou metodu na této cestě, 400 invalid_input pro tělo, které není objektem JSON.
GET /api/v1/devcontext/policy devcontext.policy

Vlastní pravidla kompilátoru

O čem kompilátor rozhoduje a jak: vrstvy v pořadí, v němž rozpočet utrácí, s druhy, které v každé přistávají, každý druh hrany, který složený graf deklaruje, s násobitelem relevance, jímž ji kompilátor v každém směru následuje — nebo důvod, proč ji odmítá následovat vůbec — selektory s tím, které z nich odvozují místo čtení, a výchozí hodnoty rozpočtu, hloubky a prahu relevance. Čtěte toto, místo abyste politiku odvozovali z odpovědi.

  • query
  • behaviorally_verified
  • vyžaduje se měření výkonu
  • cache vypnuta
  • nástroj MCP majordomus_devcontext_policy
  • zdroj MCP {"name": "devcontext-policy", "uri": "majordomus://devcontext/policy"}
  • $ devcontext policy

Žádné parametry.

Vyzkoušejte

Port je ten, který server zalogoval; 8741 je ten, o který žádá jako první. Hodnoty jsou první příklad operace.

curl -s "http://127.0.0.1:8741/api/v1/devcontext/policy"

Testováno v apps/majordomus-cli/tests/http_serve.rs, který přehrává routu s těmito hodnotami proti serveru na skutečném socketu.

Odpovědi

statustělovýznam
200 CompilerPolicy Vlastní pravidla kompilátoru: výsledek.
400 ErrorBody invalid_input: Vstup neodpovídá schématu: parametr špatného typu, neznámý parametr nebo hodnota, kterou schopnost odmítne.
404 ErrorBody not_found: Vstup pojmenovává něco, co repozitář nemá.
500 ErrorBody internal: Schopnost selhala z vlastního důvodu; zpráva ho pojmenovává.
výchozí ErrorBody Jakékoli jiné selhání, které transport hlásí: 405 method_not_allowed pro jinou metodu na této cestě, 400 invalid_input pro tělo, které není objektem JSON.

Schémata

Komponenty, které používají jen operace tohoto tagu. Každý řádek je pole daného typu, jeho doc komentář je popis; typ vlastněný jinou stránkou na ni odkazuje.

Rozpočet

object
Rozpočet a co se s ním stalo.

over_budget je pole ke čtení a to, které se nejspíš přečte špatně. Neznamená, že kompilátor omylem překročil rozpočet: každý záznam, do něhož má rozpočet co mluvit, je odmítnut ve chvíli, kdy by se nevešel, takže strop jde překročit jen záznamy, které se zahodit nesmějí — seedy, politikou, rozsahem, blokujícím pravidlem. over_budget tedy znamená věci, bez kterých tento kontext nemůže být, už stojí víc, než jste dovolili, a required_tokens je jeho vysvětlení. Poctivá odpověď na to je to říct, ne vrátit kontext, kterému chybí věc, o níž byl sestaven.

remaining_tokens je ohraničeno nulou místo klesání do záporných hodnot, takže je bezpečné ho zobrazit; překročení se čte z used_tokens proti limit_tokens, a proto se nesou obě.

use majordomus_cli::devcontext::{Budget, Tier, BYTES_PER_TOKEN};
use serde_json::json;

let tier = |name: &str, tokens: u64, required: usize| json!({
    "tier": name, "meaning": "as this tier is for", "selected": 1,
    "excluded": 0, "tokens": tokens, "required": required,
});

// the undroppable entries alone cost more than the caller allowed
let b: Budget = serde_json::from_value(json!({
    "limit_tokens": 1_000,
    "used_tokens": 2_500,
    "required_tokens": 2_500,
    "remaining_tokens": 0,
    "over_budget": true,
    "bytes_per_token": BYTES_PER_TOKEN,
    "tiers": [tier("task", 2_000, 1), tier("governance", 500, 1)],
})).unwrap();

assert!(b.over_budget);
assert!(b.used_tokens > b.limit_tokens, "the overspend is read from these two");
assert_eq!(b.required_tokens, b.used_tokens, "all of it was undroppable");
assert_eq!(b.remaining_tokens, 0, "floored, so it is safe to display");

// the divisor is carried so a caller measuring tokens differently can convert
// rather than guess what the estimate meant
assert_eq!(b.bytes_per_token, BYTES_PER_TOKEN);

// and the tiers are reported in the order the budget spends in
let order: Vec<Tier> = b.tiers.iter().map(|t| t.tier).collect();
assert_eq!(order, [Tier::Task, Tier::Governance]);
assert!(order.windows(2).all(|w| w[0] < w[1]));
vlastnosttyppovinnépopis
limit_tokens integer (uint64) ano Strop, pod nímž byla tato odpověď zkompilována, v odhadovaných tokenech.
used_tokens integer (uint64) ano Kolik stojí ti, kdo přežili.
required_tokens integer (uint64) ano Kolik stojí položky, které nelze vypustit. Nad limitem je odpověď over-budget a říká to, místo aby vypustila, co musí zachovat.
remaining_tokens integer (uint64) ano limit - used, omezené zdola nulou.
over_budget boolean ano Pravda, když samotné povinné položky překračují limit.
bytes_per_token integer (uint64) ano Bajtů na token, dělitel, který odhad používá.
úrovně pole typu TierSpend → TierSpend ano Podle úrovně, v pořadí, v jakém rozpočet utrácí.

Používá CompiledContext Explanation

CommitReference

object
Jeden commit, který vrstva sama jmenuje, a záznam, který ho jmenoval.

Faktem je dvojice, ne samotný commit. Jeden commit jmenovaný třemi záznamy jsou tři reference, protože každý záznam je samostatné tvrzení o tom, k čemu ten commit byl, a jejich sloučení by ztratilo přisouzení — což je jediný důvod, proč zde commit vůbec nést. Proto named_by uzavírá klíč řazení a dvě reference na jeden commit přežijí obě.

use majordomus_cli::devcontext::CommitReference;
use majordomus_cli::order::canonical;
use serde_json::json;

let by = |commit: &str, record: &str| -> CommitReference {
    serde_json::from_value(json!({"commit": commit, "named_by": record})).unwrap()
};

let mut commits = vec![
    by("cd04012", "majordomus://session/b"),
    by("cd04012", "majordomus://session/a"),
    by("5e31dd4", "majordomus://issue/I0301"),
];
canonical(&mut commits);

// filed under the commit, which is what a reader looks for
let order: Vec<&str> = commits.iter().map(|c| c.commit.as_str()).collect();
assert_eq!(order, ["5e31dd4", "cd04012", "cd04012"]);

// and the record ends the key, so one commit named twice stays two facts
assert_eq!(commits[1].named_by, "majordomus://session/a");
assert_eq!(commits[2].named_by, "majordomus://session/b");

// `covers` is what the record said the commit was for, when it said
let with_reason = by("cd04012", "majordomus://session/a");
assert!(with_reason.covers.is_none(), "absent rather than an empty sentence");
vlastnosttyppovinnépopis
commit string ano Commit tak, jak jej záznam napsal.
named_by string ano Záznam, který ji pojmenoval.
pokrývá string | null ne K čemu podle záznamu commit byl.

Používá GitContext

CompiledContext

object
Zkompilovaný vývojový kontext: co dát relaci, co se vynechalo, co se do čeho sbalilo a rozpočet, pod nímž se o tom všem rozhodlo.

Toto je kanonická podoba. Vykreslení do promptu je navazující projekce této hodnoty a neprovádí se zde — díky struktuře může volající znovu rozpočtovat, přeřadit nebo vysvětlit výběr, aniž by ho překompiloval.

Nad každou odpovědí platí dva invarianty a ty ji činí bezpečnou k cachování a porovnávání. fingerprint se vztahuje k indexu, z něhož byla sestavena, takže stejný požadavek proti stejnému otisku je stejná odpověď a jiný otisk je jiný strom — volající rozliší dva stromy, aniž by se ptal. A budget.used_tokens je přesně součet nákladů selected, takže aritmetika v rozpočtu popisuje seznam vedle ní, ne průchod, který byl mezitím odfiltrován.

use majordomus_cli::devcontext::{compile, CompileInput, CompiledContext};
use majordomus_cli::devcontext::{Tier, DEFAULT_BUDGET_TOKENS};
use majordomus_cli::synthetic::SyntheticRepository;

let repo = SyntheticRepository::small().unwrap();
let ctx = repo.context().unwrap();

let c: CompiledContext = compile(&ctx, CompileInput::default()).unwrap();

// the answer says which tree it is true of, and nothing else has to be asked
assert_eq!(c.schema, 1);
assert!(!c.fingerprint.is_empty(), "an answer that names no tree is not one");

// the budget describes the list beside it
let spent: u64 = c.selected.iter().map(|e| e.cost_tokens).sum();
assert_eq!(c.budget.used_tokens, spent);
assert_eq!(c.budget.limit_tokens, DEFAULT_BUDGET_TOKENS);

// every tier is reported, so an empty tier is visibly empty rather than missing
assert_eq!(c.budget.tiers.len(), Tier::ORDER.len());

// a request that named nothing has no seeds and is still an answer
assert!(c.seeds.is_empty());

// and recompiling the same request over the same tree gives the same answer
let again: CompiledContext = compile(&ctx, CompileInput::default()).unwrap();
assert_eq!(again.fingerprint, c.fingerprint);
assert_eq!(again.selected.len(), c.selected.len());
vlastnosttyppovinnépopis
schema integer (uint64) ano Formát této odpovědi.
otisk string ano Index, ze kterého byl tento výstup zkompilován, přes cestu a obsah každého objektu. Dvě odpovědi se stejným otiskem a stejným požadavkem jsou táž odpověď; jiný otisk je jiný strom.
git GitContext → GitContext ano Head, o kterém je odpověď pravdivá.
seedy pole Seed → Seed ano O co se žádalo.
vybráno pole ContextEntry → ContextEntry ano Co relaci dát, v pořadí, v jakém se utrácí rozpočet: úroveň, pak relevance, pak kanonický identifikátor.
vyloučeno pole Excluded → Excluded ano Co bylo dosaženo a nedáno, s důvodem u každého.
deduplikováno pole Deduplicated → Deduplicated ano Co se do čeho sloučilo.
konflikty pole Conflict → Conflict ano Co se s čím neshoduje.
rozpočet Budget → Budget ano Rozpočet a jeho útrata podle úrovní.
diagnostika pole Diagnostic → Diagnostic ano Co kompilátor nemohl přečíst: zdroj znalostí, který vrstva jmenuje a nedrží, záznam relace, který už nepopisuje tento checkout. Nikdy chyba: chybějící zdroj je odpověď.

Používá devcontext.compile

CompilerPolicy

object
Vlastní pravidla kompilátoru, aby volající četl rozhodnutí, a ne je odvozoval z odpovědi.

To je odpověď na otázku „proč jsem to dostal“, položenou jednou místo zpětného zjišťování z každého kontextu. Je to projekce tabulek a konstant, z nichž je tento modul postaven — Tier::ORDER, select::EDGES, Selector::ALL a čtyř výchozích hodnot — a nemá žádná vlastní čísla, takže zveřejněná pravidla a kód, který je uplatňuje, se nemohou rozejít.

Výchozí hodnoty jsou tu proto, že je dostane požadavek, který nic neříká, a volající, který požadavek ladí, musí vědět, od čeho se odchyluje.

use majordomus_cli::devcontext::{policy, CompilerPolicy, Selector, Tier};
use majordomus_cli::devcontext::{BYTES_PER_TOKEN, DEFAULT_BUDGET_TOKENS};
use majordomus_cli::devcontext::{DEFAULT_FLOOR, DEFAULT_MAX_DEPTH};
use majordomus_cli::graph;
use majordomus_cli::synthetic::SyntheticRepository;

let repo = SyntheticRepository::small().unwrap();
let ctx = repo.context().unwrap();
let rules: CompilerPolicy = policy(&ctx).unwrap();

// it names the graph it reads relations from, rather than leaving it to be guessed
assert_eq!(rules.schema, 1);
assert_eq!(rules.graph, graph::COMPOSED);

// the three tables are complete
assert_eq!(rules.tiers.len(), Tier::ORDER.len());
assert_eq!(rules.selectors.len(), Selector::ALL.len());
assert!(!rules.edges.is_empty());

// and the published defaults are the constants a silent request actually gets
assert_eq!(rules.default_budget_tokens, DEFAULT_BUDGET_TOKENS);
assert_eq!(rules.default_max_depth, DEFAULT_MAX_DEPTH);
assert_eq!(rules.default_floor, DEFAULT_FLOOR);
assert_eq!(rules.bytes_per_token, BYTES_PER_TOKEN);
vlastnosttyppovinnépopis
schema integer (uint64) ano Formát této odpovědi.
graph string ano Graf, z něhož se relace čtou.
úrovně pole TierRule → TierRule ano Úrovně v pořadí, v jakém je rozpočet utrácí.
hrany pole EdgeRule → EdgeRule ano Každý druh hrany, který složený graf má, sledovaný nebo odmítnutý.
selektory pole SelectorRule → SelectorRule ano Selektory a které z nich odvozují.
default_budget_tokens integer (uint64) ano Výchozí strop v odhadovaných tokenech.
default_max_depth integer (uint) ano Výchozí limit hloubky.
default_floor číslo (double) ano Výchozí práh relevance.
bytes_per_token integer (uint64) ano Bajtů na odhadovaný token.

Používá devcontext.policy

Konflikt

object
Dvě věci v odpovědi, které si neodpovídají, pojmenované místo tichého seřazení.

Kompilátor mohl každý rozpor vyřešit ponecháním jedné strany a pro deduplikaci to dělá. Konflikt je to, co zbude, když jsou obě strany oprávněně v odpovědi a přesto se neshodují — takže rozřešení patří relaci, která to čte, a úkolem kompilátoru je zajistit, aby byl rozpor viditelný, ne implikovaný pořadím dvou řádků.

current je strana, kterou kompilátor považuje za aktuální, což je tvrzení o novosti, nikoli o správnosti: supersedes znamená, že vrstva deklarovala, že jedno nahrazuje druhé, version znamená, že dvě verze téže věci jsou obě zde.

use majordomus_cli::devcontext::Conflict;
use majordomus_cli::order::canonical;
use serde_json::json;

let row = |current: &str, against: &str, kind: &str| -> Conflict {
    serde_json::from_value(json!({
        "current": current, "against": against, "kind": kind,
        "detail": "both are in the answer and they disagree",
    })).unwrap()
};

let mut conflicts = vec![
    row("majordomus://rule/project.x@2", "majordomus://rule/project.x@1", "version"),
    row("majordomus://adr/0052", "majordomus://adr/0031", "supersedes"),
];
canonical(&mut conflicts);

// filed under the kind of disagreement, so like reads with like
let kinds: Vec<&str> = conflicts.iter().map(|c| c.kind.as_str()).collect();
assert_eq!(kinds, ["supersedes", "version"]);

// and both sides are always named: the pair is the fact, not the winner alone
for c in &conflicts {
    assert_ne!(c.current, c.against);
    assert!(!c.detail.is_empty());
}
vlastnosttyppovinnépopis
aktuální string ano Záznam, který kompilátor považuje za aktuální.
proti string ano Záznam, proti kterému stojí.
druh string ano Jaký druh rozporu: supersedes nebo version.
detail string ano Totéž, slovy.

Používá CompiledContext

ContextEntry

object
Jedna věc, o níž kompilátor rozhodl, že ji relace má dostat: co to je, jak se k tomu došlo, co to stojí a nakolik tomu kompilátor důvěřuje.

Pozoruhodné je, že nenese prózu. Identifikátor tu je a tělo ne, protože tělo patří objektu a objects.get ho obsluhuje celé; jeho kopírování by dalo vrstvu do odpovědi dvakrát a zploštilo strukturu do textu dřív, než by cokoli rozhodlo, co zachovat. Záznam je rozhodnutí o objektu, ne jeho kopie.

Tři ze skalárů se odvozují z discovered_by, a nejsou na něm nezávislé, a čtenář je může ověřit: confidence je nejvyšší z libovolné cesty, depth nejkratší a discovered_by délky větší než jedna je důkaz, že proběhla deduplikace. required je veto — blokující pravidlo, politika, rozsah nebo seed — a je to jediné pole, které přebíjí rozpočet.

use majordomus_cli::devcontext::{tokens_for, ContextEntry, Selector, Tier};
use serde_json::json;

// one ADR, reached twice: once along a declared edge and once by matching the intent
let e: ContextEntry = serde_json::from_value(json!({
    "uri": "majordomus://adr/0052",
    "kind": "adr",
    "title": "The episode is the boundary",
    "provenance": {
        "path": ".ai/repo/adrs/0052.md", "directory": ".ai/repo/adrs",
        "source_class": "adr", "section": "adrs", "bytes": 8_192,
    },
    "tier": "decision",
    "relevance": 0.82,
    "confidence": 1.0,
    "discovered_by": [
        {"selector": "relation", "reason": "in force over this scope",
         "via": "majordomus://issue/I0301", "edge": "governed_by",
         "depth": 1, "confidence": 1.0, "weight": 0.9},
        {"selector": "intent_match", "reason": "the intent's words occur in it",
         "depth": 2, "confidence": 0.4, "weight": 0.4},
    ],
    "depth": 1,
    "cost_tokens": 2_048,
    "cost_bytes": 8_192,
    "version": "2026-09-11",
    "status": "accepted",
    "required": false,
})).unwrap();

assert_eq!(e.tier, Tier::Decision);

// the cost is the estimate over the measured bytes, and the answer carries both halves
assert_eq!(e.cost_tokens, tokens_for(e.cost_bytes));

// the derived scalars agree with the paths they are derived from
let best = e.discovered_by.iter().map(|d| d.confidence).fold(0.0_f64, f64::max);
assert_eq!(e.confidence, best);
assert_eq!(e.depth, e.discovered_by.iter().map(|d| d.depth).min().unwrap());

// two paths for one entry: the record that four things pointing here is one entry
assert_eq!(e.discovered_by.len(), 2);
assert_eq!(e.discovered_by[0].selector, Selector::Relation, "best path first");

// and the prose is not here: the identifier is how a caller gets the body
assert!(e.uri.starts_with("majordomus://"));
vlastnosttyppovinnépopis
uri string ano Kanonický identifikátor: majordomus://<kind>/<identity>, v odpovědi jedinečný.
druh string ano Druh, který pro něj vrstva deklarovala.
název string | null ne Jeho název, pokud ho má.
provenance EntryProvenance → EntryProvenance ano Kde žije.
úroveň Tier → Tier ano Z rozpočtu které úrovně se čerpá.
relevance číslo (double) ano 0,0 až 1,0, deterministické pro daný strom a požadavek. Pořadí v rámci úrovně.
jistota číslo (double) ano Nejvyšší jistota z kterékoli cesty, která se k němu dostala.
discovered_by pole Discovery → Discovery ano Každá cesta, která k ní vedla, nejlepší první. Délka větší než jedna je důkaz, že proběhla deduplikace.
hloubka integer (uint) ano Nejkratší počet skoků od seedu.
cost_tokens integer (uint64) ano Odhadované tokeny: bajty souboru dělené BYTES_PER_TOKEN, nikdy méně než jedna. Odhad, který se tak nazývá.
cost_bytes integer (uint64) ano Bajty souboru, změřená polovina ceny.
verze string | null ne Verze nebo datum, které objekt o sobě uvádí, když nějaké uvádí: verze pravidla, datum ADR, updated_at úkolu.
status string | null ne Slovo stavu, které objekt deklaruje, pokud nějaké deklaruje.
povinné boolean ano Pravda pro blokující pravidlo, politiku nebo rozsah: nikdy se kvůli rozpočtu nevypouští.
fakta mapa řetězců ne Fakta hodná nesení o tomto záznamu, která nejsou próza objektu — třída pravidla, milník úkolu, commity, které jmenují důkazy úkolu.

Používá CompiledContext Explanation

Deduplikováno

object
Dvě jména, jedna věc. Kompilátor drží jednu položku a zde zaznamená sloučení, aby otázka „proč je tento ADR uveden jednou, když na něj ukazují čtyři věci“ měla odpověď, která je stavem, ne řádkem logu.

Jeden typ pokrývá dva různé kolapsy a dropped je způsob, jak je čtenář rozliší. Strukturální kolaps je tentýž identifikátor dosažený několika cestami: nic se nezahodilo, takže dropped je prázdné a paths je počet, který činí řádek hodným vytištění — to je záznam, který čtyři věci odkazující na jedno ADR vytvořily jako jednu položku. Sémantický kolaps jsou dva různé identifikátory, které stojí za jednu věc, jeden soubor indexovaný pod dvěma druhy nebo jedno pravidlo ve dvou verzích: tam dropped jmenuje, co prohrálo, protože se skutečně něco zahodilo a čtenář má právo to vědět.

use majordomus_cli::devcontext::Deduplicated;
use serde_json::json;

// structural: one identifier, four ways in, nothing discarded
let structural: Deduplicated = serde_json::from_value(json!({
    "kept": "majordomus://adr/0052",
    "dropped": [],
    "key": "uri",
    "detail": "one object reached by 4 discovery path(s)",
    "paths": 4,
})).unwrap();
assert!(structural.dropped.is_empty(), "nothing lost; this is a record, not a decision");
assert!(structural.paths > 1, "which is what makes the row worth printing");
assert_eq!(structural.key, "uri");

// semantic: two identifiers for one rule, and the older version lost
let semantic: Deduplicated = serde_json::from_value(json!({
    "kept": "majordomus://rule/project.x@2",
    "dropped": ["majordomus://rule/project.x@1"],
    "key": "identity",
    "detail": "one rule at two versions; the newer is in the answer",
    "paths": 1,
})).unwrap();
assert_eq!(semantic.dropped, ["majordomus://rule/project.x@1"]);
assert_ne!(semantic.kept, semantic.dropped[0], "two names, and one of them lost");
vlastnosttyppovinnépopis
zachováno string ano Záznam, který přežil.
zahozeno array of string ano Co do něj bylo sloučeno nebo kvůli němu zahozeno.
klíč string ano Co z nich udělalo tutéž věc: uri, path nebo identity.
detail string ano Totéž, slovy.
cesty integer (uint) ano Kolik objevovacích cest dosáhlo přeživší položky.

Používá CompiledContext Explanation

DevContextStanding

"selected" | "excluded" | "deduplicated" | "not_reached" | "unknown"
Jaké je postavení jednoho identifikátoru v sestaveném kontextu. Kde jeden identifikátor stojí vzhledem k sestavenému kontextu.

Dvojice, na kterou je třeba si dát pozor, je NotReached a Unknown, protože v zobrazení vypadají podobně a znamenají opak. NotReached říká, že index objekt drží a tento požadavek jej nedosáhl — měnit je třeba požadavek a zvýšení hloubky nebo rozpočtu jej může přinést. Unknown říká, že index pod tím identifikátorem nedrží nic: požadavek je v pořádku a identifikátor je špatně, nebo objekt v tomto repozitáři není. Splynutí obou by poslalo čtenáře ladit požadavek, který nikdy nebyl problémem.

Deduplicated je třetí, které stojí za pojmenování: věc je v odpovědi, pod jiným identifikátorem, což není ani uvnitř, ani venku.

use majordomus_cli::devcontext::Standing;

// in the answer, one way or another
let present = [Standing::Selected, Standing::Deduplicated];
// reached and not given, so the request is what to change
let request_bound = [Standing::Excluded, Standing::NotReached];
for s in present {
    assert!(!request_bound.contains(&s));
}

// the two that must not be confused: one is the request's fault, one is not
assert_eq!(Standing::NotReached.as_str(), "not_reached");
assert_ne!(Standing::NotReached, Standing::Unknown);
assert_eq!(
    serde_json::to_value(Standing::NotReached).unwrap(),
    serde_json::json!(Standing::NotReached.as_str()),
);
  • selectedV odpovědi.
  • excludedDosaženo a nedáno; vyloučení říká proč.
  • deduplicatedSloučeno do jiného identifikátoru, nebo pro jiný zahozeno.
  • not_reachedIndex ho obsahuje a tento požadavek se k němu nikdy nedostal.
  • unknownIndex pod tímto identifikátorem nic nedrží.

Používá Explanation

Zjišťování

object
Jedna cesta, kterou byla položka nalezena: který selektor, proč, jak daleko od seedu a nakolik jí kompilátor důvěřuje. Položka si ponechává každou z nich, protože „pět cest objevování našlo tentýž ADR“ je zajímavý fakt, ne něco k ukrytí.

Discovery je druh cesty dovnitř, ne jeden průchod. Třicet tři úkolů jednoho milníku všechny dosahují toho milníku přes belongs_to a zaznamenat to třiatřicetkrát by říkalo, že odpověď měla třiatřicet důvodů, ačkoli má jeden — proto se cesta už zaznamenaná pro týž selektor, hranu a důvod uchová, místo aby se opakovala.

via a edge jsou přítomné přesně tehdy, když něco znamenají: seed nebyl dosažen z ničeho a nenásledoval žádnou hranu, takže obě chybí a na drátě se přeskočí, místo aby se serializovaly jako null. confidence je 1.0 pro každý deklarovaný selektor a nižší jen pro Selector::IntentMatch; weight je to, čím tato konkrétní cesta přispívá k relevanci, což je jiná veličina — kolik se počítá oproti tomu, jak jistá je.

use majordomus_cli::devcontext::{Discovery, Selector};
use serde_json::json;

// the request named it: nothing to come via, no edge followed, depth zero
let seed: Discovery = serde_json::from_value(json!({
    "selector": "seed",
    "reason": "named in the request",
    "depth": 0,
    "confidence": 1.0,
    "weight": 1.0,
})).unwrap();
assert_eq!(seed.selector, Selector::Seed);
assert!(seed.via.is_none() && seed.edge.is_none());
assert!(seed.selector.declared() && seed.confidence == 1.0);

// the wire form omits them rather than carrying nulls a reader must skip
let wire = serde_json::to_value(&seed).unwrap();
assert!(wire.get("via").is_none() && wire.get("edge").is_none());

// a relation says both where it came from and which edge it followed
let walked: Discovery = serde_json::from_value(json!({
    "selector": "relation",
    "reason": "the decision in force over this scope",
    "via": "majordomus://issue/I0301",
    "edge": "governed_by",
    "depth": 1,
    "confidence": 1.0,
    "weight": 0.9,
})).unwrap();
assert_eq!(walked.edge.as_deref(), Some("governed_by"));
assert_eq!(walked.via.as_deref(), Some("majordomus://issue/I0301"));

// and a guess says so, in the one field that distinguishes the two halves of an answer
let guessed: Discovery = serde_json::from_value(json!({
    "selector": "intent_match",
    "reason": "the intent's words occur in it",
    "depth": 2,
    "confidence": 0.4,
    "weight": 0.4,
})).unwrap();
assert!(!guessed.selector.declared() && guessed.confidence < 1.0);
vlastnosttyppovinnépopis
selektor Selector → Selector ano Co k tomu došlo.
důvod string ano Proč bylo zahrnuto, slovy, kterým člověk rozumí bez tohoto souboru.
přes string | null ne Položka, ze které se sem došlo, pokud se došlo z nějaké.
hrana string | null ne Následovaný druh hrany, pro Relation.
hloubka integer (uint) ano Skoky od seedu; seed je 0.
jistota číslo (double) ano 1.0, když repozitář tuto cestu deklaroval, méně, když byla odvozena.
váha číslo (double) ano Čím tato cesta přispívá k relevanci záznamu.

Používá ContextEntry

EdgeRule

object
Jedna hrana složeného grafu a co s ní kompilátor dělá.

Každý druh hrany, který graf deklaruje, dostane řádek, sledovaný či ne, což je vlastnost, kvůli které stojí za to tuto projekci číst: hrana přidaná do grafu a ne do tabulky průchodu se zde objeví jako refused s důvodem, že kompilátor pro ni nemá pravidlo, místo aby byla potichu nesledována. Tabulka, která by vypisovala jen hrany, které sledovala, by nemohla říct, co ignoruje.

forward a reverse chybí místo nuly, když se hrana tímto způsobem nenásleduje, takže „nenásledováno“ je jedna reprezentace, ne dvě. means je vlastní výklad vztahu grafem; reason je výklad tohoto kompilátoru, proč je či není kontextem, v obou směrech.

use majordomus_cli::devcontext::{edge_policy, policy, EdgeRule};
use majordomus_cli::synthetic::SyntheticRepository;

let repo = SyntheticRepository::small().unwrap();
let ctx = repo.context().unwrap();
let rules = policy(&ctx).unwrap();

// one row per edge kind the graph declares, followed or refused
let edges: &[EdgeRule] = &rules.edges;
assert!(!edges.is_empty(), "the graph declares edges");
for e in edges {
    // the row always says what the relation is and what was decided about it
    assert!(!e.means.is_empty() && !e.reason.is_empty(), "{}", e.edge);

    if e.refused {
        // not followed in either direction, and absent rather than zero
        assert!(e.forward.is_none() && e.reverse.is_none(), "{}", e.edge);
        assert!(edge_policy(&e.edge).is_none(), "{} is refused and in the table", e.edge);
    } else {
        // followed at least one way, at a weight the table declares
        assert!(e.forward.is_some() || e.reverse.is_some(), "{}", e.edge);
        let p = edge_policy(&e.edge).expect("a followed edge has a policy");
        assert_eq!(e.forward, (p.forward > 0.0).then_some(p.forward));
        assert_eq!(e.reverse, (p.reverse > 0.0).then_some(p.reverse));
    }
}
vlastnosttyppovinnépopis
hrana string ano Druh hrany tak, jak ho pojmenovává složený graf.
znamená string ano Co graf jeho prostřednictvím tvrdí.
dopředu number | null ne Násobitel relevance při sledování od zdroje k cíli; chybí, když je odmítnuto.
obráceně number | null ne Násobitel při sledování od cíle ke zdroji; chybí při odmítnutí.
důvod string ano Proč je cíl kontextem pro zdroj, nebo proč je hrana odmítnuta.
odmítnuto boolean ano Pravda, když jej kompilátor nebude sledovat ani jedním směrem.

Používá CompilerPolicy

EntryProvenance

object
Kde vybraná věc žije, jak to ví index. Nic zde není psáno ručně: každé pole je vlastní crate::model::Provenance indexu, přenesené, ne přeformulované.

Je to kopie původu z indexu, a ne jeho nové odvození, což je vlastnost, na kterou se vyplatí spoléhat: žádnou zdejší cestu nesestavil tento modul, takže cesta v odpovědi je cesta, kterou index objevil, a oba se nemohou rozejít v tom, kde něco leží.

bytes je změřená polovina nákladů položky — odhad nad ní je tokens_for — takže zde také začíná aritmetika rozpočtu.

use majordomus_cli::devcontext::{tokens_for, EntryProvenance};
use serde_json::json;

let p: EntryProvenance = serde_json::from_value(json!({
    "path": ".ai/repo/adrs/0052-the-episode-is-the-boundary.md",
    "directory": ".ai/repo/adrs",
    "source_class": "adr",
    "section": "adrs",
    "bytes": 8_192,
})).unwrap();

// the size is what the cost is computed from, and the estimate says so of itself
assert_eq!(tokens_for(p.bytes), 2_048);

// a file under no manifest section — a root README, say — carries none rather than ""
let rootish: EntryProvenance = serde_json::from_value(json!({
    "path": "README.md",
    "directory": ".",
    "source_class": "readme",
    "bytes": 4_000,
})).unwrap();
assert!(rootish.section.is_none());
assert_eq!(rootish.directory, ".", "the root is `.`, not the empty string");
vlastnosttyppovinnépopis
path string ano Cesta relativní k repozitáři, s lomítky dopředu.
adresář string ano Adresář, v němž leží.
source_class string ano Třída sources.yaml, která jej objevila.
sekce string | null ne Sekce manifestu, pod niž spadá, pokud pod nějakou spadá.
bajty integer (uint64) ano Velikost souboru v bajtech.

Používá ContextEntry

Vyloučeno

object
Jedna věc, ke které kompilátor došel a nedal ji. Vyloučené není totéž co chybějící: nedostatečně naplněný kontext se ladí podle tohoto seznamu, ne hádáním.

cost_tokens je důvod, proč tento typ existuje v takovém tvaru. Rozpočet, který by hlásil jen že něco vypustil, by nechal volajícího hádat nový strop; ten, který hlásí, co by každé vynechání stálo, mění „zvyšte rozpočet“ v aritmetiku. detail nese totéž slovy, takže řádek je čitelný, aniž by čtenář tu aritmetiku dělal sám.

Řazení staví na první místo úroveň jako explicitní rank, protože úroveň je přednost, v níž se rozpočet utrácí; uvnitř úrovně čtenář hledá důvod a identifikátor uzavírá klíč, takže seznam je úplný a týž strom ho vždy vytiskne stejně.

use majordomus_cli::devcontext::{Excluded, ExclusionReason, Tier};
use majordomus_cli::order::canonical;
use serde_json::json;

let row = |uri: &str, tier: &str, reason: &str, cost: u64| -> Excluded {
    serde_json::from_value(json!({
        "uri": uri, "tier": tier, "reason": reason,
        "detail": "as measured", "cost_tokens": cost, "relevance": 0.4,
    })).unwrap()
};

let mut excluded = vec![
    row("majordomus://knowledge/b", "history", "budget", 900),
    row("majordomus://adr/0001", "decision", "superseded", 300),
    row("majordomus://knowledge/a", "history", "budget", 1_200),
];
canonical(&mut excluded);

// the tier ranks first, so the decision tier precedes history whatever the reason says
assert_eq!(excluded[0].tier, Tier::Decision);
assert_eq!(excluded[0].reason, ExclusionReason::Superseded);
// then the reason, then the identifier — which makes the order total
let rest: Vec<&str> = excluded[1..].iter().map(|e| e.uri.as_str()).collect();
assert_eq!(rest, ["majordomus://knowledge/a", "majordomus://knowledge/b"]);

// and the row a caller acts on says what it would have cost to keep
let biggest = excluded.iter().max_by_key(|e| e.cost_tokens).unwrap();
assert_eq!(biggest.cost_tokens, 1_200);
vlastnosttyppovinnépopis
uri string ano Kanonický identifikátor, nebo jméno, které vrstva použila, když se nic nevyřešilo.
druh string | null ne Jeho druh, když se rozlišil na něco.
úroveň Tier → Tier ano Vrstva, ze které by se utratilo.
důvod ExclusionReason → ExclusionReason ano Proč v odpovědi není.
detail string ano Totéž, slovy.
cost_tokens integer (uint64) ano Kolik by to stálo, aby čtenář mohl rozhodnout, zda rozpočet zvýšit.
relevance číslo (double) ano Jaká byla jeho relevance.

Používá CompiledContext Explanation

ExclusionReason

"budget" | "relevance" | "depth" | "stale" | "superseded" | "unresolved"
Proč něco, co kompilátor vybral, nepřežilo.

Šestice se dělí na tři, na které může volající reagovat dalším dotazem — Budget, Relevance a Depth jsou všechny důsledky vlastních mezí požadavku, takže zvýšení meze položku vrátí — a tři, které jsou fakty o repozitáři: Stale a Superseded znamenají, že samotná vrstva říká, že tohle není aktuální věc, a Unresolved znamená, že vrstva jmenuje něco, co nedrží, což je visící odkaz, který by někdo měl opravit.

use majordomus_cli::devcontext::ExclusionReason as Why;

// the ones a different request would change, against the ones it would not
let request_bound = [Why::Budget, Why::Relevance, Why::Depth];
let repository_bound = [Why::Stale, Why::Superseded, Why::Unresolved];
for r in repository_bound {
    assert!(!request_bound.contains(&r), "{} is not the caller's to fix", r.as_str());
}

// and the word is the serialised form, snake_case for the two-word one
assert_eq!(Why::Superseded.as_str(), "superseded");
assert_eq!(
    serde_json::to_value(Why::Unresolved).unwrap(),
    serde_json::json!(Why::Unresolved.as_str()),
);
  • budgetRozpočet došel dřív, než se naplnila jeho úroveň.
  • relevanceJeho relevance klesla pod hranici, kterou požadavek stanovil.
  • depthJe za hloubkou, kterou požadavek povoluje.
  • staleObjekt říká, že je zastaralý nebo nahrazený.
  • supersededNovější verze téže věci je v odpovědi.
  • unresolvedVrstva ho jmenuje a pod tímto jménem nic nedrží.

Používá Excluded

Vysvětlení

object
Proč je jeden identifikátor v sestaveném kontextu, nebo proč v něm není.

standing říká, o který z pěti případů jde, a tři volitelná pole jsou jeho důkazem: nejvýše jedno z entry, excluded a deduplicated je vyplněno a které, to určuje stav, ne nezávisle na něm. detail je tatáž odpověď slovy, takže hodnota je čitelná, aniž by je čtenář musel párovat.

budget je vždy přítomné, i když je odpověď unknown, a právě to je smysl jeho nošení: nejčastější důvod k této otázce je položka, která se nevešla, a vysvětlení, které by řeklo „vyloučeno kvůli rozpočtu“ bez rozpočtu, by k jednání vyžadovalo druhé volání.

use majordomus_cli::devcontext::{compile, explain, CompileInput, ExplainInput};
use majordomus_cli::devcontext::{Explanation, Standing};
use majordomus_cli::synthetic::SyntheticRepository;

let repo = SyntheticRepository::small().unwrap();
let ctx = repo.context().unwrap();
let compiled = compile(&ctx, CompileInput::default()).unwrap();
let selected = compiled.selected[0].uri.clone();

// something in the answer: the entry is the evidence, and the other two are empty
let e: Explanation =
    explain(&ctx, ExplainInput { uri: selected.clone(), ..Default::default() }).unwrap();
assert_eq!(e.standing, Standing::Selected);
assert_eq!(e.entry.as_ref().unwrap().uri, selected);
assert!(e.excluded.is_none() && e.deduplicated.is_none());
assert!(!e.detail.is_empty(), "the standing always reads as a sentence too");

// something this repository does not hold: an answer, not an error — and the budget
// the judgement was made under is carried even here
let absent: Explanation = explain(
    &ctx,
    ExplainInput { uri: "majordomus://adr/9999".into(), ..Default::default() },
)
.unwrap();
assert_eq!(absent.standing, Standing::Unknown);
assert!(absent.entry.is_none());
assert_eq!(absent.budget.limit_tokens, compiled.budget.limit_tokens);
vlastnosttyppovinnépopis
uri string ano Na co se ptalo.
postavení DevContextStanding → DevContextStanding ano Jak to stojí.
detail string ano Totéž, slovy.
vstup ContextEntry | null → ContextEntry ne Položka, když je v odpovědi.
vyloučeno Excluded | null → Excluded ne Vyloučení, když bylo dosaženo a nebylo zadáno.
deduplikováno Deduplicated | null → Deduplicated ne Sloučení, jehož byl součástí, pokud byl sloučen nebo zahozen ve prospěch jiného.
rozpočet Budget → Budget ano Rozpočet, za kterého se posuzovalo, aby se na „vyloučeno kvůli rozpočtu“ dalo reagovat bez druhého volání.

Používá devcontext.explain

GitContext

object
Hlava, proti které byla tato odpověď sestavena. Sestavený kontext je pravdivý jen o jednom stromě a tato tři pole plus otisk indexu říkají o kterém.

branch a head jsou volitelné, protože checkout může legitimně nemít ani jedno — detached head, nebo strom, který vůbec není repozitářem git — a absence se nese, nepředstírá se. working_tree volitelné není, protože vždy je co říct: když na git nelze dosáhnout, nese unavailable: <reason> místo stavu, takže čtenář nikdy nemůže nerozlišit „čisté“ od „neptáno“.

commits se odvozuje z vlastních záznamů vybraných položek a nikdy z procházení logu. To je rozdíl mezi „commity, o kterých tato práce říká, že je vytvořila“ a „co se náhodou nedávno commitnulo“ a jen to první je kontext.

use majordomus_cli::devcontext::GitContext;
use serde_json::json;

// a tree the compiler could read
let known: GitContext = serde_json::from_value(json!({
    "branch": "master",
    "head": "cd04012f1b1dc21fd493e2be14938ffdfaccc8fa",
    "working_tree": "clean",
})).unwrap();
assert_eq!(known.branch.as_deref(), Some("master"));
assert!(known.commits.is_empty(), "no selected record named a commit");

// and one it could not: the status field still says something a reader can act on
let unknown: GitContext = serde_json::from_value(json!({
    "working_tree": "unavailable: not a git repository",
})).unwrap();
assert!(unknown.head.is_none() && unknown.branch.is_none());
assert!(
    unknown.working_tree.starts_with("unavailable: "),
    "never silently indistinguishable from clean",
);
vlastnosttyppovinnépopis
větev string | null ne Větev, když je checkout na nějaké.
head string | null ne Commit hlavy.
working_tree string ano clean, dirty nebo to, co řekl git.
commity pole CommitReference → CommitReference ne Commity, které vybrané položky jmenují jako vlastní důkaz, nejnovější záznam první. Odvozeno ze záznamů vrstvy, nikdy z procházení logu.

Používá CompiledContext

Seed

object
O čem se požadavek ptal, že se má sestavit kontext.

Seed je požadavek vrácený zpět po rozlišení a je v odpovědi proto, aby volající viděl, na co se jeho krátké jméno změnilo. from jmenuje pole požadavku, z něhož pochází — issue, milestone, uris, paths, intent — díky čemuž tentýž identifikátor, který dorazí dvěma způsoby, zůstane dvěma seedy, a ne jedním.

resolved se naplňuje jen tehdy, když řešení skutečně něco změnilo: pojmenujete-li milník jeho slugem, nese kanonickou identitu; pojmenujete-li ho touto identitou, nebylo co řešit, a pole chybí. Cesta a záměr se proti indexu neřeší vůbec — jejich uri je literál, který požadavek dal.

use majordomus_cli::devcontext::Seed;
use serde_json::json;

// named by a slug: the answer says what it became
let by_slug: Seed = serde_json::from_value(json!({
    "uri": "majordomus://milestone/M04",
    "kind": "milestone",
    "from": "milestone",
    "resolved": "M04",
})).unwrap();
assert_eq!(by_slug.resolved.as_deref(), Some("M04"));

// named canonically: nothing was resolved, so nothing is reported
let canonical: Seed = serde_json::from_value(json!({
    "uri": "majordomus://issue/I0301",
    "kind": "issue",
    "from": "issue",
})).unwrap();
assert!(canonical.resolved.is_none());

// and a path is carried verbatim: its `uri` is not a canonical identifier at all
let path: Seed = serde_json::from_value(json!({
    "uri": "apps/majordomus-cli/src",
    "kind": "path",
    "from": "paths",
})).unwrap();
assert!(!path.uri.starts_with("majordomus://"));
assert_eq!(path.from, "paths", "which request field it came from");
vlastnosttyppovinnépopis
uri string ano Kanonický identifikátor seedu, nebo literál, který požadavek dal pro záměr či cestu.
druh string ano issue, milestone, path, intent, nebo druh pojmenovaného objektu.
z string ano Odkud pochází: které pole požadavku.
vyřešeno string | null ne Na co to kompilátor rozřešil, když požadavek pojmenoval něco krátkým jménem.

Používá CompiledContext

Selektor

"seed" | "relation" | "scope_path" | "contract" | "governance" | "session_state" | "intent_match"
Co se k položce dostalo. To je původ výběru a je to jiný fakt než původ souboru: Relation říká, že kompilátor následoval hranu, kterou vrstva deklaruje, IntentMatch říká, že hádal ze slov. Odpověď, která je nerozlišovala, by nemohla říct, které polovině sebe věřit.

Dělení, na kterém záleží, je Selector::declared a je záměrně nerovnoměrné: šest ze sedmi selektorů následuje něco, co repozitář uvádí, a právě jeden hádá. Ten poměr je návrh. Odhad je omezen na jediný selektor, aby šla odhadnutá část každé odpovědi najít, zvážit a ignorovat, místo aby byla promíchána se zbytkem neoznačenou silou.

use majordomus_cli::devcontext::Selector;

// exactly one selector infers, and the answer can always be split on it
let inferred: Vec<&str> = Selector::ALL
    .iter()
    .filter(|s| !s.declared())
    .map(|s| s.as_str())
    .collect();
assert_eq!(inferred, ["intent_match"]);

// every selector has its own word, so a reason is never ambiguous
let mut words: Vec<&str> = Selector::ALL.iter().map(|s| s.as_str()).collect();
let before = words.len();
words.sort();
words.dedup();
assert_eq!(words.len(), before);

// and the word is the serialised form
assert_eq!(
    serde_json::to_value(Selector::ScopePath).unwrap(),
    serde_json::json!("scope_path"),
);
  • seedPojmenováno v požadavku.
  • relationDosaženo po typované hraně složeného grafu.
  • scope_pathJeho cesta leží pod cestou, kterou deklaruje požadavek nebo seed.
  • contractKontrakt adresáře, který některé z těchto cest dosáhne.
  • governancePravidlo nebo politika, kterou vrstva uplatňuje na všechno.
  • session_stateZaznamenaná relace této větve nebo těchto cest, předávka, kterou resolver vybral, nejnovější kontrolní bod.
  • intent_matchOdvozeno: slova intentu se vyskytují ve slovech samotného objektu.

Používá Discovery SelectorRule

SelectorRule

object
Jeden selektor a zda to, co produkuje, bylo deklarováno, nebo odvozeno.

Dvě pole, a to druhé je celý důvod, proč projekce existuje: volající, který spotřebovává sestavený kontext, potřebuje vědět, kterým selektorům může věřit jako faktu repozitáře, a odvozovat to z odpovědi by znamenalo odhadovat z hodnot jistoty. Zveřejněno zde je to pravidlo, ne vzor, který si čtenář všiml.

use majordomus_cli::devcontext::{policy, Selector, SelectorRule};
use majordomus_cli::synthetic::SyntheticRepository;

let repo = SyntheticRepository::small().unwrap();
let ctx = repo.context().unwrap();
let rules = policy(&ctx).unwrap();

// every selector is published, and each row agrees with the selector it names
let selectors: &[SelectorRule] = &rules.selectors;
assert_eq!(selectors.len(), Selector::ALL.len());
for r in selectors {
    assert_eq!(r.declared, r.selector.declared());
}

// and the inferring one is named, so the guessed half of an answer can be found
let inferred: Vec<Selector> =
    selectors.iter().filter(|r| !r.declared).map(|r| r.selector).collect();
assert_eq!(inferred, [Selector::IntentMatch]);
vlastnosttyppovinnépopis
selektor Selector → Selector ano Selektor.
deklarováno boolean ano Deklarováno repozitářem, nebo odvozeno kompilátorem.

Používá CompilerPolicy

Úroveň

"task" | "governance" | "source" | "decision" | "knowledge" | "history"
Kde položka leží v pořadí, v němž rozpočet utrácí.

Pořadí je vlastní tomuto repozitáři: lib/context.sh skládá své sekce „v pořadí autority, nejvyšší první, protože pracovník, kterému dojde rozpočet, musí ztratit nejméně spolehlivé důkazy a ne nejspolehlivější: nejdřív git, pak úloha a její politika, pak blokátory, pak autorské záznamy, pak historie událostí.“ Těchto šest vrstev je ta věta, použitá na objekty místo na sekce.

Odvozené Ord nese zátěž: varianty jsou deklarovány v pořadí autority, takže < znamená „utraceno dříve“ a „hůře se zahazuje“ a řazení rozpočtu je prosté sort, nikoli tabulka mapující úrovně na pořadí. Varianta přidaná na špatné místo by tiše přeřadila celou odpověď, proto je pořadí deklarace jedinou věcí na tomto enumu, na kterou je třeba dávat pozor.

use majordomus_cli::devcontext::Tier;

// the declaration order *is* the order the budget spends in
let mut sorted = Tier::ORDER.to_vec();
sorted.sort();
assert_eq!(sorted, Tier::ORDER.to_vec());

// so the strongest authority is the smallest, and it is the one never dropped
assert_eq!(Tier::ORDER.iter().copied().min(), Some(Tier::Task));
assert!(Tier::Task < Tier::Governance && Tier::Governance < Tier::History);

// and the serialised word is the lowercase one the report is read under
assert_eq!(
    serde_json::to_value(Tier::Governance).unwrap(),
    serde_json::json!(Tier::Governance.as_str()),
);
  • taskTo, o co bylo požádáno: úkol, jeho milník, jmenované cesty, záměr. Nikdy se nezahazuje — kontext, který ztratil to, o čem byl sestaven, není menší odpověď, je to špatná odpověď.
  • governanceProti čemu se práce bude posuzovat: politika, rozsah, kontrakty adresářů, které dosahují jmenovaných cest, a pravidla. Blokující pravidlo se nikdy nevynechá.
  • sourceKód a případy, kterých se práce dotýká.
  • decisionArchitektonická rozhodnutí, která už nad tím platí.
  • knowledgeCo zanechala předchozí relace: předávky, záznamy relací, kurátorské znalosti.
  • historyPodpůrné důkazy: případy užití, tvrzení, funkce a zbytek vrstvy.

Používá ContextEntry Excluded TierRule TierSpend

TierRule

object
Jedna úroveň, jak ji hlásí politika: její místo v pořadí útraty, k čemu slouží a které druhy objektů do ní spadají.

position je počítáno od 1 a existuje proto, že tvar na drátě nemá vlastní pořadí, na které by se čtenář mohl spolehnout — Ord enumu Tier je fakt Rustu, ne JSON — takže pořadí je explicitní pro čtenáře, kteří nejsou tímto crate. kinds je inverzí tier_for_kind, publikovanou, aby volající viděl, kam druh přistane, aniž by sestavoval odpověď a díval se.

use majordomus_cli::devcontext::{policy, tier_for_kind, Tier, TierRule};
use majordomus_cli::synthetic::SyntheticRepository;

let repo = SyntheticRepository::small().unwrap();
let ctx = repo.context().unwrap();
let rules = policy(&ctx).unwrap();

// the first tier spent is the one that is never dropped, at position 1
let first: &TierRule = &rules.tiers[0];
assert_eq!(first.tier, Tier::Task);
assert_eq!(first.position, 1, "1-based, so the order survives serialisation");

// every tier is reported, in the order the budget spends in, and explains itself
assert_eq!(rules.tiers.len(), Tier::ORDER.len());
for (i, r) in rules.tiers.iter().enumerate() {
    assert_eq!(r.position, i + 1);
    assert_eq!(r.meaning, r.tier.meaning());
}

// and every kind is listed under the tier the classifier puts it in, exactly once —
// so the published table and the function behind it cannot disagree
let mut listed = 0usize;
for r in &rules.tiers {
    for kind in &r.kinds {
        assert_eq!(tier_for_kind(kind), r.tier, "{kind} is listed under the wrong tier");
        listed += 1;
    }
}
let mut all: Vec<&str> = rules.tiers.iter().flat_map(|r| &r.kinds).map(String::as_str).collect();
all.sort();
all.dedup();
assert_eq!(all.len(), listed, "a kind is listed under one tier only");
vlastnosttyppovinnépopis
úroveň Tier → Tier ano Úroveň.
pozice integer (uint) ano Jeho pozice v pořadí, v němž rozpočet utrácí; 1 se utrácí první.
význam string ano K čemu slouží.
kinds array of string ano Druhy objektů, které do ní přistávají.

Používá CompilerPolicy

TierSpend

object
Kolik stála jedna úroveň, kolik z ní přežilo a kolik z toho nešlo vypustit.

Žádný strop na úroveň neexistuje, což stojí za zmínku, protože název jeden naznačuje: rozpočet je jediná zásoba utrácená v pořadí úrovní a toto je report o tom, co z ní která úroveň vzala, ne příděl, který dostala. Úroveň s mnoha vyloučeními proto není úroveň, která narazila na vlastní limit — je to úroveň, na kterou se dostalo, když už zásobu utratily úrovně nad ní.

meaning se nese proto, aby čtenář, který rozhoduje, pro co rozpočet zvýšit, nemusel dohledávat, co obsahuje knowledge; required je ta část selected, o které rozpočet neměl slovo.

use majordomus_cli::devcontext::{Tier, TierSpend};
use serde_json::json;

let t: TierSpend = serde_json::from_value(json!({
    "tier": "governance",
    "meaning": Tier::Governance.meaning(),
    "selected": 9,
    "excluded": 2,
    "tokens": 6_400,
    "required": 3,
})).unwrap();

assert_eq!(t.tier, Tier::Governance);
// the undroppable part is a part of what survived, never a separate count
assert!(t.required <= t.selected);
// and the row explains its own tier, so "2 excluded" is actionable
assert_eq!(t.meaning, Tier::Governance.meaning());
assert!(t.meaning.contains("policy"));
vlastnosttyppovinnépopis
úroveň Tier → Tier ano Úroveň.
význam string ano K čemu slouží.
vybráno integer (uint) ano Kolik záznamů v něm přežilo.
vyloučeno integer (uint) ano Kolik z ní bylo kvůli rozpočtu vypuštěno.
tokeny integer (uint64) ano Tokeny, které přeživší stojí.
povinné integer (uint) ano Položky v něm, které nelze zahodit.

Používá Budget

Prohledává názvy a popisy všech stránek, které tento web publikuje.