@nguyentamdat/mempalace 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.
- package/README.md +127 -0
- package/hooks/README.md +133 -0
- package/hooks/mempal_precompact_hook.sh +35 -0
- package/hooks/mempal_save_hook.sh +80 -0
- package/package.json +36 -0
- package/src/cli.ts +50 -0
- package/src/commands/compress.ts +161 -0
- package/src/commands/init.ts +40 -0
- package/src/commands/mine.ts +51 -0
- package/src/commands/search.ts +23 -0
- package/src/commands/split.ts +20 -0
- package/src/commands/status.ts +12 -0
- package/src/commands/wake-up.ts +20 -0
- package/src/config.ts +111 -0
- package/src/convo-miner.ts +373 -0
- package/src/dialect.ts +921 -0
- package/src/entity-detector.d.ts +25 -0
- package/src/entity-detector.ts +674 -0
- package/src/entity-registry.ts +806 -0
- package/src/general-extractor.ts +487 -0
- package/src/index.ts +5 -0
- package/src/knowledge-graph.ts +461 -0
- package/src/layers.ts +512 -0
- package/src/mcp-server.ts +1034 -0
- package/src/miner.ts +612 -0
- package/src/missing-modules.d.ts +43 -0
- package/src/normalize.ts +374 -0
- package/src/onboarding.ts +485 -0
- package/src/palace-graph.ts +310 -0
- package/src/room-detector-local.ts +415 -0
- package/src/room-detector.d.ts +1 -0
- package/src/room-detector.ts +6 -0
- package/src/searcher.ts +181 -0
- package/src/spellcheck.ts +200 -0
- package/src/split-mega-files.d.ts +8 -0
- package/src/split-mega-files.ts +297 -0
package/src/normalize.ts
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import { extname } from "path";
|
|
3
|
+
|
|
4
|
+
type JsonPrimitive = string | number | boolean | null;
|
|
5
|
+
type JsonValue = JsonPrimitive | JsonObject | JsonValue[];
|
|
6
|
+
type JsonObject = { [key: string]: JsonValue };
|
|
7
|
+
|
|
8
|
+
type Role = "user" | "assistant";
|
|
9
|
+
type MessageTuple = [Role, string];
|
|
10
|
+
|
|
11
|
+
type ClaudeCodeEntry = {
|
|
12
|
+
type?: JsonValue;
|
|
13
|
+
message?: JsonValue;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
type ClaudeAiEnvelope = {
|
|
17
|
+
messages?: JsonValue;
|
|
18
|
+
chat_messages?: JsonValue;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type ChatGptMessage = {
|
|
22
|
+
author?: JsonValue;
|
|
23
|
+
content?: JsonValue;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type ChatGptNode = {
|
|
27
|
+
parent?: JsonValue;
|
|
28
|
+
message?: JsonValue;
|
|
29
|
+
children?: JsonValue;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
type SpellcheckModule = {
|
|
33
|
+
spellcheckUserText?: (text: string) => string;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export async function normalize(filepath: string): Promise<string> {
|
|
37
|
+
let content: string;
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
content = readFileSync(filepath, "utf-8");
|
|
41
|
+
} catch (error) {
|
|
42
|
+
throw new Error(`Could not read ${filepath}: ${String(error)}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!content.trim()) {
|
|
46
|
+
return content;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const lines = content.split("\n");
|
|
50
|
+
if (lines.filter((line) => line.trim().startsWith(">")).length >= 3) {
|
|
51
|
+
return content;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const extension = extname(filepath).toLowerCase();
|
|
55
|
+
if (extension === ".json" || extension === ".jsonl" || ["{", "["].includes(content.trim().slice(0, 1))) {
|
|
56
|
+
const normalized = await _tryNormalizeJson(content);
|
|
57
|
+
if (normalized) {
|
|
58
|
+
return normalized;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return content;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function _tryNormalizeJson(content: string): Promise<string | null> {
|
|
66
|
+
let normalized = await _tryClaudeCodeJsonl(content);
|
|
67
|
+
if (normalized) {
|
|
68
|
+
return normalized;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let data: JsonValue;
|
|
72
|
+
try {
|
|
73
|
+
data = JSON.parse(content) as JsonValue;
|
|
74
|
+
} catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
for (const parser of [_tryClaudeAiJson, _tryChatgptJson, _trySlackJson]) {
|
|
79
|
+
normalized = await parser(data);
|
|
80
|
+
if (normalized) {
|
|
81
|
+
return normalized;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function _tryClaudeCodeJsonl(content: string): Promise<string | null> {
|
|
89
|
+
const lines = content
|
|
90
|
+
.trim()
|
|
91
|
+
.split("\n")
|
|
92
|
+
.map((line) => line.trim())
|
|
93
|
+
.filter((line) => line);
|
|
94
|
+
|
|
95
|
+
const messages: MessageTuple[] = [];
|
|
96
|
+
|
|
97
|
+
for (const line of lines) {
|
|
98
|
+
let entry: JsonValue;
|
|
99
|
+
try {
|
|
100
|
+
entry = JSON.parse(line) as JsonValue;
|
|
101
|
+
} catch {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (!isObject(entry)) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const claudeEntry = entry as ClaudeCodeEntry;
|
|
110
|
+
const msgType = typeof claudeEntry.type === "string" ? claudeEntry.type : "";
|
|
111
|
+
const message = isObject(claudeEntry.message) ? claudeEntry.message : {};
|
|
112
|
+
|
|
113
|
+
if (msgType === "human") {
|
|
114
|
+
const text = _extractContent(message.content ?? "");
|
|
115
|
+
if (text) {
|
|
116
|
+
messages.push(["user", text]);
|
|
117
|
+
}
|
|
118
|
+
} else if (msgType === "assistant") {
|
|
119
|
+
const text = _extractContent(message.content ?? "");
|
|
120
|
+
if (text) {
|
|
121
|
+
messages.push(["assistant", text]);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (messages.length >= 2) {
|
|
127
|
+
return _messagesToTranscript(messages);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function _tryClaudeAiJson(data: JsonValue): Promise<string | null> {
|
|
134
|
+
let candidate: JsonValue = data;
|
|
135
|
+
|
|
136
|
+
if (isObject(candidate)) {
|
|
137
|
+
const envelope = candidate as ClaudeAiEnvelope;
|
|
138
|
+
candidate = envelope.messages ?? envelope.chat_messages ?? [];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (!Array.isArray(candidate)) {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const messages: MessageTuple[] = [];
|
|
146
|
+
|
|
147
|
+
for (const item of candidate) {
|
|
148
|
+
if (!isObject(item)) {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const role = typeof item.role === "string" ? item.role : "";
|
|
153
|
+
const text = _extractContent(item.content ?? "");
|
|
154
|
+
|
|
155
|
+
if ((role === "user" || role === "human") && text) {
|
|
156
|
+
messages.push(["user", text]);
|
|
157
|
+
} else if ((role === "assistant" || role === "ai") && text) {
|
|
158
|
+
messages.push(["assistant", text]);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (messages.length >= 2) {
|
|
163
|
+
return _messagesToTranscript(messages);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function _tryChatgptJson(data: JsonValue): Promise<string | null> {
|
|
170
|
+
if (!isObject(data) || !isObject(data.mapping)) {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const mapping: Record<string, JsonValue> = data.mapping;
|
|
175
|
+
const messages: MessageTuple[] = [];
|
|
176
|
+
let rootId: string | null = null;
|
|
177
|
+
let fallbackRoot: string | null = null;
|
|
178
|
+
|
|
179
|
+
for (const [nodeId, nodeValue] of Object.entries(mapping)) {
|
|
180
|
+
if (!isObject(nodeValue)) {
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const node = nodeValue as ChatGptNode;
|
|
185
|
+
if (node.parent === null || node.parent === undefined) {
|
|
186
|
+
if (node.message === null || node.message === undefined) {
|
|
187
|
+
rootId = nodeId;
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (fallbackRoot === null) {
|
|
192
|
+
fallbackRoot = nodeId;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (!rootId) {
|
|
198
|
+
rootId = fallbackRoot;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (rootId) {
|
|
202
|
+
let currentId: string | null = rootId;
|
|
203
|
+
const visited = new Set<string>();
|
|
204
|
+
|
|
205
|
+
while (currentId && !visited.has(currentId)) {
|
|
206
|
+
visited.add(currentId);
|
|
207
|
+
|
|
208
|
+
const nodeValue: JsonValue | undefined = mapping[currentId];
|
|
209
|
+
const node: ChatGptNode = isObject(nodeValue) ? (nodeValue as ChatGptNode) : {};
|
|
210
|
+
const messageValue = node.message;
|
|
211
|
+
|
|
212
|
+
if (isObject(messageValue)) {
|
|
213
|
+
const message = messageValue as ChatGptMessage;
|
|
214
|
+
const role = isObject(message.author) && typeof message.author.role === "string" ? message.author.role : "";
|
|
215
|
+
const content = isObject(message.content) ? message.content : {};
|
|
216
|
+
const parts = Array.isArray(content.parts) ? content.parts : [];
|
|
217
|
+
const text = parts
|
|
218
|
+
.filter((part): part is string => typeof part === "string" && Boolean(part))
|
|
219
|
+
.join(" ")
|
|
220
|
+
.trim();
|
|
221
|
+
|
|
222
|
+
if (role === "user" && text) {
|
|
223
|
+
messages.push(["user", text]);
|
|
224
|
+
} else if (role === "assistant" && text) {
|
|
225
|
+
messages.push(["assistant", text]);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const children: JsonValue[] = Array.isArray(node.children) ? node.children : [];
|
|
230
|
+
currentId = typeof children[0] === "string" ? children[0] : null;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (messages.length >= 2) {
|
|
235
|
+
return _messagesToTranscript(messages);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async function _trySlackJson(data: JsonValue): Promise<string | null> {
|
|
242
|
+
if (!Array.isArray(data)) {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const messages: MessageTuple[] = [];
|
|
247
|
+
const seenUsers: Record<string, Role> = {};
|
|
248
|
+
let lastRole: Role | null = null;
|
|
249
|
+
|
|
250
|
+
for (const item of data) {
|
|
251
|
+
if (!isObject(item) || item.type !== "message") {
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const userIdValue = typeof item.user === "string" ? item.user : typeof item.username === "string" ? item.username : "";
|
|
256
|
+
const textValue = typeof item.text === "string" ? item.text.trim() : "";
|
|
257
|
+
|
|
258
|
+
if (!textValue || !userIdValue) {
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (!(userIdValue in seenUsers)) {
|
|
263
|
+
if (Object.keys(seenUsers).length === 0) {
|
|
264
|
+
seenUsers[userIdValue] = "user";
|
|
265
|
+
} else if (lastRole === "user") {
|
|
266
|
+
seenUsers[userIdValue] = "assistant";
|
|
267
|
+
} else {
|
|
268
|
+
seenUsers[userIdValue] = "user";
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
lastRole = seenUsers[userIdValue];
|
|
273
|
+
messages.push([seenUsers[userIdValue], textValue]);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (messages.length >= 2) {
|
|
277
|
+
return _messagesToTranscript(messages);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function _extractContent(content: JsonValue): string {
|
|
284
|
+
if (typeof content === "string") {
|
|
285
|
+
return content.trim();
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (Array.isArray(content)) {
|
|
289
|
+
const parts: string[] = [];
|
|
290
|
+
|
|
291
|
+
for (const item of content) {
|
|
292
|
+
if (typeof item === "string") {
|
|
293
|
+
parts.push(item);
|
|
294
|
+
} else if (isObject(item) && item.type === "text" && typeof item.text === "string") {
|
|
295
|
+
parts.push(item.text);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return parts.join(" ").trim();
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (isObject(content) && typeof content.text === "string") {
|
|
303
|
+
return content.text.trim();
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return "";
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export async function messagesToTranscript(messages: MessageTuple[], spellcheck = true): Promise<string> {
|
|
310
|
+
return _messagesToTranscript(messages, spellcheck);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async function _messagesToTranscript(messages: MessageTuple[], spellcheck = true): Promise<string> {
|
|
314
|
+
let fix: ((text: string) => string) | null = null;
|
|
315
|
+
|
|
316
|
+
if (spellcheck) {
|
|
317
|
+
try {
|
|
318
|
+
const spellcheckPath = "./spellcheck";
|
|
319
|
+
const spellcheckModuleUnknown: unknown = await import(spellcheckPath);
|
|
320
|
+
|
|
321
|
+
if (isSpellcheckModule(spellcheckModuleUnknown)) {
|
|
322
|
+
fix = spellcheckModuleUnknown.spellcheckUserText ?? null;
|
|
323
|
+
}
|
|
324
|
+
} catch {
|
|
325
|
+
fix = null;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const lines: string[] = [];
|
|
330
|
+
let index = 0;
|
|
331
|
+
|
|
332
|
+
while (index < messages.length) {
|
|
333
|
+
let [role, text] = messages[index];
|
|
334
|
+
|
|
335
|
+
if (role === "user") {
|
|
336
|
+
if (fix !== null) {
|
|
337
|
+
text = fix(text);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
lines.push(`> ${text}`);
|
|
341
|
+
|
|
342
|
+
if (index + 1 < messages.length && messages[index + 1][0] === "assistant") {
|
|
343
|
+
lines.push(messages[index + 1][1]);
|
|
344
|
+
index += 2;
|
|
345
|
+
} else {
|
|
346
|
+
index += 1;
|
|
347
|
+
}
|
|
348
|
+
} else {
|
|
349
|
+
lines.push(text);
|
|
350
|
+
index += 1;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
lines.push("");
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return lines.join("\n");
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function isObject(value: JsonValue | undefined | null): value is JsonObject {
|
|
360
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function isSpellcheckModule(value: unknown): value is SpellcheckModule {
|
|
364
|
+
if (typeof value !== "object" || value === null) {
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (!("spellcheckUserText" in value)) {
|
|
369
|
+
return true;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const moduleValue = value as { spellcheckUserText?: unknown };
|
|
373
|
+
return moduleValue.spellcheckUserText === undefined || typeof moduleValue.spellcheckUserText === "function";
|
|
374
|
+
}
|