<!-- source: openspec/changes/add-specialist-tool-composition/tasks.md -->

# Tasks — add-specialist-tool-composition

> Companion to proposal.md + design.md.

> **Schema-rename overlay — RAP-1170 (Wave 1, 2026-05-22).** Read
> `canvas_sessions` as `ideas`, `canvas_messages` as `messages`,
> `canvas_files` as `files`, `canvas_invitees` as `spec_invitees`
> throughout. Wave 5 below targets the post-rename schema even though
> the inline references read the original drafting names.

## Wave 1 — Scaffold

- [x] Author `proposal.md`.
- [x] Author `design.md` with D-STC-1..6.
- [x] Author `tasks.md` (this file).
- [x] Author `verification.md`.

## Wave 2 — Schema + loader (Phase 1, this PR)

- [x] `packages/muse/src/runtime/palette-schema.ts` — Zod schema per D-STC-1.
- [x] `packages/muse/src/runtime/specialist-tools.ts` — `loadPalette({ archetype, brand, root? })` per D-STC-2 with brand→platform fallback.
- [x] LRU memoisation per D-STC-4; dev-mode bypass via `NODE_ENV !== 'production'`.
- [x] Pure gate helper `filterToolsForActiveSpecialist(tools, palette)` per D-STC-3 (D-STC-7: turn-route consumption deferred to follow-up PR).
- [x] Typed `PaletteInvalidError` per D-STC-6 (carries archetype/brand/path/cause).
- [x] Slug validation rejects path traversal per D-STC-9.
- [x] Workers runtime resolves to `missing` rather than throw per D-STC-10.
- [x] Unit tests: brand override wins; platform fallback applies; Zod validation errors surface as `PaletteInvalidError`; cache hit/miss; dev-mode bypass; gate is pure + matches by id/name.

## Wave 3 — Platform default palettes

- [x] `openspec/_methodology/specialist-tool-palettes/architect/tools.yaml` — seeded with the foundation + emit tools (Phase 1).
- [x] `openspec/_methodology/specialist-tool-palettes/designer/tools.yaml` — research + contribute (`update_design`) palette; registry-only tool ids (Phase 2, RAP-1201).
- [x] `openspec/_methodology/specialist-tool-palettes/legal-counsel/tools.yaml` — narrow read-only / advisory palette (no write/execute scope) demonstrating per-archetype scoping (Phase 2, RAP-1201).
- [ ] (Other archetypes as they come online — empty file is acceptable for new archetypes pending curation.)

## Wave 4 — Brand override examples

- [x] `openspec/brands/studio/archetypes/architect/tools.yaml` — studio brand's architect override demonstrating brand-specific tools (Phase 1).
- [x] At least one brand-specific tool entry (`github_open_pr`, `github_comment`, etc.) validates the resolver picks the override file.

## Wave 5 — Turn-dispatch gate (BLOCKED on missing substrate — see note)

> **⛔ Blocked 2026-06-02 (RAP-1201 Phase 2).** Wave 5 as drafted cannot
> land against the current architecture; surfaced rather than improvised
> per CLAUDE.md Hard Rule #1/#8. Three gaps, each its own substrate work:
>
> 1. **No active-specialist substrate.** The post-RAP-1170 `ideas` table
>    has no `active_specialist_id`, no `brand_slug`, no `archetype`
>    column. The `specialists` table is the marketplace-application
>    entity (NDA/certification), not a chat-runtime activation. No brand
>    entity exists in the DB at all. There is nothing to resolve an
>    archetype + brand from.
> 2. **Tools are not composed in `turn/route.ts`.** The architect chat
>    composes `COMPOSED_TOOLS` in `packages/muse/src/modes/architect/runner.ts`
>    and passes them to `messages.create` there — not in the route.
> 3. **Two unreconciled tool systems.** The palettes target the Muse
>    foundation registry (`packages/muse/src/tools/`: `read_openspec`,
>    `read_file`, …), but the live architect chat uses
>    `modes/architect/{tools,repo-tools}.ts` (`read_openspec_index`,
>    `list_repo_tree`, `read_repo_file`, `update_*`). They overlap only
>    on `read_openspec` + `update_*`, so filtering the chat tool list by
>    the architect palette today would *drop* working repo tools.
> 4. **Workers has no `node:fs`.** Per D-STC-10 `loadPalette` returns
>    `missing` in production → the gate is an additive no-op until a
>    build-time palette bundle exists (mirroring `MethodologyBundles`).
>
> Unblocking is a separate, dispatchable change (active-specialist +
> brand entity → migration; tool-registry reconciliation; Workers palette
> bundle; then the additive gate in the runner). Tracked under RAP-1201
> for re-scope. The Phase-1 loader + gate (`filterToolsForActiveSpecialist`,
> null-safe additive) are ready to consume once the substrate lands.

