1use crate::scrub::truncate_chars;
14use crate::types::{
15 AssertionCheck, FeatureOrigin, FeatureStatus, MilestoneStatus, MissionState, MissionStatus,
16};
17
18const ITEM_MAX: usize = 160;
20
21const DECISION_MAX: usize = 200;
23
24const MAX_DECISIONS: usize = 10;
27
28pub fn render(state: &MissionState) -> String {
31 let mission = &state.mission;
32 let mut out = String::new();
33
34 out.push_str(&format!(
35 "MISSION {} [{}] — {}\n",
36 mission.id,
37 mission_status(mission.status),
38 truncate_chars(&mission.goal, ITEM_MAX)
39 ));
40 out.push_str(&format!(
41 "branch {} (from {}) | tokens in/out {}/{} | cost ${:.2}\n",
42 mission.mission_branch,
43 mission.base_branch,
44 state.totals.input,
45 state.totals.output,
46 state.total_cost_usd
47 ));
48
49 out.push_str("CONTRACT:\n");
50 for assertion in &mission.validation_contract {
51 out.push_str(&format!(
52 "- [{}|{}] {}",
53 assertion.id,
54 check_kind(assertion.check),
55 truncate_chars(&assertion.statement, ITEM_MAX)
56 ));
57 if let Some(command) = &assertion.command {
58 out.push_str(&format!(" :: {}", truncate_chars(command, ITEM_MAX)));
59 }
60 out.push('\n');
61 }
62
63 let repair_cap = state.config.max_fix_cycles_per_milestone;
64 out.push_str(&format!(
65 "REPAIR POLICY: current cap {repair_cap} rounds per milestone, per executor tier. \
66 Current policy supersedes planning/research observations. Exhaustion limits repairs; \
67 it does not justify a waiver or establish that the contract is met.\n"
68 ));
69 out.push_str("MILESTONES:\n");
70 for milestone in &mission.milestones {
71 out.push_str(&format!(
72 "{} [{}] {} (fixCycles {}, repair cap {}, remaining {})\n",
73 milestone.id,
74 milestone_status(milestone.status),
75 truncate_chars(&milestone.title, ITEM_MAX),
76 milestone.fix_cycles,
77 repair_cap,
78 repair_cap.saturating_sub(milestone.fix_cycles)
79 ));
80 for feature in &milestone.features {
81 out.push_str(&format!(
82 " {} [{}|{}] {} (runs {}, respawns {})\n",
83 feature.id,
84 feature_status(feature.status),
85 feature_origin(feature.origin),
86 truncate_chars(&feature.title, ITEM_MAX),
87 feature.worker_runs.len(),
88 feature.respawns
89 ));
90 }
91 }
92
93 out.push_str("RECENT DECISIONS:\n");
94 let skip = state.recent_decisions.len().saturating_sub(MAX_DECISIONS);
95 for decision in &state.recent_decisions[skip..] {
96 out.push_str(&format!("- {}\n", truncate_chars(decision, DECISION_MAX)));
97 }
98
99 out.push_str("OPEN USER MESSAGES:\n");
100 if state.pending_user_messages.is_empty() {
101 out.push_str("(none)\n");
102 } else {
103 for message in &state.pending_user_messages {
104 out.push_str(&format!("- {}\n", truncate_chars(message, ITEM_MAX)));
105 }
106 }
107
108 out.push_str("You are resuming from durable state; the event log is authoritative.");
109 out
110}
111
112pub fn render_reseed(state: &MissionState, plan_json: &str) -> String {
116 format!(
117 "{}\n\nAPPROVED PLAN (plan.json):\n{}",
118 render(state),
119 plan_json
120 )
121}
122
123fn mission_status(status: MissionStatus) -> &'static str {
124 match status {
125 MissionStatus::Planning => "planning",
126 MissionStatus::Approved => "approved",
127 MissionStatus::Running => "running",
128 MissionStatus::Paused => "paused",
129 MissionStatus::Blocked => "blocked",
130 MissionStatus::Validating => "validating",
131 MissionStatus::Complete => "complete",
132 MissionStatus::Failed => "failed",
133 MissionStatus::Abandoned => "abandoned",
134 }
135}
136
137fn milestone_status(status: MilestoneStatus) -> &'static str {
138 match status {
139 MilestoneStatus::Pending => "pending",
140 MilestoneStatus::Active => "active",
141 MilestoneStatus::Validating => "validating",
142 MilestoneStatus::Complete => "complete",
143 MilestoneStatus::Blocked => "blocked",
144 }
145}
146
147fn feature_status(status: FeatureStatus) -> &'static str {
148 match status {
149 FeatureStatus::Pending => "pending",
150 FeatureStatus::Active => "active",
151 FeatureStatus::Complete => "complete",
152 FeatureStatus::Skipped => "skipped",
153 FeatureStatus::Failed => "failed",
154 }
155}
156
157fn feature_origin(origin: FeatureOrigin) -> &'static str {
158 match origin {
159 FeatureOrigin::Plan => "plan",
160 FeatureOrigin::Fix => "fix",
161 }
162}
163
164fn check_kind(check: AssertionCheck) -> &'static str {
165 match check {
166 AssertionCheck::Command => "command",
167 AssertionCheck::AgentJudgement => "judgement",
168 AssertionCheck::PtyScript => "pty-script",
169 }
170}