@aiwayds/dsh-topics-memory 0.6.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.
Files changed (61) hide show
  1. package/README.md +112 -0
  2. package/README.zh.md +112 -0
  3. package/cordis.patch.yml +8 -0
  4. package/lib/commands.d.ts +21 -0
  5. package/lib/commands.js +368 -0
  6. package/lib/config.d.ts +122 -0
  7. package/lib/config.js +123 -0
  8. package/lib/delegation.d.ts +23 -0
  9. package/lib/delegation.js +21 -0
  10. package/lib/digest.d.ts +42 -0
  11. package/lib/digest.js +101 -0
  12. package/lib/distill.d.ts +186 -0
  13. package/lib/distill.js +759 -0
  14. package/lib/git.d.ts +66 -0
  15. package/lib/git.js +169 -0
  16. package/lib/ilog.d.ts +53 -0
  17. package/lib/ilog.js +70 -0
  18. package/lib/index.d.ts +73 -0
  19. package/lib/index.js +504 -0
  20. package/lib/observer.d.ts +41 -0
  21. package/lib/observer.js +128 -0
  22. package/lib/okf.d.ts +112 -0
  23. package/lib/okf.js +279 -0
  24. package/lib/onboard.d.ts +205 -0
  25. package/lib/onboard.js +664 -0
  26. package/lib/paths.d.ts +48 -0
  27. package/lib/paths.js +84 -0
  28. package/lib/retrieval.d.ts +61 -0
  29. package/lib/retrieval.js +178 -0
  30. package/lib/service.d.ts +96 -0
  31. package/lib/service.js +375 -0
  32. package/lib/store.d.ts +153 -0
  33. package/lib/store.js +489 -0
  34. package/lib/sync.d.ts +52 -0
  35. package/lib/sync.js +154 -0
  36. package/lib/tools.d.ts +8 -0
  37. package/lib/tools.js +208 -0
  38. package/lib/viz.d.ts +33 -0
  39. package/lib/viz.js +310 -0
  40. package/lib/yaml.d.ts +18 -0
  41. package/lib/yaml.js +334 -0
  42. package/package.json +97 -0
  43. package/src/commands.ts +397 -0
  44. package/src/config.ts +149 -0
  45. package/src/delegation.ts +27 -0
  46. package/src/digest.ts +125 -0
  47. package/src/distill.ts +885 -0
  48. package/src/git.ts +209 -0
  49. package/src/ilog.ts +99 -0
  50. package/src/index.ts +574 -0
  51. package/src/observer.ts +150 -0
  52. package/src/okf.ts +317 -0
  53. package/src/onboard.ts +762 -0
  54. package/src/paths.ts +104 -0
  55. package/src/retrieval.ts +219 -0
  56. package/src/service.ts +400 -0
  57. package/src/store.ts +567 -0
  58. package/src/sync.ts +170 -0
  59. package/src/tools.ts +225 -0
  60. package/src/viz.ts +333 -0
  61. package/src/yaml.ts +322 -0
