superintent 0.0.1

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 (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +226 -0
  3. package/bin/superintent.js +2 -0
  4. package/dist/commands/extract.d.ts +2 -0
  5. package/dist/commands/extract.js +66 -0
  6. package/dist/commands/init.d.ts +2 -0
  7. package/dist/commands/init.js +56 -0
  8. package/dist/commands/knowledge.d.ts +2 -0
  9. package/dist/commands/knowledge.js +647 -0
  10. package/dist/commands/search.d.ts +2 -0
  11. package/dist/commands/search.js +153 -0
  12. package/dist/commands/spec.d.ts +2 -0
  13. package/dist/commands/spec.js +283 -0
  14. package/dist/commands/status.d.ts +2 -0
  15. package/dist/commands/status.js +43 -0
  16. package/dist/commands/ticket.d.ts +4 -0
  17. package/dist/commands/ticket.js +942 -0
  18. package/dist/commands/ui.d.ts +2 -0
  19. package/dist/commands/ui.js +954 -0
  20. package/dist/db/client.d.ts +4 -0
  21. package/dist/db/client.js +26 -0
  22. package/dist/db/init-schema.d.ts +2 -0
  23. package/dist/db/init-schema.js +28 -0
  24. package/dist/db/parsers.d.ts +24 -0
  25. package/dist/db/parsers.js +79 -0
  26. package/dist/db/schema.d.ts +7 -0
  27. package/dist/db/schema.js +64 -0
  28. package/dist/db/usage.d.ts +8 -0
  29. package/dist/db/usage.js +24 -0
  30. package/dist/embed/model.d.ts +5 -0
  31. package/dist/embed/model.js +34 -0
  32. package/dist/index.d.ts +2 -0
  33. package/dist/index.js +31 -0
  34. package/dist/types.d.ts +120 -0
  35. package/dist/types.js +1 -0
  36. package/dist/ui/components/index.d.ts +6 -0
  37. package/dist/ui/components/index.js +13 -0
  38. package/dist/ui/components/knowledge.d.ts +33 -0
  39. package/dist/ui/components/knowledge.js +238 -0
  40. package/dist/ui/components/layout.d.ts +1 -0
  41. package/dist/ui/components/layout.js +323 -0
  42. package/dist/ui/components/search.d.ts +15 -0
  43. package/dist/ui/components/search.js +114 -0
  44. package/dist/ui/components/spec.d.ts +11 -0
  45. package/dist/ui/components/spec.js +253 -0
  46. package/dist/ui/components/ticket.d.ts +90 -0
  47. package/dist/ui/components/ticket.js +604 -0
  48. package/dist/ui/components/utils.d.ts +26 -0
  49. package/dist/ui/components/utils.js +34 -0
  50. package/dist/ui/styles.css +2 -0
  51. package/dist/utils/cli.d.ts +21 -0
  52. package/dist/utils/cli.js +31 -0
  53. package/dist/utils/config.d.ts +12 -0
  54. package/dist/utils/config.js +116 -0
  55. package/dist/utils/id.d.ts +6 -0
  56. package/dist/utils/id.js +13 -0
  57. package/dist/utils/io.d.ts +8 -0
  58. package/dist/utils/io.js +15 -0
  59. package/package.json +60 -0
@@ -0,0 +1,647 @@
1
+ import { Command } from 'commander';
2
+ import { getClient, closeClient } from '../db/client.js';
3
+ import { parseKnowledgeRow } from '../db/parsers.js';
4
+ import { embed } from '../embed/model.js';
5
+ import { readStdin } from '../utils/io.js';
6
+ import { generateId } from '../utils/id.js';
7
+ /**
8
+ * Parse markdown knowledge format matching SKILL.md:
9
+ *
10
+ * # {Title}
11
+ *
12
+ * **Namespace:** {project-namespace}
13
+ * **Category:** architecture|pattern|truth|principle|gotcha
14
+ * **Source:** discovery|ticket|manual
15
+ * **Origin Ticket:** {ticket-id} (optional, from ticket skill)
16
+ * **Origin Ticket Type:** {ticket-type} (optional, from ticket skill)
17
+ * **Confidence:** {0.75-0.95}
18
+ * **Scope:** new-only|global|backward-compatible|legacy-frozen
19
+ * **Tags:** {kebab-case, comma-separated}
20
+ *
21
+ * ## Content
22
+ *
23
+ * {content body}
24
+ */
25
+ function parseMarkdownKnowledge(markdown) {
26
+ const lines = markdown.split('\n');
27
+ const result = {
28
+ title: '',
29
+ namespace: '',
30
+ source: 'manual',
31
+ confidence: 0.8,
32
+ scope: 'global',
33
+ content: '',
34
+ };
35
+ let inContent = false;
36
+ const contentLines = [];
37
+ for (const line of lines) {
38
+ const trimmed = line.trim();
39
+ // Parse title: # {Title}
40
+ if (trimmed.startsWith('# ') && !trimmed.startsWith('## ')) {
41
+ result.title = trimmed.substring(2).trim();
42
+ continue;
43
+ }
44
+ // Start content section — everything after ## Content is content
45
+ if (trimmed === '## Content') {
46
+ inContent = true;
47
+ continue;
48
+ }
49
+ // Collect content lines
50
+ if (inContent) {
51
+ contentLines.push(line);
52
+ continue;
53
+ }
54
+ // Parse metadata fields — **Field:** format only
55
+ if (trimmed.startsWith('**Namespace:**')) {
56
+ result.namespace = trimmed.replace('**Namespace:**', '').trim();
57
+ }
58
+ else if (trimmed.startsWith('**Category:**')) {
59
+ result.category = trimmed.replace('**Category:**', '').trim();
60
+ }
61
+ else if (trimmed.startsWith('**Source:**')) {
62
+ result.source = trimmed.replace('**Source:**', '').trim();
63
+ }
64
+ else if (trimmed.startsWith('**Origin Ticket:**')) {
65
+ result.originTicketId = trimmed.replace('**Origin Ticket:**', '').trim();
66
+ if (result.originTicketId)
67
+ result.source = 'ticket';
68
+ }
69
+ else if (trimmed.startsWith('**Origin Ticket Type:**')) {
70
+ const typeValue = trimmed.replace('**Origin Ticket Type:**', '').trim().toLowerCase();
71
+ if (['feature', 'bugfix', 'refactor', 'docs', 'chore', 'test'].includes(typeValue)) {
72
+ result.originTicketType = typeValue;
73
+ }
74
+ }
75
+ else if (trimmed.startsWith('**Confidence:**')) {
76
+ result.confidence = parseFloat(trimmed.replace('**Confidence:**', '').trim());
77
+ }
78
+ else if (trimmed.startsWith('**Scope:**')) {
79
+ result.scope = trimmed.replace('**Scope:**', '').trim();
80
+ }
81
+ else if (trimmed.startsWith('**Tags:**')) {
82
+ const tagStr = trimmed.replace('**Tags:**', '').trim();
83
+ result.tags = tagStr.split(',').map(t => t.trim()).filter(Boolean);
84
+ }
85
+ }
86
+ result.content = contentLines.join('\n').trim();
87
+ return result;
88
+ }
89
+ export const knowledgeCommand = new Command('knowledge')
90
+ .description('Manage knowledge entries');
91
+ // Create subcommand
92
+ knowledgeCommand
93
+ .command('create')
94
+ .description('Create a new knowledge entry')
95
+ .option('--stdin', 'Read markdown from stdin')
96
+ .option('--title <title>', 'Knowledge title')
97
+ .option('--content <content>', 'Knowledge content')
98
+ .option('--namespace <namespace>', 'Project namespace (use domain, not "global")')
99
+ .option('--category <category>', 'Category: pattern|truth|principle|architecture')
100
+ .option('--tags <tags...>', 'Tags (kebab-case, intent-aware)')
101
+ .option('--source <source>', 'Source: ticket|discovery|manual', 'manual')
102
+ .option('--origin <ticketId>', 'Origin ticket ID (sets origin-type to ticket)')
103
+ .option('--confidence <n>', 'Confidence 0-1 (0.7-0.8 for patterns, 1.0 for invariants)', '0.8')
104
+ .option('--scope <scope>', 'Decision scope: new-only|backward-compatible|global|legacy-frozen', 'global')
105
+ .action(async (options) => {
106
+ try {
107
+ let id;
108
+ let title;
109
+ let content;
110
+ let namespace;
111
+ let category;
112
+ let tags;
113
+ let source;
114
+ let originTicketId;
115
+ let originTicketType;
116
+ let confidence;
117
+ let scope;
118
+ if (options.stdin) {
119
+ // Parse from stdin markdown
120
+ const markdown = await readStdin();
121
+ const parsed = parseMarkdownKnowledge(markdown);
122
+ // Field-level validation
123
+ const missing = [];
124
+ if (!parsed.title)
125
+ missing.push('title: Missing # Title header');
126
+ if (!parsed.namespace)
127
+ missing.push('namespace: Missing **Namespace:** field');
128
+ if (!parsed.content)
129
+ missing.push('content: Missing ## Content section or content is empty');
130
+ if (missing.length > 0) {
131
+ const response = {
132
+ success: false,
133
+ error: missing.join('; '),
134
+ };
135
+ console.log(JSON.stringify(response));
136
+ process.exit(1);
137
+ }
138
+ id = generateId('KNOWLEDGE');
139
+ title = parsed.title;
140
+ content = parsed.content;
141
+ namespace = parsed.namespace;
142
+ category = parsed.category || null;
143
+ tags = parsed.tags || null;
144
+ source = parsed.source;
145
+ originTicketId = parsed.originTicketId || null;
146
+ originTicketType = parsed.originTicketType || null;
147
+ confidence = parsed.confidence;
148
+ scope = parsed.scope;
149
+ }
150
+ else {
151
+ // Use CLI options
152
+ if (!options.title || !options.namespace || !options.content) {
153
+ const response = {
154
+ success: false,
155
+ error: 'Required: --title, --namespace, --content (or use --stdin)',
156
+ };
157
+ console.log(JSON.stringify(response));
158
+ process.exit(1);
159
+ }
160
+ id = generateId('KNOWLEDGE');
161
+ title = options.title;
162
+ content = options.content;
163
+ namespace = options.namespace;
164
+ category = options.category || null;
165
+ tags = options.tags || null;
166
+ source = options.origin ? 'ticket' : options.source;
167
+ originTicketId = options.origin || null;
168
+ originTicketType = null; // CLI doesn't support this yet, use stdin for full control
169
+ confidence = parseFloat(options.confidence);
170
+ scope = options.scope;
171
+ }
172
+ const client = await getClient();
173
+ // Generate embedding from title + content + tags
174
+ const tagsText = tags?.length ? ' ' + tags.join(' ') : '';
175
+ const textToEmbed = `${title} ${content}${tagsText}`;
176
+ const embedding = await embed(textToEmbed);
177
+ await client.execute({
178
+ sql: `INSERT INTO knowledge (
179
+ id, namespace, chunk_index, title, content, embedding,
180
+ category, tags, source, origin_ticket_id, origin_ticket_type, confidence, active, decision_scope
181
+ ) VALUES (?, ?, 0, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, 1, ?)`,
182
+ args: [
183
+ id,
184
+ namespace,
185
+ title,
186
+ content,
187
+ JSON.stringify(embedding),
188
+ category,
189
+ tags ? JSON.stringify(tags) : null,
190
+ source,
191
+ originTicketId,
192
+ originTicketType,
193
+ confidence,
194
+ scope,
195
+ ],
196
+ });
197
+ // Bidirectional linking: update ticket's derived_knowledge
198
+ if (originTicketId) {
199
+ const ticketResult = await client.execute({
200
+ sql: 'SELECT derived_knowledge FROM tickets WHERE id = ?',
201
+ args: [originTicketId],
202
+ });
203
+ if (ticketResult.rows.length > 0) {
204
+ const row = ticketResult.rows[0];
205
+ const existing = row.derived_knowledge ? JSON.parse(row.derived_knowledge) : [];
206
+ existing.push(id);
207
+ await client.execute({
208
+ sql: 'UPDATE tickets SET derived_knowledge = ? WHERE id = ?',
209
+ args: [JSON.stringify(existing), originTicketId],
210
+ });
211
+ }
212
+ }
213
+ closeClient();
214
+ const response = {
215
+ success: true,
216
+ data: { id, namespace, source, status: 'created' },
217
+ };
218
+ console.log(JSON.stringify(response));
219
+ }
220
+ catch (error) {
221
+ const response = {
222
+ success: false,
223
+ error: `Failed to create knowledge: ${error.message}`,
224
+ };
225
+ console.log(JSON.stringify(response));
226
+ process.exit(1);
227
+ }
228
+ });
229
+ // Get subcommand
230
+ knowledgeCommand
231
+ .command('get')
232
+ .description('Get a knowledge entry by ID')
233
+ .argument('<id>', 'Knowledge ID')
234
+ .action(async (id) => {
235
+ try {
236
+ const client = await getClient();
237
+ const result = await client.execute({
238
+ sql: `SELECT id, namespace, chunk_index, title, content,
239
+ category, tags, source, origin_ticket_id, origin_ticket_type, confidence, active, decision_scope,
240
+ usage_count, last_used_at, created_at
241
+ FROM knowledge WHERE id = ?`,
242
+ args: [id],
243
+ });
244
+ closeClient();
245
+ if (result.rows.length === 0) {
246
+ const response = {
247
+ success: false,
248
+ error: `Knowledge ${id} not found`,
249
+ };
250
+ console.log(JSON.stringify(response));
251
+ process.exit(1);
252
+ }
253
+ const knowledge = parseKnowledgeRow(result.rows[0]);
254
+ const response = {
255
+ success: true,
256
+ data: knowledge,
257
+ };
258
+ console.log(JSON.stringify(response));
259
+ }
260
+ catch (error) {
261
+ const response = {
262
+ success: false,
263
+ error: `Failed to get knowledge: ${error.message}`,
264
+ };
265
+ console.log(JSON.stringify(response));
266
+ process.exit(1);
267
+ }
268
+ });
269
+ // List subcommand
270
+ knowledgeCommand
271
+ .command('list')
272
+ .description('List knowledge entries')
273
+ .option('--namespace <namespace>', 'Filter by namespace')
274
+ .option('--category <category>', 'Filter by category')
275
+ .option('--scope <scope>', 'Filter by decision scope (new-only|backward-compatible|global|legacy-frozen)')
276
+ .option('--source <source>', 'Filter by source (ticket|discovery|manual)')
277
+ .option('--status <status>', 'Filter by status (active|inactive|all)', 'active')
278
+ .option('--limit <n>', 'Limit results', '20')
279
+ .action(async (options) => {
280
+ try {
281
+ const client = await getClient();
282
+ const conditions = [];
283
+ const args = [];
284
+ if (options.status === 'active') {
285
+ conditions.push('active = 1');
286
+ }
287
+ else if (options.status === 'inactive') {
288
+ conditions.push('active = 0');
289
+ }
290
+ // 'all' = no filter on active
291
+ if (options.namespace) {
292
+ conditions.push('namespace = ?');
293
+ args.push(options.namespace);
294
+ }
295
+ if (options.category) {
296
+ conditions.push('category = ?');
297
+ args.push(options.category);
298
+ }
299
+ if (options.scope) {
300
+ conditions.push('decision_scope = ?');
301
+ args.push(options.scope);
302
+ }
303
+ if (options.source) {
304
+ conditions.push('source = ?');
305
+ args.push(options.source);
306
+ }
307
+ let sql = `SELECT id, namespace, chunk_index, title, content,
308
+ category, tags, source, origin_ticket_id, origin_ticket_type, confidence, active, decision_scope,
309
+ usage_count, last_used_at, created_at
310
+ FROM knowledge`;
311
+ if (conditions.length > 0) {
312
+ sql += ` WHERE ${conditions.join(' AND ')}`;
313
+ }
314
+ sql += ' ORDER BY created_at DESC LIMIT ?';
315
+ args.push(parseInt(options.limit, 10));
316
+ const result = await client.execute({ sql, args });
317
+ closeClient();
318
+ const knowledge = result.rows.map((row) => parseKnowledgeRow(row));
319
+ const response = {
320
+ success: true,
321
+ data: knowledge,
322
+ };
323
+ console.log(JSON.stringify(response));
324
+ }
325
+ catch (error) {
326
+ const response = {
327
+ success: false,
328
+ error: `Failed to list knowledge: ${error.message}`,
329
+ };
330
+ console.log(JSON.stringify(response));
331
+ process.exit(1);
332
+ }
333
+ });
334
+ // Update subcommand
335
+ knowledgeCommand
336
+ .command('update')
337
+ .description('Update a knowledge entry')
338
+ .argument('<id>', 'Knowledge ID')
339
+ .option('--title <title>', 'New title')
340
+ .option('--content-stdin', 'Read new content from stdin')
341
+ .option('--namespace <namespace>', 'New namespace')
342
+ .option('--category <category>', 'New category')
343
+ .option('--tags <tags...>', 'New tags')
344
+ .option('--origin <ticketId>', 'Origin ticket ID')
345
+ .option('--confidence <n>', 'Confidence score 0-1')
346
+ .option('--scope <scope>', 'Decision scope: new-only|backward-compatible|global|legacy-frozen')
347
+ .action(async (id, options) => {
348
+ try {
349
+ const client = await getClient();
350
+ // Read content from stdin — parse as full markdown if it has ## Content section
351
+ let stdinContent;
352
+ let stdinParsed;
353
+ if (options.contentStdin) {
354
+ const raw = await readStdin();
355
+ if (raw.includes('## Content')) {
356
+ // Full knowledge markdown format — parse all fields
357
+ stdinParsed = parseMarkdownKnowledge(raw);
358
+ }
359
+ else {
360
+ // Plain content text
361
+ stdinContent = raw;
362
+ }
363
+ }
364
+ // Build dynamic update
365
+ const updates = [];
366
+ const args = [];
367
+ // CLI flags take priority over parsed stdin fields
368
+ if (options.title) {
369
+ updates.push('title = ?');
370
+ args.push(options.title);
371
+ }
372
+ else if (stdinParsed?.title) {
373
+ updates.push('title = ?');
374
+ args.push(stdinParsed.title);
375
+ }
376
+ if (stdinParsed?.content) {
377
+ updates.push('content = ?');
378
+ args.push(stdinParsed.content);
379
+ }
380
+ else if (stdinContent) {
381
+ updates.push('content = ?');
382
+ args.push(stdinContent);
383
+ }
384
+ if (options.namespace) {
385
+ updates.push('namespace = ?');
386
+ args.push(options.namespace);
387
+ }
388
+ else if (stdinParsed?.namespace) {
389
+ updates.push('namespace = ?');
390
+ args.push(stdinParsed.namespace);
391
+ }
392
+ if (options.category) {
393
+ updates.push('category = ?');
394
+ args.push(options.category);
395
+ }
396
+ else if (stdinParsed?.category) {
397
+ updates.push('category = ?');
398
+ args.push(stdinParsed.category);
399
+ }
400
+ if (options.tags) {
401
+ updates.push('tags = ?');
402
+ args.push(JSON.stringify(options.tags));
403
+ }
404
+ else if (stdinParsed?.tags?.length) {
405
+ updates.push('tags = ?');
406
+ args.push(JSON.stringify(stdinParsed.tags));
407
+ }
408
+ if (options.origin) {
409
+ updates.push('origin_ticket_id = ?');
410
+ args.push(options.origin);
411
+ }
412
+ else if (stdinParsed?.originTicketId) {
413
+ updates.push('origin_ticket_id = ?');
414
+ args.push(stdinParsed.originTicketId);
415
+ }
416
+ if (options.confidence) {
417
+ updates.push('confidence = ?');
418
+ args.push(parseFloat(options.confidence));
419
+ }
420
+ else if (stdinParsed?.confidence) {
421
+ updates.push('confidence = ?');
422
+ args.push(stdinParsed.confidence);
423
+ }
424
+ if (options.scope) {
425
+ updates.push('decision_scope = ?');
426
+ args.push(options.scope);
427
+ }
428
+ else if (stdinParsed?.scope) {
429
+ updates.push('decision_scope = ?');
430
+ args.push(stdinParsed.scope);
431
+ }
432
+ if (updates.length === 0) {
433
+ const response = {
434
+ success: false,
435
+ error: 'No fields to update',
436
+ };
437
+ console.log(JSON.stringify(response));
438
+ process.exit(1);
439
+ }
440
+ // Re-generate embedding if title, content, or tags changed
441
+ if (options.title || stdinContent || stdinParsed || options.tags) {
442
+ const current = await client.execute({
443
+ sql: 'SELECT title, content, tags FROM knowledge WHERE id = ?',
444
+ args: [id],
445
+ });
446
+ if (current.rows.length > 0) {
447
+ const row = current.rows[0];
448
+ const newTitle = options.title || row.title;
449
+ const newContent = stdinContent || row.content;
450
+ const newTags = options.tags || (row.tags ? JSON.parse(row.tags) : []);
451
+ const tagsText = newTags?.length ? ' ' + newTags.join(' ') : '';
452
+ const embedding = await embed(`${newTitle} ${newContent}${tagsText}`);
453
+ updates.push('embedding = vector32(?)');
454
+ args.push(JSON.stringify(embedding));
455
+ }
456
+ }
457
+ updates.push("updated_at = datetime('now')");
458
+ args.push(id);
459
+ const sql = `UPDATE knowledge SET ${updates.join(', ')} WHERE id = ?`;
460
+ const result = await client.execute({ sql, args });
461
+ closeClient();
462
+ if (result.rowsAffected === 0) {
463
+ const response = {
464
+ success: false,
465
+ error: `Knowledge ${id} not found`,
466
+ };
467
+ console.log(JSON.stringify(response));
468
+ process.exit(1);
469
+ }
470
+ const response = {
471
+ success: true,
472
+ data: { id, status: 'updated', updated: Object.keys(options).filter(k => options[k]) },
473
+ };
474
+ console.log(JSON.stringify(response));
475
+ }
476
+ catch (error) {
477
+ const response = {
478
+ success: false,
479
+ error: `Failed to update knowledge: ${error.message}`,
480
+ };
481
+ console.log(JSON.stringify(response));
482
+ process.exit(1);
483
+ }
484
+ });
485
+ // Deactivate subcommand
486
+ knowledgeCommand
487
+ .command('deactivate')
488
+ .description('Mark a knowledge entry as inactive')
489
+ .argument('<id>', 'Knowledge ID')
490
+ .action(async (id) => {
491
+ try {
492
+ const client = await getClient();
493
+ const result = await client.execute({
494
+ sql: 'UPDATE knowledge SET active = 0 WHERE id = ?',
495
+ args: [id],
496
+ });
497
+ closeClient();
498
+ if (result.rowsAffected === 0) {
499
+ const response = {
500
+ success: false,
501
+ error: `Knowledge ${id} not found`,
502
+ };
503
+ console.log(JSON.stringify(response));
504
+ process.exit(1);
505
+ }
506
+ const response = {
507
+ success: true,
508
+ data: { id, status: 'deactivated' },
509
+ };
510
+ console.log(JSON.stringify(response));
511
+ }
512
+ catch (error) {
513
+ const response = {
514
+ success: false,
515
+ error: `Failed to deactivate knowledge: ${error.message}`,
516
+ };
517
+ console.log(JSON.stringify(response));
518
+ process.exit(1);
519
+ }
520
+ });
521
+ // Activate subcommand
522
+ knowledgeCommand
523
+ .command('activate')
524
+ .description('Mark a knowledge entry as active')
525
+ .argument('<id>', 'Knowledge ID')
526
+ .action(async (id) => {
527
+ try {
528
+ const client = await getClient();
529
+ const result = await client.execute({
530
+ sql: 'UPDATE knowledge SET active = 1 WHERE id = ?',
531
+ args: [id],
532
+ });
533
+ closeClient();
534
+ if (result.rowsAffected === 0) {
535
+ const response = {
536
+ success: false,
537
+ error: `Knowledge ${id} not found`,
538
+ };
539
+ console.log(JSON.stringify(response));
540
+ process.exit(1);
541
+ }
542
+ const response = {
543
+ success: true,
544
+ data: { id, status: 'activated' },
545
+ };
546
+ console.log(JSON.stringify(response));
547
+ }
548
+ catch (error) {
549
+ const response = {
550
+ success: false,
551
+ error: `Failed to activate knowledge: ${error.message}`,
552
+ };
553
+ console.log(JSON.stringify(response));
554
+ process.exit(1);
555
+ }
556
+ });
557
+ // Recalculate confidence subcommand
558
+ knowledgeCommand
559
+ .command('recalculate')
560
+ .description('Recalculate confidence scores based on usage patterns')
561
+ .option('--dry-run', 'Preview changes without applying')
562
+ .action(async (options) => {
563
+ try {
564
+ const client = await getClient();
565
+ // Fetch all active knowledge with usage data
566
+ const result = await client.execute({
567
+ sql: `SELECT id, title, confidence, usage_count, last_used_at, created_at
568
+ FROM knowledge WHERE active = 1`,
569
+ args: [],
570
+ });
571
+ const now = new Date();
572
+ const adjustments = [];
573
+ for (const row of result.rows) {
574
+ const id = row.id;
575
+ const title = row.title;
576
+ const currentConfidence = row.confidence;
577
+ const usageCount = row.usage_count || 0;
578
+ const lastUsedAt = row.last_used_at;
579
+ const createdAt = row.created_at;
580
+ let adjustment = 0;
581
+ const reasons = [];
582
+ // Usage-based growth
583
+ if (usageCount > 10) {
584
+ adjustment += 0.10;
585
+ reasons.push(`high usage (${usageCount}): +0.10`);
586
+ }
587
+ else if (usageCount > 5) {
588
+ adjustment += 0.05;
589
+ reasons.push(`good usage (${usageCount}): +0.05`);
590
+ }
591
+ // Staleness-based decay
592
+ const referenceDate = lastUsedAt || createdAt;
593
+ if (referenceDate) {
594
+ const daysSince = Math.floor((now.getTime() - new Date(referenceDate).getTime()) / (1000 * 60 * 60 * 24));
595
+ if (daysSince > 180) {
596
+ adjustment -= 0.20;
597
+ reasons.push(`very stale (${daysSince}d): -0.20`);
598
+ }
599
+ else if (daysSince > 90) {
600
+ adjustment -= 0.10;
601
+ reasons.push(`stale (${daysSince}d): -0.10`);
602
+ }
603
+ }
604
+ // Skip if no adjustment needed
605
+ if (adjustment === 0)
606
+ continue;
607
+ // Calculate new confidence, clamped between 0.1 and 1.0
608
+ const newConfidence = Math.max(0.1, Math.min(1.0, currentConfidence + adjustment));
609
+ // Skip if no actual change (already at bounds)
610
+ if (Math.abs(newConfidence - currentConfidence) < 0.001)
611
+ continue;
612
+ adjustments.push({
613
+ id,
614
+ title: title.slice(0, 50),
615
+ oldConfidence: currentConfidence,
616
+ newConfidence: Math.round(newConfidence * 100) / 100,
617
+ reason: reasons.join(', '),
618
+ });
619
+ // Apply update unless dry-run
620
+ if (!options.dryRun) {
621
+ await client.execute({
622
+ sql: 'UPDATE knowledge SET confidence = ? WHERE id = ?',
623
+ args: [newConfidence, id],
624
+ });
625
+ }
626
+ }
627
+ closeClient();
628
+ const response = {
629
+ success: true,
630
+ data: {
631
+ dryRun: !!options.dryRun,
632
+ total: result.rows.length,
633
+ adjusted: adjustments.length,
634
+ adjustments,
635
+ },
636
+ };
637
+ console.log(JSON.stringify(response));
638
+ }
639
+ catch (error) {
640
+ const response = {
641
+ success: false,
642
+ error: `Failed to recalculate confidence: ${error.message}`,
643
+ };
644
+ console.log(JSON.stringify(response));
645
+ process.exit(1);
646
+ }
647
+ });
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const searchCommand: Command;