- [ ] `apps/studio/src/app/(auth-walled)/(canvas)/spec/[id]/turn/route.ts` — load palette, filter `ALL_TOOLS` via `filterToolsForActiveSpecialist` per D-STC-3 + D-STC-8. **Deferred:** sibling change `add-muse-translation-mode` (RAP-1131) also edits this file; serialize on the turn-route.
- [ ] Active specialist source: `ideas.active_specialist_id` (post-RAP-1170 rename of `canvas_sessions.active_specialist_id`); null → default architect mode resolves against the idea's brand.
- [ ] Unit test: turn handler with specialist A sees only palette-A tools; switching to specialist B reloads palette + filter.

## Wave 6 — Error wiring (BLOCKED behind Wave 5)

> **⛔ Deferred 2026-06-02.** RAP-1094's `ErrorKind` taxonomy *has*
> landed (`apps/studio/.../turn/classify-error.ts`), so wiring is
> feasible — but the `palette-invalid` error can only be thrown on the
> request path once Wave 5 actually invokes `loadPalette` during turn
> dispatch. `PaletteInvalidError` (carrying `kind: 'palette-invalid'`,
> archetype/brand/path/cause) already exists in `specialist-tools.ts`;
> mapping it into `classify-error.ts` is a ~5-line change once Wave 5
> lands. Premature to wire now.

- [ ] `palette-invalid` kind in the chat error taxonomy per D-STC-6 (depends on RAP-1094 landing first; if not yet landed, surface via `unknown` kind with structured message).
- [ ] User-visible banner copy explains "Palette for archetype X / brand Y failed validation" with file path.

## Wave 7 — Lint gates

- [x] `tools/check-palette-drift.mjs` — drift gate per D-STC-5; informational <5 missing, fails ≥5 missing. Default threshold 5, override via `--threshold=N`; also flags brand overrides whose mtime is >90d older than the platform default (informational).
- [x] `tools/check-palette-tool-ids.mjs` — asserts every palette `tools[].id` exists in the Muse tool registry (`packages/muse/src/tools/` + architect emit tools in `modes/architect/tools.ts`); fails CI on unknown ids. `skills:`/`research_agents:` ids intentionally out of scope.
- [x] Wired into CI as two steps in `.github/workflows/ci.yml` (after the OpenSpec refs guard) + a `palettes:check` npm script. (`pnpm lint:openspec` does not exist — the actual house pattern is per-guard CI steps + `tools/check-*.mjs`; tasks.md drift.)

## Wave 8 — Surface affordances (BLOCKED behind Wave 5)

> **⛔ Deferred 2026-06-02.** Both items read from "the active
> specialist's palette" — they depend on the same active-specialist
> substrate that blocks Wave 5 (no `active_specialist_id`/brand on
> `ideas`). No surface can resolve a palette to render chips/previews
> until activation exists. Tracked under RAP-1201.

- [ ] Chat empty-state suggestion chips read from the active specialist's palette (palette → chips map in `chat-suggestions.ts` from RAP-1099 P8).
- [ ] Tool-call previews in the message stream show the palette entry's `description` rather than the raw tool id.

## Wave 9 — Gates

- [ ] `pnpm --filter @rapoport-studio/muse lint` exits 0.
- [ ] `pnpm --filter @rapoport-studio/muse test` exits 0.
- [ ] `pnpm --filter studio test` exits 0.
- [ ] `pnpm typecheck` exits 0.
- [ ] `pnpm lint:openspec` exits 0 (palette drift + tool-id gates green).
- [ ] Local smoke: switch active specialist on a test canvas; observe filtered tool list in the network panel; observe per-palette suggestion chips on empty state.

## Wave 10 — PR + ratification

- [ ] Open PR titled `RAP-1132: add specialist tool composition (per-archetype palettes)`.

## Wave 11 — Archive amendments

- [ ] Amend `openspec/current/canvas.md`: add § "Specialist runtime contract" referencing the per-archetype palette + active-specialist gate.
- [ ] Amend `openspec/current/muse.md`: add § "Architect tool exposure" referencing the loader + gate.
- [ ] Amend `openspec/_root/glossary.md § Curation § Cross-reference`: resolve the cross-reference to the archived change folder.
- [ ] Move `openspec/changes/add-specialist-tool-composition/` to `openspec/archive/<YYYY-MM-DD>-add-specialist-tool-composition/`.
