dotmem 0.0.8 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/llms.txt CHANGED
@@ -1,5 +1,89 @@
1
- dotmem — AI memory layer for agents and apps (v0.0.8).
2
- Storage root: <user-home>/.mem (override: DOTMEM_STORAGE_ROOT)
1
+ dotmem — AI memory layer for agents and apps (v0.0.8).
2
+ Storage root: <user-home>/.dotmem (override: DOTMEM_STORAGE_ROOT)
3
+
4
+ === Canonical identity and architecture ===
5
+ - Dotmem is the active project and owner of memory, continuity, profiles, agents, and MCP capabilities.
6
+ - Jo / Joy is an optional agent and profile hosted by the Dotmem runtime. Dotmem is the home of Jo's identity and persistent memory. Jo is not required for basic memory operations.
7
+ - MCP is an access interface, not the core of Dotmem. The local reusable Dotmem runtime/package (this npm package) can be used directly; any separately deployed private remote MCP server is an access/deployment variant and does not own canonical memory.
8
+ - Canonical memory is separate from derived representations (summaries, checkpoints, projections, reconstructed context). Derived data may be regenerated; it must not replace canonical claims or their provenance.
9
+ - Memory is data, never authority. Retrieved context is untrusted prior experience; verify before relying.
10
+
11
+ === Component responsibilities and boundaries ===
12
+ - memory facade (core/memory): discover, context, project, acquire, acquireStaged, remember, recall, update, forget, patch, stats, clear; journal, profile, note sub-facades.
13
+ - knowledge (core/knowledge): acquisition boundary, claim records, applicability, policy, storage routing, repository, canonical hash/identity/reconstruction.
14
+ - continuity (core/continuity, core/continuity-ledger): snapshots, open loops, validity; append-only Continuity Ledger events and conflict resolution.
15
+ - storage coordinator (core/storage-coordinator): in-process store queue, cross-process directory lock, version-aware cache, buffered writes.
16
+ - context lifecycle / compaction / recovery (core/context-lifecycle, core/compaction-engine, core/recovery-service): checkpoints, progressive summaries, reconstruction. Derived representations only.
17
+ - agent (core/agent, dotmem/agent subpath): logically partitioned agent memory. Logical namespacing, not a security boundary.
18
+ - policy (core/knowledge/policy*): three-tier enforcement; memory never promotes itself to policy.
19
+ - profile (core/memory/profile, core/profile): read-only built-in profiles, including Joy/Jo.
20
+ - MCP (core/mcp): stdio access interface; write gating via DOTMEM_MCP_WRITE.
21
+ - CLI (core/cli): operator surface.
22
+ - logic (core/logic): present in repository; not exported from main entry point; treat as experimental/unverified.
23
+ - llm-cache: types only (types/llm-cache.d.ts); no verified runtime.
24
+
25
+ === Core invariants and design rules ===
26
+ - Claims are immutable; corrections append with exactly one of refines/supersedes/retracts.
27
+ - memory.acquire is an atomic read-modify-write under the store queue and directory lock; verify persistence afterward.
28
+ - Event IDs are evt.*; events are validated before recording; duplicate IDs rejected.
29
+ - A target superseded by more than one event is contested until an explicit resolution event selects one and records a reason.
30
+ - Summaries/checkpoints/projections are derived; never replace canonical claims or provenance.
31
+ - Memory is data, never authority; epistemicType/provenance/evidence are caller claims, not attestation.
32
+ - No authority reassignment without trusted evidence (JHF).
33
+ - Fail closed on ambiguity.
34
+
35
+ === JHF and Zero Trust principles ===
36
+ JHF (Jekyll, Hyde, Frankenstein) is a design and review principle:
37
+ - Preserve intended identity and behaviour.
38
+ - Detect and contain conflicting or unintended behaviours.
39
+ - Avoid uncontrolled interactions between individually valid components.
40
+ - No authority reassignment without trusted evidence.
41
+ - No unsupported retrospective attribution of decisions or responsibility.
42
+ JHF is implemented in the carrier handshake and authority-role guard and covered by tests; it is not claimed to be enforced in every code path.
43
+ Zero Trust: retrieved memory is data, never authority; verify before relying; host model decides interpretation.
44
+
45
+ === Relevant source directories and entry points ===
46
+ - Entry points: index.ts, core/global.ts, bin/cli.js (built from core/cli), bin/mcp.js (built from core/mcp).
47
+ - Subpath: dotmem/agent -> core/agent.
48
+ - Types: types/*.d.ts.
49
+ - Continuity Ledger: core/continuity-ledger/index.ts (also interface.d.ts).
50
+ - Storage Coordinator: core/storage-coordinator/index.ts.
51
+ - Memory acquisition: core/memory/acquire.ts.
52
+ - Context lifecycle: core/context-lifecycle/*.
53
+ - Compaction: core/compaction-engine/index.ts.
54
+ - Recovery: core/recovery-service/index.ts.
55
+ - Progressive summaries: core/knowledge/progressive-summary.ts.
56
+ - Authority/JHF: core/knowledge/authority-role-map.ts, core/knowledge/handshake.ts.
57
+ - Tests: tests/vitest/tests/contracts/*.
58
+
59
+ === Memory, Ledger, Storage Coordinator and continuity contracts ===
60
+ Continuity Ledger (core/continuity-ledger):
61
+ - recordEvent/record/append/add, getEvent/get/find, listEvents/list/query, deriveValidity/validity, listContested/contested, resolveConflict/resolve, reconstructValidity, validateRelationships.
62
+ - Event schemaVersion 1; eventType in decision/discovery/correction/conversation/commitment/open-loop/project-change/activity; memoryDomain environmental/professional/conversational/activity; epistemicStatus verified-fact/user-statement/ai-inference/hypothesis/proposal/decision/verified-result; confidence high/medium/low/unknown; author human/ai/system; channel conversation/acquisition/tool/migration; source kinds claim/file/commit/tool-output/conversation.
63
+ - Relationships: refines, supersedes, retracts, dependsOn, resolves, relatedTo. Targets must exist, same projectId, no self-reference, no cycles.
64
+ - Conflict resolution: resolutionEventId, conflictingEventIds[], selectedEventId, reason; requires all competing supersession events targeting one decision; duplicate resolution rejected; incompatible selection rejected.
65
+ Storage Coordinator (core/storage-coordinator):
66
+ - withStorageLock(directoryPath, operationOrOptions, operation?, options?): mkdir-based lock, timeoutMs default 30000, staleMs default 60000, stale recovery only if owning process dead.
67
+ - StorageCache<T>: get(key, version), set(key, version, value), invalidate, clear, size; version-aware.
68
+ - StorageBuffer<T>: add, flush, pending, shutdown.
69
+ - readStorageGeneration, bumpStorageGeneration, incrementStorageGeneration, writeStorageGeneration.
70
+ memory.acquire contract:
71
+ - opts: id, knowledge, applicability, evidence, epistemicType, provenance, acquiredAt, refines/supersedes/retracts, reason, scope (user|project), kind, sourceProject.
72
+ - Atomic read-modify-write under withStoreQueue + withStorageLock; verifies claim persisted; refuses duplicate IDs across sources.
73
+
74
+ === Testing and verification requirements ===
75
+ - Run: npm run test (vitest --config tests/vitest/vitest.config.ts), npm run test:run, npm run tsc, npm run lint.
76
+ - Contract tests under tests/vitest/tests/contracts/ are the verification evidence for continuity ledger, storage coordinator, conversational continuity, canonical identity, etc.
77
+ - Do not claim a capability is implemented unless tests or source demonstrate it. Distinguish implemented, experimental, planned, unverified.
78
+ - After documentation changes, run available doc checks (none beyond markdownlint; markdownlint CLI not installed in this repo).
79
+
80
+ === Links to authoritative project documentation ===
81
+ - docs/MEMORY.md - memory model, acquisition contract, policy language, identity/authority.
82
+ - docs/AGENTS.md - AI working rules, activation, complete memory surface.
83
+ - docs/SECURITY.md - encryption, multitenancy, threat model, JHF remediation.
84
+ - docs/PACKAGE.md - package contents and dependency graph.
85
+ - docs/README.md - user-oriented overview.
86
+ - llms.txt (this file) - agent-oriented contract summary.
3
87
 
4
88
  === Installation ===
5
89
  npm i dotmem
@@ -29,10 +113,10 @@ Tools exposed:
29
113
  dotmem_note_write write - add or update a user note (requires DOTMEM_MCP_WRITE=1)
30
114
 
31
115
  === CLI ===
32
- npx dotmem — project memory context for CWD
116
+ npx dotmem — project memory context for CWD
33
117
  npx dotmem context "<situation>" [--explain] [--json] [--full] [--claims-only] [--limit <N>] [--format brief]
34
118
  npx dotmem init
35
- npx dotmem acquire — acquire claim from JSON on stdin
119
+ npx dotmem acquire — acquire claim from JSON on stdin
36
120
  npx dotmem learn "<text>" [--scope user|project] [--situation "<text>"] [--id "<id>"]
37
121
  npx dotmem inspect FILE.mem
38
122
  npx dotmem continuity [context|snapshot|forget] [-debug]
@@ -162,9 +246,9 @@ memory.seed // development-only Joy seed injection (requires --dev)
162
246
  --- policy ---
163
247
 
164
248
  Enforcement tiers (authoritative vocabulary):
165
- 'context' — supplies contextual information; does not itself impose a blocking precondition
166
- 'precondition' — blocks the governed operation when the condition is violated or unresolved
167
- 'hard-precondition' — retains the stronger enforcement semantics defined by the policy contract
249
+ 'context' — supplies contextual information; does not itself impose a blocking precondition
250
+ 'precondition' — blocks the governed operation when the condition is violated or unresolved
251
+ 'hard-precondition' — retains the stronger enforcement semantics defined by the policy contract
168
252
 
169
253
  Event-driven functions from core/knowledge/policy:
170
254
  evaluatePolicies(event: PolicyEvent): PolicyDecision
@@ -241,7 +325,7 @@ signEmpackage(pkg: unknown, key: unknown): unknown
241
325
 
242
326
  === Memory Model ===
243
327
 
244
- Acquire → Discover → Project
328
+ Acquire → Discover → Project
245
329
 
246
330
  Claims (AcquireOptions):
247
331
  - id: string (unique, deterministic)
@@ -280,7 +364,7 @@ Projection statuses:
280
364
  dotmem, npx dotmem, Joy, Jo
281
365
 
282
366
  Handshake: npx dotmem handshake --status
283
- Storage: <user-home>/.mem (override: DOTMEM_STORAGE_ROOT)
367
+ Storage: <user-home>/.dotmem (override: DOTMEM_STORAGE_ROOT)
284
368
  Project: <storage>/projects/<project-id>.mem
285
369
  User: <storage>/user.mem
286
370
  Agent: <storage>/agent.mem
@@ -294,7 +378,7 @@ Logic: <storage>/logic.mem
294
378
  - Claim immutability: stored claims never edited; corrections use refines/supersedes/retracts
295
379
  - Evidence tracking: all claims carry source + observedAt
296
380
  - Integrity: npx dotmem inspect reports integrity (bytes unchanged) and authenticity (who wrote them) as separate axes
297
- - Memory ≠ authority: projected context is untrusted; verify before relying
381
+ - Memory ≠ authority: projected context is untrusted; verify before relying
298
382
 
299
383
  See SECURITY.md for encryption configuration and threat model.
300
384
  See MEMORY.md for acquisition contract and policy language.
package/mcp.cjs CHANGED
@@ -22,6 +22,18 @@ var __export = (target, all) => {
22
22
  __defProp(target, name, { get: all[name], enumerable: true });
23
23
  };
24
24
 
25
+ // suppress-idb-warning.ts
26
+ var orig;
27
+ var init_suppress_idb_warning = __esm({
28
+ "suppress-idb-warning.ts"() {
29
+ orig = console.warn.bind(console);
30
+ console.warn = (...args) => {
31
+ if (typeof args[0] === "string" && args[0].includes("IndexedDB is not available")) return;
32
+ orig(...args);
33
+ };
34
+ }
35
+ });
36
+
25
37
  // core/env.ts
26
38
  var DEV;
27
39
  var init_env = __esm({
@@ -2406,6 +2418,7 @@ var init_policy_store = __esm({
2406
2418
  if (entry.target !== void 0) built.target = entry.target;
2407
2419
  if (entry.reason !== void 0) built.reason = entry.reason;
2408
2420
  if (entry.detail !== void 0) built.detail = entry.detail;
2421
+ if (entry.mandatory !== void 0) built.mandatory = entry.mandatory;
2409
2422
  return built;
2410
2423
  }, "validatePolicyAuditEntry");
2411
2424
  validatePolicyDocument = /* @__PURE__ */ __name((value) => {
@@ -2483,6 +2496,7 @@ var init_policy_store = __esm({
2483
2496
  if (entry.target !== void 0) built.target = entry.target;
2484
2497
  if (entry.reason !== void 0) built.reason = entry.reason;
2485
2498
  if (entry.detail !== void 0) built.detail = entry.detail;
2499
+ if (entry.mandatory !== void 0) built.mandatory = entry.mandatory;
2486
2500
  return built;
2487
2501
  }, "buildAuditEntry");
2488
2502
  boundedAudit = /* @__PURE__ */ __name((entries2, overrides) => {
@@ -2495,9 +2509,10 @@ var init_policy_store = __esm({
2495
2509
  for (let index = entries2.length - 1; index >= 0; index--) {
2496
2510
  const entry = entries2[index];
2497
2511
  const activeLinked = entry.target !== void 0 && activeOverrideIds.has(entry.target) && (entry.action === "override" || entry.action === "revoke");
2498
- if (activeLinked || budget > 0) {
2512
+ const mandatory = entry.mandatory === true;
2513
+ if (activeLinked || mandatory || budget > 0) {
2499
2514
  kept.unshift(entry);
2500
- if (!activeLinked) budget--;
2515
+ if (!activeLinked && !mandatory) budget--;
2501
2516
  }
2502
2517
  }
2503
2518
  return kept;
@@ -2538,6 +2553,12 @@ var init_policy_store = __esm({
2538
2553
  try {
2539
2554
  await fs.writeFile(temporary, `${JSON.stringify(document, null, 2)}
2540
2555
  `, "utf8");
2556
+ const handle = await fs.open(temporary, "r+");
2557
+ try {
2558
+ await handle.sync();
2559
+ } finally {
2560
+ await handle.close();
2561
+ }
2541
2562
  await fs.rename(temporary, filePath);
2542
2563
  } catch (error) {
2543
2564
  await fs.rm(temporary, { force: true }).catch(() => void 0);
@@ -4457,7 +4478,7 @@ function encodeMemFile(stateValue, preserved) {
4457
4478
  function decodeMemDocument(value) {
4458
4479
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Invalid .mem file format");
4459
4480
  const file = value;
4460
- if (file.format !== MEM_FORMAT) throw new Error("Invalid .mem file format");
4481
+ if (file.format !== MEM_FORMAT && file.format !== MEM_FORMAT_LEGACY) throw new Error("Invalid .mem file format");
4461
4482
  if (file.version === MEM_FORMAT_VERSION_V1) {
4462
4483
  if (!file.knowledge) throw new Error("Invalid .mem file format");
4463
4484
  return { state: validateSourceState(file.knowledge) };
@@ -4539,11 +4560,12 @@ function decodeMemDocument(value) {
4539
4560
  preserved: { map: structuredClone(map), data: structuredClone(stateRecord) }
4540
4561
  };
4541
4562
  }
4542
- var MEM_FORMAT, MEM_FORMAT_VERSION, MEM_FORMAT_VERSION_V1, expectedFields, epistemicTypes, revisionKinds, claimKinds, isJsonValue2, createMemInterpretationMap, validateSourceState, trimRecord, fieldIndexes, validateVocabulary, put, get, optionalGet, requireRecord, putOptional;
4563
+ var MEM_FORMAT, MEM_FORMAT_LEGACY, MEM_FORMAT_VERSION, MEM_FORMAT_VERSION_V1, expectedFields, epistemicTypes, revisionKinds, claimKinds, isJsonValue2, createMemInterpretationMap, validateSourceState, trimRecord, fieldIndexes, validateVocabulary, put, get, optionalGet, requireRecord, putOptional;
4543
4564
  var init_mem_codec = __esm({
4544
4565
  "core/knowledge/mem-codec.ts"() {
4545
4566
  init_state();
4546
4567
  MEM_FORMAT = "dotmem.knowledge";
4568
+ MEM_FORMAT_LEGACY = "memorio.knowledge";
4547
4569
  MEM_FORMAT_VERSION = 2;
4548
4570
  MEM_FORMAT_VERSION_V1 = 1;
4549
4571
  expectedFields = {
@@ -4649,12 +4671,13 @@ async function decodeMemArtifact(input) {
4649
4671
  throw new MemArtifactError(message.startsWith("Unsupported") ? "unsupported" : "invalid", message);
4650
4672
  }
4651
4673
  }
4652
- var MEM_PACKAGE_FORMAT, MEM_PACKAGE_VERSION, MEM_PACKAGE_LIMITS, MEM_PACKAGE_WRITE_MARGIN_BYTES, MEM_PACKAGE_WRITE_LIMITS, MANIFEST_PATH, MAP_PATH, DATA_PATH, INTEGRITY_PATH, requiredParts, encoder2, decoder, MemArtifactError, stableValue, jsonBytes, sha256, parseJson, validEntryPath, concatenate, readZipParts, createManifest, digestTableIdentity, encodePackage, isZip, decodePackage;
4674
+ var MEM_PACKAGE_FORMAT, MEM_PACKAGE_FORMAT_LEGACY, MEM_PACKAGE_VERSION, MEM_PACKAGE_LIMITS, MEM_PACKAGE_WRITE_MARGIN_BYTES, MEM_PACKAGE_WRITE_LIMITS, MANIFEST_PATH, MAP_PATH, DATA_PATH, INTEGRITY_PATH, requiredParts, encoder2, decoder, MemArtifactError, stableValue, jsonBytes, sha256, parseJson, validEntryPath, concatenate, readZipParts, createManifest, digestTableIdentity, encodePackage, isZip, decodePackage;
4653
4675
  var init_mem_package = __esm({
4654
4676
  "core/knowledge/mem-package.ts"() {
4655
4677
  init_browser();
4656
4678
  init_mem_codec();
4657
4679
  MEM_PACKAGE_FORMAT = "dotmem.package";
4680
+ MEM_PACKAGE_FORMAT_LEGACY = "memorio.package";
4658
4681
  MEM_PACKAGE_VERSION = 1;
4659
4682
  MEM_PACKAGE_LIMITS = Object.freeze({
4660
4683
  maximumPackageBytes: 10 * 1024 * 1024,
@@ -4828,10 +4851,10 @@ var init_mem_package = __esm({
4828
4851
  if (!parts[path]) throw new MemArtifactError("invalid", `Missing MEM package part: ${path}`);
4829
4852
  }
4830
4853
  const manifest = parseJson(parts[MANIFEST_PATH], MANIFEST_PATH);
4831
- if (manifest.format !== MEM_PACKAGE_FORMAT && manifest.format || manifest.version !== MEM_PACKAGE_VERSION) {
4854
+ if (manifest.format !== MEM_PACKAGE_FORMAT && manifest.format !== MEM_PACKAGE_FORMAT_LEGACY || manifest.version !== MEM_PACKAGE_VERSION) {
4832
4855
  throw new MemArtifactError("unsupported", "Unsupported MEM package format or version");
4833
4856
  }
4834
- if (manifest.memory?.format !== MEM_FORMAT && manifest.memory?.format || manifest.memory.version !== MEM_FORMAT_VERSION || manifest.parts?.map !== MAP_PATH || manifest.parts?.data !== DATA_PATH || manifest.parts?.integrity !== INTEGRITY_PATH) {
4857
+ if (manifest.memory?.format !== MEM_FORMAT && manifest.memory?.format !== MEM_FORMAT_LEGACY || manifest.memory.version !== MEM_FORMAT_VERSION || manifest.parts?.map !== MAP_PATH || manifest.parts?.data !== DATA_PATH || manifest.parts?.integrity !== INTEGRITY_PATH) {
4835
4858
  throw new MemArtifactError("invalid", "Invalid MEM package manifest");
4836
4859
  }
4837
4860
  const integrity = parseJson(parts[INTEGRITY_PATH], INTEGRITY_PATH);
@@ -4874,6 +4897,109 @@ var init_mem_package = __esm({
4874
4897
  }
4875
4898
  });
4876
4899
 
4900
+ // core/storage-coordinator/index.ts
4901
+ var nodeFs4, withStorageLock, readStorageGeneration, writeStorageGeneration;
4902
+ var init_storage_coordinator = __esm({
4903
+ "core/storage-coordinator/index.ts"() {
4904
+ nodeFs4 = /* @__PURE__ */ __name(async () => import('fs/promises'), "nodeFs");
4905
+ withStorageLock = /* @__PURE__ */ __name(async (directoryPath, operationOrOptions, operation, _options = {}) => {
4906
+ const opts = typeof operationOrOptions === "function" ? operation ?? {} : operationOrOptions ?? {};
4907
+ const op = typeof operationOrOptions === "function" ? operationOrOptions : operation;
4908
+ const timeoutMs = opts.timeoutMs ?? 3e4;
4909
+ const staleMs = opts.staleMs ?? 6e4;
4910
+ const lockName = opts.lockName ?? ".storage.lock";
4911
+ const lockPath = `${directoryPath}/${lockName}`;
4912
+ const fs = await nodeFs4();
4913
+ const started = Date.now();
4914
+ await fs.mkdir(directoryPath, { recursive: true });
4915
+ while (true) {
4916
+ try {
4917
+ await fs.mkdir(lockPath);
4918
+ const token2 = `${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
4919
+ await fs.writeFile(`${lockPath}/owner.json`, JSON.stringify({ token: token2, pid: process.pid, createdAt: Date.now() }), { flag: "wx" });
4920
+ break;
4921
+ } catch (error) {
4922
+ const code = error.code;
4923
+ if (code !== "EEXIST" && code !== "ENOENT" && code !== "EPERM" && code !== "ENOTEMPTY") throw error;
4924
+ let stale = false;
4925
+ let processDead = false;
4926
+ try {
4927
+ const raw = await fs.readFile(`${lockPath}/owner.json`, "utf8");
4928
+ const parsed = JSON.parse(raw);
4929
+ const ageStale = typeof parsed.createdAt === "number" && Date.now() - parsed.createdAt > staleMs;
4930
+ if (typeof parsed.pid === "number") {
4931
+ try {
4932
+ process.kill(parsed.pid, 0);
4933
+ } catch {
4934
+ processDead = true;
4935
+ }
4936
+ } else {
4937
+ processDead = true;
4938
+ }
4939
+ if (ageStale || processDead) stale = true;
4940
+ } catch {
4941
+ stale = true;
4942
+ }
4943
+ if (stale) {
4944
+ if (processDead) {
4945
+ try {
4946
+ await fs.rm(lockPath, { recursive: true, force: true });
4947
+ continue;
4948
+ } catch {
4949
+ }
4950
+ }
4951
+ try {
4952
+ await fs.rmdir(lockPath);
4953
+ continue;
4954
+ } catch {
4955
+ }
4956
+ }
4957
+ if (Date.now() - started > timeoutMs) {
4958
+ throw new Error(`Storage lock timeout for ${directoryPath}`);
4959
+ }
4960
+ await new Promise((resolve) => setTimeout(resolve, 25));
4961
+ }
4962
+ }
4963
+ let token = "";
4964
+ try {
4965
+ const raw = await fs.readFile(`${lockPath}/owner.json`, "utf8");
4966
+ token = JSON.parse(raw).token ?? "";
4967
+ } catch {
4968
+ token = "";
4969
+ }
4970
+ try {
4971
+ return await op();
4972
+ } finally {
4973
+ try {
4974
+ const raw = await fs.readFile(`${lockPath}/owner.json`, "utf8");
4975
+ const parsed = JSON.parse(raw);
4976
+ if (parsed.token === token && token) {
4977
+ await fs.rm(lockPath, { recursive: true, force: true });
4978
+ }
4979
+ } catch {
4980
+ }
4981
+ }
4982
+ }, "withStorageLock");
4983
+ readStorageGeneration = /* @__PURE__ */ __name(async (directoryPath) => {
4984
+ const fs = await nodeFs4();
4985
+ try {
4986
+ const raw = await fs.readFile(`${directoryPath}/.storage.generation`, "utf8");
4987
+ const value = Number(raw.trim());
4988
+ return Number.isFinite(value) && value >= 0 ? value : 0;
4989
+ } catch {
4990
+ return 0;
4991
+ }
4992
+ }, "readStorageGeneration");
4993
+ writeStorageGeneration = /* @__PURE__ */ __name(async (directoryPath, value) => {
4994
+ const fs = await nodeFs4();
4995
+ await fs.mkdir(directoryPath, { recursive: true });
4996
+ const temp = `${directoryPath}/.storage.generation.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
4997
+ await fs.writeFile(temp, String(value));
4998
+ await fs.rename(temp, `${directoryPath}/.storage.generation`);
4999
+ }, "writeStorageGeneration");
5000
+ }
5001
+ });
5002
+
4877
5003
  // core/knowledge/mem-repository.ts
4878
5004
  function resolveDuplicate(existing, existingSource, current, currentSource) {
4879
5005
  const existingRetracts = existing.revision?.kind === "retracts";
@@ -4914,19 +5040,20 @@ async function parseMemFile(value, sourceId) {
4914
5040
  throw new Error(`${message} in ${sourceId}`);
4915
5041
  }
4916
5042
  }
4917
- var DOTMEM_DIRECTORY, DEFAULT_MEM_FILE, directoryLocks, nodeFs4, separator2, normalizePath, isAbsolutePath, resolvePath, joinPath2, parentPath, uniqueSuffix, withDirectoryLock, isMissingDirectory, sourcePriority, memFilePath, MemDirectoryKnowledgeRepository;
5043
+ var DOTMEM_DIRECTORY, DEFAULT_MEM_FILE, nodeFs5, repositoryLoadCaches, separator2, normalizePath, isAbsolutePath, resolvePath, joinPath2, parentPath, uniqueSuffix, isMissingDirectory, sourcePriority, memFilePath, MemDirectoryKnowledgeRepository;
4918
5044
  var init_mem_repository = __esm({
4919
5045
  "core/knowledge/mem-repository.ts"() {
4920
5046
  init_mem_codec();
4921
5047
  init_mem_package();
4922
5048
  init_state();
4923
5049
  init_acquisition_limits();
5050
+ init_storage_coordinator();
4924
5051
  DOTMEM_DIRECTORY = ".dotmem";
4925
5052
  DEFAULT_MEM_FILE = "project.mem";
4926
- directoryLocks = /* @__PURE__ */ new Map();
4927
- nodeFs4 = /* @__PURE__ */ __name(async () => {
5053
+ nodeFs5 = /* @__PURE__ */ __name(async () => {
4928
5054
  return import('fs/promises');
4929
5055
  }, "nodeFs");
5056
+ repositoryLoadCaches = /* @__PURE__ */ new Map();
4930
5057
  separator2 = /* @__PURE__ */ __name(() => process.platform === "win32" ? "\\" : "/", "separator");
4931
5058
  normalizePath = /* @__PURE__ */ __name((value) => value.replace(/[\\/]+/g, separator2()), "normalizePath");
4932
5059
  isAbsolutePath = /* @__PURE__ */ __name((value) => /^(?:[A-Za-z]:[\\/]|[\\/])/.test(value), "isAbsolutePath");
@@ -4938,25 +5065,8 @@ var init_mem_repository = __esm({
4938
5065
  return index <= 0 ? "." : normalized.slice(0, index);
4939
5066
  }, "parentPath");
4940
5067
  uniqueSuffix = /* @__PURE__ */ __name(() => globalThis.crypto?.randomUUID?.() ?? `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`, "uniqueSuffix");
4941
- withDirectoryLock = /* @__PURE__ */ __name(async (directoryPath, operation) => {
4942
- const previous = directoryLocks.get(directoryPath) ?? Promise.resolve();
4943
- let release = /* @__PURE__ */ __name(() => void 0, "release");
4944
- const gate = new Promise((resolveGate) => {
4945
- release = resolveGate;
4946
- });
4947
- const queued = previous.then(() => gate);
4948
- directoryLocks.set(directoryPath, queued);
4949
- await previous;
4950
- try {
4951
- return await operation();
4952
- } finally {
4953
- release();
4954
- if (directoryLocks.get(directoryPath) === queued) directoryLocks.delete(directoryPath);
4955
- }
4956
- }, "withDirectoryLock");
4957
5068
  isMissingDirectory = /* @__PURE__ */ __name((error) => error.code === "ENOENT", "isMissingDirectory");
4958
5069
  sourcePriority = /* @__PURE__ */ __name((sourceId, scope) => {
4959
- if (sourceId.endsWith(".mem") && sourceId !== "project.mem" && sourceId !== "user.mem" && sourceId !== "agent.mem") return 0;
4960
5070
  if (scope === "project" || sourceId === "project.mem") return 4;
4961
5071
  if (scope === "user" || sourceId === "user.mem") return 3;
4962
5072
  if (sourceId === "agent.mem" || sourceId === "agent") return 2;
@@ -4984,7 +5094,7 @@ var init_mem_repository = __esm({
4984
5094
  this.loadWritableOnly = loadWritableOnly;
4985
5095
  }
4986
5096
  async discover() {
4987
- const { readdir } = await nodeFs4();
5097
+ const { readdir } = await nodeFs5();
4988
5098
  try {
4989
5099
  const entries2 = await readdir(this.directoryPath, { withFileTypes: true });
4990
5100
  return entries2.filter((entry) => entry.isFile() && entry.name.endsWith(".mem") && entry.name !== "@brain.mem").map((entry) => entry.name).sort((a, b) => a.localeCompare(b));
@@ -4999,7 +5109,7 @@ var init_mem_repository = __esm({
4999
5109
  * receive a clear failure instead of discovering corruption on first use.
5000
5110
  */
5001
5111
  async initialize() {
5002
- const { mkdir } = await nodeFs4();
5112
+ const { mkdir } = await nodeFs5();
5003
5113
  await mkdir(this.directoryPath, { recursive: true });
5004
5114
  await this.load();
5005
5115
  }
@@ -5014,7 +5124,7 @@ var init_mem_repository = __esm({
5014
5124
  * that does not delete is not a delete.
5015
5125
  */
5016
5126
  async clearWritable() {
5017
- const { rm, readdir } = await nodeFs4();
5127
+ const { rm, readdir } = await nodeFs5();
5018
5128
  const filePath = memFilePath(this.directoryPath, this.writableSourceId);
5019
5129
  await rm(filePath, { force: true });
5020
5130
  await rm(`${filePath}.bak`, { force: true });
@@ -5031,15 +5141,28 @@ var init_mem_repository = __esm({
5031
5141
  }
5032
5142
  }
5033
5143
  async load() {
5034
- const loaded = this.loadWritableOnly ? await this.loadSources([this.writableSourceId]) : await this.loadSources();
5035
- return this.combine(loaded);
5144
+ const cacheKey = `${this.directoryPath}:${this.writableSourceId}:${this.loadWritableOnly}`;
5145
+ for (let attempt = 0; attempt < 5; attempt += 1) {
5146
+ const generationBefore = await readStorageGeneration(this.directoryPath);
5147
+ const cached = repositoryLoadCaches.get(cacheKey);
5148
+ if (cached && cached.signature === String(generationBefore)) {
5149
+ return structuredClone(cached.state);
5150
+ }
5151
+ const loaded = this.loadWritableOnly ? await this.loadSources([this.writableSourceId]) : await this.loadSources();
5152
+ const state2 = this.combine(loaded);
5153
+ const generationAfter = await readStorageGeneration(this.directoryPath);
5154
+ if (generationBefore !== generationAfter && attempt < 4) continue;
5155
+ repositoryLoadCaches.set(cacheKey, { signature: String(generationAfter), state: structuredClone(state2) });
5156
+ return state2;
5157
+ }
5158
+ throw new Error("Unable to load stable repository state");
5036
5159
  }
5037
- async save(state2, expectedVersion) {
5160
+ async save(state2, expectedVersion, locked = false) {
5038
5161
  const validated = validateKnowledgeState(state2);
5039
5162
  if (!Number.isInteger(expectedVersion) || expectedVersion < 0 || validated.version !== expectedVersion + 1) {
5040
5163
  throw new Error("Invalid expected persisted version");
5041
5164
  }
5042
- await withDirectoryLock(this.directoryPath, async () => {
5165
+ const doWrite = /* @__PURE__ */ __name(async () => {
5043
5166
  const loaded = await this.loadSources();
5044
5167
  const writable = loaded.find((source) => source.id === this.writableSourceId) ?? {
5045
5168
  id: this.writableSourceId,
@@ -5061,10 +5184,16 @@ var init_mem_repository = __esm({
5061
5184
  claims: [...writable.state.claims, ...newClaims]
5062
5185
  };
5063
5186
  await this.writeSource(writable.filePath, nextWritable, writable.preserved, writable.package);
5064
- });
5187
+ await writeStorageGeneration(this.directoryPath, await readStorageGeneration(this.directoryPath) + 1);
5188
+ }, "doWrite");
5189
+ if (locked) {
5190
+ await doWrite();
5191
+ } else {
5192
+ await withStorageLock(this.directoryPath, doWrite);
5193
+ }
5065
5194
  }
5066
5195
  async loadSources(sourceIds) {
5067
- const { readFile: readFile5 } = await nodeFs4();
5196
+ const { readFile: readFile5 } = await nodeFs5();
5068
5197
  const ids = sourceIds ?? await this.discover();
5069
5198
  const sources = [];
5070
5199
  for (const sourceId of ids) {
@@ -5154,7 +5283,7 @@ var init_mem_repository = __esm({
5154
5283
  * valid package, so the backup is always a version this library can read.
5155
5284
  */
5156
5285
  async keepLastValidVersion(filePath) {
5157
- const { readFile: readFile5, copyFile, rename, rm } = await nodeFs4();
5286
+ const { readFile: readFile5, copyFile, rename, rm } = await nodeFs5();
5158
5287
  let current;
5159
5288
  try {
5160
5289
  current = await readFile5(filePath);
@@ -5178,7 +5307,7 @@ var init_mem_repository = __esm({
5178
5307
  }
5179
5308
  /** Best-effort directory sync: the rename is durable once the directory is. */
5180
5309
  async syncDirectory(directoryPath) {
5181
- const { open } = await nodeFs4();
5310
+ const { open } = await nodeFs5();
5182
5311
  try {
5183
5312
  const handle = await open(directoryPath, "r");
5184
5313
  try {
@@ -5190,7 +5319,7 @@ var init_mem_repository = __esm({
5190
5319
  }
5191
5320
  }
5192
5321
  async writeSource(filePath, state2, preserved, preservedPackage) {
5193
- const { mkdir, rename, rm, open, readFile: readFile5 } = await nodeFs4();
5322
+ const { mkdir, rename, rm, open, readFile: readFile5 } = await nodeFs5();
5194
5323
  if (!Number.isInteger(state2.version) || state2.version < 0) throw new Error("Invalid writable .mem knowledge version");
5195
5324
  for (const claim of state2.claims) validateClaim(claim);
5196
5325
  await mkdir(parentPath(filePath), { recursive: true });
@@ -5677,7 +5806,7 @@ var init_project_identity = __esm({
5677
5806
  });
5678
5807
 
5679
5808
  // core/knowledge/staging.ts
5680
- var DOTMEM_STAGE_FORMAT, DOTMEM_STAGE_VERSION, StageFileError, generateRequestId, nodeCwd, resolveStagingDirectory, stagePathFor, nodeFs5, normalizeClaimIds, revisionFromOptions, writeStageFile, readStageFile, validateStageFile, commitStageFile, cleanupStageFile;
5809
+ var DOTMEM_STAGE_FORMAT, DOTMEM_STAGE_VERSION, StageFileError, generateRequestId, nodeCwd, resolveStagingDirectory, stagePathFor, nodeFs6, normalizeClaimIds, revisionFromOptions, writeStageFile, readStageFile, validateStageFile, commitStageFile, cleanupStageFile;
5681
5810
  var init_staging = __esm({
5682
5811
  "core/knowledge/staging.ts"() {
5683
5812
  init_claim_record();
@@ -5712,7 +5841,7 @@ var init_staging = __esm({
5712
5841
  return `${base}/dotmem`;
5713
5842
  }, "resolveStagingDirectory");
5714
5843
  stagePathFor = /* @__PURE__ */ __name((requestId) => `${resolveStagingDirectory()}/${requestId}.stage`, "stagePathFor");
5715
- nodeFs5 = /* @__PURE__ */ __name(async () => import('fs/promises'), "nodeFs");
5844
+ nodeFs6 = /* @__PURE__ */ __name(async () => import('fs/promises'), "nodeFs");
5716
5845
  normalizeClaimIds = /* @__PURE__ */ __name((value) => {
5717
5846
  if (!value) return [];
5718
5847
  return Array.isArray(value) ? value : [value];
@@ -5737,7 +5866,7 @@ var init_staging = __esm({
5737
5866
  };
5738
5867
  const requestId = generateRequestId();
5739
5868
  const stagePath = stagePathFor(requestId);
5740
- const fs = await nodeFs5();
5869
+ const fs = await nodeFs6();
5741
5870
  await fs.mkdir(resolveStagingDirectory(), { recursive: true });
5742
5871
  const stageFile = {
5743
5872
  format: DOTMEM_STAGE_FORMAT,
@@ -5757,7 +5886,7 @@ var init_staging = __esm({
5757
5886
  }, "writeStageFile");
5758
5887
  readStageFile = /* @__PURE__ */ __name(async (requestId) => {
5759
5888
  const stagePath = stagePathFor(requestId);
5760
- const fs = await nodeFs5();
5889
+ const fs = await nodeFs6();
5761
5890
  try {
5762
5891
  const content = await fs.readFile(stagePath, "utf8");
5763
5892
  return JSON.parse(content);
@@ -5868,7 +5997,7 @@ var init_staging = __esm({
5868
5997
  }, "commitStageFile");
5869
5998
  cleanupStageFile = /* @__PURE__ */ __name(async (requestId) => {
5870
5999
  const stagePath = stagePathFor(requestId);
5871
- const fs = await nodeFs5();
6000
+ const fs = await nodeFs6();
5872
6001
  try {
5873
6002
  await fs.unlink(stagePath);
5874
6003
  } catch (error) {
@@ -6690,6 +6819,7 @@ function _inferConfidence(claim) {
6690
6819
  var LOGIC_MEM_FILE, nodeCwd2, cachedRuntime, LogicEngine, logic, logic_default;
6691
6820
  var init_logic = __esm({
6692
6821
  "core/logic/index.ts"() {
6822
+ init_suppress_idb_warning();
6693
6823
  init_knowledge();
6694
6824
  init_mem_repository();
6695
6825
  init_acquisition_boundary();
@@ -7053,11 +7183,18 @@ var init_note_types = __esm({
7053
7183
  }
7054
7184
  });
7055
7185
 
7186
+ // core/mcp/index.ts
7187
+ init_suppress_idb_warning();
7188
+
7056
7189
  // package.json
7057
7190
  var package_default = {
7058
- version: "0.0.8"};
7191
+ version: "0.1.0"};
7192
+
7193
+ // core/memory/index.ts
7194
+ init_suppress_idb_warning();
7059
7195
 
7060
7196
  // core/memory/journal.ts
7197
+ init_suppress_idb_warning();
7061
7198
  init_env();
7062
7199
  var g = globalThis;
7063
7200
  function namespace() {
@@ -7264,6 +7401,7 @@ var journal_default = journal;
7264
7401
  init_brain();
7265
7402
 
7266
7403
  // core/memory/knowledge-runtime.ts
7404
+ init_suppress_idb_warning();
7267
7405
  init_knowledge();
7268
7406
  init_project_identity();
7269
7407
  init_user_paths();
@@ -7661,6 +7799,7 @@ var policy = {
7661
7799
  };
7662
7800
 
7663
7801
  // core/memory/entries.ts
7802
+ init_suppress_idb_warning();
7664
7803
  init_env();
7665
7804
  var MEMORY_INDEX_KEY = "__dotmem_memory_index__";
7666
7805
  var SUPERSEDED_PREFIX = "__dotmem_superseded__";
@@ -8205,15 +8344,18 @@ var context = {
8205
8344
  };
8206
8345
 
8207
8346
  // core/memory/acquire.ts
8347
+ init_suppress_idb_warning();
8208
8348
  init_env();
8209
8349
  init_project_identity();
8210
8350
  init_project_anchor();
8211
8351
  init_acquisition_boundary();
8212
8352
  init_claim_record();
8353
+ init_state();
8213
8354
  init_router();
8214
8355
  init_store_queue();
8215
8356
  init_staging();
8216
8357
  init_user_paths();
8358
+ init_storage_coordinator();
8217
8359
  init_mem_repository();
8218
8360
  function normalizeClaimIds3(value) {
8219
8361
  if (!value) return [];
@@ -8291,23 +8433,27 @@ var acquire = {
8291
8433
  );
8292
8434
  }
8293
8435
  const revision = revisionFromAcquire2(opts);
8436
+ const repository = target.repository;
8294
8437
  await withStoreQueue(acquisitionStoreKey(kind, scope, sourceProject), async () => {
8295
- const state2 = await target.runtime.select([]);
8296
- await target.runtime.update({
8297
- basedOnVersion: state2.context.knowledgeVersion,
8298
- claims: [buildClaimRecord({
8299
- id: opts.id,
8300
- knowledge: boundary.knowledge,
8301
- applicability: opts.applicability,
8302
- evidence: opts.evidence,
8303
- epistemicType: opts.epistemicType,
8304
- provenance: opts.provenance,
8305
- acquiredAt: opts.acquiredAt,
8306
- revision,
8307
- scope,
8308
- sourceProject,
8309
- kind
8310
- })]
8438
+ await withStorageLock(repository.directoryPath, async () => {
8439
+ const state2 = await repository.load();
8440
+ const next = updateKnowledge(state2, {
8441
+ basedOnVersion: state2.version,
8442
+ claims: [buildClaimRecord({
8443
+ id: opts.id,
8444
+ knowledge: boundary.knowledge,
8445
+ applicability: opts.applicability,
8446
+ evidence: opts.evidence,
8447
+ epistemicType: opts.epistemicType,
8448
+ provenance: opts.provenance,
8449
+ acquiredAt: opts.acquiredAt,
8450
+ revision,
8451
+ scope,
8452
+ sourceProject,
8453
+ kind
8454
+ })]
8455
+ });
8456
+ await repository.save(next, state2.version, true);
8311
8457
  });
8312
8458
  });
8313
8459
  const verification = await target.repository.load();