@try-works/dsh-memory 0.1.18 → 0.1.20

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 (51) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +585 -585
  3. package/STATUS.md +287 -287
  4. package/cordis.patch.yml +69 -69
  5. package/package.json +1 -1
  6. package/scripts/check-package.mjs +76 -76
  7. package/src/SYSTEM.template.md +311 -311
  8. package/src/capture.js +242 -242
  9. package/src/client/index.js +886 -886
  10. package/src/config.js +57 -57
  11. package/src/consolidate-run.js +132 -132
  12. package/src/consolidate.js +330 -330
  13. package/src/context.js +88 -88
  14. package/src/decisions.js +106 -106
  15. package/src/defaults.js +93 -93
  16. package/src/digest.js +255 -255
  17. package/src/entry.js +179 -179
  18. package/src/eval.js +72 -72
  19. package/src/files.js +60 -60
  20. package/src/fixture.js +56 -56
  21. package/src/forget.js +71 -71
  22. package/src/freshness.js +65 -65
  23. package/src/fts.js +166 -166
  24. package/src/git.js +53 -53
  25. package/src/history.js +226 -226
  26. package/src/host.js +866 -857
  27. package/src/index.js +115 -94
  28. package/src/inject.js +108 -108
  29. package/src/layers.js +178 -178
  30. package/src/ledger.js +19 -19
  31. package/src/map.js +184 -184
  32. package/src/models.js +136 -136
  33. package/src/queue.js +602 -602
  34. package/src/rank.js +31 -31
  35. package/src/recover.js +91 -91
  36. package/src/refresh.js +80 -80
  37. package/src/remember.js +58 -58
  38. package/src/rerank.js +90 -90
  39. package/src/search.js +75 -75
  40. package/src/select.js +57 -57
  41. package/src/session.js +60 -60
  42. package/src/state.js +240 -240
  43. package/src/store.js +128 -128
  44. package/src/summarize.js +195 -195
  45. package/src/system-doc.js +171 -171
  46. package/src/tasks.js +617 -617
  47. package/src/telemetry.js +122 -122
  48. package/src/tools.js +828 -818
  49. package/src/working.js +288 -288
  50. package/src/workspace-key.js +68 -68
  51. package/src/workspace.js +87 -87
