fix: add --all to cargo fmt in script/test and autoformat codebase

cargo fmt without --all fails with "Failed to find targets" in
workspace repos. This was blocking every story's gates. Also ran
cargo fmt --all to fix all existing formatting issues.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
dave
2026-04-13 14:07:08 +00:00
parent ed2526ce41
commit 845b85e7a7
128 changed files with 3566 additions and 2395 deletions
+2 -2
View File
@@ -6,8 +6,8 @@ pub mod project;
pub mod scaffold;
pub use files::{
create_directory_absolute, list_directory, list_directory_absolute, list_project_files,
read_file, write_file, FileEntry,
FileEntry, create_directory_absolute, list_directory, list_directory_absolute,
list_project_files, read_file, write_file,
};
pub use paths::{find_story_kit_root, get_home_directory, resolve_cli_path};
pub use preferences::{get_model_preference, set_model_preference};
+67 -22
View File
@@ -180,7 +180,13 @@ mod tests {
let store = make_store(&dir);
let state = SessionState::default();
let result = open_project(project_dir.to_string_lossy().to_string(), &state, &store, 3001).await;
let result = open_project(
project_dir.to_string_lossy().to_string(),
&state,
&store,
3001,
)
.await;
assert!(result.is_ok());
let root = state.get_project_root().unwrap();
@@ -201,9 +207,14 @@ mod tests {
let store = make_store(&dir);
let state = SessionState::default();
open_project(project_dir.to_string_lossy().to_string(), &state, &store, 3001)
.await
.unwrap();
open_project(
project_dir.to_string_lossy().to_string(),
&state,
&store,
3001,
)
.await
.unwrap();
assert_eq!(
fs::read_to_string(&mcp_path).unwrap(),
@@ -220,15 +231,29 @@ mod tests {
let store = make_store(&dir);
let state = SessionState::default();
open_project(project_dir.to_string_lossy().to_string(), &state, &store, 3001)
.await
.unwrap();
open_project(
project_dir.to_string_lossy().to_string(),
&state,
&store,
3001,
)
.await
.unwrap();
let mcp_path = project_dir.join(".mcp.json");
assert!(mcp_path.exists(), "open_project should write .mcp.json for new projects");
assert!(
mcp_path.exists(),
"open_project should write .mcp.json for new projects"
);
let content = fs::read_to_string(&mcp_path).unwrap();
assert!(content.contains("3001"), "mcp.json should reference the server port");
assert!(content.contains("localhost"), "mcp.json should reference localhost");
assert!(
content.contains("3001"),
"mcp.json should reference the server port"
);
assert!(
content.contains("localhost"),
"mcp.json should reference localhost"
);
}
/// Regression test for bug 371: no-arg `huskies` in empty directory skips scaffold.
@@ -242,9 +267,14 @@ mod tests {
let store = make_store(&dir);
let state = SessionState::default();
open_project(project_dir.to_string_lossy().to_string(), &state, &store, 3001)
.await
.unwrap();
open_project(
project_dir.to_string_lossy().to_string(),
&state,
&store,
3001,
)
.await
.unwrap();
assert!(
project_dir.join(".huskies/project.toml").exists(),
@@ -316,9 +346,14 @@ mod tests {
let store = make_store(&dir);
let state = SessionState::default();
open_project(project_dir.to_string_lossy().to_string(), &state, &store, 3001)
.await
.unwrap();
open_project(
project_dir.to_string_lossy().to_string(),
&state,
&store,
3001,
)
.await
.unwrap();
let projects = get_known_projects(&store).unwrap();
assert_eq!(projects.len(), 1);
@@ -383,9 +418,14 @@ mod tests {
let store = make_store(&dir);
let state = SessionState::default();
open_project(project_dir.to_string_lossy().to_string(), &state, &store, 3001)
.await
.unwrap();
open_project(
project_dir.to_string_lossy().to_string(),
&state,
&store,
3001,
)
.await
.unwrap();
// .huskies/ should have been created automatically
assert!(project_dir.join(".huskies").is_dir());
@@ -402,9 +442,14 @@ mod tests {
let store = make_store(&dir);
let state = SessionState::default();
open_project(project_dir.to_string_lossy().to_string(), &state, &store, 3001)
.await
.unwrap();
open_project(
project_dir.to_string_lossy().to_string(),
&state,
&store,
3001,
)
.await
.unwrap();
// Existing .huskies/ content should not be overwritten
assert_eq!(fs::read_to_string(&readme).unwrap(), "custom content");
+92 -27
View File
@@ -4,8 +4,7 @@ use std::path::Path;
const STORY_KIT_README: &str = include_str!("../../../../.huskies/README.md");
const BOT_TOML_MATRIX_EXAMPLE: &str =
include_str!("../../../../.huskies/bot.toml.matrix.example");
const BOT_TOML_MATRIX_EXAMPLE: &str = include_str!("../../../../.huskies/bot.toml.matrix.example");
const BOT_TOML_WHATSAPP_META_EXAMPLE: &str =
include_str!("../../../../.huskies/bot.toml.whatsapp-meta.example");
const BOT_TOML_WHATSAPP_TWILIO_EXAMPLE: &str =
@@ -194,9 +193,7 @@ pub fn detect_components_toml(root: &Path) -> String {
// No tech stack markers detected — emit a single generic component
// with an empty setup list. The ONBOARDING_PROMPT instructs the chat
// agent to inspect the project and replace this with real definitions.
sections.push(
"[[component]]\nname = \"app\"\npath = \".\"\nsetup = []\n".to_string(),
);
sections.push("[[component]]\nname = \"app\"\npath = \".\"\nsetup = []\n".to_string());
}
sections.join("\n")
@@ -826,9 +823,18 @@ mod tests {
let mcp_path = dir.path().join(".mcp.json");
assert!(mcp_path.exists(), ".mcp.json should be created by scaffold");
let content = fs::read_to_string(&mcp_path).unwrap();
assert!(content.contains("4242"), ".mcp.json should reference the given port");
assert!(content.contains("localhost"), ".mcp.json should reference localhost");
assert!(content.contains("huskies"), ".mcp.json should name the huskies server");
assert!(
content.contains("4242"),
".mcp.json should reference the given port"
);
assert!(
content.contains("localhost"),
".mcp.json should reference localhost"
);
assert!(
content.contains("huskies"),
".mcp.json should name the huskies server"
);
}
#[test]
@@ -976,7 +982,10 @@ mod tests {
fs::write(dir.path().join("go.mod"), "module example.com/app\n").unwrap();
let toml = detect_components_toml(dir.path());
assert!(!toml.contains("cargo"), "go project must not contain cargo commands");
assert!(
!toml.contains("cargo"),
"go project must not contain cargo commands"
);
assert!(toml.contains("go build"), "go project must use Go tooling");
}
@@ -986,8 +995,14 @@ mod tests {
fs::write(dir.path().join("package.json"), "{}").unwrap();
let toml = detect_components_toml(dir.path());
assert!(!toml.contains("cargo"), "node project must not contain cargo commands");
assert!(toml.contains("npm install"), "node project must use npm tooling");
assert!(
!toml.contains("cargo"),
"node project must not contain cargo commands"
);
assert!(
toml.contains("npm install"),
"node project must use npm tooling"
);
}
#[test]
@@ -995,9 +1010,15 @@ mod tests {
let dir = tempdir().unwrap();
let toml = detect_components_toml(dir.path());
assert!(!toml.contains("cargo"), "unknown stack must not contain cargo commands");
assert!(
!toml.contains("cargo"),
"unknown stack must not contain cargo commands"
);
// setup list must be empty
assert!(toml.contains("setup = []"), "unknown stack must have empty setup list");
assert!(
toml.contains("setup = []"),
"unknown stack must have empty setup list"
);
}
#[test]
@@ -1047,7 +1068,10 @@ mod tests {
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("cargo test"),
"Rust project should run cargo test"
);
assert!(!script.contains("No tests configured"));
}
@@ -1057,7 +1081,10 @@ mod tests {
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("npm test"),
"Node project without pnpm-lock should run npm test"
);
assert!(!script.contains("No tests configured"));
}
@@ -1068,18 +1095,31 @@ mod tests {
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");
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");
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();
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("pytest"),
"Python project should run pytest"
);
assert!(!script.contains("No tests configured"));
}
@@ -1089,7 +1129,10 @@ mod tests {
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");
assert!(
script.contains("pytest"),
"Python project (requirements.txt) should run pytest"
);
}
#[test]
@@ -1098,7 +1141,10 @@ mod tests {
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("go test ./..."),
"Go project should run go test ./..."
);
assert!(!script.contains("No tests configured"));
}
@@ -1109,8 +1155,14 @@ mod tests {
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");
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]
@@ -1128,13 +1180,23 @@ mod tests {
#[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();
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");
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]
@@ -1143,7 +1205,10 @@ mod tests {
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");
assert!(
content.contains("No tests configured"),
"unknown stack should use the generic stub"
);
}
// --- generate_project_toml ---
+2 -2
View File
@@ -4,7 +4,7 @@ pub mod onboarding;
pub mod search;
pub mod shell;
pub mod story_metadata;
pub mod watcher;
pub mod wizard;
#[cfg(test)]
pub(crate) mod test_helpers;
pub mod watcher;
pub mod wizard;
+3 -1
View File
@@ -159,7 +159,9 @@ mod tests {
let state = SessionState::default();
*state.project_root.lock().unwrap() = Some(dir.path().to_path_buf());
let results = search_files("target_text".to_string(), &state).await.unwrap();
let results = search_files("target_text".to_string(), &state)
.await
.unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0].path, "found.txt");
+4 -13
View File
@@ -75,12 +75,7 @@ mod tests {
#[tokio::test]
async fn exec_shell_impl_runs_allowed_command() {
let dir = tempdir().unwrap();
let result = exec_shell_impl(
"ls".to_string(),
Vec::new(),
dir.path().to_path_buf(),
)
.await;
let result = exec_shell_impl("ls".to_string(), Vec::new(), dir.path().to_path_buf()).await;
assert!(result.is_ok());
let output = result.unwrap();
@@ -92,13 +87,9 @@ mod tests {
let dir = tempdir().unwrap();
std::fs::write(dir.path().join("hello.txt"), "").unwrap();
let result = exec_shell_impl(
"ls".to_string(),
Vec::new(),
dir.path().to_path_buf(),
)
.await
.unwrap();
let result = exec_shell_impl("ls".to_string(), Vec::new(), dir.path().to_path_buf())
.await
.unwrap();
assert!(result.stdout.contains("hello.txt"));
}
+30 -11
View File
@@ -142,7 +142,10 @@ pub fn write_merge_failure(path: &Path, reason: &str) -> Result<(), String> {
fs::read_to_string(path).map_err(|e| format!("Failed to read story file: {e}"))?;
// Produce a YAML-safe inline quoted string: collapse newlines, escape inner quotes.
let escaped = reason.replace('"', "\\\"").replace('\n', " ").replace('\r', "");
let escaped = reason
.replace('"', "\\\"")
.replace('\n', " ")
.replace('\r', "");
let yaml_value = format!("\"{escaped}\"");
let updated = set_front_matter_field(&contents, "merge_failure", &yaml_value);
@@ -288,7 +291,10 @@ pub fn check_unmet_deps(project_root: &Path, stage_dir: &str, story_id: &str) ->
Ok(c) => c,
Err(_) => return Vec::new(),
};
let deps = match parse_front_matter(&contents).ok().and_then(|m| m.depends_on) {
let deps = match parse_front_matter(&contents)
.ok()
.and_then(|m| m.depends_on)
{
Some(d) => d,
None => return Vec::new(),
};
@@ -333,7 +339,10 @@ fn dep_is_done(project_root: &Path, dep_number: u32) -> bool {
fn dep_is_archived(project_root: &Path, dep_number: u32) -> bool {
let prefix = format!("{dep_number}_");
let exact = dep_number.to_string();
let dir = project_root.join(".huskies").join("work").join("6_archived");
let dir = project_root
.join(".huskies")
.join("work")
.join("6_archived");
if let Ok(entries) = fs::read_dir(&dir) {
for entry in entries.flatten() {
let path = entry.path();
@@ -365,7 +374,10 @@ pub fn check_archived_deps(project_root: &Path, stage_dir: &str, story_id: &str)
Ok(c) => c,
Err(_) => return Vec::new(),
};
let deps = match parse_front_matter(&contents).ok().and_then(|m| m.depends_on) {
let deps = match parse_front_matter(&contents)
.ok()
.and_then(|m| m.depends_on)
{
Some(d) => d,
None => return Vec::new(),
};
@@ -434,7 +446,10 @@ pub fn write_blocked_in_content(contents: &str) -> String {
/// Write or update `merge_failure` in story content (pure function).
pub fn write_merge_failure_in_content(contents: &str, reason: &str) -> String {
let escaped = reason.replace('"', "\\\"").replace('\n', " ").replace('\r', "");
let escaped = reason
.replace('"', "\\\"")
.replace('\n', " ")
.replace('\r', "");
let yaml_value = format!("\"{escaped}\"");
set_front_matter_field(contents, "merge_failure", &yaml_value)
}
@@ -465,9 +480,7 @@ pub fn parse_unchecked_todos(contents: &str) -> Vec<String> {
.lines()
.filter_map(|line| {
let trimmed = line.trim();
trimmed
.strip_prefix("- [ ] ")
.map(|text| text.to_string())
trimmed.strip_prefix("- [ ] ").map(|text| text.to_string())
})
.collect()
}
@@ -486,7 +499,10 @@ workflow: tdd
"#;
let meta = parse_front_matter(input).expect("front matter");
assert_eq!(meta.name.as_deref(), Some("Establish the TDD Workflow and Gates"));
assert_eq!(
meta.name.as_deref(),
Some("Establish the TDD Workflow and Gates")
);
assert_eq!(meta.coverage_baseline, None);
}
@@ -566,7 +582,11 @@ workflow: tdd
fn clear_front_matter_field_updates_file() {
let tmp = tempfile::tempdir().unwrap();
let path = tmp.path().join("story.md");
std::fs::write(&path, "---\nname: Test\nmerge_failure: \"bad\"\n---\n# Story\n").unwrap();
std::fs::write(
&path,
"---\nname: Test\nmerge_failure: \"bad\"\n---\n# Story\n",
)
.unwrap();
clear_front_matter_field(&path, "merge_failure").unwrap();
let contents = std::fs::read_to_string(&path).unwrap();
assert!(!contents.contains("merge_failure"));
@@ -854,5 +874,4 @@ workflow: tdd
// 99 doesn't exist anywhere.
assert!(!dep_is_archived(tmp.path(), 99));
}
}
+13 -14
View File
@@ -301,10 +301,7 @@ fn flush_pending(
pending
.iter()
.filter(|(path, _)| !path.exists())
.find(|(path, _)| {
path.file_stem()
.and_then(|s| s.to_str()) == Some(item_id.as_str())
})
.find(|(path, _)| path.file_stem().and_then(|s| s.to_str()) == Some(item_id.as_str()))
.map(|(_, stage)| stage.clone())
} else {
None
@@ -327,7 +324,7 @@ fn flush_pending(
/// All state is read from and written to CRDT — no filesystem access.
/// Worktree pruning is handled separately by the CRDT event subscriber.
pub(crate) fn sweep_done_to_archived(done_retention: Duration) {
use crate::pipeline_state::{PipelineEvent, Stage, stage_dir_name, transition, read_all_typed};
use crate::pipeline_state::{PipelineEvent, Stage, read_all_typed, stage_dir_name, transition};
for item in read_all_typed() {
if let Stage::Done { merged_at, .. } = &item.stage {
@@ -374,10 +371,7 @@ pub(crate) fn sweep_done_to_archived(done_retention: Duration) {
/// `git_root` — project root (passed to `git` commands and config loading).
/// `event_tx` — broadcast sender for `ConfigChanged` events.
/// `watcher_config` — initial sweep configuration loaded from `project.toml`.
pub fn start_watcher(
git_root: PathBuf,
event_tx: broadcast::Sender<WatcherEvent>,
) {
pub fn start_watcher(git_root: PathBuf, event_tx: broadcast::Sender<WatcherEvent>) {
std::thread::spawn(move || {
let (notify_tx, notify_rx) = mpsc::channel::<notify::Result<notify::Event>>();
@@ -1080,7 +1074,9 @@ mod tests {
// Verify the item was moved to 6_archived in the CRDT.
let items = crate::pipeline_state::read_all_typed();
let item = items.iter().find(|i| i.story_id.0 == "9880_story_sweep_old");
let item = items
.iter()
.find(|i| i.story_id.0 == "9880_story_sweep_old");
assert!(
item.is_some_and(|i| matches!(i.stage, crate::pipeline_state::Stage::Archived { .. })),
"item should be archived after sweep"
@@ -1100,7 +1096,9 @@ mod tests {
sweep_done_to_archived(Duration::from_secs(999_999));
let items = crate::pipeline_state::read_all_typed();
let item = items.iter().find(|i| i.story_id.0 == "9881_story_sweep_new");
let item = items
.iter()
.find(|i| i.story_id.0 == "9881_story_sweep_new");
assert!(
item.is_some_and(|i| matches!(i.stage, crate::pipeline_state::Stage::Done { .. })),
"item should remain in Done with long retention"
@@ -1120,7 +1118,9 @@ mod tests {
sweep_done_to_archived(Duration::ZERO);
let items = crate::pipeline_state::read_all_typed();
let item = items.iter().find(|i| i.story_id.0 == "9882_story_sweep_custom");
let item = items
.iter()
.find(|i| i.story_id.0 == "9882_story_sweep_custom");
assert!(
item.is_some_and(|i| matches!(i.stage, crate::pipeline_state::Stage::Archived { .. })),
"item should be archived with zero retention"
@@ -1172,8 +1172,7 @@ mod tests {
fn sweep_keeps_item_newer_than_retention() {
crate::db::ensure_content_store();
let one_second_ago =
(chrono::Utc::now() - chrono::Duration::seconds(1)).timestamp() as f64;
let one_second_ago = (chrono::Utc::now() - chrono::Duration::seconds(1)).timestamp() as f64;
crate::crdt_state::write_item(
"9884_story_sweep_recent",
+1 -4
View File
@@ -380,10 +380,7 @@ mod tests {
Some("generated content".to_string()),
);
assert_eq!(state.steps[1].status, StepStatus::AwaitingConfirmation);
assert_eq!(
state.steps[1].content.as_deref(),
Some("generated content")
);
assert_eq!(state.steps[1].content.as_deref(), Some("generated content"));
}
#[test]