Skip to main content

ito_domain/
planning.rs

1//! Project planning templates and helpers.
2//!
3//! Ito's planning area lives under `{ito_path}/planning`.
4//! This module provides **pure** path helpers, template content, and parsing
5//! utilities.  Filesystem I/O (init) lives in `ito-core`.
6
7use regex::Regex;
8use std::path::{Path, PathBuf};
9
10// Filesystem I/O (init_planning_structure) lives in `ito-core`.
11
12/// Path to the planning directory (`{ito_path}/planning`).
13pub fn planning_dir(ito_path: &Path) -> PathBuf {
14    ito_path.join("planning")
15}
16
17/// Path to the milestones directory (`{ito_path}/planning/milestones`).
18pub fn milestones_dir(ito_path: &Path) -> PathBuf {
19    planning_dir(ito_path).join("milestones")
20}
21
22/// Default contents for `planning/PROJECT.md`.
23pub fn project_md_template(project_name: Option<&str>, description: Option<&str>) -> String {
24    let project_name = project_name.unwrap_or("[Project Name]");
25    let description =
26        description.unwrap_or("[1-2 sentence description of what we're building and why]");
27    format!(
28        "# 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"
29    )
30}
31
32/// Default contents for `planning/ROADMAP.md`.
33pub fn roadmap_md_template() -> String {
34    "# 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"
35        .to_string()
36}
37
38/// Default contents for `planning/STATE.md`.
39pub fn state_md_template(current_date: &str, ito_dir: &str) -> String {
40    format!(
41        "# 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"
42    )
43}
44
45// NOTE: `init_planning_structure` was moved to `ito-core` to keep
46// the domain layer free of filesystem I/O.
47
48/// Parse the "Current Milestone" header block from roadmap markdown.
49pub fn read_current_progress(roadmap_contents: &str) -> Option<(String, String, String)> {
50    let re = Regex::new(r"## Current Milestone: (.+)\n- Status: (.+)\n- Phase: (.+)").unwrap();
51    let caps = re.captures(roadmap_contents)?;
52    Some((
53        caps[1].to_string(),
54        caps[2].to_string(),
55        caps[3].to_string(),
56    ))
57}
58
59/// Read phase rows from the roadmap milestone table.
60///
61/// Returns `(phase, name, status, changes)`.
62pub fn read_phase_rows(roadmap_contents: &str) -> Vec<(String, String, String, String)> {
63    let mut rows: Vec<(String, String, String, String)> = Vec::new();
64
65    let mut in_table = false;
66    let mut saw_sep = false;
67    for line in roadmap_contents.lines() {
68        let t = line.trim();
69        if t == "| Phase | Name | Status | Changes |" {
70            in_table = true;
71            saw_sep = false;
72            continue;
73        }
74        if !in_table {
75            continue;
76        }
77        if !saw_sep {
78            if t.starts_with("|-------") {
79                saw_sep = true;
80            }
81            continue;
82        }
83        if t.is_empty() || !t.starts_with('|') {
84            break;
85        }
86
87        let cols: Vec<String> = t
88            .split('|')
89            .map(|c| c.trim().to_string())
90            .filter(|c| !c.is_empty())
91            .collect();
92        if cols.len() >= 4 {
93            rows.push((
94                cols[0].clone(),
95                cols[1].clone(),
96                cols[2].clone(),
97                cols[3].clone(),
98            ));
99        }
100    }
101    rows
102}