Axe 3 (#52) : templates embarqués dans le binaire (web, python, rust, cli) - am init --template <stack> génère une config pré-remplie : groupe dev, profil par défaut, hooks projet (npm install / uv sync / cargo fetch) - substitution {project} {dir} au rendu ; --template list pour lister - fallback détection automatique de stack quand aucun template - config générée validée (parse YAML + validate) par les tests Axe 4 (#53) : am sessions export + rétention configurable - am sessions --export <id> [--output <fichier>] : JSON complet et reproductible (métadonnées + commandes history/<id>.jsonl + extrait de log 4 Ko + rendu lisible 'readable'), compatible archivage manuel - settings.sessions_retention_days (défaut 90) : purge au démarrage (silencieuse) et am sessions --retention <jours> - purge limitée à l'index dérivé sessions.json — le journal d'événements primaire n'est jamais touché ; EventKind::Prune journalisé (visibilité am audit) - sessions en cours (running) jamais purgées ; erreur claire si id inconnu REPL : sessions --export/--retention/--output + init --template ; help specs, tip cheat sheet, config.yaml, README, man pages régénérées. Version 0.4.7, ROADMAP cochée, 276 tests verts.
This commit is contained in:
+54
-29
@@ -1,5 +1,6 @@
|
||||
//! init: generate a local agent-manager.yaml for the current directory,
|
||||
//! based on the detected technology stack.
|
||||
//! based on the detected technology stack or an explicit template
|
||||
//! (issue #52: 'am init --template web|python|rust|cli').
|
||||
|
||||
use super::*;
|
||||
use crate::context;
|
||||
@@ -7,8 +8,9 @@ use anyhow::{anyhow, Context, Result};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// Generate a local config file in dir; refuses to overwrite unless force.
|
||||
/// Returns the written path.
|
||||
pub fn generate(dir: &Path, force: bool) -> Result<PathBuf> {
|
||||
/// `template` renders a pre-filled config for a stack ('am init --template');
|
||||
/// None falls back on the automatic stack detection. Returns the written path.
|
||||
pub fn generate(dir: &Path, force: bool, template: Option<&str>) -> Result<PathBuf> {
|
||||
let target = dir.join(crate::config::LOCAL_CONFIG_NAME);
|
||||
if target.exists() && !force {
|
||||
return Err(anyhow!(
|
||||
@@ -16,34 +18,51 @@ pub fn generate(dir: &Path, force: bool) -> Result<PathBuf> {
|
||||
target.display()
|
||||
));
|
||||
}
|
||||
let ctx = context::detect(dir);
|
||||
let agents = context::recommended_agents(&ctx.stack);
|
||||
let members = agents
|
||||
.iter()
|
||||
.map(|a| a.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
let stack = if ctx.stack.is_empty() {
|
||||
"aucune détectée".to_string()
|
||||
let project = dir
|
||||
.file_name()
|
||||
.map(|n| n.to_string_lossy().to_string())
|
||||
.unwrap_or_else(|| "project".to_string());
|
||||
let dir_str = dir.to_string_lossy().to_string();
|
||||
let content = if let Some(t) = template {
|
||||
crate::templates::render(t, &project, &dir_str)?
|
||||
} else {
|
||||
ctx.stack.join(", ")
|
||||
};
|
||||
let content = format!(
|
||||
"# Généré par 'am init' le {date}
|
||||
let ctx = context::detect(dir);
|
||||
let agents = context::recommended_agents(&ctx.stack);
|
||||
let members = agents
|
||||
.iter()
|
||||
.map(|a| a.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
let stack = if ctx.stack.is_empty() {
|
||||
"aucune détectée".to_string()
|
||||
} else {
|
||||
ctx.stack.join(", ")
|
||||
};
|
||||
format!(
|
||||
"# Généré par 'am init' le {date}
|
||||
# Pile détectée : {stack}
|
||||
version: \"1.0\"
|
||||
groups:
|
||||
dev: [{members}]
|
||||
agents: []
|
||||
",
|
||||
date = crate::installers::now_rfc3339(),
|
||||
);
|
||||
date = crate::installers::now_rfc3339(),
|
||||
)
|
||||
};
|
||||
std::fs::write(&target, content)
|
||||
.with_context(|| format!("cannot write {}", target.display()))?;
|
||||
Ok(target)
|
||||
}
|
||||
|
||||
pub fn run(app: &App, force: bool) -> Result<i32> {
|
||||
pub fn run(app: &App, force: bool, template: Option<&str>) -> Result<i32> {
|
||||
// 'am init --template list' prints the available templates.
|
||||
if template == Some("list") {
|
||||
println!("available templates:");
|
||||
for (name, desc) in crate::templates::TEMPLATES {
|
||||
println!(" {name:<8} {desc}");
|
||||
}
|
||||
return Ok(0);
|
||||
}
|
||||
if app.dry_run() {
|
||||
app.log.dry(format!(
|
||||
"would generate {} in the current directory",
|
||||
@@ -53,16 +72,22 @@ pub fn run(app: &App, force: bool) -> Result<i32> {
|
||||
}
|
||||
let cwd = std::env::current_dir().context("cannot determine the current directory")?;
|
||||
let ctx = context::detect(&cwd);
|
||||
let path = generate(&cwd, force)?;
|
||||
app.log.success(&format!(
|
||||
"wrote {} (stack: {})",
|
||||
path.display(),
|
||||
if ctx.stack.is_empty() {
|
||||
"aucune détectée".to_string()
|
||||
} else {
|
||||
ctx.stack.join(", ")
|
||||
}
|
||||
));
|
||||
let path = generate(&cwd, force, template)?;
|
||||
match template {
|
||||
Some(t) => app.log.success(&format!(
|
||||
"wrote {} (template: {t})",
|
||||
path.display()
|
||||
)),
|
||||
None => app.log.success(&format!(
|
||||
"wrote {} (stack: {})",
|
||||
path.display(),
|
||||
if ctx.stack.is_empty() {
|
||||
"aucune détectée".to_string()
|
||||
} else {
|
||||
ctx.stack.join(", ")
|
||||
}
|
||||
)),
|
||||
}
|
||||
app.log.info(&format!(
|
||||
"group 'dev' prêt : am start group:dev — ajustez ensuite la config générée"
|
||||
));
|
||||
|
||||
Reference in New Issue
Block a user