@remnic/core 9.3.519 → 9.3.521

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/index.js CHANGED
@@ -967,7 +967,6 @@ var DEFAULT_MAX_BINARY_SIZE_BYTES = 50 * 1024 * 1024;
967
967
  var DEFAULT_GRACE_PERIOD_DAYS = 7;
968
968
 
969
969
  // src/binary-lifecycle/backend.ts
970
- import fs from "fs";
971
970
  import fsp from "fs/promises";
972
971
  import path from "path";
973
972
  var FilesystemBackend = class {
@@ -980,40 +979,172 @@ var FilesystemBackend = class {
980
979
  this.basePath = path.resolve(basePath);
981
980
  }
982
981
  resolveRemotePath(remotePath) {
983
- if (path.isAbsolute(remotePath)) {
984
- throw new Error(`FilesystemBackend remotePath must be relative: ${JSON.stringify(remotePath)}`);
985
- }
986
- const resolved = path.resolve(this.basePath, remotePath);
982
+ const resolved = path.isAbsolute(remotePath) ? path.resolve(remotePath) : path.resolve(this.basePath, remotePath);
987
983
  const relative = path.relative(this.basePath, resolved);
988
984
  if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
989
985
  throw new Error(`FilesystemBackend remotePath escapes basePath: ${JSON.stringify(remotePath)}`);
990
986
  }
991
987
  return resolved;
992
988
  }
