synapse-kit-memory 0.0.0-stage → 0.2.2
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 +25 -2
- package/causal-memory.json +4106 -0
- package/dist/causal-benchmark.d.ts +29 -0
- package/dist/causal-config.d.ts +24 -0
- package/dist/causal-migrator.d.ts +11 -0
- package/dist/causal-query.d.ts +21 -0
- package/dist/causal-server.d.ts +299 -0
- package/dist/causal-store.d.ts +247 -0
- package/dist/cli.d.ts +12 -0
- package/dist/cli.js +2230 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +1905 -0
- package/package.json +41 -4
package/dist/index.js
ADDED
|
@@ -0,0 +1,1905 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// causal-config.ts
|
|
3
|
+
import { homedir } from "os";
|
|
4
|
+
import { resolve } from "path";
|
|
5
|
+
var CAUSAL_MEMORY_CONFIG = {
|
|
6
|
+
SCHEMA_VERSION: process.env.SYNAPSE_MEMORY_VERSION || "2.1.0",
|
|
7
|
+
GLOBAL_DB_PATH: process.env.SYNAPSE_MEMORY_PATH || resolve(homedir(), ".gemini", "antigravity", "causal_memory.json"),
|
|
8
|
+
LEGACY_BACKUP_PATH: process.env.SYNAPSE_MEMORY_BACKUP_PATH || resolve(homedir(), ".gemini", "antigravity", "memory_backup_20261101.json"),
|
|
9
|
+
LOCAL_DB_PATH: resolve(import.meta.dir, "causal-memory.json"),
|
|
10
|
+
DEFAULT_HOST: process.env.SYNAPSE_MEMORY_HOST || "127.0.0.1",
|
|
11
|
+
DEFAULT_PORT: parseInt(process.env.SYNAPSE_MEMORY_PORT || "4242", 10),
|
|
12
|
+
MCP_PROTOCOL_VERSION: process.env.SYNAPSE_MCP_VERSION || "2024-11-05",
|
|
13
|
+
MCP_SERVER_NAME: "synapse-causal-memory",
|
|
14
|
+
MAX_CONTEXT_PREVIEW_LINES: parseInt(process.env.SYNAPSE_MEMORY_MAX_PREVIEW || "5", 10)
|
|
15
|
+
};
|
|
16
|
+
// causal-store.ts
|
|
17
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, statSync } from "fs";
|
|
18
|
+
import { dirname } from "path";
|
|
19
|
+
class CausalMemoryStore {
|
|
20
|
+
dbPath;
|
|
21
|
+
database;
|
|
22
|
+
lastMtimeMs = 0;
|
|
23
|
+
alwaysActiveRules = [];
|
|
24
|
+
domainIndex = new Map;
|
|
25
|
+
commandIndex = new Map;
|
|
26
|
+
exactKeywordIndex = new Map;
|
|
27
|
+
exactFileIndex = new Map;
|
|
28
|
+
prefixFileRules = [];
|
|
29
|
+
compiledSearchRules = [];
|
|
30
|
+
titleToRuleMap = new Map;
|
|
31
|
+
idToRuleMap = new Map;
|
|
32
|
+
relationAdjacency = new Map;
|
|
33
|
+
constructor(customPath, initialDatabase) {
|
|
34
|
+
this.dbPath = customPath || (existsSync(CAUSAL_MEMORY_CONFIG.GLOBAL_DB_PATH) ? CAUSAL_MEMORY_CONFIG.GLOBAL_DB_PATH : CAUSAL_MEMORY_CONFIG.LOCAL_DB_PATH);
|
|
35
|
+
if (initialDatabase) {
|
|
36
|
+
this.database = initialDatabase;
|
|
37
|
+
this.rebuildIndices();
|
|
38
|
+
} else {
|
|
39
|
+
this.database = this.loadDatabase();
|
|
40
|
+
this.rebuildIndices();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
reloadIfNeeded() {
|
|
44
|
+
try {
|
|
45
|
+
if (!existsSync(this.dbPath))
|
|
46
|
+
return false;
|
|
47
|
+
const stat = statSync(this.dbPath);
|
|
48
|
+
if (stat.mtimeMs > this.lastMtimeMs) {
|
|
49
|
+
this.database = this.loadDatabase();
|
|
50
|
+
this.rebuildIndices();
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
} catch {}
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
reload() {
|
|
57
|
+
this.database = this.loadDatabase();
|
|
58
|
+
this.rebuildIndices();
|
|
59
|
+
}
|
|
60
|
+
loadFromMemory(db) {
|
|
61
|
+
this.database = db;
|
|
62
|
+
this.rebuildIndices();
|
|
63
|
+
}
|
|
64
|
+
rebuildIndices() {
|
|
65
|
+
this.alwaysActiveRules = [];
|
|
66
|
+
this.domainIndex.clear();
|
|
67
|
+
this.commandIndex.clear();
|
|
68
|
+
this.exactKeywordIndex.clear();
|
|
69
|
+
this.exactFileIndex.clear();
|
|
70
|
+
this.prefixFileRules = [];
|
|
71
|
+
this.compiledSearchRules = [];
|
|
72
|
+
this.titleToRuleMap.clear();
|
|
73
|
+
this.idToRuleMap.clear();
|
|
74
|
+
this.relationAdjacency.clear();
|
|
75
|
+
const addTokenToKeywordIndex = (token, rule) => {
|
|
76
|
+
if (!token)
|
|
77
|
+
return;
|
|
78
|
+
let list = this.exactKeywordIndex.get(token);
|
|
79
|
+
if (!list) {
|
|
80
|
+
list = [];
|
|
81
|
+
this.exactKeywordIndex.set(token, list);
|
|
82
|
+
}
|
|
83
|
+
if (list[list.length - 1] !== rule) {
|
|
84
|
+
list.push(rule);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
for (const rule of this.database.rules) {
|
|
88
|
+
const domainLower = rule.domain.toLowerCase();
|
|
89
|
+
const titleLower = rule.title.toLowerCase();
|
|
90
|
+
const idLower = rule.id.toLowerCase();
|
|
91
|
+
this.titleToRuleMap.set(titleLower, rule);
|
|
92
|
+
this.idToRuleMap.set(idLower, rule);
|
|
93
|
+
let domList = this.domainIndex.get(domainLower);
|
|
94
|
+
if (!domList) {
|
|
95
|
+
domList = [];
|
|
96
|
+
this.domainIndex.set(domainLower, domList);
|
|
97
|
+
}
|
|
98
|
+
domList.push(rule);
|
|
99
|
+
if (rule.triggers.alwaysActive) {
|
|
100
|
+
this.alwaysActiveRules.push(rule);
|
|
101
|
+
}
|
|
102
|
+
if (rule.triggers.commands) {
|
|
103
|
+
for (const cmd of rule.triggers.commands) {
|
|
104
|
+
const cmdLower = cmd.toLowerCase();
|
|
105
|
+
let cmdList = this.commandIndex.get(cmdLower);
|
|
106
|
+
if (!cmdList) {
|
|
107
|
+
cmdList = [];
|
|
108
|
+
this.commandIndex.set(cmdLower, cmdList);
|
|
109
|
+
}
|
|
110
|
+
cmdList.push(rule);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
addTokenToKeywordIndex(titleLower, rule);
|
|
114
|
+
addTokenToKeywordIndex(domainLower, rule);
|
|
115
|
+
for (const part of titleLower.split(/[\s_-]+/)) {
|
|
116
|
+
addTokenToKeywordIndex(part, rule);
|
|
117
|
+
}
|
|
118
|
+
if (rule.triggers.keywords) {
|
|
119
|
+
for (const kw of rule.triggers.keywords) {
|
|
120
|
+
const kwLower = kw.toLowerCase();
|
|
121
|
+
addTokenToKeywordIndex(kwLower, rule);
|
|
122
|
+
for (const sub of kwLower.split(/[\s_-]+/)) {
|
|
123
|
+
addTokenToKeywordIndex(sub, rule);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (rule.triggers.files && rule.triggers.files.length > 0) {
|
|
128
|
+
const prefixPatterns = [];
|
|
129
|
+
for (const pattern of rule.triggers.files) {
|
|
130
|
+
const cleaned = (pattern.includes("\\") ? pattern.replaceAll("\\", "/") : pattern).replace(/^\*\*\/|\*\*$|\*$/g, "").replace(/\/+$/, "").toLowerCase();
|
|
131
|
+
if (!cleaned)
|
|
132
|
+
continue;
|
|
133
|
+
let fList = this.exactFileIndex.get(cleaned);
|
|
134
|
+
if (!fList) {
|
|
135
|
+
fList = [];
|
|
136
|
+
this.exactFileIndex.set(cleaned, fList);
|
|
137
|
+
}
|
|
138
|
+
fList.push(rule);
|
|
139
|
+
if (cleaned.endsWith("-") || cleaned.endsWith("_") || cleaned.includes(":")) {
|
|
140
|
+
prefixPatterns.push(cleaned);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (prefixPatterns.length > 0) {
|
|
144
|
+
this.prefixFileRules.push({ rule, domainLower, cleanPatterns: prefixPatterns });
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
for (const rel of this.database.relations) {
|
|
149
|
+
const fromLower = rel.from.toLowerCase();
|
|
150
|
+
const toLower = rel.to.toLowerCase();
|
|
151
|
+
let fromAdj = this.relationAdjacency.get(fromLower);
|
|
152
|
+
if (!fromAdj) {
|
|
153
|
+
fromAdj = [];
|
|
154
|
+
this.relationAdjacency.set(fromLower, fromAdj);
|
|
155
|
+
}
|
|
156
|
+
fromAdj.push(rel);
|
|
157
|
+
if (toLower !== fromLower) {
|
|
158
|
+
let toAdj = this.relationAdjacency.get(toLower);
|
|
159
|
+
if (!toAdj) {
|
|
160
|
+
toAdj = [];
|
|
161
|
+
this.relationAdjacency.set(toLower, toAdj);
|
|
162
|
+
}
|
|
163
|
+
toAdj.push(rel);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
loadDatabase() {
|
|
168
|
+
try {
|
|
169
|
+
if (existsSync(this.dbPath)) {
|
|
170
|
+
const stat = statSync(this.dbPath);
|
|
171
|
+
this.lastMtimeMs = stat.mtimeMs;
|
|
172
|
+
const raw = readFileSync(this.dbPath, "utf-8");
|
|
173
|
+
const parsed = JSON.parse(raw);
|
|
174
|
+
const rules = Array.isArray(parsed.rules) ? parsed.rules : [];
|
|
175
|
+
const relations = Array.isArray(parsed.relations) ? parsed.relations : [];
|
|
176
|
+
return {
|
|
177
|
+
version: parsed.version || CAUSAL_MEMORY_CONFIG.SCHEMA_VERSION,
|
|
178
|
+
updatedAt: parsed.updatedAt || new Date().toISOString(),
|
|
179
|
+
totalRules: rules.length,
|
|
180
|
+
totalRelations: relations.length,
|
|
181
|
+
rules,
|
|
182
|
+
relations
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
} catch {}
|
|
186
|
+
return {
|
|
187
|
+
version: CAUSAL_MEMORY_CONFIG.SCHEMA_VERSION,
|
|
188
|
+
updatedAt: new Date().toISOString(),
|
|
189
|
+
totalRules: 0,
|
|
190
|
+
totalRelations: 0,
|
|
191
|
+
rules: [],
|
|
192
|
+
relations: []
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
saveDatabase() {
|
|
196
|
+
try {
|
|
197
|
+
const dir = dirname(this.dbPath);
|
|
198
|
+
if (!existsSync(dir)) {
|
|
199
|
+
mkdirSync(dir, { recursive: true });
|
|
200
|
+
}
|
|
201
|
+
const serialized = JSON.stringify(this.database, null, 2);
|
|
202
|
+
writeFileSync(this.dbPath, serialized, "utf-8");
|
|
203
|
+
this.lastMtimeMs = statSync(this.dbPath).mtimeMs;
|
|
204
|
+
if (this.dbPath === CAUSAL_MEMORY_CONFIG.GLOBAL_DB_PATH && existsSync(dirname(CAUSAL_MEMORY_CONFIG.LOCAL_DB_PATH))) {
|
|
205
|
+
writeFileSync(CAUSAL_MEMORY_CONFIG.LOCAL_DB_PATH, serialized, "utf-8");
|
|
206
|
+
}
|
|
207
|
+
} catch (error) {
|
|
208
|
+
throw new Error(`\uC778\uACFC\uC728 \uBA54\uBAA8\uB9AC \uB514\uC2A4\uD06C \uC800\uC7A5 \uC2E4\uD328: ${error instanceof Error ? error.message : String(error)}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
getAllRules() {
|
|
212
|
+
return this.database.rules;
|
|
213
|
+
}
|
|
214
|
+
getAllRelations() {
|
|
215
|
+
return this.database.relations;
|
|
216
|
+
}
|
|
217
|
+
getStatus() {
|
|
218
|
+
const domainCounts = {};
|
|
219
|
+
for (const [dom, list] of this.domainIndex.entries()) {
|
|
220
|
+
const originalDomainName = list[0]?.domain || dom;
|
|
221
|
+
domainCounts[originalDomainName] = list.length;
|
|
222
|
+
}
|
|
223
|
+
return {
|
|
224
|
+
version: this.database.version,
|
|
225
|
+
updatedAt: this.database.updatedAt,
|
|
226
|
+
totalRules: this.database.rules.length,
|
|
227
|
+
totalRelations: this.database.relations.length,
|
|
228
|
+
dbPath: this.dbPath,
|
|
229
|
+
domains: Object.keys(domainCounts),
|
|
230
|
+
domainCounts
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
getCompiledSearchRules() {
|
|
234
|
+
if (this.compiledSearchRules.length === 0 && this.database.rules.length > 0) {
|
|
235
|
+
for (const rule of this.database.rules) {
|
|
236
|
+
const domainLower = rule.domain.toLowerCase();
|
|
237
|
+
const searchableText = [
|
|
238
|
+
rule.title,
|
|
239
|
+
rule.domain,
|
|
240
|
+
...rule.triggers.keywords || [],
|
|
241
|
+
...rule.invariants,
|
|
242
|
+
...rule.actions,
|
|
243
|
+
...rule.context
|
|
244
|
+
].join(" ").toLowerCase();
|
|
245
|
+
this.compiledSearchRules.push({ rule, domainLower, searchableText });
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return this.compiledSearchRules;
|
|
249
|
+
}
|
|
250
|
+
queryResonance(options) {
|
|
251
|
+
const startTime = performance.now();
|
|
252
|
+
const targetDomain = options.domain ? options.domain.toLowerCase() : undefined;
|
|
253
|
+
const queryCommand = options.command ? options.command.toLowerCase() : undefined;
|
|
254
|
+
const propagate = options.propagateRelations ?? true;
|
|
255
|
+
const maxHops = options.maxHops ?? 1;
|
|
256
|
+
const matchedRulesMap = new Map;
|
|
257
|
+
const nonAlwaysMatchedTitles = new Set;
|
|
258
|
+
for (const rule of this.alwaysActiveRules) {
|
|
259
|
+
if (targetDomain && rule.domain.toLowerCase() !== targetDomain)
|
|
260
|
+
continue;
|
|
261
|
+
matchedRulesMap.set(rule.id, rule);
|
|
262
|
+
}
|
|
263
|
+
if (queryCommand) {
|
|
264
|
+
const cmdRules = this.commandIndex.get(queryCommand);
|
|
265
|
+
if (cmdRules) {
|
|
266
|
+
for (const rule of cmdRules) {
|
|
267
|
+
if (targetDomain && rule.domain.toLowerCase() !== targetDomain)
|
|
268
|
+
continue;
|
|
269
|
+
matchedRulesMap.set(rule.id, rule);
|
|
270
|
+
nonAlwaysMatchedTitles.add(rule.title.toLowerCase());
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (options.files && options.files.length > 0 && this.exactFileIndex.size > 0) {
|
|
275
|
+
const hasPrefixRules = this.prefixFileRules.length > 0;
|
|
276
|
+
const checkedDirs = new Set;
|
|
277
|
+
const registerHits = (hits) => {
|
|
278
|
+
if (!hits)
|
|
279
|
+
return;
|
|
280
|
+
for (let i = 0;i < hits.length; i++) {
|
|
281
|
+
const rule = hits[i];
|
|
282
|
+
if (targetDomain && rule.domain.toLowerCase() !== targetDomain)
|
|
283
|
+
continue;
|
|
284
|
+
matchedRulesMap.set(rule.id, rule);
|
|
285
|
+
nonAlwaysMatchedTitles.add(rule.title.toLowerCase());
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
let lastTwoSeg = "";
|
|
289
|
+
for (let i = 0;i < options.files.length; i++) {
|
|
290
|
+
const rawFile = options.files[i];
|
|
291
|
+
const nf = (rawFile.includes("\\") ? rawFile.replaceAll("\\", "/") : rawFile).toLowerCase();
|
|
292
|
+
registerHits(this.exactFileIndex.get(nf));
|
|
293
|
+
const firstSlash = nf.indexOf("/");
|
|
294
|
+
if (firstSlash > 0) {
|
|
295
|
+
const secondSlash = nf.indexOf("/", firstSlash + 1);
|
|
296
|
+
const twoSeg = secondSlash > 0 ? nf.slice(0, secondSlash) : nf.slice(0, firstSlash);
|
|
297
|
+
if (twoSeg !== lastTwoSeg) {
|
|
298
|
+
lastTwoSeg = twoSeg;
|
|
299
|
+
if (!checkedDirs.has(twoSeg)) {
|
|
300
|
+
checkedDirs.add(twoSeg);
|
|
301
|
+
registerHits(this.exactFileIndex.get(twoSeg));
|
|
302
|
+
const oneSeg = nf.slice(0, firstSlash);
|
|
303
|
+
if (oneSeg !== twoSeg && !checkedDirs.has(oneSeg)) {
|
|
304
|
+
checkedDirs.add(oneSeg);
|
|
305
|
+
registerHits(this.exactFileIndex.get(oneSeg));
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
const lastSlash = nf.lastIndexOf("/");
|
|
310
|
+
registerHits(this.exactFileIndex.get(nf.slice(lastSlash + 1)));
|
|
311
|
+
} else {
|
|
312
|
+
checkedDirs.add(nf);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
if (hasPrefixRules && checkedDirs.size > 0) {
|
|
316
|
+
const prefixArr = Array.from(checkedDirs);
|
|
317
|
+
for (const entry of this.prefixFileRules) {
|
|
318
|
+
if (targetDomain && entry.domainLower !== targetDomain)
|
|
319
|
+
continue;
|
|
320
|
+
if (matchedRulesMap.has(entry.rule.id))
|
|
321
|
+
continue;
|
|
322
|
+
for (const pattern of entry.cleanPatterns) {
|
|
323
|
+
if (prefixArr.some((p) => p.includes(pattern) || pattern.includes(p))) {
|
|
324
|
+
matchedRulesMap.set(entry.rule.id, entry.rule);
|
|
325
|
+
nonAlwaysMatchedTitles.add(entry.rule.title.toLowerCase());
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
const queryKeywords = (options.keywords || []).flatMap((k) => k.toLowerCase().split(/\s+/)).filter((k) => k.length > 0);
|
|
333
|
+
if (queryKeywords.length > 0) {
|
|
334
|
+
const unindexedKeywords = [];
|
|
335
|
+
for (const qk of queryKeywords) {
|
|
336
|
+
const exactHits = this.exactKeywordIndex.get(qk);
|
|
337
|
+
if (exactHits && exactHits.length > 0) {
|
|
338
|
+
for (const rule of exactHits) {
|
|
339
|
+
if (targetDomain && rule.domain.toLowerCase() !== targetDomain)
|
|
340
|
+
continue;
|
|
341
|
+
matchedRulesMap.set(rule.id, rule);
|
|
342
|
+
nonAlwaysMatchedTitles.add(rule.title.toLowerCase());
|
|
343
|
+
}
|
|
344
|
+
} else {
|
|
345
|
+
unindexedKeywords.push(qk);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
if (unindexedKeywords.length > 0) {
|
|
349
|
+
const searchPool = this.getCompiledSearchRules();
|
|
350
|
+
for (const entry of searchPool) {
|
|
351
|
+
if (targetDomain && entry.domainLower !== targetDomain)
|
|
352
|
+
continue;
|
|
353
|
+
if (matchedRulesMap.has(entry.rule.id))
|
|
354
|
+
continue;
|
|
355
|
+
for (const qk of unindexedKeywords) {
|
|
356
|
+
if (entry.searchableText.includes(qk)) {
|
|
357
|
+
matchedRulesMap.set(entry.rule.id, entry.rule);
|
|
358
|
+
nonAlwaysMatchedTitles.add(entry.rule.title.toLowerCase());
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
const activeRelationsSet = new Set;
|
|
366
|
+
const visitedTitles = new Set(Array.from(matchedRulesMap.values()).map((r) => r.title.toLowerCase()));
|
|
367
|
+
let currentFrontier = new Set(nonAlwaysMatchedTitles.size > 0 ? nonAlwaysMatchedTitles : visitedTitles);
|
|
368
|
+
const effectiveHops = propagate ? Math.max(1, maxHops) : 1;
|
|
369
|
+
for (let hop = 0;hop < effectiveHops; hop++) {
|
|
370
|
+
const nextFrontier = new Set;
|
|
371
|
+
for (const titleLower of currentFrontier) {
|
|
372
|
+
const adjRelations = this.relationAdjacency.get(titleLower);
|
|
373
|
+
if (!adjRelations)
|
|
374
|
+
continue;
|
|
375
|
+
for (const rel of adjRelations) {
|
|
376
|
+
activeRelationsSet.add(rel);
|
|
377
|
+
if (propagate && nonAlwaysMatchedTitles.size > 0) {
|
|
378
|
+
const neighborLower = rel.from.toLowerCase() === titleLower ? rel.to.toLowerCase() : rel.from.toLowerCase();
|
|
379
|
+
if (!visitedTitles.has(neighborLower)) {
|
|
380
|
+
visitedTitles.add(neighborLower);
|
|
381
|
+
nextFrontier.add(neighborLower);
|
|
382
|
+
const neighborRule = this.titleToRuleMap.get(neighborLower);
|
|
383
|
+
if (neighborRule && (!targetDomain || neighborRule.domain.toLowerCase() === targetDomain)) {
|
|
384
|
+
matchedRulesMap.set(neighborRule.id, neighborRule);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
if (nextFrontier.size === 0)
|
|
391
|
+
break;
|
|
392
|
+
currentFrontier = nextFrontier;
|
|
393
|
+
}
|
|
394
|
+
const matchedRules = Array.from(matchedRulesMap.values());
|
|
395
|
+
const activeRelations = Array.from(activeRelationsSet);
|
|
396
|
+
const duration = performance.now() - startTime;
|
|
397
|
+
const totalCount = this.database.rules.length;
|
|
398
|
+
const savings = totalCount > 0 ? ((totalCount - matchedRules.length) / totalCount * 100).toFixed(1) + "%" : "0.0%";
|
|
399
|
+
return {
|
|
400
|
+
queryDurationMs: Math.round(duration * 100) / 100,
|
|
401
|
+
matchedCount: matchedRules.length,
|
|
402
|
+
totalDatabaseRules: totalCount,
|
|
403
|
+
resonanceRules: matchedRules,
|
|
404
|
+
activeRelations,
|
|
405
|
+
tokenDietSavingsRatio: savings
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
assertRule(rule, persistToDisk = true) {
|
|
409
|
+
const existingIndex = this.database.rules.findIndex((r) => r.id === rule.id || r.title.toLowerCase() === rule.title.toLowerCase());
|
|
410
|
+
const updatedRules = [...this.database.rules];
|
|
411
|
+
const today = new Date().toISOString().split("T")[0];
|
|
412
|
+
if (existingIndex >= 0) {
|
|
413
|
+
updatedRules[existingIndex] = {
|
|
414
|
+
...rule,
|
|
415
|
+
id: rule.id || updatedRules[existingIndex].id,
|
|
416
|
+
lastVerified: today
|
|
417
|
+
};
|
|
418
|
+
} else {
|
|
419
|
+
updatedRules.push({
|
|
420
|
+
...rule,
|
|
421
|
+
lastVerified: today
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
this.database = {
|
|
425
|
+
...this.database,
|
|
426
|
+
updatedAt: new Date().toISOString(),
|
|
427
|
+
totalRules: updatedRules.length,
|
|
428
|
+
rules: updatedRules
|
|
429
|
+
};
|
|
430
|
+
this.rebuildIndices();
|
|
431
|
+
if (persistToDisk) {
|
|
432
|
+
this.saveDatabase();
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
readSmartGraph(files = []) {
|
|
436
|
+
const resonance = this.queryResonance({ files });
|
|
437
|
+
const status = this.getStatus();
|
|
438
|
+
const entities = resonance.resonanceRules.map((r) => ({
|
|
439
|
+
name: r.title,
|
|
440
|
+
entityType: r.entityType || r.domain,
|
|
441
|
+
domain: r.domain,
|
|
442
|
+
invariants: r.invariants,
|
|
443
|
+
actions: r.actions,
|
|
444
|
+
observations: [
|
|
445
|
+
...r.invariants.map((i) => `[\uBD88\uBCC0\uADDC\uCE59] ${i}`),
|
|
446
|
+
...r.actions.map((a) => `[\uC2E4\uD589\uC9C0\uCE68] ${a}`),
|
|
447
|
+
...r.context
|
|
448
|
+
]
|
|
449
|
+
}));
|
|
450
|
+
return {
|
|
451
|
+
causalMemoryEngine: `v${status.version} (Active Resonance Mode)`,
|
|
452
|
+
tokenDietSavings: resonance.tokenDietSavingsRatio,
|
|
453
|
+
queryDurationMs: resonance.queryDurationMs,
|
|
454
|
+
activeEntitiesCount: entities.length,
|
|
455
|
+
totalIndexedRules: status.totalRules,
|
|
456
|
+
totalIndexedRelations: status.totalRelations,
|
|
457
|
+
availableDomains: status.domainCounts,
|
|
458
|
+
hint: "\uBB34\uAD00\uD55C \uB3C4\uBA54\uC778 \uC9C0\uC2DD\uC740 \uD1A0\uD070 \uC808\uAC10\uC744 \uC704\uD574 0ms \uC2A4\uD0B5\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uD2B9\uC815 \uB3C4\uBA54\uC778 \uC0C1\uC138 \uC870\uD68C\uB294 search_nodes \uB610\uB294 query_causal_memory\uB97C \uC0AC\uC6A9\uD558\uC138\uC694.",
|
|
459
|
+
entities,
|
|
460
|
+
relations: resonance.activeRelations
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
searchNodes(query) {
|
|
464
|
+
const trimmed = query.trim();
|
|
465
|
+
if (!trimmed) {
|
|
466
|
+
return this.readSmartGraph();
|
|
467
|
+
}
|
|
468
|
+
const tokens = trimmed.toLowerCase().split(/\s+/).filter((t) => t.length > 0);
|
|
469
|
+
const matchedRules = [];
|
|
470
|
+
const matchedTitles = new Set;
|
|
471
|
+
const searchPool = this.getCompiledSearchRules();
|
|
472
|
+
for (const entry of searchPool) {
|
|
473
|
+
const ruleIdLower = entry.rule.id.toLowerCase();
|
|
474
|
+
if (tokens.some((tok) => entry.searchableText.includes(tok) || ruleIdLower.includes(tok))) {
|
|
475
|
+
matchedRules.push(entry.rule);
|
|
476
|
+
matchedTitles.add(entry.rule.title.toLowerCase());
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
const relationsSet = new Set;
|
|
480
|
+
for (const titleLower of matchedTitles) {
|
|
481
|
+
const adj = this.relationAdjacency.get(titleLower);
|
|
482
|
+
if (adj) {
|
|
483
|
+
for (const r of adj)
|
|
484
|
+
relationsSet.add(r);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
return {
|
|
488
|
+
entities: matchedRules.map((r) => ({
|
|
489
|
+
name: r.title,
|
|
490
|
+
entityType: r.entityType || r.domain,
|
|
491
|
+
observations: [...r.invariants, ...r.actions, ...r.context]
|
|
492
|
+
})),
|
|
493
|
+
relations: Array.from(relationsSet)
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
openNodes(names) {
|
|
497
|
+
const matchedRules = [];
|
|
498
|
+
const matchedTitles = new Set;
|
|
499
|
+
for (const name of names) {
|
|
500
|
+
const lower = name.toLowerCase();
|
|
501
|
+
const rule = this.titleToRuleMap.get(lower) || this.idToRuleMap.get(lower);
|
|
502
|
+
if (rule && !matchedTitles.has(rule.title.toLowerCase())) {
|
|
503
|
+
matchedRules.push(rule);
|
|
504
|
+
matchedTitles.add(rule.title.toLowerCase());
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
const relationsSet = new Set;
|
|
508
|
+
for (const titleLower of matchedTitles) {
|
|
509
|
+
const adj = this.relationAdjacency.get(titleLower);
|
|
510
|
+
if (adj) {
|
|
511
|
+
for (const r of adj)
|
|
512
|
+
relationsSet.add(r);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
return {
|
|
516
|
+
entities: matchedRules.map((r) => ({
|
|
517
|
+
name: r.title,
|
|
518
|
+
entityType: r.entityType || r.domain,
|
|
519
|
+
observations: [...r.invariants, ...r.actions, ...r.context]
|
|
520
|
+
})),
|
|
521
|
+
relations: Array.from(relationsSet)
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
createEntities(entities, persistToDisk = true) {
|
|
525
|
+
const created = [];
|
|
526
|
+
const updatedRules = [...this.database.rules];
|
|
527
|
+
const today = new Date().toISOString().split("T")[0];
|
|
528
|
+
for (const ent of entities) {
|
|
529
|
+
if (this.titleToRuleMap.has(ent.name.toLowerCase()))
|
|
530
|
+
continue;
|
|
531
|
+
const inferred = CausalMemoryStore.inferRuleFromEntity(ent, today);
|
|
532
|
+
updatedRules.push(inferred);
|
|
533
|
+
created.push(ent);
|
|
534
|
+
}
|
|
535
|
+
if (created.length > 0) {
|
|
536
|
+
this.database = {
|
|
537
|
+
...this.database,
|
|
538
|
+
updatedAt: new Date().toISOString(),
|
|
539
|
+
totalRules: updatedRules.length,
|
|
540
|
+
rules: updatedRules
|
|
541
|
+
};
|
|
542
|
+
this.rebuildIndices();
|
|
543
|
+
if (persistToDisk) {
|
|
544
|
+
this.saveDatabase();
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
return created;
|
|
548
|
+
}
|
|
549
|
+
addObservations(observations, persistToDisk = true) {
|
|
550
|
+
const updatedRules = [...this.database.rules];
|
|
551
|
+
const results = [];
|
|
552
|
+
let changed = false;
|
|
553
|
+
for (const item of observations) {
|
|
554
|
+
const idx = updatedRules.findIndex((r) => r.title.toLowerCase() === item.entityName.toLowerCase());
|
|
555
|
+
if (idx < 0)
|
|
556
|
+
continue;
|
|
557
|
+
const rule = updatedRules[idx];
|
|
558
|
+
const existingObs = new Set([...rule.invariants, ...rule.actions, ...rule.context]);
|
|
559
|
+
const added = [];
|
|
560
|
+
const newInvariants = [...rule.invariants];
|
|
561
|
+
const newActions = [...rule.actions];
|
|
562
|
+
const newContext = [...rule.context];
|
|
563
|
+
for (const content of item.contents) {
|
|
564
|
+
if (existingObs.has(content))
|
|
565
|
+
continue;
|
|
566
|
+
added.push(content);
|
|
567
|
+
existingObs.add(content);
|
|
568
|
+
changed = true;
|
|
569
|
+
if (content.includes("\uAE08\uC9C0") || content.includes("\uC808\uB300") || content.includes("\uD544\uC218") || content.includes("\uC8FC\uC758")) {
|
|
570
|
+
newInvariants.push(content);
|
|
571
|
+
} else if (/^\d+\.|\uB2E8\uACC4|Step/i.test(content)) {
|
|
572
|
+
newActions.push(content);
|
|
573
|
+
} else {
|
|
574
|
+
newContext.push(content);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
updatedRules[idx] = {
|
|
578
|
+
...rule,
|
|
579
|
+
invariants: newInvariants,
|
|
580
|
+
actions: newActions,
|
|
581
|
+
context: newContext,
|
|
582
|
+
lastVerified: new Date().toISOString().split("T")[0]
|
|
583
|
+
};
|
|
584
|
+
results.push({ entityName: item.entityName, addedObservations: added });
|
|
585
|
+
}
|
|
586
|
+
if (changed) {
|
|
587
|
+
this.database = {
|
|
588
|
+
...this.database,
|
|
589
|
+
updatedAt: new Date().toISOString(),
|
|
590
|
+
rules: updatedRules
|
|
591
|
+
};
|
|
592
|
+
this.rebuildIndices();
|
|
593
|
+
if (persistToDisk) {
|
|
594
|
+
this.saveDatabase();
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
return results;
|
|
598
|
+
}
|
|
599
|
+
createRelations(relations, persistToDisk = true) {
|
|
600
|
+
const updatedRelations = [...this.database.relations];
|
|
601
|
+
const created = [];
|
|
602
|
+
for (const rel of relations) {
|
|
603
|
+
const exists = updatedRelations.some((r) => r.from === rel.from && r.to === rel.to && r.relationType === rel.relationType);
|
|
604
|
+
if (!exists) {
|
|
605
|
+
updatedRelations.push(rel);
|
|
606
|
+
created.push(rel);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
if (created.length > 0) {
|
|
610
|
+
this.database = {
|
|
611
|
+
...this.database,
|
|
612
|
+
updatedAt: new Date().toISOString(),
|
|
613
|
+
totalRelations: updatedRelations.length,
|
|
614
|
+
relations: updatedRelations
|
|
615
|
+
};
|
|
616
|
+
this.rebuildIndices();
|
|
617
|
+
if (persistToDisk) {
|
|
618
|
+
this.saveDatabase();
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
return created;
|
|
622
|
+
}
|
|
623
|
+
deleteEntities(entityNames, persistToDisk = true) {
|
|
624
|
+
const nameSet = new Set(entityNames.map((n) => n.toLowerCase()));
|
|
625
|
+
const filteredRules = this.database.rules.filter((r) => !nameSet.has(r.title.toLowerCase()) && !nameSet.has(r.id.toLowerCase()));
|
|
626
|
+
const filteredRelations = this.database.relations.filter((rel) => !nameSet.has(rel.from.toLowerCase()) && !nameSet.has(rel.to.toLowerCase()));
|
|
627
|
+
this.database = {
|
|
628
|
+
...this.database,
|
|
629
|
+
updatedAt: new Date().toISOString(),
|
|
630
|
+
totalRules: filteredRules.length,
|
|
631
|
+
totalRelations: filteredRelations.length,
|
|
632
|
+
rules: filteredRules,
|
|
633
|
+
relations: filteredRelations
|
|
634
|
+
};
|
|
635
|
+
this.rebuildIndices();
|
|
636
|
+
if (persistToDisk) {
|
|
637
|
+
this.saveDatabase();
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
deleteObservations(deletions, persistToDisk = true) {
|
|
641
|
+
const updatedRules = [...this.database.rules];
|
|
642
|
+
for (const del of deletions) {
|
|
643
|
+
const idx = updatedRules.findIndex((r) => r.title.toLowerCase() === del.entityName.toLowerCase());
|
|
644
|
+
if (idx < 0)
|
|
645
|
+
continue;
|
|
646
|
+
const toDelete = new Set(del.observations);
|
|
647
|
+
const rule = updatedRules[idx];
|
|
648
|
+
updatedRules[idx] = {
|
|
649
|
+
...rule,
|
|
650
|
+
invariants: rule.invariants.filter((x) => !toDelete.has(x)),
|
|
651
|
+
actions: rule.actions.filter((x) => !toDelete.has(x)),
|
|
652
|
+
context: rule.context.filter((x) => !toDelete.has(x))
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
this.database = {
|
|
656
|
+
...this.database,
|
|
657
|
+
updatedAt: new Date().toISOString(),
|
|
658
|
+
rules: updatedRules
|
|
659
|
+
};
|
|
660
|
+
this.rebuildIndices();
|
|
661
|
+
if (persistToDisk) {
|
|
662
|
+
this.saveDatabase();
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
deleteRelations(relations, persistToDisk = true) {
|
|
666
|
+
const filtered = this.database.relations.filter((existing) => !relations.some((del) => del.from === existing.from && del.to === existing.to && del.relationType === existing.relationType));
|
|
667
|
+
this.database = {
|
|
668
|
+
...this.database,
|
|
669
|
+
updatedAt: new Date().toISOString(),
|
|
670
|
+
totalRelations: filtered.length,
|
|
671
|
+
relations: filtered
|
|
672
|
+
};
|
|
673
|
+
this.rebuildIndices();
|
|
674
|
+
if (persistToDisk) {
|
|
675
|
+
this.saveDatabase();
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
static inferRuleFromEntity(ent, today) {
|
|
679
|
+
const nameLower = ent.name.toLowerCase();
|
|
680
|
+
const typeLower = ent.entityType.toLowerCase();
|
|
681
|
+
const obsJoined = ent.observations.join(" ").toLowerCase();
|
|
682
|
+
let domain = "Knowledge_Base";
|
|
683
|
+
const triggers = {};
|
|
684
|
+
const invariants = [];
|
|
685
|
+
const actions = [];
|
|
686
|
+
const context = [];
|
|
687
|
+
const isTrueUserPreference = typeLower === "preference" || typeLower === "user_constraint" || typeLower === "forbidden_rules" || nameLower === "userpreferences" || nameLower === "user_web_navigation_preference" || nameLower === "python_strictly_forbidden" || nameLower === "forbidden_topics_until_mentioned";
|
|
688
|
+
if (isTrueUserPreference) {
|
|
689
|
+
domain = "User_Preferences_And_Rules";
|
|
690
|
+
triggers.alwaysActive = true;
|
|
691
|
+
triggers.keywords = ["\uD658\uACBD\uC124\uC815", "\uADDC\uCE59", "\uD130\uBBF8\uB110", "\uCD9C\uB825", "\uBCF4\uC548", "\uC120\uD638\uB3C4"];
|
|
692
|
+
} else if (nameLower.includes("poe2") || nameLower.includes("noble-babbage") || nameLower.includes("release_workflow") || obsJoined.includes("poe2filter-releases") || obsJoined.includes("noble-babbage")) {
|
|
693
|
+
domain = "Project_POE2_AutoFilter";
|
|
694
|
+
triggers.files = [
|
|
695
|
+
"noble-babbage",
|
|
696
|
+
"poe2filter-releases",
|
|
697
|
+
"updater.json",
|
|
698
|
+
"post_download_button.js"
|
|
699
|
+
];
|
|
700
|
+
triggers.keywords = ["poe2", "autofilter", "\uD544\uD130", "\uC624\uD1A0\uD544\uD130", "updater", "discord", "\uB9B4\uB9AC\uC988", "\uBC30\uD3EC"];
|
|
701
|
+
triggers.commands = ["release", "updater", "build", "sign"];
|
|
702
|
+
} else if (nameLower.includes("vesper") || nameLower.includes("dsp") || nameLower.includes("neurospatial audio") || typeLower.includes("audio_architecture")) {
|
|
703
|
+
domain = "Project_Vesper_DSP";
|
|
704
|
+
triggers.files = ["vesper", "crates/vesper-", "vesper-apo"];
|
|
705
|
+
triggers.keywords = ["vesper", "dsp", "\uC624\uB514\uC624", "audio", "mmcss", "virtuallock", "apo", "eq", "\uC0AC\uC6B4\uB4DC"];
|
|
706
|
+
triggers.commands = ["check", "build", "test"];
|
|
707
|
+
} else if (nameLower.includes("synapse") && !nameLower.includes("biophys")) {
|
|
708
|
+
domain = "Project_Synapse_Kit";
|
|
709
|
+
triggers.files = ["synapse", "synapse.config.ts"];
|
|
710
|
+
triggers.keywords = ["synapse", "synapse-kit", "\uC2DC\uB0C5\uC2A4", "\uC778\uACFC\uC728", "causal", "\uCEF4\uD30C\uC77C\uB7EC", "\uCCB4\uCEE4"];
|
|
711
|
+
triggers.commands = ["check", "build", "package", "sign", "init", "memory"];
|
|
712
|
+
} else if (nameLower.includes("hardware") || nameLower.includes("pcb") || nameLower.includes("jlcpcb") || nameLower.includes("m2_2230") || nameLower.includes("pocket_orange") || typeLower.includes("hardware")) {
|
|
713
|
+
domain = "Hardware_And_PCB";
|
|
714
|
+
triggers.files = ["hardware", "pcb", "kicad"];
|
|
715
|
+
triggers.keywords = ["pcb", "hardware", "\uD558\uB4DC\uC6E8\uC5B4", "\uD68C\uB85C", "jlcpcb", "kicad", "m2"];
|
|
716
|
+
} else if (nameLower.includes("biophys") || nameLower.includes("connectome") || nameLower.includes("drosophila") || nameLower.includes("cortex") || typeLower.includes("whitepaper")) {
|
|
717
|
+
domain = "BioPhys_Research_And_Architecture";
|
|
718
|
+
triggers.files = ["biophys", "whitepaper"];
|
|
719
|
+
triggers.keywords = ["biophys", "connectome", "\uC2DC\uB0C5\uC2A4\uC2E0\uACBD\uB9DD", "\uD000\uD0C0\uC774\uC81C\uC774\uC158", "\uBC31\uC11C", "\uC544\uD0A4\uD14D\uCC98"];
|
|
720
|
+
} else {
|
|
721
|
+
domain = "Knowledge_Base";
|
|
722
|
+
triggers.keywords = [ent.name.toLowerCase().replace(/_/g, " ")];
|
|
723
|
+
}
|
|
724
|
+
for (const obs of ent.observations) {
|
|
725
|
+
if (obs.includes("\uAE08\uC9C0") || obs.includes("\uC808\uB300") || obs.includes("\uD544\uC218") || obs.includes("\uC8FC\uC758") || obs.includes("VirtualLock") || obs.includes("MMCSS")) {
|
|
726
|
+
invariants.push(obs);
|
|
727
|
+
} else if (/^\d+\.|\uB2E8\uACC4|Step/i.test(obs)) {
|
|
728
|
+
actions.push(obs);
|
|
729
|
+
} else {
|
|
730
|
+
context.push(obs);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
const baseSlug = ent.name.trim().toLowerCase().replace(/[^\w\uAC00-\uD7A30-9-]+/g, "-").replace(/^-+|-+$/g, "") || "item";
|
|
734
|
+
return {
|
|
735
|
+
id: `rule-${domain.toLowerCase()}-${baseSlug}`,
|
|
736
|
+
domain,
|
|
737
|
+
title: ent.name,
|
|
738
|
+
entityType: ent.entityType,
|
|
739
|
+
triggers,
|
|
740
|
+
invariants,
|
|
741
|
+
actions,
|
|
742
|
+
context,
|
|
743
|
+
lastVerified: today
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
audit() {
|
|
747
|
+
const issues = [];
|
|
748
|
+
const seenIds = new Set;
|
|
749
|
+
let alwaysActiveCount = 0;
|
|
750
|
+
for (const rule of this.database.rules) {
|
|
751
|
+
if (rule.triggers.alwaysActive) {
|
|
752
|
+
alwaysActiveCount++;
|
|
753
|
+
}
|
|
754
|
+
if (!rule.id || rule.id.trim().length === 0) {
|
|
755
|
+
issues.push(`ID \uB204\uB77D \uADDC\uCE59 \uBC1C\uACAC: ${rule.title}`);
|
|
756
|
+
} else if (seenIds.has(rule.id)) {
|
|
757
|
+
issues.push(`\uC911\uBCF5 ID \uBC1C\uACAC: ${rule.id}`);
|
|
758
|
+
} else {
|
|
759
|
+
seenIds.add(rule.id);
|
|
760
|
+
}
|
|
761
|
+
if (!rule.domain) {
|
|
762
|
+
issues.push(`\uB3C4\uBA54\uC778 \uB204\uB77D \uADDC\uCE59: ${rule.id}`);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
if (alwaysActiveCount > 10) {
|
|
766
|
+
issues.push(`\uC0C1\uC2DC \uD65C\uC131(alwaysActive) \uADDC\uCE59 \uACFC\uB2E4 (${alwaysActiveCount}\uAC1C): \uD1A0\uD070 \uC808\uAC10 \uD6A8\uC728\uC774 \uC800\uD558\uB420 \uC218 \uC788\uC2B5\uB2C8\uB2E4.`);
|
|
767
|
+
}
|
|
768
|
+
return {
|
|
769
|
+
ok: issues.length === 0,
|
|
770
|
+
totalRules: this.database.rules.length,
|
|
771
|
+
totalRelations: this.database.relations.length,
|
|
772
|
+
alwaysActiveCount,
|
|
773
|
+
issues
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
// causal-query.ts
|
|
778
|
+
var {spawn } = globalThis.Bun;
|
|
779
|
+
async function detectModifiedFiles(cwd = process.cwd()) {
|
|
780
|
+
try {
|
|
781
|
+
const gitProc = spawn(["git", "status", "--porcelain", "-uall"], {
|
|
782
|
+
cwd,
|
|
783
|
+
stdout: "pipe",
|
|
784
|
+
stderr: "pipe"
|
|
785
|
+
});
|
|
786
|
+
const output = await new Response(gitProc.stdout).text();
|
|
787
|
+
await gitProc.exited;
|
|
788
|
+
return output.split(`
|
|
789
|
+
`).map((line) => line.slice(3).trim().replaceAll("\\", "/")).filter((line) => line.length > 0 && !line.startsWith(".synapse/") && !line.includes("/.synapse/"));
|
|
790
|
+
} catch {
|
|
791
|
+
return [];
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
async function runCausalQuery(rawOptions, storeCustomPath) {
|
|
795
|
+
const store = new CausalMemoryStore(storeCustomPath);
|
|
796
|
+
let targetFiles = rawOptions.files ? [...rawOptions.files] : [];
|
|
797
|
+
if (rawOptions.autoGit || targetFiles.length === 0) {
|
|
798
|
+
const gitFiles = await detectModifiedFiles(rawOptions.cwd || process.cwd());
|
|
799
|
+
if (gitFiles.length > 0) {
|
|
800
|
+
targetFiles = Array.from(new Set([...targetFiles, ...gitFiles]));
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
const queryOptions = {
|
|
804
|
+
files: targetFiles,
|
|
805
|
+
keywords: rawOptions.keywords,
|
|
806
|
+
command: rawOptions.command,
|
|
807
|
+
domain: rawOptions.domain,
|
|
808
|
+
propagateRelations: rawOptions.propagateRelations
|
|
809
|
+
};
|
|
810
|
+
return store.queryResonance(queryOptions);
|
|
811
|
+
}
|
|
812
|
+
function formatResonanceForAgent(result, compact = false) {
|
|
813
|
+
if (result.matchedCount === 0) {
|
|
814
|
+
return `[Causal-Memory] \uACF5\uBA85 \uADDC\uCE59 0\uAC74 (\uD1A0\uD070 \uC808\uAC10\uB960: ${result.tokenDietSavingsRatio}, \uC18C\uC694: ${result.queryDurationMs}ms)`;
|
|
815
|
+
}
|
|
816
|
+
if (compact) {
|
|
817
|
+
return JSON.stringify({
|
|
818
|
+
ok: true,
|
|
819
|
+
durationMs: result.queryDurationMs,
|
|
820
|
+
matchedCount: result.matchedCount,
|
|
821
|
+
totalDatabaseRules: result.totalDatabaseRules,
|
|
822
|
+
savings: result.tokenDietSavingsRatio,
|
|
823
|
+
rules: result.resonanceRules.map((r) => ({
|
|
824
|
+
id: r.id,
|
|
825
|
+
domain: r.domain,
|
|
826
|
+
title: r.title,
|
|
827
|
+
invariants: r.invariants,
|
|
828
|
+
actions: r.actions
|
|
829
|
+
})),
|
|
830
|
+
relations: result.activeRelations
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
const lines = [
|
|
834
|
+
`### \uD83E\uDDE0 \uC778\uACFC\uC728 \uBA54\uBAA8\uB9AC \uACF5\uBA85 \uC8FC\uC785 (${result.matchedCount}/${result.totalDatabaseRules}\uAC1C \uADDC\uCE59 \uD65C\uC131\uD654, \uC808\uAC10\uB960: ${result.tokenDietSavingsRatio}, ${result.queryDurationMs}ms)`
|
|
835
|
+
];
|
|
836
|
+
for (const rule of result.resonanceRules) {
|
|
837
|
+
lines.push(`
|
|
838
|
+
**[${rule.domain}] ${rule.title}**`);
|
|
839
|
+
if (rule.invariants.length > 0) {
|
|
840
|
+
lines.push(" - **\uBD88\uBCC0 \uADDC\uCE59(Invariants)**:");
|
|
841
|
+
for (const inv of rule.invariants) {
|
|
842
|
+
lines.push(` \u2022 ${inv}`);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
if (rule.actions.length > 0) {
|
|
846
|
+
lines.push(" - **\uC2E4\uD589 \uC808\uCC28(Actions)**:");
|
|
847
|
+
for (const act of rule.actions) {
|
|
848
|
+
lines.push(` \u2022 ${act}`);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
if (rule.context.length > 0) {
|
|
852
|
+
lines.push(" - **\uD575\uC2EC \uB9E5\uB77D(Context)**:");
|
|
853
|
+
for (const ctx of rule.context.slice(0, CAUSAL_MEMORY_CONFIG.MAX_CONTEXT_PREVIEW_LINES)) {
|
|
854
|
+
lines.push(` \u2022 ${ctx}`);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
if (result.activeRelations.length > 0) {
|
|
859
|
+
lines.push(`
|
|
860
|
+
### \uD83D\uDD17 \uD65C\uC131 \uC778\uACFC \uAC04\uC120 (Active Causal Relations)`);
|
|
861
|
+
for (const rel of result.activeRelations) {
|
|
862
|
+
lines.push(` \u2022 \`${rel.from}\` \u2500\u2500[${rel.relationType}]\u2500\u2500\u25B6 \`${rel.to}\``);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
return lines.join(`
|
|
866
|
+
`);
|
|
867
|
+
}
|
|
868
|
+
// causal-server.ts
|
|
869
|
+
import { createInterface } from "readline";
|
|
870
|
+
var CAUSAL_MCP_TOOLS = [
|
|
871
|
+
{
|
|
872
|
+
name: "query_causal_memory",
|
|
873
|
+
description: "\uC791\uC5C5 \uCEE8\uD14D\uC2A4\uD2B8(\uC218\uC815\uB41C \uD30C\uC77C, \uD0A4\uC6CC\uB4DC, \uBA85\uB839\uC5B4, \uB3C4\uBA54\uC778)\uC5D0 \uB9E4\uCE6D\uB418\uB294 \uC778\uACFC\uC728 \uC9C0\uC2DD\uACFC \uBD88\uBCC0 \uADDC\uCE59\uB9CC 0ms \uCD08\uACBD\uB7C9\uC73C\uB85C \uACF5\uBA85 \uCFFC\uB9AC\uD569\uB2C8\uB2E4.",
|
|
874
|
+
inputSchema: {
|
|
875
|
+
type: "object",
|
|
876
|
+
properties: {
|
|
877
|
+
files: {
|
|
878
|
+
type: "array",
|
|
879
|
+
items: { type: "string" },
|
|
880
|
+
description: "\uAC10\uC9C0\uD560 \uB300\uC0C1 \uD30C\uC77C \uACBD\uB85C \uBAA9\uB85D (\uC0DD\uB7B5 \uC2DC Git \uBCC0\uACBD \uD30C\uC77C \uC790\uB3D9 \uAC10\uC9C0)"
|
|
881
|
+
},
|
|
882
|
+
keywords: {
|
|
883
|
+
type: "array",
|
|
884
|
+
items: { type: "string" },
|
|
885
|
+
description: "\uAC80\uC0C9\uD560 \uB3C4\uBA54\uC778/\uC791\uC5C5 \uD0A4\uC6CC\uB4DC \uBAA9\uB85D"
|
|
886
|
+
},
|
|
887
|
+
command: {
|
|
888
|
+
type: "string",
|
|
889
|
+
description: "\uC5F0\uB3D9 \uBA85\uB839\uC5B4 (\uC608: check, build, release, sign)"
|
|
890
|
+
},
|
|
891
|
+
domain: {
|
|
892
|
+
type: "string",
|
|
893
|
+
description: "\uD2B9\uC815 \uB3C4\uBA54\uC778 \uD544\uD130"
|
|
894
|
+
},
|
|
895
|
+
compact: {
|
|
896
|
+
type: "boolean",
|
|
897
|
+
description: "\uCD08\uC18C\uD615 JSON \uD3EC\uB9F7 \uBC18\uD658 \uC5EC\uBD80"
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
},
|
|
902
|
+
{
|
|
903
|
+
name: "assert_causal_rule",
|
|
904
|
+
description: "\uC2E0\uADDC \uC778\uACFC\uC728 \uADDC\uCE59(\uC6D0\uC778 \uD2B8\uB9AC\uAC70 \u2794 \uBD88\uBCC0 \uACC4\uC57D \u2794 \uC2E4\uD589 \uC9C0\uCE68)\uC744 \uBA54\uBAA8\uB9AC\uC5D0 \uB4F1\uB85D\uD558\uAC70\uB098 \uAC31\uC2E0\uD569\uB2C8\uB2E4.",
|
|
905
|
+
inputSchema: {
|
|
906
|
+
type: "object",
|
|
907
|
+
required: ["id", "domain", "title", "triggers"],
|
|
908
|
+
properties: {
|
|
909
|
+
id: { type: "string" },
|
|
910
|
+
domain: { type: "string" },
|
|
911
|
+
title: { type: "string" },
|
|
912
|
+
triggers: { type: "object" },
|
|
913
|
+
invariants: { type: "array", items: { type: "string" } },
|
|
914
|
+
actions: { type: "array", items: { type: "string" } },
|
|
915
|
+
context: { type: "array", items: { type: "string" } }
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
},
|
|
919
|
+
{
|
|
920
|
+
name: "audit_causal_memory",
|
|
921
|
+
description: "\uC778\uACFC\uC728 \uBA54\uBAA8\uB9AC \uB370\uC774\uD130\uBCA0\uC774\uC2A4\uC758 \uBB34\uACB0\uC131, \uB3C4\uBA54\uC778 \uBD84\uD3EC, \uD1A0\uD070 \uC808\uAC10 \uC0C1\uD0DC\uB97C \uAC10\uC0AC(Audit)\uD569\uB2C8\uB2E4.",
|
|
922
|
+
inputSchema: {
|
|
923
|
+
type: "object",
|
|
924
|
+
properties: {}
|
|
925
|
+
}
|
|
926
|
+
},
|
|
927
|
+
{
|
|
928
|
+
name: "read_graph",
|
|
929
|
+
description: "Read the active causal resonance knowledge graph (automatically filters out unrelated noise for 90%+ token savings)",
|
|
930
|
+
inputSchema: {
|
|
931
|
+
type: "object",
|
|
932
|
+
properties: {}
|
|
933
|
+
}
|
|
934
|
+
},
|
|
935
|
+
{
|
|
936
|
+
name: "search_nodes",
|
|
937
|
+
description: "Search for causal rules and nodes in the knowledge graph based on a query and traverse causal relations",
|
|
938
|
+
inputSchema: {
|
|
939
|
+
type: "object",
|
|
940
|
+
required: ["query"],
|
|
941
|
+
properties: {
|
|
942
|
+
query: {
|
|
943
|
+
type: "string",
|
|
944
|
+
description: "The search query to match against rule titles, domains, triggers, and invariants"
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
},
|
|
949
|
+
{
|
|
950
|
+
name: "open_nodes",
|
|
951
|
+
description: "Open specific causal rule nodes in the knowledge graph by their names or IDs",
|
|
952
|
+
inputSchema: {
|
|
953
|
+
type: "object",
|
|
954
|
+
required: ["names"],
|
|
955
|
+
properties: {
|
|
956
|
+
names: {
|
|
957
|
+
type: "array",
|
|
958
|
+
items: { type: "string" },
|
|
959
|
+
description: "An array of entity names or rule IDs to retrieve"
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
},
|
|
964
|
+
{
|
|
965
|
+
name: "create_entities",
|
|
966
|
+
description: "Create multiple new entities (automatically converted into structured Causal Rules)",
|
|
967
|
+
inputSchema: {
|
|
968
|
+
type: "object",
|
|
969
|
+
required: ["entities"],
|
|
970
|
+
properties: {
|
|
971
|
+
entities: {
|
|
972
|
+
type: "array",
|
|
973
|
+
items: {
|
|
974
|
+
type: "object",
|
|
975
|
+
required: ["name", "entityType", "observations"],
|
|
976
|
+
properties: {
|
|
977
|
+
name: { type: "string" },
|
|
978
|
+
entityType: { type: "string" },
|
|
979
|
+
observations: { type: "array", items: { type: "string" } }
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
},
|
|
986
|
+
{
|
|
987
|
+
name: "create_relations",
|
|
988
|
+
description: "Create multiple new causal relations between nodes in the knowledge graph",
|
|
989
|
+
inputSchema: {
|
|
990
|
+
type: "object",
|
|
991
|
+
required: ["relations"],
|
|
992
|
+
properties: {
|
|
993
|
+
relations: {
|
|
994
|
+
type: "array",
|
|
995
|
+
items: {
|
|
996
|
+
type: "object",
|
|
997
|
+
required: ["from", "to", "relationType"],
|
|
998
|
+
properties: {
|
|
999
|
+
from: { type: "string" },
|
|
1000
|
+
to: { type: "string" },
|
|
1001
|
+
relationType: { type: "string" }
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
},
|
|
1008
|
+
{
|
|
1009
|
+
name: "add_observations",
|
|
1010
|
+
description: "Add new invariants, actions, or context observations to existing causal rules",
|
|
1011
|
+
inputSchema: {
|
|
1012
|
+
type: "object",
|
|
1013
|
+
required: ["observations"],
|
|
1014
|
+
properties: {
|
|
1015
|
+
observations: {
|
|
1016
|
+
type: "array",
|
|
1017
|
+
items: {
|
|
1018
|
+
type: "object",
|
|
1019
|
+
required: ["entityName", "contents"],
|
|
1020
|
+
properties: {
|
|
1021
|
+
entityName: { type: "string" },
|
|
1022
|
+
contents: { type: "array", items: { type: "string" } }
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
},
|
|
1029
|
+
{
|
|
1030
|
+
name: "delete_entities",
|
|
1031
|
+
description: "Delete multiple entities/rules and their associated causal relations",
|
|
1032
|
+
inputSchema: {
|
|
1033
|
+
type: "object",
|
|
1034
|
+
required: ["entityNames"],
|
|
1035
|
+
properties: {
|
|
1036
|
+
entityNames: {
|
|
1037
|
+
type: "array",
|
|
1038
|
+
items: { type: "string" }
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
},
|
|
1043
|
+
{
|
|
1044
|
+
name: "delete_observations",
|
|
1045
|
+
description: "Delete specific observations from entities/rules in the causal memory",
|
|
1046
|
+
inputSchema: {
|
|
1047
|
+
type: "object",
|
|
1048
|
+
required: ["deletions"],
|
|
1049
|
+
properties: {
|
|
1050
|
+
deletions: {
|
|
1051
|
+
type: "array",
|
|
1052
|
+
items: {
|
|
1053
|
+
type: "object",
|
|
1054
|
+
required: ["entityName", "observations"],
|
|
1055
|
+
properties: {
|
|
1056
|
+
entityName: { type: "string" },
|
|
1057
|
+
observations: { type: "array", items: { type: "string" } }
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
},
|
|
1064
|
+
{
|
|
1065
|
+
name: "delete_relations",
|
|
1066
|
+
description: "Delete multiple causal relations from the knowledge graph",
|
|
1067
|
+
inputSchema: {
|
|
1068
|
+
type: "object",
|
|
1069
|
+
required: ["relations"],
|
|
1070
|
+
properties: {
|
|
1071
|
+
relations: {
|
|
1072
|
+
type: "array",
|
|
1073
|
+
items: {
|
|
1074
|
+
type: "object",
|
|
1075
|
+
required: ["from", "to", "relationType"],
|
|
1076
|
+
properties: {
|
|
1077
|
+
from: { type: "string" },
|
|
1078
|
+
to: { type: "string" },
|
|
1079
|
+
relationType: { type: "string" }
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
];
|
|
1087
|
+
async function handleMcpJsonRpcRequest(rpc, store, dbPath) {
|
|
1088
|
+
const { id, method, params } = rpc;
|
|
1089
|
+
if (method.startsWith("notifications/")) {
|
|
1090
|
+
return null;
|
|
1091
|
+
}
|
|
1092
|
+
if (method === "initialize") {
|
|
1093
|
+
return {
|
|
1094
|
+
jsonrpc: "2.0",
|
|
1095
|
+
id: id ?? null,
|
|
1096
|
+
result: {
|
|
1097
|
+
protocolVersion: params?.protocolVersion || CAUSAL_MEMORY_CONFIG.MCP_PROTOCOL_VERSION,
|
|
1098
|
+
capabilities: {
|
|
1099
|
+
tools: {}
|
|
1100
|
+
},
|
|
1101
|
+
serverInfo: {
|
|
1102
|
+
name: CAUSAL_MEMORY_CONFIG.MCP_SERVER_NAME,
|
|
1103
|
+
version: CAUSAL_MEMORY_CONFIG.SCHEMA_VERSION
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
if (method === "ping") {
|
|
1109
|
+
return {
|
|
1110
|
+
jsonrpc: "2.0",
|
|
1111
|
+
id: id ?? null,
|
|
1112
|
+
result: {}
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
if (method === "tools/list") {
|
|
1116
|
+
return {
|
|
1117
|
+
jsonrpc: "2.0",
|
|
1118
|
+
id: id ?? null,
|
|
1119
|
+
result: {
|
|
1120
|
+
tools: CAUSAL_MCP_TOOLS
|
|
1121
|
+
}
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
if (method === "tools/call") {
|
|
1125
|
+
store.reloadIfNeeded();
|
|
1126
|
+
const toolName = params?.name;
|
|
1127
|
+
const args = params?.arguments || {};
|
|
1128
|
+
try {
|
|
1129
|
+
let payloadText = "";
|
|
1130
|
+
switch (toolName) {
|
|
1131
|
+
case "query_causal_memory": {
|
|
1132
|
+
let files = Array.isArray(args.files) ? args.files : [];
|
|
1133
|
+
if (files.length === 0 && args.autoGit !== false) {
|
|
1134
|
+
files = await detectModifiedFiles();
|
|
1135
|
+
}
|
|
1136
|
+
const res = store.queryResonance({
|
|
1137
|
+
files,
|
|
1138
|
+
keywords: args.keywords,
|
|
1139
|
+
command: args.command,
|
|
1140
|
+
domain: args.domain,
|
|
1141
|
+
propagateRelations: args.propagateRelations,
|
|
1142
|
+
maxHops: args.maxHops
|
|
1143
|
+
});
|
|
1144
|
+
payloadText = formatResonanceForAgent(res, Boolean(args.compact));
|
|
1145
|
+
break;
|
|
1146
|
+
}
|
|
1147
|
+
case "assert_causal_rule": {
|
|
1148
|
+
store.assertRule(args);
|
|
1149
|
+
payloadText = JSON.stringify({
|
|
1150
|
+
ok: true,
|
|
1151
|
+
message: `\uC778\uACFC\uC728 \uADDC\uCE59 \uB4F1\uB85D/\uAC31\uC2E0 \uC644\uB8CC: ${args.id}`,
|
|
1152
|
+
id: args.id
|
|
1153
|
+
});
|
|
1154
|
+
break;
|
|
1155
|
+
}
|
|
1156
|
+
case "audit_causal_memory": {
|
|
1157
|
+
const audit = store.audit();
|
|
1158
|
+
const status = store.getStatus();
|
|
1159
|
+
payloadText = JSON.stringify({ ...audit, status }, null, 2);
|
|
1160
|
+
break;
|
|
1161
|
+
}
|
|
1162
|
+
case "read_graph": {
|
|
1163
|
+
const gitFiles = await detectModifiedFiles();
|
|
1164
|
+
const smartGraph = store.readSmartGraph(gitFiles);
|
|
1165
|
+
payloadText = JSON.stringify(smartGraph, null, 2);
|
|
1166
|
+
break;
|
|
1167
|
+
}
|
|
1168
|
+
case "search_nodes": {
|
|
1169
|
+
const searchResult = store.searchNodes(String(args.query || ""));
|
|
1170
|
+
payloadText = JSON.stringify(searchResult, null, 2);
|
|
1171
|
+
break;
|
|
1172
|
+
}
|
|
1173
|
+
case "open_nodes": {
|
|
1174
|
+
const names = Array.isArray(args.names) ? args.names : [];
|
|
1175
|
+
const openResult = store.openNodes(names);
|
|
1176
|
+
payloadText = JSON.stringify(openResult, null, 2);
|
|
1177
|
+
break;
|
|
1178
|
+
}
|
|
1179
|
+
case "create_entities": {
|
|
1180
|
+
const entities = Array.isArray(args.entities) ? args.entities : [];
|
|
1181
|
+
const created = store.createEntities(entities);
|
|
1182
|
+
payloadText = JSON.stringify(created, null, 2);
|
|
1183
|
+
break;
|
|
1184
|
+
}
|
|
1185
|
+
case "create_relations": {
|
|
1186
|
+
const relations = Array.isArray(args.relations) ? args.relations : [];
|
|
1187
|
+
const created = store.createRelations(relations);
|
|
1188
|
+
payloadText = JSON.stringify(created, null, 2);
|
|
1189
|
+
break;
|
|
1190
|
+
}
|
|
1191
|
+
case "add_observations": {
|
|
1192
|
+
const observations = Array.isArray(args.observations) ? args.observations : [];
|
|
1193
|
+
const added = store.addObservations(observations);
|
|
1194
|
+
payloadText = JSON.stringify(added, null, 2);
|
|
1195
|
+
break;
|
|
1196
|
+
}
|
|
1197
|
+
case "delete_entities": {
|
|
1198
|
+
const entityNames = Array.isArray(args.entityNames) ? args.entityNames : [];
|
|
1199
|
+
store.deleteEntities(entityNames);
|
|
1200
|
+
payloadText = "Entities deleted successfully";
|
|
1201
|
+
break;
|
|
1202
|
+
}
|
|
1203
|
+
case "delete_observations": {
|
|
1204
|
+
const deletions = Array.isArray(args.deletions) ? args.deletions : [];
|
|
1205
|
+
store.deleteObservations(deletions);
|
|
1206
|
+
payloadText = "Observations deleted successfully";
|
|
1207
|
+
break;
|
|
1208
|
+
}
|
|
1209
|
+
case "delete_relations": {
|
|
1210
|
+
const relations = Array.isArray(args.relations) ? args.relations : [];
|
|
1211
|
+
store.deleteRelations(relations);
|
|
1212
|
+
payloadText = "Relations deleted successfully";
|
|
1213
|
+
break;
|
|
1214
|
+
}
|
|
1215
|
+
default:
|
|
1216
|
+
return {
|
|
1217
|
+
jsonrpc: "2.0",
|
|
1218
|
+
id: id ?? null,
|
|
1219
|
+
error: {
|
|
1220
|
+
code: -32601,
|
|
1221
|
+
message: `\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uC778\uACFC\uC728 \uBA54\uBAA8\uB9AC \uB3C4\uAD6C\uC785\uB2C8\uB2E4: ${toolName}`
|
|
1222
|
+
}
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
1225
|
+
return {
|
|
1226
|
+
jsonrpc: "2.0",
|
|
1227
|
+
id: id ?? null,
|
|
1228
|
+
result: {
|
|
1229
|
+
content: [{ type: "text", text: payloadText }]
|
|
1230
|
+
}
|
|
1231
|
+
};
|
|
1232
|
+
} catch (error) {
|
|
1233
|
+
return {
|
|
1234
|
+
jsonrpc: "2.0",
|
|
1235
|
+
id: id ?? null,
|
|
1236
|
+
result: {
|
|
1237
|
+
content: [
|
|
1238
|
+
{
|
|
1239
|
+
type: "text",
|
|
1240
|
+
text: `\uC778\uACFC\uC728 \uBA54\uBAA8\uB9AC \uB3C4\uAD6C \uC2E4\uD589 \uC624\uB958: ${error instanceof Error ? error.message : String(error)}`
|
|
1241
|
+
}
|
|
1242
|
+
],
|
|
1243
|
+
isError: true
|
|
1244
|
+
}
|
|
1245
|
+
};
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
return {
|
|
1249
|
+
jsonrpc: "2.0",
|
|
1250
|
+
id: id ?? null,
|
|
1251
|
+
error: { code: -32601, message: `Method not found: ${method}` }
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1254
|
+
function startCausalServer(options = {}) {
|
|
1255
|
+
const port = options.port || CAUSAL_MEMORY_CONFIG.DEFAULT_PORT;
|
|
1256
|
+
const host = options.host || CAUSAL_MEMORY_CONFIG.DEFAULT_HOST;
|
|
1257
|
+
const store = new CausalMemoryStore(options.dbPath);
|
|
1258
|
+
const server = Bun.serve({
|
|
1259
|
+
port,
|
|
1260
|
+
hostname: host,
|
|
1261
|
+
async fetch(req) {
|
|
1262
|
+
const url = new URL(req.url);
|
|
1263
|
+
const headers = {
|
|
1264
|
+
"Content-Type": "application/json",
|
|
1265
|
+
"Access-Control-Allow-Origin": "*",
|
|
1266
|
+
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
1267
|
+
"Access-Control-Allow-Headers": "Content-Type"
|
|
1268
|
+
};
|
|
1269
|
+
if (req.method === "OPTIONS") {
|
|
1270
|
+
return new Response(null, { headers });
|
|
1271
|
+
}
|
|
1272
|
+
try {
|
|
1273
|
+
if (url.pathname === "/health" || url.pathname === "/status") {
|
|
1274
|
+
store.reloadIfNeeded();
|
|
1275
|
+
return new Response(JSON.stringify({ ok: true, status: store.getStatus() }), { headers });
|
|
1276
|
+
}
|
|
1277
|
+
if (url.pathname === "/query" && req.method === "GET") {
|
|
1278
|
+
store.reloadIfNeeded();
|
|
1279
|
+
const files = url.searchParams.getAll("files");
|
|
1280
|
+
const keywords = url.searchParams.getAll("keywords");
|
|
1281
|
+
const command = url.searchParams.get("command") || undefined;
|
|
1282
|
+
const domain = url.searchParams.get("domain") || undefined;
|
|
1283
|
+
const result = store.queryResonance({ files, keywords, command, domain });
|
|
1284
|
+
return new Response(JSON.stringify(result), { headers });
|
|
1285
|
+
}
|
|
1286
|
+
if (url.pathname === "/query" && req.method === "POST") {
|
|
1287
|
+
store.reloadIfNeeded();
|
|
1288
|
+
const body = await req.json();
|
|
1289
|
+
let files = Array.isArray(body.files) ? body.files : [];
|
|
1290
|
+
if (body.autoGit === true && files.length === 0) {
|
|
1291
|
+
files = await detectModifiedFiles(body.cwd);
|
|
1292
|
+
}
|
|
1293
|
+
const result = store.queryResonance({
|
|
1294
|
+
files,
|
|
1295
|
+
keywords: body.keywords,
|
|
1296
|
+
command: body.command,
|
|
1297
|
+
domain: body.domain,
|
|
1298
|
+
propagateRelations: body.propagateRelations,
|
|
1299
|
+
maxHops: body.maxHops
|
|
1300
|
+
});
|
|
1301
|
+
const format = url.searchParams.get("format") || body.format;
|
|
1302
|
+
if (format === "markdown" || format === "md") {
|
|
1303
|
+
return new Response(formatResonanceForAgent(result, false), {
|
|
1304
|
+
headers: { ...headers, "Content-Type": "text/markdown; charset=utf-8" }
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
1307
|
+
if (format === "compact") {
|
|
1308
|
+
return new Response(formatResonanceForAgent(result, true), { headers });
|
|
1309
|
+
}
|
|
1310
|
+
return new Response(JSON.stringify(result), { headers });
|
|
1311
|
+
}
|
|
1312
|
+
if (url.pathname === "/assert" && req.method === "POST") {
|
|
1313
|
+
store.reloadIfNeeded();
|
|
1314
|
+
const body = await req.json();
|
|
1315
|
+
store.assertRule(body);
|
|
1316
|
+
return new Response(JSON.stringify({ ok: true, id: body.id }), { headers });
|
|
1317
|
+
}
|
|
1318
|
+
if (url.pathname === "/audit") {
|
|
1319
|
+
store.reloadIfNeeded();
|
|
1320
|
+
const auditResult = store.audit();
|
|
1321
|
+
return new Response(JSON.stringify(auditResult), { headers });
|
|
1322
|
+
}
|
|
1323
|
+
if (url.pathname === "/mcp" && req.method === "POST") {
|
|
1324
|
+
const rpc = await req.json();
|
|
1325
|
+
const responseObj = await handleMcpJsonRpcRequest(rpc, store, options.dbPath);
|
|
1326
|
+
if (!responseObj) {
|
|
1327
|
+
return new Response(null, { status: 204, headers });
|
|
1328
|
+
}
|
|
1329
|
+
return new Response(JSON.stringify(responseObj), { headers });
|
|
1330
|
+
}
|
|
1331
|
+
return new Response(JSON.stringify({ error: "Not Found" }), { status: 404, headers });
|
|
1332
|
+
} catch (err) {
|
|
1333
|
+
return new Response(JSON.stringify({ error: err instanceof Error ? err.message : String(err) }), { status: 500, headers });
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
});
|
|
1337
|
+
return server;
|
|
1338
|
+
}
|
|
1339
|
+
async function startCausalMcpStdioServer(dbPath) {
|
|
1340
|
+
const store = new CausalMemoryStore(dbPath);
|
|
1341
|
+
const daemonUrl = `http://${CAUSAL_MEMORY_CONFIG.DEFAULT_HOST}:${CAUSAL_MEMORY_CONFIG.DEFAULT_PORT}/mcp`;
|
|
1342
|
+
const rl = createInterface({
|
|
1343
|
+
input: process.stdin,
|
|
1344
|
+
terminal: false
|
|
1345
|
+
});
|
|
1346
|
+
for await (const line of rl) {
|
|
1347
|
+
const trimmed = line.trim();
|
|
1348
|
+
if (!trimmed)
|
|
1349
|
+
continue;
|
|
1350
|
+
try {
|
|
1351
|
+
const rpc = JSON.parse(trimmed);
|
|
1352
|
+
let responseObj = null;
|
|
1353
|
+
if (rpc.method === "tools/call") {
|
|
1354
|
+
try {
|
|
1355
|
+
const daemonRes = await fetch(daemonUrl, {
|
|
1356
|
+
method: "POST",
|
|
1357
|
+
headers: { "Content-Type": "application/json" },
|
|
1358
|
+
body: trimmed,
|
|
1359
|
+
signal: AbortSignal.timeout(150)
|
|
1360
|
+
});
|
|
1361
|
+
if (daemonRes.ok) {
|
|
1362
|
+
responseObj = await daemonRes.json();
|
|
1363
|
+
}
|
|
1364
|
+
} catch {}
|
|
1365
|
+
}
|
|
1366
|
+
if (!responseObj) {
|
|
1367
|
+
responseObj = await handleMcpJsonRpcRequest(rpc, store, dbPath);
|
|
1368
|
+
}
|
|
1369
|
+
if (responseObj) {
|
|
1370
|
+
process.stdout.write(JSON.stringify(responseObj) + `
|
|
1371
|
+
`);
|
|
1372
|
+
}
|
|
1373
|
+
} catch (error) {
|
|
1374
|
+
const errResp = {
|
|
1375
|
+
jsonrpc: "2.0",
|
|
1376
|
+
id: null,
|
|
1377
|
+
error: {
|
|
1378
|
+
code: -32700,
|
|
1379
|
+
message: `JSON Parse error: ${error instanceof Error ? error.message : String(error)}`
|
|
1380
|
+
}
|
|
1381
|
+
};
|
|
1382
|
+
process.stdout.write(JSON.stringify(errResp) + `
|
|
1383
|
+
`);
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
// causal-migrator.ts
|
|
1388
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
1389
|
+
import { dirname as dirname2 } from "path";
|
|
1390
|
+
function migrateLegacyMemoryGraph(sourceBackupPath = CAUSAL_MEMORY_CONFIG.LEGACY_BACKUP_PATH, targetGlobalPath = CAUSAL_MEMORY_CONFIG.GLOBAL_DB_PATH, targetLocalPath = CAUSAL_MEMORY_CONFIG.LOCAL_DB_PATH) {
|
|
1391
|
+
if (!existsSync2(sourceBackupPath)) {
|
|
1392
|
+
throw new Error(`\uAD6C\uD615 \uBA54\uBAA8\uB9AC \uBC31\uC5C5 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${sourceBackupPath}`);
|
|
1393
|
+
}
|
|
1394
|
+
const raw = readFileSync2(sourceBackupPath, "utf-8");
|
|
1395
|
+
const legacy = JSON.parse(raw);
|
|
1396
|
+
const entities = legacy.entities || [];
|
|
1397
|
+
const legacyRelations = legacy.relations || [];
|
|
1398
|
+
const today = new Date().toISOString().split("T")[0];
|
|
1399
|
+
const seenIds = new Map;
|
|
1400
|
+
const rules = [];
|
|
1401
|
+
for (const ent of entities) {
|
|
1402
|
+
const inferred = CausalMemoryStore.inferRuleFromEntity(ent, today);
|
|
1403
|
+
const baseId = inferred.id;
|
|
1404
|
+
const count = seenIds.get(baseId) || 0;
|
|
1405
|
+
seenIds.set(baseId, count + 1);
|
|
1406
|
+
const uniqueId = count === 0 ? baseId : `${baseId}-${count + 1}`;
|
|
1407
|
+
rules.push({
|
|
1408
|
+
...inferred,
|
|
1409
|
+
id: uniqueId
|
|
1410
|
+
});
|
|
1411
|
+
}
|
|
1412
|
+
const hasVesperCore = rules.some((r) => r.id === "rule-project_vesper_dsp-vesper-dsp-master-invariants");
|
|
1413
|
+
if (!hasVesperCore) {
|
|
1414
|
+
rules.push({
|
|
1415
|
+
id: "rule-project_vesper_dsp-vesper-dsp-master-invariants",
|
|
1416
|
+
domain: "Project_Vesper_DSP",
|
|
1417
|
+
title: "Vesper_DSP_Master_Invariants",
|
|
1418
|
+
entityType: "Architecture_Invariant",
|
|
1419
|
+
triggers: {
|
|
1420
|
+
files: ["D:/Vesper", "crates/vesper-", "vesper-apo", "dsp-settings.json"],
|
|
1421
|
+
keywords: ["vesper", "dsp", "mmcss", "virtuallock", "\uC624\uB514\uC624", "\uC0AC\uC6B4\uB4DC", "\uB04A\uAE40", "apo"],
|
|
1422
|
+
commands: ["check", "build", "package", "sign"]
|
|
1423
|
+
},
|
|
1424
|
+
invariants: [
|
|
1425
|
+
"\uC624\uB514\uC624 \uBC84\uD37C\uB294 \uBC18\uB4DC\uC2DC Win32 VirtualLock\uC73C\uB85C \uBB3C\uB9AC RAM\uC5D0 \uACE0\uC815\uD558\uC5EC \uB514\uC2A4\uD06C \uC2A4\uC651 \uC544\uC6C3(\uD558\uB4DC \uD398\uC774\uC9C0 \uD3F4\uD2B8)\uC744 \uC6D0\uCC9C \uCC28\uB2E8\uD560 \uAC83",
|
|
1426
|
+
"\uC624\uB514\uC624 \uCC98\uB9AC \uC2A4\uB808\uB4DC\uB294 Windows MMCSS(Pro Audio) \uC2E4\uC2DC\uAC04 \uC2A4\uCF00\uC904\uB7EC\uC5D0 \uD544\uC218 \uB4F1\uB85D\uD560 \uAC83",
|
|
1427
|
+
"\uC124\uC815 \uC800\uC7A5\uC740 \uBE0C\uB77C\uC6B0\uC800 localStorage \uB2E8\uB3C5 \uC758\uC874\uC744 \uAE08\uC9C0\uD558\uACE0 Crash-Safe \uC6D0\uC790\uC801(Atomic) \uD30C\uC77C \uC800\uC7A5(dsp-settings.json)\uC744 \uC0AC\uC6A9\uD560 \uAC83",
|
|
1428
|
+
"Windows \uC167\uB2E4\uC6B4 \uC2DC WM_QUERYENDSESSION / SM_SHUTTINGDOWN \uAC10\uC9C0 \uD6C4 \uC815\uC0C1 \uC885\uB8CC\uB97C \uBCF4\uC7A5\uD560 \uAC83"
|
|
1429
|
+
],
|
|
1430
|
+
actions: [
|
|
1431
|
+
"1. Cargo Workspace 5\uB300 \uB9C8\uC774\uD06C\uB85C \uD06C\uB808\uC774\uD2B8(vesper-config, vesper-ipc, vesper-audio-core, vesper-system, vesper-tauri) \uACBD\uACC4 \uC900\uC218",
|
|
1432
|
+
"2. \uC218\uC815\uB41C \uD06C\uB808\uC774\uD2B8 \uD2B8\uB799\uB9CC \uC778\uACFC\uC728 \uC99D\uBD84 \uBE4C\uB4DC \uC218\uD589"
|
|
1433
|
+
],
|
|
1434
|
+
context: [
|
|
1435
|
+
"\uC2E4\uC99D \uD0C0\uAE43 \uACBD\uB85C: D:\\Vesper\\dsp-boot-fix\\dsp",
|
|
1436
|
+
"APO DLL \uBC0F Taskbar \uB124\uC774\uD2F0\uBE0C \uBC14\uC774\uB108\uB9AC \uC544\uD0A4\uD14D\uCC98 \uBB34\uACB0\uC131(x64 PE) \uAC80\uC99D \uD544\uC218"
|
|
1437
|
+
],
|
|
1438
|
+
lastVerified: today
|
|
1439
|
+
});
|
|
1440
|
+
}
|
|
1441
|
+
const hasSynapseCore = rules.some((r) => r.id === "rule-project_synapse_kit-synapse-kit-core-invariants");
|
|
1442
|
+
if (!hasSynapseCore) {
|
|
1443
|
+
rules.push({
|
|
1444
|
+
id: "rule-project_synapse_kit-synapse-kit-core-invariants",
|
|
1445
|
+
domain: "Project_Synapse_Kit",
|
|
1446
|
+
title: "Synapse_Kit_Core_Invariants",
|
|
1447
|
+
entityType: "Framework_Invariant",
|
|
1448
|
+
triggers: {
|
|
1449
|
+
files: ["D:/synapse", "D:/synapse-site", "synapse.config.ts"],
|
|
1450
|
+
keywords: ["synapse", "synapse-kit", "\uC2DC\uB0C5\uC2A4", "\uC778\uACFC\uC728", "causal", "\uCEF4\uD30C\uC77C\uB7EC", "\uCCB4\uCEE4"],
|
|
1451
|
+
commands: ["check", "build", "package", "sign", "init", "memory"]
|
|
1452
|
+
},
|
|
1453
|
+
invariants: [
|
|
1454
|
+
"Git \uC0C1\uD0DC \uC2A4\uCE94 \uC2DC \uBC18\uB4DC\uC2DC git status --porcelain -uall \uD50C\uB798\uADF8\uB97C \uC0AC\uC6A9\uD558\uC5EC \uBBF8\uCD94\uC801 \uD558\uC704 \uB514\uB809\uD130\uB9AC \uD30C\uC77C \uB204\uB77D\uC744 \uBC29\uC9C0\uD560 \uAC83",
|
|
1455
|
+
"AI \uC5D0\uC774\uC804\uD2B8 \uC5F0\uB3D9 \uC2DC --compact \uBAA8\uB4DC\uB97C \uD1B5\uD574 \uC131\uACF5 \uC2DC 11\uD1A0\uD070 \uB2E8\uC77C \uB77C\uC778 JSON\uC744 \uBC18\uD658\uD558\uC5EC \uD1A0\uD070\uC744 99.8% \uC808\uAC10\uD560 \uAC83",
|
|
1456
|
+
"\uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800 \uBC0F \uB7F0\uD0C0\uC784\uC740 npm/npx\uB97C \uC808\uB300 \uC0AC\uC6A9\uD558\uC9C0 \uC54A\uACE0 \uBC18\uB4DC\uC2DC bun\uC744 \uC0AC\uC6A9\uD560 \uAC83"
|
|
1457
|
+
],
|
|
1458
|
+
actions: [
|
|
1459
|
+
"1. \uCF54\uB4DC \uBCC0\uACBD \uD6C4 bun D:/synapse/dist/cli.js check --compact\uB85C \uBB34\uACB0\uC131 \uAC80\uC99D",
|
|
1460
|
+
"2. \uBA54\uBAA8\uB9AC \uC870\uD68C \uC2DC synapse memory query\uB85C \uACF5\uBA85 \uADDC\uCE59\uB9CC \uCD5C\uC18C \uD1A0\uD070 \uC8FC\uC785"
|
|
1461
|
+
],
|
|
1462
|
+
context: [
|
|
1463
|
+
"\uC5D4\uC9C4 \uCF54\uC5B4 \uACBD\uB85C: D:\\synapse",
|
|
1464
|
+
"\uACF5\uC2DD \uBB38\uC11C \uBC0F \uC704\uD0A4 \uACBD\uB85C: D:\\synapse-site (KO/EN \uB2E4\uAD6D\uC5B4 \uB85C\uCF00\uC77C \uC790\uB3D9 \uBD84\uAE30)"
|
|
1465
|
+
],
|
|
1466
|
+
lastVerified: today
|
|
1467
|
+
});
|
|
1468
|
+
}
|
|
1469
|
+
const relations = [...legacyRelations];
|
|
1470
|
+
const coreRelations = [
|
|
1471
|
+
{
|
|
1472
|
+
from: "POE2_AutoFilter_Project",
|
|
1473
|
+
to: "POE2_AutoFilter_ReleaseProcess",
|
|
1474
|
+
relationType: "governed_by_workflow"
|
|
1475
|
+
},
|
|
1476
|
+
{
|
|
1477
|
+
from: "POE2_AutoFilter_ReleaseProcess",
|
|
1478
|
+
to: "POE2_AutoFilter_Updater",
|
|
1479
|
+
relationType: "triggers_update_of"
|
|
1480
|
+
},
|
|
1481
|
+
{
|
|
1482
|
+
from: "POE2_AutoFilter_ReleaseProcess",
|
|
1483
|
+
to: "POE2_AutoFilter_KnownBugs",
|
|
1484
|
+
relationType: "verifies_and_notifies"
|
|
1485
|
+
},
|
|
1486
|
+
{
|
|
1487
|
+
from: "Synapse_Kit_Core_Invariants",
|
|
1488
|
+
to: "Vesper_DSP_Master_Invariants",
|
|
1489
|
+
relationType: "verifies_and_builds"
|
|
1490
|
+
}
|
|
1491
|
+
];
|
|
1492
|
+
for (const cr of coreRelations) {
|
|
1493
|
+
const exists = relations.some((r) => r.from === cr.from && r.to === cr.to && r.relationType === cr.relationType);
|
|
1494
|
+
if (!exists) {
|
|
1495
|
+
relations.push(cr);
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
const db = {
|
|
1499
|
+
version: CAUSAL_MEMORY_CONFIG.SCHEMA_VERSION,
|
|
1500
|
+
updatedAt: new Date().toISOString(),
|
|
1501
|
+
totalRules: rules.length,
|
|
1502
|
+
totalRelations: relations.length,
|
|
1503
|
+
rules,
|
|
1504
|
+
relations
|
|
1505
|
+
};
|
|
1506
|
+
const serialized = JSON.stringify(db, null, 2);
|
|
1507
|
+
for (const outPath of [targetGlobalPath, targetLocalPath]) {
|
|
1508
|
+
const dir = dirname2(outPath);
|
|
1509
|
+
if (!existsSync2(dir)) {
|
|
1510
|
+
mkdirSync2(dir, { recursive: true });
|
|
1511
|
+
}
|
|
1512
|
+
writeFileSync2(outPath, serialized, "utf-8");
|
|
1513
|
+
}
|
|
1514
|
+
const alwaysActiveCount = rules.filter((r) => r.triggers.alwaysActive).length;
|
|
1515
|
+
const domains = Array.from(new Set(rules.map((r) => r.domain)));
|
|
1516
|
+
return {
|
|
1517
|
+
ok: true,
|
|
1518
|
+
totalRules: rules.length,
|
|
1519
|
+
totalRelations: relations.length,
|
|
1520
|
+
alwaysActiveCount,
|
|
1521
|
+
domains
|
|
1522
|
+
};
|
|
1523
|
+
}
|
|
1524
|
+
// causal-benchmark.ts
|
|
1525
|
+
var RESET = "\x1B[0m";
|
|
1526
|
+
var BOLD = "\x1B[1m";
|
|
1527
|
+
var CYAN = "\x1B[36m";
|
|
1528
|
+
var GREEN = "\x1B[32m";
|
|
1529
|
+
var YELLOW = "\x1B[33m";
|
|
1530
|
+
var RED = "\x1B[31m";
|
|
1531
|
+
var GRAY = "\x1B[90m";
|
|
1532
|
+
function calcPercentile(sorted, p) {
|
|
1533
|
+
if (sorted.length === 0)
|
|
1534
|
+
return 0;
|
|
1535
|
+
const idx = Math.min(sorted.length - 1, Math.floor(p / 100 * sorted.length));
|
|
1536
|
+
return Math.round(sorted[idx] * 1000) / 1000;
|
|
1537
|
+
}
|
|
1538
|
+
function generateSyntheticDatabase(ruleCount, relationCount) {
|
|
1539
|
+
const domains = [
|
|
1540
|
+
"Project_POE2_AutoFilter",
|
|
1541
|
+
"Project_Vesper_DSP",
|
|
1542
|
+
"Project_Synapse_Kit",
|
|
1543
|
+
"BioPhys_Research_And_Architecture",
|
|
1544
|
+
"Hardware_And_PCB",
|
|
1545
|
+
"Knowledge_Base"
|
|
1546
|
+
];
|
|
1547
|
+
const rules = [
|
|
1548
|
+
{
|
|
1549
|
+
id: "rule-always-1",
|
|
1550
|
+
domain: "User_Preferences_And_Rules",
|
|
1551
|
+
title: "Global_Preference_1",
|
|
1552
|
+
triggers: { alwaysActive: true },
|
|
1553
|
+
invariants: ["\uD56D\uC0C1 \uD55C\uAD6D\uC5B4 \uC6B0\uC120 \uCD9C\uB825", "bun \uD544\uC218 \uC0AC\uC6A9"],
|
|
1554
|
+
actions: [],
|
|
1555
|
+
context: []
|
|
1556
|
+
},
|
|
1557
|
+
{
|
|
1558
|
+
id: "rule-always-2",
|
|
1559
|
+
domain: "User_Preferences_And_Rules",
|
|
1560
|
+
title: "Global_Preference_2",
|
|
1561
|
+
triggers: { alwaysActive: true },
|
|
1562
|
+
invariants: ["console.log \uB514\uBC84\uADF8 \uCF54\uB4DC \uAE08\uC9C0", "\uD558\uB4DC\uCF54\uB529 \uAE08\uC9C0"],
|
|
1563
|
+
actions: [],
|
|
1564
|
+
context: []
|
|
1565
|
+
},
|
|
1566
|
+
{
|
|
1567
|
+
id: "rule-always-3",
|
|
1568
|
+
domain: "User_Preferences_And_Rules",
|
|
1569
|
+
title: "Global_Preference_3",
|
|
1570
|
+
triggers: { alwaysActive: true },
|
|
1571
|
+
invariants: ["100% \uC21C\uC218 Rust \uBC31\uC5D4\uB4DC \uC6D0\uCE59"],
|
|
1572
|
+
actions: [],
|
|
1573
|
+
context: []
|
|
1574
|
+
},
|
|
1575
|
+
{
|
|
1576
|
+
id: "rule-always-4",
|
|
1577
|
+
domain: "User_Preferences_And_Rules",
|
|
1578
|
+
title: "Global_Preference_4",
|
|
1579
|
+
triggers: { alwaysActive: true },
|
|
1580
|
+
invariants: ["\uAC80\uC99D \uCD5C\uC6B0\uC120 \uC6D0\uCE59 \uC900\uC218"],
|
|
1581
|
+
actions: [],
|
|
1582
|
+
context: []
|
|
1583
|
+
}
|
|
1584
|
+
];
|
|
1585
|
+
for (let i = 4;i < ruleCount; i++) {
|
|
1586
|
+
const dom = domains[i % domains.length];
|
|
1587
|
+
const moduleIdx = i % 500;
|
|
1588
|
+
rules.push({
|
|
1589
|
+
id: `rule-synth-${i}`,
|
|
1590
|
+
domain: dom,
|
|
1591
|
+
title: `Synth_Node_${i}`,
|
|
1592
|
+
triggers: {
|
|
1593
|
+
files: [`packages/mod_${moduleIdx}/src/file_${i}.rs`],
|
|
1594
|
+
keywords: [`kw_${i}`, `topic_${i % 250}`],
|
|
1595
|
+
commands: i % 20 === 0 ? ["build"] : i % 20 === 1 ? ["release"] : ["check"]
|
|
1596
|
+
},
|
|
1597
|
+
invariants: [`\uBD88\uBCC0 \uACC4\uC57D #${i}: \uBA54\uBAA8\uB9AC \uC548\uC804\uC131 \uBC0F \uD0C0\uC785 \uACBD\uACC4 \uBCF4\uC7A5`],
|
|
1598
|
+
actions: [`1. \uBAA8\uB4C8 ${moduleIdx} \uC815\uC801 \uAC80\uC99D \uC218\uD589`],
|
|
1599
|
+
context: [`\uD569\uC131 \uBCA4\uCE58\uB9C8\uD06C \uB178\uB4DC \uCEE8\uD14D\uC2A4\uD2B8 \uB370\uC774\uD130 #${i}`],
|
|
1600
|
+
lastVerified: "2026-11-01"
|
|
1601
|
+
});
|
|
1602
|
+
}
|
|
1603
|
+
const relations = [];
|
|
1604
|
+
relations.push({ from: "Synth_Node_10", to: "Synth_Node_11", relationType: "triggers_hop_1" }, { from: "Synth_Node_11", to: "Synth_Node_12", relationType: "triggers_hop_2" }, { from: "Synth_Node_12", to: "Synth_Node_13", relationType: "triggers_hop_3" }, { from: "Synth_Node_13", to: "Synth_Node_14", relationType: "triggers_hop_4" }, { from: "Synth_Node_14", to: "Synth_Node_15", relationType: "triggers_hop_5" }, { from: "Synth_Node_15", to: "Synth_Node_10", relationType: "circular_feedback_edge" });
|
|
1605
|
+
for (let j = relations.length;j < relationCount; j++) {
|
|
1606
|
+
const fromIdx = 4 + j % (ruleCount - 10);
|
|
1607
|
+
const toIdx = fromIdx + 1 + j % 5;
|
|
1608
|
+
relations.push({
|
|
1609
|
+
from: `Synth_Node_${fromIdx}`,
|
|
1610
|
+
to: `Synth_Node_${toIdx}`,
|
|
1611
|
+
relationType: "causal_impact_on"
|
|
1612
|
+
});
|
|
1613
|
+
}
|
|
1614
|
+
return {
|
|
1615
|
+
version: "2.1.0",
|
|
1616
|
+
updatedAt: new Date().toISOString(),
|
|
1617
|
+
totalRules: rules.length,
|
|
1618
|
+
totalRelations: relations.length,
|
|
1619
|
+
rules,
|
|
1620
|
+
relations
|
|
1621
|
+
};
|
|
1622
|
+
}
|
|
1623
|
+
async function executeOverloadBenchmark(customConfig, silent = false) {
|
|
1624
|
+
const config = {
|
|
1625
|
+
ruleScale: customConfig?.ruleScale ?? 1e4,
|
|
1626
|
+
relationScale: customConfig?.relationScale ?? 20000,
|
|
1627
|
+
concurrency: customConfig?.concurrency ?? 50,
|
|
1628
|
+
totalRequests: customConfig?.totalRequests ?? 5000,
|
|
1629
|
+
fileInputScale: customConfig?.fileInputScale ?? 1e4,
|
|
1630
|
+
crudIterations: customConfig?.crudIterations ?? 5000
|
|
1631
|
+
};
|
|
1632
|
+
const log = (msg) => {
|
|
1633
|
+
if (!silent)
|
|
1634
|
+
process.stdout.write(msg);
|
|
1635
|
+
};
|
|
1636
|
+
const benchStart = performance.now();
|
|
1637
|
+
const scenarios = [];
|
|
1638
|
+
const isExtreme = config.ruleScale >= 1e5;
|
|
1639
|
+
const modeTitle = isExtreme ? "\uC635\uC158 2 \uADF9\uD55C \uD55C\uACC4 \uB3CC\uD30C \uC2A4\uD2B8\uB808\uC2A4 \uBCA4\uCE58\uB9C8\uD06C (Stress Max)" : "\uC635\uC158 1 \uD45C\uC900 \uACFC\uBD80\uD558 \uC2A4\uD2B8\uB808\uC2A4 \uBCA4\uCE58\uB9C8\uD06C";
|
|
1640
|
+
const sloP50Ms = isExtreme ? 2 : 0.8;
|
|
1641
|
+
const sloP99Ms = isExtreme ? 5 : 1.5;
|
|
1642
|
+
const sloRps = isExtreme ? 3000 : 5000;
|
|
1643
|
+
const sloHopMs = isExtreme ? 100 : 30;
|
|
1644
|
+
const sloHeapDeltaMB = isExtreme ? 8 : 3;
|
|
1645
|
+
log(`
|
|
1646
|
+
${BOLD}${CYAN}\uD83D\uDD25 [Causal Memory 2.1] ${modeTitle} \uAC00\uB3D9${RESET}
|
|
1647
|
+
`);
|
|
1648
|
+
log(`${GRAY}\u2022 \uC2A4\uD399: \uADDC\uCE59 ${config.ruleScale.toLocaleString()}\uAC1C | \uAC04\uC120 ${config.relationScale.toLocaleString()}\uAC1C | \uB3D9\uC2DC\uC131 ${config.concurrency} \uC6CC\uCEE4 | \uC694\uCCAD ${config.totalRequests.toLocaleString()}\uD68C | \uBCC0\uACBD\uD30C\uC77C ${config.fileInputScale.toLocaleString()}\uAC1C${RESET}
|
|
1649
|
+
`);
|
|
1650
|
+
log(`${GRAY}${"\u2501".repeat(84)}${RESET}
|
|
1651
|
+
|
|
1652
|
+
`);
|
|
1653
|
+
log(`${BOLD}${YELLOW}\uD83D\uDCCC [\uC2DC\uB098\uB9AC\uC624 1] ${config.ruleScale.toLocaleString()}\uAC1C \uADDC\uCE59 & ${config.relationScale.toLocaleString()}\uAC1C \uAC04\uC120 \uC801\uC7AC \uBC0F O(1) \uC5ED\uC778\uB371\uC2A4 \uACF5\uBA85 \uD14C\uC2A4\uD2B8${RESET}
|
|
1654
|
+
`);
|
|
1655
|
+
const genStart = performance.now();
|
|
1656
|
+
const synthDb = generateSyntheticDatabase(config.ruleScale, config.relationScale);
|
|
1657
|
+
const genDurationMs = Math.round((performance.now() - genStart) * 100) / 100;
|
|
1658
|
+
const indexStart = performance.now();
|
|
1659
|
+
const scaleStore = new CausalMemoryStore(undefined, synthDb);
|
|
1660
|
+
const indexBuildMs = Math.round((performance.now() - indexStart) * 100) / 100;
|
|
1661
|
+
const queryLatencies = [];
|
|
1662
|
+
let lastResonance = scaleStore.queryResonance({ keywords: ["kw_10"] });
|
|
1663
|
+
for (let i = 0;i < 1000; i++) {
|
|
1664
|
+
const targetKw = `kw_${10 + i % 5000}`;
|
|
1665
|
+
const qStart = performance.now();
|
|
1666
|
+
lastResonance = scaleStore.queryResonance({ keywords: [targetKw] });
|
|
1667
|
+
queryLatencies.push(performance.now() - qStart);
|
|
1668
|
+
}
|
|
1669
|
+
queryLatencies.sort((a, b) => a - b);
|
|
1670
|
+
const s1P50 = calcPercentile(queryLatencies, 50);
|
|
1671
|
+
const s1P95 = calcPercentile(queryLatencies, 95);
|
|
1672
|
+
const s1P99 = calcPercentile(queryLatencies, 99);
|
|
1673
|
+
const s1Passed = s1P50 <= sloP50Ms && s1P99 <= sloP99Ms && parseFloat(lastResonance.tokenDietSavingsRatio) >= 99;
|
|
1674
|
+
scenarios.push({
|
|
1675
|
+
name: "\uC2DC\uB098\uB9AC\uC624 1: \uB300\uADDC\uBAA8 \uBCFC\uB968 \uC801\uC7AC \uBC0F O(1) \uACF5\uBA85 \uCFFC\uB9AC",
|
|
1676
|
+
passed: s1Passed,
|
|
1677
|
+
details: {
|
|
1678
|
+
totalRules: config.ruleScale,
|
|
1679
|
+
totalRelations: config.relationScale,
|
|
1680
|
+
syntheticGenMs: genDurationMs,
|
|
1681
|
+
indexBuildMs,
|
|
1682
|
+
p50Ms: s1P50,
|
|
1683
|
+
p95Ms: s1P95,
|
|
1684
|
+
p99Ms: s1P99,
|
|
1685
|
+
tokenSavings: lastResonance.tokenDietSavingsRatio
|
|
1686
|
+
}
|
|
1687
|
+
});
|
|
1688
|
+
log(` \u2022 \uC5ED\uC778\uB371\uC2A4 \uAD6C\uCD95 \uC2DC\uAC04 : ${BOLD}${GREEN}${indexBuildMs}ms${RESET} (${config.ruleScale.toLocaleString()} Rules + ${config.relationScale.toLocaleString()} Relations)
|
|
1689
|
+
`);
|
|
1690
|
+
log(` \u2022 \uACF5\uBA85 \uCFFC\uB9AC \uB808\uC774\uD134\uC2DC : ${BOLD}p50=${s1P50}ms | p95=${s1P95}ms | p99=${s1P99}ms${RESET} (\uBAA9\uD45C: p50\u2264${sloP50Ms}ms, p99\u2264${sloP99Ms}ms)
|
|
1691
|
+
`);
|
|
1692
|
+
log(` \u2022 \uD1A0\uD070 \uC808\uAC10\uB960 : ${BOLD}${GREEN}${lastResonance.tokenDietSavingsRatio}${RESET} (${lastResonance.matchedCount}/${config.ruleScale}\uAC1C\uB9CC \uC815\uBC00 \uACF5\uBA85)
|
|
1693
|
+
`);
|
|
1694
|
+
log(` \u2022 \uD310\uC815 : ${s1Passed ? `${BOLD}${GREEN}\u2705 PASS${RESET}` : `${BOLD}${RED}\u274C FAIL${RESET}`}
|
|
1695
|
+
|
|
1696
|
+
`);
|
|
1697
|
+
log(`${BOLD}${YELLOW}\uD83D\uDCCC [\uC2DC\uB098\uB9AC\uC624 2] \uB3D9\uC2DC ${config.concurrency} \uD074\uB77C\uC774\uC5B8\uD2B8 \xD7 ${config.totalRequests.toLocaleString()}\uD68C HTTP/MCP \uB370\uBAAC \uD3ED\uACA9 \uD14C\uC2A4\uD2B8${RESET}
|
|
1698
|
+
`);
|
|
1699
|
+
const benchPort = 4288;
|
|
1700
|
+
const server = startCausalServer({ port: benchPort });
|
|
1701
|
+
let successCount = 0;
|
|
1702
|
+
let errorCount = 0;
|
|
1703
|
+
const httpLatencies = [];
|
|
1704
|
+
const requestsPerWorker = Math.floor(config.totalRequests / config.concurrency);
|
|
1705
|
+
for (let w = 0;w < 10; w++) {
|
|
1706
|
+
await fetch(`http://127.0.0.1:${benchPort}/query`, {
|
|
1707
|
+
method: "POST",
|
|
1708
|
+
headers: { "Content-Type": "application/json" },
|
|
1709
|
+
body: JSON.stringify({ keywords: ["poe2"], format: "compact" })
|
|
1710
|
+
});
|
|
1711
|
+
}
|
|
1712
|
+
const concStart = performance.now();
|
|
1713
|
+
try {
|
|
1714
|
+
const workerPromises = Array.from({ length: config.concurrency }, async (_, workerId) => {
|
|
1715
|
+
const sampleQueries = [
|
|
1716
|
+
{ keywords: ["poe2", "updater"], format: "compact" },
|
|
1717
|
+
{ files: ["crates/vesper-audio-core/src/lib.rs"], format: "compact" },
|
|
1718
|
+
{ keywords: ["synapse", "causal"], format: "compact" },
|
|
1719
|
+
{ keywords: ["pcb", "kicad"], format: "compact" }
|
|
1720
|
+
];
|
|
1721
|
+
for (let r = 0;r < requestsPerWorker; r++) {
|
|
1722
|
+
const qPayload = sampleQueries[(workerId + r) % sampleQueries.length];
|
|
1723
|
+
const reqT0 = performance.now();
|
|
1724
|
+
try {
|
|
1725
|
+
const resp = await fetch(`http://127.0.0.1:${benchPort}/query`, {
|
|
1726
|
+
method: "POST",
|
|
1727
|
+
headers: { "Content-Type": "application/json" },
|
|
1728
|
+
body: JSON.stringify(qPayload)
|
|
1729
|
+
});
|
|
1730
|
+
if (resp.ok) {
|
|
1731
|
+
await resp.text();
|
|
1732
|
+
successCount++;
|
|
1733
|
+
} else {
|
|
1734
|
+
errorCount++;
|
|
1735
|
+
}
|
|
1736
|
+
} catch {
|
|
1737
|
+
errorCount++;
|
|
1738
|
+
}
|
|
1739
|
+
httpLatencies.push(performance.now() - reqT0);
|
|
1740
|
+
}
|
|
1741
|
+
});
|
|
1742
|
+
await Promise.all(workerPromises);
|
|
1743
|
+
} finally {
|
|
1744
|
+
server.stop();
|
|
1745
|
+
}
|
|
1746
|
+
const concTotalSec = (performance.now() - concStart) / 1000;
|
|
1747
|
+
const rps = Math.round(successCount / concTotalSec);
|
|
1748
|
+
httpLatencies.sort((a, b) => a - b);
|
|
1749
|
+
const s2P50 = calcPercentile(httpLatencies, 50);
|
|
1750
|
+
const s2P95 = calcPercentile(httpLatencies, 95);
|
|
1751
|
+
const s2P99 = calcPercentile(httpLatencies, 99);
|
|
1752
|
+
const s2Passed = errorCount === 0 && successCount === config.totalRequests && rps >= sloRps;
|
|
1753
|
+
scenarios.push({
|
|
1754
|
+
name: "\uC2DC\uB098\uB9AC\uC624 2: \uACE0\uB3D9\uC2DC\uC131 \uB370\uBAAC \uCC98\uB9AC\uB7C9",
|
|
1755
|
+
passed: s2Passed,
|
|
1756
|
+
details: {
|
|
1757
|
+
concurrency: config.concurrency,
|
|
1758
|
+
totalRequests: config.totalRequests,
|
|
1759
|
+
successCount,
|
|
1760
|
+
errorCount,
|
|
1761
|
+
durationSec: Math.round(concTotalSec * 100) / 100,
|
|
1762
|
+
rps,
|
|
1763
|
+
httpP50Ms: s2P50,
|
|
1764
|
+
httpP95Ms: s2P95,
|
|
1765
|
+
httpP99Ms: s2P99
|
|
1766
|
+
}
|
|
1767
|
+
});
|
|
1768
|
+
log(` \u2022 \uCD1D \uCC98\uB9AC \uC694\uCCAD : ${BOLD}${GREEN}${successCount.toLocaleString()} / ${config.totalRequests.toLocaleString()} \uC131\uACF5${RESET} (\uC624\uB958\uC728: ${errorCount === 0 ? "0.00%" : errorCount})
|
|
1769
|
+
`);
|
|
1770
|
+
log(` \u2022 \uCD08\uB2F9 \uCC98\uB9AC\uB7C9 (RPS) : ${BOLD}${CYAN}${rps.toLocaleString()} req/sec${RESET} (\uCD1D \uC18C\uC694: ${concTotalSec.toFixed(2)}s, \uBAA9\uD45C: \u2265${sloRps.toLocaleString()} req/s)
|
|
1771
|
+
`);
|
|
1772
|
+
log(` \u2022 HTTP \uC655\uBCF5 \uB808\uC774\uD134\uC2DC : ${BOLD}p50=${s2P50}ms | p95=${s2P95}ms | p99=${s2P99}ms${RESET}
|
|
1773
|
+
`);
|
|
1774
|
+
log(` \u2022 \uD310\uC815 : ${s2Passed ? `${BOLD}${GREEN}\u2705 PASS${RESET}` : `${BOLD}${RED}\u274C FAIL${RESET}`}
|
|
1775
|
+
|
|
1776
|
+
`);
|
|
1777
|
+
log(`${BOLD}${YELLOW}\uD83D\uDCCC [\uC2DC\uB098\uB9AC\uC624 3] ${config.fileInputScale.toLocaleString()}\uAC1C \uBCC0\uACBD \uD30C\uC77C \uB3D9\uC2DC \uC785\uB825 & 5-Hop \uB2E4\uC911 \uCCB4\uC778/\uC21C\uD658 \uCC38\uC870 \uC2A4\uD2B8\uB808\uC2A4${RESET}
|
|
1778
|
+
`);
|
|
1779
|
+
const massiveFiles = [];
|
|
1780
|
+
for (let f = 0;f < config.fileInputScale; f++) {
|
|
1781
|
+
massiveFiles.push(`services/microservice_${f % 100}/src/handler_${f}.ts`);
|
|
1782
|
+
}
|
|
1783
|
+
massiveFiles[Math.floor(config.fileInputScale / 2)] = "packages/mod_10/src/file_10.rs";
|
|
1784
|
+
const hopStart = performance.now();
|
|
1785
|
+
const multiHopRes = scaleStore.queryResonance({
|
|
1786
|
+
files: massiveFiles,
|
|
1787
|
+
propagateRelations: true,
|
|
1788
|
+
maxHops: 5
|
|
1789
|
+
});
|
|
1790
|
+
const hopDurationMs = Math.round((performance.now() - hopStart) * 100) / 100;
|
|
1791
|
+
const matchedTitles = new Set(multiHopRes.resonanceRules.map((r) => r.title));
|
|
1792
|
+
const all5HopsPropagated = matchedTitles.has("Synth_Node_10") && matchedTitles.has("Synth_Node_11") && matchedTitles.has("Synth_Node_12") && matchedTitles.has("Synth_Node_13") && matchedTitles.has("Synth_Node_14") && matchedTitles.has("Synth_Node_15");
|
|
1793
|
+
const s3Passed = all5HopsPropagated && hopDurationMs <= sloHopMs;
|
|
1794
|
+
scenarios.push({
|
|
1795
|
+
name: "\uC2DC\uB098\uB9AC\uC624 3: \uB300\uADDC\uBAA8 \uD30C\uC77C & 5-Hop \uB2E4\uC911 \uC778\uACFC \uC804\uD30C",
|
|
1796
|
+
passed: s3Passed,
|
|
1797
|
+
details: {
|
|
1798
|
+
inputFilesCount: config.fileInputScale,
|
|
1799
|
+
maxHops: 5,
|
|
1800
|
+
traversalDurationMs: hopDurationMs,
|
|
1801
|
+
matchedRulesCount: multiHopRes.matchedCount,
|
|
1802
|
+
activeRelationsCount: multiHopRes.activeRelations.length,
|
|
1803
|
+
all5HopsResolved: all5HopsPropagated ? "YES (Circular Loop Safe)" : "NO",
|
|
1804
|
+
tokenSavings: multiHopRes.tokenDietSavingsRatio
|
|
1805
|
+
}
|
|
1806
|
+
});
|
|
1807
|
+
log(` \u2022 \uB300\uB7C9 \uD30C\uC77C + 5-Hop \uC18C\uC694 : ${BOLD}${GREEN}${hopDurationMs}ms${RESET} (\uBAA9\uD45C: \u2264${sloHopMs}ms)
|
|
1808
|
+
`);
|
|
1809
|
+
log(` \u2022 5\uB2E8\uACC4 \uC5F0\uC1C4 \uC804\uD30C \uAC80\uC99D : ${BOLD}${all5HopsPropagated ? `${GREEN}Node_10 \u2794 Node_15 (5-Hop) \uBC0F \uC21C\uD658 \uCC38\uC870 \uBC29\uC5B4 100% \uC131\uACF5` : `${RED}\uC2E4\uD328`}${RESET}
|
|
1810
|
+
`);
|
|
1811
|
+
log(` \u2022 \uC815\uBC00 \uACA9\uB9AC \uC808\uAC10\uB960 : ${BOLD}${GREEN}${multiHopRes.tokenDietSavingsRatio}${RESET} (${multiHopRes.matchedCount}/${config.ruleScale}\uAC1C\uB9CC \uD65C\uC131\uD654)
|
|
1812
|
+
`);
|
|
1813
|
+
log(` \u2022 \uD310\uC815 : ${s3Passed ? `${BOLD}${GREEN}\u2705 PASS${RESET}` : `${BOLD}${RED}\u274C FAIL${RESET}`}
|
|
1814
|
+
|
|
1815
|
+
`);
|
|
1816
|
+
log(`${BOLD}${YELLOW}\uD83D\uDCCC [\uC2DC\uB098\uB9AC\uC624 4] ${config.crudIterations.toLocaleString()}\uD68C \uC5F0\uC18D CRUD \uD2B8\uB79C\uC7AD\uC158 & \uD799 \uBA54\uBAA8\uB9AC \uB204\uC218 \uAC10\uC0AC (Zero-Leak Audit)${RESET}
|
|
1817
|
+
`);
|
|
1818
|
+
if (typeof Bun !== "undefined" && typeof Bun.gc === "function") {
|
|
1819
|
+
Bun.gc(true);
|
|
1820
|
+
}
|
|
1821
|
+
const baseMem = process.memoryUsage();
|
|
1822
|
+
const baseHeapMB = baseMem.heapUsed / (1024 * 1024);
|
|
1823
|
+
const crudStore = new CausalMemoryStore(undefined, {
|
|
1824
|
+
version: "2.1.0",
|
|
1825
|
+
updatedAt: new Date().toISOString(),
|
|
1826
|
+
totalRules: 0,
|
|
1827
|
+
totalRelations: 0,
|
|
1828
|
+
rules: [],
|
|
1829
|
+
relations: []
|
|
1830
|
+
});
|
|
1831
|
+
const crudStart = performance.now();
|
|
1832
|
+
for (let c = 0;c < config.crudIterations; c++) {
|
|
1833
|
+
const tempName = `Ephemeral_Entity_${c % 50}`;
|
|
1834
|
+
crudStore.createEntities([
|
|
1835
|
+
{
|
|
1836
|
+
name: tempName,
|
|
1837
|
+
entityType: "StressTest",
|
|
1838
|
+
observations: [`\uC808\uB300 \uADDC\uCE59 #${c}`, `1. \uC2E4\uD589 \uB2E8\uACC4 #${c}`]
|
|
1839
|
+
}
|
|
1840
|
+
], false);
|
|
1841
|
+
crudStore.addObservations([{ entityName: tempName, contents: [`\uCD94\uAC00 \uAD00\uCC30 #${c}`] }], false);
|
|
1842
|
+
crudStore.queryResonance({ keywords: [tempName] });
|
|
1843
|
+
crudStore.deleteEntities([tempName], false);
|
|
1844
|
+
}
|
|
1845
|
+
const crudDurationMs = Math.round((performance.now() - crudStart) * 100) / 100;
|
|
1846
|
+
if (typeof Bun !== "undefined" && typeof Bun.gc === "function") {
|
|
1847
|
+
Bun.gc(true);
|
|
1848
|
+
}
|
|
1849
|
+
const afterMem = process.memoryUsage();
|
|
1850
|
+
const afterHeapMB = afterMem.heapUsed / (1024 * 1024);
|
|
1851
|
+
const rssMB = Math.round(afterMem.rss / (1024 * 1024) * 100) / 100;
|
|
1852
|
+
const heapDeltaMB = Math.round(Math.max(0, afterHeapMB - baseHeapMB) * 100) / 100;
|
|
1853
|
+
const s4Passed = heapDeltaMB <= sloHeapDeltaMB && crudStore.getAllRules().length === 0;
|
|
1854
|
+
scenarios.push({
|
|
1855
|
+
name: "\uC2DC\uB098\uB9AC\uC624 4: \uC5F0\uC18D CRUD \uB0B4\uAD6C\uB3C4 \uBC0F \uD799 \uB204\uC218 \uAC10\uC0AC",
|
|
1856
|
+
passed: s4Passed,
|
|
1857
|
+
details: {
|
|
1858
|
+
crudCycles: config.crudIterations,
|
|
1859
|
+
totalOperations: config.crudIterations * 4,
|
|
1860
|
+
durationMs: crudDurationMs,
|
|
1861
|
+
baseHeapMB: Math.round(baseHeapMB * 100) / 100,
|
|
1862
|
+
afterHeapMB: Math.round(afterHeapMB * 100) / 100,
|
|
1863
|
+
heapDeltaMB,
|
|
1864
|
+
rssMB,
|
|
1865
|
+
residualRules: crudStore.getAllRules().length
|
|
1866
|
+
}
|
|
1867
|
+
});
|
|
1868
|
+
log(` \u2022 \uCD1D \uC218\uD589 \uC5F0\uC0B0 \uC218 : ${BOLD}${(config.crudIterations * 4).toLocaleString()}\uD68C CRUD \uC5F0\uC0B0${RESET} (\uC18C\uC694: ${crudDurationMs}ms)
|
|
1869
|
+
`);
|
|
1870
|
+
log(` \u2022 \uD799 \uBA54\uBAA8\uB9AC \uBCC0\uD654\uB7C9 : ${BOLD}${GREEN}\u0394 ${heapDeltaMB} MB${RESET} (\uC2DC\uC791: ${baseHeapMB.toFixed(2)}MB \u2794 \uC885\uB8CC: ${afterHeapMB.toFixed(2)}MB, \uBAA9\uD45C: \u2264${sloHeapDeltaMB.toFixed(1)}MB)
|
|
1871
|
+
`);
|
|
1872
|
+
log(` \u2022 \uD504\uB85C\uC138\uC2A4 RSS \uC810\uC720 : ${BOLD}${rssMB} MB${RESET}
|
|
1873
|
+
`);
|
|
1874
|
+
log(` \u2022 \uD310\uC815 : ${s4Passed ? `${BOLD}${GREEN}\u2705 PASS${RESET}` : `${BOLD}${RED}\u274C FAIL${RESET}`}
|
|
1875
|
+
|
|
1876
|
+
`);
|
|
1877
|
+
const totalDurationMs = Math.round((performance.now() - benchStart) * 100) / 100;
|
|
1878
|
+
const allPassed = scenarios.every((s) => s.passed);
|
|
1879
|
+
log(`${GRAY}${"\u2501".repeat(84)}${RESET}
|
|
1880
|
+
`);
|
|
1881
|
+
log(`${BOLD}${allPassed ? GREEN : RED}\uD83C\uDFC6 [\uCD5C\uC885 \uBCA4\uCE58\uB9C8\uD06C \uACB0\uACFC] 4\uB300 \uACFC\uBD80\uD558 \uCC28\uC6D0 \uC804\uC218 ${allPassed ? "ALL PASS (100% \uD569\uACA9)" : "\uC77C\uBD80 \uBBF8\uB2EC"} (\uCD1D \uC18C\uC694 \uC2DC\uAC04: ${(totalDurationMs / 1000).toFixed(2)}s)${RESET}
|
|
1882
|
+
|
|
1883
|
+
`);
|
|
1884
|
+
return {
|
|
1885
|
+
timestamp: new Date().toISOString(),
|
|
1886
|
+
config,
|
|
1887
|
+
allPassed,
|
|
1888
|
+
totalDurationMs,
|
|
1889
|
+
scenarios
|
|
1890
|
+
};
|
|
1891
|
+
}
|
|
1892
|
+
export {
|
|
1893
|
+
CAUSAL_MCP_TOOLS,
|
|
1894
|
+
CAUSAL_MEMORY_CONFIG,
|
|
1895
|
+
CausalMemoryStore,
|
|
1896
|
+
detectModifiedFiles,
|
|
1897
|
+
executeOverloadBenchmark,
|
|
1898
|
+
formatResonanceForAgent,
|
|
1899
|
+
generateSyntheticDatabase,
|
|
1900
|
+
handleMcpJsonRpcRequest,
|
|
1901
|
+
migrateLegacyMemoryGraph,
|
|
1902
|
+
runCausalQuery,
|
|
1903
|
+
startCausalMcpStdioServer,
|
|
1904
|
+
startCausalServer
|
|
1905
|
+
};
|