SR-3 M1 migration — copy legacy skill rows + skill_overrides_json into agent_skill #871

Closed
opened 2026-05-05 10:28:54 +00:00 by claude-desktop · 1 comment
Collaborator

User story

As an operator, I want my existing scope-ladder skills (global / agent_type / instance) and any skill_overrides_json rewrites to be copied into the new agent_skill table at first boot after the cutover, so that I keep every customisation I made without re-doing it through the wizard.

Acceptance criteria

Migration script

  • Drizzle data-migration runs once at boot (idempotent — guarded by a sentinel row in a migrations-state table).
  • For every row in legacy skill:
    • scope='global' → INSERT one (scope='type', agent_type, name, body) row per agent_type listed in agent_type.
    • scope='agent_type' → INSERT one (scope='type', agent_type, name, body) row directly.
    • scope='instance' → INSERT (scope='instance', agent_type, instance_id, name, body) with agent_type looked up via JOIN agents ON agents.id = instance_id. Trigger does not fire because the join supplies the right value.
    • scope='builtin' → ignored (already dead per DOB-1).
  • For every agent_type.skill_overrides_json non-null entry:
    • For each (base, override) pair, look up the override body from whichever source currently fires (DB ladder, falling back to FS skills/<override>.md), then INSERT (scope='type', agent_type, name=base, body) into agent_skill. Base name wins; renamed body content goes in.
    • Example: designer { "implement": "design-implement" } → row (scope='type', agent_type='designer', name='implement', body=<body of design-implement.md>).
  • For every agent_type row, set apply_artifact_style = 1 (current behaviour is unconditional-on).
  • For every agent_type row's legacy caveman.enabled setting (resolver-derived from agent_type_config):
    • enabled = true → set apply_caveman = 1.
    • caveman.labels non-empty → emit a one-shot warning to a migration_log table (or stderr if no such table) noting per-label gating dropped.

Drift detection

  • Migration emits a warning per (name, type) where the legacy row body differs from the matching skills/<name>.md body on disk. Operator's hint: commit the live edits back to the file before adding new types.

Tests

  • Three golden-fixture migration tests using snapshot databases:
    • F1: only scope='global' rows + 3 agent_types → expect 3 type rows per legacy global row.
    • F2: legacy skill_overrides_json (designer remap) → expect renamed-base rows with the override body.
    • F3: instance-scope rows + agent type → expect instance rows with denormalised agent_type set correctly.

Out of scope

  • Dropping the legacy skill table or skill_overrides_json column (covered in SR-11).
  • Removing legacy reader code paths (covered in SR-4 / SR-5).
  • Onboarding wizard or new types added post-migration (covered in SR-6).

References

  • Spec: specs/skills-rework.md §Migration, §M1.
  • Legacy resolver: apps/server/src/infrastructure/database/agent-type-config.ts::resolveSkill.
  • Legacy routing-rewrite source: apps/server/src/http/webhook-routing.ts::skillForAgent.
  • Depends on SR-1.
## User story As an operator, I want my existing scope-ladder skills (global / agent_type / instance) and any `skill_overrides_json` rewrites to be copied into the new `agent_skill` table at first boot after the cutover, so that I keep every customisation I made without re-doing it through the wizard. ## Acceptance criteria ### Migration script - [ ] Drizzle data-migration runs once at boot (idempotent — guarded by a sentinel row in a migrations-state table). - [ ] For every row in legacy `skill`: - `scope='global'` → INSERT one `(scope='type', agent_type, name, body)` row per `agent_type` listed in `agent_type`. - `scope='agent_type'` → INSERT one `(scope='type', agent_type, name, body)` row directly. - `scope='instance'` → INSERT `(scope='instance', agent_type, instance_id, name, body)` with `agent_type` looked up via `JOIN agents ON agents.id = instance_id`. Trigger does not fire because the join supplies the right value. - `scope='builtin'` → ignored (already dead per DOB-1). - [ ] For every `agent_type.skill_overrides_json` non-null entry: - For each `(base, override)` pair, look up the override body from whichever source currently fires (DB ladder, falling back to FS `skills/<override>.md`), then INSERT `(scope='type', agent_type, name=base, body)` into `agent_skill`. Base name wins; renamed body content goes in. - Example: `designer { "implement": "design-implement" }` → row `(scope='type', agent_type='designer', name='implement', body=<body of design-implement.md>)`. - [ ] For every `agent_type` row, set `apply_artifact_style = 1` (current behaviour is unconditional-on). - [ ] For every `agent_type` row's legacy `caveman.enabled` setting (resolver-derived from `agent_type_config`): - `enabled = true` → set `apply_caveman = 1`. - `caveman.labels` non-empty → emit a one-shot warning to a `migration_log` table (or stderr if no such table) noting per-label gating dropped. ### Drift detection - [ ] Migration emits a warning per `(name, type)` where the legacy row body differs from the matching `skills/<name>.md` body on disk. Operator's hint: commit the live edits back to the file before adding new types. ### Tests - [ ] Three golden-fixture migration tests using snapshot databases: - F1: only `scope='global'` rows + 3 agent_types → expect 3 type rows per legacy global row. - F2: legacy `skill_overrides_json` (designer remap) → expect renamed-base rows with the override body. - F3: instance-scope rows + agent type → expect instance rows with denormalised `agent_type` set correctly. ## Out of scope - Dropping the legacy `skill` table or `skill_overrides_json` column (covered in SR-11). - Removing legacy reader code paths (covered in SR-4 / SR-5). - Onboarding wizard or new types added post-migration (covered in SR-6). ## References - Spec: `specs/skills-rework.md` §Migration, §M1. - Legacy resolver: `apps/server/src/infrastructure/database/agent-type-config.ts::resolveSkill`. - Legacy routing-rewrite source: `apps/server/src/http/webhook-routing.ts::skillForAgent`. - Depends on **SR-1**.
Collaborator

🦵 @charles kicked the queue — re-running implement on @code-lead.

🦵 @charles kicked the queue — re-running implement on @code-lead.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/claude-hooks#871
No description provided.