993
- async upload(localPath, remotePath) {
989
+ isInsideBase(candidate, realBase) {
990
+ const relative = path.relative(realBase, candidate);
991
+ return relative === "" || relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
992
+ }
993
+ async realBasePathIfExists() {
994
+ try {
995
+ const stat = await fsp.lstat(this.basePath);
996
+ if (stat.isSymbolicLink()) {
997
+ throw new Error(`FilesystemBackend basePath must not be a symlink: ${this.basePath}`);
998
+ }
999
+ if (!stat.isDirectory()) {
1000
+ throw new Error(`FilesystemBackend basePath must be a directory: ${this.basePath}`);
1001
+ }
1002
+ return await fsp.realpath(this.basePath);
1003
+ } catch (err) {
1004
+ if (err.code === "ENOENT") {
1005
+ return null;
1006
+ }
1007
+ throw err;
1008
+ }
1009
+ }
1010
+ async ensureBaseDirectory() {
1011
+ await fsp.mkdir(this.basePath, { recursive: true });
1012
+ const realBase = await this.realBasePathIfExists();
1013
+ if (realBase === null) {
1014
+ throw new Error(`FilesystemBackend failed to create basePath: ${this.basePath}`);
1015
+ }
1016
+ return realBase;
1017
+ }
1018
+ async ensureSafeParentDirectory(dest) {
1019
+ const realBase = await this.ensureBaseDirectory();
1020
+ const destDir = path.dirname(dest);
1021
+ const relativeDir = path.relative(this.basePath, destDir);
1022
+ const segments = relativeDir === "" ? [] : relativeDir.split(path.sep);
1023
+ let current = this.basePath;
1024
+ for (const segment of segments) {
1025
+ if (segment === "." || segment === "") continue;
1026
+ current = path.join(current, segment);
1027
+ try {
1028
+ const stat = await fsp.lstat(current);
1029
+ if (stat.isSymbolicLink()) {
1030
+ throw new Error(`FilesystemBackend remotePath traverses symlink: ${current}`);
1031
+ }
1032
+ if (!stat.isDirectory()) {
1033
+ throw new Error(`FilesystemBackend remotePath parent is not a directory: ${current}`);
1034
+ }
1035
+ } catch (err) {
1036
+ if (err.code !== "ENOENT") {
1037
+ throw err;
1038
+ }
1039
+ await fsp.mkdir(current);
1040
+ }
1041
+ }
1042
+ const realParent = await fsp.realpath(destDir);
1043
+ if (!this.isInsideBase(realParent, realBase)) {
1044
+ throw new Error(`FilesystemBackend remotePath parent escapes basePath: ${dest}`);
1045
+ }
1046
+ return realBase;
1047
+ }
1048
+ async resolveExistingRemotePath(remotePath) {
994
1049
  const dest = this.resolveRemotePath(remotePath);
1050
+ const realBase = await this.realBasePathIfExists();
1051
+ if (realBase === null) {
1052
+ return null;
1053
+ }
995
1054
  const destDir = path.dirname(dest);
996
- await fsp.mkdir(destDir, { recursive: true });
997
- await fsp.copyFile(localPath, dest);
1055
+ const relativeDir = path.relative(this.basePath, destDir);
1056
+ const segments = relativeDir === "" ? [] : relativeDir.split(path.sep);
1057
+ let current = this.basePath;
1058
+ for (const segment of segments) {
1059
+ if (segment === "." || segment === "") continue;
1060
+ current = path.join(current, segment);
1061
+ let stat;
1062
+ try {
1063
+ stat = await fsp.lstat(current);
1064
+ } catch (err) {
1065
+ if (err.code === "ENOENT") {
1066
+ return null;
1067
+ }
1068
+ throw err;
1069
+ }
1070
+ if (stat.isSymbolicLink()) {
1071
+ throw new Error(`FilesystemBackend remotePath traverses symlink: ${current}`);
1072
+ }
1073
+ if (!stat.isDirectory()) {
1074
+ return null;
1075
+ }
1076
+ }
1077
+ const realParent = await fsp.realpath(destDir).catch((err) => {
1078
+ if (err.code === "ENOENT") return null;
1079
+ throw err;
1080
+ });
1081
+ if (realParent === null) {
1082
+ return null;
1083
+ }
1084
+ if (!this.isInsideBase(realParent, realBase)) {
1085
+ throw new Error(`FilesystemBackend remotePath parent escapes basePath: ${JSON.stringify(remotePath)}`);
1086
+ }
1087
+ try {
1088
+ const stat = await fsp.lstat(dest);
1089
+ if (stat.isSymbolicLink()) {
1090
+ throw new Error(`FilesystemBackend remotePath points to symlink: ${dest}`);
1091
+ }
1092
+ if (!stat.isFile()) {
1093
+ return null;
1094
+ }
1095
+ } catch (err) {
1096
+ if (err.code === "ENOENT") {
1097
+ return null;
1098
+ }
1099
+ throw err;
1100
+ }
1101
+ const realDest = await fsp.realpath(dest);
1102
+ if (!this.isInsideBase(realDest, realBase)) {
1103
+ throw new Error(`FilesystemBackend remotePath escapes basePath: ${JSON.stringify(remotePath)}`);
1104
+ }
998
1105
  return dest;
999
1106
  }
1000
- async exists(remotePath) {
1107
+ async upload(localPath, remotePath) {
1108
+ if (path.isAbsolute(remotePath)) {
1109
+ throw new Error(`FilesystemBackend upload remotePath must be relative: ${JSON.stringify(remotePath)}`);
1110
+ }
1001
1111
  const dest = this.resolveRemotePath(remotePath);
1112
+ const realBase = await this.ensureSafeParentDirectory(dest);
1002
1113
  try {
1003
- await fsp.access(dest, fs.constants.F_OK);
1004
- return true;
1005
- } catch {
1006
- return false;
1114
+ const stat = await fsp.lstat(dest);
1115
+ if (stat.isSymbolicLink()) {
1116
+ throw new Error(`FilesystemBackend remotePath points to symlink: ${dest}`);
1117
+ }
1118
+ } catch (err) {
1119
+ if (err.code !== "ENOENT") {
1120
+ throw err;
1121
+ }
1007
1122
  }
1123
+ await fsp.copyFile(localPath, dest);
1124
+ const realDest = await fsp.realpath(dest);
1125
+ if (!this.isInsideBase(realDest, realBase)) {
1126
+ throw new Error(`FilesystemBackend remotePath escapes basePath: ${JSON.stringify(remotePath)}`);
1127
+ }
1128
+ return remotePath;
1129
+ }
1130
+ async exists(remotePath) {
1131
+ const dest = await this.resolveExistingRemotePath(remotePath);
1132
+ return dest !== null;
1008
1133
  }
1009
1134
  async delete(remotePath) {
1010
- const dest = this.resolveRemotePath(remotePath);
1135
+ const dest = await this.resolveExistingRemotePath(remotePath);
1136
+ if (dest === null) {
1137
+ return;
1138
+ }
1011
1139
  try {
1012
1140
  await fsp.unlink(dest);
1013
1141
  } catch (err) {
1014
1142
  if (err.code !== "ENOENT") throw err;
1015
1143
  }
1016
1144
  }
1145
+ getRedirectTarget(remotePath) {
1146
+ return this.resolveRemotePath(remotePath);
1147
+ }
1017
1148
  };
1018
1149
  var NoneBackend = class {
1019
1150
  type = "none";
@@ -1125,20 +1256,29 @@ function manifestPath(memoryDir) {
1125
1256
  }
1126
1257
  async function readManifest(memoryDir) {
1127
1258
  const filePath = manifestPath(memoryDir);
1259
+ let raw;
1128
1260
  try {
1129
- const raw = await fsp3.readFile(filePath, "utf-8");
1130
- const parsed = JSON.parse(raw);
1131
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
1132
- return emptyManifest();
1133
- }
1134
- const obj = parsed;
1135
- if (obj.version !== 1 || !Array.isArray(obj.assets)) {
1261
+ raw = await fsp3.readFile(filePath, "utf-8");
1262
+ } catch (err) {
1263
+ if (err.code === "ENOENT") {
1136
1264
  return emptyManifest();
1137
1265
  }
1138
- return parsed;
1139
- } catch {
1140
- return emptyManifest();
1266
+ throw new Error(`Failed to read binary lifecycle manifest at ${filePath}: ${err instanceof Error ? err.message : String(err)}`);
1141
1267
  }
1268
+ let parsed;
1269
+ try {
1270
+ parsed = JSON.parse(raw);
1271
+ } catch (err) {
1272
+ throw new Error(`Invalid binary lifecycle manifest JSON at ${filePath}: ${err instanceof Error ? err.message : String(err)}`);
1273
+ }
1274
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
1275
+ throw new Error(`Invalid binary lifecycle manifest shape at ${filePath}: expected object`);
1276
+ }
1277
+ const obj = parsed;
1278
+ if (obj.version !== 1 || !Array.isArray(obj.assets)) {
1279
+ throw new Error(`Invalid binary lifecycle manifest shape at ${filePath}: expected version 1 with assets array`);
1280
+ }
1281
+ return parsed;
1142
1282
  }
1143
1283
  async function writeManifest(memoryDir, manifest) {
1144
1284
  const dir = manifestDir(memoryDir);
@@ -1186,11 +1326,33 @@ function guessMimeType(ext) {
1186
1326
  function escapeRegex(s) {
1187
1327
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1188
1328
  }
1329
+ function resolveManifestAssetPath(memoryDir, originalPath) {
1330
+ if (originalPath.length === 0 || originalPath.includes("\0") || originalPath.includes("\\") || path4.isAbsolute(originalPath) || path4.win32.isAbsolute(originalPath)) {
1331
+ return null;
1332
+ }
1333
+ const memoryRoot = path4.resolve(memoryDir);
1334
+ const fullPath = path4.resolve(memoryRoot, originalPath);
1335
+ const relative = path4.relative(memoryRoot, fullPath);
1336
+ if (relative === "" || relative === ".." || relative.startsWith(`..${path4.sep}`) || path4.isAbsolute(relative)) {
1337
+ return null;
1338
+ }
1339
+ return fullPath;
1340
+ }
1189
1341
  function validateBinaryLifecycleConfig(config) {
1190
1342
  if (typeof config.gracePeriodDays !== "number" || !Number.isFinite(config.gracePeriodDays) || !Number.isInteger(config.gracePeriodDays) || config.gracePeriodDays < 0) {
1191
1343
  throw new Error("binary lifecycle gracePeriodDays must be a finite non-negative integer");
1192
1344
  }
1193
1345
  }
1346
+ function remotePathForAsset(backend, relPath) {
1347
+ const normalized = relPath.split(path4.sep).join("/");
1348
+ if (backend.type === "filesystem") {
1349
+ return `.binary-lifecycle/mirrors/${normalized}`;
1350
+ }
1351
+ return normalized;
1352
+ }
1353
+ function markdownTargetForAsset(asset) {
1354
+ return asset.redirectPath ?? asset.mirroredPath;
1355
+ }
1194
1356
  async function stageMirror(memoryDir, newPaths, backend, assets, log2, dryRun) {
1195
1357
  let mirrored = 0;
1196
1358
  const errors = [];
@@ -1201,14 +1363,16 @@ async function stageMirror(memoryDir, newPaths, backend, assets, log2, dryRun) {
1201
1363
  const contentHash = await hashFile2(fullPath);
1202
1364
  const ext = path4.extname(relPath);
1203
1365
  const mimeType = guessMimeType(ext);
1204
- const remotePath = relPath;
1366
+ const remotePath = remotePathForAsset(backend, relPath);
1205
1367
  let backendLocation = remotePath;
1206
1368
  if (!dryRun) {
1207
1369
  backendLocation = await backend.upload(fullPath, remotePath);
1208
1370
  }
1371
+ const redirectPath = backend.getRedirectTarget?.(backendLocation);
1209
1372
  const record = {
1210
1373
  originalPath: relPath,
1211
1374
  mirroredPath: backendLocation,
1375
+ ...redirectPath ? { redirectPath } : {},
1212
1376
  contentHash,
1213
1377
  sizeBytes: stat.size,
1214
1378
  mimeType,
@@ -1232,62 +1396,193 @@ async function stageMirror(memoryDir, newPaths, backend, assets, log2, dryRun) {
1232
1396
  }
1233
1397
  return { mirrored, errors };
1234
1398
  }
1235
- async function stageRedirect(memoryDir, assets, log2, dryRun) {
1399
+ async function stageRedirect(memoryDir, assets, log2, dryRun, readMarkdownFile, writeMarkdownFile) {
1236
1400
  let redirected = 0;
1237
1401
  const errors = [];
1238
- const candidates = assets.filter((a) => a.status === "mirrored");
1402
+ const candidates = assets.filter((a) => a.status === "mirrored" || a.status === "error");
1239
1403
  if (candidates.length === 0) return { redirected, errors };
1240
1404
  const mdFiles = await findMarkdownFiles(memoryDir);
1241
1405
  for (const asset of candidates) {
1242
- let matchCount = 0;
1243
- let writeFailCount = 0;
1406
+ const assetAbsolute = resolveManifestAssetPath(memoryDir, asset.originalPath);
1407
+ if (assetAbsolute === null) {
1408
+ const msg = `redirect blocked for ${asset.originalPath}: manifest path is outside memoryDir`;
1409
+ log2.error(`[binary-lifecycle] ${msg}`);
1410
+ errors.push(msg);
1411
+ if (!dryRun) {
1412
+ asset.status = "error";
1413
+ }
1414
+ continue;
1415
+ }
1416
+ const updates = [];
1417
+ let scanFailCount = 0;
1244
1418
  for (const mdPath of mdFiles) {
1245
1419
  try {
1246
- const content = await fsp4.readFile(mdPath, "utf-8");
1247
- const mdDir = path4.dirname(mdPath);
1248
- const assetAbsolute = path4.join(memoryDir, asset.originalPath);
1249
- const relativeToMd = path4.relative(mdDir, assetAbsolute);
1250
- const relativeForward = relativeToMd.split(path4.sep).join("/");
1251
- const escaped = escapeRegex(relativeForward);
1252
- const pattern = new RegExp(
1253
- `(!?\\[[^\\]]*\\]\\()(\\.\\/)?(${escaped})(\\))`,
1254
- "g"
1255
- );
1420
+ const content = await readMarkdownFile(mdPath);
1421
+ const pattern = markdownReferencePattern(asset, assetAbsolute, mdPath);
1256
1422
  if (!pattern.test(content)) continue;
1257
- matchCount++;
1258
- if (!dryRun) {
1259
- pattern.lastIndex = 0;
1260
- const updated = content.replace(pattern, (_match, open, _dotSlash, _file, close) => {
1261
- return `${open}${asset.mirroredPath}${close}`;
1262
- });
1263
- await fsp4.writeFile(mdPath, updated, "utf-8");
1264
- }
1423
+ pattern.lastIndex = 0;
1424
+ const updated = content.replace(pattern, (_match, open, _target, close) => {
1425
+ return `${open}${markdownTargetForAsset(asset)}${close}`;
1426
+ });
1427
+ updates.push({ mdPath, content: updated });
1265
1428
  } catch (err) {
1266
- writeFailCount++;
1429
+ scanFailCount++;
1267
1430
  const msg = `redirect scan failed for ${mdPath}: ${err instanceof Error ? err.message : String(err)}`;
1268
1431
  log2.error(`[binary-lifecycle] ${msg}`);
1269
1432
  errors.push(msg);
1270
1433
  }
1271
1434
  }
1272
- if (matchCount > 0 && writeFailCount === 0) {
1435
+ if (scanFailCount > 0) {
1273
1436
  if (!dryRun) {
1274
- asset.status = "redirected";
1275
- asset.redirectedAt = (/* @__PURE__ */ new Date()).toISOString();
1437
+ asset.status = "error";
1276
1438
  }
1439
+ log2.warn(
1440
+ `[binary-lifecycle] redirect blocked for ${asset.originalPath}: ${scanFailCount} markdown scan failure(s)${dryRun ? "" : " \u2014 status set to error"}`
1441
+ );
1442
+ continue;
1443
+ }
1444
+ if (updates.length === 0) {
1445
+ if (asset.status === "error") {
1446
+ const verifyResult2 = await countRemainingLocalReferences(
1447
+ memoryDir,
1448
+ asset,
1449
+ assetAbsolute,
1450
+ mdFiles,
1451
+ readMarkdownFile
1452
+ );
1453
+ if (verifyResult2.errors.length > 0 || verifyResult2.remaining > 0) {
1454
+ if (!dryRun) {
1455
+ asset.status = "error";
1456
+ }
1457
+ for (const msg of verifyResult2.errors) {
1458
+ log2.error(`[binary-lifecycle] ${msg}`);
1459
+ errors.push(msg);
1460
+ }
1461
+ if (verifyResult2.remaining > 0) {
1462
+ const msg = `redirect verification failed for ${asset.originalPath}: ${verifyResult2.remaining} local reference(s) remain`;
1463
+ log2.warn(`[binary-lifecycle] ${msg}`);
1464
+ errors.push(msg);
1465
+ }
1466
+ continue;
1467
+ }
1468
+ if (asset.redirectedAt === void 0) {
1469
+ if (!dryRun) {
1470
+ asset.status = "mirrored";
1471
+ }
1472
+ log2.info(`[binary-lifecycle] preserved mirrored asset without redirected marker: ${asset.originalPath}${dryRun ? " [dry-run]" : ""}`);
1473
+ continue;
1474
+ }
1475
+ if (!Number.isFinite(new Date(asset.mirroredAt).getTime())) {
1476
+ const msg = `redirect blocked for ${asset.originalPath}: manifest mirroredAt is invalid`;
1477
+ log2.error(`[binary-lifecycle] ${msg}`);
1478
+ errors.push(msg);
1479
+ if (!dryRun) {
1480
+ asset.status = "error";
1481
+ }
1482
+ continue;
1483
+ }
1484
+ if (!dryRun) {
1485
+ asset.status = "redirected";
1486
+ asset.redirectedAt = (/* @__PURE__ */ new Date()).toISOString();
1487
+ }
1488
+ redirected++;
1489
+ log2.info(`[binary-lifecycle] redirected: ${asset.originalPath}${dryRun ? " [dry-run]" : ""}`);
1490
+ }
1491
+ continue;
1492
+ }
1493
+ if (dryRun) {
1277
1494
  redirected++;
1278
- log2.info(`[binary-lifecycle] redirected: ${asset.originalPath}${dryRun ? " [dry-run]" : ""}`);
1279
- } else if (matchCount > 0 && writeFailCount > 0) {
1495
+ log2.info(`[binary-lifecycle] redirected: ${asset.originalPath} [dry-run]`);
1496
+ continue;
1497
+ }
1498
+ let writeFailCount = 0;
1499
+ for (const update of updates) {
1500
+ try {
1501
+ await writeMarkdownFile(update.mdPath, update.content);
1502
+ } catch (err) {
1503
+ writeFailCount++;
1504
+ const msg = `redirect write failed for ${update.mdPath}: ${err instanceof Error ? err.message : String(err)}`;
1505
+ log2.error(`[binary-lifecycle] ${msg}`);
1506
+ errors.push(msg);
1507
+ }
1508
+ }
1509
+ if (writeFailCount > 0) {
1280
1510
  if (!dryRun) {
1281
1511
  asset.status = "error";
1282
1512
  }
1283
1513
  log2.warn(
1284
- `[binary-lifecycle] redirect partial failure for ${asset.originalPath}: ${matchCount} match(es), ${writeFailCount} write failure(s)${dryRun ? "" : " \u2014 status set to error"}`
1514
+ `[binary-lifecycle] redirect write failure for ${asset.originalPath}: ${writeFailCount} write failure(s) \u2014 status set to error`
1285
1515
  );
1516
+ continue;
1517
+ }
1518
+ const redirectedAt = (/* @__PURE__ */ new Date()).toISOString();
1519
+ asset.redirectedAt = redirectedAt;
1520
+ const verifyResult = await countRemainingLocalReferences(
1521
+ memoryDir,
1522
+ asset,
1523
+ assetAbsolute,
1524
+ mdFiles,
1525
+ readMarkdownFile
1526
+ );
1527
+ if (verifyResult.errors.length > 0 || verifyResult.remaining > 0) {
1528
+ asset.status = "error";
1529
+ for (const msg of verifyResult.errors) {
1530
+ log2.error(`[binary-lifecycle] ${msg}`);
1531
+ errors.push(msg);
1532
+ }
1533
+ if (verifyResult.remaining > 0) {
1534
+ const msg = `redirect verification failed for ${asset.originalPath}: ${verifyResult.remaining} local reference(s) remain`;
1535
+ log2.warn(`[binary-lifecycle] ${msg}`);
1536
+ errors.push(msg);
1537
+ }
1538
+ continue;
1286
1539
  }
1540
+ asset.status = "redirected";
1541
+ asset.redirectedAt = redirectedAt;
1542
+ redirected++;
1543
+ log2.info(`[binary-lifecycle] redirected: ${asset.originalPath}`);
1287
1544
  }
1288
1545
  return { redirected, errors };
1289
1546
  }
1290
- async function stageClean(memoryDir, assets, gracePeriodDays, log2, dryRun, forceClean) {
1547
+ async function countRemainingLocalReferences(memoryDir, asset, assetAbsolute, mdFiles, readMarkdownFile) {
1548
+ let remaining = 0;
1549
+ const errors = [];
1550
+ for (const mdPath of mdFiles) {
1551
+ try {
1552
+ const content = await readMarkdownFile(mdPath);
1553
+ const pattern = markdownReferencePattern(asset, assetAbsolute, mdPath);
1554
+ if (pattern.test(content)) {
1555
+ remaining++;
1556
+ }
1557
+ } catch (err) {
1558
+ errors.push(`redirect verification failed for ${mdPath}: ${err instanceof Error ? err.message : String(err)}`);
1559
+ }
1560
+ }
1561
+ return { remaining, errors };
1562
+ }
1563
+ function markdownReferencePattern(asset, assetAbsolute, mdPath) {
1564
+ const mdDir = path4.dirname(mdPath);
1565
+ const candidates = /* @__PURE__ */ new Set();
1566
+ const addCandidate = (candidate) => {
1567
+ const normalized = candidate.split(path4.sep).join("/");
1568
+ if (normalized.length === 0) return;
1569
+ candidates.add(normalized);
1570
+ const isParentTraversal = normalized === ".." || normalized.startsWith("../");
1571
+ if (!normalized.startsWith("./") && !normalized.startsWith("/") && !isParentTraversal) {
1572
+ candidates.add(`./${normalized}`);
1573
+ }
1574
+ };
1575
+ addCandidate(path4.relative(mdDir, assetAbsolute));
1576
+ const originalPath = asset.originalPath.split(path4.sep).join("/");
1577
+ const originalAsFileRelative = path4.resolve(mdDir, ...originalPath.split("/"));
1578
+ if (path4.resolve(originalAsFileRelative) === path4.resolve(assetAbsolute)) {
1579
+ addCandidate(originalPath);
1580
+ }
1581
+ addCandidate(`/${originalPath}`);
1582
+ const alternatives = [...candidates].sort((a, b) => b.length - a.length).map(escapeRegex).join("|");
1583
+ return new RegExp(`(!?\\[[^\\]]*\\]\\()(${alternatives})(\\))`, "g");
1584
+ }
1585
+ async function stageClean(memoryDir, assets, backend, gracePeriodDays, log2, dryRun, forceClean) {
1291
1586
  let cleaned = 0;
1292
1587
  const errors = [];
1293
1588
  const now = Date.now();
@@ -1297,11 +1592,44 @@ async function stageClean(memoryDir, assets, gracePeriodDays, log2, dryRun, forc
1297
1592
  );
1298
1593
  for (const asset of candidates) {
1299
1594
  const mirroredMs = new Date(asset.mirroredAt).getTime();
1595
+ if (!Number.isFinite(mirroredMs)) {
1596
+ const msg = `clean blocked for ${asset.originalPath}: manifest mirroredAt is invalid`;
1597
+ log2.error(`[binary-lifecycle] ${msg}`);
1598
+ errors.push(msg);
1599
+ if (!dryRun) {
1600
+ asset.status = "error";
1601
+ }
1602
+ continue;
1603
+ }
1300
1604
  const ageMs = now - mirroredMs;
1301
1605
  if (!forceClean && ageMs < graceMs) {
1302
1606
  continue;
1303
1607
  }
1304
- const fullPath = path4.join(memoryDir, asset.originalPath);
1608
+ const fullPath = resolveManifestAssetPath(memoryDir, asset.originalPath);
1609
+ if (fullPath === null) {
1610
+ const msg = `clean blocked for ${asset.originalPath}: manifest path is outside memoryDir`;
1611
+ log2.error(`[binary-lifecycle] ${msg}`);
1612
+ errors.push(msg);
1613
+ if (!dryRun) {
1614
+ asset.status = "error";
1615
+ }
1616
+ continue;
1617
+ }
1618
+ let remoteExists;
1619
+ try {
1620
+ remoteExists = await backend.exists(asset.mirroredPath);
1621
+ } catch (err) {
1622
+ const msg = `clean blocked for ${asset.originalPath}: failed to verify mirrored copy: ${err instanceof Error ? err.message : String(err)}`;
1623
+ log2.error(`[binary-lifecycle] ${msg}`);
1624
+ errors.push(msg);
1625
+ continue;
1626
+ }
1627
+ if (!remoteExists) {
1628
+ const msg = `clean blocked for ${asset.originalPath}: mirrored copy is missing`;
1629
+ log2.error(`[binary-lifecycle] ${msg}`);
1630
+ errors.push(msg);
1631
+ continue;
1632
+ }
1305
1633
  try {
1306
1634
  const currentHash = await hashFile2(fullPath);
1307
1635
  if (currentHash !== asset.contentHash) {
@@ -1319,9 +1647,11 @@ async function stageClean(memoryDir, assets, gracePeriodDays, log2, dryRun, forc
1319
1647
  log2.info(`[binary-lifecycle] cleaned: ${asset.originalPath}${dryRun ? " [dry-run]" : ""}`);
1320
1648
  } catch (err) {
1321
1649
  if (err.code === "ENOENT") {
1322
- asset.status = "cleaned";
1323
- asset.cleanedAt = (/* @__PURE__ */ new Date()).toISOString();
1324
- cleaned++;
1650
+ if (!dryRun) {
1651
+ asset.status = "cleaned";
1652
+ asset.cleanedAt = (/* @__PURE__ */ new Date()).toISOString();
1653
+ cleaned++;
1654
+ }
1325
1655
  } else {
1326
1656
  const msg = `clean failed for ${asset.originalPath}: ${err instanceof Error ? err.message : String(err)}`;
1327
1657
  log2.error(`[binary-lifecycle] ${msg}`);
@@ -1378,10 +1708,18 @@ async function runBinaryLifecyclePipeline(memoryDir, config, backend, log2, opts
1378
1708
  log2,
1379
1709
  dryRun
1380
1710
  );
1381
- const redirectResult = await stageRedirect(memoryDir, manifest.assets, log2, dryRun);
1711
+ const redirectResult = await stageRedirect(
1712
+ memoryDir,
1713
+ manifest.assets,
1714
+ log2,
1715
+ dryRun,
1716
+ opts?.readMarkdownFile ?? ((filePath) => fsp4.readFile(filePath, "utf-8")),
1717
+ opts?.writeMarkdownFile ?? ((filePath, content) => fsp4.writeFile(filePath, content, "utf-8"))
1718
+ );
1382
1719
  const cleanResult = await stageClean(
1383
1720
  memoryDir,
1384
1721
  manifest.assets,
1722
+ backend,
1385
1723
  config.gracePeriodDays,
1386
1724
  log2,
1387
1725
  dryRun,
@@ -1407,7 +1745,7 @@ async function runBinaryLifecyclePipeline(memoryDir, config, backend, log2, opts
1407
1745
  }
1408
1746
 
1409
1747
  // src/projection/index.ts
1410
- import fs2 from "fs";
1748
+ import fs from "fs";
1411
1749
  import path5 from "path";
1412
1750
  var VALID_PROJECTION_CATEGORIES = new Set(ALL_CATEGORY_KEYS);
1413
1751
  async function generateContextTree(options) {
@@ -1426,14 +1764,14 @@ async function generateContextTree(options) {
1426
1764
  const resolvedMemoryDir = path5.resolve(memoryDir);
1427
1765
  const resolvedOutputDir = path5.resolve(outputDir);
1428
1766
  const requestedCategories = validateProjectionCategories(filterCategories);
1429
- const realMemoryDir = fs2.realpathSync(resolvedMemoryDir);
1767
+ const realMemoryDir = fs.realpathSync(resolvedMemoryDir);
1430
1768
  assertNotSymlink(resolvedOutputDir, "context tree outputDir");
1431
- fs2.mkdirSync(resolvedOutputDir, { recursive: true });
1432
- const realOutputDir = fs2.realpathSync(resolvedOutputDir);
1769
+ fs.mkdirSync(resolvedOutputDir, { recursive: true });
1770
+ const realOutputDir = fs.realpathSync(resolvedOutputDir);
1433
1771
  const allCategories = (requestedCategories ?? ALL_CATEGORY_KEYS).filter((c) => c !== "question");
1434
1772
  for (const category of allCategories) {
1435
1773
  const categoryDir = getCategoryDir(memoryDir, category);
1436
- if (!fs2.existsSync(categoryDir)) continue;
1774
+ if (!fs.existsSync(categoryDir)) continue;
1437
1775
  assertSafeInputRoot(realMemoryDir, categoryDir, `${category} memory category`);
1438
1776
  categoryCounts[category] = 0;
1439
1777
  const files = walkR(categoryDir, realMemoryDir);
@@ -1443,7 +1781,7 @@ async function generateContextTree(options) {
1443
1781
  nodesSkipped++;
1444
1782
  continue;
1445
1783
  }
1446
- const content = fs2.readFileSync(filePath, "utf8");
1784
+ const content = fs.readFileSync(filePath, "utf8");
1447
1785
  const fm = parseFrontmatter(content);
1448
1786
  if (!fm) {
1449
1787
  nodesSkipped++;
@@ -1463,7 +1801,7 @@ async function generateContextTree(options) {
1463
1801
  }
1464
1802
  if (includeEntities) {
1465
1803
  const entitiesDir = path5.join(memoryDir, "entities");
1466
- if (fs2.existsSync(entitiesDir)) {
1804
+ if (fs.existsSync(entitiesDir)) {
1467
1805
  assertSafeInputRoot(realMemoryDir, entitiesDir, "entities root");
1468
1806
  categoryCounts["entity"] = 0;
1469
1807
  const entityFiles = walkR(entitiesDir, realMemoryDir);
@@ -1473,7 +1811,7 @@ async function generateContextTree(options) {
1473
1811
  nodesSkipped++;
1474
1812
  continue;
1475
1813
  }
1476
- const content = fs2.readFileSync(filePath, "utf8");
1814
+ const content = fs.readFileSync(filePath, "utf8");
1477
1815
  const fileName = path5.basename(filePath, ".md");
1478
1816
  const node = projectEntityNode(fileName, content);
1479
1817
  const outputPath = resolveContainedOutputPath(realOutputDir, "entities", `${fileName}.md`);
@@ -1487,7 +1825,7 @@ async function generateContextTree(options) {
1487
1825
  const shouldIncludeQuestions = includeQuestions && (requestedCategories === void 0 || requestedCategories.includes("question"));
1488
1826
  if (shouldIncludeQuestions) {
1489
1827
  const questionsDir = path5.join(memoryDir, "questions");
1490
- if (fs2.existsSync(questionsDir)) {
1828
+ if (fs.existsSync(questionsDir)) {
1491
1829
  assertSafeInputRoot(realMemoryDir, questionsDir, "questions root");
1492
1830
  categoryCounts["question"] = 0;
1493
1831
  const qFiles = walkR(questionsDir, realMemoryDir);
@@ -1497,7 +1835,7 @@ async function generateContextTree(options) {
1497
1835
  nodesSkipped++;
1498
1836
  continue;
1499
1837
  }
1500
- const content = fs2.readFileSync(filePath, "utf8");
1838
+ const content = fs.readFileSync(filePath, "utf8");
1501
1839
  const fm = parseFrontmatter(content);
1502
1840
  if (!fm) {
1503
1841
  nodesSkipped++;
@@ -1532,7 +1870,7 @@ function isPathInside(root, candidate) {
1532
1870
  }
1533
1871
  function assertNotSymlink(targetPath, label) {
1534
1872
  try {
1535
- if (fs2.lstatSync(targetPath).isSymbolicLink()) {
1873
+ if (fs.lstatSync(targetPath).isSymbolicLink()) {
1536
1874
  throw new Error(`${label} must not be a symlink: ${targetPath}`);
1537
1875
  }
1538
1876
  } catch (err) {
@@ -1541,14 +1879,14 @@ function assertNotSymlink(targetPath, label) {
1541
1879
  }
1542
1880
  }
1543
1881
  function assertSafeInputRoot(realMemoryDir, targetPath, label) {
1544
- const stat = fs2.lstatSync(targetPath);
1882
+ const stat = fs.lstatSync(targetPath);
1545
1883
  if (stat.isSymbolicLink()) {
1546
1884
  throw new Error(`${label} must not be a symlink: ${targetPath}`);
1547
1885
  }
1548
1886
  if (!stat.isDirectory()) {
1549
1887
  throw new Error(`${label} must be a directory: ${targetPath}`);
1550
1888
  }
1551
- const realTarget = fs2.realpathSync(targetPath);
1889
+ const realTarget = fs.realpathSync(targetPath);
1552
1890
  if (!isPathInside(realMemoryDir, realTarget)) {
1553
1891
  throw new Error(`${label} escapes memoryDir: ${targetPath}`);
1554
1892
  }
@@ -1562,7 +1900,7 @@ function assertSafeOutputTarget(realOutputDir, outputPath) {
1562
1900
  for (const segment of relative.split(path5.sep).filter(Boolean)) {
1563
1901
  current = path5.join(current, segment);
1564
1902
  try {
1565
- const stat = fs2.lstatSync(current);
1903
+ const stat = fs.lstatSync(current);
1566
1904
  if (stat.isSymbolicLink()) {
1567
1905
  throw new Error(`context tree output path contains symlink: ${current}`);
1568
1906
  }
@@ -1599,13 +1937,13 @@ function resolveContainedOutputPath(outputRoot, ...segments) {
1599
1937
  }
1600
1938
  function writeProjectedContent(realOutputDir, outputPath, generatedContent) {
1601
1939
  assertSafeOutputTarget(realOutputDir, outputPath);
1602
- fs2.mkdirSync(path5.dirname(outputPath), { recursive: true });
1603
- const realParent = fs2.realpathSync(path5.dirname(outputPath));
1940
+ fs.mkdirSync(path5.dirname(outputPath), { recursive: true });
1941
+ const realParent = fs.realpathSync(path5.dirname(outputPath));
1604
1942
  if (!isPathInside(realOutputDir, realParent)) {
1605
1943
  throw new Error(`context tree output path escapes outputDir: ${outputPath}`);
1606
1944
  }
1607
- const existingContent = fs2.existsSync(outputPath) ? fs2.readFileSync(outputPath, "utf8") : "";
1608
- fs2.writeFileSync(
1945
+ const existingContent = fs.existsSync(outputPath) ? fs.readFileSync(outputPath, "utf8") : "";
1946
+ fs.writeFileSync(
1609
1947
  outputPath,
1610
1948
  preserveManualFencedBlocks(generatedContent, existingContent)
1611
1949
  );
@@ -1638,19 +1976,19 @@ function extractManualFencedBlocks(content) {
1638
1976
  function walkR(dir, realMemoryDir) {
1639
1977
  const results = [];
1640
1978
  function walk(directory) {
1641
- for (const entry of fs2.readdirSync(directory, { withFileTypes: true })) {
1979
+ for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
1642
1980
  const fullPath = path5.join(directory, entry.name);
1643
1981
  if (entry.isSymbolicLink()) {
1644
1982
  throw new Error(`context tree input path contains symlink: ${fullPath}`);
1645
1983
  }
1646
1984
  if (entry.isDirectory()) {
1647
- const realDir = fs2.realpathSync(fullPath);
1985
+ const realDir = fs.realpathSync(fullPath);
1648
1986
  if (!isPathInside(realMemoryDir, realDir)) {
1649
1987
  throw new Error(`context tree input path escapes memoryDir: ${fullPath}`);
1650
1988
  }
1651
1989
  walk(fullPath);
1652
1990
  } else if (entry.name.endsWith(".md")) {
1653
- const realFile = fs2.realpathSync(fullPath);
1991
+ const realFile = fs.realpathSync(fullPath);
1654
1992
  if (!isPathInside(realMemoryDir, realFile)) {
1655
1993
  throw new Error(`context tree input path escapes memoryDir: ${fullPath}`);
1656
1994
  }
@@ -1796,7 +2134,7 @@ function generateIndex(categoryCounts, outputDir) {
1796
2134
  }
1797
2135
 
1798
2136
  // src/onboarding/index.ts
1799
- import fs3 from "fs";
2137
+ import fs2 from "fs";
1800
2138
  import path6 from "path";
1801
2139
  var LANGUAGE_RULES = [
1802
2140
  {
@@ -1902,7 +2240,7 @@ function onboard(options) {
1902
2240
  const directory = path6.resolve(options.directory ?? process.cwd());
1903
2241
  let rootStat;
1904
2242
  try {
1905
- rootStat = fs3.statSync(directory);
2243
+ rootStat = fs2.statSync(directory);
1906
2244
  } catch (err) {
1907
2245
  throw new Error(`Cannot scan onboarding directory ${directory}: ${err instanceof Error ? err.message : String(err)}`);
1908
2246
  }
@@ -1932,7 +2270,7 @@ function walkDir(root, exclude, maxDepth) {
1932
2270
  if (depth > maxDepth) return;
1933
2271
  let entries;
1934
2272
  try {
1935
- entries = fs3.readdirSync(dir, { withFileTypes: true });
2273
+ entries = fs2.readdirSync(dir, { withFileTypes: true });
1936
2274
  } catch (err) {
1937
2275
  if (depth === 0) {
1938
2276
  throw new Error(`Cannot scan onboarding directory ${root}: ${err instanceof Error ? err.message : String(err)}`);
@@ -2008,7 +2346,7 @@ function detectShape(files, root) {
2008
2346
  );
2009
2347
  const rootDirs = /* @__PURE__ */ new Set();
2010
2348
  try {
2011
- for (const entry of fs3.readdirSync(root, { withFileTypes: true })) {
2349
+ for (const entry of fs2.readdirSync(root, { withFileTypes: true })) {
2012
2350
  if (entry.isDirectory()) rootDirs.add(entry.name);
2013
2351
  }
2014
2352
  } catch {
@@ -2089,7 +2427,7 @@ function discoverDocs(files, root) {
2089
2427
  if (kind) {
2090
2428
  let size = 0;
2091
2429
  try {
2092
- size = fs3.statSync(filePath).size;
2430
+ size = fs2.statSync(filePath).size;
2093
2431
  } catch {
2094
2432
  }
2095
2433
  docs.push({
@@ -2131,14 +2469,14 @@ function buildPlan(languages, shape, docs, _root) {
2131
2469
  }
2132
2470
  function readJsonSafe(filePath) {
2133
2471
  try {
2134
- return JSON.parse(fs3.readFileSync(filePath, "utf8"));
2472
+ return JSON.parse(fs2.readFileSync(filePath, "utf8"));
2135
2473
  } catch {
2136
2474
  return null;
2137
2475
  }
2138
2476
  }
2139
2477
  function readTomlWorkspace(filePath) {
2140
2478
  try {
2141
- const content = fs3.readFileSync(filePath, "utf8");
2479
+ const content = fs2.readFileSync(filePath, "utf8");
2142
2480
  return content.includes("[workspace]");
2143
2481
  } catch {
2144
2482
  return false;
@@ -2146,7 +2484,7 @@ function readTomlWorkspace(filePath) {
2146
2484
  }
2147
2485
 
2148
2486
  // src/curation/index.ts
2149
- import fs4 from "fs";
2487
+ import fs3 from "fs";
2150
2488
  import path7 from "path";
2151
2489
  import crypto4 from "crypto";
2152
2490
  async function curate(options) {
@@ -2232,12 +2570,12 @@ async function curate(options) {
2232
2570
  };
2233
2571
  }
2234
2572
  function resolveTargets(targetPath) {
2235
- const stat = fs4.statSync(targetPath);
2573
+ const stat = fs3.statSync(targetPath);
2236
2574
  if (stat.isFile()) return [targetPath];
2237
2575
  const results = [];
2238
2576
  const extensions = /* @__PURE__ */ new Set([".md", ".txt", ".mdx", ".rst"]);
2239
2577
  function walk(dir) {
2240
- for (const entry of fs4.readdirSync(dir, { withFileTypes: true })) {
2578
+ for (const entry of fs3.readdirSync(dir, { withFileTypes: true })) {
2241
2579
  const fullPath = path7.join(dir, entry.name);
2242
2580
  if (entry.isDirectory()) {
2243
2581
  if (entry.name !== "node_modules" && entry.name !== ".git") {
@@ -2253,7 +2591,7 @@ function resolveTargets(targetPath) {
2253
2591
  }
2254
2592
  function resolveProvenanceRoot(targetPath) {
2255
2593
  const resolvedTarget = path7.resolve(targetPath);
2256
- const stat = fs4.statSync(resolvedTarget);
2594
+ const stat = fs3.statSync(resolvedTarget);
2257
2595
  return stat.isFile() ? path7.dirname(resolvedTarget) : resolvedTarget;
2258
2596
  }
2259
2597
  function extractStatements(content, filePath, projectRoot, source, sourceFileHash, categoryOverride, confidence, entityRef, tags) {
@@ -2348,11 +2686,11 @@ function findContradiction(stmt, existing) {
2348
2686
  }
2349
2687
  function loadExistingMemories(memoryDir) {
2350
2688
  const result = /* @__PURE__ */ new Map();
2351
- if (!fs4.existsSync(memoryDir)) return result;
2689
+ if (!fs3.existsSync(memoryDir)) return result;
2352
2690
  const dirs = ALL_CATEGORY_DIRS;
2353
2691
  for (const dir of dirs) {
2354
2692
  const fullDir = path7.join(memoryDir, dir);
2355
- if (!fs4.existsSync(fullDir)) continue;
2693
+ if (!fs3.existsSync(fullDir)) continue;
2356
2694
  walkFiles(fullDir, (filePath) => {
2357
2695
  const content = readFileSafe(filePath);
2358
2696
  if (!content) return;
@@ -2374,7 +2712,7 @@ function writeStatement(stmt, memoryDir) {
2374
2712
  const dateDir = now.toISOString().split("T")[0];
2375
2713
  const categoryDir = getCategoryDir(memoryDir, stmt.category);
2376
2714
  const dir = path7.join(categoryDir, dateDir);
2377
- fs4.mkdirSync(dir, { recursive: true });
2715
+ fs3.mkdirSync(dir, { recursive: true });
2378
2716
  const fileName = `${stmt.category}-${Date.now()}-${stmt.id.slice(0, 8)}.md`;
2379
2717
  const filePath = path7.join(dir, fileName);
2380
2718
  const frontmatter = [
@@ -2396,7 +2734,7 @@ function writeStatement(stmt, memoryDir) {
2396
2734
 
2397
2735
  ${stmt.content}
2398
2736
  `;
2399
- fs4.writeFileSync(filePath, body);
2737
+ fs3.writeFileSync(filePath, body);
2400
2738
  return filePath;
2401
2739
  }
2402
2740
  function generateId() {
@@ -2413,7 +2751,7 @@ function tierFromConfidence(confidence) {
2413
2751
  }
2414
2752
  function readFileSafe(filePath) {
2415
2753
  try {
2416
- return fs4.readFileSync(filePath, "utf8");
2754
+ return fs3.readFileSync(filePath, "utf8");
2417
2755
  } catch {
2418
2756
  return null;
2419
2757
  }
@@ -2442,7 +2780,7 @@ function extractBody2(content) {
2442
2780
  return match ? match[1].trim() : content.trim();
2443
2781
  }
2444
2782
  function walkFiles(dir, callback) {
2445
- for (const entry of fs4.readdirSync(dir, { withFileTypes: true })) {
2783
+ for (const entry of fs3.readdirSync(dir, { withFileTypes: true })) {
2446
2784
  const fullPath = path7.join(dir, entry.name);
2447
2785
  if (entry.isDirectory()) {
2448
2786
  walkFiles(fullPath, callback);
@@ -2453,7 +2791,7 @@ function walkFiles(dir, callback) {
2453
2791
  }
2454
2792
 
2455
2793
  // src/dedup/index.ts
2456
- import fs5 from "fs";
2794
+ import fs4 from "fs";
2457
2795
  import path8 from "path";
2458
2796
  import crypto5 from "crypto";
2459
2797
  var DEFAULT_DEDUP_THRESHOLD = 0.85;
@@ -2592,18 +2930,18 @@ function stripNegation(text) {
2592
2930
  function loadMemories(memoryDir, categories, maxLoad = 1e4) {
2593
2931
  const result = [];
2594
2932
  const allCategories = categories ?? ALL_CATEGORY_DIRS;
2595
- if (!fs5.existsSync(memoryDir)) return result;
2596
- const memoryRootReal = fs5.realpathSync(memoryDir);
2933
+ if (!fs4.existsSync(memoryDir)) return result;
2934
+ const memoryRootReal = fs4.realpathSync(memoryDir);
2597
2935
  for (const category of allCategories) {
2598
2936
  if (result.length >= maxLoad) break;
2599
2937
  const dir = path8.join(memoryDir, category);
2600
- if (!fs5.existsSync(dir)) continue;
2601
- const categoryStat = fs5.lstatSync(dir);
2938
+ if (!fs4.existsSync(dir)) continue;
2939
+ const categoryStat = fs4.lstatSync(dir);
2602
2940
  if (categoryStat.isSymbolicLink()) {
2603
2941
  throw new Error(`Refusing to scan symlinked memory category directory: ${dir}`);
2604
2942
  }
2605
2943
  if (!categoryStat.isDirectory()) continue;
2606
- const categoryRootReal = fs5.realpathSync(dir);
2944
+ const categoryRootReal = fs4.realpathSync(dir);
2607
2945
  assertPathInsideRoot(memoryRootReal, categoryRootReal, dir);
2608
2946
  walkMdFiles(dir, memoryRootReal, categoryRootReal, (filePath) => {
2609
2947
  if (result.length >= maxLoad) return;
@@ -2627,14 +2965,14 @@ function hashContent2(content) {
2627
2965
  }
2628
2966
  function readFileSafe2(filePath, memoryRootReal, categoryRootReal) {
2629
2967
  try {
2630
- const fileStat = fs5.lstatSync(filePath);
2968
+ const fileStat = fs4.lstatSync(filePath);
2631
2969
  if (fileStat.isSymbolicLink()) {
2632
2970
  throw new Error(`Refusing to read symlinked memory file: ${filePath}`);
2633
2971
  }
2634
- const fileReal = fs5.realpathSync(filePath);
2972
+ const fileReal = fs4.realpathSync(filePath);
2635
2973
  assertPathInsideRoot(memoryRootReal, fileReal, filePath);
2636
2974
  assertPathInsideRoot(categoryRootReal, fileReal, filePath);
2637
- return fs5.readFileSync(filePath, "utf8");
2975
+ return fs4.readFileSync(filePath, "utf8");
2638
2976
  } catch {
2639
2977
  return null;
2640
2978
  }
@@ -2664,13 +3002,13 @@ function assertPathInsideRoot(rootReal, targetReal, sourcePath) {
2664
3002
  throw new Error(`Refusing to scan memory path outside root: ${sourcePath}`);
2665
3003
  }
2666
3004
  function walkMdFiles(dir, memoryRootReal, categoryRootReal, callback) {
2667
- for (const entry of fs5.readdirSync(dir, { withFileTypes: true })) {
3005
+ for (const entry of fs4.readdirSync(dir, { withFileTypes: true })) {
2668
3006
  const fullPath = path8.join(dir, entry.name);
2669
- const entryStat = fs5.lstatSync(fullPath);
3007
+ const entryStat = fs4.lstatSync(fullPath);
2670
3008
  if (entryStat.isSymbolicLink()) {
2671
3009
  throw new Error(`Refusing to scan symlinked memory path: ${fullPath}`);
2672
3010
  }
2673
- const entryReal = fs5.realpathSync(fullPath);
3011
+ const entryReal = fs4.realpathSync(fullPath);
2674
3012
  assertPathInsideRoot(memoryRootReal, entryReal, fullPath);
2675
3013
  assertPathInsideRoot(categoryRootReal, entryReal, fullPath);
2676
3014
  if (entryStat.isDirectory()) {
@@ -2682,14 +3020,14 @@ function walkMdFiles(dir, memoryRootReal, categoryRootReal, callback) {
2682
3020
  }
2683
3021
 
2684
3022
  // src/review/index.ts
2685
- import fs6 from "fs";
3023
+ import fs5 from "fs";
2686
3024
  import path9 from "path";
2687
3025
  var DEFAULT_CONFIDENCE_THRESHOLD = 0.7;
2688
3026
  function realMemoryRoot(memoryDir) {
2689
3027
  try {
2690
- const stat = fs6.lstatSync(memoryDir);
3028
+ const stat = fs5.lstatSync(memoryDir);
2691
3029
  if (!stat.isDirectory() || stat.isSymbolicLink()) return null;
2692
- return fs6.realpathSync(memoryDir);
3030
+ return fs5.realpathSync(memoryDir);
2693
3031
  } catch {
2694
3032
  return null;
2695
3033
  }
@@ -2700,21 +3038,21 @@ function isPathInside2(rootReal, candidateReal) {
2700
3038
  }
2701
3039
  function isSafeDirectory(rootReal, dir) {
2702
3040
  try {
2703
- const stat = fs6.lstatSync(dir);
3041
+ const stat = fs5.lstatSync(dir);
2704
3042
  if (!stat.isDirectory() || stat.isSymbolicLink()) return false;
2705
- return isPathInside2(rootReal, fs6.realpathSync(dir));
3043
+ return isPathInside2(rootReal, fs5.realpathSync(dir));
2706
3044
  } catch {
2707
3045
  return false;
2708
3046
  }
2709
3047
  }
2710
3048
  function ensureSafeDirectory(rootReal, dir) {
2711
- if (fs6.existsSync(dir)) {
3049
+ if (fs5.existsSync(dir)) {
2712
3050
  if (!isSafeDirectory(rootReal, dir)) {
2713
3051
  throw new Error(`Refusing to write through unsafe review path: ${dir}`);
2714
3052
  }
2715
3053
  return;
2716
3054
  }
2717
- fs6.mkdirSync(dir, { recursive: true });
3055
+ fs5.mkdirSync(dir, { recursive: true });
2718
3056
  if (!isSafeDirectory(rootReal, dir)) {
2719
3057
  throw new Error(`Refusing to write through unsafe review path: ${dir}`);
2720
3058
  }
@@ -2740,7 +3078,7 @@ function listReviewItems(options) {
2740
3078
  items.push(item);
2741
3079
  };
2742
3080
  const suggestionsDir = path9.join(memoryDir, "suggestions");
2743
- if (!isLimitReached() && fs6.existsSync(suggestionsDir) && isSafeDirectory(rootReal, suggestionsDir)) {
3081
+ if (!isLimitReached() && fs5.existsSync(suggestionsDir) && isSafeDirectory(rootReal, suggestionsDir)) {
2744
3082
  walkMd(rootReal, suggestionsDir, (filePath, content) => {
2745
3083
  if (isLimitReached()) return true;
2746
3084
  const fm = parseFrontmatter4(content);
@@ -2761,7 +3099,7 @@ function listReviewItems(options) {
2761
3099
  });
2762
3100
  }
2763
3101
  const reviewDir = path9.join(memoryDir, "review");
2764
- if (!isLimitReached() && fs6.existsSync(reviewDir) && isSafeDirectory(rootReal, reviewDir)) {
3102
+ if (!isLimitReached() && fs5.existsSync(reviewDir) && isSafeDirectory(rootReal, reviewDir)) {
2765
3103
  walkMd(rootReal, reviewDir, (filePath, content) => {
2766
3104
  if (isLimitReached()) return true;
2767
3105
  const fm = parseFrontmatter4(content);
@@ -2786,7 +3124,7 @@ function listReviewItems(options) {
2786
3124
  for (const category of categories) {
2787
3125
  if (isLimitReached()) break;
2788
3126
  const dir = path9.join(memoryDir, category);
2789
- if (!fs6.existsSync(dir) || !isSafeDirectory(rootReal, dir)) continue;
3127
+ if (!fs5.existsSync(dir) || !isSafeDirectory(rootReal, dir)) continue;
2790
3128
  walkMd(rootReal, dir, (filePath, content) => {
2791
3129
  if (isLimitReached()) return true;
2792
3130
  const fm = parseFrontmatter4(content);
@@ -2833,7 +3171,7 @@ function approveItem(memoryDir, itemId, options) {
2833
3171
  if (!found) {
2834
3172
  return { itemId, action: "approve", message: "Item not found" };
2835
3173
  }
2836
- const content = fs6.readFileSync(found.filePath, "utf8");
3174
+ const content = fs5.readFileSync(found.filePath, "utf8");
2837
3175
  const fm = parseFrontmatter4(content);
2838
3176
  if (!fm) return { itemId, action: "approve", message: "Could not parse frontmatter" };
2839
3177
  const updatedContent = updateFrontmatterFields(content, {
@@ -2842,7 +3180,7 @@ function approveItem(memoryDir, itemId, options) {
2842
3180
  reviewDismissed: null
2843
3181
  });
2844
3182
  if (found.location === "category") {
2845
- fs6.writeFileSync(found.filePath, updatedContent, "utf8");
3183
+ fs5.writeFileSync(found.filePath, updatedContent, "utf8");
2846
3184
  return {
2847
3185
  itemId,
2848
3186
  action: "approve",
@@ -2857,7 +3195,7 @@ function approveItem(memoryDir, itemId, options) {
2857
3195
  const outputPath = path9.join(targetDir, dateDir, path9.basename(found.filePath));
2858
3196
  ensureSafeDirectory(rootReal, path9.dirname(outputPath));
2859
3197
  const promotedPath = writeFileWithoutClobber(outputPath, updatedContent, itemId);
2860
- fs6.unlinkSync(found.filePath);
3198
+ fs5.unlinkSync(found.filePath);
2861
3199
  return {
2862
3200
  itemId,
2863
3201
  action: "approve",
@@ -2871,11 +3209,11 @@ function dismissItem(memoryDir, itemId, options) {
2871
3209
  return { itemId, action: "dismiss", message: "Item not found" };
2872
3210
  }
2873
3211
  if (found.location === "queue") {
2874
- fs6.unlinkSync(found.filePath);
3212
+ fs5.unlinkSync(found.filePath);
2875
3213
  return { itemId, action: "dismiss", message: "Dismissed and removed" };
2876
3214
  }
2877
- const content = fs6.readFileSync(found.filePath, "utf8");
2878
- fs6.writeFileSync(
3215
+ const content = fs5.readFileSync(found.filePath, "utf8");
3216
+ fs5.writeFileSync(
2879
3217
  found.filePath,
2880
3218
  updateFrontmatterFields(content, {
2881
3219
  reviewDismissed: "true",
@@ -2895,12 +3233,12 @@ function flagItem(memoryDir, itemId, options) {
2895
3233
  if (!found) {
2896
3234
  return { itemId, action: "flag", message: "Item not found" };
2897
3235
  }
2898
- const content = fs6.readFileSync(found.filePath, "utf8");
3236
+ const content = fs5.readFileSync(found.filePath, "utf8");
2899
3237
  const fixed = updateFrontmatterFields(content, {
2900
3238
  flagged: "true",
2901
3239
  flaggedAt: (/* @__PURE__ */ new Date()).toISOString()
2902
3240
  });
2903
- fs6.writeFileSync(found.filePath, fixed);
3241
+ fs5.writeFileSync(found.filePath, fixed);
2904
3242
  return {
2905
3243
  itemId,
2906
3244
  action: "flag",
@@ -2913,13 +3251,13 @@ function findReviewFileById(memoryDir, id, options = {}) {
2913
3251
  if (!rootReal) return null;
2914
3252
  for (const loc of ["suggestions", "review"]) {
2915
3253
  const dir = path9.join(memoryDir, loc);
2916
- if (!fs6.existsSync(dir) || !isSafeDirectory(rootReal, dir)) continue;
3254
+ if (!fs5.existsSync(dir) || !isSafeDirectory(rootReal, dir)) continue;
2917
3255
  const found = findFileById(rootReal, dir, id);
2918
3256
  if (found) return { filePath: found, location: "queue" };
2919
3257
  }
2920
3258
  for (const category of ALL_CATEGORY_DIRS) {
2921
3259
  const dir = path9.join(memoryDir, category);
2922
- if (!fs6.existsSync(dir) || !isSafeDirectory(rootReal, dir)) continue;
3260
+ if (!fs5.existsSync(dir) || !isSafeDirectory(rootReal, dir)) continue;
2923
3261
  const found = findFileById(rootReal, dir, id, (fm) => isLowConfidenceReviewCandidate(fm, options));
2924
3262
  if (found) return { filePath: found, location: "category" };
2925
3263
  }
@@ -2985,7 +3323,7 @@ function updateFrontmatterFields(content, fields) {
2985
3323
  }
2986
3324
  function readFileSafe3(filePath) {
2987
3325
  try {
2988
- return fs6.readFileSync(filePath, "utf8");
3326
+ return fs5.readFileSync(filePath, "utf8");
2989
3327
  } catch {
2990
3328
  return null;
2991
3329
  }
@@ -2999,7 +3337,7 @@ function writeFileWithoutClobber(basePath, content, discriminator) {
2999
3337
  `${parsed.name}-${safeDiscriminator}${attempt === 1 ? "" : `-${attempt}`}${parsed.ext || ".md"}`
3000
3338
  );
3001
3339
  try {
3002
- fs6.writeFileSync(candidate, content, { encoding: "utf8", flag: "wx" });
3340
+ fs5.writeFileSync(candidate, content, { encoding: "utf8", flag: "wx" });
3003
3341
  return candidate;
3004
3342
  } catch (error) {
3005
3343
  if (error.code === "EEXIST") continue;
@@ -3049,7 +3387,7 @@ function extractBody4(content) {
3049
3387
  }
3050
3388
  function walkMd(rootReal, dir, callback) {
3051
3389
  if (!isSafeDirectory(rootReal, dir)) return false;
3052
- for (const entry of fs6.readdirSync(dir, { withFileTypes: true })) {
3390
+ for (const entry of fs5.readdirSync(dir, { withFileTypes: true })) {
3053
3391
  const fullPath = path9.join(dir, entry.name);
3054
3392
  if (entry.isSymbolicLink()) continue;
3055
3393
  if (entry.isDirectory()) {
@@ -3064,7 +3402,7 @@ function walkMd(rootReal, dir, callback) {
3064
3402
  function walkMdPaths(rootReal, dir) {
3065
3403
  const results = [];
3066
3404
  if (!isSafeDirectory(rootReal, dir)) return results;
3067
- for (const entry of fs6.readdirSync(dir, { withFileTypes: true })) {
3405
+ for (const entry of fs5.readdirSync(dir, { withFileTypes: true })) {
3068
3406
  const fullPath = path9.join(dir, entry.name);
3069
3407
  if (entry.isSymbolicLink()) continue;
3070
3408
  if (entry.isDirectory()) {
@@ -3077,7 +3415,7 @@ function walkMdPaths(rootReal, dir) {
3077
3415
  }
3078
3416
 
3079
3417
  // src/sync/index.ts
3080
- import fs7 from "fs";
3418
+ import fs6 from "fs";
3081
3419
  import path10 from "path";
3082
3420
  import crypto6 from "crypto";
3083
3421
  var DEFAULT_EXTENSIONS = /* @__PURE__ */ new Set([".md", ".txt", ".mdx", ".rst"]);
@@ -3116,8 +3454,8 @@ function syncChanges(options) {
3116
3454
  for (const [relPath, hash] of Object.entries(currentFiles)) {
3117
3455
  newState.fileHashes[relPath] = hash;
3118
3456
  }
3119
- fs7.mkdirSync(path10.dirname(stateFilePath), { recursive: true });
3120
- fs7.writeFileSync(stateFilePath, JSON.stringify(newState, null, 2));
3457
+ fs6.mkdirSync(path10.dirname(stateFilePath), { recursive: true });
3458
+ fs6.writeFileSync(stateFilePath, JSON.stringify(newState, null, 2));
3121
3459
  }
3122
3460
  return {
3123
3461
  scanned: Object.keys(currentFiles).length,
@@ -3171,7 +3509,7 @@ function scanFiles(root, extensions, exclude) {
3171
3509
  function walk(dir, isRoot = false) {
3172
3510
  let entries;
3173
3511
  try {
3174
- entries = fs7.readdirSync(dir, { withFileTypes: true });
3512
+ entries = fs6.readdirSync(dir, { withFileTypes: true });
3175
3513
  } catch (err) {
3176
3514
  if (isRoot) {
3177
3515
  throw new Error(
@@ -3190,7 +3528,7 @@ function scanFiles(root, extensions, exclude) {
3190
3528
  if (!extensions.has(ext)) continue;
3191
3529
  const relPath = path10.relative(root, fullPath);
3192
3530
  try {
3193
- const content = fs7.readFileSync(fullPath, "utf8");
3531
+ const content = fs6.readFileSync(fullPath, "utf8");
3194
3532
  result[relPath] = hashContent3(content);
3195
3533
  } catch {
3196
3534
  }
@@ -3207,7 +3545,7 @@ function computeDiff(current, previous, sourceDir) {
3207
3545
  if (!(relPath in previous)) {
3208
3546
  let size = 0;
3209
3547
  try {
3210
- size = fs7.statSync(fullPath).size;
3548
+ size = fs6.statSync(fullPath).size;
3211
3549
  } catch {
3212
3550
  }
3213
3551
  changes.push({
@@ -3220,7 +3558,7 @@ function computeDiff(current, previous, sourceDir) {
3220
3558
  } else if (previous[relPath] !== hash) {
3221
3559
  let size = 0;
3222
3560
  try {
3223
- size = fs7.statSync(fullPath).size;
3561
+ size = fs6.statSync(fullPath).size;
3224
3562
  } catch {
3225
3563
  }
3226
3564
  changes.push({
@@ -3248,7 +3586,7 @@ function computeDiff(current, previous, sourceDir) {
3248
3586
  }
3249
3587
  function loadState(stateFilePath) {
3250
3588
  try {
3251
- const raw = fs7.readFileSync(stateFilePath, "utf8");
3589
+ const raw = fs6.readFileSync(stateFilePath, "utf8");
3252
3590
  return JSON.parse(raw);
3253
3591
  } catch {
3254
3592
  return {
@@ -3263,7 +3601,7 @@ function hashContent3(content) {
3263
3601
  }
3264
3602
 
3265
3603
  // src/spaces/index.ts
3266
- import fs8 from "fs";
3604
+ import fs7 from "fs";
3267
3605
  import path11 from "path";
3268
3606
  import crypto7 from "crypto";
3269
3607
  var MANIFEST_VERSION = 1;
@@ -3279,7 +3617,7 @@ function getManifestPath(baseDir) {
3279
3617
  }
3280
3618
  function loadManifest(baseDir, memoryDirOverride) {
3281
3619
  const manifestPath2 = getManifestPath(baseDir);
3282
- if (!fs8.existsSync(manifestPath2)) {
3620
+ if (!fs7.existsSync(manifestPath2)) {
3283
3621
  const personalSpace = createPersonalSpace(baseDir, memoryDirOverride);
3284
3622
  const manifest = {
3285
3623
  activeSpaceId: personalSpace.id,
@@ -3289,19 +3627,19 @@ function loadManifest(baseDir, memoryDirOverride) {
3289
3627
  saveManifest(manifest, baseDir);
3290
3628
  return manifest;
3291
3629
  }
3292
- const raw = JSON.parse(fs8.readFileSync(manifestPath2, "utf8"));
3630
+ const raw = JSON.parse(fs7.readFileSync(manifestPath2, "utf8"));
3293
3631
  return raw;
3294
3632
  }
3295
3633
  function saveManifest(manifest, baseDir) {
3296
3634
  const manifestPath2 = getManifestPath(baseDir);
3297
- fs8.mkdirSync(path11.dirname(manifestPath2), { recursive: true });
3298
- fs8.writeFileSync(manifestPath2, JSON.stringify(manifest, null, 2) + "\n");
3635
+ fs7.mkdirSync(path11.dirname(manifestPath2), { recursive: true });
3636
+ fs7.writeFileSync(manifestPath2, JSON.stringify(manifest, null, 2) + "\n");
3299
3637
  }
3300
3638
  function createPersonalSpace(baseDir, memoryDirOverride) {
3301
3639
  const homeDir = baseDir ?? resolveHomeDir();
3302
3640
  const standalonePath = path11.join(homeDir, ".engram", "memory");
3303
3641
  const openclawPath = path11.join(homeDir, ".openclaw", "workspace", "memory", "local");
3304
- const memoryDir = memoryDirOverride ?? readEnvVar("REMNIC_MEMORY_DIR") ?? readEnvVar("ENGRAM_MEMORY_DIR") ?? (fs8.existsSync(standalonePath) ? standalonePath : fs8.existsSync(openclawPath) ? openclawPath : standalonePath);
3642
+ const memoryDir = memoryDirOverride ?? readEnvVar("REMNIC_MEMORY_DIR") ?? readEnvVar("ENGRAM_MEMORY_DIR") ?? (fs7.existsSync(standalonePath) ? standalonePath : fs7.existsSync(openclawPath) ? openclawPath : standalonePath);
3305
3643
  const normalizedMemoryDir = normalizeSpaceMemoryDir(memoryDir);
3306
3644
  const now = (/* @__PURE__ */ new Date()).toISOString();
3307
3645
  return {
@@ -3353,7 +3691,7 @@ function createSpace(options) {
3353
3691
  owner: readEnvVar("USER"),
3354
3692
  parentSpaceId: options.parentSpaceId
3355
3693
  };
3356
- fs8.mkdirSync(memoryDir, { recursive: true });
3694
+ fs7.mkdirSync(memoryDir, { recursive: true });
3357
3695
  manifest.spaces.push(space);
3358
3696
  manifest.updatedAt = now;
3359
3697
  saveManifest(manifest, options.baseDir);
@@ -3528,30 +3866,30 @@ function mergeSpaces(sourceSpaceId, targetSpaceId, options) {
3528
3866
  }
3529
3867
  function getAuditLog(baseDir) {
3530
3868
  const auditPath = path11.join(getSpacesDir(baseDir), "audit.jsonl");
3531
- if (!fs8.existsSync(auditPath)) return [];
3532
- const lines = fs8.readFileSync(auditPath, "utf8").trim().split("\n");
3869
+ if (!fs7.existsSync(auditPath)) return [];
3870
+ const lines = fs7.readFileSync(auditPath, "utf8").trim().split("\n");
3533
3871
  return lines.filter((l) => l.trim()).map((l) => JSON.parse(l));
3534
3872
  }
3535
3873
  function appendAudit(entry, baseDir) {
3536
3874
  const auditPath = path11.join(getSpacesDir(baseDir), "audit.jsonl");
3537
- fs8.mkdirSync(path11.dirname(auditPath), { recursive: true });
3875
+ fs7.mkdirSync(path11.dirname(auditPath), { recursive: true });
3538
3876
  const full = {
3539
3877
  id: crypto7.randomUUID(),
3540
3878
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3541
3879
  ...entry
3542
3880
  };
3543
- fs8.appendFileSync(auditPath, JSON.stringify(full) + "\n");
3881
+ fs7.appendFileSync(auditPath, JSON.stringify(full) + "\n");
3544
3882
  }
3545
3883
  function copyMemories(sourceDir, targetDir, options) {
3546
3884
  let merged = 0;
3547
3885
  const conflicts = [];
3548
3886
  let skipped = 0;
3549
- if (!fs8.existsSync(sourceDir)) {
3887
+ if (!fs7.existsSync(sourceDir)) {
3550
3888
  return { merged: 0, conflicts: [], skipped: 0 };
3551
3889
  }
3552
- const sourceRoot = fs8.realpathSync(sourceDir);
3553
- fs8.mkdirSync(targetDir, { recursive: true });
3554
- const targetRoot = fs8.realpathSync(targetDir);
3890
+ const sourceRoot = fs7.realpathSync(sourceDir);
3891
+ fs7.mkdirSync(targetDir, { recursive: true });
3892
+ const targetRoot = fs7.realpathSync(targetDir);
3555
3893
  const sourceFiles = walkMd2(sourceRoot);
3556
3894
  for (const sourcePath of sourceFiles) {
3557
3895
  const sourceRealPath = safeRealpath(sourcePath);
@@ -3564,7 +3902,7 @@ function copyMemories(sourceDir, targetDir, options) {
3564
3902
  skipped++;
3565
3903
  continue;
3566
3904
  }
3567
- const content = fs8.readFileSync(sourcePath, "utf8");
3905
+ const content = fs7.readFileSync(sourcePath, "utf8");
3568
3906
  const relativePath = path11.relative(sourceRoot, sourceRealPath);
3569
3907
  const targetPath = path11.resolve(targetRoot, relativePath);
3570
3908
  if (!isPathInsideRoot(targetPath, targetRoot)) {
@@ -3579,7 +3917,7 @@ function copyMemories(sourceDir, targetDir, options) {
3579
3917
  continue;
3580
3918
  }
3581
3919
  }
3582
- if (fs8.existsSync(targetPath)) {
3920
+ if (fs7.existsSync(targetPath)) {
3583
3921
  const targetStat = safeLstat(targetPath);
3584
3922
  if (!targetStat?.isFile() || targetStat.isSymbolicLink()) {
3585
3923
  skipped++;
@@ -3591,8 +3929,8 @@ function copyMemories(sourceDir, targetDir, options) {
3591
3929
  continue;
3592
3930
  }
3593
3931
  }
3594
- if (fs8.existsSync(targetPath) && !options?.force) {
3595
- const targetContent = fs8.readFileSync(targetPath, "utf8");
3932
+ if (fs7.existsSync(targetPath) && !options?.force) {
3933
+ const targetContent = fs7.readFileSync(targetPath, "utf8");
3596
3934
  const targetHash = hashContent4(targetContent);
3597
3935
  if (sourceHash !== targetHash) {
3598
3936
  conflicts.push({
@@ -3608,13 +3946,13 @@ function copyMemories(sourceDir, targetDir, options) {
3608
3946
  skipped++;
3609
3947
  continue;
3610
3948
  }
3611
- fs8.mkdirSync(path11.dirname(targetPath), { recursive: true });
3949
+ fs7.mkdirSync(path11.dirname(targetPath), { recursive: true });
3612
3950
  const targetParentRealPath = safeRealpath(path11.dirname(targetPath));
3613
3951
  if (!targetParentRealPath || !isPathInsideRoot(targetParentRealPath, targetRoot)) {
3614
3952
  skipped++;
3615
3953
  continue;
3616
3954
  }
3617
- fs8.writeFileSync(targetPath, content);
3955
+ fs7.writeFileSync(targetPath, content);
3618
3956
  merged++;
3619
3957
  }
3620
3958
  return { merged, conflicts, skipped };
@@ -3625,7 +3963,7 @@ function hashContent4(content) {
3625
3963
  function walkMd2(dir) {
3626
3964
  const results = [];
3627
3965
  function walk(d) {
3628
- for (const entry of fs8.readdirSync(d, { withFileTypes: true })) {
3966
+ for (const entry of fs7.readdirSync(d, { withFileTypes: true })) {
3629
3967
  const fullPath = path11.join(d, entry.name);
3630
3968
  if (entry.isSymbolicLink()) {
3631
3969
  continue;
@@ -3642,14 +3980,14 @@ function walkMd2(dir) {
3642
3980
  }
3643
3981
  function safeLstat(filePath) {
3644
3982
  try {
3645
- return fs8.lstatSync(filePath);
3983
+ return fs7.lstatSync(filePath);
3646
3984
  } catch {
3647
3985
  return null;
3648
3986
  }
3649
3987
  }
3650
3988
  function safeRealpath(filePath) {
3651
3989
  try {
3652
- return fs8.realpathSync(filePath);
3990
+ return fs7.realpathSync(filePath);
3653
3991
  } catch {
3654
3992
  return null;
3655
3993
  }
@@ -3775,7 +4113,7 @@ var REMNIC_RECALL_DECISION_RULES = `## When to Use Recall vs Direct Read
3775
4113
  `;
3776
4114
 
3777
4115
  // src/memory-extension/codex-publisher.ts
3778
- import fs9 from "fs";
4116
+ import fs8 from "fs";
3779
4117
  import os from "os";
3780
4118
  import path12 from "path";
3781
4119
  var REMNIC_EXTENSION_DIR_NAME = "remnic";
@@ -3810,7 +4148,7 @@ var CodexMemoryExtensionPublisher = class {
3810
4148
  async isHostAvailable() {
3811
4149
  try {
3812
4150
  const home = readEnvVar("CODEX_HOME")?.trim() || path12.join(resolveHomeDir(), ".codex");
3813
- return fs9.existsSync(home);
4151
+ return fs8.existsSync(home);
3814
4152
  } catch {
3815
4153
  return false;
3816
4154
  }
@@ -3864,18 +4202,18 @@ When running inside the Codex phase-2 consolidation sandbox:
3864
4202
  const filesWritten = [];
3865
4203
  const skipped = [];
3866
4204
  ctx.log.info(`Publishing Codex memory extension to ${extensionRoot}`);
3867
- fs9.mkdirSync(extensionRoot, { recursive: true });
4205
+ fs8.mkdirSync(extensionRoot, { recursive: true });
3868
4206
  const content = await this.renderInstructions(ctx);
3869
4207
  const tmpPath = `${instructionsPath}.tmp-${process.pid}-${Date.now()}`;
3870
4208
  try {
3871
- fs9.writeFileSync(tmpPath, content, "utf-8");
3872
- fs9.renameSync(tmpPath, instructionsPath);
4209
+ fs8.writeFileSync(tmpPath, content, "utf-8");
4210
+ fs8.renameSync(tmpPath, instructionsPath);
3873
4211
  filesWritten.push(instructionsPath);
3874
4212
  ctx.log.info(`Wrote ${instructionsPath}`);
3875
4213
  } catch (err) {
3876
4214
  try {
3877
- if (fs9.existsSync(tmpPath)) {
3878
- fs9.unlinkSync(tmpPath);
4215
+ if (fs8.existsSync(tmpPath)) {
4216
+ fs8.unlinkSync(tmpPath);
3879
4217
  }
3880
4218
  } catch {
3881
4219
  }
@@ -3890,8 +4228,8 @@ When running inside the Codex phase-2 consolidation sandbox:
3890
4228
  }
3891
4229
  async unpublish() {
3892
4230
  const extensionRoot = await this.resolveExtensionRoot();
3893
- if (fs9.existsSync(extensionRoot)) {
3894
- fs9.rmSync(extensionRoot, { recursive: true, force: true });
4231
+ if (fs8.existsSync(extensionRoot)) {
4232
+ fs8.rmSync(extensionRoot, { recursive: true, force: true });
3895
4233
  }
3896
4234
  }
3897
4235
  };