@clawdactual/chitin 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 (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +318 -0
  3. package/dist/carapace/client.d.ts +41 -0
  4. package/dist/carapace/client.d.ts.map +1 -0
  5. package/dist/carapace/client.js +62 -0
  6. package/dist/carapace/client.js.map +1 -0
  7. package/dist/carapace/config.d.ts +9 -0
  8. package/dist/carapace/config.d.ts.map +1 -0
  9. package/dist/carapace/config.js +24 -0
  10. package/dist/carapace/config.js.map +1 -0
  11. package/dist/carapace/index.d.ts +5 -0
  12. package/dist/carapace/index.d.ts.map +1 -0
  13. package/dist/carapace/index.js +3 -0
  14. package/dist/carapace/index.js.map +1 -0
  15. package/dist/carapace/mapper.d.ts +71 -0
  16. package/dist/carapace/mapper.d.ts.map +1 -0
  17. package/dist/carapace/mapper.js +83 -0
  18. package/dist/carapace/mapper.js.map +1 -0
  19. package/dist/db/embeddings.d.ts +12 -0
  20. package/dist/db/embeddings.d.ts.map +1 -0
  21. package/dist/db/embeddings.js +58 -0
  22. package/dist/db/embeddings.js.map +1 -0
  23. package/dist/db/history.d.ts +52 -0
  24. package/dist/db/history.d.ts.map +1 -0
  25. package/dist/db/history.js +99 -0
  26. package/dist/db/history.js.map +1 -0
  27. package/dist/db/repository.d.ts +64 -0
  28. package/dist/db/repository.d.ts.map +1 -0
  29. package/dist/db/repository.js +298 -0
  30. package/dist/db/repository.js.map +1 -0
  31. package/dist/db/schema.d.ts +5 -0
  32. package/dist/db/schema.d.ts.map +1 -0
  33. package/dist/db/schema.js +68 -0
  34. package/dist/db/schema.js.map +1 -0
  35. package/dist/engine/conflicts.d.ts +52 -0
  36. package/dist/engine/conflicts.d.ts.map +1 -0
  37. package/dist/engine/conflicts.js +232 -0
  38. package/dist/engine/conflicts.js.map +1 -0
  39. package/dist/engine/context-detect.d.ts +17 -0
  40. package/dist/engine/context-detect.d.ts.map +1 -0
  41. package/dist/engine/context-detect.js +132 -0
  42. package/dist/engine/context-detect.js.map +1 -0
  43. package/dist/engine/marshal.d.ts +19 -0
  44. package/dist/engine/marshal.d.ts.map +1 -0
  45. package/dist/engine/marshal.js +74 -0
  46. package/dist/engine/marshal.js.map +1 -0
  47. package/dist/engine/retrieve.d.ts +36 -0
  48. package/dist/engine/retrieve.d.ts.map +1 -0
  49. package/dist/engine/retrieve.js +45 -0
  50. package/dist/engine/retrieve.js.map +1 -0
  51. package/dist/index.d.ts +3 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +769 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/types.d.ts +60 -0
  56. package/dist/types.d.ts.map +1 -0
  57. package/dist/types.js +2 -0
  58. package/dist/types.js.map +1 -0
  59. package/package.json +58 -0
  60. package/seed.json +45 -0
package/dist/index.js ADDED
@@ -0,0 +1,769 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { initDatabase, closeDatabase } from './db/schema.js';
4
+ import { InsightRepository } from './db/repository.js';
5
+ import { EmbeddingStore } from './db/embeddings.js';
6
+ import { RetrievalEngine } from './engine/retrieve.js';
7
+ import { InsightHistory } from './db/history.js';
8
+ import { marshal, estimateTokens } from './engine/marshal.js';
9
+ import { detectContext } from './engine/context-detect.js';
10
+ import { loadCarapaceConfig } from './carapace/config.js';
11
+ import { CarapaceClient, CarapaceError } from './carapace/client.js';
12
+ import { mapInsightToContribution, mapContributionToInsight, isPromotable } from './carapace/mapper.js';
13
+ import path from 'node:path';
14
+ import os from 'node:os';
15
+ import fs from 'node:fs';
16
+ const DEFAULT_DB_DIR = path.join(os.homedir(), '.config', 'chitin');
17
+ const DEFAULT_DB_PATH = path.join(DEFAULT_DB_DIR, 'insights.db');
18
+ function ensureDbDir(dbPath) {
19
+ const dir = path.dirname(dbPath);
20
+ if (!fs.existsSync(dir)) {
21
+ fs.mkdirSync(dir, { recursive: true });
22
+ }
23
+ }
24
+ function getDb(dbPath) {
25
+ ensureDbDir(dbPath);
26
+ initDatabase(dbPath);
27
+ return {
28
+ repo: new InsightRepository(),
29
+ embeddings: new EmbeddingStore(),
30
+ };
31
+ }
32
+ const program = new Command();
33
+ program
34
+ .name('chitin')
35
+ .description('Personality persistence layer for AI agents. Structured insights about how you think, not what you remember.')
36
+ .version('0.1.0')
37
+ .option('--db <path>', 'Database path', DEFAULT_DB_PATH);
38
+ // === contribute ===
39
+ program
40
+ .command('contribute')
41
+ .description('Add a new insight')
42
+ .requiredOption('--type <type>', 'Insight type: behavioral | personality | relational | principle | skill')
43
+ .requiredOption('--claim <text>', 'The core insight')
44
+ .option('--reasoning <text>', 'How you arrived at this')
45
+ .option('--context <text>', 'When this applies')
46
+ .option('--limitations <text>', 'When this breaks down')
47
+ .requiredOption('--confidence <number>', 'Confidence level 0.0-1.0')
48
+ .option('--tags <tags>', 'Comma-separated tags')
49
+ .option('--source <text>', 'What experience led to this')
50
+ .option('--json', 'Input from JSON stdin')
51
+ .option('--force', 'Skip conflict detection')
52
+ .option('--format <fmt>', 'Output format: json | human', 'human')
53
+ .action((opts) => {
54
+ const dbPath = program.opts().db;
55
+ const { repo } = getDb(dbPath);
56
+ try {
57
+ const input = opts.json
58
+ ? JSON.parse(fs.readFileSync('/dev/stdin', 'utf-8'))
59
+ : {
60
+ type: opts.type,
61
+ claim: opts.claim,
62
+ reasoning: opts.reasoning,
63
+ context: opts.context,
64
+ limitations: opts.limitations,
65
+ confidence: parseFloat(opts.confidence),
66
+ tags: opts.tags ? opts.tags.split(',').map((t) => t.trim()) : [],
67
+ source: opts.source,
68
+ };
69
+ const result = repo.contributeWithCheck(input, { force: !!opts.force });
70
+ if (opts.format === 'json') {
71
+ console.log(JSON.stringify({
72
+ insight: result.insight,
73
+ conflicts: result.conflicts.map(c => ({
74
+ id: c.insight.id,
75
+ type: c.insight.type,
76
+ claim: c.insight.claim,
77
+ similarity: c.similarity,
78
+ tensionScore: c.tensionScore,
79
+ tensionReason: c.tensionReason,
80
+ conflictScore: c.conflictScore,
81
+ })),
82
+ }, null, 2));
83
+ }
84
+ else {
85
+ console.log(`✓ Contributed ${result.insight.type} insight: ${result.insight.id}`);
86
+ console.log(` "${result.insight.claim}"`);
87
+ console.log(` confidence: ${result.insight.confidence} | tags: ${result.insight.tags.join(', ') || '(none)'}`);
88
+ if (result.conflicts.length > 0) {
89
+ console.log('');
90
+ console.log(`⚠ ${result.conflicts.length} potential conflict(s) detected:`);
91
+ for (const c of result.conflicts) {
92
+ const pct = (c.conflictScore * 100).toFixed(0);
93
+ console.log('');
94
+ console.log(` [${c.insight.type}] "${c.insight.claim}"`);
95
+ console.log(` conflict: ${pct}% | tension: ${c.tensionReason}`);
96
+ console.log(` id: ${c.insight.id}`);
97
+ }
98
+ console.log('');
99
+ console.log(' Consider: chitin merge, chitin update, or chitin archive to resolve.');
100
+ }
101
+ }
102
+ }
103
+ finally {
104
+ closeDatabase();
105
+ }
106
+ });
107
+ // === get ===
108
+ program
109
+ .command('get <id>')
110
+ .description('Get a specific insight by ID')
111
+ .option('--format <fmt>', 'Output format: json | human', 'human')
112
+ .action((id, opts) => {
113
+ const dbPath = program.opts().db;
114
+ const { repo } = getDb(dbPath);
115
+ try {
116
+ const insight = repo.get(id);
117
+ if (!insight) {
118
+ console.error(`Insight not found: ${id}`);
119
+ process.exit(1);
120
+ }
121
+ if (opts.format === 'json') {
122
+ console.log(JSON.stringify(insight, null, 2));
123
+ }
124
+ else {
125
+ console.log(`[${insight.type}] ${insight.claim}`);
126
+ if (insight.reasoning)
127
+ console.log(` Reasoning: ${insight.reasoning}`);
128
+ if (insight.context)
129
+ console.log(` Context: ${insight.context}`);
130
+ if (insight.limitations)
131
+ console.log(` Limitations: ${insight.limitations}`);
132
+ console.log(` Confidence: ${insight.confidence} | Reinforced: ${insight.reinforcementCount}x`);
133
+ console.log(` Tags: ${insight.tags.join(', ') || '(none)'}`);
134
+ console.log(` ID: ${insight.id}`);
135
+ }
136
+ }
137
+ finally {
138
+ closeDatabase();
139
+ }
140
+ });
141
+ // === update ===
142
+ program
143
+ .command('update <id>')
144
+ .description('Update an existing insight')
145
+ .option('--claim <text>', 'Updated claim')
146
+ .option('--reasoning <text>', 'Updated reasoning')
147
+ .option('--context <text>', 'Updated context')
148
+ .option('--limitations <text>', 'Updated limitations')
149
+ .option('--confidence <number>', 'Updated confidence')
150
+ .option('--tags <tags>', 'Updated comma-separated tags')
151
+ .option('--source <text>', 'Updated source')
152
+ .option('--format <fmt>', 'Output format: json | human', 'human')
153
+ .action((id, opts) => {
154
+ const dbPath = program.opts().db;
155
+ const { repo } = getDb(dbPath);
156
+ try {
157
+ const updates = {};
158
+ if (opts.claim)
159
+ updates.claim = opts.claim;
160
+ if (opts.reasoning)
161
+ updates.reasoning = opts.reasoning;
162
+ if (opts.context)
163
+ updates.context = opts.context;
164
+ if (opts.limitations)
165
+ updates.limitations = opts.limitations;
166
+ if (opts.confidence)
167
+ updates.confidence = parseFloat(opts.confidence);
168
+ if (opts.tags)
169
+ updates.tags = opts.tags.split(',').map((t) => t.trim());
170
+ if (opts.source)
171
+ updates.source = opts.source;
172
+ const insight = repo.update(id, updates);
173
+ if (opts.format === 'json') {
174
+ console.log(JSON.stringify(insight, null, 2));
175
+ }
176
+ else {
177
+ console.log(`✓ Updated insight: ${insight.id}`);
178
+ console.log(` "${insight.claim}"`);
179
+ }
180
+ }
181
+ finally {
182
+ closeDatabase();
183
+ }
184
+ });
185
+ // === reinforce ===
186
+ program
187
+ .command('reinforce <id>')
188
+ .description('Bump reinforcement count for an insight')
189
+ .action((id) => {
190
+ const dbPath = program.opts().db;
191
+ const { repo } = getDb(dbPath);
192
+ try {
193
+ const insight = repo.reinforce(id);
194
+ console.log(`✓ Reinforced: ${insight.claim}`);
195
+ console.log(` Count: ${insight.reinforcementCount}`);
196
+ }
197
+ finally {
198
+ closeDatabase();
199
+ }
200
+ });
201
+ // === archive ===
202
+ program
203
+ .command('archive <id>')
204
+ .description('Remove an insight (soft delete)')
205
+ .action((id) => {
206
+ const dbPath = program.opts().db;
207
+ const { repo } = getDb(dbPath);
208
+ try {
209
+ repo.archive(id);
210
+ console.log(`✓ Archived insight: ${id}`);
211
+ }
212
+ finally {
213
+ closeDatabase();
214
+ }
215
+ });
216
+ // === list ===
217
+ program
218
+ .command('list')
219
+ .description('List insights with optional filters')
220
+ .option('--type <types>', 'Filter by type (comma-separated)')
221
+ .option('--tags <tags>', 'Filter by tags (comma-separated)')
222
+ .option('--min-confidence <number>', 'Minimum confidence threshold')
223
+ .option('--format <fmt>', 'Output format: json | human', 'human')
224
+ .action((opts) => {
225
+ const dbPath = program.opts().db;
226
+ const { repo } = getDb(dbPath);
227
+ try {
228
+ const options = {};
229
+ if (opts.type)
230
+ options.types = opts.type.split(',').map((t) => t.trim());
231
+ if (opts.tags)
232
+ options.tags = opts.tags.split(',').map((t) => t.trim());
233
+ if (opts.minConfidence)
234
+ options.minConfidence = parseFloat(opts.minConfidence);
235
+ const insights = repo.list(options);
236
+ if (opts.format === 'json') {
237
+ console.log(JSON.stringify(insights, null, 2));
238
+ }
239
+ else {
240
+ if (insights.length === 0) {
241
+ console.log('No insights found.');
242
+ return;
243
+ }
244
+ for (const i of insights) {
245
+ const tags = i.tags.length > 0 ? ` [${i.tags.join(', ')}]` : '';
246
+ const reinforced = i.reinforcementCount > 0 ? ` (${i.reinforcementCount}×)` : '';
247
+ console.log(` [${i.type}] ${i.claim}${tags}${reinforced}`);
248
+ console.log(` confidence: ${i.confidence} | id: ${i.id}`);
249
+ }
250
+ console.log(`\n${insights.length} insight(s)`);
251
+ }
252
+ }
253
+ finally {
254
+ closeDatabase();
255
+ }
256
+ });
257
+ // === stats ===
258
+ program
259
+ .command('stats')
260
+ .description('Show insight statistics')
261
+ .option('--format <fmt>', 'Output format: json | human', 'human')
262
+ .action((opts) => {
263
+ const dbPath = program.opts().db;
264
+ const { repo } = getDb(dbPath);
265
+ try {
266
+ const stats = repo.stats();
267
+ if (opts.format === 'json') {
268
+ console.log(JSON.stringify(stats, null, 2));
269
+ }
270
+ else {
271
+ console.log('Chitin Insights');
272
+ console.log(` Total: ${stats.total}`);
273
+ console.log(` Average confidence: ${stats.averageConfidence.toFixed(2)}`);
274
+ console.log(' By type:');
275
+ for (const [type, count] of Object.entries(stats.byType)) {
276
+ if (count > 0)
277
+ console.log(` ${type}: ${count}`);
278
+ }
279
+ }
280
+ }
281
+ finally {
282
+ closeDatabase();
283
+ }
284
+ });
285
+ // === history ===
286
+ program
287
+ .command('history <id>')
288
+ .description('View the evolution history of an insight')
289
+ .option('--limit <number>', 'Maximum entries to show')
290
+ .option('--format <fmt>', 'Output format: json | human', 'human')
291
+ .action((id, opts) => {
292
+ const dbPath = program.opts().db;
293
+ const { repo } = getDb(dbPath);
294
+ try {
295
+ const insight = repo.get(id);
296
+ if (!insight) {
297
+ console.error(`Insight not found: ${id}`);
298
+ process.exit(1);
299
+ }
300
+ const history = new InsightHistory();
301
+ const entries = history.getHistory(id, {
302
+ limit: opts.limit ? parseInt(opts.limit) : undefined,
303
+ });
304
+ if (opts.format === 'json') {
305
+ console.log(JSON.stringify({ insight, history: entries }, null, 2));
306
+ return;
307
+ }
308
+ console.log(`[${insight.type}] "${insight.claim}"`);
309
+ console.log(` Current: confidence ${insight.confidence} | ${insight.reinforcementCount}× reinforced\n`);
310
+ if (entries.length === 0) {
311
+ console.log(' No history recorded.');
312
+ return;
313
+ }
314
+ for (const entry of entries) {
315
+ const time = entry.changedAt;
316
+ const source = entry.source ? ` (${entry.source})` : '';
317
+ switch (entry.changeType) {
318
+ case 'create':
319
+ console.log(` ${time} [create] "${entry.newValue}"`);
320
+ break;
321
+ case 'reinforce':
322
+ console.log(` ${time} [reinforce] ${entry.field}: ${entry.oldValue} → ${entry.newValue}${source}`);
323
+ break;
324
+ case 'update':
325
+ console.log(` ${time} [update] ${entry.field}: "${entry.oldValue}" → "${entry.newValue}"`);
326
+ break;
327
+ case 'merge':
328
+ console.log(` ${time} [merge] ${entry.field}: "${entry.oldValue}" → "${entry.newValue}"${source}`);
329
+ break;
330
+ }
331
+ }
332
+ console.log(`\n ${entries.length} event(s)`);
333
+ }
334
+ finally {
335
+ closeDatabase();
336
+ }
337
+ });
338
+ // === changelog ===
339
+ program
340
+ .command('changelog')
341
+ .description('View recent changes across all insights')
342
+ .option('--days <number>', 'Limit to last N days')
343
+ .option('--limit <number>', 'Maximum entries', '20')
344
+ .option('--format <fmt>', 'Output format: json | human', 'human')
345
+ .action((opts) => {
346
+ const dbPath = program.opts().db;
347
+ getDb(dbPath); // init db
348
+ try {
349
+ const history = new InsightHistory();
350
+ const entries = history.getChangelog({
351
+ days: opts.days ? parseInt(opts.days) : undefined,
352
+ limit: parseInt(opts.limit),
353
+ });
354
+ if (opts.format === 'json') {
355
+ console.log(JSON.stringify(entries, null, 2));
356
+ return;
357
+ }
358
+ if (entries.length === 0) {
359
+ console.log('No recent changes.');
360
+ return;
361
+ }
362
+ console.log(`Recent changes (${entries.length}):\n`);
363
+ for (const entry of entries) {
364
+ const time = entry.changedAt;
365
+ const source = entry.source ? ` (${entry.source})` : '';
366
+ const idShort = entry.insightId.slice(0, 8);
367
+ switch (entry.changeType) {
368
+ case 'create':
369
+ console.log(` ${time} [create] ${idShort}… "${entry.newValue}"`);
370
+ break;
371
+ case 'reinforce':
372
+ console.log(` ${time} [reinforce] ${idShort}… ${entry.oldValue} → ${entry.newValue}${source}`);
373
+ break;
374
+ case 'update':
375
+ console.log(` ${time} [update] ${idShort}… ${entry.field}: "${entry.oldValue}" → "${entry.newValue}"`);
376
+ break;
377
+ case 'merge':
378
+ console.log(` ${time} [merge] ${idShort}… ${entry.field}${source}`);
379
+ break;
380
+ }
381
+ }
382
+ }
383
+ finally {
384
+ closeDatabase();
385
+ }
386
+ });
387
+ // === reflect ===
388
+ program
389
+ .command('reflect')
390
+ .description('Review pending session reflections and current insight state')
391
+ .option('--pending-path <path>', 'Path to pending-reflection.json', path.join(DEFAULT_DB_DIR, 'pending-reflection.json'))
392
+ .option('--clear', 'Clear pending reflections after display')
393
+ .option('--format <fmt>', 'Output format: json | human', 'human')
394
+ .action((opts) => {
395
+ const dbPath = program.opts().db;
396
+ const { repo } = getDb(dbPath);
397
+ try {
398
+ // Read pending reflections
399
+ let pending = [];
400
+ try {
401
+ if (fs.existsSync(opts.pendingPath)) {
402
+ pending = JSON.parse(fs.readFileSync(opts.pendingPath, 'utf-8'));
403
+ }
404
+ }
405
+ catch {
406
+ pending = [];
407
+ }
408
+ const stats = repo.stats();
409
+ if (opts.format === 'json') {
410
+ console.log(JSON.stringify({ pending, stats }, null, 2));
411
+ if (opts.clear && pending.length > 0) {
412
+ fs.writeFileSync(opts.pendingPath, '[]');
413
+ }
414
+ return;
415
+ }
416
+ // Human format
417
+ if (pending.length === 0) {
418
+ console.log('No pending reflections.');
419
+ }
420
+ else {
421
+ console.log(`${pending.length} pending reflection(s):\n`);
422
+ for (const entry of pending) {
423
+ const time = entry.timestamp ? new Date(entry.timestamp).toLocaleString() : 'unknown';
424
+ console.log(` • ${entry.sessionKey} (${entry.reason}) — ${time}`);
425
+ }
426
+ }
427
+ console.log(`\nCurrent state: ${stats.total} insight(s), avg confidence ${stats.averageConfidence.toFixed(2)}`);
428
+ if (opts.clear && pending.length > 0) {
429
+ fs.writeFileSync(opts.pendingPath, '[]');
430
+ console.log('\n✓ Cleared pending reflections.');
431
+ }
432
+ }
433
+ finally {
434
+ closeDatabase();
435
+ }
436
+ });
437
+ // === similar ===
438
+ program
439
+ .command('similar <claim>')
440
+ .description('Find insights similar to a given claim')
441
+ .option('--threshold <number>', 'Minimum similarity (0-1)', '0.2')
442
+ .option('--format <fmt>', 'Output format: json | human', 'human')
443
+ .action((claim, opts) => {
444
+ const dbPath = program.opts().db;
445
+ const { repo } = getDb(dbPath);
446
+ try {
447
+ const threshold = parseFloat(opts.threshold);
448
+ const results = repo.findSimilar(claim, threshold);
449
+ if (opts.format === 'json') {
450
+ console.log(JSON.stringify(results.map(r => ({
451
+ id: r.insight.id,
452
+ type: r.insight.type,
453
+ claim: r.insight.claim,
454
+ similarity: r.similarity,
455
+ })), null, 2));
456
+ return;
457
+ }
458
+ if (results.length === 0) {
459
+ console.log('No similar insights found.');
460
+ return;
461
+ }
462
+ console.log(`Found ${results.length} similar insight(s):\n`);
463
+ for (const r of results) {
464
+ const pct = (r.similarity * 100).toFixed(0);
465
+ console.log(` [${pct}%] [${r.insight.type}] ${r.insight.claim}`);
466
+ console.log(` id: ${r.insight.id}`);
467
+ }
468
+ }
469
+ finally {
470
+ closeDatabase();
471
+ }
472
+ });
473
+ // === merge ===
474
+ program
475
+ .command('merge <sourceId> <targetId>')
476
+ .description('Merge source insight into target (source is deleted)')
477
+ .option('--claim <text>', 'Override the merged claim')
478
+ .option('--format <fmt>', 'Output format: json | human', 'human')
479
+ .action((sourceId, targetId, opts) => {
480
+ const dbPath = program.opts().db;
481
+ const { repo } = getDb(dbPath);
482
+ try {
483
+ const mergeOpts = {};
484
+ if (opts.claim)
485
+ mergeOpts.claim = opts.claim;
486
+ const merged = repo.merge(sourceId, targetId, mergeOpts);
487
+ if (opts.format === 'json') {
488
+ console.log(JSON.stringify(merged, null, 2));
489
+ }
490
+ else {
491
+ console.log(`✓ Merged into: ${merged.id}`);
492
+ console.log(` "${merged.claim}"`);
493
+ console.log(` confidence: ${merged.confidence} | reinforced: ${merged.reinforcementCount}×`);
494
+ console.log(` tags: ${merged.tags.join(', ') || '(none)'}`);
495
+ }
496
+ }
497
+ finally {
498
+ closeDatabase();
499
+ }
500
+ });
501
+ // === retrieve ===
502
+ program
503
+ .command('retrieve')
504
+ .description('Get relevant personality context for a query')
505
+ .requiredOption('--query <text>', 'The incoming context/message')
506
+ .option('--budget <number>', 'Token budget for output', '2000')
507
+ .option('--max-results <number>', 'Maximum insights to consider', '15')
508
+ .option('--format <fmt>', 'Output format: compact | json | full', 'compact')
509
+ .action((opts) => {
510
+ const dbPath = program.opts().db;
511
+ const { repo, embeddings } = getDb(dbPath);
512
+ try {
513
+ const engine = new RetrievalEngine(repo, embeddings);
514
+ const context = detectContext(opts.query);
515
+ // For now, without real embeddings, fall back to listing by type boost
516
+ // TODO: integrate real embedding generation
517
+ const allInsights = repo.list();
518
+ if (allInsights.length === 0) {
519
+ if (opts.format === 'json') {
520
+ console.log(JSON.stringify({ insights: [], context: '', tokenEstimate: 0 }));
521
+ }
522
+ else {
523
+ console.log('No insights stored yet. Use `chitin contribute` to add some.');
524
+ }
525
+ return;
526
+ }
527
+ // Check if we have embeddings
528
+ const missing = embeddings.findMissingEmbeddings();
529
+ const hasEmbeddings = missing.length < allInsights.length;
530
+ let scoredInsights;
531
+ if (hasEmbeddings) {
532
+ // Use semantic retrieval (need query embedding — placeholder for now)
533
+ // TODO: generate real embedding for query
534
+ console.error('Note: Real embedding generation not yet wired. Using type-boosted fallback.');
535
+ }
536
+ // Fallback: score all insights using type boosts and confidence
537
+ scoredInsights = allInsights.map(insight => {
538
+ const typeBoost = context.typeBoosts[insight.type] ?? 1.0;
539
+ const reinforcementFactor = Math.log2(insight.reinforcementCount + 2);
540
+ const score = insight.confidence * reinforcementFactor * typeBoost;
541
+ return { insight, similarity: 1.0, score };
542
+ });
543
+ scoredInsights.sort((a, b) => b.score - a.score);
544
+ scoredInsights = scoredInsights.slice(0, parseInt(opts.maxResults));
545
+ const budget = parseInt(opts.budget);
546
+ if (opts.format === 'json') {
547
+ const output = marshal(scoredInsights, { tokenBudget: budget });
548
+ console.log(JSON.stringify({
549
+ category: context.category,
550
+ insights: scoredInsights.map(s => ({
551
+ id: s.insight.id,
552
+ type: s.insight.type,
553
+ claim: s.insight.claim,
554
+ score: s.score,
555
+ })),
556
+ context: output,
557
+ tokenEstimate: estimateTokens(output),
558
+ }, null, 2));
559
+ }
560
+ else if (opts.format === 'full') {
561
+ const output = marshal(scoredInsights, { tokenBudget: budget, format: 'full', includeContext: true });
562
+ console.log(output);
563
+ }
564
+ else {
565
+ const output = marshal(scoredInsights, { tokenBudget: budget });
566
+ console.log(output);
567
+ }
568
+ }
569
+ finally {
570
+ closeDatabase();
571
+ }
572
+ });
573
+ // === export ===
574
+ program
575
+ .command('export')
576
+ .description('Export all insights as JSON')
577
+ .action(() => {
578
+ const dbPath = program.opts().db;
579
+ const { repo } = getDb(dbPath);
580
+ try {
581
+ const insights = repo.list();
582
+ console.log(JSON.stringify(insights, null, 2));
583
+ }
584
+ finally {
585
+ closeDatabase();
586
+ }
587
+ });
588
+ // === import ===
589
+ program
590
+ .command('import <file>')
591
+ .description('Import insights from a JSON file')
592
+ .option('--merge', 'Merge with existing (skip duplicates by claim)')
593
+ .action((file, opts) => {
594
+ const dbPath = program.opts().db;
595
+ const { repo } = getDb(dbPath);
596
+ try {
597
+ const data = JSON.parse(fs.readFileSync(file, 'utf-8'));
598
+ const insights = Array.isArray(data) ? data : [data];
599
+ let imported = 0;
600
+ let skipped = 0;
601
+ const existing = opts.merge ? new Set(repo.list().map(i => i.claim)) : new Set();
602
+ for (const item of insights) {
603
+ if (opts.merge && existing.has(item.claim)) {
604
+ skipped++;
605
+ continue;
606
+ }
607
+ repo.contribute({
608
+ type: item.type,
609
+ claim: item.claim,
610
+ reasoning: item.reasoning,
611
+ context: item.context,
612
+ limitations: item.limitations,
613
+ confidence: item.confidence ?? 0.5,
614
+ tags: item.tags ?? [],
615
+ source: item.source,
616
+ });
617
+ imported++;
618
+ }
619
+ console.log(`✓ Imported ${imported} insight(s)${skipped > 0 ? `, skipped ${skipped} duplicate(s)` : ''}`);
620
+ }
621
+ finally {
622
+ closeDatabase();
623
+ }
624
+ });
625
+ // === promote ===
626
+ program
627
+ .command('promote <id>')
628
+ .description('Promote a Chitin insight to Carapace (shared knowledge base)')
629
+ .option('--domain-tags <tags>', 'Override domain tags (comma-separated)')
630
+ .option('--force', 'Skip promotability checks')
631
+ .option('--carapace-config <path>', 'Path to Carapace credentials JSON')
632
+ .option('--format <fmt>', 'Output format: json | human', 'human')
633
+ .action(async (id, opts) => {
634
+ const dbPath = program.opts().db;
635
+ const { repo } = getDb(dbPath);
636
+ try {
637
+ const insight = repo.get(id);
638
+ if (!insight) {
639
+ console.error(`Insight not found: ${id}`);
640
+ process.exit(1);
641
+ }
642
+ // Check promotability
643
+ const check = isPromotable(insight, { force: !!opts.force });
644
+ if (!check.promotable) {
645
+ console.error('⚠ Insight is not promotable:');
646
+ for (const reason of check.reasons) {
647
+ console.error(` - ${reason}`);
648
+ }
649
+ console.error('\nUse --force to override.');
650
+ process.exit(1);
651
+ }
652
+ if (check.reasons.length > 0 && opts.force) {
653
+ console.warn('⚠ Promoting with warnings:');
654
+ for (const reason of check.reasons) {
655
+ console.warn(` - ${reason}`);
656
+ }
657
+ console.warn('');
658
+ }
659
+ // Load Carapace config and create client
660
+ const carapaceConfig = loadCarapaceConfig(opts.carapaceConfig);
661
+ const client = new CarapaceClient({ apiKey: carapaceConfig.apiKey });
662
+ // Map insight to contribution
663
+ const domainTags = opts.domainTags
664
+ ? opts.domainTags.split(',').map((t) => t.trim())
665
+ : undefined;
666
+ const contribution = mapInsightToContribution(insight, { domainTags });
667
+ // Submit to Carapace
668
+ const result = await client.contribute(contribution);
669
+ // Update insight source to track the promotion
670
+ const carapaceId = result.id;
671
+ if (carapaceId) {
672
+ repo.update(id, { source: `carapace:${carapaceId}` });
673
+ }
674
+ if (opts.format === 'json') {
675
+ console.log(JSON.stringify({ chitin: { id: insight.id }, carapace: result }, null, 2));
676
+ }
677
+ else {
678
+ console.log(`✓ Promoted to Carapace: ${carapaceId ?? 'unknown'}`);
679
+ console.log(` "${insight.claim.slice(0, 100)}${insight.claim.length > 100 ? '...' : ''}"`);
680
+ console.log(` Chitin ID: ${insight.id}`);
681
+ console.log(` Carapace ID: ${carapaceId ?? 'unknown'}`);
682
+ const recs = result.recommendations;
683
+ if (recs?.related?.length) {
684
+ console.log(` Related: ${recs.related.length} similar insight(s) on Carapace`);
685
+ }
686
+ }
687
+ }
688
+ catch (e) {
689
+ if (e instanceof CarapaceError) {
690
+ console.error(`Carapace error: ${e.message} (${e.code})`);
691
+ process.exit(1);
692
+ }
693
+ throw e;
694
+ }
695
+ finally {
696
+ closeDatabase();
697
+ }
698
+ });
699
+ // === import-carapace ===
700
+ program
701
+ .command('import-carapace <contribution-id>')
702
+ .description('Import a Carapace contribution as a personal Chitin insight')
703
+ .option('--type <type>', 'Insight type (default: skill)', 'skill')
704
+ .option('--carapace-config <path>', 'Path to Carapace credentials JSON')
705
+ .option('--force', 'Skip conflict detection')
706
+ .option('--format <fmt>', 'Output format: json | human', 'human')
707
+ .action(async (contributionId, opts) => {
708
+ const dbPath = program.opts().db;
709
+ const { repo } = getDb(dbPath);
710
+ try {
711
+ // Load Carapace config and create client
712
+ const carapaceConfig = loadCarapaceConfig(opts.carapaceConfig);
713
+ const client = new CarapaceClient({ apiKey: carapaceConfig.apiKey });
714
+ // Fetch contribution from Carapace
715
+ const contribution = await client.get(contributionId);
716
+ // Check if already imported
717
+ const existing = repo.list();
718
+ const alreadyImported = existing.find(i => i.source === `carapace:${contributionId}`);
719
+ if (alreadyImported) {
720
+ console.error(`Already imported as Chitin insight: ${alreadyImported.id}`);
721
+ process.exit(1);
722
+ }
723
+ // Map to Chitin insight
724
+ const type = opts.type;
725
+ const input = mapContributionToInsight(contribution, { type });
726
+ // Contribute locally
727
+ const result = repo.contributeWithCheck(input, { force: !!opts.force });
728
+ if (opts.format === 'json') {
729
+ console.log(JSON.stringify({
730
+ carapace: { id: contributionId },
731
+ chitin: result.insight,
732
+ conflicts: result.conflicts.length,
733
+ }, null, 2));
734
+ }
735
+ else {
736
+ console.log(`✓ Imported from Carapace: ${result.insight.id}`);
737
+ console.log(` "${result.insight.claim.slice(0, 100)}${result.insight.claim.length > 100 ? '...' : ''}"`);
738
+ console.log(` Type: ${result.insight.type} | Confidence: ${result.insight.confidence}`);
739
+ console.log(` Source: carapace:${contributionId}`);
740
+ if (result.conflicts.length > 0) {
741
+ console.log('');
742
+ console.log(`⚠ ${result.conflicts.length} potential conflict(s) with existing insights.`);
743
+ }
744
+ }
745
+ }
746
+ catch (e) {
747
+ if (e instanceof CarapaceError) {
748
+ console.error(`Carapace error: ${e.message} (${e.code})`);
749
+ process.exit(1);
750
+ }
751
+ throw e;
752
+ }
753
+ finally {
754
+ closeDatabase();
755
+ }
756
+ });
757
+ // === init ===
758
+ program
759
+ .command('init')
760
+ .description('Initialize the database')
761
+ .action(() => {
762
+ const dbPath = program.opts().db;
763
+ ensureDbDir(dbPath);
764
+ initDatabase(dbPath);
765
+ console.log(`✓ Database initialized at ${dbPath}`);
766
+ closeDatabase();
767
+ });
768
+ program.parse();
769
+ //# sourceMappingURL=index.js.map