memorix 0.9.4 → 0.9.6
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 +11 -0
- package/dist/cli/index.js +133 -80
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +133 -80
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.9.6] — 2026-02-25
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- **Cross-IDE project identity fragmentation** — Data was stored in per-project subdirectories (`~/.memorix/data/<projectId>/`), but different IDEs often detected different projectIds for the same repo (e.g. `placeholder/repo` vs `local/repo` vs `local/Kiro`). This caused observations to silently split across directories, making cross-IDE relay unreliable. Now **all data is stored in a single flat directory** (`~/.memorix/data/`). projectId is metadata only, not used for directory partitioning. Existing per-project subdirectories are automatically merged on first startup (IDs remapped, graphs deduplicated, subdirs backed up to `.migrated-subdirs/`).
|
|
9
|
+
- **`scope: 'project'` parameter now works** — Previously accepted but ignored. Now properly filters search results by the current project's ID via Orama where-clause.
|
|
10
|
+
|
|
11
|
+
## [0.9.5] — 2026-02-25
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- **Claude Code hooks `matcher` format** — `matcher` must be a **string** (tool name pattern like `"Bash"`, `"Edit|Write"`), not an object. For hooks that should fire on ALL events, `matcher` is now omitted entirely instead of using `{}`. Fixes `matcher: Expected string, but received object` validation error on Claude Code startup.
|
|
15
|
+
|
|
5
16
|
## [0.9.4] — 2026-02-25
|
|
6
17
|
|
|
7
18
|
### Fixed
|
package/dist/cli/index.js
CHANGED
|
@@ -112,7 +112,7 @@ __export(persistence_exports, {
|
|
|
112
112
|
loadIdCounter: () => loadIdCounter,
|
|
113
113
|
loadObservationsJson: () => loadObservationsJson,
|
|
114
114
|
loadSessionsJson: () => loadSessionsJson,
|
|
115
|
-
|
|
115
|
+
migrateSubdirsToFlat: () => migrateSubdirsToFlat,
|
|
116
116
|
saveGraphJsonl: () => saveGraphJsonl,
|
|
117
117
|
saveIdCounter: () => saveIdCounter,
|
|
118
118
|
saveObservationsJson: () => saveObservationsJson,
|
|
@@ -121,18 +121,13 @@ __export(persistence_exports, {
|
|
|
121
121
|
import { promises as fs2 } from "fs";
|
|
122
122
|
import path3 from "path";
|
|
123
123
|
import os from "os";
|
|
124
|
-
function
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
async function getProjectDataDir(projectId, baseDir) {
|
|
128
|
-
if (projectId === "__invalid__") {
|
|
124
|
+
async function getProjectDataDir(_projectId, baseDir) {
|
|
125
|
+
if (_projectId === "__invalid__") {
|
|
129
126
|
throw new Error("Cannot create data directory for invalid project");
|
|
130
127
|
}
|
|
131
128
|
const base = baseDir ?? DEFAULT_DATA_DIR;
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
await fs2.mkdir(dataDir, { recursive: true });
|
|
135
|
-
return dataDir;
|
|
129
|
+
await fs2.mkdir(base, { recursive: true });
|
|
130
|
+
return base;
|
|
136
131
|
}
|
|
137
132
|
function getBaseDataDir(baseDir) {
|
|
138
133
|
return baseDir ?? DEFAULT_DATA_DIR;
|
|
@@ -146,78 +141,141 @@ async function listProjectDirs(baseDir) {
|
|
|
146
141
|
return [];
|
|
147
142
|
}
|
|
148
143
|
}
|
|
149
|
-
async function
|
|
144
|
+
async function migrateSubdirsToFlat(baseDir) {
|
|
150
145
|
const base = baseDir ?? DEFAULT_DATA_DIR;
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
let sourceObsPath = null;
|
|
146
|
+
await fs2.mkdir(base, { recursive: true });
|
|
147
|
+
let entries;
|
|
154
148
|
try {
|
|
155
|
-
await fs2.
|
|
156
|
-
sourceObsPath = globalObsPath;
|
|
149
|
+
entries = await fs2.readdir(base, { withFileTypes: true });
|
|
157
150
|
} catch {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
const dataDirs = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => path3.join(base, e.name));
|
|
154
|
+
if (dataDirs.length === 0) return false;
|
|
155
|
+
const subdirData = [];
|
|
156
|
+
for (const dir of dataDirs) {
|
|
157
|
+
const obsPath = path3.join(dir, "observations.json");
|
|
158
158
|
try {
|
|
159
|
-
await fs2.
|
|
160
|
-
|
|
159
|
+
const data = await fs2.readFile(obsPath, "utf-8");
|
|
160
|
+
const obs = JSON.parse(data);
|
|
161
|
+
if (Array.isArray(obs) && obs.length > 0) {
|
|
162
|
+
let graph = { entities: [], relations: [] };
|
|
163
|
+
try {
|
|
164
|
+
const graphData = await fs2.readFile(path3.join(dir, "graph.jsonl"), "utf-8");
|
|
165
|
+
const lines = graphData.split("\n").filter((l) => l.trim());
|
|
166
|
+
for (const line of lines) {
|
|
167
|
+
const item = JSON.parse(line);
|
|
168
|
+
if (item.type === "entity") graph.entities.push(item);
|
|
169
|
+
if (item.type === "relation") graph.relations.push(item);
|
|
170
|
+
}
|
|
171
|
+
} catch {
|
|
172
|
+
}
|
|
173
|
+
subdirData.push({ dir, obs, graph });
|
|
174
|
+
}
|
|
161
175
|
} catch {
|
|
162
|
-
return false;
|
|
163
176
|
}
|
|
164
177
|
}
|
|
165
|
-
|
|
178
|
+
if (subdirData.length === 0) return false;
|
|
179
|
+
let baseObs = [];
|
|
166
180
|
try {
|
|
167
|
-
const data = await fs2.readFile(
|
|
168
|
-
|
|
169
|
-
if (!Array.isArray(
|
|
181
|
+
const data = await fs2.readFile(path3.join(base, "observations.json"), "utf-8");
|
|
182
|
+
baseObs = JSON.parse(data);
|
|
183
|
+
if (!Array.isArray(baseObs)) baseObs = [];
|
|
170
184
|
} catch {
|
|
171
|
-
return false;
|
|
172
185
|
}
|
|
173
|
-
|
|
174
|
-
const projectObsPath = path3.join(projectDir2, "observations.json");
|
|
175
|
-
let projectObs = [];
|
|
186
|
+
let baseGraph = { entities: [], relations: [] };
|
|
176
187
|
try {
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
188
|
+
const graphData = await fs2.readFile(path3.join(base, "graph.jsonl"), "utf-8");
|
|
189
|
+
const lines = graphData.split("\n").filter((l) => l.trim());
|
|
190
|
+
for (const line of lines) {
|
|
191
|
+
const item = JSON.parse(line);
|
|
192
|
+
if (item.type === "entity") baseGraph.entities.push(item);
|
|
193
|
+
if (item.type === "relation") baseGraph.relations.push(item);
|
|
194
|
+
}
|
|
180
195
|
} catch {
|
|
181
196
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
197
|
+
const allObs = [...baseObs];
|
|
198
|
+
for (const { obs } of subdirData) {
|
|
199
|
+
for (const o of obs) {
|
|
200
|
+
const isDuplicate = allObs.some(
|
|
201
|
+
(existing) => existing.title === o.title && existing.createdAt === o.createdAt
|
|
202
|
+
);
|
|
203
|
+
if (!isDuplicate) {
|
|
204
|
+
allObs.push(o);
|
|
205
|
+
}
|
|
190
206
|
}
|
|
191
207
|
}
|
|
192
|
-
|
|
193
|
-
for (
|
|
194
|
-
|
|
208
|
+
allObs.sort((a, b) => (a.createdAt || "").localeCompare(b.createdAt || ""));
|
|
209
|
+
for (let i = 0; i < allObs.length; i++) {
|
|
210
|
+
allObs[i].id = i + 1;
|
|
195
211
|
}
|
|
196
|
-
|
|
197
|
-
for (const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
} catch {
|
|
212
|
+
const entityMap = /* @__PURE__ */ new Map();
|
|
213
|
+
for (const e of baseGraph.entities) entityMap.set(e.name, e);
|
|
214
|
+
for (const { graph } of subdirData) {
|
|
215
|
+
for (const e of graph.entities) {
|
|
216
|
+
if (!entityMap.has(e.name)) {
|
|
217
|
+
entityMap.set(e.name, e);
|
|
218
|
+
} else {
|
|
219
|
+
const existing = entityMap.get(e.name);
|
|
220
|
+
const obsSet = /* @__PURE__ */ new Set([...existing.observations || [], ...e.observations || []]);
|
|
221
|
+
existing.observations = [...obsSet];
|
|
207
222
|
}
|
|
208
223
|
}
|
|
209
224
|
}
|
|
210
|
-
const
|
|
225
|
+
const relationSet = /* @__PURE__ */ new Set();
|
|
226
|
+
const mergedRelations = [];
|
|
227
|
+
for (const rel of [...baseGraph.relations, ...subdirData.flatMap((d) => d.graph.relations)]) {
|
|
228
|
+
const key = `${rel.from}|${rel.to}|${rel.relationType}`;
|
|
229
|
+
if (!relationSet.has(key)) {
|
|
230
|
+
relationSet.add(key);
|
|
231
|
+
mergedRelations.push(rel);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
await fs2.writeFile(path3.join(base, "observations.json"), JSON.stringify(allObs, null, 2), "utf-8");
|
|
211
235
|
await fs2.writeFile(
|
|
212
|
-
path3.join(
|
|
213
|
-
JSON.stringify({ nextId:
|
|
236
|
+
path3.join(base, "counter.json"),
|
|
237
|
+
JSON.stringify({ nextId: allObs.length + 1 }),
|
|
214
238
|
"utf-8"
|
|
215
239
|
);
|
|
216
|
-
|
|
217
|
-
|
|
240
|
+
const graphLines = [
|
|
241
|
+
...[...entityMap.values()].map((e) => JSON.stringify({ type: "entity", name: e.name, entityType: e.entityType, observations: e.observations })),
|
|
242
|
+
...mergedRelations.map((r) => JSON.stringify({ type: "relation", from: r.from, to: r.to, relationType: r.relationType }))
|
|
243
|
+
];
|
|
244
|
+
if (graphLines.length > 0) {
|
|
245
|
+
await fs2.writeFile(path3.join(base, "graph.jsonl"), graphLines.join("\n"), "utf-8");
|
|
246
|
+
}
|
|
247
|
+
let allSessions = [];
|
|
248
|
+
try {
|
|
249
|
+
const data = await fs2.readFile(path3.join(base, "sessions.json"), "utf-8");
|
|
250
|
+
allSessions = JSON.parse(data);
|
|
251
|
+
if (!Array.isArray(allSessions)) allSessions = [];
|
|
252
|
+
} catch {
|
|
253
|
+
}
|
|
254
|
+
for (const { dir } of subdirData) {
|
|
218
255
|
try {
|
|
219
|
-
await fs2.
|
|
220
|
-
|
|
256
|
+
const data = await fs2.readFile(path3.join(dir, "sessions.json"), "utf-8");
|
|
257
|
+
const sessions = JSON.parse(data);
|
|
258
|
+
if (Array.isArray(sessions)) allSessions.push(...sessions);
|
|
259
|
+
} catch {
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (allSessions.length > 0) {
|
|
263
|
+
await fs2.writeFile(path3.join(base, "sessions.json"), JSON.stringify(allSessions, null, 2), "utf-8");
|
|
264
|
+
}
|
|
265
|
+
const backupDir = path3.join(base, ".migrated-subdirs");
|
|
266
|
+
await fs2.mkdir(backupDir, { recursive: true });
|
|
267
|
+
for (const { dir } of subdirData) {
|
|
268
|
+
const dirName = path3.basename(dir);
|
|
269
|
+
try {
|
|
270
|
+
await fs2.rename(dir, path3.join(backupDir, dirName));
|
|
271
|
+
} catch {
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
for (const dir of dataDirs) {
|
|
275
|
+
const dirName = path3.basename(dir);
|
|
276
|
+
try {
|
|
277
|
+
await fs2.access(dir);
|
|
278
|
+
await fs2.rename(dir, path3.join(backupDir, dirName));
|
|
221
279
|
} catch {
|
|
222
280
|
}
|
|
223
281
|
}
|
|
@@ -3700,23 +3758,18 @@ function resolveHookCommand() {
|
|
|
3700
3758
|
}
|
|
3701
3759
|
function generateClaudeConfig() {
|
|
3702
3760
|
const cmd = `${resolveHookCommand()} hook`;
|
|
3703
|
-
const
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
type: "command",
|
|
3708
|
-
command: cmd,
|
|
3709
|
-
timeout: 10
|
|
3710
|
-
}
|
|
3711
|
-
]
|
|
3761
|
+
const hookEntry = {
|
|
3762
|
+
type: "command",
|
|
3763
|
+
command: cmd,
|
|
3764
|
+
timeout: 10
|
|
3712
3765
|
};
|
|
3713
3766
|
return {
|
|
3714
3767
|
hooks: {
|
|
3715
|
-
SessionStart: [
|
|
3716
|
-
PostToolUse: [
|
|
3717
|
-
UserPromptSubmit: [
|
|
3718
|
-
PreCompact: [
|
|
3719
|
-
Stop: [
|
|
3768
|
+
SessionStart: [{ hooks: [hookEntry] }],
|
|
3769
|
+
PostToolUse: [{ hooks: [hookEntry] }],
|
|
3770
|
+
UserPromptSubmit: [{ hooks: [hookEntry] }],
|
|
3771
|
+
PreCompact: [{ hooks: [hookEntry] }],
|
|
3772
|
+
Stop: [{ hooks: [hookEntry] }]
|
|
3720
3773
|
}
|
|
3721
3774
|
};
|
|
3722
3775
|
}
|
|
@@ -5389,10 +5442,10 @@ async function createMemorixServer(cwd, existingServer) {
|
|
|
5389
5442
|
);
|
|
5390
5443
|
}
|
|
5391
5444
|
try {
|
|
5392
|
-
const {
|
|
5393
|
-
const migrated = await
|
|
5445
|
+
const { migrateSubdirsToFlat: migrateSubdirsToFlat2 } = await Promise.resolve().then(() => (init_persistence(), persistence_exports));
|
|
5446
|
+
const migrated = await migrateSubdirsToFlat2();
|
|
5394
5447
|
if (migrated) {
|
|
5395
|
-
console.error(`[memorix] Migrated
|
|
5448
|
+
console.error(`[memorix] Migrated per-project subdirectories into flat storage`);
|
|
5396
5449
|
}
|
|
5397
5450
|
} catch {
|
|
5398
5451
|
}
|
|
@@ -5615,10 +5668,10 @@ Use this as the \`topicKey\` parameter in \`memorix_store\` to enable upsert beh
|
|
|
5615
5668
|
maxTokens: safeMaxTokens,
|
|
5616
5669
|
since,
|
|
5617
5670
|
until,
|
|
5618
|
-
//
|
|
5619
|
-
//
|
|
5620
|
-
//
|
|
5621
|
-
projectId: void 0
|
|
5671
|
+
// All data is in a single flat directory. projectId is metadata only.
|
|
5672
|
+
// scope: 'project' → filter by current projectId in Orama
|
|
5673
|
+
// scope: 'global' or omitted → search all observations (default)
|
|
5674
|
+
projectId: scope === "project" ? project.id : void 0
|
|
5622
5675
|
});
|
|
5623
5676
|
let text = result.formatted;
|
|
5624
5677
|
if (!syncAdvisoryShown && syncAdvisory) {
|