Skip to content
Draft
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
13 changes: 11 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,15 @@ go build ./...
go test ./...
```

After every change to CLI code, rebuild and install locally so `azd copilot` runs your latest bits:

```bash
cd cli
mage build
```

This kills running extension processes, builds the binary, and installs it via `azd x build`. Always do this before testing changes locally.

## Adding a Custom Skill

Create a directory in `skills/` with a `SKILL.md`:
Expand All @@ -57,8 +66,8 @@ The `SKILL.md` must have YAML frontmatter with `name` and `description`. After a
| Command | What it does |
|---------|-------------|
| `mage SyncSkills` | Pull latest upstream skills into `ghcp4a-skills/` (smart merge — keeps your changes) |
| `mage SyncSkills /path/to/clone` | Sync from a local clone instead of cloning remotely |
| `mage SyncSkills url@branch` | Sync from a custom repo/branch (e.g. a fork) |
| `mage SyncSkillsFrom /path/to/clone` | Sync from a local clone instead of cloning remotely |
| `mage SyncSkillsFrom url@branch` | Sync from a custom repo/branch (e.g. a fork) |
| `mage ContributeSkills` | Create a branch with your `ghcp4a-skills/` changes for a PR to upstream |

## MCP Server Configuration
Expand Down
2 changes: 1 addition & 1 deletion cli/counts.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"agents": 16,
"skills": 29
"skills": 28
}
2 changes: 1 addition & 1 deletion cli/extension.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ displayName: Azure Copilot CLI
description: |
AI-powered Azure development assistant. Describe what you want to build,
and Copilot builds and deploys it to Azure. Includes 16 specialized agents
and 29 Azure skills.
and 28 Azure skills.
usage: azd copilot <command> [options]
version: 0.1.7
entryPoint: copilot
Expand Down
18 changes: 12 additions & 6 deletions cli/magefile.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,14 @@ const (
skillsTargetPath = "src/internal/assets/ghcp4a-skills"
)

// SyncSkills syncs upstream skills from microsoft/GitHub-Copilot-for-Azure.
// SyncSkills syncs upstream skills from microsoft/GitHub-Copilot-for-Azure (main branch).
//
// For custom sources, use SyncSkillsFrom instead.
func SyncSkills() error {
return SyncSkillsFrom("")
}

// SyncSkillsFrom syncs upstream skills from a custom source.
//
// When syncing from a local path, an exact sync is performed: the target is
// mirrored to match the source, including file deletions and content updates.
Expand All @@ -184,11 +191,10 @@ const (
//
// Examples:
//
// mage SyncSkills # upstream main
// mage SyncSkills C:\code\GitHub-Copilot-for-Azure # local folder
// mage SyncSkills https://github.com/user/fork.git # custom repo
// mage SyncSkills https://github.com/user/fork.git@my-branch # custom repo + branch
func SyncSkills(source string) error {
// mage SyncSkillsFrom C:\code\GitHub-Copilot-for-Azure # local folder
// mage SyncSkillsFrom https://github.com/user/fork.git # custom repo
// mage SyncSkillsFrom https://github.com/user/fork.git@my-branch # custom repo + branch
func SyncSkillsFrom(source string) error {
fmt.Println("🔄 Syncing upstream Azure skills...")

var sourceDir string
Expand Down
2 changes: 1 addition & 1 deletion cli/src/cmd/copilot/commands/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,6 @@ func runQuickAction(cmd *cobra.Command, action string, prompt string) error {
// Launch Copilot with the specific prompt
return copilot.Launch(cmd.Context(), copilot.Options{
Prompt: prompt,
Agent: "azure-manager",
Agent: "squad",
})
}
2 changes: 1 addition & 1 deletion cli/src/cmd/copilot/commands/agents.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func NewAgentsCommand() *cobra.Command {
Long: fmt.Sprintf(`List and manage the %d specialized Azure agents.

