( skillName: string, updates: SkillUpdate[], )
| 186 | * Fire-and-forget — does not block the main conversation. |
| 187 | */ |
| 188 | export async function applySkillImprovement( |
| 189 | skillName: string, |
| 190 | updates: SkillUpdate[], |
| 191 | ): Promise<void> { |
| 192 | if (!skillName) return |
| 193 | |
| 194 | const { join } = await import('path') |
| 195 | const fs = await import('fs/promises') |
| 196 | |
| 197 | // Skills live at .claude/skills/<name>/SKILL.md relative to CWD |
| 198 | const filePath = join(getCwd(), '.claude', 'skills', skillName, 'SKILL.md') |
| 199 | |
| 200 | let currentContent: string |
| 201 | try { |
| 202 | currentContent = await fs.readFile(filePath, 'utf-8') |
| 203 | } catch { |
| 204 | logError( |
| 205 | new Error(`Failed to read skill file for improvement: ${filePath}`), |
| 206 | ) |
| 207 | return |
| 208 | } |
| 209 | |
| 210 | const updateList = updates.map(u => `- ${u.section}: ${u.change}`).join('\n') |
| 211 | |
| 212 | const response = await queryModelWithoutStreaming({ |
| 213 | messages: [ |
| 214 | createUserMessage({ |
| 215 | content: `You are editing a skill definition file. Apply the following improvements to the skill. |
| 216 | |
| 217 | <current_skill_file> |
| 218 | ${currentContent} |
| 219 | </current_skill_file> |
| 220 | |
| 221 | <improvements> |
| 222 | ${updateList} |
| 223 | </improvements> |
| 224 | |
| 225 | Rules: |
| 226 | - Integrate the improvements naturally into the existing structure |
| 227 | - Preserve frontmatter (--- block) exactly as-is |
| 228 | - Preserve the overall format and style |
| 229 | - Do not remove existing content unless an improvement explicitly replaces it |
| 230 | - Output the complete updated file inside <updated_file> tags`, |
| 231 | }), |
| 232 | ], |
| 233 | systemPrompt: asSystemPrompt([ |
| 234 | 'You edit skill definition files to incorporate user preferences. Output only the updated file content.', |
| 235 | ]), |
| 236 | thinkingConfig: { type: 'disabled' as const }, |
| 237 | tools: [], |
| 238 | signal: createAbortController().signal, |
| 239 | options: { |
| 240 | getToolPermissionContext: async () => getEmptyToolPermissionContext(), |
| 241 | model: getSmallFastModel(), |
| 242 | toolChoice: undefined, |
| 243 | isNonInteractiveSession: false, |
| 244 | hasAppendSystemPrompt: false, |
| 245 | temperatureOverride: 0, |
no test coverage detected