From df2f20a5e5ee820a35585782eb9750ec3fb582c2 Mon Sep 17 00:00:00 2001 From: dave Date: Thu, 16 Apr 2026 00:18:42 +0000 Subject: [PATCH] huskies: merge 589_story_wizard_auto_detects_project_components_and_configures_scripts_accordingly --- server/src/http/mcp/wizard_tools.rs | 153 ++++++++- server/src/http/wizard.rs | 6 +- server/src/io/fs/scaffold.rs | 478 ++++++++++++++++++++++++++++ server/src/io/wizard.rs | 14 +- 4 files changed, 643 insertions(+), 8 deletions(-) diff --git a/server/src/http/mcp/wizard_tools.rs b/server/src/http/mcp/wizard_tools.rs index 779704a9..d11015e2 100644 --- a/server/src/http/mcp/wizard_tools.rs +++ b/server/src/http/mcp/wizard_tools.rs @@ -43,6 +43,8 @@ pub(crate) fn step_output_path( .join("STACK.md"), ), WizardStep::TestScript => Some(project_root.join("script").join("test")), + WizardStep::BuildScript => Some(project_root.join("script").join("build")), + WizardStep::LintScript => Some(project_root.join("script").join("lint")), WizardStep::ReleaseScript => Some(project_root.join("script").join("release")), WizardStep::TestCoverage => Some(project_root.join("script").join("test_coverage")), WizardStep::Scaffold => None, @@ -52,7 +54,11 @@ pub(crate) fn step_output_path( pub(crate) fn is_script_step(step: WizardStep) -> bool { matches!( step, - WizardStep::TestScript | WizardStep::ReleaseScript | WizardStep::TestCoverage + WizardStep::TestScript + | WizardStep::BuildScript + | WizardStep::LintScript + | WizardStep::ReleaseScript + | WizardStep::TestCoverage ) } @@ -256,6 +262,90 @@ pub(crate) fn generation_hint(step: WizardStep, project_root: &Path) -> String { } } } + WizardStep::BuildScript => { + if bare { + "This is a bare project with no existing code. Read the STACK.md generated \ + in the previous step (or ask the user about their stack if it was skipped) \ + and generate a `script/build` shell script (#!/usr/bin/env bash, set -euo pipefail) \ + with appropriate build commands for their chosen language and framework." + .to_string() + } else { + let has_cargo = project_root.join("Cargo.toml").exists(); + let has_pkg = project_root.join("package.json").exists(); + let has_pnpm = project_root.join("pnpm-lock.yaml").exists(); + let has_frontend_subdir = + project_root.join("frontend").join("package.json").exists() + || project_root.join("client").join("package.json").exists(); + let has_go = project_root.join("go.mod").exists(); + let mut cmds = Vec::new(); + if has_cargo { + cmds.push("cargo build --release"); + } + if has_pkg { + cmds.push(if has_pnpm { + "pnpm run build" + } else { + "npm run build" + }); + } + if has_frontend_subdir { + cmds.push("(cd frontend && npm run build)"); + } + if has_go { + cmds.push("go build ./..."); + } + if cmds.is_empty() { + "Generate a `script/build` shell script (#!/usr/bin/env bash, set -euo pipefail) that builds the project.".to_string() + } else { + format!( + "Generate a `script/build` shell script (#!/usr/bin/env bash, set -euo pipefail) that runs: {}", + cmds.join(", ") + ) + } + } + } + WizardStep::LintScript => { + if bare { + "This is a bare project with no existing code. Read the STACK.md generated \ + in the previous step (or ask the user about their stack if it was skipped) \ + and generate a `script/lint` shell script (#!/usr/bin/env bash, set -euo pipefail) \ + with appropriate lint commands for their chosen language and framework." + .to_string() + } else { + let has_cargo = project_root.join("Cargo.toml").exists(); + let has_pkg = project_root.join("package.json").exists(); + let has_pnpm = project_root.join("pnpm-lock.yaml").exists(); + let has_python = project_root.join("pyproject.toml").exists() + || project_root.join("requirements.txt").exists(); + let has_go = project_root.join("go.mod").exists(); + let mut cmds = Vec::new(); + if has_cargo { + cmds.push("cargo fmt --all --check"); + cmds.push("cargo clippy -- -D warnings"); + } + if has_pkg { + cmds.push(if has_pnpm { + "pnpm run lint" + } else { + "npm run lint" + }); + } + if has_python { + cmds.push("flake8 . (or ruff check . if ruff is configured)"); + } + if has_go { + cmds.push("go vet ./..."); + } + if cmds.is_empty() { + "Generate a `script/lint` shell script (#!/usr/bin/env bash, set -euo pipefail) that runs the project's linters.".to_string() + } else { + format!( + "Generate a `script/lint` shell script (#!/usr/bin/env bash, set -euo pipefail) that runs: {}", + cmds.join(", ") + ) + } + } + } WizardStep::ReleaseScript => { if bare { "This is a bare project with no existing code. Read the STACK.md generated \ @@ -554,8 +644,8 @@ mod tests { fn wizard_complete_returns_done_message() { let dir = TempDir::new().unwrap(); let ctx = setup(&dir); - // Skip all remaining steps. - for _ in 0..5 { + // Skip all remaining steps (scaffold is pre-confirmed, so 7 remaining). + for _ in 0..7 { tool_wizard_skip(&ctx).unwrap(); } let result = tool_wizard_status(&ctx).unwrap(); @@ -666,4 +756,61 @@ mod tests { assert!(hint.contains("cargo nextest")); assert!(!hint.contains("bare project")); } + + #[test] + fn generation_hint_bare_build_script_references_stack() { + let dir = TempDir::new().unwrap(); + std::fs::create_dir_all(dir.path().join(".huskies")).unwrap(); + let hint = generation_hint(WizardStep::BuildScript, dir.path()); + assert!(hint.contains("bare project")); + assert!(hint.contains("STACK.md")); + } + + #[test] + fn generation_hint_bare_lint_script_references_stack() { + let dir = TempDir::new().unwrap(); + std::fs::create_dir_all(dir.path().join(".huskies")).unwrap(); + let hint = generation_hint(WizardStep::LintScript, dir.path()); + assert!(hint.contains("bare project")); + assert!(hint.contains("STACK.md")); + } + + #[test] + fn generation_hint_existing_project_build_script_detects_cargo() { + let dir = TempDir::new().unwrap(); + std::fs::write(dir.path().join("Cargo.toml"), "[package]").unwrap(); + let hint = generation_hint(WizardStep::BuildScript, dir.path()); + assert!(hint.contains("cargo build --release")); + assert!(!hint.contains("bare project")); + } + + #[test] + fn generation_hint_existing_project_lint_script_detects_cargo() { + let dir = TempDir::new().unwrap(); + std::fs::write(dir.path().join("Cargo.toml"), "[package]").unwrap(); + let hint = generation_hint(WizardStep::LintScript, dir.path()); + assert!(hint.contains("cargo fmt --all --check")); + assert!(hint.contains("cargo clippy -- -D warnings")); + assert!(!hint.contains("bare project")); + } + + #[test] + fn step_output_path_build_script_returns_script_build() { + let dir = TempDir::new().unwrap(); + let path = step_output_path(dir.path(), WizardStep::BuildScript).unwrap(); + assert!(path.ends_with("script/build")); + } + + #[test] + fn step_output_path_lint_script_returns_script_lint() { + let dir = TempDir::new().unwrap(); + let path = step_output_path(dir.path(), WizardStep::LintScript).unwrap(); + assert!(path.ends_with("script/lint")); + } + + #[test] + fn is_script_step_includes_build_and_lint() { + assert!(is_script_step(WizardStep::BuildScript)); + assert!(is_script_step(WizardStep::LintScript)); + } } diff --git a/server/src/http/wizard.rs b/server/src/http/wizard.rs index 88b23947..405e6cd5 100644 --- a/server/src/http/wizard.rs +++ b/server/src/http/wizard.rs @@ -195,7 +195,7 @@ mod tests { let body: serde_json::Value = resp.0.into_body().into_json().await.unwrap(); assert_eq!(body["current_step_index"], 1); assert!(!body["completed"].as_bool().unwrap()); - assert_eq!(body["steps"].as_array().unwrap().len(), 6); + assert_eq!(body["steps"].as_array().unwrap().len(), 8); assert_eq!(body["steps"][0]["status"], "confirmed"); } @@ -279,11 +279,13 @@ mod tests { let (dir, client) = setup(); WizardState::init_if_missing(dir.path()); - // Steps 2-6 (scaffold is already confirmed) + // Steps 2-8 (scaffold is already confirmed) let steps = [ "context", "stack", "test_script", + "build_script", + "lint_script", "release_script", "test_coverage", ]; diff --git a/server/src/io/fs/scaffold.rs b/server/src/io/fs/scaffold.rs index 6f70b79f..e8063367 100644 --- a/server/src/io/fs/scaffold.rs +++ b/server/src/io/fs/scaffold.rs @@ -223,6 +223,139 @@ fn detect_node_test_cmd(pkg_dir: &Path) -> String { } } +/// Detect the appropriate Node.js build command for a directory containing `package.json`. +fn detect_node_build_cmd(pkg_dir: &Path) -> String { + if pkg_dir.join("pnpm-lock.yaml").exists() { + "pnpm run build".to_string() + } else { + "npm run build".to_string() + } +} + +/// Detect the appropriate Node.js lint command for a directory containing `package.json`. +/// +/// Reads the `package.json` content to identify eslint. Falls back to +/// `npm run lint` or `pnpm run lint` based on which lock file is present. +fn detect_node_lint_cmd(pkg_dir: &Path) -> String { + let has_pnpm = pkg_dir.join("pnpm-lock.yaml").exists(); + let content = std::fs::read_to_string(pkg_dir.join("package.json")).unwrap_or_default(); + if content.contains("\"eslint\"") { + let pm = if has_pnpm { "pnpm" } else { "npx" }; + return format!("{pm} eslint ."); + } + if has_pnpm { + "pnpm run lint".to_string() + } else { + "npm run lint".to_string() + } +} + +/// Generate `script/build` content for a new project at `root`. +/// +/// Inspects well-known marker files to identify which tech stacks are present +/// and emits the appropriate build commands. Multi-stack projects get combined +/// commands run sequentially. Falls back to a generic stub when no markers +/// are found so the scaffold is always valid. +/// +/// For projects with a frontend in a known subdirectory (`frontend/`, `client/`), +/// the build command is detected from the presence of `pnpm-lock.yaml`. +pub fn detect_script_build(root: &Path) -> String { + let mut commands: Vec = Vec::new(); + + if root.join("Cargo.toml").exists() { + commands.push("cargo build --release".to_string()); + } + + if root.join("package.json").exists() { + commands.push(detect_node_build_cmd(root)); + } + + // Detect frontend in known subdirectories (e.g. frontend/, client/) + for subdir in &["frontend", "client"] { + let sub_path = root.join(subdir); + if sub_path.join("package.json").exists() { + let cmd = detect_node_build_cmd(&sub_path); + commands.push(format!("(cd {} && {})", subdir, cmd)); + } + } + + if root.join("pyproject.toml").exists() { + commands.push("python -m build".to_string()); + } + + if root.join("go.mod").exists() { + commands.push("go build ./...".to_string()); + } + + if commands.is_empty() { + return "#!/usr/bin/env bash\nset -euo pipefail\n\n# Add your project's build commands here.\necho \"No build configured\"\n".to_string(); + } + + let mut script = "#!/usr/bin/env bash\nset -euo pipefail\n\n".to_string(); + for cmd in commands { + script.push_str(&cmd); + script.push('\n'); + } + script +} + +/// Generate `script/lint` content for a new project at `root`. +/// +/// Inspects well-known marker files to identify which linters are present +/// and emits the appropriate lint commands. Multi-stack projects get combined +/// commands run sequentially. Falls back to a generic stub when no markers +/// are found so the scaffold is always valid. +/// +/// For projects with a frontend in a known subdirectory (`frontend/`, `client/`), +/// the lint command is detected from the `package.json` (eslint, npm, pnpm). +pub fn detect_script_lint(root: &Path) -> String { + let mut commands: Vec = Vec::new(); + + if root.join("Cargo.toml").exists() { + commands.push("cargo fmt --all --check".to_string()); + commands.push("cargo clippy -- -D warnings".to_string()); + } + + if root.join("package.json").exists() { + commands.push(detect_node_lint_cmd(root)); + } + + // Detect frontend in known subdirectories (e.g. frontend/, client/) + for subdir in &["frontend", "client"] { + let sub_path = root.join(subdir); + if sub_path.join("package.json").exists() { + let cmd = detect_node_lint_cmd(&sub_path); + commands.push(format!("(cd {} && {})", subdir, cmd)); + } + } + + if root.join("pyproject.toml").exists() || root.join("requirements.txt").exists() { + let mut content = std::fs::read_to_string(root.join("pyproject.toml")).unwrap_or_default(); + content + .push_str(&std::fs::read_to_string(root.join("requirements.txt")).unwrap_or_default()); + if content.contains("ruff") { + commands.push("ruff check .".to_string()); + } else { + commands.push("flake8 .".to_string()); + } + } + + if root.join("go.mod").exists() { + commands.push("go vet ./...".to_string()); + } + + if commands.is_empty() { + return "#!/usr/bin/env bash\nset -euo pipefail\n\n# Add your project's lint commands here.\necho \"No linters configured\"\n".to_string(); + } + + let mut script = "#!/usr/bin/env bash\nset -euo pipefail\n\n".to_string(); + for cmd in commands { + script.push_str(&cmd); + script.push('\n'); + } + script +} + /// Generate `script/test` content for a new project at `root`. /// /// Inspects well-known marker files to identify which tech stacks are present @@ -449,6 +582,10 @@ pub(crate) fn scaffold_story_kit(root: &Path, port: u16) -> Result<(), String> { write_file_if_missing(&tech_root.join("STACK.md"), STORY_KIT_STACK)?; let script_test_content = detect_script_test(root); write_script_if_missing(&script_root.join("test"), &script_test_content)?; + let script_build_content = detect_script_build(root); + write_script_if_missing(&script_root.join("build"), &script_build_content)?; + let script_lint_content = detect_script_lint(root); + write_script_if_missing(&script_root.join("lint"), &script_lint_content)?; write_file_if_missing(&root.join("CLAUDE.md"), STORY_KIT_CLAUDE_MD)?; // Write per-transport bot.toml example files so users can see all options. @@ -1387,6 +1524,347 @@ mod tests { ); } + // --- detect_script_build --- + + #[test] + fn detect_script_build_no_markers_returns_stub() { + let dir = tempdir().unwrap(); + let script = detect_script_build(dir.path()); + assert!( + script.contains("No build configured"), + "fallback should contain the generic stub message" + ); + assert!(script.starts_with("#!/usr/bin/env bash")); + } + + #[test] + fn detect_script_build_cargo_toml_adds_cargo_build_release() { + let dir = tempdir().unwrap(); + fs::write(dir.path().join("Cargo.toml"), "[package]\nname = \"x\"\n").unwrap(); + + let script = detect_script_build(dir.path()); + assert!( + script.contains("cargo build --release"), + "Rust project should run cargo build --release" + ); + assert!(!script.contains("No build configured")); + } + + #[test] + fn detect_script_build_package_json_npm_adds_npm_run_build() { + let dir = tempdir().unwrap(); + fs::write(dir.path().join("package.json"), "{}").unwrap(); + + let script = detect_script_build(dir.path()); + assert!( + script.contains("npm run build"), + "Node project without pnpm-lock should run npm run build" + ); + } + + #[test] + fn detect_script_build_package_json_pnpm_adds_pnpm_run_build() { + let dir = tempdir().unwrap(); + fs::write(dir.path().join("package.json"), "{}").unwrap(); + fs::write(dir.path().join("pnpm-lock.yaml"), "").unwrap(); + + let script = detect_script_build(dir.path()); + assert!( + script.contains("pnpm run build"), + "Node project with pnpm-lock should run pnpm run build" + ); + assert!( + !script.lines().any(|l| l.trim() == "npm run build"), + "should not use npm when pnpm-lock.yaml is present" + ); + } + + #[test] + fn detect_script_build_go_mod_adds_go_build() { + let dir = tempdir().unwrap(); + fs::write(dir.path().join("go.mod"), "module example.com/app\n").unwrap(); + + let script = detect_script_build(dir.path()); + assert!( + script.contains("go build ./..."), + "Go project should run go build ./..." + ); + } + + #[test] + fn detect_script_build_pyproject_toml_adds_python_build() { + let dir = tempdir().unwrap(); + fs::write( + dir.path().join("pyproject.toml"), + "[project]\nname = \"x\"\n", + ) + .unwrap(); + + let script = detect_script_build(dir.path()); + assert!( + script.contains("python -m build"), + "Python project should run python -m build" + ); + } + + #[test] + fn detect_script_build_frontend_subdir_detected() { + let dir = tempdir().unwrap(); + let frontend = dir.path().join("frontend"); + fs::create_dir_all(&frontend).unwrap(); + fs::write(frontend.join("package.json"), "{}").unwrap(); + + let script = detect_script_build(dir.path()); + assert!( + script.contains("cd frontend"), + "frontend subdir should be detected for build" + ); + assert!(script.contains("npm run build")); + } + + #[test] + fn detect_script_build_rust_plus_frontend_subdir_both_included() { + let dir = tempdir().unwrap(); + fs::write( + dir.path().join("Cargo.toml"), + "[package]\nname = \"server\"\n", + ) + .unwrap(); + let frontend = dir.path().join("frontend"); + fs::create_dir_all(&frontend).unwrap(); + fs::write(frontend.join("package.json"), "{}").unwrap(); + + let script = detect_script_build(dir.path()); + assert!(script.contains("cargo build --release")); + assert!(script.contains("cd frontend")); + assert!(script.contains("npm run build")); + } + + // --- detect_script_lint --- + + #[test] + fn detect_script_lint_no_markers_returns_stub() { + let dir = tempdir().unwrap(); + let script = detect_script_lint(dir.path()); + assert!( + script.contains("No linters configured"), + "fallback should contain the generic stub message" + ); + assert!(script.starts_with("#!/usr/bin/env bash")); + } + + #[test] + fn detect_script_lint_cargo_toml_adds_fmt_and_clippy() { + let dir = tempdir().unwrap(); + fs::write(dir.path().join("Cargo.toml"), "[package]\nname = \"x\"\n").unwrap(); + + let script = detect_script_lint(dir.path()); + assert!( + script.contains("cargo fmt --all --check"), + "Rust project should check formatting" + ); + assert!( + script.contains("cargo clippy -- -D warnings"), + "Rust project should run clippy" + ); + assert!(!script.contains("No linters configured")); + } + + #[test] + fn detect_script_lint_package_json_without_eslint_uses_npm_run_lint() { + let dir = tempdir().unwrap(); + fs::write(dir.path().join("package.json"), "{}").unwrap(); + + let script = detect_script_lint(dir.path()); + assert!( + script.contains("npm run lint"), + "Node project without eslint dep should fall back to npm run lint" + ); + } + + #[test] + fn detect_script_lint_package_json_with_eslint_uses_npx_eslint() { + let dir = tempdir().unwrap(); + fs::write( + dir.path().join("package.json"), + r#"{"devDependencies":{"eslint":"^8.0.0"}}"#, + ) + .unwrap(); + + let script = detect_script_lint(dir.path()); + assert!( + script.contains("npx eslint ."), + "Node project with eslint should use npx eslint ." + ); + } + + #[test] + fn detect_script_lint_pnpm_with_eslint_uses_pnpm_eslint() { + let dir = tempdir().unwrap(); + fs::write( + dir.path().join("package.json"), + r#"{"devDependencies":{"eslint":"^8.0.0"}}"#, + ) + .unwrap(); + fs::write(dir.path().join("pnpm-lock.yaml"), "").unwrap(); + + let script = detect_script_lint(dir.path()); + assert!( + script.contains("pnpm eslint ."), + "pnpm project with eslint should use pnpm eslint ." + ); + } + + #[test] + fn detect_script_lint_python_requirements_uses_flake8() { + let dir = tempdir().unwrap(); + fs::write(dir.path().join("requirements.txt"), "flask\n").unwrap(); + + let script = detect_script_lint(dir.path()); + assert!( + script.contains("flake8 ."), + "Python project without ruff should use flake8" + ); + } + + #[test] + fn detect_script_lint_python_with_ruff_uses_ruff() { + let dir = tempdir().unwrap(); + fs::write( + dir.path().join("pyproject.toml"), + "[project]\nname = \"x\"\n\n[tool.ruff]\n", + ) + .unwrap(); + + let script = detect_script_lint(dir.path()); + assert!( + script.contains("ruff check ."), + "Python project with ruff configured should use ruff" + ); + assert!( + !script.contains("flake8"), + "should not use flake8 when ruff is configured" + ); + } + + #[test] + fn detect_script_lint_go_mod_adds_go_vet() { + let dir = tempdir().unwrap(); + fs::write(dir.path().join("go.mod"), "module example.com/app\n").unwrap(); + + let script = detect_script_lint(dir.path()); + assert!( + script.contains("go vet ./..."), + "Go project should run go vet ./..." + ); + } + + #[test] + fn detect_script_lint_frontend_subdir_detected() { + let dir = tempdir().unwrap(); + let frontend = dir.path().join("frontend"); + fs::create_dir_all(&frontend).unwrap(); + fs::write(frontend.join("package.json"), "{}").unwrap(); + + let script = detect_script_lint(dir.path()); + assert!( + script.contains("cd frontend"), + "frontend subdir should be detected for lint" + ); + } + + #[test] + fn detect_script_lint_rust_plus_frontend_subdir_both_included() { + let dir = tempdir().unwrap(); + fs::write( + dir.path().join("Cargo.toml"), + "[package]\nname = \"server\"\n", + ) + .unwrap(); + let frontend = dir.path().join("frontend"); + fs::create_dir_all(&frontend).unwrap(); + fs::write(frontend.join("package.json"), "{}").unwrap(); + + let script = detect_script_lint(dir.path()); + assert!(script.contains("cargo fmt --all --check")); + assert!(script.contains("cargo clippy -- -D warnings")); + assert!(script.contains("cd frontend")); + } + + #[test] + fn scaffold_story_kit_creates_script_build_and_lint() { + let dir = tempdir().unwrap(); + scaffold_story_kit(dir.path(), 3001).unwrap(); + + assert!( + dir.path().join("script/build").exists(), + "script/build should be created by scaffold" + ); + assert!( + dir.path().join("script/lint").exists(), + "script/lint should be created by scaffold" + ); + } + + #[cfg(unix)] + #[test] + fn scaffold_story_kit_creates_executable_script_build_and_lint() { + use std::os::unix::fs::PermissionsExt; + + let dir = tempdir().unwrap(); + scaffold_story_kit(dir.path(), 3001).unwrap(); + + for name in &["build", "lint"] { + let path = dir.path().join("script").join(name); + assert!(path.exists(), "script/{name} should be created"); + let perms = fs::metadata(&path).unwrap().permissions(); + assert!( + perms.mode() & 0o111 != 0, + "script/{name} should be executable" + ); + } + } + + #[test] + fn scaffold_script_build_contains_detected_commands_for_rust() { + let dir = tempdir().unwrap(); + fs::write( + dir.path().join("Cargo.toml"), + "[package]\nname = \"myapp\"\n", + ) + .unwrap(); + + scaffold_story_kit(dir.path(), 3001).unwrap(); + + let content = fs::read_to_string(dir.path().join("script/build")).unwrap(); + assert!( + content.contains("cargo build --release"), + "Rust project scaffold should set cargo build --release in script/build" + ); + } + + #[test] + fn scaffold_script_lint_contains_detected_commands_for_rust() { + let dir = tempdir().unwrap(); + fs::write( + dir.path().join("Cargo.toml"), + "[package]\nname = \"myapp\"\n", + ) + .unwrap(); + + scaffold_story_kit(dir.path(), 3001).unwrap(); + + let content = fs::read_to_string(dir.path().join("script/lint")).unwrap(); + assert!( + content.contains("cargo fmt --all --check"), + "Rust project scaffold should include fmt check in script/lint" + ); + assert!( + content.contains("cargo clippy -- -D warnings"), + "Rust project scaffold should include clippy in script/lint" + ); + } + // --- generate_project_toml --- #[test] diff --git a/server/src/io/wizard.rs b/server/src/io/wizard.rs index 32b02804..dad94968 100644 --- a/server/src/io/wizard.rs +++ b/server/src/io/wizard.rs @@ -16,9 +16,13 @@ pub enum WizardStep { Stack, /// Step 4: create script/test TestScript, - /// Step 5: create script/release + /// Step 5: create script/build + BuildScript, + /// Step 6: create script/lint + LintScript, + /// Step 7: create script/release ReleaseScript, - /// Step 6: create script/test_coverage + /// Step 8: create script/test_coverage TestCoverage, } @@ -29,6 +33,8 @@ impl WizardStep { WizardStep::Context, WizardStep::Stack, WizardStep::TestScript, + WizardStep::BuildScript, + WizardStep::LintScript, WizardStep::ReleaseScript, WizardStep::TestCoverage, ]; @@ -40,6 +46,8 @@ impl WizardStep { WizardStep::Context => "Generate project context (00_CONTEXT.md)", WizardStep::Stack => "Generate tech stack spec (STACK.md)", WizardStep::TestScript => "Create test script (script/test)", + WizardStep::BuildScript => "Create build script (script/build)", + WizardStep::LintScript => "Create lint script (script/lint)", WizardStep::ReleaseScript => "Create release script (script/release)", WizardStep::TestCoverage => "Create test coverage script (script/test_coverage)", } @@ -262,7 +270,7 @@ mod tests { #[test] fn default_state_has_all_steps_pending() { let state = WizardState::default(); - assert_eq!(state.steps.len(), 6); + assert_eq!(state.steps.len(), 8); for step in &state.steps { assert_eq!(step.status, StepStatus::Pending); }