Skip to content

Commit 14e7b7f

Browse files
Compile: write into directory when --output is a directory (#331)
* feat(compile): place output in directory when --output is a directory Agent-Logs-Url: https://github.com/githubnext/ado-aw/sessions/9485542a-dae0-4395-9587-7f5ef457c5dd Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com> * refactor(compile): compute default filename lazily; assert YAML content in test Agent-Logs-Url: https://github.com/githubnext/ado-aw/sessions/f02521ba-67d6-456d-af2a-a228d50389ab Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com> * Merge branch 'main' into copilot/place-compiled-agent-file Agent-Logs-Url: https://github.com/githubnext/ado-aw/sessions/fa807a16-0f6a-4e5d-9bf7-9fbdc24c6de6 Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
1 parent 6877def commit 14e7b7f

3 files changed

Lines changed: 79 additions & 4 deletions

File tree

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1015,7 +1015,7 @@ Global flags (apply to all subcommands): `--verbose, -v` (enable info-level logg
10151015
- Creates `.github/agents/ado-aw.agent.md` — a Copilot dispatcher agent that routes to specialized prompts for creating, updating, and debugging agentic pipelines
10161016
- The agent auto-downloads the ado-aw compiler and handles the full lifecycle (create → compile → check)
10171017
- `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.
1018-
- `--output, -o <path>` - Optional output path for generated YAML (only valid when a path is provided)
1018+
- `--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`).
10191019
- `--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.
10201020
- `--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.
10211021
- `check <pipeline>` - Verify that a compiled pipeline matches its source markdown

src/compile/mod.rs

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,26 @@ async fn compile_pipeline_inner(
101101

102102
// Determine output path. By default use `.lock.yml` to match
103103
// gh-aw's convention for compiled-pipeline files (so they can be
104-
// marked as generated and merge=ours via `.gitattributes`).
104+
// marked as generated and merge=ours via `.gitattributes`). When the
105+
// caller passes an existing directory, place the compiled file inside
106+
// it using the default filename derived from the input markdown's stem
107+
// (e.g. `foo.md` -> `<dir>/foo.lock.yml`).
105108
let yaml_output_path = match output_path {
106-
Some(p) => PathBuf::from(p),
109+
Some(p) => {
110+
let p = PathBuf::from(p);
111+
if p.is_dir() {
112+
let default_filename = input_path
113+
.with_extension("lock.yml")
114+
.file_name()
115+
.map(PathBuf::from)
116+
.with_context(|| {
117+
format!("Invalid input path: {}", input_path.display())
118+
})?;
119+
p.join(default_filename)
120+
} else {
121+
p
122+
}
123+
}
107124
None => input_path.with_extension("lock.yml"),
108125
};
109126

@@ -734,4 +751,59 @@ Body
734751
diff
735752
);
736753
}
754+
755+
#[tokio::test]
756+
async fn test_compile_pipeline_output_is_directory() {
757+
// When --output points to an existing directory, the compiled YAML
758+
// should be placed inside it using the default filename derived from
759+
// the input markdown's stem.
760+
let temp_dir = std::env::temp_dir().join(format!(
761+
"ado-aw-compile-dir-output-{}-{}",
762+
std::process::id(),
763+
std::time::SystemTime::now()
764+
.duration_since(std::time::UNIX_EPOCH)
765+
.unwrap()
766+
.as_nanos()
767+
));
768+
std::fs::create_dir_all(&temp_dir).unwrap();
769+
770+
let input_path = temp_dir.join("my-agent.md");
771+
std::fs::write(
772+
&input_path,
773+
r#"---
774+
name: "Test Agent"
775+
description: "A test agent for directory output"
776+
---
777+
778+
## Body
779+
"#,
780+
)
781+
.unwrap();
782+
783+
let output_dir = temp_dir.join("out");
784+
std::fs::create_dir_all(&output_dir).unwrap();
785+
786+
compile_pipeline(
787+
input_path.to_str().unwrap(),
788+
Some(output_dir.to_str().unwrap()),
789+
true,
790+
false,
791+
)
792+
.await
793+
.expect("compile_pipeline should succeed");
794+
795+
let expected = output_dir.join("my-agent.lock.yml");
796+
assert!(
797+
expected.exists(),
798+
"expected compiled YAML at {}",
799+
expected.display()
800+
);
801+
let contents = std::fs::read_to_string(&expected).unwrap();
802+
assert!(
803+
contents.contains("@ado-aw"),
804+
"expected compiled YAML to contain the @ado-aw source header"
805+
);
806+
807+
let _ = std::fs::remove_dir_all(&temp_dir);
808+
}
737809
}

src/main.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ enum Commands {
3030
/// Path to the input markdown file. If omitted, auto-discovers and
3131
/// recompiles all existing agentic pipelines in the current directory.
3232
path: Option<String>,
33-
/// Optional output path for the generated YAML file
33+
/// Optional output path for the generated YAML file. If the path
34+
/// refers to an existing directory, the compiled YAML is written
35+
/// inside that directory using the default filename derived from
36+
/// the input markdown (e.g. `foo.md` -> `<dir>/foo.lock.yml`).
3437
#[arg(short, long)]
3538
output: Option<String>,
3639
/// Omit the "Verify pipeline integrity" step from the generated pipeline.

0 commit comments

Comments
 (0)