diff --git a/docs/cli.md b/docs/cli.md index 2313cb4d..4ad83f1a 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -9,6 +9,7 @@ Global flags (apply to all subcommands): `--verbose, -v` (enable info-level logg - `init` - Initialize a repository for AI-first agentic pipeline authoring - `--path ` - Target directory (defaults to current directory) - `--force` - Bypass the GitHub-remote guard (use when running inside a GitHub-hosted repository like `githubnext/ado-aw` itself) + - `--agency` - **Additive.** Also generate an Agency / [Claude Code plugin](https://code.claude.com/docs/en/plugins-reference) under `.github/ado-aw` (in addition to the standard Copilot agent file). Emits `.github/ado-aw/.claude-plugin/plugin.json` (manifest) + `marketplace.json`, an `agents/ado-aw.md` dispatcher subagent, and `commands/{create,update,debug}-ado-agentic-workflow.md` slash commands. - 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. diff --git a/src/data/agency-plugin/.claude-plugin/marketplace.json b/src/data/agency-plugin/.claude-plugin/marketplace.json new file mode 100644 index 00000000..616c1331 --- /dev/null +++ b/src/data/agency-plugin/.claude-plugin/marketplace.json @@ -0,0 +1,18 @@ +{ + "name": "ado-aw", + "owner": { + "name": "githubnext", + "url": "https://github.com/githubnext/ado-aw" + }, + "metadata": { + "description": "Azure DevOps Agentic Pipelines (ado-aw) plugin marketplace", + "version": "{{ compiler_version }}" + }, + "plugins": [ + { + "name": "ado-aw", + "source": "./", + "description": "Create, update, and debug Azure DevOps agentic pipelines with ado-aw" + } + ] +} diff --git a/src/data/agency-plugin/.claude-plugin/plugin.json b/src/data/agency-plugin/.claude-plugin/plugin.json new file mode 100644 index 00000000..c18e6e43 --- /dev/null +++ b/src/data/agency-plugin/.claude-plugin/plugin.json @@ -0,0 +1,19 @@ +{ + "name": "ado-aw", + "version": "{{ compiler_version }}", + "description": "Azure DevOps Agentic Pipelines (ado-aw) - Create, update, and debug AI-powered ADO pipelines", + "author": { + "name": "githubnext", + "url": "https://github.com/githubnext/ado-aw" + }, + "homepage": "https://github.com/githubnext/ado-aw", + "repository": "https://github.com/githubnext/ado-aw", + "license": "MIT", + "keywords": [ + "azure-devops", + "ado", + "agentic", + "pipelines", + "ado-aw" + ] +} diff --git a/src/data/agency-plugin/agents/ado-aw.md b/src/data/agency-plugin/agents/ado-aw.md new file mode 100644 index 00000000..979fb250 --- /dev/null +++ b/src/data/agency-plugin/agents/ado-aw.md @@ -0,0 +1,107 @@ +--- +name: ado-aw +description: Create, update, and debug Azure DevOps agentic pipelines using ado-aw. Use when the user wants to author, modify, or troubleshoot AI-powered Azure DevOps pipelines written as markdown agent files. +--- + +# ADO Agentic Pipelines Agent + +This agent helps you create and manage Azure DevOps agentic pipelines using **ado-aw**. + +ado-aw compiles human-friendly markdown files with YAML front matter into secure, multi-stage Azure DevOps pipelines that run AI agents in network-isolated sandboxes. + +## Setup + +Before creating or compiling workflows, ensure the ado-aw compiler is available. Use the first-time installer for your platform: + +```bash +# Linux +curl -fsSL https://github.com/githubnext/ado-aw/releases/latest/download/install-linux.sh | sh + +# macOS (Apple Silicon) +curl -fsSL https://github.com/githubnext/ado-aw/releases/latest/download/install-macos.sh | sh + +# Windows (PowerShell) +powershell -ExecutionPolicy Bypass -NoProfile -Command "iwr https://github.com/githubnext/ado-aw/releases/latest/download/install-windows.ps1 -UseBasicParsing | iex" +``` + +These scripts validate release checksums, install `ado-aw`, and update PATH when needed. + +Verify: `ado-aw --version` + +## What This Agent Does + +This is a **dispatcher agent** that routes your request to the appropriate specialized prompt. The matching slash commands are also available: + +- **Creating new agentic pipelines** → `/create-ado-agentic-workflow` +- **Updating existing pipelines** → `/update-ado-agentic-workflow` +- **Debugging failing pipelines** → `/debug-ado-agentic-workflow` + +## Available Prompts + +### Create New Agentic Pipeline +**Load when**: User wants to create a new agentic pipeline from scratch + +**Prompt file**: https://raw.githubusercontent.com/githubnext/ado-aw/v{{ compiler_version }}/prompts/create-ado-agentic-workflow.md + +**Use cases**: +- "Create an agentic pipeline that reviews PRs weekly" +- "I need a pipeline that triages work items daily" +- "Design a scheduled dependency updater" + +### Update Existing Pipeline +**Load when**: User wants to modify an existing agent workflow file + +**Prompt file**: https://raw.githubusercontent.com/githubnext/ado-aw/v{{ compiler_version }}/prompts/update-ado-agentic-workflow.md + +**Use cases**: +- "Add the Azure DevOps MCP to my pipeline" +- "Change the schedule from daily to weekly" +- "Add work item creation as a safe output" + +### Debug Failing Pipeline +**Load when**: User needs to troubleshoot a failing agentic pipeline + +**Prompt file**: https://raw.githubusercontent.com/githubnext/ado-aw/v{{ compiler_version }}/prompts/debug-ado-agentic-workflow.md + +**Use cases**: +- "Why is my agentic pipeline failing?" +- "The agent can't reach the MCP server" +- "Safe outputs aren't being processed" + +## Instructions + +When a user interacts with you: + +1. **Identify the task type** from the user's request +2. **Load the appropriate prompt** from the URLs listed above +3. **Follow the loaded prompt's instructions** exactly +4. **If uncertain**, ask clarifying questions to determine the right prompt + +## Quick Reference + +```bash +# Compile an agent file to pipeline YAML +ado-aw compile + +# Recompile all detected pipelines +ado-aw compile + +# Verify pipeline matches source +ado-aw check +``` + +## Key Features + +- **Natural language pipelines**: Write in markdown with YAML frontmatter +- **3-stage security**: Agent → Threat Analysis → Safe Output Execution +- **Network isolation**: AWF (Agentic Workflow Firewall) with domain whitelisting +- **MCP Gateway**: Tool routing for Azure DevOps, custom MCPs +- **Safe outputs**: Controlled write operations (PRs, work items, wiki pages) +- **Agent memory**: Persistent storage across pipeline runs + +## Important Notes + +- Agent files must be compiled with `ado-aw compile` after YAML frontmatter changes +- Markdown body (agent instructions) changes do NOT require recompilation +- The agent never has direct write access — all mutations go through safe outputs +- Full reference: https://raw.githubusercontent.com/githubnext/ado-aw/v{{ compiler_version }}/AGENTS.md diff --git a/src/data/agency-plugin/commands/create-ado-agentic-workflow.md b/src/data/agency-plugin/commands/create-ado-agentic-workflow.md new file mode 100644 index 00000000..630d0409 --- /dev/null +++ b/src/data/agency-plugin/commands/create-ado-agentic-workflow.md @@ -0,0 +1,14 @@ +--- +description: Create a new Azure DevOps agentic pipeline from scratch with ado-aw. +--- + +You are creating a new **ado-aw** agentic pipeline. Make sure the `ado-aw` +compiler is installed (`ado-aw --version`); if not, install it using the +platform installer scripts from the ado-aw releases page. + +Load the **entire** content of the following prompt and follow its +instructions precisely: + +https://raw.githubusercontent.com/githubnext/ado-aw/v{{ compiler_version }}/prompts/create-ado-agentic-workflow.md + +The user's request: $ARGUMENTS diff --git a/src/data/agency-plugin/commands/debug-ado-agentic-workflow.md b/src/data/agency-plugin/commands/debug-ado-agentic-workflow.md new file mode 100644 index 00000000..d7f36c4f --- /dev/null +++ b/src/data/agency-plugin/commands/debug-ado-agentic-workflow.md @@ -0,0 +1,14 @@ +--- +description: Debug a failing Azure DevOps agentic pipeline authored with ado-aw. +--- + +You are debugging a failing **ado-aw** agentic pipeline. Make sure the +`ado-aw` compiler is installed (`ado-aw --version`); if not, install it using +the platform installer scripts from the ado-aw releases page. + +Load the **entire** content of the following prompt and follow its +instructions precisely: + +https://raw.githubusercontent.com/githubnext/ado-aw/v{{ compiler_version }}/prompts/debug-ado-agentic-workflow.md + +The user's request: $ARGUMENTS diff --git a/src/data/agency-plugin/commands/update-ado-agentic-workflow.md b/src/data/agency-plugin/commands/update-ado-agentic-workflow.md new file mode 100644 index 00000000..6e0fc4ea --- /dev/null +++ b/src/data/agency-plugin/commands/update-ado-agentic-workflow.md @@ -0,0 +1,15 @@ +--- +description: Update an existing Azure DevOps agentic pipeline authored with ado-aw. +--- + +You are updating an existing **ado-aw** agentic pipeline. Make sure the +`ado-aw` compiler is installed (`ado-aw --version`); if not, install it using +the platform installer scripts from the ado-aw releases page. + +Load the **entire** content of the following prompt and follow its +instructions precisely. Read the existing agent file before making any +changes, and recompile with `ado-aw compile` after editing YAML front matter: + +https://raw.githubusercontent.com/githubnext/ado-aw/v{{ compiler_version }}/prompts/update-ado-agentic-workflow.md + +The user's request: $ARGUMENTS diff --git a/src/init.rs b/src/init.rs index 8b64c86b..c9cdfe0e 100644 --- a/src/init.rs +++ b/src/init.rs @@ -1,5 +1,5 @@ use anyhow::{Context, Result}; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; // The agent template is embedded from src/data/init-agent.md const AGENT_TEMPLATE: &str = include_str!("data/init-agent.md"); @@ -7,7 +7,49 @@ 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>) -> Result<()> { +/// Root directory (relative to the target repo) for the generated Agency / +/// Claude Code plugin. Placed under `.github/ado-aw` so it does not pollute +/// the user's source tree like the conventional top-level plugin folder would. +const AGENCY_PLUGIN_DIR: &str = ".github/ado-aw"; + +/// Files that make up the Agency plugin, following the Claude Code plugin +/// conventions (https://code.claude.com/docs/en/plugins-reference): +/// - `.claude-plugin/plugin.json` — plugin manifest +/// - `.claude-plugin/marketplace.json` — marketplace so the plugin is +/// installable straight from this dir +/// - `agents/ado-aw.md` — dispatcher subagent +/// - `commands/*.md` — slash commands routing to prompts +/// +/// Each entry is `(relative path within the plugin dir, embedded template)`. +/// `{{ compiler_version }}` placeholders are substituted at write time. +const AGENCY_PLUGIN_FILES: &[(&str, &str)] = &[ + ( + ".claude-plugin/plugin.json", + include_str!("data/agency-plugin/.claude-plugin/plugin.json"), + ), + ( + ".claude-plugin/marketplace.json", + include_str!("data/agency-plugin/.claude-plugin/marketplace.json"), + ), + ( + "agents/ado-aw.md", + include_str!("data/agency-plugin/agents/ado-aw.md"), + ), + ( + "commands/create-ado-agentic-workflow.md", + include_str!("data/agency-plugin/commands/create-ado-agentic-workflow.md"), + ), + ( + "commands/update-ado-agentic-workflow.md", + include_str!("data/agency-plugin/commands/update-ado-agentic-workflow.md"), + ), + ( + "commands/debug-ado-agentic-workflow.md", + include_str!("data/agency-plugin/commands/debug-ado-agentic-workflow.md"), + ), +]; + +pub async fn run(path: Option<&std::path::Path>, agency: bool) -> Result<()> { let base = path .map(PathBuf::from) .unwrap_or_else(|| PathBuf::from(".")); @@ -33,6 +75,13 @@ pub async fn run(path: Option<&std::path::Path>) -> Result<()> { // Print success message println!("✓ Created {}", agent_path.display()); + + // `--agency` is additive: keep the standard agent file above and also emit + // the Agency / Claude Code plugin. + if agency { + write_agency_plugin(&base, version).await?; + } + println!(); println!("This agent helps you create, update, and debug Azure DevOps agentic pipelines."); println!("It will automatically download the ado-aw compiler and handle compilation."); @@ -47,3 +96,29 @@ pub async fn run(path: Option<&std::path::Path>) -> Result<()> { Ok(()) } + +/// Write the Agency / Claude Code plugin into `/.github/ado-aw`. +/// +/// The plugin is additive to the standard agent file and follows the Claude +/// Code plugin conventions, written under `.github/ado-aw` so it does not +/// pollute the user's source tree. +async fn write_agency_plugin(base: &Path, version: &str) -> Result<()> { + let plugin_root = base.join(AGENCY_PLUGIN_DIR); + + for (rel_path, template) in AGENCY_PLUGIN_FILES { + let dest = plugin_root.join(rel_path); + if let Some(parent) = dest.parent() { + tokio::fs::create_dir_all(parent) + .await + .with_context(|| format!("Failed to create directory: {}", parent.display()))?; + } + let content = template.replace("{{ compiler_version }}", version); + tokio::fs::write(&dest, content) + .await + .with_context(|| format!("Failed to write plugin file: {}", dest.display()))?; + } + + println!("✓ Created Agency plugin in {}", plugin_root.display()); + + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index 05bf8058..c876b23d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -291,6 +291,10 @@ enum Commands { /// GitHub-hosted repository like `githubnext/ado-aw` itself) #[arg(long)] force: bool, + /// Additionally generate an Agency / Claude Code plugin under + /// `.github/ado-aw` (additive to the standard agent file). + #[arg(long)] + agency: bool, }, /// (Deprecated) Set GITHUB_TOKEN on every matched ADO definition. /// Use `secrets set GITHUB_TOKEN ` instead. @@ -1085,7 +1089,11 @@ async fn main() -> Result<()> { ) .await?; } - Commands::Init { path, force } => { + Commands::Init { + path, + force, + agency, + } => { let init_path = path.as_deref().unwrap_or(Path::new(".")); // `--force` bypasses the GitHub-remote guard so maintainers can // run `ado-aw init` inside this repository (or other GitHub-hosted @@ -1093,7 +1101,7 @@ async fn main() -> Result<()> { if !force { ensure_non_github_remote_for_ado_aw("init", init_path).await?; } - init::run(path.as_deref()).await?; + init::run(path.as_deref(), agency).await?; } Commands::Configure { token, diff --git a/tests/init_tests.rs b/tests/init_tests.rs index b3344fba..bfd6c3a7 100644 --- a/tests/init_tests.rs +++ b/tests/init_tests.rs @@ -75,7 +75,93 @@ fn test_init_overwrites_by_default() { ); } -/// Test that `--force` is advertised in `init --help` and describes its +/// Test that `init --agency` is additive: it produces the standard agent file +/// AND the Agency / Claude Code plugin under the `.github/ado-aw` directory. +#[test] +fn test_init_agency_generates_plugin() { + let temp_dir = tempfile::tempdir().expect("Failed to create temp directory"); + + let output = ado_aw_bin() + .args([ + "init", + "--agency", + "--path", + temp_dir.path().to_str().unwrap(), + ]) + .output() + .expect("Failed to run ado-aw init --agency"); + + assert!( + output.status.success(), + "init --agency should succeed: {}", + String::from_utf8_lossy(&output.stderr) + ); + + // Standard agent file is still produced (additive behavior). + let agent_path = temp_dir.path().join(".github/agents/ado-aw.agent.md"); + assert!( + agent_path.exists(), + "Standard agent file should still be created with --agency" + ); + + // Claude Code plugin manifest + marketplace under `.github/ado-aw`. + let plugin_root = temp_dir.path().join(".github/ado-aw"); + let plugin_json = plugin_root.join(".claude-plugin/plugin.json"); + let marketplace_json = plugin_root.join(".claude-plugin/marketplace.json"); + assert!(plugin_json.exists(), "plugin.json should be created"); + assert!( + marketplace_json.exists(), + "marketplace.json should be created" + ); + + // Subagent + slash commands. + assert!( + plugin_root.join("agents/ado-aw.md").exists(), + "Agency subagent should be created" + ); + for cmd in [ + "create-ado-agentic-workflow.md", + "update-ado-agentic-workflow.md", + "debug-ado-agentic-workflow.md", + ] { + assert!( + plugin_root.join("commands").join(cmd).exists(), + "Command {cmd} should be created" + ); + } + + // Version placeholder must be substituted in the manifest. + let manifest = + fs::read_to_string(&plugin_json).expect("Should be able to read plugin.json"); + assert!( + !manifest.contains("{{ compiler_version }}"), + "Version placeholder should be replaced in plugin.json" + ); + // plugin.json must be valid JSON with the expected plugin name. + let parsed: serde_json::Value = + serde_json::from_str(&manifest).expect("plugin.json should be valid JSON"); + assert_eq!( + parsed["name"], "ado-aw", + "plugin.json name should be ado-aw" + ); +} + +/// Test that `init` WITHOUT `--agency` does not create the plugin directory. +#[test] +fn test_init_without_agency_skips_plugin() { + let temp_dir = tempfile::tempdir().expect("Failed to create temp directory"); + + 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(), "init should succeed"); + + assert!( + !temp_dir.path().join(".github/ado-aw").exists(), + "Plugin directory should not be created without --agency" + ); +} /// actual purpose: bypassing the GitHub-remote guard so maintainers can run /// `ado-aw init` inside a GitHub-hosted fork of `ado-aw` itself. ///