dsh-mnemon-source-runtime 0.5.14 → 0.5.15

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/README.md CHANGED
@@ -21,6 +21,12 @@ Disabling participation does not delete stored entries. Source data remains sepa
21
21
 
22
22
  Model projections annotate each entry with its recorded importance and elapsed whole-day ages, for example `[importance=critical; created=14d; updated=2d]`, followed by the exact content. Future timestamps show `future`; unparseable ones show `unknown`. Ages are captured once per projection and remain fixed for that turn. Annotations count toward the Strategy's projection budget, but do not change stored JSON/Markdown, content matching or storage capacity. For replacement/removal, use only the entry content. Current instructions retain priority.
23
23
 
24
+ ## Storage files
25
+
26
+ The `./contracts` entry names this Source's files under a storage root (`runtime/memories.json`, `USER.md`, `MEMORY.md`, the `.memories.lock` lock and the `archived/` local archive) and exports the rules a copy of them follows: `runtimeProjection` and `runtimeBytes` write and count USER.md and MEMORY.md exactly as the controller does, and `summarizeRuntimeMemories` counts a stored file for an inventory. They are pure and browser-safe. The `dsh-mnemon` Starter's backup, sync and storage inventory use them instead of keeping copies.
27
+
28
+ The same entry exports the size limits the configuration and the controller apply: `RUNTIME_MEMORY_LIMITS`, the default limit of USER.md and of MEMORY.md, and `RUNTIME_MEMORY_LIMIT_MAX_BYTES`, the largest limit either may be given. The Starter's settings take their defaults and bounds from them.
29
+
24
30
  ## Source-owned UI and tests
25
31
 
26
32
  The optional `./client` entry is an ordinary DSH Client plugin. This package owns its pages and `presentation/` resources and uses `dsh-mnemon/client` for the shared frame and scoped management client. It never receives a Host Context, credentials or another Source's controller.
@@ -0,0 +1,81 @@
1
+ //#region src/defaults.ts
2
+ const DEFAULT_RUNTIME_MEMORY_LIMIT_BYTES = 10240;
3
+ const DEFAULT_RUNTIME_USER_LIMIT_BYTES = 4096;
4
+ const MAX_RUNTIME_MEMORY_LIMIT_BYTES = 1048576;
5
+ //#endregion
6
+ //#region src/contracts.ts
7
+ const RUNTIME_MEMORY_VERSION = 1;
8
+ const RUNTIME_ENTRY_DELIMITER = "\n§\n";
9
+ const RUNTIME_MEMORY_LIMITS = {
10
+ memory: DEFAULT_RUNTIME_MEMORY_LIMIT_BYTES,
11
+ user: DEFAULT_RUNTIME_USER_LIMIT_BYTES
12
+ };
13
+ /** The largest limit, in UTF-8 bytes, either target's file may be given. */
14
+ const RUNTIME_MEMORY_LIMIT_MAX_BYTES = MAX_RUNTIME_MEMORY_LIMIT_BYTES;
15
+ /** The longest git branch name a scope may hold. */
16
+ const RUNTIME_BRANCH_NAME_MAX = 128;
17
+ /** The characters a git branch name in a scope may use. */
18
+ const RUNTIME_BRANCH_NAME_PATTERN = /^[A-Za-z0-9._/-]+$/u;
19
+ /**
20
+ * Read a stored branch scope. Absent or malformed data reads as no scope, so an entry that
21
+ * a hand edit or another installation left malformed is projected on every branch instead
22
+ * of breaking the projection that reads it.
23
+ */
24
+ function parseRuntimeBranches(value) {
25
+ if (!Array.isArray(value)) return void 0;
26
+ const branches = [];
27
+ for (const item of value) {
28
+ if (typeof item !== "string" || item === "" || item.length > 128 || !RUNTIME_BRANCH_NAME_PATTERN.test(item)) return void 0;
29
+ branches.push(item);
30
+ }
31
+ return branches;
32
+ }
33
+ /** The Runtime Source's directory under a storage root. */
34
+ const RUNTIME_DIRECTORY = "runtime";
35
+ /** The entries themselves, the source of truth the projections are written from. */
36
+ const RUNTIME_MEMORIES_FILE = "memories.json";
37
+ /** Each target's projection: its entries' plain content, the file a person reads. */
38
+ const RUNTIME_PROJECTION_FILES = {
39
+ user: "USER.md",
40
+ memory: "MEMORY.md"
41
+ };
42
+ /** Created exclusively while the files above are read or written; whoever copies them takes it too. */
43
+ const RUNTIME_LOCK_FILE = ".memories.lock";
44
+ /** A lock file older than this was left by a process that stopped, and may be taken over. */
45
+ const RUNTIME_LOCK_STALE_MS = 3e4;
46
+ /** The local archive of entries that left MEMORY.md while no Memory Space could take them (#336). */
47
+ const RUNTIME_ARCHIVE_DIRECTORY = "archived";
48
+ /** The longest content one entry may hold, in UTF-8 bytes. */
49
+ const RUNTIME_ENTRY_MAX_BYTES = 8192;
50
+ let utf8;
51
+ function joinedContent(entries, target) {
52
+ return entries.filter((entry) => entry.target === target).map((entry) => entry.content).join(RUNTIME_ENTRY_DELIMITER);
53
+ }
54
+ /** The UTF-8 bytes one target's entries take against its limit: their contents joined by the delimiter. */
55
+ function runtimeBytes(entries, target) {
56
+ utf8 ??= new TextEncoder();
57
+ return utf8.encode(joinedContent(entries, target)).byteLength;
58
+ }
59
+ /** One target's projection file, USER.md or MEMORY.md, exactly as the Runtime Source writes it. */
60
+ function runtimeProjection(entries, target) {
61
+ const content = joinedContent(entries, target);
62
+ return content === "" ? "" : `${content}\n`;
63
+ }
64
+ function isObject(value) {
65
+ return typeof value === "object" && value !== null && !Array.isArray(value);
66
+ }
67
+ /**
68
+ * Count the entries of a parsed memories.json for a read-only inventory. It never throws: a file
69
+ * without an entry list is not `valid`, and a malformed entry is counted or skipped, never refused.
70
+ */
71
+ function summarizeRuntimeMemories(value) {
72
+ const entries = isObject(value) && Array.isArray(value.entries) ? value.entries.filter(isObject) : [];
73
+ return {
74
+ valid: isObject(value) && Array.isArray(value.entries),
75
+ entries: entries.length,
76
+ user: entries.filter((entry) => entry.target === "user").length,
77
+ memory: entries.filter((entry) => entry.target === "memory").length
78
+ };
79
+ }
80
+ //#endregion
81
+ export { summarizeRuntimeMemories as _, RUNTIME_ENTRY_DELIMITER as a, MAX_RUNTIME_MEMORY_LIMIT_BYTES as b, RUNTIME_LOCK_STALE_MS as c, RUNTIME_MEMORY_LIMIT_MAX_BYTES as d, RUNTIME_MEMORY_VERSION as f, runtimeProjection as g, runtimeBytes as h, RUNTIME_DIRECTORY as i, RUNTIME_MEMORIES_FILE as l, parseRuntimeBranches as m, RUNTIME_BRANCH_NAME_MAX as n, RUNTIME_ENTRY_MAX_BYTES as o, RUNTIME_PROJECTION_FILES as p, RUNTIME_BRANCH_NAME_PATTERN as r, RUNTIME_LOCK_FILE as s, RUNTIME_ARCHIVE_DIRECTORY as t, RUNTIME_MEMORY_LIMITS as u, DEFAULT_RUNTIME_MEMORY_LIMIT_BYTES as v, DEFAULT_RUNTIME_USER_LIMIT_BYTES as y };
@@ -97,6 +97,8 @@ interface RuntimeMemoryLimits {
97
97
  readonly user: number;
98
98
  }
