@johpaz/hive-sdk 0.0.18 → 0.1.4

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 (41) hide show
  1. package/bun.lock +291 -1
  2. package/docs/HIVE-HARNESS.md +113 -0
  3. package/package.json +36 -2
  4. package/packages/cli/package.json +1 -1
  5. package/packages/core/package.json +13 -2
  6. package/packages/core/src/ace/Tracer.ts +1 -1
  7. package/packages/core/src/agent/AgentRunner.ts +12 -0
  8. package/packages/core/src/agent/ContextCompiler.ts +4 -4
  9. package/packages/core/src/agent/ConversationStore.ts +30 -20
  10. package/packages/core/src/agent/selectors/PlaybookSelector.ts +50 -76
  11. package/packages/core/src/agent/selectors/SkillSelector.ts +106 -262
  12. package/packages/core/src/agent/selectors/ToolSelector.ts +53 -89
  13. package/packages/core/src/auth/auth.ts +36 -23
  14. package/packages/core/src/harness/boot-id.ts +20 -0
  15. package/packages/core/src/harness/collections.ts +98 -0
  16. package/packages/core/src/harness/db-helpers.ts +87 -0
  17. package/packages/core/src/harness/durable-queue.ts +337 -0
  18. package/packages/core/src/harness/goal-verifier.ts +141 -0
  19. package/packages/core/src/harness/harness.test.ts +236 -0
  20. package/packages/core/src/harness/index.ts +34 -0
  21. package/packages/core/src/harness/job-store.ts +399 -0
  22. package/packages/core/src/harness/proof-packet.ts +69 -0
  23. package/packages/core/src/harness/reconcile.ts +149 -0
  24. package/packages/core/src/harness/run-epoch.ts +32 -0
  25. package/packages/core/src/harness/run-store.ts +334 -0
  26. package/packages/core/src/index.ts +13 -0
  27. package/packages/core/src/memory/Scratchpad.test.ts +23 -21
  28. package/packages/core/src/memory/Scratchpad.ts +41 -24
  29. package/packages/core/src/storage/HiveDBStorage.ts +64 -0
  30. package/packages/core/src/storage/SQLiteStorage.ts +7 -0
  31. package/packages/core/src/storage/hiveSeed.ts +308 -0
  32. package/packages/core/src/storage/hiveStorage.test.ts +38 -0
  33. package/packages/core/src/storage/index.ts +11 -0
  34. package/packages/core/src/storage/seed.ts +5 -1
  35. package/packages/core/src/storage/usage.ts +106 -167
  36. package/packages/core/src/tool-runtime/tool-runtime.test.ts +11 -3
  37. package/packages/core/src/tools/agents/get-available-models.ts +52 -56
  38. package/packages/core/src/tools/agents/index.ts +77 -60
  39. package/packages/core/src/tools/core/index.ts +106 -291
  40. package/packages/core/src/tools/meeting/index.ts +83 -93
  41. package/packages/core/src/utils/toon.ts +4 -4
@@ -1,37 +1,24 @@
1
1
  /**
2
- * FTS5-based Dynamic Skill Selector Module
3
- *
2
+ * HiveDB-based Dynamic Skill Selector Module
3
+ *
4
4
  * Context Compiler Level 4 - Intelligent Skill Selection
5
- *
6
- * This module uses SQLite FTS5 bm25() scoring to select the most relevant
7
- * skills (0-5) based on the user message, similar to tool selection.
8
- *
9
- * DESIGN DECISIONS:
10
- *
11
- * 1. Reads from skills table in database (not hardcoded catalog)
12
- * 2. Maximum 5 skills per turn for balanced context injection
13
- * 3. Relevance threshold for conversational messages
14
- * 4. Uses skill descriptions for FTS5 matching
15
- * 5. Returns skill content for injection into system prompt
5
+ *
6
+ * Uses HiveDB hybrid search (BM25 + optional vector) over the skills index.
16
7
  */
17
8
 
