@bubstack/moe-memory 0.1.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/.mcp.json +18 -0
- package/README.md +47 -0
- package/agents/search-conversations.md +171 -0
- package/dist/LICENSE +25 -0
- package/dist/cli.d.ts +25 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +125 -0
- package/dist/cli.js.map +1 -0
- package/dist/codex-hook-trust.d.ts +4 -0
- package/dist/codex-hook-trust.d.ts.map +1 -0
- package/dist/codex-hook-trust.js +114 -0
- package/dist/codex-hook-trust.js.map +1 -0
- package/dist/codex-support.d.ts +6 -0
- package/dist/codex-support.d.ts.map +1 -0
- package/dist/codex-support.js +29 -0
- package/dist/codex-support.js.map +1 -0
- package/dist/constants.d.ts +14 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +14 -0
- package/dist/constants.js.map +1 -0
- package/dist/db.d.ts +103 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +430 -0
- package/dist/db.js.map +1 -0
- package/dist/doctor-cli.d.ts +3 -0
- package/dist/doctor-cli.d.ts.map +1 -0
- package/dist/doctor-cli.js +43 -0
- package/dist/doctor-cli.js.map +1 -0
- package/dist/doctor.d.ts +17 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +98 -0
- package/dist/doctor.js.map +1 -0
- package/dist/embedding-migration.d.ts +73 -0
- package/dist/embedding-migration.d.ts.map +1 -0
- package/dist/embedding-migration.js +120 -0
- package/dist/embedding-migration.js.map +1 -0
- package/dist/embeddings.d.ts +78 -0
- package/dist/embeddings.d.ts.map +1 -0
- package/dist/embeddings.js +180 -0
- package/dist/embeddings.js.map +1 -0
- package/dist/file-lock.d.ts +39 -0
- package/dist/file-lock.d.ts.map +1 -0
- package/dist/file-lock.js +91 -0
- package/dist/file-lock.js.map +1 -0
- package/dist/index-cli.d.ts +10 -0
- package/dist/index-cli.d.ts.map +1 -0
- package/dist/index-cli.js +195 -0
- package/dist/index-cli.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/indexer.d.ts +4 -0
- package/dist/indexer.d.ts.map +1 -0
- package/dist/indexer.js +318 -0
- package/dist/indexer.js.map +1 -0
- package/dist/install-check.d.ts +48 -0
- package/dist/install-check.d.ts.map +1 -0
- package/dist/install-check.js +82 -0
- package/dist/install-check.js.map +1 -0
- package/dist/journal/index.d.ts +5 -0
- package/dist/journal/index.d.ts.map +1 -0
- package/dist/journal/index.js +5 -0
- package/dist/journal/index.js.map +1 -0
- package/dist/journal/legacy-sidecars.d.ts +74 -0
- package/dist/journal/legacy-sidecars.d.ts.map +1 -0
- package/dist/journal/legacy-sidecars.js +151 -0
- package/dist/journal/legacy-sidecars.js.map +1 -0
- package/dist/journal/markdown.d.ts +91 -0
- package/dist/journal/markdown.d.ts.map +1 -0
- package/dist/journal/markdown.js +234 -0
- package/dist/journal/markdown.js.map +1 -0
- package/dist/journal/search.d.ts +91 -0
- package/dist/journal/search.d.ts.map +1 -0
- package/dist/journal/search.js +203 -0
- package/dist/journal/search.js.map +1 -0
- package/dist/journal/store.d.ts +96 -0
- package/dist/journal/store.d.ts.map +1 -0
- package/dist/journal/store.js +270 -0
- package/dist/journal/store.js.map +1 -0
- package/dist/journal-cli.d.ts +19 -0
- package/dist/journal-cli.d.ts.map +1 -0
- package/dist/journal-cli.js +153 -0
- package/dist/journal-cli.js.map +1 -0
- package/dist/logging.d.ts +6 -0
- package/dist/logging.d.ts.map +1 -0
- package/dist/logging.js +20 -0
- package/dist/logging.js.map +1 -0
- package/dist/mcp-server.d.ts +34 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +607 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/parser.d.ts +11 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +445 -0
- package/dist/parser.js.map +1 -0
- package/dist/paths.d.ts +103 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +258 -0
- package/dist/paths.js.map +1 -0
- package/dist/search-cli.d.ts +3 -0
- package/dist/search-cli.d.ts.map +1 -0
- package/dist/search-cli.js +138 -0
- package/dist/search-cli.js.map +1 -0
- package/dist/search.d.ts +36 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +371 -0
- package/dist/search.js.map +1 -0
- package/dist/show-cli.d.ts +3 -0
- package/dist/show-cli.d.ts.map +1 -0
- package/dist/show-cli.js +60 -0
- package/dist/show-cli.js.map +1 -0
- package/dist/show.d.ts +3 -0
- package/dist/show.d.ts.map +1 -0
- package/dist/show.js +984 -0
- package/dist/show.js.map +1 -0
- package/dist/stats-cli.d.ts +3 -0
- package/dist/stats-cli.d.ts.map +1 -0
- package/dist/stats-cli.js +51 -0
- package/dist/stats-cli.js.map +1 -0
- package/dist/stats.d.ts +19 -0
- package/dist/stats.d.ts.map +1 -0
- package/dist/stats.js +116 -0
- package/dist/stats.js.map +1 -0
- package/dist/summarizer.d.ts +83 -0
- package/dist/summarizer.d.ts.map +1 -0
- package/dist/summarizer.js +526 -0
- package/dist/summarizer.js.map +1 -0
- package/dist/summary-sentinel.d.ts +31 -0
- package/dist/summary-sentinel.d.ts.map +1 -0
- package/dist/summary-sentinel.js +80 -0
- package/dist/summary-sentinel.js.map +1 -0
- package/dist/sync-cli.d.ts +15 -0
- package/dist/sync-cli.d.ts.map +1 -0
- package/dist/sync-cli.js +202 -0
- package/dist/sync-cli.js.map +1 -0
- package/dist/sync.d.ts +35 -0
- package/dist/sync.d.ts.map +1 -0
- package/dist/sync.js +204 -0
- package/dist/sync.js.map +1 -0
- package/dist/types.d.ts +125 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +32 -0
- package/dist/types.js.map +1 -0
- package/dist/verify.d.ts +30 -0
- package/dist/verify.d.ts.map +1 -0
- package/dist/verify.js +174 -0
- package/dist/verify.js.map +1 -0
- package/dist/version.d.ts +13 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +13 -0
- package/dist/version.js.map +1 -0
- package/hooks/hooks.json +15 -0
- package/package.json +71 -0
- package/prompts/search-agent.md +160 -0
- package/skills/remembering-conversations/MCP-TOOLS.md +147 -0
- package/skills/remembering-conversations/SKILL.md +97 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One-way importer for private-journal-mcp's `.embedding` sidecars.
|
|
3
|
+
*
|
|
4
|
+
* Upstream wrote, beside every `<entry>.md`, an `<entry>.embedding` JSON file:
|
|
5
|
+
*
|
|
6
|
+
* { embedding: number[], text: string, sections: string[], timestamp: number, path: string }
|
|
7
|
+
*
|
|
8
|
+
* Two reasons that data cannot simply be loaded into the new index:
|
|
9
|
+
*
|
|
10
|
+
* 1. **The vectors are from a different encoder.** Sidecars were produced by
|
|
11
|
+
* `Xenova/all-MiniLM-L6-v2`; this package embeds with `Xenova/bge-small-en-v1.5`
|
|
12
|
+
* at dtype q8 and an asymmetric query prefix. Both are 384-dimensional, so a
|
|
13
|
+
* vec0 column accepts either without complaint and a mixed corpus ranks
|
|
14
|
+
* wrongly with no error. So the sidecar's `embedding` array is DISCARDED and
|
|
15
|
+
* the entry is re-embedded from its markdown.
|
|
16
|
+
* 2. **`path` was baked in absolute.** Upstream's search returned the path stored
|
|
17
|
+
* inside the JSON rather than the path it had just walked, so renaming the
|
|
18
|
+
* journal directory produced results that `read_journal_entry` then refused.
|
|
19
|
+
* So the sidecar's `path` is DISCARDED too, and the walk decides.
|
|
20
|
+
*
|
|
21
|
+
* Which leaves nothing worth importing from the sidecar itself. What this
|
|
22
|
+
* function actually does is find the orphans — `.md` files with a sidecar whose
|
|
23
|
+
* day-directory is not where the new layout expects it — and, mainly, delete the
|
|
24
|
+
* sidecars once the entry is indexed, so the old and new indexes cannot drift.
|
|
25
|
+
*
|
|
26
|
+
* It is opt-in (`moe-memory journal import-legacy`) and never runs on its own:
|
|
27
|
+
* deleting a file the user might still want read by an upstream install is not
|
|
28
|
+
* something to do silently.
|
|
29
|
+
*
|
|
30
|
+
* FIXED: it searched only the CURRENT roots. The command exists because the
|
|
31
|
+
* paths moved on import — `<project>/.private-journal` → `<project>/.moe-journal`
|
|
32
|
+
* and `~/.private-journal` → `<data dir>/journal` — so on any install that had
|
|
33
|
+
* not already hand-copied its journal across, it walked exactly the two
|
|
34
|
+
* directories the sidecars provably are not in, found nothing, and printed
|
|
35
|
+
* "Legacy .embedding sidecars found: 0". Indistinguishable from success. It now
|
|
36
|
+
* also surveys the upstream directories (`findLegacyJournalRoots`) and reports
|
|
37
|
+
* them in `legacy`, so the caller can tell the user their data is over there.
|
|
38
|
+
*
|
|
39
|
+
* It reports rather than migrates, deliberately, matching `findLegacyDataDir`.
|
|
40
|
+
* Journal entries are private reflections; quietly relocating them is worse
|
|
41
|
+
* than quietly relocating a conversation archive, and that was already judged
|
|
42
|
+
* too much to do behind someone's back.
|
|
43
|
+
*/
|
|
44
|
+
import fs from "node:fs/promises";
|
|
45
|
+
import path from "node:path";
|
|
46
|
+
import { getJournalIndexState } from "../db.js";
|
|
47
|
+
import { findLegacyJournalRoots } from "../paths.js";
|
|
48
|
+
import { DAY_DIR_PATTERN, journalEntryId } from "./markdown.js";
|
|
49
|
+
/** Count `.md` entries and `.embedding` sidecars under a journal root. */
|
|
50
|
+
async function surveyRoot(root) {
|
|
51
|
+
let entries = 0;
|
|
52
|
+
let sidecars = 0;
|
|
53
|
+
let dayDirs;
|
|
54
|
+
try {
|
|
55
|
+
dayDirs = await fs.readdir(root);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return { entries, sidecars };
|
|
59
|
+
}
|
|
60
|
+
for (const dayDir of dayDirs) {
|
|
61
|
+
if (!DAY_DIR_PATTERN.test(dayDir))
|
|
62
|
+
continue;
|
|
63
|
+
let files;
|
|
64
|
+
try {
|
|
65
|
+
files = await fs.readdir(path.join(root, dayDir));
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
for (const file of files) {
|
|
71
|
+
if (file.endsWith(".md"))
|
|
72
|
+
entries++;
|
|
73
|
+
else if (file.endsWith(".embedding"))
|
|
74
|
+
sidecars++;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return { entries, sidecars };
|
|
78
|
+
}
|
|
79
|
+
export async function importLegacyJournalSidecars(db, store, options = {}) {
|
|
80
|
+
const result = {
|
|
81
|
+
found: 0,
|
|
82
|
+
indexed: 0,
|
|
83
|
+
removed: 0,
|
|
84
|
+
orphaned: [],
|
|
85
|
+
legacy: [],
|
|
86
|
+
};
|
|
87
|
+
// Look where the data actually is before reporting zero.
|
|
88
|
+
for (const legacyRoot of findLegacyJournalRoots()) {
|
|
89
|
+
const survey = await surveyRoot(legacyRoot);
|
|
90
|
+
if (survey.entries === 0 && survey.sidecars === 0)
|
|
91
|
+
continue;
|
|
92
|
+
result.legacy.push({ root: legacyRoot, ...survey });
|
|
93
|
+
}
|
|
94
|
+
// Re-index first: the markdown files are the source of truth, and every
|
|
95
|
+
// sidecar's entry has to exist in the new index before we consider deleting it.
|
|
96
|
+
await store.indexJournal(db);
|
|
97
|
+
const state = getJournalIndexState(db);
|
|
98
|
+
for (const root of store.roots()) {
|
|
99
|
+
let dayDirs;
|
|
100
|
+
try {
|
|
101
|
+
dayDirs = await fs.readdir(root.path);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
for (const dayDir of dayDirs) {
|
|
107
|
+
if (!DAY_DIR_PATTERN.test(dayDir))
|
|
108
|
+
continue;
|
|
109
|
+
const dayPath = path.join(root.path, dayDir);
|
|
110
|
+
let files;
|
|
111
|
+
try {
|
|
112
|
+
files = await fs.readdir(dayPath);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
for (const file of files) {
|
|
118
|
+
if (!file.endsWith(".embedding"))
|
|
119
|
+
continue;
|
|
120
|
+
result.found++;
|
|
121
|
+
const sidecarPath = path.join(dayPath, file);
|
|
122
|
+
const entryPath = sidecarPath.replace(/\.embedding$/, ".md");
|
|
123
|
+
try {
|
|
124
|
+
await fs.access(entryPath);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
result.orphaned.push(sidecarPath);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
// The scope the walk would assign is not knowable here without reading
|
|
131
|
+
// the entry, so check both possibilities.
|
|
132
|
+
const indexed = state.has(journalEntryId("project", root.path, entryPath)) ||
|
|
133
|
+
state.has(journalEntryId("user", root.path, entryPath));
|
|
134
|
+
if (!indexed)
|
|
135
|
+
continue;
|
|
136
|
+
result.indexed++;
|
|
137
|
+
if (options.remove) {
|
|
138
|
+
try {
|
|
139
|
+
await fs.unlink(sidecarPath);
|
|
140
|
+
result.removed++;
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
console.error(`moe-memory: could not remove legacy sidecar ${sidecarPath}: ${String(error)}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return result;
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=legacy-sidecars.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"legacy-sidecars.js","sourceRoot":"","sources":["../../src/journal/legacy-sidecars.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The journal entry's on-disk format.
|
|
3
|
+
*
|
|
4
|
+
* Carried over from private-journal-mcp unchanged in substance, because it is a
|
|
5
|
+
* data contract with files already written:
|
|
6
|
+
*
|
|
7
|
+
* <root>/YYYY-MM-DD/HH-MM-SS-µµµµµµ.md
|
|
8
|
+
*
|
|
9
|
+
* ---
|
|
10
|
+
* title: "3:04:05 PM - May 27, 2025"
|
|
11
|
+
* date: 2025-05-27T20:04:05.123Z
|
|
12
|
+
* timestamp: 1748376245123
|
|
13
|
+
* ---
|
|
14
|
+
*
|
|
15
|
+
* ## Reflections
|
|
16
|
+
* …
|
|
17
|
+
*
|
|
18
|
+
* `µµµµµµ` is pseudo-microseconds — `ms * 1000 + floor(random() * 1000)` — so two
|
|
19
|
+
* writes inside the same millisecond collide about one time in a thousand. Kept
|
|
20
|
+
* as-is: changing the filename shape would orphan every existing entry, and the
|
|
21
|
+
* collision is the upstream behaviour its tests pin.
|
|
22
|
+
*
|
|
23
|
+
* What is NOT carried over is the `.embedding` JSON sidecar. That was the only
|
|
24
|
+
* enumeration path in the whole upstream package — search, list_recent_entries
|
|
25
|
+
* and read_recent_entries all read `*.embedding` and never listed `*.md` — so an
|
|
26
|
+
* entry whose embedding failed was written and then permanently invisible. The
|
|
27
|
+
* markdown files are the source of truth here and SQLite is a rebuildable index.
|
|
28
|
+
*/
|
|
29
|
+
import { type JournalScope, type JournalThoughts } from "../types.js";
|
|
30
|
+
/** `YYYY-MM-DD` day-directory name. */
|
|
31
|
+
export declare const DAY_DIR_PATTERN: RegExp;
|
|
32
|
+
export declare function formatDayDirectory(date: Date): string;
|
|
33
|
+
export declare function formatEntryBasename(date: Date): string;
|
|
34
|
+
/**
|
|
35
|
+
* Render an entry. Section order is fixed by JOURNAL_SECTION_HEADINGS and empty
|
|
36
|
+
* categories are omitted.
|
|
37
|
+
*/
|
|
38
|
+
export declare function formatEntry(thoughts: JournalThoughts, timestamp: Date): string;
|
|
39
|
+
/**
|
|
40
|
+
* Strip frontmatter and section headers, and harvest the headings.
|
|
41
|
+
*
|
|
42
|
+
* The returned `text` is what gets embedded; `sections` is what the
|
|
43
|
+
* `search_journal` section filter matches against.
|
|
44
|
+
*/
|
|
45
|
+
export declare function extractSearchableText(markdownContent: string): {
|
|
46
|
+
text: string;
|
|
47
|
+
sections: string[];
|
|
48
|
+
};
|
|
49
|
+
export declare function sectionsMatch(entrySections: string[], requested: string[]): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Read the epoch-millisecond `timestamp:` out of an entry's frontmatter.
|
|
52
|
+
*
|
|
53
|
+
* Preferred over `timestampFromEntryPath` because the filename only carries
|
|
54
|
+
* second resolution — `HH-MM-SS-µµµµµµ` looks precise but the µ field is
|
|
55
|
+
* `ms * 1000 + random`, so it cannot be read back as a time. Two entries written
|
|
56
|
+
* in the same second are indistinguishable by path and would sort arbitrarily in
|
|
57
|
+
* a chronological listing.
|
|
58
|
+
*/
|
|
59
|
+
export declare function timestampFromFrontmatter(markdownContent: string): number | null;
|
|
60
|
+
/**
|
|
61
|
+
* Recover the entry's timestamp from its path when the frontmatter is not to
|
|
62
|
+
* hand. Returns null if either the filename or the day directory does not match.
|
|
63
|
+
*/
|
|
64
|
+
export declare function timestampFromEntryPath(filePath: string): Date | null;
|
|
65
|
+
/**
|
|
66
|
+
* Id for an entry, and it is deliberately asymmetric by scope:
|
|
67
|
+
*
|
|
68
|
+
* user md5(scope + ':' + path relative to its root)
|
|
69
|
+
* project md5(scope + ':' + absolute root + ':' + path relative to its root)
|
|
70
|
+
*
|
|
71
|
+
* Neither form is the absolute entry path. Upstream stored that as the record's
|
|
72
|
+
* identity inside the sidecar, so renaming the journal directory made every
|
|
73
|
+
* existing record unreadable — search listed them and read refused them. A
|
|
74
|
+
* root-relative id survives the root moving, and the absolute `path` column is
|
|
75
|
+
* refreshed from the walk on every index run.
|
|
76
|
+
*
|
|
77
|
+
* `project` scope has to give up some of that. Every project on the machine
|
|
78
|
+
* shares one database and a relative entry path is only a date directory plus a
|
|
79
|
+
* timestamped filename, so two repos that journalled in the same second produced
|
|
80
|
+
* the same id and one silently replaced the other. The root is what distinguishes
|
|
81
|
+
* them. There is exactly one user root, so including it there would buy no
|
|
82
|
+
* discrimination and cost the move-stability property for nothing — hence the
|
|
83
|
+
* asymmetry rather than a blanket change.
|
|
84
|
+
*/
|
|
85
|
+
export declare function journalEntryId(scope: JournalScope, root: string, entryPath: string): string;
|
|
86
|
+
/**
|
|
87
|
+
* Pick the excerpt to show for a hit: a sliding window scored by how many query
|
|
88
|
+
* words it contains. Carried over from private-journal-mcp unchanged.
|
|
89
|
+
*/
|
|
90
|
+
export declare function generateExcerpt(text: string, query: string, maxLength?: number): string;
|
|
91
|
+
//# sourceMappingURL=markdown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/journal/markdown.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAIH,OAAO,EAA4B,KAAK,YAAY,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAEhG,uCAAuC;AACvC,eAAO,MAAM,eAAe,QAAwB,CAAC;AAErD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAKrD;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAQtD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,eAAe,EAAE,SAAS,EAAE,IAAI,GAAG,MAAM,CA2B9E;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,eAAe,EAAE,MAAM,GAAG;IAC9D,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB,CAgBA;AAuBD,wBAAgB,aAAa,CAAC,aAAa,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,OAAO,CAQnF;AAED;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM/E;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAwBpE;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAmB3F;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,SAAM,GAAG,MAAM,CA0BpF"}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The journal entry's on-disk format.
|
|
3
|
+
*
|
|
4
|
+
* Carried over from private-journal-mcp unchanged in substance, because it is a
|
|
5
|
+
* data contract with files already written:
|
|
6
|
+
*
|
|
7
|
+
* <root>/YYYY-MM-DD/HH-MM-SS-µµµµµµ.md
|
|
8
|
+
*
|
|
9
|
+
* ---
|
|
10
|
+
* title: "3:04:05 PM - May 27, 2025"
|
|
11
|
+
* date: 2025-05-27T20:04:05.123Z
|
|
12
|
+
* timestamp: 1748376245123
|
|
13
|
+
* ---
|
|
14
|
+
*
|
|
15
|
+
* ## Reflections
|
|
16
|
+
* …
|
|
17
|
+
*
|
|
18
|
+
* `µµµµµµ` is pseudo-microseconds — `ms * 1000 + floor(random() * 1000)` — so two
|
|
19
|
+
* writes inside the same millisecond collide about one time in a thousand. Kept
|
|
20
|
+
* as-is: changing the filename shape would orphan every existing entry, and the
|
|
21
|
+
* collision is the upstream behaviour its tests pin.
|
|
22
|
+
*
|
|
23
|
+
* What is NOT carried over is the `.embedding` JSON sidecar. That was the only
|
|
24
|
+
* enumeration path in the whole upstream package — search, list_recent_entries
|
|
25
|
+
* and read_recent_entries all read `*.embedding` and never listed `*.md` — so an
|
|
26
|
+
* entry whose embedding failed was written and then permanently invisible. The
|
|
27
|
+
* markdown files are the source of truth here and SQLite is a rebuildable index.
|
|
28
|
+
*/
|
|
29
|
+
import crypto from "node:crypto";
|
|
30
|
+
import path from "node:path";
|
|
31
|
+
import { JOURNAL_SECTION_HEADINGS } from "../types.js";
|
|
32
|
+
/** `YYYY-MM-DD` day-directory name. */
|
|
33
|
+
export const DAY_DIR_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
|
|
34
|
+
export function formatDayDirectory(date) {
|
|
35
|
+
const year = date.getFullYear();
|
|
36
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
37
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
38
|
+
return `${year}-${month}-${day}`;
|
|
39
|
+
}
|
|
40
|
+
export function formatEntryBasename(date) {
|
|
41
|
+
const hours = String(date.getHours()).padStart(2, "0");
|
|
42
|
+
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
43
|
+
const seconds = String(date.getSeconds()).padStart(2, "0");
|
|
44
|
+
const microseconds = String(date.getMilliseconds() * 1000 + Math.floor(Math.random() * 1000)).padStart(6, "0");
|
|
45
|
+
return `${hours}-${minutes}-${seconds}-${microseconds}`;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Render an entry. Section order is fixed by JOURNAL_SECTION_HEADINGS and empty
|
|
49
|
+
* categories are omitted.
|
|
50
|
+
*/
|
|
51
|
+
export function formatEntry(thoughts, timestamp) {
|
|
52
|
+
const timeDisplay = timestamp.toLocaleTimeString("en-US", {
|
|
53
|
+
hour12: true,
|
|
54
|
+
hour: "numeric",
|
|
55
|
+
minute: "2-digit",
|
|
56
|
+
second: "2-digit",
|
|
57
|
+
});
|
|
58
|
+
const dateDisplay = timestamp.toLocaleDateString("en-US", {
|
|
59
|
+
year: "numeric",
|
|
60
|
+
month: "long",
|
|
61
|
+
day: "numeric",
|
|
62
|
+
});
|
|
63
|
+
const sections = [];
|
|
64
|
+
for (const [key, heading] of JOURNAL_SECTION_HEADINGS) {
|
|
65
|
+
const value = thoughts[key];
|
|
66
|
+
if (value)
|
|
67
|
+
sections.push(`## ${heading}\n\n${value}`);
|
|
68
|
+
}
|
|
69
|
+
return `---
|
|
70
|
+
title: "${timeDisplay} - ${dateDisplay}"
|
|
71
|
+
date: ${timestamp.toISOString()}
|
|
72
|
+
timestamp: ${timestamp.getTime()}
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
${sections.join("\n\n")}
|
|
76
|
+
`;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Strip frontmatter and section headers, and harvest the headings.
|
|
80
|
+
*
|
|
81
|
+
* The returned `text` is what gets embedded; `sections` is what the
|
|
82
|
+
* `search_journal` section filter matches against.
|
|
83
|
+
*/
|
|
84
|
+
export function extractSearchableText(markdownContent) {
|
|
85
|
+
// Remove YAML frontmatter
|
|
86
|
+
const withoutFrontmatter = markdownContent.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/, "");
|
|
87
|
+
const sections = [];
|
|
88
|
+
const sectionMatches = withoutFrontmatter.match(/^## (.+)$/gm);
|
|
89
|
+
if (sectionMatches) {
|
|
90
|
+
sections.push(...sectionMatches.map((match) => match.replace(/^## /, "").trim()));
|
|
91
|
+
}
|
|
92
|
+
const cleanText = withoutFrontmatter
|
|
93
|
+
.replace(/^## .+$/gm, "") // Remove section headers
|
|
94
|
+
.replace(/\n{3,}/g, "\n\n") // Normalize whitespace
|
|
95
|
+
.trim();
|
|
96
|
+
return { text: cleanText, sections };
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Compare a caller-supplied section name against the rendered headings stored
|
|
100
|
+
* on the entry.
|
|
101
|
+
*
|
|
102
|
+
* FIXED FROM UPSTREAM. private-journal-mcp compared the caller's value directly
|
|
103
|
+
* against the rendered heading text, so of the six documented values only
|
|
104
|
+
* `reflections` and `observations` ever matched — `project_notes`,
|
|
105
|
+
* `user_context`, `technical_insights` and `world_knowledge` all matched
|
|
106
|
+
* nothing. The broken form was the worked example inside the live tool
|
|
107
|
+
* description, i.e. the model was actively instructed to pass filters that
|
|
108
|
+
* silently returned zero results.
|
|
109
|
+
*
|
|
110
|
+
* Both sides are normalised to lowercase alphanumerics, and the upstream
|
|
111
|
+
* substring leniency is preserved (`reflection` still matches `Reflections`).
|
|
112
|
+
* `feelings` still matches the legacy `## Feelings` heading on pre-2.0.0
|
|
113
|
+
* entries — the one compatibility promise upstream made to data already on disk.
|
|
114
|
+
*/
|
|
115
|
+
function normalizeSectionName(value) {
|
|
116
|
+
return value.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
117
|
+
}
|
|
118
|
+
export function sectionsMatch(entrySections, requested) {
|
|
119
|
+
if (requested.length === 0)
|
|
120
|
+
return true;
|
|
121
|
+
const normalizedEntry = entrySections.map(normalizeSectionName);
|
|
122
|
+
return requested.some((requestedSection) => {
|
|
123
|
+
const needle = normalizeSectionName(requestedSection);
|
|
124
|
+
if (!needle)
|
|
125
|
+
return false;
|
|
126
|
+
return normalizedEntry.some((entrySection) => entrySection.includes(needle));
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Read the epoch-millisecond `timestamp:` out of an entry's frontmatter.
|
|
131
|
+
*
|
|
132
|
+
* Preferred over `timestampFromEntryPath` because the filename only carries
|
|
133
|
+
* second resolution — `HH-MM-SS-µµµµµµ` looks precise but the µ field is
|
|
134
|
+
* `ms * 1000 + random`, so it cannot be read back as a time. Two entries written
|
|
135
|
+
* in the same second are indistinguishable by path and would sort arbitrarily in
|
|
136
|
+
* a chronological listing.
|
|
137
|
+
*/
|
|
138
|
+
export function timestampFromFrontmatter(markdownContent) {
|
|
139
|
+
const match = markdownContent.match(/^---\r?\n[\s\S]*?^timestamp:\s*(\d+)\s*$/m);
|
|
140
|
+
const raw = match?.[1];
|
|
141
|
+
if (!raw)
|
|
142
|
+
return null;
|
|
143
|
+
const value = Number.parseInt(raw, 10);
|
|
144
|
+
return Number.isFinite(value) && value > 0 ? value : null;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Recover the entry's timestamp from its path when the frontmatter is not to
|
|
148
|
+
* hand. Returns null if either the filename or the day directory does not match.
|
|
149
|
+
*/
|
|
150
|
+
export function timestampFromEntryPath(filePath) {
|
|
151
|
+
const filename = path.basename(filePath, ".md");
|
|
152
|
+
const timeMatch = filename.match(/^(\d{2})-(\d{2})-(\d{2})-\d{6}$/);
|
|
153
|
+
if (!timeMatch)
|
|
154
|
+
return null;
|
|
155
|
+
const dirName = path.basename(path.dirname(filePath));
|
|
156
|
+
const dateMatch = dirName.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
|
157
|
+
if (!dateMatch)
|
|
158
|
+
return null;
|
|
159
|
+
// Indices are checked by the regexes above; the non-null assertions the
|
|
160
|
+
// upstream version relied on are replaced with explicit reads so
|
|
161
|
+
// noUncheckedIndexedAccess is satisfied without a cast.
|
|
162
|
+
const [, hours, minutes, seconds] = timeMatch;
|
|
163
|
+
const [, year, month, day] = dateMatch;
|
|
164
|
+
if (!hours || !minutes || !seconds || !year || !month || !day)
|
|
165
|
+
return null;
|
|
166
|
+
return new Date(Number.parseInt(year, 10), Number.parseInt(month, 10) - 1, Number.parseInt(day, 10), Number.parseInt(hours, 10), Number.parseInt(minutes, 10), Number.parseInt(seconds, 10));
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Id for an entry, and it is deliberately asymmetric by scope:
|
|
170
|
+
*
|
|
171
|
+
* user md5(scope + ':' + path relative to its root)
|
|
172
|
+
* project md5(scope + ':' + absolute root + ':' + path relative to its root)
|
|
173
|
+
*
|
|
174
|
+
* Neither form is the absolute entry path. Upstream stored that as the record's
|
|
175
|
+
* identity inside the sidecar, so renaming the journal directory made every
|
|
176
|
+
* existing record unreadable — search listed them and read refused them. A
|
|
177
|
+
* root-relative id survives the root moving, and the absolute `path` column is
|
|
178
|
+
* refreshed from the walk on every index run.
|
|
179
|
+
*
|
|
180
|
+
* `project` scope has to give up some of that. Every project on the machine
|
|
181
|
+
* shares one database and a relative entry path is only a date directory plus a
|
|
182
|
+
* timestamped filename, so two repos that journalled in the same second produced
|
|
183
|
+
* the same id and one silently replaced the other. The root is what distinguishes
|
|
184
|
+
* them. There is exactly one user root, so including it there would buy no
|
|
185
|
+
* discrimination and cost the move-stability property for nothing — hence the
|
|
186
|
+
* asymmetry rather than a blanket change.
|
|
187
|
+
*/
|
|
188
|
+
export function journalEntryId(scope, root, entryPath) {
|
|
189
|
+
const relative = path.relative(root, entryPath).split(path.sep).join("/");
|
|
190
|
+
// The root joins the key for `project` scope only, and the asymmetry is the
|
|
191
|
+
// point.
|
|
192
|
+
//
|
|
193
|
+
// Every project on the machine shares ONE database (src/paths.ts), and a
|
|
194
|
+
// relative entry path is just a date directory and a timestamped filename. So
|
|
195
|
+
// for project entries the root is the only thing distinguishing two repos that
|
|
196
|
+
// journalled in the same second — without it they hash to one primary key and
|
|
197
|
+
// one silently replaces the other.
|
|
198
|
+
//
|
|
199
|
+
// There is exactly one user root, so for `user` scope the root adds no
|
|
200
|
+
// discrimination and including it would cost the property upstream built this
|
|
201
|
+
// function for: an id that survives the journal directory moving. That
|
|
202
|
+
// property is kept, and it is what the "stable across the root moving" test
|
|
203
|
+
// asserts.
|
|
204
|
+
const key = scope === "project" ? `${scope}:${path.resolve(root)}:${relative}` : `${scope}:${relative}`;
|
|
205
|
+
return crypto.createHash("md5").update(key).digest("hex");
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Pick the excerpt to show for a hit: a sliding window scored by how many query
|
|
209
|
+
* words it contains. Carried over from private-journal-mcp unchanged.
|
|
210
|
+
*/
|
|
211
|
+
export function generateExcerpt(text, query, maxLength = 200) {
|
|
212
|
+
if (!query || query.trim() === "") {
|
|
213
|
+
return text.slice(0, maxLength) + (text.length > maxLength ? "..." : "");
|
|
214
|
+
}
|
|
215
|
+
const queryWords = query.toLowerCase().split(/\s+/);
|
|
216
|
+
const textLower = text.toLowerCase();
|
|
217
|
+
let bestPosition = 0;
|
|
218
|
+
let bestScore = 0;
|
|
219
|
+
for (let i = 0; i <= text.length - maxLength; i += 20) {
|
|
220
|
+
const window = textLower.slice(i, i + maxLength);
|
|
221
|
+
const score = queryWords.reduce((sum, word) => sum + (window.includes(word) ? 1 : 0), 0);
|
|
222
|
+
if (score > bestScore) {
|
|
223
|
+
bestScore = score;
|
|
224
|
+
bestPosition = i;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
let excerpt = text.slice(bestPosition, bestPosition + maxLength);
|
|
228
|
+
if (bestPosition > 0)
|
|
229
|
+
excerpt = `...${excerpt}`;
|
|
230
|
+
if (bestPosition + maxLength < text.length)
|
|
231
|
+
excerpt += "...";
|
|
232
|
+
return excerpt;
|
|
233
|
+
}
|
|
234
|
+
//# sourceMappingURL=markdown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.js","sourceRoot":"","sources":["../../src/journal/markdown.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Journal retrieval.
|
|
3
|
+
*
|
|
4
|
+
* Reconciled from private-journal-mcp's `SearchService`. The storage half lost:
|
|
5
|
+
* its `search()` loaded every `.embedding` sidecar under both roots into memory
|
|
6
|
+
* on every query and cosine-scored them in JS — O(n) I/O and O(n) memory per
|
|
7
|
+
* call, with no cache. That is replaced by the same sqlite-vec KNN the
|
|
8
|
+
* conversation half uses, over `vec_journal_entries`.
|
|
9
|
+
*
|
|
10
|
+
* Two things were carried FORWARD unchanged in substance, because they are the
|
|
11
|
+
* best-engineered code in that source and the conversation half has no
|
|
12
|
+
* equivalent:
|
|
13
|
+
*
|
|
14
|
+
* - `readEntry`'s two-stage containment guard: resolve, require `.md`, require
|
|
15
|
+
* containment in the journal roots, realpath, then require containment in
|
|
16
|
+
* the REALPATH'd roots. The second stage blocks a symlink escape, and the
|
|
17
|
+
* two stages exist precisely because macOS `/tmp` and `/var` are symlinks —
|
|
18
|
+
* do not "simplify" it to one check.
|
|
19
|
+
* - `generateExcerpt`, in ./markdown.ts.
|
|
20
|
+
*
|
|
21
|
+
* `SearchResult` and `SearchOptions` were exported by BOTH upstream `search.ts`
|
|
22
|
+
* files with different shapes. The conversation names keep their meaning; these
|
|
23
|
+
* are `JournalSearchResult` and `JournalSearchOptions`, so the barrel can export
|
|
24
|
+
* all four.
|
|
25
|
+
*/
|
|
26
|
+
import type Database from "better-sqlite3";
|
|
27
|
+
import { type EmbedFn } from "../embeddings.js";
|
|
28
|
+
import type { JournalScope, JournalSearchResult } from "../types.js";
|
|
29
|
+
/** `both` means "do not filter by scope", matching upstream's `type` parameter. */
|
|
30
|
+
export type JournalScopeFilter = JournalScope | "both";
|
|
31
|
+
export interface JournalSearchOptions {
|
|
32
|
+
limit?: number | undefined;
|
|
33
|
+
minScore?: number | undefined;
|
|
34
|
+
sections?: string[] | undefined;
|
|
35
|
+
dateRange?: {
|
|
36
|
+
start?: Date | undefined;
|
|
37
|
+
end?: Date | undefined;
|
|
38
|
+
} | undefined;
|
|
39
|
+
/** Which journal to search. Named `type` on the MCP wire; kept as `scope` here. */
|
|
40
|
+
scope?: JournalScopeFilter | undefined;
|
|
41
|
+
}
|
|
42
|
+
export interface JournalRecentOptions {
|
|
43
|
+
limit?: number | undefined;
|
|
44
|
+
scope?: JournalScopeFilter | undefined;
|
|
45
|
+
dateRange?: {
|
|
46
|
+
start?: Date | undefined;
|
|
47
|
+
end?: Date | undefined;
|
|
48
|
+
} | undefined;
|
|
49
|
+
}
|
|
50
|
+
export interface JournalEntryContent {
|
|
51
|
+
path: string;
|
|
52
|
+
content: string;
|
|
53
|
+
sections: string[];
|
|
54
|
+
timestamp: number;
|
|
55
|
+
scope: JournalScope;
|
|
56
|
+
}
|
|
57
|
+
export interface JournalSearchServiceOptions {
|
|
58
|
+
/**
|
|
59
|
+
* Injected query encoder. Defaults to `generateQueryEmbedding`, which prepends
|
|
60
|
+
* the BGE retrieval prefix — bge is asymmetric, and private-journal-mcp
|
|
61
|
+
* embedded its queries with the same call it used for documents, which costs
|
|
62
|
+
* recall silently. The seam exists so the CI-safe test project can exercise
|
|
63
|
+
* retrieval without a model download; production should never pass it.
|
|
64
|
+
*/
|
|
65
|
+
embedQuery?: EmbedFn | undefined;
|
|
66
|
+
}
|
|
67
|
+
export declare class JournalSearchService {
|
|
68
|
+
private readonly db;
|
|
69
|
+
private readonly roots;
|
|
70
|
+
private readonly embedQuery;
|
|
71
|
+
/**
|
|
72
|
+
* @param db an open database from `initDatabase()`
|
|
73
|
+
* @param roots the journal roots, already de-duplicated (see `JournalStore.roots`)
|
|
74
|
+
*/
|
|
75
|
+
constructor(db: Database.Database, roots: string[], options?: JournalSearchServiceOptions);
|
|
76
|
+
search(query: string, options?: JournalSearchOptions): Promise<JournalSearchResult[]>;
|
|
77
|
+
listRecent(options?: JournalRecentOptions): JournalSearchResult[];
|
|
78
|
+
readRecentEntries(options?: {
|
|
79
|
+
limit?: number | undefined;
|
|
80
|
+
scope?: JournalScopeFilter | undefined;
|
|
81
|
+
}): Promise<JournalEntryContent[]>;
|
|
82
|
+
/**
|
|
83
|
+
* Read one entry off disk.
|
|
84
|
+
*
|
|
85
|
+
* Carried over from private-journal-mcp with its guard intact. The error text
|
|
86
|
+
* is treated as API by three assertions, so it is preserved verbatim.
|
|
87
|
+
*/
|
|
88
|
+
readEntry(filePath: string): Promise<string | null>;
|
|
89
|
+
private realRoots;
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=search.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/journal/search.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAIH,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAE3C,OAAO,EAAE,KAAK,OAAO,EAA0B,MAAM,kBAAkB,CAAC;AAExE,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAGrE,mFAAmF;AACnF,MAAM,MAAM,kBAAkB,GAAG,YAAY,GAAG,MAAM,CAAC;AAEvD,MAAM,WAAW,oBAAoB;IACnC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAChC,SAAS,CAAC,EACN;QACE,KAAK,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;QACzB,GAAG,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;KACxB,GACD,SAAS,CAAC;IACd,mFAAmF;IACnF,KAAK,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAC;CACxC;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,KAAK,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAC;IACvC,SAAS,CAAC,EACN;QACE,KAAK,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;QACzB,GAAG,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;KACxB,GACD,SAAS,CAAC;CACf;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,YAAY,CAAC;CACrB;AA0CD,MAAM,WAAW,2BAA2B;IAC1C;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAClC;AAED,qBAAa,oBAAoB;IAS7B,OAAO,CAAC,QAAQ,CAAC,EAAE;IARrB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAW;IACjC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAU;IAErC;;;OAGG;gBAEgB,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACtC,KAAK,EAAE,MAAM,EAAE,EACf,OAAO,GAAE,2BAAgC;IAMrC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,oBAAyB,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC;IA2C/F,UAAU,CAAC,OAAO,GAAE,oBAAyB,GAAG,mBAAmB,EAAE;IAsB/D,iBAAiB,CACrB,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QAAC,KAAK,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAA;KAAO,GACnF,OAAO,CAAC,mBAAmB,EAAE,CAAC;IAmBjC;;;;;OAKG;IACG,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;YAsC3C,SAAS;CAWxB"}
|