Skip to content

fix(sdk): merge skills from both .copilot/skills/ and .squad/skills/ (#77)#669

Closed
diberry wants to merge 1 commit intobradygaster:devfrom
diberry:squad/77-fix-skills-discovery
Closed

fix(sdk): merge skills from both .copilot/skills/ and .squad/skills/ (#77)#669
diberry wants to merge 1 commit intobradygaster:devfrom
diberry:squad/77-fix-skills-discovery

Conversation

@diberry
Copy link
Copy Markdown
Collaborator

@diberry diberry commented Mar 28, 2026

Fix: Skills Discovery Bug (Customer-Reported)

Origin: Customer-reported bug, fully investigated with 11-scenario error matrix and PRD.

Problem

Skills in .squad/skills/ are invisible when .copilot/skills/ exists (even if empty). This affects every repo where squad init runs, because init always creates .copilot/skills/.

A customer reported that only .copilot/skills/ was being read and .squad/skills/ was completely ignored. After investigation, we confirmed this is a code-level bug in the SDK, not a configuration issue.

Root Cause

Two files use an either/or pattern instead of merging both directories:

  • skill-source.ts ~line 41 -- skillsDir getter returned .copilot/ OR .squad/, never both
  • resolver.ts ~line 62 -- .find() returned first existing dir only

Investigation

Full PRD with 11-scenario error matrix at diberry/squad#77. Scenarios include:

  • Skills in .squad/ only (works pre-init, breaks after)
  • Skills in both dirs (.squad/ silently lost)
  • Empty .copilot/skills/ shadows populated .squad/skills/
  • Fresh squad init immediately breaks existing .squad/skills/
  • Upgrade from older Squad versions loses skills

Fix

Both files now merge both directories at read-time. .copilot/skills/ wins on name conflicts:

skill-source.ts -- skillsDir (single string) changed to skillsDirs (array). Returns both .copilot/skills/ and .squad/skills/ if they exist. listSkills(), getSkill(), and getContent() loop all dirs with a seen Map for dedup.

resolver.ts -- .find() changed to for...of loop across all candidate dirs (.copilot/, .squad/, .ai-team/) with Set<string> dedup.

squad.agent.md -- 4 path references updated from .squad/skills/ to .copilot/skills/ (canonical write target going forward).

Files Changed (5 total)

File Change
packages/squad-sdk/src/skills/skill-source.ts Merge logic (110 lines)
packages/squad-sdk/src/upstream/resolver.ts Dedup loop (27 lines)
.github/agents/squad.agent.md 4 skill path refs
test/skill-source.test.ts 8 new tests (120 lines)
test/upstream.test.ts 3 new tests (99 lines)

Test Coverage

  • 11 new tests: merge, dedup, conflict priority, missing dirs, fallback, malformed files
  • All 47 existing + new tests pass
  • Build clean

Related

  • Architecture follow-up: diberry/squad#79 -- file layout manifest to prevent this class of bug systematically

Fixes #77 — skill discovery only read one directory, ignoring the other.

Changes:
- skill-source.ts: Replace skillsDir (either/or) with skillsDirs (array of
  both dirs). listSkills/getSkill/getContent now scan all dirs with
  .copilot/skills/ winning on name conflicts.
- resolver.ts: Replace .find() in readSkills() with a loop that collects
  from all candidate dirs, dedup by name (first wins).
- squad.agent.md: Fix SKILL EXTRACTION, plugin install, and MCP skill
  reference paths from .squad/skills/ to .copilot/skills/.
- Tests: 8 new skill-source tests + 3 new upstream tests covering merge,
  dedup, priority, empty dir, backward/forward compat.

Closes #77

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@diberry
Copy link
Copy Markdown
Collaborator Author

diberry commented Mar 28, 2026

Closing -- will re-open via fork-first pipeline when fully polished.

@diberry diberry closed this Mar 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants