Skip to main content

archidoc_engine/
init.rs

1use std::path::Path;
2
3/// Supported comment styles for different languages.
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum CommentStyle {
6    /// Rust doc comments: `//!`
7    Rust,
8    /// TypeScript/JavaScript JSDoc: `/** ... */`
9    TypeScript,
10}
11
12impl CommentStyle {
13    /// Auto-detect from project root by looking for Cargo.toml / package.json.
14    pub fn detect(root: &Path) -> Option<Self> {
15        if root.join("Cargo.toml").exists() {
16            Some(Self::Rust)
17        } else if root.join("package.json").exists() {
18            Some(Self::TypeScript)
19        } else {
20            None
21        }
22    }
23
24    pub fn from_lang(lang: &str) -> Option<Self> {
25        match lang.to_lowercase().as_str() {
26            "rust" | "rs" => Some(Self::Rust),
27            "typescript" | "ts" | "javascript" | "js" => Some(Self::TypeScript),
28            _ => None,
29        }
30    }
31}
32
33/// Generate a root-level annotation template for a project's entry file.
34///
35/// Outputs a doc comment block with recommended architectural sections,
36/// each with TODO placeholders. Designed to be pasted into `lib.rs`, `index.ts`, etc.
37pub fn generate_template(style: CommentStyle) -> String {
38    let sections = vec![
39        Section::heading("@c4 container"),
40        Section::heading("[Project Name]").h1(),
41        Section::blank(),
42        Section::line("[TODO: One-line description — what this system does and why it exists.]"),
43        Section::blank(),
44        // ── Yellow tier: Intent ──────────────────────────────────────
45        Section::heading("Business Context").h2(),
46        Section::blank(),
47        Section::line("- TODO: What problem does this solve? (business need, not technical)"),
48        Section::line("- TODO: Who sponsors this project and what is their primary goal?"),
49        Section::line("- TODO: What existing process does this replace or augment?"),
50        Section::line("- TODO: What is the value proposition? (saves time, reduces risk, unlocks revenue)"),
51        Section::blank(),
52        Section::heading("Domain Model").h2(),
53        Section::blank(),
54        Section::line("- TODO: Key domain concepts and relationships (ubiquitous language)"),
55        Section::line("- TODO: Bounded contexts — where does this system's vocabulary differ from adjacent systems?"),
56        Section::line("- TODO: Domain events — what real-world events does this system respond to?"),
57        Section::blank(),
58        Section::heading("Users & Stakeholders").h2(),
59        Section::blank(),
60        Section::line("- TODO: Primary users — who uses this daily and what are their goals?"),
61        Section::line("- TODO: Secondary stakeholders — who cares about outcomes but doesn't interact directly?"),
62        Section::line("- TODO: What does a typical user workflow look like?"),
63        Section::blank(),
64        Section::heading("Success Criteria").h2(),
65        Section::blank(),
66        Section::line("- TODO: How do you know this project is succeeding? (measurable outcomes)"),
67        Section::line("- TODO: What must be true before this ships? (acceptance criteria)"),
68        Section::line("- TODO: What must this NOT do? (anti-goals, failure modes)"),
69        Section::blank(),
70        Section::heading("Constraints & Trade-offs").h2(),
71        Section::blank(),
72        Section::line("- TODO: Deliberate trade-offs (e.g., maintainability over performance)"),
73        Section::line("- TODO: Non-negotiable constraints (regulatory, timeline, budget, compatibility)"),
74        Section::line("- TODO: What was explicitly ruled out and why?"),
75        Section::blank(),
76        // ── Blue tier: Structure ─────────────────────────────────────
77        Section::heading("C4 Context").h2(),
78        Section::blank(),
79        Section::code_block(
80            "mermaid",
81            &[
82                "C4Context",
83                "    title System Context Diagram",
84                "",
85                "    Person(user, \"TODO: User\", \"TODO: Primary user/actor\")",
86                "    System(system, \"TODO: System Name\", \"TODO: System purpose\")",
87                "    System_Ext(ext1, \"TODO: External System\", \"TODO: External dependency\")",
88                "",
89                "    Rel(user, system, \"Uses\")",
90                "    Rel(system, ext1, \"TODO: relationship\", \"TODO: protocol\")",
91                "",
92                "    UpdateLayoutConfig($c4ShapeInRow=\"3\", $c4BoundaryInRow=\"1\")",
93            ],
94        ),
95        Section::blank(),
96        Section::heading("Data Flow").h2(),
97        Section::blank(),
98        Section::line("1. TODO: Primary command/request flow (e.g., Frontend -> API -> Service -> DB)"),
99        Section::line("2. TODO: Primary data/response flow (e.g., DB -> Service -> Frontend)"),
100        Section::line("3. TODO: Secondary flows (settings, config, async jobs, etc.)"),
101        Section::blank(),
102        Section::heading("Concurrency & Data Patterns").h2(),
103        Section::blank(),
104        Section::line("- TODO: Key concurrency primitives (locks, channels, atomics, async, etc.)"),
105        Section::line("- TODO: Data access patterns (caching, buffering, connection pooling, etc.)"),
106        Section::blank(),
107        Section::heading("Deployment").h2(),
108        Section::blank(),
109        Section::line("- TODO: Where does this run? (local, cloud, hybrid, embedded)"),
110        Section::line("- TODO: Key infrastructure (Docker, K8s, serverless, etc.)"),
111        Section::blank(),
112        Section::heading("External Dependencies").h2(),
113        Section::blank(),
114        Section::line("- TODO: Third-party APIs and services"),
115        Section::line("- TODO: Databases and storage systems"),
116    ];
117
118    render(style, &sections)
119}
120
121// -- Internal rendering helpers --
122
123enum Section {
124    Line(String),
125    Blank,
126    Heading { text: String, level: u8 },
127    CodeBlock { lang: String, lines: Vec<String> },
128}
129
130impl Section {
131    fn heading(text: &str) -> Self {
132        Self::Heading {
133            text: text.to_string(),
134            level: 0,
135        }
136    }
137
138    fn h1(self) -> Self {
139        match self {
140            Self::Heading { text, .. } => Self::Heading { text, level: 1 },
141            other => other,
142        }
143    }
144
145    fn h2(self) -> Self {
146        match self {
147            Self::Heading { text, .. } => Self::Heading { text, level: 2 },
148            other => other,
149        }
150    }
151
152    fn line(text: &str) -> Self {
153        Self::Line(text.to_string())
154    }
155
156    fn blank() -> Self {
157        Self::Blank
158    }
159
160    fn code_block(lang: &str, lines: &[&str]) -> Self {
161        Self::CodeBlock {
162            lang: lang.to_string(),
163            lines: lines.iter().map(|s| s.to_string()).collect(),
164        }
165    }
166}
167
168fn render(style: CommentStyle, sections: &[Section]) -> String {
169    let mut out = String::new();
170
171    for section in sections {
172        match section {
173            Section::Blank => {
174                out.push_str(&comment_line(style, ""));
175                out.push('\n');
176            }
177            Section::Line(text) => {
178                out.push_str(&comment_line(style, text));
179                out.push('\n');
180            }
181            Section::Heading { text, level } => {
182                let prefix = match level {
183                    1 => "# ",
184                    2 => "## ",
185                    3 => "### ",
186                    _ => "",
187                };
188                out.push_str(&comment_line(style, &format!("{}{}", prefix, text)));
189                out.push('\n');
190            }
191            Section::CodeBlock { lang, lines } => {
192                out.push_str(&comment_line(style, &format!("```{}", lang)));
193                out.push('\n');
194                for line in lines {
195                    out.push_str(&comment_line(style, line));
196                    out.push('\n');
197                }
198                out.push_str(&comment_line(style, "```"));
199                out.push('\n');
200            }
201        }
202    }
203
204    out
205}
206
207fn comment_line(style: CommentStyle, text: &str) -> String {
208    match style {
209        CommentStyle::Rust => {
210            if text.is_empty() {
211                "//!".to_string()
212            } else {
213                format!("//! {}", text)
214            }
215        }
216        CommentStyle::TypeScript => {
217            if text.is_empty() {
218                " *".to_string()
219            } else {
220                format!(" * {}", text)
221            }
222        }
223    }
224}
225
226/// Wrap TypeScript output in JSDoc delimiters.
227pub fn wrap_jsdoc(content: &str) -> String {
228    format!("/**\n{} */\n", content)
229}
230
231#[cfg(test)]
232mod tests {
233    use super::*;
234
235    #[test]
236    fn rust_template_has_c4_marker() {
237        let out = generate_template(CommentStyle::Rust);
238        assert!(out.contains("//! @c4 container"));
239    }
240
241    #[test]
242    fn rust_template_has_all_sections() {
243        let out = generate_template(CommentStyle::Rust);
244        assert!(out.contains("# [Project Name]"));
245        // Yellow tier — intent
246        assert!(out.contains("## Business Context"));
247        assert!(out.contains("## Domain Model"));
248        assert!(out.contains("## Users & Stakeholders"));
249        assert!(out.contains("## Success Criteria"));
250        assert!(out.contains("## Constraints & Trade-offs"));
251        // Blue tier — structure
252        assert!(out.contains("## C4 Context"));
253        assert!(out.contains("## Data Flow"));
254        assert!(out.contains("## Concurrency & Data Patterns"));
255        assert!(out.contains("## Deployment"));
256        assert!(out.contains("## External Dependencies"));
257    }
258
259    #[test]
260    fn rust_template_has_mermaid_block() {
261        let out = generate_template(CommentStyle::Rust);
262        assert!(out.contains("```mermaid"));
263        assert!(out.contains("C4Context"));
264        assert!(out.contains("```"));
265    }
266
267    #[test]
268    fn rust_template_has_todo_placeholders() {
269        let out = generate_template(CommentStyle::Rust);
270        assert!(out.contains("TODO:"));
271        assert!(out.contains("[TODO: One-line description"));
272    }
273
274    #[test]
275    fn typescript_template_uses_jsdoc_style() {
276        let out = generate_template(CommentStyle::TypeScript);
277        assert!(out.contains(" * @c4 container"));
278        assert!(out.contains(" * ## Data Flow"));
279    }
280
281    #[test]
282    fn detect_rust_from_cargo_toml() {
283        let tmp = tempfile::TempDir::new().unwrap();
284        std::fs::write(tmp.path().join("Cargo.toml"), "").unwrap();
285        assert_eq!(CommentStyle::detect(tmp.path()), Some(CommentStyle::Rust));
286    }
287
288    #[test]
289    fn detect_ts_from_package_json() {
290        let tmp = tempfile::TempDir::new().unwrap();
291        std::fs::write(tmp.path().join("package.json"), "").unwrap();
292        assert_eq!(
293            CommentStyle::detect(tmp.path()),
294            Some(CommentStyle::TypeScript)
295        );
296    }
297
298    #[test]
299    fn from_lang_parsing() {
300        assert_eq!(CommentStyle::from_lang("rust"), Some(CommentStyle::Rust));
301        assert_eq!(CommentStyle::from_lang("rs"), Some(CommentStyle::Rust));
302        assert_eq!(CommentStyle::from_lang("ts"), Some(CommentStyle::TypeScript));
303        assert_eq!(CommentStyle::from_lang("unknown"), None);
304    }
305
306    #[test]
307    fn blank_lines_are_comment_only() {
308        let out = generate_template(CommentStyle::Rust);
309        // Blank lines should be "//!" with no trailing space
310        assert!(out.contains("\n//!\n"));
311        assert!(!out.contains("//! \n"));
312    }
313}