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
14 changes: 2 additions & 12 deletions .github/workflows/copilot-cli-safeoutputs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,8 @@ jobs:
id: copilot-version
run: |
set -euo pipefail
version="$(
python3 - <<'PY'
import pathlib
import re

text = pathlib.Path("src/engine.rs").read_text()
match = re.search(r'pub const COPILOT_CLI_VERSION: &str = "([^"]+)"', text)
if not match:
raise SystemExit("COPILOT_CLI_VERSION not found")
print(match.group(1))
PY
)"
version="$(cargo run --quiet -- catalog --kind versions --json | jq -r '.versions.copilot_cli')"
test -n "$version" && test "$version" != "null"
echo "version=${version}" >> "$GITHUB_OUTPUT"

- name: Install compiler-pinned GitHub Copilot CLI
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -564,7 +564,7 @@ Commands:
graph Query the resolved dependency graph for an agent source file
whatif Static reachability: classify jobs skipped if a step or job fails
lint Run structural lint checks over an agent source file
catalog List safe-outputs, runtimes, tools, engines, and models
catalog List safe-outputs, runtimes, tools, engines, models, and versions

Options:
-v, --verbose Enable info-level logging
Expand Down
3 changes: 2 additions & 1 deletion docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,9 @@ Both `--all-repos` and `--source` route through `ado-aw`'s `discover_ado_aw_pipe
- `<source>` - Path to the agent markdown file.
- `--json` - Emit lint findings as structured JSON instead of the human-readable report.

- `catalog [--kind <safe-outputs|runtimes|tools|engines|models>] [--json]` - List the compiler's in-tree registries for scripting or discovery.
- `catalog [--kind <safe-outputs|runtimes|tools|engines|models|versions>] [--json]` - List the compiler's in-tree registries for scripting or discovery.
- `--kind <...>` - Restrict output to one category. When omitted, emits every category.
- `--kind versions` - Emit the compiler's pinned **semver** versions (`copilot_cli`, `awf`, `mcpg`) as a single source of truth. CI reads these deterministically instead of scraping the Rust source, e.g. `ado-aw catalog --kind versions --json | jq -r '.versions.copilot_cli'`.
- `--json` - Emit the catalog as structured JSON instead of the human-readable report.

### Hidden Build-Time Tools
Expand Down
4 changes: 2 additions & 2 deletions site/src/content/docs/setup/cli.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -140,15 +140,15 @@ Options:

### `catalog`

List the in-tree registries of safe-output tools, runtimes, first-class tools, engines, and models.
List the in-tree registries of safe-output tools, runtimes, first-class tools, engines, models, and pinned versions.

```bash
ado-aw catalog [--kind <category>] [--json]
```

Options:

- `--kind safe-outputs|runtimes|tools|engines|models` -- restrict output to one category; omit to see all
- `--kind safe-outputs|runtimes|tools|engines|models|versions` -- restrict output to one category; omit to see all. `--kind versions` emits the pinned semver versions (`copilot_cli`, `awf`, `mcpg`) as a deterministic source of truth for CI.
- `--json` -- emit the catalog as JSON

### `trace <build-id-or-url>`
Expand Down
2 changes: 2 additions & 0 deletions src/compile/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ pub use common::ADO_MCP_ENTRYPOINT;
pub use common::ADO_MCP_IMAGE;
pub use common::ADO_MCP_PACKAGE;
pub use common::ADO_MCP_SERVER_NAME;
pub use common::AWF_VERSION;
pub use common::HEADER_MARKER;
pub use common::MCPG_VERSION;
pub use common::normalize_source_path;
#[allow(unused_imports)]
pub use common::parse_markdown;
Expand Down
71 changes: 69 additions & 2 deletions src/inspect/catalog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ use std::fmt;

use serde::Serialize;

use crate::engine::DEFAULT_COPILOT_MODEL;
use crate::compile::{AWF_VERSION, MCPG_VERSION};
use crate::engine::{COPILOT_CLI_VERSION, DEFAULT_COPILOT_MODEL};
use crate::safe_outputs::{ALL_KNOWN_SAFE_OUTPUTS, ALWAYS_ON_TOOLS, DEBUG_ONLY_TOOLS};

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
Expand All @@ -28,6 +29,21 @@ pub struct ToolCatalogEntry {
pub description: String,
}

/// Pinned semver versions the compiler embeds, surfaced so CI and tooling can
/// read them deterministically instead of scraping the Rust source. Only
/// genuine semver version constants are included — container image references
/// and the default model name are intentionally excluded.
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct VersionCatalog {
/// Pinned GitHub Copilot CLI version (`engine::COPILOT_CLI_VERSION`).
pub copilot_cli: String,
/// Pinned AWF (Agentic Workflow Firewall) binary version
/// (`compile::common::AWF_VERSION`).
pub awf: String,
/// Pinned MCP Gateway version (`compile::common::MCPG_VERSION`).
pub mcpg: String,
}

