use crate::contract_lint;
use crate::cost;
use crate::events::{Event, EventKind};
use crate::runner;
use crate::scrub;
use crate::types::*;
use std::collections::HashMap;
use std::path::Path;
#[allow(clippy::too_many_arguments)]
pub fn render_plan_markdown(
plan: &Plan,
mission: &Mission,
estimate: &cost::CostEstimate,
two_path: Option<&cost::TwoPathEstimate>,
fit_note: Option<&str>,
missions_used: usize,
contract_lint: &contract_lint::ContractLintReport,
gate_reports: &[crate::gate::GateReport],
pool: &[CandidateSpec],
) -> String {
use std::fmt::Write as _;
let mut md = String::new();
let _ = writeln!(md, "# Mission plan — {}", mission.id);
let _ = writeln!(md, "\n**Goal:** {}\n", plan.goal);
let _ = writeln!(
md,
"Branch `{}` (from `{}`). Approved plan of record; the machine-readable \
twin is [plan.json](plan.json). Live status: `kranz status` or the dashboard.\n",
mission.mission_branch, mission.base_branch
);
if let Some(policy) = plan.reviewer_independence {
let roles = [
policy.scrutiny.then_some("scrutiny"),
policy.functional.then_some("functional"),
]
.into_iter()
.flatten()
.collect::<Vec<_>>()
.join(" and ");
let _ = writeln!(
md,
"## Reviewer independence\n\nRequired for {roles}: a known model \
family different from every recorded worker attempt. Pinned at approval; fallback, \
retry, unknown provenance or a skipped required reviewer cannot weaken it.\n"
);
}
let _ = writeln!(md, "## Cost estimate\n");
let provenance = if missions_used == 0 {
"built-in defaults — no completed missions yet".to_string()
} else {
format!("based on {missions_used} completed mission(s)")
};
match estimate.confidence {
cost::Confidence::High => {
let _ = writeln!(
md,
"Estimated **${:.2} – ${:.2}** (expected ~${:.2}). Rough estimate — live usage is \
authoritative; {provenance}.\n",
estimate.low_usd, estimate.high_usd, estimate.expected_usd
);
}
cost::Confidence::Low => {
let _ = writeln!(
md,
"Estimated **${:.2} – ${:.2}** (expected ~${:.2}). Doc-heavy / judgement-heavy \
shape — the calibration corpus lacks a comparable mission, so this is **LOW \
CONFIDENCE** and ${:.2} is a soft ceiling, not a tight bound; {provenance}.\n",
estimate.low_usd, estimate.high_usd, estimate.expected_usd, estimate.high_usd
);
}
}
if let Some(two_path) = two_path {
let _ = writeln!(
md,
"This plan routes to the **local tier**: **$0 marginal** (fixed hardware + \
electricity, not per-token) if it completes locally. If it escalates to frontier: \
**${:.2} – ${:.2}** (expected ~${:.2}), which prices the tier switch's cache-miss \
once (${:.2} — the first post-escalation turn re-reads the full prefix uncached; \
escalation happens at feature/milestone edges, where no warm cache exists to lose).\n",
two_path.escalated.low_usd,
two_path.escalated.high_usd,
two_path.escalated.expected_usd,
two_path.cache_miss_usd,
);
}
if let Some(note) = fit_note {
let _ = writeln!(md, "{note}\n");
}
if !pool.is_empty() {
let n = pool.len();
let _ = writeln!(md, "## Dispatch pool — {n} candidates per unit of work\n");
let _ = writeln!(
md,
"Every worker feature is dispatched to **{n} backends concurrently** \
(heterogeneous dispatch, KRZ-303), one git worktree per stream:"
);
for (i, candidate) in pool.iter().enumerate() {
let _ = writeln!(
md,
"{}. `{}` / `{}`",
i + 1,
candidate.backend,
candidate.model
);
}
let _ = writeln!(md);
let _ = writeln!(
md,
"Each stream's output is recorded as a sibling **candidate for judgement** tied to \
the same unit of work. The engine never selects, ranks, or merges a candidate into \
a winner — selection is a later human judgement act — and the pool's claimed value \
is **divergence for scrutiny, not throughput**.\n"
);
let _ = writeln!(
md,
"**Cost multiplies by {n}:** the estimate above already prices all {n} candidates \
for every worker unit, and the per-mission budget applies to that SUM.\n"
);
}
if let Some(alternatives) = &plan.considered_alternatives {
let _ = writeln!(md, "## Considered alternatives\n");
let _ = writeln!(md, "**Chosen approach:** {}\n", alternatives.chosen.trim());
if !alternatives.rejected.is_empty() {
let _ = writeln!(md, "Rejected shapes:");
for rejected in &alternatives.rejected {
let _ = writeln!(
md,
"- **{}** — {}",
rejected.approach.trim(),
rejected.trade_off.trim()
);
}
let _ = writeln!(md);
}
}
let _ = writeln!(md, "## Validation contract\n");
let _ = writeln!(
md,
"Defined before any feature; gates mission completion.\n"
);
for a in &plan.validation_contract {
match (&a.check, &a.command) {
(AssertionCheck::Command, Some(cmd)) => {
let _ = writeln!(
md,
"- **[{}]** {}\n `{}`",
a.id.trim(),
a.statement.trim(),
cmd.trim()
);
}
(AssertionCheck::PtyScript, _) => {
let command = a
.pty_script
.as_ref()
.map(|s| s.command.trim())
.unwrap_or("MISSING");
let _ = writeln!(
md,
"- **[{}]** {}\n pty script: `{command}`",
a.id.trim(),
a.statement.trim()
);
}
_ => {
let _ = writeln!(
md,
"- **[{}]** {} *(agent judgement)*",
a.id.trim(),
a.statement.trim()
);
}
}
}
if !contract_lint.is_empty() {
let _ = writeln!(md, "## Contract lint\n");
let _ = writeln!(
md,
"Each `check: command` assertion above was run once against the untouched base \
tree at approval time. Suspects are assertions that already pass (or could not \
reach a verdict) before this plan's work lands — a possible polarity/vacuity bug \
in the assertion itself. This never blocks approval.\n"
);
let _ = writeln!(md, "{}\n", contract_lint.summary());
if !gate_reports.is_empty() {
let _ = writeln!(
md,
"{}\n",
crate::contract_gates::render_gate_verdicts(gate_reports)
);
}
}
for (mi, m) in plan.milestones.iter().enumerate() {
let _ = writeln!(md, "\n## Milestone {} — {}\n", mi + 1, m.title);
for (fi, f) in m.features.iter().enumerate() {
let _ = writeln!(md, "### {}.{} {}\n", mi + 1, fi + 1, f.title);
let _ = writeln!(md, "{}\n", f.spec.trim());
if !f.validation_criteria.is_empty() {
let _ = writeln!(md, "Done when:");
for c in &f.validation_criteria {
let _ = writeln!(md, "- {c}");
}
let _ = writeln!(md);
}
}
}
if let Some(pin) = &plan.standards_manifest {
let _ = writeln!(md, "\n{}", crate::pack::resolution::render_pin_section(pin));
}
while md.ends_with('\n') {
md.pop();
}
md.push('\n');
md
}
#[derive(Debug, Clone, Default, serde::Deserialize)]
#[serde(rename_all = "camelCase", default)]
pub(crate) struct Research {
files_read: Vec<String>,
sources: Vec<String>,
facts: Vec<ResearchFact>,
ambiguities: Vec<String>,
candidate_knowledge_updates: Vec<String>,
}
#[derive(Debug, Clone, Default, serde::Deserialize)]
#[serde(rename_all = "camelCase", default)]
struct ResearchFact {
fact: String,
evidence: String,
}
impl Research {
fn is_empty(&self) -> bool {
self.files_read.is_empty()
&& self.sources.is_empty()
&& self.facts.is_empty()
&& self.ambiguities.is_empty()
&& self.candidate_knowledge_updates.is_empty()
}
}
pub(crate) fn extract_research(plan_text: &str) -> Option<Research> {
let value: serde_json::Value = runner::parse_report(plan_text)?;
let research: Research = serde_json::from_value(value.get("research")?.clone()).ok()?;
(!research.is_empty()).then_some(research)
}
pub(crate) fn render_research_markdown(research: &Research, mission_id: &str) -> String {
use std::fmt::Write as _;
let mut md = format!(
"# Research — {mission_id}\n\nEvidence behind the approved plan \
(roadmap M1 / repo-knowledge-store slice 1). Candidate knowledge updates \
feed `docs/knowledge/`.\n"
);
let list = |md: &mut String, title: &str, items: &[String]| {
let items: Vec<&str> = items
.iter()
.map(|s| s.trim())
.filter(|s| !s.is_empty())
.collect();
if items.is_empty() {
return;
}
let _ = write!(md, "\n## {title}\n\n");
for it in items {
let _ = writeln!(md, "- {it}");
}
};
list(&mut md, "Files & docs read", &research.files_read);
list(&mut md, "External sources", &research.sources);
let facts: Vec<&ResearchFact> = research
.facts
.iter()
.filter(|f| !f.fact.trim().is_empty())
.collect();
if !facts.is_empty() {
let _ = write!(md, "\n## Facts\n\n");
for f in facts {
let fact = f.fact.trim();
let ev = f.evidence.trim();
if ev.is_empty() {
let _ = writeln!(md, "- {fact}");
} else {
let _ = writeln!(md, "- {fact} — `{ev}`");
}
}
}
list(&mut md, "Ambiguities & stale docs", &research.ambiguities);
list(
&mut md,
"Candidate knowledge updates",
&research.candidate_knowledge_updates,
);
md
}
pub fn render_revised_plan_markdown(
plan: &Plan,
mission: &Mission,
dropped_feature_ids: &[String],
added_features: &[PlanFeature],
) -> String {
use std::fmt::Write as _;
let mut md = String::new();
let _ = writeln!(md, "# Revised mission plan — {}", mission.id);
let _ = writeln!(md, "\n**Goal:** {}\n", plan.goal);
let _ = writeln!(
md,
"Branch `{}` (from `{}`). Mid-mission revision of the plan of record \
([plan.md](plan.md)); completed milestones are preserved unchanged.\n",
mission.mission_branch, mission.base_branch
);
let _ = writeln!(md, "## Re-plan changes applied\n");
let _ = writeln!(
md,
"Completed milestones are frozen unchanged. Remaining milestones are merged by \
position; existing pending features match by title, omitted pending features are \
skipped, and new feature titles are appended with revision-scoped ids. Review the \
resulting plan below and the `plan.revised` event for the exact machine state.\n"
);
if !dropped_feature_ids.is_empty() {
let _ = writeln!(
md,
"- Dropped (skipped) features: {}",
dropped_feature_ids.join(", ")
);
}
if !added_features.is_empty() {
let titles: Vec<String> = added_features
.iter()
.map(|f| f.title.trim().to_string())
.collect();
let _ = writeln!(md, "- Added features: {}", titles.join(", "));
}
if dropped_feature_ids.is_empty() && added_features.is_empty() {
let _ = writeln!(
md,
"- Per-feature legacy diff: not supplied for this revision path"
);
}
let _ = writeln!(md);
let _ = writeln!(md, "## Full revised plan\n");
for (mi, m) in plan.milestones.iter().enumerate() {
let _ = writeln!(md, "### Milestone {} — {}\n", mi + 1, m.title);
for (fi, f) in m.features.iter().enumerate() {
let _ = writeln!(md, "#### {}.{} {}\n", mi + 1, fi + 1, f.title);
let _ = writeln!(md, "{}\n", f.spec.trim());
if !f.validation_criteria.is_empty() {
let _ = writeln!(md, "Done when:");
for c in &f.validation_criteria {
let _ = writeln!(md, "- {c}");
}
let _ = writeln!(md);
}
}
}
while md.ends_with('\n') {
md.pop();
}
md.push('\n');
md
}
fn latest_decision_summary<'a>(events: &'a [Event], prefix: &str) -> Option<&'a str> {
events.iter().rev().find_map(|event| match &event.kind {
EventKind::OrchestratorDecision { summary, .. } => {
summary.strip_prefix(prefix).map(str::trim)
}
_ => None,
})
}
pub fn render_mission_report(
state: &MissionState,
events: &[Event],
plan: &Plan,
estimate: &cost::CostEstimate,
execution_root: &Path,
workspace_contract: Option<&crate::workspace_contract::WorkspaceContract>,
) -> String {
use std::fmt::Write as _;
let mission = &state.mission;
let mut md = String::new();
let _ = writeln!(md, "# Mission report — {}", mission.id);
let _ = writeln!(md, "\n**Goal:** {}\n", mission.goal);
let _ = writeln!(
md,
"Branch `{}` (from `{}`). Plan of record: [plan.md](plan.md).\n",
mission.mission_branch, mission.base_branch
);
let _ = writeln!(md, "## The plan\n");
let feature_total: usize = mission.milestones.iter().map(|m| m.features.len()).sum();
let command_assertions = plan
.validation_contract
.iter()
.filter(|a| a.check == AssertionCheck::Command)
.count();
let judgement_assertions = plan.validation_contract.len() - command_assertions;
let _ = writeln!(
md,
"{} milestone{}, {} feature{}, gated by {} contract assertion{} ({} command, {} judgement).\n",
mission.milestones.len(),
if mission.milestones.len() == 1 { "" } else { "s" },
feature_total,
if feature_total == 1 { "" } else { "s" },
plan.validation_contract.len(),
if plan.validation_contract.len() == 1 { "" } else { "s" },
command_assertions,
judgement_assertions,
);
for (mi, m) in plan.milestones.iter().enumerate() {
let _ = writeln!(md, "{}. **{}**", mi + 1, m.title);
for f in &m.features {
let intent = first_sentence(&f.spec);
if intent.is_empty() {
let _ = writeln!(md, " - {}", f.title);
} else {
let _ = writeln!(md, " - {} — {intent}", f.title);
}
}
}
if let Some(alternatives) = &plan.considered_alternatives {
let chosen = first_sentence(&alternatives.chosen);
if !chosen.is_empty() {
let _ = writeln!(md, "\n**Chosen approach:** {chosen}");
}
}
let completed_ts = events
.iter()
.rev()
.find_map(|e| matches!(e.kind, EventKind::MissionCompleted {}).then_some(e.ts))
.or_else(|| events.last().map(|e| e.ts))
.unwrap_or(mission.created_at);
let paused = paused_time(events, completed_ts);
let elapsed = std::cmp::max(
completed_ts - mission.created_at - paused,
chrono::Duration::zero(),
);
let _ = write!(md, "**Elapsed:** {}", format_duration(elapsed));
if paused > chrono::Duration::zero() {
let _ = write!(md, " ({} paused)", format_duration(paused));
}
let _ = writeln!(md);
let t = &state.totals;
let _ = writeln!(
md,
"**Tokens:** {} in / {} out / {} cache read / {} cache write",
t.input, t.output, t.cache_read, t.cache_write
);
let cost_note = match crate::cost::mission_cost_class(state) {
crate::cost::MissionCostClass::Frontier => "",
crate::cost::MissionCostClass::Local => {
" — local tier: $0 marginal (fixed hardware + electricity, not \
per-token); excluded from frontier-cost calibration"
}
crate::cost::MissionCostClass::Mixed => {
" — mixed local→frontier (escalated mid-mission); excluded from \
frontier-cost calibration"
}
};
let _ = writeln!(
md,
"**Cost:** ${:.2} actual{cost_note} vs ${:.2}–${:.2} estimated (expected ${:.2})",
state.total_cost_usd, estimate.low_usd, estimate.high_usd, estimate.expected_usd
);
let _ = writeln!(md, "\n## Workspace");
let isolation = match state.config.isolation() {
WorkerIsolation::Worktree => "worktree",
WorkerIsolation::Checkout => "checkout",
};
let _ = writeln!(md, "- **Isolation:** `{isolation}`");
let _ = writeln!(
md,
"- **Worker/validator cwd:** `{}`",
execution_root.display()
);
let _ = writeln!(
md,
"- **Sandbox:** worker `{}`; scrutiny `{}`; functional `{}`",
sandbox_enforce_label(state.config.worker.sandbox.enforce),
sandbox_enforce_label(state.config.validator_scrutiny.sandbox.enforce),
sandbox_enforce_label(state.config.validator_functional.sandbox.enforce),
);
if let Some(pin) = &state.workspace_pin {
let provider_note = if pin.provider == "local-worktree" {
" (source isolation)"
} else {
""
};
let version_label = if pin.provider == "remote" {
format!("adapter {}", pin.version)
} else if pin.version == "none" {
"no workspace contract".to_string()
} else {
format!("contract schema v{}", pin.version)
};
let _ = writeln!(
md,
"- **Provider:** {}{provider_note} · template: {} · {version_label}",
pin.provider, pin.template
);
}
match workspace_contract {
Some(contract) => {
let _ = writeln!(
md,
"- **Workspace contract:** present ({} services, {} previews)",
contract.services.len(),
contract.previews.len()
);
for (label, prefix) in [
("Bootstrap", crate::workspace_gate::BOOTSTRAP_SUMMARY_PREFIX),
("Readiness", crate::workspace_gate::READINESS_SUMMARY_PREFIX),
] {
match latest_decision_summary(events, prefix) {
Some(outcome) => {
let _ = writeln!(md, "- **{label}:** {outcome}");
}
None => {
let _ = writeln!(md, "- **{label}:** not run yet");
}
}
}
}
None => {
let _ = writeln!(
md,
"- **Workspace contract:** no workspace contract (source isolation only)"
);
}
}
let preflight = events.iter().rev().find_map(|event| match &event.kind {
EventKind::OrchestratorDecision { summary, .. } if summary.starts_with("preflight:") => {
Some(summary.as_str())
}
_ => None,
});
match preflight {
Some(summary) => {
let _ = writeln!(md, "- **Preflight:** {summary}");
}
None => {
let _ = writeln!(md, "- **Preflight:** clear — no advisory issues recorded");
}
}
let _ = writeln!(md, "\n## What shipped");
for (mi, m) in mission.milestones.iter().enumerate() {
let _ = writeln!(
md,
"\n### Milestone {} — {} {}\n",
mi + 1,
m.title,
milestone_icon(m.status)
);
for f in &m.features {
let runs = f.worker_runs.len();
let _ = write!(
md,
"- {} **{}**{} — {} run{}",
feature_icon(f.status),
f.title,
if f.origin == FeatureOrigin::Fix {
" *(fix)*"
} else {
""
},
runs,
if runs == 1 { "" } else { "s" },
);
if f.respawns > 0 {
let _ = write!(
md,
", {} respawn{}",
f.respawns,
if f.respawns == 1 { "" } else { "s" }
);
}
let _ = writeln!(md);
let intent = first_sentence(&f.spec);
if !intent.is_empty() {
let _ = writeln!(md, " {intent}");
}
for commit in &f.commits {
let _ = writeln!(md, " - {}", short_commit(commit));
}
let mut candidates: Vec<&WorkerRun> = f
.worker_runs
.iter()
.filter_map(|id| state.runs.get(id))
.filter(|r| r.candidate.is_some())
.collect();
if !candidates.is_empty() {
candidates.sort_by_key(|r| r.candidate.as_ref().map(|c| c.index).unwrap_or(0));
let n = candidates
.first()
.and_then(|r| r.candidate.as_ref())
.map(|c| c.count)
.unwrap_or(candidates.len() as u32);
let _ = writeln!(
md,
" **{} candidates for judgement** (no winner selected; selection is a \
later human judgement act):",
candidates.len()
);
for r in candidates {
let c = r
.candidate
.as_ref()
.expect("filtered to candidate-linked runs");
let result = match r.result {
Some(RunResult::Pass) => "pass",
Some(RunResult::Fail) => "fail",
Some(RunResult::Partial) => "partial",
None => "no terminal state recorded",
};
let _ = writeln!(
md,
" - candidate {}/{}: `{}` / `{}` — {} — branch `kranz/pool/{}/{}-c{}`",
c.index,
n.saturating_sub(1),
c.backend,
r.model,
result,
mission.id,
f.id,
c.index
);
}
}
if f.status == FeatureStatus::Complete {
for criterion in &f.validation_criteria {
let _ = writeln!(md, " - ✓ {criterion}");
}
}
}
}
let _ = writeln!(md, "\n## Validation history");
let rounds = collect_validation_rounds(events);
let mut per_milestone_round: HashMap<&str, usize> = HashMap::new();
let mut rendered_any = false;
for round in &rounds {
if round.findings.is_empty() && !round.clean {
continue;
}
rendered_any = true;
match round.milestone_id {
Some(id) => {
let n = per_milestone_round.entry(id).or_insert(0);
*n += 1;
let title = mission
.milestones
.iter()
.find(|m| m.id == id)
.map(|m| m.title.as_str())
.unwrap_or("");
let _ = writeln!(md, "\n### {id} round {n} — {title}\n");
}
None => {
let _ = writeln!(md, "\n### Final gate\n");
}
}
if round.findings.is_empty() {
let _ = writeln!(md, "No findings.");
continue;
}
for (run_id, finding) in &round.findings {
let gate = if *run_id == crate::reducer::ENGINE_RUN_ID {
" *(final gate)*"
} else {
""
};
let evidence = scrub::scrub_and_truncate(
&finding
.evidence
.split_whitespace()
.collect::<Vec<_>>()
.join(" "),
200,
);
let _ = writeln!(
md,
"- [{}] {}{gate} — {evidence}",
finding.severity, finding.subject
);
}
if round.fix_features > 0 {
let _ = writeln!(
md,
"\nDisposition: {} fix feature(s) created.",
round.fix_features
);
}
if !round.waived.is_empty() {
let _ = writeln!(md, "\nDisposition: waived.");
for reasons in &round.waived {
for line in reasons.lines() {
let _ = writeln!(md, "{line}");
}
}
}
if let Some(reason) = round.blocked {
let _ = writeln!(md, "\nDisposition: milestone blocked — {reason}");
}
}
if !rendered_any {
let _ = writeln!(md, "\nNo validation rounds were recorded.");
}
if let Some(coverage) = crate::standards_coverage::standards_coverage(&mission.id, events) {
let _ = writeln!(
md,
"\n{}",
crate::standards_coverage::render_coverage_markdown(&coverage).trim_end_matches('\n')
);
}
let _ = writeln!(md, "\n## Contract outcomes");
if plan.validation_contract.is_empty() {
let _ = writeln!(md, "\nNo contract assertions were defined.");
} else {
let _ = writeln!(md);
for a in &plan.validation_contract {
let check = match (&a.check, &a.command) {
(AssertionCheck::Command, Some(cmd)) => format!("command: `{cmd}`"),
(AssertionCheck::Command, None) => "command".to_string(),
(AssertionCheck::PtyScript, _) => "pty script".to_string(),
_ => "agent judgement".to_string(),
};
let _ = writeln!(md, "- ✅ **[{}]** {} *({check})*", a.id, a.statement);
}
let _ = writeln!(
md,
"\nAll assertions passed at the final contract gate (waivers, if any, appear in \
the validation history)."
);
}
md
}
struct ValidationRound<'a> {
milestone_id: Option<&'a str>,
findings: Vec<(&'a str, &'a Finding)>,
fix_features: usize,
waived: Vec<&'a str>,
blocked: Option<&'a str>,
clean: bool,
}
fn collect_validation_rounds(events: &[Event]) -> Vec<ValidationRound<'_>> {
fn round(milestone_id: Option<&str>) -> ValidationRound<'_> {
ValidationRound {
milestone_id,
findings: Vec::new(),
fix_features: 0,
waived: Vec::new(),
blocked: None,
clean: false,
}
}
let mut rounds: Vec<ValidationRound<'_>> = Vec::new();
for event in events {
match &event.kind {
EventKind::MilestoneValidating { milestone_id } => {
rounds.push(round(Some(milestone_id)));
}
EventKind::MissionValidating {} => rounds.push(round(None)),
EventKind::ValidationFinding {
milestone_id,
run_id,
finding,
} => {
let gate = run_id == crate::reducer::ENGINE_RUN_ID;
let fits = rounds
.last()
.is_some_and(|r| !gate || r.milestone_id.is_none());
if !fits {
rounds.push(round(if gate { None } else { Some(milestone_id) }));
}
rounds
.last_mut()
.expect("pushed above")
.findings
.push((run_id, finding));
}
EventKind::FixFeatureCreated { .. } => {
if let Some(r) = rounds.iter_mut().rev().find(|r| !r.findings.is_empty()) {
r.fix_features += 1;
}
}
EventKind::OrchestratorDecision { summary, detail }
if summary.starts_with("waived") =>
{
if let Some(r) = rounds.iter_mut().rev().find(|r| !r.findings.is_empty()) {
r.waived.push(detail.as_deref().unwrap_or(summary));
}
}
EventKind::MilestoneBlocked { reason, .. } => {
if let Some(r) = rounds.iter_mut().rev().find(|r| !r.findings.is_empty()) {
r.blocked = Some(reason);
}
}
EventKind::MilestoneCompleted { milestone_id, .. } => {
if let Some(r) = rounds.last_mut() {
if r.milestone_id == Some(milestone_id.as_str()) && r.findings.is_empty() {
r.clean = true;
}
}
}
_ => {}
}
}
rounds
}
fn paused_time(events: &[Event], end: chrono::DateTime<chrono::Utc>) -> chrono::Duration {
let mut total = chrono::Duration::zero();
let mut paused_at: Option<chrono::DateTime<chrono::Utc>> = None;
for event in events {
match &event.kind {
EventKind::MissionPaused {} => {
if paused_at.is_none() {
paused_at = Some(event.ts);
}
}
EventKind::MissionResumed {} => {
if let Some(start) = paused_at.take() {
total += event.ts - start;
}
}
_ => {}
}
}
if let Some(start) = paused_at {
total += end - start;
}
total
}
fn format_duration(d: chrono::Duration) -> String {
let secs = d.num_seconds().max(0);
let (h, m, s) = (secs / 3600, (secs % 3600) / 60, secs % 60);
if h > 0 {
format!("{h}h {m:02}m {s:02}s")
} else if m > 0 {
format!("{m}m {s:02}s")
} else {
format!("{s}s")
}
}
fn short_commit(entry: &str) -> String {
let (sha, subject) = match entry.split_once(' ') {
Some((sha, subject)) => (sha, subject.trim()),
None => (entry, ""),
};
let looks_sha = sha.len() >= 7 && sha.chars().all(|c| c.is_ascii_hexdigit());
match (looks_sha, subject.is_empty()) {
(true, false) => format!("`{}` {}", &sha[..7], subject),
(true, true) => format!("`{}`", &sha[..7]),
_ => entry.to_string(),
}
}
fn first_sentence(text: &str) -> String {
const MAX: usize = 140;
let trimmed = text.trim();
if trimmed.is_empty() {
return String::new();
}
let first_line = trimmed.lines().next().unwrap_or("").trim();
let sentence_end = first_line
.find(". ")
.map(|i| i + 1)
.unwrap_or(first_line.len());
let candidate = first_line[..sentence_end].trim();
let candidate = if candidate.is_empty() {
first_line
} else {
candidate
};
if candidate.chars().count() <= MAX {
candidate.to_string()
} else {
let mut out: String = candidate.chars().take(MAX - 1).collect();
out.push('…');
out
}
}
fn feature_icon(status: FeatureStatus) -> &'static str {
match status {
FeatureStatus::Complete => "✅",
FeatureStatus::Failed => "❌",
FeatureStatus::Skipped => "⏭",
FeatureStatus::Active | FeatureStatus::Pending => "⏳",
}
}
fn milestone_icon(status: MilestoneStatus) -> &'static str {
match status {
MilestoneStatus::Complete => "✅",
MilestoneStatus::Blocked => "⛔",
_ => "⏳",
}
}
fn sandbox_enforce_label(enforce: SandboxEnforce) -> &'static str {
match enforce {
SandboxEnforce::Off => "off",
SandboxEnforce::Fs => "fs",
SandboxEnforce::FsNet => "fs+net",
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn extract_research_pulls_the_optional_object() {
let text = r#"{
"goal": "g",
"milestones": [{"title":"m","features":[{"title":"f","spec":"s","validationCriteria":["c"]}]}],
"validationContract": [],
"research": {
"filesRead": ["crates/engine/src/orchestrator.rs"],
"sources": ["https://example.com"],
"facts": [{"fact":"the run loop folds events","evidence":"reducer.rs"}],
"ambiguities": ["stale doc X"],
"candidateKnowledgeUpdates": ["add architecture/run-loop.md"]
}
}"#;
let r = extract_research(text).expect("research present");
assert_eq!(r.files_read, vec!["crates/engine/src/orchestrator.rs"]);
assert_eq!(r.facts.len(), 1);
assert_eq!(r.facts[0].evidence, "reducer.rs");
assert_eq!(r.candidate_knowledge_updates.len(), 1);
let none = r#"{"goal":"g","milestones":[{"title":"m","features":[{"title":"f","spec":"s","validationCriteria":["c"]}]}],"validationContract":[]}"#;
assert!(extract_research(none).is_none());
let empty = r#"{"goal":"g","milestones":[],"validationContract":[],"research":{}}"#;
assert!(extract_research(empty).is_none());
}
#[test]
fn render_research_markdown_lays_out_sections() {
let r = Research {
files_read: vec!["a.rs".into(), " ".into()],
sources: vec![],
facts: vec![
ResearchFact {
fact: "x holds".into(),
evidence: "a.rs:10".into(),
},
ResearchFact {
fact: " ".into(),
evidence: "".into(),
},
],
ambiguities: vec!["doc drift".into()],
candidate_knowledge_updates: vec!["note Y".into()],
};
let md = render_research_markdown(&r, "m-1");
assert!(md.contains("# Research — m-1"), "{md}");
assert!(md.contains("## Files & docs read"));
assert!(md.contains("- a.rs"));
assert!(md.contains("## Facts"));
assert!(md.contains("- x holds — `a.rs:10`"), "{md}");
assert!(md.contains("## Ambiguities & stale docs"));
assert!(md.contains("- note Y"));
assert!(!md.contains("## External sources"));
assert!(!md.contains("- \n"));
}
}