use std::fmt::Write as _;
use crate::engine::{normalize_prompt, stable_id, SymbolicAnswer};
use crate::event_log::EventLog;
use crate::seed;
use crate::solver_handlers::finalize_simple;
use crate::solver_helpers::{last_assistant_turn, last_user_turn};
use super::software_project::{is_approval_prompt, lino_string, SoftwareProjectMeaning};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum FollowUpKind {
Verification,
Execution,
Demonstration,
}
impl FollowUpKind {
const fn label(self) -> &'static str {
match self {
Self::Verification => "verification",
Self::Execution => "execution",
Self::Demonstration => "demonstration",
}
}
const fn action(self) -> &'static str {
match self {
Self::Verification => "test",
Self::Execution => "run",
Self::Demonstration => "show",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct SoftwareProjectFollowUp {
kind: FollowUpKind,
target_site: Option<String>,
expected_output: Option<String>,
}
pub fn try_software_project_followup(
prompt: &str,
normalized: &str,
log: &mut EventLog,
) -> Option<SymbolicAnswer> {
let canonical = normalize_prompt(prompt);
let normalized = if canonical.is_empty() {
normalized
} else {
canonical.as_str()
};
if is_approval_prompt(normalized) {
return None;
}
let (meaning, approved) = prior_software_project_dialogue(log)?;
let follow_up = detect_follow_up(prompt, normalized)?;
record_follow_up(log, &meaning, &follow_up, approved);
let body = render_follow_up_response(&meaning, &follow_up, approved);
Some(finalize_simple(
prompt,
log,
"software_project_followup",
"response:software_project_followup",
&body,
0.74,
))
}
fn detect_follow_up(prompt: &str, normalized: &str) -> Option<SoftwareProjectFollowUp> {
let kind = follow_up_kind(normalized)?;
Some(SoftwareProjectFollowUp {
kind,
target_site: extract_target_site(prompt),
expected_output: extract_expected_output(prompt),
})
}
fn follow_up_kind(normalized: &str) -> Option<FollowUpKind> {
for (role, kind) in [
(
seed::ROLE_SOFTWARE_FOLLOWUP_VERIFICATION,
FollowUpKind::Verification,
),
(
seed::ROLE_SOFTWARE_FOLLOWUP_EXECUTION,
FollowUpKind::Execution,
),
(
seed::ROLE_SOFTWARE_FOLLOWUP_DEMONSTRATION,
FollowUpKind::Demonstration,
),
] {
let mentioned = seed::lexicon().meanings_with_role(role).any(|meaning| {
meaning
.words()
.any(|word| !word.is_empty() && normalized.contains(word))
});
if mentioned {
return Some(kind);
}
}
None
}
fn extract_target_site(prompt: &str) -> Option<String> {
for raw in prompt.split_whitespace() {
let token = raw.trim_matches(|character: char| !character.is_ascii_alphanumeric());
if !token.contains('.') {
continue;
}
let mut parts = token.rsplitn(2, '.');
let tld = parts.next().unwrap_or("");
let host = parts.next().unwrap_or("");
if tld.len() >= 2
&& tld.chars().all(|character| character.is_ascii_alphabetic())
&& host
.chars()
.any(|character| character.is_ascii_alphabetic())
{
return Some(token.to_lowercase());
}
}
None
}
fn extract_expected_output(prompt: &str) -> Option<String> {
let lower = prompt.to_lowercase();
let forms = seed::lexicon().role_word_forms(seed::ROLE_OUTPUT_DISPLAY_REQUEST);
for form in &forms {
let marker = form.before_slot();
let Some(start) = lower.find(marker).map(|index| index + marker.len()) else {
continue;
};
let tail = &prompt[start..];
let stop = tail.find(['.', '?', '\n', ';']).unwrap_or(tail.len());
let clause = tail[..stop]
.split_whitespace()
.take(12)
.collect::<Vec<_>>()
.join(" ");
if !clause.is_empty() {
return Some(clause);
}
}
None
}
fn record_follow_up(
log: &mut EventLog,
meaning: &SoftwareProjectMeaning,
follow_up: &SoftwareProjectFollowUp,
approved: bool,
) {
log.append("formalization", "text_to_links_notation".to_owned());
log.append("meaning", follow_up_meaning_id(meaning, follow_up));
log.append("software_project:parent", meaning.meaning_id());
log.append(
"software_project:follow_up_kind",
follow_up.kind.label().to_owned(),
);
if let Some(site) = &follow_up.target_site {
log.append("software_project:target_site", site.clone());
}
if let Some(output) = &follow_up.expected_output {
log.append("software_project:expected_output", output.clone());
}
log.append(
"approval_state",
if approved { "approved" } else { "proposed" }.to_owned(),
);
for gate in follow_up_gates() {
log.append("approval_gate", gate.to_owned());
}
}
fn follow_up_meaning_id(
meaning: &SoftwareProjectMeaning,
follow_up: &SoftwareProjectFollowUp,
) -> String {
let key = format!(
"parent={};kind={};site={};output={}",
meaning.meaning_id(),
follow_up.kind.label(),
follow_up.target_site.as_deref().unwrap_or(""),
follow_up.expected_output.as_deref().unwrap_or(""),
);
stable_id("software_project_followup", &key)
}
const fn follow_up_gates() -> [&'static str; 3] {
["generated_code", "test_execution", "network_access"]
}
fn follow_up_reasoning_steps(
meaning: &SoftwareProjectMeaning,
follow_up: &SoftwareProjectFollowUp,
) -> Vec<String> {
let mut steps = vec![format!(
"Recognize \"{}\" as a {} request that exercises the {} from the active plan, not a fact lookup.",
follow_up.kind.action(),
follow_up.kind.label(),
meaning.artifact
)];
if let Some(site) = &follow_up.target_site {
steps.push(format!(
"Bind the test target to {site} and keep live fetches behind the network_access gate.",
));
}
if let Some(output) = &follow_up.expected_output {
steps.push(format!(
"Record the expected output as \"{output}\" so the test harness can assert it.",
));
}
steps.push(String::from(
"Drive the artifact through a deterministic fixture before any host API or network call.",
));
steps.push(String::from(
"Keep code execution behind approval gates because the sandbox cannot run untrusted code.",
));
steps
}
fn follow_up_plan_steps(
meaning: &SoftwareProjectMeaning,
follow_up: &SoftwareProjectFollowUp,
) -> Vec<String> {
let site = follow_up
.target_site
.clone()
.unwrap_or_else(|| String::from("the requested target"));
let mut steps = vec![
format!(
"Generate the {} core plus a deterministic test harness with a captured {site} fixture.",
meaning.artifact
),
String::from(
"Assert each requirement (parsing, extraction, counting, summary) against the fixture.",
),
];
if let Some(output) = &follow_up.expected_output {
steps.push(format!("Surface {output} from the fixture run."));
}
steps.push(format!(
"Run the {} test command once the generated_code gate is approved.",
meaning.implementation_language
));
steps.push(format!(
"Promote the run to live {site} only after the test_execution and network_access gates pass.",
));
steps
}
fn render_follow_up_response(
meaning: &SoftwareProjectMeaning,
follow_up: &SoftwareProjectFollowUp,
approved: bool,
) -> String {
let mut body = String::new();
let _ = writeln!(
body,
"Recorded a {} follow-up for the {} from the active plan.",
follow_up.kind.label(),
meaning.artifact
);
body.push('\n');
body.push_str("Formalized meaning:\n```lino\n");
body.push_str("software_project_followup\n");
let _ = writeln!(
body,
" parent_request {}",
lino_string(&meaning.meaning_id())
);
let _ = writeln!(body, " parent_artifact {}", lino_string(meaning.artifact));
let _ = writeln!(body, " action {}", lino_string(follow_up.kind.action()));
let _ = writeln!(body, " follow_up_kind {}", follow_up.kind.label());
if let Some(site) = &follow_up.target_site {
let _ = writeln!(body, " target_site {}", lino_string(site));
}
if let Some(output) = &follow_up.expected_output {
let _ = writeln!(body, " expected_output {}", lino_string(output));
}
let _ = writeln!(body, " delivery_mode {}", meaning.delivery_mode_label());
let _ = writeln!(
body,
" implementation_language {}",
lino_string(meaning.implementation_language)
);
let _ = writeln!(
body,
" approval_state {}",
if approved { "approved" } else { "proposed" }
);
body.push_str(" approval_required true\n");
for gate in follow_up_gates() {
let _ = writeln!(body, " approval_gate {}", lino_string(gate));
}
body.push_str("```\n\nReasoning steps:\n");
for (index, step) in follow_up_reasoning_steps(meaning, follow_up)
.iter()
.enumerate()
{
let _ = writeln!(body, "{}. {step}", index + 1);
}
body.push_str("\nVerification plan:\n");
for (index, step) in follow_up_plan_steps(meaning, follow_up).iter().enumerate() {
let _ = writeln!(body, "{}. {step}", index + 1);
}
body.push('\n');
if approved {
body.push_str(
"The plan is approved, so the generated starter already includes this test harness. \
Running it live needs the test_execution and network_access gates.",
);
} else {
body.push_str(
"Reply `approve plan` to generate the artifact plus this test harness. Running it live \
against the target needs the test_execution and network_access gates.",
);
}
body
}
fn prior_software_project_dialogue(log: &EventLog) -> Option<(SoftwareProjectMeaning, bool)> {
let assistant = last_assistant_turn(log)?;
if !assistant.contains("software_project_request") {
return None;
}
let approved = assistant.contains("approval_state approved");
let prior_prompt = last_user_turn(log)?;
let normalized = normalize_prompt(prior_prompt);
let meaning = SoftwareProjectMeaning::from_prompt(prior_prompt, &normalized)?;
Some((meaning, approved))
}