@try-works/dsh-memory 0.1.28 → 0.1.29

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/STATUS.md CHANGED
@@ -3,8 +3,9 @@
3
3
  A tracker and planner: what exists, what is verified working, what is written but not
4
4
  loaded, and what is left. Kept as a record of MEASURED state, not intention.
5
5
 
6
- **Last measured: 2026-10-17** (408 tests passing; plugin at 0.1.27, published and installed
7
- byte-identical and confirmed LOADED by the running host; queue terminal-clean).
6
+ **Last measured: 2026-10-17** (408 tests passing; plugin at 0.1.28, published and installed
7
+ byte-identical and confirmed LOADED by the running harness at 0.2.1-rc.2; queue terminal-clean;
8
+ the Memory tab reads the published map under the new event name).
8
9
 
9
10
  ## How to read this
10
11
 
@@ -67,6 +68,7 @@ Observed in the running host. Each row names the effect that was seen, not a cla
67
68
  | Boot tasks backup + restore ran live | at boot (2026-10-10T22:03:14Z) the log records `tasks-backup ok:true written:0 refreshed:5` and `tasks-restore ok:true owner:session-6e8d9a10 present:5 added:0 paused:0` — the restore reached the schedule service, found everything present, and duplicated nothing |
68
69
  | Git sync works live | after the 0.1.22+ boot the log shows real `sync` events, `state/sync.json` records `ok:true remote:https://github.com/try-works/dsh-memory-store` (2026-10-11T10:34:54Z), and `git log` in the store holds `memory(store): update [dsh-memory]` commits — the first syncs this plugin has ever performed |
69
70
  | History ingest works live | `history-check ok=true` entries after the boot, including `count=1` at 09:37Z — a commit was actually ingested; the hourly `null.run` error era is over |
71
+ | Map publishing works on harness 0.2.1 | after the 0.1.28 boot the log shows `map-publish ok:true` on every cadence, the live session log carries `plugin:memory/map` rows with `ignorable:true`, the harness's own `validateStoredEvents` accepts the log, and the Memory tab renders (confirmed in the UI). The 0.2.1 contract is different from what 0.1.27 assumed: `Session.append` takes options only for surface event types, so an out-of-repo record is written with `appendPluginRecord` under the `plugin:` namespace |
70
72
 
71
73
  ## Shipped and LOADED
72
74
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@try-works/dsh-memory",
3
- "version": "0.1.28",
3
+ "version": "0.1.29",
4
4
  "description": "Git-backed, workspace-scoped memory for the DeepSeek Harness (global layer + per-workspace layers)",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/src/fts.js CHANGED
