storkit: merge 372_story_scaffold_auto_detects_tech_stack_and_configures_script_test
This commit is contained in:
@@ -110,7 +110,7 @@ role = "Full-stack engineer. Implements features across all components."
|
||||
model = "sonnet"
|
||||
max_turns = 50
|
||||
max_budget_usd = 5.00
|
||||
prompt = "You are working in a git worktree on story {{story_id}}. Read CLAUDE.md first, then .storkit/README.md to understand the dev process. Follow the workflow through implementation and verification. The worktree and feature branch already exist - do not create them. Check .mcp.json for MCP tools. Do NOT accept the story or merge - commit your work and stop.\n\nIMPORTANT: Commit all your work before your process exits. The server will automatically run acceptance gates when your process exits."
|
||||
prompt = "You are working in a git worktree on story {{story_id}}. Read CLAUDE.md first, then .storkit/README.md to understand the dev process. Follow the workflow through implementation and verification. The worktree and feature branch already exist - do not create them. Check .mcp.json for MCP tools. Do NOT accept the story or merge - commit your work and stop.\n\nIMPORTANT: Commit all your work before your process exits. The server will automatically run acceptance gates when your process exits.\n\nIf `script/test` still contains the generic 'No tests configured' stub, update it to run the project's actual test suite before starting implementation."
|
||||
system_prompt = "You are a full-stack engineer working autonomously in a git worktree. Follow the Story-Driven Test Workflow strictly. Commit all your work before finishing. Do not accept stories, move them to archived, or merge to master."
|
||||
|
||||
[[agent]]
|
||||
@@ -215,6 +215,47 @@ pub fn detect_components_toml(root: &Path) -> String {
|
||||
sections.join("\n")
|
||||
}
|
||||
|
||||
/// Generate `script/test` content for a new project at `root`.
|
||||
///
|
||||
/// Inspects well-known marker files to identify which tech stacks are present
|
||||
/// and emits the appropriate test commands. Multi-stack projects get combined
|
||||
/// commands run sequentially. Falls back to the generic stub when no markers
|
||||
/// are found so the scaffold is always valid.
|
||||
pub fn detect_script_test(root: &Path) -> String {
|
||||
let mut commands: Vec<&str> = Vec::new();
|
||||
|
||||
if root.join("Cargo.toml").exists() {
|
||||
commands.push("cargo test");
|
||||
}
|
||||
|
||||
if root.join("package.json").exists() {
|
||||
if root.join("pnpm-lock.yaml").exists() {
|
||||
commands.push("pnpm test");
|
||||
} else {
|
||||
commands.push("npm test");
|
||||
}
|
||||
}
|
||||
|
||||
if root.join("pyproject.toml").exists() || root.join("requirements.txt").exists() {
|
||||
commands.push("pytest");
|
||||
}
|
||||
|
||||
if root.join("go.mod").exists() {
|
||||
commands.push("go test ./...");
|
||||
}
|
||||
|
||||
if commands.is_empty() {
|
||||
return STORY_KIT_SCRIPT_TEST.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 a complete `project.toml` for a new project at `root`.
|
||||
///
|
||||
/// Detects the tech stack via [`detect_components_toml`] and prepends the
|
||||
@@ -442,7 +483,8 @@ fn scaffold_story_kit(root: &Path, port: u16) -> Result<(), String> {
|
||||
write_file_if_missing(&story_kit_root.join("project.toml"), &project_toml_content)?;
|
||||
write_file_if_missing(&specs_root.join("00_CONTEXT.md"), STORY_KIT_CONTEXT)?;
|
||||
write_file_if_missing(&tech_root.join("STACK.md"), STORY_KIT_STACK)?;
|
||||
write_script_if_missing(&script_root.join("test"), STORY_KIT_SCRIPT_TEST)?;
|
||||
let script_test_content = detect_script_test(root);
|
||||
write_script_if_missing(&script_root.join("test"), &script_test_content)?;
|
||||
write_file_if_missing(&root.join("CLAUDE.md"), STORY_KIT_CLAUDE_MD)?;
|
||||
|
||||
// Write .mcp.json at the project root so agents can find the MCP server.
|
||||
@@ -1652,6 +1694,124 @@ mod tests {
|
||||
assert!(!toml.contains("name = \"app\""));
|
||||
}
|
||||
|
||||
// --- detect_script_test ---
|
||||
|
||||
#[test]
|
||||
fn detect_script_test_no_markers_returns_stub() {
|
||||
let dir = tempdir().unwrap();
|
||||
let script = detect_script_test(dir.path());
|
||||
assert!(
|
||||
script.contains("No tests configured"),
|
||||
"fallback should contain the generic stub message"
|
||||
);
|
||||
assert!(script.starts_with("#!/usr/bin/env bash"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detect_script_test_cargo_toml_adds_cargo_test() {
|
||||
let dir = tempdir().unwrap();
|
||||
fs::write(dir.path().join("Cargo.toml"), "[package]\nname = \"x\"\n").unwrap();
|
||||
|
||||
let script = detect_script_test(dir.path());
|
||||
assert!(script.contains("cargo test"), "Rust project should run cargo test");
|
||||
assert!(!script.contains("No tests configured"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detect_script_test_package_json_npm_adds_npm_test() {
|
||||
let dir = tempdir().unwrap();
|
||||
fs::write(dir.path().join("package.json"), "{}").unwrap();
|
||||
|
||||
let script = detect_script_test(dir.path());
|
||||
assert!(script.contains("npm test"), "Node project without pnpm-lock should run npm test");
|
||||
assert!(!script.contains("No tests configured"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detect_script_test_package_json_pnpm_adds_pnpm_test() {
|
||||
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_test(dir.path());
|
||||
assert!(script.contains("pnpm test"), "Node project with pnpm-lock should run pnpm test");
|
||||
// "pnpm test" is a substring of itself; verify there's no bare "npm test" line
|
||||
assert!(!script.lines().any(|l| l.trim() == "npm test"), "should not use npm when pnpm-lock.yaml is present");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detect_script_test_pyproject_toml_adds_pytest() {
|
||||
let dir = tempdir().unwrap();
|
||||
fs::write(dir.path().join("pyproject.toml"), "[project]\nname = \"x\"\n").unwrap();
|
||||
|
||||
let script = detect_script_test(dir.path());
|
||||
assert!(script.contains("pytest"), "Python project should run pytest");
|
||||
assert!(!script.contains("No tests configured"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detect_script_test_requirements_txt_adds_pytest() {
|
||||
let dir = tempdir().unwrap();
|
||||
fs::write(dir.path().join("requirements.txt"), "flask\n").unwrap();
|
||||
|
||||
let script = detect_script_test(dir.path());
|
||||
assert!(script.contains("pytest"), "Python project (requirements.txt) should run pytest");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detect_script_test_go_mod_adds_go_test() {
|
||||
let dir = tempdir().unwrap();
|
||||
fs::write(dir.path().join("go.mod"), "module example.com/app\n").unwrap();
|
||||
|
||||
let script = detect_script_test(dir.path());
|
||||
assert!(script.contains("go test ./..."), "Go project should run go test ./...");
|
||||
assert!(!script.contains("No tests configured"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detect_script_test_multi_stack_combines_commands() {
|
||||
let dir = tempdir().unwrap();
|
||||
fs::write(dir.path().join("go.mod"), "module example.com/app\n").unwrap();
|
||||
fs::write(dir.path().join("package.json"), "{}").unwrap();
|
||||
|
||||
let script = detect_script_test(dir.path());
|
||||
assert!(script.contains("go test ./..."), "multi-stack should include Go test command");
|
||||
assert!(script.contains("npm test"), "multi-stack should include Node test command");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detect_script_test_output_starts_with_shebang() {
|
||||
let dir = tempdir().unwrap();
|
||||
fs::write(dir.path().join("Cargo.toml"), "[package]\nname = \"x\"\n").unwrap();
|
||||
|
||||
let script = detect_script_test(dir.path());
|
||||
assert!(
|
||||
script.starts_with("#!/usr/bin/env bash\nset -euo pipefail\n"),
|
||||
"generated script should start with bash shebang and set -euo pipefail"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scaffold_script_test_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/test")).unwrap();
|
||||
assert!(content.contains("cargo test"), "Rust project scaffold should set cargo test in script/test");
|
||||
assert!(!content.contains("No tests configured"), "should not use stub when stack is detected");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scaffold_script_test_fallback_stub_when_no_stack() {
|
||||
let dir = tempdir().unwrap();
|
||||
scaffold_story_kit(dir.path(), 3001).unwrap();
|
||||
|
||||
let content = fs::read_to_string(dir.path().join("script/test")).unwrap();
|
||||
assert!(content.contains("No tests configured"), "unknown stack should use the generic stub");
|
||||
}
|
||||
|
||||
// --- generate_project_toml ---
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user