package/src/system-doc.js CHANGED
@@ -1,171 +1,171 @@
1
- // The system document: the memory system describing itself, as built.
2
- //
3
- // This is not a memory layer - it is not a fact about the user's work, it is the manual for the
4
- // machinery. It is rendered from SYSTEM.template.md on every boot so it can never drift from the
5
- // build, and it lives at the store root (committed) so the same description travels with the
6
- // memory to every machine.
7
- //
8
- // It exists because an agent that reads and writes memory needs to understand the intent behind
9
- // it: which layer a fact belongs in, why writes are deterministic, and what is derived and
10
- // therefore safe to lose. Without that, memory becomes a junk drawer.
11
- import { readFileSync, readdirSync, writeFileSync } from 'node:fs'
12
- import { dirname, join } from 'node:path'
13
- import { fileURLToPath } from 'node:url'
14
- import { LAYER_CODES, LAYER_DIRS, LAYERED, VOLATILITY_DAYS, WORKSPACE_ONLY_LAYERS } from './entry.js'
15
-
16
- /** Bumped whenever the described behaviour changes, so a stale on-disk copy is detectable. */
17
- export const SYSTEM_DOC_VERSION = 2
18
-
19
- /** The template ships beside this module; resolve it relative to the module, not the cwd. */
20
- export function templatePath() {
21
- return join(dirname(fileURLToPath(import.meta.url)), 'SYSTEM.template.md')
22
- }
23
-
24
- /** Absolute path of the rendered document inside a store. */
25
- export function systemDocPath(root) { return join(root, 'SYSTEM.md') }
26
-
27
- /** The layer diagram, kept as data so it cannot break the surrounding code. */
28
- const DIAGRAM = [
29
- 'global/ workspaces/<key>/',
30
- ' semantic.md semantic.md',
31
- ' semantic/<topic>.md semantic/<topic>.md',
32
- ' episodic.md + episodic/<topic>.md',
33
- ' procedural.md + procedural/<topic>.md',
34
- ' learnings.md + learnings/<topic>.md',
35
- ' working.md working.md (single file, never indexed)',
36
- ' junk.md junk.md (single file, never indexed)',
37
- ' codebase.md + codebase/<topic>.md (workspace only)',
38
- ' history.md + history/<day>.md (workspace only)',
39
- ' state.md state.md (repo state; replaced per section)',
40
- ' working.md working.md (YOUR task/activity head)',
41
- ].join(String.fromCharCode(10))
42
-
43
- /** Count the markdown files inside one layer directory, or 0 when it does not exist. */
44
- function countTopics(root, scopeDir, layer) {
45
- try {
46
- return readdirSync(join(root, scopeDir, LAYER_DIRS[layer])).filter((n) => n.endsWith('.md')).length
47
- } catch { return 0 }
48
- }
49
-
50
- /**
51
- * Summarise what the store currently holds, so the document reports reality rather than intent.
52
- * Best-effort: an unreadable scope is reported as absent, never thrown.
53
- */
54
- export function describeStore(root) {
55
- const scopes = [{ label: 'global', dir: 'global' }]
56
- try {
57
- for (const name of readdirSync(join(root, 'workspaces'))) scopes.push({ label: name, dir: join('workspaces', name) })
58
- } catch {}
59
- return scopes.map((scope) => {
60
- const counts = {}
61
- for (const layer of LAYERED) counts[layer] = countTopics(root, scope.dir, layer)
62
- return { scope: scope.label, counts, total: Object.values(counts).reduce((a, b) => a + b, 0) }
63
- })
64
- }
65
-
66
- /** Format a millisecond cadence for humans: 3600000 -> '1h'. */
67
- export function humanDuration(ms) {
68
- const n = Number(ms)
69
- if (!Number.isFinite(n) || n <= 0) return String(ms)
70
- if (n % 86400000 === 0) return (n / 86400000) + 'd'
71
- if (n % 3600000 === 0) return (n / 3600000) + 'h'
72
- if (n % 60000 === 0) return (n / 60000) + 'm'
73
- if (n % 1000 === 0) return (n / 1000) + 's'
74
- return n + 'ms'
75
- }
76
-
77
- /** What each layer is for, keyed by layer so the table cannot drift from the vocabulary. */
78
- const LAYER_PURPOSE = {
79
- semantic: 'Durable facts that stay true: how something works, what was decided, environment facts, versions, where things live.',
80
- episodic: 'What happened: an incident, a migration, a change made on a date and why.',
81
- procedural: 'How to do something: runbooks, commands, build and test steps, order of operations.',
82
- learning: 'A lesson from experience - what you wish you had known an hour earlier.',
83
- codebase: 'What a module, file or subsystem of THIS repository does. Workspace only.',
84
- history: 'How the repository changed: one entry per commit, grouped by day. Workspace only.',
85
- decision: 'A settled choice and why it won: dependencies, conventions, architecture. Available in both scopes.',
86
- }
87
-
88
- /**
89
- * The per-layer table, generated from the layer vocabulary so a newly added layer appears here
90
- * automatically. A layer with no purpose recorded still gets a row, flagged rather than omitted.
91
- */
92
- function layerTable() {
93
- const rows = ['| Layer | Code | Use it for |', '| --- | --- | --- |']
94
- for (const layer of LAYERED) {
95
- const purpose = LAYER_PURPOSE[layer] ?? '(undocumented)'
96
- rows.push('| `' + layer + '` | `' + (LAYER_CODES[layer] ?? layer) + '` | ' + purpose + ' |')
97
- }
98
- return rows.join(String.fromCharCode(10))
99
- }
100
- /** The current-state table, so the document shows what this store actually holds. */
101
- function storeState(root) {
102
- if (!root) return '(store root unknown)'
103
- const rows = describeStore(root)
104
- const layers = LAYERED
105
- const lines = [
106
- '| Scope | ' + layers.join(' | ') + ' | total |',
107
- '| --- | ' + layers.map(() => '---').join(' | ') + ' | --- |',
108
- ]
109
- for (const row of rows) {
110
- lines.push('| ' + row.scope + ' | ' + layers.map((l) => row.counts[l]).join(' | ') + ' | ' + row.total + ' |')
111
- }
112
- return lines.join(String.fromCharCode(10))
113
- }
114
-
115
- /**
116
- * Render the system document by filling the template.
117
- * @param options.cfg - resolved plugin config, quoted so the document matches this build.
118
- * @param options.version - plugin version.
119
- * @param options.store - store root, used for the current-state table.
120
- * @param options.now - ISO timestamp.
121
- * @returns the markdown document.
122
- */
123
- export function renderSystemDoc(options = {}) {
124
- const cfg = options.cfg ?? {}
125
- const template = options.template ?? readFileSync(templatePath(), 'utf8')
126
- const values = {
127
- documentVersion: String(SYSTEM_DOC_VERSION),
128
- version: String(options.version ?? 'unknown'),
129
- generated: options.now ?? new Date().toISOString(),
130
- store: String(options.store ?? '(unknown)'),
131
- diagram: DIAGRAM,
132
- layerTable: layerTable(),
133
- workspaceOnly: WORKSPACE_ONLY_LAYERS.map((l) => '`' + l + '`').join(' and '),
134
- captureInterval: humanDuration(cfg.autoCaptureIntervalMs),
135
- consolidateInterval: humanDuration(cfg.autoConsolidateIntervalMs),
136
- refreshInterval: humanDuration(cfg.autoRefreshIntervalMs),
137
- historyInterval: humanDuration(cfg.autoHistoryIntervalMs),
138
- syncInterval: humanDuration(cfg.autoSyncIntervalMs),
139
- commitDebounce: humanDuration(cfg.commitDebounceMs),
140
- effort: String(cfg.memoryReasoningEffort ?? '(model default)'),
141
- minSignalChars: String(cfg.summaryMinSignalChars ?? ''),
142
- digestKeep: String(cfg.digestKeepCount ?? ''),
143
- stableDays: String(VOLATILITY_DAYS.stable),
144
- normalDays: String(VOLATILITY_DAYS.normal),
145
- volatileDays: String(VOLATILITY_DAYS.volatile),
146
- storeState: storeState(options.store),
147
- }
148
- return template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
149
- return Object.hasOwn(values, key) ? String(values[key]) : match
150
- })
151
- }
152
-
153
- /**
154
- * Write SYSTEM.md into a store. Always overwrites: the document is generated, never edited
155
- * in place, so a stale copy must not survive a boot.
156
- * @returns the path written.
157
- */
158
- export function writeSystemDoc(root, options = {}) {
159
- const target = systemDocPath(root)
160
- writeFileSync(target, renderSystemDoc({ ...options, store: root }), 'utf8')
161
- return target
162
- }
163
-
164
- /** True when the on-disk document is missing or was written by an older version. */
165
- export function systemDocStale(root) {
166
- try {
167
- const text = readFileSync(systemDocPath(root), 'utf8')
168
- const found = /Document version: (\d+)/.exec(text)
169
- return found === null || Number(found[1]) !== SYSTEM_DOC_VERSION
170
- } catch { return true }
171
- }
1
+ // The system document: the memory system describing itself, as built.
2
+ //
3
+ // This is not a memory layer - it is not a fact about the user's work, it is the manual for the
4
+ // machinery. It is rendered from SYSTEM.template.md on every boot so it can never drift from the
5
+ // build, and it lives at the store root (committed) so the same description travels with the
6
+ // memory to every machine.
7
+ //
8
+ // It exists because an agent that reads and writes memory needs to understand the intent behind
9
+ // it: which layer a fact belongs in, why writes are deterministic, and what is derived and
10
+ // therefore safe to lose. Without that, memory becomes a junk drawer.
11
+ import { readFileSync, readdirSync, writeFileSync } from 'node:fs'
12
+ import { dirname, join } from 'node:path'
13
+ import { fileURLToPath } from 'node:url'
14
+ import { LAYER_CODES, LAYER_DIRS, LAYERED, VOLATILITY_DAYS, WORKSPACE_ONLY_LAYERS } from './entry.js'
15
+
16
+ /** Bumped whenever the described behaviour changes, so a stale on-disk copy is detectable. */
17
+ export const SYSTEM_DOC_VERSION = 2
18
+
19
+ /** The template ships beside this module; resolve it relative to the module, not the cwd. */
20
+ export function templatePath() {
21
+ return join(dirname(fileURLToPath(import.meta.url)), 'SYSTEM.template.md')
22
+ }
23
+
24
+ /** Absolute path of the rendered document inside a store. */
25
+ export function systemDocPath(root) { return join(root, 'SYSTEM.md') }
26
+
27
+ /** The layer diagram, kept as data so it cannot break the surrounding code. */
28
+ const DIAGRAM = [
29
+ 'global/ workspaces/<key>/',
30
+ ' semantic.md semantic.md',
31
+ ' semantic/<topic>.md semantic/<topic>.md',
32
+ ' episodic.md + episodic/<topic>.md',
33
+ ' procedural.md + procedural/<topic>.md',
34
+ ' learnings.md + learnings/<topic>.md',
35
+ ' working.md working.md (single file, never indexed)',
36
+ ' junk.md junk.md (single file, never indexed)',
37
+ ' codebase.md + codebase/<topic>.md (workspace only)',
38
+ ' history.md + history/<day>.md (workspace only)',
39
+ ' state.md state.md (repo state; replaced per section)',
40
+ ' working.md working.md (YOUR task/activity head)',
41
+ ].join(String.fromCharCode(10))
42
+
43
+ /** Count the markdown files inside one layer directory, or 0 when it does not exist. */
44
+ function countTopics(root, scopeDir, layer) {
45
+ try {
46
+ return readdirSync(join(root, scopeDir, LAYER_DIRS[layer])).filter((n) => n.endsWith('.md')).length
47
+ } catch { return 0 }
48
+ }
49
+
50
+ /**
51
+ * Summarise what the store currently holds, so the document reports reality rather than intent.
52
+ * Best-effort: an unreadable scope is reported as absent, never thrown.
53
+ */
54
+ export function describeStore(root) {
55
+ const scopes = [{ label: 'global', dir: 'global' }]
56
+ try {
57
+ for (const name of readdirSync(join(root, 'workspaces'))) scopes.push({ label: name, dir: join('workspaces', name) })
58
+ } catch {}
59
+ return scopes.map((scope) => {
60
+ const counts = {}
61
+ for (const layer of LAYERED) counts[layer] = countTopics(root, scope.dir, layer)
62
+ return { scope: scope.label, counts, total: Object.values(counts).reduce((a, b) => a + b, 0) }
63
+ })
64
+ }
65
+
66
+ /** Format a millisecond cadence for humans: 3600000 -> '1h'. */
67
+ export function humanDuration(ms) {
68
+ const n = Number(ms)
69
+ if (!Number.isFinite(n) || n <= 0) return String(ms)
70
+ if (n % 86400000 === 0) return (n / 86400000) + 'd'
71
+ if (n % 3600000 === 0) return (n / 3600000) + 'h'
72
+ if (n % 60000 === 0) return (n / 60000) + 'm'
73
+ if (n % 1000 === 0) return (n / 1000) + 's'
74
+ return n + 'ms'
75
+ }
76
+
77
+ /** What each layer is for, keyed by layer so the table cannot drift from the vocabulary. */
78
+ const LAYER_PURPOSE = {
79
+ semantic: 'Durable facts that stay true: how something works, what was decided, environment facts, versions, where things live.',
80
+ episodic: 'What happened: an incident, a migration, a change made on a date and why.',
81
+ procedural: 'How to do something: runbooks, commands, build and test steps, order of operations.',
82
+ learning: 'A lesson from experience - what you wish you had known an hour earlier.',
83
+ codebase: 'What a module, file or subsystem of THIS repository does. Workspace only.',
84
+ history: 'How the repository changed: one entry per commit, grouped by day. Workspace only.',
85
+ decision: 'A settled choice and why it won: dependencies, conventions, architecture. Available in both scopes.',
86
+ }
87
+
88
+ /**
89
+ * The per-layer table, generated from the layer vocabulary so a newly added layer appears here
90
+ * automatically. A layer with no purpose recorded still gets a row, flagged rather than omitted.
91
+ */
92
+ function layerTable() {
93
+ const rows = ['| Layer | Code | Use it for |', '| --- | --- | --- |']
94
+ for (const layer of LAYERED) {
95
+ const purpose = LAYER_PURPOSE[layer] ?? '(undocumented)'
96
+ rows.push('| `' + layer + '` | `' + (LAYER_CODES[layer] ?? layer) + '` | ' + purpose + ' |')
97
+ }
98
+ return rows.join(String.fromCharCode(10))
99
+ }
100
+ /** The current-state table, so the document shows what this store actually holds. */
101
+ function storeState(root) {
102
+ if (!root) return '(store root unknown)'
103
+ const rows = describeStore(root)
104
+ const layers = LAYERED
105
+ const lines = [
106
+ '| Scope | ' + layers.join(' | ') + ' | total |',
107
+ '| --- | ' + layers.map(() => '---').join(' | ') + ' | --- |',
108
+ ]
109
+ for (const row of rows) {
110
+ lines.push('| ' + row.scope + ' | ' + layers.map((l) => row.counts[l]).join(' | ') + ' | ' + row.total + ' |')
111
+ }
112
+ return lines.join(String.fromCharCode(10))
113
+ }
114
+
115
+ /**
116
+ * Render the system document by filling the template.
117
+ * @param options.cfg - resolved plugin config, quoted so the document matches this build.
118
+ * @param options.version - plugin version.
119
+ * @param options.store - store root, used for the current-state table.
120
+ * @param options.now - ISO timestamp.
121
+ * @returns the markdown document.
122
+ */
123
+ export function renderSystemDoc(options = {}) {
124
+ const cfg = options.cfg ?? {}
125
+ const template = options.template ?? readFileSync(templatePath(), 'utf8')
126
+ const values = {
127
+ documentVersion: String(SYSTEM_DOC_VERSION),
128
+ version: String(options.version ?? 'unknown'),
129
+ generated: options.now ?? new Date().toISOString(),
130
+ store: String(options.store ?? '(unknown)'),
131
+ diagram: DIAGRAM,
132
+ layerTable: layerTable(),
133
+ workspaceOnly: WORKSPACE_ONLY_LAYERS.map((l) => '`' + l + '`').join(' and '),
134
+ captureInterval: humanDuration(cfg.autoCaptureIntervalMs),
135
+ consolidateInterval: humanDuration(cfg.autoConsolidateIntervalMs),
136
+ refreshInterval: humanDuration(cfg.autoRefreshIntervalMs),
137
+ historyInterval: humanDuration(cfg.autoHistoryIntervalMs),
138
+ syncInterval: humanDuration(cfg.autoSyncIntervalMs),
139
+ commitDebounce: humanDuration(cfg.commitDebounceMs),
140
+ effort: String(cfg.memoryReasoningEffort ?? '(model default)'),
141
+ minSignalChars: String(cfg.summaryMinSignalChars ?? ''),
142
+ digestKeep: String(cfg.digestKeepCount ?? ''),
143
+ stableDays: String(VOLATILITY_DAYS.stable),
144
+ normalDays: String(VOLATILITY_DAYS.normal),
145
+ volatileDays: String(VOLATILITY_DAYS.volatile),
146
+ storeState: storeState(options.store),
147
+ }
148
+ return template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
149
+ return Object.hasOwn(values, key) ? String(values[key]) : match
150
+ })
151
+ }
152
+
153
+ /**
154
+ * Write SYSTEM.md into a store. Always overwrites: the document is generated, never edited
155
+ * in place, so a stale copy must not survive a boot.
156
+ * @returns the path written.
157
+ */
158
+ export function writeSystemDoc(root, options = {}) {
159
+ const target = systemDocPath(root)
160
+ writeFileSync(target, renderSystemDoc({ ...options, store: root }), 'utf8')
161
+ return target
162
+ }
163
+
164
+ /** True when the on-disk document is missing or was written by an older version. */
165
+ export function systemDocStale(root) {
166
+ try {
167
+ const text = readFileSync(systemDocPath(root), 'utf8')
168
+ const found = /Document version: (\d+)/.exec(text)
169
+ return found === null || Number(found[1]) !== SYSTEM_DOC_VERSION
170
+ } catch { return true }
171
+ }