1use anyhow::{Context, Result};
13use camino::Utf8Path;
14use xchecker_utils::atomic_write;
15use xchecker_utils::paths;
16
17pub const TEMPLATE_FULLSTACK_NEXTJS: &str = "fullstack-nextjs";
19pub const TEMPLATE_RUST_MICROSERVICE: &str = "rust-microservice";
20pub const TEMPLATE_PYTHON_FASTAPI: &str = "python-fastapi";
21pub const TEMPLATE_DOCS_REFACTOR: &str = "docs-refactor";
22
23pub const BUILT_IN_TEMPLATES: &[&str] = &[
25 TEMPLATE_FULLSTACK_NEXTJS,
26 TEMPLATE_RUST_MICROSERVICE,
27 TEMPLATE_PYTHON_FASTAPI,
28 TEMPLATE_DOCS_REFACTOR,
29];
30
31#[derive(Debug, Clone)]
33pub struct TemplateInfo {
34 pub id: &'static str,
36 pub name: &'static str,
38 pub description: &'static str,
40 pub use_case: &'static str,
42 pub prerequisites: &'static [&'static str],
44}
45
46#[must_use]
48pub fn list_templates() -> Vec<TemplateInfo> {
49 vec![
50 TemplateInfo {
51 id: TEMPLATE_FULLSTACK_NEXTJS,
52 name: "Full-Stack Next.js",
53 description: "Template for full-stack web applications using Next.js",
54 use_case: "Building modern web applications with React, Next.js, and a backend API",
55 prerequisites: &["Node.js 18+", "npm or yarn", "Next.js knowledge"],
56 },
57 TemplateInfo {
58 id: TEMPLATE_RUST_MICROSERVICE,
59 name: "Rust Microservice",
60 description: "Template for Rust-based microservices and CLI tools",
61 use_case: "Building performant backend services, CLI tools, or system utilities in Rust",
62 prerequisites: &["Rust 1.70+", "Cargo", "Basic Rust knowledge"],
63 },
64 TemplateInfo {
65 id: TEMPLATE_PYTHON_FASTAPI,
66 name: "Python FastAPI",
67 description: "Template for Python REST APIs using FastAPI",
68 use_case: "Building REST APIs, data processing services, or ML model endpoints",
69 prerequisites: &["Python 3.10+", "pip or poetry", "FastAPI knowledge"],
70 },
71 TemplateInfo {
72 id: TEMPLATE_DOCS_REFACTOR,
73 name: "Documentation Refactor",
74 description: "Template for documentation improvements and refactoring",
75 use_case: "Restructuring, improving, or migrating documentation",
76 prerequisites: &["Markdown knowledge", "Understanding of target docs"],
77 },
78 ]
79}
80
81#[must_use]
83pub fn get_template(id: &str) -> Option<TemplateInfo> {
84 list_templates().into_iter().find(|t| t.id == id)
85}
86
87#[must_use]
89pub fn is_valid_template(id: &str) -> bool {
90 BUILT_IN_TEMPLATES.contains(&id)
91}
92
93pub fn init_from_template(template_id: &str, spec_id: &str) -> Result<()> {
109 let template = get_template(template_id).ok_or_else(|| {
111 anyhow::anyhow!(
112 "Unknown template '{}'. Run 'xchecker template list' to see available templates.",
113 template_id
114 )
115 })?;
116
117 let spec_dir = paths::spec_root(spec_id);
119 let artifacts_dir = spec_dir.join("artifacts");
120 let context_dir = spec_dir.join("context");
121 let receipts_dir = spec_dir.join("receipts");
122
123 if spec_dir.exists() {
125 anyhow::bail!("Spec '{}' already exists at: {}", spec_id, spec_dir);
126 }
127
128 paths::ensure_dir_all(&artifacts_dir)
130 .with_context(|| format!("Failed to create artifacts directory: {}", artifacts_dir))?;
131 paths::ensure_dir_all(&context_dir)
132 .with_context(|| format!("Failed to create context directory: {}", context_dir))?;
133 paths::ensure_dir_all(&receipts_dir)
134 .with_context(|| format!("Failed to create receipts directory: {}", receipts_dir))?;
135
136 let problem_statement = generate_problem_statement(template_id, spec_id);
138 let readme_content = generate_readme(&template);
139
140 let problem_path = context_dir.join("problem-statement.md");
142 atomic_write::write_file_atomic(&problem_path, &problem_statement)
143 .with_context(|| format!("Failed to write problem statement: {}", problem_path))?;
144
145 let readme_path = spec_dir.join("README.md");
147 atomic_write::write_file_atomic(&readme_path, &readme_content)
148 .with_context(|| format!("Failed to write README: {}", readme_path))?;
149
150 ensure_minimal_config()?;
152
153 Ok(())
154}
155
156fn generate_problem_statement(template_id: &str, spec_id: &str) -> String {
158 match template_id {
159 TEMPLATE_FULLSTACK_NEXTJS => format!(
160 r#"# Problem Statement: {spec_id}
161
162## Overview
163
164Build a full-stack web application using Next.js with the following capabilities:
165
166- Modern React-based frontend with server-side rendering
167- API routes for backend functionality
168- Database integration (PostgreSQL/Prisma recommended)
169- Authentication and authorization
170- Responsive design with Tailwind CSS
171
172## Goals
173
1741. Create a production-ready Next.js application
1752. Implement core features with proper error handling
1763. Set up testing infrastructure (Jest, React Testing Library)
1774. Configure CI/CD pipeline
1785. Document API endpoints and usage
179
180## Constraints
181
182- Use TypeScript for type safety
183- Follow Next.js App Router conventions
184- Implement proper security practices
185- Ensure accessibility compliance (WCAG 2.1 AA)
186
187## Success Criteria
188
189- All core features implemented and tested
190- Performance targets met (Core Web Vitals)
191- Documentation complete
192- CI/CD pipeline operational
193"#,
194 spec_id = spec_id
195 ),
196 TEMPLATE_RUST_MICROSERVICE => format!(
197 r#"# Problem Statement: {spec_id}
198
199## Overview
200
201Build a Rust microservice/CLI tool with the following capabilities:
202
203- High-performance request handling
204- Structured logging and observability
205- Configuration management
206- Graceful shutdown handling
207- Comprehensive error handling
208
209## Goals
210
2111. Create a production-ready Rust service
2122. Implement core business logic
2133. Set up testing infrastructure (unit, integration, property-based)
2144. Configure CI/CD pipeline
2155. Document API/CLI usage
216
217## Constraints
218
219- Use stable Rust (1.70+)
220- Follow Rust idioms and best practices
221- Minimize dependencies where practical
222- Ensure cross-platform compatibility (Linux, macOS, Windows)
223
224## Success Criteria
225
226- All core features implemented and tested
227- Performance targets met
228- Documentation complete
229- CI/CD pipeline operational
230"#,
231 spec_id = spec_id
232 ),
233 TEMPLATE_PYTHON_FASTAPI => format!(
234 r#"# Problem Statement: {spec_id}
235
236## Overview
237
238Build a Python REST API using FastAPI with the following capabilities:
239
240- RESTful API endpoints with automatic OpenAPI documentation
241- Database integration (SQLAlchemy/PostgreSQL)
242- Authentication (JWT/OAuth2)
243- Input validation with Pydantic
244- Async request handling
245
246## Goals
247
2481. Create a production-ready FastAPI application
2492. Implement core API endpoints
2503. Set up testing infrastructure (pytest, httpx)
2514. Configure CI/CD pipeline
2525. Document API endpoints
253
254## Constraints
255
256- Use Python 3.10+
257- Follow PEP 8 style guidelines
258- Use type hints throughout
259- Implement proper error handling
260
261## Success Criteria
262
263- All API endpoints implemented and tested
264- OpenAPI documentation complete
265- Performance targets met
266- CI/CD pipeline operational
267"#,
268 spec_id = spec_id
269 ),
270 TEMPLATE_DOCS_REFACTOR => format!(
271 r#"# Problem Statement: {spec_id}
272
273## Overview
274
275Refactor and improve documentation with the following goals:
276
277- Restructure documentation for better navigation
278- Improve clarity and consistency
279- Add missing documentation
280- Update outdated content
281- Improve code examples
282
283## Goals
284
2851. Audit existing documentation
2862. Create documentation structure plan
2873. Rewrite/improve key sections
2884. Add missing content
2895. Validate all code examples
290
291## Constraints
292
293- Maintain backward compatibility with existing links where possible
294- Follow documentation style guide
295- Ensure all code examples are tested
296- Keep documentation in sync with code
297
298## Success Criteria
299
300- Documentation structure improved
301- All sections reviewed and updated
302- Code examples validated
303- Navigation improved
304- Search functionality working
305"#,
306 spec_id = spec_id
307 ),
308 _ => format!(
309 r#"# Problem Statement: {spec_id}
310
311## Overview
312
313[Describe the problem you're trying to solve]
314
315## Goals
316
3171. [Goal 1]
3182. [Goal 2]
3193. [Goal 3]
320
321## Constraints
322
323- [Constraint 1]
324- [Constraint 2]
325
326## Success Criteria
327
328- [Criterion 1]
329- [Criterion 2]
330"#,
331 spec_id = spec_id
332 ),
333 }
334}
335
336fn generate_readme(template: &TemplateInfo) -> String {
338 let prerequisites = template
339 .prerequisites
340 .iter()
341 .map(|p| format!("- {}", p))
342 .collect::<Vec<_>>()
343 .join("\n");
344
345 format!(
346 r#"# {name}
347
348{description}
349
350## Intended Use
351
352{use_case}
353
354## Prerequisites
355
356{prerequisites}
357
358## Getting Started
359
3601. Review the problem statement in `context/problem-statement.md`
3612. Run the requirements phase:
362 ```bash
363 xchecker resume <spec-id> --phase requirements
364 ```
3653. Review generated requirements in `artifacts/`
3664. Continue with design phase:
367 ```bash
368 xchecker resume <spec-id> --phase design
369 ```
3705. Continue through remaining phases as needed
371
372## Basic Flow
373
374```
375Requirements → Design → Tasks → Review → Fixup → Final
376```
377
378Each phase builds on the previous one:
379- **Requirements**: Generate detailed requirements from the problem statement
380- **Design**: Create architecture and design documents
381- **Tasks**: Break down into implementation tasks
382- **Review**: Review and validate the spec
383- **Fixup**: Apply any suggested changes
384- **Final**: Finalize the spec
385
386## Commands
387
388```bash
389# Check spec status
390xchecker status <spec-id>
391
392# Resume from a specific phase
393xchecker resume <spec-id> --phase <phase>
394
395# Run in dry-run mode (no LLM calls)
396xchecker resume <spec-id> --phase <phase> --dry-run
397```
398
399## More Information
400
401- [xchecker Documentation](https://github.com/your-org/xchecker)
402- [Configuration Guide](../../docs/CONFIGURATION.md)
403"#,
404 name = template.name,
405 description = template.description,
406 use_case = template.use_case,
407 prerequisites = prerequisites,
408 )
409}
410
411fn ensure_minimal_config() -> Result<()> {
413 let config_dir = Utf8Path::new(".xchecker");
414 let config_path = config_dir.join("config.toml");
415
416 if config_path.exists() {
418 return Ok(());
419 }
420
421 if !config_dir.exists() {
423 paths::ensure_dir_all(config_dir)
424 .with_context(|| format!("Failed to create config directory: {}", config_dir))?;
425 }
426
427 let config_content = r#"# xchecker configuration
428# See docs/CONFIGURATION.md for all options
429
430[defaults]
431# model = "haiku"
432# max_turns = 5
433
434[packet]
435# packet_max_bytes = 65536
436# packet_max_lines = 1200
437
438[runner]
439# runner_mode = "auto"
440
441[llm]
442# provider = "claude-cli"
443# execution_strategy = "controlled"
444"#;
445
446 atomic_write::write_file_atomic(&config_path, config_content)
447 .with_context(|| format!("Failed to write config file: {}", config_path))?;
448
449 Ok(())
450}
451
452#[cfg(test)]
453mod tests {
454 use super::*;
455
456 #[test]
457 fn test_list_templates() {
458 let templates = list_templates();
459 assert_eq!(templates.len(), 4);
460
461 let ids: Vec<&str> = templates.iter().map(|t| t.id).collect();
462 assert!(ids.contains(&TEMPLATE_FULLSTACK_NEXTJS));
463 assert!(ids.contains(&TEMPLATE_RUST_MICROSERVICE));
464 assert!(ids.contains(&TEMPLATE_PYTHON_FASTAPI));
465 assert!(ids.contains(&TEMPLATE_DOCS_REFACTOR));
466 }
467
468 #[test]
469 fn test_get_template_valid() {
470 let template = get_template(TEMPLATE_FULLSTACK_NEXTJS);
471 assert!(template.is_some());
472 let t = template.unwrap();
473 assert_eq!(t.id, TEMPLATE_FULLSTACK_NEXTJS);
474 assert!(!t.name.is_empty());
475 assert!(!t.description.is_empty());
476 }
477
478 #[test]
479 fn test_get_template_invalid() {
480 let template = get_template("nonexistent-template");
481 assert!(template.is_none());
482 }
483
484 #[test]
485 fn test_is_valid_template() {
486 assert!(is_valid_template(TEMPLATE_FULLSTACK_NEXTJS));
487 assert!(is_valid_template(TEMPLATE_RUST_MICROSERVICE));
488 assert!(is_valid_template(TEMPLATE_PYTHON_FASTAPI));
489 assert!(is_valid_template(TEMPLATE_DOCS_REFACTOR));
490 assert!(!is_valid_template("invalid-template"));
491 }
492
493 #[test]
494 fn test_generate_problem_statement() {
495 let content = generate_problem_statement(TEMPLATE_FULLSTACK_NEXTJS, "my-app");
496 assert!(content.contains("my-app"));
497 assert!(content.contains("Next.js"));
498 assert!(content.contains("Problem Statement"));
499 }
500
501 #[test]
502 fn test_generate_readme() {
503 let template = get_template(TEMPLATE_RUST_MICROSERVICE).unwrap();
504 let readme = generate_readme(&template);
505 assert!(readme.contains("Rust Microservice"));
506 assert!(readme.contains("Prerequisites"));
507 assert!(readme.contains("Getting Started"));
508 }
509
510 #[test]
511 fn test_init_from_template() {
512 let _temp_dir = paths::with_isolated_home();
514
515 let result = init_from_template(TEMPLATE_RUST_MICROSERVICE, "test-rust-service");
516 assert!(result.is_ok());
517
518 let spec_dir = paths::spec_root("test-rust-service");
520 assert!(spec_dir.exists());
521 assert!(spec_dir.join("artifacts").exists());
522 assert!(spec_dir.join("context").exists());
523 assert!(spec_dir.join("receipts").exists());
524
525 assert!(spec_dir.join("context/problem-statement.md").exists());
527 assert!(spec_dir.join("README.md").exists());
528 }
529
530 #[test]
531 fn test_init_from_template_invalid() {
532 let _temp_dir = paths::with_isolated_home();
533
534 let result = init_from_template("invalid-template", "test-spec");
535 assert!(result.is_err());
536 assert!(result.unwrap_err().to_string().contains("Unknown template"));
537 }
538
539 #[test]
540 fn test_init_from_template_already_exists() {
541 let _temp_dir = paths::with_isolated_home();
542
543 init_from_template(TEMPLATE_PYTHON_FASTAPI, "existing-spec").unwrap();
545
546 let result = init_from_template(TEMPLATE_PYTHON_FASTAPI, "existing-spec");
548 assert!(result.is_err());
549 assert!(result.unwrap_err().to_string().contains("already exists"));
550 }
551}