pub fn spend(
index: &Index,
sel: Selection,
seeds: &BTreeSet<String>,
limit: u64,
supersedes: &BTreeMap<String, Vec<String>>,
) -> (Spend, Vec<Diagnostic>)Expand description
Compile the selection into the answer: fold, decide, order, spend.
The four stages run in that order and none of them re-reads the repository. seeds is
what the request asked about, and it is the one input that can push the answer over its
ceiling: a seed is never dropped for budget, and neither is the policy or the scope, so
limit bounds what the budget chooses and not what it must keep. When the entries it
may not drop cost more than the limit, the answer says over_budget rather than
returning a context missing the thing it was compiled about.
supersedes maps an identifier to the identifiers it declares it supersedes, and is
what turns two versions of one thing in the answer into one entry and a recorded
conflict. The diagnostics that come back alongside are what could not be read — never an
error, because an absent source is an answer.
use std::collections::{BTreeMap, BTreeSet};
use majordomus_cli::devcontext::budget::spend;
use majordomus_cli::devcontext::select::{Candidate, Selection};
use majordomus_cli::devcontext::{Discovery, Selector, Tier};
use majordomus_cli::synthetic::SyntheticRepository;
let repo = SyntheticRepository::small().unwrap();
let index = repo.index().unwrap();
let seed_uri = "majordomus://issue/I0301";
let candidate = Candidate {
uri: seed_uri.into(),
kind: "issue".into(),
title: Some("the thing being worked on".into()),
tier: Tier::Task,
relevance: 1.0,
depth: 0,
discovered_by: vec![Discovery {
selector: Selector::Seed,
reason: "named in the request".into(),
via: None,
edge: None,
depth: 0,
confidence: 1.0,
weight: 1.0,
}],
object: None,
path: None,
status: None,
// 40_000 bytes is 10_000 tokens, against a ceiling of 10
facts: BTreeMap::from([("bytes".to_string(), "40000".to_string())]),
};
let selection = Selection {
candidates: BTreeMap::from([(seed_uri.to_string(), candidate)]),
unresolved: Vec::new(),
diagnostics: Vec::new(),
};
let seeds = BTreeSet::from([seed_uri.to_string()]);
let (s, _) = spend(&index, selection, &seeds, 10, &BTreeMap::new());
// the seed survives a ceiling it does not fit in: a context that lost the thing it was
// compiled about is not a smaller answer, it is the wrong one
assert_eq!(s.selected.len(), 1);
assert!(s.selected[0].required);
assert!(s.excluded.is_empty());
// and the answer says so rather than pretending it stayed inside the budget
assert!(s.budget.over_budget);
assert_eq!(s.budget.used_tokens, 10_000);
assert_eq!(s.budget.required_tokens, 10_000, "all of it is undroppable");
assert_eq!(s.budget.remaining_tokens, 0, "floored, never negative");