feat: add main features

Agents for developpement added + frontend add + backend added. Git viewer created + agent and template creator + layout and project creator
This commit is contained in:
2026-06-06 01:27:01 +02:00
parent 55b3bee2c8
commit 307ae71857
273 changed files with 48740 additions and 0 deletions

View File

@ -0,0 +1,12 @@
//! Git use cases (ARCHITECTURE §6, L8). Local Git operations orchestrated over
//! the [`domain::ports::GitPort`]: status, staging, commit, branches, checkout
//! and log. State-changing operations announce [`domain::DomainEvent::GitStateChanged`].
mod usecases;
pub use usecases::{
GitBranches, GitBranchesInput, GitBranchesOutput, GitCheckout, GitCheckoutInput, GitCommit,
GitCommitInput, GitCommitOutput, GitGraph, GitGraphInput, GitGraphOutput, GitInit, GitInitInput,
GitLog, GitLogInput, GitLogOutput, GitStage, GitStagePathInput, GitStatus, GitStatusInput,
GitStatusOutput, GitUnstage,
};

View File

@ -0,0 +1,382 @@
//! Git use cases (ARCHITECTURE §6, L8). Thin orchestration over the [`GitPort`]:
//! validate the root, call the port, and (for state-changing operations) announce
//! [`DomainEvent::GitStateChanged`] so the UI can refresh.
use std::sync::Arc;
use domain::ports::{EventBus, GitCommitInfo, GitFileStatus, GitPort, GraphCommit};
use domain::{DomainEvent, ProjectId, ProjectPath};
use crate::error::AppError;
/// Parses a raw root string into a validated [`ProjectPath`].
fn parse_root(root: &str) -> Result<ProjectPath, AppError> {
ProjectPath::new(root).map_err(|e| AppError::Invalid(e.to_string()))
}
// ---------------------------------------------------------------------------
// GitStatus
// ---------------------------------------------------------------------------
/// Input for [`GitStatus::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GitStatusInput {
/// Absolute repository root.
pub root: String,
}
/// Output of [`GitStatus::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GitStatusOutput {
/// Changed paths (staged flag per path).
pub entries: Vec<GitFileStatus>,
}
/// Reports the working-tree status of a repository.
pub struct GitStatus {
git: Arc<dyn GitPort>,
}
impl GitStatus {
/// Builds the use case from the [`GitPort`].
#[must_use]
pub fn new(git: Arc<dyn GitPort>) -> Self {
Self { git }
}
/// Returns the status.
///
/// # Errors
/// - [`AppError::Invalid`] for a non-absolute root,
/// - [`AppError::Git`] if the repo is missing or the operation fails.
pub async fn execute(&self, input: GitStatusInput) -> Result<GitStatusOutput, AppError> {
let root = parse_root(&input.root)?;
let entries = self.git.status(&root).await?;
Ok(GitStatusOutput { entries })
}
}
// ---------------------------------------------------------------------------
// GitStage / GitUnstage
// ---------------------------------------------------------------------------
/// Input for [`GitStage::execute`] / [`GitUnstage::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GitStagePathInput {
/// Absolute repository root.
pub root: String,
/// Repo-relative path to (un)stage.
pub path: String,
}
/// Stages a path (adds it to the index).
pub struct GitStage {
git: Arc<dyn GitPort>,
}
impl GitStage {
/// Builds the use case.
#[must_use]
pub fn new(git: Arc<dyn GitPort>) -> Self {
Self { git }
}
/// Stages the path.
///
/// # Errors
/// [`AppError::Invalid`] for a bad root, [`AppError::Git`] on failure.
pub async fn execute(&self, input: GitStagePathInput) -> Result<(), AppError> {
let root = parse_root(&input.root)?;
self.git.stage(&root, &input.path).await?;
Ok(())
}
}
/// Unstages a path (resets it to its HEAD state, or removes it when unborn).
pub struct GitUnstage {
git: Arc<dyn GitPort>,
}
impl GitUnstage {
/// Builds the use case.
#[must_use]
pub fn new(git: Arc<dyn GitPort>) -> Self {
Self { git }
}
/// Unstages the path.
///
/// # Errors
/// [`AppError::Invalid`] for a bad root, [`AppError::Git`] on failure.
pub async fn execute(&self, input: GitStagePathInput) -> Result<(), AppError> {
let root = parse_root(&input.root)?;
self.git.unstage(&root, &input.path).await?;
Ok(())
}
}
// ---------------------------------------------------------------------------
// GitCommit
// ---------------------------------------------------------------------------
/// Input for [`GitCommit::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GitCommitInput {
/// The project (for the emitted event).
pub project_id: ProjectId,
/// Absolute repository root.
pub root: String,
/// Commit message.
pub message: String,
}
/// Output of [`GitCommit::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GitCommitOutput {
/// The created commit.
pub commit: GitCommitInfo,
}
/// Commits the staged index, announcing [`DomainEvent::GitStateChanged`].
pub struct GitCommit {
git: Arc<dyn GitPort>,
events: Arc<dyn EventBus>,
}
impl GitCommit {
/// Builds the use case from its ports.
#[must_use]
pub fn new(git: Arc<dyn GitPort>, events: Arc<dyn EventBus>) -> Self {
Self { git, events }
}
/// Creates the commit.
///
/// # Errors
/// - [`AppError::Invalid`] for a bad root or an empty message,
/// - [`AppError::Git`] on failure.
pub async fn execute(&self, input: GitCommitInput) -> Result<GitCommitOutput, AppError> {
if input.message.trim().is_empty() {
return Err(AppError::Invalid("commit message is empty".to_owned()));
}
let root = parse_root(&input.root)?;
let commit = self.git.commit(&root, &input.message).await?;
self.events.publish(DomainEvent::GitStateChanged {
project_id: input.project_id,
});
Ok(GitCommitOutput { commit })
}
}
// ---------------------------------------------------------------------------
// GitBranches
// ---------------------------------------------------------------------------
/// Input for [`GitBranches::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GitBranchesInput {
/// Absolute repository root.
pub root: String,
}
/// Output of [`GitBranches::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GitBranchesOutput {
/// All local branches.
pub branches: Vec<String>,
/// The current branch (`None` when detached or unborn).
pub current: Option<String>,
}
/// Lists local branches and the current one.
pub struct GitBranches {
git: Arc<dyn GitPort>,
}
impl GitBranches {
/// Builds the use case.
#[must_use]
pub fn new(git: Arc<dyn GitPort>) -> Self {
Self { git }
}
/// Lists branches + current.
///
/// # Errors
/// [`AppError::Invalid`] for a bad root, [`AppError::Git`] on failure.
pub async fn execute(&self, input: GitBranchesInput) -> Result<GitBranchesOutput, AppError> {
let root = parse_root(&input.root)?;
let branches = self.git.branches(&root).await?;
let current = self.git.current_branch(&root).await?;
Ok(GitBranchesOutput { branches, current })
}
}
// ---------------------------------------------------------------------------
// GitCheckout
// ---------------------------------------------------------------------------
/// Input for [`GitCheckout::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GitCheckoutInput {
/// The project (for the emitted event).
pub project_id: ProjectId,
/// Absolute repository root.
pub root: String,
/// Branch to check out.
pub branch: String,
}
/// Checks out a branch, announcing [`DomainEvent::GitStateChanged`].
pub struct GitCheckout {
git: Arc<dyn GitPort>,
events: Arc<dyn EventBus>,
}
impl GitCheckout {
/// Builds the use case from its ports.
#[must_use]
pub fn new(git: Arc<dyn GitPort>, events: Arc<dyn EventBus>) -> Self {
Self { git, events }
}
/// Checks out the branch.
///
/// # Errors
/// [`AppError::Invalid`] for a bad root, [`AppError::Git`] on failure.
pub async fn execute(&self, input: GitCheckoutInput) -> Result<(), AppError> {
let root = parse_root(&input.root)?;
self.git.checkout(&root, &input.branch).await?;
self.events.publish(DomainEvent::GitStateChanged {
project_id: input.project_id,
});
Ok(())
}
}
// ---------------------------------------------------------------------------
// GitLog
// ---------------------------------------------------------------------------
/// Input for [`GitLog::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GitLogInput {
/// Absolute repository root.
pub root: String,
/// Maximum number of commits to return.
pub limit: usize,
}
/// Output of [`GitLog::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GitLogOutput {
/// Recent commits, newest first.
pub commits: Vec<GitCommitInfo>,
}
/// Returns the recent commit log.
pub struct GitLog {
git: Arc<dyn GitPort>,
}
impl GitLog {
/// Builds the use case.
#[must_use]
pub fn new(git: Arc<dyn GitPort>) -> Self {
Self { git }
}
/// Returns the log.
///
/// # Errors
/// [`AppError::Invalid`] for a bad root, [`AppError::Git`] on failure.
pub async fn execute(&self, input: GitLogInput) -> Result<GitLogOutput, AppError> {
let root = parse_root(&input.root)?;
let commits = self.git.log(&root, input.limit).await?;
Ok(GitLogOutput { commits })
}
}
// ---------------------------------------------------------------------------
// GitInit
// ---------------------------------------------------------------------------
/// Input for [`GitInit::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GitInitInput {
/// The project (for the emitted event).
pub project_id: ProjectId,
/// Absolute repository root.
pub root: String,
}
/// Initialises a repository at the project root, announcing
/// [`DomainEvent::GitStateChanged`].
pub struct GitInit {
git: Arc<dyn GitPort>,
events: Arc<dyn EventBus>,
}
impl GitInit {
/// Builds the use case from its ports.
#[must_use]
pub fn new(git: Arc<dyn GitPort>, events: Arc<dyn EventBus>) -> Self {
Self { git, events }
}
/// Initialises the repository.
///
/// # Errors
/// [`AppError::Invalid`] for a bad root, [`AppError::Git`] on failure.
pub async fn execute(&self, input: GitInitInput) -> Result<(), AppError> {
let root = parse_root(&input.root)?;
self.git.init(&root).await?;
self.events.publish(DomainEvent::GitStateChanged {
project_id: input.project_id,
});
Ok(())
}
}
// ---------------------------------------------------------------------------
// GitGraph
// ---------------------------------------------------------------------------
/// Input for [`GitGraph::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GitGraphInput {
/// Absolute repository root.
pub root: String,
/// Maximum number of commits to return.
pub limit: usize,
}
/// Output of [`GitGraph::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GitGraphOutput {
/// Graph commits (topological + time order), newest first.
pub commits: Vec<GraphCommit>,
}
/// Returns the full commit graph for all local branches.
pub struct GitGraph {
git: Arc<dyn GitPort>,
}
impl GitGraph {
/// Builds the use case.
#[must_use]
pub fn new(git: Arc<dyn GitPort>) -> Self {
Self { git }
}
/// Returns the graph.
///
/// # Errors
/// [`AppError::Invalid`] for a bad root, [`AppError::Git`] on failure.
pub async fn execute(&self, input: GitGraphInput) -> Result<GitGraphOutput, AppError> {
let root = parse_root(&input.root)?;
let commits = self.git.log_graph(&root, input.limit).await?;
Ok(GitGraphOutput { commits })
}
}