basic project model
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use g3_config::Config;
|
use g3_config::Config;
|
||||||
use g3_core::Agent;
|
use g3_core::{Agent, project::Project};
|
||||||
use rustyline::error::ReadlineError;
|
use rustyline::error::ReadlineError;
|
||||||
use rustyline::DefaultEditor;
|
use rustyline::DefaultEditor;
|
||||||
use std::fs::OpenOptions;
|
use std::fs::OpenOptions;
|
||||||
@@ -33,6 +33,10 @@ pub struct Cli {
|
|||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
pub config: Option<String>,
|
pub config: Option<String>,
|
||||||
|
|
||||||
|
/// Workspace directory (defaults to current directory)
|
||||||
|
#[arg(short, long)]
|
||||||
|
pub workspace: Option<PathBuf>,
|
||||||
|
|
||||||
/// Task to execute (if provided, runs in single-shot mode instead of interactive)
|
/// Task to execute (if provided, runs in single-shot mode instead of interactive)
|
||||||
pub task: Option<String>,
|
pub task: Option<String>,
|
||||||
|
|
||||||
@@ -77,6 +81,30 @@ pub async fn run() -> Result<()> {
|
|||||||
|
|
||||||
info!("Starting G3 AI Coding Agent");
|
info!("Starting G3 AI Coding Agent");
|
||||||
|
|
||||||
|
// Set up workspace directory
|
||||||
|
let workspace_dir = if let Some(ws) = cli.workspace {
|
||||||
|
ws
|
||||||
|
} else if cli.autonomous {
|
||||||
|
// For autonomous mode, use G3_WORKSPACE env var or default
|
||||||
|
setup_workspace_directory()?
|
||||||
|
} else {
|
||||||
|
// Default to current directory for interactive/single-shot mode
|
||||||
|
std::env::current_dir()?
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create project model
|
||||||
|
let project = if cli.autonomous {
|
||||||
|
Project::new_autonomous(workspace_dir.clone())?
|
||||||
|
} else {
|
||||||
|
Project::new(workspace_dir.clone())
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ensure workspace exists and enter it
|
||||||
|
project.ensure_workspace_exists()?;
|
||||||
|
project.enter_workspace()?;
|
||||||
|
|
||||||
|
info!("Using workspace: {}", project.workspace().display());
|
||||||
|
|
||||||
// Load configuration
|
// Load configuration
|
||||||
let config = Config::load(cli.config.as_deref())?;
|
let config = Config::load(cli.config.as_deref())?;
|
||||||
|
|
||||||
@@ -87,7 +115,7 @@ pub async fn run() -> Result<()> {
|
|||||||
if cli.autonomous {
|
if cli.autonomous {
|
||||||
// Autonomous mode with coach-player feedback loop
|
// Autonomous mode with coach-player feedback loop
|
||||||
info!("Starting autonomous mode");
|
info!("Starting autonomous mode");
|
||||||
run_autonomous(agent, cli.show_prompt, cli.show_code, cli.max_turns).await?;
|
run_autonomous(agent, project, cli.show_prompt, cli.show_code, cli.max_turns).await?;
|
||||||
} else if let Some(task) = cli.task {
|
} else if let Some(task) = cli.task {
|
||||||
// Single-shot mode
|
// Single-shot mode
|
||||||
info!("Executing task: {}", task);
|
info!("Executing task: {}", task);
|
||||||
@@ -98,6 +126,7 @@ pub async fn run() -> Result<()> {
|
|||||||
} else {
|
} else {
|
||||||
// Interactive mode (default)
|
// Interactive mode (default)
|
||||||
info!("Starting interactive mode");
|
info!("Starting interactive mode");
|
||||||
|
println!("📁 Workspace: {}", project.workspace().display());
|
||||||
run_interactive(agent, cli.show_prompt, cli.show_code).await?;
|
run_interactive(agent, cli.show_prompt, cli.show_code).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -519,12 +548,10 @@ fn format_duration(duration: Duration) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run_autonomous(mut agent: Agent, show_prompt: bool, show_code: bool, max_turns: usize) -> Result<()> {
|
async fn run_autonomous(mut agent: Agent, project: Project, show_prompt: bool, show_code: bool, max_turns: usize) -> Result<()> {
|
||||||
// Set up workspace directory
|
|
||||||
let workspace_dir = setup_workspace_directory()?;
|
|
||||||
|
|
||||||
// Set up logging
|
// Set up logging
|
||||||
let logger = AutonomousLogger::new(&workspace_dir)?;
|
project.ensure_logs_dir()?;
|
||||||
|
let logger = AutonomousLogger::new(&project.logs_dir())?;
|
||||||
|
|
||||||
// Initialize session metrics
|
// Initialize session metrics
|
||||||
let mut session_metrics = SessionMetrics::new();
|
let mut session_metrics = SessionMetrics::new();
|
||||||
@@ -533,31 +560,27 @@ async fn run_autonomous(mut agent: Agent, show_prompt: bool, show_code: bool, ma
|
|||||||
logger.log(&format!("🤖 G3 AI Coding Agent - Autonomous Mode"));
|
logger.log(&format!("🤖 G3 AI Coding Agent - Autonomous Mode"));
|
||||||
logger.log(&format!(
|
logger.log(&format!(
|
||||||
"📁 Using workspace directory: {}",
|
"📁 Using workspace directory: {}",
|
||||||
workspace_dir.display()
|
project.workspace().display()
|
||||||
));
|
));
|
||||||
|
logger.log(&format!("📂 Project: {}", project.name));
|
||||||
// Change to workspace directory
|
|
||||||
std::env::set_current_dir(&workspace_dir)?;
|
|
||||||
logger.log("📂 Changed to workspace directory");
|
|
||||||
|
|
||||||
logger.log("🎯 Looking for requirements.md in workspace directory...");
|
logger.log("🎯 Looking for requirements.md in workspace directory...");
|
||||||
|
|
||||||
// Check if requirements.md exists
|
// Check if requirements exist
|
||||||
let requirements_path = workspace_dir.join("requirements.md");
|
if !project.has_requirements() {
|
||||||
if !requirements_path.exists() {
|
|
||||||
logger.log("❌ Error: requirements.md not found in workspace directory");
|
logger.log("❌ Error: requirements.md not found in workspace directory");
|
||||||
logger.log(&format!(
|
logger.log(&format!(
|
||||||
" Please create a requirements.md file with your project requirements at:"
|
" Please create a requirements.md file with your project requirements at:"
|
||||||
));
|
));
|
||||||
logger.log(&format!(" {}", requirements_path.display()));
|
logger.log(&format!(" {}/requirements.md", project.workspace().display()));
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read requirements.md
|
// Read requirements
|
||||||
let requirements = match std::fs::read_to_string(&requirements_path) {
|
let requirements = match project.read_requirements()? {
|
||||||
Ok(content) => content,
|
Some(content) => content,
|
||||||
Err(e) => {
|
None => {
|
||||||
logger.log(&format!("❌ Error reading requirements.md: {}", e));
|
logger.log("❌ Error: Could not read requirements.md");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -569,12 +592,11 @@ async fn run_autonomous(mut agent: Agent, show_prompt: bool, show_code: bool, ma
|
|||||||
));
|
));
|
||||||
|
|
||||||
// Check if there are existing project files (skip first player turn if so)
|
// Check if there are existing project files (skip first player turn if so)
|
||||||
let has_existing_files = check_existing_project_files(&workspace_dir, &logger)?;
|
let has_existing_files = check_existing_project_files(project.workspace(), &logger)?;
|
||||||
|
|
||||||
logger.log("🔄 Starting coach-player feedback loop...");
|
logger.log("🔄 Starting coach-player feedback loop...");
|
||||||
logger.log("");
|
logger.log("");
|
||||||
|
|
||||||
const MAX_TURNS: usize = 5;
|
|
||||||
let mut turn = 1;
|
let mut turn = 1;
|
||||||
let mut coach_feedback = String::new();
|
let mut coach_feedback = String::new();
|
||||||
let mut skip_player_turn = has_existing_files;
|
let mut skip_player_turn = has_existing_files;
|
||||||
@@ -656,12 +678,11 @@ async fn run_autonomous(mut agent: Agent, show_prompt: bool, show_code: bool, ma
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create a new agent instance for coach mode to ensure fresh context
|
// Create a new agent instance for coach mode to ensure fresh context
|
||||||
// Make sure the coach agent also operates in the workspace directory
|
|
||||||
let config = g3_config::Config::load(None)?;
|
let config = g3_config::Config::load(None)?;
|
||||||
let mut coach_agent = Agent::new(config).await?;
|
let mut coach_agent = Agent::new(config).await?;
|
||||||
|
|
||||||
// Ensure coach agent is also in the workspace directory
|
// Ensure coach agent is also in the workspace directory
|
||||||
std::env::set_current_dir(&workspace_dir)?;
|
project.enter_workspace()?;
|
||||||
|
|
||||||
logger.log_section(&format!("TURN {}/{} - COACH MODE", turn, max_turns));
|
logger.log_section(&format!("TURN {}/{} - COACH MODE", turn, max_turns));
|
||||||
|
|
||||||
@@ -784,7 +805,7 @@ Keep your response concise and focused on actionable items.",
|
|||||||
/// Check if there are existing project files in the workspace directory
|
/// Check if there are existing project files in the workspace directory
|
||||||
/// Returns true if project files are found (excluding requirements.md and logs directory)
|
/// Returns true if project files are found (excluding requirements.md and logs directory)
|
||||||
fn check_existing_project_files(
|
fn check_existing_project_files(
|
||||||
workspace_dir: &PathBuf,
|
workspace_dir: &std::path::Path,
|
||||||
logger: &AutonomousLogger,
|
logger: &AutonomousLogger,
|
||||||
) -> Result<bool> {
|
) -> Result<bool> {
|
||||||
logger.log("🔍 Checking for existing project files...");
|
logger.log("🔍 Checking for existing project files...");
|
||||||
@@ -897,13 +918,7 @@ struct AutonomousLogger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AutonomousLogger {
|
impl AutonomousLogger {
|
||||||
fn new(workspace_dir: &PathBuf) -> Result<Self> {
|
fn new(logs_dir: &PathBuf) -> Result<Self> {
|
||||||
// Create logs subdirectory
|
|
||||||
let logs_dir = workspace_dir.join("logs");
|
|
||||||
if !logs_dir.exists() {
|
|
||||||
std::fs::create_dir_all(&logs_dir)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create log file with timestamp in logs subdirectory
|
// Create log file with timestamp in logs subdirectory
|
||||||
let timestamp = chrono::Utc::now().format("%Y%m%d_%H%M%S");
|
let timestamp = chrono::Utc::now().format("%Y%m%d_%H%M%S");
|
||||||
let log_path = logs_dir.join(format!("g3_autonomous_{}.log", timestamp));
|
let log_path = logs_dir.join(format!("g3_autonomous_{}.log", timestamp));
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
pub mod project;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use g3_config::Config;
|
use g3_config::Config;
|
||||||
use g3_execution::CodeExecutor;
|
use g3_execution::CodeExecutor;
|
||||||
|
|||||||
120
crates/g3-core/src/project.rs
Normal file
120
crates/g3-core/src/project.rs
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
/// Represents a G3 project with workspace configuration
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Project {
|
||||||
|
/// The workspace directory for the project
|
||||||
|
pub workspace_dir: PathBuf,
|
||||||
|
|
||||||
|
/// Path to the requirements document (for autonomous mode)
|
||||||
|
pub requirements_path: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// Whether the project is in autonomous mode
|
||||||
|
pub autonomous: bool,
|
||||||
|
|
||||||
|
/// Project name (derived from workspace directory name)
|
||||||
|
pub name: String,
|
||||||
|
|
||||||
|
/// Session ID for tracking
|
||||||
|
pub session_id: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Project {
|
||||||
|
/// Create a new project with the given workspace directory
|
||||||
|
pub fn new(workspace_dir: PathBuf) -> Self {
|
||||||
|
let name = workspace_dir
|
||||||
|
.file_name()
|
||||||
|
.and_then(|n| n.to_str())
|
||||||
|
.unwrap_or("unnamed")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
workspace_dir,
|
||||||
|
requirements_path: None,
|
||||||
|
autonomous: false,
|
||||||
|
name,
|
||||||
|
session_id: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a project for autonomous mode
|
||||||
|
pub fn new_autonomous(workspace_dir: PathBuf) -> Result<Self> {
|
||||||
|
let mut project = Self::new(workspace_dir.clone());
|
||||||
|
project.autonomous = true;
|
||||||
|
|
||||||
|
// Look for requirements.md in the workspace directory
|
||||||
|
let requirements_path = workspace_dir.join("requirements.md");
|
||||||
|
if requirements_path.exists() {
|
||||||
|
project.requirements_path = Some(requirements_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(project)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the workspace directory and update related paths
|
||||||
|
pub fn set_workspace(&mut self, workspace_dir: PathBuf) {
|
||||||
|
self.workspace_dir = workspace_dir.clone();
|
||||||
|
self.name = workspace_dir
|
||||||
|
.file_name()
|
||||||
|
.and_then(|n| n.to_str())
|
||||||
|
.unwrap_or("unnamed")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
// Update requirements path if in autonomous mode
|
||||||
|
if self.autonomous {
|
||||||
|
let requirements_path = workspace_dir.join("requirements.md");
|
||||||
|
if requirements_path.exists() {
|
||||||
|
self.requirements_path = Some(requirements_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the workspace directory
|
||||||
|
pub fn workspace(&self) -> &Path {
|
||||||
|
&self.workspace_dir
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if requirements file exists
|
||||||
|
pub fn has_requirements(&self) -> bool {
|
||||||
|
self.requirements_path.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read the requirements file content
|
||||||
|
pub fn read_requirements(&self) -> Result<Option<String>> {
|
||||||
|
if let Some(ref path) = self.requirements_path {
|
||||||
|
Ok(Some(std::fs::read_to_string(path)?))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create the workspace directory if it doesn't exist
|
||||||
|
pub fn ensure_workspace_exists(&self) -> Result<()> {
|
||||||
|
if !self.workspace_dir.exists() {
|
||||||
|
std::fs::create_dir_all(&self.workspace_dir)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Change to the workspace directory
|
||||||
|
pub fn enter_workspace(&self) -> Result<()> {
|
||||||
|
std::env::set_current_dir(&self.workspace_dir)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the logs directory for the project
|
||||||
|
pub fn logs_dir(&self) -> PathBuf {
|
||||||
|
self.workspace_dir.join("logs")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ensure the logs directory exists
|
||||||
|
pub fn ensure_logs_dir(&self) -> Result<()> {
|
||||||
|
let logs_dir = self.logs_dir();
|
||||||
|
if !logs_dir.exists() {
|
||||||
|
std::fs::create_dir_all(&logs_dir)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user