Each agent is an expert in a specific domain of Azure development:
- azure-manager: Orchestrates all agents, main entry point
- squad: Azure Squad coordinator — routes work to specialized agents
- azure-architect: Infrastructure design, Bicep, networking
- azure-dev: Application code, APIs, frontend
- azure-data: Database schema, queries, migrations
Expand Down
4 changes: 2 additions & 2 deletions cli/src/cmd/copilot/commands/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ func runBuild(cmd *cobra.Command, description string) error {
// Launch Copilot to generate spec
if err := copilot.Launch(cmd.Context(), copilot.Options{
Prompt: prompt,
Agent: "azure-manager",
Agent: "squad",
}); err != nil {
return err
}
Expand Down Expand Up @@ -146,7 +146,7 @@ func runBuildFromSpec(cmd *cobra.Command) error {
// Launch Copilot with the build prompt
return copilot.Launch(cmd.Context(), copilot.Options{
Prompt: prompt,
Agent: "azure-manager",
Agent: "squad",
})
}

Expand Down
2 changes: 1 addition & 1 deletion cli/src/cmd/copilot/commands/checkpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ If no checkpoint ID is provided, resumes from the latest checkpoint.`,

return copilot.Launch(cmd.Context(), copilot.Options{
Prompt: prompt,
Agent: "azure-manager",
Agent: "squad",
})
},
}
Expand Down
96 changes: 94 additions & 2 deletions cli/src/cmd/copilot/commands/mcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ package commands
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"

"github.com/jongio/azd-copilot/cli/src/internal/copilot"
"github.com/mark3labs/mcp-go/mcp"
Expand Down Expand Up @@ -88,7 +91,7 @@ func registerMCPTools(s *server.MCPServer) {
),
func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
agents := []string{
"azure-manager - Orchestrates all agents, main entry point",
"squad - Azure Squad coordinator, routes work to specialized agents",
"azure-architect - Infrastructure design, Bicep, networking",
"azure-dev - Application code, APIs, frontend",
"azure-data - Database schema, queries, migrations",
Expand Down Expand Up @@ -162,6 +165,95 @@ func registerMCPTools(s *server.MCPServer) {
return mcp.NewToolResultText(result), nil
},
)

// Tool: list_squad_members
s.AddTool(
mcp.NewTool("list_squad_members",
mcp.WithDescription("List all members of the Azure Squad team"),
),
func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
projectDir := getSquadProjectDir()
teamFile := filepath.Join(projectDir, ".ai-team", "team.md")
data, err := os.ReadFile(teamFile)
if err != nil {
return mcp.NewToolResultText("No Squad team found. Run 'azd copilot squad init' to create one."), nil
}
return mcp.NewToolResultText(string(data)), nil
},
)

// Tool: get_squad_decisions
s.AddTool(
mcp.NewTool("get_squad_decisions",
mcp.WithDescription("Get the shared decisions log for the Azure Squad team"),
),
func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
projectDir := getSquadProjectDir()
decisionsFile := filepath.Join(projectDir, ".ai-team", "decisions.md")
data, err := os.ReadFile(decisionsFile)
if err != nil {
return mcp.NewToolResultText("No decisions file found. The Squad team may not be initialized."), nil
}
return mcp.NewToolResultText(string(data)), nil
},
)

// Tool: create_squad_decision
s.AddTool(
mcp.NewTool("create_squad_decision",
mcp.WithDescription("Write a decision to the Squad team's shared decision inbox"),
mcp.WithString("author", mcp.Required(), mcp.Description("Name of the agent or user making the decision")),
mcp.WithString("summary", mcp.Required(), mcp.Description("Brief summary of the decision")),
mcp.WithString("detail", mcp.Description("Detailed explanation of the decision and rationale")),
),
func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
args, ok := req.Params.Arguments.(map[string]interface{})
if !ok {
return mcp.NewToolResultText("Error: invalid arguments"), nil
}
author, _ := args["author"].(string)
summary, _ := args["summary"].(string)
detail, _ := args["detail"].(string)

projectDir := getSquadProjectDir()

inboxDir := filepath.Join(projectDir, ".ai-team", "decisions", "inbox")
if err := os.MkdirAll(inboxDir, 0755); err != nil {
return mcp.NewToolResultText(fmt.Sprintf("Error creating inbox directory: %v", err)), nil
}

// Create a slug from the summary
slug := strings.ToLower(strings.ReplaceAll(summary, " ", "-"))
if len(slug) > 50 {
slug = slug[:50]
}

filename := filepath.Join(inboxDir, fmt.Sprintf("%s-%s.md", strings.ToLower(author), slug))
content := fmt.Sprintf("### %s\n**By:** %s\n**What:** %s\n", summary, author, summary)
if detail != "" {
content += fmt.Sprintf("**Why:** %s\n", detail)
}

if err := os.WriteFile(filename, []byte(content), 0644); err != nil {
return mcp.NewToolResultText(fmt.Sprintf("Error writing decision: %v", err)), nil
}

return mcp.NewToolResultText(fmt.Sprintf("Decision written to: %s", filename)), nil
},
)
}

// getSquadProjectDir resolves the project directory for Squad operations.
func getSquadProjectDir() string {
if dir := os.Getenv("AZD_SQUAD_DIR"); dir != "" {
// AZD_SQUAD_DIR points to .ai-team/ dir, return parent
return filepath.Dir(dir)
}
if dir := os.Getenv("AZD_COPILOT_PROJECT_DIR"); dir != "" {
return dir
}
cwd, _ := os.Getwd()
return cwd
}

func registerMCPResources(s *server.MCPServer) {
Expand All @@ -178,7 +270,7 @@ func registerMCPResources(s *server.MCPServer) {

The following specialized agents are available:

1. **azure-manager** - Orchestrates all agents, main entry point
1. **squad** - Azure Squad coordinator, routes work to specialized agents
2. **azure-architect** - Infrastructure design, Bicep, networking
3. **azure-dev** - Application code, APIs, frontend
4. **azure-data** - Database schema, queries, migrations
Expand Down
6 changes: 3 additions & 3 deletions cli/src/cmd/copilot/commands/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,8 @@ func outputMetadata() error {
Short: "a",
Description: "Use a specific agent",
Type: "string",
Default: "azure-manager",
Values: []string{"azure-manager", "azure-architect", "azure-dev", "azure-data", "azure-ai", "azure-security", "azure-devops"},
Default: "squad",
Values: []string{"squad", "azure-architect", "azure-dev", "azure-data", "azure-ai", "azure-security", "azure-devops"},
},
{
Name: "model",
Expand Down Expand Up @@ -188,7 +188,7 @@ func outputMetadata() error {
Completions: []completionOption{
{
Context: "agent",
Values: []string{"azure-manager", "azure-architect", "azure-dev", "azure-data", "azure-ai", "azure-security", "azure-devops"},
Values: []string{"squad", "azure-architect", "azure-dev", "azure-data", "azure-ai", "azure-security", "azure-devops"},
},
{
Context: "mode",
Expand Down
2 changes: 1 addition & 1 deletion cli/src/cmd/copilot/commands/sessions.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ func listSessions(cmd *cobra.Command, args []string) error {
// Try to detect agent from checkpoints
checkpointsDir := filepath.Join(sessionPath, "checkpoints")
if cpEntries, err := os.ReadDir(checkpointsDir); err == nil && len(cpEntries) > 0 {
session.Agent = "azure-manager" // Default assumption
session.Agent = "squad" // Default assumption
}

sessions = append(sessions, session)
Expand Down
Loading
Loading