Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 60 additions & 8 deletions crates/builderbot-actions/src/detector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ The response must be a JSON array of action objects. Each action object must hav
- command: string (exact shell command to run, e.g., "npm test", "just build")
- actionType: string (one of: "prerun", "run", "build", "format", "check", "test", "cleanUp")
- autoCommit: boolean (true if action modifies files and should auto-commit)
- source: string (which file this was detected from, e.g., "package.json", "justfile")
- source: string (which file this was detected from, e.g., "package.json", "justfile", "subdir/justfile")

Action type guidelines:
- "prerun": Commands that should run automatically on worktree creation (like "npm install", "yarn", "pnpm install", "lefthook install")
Expand All @@ -55,6 +55,13 @@ Special instructions for lefthook:
- If lefthook.yml is present in the project, ALWAYS include "lefthook install" as a prerun action
- This ensures git hooks are properly installed in each new worktree

Special instructions for subdirectory build files:
- If justfile/Justfile/Makefile/makefile files are found in subdirectories, detect actions from them
- For commands from subdirectory build files, prefix the command with "cd <subpath> && "
- Example: if "staged/justfile" contains a "dev" target, the command should be "cd staged && just dev"
- Include the subdirectory path in the source field (e.g., "source": "staged/justfile")
- This allows running commands in the correct directory context

Action ordering (list most important first):
- Primary dev commands should come first (like "dev", "start")
- Commonly used commands next (like "test", "build", "format")
Expand Down Expand Up @@ -100,6 +107,13 @@ Return ONLY a JSON array with detected actions. Example (ordered by importance):
"autoCommit": false,
"source": "package.json"
},
{
"name": "Subproject Dev",
"command": "cd staged && just dev",
"actionType": "run",
"autoCommit": false,
"source": "staged/justfile"
},
{
"name": "Test",
"command": "npm test",
Expand All @@ -114,6 +128,13 @@ Return ONLY a JSON array with detected actions. Example (ordered by importance):
"autoCommit": false,
"source": "package.json"
},
{
"name": "Subproject Format",
"command": "cd app && make fmt",
"actionType": "format",
"autoCommit": true,
"source": "app/Makefile"
},
{
"name": "Format",
"command": "prettier --write .",
Expand Down Expand Up @@ -164,9 +185,25 @@ impl ActionDetector {
}
}

/// Collect a list of files in the directory
/// Collect a list of files in the directory (recursively up to 2 levels)
fn collect_file_list(dir: &Path) -> Result<String> {
let mut files = Vec::new();
collect_file_list_recursive(dir, "", &mut files, 0, 2)?;
files.sort();
Ok(files.join("\n"))
}

/// Recursively collect file listings with indentation
fn collect_file_list_recursive(
dir: &Path,
prefix: &str,
files: &mut Vec<String>,
depth: usize,
max_depth: usize,
) -> Result<()> {
if depth >= max_depth {
return Ok(());
}

if let Ok(entries) = std::fs::read_dir(dir) {
for entry in entries.flatten() {
Expand All @@ -175,24 +212,36 @@ fn collect_file_list(dir: &Path) -> Result<String> {
let name_str = name.to_string_lossy();

// Skip hidden files and common directories
if name_str.starts_with('.') || name_str == "node_modules" || name_str == "target" {
if name_str.starts_with('.')
|| name_str == "node_modules"
|| name_str == "target"
|| name_str == "dist"
|| name_str == "build"
{
continue;
}

let full_path = if prefix.is_empty() {
name_str.to_string()
} else {
format!("{}/{}", prefix, name_str)
};

if file_type.is_file() {
files.push(name_str.to_string());
files.push(full_path);
} else if file_type.is_dir() {
files.push(format!("{}/", name_str));
files.push(format!("{}/", full_path));
// Recurse into subdirectory
collect_file_list_recursive(&entry.path(), &full_path, files, depth + 1, max_depth)?;
}
}
}
}

files.sort();
Ok(files.join("\n"))
Ok(())
}

/// Collect contents of relevant build/config files
/// Collect contents of relevant build/config files from root directory only
fn collect_relevant_files(dir: &Path) -> Result<String> {
let relevant_files = [
"package.json",
Expand All @@ -214,6 +263,8 @@ fn collect_relevant_files(dir: &Path) -> Result<String> {

let mut contents = Vec::new();

// Collect files from root directory only
// The AI agent will be instructed to search subdirectories if needed
for file_name in &relevant_files {
let file_path = dir.join(file_name);
if file_path.exists() {
Expand All @@ -236,6 +287,7 @@ fn collect_relevant_files(dir: &Path) -> Result<String> {
}
}


/// Parse the AI response and extract suggested actions
fn parse_ai_response(response: &str) -> Result<Vec<SuggestedAction>> {
// Try to extract JSON from the response
Expand Down