dsh-engram 0.2.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/lib/util.js ADDED
@@ -0,0 +1,119 @@
1
+ /**
2
+ * dsh-engram: tiny shared utilities. Zero dependencies.
3
+ */
4
+
5
+ import { createHash, randomUUID } from "node:crypto";
6
+ import { basename, resolve } from "node:path";
7
+
8
+ /** Random id for memories / tasks / links. */
9
+ export function uuid() {
10
+ return randomUUID();
11
+ }
12
+
13
+ export const now = () => Date.now();
14
+
15
+ /**
16
+ * Deterministic entity-node id from a name: `ent_<slug>` (lowercase, dashes),
17
+ * falling back to a short random id when the name slugs to nothing. esr_node
18
+ * uses it so re-calling with the same name updates the same node and esr_link
19
+ * can target it without a prior id lookup.
20
+ */
21
+ export function slugId(name) {
22
+ const slug = String(name)
23
+ .toLowerCase()
24
+ .normalize("NFKD")
25
+ .replace(/[^a-z0-9]+/g, "-")
26
+ .replace(/^-+|-+$/g, "")
27
+ .slice(0, 48);
28
+ return slug.length > 0 ? slug : shortId(uuid());
29
+ }
30
+
31
+ /** First 8 id chars as a short stable index marker (`#ab12cd34`). */
32
+ export function shortId(id) {
33
+ const bare = String(id).replace(/[^a-z0-9]/gi, "");
34
+ return (bare.length >= 8 ? bare.slice(0, 8) : String(id).slice(0, 8)) || "?";
35
+ }
36
+
37
+ /** Truncate by Unicode code points (CJK-safe). */
38
+ export function truncate(text, max) {
39
+ const chars = [...String(text)];
40
+ return chars.length <= max ? text : `${chars.slice(0, max).join("")}…`;
41
+ }
42
+
43
+ /** `MM-DD` from epoch millis — saves 3 chars per line vs full ISO. */
44
+ export function fmtDate(ms) {
45
+ const d = new Date(ms);
46
+ const p = (n) => String(n).padStart(2, "0");
47
+ return `${p(d.getMonth() + 1)}-${p(d.getDate())}`;
48
+ }
49
+
50
+ /** Short content hash for exact-duplicate detection. */
51
+ export function hashText(text) {
52
+ return createHash("sha256").update(String(text)).digest("hex").slice(0, 16);
53
+ }
54
+
55
+ /** Normalized workspace key: absolute, trailing separator removed, win32 case-folded. */
56
+ export function workspaceKey(cwd) {
57
+ let key = resolve(cwd);
58
+ if (process.platform === "win32") key = key.toLowerCase();
59
+ return key.replace(/[\\/]+$/, "") || key;
60
+ }
61
+
62
+ /** Short human label for an index header. */
63
+ export function label(cwd) {
64
+ return basename(cwd) || cwd;
65
+ }
66
+
67
+ /** Escape `<` the way DSH system sections do (XML-ish sanitization). */
68
+ export function escapeLt(text) {
69
+ return String(text).replaceAll("<", "\\u003c");
70
+ }
71
+
72
+ /**
73
+ * CJK-aware tokenization: ASCII words plus CJK runs with overlapping bigrams.
74
+ * Deterministic, no model — the same vocabulary used for recall scoring and
75
+ * auto-capture dedup.
76
+ */
77
+ export function tokenize(input) {
78
+ const tokens = new Set();
79
+ for (const word of String(input).toLowerCase().split(/\s+/)) {
80
+ if (word.length === 0) continue;
81
+ const runs = word.match(/[\u3400-\u9fff]+|[^\u3400-\u9fff]+/g) ?? [];
82
+ for (const run of runs) {
83
+ if (/^[\u3400-\u9fff]+$/.test(run)) {
84
+ if (run.length >= 2) {
85
+ for (let i = 0; i < run.length - 1; i += 1) tokens.add(run.slice(i, i + 2));
86
+ }
87
+ } else {
88
+ for (const token of run.split(/[^a-z0-9]+/)) {
89
+ if (token.length > 0) tokens.add(token);
90
+ }
91
+ }
92
+ }
93
+ }
94
+ return [...tokens];
95
+ }
96
+
97
+ /**
98
+ * Deterministic 3-tier scoring over one record's text + tags:
99
+ * exact tag match (3) > case-insensitive text substring (2) > fuzzy tag overlap (1),
100
+ * plus a phrase boost (4) for multi-word queries. Mirrors the proven
101
+ * dsh-native-memory scoring model.
102
+ */
103
+ export function scoreRecord(record, tokens, fullQuery, phraseBoost) {
104
+ const text = String(record.text).toLowerCase();
105
+ const tags = (record.tags ?? []).map((t) => String(t).toLowerCase());
106
+ let score = 0;
107
+ for (const token of tokens) {
108
+ if (tags.includes(token)) score += 3;
109
+ else if (tags.some((tag) => tag.includes(token) || token.includes(tag))) score += 1;
110
+ if (text.includes(token)) score += 2;
111
+ }
112
+ if (phraseBoost && fullQuery.length > 1 && text.includes(fullQuery)) score += 4;
113
+ return score;
114
+ }
115
+
116
+ /** Newest first, then id ascending (deterministic). */
117
+ export function byRecency(left, right) {
118
+ return (right.updatedAt ?? 0) - (left.updatedAt ?? 0) || String(left.id).localeCompare(String(right.id));
119
+ }
package/package.json ADDED
@@ -0,0 +1,92 @@
1
+ {
2
+ "name": "dsh-engram",
3
+ "description": "Minimalist long-term memory for DeepSeek Harness, distilling symbolic-index + pi-esr ideas: zero-LLM auto-capture, a symbolic [ENGRAM] index with progressive disclosure, and an ESR-lite evidence-closure protocol (esr_task / esr_node / esr_close / esr_link). Real agent-behaviour telemetry (usage rollup + /stats) and a deterministic offline recall benchmark (npm run eval). Hot path is model-free; storage on ctx.storageDomain; web memory viewer, observability panel and config card through DSH's native settings slots.",
4
+ "version": "0.2.0",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/skepsun/dsh-engram.git"
9
+ },
10
+ "homepage": "https://github.com/skepsun/dsh-engram",
11
+ "bugs": {
12
+ "url": "https://github.com/skepsun/dsh-engram/issues"
13
+ },
14
+ "type": "module",
15
+ "main": "lib/index.js",
16
+ "exports": {
17
+ ".": "./lib/index.js",
18
+ "./client": "./lib/client.js",
19
+ "./package.json": "./package.json",
20
+ "./cordis.patch.yml": "./cordis.patch.yml"
21
+ },
22
+ "files": [
23
+ "lib",
24
+ "cordis.patch.yml",
25
+ "README.md",
26
+ "README.zh.md",
27
+ "LICENSE"
28
+ ],
29
+ "engines": {
30
+ "node": ">=22.19.0"
31
+ },
32
+ "scripts": {
33
+ "setup": "node scripts/setup-links.mjs",
34
+ "prepublishOnly": "node scripts/prepublish-check.mjs",
35
+ "build:client": "node client/build.mjs",
36
+ "test": "node --test \"test/**/*.test.mjs\"",
37
+ "eval": "node eval/recall-bench.mjs",
38
+ "typecheck": "echo ok"
39
+ },
40
+ "publishConfig": {
41
+ "access": "public",
42
+ "registry": "https://registry.npmjs.org"
43
+ },
44
+ "dsh": {
45
+ "bundle": {
46
+ "patch": "./cordis.patch.yml"
47
+ },
48
+ "client": {
49
+ "platform": "web",
50
+ "inject": [
51
+ "@deepseek-ai/dsh-client-runtime",
52
+ "@deepseek-ai/dsh-client-connection",
53
+ "@deepseek-ai/dsh-client-ui-settings"
54
+ ]
55
+ }
56
+ },
57
+ "dependencies": {
58
+ "zod": "^4.4.3"
59
+ },
60
+ "peerDependencies": {
61
+ "@deepseek-ai/cordis": "^4.0.1",
62
+ "@deepseek-ai/dsh-storage-domain": "^0.1.0-rc.7",
63
+ "@deepseek-ai/dsh-settings": "^0.1.0-rc.7",
64
+ "@deepseek-ai/schemastery": "^3.18.1"
65
+ },
66
+ "peerDependenciesMeta": {
67
+ "@deepseek-ai/cordis": {
68
+ "optional": true
69
+ },
70
+ "@deepseek-ai/dsh-storage-domain": {
71
+ "optional": true
72
+ },
73
+ "@deepseek-ai/dsh-settings": {
74
+ "optional": true
75
+ },
76
+ "@deepseek-ai/schemastery": {
77
+ "optional": true
78
+ }
79
+ },
80
+ "keywords": [
81
+ "dsh",
82
+ "dsh-plugin",
83
+ "deepseek-harness",
84
+ "memory",
85
+ "long-term-memory",
86
+ "symbolic-index",
87
+ "pi-esr"
88
+ ],
89
+ "publishConfig": {
90
+ "access": "public"
91
+ }
92
+ }