1use crate::unit::Unit;
2
3pub fn build_decomposition_prompt(id: &str, unit: &Unit, strategy: Option<&str>) -> String {
9 let strategy_guidance = match strategy {
10 Some("feature") | Some("by-feature") => {
11 "Split by feature — each child is a vertical slice (types + impl + tests for one feature)."
12 }
13 Some("layer") | Some("by-layer") => {
14 "Split by layer — types/interfaces first, then implementation, then tests."
15 }
16 Some("file") | Some("by-file") => {
17 "Split by file — each child handles one file or closely related file group."
18 }
19 Some("phase") => {
20 "Split by phase — scaffold first, then core logic, then edge cases, then polish."
21 }
22 Some(other) => {
23 return build_prompt_text(id, unit, other);
24 }
25 None => "Choose the best strategy: by-feature (vertical slices), by-layer, or by-file.",
26 };
27
28 build_prompt_text(id, unit, strategy_guidance)
29}
30
31fn build_prompt_text(id: &str, unit: &Unit, strategy_guidance: &str) -> String {
32 let title = &unit.title;
33 let priority = unit.priority;
34 let description = unit.description.as_deref().unwrap_or("(no description)");
35
36 let mut dep_context = String::new();
37 if !unit.produces.is_empty() {
38 dep_context.push_str(&format!("\nProduces: {}\n", unit.produces.join(", ")));
39 }
40 if !unit.requires.is_empty() {
41 dep_context.push_str(&format!("Requires: {}\n", unit.requires.join(", ")));
42 }
43
44 format!(
45 r#"Decompose unit {id} into smaller child units.
46
47## Parent Unit
48- **ID:** {id}
49- **Title:** {title}
50- **Priority:** P{priority}
51{dep_context}
52## Strategy
53{strategy_guidance}
54
55## Goal
56Each child unit must be **completable by a fast, non-thinking model in a single pass**.
57This means: no design decisions, no ambiguity, no exploration. Every child should
58read like a recipe — follow the steps, pass verify, done.
59
60## What Makes a Unit One-Shottable
61- **Specific instructions** — exact file paths, function signatures, concrete steps
62- **Embedded context** — relevant types, patterns, and existing code in the description itself
63- **Small scope** — touches 1-3 files, writes 1-5 functions
64- **Clear acceptance** — the verify command tests exactly what matters
65- **No decisions left** — the "what" and "how" are both fully specified
66
67## Splitting Rules
68- Create **2-4 children** for medium units, **3-5** for large ones
69- **Maximize parallelism** — prefer independent units over sequential chains
70- Each child must have a **verify command** that exits 0 on success
71- Children should be independently testable where possible
72- Use `--produces` and `--requires` to express dependencies between siblings
73
74## Context Embedding Rules
75- **Embed context into descriptions** — don't reference files, include the relevant types/signatures
76- Include: concrete file paths, function signatures, type definitions
77- Include: specific steps, edge cases, error handling requirements
78- Be specific: "Add `fn validate_email(s: &str) -> bool` to `src/util.rs`" not "add validation"
79
80## How to Create Children
81Use `mana create` for each child unit:
82
83```
84mana create "child title" \
85 --parent {id} \
86 --priority {priority} \
87 --verify "test command that exits 0" \
88 --produces "artifact_name" \
89 --requires "artifact_from_sibling" \
90 --description "Full description with:
91- What to implement
92- Which files to modify (with paths)
93- Key types/signatures to use or create
94- Acceptance criteria
95- Edge cases to handle"
96```
97
98## Description Template
99A good child unit description includes:
1001. **What**: One clear sentence of what this child does
1012. **Files**: Specific file paths with what changes in each
1023. **Context**: Embedded type definitions, function signatures, patterns to follow
1034. **Acceptance**: Concrete criteria the verify command checks
1045. **Edge cases**: What could go wrong, what to handle
105
106## Your Task
1071. Read the parent unit's description below
1082. Examine referenced source files to understand the code
1093. Decide on a split strategy
1104. Create 2-5 child units using `mana create` commands
1115. Ensure every child has a verify command and enough embedded context
112 that a fast model can implement it without exploring the codebase
1136. After creating children, run `mana tree {id}` to show the result
114
115## Parent Unit Description
116{description}"#,
117 )
118}
119
120pub fn shell_escape(s: &str) -> String {
122 let escaped = s.replace('\'', "'\\''");
123 format!("'{}'", escaped)
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129
130 #[test]
131 fn build_prompt_includes_rules() {
132 let unit = Unit::new("42", "Implement auth system");
133 let prompt = build_decomposition_prompt("42", &unit, None);
134
135 assert!(prompt.contains("Decompose unit 42"));
136 assert!(prompt.contains("Implement auth system"));
137 assert!(prompt.contains("non-thinking model"));
138 assert!(prompt.contains("Maximize parallelism"));
139 assert!(prompt.contains("Embed context"));
140 assert!(prompt.contains("verify command"));
141 assert!(prompt.contains("mana create"));
142 assert!(prompt.contains("--parent 42"));
143 assert!(prompt.contains("--produces"));
144 assert!(prompt.contains("--requires"));
145 }
146
147 #[test]
148 fn build_prompt_with_strategy() {
149 let unit = Unit::new("1", "Big task");
150 let prompt = build_decomposition_prompt("1", &unit, Some("by-feature"));
151 assert!(prompt.contains("vertical slice"));
152 }
153
154 #[test]
155 fn build_prompt_includes_produces_requires() {
156 let mut unit = Unit::new("5", "Task with deps");
157 unit.produces = vec!["auth_types".to_string(), "auth_middleware".to_string()];
158 unit.requires = vec!["db_connection".to_string()];
159
160 let prompt = build_decomposition_prompt("5", &unit, None);
161 assert!(prompt.contains("auth_types"));
162 assert!(prompt.contains("db_connection"));
163 }
164
165 #[test]
166 fn build_prompt_custom_strategy_passed_through() {
167 let unit = Unit::new("1", "Task");
168 let prompt = build_decomposition_prompt("1", &unit, Some("my custom approach"));
169 assert!(prompt.contains("my custom approach"));
170 }
171
172 #[test]
173 fn shell_escape_simple() {
174 assert_eq!(shell_escape("hello world"), "'hello world'");
175 }
176
177 #[test]
178 fn shell_escape_with_quotes() {
179 assert_eq!(shell_escape("it's here"), "'it'\\''s here'");
180 }
181}