This commit is contained in:
couscous
2025-06-25 09:27:29 +01:00
parent 1666ed6dca
commit 4bd9f51b98
21 changed files with 136 additions and 63 deletions

View File

@@ -1,5 +1,5 @@
use std::collections::HashMap;
use std::sync::Arc;
use std::{collections::HashMap, sync::Arc};
use tokio::sync::Mutex;
use uuid::Uuid;

View File

@@ -1,4 +1,5 @@
use std::{env, fs, path::Path};
use ts_rs::TS; // in [build-dependencies]
fn generate_constants() -> String {

View File

@@ -1,12 +1,14 @@
use git2::Repository;
use uuid::Uuid;
use crate::app_state::AppState;
use crate::models::{
execution_process::{ExecutionProcess, ExecutionProcessStatus, ExecutionProcessType},
task::{Task, TaskStatus},
task_attempt::{TaskAttempt, TaskAttemptStatus},
task_attempt_activity::{CreateTaskAttemptActivity, TaskAttemptActivity},
use crate::{
app_state::AppState,
models::{
execution_process::{ExecutionProcess, ExecutionProcessStatus, ExecutionProcessType},
task::{Task, TaskStatus},
task_attempt::{TaskAttempt, TaskAttemptStatus},
task_attempt_activity::{CreateTaskAttemptActivity, TaskAttemptActivity},
},
};
/// Commit any unstaged changes in the worktree after execution completion

View File

@@ -1,7 +1,9 @@
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use tokio::io::{AsyncBufReadExt, BufReader};
use tokio::process::Child;
use tokio::{
io::{AsyncBufReadExt, BufReader},
process::Child,
};
use ts_rs::TS;
use uuid::Uuid;
@@ -156,8 +158,7 @@ pub async fn stream_output_to_db(
execution_process_id: Uuid,
is_stdout: bool,
) {
use crate::models::execution_process::ExecutionProcess;
use crate::models::executor_session::ExecutorSession;
use crate::models::{execution_process::ExecutionProcess, executor_session::ExecutorSession};
let mut reader = BufReader::new(output);
let mut line = String::new();

View File

@@ -2,8 +2,10 @@ use async_trait::async_trait;
use tokio::process::Child;
use uuid::Uuid;
use crate::executor::{Executor, ExecutorError};
use crate::models::task::Task;
use crate::{
executor::{Executor, ExecutorError},
models::task::Task,
};
/// An executor that uses Amp to process tasks
pub struct AmpExecutor;
@@ -28,6 +30,7 @@ impl Executor for AmpExecutor {
.ok_or(ExecutorError::TaskNotFound)?;
use std::process::Stdio;
use tokio::{io::AsyncWriteExt, process::Command};
let prompt = format!(
@@ -69,6 +72,7 @@ impl Executor for AmpFollowupExecutor {
worktree_path: &str,
) -> Result<Child, ExecutorError> {
use std::process::Stdio;
use tokio::{io::AsyncWriteExt, process::Command};
let mut child = Command::new("npx")

View File

@@ -2,8 +2,10 @@ use async_trait::async_trait;
use tokio::process::{Child, Command};
use uuid::Uuid;
use crate::executor::{Executor, ExecutorError};
use crate::models::task::Task;
use crate::{
executor::{Executor, ExecutorError},
models::task::Task,
};
/// An executor that uses Claude CLI to process tasks
pub struct ClaudeExecutor;

View File

@@ -2,9 +2,10 @@ use async_trait::async_trait;
use tokio::process::{Child, Command};
use uuid::Uuid;
use crate::executor::{Executor, ExecutorError};
use crate::models::project::Project;
use crate::models::task::Task;
use crate::{
executor::{Executor, ExecutorError},
models::{project::Project, task::Task},
};
/// Executor for running project dev server scripts
pub struct DevServerExecutor {

View File

@@ -2,8 +2,10 @@ use async_trait::async_trait;
use tokio::process::{Child, Command};
use uuid::Uuid;
use crate::executor::{Executor, ExecutorError};
use crate::models::task::Task;
use crate::{
executor::{Executor, ExecutorError},
models::task::Task,
};
/// A dummy executor that echoes the task title and description
pub struct EchoExecutor;

View File

@@ -2,9 +2,10 @@ use async_trait::async_trait;
use tokio::process::{Child, Command};
use uuid::Uuid;
use crate::executor::{Executor, ExecutorError};
use crate::models::project::Project;
use crate::models::task::Task;
use crate::{
executor::{Executor, ExecutorError},
models::{project::Project, task::Task},
};
/// Executor for running project setup scripts
pub struct SetupScriptExecutor {

View File

@@ -1,3 +1,5 @@
use std::{str::FromStr, sync::Arc};
use axum::{
body::Body,
extract::Extension,
@@ -8,8 +10,6 @@ use axum::{
};
use rust_embed::RustEmbed;
use sqlx::{sqlite::SqliteConnectOptions, SqlitePool};
use std::str::FromStr;
use std::sync::Arc;
use tokio::sync::RwLock;
use tower_http::cors::CorsLayer;
@@ -87,6 +87,7 @@ async fn serve_sound_file(
axum::extract::Path(filename): axum::extract::Path<String>,
) -> impl IntoResponse {
use std::path::Path;
use tokio::fs;
// Validate filename contains only expected sound files

View File

@@ -1,8 +1,10 @@
use crate::executor::ExecutorConfig;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use ts_rs::TS;
use crate::executor::ExecutorConfig;
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct Config {

View File

@@ -1,15 +1,16 @@
use std::path::Path;
use chrono::{DateTime, Utc};
use git2::build::CheckoutBuilder;
use git2::{Error as GitError, MergeOptions, Oid, RebaseOptions, Repository};
use git2::{
build::CheckoutBuilder, Error as GitError, MergeOptions, Oid, RebaseOptions, Repository,
};
use serde::{Deserialize, Serialize};
use sqlx::{FromRow, SqlitePool, Type};
use std::path::Path;
use tracing::{debug, error, info};
use ts_rs::TS;
use uuid::Uuid;
use super::project::Project;
use super::task::Task;
use super::{project::Project, task::Task};
use crate::executor::Executor;
#[derive(Debug)]
@@ -542,8 +543,10 @@ impl TaskAttempt {
project_id: Uuid,
prompt: &str,
) -> Result<(), TaskAttemptError> {
use crate::models::executor_session::ExecutorSession;
use crate::models::task::{Task, TaskStatus};
use crate::models::{
executor_session::ExecutorSession,
task::{Task, TaskStatus},
};
// Update task status to indicate follow-up execution has started
Task::update_status(pool, task_id, project_id, TaskStatus::InProgress).await?;
@@ -646,10 +649,19 @@ impl TaskAttempt {
) {
// Extract follow-up prompt if this is a follow-up execution
let followup_prompt = match &executor_type {
crate::executor::ExecutorType::FollowUpCodingAgent { prompt, .. } => Some(prompt.clone()),
crate::executor::ExecutorType::FollowUpCodingAgent { prompt, .. } => {
Some(prompt.clone())
}
_ => None,
};
Self::create_executor_session_record(pool, attempt_id, task_id, process_id, followup_prompt).await?;
Self::create_executor_session_record(
pool,
attempt_id,
task_id,
process_id,
followup_prompt,
)
.await?;
}
// Create activity record (skip for dev servers as they run in parallel)
@@ -960,7 +972,11 @@ impl TaskAttempt {
let diff = if parents.len() >= 2 {
let base_tree = parents[0].tree()?; // Main branch before merge
let merged_tree = parents[1].tree()?; // The branch that was merged
main_repo.diff_tree_to_tree(Some(&base_tree), Some(&merged_tree), Some(&mut diff_opts))?
main_repo.diff_tree_to_tree(
Some(&base_tree),
Some(&merged_tree),
Some(&mut diff_opts),
)?
} else {
// Fast-forward merge or single parent - compare merge commit with its parent
let base_tree = if !parents.is_empty() {
@@ -970,7 +986,11 @@ impl TaskAttempt {
main_repo.find_tree(git2::Oid::zero())?
};
let merged_tree = merge_commit.tree()?;
main_repo.diff_tree_to_tree(Some(&base_tree), Some(&merged_tree), Some(&mut diff_opts))?
main_repo.diff_tree_to_tree(
Some(&base_tree),
Some(&merged_tree),
Some(&mut diff_opts),
)?
};
// Process each diff delta (file change)
@@ -1049,8 +1069,11 @@ impl TaskAttempt {
diff_opts.context_lines(10); // Include 10 lines of context around changes
diff_opts.interhunk_lines(0); // Don't merge hunks
let diff =
worktree_repo.diff_tree_to_tree(Some(&base_tree), Some(&current_tree), Some(&mut diff_opts))?;
let diff = worktree_repo.diff_tree_to_tree(
Some(&base_tree),
Some(&current_tree),
Some(&mut diff_opts),
)?;
// Process each diff delta (file change)
diff.foreach(
@@ -1249,8 +1272,12 @@ impl TaskAttempt {
statuses.iter().any(|entry| {
let status = entry.status();
// Check for any unstaged or staged changes
status.is_wt_modified() || status.is_wt_new() || status.is_wt_deleted() ||
status.is_index_modified() || status.is_index_new() || status.is_index_deleted()
status.is_wt_modified()
|| status.is_wt_new()
|| status.is_wt_deleted()
|| status.is_index_modified()
|| status.is_index_new()
|| status.is_index_deleted()
})
};

View File

@@ -1,20 +1,23 @@
use std::sync::Arc;
use axum::{
extract::Extension,
response::Json as ResponseJson,
routing::{get, post},
Json, Router,
};
use std::sync::Arc;
use tokio::sync::RwLock;
use crate::models::{
config::{Config, EditorConstants, SoundConstants},
ApiResponse,
};
use crate::utils;
use serde::{Deserialize, Serialize};
use tokio::sync::RwLock;
use ts_rs::TS;
use crate::{
models::{
config::{Config, EditorConstants, SoundConstants},
ApiResponse,
},
utils,
};
pub fn config_router() -> Router {
Router::new()
.route("/config", get(get_config))

View File

@@ -1,9 +1,12 @@
use std::{
fs,
path::{Path, PathBuf},
};
use axum::{
extract::Query, http::StatusCode, response::Json as ResponseJson, routing::get, Router,
};
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::{Path, PathBuf};
use ts_rs::TS;
use crate::models::ApiResponse;

View File

@@ -1,6 +1,7 @@
use crate::models::ApiResponse;
use axum::response::Json;
use crate::models::ApiResponse;
pub async fn health_check() -> Json<ApiResponse<String>> {
Json(ApiResponse {
success: true,

View File

@@ -1,3 +1,5 @@
use std::collections::HashMap;
use axum::{
extract::{Extension, Path, Query},
http::StatusCode,
@@ -6,11 +8,12 @@ use axum::{
Json, Router,
};
use sqlx::SqlitePool;
use std::collections::HashMap;
use uuid::Uuid;
use crate::models::{
project::{CreateProject, Project, ProjectWithBranch, SearchMatchType, SearchResult, UpdateProject},
project::{
CreateProject, Project, ProjectWithBranch, SearchMatchType, SearchResult, UpdateProject,
},
ApiResponse,
};
@@ -305,9 +308,10 @@ async fn search_files_in_repo(
repo_path: &str,
query: &str,
) -> Result<Vec<SearchResult>, Box<dyn std::error::Error + Send + Sync>> {
use ignore::WalkBuilder;
use std::path::Path;
use ignore::WalkBuilder;
let repo_path = Path::new(repo_path);
if !repo_path.exists() {

View File

@@ -1,3 +1,5 @@
use std::sync::Arc;
use axum::{
extract::{Extension, Path, Query},
http::StatusCode,
@@ -6,7 +8,6 @@ use axum::{
Json, Router,
};
use sqlx::SqlitePool;
use std::sync::Arc;
use tokio::sync::RwLock;
use uuid::Uuid;
@@ -17,7 +18,9 @@ use crate::models::{
BranchStatus, CreateFollowUpAttempt, CreateTaskAttempt, TaskAttempt, TaskAttemptStatus,
WorktreeDiff,
},
task_attempt_activity::{CreateTaskAttemptActivity, TaskAttemptActivity, TaskAttemptActivityWithPrompt},
task_attempt_activity::{
CreateTaskAttemptActivity, TaskAttemptActivity, TaskAttemptActivityWithPrompt,
},
ApiResponse,
};

View File

@@ -1,6 +1,7 @@
use directories::ProjectDirs;
use std::env;
use directories::ProjectDirs;
pub fn asset_dir() -> std::path::PathBuf {
let proj = if cfg!(debug_assertions) {
ProjectDirs::from("ai", "bloop-dev", env!("CARGO_PKG_NAME"))

11
rust-toolchain.toml Normal file
View File

@@ -0,0 +1,11 @@
[toolchain]
channel = "nightly-2025-05-18"
components = [
"rustfmt",
"rustc",
"rust-analyzer",
"rust-src",
"rust-std",
"cargo",
]
profile = "default"

3
rustfmt.toml Normal file
View File

@@ -0,0 +1,3 @@
reorder_imports = true
group_imports = "StdExternalCrate"
imports_granularity = "Crate"