Skip to main content

dial/iteration/
context.rs

1use crate::errors::Result;
2use crate::learning::increment_learning_reference;
3use crate::task::models::Task;
4use crate::TRUST_THRESHOLD;
5use rusqlite::Connection;
6
7/// Behavioral guardrails ("signs") that prevent context rot
8/// by reminding the agent of critical rules at the start of each task.
9const SIGNS: &[&str] = &[
10    "ONE TASK ONLY: Complete exactly this task. No scope creep.",
11    "SEARCH BEFORE CREATE: Always search for existing files/functions before creating new ones.",
12    "NO PLACEHOLDERS: Every implementation must be complete. No TODO, FIXME, or stub code.",
13    "VALIDATE BEFORE DONE: Run `dial validate` after implementing. Don't mark complete without testing.",
14    "RECORD LEARNINGS: After success, capture what you learned with `dial learn \"...\" -c category`.",
15    "FAIL FAST: If blocked or confused, stop and ask rather than guessing.",
16];
17
18pub fn gather_context(conn: &Connection, task: &Task) -> Result<String> {
19    gather_context_impl(conn, task, true)
20}
21
22pub fn gather_context_without_signs(conn: &Connection, task: &Task) -> Result<String> {
23    gather_context_impl(conn, task, false)
24}
25
26fn gather_context_impl(conn: &Connection, task: &Task, include_signs: bool) -> Result<String> {
27    let mut context = Vec::new();
28
29    // Add behavioral signs first (most important for context rot prevention)
30    if include_signs {
31        context.push("## ⚠️ SIGNS (Critical Rules)\n".to_string());
32        for sign in SIGNS {
33            context.push(format!("- **{}**", sign));
34        }
35        context.push(String::new());
36    }
37
38    // Get relevant spec sections
39    if let Some(spec_id) = task.spec_section_id {
40        let mut stmt = conn.prepare(
41            "SELECT content FROM spec_sections WHERE id = ?1",
42        )?;
43
44        if let Ok(content) = stmt.query_row([spec_id], |row| row.get::<_, String>(0)) {
45            context.push(format!("## Relevant Specification\n\n{}", content));
46        }
47    }
48
49    // Search for task-related specs
50    let mut stmt = conn.prepare(
51        "SELECT s.heading_path, s.content
52         FROM spec_sections s
53         INNER JOIN spec_sections_fts fts ON s.id = fts.rowid
54         WHERE spec_sections_fts MATCH ?1
55         ORDER BY rank LIMIT 3",
56    )?;
57
58    let related_specs: Vec<(String, String)> = stmt
59        .query_map([&task.description], |row| Ok((row.get(0)?, row.get(1)?)))?
60        .filter_map(|r| r.ok())
61        .collect();
62
63    if !related_specs.is_empty() {
64        context.push("## Related Specifications\n".to_string());
65        for (heading, content) in related_specs {
66            let preview = if content.len() > 500 {
67                &content[..500]
68            } else {
69                &content
70            };
71            context.push(format!("### {}\n{}", heading, preview));
72        }
73    }
74
75    // Get trusted solutions for common patterns
76    let mut stmt = conn.prepare(
77        "SELECT s.description, fp.pattern_key
78         FROM solutions s
79         INNER JOIN failure_patterns fp ON s.pattern_id = fp.id
80         WHERE s.confidence >= ?1
81         ORDER BY fp.occurrence_count DESC LIMIT 5",
82    )?;
83
84    let solutions: Vec<(String, String)> = stmt
85        .query_map([TRUST_THRESHOLD], |row| Ok((row.get(0)?, row.get(1)?)))?
86        .filter_map(|r| r.ok())
87        .collect();
88
89    if !solutions.is_empty() {
90        context.push("## Trusted Solutions (apply if relevant failure occurs)\n".to_string());
91        for (description, pattern_key) in solutions {
92            context.push(format!("- **{}**: {}", pattern_key, description));
93        }
94    }
95
96    // Get recent failures to avoid
97    let mut stmt = conn.prepare(
98        "SELECT f.error_text, fp.pattern_key
99         FROM failures f
100         INNER JOIN failure_patterns fp ON f.pattern_id = fp.id
101         WHERE f.resolved = 0
102         ORDER BY f.created_at DESC LIMIT 5",
103    )?;
104
105    let failures: Vec<(String, String)> = stmt
106        .query_map([], |row| Ok((row.get(0)?, row.get(1)?)))?
107        .filter_map(|r| r.ok())
108        .collect();
109
110    if !failures.is_empty() {
111        context.push("## Recent Unresolved Failures (avoid these)\n".to_string());
112        for (error_text, pattern_key) in failures {
113            let preview = if error_text.len() > 200 {
114                &error_text[..200]
115            } else {
116                &error_text
117            };
118            context.push(format!("- **{}**: {}", pattern_key, preview));
119        }
120    }
121
122    // Get project learnings
123    let mut stmt = conn.prepare(
124        "SELECT id, category, description
125         FROM learnings
126         ORDER BY times_referenced DESC, discovered_at DESC
127         LIMIT 10",
128    )?;
129
130    let learnings: Vec<(i64, Option<String>, String)> = stmt
131        .query_map([], |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)))?
132        .filter_map(|r| r.ok())
133        .collect();
134
135    if !learnings.is_empty() {
136        context.push("## Project Learnings (apply these patterns)\n".to_string());
137        for (id, category, description) in learnings {
138            let cat_str = category.map(|c| format!("[{}]", c)).unwrap_or_default();
139            context.push(format!("- {} {}", cat_str, description));
140            // Increment reference count
141            let _ = increment_learning_reference(conn, id);
142        }
143    }
144
145    Ok(context.join("\n\n"))
146}
147
148/// Generate a fresh context prompt for spawning a sub-agent (orchestrator mode).
149/// This produces a self-contained prompt that can be used to spawn a fresh AI session.
150pub fn generate_subagent_prompt(conn: &Connection, task: &Task) -> Result<String> {
151    let context = gather_context(conn, task)?;
152
153    let prompt = format!(
154        r#"# DIAL Sub-Agent Task
155
156You are a fresh AI agent spawned by DIAL to complete ONE task with clean context.
157
158## Your Task
159**Task #{id}:** {description}
160
161{context}
162
163## Instructions
164
1651. **Implement** the task completely (no placeholders)
1662. **Test** your implementation locally if possible
1673. When done, output: `DIAL_COMPLETE: <summary of what was done>`
1684. If blocked, output: `DIAL_BLOCKED: <what is blocking>`
1695. If you learned something valuable, output: `DIAL_LEARNING: <category>: <what you learned>`
170
171Do NOT deviate from this task. Do NOT start other tasks.
172"#,
173        id = task.id,
174        description = task.description,
175        context = context
176    );
177
178    Ok(prompt)
179}