use crate::knowledge::{lang_by_id, libs_for, libraries, project_type_aliases, suggest_language}; use serde::Deserialize; use crate::model::*; use chrono::Utc; #[derive(Clone)] pub struct PlanInput { pub name: String, pub description: String, pub project_type_raw: String, pub audience: String, pub scale: String, pub language_pref: Option, pub database: String, pub auth: String, pub deployment: String, pub features: Vec, pub constraints: Vec, pub template_slug: Option, } pub fn detect_domain(idea: &str, ptype: &str) -> &'static str { let low = idea.to_lowercase(); if low.contains("cube") || low.contains("puzzle") || low.contains("solver") || low.contains("algorithm") || low.contains("search") || low.contains("pathfinding") || low.contains("sort") { return "algorithmic"; } if low.contains("web") || low.contains("http") || low.contains("api") || low.contains("server") || low.contains("site") || ptype == "web-app" || ptype == "rest-api" { return "web"; } if low.contains("data") || low.contains("pipeline") || low.contains("etl") || low.contains("csv") || low.contains("parquet") || ptype == "data-pipeline" { return "data"; } if low.contains("ml") || low.contains("model") || low.contains("train") || low.contains("predict") || ptype == "ml-project" { return "ml"; } if low.contains("game") || low.contains("player") || low.contains("sprite") || ptype == "game" { return "game"; } if ptype == "cli" { return "cli"; } "default" } pub fn domain_guidance(domain: &str) -> Option { match domain { "algorithmic" => Some(include_str!("../data/domains/algorithmic.md").to_string()), "web" => Some(include_str!("../data/domains/web.md").to_string()), "data" => Some(include_str!("../data/domains/data.md").to_string()), "ml" => Some(include_str!("../data/domains/ml.md").to_string()), "game" => Some(include_str!("../data/domains/game.md").to_string()), _ => None, } } #[derive(Deserialize, Clone, Debug)] pub struct CorpusEntry { pub name: String, pub keywords: Vec, pub problem_type: String, pub file_tree: Vec, pub libraries: Vec, pub milestones: Vec, pub risks: Vec, pub pitfalls: Vec, } #[derive(Deserialize, Clone, Debug)] pub struct CorpusMilestone { pub name: String, pub goal: String, pub tasks: Vec, } #[derive(Deserialize, Clone, Debug)] pub struct CorpusTask { pub id: String, pub title: String, pub description: String, pub depends_on: Vec, } static CORPUS: std::sync::OnceLock> = std::sync::OnceLock::new(); fn corpus() -> &'static [CorpusEntry] { CORPUS.get_or_init(|| { serde_json::from_str(include_str!("../data/corpus.json")).unwrap_or_default() }) } pub fn find_corpus(idea: &str) -> Option { let low = idea.to_lowercase(); let mut best: Option<(usize, CorpusEntry)> = None; for c in corpus() { let score: usize = c.keywords.iter().filter(|k| low.contains(&k.to_lowercase())).count(); if score >= 2 { if best.as_ref().map(|(s, _)| score > *s).unwrap_or(true) { best = Some((score, c.clone())); } } } best.map(|(_, c)| c) } pub fn template_slug_for(ptype: &str) -> Option<&'static str> { match ptype { "cli" => Some("cli-tool"), "rest-api" => Some("rest-api"), "web-app" => Some("web-app"), "library" => Some("library"), "desktop" => Some("desktop-app"), "data-pipeline" => Some("data-pipeline"), "ml-project" => Some("ml-project"), _ => None, } } pub fn infer_requirements(idea: &str) -> (String, String, String, String, Vec) { let low = idea.to_lowercase(); // database let db = if low.contains("sqlite") { "sqlite" } else if low.contains("postgres") || low.contains("postgresql") { "postgres" } else if low.contains("mongo") { "mongo" } else if low.contains("database") || low.contains("db") || low.contains("storage") || low.contains("persist") { "sqlite" } else { "none" }; // auth let auth = if low.contains("oauth") { "oauth" } else if low.contains("jwt") || low.contains("token") { "api-key" } else if low.contains("login") || low.contains("auth") || low.contains("admin") || low.contains("tripcode") { "basic" } else { "none" }; // deployment let deploy = if low.contains("docker") || low.contains("container") || low.contains("kubernetes") || low.contains("k8s") { "container" } else if low.contains("cloud") || low.contains("serverless") || low.contains("lambda") || low.contains("aws") || low.contains("gcp") || low.contains("azure") { "cloud" } else if low.contains("desktop") { "desktop" } else { "local" }; // scale let scale = if low.contains("large") || low.contains("scale") || low.contains("high traffic") || low.contains("millions") || low.contains("concurrent") || low.contains("production") { "large" } else if low.contains("medium") { "medium" } else { "small" }; // strip parentheticals from whole idea first let mut cleaned = idea.to_string(); while let Some(s) = cleaned.find('(') { if let Some(e) = cleaned[s..].find(')') { cleaned = format!("{}{}", &cleaned[..s], &cleaned[s+e+1..]); } else { break; } } // features: split on commas/semicolons/newlines; normalize and dedupe let mut features: Vec = Vec::new(); for part in cleaned.split(|c: char| c == ',' || c == ';' || c == '\n') { let p = part.trim().trim_matches(|c: char| c == '.' || c == ' ' || c == '-').to_string(); if p.len() < 6 || p.len() > 110 { continue; } let low = p.to_lowercase(); if low.contains("commands:") || low.contains("supports") || low.contains("designed") || low.contains("http://") || low.contains("--json") { continue; } if p.split_whitespace().count() < 3 { continue; } features.push(p); } // keyword-based feature extraction (supplement if few features) if features.len() < 4 { let low = idea.to_lowercase(); let cmd_keywords = ["init", "validate", "upload", "download", "search", "info", "review", "reviews", "update", "llmhelp", "run", "pack", "diff", "pull", "fork", "delete", "status", "doubles", "merge", "transfer", "bump"]; for kw in &cmd_keywords { if low.contains(kw) { let f = format!("{} command", kw); if !features.iter().any(|x| x.to_lowercase() == f.to_lowercase()) { features.push(f); } } } } if features.is_empty() { features.push(idea.trim().to_string()); } // dedupe case-insensitively let mut seen = std::collections::HashSet::new(); features.retain(|f| seen.insert(f.to_lowercase())); features.truncate(10); (db.to_string(), auth.to_string(), deploy.to_string(), scale.to_string(), features) } pub fn build_plan(input: PlanInput) -> ProjectPlan { let ptype = project_type_aliases(&input.project_type_raw).to_string(); let lang_id = suggest_language(&ptype, input.language_pref.as_deref()); let lang = lang_by_id(&lang_id); let domain = detect_domain(&input.description, &ptype); let corpus_entry = find_corpus(&input.description); let lang_name = lang.map(|l| l.name.clone()).unwrap_or_else(|| lang_id.clone()); let language_guidance = if lang_id == "rust" { Some(include_str!("../data/rust_guidance.md").to_string()) } else { None }; let pitfalls = if let Some(c) = &corpus_entry { if !c.pitfalls.is_empty() { c.pitfalls.clone() } else { build_pitfalls(&ptype, &lang_id, &input) } } else { build_pitfalls(&ptype, &lang_id, &input) }; let mut libs: Vec = Vec::new(); let domains: Vec<&str> = match ptype.as_str() { "cli" => vec!["cli", "serialization", "observability"], "rest-api" => vec!["web-backend", "database", "serialization", "validation", "observability"], "web-app" => vec!["web-frontend", "validation", "build"], "library" => vec!["serialization"], "desktop" => vec!["desktop", "ui"], "mobile" => vec!["mobile", "ui"], "data-pipeline" => vec!["data", "validation", "cli"], "ml-project" => vec!["ml", "data", "web-backend"], _ => vec!["cli"], }; for d in &domains { for l in libs_for(&lang_id, Some(d)) { // avoid competing frameworks: keep first web-backend lib only if d == &"web-backend" && libs.iter().any(|x| x.domain == "web-backend") { continue; } libs.push((*l).clone()); } } if ptype == "rest-api" { for l in libs_for(&lang_id, Some("async")) { libs.push((*l).clone()); } for l in libs_for(&lang_id, Some("http-client")) { libs.push((*l).clone()); } } let mut seen = std::collections::HashSet::new(); libs.retain(|l| seen.insert(l.name.clone())); // keyword-driven additions let text = format!("{} {}", input.description, input.features.join(" ")).to_lowercase(); let keyword_map: Vec<(&str, &str)> = vec![ ("tar", "archive"), ("tar.gz", "archive"), ("pack", "archive"), ("gzip", "compression"), ("compress", "compression"), ("walk", "filesystem"), ("directory", "filesystem"), ("file", "filesystem"), ("http", "http-client"), ("hub", "http-client"), ("api", "http-client"), ("request", "http-client"), ("sha", "crypto"), ("checksum", "crypto"), ("hash", "crypto"), ("temp", "filesystem"), ("tempfile", "filesystem"), ("self-update", "update"), ("self update", "update"), ("update", "update"), ("semver", "versioning"), ("version", "versioning"), ("json", "serialization"), ("toml", "serialization"), ("manifest", "serialization"), ("search", "serialization"), ]; for (kw, domain) in &keyword_map { if text.contains(kw) { if let Some(l) = libs_for(&lang_id, Some(domain)).first() { if !libs.iter().any(|x| x.name == l.name) { libs.push((**l).clone()); } } } } // remove irrelevant crates for cli type (image/chrono not needed unless keywords) if ptype == "cli" { if !text.contains("image") && !text.contains("thumbnail") && !text.contains("upload") { libs.retain(|l| l.domain != "image"); } if !text.contains("time") && !text.contains("timestamp") && !text.contains("date") { libs.retain(|l| l.domain != "time"); } } // dedupe again after keyword additions let mut seen2 = std::collections::HashSet::new(); libs.retain(|l| seen2.insert(l.name.clone())); // corpus override for libraries if let Some(c) = &corpus_entry { if !c.libraries.is_empty() { let mut new_libs = Vec::new(); for name in &c.libraries { if let Some(l) = libraries().iter().find(|l| l.name == *name && l.lang == lang_id) { new_libs.push((*l).clone()); } else { new_libs.push(Library { name: name.clone(), lang: lang_id.clone(), domain: "general".to_string(), purpose: "Recommended by corpus".to_string(), url: None, note: None, }); } } if !new_libs.is_empty() { libs = new_libs; } } } if input.database == "none" { libs.retain(|l| l.domain != "database"); } // constraints influence on libraries let constraints_text = input.constraints.join(" ").to_lowercase(); if constraints_text.contains("offline") { libs.retain(|l| l.domain != "http-client" && l.domain != "update"); } if constraints_text.contains("small") || constraints_text.contains("minimal") { libs.retain(|l| l.name != "tracing"); } let framework = libs.iter().find(|l| { matches!(l.domain.as_str(), "web-backend" | "web-frontend" | "desktop" | "mobile") }).map(|l| l.name.clone()); let stack = Stack { language: lang_name.clone(), framework: framework.clone(), libraries: libs.iter().map(|l| l.name.clone()).collect(), rationale: format!( "{} chosen for {} project (scale: {}).", lang_name, ptype, input.scale ), }; let architecture = match ptype.as_str() { "cli" => "Single binary CLI. Flow: main -> cli (clap) -> command dispatch -> domain logic -> io. Modules: main, cli, config, manifest, pack, hub, update, error, io. Use anyhow at boundaries, thiserror in library code. Keep main thin; all logic in lib/testable modules.".to_string(), "rest-api" => "Layered API: router -> handlers -> service -> repository -> DB. Middleware: tracing, CORS, auth, rate-limit. Health + readiness endpoints. Migrations via sqlx. Config via env + dotenvy.".to_string(), "web-app" => "SPA: pages -> components -> hooks -> API client -> backend. Route-based code splitting. Form validation with zod. State via react-query/swr. Error boundaries.".to_string(), "library" => "Public API in lib.rs; internal modules private; feature flags for optional deps; property tests; docs with examples. Semver discipline.".to_string(), "desktop" => "Tauri: Rust core (commands, state, fs, http) + web frontend. IPC commands typed; packaging via tauri-cli.".to_string(), "mobile" => "Cross-platform: shared core (models, networking) + platform UI (SwiftUI/Compose). Offline-first with sync.".to_string(), "data-pipeline" => "Stages: extract -> validate -> transform -> load. Idempotent, checkpointed, retryable. CLI entrypoint; config-driven.".to_string(), "ml-project" => "Pipeline: data prep -> training -> evaluation -> artifact -> serving (FastAPI/gRPC). Experiment tracking, versioned datasets.".to_string(), _ => "Modular monolith with clear module boundaries and dependency injection.".to_string(), }; let file_tree = if let Some(c) = &corpus_entry { c.file_tree.clone() } else { template_file_tree(&input.template_slug, &ptype) }; let milestones = if let Some(c) = &corpus_entry { if !c.milestones.is_empty() { c.milestones.iter().map(|m| Milestone { name: m.name.clone(), goal: m.goal.clone(), tasks: m.tasks.iter().map(|t| PlanTask { id: t.id.clone(), title: t.title.clone(), description: t.description.clone(), depends_on: t.depends_on.clone(), }).collect(), }).collect() } else { build_milestones(&ptype, &input) } } else { build_milestones(&ptype, &input) }; let mut standards = vec![ "Use strict typing; avoid `any`/`unwrap` in production paths.".to_string(), "Handle errors explicitly; no silent failures.".to_string(), "Add unit tests for core logic; integration tests for boundaries.".to_string(), "Keep functions small; single responsibility.".to_string(), "Document public API.".to_string(), ]; if lang_id == "rust" { standards.push("Run cargo fmt, clippy; fix all warnings.".to_string()); standards.push("Prefer anyhow for app errors, thiserror for library errors.".to_string()); } if lang_id == "typescript" { standards.push("Enable strict TS; use eslint + prettier.".to_string()); } if lang_id == "python" { standards.push("Type hints everywhere; ruff + mypy; pyproject.toml.".to_string()); } let constraints_text = input.constraints.join(" ").to_lowercase(); if constraints_text.contains("performance") || constraints_text.contains("fast") { standards.push("Profile before optimizing; use rayon for parallelism; avoid allocations in hot paths.".to_string()); } if constraints_text.contains("offline") { standards.push("No network at runtime; bundle all assets.".to_string()); } let mut risks = Vec::new(); if input.scale == "large" { risks.push("Scope creep: break into phases; ship MVP first.".to_string()); } if input.database != "none" { risks.push("DB schema migrations: plan versioning early.".to_string()); } if input.auth != "none" { risks.push("Auth security: use established libs; never roll crypto.".to_string()); } // type-specific risks match ptype.as_str() { "rest-api" => { risks.push("Missing input validation at API boundary.".to_string()); risks.push("N+1 queries and missing DB indexes.".to_string()); risks.push("Breaking API changes without versioning.".to_string()); } "web-app" => { risks.push("State desync between client and server.".to_string()); risks.push("Bundle bloat and poor loading states.".to_string()); } "data-pipeline" => { risks.push("Partial failures and retry storms.".to_string()); risks.push("Schema drift breaking downstream consumers.".to_string()); risks.push("Memory blowup on large inputs.".to_string()); } "ml-project" => { risks.push("Data leakage between train/test.".to_string()); risks.push("Non-reproducible training (seeds/deps).".to_string()); risks.push("Serving without monitoring.".to_string()); } "library" => { risks.push("Breaking public API without major bump.".to_string()); risks.push("Leaking internal types.".to_string()); } "desktop" => { risks.push("Packaging and code-signing issues.".to_string()); risks.push("IPC security and large payload handling.".to_string()); } _ => {} } let text = format!("{} {}", input.description, input.features.join(" ")).to_lowercase(); if ptype == "cli" { risks.push("Tar path traversal and accidental inclusion of secrets in bundles.".to_string()); risks.push("Self-update supply chain: verify checksums; support rollback.".to_string()); risks.push("Hub API drift and versioning; handle 401/403/404 gracefully.".to_string()); risks.push("Unstable --json output breaking agent consumers.".to_string()); risks.push("Version conflicts and non-deterministic packaging.".to_string()); } else { if text.contains("upload") || text.contains("image") { risks.push("File upload abuse: enforce size limits, validate types, store outside web root.".to_string()); } if text.contains("auth") || text.contains("login") || text.contains("admin") { risks.push("CSRF/XSS and session fixation on auth flows.".to_string()); } if text.contains("search") || text.contains("fts") { risks.push("Search performance and injection on user queries.".to_string()); } if text.contains("websocket") || text.contains("ws") || text.contains("live") || text.contains("real-time") { risks.push("WebSocket connection limits and backpressure.".to_string()); } if text.contains("public") || text.contains("post") || text.contains("comment") { risks.push("Abuse/spam and rate limiting on public endpoints.".to_string()); } } if libs.is_empty() { risks.push("No libraries matched; verify language/domain.".to_string()); } let reqs = vec![ Requirement { key: "project_type".into(), value: ptype.clone() }, Requirement { key: "database".into(), value: input.database.clone() }, Requirement { key: "auth".into(), value: input.auth.clone() }, Requirement { key: "deployment".into(), value: input.deployment.clone() }, Requirement { key: "scale".into(), value: input.scale.clone() }, ]; let mut requirements = reqs; for f in &input.features { requirements.push(Requirement { key: "feature".into(), value: f.clone() }); } for c in &input.constraints { requirements.push(Requirement { key: "constraint".into(), value: c.clone() }); } // corpus override for risks let risks = if let Some(c) = &corpus_entry { if !c.risks.is_empty() { c.risks.clone() } else { risks } } else { risks }; ProjectPlan { name: input.name, description: input.description, project_type: ptype, audience: input.audience, scale: input.scale, requirements, stack, libraries: libs, architecture, file_tree, milestones, coding_standards: standards, risks, template: input.template_slug, created_at: Utc::now().to_rfc3339(), ai_assistance: None, language_guidance, domain: domain.to_string(), domain_guidance: domain_guidance(domain), pitfalls, } } fn template_file_tree(slug: &Option, ptype: &str) -> Vec { let mut base = if let Some(s) = slug { if let Some(t) = crate::templates::find_template(s) { t.files.iter().map(|(p, _)| p.clone()).collect() } else { Vec::new() } } else { Vec::new() }; if base.is_empty() { base = match ptype { "cli" => vec!["Cargo.toml".into(), "src/main.rs".into(), "README.md".into()], "rest-api" => vec!["Cargo.toml".into(), "src/main.rs".into(), "migrations/".into(), "README.md".into()], "web-app" => vec!["package.json".into(), "index.html".into(), "src/main.tsx".into(), "src/App.tsx".into()], "library" => vec!["Cargo.toml".into(), "src/lib.rs".into(), "README.md".into()], "desktop" => vec!["src-tauri/Cargo.toml".into(), "src-tauri/src/main.rs".into()], "data-pipeline" => vec!["pyproject.toml".into(), "pipeline.py".into()], "ml-project" => vec!["pyproject.toml".into(), "train.py".into()], _ => vec!["README.md".into()], }; } // expand cli file tree with common modules if ptype == "cli" { let extras = ["src/cli.rs", "src/config.rs", "src/error.rs", "tests/cli.rs"]; for e in extras { if !base.iter().any(|b| b == e) { base.push(e.into()); } } } // expand rest-api file tree if ptype == "rest-api" { let extras = ["src/handlers.rs", "src/models.rs", "src/state.rs", "src/error.rs", "tests/api.rs"]; for e in extras { if !base.iter().any(|b| b == e) { base.push(e.into()); } } } base } fn build_milestones(ptype: &str, input: &PlanInput) -> Vec { let mut ms = Vec::new(); ms.push(Milestone { name: "M0: Setup".into(), goal: "Project scaffolding, tooling, CI".into(), tasks: vec![ PlanTask { id: "M0-1".into(), title: "Scaffold project from template".into(), description: "Generate files, install deps.".into(), depends_on: vec![] }, PlanTask { id: "M0-2".into(), title: "Configure lint/format/test".into(), description: "Set up formatters and CI.".into(), depends_on: vec!["M0-1".into()] }, ], }); ms.push(Milestone { name: "M1: Core".into(), goal: "Implement core domain logic".into(), tasks: vec![ PlanTask { id: "M1-1".into(), title: "Define data models".into(), description: "Structs/schemas for domain entities.".into(), depends_on: vec!["M0-1".into()] }, PlanTask { id: "M1-2".into(), title: "Implement core operations".into(), description: "Business logic with tests.".into(), depends_on: vec!["M1-1".into()] }, ], }); if !input.features.is_empty() { let tasks: Vec = input.features.iter().enumerate().map(|(i, f)| { PlanTask { id: format!("F{}-{}", i + 1, i + 1), title: f.clone(), description: format!("Implement and test: {}", f), depends_on: vec!["M1-2".to_string()], } }).collect(); ms.push(Milestone { name: "M2: Features".into(), goal: "Deliver user-facing features".into(), tasks, }); } // domain-specific milestone let domain = detect_domain(&input.description, ptype); match domain { "algorithmic" => { ms.push(Milestone { name: "M-Algorithm".into(), goal: "Model and solve".into(), tasks: vec![ PlanTask { id: "MA-1".into(), title: "State model".into(), description: "Represent problem state; define invariants.".into(), depends_on: vec!["M1-1".into()] }, PlanTask { id: "MA-2".into(), title: "Move/transition generation".into(), description: "Generate and apply transitions; support undo.".into(), depends_on: vec!["MA-1".into()] }, PlanTask { id: "MA-3".into(), title: "Solver algorithm".into(), description: "IDA*/A*/DP with heuristic; validate correctness.".into(), depends_on: vec!["MA-2".into()] }, PlanTask { id: "MA-4".into(), title: "Benchmarks".into(), description: "Criterion benchmarks; profile hot paths.".into(), depends_on: vec!["MA-3".into()] }, ], }); } "web" => { ms.push(Milestone { name: "M-Web".into(), goal: "HTTP layer".into(), tasks: vec![ PlanTask { id: "MW-1".into(), title: "Routes & handlers".into(), description: "CRUD endpoints, validation, errors.".into(), depends_on: vec!["M1-2".into()] }, PlanTask { id: "MW-2".into(), title: "Persistence".into(), description: "Migrations + repository.".into(), depends_on: vec!["MW-1".into()] }, PlanTask { id: "MW-3".into(), title: "Auth middleware".into(), description: format!("Auth: {}", input.auth), depends_on: vec!["MW-1".into()] }, ], }); } "data" => { ms.push(Milestone { name: "M-Pipeline".into(), goal: "ETL stages".into(), tasks: vec![ PlanTask { id: "MP-1".into(), title: "Extract".into(), description: "Ingest sources.".into(), depends_on: vec!["M1-2".into()] }, PlanTask { id: "MP-2".into(), title: "Transform".into(), description: "Clean/transform.".into(), depends_on: vec!["MP-1".into()] }, PlanTask { id: "MP-3".into(), title: "Load".into(), description: "Write outputs.".into(), depends_on: vec!["MP-2".into()] }, ], }); } "ml" => { ms.push(Milestone { name: "M-ML".into(), goal: "Train & serve".into(), tasks: vec![ PlanTask { id: "MM-1".into(), title: "Data prep".into(), description: "Datasets, splits.".into(), depends_on: vec!["M1-2".into()] }, PlanTask { id: "MM-2".into(), title: "Training".into(), description: "Model + optimizer.".into(), depends_on: vec!["MM-1".into()] }, PlanTask { id: "MM-3".into(), title: "Serving".into(), description: "Inference API.".into(), depends_on: vec!["MM-2".into()] }, ], }); } _ => {} } match ptype { "rest-api" => { ms.push(Milestone { name: "M2: API".into(), goal: "HTTP endpoints".into(), tasks: vec![ PlanTask { id: "M2-1".into(), title: "Routes & handlers".into(), description: "CRUD endpoints, validation, errors.".into(), depends_on: vec!["M1-2".into()] }, PlanTask { id: "M2-2".into(), title: "Persistence".into(), description: "DB migrations + repository.".into(), depends_on: vec!["M2-1".into()] }, PlanTask { id: "M2-3".into(), title: "Auth middleware".into(), description: format!("Auth: {}", input.auth), depends_on: vec!["M2-1".into()] }, ], }); } "web-app" => { ms.push(Milestone { name: "M2: UI".into(), goal: "Screens & interactions".into(), tasks: vec![ PlanTask { id: "M2-1".into(), title: "Component library".into(), description: "Shared components.".into(), depends_on: vec!["M1-2".into()] }, PlanTask { id: "M2-2".into(), title: "Pages & routing".into(), description: "Routes, layouts.".into(), depends_on: vec!["M2-1".into()] }, PlanTask { id: "M2-3".into(), title: "API integration".into(), description: "Client + state management.".into(), depends_on: vec!["M2-2".into()] }, ], }); } "data-pipeline" => { ms.push(Milestone { name: "M2: Pipeline".into(), goal: "ETL stages".into(), tasks: vec![ PlanTask { id: "M2-1".into(), title: "Extract".into(), description: "Ingest sources.".into(), depends_on: vec!["M1-2".into()] }, PlanTask { id: "M2-2".into(), title: "Transform".into(), description: "Clean/transform.".into(), depends_on: vec!["M2-1".into()] }, PlanTask { id: "M2-3".into(), title: "Load".into(), description: "Write outputs.".into(), depends_on: vec!["M2-2".into()] }, ], }); } "ml-project" => { ms.push(Milestone { name: "M2: Training".into(), goal: "Model training & eval".into(), tasks: vec![ PlanTask { id: "M2-1".into(), title: "Data prep".into(), description: "Datasets, splits.".into(), depends_on: vec!["M1-2".into()] }, PlanTask { id: "M2-2".into(), title: "Training loop".into(), description: "Model + optimizer.".into(), depends_on: vec!["M2-1".into()] }, PlanTask { id: "M2-3".into(), title: "Evaluation".into(), description: "Metrics, checkpoints.".into(), depends_on: vec!["M2-2".into()] }, ], }); ms.push(Milestone { name: "M3: Serving".into(), goal: "Serve model".into(), tasks: vec![ PlanTask { id: "M3-1".into(), title: "Inference API".into(), description: "FastAPI endpoint.".into(), depends_on: vec!["M2-3".into()] }, ], }); } _ => {} } ms.push(Milestone { name: "M-Final: Polish".into(), goal: "Docs, tests, release".into(), tasks: vec![ PlanTask { id: "MF-1".into(), title: "Documentation".into(), description: "README, usage, examples.".into(), depends_on: vec![] }, PlanTask { id: "MF-2".into(), title: "Release build".into(), description: format!("Deploy target: {}", input.deployment), depends_on: vec![] }, ], }); ms } fn build_pitfalls(ptype: &str, lang_id: &str, input: &PlanInput) -> Vec { let mut p = Vec::new(); // generic (kept minimal) p.push("Skipping tests until the end; regressions accumulate.".to_string()); // type-specific match ptype { "cli" => { p.push("Blocking the terminal with long-running tasks without progress output.".to_string()); p.push("Poor arg validation and unclear error messages.".to_string()); p.push("Not handling SIGINT/Ctrl-C gracefully.".to_string()); } "rest-api" => { p.push("Missing input validation at the boundary.".to_string()); p.push("N+1 queries and missing DB indexes.".to_string()); p.push("No rate limiting / auth on sensitive endpoints.".to_string()); p.push("Breaking API changes without versioning.".to_string()); } "web-app" => { p.push("State desync between client and server.".to_string()); p.push("Ignoring loading/error states in UI.".to_string()); p.push("Bundle bloat from unused dependencies.".to_string()); } "library" => { p.push("Leaking internal types in public API.".to_string()); p.push("Breaking semver without a major bump.".to_string()); p.push("Missing docs/examples.".to_string()); p.push("Overly broad public surface.".to_string()); } "desktop" => { p.push("Packaging and code-signing issues.".to_string()); p.push("Blocking the UI thread with sync work.".to_string()); p.push("Large bundle size.".to_string()); } "mobile" => { p.push("Platform-specific bugs and testing gaps.".to_string()); p.push("Offline sync conflicts.".to_string()); p.push("App store review rejections.".to_string()); } "data-pipeline" => { p.push("Not handling partial failures / retries.".to_string()); p.push("Schema drift breaking downstream consumers.".to_string()); p.push("Loading everything into memory.".to_string()); } "ml-project" => { p.push("Data leakage between train/test splits.".to_string()); p.push("Not pinning dependencies / random seeds.".to_string()); p.push("Serving a model without monitoring.".to_string()); } _ => {} } if lang_id == "rust" { p.push("Overusing clone() instead of references.".to_string()); p.push("unwrap() panics in production paths.".to_string()); p.push("Blocking async runtime with sync I/O.".to_string()); } if input.features.iter().any(|f| f.to_lowercase().contains("auth")) { p.push("Rolling your own crypto/auth; use established libs.".to_string()); } let text = format!("{} {}", input.description, input.features.join(" ")).to_lowercase(); if text.contains("tar") || text.contains("pack") || text.contains("archive") { p.push("Tar path traversal and accidental inclusion of secrets in bundles.".to_string()); p.push("Non-deterministic archive ordering breaking reproducible builds.".to_string()); } if text.contains("self-update") || text.contains("self update") || text.contains("update") { p.push("Self-update supply chain: verify checksums/signatures; support rollback.".to_string()); } if text.contains("hub") || text.contains("api") { p.push("Hub API drift and versioning; handle 401/403/404 gracefully.".to_string()); } if text.contains("json") || text.contains("--json") { p.push("Unstable --json output breaking agent consumers.".to_string()); } p }