use crate::state::Status;
pub struct PromptContext {
pub story_id: String,
pub stories_dir: String,
pub decisions_dir: String,
pub last_rejection: Option<String>,
pub from: Status,
pub to: Status,
}
impl PromptContext {
pub fn po_groom(&self) -> String {
format!(
"Refina {id}. Lee {dir}/{id}.md.\n\
ValÃdala contra el Definition of Ready. Si está lista, muévela de {from} → {to}.\n\
Si necesitas tomar decisiones, documéntalas en {dec}/.\n\
Añade entrada en Activity Log con el formato: YYYY-MM-DD | PO | descripción.\n\
NO preguntes nada al usuario. Trabaja de forma 100% autónoma.",
id = self.story_id,
dir = self.stories_dir,
dec = self.decisions_dir,
from = self.from,
to = self.to,
)
}
pub fn po_validate(&self) -> String {
format!(
"Valida {id} para Done. Lee {dir}/{id}.md.\n\
Verifica que el valor de negocio se cumple. Si OK → {to}.\n\
Si no: rechaza a In Review o In Progress según gravedad. Detalla el motivo.\n\
Documenta la decisión en {dec}/.\n\
NO preguntes. 100% autónomo.",
id = self.story_id,
dir = self.stories_dir,
dec = self.decisions_dir,
to = self.to,
)
}
pub fn qa_tests(&self) -> String {
format!(
"Escribe tests para {id}. Lee {dir}/{id}.md.\n\
Escribe los tests necesarios según los criterios de aceptación.\n\
Mueve el estado de {from} → {to}.\n\
Añade entrada en Activity Log: YYYY-MM-DD | QA | descripción.\n\
Si necesitas crear placeholders en src/ para que los tests compilen, hazlo.\n\
Documenta decisiones de diseño en {dec}/.\n\
NO preguntes. 100% autónomo.",
id = self.story_id,
dir = self.stories_dir,
dec = self.decisions_dir,
from = self.from,
to = self.to,
)
}
pub fn qa_fix_tests(&self) -> String {
format!(
"Corrige los tests de {id}. El Developer reportó problemas con los tests actuales.\n\
Lee {dir}/{id}.md, especialmente el Activity Log para entender el feedback.\n\
Corrige los tests. El estado se mantiene en {to}.\n\
Añade entrada en Activity Log: YYYY-MM-DD | QA | descripción de la corrección.\n\
NO preguntes. 100% autónomo.",
id = self.story_id,
dir = self.stories_dir,
to = self.to,
)
}
pub fn dev_implement(&self) -> String {
format!(
"Implementa {id}. Lee {dir}/{id}.md.\n\
Los tests ya existen (QA los escribió). Búscalos y haz que pasen.\n\
Implementa en el código fuente. Ejecuta build + tests.\n\
Mueve de {from} → {to}.\n\
Añade entrada en Activity Log: YYYY-MM-DD | Dev | descripción.\n\
Documenta decisiones de arquitectura en {dec}/.\n\
NO preguntes. 100% autónomo.",
id = self.story_id,
dir = self.stories_dir,
dec = self.decisions_dir,
from = self.from,
to = self.to,
)
}
pub fn dev_fix(&self) -> String {
let rejection = self
.last_rejection
.as_deref()
.unwrap_or("(revisa el Activity Log para los detalles)");
format!(
"Corrige {id}. El Reviewer/PO rechazó la implementación anterior:\n\
\n {rejection}\n\
\n\
Lee {dir}/{id}.md, especialmente el Activity Log, para el contexto completo.\n\
Corrige la implementación. Ejecuta build + tests.\n\
Mueve de {from} → {to}.\n\
Añade entrada en Activity Log: YYYY-MM-DD | Dev | qué corregiste y por qué.\n\
NO preguntes. 100% autónomo.",
id = self.story_id,
dir = self.stories_dir,
from = self.from,
to = self.to,
)
}
pub fn reviewer(&self) -> String {
format!(
"Revisa {id}. Lee {dir}/{id}.md.\n\
Verifica el DoD técnico. Ejecuta cargo test, clippy, fmt.\n\
Si TODO OK → Business Review.\n\
Si algo falla → In Progress, con detalles CONCRETOS de archivo, lÃnea y problema.\n\
Añade entrada en Activity Log: YYYY-MM-DD | Reviewer | resultado.\n\
Documenta observaciones en {dec}/.\n\
NO preguntes. 100% autónomo.",
id = self.story_id,
dir = self.stories_dir,
dec = self.decisions_dir,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn ctx() -> PromptContext {
PromptContext {
story_id: "STORY-042".into(),
stories_dir: "product/stories".into(),
decisions_dir: "product/decisions".into(),
last_rejection: Some("Falta test para edge case CA3".into()),
from: Status::InProgress,
to: Status::InReview,
}
}
#[test]
fn po_groom_contains_story_id() {
let groom_ctx = PromptContext {
from: Status::Draft,
to: Status::Ready,
..ctx()
};
let prompt = groom_ctx.po_groom();
assert!(prompt.contains("STORY-042"));
assert!(prompt.contains("Draft"));
assert!(prompt.contains("Ready"));
}
#[test]
fn dev_fix_includes_rejection() {
let prompt = ctx().dev_fix();
assert!(prompt.contains("Falta test para edge case CA3"));
assert!(prompt.contains("In Progress"));
assert!(prompt.contains("In Review"));
}
#[test]
fn all_prompts_contain_story_id() {
for prompt in [
ctx().po_groom(),
ctx().po_validate(),
ctx().qa_tests(),
ctx().qa_fix_tests(),
ctx().dev_implement(),
ctx().dev_fix(),
ctx().reviewer(),
] {
assert!(
prompt.contains("STORY-042"),
"prompt should mention STORY-042"
);
}
}
#[test]
fn all_prompts_contain_no_preguntes() {
for prompt in [
ctx().po_groom(),
ctx().po_validate(),
ctx().qa_tests(),
ctx().qa_fix_tests(),
ctx().dev_implement(),
ctx().dev_fix(),
ctx().reviewer(),
] {
assert!(
prompt.contains("NO preguntes"),
"prompt should tell agent not to ask user"
);
}
}
}