dial/iteration/
context.rs1use crate::errors::Result;
2use crate::learning::increment_learning_reference;
3use crate::task::models::Task;
4use crate::TRUST_THRESHOLD;
5use rusqlite::Connection;
6
7const 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 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 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 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 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 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 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 let _ = increment_learning_reference(conn, id);
142 }
143 }
144
145 Ok(context.join("\n\n"))
146}
147
148pub 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}