diff --git a/AGENTS.md b/AGENTS.md index f90a7a50..5d214a9a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1003,7 +1003,7 @@ Global flags (apply to all subcommands): `--verbose, -v` (enable info-level logg - 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. - - `--output, -o ` - Optional output path for generated YAML (only valid when a path is provided) + - `--output, -o ` - Optional output path for the generated YAML (only valid when a path is provided). If the path is an existing directory, the compiled YAML is written inside that directory using the default filename derived from the markdown source (e.g. `foo.md` → `/foo.lock.yml`). - `--skip-integrity` - *(debug builds only)* Omit the "Verify pipeline integrity" step from the generated pipeline. Useful during local development when the compiled output won't match a released compiler version. This flag is not available in release builds. - `--debug-pipeline` - *(debug builds only)* Include MCPG debug diagnostics in the generated pipeline: `DEBUG=*` environment variable for verbose MCPG logging, stderr streaming to log files, and a "Verify MCP backends" step that probes each backend with MCP initialize + tools/list before the agent runs. This flag is not available in release builds. - `check ` - Verify that a compiled pipeline matches its source markdown diff --git a/src/compile/mod.rs b/src/compile/mod.rs index 9a1b77ac..fe1fcb1d 100644 --- a/src/compile/mod.rs +++ b/src/compile/mod.rs @@ -101,9 +101,26 @@ async fn compile_pipeline_inner( // Determine output path. By default use `.lock.yml` to match // gh-aw's convention for compiled-pipeline files (so they can be - // marked as generated and merge=ours via `.gitattributes`). + // marked as generated and merge=ours via `.gitattributes`). When the + // caller passes an existing directory, place the compiled file inside + // it using the default filename derived from the input markdown's stem + // (e.g. `foo.md` -> `/foo.lock.yml`). let yaml_output_path = match output_path { - Some(p) => PathBuf::from(p), + Some(p) => { + let p = PathBuf::from(p); + if p.is_dir() { + let default_filename = input_path + .with_extension("lock.yml") + .file_name() + .map(PathBuf::from) + .with_context(|| { + format!("Invalid input path: {}", input_path.display()) + })?; + p.join(default_filename) + } else { + p + } + } None => input_path.with_extension("lock.yml"), }; @@ -734,4 +751,59 @@ Body diff ); } + + #[tokio::test] + async fn test_compile_pipeline_output_is_directory() { + // When --output points to an existing directory, the compiled YAML + // should be placed inside it using the default filename derived from + // the input markdown's stem. + let temp_dir = std::env::temp_dir().join(format!( + "ado-aw-compile-dir-output-{}-{}", + std::process::id(), + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_nanos() + )); + std::fs::create_dir_all(&temp_dir).unwrap(); + + let input_path = temp_dir.join("my-agent.md"); + std::fs::write( + &input_path, + r#"--- +name: "Test Agent" +description: "A test agent for directory output" +--- + +## Body +"#, + ) + .unwrap(); + + let output_dir = temp_dir.join("out"); + std::fs::create_dir_all(&output_dir).unwrap(); + + compile_pipeline( + input_path.to_str().unwrap(), + Some(output_dir.to_str().unwrap()), + true, + false, + ) + .await + .expect("compile_pipeline should succeed"); + + let expected = output_dir.join("my-agent.lock.yml"); + assert!( + expected.exists(), + "expected compiled YAML at {}", + expected.display() + ); + let contents = std::fs::read_to_string(&expected).unwrap(); + assert!( + contents.contains("@ado-aw"), + "expected compiled YAML to contain the @ado-aw source header" + ); + + let _ = std::fs::remove_dir_all(&temp_dir); + } } diff --git a/src/main.rs b/src/main.rs index 7d4dd245..c5a927d0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,7 +30,10 @@ enum Commands { /// Path to the input markdown file. If omitted, auto-discovers and /// recompiles all existing agentic pipelines in the current directory. path: Option, - /// Optional output path for the generated YAML file + /// Optional output path for the generated YAML file. If the path + /// refers to an existing directory, the compiled YAML is written + /// inside that directory using the default filename derived from + /// the input markdown (e.g. `foo.md` -> `/foo.lock.yml`). #[arg(short, long)] output: Option, /// Omit the "Verify pipeline integrity" step from the generated pipeline.