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 AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 [<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.
- `--output, -o <path>` - Optional output path for generated YAML (only valid when a path is provided)
- `--output, -o <path>` - 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` → `<dir>/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 <pipeline>` - Verify that a compiled pipeline matches its source markdown
Expand Down
76 changes: 74 additions & 2 deletions src/compile/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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` -> `<dir>/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"),
};

Expand Down Expand Up @@ -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);
}
}
5 changes: 4 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,
/// 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` -> `<dir>/foo.lock.yml`).
#[arg(short, long)]
output: Option<String>,
/// Omit the "Verify pipeline integrity" step from the generated pipeline.
Expand Down
Loading