SR-2 resolveSkillBody + renderPrompt + abortDispatchMissingSkill #870

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

User story

As a platform engineer, I want a single resolver that picks the right skill body for a (agent_type, instance_id, name) triple, a single render pipeline that interpolates vars and appends the recommended style guides, and a missing-row policy that fails loud-but-graceful, so that every dispatch path uses the same code with no fallback magic.

Acceptance criteria

Resolver

  • New resolveSkillBody(agentType, instanceId | null, name): Promise<string | null> in apps/server/src/domain/analytics/skill-loader.ts.
  • Implemented as a single SELECT against agent_skill with ORDER BY scope DESC LIMIT 1 (instance row wins because 'instance' > 'type' lexicographically).
  • Returns null when neither layer has a row. No filesystem fallback. No <base>-delta lookup.

Render pipeline

  • renderPrompt(opts): Promise<string> where opts carries { agentType, instanceId, skillName, vars, instance, globalCavemanMode }.
  • Pipeline order: resolveSkillBodyinterpolate(template, vars)applyAppendix(prompt, instance.prompt_appendix) → caveman (if globalCavemanMode || agent_type.apply_caveman) → artifact-style (if agent_type.apply_artifact_style).
  • Caveman / artifact-style appendix bodies are loaded via resolveSkillBody(..., 'caveman') / resolveSkillBody(..., 'artifact-style'). If a recommended appendix body is missing, log [dispatch] appendix_missing and continue without it (do NOT abort).
  • Returns the final string ready to dispatch.

Missing-row policy

  • New abortDispatchMissingSkill(forge, repo, issueOrPr, agentType, skillName) helper:
    • Logs [dispatch] skill_missing with {forge, repo, issue, type, name, instance_id}.
    • Posts a forge comment: 🚫 Dispatch aborted: skill '<name>' missing for agent type '<type>'. Seed it at /agents/<type>/skills.
    • Strips the dispatch label that fired the route (delegates to existing label-removal helpers; safe-no-op if no label was present).
    • Inserts a task_history row with outcome = 'aborted_no_skill'.
  • Helper is the only place that handles the abort path. Call sites just check for null and call abortDispatchMissingSkill(...) then return.

Tests

  • resolveSkillBody.test.ts — instance > type > null. Single-query behaviour proven via fixture.
  • renderPrompt.test.ts — appendix order, both bools off, both bools on, globalCavemanMode overrides apply_caveman = 0, missing appendix logs and continues.
  • abort-missing-skill.test.ts — happy path: comment posted, label stripped, task_history row written, outcome = 'aborted_no_skill'.

Out of scope

  • Wiring this into the actual dispatch call sites (covered in SR-4).
  • Removing the legacy skillForEvent / STATELESS_SKILLS / maybeApplyCavemanAppendix (covered in SR-4).
  • The forge-comment helper itself — reuse the existing one. Same for label removal.

References

  • Spec: specs/skills-rework.md §Resolution rules, §Dispatch path, §Missing-row policy.
  • Existing render code: apps/server/src/domain/analytics/skill-loader.ts.
  • Existing label-strip helper: apps/server/src/domain/workflow/event-handlers.ts.
  • Depends on SR-1.
## User story As a platform engineer, I want a single resolver that picks the right skill body for a `(agent_type, instance_id, name)` triple, a single render pipeline that interpolates vars and appends the recommended style guides, and a missing-row policy that fails loud-but-graceful, so that every dispatch path uses the same code with no fallback magic. ## Acceptance criteria ### Resolver - [ ] New `resolveSkillBody(agentType, instanceId | null, name): Promise<string | null>` in `apps/server/src/domain/analytics/skill-loader.ts`. - [ ] Implemented as a single SELECT against `agent_skill` with `ORDER BY scope DESC LIMIT 1` (instance row wins because `'instance' > 'type'` lexicographically). - [ ] Returns `null` when neither layer has a row. No filesystem fallback. No `<base>-delta` lookup. ### Render pipeline - [ ] `renderPrompt(opts): Promise<string>` where `opts` carries `{ agentType, instanceId, skillName, vars, instance, globalCavemanMode }`. - [ ] Pipeline order: `resolveSkillBody` → `interpolate(template, vars)` → `applyAppendix(prompt, instance.prompt_appendix)` → caveman (if `globalCavemanMode || agent_type.apply_caveman`) → artifact-style (if `agent_type.apply_artifact_style`). - [ ] Caveman / artifact-style appendix bodies are loaded via `resolveSkillBody(..., 'caveman')` / `resolveSkillBody(..., 'artifact-style')`. If a recommended appendix body is missing, log `[dispatch] appendix_missing` and continue without it (do NOT abort). - [ ] Returns the final string ready to dispatch. ### Missing-row policy - [ ] New `abortDispatchMissingSkill(forge, repo, issueOrPr, agentType, skillName)` helper: - Logs `[dispatch] skill_missing` with `{forge, repo, issue, type, name, instance_id}`. - Posts a forge comment: `🚫 Dispatch aborted: skill '<name>' missing for agent type '<type>'. Seed it at /agents/<type>/skills.` - Strips the dispatch label that fired the route (delegates to existing label-removal helpers; safe-no-op if no label was present). - Inserts a `task_history` row with `outcome = 'aborted_no_skill'`. - [ ] Helper is the only place that handles the abort path. Call sites just check for `null` and call `abortDispatchMissingSkill(...)` then `return`. ### Tests - [ ] `resolveSkillBody.test.ts` — instance > type > null. Single-query behaviour proven via fixture. - [ ] `renderPrompt.test.ts` — appendix order, both bools off, both bools on, `globalCavemanMode` overrides `apply_caveman = 0`, missing appendix logs and continues. - [ ] `abort-missing-skill.test.ts` — happy path: comment posted, label stripped, task_history row written, `outcome = 'aborted_no_skill'`. ## Out of scope - Wiring this into the actual dispatch call sites (covered in SR-4). - Removing the legacy `skillForEvent` / `STATELESS_SKILLS` / `maybeApplyCavemanAppendix` (covered in SR-4). - The forge-comment helper itself — reuse the existing one. Same for label removal. ## References - Spec: `specs/skills-rework.md` §Resolution rules, §Dispatch path, §Missing-row policy. - Existing render code: `apps/server/src/domain/analytics/skill-loader.ts`. - Existing label-strip helper: `apps/server/src/domain/workflow/event-handlers.ts`. - Depends on **SR-1**.
Collaborator

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

🦵 @charles kicked the queue — re-running implement on @dev.
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#870
No description provided.