diff --git a/docs/cli.md b/docs/cli.md index bb272c69..b3de7970 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -8,7 +8,7 @@ Global flags (apply to all subcommands): `--verbose, -v` (enable info-level logg - `init` - Initialize a repository for AI-first agentic pipeline authoring - `--path ` - Target directory (defaults to current directory) - - `--force` - Overwrite existing agent file + - `--force` - Bypass the GitHub-remote guard (use when running inside a GitHub-hosted repository like `githubnext/ado-aw` itself) - Creates `.github/agents/ado-aw.agent.md` — a Copilot dispatcher agent that routes to specialized prompts for creating, updating, and debugging agentic pipelines - The agent auto-downloads the ado-aw compiler and handles the full lifecycle (create → compile → check) - `compile []` - Compile a markdown file to Azure DevOps pipeline YAML. If no path is given, auto-discovers and recompiles all detected agentic pipelines in the current directory. diff --git a/src/init.rs b/src/init.rs index 3037c70c..49f0ff1e 100644 --- a/src/init.rs +++ b/src/init.rs @@ -7,18 +7,13 @@ const AGENT_TEMPLATE: &str = include_str!("data/init-agent.md"); const AGENT_DIR: &str = ".github/agents"; const AGENT_FILENAME: &str = "ado-aw.agent.md"; -pub async fn run(path: Option<&std::path::Path>, force: bool) -> Result<()> { +pub async fn run(path: Option<&std::path::Path>) -> Result<()> { let base = path.map(PathBuf::from).unwrap_or_else(|| PathBuf::from(".")); let agent_dir = base.join(AGENT_DIR); let agent_path = agent_dir.join(AGENT_FILENAME); - // Check if file already exists - if agent_path.exists() && !force { - anyhow::bail!( - "{} already exists. Use --force to overwrite.", - agent_path.display() - ); - } + // `init` always (re)writes the agent file so it stays in sync with the + // currently installed compiler version. // Create directory structure tokio::fs::create_dir_all(&agent_dir) diff --git a/src/main.rs b/src/main.rs index 1b2985e3..583ecbc5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -104,7 +104,8 @@ enum Commands { /// Target directory (defaults to current directory) #[arg(long)] path: Option, - /// Overwrite existing agent file + /// Bypass the GitHub-remote guard (use when running inside a + /// GitHub-hosted repository like `githubnext/ado-aw` itself) #[arg(long)] force: bool, }, @@ -449,8 +450,13 @@ async fn main() -> Result<()> { } Commands::Init { path, force } => { let init_path = path.as_deref().unwrap_or(Path::new(".")); - ensure_non_github_remote_for_ado_aw("init", init_path).await?; - init::run(path.as_deref(), force).await?; + // `--force` bypasses the GitHub-remote guard so maintainers can + // run `ado-aw init` inside this repository (or other GitHub-hosted + // forks) for development and example regeneration. + if !force { + ensure_non_github_remote_for_ado_aw("init", init_path).await?; + } + init::run(path.as_deref()).await?; } Commands::Configure { token, diff --git a/tests/init_tests.rs b/tests/init_tests.rs index 7c2c7c4d..a55f5d4b 100644 --- a/tests/init_tests.rs +++ b/tests/init_tests.rs @@ -32,9 +32,9 @@ fn test_init_creates_agent_file() { ); } -/// Test that `init` refuses to overwrite without --force +/// Test that `init` always overwrites an existing agent file (no --force needed) #[test] -fn test_init_refuses_overwrite_without_force() { +fn test_init_overwrites_by_default() { let temp_dir = tempfile::tempdir().expect("Failed to create temp directory"); // First run should succeed @@ -44,21 +44,34 @@ fn test_init_refuses_overwrite_without_force() { .expect("Failed to run ado-aw init"); assert!(output.status.success(), "First init should succeed"); - // Second run without --force should fail + let agent_path = temp_dir.path().join(".github/agents/ado-aw.agent.md"); + + // Tamper with the file + fs::write(&agent_path, "tampered content").expect("Should write tampered content"); + + // Second run without --force should still succeed and restore the template let output = ado_aw_bin() .args(["init", "--path", temp_dir.path().to_str().unwrap()]) .output() .expect("Failed to run ado-aw init"); - assert!(!output.status.success(), "Second init without --force should fail"); + assert!( + output.status.success(), + "Second init should succeed and overwrite: {}", + String::from_utf8_lossy(&output.stderr) + ); - let stderr = String::from_utf8_lossy(&output.stderr); + let content = fs::read_to_string(&agent_path).expect("Should read agent file"); + assert!( + content.contains("ADO Agentic Pipelines Agent"), + "Default init should restore the template content" + ); assert!( - stderr.contains("already exists"), - "Error should mention file already exists: {stderr}" + !content.contains("tampered"), + "Tampered content should be overwritten" ); } -/// Test that `init --force` overwrites an existing agent file +/// Test that `init --force` also overwrites an existing agent file #[test] fn test_init_force_overwrites() { let temp_dir = tempfile::tempdir().expect("Failed to create temp directory");