diff --git a/.squad/skills/agent-collaboration/SKILL.md b/.copilot/skills/agent-collaboration/SKILL.md similarity index 100% rename from .squad/skills/agent-collaboration/SKILL.md rename to .copilot/skills/agent-collaboration/SKILL.md diff --git a/.squad/skills/agent-conduct/SKILL.md b/.copilot/skills/agent-conduct/SKILL.md similarity index 100% rename from .squad/skills/agent-conduct/SKILL.md rename to .copilot/skills/agent-conduct/SKILL.md diff --git a/.squad/skills/architectural-proposals/SKILL.md b/.copilot/skills/architectural-proposals/SKILL.md similarity index 100% rename from .squad/skills/architectural-proposals/SKILL.md rename to .copilot/skills/architectural-proposals/SKILL.md diff --git a/.squad/skills/ci-validation-gates/SKILL.md b/.copilot/skills/ci-validation-gates/SKILL.md similarity index 100% rename from .squad/skills/ci-validation-gates/SKILL.md rename to .copilot/skills/ci-validation-gates/SKILL.md diff --git a/.squad/skills/cli-wiring/SKILL.md b/.copilot/skills/cli-wiring/SKILL.md similarity index 100% rename from .squad/skills/cli-wiring/SKILL.md rename to .copilot/skills/cli-wiring/SKILL.md diff --git a/.squad/skills/client-compatibility/SKILL.md b/.copilot/skills/client-compatibility/SKILL.md similarity index 100% rename from .squad/skills/client-compatibility/SKILL.md rename to .copilot/skills/client-compatibility/SKILL.md diff --git a/.squad/skills/distributed-mesh/SKILL.md b/.copilot/skills/distributed-mesh/SKILL.md similarity index 98% rename from .squad/skills/distributed-mesh/SKILL.md rename to .copilot/skills/distributed-mesh/SKILL.md index 624db9626..b2e924450 100644 --- a/.squad/skills/distributed-mesh/SKILL.md +++ b/.copilot/skills/distributed-mesh/SKILL.md @@ -157,7 +157,7 @@ Ask these questions (adapt phrasing naturally, but get these answers): ### Step 2: GENERATE `mesh.json` -Using the answers from Step 1, create a `mesh.json` file at the project root. Use `mesh.json.example` from THIS skill's directory (`.squad/skills/distributed-mesh/mesh.json.example`) as the schema template. +Using the answers from Step 1, create a `mesh.json` file at the project root. Use `mesh.json.example` from THIS skill's directory (`.copilot/skills/distributed-mesh/mesh.json.example`) as the schema template. Structure: @@ -187,10 +187,10 @@ Write this file to the project root. Do NOT write any other code. Copy the bundled sync scripts from THIS skill's directory into the project root: -- **Source:** `.squad/skills/distributed-mesh/sync-mesh.sh` +- **Source:** `.copilot/skills/distributed-mesh/sync-mesh.sh` - **Destination:** `sync-mesh.sh` (project root) -- **Source:** `.squad/skills/distributed-mesh/sync-mesh.ps1` +- **Source:** `.copilot/skills/distributed-mesh/sync-mesh.ps1` - **Destination:** `sync-mesh.ps1` (project root) These are bundled resources. Do NOT generate them — COPY them directly. diff --git a/.squad/skills/distributed-mesh/mesh.json.example b/.copilot/skills/distributed-mesh/mesh.json.example similarity index 100% rename from .squad/skills/distributed-mesh/mesh.json.example rename to .copilot/skills/distributed-mesh/mesh.json.example diff --git a/.squad/skills/distributed-mesh/sync-mesh.ps1 b/.copilot/skills/distributed-mesh/sync-mesh.ps1 similarity index 100% rename from .squad/skills/distributed-mesh/sync-mesh.ps1 rename to .copilot/skills/distributed-mesh/sync-mesh.ps1 diff --git a/.squad/skills/distributed-mesh/sync-mesh.sh b/.copilot/skills/distributed-mesh/sync-mesh.sh similarity index 100% rename from .squad/skills/distributed-mesh/sync-mesh.sh rename to .copilot/skills/distributed-mesh/sync-mesh.sh diff --git a/.squad/skills/git-workflow/SKILL.md b/.copilot/skills/git-workflow/SKILL.md similarity index 100% rename from .squad/skills/git-workflow/SKILL.md rename to .copilot/skills/git-workflow/SKILL.md diff --git a/.squad/skills/github-multi-account/SKILL.md b/.copilot/skills/github-multi-account/SKILL.md similarity index 100% rename from .squad/skills/github-multi-account/SKILL.md rename to .copilot/skills/github-multi-account/SKILL.md diff --git a/.squad/skills/history-hygiene/SKILL.md b/.copilot/skills/history-hygiene/SKILL.md similarity index 100% rename from .squad/skills/history-hygiene/SKILL.md rename to .copilot/skills/history-hygiene/SKILL.md diff --git a/.squad/skills/init-mode/SKILL.md b/.copilot/skills/init-mode/SKILL.md similarity index 100% rename from .squad/skills/init-mode/SKILL.md rename to .copilot/skills/init-mode/SKILL.md diff --git a/.squad/skills/model-selection/SKILL.md b/.copilot/skills/model-selection/SKILL.md similarity index 100% rename from .squad/skills/model-selection/SKILL.md rename to .copilot/skills/model-selection/SKILL.md diff --git a/.squad/skills/release-process/SKILL.md b/.copilot/skills/release-process/SKILL.md similarity index 100% rename from .squad/skills/release-process/SKILL.md rename to .copilot/skills/release-process/SKILL.md diff --git a/.squad/skills/reskill/SKILL.md b/.copilot/skills/reskill/SKILL.md similarity index 97% rename from .squad/skills/reskill/SKILL.md rename to .copilot/skills/reskill/SKILL.md index 946de0e0b..ab6571010 100644 --- a/.squad/skills/reskill/SKILL.md +++ b/.copilot/skills/reskill/SKILL.md @@ -23,7 +23,7 @@ Read all agent charters and histories. Measure byte sizes. Identify: ### Step 2: Extract For each identified pattern: -1. Create or update a skill at `.squad/skills/{skill-name}/SKILL.md` +1. Create or update a skill at `.copilot/skills/{skill-name}/SKILL.md` 2. Follow the skill template format (frontmatter + Context + Patterns + Examples + Anti-Patterns) 3. Set confidence: low (first observation), medium (2+ agents), high (team-wide) diff --git a/.squad/skills/reviewer-protocol/SKILL.md b/.copilot/skills/reviewer-protocol/SKILL.md similarity index 100% rename from .squad/skills/reviewer-protocol/SKILL.md rename to .copilot/skills/reviewer-protocol/SKILL.md diff --git a/.squad/skills/secret-handling/SKILL.md b/.copilot/skills/secret-handling/SKILL.md similarity index 100% rename from .squad/skills/secret-handling/SKILL.md rename to .copilot/skills/secret-handling/SKILL.md diff --git a/.squad/skills/squad-conventions/SKILL.md b/.copilot/skills/squad-conventions/SKILL.md similarity index 97% rename from .squad/skills/squad-conventions/SKILL.md rename to .copilot/skills/squad-conventions/SKILL.md index 72eca68ed..eae1d1f6e 100644 --- a/.squad/skills/squad-conventions/SKILL.md +++ b/.copilot/skills/squad-conventions/SKILL.md @@ -28,7 +28,7 @@ Colors are defined as constants at the top of `index.js`: `GREEN`, `RED`, `DIM`, - `.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) +- `.copilot/skills/` — Team skills in SKILL.md format (user-owned) - `.squad/decisions/inbox/` — Drop-box for parallel decision writes ### Windows Compatibility diff --git a/.github/agents/squad.agent.md b/.github/agents/squad.agent.md index 3ff306728..76e094490 100644 --- a/.github/agents/squad.agent.md +++ b/.github/agents/squad.agent.md @@ -29,7 +29,7 @@ Check: Does `.squad/team.md` exist? (fall back to `.ai-team/team.md` for repos m ## Init Mode -**Skill:** Read `.squad/skills/init-mode/SKILL.md` when entering Init Mode (team.md missing or empty). +**Skill:** Read `.copilot/skills/init-mode/SKILL.md` when entering Init Mode (team.md missing or empty). **Core rules (always loaded):** - Phase 1: Propose team → use `ask_user` → **STOP** and wait for confirmation @@ -207,7 +207,7 @@ The routing table determines **WHO** handles work. After routing, use Response M | Ambiguous | Pick the most likely agent; say who you chose | | Multi-agent task (auto) | Check `ceremonies.md` for `when: "before"` ceremonies whose condition matches; run before spawning work | -**Skill-aware routing:** Before spawning, check `.squad/skills/` for skills relevant to the task domain. If a matching skill exists, add to the spawn prompt: `Relevant skill: .squad/skills/{name}/SKILL.md — read before starting.` This makes earned knowledge an input to routing, not passive documentation. +**Skill-aware routing:** Before spawning, check `.copilot/skills/` for skills relevant to the task domain. If a matching skill exists, add to the spawn prompt: `Relevant skill: .copilot/skills/{name}/SKILL.md — read before starting.` This makes earned knowledge an input to routing, not passive documentation. ### Skill Confidence Lifecycle @@ -287,7 +287,7 @@ For read-only queries, use the explore agent: `agent_type: "explore"` with `"You ### Per-Agent Model Selection -**Skill:** Read `.squad/skills/model-selection/SKILL.md` before spawning any agent. +**Skill:** Read `.copilot/skills/model-selection/SKILL.md` before spawning any agent. **Core rules (always loaded):** - 4-layer hierarchy: User Override → Charter Preference → Task-Aware Auto → Default (haiku) @@ -298,7 +298,7 @@ For read-only queries, use the explore agent: `agent_type: "explore"` with `"You ### Client Compatibility -**Skill:** Read `.squad/skills/client-compatibility/SKILL.md` for platform detection and adaptive spawning patterns. +**Skill:** Read `.copilot/skills/client-compatibility/SKILL.md` for platform detection and adaptive spawning patterns. **Core rules (always loaded):** - Platform detection: `task` available → CLI (full control), `runSubagent` → VS Code (session model, parallel in one turn), neither → inline fallback @@ -310,7 +310,7 @@ For read-only queries, use the explore agent: `agent_type: "explore"` with `"You 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, graceful degradation. Read `.squad/templates/mcp-config.md` for config file locations, sample configs, and authentication notes. +> **Full patterns:** Read `.copilot/skills/mcp-tool-discovery/SKILL.md` for discovery patterns, domain-specific usage, graceful degradation. Read `.squad/templates/mcp-config.md` for config file locations, sample configs, and authentication notes. #### Detection @@ -506,7 +506,7 @@ prompt: | Read .squad/decisions.md (team decisions to respect). If .squad/identity/wisdom.md exists, read it before starting work. If .squad/identity/now.md exists, read it at spawn time. - If .squad/skills/ has relevant SKILL.md files, read them before working. + If .copilot/skills/ has relevant SKILL.md files, read them before working. {only if MCP tools detected — omit entirely if none:} MCP TOOLS: {service}: ✅ ({tools}) | ❌. Fall back to CLI when unavailable. @@ -530,7 +530,7 @@ prompt: | 2. If you made a team-relevant decision, write to: .squad/decisions/inbox/{name}-{brief-slug}.md 3. SKILL EXTRACTION: If you found a reusable pattern, write/update - .squad/skills/{skill-name}/SKILL.md (read templates/skill.md for format). + .copilot/skills/{skill-name}/SKILL.md (read templates/skill.md for format). ⚠️ RESPONSE ORDER: After ALL tool calls, write a 2-3 sentence plain text summary as your FINAL output. No tool calls after this summary. @@ -615,7 +615,7 @@ Ceremonies are structured team meetings where agents align before or after work. If the user says "I need a designer" or "add someone for DevOps": 1. **Allocate a name** from the current assignment's universe (read from `.squad/casting/history.json`). If the universe is exhausted, apply overflow handling (see Casting & Persistent Naming → Overflow Handling). -2. **Check plugin marketplaces.** If `.squad/plugins/marketplaces.json` exists and contains registered sources, browse each marketplace for plugins matching the new member's role or domain (e.g., "azure-cloud-development" for an Azure DevOps role). Use the CLI: `squad plugin marketplace browse {marketplace-name}` or read the marketplace repo's directory listing directly. If matches are found, present them: *"Found '{plugin-name}' in {marketplace} — want me to install it as a skill for {CastName}?"* If the user accepts, copy the plugin content into `.squad/skills/{plugin-name}/SKILL.md` or merge relevant instructions into the agent's charter. If no marketplaces are configured, skip silently. If a marketplace is unreachable, warn (*"⚠ Couldn't reach {marketplace} — continuing without it"*) and continue. +2. **Check plugin marketplaces.** If `.squad/plugins/marketplaces.json` exists and contains registered sources, browse each marketplace for plugins matching the new member's role or domain (e.g., "azure-cloud-development" for an Azure DevOps role). Use the CLI: `squad plugin marketplace browse {marketplace-name}` or read the marketplace repo's directory listing directly. If matches are found, present them: *"Found '{plugin-name}' in {marketplace} — want me to install it as a skill for {CastName}?"* If the user accepts, copy the plugin content into `.copilot/skills/{plugin-name}/SKILL.md` or merge relevant instructions into the agent's charter. If no marketplaces are configured, skip silently. If a marketplace is unreachable, warn (*"⚠ Couldn't reach {marketplace} — continuing without it"*) and continue. 3. Generate a new charter.md + history.md (seeded with project context from team.md), using the cast name. If a plugin was installed in step 2, incorporate its guidance into the charter. 4. **Update `.squad/casting/registry.json`** with the new agent entry. 5. Add to team.md roster. @@ -638,7 +638,7 @@ If the user wants to remove someone: **Core rules (always loaded):** - Check `.squad/plugins/marketplaces.json` during Add Team Member flow (after name allocation, before charter) - Present matching plugins for user approval -- Install: copy to `.squad/skills/{plugin-name}/SKILL.md`, log to history.md +- Install: copy to `.copilot/skills/{plugin-name}/SKILL.md`, log to history.md - Skip silently if no marketplaces configured --- @@ -741,7 +741,7 @@ When `.squad/team.md` exists but `.squad/casting/` does not: ## Reviewer Rejection Protocol -**Skill:** Read `.squad/skills/reviewer-protocol/SKILL.md` when a Reviewer rejects work. +**Skill:** Read `.copilot/skills/reviewer-protocol/SKILL.md` when a Reviewer rejects work. **Core rules (always loaded):** - On rejection: original author is **locked out** — may NOT self-revise, no exceptions diff --git a/packages/squad-cli/bradygaster-squad-cli-0.8.25-build.10.tgz b/packages/squad-cli/bradygaster-squad-cli-0.8.25-build.10.tgz new file mode 100644 index 000000000..9c2449f49 Binary files /dev/null and b/packages/squad-cli/bradygaster-squad-cli-0.8.25-build.10.tgz differ diff --git a/packages/squad-cli/src/cli/commands/build.ts b/packages/squad-cli/src/cli/commands/build.ts index e2261a8a7..dee7b1c3d 100644 --- a/packages/squad-cli/src/cli/commands/build.ts +++ b/packages/squad-cli/src/cli/commands/build.ts @@ -270,7 +270,7 @@ function generateCeremoniesDispatchTable(ceremonies: readonly CeremonyDefinition const schedule = c.schedule ?? '—'; const participants = c.participants?.join(', ') ?? '—'; const slug = c.name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, ''); - const skillPath = `.squad/skills/ceremony-${slug}/SKILL.md`; + const skillPath = `.copilot/skills/ceremony-${slug}/SKILL.md`; lines.push(`| ${c.name} | ${trigger} | ${schedule} | ${participants} | \`${skillPath}\` |`); } @@ -382,7 +382,7 @@ function buildFilePlan(config: SquadSDKConfig): GeneratedFile[] { for (const c of config.ceremonies) { const slug = c.name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, ''); files.push({ - relPath: `.squad/skills/ceremony-${slug}/SKILL.md`, + relPath: `.copilot/skills/ceremony-${slug}/SKILL.md`, content: generateCeremonySkillFile(c), }); } @@ -399,7 +399,7 @@ function buildFilePlan(config: SquadSDKConfig): GeneratedFile[] { if (config.skills && config.skills.length > 0) { for (const skill of config.skills) { files.push({ - relPath: `.squad/skills/${skill.name}/SKILL.md`, + relPath: `.copilot/skills/${skill.name}/SKILL.md`, content: generateSkillFile(skill), }); } diff --git a/packages/squad-cli/src/cli/commands/export.ts b/packages/squad-cli/src/cli/commands/export.ts index 30f4687c1..9cb64bd11 100644 --- a/packages/squad-cli/src/cli/commands/export.ts +++ b/packages/squad-cli/src/cli/commands/export.ts @@ -71,11 +71,18 @@ export async function runExport(dest: string, outPath?: string): Promise { } // Read skills - const skillsDir = path.join(squadInfo.path, 'skills'); + const skillSources = [ + { dir: path.join(dest, '.copilot', 'skills'), layout: 'nested' as const }, + { dir: path.join(squadInfo.path, 'skills'), layout: 'nested' as const }, + { dir: path.join(dest, '.ai-team', 'skills'), layout: 'flat' as const }, + ]; + const skillsSource = skillSources.find(({ dir }) => fs.existsSync(dir)); try { - if (fs.existsSync(skillsDir)) { - for (const entry of fs.readdirSync(skillsDir)) { - const skillFile = path.join(skillsDir, entry, 'SKILL.md'); + if (skillsSource) { + for (const entry of fs.readdirSync(skillsSource.dir)) { + const skillFile = skillsSource.layout === 'nested' + ? path.join(skillsSource.dir, entry, 'SKILL.md') + : path.join(skillsSource.dir, entry); if (fs.existsSync(skillFile)) { manifest.skills.push(fs.readFileSync(skillFile, 'utf8')); } diff --git a/packages/squad-cli/src/cli/commands/import.ts b/packages/squad-cli/src/cli/commands/import.ts index 8ac378984..f9009f7c0 100644 --- a/packages/squad-cli/src/cli/commands/import.ts +++ b/packages/squad-cli/src/cli/commands/import.ts @@ -70,7 +70,7 @@ export async function runImport(dest: string, importPath: string, force: boolean fs.mkdirSync(path.join(squadDir, 'decisions', 'inbox'), { recursive: true }); fs.mkdirSync(path.join(squadDir, 'orchestration-log'), { recursive: true }); fs.mkdirSync(path.join(squadDir, 'log'), { recursive: true }); - fs.mkdirSync(path.join(squadDir, 'skills'), { recursive: true }); + fs.mkdirSync(path.join(dest, '.copilot', 'skills'), { recursive: true }); // Write empty project-specific files fs.writeFileSync(path.join(squadDir, 'decisions.md'), ''); @@ -114,7 +114,7 @@ export async function runImport(dest: string, importPath: string, force: boolean const skillName = nameMatch ? nameMatch[1]!.trim().toLowerCase().replace(/\s+/g, '-') : `skill-${manifest.skills.indexOf(skillContent)}`; - const skillDir = path.join(squadDir, 'skills', skillName); + const skillDir = path.join(dest, '.copilot', 'skills', skillName); fs.mkdirSync(skillDir, { recursive: true }); fs.writeFileSync(path.join(skillDir, 'SKILL.md'), skillContent); } diff --git a/packages/squad-cli/src/cli/core/migrations.ts b/packages/squad-cli/src/cli/core/migrations.ts index eb91ba6fa..c9ad3d331 100644 --- a/packages/squad-cli/src/cli/core/migrations.ts +++ b/packages/squad-cli/src/cli/core/migrations.ts @@ -46,6 +46,37 @@ const migrations: Migration[] = [ } } } + }, + { + version: '0.9.0', + description: 'Copy legacy .squad skills into .copilot/skills', + run(squadDir: string) { + const projectRoot = path.dirname(squadDir); + const legacySkillsDir = path.join(squadDir, 'skills'); + if (!fs.existsSync(legacySkillsDir)) { + return; + } + + const skillNames = fs.readdirSync(legacySkillsDir).filter(entry => + fs.existsSync(path.join(legacySkillsDir, entry, 'SKILL.md')), + ); + if (skillNames.length === 0) { + return; + } + + const copilotSkillsDir = path.join(projectRoot, '.copilot', 'skills'); + fs.mkdirSync(copilotSkillsDir, { recursive: true }); + + for (const skillName of skillNames) { + fs.cpSync( + path.join(legacySkillsDir, skillName), + path.join(copilotSkillsDir, skillName), + { recursive: true, force: false, errorOnExist: false }, + ); + } + + success(`Migrated skills to .copilot/skills: ${skillNames.join(', ')}`); + } } ]; diff --git a/packages/squad-cli/src/cli/core/templates.ts b/packages/squad-cli/src/cli/core/templates.ts index c39e9cba0..cef5b3f21 100644 --- a/packages/squad-cli/src/cli/core/templates.ts +++ b/packages/squad-cli/src/cli/core/templates.ts @@ -166,7 +166,7 @@ export const TEMPLATE_MANIFEST: TemplateFile[] = [ // Skills subdirectory (squad-owned) { source: 'skills/squad-conventions/SKILL.md', - destination: 'skills/squad-conventions/SKILL.md', + destination: '../.copilot/skills/squad-conventions/SKILL.md', overwriteOnUpgrade: true, description: 'Squad conventions skill definition', }, diff --git a/packages/squad-cli/templates/mcp-config.md b/packages/squad-cli/templates/mcp-config.md index 2e361ee4b..8ecabecf5 100644 --- a/packages/squad-cli/templates/mcp-config.md +++ b/packages/squad-cli/templates/mcp-config.md @@ -2,7 +2,7 @@ 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. +> **Full patterns:** Read `.copilot/skills/mcp-tool-discovery/SKILL.md` for discovery patterns, domain-specific usage, and graceful degradation. ## Config File Locations diff --git a/packages/squad-cli/templates/plugin-marketplace.md b/packages/squad-cli/templates/plugin-marketplace.md index 893632816..efef3d7a1 100644 --- a/packages/squad-cli/templates/plugin-marketplace.md +++ b/packages/squad-cli/templates/plugin-marketplace.md @@ -38,7 +38,7 @@ During the **Adding Team Members** flow, AFTER allocating a name but BEFORE gene ## 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` +2. Copy it into the agent's skills directory: `.copilot/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}."* diff --git a/packages/squad-cli/templates/squad.agent.md b/packages/squad-cli/templates/squad.agent.md index 23542b254..dffd7ee10 100644 --- a/packages/squad-cli/templates/squad.agent.md +++ b/packages/squad-cli/templates/squad.agent.md @@ -234,7 +234,7 @@ The routing table determines **WHO** handles work. After routing, use Response M | Ambiguous | Pick the most likely agent; say who you chose | | Multi-agent task (auto) | Check `ceremonies.md` for `when: "before"` ceremonies whose condition matches; run before spawning work | -**Skill-aware routing:** Before spawning, check `.squad/skills/` for skills relevant to the task domain. If a matching skill exists, add to the spawn prompt: `Relevant skill: .squad/skills/{name}/SKILL.md — read before starting.` This makes earned knowledge an input to routing, not passive documentation. +**Skill-aware routing:** Before spawning, check `.copilot/skills/` for skills relevant to the task domain. If a matching skill exists, add to the spawn prompt: `Relevant skill: .copilot/skills/{name}/SKILL.md — read before starting.` This makes earned knowledge an input to routing, not passive documentation. ### Skill Confidence Lifecycle @@ -454,7 +454,7 @@ The `sql` tool is **CLI-only**. It does not exist on VS Code, JetBrains, or GitH 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, graceful degradation. Read `.squad/templates/mcp-config.md` for config file locations, sample configs, and authentication notes. +> **Full patterns:** Read `.copilot/skills/mcp-tool-discovery/SKILL.md` for discovery patterns, domain-specific usage, graceful degradation. Read `.squad/templates/mcp-config.md` for config file locations, sample configs, and authentication notes. #### Detection @@ -649,7 +649,7 @@ prompt: | Read .squad/decisions.md (team decisions to respect). If .squad/identity/wisdom.md exists, read it before starting work. If .squad/identity/now.md exists, read it at spawn time. - If .squad/skills/ has relevant SKILL.md files, read them before working. + If .copilot/skills/ has relevant SKILL.md files, read them before working. {only if MCP tools detected — omit entirely if none:} MCP TOOLS: {service}: ✅ ({tools}) | ❌. Fall back to CLI when unavailable. @@ -671,7 +671,7 @@ prompt: | 2. If you made a team-relevant decision, write to: .squad/decisions/inbox/{name}-{brief-slug}.md 3. SKILL EXTRACTION: If you found a reusable pattern, write/update - .squad/skills/{skill-name}/SKILL.md (read templates/skill.md for format). + .copilot/skills/{skill-name}/SKILL.md (read templates/skill.md for format). ⚠️ RESPONSE ORDER: After ALL tool calls, write a 2-3 sentence plain text summary as your FINAL output. No tool calls after this summary. @@ -756,7 +756,7 @@ Ceremonies are structured team meetings where agents align before or after work. If the user says "I need a designer" or "add someone for DevOps": 1. **Allocate a name** from the current assignment's universe (read from `.squad/casting/history.json`). If the universe is exhausted, apply overflow handling (see Casting & Persistent Naming → Overflow Handling). -2. **Check plugin marketplaces.** If `.squad/plugins/marketplaces.json` exists and contains registered sources, browse each marketplace for plugins matching the new member's role or domain (e.g., "azure-cloud-development" for an Azure DevOps role). Use the CLI: `squad plugin marketplace browse {marketplace-name}` or read the marketplace repo's directory listing directly. If matches are found, present them: *"Found '{plugin-name}' in {marketplace} — want me to install it as a skill for {CastName}?"* If the user accepts, copy the plugin content into `.squad/skills/{plugin-name}/SKILL.md` or merge relevant instructions into the agent's charter. If no marketplaces are configured, skip silently. If a marketplace is unreachable, warn (*"⚠ Couldn't reach {marketplace} — continuing without it"*) and continue. +2. **Check plugin marketplaces.** If `.squad/plugins/marketplaces.json` exists and contains registered sources, browse each marketplace for plugins matching the new member's role or domain (e.g., "azure-cloud-development" for an Azure DevOps role). Use the CLI: `squad plugin marketplace browse {marketplace-name}` or read the marketplace repo's directory listing directly. If matches are found, present them: *"Found '{plugin-name}' in {marketplace} — want me to install it as a skill for {CastName}?"* If the user accepts, copy the plugin content into `.copilot/skills/{plugin-name}/SKILL.md` or merge relevant instructions into the agent's charter. If no marketplaces are configured, skip silently. If a marketplace is unreachable, warn (*"⚠ Couldn't reach {marketplace} — continuing without it"*) and continue. 3. Generate a new charter.md + history.md (seeded with project context from team.md), using the cast name. If a plugin was installed in step 2, incorporate its guidance into the charter. 4. **Update `.squad/casting/registry.json`** with the new agent entry. 5. Add to team.md roster. @@ -779,7 +779,7 @@ If the user wants to remove someone: **Core rules (always loaded):** - Check `.squad/plugins/marketplaces.json` during Add Team Member flow (after name allocation, before charter) - Present matching plugins for user approval -- Install: copy to `.squad/skills/{plugin-name}/SKILL.md`, log to history.md +- Install: copy to `.copilot/skills/{plugin-name}/SKILL.md`, log to history.md - Skip silently if no marketplaces configured --- diff --git a/packages/squad-sdk/bradygaster-squad-sdk-0.8.25-build.10.tgz b/packages/squad-sdk/bradygaster-squad-sdk-0.8.25-build.10.tgz new file mode 100644 index 000000000..3a2b7c71b Binary files /dev/null and b/packages/squad-sdk/bradygaster-squad-sdk-0.8.25-build.10.tgz differ diff --git a/packages/squad-sdk/src/config/init.ts b/packages/squad-sdk/src/config/init.ts index 9a768e710..a2978f439 100644 --- a/packages/squad-sdk/src/config/init.ts +++ b/packages/squad-sdk/src/config/init.ts @@ -682,7 +682,7 @@ export async function initSquad(options: InitOptions): Promise { join(squadDir, 'casting'), join(squadDir, 'decisions'), join(squadDir, 'decisions', 'inbox'), - join(squadDir, 'skills'), + join(teamRoot, '.copilot', 'skills'), join(squadDir, 'plugins'), join(squadDir, 'identity'), join(squadDir, 'orchestration-log'), @@ -901,13 +901,13 @@ ${projectDescription ? `- **Description:** ${projectDescription}\n` : ''}- **Cre // Copy starter skills // ------------------------------------------------------------------------- - const skillsDir = join(squadDir, 'skills'); + const skillsDir = join(teamRoot, '.copilot', 'skills'); if (templatesDir && existsSync(join(templatesDir, 'skills'))) { const skillsSrc = join(templatesDir, 'skills'); const existingSkills = existsSync(skillsDir) ? readdirSync(skillsDir) : []; if (existingSkills.length === 0) { cpSync(skillsSrc, skillsDir, { recursive: true }); - createdFiles.push('.squad/skills'); + createdFiles.push('.copilot/skills'); } } diff --git a/packages/squad-sdk/src/sharing/consult.ts b/packages/squad-sdk/src/sharing/consult.ts index 03b761f81..2bd2d4fbf 100644 --- a/packages/squad-sdk/src/sharing/consult.ts +++ b/packages/squad-sdk/src/sharing/consult.ts @@ -177,7 +177,7 @@ You are **Squad (Consultant)** — working on **${projectName}** using a copy of - **Team:** \`.squad/team.md\` for roster and roles - **Routing:** \`.squad/routing.md\` for task routing rules - **Decisions:** \`.squad/decisions.md\` for your established patterns -- **Skills:** \`.squad/skills/\` for reusable capabilities +- **Skills:** \`.copilot/skills/\` for reusable capabilities - **Agents:** \`.squad/agents/\` for your squad agents Work as you would with your personal squad, but in this external codebase. @@ -1006,7 +1006,7 @@ function extractSkillName(content: string): string | null { /** * Merge staged learnings into personal squad. * - * Routes skills to personal squad directory via resolveGlobalSquadPath() to skills/{name}/SKILL.md + * Routes skills to personal squad directory via resolveGlobalSquadPath() to .copilot/skills/{name}/SKILL.md * Routes decisions to decisions.md in personal squad directory (with smart merge) * * @param learnings - Staged learnings to merge @@ -1035,8 +1035,8 @@ export async function mergeToPersonalSquad( } } - // Route skills to personal squad directory (via resolveGlobalSquadPath()) at skills/{name}/SKILL.md - const skillsDir = path.join(personalSquadRoot, 'skills'); + // Route skills to personal squad directory (via resolveGlobalSquadPath()) at .copilot/skills/{name}/SKILL.md + const skillsDir = path.join(path.dirname(personalSquadRoot), '.copilot', 'skills'); for (const skill of skills) { const skillName = extractSkillName(skill.content) || skill.filename.replace('.md', ''); const skillDir = path.join(skillsDir, skillName); diff --git a/packages/squad-sdk/src/sharing/export.ts b/packages/squad-sdk/src/sharing/export.ts index 1e2a0ecf0..2829bb33f 100644 --- a/packages/squad-sdk/src/sharing/export.ts +++ b/packages/squad-sdk/src/sharing/export.ts @@ -127,10 +127,22 @@ export function exportSquadConfig(projectDir: string, options?: ExportOptions): const skills: string[] = []; if (opts.includeSkills) { - const skillsDir = join(projectDir, '.ai-team', 'skills'); - if (existsSync(skillsDir)) { - const skillFiles = readdirSync(skillsDir).filter(f => f.endsWith('.md')); - skills.push(...skillFiles.map(f => basename(f, '.md'))); + const skillSources = [ + { dir: join(projectDir, '.copilot', 'skills'), layout: 'nested' as const }, + { dir: join(projectDir, '.squad', 'skills'), layout: 'nested' as const }, + { dir: join(projectDir, '.ai-team', 'skills'), layout: 'flat' as const }, + ]; + const source = skillSources.find(({ dir }) => existsSync(dir)); + if (source) { + if (source.layout === 'nested') { + const skillDirs = readdirSync(source.dir, { withFileTypes: true }) + .filter(entry => entry.isDirectory() && existsSync(join(source.dir, entry.name, 'SKILL.md'))) + .map(entry => entry.name); + skills.push(...skillDirs); + } else { + const skillFiles = readdirSync(source.dir).filter(f => f.endsWith('.md')); + skills.push(...skillFiles.map(f => basename(f, '.md'))); + } } } diff --git a/packages/squad-sdk/src/skills/handler-types.ts b/packages/squad-sdk/src/skills/handler-types.ts index d3ee843ee..ecc79465e 100644 --- a/packages/squad-sdk/src/skills/handler-types.ts +++ b/packages/squad-sdk/src/skills/handler-types.ts @@ -1,7 +1,7 @@ /** * Type system for the skill-script model. * - * This module defines the complete type contract for backend skills in `.squad/skills/`. + * This module defines the complete type contract for backend skills in `.copilot/skills/`. * Skills contain `scripts/` directories with executable JS handlers that replace built-in * tool handlers in ToolRegistry. * diff --git a/packages/squad-sdk/src/skills/skill-script-loader.ts b/packages/squad-sdk/src/skills/skill-script-loader.ts index 27f4e0748..6a8a683f5 100644 --- a/packages/squad-sdk/src/skills/skill-script-loader.ts +++ b/packages/squad-sdk/src/skills/skill-script-loader.ts @@ -2,7 +2,7 @@ * Skill Script Loader * * Runtime loader for executable skill handlers from backend skill directories. - * Backend skills in `.squad/skills/{name}/scripts/` contain `.js` handler files + * Backend skills in `.copilot/skills/{name}/scripts/` contain `.js` handler files * that replace built-in tool handlers in ToolRegistry. * * Supports: @@ -86,7 +86,7 @@ function toFileUrl(filePath: string): string { * * Algorithm: * 1. Absolute paths used as-is - * 2. With teamRoot: strip leading `.squad/` prefix to avoid double-nesting, resolve relative to teamRoot + * 2. With teamRoot: resolve `.copilot/` paths from projectRoot and strip legacy `.squad/` paths relative to teamRoot * 3. Without teamRoot: resolve relative to projectRoot * 4. Path containment check: final path must be within projectRoot or teamRoot * 5. Reject paths with `..` segments that escape the boundary (throw Error) @@ -122,8 +122,15 @@ export function resolveSkillPath( return resolved; } - // 2. With teamRoot: strip leading .squad/ prefix, resolve relative to teamRoot + // 2. With teamRoot: resolve .copilot/ paths from projectRoot, legacy .squad/ paths from teamRoot if (teamRoot) { + if (skillPath.startsWith('.copilot/')) { + const resolved = path.resolve(projectRoot, skillPath); + const real = realOrLogical(resolved); + if (!isContained(real, projectRoot)) throw new Error(`Path escapes containment: ${skillPath} resolves outside project root`); + return resolved; + } + const stripped = skillPath.startsWith('.squad/') ? skillPath.slice(7) : skillPath; const resolved = path.resolve(teamRoot, stripped); const real = realOrLogical(resolved); diff --git a/packages/squad-sdk/src/skills/skill-source.ts b/packages/squad-sdk/src/skills/skill-source.ts index cf27035ee..6cfdc196f 100644 --- a/packages/squad-sdk/src/skills/skill-source.ts +++ b/packages/squad-sdk/src/skills/skill-source.ts @@ -39,6 +39,9 @@ export class LocalSkillSource implements SkillSource { } private get skillsDir(): string { + const copilotDir = path.join(this.basePath, '.copilot', 'skills'); + if (fs.existsSync(copilotDir)) return copilotDir; + // Backward compat: fall back to legacy location return path.join(this.basePath, '.squad', 'skills'); } @@ -129,7 +132,7 @@ export class GitHubSkillSource implements SkillSource { this.owner = parts[0]; this.repoName = parts[1]; this.branch = options?.ref; - this.pathPrefix = options?.pathPrefix ?? '.squad/skills'; + this.pathPrefix = options?.pathPrefix ?? '.copilot/skills'; this.fetcher = options?.fetcher ?? { async listDirectory() { throw new Error('No GitHubFetcher configured'); }, async getFileContent() { throw new Error('No GitHubFetcher configured'); }, diff --git a/packages/squad-sdk/src/tools/index.ts b/packages/squad-sdk/src/tools/index.ts index cc9180943..52ccce7ff 100644 --- a/packages/squad-sdk/src/tools/index.ts +++ b/packages/squad-sdk/src/tools/index.ts @@ -88,7 +88,7 @@ export interface StatusQuery { } export interface SkillRequest { - /** Skill name (maps to .squad/skills/{name}/SKILL.md) */ + /** Skill name (maps to .copilot/skills/{name}/SKILL.md) */ skillName: string; /** Operation: read the skill or write/update it */ operation: 'read' | 'write'; @@ -490,7 +490,7 @@ export class ToolRegistry { // squad_skill: Read/write agent skills const squadSkill = defineTool({ name: 'squad_skill', - description: 'Read or write agent skill definitions. Skills are stored in .squad/skills/{name}/SKILL.md.', + description: 'Read or write agent skill definitions. Skills are stored in .copilot/skills/{name}/SKILL.md.', parameters: { type: 'object', properties: { @@ -520,7 +520,14 @@ export class ToolRegistry { return { textResultForLlm: 'Invalid skill name: must contain only letters, numbers, hyphens, and underscores', resultType: 'failure', error: 'Invalid skillName' }; } try { - const skillDir = path.join(this.squadRoot, 'skills', args.skillName); + const projectRoot = path.dirname(this.squadRoot); + const legacySkillDir = path.join(this.squadRoot, 'skills', args.skillName); + const copilotSkillDir = path.join(projectRoot, '.copilot', 'skills', args.skillName); + const skillDir = args.operation === 'write' + ? copilotSkillDir + : fs.existsSync(path.join(copilotSkillDir, 'SKILL.md')) + ? copilotSkillDir + : legacySkillDir; const skillFile = path.join(skillDir, 'SKILL.md'); if (args.operation === 'read') { @@ -562,7 +569,7 @@ export class ToolRegistry { fs.writeFileSync(skillFile, skillContent, 'utf-8'); return { - textResultForLlm: `Skill written: ${args.skillName} (skills/${args.skillName}/SKILL.md)`, + textResultForLlm: `Skill written: ${args.skillName} (.copilot/skills/${args.skillName}/SKILL.md)`, resultType: 'success', toolTelemetry: { skillName: args.skillName, operation: 'write', confidence: args.confidence }, }; diff --git a/packages/squad-sdk/src/upstream/resolver.ts b/packages/squad-sdk/src/upstream/resolver.ts index c424334f9..a57720f05 100644 --- a/packages/squad-sdk/src/upstream/resolver.ts +++ b/packages/squad-sdk/src/upstream/resolver.ts @@ -53,15 +53,24 @@ function findSquadDir(sourcePath: string): string | null { * Read all skills from a squad directory's skills/ folder. */ function readSkills(squadDir: string): Array<{ name: string; content: string }> { - const skillsDir = path.join(squadDir, 'skills'); - if (!fs.existsSync(skillsDir)) return []; + const projectDir = path.dirname(squadDir); + const candidateDirs = [ + { dir: path.join(projectDir, '.copilot', 'skills'), layout: 'nested' as const }, + { dir: path.join(squadDir, 'skills'), layout: 'nested' as const }, + { dir: path.join(projectDir, '.ai-team', 'skills'), layout: 'flat' as const }, + ]; + const source = candidateDirs.find(({ dir }) => fs.existsSync(dir)); + if (!source) return []; const skills: Array<{ name: string; content: string }> = []; try { - for (const entry of fs.readdirSync(skillsDir)) { - const skillFile = path.join(skillsDir, entry, 'SKILL.md'); + for (const entry of fs.readdirSync(source.dir)) { + const skillFile = source.layout === 'nested' + ? path.join(source.dir, entry, 'SKILL.md') + : path.join(source.dir, entry); if (fs.existsSync(skillFile)) { - skills.push({ name: entry, content: fs.readFileSync(skillFile, 'utf8') }); + const name = source.layout === 'nested' ? entry : path.basename(entry, '.md'); + skills.push({ name, content: fs.readFileSync(skillFile, 'utf8') }); } } } catch { diff --git a/packages/squad-sdk/templates/mcp-config.md b/packages/squad-sdk/templates/mcp-config.md index 9ddc78e9b..6293bed11 100644 --- a/packages/squad-sdk/templates/mcp-config.md +++ b/packages/squad-sdk/templates/mcp-config.md @@ -2,7 +2,7 @@ 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. +> **Full patterns:** Read `.copilot/skills/mcp-tool-discovery/SKILL.md` for discovery patterns, domain-specific usage, and graceful degradation. ## Security Considerations diff --git a/packages/squad-sdk/templates/plugin-marketplace.md b/packages/squad-sdk/templates/plugin-marketplace.md index 893632816..efef3d7a1 100644 --- a/packages/squad-sdk/templates/plugin-marketplace.md +++ b/packages/squad-sdk/templates/plugin-marketplace.md @@ -38,7 +38,7 @@ During the **Adding Team Members** flow, AFTER allocating a name but BEFORE gene ## 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` +2. Copy it into the agent's skills directory: `.copilot/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}."* diff --git a/packages/squad-sdk/templates/squad.agent.md b/packages/squad-sdk/templates/squad.agent.md index 39a97d2d2..970747bab 100644 --- a/packages/squad-sdk/templates/squad.agent.md +++ b/packages/squad-sdk/templates/squad.agent.md @@ -234,7 +234,7 @@ The routing table determines **WHO** handles work. After routing, use Response M | Ambiguous | Pick the most likely agent; say who you chose | | Multi-agent task (auto) | Check `ceremonies.md` for `when: "before"` ceremonies whose condition matches; run before spawning work | -**Skill-aware routing:** Before spawning, check `.squad/skills/` for skills relevant to the task domain. If a matching skill exists, add to the spawn prompt: `Relevant skill: .squad/skills/{name}/SKILL.md — read before starting.` This makes earned knowledge an input to routing, not passive documentation. +**Skill-aware routing:** Before spawning, check `.copilot/skills/` for skills relevant to the task domain. If a matching skill exists, add to the spawn prompt: `Relevant skill: .copilot/skills/{name}/SKILL.md — read before starting.` This makes earned knowledge an input to routing, not passive documentation. ### Skill Confidence Lifecycle @@ -454,7 +454,7 @@ The `sql` tool is **CLI-only**. It does not exist on VS Code, JetBrains, or GitH 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, graceful degradation. Read `.squad/templates/mcp-config.md` for config file locations, sample configs, and authentication notes. +> **Full patterns:** Read `.copilot/skills/mcp-tool-discovery/SKILL.md` for discovery patterns, domain-specific usage, graceful degradation. Read `.squad/templates/mcp-config.md` for config file locations, sample configs, and authentication notes. #### Detection @@ -649,7 +649,7 @@ prompt: | Read .squad/decisions.md (team decisions to respect). If .squad/identity/wisdom.md exists, read it before starting work. If .squad/identity/now.md exists, read it at spawn time. - If .squad/skills/ has relevant SKILL.md files, read them before working. + If .copilot/skills/ has relevant SKILL.md files, read them before working. {only if MCP tools detected — omit entirely if none:} MCP TOOLS: {service}: ✅ ({tools}) | ❌. Fall back to CLI when unavailable. @@ -671,7 +671,7 @@ prompt: | 2. If you made a team-relevant decision, write to: .squad/decisions/inbox/{name}-{brief-slug}.md 3. SKILL EXTRACTION: If you found a reusable pattern, write/update - .squad/skills/{skill-name}/SKILL.md (read templates/skill.md for format). + .copilot/skills/{skill-name}/SKILL.md (read templates/skill.md for format). ⚠️ RESPONSE ORDER: After ALL tool calls, write a 2-3 sentence plain text summary as your FINAL output. No tool calls after this summary. @@ -756,7 +756,7 @@ Ceremonies are structured team meetings where agents align before or after work. If the user says "I need a designer" or "add someone for DevOps": 1. **Allocate a name** from the current assignment's universe (read from `.squad/casting/history.json`). If the universe is exhausted, apply overflow handling (see Casting & Persistent Naming → Overflow Handling). -2. **Check plugin marketplaces.** If `.squad/plugins/marketplaces.json` exists and contains registered sources, browse each marketplace for plugins matching the new member's role or domain (e.g., "azure-cloud-development" for an Azure DevOps role). Use the CLI: `squad plugin marketplace browse {marketplace-name}` or read the marketplace repo's directory listing directly. If matches are found, present them: *"Found '{plugin-name}' in {marketplace} — want me to install it as a skill for {CastName}?"* If the user accepts, copy the plugin content into `.squad/skills/{plugin-name}/SKILL.md` or merge relevant instructions into the agent's charter. If no marketplaces are configured, skip silently. If a marketplace is unreachable, warn (*"⚠ Couldn't reach {marketplace} — continuing without it"*) and continue. +2. **Check plugin marketplaces.** If `.squad/plugins/marketplaces.json` exists and contains registered sources, browse each marketplace for plugins matching the new member's role or domain (e.g., "azure-cloud-development" for an Azure DevOps role). Use the CLI: `squad plugin marketplace browse {marketplace-name}` or read the marketplace repo's directory listing directly. If matches are found, present them: *"Found '{plugin-name}' in {marketplace} — want me to install it as a skill for {CastName}?"* If the user accepts, copy the plugin content into `.copilot/skills/{plugin-name}/SKILL.md` or merge relevant instructions into the agent's charter. If no marketplaces are configured, skip silently. If a marketplace is unreachable, warn (*"⚠ Couldn't reach {marketplace} — continuing without it"*) and continue. 3. Generate a new charter.md + history.md (seeded with project context from team.md), using the cast name. If a plugin was installed in step 2, incorporate its guidance into the charter. 4. **Update `.squad/casting/registry.json`** with the new agent entry. 5. Add to team.md roster. @@ -779,7 +779,7 @@ If the user wants to remove someone: **Core rules (always loaded):** - Check `.squad/plugins/marketplaces.json` during Add Team Member flow (after name allocation, before charter) - Present matching plugins for user approval -- Install: copy to `.squad/skills/{plugin-name}/SKILL.md`, log to history.md +- Install: copy to `.copilot/skills/{plugin-name}/SKILL.md`, log to history.md - Skip silently if no marketplaces configured --- diff --git a/test/cli/export-import.test.ts b/test/cli/export-import.test.ts index 16d4733bd..b25b45a00 100644 --- a/test/cli/export-import.test.ts +++ b/test/cli/export-import.test.ts @@ -118,7 +118,7 @@ describe('CLI: export/import commands', () => { await writeFile(teamPath, '# Team\n'); // Create a skill - const skillDir = join(TEST_ROOT, '.squad', 'skills', 'test-skill'); + const skillDir = join(TEST_ROOT, '.copilot', 'skills', 'test-skill'); await mkdir(skillDir, { recursive: true }); const skillContent = 'name: "Test Skill"\ndescription: "A test skill"'; await writeFile(join(skillDir, 'SKILL.md'), skillContent); diff --git a/test/cli/init.test.ts b/test/cli/init.test.ts index 87a697a4d..c815b476a 100644 --- a/test/cli/init.test.ts +++ b/test/cli/init.test.ts @@ -61,7 +61,7 @@ describe('CLI: init command', () => { expect(existsSync(join(TEST_ROOT, '.squad', 'decisions', 'inbox'))).toBe(true); expect(existsSync(join(TEST_ROOT, '.squad', 'orchestration-log'))).toBe(true); expect(existsSync(join(TEST_ROOT, '.squad', 'casting'))).toBe(true); - expect(existsSync(join(TEST_ROOT, '.squad', 'skills'))).toBe(true); + expect(existsSync(join(TEST_ROOT, '.copilot', 'skills'))).toBe(true); expect(existsSync(join(TEST_ROOT, '.squad', 'plugins'))).toBe(true); expect(existsSync(join(TEST_ROOT, '.squad', 'identity'))).toBe(true); }); @@ -138,7 +138,7 @@ describe('CLI: init command', () => { it('should copy starter skills if none exist', async () => { await runInit(TEST_ROOT); - const skillsPath = join(TEST_ROOT, '.squad', 'skills'); + const skillsPath = join(TEST_ROOT, '.copilot', 'skills'); const skills = await readdir(skillsPath); // Should have at least one skill diff --git a/test/human-journeys.test.ts b/test/human-journeys.test.ts index 67fc8e133..be781fe3a 100644 --- a/test/human-journeys.test.ts +++ b/test/human-journeys.test.ts @@ -110,7 +110,7 @@ describe('Journey 1: I just installed this (squad init)', () => { // The human sees: a .squad/ directory was created expect(existsSync(join(tempDir, '.squad'))).toBe(true); - expect(existsSync(join(tempDir, '.squad', 'skills'))).toBe(true); + expect(existsSync(join(tempDir, '.copilot', 'skills'))).toBe(true); expect(existsSync(join(tempDir, '.squad', 'identity'))).toBe(true); expect(existsSync(join(tempDir, '.squad', 'ceremonies.md'))).toBe(true); }); diff --git a/test/init-sdk.test.ts b/test/init-sdk.test.ts index e3d14a23a..63227d251 100644 --- a/test/init-sdk.test.ts +++ b/test/init-sdk.test.ts @@ -137,8 +137,8 @@ describe('squad init --sdk flag', () => { // Assert: .squad/decisions/inbox/ exists expect(existsSync(join(tempDir, '.squad', 'decisions', 'inbox'))).toBe(true); - // Assert: .squad/skills/ exists - expect(existsSync(join(tempDir, '.squad', 'skills'))).toBe(true); + // Assert: .copilot/skills/ exists + expect(existsSync(join(tempDir, '.copilot', 'skills'))).toBe(true); // Assert: .squad/identity/ exists expect(existsSync(join(tempDir, '.squad', 'identity'))).toBe(true); diff --git a/test/init.test.ts b/test/init.test.ts index 824914456..63b883ac5 100644 --- a/test/init.test.ts +++ b/test/init.test.ts @@ -136,7 +136,7 @@ describe('Squad Initialization', () => { expect(existsSync(join(TEST_ROOT, '.squad', 'agents'))).toBe(true); expect(existsSync(join(TEST_ROOT, '.squad', 'casting'))).toBe(true); expect(existsSync(join(TEST_ROOT, '.squad', 'decisions'))).toBe(true); - expect(existsSync(join(TEST_ROOT, '.squad', 'skills'))).toBe(true); + expect(existsSync(join(TEST_ROOT, '.copilot', 'skills'))).toBe(true); }); it('should create .gitattributes for merge drivers', async () => { diff --git a/test/repl-ux-fixes.test.ts b/test/repl-ux-fixes.test.ts index 699845463..3ecbe93a5 100644 --- a/test/repl-ux-fixes.test.ts +++ b/test/repl-ux-fixes.test.ts @@ -102,10 +102,12 @@ describe('#596 — Init creates complete .squad/ directory', () => { expect(existsSync(join(squadDir, 'decisions', 'inbox'))).toBe(true); expect(existsSync(join(squadDir, 'orchestration-log'))).toBe(true); expect(existsSync(join(squadDir, 'casting'))).toBe(true); - expect(existsSync(join(squadDir, 'skills'))).toBe(true); expect(existsSync(join(squadDir, 'plugins'))).toBe(true); expect(existsSync(join(squadDir, 'identity'))).toBe(true); + // Skills now live in .copilot/skills/ (not .squad/skills/) + expect(existsSync(join(tmpRoot, '.copilot', 'skills'))).toBe(true); + // Required files expect(existsSync(join(squadDir, 'ceremonies.md'))).toBe(true); expect(existsSync(join(squadDir, 'identity', 'now.md'))).toBe(true); diff --git a/test/skill-script-loader.test.ts b/test/skill-script-loader.test.ts index 5118e54d3..581901922 100644 --- a/test/skill-script-loader.test.ts +++ b/test/skill-script-loader.test.ts @@ -589,12 +589,15 @@ describe('resolveSkillPath()', () => { expect(result).toBe(path.resolve(teamRoot, 'skills', 'my-skill')); }); - it('should strip .squad/ prefix when teamRoot is provided', () => { - // Use forward slash format that the implementation checks for + it('should resolve .copilot/ prefix from projectRoot when teamRoot is provided', () => { + const relative = '.copilot/skills/my-skill'; + const result = resolveSkillPath(relative, projectRoot, teamRoot); + expect(result).toBe(path.resolve(projectRoot, '.copilot', 'skills', 'my-skill')); + }); + + it('should strip legacy .squad/ prefix when teamRoot is provided', () => { const relative = '.squad/skills/my-skill'; const result = resolveSkillPath(relative, projectRoot, teamRoot); - // Implementation strips .squad/ and resolves from teamRoot - // So .squad/skills/my-skill becomes teamRoot/skills/my-skill expect(result).toBe(path.resolve(teamRoot, 'skills', 'my-skill')); }); diff --git a/test/skill-source.test.ts b/test/skill-source.test.ts index 18eb0de3e..f8d2fb20e 100644 --- a/test/skill-source.test.ts +++ b/test/skill-source.test.ts @@ -76,14 +76,14 @@ describe('LocalSkillSource', () => { expect(source.type).toBe('local'); }); - it('should return empty list when .squad/skills/ does not exist', async () => { + it('should return empty list when .copilot/skills/ does not exist', async () => { const source = new LocalSkillSource(tempDir); const skills = await source.listSkills(); expect(skills).toEqual([]); }); - it('should list skills from .squad/skills/ directories', async () => { - const skillsDir = path.join(tempDir, '.squad', 'skills', 'ts-testing'); + it('should list skills from .copilot/skills/ directories', async () => { + const skillsDir = path.join(tempDir, '.copilot', 'skills', 'ts-testing'); fs.mkdirSync(skillsDir, { recursive: true }); fs.writeFileSync(path.join(skillsDir, 'SKILL.md'), SKILL_MD); @@ -98,7 +98,7 @@ describe('LocalSkillSource', () => { }); it('should skip directories without SKILL.md', async () => { - const emptyDir = path.join(tempDir, '.squad', 'skills', 'empty-skill'); + const emptyDir = path.join(tempDir, '.copilot', 'skills', 'empty-skill'); fs.mkdirSync(emptyDir, { recursive: true }); const source = new LocalSkillSource(tempDir); @@ -107,7 +107,7 @@ describe('LocalSkillSource', () => { }); it('should getSkill by id', async () => { - const skillsDir = path.join(tempDir, '.squad', 'skills', 'ts-testing'); + const skillsDir = path.join(tempDir, '.copilot', 'skills', 'ts-testing'); fs.mkdirSync(skillsDir, { recursive: true }); fs.writeFileSync(path.join(skillsDir, 'SKILL.md'), SKILL_MD); @@ -130,7 +130,7 @@ describe('LocalSkillSource', () => { }); it('should getContent by id', async () => { - const skillsDir = path.join(tempDir, '.squad', 'skills', 'ts-testing'); + const skillsDir = path.join(tempDir, '.copilot', 'skills', 'ts-testing'); fs.mkdirSync(skillsDir, { recursive: true }); fs.writeFileSync(path.join(skillsDir, 'SKILL.md'), SKILL_MD); @@ -145,6 +145,18 @@ describe('LocalSkillSource', () => { expect(content).toBeNull(); }); + it('should fall back to legacy .squad/skills/ when .copilot/skills/ is absent', async () => { + const legacyDir = path.join(tempDir, '.squad', 'skills', 'legacy-skill'); + fs.mkdirSync(legacyDir, { recursive: true }); + fs.writeFileSync(path.join(legacyDir, 'SKILL.md'), SKILL_MD_MINIMAL); + + const source = new LocalSkillSource(tempDir); + const skills = await source.listSkills(); + + expect(skills).toHaveLength(1); + expect(skills[0].id).toBe('legacy-skill'); + }); + it('should support custom priority', () => { const source = new LocalSkillSource(tempDir, 10); expect(source.priority).toBe(10); @@ -165,7 +177,7 @@ describe('GitHubSkillSource', () => { it('should list skills from GitHub repo', async () => { const fetcher = makeFetcher( [{ name: 'ts-testing', type: 'dir' }], - { '.squad/skills/ts-testing/SKILL.md': SKILL_MD }, + { '.copilot/skills/ts-testing/SKILL.md': SKILL_MD }, ); const source = new GitHubSkillSource('acme/repo', { fetcher }); @@ -189,7 +201,7 @@ describe('GitHubSkillSource', () => { it('should getSkill from GitHub', async () => { const fetcher = makeFetcher([], { - '.squad/skills/docker/SKILL.md': SKILL_MD_MINIMAL, + '.copilot/skills/docker/SKILL.md': SKILL_MD_MINIMAL, }); const source = new GitHubSkillSource('acme/repo', { fetcher }); @@ -210,7 +222,7 @@ describe('GitHubSkillSource', () => { it('should getContent from GitHub', async () => { const fetcher = makeFetcher([], { - '.squad/skills/docker/SKILL.md': SKILL_MD_MINIMAL, + '.copilot/skills/docker/SKILL.md': SKILL_MD_MINIMAL, }); const source = new GitHubSkillSource('acme/repo', { fetcher }); @@ -234,11 +246,11 @@ describe('SkillSourceRegistry', () => { it('should list skills from all sources', async () => { const fetcher1 = makeFetcher( [{ name: 'skill-a', type: 'dir' }], - { '.squad/skills/skill-a/SKILL.md': SKILL_MD }, + { '.copilot/skills/skill-a/SKILL.md': SKILL_MD }, ); const fetcher2 = makeFetcher( [{ name: 'skill-b', type: 'dir' }], - { '.squad/skills/skill-b/SKILL.md': SKILL_MD_MINIMAL }, + { '.copilot/skills/skill-b/SKILL.md': SKILL_MD_MINIMAL }, ); const registry = new SkillSourceRegistry(); @@ -252,7 +264,7 @@ describe('SkillSourceRegistry', () => { it('should find skill across sources', async () => { const fetcher = makeFetcher([], { - '.squad/skills/docker/SKILL.md': SKILL_MD_MINIMAL, + '.copilot/skills/docker/SKILL.md': SKILL_MD_MINIMAL, }); const registry = new SkillSourceRegistry(); @@ -274,7 +286,7 @@ describe('SkillSourceRegistry', () => { it('should get content across sources', async () => { const fetcher = makeFetcher([], { - '.squad/skills/docker/SKILL.md': SKILL_MD_MINIMAL, + '.copilot/skills/docker/SKILL.md': SKILL_MD_MINIMAL, }); const registry = new SkillSourceRegistry(); @@ -286,10 +298,10 @@ describe('SkillSourceRegistry', () => { it('should respect source priority ordering', async () => { const fetcherHigh = makeFetcher([], { - '.squad/skills/shared/SKILL.md': `---\nname: High Priority\ndomain: hp\ntriggers: []\nroles: []\n---\nHigh priority content.`, + '.copilot/skills/shared/SKILL.md': `---\nname: High Priority\ndomain: hp\ntriggers: []\nroles: []\n---\nHigh priority content.`, }); const fetcherLow = makeFetcher([], { - '.squad/skills/shared/SKILL.md': `---\nname: Low Priority\ndomain: lp\ntriggers: []\nroles: []\n---\nLow priority content.`, + '.copilot/skills/shared/SKILL.md': `---\nname: Low Priority\ndomain: lp\ntriggers: []\nroles: []\n---\nLow priority content.`, }); const registry = new SkillSourceRegistry(); diff --git a/test/skills-export-import.test.js b/test/skills-export-import.test.js index 286cee80e..d88edb044 100644 --- a/test/skills-export-import.test.js +++ b/test/skills-export-import.test.js @@ -68,7 +68,7 @@ describe('Skills survive export/import round-trip (#82)', () => { initSquad(tmpDir1); // Create a test skill - const skillDir = path.join(tmpDir1, '.ai-team', 'skills', 'test-skill'); + const skillDir = path.join(tmpDir1, '.copilot', 'skills', 'test-skill'); fs.mkdirSync(skillDir, { recursive: true }); const skillContent = `--- name: test-skill @@ -109,7 +109,7 @@ This is a test skill for verifying export/import functionality. initSquad(tmpDir1); // Create a test skill with unique content - const skillDir = path.join(tmpDir1, '.ai-team', 'skills', 'import-test-skill'); + const skillDir = path.join(tmpDir1, '.copilot', 'skills', 'import-test-skill'); fs.mkdirSync(skillDir, { recursive: true }); const skillContent = `--- name: import-test-skill @@ -135,7 +135,7 @@ This skill has unique content to verify import works correctly. const exportResult = runSquad(['export'], tmpDir1); assert.equal(exportResult.exitCode, 0, `export should succeed: ${exportResult.stdout}`); - // Initialize squad in second temp dir (to have .ai-team structure) + // Initialize squad in second temp dir (to have squad structure) initSquad(tmpDir2); // Import into second temp dir with --force @@ -143,7 +143,7 @@ This skill has unique content to verify import works correctly. assert.equal(importResult.exitCode, 0, `import should succeed: ${importResult.stdout}`); // Verify the skill exists in the new directory - const importedSkillPath = path.join(tmpDir2, '.ai-team', 'skills', 'import-test-skill', 'SKILL.md'); + const importedSkillPath = path.join(tmpDir2, '.copilot', 'skills', 'import-test-skill', 'SKILL.md'); assert.ok(fs.existsSync(importedSkillPath), 'imported skill file should exist'); // Verify the content matches @@ -163,7 +163,7 @@ This skill has unique content to verify import works correctly. ]; for (const skill of skills) { - const skillDir = path.join(tmpDir1, '.ai-team', 'skills', skill.name); + const skillDir = path.join(tmpDir1, '.copilot', 'skills', skill.name); fs.mkdirSync(skillDir, { recursive: true }); const skillContent = `--- name: ${skill.name} @@ -196,7 +196,7 @@ ${skill.content} // Verify all skills exist for (const skill of skills) { - const importedSkillPath = path.join(tmpDir2, '.ai-team', 'skills', skill.name, 'SKILL.md'); + const importedSkillPath = path.join(tmpDir2, '.copilot', 'skills', skill.name, 'SKILL.md'); assert.ok(fs.existsSync(importedSkillPath), `skill ${skill.name} should exist after import`); const content = fs.readFileSync(importedSkillPath, 'utf8'); @@ -209,7 +209,7 @@ ${skill.content} initSquad(tmpDir1); // Create a skill with high confidence - const skillDir = path.join(tmpDir1, '.ai-team', 'skills', 'confidence-test'); + const skillDir = path.join(tmpDir1, '.copilot', 'skills', 'confidence-test'); fs.mkdirSync(skillDir, { recursive: true }); const skillContent = `--- name: confidence-test @@ -231,7 +231,7 @@ This skill should maintain its high confidence level after import. runSquad(['import', exportPath, '--force'], tmpDir2); // Verify confidence is preserved - const importedSkillPath = path.join(tmpDir2, '.ai-team', 'skills', 'confidence-test', 'SKILL.md'); + const importedSkillPath = path.join(tmpDir2, '.copilot', 'skills', 'confidence-test', 'SKILL.md'); const importedContent = fs.readFileSync(importedSkillPath, 'utf8'); assert.ok(importedContent.includes('confidence: high'), 'skill confidence level should be preserved'); }); @@ -241,7 +241,7 @@ This skill should maintain its high confidence level after import. initSquad(tmpDir1); // Create a skill - const skillDir = path.join(tmpDir1, '.ai-team', 'skills', 'report-test'); + const skillDir = path.join(tmpDir1, '.copilot', 'skills', 'report-test'); fs.mkdirSync(skillDir, { recursive: true }); fs.writeFileSync(path.join(skillDir, 'SKILL.md'), `--- name: report-test diff --git a/test/tools.test.ts b/test/tools.test.ts index 70c735dda..fb23f7ede 100644 --- a/test/tools.test.ts +++ b/test/tools.test.ts @@ -578,15 +578,18 @@ describe('squad_status handler', () => { describe('squad_skill handler', () => { let registry: ToolRegistry; let testRoot: string; + let projectRoot: string; beforeEach(() => { - testRoot = path.join('.', '.test-squad-skill-' + randomUUID()); + projectRoot = path.join('.', '.test-squad-skill-' + randomUUID()); + testRoot = path.join(projectRoot, '.squad'); + fs.mkdirSync(testRoot, { recursive: true }); registry = new ToolRegistry(testRoot); }); afterEach(() => { - if (fs.existsSync(testRoot)) { - fs.rmSync(testRoot, { recursive: true, force: true }); + if (fs.existsSync(projectRoot)) { + fs.rmSync(projectRoot, { recursive: true, force: true }); } }); @@ -611,7 +614,7 @@ describe('squad_skill handler', () => { resultType: 'success', }); - const skillFile = path.join(testRoot, 'skills', 'typescript-refactoring', 'SKILL.md'); + const skillFile = path.join(projectRoot, '.copilot', 'skills', 'typescript-refactoring', 'SKILL.md'); expect(fs.existsSync(skillFile)).toBe(true); const content = fs.readFileSync(skillFile, 'utf-8'); @@ -706,7 +709,7 @@ describe('squad_skill handler', () => { } ); - const skillFile = path.join(testRoot, 'skills', 'test-skill', 'SKILL.md'); + const skillFile = path.join(projectRoot, '.copilot', 'skills', 'test-skill', 'SKILL.md'); const content = fs.readFileSync(skillFile, 'utf-8'); expect(content).toContain('**Confidence:** medium'); });