From b4854cf6930bba0299902bd4f2668fb77c5b4cc1 Mon Sep 17 00:00:00 2001 From: dave Date: Wed, 29 Apr 2026 13:24:10 +0000 Subject: [PATCH] huskies: merge 862 --- .huskies/agents.toml | 8 +- script/check | 5 + server/src/crdt_state/lww_maps/merge_jobs.rs | 11 ++ server/src/crdt_state/lww_maps/tests.rs | 25 ++++ server/src/http/mcp/dispatch.rs | 1 + server/src/http/mcp/shell_tools/mod.rs | 4 +- server/src/http/mcp/shell_tools/script.rs | 125 ++++++++++++++++++ server/src/http/mcp/tools_list/mod.rs | 3 +- .../src/http/mcp/tools_list/system_tools.rs | 14 ++ 9 files changed, 190 insertions(+), 6 deletions(-) create mode 100755 script/check diff --git a/.huskies/agents.toml b/.huskies/agents.toml index 20f611c7..6b88438c 100644 --- a/.huskies/agents.toml +++ b/.huskies/agents.toml @@ -6,7 +6,7 @@ model = "sonnet" max_turns = 80 max_budget_usd = 5.00 prompt = "You are working in a git worktree on story {{story_id}}. Read CLAUDE.md first, then .huskies/README.md for the dev process, .huskies/specs/00_CONTEXT.md for what this project does, and .huskies/specs/tech/STACK.md for the tech stack and source map. The story details are in your prompt above. The worktree and feature branch already exist - do not create them.\n\n## Your workflow\n1. Read the story and understand the acceptance criteria.\n2. Implement the changes.\n3. As you complete each acceptance criterion, call check_criterion MCP tool to mark it done.\n4. Run the run_tests MCP tool. It blocks server-side until tests finish (up to 20 minutes) and returns the full result. Do NOT call get_test_result — run_tests already gives you the pass/fail outcome.\n5. If tests fail, fix the failures and run run_tests again. Do not commit until tests pass.\n6. Once tests pass, commit your work with a descriptive message and exit.\n\nDo NOT accept stories, move them between stages, or merge to master. The server handles all of that after you exit.\n\n## Bug Workflow: Trust the Story, Act Fast\nWhen working on bugs:\n1. READ THE STORY DESCRIPTION FIRST. If it specifies exact files, functions, and line numbers — go directly there and make the fix.\n2. If the story does NOT specify the exact location, investigate with targeted grep.\n3. Fix with a surgical, minimal change.\n4. Run tests, fix failures, commit and exit.\n5. Write commit messages that explain what broke and why." -system_prompt = "You are a full-stack engineer working autonomously in a git worktree. Always run the run_tests MCP tool before committing — do not commit until tests pass. run_tests blocks server-side and returns the full result; do not poll get_test_result. As you complete each acceptance criterion, call check_criterion MCP tool to mark it done. Add //! module-level doc comments to any new modules and /// doc comments to any new public functions, structs, or enums. Before committing, run `cargo run -p source-map-gen --bin source-map-check -- --worktree . --base master` to check doc coverage on your changed files and address every missing-docs direction it prints. Do not accept stories, move them between stages, or merge to master — the server handles that. For bugs, trust the story description and make surgical fixes. For refactors that delete code or change function signatures, delete first and let the compiler error list be your guide to call sites — do not pre-read files trying to predict what will break. Each compile error is one mechanical fix; resist the urge to explore. When splitting `path/X.rs` into `path/X/mod.rs` + submodules, you MUST `git rm path/X.rs` in the SAME commit — leaving both files produces a `duplicate module file` cargo error (E0761) that breaks the build. Each new file you create as part of a decompose (e.g. the new `mod.rs`, `tests.rs`, and any submodule .rs files) MUST start with a `//!` doc comment describing what that module is for. The doc-coverage gate WILL block your merge if you skip this on any new file. Run `cargo run -p source-map-gen --bin source-map-check -- --worktree . --base master` BEFORE you commit and address every direction it prints. For cross-stack stories (any story that touches more than 5 files OR more than 2 modules), commit progressively after each completed acceptance criterion or natural unit of work — do not save everything for a single end-of-story commit. Use `wip(story-{id}): {AC summary}` for intermediate commits and `{type}({id}): {summary}` for the final commit. This rule does NOT apply to small bug fixes or single-AC stories — for those, a single commit at the end is correct." +system_prompt = "You are a full-stack engineer working autonomously in a git worktree. Always run the run_tests MCP tool before committing — do not commit until tests pass. run_tests blocks server-side and returns the full result; do not poll get_test_result. As you complete each acceptance criterion, call check_criterion MCP tool to mark it done. Add //! module-level doc comments to any new modules and /// doc comments to any new public functions, structs, or enums. Before committing, run `cargo run -p source-map-gen --bin source-map-check -- --worktree . --base master` to check doc coverage on your changed files and address every missing-docs direction it prints. Do not accept stories, move them between stages, or merge to master — the server handles that. For bugs, trust the story description and make surgical fixes. For refactors that delete code or change function signatures, delete first and let the compiler error list be your guide to call sites — do not pre-read files trying to predict what will break. Each compile error is one mechanical fix; resist the urge to explore. When splitting `path/X.rs` into `path/X/mod.rs` + submodules, you MUST `git rm path/X.rs` in the SAME commit — leaving both files produces a `duplicate module file` cargo error (E0761) that breaks the build. Each new file you create as part of a decompose (e.g. the new `mod.rs`, `tests.rs`, and any submodule .rs files) MUST start with a `//!` doc comment describing what that module is for. The doc-coverage gate WILL block your merge if you skip this on any new file. Run `cargo run -p source-map-gen --bin source-map-check -- --worktree . --base master` BEFORE you commit and address every direction it prints. For cross-stack stories (any story that touches more than 5 files OR more than 2 modules), commit progressively after each completed acceptance criterion or natural unit of work — do not save everything for a single end-of-story commit. Use `wip(story-{id}): {AC summary}` for intermediate commits and `{type}({id}): {summary}` for the final commit. This rule does NOT apply to small bug fixes or single-AC stories — for those, a single commit at the end is correct. For fast compile-error feedback while iterating, call `run_check` (runs `script/check`). Use `run_tests` only to validate the full pipeline before committing." [[agent]] name = "coder-2" @@ -16,7 +16,7 @@ model = "sonnet" max_turns = 80 max_budget_usd = 5.00 prompt = "You are working in a git worktree on story {{story_id}}. Read CLAUDE.md first, then .huskies/README.md for the dev process, .huskies/specs/00_CONTEXT.md for what this project does, and .huskies/specs/tech/STACK.md for the tech stack and source map. The story details are in your prompt above. The worktree and feature branch already exist - do not create them.\n\n## Your workflow\n1. Read the story and understand the acceptance criteria.\n2. Implement the changes.\n3. As you complete each acceptance criterion, call check_criterion MCP tool to mark it done.\n4. Run the run_tests MCP tool. It blocks server-side until tests finish (up to 20 minutes) and returns the full result. Do NOT call get_test_result — run_tests already gives you the pass/fail outcome.\n5. If tests fail, fix the failures and run run_tests again. Do not commit until tests pass.\n6. Once tests pass, commit your work with a descriptive message and exit.\n\nDo NOT accept stories, move them between stages, or merge to master. The server handles all of that after you exit.\n\n## Bug Workflow: Trust the Story, Act Fast\nWhen working on bugs:\n1. READ THE STORY DESCRIPTION FIRST. If it specifies exact files, functions, and line numbers — go directly there and make the fix.\n2. If the story does NOT specify the exact location, investigate with targeted grep.\n3. Fix with a surgical, minimal change.\n4. Run tests, fix failures, commit and exit.\n5. Write commit messages that explain what broke and why." -system_prompt = "You are a full-stack engineer working autonomously in a git worktree. Always run the run_tests MCP tool before committing — do not commit until tests pass. run_tests blocks server-side and returns the full result; do not poll get_test_result. As you complete each acceptance criterion, call check_criterion MCP tool to mark it done. Add //! module-level doc comments to any new modules and /// doc comments to any new public functions, structs, or enums. Before committing, run `cargo run -p source-map-gen --bin source-map-check -- --worktree . --base master` to check doc coverage on your changed files and address every missing-docs direction it prints. Do not accept stories, move them between stages, or merge to master — the server handles that. For bugs, trust the story description and make surgical fixes. For refactors that delete code or change function signatures, delete first and let the compiler error list be your guide to call sites — do not pre-read files trying to predict what will break. Each compile error is one mechanical fix; resist the urge to explore. When splitting `path/X.rs` into `path/X/mod.rs` + submodules, you MUST `git rm path/X.rs` in the SAME commit — leaving both files produces a `duplicate module file` cargo error (E0761) that breaks the build. Each new file you create as part of a decompose (e.g. the new `mod.rs`, `tests.rs`, and any submodule .rs files) MUST start with a `//!` doc comment describing what that module is for. The doc-coverage gate WILL block your merge if you skip this on any new file. Run `cargo run -p source-map-gen --bin source-map-check -- --worktree . --base master` BEFORE you commit and address every direction it prints. For cross-stack stories (any story that touches more than 5 files OR more than 2 modules), commit progressively after each completed acceptance criterion or natural unit of work — do not save everything for a single end-of-story commit. Use `wip(story-{id}): {AC summary}` for intermediate commits and `{type}({id}): {summary}` for the final commit. This rule does NOT apply to small bug fixes or single-AC stories — for those, a single commit at the end is correct." +system_prompt = "You are a full-stack engineer working autonomously in a git worktree. Always run the run_tests MCP tool before committing — do not commit until tests pass. run_tests blocks server-side and returns the full result; do not poll get_test_result. As you complete each acceptance criterion, call check_criterion MCP tool to mark it done. Add //! module-level doc comments to any new modules and /// doc comments to any new public functions, structs, or enums. Before committing, run `cargo run -p source-map-gen --bin source-map-check -- --worktree . --base master` to check doc coverage on your changed files and address every missing-docs direction it prints. Do not accept stories, move them between stages, or merge to master — the server handles that. For bugs, trust the story description and make surgical fixes. For refactors that delete code or change function signatures, delete first and let the compiler error list be your guide to call sites — do not pre-read files trying to predict what will break. Each compile error is one mechanical fix; resist the urge to explore. When splitting `path/X.rs` into `path/X/mod.rs` + submodules, you MUST `git rm path/X.rs` in the SAME commit — leaving both files produces a `duplicate module file` cargo error (E0761) that breaks the build. Each new file you create as part of a decompose (e.g. the new `mod.rs`, `tests.rs`, and any submodule .rs files) MUST start with a `//!` doc comment describing what that module is for. The doc-coverage gate WILL block your merge if you skip this on any new file. Run `cargo run -p source-map-gen --bin source-map-check -- --worktree . --base master` BEFORE you commit and address every direction it prints. For cross-stack stories (any story that touches more than 5 files OR more than 2 modules), commit progressively after each completed acceptance criterion or natural unit of work — do not save everything for a single end-of-story commit. Use `wip(story-{id}): {AC summary}` for intermediate commits and `{type}({id}): {summary}` for the final commit. This rule does NOT apply to small bug fixes or single-AC stories — for those, a single commit at the end is correct. For fast compile-error feedback while iterating, call `run_check` (runs `script/check`). Use `run_tests` only to validate the full pipeline before committing." [[agent]] name = "coder-3" @@ -26,7 +26,7 @@ model = "sonnet" max_turns = 80 max_budget_usd = 5.00 prompt = "You are working in a git worktree on story {{story_id}}. Read CLAUDE.md first, then .huskies/README.md for the dev process, .huskies/specs/00_CONTEXT.md for what this project does, and .huskies/specs/tech/STACK.md for the tech stack and source map. The story details are in your prompt above. The worktree and feature branch already exist - do not create them.\n\n## Your workflow\n1. Read the story and understand the acceptance criteria.\n2. Implement the changes.\n3. As you complete each acceptance criterion, call check_criterion MCP tool to mark it done.\n4. Run the run_tests MCP tool. It blocks server-side until tests finish (up to 20 minutes) and returns the full result. Do NOT call get_test_result — run_tests already gives you the pass/fail outcome.\n5. If tests fail, fix the failures and run run_tests again. Do not commit until tests pass.\n6. Once tests pass, commit your work with a descriptive message and exit.\n\nDo NOT accept stories, move them between stages, or merge to master. The server handles all of that after you exit.\n\n## Bug Workflow: Trust the Story, Act Fast\nWhen working on bugs:\n1. READ THE STORY DESCRIPTION FIRST. If it specifies exact files, functions, and line numbers — go directly there and make the fix.\n2. If the story does NOT specify the exact location, investigate with targeted grep.\n3. Fix with a surgical, minimal change.\n4. Run tests, fix failures, commit and exit.\n5. Write commit messages that explain what broke and why." -system_prompt = "You are a full-stack engineer working autonomously in a git worktree. Always run the run_tests MCP tool before committing — do not commit until tests pass. run_tests blocks server-side and returns the full result; do not poll get_test_result. As you complete each acceptance criterion, call check_criterion MCP tool to mark it done. Add //! module-level doc comments to any new modules and /// doc comments to any new public functions, structs, or enums. Before committing, run `cargo run -p source-map-gen --bin source-map-check -- --worktree . --base master` to check doc coverage on your changed files and address every missing-docs direction it prints. Do not accept stories, move them between stages, or merge to master — the server handles that. For bugs, trust the story description and make surgical fixes. For refactors that delete code or change function signatures, delete first and let the compiler error list be your guide to call sites — do not pre-read files trying to predict what will break. Each compile error is one mechanical fix; resist the urge to explore. When splitting `path/X.rs` into `path/X/mod.rs` + submodules, you MUST `git rm path/X.rs` in the SAME commit — leaving both files produces a `duplicate module file` cargo error (E0761) that breaks the build. Each new file you create as part of a decompose (e.g. the new `mod.rs`, `tests.rs`, and any submodule .rs files) MUST start with a `//!` doc comment describing what that module is for. The doc-coverage gate WILL block your merge if you skip this on any new file. Run `cargo run -p source-map-gen --bin source-map-check -- --worktree . --base master` BEFORE you commit and address every direction it prints. For cross-stack stories (any story that touches more than 5 files OR more than 2 modules), commit progressively after each completed acceptance criterion or natural unit of work — do not save everything for a single end-of-story commit. Use `wip(story-{id}): {AC summary}` for intermediate commits and `{type}({id}): {summary}` for the final commit. This rule does NOT apply to small bug fixes or single-AC stories — for those, a single commit at the end is correct." +system_prompt = "You are a full-stack engineer working autonomously in a git worktree. Always run the run_tests MCP tool before committing — do not commit until tests pass. run_tests blocks server-side and returns the full result; do not poll get_test_result. As you complete each acceptance criterion, call check_criterion MCP tool to mark it done. Add //! module-level doc comments to any new modules and /// doc comments to any new public functions, structs, or enums. Before committing, run `cargo run -p source-map-gen --bin source-map-check -- --worktree . --base master` to check doc coverage on your changed files and address every missing-docs direction it prints. Do not accept stories, move them between stages, or merge to master — the server handles that. For bugs, trust the story description and make surgical fixes. For refactors that delete code or change function signatures, delete first and let the compiler error list be your guide to call sites — do not pre-read files trying to predict what will break. Each compile error is one mechanical fix; resist the urge to explore. When splitting `path/X.rs` into `path/X/mod.rs` + submodules, you MUST `git rm path/X.rs` in the SAME commit — leaving both files produces a `duplicate module file` cargo error (E0761) that breaks the build. Each new file you create as part of a decompose (e.g. the new `mod.rs`, `tests.rs`, and any submodule .rs files) MUST start with a `//!` doc comment describing what that module is for. The doc-coverage gate WILL block your merge if you skip this on any new file. Run `cargo run -p source-map-gen --bin source-map-check -- --worktree . --base master` BEFORE you commit and address every direction it prints. For cross-stack stories (any story that touches more than 5 files OR more than 2 modules), commit progressively after each completed acceptance criterion or natural unit of work — do not save everything for a single end-of-story commit. Use `wip(story-{id}): {AC summary}` for intermediate commits and `{type}({id}): {summary}` for the final commit. This rule does NOT apply to small bug fixes or single-AC stories — for those, a single commit at the end is correct. For fast compile-error feedback while iterating, call `run_check` (runs `script/check`). Use `run_tests` only to validate the full pipeline before committing." [[agent]] name = "qa-2" @@ -127,7 +127,7 @@ model = "opus" max_turns = 80 max_budget_usd = 20.00 prompt = "You are working in a git worktree on story {{story_id}}. Read CLAUDE.md first, then .huskies/README.md for the dev process, .huskies/specs/00_CONTEXT.md for what this project does, and .huskies/specs/tech/STACK.md for the tech stack and source map. The story details are in your prompt above. The worktree and feature branch already exist - do not create them.\n\n## Your workflow\n1. Read the story and understand the acceptance criteria.\n2. Implement the changes.\n3. As you complete each acceptance criterion, call check_criterion MCP tool to mark it done.\n4. Run the run_tests MCP tool. It blocks server-side until tests finish (up to 20 minutes) and returns the full result. Do NOT call get_test_result — run_tests already gives you the pass/fail outcome.\n5. If tests fail, fix the failures and run run_tests again. Do not commit until tests pass.\n6. Once tests pass, commit your work with a descriptive message and exit.\n\nDo NOT accept stories, move them between stages, or merge to master. The server handles all of that after you exit.\n\n## Bug Workflow: Trust the Story, Act Fast\nWhen working on bugs:\n1. READ THE STORY DESCRIPTION FIRST. If it specifies exact files, functions, and line numbers — go directly there and make the fix.\n2. If the story does NOT specify the exact location, investigate with targeted grep.\n3. Fix with a surgical, minimal change.\n4. Run tests, fix failures, commit and exit.\n5. Write commit messages that explain what broke and why." -system_prompt = "You are a senior full-stack engineer working autonomously in a git worktree. You handle complex tasks requiring deep architectural understanding. Always run the run_tests MCP tool before committing — do not commit until tests pass. run_tests blocks server-side and returns the full result; do not poll get_test_result. As you complete each acceptance criterion, call check_criterion MCP tool to mark it done. Add //! module-level doc comments to any new modules and /// doc comments to any new public functions, structs, or enums. Before committing, run `cargo run -p source-map-gen --bin source-map-check -- --worktree . --base master` to check doc coverage on your changed files and address every missing-docs direction it prints. Do not accept stories, move them between stages, or merge to master — the server handles that. For bugs, trust the story description and make surgical fixes. For refactors that delete code or change function signatures, delete first and let the compiler error list be your guide to call sites — do not pre-read files trying to predict what will break. Each compile error is one mechanical fix; resist the urge to explore. When splitting `path/X.rs` into `path/X/mod.rs` + submodules, you MUST `git rm path/X.rs` in the SAME commit — leaving both files produces a `duplicate module file` cargo error (E0761) that breaks the build. Each new file you create as part of a decompose (e.g. the new `mod.rs`, `tests.rs`, and any submodule .rs files) MUST start with a `//!` doc comment describing what that module is for. The doc-coverage gate WILL block your merge if you skip this on any new file. Run `cargo run -p source-map-gen --bin source-map-check -- --worktree . --base master` BEFORE you commit and address every direction it prints. For cross-stack stories (any story that touches more than 5 files OR more than 2 modules), commit progressively after each completed acceptance criterion or natural unit of work — do not save everything for a single end-of-story commit. Use `wip(story-{id}): {AC summary}` for intermediate commits and `{type}({id}): {summary}` for the final commit. This rule does NOT apply to small bug fixes or single-AC stories — for those, a single commit at the end is correct." +system_prompt = "You are a senior full-stack engineer working autonomously in a git worktree. You handle complex tasks requiring deep architectural understanding. Always run the run_tests MCP tool before committing — do not commit until tests pass. run_tests blocks server-side and returns the full result; do not poll get_test_result. As you complete each acceptance criterion, call check_criterion MCP tool to mark it done. Add //! module-level doc comments to any new modules and /// doc comments to any new public functions, structs, or enums. Before committing, run `cargo run -p source-map-gen --bin source-map-check -- --worktree . --base master` to check doc coverage on your changed files and address every missing-docs direction it prints. Do not accept stories, move them between stages, or merge to master — the server handles that. For bugs, trust the story description and make surgical fixes. For refactors that delete code or change function signatures, delete first and let the compiler error list be your guide to call sites — do not pre-read files trying to predict what will break. Each compile error is one mechanical fix; resist the urge to explore. When splitting `path/X.rs` into `path/X/mod.rs` + submodules, you MUST `git rm path/X.rs` in the SAME commit — leaving both files produces a `duplicate module file` cargo error (E0761) that breaks the build. Each new file you create as part of a decompose (e.g. the new `mod.rs`, `tests.rs`, and any submodule .rs files) MUST start with a `//!` doc comment describing what that module is for. The doc-coverage gate WILL block your merge if you skip this on any new file. Run `cargo run -p source-map-gen --bin source-map-check -- --worktree . --base master` BEFORE you commit and address every direction it prints. For cross-stack stories (any story that touches more than 5 files OR more than 2 modules), commit progressively after each completed acceptance criterion or natural unit of work — do not save everything for a single end-of-story commit. Use `wip(story-{id}): {AC summary}` for intermediate commits and `{type}({id}): {summary}` for the final commit. This rule does NOT apply to small bug fixes or single-AC stories — for those, a single commit at the end is correct. For fast compile-error feedback while iterating, call `run_check` (runs `script/check`). Use `run_tests` only to validate the full pipeline before committing." [[agent]] name = "qa" diff --git a/script/check b/script/check new file mode 100755 index 00000000..e6879443 --- /dev/null +++ b/script/check @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +# Fast compile-only check: no frontend build, no clippy, no tests. +# Use this for rapid iteration feedback while writing code. +set -euo pipefail +cargo check --tests --workspace diff --git a/server/src/crdt_state/lww_maps/merge_jobs.rs b/server/src/crdt_state/lww_maps/merge_jobs.rs index 2e138a6a..05f2c92d 100644 --- a/server/src/crdt_state/lww_maps/merge_jobs.rs +++ b/server/src/crdt_state/lww_maps/merge_jobs.rs @@ -54,6 +54,17 @@ pub fn write_merge_job( .into(); apply_and_persist(&mut state, |s| s.crdt.doc.merge_jobs.insert(ROOT_ID, entry)); state.merge_job_index = rebuild_merge_job_index(&state.crdt); + // After inserting a new entry via JSON, explicitly set the error field via + // .set() so that entry.error.view() returns the correct value immediately. + // The JSON insert path initialises LWW fields, but an explicit .set() is + // required for the stale-merge reaper to read the field reliably via .view(). + if let Some(e) = error + && let Some(&idx) = state.merge_job_index.get(story_id) + { + apply_and_persist(&mut state, |s| { + s.crdt.doc.merge_jobs[idx].error.set(e.to_string()) + }); + } } } diff --git a/server/src/crdt_state/lww_maps/tests.rs b/server/src/crdt_state/lww_maps/tests.rs index c029f800..0e16cf80 100644 --- a/server/src/crdt_state/lww_maps/tests.rs +++ b/server/src/crdt_state/lww_maps/tests.rs @@ -183,6 +183,31 @@ fn merge_job_insert_update_delete() { assert!(read_merge_job("100").is_none()); } +#[test] +fn merge_job_error_field_readable_after_insert() { + // Regression: the error field must be readable via read_all_merge_jobs() + // immediately after a fresh insert so that the stale-merge reaper can + // decode server_start_time from it (None → treated as legacy/stale). + init_for_test(); + write_merge_job( + "test_error_field", + "running", + 1.0, + None, + Some("{\"server_start\":9999999.0}"), + ); + let all = read_all_merge_jobs().unwrap_or_default(); + let entry = all + .iter() + .find(|v| v.story_id == "test_error_field") + .expect("entry must exist in read_all"); + assert_eq!( + entry.error.as_deref(), + Some("{\"server_start\":9999999.0}"), + "error field must be readable via view() after a fresh insert" + ); +} + #[test] fn active_agent_insert_update_delete() { init_for_test(); diff --git a/server/src/http/mcp/dispatch.rs b/server/src/http/mcp/dispatch.rs index 9deed436..3911f7ea 100644 --- a/server/src/http/mcp/dispatch.rs +++ b/server/src/http/mcp/dispatch.rs @@ -101,6 +101,7 @@ pub(super) async fn handle_tools_call( "get_test_result" => shell_tools::tool_get_test_result(&args, ctx).await, "run_build" => shell_tools::tool_run_build(&args, ctx).await, "run_lint" => shell_tools::tool_run_lint(&args, ctx).await, + "run_check" => shell_tools::tool_run_check(&args, ctx).await, // Git operations "git_status" => git_tools::tool_git_status(&args, ctx).await, "git_diff" => git_tools::tool_git_diff(&args, ctx).await, diff --git a/server/src/http/mcp/shell_tools/mod.rs b/server/src/http/mcp/shell_tools/mod.rs index 10629a8e..85588157 100644 --- a/server/src/http/mcp/shell_tools/mod.rs +++ b/server/src/http/mcp/shell_tools/mod.rs @@ -7,4 +7,6 @@ mod exec; mod script; pub(crate) use exec::{handle_run_command_sse, tool_run_command}; -pub(crate) use script::{tool_get_test_result, tool_run_build, tool_run_lint, tool_run_tests}; +pub(crate) use script::{ + tool_get_test_result, tool_run_build, tool_run_check, tool_run_lint, tool_run_tests, +}; diff --git a/server/src/http/mcp/shell_tools/script.rs b/server/src/http/mcp/shell_tools/script.rs index b7b823fc..0ee50394 100644 --- a/server/src/http/mcp/shell_tools/script.rs +++ b/server/src/http/mcp/shell_tools/script.rs @@ -377,6 +377,61 @@ pub(crate) async fn tool_run_lint(args: &Value, ctx: &AppContext) -> Result Result { + let project_root = ctx.services.agents.get_project_root(&ctx.state)?; + + let working_dir = match args.get("worktree_path").and_then(|v| v.as_str()) { + Some(wt) => validate_working_dir(wt, ctx)?, + None => project_root + .canonicalize() + .map_err(|e| format!("Cannot canonicalize project root: {e}"))?, + }; + + let script_path = working_dir.join("script").join("check"); + if !script_path.exists() { + return Err(format!( + "script/check not found: {}. Create script/check (e.g. `cargo check --tests --workspace`) to enable fast compile feedback.", + script_path.display() + )); + } + + let result = tokio::task::spawn_blocking({ + let script = script_path.clone(); + let dir = working_dir.clone(); + move || { + std::process::Command::new("bash") + .arg(&script) + .current_dir(&dir) + .output() + } + }) + .await + .map_err(|e| format!("Task join error: {e}"))? + .map_err(|e| format!("Failed to spawn script/check: {e}"))?; + + let stdout = String::from_utf8_lossy(&result.stdout); + let stderr = String::from_utf8_lossy(&result.stderr); + // No truncation: agents need the full compiler output to diagnose errors. + let output = format!("{stdout}{stderr}"); + let exit_code = result.status.code().unwrap_or(-1); + + serde_json::to_string_pretty(&json!({ + "passed": result.status.success(), + "exit_code": exit_code, + "output": output, + })) + .map_err(|e| format!("Serialization error: {e}")) +} + #[cfg(test)] mod tests { use super::*; @@ -551,6 +606,76 @@ mod tests { assert_eq!(parsed["exit_code"], 1); } + // ── tool_run_check ──────────────────────────────────────────────── + + #[tokio::test] + async fn tool_run_check_missing_script_returns_error() { + let tmp = tempfile::tempdir().unwrap(); + let ctx = test_ctx(tmp.path()); + let result = tool_run_check(&json!({}), &ctx).await; + assert!(result.is_err()); + let err = result.unwrap_err(); + assert!( + err.contains("script/check"), + "error should name script/check: {err}" + ); + assert!( + err.contains("not found"), + "error should say not found: {err}" + ); + } + + #[tokio::test] + async fn tool_run_check_returns_full_output_on_nonzero_exit() { + let tmp = tempfile::tempdir().unwrap(); + let script_dir = tmp.path().join("script"); + std::fs::create_dir_all(&script_dir).unwrap(); + let script_path = script_dir.join("check"); + // Script that emits 150 lines (exceeds the 100-line truncation limit used + // by run_build/run_lint) and exits non-zero to verify no truncation occurs. + std::fs::write( + &script_path, + "#!/usr/bin/env bash\nfor i in $(seq 1 150); do echo \"error[$i]: compile error on line $i\"; done\nexit 1\n", + ) + .unwrap(); + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + std::fs::set_permissions(&script_path, std::fs::Permissions::from_mode(0o755)).unwrap(); + } + + let ctx = test_ctx(tmp.path()); + let result = tool_run_check(&json!({}), &ctx).await.unwrap(); + let parsed: serde_json::Value = serde_json::from_str(&result).unwrap(); + + assert_eq!(parsed["passed"], false); + assert_eq!(parsed["exit_code"], 1); + let output = parsed["output"].as_str().unwrap(); + // All 150 lines must be present — no truncation. + assert!(output.contains("error[1]"), "should contain first line"); + assert!(output.contains("error[150]"), "should contain last line"); + assert!(!output.contains("omitted"), "must not truncate output"); + } + + #[tokio::test] + async fn tool_run_check_passes_when_script_exits_zero() { + let tmp = tempfile::tempdir().unwrap(); + let script_dir = tmp.path().join("script"); + std::fs::create_dir_all(&script_dir).unwrap(); + let script_path = script_dir.join("check"); + std::fs::write(&script_path, "#!/usr/bin/env bash\nexit 0\n").unwrap(); + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + std::fs::set_permissions(&script_path, std::fs::Permissions::from_mode(0o755)).unwrap(); + } + let ctx = test_ctx(tmp.path()); + let result = tool_run_check(&json!({}), &ctx).await.unwrap(); + let parsed: serde_json::Value = serde_json::from_str(&result).unwrap(); + assert_eq!(parsed["passed"], true); + assert_eq!(parsed["exit_code"], 0); + } + // ── truncate_output ─────────────────────────────────────────────── #[test] diff --git a/server/src/http/mcp/tools_list/mod.rs b/server/src/http/mcp/tools_list/mod.rs index a1af0d20..1df65496 100644 --- a/server/src/http/mcp/tools_list/mod.rs +++ b/server/src/http/mcp/tools_list/mod.rs @@ -87,7 +87,8 @@ mod tests { assert!(names.contains(&"get_version")); assert!(names.contains(&"remove_criterion")); assert!(names.contains(&"mesh_status")); - assert_eq!(tools.len(), 67); + assert!(names.contains(&"run_check")); + assert_eq!(tools.len(), 68); } #[test] diff --git a/server/src/http/mcp/tools_list/system_tools.rs b/server/src/http/mcp/tools_list/system_tools.rs index 59b0b733..7e0f09d5 100644 --- a/server/src/http/mcp/tools_list/system_tools.rs +++ b/server/src/http/mcp/tools_list/system_tools.rs @@ -151,6 +151,20 @@ pub(super) fn system_tools() -> Vec { "required": [] } }), + json!({ + "name": "run_check", + "description": "Run script/check (cargo check --tests --workspace) in the agent's worktree for fast compile-only feedback. Returns the full, untruncated output so every compiler diagnostic is visible. Much faster than run_tests — use this while iterating to catch compile errors; call run_tests only to validate the full pipeline before committing.", + "inputSchema": { + "type": "object", + "properties": { + "worktree_path": { + "type": "string", + "description": "Optional absolute path to a worktree to run the check in. Must be inside .huskies/worktrees/. Defaults to the project root." + } + }, + "required": [] + } + }), json!({ "name": "git_status", "description": "Return the working tree status of an agent's worktree (staged, unstaged, and untracked files). The worktree_path must be inside .huskies/worktrees/. Push and remote operations are not available.",