dotmem 0.0.8 → 0.0.9
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/AGENTS.md +12 -0
- package/JOY.md +55 -0
- package/README.md +74 -291
- package/cli.cjs +180 -55
- package/cli.js +180 -55
- package/global.cjs +176 -55
- package/global.js +176 -55
- package/index.cjs +1867 -63
- package/index.js +1839 -56
- package/llms.txt +92 -8
- package/mcp.cjs +177 -56
- package/mcp.js +177 -56
- package/package.json +1 -1
package/llms.txt
CHANGED
|
@@ -1,6 +1,90 @@
|
|
|
1
|
-
dotmem
|
|
1
|
+
dotmem — AI memory layer for agents and apps (v0.0.8).
|
|
2
2
|
Storage root: <user-home>/.mem (override: DOTMEM_STORAGE_ROOT)
|
|
3
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.
|
|
87
|
+
|
|
4
88
|
=== Installation ===
|
|
5
89
|
npm i dotmem
|
|
6
90
|
bun 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
|
|
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
|
|
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'
|
|
166
|
-
'precondition'
|
|
167
|
-
'hard-precondition'
|
|
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
|
|
328
|
+
Acquire → Discover → Project
|
|
245
329
|
|
|
246
330
|
Claims (AcquireOptions):
|
|
247
331
|
- id: string (unique, deterministic)
|
|
@@ -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
|
|
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
|
@@ -2406,6 +2406,7 @@ var init_policy_store = __esm({
|
|
|
2406
2406
|
if (entry.target !== void 0) built.target = entry.target;
|
|
2407
2407
|
if (entry.reason !== void 0) built.reason = entry.reason;
|
|
2408
2408
|
if (entry.detail !== void 0) built.detail = entry.detail;
|
|
2409
|
+
if (entry.mandatory !== void 0) built.mandatory = entry.mandatory;
|
|
2409
2410
|
return built;
|
|
2410
2411
|
}, "validatePolicyAuditEntry");
|
|
2411
2412
|
validatePolicyDocument = /* @__PURE__ */ __name((value) => {
|
|
@@ -2483,6 +2484,7 @@ var init_policy_store = __esm({
|
|
|
2483
2484
|
if (entry.target !== void 0) built.target = entry.target;
|
|
2484
2485
|
if (entry.reason !== void 0) built.reason = entry.reason;
|
|
2485
2486
|
if (entry.detail !== void 0) built.detail = entry.detail;
|
|
2487
|
+
if (entry.mandatory !== void 0) built.mandatory = entry.mandatory;
|
|
2486
2488
|
return built;
|
|
2487
2489
|
}, "buildAuditEntry");
|
|
2488
2490
|
boundedAudit = /* @__PURE__ */ __name((entries2, overrides) => {
|
|
@@ -2495,9 +2497,10 @@ var init_policy_store = __esm({
|
|
|
2495
2497
|
for (let index = entries2.length - 1; index >= 0; index--) {
|
|
2496
2498
|
const entry = entries2[index];
|
|
2497
2499
|
const activeLinked = entry.target !== void 0 && activeOverrideIds.has(entry.target) && (entry.action === "override" || entry.action === "revoke");
|
|
2498
|
-
|
|
2500
|
+
const mandatory = entry.mandatory === true;
|
|
2501
|
+
if (activeLinked || mandatory || budget > 0) {
|
|
2499
2502
|
kept.unshift(entry);
|
|
2500
|
-
if (!activeLinked) budget--;
|
|
2503
|
+
if (!activeLinked && !mandatory) budget--;
|
|
2501
2504
|
}
|
|
2502
2505
|
}
|
|
2503
2506
|
return kept;
|
|
@@ -2538,6 +2541,12 @@ var init_policy_store = __esm({
|
|
|
2538
2541
|
try {
|
|
2539
2542
|
await fs.writeFile(temporary, `${JSON.stringify(document, null, 2)}
|
|
2540
2543
|
`, "utf8");
|
|
2544
|
+
const handle = await fs.open(temporary, "r+");
|
|
2545
|
+
try {
|
|
2546
|
+
await handle.sync();
|
|
2547
|
+
} finally {
|
|
2548
|
+
await handle.close();
|
|
2549
|
+
}
|
|
2541
2550
|
await fs.rename(temporary, filePath);
|
|
2542
2551
|
} catch (error) {
|
|
2543
2552
|
await fs.rm(temporary, { force: true }).catch(() => void 0);
|
|
@@ -4874,6 +4883,109 @@ var init_mem_package = __esm({
|
|
|
4874
4883
|
}
|
|
4875
4884
|
});
|
|
4876
4885
|
|
|
4886
|
+
// core/storage-coordinator/index.ts
|
|
4887
|
+
var nodeFs4, withStorageLock, readStorageGeneration, writeStorageGeneration;
|
|
4888
|
+
var init_storage_coordinator = __esm({
|
|
4889
|
+
"core/storage-coordinator/index.ts"() {
|
|
4890
|
+
nodeFs4 = /* @__PURE__ */ __name(async () => import('fs/promises'), "nodeFs");
|
|
4891
|
+
withStorageLock = /* @__PURE__ */ __name(async (directoryPath, operationOrOptions, operation, _options = {}) => {
|
|
4892
|
+
const opts = typeof operationOrOptions === "function" ? operation ?? {} : operationOrOptions ?? {};
|
|
4893
|
+
const op = typeof operationOrOptions === "function" ? operationOrOptions : operation;
|
|
4894
|
+
const timeoutMs = opts.timeoutMs ?? 3e4;
|
|
4895
|
+
const staleMs = opts.staleMs ?? 6e4;
|
|
4896
|
+
const lockName = opts.lockName ?? ".storage.lock";
|
|
4897
|
+
const lockPath = `${directoryPath}/${lockName}`;
|
|
4898
|
+
const fs = await nodeFs4();
|
|
4899
|
+
const started = Date.now();
|
|
4900
|
+
await fs.mkdir(directoryPath, { recursive: true });
|
|
4901
|
+
while (true) {
|
|
4902
|
+
try {
|
|
4903
|
+
await fs.mkdir(lockPath);
|
|
4904
|
+
const token2 = `${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
4905
|
+
await fs.writeFile(`${lockPath}/owner.json`, JSON.stringify({ token: token2, pid: process.pid, createdAt: Date.now() }), { flag: "wx" });
|
|
4906
|
+
break;
|
|
4907
|
+
} catch (error) {
|
|
4908
|
+
const code = error.code;
|
|
4909
|
+
if (code !== "EEXIST" && code !== "ENOENT" && code !== "EPERM" && code !== "ENOTEMPTY") throw error;
|
|
4910
|
+
let stale = false;
|
|
4911
|
+
let processDead = false;
|
|
4912
|
+
try {
|
|
4913
|
+
const raw = await fs.readFile(`${lockPath}/owner.json`, "utf8");
|
|
4914
|
+
const parsed = JSON.parse(raw);
|
|
4915
|
+
const ageStale = typeof parsed.createdAt === "number" && Date.now() - parsed.createdAt > staleMs;
|
|
4916
|
+
if (typeof parsed.pid === "number") {
|
|
4917
|
+
try {
|
|
4918
|
+
process.kill(parsed.pid, 0);
|
|
4919
|
+
} catch {
|
|
4920
|
+
processDead = true;
|
|
4921
|
+
}
|
|
4922
|
+
} else {
|
|
4923
|
+
processDead = true;
|
|
4924
|
+
}
|
|
4925
|
+
if (ageStale || processDead) stale = true;
|
|
4926
|
+
} catch {
|
|
4927
|
+
stale = true;
|
|
4928
|
+
}
|
|
4929
|
+
if (stale) {
|
|
4930
|
+
if (processDead) {
|
|
4931
|
+
try {
|
|
4932
|
+
await fs.rm(lockPath, { recursive: true, force: true });
|
|
4933
|
+
continue;
|
|
4934
|
+
} catch {
|
|
4935
|
+
}
|
|
4936
|
+
}
|
|
4937
|
+
try {
|
|
4938
|
+
await fs.rmdir(lockPath);
|
|
4939
|
+
continue;
|
|
4940
|
+
} catch {
|
|
4941
|
+
}
|
|
4942
|
+
}
|
|
4943
|
+
if (Date.now() - started > timeoutMs) {
|
|
4944
|
+
throw new Error(`Storage lock timeout for ${directoryPath}`);
|
|
4945
|
+
}
|
|
4946
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
4947
|
+
}
|
|
4948
|
+
}
|
|
4949
|
+
let token = "";
|
|
4950
|
+
try {
|
|
4951
|
+
const raw = await fs.readFile(`${lockPath}/owner.json`, "utf8");
|
|
4952
|
+
token = JSON.parse(raw).token ?? "";
|
|
4953
|
+
} catch {
|
|
4954
|
+
token = "";
|
|
4955
|
+
}
|
|
4956
|
+
try {
|
|
4957
|
+
return await op();
|
|
4958
|
+
} finally {
|
|
4959
|
+
try {
|
|
4960
|
+
const raw = await fs.readFile(`${lockPath}/owner.json`, "utf8");
|
|
4961
|
+
const parsed = JSON.parse(raw);
|
|
4962
|
+
if (parsed.token === token && token) {
|
|
4963
|
+
await fs.rm(lockPath, { recursive: true, force: true });
|
|
4964
|
+
}
|
|
4965
|
+
} catch {
|
|
4966
|
+
}
|
|
4967
|
+
}
|
|
4968
|
+
}, "withStorageLock");
|
|
4969
|
+
readStorageGeneration = /* @__PURE__ */ __name(async (directoryPath) => {
|
|
4970
|
+
const fs = await nodeFs4();
|
|
4971
|
+
try {
|
|
4972
|
+
const raw = await fs.readFile(`${directoryPath}/.storage.generation`, "utf8");
|
|
4973
|
+
const value = Number(raw.trim());
|
|
4974
|
+
return Number.isFinite(value) && value >= 0 ? value : 0;
|
|
4975
|
+
} catch {
|
|
4976
|
+
return 0;
|
|
4977
|
+
}
|
|
4978
|
+
}, "readStorageGeneration");
|
|
4979
|
+
writeStorageGeneration = /* @__PURE__ */ __name(async (directoryPath, value) => {
|
|
4980
|
+
const fs = await nodeFs4();
|
|
4981
|
+
await fs.mkdir(directoryPath, { recursive: true });
|
|
4982
|
+
const temp = `${directoryPath}/.storage.generation.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
4983
|
+
await fs.writeFile(temp, String(value));
|
|
4984
|
+
await fs.rename(temp, `${directoryPath}/.storage.generation`);
|
|
4985
|
+
}, "writeStorageGeneration");
|
|
4986
|
+
}
|
|
4987
|
+
});
|
|
4988
|
+
|
|
4877
4989
|
// core/knowledge/mem-repository.ts
|
|
4878
4990
|
function resolveDuplicate(existing, existingSource, current, currentSource) {
|
|
4879
4991
|
const existingRetracts = existing.revision?.kind === "retracts";
|
|
@@ -4914,19 +5026,20 @@ async function parseMemFile(value, sourceId) {
|
|
|
4914
5026
|
throw new Error(`${message} in ${sourceId}`);
|
|
4915
5027
|
}
|
|
4916
5028
|
}
|
|
4917
|
-
var DOTMEM_DIRECTORY, DEFAULT_MEM_FILE,
|
|
5029
|
+
var DOTMEM_DIRECTORY, DEFAULT_MEM_FILE, nodeFs5, repositoryLoadCaches, separator2, normalizePath, isAbsolutePath, resolvePath, joinPath2, parentPath, uniqueSuffix, isMissingDirectory, sourcePriority, memFilePath, MemDirectoryKnowledgeRepository;
|
|
4918
5030
|
var init_mem_repository = __esm({
|
|
4919
5031
|
"core/knowledge/mem-repository.ts"() {
|
|
4920
5032
|
init_mem_codec();
|
|
4921
5033
|
init_mem_package();
|
|
4922
5034
|
init_state();
|
|
4923
5035
|
init_acquisition_limits();
|
|
5036
|
+
init_storage_coordinator();
|
|
4924
5037
|
DOTMEM_DIRECTORY = ".dotmem";
|
|
4925
5038
|
DEFAULT_MEM_FILE = "project.mem";
|
|
4926
|
-
|
|
4927
|
-
nodeFs4 = /* @__PURE__ */ __name(async () => {
|
|
5039
|
+
nodeFs5 = /* @__PURE__ */ __name(async () => {
|
|
4928
5040
|
return import('fs/promises');
|
|
4929
5041
|
}, "nodeFs");
|
|
5042
|
+
repositoryLoadCaches = /* @__PURE__ */ new Map();
|
|
4930
5043
|
separator2 = /* @__PURE__ */ __name(() => process.platform === "win32" ? "\\" : "/", "separator");
|
|
4931
5044
|
normalizePath = /* @__PURE__ */ __name((value) => value.replace(/[\\/]+/g, separator2()), "normalizePath");
|
|
4932
5045
|
isAbsolutePath = /* @__PURE__ */ __name((value) => /^(?:[A-Za-z]:[\\/]|[\\/])/.test(value), "isAbsolutePath");
|
|
@@ -4938,25 +5051,8 @@ var init_mem_repository = __esm({
|
|
|
4938
5051
|
return index <= 0 ? "." : normalized.slice(0, index);
|
|
4939
5052
|
}, "parentPath");
|
|
4940
5053
|
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
5054
|
isMissingDirectory = /* @__PURE__ */ __name((error) => error.code === "ENOENT", "isMissingDirectory");
|
|
4958
5055
|
sourcePriority = /* @__PURE__ */ __name((sourceId, scope) => {
|
|
4959
|
-
if (sourceId.endsWith(".mem") && sourceId !== "project.mem" && sourceId !== "user.mem" && sourceId !== "agent.mem") return 0;
|
|
4960
5056
|
if (scope === "project" || sourceId === "project.mem") return 4;
|
|
4961
5057
|
if (scope === "user" || sourceId === "user.mem") return 3;
|
|
4962
5058
|
if (sourceId === "agent.mem" || sourceId === "agent") return 2;
|
|
@@ -4984,7 +5080,7 @@ var init_mem_repository = __esm({
|
|
|
4984
5080
|
this.loadWritableOnly = loadWritableOnly;
|
|
4985
5081
|
}
|
|
4986
5082
|
async discover() {
|
|
4987
|
-
const { readdir } = await
|
|
5083
|
+
const { readdir } = await nodeFs5();
|
|
4988
5084
|
try {
|
|
4989
5085
|
const entries2 = await readdir(this.directoryPath, { withFileTypes: true });
|
|
4990
5086
|
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 +5095,7 @@ var init_mem_repository = __esm({
|
|
|
4999
5095
|
* receive a clear failure instead of discovering corruption on first use.
|
|
5000
5096
|
*/
|
|
5001
5097
|
async initialize() {
|
|
5002
|
-
const { mkdir } = await
|
|
5098
|
+
const { mkdir } = await nodeFs5();
|
|
5003
5099
|
await mkdir(this.directoryPath, { recursive: true });
|
|
5004
5100
|
await this.load();
|
|
5005
5101
|
}
|
|
@@ -5014,7 +5110,7 @@ var init_mem_repository = __esm({
|
|
|
5014
5110
|
* that does not delete is not a delete.
|
|
5015
5111
|
*/
|
|
5016
5112
|
async clearWritable() {
|
|
5017
|
-
const { rm, readdir } = await
|
|
5113
|
+
const { rm, readdir } = await nodeFs5();
|
|
5018
5114
|
const filePath = memFilePath(this.directoryPath, this.writableSourceId);
|
|
5019
5115
|
await rm(filePath, { force: true });
|
|
5020
5116
|
await rm(`${filePath}.bak`, { force: true });
|
|
@@ -5031,15 +5127,28 @@ var init_mem_repository = __esm({
|
|
|
5031
5127
|
}
|
|
5032
5128
|
}
|
|
5033
5129
|
async load() {
|
|
5034
|
-
const
|
|
5035
|
-
|
|
5130
|
+
const cacheKey = `${this.directoryPath}:${this.writableSourceId}:${this.loadWritableOnly}`;
|
|
5131
|
+
for (let attempt = 0; attempt < 5; attempt += 1) {
|
|
5132
|
+
const generationBefore = await readStorageGeneration(this.directoryPath);
|
|
5133
|
+
const cached = repositoryLoadCaches.get(cacheKey);
|
|
5134
|
+
if (cached && cached.signature === String(generationBefore)) {
|
|
5135
|
+
return structuredClone(cached.state);
|
|
5136
|
+
}
|
|
5137
|
+
const loaded = this.loadWritableOnly ? await this.loadSources([this.writableSourceId]) : await this.loadSources();
|
|
5138
|
+
const state2 = this.combine(loaded);
|
|
5139
|
+
const generationAfter = await readStorageGeneration(this.directoryPath);
|
|
5140
|
+
if (generationBefore !== generationAfter && attempt < 4) continue;
|
|
5141
|
+
repositoryLoadCaches.set(cacheKey, { signature: String(generationAfter), state: structuredClone(state2) });
|
|
5142
|
+
return state2;
|
|
5143
|
+
}
|
|
5144
|
+
throw new Error("Unable to load stable repository state");
|
|
5036
5145
|
}
|
|
5037
|
-
async save(state2, expectedVersion) {
|
|
5146
|
+
async save(state2, expectedVersion, locked = false) {
|
|
5038
5147
|
const validated = validateKnowledgeState(state2);
|
|
5039
5148
|
if (!Number.isInteger(expectedVersion) || expectedVersion < 0 || validated.version !== expectedVersion + 1) {
|
|
5040
5149
|
throw new Error("Invalid expected persisted version");
|
|
5041
5150
|
}
|
|
5042
|
-
|
|
5151
|
+
const doWrite = /* @__PURE__ */ __name(async () => {
|
|
5043
5152
|
const loaded = await this.loadSources();
|
|
5044
5153
|
const writable = loaded.find((source) => source.id === this.writableSourceId) ?? {
|
|
5045
5154
|
id: this.writableSourceId,
|
|
@@ -5061,10 +5170,16 @@ var init_mem_repository = __esm({
|
|
|
5061
5170
|
claims: [...writable.state.claims, ...newClaims]
|
|
5062
5171
|
};
|
|
5063
5172
|
await this.writeSource(writable.filePath, nextWritable, writable.preserved, writable.package);
|
|
5064
|
-
|
|
5173
|
+
await writeStorageGeneration(this.directoryPath, await readStorageGeneration(this.directoryPath) + 1);
|
|
5174
|
+
}, "doWrite");
|
|
5175
|
+
if (locked) {
|
|
5176
|
+
await doWrite();
|
|
5177
|
+
} else {
|
|
5178
|
+
await withStorageLock(this.directoryPath, doWrite);
|
|
5179
|
+
}
|
|
5065
5180
|
}
|
|
5066
5181
|
async loadSources(sourceIds) {
|
|
5067
|
-
const { readFile: readFile5 } = await
|
|
5182
|
+
const { readFile: readFile5 } = await nodeFs5();
|
|
5068
5183
|
const ids = sourceIds ?? await this.discover();
|
|
5069
5184
|
const sources = [];
|
|
5070
5185
|
for (const sourceId of ids) {
|
|
@@ -5154,7 +5269,7 @@ var init_mem_repository = __esm({
|
|
|
5154
5269
|
* valid package, so the backup is always a version this library can read.
|
|
5155
5270
|
*/
|
|
5156
5271
|
async keepLastValidVersion(filePath) {
|
|
5157
|
-
const { readFile: readFile5, copyFile, rename, rm } = await
|
|
5272
|
+
const { readFile: readFile5, copyFile, rename, rm } = await nodeFs5();
|
|
5158
5273
|
let current;
|
|
5159
5274
|
try {
|
|
5160
5275
|
current = await readFile5(filePath);
|
|
@@ -5178,7 +5293,7 @@ var init_mem_repository = __esm({
|
|
|
5178
5293
|
}
|
|
5179
5294
|
/** Best-effort directory sync: the rename is durable once the directory is. */
|
|
5180
5295
|
async syncDirectory(directoryPath) {
|
|
5181
|
-
const { open } = await
|
|
5296
|
+
const { open } = await nodeFs5();
|
|
5182
5297
|
try {
|
|
5183
5298
|
const handle = await open(directoryPath, "r");
|
|
5184
5299
|
try {
|
|
@@ -5190,7 +5305,7 @@ var init_mem_repository = __esm({
|
|
|
5190
5305
|
}
|
|
5191
5306
|
}
|
|
5192
5307
|
async writeSource(filePath, state2, preserved, preservedPackage) {
|
|
5193
|
-
const { mkdir, rename, rm, open, readFile: readFile5 } = await
|
|
5308
|
+
const { mkdir, rename, rm, open, readFile: readFile5 } = await nodeFs5();
|
|
5194
5309
|
if (!Number.isInteger(state2.version) || state2.version < 0) throw new Error("Invalid writable .mem knowledge version");
|
|
5195
5310
|
for (const claim of state2.claims) validateClaim(claim);
|
|
5196
5311
|
await mkdir(parentPath(filePath), { recursive: true });
|
|
@@ -5677,7 +5792,7 @@ var init_project_identity = __esm({
|
|
|
5677
5792
|
});
|
|
5678
5793
|
|
|
5679
5794
|
// core/knowledge/staging.ts
|
|
5680
|
-
var DOTMEM_STAGE_FORMAT, DOTMEM_STAGE_VERSION, StageFileError, generateRequestId, nodeCwd, resolveStagingDirectory, stagePathFor,
|
|
5795
|
+
var DOTMEM_STAGE_FORMAT, DOTMEM_STAGE_VERSION, StageFileError, generateRequestId, nodeCwd, resolveStagingDirectory, stagePathFor, nodeFs6, normalizeClaimIds, revisionFromOptions, writeStageFile, readStageFile, validateStageFile, commitStageFile, cleanupStageFile;
|
|
5681
5796
|
var init_staging = __esm({
|
|
5682
5797
|
"core/knowledge/staging.ts"() {
|
|
5683
5798
|
init_claim_record();
|
|
@@ -5712,7 +5827,7 @@ var init_staging = __esm({
|
|
|
5712
5827
|
return `${base}/dotmem`;
|
|
5713
5828
|
}, "resolveStagingDirectory");
|
|
5714
5829
|
stagePathFor = /* @__PURE__ */ __name((requestId) => `${resolveStagingDirectory()}/${requestId}.stage`, "stagePathFor");
|
|
5715
|
-
|
|
5830
|
+
nodeFs6 = /* @__PURE__ */ __name(async () => import('fs/promises'), "nodeFs");
|
|
5716
5831
|
normalizeClaimIds = /* @__PURE__ */ __name((value) => {
|
|
5717
5832
|
if (!value) return [];
|
|
5718
5833
|
return Array.isArray(value) ? value : [value];
|
|
@@ -5737,7 +5852,7 @@ var init_staging = __esm({
|
|
|
5737
5852
|
};
|
|
5738
5853
|
const requestId = generateRequestId();
|
|
5739
5854
|
const stagePath = stagePathFor(requestId);
|
|
5740
|
-
const fs = await
|
|
5855
|
+
const fs = await nodeFs6();
|
|
5741
5856
|
await fs.mkdir(resolveStagingDirectory(), { recursive: true });
|
|
5742
5857
|
const stageFile = {
|
|
5743
5858
|
format: DOTMEM_STAGE_FORMAT,
|
|
@@ -5757,7 +5872,7 @@ var init_staging = __esm({
|
|
|
5757
5872
|
}, "writeStageFile");
|
|
5758
5873
|
readStageFile = /* @__PURE__ */ __name(async (requestId) => {
|
|
5759
5874
|
const stagePath = stagePathFor(requestId);
|
|
5760
|
-
const fs = await
|
|
5875
|
+
const fs = await nodeFs6();
|
|
5761
5876
|
try {
|
|
5762
5877
|
const content = await fs.readFile(stagePath, "utf8");
|
|
5763
5878
|
return JSON.parse(content);
|
|
@@ -5868,7 +5983,7 @@ var init_staging = __esm({
|
|
|
5868
5983
|
}, "commitStageFile");
|
|
5869
5984
|
cleanupStageFile = /* @__PURE__ */ __name(async (requestId) => {
|
|
5870
5985
|
const stagePath = stagePathFor(requestId);
|
|
5871
|
-
const fs = await
|
|
5986
|
+
const fs = await nodeFs6();
|
|
5872
5987
|
try {
|
|
5873
5988
|
await fs.unlink(stagePath);
|
|
5874
5989
|
} catch (error) {
|
|
@@ -7055,7 +7170,7 @@ var init_note_types = __esm({
|
|
|
7055
7170
|
|
|
7056
7171
|
// package.json
|
|
7057
7172
|
var package_default = {
|
|
7058
|
-
version: "0.0.
|
|
7173
|
+
version: "0.0.9"};
|
|
7059
7174
|
|
|
7060
7175
|
// core/memory/journal.ts
|
|
7061
7176
|
init_env();
|
|
@@ -8210,10 +8325,12 @@ init_project_identity();
|
|
|
8210
8325
|
init_project_anchor();
|
|
8211
8326
|
init_acquisition_boundary();
|
|
8212
8327
|
init_claim_record();
|
|
8328
|
+
init_state();
|
|
8213
8329
|
init_router();
|
|
8214
8330
|
init_store_queue();
|
|
8215
8331
|
init_staging();
|
|
8216
8332
|
init_user_paths();
|
|
8333
|
+
init_storage_coordinator();
|
|
8217
8334
|
init_mem_repository();
|
|
8218
8335
|
function normalizeClaimIds3(value) {
|
|
8219
8336
|
if (!value) return [];
|
|
@@ -8291,23 +8408,27 @@ var acquire = {
|
|
|
8291
8408
|
);
|
|
8292
8409
|
}
|
|
8293
8410
|
const revision = revisionFromAcquire2(opts);
|
|
8411
|
+
const repository = target.repository;
|
|
8294
8412
|
await withStoreQueue(acquisitionStoreKey(kind, scope, sourceProject), async () => {
|
|
8295
|
-
|
|
8296
|
-
|
|
8297
|
-
|
|
8298
|
-
|
|
8299
|
-
|
|
8300
|
-
|
|
8301
|
-
|
|
8302
|
-
|
|
8303
|
-
|
|
8304
|
-
|
|
8305
|
-
|
|
8306
|
-
|
|
8307
|
-
|
|
8308
|
-
|
|
8309
|
-
|
|
8310
|
-
|
|
8413
|
+
await withStorageLock(repository.directoryPath, async () => {
|
|
8414
|
+
const state2 = await repository.load();
|
|
8415
|
+
const next = updateKnowledge(state2, {
|
|
8416
|
+
basedOnVersion: state2.version,
|
|
8417
|
+
claims: [buildClaimRecord({
|
|
8418
|
+
id: opts.id,
|
|
8419
|
+
knowledge: boundary.knowledge,
|
|
8420
|
+
applicability: opts.applicability,
|
|
8421
|
+
evidence: opts.evidence,
|
|
8422
|
+
epistemicType: opts.epistemicType,
|
|
8423
|
+
provenance: opts.provenance,
|
|
8424
|
+
acquiredAt: opts.acquiredAt,
|
|
8425
|
+
revision,
|
|
8426
|
+
scope,
|
|
8427
|
+
sourceProject,
|
|
8428
|
+
kind
|
|
8429
|
+
})]
|
|
8430
|
+
});
|
|
8431
|
+
await repository.save(next, state2.version, true);
|
|
8311
8432
|
});
|
|
8312
8433
|
});
|
|
8313
8434
|
const verification = await target.repository.load();
|