memorix 1.6.1 → 1.6.3
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/CHANGELOG.md +35 -0
- package/TEAM.md +86 -86
- package/dist/cli/index.js +2582 -1845
- package/dist/index.js +1355 -683
- package/dist/maintenance-runner.js +1621 -1096
- package/dist/media-audio-runner.js +1612 -1088
- package/dist/media-video-runner.js +1612 -1088
- package/dist/memcode-runtime/CHANGELOG.md +35 -0
- package/dist/sdk.js +2007 -1396
- package/dist/vector-backfill-runner.js +1610 -1086
- package/docs/DESIGN_DECISIONS.md +357 -357
- package/docs/PERFORMANCE.md +15 -0
- package/package.json +2 -1
- package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
- package/plugins/codebuddy/memorix/.codebuddy-plugin/plugin.json +1 -1
- package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
- package/plugins/copilot/memorix/plugin.json +1 -1
- package/plugins/gemini/memorix/gemini-extension.json +1 -1
- package/plugins/omp/memorix/package.json +1 -1
- package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
- package/plugins/pi/memorix/package.json +1 -1
- package/server.json +2 -2
- package/src/audit/index.ts +156 -156
- package/src/cli/commands/background.ts +51 -5
- package/src/cli/commands/formation.ts +48 -48
- package/src/cli/commands/git-hook-install.ts +111 -111
- package/src/cli/commands/handoff.ts +54 -54
- package/src/cli/commands/hooks-status.ts +63 -63
- package/src/cli/commands/ingest.ts +44 -44
- package/src/cli/commands/integrate-shared.ts +15 -15
- package/src/cli/commands/lock.ts +82 -82
- package/src/cli/commands/message.ts +104 -104
- package/src/cli/commands/operator-shared.ts +18 -11
- package/src/cli/commands/poll.ts +58 -58
- package/src/cli/commands/reasoning.ts +118 -118
- package/src/cli/commands/serve-http.ts +29 -7
- package/src/cli/commands/serve-shared.ts +49 -6
- package/src/cli/commands/serve.ts +4 -0
- package/src/cli/commands/skills.ts +114 -114
- package/src/cli/commands/task.ts +167 -167
- package/src/cli/commands/transfer.ts +47 -47
- package/src/cli/index.ts +406 -406
- package/src/cli/tui/ChatView.tsx +234 -234
- package/src/cli/tui/CommandBar.tsx +312 -312
- package/src/cli/tui/ContextRail.tsx +118 -118
- package/src/cli/tui/HeaderBar.tsx +72 -72
- package/src/cli/tui/LogoBanner.tsx +51 -51
- package/src/cli/tui/index.ts +41 -41
- package/src/cli/version.ts +7 -7
- package/src/cli/workbench.ts +1 -1
- package/src/dashboard/project-classification.ts +64 -64
- package/src/embedding/api-provider.ts +503 -27
- package/src/embedding/provider.ts +20 -3
- package/src/git/hooks-path.ts +85 -85
- package/src/hooks/lifecycle.ts +1 -0
- package/src/hooks/pattern-detector.ts +173 -173
- package/src/hooks/significance-filter.ts +250 -250
- package/src/llm/memory-manager.ts +328 -328
- package/src/llm/quality.ts +248 -248
- package/src/memory/attribution-guard.ts +249 -249
- package/src/memory/disclosure-policy.ts +135 -135
- package/src/memory/entity-extractor.ts +197 -197
- package/src/memory/formation/evaluate.ts +217 -217
- package/src/memory/freshness.ts +122 -122
- package/src/memory/graph.ts +197 -197
- package/src/memory/observations.ts +68 -18
- package/src/memory/refs.ts +94 -94
- package/src/memory/secret-filter.ts +79 -79
- package/src/memory/session.ts +13 -3
- package/src/orchestrate/adapters/claude-stream.ts +192 -192
- package/src/orchestrate/adapters/claude.ts +111 -111
- package/src/orchestrate/adapters/codex-stream.ts +134 -134
- package/src/orchestrate/adapters/codex.ts +41 -41
- package/src/orchestrate/adapters/gemini-stream.ts +166 -166
- package/src/orchestrate/adapters/gemini.ts +42 -42
- package/src/orchestrate/adapters/index.ts +73 -73
- package/src/orchestrate/adapters/opencode-stream.ts +143 -143
- package/src/orchestrate/adapters/opencode.ts +47 -47
- package/src/orchestrate/adapters/types.ts +77 -77
- package/src/orchestrate/capability-router.ts +284 -284
- package/src/orchestrate/cost-tracker.ts +219 -219
- package/src/orchestrate/error-recovery.ts +191 -191
- package/src/orchestrate/evidence.ts +140 -140
- package/src/orchestrate/ledger.ts +110 -110
- package/src/orchestrate/memorix-bridge.ts +343 -343
- package/src/orchestrate/output-budget.ts +80 -80
- package/src/orchestrate/permission.ts +152 -152
- package/src/orchestrate/pipeline-trace.ts +131 -131
- package/src/orchestrate/prompt-builder.ts +155 -155
- package/src/orchestrate/ring-buffer.ts +37 -37
- package/src/orchestrate/task-graph.ts +389 -389
- package/src/project/aliases.ts +374 -374
- package/src/project/launch-root.ts +115 -0
- package/src/rules/adapters/trae.ts +56 -56
- package/src/rules/syncer.ts +235 -235
- package/src/runtime/isolated-maintenance.ts +12 -1
- package/src/sdk.ts +12 -8
- package/src/search/query-expansion.ts +52 -52
- package/src/server/formation-timeout.ts +27 -27
- package/src/server.ts +38 -5
- package/src/skills/mini-skills.ts +386 -386
- package/src/store/chat-store.ts +119 -119
- package/src/store/graph-store.ts +249 -249
- package/src/store/mini-skill-store.ts +349 -349
- package/src/store/obs-store.ts +2 -2
- package/src/store/orama-store.ts +21 -3
- package/src/store/persistence-json.ts +212 -212
- package/src/store/persistence.ts +291 -291
- package/src/store/project-affinity.ts +195 -195
- package/src/store/sqlite-db.ts +2 -0
- package/src/store/sqlite-store.ts +19 -4
- package/src/team/event-bus.ts +76 -76
- package/src/team/handoff.ts +161 -161
- package/src/team/poll.ts +132 -132
- package/src/workspace/mcp-adapters/codex.ts +191 -191
- package/src/workspace/mcp-adapters/copilot.ts +105 -105
- package/src/workspace/mcp-adapters/cursor.ts +53 -53
- package/src/workspace/mcp-adapters/kiro.ts +64 -64
- package/src/workspace/mcp-adapters/opencode.ts +123 -123
- package/src/workspace/mcp-adapters/trae.ts +134 -134
- package/src/workspace/mcp-adapters/windsurf.ts +91 -91
- package/src/workspace/sanitizer.ts +60 -60
package/src/store/obs-store.ts
CHANGED
|
@@ -59,7 +59,7 @@ export interface ObservationStore {
|
|
|
59
59
|
/** Load one project's observations without scanning the shared flat store. */
|
|
60
60
|
loadByProject(
|
|
61
61
|
projectId: string,
|
|
62
|
-
options?: { status?: string; limit?: number; offset?: number; afterId?: number },
|
|
62
|
+
options?: { status?: string; limit?: number; offset?: number; afterId?: number; newestFirst?: boolean },
|
|
63
63
|
): Promise<Observation[]>;
|
|
64
64
|
|
|
65
65
|
/** Count one project's observations without materializing the rows. */
|
|
@@ -231,7 +231,7 @@ export class DegradedBackend implements ObservationStore {
|
|
|
231
231
|
return [];
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
-
async loadByProject(_projectId: string, _options?: { status?: string; limit?: number; offset?: number; afterId?: number }): Promise<Observation[]> {
|
|
234
|
+
async loadByProject(_projectId: string, _options?: { status?: string; limit?: number; offset?: number; afterId?: number; newestFirst?: boolean }): Promise<Observation[]> {
|
|
235
235
|
return [];
|
|
236
236
|
}
|
|
237
237
|
|
package/src/store/orama-store.ts
CHANGED
|
@@ -331,6 +331,8 @@ export interface HydrateIndexOptions {
|
|
|
331
331
|
allowNetworkProbe?: boolean;
|
|
332
332
|
/** Attach cached vectors after lexical hydration instead of awaiting cache I/O. */
|
|
333
333
|
deferCachedVectors?: boolean;
|
|
334
|
+
/** Skip cached-vector attach entirely (one-shot CLI reads). */
|
|
335
|
+
skipCachedVectors?: boolean;
|
|
334
336
|
}
|
|
335
337
|
|
|
336
338
|
type HydrationCandidate = { observation: any; id: string };
|
|
@@ -378,6 +380,9 @@ async function attachCachedVectors(
|
|
|
378
380
|
} catch {
|
|
379
381
|
// Vector cache hydration is best-effort. The normal backfill lane owns misses.
|
|
380
382
|
}
|
|
383
|
+
if (index % 50 === 0) {
|
|
384
|
+
await new Promise<void>((resolve) => setImmediate(resolve));
|
|
385
|
+
}
|
|
381
386
|
}
|
|
382
387
|
}
|
|
383
388
|
|
|
@@ -437,11 +442,20 @@ export async function hydrateIndex(
|
|
|
437
442
|
await insert(database, doc);
|
|
438
443
|
rememberObservationDoc(doc);
|
|
439
444
|
inserted++;
|
|
445
|
+
// Keep the HTTP event loop alive on large corpora so /health still answers.
|
|
446
|
+
if (inserted % 50 === 0) {
|
|
447
|
+
await new Promise<void>((resolve) => setImmediate(resolve));
|
|
448
|
+
}
|
|
449
|
+
if (inserted % 200 === 0) {
|
|
450
|
+
await new Promise<void>((resolve) => setTimeout(resolve, 0));
|
|
451
|
+
}
|
|
440
452
|
} catch { /* skip malformed entries */ }
|
|
441
453
|
}
|
|
442
454
|
|
|
443
|
-
deferredCachedVectorHydration =
|
|
444
|
-
?
|
|
455
|
+
deferredCachedVectorHydration = options.skipCachedVectors
|
|
456
|
+
? null
|
|
457
|
+
: deferCachedVectors
|
|
458
|
+
? attachCachedVectors(database, candidates).catch(() => {})
|
|
445
459
|
: null;
|
|
446
460
|
return inserted;
|
|
447
461
|
}
|
|
@@ -450,10 +464,14 @@ export async function hydrateIndex(
|
|
|
450
464
|
* Prepare the lexical index without probing a remote embedding API. Cached
|
|
451
465
|
* vectors attach after the disk cache is ready, outside the startup path.
|
|
452
466
|
*/
|
|
453
|
-
export async function hydrateIndexForStartup(
|
|
467
|
+
export async function hydrateIndexForStartup(
|
|
468
|
+
observations: any[],
|
|
469
|
+
options: Pick<HydrateIndexOptions, 'skipCachedVectors'> = {},
|
|
470
|
+
): Promise<number> {
|
|
454
471
|
return hydrateIndex(observations, {
|
|
455
472
|
allowNetworkProbe: false,
|
|
456
473
|
deferCachedVectors: true,
|
|
474
|
+
skipCachedVectors: options.skipCachedVectors,
|
|
457
475
|
});
|
|
458
476
|
}
|
|
459
477
|
|
|
@@ -1,212 +1,212 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* JSON Persistence Helpers — Migration / Export / Debug Only
|
|
3
|
-
*
|
|
4
|
-
* These functions read/write JSON/JSONL files for one-time migration
|
|
5
|
-
* from legacy storage formats into SQLite, or for export/import and debug.
|
|
6
|
-
*
|
|
7
|
-
* NOT used as runtime canonical store — SQLite is the sole canonical backend.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { promises as fs } from 'node:fs';
|
|
11
|
-
import path from 'node:path';
|
|
12
|
-
import { atomicWriteFile } from './file-lock.js';
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Get the file path for the knowledge graph JSONL file.
|
|
16
|
-
* (MCP-compatible format, same as official Memory Server)
|
|
17
|
-
*/
|
|
18
|
-
export function getGraphFilePath(projectDir: string): string {
|
|
19
|
-
return path.join(projectDir, 'graph.jsonl');
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Save the knowledge graph in JSONL format (MCP-compatible).
|
|
24
|
-
* Each line is a JSON object with type: "entity" or "relation".
|
|
25
|
-
*
|
|
26
|
-
* Format adopted from MCP Official Memory Server.
|
|
27
|
-
*/
|
|
28
|
-
export async function saveGraphJsonl(
|
|
29
|
-
projectDir: string,
|
|
30
|
-
entities: Array<{ name: string; entityType: string; observations: string[] }>,
|
|
31
|
-
relations: Array<{ from: string; to: string; relationType: string }>,
|
|
32
|
-
): Promise<void> {
|
|
33
|
-
const lines = [
|
|
34
|
-
...entities.map((e) =>
|
|
35
|
-
JSON.stringify({ type: 'entity', name: e.name, entityType: e.entityType, observations: e.observations }),
|
|
36
|
-
),
|
|
37
|
-
...relations.map((r) =>
|
|
38
|
-
JSON.stringify({ type: 'relation', from: r.from, to: r.to, relationType: r.relationType }),
|
|
39
|
-
),
|
|
40
|
-
];
|
|
41
|
-
await atomicWriteFile(getGraphFilePath(projectDir), lines.join('\n'));
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Load the knowledge graph from JSONL format.
|
|
46
|
-
*/
|
|
47
|
-
export async function loadGraphJsonl(
|
|
48
|
-
projectDir: string,
|
|
49
|
-
): Promise<{
|
|
50
|
-
entities: Array<{ name: string; entityType: string; observations: string[] }>;
|
|
51
|
-
relations: Array<{ from: string; to: string; relationType: string }>;
|
|
52
|
-
}> {
|
|
53
|
-
const filePath = getGraphFilePath(projectDir);
|
|
54
|
-
try {
|
|
55
|
-
const data = await fs.readFile(filePath, 'utf-8');
|
|
56
|
-
const lines = data.split('\n').filter((line) => line.trim() !== '');
|
|
57
|
-
return lines.reduce(
|
|
58
|
-
(graph, line) => {
|
|
59
|
-
const item = JSON.parse(line);
|
|
60
|
-
if (item.type === 'entity') {
|
|
61
|
-
graph.entities.push({
|
|
62
|
-
name: item.name,
|
|
63
|
-
entityType: item.entityType,
|
|
64
|
-
observations: item.observations,
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
if (item.type === 'relation') {
|
|
68
|
-
graph.relations.push({
|
|
69
|
-
from: item.from,
|
|
70
|
-
to: item.to,
|
|
71
|
-
relationType: item.relationType,
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
return graph;
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
entities: [] as Array<{ name: string; entityType: string; observations: string[] }>,
|
|
78
|
-
relations: [] as Array<{ from: string; to: string; relationType: string }>
|
|
79
|
-
},
|
|
80
|
-
);
|
|
81
|
-
} catch (error) {
|
|
82
|
-
if (error instanceof Error && 'code' in error && (error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
83
|
-
return { entities: [], relations: [] };
|
|
84
|
-
}
|
|
85
|
-
throw error;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Save observation data as JSON (for Orama restore / export).
|
|
91
|
-
*/
|
|
92
|
-
export async function saveObservationsJson(
|
|
93
|
-
projectDir: string,
|
|
94
|
-
observations: unknown[],
|
|
95
|
-
): Promise<void> {
|
|
96
|
-
const filePath = path.join(projectDir, 'observations.json');
|
|
97
|
-
await atomicWriteFile(filePath, JSON.stringify(observations, null, 2));
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Load observation data from JSON.
|
|
102
|
-
*/
|
|
103
|
-
export async function loadObservationsJson(projectDir: string): Promise<unknown[]> {
|
|
104
|
-
const filePath = path.join(projectDir, 'observations.json');
|
|
105
|
-
try {
|
|
106
|
-
const data = await fs.readFile(filePath, 'utf-8');
|
|
107
|
-
return JSON.parse(data);
|
|
108
|
-
} catch (error) {
|
|
109
|
-
if (error instanceof Error && 'code' in error && (error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
110
|
-
return [];
|
|
111
|
-
}
|
|
112
|
-
throw error;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Save the next observation ID counter (legacy JSON format).
|
|
118
|
-
*/
|
|
119
|
-
export async function saveIdCounter(projectDir: string, nextId: number): Promise<void> {
|
|
120
|
-
const filePath = path.join(projectDir, 'counter.json');
|
|
121
|
-
await atomicWriteFile(filePath, JSON.stringify({ nextId }));
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Load the next observation ID counter (legacy JSON format).
|
|
126
|
-
* For runtime use, prefer the SQLite meta table via SqliteBackend.
|
|
127
|
-
*/
|
|
128
|
-
export async function loadIdCounter(projectDir: string): Promise<number> {
|
|
129
|
-
const filePath = path.join(projectDir, 'counter.json');
|
|
130
|
-
try {
|
|
131
|
-
const data = await fs.readFile(filePath, 'utf-8');
|
|
132
|
-
return JSON.parse(data).nextId ?? 1;
|
|
133
|
-
} catch {
|
|
134
|
-
return 1;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Save mini-skills data as JSON (migration source only).
|
|
140
|
-
*/
|
|
141
|
-
export async function saveMiniSkillsJson(
|
|
142
|
-
projectDir: string,
|
|
143
|
-
skills: unknown[],
|
|
144
|
-
): Promise<void> {
|
|
145
|
-
const filePath = path.join(projectDir, 'mini-skills.json');
|
|
146
|
-
await atomicWriteFile(filePath, JSON.stringify(skills, null, 2));
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Load mini-skills data from JSON (migration source only).
|
|
151
|
-
*/
|
|
152
|
-
export async function loadMiniSkillsJson(projectDir: string): Promise<unknown[]> {
|
|
153
|
-
const filePath = path.join(projectDir, 'mini-skills.json');
|
|
154
|
-
try {
|
|
155
|
-
const data = await fs.readFile(filePath, 'utf-8');
|
|
156
|
-
const parsed = JSON.parse(data);
|
|
157
|
-
return Array.isArray(parsed) ? parsed : [];
|
|
158
|
-
} catch (error) {
|
|
159
|
-
if (error instanceof Error && 'code' in error && (error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
160
|
-
return [];
|
|
161
|
-
}
|
|
162
|
-
throw error;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Load the mini-skills ID counter (legacy JSON format).
|
|
168
|
-
*/
|
|
169
|
-
export async function loadMiniSkillsCounter(projectDir: string): Promise<number> {
|
|
170
|
-
const filePath = path.join(projectDir, 'mini-skills-counter.json');
|
|
171
|
-
try {
|
|
172
|
-
const data = await fs.readFile(filePath, 'utf-8');
|
|
173
|
-
return JSON.parse(data).nextId ?? 1;
|
|
174
|
-
} catch {
|
|
175
|
-
return 1;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Save the mini-skills ID counter (legacy JSON format).
|
|
181
|
-
*/
|
|
182
|
-
export async function saveMiniSkillsCounter(projectDir: string, nextId: number): Promise<void> {
|
|
183
|
-
const filePath = path.join(projectDir, 'mini-skills-counter.json');
|
|
184
|
-
await atomicWriteFile(filePath, JSON.stringify({ nextId }));
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Save sessions data as JSON (migration source only).
|
|
189
|
-
*/
|
|
190
|
-
export async function saveSessionsJson(
|
|
191
|
-
projectDir: string,
|
|
192
|
-
sessions: unknown[],
|
|
193
|
-
): Promise<void> {
|
|
194
|
-
const filePath = path.join(projectDir, 'sessions.json');
|
|
195
|
-
await atomicWriteFile(filePath, JSON.stringify(sessions, null, 2));
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Load sessions data from JSON (migration source only).
|
|
200
|
-
*/
|
|
201
|
-
export async function loadSessionsJson(projectDir: string): Promise<unknown[]> {
|
|
202
|
-
const filePath = path.join(projectDir, 'sessions.json');
|
|
203
|
-
try {
|
|
204
|
-
const data = await fs.readFile(filePath, 'utf-8');
|
|
205
|
-
return JSON.parse(data);
|
|
206
|
-
} catch (error) {
|
|
207
|
-
if (error instanceof Error && 'code' in error && (error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
208
|
-
return [];
|
|
209
|
-
}
|
|
210
|
-
throw error;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* JSON Persistence Helpers — Migration / Export / Debug Only
|
|
3
|
+
*
|
|
4
|
+
* These functions read/write JSON/JSONL files for one-time migration
|
|
5
|
+
* from legacy storage formats into SQLite, or for export/import and debug.
|
|
6
|
+
*
|
|
7
|
+
* NOT used as runtime canonical store — SQLite is the sole canonical backend.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { promises as fs } from 'node:fs';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { atomicWriteFile } from './file-lock.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get the file path for the knowledge graph JSONL file.
|
|
16
|
+
* (MCP-compatible format, same as official Memory Server)
|
|
17
|
+
*/
|
|
18
|
+
export function getGraphFilePath(projectDir: string): string {
|
|
19
|
+
return path.join(projectDir, 'graph.jsonl');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Save the knowledge graph in JSONL format (MCP-compatible).
|
|
24
|
+
* Each line is a JSON object with type: "entity" or "relation".
|
|
25
|
+
*
|
|
26
|
+
* Format adopted from MCP Official Memory Server.
|
|
27
|
+
*/
|
|
28
|
+
export async function saveGraphJsonl(
|
|
29
|
+
projectDir: string,
|
|
30
|
+
entities: Array<{ name: string; entityType: string; observations: string[] }>,
|
|
31
|
+
relations: Array<{ from: string; to: string; relationType: string }>,
|
|
32
|
+
): Promise<void> {
|
|
33
|
+
const lines = [
|
|
34
|
+
...entities.map((e) =>
|
|
35
|
+
JSON.stringify({ type: 'entity', name: e.name, entityType: e.entityType, observations: e.observations }),
|
|
36
|
+
),
|
|
37
|
+
...relations.map((r) =>
|
|
38
|
+
JSON.stringify({ type: 'relation', from: r.from, to: r.to, relationType: r.relationType }),
|
|
39
|
+
),
|
|
40
|
+
];
|
|
41
|
+
await atomicWriteFile(getGraphFilePath(projectDir), lines.join('\n'));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Load the knowledge graph from JSONL format.
|
|
46
|
+
*/
|
|
47
|
+
export async function loadGraphJsonl(
|
|
48
|
+
projectDir: string,
|
|
49
|
+
): Promise<{
|
|
50
|
+
entities: Array<{ name: string; entityType: string; observations: string[] }>;
|
|
51
|
+
relations: Array<{ from: string; to: string; relationType: string }>;
|
|
52
|
+
}> {
|
|
53
|
+
const filePath = getGraphFilePath(projectDir);
|
|
54
|
+
try {
|
|
55
|
+
const data = await fs.readFile(filePath, 'utf-8');
|
|
56
|
+
const lines = data.split('\n').filter((line) => line.trim() !== '');
|
|
57
|
+
return lines.reduce(
|
|
58
|
+
(graph, line) => {
|
|
59
|
+
const item = JSON.parse(line);
|
|
60
|
+
if (item.type === 'entity') {
|
|
61
|
+
graph.entities.push({
|
|
62
|
+
name: item.name,
|
|
63
|
+
entityType: item.entityType,
|
|
64
|
+
observations: item.observations,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
if (item.type === 'relation') {
|
|
68
|
+
graph.relations.push({
|
|
69
|
+
from: item.from,
|
|
70
|
+
to: item.to,
|
|
71
|
+
relationType: item.relationType,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
return graph;
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
entities: [] as Array<{ name: string; entityType: string; observations: string[] }>,
|
|
78
|
+
relations: [] as Array<{ from: string; to: string; relationType: string }>
|
|
79
|
+
},
|
|
80
|
+
);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
if (error instanceof Error && 'code' in error && (error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
83
|
+
return { entities: [], relations: [] };
|
|
84
|
+
}
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Save observation data as JSON (for Orama restore / export).
|
|
91
|
+
*/
|
|
92
|
+
export async function saveObservationsJson(
|
|
93
|
+
projectDir: string,
|
|
94
|
+
observations: unknown[],
|
|
95
|
+
): Promise<void> {
|
|
96
|
+
const filePath = path.join(projectDir, 'observations.json');
|
|
97
|
+
await atomicWriteFile(filePath, JSON.stringify(observations, null, 2));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Load observation data from JSON.
|
|
102
|
+
*/
|
|
103
|
+
export async function loadObservationsJson(projectDir: string): Promise<unknown[]> {
|
|
104
|
+
const filePath = path.join(projectDir, 'observations.json');
|
|
105
|
+
try {
|
|
106
|
+
const data = await fs.readFile(filePath, 'utf-8');
|
|
107
|
+
return JSON.parse(data);
|
|
108
|
+
} catch (error) {
|
|
109
|
+
if (error instanceof Error && 'code' in error && (error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Save the next observation ID counter (legacy JSON format).
|
|
118
|
+
*/
|
|
119
|
+
export async function saveIdCounter(projectDir: string, nextId: number): Promise<void> {
|
|
120
|
+
const filePath = path.join(projectDir, 'counter.json');
|
|
121
|
+
await atomicWriteFile(filePath, JSON.stringify({ nextId }));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Load the next observation ID counter (legacy JSON format).
|
|
126
|
+
* For runtime use, prefer the SQLite meta table via SqliteBackend.
|
|
127
|
+
*/
|
|
128
|
+
export async function loadIdCounter(projectDir: string): Promise<number> {
|
|
129
|
+
const filePath = path.join(projectDir, 'counter.json');
|
|
130
|
+
try {
|
|
131
|
+
const data = await fs.readFile(filePath, 'utf-8');
|
|
132
|
+
return JSON.parse(data).nextId ?? 1;
|
|
133
|
+
} catch {
|
|
134
|
+
return 1;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Save mini-skills data as JSON (migration source only).
|
|
140
|
+
*/
|
|
141
|
+
export async function saveMiniSkillsJson(
|
|
142
|
+
projectDir: string,
|
|
143
|
+
skills: unknown[],
|
|
144
|
+
): Promise<void> {
|
|
145
|
+
const filePath = path.join(projectDir, 'mini-skills.json');
|
|
146
|
+
await atomicWriteFile(filePath, JSON.stringify(skills, null, 2));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Load mini-skills data from JSON (migration source only).
|
|
151
|
+
*/
|
|
152
|
+
export async function loadMiniSkillsJson(projectDir: string): Promise<unknown[]> {
|
|
153
|
+
const filePath = path.join(projectDir, 'mini-skills.json');
|
|
154
|
+
try {
|
|
155
|
+
const data = await fs.readFile(filePath, 'utf-8');
|
|
156
|
+
const parsed = JSON.parse(data);
|
|
157
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
158
|
+
} catch (error) {
|
|
159
|
+
if (error instanceof Error && 'code' in error && (error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
160
|
+
return [];
|
|
161
|
+
}
|
|
162
|
+
throw error;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Load the mini-skills ID counter (legacy JSON format).
|
|
168
|
+
*/
|
|
169
|
+
export async function loadMiniSkillsCounter(projectDir: string): Promise<number> {
|
|
170
|
+
const filePath = path.join(projectDir, 'mini-skills-counter.json');
|
|
171
|
+
try {
|
|
172
|
+
const data = await fs.readFile(filePath, 'utf-8');
|
|
173
|
+
return JSON.parse(data).nextId ?? 1;
|
|
174
|
+
} catch {
|
|
175
|
+
return 1;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Save the mini-skills ID counter (legacy JSON format).
|
|
181
|
+
*/
|
|
182
|
+
export async function saveMiniSkillsCounter(projectDir: string, nextId: number): Promise<void> {
|
|
183
|
+
const filePath = path.join(projectDir, 'mini-skills-counter.json');
|
|
184
|
+
await atomicWriteFile(filePath, JSON.stringify({ nextId }));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Save sessions data as JSON (migration source only).
|
|
189
|
+
*/
|
|
190
|
+
export async function saveSessionsJson(
|
|
191
|
+
projectDir: string,
|
|
192
|
+
sessions: unknown[],
|
|
193
|
+
): Promise<void> {
|
|
194
|
+
const filePath = path.join(projectDir, 'sessions.json');
|
|
195
|
+
await atomicWriteFile(filePath, JSON.stringify(sessions, null, 2));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Load sessions data from JSON (migration source only).
|
|
200
|
+
*/
|
|
201
|
+
export async function loadSessionsJson(projectDir: string): Promise<unknown[]> {
|
|
202
|
+
const filePath = path.join(projectDir, 'sessions.json');
|
|
203
|
+
try {
|
|
204
|
+
const data = await fs.readFile(filePath, 'utf-8');
|
|
205
|
+
return JSON.parse(data);
|
|
206
|
+
} catch (error) {
|
|
207
|
+
if (error instanceof Error && 'code' in error && (error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
208
|
+
return [];
|
|
209
|
+
}
|
|
210
|
+
throw error;
|
|
211
|
+
}
|
|
212
|
+
}
|