renor-engine 0.0.0-stage → 1.0.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.
Files changed (176) hide show
  1. package/LICENSE +661 -0
  2. package/NOTICE +73 -0
  3. package/README.md +105 -2
  4. package/dist/BUILD.json +6 -0
  5. package/dist/api/apiAuth.js +43 -0
  6. package/dist/api/server.js +172 -0
  7. package/dist/buildIdentity.js +132 -0
  8. package/dist/cli-extractors.js +46 -0
  9. package/dist/cli.js +491 -0
  10. package/dist/config.js +65 -0
  11. package/dist/context/composer.js +320 -0
  12. package/dist/context/readHonesty.js +478 -0
  13. package/dist/context/rowStore.js +238 -0
  14. package/dist/conversation/formatter.js +81 -0
  15. package/dist/conversation/parser.js +189 -0
  16. package/dist/conversation/types.js +2 -0
  17. package/dist/conversation/writer.js +42 -0
  18. package/dist/db/client.js +147 -0
  19. package/dist/db/indexSalvage.js +81 -0
  20. package/dist/db/migrations.js +642 -0
  21. package/dist/db/readCache.js +109 -0
  22. package/dist/db/schema.js +5 -0
  23. package/dist/db/statements.js +89 -0
  24. package/dist/digests/digestCache.js +142 -0
  25. package/dist/digests/digests.js +597 -0
  26. package/dist/digests/worker.js +348 -0
  27. package/dist/embeddings/caching.js +337 -0
  28. package/dist/embeddings/coalescing.js +177 -0
  29. package/dist/embeddings/interactive.js +103 -0
  30. package/dist/embeddings/local.js +68 -0
  31. package/dist/embeddings/openai.js +105 -0
  32. package/dist/embeddings/rebuild.js +166 -0
  33. package/dist/embeddings/simdScan.js +258 -0
  34. package/dist/embeddings/space.js +21 -0
  35. package/dist/embeddings/types.js +14 -0
  36. package/dist/embeddings/vectorIndex.js +675 -0
  37. package/dist/embeddings/vertex.js +224 -0
  38. package/dist/eval/artifacts.js +42 -0
  39. package/dist/eval/cacheReport.js +139 -0
  40. package/dist/eval/comparison.js +182 -0
  41. package/dist/eval/context700-candidate.js +145 -0
  42. package/dist/eval/evalCache.js +525 -0
  43. package/dist/eval/hashEmbedding.js +35 -0
  44. package/dist/eval/longmemeval.js +56 -0
  45. package/dist/eval/metrics.js +181 -0
  46. package/dist/eval/renorbenchBaseline.js +486 -0
  47. package/dist/eval/renorbenchDocuments.js +134 -0
  48. package/dist/eval/renorbenchProduct.js +182 -0
  49. package/dist/eval/runLedger.js +279 -0
  50. package/dist/eval/runManifest.js +479 -0
  51. package/dist/eval/scoring.js +197 -0
  52. package/dist/evidence/attach.js +267 -0
  53. package/dist/evidence/background.js +147 -0
  54. package/dist/evidence/evidenceCache.js +182 -0
  55. package/dist/evidence/gate.js +37 -0
  56. package/dist/evidence/keyPool.js +85 -0
  57. package/dist/evidence/ollamaChat.js +84 -0
  58. package/dist/evidence/optin.js +165 -0
  59. package/dist/evidence/options.js +130 -0
  60. package/dist/evidence/secondPass.js +456 -0
  61. package/dist/extraction/classifyCapture.js +34 -0
  62. package/dist/extraction/decision-model.js +174 -0
  63. package/dist/extraction/eventExtractor.js +113 -0
  64. package/dist/extraction/extractionClient.js +122 -0
  65. package/dist/extraction/factExtractor.js +46 -0
  66. package/dist/extraction/factsWritePath.js +179 -0
  67. package/dist/extraction/preferenceExtractor.js +153 -0
  68. package/dist/extraction/secretGuard.js +176 -0
  69. package/dist/extraction/secretScanner.js +665 -0
  70. package/dist/extraction/typedFacts.js +224 -0
  71. package/dist/extraction/writePathAdjudication.js +110 -0
  72. package/dist/importing/apply.js +644 -0
  73. package/dist/importing/bundle-write.js +80 -0
  74. package/dist/importing/cli.js +407 -0
  75. package/dist/importing/existing.js +49 -0
  76. package/dist/importing/facts.js +123 -0
  77. package/dist/importing/forgetImported.js +79 -0
  78. package/dist/importing/hosted.js +467 -0
  79. package/dist/importing/index.js +29 -0
  80. package/dist/importing/journalLog.js +96 -0
  81. package/dist/importing/markdown.js +98 -0
  82. package/dist/importing/plan.js +206 -0
  83. package/dist/importing/secretScreen.js +113 -0
  84. package/dist/importing/serialize.js +188 -0
  85. package/dist/importing/source.js +463 -0
  86. package/dist/importing/types.js +6 -0
  87. package/dist/indexer/chunker.js +319 -0
  88. package/dist/indexer/dailyEntries.js +63 -0
  89. package/dist/indexer/hasher.js +10 -0
  90. package/dist/indexer/laneRuns.js +48 -0
  91. package/dist/indexer/o200k.js +481 -0
  92. package/dist/indexer/o200k.json +8 -0
  93. package/dist/indexer/outsideRoot.js +77 -0
  94. package/dist/indexer/parser.js +167 -0
  95. package/dist/indexer/pipeline.js +1168 -0
  96. package/dist/indexer/staleness.js +201 -0
  97. package/dist/indexer/tokenizer.js +77 -0
  98. package/dist/indexer/walker.js +184 -0
  99. package/dist/mcp/hive.js +88 -0
  100. package/dist/mcp/requestErrors.js +47 -0
  101. package/dist/mcp/server.js +396 -0
  102. package/dist/memory/agentLease.js +43 -0
  103. package/dist/memory/confinedWrite.js +315 -0
  104. package/dist/memory/controls.js +73 -0
  105. package/dist/memory/dailyNote.js +265 -0
  106. package/dist/memory/durableAppend.js +57 -0
  107. package/dist/memory/factConflicts.js +203 -0
  108. package/dist/memory/factTerms.js +321 -0
  109. package/dist/memory/hiveNameHeader.cjs +10 -0
  110. package/dist/memory/inventory.js +55 -0
  111. package/dist/memory/invisibleText.js +20 -0
  112. package/dist/memory/lifecycle.js +1756 -0
  113. package/dist/memory/limits.js +13 -0
  114. package/dist/memory/requests.js +183 -0
  115. package/dist/memory/sourceName.js +200 -0
  116. package/dist/memory/whatsNew.js +158 -0
  117. package/dist/memory/writer.js +175 -0
  118. package/dist/memory/writerConflicts.js +85 -0
  119. package/dist/memorymap/memoryMap.js +1568 -0
  120. package/dist/memorymap/replacedJournal.js +58 -0
  121. package/dist/memorymap/replacements.js +405 -0
  122. package/dist/mvp/activity.js +86 -0
  123. package/dist/mvp/agentMemory.js +1744 -0
  124. package/dist/mvp/auth.js +531 -0
  125. package/dist/mvp/cli.js +73 -0
  126. package/dist/mvp/client-store.js +462 -0
  127. package/dist/mvp/compileCache.js +249 -0
  128. package/dist/mvp/compileSeed.js +27 -0
  129. package/dist/mvp/config.js +288 -0
  130. package/dist/mvp/connectionStatus.js +80 -0
  131. package/dist/mvp/contextRelevance.js +413 -0
  132. package/dist/mvp/engine.js +1065 -0
  133. package/dist/mvp/exportAll.js +359 -0
  134. package/dist/mvp/factLanePack.js +77 -0
  135. package/dist/mvp/factMarkers.js +49 -0
  136. package/dist/mvp/factsLane.js +203 -0
  137. package/dist/mvp/hiddenSpans.js +585 -0
  138. package/dist/mvp/history.js +413 -0
  139. package/dist/mvp/hitDates.js +31 -0
  140. package/dist/mvp/hitFloor.js +36 -0
  141. package/dist/mvp/identity.js +18 -0
  142. package/dist/mvp/idle.js +113 -0
  143. package/dist/mvp/memoryDir.js +98 -0
  144. package/dist/mvp/posthogMcp.js +210 -0
  145. package/dist/mvp/readScope.js +104 -0
  146. package/dist/mvp/readUsefulness.js +222 -0
  147. package/dist/mvp/sanitize.js +48 -0
  148. package/dist/mvp/schemaRejections.js +91 -0
  149. package/dist/mvp/scope.js +127 -0
  150. package/dist/mvp/server.js +2089 -0
  151. package/dist/mvp/serverCard.js +29 -0
  152. package/dist/mvp/storedSize.js +139 -0
  153. package/dist/mvp/telemetry.js +441 -0
  154. package/dist/retrieval/adviceLines.js +195 -0
  155. package/dist/retrieval/adviceQuestion.js +44 -0
  156. package/dist/retrieval/conversationShape.js +44 -0
  157. package/dist/retrieval/dailyEntryTime.js +55 -0
  158. package/dist/retrieval/entityLookup.js +113 -0
  159. package/dist/retrieval/factsFirst.js +188 -0
  160. package/dist/retrieval/keyword.js +84 -0
  161. package/dist/retrieval/ledgerEventTime.js +51 -0
  162. package/dist/retrieval/ledgerLiveness.js +83 -0
  163. package/dist/retrieval/ledgerPresentation.js +161 -0
  164. package/dist/retrieval/noteIdentity.js +57 -0
  165. package/dist/retrieval/orchestrator.js +1086 -0
  166. package/dist/retrieval/queryHelpers.js +142 -0
  167. package/dist/retrieval/questionSpan.js +475 -0
  168. package/dist/retrieval/readerEvents.js +690 -0
  169. package/dist/retrieval/readerLayout.js +503 -0
  170. package/dist/retrieval/relativeTimeHints.js +113 -0
  171. package/dist/retrieval/semantic.js +67 -0
  172. package/dist/router/routerCache.js +106 -0
  173. package/dist/router/sessionRouter.js +726 -0
  174. package/dist/toolRefusal.js +120 -0
  175. package/dist/watcher/watcher.js +75 -0
  176. package/package.json +120 -4
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.removeRowsOutsideRoot = removeRowsOutsideRoot;
7
+ exports.reindexPending = reindexPending;
8
+ exports.clearReindexPending = clearReindexPending;
9
+ const path_1 = __importDefault(require("path"));
10
+ const sqliteVec = require('sqlite-vec');
11
+ /**
12
+ * Remove the index's rows for files outside the folder it is opened on.
13
+ *
14
+ * The index names each note by its absolute path. A workspace folder moved or restored from a backup into another
15
+ * directory therefore leaves every row of the old location behind, and a search serves each note twice: once from
16
+ * the new folder, and once from the old path, which no longer exists. This removes the rows whose path is absolute and
17
+ * not under `memoryDir`: the note, and what the note made (its chunks, entities, structured rows and lane runs go with
18
+ * it by cascade), plus the facts, relations and write events that name it, and the vectors of its chunks.
19
+ *
20
+ * Rows that name no file are kept: the engine's lifecycle facts (source `_mcp_`), the ledger's events and the agents'
21
+ * requests and cursors are not made from a note's path, so the folder's move does not touch them. Rows for notes under
22
+ * the folder are kept as they are, so an unmoved folder removes nothing and its index is not changed.
23
+ *
24
+ * The index is derived from the Markdown, so what is removed is indexed again by the next pass over the folder, at the
25
+ * paths it has now (`engineIndex`). The removal and the mark that the folder is owed a pass (`reindexPending`) are one
26
+ * transaction: a process that ends after it finds the mark on its next start, however long the pass would have taken.
27
+ * The canonical notes and the ledger are never written.
28
+ */
29
+ function removeRowsOutsideRoot(db, memoryDir) {
30
+ const root = path_1.default.resolve(memoryDir);
31
+ const params = { root, under: root.endsWith(path_1.default.sep) ? root : root + path_1.default.sep };
32
+ // Absolute, and neither the folder itself nor below it. `length(@under)` counts characters as `substr` does.
33
+ const outside = (column) => `(substr(${column}, 1, 1) = '/' AND ${column} <> @root AND substr(${column}, 1, length(@under)) <> @under)`;
34
+ return db.transaction(() => {
35
+ const removed = { notes: 0, facts: 0, relations: 0, events: 0, vectors: 0 };
36
+ // Facts first: fact_terms is keyed by fact id and has no cascade, so its rows go with the facts that name the note.
37
+ const factIds = `SELECT id FROM facts WHERE ${outside('source_path')}`;
38
+ db.prepare(`DELETE FROM fact_terms WHERE fact_id IN (${factIds})`).run(params);
39
+ removed.facts = db.prepare(`DELETE FROM facts WHERE ${outside('source_path')}`).run(params).changes;
40
+ removed.relations = db.prepare(`DELETE FROM relations WHERE ${outside('source_note')}`).run(params).changes;
41
+ removed.events = db.prepare(`DELETE FROM write_events WHERE ${outside('note_path')}`).run(params).changes;
42
+ // Notes last: chunks, entities, structured rows and lane runs cascade from them, and chunks_fts and the note
43
+ // identity index follow their triggers.
44
+ removed.notes = db.prepare(`DELETE FROM notes WHERE ${outside('path')}`).run(params).changes;
45
+ // Vectors are keyed by chunk id with no cascade: those of the chunks just removed are orphans now. Only a removed
46
+ // note can leave one, so an index with nothing to remove is not read for them.
47
+ const vectorTable = db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'chunk_embeddings'").get();
48
+ if (removed.notes > 0 && vectorTable !== undefined) {
49
+ // The table is a vec0 virtual table: this connection can only read it once the extension is loaded on it.
50
+ sqliteVec.load(db);
51
+ const orphans = db.prepare('SELECT rowid FROM chunk_embeddings WHERE rowid NOT IN (SELECT id FROM chunks)').pluck().all();
52
+ if (orphans.length > 0) {
53
+ db.prepare('DELETE FROM chunk_embeddings WHERE rowid IN (SELECT value FROM json_each(?))').run(JSON.stringify(orphans));
54
+ }
55
+ removed.vectors = orphans.length;
56
+ }
57
+ const total = removed.notes + removed.facts + removed.relations + removed.events + removed.vectors;
58
+ if (total > 0) {
59
+ db.prepare(`INSERT INTO index_state (key, value) VALUES (?, ?) ON CONFLICT (key) DO UPDATE SET value = excluded.value`)
60
+ .run(REINDEX_PENDING, params.root);
61
+ }
62
+ return { ...removed, total };
63
+ })();
64
+ }
65
+ const REINDEX_PENDING = 'reindex_pending';
66
+ /**
67
+ * True while the folder is owed an index pass: a removal made it, and no pass has since finished without a failure.
68
+ * A start reads this rather than the count of rows it removed, so a start that is cut short does not leave an index
69
+ * that nothing indexes again.
70
+ */
71
+ function reindexPending(db) {
72
+ return db.prepare('SELECT 1 FROM index_state WHERE key = ?').get(REINDEX_PENDING) !== undefined;
73
+ }
74
+ /** Clears the mark once the folder has been indexed at its current paths with no failure. */
75
+ function clearReindexPending(db) {
76
+ db.prepare('DELETE FROM index_state WHERE key = ?').run(REINDEX_PENDING);
77
+ }
@@ -0,0 +1,167 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.assertFrontmatterBounded = assertFrontmatterBounded;
7
+ exports.parseNote = parseNote;
8
+ exports.parseNoteDetailed = parseNoteDetailed;
9
+ exports.wikilinkTargets = wikilinkTargets;
10
+ exports.wikilinksSettled = wikilinksSettled;
11
+ const gray_matter_1 = __importDefault(require("gray-matter"));
12
+ const path_1 = __importDefault(require("path"));
13
+ const dailyNote_1 = require("../memory/dailyNote");
14
+ const dailyEntries_1 = require("./dailyEntries");
15
+ /**
16
+ * The first index at or after `p` where `pattern` matches (the text's length when none does), memoised: every p
17
+ * up to that index has the same answer, so a run of failing `[[` starts shares one scan. Without it each start
18
+ * scans to the end of the text and a pathological note (`[[[[...`) costs O(n^2).
19
+ */
20
+ function nextMatch(text, pattern) {
21
+ let from = -1;
22
+ let at = -1;
23
+ return p => {
24
+ if (from <= p && p <= at)
25
+ return at;
26
+ pattern.lastIndex = p;
27
+ const m = pattern.exec(text);
28
+ from = p;
29
+ at = m === null ? text.length : m.index;
30
+ return at;
31
+ };
32
+ }
33
+ /**
34
+ * The wikilinks in `text` from `from`, matched as `[[target]]`, `[[target|alias]]` or `[[target#heading]]`: the
35
+ * target is the run up to the first `]`, `|` or `#` (it may span lines), closed by `]]`, or by a `|` or `#` and an
36
+ * alias that ends at the first `]` followed by another `]`. Matches do not overlap, and a `[[` that does not close
37
+ * is retried one character on. The same matches as the regex /\[\[([^\]|#]+)(?:[|#][^\]]*)?]]/g, in linear time.
38
+ */
39
+ function wikilinkSpans(text, from) {
40
+ const spans = [];
41
+ const nextDelimiter = nextMatch(text, /[\]|#]/g);
42
+ const nextBracket = nextMatch(text, /\]/g);
43
+ let open = text.indexOf('[[', from);
44
+ while (open !== -1) {
45
+ const start = open + 2;
46
+ const delimiter = nextDelimiter(start);
47
+ let end = -1;
48
+ if (delimiter > start && delimiter < text.length) {
49
+ if (text[delimiter] === ']') {
50
+ if (text[delimiter + 1] === ']')
51
+ end = delimiter + 2;
52
+ }
53
+ else {
54
+ const close = nextBracket(delimiter + 1);
55
+ if (text[close + 1] === ']')
56
+ end = close + 2;
57
+ }
58
+ }
59
+ if (end === -1) {
60
+ open = text.indexOf('[[', open + 1);
61
+ }
62
+ else {
63
+ spans.push({ end, target: text.slice(start, delimiter) });
64
+ open = text.indexOf('[[', end);
65
+ }
66
+ }
67
+ return spans;
68
+ }
69
+ /**
70
+ * Frontmatter is stored as JSON. A YAML alias shares one parsed value, so a small note can serialize to
71
+ * far more bytes than it holds: a 2,500-character scalar aliased 2,000 times is a 15 KB note and 5 MB of
72
+ * JSON. Total the serialized size while walking the expanded tree and refuse past a bound proportional to
73
+ * the note, before anything is written.
74
+ */
75
+ const FRONTMATTER_FLOOR_BYTES = 64 * 1024;
76
+ const FRONTMATTER_EXPANSION = 4;
77
+ const FRONTMATTER_DEPTH_LIMIT = 64;
78
+ function assertFrontmatterBounded(value, sourceLength) {
79
+ const limit = FRONTMATTER_FLOOR_BYTES + FRONTMATTER_EXPANSION * sourceLength;
80
+ let size = 0;
81
+ const walk = (v, depth) => {
82
+ if (depth > FRONTMATTER_DEPTH_LIMIT)
83
+ throw new Error('Markdown frontmatter is too large or deeply aliased');
84
+ // Size each value the way JSON.stringify will write it, including toJSON (dates, binary).
85
+ if (v && typeof v.toJSON === 'function')
86
+ v = v.toJSON();
87
+ if (Array.isArray(v)) {
88
+ size += 2 + Math.max(v.length - 1, 0); // brackets and commas
89
+ for (const child of v)
90
+ walk(child, depth + 1);
91
+ }
92
+ else if (v && typeof v === 'object') {
93
+ const entries = Object.entries(v);
94
+ size += 2 + Math.max(entries.length - 1, 0);
95
+ for (const [key, child] of entries) {
96
+ size += JSON.stringify(key).length + 1;
97
+ walk(child, depth + 1);
98
+ }
99
+ }
100
+ else {
101
+ size += JSON.stringify(v)?.length ?? 0;
102
+ }
103
+ if (size > limit)
104
+ throw new Error('Markdown frontmatter is too large or deeply aliased');
105
+ };
106
+ walk(value, 0);
107
+ }
108
+ /**
109
+ * gray-matter caches every string it parses, and caches before it parses: a parse that throws leaves empty
110
+ * `data` in the cache and the next call for the same text returns it as if it had parsed. Any options object
111
+ * bypasses that cache. It also ships a JavaScript engine that evaluates `---js` frontmatter as code; a note
112
+ * is data, so only the YAML and JSON engines are usable.
113
+ */
114
+ const MATTER_OPTIONS = {
115
+ engines: {
116
+ javascript: () => { throw new Error('Unsupported frontmatter language: javascript'); },
117
+ },
118
+ };
119
+ function parseNote(filePath, content) {
120
+ return parseNoteDetailed(filePath, content).note;
121
+ }
122
+ /**
123
+ * parseNote, with what the indexer needs to index an append to the note by its appended part (pipeline.ts): the
124
+ * text after the frontmatter the body was trimmed from (a suffix of `content`), and whether the title came from a
125
+ * heading in it rather than from the file name.
126
+ */
127
+ function parseNoteDetailed(filePath, content) {
128
+ // The one door every reader of a note's text goes through (the index behind search, recall and get_context,
129
+ // the memory map's note view, the evidence pass). A daily note keeps a line of the user's words that would
130
+ // start like an entry behind a backslash; the words are restored here, so none of them sees the escape.
131
+ const text = (0, dailyNote_1.isDailyNotePath)(filePath) ? (0, dailyNote_1.decodeDailyNoteText)(content) : content;
132
+ const { data: frontmatter, content: rawBody } = (0, gray_matter_1.default)(text, MATTER_OPTIONS);
133
+ assertFrontmatterBounded(frontmatter, content.length);
134
+ const heading = rawBody.match(/^#\s+(.+)$/m);
135
+ const title = heading?.[1].trim() ?? path_1.default.basename(filePath, '.md');
136
+ const wikilinks = wikilinkTargets(rawBody);
137
+ const body = rawBody.trim();
138
+ const entryStarts = (0, dailyNote_1.isDailyNotePath)(filePath) ? (0, dailyEntries_1.dailyEntryStarts)(content, text, rawBody, body) : undefined;
139
+ return {
140
+ note: {
141
+ path: filePath,
142
+ title,
143
+ frontmatter,
144
+ body,
145
+ wikilinks,
146
+ ...(entryStarts ? { entryStarts } : {}),
147
+ },
148
+ rawBody,
149
+ titleFromHeading: heading !== null,
150
+ };
151
+ }
152
+ /** The wikilink targets in `text`, each once, in the order they first appear. */
153
+ function wikilinkTargets(text, from = 0) {
154
+ return [...new Set(wikilinkSpans(text, from).map(s => s.target.trim()))].filter(Boolean);
155
+ }
156
+ /**
157
+ * Whether text appended to `text` can only add wikilinks after the ones it holds, scanning as `wikilinkTargets`
158
+ * does from `from`. A link's target may span lines, so an opening `[[` with no `]` after it could still be closed
159
+ * by what is appended, and become a link that starts in `text`; any other opening is settled by a `]` in `text`.
160
+ */
161
+ function wikilinksSettled(text, from = 0) {
162
+ let settledTo = from;
163
+ for (const span of wikilinkSpans(text, from))
164
+ settledTo = span.end;
165
+ const open = text.lastIndexOf('[[');
166
+ return open < settledTo || text.indexOf(']', open + 2) !== -1;
167
+ }