@tanstack/ai-code-mode-skills 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +199 -0
  2. package/dist/esm/code-mode-with-skills.d.ts +58 -0
  3. package/dist/esm/code-mode-with-skills.js +124 -0
  4. package/dist/esm/code-mode-with-skills.js.map +1 -0
  5. package/dist/esm/create-skill-management-tools.d.ts +40 -0
  6. package/dist/esm/create-skill-management-tools.js +198 -0
  7. package/dist/esm/create-skill-management-tools.js.map +1 -0
  8. package/dist/esm/create-skills-system-prompt.d.ts +22 -0
  9. package/dist/esm/create-skills-system-prompt.js +236 -0
  10. package/dist/esm/create-skills-system-prompt.js.map +1 -0
  11. package/dist/esm/generate-skill-types.d.ts +7 -0
  12. package/dist/esm/generate-skill-types.js +87 -0
  13. package/dist/esm/generate-skill-types.js.map +1 -0
  14. package/dist/esm/index.d.ts +13 -0
  15. package/dist/esm/index.js +29 -0
  16. package/dist/esm/index.js.map +1 -0
  17. package/dist/esm/select-relevant-skills.d.ts +29 -0
  18. package/dist/esm/select-relevant-skills.js +79 -0
  19. package/dist/esm/select-relevant-skills.js.map +1 -0
  20. package/dist/esm/skills-to-bindings.d.ts +34 -0
  21. package/dist/esm/skills-to-bindings.js +77 -0
  22. package/dist/esm/skills-to-bindings.js.map +1 -0
  23. package/dist/esm/skills-to-tools.d.ts +74 -0
  24. package/dist/esm/skills-to-tools.js +189 -0
  25. package/dist/esm/skills-to-tools.js.map +1 -0
  26. package/dist/esm/storage/file-storage.d.ts +27 -0
  27. package/dist/esm/storage/file-storage.js +149 -0
  28. package/dist/esm/storage/file-storage.js.map +1 -0
  29. package/dist/esm/storage/index.d.ts +3 -0
  30. package/dist/esm/storage/index.js +7 -0
  31. package/dist/esm/storage/index.js.map +1 -0
  32. package/dist/esm/storage/memory-storage.d.ts +17 -0
  33. package/dist/esm/storage/memory-storage.js +99 -0
  34. package/dist/esm/storage/memory-storage.js.map +1 -0
  35. package/dist/esm/trust-strategies.d.ts +50 -0
  36. package/dist/esm/trust-strategies.js +63 -0
  37. package/dist/esm/trust-strategies.js.map +1 -0
  38. package/dist/esm/types.d.ts +216 -0
  39. package/package.json +82 -0
  40. package/src/code-mode-with-skills.ts +204 -0
  41. package/src/create-skill-management-tools.ts +296 -0
  42. package/src/create-skills-system-prompt.ts +289 -0
  43. package/src/generate-skill-types.ts +162 -0
  44. package/src/index.ts +51 -0
  45. package/src/select-relevant-skills.ts +136 -0
  46. package/src/skills-to-bindings.ts +134 -0
  47. package/src/skills-to-tools.ts +319 -0
  48. package/src/storage/file-storage.ts +243 -0
  49. package/src/storage/index.ts +6 -0
  50. package/src/storage/memory-storage.ts +163 -0
  51. package/src/trust-strategies.ts +142 -0
  52. package/src/types.ts +289 -0