@@ -54,11 +54,43 @@ export function buildTrigramQuery(input) {
54
54
  /**
55
55
  * Create both indexes when missing. The word table serves normal queries; the trigram table
56
56
  * serves substring queries (no-space scripts), which the word tokenizer cannot express.
57
+ *
58
+ * MANDATORY, both tables. The trigram table is the ONLY route for a no-space script such as
59
+ * Chinese or Japanese, and the fallback for a partial-word query that the word table missed, so
60
+ * failing to create it must not be swallowed: a store without it silently makes a whole class of
61
+ * queries unanswerable, and the caller sees "no results" rather than an error. Throw here, where
62
+ * the cause is still visible, instead of degrading search quietly.
57
63
  */
58
64
  function ensureTables(db) {
59
65
  db.exec('CREATE VIRTUAL TABLE IF NOT EXISTS mem_fts USING fts5(file, line, section, content)')
60
- try { db.exec("CREATE VIRTUAL TABLE IF NOT EXISTS " + SUBSTRING_TABLE + " USING fts5(file, line, section, content, tokenize='trigram')") }
61
- catch { /* a build without trigram still works, just without substring search */ }
66
+ try {
67
+ db.exec("CREATE VIRTUAL TABLE IF NOT EXISTS " + SUBSTRING_TABLE + " USING fts5(file, line, section, content, tokenize='trigram')")
68
+ } catch (error) {
69
+ throw new Error('the substring (trigram) index is required and could not be created: ' + String((error && error.message) || error))
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Run a block of writes inside ONE transaction.
75
+ *
76
+ * Without this, every INSERT is its own implicit transaction, so a row costs a commit and an
77
+ * fsync. A full rebuild of this store writes ~292,000 rows (word table + trigram table), and a
78
+ * measured rebuild did not finish in ten minutes. Batched, the same work is seconds - which is
79
+ * what makes "the index is derived, delete it and rebuild" a safe recovery step instead of an
80
+ * outage for every query.
81
+ * @param run - the work to perform inside the transaction.
82
+ * @returns whatever `run` returned, after the transaction commits.
83
+ */
84
+ function inTransaction(db, run) {
85
+ db.exec('BEGIN')
86
+ try {
87
+ const result = run()
88
+ db.exec('COMMIT')
89
+ return result
90
+ } catch (error) {
91
+ try { db.exec('ROLLBACK') } catch { /* a failed rollback must not mask the real error */ }
92
+ throw error
93
+ }
62
94
  }
63
95
 
64
96
  /**
@@ -122,11 +154,13 @@ export function updateIndex(root, files) {
122
154
  let dropTri
123
155
  try { dropTri = db.prepare('DELETE FROM ' + SUBSTRING_TABLE + ' WHERE file = ?') } catch { dropTri = undefined }
124
156
  let rows = 0
125
- for (const file of list) {
126
- drop.run(relOf(root, file))
127
- if (dropTri) try { dropTri.run(relOf(root, file)) } catch {}
128
- try { rows += indexFile(db, root, file) } catch {}
129
- }
157
+ inTransaction(db, () => {
158
+ for (const file of list) {
159
+ drop.run(relOf(root, file))
160
+ if (dropTri) try { dropTri.run(relOf(root, file)) } catch {}
161
+ try { rows += indexFile(db, root, file) } catch {}
162
+ }
163
+ })
130
164
  db.close()
131
165
  return { rows, files: list.length }
132
166
  }
@@ -159,8 +193,11 @@ export function buildIndex(root) {
159
193
  db.exec('DROP TABLE IF EXISTS mem_fts')
160
194
  db.exec('DROP TABLE IF EXISTS ' + SUBSTRING_TABLE)
161
195
  ensureTables(db)
162
- let rows = 0
163
- for (const file of indexedFiles(root)) rows += indexFile(db, root, file)
196
+ const rows = inTransaction(db, () => {
197
+ let total = 0
198
+ for (const file of indexedFiles(root)) total += indexFile(db, root, file)
199
+ return total
200
+ })
164
201
  db.close()
165
202
  return { dbPath, rows }
166
203
  }
@@ -1,13 +0,0 @@
1
- import { Context } from "@deepseek-ai/cordis"
2
- import { SessionStore } from "@deepseek-ai/dsh-session"
3
- import JsonlSessionPersistence from "@deepseek-ai/dsh-session-persistence-jsonl"
4
- const ctx = new Context()
5
- await ctx.plugin(SessionStore)
6
- await ctx.plugin(JsonlSessionPersistence, { root: "C:/Users/erikb/.dsh/sessions", compression: "zstd" })
7
- const p = ctx.get("sessionPersistence")
8
- const id = "session-b21ecc7e-7743-4b06-b07f-453541dbcde5"
9
- try {
10
- const h = await p.open(id, "read")
11
- const snap = await h.read()
12
- console.log("REAL OPEN OK — events:", snap?.events?.length ?? snap?.log?.length ?? "(shape)")
13
- } catch (e) { console.log("REAL OPEN FAILED:", String(e.message).slice(0, 300)) }
@@ -1,16 +0,0 @@
1
- import { readFileSync } from "node:fs"
2
- import { scanZstdFrames, decompressZstdFrame, decompressZstdPrefix } from "file:///D:/deepseek-harness/packages/session/session-persistence-jsonl/src/zstd.ts"
3
- import { validateStoredEvents } from "file:///D:/deepseek-harness/packages/session/session-persistence/src/storage-contract.ts"
4
- const buf = readFileSync(process.argv[2])
5
- const scan = scanZstdFrames(buf)
6
- let text = ""
7
- for (const f of scan.frames) text += (await decompressZstdFrame(buf.subarray(f.start, f.end))).toString("utf8")
8
- if (scan.tornStart !== undefined) text += (await decompressZstdPrefix(buf.subarray(scan.tornStart))).toString("utf8")
9
- const rows = text.split("\n").filter(Boolean).map(l => JSON.parse(l))
10
- const meta = rows[0]; const events = rows.slice(1)
11
- const dups = new Map(); for (const e of events) dups.set(e.seq, (dups.get(e.seq) ?? 0) + 1)
12
- console.log("events:", events.length, "| duplicate seqs:", [...dups].filter(([,n]) => n > 1).slice(0, 5))
13
- const bad = events.filter(e => e.type === "memory/map" && e.ignorable !== true)
14
- console.log("memory/map without marker:", bad.length)
15
- try { validateStoredEvents(meta, events, { kind: "jsonl", path: process.argv[2] }); console.log("VALIDATOR: OK") }
16
- catch (e) { console.log("VALIDATOR THREW:", String(e.message).slice(0, 300)) }