#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
pub struct Catalog {
#[serde(skip_serializing_if = "Vec::is_empty")]
Expand All @@ -40,6 +56,8 @@ pub struct Catalog {
pub engines: Vec<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub models: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub versions: Option<VersionCatalog>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
Expand All @@ -51,7 +69,7 @@ impl fmt::Display for UnknownCatalogKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"unknown --kind '{}' (expected one of: safe-outputs, runtimes, tools, engines, models)",
"unknown --kind '{}' (expected one of: safe-outputs, runtimes, tools, engines, models, versions)",
self.kind
)
}
Expand All @@ -66,6 +84,7 @@ pub enum CatalogKind {
Tools,
Engines,
Models,
Versions,
}

impl CatalogKind {
Expand All @@ -76,6 +95,7 @@ impl CatalogKind {
"tools" => Ok(Self::Tools),
"engines" => Ok(Self::Engines),
"models" => Ok(Self::Models),
"versions" => Ok(Self::Versions),
other => Err(UnknownCatalogKind {
kind: other.to_string(),
}),
Expand All @@ -90,6 +110,7 @@ pub fn catalog() -> Catalog {
tools: tools(),
engines: engines(),
models: models(),
versions: Some(versions()),
}
}

Expand All @@ -116,6 +137,10 @@ pub fn catalog_kind(kind: &str) -> Result<Catalog, UnknownCatalogKind> {
models: models(),
..Catalog::default()
},
CatalogKind::Versions => Catalog {
versions: Some(versions()),
..Catalog::default()
},
})
}

Expand Down Expand Up @@ -161,10 +186,25 @@ pub fn render_text(catalog: &Catalog) -> String {
for model in &catalog.models {
out.push_str(&format!(" {model}\n"));
}
out.push('\n');
}
if let Some(versions) = &catalog.versions {
out.push_str("Versions\n");
out.push_str(&format!(" copilot-cli {}\n", versions.copilot_cli));
out.push_str(&format!(" awf {}\n", versions.awf));
out.push_str(&format!(" mcpg {}\n", versions.mcpg));
}
out.trim_end().to_string()
}

fn versions() -> VersionCatalog {
VersionCatalog {
copilot_cli: COPILOT_CLI_VERSION.to_string(),
awf: AWF_VERSION.to_string(),
mcpg: MCPG_VERSION.to_string(),
}
}

fn safe_outputs() -> Vec<SafeOutputCatalogEntry> {
ALL_KNOWN_SAFE_OUTPUTS
.iter()
Expand Down Expand Up @@ -319,4 +359,31 @@ mod tests {
let err = catalog_kind("widgets").unwrap_err();
assert_eq!(err.kind, "widgets");
}

#[test]
fn versions_inspect_catalog_kind_returns_pinned_semver_constants() {
let catalog = catalog_kind("versions").unwrap();
let versions = catalog.versions.expect("versions populated");
assert_eq!(versions.copilot_cli, COPILOT_CLI_VERSION);
assert_eq!(versions.awf, AWF_VERSION);
assert_eq!(versions.mcpg, MCPG_VERSION);
// Only the versions category is populated for --kind versions.
assert!(catalog.safe_outputs.is_empty());
assert!(catalog.models.is_empty());
}

#[test]
fn full_catalog_includes_versions() {
let catalog = catalog();
assert!(catalog.versions.is_some());
}

#[test]
fn versions_catalog_serializes_semver_fields_as_json() {
let catalog = catalog_kind("versions").unwrap();
let value = serde_json::to_value(&catalog).unwrap();
assert_eq!(value["versions"]["copilot_cli"], COPILOT_CLI_VERSION);
assert_eq!(value["versions"]["awf"], AWF_VERSION);
assert_eq!(value["versions"]["mcpg"], MCPG_VERSION);
}
}
4 changes: 2 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -606,9 +606,9 @@ enum Commands {
#[arg(long)]
json: bool,
},
/// List safe-outputs, runtimes, tools, engines, and models.
/// List safe-outputs, runtimes, tools, engines, models, and versions.
Catalog {
/// Category to emit: safe-outputs, runtimes, tools, engines, or models.
/// Category to emit: safe-outputs, runtimes, tools, engines, models, or versions.
#[arg(long)]
kind: Option<String>,
/// Emit the catalog as JSON.
Expand Down
4 changes: 2 additions & 2 deletions src/mcp_author/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ struct WhatIfParams {

#[derive(Debug, Deserialize, JsonSchema)]
struct CatalogParams {
/// Optional category: safe-outputs, runtimes, tools, engines, or models.
/// Optional category: safe-outputs, runtimes, tools, engines, models, or versions.
kind: Option<String>,
}

Expand Down Expand Up @@ -301,7 +301,7 @@ impl AuthorMcp {

#[tool(
name = "catalog",
description = "List supported safe-outputs, runtimes, tools, engines, and models."
description = "List supported safe-outputs, runtimes, tools, engines, models, and pinned versions."
)]
async fn catalog(&self, params: Parameters<CatalogParams>) -> Result<CallToolResult, McpError> {
let catalog = inspect::build_catalog(params.0.kind.as_deref()).map_err(to_mcp_error)?;
Expand Down
Loading