From 795b08c259100b55187e3c3c220a388aea42f3bf Mon Sep 17 00:00:00 2001 From: root Date: Fri, 15 May 2026 12:00:54 -0500 Subject: [PATCH] chore: remove .squad-templates/ directory Closes #66 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .squad-templates/casting-history.json | 4 - .squad-templates/casting-policy.json | 37 -- .squad-templates/casting-registry.json | 3 - .squad-templates/ceremonies.md | 41 --- .squad-templates/charter.md | 53 --- .squad-templates/constraint-tracking.md | 38 --- .squad-templates/copilot-instructions.md | 46 --- .squad-templates/history.md | 10 - .squad-templates/identity/now.md | 9 - .squad-templates/identity/wisdom.md | 15 - .squad-templates/mcp-config.md | 90 ----- .squad-templates/multi-agent-format.md | 28 -- .squad-templates/orchestration-log.md | 27 -- .squad-templates/plugin-marketplace.md | 49 --- .squad-templates/raw-agent-output.md | 37 -- .squad-templates/roster.md | 60 ---- .squad-templates/routing.md | 54 --- .squad-templates/run-output.md | 50 --- .squad-templates/scribe-charter.md | 119 ------- .squad-templates/skill.md | 24 -- .../skills/squad-conventions/SKILL.md | 69 ---- .squad-templates/workflows/squad-ci.yml | 24 -- .squad-templates/workflows/squad-docs.yml | 50 --- .../workflows/squad-heartbeat.yml | 316 ------------------ .../workflows/squad-insider-release.yml | 61 ---- .../workflows/squad-issue-assign.yml | 161 --------- .../workflows/squad-label-enforce.yml | 181 ---------- .squad-templates/workflows/squad-preview.yml | 55 --- .squad-templates/workflows/squad-promote.yml | 121 ------- .squad-templates/workflows/squad-release.yml | 77 ----- .squad-templates/workflows/squad-triage.yml | 260 -------------- .../workflows/sync-squad-labels.yml | 169 ---------- 32 files changed, 2338 deletions(-) delete mode 100644 .squad-templates/casting-history.json delete mode 100644 .squad-templates/casting-policy.json delete mode 100644 .squad-templates/casting-registry.json delete mode 100644 .squad-templates/ceremonies.md delete mode 100644 .squad-templates/charter.md delete mode 100644 .squad-templates/constraint-tracking.md delete mode 100644 .squad-templates/copilot-instructions.md delete mode 100644 .squad-templates/history.md delete mode 100644 .squad-templates/identity/now.md delete mode 100644 .squad-templates/identity/wisdom.md delete mode 100644 .squad-templates/mcp-config.md delete mode 100644 .squad-templates/multi-agent-format.md delete mode 100644 .squad-templates/orchestration-log.md delete mode 100644 .squad-templates/plugin-marketplace.md delete mode 100644 .squad-templates/raw-agent-output.md delete mode 100644 .squad-templates/roster.md delete mode 100644 .squad-templates/routing.md delete mode 100644 .squad-templates/run-output.md delete mode 100644 .squad-templates/scribe-charter.md delete mode 100644 .squad-templates/skill.md delete mode 100644 .squad-templates/skills/squad-conventions/SKILL.md delete mode 100644 .squad-templates/workflows/squad-ci.yml delete mode 100644 .squad-templates/workflows/squad-docs.yml delete mode 100644 .squad-templates/workflows/squad-heartbeat.yml delete mode 100644 .squad-templates/workflows/squad-insider-release.yml delete mode 100644 .squad-templates/workflows/squad-issue-assign.yml delete mode 100644 .squad-templates/workflows/squad-label-enforce.yml delete mode 100644 .squad-templates/workflows/squad-preview.yml delete mode 100644 .squad-templates/workflows/squad-promote.yml delete mode 100644 .squad-templates/workflows/squad-release.yml delete mode 100644 .squad-templates/workflows/squad-triage.yml delete mode 100644 .squad-templates/workflows/sync-squad-labels.yml diff --git a/.squad-templates/casting-history.json b/.squad-templates/casting-history.json deleted file mode 100644 index bcc5d02..0000000 --- a/.squad-templates/casting-history.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "universe_usage_history": [], - "assignment_cast_snapshots": {} -} diff --git a/.squad-templates/casting-policy.json b/.squad-templates/casting-policy.json deleted file mode 100644 index 1679ae0..0000000 --- a/.squad-templates/casting-policy.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "casting_policy_version": "1.1", - "allowlist_universes": [ - "The Usual Suspects", - "Reservoir Dogs", - "Alien", - "Ocean's Eleven", - "Arrested Development", - "Star Wars", - "The Matrix", - "Firefly", - "The Goonies", - "The Simpsons", - "Breaking Bad", - "Lost", - "Marvel Cinematic Universe", - "DC Universe", - "Star Trek" - ], - "universe_capacity": { - "The Usual Suspects": 6, - "Reservoir Dogs": 8, - "Alien": 8, - "Ocean's Eleven": 14, - "Arrested Development": 15, - "Star Wars": 12, - "The Matrix": 10, - "Firefly": 10, - "The Goonies": 8, - "The Simpsons": 20, - "Breaking Bad": 12, - "Lost": 18, - "Marvel Cinematic Universe": 25, - "DC Universe": 18, - "Star Trek": 14 - } -} diff --git a/.squad-templates/casting-registry.json b/.squad-templates/casting-registry.json deleted file mode 100644 index 8d44cc5..0000000 --- a/.squad-templates/casting-registry.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "agents": {} -} diff --git a/.squad-templates/ceremonies.md b/.squad-templates/ceremonies.md deleted file mode 100644 index 45b4a58..0000000 --- a/.squad-templates/ceremonies.md +++ /dev/null @@ -1,41 +0,0 @@ -# Ceremonies - -> Team meetings that happen before or after work. Each squad configures their own. - -## Design Review - -| Field | Value | -|-------|-------| -| **Trigger** | auto | -| **When** | before | -| **Condition** | multi-agent task involving 2+ agents modifying shared systems | -| **Facilitator** | lead | -| **Participants** | all-relevant | -| **Time budget** | focused | -| **Enabled** | ✅ yes | - -**Agenda:** -1. Review the task and requirements -2. Agree on interfaces and contracts between components -3. Identify risks and edge cases -4. Assign action items - ---- - -## Retrospective - -| Field | Value | -|-------|-------| -| **Trigger** | auto | -| **When** | after | -| **Condition** | build failure, test failure, or reviewer rejection | -| **Facilitator** | lead | -| **Participants** | all-involved | -| **Time budget** | focused | -| **Enabled** | ✅ yes | - -**Agenda:** -1. What happened? (facts only) -2. Root cause analysis -3. What should change? -4. Action items for next iteration diff --git a/.squad-templates/charter.md b/.squad-templates/charter.md deleted file mode 100644 index 03e6c09..0000000 --- a/.squad-templates/charter.md +++ /dev/null @@ -1,53 +0,0 @@ -# {Name} — {Role} - -> {One-line personality statement — what makes this person tick} - -## Identity - -- **Name:** {Name} -- **Role:** {Role title} -- **Expertise:** {2-3 specific skills relevant to the project} -- **Style:** {How they communicate — direct? thorough? opinionated?} - -## What I Own - -- {Area of responsibility 1} -- {Area of responsibility 2} -- {Area of responsibility 3} - -## How I Work - -- {Key approach or principle 1} -- {Key approach or principle 2} -- {Pattern or convention I follow} - -## Boundaries - -**I handle:** {types of work this agent does} - -**I don't handle:** {types of work that belong to other team members} - -**When I'm unsure:** I say so and suggest who might know. - -**If I review others' work:** On rejection, I may require a different agent to revise (not the original author) or request a new specialist be spawned. The Coordinator enforces this. - -## Model - -- **Preferred:** auto -- **Rationale:** Coordinator selects the best model based on task type — cost first unless writing code -- **Fallback:** Standard chain — the coordinator handles fallback automatically - -## Collaboration - -Before starting work, run `git rev-parse --show-toplevel` to find the repo root, or use the `TEAM ROOT` provided in the spawn prompt. All `.squad/` paths must be resolved relative to this root — do not assume CWD is the repo root (you may be in a worktree or subdirectory). - -Before starting work, read `.squad/decisions.md` for team decisions that affect me. -After making a decision others should know, write it to `.squad/decisions/inbox/{my-name}-{brief-slug}.md` — the Scribe will merge it. -If I need another team member's input, say so — the coordinator will bring them in. - -## Voice - -{1-2 sentences describing personality. Not generic — specific. This agent has OPINIONS. -They have preferences. They push back. They have a style that's distinctly theirs. -Example: "Opinionated about test coverage. Will push back if tests are skipped. -Prefers integration tests over mocks. Thinks 80% coverage is the floor, not the ceiling."} diff --git a/.squad-templates/constraint-tracking.md b/.squad-templates/constraint-tracking.md deleted file mode 100644 index 1936c3f..0000000 --- a/.squad-templates/constraint-tracking.md +++ /dev/null @@ -1,38 +0,0 @@ -# Constraint Budget Tracking - -When the user or system imposes constraints (question limits, revision limits, time budgets), maintain a visible counter in your responses and in the artifact. - -## Format - -``` -📊 Clarifying questions used: 2 / 3 -``` - -## Rules - -- Update the counter each time the constraint is consumed -- When a constraint is exhausted, state it: `📊 Question budget exhausted (3/3). Proceeding with current information.` -- If no constraints are active, do not display counters -- Include the final constraint status in multi-agent artifacts - -## Example Session - -``` -Coordinator: Spawning agents to analyze requirements... -📊 Clarifying questions used: 0 / 3 - -Agent asks clarification: "Should we support OAuth?" -Coordinator: Checking with user... -📊 Clarifying questions used: 1 / 3 - -Agent asks clarification: "What's the rate limit?" -Coordinator: Checking with user... -📊 Clarifying questions used: 2 / 3 - -Agent asks clarification: "Do we need RBAC?" -Coordinator: Checking with user... -📊 Clarifying questions used: 3 / 3 - -Agent asks clarification: "Should we cache responses?" -Coordinator: 📊 Question budget exhausted (3/3). Proceeding without clarification. -``` diff --git a/.squad-templates/copilot-instructions.md b/.squad-templates/copilot-instructions.md deleted file mode 100644 index ddc20f1..0000000 --- a/.squad-templates/copilot-instructions.md +++ /dev/null @@ -1,46 +0,0 @@ -# Copilot Coding Agent — Squad Instructions - -You are working on a project that uses **Squad**, an AI team framework. When picking up issues autonomously, follow these guidelines. - -## Team Context - -Before starting work on any issue: - -1. Read `.squad/team.md` for the team roster, member roles, and your capability profile. -2. Read `.squad/routing.md` for work routing rules. -3. If the issue has a `squad:{member}` label, read that member's charter at `.squad/agents/{member}/charter.md` to understand their domain expertise and coding style — work in their voice. - -## Capability Self-Check - -Before starting work, check your capability profile in `.squad/team.md` under the **Coding Agent → Capabilities** section. - -- **🟢 Good fit** — proceed autonomously. -- **🟡 Needs review** — proceed, but note in the PR description that a squad member should review. -- **🔴 Not suitable** — do NOT start work. Instead, comment on the issue: - ``` - 🤖 This issue doesn't match my capability profile (reason: {why}). Suggesting reassignment to a squad member. - ``` - -## Branch Naming - -Use the squad branch convention: -``` -squad/{issue-number}-{kebab-case-slug} -``` -Example: `squad/42-fix-login-validation` - -## PR Guidelines - -When opening a PR: -- Reference the issue: `Closes #{issue-number}` -- If the issue had a `squad:{member}` label, mention the member: `Working as {member} ({role})` -- If this is a 🟡 needs-review task, add to the PR description: `⚠️ This task was flagged as "needs review" — please have a squad member review before merging.` -- Follow any project conventions in `.squad/decisions.md` - -## Decisions - -If you make a decision that affects other team members, write it to: -``` -.squad/decisions/inbox/copilot-{brief-slug}.md -``` -The Scribe will merge it into the shared decisions file. diff --git a/.squad-templates/history.md b/.squad-templates/history.md deleted file mode 100644 index d975a5c..0000000 --- a/.squad-templates/history.md +++ /dev/null @@ -1,10 +0,0 @@ -# Project Context - -- **Owner:** {user name} -- **Project:** {project description} -- **Stack:** {languages, frameworks, tools} -- **Created:** {timestamp} - -## Learnings - - diff --git a/.squad-templates/identity/now.md b/.squad-templates/identity/now.md deleted file mode 100644 index 04e1dfe..0000000 --- a/.squad-templates/identity/now.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -updated_at: {timestamp} -focus_area: {brief description} -active_issues: [] ---- - -# What We're Focused On - -{Narrative description of current focus — 1-3 sentences. Updated by coordinator at session start.} diff --git a/.squad-templates/identity/wisdom.md b/.squad-templates/identity/wisdom.md deleted file mode 100644 index c3b978e..0000000 --- a/.squad-templates/identity/wisdom.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -last_updated: {timestamp} ---- - -# Team Wisdom - -Reusable patterns and heuristics learned through work. NOT transcripts — each entry is a distilled, actionable insight. - -## Patterns - - - -## Anti-Patterns - - diff --git a/.squad-templates/mcp-config.md b/.squad-templates/mcp-config.md deleted file mode 100644 index 2e361ee..0000000 --- a/.squad-templates/mcp-config.md +++ /dev/null @@ -1,90 +0,0 @@ -# MCP Integration — Configuration and Samples - -MCP (Model Context Protocol) servers extend Squad with tools for external services — Trello, Aspire dashboards, Azure, Notion, and more. The user configures MCP servers in their environment; Squad discovers and uses them. - -> **Full patterns:** Read `.squad/skills/mcp-tool-discovery/SKILL.md` for discovery patterns, domain-specific usage, and graceful degradation. - -## Config File Locations - -Users configure MCP servers at these locations (checked in priority order): -1. **Repository-level:** `.copilot/mcp-config.json` (team-shared, committed to repo) -2. **Workspace-level:** `.vscode/mcp.json` (VS Code workspaces) -3. **User-level:** `~/.copilot/mcp-config.json` (personal) -4. **CLI override:** `--additional-mcp-config` flag (session-specific) - -## Sample Config — Trello - -```json -{ - "mcpServers": { - "trello": { - "command": "npx", - "args": ["-y", "@trello/mcp-server"], - "env": { - "TRELLO_API_KEY": "${TRELLO_API_KEY}", - "TRELLO_TOKEN": "${TRELLO_TOKEN}" - } - } - } -} -``` - -## Sample Config — GitHub - -```json -{ - "mcpServers": { - "github": { - "command": "npx", - "args": ["-y", "@modelcontextprotocol/server-github"], - "env": { - "GITHUB_TOKEN": "${GITHUB_TOKEN}" - } - } - } -} -``` - -## Sample Config — Azure - -```json -{ - "mcpServers": { - "azure": { - "command": "npx", - "args": ["-y", "@azure/mcp-server"], - "env": { - "AZURE_SUBSCRIPTION_ID": "${AZURE_SUBSCRIPTION_ID}", - "AZURE_CLIENT_ID": "${AZURE_CLIENT_ID}", - "AZURE_CLIENT_SECRET": "${AZURE_CLIENT_SECRET}", - "AZURE_TENANT_ID": "${AZURE_TENANT_ID}" - } - } - } -} -``` - -## Sample Config — Aspire - -```json -{ - "mcpServers": { - "aspire": { - "command": "npx", - "args": ["-y", "@aspire/mcp-server"], - "env": { - "ASPIRE_DASHBOARD_URL": "${ASPIRE_DASHBOARD_URL}" - } - } - } -} -``` - -## Authentication Notes - -- **GitHub MCP requires a separate token** from the `gh` CLI auth. Generate at https://github.com/settings/tokens -- **Trello requires API key + token** from https://trello.com/power-ups/admin -- **Azure requires service principal credentials** — see Azure docs for setup -- **Aspire uses the dashboard URL** — typically `http://localhost:18888` during local dev - -Auth is a real blocker for some MCP servers. Users need separate tokens for GitHub MCP, Azure MCP, Trello MCP, etc. This is a documentation problem, not a code problem. diff --git a/.squad-templates/multi-agent-format.md b/.squad-templates/multi-agent-format.md deleted file mode 100644 index b655ee9..0000000 --- a/.squad-templates/multi-agent-format.md +++ /dev/null @@ -1,28 +0,0 @@ -# Multi-Agent Artifact Format - -When multiple agents contribute to a final artifact (document, analysis, design), use this format. The assembled result must include: - -- Termination condition -- Constraint budgets (if active) -- Reviewer verdicts (if any) -- Raw agent outputs appendix - -## Assembly Structure - -The assembled result goes at the top. Below it, include: - -``` -## APPENDIX: RAW AGENT OUTPUTS - -### {Name} ({Role}) — Raw Output -{Paste agent's verbatim response here, unedited} - -### {Name} ({Role}) — Raw Output -{Paste agent's verbatim response here, unedited} -``` - -## Appendix Rules - -This appendix is for diagnostic integrity. Do not edit, summarize, or polish the raw outputs. The Coordinator may not rewrite raw agent outputs; it may only paste them verbatim and assemble the final artifact above. - -See `.squad/templates/run-output.md` for the complete output format template. diff --git a/.squad-templates/orchestration-log.md b/.squad-templates/orchestration-log.md deleted file mode 100644 index 37d94d1..0000000 --- a/.squad-templates/orchestration-log.md +++ /dev/null @@ -1,27 +0,0 @@ -# Orchestration Log Entry - -> One file per agent spawn. Saved to `.squad/orchestration-log/{timestamp}-{agent-name}.md` - ---- - -### {timestamp} — {task summary} - -| Field | Value | -|-------|-------| -| **Agent routed** | {Name} ({Role}) | -| **Why chosen** | {Routing rationale — what in the request matched this agent} | -| **Mode** | {`background` / `sync`} | -| **Why this mode** | {Brief reason — e.g., "No hard data dependencies" or "User needs to approve architecture"} | -| **Files authorized to read** | {Exact file paths the agent was told to read} | -| **File(s) agent must produce** | {Exact file paths the agent is expected to create or modify} | -| **Outcome** | {Completed / Rejected by {Reviewer} / Escalated} | - ---- - -## Rules - -1. **One file per agent spawn.** Named `{timestamp}-{agent-name}.md`. -2. **Log BEFORE spawning.** The entry must exist before the agent runs. -3. **Update outcome AFTER the agent completes.** Fill in the Outcome field. -4. **Never delete or edit past entries.** Append-only. -5. **If a reviewer rejects work,** log the rejection as a new entry with the revision agent. diff --git a/.squad-templates/plugin-marketplace.md b/.squad-templates/plugin-marketplace.md deleted file mode 100644 index 8936328..0000000 --- a/.squad-templates/plugin-marketplace.md +++ /dev/null @@ -1,49 +0,0 @@ -# Plugin Marketplace - -Plugins are curated agent templates, skills, instructions, and prompts shared by the community via GitHub repositories (e.g., `github/awesome-copilot`, `anthropics/skills`). They provide ready-made expertise for common domains — cloud platforms, frameworks, testing strategies, etc. - -## Marketplace State - -Registered marketplace sources are stored in `.squad/plugins/marketplaces.json`: - -```json -{ - "marketplaces": [ - { - "name": "awesome-copilot", - "source": "github/awesome-copilot", - "added_at": "2026-02-14T00:00:00Z" - } - ] -} -``` - -## CLI Commands - -Users manage marketplaces via the CLI: -- `squad plugin marketplace add {owner/repo}` — Register a GitHub repo as a marketplace source -- `squad plugin marketplace remove {name}` — Remove a registered marketplace -- `squad plugin marketplace list` — List registered marketplaces -- `squad plugin marketplace browse {name}` — List available plugins in a marketplace - -## When to Browse - -During the **Adding Team Members** flow, AFTER allocating a name but BEFORE generating the charter: - -1. Read `.squad/plugins/marketplaces.json`. If the file doesn't exist or `marketplaces` is empty, skip silently. -2. For each registered marketplace, search for plugins whose name or description matches the new member's role or domain keywords. -3. Present matching plugins to the user: *"Found '{plugin-name}' in {marketplace} marketplace — want me to install it as a skill for {CastName}?"* -4. If the user accepts, install the plugin (see below). If they decline or skip, proceed without it. - -## How to Install a Plugin - -1. Read the plugin content from the marketplace repository (the plugin's `SKILL.md` or equivalent). -2. Copy it into the agent's skills directory: `.squad/skills/{plugin-name}/SKILL.md` -3. If the plugin includes charter-level instructions (role boundaries, tool preferences), merge those into the agent's `charter.md`. -4. Log the installation in the agent's `history.md`: *"📦 Plugin '{plugin-name}' installed from {marketplace}."* - -## Graceful Degradation - -- **No marketplaces configured:** Skip the marketplace check entirely. No warning, no prompt. -- **Marketplace unreachable:** Warn the user (*"⚠ Couldn't reach {marketplace} — continuing without it"*) and proceed with team member creation normally. -- **No matching plugins:** Inform the user (*"No matching plugins found in configured marketplaces"*) and proceed. diff --git a/.squad-templates/raw-agent-output.md b/.squad-templates/raw-agent-output.md deleted file mode 100644 index fa00682..0000000 --- a/.squad-templates/raw-agent-output.md +++ /dev/null @@ -1,37 +0,0 @@ -# Raw Agent Output — Appendix Format - -> This template defines the format for the `## APPENDIX: RAW AGENT OUTPUTS` section -> in any multi-agent artifact. - -## Rules - -1. **Verbatim only.** Paste the agent's response exactly as returned. No edits. -2. **No summarizing.** Do not condense, paraphrase, or rephrase any part of the output. -3. **No rewriting.** Do not fix typos, grammar, formatting, or style. -4. **No code fences around the entire output.** The raw output is pasted as-is, not wrapped in ``` blocks. -5. **One section per agent.** Each agent that contributed gets its own heading. -6. **Order matches work order.** List agents in the order they were spawned. -7. **Include all outputs.** Even if an agent's work was rejected, include their output for diagnostic traceability. - -## Format - -```markdown -## APPENDIX: RAW AGENT OUTPUTS - -### {Name} ({Role}) — Raw Output - -{Paste agent's verbatim response here, unedited} - -### {Name} ({Role}) — Raw Output - -{Paste agent's verbatim response here, unedited} -``` - -## Why This Exists - -The appendix provides diagnostic integrity. It lets anyone verify: -- What each agent actually said (vs. what the Coordinator assembled) -- Whether the Coordinator faithfully represented agent work -- What was lost or changed in synthesis - -Without raw outputs, multi-agent collaboration is unauditable. diff --git a/.squad-templates/roster.md b/.squad-templates/roster.md deleted file mode 100644 index b25430d..0000000 --- a/.squad-templates/roster.md +++ /dev/null @@ -1,60 +0,0 @@ -# Team Roster - -> {One-line project description} - -## Coordinator - -| Name | Role | Notes | -|------|------|-------| -| Squad | Coordinator | Routes work, enforces handoffs and reviewer gates. Does not generate domain artifacts. | - -## Members - -| Name | Role | Charter | Status | -|------|------|---------|--------| -| {Name} | {Role} | `.squad/agents/{name}/charter.md` | ✅ Active | -| {Name} | {Role} | `.squad/agents/{name}/charter.md` | ✅ Active | -| {Name} | {Role} | `.squad/agents/{name}/charter.md` | ✅ Active | -| {Name} | {Role} | `.squad/agents/{name}/charter.md` | ✅ Active | -| Scribe | Session Logger | `.squad/agents/scribe/charter.md` | 📋 Silent | -| Ralph | Work Monitor | — | 🔄 Monitor | - -## Coding Agent - - - -| Name | Role | Charter | Status | -|------|------|---------|--------| -| @copilot | Coding Agent | — | 🤖 Coding Agent | - -### Capabilities - -**🟢 Good fit — auto-route when enabled:** -- Bug fixes with clear reproduction steps -- Test coverage (adding missing tests, fixing flaky tests) -- Lint/format fixes and code style cleanup -- Dependency updates and version bumps -- Small isolated features with clear specs -- Boilerplate/scaffolding generation -- Documentation fixes and README updates - -**🟡 Needs review — route to @copilot but flag for squad member PR review:** -- Medium features with clear specs and acceptance criteria -- Refactoring with existing test coverage -- API endpoint additions following established patterns -- Migration scripts with well-defined schemas - -**🔴 Not suitable — route to squad member instead:** -- Architecture decisions and system design -- Multi-system integration requiring coordination -- Ambiguous requirements needing clarification -- Security-critical changes (auth, encryption, access control) -- Performance-critical paths requiring benchmarking -- Changes requiring cross-team discussion - -## Project Context - -- **Owner:** {user name} -- **Stack:** {languages, frameworks, tools} -- **Description:** {what the project does, in one sentence} -- **Created:** {timestamp} diff --git a/.squad-templates/routing.md b/.squad-templates/routing.md deleted file mode 100644 index 490b128..0000000 --- a/.squad-templates/routing.md +++ /dev/null @@ -1,54 +0,0 @@ -# Work Routing - -How to decide who handles what. - -## Routing Table - -| Work Type | Route To | Examples | -|-----------|----------|----------| -| {domain 1} | {Name} | {example tasks} | -| {domain 2} | {Name} | {example tasks} | -| {domain 3} | {Name} | {example tasks} | -| Code review | {Name} | Review PRs, check quality, suggest improvements | -| Testing | {Name} | Write tests, find edge cases, verify fixes | -| Scope & priorities | {Name} | What to build next, trade-offs, decisions | -| Async issue work (bugs, tests, small features) | @copilot 🤖 | Well-defined tasks matching capability profile | -| Session logging | Scribe | Automatic — never needs routing | - -## Issue Routing - -| Label | Action | Who | -|-------|--------|-----| -| `squad` | Triage: analyze issue, evaluate @copilot fit, assign `squad:{member}` label | Lead | -| `squad:{name}` | Pick up issue and complete the work | Named member | -| `squad:copilot` | Assign to @copilot for autonomous work (if enabled) | @copilot 🤖 | - -### How Issue Assignment Works - -1. When a GitHub issue gets the `squad` label, the **Lead** triages it — analyzing content, evaluating @copilot's capability profile, assigning the right `squad:{member}` label, and commenting with triage notes. -2. **@copilot evaluation:** The Lead checks if the issue matches @copilot's capability profile (🟢 good fit / 🟡 needs review / 🔴 not suitable). If it's a good fit, the Lead may route to `squad:copilot` instead of a squad member. -3. When a `squad:{member}` label is applied, that member picks up the issue in their next session. -4. When `squad:copilot` is applied and auto-assign is enabled, `@copilot` is assigned on the issue and picks it up autonomously. -5. Members can reassign by removing their label and adding another member's label. -6. The `squad` label is the "inbox" — untriaged issues waiting for Lead review. - -### Lead Triage Guidance for @copilot - -When triaging, the Lead should ask: - -1. **Is this well-defined?** Clear title, reproduction steps or acceptance criteria, bounded scope → likely 🟢 -2. **Does it follow existing patterns?** Adding a test, fixing a known bug, updating a dependency → likely 🟢 -3. **Does it need design judgment?** Architecture, API design, UX decisions → likely 🔴 -4. **Is it security-sensitive?** Auth, encryption, access control → always 🔴 -5. **Is it medium complexity with specs?** Feature with clear requirements, refactoring with tests → likely 🟡 - -## Rules - -1. **Eager by default** — spawn all agents who could usefully start work, including anticipatory downstream work. -2. **Scribe always runs** after substantial work, always as `mode: "background"`. Never blocks. -3. **Quick facts → coordinator answers directly.** Don't spawn an agent for "what port does the server run on?" -4. **When two agents could handle it**, pick the one whose domain is the primary concern. -5. **"Team, ..." → fan-out.** Spawn all relevant agents in parallel as `mode: "background"`. -6. **Anticipate downstream work.** If a feature is being built, spawn the tester to write test cases from requirements simultaneously. -7. **Issue-labeled work** — when a `squad:{member}` label is applied to an issue, route to that member. The Lead handles all `squad` (base label) triage. -8. **@copilot routing** — when evaluating issues, check @copilot's capability profile in `team.md`. Route 🟢 good-fit tasks to `squad:copilot`. Flag 🟡 needs-review tasks for PR review. Keep 🔴 not-suitable tasks with squad members. diff --git a/.squad-templates/run-output.md b/.squad-templates/run-output.md deleted file mode 100644 index 8a9efbc..0000000 --- a/.squad-templates/run-output.md +++ /dev/null @@ -1,50 +0,0 @@ -# Run Output — {task title} - -> Final assembled artifact from a multi-agent run. - -## Termination Condition - -**Reason:** {One of: User accepted | Reviewer approved | Constraint budget exhausted | Deadlock — escalated to user | User cancelled} - -## Constraint Budgets - - - -| Constraint | Used | Max | Status | -|------------|------|-----|--------| -| Clarifying questions | 📊 {n} | {max} | {Active / Exhausted} | -| Revision cycles | 📊 {n} | {max} | {Active / Exhausted} | - -## Result - -{Assembled final artifact goes here. This is the Coordinator's synthesis of agent outputs.} - ---- - -## Reviewer Verdict - - - -### Review by {Name} ({Role}) - -| Field | Value | -|-------|-------| -| **Verdict** | {Approved / Rejected} | -| **What's wrong** | {Specific issue — not vague} | -| **Why it matters** | {Impact if not fixed} | -| **Who fixes it** | {Name of agent assigned to revise — MUST NOT be the original author} | -| **Revision budget** | 📊 {used} / {max} revision cycles remaining | - ---- - -## APPENDIX: RAW AGENT OUTPUTS - - - -### {Name} ({Role}) — Raw Output - -{Paste agent's verbatim response here, unedited} - -### {Name} ({Role}) — Raw Output - -{Paste agent's verbatim response here, unedited} diff --git a/.squad-templates/scribe-charter.md b/.squad-templates/scribe-charter.md deleted file mode 100644 index 9082faa..0000000 --- a/.squad-templates/scribe-charter.md +++ /dev/null @@ -1,119 +0,0 @@ -# Scribe - -> The team's memory. Silent, always present, never forgets. - -## Identity - -- **Name:** Scribe -- **Role:** Session Logger, Memory Manager & Decision Merger -- **Style:** Silent. Never speaks to the user. Works in the background. -- **Mode:** Always spawned as `mode: "background"`. Never blocks the conversation. - -## What I Own - -- `.squad/log/` — session logs (what happened, who worked, what was decided) -- `.squad/decisions.md` — the shared decision log all agents read (canonical, merged) -- `.squad/decisions/inbox/` — decision drop-box (agents write here, I merge) -- Cross-agent context propagation — when one agent's decision affects another - -## How I Work - -**Worktree awareness:** Use the `TEAM ROOT` provided in the spawn prompt to resolve all `.squad/` paths. If no TEAM ROOT is given, run `git rev-parse --show-toplevel` as fallback. Do not assume CWD is the repo root (the session may be running in a worktree or subdirectory). - -After every substantial work session: - -1. **Log the session** to `.squad/log/{timestamp}-{topic}.md`: - - Who worked - - What was done - - Decisions made - - Key outcomes - - Brief. Facts only. - -2. **Merge the decision inbox:** - - Read all files in `.squad/decisions/inbox/` - - APPEND each decision's contents to `.squad/decisions.md` - - Delete each inbox file after merging - -3. **Deduplicate and consolidate decisions.md:** - - Parse the file into decision blocks (each block starts with `### `). - - **Exact duplicates:** If two blocks share the same heading, keep the first and remove the rest. - - **Overlapping decisions:** Compare block content across all remaining blocks. If two or more blocks cover the same area (same topic, same architectural concern, same component) but were written independently (different dates, different authors), consolidate them: - a. Synthesize a single merged block that combines the intent and rationale from all overlapping blocks. - b. Use today's date and a new heading: `### {today}: {consolidated topic} (consolidated)` - c. Credit all original authors: `**By:** {Name1}, {Name2}` - d. Under **What:**, combine the decisions. Note any differences or evolution. - e. Under **Why:**, merge the rationale, preserving unique reasoning from each. - f. Remove the original overlapping blocks. - - Write the updated file back. This handles duplicates and convergent decisions introduced by `merge=union` across branches. - -4. **Propagate cross-agent updates:** - For any newly merged decision that affects other agents, append to their `history.md`: - ``` - 📌 Team update ({timestamp}): {summary} — decided by {Name} - ``` - -5. **Commit `.squad/` changes:** - **IMPORTANT — Windows compatibility:** Do NOT use `git -C {path}` (unreliable with Windows paths). - Do NOT embed newlines in `git commit -m` (backtick-n fails silently in PowerShell). - Instead: - - `cd` into the team root first. - - Stage all `.squad/` files: `git add .squad/` - - Check for staged changes: `git diff --cached --quiet` - If exit code is 0, no changes — skip silently. - - Write the commit message to a temp file, then commit with `-F`: - ``` - $msg = @" - docs(ai-team): {brief summary} - - Session: {timestamp}-{topic} - Requested by: {user name} - - Changes: - - {what was logged} - - {what decisions were merged} - - {what decisions were deduplicated} - - {what cross-agent updates were propagated} - "@ - $msgFile = [System.IO.Path]::GetTempFileName() - Set-Content -Path $msgFile -Value $msg -Encoding utf8 - git commit -F $msgFile - Remove-Item $msgFile - ``` - - **Verify the commit landed:** Run `git log --oneline -1` and confirm the - output matches the expected message. If it doesn't, report the error. - -6. **Never speak to the user.** Never appear in responses. Work silently. - -## The Memory Architecture - -``` -.squad/ -├── decisions.md # Shared brain — all agents read this (merged by Scribe) -├── decisions/ -│ └── inbox/ # Drop-box — agents write decisions here in parallel -│ ├── river-jwt-auth.md -│ └── kai-component-lib.md -├── orchestration-log/ # Per-spawn log entries -│ ├── 2025-07-01T10-00-river.md -│ └── 2025-07-01T10-00-kai.md -├── log/ # Session history — searchable record -│ ├── 2025-07-01-setup.md -│ └── 2025-07-02-api.md -└── agents/ - ├── kai/history.md # Kai's personal knowledge - ├── river/history.md # River's personal knowledge - └── ... -``` - -- **decisions.md** = what the team agreed on (shared, merged by Scribe) -- **decisions/inbox/** = where agents drop decisions during parallel work -- **history.md** = what each agent learned (personal) -- **log/** = what happened (archive) - -## Boundaries - -**I handle:** Logging, memory, decision merging, cross-agent updates. - -**I don't handle:** Any domain work. I don't write code, review PRs, or make decisions. - -**I am invisible.** If a user notices me, something went wrong. diff --git a/.squad-templates/skill.md b/.squad-templates/skill.md deleted file mode 100644 index c747db9..0000000 --- a/.squad-templates/skill.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -name: "{skill-name}" -description: "{what this skill teaches agents}" -domain: "{e.g., testing, api-design, error-handling}" -confidence: "low|medium|high" -source: "{how this was learned: manual, observed, earned}" -tools: - # Optional — declare MCP tools relevant to this skill's patterns - # - name: "{tool-name}" - # description: "{what this tool does}" - # when: "{when to use this tool}" ---- - -## Context -{When and why this skill applies} - -## Patterns -{Specific patterns, conventions, or approaches} - -## Examples -{Code examples or references} - -## Anti-Patterns -{What to avoid} diff --git a/.squad-templates/skills/squad-conventions/SKILL.md b/.squad-templates/skills/squad-conventions/SKILL.md deleted file mode 100644 index 72eca68..0000000 --- a/.squad-templates/skills/squad-conventions/SKILL.md +++ /dev/null @@ -1,69 +0,0 @@ ---- -name: "squad-conventions" -description: "Core conventions and patterns used in the Squad codebase" -domain: "project-conventions" -confidence: "high" -source: "manual" ---- - -## Context -These conventions apply to all work on the Squad CLI tool (`create-squad`). Squad is a zero-dependency Node.js package that adds AI agent teams to any project. Understanding these patterns is essential before modifying any Squad source code. - -## Patterns - -### Zero Dependencies -Squad has zero runtime dependencies. Everything uses Node.js built-ins (`fs`, `path`, `os`, `child_process`). Do not add packages to `dependencies` in `package.json`. This is a hard constraint, not a preference. - -### Node.js Built-in Test Runner -Tests use `node:test` and `node:assert/strict` — no test frameworks. Run with `npm test`. Test files live in `test/`. The test command is `node --test test/`. - -### Error Handling — `fatal()` Pattern -All user-facing errors use the `fatal(msg)` function which prints a red `✗` prefix and exits with code 1. Never throw unhandled exceptions or print raw stack traces. The global `uncaughtException` handler calls `fatal()` as a safety net. - -### ANSI Color Constants -Colors are defined as constants at the top of `index.js`: `GREEN`, `RED`, `DIM`, `BOLD`, `RESET`. Use these constants — do not inline ANSI escape codes. - -### File Structure -- `.squad/` — Team state (user-owned, never overwritten by upgrades) -- `.squad/templates/` — Template files copied from `templates/` (Squad-owned, overwritten on upgrade) -- `.github/agents/squad.agent.md` — Coordinator prompt (Squad-owned, overwritten on upgrade) -- `templates/` — Source templates shipped with the npm package -- `.squad/skills/` — Team skills in SKILL.md format (user-owned) -- `.squad/decisions/inbox/` — Drop-box for parallel decision writes - -### Windows Compatibility -Always use `path.join()` for file paths — never hardcode `/` or `\` separators. Squad must work on Windows, macOS, and Linux. All tests must pass on all platforms. - -### Init Idempotency -The init flow uses a skip-if-exists pattern: if a file or directory already exists, skip it and report "already exists." Never overwrite user state during init. The upgrade flow overwrites only Squad-owned files. - -### Copy Pattern -`copyRecursive(src, target)` handles both files and directories. It creates parent directories with `{ recursive: true }` and uses `fs.copyFileSync` for files. - -## Examples - -```javascript -// Error handling -function fatal(msg) { - console.error(`${RED}✗${RESET} ${msg}`); - process.exit(1); -} - -// File path construction (Windows-safe) -const agentDest = path.join(dest, '.github', 'agents', 'squad.agent.md'); - -// Skip-if-exists pattern -if (!fs.existsSync(ceremoniesDest)) { - fs.copyFileSync(ceremoniesSrc, ceremoniesDest); - console.log(`${GREEN}✓${RESET} .squad/ceremonies.md`); -} else { - console.log(`${DIM}ceremonies.md already exists — skipping${RESET}`); -} -``` - -## Anti-Patterns -- **Adding npm dependencies** — Squad is zero-dep. Use Node.js built-ins only. -- **Hardcoded path separators** — Never use `/` or `\` directly. Always `path.join()`. -- **Overwriting user state on init** — Init skips existing files. Only upgrade overwrites Squad-owned files. -- **Raw stack traces** — All errors go through `fatal()`. Users see clean messages, not stack traces. -- **Inline ANSI codes** — Use the color constants (`GREEN`, `RED`, `DIM`, `BOLD`, `RESET`). diff --git a/.squad-templates/workflows/squad-ci.yml b/.squad-templates/workflows/squad-ci.yml deleted file mode 100644 index 2f809d7..0000000 --- a/.squad-templates/workflows/squad-ci.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Squad CI - -on: - pull_request: - branches: [dev, preview, main, insider] - types: [opened, synchronize, reopened] - push: - branches: [dev, insider] - -permissions: - contents: read - -jobs: - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-node@v4 - with: - node-version: 22 - - - name: Run tests - run: node --test test/*.test.js diff --git a/.squad-templates/workflows/squad-docs.yml b/.squad-templates/workflows/squad-docs.yml deleted file mode 100644 index 307d502..0000000 --- a/.squad-templates/workflows/squad-docs.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: Squad Docs — Build & Deploy - -on: - workflow_dispatch: - push: - branches: [preview] - paths: - - 'docs/**' - - '.github/workflows/squad-docs.yml' - -permissions: - contents: read - pages: write - id-token: write - -concurrency: - group: pages - cancel-in-progress: true - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-node@v4 - with: - node-version: '22' - - - name: Install build dependencies - run: npm install --no-save markdown-it markdown-it-anchor - - - name: Build docs site - run: node docs/build.js --out _site --base /squad - - - name: Upload Pages artifact - uses: actions/upload-pages-artifact@v3 - with: - path: _site - - deploy: - needs: build - runs-on: ubuntu-latest - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 diff --git a/.squad-templates/workflows/squad-heartbeat.yml b/.squad-templates/workflows/squad-heartbeat.yml deleted file mode 100644 index 62fcb66..0000000 --- a/.squad-templates/workflows/squad-heartbeat.yml +++ /dev/null @@ -1,316 +0,0 @@ -name: Squad Heartbeat (Ralph) - -on: - # schedule: - # Cron disabled by default — runs too many Actions minutes across repos. - # Uncomment below (and the 'schedule:' key) for proactive 30-min polling: - # - cron: '*/30 * * * *' - - # React to completed work or new squad work - issues: - types: [closed, labeled] - pull_request: - types: [closed] - - # Manual trigger - workflow_dispatch: - -permissions: - issues: write - contents: read - pull-requests: read - -jobs: - heartbeat: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Ralph — Check for squad work - uses: actions/github-script@v7 - with: - script: | - const fs = require('fs'); - - // Read team roster — check .squad/ first, fall back to .ai-team/ - let teamFile = '.squad/team.md'; - if (!fs.existsSync(teamFile)) { - teamFile = '.ai-team/team.md'; - } - if (!fs.existsSync(teamFile)) { - core.info('No .squad/team.md or .ai-team/team.md found — Ralph has nothing to monitor'); - return; - } - - const content = fs.readFileSync(teamFile, 'utf8'); - - // Check if Ralph is on the roster - if (!content.includes('Ralph') || !content.includes('🔄')) { - core.info('Ralph not on roster — heartbeat disabled'); - return; - } - - // Parse members from roster - const lines = content.split('\n'); - const members = []; - let inMembersTable = false; - for (const line of lines) { - if (line.match(/^##\s+(Members|Team Roster)/i)) { - inMembersTable = true; - continue; - } - if (inMembersTable && line.startsWith('## ')) break; - if (inMembersTable && line.startsWith('|') && !line.includes('---') && !line.includes('Name')) { - const cells = line.split('|').map(c => c.trim()).filter(Boolean); - if (cells.length >= 2 && !['Scribe', 'Ralph'].includes(cells[0])) { - members.push({ - name: cells[0], - role: cells[1], - label: `squad:${cells[0].toLowerCase()}` - }); - } - } - } - - if (members.length === 0) { - core.info('No squad members found — nothing to monitor'); - return; - } - - // 1. Find untriaged issues (labeled "squad" but no "squad:{member}" label) - const { data: squadIssues } = await github.rest.issues.listForRepo({ - owner: context.repo.owner, - repo: context.repo.repo, - labels: 'squad', - state: 'open', - per_page: 20 - }); - - const memberLabels = members.map(m => m.label); - const untriaged = squadIssues.filter(issue => { - const issueLabels = issue.labels.map(l => l.name); - return !memberLabels.some(ml => issueLabels.includes(ml)); - }); - - // 2. Find assigned but unstarted issues (has squad:{member} label, no assignee) - const unstarted = []; - for (const member of members) { - try { - const { data: memberIssues } = await github.rest.issues.listForRepo({ - owner: context.repo.owner, - repo: context.repo.repo, - labels: member.label, - state: 'open', - per_page: 10 - }); - for (const issue of memberIssues) { - if (!issue.assignees || issue.assignees.length === 0) { - unstarted.push({ issue, member }); - } - } - } catch (e) { - // Label may not exist yet - } - } - - // 3. Find squad issues missing triage verdict (no go:* label) - const missingVerdict = squadIssues.filter(issue => { - const labels = issue.labels.map(l => l.name); - return !labels.some(l => l.startsWith('go:')); - }); - - // 4. Find go:yes issues missing release target - const goYesIssues = squadIssues.filter(issue => { - const labels = issue.labels.map(l => l.name); - return labels.includes('go:yes') && !labels.some(l => l.startsWith('release:')); - }); - - // 4b. Find issues missing type: label - const missingType = squadIssues.filter(issue => { - const labels = issue.labels.map(l => l.name); - return !labels.some(l => l.startsWith('type:')); - }); - - // 5. Find open PRs that need attention - const { data: openPRs } = await github.rest.pulls.list({ - owner: context.repo.owner, - repo: context.repo.repo, - state: 'open', - per_page: 20 - }); - - const squadPRs = openPRs.filter(pr => - pr.labels.some(l => l.name.startsWith('squad')) - ); - - // Build status summary - const summary = []; - if (untriaged.length > 0) { - summary.push(`🔴 **${untriaged.length} untriaged issue(s)** need triage`); - } - if (unstarted.length > 0) { - summary.push(`🟡 **${unstarted.length} assigned issue(s)** have no assignee`); - } - if (missingVerdict.length > 0) { - summary.push(`⚪ **${missingVerdict.length} issue(s)** missing triage verdict (no \`go:\` label)`); - } - if (goYesIssues.length > 0) { - summary.push(`⚪ **${goYesIssues.length} approved issue(s)** missing release target (no \`release:\` label)`); - } - if (missingType.length > 0) { - summary.push(`⚪ **${missingType.length} issue(s)** missing \`type:\` label`); - } - if (squadPRs.length > 0) { - const drafts = squadPRs.filter(pr => pr.draft).length; - const ready = squadPRs.length - drafts; - if (drafts > 0) summary.push(`🟡 **${drafts} draft PR(s)** in progress`); - if (ready > 0) summary.push(`🟢 **${ready} PR(s)** open for review/merge`); - } - - if (summary.length === 0) { - core.info('📋 Board is clear — Ralph found no pending work'); - return; - } - - core.info(`🔄 Ralph found work:\n${summary.join('\n')}`); - - // Auto-triage untriaged issues - for (const issue of untriaged) { - const issueText = `${issue.title}\n${issue.body || ''}`.toLowerCase(); - let assignedMember = null; - let reason = ''; - - // Simple keyword-based routing - for (const member of members) { - const role = member.role.toLowerCase(); - if ((role.includes('frontend') || role.includes('ui')) && - (issueText.includes('ui') || issueText.includes('frontend') || - issueText.includes('css') || issueText.includes('component'))) { - assignedMember = member; - reason = 'Matches frontend/UI domain'; - break; - } - if ((role.includes('backend') || role.includes('api') || role.includes('server')) && - (issueText.includes('api') || issueText.includes('backend') || - issueText.includes('database') || issueText.includes('endpoint'))) { - assignedMember = member; - reason = 'Matches backend/API domain'; - break; - } - if ((role.includes('test') || role.includes('qa')) && - (issueText.includes('test') || issueText.includes('bug') || - issueText.includes('fix') || issueText.includes('regression'))) { - assignedMember = member; - reason = 'Matches testing/QA domain'; - break; - } - } - - // Default to Lead - if (!assignedMember) { - const lead = members.find(m => - m.role.toLowerCase().includes('lead') || - m.role.toLowerCase().includes('architect') - ); - if (lead) { - assignedMember = lead; - reason = 'No domain match — routed to Lead'; - } - } - - if (assignedMember) { - // Add member label - await github.rest.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - labels: [assignedMember.label] - }); - - // Post triage comment - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - body: [ - `### 🔄 Ralph — Auto-Triage`, - '', - `**Assigned to:** ${assignedMember.name} (${assignedMember.role})`, - `**Reason:** ${reason}`, - '', - `> Ralph auto-triaged this issue via the squad heartbeat. To reassign, swap the \`squad:*\` label.` - ].join('\n') - }); - - core.info(`Auto-triaged #${issue.number} → ${assignedMember.name}`); - } - } - - # Copilot auto-assign step (uses PAT if available) - - name: Ralph — Assign @copilot issues - if: success() - uses: actions/github-script@v7 - with: - github-token: ${{ secrets.COPILOT_ASSIGN_TOKEN || secrets.GITHUB_TOKEN }} - script: | - const fs = require('fs'); - - let teamFile = '.squad/team.md'; - if (!fs.existsSync(teamFile)) { - teamFile = '.ai-team/team.md'; - } - if (!fs.existsSync(teamFile)) return; - - const content = fs.readFileSync(teamFile, 'utf8'); - - // Check if @copilot is on the team with auto-assign - const hasCopilot = content.includes('🤖 Coding Agent') || content.includes('@copilot'); - const autoAssign = content.includes(''); - if (!hasCopilot || !autoAssign) return; - - // Find issues labeled squad:copilot with no assignee - try { - const { data: copilotIssues } = await github.rest.issues.listForRepo({ - owner: context.repo.owner, - repo: context.repo.repo, - labels: 'squad:copilot', - state: 'open', - per_page: 5 - }); - - const unassigned = copilotIssues.filter(i => - !i.assignees || i.assignees.length === 0 - ); - - if (unassigned.length === 0) { - core.info('No unassigned squad:copilot issues'); - return; - } - - // Get repo default branch - const { data: repoData } = await github.rest.repos.get({ - owner: context.repo.owner, - repo: context.repo.repo - }); - - for (const issue of unassigned) { - try { - await github.request('POST /repos/{owner}/{repo}/issues/{issue_number}/assignees', { - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - assignees: ['copilot-swe-agent[bot]'], - agent_assignment: { - target_repo: `${context.repo.owner}/${context.repo.repo}`, - base_branch: repoData.default_branch, - custom_instructions: `Read .squad/team.md (or .ai-team/team.md) for team context and .squad/routing.md (or .ai-team/routing.md) for routing rules.` - } - }); - core.info(`Assigned copilot-swe-agent[bot] to #${issue.number}`); - } catch (e) { - core.warning(`Failed to assign @copilot to #${issue.number}: ${e.message}`); - } - } - } catch (e) { - core.info(`No squad:copilot label found or error: ${e.message}`); - } diff --git a/.squad-templates/workflows/squad-insider-release.yml b/.squad-templates/workflows/squad-insider-release.yml deleted file mode 100644 index a3124d1..0000000 --- a/.squad-templates/workflows/squad-insider-release.yml +++ /dev/null @@ -1,61 +0,0 @@ -name: Squad Insider Release - -on: - push: - branches: [insider] - -permissions: - contents: write - -jobs: - release: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - uses: actions/setup-node@v4 - with: - node-version: 22 - - - name: Run tests - run: node --test test/*.test.js - - - name: Read version from package.json - id: version - run: | - VERSION=$(node -e "console.log(require('./package.json').version)") - SHORT_SHA=$(git rev-parse --short HEAD) - INSIDER_VERSION="${VERSION}-insider+${SHORT_SHA}" - INSIDER_TAG="v${INSIDER_VERSION}" - echo "version=$VERSION" >> "$GITHUB_OUTPUT" - echo "short_sha=$SHORT_SHA" >> "$GITHUB_OUTPUT" - echo "insider_version=$INSIDER_VERSION" >> "$GITHUB_OUTPUT" - echo "insider_tag=$INSIDER_TAG" >> "$GITHUB_OUTPUT" - echo "📦 Base Version: $VERSION (Short SHA: $SHORT_SHA)" - echo "🏷️ Insider Version: $INSIDER_VERSION" - echo "🔖 Insider Tag: $INSIDER_TAG" - - - name: Create git tag - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git tag -a "${{ steps.version.outputs.insider_tag }}" -m "Insider Release ${{ steps.version.outputs.insider_tag }}" - git push origin "${{ steps.version.outputs.insider_tag }}" - - - name: Create GitHub Release - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - gh release create "${{ steps.version.outputs.insider_tag }}" \ - --title "${{ steps.version.outputs.insider_tag }}" \ - --notes "This is an insider/development build of Squad. Install with:\`\`\`bash\nnpx github:bradygaster/squad#${{ steps.version.outputs.insider_tag }}\n\`\`\`\n\n**Note:** Insider builds may be unstable and are intended for early adopters and testing only." \ - --prerelease - - - name: Verify release - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - gh release view "${{ steps.version.outputs.insider_tag }}" - echo "✅ Insider Release ${{ steps.version.outputs.insider_tag }} created and verified." diff --git a/.squad-templates/workflows/squad-issue-assign.yml b/.squad-templates/workflows/squad-issue-assign.yml deleted file mode 100644 index ad140f4..0000000 --- a/.squad-templates/workflows/squad-issue-assign.yml +++ /dev/null @@ -1,161 +0,0 @@ -name: Squad Issue Assign - -on: - issues: - types: [labeled] - -permissions: - issues: write - contents: read - -jobs: - assign-work: - # Only trigger on squad:{member} labels (not the base "squad" label) - if: startsWith(github.event.label.name, 'squad:') - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Identify assigned member and trigger work - uses: actions/github-script@v7 - with: - script: | - const fs = require('fs'); - const issue = context.payload.issue; - const label = context.payload.label.name; - - // Extract member name from label (e.g., "squad:ripley" → "ripley") - const memberName = label.replace('squad:', '').toLowerCase(); - - // Read team roster — check .squad/ first, fall back to .ai-team/ - let teamFile = '.squad/team.md'; - if (!fs.existsSync(teamFile)) { - teamFile = '.ai-team/team.md'; - } - if (!fs.existsSync(teamFile)) { - core.warning('No .squad/team.md or .ai-team/team.md found — cannot assign work'); - return; - } - - const content = fs.readFileSync(teamFile, 'utf8'); - const lines = content.split('\n'); - - // Check if this is a coding agent assignment - const isCopilotAssignment = memberName === 'copilot'; - - let assignedMember = null; - if (isCopilotAssignment) { - assignedMember = { name: '@copilot', role: 'Coding Agent' }; - } else { - let inMembersTable = false; - for (const line of lines) { - if (line.match(/^##\s+(Members|Team Roster)/i)) { - inMembersTable = true; - continue; - } - if (inMembersTable && line.startsWith('## ')) { - break; - } - if (inMembersTable && line.startsWith('|') && !line.includes('---') && !line.includes('Name')) { - const cells = line.split('|').map(c => c.trim()).filter(Boolean); - if (cells.length >= 2 && cells[0].toLowerCase() === memberName) { - assignedMember = { name: cells[0], role: cells[1] }; - break; - } - } - } - } - - if (!assignedMember) { - core.warning(`No member found matching label "${label}"`); - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - body: `⚠️ No squad member found matching label \`${label}\`. Check \`.squad/team.md\` (or \`.ai-team/team.md\`) for valid member names.` - }); - return; - } - - // Post assignment acknowledgment - let comment; - if (isCopilotAssignment) { - comment = [ - `### 🤖 Routed to @copilot (Coding Agent)`, - '', - `**Issue:** #${issue.number} — ${issue.title}`, - '', - `@copilot has been assigned and will pick this up automatically.`, - '', - `> The coding agent will create a \`copilot/*\` branch and open a draft PR.`, - `> Review the PR as you would any team member's work.`, - ].join('\n'); - } else { - comment = [ - `### 📋 Assigned to ${assignedMember.name} (${assignedMember.role})`, - '', - `**Issue:** #${issue.number} — ${issue.title}`, - '', - `${assignedMember.name} will pick this up in the next Copilot session.`, - '', - `> **For Copilot coding agent:** If enabled, this issue will be worked automatically.`, - `> Otherwise, start a Copilot session and say:`, - `> \`${assignedMember.name}, work on issue #${issue.number}\``, - ].join('\n'); - } - - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - body: comment - }); - - core.info(`Issue #${issue.number} assigned to ${assignedMember.name} (${assignedMember.role})`); - - # Separate step: assign @copilot using PAT (required for coding agent) - - name: Assign @copilot coding agent - if: github.event.label.name == 'squad:copilot' - uses: actions/github-script@v7 - with: - github-token: ${{ secrets.COPILOT_ASSIGN_TOKEN }} - script: | - const owner = context.repo.owner; - const repo = context.repo.repo; - const issue_number = context.payload.issue.number; - - // Get the default branch name (main, master, etc.) - const { data: repoData } = await github.rest.repos.get({ owner, repo }); - const baseBranch = repoData.default_branch; - - try { - await github.request('POST /repos/{owner}/{repo}/issues/{issue_number}/assignees', { - owner, - repo, - issue_number, - assignees: ['copilot-swe-agent[bot]'], - agent_assignment: { - target_repo: `${owner}/${repo}`, - base_branch: baseBranch, - custom_instructions: '', - custom_agent: '', - model: '' - }, - headers: { - 'X-GitHub-Api-Version': '2022-11-28' - } - }); - core.info(`Assigned copilot-swe-agent to issue #${issue_number} (base: ${baseBranch})`); - } catch (err) { - core.warning(`Assignment with agent_assignment failed: ${err.message}`); - // Fallback: try without agent_assignment - try { - await github.rest.issues.addAssignees({ - owner, repo, issue_number, - assignees: ['copilot-swe-agent'] - }); - core.info(`Fallback assigned copilot-swe-agent to issue #${issue_number}`); - } catch (err2) { - core.warning(`Fallback also failed: ${err2.message}`); - } - } diff --git a/.squad-templates/workflows/squad-label-enforce.yml b/.squad-templates/workflows/squad-label-enforce.yml deleted file mode 100644 index 633d220..0000000 --- a/.squad-templates/workflows/squad-label-enforce.yml +++ /dev/null @@ -1,181 +0,0 @@ -name: Squad Label Enforce - -on: - issues: - types: [labeled] - -permissions: - issues: write - contents: read - -jobs: - enforce: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Enforce mutual exclusivity - uses: actions/github-script@v7 - with: - script: | - const issue = context.payload.issue; - const appliedLabel = context.payload.label.name; - - // Namespaces with mutual exclusivity rules - const EXCLUSIVE_PREFIXES = ['go:', 'release:', 'type:', 'priority:']; - - // Skip if not a managed namespace label - if (!EXCLUSIVE_PREFIXES.some(p => appliedLabel.startsWith(p))) { - core.info(`Label ${appliedLabel} is not in a managed namespace — skipping`); - return; - } - - const allLabels = issue.labels.map(l => l.name); - - // Handle go: namespace (mutual exclusivity) - if (appliedLabel.startsWith('go:')) { - const otherGoLabels = allLabels.filter(l => - l.startsWith('go:') && l !== appliedLabel - ); - - if (otherGoLabels.length > 0) { - // Remove conflicting go: labels - for (const label of otherGoLabels) { - await github.rest.issues.removeLabel({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - name: label - }); - core.info(`Removed conflicting label: ${label}`); - } - - // Post update comment - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - body: `🏷️ Triage verdict updated → \`${appliedLabel}\`` - }); - } - - // Auto-apply release:backlog if go:yes and no release target - if (appliedLabel === 'go:yes') { - const hasReleaseLabel = allLabels.some(l => l.startsWith('release:')); - if (!hasReleaseLabel) { - await github.rest.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - labels: ['release:backlog'] - }); - - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - body: `📋 Marked as \`release:backlog\` — assign a release target when ready.` - }); - - core.info('Applied release:backlog for go:yes issue'); - } - } - - // Remove release: labels if go:no - if (appliedLabel === 'go:no') { - const releaseLabels = allLabels.filter(l => l.startsWith('release:')); - if (releaseLabels.length > 0) { - for (const label of releaseLabels) { - await github.rest.issues.removeLabel({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - name: label - }); - core.info(`Removed release label from go:no issue: ${label}`); - } - } - } - } - - // Handle release: namespace (mutual exclusivity) - if (appliedLabel.startsWith('release:')) { - const otherReleaseLabels = allLabels.filter(l => - l.startsWith('release:') && l !== appliedLabel - ); - - if (otherReleaseLabels.length > 0) { - // Remove conflicting release: labels - for (const label of otherReleaseLabels) { - await github.rest.issues.removeLabel({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - name: label - }); - core.info(`Removed conflicting label: ${label}`); - } - - // Post update comment - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - body: `🏷️ Release target updated → \`${appliedLabel}\`` - }); - } - } - - // Handle type: namespace (mutual exclusivity) - if (appliedLabel.startsWith('type:')) { - const otherTypeLabels = allLabels.filter(l => - l.startsWith('type:') && l !== appliedLabel - ); - - if (otherTypeLabels.length > 0) { - for (const label of otherTypeLabels) { - await github.rest.issues.removeLabel({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - name: label - }); - core.info(`Removed conflicting label: ${label}`); - } - - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - body: `🏷️ Issue type updated → \`${appliedLabel}\`` - }); - } - } - - // Handle priority: namespace (mutual exclusivity) - if (appliedLabel.startsWith('priority:')) { - const otherPriorityLabels = allLabels.filter(l => - l.startsWith('priority:') && l !== appliedLabel - ); - - if (otherPriorityLabels.length > 0) { - for (const label of otherPriorityLabels) { - await github.rest.issues.removeLabel({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - name: label - }); - core.info(`Removed conflicting label: ${label}`); - } - - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - body: `🏷️ Priority updated → \`${appliedLabel}\`` - }); - } - } - - core.info(`Label enforcement complete for ${appliedLabel}`); diff --git a/.squad-templates/workflows/squad-preview.yml b/.squad-templates/workflows/squad-preview.yml deleted file mode 100644 index 9298c36..0000000 --- a/.squad-templates/workflows/squad-preview.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: Squad Preview Validation - -on: - push: - branches: [preview] - -permissions: - contents: read - -jobs: - validate: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-node@v4 - with: - node-version: 22 - - - name: Validate version consistency - run: | - VERSION=$(node -e "console.log(require('./package.json').version)") - if ! grep -q "## \[$VERSION\]" CHANGELOG.md 2>/dev/null; then - echo "::error::Version $VERSION not found in CHANGELOG.md — update CHANGELOG.md before release" - exit 1 - fi - echo "✅ Version $VERSION validated in CHANGELOG.md" - - - name: Run tests - run: node --test test/*.test.js - - - name: Check no .ai-team/ or .squad/ files are tracked - run: | - FOUND_FORBIDDEN=0 - if git ls-files --error-unmatch .ai-team/ 2>/dev/null; then - echo "::error::❌ .ai-team/ files are tracked on preview — this must not ship." - FOUND_FORBIDDEN=1 - fi - if git ls-files --error-unmatch .squad/ 2>/dev/null; then - echo "::error::❌ .squad/ files are tracked on preview — this must not ship." - FOUND_FORBIDDEN=1 - fi - if [ $FOUND_FORBIDDEN -eq 1 ]; then - exit 1 - fi - echo "✅ No .ai-team/ or .squad/ files tracked — clean for release." - - - name: Validate package.json version - run: | - VERSION=$(node -e "console.log(require('./package.json').version)") - if [ -z "$VERSION" ]; then - echo "::error::❌ No version field found in package.json." - exit 1 - fi - echo "✅ package.json version: $VERSION" diff --git a/.squad-templates/workflows/squad-promote.yml b/.squad-templates/workflows/squad-promote.yml deleted file mode 100644 index 07bac32..0000000 --- a/.squad-templates/workflows/squad-promote.yml +++ /dev/null @@ -1,121 +0,0 @@ -name: Squad Promote - -on: - workflow_dispatch: - inputs: - dry_run: - description: 'Dry run — show what would happen without pushing' - required: false - default: 'false' - type: choice - options: ['false', 'true'] - -permissions: - contents: write - -jobs: - dev-to-preview: - name: Promote dev → preview - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Configure git - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - - - name: Fetch all branches - run: git fetch --all - - - name: Show current state (dry run info) - run: | - echo "=== dev HEAD ===" && git log origin/dev -1 --oneline - echo "=== preview HEAD ===" && git log origin/preview -1 --oneline - echo "=== Files that would be stripped ===" - git diff origin/preview..origin/dev --name-only | grep -E "^(\.(ai-team|squad|ai-team-templates|squad-templates)|team-docs/|docs/proposals/)" || echo "(none)" - - - name: Merge dev → preview (strip forbidden paths) - if: ${{ inputs.dry_run == 'false' }} - run: | - git checkout preview - git merge origin/dev --no-commit --no-ff -X theirs || true - - # Strip forbidden paths from merge commit - git rm -rf --cached --ignore-unmatch \ - .ai-team/ \ - .squad/ \ - .ai-team-templates/ \ - .squad-templates/ \ - team-docs/ \ - "docs/proposals/" || true - - # Commit if there are staged changes - if ! git diff --cached --quiet; then - git commit -m "chore: promote dev → preview (v$(node -e "console.log(require('./package.json').version)"))" - git push origin preview - echo "✅ Pushed preview branch" - else - echo "ℹ️ Nothing to commit — preview is already up to date" - fi - - - name: Dry run complete - if: ${{ inputs.dry_run == 'true' }} - run: echo "🔍 Dry run complete — no changes pushed." - - preview-to-main: - name: Promote preview → main (release) - needs: dev-to-preview - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Configure git - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - - - name: Fetch all branches - run: git fetch --all - - - name: Show current state - run: | - echo "=== preview HEAD ===" && git log origin/preview -1 --oneline - echo "=== main HEAD ===" && git log origin/main -1 --oneline - echo "=== Version ===" && node -e "console.log('v' + require('./package.json').version)" - - - name: Validate preview is release-ready - run: | - git checkout preview - VERSION=$(node -e "console.log(require('./package.json').version)") - if ! grep -q "## \[$VERSION\]" CHANGELOG.md 2>/dev/null; then - echo "::error::Version $VERSION not found in CHANGELOG.md — update before releasing" - exit 1 - fi - echo "✅ Version $VERSION has CHANGELOG entry" - - # Verify no forbidden files on preview - FORBIDDEN=$(git ls-files | grep -E "^(\.(ai-team|squad|ai-team-templates|squad-templates)/|team-docs/|docs/proposals/)" || true) - if [ -n "$FORBIDDEN" ]; then - echo "::error::Forbidden files found on preview: $FORBIDDEN" - exit 1 - fi - echo "✅ No forbidden files on preview" - - - name: Merge preview → main - if: ${{ inputs.dry_run == 'false' }} - run: | - git checkout main - git merge origin/preview --no-ff -m "chore: promote preview → main (v$(node -e "console.log(require('./package.json').version)"))" - git push origin main - echo "✅ Pushed main — squad-release.yml will tag and publish the release" - - - name: Dry run complete - if: ${{ inputs.dry_run == 'true' }} - run: echo "🔍 Dry run complete — no changes pushed." diff --git a/.squad-templates/workflows/squad-release.yml b/.squad-templates/workflows/squad-release.yml deleted file mode 100644 index bbd5de7..0000000 --- a/.squad-templates/workflows/squad-release.yml +++ /dev/null @@ -1,77 +0,0 @@ -name: Squad Release - -on: - push: - branches: [main] - -permissions: - contents: write - -jobs: - release: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - uses: actions/setup-node@v4 - with: - node-version: 22 - - - name: Run tests - run: node --test test/*.test.js - - - name: Validate version consistency - run: | - VERSION=$(node -e "console.log(require('./package.json').version)") - if ! grep -q "## \[$VERSION\]" CHANGELOG.md 2>/dev/null; then - echo "::error::Version $VERSION not found in CHANGELOG.md — update CHANGELOG.md before release" - exit 1 - fi - echo "✅ Version $VERSION validated in CHANGELOG.md" - - - name: Read version from package.json - id: version - run: | - VERSION=$(node -e "console.log(require('./package.json').version)") - echo "version=$VERSION" >> "$GITHUB_OUTPUT" - echo "tag=v$VERSION" >> "$GITHUB_OUTPUT" - echo "📦 Version: $VERSION (tag: v$VERSION)" - - - name: Check if tag already exists - id: check_tag - run: | - if git rev-parse "refs/tags/${{ steps.version.outputs.tag }}" >/dev/null 2>&1; then - echo "exists=true" >> "$GITHUB_OUTPUT" - echo "⏭️ Tag ${{ steps.version.outputs.tag }} already exists — skipping release." - else - echo "exists=false" >> "$GITHUB_OUTPUT" - echo "🆕 Tag ${{ steps.version.outputs.tag }} does not exist — creating release." - fi - - - name: Create git tag - if: steps.check_tag.outputs.exists == 'false' - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git tag -a "${{ steps.version.outputs.tag }}" -m "Release ${{ steps.version.outputs.tag }}" - git push origin "${{ steps.version.outputs.tag }}" - - - name: Create GitHub Release - if: steps.check_tag.outputs.exists == 'false' - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - gh release create "${{ steps.version.outputs.tag }}" \ - --title "${{ steps.version.outputs.tag }}" \ - --generate-notes \ - --latest - - - name: Verify release - if: steps.check_tag.outputs.exists == 'false' - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - gh release view "${{ steps.version.outputs.tag }}" - echo "✅ Release ${{ steps.version.outputs.tag }} created and verified." diff --git a/.squad-templates/workflows/squad-triage.yml b/.squad-templates/workflows/squad-triage.yml deleted file mode 100644 index a58be9b..0000000 --- a/.squad-templates/workflows/squad-triage.yml +++ /dev/null @@ -1,260 +0,0 @@ -name: Squad Triage - -on: - issues: - types: [labeled] - -permissions: - issues: write - contents: read - -jobs: - triage: - if: github.event.label.name == 'squad' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Triage issue via Lead agent - uses: actions/github-script@v7 - with: - script: | - const fs = require('fs'); - const issue = context.payload.issue; - - // Read team roster — check .squad/ first, fall back to .ai-team/ - let teamFile = '.squad/team.md'; - if (!fs.existsSync(teamFile)) { - teamFile = '.ai-team/team.md'; - } - if (!fs.existsSync(teamFile)) { - core.warning('No .squad/team.md or .ai-team/team.md found — cannot triage'); - return; - } - - const content = fs.readFileSync(teamFile, 'utf8'); - const lines = content.split('\n'); - - // Check if @copilot is on the team - const hasCopilot = content.includes('🤖 Coding Agent'); - const copilotAutoAssign = content.includes(''); - - // Parse @copilot capability profile - let goodFitKeywords = []; - let needsReviewKeywords = []; - let notSuitableKeywords = []; - - if (hasCopilot) { - // Extract capability tiers from team.md - const goodFitMatch = content.match(/🟢\s*Good fit[^:]*:\s*(.+)/i); - const needsReviewMatch = content.match(/🟡\s*Needs review[^:]*:\s*(.+)/i); - const notSuitableMatch = content.match(/🔴\s*Not suitable[^:]*:\s*(.+)/i); - - if (goodFitMatch) { - goodFitKeywords = goodFitMatch[1].toLowerCase().split(',').map(s => s.trim()); - } else { - goodFitKeywords = ['bug fix', 'test coverage', 'lint', 'format', 'dependency update', 'small feature', 'scaffolding', 'doc fix', 'documentation']; - } - if (needsReviewMatch) { - needsReviewKeywords = needsReviewMatch[1].toLowerCase().split(',').map(s => s.trim()); - } else { - needsReviewKeywords = ['medium feature', 'refactoring', 'api endpoint', 'migration']; - } - if (notSuitableMatch) { - notSuitableKeywords = notSuitableMatch[1].toLowerCase().split(',').map(s => s.trim()); - } else { - notSuitableKeywords = ['architecture', 'system design', 'security', 'auth', 'encryption', 'performance']; - } - } - - const members = []; - let inMembersTable = false; - for (const line of lines) { - if (line.match(/^##\s+(Members|Team Roster)/i)) { - inMembersTable = true; - continue; - } - if (inMembersTable && line.startsWith('## ')) { - break; - } - if (inMembersTable && line.startsWith('|') && !line.includes('---') && !line.includes('Name')) { - const cells = line.split('|').map(c => c.trim()).filter(Boolean); - if (cells.length >= 2 && cells[0] !== 'Scribe') { - members.push({ - name: cells[0], - role: cells[1] - }); - } - } - } - - // Read routing rules — check .squad/ first, fall back to .ai-team/ - let routingFile = '.squad/routing.md'; - if (!fs.existsSync(routingFile)) { - routingFile = '.ai-team/routing.md'; - } - let routingContent = ''; - if (fs.existsSync(routingFile)) { - routingContent = fs.readFileSync(routingFile, 'utf8'); - } - - // Find the Lead - const lead = members.find(m => - m.role.toLowerCase().includes('lead') || - m.role.toLowerCase().includes('architect') || - m.role.toLowerCase().includes('coordinator') - ); - - if (!lead) { - core.warning('No Lead role found in team roster — cannot triage'); - return; - } - - // Build triage context - const memberList = members.map(m => - `- **${m.name}** (${m.role}) → label: \`squad:${m.name.toLowerCase()}\`` - ).join('\n'); - - // Determine best assignee based on issue content and routing - const issueText = `${issue.title}\n${issue.body || ''}`.toLowerCase(); - - let assignedMember = null; - let triageReason = ''; - let copilotTier = null; - - // First, evaluate @copilot fit if enabled - if (hasCopilot) { - const isNotSuitable = notSuitableKeywords.some(kw => issueText.includes(kw)); - const isGoodFit = !isNotSuitable && goodFitKeywords.some(kw => issueText.includes(kw)); - const isNeedsReview = !isNotSuitable && !isGoodFit && needsReviewKeywords.some(kw => issueText.includes(kw)); - - if (isGoodFit) { - copilotTier = 'good-fit'; - assignedMember = { name: '@copilot', role: 'Coding Agent' }; - triageReason = '🟢 Good fit for @copilot — matches capability profile'; - } else if (isNeedsReview) { - copilotTier = 'needs-review'; - assignedMember = { name: '@copilot', role: 'Coding Agent' }; - triageReason = '🟡 Routing to @copilot (needs review) — a squad member should review the PR'; - } else if (isNotSuitable) { - copilotTier = 'not-suitable'; - // Fall through to normal routing - } - } - - // If not routed to @copilot, use keyword-based routing - if (!assignedMember) { - for (const member of members) { - const role = member.role.toLowerCase(); - if ((role.includes('frontend') || role.includes('ui')) && - (issueText.includes('ui') || issueText.includes('frontend') || - issueText.includes('css') || issueText.includes('component') || - issueText.includes('button') || issueText.includes('page') || - issueText.includes('layout') || issueText.includes('design'))) { - assignedMember = member; - triageReason = 'Issue relates to frontend/UI work'; - break; - } - if ((role.includes('backend') || role.includes('api') || role.includes('server')) && - (issueText.includes('api') || issueText.includes('backend') || - issueText.includes('database') || issueText.includes('endpoint') || - issueText.includes('server') || issueText.includes('auth'))) { - assignedMember = member; - triageReason = 'Issue relates to backend/API work'; - break; - } - if ((role.includes('test') || role.includes('qa') || role.includes('quality')) && - (issueText.includes('test') || issueText.includes('bug') || - issueText.includes('fix') || issueText.includes('regression') || - issueText.includes('coverage'))) { - assignedMember = member; - triageReason = 'Issue relates to testing/quality work'; - break; - } - if ((role.includes('devops') || role.includes('infra') || role.includes('ops')) && - (issueText.includes('deploy') || issueText.includes('ci') || - issueText.includes('pipeline') || issueText.includes('docker') || - issueText.includes('infrastructure'))) { - assignedMember = member; - triageReason = 'Issue relates to DevOps/infrastructure work'; - break; - } - } - } - - // Default to Lead if no routing match - if (!assignedMember) { - assignedMember = lead; - triageReason = 'No specific domain match — assigned to Lead for further analysis'; - } - - const isCopilot = assignedMember.name === '@copilot'; - const assignLabel = isCopilot ? 'squad:copilot' : `squad:${assignedMember.name.toLowerCase()}`; - - // Add the member-specific label - await github.rest.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - labels: [assignLabel] - }); - - // Apply default triage verdict - await github.rest.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - labels: ['go:needs-research'] - }); - - // Auto-assign @copilot if enabled - if (isCopilot && copilotAutoAssign) { - try { - await github.rest.issues.addAssignees({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - assignees: ['copilot'] - }); - } catch (err) { - core.warning(`Could not auto-assign @copilot: ${err.message}`); - } - } - - // Build copilot evaluation note - let copilotNote = ''; - if (hasCopilot && !isCopilot) { - if (copilotTier === 'not-suitable') { - copilotNote = `\n\n**@copilot evaluation:** 🔴 Not suitable — issue involves work outside the coding agent's capability profile.`; - } else { - copilotNote = `\n\n**@copilot evaluation:** No strong capability match — routed to squad member.`; - } - } - - // Post triage comment - const comment = [ - `### 🏗️ Squad Triage — ${lead.name} (${lead.role})`, - '', - `**Issue:** #${issue.number} — ${issue.title}`, - `**Assigned to:** ${assignedMember.name} (${assignedMember.role})`, - `**Reason:** ${triageReason}`, - copilotTier === 'needs-review' ? `\n⚠️ **PR review recommended** — a squad member should review @copilot's work on this one.` : '', - copilotNote, - '', - `---`, - '', - `**Team roster:**`, - memberList, - hasCopilot ? `- **@copilot** (Coding Agent) → label: \`squad:copilot\`` : '', - '', - `> To reassign, remove the current \`squad:*\` label and add the correct one.`, - ].filter(Boolean).join('\n'); - - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - body: comment - }); - - core.info(`Triaged issue #${issue.number} → ${assignedMember.name} (${assignLabel})`); diff --git a/.squad-templates/workflows/sync-squad-labels.yml b/.squad-templates/workflows/sync-squad-labels.yml deleted file mode 100644 index fbcfd9c..0000000 --- a/.squad-templates/workflows/sync-squad-labels.yml +++ /dev/null @@ -1,169 +0,0 @@ -name: Sync Squad Labels - -on: - push: - paths: - - '.squad/team.md' - - '.ai-team/team.md' - workflow_dispatch: - -permissions: - issues: write - contents: read - -jobs: - sync-labels: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Parse roster and sync labels - uses: actions/github-script@v7 - with: - script: | - const fs = require('fs'); - let teamFile = '.squad/team.md'; - if (!fs.existsSync(teamFile)) { - teamFile = '.ai-team/team.md'; - } - - if (!fs.existsSync(teamFile)) { - core.info('No .squad/team.md or .ai-team/team.md found — skipping label sync'); - return; - } - - const content = fs.readFileSync(teamFile, 'utf8'); - const lines = content.split('\n'); - - // Parse the Members table for agent names - const members = []; - let inMembersTable = false; - for (const line of lines) { - if (line.match(/^##\s+(Members|Team Roster)/i)) { - inMembersTable = true; - continue; - } - if (inMembersTable && line.startsWith('## ')) { - break; - } - if (inMembersTable && line.startsWith('|') && !line.includes('---') && !line.includes('Name')) { - const cells = line.split('|').map(c => c.trim()).filter(Boolean); - if (cells.length >= 2 && cells[0] !== 'Scribe') { - members.push({ - name: cells[0], - role: cells[1] - }); - } - } - } - - core.info(`Found ${members.length} squad members: ${members.map(m => m.name).join(', ')}`); - - // Check if @copilot is on the team - const hasCopilot = content.includes('🤖 Coding Agent'); - - // Define label color palette for squad labels - const SQUAD_COLOR = '9B8FCC'; - const MEMBER_COLOR = '9B8FCC'; - const COPILOT_COLOR = '10b981'; - - // Define go: and release: labels (static) - const GO_LABELS = [ - { name: 'go:yes', color: '0E8A16', description: 'Ready to implement' }, - { name: 'go:no', color: 'B60205', description: 'Not pursuing' }, - { name: 'go:needs-research', color: 'FBCA04', description: 'Needs investigation' } - ]; - - const RELEASE_LABELS = [ - { name: 'release:v0.4.0', color: '6B8EB5', description: 'Targeted for v0.4.0' }, - { name: 'release:v0.5.0', color: '6B8EB5', description: 'Targeted for v0.5.0' }, - { name: 'release:v0.6.0', color: '8B7DB5', description: 'Targeted for v0.6.0' }, - { name: 'release:v1.0.0', color: '8B7DB5', description: 'Targeted for v1.0.0' }, - { name: 'release:backlog', color: 'D4E5F7', description: 'Not yet targeted' } - ]; - - const TYPE_LABELS = [ - { name: 'type:feature', color: 'DDD1F2', description: 'New capability' }, - { name: 'type:bug', color: 'FF0422', description: 'Something broken' }, - { name: 'type:spike', color: 'F2DDD4', description: 'Research/investigation — produces a plan, not code' }, - { name: 'type:docs', color: 'D4E5F7', description: 'Documentation work' }, - { name: 'type:chore', color: 'D4E5F7', description: 'Maintenance, refactoring, cleanup' }, - { name: 'type:epic', color: 'CC4455', description: 'Parent issue that decomposes into sub-issues' } - ]; - - // High-signal labels — these MUST visually dominate all others - const SIGNAL_LABELS = [ - { name: 'bug', color: 'FF0422', description: 'Something isn\'t working' }, - { name: 'feedback', color: '00E5FF', description: 'User feedback — high signal, needs attention' } - ]; - - const PRIORITY_LABELS = [ - { name: 'priority:p0', color: 'B60205', description: 'Blocking release' }, - { name: 'priority:p1', color: 'D93F0B', description: 'This sprint' }, - { name: 'priority:p2', color: 'FBCA04', description: 'Next sprint' } - ]; - - // Ensure the base "squad" triage label exists - const labels = [ - { name: 'squad', color: SQUAD_COLOR, description: 'Squad triage inbox — Lead will assign to a member' } - ]; - - for (const member of members) { - labels.push({ - name: `squad:${member.name.toLowerCase()}`, - color: MEMBER_COLOR, - description: `Assigned to ${member.name} (${member.role})` - }); - } - - // Add @copilot label if coding agent is on the team - if (hasCopilot) { - labels.push({ - name: 'squad:copilot', - color: COPILOT_COLOR, - description: 'Assigned to @copilot (Coding Agent) for autonomous work' - }); - } - - // Add go:, release:, type:, priority:, and high-signal labels - labels.push(...GO_LABELS); - labels.push(...RELEASE_LABELS); - labels.push(...TYPE_LABELS); - labels.push(...PRIORITY_LABELS); - labels.push(...SIGNAL_LABELS); - - // Sync labels (create or update) - for (const label of labels) { - try { - await github.rest.issues.getLabel({ - owner: context.repo.owner, - repo: context.repo.repo, - name: label.name - }); - // Label exists — update it - await github.rest.issues.updateLabel({ - owner: context.repo.owner, - repo: context.repo.repo, - name: label.name, - color: label.color, - description: label.description - }); - core.info(`Updated label: ${label.name}`); - } catch (err) { - if (err.status === 404) { - // Label doesn't exist — create it - await github.rest.issues.createLabel({ - owner: context.repo.owner, - repo: context.repo.repo, - name: label.name, - color: label.color, - description: label.description - }); - core.info(`Created label: ${label.name}`); - } else { - throw err; - } - } - } - - core.info(`Label sync complete: ${labels.length} labels synced`);