feat(sdk): runtime-extensible role catalog for marketplace plugins (#975)#976
Open
goldsziggy wants to merge 2 commits intobradygaster:devfrom
Open
feat(sdk): runtime-extensible role catalog for marketplace plugins (#975)#976goldsziggy wants to merge 2 commits intobradygaster:devfrom
goldsziggy wants to merge 2 commits intobradygaster:devfrom
Conversation
…radygaster#975) Introduces a plugin role registry that `useRole()`, `listRoles()`, `searchRoles()`, and `getCategories()` consult after the built-in `BASE_ROLES`. Marketplace plugins can ship a `roles/` directory of role definitions that `loadPluginRolesFromDir()` discovers from `.squad/plugins/<name>/roles/*.json` and registers with the SDK. Plugin roles are additive only — the registry throws on id collisions with built-in roles, so a plugin cannot silently shadow a built-in. Second-plugin duplicates are skipped (not fatal) and reported on the result. The `squad`, `squad roles`, and `squad init` entry points now load `.squad/plugins/*/roles/*.json` before dispatching, so plugin roles show up alongside built-ins in the roles listing, the hire wizard, and the SDK scaffolding path (`squad init --sdk --roles`). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- features/built-in-roles.md — adds "Plugin-contributed roles" section with file format, useRole() resolution, and collision rules - features/plugins.md — marketplace repo structure now shows roles/ directory and links to the built-in-roles doc - guide/building-extensions.md — extension structure shows optional roles/ directory Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #975. Lets marketplace plugins ship a
roles/directory whose definitions resolve throughuseRole()/listRoles()/searchRoles()/getCategories()identically to built-in roles.packages/squad-sdk/src/roles/registry.ts— runtime role registry.registerPluginRoles(plugin, roles)throws on collision with a built-in (additive-only guarantee) and skips duplicate-plugin ids with a structuredskippedresult.packages/squad-sdk/src/roles/loader.ts—loadPluginRolesFromDir(<squadDir>/plugins)scans<plugin>/roles/*.json(single role or array) and registers each.registerPluginRoles,unregisterPluginRole,clearPluginRoles,getPluginRoles,getPluginRoleRegistrations,getAllRoles,loadPluginRolesFromDir, plus types.loadPluginRolesForDest()helper is invoked fromsquad(interactive),squad roles, andsquad init, so plugin roles appear in the catalog, hire wizard, and--sdk --rolesscaffolding.squad rolesgains a "🔌 Plugin Roles" section grouped by plugin name.test/plugin-roles.test.ts(15 new cases) covers collision, duplicate-skip, category contribution,useRole()resolution, loader edge cases (missing dir, invalid JSON, non-JSON entries, built-in shadow attempt). Existing role tests unchanged and passing.Design notes
BASE_ROLESingetAllRoles()so iteration order stays deterministic. Collision with a built-in id is a hard error — an organization that wants a tweakedbackendmust use a namespaced id like@acme/backend.loadPluginRolesForDest()is idempotent per-directory for a single process (pass{ force: true }to reload aftersquad plugin marketplace add …).Risks / call-outs
clearPluginRoles()inbeforeEach/afterEach, but future contributors need the same discipline.JSON.parses whatever is on disk and hands it toregisterPluginRoles(). Missing fields will blow up later inuseRole()(TypeScript types aren't enforced at runtime). A follow-up could add a Zod-style schema check; issue scope didn't require it..squad/plugins/can register any non-built-in role id. We cannot shadow a built-in (guard), but a plugin could register@malicious/leadand socially engineer users into casting it. Same trust model as the existing plugin marketplace; worth naming.SDK_ROLES_STARTER_TEAMinconfig/init.tsstill points at built-in ids only.squad init --sdk --roleswon't auto-select a plugin role; users opt in explicitly by passing the plugin role id (e.g. via the hire wizard oruseRole('@acme/x', …)insquad.config.ts). Intentional — we don't want init to pick up arbitrary plugin-provided roles as the default team composition.squad/squad roles/squad initinvocation. If.squad/pluginsis missing the cost is a singlestatSync. If populated, it reads everyroles/*.json. Negligible for reasonable plugin counts but worth watching if a plugin ships hundreds of roles.npm run lint/npm run buildfails ondevtoday withTOKEN_PATHinplatform/comms-teams.ts— unrelated to this PR but flagging so reviewers don't attribute it.Test plan
npx vitest run test/roles.test.ts test/plugin-roles.test.ts test/init-base-roles.test.ts test/fact-checker-role.test.ts test/casting.test.ts test/casting-engine-integration.test.ts test/cast-parser.test.ts test/init-sdk.test.ts test/init-scaffolding.test.ts test/builders.test.ts test/consumer-imports.test.ts test/package-exports.test.ts test/shell.test.ts— 375/375 pass locallytsc --noEmitclean on bothpackages/squad-sdkandpackages/squad-clifor the changed files.squad/plugins/demo/roles/x.json, runsquad roles— plugin section appearsuseRole('@demo/x', { name: 'foo' })insquad.config.tsresolves without error🤖 Generated with Claude Code