eklavya 1.57.0 → 1.59.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/README.md +1 -0
- package/dist/cli-memory.js +94 -81
- package/dist/cli-memory.js.map +1 -1
- package/dist/cli.js +49 -44
- package/dist/cli.js.map +1 -1
- package/dist/hooks/session-start.js +2 -2
- package/dist/hooks/session-start.js.map +1 -1
- package/dist/install.js +16 -16
- package/dist/install.js.map +1 -1
- package/dist/onboard.js +34 -8
- package/dist/onboard.js.map +1 -1
- package/dist/plugin/.claude-plugin/plugin.json +1 -1
- package/dist/theme.js +22 -0
- package/dist/theme.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/cli-memory.js
CHANGED
|
@@ -16,7 +16,7 @@ import { GLOBAL_PROJECT, projectKey } from './store.js';
|
|
|
16
16
|
import { missingProjects, moveProject } from './relocate.js';
|
|
17
17
|
import { claudeHome } from './install.js';
|
|
18
18
|
import { guessProjectMap } from './claude-mem.js';
|
|
19
|
-
import { spin } from './theme.js';
|
|
19
|
+
import { bold, dim, failText, paint, spin, styleUsage } from './theme.js';
|
|
20
20
|
import { importOffThread } from './memory/import-worker.js';
|
|
21
21
|
import { isInternalObserver, releaseWorker, renewWorker, reserveWorker, stopWorker, workerStatus, } from './memory/reservation.js';
|
|
22
22
|
import { identityFor } from './memory/identity.js';
|
|
@@ -28,9 +28,11 @@ import { replayProject, transcriptDirFor, transcriptsFor } from './memory/replay
|
|
|
28
28
|
import { droppedCount } from './memory/spool.js';
|
|
29
29
|
import { savingsFrom, savingsLine } from './memory/tokens.js';
|
|
30
30
|
import { inventory, exportPayload, restoreExport, EXPORT_SCHEMA_VERSION, ImportError, IMPORTED_TABLES, verifyImport, } from './memory/import.js';
|
|
31
|
+
/** A `label: value` row with its label dim; a line without that shape is returned as it is. */
|
|
32
|
+
const kv = (line) => line.replace(/^(\s*[a-z][a-z ]*:)(\s+)(?=\S)/, (_m, k, gap) => `${dim(k)}${gap}`);
|
|
31
33
|
/** The same as `cli.ts`'s: importing that one would run its `main()`. */
|
|
32
34
|
function fail(message) {
|
|
33
|
-
process.stderr.write(`${message}\n`);
|
|
35
|
+
process.stderr.write(`${failText(message)}\n`);
|
|
34
36
|
process.exit(1);
|
|
35
37
|
}
|
|
36
38
|
/**
|
|
@@ -156,7 +158,7 @@ function memoryStatus() {
|
|
|
156
158
|
`pending: ${pendingEventCount(db, project)} evidence events here, ${pendingEventCount(db)} in total`,
|
|
157
159
|
`queue: ${queue.pending} pending · ${queue.paused} paused · ${queue.failed} failed${queue.quarantined ? ` · ${queue.quarantined} quarantined` : ''}`,
|
|
158
160
|
`oldest job: ${queue.oldest ?? '—'}`,
|
|
159
|
-
...(pauseLine(db) ? [`paused: ${pauseLine(db)} — fix it, then: eklavya memory process`] : []),
|
|
161
|
+
...(pauseLine(db) ? [`paused: ${paint.warn(`${pauseLine(db)} — fix it, then: eklavya memory process`)}`] : []),
|
|
160
162
|
`worker: ${workerLine(db)}`,
|
|
161
163
|
// Named separately from the summarizer because they answer different
|
|
162
164
|
// questions: one is "will anything leave this machine", the other is
|
|
@@ -170,7 +172,7 @@ function memoryStatus() {
|
|
|
170
172
|
`reads: ${readLine(readTotals(db))}`,
|
|
171
173
|
savingsLine(savings),
|
|
172
174
|
];
|
|
173
|
-
process.stdout.write(`${lines.join('\n')}\n`);
|
|
175
|
+
process.stdout.write(`${lines.map(kv).join('\n')}\n`);
|
|
174
176
|
}
|
|
175
177
|
finally {
|
|
176
178
|
db.close();
|
|
@@ -194,12 +196,13 @@ function memorySearch(argv) {
|
|
|
194
196
|
limit: numberFlag(argv, '--limit', 10),
|
|
195
197
|
});
|
|
196
198
|
if (!hits.length) {
|
|
197
|
-
process.stdout.write('No matches
|
|
199
|
+
process.stdout.write(`${dim('No matches.')}\n`);
|
|
198
200
|
return;
|
|
199
201
|
}
|
|
200
202
|
for (const hit of hits) {
|
|
201
|
-
process.stdout.write(`#${hit.entry.id} ${hit.entry.occurred_at.slice(0, 16).replace('T', ' ')} ${hit.entry.title}\n` +
|
|
202
|
-
` ${hit.entry.type ?? hit.entry.kind} · score ${hit.score.toFixed(3)} · ${hit.via}${hit.entry.import_source ? ` · imported from ${hit.entry.import_source}` : ''}
|
|
203
|
+
process.stdout.write(`${paint.aged(`#${hit.entry.id}`)} ${hit.entry.occurred_at.slice(0, 16).replace('T', ' ')} ${bold(hit.entry.title)}\n` +
|
|
204
|
+
dim(` ${hit.entry.type ?? hit.entry.kind} · score ${hit.score.toFixed(3)} · ${hit.via}${hit.entry.import_source ? ` · imported from ${hit.entry.import_source}` : ''}`) +
|
|
205
|
+
'\n');
|
|
203
206
|
}
|
|
204
207
|
}
|
|
205
208
|
finally {
|
|
@@ -215,11 +218,11 @@ function memoryTimeline(argv) {
|
|
|
215
218
|
since: flag(argv, '--since') ?? null,
|
|
216
219
|
});
|
|
217
220
|
if (!rows.length) {
|
|
218
|
-
process.stdout.write('Nothing recorded for this project yet
|
|
221
|
+
process.stdout.write(`${dim('Nothing recorded for this project yet.')}\n`);
|
|
219
222
|
return;
|
|
220
223
|
}
|
|
221
224
|
for (const row of rows) {
|
|
222
|
-
process.stdout.write(`#${row.id} ${row.occurred_at.slice(0, 16).replace('T', ' ')} ${row.kind} ${row.title}\n`);
|
|
225
|
+
process.stdout.write(`${paint.aged(`#${row.id}`)} ${row.occurred_at.slice(0, 16).replace('T', ' ')} ${dim(row.kind)} ${row.title}\n`);
|
|
223
226
|
}
|
|
224
227
|
}
|
|
225
228
|
finally {
|
|
@@ -237,28 +240,30 @@ function memoryShow(argv) {
|
|
|
237
240
|
fail(`No memory entry #${id}.`);
|
|
238
241
|
const tags = entryTags(db, id);
|
|
239
242
|
const lines = [
|
|
240
|
-
`#${entry.id} ${entry.title}`,
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
243
|
+
`${paint.aged(`#${entry.id}`)} ${bold(entry.title)}`,
|
|
244
|
+
...[
|
|
245
|
+
`kind: ${entry.kind}${entry.type ? ` / ${entry.type}` : ''}`,
|
|
246
|
+
`project: ${entry.project}`,
|
|
247
|
+
`occurred: ${entry.occurred_at}`,
|
|
248
|
+
`generator: ${entry.generator}`,
|
|
249
|
+
...(entry.import_source ? [`imported: from ${entry.import_source} (unassessed — no mastery, no attempts)`] : []),
|
|
250
|
+
...(entry.superseded_by ? [`superseded by #${entry.superseded_by}`] : []),
|
|
251
|
+
...(tags.length ? [`tags: ${tags.join(', ')}`] : []),
|
|
252
|
+
...(entry.files ? [`files: ${JSON.parse(entry.files).join(', ')}`] : []),
|
|
253
|
+
].map(kv),
|
|
249
254
|
'',
|
|
250
255
|
entry.narrative || '(no narrative)',
|
|
251
256
|
];
|
|
252
257
|
const facts = entry.facts ? JSON.parse(entry.facts) : [];
|
|
253
258
|
if (facts.length)
|
|
254
|
-
lines.push('', 'Facts:', ...facts.map((f) => ` - ${f}`));
|
|
259
|
+
lines.push('', bold('Facts:'), ...facts.map((f) => ` - ${f}`));
|
|
255
260
|
const events = entryEvents(db, id);
|
|
256
|
-
lines.push('', `Evidence (${events.length}):`);
|
|
261
|
+
lines.push('', bold(`Evidence (${events.length}):`));
|
|
257
262
|
for (const event of events) {
|
|
258
263
|
lines.push(` ${event.occurred_at.slice(0, 16).replace('T', ' ')} ${event.kind}${event.tool ? `/${event.tool}` : ''} ${event.body.slice(0, 120).replace(/\s+/g, ' ')}`);
|
|
259
264
|
}
|
|
260
265
|
if (!events.length)
|
|
261
|
-
lines.push(' (none linked — imported or hand-written entries carry no local evidence)');
|
|
266
|
+
lines.push(dim(' (none linked — imported or hand-written entries carry no local evidence)'));
|
|
262
267
|
process.stdout.write(`${lines.join('\n')}\n`);
|
|
263
268
|
}
|
|
264
269
|
finally {
|
|
@@ -304,7 +309,7 @@ function memoryProcess(argv) {
|
|
|
304
309
|
if (!token) {
|
|
305
310
|
const holder = workerStatus(db);
|
|
306
311
|
if (!background) {
|
|
307
|
-
process.stdout.write(`another memory worker is running${holder?.pid ? ` (pid ${holder.pid})` : ''} — its queue is this queue, so nothing to do
|
|
312
|
+
process.stdout.write(dim(`another memory worker is running${holder?.pid ? ` (pid ${holder.pid})` : ''} — its queue is this queue, so nothing to do.`) + '\n');
|
|
308
313
|
}
|
|
309
314
|
db.close();
|
|
310
315
|
return;
|
|
@@ -337,7 +342,7 @@ function runWorker(db, token, config, maxJobs, background, resumed) {
|
|
|
337
342
|
loadConfig: () => loadConfig().config,
|
|
338
343
|
}).then((result) => {
|
|
339
344
|
if (!background) {
|
|
340
|
-
process.stdout.write(`${resumed ? `resumed ${resumed} paused · ` : ''}processed ${result.processed} · entries ${result.entries} · failed ${result.failed} · skipped ${result.skipped}
|
|
345
|
+
process.stdout.write(paint.ok(`${resumed ? `resumed ${resumed} paused · ` : ''}processed ${result.processed} · entries ${result.entries} · failed ${result.failed} · skipped ${result.skipped}`) + `${result.handedOff ? dim(' · more queued, continuing in the background') : ''}\n`);
|
|
341
346
|
}
|
|
342
347
|
db.close();
|
|
343
348
|
},
|
|
@@ -370,16 +375,18 @@ function memoryBacklog(argv) {
|
|
|
370
375
|
if (action === 'list') {
|
|
371
376
|
const groups = backlogSummary(db, sel);
|
|
372
377
|
if (!groups.length) {
|
|
373
|
-
process.stdout.write('No unfinished jobs or helper sessions
|
|
378
|
+
process.stdout.write(`${dim('No unfinished jobs or helper sessions.')}\n`);
|
|
374
379
|
return;
|
|
375
380
|
}
|
|
376
381
|
for (const g of groups) {
|
|
377
|
-
process.stdout.write(`${String(g.batches).padStart(6)} ${g.status.padEnd(11)} ${g.project}${g.helper ? ' [observer helper sessions]' : ''}\n` +
|
|
378
|
-
` ${g.events} events · ${g.oldest.slice(0, 16).replace('T', ' ')} → ${g.newest.slice(0, 16).replace('T', ' ')}
|
|
382
|
+
process.stdout.write(`${String(g.batches).padStart(6)} ${g.status.padEnd(11)} ${g.project}${g.helper ? paint.warn(' [observer helper sessions]') : ''}\n` +
|
|
383
|
+
dim(` ${g.events} events · ${g.oldest.slice(0, 16).replace('T', ' ')} → ${g.newest.slice(0, 16).replace('T', ' ')}`) +
|
|
384
|
+
'\n');
|
|
379
385
|
}
|
|
380
386
|
if (groups.some((g) => g.helper)) {
|
|
381
|
-
process.stdout.write('\nHelper sessions are the observer summarising its own runs — noise. Delete them, and the memories\n' +
|
|
382
|
-
'they produced, or set the unfinished ones aside
|
|
387
|
+
process.stdout.write(dim('\nHelper sessions are the observer summarising its own runs — noise. Delete them, and the memories\n' +
|
|
388
|
+
'they produced, or set the unfinished ones aside:') +
|
|
389
|
+
'\n' +
|
|
383
390
|
' eklavya memory backlog discard --helpers\n eklavya memory backlog quarantine --helpers\n');
|
|
384
391
|
}
|
|
385
392
|
return;
|
|
@@ -387,14 +394,14 @@ function memoryBacklog(argv) {
|
|
|
387
394
|
if (!selected)
|
|
388
395
|
fail(`eklavya memory backlog ${action} needs --helpers, --project <key>, --session <id> or --batch <id>.`);
|
|
389
396
|
if (action === 'quarantine') {
|
|
390
|
-
process.stdout.write(`quarantined ${quarantineBacklog(db, sel)} job(s) — kept, and never processed until restored.\n`);
|
|
397
|
+
process.stdout.write(`${paint.ok(`quarantined ${quarantineBacklog(db, sel)} job(s)`)} — kept, and never processed until restored.\n`);
|
|
391
398
|
}
|
|
392
399
|
else if (action === 'restore') {
|
|
393
|
-
process.stdout.write(`restored ${restoreBacklog(db, sel)} job(s) to the queue.\n`);
|
|
400
|
+
process.stdout.write(`${paint.ok(`restored ${restoreBacklog(db, sel)} job(s)`)} to the queue.\n`);
|
|
394
401
|
}
|
|
395
402
|
else if (action === 'discard') {
|
|
396
403
|
const gone = discardBacklog(db, sel);
|
|
397
|
-
process.stdout.write(`discarded ${gone.batches} batch(es), ${gone.events} evidence event(s) and ${gone.entries} memory entr${gone.entries === 1 ? 'y' : 'ies'}.\n`);
|
|
404
|
+
process.stdout.write(`${paint.ok(`discarded ${gone.batches} batch(es), ${gone.events} evidence event(s) and ${gone.entries} memory entr${gone.entries === 1 ? 'y' : 'ies'}`)}.\n`);
|
|
398
405
|
}
|
|
399
406
|
else {
|
|
400
407
|
fail('Usage: eklavya memory backlog [list|quarantine|discard|restore] [--helpers] [--project <key>] [--session <id>] [--batch <id>]');
|
|
@@ -416,7 +423,7 @@ function memoryStop() {
|
|
|
416
423
|
const { config } = loadConfig();
|
|
417
424
|
process.stdout.write(stopMessage(outcome));
|
|
418
425
|
if (config.providers.observer) {
|
|
419
|
-
process.stdout.write('the next session seam starts a new one. To keep it stopped: eklavya config set providers.observer null\n
|
|
426
|
+
process.stdout.write(`${dim('the next session seam starts a new one. To keep it stopped: eklavya config set providers.observer null')}\n`);
|
|
420
427
|
}
|
|
421
428
|
db.close();
|
|
422
429
|
});
|
|
@@ -424,9 +431,9 @@ function memoryStop() {
|
|
|
424
431
|
/** What `memory stop` says it did. Exported for the outcomes a test cannot stage in time. */
|
|
425
432
|
export function stopMessage(outcome) {
|
|
426
433
|
if (!outcome.stopped)
|
|
427
|
-
return 'no memory worker is running
|
|
428
|
-
return (`stopped the memory worker${outcome.pid ? ` (pid ${outcome.pid})` : ''}${outcome.child ? ` and its claude call (pid ${outcome.child})` : ''}${outcome.forced ? ' — it had to be killed' : ''}. Unfinished jobs stay queued.\n` +
|
|
429
|
-
(outcome.released ? '' : 'something it started would not exit; the slot stays held until it does
|
|
434
|
+
return `${dim('no memory worker is running.')}\n`;
|
|
435
|
+
return (`${paint.ok(`stopped the memory worker${outcome.pid ? ` (pid ${outcome.pid})` : ''}${outcome.child ? ` and its claude call (pid ${outcome.child})` : ''}`)}${outcome.forced ? paint.warn(' — it had to be killed') : ''}. Unfinished jobs stay queued.\n` +
|
|
436
|
+
(outcome.released ? '' : `${paint.warn('something it started would not exit; the slot stays held until it does.')}\n`));
|
|
430
437
|
}
|
|
431
438
|
/**
|
|
432
439
|
* Backfills from Claude Code's own transcripts.
|
|
@@ -441,13 +448,13 @@ function memoryReplay(argv) {
|
|
|
441
448
|
try {
|
|
442
449
|
const { config } = loadConfig();
|
|
443
450
|
if (!config.memory.enabled) {
|
|
444
|
-
process.stdout.write('memory.enabled is false, so there is nowhere to replay into
|
|
451
|
+
process.stdout.write(`${paint.warn('memory.enabled is false, so there is nowhere to replay into.')}\n`);
|
|
445
452
|
return;
|
|
446
453
|
}
|
|
447
454
|
const cwd = process.cwd();
|
|
448
455
|
const files = transcriptsFor(cwd);
|
|
449
456
|
if (!files.length) {
|
|
450
|
-
process.stdout.write(
|
|
457
|
+
process.stdout.write(`${dim('No Claude Code transcripts found for this checkout.')}\n${dim('Looked in:')} ${paint.aged(transcriptDirFor(cwd))}\n`);
|
|
451
458
|
return;
|
|
452
459
|
}
|
|
453
460
|
const limit = Number(flag(argv, '--limit', '20'));
|
|
@@ -459,13 +466,15 @@ function memoryReplay(argv) {
|
|
|
459
466
|
excluded: sum.excluded + r.excluded,
|
|
460
467
|
}), { read: 0, captured: 0, duplicates: 0, excluded: 0 });
|
|
461
468
|
process.stdout.write([
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
469
|
+
...[
|
|
470
|
+
`transcripts: ${results.length} of ${files.length}`,
|
|
471
|
+
`lines read: ${total.read}`,
|
|
472
|
+
`captured: ${total.captured}`,
|
|
473
|
+
`already had: ${total.duplicates}`,
|
|
474
|
+
`excluded: ${total.excluded} (privacy filter, or capture set to minimal)`,
|
|
475
|
+
].map(kv),
|
|
467
476
|
'',
|
|
468
|
-
'Run `eklavya memory process` to summarise what was captured.',
|
|
477
|
+
dim('Run `eklavya memory process` to summarise what was captured.'),
|
|
469
478
|
'',
|
|
470
479
|
].join('\n'));
|
|
471
480
|
}
|
|
@@ -478,14 +487,14 @@ function memoryPrune() {
|
|
|
478
487
|
try {
|
|
479
488
|
const { config } = loadConfig();
|
|
480
489
|
if (!config.memory.retention_days) {
|
|
481
|
-
process.stdout.write('memory.retention_days is not set for this project, so its raw evidence is kept until deleted by hand
|
|
490
|
+
process.stdout.write(`${dim('memory.retention_days is not set for this project, so its raw evidence is kept until deleted by hand.')}\n`);
|
|
482
491
|
return;
|
|
483
492
|
}
|
|
484
493
|
// This project only, under this project's resolved retention: another
|
|
485
494
|
// project may keep its evidence (retention unset) or a different window.
|
|
486
495
|
const project = currentProject();
|
|
487
496
|
const removed = pruneEvidence(db, config, { project });
|
|
488
|
-
process.stdout.write(`Deleted ${removed} raw evidence events older than ${config.memory.retention_days} days in ${project}.\n`);
|
|
497
|
+
process.stdout.write(`${paint.ok(`Deleted ${removed} raw evidence events`)} older than ${config.memory.retention_days} days in ${project}.\n`);
|
|
489
498
|
}
|
|
490
499
|
finally {
|
|
491
500
|
db.close();
|
|
@@ -504,10 +513,10 @@ function memoryMove(argv) {
|
|
|
504
513
|
if (!oldArg) {
|
|
505
514
|
const missing = missingProjects(db);
|
|
506
515
|
process.stdout.write(missing.length
|
|
507
|
-
?
|
|
516
|
+
? `${bold('History filed under folders that no longer exist:')}\n${missing
|
|
508
517
|
.map((m) => ` ${m.project} (${m.entries} entries)`)
|
|
509
518
|
.join('\n')}\nRe-file one here: eklavya memory move <old>\n`
|
|
510
|
-
: 'No history is filed under a folder that no longer exists
|
|
519
|
+
: `${dim('No history is filed under a folder that no longer exists.')}\n`);
|
|
511
520
|
return;
|
|
512
521
|
}
|
|
513
522
|
const from = path.resolve(oldArg);
|
|
@@ -529,8 +538,8 @@ function memoryMove(argv) {
|
|
|
529
538
|
if (!held.n)
|
|
530
539
|
fail(`No history is filed under ${from}. Run \`eklavya memory move\` to list the folders that have some.`);
|
|
531
540
|
const report = moveProject(db, from, to);
|
|
532
|
-
process.stdout.write(`Moved ${from} → ${to}: ${report.rows} rows re-filed, ${report.entries} memory entries here now.\n` +
|
|
533
|
-
report.left.map((f) => `Left in place, because ${to} already has one: ${f}\n`).join(''));
|
|
541
|
+
process.stdout.write(`${paint.ok(`Moved ${from} → ${to}`)}: ${report.rows} rows re-filed, ${report.entries} memory entries here now.\n` +
|
|
542
|
+
report.left.map((f) => `${paint.warn(`Left in place, because ${to} already has one: ${f}`)}\n`).join(''));
|
|
534
543
|
}
|
|
535
544
|
finally {
|
|
536
545
|
db.close();
|
|
@@ -543,7 +552,7 @@ function dispositionReport(fields) {
|
|
|
543
552
|
const group = fields.filter((f) => f.kind === kind);
|
|
544
553
|
if (!group.length)
|
|
545
554
|
continue;
|
|
546
|
-
lines.push('', `${kind} (${group.length}):`);
|
|
555
|
+
lines.push('', bold(`${kind} (${group.length}):`));
|
|
547
556
|
for (const f of group) {
|
|
548
557
|
lines.push(` ${f.table}.${f.field}${f.to ? ` -> ${f.to}` : ''}${f.reason ? ` — ${f.reason}` : ''}`);
|
|
549
558
|
}
|
|
@@ -591,17 +600,17 @@ function verifyLines(r) {
|
|
|
591
600
|
const missing = r.tables.reduce((n, t) => n + t.missing.length, 0);
|
|
592
601
|
const width = Math.max(0, ...r.projects.map((p) => p.project.length));
|
|
593
602
|
return [
|
|
594
|
-
`verify: ${r.sourcePath}
|
|
603
|
+
kv(`verify: ${r.sourcePath}`),
|
|
595
604
|
...r.tables.map((t) => ` ${t.table.padEnd(18)} ${String(t.source).padStart(6)} in source · ${String(t.present).padStart(6)} in Eklavya · ${t.missing.length} missing${t.missing.length ? ` (ids ${t.missing.slice(0, 10).join(', ')}${t.missing.length > 10 ? ', …' : ''})` : ''}`),
|
|
596
605
|
` changed since import: ${r.changed} entr${r.changed === 1 ? 'y' : 'ies'}`,
|
|
597
|
-
'placement:',
|
|
606
|
+
bold('placement:'),
|
|
598
607
|
...r.projects.flatMap((p) => Object.entries(p.filedUnder).map(([to, n]) => {
|
|
599
608
|
const placed = path.isAbsolute(to);
|
|
600
609
|
return ` ${String(n).padStart(6)} ${p.project.padEnd(width)} ${placed ? `→ ${to}` : 'not placed — searchable only with --all-projects; --map it to a checkout'}`;
|
|
601
610
|
})),
|
|
602
611
|
missing
|
|
603
|
-
? `INCOMPLETE: ${missing} source row(s) are not in Eklavya — re-run without --verify to import them.`
|
|
604
|
-
: 'complete: every source row is in Eklavya.',
|
|
612
|
+
? paint.fail(`INCOMPLETE: ${missing} source row(s) are not in Eklavya — re-run without --verify to import them.`)
|
|
613
|
+
: paint.ok('complete: every source row is in Eklavya.'),
|
|
605
614
|
];
|
|
606
615
|
}
|
|
607
616
|
async function memoryImport(argv) {
|
|
@@ -633,14 +642,16 @@ async function memoryImport(argv) {
|
|
|
633
642
|
const projectMap = { ...guessProjectMap(source, claudeHome(), unsure), ...explicit };
|
|
634
643
|
for (const name of Object.keys(explicit))
|
|
635
644
|
delete unsure[name];
|
|
636
|
-
const unsureLines = Object.entries(unsure).map(([name, paths]) => ` ${name}: ${paths.length} checkouts carry this name — pick one: --map ${name}=<path> (${paths.join(', ')})`);
|
|
645
|
+
const unsureLines = Object.entries(unsure).map(([name, paths]) => paint.warn(` ${name}: ${paths.length} checkouts carry this name — pick one: --map ${name}=<path> (${paths.join(', ')})`));
|
|
637
646
|
const lines = [
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
647
|
+
...[
|
|
648
|
+
`source: ${found.sourcePath}`,
|
|
649
|
+
`schema: ${found.schemaVersion ?? 'unversioned'} (this importer understands up to ${found.supportedMax})`,
|
|
650
|
+
`range: ${found.dateRange.from?.slice(0, 10) ?? '—'} … ${found.dateRange.to?.slice(0, 10) ?? '—'}`,
|
|
651
|
+
].map(kv),
|
|
652
|
+
bold('tables:'),
|
|
642
653
|
...found.tables.map((t) => ` ${t.rows.toString().padStart(7)} ${t.name}${t.known ? '' : ' (unrecognised)'}`),
|
|
643
|
-
'projects:',
|
|
654
|
+
bold('projects:'),
|
|
644
655
|
...found.projects.map((p) => ` ${p.entries.toString().padStart(7)} ${p.project}`),
|
|
645
656
|
dispositionReport(found.fields),
|
|
646
657
|
];
|
|
@@ -656,7 +667,7 @@ async function memoryImport(argv) {
|
|
|
656
667
|
...planned.map(([from, to]) => `would map: ${from} -> ${to}`),
|
|
657
668
|
...(unmapped.length ? [`would keep as-is: ${unmapped.join(', ')}`] : []),
|
|
658
669
|
...unsureLines,
|
|
659
|
-
'Dry run: nothing was written, and the source was opened read-only.',
|
|
670
|
+
dim('Dry run: nothing was written, and the source was opened read-only.'),
|
|
660
671
|
'',
|
|
661
672
|
].join('\n'));
|
|
662
673
|
return;
|
|
@@ -667,20 +678,20 @@ async function memoryImport(argv) {
|
|
|
667
678
|
const rows = IMPORTED_TABLES.map((t) => ` ${t.padEnd(18)} read ${report.read[t]} · imported ${report.imported[t]} · already present ${report.skipped[t]}`);
|
|
668
679
|
process.stdout.write([
|
|
669
680
|
'',
|
|
670
|
-
`snapshot: ${report.snapshot}
|
|
681
|
+
kv(`snapshot: ${report.snapshot}`),
|
|
671
682
|
...rows,
|
|
672
|
-
` concept candidates: ${report.candidates} (all unassessed — no mastery, no attempts, no gate touched)`,
|
|
673
|
-
` evidence links: ${report.links} (drill-down from an entry to the prompts and tool uses behind it)`,
|
|
683
|
+
` concept candidates: ${report.candidates} ${dim('(all unassessed — no mastery, no attempts, no gate touched)')}`,
|
|
684
|
+
` evidence links: ${report.links} ${dim('(drill-down from an entry to the prompts and tool uses behind it)')}`,
|
|
674
685
|
` re-indexed: ${report.reindexed} entries`,
|
|
675
686
|
...(report.rehomed ? [` re-homed: ${report.rehomed} entries an earlier run left under a bare project name`] : []),
|
|
676
|
-
` validation: ${report.validation.ok ? 'ok' : `FAILED — ${report.validation.notes.join('; ')}`}`,
|
|
687
|
+
` validation: ${report.validation.ok ? paint.ok('ok') : paint.fail(`FAILED — ${report.validation.notes.join('; ')}`)}`,
|
|
677
688
|
...report.projectsMapped.map((p) => ` mapped: ${p.from} -> ${p.to}`),
|
|
678
689
|
// The unmapped list is the useful half: those rows only ever surface
|
|
679
690
|
// under --all-projects until somebody maps them.
|
|
680
691
|
...(report.projectsKept.length
|
|
681
692
|
? [
|
|
682
693
|
` kept as-is: ${report.projectsKept.join(', ')}`,
|
|
683
|
-
' (unmapped projects are searchable only with --all-projects; re-run with --map to file them under a checkout)',
|
|
694
|
+
dim(' (unmapped projects are searchable only with --all-projects; re-run with --map to file them under a checkout)'),
|
|
684
695
|
]
|
|
685
696
|
: []),
|
|
686
697
|
'',
|
|
@@ -724,7 +735,7 @@ function memoryExport(argv) {
|
|
|
724
735
|
// `--force` replaced, whose old mode `mode` would otherwise keep.
|
|
725
736
|
fs.writeFileSync(out, `${JSON.stringify(payload, null, 2)}\n`, { encoding: 'utf8', mode: 0o600, flag: force ? 'w' : 'wx' });
|
|
726
737
|
fs.chmodSync(out, 0o600);
|
|
727
|
-
process.stdout.write(
|
|
738
|
+
process.stdout.write(`${paint.ok('Wrote')} ${paint.aged(out)} — ${payload.entries.length} entries, schema version ${EXPORT_SCHEMA_VERSION}\n`);
|
|
728
739
|
}
|
|
729
740
|
finally {
|
|
730
741
|
db.close();
|
|
@@ -746,13 +757,15 @@ function memoryRestore(argv) {
|
|
|
746
757
|
try {
|
|
747
758
|
const r = restoreExport(db, path.resolve(from));
|
|
748
759
|
process.stdout.write([
|
|
749
|
-
`Restored ${from} (export schema version ${r.schemaVersion}):`,
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
760
|
+
`${paint.ok(`Restored ${from}`)} (export schema version ${r.schemaVersion}):`,
|
|
761
|
+
...[
|
|
762
|
+
` entries: ${r.entries.restored} restored, ${r.entries.skipped} already here`,
|
|
763
|
+
` evidence: ${r.evidence.restored} restored, ${r.evidence.skipped} already here`,
|
|
764
|
+
` links: ${r.tags} tag(s), ${r.links} evidence link(s)`,
|
|
765
|
+
` receipts: ${r.receipts.restored} restored, ${r.receipts.skipped} already here (${r.receiptItems} item(s))`,
|
|
766
|
+
` reindexed: ${r.reindexed} entries — search index and vectors rebuilt`,
|
|
767
|
+
].map(kv),
|
|
768
|
+
dim('Learning history was not touched: no attempt, mastery or gate row is written by a restore.'),
|
|
756
769
|
'',
|
|
757
770
|
].join('\n'));
|
|
758
771
|
}
|
|
@@ -798,7 +811,7 @@ function memorySync(argv) {
|
|
|
798
811
|
.join(', ')
|
|
799
812
|
: 'none seen yet'}`,
|
|
800
813
|
];
|
|
801
|
-
process.stdout.write(`${lines.join('\n')}\n`);
|
|
814
|
+
process.stdout.write(`${lines.map(kv).join('\n')}\n`);
|
|
802
815
|
return;
|
|
803
816
|
}
|
|
804
817
|
const result = sub === 'push' ? push(db, config, { target }) : pull(db, config, { target });
|
|
@@ -809,23 +822,23 @@ function memorySync(argv) {
|
|
|
809
822
|
}
|
|
810
823
|
if (sub === 'push') {
|
|
811
824
|
const r = result;
|
|
812
|
-
process.stdout.write(
|
|
825
|
+
process.stdout.write(`${paint.ok('Pushed')} to ${r.target} as ${r.device_id}: ${r.staged} new revision${r.staged === 1 ? '' : 's'}, ${r.written} record${r.written === 1 ? '' : 's'} written, ${r.already} already there.\n`);
|
|
813
826
|
return;
|
|
814
827
|
}
|
|
815
828
|
const r = result;
|
|
816
|
-
process.stdout.write(
|
|
829
|
+
process.stdout.write(`${paint.ok('Pulled')} from ${r.target}: ${r.applied} applied (${r.tombstones} deletion${r.tombstones === 1 ? '' : 's'}), ${r.skipped} already known, ${r.conflicts} quarantined.\n`);
|
|
817
830
|
if (r.recovered) {
|
|
818
831
|
process.stdout.write(`Recovered ${r.recovered} earlier record${r.recovered === 1 ? '' : 's'} that arrived late before this version.\n`);
|
|
819
832
|
}
|
|
820
833
|
if (r.conflicts) {
|
|
821
|
-
process.stdout.write('Quarantined versions are kept whole in sync_conflicts — nothing was overwritten
|
|
834
|
+
process.stdout.write(`${paint.warn('Quarantined versions are kept whole in sync_conflicts — nothing was overwritten.')}\n`);
|
|
822
835
|
}
|
|
823
836
|
if (r.stalled.length) {
|
|
824
|
-
process.stdout.write(`Could not read a record from: ${r.stalled.join(', ')} — likely still being written. The next pull reads it
|
|
837
|
+
process.stdout.write(`${paint.warn(`Could not read a record from: ${r.stalled.join(', ')} — likely still being written. The next pull reads it.`)}\n`);
|
|
825
838
|
}
|
|
826
839
|
if (r.outstanding.length) {
|
|
827
840
|
const owed = r.outstanding.map((o) => `${o.device_id} (${o.missing} revision${o.missing === 1 ? '' : 's'}, from ${o.first})`);
|
|
828
|
-
process.stdout.write(`Still owed: ${owed.join(', ')}. Later records were applied; the next pull applies these once they arrive and can be read
|
|
841
|
+
process.stdout.write(`${paint.warn(`Still owed: ${owed.join(', ')}. Later records were applied; the next pull applies these once they arrive and can be read.`)}\n`);
|
|
829
842
|
}
|
|
830
843
|
}
|
|
831
844
|
finally {
|
|
@@ -866,7 +879,7 @@ export function memoryCommand(argv) {
|
|
|
866
879
|
case 'sync':
|
|
867
880
|
return memorySync(rest);
|
|
868
881
|
default:
|
|
869
|
-
process.stderr.write(MEMORY_USAGE);
|
|
882
|
+
process.stderr.write(styleUsage(MEMORY_USAGE));
|
|
870
883
|
process.exit(1);
|
|
871
884
|
}
|
|
872
885
|
}
|