crbro-memory 2.1.2 → 2.3.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/bin/crbro.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // ─── CRBRO CLI ───────────────────────────────────────────────────
4
4
  // Command-line interface for CRBRO memory system
5
- // Supports: init, status, mine, setup-miner, miner-status,
5
+ // Supports: init, status, secret, mine, setup-miner, miner-status,
6
6
  // remove-miner, and MCP server mode (default)
7
7
 
8
8
  import { platform, homedir } from 'os';
@@ -584,6 +584,149 @@ if (command === 'init') {
584
584
  }
585
585
  }).catch(console.error);
586
586
 
587
+ } else if (command === 'secret') {
588
+ // ─── Credentials, from the terminal ────────────────────────────
589
+ //
590
+ // Until 2.3 the only way to store a credential was the crbro_secret MCP
591
+ // tool, which means typing it into a conversation with a model. For a
592
+ // module whose whole point is that a secret never touches the brain, that
593
+ // was the wrong last mile. These subcommands close it: the value is read
594
+ // from stdin, never from argv, so it stays out of the shell history and
595
+ // out of the process table where `ps` and Task Manager can read it.
596
+ const sub = args[1];
597
+ const name = args[2];
598
+
599
+ const readPiped = () => new Promise((resolve, reject) => {
600
+ let data = '';
601
+ process.stdin.setEncoding('utf8');
602
+ process.stdin.on('data', (d) => { data += d; });
603
+ process.stdin.on('end', () => resolve(data.replace(/\r?\n$/, '')));
604
+ process.stdin.on('error', reject);
605
+ });
606
+
607
+ // Interactive read with the echo off. Raw mode hands us every keystroke,
608
+ // so nothing is drawn and nothing survives in the terminal scrollback.
609
+ const readHidden = (promptText) => new Promise((resolve, reject) => {
610
+ const stdin = process.stdin;
611
+ let value = '';
612
+ const cleanup = () => {
613
+ stdin.removeListener('data', onData);
614
+ if (stdin.isTTY) stdin.setRawMode(false);
615
+ stdin.pause();
616
+ };
617
+ const onData = (chunk) => {
618
+ for (const ch of chunk.toString('utf8')) {
619
+ if (ch === '\r' || ch === '\n') { cleanup(); process.stderr.write('\n'); return resolve(value); }
620
+ if (ch === '\u0003') { cleanup(); process.stderr.write('\n'); return reject(new Error('Cancelled — nothing was stored.')); }
621
+ if (ch === '\u007f' || ch === '\b') { value = value.slice(0, -1); continue; }
622
+ value += ch;
623
+ }
624
+ };
625
+ process.stderr.write(promptText);
626
+ if (stdin.isTTY) stdin.setRawMode(true);
627
+ stdin.resume();
628
+ stdin.on('data', onData);
629
+ });
630
+
631
+ const flagValue = (flag) => {
632
+ const i = args.indexOf(flag);
633
+ return i !== -1 && args[i + 1] ? args[i + 1] : '';
634
+ };
635
+
636
+ import('../dist/engine/keychain.js').then(async (kc) => {
637
+ try {
638
+ if (sub === 'status') {
639
+ const { backend, reason } = kc.detectBackend();
640
+ console.log('');
641
+ console.log(' 🔐 CRBRO keychain');
642
+ console.log(' ─────────────────');
643
+ console.log(` Backend: ${backend || '❌ none available'}`);
644
+ if (reason) console.log(` Reason: ${reason}`);
645
+ if (backend === 'windows-dpapi') {
646
+ const dir = process.env['CRBRO_KEYS_DIR'] || join(homedir(), '.crbro-keys');
647
+ console.log(` Store: ${join(dir, 'keys.dpapi')}`);
648
+ console.log(' Sealed to this Windows account: copied to another');
649
+ console.log(' machine or lifted from a backup, it is unreadable.');
650
+ }
651
+ console.log('');
652
+ console.log(' The store lives outside the brain. No sync, no team space and no');
653
+ console.log(' crbro_share can reach it — per machine, on purpose.');
654
+ console.log('');
655
+ return;
656
+ }
657
+
658
+ if (sub === 'list') {
659
+ const secrets = kc.listSecrets();
660
+ console.log('');
661
+ if (secrets.length === 0) {
662
+ console.log(' 🔐 No credentials stored on this machine yet.');
663
+ console.log(' Store one: npx crbro-memory secret set MY_TOKEN');
664
+ console.log('');
665
+ return;
666
+ }
667
+ console.log(` 🔐 ${secrets.length} credential(s) — names only, values are never listed.`);
668
+ console.log(' ──────────────────────────────────────────────────────────');
669
+ for (const s of secrets) {
670
+ console.log(` ${s.name.padEnd(28)} ${s.updated} ${s.description || ''}`.trimEnd());
671
+ }
672
+ console.log('');
673
+ return;
674
+ }
675
+
676
+ if (!name) {
677
+ console.error(` ❌ Missing name. Usage: npx crbro-memory secret ${sub || '<set|get|list|remove|status>'} NAME`);
678
+ process.exit(1);
679
+ }
680
+
681
+ if (sub === 'set') {
682
+ const value = process.stdin.isTTY
683
+ ? await readHidden(` Value for ${name} (input hidden, Enter to finish): `)
684
+ : await readPiped();
685
+ if (!value) {
686
+ console.error(' ❌ Empty value. Nothing was stored.');
687
+ process.exit(1);
688
+ }
689
+ kc.setSecret(name, value, flagValue('--description'));
690
+ console.log(` ✅ ${name} sealed in the OS keychain. CRBRO keeps no copy.`);
691
+ console.log(' Record only the NAME in the brain, never the value.');
692
+ return;
693
+ }
694
+
695
+ if (sub === 'get') {
696
+ const value = kc.getSecret(name);
697
+ if (value === null) {
698
+ console.error(` ❌ ${name} not found on this machine.`);
699
+ process.exit(1);
700
+ }
701
+ if (process.stdout.isTTY) {
702
+ process.stderr.write(' ⚠️ Printing a credential to the screen. Pipe it instead to keep it out of the scrollback.\n');
703
+ }
704
+ process.stdout.write(value + '\n');
705
+ return;
706
+ }
707
+
708
+ if (sub === 'remove') {
709
+ if (!args.includes('--yes')) {
710
+ console.error(` ❌ This deletes ${name} from the keychain and cannot be undone.`);
711
+ console.error(` Re-run to confirm: npx crbro-memory secret remove ${name} --yes`);
712
+ process.exit(1);
713
+ }
714
+ const removed = kc.removeSecret(name);
715
+ console.log(removed ? ` ✅ ${name} removed.` : ` ⚪ ${name} was not there. Nothing changed.`);
716
+ return;
717
+ }
718
+
719
+ console.error(' Usage: npx crbro-memory secret <set|get|list|remove|status> [NAME]');
720
+ process.exit(1);
721
+ } catch (err) {
722
+ console.error(` ❌ ${err instanceof Error ? err.message : err}`);
723
+ process.exit(1);
724
+ }
725
+ }).catch((err) => {
726
+ console.error(` ❌ ${err instanceof Error ? err.message : err}`);
727
+ process.exit(1);
728
+ });
729
+
587
730
  } else if (command === '--help' || command === '-h') {
588
731
  // ─── Help ──────────────────────────────────────────────────────
589
732
  console.log('');
@@ -609,6 +752,13 @@ if (command === 'init') {
609
752
  console.log(' npx crbro-memory semantic build Embed the whole brain once (needs CRBRO_SEMANTIC=1)');
610
753
  console.log(' npx crbro-memory semantic status Runtime, model and whether it is enabled');
611
754
  console.log('');
755
+ console.log(' Credentials (per machine, sealed in the OS keychain):');
756
+ console.log(' npx crbro-memory secret set NAME Store one; the value is read from stdin, never argv');
757
+ console.log(' npx crbro-memory secret list Names only, never values');
758
+ console.log(' npx crbro-memory secret get NAME Print one; pipe it to keep it off the screen');
759
+ console.log(' npx crbro-memory secret remove NAME --yes');
760
+ console.log(' npx crbro-memory secret status Which keychain this machine offers');
761
+ console.log('');
612
762
  console.log(' Server:');
613
763
  console.log(' npx crbro-memory Start MCP server (stdio)');
614
764
  console.log('');
@@ -1,5 +1,11 @@
1
1
  import type { Brain } from './brain.js';
2
2
  import type { SessionLog } from '../types/index.js';
3
+ /**
4
+ * Accept 'session_2026-09-03' or '2026-09-03'; null for anything that is not
5
+ * a day id, so a reference never turns into a path — "../manifest" was read
6
+ * back as a log before this check existed.
7
+ */
8
+ export declare function normalizeSessionId(ref: string): string | null;
3
9
  export declare class Hippocampus {
4
10
  private brain;
5
11
  constructor(brain: Brain);
@@ -39,6 +45,8 @@ export declare class Hippocampus {
39
45
  date: string;
40
46
  kinds: string[];
41
47
  }>>;
48
+ /** One log by id, whole; null when the reference is not a day id or the log is gone. */
49
+ readSession(sessionRef: string): Promise<SessionLog | null>;
42
50
  /**
43
51
  * Delete one session log, after copying it to quarantine — the same
44
52
  * "nothing is deleted outright" rule the cortex follows.
@@ -1 +1 @@
1
- {"version":3,"file":"hippocampus.d.ts","sourceRoot":"","sources":["../../src/engine/hippocampus.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAUpD,qBAAa,WAAW;IACV,OAAO,CAAC,KAAK;gBAAL,KAAK,EAAE,KAAK;IAEhC;;;;;;;OAOG;IACG,UAAU,CAAC,IAAI,EAAE;QACrB,OAAO,EAAE,MAAM,CAAC;QAChB,cAAc,EAAE,MAAM,EAAE,CAAC;QACzB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,mBAAmB,CAAC,EAAE,MAAM,CAAC;QAC7B,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC3B,GAAG,OAAO,CAAC,UAAU,GAAG;QAAE,QAAQ,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IA0ChD;;OAEG;IACG,YAAY,CAAC,KAAK,GAAE,MAAW,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAe7D;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAK5C;;;;OAIG;IACG,YAAY,IAAI,OAAO,CAAC,KAAK,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IAW3F;;;OAGG;IACG,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;CAgBlH"}
1
+ {"version":3,"file":"hippocampus.d.ts","sourceRoot":"","sources":["../../src/engine/hippocampus.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAIpD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI7D;AAED,qBAAa,WAAW;IACV,OAAO,CAAC,KAAK;gBAAL,KAAK,EAAE,KAAK;IAEhC;;;;;;;OAOG;IACG,UAAU,CAAC,IAAI,EAAE;QACrB,OAAO,EAAE,MAAM,CAAC;QAChB,cAAc,EAAE,MAAM,EAAE,CAAC;QACzB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,mBAAmB,CAAC,EAAE,MAAM,CAAC;QAC7B,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC3B,GAAG,OAAO,CAAC,UAAU,GAAG;QAAE,QAAQ,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IA0ChD;;OAEG;IACG,YAAY,CAAC,KAAK,GAAE,MAAW,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAe7D;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAK5C;;;;OAIG;IACG,YAAY,IAAI,OAAO,CAAC,KAAK,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IAW3F,wFAAwF;IAClF,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAMjE;;;OAGG;IACG,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;CAiBlH"}
@@ -3,14 +3,20 @@
3
3
  // Session logging and memory formation
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
5
5
  exports.Hippocampus = void 0;
6
+ exports.normalizeSessionId = normalizeSessionId;
6
7
  const fs_js_1 = require("../utils/fs.js");
7
8
  const ids_js_1 = require("../utils/ids.js");
8
9
  const secrets_js_1 = require("./secrets.js");
9
10
  const SESSION_PREFIX = 'session_';
10
- /** Accept 'session_2026-09-03' or '2026-09-03'. */
11
+ /**
12
+ * Accept 'session_2026-09-03' or '2026-09-03'; null for anything that is not
13
+ * a day id, so a reference never turns into a path — "../manifest" was read
14
+ * back as a log before this check existed.
15
+ */
11
16
  function normalizeSessionId(ref) {
12
17
  const t = (ref || '').trim();
13
- return t.startsWith(SESSION_PREFIX) ? t : `${SESSION_PREFIX}${t}`;
18
+ const id = t.startsWith(SESSION_PREFIX) ? t : `${SESSION_PREFIX}${t}`;
19
+ return /^session_\d{4}-\d{2}-\d{2}$/.test(id) ? id : null;
14
20
  }
15
21
  class Hippocampus {
16
22
  brain;
@@ -99,12 +105,21 @@ class Hippocampus {
99
105
  }
100
106
  return out;
101
107
  }
108
+ /** One log by id, whole; null when the reference is not a day id or the log is gone. */
109
+ async readSession(sessionRef) {
110
+ const session_id = normalizeSessionId(sessionRef);
111
+ if (!session_id)
112
+ return null;
113
+ return (0, fs_js_1.readJSON)(this.brain.paths.session(session_id));
114
+ }
102
115
  /**
103
116
  * Delete one session log, after copying it to quarantine — the same
104
117
  * "nothing is deleted outright" rule the cortex follows.
105
118
  */
106
119
  async forgetSession(sessionRef) {
107
120
  const session_id = normalizeSessionId(sessionRef);
121
+ if (!session_id)
122
+ return { session_id: (sessionRef || '').trim(), removed: false, backup: null };
108
123
  const ruta = this.brain.paths.session(session_id);
109
124
  const log = await (0, fs_js_1.readJSON)(ruta);
110
125
  if (!log)
@@ -1 +1 @@
1
- {"version":3,"file":"hippocampus.js","sourceRoot":"","sources":["../../src/engine/hippocampus.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"hippocampus.js","sourceRoot":"","sources":["../../src/engine/hippocampus.ts"],"names":[],"mappings":"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"}
@@ -10,10 +10,30 @@ import type { Neuron, SearchResult } from '../types/index.js';
10
10
  * 2.0) are no longer chunked, so an index built before it must be rebuilt or
11
11
  * it would keep serving entries that were retired.
12
12
  */
13
- export declare const INDEX_VERSION = 6;
13
+ export declare const INDEX_VERSION = 7;
14
14
  /** Filled in by search/searchMany when the caller passes it: what the list left out. */
15
15
  export interface SearchStats {
16
16
  matched_neurons?: number;
17
+ /** Day logs whose summary mentions the query — a list of their own, never mixed with neurons. */
18
+ sessions?: SessionMatch[];
19
+ /** How many day logs had a hit at all, before the cap on sessions: three never reads as "only three". */
20
+ sessions_total?: number;
21
+ }
22
+ /**
23
+ * A session log that matched. The diary is searchable since 2.2, but it is
24
+ * narrative, not knowledge: a session hit points at where something was
25
+ * told, it never outranks the fact that answers. Read the whole log with
26
+ * crbro_inspect view=sessions session=<session_id>.
27
+ */
28
+ export interface SessionMatch {
29
+ session_id: string;
30
+ date: string;
31
+ entry_id: string;
32
+ preview: string;
33
+ chars: number;
34
+ matched_terms: number;
35
+ query_terms: number;
36
+ confidence: 'strong' | 'weak';
17
37
  }
18
38
  export declare class SearchEngine {
19
39
  private brain;
@@ -124,6 +144,8 @@ export declare class SearchEngine {
124
144
  }): Promise<{
125
145
  results: SearchResult[];
126
146
  matched_neurons: number;
147
+ sessions: SessionMatch[];
148
+ sessions_total: number;
127
149
  }>;
128
150
  /**
129
151
  * Blend the vector ranking into the lexical one with reciprocal-rank
@@ -164,6 +186,16 @@ export declare class SearchEngine {
164
186
  private searchTerm;
165
187
  /** Build and insert every chunk of a neuron. */
166
188
  private insertNeuronChunks;
189
+ /**
190
+ * Index one session log, replacing whatever it had. Called by consolidate
191
+ * for the day just logged and by rebuild for the whole diary.
192
+ */
193
+ indexSession(log: {
194
+ session_id: string;
195
+ date?: string;
196
+ summary?: string;
197
+ }): Promise<void>;
198
+ private insertSessionChunks;
167
199
  private put;
168
200
  /**
169
201
  * Drop every chunk belonging to a neuron.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/search/index.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAQ,MAAM,mBAAmB,CAAC;AAEpE;;;;;;;;;GASG;AACH,eAAO,MAAM,aAAa,IAAI,CAAC;AAoE/B,wFAAwF;AACxF,MAAM,WAAW,WAAW;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAiBD,qBAAa,YAAY;IA2BX,OAAO,CAAC,KAAK;IA1BzB,OAAO,CAAC,EAAE,CAAyB;IACnC,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,YAAY,CAA+B;IAEnD;;;;;;;OAOG;IACH,OAAO,CAAC,cAAc,CAAkC;IAExD;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAA8B;IAC9C,gEAAgE;IAChE,OAAO,CAAC,YAAY,CAA6B;IACjD,gEAAgE;IAChE,OAAO,CAAC,SAAS,CAA8B;gBAE3B,KAAK,EAAE,KAAK;IAMhC,6DAA6D;IAC7D,aAAa,IAAI,MAAM;IAMvB;;;;;;;;OAQG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA6B3B;;;;;;;;;;OAUG;YACW,OAAO;IAQrB;;;;;OAKG;IACG,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC;IA0BhC;;OAEG;IACG,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWhD;;;OAGG;IACG,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOnD,6EAA6E;IACvE,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAItC,mCAAmC;IAC7B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAgB9B,0EAA0E;IACpE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAM5B;;;;;;;;OAQG;IACG,MAAM,CACV,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,WAAW,CAAA;KAAE,GACjE,OAAO,CAAC,YAAY,EAAE,CAAC;IAwE1B;;;;;;OAMG;IACG,UAAU,CACd,OAAO,EAAE,MAAM,EAAE,EACjB,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,WAAW,CAAA;KAAO,GACrE,OAAO,CAAC,YAAY,EAAE,CAAC;IAmD1B;;;;OAIG;IACG,mBAAmB,CACvB,OAAO,EAAE,MAAM,EAAE,EACjB,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAO,GAChD,OAAO,CAAC;QAAE,OAAO,EAAE,YAAY,EAAE,CAAC;QAAC,eAAe,EAAE,MAAM,CAAA;KAAE,CAAC;IAMhE;;;;;;OAMG;YACW,YAAY;IA0C1B,mEAAmE;YACrD,eAAe;IAY7B;;;;;;;;;;;;OAYG;YACW,kBAAkB;IAiGhC;;;;OAIG;IACH,OAAO,CAAC,SAAS;IA2BjB;;;;;OAKG;YACW,UAAU;IAyCxB,gDAAgD;YAClC,kBAAkB;YA8HlB,GAAG;IAwBjB;;;;;;;OAOG;YACW,kBAAkB;IAkBhC;;;;OAIG;IACH,OAAO,CAAC,eAAe;IAoBvB,OAAO,CAAC,SAAS;IAWjB,OAAO,CAAC,SAAS;IAYjB;;;;OAIG;YACW,eAAe;CAO9B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/search/index.ts"],"names":[],"mappings":"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"}
@@ -36,7 +36,7 @@ const ops_js_1 = require("../sync/ops.js");
36
36
  * 2.0) are no longer chunked, so an index built before it must be rebuilt or
37
37
  * it would keep serving entries that were retired.
38
38
  */
39
- exports.INDEX_VERSION = 6; // 4: keys field (1.15) · 5: entry_status (2.0) · 6: eid per chunk (2.1)
39
+ exports.INDEX_VERSION = 7; // 4: keys field (1.15) · 5: entry_status (2.0) · 6: eid per chunk (2.1) · 7: session logs indexed (2.2)
40
40
  /** Weight given to a neuron for each *additional* chunk that matches. */
41
41
  const BREADTH_BONUS = 0.05;
42
42
  /** Cap on the breadth bonus — breadth is a tiebreaker, never the main signal. */
@@ -95,6 +95,62 @@ const SCHEMA = {
95
95
  added: 'string',
96
96
  heat: 'number',
97
97
  };
98
+ const SESSION_MATCHES = 3;
99
+ const SESSION_CHUNK = 700;
100
+ /** Best chunk per session, top sessions by coverage-weighted score; total is how many days had a hit. */
101
+ function topSessions(hits, queryTerms) {
102
+ const porSesion = new Map();
103
+ for (const h of hits.values()) {
104
+ const prev = porSesion.get(h.session);
105
+ if (!prev || h.score * h.matched > prev.score * prev.matched)
106
+ porSesion.set(h.session, h);
107
+ }
108
+ const matches = [...porSesion.values()]
109
+ // Ties broken by id, newest first, so the list is the same on every run.
110
+ .sort((a, b) => b.score * b.matched - a.score * a.matched || (a.session < b.session ? 1 : -1))
111
+ .slice(0, SESSION_MATCHES)
112
+ .map((h) => {
113
+ const strong = queryTerms <= 1 ? h.matched >= 1 : h.matched >= 2 && h.matched / queryTerms >= 0.5;
114
+ return {
115
+ session_id: h.session,
116
+ date: h.date,
117
+ entry_id: h.eid,
118
+ preview: h.text.length > 300 ? `${h.text.slice(0, 300).trimEnd()}…` : h.text,
119
+ chars: h.text.length,
120
+ matched_terms: h.matched,
121
+ query_terms: queryTerms,
122
+ confidence: strong ? 'strong' : 'weak',
123
+ };
124
+ });
125
+ return { matches, total: porSesion.size };
126
+ }
127
+ /**
128
+ * A summary becomes paragraphs of at most SESSION_CHUNK characters: a match
129
+ * then points at the paragraph that mentions the thing, and the preview is
130
+ * the paragraph itself, not the opening of a 7,000-character day.
131
+ */
132
+ function sessionChunks(summary) {
133
+ const out = [];
134
+ for (const parrafo of summary.split(/\n-{3,}\n|\n{2,}/)) {
135
+ const p = parrafo.trim();
136
+ if (!p)
137
+ continue;
138
+ if (p.length <= SESSION_CHUNK) {
139
+ out.push(p);
140
+ continue;
141
+ }
142
+ let resto = p;
143
+ while (resto.length > SESSION_CHUNK) {
144
+ const corte = resto.lastIndexOf(' ', SESSION_CHUNK);
145
+ const at = corte > SESSION_CHUNK / 2 ? corte : SESSION_CHUNK;
146
+ out.push(resto.slice(0, at).trim());
147
+ resto = resto.slice(at).trim();
148
+ }
149
+ if (resto)
150
+ out.push(resto);
151
+ }
152
+ return out;
153
+ }
98
154
  class SearchEngine {
99
155
  brain;
100
156
  db = null;
@@ -184,8 +240,10 @@ class SearchEngine {
184
240
  if (indexAt === 0)
185
241
  return true;
186
242
  const cortexAt = await (0, fs_js_1.newestMtime)(this.brain.paths.cortex);
243
+ // The diary is indexed too (2.2): a log written by another process counts.
244
+ const diaryAt = await (0, fs_js_1.newestMtime)(this.brain.paths.hippocampus);
187
245
  // One second of slack: a write landing during a rebuild is not staleness.
188
- return cortexAt > indexAt + 1000;
246
+ return Math.max(cortexAt, diaryAt) > indexAt + 1000;
189
247
  }
190
248
  /**
191
249
  * Rebuild the whole index from the cortex.
@@ -209,6 +267,17 @@ class SearchEngine {
209
267
  // Skip unreadable neuron, keep going.
210
268
  }
211
269
  }
270
+ // The diary too (2.2): every session log, as paragraphs.
271
+ for (const id of await (0, fs_js_1.listJSONFiles)(this.brain.paths.hippocampus)) {
272
+ try {
273
+ const log = await (0, fs_js_1.readJSON)(this.brain.paths.session(id));
274
+ if (log?.summary)
275
+ await this.insertSessionChunks(log);
276
+ }
277
+ catch {
278
+ // Skip unreadable log, keep going.
279
+ }
280
+ }
212
281
  // Embeddings off the critical path: a rebuild after an upgrade must not
213
282
  // hold boot for minutes on a big brain. Recall serves what is embedded so
214
283
  // far; the vectors go to disk with the next index write.
@@ -293,6 +362,7 @@ class SearchEngine {
293
362
  if (terms.length === 0)
294
363
  return [];
295
364
  const perChunk = new Map();
365
+ const perSession = new Map();
296
366
  for (const term of terms) {
297
367
  const hits = await this.searchTerm(term, options?.domain);
298
368
  if (hits.length === 0)
@@ -302,6 +372,19 @@ class SearchEngine {
302
372
  const doc = hit.document;
303
373
  const key = doc.id;
304
374
  const normalised = best > 0 ? hit.score / best : 0;
375
+ // Session logs are searched, but never as neurons: they keep a list
376
+ // of their own, so a long narrative cannot outrank the fact that
377
+ // answers, and the neuron path below never sees them.
378
+ if (doc.kind === 'session') {
379
+ const prev = perSession.get(key);
380
+ if (prev) {
381
+ prev.score += normalised;
382
+ prev.matched += 1;
383
+ }
384
+ else
385
+ perSession.set(key, { session: String(doc.name || ''), date: String(doc.added || ''), text: String(doc.text || ''), eid: String(doc.eid || ''), score: normalised, matched: 1 });
386
+ continue;
387
+ }
305
388
  const existing = perChunk.get(key);
306
389
  if (existing) {
307
390
  existing.score += normalised;
@@ -334,6 +417,11 @@ class SearchEngine {
334
417
  if (this.semantic && this.semantic.ready() && this.semantic.count() > 0) {
335
418
  await this.fuseSemantic(query, perChunk, options?.domain);
336
419
  }
420
+ if (options?.stats) {
421
+ const top = topSessions(perSession, terms.length);
422
+ options.stats.sessions = top.matches;
423
+ options.stats.sessions_total = top.total;
424
+ }
337
425
  if (perChunk.size === 0)
338
426
  return [];
339
427
  // Group chunks by neuron, best first.
@@ -367,7 +455,36 @@ class SearchEngine {
367
455
  if (distintas.length === 1)
368
456
  return this.search(distintas[0], options);
369
457
  const limit = options.limit ?? 10;
370
- const listas = await Promise.all(distintas.map(q => this.search(q, { ...options, limit: Math.max(limit, 10) * 2 })));
458
+ const porConsulta = distintas.map(() => ({}));
459
+ const listas = await Promise.all(distintas.map((q, i) => this.search(q, { ...options, stats: porConsulta[i], limit: Math.max(limit, 10) * 2 })));
460
+ if (options.stats) {
461
+ // Session hits: a day that several phrasings point at ranks first, then
462
+ // by accumulated coverage (matched over query terms, so 2/2 beats 3/8).
463
+ // The row kept is the best-covered one, strong if any phrasing found it
464
+ // so — and the same phrasings in any order give the same list.
465
+ const cobertura = (m) => (m.query_terms > 0 ? m.matched_terms / m.query_terms : 0);
466
+ const fusion = new Map();
467
+ for (const s of porConsulta) {
468
+ for (const m of s.sessions || []) {
469
+ const f = fusion.get(m.session_id);
470
+ if (!f) {
471
+ fusion.set(m.session_id, { row: m, cobertura: cobertura(m), frases: 1, strong: m.confidence === 'strong' });
472
+ continue;
473
+ }
474
+ f.cobertura += cobertura(m);
475
+ f.frases += 1;
476
+ f.strong = f.strong || m.confidence === 'strong';
477
+ if (cobertura(m) > cobertura(f.row) || (cobertura(m) === cobertura(f.row) && m.matched_terms > f.row.matched_terms))
478
+ f.row = m;
479
+ }
480
+ }
481
+ options.stats.sessions = [...fusion.values()]
482
+ .sort((a, b) => b.frases - a.frases || b.cobertura - a.cobertura || (a.row.session_id < b.row.session_id ? 1 : -1))
483
+ .slice(0, SESSION_MATCHES)
484
+ .map(f => ({ ...f.row, confidence: f.strong ? 'strong' : f.row.confidence }));
485
+ // Each phrasing counted its own days: the union is at least the largest count.
486
+ options.stats.sessions_total = Math.max(fusion.size, ...porConsulta.map(s => s.sessions_total || 0));
487
+ }
371
488
  const fused = new Map();
372
489
  const alsoOf = (r) => alsoLine(r.matching_content, r.matched_kind || '', r.matched_added || '', r.entry_id);
373
490
  for (const lista of listas) {
@@ -424,7 +541,8 @@ class SearchEngine {
424
541
  async searchManyWithStats(queries, options = {}) {
425
542
  const stats = {};
426
543
  const results = await this.searchMany(queries, { ...options, stats });
427
- return { results, matched_neurons: stats.matched_neurons ?? results.length };
544
+ const sessions = stats.sessions ?? [];
545
+ return { results, matched_neurons: stats.matched_neurons ?? results.length, sessions, sessions_total: stats.sessions_total ?? sessions.length };
428
546
  }
429
547
  /**
430
548
  * Blend the vector ranking into the lexical one with reciprocal-rank
@@ -455,7 +573,7 @@ class SearchEngine {
455
573
  catch {
456
574
  doc = undefined;
457
575
  }
458
- if (!doc || doc.kind === 'header')
576
+ if (!doc || doc.kind === 'header' || doc.kind === 'session')
459
577
  continue;
460
578
  if (domain && doc.domain !== domain)
461
579
  continue;
@@ -642,7 +760,9 @@ class SearchEngine {
642
760
  // Filtered here, not with an Orama `where` clause: a `where` on a
643
761
  // plain string field matches nothing at all, so the old code turned
644
762
  // every domain-scoped recall into zero results (measured).
645
- return domain ? hits.filter(h => h.document.domain === domain) : hits;
763
+ // Day logs have no domain: a domain-scoped recall still lists the days
764
+ // that mention it, in their own list, or the diary vanished in silence.
765
+ return domain ? hits.filter(h => h.document.kind === 'session' || h.document.domain === domain) : hits;
646
766
  };
647
767
  let hits = await run(0);
648
768
  // Try the other grammatical number too, and keep the best score each
@@ -662,8 +782,14 @@ class SearchEngine {
662
782
  }
663
783
  hits = [...mejor.values()].sort((a, b) => b.score - a.score);
664
784
  }
665
- if (hits.length === 0 && term.length >= 5) {
666
- hits = await run(1);
785
+ // Decided on neuron hits, not on hits at all: since 2.2 a day log that
786
+ // repeats the user's typo verbatim is an exact hit, and it must not switch
787
+ // off the slack that still finds the fact spelled right.
788
+ if (term.length >= 5 && !hits.some(h => h.document.kind !== 'session')) {
789
+ const vistos = new Set(hits.map(h => h.document.id));
790
+ const fuzzy = (await run(1)).filter(h => h.document.kind !== 'session' && !vistos.has(h.document.id));
791
+ if (fuzzy.length > 0)
792
+ hits = [...hits, ...fuzzy].sort((a, b) => b.score - a.score);
667
793
  }
668
794
  return hits;
669
795
  }
@@ -795,6 +921,38 @@ class SearchEngine {
795
921
  });
796
922
  }
797
923
  }
924
+ /**
925
+ * Index one session log, replacing whatever it had. Called by consolidate
926
+ * for the day just logged and by rebuild for the whole diary.
927
+ */
928
+ async indexSession(log) {
929
+ if (!this.db)
930
+ await this.init();
931
+ if (!this.db)
932
+ return;
933
+ await this.removeNeuronChunks(`session:${log.session_id}`);
934
+ if (log.summary)
935
+ await this.insertSessionChunks(log);
936
+ this.markDirty();
937
+ }
938
+ async insertSessionChunks(log) {
939
+ const trozos = sessionChunks(String(log.summary || ''));
940
+ for (let i = 0; i < trozos.length; i++) {
941
+ await this.put({
942
+ id: (0, hash_js_1.chunkId)(`session:${log.session_id}`, trozos[i]),
943
+ neuron: `session:${log.session_id}`,
944
+ name: log.session_id,
945
+ text: trozos[i],
946
+ keys: '',
947
+ kind: 'session',
948
+ eid: `${log.session_id}#${i}`,
949
+ domain: '',
950
+ tags: '',
951
+ added: log.date || '',
952
+ heat: 0,
953
+ });
954
+ }
955
+ }
798
956
  async put(doc) {
799
957
  try {
800
958
  await (0, orama_1.insert)(this.db, doc);
@@ -815,8 +973,9 @@ class SearchEngine {
815
973
  }
816
974
  ids.add(String(doc.id));
817
975
  // Headers are name + tags: lexical only, they would only add noise to
818
- // the vector index.
819
- if (this.semantic && doc.kind !== 'header') {
976
+ // the vector index. Session logs stay lexical too, for now: the words a
977
+ // day was described with are the words it is asked about.
978
+ if (this.semantic && doc.kind !== 'header' && doc.kind !== 'session') {
820
979
  this.pendingEmbed.set(String(doc.id), String(doc.text || ''));
821
980
  }
822
981
  }