ralph-hero-knowledge-index 0.1.53 → 0.1.55

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ralph-knowledge",
3
- "version": "0.1.53",
3
+ "version": "0.1.55",
4
4
  "description": "Knowledge graph for ralph-hero: semantic search, relationship traversal, and document indexing across thoughts/ documents. Optional companion to ralph-hero.",
5
5
  "author": {
6
6
  "name": "Chad Dubiel",
package/.mcp.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "mcpServers": {
3
3
  "ralph-knowledge": {
4
4
  "command": "npx",
5
- "args": ["-y", "ralph-hero-knowledge-index@0.1.52"]
5
+ "args": ["-y", "ralph-hero-knowledge-index@0.1.53"]
6
6
  }
7
7
  }
8
8
  }
@@ -940,7 +940,7 @@ export function chunkText(text: string, opts: ChunkerOptions = {}): Chunk[] {
940
940
  <div class='footer quiet pad2 space-top1 center small'>
941
941
  Code coverage generated by
942
942
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
943
- at 2026-06-29T08:45:57.753Z
943
+ at 2026-07-26T13:32:06.309Z
944
944
  </div>
945
945
  <script src="prettify.js"></script>
946
946
  <script>
@@ -385,7 +385,7 @@ export function loadConfig(): KnowledgeConfig {
385
385
  <div class='footer quiet pad2 space-top1 center small'>
386
386
  Code coverage generated by
387
387
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
388
- at 2026-06-29T08:45:57.753Z
388
+ at 2026-07-26T13:32:06.309Z
389
389
  </div>
390
390
  <script src="prettify.js"></script>
391
391
  <script>
@@ -1897,7 +1897,7 @@ export class KnowledgeDB {
1897
1897
  <div class='footer quiet pad2 space-top1 center small'>
1898
1898
  Code coverage generated by
1899
1899
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
1900
- at 2026-06-29T08:45:57.753Z
1900
+ at 2026-07-26T13:32:06.309Z
1901
1901
  </div>
1902
1902
  <script src="prettify.js"></script>
1903
1903
  <script>
@@ -763,7 +763,7 @@ export function prepareTextForEmbedding(
763
763
  <div class='footer quiet pad2 space-top1 center small'>
764
764
  Code coverage generated by
765
765
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
766
- at 2026-06-29T08:45:57.753Z
766
+ at 2026-07-26T13:32:06.309Z
767
767
  </div>
768
768
  <script src="prettify.js"></script>
769
769
  <script>
@@ -199,7 +199,7 @@ export function findMarkdownFiles(dir: string, matcher?: IgnoreMatcher): string[
199
199
  <div class='footer quiet pad2 space-top1 center small'>
200
200
  Code coverage generated by
201
201
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
202
- at 2026-06-29T08:45:57.753Z
202
+ at 2026-07-26T13:32:06.309Z
203
203
  </div>
204
204
  <script src="prettify.js"></script>
205
205
  <script>
@@ -295,7 +295,7 @@ export function formatTraverseResults(
295
295
  <div class='footer quiet pad2 space-top1 center small'>
296
296
  Code coverage generated by
297
297
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
298
- at 2026-06-29T08:45:57.753Z
298
+ at 2026-07-26T13:32:06.309Z
299
299
  </div>
300
300
  <script src="prettify.js"></script>
301
301
  <script>
@@ -670,7 +670,7 @@ export function writeTypeIndex(
670
670
  const superseded = docs.filter((d) =&gt; d.status === "superseded");
671
671
  &nbsp;
672
672
  const sortByDate = (a: ParsedDocument, b: ParsedDocument) =&gt;
673
- (b.date ?? <span class="branch-1 cbranch-no" title="branch not covered" >"").l</span>ocaleCompare(a.date ?? <span class="branch-1 cbranch-no" title="branch not covered" >"")</span>;
673
+ (b.date ?? <span class="branch-1 cbranch-no" title="branch not covered" >"")</span>.localeCompare(a.date ?? <span class="branch-1 cbranch-no" title="branch not covered" >"")</span>;
674
674
  active.sort(sortByDate);
675
675
  superseded.sort(sortByDate);
676
676
  &nbsp;
@@ -791,7 +791,7 @@ export function writeIssueHubs(outDir: string, allDocs: ParsedDocument[]): void
791
791
  const RECENT_LIMIT = 20;
792
792
  &nbsp;
793
793
  export function writeMasterIndex(outDir: string, allDocs: ParsedDocument[], hasUncategorized = false): void {
794
- const sorted = [...allDocs].sort((a, b) =&gt; (b.date ?? <span class="branch-1 cbranch-no" title="branch not covered" >"").l</span>ocaleCompare(a.date ?? <span class="branch-1 cbranch-no" title="branch not covered" >"")</span>);
794
+ const sorted = [...allDocs].sort((a, b) =&gt; (b.date ?? <span class="branch-1 cbranch-no" title="branch not covered" >"")</span>.localeCompare(a.date ?? <span class="branch-1 cbranch-no" title="branch not covered" >"")</span>);
795
795
  const recent = sorted.slice(0, RECENT_LIMIT);
796
796
  &nbsp;
797
797
  const lines: string[] = [
@@ -946,7 +946,7 @@ export function generateIndexes(outDir: string, allDocs: ParsedDocument[]): void
946
946
  <div class='footer quiet pad2 space-top1 center small'>
947
947
  Code coverage generated by
948
948
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
949
- at 2026-06-29T08:45:57.753Z
949
+ at 2026-07-26T13:32:06.309Z
950
950
  </div>
951
951
  <script src="prettify.js"></script>
952
952
  <script>
@@ -292,7 +292,7 @@ export class GraphBuilder {
292
292
  <div class='footer quiet pad2 space-top1 center small'>
293
293
  Code coverage generated by
294
294
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
295
- at 2026-06-29T08:45:57.753Z
295
+ at 2026-07-26T13:32:06.309Z
296
296
  </div>
297
297
  <script src="prettify.js"></script>
298
298
  <script>
@@ -2386,9 +2386,9 @@ export function registerGraphTools(server: McpServer, db: KnowledgeDB): void {
2386
2386
  // Run Louvain to partition, then extract the community subgraph
2387
2387
  const partition: Record&lt;string, number&gt; =
2388
2388
  graph.size &gt; 0
2389
- ? louvain(graph, { rng: () =&gt; 0.5 }<span class="fstat-no" title="function not covered" ><span class="branch-1 cbranch-no" title="branch not covered" >)</span></span>
2389
+ ? louvain(graph, { rng: () =&gt; 0.5 })
2390
2390
  : // No edges — each node is its own community, numbered by iteration order
2391
- (() =&gt; {
2391
+ (() =&gt; <span class="fstat-no" title="function not covered" ><span class="branch-1 cbranch-no" title="branch not covered" >{</span></span>
2392
2392
  const p: Record&lt;string, number&gt; = <span class="cstat-no" title="statement not covered" >{};</span>
2393
2393
  let idx = <span class="cstat-no" title="statement not covered" >0;</span>
2394
2394
  <span class="cstat-no" title="statement not covered" > graph.<span class="fstat-no" title="function not covered" >forEachNode((n</span>) =&gt; { <span class="cstat-no" title="statement not covered" >p[n] = idx++; }</span>);</span>
@@ -2821,7 +2821,7 @@ export function registerGraphTools(server: McpServer, db: KnowledgeDB): void {
2821
2821
  <div class='footer quiet pad2 space-top1 center small'>
2822
2822
  Code coverage generated by
2823
2823
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
2824
- at 2026-06-29T08:45:57.753Z
2824
+ at 2026-07-26T13:32:06.309Z
2825
2825
  </div>
2826
2826
  <script src="prettify.js"></script>
2827
2827
  <script>
@@ -1606,7 +1606,7 @@ export class HybridSearch {
1606
1606
  <div class='footer quiet pad2 space-top1 center small'>
1607
1607
  Code coverage generated by
1608
1608
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
1609
- at 2026-06-29T08:45:57.753Z
1609
+ at 2026-07-26T13:32:06.309Z
1610
1610
  </div>
1611
1611
  <script src="prettify.js"></script>
1612
1612
  <script>
@@ -316,7 +316,7 @@ export function loadIgnoreForRoot(
316
316
  <div class='footer quiet pad2 space-top1 center small'>
317
317
  Code coverage generated by
318
318
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
319
- at 2026-06-29T08:45:57.753Z
319
+ at 2026-07-26T13:32:06.309Z
320
320
  </div>
321
321
  <script src="prettify.js"></script>
322
322
  <script>
@@ -386,7 +386,7 @@
386
386
  <div class='footer quiet pad2 space-top1 center small'>
387
387
  Code coverage generated by
388
388
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
389
- at 2026-06-29T08:45:57.753Z
389
+ at 2026-07-26T13:32:06.309Z
390
390
  </div>
391
391
  <script src="prettify.js"></script>
392
392
  <script>
@@ -2052,8 +2052,8 @@ export function createServer(dbPath: string, opts: CreateServerOptions = {}) {
2052
2052
  // avoid touching the real dream-memories tree or paying the ONNX model
2053
2053
  // download in unit tests.
2054
2054
  const rememberFs = opts.rememberFs ?? {
2055
- <span class="fstat-no" title="function not covered" > mkdir: (<span class="cstat-no" title="statement not covered" >d</span>ir: string) =&gt; mkdirSync(dir, { recursive: true }),</span>
2056
- <span class="fstat-no" title="function not covered" > write: (p</span>ath: string, <span class="cstat-no" title="statement not covered" >body: string) =&gt; writeFileSync(path, body, "utf-8"),</span>
2055
+ <span class="fstat-no" title="function not covered" > mkdir: (d</span>ir: string) =&gt; <span class="cstat-no" title="statement not covered" >mkdirSync(dir, { recursive: true }),</span>
2056
+ <span class="fstat-no" title="function not covered" > write: (p</span>ath: string, body: string) =&gt; <span class="cstat-no" title="statement not covered" >writeFileSync(path, body, "utf-8"),</span>
2057
2057
  };
2058
2058
  const rememberReindexImpl = opts.rememberReindex ?? reindexPath;
2059
2059
  const rememberNow = opts.<span class="fstat-no" title="function not covered" ><span class="branch-1 cbranch-no" title="branch not covered" >rememberNow ?? (() =&gt; <span class="cstat-no" title="statement not covered" >n</span>ew Date());</span></span>
@@ -2065,7 +2065,7 @@ export function createServer(dbPath: string, opts: CreateServerOptions = {}) {
2065
2065
  // fail-open local LLM completion; tests inject a stub via `thinkComplete`.
2066
2066
  const thinkComplete =
2067
2067
  opts.<span class="fstat-no" title="function not covered" ><span class="branch-1 cbranch-no" title="branch not covered" >thinkComplete ??</span></span>
2068
- ((<span class="cstat-no" title="statement not covered" >prompt: string) =&gt; createLlmClient().complete(prompt, { maxTokens: 1024 }));</span>
2068
+ ((prompt: string) =&gt; <span class="cstat-no" title="statement not covered" >createLlmClient().complete(prompt, { maxTokens: 1024 }));</span>
2069
2069
  &nbsp;
2070
2070
  server.tool(
2071
2071
  "knowledge_search",
@@ -2776,7 +2776,7 @@ server.connect(transport).catch(console.error);
2776
2776
  <div class='footer quiet pad2 space-top1 center small'>
2777
2777
  Code coverage generated by
2778
2778
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
2779
- at 2026-06-29T08:45:57.753Z
2779
+ at 2026-07-26T13:32:06.309Z
2780
2780
  </div>
2781
2781
  <script src="prettify.js"></script>
2782
2782
  <script>
@@ -592,7 +592,7 @@ export function createLlmClient(opts: LlmClientOptions = {}): LlmClient {
592
592
  <div class='footer quiet pad2 space-top1 center small'>
593
593
  Code coverage generated by
594
594
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
595
- at 2026-06-29T08:45:57.753Z
595
+ at 2026-07-26T13:32:06.309Z
596
596
  </div>
597
597
  <script src="prettify.js"></script>
598
598
  <script>
@@ -622,7 +622,7 @@ export function parseDocument(id: string, path: string, raw: string): ParsedDocu
622
622
  <div class='footer quiet pad2 space-top1 center small'>
623
623
  Code coverage generated by
624
624
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
625
- at 2026-06-29T08:45:57.753Z
625
+ at 2026-07-26T13:32:06.309Z
626
626
  </div>
627
627
  <script src="prettify.js"></script>
628
628
  <script>
@@ -1894,7 +1894,7 @@ export function <span class="fstat-no" title="function not covered" >resolveDirs
1894
1894
  const cliDb = <span class="cstat-no" title="statement not covered" >positional.<span class="fstat-no" title="function not covered" >find(a</span> =&gt; <span class="cstat-no" title="statement not covered" >a.endsWith(".db"))</span>;</span>
1895
1895
  const cliDirs = <span class="cstat-no" title="statement not covered" >positional.<span class="fstat-no" title="function not covered" >filter(a</span> =&gt; <span class="cstat-no" title="statement not covered" >!a.endsWith(".db"))</span>;</span>
1896
1896
  &nbsp;
1897
- const <span class="cstat-no" title="statement not covered" >config = loadConfig();</span>
1897
+ const config = <span class="cstat-no" title="statement not covered" >loadConfig()</span>;
1898
1898
  &nbsp;
1899
1899
  const <span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" >resolveDbPath = (): string =&gt;</span></span>
1900
1900
  <span class="cstat-no" title="statement not covered" > cliDb ??</span>
@@ -1978,9 +1978,9 @@ export async function <span class="fstat-no" title="function not covered" >reind
1978
1978
  ): Promise&lt;{ indexed: boolean }&gt; {
1979
1979
  let db: KnowledgeDB | undefined;
1980
1980
  <span class="cstat-no" title="statement not covered" > try {</span>
1981
- const <span class="cstat-no" title="statement not covered" >absPath = resolve(filePath);</span>
1982
- const <span class="cstat-no" title="statement not covered" >raw = readFileSync(absPath, "utf-8");</span>
1983
- const <span class="cstat-no" title="statement not covered" >id = basename(absPath, ".md");</span>
1981
+ const absPath = <span class="cstat-no" title="statement not covered" >resolve(filePath);</span>
1982
+ const raw = <span class="cstat-no" title="statement not covered" >readFileSync(absPath, "utf-8");</span>
1983
+ const id = <span class="cstat-no" title="statement not covered" >basename(absPath, ".md");</span>
1984
1984
  const relPath = <span class="cstat-no" title="statement not covered" >absPath; // single-path mode — use absolute path verbatim</span>
1985
1985
  &nbsp;
1986
1986
  <span class="cstat-no" title="statement not covered" > db = new KnowledgeDB(dbPath);</span>
@@ -1989,7 +1989,7 @@ export async function <span class="fstat-no" title="function not covered" >reind
1989
1989
  const vec = <span class="cstat-no" title="statement not covered" >new VectorSearch(db);</span>
1990
1990
  <span class="cstat-no" title="statement not covered" > vec.createIndex();</span>
1991
1991
  &nbsp;
1992
- const <span class="cstat-no" title="statement not covered" >parsed = parseDocument(id, relPath, raw);</span>
1992
+ const parsed = <span class="cstat-no" title="statement not covered" >parseDocument(id, relPath, raw);</span>
1993
1993
  &nbsp;
1994
1994
  <span class="cstat-no" title="statement not covered" > if (db.documentExists(parsed.id)) {</span>
1995
1995
  <span class="cstat-no" title="statement not covered" > fts.deleteFtsEntry(parsed.id);</span>
@@ -2086,7 +2086,7 @@ const isMain = process.argv[1]?.endsWith("reindex.js");
2086
2086
  <div class='footer quiet pad2 space-top1 center small'>
2087
2087
  Code coverage generated by
2088
2088
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
2089
- at 2026-06-29T08:45:57.753Z
2089
+ at 2026-07-26T13:32:06.309Z
2090
2090
  </div>
2091
2091
  <script src="prettify.js"></script>
2092
2092
  <script>
@@ -790,7 +790,7 @@ export class Reranker {
790
790
  <div class='footer quiet pad2 space-top1 center small'>
791
791
  Code coverage generated by
792
792
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
793
- at 2026-06-29T08:45:57.753Z
793
+ at 2026-07-26T13:32:06.309Z
794
794
  </div>
795
795
  <script src="prettify.js"></script>
796
796
  <script>
@@ -814,7 +814,7 @@ export class FtsSearch {
814
814
  <div class='footer quiet pad2 space-top1 center small'>
815
815
  Code coverage generated by
816
816
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
817
- at 2026-06-29T08:45:57.753Z
817
+ at 2026-07-26T13:32:06.309Z
818
818
  </div>
819
819
  <script src="prettify.js"></script>
820
820
  <script>
@@ -402,7 +402,7 @@ export function buildThinkPrompt(query: string, sources: ThinkSource[]): string
402
402
  .map((s) =&gt; {
403
403
  const title = s.title ? ` (${s.title})` : <span class="branch-1 cbranch-no" title="branch not covered" >"";</span>
404
404
  const tier = s.tier ? ` {${s.tier}}` : <span class="branch-1 cbranch-no" title="branch not covered" >"";</span>
405
- const snippet = (s.snippet || <span class="branch-1 cbranch-no" title="branch not covered" >"").s</span>lice(0, SNIPPET_CLIP);
405
+ const snippet = (s.snippet || <span class="branch-1 cbranch-no" title="branch not covered" >"")</span>.slice(0, SNIPPET_CLIP);
406
406
  return `[${s.id}]${title}${tier}\n${snippet}`;
407
407
  })
408
408
  .join("\n\n");
@@ -428,7 +428,7 @@ export function buildThinkPrompt(query: string, sources: ThinkSource[]): string
428
428
  export function parseThinkResponse(
429
429
  text: string,
430
430
  ): { answer: string; gaps: string } | null {
431
- let raw = (text || <span class="branch-1 cbranch-no" title="branch not covered" >"").t</span>rim();
431
+ let raw = (text || <span class="branch-1 cbranch-no" title="branch not covered" >"")</span>.trim();
432
432
  if (raw.startsWith("```")) {
433
433
  const firstNl = raw.indexOf("\n");
434
434
  <span class="missing-if-branch" title="else path not taken" >E</span>if (firstNl !== -1) raw = raw.slice(firstNl + 1);
@@ -514,7 +514,7 @@ export async function think(
514
514
  <div class='footer quiet pad2 space-top1 center small'>
515
515
  Code coverage generated by
516
516
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
517
- at 2026-06-29T08:45:57.753Z
517
+ at 2026-07-26T13:32:06.309Z
518
518
  </div>
519
519
  <script src="prettify.js"></script>
520
520
  <script>
@@ -487,7 +487,7 @@ export class Traverser {
487
487
  <div class='footer quiet pad2 space-top1 center small'>
488
488
  Code coverage generated by
489
489
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
490
- at 2026-06-29T08:45:57.753Z
490
+ at 2026-07-26T13:32:06.309Z
491
491
  </div>
492
492
  <script src="prettify.js"></script>
493
493
  <script>
@@ -439,7 +439,7 @@ export class VectorSearch {
439
439
  <div class='footer quiet pad2 space-top1 center small'>
440
440
  Code coverage generated by
441
441
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
442
- at 2026-06-29T08:45:57.753Z
442
+ at 2026-07-26T13:32:06.309Z
443
443
  </div>
444
444
  <script src="prettify.js"></script>
445
445
  <script>
@@ -973,7 +973,7 @@ FN:393,(anonymous_11)
973
973
  FN:411,(anonymous_12)
974
974
  FN:470,(anonymous_13)
975
975
  FN:490,(anonymous_14)
976
- FN:490,(anonymous_15)
976
+ FN:492,(anonymous_15)
977
977
  FN:495,(anonymous_16)
978
978
  FN:500,(anonymous_17)
979
979
  FN:501,(anonymous_18)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ralph-hero-knowledge-index",
3
- "version": "0.1.53",
3
+ "version": "0.1.55",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -34,7 +34,7 @@
34
34
  "graphology-simple-path": "^0.2.0",
35
35
  "graphology-traversal": "^0.3.1",
36
36
  "graphology-types": "^0.24.8",
37
- "ignore": "^7.0.5",
37
+ "ignore": "^7.0.6",
38
38
  "sqlite-vec": "^0.1.9",
39
39
  "yaml": "^2.9.0",
40
40
  "zod": "^3.25.0"
@@ -42,8 +42,8 @@
42
42
  "devDependencies": {
43
43
  "@types/better-sqlite3": "^7.6.13",
44
44
  "@types/node": "^25.9.1",
45
- "@vitest/coverage-v8": "^4.1.7",
46
- "tsx": "^4.22.3",
45
+ "@vitest/coverage-v8": "^4.1.10",
46
+ "tsx": "^4.23.1",
47
47
  "typescript": "^6.0.3",
48
48
  "vitest": "^4.0.0"
49
49
  }
@@ -56,6 +56,8 @@ Scope matters: searching unscoped over a 3000+ document corpus dilutes the signa
56
56
 
57
57
  Use `knowledge_search` (semantic) plus `knowledge_central` (find central documents in communities) to surface 5–10 candidate atomic axioms within the chosen domain.
58
58
 
59
+ **Also read the meta-reflection staging file** `~/projects/thoughts/wiki/_candidates.jsonl` (one JSON object per line: `axiom`, `rationale`, `source_reflection_ids`, `hash`). The weekly `meta_reflect.py` pass (GH-1513) stages pre-distilled wiki candidates here from the reflection tier — treat each as a strong, already-corroborated candidate, but still run it through the full distillation + gates below (staging is a suggestion, never a promotion). Skip any whose `hash` axiom is already an existing wiki entry or already in `_rejected.jsonl`.
60
+
59
61
  A candidate is high-signal if it:
60
62
  - States a principle in declarative form ("X must Y", "always X", "never X", "the rule is")
61
63
  - Recurs across 3+ documents (corroboration is the strongest durability signal)
@@ -1,11 +1,21 @@
1
1
  ---
2
- description: Run the dream-loop — the nightly memory-consolidation pipeline that ingests the last window of raw activity (Gemma lab logs, git history, llm-cli transcripts, Claude Code sessions) as memory_tier=raw documents, then clusters them and synthesizes one memory_tier=reflection per cluster. This is the memorykeepers surface that orchestrates the existing scripts/dream/ infra (ingest.py + reflect.py + logrotate.sh) on demand. Use when the user mentions "dream", "dream-loop", "consolidate memories", "run reflection", "memorykeepers", "ingest raw memories", or wants to manually trigger the memory tiers (raw to reflection) without waiting for the 03:00 launchd fire. The launchd nightly schedule and the model-gate dream-now zsh function are separate; this skill is the in-session manual entrypoint.
3
- argument-hint: "[--since 24h]"
2
+ description: Run, bootstrap, or verify the dream-loop — the nightly memory-consolidation pipeline that ingests the last window of raw activity (Gemma lab logs, git history, llm-cli transcripts, Claude Code sessions) as memory_tier=raw documents, then clusters them and synthesizes one memory_tier=reflection per cluster. This is the memorykeepers surface that orchestrates the existing scripts/dream/ infra (ingest.py + reflect.py + logrotate.sh). Use when the user mentions "dream", "dream-loop", "dream-now", "consolidate memories", "run reflection", "memorykeepers", "ingest raw memories", or wants to manually trigger the memory tiers (raw to reflection) without waiting for the 03:00 launchd fire. Also use for machine wiring and health checks — "bootstrap the dream loop", "set up dream-loop", "install the nightly schedule", "check dream-loop status", "verify the dream loop ran", "did the dream loop ingest anything" — via --mode bootstrap and --mode verify.
3
+ argument-hint: "[--since 24h | --mode bootstrap|verify]"
4
4
  ---
5
5
 
6
6
  # Dream-Loop — Memory Consolidation (memorykeepers)
7
7
 
8
- The dream-loop is the memory-consolidation pipeline for ralph-knowledge. It pulls raw activity into `memory_tier=raw` documents, then clusters them (UMAP/HDBSCAN) and asks a local model to synthesize one `memory_tier=reflection` per cluster. This skill is the in-session manual entrypoint that the memorykeepers team owns.
8
+ The dream-loop is the memory-consolidation pipeline for ralph-knowledge. It pulls raw activity into `memory_tier=raw` documents, then clusters them (UMAP/HDBSCAN) and asks a local model to synthesize one `memory_tier=reflection` per cluster. This skill is the memorykeepers surface for the pipeline: a manual run entrypoint by default, plus `--mode bootstrap` (first-time machine wiring) and `--mode verify` (post-run health checks).
9
+
10
+ ## Modes
11
+
12
+ | Mode | When | What it does |
13
+ |------|------|--------------|
14
+ | *(default)* run | "run the dream loop", "consolidate memories" | Steps 1–6 below: ingest → reflect → logrotate → report |
15
+ | `--mode bootstrap` | first run on a machine, "bootstrap the dream loop" | Delegates to `scripts/dream/bootstrap.sh`; see **Bootstrap** |
16
+ | `--mode verify` | "did it run?", "check dream-loop status" | Read-only health checks; see **Verify** |
17
+
18
+ Resolve the mode from intent, not just the flag — "set up the dream loop on this machine" is bootstrap, "did last night's run work?" is verify, even without `--mode`.
9
19
 
10
20
  ## How you talk
11
21
 
@@ -25,7 +35,7 @@ You refuse to:
25
35
  3. **`~/.ralph/knowledge.config.json` authored** with the `roots` to scan and `dbPath`.
26
36
  4. **A local model reachable** at `RALPH_LLM_URL` (default `http://localhost:8000`) for the reflection synthesis step. If the model is unreachable, the pipeline fails open: empty reflections and a single stderr warning — raw ingest still succeeds.
27
37
 
28
- If `scripts/dream/` is absent, surface a clear error and stop: the dream-loop infra has not been set up. Direct the user to `/ralph-knowledge:setup` (which installs the launchd schedule and can bootstrap the scripts).
38
+ If any prerequisite is missing, that is a bootstrap problem, not a run problem switch to **Bootstrap** below rather than surfacing a bare error.
29
39
 
30
40
  ## Workflow
31
41
 
@@ -70,8 +80,47 @@ State, as facts with paths and tiers:
70
80
  - any clusters that crossed the recurring-failure thresholds and would warrant a `process-improvement` follow-up
71
81
  - whether the local model was reachable, or whether the run failed open
72
82
 
83
+ ## Bootstrap (`--mode bootstrap`)
84
+
85
+ `scripts/dream/bootstrap.sh` is the single source of truth for machine wiring — it idempotently provisions `~/.ralph/knowledge.config.json`, renders + loads the templated launchd plist for the 03:00 nightly fire, and smoke-tests ingest. Never re-implement its steps inline; when wiring logic changes, it changes in `bootstrap.sh`.
86
+
87
+ ```bash
88
+ cd <repo>/scripts/dream && uv sync # python deps (one-time)
89
+ cd <repo>/plugin/ralph-knowledge && npm install && npm run build # indexer (one-time)
90
+ bash <repo>/scripts/dream/bootstrap.sh # config + launchd + smoke ingest
91
+ ```
92
+
93
+ After it completes, run the **Verify** checks to confirm the wiring took. `/ralph-knowledge:setup` Step 4 invokes the same script — either surface is fine; they cannot drift because both delegate.
94
+
95
+ **Machine conventions (optional):** if the machine has a model-gate checkout (`~/projects/model-gate`), prefer its surfaces for anything model-adjacent: `model-up <name>` to load the reflection model safely (stop-before-start on :8000), and the gate-aware `dream-now` zsh function as the shell-side equivalent of a full run. Registry of valid model names: `~/projects/models.yml`; setup details: `model-gate/README.md`. Absent model-gate, any OpenAI-compatible server at `RALPH_LLM_URL` works.
96
+
97
+ ## Verify (`--mode verify`)
98
+
99
+ Read-only. Run what applies, report as facts with counts and paths:
100
+
101
+ ```bash
102
+ # DB schema + tier breakdown
103
+ sqlite3 ~/.ralph-hero/knowledge.db "SELECT * FROM meta WHERE key='schema_version'"
104
+ sqlite3 ~/.ralph-hero/knowledge.db "SELECT memory_tier, COUNT(*) FROM documents GROUP BY memory_tier"
105
+
106
+ # Files on disk vs indexed (stale index if these diverge)
107
+ echo "On disk: $(find ~/projects/thoughts/dream-memories -name '*.md' | wc -l)"
108
+ echo "Indexed: $(sqlite3 ~/.ralph-hero/knowledge.db 'SELECT COUNT(*) FROM documents WHERE path LIKE "%dream-memories%"')"
109
+
110
+ # Local model reachability
111
+ curl -fsS http://localhost:8000/v1/models | jq '.data[].id'
112
+
113
+ # launchd nightly job (if installed): PID "-" when idle, exit status 0 after a clean run
114
+ launchctl list | grep dream-loop
115
+ ```
116
+
117
+ Interpretation notes:
118
+ - `schema_version=3` is **correct, not stale** — the `wiki`-tier CHECK-constraint change is labeled "Migration v4" in `db.ts` but deliberately does not bump the meta stamp.
119
+ - Four tiers exist: `doc` (curated), `raw` (dream ingest), `reflection` (synthesized), `wiki` (human-curated via `/ralph-knowledge:curate`; not writable by the record tool). A healthy post-run breakdown shows raw and reflection counts growing.
120
+ - Zero reflections with nonzero raws usually means the model was unreachable at run time (the pipeline fails open) — check reachability above, then re-run reflect for the window.
121
+
73
122
  ## Relationship to other surfaces
74
123
 
75
- - **Nightly schedule** — `scripts/dream/launchd/com.dubiel.dream-loop.plist.template`, installed by `/ralph-knowledge:setup` (or `bootstrap.sh`). Fires at 03:00 daily. This skill is the manual on-demand counterpart.
76
- - **`dream-now`** — a model-gate zsh function that is gate-aware (loads the model first, then ingest + reflect + logrotate). Owned by model-gate, not this plugin. This skill does not call `dream-now`; it invokes the scripts directly.
124
+ - **Nightly schedule** — `scripts/dream/launchd/com.dubiel.dream-loop.plist.template`, installed by `bootstrap.sh` (directly or via `/ralph-knowledge:setup`). Fires at 03:00 daily. This skill is the manual on-demand counterpart.
125
+ - **`dream-now`** — a model-gate zsh function that is gate-aware (loads the model first, then ingest + reflect + logrotate). Owned by model-gate, not this plugin. The default run mode here invokes the scripts directly; point users at `dream-now` when they want the gate-managed shell path.
77
126
  - **`/ralph-knowledge:curate`** — the human-gated promotion of reflections into the personal wiki tier. The dream-loop produces reflections; curate (not this skill) decides what becomes canonical.