Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <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 [<path>]` - 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.
Expand Down
11 changes: 3 additions & 8 deletions src/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
12 changes: 9 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ enum Commands {
/// Target directory (defaults to current directory)
#[arg(long)]
path: Option<PathBuf>,
/// 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,
},
Expand Down Expand Up @@ -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,
Expand Down
29 changes: 21 additions & 8 deletions tests/init_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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");
Expand Down