This change removes the legacy logs/ directory and consolidates all session data, error logs, and discovery files under the .g3/ directory. New directory structure: - .g3/sessions/<session_id>/session.json - session logs - .g3/errors/ - error logs (was logs/errors/) - .g3/background_processes/ - background process logs - .g3/discovery/ - planner discovery files (was workspace/logs/) Changes: - paths.rs: Remove get_logs_dir()/logs_dir(), add get_errors_dir(), get_background_processes_dir(), get_discovery_dir() - session.rs: Anonymous sessions now use .g3/sessions/anonymous_<ts>/ - error_handling.rs: Errors now saved to .g3/errors/ - project.rs: Remove logs_dir() and ensure_logs_dir() methods - feedback_extraction.rs: Remove logs_dir field and fallback logic - planner: Use .g3/ for workspace data and .g3/discovery/ for reports - flock.rs: Look for session metrics in .g3/sessions/ - coach_feedback.rs: Remove fallback to logs/ path - Update all tests to use new paths - Update README.md and .gitignore
131 lines
4.1 KiB
Rust
131 lines
4.1 KiB
Rust
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>,
|
|
|
|
/// Override requirements text (takes precedence over requirements_path)
|
|
pub requirements_text: Option<String>,
|
|
|
|
/// 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,
|
|
requirements_text: 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)
|
|
}
|
|
|
|
/// Create a project for autonomous mode with requirements text override
|
|
pub fn new_autonomous_with_requirements(
|
|
workspace_dir: PathBuf,
|
|
requirements_text: String,
|
|
) -> Result<Self> {
|
|
let mut project = Self::new(workspace_dir.clone());
|
|
project.autonomous = true;
|
|
project.requirements_text = Some(requirements_text);
|
|
|
|
// Don't look for requirements.md file when text is provided
|
|
// The text override takes precedence
|
|
|
|
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 {
|
|
// Has requirements if either text override is provided or requirements file exists
|
|
self.requirements_text.is_some() || self.requirements_path.is_some()
|
|
}
|
|
|
|
/// Read the requirements file content
|
|
pub fn read_requirements(&self) -> Result<Option<String>> {
|
|
// Prioritize requirements text override
|
|
if let Some(ref text) = self.requirements_text {
|
|
Ok(Some(text.clone()))
|
|
} else if let Some(ref path) = self.requirements_path {
|
|
// Fall back to reading from file
|
|
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(())
|
|
}
|
|
}
|