#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum StagnationPattern {
Spinning,
NoDrift,
DiminishingReturns,
Oscillation,
}
#[derive(Debug, Clone)]
pub struct LateralPersona {
pub name: &'static str,
pub philosophy: &'static str,
pub approach: &'static [&'static str],
pub questions: &'static [&'static str],
pub affinities: &'static [StagnationPattern],
}
pub static PERSONAS: &[LateralPersona] = &[
LateralPersona {
name: "Contrarian",
philosophy: "What everyone assumes is true, you examine.",
approach: &[
"List the assumptions being made",
"Consider the opposite of each assumption",
"Challenge the problem statement itself",
"What if the goal is wrong?",
],
questions: &[
"What is everyone assuming that might be false?",
"If you inverted the goal, what would that look like?",
"What would a critic say about this approach?",
],
affinities: &[
StagnationPattern::Spinning,
StagnationPattern::Oscillation,
StagnationPattern::DiminishingReturns,
StagnationPattern::NoDrift,
],
},
LateralPersona {
name: "Hacker",
philosophy: "Rules are obstacles to route around.",
approach: &[
"Identify the constraints blocking progress",
"Question whether each constraint is real or assumed",
"Find the edge case that bypasses the constraint",
"Can you achieve the goal without following the normal path?",
],
questions: &[
"What constraint is causing the most pain?",
"Is this constraint real or self-imposed?",
"Can you achieve a similar result with a completely different approach?",
],
affinities: &[StagnationPattern::Spinning],
},
LateralPersona {
name: "Simplifier",
philosophy: "Complexity is the enemy. Cut until it works.",
approach: &[
"List every component in the current approach",
"Challenge whether each component is necessary",
"Find the minimum that could possibly work",
"Implement the simplest thing first",
],
questions: &[
"What can you remove without breaking the goal?",
"Is there a simpler way to express the same thing?",
"What would YAGNI (You Ain't Gonna Need It) cut?",
],
affinities: &[
StagnationPattern::DiminishingReturns,
StagnationPattern::Oscillation,
],
},
LateralPersona {
name: "Researcher",
philosophy: "Most problems exist because we're missing information.",
approach: &[
"Define what you don't know",
"Gather evidence from the codebase or docs",
"Look for patterns in the failure history",
"Form a new hypothesis based on evidence",
],
questions: &[
"What information are you missing?",
"What does the error/failure pattern tell you?",
"Has anyone solved a similar problem before?",
],
affinities: &[
StagnationPattern::NoDrift,
StagnationPattern::DiminishingReturns,
],
},
LateralPersona {
name: "Architect",
philosophy: "If you're fighting the architecture, the architecture is wrong.",
approach: &[
"Identify structural symptoms (repeated patterns, workarounds)",
"Map the current structure",
"Find the misalignment between structure and goal",
"Propose a structural change that makes the goal natural",
],
questions: &[
"Is the current structure fighting the goal?",
"What structural change would make this trivial?",
"Are you working against the grain of the system?",
],
affinities: &[StagnationPattern::Oscillation, StagnationPattern::NoDrift],
},
];
pub fn select_persona(
pattern: StagnationPattern,
tried: &[&str],
) -> Option<&'static LateralPersona> {
PERSONAS
.iter()
.filter(|p| p.affinities.contains(&pattern))
.find(|p| !tried.contains(&p.name))
}
pub fn build_lateral_prompt(
persona: &LateralPersona,
goal: &str,
current_approach: &str,
failed_attempts: &[String],
) -> String {
let mut parts = vec![
format!("## Persona: {}", persona.name),
format!("_\"{}\"_", persona.philosophy),
String::new(),
"## Problem Context".to_string(),
goal.to_string(),
String::new(),
"## Current Approach (Not Working)".to_string(),
current_approach.to_string(),
];
if !failed_attempts.is_empty() {
parts.push(String::new());
parts.push("## Previous Failed Attempts".to_string());
for attempt in failed_attempts {
parts.push(format!("- {attempt}"));
}
}
parts.push(String::new());
parts.push("## Lateral Thinking Instructions".to_string());
for step in persona.approach {
parts.push(format!("- {step}"));
}
parts.push(String::new());
parts.push("## Questions to Consider".to_string());
for q in persona.questions {
parts.push(format!("- {q}"));
}
parts.push(String::new());
parts.push("## Your Alternative Approach".to_string());
parts.push(
"Propose a fundamentally different approach that addresses the root cause.".to_string(),
);
parts.join("\n")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_select_persona_by_affinity() {
let p = select_persona(StagnationPattern::Spinning, &[]);
assert!(p.is_some());
assert_eq!(p.unwrap().name, "Contrarian");
}
#[test]
fn test_select_persona_excludes_tried() {
let p = select_persona(StagnationPattern::Spinning, &["Contrarian"]);
assert!(p.is_some());
assert_eq!(p.unwrap().name, "Hacker");
}
#[test]
fn test_select_persona_returns_none_when_all_exhausted() {
let tried: &[&str] = &["Contrarian", "Hacker"];
let p = select_persona(StagnationPattern::Spinning, tried);
assert!(p.is_none());
}
#[test]
fn test_build_lateral_prompt_contains_persona() {
let persona = &PERSONAS[0]; let prompt =
build_lateral_prompt(persona, "Fix the auth bug", "Tried adding null checks", &[]);
assert!(prompt.contains("Contrarian"));
assert!(prompt.contains("auth bug"));
assert!(prompt.contains("null checks"));
}
#[test]
fn test_build_lateral_prompt_includes_failed_attempts() {
let persona = &PERSONAS[0];
let prompt = build_lateral_prompt(
persona,
"Goal",
"Approach",
&["Attempt 1".to_string(), "Attempt 2".to_string()],
);
assert!(prompt.contains("Attempt 1"));
assert!(prompt.contains("Attempt 2"));
}
}