@@ -0,0 +1,134 @@
1
+ import type { ToolExecutionContext } from '@tanstack/ai'
2
+ import type { ToolBinding } from '@tanstack/ai-code-mode'
3
+ import type { Skill, SkillStorage } from './types'
4
+
5
+ interface SkillsToBindingsOptions {
6
+ /**
7
+ * Skills to convert to bindings
8
+ */
9
+ skills: Array<Skill>
10
+
11
+ /**
12
+ * Tool execution context for emitting custom events
13
+ */
14
+ context?: ToolExecutionContext
15
+
16
+ /**
17
+ * Function to execute skill code in the sandbox
18
+ * The skill code receives `input` as a variable
19
+ */
20
+ executeInSandbox: (code: string, input: unknown) => Promise<unknown>
21
+
22
+ /**
23
+ * Storage for updating execution stats
24
+ */
25
+ storage: SkillStorage
26
+ }
27
+
28
+ /**
29
+ * Convert skills to sandbox bindings with the skill_ prefix.
30
+ * Skills become callable functions inside the sandbox.
31
+ */
32
+ export function skillsToBindings({
33
+ skills,
34
+ context,
35
+ executeInSandbox,
36
+ storage,
37
+ }: SkillsToBindingsOptions): Record<string, ToolBinding> {
38
+ const bindings: Record<string, ToolBinding> = {}
39
+
40
+ for (const skill of skills) {
41
+ const bindingName = `skill_${skill.name}`
42
+
43
+ bindings[bindingName] = {
44
+ name: bindingName,
45
+ description: skill.description,
46
+ inputSchema: skill.inputSchema,
47
+ outputSchema: skill.outputSchema,
48
+ execute: async (input: unknown) => {
49
+ const startTime = Date.now()
50
+
51
+ // Emit skill call event
52
+ context?.emitCustomEvent('code_mode:skill_call', {
53
+ skill: skill.name,
54
+ input,
55
+ timestamp: startTime,
56
+ })
57
+
58
+ try {
59
+ // Wrap the skill code to receive input as a variable
60
+ const wrappedCode = `
61
+ const input = ${JSON.stringify(input)};
62
+ ${skill.code}
63
+ `
64
+
65
+ const result = await executeInSandbox(wrappedCode, input)
66
+ const duration = Date.now() - startTime
67
+
68
+ // Emit success event
69
+ context?.emitCustomEvent('code_mode:skill_result', {
70
+ skill: skill.name,
71
+ result,
72
+ duration,
73
+ timestamp: Date.now(),
74
+ })
75
+
76
+ // Update stats (async, don't await to not block)
77
+ storage.updateStats(skill.name, true).catch(() => {
78
+ // Silently ignore stats update failures
79
+ })
80
+
81
+ return result
82
+ } catch (error) {
83
+ const duration = Date.now() - startTime
84
+
85
+ // Emit error event
86
+ context?.emitCustomEvent('code_mode:skill_error', {
87
+ skill: skill.name,
88
+ error: error instanceof Error ? error.message : String(error),
89
+ duration,
90
+ timestamp: Date.now(),
91
+ })
92
+
93
+ // Update stats (async, don't await)
94
+ storage.updateStats(skill.name, false).catch(() => {
95
+ // Silently ignore stats update failures
96
+ })
97
+
98
+ throw error
99
+ }
100
+ },
101
+ }
102
+ }
103
+
104
+ return bindings
105
+ }
106
+
107
+ /**
108
+ * Create a simple binding record for skills without full sandbox execution.
109
+ * This is used when skills are being documented in the system prompt
110
+ * but not yet being executed.
111
+ */
112
+ export function skillsToSimpleBindings(
113
+ skills: Array<Skill>,
114
+ ): Record<string, ToolBinding> {
115
+ const bindings: Record<string, ToolBinding> = {}
116
+
117
+ for (const skill of skills) {
118
+ const bindingName = `skill_${skill.name}`
119
+
120
+ bindings[bindingName] = {
121
+ name: bindingName,
122
+ description: skill.description,
123
+ inputSchema: skill.inputSchema,
124
+ outputSchema: skill.outputSchema,
125
+ execute: async () => {
126
+ throw new Error(
127
+ `Skill ${skill.name} is not available for execution in this context`,
128
+ )
129
+ },
130
+ }
131
+ }
132
+
133
+ return bindings
134
+ }
@@ -0,0 +1,319 @@
1
+ import { toolDefinition } from '@tanstack/ai'
2
+ import {
3
+ createEventAwareBindings,
4
+ stripTypeScript,
5
+ toolsToBindings,
6
+ } from '@tanstack/ai-code-mode'
7
+ import { z } from 'zod'
8
+ import type { ServerTool, ToolExecutionContext } from '@tanstack/ai'
9
+ import type {
10
+ CodeModeTool,
11
+ IsolateDriver,
12
+ ToolBinding,
13
+ } from '@tanstack/ai-code-mode'
14
+ import type { Skill, SkillStorage } from './types'
15
+
16
+ /**
17
+ * Options for converting a single skill to a tool
18
+ */
19
+ export interface SkillToToolOptions {
20
+ /**
21
+ * The skill to convert
22
+ */
23
+ skill: Skill
24
+
25
+ /**
26
+ * Isolate driver for executing skill code
27
+ */
28
+ driver: IsolateDriver
29
+
30
+ /**
31
+ * Pre-computed bindings for external_* functions
32
+ */
33
+ bindings: Record<string, ToolBinding>
34
+
35
+ /**
36
+ * Storage for updating execution stats
37
+ */
38
+ storage: SkillStorage
39
+
40
+ /**
41
+ * Timeout for skill execution in ms
42
+ * @default 30000
43
+ */
44
+ timeout?: number
45
+
46
+ /**
47
+ * Memory limit in bytes
48
+ * @default 128
49
+ */
50
+ memoryLimit?: number
51
+ }
52
+
53
+ interface SkillsToToolsOptions {
54
+ /**
55
+ * Skills to convert to tools
56
+ */
57
+ skills: Array<Skill>
58
+
59
+ /**
60
+ * Isolate driver for executing skill code
61
+ */
62
+ driver: IsolateDriver
63
+
64
+ /**
65
+ * Original tools that become external_* bindings
66
+ * (so skills can call external_* functions)
67
+ */
68
+ tools: Array<CodeModeTool>
69
+
70
+ /**
71
+ * Storage for updating execution stats
72
+ */
73
+ storage: SkillStorage
74
+
75
+ /**
76
+ * Timeout for skill execution in ms
77
+ * @default 30000
78
+ */
79
+ timeout?: number
80
+
81
+ /**
82
+ * Memory limit in bytes
83
+ * @default 128
84
+ */
85
+ memoryLimit?: number
86
+ }
87
+
88
+ /**
89
+ * Convert JSON Schema to Zod schema.
90
+ * This is a simplified converter that handles common cases.
91
+ */
92
+ function jsonSchemaToZod(schema: Record<string, unknown>): z.ZodType {
93
+ const type = schema.type as string
94
+
95
+ if (type === 'string') {
96
+ let zodString = z.string()
97
+ if (schema.description) {
98
+ zodString = zodString.describe(schema.description as string)
99
+ }
100
+ return zodString
101
+ }
102
+ if (type === 'number' || type === 'integer') {
103
+ let zodNum = z.number()
104
+ if (schema.description) {
105
+ zodNum = zodNum.describe(schema.description as string)
106
+ }
107
+ return zodNum
108
+ }
109
+ if (type === 'boolean') {
110
+ let zodBool = z.boolean()
111
+ if (schema.description) {
112
+ zodBool = zodBool.describe(schema.description as string)
113
+ }
114
+ return zodBool
115
+ }
116
+ if (type === 'array') {
117
+ const items = schema.items as Record<string, unknown> | undefined
118
+ if (items) {
119
+ return z.array(jsonSchemaToZod(items))
120
+ }
121
+ return z.array(z.unknown())
122
+ }
123
+ if (type === 'object') {
124
+ const properties = schema.properties as
125
+ | Record<string, Record<string, unknown>>
126
+ | undefined
127
+ const required = (schema.required as Array<string> | undefined) ?? []
128
+
129
+ if (properties) {
130
+ const shape: Record<string, z.ZodType> = {}
131
+ for (const [key, propSchema] of Object.entries(properties)) {
132
+ let zodProp = jsonSchemaToZod(propSchema)
133
+ if (!required.includes(key)) {
134
+ zodProp = zodProp.optional()
135
+ }
136
+ shape[key] = zodProp
137
+ }
138
+ return z.object(shape)
139
+ }
140
+ return z.record(z.string(), z.unknown())
141
+ }
142
+
143
+ // Fallback
144
+ return z.unknown()
145
+ }
146
+
147
+ /**
148
+ * Convert a single skill to a ServerTool that the LLM can call directly.
149
+ * The skill executes its code in the sandbox with access to external_* bindings.
150
+ */
151
+ export function skillToTool({
152
+ skill,
153
+ driver,
154
+ bindings,
155
+ storage,
156
+ timeout = 30000,
157
+ memoryLimit = 128,
158
+ }: SkillToToolOptions): ServerTool<any, any, any> {
159
+ // Generate input and output schemas from JSON Schema
160
+ const inputSchema = jsonSchemaToZod(skill.inputSchema)
161
+ const outputSchema = jsonSchemaToZod(skill.outputSchema)
162
+
163
+ return toolDefinition({
164
+ name: skill.name,
165
+ description: `[SKILL] ${skill.description}`,
166
+ inputSchema,
167
+ outputSchema,
168
+ }).server(async (input: unknown, context?: ToolExecutionContext) => {
169
+ const startTime = Date.now()
170
+ const emitCustomEvent = context?.emitCustomEvent || (() => {})
171
+
172
+ // Emit skill call event
173
+ emitCustomEvent('code_mode:skill_call', {
174
+ skill: skill.name,
175
+ input,
176
+ timestamp: startTime,
177
+ })
178
+
179
+ let isolateContext = null
180
+
181
+ try {
182
+ console.log(
183
+ `[Skill:${skill.name}] Starting execution with input:`,
184
+ JSON.stringify(input).substring(0, 200),
185
+ )
186
+
187
+ // Wrap the skill code to receive input as a variable
188
+ const wrappedCode = `
189
+ const input = ${JSON.stringify(input)};
190
+ ${skill.code}
191
+ `
192
+ console.log(
193
+ `[Skill:${skill.name}] Wrapped code (first 500 chars):`,
194
+ wrappedCode.substring(0, 500),
195
+ )
196
+
197
+ // Strip TypeScript to JavaScript
198
+ const strippedCode = await stripTypeScript(wrappedCode)
199
+ console.log(
200
+ `[Skill:${skill.name}] Stripped code (first 500 chars):`,
201
+ strippedCode.substring(0, 500),
202
+ )
203
+
204
+ // Create event-aware bindings
205
+ const eventAwareBindings = createEventAwareBindings(
206
+ bindings,
207
+ emitCustomEvent,
208
+ )
209
+ console.log(
210
+ `[Skill:${skill.name}] Event-aware bindings:`,
211
+ Object.keys(eventAwareBindings),
212
+ )
213
+
214
+ // Create sandbox context
215
+ console.log(`[Skill:${skill.name}] Creating sandbox context...`)
216
+ isolateContext = await driver.createContext({
217
+ bindings: eventAwareBindings,
218
+ timeout,
219
+ memoryLimit,
220
+ })
221
+ console.log(`[Skill:${skill.name}] Sandbox context created`)
222
+
223
+ // Execute the code
224
+ console.log(`[Skill:${skill.name}] Executing code...`)
225
+ const executionResult = await isolateContext.execute(strippedCode)
226
+ console.log(`[Skill:${skill.name}] Execution result:`, {
227
+ success: executionResult.success,
228
+ hasValue: 'value' in executionResult,
229
+ error: executionResult.error,
230
+ logs: executionResult.logs,
231
+ })
232
+
233
+ const duration = Date.now() - startTime
234
+
235
+ if (!executionResult.success) {
236
+ console.error(
237
+ `[Skill:${skill.name}] Execution failed:`,
238
+ executionResult.error,
239
+ )
240
+ throw new Error(
241
+ executionResult.error?.message || 'Skill execution failed',
242
+ )
243
+ }
244
+
245
+ // Emit success event
246
+ emitCustomEvent('code_mode:skill_result', {
247
+ skill: skill.name,
248
+ result: executionResult.value,
249
+ duration,
250
+ timestamp: Date.now(),
251
+ })
252
+
253
+ // Update stats (async, don't await to not block)
254
+ storage.updateStats(skill.name, true).catch(() => {
255
+ // Silently ignore stats update failures
256
+ })
257
+
258
+ return executionResult.value
259
+ } catch (error) {
260
+ const duration = Date.now() - startTime
261
+ console.error(`[Skill:${skill.name}] CAUGHT ERROR:`, {
262
+ message: error instanceof Error ? error.message : String(error),
263
+ stack: error instanceof Error ? error.stack : undefined,
264
+ duration,
265
+ })
266
+
267
+ // Emit error event
268
+ emitCustomEvent('code_mode:skill_error', {
269
+ skill: skill.name,
270
+ error: error instanceof Error ? error.message : String(error),
271
+ duration,
272
+ timestamp: Date.now(),
273
+ })
274
+
275
+ // Update stats (async, don't await)
276
+ storage.updateStats(skill.name, false).catch(() => {
277
+ // Silently ignore stats update failures
278
+ })
279
+
280
+ throw error
281
+ } finally {
282
+ if (isolateContext) {
283
+ await isolateContext.dispose()
284
+ }
285
+ }
286
+ })
287
+ }
288
+
289
+ /**
290
+ * Convert multiple skills to ServerTools that the LLM can call directly.
291
+ * Skills become real tools that execute their code in the sandbox.
292
+ */
293
+ export function skillsToTools({
294
+ skills,
295
+ driver,
296
+ tools,
297
+ storage,
298
+ timeout = 30000,
299
+ memoryLimit = 128,
300
+ }: SkillsToToolsOptions): Array<ServerTool<any, any, any>> {
301
+ // Pre-compute bindings from tools (these are shared across all skill executions)
302
+ console.log(
303
+ '[SkillsToTools] Creating bindings from tools:',
304
+ tools.map((t) => t.name),
305
+ )
306
+ const bindings = toolsToBindings(tools, 'external_')
307
+ console.log('[SkillsToTools] Created bindings:', Object.keys(bindings))
308
+
309
+ return skills.map((skill) =>
310
+ skillToTool({
311
+ skill,
312
+ driver,
313
+ bindings,
314
+ storage,
315
+ timeout,
316
+ memoryLimit,
317
+ }),
318
+ )
319
+ }
@@ -0,0 +1,243 @@
1
+ import { mkdir, readFile, rm, writeFile } from 'node:fs/promises'
2
+ import { join } from 'node:path'
3
+ import { existsSync } from 'node:fs'
4
+ import { createDefaultTrustStrategy } from '../trust-strategies'
5
+ import type {
6
+ Skill,
7
+ SkillIndexEntry,
8
+ SkillSearchOptions,
9
+ SkillStorage,
10
+ } from '../types'
11
+ import type { TrustStrategy } from '../trust-strategies'
12
+
13
+ export interface FileSkillStorageOptions {
14
+ /**
15
+ * Directory path for storing skills
16
+ */
17
+ directory: string
18
+
19
+ /**
20
+ * Trust strategy for determining skill trust levels
21
+ * @default createDefaultTrustStrategy()
22
+ */
23
+ trustStrategy?: TrustStrategy
24
+ }
25
+
26
+ /**
27
+ * File-system based skill storage
28
+ *
29
+ * Directory structure:
30
+ * .skills/
31
+ * _index.json # Fast catalog loading
32
+ * fetch_github_stats/
33
+ * meta.json # Metadata (description, schemas, hints, stats)
34
+ * code.ts # The actual TypeScript code
35
+ * deploy_to_prod/
36
+ * meta.json
37
+ * code.ts
38
+ */
39
+ export function createFileSkillStorage(
40
+ directoryOrOptions: string | FileSkillStorageOptions,
41
+ ): SkillStorage {
42
+ const options =
43
+ typeof directoryOrOptions === 'string'
44
+ ? { directory: directoryOrOptions }
45
+ : directoryOrOptions
46
+
47
+ const { directory, trustStrategy = createDefaultTrustStrategy() } = options
48
+ const indexPath = join(directory, '_index.json')
49
+
50
+ console.log('[FileSkillStorage] Initialized with directory:', directory)
51
+
52
+ async function ensureDirectory(): Promise<void> {
53
+ if (!existsSync(directory)) {
54
+ console.log('[FileSkillStorage] Creating directory:', directory)
55
+ await mkdir(directory, { recursive: true })
56
+ }
57
+ }
58
+
59
+ async function loadIndex(): Promise<Array<SkillIndexEntry>> {
60
+ await ensureDirectory()
61
+
62
+ if (!existsSync(indexPath)) {
63
+ return []
64
+ }
65
+
66
+ const content = await readFile(indexPath, 'utf-8')
67
+ return JSON.parse(content) as Array<SkillIndexEntry>
68
+ }
69
+
70
+ async function loadAll(): Promise<Array<Skill>> {
71
+ const index = await loadIndex()
72
+ const skills: Array<Skill> = []
73
+
74
+ for (const entry of index) {
75
+ const skill = await get(entry.name)
76
+ if (skill) {
77
+ skills.push(skill)
78
+ }
79
+ }
80
+
81
+ return skills
82
+ }
83
+
84
+ async function saveIndex(index: Array<SkillIndexEntry>): Promise<void> {
85
+ await writeFile(indexPath, JSON.stringify(index, null, 2))
86
+ }
87
+
88
+ async function get(name: string): Promise<Skill | null> {
89
+ const skillDir = join(directory, name)
90
+ const metaPath = join(skillDir, 'meta.json')
91
+ const codePath = join(skillDir, 'code.ts')
92
+
93
+ if (!existsSync(metaPath)) {
94
+ return null
95
+ }
96
+
97
+ const [metaContent, code] = await Promise.all([
98
+ readFile(metaPath, 'utf-8'),
99
+ readFile(codePath, 'utf-8'),
100
+ ])
101
+
102
+ const meta = JSON.parse(metaContent) as Omit<Skill, 'code'>
103
+ return { ...meta, code }
104
+ }
105
+
106
+ async function save(
107
+ skill: Omit<Skill, 'createdAt' | 'updatedAt'>,
108
+ ): Promise<Skill> {
109
+ await ensureDirectory()
110
+
111
+ const skillDir = join(directory, skill.name)
112
+ const metaPath = join(skillDir, 'meta.json')
113
+ const codePath = join(skillDir, 'code.ts')
114
+
115
+ const now = new Date().toISOString()
116
+ const existing = await get(skill.name)
117
+
118
+ const fullSkill: Skill = {
119
+ ...skill,
120
+ createdAt: existing?.createdAt ?? now,
121
+ updatedAt: now,
122
+ }
123
+
124
+ // Separate code from metadata
125
+ const { code, ...meta } = fullSkill
126
+
127
+ // Write skill files
128
+ await mkdir(skillDir, { recursive: true })
129
+ await Promise.all([
130
+ writeFile(metaPath, JSON.stringify(meta, null, 2)),
131
+ writeFile(codePath, code),
132
+ ])
133
+
134
+ // Update index
135
+ const index = await loadIndex()
136
+ const indexEntry: SkillIndexEntry = {
137
+ id: fullSkill.id,
138
+ name: fullSkill.name,
139
+ description: fullSkill.description,
140
+ usageHints: fullSkill.usageHints,
141
+ trustLevel: fullSkill.trustLevel,
142
+ }
143
+
144
+ const existingIdx = index.findIndex((s) => s.name === skill.name)
145
+ if (existingIdx >= 0) {
146
+ index[existingIdx] = indexEntry
147
+ } else {
148
+ index.push(indexEntry)
149
+ }
150
+ await saveIndex(index)
151
+
152
+ return fullSkill
153
+ }
154
+
155
+ async function deleteSkill(name: string): Promise<boolean> {
156
+ const skillDir = join(directory, name)
157
+
158
+ if (!existsSync(skillDir)) {
159
+ return false
160
+ }
161
+
162
+ await rm(skillDir, { recursive: true })
163
+
164
+ // Update index
165
+ const index = await loadIndex()
166
+ const filtered = index.filter((s) => s.name !== name)
167
+ await saveIndex(filtered)
168
+
169
+ return true
170
+ }
171
+
172
+ async function search(
173
+ query: string,
174
+ options: SkillSearchOptions = {},
175
+ ): Promise<Array<SkillIndexEntry>> {
176
+ const { limit = 5 } = options
177
+ const index = await loadIndex()
178
+
179
+ // Simple text matching - can be replaced with embeddings
180
+ const queryLower = query.toLowerCase()
181
+ const terms = queryLower.split(/\s+/)
182
+
183
+ const scored = index.map((skill) => {
184
+ let score = 0
185
+ const searchText = [skill.name, skill.description, ...skill.usageHints]
186
+ .join(' ')
187
+ .toLowerCase()
188
+
189
+ for (const term of terms) {
190
+ if (searchText.includes(term)) {
191
+ score += 1
192
+ }
193
+ // Boost exact name matches
194
+ if (skill.name.toLowerCase().includes(term)) {
195
+ score += 2
196
+ }
197
+ }
198
+
199
+ return { skill, score }
200
+ })
201
+
202
+ return scored
203
+ .filter((s) => s.score > 0)
204
+ .sort((a, b) => b.score - a.score)
205
+ .slice(0, limit)
206
+ .map((s) => s.skill)
207
+ }
208
+
209
+ async function updateStats(name: string, success: boolean): Promise<void> {
210
+ const skill = await get(name)
211
+ if (!skill) return
212
+
213
+ const { executions, successRate } = skill.stats
214
+ const newExecutions = executions + 1
215
+ const newSuccessRate =
216
+ (successRate * executions + (success ? 1 : 0)) / newExecutions
217
+
218
+ const newStats = { executions: newExecutions, successRate: newSuccessRate }
219
+
220
+ // Use trust strategy to calculate new trust level
221
+ const newTrustLevel = trustStrategy.calculateTrustLevel(
222
+ skill.trustLevel,
223
+ newStats,
224
+ )
225
+
226
+ await save({
227
+ ...skill,
228
+ stats: newStats,
229
+ trustLevel: newTrustLevel,
230
+ })
231
+ }
232
+
233
+ return {
234
+ loadIndex,
235
+ loadAll,
236
+ get,
237
+ save,
238
+ delete: deleteSkill,
239
+ search,
240
+ updateStats,
241
+ trustStrategy,
242
+ }
243
+ }
@@ -0,0 +1,6 @@
1
+ // Storage implementations
2
+ export { createFileSkillStorage } from './file-storage'
3
+ export { createMemorySkillStorage } from './memory-storage'
4
+
5
+ // Re-export types
6
+ export type { SkillStorage, Skill, SkillIndexEntry } from '../types'