//! Project planning templates and helpers.
//!
//! Ito's planning area lives under `{ito_path}/planning`.
//! This module provides **pure** path helpers, template content, and parsing
//! utilities. Filesystem I/O (init) lives in `ito-core`.
use regex::Regex;
use std::path::{Path, PathBuf};
// Filesystem I/O (init_planning_structure) lives in `ito-core`.
/// Path to the planning directory (`{ito_path}/planning`).
pub fn planning_dir(ito_path: &Path) -> PathBuf {
ito_path.join("planning")
}
/// Path to the milestones directory (`{ito_path}/planning/milestones`).
pub fn milestones_dir(ito_path: &Path) -> PathBuf {
planning_dir(ito_path).join("milestones")
}
/// Default contents for `planning/PROJECT.md`.
pub fn project_md_template(project_name: Option<&str>, description: Option<&str>) -> String {
let project_name = project_name.unwrap_or("[Project Name]");
let description =
description.unwrap_or("[1-2 sentence description of what we're building and why]");
format!(
"# Project: {project_name}\n\n## Vision\n{description}\n\n## Core Value Proposition\n[What makes this valuable to users]\n\n## Constraints\n- Technical: [stack, compatibility requirements]\n- Resources: [team size, expertise gaps]\n\n## Stakeholders\n- [Role]: [Concerns and success criteria]\n\n## Out of Scope\n- [Explicitly excluded features/concerns]\n\n## AI Assistant Notes\n[Special instructions for AI tools working on this project]\n- Preferred patterns: [...]\n- Avoid: [...]\n- Always check: [...]\n"
)
}
/// Default contents for `planning/ROADMAP.md`.
pub fn roadmap_md_template() -> String {
"# Roadmap\n\n## Current Milestone: v1-core\n- Status: Not Started\n- Phase: 0 of 0\n\n## Milestones\n\n### v1-core\nTarget: [Define the goal for this milestone]\n\n| Phase | Name | Status | Changes |\n|-------|------|--------|---------|\n| 1 | [Phase Name] | Pending | - |\n\n## Completed Milestones\n[None yet]\n"
.to_string()
}
/// Default contents for `planning/STATE.md`.
pub fn state_md_template(current_date: &str, ito_dir: &str) -> String {
format!(
"# Project State\n\nLast Updated: {current_date}\n\n## Current Focus\n[What we're working on right now]\n\n## Recent Decisions\n- {current_date}: Project initialized\n\n## Open Questions\n- [ ] [Question needing resolution]\n\n## Blockers\n[None currently]\n\n## Session Notes\n### {current_date} - Initial Setup\n- Completed: Project planning structure initialized\n- Next: Define project vision and first milestone\n\n---\n## For AI Assistants\nWhen resuming work on this project:\n1. Read this STATE.md first\n2. Check ROADMAP.md for current phase\n3. Review any in-progress changes in `{ito_dir}/changes/`\n4. Continue from \"Current Focus\" above\n"
)
}
// NOTE: `init_planning_structure` was moved to `ito-core` to keep
// the domain layer free of filesystem I/O.
/// Parse the "Current Milestone" header block from roadmap markdown.
pub fn read_current_progress(roadmap_contents: &str) -> Option<(String, String, String)> {
let re = Regex::new(r"## Current Milestone: (.+)\n- Status: (.+)\n- Phase: (.+)").unwrap();
let caps = re.captures(roadmap_contents)?;
Some((
caps[1].to_string(),
caps[2].to_string(),
caps[3].to_string(),
))
}
/// Read phase rows from the roadmap milestone table.
///
/// Returns `(phase, name, status, changes)`.
pub fn read_phase_rows(roadmap_contents: &str) -> Vec<(String, String, String, String)> {
let mut rows: Vec<(String, String, String, String)> = Vec::new();
let mut in_table = false;
let mut saw_sep = false;
for line in roadmap_contents.lines() {
let t = line.trim();
if t == "| Phase | Name | Status | Changes |" {
in_table = true;
saw_sep = false;
continue;
}
if !in_table {
continue;
}
if !saw_sep {
if t.starts_with("|-------") {
saw_sep = true;
}
continue;
}
if t.is_empty() || !t.starts_with('|') {
break;
}
let cols: Vec<String> = t
.split('|')
.map(|c| c.trim().to_string())
.filter(|c| !c.is_empty())
.collect();
if cols.len() >= 4 {
rows.push((
cols[0].clone(),
cols[1].clone(),
cols[2].clone(),
cols[3].clone(),
));
}
}
rows
}