@remnic/plugin-openclaw 1.0.7 → 1.0.8
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/dist/{causal-consolidation-S6M7UTZG.js → causal-consolidation-33R5JTPX.js} +7 -6
- package/dist/chunk-5ZW5XJQ6.js +125 -0
- package/dist/chunk-6OJAU466.js +148 -0
- package/dist/{chunk-QHMR3D7U.js → chunk-BQLPVRIU.js} +109 -2
- package/dist/{chunk-KPMXWORS.js → chunk-JJSNPSCD.js} +608 -354
- package/dist/{chunk-SVGN3ACY.js → chunk-PFH73PN6.js} +2 -2
- package/dist/consolidation-undo-5ZSX4MWO.js +426 -0
- package/dist/{contradiction-scan-LRRLWUOS.js → contradiction-scan-U3QKHWQN.js} +42 -6
- package/dist/{engine-WGNTTFYE.js → engine-M5G6ZJU7.js} +3 -2
- package/dist/extraction-judge-telemetry-GHOTVYMP.js +14 -0
- package/dist/index.js +5979 -1220
- package/dist/{storage-BA6OBLMK.js → storage-DM4ZGOCN.js} +2 -1
- package/openclaw.plugin.json +259 -14
- package/package.json +2 -2
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createVersion
|
|
3
|
+
} from "./chunk-6OJAU466.js";
|
|
1
4
|
import {
|
|
2
5
|
log
|
|
3
6
|
} from "./chunk-UFU5GGGA.js";
|
|
4
7
|
|
|
5
8
|
// ../remnic-core/src/storage.ts
|
|
6
|
-
import { access
|
|
9
|
+
import { access, readdir, readFile as readFile2, stat as stat2, writeFile as writeFile2, mkdir as mkdir2, unlink, rename, appendFile } from "fs/promises";
|
|
7
10
|
import { appendFileSync, mkdirSync, statSync } from "fs";
|
|
8
|
-
import { createHash
|
|
9
|
-
import
|
|
11
|
+
import { createHash } from "crypto";
|
|
12
|
+
import path4 from "path";
|
|
10
13
|
|
|
11
14
|
// ../remnic-core/src/memory-cache.ts
|
|
12
15
|
var entityCacheByDir = /* @__PURE__ */ new Map();
|
|
@@ -173,128 +176,24 @@ function sanitizeMemoryContent(text) {
|
|
|
173
176
|
};
|
|
174
177
|
}
|
|
175
178
|
|
|
176
|
-
// ../remnic-core/src/
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const prev = writeLocks.get(pageKey) ?? Promise.resolve();
|
|
195
|
-
const next = prev.then(fn, fn);
|
|
196
|
-
writeLocks.set(pageKey, next.then(() => {
|
|
197
|
-
}, () => {
|
|
198
|
-
}));
|
|
199
|
-
return next;
|
|
200
|
-
}
|
|
201
|
-
function contentHash(content) {
|
|
202
|
-
return createHash("sha256").update(content, "utf-8").digest("hex");
|
|
203
|
-
}
|
|
204
|
-
function sidecarKey(pagePath) {
|
|
205
|
-
const withoutExt = pagePath.replace(/\.md$/i, "");
|
|
206
|
-
return withoutExt.replace(/[\\/]/g, "__");
|
|
207
|
-
}
|
|
208
|
-
function sidecarDir(memoryDir, sidecar, pagePath) {
|
|
209
|
-
return path2.join(memoryDir, sidecar, sidecarKey(pagePath));
|
|
210
|
-
}
|
|
211
|
-
function manifestPath(memoryDir, sidecar, pagePath) {
|
|
212
|
-
return path2.join(sidecarDir(memoryDir, sidecar, pagePath), "manifest.json");
|
|
213
|
-
}
|
|
214
|
-
async function readManifest(memoryDir, sidecar, pagePath) {
|
|
215
|
-
const mp = manifestPath(memoryDir, sidecar, pagePath);
|
|
216
|
-
try {
|
|
217
|
-
const raw = await readFile2(mp, "utf-8");
|
|
218
|
-
const parsed = JSON.parse(raw);
|
|
219
|
-
if (typeof parsed !== "object" || parsed === null) {
|
|
220
|
-
return { pagePath, versions: [], currentVersion: "0" };
|
|
221
|
-
}
|
|
222
|
-
const obj = parsed;
|
|
223
|
-
const versions = Array.isArray(obj.versions) ? obj.versions : [];
|
|
224
|
-
const currentVersion = typeof obj.currentVersion === "string" ? obj.currentVersion : "0";
|
|
225
|
-
return { pagePath: typeof obj.pagePath === "string" ? obj.pagePath : pagePath, versions, currentVersion };
|
|
226
|
-
} catch {
|
|
227
|
-
return { pagePath, versions: [], currentVersion: "0" };
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
async function writeManifest(memoryDir, sidecar, pagePath, history) {
|
|
231
|
-
const dir = sidecarDir(memoryDir, sidecar, pagePath);
|
|
232
|
-
await mkdir2(dir, { recursive: true });
|
|
233
|
-
const mp = manifestPath(memoryDir, sidecar, pagePath);
|
|
234
|
-
await writeFile2(mp, JSON.stringify(history, null, 2) + "\n", "utf-8");
|
|
235
|
-
}
|
|
236
|
-
async function createVersion(pagePath, content, trigger, config, log2 = NOOP_LOGGER, note, memoryDir) {
|
|
237
|
-
const { sidecarDir: sidecar, maxVersionsPerPage } = config;
|
|
238
|
-
const resolvedMemoryDir = memoryDir ?? resolveMemoryDir(pagePath);
|
|
239
|
-
const mPath = manifestPath(resolvedMemoryDir, sidecar, relPath(pagePath, resolvedMemoryDir));
|
|
240
|
-
return withPageLock(mPath, async () => {
|
|
241
|
-
const history = await readManifest(resolvedMemoryDir, sidecar, relPath(pagePath, resolvedMemoryDir));
|
|
242
|
-
const nextId = String(history.versions.length > 0 ? Math.max(...history.versions.map((v) => Number(v.versionId))) + 1 : 1);
|
|
243
|
-
const hash = contentHash(content);
|
|
244
|
-
const version = {
|
|
245
|
-
versionId: nextId,
|
|
246
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
247
|
-
contentHash: hash,
|
|
248
|
-
sizeBytes: Buffer.byteLength(content, "utf-8"),
|
|
249
|
-
trigger,
|
|
250
|
-
...note !== void 0 ? { note } : {}
|
|
251
|
-
};
|
|
252
|
-
const dir = sidecarDir(resolvedMemoryDir, sidecar, relPath(pagePath, resolvedMemoryDir));
|
|
253
|
-
await mkdir2(dir, { recursive: true });
|
|
254
|
-
const ext = path2.extname(pagePath) || ".md";
|
|
255
|
-
const snapshotPath = path2.join(dir, `${nextId}${ext}`);
|
|
256
|
-
await writeFile2(snapshotPath, content, "utf-8");
|
|
257
|
-
history.versions.push(version);
|
|
258
|
-
history.currentVersion = nextId;
|
|
259
|
-
if (maxVersionsPerPage > 0 && history.versions.length > maxVersionsPerPage) {
|
|
260
|
-
const toRemove = history.versions.splice(0, history.versions.length - maxVersionsPerPage);
|
|
261
|
-
for (const old of toRemove) {
|
|
262
|
-
const oldPath = path2.join(dir, `${old.versionId}${ext}`);
|
|
263
|
-
try {
|
|
264
|
-
await unlink(oldPath);
|
|
265
|
-
} catch {
|
|
266
|
-
log2.debug(`page-versioning: could not remove old snapshot ${oldPath}`);
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
await writeManifest(resolvedMemoryDir, sidecar, relPath(pagePath, resolvedMemoryDir), history);
|
|
271
|
-
log2.debug(`page-versioning: created version ${nextId} for ${pagePath} (trigger=${trigger})`);
|
|
272
|
-
return version;
|
|
273
|
-
});
|
|
274
|
-
}
|
|
275
|
-
function resolveMemoryDir(pagePath) {
|
|
276
|
-
const knownSubdirs = /* @__PURE__ */ new Set([
|
|
277
|
-
"facts",
|
|
278
|
-
"corrections",
|
|
279
|
-
"entities",
|
|
280
|
-
"state",
|
|
281
|
-
"artifacts",
|
|
282
|
-
"questions",
|
|
283
|
-
"profiles"
|
|
284
|
-
]);
|
|
285
|
-
let dir = path2.dirname(pagePath);
|
|
286
|
-
for (let depth = 0; depth < 5; depth++) {
|
|
287
|
-
const base = path2.basename(dir);
|
|
288
|
-
if (knownSubdirs.has(base) || /^\d{4}-\d{2}-\d{2}$/.test(base)) {
|
|
289
|
-
dir = path2.dirname(dir);
|
|
290
|
-
} else {
|
|
291
|
-
break;
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
return dir;
|
|
295
|
-
}
|
|
296
|
-
function relPath(pagePath, memoryDir) {
|
|
297
|
-
return path2.relative(memoryDir, pagePath);
|
|
179
|
+
// ../remnic-core/src/consolidation-operator.ts
|
|
180
|
+
var CONSOLIDATION_OPERATORS = [
|
|
181
|
+
"split",
|
|
182
|
+
"merge",
|
|
183
|
+
"update"
|
|
184
|
+
];
|
|
185
|
+
var DERIVED_FROM_ENTRY_RE = /^(.+):(\d+)$/;
|
|
186
|
+
function isValidDerivedFromEntry(entry) {
|
|
187
|
+
if (typeof entry !== "string") return false;
|
|
188
|
+
const match = entry.match(DERIVED_FROM_ENTRY_RE);
|
|
189
|
+
if (!match) return false;
|
|
190
|
+
const pathPart = match[1];
|
|
191
|
+
if (pathPart.length === 0 || pathPart.trim().length === 0) return false;
|
|
192
|
+
const versionNum = Number(match[2]);
|
|
193
|
+
return Number.isInteger(versionNum) && versionNum >= 0;
|
|
194
|
+
}
|
|
195
|
+
function isConsolidationOperator(value) {
|
|
196
|
+
return typeof value === "string" && CONSOLIDATION_OPERATORS.includes(value);
|
|
298
197
|
}
|
|
299
198
|
|
|
300
199
|
// ../remnic-core/src/entity-schema.ts
|
|
@@ -651,7 +550,7 @@ function confidenceTier(score) {
|
|
|
651
550
|
var SPECULATIVE_TTL_DAYS = 30;
|
|
652
551
|
|
|
653
552
|
// ../remnic-core/src/memory-projection-store.ts
|
|
654
|
-
import
|
|
553
|
+
import path2 from "path";
|
|
655
554
|
import { readFileSync } from "fs";
|
|
656
555
|
|
|
657
556
|
// ../remnic-core/src/runtime/better-sqlite.ts
|
|
@@ -684,7 +583,7 @@ function openBetterSqlite3(file, options) {
|
|
|
684
583
|
// ../remnic-core/src/memory-projection-store.ts
|
|
685
584
|
var MEMORY_PROJECTION_SCHEMA_VERSION = 2;
|
|
686
585
|
function getMemoryProjectionPath(memoryDir) {
|
|
687
|
-
return
|
|
586
|
+
return path2.join(memoryDir, "state", "memory-projection.sqlite");
|
|
688
587
|
}
|
|
689
588
|
function listTableColumns(db, tableName) {
|
|
690
589
|
try {
|
|
@@ -916,7 +815,7 @@ function parseCurrentRow(memoryDir, row) {
|
|
|
916
815
|
category: row.category,
|
|
917
816
|
status: row.status,
|
|
918
817
|
lifecycleState: typeof row.lifecycle_state === "string" ? row.lifecycle_state : void 0,
|
|
919
|
-
path:
|
|
818
|
+
path: path2.join(memoryDir, row.path_rel),
|
|
920
819
|
pathRel: row.path_rel,
|
|
921
820
|
created: row.created_at,
|
|
922
821
|
updated: row.updated_at,
|
|
@@ -1077,7 +976,7 @@ function readProjectedMemoryBrowse(memoryDir, options) {
|
|
|
1077
976
|
return true;
|
|
1078
977
|
}
|
|
1079
978
|
try {
|
|
1080
|
-
const filePath =
|
|
979
|
+
const filePath = path2.join(memoryDir, row.path_rel);
|
|
1081
980
|
const content = readFileSync(filePath, "utf-8").toLowerCase();
|
|
1082
981
|
return content.includes(normalizedQuery);
|
|
1083
982
|
} catch {
|
|
@@ -1091,7 +990,7 @@ function readProjectedMemoryBrowse(memoryDir, options) {
|
|
|
1091
990
|
(row) => typeof row.memory_id === "string" && typeof row.path_rel === "string" && typeof row.category === "string" && typeof row.status === "string"
|
|
1092
991
|
).map((row) => ({
|
|
1093
992
|
id: row.memory_id,
|
|
1094
|
-
path:
|
|
993
|
+
path: path2.join(memoryDir, row.path_rel),
|
|
1095
994
|
category: row.category,
|
|
1096
995
|
status: row.status,
|
|
1097
996
|
created: typeof row.created_at === "string" ? row.created_at : void 0,
|
|
@@ -1125,7 +1024,7 @@ function readProjectedMemoryBrowse(memoryDir, options) {
|
|
|
1125
1024
|
(row) => typeof row.memory_id === "string" && typeof row.path_rel === "string" && typeof row.category === "string" && typeof row.status === "string"
|
|
1126
1025
|
).map((row) => ({
|
|
1127
1026
|
id: row.memory_id,
|
|
1128
|
-
path:
|
|
1027
|
+
path: path2.join(memoryDir, row.path_rel),
|
|
1129
1028
|
category: row.category,
|
|
1130
1029
|
status: row.status,
|
|
1131
1030
|
created: typeof row.created_at === "string" ? row.created_at : void 0,
|
|
@@ -1341,7 +1240,7 @@ function readProjectedGovernanceRecord(memoryDir) {
|
|
|
1341
1240
|
}
|
|
1342
1241
|
|
|
1343
1242
|
// ../remnic-core/src/memory-lifecycle-ledger-utils.ts
|
|
1344
|
-
import
|
|
1243
|
+
import path3 from "path";
|
|
1345
1244
|
var MEMORY_LIFECYCLE_RULE_VERSION = "memory-lifecycle-ledger.v1";
|
|
1346
1245
|
var MEMORY_LIFECYCLE_EVENT_SORT_ORDER = {
|
|
1347
1246
|
created: 0,
|
|
@@ -1357,8 +1256,8 @@ var MEMORY_LIFECYCLE_EVENT_SORT_ORDER = {
|
|
|
1357
1256
|
archived: 10
|
|
1358
1257
|
};
|
|
1359
1258
|
function toMemoryPathRel(baseDir, filePath) {
|
|
1360
|
-
if (!baseDir) return filePath.split(
|
|
1361
|
-
return
|
|
1259
|
+
if (!baseDir) return filePath.split(path3.sep).join("/");
|
|
1260
|
+
return path3.relative(baseDir, filePath).split(path3.sep).join("/");
|
|
1362
1261
|
}
|
|
1363
1262
|
function isArchivedMemoryPath(pathRel) {
|
|
1364
1263
|
return pathRel === "archive" || pathRel.startsWith("archive/");
|
|
@@ -1722,6 +1621,17 @@ var ARTIFACT_SEARCH_STOPWORDS = /* @__PURE__ */ new Set([
|
|
|
1722
1621
|
function tokenizeArtifactSearchText(input) {
|
|
1723
1622
|
return input.toLowerCase().split(/[^a-z0-9]+/i).map((t) => t.trim()).filter((t) => t.length >= 2).filter((t) => !ARTIFACT_SEARCH_STOPWORDS.has(t));
|
|
1724
1623
|
}
|
|
1624
|
+
function assertMemoryWorthCounter(field, value) {
|
|
1625
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
1626
|
+
throw new Error(`${field} must be a finite number, got ${String(value)}`);
|
|
1627
|
+
}
|
|
1628
|
+
if (!Number.isInteger(value)) {
|
|
1629
|
+
throw new Error(`${field} must be an integer, got ${value}`);
|
|
1630
|
+
}
|
|
1631
|
+
if (value < 0) {
|
|
1632
|
+
throw new Error(`${field} must be >= 0, got ${value}`);
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1725
1635
|
function serializeFrontmatter(fm) {
|
|
1726
1636
|
const lines = [
|
|
1727
1637
|
"---",
|
|
@@ -1754,6 +1664,14 @@ function serializeFrontmatter(fm) {
|
|
|
1754
1664
|
lines.push(`accessCount: ${fm.accessCount}`);
|
|
1755
1665
|
}
|
|
1756
1666
|
if (fm.lastAccessed) lines.push(`lastAccessed: ${fm.lastAccessed}`);
|
|
1667
|
+
if (fm.mw_success !== void 0) {
|
|
1668
|
+
assertMemoryWorthCounter("mw_success", fm.mw_success);
|
|
1669
|
+
lines.push(`mw_success: ${fm.mw_success}`);
|
|
1670
|
+
}
|
|
1671
|
+
if (fm.mw_fail !== void 0) {
|
|
1672
|
+
assertMemoryWorthCounter("mw_fail", fm.mw_fail);
|
|
1673
|
+
lines.push(`mw_fail: ${fm.mw_fail}`);
|
|
1674
|
+
}
|
|
1757
1675
|
if (fm.importance) {
|
|
1758
1676
|
lines.push(`importanceScore: ${fm.importance.score}`);
|
|
1759
1677
|
lines.push(`importanceLevel: ${fm.importance.level}`);
|
|
@@ -1791,6 +1709,33 @@ function serializeFrontmatter(fm) {
|
|
|
1791
1709
|
lines.push(`structuredAttributes: ${JSON.stringify(fm.structuredAttributes)}`);
|
|
1792
1710
|
}
|
|
1793
1711
|
if (fm.contentHash) lines.push(`contentHash: ${fm.contentHash}`);
|
|
1712
|
+
if (fm.derived_from !== void 0) {
|
|
1713
|
+
if (!Array.isArray(fm.derived_from)) {
|
|
1714
|
+
throw new Error(
|
|
1715
|
+
`serializeFrontmatter: derived_from must be an array of "<path>:<version>" strings`
|
|
1716
|
+
);
|
|
1717
|
+
}
|
|
1718
|
+
for (const entry of fm.derived_from) {
|
|
1719
|
+
if (!isValidDerivedFromEntry(entry)) {
|
|
1720
|
+
throw new Error(
|
|
1721
|
+
`serializeFrontmatter: invalid derived_from entry ${JSON.stringify(entry)} \u2014 expected "<path>:<version>" with version >= 0`
|
|
1722
|
+
);
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
if (fm.derived_from.length > 0) {
|
|
1726
|
+
lines.push(
|
|
1727
|
+
`derived_from: [${fm.derived_from.map((e) => `"${e.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`).join(", ")}]`
|
|
1728
|
+
);
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
if (fm.derived_via !== void 0) {
|
|
1732
|
+
if (!isConsolidationOperator(fm.derived_via)) {
|
|
1733
|
+
throw new Error(
|
|
1734
|
+
`serializeFrontmatter: invalid derived_via ${JSON.stringify(fm.derived_via)} \u2014 expected one of "split" | "merge" | "update"`
|
|
1735
|
+
);
|
|
1736
|
+
}
|
|
1737
|
+
lines.push(`derived_via: ${fm.derived_via}`);
|
|
1738
|
+
}
|
|
1794
1739
|
lines.push("---");
|
|
1795
1740
|
return lines.join("\n");
|
|
1796
1741
|
}
|
|
@@ -1823,13 +1768,54 @@ function parseLinkReasonValue(rawValue) {
|
|
|
1823
1768
|
return legacyValue;
|
|
1824
1769
|
}
|
|
1825
1770
|
}
|
|
1771
|
+
function parseMemoryWorthCounterField(raw) {
|
|
1772
|
+
if (raw === void 0) return void 0;
|
|
1773
|
+
const trimmed = raw.trim();
|
|
1774
|
+
if (trimmed.length === 0) return void 0;
|
|
1775
|
+
const n = Number(trimmed);
|
|
1776
|
+
if (!Number.isFinite(n) || !Number.isInteger(n) || n < 0) return void 0;
|
|
1777
|
+
return n;
|
|
1778
|
+
}
|
|
1826
1779
|
function parseFrontmatter2(raw) {
|
|
1827
1780
|
const match = raw.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
1828
1781
|
if (!match) return null;
|
|
1829
1782
|
const fmBlock = match[1];
|
|
1830
1783
|
const content = match[2].trim();
|
|
1831
1784
|
const fm = {};
|
|
1832
|
-
|
|
1785
|
+
const rawLines = fmBlock.split("\n");
|
|
1786
|
+
const lines = [];
|
|
1787
|
+
let i = 0;
|
|
1788
|
+
while (i < rawLines.length) {
|
|
1789
|
+
const line = rawLines[i];
|
|
1790
|
+
const colonIdx = line.indexOf(":");
|
|
1791
|
+
if (colonIdx !== -1 && line.slice(colonIdx + 1).trim() === "") {
|
|
1792
|
+
const baseIndent = line.match(/^\s*/)[0].length;
|
|
1793
|
+
const items = [];
|
|
1794
|
+
let j = i + 1;
|
|
1795
|
+
while (j < rawLines.length) {
|
|
1796
|
+
const next = rawLines[j];
|
|
1797
|
+
const m = next.match(/^(\s+)- (.*)$/);
|
|
1798
|
+
if (!m || m[1].length <= baseIndent) break;
|
|
1799
|
+
let item = m[2].trim();
|
|
1800
|
+
if (item.startsWith('"') && item.endsWith('"') && item.length >= 2) {
|
|
1801
|
+
item = item.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
1802
|
+
} else if (item.startsWith("'") && item.endsWith("'") && item.length >= 2) {
|
|
1803
|
+
item = item.slice(1, -1).replace(/''/g, "'");
|
|
1804
|
+
}
|
|
1805
|
+
items.push(item);
|
|
1806
|
+
j++;
|
|
1807
|
+
}
|
|
1808
|
+
if (items.length > 0) {
|
|
1809
|
+
const inline = items.map((v) => `"${v.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`).join(", ");
|
|
1810
|
+
lines.push(`${line.slice(0, colonIdx + 1)} [${inline}]`);
|
|
1811
|
+
i = j;
|
|
1812
|
+
continue;
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
lines.push(line);
|
|
1816
|
+
i++;
|
|
1817
|
+
}
|
|
1818
|
+
for (const line of lines) {
|
|
1833
1819
|
const colonIdx = line.indexOf(":");
|
|
1834
1820
|
if (colonIdx === -1) continue;
|
|
1835
1821
|
const key = line.slice(0, colonIdx).trim();
|
|
@@ -1855,9 +1841,84 @@ function parseFrontmatter2(raw) {
|
|
|
1855
1841
|
if (lineageMatch) {
|
|
1856
1842
|
lineage = lineageMatch[1].split(",").map((l) => l.trim().replace(/^"|"$/g, "")).filter(Boolean);
|
|
1857
1843
|
}
|
|
1844
|
+
let derived_from;
|
|
1845
|
+
const derivedFromStr = (fm.derived_from ?? "").trim();
|
|
1846
|
+
if (derivedFromStr.startsWith("[") && derivedFromStr.endsWith("]")) {
|
|
1847
|
+
const inner = derivedFromStr.slice(1, -1);
|
|
1848
|
+
const entries = [];
|
|
1849
|
+
let i2 = 0;
|
|
1850
|
+
while (i2 < inner.length) {
|
|
1851
|
+
const ch = inner[i2];
|
|
1852
|
+
if (ch === '"') {
|
|
1853
|
+
let buf = "";
|
|
1854
|
+
i2++;
|
|
1855
|
+
while (i2 < inner.length) {
|
|
1856
|
+
const c = inner[i2];
|
|
1857
|
+
if (c === "\\" && i2 + 1 < inner.length) {
|
|
1858
|
+
const next = inner[i2 + 1];
|
|
1859
|
+
if (next === '"') {
|
|
1860
|
+
buf += '"';
|
|
1861
|
+
i2 += 2;
|
|
1862
|
+
continue;
|
|
1863
|
+
}
|
|
1864
|
+
if (next === "\\") {
|
|
1865
|
+
buf += "\\";
|
|
1866
|
+
i2 += 2;
|
|
1867
|
+
continue;
|
|
1868
|
+
}
|
|
1869
|
+
buf += c;
|
|
1870
|
+
i2++;
|
|
1871
|
+
continue;
|
|
1872
|
+
}
|
|
1873
|
+
if (c === '"') {
|
|
1874
|
+
i2++;
|
|
1875
|
+
break;
|
|
1876
|
+
}
|
|
1877
|
+
buf += c;
|
|
1878
|
+
i2++;
|
|
1879
|
+
}
|
|
1880
|
+
if (buf.length > 0) entries.push(buf);
|
|
1881
|
+
} else if (ch === "'") {
|
|
1882
|
+
let buf = "";
|
|
1883
|
+
i2++;
|
|
1884
|
+
while (i2 < inner.length) {
|
|
1885
|
+
const c = inner[i2];
|
|
1886
|
+
if (c === "'") {
|
|
1887
|
+
if (i2 + 1 < inner.length && inner[i2 + 1] === "'") {
|
|
1888
|
+
buf += "'";
|
|
1889
|
+
i2 += 2;
|
|
1890
|
+
continue;
|
|
1891
|
+
}
|
|
1892
|
+
i2++;
|
|
1893
|
+
break;
|
|
1894
|
+
}
|
|
1895
|
+
buf += c;
|
|
1896
|
+
i2++;
|
|
1897
|
+
}
|
|
1898
|
+
if (buf.length > 0) entries.push(buf);
|
|
1899
|
+
} else if (ch === "," || /\s/.test(ch)) {
|
|
1900
|
+
i2++;
|
|
1901
|
+
} else {
|
|
1902
|
+
let buf = "";
|
|
1903
|
+
while (i2 < inner.length) {
|
|
1904
|
+
const c = inner[i2];
|
|
1905
|
+
if (c === "," || /\s/.test(c)) break;
|
|
1906
|
+
buf += c;
|
|
1907
|
+
i2++;
|
|
1908
|
+
}
|
|
1909
|
+
if (buf.length > 0) entries.push(buf);
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
if (entries.length > 0) derived_from = entries;
|
|
1913
|
+
}
|
|
1914
|
+
const derivedViaRaw = (fm.derived_via ?? "").trim();
|
|
1915
|
+
const derivedViaUnquoted = derivedViaRaw.startsWith('"') && derivedViaRaw.endsWith('"') || derivedViaRaw.startsWith("'") && derivedViaRaw.endsWith("'") ? derivedViaRaw.slice(1, -1) : derivedViaRaw;
|
|
1916
|
+
const derived_via = isConsolidationOperator(derivedViaUnquoted) ? derivedViaUnquoted : void 0;
|
|
1858
1917
|
const accessCount = fm.accessCount ? parseInt(fm.accessCount, 10) : void 0;
|
|
1859
1918
|
const decayScore = fm.decayScore !== void 0 ? parseFloat(fm.decayScore) : void 0;
|
|
1860
1919
|
const heatScore = fm.heatScore !== void 0 ? parseFloat(fm.heatScore) : void 0;
|
|
1920
|
+
const mw_success = parseMemoryWorthCounterField(fm.mw_success);
|
|
1921
|
+
const mw_fail = parseMemoryWorthCounterField(fm.mw_fail);
|
|
1861
1922
|
let importance;
|
|
1862
1923
|
if (fm.importanceScore) {
|
|
1863
1924
|
const score = parseFloat(fm.importanceScore);
|
|
@@ -1909,6 +1970,9 @@ function parseFrontmatter2(raw) {
|
|
|
1909
1970
|
// Access tracking
|
|
1910
1971
|
accessCount: accessCount && accessCount > 0 ? accessCount : void 0,
|
|
1911
1972
|
lastAccessed: fm.lastAccessed || void 0,
|
|
1973
|
+
// Memory Worth counters (issue #560)
|
|
1974
|
+
mw_success,
|
|
1975
|
+
mw_fail,
|
|
1912
1976
|
// Importance scoring
|
|
1913
1977
|
importance,
|
|
1914
1978
|
// Chunking
|
|
@@ -1927,7 +1991,11 @@ function parseFrontmatter2(raw) {
|
|
|
1927
1991
|
// Structured attributes (JSON on a single line)
|
|
1928
1992
|
structuredAttributes: parseStructuredAttributes(fm.structuredAttributes),
|
|
1929
1993
|
// Raw-content dedup hash (format-agnostic archive/consolidation cleanup)
|
|
1930
|
-
contentHash: fm.contentHash || void 0
|
|
1994
|
+
contentHash: fm.contentHash || void 0,
|
|
1995
|
+
// Consolidation provenance (issue #561) — read-through only in this
|
|
1996
|
+
// PR; no code produces these fields yet.
|
|
1997
|
+
derived_from,
|
|
1998
|
+
derived_via
|
|
1931
1999
|
},
|
|
1932
2000
|
content
|
|
1933
2001
|
};
|
|
@@ -1964,17 +2032,17 @@ var KNOWN_ENTITY_FILENAME_PREFIXES = /* @__PURE__ */ new Set([
|
|
|
1964
2032
|
"topic"
|
|
1965
2033
|
]);
|
|
1966
2034
|
function inferEntityTypeFromFilename(pathRel) {
|
|
1967
|
-
const basename =
|
|
2035
|
+
const basename = path4.basename(pathRel, ".md").toLowerCase();
|
|
1968
2036
|
const separator = basename.indexOf("-");
|
|
1969
2037
|
if (separator <= 0) return void 0;
|
|
1970
2038
|
const candidate = basename.slice(0, separator);
|
|
1971
2039
|
return KNOWN_ENTITY_FILENAME_PREFIXES.has(candidate) ? candidate : void 0;
|
|
1972
2040
|
}
|
|
1973
2041
|
function normalizeFrontmatterForPath(frontmatter, pathRel, content = "") {
|
|
1974
|
-
const normalizedPath = pathRel.split(
|
|
2042
|
+
const normalizedPath = pathRel.split(path4.sep).join("/");
|
|
1975
2043
|
let normalizedFrontmatter = frontmatter;
|
|
1976
2044
|
if (normalizedPath === "entities" || normalizedPath.startsWith("entities/") || normalizedPath.includes("/entities/")) {
|
|
1977
|
-
const basename =
|
|
2045
|
+
const basename = path4.basename(pathRel, ".md");
|
|
1978
2046
|
const inferredType = inferEntityTypeFromContent(content) || inferEntityTypeFromFilename(pathRel) || "entity";
|
|
1979
2047
|
const existingTags = Array.isArray(frontmatter.tags) ? frontmatter.tags : [];
|
|
1980
2048
|
normalizedFrontmatter = {
|
|
@@ -2039,12 +2107,12 @@ var ContentHashIndex = class _ContentHashIndex {
|
|
|
2039
2107
|
dirty = false;
|
|
2040
2108
|
filePath;
|
|
2041
2109
|
constructor(stateDir) {
|
|
2042
|
-
this.filePath =
|
|
2110
|
+
this.filePath = path4.join(stateDir, "fact-hashes.txt");
|
|
2043
2111
|
}
|
|
2044
2112
|
/** Load existing hashes from disk. Safe to call multiple times. */
|
|
2045
2113
|
async load() {
|
|
2046
2114
|
try {
|
|
2047
|
-
const raw = await
|
|
2115
|
+
const raw = await readFile2(this.filePath, "utf-8");
|
|
2048
2116
|
for (const line of raw.split("\n")) {
|
|
2049
2117
|
const trimmed = line.trim();
|
|
2050
2118
|
if (trimmed.length > 0) {
|
|
@@ -2074,8 +2142,8 @@ var ContentHashIndex = class _ContentHashIndex {
|
|
|
2074
2142
|
/** Persist index to disk if changed. */
|
|
2075
2143
|
async save() {
|
|
2076
2144
|
if (!this.dirty) return;
|
|
2077
|
-
await
|
|
2078
|
-
await
|
|
2145
|
+
await mkdir2(path4.dirname(this.filePath), { recursive: true });
|
|
2146
|
+
await writeFile2(this.filePath, [...this.hashes].join("\n") + "\n", "utf-8");
|
|
2079
2147
|
this.dirty = false;
|
|
2080
2148
|
log.debug(`content-hash index: saved ${this.hashes.size} hashes`);
|
|
2081
2149
|
}
|
|
@@ -2120,7 +2188,7 @@ var ContentHashIndex = class _ContentHashIndex {
|
|
|
2120
2188
|
/** Normalize content and compute SHA-256 hash. */
|
|
2121
2189
|
static computeHash(content) {
|
|
2122
2190
|
const normalized = _ContentHashIndex.normalizeContent(content);
|
|
2123
|
-
return
|
|
2191
|
+
return createHash("sha256").update(normalized).digest("hex");
|
|
2124
2192
|
}
|
|
2125
2193
|
};
|
|
2126
2194
|
function normalizeAttributePairs(pairs) {
|
|
@@ -2611,7 +2679,7 @@ function fingerprintEntityStructuredFacts(entity) {
|
|
|
2611
2679
|
facts: normalizeStructuredSectionFacts(section.facts).slice().sort((left, right) => left.localeCompare(right))
|
|
2612
2680
|
})).filter((section) => section.facts.length > 0).sort((left, right) => left.key.localeCompare(right.key) || left.title.localeCompare(right.title) || left.facts.join("\n").localeCompare(right.facts.join("\n")));
|
|
2613
2681
|
if (normalizedSections.length === 0) return void 0;
|
|
2614
|
-
return
|
|
2682
|
+
return createHash("sha256").update(JSON.stringify(normalizedSections)).digest("hex");
|
|
2615
2683
|
}
|
|
2616
2684
|
function isEntitySynthesisStale(entity) {
|
|
2617
2685
|
const structuredFactCount = countEntityStructuredFacts(entity);
|
|
@@ -2902,6 +2970,27 @@ function buildEntitySchemaCacheKey(entitySchemas) {
|
|
|
2902
2970
|
]);
|
|
2903
2971
|
return JSON.stringify(normalized);
|
|
2904
2972
|
}
|
|
2973
|
+
function isValidBufferSurpriseEvent(value) {
|
|
2974
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
2975
|
+
const v = value;
|
|
2976
|
+
if (v.event !== "BUFFER_SURPRISE") return false;
|
|
2977
|
+
if (typeof v.timestamp !== "string" || v.timestamp.length === 0) return false;
|
|
2978
|
+
if (!Number.isFinite(Date.parse(v.timestamp))) return false;
|
|
2979
|
+
if (typeof v.bufferKey !== "string" || v.bufferKey.length === 0) return false;
|
|
2980
|
+
if (v.sessionKey !== null && typeof v.sessionKey !== "string") return false;
|
|
2981
|
+
if (v.turnRole !== "user" && v.turnRole !== "assistant") return false;
|
|
2982
|
+
if (typeof v.surpriseScore !== "number" || !Number.isFinite(v.surpriseScore)) {
|
|
2983
|
+
return false;
|
|
2984
|
+
}
|
|
2985
|
+
if (v.surpriseScore < 0 || v.surpriseScore > 1) return false;
|
|
2986
|
+
if (typeof v.threshold !== "number" || !Number.isFinite(v.threshold)) return false;
|
|
2987
|
+
if (v.threshold < 0 || v.threshold > 1) return false;
|
|
2988
|
+
if (typeof v.triggeredFlush !== "boolean") return false;
|
|
2989
|
+
if (typeof v.turnCountInWindow !== "number" || !Number.isFinite(v.turnCountInWindow)) {
|
|
2990
|
+
return false;
|
|
2991
|
+
}
|
|
2992
|
+
return true;
|
|
2993
|
+
}
|
|
2905
2994
|
var StorageManager = class _StorageManager {
|
|
2906
2995
|
constructor(baseDir, entitySchemas) {
|
|
2907
2996
|
this.baseDir = baseDir;
|
|
@@ -2979,24 +3068,65 @@ var StorageManager = class _StorageManager {
|
|
|
2979
3068
|
async snapshotBeforeWrite(filePath, trigger) {
|
|
2980
3069
|
if (!this._versioningConfig || !this._versioningConfig.enabled) return;
|
|
2981
3070
|
try {
|
|
2982
|
-
const existing = await
|
|
3071
|
+
const existing = await readFile2(filePath, "utf-8");
|
|
2983
3072
|
await createVersion(filePath, existing, trigger, this._versioningConfig, log, void 0, this.baseDir);
|
|
2984
3073
|
} catch {
|
|
2985
3074
|
}
|
|
2986
3075
|
}
|
|
3076
|
+
/**
|
|
3077
|
+
* Consolidation provenance helper (issue #561 PR 2).
|
|
3078
|
+
*
|
|
3079
|
+
* Captures the current on-disk content of a source memory as a
|
|
3080
|
+
* page-version snapshot so the downstream consolidated write can record a
|
|
3081
|
+
* `derived_from` pointer that actually resolves. Returns the
|
|
3082
|
+
* `"<relative-path>:<versionId>"` entry expected by the `derived_from`
|
|
3083
|
+
* frontmatter field.
|
|
3084
|
+
*
|
|
3085
|
+
* Returns `null` when versioning is disabled (snapshots would not be
|
|
3086
|
+
* created), when the file does not exist (nothing to snapshot), or when
|
|
3087
|
+
* the snapshot write itself fails (best-effort — callers skip the entry
|
|
3088
|
+
* rather than block the consolidation).
|
|
3089
|
+
*/
|
|
3090
|
+
async snapshotForProvenance(filePath) {
|
|
3091
|
+
if (!this._versioningConfig || !this._versioningConfig.enabled) return null;
|
|
3092
|
+
let existing;
|
|
3093
|
+
try {
|
|
3094
|
+
existing = await readFile2(filePath, "utf-8");
|
|
3095
|
+
} catch {
|
|
3096
|
+
return null;
|
|
3097
|
+
}
|
|
3098
|
+
try {
|
|
3099
|
+
const version = await createVersion(
|
|
3100
|
+
filePath,
|
|
3101
|
+
existing,
|
|
3102
|
+
"consolidation",
|
|
3103
|
+
this._versioningConfig,
|
|
3104
|
+
log,
|
|
3105
|
+
void 0,
|
|
3106
|
+
this.baseDir
|
|
3107
|
+
);
|
|
3108
|
+
const rel = path4.relative(this.baseDir, filePath).split(path4.sep).join("/");
|
|
3109
|
+
return `${rel}:${version.versionId}`;
|
|
3110
|
+
} catch (err) {
|
|
3111
|
+
log.warn(
|
|
3112
|
+
`storage.snapshotForProvenance: failed to snapshot ${filePath}: ${err instanceof Error ? err.message : String(err)}`
|
|
3113
|
+
);
|
|
3114
|
+
return null;
|
|
3115
|
+
}
|
|
3116
|
+
}
|
|
2987
3117
|
/** The root directory of this storage instance. */
|
|
2988
3118
|
get dir() {
|
|
2989
3119
|
return this.baseDir;
|
|
2990
3120
|
}
|
|
2991
3121
|
identityFilePath(workspaceDir, namespace) {
|
|
2992
3122
|
const rawNamespace = typeof namespace === "string" ? namespace.trim() : "";
|
|
2993
|
-
if (!rawNamespace) return
|
|
3123
|
+
if (!rawNamespace) return path4.join(workspaceDir, "IDENTITY.md");
|
|
2994
3124
|
const safeNamespace = rawNamespace.replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
2995
|
-
return
|
|
3125
|
+
return path4.join(workspaceDir, `IDENTITY.${safeNamespace}.md`);
|
|
2996
3126
|
}
|
|
2997
3127
|
versionFilePath(kind) {
|
|
2998
3128
|
const fileName = kind === "memory-status" ? ".memory-status-version.log" : kind === "artifact-write" ? ".artifact-write-version.log" : ".cold-write-version.log";
|
|
2999
|
-
return
|
|
3129
|
+
return path4.join(this.stateDir, fileName);
|
|
3000
3130
|
}
|
|
3001
3131
|
bumpSharedVersion(kind, fallbackMap) {
|
|
3002
3132
|
const filePath = this.versionFilePath(kind);
|
|
@@ -3033,25 +3163,28 @@ var StorageManager = class _StorageManager {
|
|
|
3033
3163
|
return this.readSharedVersion("artifact-write", _StorageManager.artifactWriteVersionByDir);
|
|
3034
3164
|
}
|
|
3035
3165
|
get factsDir() {
|
|
3036
|
-
return
|
|
3166
|
+
return path4.join(this.baseDir, "facts");
|
|
3037
3167
|
}
|
|
3038
3168
|
get correctionsDir() {
|
|
3039
|
-
return
|
|
3169
|
+
return path4.join(this.baseDir, "corrections");
|
|
3040
3170
|
}
|
|
3041
3171
|
get proceduresDir() {
|
|
3042
|
-
return
|
|
3172
|
+
return path4.join(this.baseDir, "procedures");
|
|
3173
|
+
}
|
|
3174
|
+
get reasoningTracesDir() {
|
|
3175
|
+
return path4.join(this.baseDir, "reasoning-traces");
|
|
3043
3176
|
}
|
|
3044
3177
|
get entitiesDir() {
|
|
3045
|
-
return
|
|
3178
|
+
return path4.join(this.baseDir, "entities");
|
|
3046
3179
|
}
|
|
3047
3180
|
get stateDir() {
|
|
3048
|
-
return
|
|
3181
|
+
return path4.join(this.baseDir, "state");
|
|
3049
3182
|
}
|
|
3050
3183
|
get entitySynthesisQueuePath() {
|
|
3051
|
-
return
|
|
3184
|
+
return path4.join(this.stateDir, "entity-synthesis-queue.json");
|
|
3052
3185
|
}
|
|
3053
3186
|
get factHashIndexReadyPath() {
|
|
3054
|
-
return
|
|
3187
|
+
return path4.join(this.stateDir, "fact-hashes.ready");
|
|
3055
3188
|
}
|
|
3056
3189
|
async getFactHashIndex() {
|
|
3057
3190
|
if (this.factHashIndex) {
|
|
@@ -3079,7 +3212,7 @@ var StorageManager = class _StorageManager {
|
|
|
3079
3212
|
}
|
|
3080
3213
|
this.factHashIndexAuthoritativePromise = (async () => {
|
|
3081
3214
|
try {
|
|
3082
|
-
await
|
|
3215
|
+
await access(this.factHashIndexReadyPath);
|
|
3083
3216
|
this.factHashIndexAuthoritative = true;
|
|
3084
3217
|
return;
|
|
3085
3218
|
} catch {
|
|
@@ -3117,8 +3250,8 @@ var StorageManager = class _StorageManager {
|
|
|
3117
3250
|
);
|
|
3118
3251
|
}
|
|
3119
3252
|
await factHashIndex.save();
|
|
3120
|
-
await
|
|
3121
|
-
await
|
|
3253
|
+
await mkdir2(path4.dirname(this.factHashIndexReadyPath), { recursive: true });
|
|
3254
|
+
await writeFile2(this.factHashIndexReadyPath, "v1\n", "utf-8");
|
|
3122
3255
|
this.factHashIndexAuthoritative = true;
|
|
3123
3256
|
})().finally(() => {
|
|
3124
3257
|
this.factHashIndexAuthoritativePromise = null;
|
|
@@ -3126,55 +3259,68 @@ var StorageManager = class _StorageManager {
|
|
|
3126
3259
|
await this.factHashIndexAuthoritativePromise;
|
|
3127
3260
|
}
|
|
3128
3261
|
get questionsDir() {
|
|
3129
|
-
return
|
|
3262
|
+
return path4.join(this.baseDir, "questions");
|
|
3130
3263
|
}
|
|
3131
3264
|
get artifactsDir() {
|
|
3132
|
-
return
|
|
3265
|
+
return path4.join(this.baseDir, "artifacts");
|
|
3133
3266
|
}
|
|
3134
3267
|
get identityDir() {
|
|
3135
|
-
return
|
|
3268
|
+
return path4.join(this.baseDir, "identity");
|
|
3136
3269
|
}
|
|
3137
3270
|
get identityAnchorPath() {
|
|
3138
|
-
return
|
|
3271
|
+
return path4.join(this.identityDir, "identity-anchor.md");
|
|
3139
3272
|
}
|
|
3140
3273
|
get identityIncidentsDir() {
|
|
3141
|
-
return
|
|
3274
|
+
return path4.join(this.identityDir, "incidents");
|
|
3142
3275
|
}
|
|
3143
3276
|
get identityAuditsWeeklyDir() {
|
|
3144
|
-
return
|
|
3277
|
+
return path4.join(this.identityDir, "audits", "weekly");
|
|
3145
3278
|
}
|
|
3146
3279
|
get identityAuditsMonthlyDir() {
|
|
3147
|
-
return
|
|
3280
|
+
return path4.join(this.identityDir, "audits", "monthly");
|
|
3148
3281
|
}
|
|
3149
3282
|
get identityImprovementLoopsPath() {
|
|
3150
|
-
return
|
|
3283
|
+
return path4.join(this.identityDir, "improvement-loops.md");
|
|
3151
3284
|
}
|
|
3152
3285
|
get identityReflectionsPath() {
|
|
3153
|
-
return
|
|
3286
|
+
return path4.join(this.identityDir, "reflections.md");
|
|
3154
3287
|
}
|
|
3155
3288
|
get profilePath() {
|
|
3156
|
-
return
|
|
3289
|
+
return path4.join(this.baseDir, "profile.md");
|
|
3157
3290
|
}
|
|
3158
3291
|
get memoryActionsPath() {
|
|
3159
|
-
return
|
|
3292
|
+
return path4.join(this.stateDir, "memory-actions.jsonl");
|
|
3160
3293
|
}
|
|
3161
3294
|
get memoryLifecycleLedgerPath() {
|
|
3162
|
-
return
|
|
3295
|
+
return path4.join(this.stateDir, "memory-lifecycle-ledger.jsonl");
|
|
3163
3296
|
}
|
|
3164
3297
|
get compressionGuidelinesPath() {
|
|
3165
|
-
return
|
|
3298
|
+
return path4.join(this.stateDir, "compression-guidelines.md");
|
|
3166
3299
|
}
|
|
3167
3300
|
get compressionGuidelineDraftPath() {
|
|
3168
|
-
return
|
|
3301
|
+
return path4.join(this.stateDir, "compression-guidelines.draft.md");
|
|
3169
3302
|
}
|
|
3170
3303
|
get compressionGuidelineStatePath() {
|
|
3171
|
-
return
|
|
3304
|
+
return path4.join(this.stateDir, "compression-guideline-state.json");
|
|
3172
3305
|
}
|
|
3173
3306
|
get compressionGuidelineDraftStatePath() {
|
|
3174
|
-
return
|
|
3307
|
+
return path4.join(this.stateDir, "compression-guideline-draft-state.json");
|
|
3175
3308
|
}
|
|
3176
3309
|
get behaviorSignalsPath() {
|
|
3177
|
-
return
|
|
3310
|
+
return path4.join(this.stateDir, "behavior-signals.jsonl");
|
|
3311
|
+
}
|
|
3312
|
+
/**
|
|
3313
|
+
* Buffer surprise telemetry ledger (issue #563 PR 3).
|
|
3314
|
+
*
|
|
3315
|
+
* Append-only JSONL of per-turn `BUFFER_SURPRISE` events emitted by
|
|
3316
|
+
* `SmartBuffer` when `bufferSurpriseTriggerEnabled` is on. Each row
|
|
3317
|
+
* captures the score, the threshold in force at the time, whether the
|
|
3318
|
+
* turn caused an extract_now upgrade, and the buffer size. Kept in
|
|
3319
|
+
* `state/` alongside the other append-only ledgers so cleanup and
|
|
3320
|
+
* governance sweeps can treat it uniformly.
|
|
3321
|
+
*/
|
|
3322
|
+
get bufferSurpriseLedgerPath() {
|
|
3323
|
+
return path4.join(this.stateDir, "buffer-surprise-ledger.jsonl");
|
|
3178
3324
|
}
|
|
3179
3325
|
/**
|
|
3180
3326
|
* Load user-defined entity aliases from config/aliases.json in the memory store.
|
|
@@ -3182,9 +3328,9 @@ var StorageManager = class _StorageManager {
|
|
|
3182
3328
|
* Call this once at startup (e.g. from orchestrator.initialize()).
|
|
3183
3329
|
*/
|
|
3184
3330
|
async loadAliases() {
|
|
3185
|
-
const aliasPath =
|
|
3331
|
+
const aliasPath = path4.join(this.baseDir, "config", "aliases.json");
|
|
3186
3332
|
try {
|
|
3187
|
-
const raw = await
|
|
3333
|
+
const raw = await readFile2(aliasPath, "utf-8");
|
|
3188
3334
|
const parsed = JSON.parse(raw);
|
|
3189
3335
|
if (typeof parsed === "object" && parsed !== null) {
|
|
3190
3336
|
userAliases = parsed;
|
|
@@ -3196,18 +3342,19 @@ var StorageManager = class _StorageManager {
|
|
|
3196
3342
|
}
|
|
3197
3343
|
async ensureDirectories() {
|
|
3198
3344
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
3199
|
-
await
|
|
3200
|
-
await
|
|
3201
|
-
await
|
|
3202
|
-
await
|
|
3203
|
-
await
|
|
3204
|
-
await
|
|
3205
|
-
await
|
|
3206
|
-
await
|
|
3207
|
-
await
|
|
3208
|
-
await
|
|
3209
|
-
await
|
|
3210
|
-
await
|
|
3345
|
+
await mkdir2(path4.join(this.factsDir, today), { recursive: true });
|
|
3346
|
+
await mkdir2(path4.join(this.proceduresDir, today), { recursive: true });
|
|
3347
|
+
await mkdir2(path4.join(this.reasoningTracesDir, today), { recursive: true });
|
|
3348
|
+
await mkdir2(this.correctionsDir, { recursive: true });
|
|
3349
|
+
await mkdir2(this.entitiesDir, { recursive: true });
|
|
3350
|
+
await mkdir2(this.stateDir, { recursive: true });
|
|
3351
|
+
await mkdir2(this.questionsDir, { recursive: true });
|
|
3352
|
+
await mkdir2(this.artifactsDir, { recursive: true });
|
|
3353
|
+
await mkdir2(this.identityDir, { recursive: true });
|
|
3354
|
+
await mkdir2(this.identityIncidentsDir, { recursive: true });
|
|
3355
|
+
await mkdir2(this.identityAuditsWeeklyDir, { recursive: true });
|
|
3356
|
+
await mkdir2(this.identityAuditsMonthlyDir, { recursive: true });
|
|
3357
|
+
await mkdir2(path4.join(this.baseDir, "config"), { recursive: true });
|
|
3211
3358
|
}
|
|
3212
3359
|
async writeMemory(category, content, options = {}) {
|
|
3213
3360
|
await this.ensureDirectories();
|
|
@@ -3250,6 +3397,12 @@ var StorageManager = class _StorageManager {
|
|
|
3250
3397
|
if (options.status !== void 0) {
|
|
3251
3398
|
fm.status = options.status;
|
|
3252
3399
|
}
|
|
3400
|
+
if (options.derivedFrom !== void 0 && options.derivedFrom.length > 0) {
|
|
3401
|
+
fm.derived_from = options.derivedFrom;
|
|
3402
|
+
}
|
|
3403
|
+
if (options.derivedVia !== void 0) {
|
|
3404
|
+
fm.derived_via = options.derivedVia;
|
|
3405
|
+
}
|
|
3253
3406
|
let enrichedContent = content;
|
|
3254
3407
|
if (options.structuredAttributes && Object.keys(options.structuredAttributes).length > 0) {
|
|
3255
3408
|
enrichedContent = `${content}
|
|
@@ -3269,15 +3422,18 @@ ${sanitized.text}
|
|
|
3269
3422
|
`;
|
|
3270
3423
|
let filePath;
|
|
3271
3424
|
if (category === "correction") {
|
|
3272
|
-
filePath =
|
|
3425
|
+
filePath = path4.join(this.correctionsDir, `${id}.md`);
|
|
3273
3426
|
} else if (category === "procedure") {
|
|
3274
|
-
await
|
|
3275
|
-
filePath =
|
|
3427
|
+
await mkdir2(path4.join(this.proceduresDir, today), { recursive: true });
|
|
3428
|
+
filePath = path4.join(this.proceduresDir, today, `${id}.md`);
|
|
3429
|
+
} else if (category === "reasoning_trace") {
|
|
3430
|
+
await mkdir2(path4.join(this.reasoningTracesDir, today), { recursive: true });
|
|
3431
|
+
filePath = path4.join(this.reasoningTracesDir, today, `${id}.md`);
|
|
3276
3432
|
} else {
|
|
3277
|
-
filePath =
|
|
3433
|
+
filePath = path4.join(this.factsDir, today, `${id}.md`);
|
|
3278
3434
|
}
|
|
3279
3435
|
await this.snapshotBeforeWrite(filePath, "write");
|
|
3280
|
-
await
|
|
3436
|
+
await writeFile2(filePath, fileContent, "utf-8");
|
|
3281
3437
|
this.invalidateAllMemoriesCache();
|
|
3282
3438
|
await this.appendGeneratedMemoryLifecycleEventFailOpen("storage.writeMemory", {
|
|
3283
3439
|
memoryId: id,
|
|
@@ -3321,8 +3477,8 @@ ${sanitized.text}
|
|
|
3321
3477
|
await this.ensureDirectories();
|
|
3322
3478
|
const now = /* @__PURE__ */ new Date();
|
|
3323
3479
|
const day = now.toISOString().slice(0, 10);
|
|
3324
|
-
const dir =
|
|
3325
|
-
await
|
|
3480
|
+
const dir = path4.join(this.artifactsDir, day);
|
|
3481
|
+
await mkdir2(dir, { recursive: true });
|
|
3326
3482
|
const id = `artifact-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
3327
3483
|
const fm = {
|
|
3328
3484
|
id,
|
|
@@ -3345,8 +3501,8 @@ ${sanitized.text}
|
|
|
3345
3501
|
log.warn(`artifact content rejected for ${id}; violations=${sanitized.violations.join(", ")}`);
|
|
3346
3502
|
return "";
|
|
3347
3503
|
}
|
|
3348
|
-
const filePath =
|
|
3349
|
-
await
|
|
3504
|
+
const filePath = path4.join(dir, `${id}.md`);
|
|
3505
|
+
await writeFile2(filePath, `${serializeFrontmatter(fm)}
|
|
3350
3506
|
|
|
3351
3507
|
${sanitized.text}
|
|
3352
3508
|
`, "utf-8");
|
|
@@ -3373,7 +3529,7 @@ ${sanitized.text}
|
|
|
3373
3529
|
try {
|
|
3374
3530
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
3375
3531
|
for (const entry of entries) {
|
|
3376
|
-
const fullPath =
|
|
3532
|
+
const fullPath = path4.join(dir, entry.name);
|
|
3377
3533
|
if (entry.isDirectory()) {
|
|
3378
3534
|
await readDir(fullPath);
|
|
3379
3535
|
continue;
|
|
@@ -3439,7 +3595,7 @@ ${sanitized.text}
|
|
|
3439
3595
|
log.debug(`fuzzy match: "${normalized}" \u2192 existing "${match}"`);
|
|
3440
3596
|
normalized = match;
|
|
3441
3597
|
}
|
|
3442
|
-
const filePath =
|
|
3598
|
+
const filePath = path4.join(this.entitiesDir, `${normalized}.md`);
|
|
3443
3599
|
let entity = {
|
|
3444
3600
|
name,
|
|
3445
3601
|
type,
|
|
@@ -3458,7 +3614,7 @@ ${sanitized.text}
|
|
|
3458
3614
|
aliases: []
|
|
3459
3615
|
};
|
|
3460
3616
|
try {
|
|
3461
|
-
const existing = await
|
|
3617
|
+
const existing = await readFile2(filePath, "utf-8");
|
|
3462
3618
|
entity = parseEntityFile(existing, this.entitySchemas);
|
|
3463
3619
|
} catch {
|
|
3464
3620
|
}
|
|
@@ -3516,7 +3672,7 @@ ${sanitized.text}
|
|
|
3516
3672
|
entity.created = entity.created || timestamp;
|
|
3517
3673
|
entity.updated = (/* @__PURE__ */ new Date()).toISOString();
|
|
3518
3674
|
await this.snapshotBeforeWrite(filePath, "write");
|
|
3519
|
-
await
|
|
3675
|
+
await writeFile2(filePath, serializeEntityFile(entity, this.entitySchemas), "utf-8");
|
|
3520
3676
|
this.invalidateKnowledgeIndexCache();
|
|
3521
3677
|
this.bumpMemoryStatusVersion();
|
|
3522
3678
|
log.debug(`wrote entity ${normalized}`);
|
|
@@ -3524,7 +3680,7 @@ ${sanitized.text}
|
|
|
3524
3680
|
}
|
|
3525
3681
|
async readProfile() {
|
|
3526
3682
|
try {
|
|
3527
|
-
return await
|
|
3683
|
+
return await readFile2(this.profilePath, "utf-8");
|
|
3528
3684
|
} catch {
|
|
3529
3685
|
return "";
|
|
3530
3686
|
}
|
|
@@ -3532,7 +3688,7 @@ ${sanitized.text}
|
|
|
3532
3688
|
async writeProfile(content) {
|
|
3533
3689
|
await this.ensureDirectories();
|
|
3534
3690
|
await this.snapshotBeforeWrite(this.profilePath, "consolidation");
|
|
3535
|
-
await
|
|
3691
|
+
await writeFile2(this.profilePath, content, "utf-8");
|
|
3536
3692
|
log.debug("updated profile.md");
|
|
3537
3693
|
}
|
|
3538
3694
|
/**
|
|
@@ -3650,7 +3806,7 @@ ${sanitized.text}
|
|
|
3650
3806
|
* per-process in-memory cache safe across process boundaries.
|
|
3651
3807
|
*/
|
|
3652
3808
|
invalidateColdMemoriesCache() {
|
|
3653
|
-
const coldRoot =
|
|
3809
|
+
const coldRoot = path4.join(this.baseDir, "cold");
|
|
3654
3810
|
_StorageManager.coldMemoriesCache.delete(coldRoot);
|
|
3655
3811
|
this.bumpColdWriteVersion();
|
|
3656
3812
|
}
|
|
@@ -3678,7 +3834,7 @@ ${sanitized.text}
|
|
|
3678
3834
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
3679
3835
|
const subdirs = [];
|
|
3680
3836
|
for (const entry of entries) {
|
|
3681
|
-
const fullPath =
|
|
3837
|
+
const fullPath = path4.join(dir, entry.name);
|
|
3682
3838
|
if (entry.isDirectory()) {
|
|
3683
3839
|
subdirs.push(fullPath);
|
|
3684
3840
|
} else if (entry.name.endsWith(".md")) {
|
|
@@ -3693,6 +3849,7 @@ ${sanitized.text}
|
|
|
3693
3849
|
};
|
|
3694
3850
|
await collectPaths(this.factsDir);
|
|
3695
3851
|
await collectPaths(this.proceduresDir);
|
|
3852
|
+
await collectPaths(this.reasoningTracesDir);
|
|
3696
3853
|
await collectPaths(this.correctionsDir);
|
|
3697
3854
|
return filePaths;
|
|
3698
3855
|
}
|
|
@@ -3705,7 +3862,7 @@ ${sanitized.text}
|
|
|
3705
3862
|
const results = await Promise.all(
|
|
3706
3863
|
batch.map(async (fullPath) => {
|
|
3707
3864
|
try {
|
|
3708
|
-
const raw = await
|
|
3865
|
+
const raw = await readFile2(fullPath, "utf-8");
|
|
3709
3866
|
const parsed = parseFrontmatter2(raw);
|
|
3710
3867
|
if (!parsed) return null;
|
|
3711
3868
|
return {
|
|
@@ -3730,7 +3887,7 @@ ${sanitized.text}
|
|
|
3730
3887
|
}
|
|
3731
3888
|
async readWindowUpdatedMs(filePath) {
|
|
3732
3889
|
try {
|
|
3733
|
-
const raw = await
|
|
3890
|
+
const raw = await readFile2(filePath, "utf-8");
|
|
3734
3891
|
const match = raw.match(/^---\n([\s\S]*?)\n---\n?/);
|
|
3735
3892
|
if (!match) return null;
|
|
3736
3893
|
const frontmatterBlock = match[1];
|
|
@@ -3760,7 +3917,7 @@ ${sanitized.text}
|
|
|
3760
3917
|
const correctionPaths = [];
|
|
3761
3918
|
const factPaths = [];
|
|
3762
3919
|
for (const filePath of filePaths) {
|
|
3763
|
-
if (filePath === this.correctionsDir || filePath.startsWith(`${this.correctionsDir}${
|
|
3920
|
+
if (filePath === this.correctionsDir || filePath.startsWith(`${this.correctionsDir}${path4.sep}`)) {
|
|
3764
3921
|
correctionPaths.push(filePath);
|
|
3765
3922
|
} else {
|
|
3766
3923
|
factPaths.push(filePath);
|
|
@@ -3880,7 +4037,7 @@ ${sanitized.text}
|
|
|
3880
4037
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
3881
4038
|
const subdirs = [];
|
|
3882
4039
|
for (const entry of entries) {
|
|
3883
|
-
const fullPath =
|
|
4040
|
+
const fullPath = path4.join(dir, entry.name);
|
|
3884
4041
|
if (entry.isDirectory()) {
|
|
3885
4042
|
subdirs.push(fullPath);
|
|
3886
4043
|
} else if (entry.name.endsWith(".md")) {
|
|
@@ -3909,12 +4066,12 @@ ${sanitized.text}
|
|
|
3909
4066
|
try {
|
|
3910
4067
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
3911
4068
|
for (const entry of entries) {
|
|
3912
|
-
const fullPath =
|
|
4069
|
+
const fullPath = path4.join(dir, entry.name);
|
|
3913
4070
|
if (entry.isDirectory()) {
|
|
3914
4071
|
await readDir(fullPath);
|
|
3915
4072
|
} else if (entry.name.endsWith(".md")) {
|
|
3916
4073
|
try {
|
|
3917
|
-
const raw = await
|
|
4074
|
+
const raw = await readFile2(fullPath, "utf-8");
|
|
3918
4075
|
const parsed = parseFrontmatter2(raw);
|
|
3919
4076
|
if (parsed) {
|
|
3920
4077
|
memories.push({
|
|
@@ -3940,7 +4097,7 @@ ${sanitized.text}
|
|
|
3940
4097
|
/** Read a single memory file by its absolute path. Returns null if unreadable. */
|
|
3941
4098
|
async readMemoryByPath(filePath) {
|
|
3942
4099
|
try {
|
|
3943
|
-
const raw = await
|
|
4100
|
+
const raw = await readFile2(filePath, "utf-8");
|
|
3944
4101
|
const parsed = parseFrontmatter2(raw);
|
|
3945
4102
|
if (parsed) {
|
|
3946
4103
|
return {
|
|
@@ -3953,11 +4110,11 @@ ${sanitized.text}
|
|
|
3953
4110
|
content: parsed.content
|
|
3954
4111
|
};
|
|
3955
4112
|
}
|
|
3956
|
-
const normalizedPath = filePath.split(
|
|
4113
|
+
const normalizedPath = filePath.split(path4.sep).join("/");
|
|
3957
4114
|
if (normalizedPath.includes("/entities/") && filePath.endsWith(".md")) {
|
|
3958
4115
|
const entity = parseEntityFile(raw, this.entitySchemas);
|
|
3959
4116
|
if (!entity.name) return null;
|
|
3960
|
-
const nameWithoutExt =
|
|
4117
|
+
const nameWithoutExt = path4.basename(filePath, ".md");
|
|
3961
4118
|
const fileMtime = entity.updated || await stat2(filePath).then((s) => s.mtime.toISOString()).catch(() => (/* @__PURE__ */ new Date(0)).toISOString());
|
|
3962
4119
|
return {
|
|
3963
4120
|
path: filePath,
|
|
@@ -3980,7 +4137,7 @@ ${sanitized.text}
|
|
|
3980
4137
|
}
|
|
3981
4138
|
}
|
|
3982
4139
|
resolveTierRootDir(tier) {
|
|
3983
|
-
return tier === "cold" ?
|
|
4140
|
+
return tier === "cold" ? path4.join(this.baseDir, "cold") : this.baseDir;
|
|
3984
4141
|
}
|
|
3985
4142
|
resolveMemoryDateDir(memory) {
|
|
3986
4143
|
const preferred = memory.frontmatter.created || memory.frontmatter.updated;
|
|
@@ -3995,30 +4152,33 @@ ${sanitized.text}
|
|
|
3995
4152
|
buildTierMemoryPath(memory, tier) {
|
|
3996
4153
|
const root = this.resolveTierRootDir(tier);
|
|
3997
4154
|
if (this.isArtifactMemory(memory)) {
|
|
3998
|
-
return
|
|
4155
|
+
return path4.join(root, "artifacts", this.resolveMemoryDateDir(memory), `${memory.frontmatter.id}.md`);
|
|
3999
4156
|
}
|
|
4000
4157
|
if (memory.frontmatter.category === "correction") {
|
|
4001
|
-
return
|
|
4158
|
+
return path4.join(root, "corrections", `${memory.frontmatter.id}.md`);
|
|
4002
4159
|
}
|
|
4003
4160
|
if (memory.frontmatter.category === "procedure") {
|
|
4004
|
-
return
|
|
4161
|
+
return path4.join(root, "procedures", this.resolveMemoryDateDir(memory), `${memory.frontmatter.id}.md`);
|
|
4162
|
+
}
|
|
4163
|
+
if (memory.frontmatter.category === "reasoning_trace") {
|
|
4164
|
+
return path4.join(root, "reasoning-traces", this.resolveMemoryDateDir(memory), `${memory.frontmatter.id}.md`);
|
|
4005
4165
|
}
|
|
4006
|
-
return
|
|
4166
|
+
return path4.join(root, "facts", this.resolveMemoryDateDir(memory), `${memory.frontmatter.id}.md`);
|
|
4007
4167
|
}
|
|
4008
4168
|
async writeMemoryFileAtomic(targetPath, memory) {
|
|
4009
4169
|
const fileContent = `${serializeFrontmatter(memory.frontmatter)}
|
|
4010
4170
|
|
|
4011
4171
|
${memory.content}
|
|
4012
4172
|
`;
|
|
4013
|
-
await
|
|
4173
|
+
await mkdir2(path4.dirname(targetPath), { recursive: true });
|
|
4014
4174
|
const tempPath = `${targetPath}.tmp-${process.pid}-${Date.now()}`;
|
|
4015
4175
|
try {
|
|
4016
|
-
await
|
|
4176
|
+
await writeFile2(tempPath, fileContent, "utf-8");
|
|
4017
4177
|
await rename(tempPath, targetPath);
|
|
4018
4178
|
this.invalidateAllMemoriesCache();
|
|
4019
4179
|
} catch (err) {
|
|
4020
4180
|
try {
|
|
4021
|
-
await
|
|
4181
|
+
await unlink(tempPath);
|
|
4022
4182
|
} catch {
|
|
4023
4183
|
}
|
|
4024
4184
|
throw err;
|
|
@@ -4026,11 +4186,11 @@ ${memory.content}
|
|
|
4026
4186
|
}
|
|
4027
4187
|
async moveMemoryToPath(memory, targetPath) {
|
|
4028
4188
|
await this.writeMemoryFileAtomic(targetPath, memory);
|
|
4029
|
-
const sourcePath =
|
|
4030
|
-
const destPath =
|
|
4189
|
+
const sourcePath = path4.resolve(memory.path);
|
|
4190
|
+
const destPath = path4.resolve(targetPath);
|
|
4031
4191
|
if (sourcePath !== destPath) {
|
|
4032
4192
|
try {
|
|
4033
|
-
await
|
|
4193
|
+
await unlink(memory.path);
|
|
4034
4194
|
} catch (err) {
|
|
4035
4195
|
const message = err instanceof Error ? err.message : String(err);
|
|
4036
4196
|
if (!message.includes("ENOENT")) {
|
|
@@ -4042,15 +4202,15 @@ ${memory.content}
|
|
|
4042
4202
|
}
|
|
4043
4203
|
async migrateMemoryToTier(memory, targetTier) {
|
|
4044
4204
|
const targetPath = this.buildTierMemoryPath(memory, targetTier);
|
|
4045
|
-
const sourcePath =
|
|
4046
|
-
const destPath =
|
|
4205
|
+
const sourcePath = path4.resolve(memory.path);
|
|
4206
|
+
const destPath = path4.resolve(targetPath);
|
|
4047
4207
|
if (sourcePath === destPath) {
|
|
4048
4208
|
return { changed: false, targetPath };
|
|
4049
4209
|
}
|
|
4050
4210
|
const existing = await this.readMemoryByPath(targetPath);
|
|
4051
4211
|
if (existing?.frontmatter.id === memory.frontmatter.id) {
|
|
4052
4212
|
try {
|
|
4053
|
-
await
|
|
4213
|
+
await unlink(memory.path);
|
|
4054
4214
|
} catch (err) {
|
|
4055
4215
|
const message = err instanceof Error ? err.message : String(err);
|
|
4056
4216
|
if (!message.includes("ENOENT")) {
|
|
@@ -4069,7 +4229,7 @@ ${memory.content}
|
|
|
4069
4229
|
return { changed: true, targetPath };
|
|
4070
4230
|
}
|
|
4071
4231
|
get archiveDir() {
|
|
4072
|
-
return
|
|
4232
|
+
return path4.join(this.baseDir, "archive");
|
|
4073
4233
|
}
|
|
4074
4234
|
/**
|
|
4075
4235
|
* Archive a memory by moving it from facts/ to archive/YYYY-MM-DD/.
|
|
@@ -4080,8 +4240,8 @@ ${memory.content}
|
|
|
4080
4240
|
try {
|
|
4081
4241
|
const now = lifecycle?.at ?? /* @__PURE__ */ new Date();
|
|
4082
4242
|
const today = now.toISOString().slice(0, 10);
|
|
4083
|
-
const destDir =
|
|
4084
|
-
await
|
|
4243
|
+
const destDir = path4.join(this.archiveDir, today);
|
|
4244
|
+
await mkdir2(destDir, { recursive: true });
|
|
4085
4245
|
const updatedFm = {
|
|
4086
4246
|
...memory.frontmatter,
|
|
4087
4247
|
status: "archived",
|
|
@@ -4092,9 +4252,9 @@ ${memory.content}
|
|
|
4092
4252
|
|
|
4093
4253
|
${memory.content}
|
|
4094
4254
|
`;
|
|
4095
|
-
const destPath =
|
|
4096
|
-
await
|
|
4097
|
-
await
|
|
4255
|
+
const destPath = path4.join(destDir, path4.basename(memory.path));
|
|
4256
|
+
await writeFile2(destPath, fileContent, "utf-8");
|
|
4257
|
+
await unlink(memory.path);
|
|
4098
4258
|
this.invalidateAllMemoriesCache();
|
|
4099
4259
|
await this.appendGeneratedMemoryLifecycleEventFailOpen(
|
|
4100
4260
|
"storage.archiveMemory",
|
|
@@ -4129,7 +4289,7 @@ ${memory.content}
|
|
|
4129
4289
|
}
|
|
4130
4290
|
async readEntity(name) {
|
|
4131
4291
|
try {
|
|
4132
|
-
return await
|
|
4292
|
+
return await readFile2(path4.join(this.entitiesDir, `${name}.md`), "utf-8");
|
|
4133
4293
|
} catch {
|
|
4134
4294
|
return "";
|
|
4135
4295
|
}
|
|
@@ -4183,7 +4343,7 @@ ${memory.content}
|
|
|
4183
4343
|
const memory = memories.find((m) => m.frontmatter.id === id);
|
|
4184
4344
|
if (!memory) return false;
|
|
4185
4345
|
try {
|
|
4186
|
-
await
|
|
4346
|
+
await unlink(memory.path);
|
|
4187
4347
|
this.invalidateAllMemoriesCache();
|
|
4188
4348
|
this.bumpMemoryStatusVersion();
|
|
4189
4349
|
log.debug(`invalidated memory ${id}`);
|
|
@@ -4214,7 +4374,7 @@ ${memory.content}
|
|
|
4214
4374
|
|
|
4215
4375
|
${sanitized.text}
|
|
4216
4376
|
`;
|
|
4217
|
-
await
|
|
4377
|
+
await writeFile2(memory.path, fileContent, "utf-8");
|
|
4218
4378
|
this.invalidateAllMemoriesCache();
|
|
4219
4379
|
await this.appendGeneratedMemoryLifecycleEventFailOpen("storage.updateMemory", {
|
|
4220
4380
|
memoryId: id,
|
|
@@ -4246,9 +4406,9 @@ ${sanitized.text}
|
|
|
4246
4406
|
|
|
4247
4407
|
${memory.content}
|
|
4248
4408
|
`;
|
|
4249
|
-
await
|
|
4409
|
+
await writeFile2(memory.path, fileContent, "utf-8");
|
|
4250
4410
|
this.invalidateAllMemoriesCache();
|
|
4251
|
-
if (memory.path.includes(`${
|
|
4411
|
+
if (memory.path.includes(`${path4.sep}cold${path4.sep}`)) {
|
|
4252
4412
|
this.invalidateColdMemoriesCache();
|
|
4253
4413
|
}
|
|
4254
4414
|
await this.appendGeneratedMemoryLifecycleEventFailOpen(
|
|
@@ -4295,7 +4455,7 @@ ${memory.content}
|
|
|
4295
4455
|
const expiresAt = new Date(m.frontmatter.expiresAt).getTime();
|
|
4296
4456
|
if (expiresAt < now) {
|
|
4297
4457
|
try {
|
|
4298
|
-
await
|
|
4458
|
+
await unlink(m.path);
|
|
4299
4459
|
deleted.push(m);
|
|
4300
4460
|
log.debug(`cleaned expired memory ${m.frontmatter.id} (TTL expired)`);
|
|
4301
4461
|
} catch {
|
|
@@ -4309,9 +4469,9 @@ ${memory.content}
|
|
|
4309
4469
|
return deleted;
|
|
4310
4470
|
}
|
|
4311
4471
|
async loadBuffer() {
|
|
4312
|
-
const bufferPath =
|
|
4472
|
+
const bufferPath = path4.join(this.stateDir, "buffer.json");
|
|
4313
4473
|
try {
|
|
4314
|
-
const raw = await
|
|
4474
|
+
const raw = await readFile2(bufferPath, "utf-8");
|
|
4315
4475
|
return JSON.parse(raw);
|
|
4316
4476
|
} catch {
|
|
4317
4477
|
return { turns: [], lastExtractionAt: null, extractionCount: 0 };
|
|
@@ -4319,13 +4479,13 @@ ${memory.content}
|
|
|
4319
4479
|
}
|
|
4320
4480
|
async saveBuffer(state) {
|
|
4321
4481
|
await this.ensureDirectories();
|
|
4322
|
-
const bufferPath =
|
|
4323
|
-
await
|
|
4482
|
+
const bufferPath = path4.join(this.stateDir, "buffer.json");
|
|
4483
|
+
await writeFile2(bufferPath, JSON.stringify(state, null, 2), "utf-8");
|
|
4324
4484
|
}
|
|
4325
4485
|
async loadMeta() {
|
|
4326
|
-
const metaPath =
|
|
4486
|
+
const metaPath = path4.join(this.stateDir, "meta.json");
|
|
4327
4487
|
try {
|
|
4328
|
-
const raw = await
|
|
4488
|
+
const raw = await readFile2(metaPath, "utf-8");
|
|
4329
4489
|
const parsed = JSON.parse(raw);
|
|
4330
4490
|
return {
|
|
4331
4491
|
extractionCount: typeof parsed.extractionCount === "number" ? parsed.extractionCount : 0,
|
|
@@ -4355,8 +4515,8 @@ ${memory.content}
|
|
|
4355
4515
|
}
|
|
4356
4516
|
async saveMeta(state) {
|
|
4357
4517
|
await this.ensureDirectories();
|
|
4358
|
-
const metaPath =
|
|
4359
|
-
await
|
|
4518
|
+
const metaPath = path4.join(this.stateDir, "meta.json");
|
|
4519
|
+
await writeFile2(metaPath, JSON.stringify(state, null, 2), "utf-8");
|
|
4360
4520
|
}
|
|
4361
4521
|
async appendMemoryActionEvents(events) {
|
|
4362
4522
|
if (events.length === 0) return 0;
|
|
@@ -4388,12 +4548,102 @@ ${memory.content}
|
|
|
4388
4548
|
await appendFile(this.memoryLifecycleLedgerPath, payload, "utf-8");
|
|
4389
4549
|
return events.length;
|
|
4390
4550
|
}
|
|
4551
|
+
/**
|
|
4552
|
+
* Append a batch of `BUFFER_SURPRISE` telemetry events (issue #563 PR 3).
|
|
4553
|
+
*
|
|
4554
|
+
* Each event records a single buffer flush decision driven by the
|
|
4555
|
+
* surprise gate. The ledger is consumed by
|
|
4556
|
+
* `reportBufferSurpriseDistribution` (Doctor report) and by downstream
|
|
4557
|
+
* benchmark analysis. This method is fire-and-forget by contract:
|
|
4558
|
+
* callers log but do not fail the hot path if the append throws.
|
|
4559
|
+
*/
|
|
4560
|
+
async appendBufferSurpriseEvents(events) {
|
|
4561
|
+
if (events.length === 0) return 0;
|
|
4562
|
+
await this.ensureDirectories();
|
|
4563
|
+
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
4564
|
+
const payload = events.map((event) => {
|
|
4565
|
+
const normalized = {
|
|
4566
|
+
...event,
|
|
4567
|
+
event: "BUFFER_SURPRISE",
|
|
4568
|
+
timestamp: event.timestamp && event.timestamp.length > 0 ? event.timestamp : nowIso
|
|
4569
|
+
};
|
|
4570
|
+
return `${JSON.stringify(normalized)}
|
|
4571
|
+
`;
|
|
4572
|
+
}).join("");
|
|
4573
|
+
await appendFile(this.bufferSurpriseLedgerPath, payload, "utf-8");
|
|
4574
|
+
return events.length;
|
|
4575
|
+
}
|
|
4576
|
+
/**
|
|
4577
|
+
* Read the buffer-surprise ledger, most recent rows last.
|
|
4578
|
+
*
|
|
4579
|
+
* `limit` bounds the number of **valid rows** returned (not the
|
|
4580
|
+
* number of raw lines parsed). We parse every row, discard malformed
|
|
4581
|
+
* ones, then take the tail — so a partial/truncated trailing line
|
|
4582
|
+
* (the common failure mode after an interrupted append) cannot hide
|
|
4583
|
+
* otherwise-valid recent data above it.
|
|
4584
|
+
*
|
|
4585
|
+
* Non-positive / non-integer / non-finite limits return `[]` rather
|
|
4586
|
+
* than the entire file, matching the other ledger readers in this
|
|
4587
|
+
* class and protecting against `slice(-0.5)` → `slice(-0)` silently
|
|
4588
|
+
* devolving into an unbounded parse.
|
|
4589
|
+
*
|
|
4590
|
+
* # Performance note
|
|
4591
|
+
*
|
|
4592
|
+
* For very large ledgers (issue #563 follow-up), a tail-first reader
|
|
4593
|
+
* would avoid parsing the full file when only a recent window is
|
|
4594
|
+
* needed. We keep the full-scan implementation here because:
|
|
4595
|
+
*
|
|
4596
|
+
* - the ledger is opt-in (flag off by default), so early deployments
|
|
4597
|
+
* accumulate rows slowly;
|
|
4598
|
+
* - telemetry rows are small (~200 bytes), so even 100k rows parse
|
|
4599
|
+
* in well under a second;
|
|
4600
|
+
* - the governance archive/cleanup flow can trim the ledger when
|
|
4601
|
+
* size becomes a concern, reusing the existing maintenance hooks.
|
|
4602
|
+
*
|
|
4603
|
+
* Swap to a chunked tail-reader if production logs show this is a
|
|
4604
|
+
* hot path — leaving that work for a follow-up keeps this PR scoped
|
|
4605
|
+
* to correctness, not optimization.
|
|
4606
|
+
*/
|
|
4607
|
+
async readBufferSurpriseEvents(options = {}) {
|
|
4608
|
+
let raw;
|
|
4609
|
+
try {
|
|
4610
|
+
raw = await readFile2(this.bufferSurpriseLedgerPath, "utf-8");
|
|
4611
|
+
} catch (err) {
|
|
4612
|
+
const code = err.code;
|
|
4613
|
+
if (code === "ENOENT") return [];
|
|
4614
|
+
throw err;
|
|
4615
|
+
}
|
|
4616
|
+
let effectiveLimit = null;
|
|
4617
|
+
if (options.limit !== void 0) {
|
|
4618
|
+
if (typeof options.limit !== "number" || !Number.isFinite(options.limit) || options.limit <= 0) {
|
|
4619
|
+
return [];
|
|
4620
|
+
}
|
|
4621
|
+
const floored = Math.floor(options.limit);
|
|
4622
|
+
if (floored <= 0) return [];
|
|
4623
|
+
effectiveLimit = floored;
|
|
4624
|
+
}
|
|
4625
|
+
const lines = raw.split("\n");
|
|
4626
|
+
const events = [];
|
|
4627
|
+
for (const line of lines) {
|
|
4628
|
+
const trimmed = line.trim();
|
|
4629
|
+
if (trimmed.length === 0) continue;
|
|
4630
|
+
try {
|
|
4631
|
+
const parsed = JSON.parse(trimmed);
|
|
4632
|
+
if (isValidBufferSurpriseEvent(parsed)) {
|
|
4633
|
+
events.push(parsed);
|
|
4634
|
+
}
|
|
4635
|
+
} catch {
|
|
4636
|
+
}
|
|
4637
|
+
}
|
|
4638
|
+
if (effectiveLimit === null) return events;
|
|
4639
|
+
return events.slice(-effectiveLimit);
|
|
4640
|
+
}
|
|
4391
4641
|
async appendBehaviorSignals(events) {
|
|
4392
4642
|
if (events.length === 0) return 0;
|
|
4393
4643
|
await this.ensureDirectories();
|
|
4394
4644
|
let existingKeys = /* @__PURE__ */ new Set();
|
|
4395
4645
|
try {
|
|
4396
|
-
const raw = await
|
|
4646
|
+
const raw = await readFile2(this.behaviorSignalsPath, "utf-8");
|
|
4397
4647
|
const lines = raw.split("\n");
|
|
4398
4648
|
for (const line of lines) {
|
|
4399
4649
|
const row = line.trim();
|
|
@@ -4429,7 +4679,7 @@ ${memory.content}
|
|
|
4429
4679
|
async appendReextractJobs(events) {
|
|
4430
4680
|
if (events.length === 0) return 0;
|
|
4431
4681
|
await this.ensureDirectories();
|
|
4432
|
-
const filePath =
|
|
4682
|
+
const filePath = path4.join(this.stateDir, "reextract-jobs.jsonl");
|
|
4433
4683
|
const lines = events.map((event) => JSON.stringify(event)).join("\n") + "\n";
|
|
4434
4684
|
try {
|
|
4435
4685
|
await appendFile(filePath, lines, "utf-8");
|
|
@@ -4440,9 +4690,9 @@ ${memory.content}
|
|
|
4440
4690
|
}
|
|
4441
4691
|
async readReextractJobs(limit = 200) {
|
|
4442
4692
|
const safeLimit = Number.isFinite(limit) ? Math.max(1, Math.min(1e3, Math.floor(limit))) : 200;
|
|
4443
|
-
const filePath =
|
|
4693
|
+
const filePath = path4.join(this.stateDir, "reextract-jobs.jsonl");
|
|
4444
4694
|
try {
|
|
4445
|
-
const raw = await
|
|
4695
|
+
const raw = await readFile2(filePath, "utf-8");
|
|
4446
4696
|
const lines = raw.split("\n").filter((line) => line.trim().length > 0);
|
|
4447
4697
|
const parsed = [];
|
|
4448
4698
|
for (const line of lines) {
|
|
@@ -4470,7 +4720,7 @@ ${memory.content}
|
|
|
4470
4720
|
const cappedLimit = Math.max(0, Math.floor(limit));
|
|
4471
4721
|
if (cappedLimit === 0) return [];
|
|
4472
4722
|
try {
|
|
4473
|
-
const raw = await
|
|
4723
|
+
const raw = await readFile2(this.behaviorSignalsPath, "utf-8");
|
|
4474
4724
|
const out = [];
|
|
4475
4725
|
const lines = raw.split("\n");
|
|
4476
4726
|
for (let i = lines.length - 1; i >= 0 && out.length < cappedLimit; i -= 1) {
|
|
@@ -4493,7 +4743,7 @@ ${memory.content}
|
|
|
4493
4743
|
const cappedLimit = Math.max(0, Math.floor(limit));
|
|
4494
4744
|
if (cappedLimit === 0) return [];
|
|
4495
4745
|
try {
|
|
4496
|
-
const raw = await
|
|
4746
|
+
const raw = await readFile2(this.memoryActionsPath, "utf-8");
|
|
4497
4747
|
const out = [];
|
|
4498
4748
|
const lines = raw.split("\n");
|
|
4499
4749
|
for (let i = lines.length - 1; i >= 0 && out.length < cappedLimit; i -= 1) {
|
|
@@ -4514,7 +4764,7 @@ ${memory.content}
|
|
|
4514
4764
|
}
|
|
4515
4765
|
async readAllMemoryLifecycleEvents() {
|
|
4516
4766
|
try {
|
|
4517
|
-
const raw = await
|
|
4767
|
+
const raw = await readFile2(this.memoryLifecycleLedgerPath, "utf-8");
|
|
4518
4768
|
const out = [];
|
|
4519
4769
|
const lines = raw.split("\n");
|
|
4520
4770
|
for (const line of lines) {
|
|
@@ -4541,34 +4791,34 @@ ${memory.content}
|
|
|
4541
4791
|
}
|
|
4542
4792
|
async writeCompressionGuidelines(content) {
|
|
4543
4793
|
await this.ensureDirectories();
|
|
4544
|
-
await
|
|
4794
|
+
await writeFile2(this.compressionGuidelinesPath, content, "utf-8");
|
|
4545
4795
|
}
|
|
4546
4796
|
async readCompressionGuidelines() {
|
|
4547
4797
|
try {
|
|
4548
|
-
return await
|
|
4798
|
+
return await readFile2(this.compressionGuidelinesPath, "utf-8");
|
|
4549
4799
|
} catch {
|
|
4550
4800
|
return null;
|
|
4551
4801
|
}
|
|
4552
4802
|
}
|
|
4553
4803
|
async writeCompressionGuidelineDraft(content) {
|
|
4554
4804
|
await this.ensureDirectories();
|
|
4555
|
-
await
|
|
4805
|
+
await writeFile2(this.compressionGuidelineDraftPath, content, "utf-8");
|
|
4556
4806
|
}
|
|
4557
4807
|
async readCompressionGuidelineDraft() {
|
|
4558
4808
|
try {
|
|
4559
|
-
return await
|
|
4809
|
+
return await readFile2(this.compressionGuidelineDraftPath, "utf-8");
|
|
4560
4810
|
} catch {
|
|
4561
4811
|
return null;
|
|
4562
4812
|
}
|
|
4563
4813
|
}
|
|
4564
4814
|
async writeCompressionGuidelineOptimizerState(state) {
|
|
4565
4815
|
await this.ensureDirectories();
|
|
4566
|
-
await
|
|
4816
|
+
await writeFile2(this.compressionGuidelineStatePath, `${JSON.stringify(state, null, 2)}
|
|
4567
4817
|
`, "utf-8");
|
|
4568
4818
|
}
|
|
4569
4819
|
async writeCompressionGuidelineDraftState(state) {
|
|
4570
4820
|
await this.ensureDirectories();
|
|
4571
|
-
await
|
|
4821
|
+
await writeFile2(this.compressionGuidelineDraftStatePath, `${JSON.stringify(state, null, 2)}
|
|
4572
4822
|
`, "utf-8");
|
|
4573
4823
|
}
|
|
4574
4824
|
async readCompressionGuidelineOptimizerState() {
|
|
@@ -4590,8 +4840,8 @@ ${memory.content}
|
|
|
4590
4840
|
return false;
|
|
4591
4841
|
}
|
|
4592
4842
|
if (draftState.contentHash) {
|
|
4593
|
-
const
|
|
4594
|
-
if (
|
|
4843
|
+
const contentHash = createHash("sha256").update(draftContent).digest("hex");
|
|
4844
|
+
if (contentHash !== draftState.contentHash) return false;
|
|
4595
4845
|
}
|
|
4596
4846
|
await this.writeCompressionGuidelines(draftContent);
|
|
4597
4847
|
await this.writeCompressionGuidelineOptimizerState({
|
|
@@ -4599,8 +4849,8 @@ ${memory.content}
|
|
|
4599
4849
|
activationState: "active"
|
|
4600
4850
|
});
|
|
4601
4851
|
await Promise.all([
|
|
4602
|
-
|
|
4603
|
-
|
|
4852
|
+
unlink(this.compressionGuidelineDraftPath).catch(() => void 0),
|
|
4853
|
+
unlink(this.compressionGuidelineDraftStatePath).catch(() => void 0)
|
|
4604
4854
|
]);
|
|
4605
4855
|
return true;
|
|
4606
4856
|
}
|
|
@@ -4617,12 +4867,12 @@ ${memory.content}
|
|
|
4617
4867
|
return typeof rule.action === "string" && typeof rule.delta === "number" && Number.isFinite(rule.delta) && (rule.direction === "increase" || rule.direction === "decrease" || rule.direction === "hold") && (rule.confidence === "low" || rule.confidence === "medium" || rule.confidence === "high") && Array.isArray(rule.notes) && rule.notes.every((note) => typeof note === "string");
|
|
4618
4868
|
};
|
|
4619
4869
|
try {
|
|
4620
|
-
const raw = await
|
|
4870
|
+
const raw = await readFile2(filePath, "utf-8");
|
|
4621
4871
|
const parsed = JSON.parse(raw);
|
|
4622
4872
|
const sourceWindow = parsed?.sourceWindow;
|
|
4623
4873
|
const eventCounts = parsed?.eventCounts;
|
|
4624
4874
|
const activationState = parsed?.activationState === "draft" || parsed?.activationState === "active" ? parsed.activationState : void 0;
|
|
4625
|
-
const
|
|
4875
|
+
const contentHash = typeof parsed?.contentHash === "string" && parsed.contentHash.length > 0 ? parsed.contentHash : void 0;
|
|
4626
4876
|
const actionSummaries = Array.isArray(parsed?.actionSummaries) ? parsed.actionSummaries.filter(isValidActionSummary) : void 0;
|
|
4627
4877
|
const ruleUpdates = Array.isArray(parsed?.ruleUpdates) ? parsed.ruleUpdates.filter(isValidRuleUpdate) : void 0;
|
|
4628
4878
|
if (!isFiniteNonNegativeInteger(parsed?.version) || typeof parsed?.updatedAt !== "string" || parsed.updatedAt.length === 0 || !sourceWindow || typeof sourceWindow.from !== "string" || sourceWindow.from.length === 0 || typeof sourceWindow.to !== "string" || sourceWindow.to.length === 0 || !eventCounts || !isFiniteNonNegativeInteger(eventCounts.total) || !isFiniteNonNegativeInteger(eventCounts.applied) || !isFiniteNonNegativeInteger(eventCounts.skipped) || !isFiniteNonNegativeInteger(eventCounts.failed) || !isFiniteNonNegativeInteger(parsed?.guidelineVersion)) {
|
|
@@ -4642,7 +4892,7 @@ ${memory.content}
|
|
|
4642
4892
|
failed: eventCounts.failed
|
|
4643
4893
|
},
|
|
4644
4894
|
guidelineVersion: parsed.guidelineVersion,
|
|
4645
|
-
...
|
|
4895
|
+
...contentHash ? { contentHash } : {},
|
|
4646
4896
|
...activationState ? { activationState } : {},
|
|
4647
4897
|
...actionSummaries ? { actionSummaries } : {},
|
|
4648
4898
|
...ruleUpdates ? { ruleUpdates } : {}
|
|
@@ -4653,11 +4903,11 @@ ${memory.content}
|
|
|
4653
4903
|
}
|
|
4654
4904
|
async writeIdentityAnchor(content) {
|
|
4655
4905
|
await this.ensureDirectories();
|
|
4656
|
-
await
|
|
4906
|
+
await writeFile2(this.identityAnchorPath, content, "utf-8");
|
|
4657
4907
|
}
|
|
4658
4908
|
async readIdentityAnchor() {
|
|
4659
4909
|
try {
|
|
4660
|
-
return await
|
|
4910
|
+
return await readFile2(this.identityAnchorPath, "utf-8");
|
|
4661
4911
|
} catch {
|
|
4662
4912
|
return null;
|
|
4663
4913
|
}
|
|
@@ -4669,8 +4919,8 @@ ${memory.content}
|
|
|
4669
4919
|
const date = nowIso.slice(0, 10);
|
|
4670
4920
|
const id = this.generateId("incident");
|
|
4671
4921
|
const incident = createContinuityIncidentRecord(id, input, nowIso);
|
|
4672
|
-
const filePath =
|
|
4673
|
-
await
|
|
4922
|
+
const filePath = path4.join(this.identityIncidentsDir, `${date}-${id}.md`);
|
|
4923
|
+
await writeFile2(filePath, serializeContinuityIncident(incident), "utf-8");
|
|
4674
4924
|
return { ...incident, filePath };
|
|
4675
4925
|
}
|
|
4676
4926
|
async readContinuityIncidents(limit = 200, state = "all") {
|
|
@@ -4682,9 +4932,9 @@ ${memory.content}
|
|
|
4682
4932
|
const incidents = [];
|
|
4683
4933
|
for (const file of candidates) {
|
|
4684
4934
|
if (incidents.length >= cappedLimit) break;
|
|
4685
|
-
const filePath =
|
|
4935
|
+
const filePath = path4.join(this.identityIncidentsDir, file);
|
|
4686
4936
|
try {
|
|
4687
|
-
const raw = await
|
|
4937
|
+
const raw = await readFile2(filePath, "utf-8");
|
|
4688
4938
|
const parsed = parseContinuityIncident(raw);
|
|
4689
4939
|
if (!parsed) continue;
|
|
4690
4940
|
if (state !== "all" && parsed.state !== state) continue;
|
|
@@ -4703,33 +4953,33 @@ ${memory.content}
|
|
|
4703
4953
|
if (!target || !directFilePath) return null;
|
|
4704
4954
|
if (target.state === "closed") return target;
|
|
4705
4955
|
const closed = closeContinuityIncidentRecord(target, closure, (/* @__PURE__ */ new Date()).toISOString());
|
|
4706
|
-
await
|
|
4956
|
+
await writeFile2(directFilePath, serializeContinuityIncident(closed), "utf-8");
|
|
4707
4957
|
return { ...closed, filePath: directFilePath };
|
|
4708
4958
|
}
|
|
4709
4959
|
async writeIdentityAudit(period, key, content) {
|
|
4710
4960
|
await this.ensureDirectories();
|
|
4711
4961
|
const safeKey = this.sanitizeIdentityAuditKey(key);
|
|
4712
4962
|
const dir = period === "weekly" ? this.identityAuditsWeeklyDir : this.identityAuditsMonthlyDir;
|
|
4713
|
-
const filePath =
|
|
4714
|
-
await
|
|
4963
|
+
const filePath = path4.join(dir, `${safeKey}.md`);
|
|
4964
|
+
await writeFile2(filePath, content, "utf-8");
|
|
4715
4965
|
return filePath;
|
|
4716
4966
|
}
|
|
4717
4967
|
async readIdentityAudit(period, key) {
|
|
4718
4968
|
try {
|
|
4719
4969
|
const safeKey = this.sanitizeIdentityAuditKey(key);
|
|
4720
4970
|
const dir = period === "weekly" ? this.identityAuditsWeeklyDir : this.identityAuditsMonthlyDir;
|
|
4721
|
-
return await
|
|
4971
|
+
return await readFile2(path4.join(dir, `${safeKey}.md`), "utf-8");
|
|
4722
4972
|
} catch {
|
|
4723
4973
|
return null;
|
|
4724
4974
|
}
|
|
4725
4975
|
}
|
|
4726
4976
|
async writeIdentityImprovementLoops(content) {
|
|
4727
4977
|
await this.ensureDirectories();
|
|
4728
|
-
await
|
|
4978
|
+
await writeFile2(this.identityImprovementLoopsPath, content, "utf-8");
|
|
4729
4979
|
}
|
|
4730
4980
|
async readIdentityImprovementLoops() {
|
|
4731
4981
|
try {
|
|
4732
|
-
return await
|
|
4982
|
+
return await readFile2(this.identityImprovementLoopsPath, "utf-8");
|
|
4733
4983
|
} catch {
|
|
4734
4984
|
return null;
|
|
4735
4985
|
}
|
|
@@ -4767,7 +5017,7 @@ ${memory.content}
|
|
|
4767
5017
|
}
|
|
4768
5018
|
async readContinuityIncidentFile(filePath) {
|
|
4769
5019
|
try {
|
|
4770
|
-
const raw = await
|
|
5020
|
+
const raw = await readFile2(filePath, "utf-8");
|
|
4771
5021
|
const parsed = parseContinuityIncident(raw);
|
|
4772
5022
|
return parsed ? { ...parsed, filePath } : null;
|
|
4773
5023
|
} catch {
|
|
@@ -4778,12 +5028,12 @@ ${memory.content}
|
|
|
4778
5028
|
const fileNames = await this.readContinuityIncidentFileNames();
|
|
4779
5029
|
const directMatch = fileNames.find((name) => name.endsWith(`-${id}.md`));
|
|
4780
5030
|
if (directMatch) {
|
|
4781
|
-
const directPath =
|
|
5031
|
+
const directPath = path4.join(this.identityIncidentsDir, directMatch);
|
|
4782
5032
|
const parsed = await this.readContinuityIncidentFile(directPath);
|
|
4783
5033
|
if (parsed?.id === id) return directPath;
|
|
4784
5034
|
}
|
|
4785
5035
|
for (const fileName of fileNames) {
|
|
4786
|
-
const filePath =
|
|
5036
|
+
const filePath = path4.join(this.identityIncidentsDir, fileName);
|
|
4787
5037
|
const parsed = await this.readContinuityIncidentFile(filePath);
|
|
4788
5038
|
if (parsed?.id === id) return filePath;
|
|
4789
5039
|
}
|
|
@@ -4797,7 +5047,7 @@ ${memory.content}
|
|
|
4797
5047
|
return trimmed;
|
|
4798
5048
|
}
|
|
4799
5049
|
async writeQuestion(question, context, priority) {
|
|
4800
|
-
await
|
|
5050
|
+
await mkdir2(this.questionsDir, { recursive: true });
|
|
4801
5051
|
const id = this.generateId("q");
|
|
4802
5052
|
const frontmatter = {
|
|
4803
5053
|
id,
|
|
@@ -4813,8 +5063,8 @@ ${question}
|
|
|
4813
5063
|
|
|
4814
5064
|
**Context:** ${context}
|
|
4815
5065
|
`;
|
|
4816
|
-
const filePath =
|
|
4817
|
-
await
|
|
5066
|
+
const filePath = path4.join(this.questionsDir, `${id}.md`);
|
|
5067
|
+
await writeFile2(filePath, content, "utf-8");
|
|
4818
5068
|
log.debug(`wrote question ${id} to ${filePath}`);
|
|
4819
5069
|
this.invalidateQuestionsCache();
|
|
4820
5070
|
return id;
|
|
@@ -4837,8 +5087,8 @@ ${question}
|
|
|
4837
5087
|
const questions = [];
|
|
4838
5088
|
for (const file of files) {
|
|
4839
5089
|
if (!file.endsWith(".md")) continue;
|
|
4840
|
-
const filePath =
|
|
4841
|
-
const raw = await
|
|
5090
|
+
const filePath = path4.join(this.questionsDir, file);
|
|
5091
|
+
const raw = await readFile2(filePath, "utf-8");
|
|
4842
5092
|
const parsed = this.parseQuestionFile(raw, filePath);
|
|
4843
5093
|
if (parsed) {
|
|
4844
5094
|
questions.push(parsed);
|
|
@@ -4860,7 +5110,7 @@ ${question}
|
|
|
4860
5110
|
if (!match) return null;
|
|
4861
5111
|
const frontmatterStr = match[1];
|
|
4862
5112
|
const body = match[2].trim();
|
|
4863
|
-
const id = this.extractFrontmatterValue(frontmatterStr, "id") ??
|
|
5113
|
+
const id = this.extractFrontmatterValue(frontmatterStr, "id") ?? path4.basename(filePath, ".md");
|
|
4864
5114
|
const created = this.extractFrontmatterValue(frontmatterStr, "created") ?? "";
|
|
4865
5115
|
const priority = parseFloat(
|
|
4866
5116
|
this.extractFrontmatterValue(frontmatterStr, "priority") ?? "0.5"
|
|
@@ -4881,7 +5131,7 @@ ${question}
|
|
|
4881
5131
|
const questions = await this.readQuestions();
|
|
4882
5132
|
const q = questions.find((q2) => q2.id === id);
|
|
4883
5133
|
if (!q) return false;
|
|
4884
|
-
let raw = await
|
|
5134
|
+
let raw = await readFile2(q.filePath, "utf-8");
|
|
4885
5135
|
raw = raw.replace(/resolved: false/, "resolved: true");
|
|
4886
5136
|
raw = raw.replace(
|
|
4887
5137
|
/---\n\n/,
|
|
@@ -4890,7 +5140,7 @@ ${question}
|
|
|
4890
5140
|
|
|
4891
5141
|
`
|
|
4892
5142
|
);
|
|
4893
|
-
await
|
|
5143
|
+
await writeFile2(q.filePath, raw, "utf-8");
|
|
4894
5144
|
log.debug(`resolved question ${id}`);
|
|
4895
5145
|
return true;
|
|
4896
5146
|
}
|
|
@@ -4900,14 +5150,14 @@ ${question}
|
|
|
4900
5150
|
async readIdentity(workspaceDir, namespace) {
|
|
4901
5151
|
const identityPath = this.identityFilePath(workspaceDir, namespace);
|
|
4902
5152
|
try {
|
|
4903
|
-
return await
|
|
5153
|
+
return await readFile2(identityPath, "utf-8");
|
|
4904
5154
|
} catch {
|
|
4905
5155
|
return "";
|
|
4906
5156
|
}
|
|
4907
5157
|
}
|
|
4908
5158
|
async writeIdentity(workspaceDir, content, namespace) {
|
|
4909
5159
|
const identityPath = this.identityFilePath(workspaceDir, namespace);
|
|
4910
|
-
await
|
|
5160
|
+
await writeFile2(identityPath, content, "utf-8");
|
|
4911
5161
|
log.debug(`wrote consolidated IDENTITY.md (${content.length} chars)`);
|
|
4912
5162
|
}
|
|
4913
5163
|
/** Max size for IDENTITY.md before we stop appending reflections (15KB leaves room under 20KB gateway limit) */
|
|
@@ -4918,22 +5168,22 @@ ${question}
|
|
|
4918
5168
|
const identityPath = this.identityFilePath(workspaceDir, opts?.namespace);
|
|
4919
5169
|
let existing = "";
|
|
4920
5170
|
try {
|
|
4921
|
-
existing = await
|
|
5171
|
+
existing = await readFile2(identityPath, "utf-8");
|
|
4922
5172
|
} catch {
|
|
4923
5173
|
}
|
|
4924
5174
|
const hygiene = opts?.hygiene;
|
|
4925
|
-
const rotateEnabled = hygiene?.enabled === true && hygiene.rotateEnabled === true && Array.isArray(hygiene.rotatePaths) && hygiene.rotatePaths.includes(
|
|
5175
|
+
const rotateEnabled = hygiene?.enabled === true && hygiene.rotateEnabled === true && Array.isArray(hygiene.rotatePaths) && hygiene.rotatePaths.includes(path4.basename(identityPath));
|
|
4926
5176
|
if (rotateEnabled) {
|
|
4927
5177
|
const maxBytes = hygiene.rotateMaxBytes;
|
|
4928
5178
|
if (existing.length > maxBytes) {
|
|
4929
|
-
const archiveDir =
|
|
5179
|
+
const archiveDir = path4.join(workspaceDir, hygiene.archiveDir);
|
|
4930
5180
|
const { newContent } = await rotateMarkdownFileToArchive({
|
|
4931
5181
|
filePath: identityPath,
|
|
4932
5182
|
archiveDir,
|
|
4933
5183
|
archivePrefix: "IDENTITY",
|
|
4934
5184
|
keepTailChars: hygiene.rotateKeepTailChars
|
|
4935
5185
|
});
|
|
4936
|
-
await
|
|
5186
|
+
await writeFile2(identityPath, newContent, "utf-8");
|
|
4937
5187
|
existing = newContent;
|
|
4938
5188
|
log.info(
|
|
4939
5189
|
`rotated IDENTITY.md to archive (size=${existing.length} chars, maxBytes=${maxBytes})`
|
|
@@ -4964,24 +5214,24 @@ ${question}
|
|
|
4964
5214
|
|
|
4965
5215
|
${reflection}
|
|
4966
5216
|
`;
|
|
4967
|
-
await
|
|
5217
|
+
await writeFile2(identityPath, existing + section, "utf-8");
|
|
4968
5218
|
log.debug(`appended reflection to ${identityPath}`);
|
|
4969
5219
|
}
|
|
4970
5220
|
async readIdentityReflections() {
|
|
4971
5221
|
try {
|
|
4972
|
-
return await
|
|
5222
|
+
return await readFile2(this.identityReflectionsPath, "utf-8");
|
|
4973
5223
|
} catch {
|
|
4974
5224
|
return null;
|
|
4975
5225
|
}
|
|
4976
5226
|
}
|
|
4977
5227
|
async writeIdentityReflections(content) {
|
|
4978
|
-
await
|
|
4979
|
-
await
|
|
5228
|
+
await mkdir2(this.identityDir, { recursive: true });
|
|
5229
|
+
await writeFile2(this.identityReflectionsPath, content, "utf-8");
|
|
4980
5230
|
}
|
|
4981
5231
|
async appendIdentityReflection(reflection) {
|
|
4982
5232
|
let existing = "";
|
|
4983
5233
|
try {
|
|
4984
|
-
existing = await
|
|
5234
|
+
existing = await readFile2(this.identityReflectionsPath, "utf-8");
|
|
4985
5235
|
} catch {
|
|
4986
5236
|
}
|
|
4987
5237
|
if (existing.length > _StorageManager.IDENTITY_MAX_BYTES) {
|
|
@@ -5006,8 +5256,8 @@ ${reflection}
|
|
|
5006
5256
|
|
|
5007
5257
|
${reflection}
|
|
5008
5258
|
`;
|
|
5009
|
-
await
|
|
5010
|
-
await
|
|
5259
|
+
await mkdir2(this.identityDir, { recursive: true });
|
|
5260
|
+
await writeFile2(this.identityReflectionsPath, `${existing.trimEnd()}${section}`, "utf-8");
|
|
5011
5261
|
log.debug(`appended namespace-local reflection to ${this.identityReflectionsPath}`);
|
|
5012
5262
|
}
|
|
5013
5263
|
// ---------------------------------------------------------------------------
|
|
@@ -5018,10 +5268,10 @@ ${reflection}
|
|
|
5018
5268
|
* Deduplicates by target+label.
|
|
5019
5269
|
*/
|
|
5020
5270
|
async addEntityRelationship(name, rel) {
|
|
5021
|
-
const filePath =
|
|
5271
|
+
const filePath = path4.join(this.entitiesDir, `${name}.md`);
|
|
5022
5272
|
let entity;
|
|
5023
5273
|
try {
|
|
5024
|
-
const content = await
|
|
5274
|
+
const content = await readFile2(filePath, "utf-8");
|
|
5025
5275
|
entity = parseEntityFile(content, this.entitySchemas);
|
|
5026
5276
|
} catch {
|
|
5027
5277
|
log.debug(`addEntityRelationship: entity file ${name}.md not found`);
|
|
@@ -5033,7 +5283,7 @@ ${reflection}
|
|
|
5033
5283
|
if (exists) return;
|
|
5034
5284
|
entity.relationships.push(rel);
|
|
5035
5285
|
entity.updated = (/* @__PURE__ */ new Date()).toISOString();
|
|
5036
|
-
await
|
|
5286
|
+
await writeFile2(filePath, serializeEntityFile(entity, this.entitySchemas), "utf-8");
|
|
5037
5287
|
this.invalidateKnowledgeIndexCache();
|
|
5038
5288
|
}
|
|
5039
5289
|
/**
|
|
@@ -5041,10 +5291,10 @@ ${reflection}
|
|
|
5041
5291
|
* Prepends to the beginning, prunes oldest entries beyond maxEntries.
|
|
5042
5292
|
*/
|
|
5043
5293
|
async addEntityActivity(name, entry, maxEntries) {
|
|
5044
|
-
const filePath =
|
|
5294
|
+
const filePath = path4.join(this.entitiesDir, `${name}.md`);
|
|
5045
5295
|
let entity;
|
|
5046
5296
|
try {
|
|
5047
|
-
const content = await
|
|
5297
|
+
const content = await readFile2(filePath, "utf-8");
|
|
5048
5298
|
entity = parseEntityFile(content, this.entitySchemas);
|
|
5049
5299
|
} catch {
|
|
5050
5300
|
log.debug(`addEntityActivity: entity file ${name}.md not found`);
|
|
@@ -5055,17 +5305,17 @@ ${reflection}
|
|
|
5055
5305
|
entity.activity = entity.activity.slice(0, maxEntries);
|
|
5056
5306
|
}
|
|
5057
5307
|
entity.updated = (/* @__PURE__ */ new Date()).toISOString();
|
|
5058
|
-
await
|
|
5308
|
+
await writeFile2(filePath, serializeEntityFile(entity, this.entitySchemas), "utf-8");
|
|
5059
5309
|
this.invalidateKnowledgeIndexCache();
|
|
5060
5310
|
}
|
|
5061
5311
|
/**
|
|
5062
5312
|
* Add an alias to an entity file. Deduplicates.
|
|
5063
5313
|
*/
|
|
5064
5314
|
async addEntityAlias(name, alias) {
|
|
5065
|
-
const filePath =
|
|
5315
|
+
const filePath = path4.join(this.entitiesDir, `${name}.md`);
|
|
5066
5316
|
let entity;
|
|
5067
5317
|
try {
|
|
5068
|
-
const content = await
|
|
5318
|
+
const content = await readFile2(filePath, "utf-8");
|
|
5069
5319
|
entity = parseEntityFile(content, this.entitySchemas);
|
|
5070
5320
|
} catch {
|
|
5071
5321
|
log.debug(`addEntityAlias: entity file ${name}.md not found`);
|
|
@@ -5074,17 +5324,17 @@ ${reflection}
|
|
|
5074
5324
|
if (entity.aliases.includes(alias)) return;
|
|
5075
5325
|
entity.aliases.push(alias);
|
|
5076
5326
|
entity.updated = (/* @__PURE__ */ new Date()).toISOString();
|
|
5077
|
-
await
|
|
5327
|
+
await writeFile2(filePath, serializeEntityFile(entity, this.entitySchemas), "utf-8");
|
|
5078
5328
|
this.invalidateKnowledgeIndexCache();
|
|
5079
5329
|
}
|
|
5080
5330
|
/**
|
|
5081
5331
|
* Set or rewrite the synthesis layer of an entity file.
|
|
5082
5332
|
*/
|
|
5083
5333
|
async updateEntitySynthesis(name, synthesis, options = {}) {
|
|
5084
|
-
const filePath =
|
|
5334
|
+
const filePath = path4.join(this.entitiesDir, `${name}.md`);
|
|
5085
5335
|
let entity;
|
|
5086
5336
|
try {
|
|
5087
|
-
const content = await
|
|
5337
|
+
const content = await readFile2(filePath, "utf-8");
|
|
5088
5338
|
entity = parseEntityFile(content, this.entitySchemas);
|
|
5089
5339
|
} catch {
|
|
5090
5340
|
log.debug(`updateEntitySynthesis: entity file ${name}.md not found`);
|
|
@@ -5103,7 +5353,7 @@ ${reflection}
|
|
|
5103
5353
|
entity.synthesisStructuredFactDigest = synthesisStructuredFactDigest;
|
|
5104
5354
|
entity.synthesisVersion = Math.max(0, entity.synthesisVersion ?? 0) + (options.incrementVersion === false ? 0 : 1);
|
|
5105
5355
|
entity.updated = entityUpdatedAt;
|
|
5106
|
-
await
|
|
5356
|
+
await writeFile2(filePath, serializeEntityFile(entity, this.entitySchemas), "utf-8");
|
|
5107
5357
|
await this.removeEntitySynthesisQueueEntries([
|
|
5108
5358
|
.../* @__PURE__ */ new Set([name, normalizeEntityName(entity.name, entity.type)])
|
|
5109
5359
|
]);
|
|
@@ -5117,8 +5367,8 @@ ${reflection}
|
|
|
5117
5367
|
const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5118
5368
|
let synthesisTimelineCount;
|
|
5119
5369
|
try {
|
|
5120
|
-
const filePath =
|
|
5121
|
-
const content = await
|
|
5370
|
+
const filePath = path4.join(this.entitiesDir, `${name}.md`);
|
|
5371
|
+
const content = await readFile2(filePath, "utf-8");
|
|
5122
5372
|
synthesisTimelineCount = parseEntityFile(content, this.entitySchemas).timeline.length;
|
|
5123
5373
|
} catch {
|
|
5124
5374
|
synthesisTimelineCount = void 0;
|
|
@@ -5131,7 +5381,7 @@ ${reflection}
|
|
|
5131
5381
|
}
|
|
5132
5382
|
async readEntitySynthesisQueue() {
|
|
5133
5383
|
try {
|
|
5134
|
-
const raw = await
|
|
5384
|
+
const raw = await readFile2(this.entitySynthesisQueuePath, "utf-8");
|
|
5135
5385
|
const parsed = JSON.parse(raw);
|
|
5136
5386
|
return Array.isArray(parsed.entityNames) ? parsed.entityNames.filter((value) => typeof value === "string") : [];
|
|
5137
5387
|
} catch {
|
|
@@ -5155,8 +5405,8 @@ ${reflection}
|
|
|
5155
5405
|
const rightTs = latestEntityTimelineTimestamp(right.entity) ?? "";
|
|
5156
5406
|
return compareEntityTimestamps(rightTs, leftTs);
|
|
5157
5407
|
}).map(({ entityName }) => entityName);
|
|
5158
|
-
await
|
|
5159
|
-
await
|
|
5408
|
+
await mkdir2(this.stateDir, { recursive: true });
|
|
5409
|
+
await writeFile2(
|
|
5160
5410
|
this.entitySynthesisQueuePath,
|
|
5161
5411
|
JSON.stringify(
|
|
5162
5412
|
{
|
|
@@ -5176,8 +5426,8 @@ ${reflection}
|
|
|
5176
5426
|
if (queue.length === 0) return;
|
|
5177
5427
|
const removals = new Set(entityNames);
|
|
5178
5428
|
const nextQueue = queue.filter((name) => !removals.has(name));
|
|
5179
|
-
await
|
|
5180
|
-
await
|
|
5429
|
+
await mkdir2(this.stateDir, { recursive: true });
|
|
5430
|
+
await writeFile2(
|
|
5181
5431
|
this.entitySynthesisQueuePath,
|
|
5182
5432
|
JSON.stringify(
|
|
5183
5433
|
{
|
|
@@ -5198,7 +5448,7 @@ ${reflection}
|
|
|
5198
5448
|
if (!raw) continue;
|
|
5199
5449
|
const serialized = serializeEntityFile(parseEntityFile(raw, this.entitySchemas), this.entitySchemas);
|
|
5200
5450
|
if (raw.trimEnd() === serialized.trimEnd()) continue;
|
|
5201
|
-
await
|
|
5451
|
+
await writeFile2(path4.join(this.entitiesDir, `${entityName}.md`), serialized, "utf-8");
|
|
5202
5452
|
migrated += 1;
|
|
5203
5453
|
}
|
|
5204
5454
|
if (migrated > 0) {
|
|
@@ -5232,7 +5482,7 @@ ${reflection}
|
|
|
5232
5482
|
const batch = mdFiles.slice(i, i + BATCH_SIZE);
|
|
5233
5483
|
const results = await Promise.all(
|
|
5234
5484
|
batch.map(
|
|
5235
|
-
(entry) =>
|
|
5485
|
+
(entry) => readFile2(path4.join(this.entitiesDir, entry), "utf-8").catch(() => null)
|
|
5236
5486
|
)
|
|
5237
5487
|
);
|
|
5238
5488
|
for (const content of results) {
|
|
@@ -5372,9 +5622,9 @@ ${rows.join("\n")}
|
|
|
5372
5622
|
extraSections: []
|
|
5373
5623
|
};
|
|
5374
5624
|
for (const file of files) {
|
|
5375
|
-
const filePath =
|
|
5625
|
+
const filePath = path4.join(this.entitiesDir, file);
|
|
5376
5626
|
try {
|
|
5377
|
-
const content = await
|
|
5627
|
+
const content = await readFile2(filePath, "utf-8");
|
|
5378
5628
|
const parsed = parseEntityFile(content, this.entitySchemas);
|
|
5379
5629
|
if (!mergedEntity.type || mergedEntity.type === "other") {
|
|
5380
5630
|
mergedEntity.type = parsed.type;
|
|
@@ -5501,13 +5751,13 @@ ${rows.join("\n")}
|
|
|
5501
5751
|
}
|
|
5502
5752
|
mergedEntity.created = mergedEntity.created || mergedEntity.updated || (/* @__PURE__ */ new Date()).toISOString();
|
|
5503
5753
|
mergedEntity.updated = mergedEntity.updated || (/* @__PURE__ */ new Date()).toISOString();
|
|
5504
|
-
const canonicalPath =
|
|
5505
|
-
await
|
|
5754
|
+
const canonicalPath = path4.join(this.entitiesDir, `${canonical}.md`);
|
|
5755
|
+
await writeFile2(canonicalPath, serializeEntityFile(mergedEntity, this.entitySchemas), "utf-8");
|
|
5506
5756
|
for (const file of files) {
|
|
5507
|
-
const filePath =
|
|
5757
|
+
const filePath = path4.join(this.entitiesDir, file);
|
|
5508
5758
|
if (filePath !== canonicalPath) {
|
|
5509
5759
|
try {
|
|
5510
|
-
await
|
|
5760
|
+
await unlink(filePath);
|
|
5511
5761
|
merged++;
|
|
5512
5762
|
log.debug(`merged entity ${file} \u2192 ${canonical}.md`);
|
|
5513
5763
|
} catch {
|
|
@@ -5532,7 +5782,7 @@ ${rows.join("\n")}
|
|
|
5532
5782
|
const updatedAt = new Date(m.frontmatter.updated).getTime();
|
|
5533
5783
|
if (updatedAt < cutoff) {
|
|
5534
5784
|
try {
|
|
5535
|
-
await
|
|
5785
|
+
await unlink(m.path);
|
|
5536
5786
|
deleted.push(m);
|
|
5537
5787
|
log.debug(`cleaned expired commitment ${m.frontmatter.id}`);
|
|
5538
5788
|
} catch {
|
|
@@ -5569,7 +5819,7 @@ ${rows.join("\n")}
|
|
|
5569
5819
|
${memory.content}
|
|
5570
5820
|
`;
|
|
5571
5821
|
try {
|
|
5572
|
-
await
|
|
5822
|
+
await writeFile2(memory.path, fileContent, "utf-8");
|
|
5573
5823
|
updated++;
|
|
5574
5824
|
} catch (err) {
|
|
5575
5825
|
log.debug(`failed to update access tracking for ${entry.memoryId}: ${err}`);
|
|
@@ -5602,7 +5852,7 @@ ${memory.content}
|
|
|
5602
5852
|
const filePaths = await this.collectActiveMemoryPaths();
|
|
5603
5853
|
const foundIds = /* @__PURE__ */ new Set();
|
|
5604
5854
|
for (const filePath of filePaths) {
|
|
5605
|
-
const basename =
|
|
5855
|
+
const basename = path4.basename(filePath, ".md");
|
|
5606
5856
|
if (wantedIds.has(basename)) {
|
|
5607
5857
|
foundIds.add(basename);
|
|
5608
5858
|
if (foundIds.size === wantedIds.size) break;
|
|
@@ -5700,14 +5950,17 @@ ${sanitized.text}
|
|
|
5700
5950
|
`;
|
|
5701
5951
|
let filePath;
|
|
5702
5952
|
if (category === "correction") {
|
|
5703
|
-
filePath =
|
|
5953
|
+
filePath = path4.join(this.correctionsDir, `${id}.md`);
|
|
5704
5954
|
} else if (category === "procedure") {
|
|
5705
|
-
await
|
|
5706
|
-
filePath =
|
|
5955
|
+
await mkdir2(path4.join(this.proceduresDir, today), { recursive: true });
|
|
5956
|
+
filePath = path4.join(this.proceduresDir, today, `${id}.md`);
|
|
5957
|
+
} else if (category === "reasoning_trace") {
|
|
5958
|
+
await mkdir2(path4.join(this.reasoningTracesDir, today), { recursive: true });
|
|
5959
|
+
filePath = path4.join(this.reasoningTracesDir, today, `${id}.md`);
|
|
5707
5960
|
} else {
|
|
5708
|
-
filePath =
|
|
5961
|
+
filePath = path4.join(this.factsDir, today, `${id}.md`);
|
|
5709
5962
|
}
|
|
5710
|
-
await
|
|
5963
|
+
await writeFile2(filePath, fileContent, "utf-8");
|
|
5711
5964
|
log.debug(`wrote chunk ${id} (${chunkIndex + 1}/${chunkTotal}) to ${filePath}`);
|
|
5712
5965
|
return id;
|
|
5713
5966
|
}
|
|
@@ -5743,7 +5996,7 @@ ${sanitized.text}
|
|
|
5743
5996
|
${oldMemory.content}
|
|
5744
5997
|
`;
|
|
5745
5998
|
try {
|
|
5746
|
-
await
|
|
5999
|
+
await writeFile2(oldMemory.path, fileContent, "utf-8");
|
|
5747
6000
|
await this.appendGeneratedMemoryLifecycleEventFailOpen("storage.supersedeMemory", {
|
|
5748
6001
|
memoryId: oldMemoryId,
|
|
5749
6002
|
eventType: "superseded",
|
|
@@ -5774,15 +6027,15 @@ Reason: ${reason}`, {
|
|
|
5774
6027
|
// Memory Summarization (Phase 4A)
|
|
5775
6028
|
// ---------------------------------------------------------------------------
|
|
5776
6029
|
get summariesDir() {
|
|
5777
|
-
return
|
|
6030
|
+
return path4.join(this.baseDir, "summaries");
|
|
5778
6031
|
}
|
|
5779
6032
|
/**
|
|
5780
6033
|
* Write a memory summary.
|
|
5781
6034
|
*/
|
|
5782
6035
|
async writeSummary(summary) {
|
|
5783
|
-
await
|
|
5784
|
-
const filePath =
|
|
5785
|
-
await
|
|
6036
|
+
await mkdir2(this.summariesDir, { recursive: true });
|
|
6037
|
+
const filePath = path4.join(this.summariesDir, `${summary.id}.json`);
|
|
6038
|
+
await writeFile2(filePath, JSON.stringify(summary, null, 2), "utf-8");
|
|
5786
6039
|
log.debug(`wrote summary ${summary.id}`);
|
|
5787
6040
|
}
|
|
5788
6041
|
/**
|
|
@@ -5794,8 +6047,8 @@ Reason: ${reason}`, {
|
|
|
5794
6047
|
const summaries = [];
|
|
5795
6048
|
for (const file of files) {
|
|
5796
6049
|
if (!file.endsWith(".json")) continue;
|
|
5797
|
-
const filePath =
|
|
5798
|
-
const raw = await
|
|
6050
|
+
const filePath = path4.join(this.summariesDir, file);
|
|
6051
|
+
const raw = await readFile2(filePath, "utf-8");
|
|
5799
6052
|
summaries.push(JSON.parse(raw));
|
|
5800
6053
|
}
|
|
5801
6054
|
return summaries;
|
|
@@ -5825,7 +6078,7 @@ Reason: ${reason}`, {
|
|
|
5825
6078
|
${memory.content}
|
|
5826
6079
|
`;
|
|
5827
6080
|
try {
|
|
5828
|
-
await
|
|
6081
|
+
await writeFile2(memory.path, fileContent, "utf-8");
|
|
5829
6082
|
await this.appendGeneratedMemoryLifecycleEventFailOpen("storage.archiveMemories", {
|
|
5830
6083
|
memoryId: id,
|
|
5831
6084
|
eventType: "archived",
|
|
@@ -5853,18 +6106,18 @@ ${memory.content}
|
|
|
5853
6106
|
* Save topic scores to meta.json.
|
|
5854
6107
|
*/
|
|
5855
6108
|
async saveTopics(topics) {
|
|
5856
|
-
const metaPath =
|
|
5857
|
-
await
|
|
5858
|
-
await
|
|
6109
|
+
const metaPath = path4.join(this.stateDir, "topics.json");
|
|
6110
|
+
await mkdir2(this.stateDir, { recursive: true });
|
|
6111
|
+
await writeFile2(metaPath, JSON.stringify({ topics, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2), "utf-8");
|
|
5859
6112
|
log.debug(`saved ${topics.length} topic scores`);
|
|
5860
6113
|
}
|
|
5861
6114
|
/**
|
|
5862
6115
|
* Load topic scores from meta.json.
|
|
5863
6116
|
*/
|
|
5864
6117
|
async loadTopics() {
|
|
5865
|
-
const metaPath =
|
|
6118
|
+
const metaPath = path4.join(this.stateDir, "topics.json");
|
|
5866
6119
|
try {
|
|
5867
|
-
const raw = await
|
|
6120
|
+
const raw = await readFile2(metaPath, "utf-8");
|
|
5868
6121
|
return JSON.parse(raw);
|
|
5869
6122
|
} catch {
|
|
5870
6123
|
return { topics: [], updatedAt: null };
|
|
@@ -5953,6 +6206,7 @@ export {
|
|
|
5953
6206
|
setCachedQmdSearch,
|
|
5954
6207
|
lintWorkspaceFiles,
|
|
5955
6208
|
rotateMarkdownFileToArchive,
|
|
6209
|
+
isConsolidationOperator,
|
|
5956
6210
|
confidenceTier,
|
|
5957
6211
|
openBetterSqlite3,
|
|
5958
6212
|
MEMORY_PROJECTION_SCHEMA_VERSION,
|