@@ -134,15 +134,39 @@ fn detect_stack() -> serde_json::Value {
134134 // --- Backend detection ---
135135 let mut backend = json ! ( { } ) ;
136136
137- // Rust detection
137+ // Rust detection — check repo root and one level of subdirectories
138138 let cargo_toml = repo. join ( "Cargo.toml" ) ;
139- if cargo_toml. exists ( ) {
139+ let rust_dir = if cargo_toml. exists ( ) {
140+ Some ( ( repo. clone ( ) , cargo_toml. clone ( ) ) )
141+ } else {
142+ // Check immediate subdirectories for Cargo.toml (e.g., flowctl/Cargo.toml)
143+ fs:: read_dir ( & repo) . ok ( ) . and_then ( |entries| {
144+ entries
145+ . filter_map ( Result :: ok)
146+ . filter ( |e| e. file_type ( ) . is_ok_and ( |ft| ft. is_dir ( ) ) )
147+ . find_map ( |e| {
148+ let candidate = e. path ( ) . join ( "Cargo.toml" ) ;
149+ if candidate. exists ( ) {
150+ Some ( ( e. path ( ) , candidate) )
151+ } else {
152+ None
153+ }
154+ } )
155+ } )
156+ } ;
157+ if let Some ( ( rust_root, cargo_path) ) = rust_dir {
140158 backend[ "language" ] = json ! ( "rust" ) ;
141- backend[ "test" ] = json ! ( "cargo test" ) ;
142- backend[ "lint" ] = json ! ( "cargo clippy -- -D warnings" ) ;
143- backend[ "typecheck" ] = json ! ( "cargo check" ) ;
159+ // Use relative path prefix for subdirectory workspaces
160+ let prefix = if rust_root == repo {
161+ String :: new ( )
162+ } else {
163+ format ! ( "cd {} && " , rust_root. file_name( ) . unwrap_or_default( ) . to_string_lossy( ) )
164+ } ;
165+ backend[ "test" ] = json ! ( format!( "{prefix}cargo test --all" ) ) ;
166+ backend[ "lint" ] = json ! ( format!( "{prefix}cargo clippy --all-targets -- -D warnings" ) ) ;
167+ backend[ "typecheck" ] = json ! ( format!( "{prefix}cargo check" ) ) ;
144168
145- if let Ok ( content) = fs:: read_to_string ( & cargo_toml ) {
169+ if let Ok ( content) = fs:: read_to_string ( & cargo_path ) {
146170 if content. contains ( "actix" ) {
147171 backend[ "framework" ] = json ! ( "actix" ) ;
148172 } else if content. contains ( "axum" ) {
0 commit comments