99
99
  declare const RUNTIME_MEMORY_LIMITS: RuntimeMemoryLimits;
100
+ /** The largest limit, in UTF-8 bytes, either target's file may be given. */
101
+ declare const RUNTIME_MEMORY_LIMIT_MAX_BYTES: number;
100
102
  /** The longest git branch name a scope may hold. */
101
103
  declare const RUNTIME_BRANCH_NAME_MAX = 128;
102
104
  /** The characters a git branch name in a scope may use. */
@@ -107,5 +109,41 @@ declare const RUNTIME_BRANCH_NAME_PATTERN: RegExp;
107
109
  * of breaking the projection that reads it.
108
110
  */
109
111
  declare function parseRuntimeBranches(value: unknown): string[] | undefined;
112
+ /** The Runtime Source's directory under a storage root. */
113
+ declare const RUNTIME_DIRECTORY = "runtime";
114
+ /** The entries themselves, the source of truth the projections are written from. */
115
+ declare const RUNTIME_MEMORIES_FILE = "memories.json";
116
+ /** Each target's projection: its entries' plain content, the file a person reads. */
117
+ declare const RUNTIME_PROJECTION_FILES: {
118
+ readonly user: "USER.md";
119
+ readonly memory: "MEMORY.md";
120
+ };
121
+ /** Created exclusively while the files above are read or written; whoever copies them takes it too. */
122
+ declare const RUNTIME_LOCK_FILE = ".memories.lock";
123
+ /** A lock file older than this was left by a process that stopped, and may be taken over. */
124
+ declare const RUNTIME_LOCK_STALE_MS = 30000;
125
+ /** The local archive of entries that left MEMORY.md while no Memory Space could take them (#336). */
126
+ declare const RUNTIME_ARCHIVE_DIRECTORY = "archived";
127
+ /** The longest content one entry may hold, in UTF-8 bytes. */
128
+ declare const RUNTIME_ENTRY_MAX_BYTES: number;
129
+ type RuntimeEntryText = Pick<RuntimeMemoryEntry, 'target' | 'content'>;
130
+ /** The UTF-8 bytes one target's entries take against its limit: their contents joined by the delimiter. */
131
+ declare function runtimeBytes(entries: ReadonlyArray<RuntimeEntryText>, target: RuntimeMemoryTarget): number;
132
+ /** One target's projection file, USER.md or MEMORY.md, exactly as the Runtime Source writes it. */
133
+ declare function runtimeProjection(entries: ReadonlyArray<RuntimeEntryText>, target: RuntimeMemoryTarget): string;
134
+ /** What a stored memories.json holds, counted without judging each entry. */
135
+ interface RuntimeMemoriesSummary {
136
+ /** Whether the file has an entry list at all. Without one, every count is 0. */
137
+ valid: boolean;
138
+ /** Every entry that is an object, whatever its target. */
139
+ entries: number;
140
+ user: number;
141
+ memory: number;
142
+ }
143
+ /**
144
+ * Count the entries of a parsed memories.json for a read-only inventory. It never throws: a file
145
+ * without an entry list is not `valid`, and a malformed entry is counted or skipped, never refused.
146
+ */
147
+ declare function summarizeRuntimeMemories(value: unknown): RuntimeMemoriesSummary;
110
148
  //#endregion
