@remnic/core 9.3.748 → 9.3.749

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.
@@ -538,7 +538,7 @@ import {
538
538
  } from "./chunk-PVGDJXVK.js";
539
539
 
540
540
  // src/orchestrator.ts
541
- import path12 from "path";
541
+ import path13 from "path";
542
542
  import os2 from "os";
543
543
  import { createHash as createHash7 } from "crypto";
544
544
  import { existsSync, readFileSync } from "fs";
@@ -2168,7 +2168,7 @@ var RecallRerankCoordinator = class _RecallRerankCoordinator {
2168
2168
  nsMap = buildMemoryWorthCounterMap(memories);
2169
2169
  this.memoryWorthCounterCache.set(ns, { at: nowMs, counters: nsMap });
2170
2170
  }
2171
- for (const [path27, c] of nsMap) counters.set(path27, c);
2171
+ for (const [path28, c] of nsMap) counters.set(path28, c);
2172
2172
  } catch (err) {
2173
2173
  log.debug("memory-worth: failed to read namespace, skipping", {
2174
2174
  namespace: ns,
@@ -2253,7 +2253,7 @@ var RecallRerankCoordinator = class _RecallRerankCoordinator {
2253
2253
  namespaces,
2254
2254
  {
2255
2255
  readNamespaceMemories: async (ns) => (await this.getStorage(ns)).readAllMemories(),
2256
- readMemoryFrontmatter: async (path27) => {
2256
+ readMemoryFrontmatter: async (path28) => {
2257
2257
  if (!fallbackReader) {
2258
2258
  for (const ns of namespaces) {
2259
2259
  try {
@@ -2264,7 +2264,7 @@ var RecallRerankCoordinator = class _RecallRerankCoordinator {
2264
2264
  }
2265
2265
  }
2266
2266
  if (!fallbackReader) return null;
2267
- const memory = await this.readQmdResultMemory(path27, fallbackReader, namespaces);
2267
+ const memory = await this.readQmdResultMemory(path28, fallbackReader, namespaces);
2268
2268
  return memory ? memory.frontmatter : null;
2269
2269
  }
2270
2270
  },
@@ -12663,6 +12663,278 @@ var OrchestratorInitCoordinator = class {
12663
12663
  }
12664
12664
  };
12665
12665
 
12666
+ // src/orchestration/persistence-index.ts
12667
+ import path12 from "path";
12668
+ var PersistenceIndexCoordinator = class {
12669
+ constructor(deps) {
12670
+ this.deps = deps;
12671
+ }
12672
+ deps;
12673
+ async hasContentHashDedup(targetStorage, content) {
12674
+ const index = await this.deps.contentHashIndexForStorage(targetStorage);
12675
+ return index ? index.has(content) : false;
12676
+ }
12677
+ async addContentHashDedup(targetStorage, content) {
12678
+ const index = await this.deps.contentHashIndexForStorage(targetStorage);
12679
+ if (!index) return;
12680
+ index.add(content);
12681
+ }
12682
+ async removeContentHashForMemory(targetStorage, memory, context) {
12683
+ const index = await this.deps.contentHashIndexForStorage(targetStorage);
12684
+ if (!index) return;
12685
+ if (memory.frontmatter.contentHash) {
12686
+ index.removeByHash(memory.frontmatter.contentHash);
12687
+ return;
12688
+ }
12689
+ log.warn(
12690
+ `[${context}] removing hash for legacy memory ${memory.frontmatter.id ?? "(unknown)"} via content fallback - no contentHash in frontmatter`
12691
+ );
12692
+ index.remove(memory.content);
12693
+ }
12694
+ /**
12695
+ * Issue #1671 — backfill bi-temporal bounds onto an existing promoted/deduped
12696
+ * copy that was written BEFORE the source fact carried a resolved
12697
+ * `invalid_at`/`observedAt`/`eventTimeSource`.
12698
+ *
12699
+ * On re-extraction/backfill, a fact may now carry a resolved end bound (e.g.
12700
+ * "until June 2025") that the existing copy lacks because it was promoted
12701
+ * before bi-temporal wiring existed. Without this backfill, recall keeps
12702
+ * surfacing an expired fact even though the source copy now expires correctly.
12703
+ *
12704
+ * Finds the active fact in `targetStorage` matching `dedupContent`, then
12705
+ * patches the temporal frontmatter the existing copy is missing. Best-effort
12706
+ * / fail-open — any I/O error is logged and swallowed so the dedup
12707
+ * short-circuit is never blocked by a backfill failure.
12708
+ *
12709
+ * Matching: the stored `frontmatter.contentHash` is compared against
12710
+ * `ContentHashIndex.computeHash(dedupContent)` first (the exact hash the
12711
+ * content-hash index uses), then falls back to stripping citations and
12712
+ * comparing normalized bodies. This handles inline-attribution deployments
12713
+ * where the persisted body carries a citation marker the dedup key does not.
12714
+ *
12715
+ * I/O gate: only triggers when `invalidAt` is present (the end bound that
12716
+ * actually changes recall behavior by expiring the fact). `observedAt` and
12717
+ * `eventTimeSource` alone don'\''t expire a fact, so backfilling them without
12718
+ * an end bound would cause a full readAllMemories scan on every dedup hit
12719
+ * under biTemporal for no recall benefit.
12720
+ *
12721
+ * Only patches fields the existing copy LACKS — never overwrites a bound the
12722
+ * copy already carries.
12723
+ */
12724
+ async backfillTemporalBoundsOnDedupHit(targetStorage, dedupContent, bounds, entityRef) {
12725
+ const hasExtractedStart = bounds.validFrom !== void 0 && bounds.eventTimeSource === "extracted";
12726
+ if (!bounds.invalidAt && !hasExtractedStart) return;
12727
+ try {
12728
+ const incomingHash = ContentHashIndex.computeHash(dedupContent);
12729
+ const normalizedIncoming = ContentHashIndex.normalizeContent(dedupContent);
12730
+ const incomingEntityNorm = entityRef ? normalizeSupersessionKey(entityRef) : void 0;
12731
+ const all = await targetStorage.readAllMemories();
12732
+ const existing = all.find((m) => {
12733
+ if (m.frontmatter.category !== "fact") return false;
12734
+ if ((m.frontmatter.status ?? "active") !== "active") return false;
12735
+ if (incomingEntityNorm && m.frontmatter.entityRef && normalizeSupersessionKey(m.frontmatter.entityRef) !== incomingEntityNorm) {
12736
+ return false;
12737
+ }
12738
+ if (m.frontmatter.contentHash) {
12739
+ return m.frontmatter.contentHash === incomingHash;
12740
+ }
12741
+ return ContentHashIndex.normalizeContent(
12742
+ stripCitationForTemplate(m.content ?? "", this.deps.config.inlineSourceAttributionFormat)
12743
+ ) === normalizedIncoming;
12744
+ });
12745
+ if (!existing) return;
12746
+ const patch = {};
12747
+ const fm = existing.frontmatter;
12748
+ if (bounds.invalidAt && (!fm.invalid_at || fm.invalid_at.length === 0)) {
12749
+ patch.invalid_at = bounds.invalidAt;
12750
+ }
12751
+ if (bounds.observedAt && (!fm.observedAt || fm.observedAt.length === 0)) {
12752
+ patch.observedAt = bounds.observedAt;
12753
+ }
12754
+ if (bounds.eventTimeSource && (!fm.eventTimeSource || fm.eventTimeSource.length === 0)) {
12755
+ patch.eventTimeSource = bounds.eventTimeSource;
12756
+ }
12757
+ if (bounds.validFrom && bounds.eventTimeSource === "extracted" && fm.valid_at !== bounds.validFrom) {
12758
+ patch.valid_at = bounds.validFrom;
12759
+ if (fm.eventTimeSource !== "extracted") {
12760
+ patch.eventTimeSource = "extracted";
12761
+ }
12762
+ }
12763
+ if (Object.keys(patch).length === 0) return;
12764
+ const ok = await targetStorage.writeMemoryFrontmatter(existing, patch);
12765
+ if (ok) {
12766
+ log.debug(
12767
+ `bitemporal-backfill: patched ${Object.keys(patch).join(",")} onto existing fact ${fm.id ?? "(unknown)"} in ${targetStorage.dir}`
12768
+ );
12769
+ }
12770
+ } catch (err) {
12771
+ log.warn(
12772
+ `bitemporal-backfill: failed open for ${targetStorage.dir}: ${err}`
12773
+ );
12774
+ }
12775
+ }
12776
+ async saveContentHashIndexes() {
12777
+ const indexes = /* @__PURE__ */ new Set();
12778
+ if (this.deps.contentHashIndex) indexes.add(this.deps.contentHashIndex);
12779
+ for (const index of this.deps.contentHashIndexesByStorageDir.values()) {
12780
+ indexes.add(index);
12781
+ }
12782
+ for (const index of indexes) {
12783
+ await index.save();
12784
+ }
12785
+ }
12786
+ async indexPersistedMemory(storage, memoryId) {
12787
+ if (!resolveMemoryLifecycleCapabilities(this.deps.config).embeddingFallback) return;
12788
+ if (!await this.deps.embeddingFallback.isAvailable()) return;
12789
+ const memory = await storage.getMemoryById(memoryId);
12790
+ if (!memory) return;
12791
+ await this.deps.embeddingFallback.indexFile(
12792
+ memoryId,
12793
+ memory.content,
12794
+ memory.path
12795
+ );
12796
+ }
12797
+ /**
12798
+ * Build a graph edge for a persisted memory (v8.2).
12799
+ * Shared helper used by both the chunked and non-chunked write paths to avoid duplication.
12800
+ * Fail-open: caller wraps in try/catch.
12801
+ */
12802
+ async buildGraphEdge(storage, memoryRelPath, entityRef, memoryId, factContent, allMemsForGraph, memoryPathById, threadIdForEdge, threadEpisodeIdsForGraph, fallbackCausalPredecessor, graphCaps = resolveGraphConstructionCapabilities(this.deps.config)) {
12803
+ const entitySiblings = [];
12804
+ if (entityRef) {
12805
+ try {
12806
+ const allMems = allMemsForGraph ?? [];
12807
+ for (const m of allMems) {
12808
+ if (m.frontmatter.entityRef === entityRef) {
12809
+ const rel = path12.relative(storage.dir, m.path);
12810
+ if (rel !== memoryRelPath) entitySiblings.push(rel);
12811
+ }
12812
+ }
12813
+ } catch {
12814
+ }
12815
+ }
12816
+ const recentInThread = [];
12817
+ if (threadIdForEdge && threadEpisodeIdsForGraph?.length) {
12818
+ try {
12819
+ recentInThread.push(
12820
+ ...resolveRecentThreadMemoryPaths({
12821
+ threadEpisodeIds: threadEpisodeIdsForGraph,
12822
+ currentMemoryId: memoryId,
12823
+ allMemsForGraph,
12824
+ pathById: memoryPathById,
12825
+ storageDir: storage.dir,
12826
+ maxRecent: 3
12827
+ })
12828
+ );
12829
+ } catch {
12830
+ }
12831
+ }
12832
+ if (recentInThread.length === 0 && graphCaps.graphWriteSessionAdjacency && fallbackCausalPredecessor && fallbackCausalPredecessor !== memoryRelPath) {
12833
+ recentInThread.push(fallbackCausalPredecessor);
12834
+ }
12835
+ const causalPredecessor = recentInThread[recentInThread.length - 1] ?? fallbackCausalPredecessor;
12836
+ await this.deps.graphIndexFor(storage).onMemoryWritten({
12837
+ memoryPath: memoryRelPath,
12838
+ entityRef,
12839
+ content: factContent,
12840
+ created: (/* @__PURE__ */ new Date()).toISOString(),
12841
+ threadId: threadIdForEdge,
12842
+ recentInThread,
12843
+ entitySiblings,
12844
+ causalPredecessor,
12845
+ graphCapsOverride: {
12846
+ entityGraph: graphCaps.entityGraph,
12847
+ timeGraph: graphCaps.timeGraph,
12848
+ causalGraph: graphCaps.causalGraph,
12849
+ multiGraphMemory: graphCaps.multiGraphMemory
12850
+ }
12851
+ });
12852
+ }
12853
+ /**
12854
+ * Batch-update temporal and tag indexes after extraction (v8.1).
12855
+ * Reads each persisted memory's path + frontmatter and adds them to
12856
+ * state/index_time.json and state/index_tags.json.
12857
+ * Fail-open: any error is logged but does not abort extraction.
12858
+ */
12859
+ async updateTemporalTagIndexes(storage, persistedIds) {
12860
+ const caps = resolveCapabilities(this.deps.config);
12861
+ if (!resolveIndexingCapabilities(this.deps.config).queryAwareIndexing && !caps.parallelRetrieval)
12862
+ return;
12863
+ const needsFullRebuild = !indexesExist(this.deps.config.memoryDir);
12864
+ if (!needsFullRebuild && persistedIds.length === 0) return;
12865
+ try {
12866
+ const allMemories = needsFullRebuild && resolveNamespaceCapabilities(this.deps.config).namespaces ? await this.deps.readAllMemoriesForNamespaces(
12867
+ Array.from(
12868
+ /* @__PURE__ */ new Set([
12869
+ this.deps.config.defaultNamespace,
12870
+ this.deps.config.sharedNamespace,
12871
+ ...this.deps.config.namespacePolicies.map((p) => p.name)
12872
+ ])
12873
+ )
12874
+ ) : await storage.readAllMemories();
12875
+ const pool = needsFullRebuild ? allMemories.filter((m) => isActiveMemoryStatus(m.frontmatter.status)) : (() => {
12876
+ const idSet = new Set(persistedIds);
12877
+ return allMemories.filter((m) => idSet.has(m.frontmatter.id));
12878
+ })();
12879
+ const entries = [];
12880
+ for (const mem of pool) {
12881
+ if (mem.path && mem.frontmatter?.created) {
12882
+ entries.push({
12883
+ path: mem.path,
12884
+ createdAt: mem.frontmatter.created,
12885
+ tags: mem.frontmatter.tags ?? []
12886
+ });
12887
+ }
12888
+ }
12889
+ if (needsFullRebuild) {
12890
+ clearIndexes(this.deps.config.memoryDir);
12891
+ if (entries.length > 0) {
12892
+ indexMemoriesBatch(this.deps.config.memoryDir, entries);
12893
+ }
12894
+ log.info(
12895
+ `temporal-index: bootstrapped from ${entries.length} active memories`
12896
+ );
12897
+ } else if (entries.length > 0) {
12898
+ indexMemoriesBatch(this.deps.config.memoryDir, entries);
12899
+ }
12900
+ } catch (err) {
12901
+ log.debug(`temporal-index update failed (non-fatal): ${err}`);
12902
+ }
12903
+ }
12904
+ /**
12905
+ * Issue #373 — nearest-neighbor lookup for the write-time semantic dedup
12906
+ * guard. Returns the top-K embedding hits against the currently indexed
12907
+ * memories, or an empty array when the embedding backend is unavailable.
12908
+ * Intentionally does NOT throw; `decideSemanticDedup` treats both "empty"
12909
+ * and "error" outcomes as fail-open (keep the candidate).
12910
+ *
12911
+ * PR #399 P1 fix: when namespaces are enabled the lookup must be scoped
12912
+ * to the SAME namespace as the fact being written. Otherwise a
12913
+ * high-similarity memory from another namespace can suppress a write in
12914
+ * the target namespace — cross-tenant data loss. Callers pass the target
12915
+ * storage so we can translate its root directory into the correct index
12916
+ * path prefix (and, for the legacy default-namespace layout at
12917
+ * `memoryDir` root, an exclusion list for `namespaces/*`).
12918
+ */
12919
+ async semanticDedupLookup(content, limit, targetStorage) {
12920
+ if (!resolveMemoryLifecycleCapabilities(this.deps.config).embeddingFallback) {
12921
+ throw new Error("semantic dedup: embedding backend not configured");
12922
+ }
12923
+ if (!await this.deps.embeddingFallback.isAvailable()) {
12924
+ log.debug("semantic dedup: embedding backend unavailable, skipping");
12925
+ throw new Error("semantic dedup: embedding backend unavailable");
12926
+ }
12927
+ const scope = this.deps.semanticDedupScopeFor(targetStorage);
12928
+ const hits = await this.deps.embeddingFallback.search(content, limit, { ...scope, throwOnTimeout: true });
12929
+ if (!Array.isArray(hits) || hits.length === 0) return [];
12930
+ return hits.map((hit) => ({
12931
+ id: hit.id,
12932
+ score: hit.score,
12933
+ path: hit.path
12934
+ }));
12935
+ }
12936
+ };
12937
+
12666
12938
  // src/maintenance/pattern-reinforcement.ts
12667
12939
  function patternReinforcementKey(content) {
12668
12940
  return content.trim().toLowerCase().replace(/\s+/g, " ").slice(0, 200);
@@ -13533,7 +13805,7 @@ async function qmdStartupCollectionCheckWithTimeout(promise, controller, label)
13533
13805
  return await Promise.race([checkedPromise, timeoutPromise]);
13534
13806
  }
13535
13807
  function defaultWorkspaceDir() {
13536
- return path12.join(os2.homedir(), ".openclaw", "workspace");
13808
+ return path13.join(os2.homedir(), ".openclaw", "workspace");
13537
13809
  }
13538
13810
  function sanitizeSessionKeyForFilename(sessionKey) {
13539
13811
  const readable = sessionKey.replace(/[^a-zA-Z0-9._-]/g, "_");
@@ -13894,7 +14166,7 @@ function buildMemoryPathById(allMemsForGraph, storageDir) {
13894
14166
  for (const mem of allMemsForGraph ?? []) {
13895
14167
  const id = mem.frontmatter.id;
13896
14168
  if (!id) continue;
13897
- pathById.set(id, path12.relative(storageDir, mem.path));
14169
+ pathById.set(id, path13.relative(storageDir, mem.path));
13898
14170
  }
13899
14171
  return pathById;
13900
14172
  }
@@ -13902,7 +14174,7 @@ function appendMemoryToGraphContext(options) {
13902
14174
  if (!Array.isArray(options.allMemsForGraph)) return;
13903
14175
  const nowIso = (/* @__PURE__ */ new Date()).toISOString();
13904
14176
  options.allMemsForGraph.push({
13905
- path: path12.join(options.storageDir, options.memoryRelPath),
14177
+ path: path13.join(options.storageDir, options.memoryRelPath),
13906
14178
  content: options.content,
13907
14179
  frontmatter: {
13908
14180
  id: options.memoryId,
@@ -13922,16 +14194,16 @@ function resolvePersistedMemoryRelativePath(options) {
13922
14194
  const persisted = options.pathById.get(options.memoryId);
13923
14195
  if (persisted) return persisted;
13924
14196
  if (options.category === "correction") {
13925
- return path12.join("corrections", `${options.memoryId}.md`);
14197
+ return path13.join("corrections", `${options.memoryId}.md`);
13926
14198
  }
13927
14199
  const subtree = categoryDirName(options.category);
13928
14200
  const idParts = options.memoryId.split("-");
13929
14201
  const maybeTimestamp = Number(idParts[1]);
13930
14202
  if (Number.isFinite(maybeTimestamp) && maybeTimestamp > 0) {
13931
14203
  const day = new Date(maybeTimestamp).toISOString().slice(0, 10);
13932
- return path12.join(subtree, day, `${options.memoryId}.md`);
14204
+ return path13.join(subtree, day, `${options.memoryId}.md`);
13933
14205
  }
13934
- return path12.join(subtree, `${options.memoryId}.md`);
14206
+ return path13.join(subtree, `${options.memoryId}.md`);
13935
14207
  }
13936
14208
  var Orchestrator = class _Orchestrator {
13937
14209
  storage;
@@ -14492,7 +14764,7 @@ var Orchestrator = class _Orchestrator {
14492
14764
  const defaultNs = normalizeNamespaceIdentity(this.config.defaultNamespace);
14493
14765
  if (ns !== defaultNs && !isSafeRouteNamespace(ns)) return;
14494
14766
  if (!this.storageDirMatchesNamespaceHint(ns, storageDir)) return;
14495
- const resolvedStorageDir = path12.resolve(storageDir);
14767
+ const resolvedStorageDir = path13.resolve(storageDir);
14496
14768
  let hints = this.namespaceStorageDirHints.get(resolvedStorageDir);
14497
14769
  if (!hints) {
14498
14770
  hints = /* @__PURE__ */ new Set();
@@ -14503,21 +14775,21 @@ var Orchestrator = class _Orchestrator {
14503
14775
  storageDirMatchesNamespaceHint(namespace, storageDir) {
14504
14776
  const ns = normalizeNamespaceIdentity(namespace);
14505
14777
  if (!ns) return false;
14506
- const resolvedStorageDir = path12.resolve(storageDir);
14507
- const resolvedMemoryDir = path12.resolve(this.config.memoryDir);
14778
+ const resolvedStorageDir = path13.resolve(storageDir);
14779
+ const resolvedMemoryDir = path13.resolve(this.config.memoryDir);
14508
14780
  const defaultNs = normalizeNamespaceIdentity(this.config.defaultNamespace);
14509
14781
  if (resolvedStorageDir === resolvedMemoryDir) return ns === defaultNs;
14510
- const resolvedNamespacesDir = path12.join(resolvedMemoryDir, "namespaces");
14782
+ const resolvedNamespacesDir = path13.join(resolvedMemoryDir, "namespaces");
14511
14783
  if (!isPathInsideStorageRoot(resolvedNamespacesDir, resolvedStorageDir)) return false;
14512
- const rawRoot = path12.resolve(resolvedNamespacesDir, ns);
14513
- const tokenRoot = path12.resolve(resolvedNamespacesDir, namespaceIdentityToken(ns));
14784
+ const rawRoot = path13.resolve(resolvedNamespacesDir, ns);
14785
+ const tokenRoot = path13.resolve(resolvedNamespacesDir, namespaceIdentityToken(ns));
14514
14786
  return resolvedStorageDir === rawRoot || resolvedStorageDir === tokenRoot;
14515
14787
  }
14516
14788
  namespaceStorageDirHintOwnershipRank(record, resolvedStorageDir, configured) {
14517
- if (resolvedStorageDir === path12.resolve(this.config.memoryDir)) {
14789
+ if (resolvedStorageDir === path13.resolve(this.config.memoryDir)) {
14518
14790
  return record.namespace === normalizeNamespaceIdentity(this.config.defaultNamespace) ? 0 : 3;
14519
14791
  }
14520
- const leaf = path12.basename(resolvedStorageDir);
14792
+ const leaf = path13.basename(resolvedStorageDir);
14521
14793
  const tokenOwnsRoot = namespaceIdentityToken(record.namespace) === leaf;
14522
14794
  if (tokenOwnsRoot && configured.has(record.namespace)) return 0;
14523
14795
  if (record.namespace === leaf) return 1;
@@ -14545,7 +14817,7 @@ var Orchestrator = class _Orchestrator {
14545
14817
  loadNamespaceStorageDirHintsFromCatalog() {
14546
14818
  if (this.namespaceStorageDirHintsLoaded || !this.namespaceCatalog.enabled) return;
14547
14819
  this.namespaceStorageDirHintsLoaded = true;
14548
- const catalogPath = path12.join(this.config.memoryDir, "state", "namespaces.jsonl");
14820
+ const catalogPath = path13.join(this.config.memoryDir, "state", "namespaces.jsonl");
14549
14821
  if (!existsSync(catalogPath)) return;
14550
14822
  let body;
14551
14823
  try {
@@ -14582,7 +14854,7 @@ var Orchestrator = class _Orchestrator {
14582
14854
  if (!this.storageDirMatchesNamespaceHint(record.namespace, record.storageDir)) {
14583
14855
  continue;
14584
14856
  }
14585
- const resolvedStorageDir = path12.resolve(record.storageDir);
14857
+ const resolvedStorageDir = path13.resolve(record.storageDir);
14586
14858
  const current = preferredByStorageDir.get(resolvedStorageDir);
14587
14859
  preferredByStorageDir.set(
14588
14860
  resolvedStorageDir,
@@ -14730,123 +15002,40 @@ var Orchestrator = class _Orchestrator {
14730
15002
  return index;
14731
15003
  }
14732
15004
  async hasContentHashDedup(targetStorage, content) {
14733
- const index = await this.contentHashIndexForStorage(targetStorage);
14734
- return index ? index.has(content) : false;
15005
+ return this.persistenceIndexCoordinator.hasContentHashDedup(
15006
+ targetStorage,
15007
+ content
15008
+ );
14735
15009
  }
14736
15010
  async addContentHashDedup(targetStorage, content) {
14737
- const index = await this.contentHashIndexForStorage(targetStorage);
14738
- if (!index) return;
14739
- index.add(content);
15011
+ return this.persistenceIndexCoordinator.addContentHashDedup(
15012
+ targetStorage,
15013
+ content
15014
+ );
14740
15015
  }
14741
15016
  async removeContentHashForMemory(targetStorage, memory, context) {
14742
- const index = await this.contentHashIndexForStorage(targetStorage);
14743
- if (!index) return;
14744
- if (memory.frontmatter.contentHash) {
14745
- index.removeByHash(memory.frontmatter.contentHash);
14746
- return;
14747
- }
14748
- log.warn(
14749
- `[${context}] removing hash for legacy memory ${memory.frontmatter.id ?? "(unknown)"} via content fallback - no contentHash in frontmatter`
15017
+ return this.persistenceIndexCoordinator.removeContentHashForMemory(
15018
+ targetStorage,
15019
+ memory,
15020
+ context
14750
15021
  );
14751
- index.remove(memory.content);
14752
15022
  }
14753
- /**
14754
- * Issue #1671 — backfill bi-temporal bounds onto an existing promoted/deduped
14755
- * copy that was written BEFORE the source fact carried a resolved
14756
- * `invalid_at`/`observedAt`/`eventTimeSource`.
14757
- *
14758
- * On re-extraction/backfill, a fact may now carry a resolved end bound (e.g.
14759
- * "until June 2025") that the existing copy lacks because it was promoted
14760
- * before bi-temporal wiring existed. Without this backfill, recall keeps
14761
- * surfacing an expired fact even though the source copy now expires correctly.
14762
- *
14763
- * Finds the active fact in `targetStorage` matching `dedupContent`, then
14764
- * patches the temporal frontmatter the existing copy is missing. Best-effort
14765
- * / fail-open — any I/O error is logged and swallowed so the dedup
14766
- * short-circuit is never blocked by a backfill failure.
14767
- *
14768
- * Matching: the stored `frontmatter.contentHash` is compared against
14769
- * `ContentHashIndex.computeHash(dedupContent)` first (the exact hash the
14770
- * content-hash index uses), then falls back to stripping citations and
14771
- * comparing normalized bodies. This handles inline-attribution deployments
14772
- * where the persisted body carries a citation marker the dedup key does not.
14773
- *
14774
- * I/O gate: only triggers when `invalidAt` is present (the end bound that
14775
- * actually changes recall behavior by expiring the fact). `observedAt` and
14776
- * `eventTimeSource` alone don'\''t expire a fact, so backfilling them without
14777
- * an end bound would cause a full readAllMemories scan on every dedup hit
14778
- * under biTemporal for no recall benefit.
14779
- *
14780
- * Only patches fields the existing copy LACKS — never overwrites a bound the
14781
- * copy already carries.
14782
- */
14783
15023
  async backfillTemporalBoundsOnDedupHit(targetStorage, dedupContent, bounds, entityRef) {
14784
- const hasExtractedStart = bounds.validFrom !== void 0 && bounds.eventTimeSource === "extracted";
14785
- if (!bounds.invalidAt && !hasExtractedStart) return;
14786
- try {
14787
- const incomingHash = ContentHashIndex.computeHash(dedupContent);
14788
- const normalizedIncoming = ContentHashIndex.normalizeContent(dedupContent);
14789
- const incomingEntityNorm = entityRef ? normalizeSupersessionKey(entityRef) : void 0;
14790
- const all = await targetStorage.readAllMemories();
14791
- const existing = all.find((m) => {
14792
- if (m.frontmatter.category !== "fact") return false;
14793
- if ((m.frontmatter.status ?? "active") !== "active") return false;
14794
- if (incomingEntityNorm && m.frontmatter.entityRef && normalizeSupersessionKey(m.frontmatter.entityRef) !== incomingEntityNorm) {
14795
- return false;
14796
- }
14797
- if (m.frontmatter.contentHash) {
14798
- return m.frontmatter.contentHash === incomingHash;
14799
- }
14800
- return ContentHashIndex.normalizeContent(
14801
- stripCitationForTemplate(m.content ?? "", this.config.inlineSourceAttributionFormat)
14802
- ) === normalizedIncoming;
14803
- });
14804
- if (!existing) return;
14805
- const patch = {};
14806
- const fm = existing.frontmatter;
14807
- if (bounds.invalidAt && (!fm.invalid_at || fm.invalid_at.length === 0)) {
14808
- patch.invalid_at = bounds.invalidAt;
14809
- }
14810
- if (bounds.observedAt && (!fm.observedAt || fm.observedAt.length === 0)) {
14811
- patch.observedAt = bounds.observedAt;
14812
- }
14813
- if (bounds.eventTimeSource && (!fm.eventTimeSource || fm.eventTimeSource.length === 0)) {
14814
- patch.eventTimeSource = bounds.eventTimeSource;
14815
- }
14816
- if (bounds.validFrom && bounds.eventTimeSource === "extracted" && fm.valid_at !== bounds.validFrom) {
14817
- patch.valid_at = bounds.validFrom;
14818
- if (fm.eventTimeSource !== "extracted") {
14819
- patch.eventTimeSource = "extracted";
14820
- }
14821
- }
14822
- if (Object.keys(patch).length === 0) return;
14823
- const ok = await targetStorage.writeMemoryFrontmatter(existing, patch);
14824
- if (ok) {
14825
- log.debug(
14826
- `bitemporal-backfill: patched ${Object.keys(patch).join(",")} onto existing fact ${fm.id ?? "(unknown)"} in ${targetStorage.dir}`
14827
- );
14828
- }
14829
- } catch (err) {
14830
- log.warn(
14831
- `bitemporal-backfill: failed open for ${targetStorage.dir}: ${err}`
14832
- );
14833
- }
15024
+ return this.persistenceIndexCoordinator.backfillTemporalBoundsOnDedupHit(
15025
+ targetStorage,
15026
+ dedupContent,
15027
+ bounds,
15028
+ entityRef
15029
+ );
14834
15030
  }
14835
15031
  async saveContentHashIndexes() {
14836
- const indexes = /* @__PURE__ */ new Set();
14837
- if (this.contentHashIndex) indexes.add(this.contentHashIndex);
14838
- for (const index of this.contentHashIndexesByStorageDir.values()) {
14839
- indexes.add(index);
14840
- }
14841
- for (const index of indexes) {
14842
- await index.save();
14843
- }
15032
+ return this.persistenceIndexCoordinator.saveContentHashIndexes();
14844
15033
  }
14845
15034
  constructor(config) {
14846
15035
  this.config = config;
14847
15036
  this.profiler = new ProfilingCollector({
14848
15037
  enabled: resolvePipelineProcessingCapabilities(this.config).profiling,
14849
- storageDir: config.profilingStorageDir || path12.join(config.memoryDir, "profiling"),
15038
+ storageDir: config.profilingStorageDir || path13.join(config.memoryDir, "profiling"),
14850
15039
  maxTraces: config.profilingMaxTraces
14851
15040
  });
14852
15041
  this.namespaceCatalog = new NamespaceCatalog(config);
@@ -14916,7 +15105,7 @@ var Orchestrator = class _Orchestrator {
14916
15105
  this.compounding = resolveConsolidationCapabilities(config).compounding ? new CompoundingEngine(config, this.storage) : void 0;
14917
15106
  this.buffer = new SmartBuffer(config, this.storage);
14918
15107
  this.transcript = new TranscriptManager(config);
14919
- this.conversationIndexDir = path12.join(
15108
+ this.conversationIndexDir = path13.join(
14920
15109
  config.memoryDir,
14921
15110
  "conversation-index",
14922
15111
  "chunks"
@@ -15052,7 +15241,7 @@ var Orchestrator = class _Orchestrator {
15052
15241
  saveContentHashIndexes: () => this.saveContentHashIndexes()
15053
15242
  });
15054
15243
  this.threading = new ThreadingManager(
15055
- path12.join(config.memoryDir, "threads"),
15244
+ path13.join(config.memoryDir, "threads"),
15056
15245
  config.threadingGapMinutes
15057
15246
  );
15058
15247
  const lifecycleCaps = resolveMemoryLifecycleCapabilities(config);
@@ -15467,15 +15656,15 @@ ${doc.content}` : doc.content,
15467
15656
  this.lastFileHygieneRunAtMs = now;
15468
15657
  if (hygiene.rotateEnabled) {
15469
15658
  for (const rel of hygiene.rotatePaths) {
15470
- const abs = path12.isAbsolute(rel) ? rel : path12.join(this.config.workspaceDir, rel);
15659
+ const abs = path13.isAbsolute(rel) ? rel : path13.join(this.config.workspaceDir, rel);
15471
15660
  try {
15472
15661
  const raw = await readFile5(abs, "utf-8");
15473
15662
  if (raw.length > hygiene.rotateMaxBytes) {
15474
- const archiveDir = path12.join(
15663
+ const archiveDir = path13.join(
15475
15664
  this.config.workspaceDir,
15476
15665
  hygiene.archiveDir
15477
15666
  );
15478
- const base = path12.basename(abs);
15667
+ const base = path13.basename(abs);
15479
15668
  const prefix = base.toUpperCase().replace(/\.MD$/i, "").replace(/[^A-Z0-9]+/g, "-") || "FILE";
15480
15669
  const { newContent } = await rotateMarkdownFileToArchive({
15481
15670
  filePath: abs,
@@ -15500,8 +15689,8 @@ ${doc.content}` : doc.content,
15500
15689
  log.warn(w.message);
15501
15690
  }
15502
15691
  if (hygiene.warningsLogEnabled && warnings.length > 0) {
15503
- const fp = path12.join(this.config.memoryDir, hygiene.warningsLogPath);
15504
- await mkdir6(path12.dirname(fp), { recursive: true });
15692
+ const fp = path13.join(this.config.memoryDir, hygiene.warningsLogPath);
15693
+ await mkdir6(path13.dirname(fp), { recursive: true });
15505
15694
  const stamp = (/* @__PURE__ */ new Date()).toISOString();
15506
15695
  const block = `
15507
15696
 
@@ -15640,7 +15829,7 @@ ${doc.content}` : doc.content,
15640
15829
  for (const categoryDir of RECALL_FALLBACK_DIRS) {
15641
15830
  if (memoryRootReal === null) break;
15642
15831
  for (const date of datesToScan) {
15643
- const dateDir = path12.join(storage.dir, categoryDir, date);
15832
+ const dateDir = path13.join(storage.dir, categoryDir, date);
15644
15833
  try {
15645
15834
  const dirStat = await lstat(dateDir);
15646
15835
  if (dirStat.isSymbolicLink() || !dirStat.isDirectory()) continue;
@@ -15649,7 +15838,7 @@ ${doc.content}` : doc.content,
15649
15838
  for (const entry of entries) {
15650
15839
  if (entry.isSymbolicLink()) continue;
15651
15840
  if (!entry.name.endsWith(".md")) continue;
15652
- const fullPath = path12.join(dateDir, entry.name);
15841
+ const fullPath = path13.join(dateDir, entry.name);
15653
15842
  try {
15654
15843
  assertPathInsideRoot(memoryRootReal, await realpath(fullPath), fullPath);
15655
15844
  const raw = await readFile5(fullPath, "utf-8");
@@ -15671,7 +15860,7 @@ ${doc.content}` : doc.content,
15671
15860
  facts.push({
15672
15861
  path: fullPath,
15673
15862
  frontmatter: {
15674
- id: fm.id || path12.basename(entry.name, ".md"),
15863
+ id: fm.id || path13.basename(entry.name, ".md"),
15675
15864
  category: fm.category || "fact",
15676
15865
  created,
15677
15866
  updated: fm.updated || created,
@@ -15694,13 +15883,13 @@ ${doc.content}` : doc.content,
15694
15883
  return a.frontmatter.created < b.frontmatter.created ? -1 : 1;
15695
15884
  });
15696
15885
  const hourlySummaries = [];
15697
- const hourlyBaseDir = path12.join(storage.dir, "summaries", "hourly");
15886
+ const hourlyBaseDir = path13.join(storage.dir, "summaries", "hourly");
15698
15887
  try {
15699
15888
  const sessionKeys = await readdir3(hourlyBaseDir, { withFileTypes: true });
15700
15889
  for (const sk of sessionKeys) {
15701
15890
  if (!sk.isDirectory()) continue;
15702
15891
  for (const date of datesToScan) {
15703
- const summaryFile = path12.join(hourlyBaseDir, sk.name, `${date}.md`);
15892
+ const summaryFile = path13.join(hourlyBaseDir, sk.name, `${date}.md`);
15704
15893
  try {
15705
15894
  const raw = await readFile5(summaryFile, "utf-8");
15706
15895
  const filtered = filterHourlySummaryMarkdownForLocalDay(
@@ -16175,12 +16364,12 @@ ${doc.content}` : doc.content,
16175
16364
  }
16176
16365
  async resolveStateDirForNamespace(namespace) {
16177
16366
  if (!resolveNamespaceCapabilities(this.config).namespaces) {
16178
- return path12.join(this.config.memoryDir, "state");
16367
+ return path13.join(this.config.memoryDir, "state");
16179
16368
  }
16180
16369
  if (namespace !== this.config.defaultNamespace) {
16181
- return path12.join(this.config.memoryDir, "namespaces", namespace, "state");
16370
+ return path13.join(this.config.memoryDir, "namespaces", namespace, "state");
16182
16371
  }
16183
- const candidate = path12.join(
16372
+ const candidate = path13.join(
16184
16373
  this.config.memoryDir,
16185
16374
  "namespaces",
16186
16375
  this.config.defaultNamespace
@@ -16188,11 +16377,11 @@ ${doc.content}` : doc.content,
16188
16377
  try {
16189
16378
  const candidateStat = await stat3(candidate);
16190
16379
  if (candidateStat.isDirectory()) {
16191
- return path12.join(candidate, "state");
16380
+ return path13.join(candidate, "state");
16192
16381
  }
16193
16382
  } catch {
16194
16383
  }
16195
- return path12.join(this.config.memoryDir, "state");
16384
+ return path13.join(this.config.memoryDir, "state");
16196
16385
  }
16197
16386
  buildGraphRecallRankedResults(results, sourceLabelResolver, limit = 64) {
16198
16387
  return results.slice(0, limit).map((result) => ({
@@ -16689,6 +16878,36 @@ ${doc.content}` : doc.content,
16689
16878
  }
16690
16879
  return this._orchestratorInitCoordinator;
16691
16880
  }
16881
+ /**
16882
+ * Persistence-index coordinator (issue #1526 seam 23). Owns post-persist
16883
+ * bookkeeping (content-hash dedup, temporal indexes, graph edges,
16884
+ * semantic dedup lookup). Lazy + accessor-wired (late-binding rule).
16885
+ */
16886
+ _persistenceIndexCoordinator;
16887
+ get persistenceIndexCoordinator() {
16888
+ if (!this._persistenceIndexCoordinator) {
16889
+ const self = this;
16890
+ this._persistenceIndexCoordinator = new PersistenceIndexCoordinator({
16891
+ get config() {
16892
+ return self.config;
16893
+ },
16894
+ get contentHashIndex() {
16895
+ return self.contentHashIndex;
16896
+ },
16897
+ contentHashIndexForStorage: (targetStorage) => self.contentHashIndexForStorage(targetStorage),
16898
+ get contentHashIndexesByStorageDir() {
16899
+ return self.contentHashIndexesByStorageDir;
16900
+ },
16901
+ get embeddingFallback() {
16902
+ return self.embeddingFallback;
16903
+ },
16904
+ graphIndexFor: (storage) => self.graphIndexFor(storage),
16905
+ readAllMemoriesForNamespaces: (namespaces) => self.readAllMemoriesForNamespaces(namespaces),
16906
+ semanticDedupScopeFor: (targetStorage) => self.semanticDedupScopeFor(targetStorage)
16907
+ });
16908
+ }
16909
+ return this._persistenceIndexCoordinator;
16910
+ }
16692
16911
  async recallInternal(prompt, sessionKey, options = {}, caps = resolveCapabilities(this.config), graphCaps = resolveGraphConstructionCapabilities(this.config), lifecycleCaps = resolveMemoryLifecycleCapabilities(this.config)) {
16693
16912
  return this.recallInternalCoordinator.recallInternal(
16694
16913
  prompt,
@@ -16949,71 +17168,25 @@ ${doc.content}` : doc.content,
16949
17168
  }
16950
17169
  }
16951
17170
  async indexPersistedMemory(storage, memoryId) {
16952
- if (!resolveMemoryLifecycleCapabilities(this.config).embeddingFallback) return;
16953
- if (!await this.embeddingFallback.isAvailable()) return;
16954
- const memory = await storage.getMemoryById(memoryId);
16955
- if (!memory) return;
16956
- await this.embeddingFallback.indexFile(
16957
- memoryId,
16958
- memory.content,
16959
- memory.path
17171
+ return this.persistenceIndexCoordinator.indexPersistedMemory(
17172
+ storage,
17173
+ memoryId
16960
17174
  );
16961
17175
  }
16962
- /**
16963
- * Build a graph edge for a persisted memory (v8.2).
16964
- * Shared helper used by both the chunked and non-chunked write paths to avoid duplication.
16965
- * Fail-open: caller wraps in try/catch.
16966
- */
16967
17176
  async buildGraphEdge(storage, memoryRelPath, entityRef, memoryId, factContent, allMemsForGraph, memoryPathById, threadIdForEdge, threadEpisodeIdsForGraph, fallbackCausalPredecessor, graphCaps = resolveGraphConstructionCapabilities(this.config)) {
16968
- const entitySiblings = [];
16969
- if (entityRef) {
16970
- try {
16971
- const allMems = allMemsForGraph ?? [];
16972
- for (const m of allMems) {
16973
- if (m.frontmatter.entityRef === entityRef) {
16974
- const rel = path12.relative(storage.dir, m.path);
16975
- if (rel !== memoryRelPath) entitySiblings.push(rel);
16976
- }
16977
- }
16978
- } catch {
16979
- }
16980
- }
16981
- const recentInThread = [];
16982
- if (threadIdForEdge && threadEpisodeIdsForGraph?.length) {
16983
- try {
16984
- recentInThread.push(
16985
- ...resolveRecentThreadMemoryPaths({
16986
- threadEpisodeIds: threadEpisodeIdsForGraph,
16987
- currentMemoryId: memoryId,
16988
- allMemsForGraph,
16989
- pathById: memoryPathById,
16990
- storageDir: storage.dir,
16991
- maxRecent: 3
16992
- })
16993
- );
16994
- } catch {
16995
- }
16996
- }
16997
- if (recentInThread.length === 0 && graphCaps.graphWriteSessionAdjacency && fallbackCausalPredecessor && fallbackCausalPredecessor !== memoryRelPath) {
16998
- recentInThread.push(fallbackCausalPredecessor);
16999
- }
17000
- const causalPredecessor = recentInThread[recentInThread.length - 1] ?? fallbackCausalPredecessor;
17001
- await this.graphIndexFor(storage).onMemoryWritten({
17002
- memoryPath: memoryRelPath,
17177
+ return this.persistenceIndexCoordinator.buildGraphEdge(
17178
+ storage,
17179
+ memoryRelPath,
17003
17180
  entityRef,
17004
- content: factContent,
17005
- created: (/* @__PURE__ */ new Date()).toISOString(),
17006
- threadId: threadIdForEdge,
17007
- recentInThread,
17008
- entitySiblings,
17009
- causalPredecessor,
17010
- graphCapsOverride: {
17011
- entityGraph: graphCaps.entityGraph,
17012
- timeGraph: graphCaps.timeGraph,
17013
- causalGraph: graphCaps.causalGraph,
17014
- multiGraphMemory: graphCaps.multiGraphMemory
17015
- }
17016
- });
17181
+ memoryId,
17182
+ factContent,
17183
+ allMemsForGraph,
17184
+ memoryPathById,
17185
+ threadIdForEdge,
17186
+ threadEpisodeIdsForGraph,
17187
+ fallbackCausalPredecessor,
17188
+ graphCaps
17189
+ );
17017
17190
  }
17018
17191
  graphIndexFor(storage) {
17019
17192
  const key = storage.dir;
@@ -17023,56 +17196,11 @@ ${doc.content}` : doc.content,
17023
17196
  this.graphIndexes.set(key, created);
17024
17197
  return created;
17025
17198
  }
17026
- /**
17027
- * Batch-update temporal and tag indexes after extraction (v8.1).
17028
- * Reads each persisted memory's path + frontmatter and adds them to
17029
- * state/index_time.json and state/index_tags.json.
17030
- * Fail-open: any error is logged but does not abort extraction.
17031
- */
17032
17199
  async updateTemporalTagIndexes(storage, persistedIds) {
17033
- const caps = resolveCapabilities(this.config);
17034
- if (!resolveIndexingCapabilities(this.config).queryAwareIndexing && !caps.parallelRetrieval)
17035
- return;
17036
- const needsFullRebuild = !indexesExist(this.config.memoryDir);
17037
- if (!needsFullRebuild && persistedIds.length === 0) return;
17038
- try {
17039
- const allMemories = needsFullRebuild && resolveNamespaceCapabilities(this.config).namespaces ? await this.readAllMemoriesForNamespaces(
17040
- Array.from(
17041
- /* @__PURE__ */ new Set([
17042
- this.config.defaultNamespace,
17043
- this.config.sharedNamespace,
17044
- ...this.config.namespacePolicies.map((p) => p.name)
17045
- ])
17046
- )
17047
- ) : await storage.readAllMemories();
17048
- const pool = needsFullRebuild ? allMemories.filter((m) => isActiveMemoryStatus(m.frontmatter.status)) : (() => {
17049
- const idSet = new Set(persistedIds);
17050
- return allMemories.filter((m) => idSet.has(m.frontmatter.id));
17051
- })();
17052
- const entries = [];
17053
- for (const mem of pool) {
17054
- if (mem.path && mem.frontmatter?.created) {
17055
- entries.push({
17056
- path: mem.path,
17057
- createdAt: mem.frontmatter.created,
17058
- tags: mem.frontmatter.tags ?? []
17059
- });
17060
- }
17061
- }
17062
- if (needsFullRebuild) {
17063
- clearIndexes(this.config.memoryDir);
17064
- if (entries.length > 0) {
17065
- indexMemoriesBatch(this.config.memoryDir, entries);
17066
- }
17067
- log.info(
17068
- `temporal-index: bootstrapped from ${entries.length} active memories`
17069
- );
17070
- } else if (entries.length > 0) {
17071
- indexMemoriesBatch(this.config.memoryDir, entries);
17072
- }
17073
- } catch (err) {
17074
- log.debug(`temporal-index update failed (non-fatal): ${err}`);
17075
- }
17200
+ return this.persistenceIndexCoordinator.updateTemporalTagIndexes(
17201
+ storage,
17202
+ persistedIds
17203
+ );
17076
17204
  }
17077
17205
  /** IDs of facts persisted in the last extraction */
17078
17206
  lastPersistedIds = [];
@@ -17286,37 +17414,12 @@ ${reflectionsContent.trim()}
17286
17414
  recallSource
17287
17415
  );
17288
17416
  }
17289
- /**
17290
- * Issue #373 — nearest-neighbor lookup for the write-time semantic dedup
17291
- * guard. Returns the top-K embedding hits against the currently indexed
17292
- * memories, or an empty array when the embedding backend is unavailable.
17293
- * Intentionally does NOT throw; `decideSemanticDedup` treats both "empty"
17294
- * and "error" outcomes as fail-open (keep the candidate).
17295
- *
17296
- * PR #399 P1 fix: when namespaces are enabled the lookup must be scoped
17297
- * to the SAME namespace as the fact being written. Otherwise a
17298
- * high-similarity memory from another namespace can suppress a write in
17299
- * the target namespace — cross-tenant data loss. Callers pass the target
17300
- * storage so we can translate its root directory into the correct index
17301
- * path prefix (and, for the legacy default-namespace layout at
17302
- * `memoryDir` root, an exclusion list for `namespaces/*`).
17303
- */
17304
17417
  async semanticDedupLookup(content, limit, targetStorage) {
17305
- if (!resolveMemoryLifecycleCapabilities(this.config).embeddingFallback) {
17306
- throw new Error("semantic dedup: embedding backend not configured");
17307
- }
17308
- if (!await this.embeddingFallback.isAvailable()) {
17309
- log.debug("semantic dedup: embedding backend unavailable, skipping");
17310
- throw new Error("semantic dedup: embedding backend unavailable");
17311
- }
17312
- const scope = this.semanticDedupScopeFor(targetStorage);
17313
- const hits = await this.embeddingFallback.search(content, limit, { ...scope, throwOnTimeout: true });
17314
- if (!Array.isArray(hits) || hits.length === 0) return [];
17315
- return hits.map((hit) => ({
17316
- id: hit.id,
17317
- score: hit.score,
17318
- path: hit.path
17319
- }));
17418
+ return this.persistenceIndexCoordinator.semanticDedupLookup(
17419
+ content,
17420
+ limit,
17421
+ targetStorage
17422
+ );
17320
17423
  }
17321
17424
  /**
17322
17425
  * Resolve the namespace-scoped filter to pass into
@@ -17334,12 +17437,12 @@ ${reflectionsContent.trim()}
17334
17437
  */
17335
17438
  semanticDedupScopeFor(targetStorage) {
17336
17439
  if (!resolveNamespaceCapabilities(this.config).namespaces) return {};
17337
- const memoryDir = path12.resolve(this.config.memoryDir);
17338
- const storageDir = path12.resolve(targetStorage.dir);
17440
+ const memoryDir = path13.resolve(this.config.memoryDir);
17441
+ const storageDir = path13.resolve(targetStorage.dir);
17339
17442
  if (storageDir === memoryDir) {
17340
17443
  return { pathExcludePrefixes: ["namespaces/"] };
17341
17444
  }
17342
- let rel = path12.relative(memoryDir, storageDir);
17445
+ let rel = path13.relative(memoryDir, storageDir);
17343
17446
  if (!rel || rel.startsWith("..")) {
17344
17447
  log.debug(
17345
17448
  `semantic dedup: target storage dir ${storageDir} is outside memoryDir ${memoryDir}; scoping lookup to absolute path prefix`
@@ -18084,9 +18187,9 @@ function readStructuralSymbol(entry) {
18084
18187
  const rec = entry;
18085
18188
  const symbol = typeof rec.symbol === "string" ? rec.symbol.trim() : "";
18086
18189
  if (!symbol) return null;
18087
- const path27 = typeof rec.path === "string" && rec.path.length > 0 ? rec.path : void 0;
18190
+ const path28 = typeof rec.path === "string" && rec.path.length > 0 ? rec.path : void 0;
18088
18191
  const kind = typeof rec.kind === "string" && rec.kind.length > 0 ? rec.kind : void 0;
18089
- const item = path27 !== void 0 && kind !== void 0 ? { symbol, path: path27, kind } : path27 !== void 0 ? { symbol, path: path27 } : kind !== void 0 ? { symbol, kind } : { symbol };
18192
+ const item = path28 !== void 0 && kind !== void 0 ? { symbol, path: path28, kind } : path28 !== void 0 ? { symbol, path: path28 } : kind !== void 0 ? { symbol, kind } : { symbol };
18090
18193
  return item;
18091
18194
  }
18092
18195
  function classifySpawnError(err) {
@@ -18160,7 +18263,7 @@ var DEFAULT_GRACE_PERIOD_DAYS = 7;
18160
18263
 
18161
18264
  // src/binary-lifecycle/backend.ts
18162
18265
  import fsp from "fs/promises";
18163
- import path13 from "path";
18266
+ import path14 from "path";
18164
18267
  var FilesystemBackend = class {
18165
18268
  type = "filesystem";
18166
18269
  basePath;
@@ -18168,19 +18271,19 @@ var FilesystemBackend = class {
18168
18271
  if (!basePath || basePath.trim().length === 0) {
18169
18272
  throw new Error("FilesystemBackend requires a non-empty basePath");
18170
18273
  }
18171
- this.basePath = path13.resolve(basePath);
18274
+ this.basePath = path14.resolve(basePath);
18172
18275
  }
18173
18276
  resolveRemotePath(remotePath) {
18174
- const resolved = path13.isAbsolute(remotePath) ? path13.resolve(remotePath) : path13.resolve(this.basePath, remotePath);
18175
- const relative = path13.relative(this.basePath, resolved);
18176
- if (relative === ".." || relative.startsWith(`..${path13.sep}`) || path13.isAbsolute(relative)) {
18277
+ const resolved = path14.isAbsolute(remotePath) ? path14.resolve(remotePath) : path14.resolve(this.basePath, remotePath);
18278
+ const relative = path14.relative(this.basePath, resolved);
18279
+ if (relative === ".." || relative.startsWith(`..${path14.sep}`) || path14.isAbsolute(relative)) {
18177
18280
  throw new Error(`FilesystemBackend remotePath escapes basePath: ${JSON.stringify(remotePath)}`);
18178
18281
  }
18179
18282
  return resolved;
18180
18283
  }
18181
18284
  isInsideBase(candidate, realBase) {
18182
- const relative = path13.relative(realBase, candidate);
18183
- return relative === "" || relative !== ".." && !relative.startsWith(`..${path13.sep}`) && !path13.isAbsolute(relative);
18285
+ const relative = path14.relative(realBase, candidate);
18286
+ return relative === "" || relative !== ".." && !relative.startsWith(`..${path14.sep}`) && !path14.isAbsolute(relative);
18184
18287
  }
18185
18288
  async realBasePathIfExists() {
18186
18289
  try {
@@ -18209,13 +18312,13 @@ var FilesystemBackend = class {
18209
18312
  }
18210
18313
  async ensureSafeParentDirectory(dest) {
18211
18314
  const realBase = await this.ensureBaseDirectory();
18212
- const destDir = path13.dirname(dest);
18213
- const relativeDir = path13.relative(this.basePath, destDir);
18214
- const segments = relativeDir === "" ? [] : relativeDir.split(path13.sep);
18315
+ const destDir = path14.dirname(dest);
18316
+ const relativeDir = path14.relative(this.basePath, destDir);
18317
+ const segments = relativeDir === "" ? [] : relativeDir.split(path14.sep);
18215
18318
  let current = this.basePath;
18216
18319
  for (const segment of segments) {
18217
18320
  if (segment === "." || segment === "") continue;
18218
- current = path13.join(current, segment);
18321
+ current = path14.join(current, segment);
18219
18322
  try {
18220
18323
  const stat5 = await fsp.lstat(current);
18221
18324
  if (stat5.isSymbolicLink()) {
@@ -18243,13 +18346,13 @@ var FilesystemBackend = class {
18243
18346
  if (realBase === null) {
18244
18347
  return null;
18245
18348
  }
18246
- const destDir = path13.dirname(dest);
18247
- const relativeDir = path13.relative(this.basePath, destDir);
18248
- const segments = relativeDir === "" ? [] : relativeDir.split(path13.sep);
18349
+ const destDir = path14.dirname(dest);
18350
+ const relativeDir = path14.relative(this.basePath, destDir);
18351
+ const segments = relativeDir === "" ? [] : relativeDir.split(path14.sep);
18249
18352
  let current = this.basePath;
18250
18353
  for (const segment of segments) {
18251
18354
  if (segment === "." || segment === "") continue;
18252
- current = path13.join(current, segment);
18355
+ current = path14.join(current, segment);
18253
18356
  let stat5;
18254
18357
  try {
18255
18358
  stat5 = await fsp.lstat(current);
@@ -18297,7 +18400,7 @@ var FilesystemBackend = class {
18297
18400
  return dest;
18298
18401
  }
18299
18402
  async upload(localPath, remotePath) {
18300
- if (path13.isAbsolute(remotePath)) {
18403
+ if (path14.isAbsolute(remotePath)) {
18301
18404
  throw new Error(`FilesystemBackend upload remotePath must be relative: ${JSON.stringify(remotePath)}`);
18302
18405
  }
18303
18406
  const dest = this.resolveRemotePath(remotePath);
@@ -18370,7 +18473,7 @@ function createBackend(cfg) {
18370
18473
 
18371
18474
  // src/binary-lifecycle/scanner.ts
18372
18475
  import fsp2 from "fs/promises";
18373
- import path14 from "path";
18476
+ import path15 from "path";
18374
18477
  import crypto from "crypto";
18375
18478
  function matchesPatterns(filename, patterns) {
18376
18479
  const lower = filename.toLowerCase();
@@ -18395,8 +18498,8 @@ async function scanForBinaries(memoryDir, config, manifest) {
18395
18498
  return;
18396
18499
  }
18397
18500
  for (const entry of entries) {
18398
- const fullPath = path14.join(dir, entry.name);
18399
- const relativePath = path14.relative(memoryDir, fullPath).split(path14.sep).join("/");
18501
+ const fullPath = path15.join(dir, entry.name);
18502
+ const relativePath = path15.relative(memoryDir, fullPath).split(path15.sep).join("/");
18400
18503
  if (entry.isDirectory()) {
18401
18504
  if (entry.name === ".binary-lifecycle") continue;
18402
18505
  await walk(fullPath);
@@ -18436,15 +18539,15 @@ async function hashFile(filePath) {
18436
18539
 
18437
18540
  // src/binary-lifecycle/manifest.ts
18438
18541
  import fsp3 from "fs/promises";
18439
- import path15 from "path";
18542
+ import path16 from "path";
18440
18543
  import crypto2 from "crypto";
18441
18544
  var MANIFEST_DIR = ".binary-lifecycle";
18442
18545
  var MANIFEST_FILE = "manifest.json";
18443
18546
  function manifestDir(memoryDir) {
18444
- return path15.join(memoryDir, MANIFEST_DIR);
18547
+ return path16.join(memoryDir, MANIFEST_DIR);
18445
18548
  }
18446
18549
  function manifestPath(memoryDir) {
18447
- return path15.join(memoryDir, MANIFEST_DIR, MANIFEST_FILE);
18550
+ return path16.join(memoryDir, MANIFEST_DIR, MANIFEST_FILE);
18448
18551
  }
18449
18552
  async function readManifest(memoryDir) {
18450
18553
  const filePath = manifestPath(memoryDir);
@@ -18496,7 +18599,7 @@ function emptyManifest() {
18496
18599
 
18497
18600
  // src/binary-lifecycle/pipeline.ts
18498
18601
  import fsp4 from "fs/promises";
18499
- import path16 from "path";
18602
+ import path17 from "path";
18500
18603
  import crypto3 from "crypto";
18501
18604
  async function hashFile2(filePath) {
18502
18605
  const content = await fsp4.readFile(filePath);
@@ -18519,13 +18622,13 @@ function escapeRegex(s) {
18519
18622
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
18520
18623
  }
18521
18624
  function resolveManifestAssetPath(memoryDir, originalPath) {
18522
- if (originalPath.length === 0 || originalPath.includes("\0") || originalPath.includes("\\") || path16.isAbsolute(originalPath) || path16.win32.isAbsolute(originalPath)) {
18625
+ if (originalPath.length === 0 || originalPath.includes("\0") || originalPath.includes("\\") || path17.isAbsolute(originalPath) || path17.win32.isAbsolute(originalPath)) {
18523
18626
  return null;
18524
18627
  }
18525
- const memoryRoot = path16.resolve(memoryDir);
18526
- const fullPath = path16.resolve(memoryRoot, originalPath);
18527
- const relative = path16.relative(memoryRoot, fullPath);
18528
- if (relative === "" || relative === ".." || relative.startsWith(`..${path16.sep}`) || path16.isAbsolute(relative)) {
18628
+ const memoryRoot = path17.resolve(memoryDir);
18629
+ const fullPath = path17.resolve(memoryRoot, originalPath);
18630
+ const relative = path17.relative(memoryRoot, fullPath);
18631
+ if (relative === "" || relative === ".." || relative.startsWith(`..${path17.sep}`) || path17.isAbsolute(relative)) {
18529
18632
  return null;
18530
18633
  }
18531
18634
  return fullPath;
@@ -18536,7 +18639,7 @@ function validateBinaryLifecycleConfig(config) {
18536
18639
  }
18537
18640
  }
18538
18641
  function remotePathForAsset(backend, relPath) {
18539
- const normalized = relPath.split(path16.sep).join("/");
18642
+ const normalized = relPath.split(path17.sep).join("/");
18540
18643
  if (backend.type === "filesystem") {
18541
18644
  return `.binary-lifecycle/mirrors/${normalized}`;
18542
18645
  }
@@ -18549,11 +18652,11 @@ async function stageMirror(memoryDir, newPaths, backend, assets, log2, dryRun) {
18549
18652
  let mirrored = 0;
18550
18653
  const errors = [];
18551
18654
  for (const relPath of newPaths) {
18552
- const fullPath = path16.join(memoryDir, relPath);
18655
+ const fullPath = path17.join(memoryDir, relPath);
18553
18656
  try {
18554
18657
  const stat5 = await fsp4.stat(fullPath);
18555
18658
  const contentHash = await hashFile2(fullPath);
18556
- const ext = path16.extname(relPath);
18659
+ const ext = path17.extname(relPath);
18557
18660
  const mimeType = guessMimeType(ext);
18558
18661
  const remotePath = remotePathForAsset(backend, relPath);
18559
18662
  let backendLocation = remotePath;
@@ -18753,10 +18856,10 @@ async function countRemainingLocalReferences(memoryDir, asset, assetAbsolute, md
18753
18856
  return { remaining, errors };
18754
18857
  }
18755
18858
  function markdownReferencePattern(asset, assetAbsolute, mdPath) {
18756
- const mdDir = path16.dirname(mdPath);
18859
+ const mdDir = path17.dirname(mdPath);
18757
18860
  const candidates = /* @__PURE__ */ new Set();
18758
18861
  const addCandidate = (candidate) => {
18759
- const normalized = candidate.split(path16.sep).join("/");
18862
+ const normalized = candidate.split(path17.sep).join("/");
18760
18863
  if (normalized.length === 0) return;
18761
18864
  candidates.add(normalized);
18762
18865
  const isParentTraversal = normalized === ".." || normalized.startsWith("../");
@@ -18764,10 +18867,10 @@ function markdownReferencePattern(asset, assetAbsolute, mdPath) {
18764
18867
  candidates.add(`./${normalized}`);
18765
18868
  }
18766
18869
  };
18767
- addCandidate(path16.relative(mdDir, assetAbsolute));
18768
- const originalPath = asset.originalPath.split(path16.sep).join("/");
18769
- const originalAsFileRelative = path16.resolve(mdDir, ...originalPath.split("/"));
18770
- if (path16.resolve(originalAsFileRelative) === path16.resolve(assetAbsolute)) {
18870
+ addCandidate(path17.relative(mdDir, assetAbsolute));
18871
+ const originalPath = asset.originalPath.split(path17.sep).join("/");
18872
+ const originalAsFileRelative = path17.resolve(mdDir, ...originalPath.split("/"));
18873
+ if (path17.resolve(originalAsFileRelative) === path17.resolve(assetAbsolute)) {
18771
18874
  addCandidate(originalPath);
18772
18875
  }
18773
18876
  addCandidate(`/${originalPath}`);
@@ -18863,7 +18966,7 @@ async function findMarkdownFiles(dir) {
18863
18966
  return;
18864
18967
  }
18865
18968
  for (const entry of entries) {
18866
- const full = path16.join(current, entry.name);
18969
+ const full = path17.join(current, entry.name);
18867
18970
  if (entry.isDirectory()) {
18868
18971
  if (entry.name === ".binary-lifecycle") continue;
18869
18972
  await walk(full);
@@ -18938,7 +19041,7 @@ async function runBinaryLifecyclePipeline(memoryDir, config, backend, log2, opts
18938
19041
 
18939
19042
  // src/projection/index.ts
18940
19043
  import fs from "fs";
18941
- import path17 from "path";
19044
+ import path18 from "path";
18942
19045
  var VALID_PROJECTION_CATEGORIES = new Set(ALL_CATEGORY_KEYS);
18943
19046
  async function generateContextTree(options) {
18944
19047
  const startTime = Date.now();
@@ -18953,8 +19056,8 @@ async function generateContextTree(options) {
18953
19056
  let nodesGenerated = 0;
18954
19057
  let nodesSkipped = 0;
18955
19058
  const categoryCounts = {};
18956
- const resolvedMemoryDir = path17.resolve(memoryDir);
18957
- const resolvedOutputDir = path17.resolve(outputDir);
19059
+ const resolvedMemoryDir = path18.resolve(memoryDir);
19060
+ const resolvedOutputDir = path18.resolve(outputDir);
18958
19061
  const requestedCategories = validateProjectionCategories(filterCategories);
18959
19062
  const realMemoryDir = assertSafeMemoryRoot(resolvedMemoryDir);
18960
19063
  assertNotSymlink(resolvedOutputDir, "context tree outputDir");
@@ -18992,7 +19095,7 @@ async function generateContextTree(options) {
18992
19095
  }
18993
19096
  }
18994
19097
  if (includeEntities) {
18995
- const entitiesDir = path17.join(memoryDir, "entities");
19098
+ const entitiesDir = path18.join(memoryDir, "entities");
18996
19099
  if (fs.existsSync(entitiesDir)) {
18997
19100
  assertSafeInputRoot(realMemoryDir, entitiesDir, "entities root");
18998
19101
  categoryCounts["entity"] = 0;
@@ -19004,7 +19107,7 @@ async function generateContextTree(options) {
19004
19107
  continue;
19005
19108
  }
19006
19109
  const content = fs.readFileSync(filePath, "utf8");
19007
- const fileName = path17.basename(filePath, ".md");
19110
+ const fileName = path18.basename(filePath, ".md");
19008
19111
  const node = projectEntityNode(fileName, content);
19009
19112
  const outputPath = resolveContainedOutputPath(realOutputDir, "entities", `${fileName}.md`);
19010
19113
  writeProjectedContent(realOutputDir, outputPath, node.content);
@@ -19016,7 +19119,7 @@ async function generateContextTree(options) {
19016
19119
  }
19017
19120
  const shouldIncludeQuestions = includeQuestions && (requestedCategories === void 0 || requestedCategories.includes("question"));
19018
19121
  if (shouldIncludeQuestions) {
19019
- const questionsDir = path17.join(memoryDir, "questions");
19122
+ const questionsDir = path18.join(memoryDir, "questions");
19020
19123
  if (fs.existsSync(questionsDir)) {
19021
19124
  assertSafeInputRoot(realMemoryDir, questionsDir, "questions root");
19022
19125
  categoryCounts["question"] = 0;
@@ -19057,8 +19160,8 @@ async function generateContextTree(options) {
19057
19160
  };
19058
19161
  }
19059
19162
  function isPathInside(root, candidate) {
19060
- const relative = path17.relative(root, candidate);
19061
- return relative === "" || !relative.startsWith("..") && !path17.isAbsolute(relative);
19163
+ const relative = path18.relative(root, candidate);
19164
+ return relative === "" || !relative.startsWith("..") && !path18.isAbsolute(relative);
19062
19165
  }
19063
19166
  function assertNotSymlink(targetPath, label) {
19064
19167
  try {
@@ -19095,12 +19198,12 @@ function assertSafeInputRoot(realMemoryDir, targetPath, label) {
19095
19198
  }
19096
19199
  function assertSafeOutputTarget(realOutputDir, outputPath) {
19097
19200
  let current = realOutputDir;
19098
- const relative = path17.relative(realOutputDir, outputPath);
19099
- if (relative.startsWith("..") || path17.isAbsolute(relative)) {
19201
+ const relative = path18.relative(realOutputDir, outputPath);
19202
+ if (relative.startsWith("..") || path18.isAbsolute(relative)) {
19100
19203
  throw new Error(`context tree output path escapes outputDir: ${outputPath}`);
19101
19204
  }
19102
- for (const segment of relative.split(path17.sep).filter(Boolean)) {
19103
- current = path17.join(current, segment);
19205
+ for (const segment of relative.split(path18.sep).filter(Boolean)) {
19206
+ current = path18.join(current, segment);
19104
19207
  try {
19105
19208
  const stat5 = fs.lstatSync(current);
19106
19209
  if (stat5.isSymbolicLink()) {
@@ -19130,17 +19233,17 @@ function validateProjectionCategories(categories) {
19130
19233
  return validated;
19131
19234
  }
19132
19235
  function resolveContainedOutputPath(outputRoot, ...segments) {
19133
- const resolved = path17.resolve(outputRoot, ...segments);
19134
- const relative = path17.relative(outputRoot, resolved);
19135
- if (relative === "" || !relative.startsWith("..") && !path17.isAbsolute(relative)) {
19236
+ const resolved = path18.resolve(outputRoot, ...segments);
19237
+ const relative = path18.relative(outputRoot, resolved);
19238
+ if (relative === "" || !relative.startsWith("..") && !path18.isAbsolute(relative)) {
19136
19239
  return resolved;
19137
19240
  }
19138
19241
  throw new Error(`context tree output path escapes outputDir: ${segments.join("/")}`);
19139
19242
  }
19140
19243
  function writeProjectedContent(realOutputDir, outputPath, generatedContent) {
19141
19244
  assertSafeOutputTarget(realOutputDir, outputPath);
19142
- fs.mkdirSync(path17.dirname(outputPath), { recursive: true });
19143
- const realParent = fs.realpathSync(path17.dirname(outputPath));
19245
+ fs.mkdirSync(path18.dirname(outputPath), { recursive: true });
19246
+ const realParent = fs.realpathSync(path18.dirname(outputPath));
19144
19247
  if (!isPathInside(realOutputDir, realParent)) {
19145
19248
  throw new Error(`context tree output path escapes outputDir: ${outputPath}`);
19146
19249
  }
@@ -19179,7 +19282,7 @@ function walkR(dir, realMemoryDir) {
19179
19282
  const results = [];
19180
19283
  function walk(directory) {
19181
19284
  for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
19182
- const fullPath = path17.join(directory, entry.name);
19285
+ const fullPath = path18.join(directory, entry.name);
19183
19286
  if (entry.isSymbolicLink()) {
19184
19287
  throw new Error(`context tree input path contains symlink: ${fullPath}`);
19185
19288
  }
@@ -19232,13 +19335,13 @@ function extractBody(content) {
19232
19335
  }
19233
19336
  function projectNode(filePath, category, fm, rawContent) {
19234
19337
  const body = extractBody(rawContent);
19235
- const fileName = path17.basename(filePath, ".md");
19236
- const dateDir = path17.basename(path17.dirname(filePath));
19338
+ const fileName = path18.basename(filePath, ".md");
19339
+ const dateDir = path18.basename(path18.dirname(filePath));
19237
19340
  let relPath;
19238
19341
  if (/^\d{4}-\d{2}-\d{2}$/.test(dateDir)) {
19239
- relPath = path17.join(category, dateDir, `${fileName}.md`);
19342
+ relPath = path18.join(category, dateDir, `${fileName}.md`);
19240
19343
  } else {
19241
- relPath = path17.join(category, `${fileName}.md`);
19344
+ relPath = path18.join(category, `${fileName}.md`);
19242
19345
  }
19243
19346
  const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
19244
19347
  const md = `# ${fm.id}
@@ -19284,7 +19387,7 @@ function projectEntityNode(fileName, content) {
19284
19387
  ${content}
19285
19388
  `;
19286
19389
  return {
19287
- path: path17.join("entities", `${fileName}.md`),
19390
+ path: path18.join("entities", `${fileName}.md`),
19288
19391
  category: "entity",
19289
19392
  title: fileName,
19290
19393
  content: md,
@@ -19337,7 +19440,7 @@ function generateIndex(categoryCounts, outputDir) {
19337
19440
 
19338
19441
  // src/onboarding/index.ts
19339
19442
  import fs2 from "fs";
19340
- import path18 from "path";
19443
+ import path19 from "path";
19341
19444
  var LANGUAGE_RULES = [
19342
19445
  {
19343
19446
  language: "TypeScript",
@@ -19439,7 +19542,7 @@ function onboard(options) {
19439
19542
  maxDepth = 6,
19440
19543
  excludeDirs = []
19441
19544
  } = options;
19442
- const directory = path18.resolve(options.directory ?? process.cwd());
19545
+ const directory = path19.resolve(options.directory ?? process.cwd());
19443
19546
  let rootStat;
19444
19547
  try {
19445
19548
  rootStat = fs2.statSync(directory);
@@ -19481,7 +19584,7 @@ function walkDir(root, exclude, maxDepth) {
19481
19584
  }
19482
19585
  for (const entry of entries) {
19483
19586
  if (exclude.has(entry.name)) continue;
19484
- const fullPath = path18.join(dir, entry.name);
19587
+ const fullPath = path19.join(dir, entry.name);
19485
19588
  if (entry.isDirectory()) {
19486
19589
  walk(fullPath, depth + 1);
19487
19590
  } else if (entry.isFile()) {
@@ -19496,11 +19599,11 @@ function detectLanguages(files, root) {
19496
19599
  const results = [];
19497
19600
  const extCounts = /* @__PURE__ */ new Map();
19498
19601
  for (const f of files) {
19499
- const ext = path18.extname(f).toLowerCase();
19602
+ const ext = path19.extname(f).toLowerCase();
19500
19603
  if (ext) extCounts.set(ext, (extCounts.get(ext) ?? 0) + 1);
19501
19604
  }
19502
19605
  const rootFiles = new Set(
19503
- files.filter((f) => path18.dirname(f) === root).map((f) => path18.basename(f))
19606
+ files.filter((f) => path19.dirname(f) === root).map((f) => path19.basename(f))
19504
19607
  );
19505
19608
  for (const rule of LANGUAGE_RULES) {
19506
19609
  const evidence = [];
@@ -19544,7 +19647,7 @@ function detectLanguages(files, root) {
19544
19647
  }
19545
19648
  function detectShape(files, root) {
19546
19649
  const rootFiles = new Set(
19547
- files.filter((f) => path18.dirname(f) === root).map((f) => path18.basename(f))
19650
+ files.filter((f) => path19.dirname(f) === root).map((f) => path19.basename(f))
19548
19651
  );
19549
19652
  const rootDirs = /* @__PURE__ */ new Set();
19550
19653
  try {
@@ -19555,7 +19658,7 @@ function detectShape(files, root) {
19555
19658
  }
19556
19659
  const evidence = [];
19557
19660
  if (rootFiles.has("package.json")) {
19558
- const pkg = readJsonSafe(path18.join(root, "package.json"));
19661
+ const pkg = readJsonSafe(path19.join(root, "package.json"));
19559
19662
  if (pkg?.workspaces) {
19560
19663
  evidence.push("package.json has workspaces");
19561
19664
  return { shape: "monorepo", evidence };
@@ -19569,13 +19672,13 @@ function detectShape(files, root) {
19569
19672
  evidence.push("workspace manifest found");
19570
19673
  return { shape: "workspace", evidence };
19571
19674
  }
19572
- const cargoToml = readTomlWorkspace(path18.join(root, "Cargo.toml"));
19675
+ const cargoToml = readTomlWorkspace(path19.join(root, "Cargo.toml"));
19573
19676
  if (cargoToml) {
19574
19677
  evidence.push("Cargo.toml has workspace");
19575
19678
  return { shape: "workspace", evidence };
19576
19679
  }
19577
19680
  if (rootFiles.has("package.json")) {
19578
- const pkg = readJsonSafe(path18.join(root, "package.json"));
19681
+ const pkg = readJsonSafe(path19.join(root, "package.json"));
19579
19682
  if (pkg?.exports || pkg?.main) {
19580
19683
  if (pkg?.bin) {
19581
19684
  evidence.push("package.json has bin");
@@ -19609,8 +19712,8 @@ function discoverDocs(files, root) {
19609
19712
  { pattern: /^\.editorconfig$/i, kind: "config" }
19610
19713
  ];
19611
19714
  for (const filePath of files) {
19612
- const basename = path18.basename(filePath).toLowerCase();
19613
- const relPath = path18.relative(root, filePath);
19715
+ const basename = path19.basename(filePath).toLowerCase();
19716
+ const relPath = path19.relative(root, filePath);
19614
19717
  let kind;
19615
19718
  for (const { pattern, kind: k } of docPatterns) {
19616
19719
  if (pattern.test(basename)) {
@@ -19622,7 +19725,7 @@ function discoverDocs(files, root) {
19622
19725
  kind = "docs";
19623
19726
  }
19624
19727
  if (!kind && (basename.endsWith(".md") || basename.endsWith(".mdx"))) {
19625
- if (path18.dirname(relPath) === "." || isUnderDocsDir(relPath)) {
19728
+ if (path19.dirname(relPath) === "." || isUnderDocsDir(relPath)) {
19626
19729
  kind = "docs";
19627
19730
  }
19628
19731
  }
@@ -19643,7 +19746,7 @@ function discoverDocs(files, root) {
19643
19746
  return docs;
19644
19747
  }
19645
19748
  function isUnderDocsDir(relPath) {
19646
- const parts = relPath.split(path18.sep);
19749
+ const parts = relPath.split(path19.sep);
19647
19750
  return parts[0] === "docs" || parts[0] === "doc" || parts[0] === "documentation";
19648
19751
  }
19649
19752
  function buildPlan(languages, shape, docs, _root) {
@@ -19687,7 +19790,7 @@ function readTomlWorkspace(filePath) {
19687
19790
 
19688
19791
  // src/curation/index.ts
19689
19792
  import fs3 from "fs";
19690
- import path19 from "path";
19793
+ import path20 from "path";
19691
19794
  import crypto4 from "crypto";
19692
19795
  async function curate(options) {
19693
19796
  const startTime = Date.now();
@@ -19778,12 +19881,12 @@ function resolveTargets(targetPath) {
19778
19881
  const extensions = /* @__PURE__ */ new Set([".md", ".txt", ".mdx", ".rst"]);
19779
19882
  function walk(dir) {
19780
19883
  for (const entry of fs3.readdirSync(dir, { withFileTypes: true })) {
19781
- const fullPath = path19.join(dir, entry.name);
19884
+ const fullPath = path20.join(dir, entry.name);
19782
19885
  if (entry.isDirectory()) {
19783
19886
  if (entry.name !== "node_modules" && entry.name !== ".git") {
19784
19887
  walk(fullPath);
19785
19888
  }
19786
- } else if (extensions.has(path19.extname(entry.name).toLowerCase())) {
19889
+ } else if (extensions.has(path20.extname(entry.name).toLowerCase())) {
19787
19890
  results.push(fullPath);
19788
19891
  }
19789
19892
  }
@@ -19792,12 +19895,12 @@ function resolveTargets(targetPath) {
19792
19895
  return results;
19793
19896
  }
19794
19897
  function resolveProvenanceRoot(targetPath) {
19795
- const resolvedTarget = path19.resolve(targetPath);
19898
+ const resolvedTarget = path20.resolve(targetPath);
19796
19899
  const stat5 = fs3.statSync(resolvedTarget);
19797
- return stat5.isFile() ? path19.dirname(resolvedTarget) : resolvedTarget;
19900
+ return stat5.isFile() ? path20.dirname(resolvedTarget) : resolvedTarget;
19798
19901
  }
19799
19902
  function extractStatements(content, filePath, projectRoot, source, sourceFileHash, categoryOverride, confidence, entityRef, tags) {
19800
- const relativePath = path19.relative(projectRoot, path19.resolve(filePath)) || path19.basename(filePath);
19903
+ const relativePath = path20.relative(projectRoot, path20.resolve(filePath)) || path20.basename(filePath);
19801
19904
  const statements = [];
19802
19905
  const now = (/* @__PURE__ */ new Date()).toISOString();
19803
19906
  const paragraphs = content.split(/\n{2,}/).map((p) => p.trim()).filter((p) => p.length > 20 && p.length < 2e3);
@@ -19891,7 +19994,7 @@ function loadExistingMemories(memoryDir) {
19891
19994
  if (!fs3.existsSync(memoryDir)) return result;
19892
19995
  const dirs = ALL_CATEGORY_DIRS;
19893
19996
  for (const dir of dirs) {
19894
- const fullDir = path19.join(memoryDir, dir);
19997
+ const fullDir = path20.join(memoryDir, dir);
19895
19998
  if (!fs3.existsSync(fullDir)) continue;
19896
19999
  walkFiles(fullDir, (filePath) => {
19897
20000
  const content = readFileSafe(filePath);
@@ -19913,10 +20016,10 @@ function writeStatement(stmt, memoryDir) {
19913
20016
  const now = /* @__PURE__ */ new Date();
19914
20017
  const dateDir = now.toISOString().split("T")[0];
19915
20018
  const categoryDir = getCategoryDir(memoryDir, stmt.category);
19916
- const dir = path19.join(categoryDir, dateDir);
20019
+ const dir = path20.join(categoryDir, dateDir);
19917
20020
  fs3.mkdirSync(dir, { recursive: true });
19918
20021
  const fileName = `${stmt.category}-${Date.now()}-${stmt.id.slice(0, 8)}.md`;
19919
- const filePath = path19.join(dir, fileName);
20022
+ const filePath = path20.join(dir, fileName);
19920
20023
  const frontmatter = [
19921
20024
  "---",
19922
20025
  `id: ${stmt.id}`,
@@ -19983,7 +20086,7 @@ function extractBody2(content) {
19983
20086
  }
19984
20087
  function walkFiles(dir, callback) {
19985
20088
  for (const entry of fs3.readdirSync(dir, { withFileTypes: true })) {
19986
- const fullPath = path19.join(dir, entry.name);
20089
+ const fullPath = path20.join(dir, entry.name);
19987
20090
  if (entry.isDirectory()) {
19988
20091
  walkFiles(fullPath, callback);
19989
20092
  } else if (entry.name.endsWith(".md")) {
@@ -19994,7 +20097,7 @@ function walkFiles(dir, callback) {
19994
20097
 
19995
20098
  // src/dedup/index.ts
19996
20099
  import fs4 from "fs";
19997
- import path20 from "path";
20100
+ import path21 from "path";
19998
20101
  import crypto5 from "crypto";
19999
20102
  var DEFAULT_DEDUP_THRESHOLD = 0.85;
20000
20103
  var DEFAULT_MAX_LOAD = 1e4;
@@ -20136,7 +20239,7 @@ function loadMemories(memoryDir, categories, maxLoad = 1e4) {
20136
20239
  const memoryRootReal = fs4.realpathSync(memoryDir);
20137
20240
  for (const category of allCategories) {
20138
20241
  if (result.length >= maxLoad) break;
20139
- const dir = path20.join(memoryDir, category);
20242
+ const dir = path21.join(memoryDir, category);
20140
20243
  if (!fs4.existsSync(dir)) continue;
20141
20244
  const categoryStat = fs4.lstatSync(dir);
20142
20245
  if (categoryStat.isSymbolicLink()) {
@@ -20197,15 +20300,15 @@ function extractBody3(content) {
20197
20300
  return match ? match[1].trim() : content.trim();
20198
20301
  }
20199
20302
  function assertPathInsideRoot2(rootReal, targetReal, sourcePath) {
20200
- const rel = path20.relative(rootReal, targetReal);
20201
- if (rel === "" || !rel.startsWith("..") && !path20.isAbsolute(rel)) {
20303
+ const rel = path21.relative(rootReal, targetReal);
20304
+ if (rel === "" || !rel.startsWith("..") && !path21.isAbsolute(rel)) {
20202
20305
  return;
20203
20306
  }
20204
20307
  throw new Error(`Refusing to scan memory path outside root: ${sourcePath}`);
20205
20308
  }
20206
20309
  function walkMdFiles(dir, memoryRootReal, categoryRootReal, callback) {
20207
20310
  for (const entry of fs4.readdirSync(dir, { withFileTypes: true })) {
20208
- const fullPath = path20.join(dir, entry.name);
20311
+ const fullPath = path21.join(dir, entry.name);
20209
20312
  const entryStat = fs4.lstatSync(fullPath);
20210
20313
  if (entryStat.isSymbolicLink()) {
20211
20314
  throw new Error(`Refusing to scan symlinked memory path: ${fullPath}`);
@@ -20223,7 +20326,7 @@ function walkMdFiles(dir, memoryRootReal, categoryRootReal, callback) {
20223
20326
 
20224
20327
  // src/review/index.ts
20225
20328
  import fs5 from "fs";
20226
- import path21 from "path";
20329
+ import path22 from "path";
20227
20330
  var DEFAULT_CONFIDENCE_THRESHOLD = 0.7;
20228
20331
  function realMemoryRoot(memoryDir) {
20229
20332
  try {
@@ -20235,8 +20338,8 @@ function realMemoryRoot(memoryDir) {
20235
20338
  }
20236
20339
  }
20237
20340
  function isPathInside2(rootReal, candidateReal) {
20238
- const relative = path21.relative(rootReal, candidateReal);
20239
- return relative === "" || !!relative && !relative.startsWith("..") && !path21.isAbsolute(relative);
20341
+ const relative = path22.relative(rootReal, candidateReal);
20342
+ return relative === "" || !!relative && !relative.startsWith("..") && !path22.isAbsolute(relative);
20240
20343
  }
20241
20344
  function isSafeDirectory(rootReal, dir) {
20242
20345
  try {
@@ -20279,7 +20382,7 @@ function listReviewItems(options) {
20279
20382
  if (filterReason && item.reviewReason !== filterReason) return;
20280
20383
  items.push(item);
20281
20384
  };
20282
- const suggestionsDir = path21.join(memoryDir, "suggestions");
20385
+ const suggestionsDir = path22.join(memoryDir, "suggestions");
20283
20386
  if (!isLimitReached() && fs5.existsSync(suggestionsDir) && isSafeDirectory(rootReal, suggestionsDir)) {
20284
20387
  walkMd(rootReal, suggestionsDir, (filePath, content) => {
20285
20388
  if (isLimitReached()) return true;
@@ -20300,7 +20403,7 @@ function listReviewItems(options) {
20300
20403
  return isLimitReached();
20301
20404
  });
20302
20405
  }
20303
- const reviewDir = path21.join(memoryDir, "review");
20406
+ const reviewDir = path22.join(memoryDir, "review");
20304
20407
  if (!isLimitReached() && fs5.existsSync(reviewDir) && isSafeDirectory(rootReal, reviewDir)) {
20305
20408
  walkMd(rootReal, reviewDir, (filePath, content) => {
20306
20409
  if (isLimitReached()) return true;
@@ -20325,7 +20428,7 @@ function listReviewItems(options) {
20325
20428
  const categories = ALL_CATEGORY_DIRS;
20326
20429
  for (const category of categories) {
20327
20430
  if (isLimitReached()) break;
20328
- const dir = path21.join(memoryDir, category);
20431
+ const dir = path22.join(memoryDir, category);
20329
20432
  if (!fs5.existsSync(dir) || !isSafeDirectory(rootReal, dir)) continue;
20330
20433
  walkMd(rootReal, dir, (filePath, content) => {
20331
20434
  if (isLimitReached()) return true;
@@ -20410,8 +20513,8 @@ function approveItem(memoryDir, itemId, options) {
20410
20513
  const targetDir = getCategoryDir(memoryDir, category);
20411
20514
  const dateDir = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
20412
20515
  ensureSafeDirectory(rootReal, targetDir);
20413
- const outputPath = path21.join(targetDir, dateDir, path21.basename(found.filePath));
20414
- ensureSafeDirectory(rootReal, path21.dirname(outputPath));
20516
+ const outputPath = path22.join(targetDir, dateDir, path22.basename(found.filePath));
20517
+ ensureSafeDirectory(rootReal, path22.dirname(outputPath));
20415
20518
  const promotedPath = writeFileWithoutClobber(outputPath, updatedContent, itemId);
20416
20519
  fs5.unlinkSync(found.filePath);
20417
20520
  return {
@@ -20469,13 +20572,13 @@ function findReviewFileById(memoryDir, id, options = {}) {
20469
20572
  const rootReal = realMemoryRoot(memoryDir);
20470
20573
  if (!rootReal) return null;
20471
20574
  for (const loc of ["suggestions", "review"]) {
20472
- const dir = path21.join(memoryDir, loc);
20575
+ const dir = path22.join(memoryDir, loc);
20473
20576
  if (!fs5.existsSync(dir) || !isSafeDirectory(rootReal, dir)) continue;
20474
20577
  const found = findFileById(rootReal, dir, id);
20475
20578
  if (found) return { filePath: found, location: "queue" };
20476
20579
  }
20477
20580
  for (const category of ALL_CATEGORY_DIRS) {
20478
- const dir = path21.join(memoryDir, category);
20581
+ const dir = path22.join(memoryDir, category);
20479
20582
  if (!fs5.existsSync(dir) || !isSafeDirectory(rootReal, dir)) continue;
20480
20583
  const found = findFileById(rootReal, dir, id, (fm) => isLowConfidenceReviewCandidate(fm, options));
20481
20584
  if (found) return { filePath: found, location: "category" };
@@ -20551,10 +20654,10 @@ function readFileSafe3(filePath) {
20551
20654
  }
20552
20655
  }
20553
20656
  function writeFileWithoutClobber(basePath, content, discriminator) {
20554
- const parsed = path21.parse(basePath);
20657
+ const parsed = path22.parse(basePath);
20555
20658
  const safeDiscriminator = sanitizeFilePart(discriminator);
20556
20659
  for (let attempt = 0; attempt < 1e3; attempt++) {
20557
- const candidate = attempt === 0 ? basePath : path21.join(
20660
+ const candidate = attempt === 0 ? basePath : path22.join(
20558
20661
  parsed.dir,
20559
20662
  `${parsed.name}-${safeDiscriminator}${attempt === 1 ? "" : `-${attempt}`}${parsed.ext || ".md"}`
20560
20663
  );
@@ -20610,7 +20713,7 @@ function extractBody4(content) {
20610
20713
  function walkMd(rootReal, dir, callback) {
20611
20714
  if (!isSafeDirectory(rootReal, dir)) return false;
20612
20715
  for (const entry of fs5.readdirSync(dir, { withFileTypes: true })) {
20613
- const fullPath = path21.join(dir, entry.name);
20716
+ const fullPath = path22.join(dir, entry.name);
20614
20717
  if (entry.isSymbolicLink()) continue;
20615
20718
  if (entry.isDirectory()) {
20616
20719
  if (walkMd(rootReal, fullPath, callback)) return true;
@@ -20625,7 +20728,7 @@ function walkMdPaths(rootReal, dir) {
20625
20728
  const results = [];
20626
20729
  if (!isSafeDirectory(rootReal, dir)) return results;
20627
20730
  for (const entry of fs5.readdirSync(dir, { withFileTypes: true })) {
20628
- const fullPath = path21.join(dir, entry.name);
20731
+ const fullPath = path22.join(dir, entry.name);
20629
20732
  if (entry.isSymbolicLink()) continue;
20630
20733
  if (entry.isDirectory()) {
20631
20734
  results.push(...walkMdPaths(rootReal, fullPath));
@@ -20638,7 +20741,7 @@ function walkMdPaths(rootReal, dir) {
20638
20741
 
20639
20742
  // src/sync/index.ts
20640
20743
  import fs6 from "fs";
20641
- import path22 from "path";
20744
+ import path23 from "path";
20642
20745
  import crypto6 from "crypto";
20643
20746
  var DEFAULT_EXTENSIONS = /* @__PURE__ */ new Set([".md", ".txt", ".mdx", ".rst"]);
20644
20747
  var DEFAULT_EXCLUDE2 = /* @__PURE__ */ new Set([
@@ -20660,7 +20763,7 @@ function syncChanges(options) {
20660
20763
  } = options;
20661
20764
  const extSet = new Set(extensions);
20662
20765
  const excludeSet = /* @__PURE__ */ new Set([...DEFAULT_EXCLUDE2, ...excludeDirs]);
20663
- const stateFilePath = options.stateFile ?? path22.join(memoryDir, ".sync-state.json");
20766
+ const stateFilePath = options.stateFile ?? path23.join(memoryDir, ".sync-state.json");
20664
20767
  const prevState = loadState(stateFilePath);
20665
20768
  const currentFiles = scanFiles(sourceDir, extSet, excludeSet);
20666
20769
  const changes = computeDiff(currentFiles, prevState.fileHashes, sourceDir);
@@ -20676,7 +20779,7 @@ function syncChanges(options) {
20676
20779
  for (const [relPath, hash] of Object.entries(currentFiles)) {
20677
20780
  newState.fileHashes[relPath] = hash;
20678
20781
  }
20679
- fs6.mkdirSync(path22.dirname(stateFilePath), { recursive: true });
20782
+ fs6.mkdirSync(path23.dirname(stateFilePath), { recursive: true });
20680
20783
  fs6.writeFileSync(stateFilePath, JSON.stringify(newState, null, 2));
20681
20784
  }
20682
20785
  return {
@@ -20742,13 +20845,13 @@ function scanFiles(root, extensions, exclude) {
20742
20845
  }
20743
20846
  for (const entry of entries) {
20744
20847
  if (exclude.has(entry.name)) continue;
20745
- const fullPath = path22.join(dir, entry.name);
20848
+ const fullPath = path23.join(dir, entry.name);
20746
20849
  if (entry.isDirectory()) {
20747
20850
  walk(fullPath);
20748
20851
  } else if (entry.isFile()) {
20749
- const ext = path22.extname(entry.name).toLowerCase();
20852
+ const ext = path23.extname(entry.name).toLowerCase();
20750
20853
  if (!extensions.has(ext)) continue;
20751
- const relPath = path22.relative(root, fullPath);
20854
+ const relPath = path23.relative(root, fullPath);
20752
20855
  try {
20753
20856
  const content = fs6.readFileSync(fullPath, "utf8");
20754
20857
  result[relPath] = hashContent3(content);
@@ -20763,7 +20866,7 @@ function scanFiles(root, extensions, exclude) {
20763
20866
  function computeDiff(current, previous, sourceDir) {
20764
20867
  const changes = [];
20765
20868
  for (const [relPath, hash] of Object.entries(current)) {
20766
- const fullPath = path22.join(sourceDir, relPath);
20869
+ const fullPath = path23.join(sourceDir, relPath);
20767
20870
  if (!(relPath in previous)) {
20768
20871
  let size = 0;
20769
20872
  try {
@@ -20796,7 +20899,7 @@ function computeDiff(current, previous, sourceDir) {
20796
20899
  for (const relPath of Object.keys(previous)) {
20797
20900
  if (!(relPath in current)) {
20798
20901
  changes.push({
20799
- filePath: path22.join(sourceDir, relPath),
20902
+ filePath: path23.join(sourceDir, relPath),
20800
20903
  relativePath: relPath,
20801
20904
  type: "deleted",
20802
20905
  currentHash: "",
@@ -20826,20 +20929,20 @@ function hashContent3(content) {
20826
20929
  import { spawnSync } from "child_process";
20827
20930
  import crypto7 from "crypto";
20828
20931
  import fs7 from "fs";
20829
- import path23 from "path";
20932
+ import path24 from "path";
20830
20933
  var MANIFEST_VERSION = 1;
20831
20934
  var MANIFEST_LOCK_STALE_MS = 3e4;
20832
20935
  var MANIFEST_LOCK_TIMEOUT_MS = MANIFEST_LOCK_STALE_MS + 1e4;
20833
20936
  var MANIFEST_LOCK_SLEEP_MS = 20;
20834
20937
  function normalizeSpaceMemoryDir(memoryDir) {
20835
- return path23.resolve(memoryDir);
20938
+ return path24.resolve(memoryDir);
20836
20939
  }
20837
20940
  function getSpacesDir(baseDir) {
20838
20941
  const homeDir = baseDir ?? resolveHomeDir();
20839
- return path23.join(homeDir, ".config", "engram", "spaces");
20942
+ return path24.join(homeDir, ".config", "engram", "spaces");
20840
20943
  }
20841
20944
  function getManifestPath(baseDir) {
20842
- return path23.join(getSpacesDir(baseDir), "manifest.json");
20945
+ return path24.join(getSpacesDir(baseDir), "manifest.json");
20843
20946
  }
20844
20947
  function loadManifest(baseDir, memoryDirOverride) {
20845
20948
  if (fs7.existsSync(getManifestPath(baseDir))) {
@@ -20886,9 +20989,9 @@ function readManifestUnlocked(baseDir, memoryDirOverride, options = {}) {
20886
20989
  }
20887
20990
  function saveManifestUnlocked(manifest, baseDir) {
20888
20991
  const manifestPath2 = getManifestPath(baseDir);
20889
- const manifestDir2 = path23.dirname(manifestPath2);
20992
+ const manifestDir2 = path24.dirname(manifestPath2);
20890
20993
  fs7.mkdirSync(manifestDir2, { recursive: true });
20891
- const tempPath = path23.join(manifestDir2, `.manifest.${process.pid}.${Date.now()}.${crypto7.randomUUID()}.tmp`);
20994
+ const tempPath = path24.join(manifestDir2, `.manifest.${process.pid}.${Date.now()}.${crypto7.randomUUID()}.tmp`);
20892
20995
  try {
20893
20996
  fs7.writeFileSync(tempPath, `${JSON.stringify(manifest, null, 2)}
20894
20997
  `, { flag: "wx" });
@@ -20903,7 +21006,7 @@ function saveManifestUnlocked(manifest, baseDir) {
20903
21006
  }
20904
21007
  function withManifestLock(baseDir, operation) {
20905
21008
  const lockDir = `${getManifestPath(baseDir)}.lock`;
20906
- fs7.mkdirSync(path23.dirname(lockDir), { recursive: true });
21009
+ fs7.mkdirSync(path24.dirname(lockDir), { recursive: true });
20907
21010
  const lockOwner = acquireManifestLock(lockDir);
20908
21011
  try {
20909
21012
  return operation();
@@ -20929,7 +21032,7 @@ function acquireManifestLock(lockDir) {
20929
21032
  try {
20930
21033
  fs7.mkdirSync(lockDir, { recursive: false });
20931
21034
  try {
20932
- fs7.writeFileSync(path23.join(lockDir, "owner"), `${owner}
21035
+ fs7.writeFileSync(path24.join(lockDir, "owner"), `${owner}
20933
21036
  `, { flag: "wx" });
20934
21037
  } catch (error) {
20935
21038
  fs7.rmSync(lockDir, { recursive: true, force: true });
@@ -20959,7 +21062,7 @@ function acquireManifestLock(lockDir) {
20959
21062
  }
20960
21063
  function releaseManifestLock(lockDir, owner) {
20961
21064
  try {
20962
- const ownerPath = path23.join(lockDir, "owner");
21065
+ const ownerPath = path24.join(lockDir, "owner");
20963
21066
  if (fs7.readFileSync(ownerPath, "utf8").trim() === owner) {
20964
21067
  fs7.rmSync(lockDir, { recursive: true, force: true });
20965
21068
  }
@@ -20975,7 +21078,7 @@ function removeStaleManifestLock(lockDir) {
20975
21078
  try {
20976
21079
  fs7.mkdirSync(reclaimDir, { recursive: false });
20977
21080
  try {
20978
- fs7.writeFileSync(path23.join(reclaimDir, "owner"), `${reclaimOwner}
21081
+ fs7.writeFileSync(path24.join(reclaimDir, "owner"), `${reclaimOwner}
20979
21082
  `, { flag: "wx" });
20980
21083
  } catch (error) {
20981
21084
  fs7.rmSync(reclaimDir, { recursive: true, force: true });
@@ -21050,7 +21153,7 @@ function readManifestLockSnapshot(lockDir) {
21050
21153
  throw error;
21051
21154
  }
21052
21155
  try {
21053
- const owner = fs7.readFileSync(path23.join(lockDir, "owner"), "utf8").trim();
21156
+ const owner = fs7.readFileSync(path24.join(lockDir, "owner"), "utf8").trim();
21054
21157
  return { mtimeMs: stat5.mtimeMs, owner };
21055
21158
  } catch (error) {
21056
21159
  if (error.code === "ENOENT") {
@@ -21122,8 +21225,8 @@ function sleepSync(ms) {
21122
21225
  }
21123
21226
  function createPersonalSpace(baseDir, memoryDirOverride) {
21124
21227
  const homeDir = baseDir ?? resolveHomeDir();
21125
- const standalonePath = path23.join(homeDir, ".engram", "memory");
21126
- const openclawPath = path23.join(homeDir, ".openclaw", "workspace", "memory", "local");
21228
+ const standalonePath = path24.join(homeDir, ".engram", "memory");
21229
+ const openclawPath = path24.join(homeDir, ".openclaw", "workspace", "memory", "local");
21127
21230
  const memoryDir = memoryDirOverride ?? readEnvVar("REMNIC_MEMORY_DIR") ?? readEnvVar("ENGRAM_MEMORY_DIR") ?? (fs7.existsSync(standalonePath) ? standalonePath : fs7.existsSync(openclawPath) ? openclawPath : standalonePath);
21128
21231
  const normalizedMemoryDir = normalizeSpaceMemoryDir(memoryDir);
21129
21232
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -21152,7 +21255,7 @@ function createSpace(options) {
21152
21255
  const id = options.name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-");
21153
21256
  const now = (/* @__PURE__ */ new Date()).toISOString();
21154
21257
  const memoryDir = normalizeSpaceMemoryDir(
21155
- options.memoryDir ?? path23.join(getSpacesDir(options.baseDir), id, "memory")
21258
+ options.memoryDir ?? path24.join(getSpacesDir(options.baseDir), id, "memory")
21156
21259
  );
21157
21260
  const space = updateManifest(options.baseDir, (manifest) => {
21158
21261
  if (manifest.spaces.some((s) => s.id === id)) {
@@ -21373,14 +21476,14 @@ function mergeSpaces(sourceSpaceId, targetSpaceId, options) {
21373
21476
  };
21374
21477
  }
21375
21478
  function getAuditLog(baseDir) {
21376
- const auditPath = path23.join(getSpacesDir(baseDir), "audit.jsonl");
21479
+ const auditPath = path24.join(getSpacesDir(baseDir), "audit.jsonl");
21377
21480
  if (!fs7.existsSync(auditPath)) return [];
21378
21481
  const lines = fs7.readFileSync(auditPath, "utf8").trim().split("\n");
21379
21482
  return lines.filter((l) => l.trim()).map((l) => JSON.parse(l));
21380
21483
  }
21381
21484
  function appendAudit(entry, baseDir) {
21382
- const auditPath = path23.join(getSpacesDir(baseDir), "audit.jsonl");
21383
- fs7.mkdirSync(path23.dirname(auditPath), { recursive: true });
21485
+ const auditPath = path24.join(getSpacesDir(baseDir), "audit.jsonl");
21486
+ fs7.mkdirSync(path24.dirname(auditPath), { recursive: true });
21384
21487
  const full = {
21385
21488
  id: crypto7.randomUUID(),
21386
21489
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -21412,8 +21515,8 @@ function copyMemories(sourceDir, targetDir, options) {
21412
21515
  continue;
21413
21516
  }
21414
21517
  const content = fs7.readFileSync(sourcePath, "utf8");
21415
- const relativePath = path23.relative(sourceRoot, sourceRealPath);
21416
- const targetPath = path23.resolve(targetRoot, relativePath);
21518
+ const relativePath = path24.relative(sourceRoot, sourceRealPath);
21519
+ const targetPath = path24.resolve(targetRoot, relativePath);
21417
21520
  if (!isPathInsideRoot(targetPath, targetRoot)) {
21418
21521
  skipped++;
21419
21522
  continue;
@@ -21455,8 +21558,8 @@ function copyMemories(sourceDir, targetDir, options) {
21455
21558
  skipped++;
21456
21559
  continue;
21457
21560
  }
21458
- fs7.mkdirSync(path23.dirname(targetPath), { recursive: true });
21459
- const targetParentRealPath = safeRealpath(path23.dirname(targetPath));
21561
+ fs7.mkdirSync(path24.dirname(targetPath), { recursive: true });
21562
+ const targetParentRealPath = safeRealpath(path24.dirname(targetPath));
21460
21563
  if (!targetParentRealPath || !isPathInsideRoot(targetParentRealPath, targetRoot)) {
21461
21564
  skipped++;
21462
21565
  continue;
@@ -21473,7 +21576,7 @@ function walkMd2(dir) {
21473
21576
  const results = [];
21474
21577
  function walk(d) {
21475
21578
  for (const entry of fs7.readdirSync(d, { withFileTypes: true })) {
21476
- const fullPath = path23.join(d, entry.name);
21579
+ const fullPath = path24.join(d, entry.name);
21477
21580
  if (entry.isSymbolicLink()) {
21478
21581
  continue;
21479
21582
  }
@@ -21502,8 +21605,8 @@ function safeRealpath(filePath) {
21502
21605
  }
21503
21606
  }
21504
21607
  function isPathInsideRoot(candidatePath, rootPath) {
21505
- const relative = path23.relative(rootPath, candidatePath);
21506
- return relative === "" || !relative.startsWith("..") && !path23.isAbsolute(relative);
21608
+ const relative = path24.relative(rootPath, candidatePath);
21609
+ return relative === "" || !relative.startsWith("..") && !path24.isAbsolute(relative);
21507
21610
  }
21508
21611
  function parseSimpleFrontmatter(content) {
21509
21612
  const match = content.match(/^---\n([\s\S]*?)\n---/);
@@ -21624,7 +21727,7 @@ var REMNIC_RECALL_DECISION_RULES = `## When to Use Recall vs Direct Read
21624
21727
  // src/memory-extension/codex-publisher.ts
21625
21728
  import fs8 from "fs";
21626
21729
  import os3 from "os";
21627
- import path24 from "path";
21730
+ import path25 from "path";
21628
21731
  var REMNIC_EXTENSION_DIR_NAME = "remnic";
21629
21732
  function resolveEnvHome(env) {
21630
21733
  if (env === void 0) return resolveHomeDir();
@@ -21633,12 +21736,12 @@ function resolveEnvHome(env) {
21633
21736
  function expandTildeWithHome(input, homeDir) {
21634
21737
  if (input === "~") return homeDir;
21635
21738
  if (input.startsWith("~/") || input.startsWith("~\\")) {
21636
- return path24.join(homeDir, input.slice(2));
21739
+ return path25.join(homeDir, input.slice(2));
21637
21740
  }
21638
21741
  return input;
21639
21742
  }
21640
21743
  function normalizeHostRoot(input, homeDir) {
21641
- return path24.resolve(expandTildeWithHome(input.trim(), homeDir));
21744
+ return path25.resolve(expandTildeWithHome(input.trim(), homeDir));
21642
21745
  }
21643
21746
  var CodexMemoryExtensionPublisher = class {
21644
21747
  hostId = "codex";
@@ -21652,12 +21755,12 @@ var CodexMemoryExtensionPublisher = class {
21652
21755
  async resolveExtensionRoot(env) {
21653
21756
  const homeDir = resolveEnvHome(env);
21654
21757
  const codexHomeInput = env === void 0 ? readEnvVar("CODEX_HOME")?.trim() : env.CODEX_HOME?.trim();
21655
- const codexHome = codexHomeInput ? normalizeHostRoot(codexHomeInput, homeDir) : path24.resolve(homeDir, ".codex");
21656
- return path24.join(codexHome, "memories_extensions", REMNIC_EXTENSION_DIR_NAME);
21758
+ const codexHome = codexHomeInput ? normalizeHostRoot(codexHomeInput, homeDir) : path25.resolve(homeDir, ".codex");
21759
+ return path25.join(codexHome, "memories_extensions", REMNIC_EXTENSION_DIR_NAME);
21657
21760
  }
21658
21761
  async isHostAvailable() {
21659
21762
  try {
21660
- const home = readEnvVar("CODEX_HOME")?.trim() || path24.join(resolveHomeDir(), ".codex");
21763
+ const home = readEnvVar("CODEX_HOME")?.trim() || path25.join(resolveHomeDir(), ".codex");
21661
21764
  return fs8.existsSync(home);
21662
21765
  } catch {
21663
21766
  return false;
@@ -21708,7 +21811,7 @@ When running inside the Codex phase-2 consolidation sandbox:
21708
21811
  }
21709
21812
  async publish(ctx) {
21710
21813
  const extensionRoot = await this.resolveExtensionRoot();
21711
- const instructionsPath = path24.join(extensionRoot, "instructions.md");
21814
+ const instructionsPath = path25.join(extensionRoot, "instructions.md");
21712
21815
  const filesWritten = [];
21713
21816
  const skipped = [];
21714
21817
  ctx.log.info(`Publishing Codex memory extension to ${extensionRoot}`);
@@ -21828,7 +21931,7 @@ function publisherForConnector(connectorId) {
21828
21931
  // src/session-summaries/index.ts
21829
21932
  import { createHash as createHash8 } from "crypto";
21830
21933
  import { lstat as lstat2, mkdir as mkdir7, readFile as readFile6, readdir as readdir4, rename, rm, stat as stat4, writeFile as writeFile7 } from "fs/promises";
21831
- import path25 from "path";
21934
+ import path26 from "path";
21832
21935
 
21833
21936
  // src/session-summaries/adapters.ts
21834
21937
  var VALID_ROLES = /* @__PURE__ */ new Set(["user", "assistant", "tool", "system", "other"]);
@@ -22333,7 +22436,7 @@ function shortHash(value, length = 16) {
22333
22436
  async function listTranscriptFiles(root, maxFiles) {
22334
22437
  const out = [];
22335
22438
  let truncated = false;
22336
- const entrySortKey = (entryName, isDirectory) => isDirectory ? `${entryName}${path25.sep}` : entryName;
22439
+ const entrySortKey = (entryName, isDirectory) => isDirectory ? `${entryName}${path26.sep}` : entryName;
22337
22440
  async function visit(dir) {
22338
22441
  if (truncated) return;
22339
22442
  const entries = (await readdir4(dir, { withFileTypes: true })).sort(
@@ -22343,13 +22446,13 @@ async function listTranscriptFiles(root, maxFiles) {
22343
22446
  if (truncated) return;
22344
22447
  if (entry.name.startsWith(".")) continue;
22345
22448
  if (entry.isSymbolicLink()) continue;
22346
- const fullPath = path25.join(dir, entry.name);
22449
+ const fullPath = path26.join(dir, entry.name);
22347
22450
  if (entry.isDirectory()) {
22348
22451
  await visit(fullPath);
22349
22452
  continue;
22350
22453
  }
22351
22454
  if (!entry.isFile()) continue;
22352
- const ext = path25.extname(entry.name).toLowerCase();
22455
+ const ext = path26.extname(entry.name).toLowerCase();
22353
22456
  if (!SUPPORTED_EXTENSIONS.has(ext)) continue;
22354
22457
  out.push(fullPath);
22355
22458
  if (out.length > maxFiles) {
@@ -22376,7 +22479,7 @@ function normalizeInputDir(inputDir) {
22376
22479
  if (typeof inputDir !== "string" || inputDir.trim().length === 0) {
22377
22480
  throw new Error("inputDir must be a non-empty string");
22378
22481
  }
22379
- return path25.resolve(expandTildePath(inputDir.trim()));
22482
+ return path26.resolve(expandTildePath(inputDir.trim()));
22380
22483
  }
22381
22484
  function initRoleCounts() {
22382
22485
  return {
@@ -22533,11 +22636,11 @@ async function collectLocalSessionSummaries(options) {
22533
22636
  continue;
22534
22637
  }
22535
22638
  seenFileHashes.add(fileHash);
22536
- const fileExtension = path25.extname(filePath).toLowerCase();
22639
+ const fileExtension = path26.extname(filePath).toLowerCase();
22537
22640
  const parsed = await adapter.parseFile(
22538
22641
  {
22539
22642
  content,
22540
- fileName: path25.basename(filePath),
22643
+ fileName: path26.basename(filePath),
22541
22644
  fileExtension,
22542
22645
  fileRef: fileHash
22543
22646
  },
@@ -22593,7 +22696,7 @@ async function collectLocalSessionSummaries(options) {
22593
22696
  }
22594
22697
  async function readRedactionConfig(pathLike) {
22595
22698
  if (!pathLike) return void 0;
22596
- const raw = await readFile6(path25.resolve(expandTildePath(pathLike)), "utf-8");
22699
+ const raw = await readFile6(path26.resolve(expandTildePath(pathLike)), "utf-8");
22597
22700
  const parsed = JSON.parse(raw);
22598
22701
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
22599
22702
  throw new Error("redaction config must be a JSON object");
@@ -22606,15 +22709,15 @@ function toJsonl(drafts) {
22606
22709
  }
22607
22710
  function defaultDraftOutputPath(memoryDir, generatedAt) {
22608
22711
  const stamp = generatedAt.replace(/[:.]/g, "-");
22609
- return path25.join(
22610
- path25.resolve(expandTildePath(memoryDir)),
22712
+ return path26.join(
22713
+ path26.resolve(expandTildePath(memoryDir)),
22611
22714
  "state",
22612
22715
  "session-summary-drafts",
22613
22716
  `session-summaries-${stamp}.jsonl`
22614
22717
  );
22615
22718
  }
22616
22719
  async function writeDrafts(filePath, drafts) {
22617
- await mkdir7(path25.dirname(filePath), { recursive: true });
22720
+ await mkdir7(path26.dirname(filePath), { recursive: true });
22618
22721
  const tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
22619
22722
  try {
22620
22723
  await writeFile7(tempPath, toJsonl(drafts), "utf-8");
@@ -22632,7 +22735,7 @@ async function runLocalSessionSummaryCliCommand(options) {
22632
22735
  });
22633
22736
  const wroteFiles = [];
22634
22737
  if (options.output) {
22635
- const outputPath = path25.resolve(expandTildePath(options.output));
22738
+ const outputPath = path26.resolve(expandTildePath(options.output));
22636
22739
  await writeDrafts(outputPath, report.drafts);
22637
22740
  wroteFiles.push(outputPath);
22638
22741
  }
@@ -22667,19 +22770,19 @@ async function runLocalSessionSummaryCliCommand(options) {
22667
22770
 
22668
22771
  // src/transfer/capsule-fork.ts
22669
22772
  import { lstat as lstat3, mkdir as mkdir8, readFile as readFile7, realpath as realpath2, writeFile as writeFile8 } from "fs/promises";
22670
- import path26 from "path";
22773
+ import path27 from "path";
22671
22774
  async function forkCapsule(opts) {
22672
22775
  validateForkId(opts.forkId);
22673
- const rootAbs = path26.resolve(opts.targetRoot);
22776
+ const rootAbs = path27.resolve(opts.targetRoot);
22674
22777
  await assertIsDirectoryNotSymlink(rootAbs, "forkCapsule", "targetRoot");
22675
- const forkDirAbs = path26.join(rootAbs, "forks", opts.forkId);
22778
+ const forkDirAbs = path27.join(rootAbs, "forks", opts.forkId);
22676
22779
  const forkEntryExists = await pathEntryExists(forkDirAbs);
22677
22780
  if (forkEntryExists) {
22678
22781
  throw new Error(
22679
22782
  `forkCapsule: fork path already exists \u2014 forkId "${opts.forkId}" is already in use at: ${forkDirAbs}`
22680
22783
  );
22681
22784
  }
22682
- const archiveAbs = path26.resolve(opts.sourceArchive);
22785
+ const archiveAbs = path27.resolve(opts.sourceArchive);
22683
22786
  const importResult = await importCapsule({
22684
22787
  archivePath: archiveAbs,
22685
22788
  root: rootAbs,
@@ -22701,10 +22804,10 @@ async function forkCapsule(opts) {
22701
22804
  importedRecords: importResult.imported.length,
22702
22805
  skippedRecords: importResult.skipped.length
22703
22806
  };
22704
- const lineagePath = path26.join(forkDirAbs, "lineage.json");
22807
+ const lineagePath = path27.join(forkDirAbs, "lineage.json");
22705
22808
  const rootReal = await realpath2(rootAbs);
22706
22809
  await assertRealpathInsideRoot(rootReal, lineagePath, `forks/${opts.forkId}/lineage.json`, "forkCapsule");
22707
- await mkdir8(path26.dirname(lineagePath), { recursive: true });
22810
+ await mkdir8(path27.dirname(lineagePath), { recursive: true });
22708
22811
  await writeFile8(lineagePath, JSON.stringify(lineage, null, 2) + "\n", "utf-8");
22709
22812
  return {
22710
22813
  archivePath: archiveAbs,
@@ -22718,11 +22821,11 @@ async function readForkLineage(targetRoot, forkId) {
22718
22821
  if (typeof forkId !== "string" || forkId.length === 0 || forkId.length > 64 || !CAPSULE_ID_PATTERN.test(forkId)) {
22719
22822
  return null;
22720
22823
  }
22721
- const rootAbs = path26.resolve(targetRoot);
22824
+ const rootAbs = path27.resolve(targetRoot);
22722
22825
  const rootReal = await realpath2(rootAbs).catch(() => rootAbs);
22723
- const lineagePath = path26.join(rootReal, "forks", forkId, "lineage.json");
22724
- const rel = path26.relative(rootReal, lineagePath);
22725
- if (rel.startsWith("..") || path26.isAbsolute(rel)) {
22826
+ const lineagePath = path27.join(rootReal, "forks", forkId, "lineage.json");
22827
+ const rel = path27.relative(rootReal, lineagePath);
22828
+ if (rel.startsWith("..") || path27.isAbsolute(rel)) {
22726
22829
  return null;
22727
22830
  }
22728
22831
  if (!await isLineagePathContained(rootReal, lineagePath)) {
@@ -22763,19 +22866,19 @@ async function pathEntryExists(absPath) {
22763
22866
  async function isLineagePathContained(rootReal, lineagePath) {
22764
22867
  let existing = lineagePath;
22765
22868
  const suffix = [];
22766
- while (existing !== path26.dirname(existing)) {
22869
+ while (existing !== path27.dirname(existing)) {
22767
22870
  const st = await lstat3(existing).catch(() => null);
22768
22871
  if (st !== null) break;
22769
- suffix.unshift(path26.basename(existing));
22770
- existing = path26.dirname(existing);
22872
+ suffix.unshift(path27.basename(existing));
22873
+ existing = path27.dirname(existing);
22771
22874
  }
22772
22875
  const existingReal = await realpath2(existing).catch(() => existing);
22773
- const targetReal = suffix.length > 0 ? path26.join(existingReal, ...suffix) : existingReal;
22774
- const rel = path26.relative(rootReal, targetReal);
22876
+ const targetReal = suffix.length > 0 ? path27.join(existingReal, ...suffix) : existingReal;
22877
+ const rel = path27.relative(rootReal, targetReal);
22775
22878
  if (rel === "") return true;
22776
22879
  if (rel === "..") return false;
22777
- if (rel.startsWith(`..${path26.sep}`)) return false;
22778
- if (path26.isAbsolute(rel)) return false;
22880
+ if (rel.startsWith(`..${path27.sep}`)) return false;
22881
+ if (path27.isAbsolute(rel)) return false;
22779
22882
  return true;
22780
22883
  }
22781
22884
 
@@ -22911,4 +23014,4 @@ export {
22911
23014
  resolvePersistedMemoryRelativePath,
22912
23015
  Orchestrator
22913
23016
  };
22914
- //# sourceMappingURL=chunk-ZWGXZ7ID.js.map
23017
+ //# sourceMappingURL=chunk-PNUOQYRJ.js.map