18
- import { getDb } from "../../storage/SQLiteStorage.ts"
9
+ import { getHiveDB } from "../../storage/HiveDBStorage.ts"
19
10
  import { logger } from "../../utils/logger.ts"
11
+ import type { HiveSkillDoc } from "../../storage/hiveSeed.ts"
12
+ import type { IndexDoc } from "@johpaz/hive-db"
20
13
 
21
14
  const log = logger.child("skill-selector")
22
15
 
23
16
  // ─── Minimal Skill Set ─────────────────────────────────────────────────────────
24
17
 
25
- /**
26
- * Skills mínimas que SIEMPRE están disponibles (asociadas a las 4 tools iniciales)
27
- * - memory_manager: usa save_note (notas persistentes)
28
- * - canvas_report: usa report_progress (reportes de progreso)
29
- * - task_orchestrator: usa notify (comunicación entre agentes)
30
- */
31
18
  export const MINIMAL_SKILL_NAMES = new Set([
32
- "memory_manager", // Asociada a save_note
33
- "canvas_report", // Asociada a report_progress
34
- "task_orchestrator", // Asociada a notify y agent coordination
19
+ "memory_manager",
20
+ "canvas_report",
21
+ "task_orchestrator",
35
22
  ])
36
23
 
37
24
  // ─── Types ───────────────────────────────────────────────────────────────────────