111
- export { RUNTIME_BRANCH_NAME_MAX, RUNTIME_BRANCH_NAME_PATTERN, RUNTIME_ENTRY_DELIMITER, RUNTIME_MEMORY_LIMITS, RUNTIME_MEMORY_VERSION, RuntimeMemoryAction, RuntimeMemoryCompactedEntry, RuntimeMemoryEntry, RuntimeMemoryEntryOrigin, RuntimeMemoryImportance, RuntimeMemoryLimits, RuntimeMemoryMaintenancePlan, RuntimeMemoryMutation, RuntimeMemoryMutationResult, RuntimeMemorySnapshot, RuntimeMemoryTarget, RuntimeMemoryTargetView, RuntimeMemoryUsage, parseRuntimeBranches };
149
+ export { RUNTIME_ARCHIVE_DIRECTORY, RUNTIME_BRANCH_NAME_MAX, RUNTIME_BRANCH_NAME_PATTERN, RUNTIME_DIRECTORY, RUNTIME_ENTRY_DELIMITER, RUNTIME_ENTRY_MAX_BYTES, RUNTIME_LOCK_FILE, RUNTIME_LOCK_STALE_MS, RUNTIME_MEMORIES_FILE, RUNTIME_MEMORY_LIMITS, RUNTIME_MEMORY_LIMIT_MAX_BYTES, RUNTIME_MEMORY_VERSION, RUNTIME_PROJECTION_FILES, RuntimeMemoriesSummary, RuntimeMemoryAction, RuntimeMemoryCompactedEntry, RuntimeMemoryEntry, RuntimeMemoryEntryOrigin, RuntimeMemoryImportance, RuntimeMemoryLimits, RuntimeMemoryMaintenancePlan, RuntimeMemoryMutation, RuntimeMemoryMutationResult, RuntimeMemorySnapshot, RuntimeMemoryTarget, RuntimeMemoryTargetView, RuntimeMemoryUsage, parseRuntimeBranches, runtimeBytes, runtimeProjection, summarizeRuntimeMemories };
package/lib/contracts.js CHANGED
@@ -1,2 +1,2 @@
1
- import { a as RUNTIME_MEMORY_VERSION, i as RUNTIME_MEMORY_LIMITS, n as RUNTIME_BRANCH_NAME_PATTERN, o as parseRuntimeBranches, r as RUNTIME_ENTRY_DELIMITER, t as RUNTIME_BRANCH_NAME_MAX } from "./contracts-B6vRCw3C.js";
2
- export { RUNTIME_BRANCH_NAME_MAX, RUNTIME_BRANCH_NAME_PATTERN, RUNTIME_ENTRY_DELIMITER, RUNTIME_MEMORY_LIMITS, RUNTIME_MEMORY_VERSION, parseRuntimeBranches };
1
+ import { _ as summarizeRuntimeMemories, a as RUNTIME_ENTRY_DELIMITER, c as RUNTIME_LOCK_STALE_MS, d as RUNTIME_MEMORY_LIMIT_MAX_BYTES, f as RUNTIME_MEMORY_VERSION, g as runtimeProjection, h as runtimeBytes, i as RUNTIME_DIRECTORY, l as RUNTIME_MEMORIES_FILE, m as parseRuntimeBranches, n as RUNTIME_BRANCH_NAME_MAX, o as RUNTIME_ENTRY_MAX_BYTES, p as RUNTIME_PROJECTION_FILES, r as RUNTIME_BRANCH_NAME_PATTERN, s as RUNTIME_LOCK_FILE, t as RUNTIME_ARCHIVE_DIRECTORY, u as RUNTIME_MEMORY_LIMITS } from "./contracts-DycwaUv_.js";
2
+ export { RUNTIME_ARCHIVE_DIRECTORY, RUNTIME_BRANCH_NAME_MAX, RUNTIME_BRANCH_NAME_PATTERN, RUNTIME_DIRECTORY, RUNTIME_ENTRY_DELIMITER, RUNTIME_ENTRY_MAX_BYTES, RUNTIME_LOCK_FILE, RUNTIME_LOCK_STALE_MS, RUNTIME_MEMORIES_FILE, RUNTIME_MEMORY_LIMITS, RUNTIME_MEMORY_LIMIT_MAX_BYTES, RUNTIME_MEMORY_VERSION, RUNTIME_PROJECTION_FILES, parseRuntimeBranches, runtimeBytes, runtimeProjection, summarizeRuntimeMemories };
package/lib/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { RUNTIME_BRANCH_NAME_MAX, RUNTIME_BRANCH_NAME_PATTERN, RUNTIME_ENTRY_DELIMITER, RUNTIME_MEMORY_LIMITS, RUNTIME_MEMORY_VERSION, RuntimeMemoryAction, RuntimeMemoryCompactedEntry, RuntimeMemoryEntry, RuntimeMemoryEntryOrigin, RuntimeMemoryImportance, RuntimeMemoryLimits, RuntimeMemoryMaintenancePlan, RuntimeMemoryMutation, RuntimeMemoryMutationResult, RuntimeMemorySnapshot, RuntimeMemoryTarget, RuntimeMemoryTargetView, RuntimeMemoryUsage, parseRuntimeBranches } from "./contracts.js";
1
+ import { RUNTIME_ARCHIVE_DIRECTORY, RUNTIME_BRANCH_NAME_MAX, RUNTIME_BRANCH_NAME_PATTERN, RUNTIME_DIRECTORY, RUNTIME_ENTRY_DELIMITER, RUNTIME_ENTRY_MAX_BYTES, RUNTIME_LOCK_FILE, RUNTIME_LOCK_STALE_MS, RUNTIME_MEMORIES_FILE, RUNTIME_MEMORY_LIMITS, RUNTIME_MEMORY_LIMIT_MAX_BYTES, RUNTIME_MEMORY_VERSION, RUNTIME_PROJECTION_FILES, RuntimeMemoriesSummary, RuntimeMemoryAction, RuntimeMemoryCompactedEntry, RuntimeMemoryEntry, RuntimeMemoryEntryOrigin, RuntimeMemoryImportance, RuntimeMemoryLimits, RuntimeMemoryMaintenancePlan, RuntimeMemoryMutation, RuntimeMemoryMutationResult, RuntimeMemorySnapshot, RuntimeMemoryTarget, RuntimeMemoryTargetView, RuntimeMemoryUsage, parseRuntimeBranches, runtimeBytes, runtimeProjection, summarizeRuntimeMemories } from "./contracts.js";
2
2
  import z from "@deepseek-ai/schemastery";
