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:
133
crates/domain/src/profile.rs
Normal file
133
crates/domain/src/profile.rs
Normal file
@ -0,0 +1,133 @@
|
||||
//! AI runtime profile and its context-injection strategy.
|
||||
//!
|
||||
//! A profile is *declarative configuration* (see CONTEXT.md §9): adding an AI
|
||||
//! means adding data, not code (Open/Closed). The [`crate::ports::AgentRuntime`]
|
||||
//! port is parameterised by an [`AgentProfile`].
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::error::DomainError;
|
||||
use crate::ids::ProfileId;
|
||||
|
||||
/// Strategy for injecting an agent's `.md` context into the launched CLI.
|
||||
///
|
||||
/// Invariants:
|
||||
/// - `ConventionFile.target` is a relative file name without `..` and not absolute,
|
||||
/// - `Env.var` is a valid environment-variable identifier,
|
||||
/// - `Flag.flag` is non-empty.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase", tag = "strategy")]
|
||||
pub enum ContextInjection {
|
||||
/// Write/symlink the `.md` to a conventional file (e.g. `CLAUDE.md`).
|
||||
ConventionFile {
|
||||
/// Relative target file name.
|
||||
target: String,
|
||||
},
|
||||
/// Pass the context file path through a CLI flag.
|
||||
Flag {
|
||||
/// The flag template (e.g. `--context-file {path}` or `-f`).
|
||||
flag: String,
|
||||
},
|
||||
/// Pipe the Markdown content on stdin.
|
||||
Stdin,
|
||||
/// Pass the context via an environment variable.
|
||||
Env {
|
||||
/// Environment variable name.
|
||||
var: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl ContextInjection {
|
||||
/// Validated `ConventionFile` constructor.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns [`DomainError::PathNotRelativeSafe`] if `target` is absolute or
|
||||
/// contains `..`.
|
||||
pub fn convention_file(target: impl Into<String>) -> Result<Self, DomainError> {
|
||||
let target = target.into();
|
||||
crate::validation::relative_safe(&target)?;
|
||||
Ok(Self::ConventionFile { target })
|
||||
}
|
||||
|
||||
/// Validated `Flag` constructor.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns [`DomainError::EmptyField`] if `flag` is empty.
|
||||
pub fn flag(flag: impl Into<String>) -> Result<Self, DomainError> {
|
||||
let flag = flag.into();
|
||||
crate::validation::non_empty(&flag, "contextInjection.flag")?;
|
||||
Ok(Self::Flag { flag })
|
||||
}
|
||||
|
||||
/// `Stdin` constructor (no validation needed).
|
||||
#[must_use]
|
||||
pub const fn stdin() -> Self {
|
||||
Self::Stdin
|
||||
}
|
||||
|
||||
/// Validated `Env` constructor.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns [`DomainError::InvalidEnvVar`] if `var` is not a valid identifier.
|
||||
pub fn env(var: impl Into<String>) -> Result<Self, DomainError> {
|
||||
let var = var.into();
|
||||
crate::validation::valid_env_var(&var)?;
|
||||
Ok(Self::Env { var })
|
||||
}
|
||||
}
|
||||
|
||||
/// Declarative runtime configuration for one AI CLI.
|
||||
///
|
||||
/// Invariants:
|
||||
/// - `name` and `command` non-empty,
|
||||
/// - `context_injection` is itself valid (guaranteed by its constructors).
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AgentProfile {
|
||||
/// Stable identifier.
|
||||
pub id: ProfileId,
|
||||
/// Display name.
|
||||
pub name: String,
|
||||
/// Launch command (e.g. `claude`, `codex`, `gemini`, `aider`).
|
||||
pub command: String,
|
||||
/// Static arguments.
|
||||
pub args: Vec<String>,
|
||||
/// Context-injection strategy.
|
||||
pub context_injection: ContextInjection,
|
||||
/// Optional detection command (e.g. `claude --version`).
|
||||
pub detect: Option<String>,
|
||||
/// Working-directory template (e.g. `"{projectRoot}"`).
|
||||
pub cwd_template: String,
|
||||
}
|
||||
|
||||
impl AgentProfile {
|
||||
/// Builds a validated profile.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns [`DomainError::EmptyField`] if `name` or `command` is empty.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
id: ProfileId,
|
||||
name: impl Into<String>,
|
||||
command: impl Into<String>,
|
||||
args: Vec<String>,
|
||||
context_injection: ContextInjection,
|
||||
detect: Option<String>,
|
||||
cwd_template: impl Into<String>,
|
||||
) -> Result<Self, DomainError> {
|
||||
let name = name.into();
|
||||
let command = command.into();
|
||||
let cwd_template = cwd_template.into();
|
||||
crate::validation::non_empty(&name, "profile.name")?;
|
||||
crate::validation::non_empty(&command, "profile.command")?;
|
||||
Ok(Self {
|
||||
id,
|
||||
name,
|
||||
command,
|
||||
args,
|
||||
context_injection,
|
||||
detect,
|
||||
cwd_template,
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user