@@ -68,19 +55,10 @@ export interface SkillSelectorResult {
68
55
 
69
56
  // ─── Configuration ─────────────────────────────────────────────────────────
70
57
 
71
- /** Maximum skills to return per message */
72
- const MAX_SKILLS_PER_TURN = 4 // Increased from 2 to allow more skills
58
+ const MAX_SKILLS_PER_TURN = 4
73
59
 
74
- /**
75
- * Minimum bm25 score threshold. Below this = conversational, no skills needed.
76
- *
77
- * CRITICAL: bm25() returns NEGATIVE scores where closer to 0 = more relevant.
78
- * - Score of -5 is MORE relevant than -20
79
- * - We use -15 as threshold to allow reasonable matching while filtering noise
80
- */
81
- const MIN_RELEVANCE_THRESHOLD = -15 // Increased from -5 to allow more matches
60
+ const MIN_RELEVANCE_THRESHOLD = 0.5
82
61
 
83
- /** Stopwords to filter out before FTS5 query construction */
84
62
  const STOPWORDS = new Set([
85
63
  "que", "con", "para", "por", "una", "uno", "los", "las", "del",
86
64
  "como", "esta", "esto", "ese", "eso", "the", "and", "for",
@@ -92,7 +70,6 @@ const STOPWORDS = new Set([
92
70
  "puedes", "necesito", "quiero", "podés", "necesitás", "querés",
93
71
  ])
94
72
 
95
- /** Conversational patterns that should return empty skill list */
96
73
  const CONVERSATIONAL_PATTERNS = [
97
74
  /^(hola|hi|hello|hey|buenos? días?|buenas? noches?|qué tal|howdy)/i,
98
75
  /^(gracias|thank you|thanks|muchas gracias|muchas thanks)/i,
@@ -101,46 +78,29 @@ const CONVERSATIONAL_PATTERNS = [
101
78
  /^(adiós|bye|nos vemos|see you|later|chau)/i,
102
79
  /^(entiendo|understand|i see|ya veo|got it)/i,
103
80
  /^(bien|good|great|excelente|awesome|perfect)/i,
104
- /^(?:\?|¿)$/, // Just a question mark
81
+ /^(?:\?|¿)$/,
105
82
  ]
106
83
 
107
84
  // ─── Helper Functions ───────────────────────────────────────────────────────
108
85
 
109
- /**
110
- * Check if message is purely conversational (no skills needed)
111
- */
112
86
  function isConversational(message: string): boolean {
113
87
  const trimmed = message.trim()
114
-
115
- // Empty or very short messages
116
88
  if (trimmed.length < 2) return true
117
-
118
- // Check conversational patterns
119
89
  for (const pattern of CONVERSATIONAL_PATTERNS) {
120
90
  if (pattern.test(trimmed)) {
121
91
  log.debug(`[skill-selector] Message matched conversational pattern: ${pattern}`)
122
92
  return true
123
93
  }
124
94
  }
125
-
126
- // Check if all words are stopwords (likely conversational)
127
95
  const words = trimmed.toLowerCase().split(/\s+/)
128
96
  const meaningfulWords = words.filter(w => w.length > 2 && !STOPWORDS.has(w))
129
97
  if (meaningfulWords.length === 0) {
130
98
  log.debug(`[skill-selector] All words are stopwords - conversational`)
131
99
  return true
132
100
  }
133
-
134
101
  return false
135
102
  }
136
103
 
137
- /**
138
- * Build FTS5 query from user message
139
- *
140
- * Uses prefix matching for better recall:
141
- * - "generar" matches "generando", "generación", "genera"
142
- * - "código" matches "codigos", "codificar"
143
- */
144
104
  function buildFTSQuery(message: string): string {
145
105
  const words = message
146
106
  .toLowerCase()
@@ -150,144 +110,94 @@ function buildFTSQuery(message: string): string {
150
110
  .slice(0, 8)
151
111
 
152
112
  if (words.length === 0) return ""
153
-
154
- // Use prefix matching for better recall (e.g., "gener*" matches "generar", "generando", "generación")
155
- return words.map(w => `${w}*`).join(" OR ")
113
+ return words.join(" ")
156
114
  }
157
115
 
158
- /**
159
- * Check if message matches explicit triggers from a skill
160
- */
161
- function matchTriggers(message: string, triggersJson: string | null): boolean {
162
- if (!triggersJson) return false
116
+ function matchTriggers(message: string, triggers: string[]): boolean {
117
+ if (!triggers || triggers.length === 0) return false
118
+ const lowerMessage = message.toLowerCase()
119
+ return triggers.some(trigger =>
120
+ lowerMessage.includes(trigger.toLowerCase())
121
+ )
122
+ }
163
123
 
164
- try {
165
- // Triggers are stored as comma-separated string in DB (e.g., "trigger1,trigger2")
166
- const triggers: string[] = triggersJson.split(",").map(t => t.trim()).filter(t => t.length > 0)
167
- if (triggers.length === 0) return false
168
-
169
- const lowerMessage = message.toLowerCase()
170
- return triggers.some(trigger =>
171
- lowerMessage.includes(trigger.toLowerCase())
172
- )
173
- } catch (err) {
174
- log.warn(`[skill-selector] Failed to parse triggers: ${(err as Error).message}`)
175
- return false
124
+ function toSkillDescriptor(doc: HiveSkillDoc): SkillDescriptor {
125
+ return {
126
+ id: doc.id,
127
+ name: doc.name,
128
+ description: doc.description,
129
+ category: doc.category,
130
+ tools: Array.isArray(doc.tools) ? doc.tools.join(",") : String(doc.tools ?? ""),
131
+ triggers: Array.isArray(doc.triggers) ? doc.triggers.join(",") : String(doc.triggers ?? ""),
132
+ preferred_agents: Array.isArray(doc.preferredAgents) ? doc.preferredAgents.join(",") : String(doc.preferredAgents ?? ""),
133
+ body: doc.body,
134
+ version: doc.version,
135
+ version_num: doc.versionNum,
136
+ active: doc.active ? 1 : 0,
176
137
  }
177
138
  }
178
139
 
179
140
  // ─── Main Selection Function ─────────────────────────────────────────────────
180
141
 
181
- /**
182
- * Select skills for a given user message using hybrid matching:
183
- * 1. First check explicit triggers (high confidence match)
184
- * 2. Fallback to FTS5 bm25() scoring for semantic matching
185
- *
186
- * @param userMessage - The raw user message
187
- * @returns Array of 0-5 selected skills with scores
188
- *
189
- * ALGORITHM:
190
- * 1. If conversational → return []
191
- * 2. Check explicit triggers from all enabled skills
192
- * 3. If trigger match found → return matching skill immediately
193
- * 4. Build FTS5 query from message keywords
194
- * 5. Query skills_fts with bm25() scoring
195
- * 6. Filter results below MIN_RELEVANCE_THRESHOLD
196
- * 7. Return top MAX_SKILLS_PER_TURN results
197
- */
198
- export function selectSkills(userMessage: string): SkillDescriptor[] {
142
+ export async function selectSkills(userMessage: string): Promise<SkillDescriptor[]> {
199
143
  const startTime = performance.now()
200
144
 
201
145
  log.debug(`[skill-selector] Processing user message: "${userMessage.substring(0, 100)}"`)
202
146
 
203
- // Step 1: Check if conversational
204
147
  if (isConversational(userMessage)) {
205
148
  log.debug(`[skill-selector] Conversational message, returning empty array`)
206
149
  return []
207
150
  }
208
151
 
209
- // Step 2: Check explicit triggers first (high priority)
210
- const db = getDb()
211
- const allSkills = db.query(`
212
- SELECT id, name, description, category, tools, triggers, preferred_agents, body, version, version_num, active
213
- FROM skills
214
- WHERE active = 1
215
- `).all() as SkillDescriptor[]
152
+ const db = await getHiveDB()
153
+ const skillsCol = db.collection<HiveSkillDoc>("skills")
154
+ const entries = await skillsCol.scan()
155
+ const allSkills = entries.map(e => e.doc).filter(s => s.active)
216
156
 
217
- // Check trigger match - if found, return immediately with high confidence
218
157
  for (const skill of allSkills) {
219
- if (skill.triggers && matchTriggers(userMessage, skill.triggers)) {
158
+ if (matchTriggers(userMessage, skill.triggers)) {
220
159
  log.info(`[skill-selector] Trigger match found: ${skill.name}`)
221
- return [skill]
160
+ return [toSkillDescriptor(skill)]
222
161
  }
223
162
  }
224
163
 
225
- // Step 3: Build FTS5 query for semantic matching
226
164
  const ftsQuery = buildFTSQuery(userMessage)
227
165
  if (!ftsQuery) {
228
- log.debug(`[skill-selector] No valid FTS query terms, returning empty array`)
166
+ log.debug(`[skill-selector] No valid query terms, returning empty array`)
229
167
  return []
230
168
  }
231
169
 
232
- log.debug(`[skill-selector] FTS query: "${ftsQuery}"`)
233
-
234
- // Step 4: Execute FTS5 query with bm25 scoring
235
- // Use bm25() with column weights for relevance scoring
236
- // FTS5 table columns: id, name, description, category, tools, triggers, body
237
- // Weights: id=1.0, name=4.0, description=5.0, category=1.0, tools=1.0, triggers=5.0, body=2.0
238
- // Higher weight on description (5.0) and triggers (5.0) for best semantic matching
239
- const ftsResults = db.query(`
240
- SELECT id, bm25(skills_fts, 1.0, 4.0, 5.0, 1.0, 1.0, 5.0, 2.0) as bm25_score
241
- FROM skills_fts
242
- WHERE skills_fts MATCH ?
243
- ORDER BY bm25_score ASC
244
- LIMIT 20
245
- `).all(ftsQuery) as { id: string; bm25_score: number }[]
246
-
247
- if (ftsResults.length === 0) {
248
- log.debug(`[skill-selector] No FTS matches, returning empty array`)
249
- return []
250
- }
170
+ log.debug(`[skill-selector] Search query: "${ftsQuery}"`)
251
171
 
252
- // Log raw scores for debugging
253
- log.info(`[skill-selector] Raw FTS scores: ${ftsResults.slice(0, 10).map(r => `id=${r.id}, score=${r.bm25_score.toFixed(2)}`).join(", ")}`)
172
+ const hits = await db.queryHybrid({
173
+ text: ftsQuery,
174
+ k: 20,
175
+ boosts: { name: 4.0, body: 5.0, tags: 3.0 },
176
+ })
254
177
 
255
- // Step 5: Apply relevance threshold filter
256
- const relevantResults = ftsResults.filter(r => r.bm25_score >= MIN_RELEVANCE_THRESHOLD)
257
-
258
- if (relevantResults.length === 0) {
259
- log.debug(`[skill-selector] All results below threshold ${MIN_RELEVANCE_THRESHOLD}, returning empty`)
178
+ if (hits.length === 0) {
179
+ log.debug(`[skill-selector] No index matches, returning empty array`)
260
180
  return []
261
181
  }
262
182
 
263
- // Step 6: Fetch full skill details from database
264
- const skillIds = relevantResults.map(r => r.id)
183
+ log.info(`[skill-selector] Raw scores: ${hits.slice(0, 10).map(r => `id=${r.id}, score=${r.score.toFixed(2)}`).join(", ")}`)
265
184
 
266
- let dbSkills: SkillDescriptor[] = []
267
- try {
268
- const db = getDb()
269
- dbSkills = db.query(`
270
- SELECT id, name, description, category, tools, triggers, preferred_agents, body, version, version_num, active
271
- FROM skills
272
- WHERE id IN (${skillIds.map(() => '?').join(',')})
273
- AND active = 1
274
- `).all(...skillIds) as SkillDescriptor[]
275
- } catch (err) {
276
- log.warn(`[skill-selector] Failed to fetch skills from DB:`, err)
185
+ const relevantResults = hits.filter(r => r.score >= MIN_RELEVANCE_THRESHOLD)
186
+ if (relevantResults.length === 0) {
187
+ log.debug(`[skill-selector] All results below threshold ${MIN_RELEVANCE_THRESHOLD}, returning empty`)
277
188
  return []
278
189
  }
279
190
 
280
- // Map scores to skills
281
- const skillMap = new Map(dbSkills.map(s => [s.id, s]))
191
+ const skillMap = new Map(allSkills.map(s => [s.id, s]))
282
192
  const scoredSkills: SelectedSkill[] = []
283
193
 
284
- for (const result of relevantResults) {
285
- const skill = skillMap.get(result.id)
194
+ for (const hit of relevantResults) {
195
+ const skill = skillMap.get(hit.id)
286
196
  if (skill) {
287
197
  scoredSkills.push({
288
198
  id: skill.id,
289
199
  name: skill.name,
290
- score: result.bm25_score,
200
+ score: hit.score,
291
201
  category: skill.category,
292
202
  description: skill.description || "",
293
203
  body: skill.body,
@@ -295,11 +205,8 @@ export function selectSkills(userMessage: string): SkillDescriptor[] {
295
205
  }
296
206
  }
297
207
 
298
- // Step 7: Take top N skills
299
208
  const topSkills = scoredSkills.slice(0, MAX_SKILLS_PER_TURN)
300
-
301
- // Step 8: Return as SkillDescriptor array
302
- const result = topSkills.map(t => skillMap.get(t.id)!).filter(Boolean)
209
+ const result = topSkills.map(t => toSkillDescriptor(skillMap.get(t.id)!)).filter(Boolean)
303
210
 
304
211
  const timing = performance.now() - startTime
305
212
 
@@ -315,23 +222,15 @@ export function selectSkills(userMessage: string): SkillDescriptor[] {
315
222
 
316
223
  // ─── Minimal Skills Loader ───────────────────────────────────────────────────
317
224
 
318
- /**
319
- * Load minimal skills that are ALWAYS available (associated with MINIMAL_TOOLS)
320
- * These are loaded at startup, not via FTS5 search.
321
- *
322
- * @returns Array of minimal skills (memory_manager, canvas_report, task_orchestrator)
323
- */
324
- export function getMinimalSkills(): SkillDescriptor[] {
325
- const db = getDb()
326
-
225
+ export async function getMinimalSkills(): Promise<SkillDescriptor[]> {
327
226
  try {
328
- const placeholders = Array.from(MINIMAL_SKILL_NAMES).map(() => "?").join(",")
329
- const skills = db.query(`
330
- SELECT id, name, description, category, tools, triggers, preferred_agents, body, version, version_num, active
331
- FROM skills
332
- WHERE name IN (${placeholders})
333
- AND active = 1
334
- `).all(...MINIMAL_SKILL_NAMES) as SkillDescriptor[]
227
+ const db = await getHiveDB()
228
+ const skillsCol = db.collection<HiveSkillDoc>("skills")
229
+ const entries = await skillsCol.scan()
230
+ const skills = entries
231
+ .map(e => e.doc)
232
+ .filter(s => s.active && MINIMAL_SKILL_NAMES.has(s.name))
233
+ .map(toSkillDescriptor)
335
234
 
336
235
  log.info(`[skill-selector] Loaded ${skills.length} minimal skills: ${skills.map(s => s.name).join(", ")}`)
337
236
  return skills
@@ -341,136 +240,81 @@ export function getMinimalSkills(): SkillDescriptor[] {
341
240
  }
342
241
  }
343
242
 
344
- // ─── Sync Skills to FTS5 ───────────────────────────────────────────────────
243
+ // ─── Sync Skills to Index ───────────────────────────────────────────────────
345
244
 
346
- /**
347
- * Sync all enabled skills from database to FTS5
348
- * Should be called on initialization from gateway/initializer.ts
349
- * The skills_fts table is created by schema.ts (v0.0.28 includes description)
350
- */
351
245
  export async function syncSkillsToFTS(): Promise<void> {
352
- const db = getDb()
246
+ const db = await getHiveDB()
353
247
 
354
248
  try {
355
- // Step 1: Get all enabled skills from database (v0.0.28 schema with description)
356
- const dbSkills = db.query(`
357
- SELECT id, name, description, category, tools, triggers, body
358
- FROM skills
359
- WHERE active = 1
360
- `).all() as Array<{
361
- id: string
362
- name: string
363
- description: string
364
- category: string
365
- tools: string
366
- triggers: string
367
- body: string
368
- }>
249
+ const skillsCol = db.collection<HiveSkillDoc>("skills")
250
+ const entries = await skillsCol.scan()
251
+ const dbSkills = entries.map(e => e.doc).filter(s => s.active)
369
252
 
370
253
  if (dbSkills.length === 0) {
371
254
  log.debug(`[skill-selector] No skills found in DB to sync`)
255
+ return
372
256
  }
373
257
 
374
- // Step 2: Atomic transaction for FTS5 sync
375
- const syncTransaction = db.transaction(() => {
376
- // Verify table exists
377
- const tableCheck = db.query("SELECT name FROM sqlite_master WHERE type='table' AND name='skills_fts'").get()
378
- if (!tableCheck) {
379
- throw new Error("skills_fts table does not exist!")
380
- }
381
-
382
- // A: Clear existing data
383
- db.run("DELETE FROM skills_fts")
384
-
385
- // B: Prepare insertion (v0.0.28 schema with description)
386
- const insert = db.prepare(`
387
- INSERT INTO skills_fts(id, name, description, category, tools, triggers, body)
388
- VALUES (?, ?, ?, ?, ?, ?, ?)
389
- `)
390
-
391
- // C: Re-populate
392
- for (const skill of dbSkills) {
393
- insert.run(
394
- skill.id,
395
- skill.name,
396
- skill.description || "",
397
- skill.category,
398
- skill.tools,
399
- skill.triggers,
400
- skill.body
401
- )
402
- }
403
- })
404
-
405
- // Execute transaction
406
- syncTransaction()
407
-
408
- log.info(`[skill-selector] Atomic sync complete: ${dbSkills.length} skills indexed in FTS5`)
258
+ const docs: IndexDoc[] = dbSkills.map(skill => ({
259
+ id: skill.id,
260
+ name: skill.name,
261
+ body: `${skill.description || ""} ${skill.body || ""}`,
262
+ tags: [skill.category, ...skill.tools, ...skill.triggers].join(" "),
263
+ filters: [{ field: "type", value: "skill" }],
264
+ }))
265
+
266
+ await db.upsertBatch(docs)
267
+
268
+ log.info(`[skill-selector] Atomic sync complete: ${dbSkills.length} skills indexed in HiveDB`)
409
269
 
410
270
  } catch (err) {
411
271
  log.error(`[skill-selector] Transactional sync failed:`, err)
412
- throw err // Re-throw to inform initializer
272
+ throw err
413
273
  }
414
274
  }
275
+
415
276
  // ─── Initialization ───────────────────────────────────────────────────────
416
277
 
417
- /**
418
- * Initialize the skill selector
419
- * DEPRECATED: syncSkillsToFTS() is now called from gateway/initializer.ts
420
- * This function is kept for backward compatibility but is no longer needed
421
- */
422
278
  export function initializeSkillSelector(): void {
423
- log.info(`[skill-selector] Initializing skill selector (deprecated - sync is done in gateway/initializer.ts)`)
424
- // syncSkillsToFTS() - No longer needed here, done in gateway/initializer.ts
279
+ log.info(`[skill-selector] Initializing skill selector (deprecated - sync is done in seed)`)
425
280
  }
426
281
 
427
282
  // ─── Debug/Test Helpers ─────────────────────────────────────────────────────
428
283
 
429
- /**
430
- * Get all enabled skills from database (for debugging/testing)
431
- */
432
- export function getAllSkillsFromDB(): SkillDescriptor[] {
284
+ export async function getAllSkillsFromDB(): Promise<SkillDescriptor[]> {
433
285
  try {
434
- const db = getDb()
435
- return db.query(`
436
- SELECT id, name, description, category, tools, triggers, preferred_agents, body, version, version_num, active
437
- FROM skills
438
- WHERE active = 1
439
- `).all() as SkillDescriptor[]
286
+ const db = await getHiveDB()
287
+ const skillsCol = db.collection<HiveSkillDoc>("skills")
288
+ const entries = await skillsCol.scan()
289
+ return entries.map(e => toSkillDescriptor(e.doc)).filter(s => s.active)
440
290
  } catch (err) {
441
291
  log.error(`[skill-selector] Failed to fetch skills:`, err)
442
292
  return []
443
293
  }
444
294
  }
445
295
 
446
- /**
447
- * Get skill by name
448
- */
449
- export function getSkillByName(name: string): SkillDescriptor | undefined {
296
+ export async function getSkillByName(name: string): Promise<SkillDescriptor | undefined> {
450
297
  try {
451
- const db = getDb()
452
- return db.query(`
453
- SELECT id, name, description, category, tools, triggers, preferred_agents, body, version, version_num, active
454
- FROM skills
455
- WHERE name = ? AND active = 1
456
- `).get(name) as SkillDescriptor | undefined
298
+ const db = await getHiveDB()
299
+ const skillsCol = db.collection<HiveSkillDoc>("skills")
300
+ const entries = await skillsCol.scan()
301
+ const skill = entries.map(e => e.doc).find(s => s.name === name && s.active)
302
+ return skill ? toSkillDescriptor(skill) : undefined
457
303
  } catch (err) {
458
304
  log.error(`[skill-selector] Failed to fetch skill by name:`, err)
459
305
  return undefined
460
306
  }
461
307
  }
462
308
 
463
- /**
464
- * Get skills by category
465
- */
466
- export function getSkillsByCategory(category: string): SkillDescriptor[] {
309
+ export async function getSkillsByCategory(category: string): Promise<SkillDescriptor[]> {
467
310
  try {
468
- const db = getDb()
469
- return db.query(`
470
- SELECT id, name, description, category, tools, triggers, preferred_agents, body, version, version_num, active
471
- FROM skills
472
- WHERE category = ? AND active = 1
473
- `).all(category) as SkillDescriptor[]
311
+ const db = await getHiveDB()
312
+ const skillsCol = db.collection<HiveSkillDoc>("skills")
313
+ const entries = await skillsCol.scan()
314
+ return entries
315
+ .map(e => e.doc)
316
+ .filter(s => s.active && s.category === category)
317
+ .map(toSkillDescriptor)
474
318
  } catch (err) {
475
319
  log.error(`[skill-selector] Failed to fetch skills by category:`, err)
476
320
  return []