3
3
  import { MemorySourceDefinition } from "dsh-mnemon/contracts";
4
4
  import { Context } from "@deepseek-ai/cordis";
@@ -21,4 +21,4 @@ declare const inject: string[];
21
21
  declare const memoryPlugin: import("dsh-mnemon/extension-sdk").MemoryPluginDescriptor;
22
22
  declare function apply(ctx: Context, config?: Config): void;
23
23
  //#endregion
24
- export { Config, type RUNTIME_BRANCH_NAME_MAX, type RUNTIME_BRANCH_NAME_PATTERN, type RUNTIME_ENTRY_DELIMITER, type RUNTIME_MEMORY_LIMITS, RUNTIME_MEMORY_SOURCE, type RUNTIME_MEMORY_VERSION, type RuntimeMemoryAction, type RuntimeMemoryCompactedEntry, type RuntimeMemoryEntry, type RuntimeMemoryEntryOrigin, type RuntimeMemoryImportance, type RuntimeMemoryLimits, type RuntimeMemoryMaintenancePlan, type RuntimeMemoryMutation, type RuntimeMemoryMutationResult, type RuntimeMemorySnapshot, type RuntimeMemoryTarget, type RuntimeMemoryTargetView, type RuntimeMemoryUsage, apply, createRuntimeMemorySource, inject, memoryPlugin, name, type parseRuntimeBranches };
24
+ export { Config, type RUNTIME_ARCHIVE_DIRECTORY, type RUNTIME_BRANCH_NAME_MAX, type RUNTIME_BRANCH_NAME_PATTERN, type RUNTIME_DIRECTORY, type RUNTIME_ENTRY_DELIMITER, type RUNTIME_ENTRY_MAX_BYTES, type RUNTIME_LOCK_FILE, type RUNTIME_LOCK_STALE_MS, type RUNTIME_MEMORIES_FILE, type RUNTIME_MEMORY_LIMITS, type RUNTIME_MEMORY_LIMIT_MAX_BYTES, RUNTIME_MEMORY_SOURCE, type RUNTIME_MEMORY_VERSION, type RUNTIME_PROJECTION_FILES, type RuntimeMemoriesSummary, type RuntimeMemoryAction, type RuntimeMemoryCompactedEntry, type RuntimeMemoryEntry, type RuntimeMemoryEntryOrigin, type RuntimeMemoryImportance, type RuntimeMemoryLimits, type RuntimeMemoryMaintenancePlan, type RuntimeMemoryMutation, type RuntimeMemoryMutationResult, type RuntimeMemorySnapshot, type RuntimeMemoryTarget, type RuntimeMemoryTargetView, type RuntimeMemoryUsage, apply, createRuntimeMemorySource, inject, memoryPlugin, name, type parseRuntimeBranches, type runtimeBytes, type runtimeProjection, type summarizeRuntimeMemories };
package/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { i as RUNTIME_MEMORY_LIMITS, l as MAX_RUNTIME_MEMORY_LIMIT_BYTES, n as RUNTIME_BRANCH_NAME_PATTERN, o as parseRuntimeBranches, r as RUNTIME_ENTRY_DELIMITER } from "./contracts-B6vRCw3C.js";
1
+ import { a as RUNTIME_ENTRY_DELIMITER, b as MAX_RUNTIME_MEMORY_LIMIT_BYTES, g as runtimeProjection, h as runtimeBytes, i as RUNTIME_DIRECTORY, l as RUNTIME_MEMORIES_FILE, m as parseRuntimeBranches, o as RUNTIME_ENTRY_MAX_BYTES, p as RUNTIME_PROJECTION_FILES, r as RUNTIME_BRANCH_NAME_PATTERN, s as RUNTIME_LOCK_FILE, t as RUNTIME_ARCHIVE_DIRECTORY, u as RUNTIME_MEMORY_LIMITS } from "./contracts-DycwaUv_.js";
2
2
  import { createMemoryMutationReceipt, defineMemoryPlugin, defineMemorySource, installMemory, memoryConfigurationDigest, memoryInputRecord, memoryInputStringArray, memoryInputText, truncateMemoryText } from "dsh-mnemon/extension-sdk";