package/lib/yaml.js ADDED
@@ -0,0 +1,334 @@
1
+ /**
2
+ * Minimal YAML subset for OKF frontmatter — parse and serialize, zero deps.
3
+ *
4
+ * Scope is deliberately narrow: exactly what OKF v0.2 frontmatter needs and
5
+ * nothing more. Maps (nested), block sequences, inline sequences `[a, b]`,
6
+ * inline maps `{by: x, at: y}`, plain/quoted scalars, numbers, booleans, null.
7
+ * Comments: full-line only (trailing comments are unsafe — URLs may contain `#`).
8
+ * Block scalars (`|`, `>`) are NOT supported; never emitted by this plugin.
9
+ *
10
+ * @module yaml
11
+ */
12
+ export class YamlError extends Error {
13
+ name = 'YamlError';
14
+ }
15
+ function stripComments(raw) {
16
+ const lines = [];
17
+ raw.split('\n').forEach((l, i) => {
18
+ if (l.trim() === '' || l.trimStart().startsWith('#'))
19
+ return;
20
+ const indent = l.length - l.trimStart().length;
21
+ lines.push({ indent, text: l.trim(), no: i + 1 });
22
+ });
23
+ return lines;
24
+ }
25
+ function unquote(s) {
26
+ if (s.length >= 2 && ((s.startsWith('"') && s.endsWith('"')) || (s.startsWith("'") && s.endsWith("'")))) {
27
+ const inner = s.slice(1, -1);
28
+ // Double quotes: unescape \" and \\; single quotes: '' is a literal '.
29
+ if (s.startsWith('"'))
30
+ return inner.replaceAll('\\"', '"').replaceAll('\\\\', '\\');
31
+ return inner.replaceAll("''", "'");
32
+ }
33
+ return s;
34
+ }
35
+ /** A fully quoted scalar: balanced matching quote pair, allowing surrounding whitespace. */
36
+ function isQuotedScalar(s) {
37
+ return s.length >= 2 && ((s.startsWith('"') && s.endsWith('"')) || (s.startsWith("'") && s.endsWith("'")));
38
+ }
39
+ function scalar(raw) {
40
+ const s = raw.trim();
41
+ if (s === '' || s === '~' || s === 'null' || s === 'Null' || s === 'NULL')
42
+ return null;
43
+ if ((s.startsWith('"') && s.endsWith('"')) || (s.startsWith("'") && s.endsWith("'")))
44
+ return unquote(s);
45
+ if (s === 'true' || s === 'True' || s === 'TRUE')
46
+ return true;
47
+ if (s === 'false' || s === 'False' || s === 'FALSE')
48
+ return false;
49
+ if (/^-?\d+$/.test(s))
50
+ return Number(s);
51
+ if (/^-?\d+\.\d+$/.test(s))
52
+ return Number(s);
53
+ // Dates, URLs, versions: stay strings (no implicit date coercion).
54
+ return s;
55
+ }
56
+ function splitInlineItems(s) {
57
+ // Split on commas not inside quotes or nested brackets.
58
+ const items = [];
59
+ let depth = 0;
60
+ let quote;
61
+ let cur = '';
62
+ for (const ch of s) {
63
+ if (quote !== undefined) {
64
+ cur += ch;
65
+ if (ch === quote)
66
+ quote = undefined;
67
+ continue;
68
+ }
69
+ if (ch === '"' || ch === "'") {
70
+ quote = ch;
71
+ cur += ch;
72
+ continue;
73
+ }
74
+ if (ch === '[' || ch === '{')
75
+ depth += 1;
76
+ if (ch === ']' || ch === '}')
77
+ depth -= 1;
78
+ if (ch === ',' && depth === 0) {
79
+ items.push(cur);
80
+ cur = '';
81
+ continue;
82
+ }
83
+ cur += ch;
84
+ }
85
+ if (cur.trim() !== '')
86
+ items.push(cur);
87
+ return items.map((x) => x.trim()).filter((x) => x !== '');
88
+ }
89
+ /** Split `key: value` at the first `: ` (or trailing `:`); key may be quoted. */
90
+ function splitKeyValue(text, no) {
91
+ const m = /^("([^"]*)"|'([^']*)'|[^:]+):(?::?\s(.*))?$/.exec(text);
92
+ if (m === null || (m[4] === undefined && !text.endsWith(':'))) {
93
+ throw new YamlError(`yaml: line ${no}: expected "key: value", got "${text}"`);
94
+ }
95
+ const key = m[2] ?? m[3] ?? m[1];
96
+ const value = (m[4] ?? '').trim();
97
+ return { key: unquote(key), value };
98
+ }
99
+ function parseBlock(lines, start, indent) {
100
+ if (start >= lines.length)
101
+ return { value: null, next: start };
102
+ const first = lines[start];
103
+ if (first.text.startsWith('- ') || first.text === '-') {
104
+ return parseBlockArray(lines, start, indent);
105
+ }
106
+ return parseBlockMap(lines, start, indent);
107
+ }
108
+ /** Parse a sequence starting at `start`; items are scalars, nested blocks, or inline-key maps. */
109
+ function parseBlockArray(lines, start, indent) {
110
+ const arr = [];
111
+ let i = start;
112
+ while (i < lines.length && lines[i].indent === indent && (lines[i].text.startsWith('- ') || lines[i].text === '-')) {
113
+ const raw = lines[i].text === '-' ? '' : lines[i].text.slice(2).trim();
114
+ if (raw === '') {
115
+ // Bare "-": nested block belongs to this item.
116
+ if (i + 1 < lines.length && lines[i + 1].indent > indent) {
117
+ const nested = parseBlock(lines, i + 1, lines[i + 1].indent);
118
+ arr.push(nested.value);
119
+ i = nested.next;
120
+ }
121
+ else {
122
+ arr.push(null);
123
+ i += 1;
124
+ }
125
+ continue;
126
+ }
127
+ if (isQuotedScalar(raw)) {
128
+ // Fully quoted scalar first: a quoted item containing `: ` is a string
129
+ // (e.g. `- "id-token: write"`), never an inline map. The writer quotes
130
+ // such scalars (needsQuote), so parse must read back exactly what it wrote.
131
+ arr.push(scalar(raw));
132
+ i += 1;
133
+ continue;
134
+ }
135
+ if (raw.includes(': ') || raw.endsWith(':')) {
136
+ // Map item: first key inline after "- ", continuation keys at indent+2.
137
+ const item = {};
138
+ const { key, value } = splitKeyValue(raw, lines[i].no);
139
+ i += 1;
140
+ if (value === '') {
141
+ if (i < lines.length && lines[i].indent > indent) {
142
+ const nested = parseBlock(lines, i, lines[i].indent);
143
+ item[key] = nested.value;
144
+ i = nested.next;
145
+ }
146
+ else {
147
+ item[key] = null;
148
+ }
149
+ }
150
+ else if (value.startsWith('[') && value.endsWith(']')) {
151
+ const inner = value.slice(1, -1).trim();
152
+ item[key] = inner === '' ? [] : splitInlineItems(inner).map((x) => scalar(x));
153
+ }
154
+ else if (value.startsWith('{') && value.endsWith('}')) {
155
+ item[key] = parseInlineMap(value);
156
+ }
157
+ else {
158
+ item[key] = scalar(value);
159
+ }
160
+ while (i < lines.length && lines[i].indent === indent + 2 && !lines[i].text.startsWith('- ')) {
161
+ const consumed = parseMapEntryInto(item, lines, i);
162
+ i = consumed;
163
+ }
164
+ arr.push(item);
165
+ continue;
166
+ }
167
+ arr.push(scalar(raw));
168
+ i += 1;
169
+ }
170
+ return { value: arr, next: i };
171
+ }
172
+ function parseInlineMap(value) {
173
+ const inner = value.slice(1, -1).trim();
174
+ const item = {};
175
+ if (inner !== '') {
176
+ for (const pair of splitInlineItems(inner)) {
177
+ const kv = splitKeyValue(pair, 0);
178
+ item[kv.key] = scalar(kv.value);
179
+ }
180
+ }
181
+ return item;
182
+ }
183
+ /** Parse one `key: value` line into `obj`; returns the next line index. */
184
+ function parseMapEntryInto(obj, lines, i) {
185
+ const { key, value } = splitKeyValue(lines[i].text, lines[i].no);
186
+ if (value === '') {
187
+ if (i + 1 < lines.length && lines[i + 1].indent > lines[i].indent) {
188
+ const nested = parseBlock(lines, i + 1, lines[i + 1].indent);
189
+ obj[key] = nested.value;
190
+ return nested.next;
191
+ }
192
+ obj[key] = null;
193
+ return i + 1;
194
+ }
195
+ if (value.startsWith('[') && value.endsWith(']')) {
196
+ const inner = value.slice(1, -1).trim();
197
+ obj[key] = inner === '' ? [] : splitInlineItems(inner).map((x) => scalar(x));
198
+ return i + 1;
199
+ }
200
+ if (value.startsWith('{') && value.endsWith('}')) {
201
+ obj[key] = parseInlineMap(value);
202
+ return i + 1;
203
+ }
204
+ obj[key] = scalar(value);
205
+ return i + 1;
206
+ }
207
+ function parseBlockMap(lines, start, indent) {
208
+ const obj = {};
209
+ let i = start;
210
+ while (i < lines.length && lines[i].indent === indent && !lines[i].text.startsWith('- ')) {
211
+ i = parseMapEntryInto(obj, lines, i);
212
+ }
213
+ return { value: obj, next: i };
214
+ }
215
+ /** Parse the YAML subset into a plain JS value (object at top level). */
216
+ export function parseYaml(text) {
217
+ const lines = stripComments(text);
218
+ if (lines.length === 0)
219
+ return {};
220
+ const { value, next } = parseBlock(lines, 0, lines[0].indent);
221
+ if (next < lines.length) {
222
+ throw new YamlError(`yaml: line ${lines[next].no}: inconsistent indentation near "${lines[next].text}"`);
223
+ }
224
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) {
225
+ throw new YamlError('yaml: top-level must be a mapping');
226
+ }
227
+ return value;
228
+ }
229
+ /** True when a scalar needs quoting on output (empty, special, or ambiguous). */
230
+ function needsQuote(s) {
231
+ if (s === '')
232
+ return true;
233
+ if (/^[\s]|[\s]$/.test(s))
234
+ return true;
235
+ if (/^(true|false|null|True|False|Null|TRUE|FALSE|NULL|~|yes|no|on|off)$/i.test(s))
236
+ return true;
237
+ if (/^-?\d+(\.\d+)?$/.test(s))
238
+ return true;
239
+ if (/^202\d-/.test(s))
240
+ return false; // ISO dates are unambiguous and safe unquoted
241
+ if (/[:#{}[\],&*'?"|>%@`!]/.test(s))
242
+ return true;
243
+ if (/^\s+-/.test(s))
244
+ return true;
245
+ return false;
246
+ }
247
+ function quote(s) {
248
+ return `"${s.replaceAll('\\', '\\\\').replaceAll('"', '\\"')}"`;
249
+ }
250
+ function scalarOut(v) {
251
+ if (v === null || v === undefined)
252
+ return 'null';
253
+ if (typeof v === 'boolean')
254
+ return v ? 'true' : 'false';
255
+ if (typeof v === 'number')
256
+ return String(v);
257
+ const s = String(v);
258
+ return needsQuote(s) ? quote(s) : s;
259
+ }
260
+ function isPlainKey(k) {
261
+ return k !== '' && !/[:#\s]/.test(k);
262
+ }
263
+ function writeMap(obj, indent, out) {
264
+ const pad = ' '.repeat(indent);
265
+ for (const [key, value] of Object.entries(obj)) {
266
+ const k = isPlainKey(key) ? key : quote(key);
267
+ if (value === null || value === undefined) {
268
+ out.push(`${pad}${k}: null`);
269
+ }
270
+ else if (Array.isArray(value)) {
271
+ if (value.length === 0) {
272
+ out.push(`${pad}${k}: []`);
273
+ }
274
+ else {
275
+ out.push(`${pad}${k}:`);
276
+ writeArray(value, indent + 1, out);
277
+ }
278
+ }
279
+ else if (typeof value === 'object') {
280
+ const entries = Object.entries(value);
281
+ if (entries.length === 0) {
282
+ out.push(`${pad}${k}: {}`);
283
+ }
284
+ else {
285
+ out.push(`${pad}${k}:`);
286
+ writeMap(value, indent + 1, out);
287
+ }
288
+ }
289
+ else {
290
+ out.push(`${pad}${k}: ${scalarOut(value)}`);
291
+ }
292
+ }
293
+ }
294
+ function writeArray(arr, indent, out) {
295
+ const pad = ' '.repeat(indent);
296
+ for (const item of arr) {
297
+ if (item === null || item === undefined) {
298
+ out.push(`${pad}- null`);
299
+ }
300
+ else if (typeof item === 'object' && !Array.isArray(item)) {
301
+ const entries = Object.entries(item);
302
+ if (entries.length === 0) {
303
+ out.push(`${pad}- {}`);
304
+ continue;
305
+ }
306
+ entries.forEach(([k, v], idx) => {
307
+ const kk = isPlainKey(k) ? k : quote(k);
308
+ if (v === null || v === undefined) {
309
+ out.push(`${pad}${idx === 0 ? '- ' : ' '}${kk}: null`);
310
+ }
311
+ else if (typeof v === 'object') {
312
+ out.push(`${pad}${idx === 0 ? '- ' : ' '}${kk}:`);
313
+ writeMap(v, indent + 2, out);
314
+ }
315
+ else {
316
+ out.push(`${pad}${idx === 0 ? '- ' : ' '}${kk}: ${scalarOut(v)}`);
317
+ }
318
+ });
319
+ }
320
+ else if (Array.isArray(item)) {
321
+ out.push(`${pad}-`);
322
+ writeArray(item, indent + 1, out);
323
+ }
324
+ else {
325
+ out.push(`${pad}- ${scalarOut(item)}`);
326
+ }
327
+ }
328
+ }
329
+ /** Serialize a plain JS value into the YAML subset (deterministic, stable). */
330
+ export function stringifyYaml(value) {
331
+ const out = [];
332
+ writeMap(value, 0, out);
333
+ return out.join('\n') + '\n';
334
+ }
package/package.json ADDED
@@ -0,0 +1,97 @@
1
+ {
2
+ "name": "@aiwayds/dsh-topics-memory",
3
+ "version": "0.6.0",
4
+ "description": "dsh-plugin: OKF v0.2 topic memory for dsh - local-first git-tracked bundle, hot-path LLM-free injection, two-stage observer with background distill",
5
+ "type": "module",
6
+ "main": "lib/index.js",
7
+ "types": "lib/index.d.ts",
8
+ "license": "MIT",
9
+ "keywords": [
10
+ "dsh",
11
+ "dsh-plugin",
12
+ "plugin",
13
+ "topic-memory",
14
+ "okf",
15
+ "open-knowledge-format",
16
+ "memory",
17
+ "topic"
18
+ ],
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/fan56/dsh-topics-memory.git"
22
+ },
23
+ "homepage": "https://github.com/fan56/dsh-topics-memory",
24
+ "engines": {
25
+ "node": ">=22.19.0"
26
+ },
27
+ "dsh": {
28
+ "bundle": {
29
+ "patch": "./cordis.patch.yml"
30
+ }
31
+ },
32
+ "files": [
33
+ "lib",
34
+ "cordis.patch.yml",
35
+ "src"
36
+ ],
37
+ "scripts": {
38
+ "build": "tsc -p tsconfig.json",
39
+ "check": "tsc --noEmit -p tsconfig.json",
40
+ "pretest": "tsc -p tsconfig.json",
41
+ "test": "node --test test/yaml.test.mjs test/okf.test.mjs test/retrieval.test.mjs test/digest.test.mjs test/ilog.test.mjs test/store.test.mjs test/paths.test.mjs test/git.test.mjs test/sync.test.mjs test/tools.test.mjs test/dedup.test.mjs test/commands.test.mjs test/onboard.test.mjs test/delegation.test.mjs test/observer.test.mjs test/distill.test.mjs test/viz.test.mjs test/corpus.test.mjs test/llm-scope.test.mjs test/settings-migration.test.mjs"
42
+ },
43
+ "dependencies": {},
44
+ "peerDependencies": {
45
+ "@deepseek-ai/cordis": "^4.0.2",
46
+ "@deepseek-ai/dsh-tools": ">=0.1.2-alpha.4",
47
+ "@deepseek-ai/dsh-llm": ">=0.1.2-alpha.4",
48
+ "@deepseek-ai/dsh-settings": ">=0.1.2-alpha.4",
49
+ "@deepseek-ai/dsh-commands": ">=0.1.2-alpha.4",
50
+ "@deepseek-ai/dsh-util-values": ">=0.1.2-alpha.4",
51
+ "@deepseek-ai/schemastery": "^3.18.2"
52
+ },
53
+ "peerDependenciesMeta": {
54
+ "@deepseek-ai/cordis": {
55
+ "optional": true
56
+ },
57
+ "@deepseek-ai/dsh-tools": {
58
+ "optional": true
59
+ },
60
+ "@deepseek-ai/dsh-llm": {
61
+ "optional": true
62
+ },
63
+ "@deepseek-ai/dsh-settings": {
64
+ "optional": true
65
+ },
66
+ "@deepseek-ai/dsh-commands": {
67
+ "optional": true
68
+ },
69
+ "@deepseek-ai/dsh-util-values": {
70
+ "optional": true
71
+ },
72
+ "@deepseek-ai/schemastery": {
73
+ "optional": true
74
+ }
75
+ },
76
+ "devDependencies": {
77
+ "@deepseek-ai/cordis": "4.0.2",
78
+ "@deepseek-ai/dsh-agent": "0.1.2-alpha.4",
79
+ "@deepseek-ai/dsh-attachment": "0.1.2-alpha.4",
80
+ "@deepseek-ai/dsh-brand": "0.1.2-alpha.4",
81
+ "@deepseek-ai/dsh-code-runtime": "0.1.2-alpha.4",
82
+ "@deepseek-ai/dsh-commands": "0.1.2-alpha.4",
83
+ "@deepseek-ai/dsh-invariants": "0.1.2-alpha.4",
84
+ "@deepseek-ai/dsh-llm": "0.1.2-alpha.4",
85
+ "@deepseek-ai/dsh-scope": "0.1.2-alpha.4",
86
+ "@deepseek-ai/dsh-session": "0.1.2-alpha.4",
87
+ "@deepseek-ai/dsh-settings": "0.1.2-alpha.4",
88
+ "@deepseek-ai/dsh-system-prompt": "0.1.2-alpha.4",
89
+ "@deepseek-ai/dsh-timeout": "0.1.2-alpha.4",
90
+ "@deepseek-ai/dsh-tools": "0.1.2-alpha.4",
91
+ "@deepseek-ai/dsh-user-approval": "0.1.2-alpha.4",
92
+ "@deepseek-ai/dsh-util-values": "0.1.2-alpha.4",
93
+ "@deepseek-ai/schemastery": "3.18.2",
94
+ "@types/node": "^22.19.0",
95
+ "typescript": "^5.9.2"
96
+ }
97
+ }