@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.
- package/LICENSE +21 -0
- package/README.md +318 -0
- package/dist/carapace/client.d.ts +41 -0
- package/dist/carapace/client.d.ts.map +1 -0
- package/dist/carapace/client.js +62 -0
- package/dist/carapace/client.js.map +1 -0
- package/dist/carapace/config.d.ts +9 -0
- package/dist/carapace/config.d.ts.map +1 -0
- package/dist/carapace/config.js +24 -0
- package/dist/carapace/config.js.map +1 -0
- package/dist/carapace/index.d.ts +5 -0
- package/dist/carapace/index.d.ts.map +1 -0
- package/dist/carapace/index.js +3 -0
- package/dist/carapace/index.js.map +1 -0
- package/dist/carapace/mapper.d.ts +71 -0
- package/dist/carapace/mapper.d.ts.map +1 -0
- package/dist/carapace/mapper.js +83 -0
- package/dist/carapace/mapper.js.map +1 -0
- package/dist/db/embeddings.d.ts +12 -0
- package/dist/db/embeddings.d.ts.map +1 -0
- package/dist/db/embeddings.js +58 -0
- package/dist/db/embeddings.js.map +1 -0
- package/dist/db/history.d.ts +52 -0
- package/dist/db/history.d.ts.map +1 -0
- package/dist/db/history.js +99 -0
- package/dist/db/history.js.map +1 -0
- package/dist/db/repository.d.ts +64 -0
- package/dist/db/repository.d.ts.map +1 -0
- package/dist/db/repository.js +298 -0
- package/dist/db/repository.js.map +1 -0
- package/dist/db/schema.d.ts +5 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +68 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/engine/conflicts.d.ts +52 -0
- package/dist/engine/conflicts.d.ts.map +1 -0
- package/dist/engine/conflicts.js +232 -0
- package/dist/engine/conflicts.js.map +1 -0
- package/dist/engine/context-detect.d.ts +17 -0
- package/dist/engine/context-detect.d.ts.map +1 -0
- package/dist/engine/context-detect.js +132 -0
- package/dist/engine/context-detect.js.map +1 -0
- package/dist/engine/marshal.d.ts +19 -0
- package/dist/engine/marshal.d.ts.map +1 -0
- package/dist/engine/marshal.js +74 -0
- package/dist/engine/marshal.js.map +1 -0
- package/dist/engine/retrieve.d.ts +36 -0
- package/dist/engine/retrieve.d.ts.map +1 -0
- package/dist/engine/retrieve.js +45 -0
- package/dist/engine/retrieve.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +769 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +60 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +58 -0
- 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
|