@kernel.chat/kbot 3.99.19 → 3.99.20

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/dist/cli.js CHANGED
@@ -879,6 +879,29 @@ async function main() {
879
879
  printInfo(` ${result.skipped} skipped (existing user-authored files preserved)`);
880
880
  printInfo(` → ${result.destination}`);
881
881
  });
882
+ const memoryCmd = program.command('memory').description('Inspect or prune kbot\'s memory store');
883
+ memoryCmd
884
+ .command('prune')
885
+ .description('Compact ~/.kbot/memory/solutions.json — removes stale, unused, or obsolete entries')
886
+ .option('--max-age-days <n>', 'Drop entries older than N days with zero reuses', '30')
887
+ .option('--obsolete-version <prefix>', 'Drop entries whose solution mentions this version prefix')
888
+ .option('--dry-run', 'Count what would be pruned without writing')
889
+ .action(async (opts) => {
890
+ const { pruneSolutions } = await import('./memory-prune.js');
891
+ const result = pruneSolutions({
892
+ maxAgeDays: opts.maxAgeDays ? parseInt(opts.maxAgeDays, 10) : undefined,
893
+ obsoleteVersionPrefix: opts.obsoleteVersion,
894
+ dryRun: opts.dryRun,
895
+ });
896
+ printInfo(`Total: ${result.total} · Kept: ${result.kept} · Pruned: ${result.pruned}`);
897
+ for (const [reason, count] of Object.entries(result.reasons)) {
898
+ printInfo(` ${reason}: ${count}`);
899
+ }
900
+ if (result.backup)
901
+ printSuccess(`Backup written: ${result.backup}`);
902
+ if (opts.dryRun)
903
+ printInfo('(dry run — no changes written)');
904
+ });
882
905
  program
883
906
  .command('design <brief...>')
884
907
  .description('Local-first alternative to Claude Design. Reads your repo\'s design tokens and generates an HTML prototype applying your visual system.')
@@ -0,0 +1,19 @@
1
+ export interface PruneOptions {
2
+ /** Drop entries older than this many days (default: 30) */
3
+ maxAgeDays?: number;
4
+ /** Drop entries whose solution mentions versions below this (e.g. "3.99.0") */
5
+ obsoleteVersionPrefix?: string;
6
+ /** Drop entries with confidence < this and reuses === 0 (default: 0.3) */
7
+ minConfidenceIfUnused?: number;
8
+ /** Dry run — count what would be pruned without writing */
9
+ dryRun?: boolean;
10
+ }
11
+ export interface PruneResult {
12
+ total: number;
13
+ kept: number;
14
+ pruned: number;
15
+ reasons: Record<string, number>;
16
+ backup?: string;
17
+ }
18
+ export declare function pruneSolutions(opts?: PruneOptions): PruneResult;
19
+ //# sourceMappingURL=memory-prune.d.ts.map
@@ -0,0 +1,77 @@
1
+ // Memory prune — compact ~/.kbot/memory/solutions.json by removing entries
2
+ // that are likely stale: outdated version references, old timestamps, or low
3
+ // confidence + zero reuses. Lightweight, safe, reversible (writes a .bak file).
4
+ import { readFileSync, writeFileSync, existsSync, copyFileSync } from 'node:fs';
5
+ import { join } from 'node:path';
6
+ import { homedir } from 'node:os';
7
+ export function pruneSolutions(opts = {}) {
8
+ const maxAgeDays = opts.maxAgeDays ?? 30;
9
+ const minConf = opts.minConfidenceIfUnused ?? 0.3;
10
+ const obsoletePrefix = opts.obsoleteVersionPrefix;
11
+ const path = join(homedir(), '.kbot', 'memory', 'solutions.json');
12
+ if (!existsSync(path)) {
13
+ return { total: 0, kept: 0, pruned: 0, reasons: {} };
14
+ }
15
+ const raw = readFileSync(path, 'utf-8');
16
+ let entries;
17
+ try {
18
+ entries = JSON.parse(raw);
19
+ if (!Array.isArray(entries))
20
+ throw new Error('not array');
21
+ }
22
+ catch {
23
+ return { total: 0, kept: 0, pruned: 0, reasons: { parse_error: 1 } };
24
+ }
25
+ const cutoff = Date.now() - maxAgeDays * 24 * 60 * 60 * 1000;
26
+ const reasons = {};
27
+ const kept = [];
28
+ for (const e of entries) {
29
+ const createdMs = toMillis(e.created);
30
+ const reuses = e.reuses ?? 0;
31
+ const conf = e.confidence ?? 0.5;
32
+ const solution = String(e.solution ?? '');
33
+ // Age: anything older than cutoff AND never reused gets pruned
34
+ if (createdMs > 0 && createdMs < cutoff && reuses === 0) {
35
+ reasons.stale_and_unused = (reasons.stale_and_unused || 0) + 1;
36
+ continue;
37
+ }
38
+ // Obsolete version references
39
+ if (obsoletePrefix && solution.includes(obsoletePrefix)) {
40
+ reasons.obsolete_version = (reasons.obsolete_version || 0) + 1;
41
+ continue;
42
+ }
43
+ // Low confidence and never reused — likely wrong from the start
44
+ if (conf < minConf && reuses === 0) {
45
+ reasons.low_confidence_unused = (reasons.low_confidence_unused || 0) + 1;
46
+ continue;
47
+ }
48
+ // Empty or tiny solution content
49
+ if (solution.trim().length < 20) {
50
+ reasons.empty_solution = (reasons.empty_solution || 0) + 1;
51
+ continue;
52
+ }
53
+ kept.push(e);
54
+ }
55
+ const result = {
56
+ total: entries.length,
57
+ kept: kept.length,
58
+ pruned: entries.length - kept.length,
59
+ reasons,
60
+ };
61
+ if (!opts.dryRun && result.pruned > 0) {
62
+ const backup = path + '.bak';
63
+ copyFileSync(path, backup);
64
+ writeFileSync(path, JSON.stringify(kept, null, 2));
65
+ result.backup = backup;
66
+ }
67
+ return result;
68
+ }
69
+ function toMillis(created) {
70
+ if (!created)
71
+ return 0;
72
+ if (typeof created === 'number')
73
+ return created > 1e12 ? created : created * 1000;
74
+ const parsed = Date.parse(created);
75
+ return Number.isNaN(parsed) ? 0 : parsed;
76
+ }
77
+ //# sourceMappingURL=memory-prune.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kernel.chat/kbot",
3
- "version": "3.99.19",
3
+ "version": "3.99.20",
4
4
  "description": "Open-source terminal AI agent. 787+ tools, 35 agents, 20 providers. Dreams, learns, watches your system. Controls your phone. Fully local, fully sovereign. MIT.",
5
5
  "type": "module",
6
6
  "repository": {