3
3
  import { homedir } from "node:os";
4
4
  import { basename, isAbsolute, join } from "node:path";
@@ -75,9 +75,7 @@ function resolveGitBranch(cwd) {
75
75
  //#endregion
76
76
  //#region src/controller.ts
77
77
  const LOCK_TIMEOUT_MS = 5e3;
78
- const LOCK_STALE_MS = 3e4;
79
78
  const LOCK_RETRY_MS = 20;
80
- const MAX_ENTRY_BYTES = 8192;
81
79
  /** Append to a file that is not a symbolic link; the open fails on one. */
82
80
  function appendWithoutLinks(path, text) {
83
81
  const descriptor = openSync(path, constants.O_WRONLY | constants.O_APPEND | constants.O_CREAT | (constants.O_NOFOLLOW ?? 0), 384);
@@ -147,7 +145,7 @@ function normalizeContent(value, field) {
147
145
  if (content === "") throw new Error(`${field} is required`);
148
146
  if (content.includes("§")) throw new Error(`${field} must not contain the reserved § entry delimiter`);
149
147
  const bytes = Buffer.byteLength(content, "utf8");
150
- if (bytes > MAX_ENTRY_BYTES) throw new Error(`${field} is too large (${bytes} bytes; max ${MAX_ENTRY_BYTES})`);
148
+ if (bytes > 8192) throw new Error(`${field} is too large (${bytes} bytes; max ${RUNTIME_ENTRY_MAX_BYTES})`);
151
149
  return content;
152
150
  }
153
151
  function parseOrigin(value) {
@@ -177,14 +175,6 @@ function parseEntry(value) {
177
175
  ...origin === void 0 ? {} : { origin }
178
176
  };
179
177
  }
180
- function byteCount(entries, target) {
181
- const content = entries.filter((entry) => entry.target === target).map((entry) => entry.content).join(RUNTIME_ENTRY_DELIMITER);
182
- return Buffer.byteLength(content, "utf8");
183
- }
184
- function markdown(entries, target) {
185
- const content = entries.filter((entry) => entry.target === target).map((entry) => entry.content).join(RUNTIME_ENTRY_DELIMITER);
186
- return content === "" ? "" : `${content}\n`;
187
- }
188
178
  function age(timestamp, projectedAt) {
189
179
  const time = Date.parse(timestamp);
190
180
  if (!Number.isFinite(time)) return "unknown";
@@ -365,14 +355,14 @@ var RuntimeMemoryController = class RuntimeMemoryController {
365
355
  memory: limits.memory,
366
356
  user: limits.user
367
357
  };
368
- this.directory = join(runner.effectiveDataDir(), "runtime");
369
- this.sourcePath = join(this.directory, "memories.json");
370
- this.memoryPath = join(this.directory, "MEMORY.md");
371
- this.localUserPath = join(this.directory, "USER.md");
372
- this.lockPath = join(this.directory, ".memories.lock");
373
- this.archivePath = join(this.directory, "archived", "MEMORY.md");
374
- this.archiveSourcePath = join(this.directory, "archived", "memories.jsonl");
375
- const userDirectory = userRunner === void 0 ? this.directory : join(userRunner.effectiveDataDir(), "runtime");
358
+ this.directory = join(runner.effectiveDataDir(), RUNTIME_DIRECTORY);
359
+ this.sourcePath = join(this.directory, RUNTIME_MEMORIES_FILE);
360
+ this.memoryPath = join(this.directory, RUNTIME_PROJECTION_FILES.memory);
361
+ this.localUserPath = join(this.directory, RUNTIME_PROJECTION_FILES.user);
362
+ this.lockPath = join(this.directory, RUNTIME_LOCK_FILE);
363
+ this.archivePath = join(this.directory, RUNTIME_ARCHIVE_DIRECTORY, "MEMORY.md");
364
+ this.archiveSourcePath = join(this.directory, RUNTIME_ARCHIVE_DIRECTORY, "memories.jsonl");
365
+ const userDirectory = userRunner === void 0 ? this.directory : join(userRunner.effectiveDataDir(), RUNTIME_DIRECTORY);
376
366
  this.userController = userDirectory === this.directory || userRunner === void 0 ? void 0 : new RuntimeMemoryController(userRunner, now, this.limits);
377
367
  this.userPath = this.userController?.userPath ?? this.localUserPath;
378
368
  this.userSourcePath = this.userController?.sourcePath ?? this.sourcePath;
@@ -487,8 +477,8 @@ ${memory || "(empty)"}
487
477
  const operation = this.queue.then(() => this.withLock(() => {
488
478
  const file = this.readSource();
489
479
  const prepared = prepareMutation(file.entries, request, this.now().toISOString());
490
- const used = byteCount(file.entries, request.target);
491
- const projected = byteCount(prepared.projectedEntries, request.target);
480
+ const used = runtimeBytes(file.entries, request.target);
481
+ const projected = runtimeBytes(prepared.projectedEntries, request.target);
492
482
  const limit = this.limits[request.target];
493
483
  return {
494
484
  revision: revision(file),
@@ -532,9 +522,9 @@ ${memory || "(empty)"}
532
522
  const fitted = packCompactionCandidates(replacements, request.target, compactedByteBudget);
533
523
  const targetEntries = [...fitted, ...prepared.pendingEntry === void 0 ? [] : [prepared.pendingEntry]];
534
524
  const entries = [...file.entries.filter((entry) => entry.target !== request.target), ...targetEntries];
535
- const used = byteCount(entries, request.target);
525
+ const used = runtimeBytes(entries, request.target);
536
526
  const limit = this.limits[request.target];
537
- if (used > limit) throw new RuntimeMemoryCapacityError(request.target, byteCount(file.entries, request.target), used, limit);
527
+ if (used > limit) throw new RuntimeMemoryCapacityError(request.target, runtimeBytes(file.entries, request.target), used, limit);
538
528
  const next = {
539
529
  version: 1,
540
530
  entries
@@ -556,7 +546,7 @@ ${memory || "(empty)"}
556
546
  }
557
547
  /** Append entries to the local archive: JSON Lines to restore from, and Markdown to read. */
558
548
  archiveLocally(entries, fromRevision, archivedAt) {
559
- const directory = join(this.directory, "archived");
549
+ const directory = join(this.directory, RUNTIME_ARCHIVE_DIRECTORY);
560
550
  mkdirSync(directory, {
561
551
  recursive: true,
562
552
  mode: 448
@@ -594,9 +584,9 @@ ${memory || "(empty)"}
594
584
  const file = this.readSource();
595
585
  const prepared = prepareMutation(file.entries, request, this.now().toISOString());
596
586
  if (!prepared.changed) return this.result(request.target, prepared.projectedEntries, prepared.fields);
597
- const used = byteCount(prepared.projectedEntries, request.target);
587
+ const used = runtimeBytes(prepared.projectedEntries, request.target);
598
588
  const limit = this.limits[request.target];
599
- if (used > limit) throw new RuntimeMemoryCapacityError(request.target, byteCount(file.entries, request.target), used, limit);
589
+ if (used > limit) throw new RuntimeMemoryCapacityError(request.target, runtimeBytes(file.entries, request.target), used, limit);
600
590
  this.persist({
601
591
  version: 1,
602
592
  entries: prepared.projectedEntries
@@ -610,7 +600,7 @@ ${memory || "(empty)"}
610
600
  target,
611
601
  entryCount: entries.filter((entry) => entry.target === target).length,
612
602
  usage: {
613
- used: byteCount(entries, target),
603
+ used: runtimeBytes(entries, target),
614
604
  limit: this.limits[target]
615
605
  },
616
606
  ...fields.added === void 0 ? {} : { added: fields.added },
@@ -622,7 +612,7 @@ ${memory || "(empty)"}
622
612
  return {
623
613
  target,
624
614
  entryCount: entries.filter((entry) => entry.target === target).length,
625
- used: byteCount(entries, target),
615
+ used: runtimeBytes(entries, target),
626
616
  limit: this.limits[target],
627
617
  markdownPath: target === "memory" ? this.memoryPath : this.userPath
628
618
  };
@@ -667,8 +657,8 @@ ${memory || "(empty)"}
667
657
  });
668
658
  const nonce = `${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}`;
669
659
  const writes = [
670
- [this.localUserPath, markdown(file.entries, "user")],
671
- [this.memoryPath, markdown(file.entries, "memory")],
660
+ [this.localUserPath, runtimeProjection(file.entries, "user")],
661
+ [this.memoryPath, runtimeProjection(file.entries, "memory")],
672
662
  [this.sourcePath, `${JSON.stringify(file, null, 2)}\n`]
673
663
  ];
674
664
  const temporaries = writes.map(([path]) => join(this.directory, `.${basename(path)}.${nonce}.tmp`));
@@ -684,7 +674,7 @@ ${memory || "(empty)"}
684
674
  }
685
675
  repairProjections(file) {
686
676
  for (const [path, target] of [[this.localUserPath, "user"], [this.memoryPath, "memory"]]) {
687
- const expected = markdown(file.entries, target);
677
+ const expected = runtimeProjection(file.entries, target);
688
678
  let current;
689
679
  try {
690
680
  current = readFileSync(path, "utf8");
@@ -712,7 +702,7 @@ ${memory || "(empty)"}
712
702
  } catch (error) {
713
703
  if ((isRecord(error) && typeof error.code === "string" ? error.code : void 0) !== "EEXIST") throw error;
714
704
  try {
715
- if (Date.now() - statSync(this.lockPath).mtimeMs > LOCK_STALE_MS) {
705
+ if (Date.now() - statSync(this.lockPath).mtimeMs > 3e4) {
716
706
  rmSync(this.lockPath, { force: true });
717
707
  continue;
718
708
  }
@@ -96,6 +96,8 @@ export interface RuntimeMemoryLimits {
96
96
  readonly user: number;
97
97
  }
98
98
  export declare const RUNTIME_MEMORY_LIMITS: RuntimeMemoryLimits;
99
+ /** The largest limit, in UTF-8 bytes, either target's file may be given. */
100
+ export declare const RUNTIME_MEMORY_LIMIT_MAX_BYTES: number;
99
101
  /** The longest git branch name a scope may hold. */
100
102
  export declare const RUNTIME_BRANCH_NAME_MAX = 128;
101
103
  /** The characters a git branch name in a scope may use. */
@@ -106,3 +108,40 @@ export declare const RUNTIME_BRANCH_NAME_PATTERN: RegExp;
106
108
  * of breaking the projection that reads it.
107
109
  */
108
110
  export declare function parseRuntimeBranches(value: unknown): string[] | undefined;
111
+ /** The Runtime Source's directory under a storage root. */
112
+ export declare const RUNTIME_DIRECTORY = "runtime";
113
+ /** The entries themselves, the source of truth the projections are written from. */
114
+ export declare const RUNTIME_MEMORIES_FILE = "memories.json";
115
+ /** Each target's projection: its entries' plain content, the file a person reads. */
116
+ export declare const RUNTIME_PROJECTION_FILES: {
117
+ readonly user: "USER.md";
118
+ readonly memory: "MEMORY.md";
119
+ };
120
+ /** Created exclusively while the files above are read or written; whoever copies them takes it too. */
121
+ export declare const RUNTIME_LOCK_FILE = ".memories.lock";
122
+ /** A lock file older than this was left by a process that stopped, and may be taken over. */
123
+ export declare const RUNTIME_LOCK_STALE_MS = 30000;
124
+ /** The local archive of entries that left MEMORY.md while no Memory Space could take them (#336). */
125
+ export declare const RUNTIME_ARCHIVE_DIRECTORY = "archived";
126
+ /** The longest content one entry may hold, in UTF-8 bytes. */
127
+ export declare const RUNTIME_ENTRY_MAX_BYTES: number;
128
+ type RuntimeEntryText = Pick<RuntimeMemoryEntry, 'target' | 'content'>;
129
+ /** The UTF-8 bytes one target's entries take against its limit: their contents joined by the delimiter. */
130
+ export declare function runtimeBytes(entries: ReadonlyArray<RuntimeEntryText>, target: RuntimeMemoryTarget): number;
131
+ /** One target's projection file, USER.md or MEMORY.md, exactly as the Runtime Source writes it. */
132
+ export declare function runtimeProjection(entries: ReadonlyArray<RuntimeEntryText>, target: RuntimeMemoryTarget): string;
133
+ /** What a stored memories.json holds, counted without judging each entry. */
134
+ export interface RuntimeMemoriesSummary {
135
+ /** Whether the file has an entry list at all. Without one, every count is 0. */
136
+ valid: boolean;
137
+ /** Every entry that is an object, whatever its target. */
138
+ entries: number;
139
+ user: number;
140
+ memory: number;
141
+ }
142
+ /**
143
+ * Count the entries of a parsed memories.json for a read-only inventory. It never throws: a file
144
+ * without an entry list is not `valid`, and a malformed entry is counted or skipped, never refused.
145
+ */
146
+ export declare function summarizeRuntimeMemories(value: unknown): RuntimeMemoriesSummary;
147
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-mnemon-source-runtime",
3
- "version": "0.5.14",
3
+ "version": "0.5.15",
4
4
  "description": "Self-contained runtime Source for Composable View Memory.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -55,12 +55,12 @@
55
55
  "react": "^18.2.0"
56
56
  },
57
57
  "devDependencies": {
58
- "@deepseek-ai/dsh-client-ui-primitives": "0.1.7-rc.2",
58
+ "@deepseek-ai/dsh-client-ui-primitives": "0.2.1-rc.2",
59
59
  "@testing-library/react": "^16.3.0",
60
60
  "@types/node": "^22.0.0",
61
61
  "@types/react": "^18.3.0",
62
62
  "@types/react-dom": "^18.3.0",
63
- "dsh-mnemon": "^0.5.26",
63
+ "dsh-mnemon": "^0.5.32",
64
64
  "jsdom": "^26.0.0",
65
65
  "react": "^18.2.0",
66
66
  "react-dom": "^18.2.0",
@@ -84,9 +84,9 @@
84
84
  "micromark-util-sanitize-uri": "^2.0.1",
85
85
  "micromark-util-symbol": "^2.0.1",
86
86
  "shiki": "^4.3.1",
87
- "@deepseek-ai/dsh-client-store": "0.1.7-rc.2",
88
- "@deepseek-ai/dsh-util-code-language": "0.1.7-rc.2",
89
- "@deepseek-ai/dsh-util-workspace-path": "0.1.7-rc.2",
87
+ "@deepseek-ai/dsh-client-store": "0.2.1-rc.2",
88
+ "@deepseek-ai/dsh-util-code-language": "0.2.1-rc.2",
89
+ "@deepseek-ai/dsh-util-workspace-path": "0.2.1-rc.2",
90
90
  "diff": "^9.0.0",
91
91
  "immer": "^10.1.1",
92
92
  "simple-icons": "16.31.0",
@@ -1,32 +0,0 @@
1
- //#region src/defaults.ts
2
- const DEFAULT_RUNTIME_MEMORY_LIMIT_BYTES = 10240;
3
- const DEFAULT_RUNTIME_USER_LIMIT_BYTES = 4096;
4
- const MAX_RUNTIME_MEMORY_LIMIT_BYTES = 1048576;
5
- //#endregion
6
- //#region src/contracts.ts
7
- const RUNTIME_MEMORY_VERSION = 1;
8
- const RUNTIME_ENTRY_DELIMITER = "\n§\n";
9
- const RUNTIME_MEMORY_LIMITS = {
10
- memory: DEFAULT_RUNTIME_MEMORY_LIMIT_BYTES,
11
- user: DEFAULT_RUNTIME_USER_LIMIT_BYTES
12
- };
13
- /** The longest git branch name a scope may hold. */
14
- const RUNTIME_BRANCH_NAME_MAX = 128;
15
- /** The characters a git branch name in a scope may use. */
16
- const RUNTIME_BRANCH_NAME_PATTERN = /^[A-Za-z0-9._/-]+$/u;
17
- /**
18
- * Read a stored branch scope. Absent or malformed data reads as no scope, so an entry that
19
- * a hand edit or another installation left malformed is projected on every branch instead
20
- * of breaking the projection that reads it.
21
- */
22
- function parseRuntimeBranches(value) {
23
- if (!Array.isArray(value)) return void 0;
24
- const branches = [];
25
- for (const item of value) {
26
- if (typeof item !== "string" || item === "" || item.length > 128 || !RUNTIME_BRANCH_NAME_PATTERN.test(item)) return void 0;
27
- branches.push(item);
28
- }
29
- return branches;
30
- }
31
- //#endregion
32
- export { RUNTIME_MEMORY_VERSION as a, DEFAULT_RUNTIME_USER_LIMIT_BYTES as c, RUNTIME_MEMORY_LIMITS as i, MAX_RUNTIME_MEMORY_LIMIT_BYTES as l, RUNTIME_BRANCH_NAME_PATTERN as n, parseRuntimeBranches as o, RUNTIME_ENTRY_DELIMITER as r, DEFAULT_RUNTIME_MEMORY_LIMIT_BYTES as s, RUNTIME_BRANCH_NAME_MAX as t };