Skip to content

feat(sdk): runtime-extensible role catalog for marketplace plugins (#975)#976

Open
goldsziggy wants to merge 2 commits intobradygaster:devfrom
goldsziggy:feat/975-marketplace-plugin-roles
Open

feat(sdk): runtime-extensible role catalog for marketplace plugins (#975)#976
goldsziggy wants to merge 2 commits intobradygaster:devfrom
goldsziggy:feat/975-marketplace-plugin-roles

Conversation

@goldsziggy
Copy link
Copy Markdown

Summary

Closes #975. Lets marketplace plugins ship a roles/ directory whose definitions resolve through useRole() / listRoles() / searchRoles() / getCategories() identically to built-in roles.

  • New module: 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 structured skipped result.
  • New module: packages/squad-sdk/src/roles/loader.tsloadPluginRolesFromDir(<squadDir>/plugins) scans <plugin>/roles/*.json (single role or array) and registers each.
  • SDK re-exports: registerPluginRoles, unregisterPluginRole, clearPluginRoles, getPluginRoles, getPluginRoleRegistrations, getAllRoles, loadPluginRolesFromDir, plus types.
  • CLI: new loadPluginRolesForDest() helper is invoked from squad (interactive), squad roles, and squad init, so plugin roles appear in the catalog, hire wizard, and --sdk --roles scaffolding.
  • CLI UX: squad roles gains a "🔌 Plugin Roles" section grouped by plugin name.
  • Tests: 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

  • Plugin roles are appended after BASE_ROLES in getAllRoles() so iteration order stays deterministic. Collision with a built-in id is a hard error — an organization that wants a tweaked backend must use a namespaced id like @acme/backend.
  • The registry is process-global module state. loadPluginRolesForDest() is idempotent per-directory for a single process (pass { force: true } to reload after squad plugin marketplace add …).

Risks / call-outs

  1. Process-global registry state. Tests that register plugin roles and don't clean up can leak into later files. New tests use clearPluginRoles() in beforeEach/afterEach, but future contributors need the same discipline.
  2. Unvalidated plugin role shape. The loader JSON.parses whatever is on disk and hands it to registerPluginRoles(). Missing fields will blow up later in useRole() (TypeScript types aren't enforced at runtime). A follow-up could add a Zod-style schema check; issue scope didn't require it.
  3. Supply-chain trust boundary. A plugin with write access to .squad/plugins/ can register any non-built-in role id. We cannot shadow a built-in (guard), but a plugin could register @malicious/lead and socially engineer users into casting it. Same trust model as the existing plugin marketplace; worth naming.
  4. Init defaults are unchanged. SDK_ROLES_STARTER_TEAM in config/init.ts still points at built-in ids only. squad init --sdk --roles won't auto-select a plugin role; users opt in explicitly by passing the plugin role id (e.g. via the hire wizard or useRole('@acme/x', …) in squad.config.ts). Intentional — we don't want init to pick up arbitrary plugin-provided roles as the default team composition.
  5. CLI startup cost. Loader runs on every squad / squad roles / squad init invocation. If .squad/plugins is missing the cost is a single statSync. If populated, it reads every roles/*.json. Negligible for reasonable plugin counts but worth watching if a plugin ships hundreds of roles.
  6. Pre-existing build-state oddity. npm run lint / npm run build fails on dev today with TOKEN_PATH in platform/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 locally
  • tsc --noEmit clean on both packages/squad-sdk and packages/squad-cli for the changed files
  • Manual: drop a sample role JSON in .squad/plugins/demo/roles/x.json, run squad roles — plugin section appears
  • Manual: useRole('@demo/x', { name: 'foo' }) in squad.config.ts resolves without error

🤖 Generated with Claude Code

goldsziggy and others added 2 commits April 14, 2026 15:46
…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>
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.

Feature Request: Allow marketplace plugins to extend default agent catalog

1 participant