@modusensus/dsh-mneme 0.4.6 → 0.4.7

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
@@ -184,6 +184,7 @@ v0.3.0 起新增**记忆基因**层:从记忆里抽取**命名实体**、**带
184
184
  | **v0.4.4** | ✅ 完成 | autoDream 决策覆盖修复 | issue#9 方案C:滑动窗口 `dreamMaxSnapshotSize`(默认200,updated_at 倒序截断) + 隐式 keep `dreamImplicitKeep`(默认true) + 覆盖率下限 `dreamMinExplicitCoverage`(默认50%) + 固定决策 schema;487 测试全绿 |
185
185
  | **v0.4.5** | ✅ 完成 | epistemic trust + recall eval | 记忆可信度分级 `trustEpistemicWeighting`(observation>inferred>subjective:检索排序优先高可信、注入标注 `[verified]`、dream merge/conflict 偏向高可信;opt-in 默认关)+ 检索评估 `evaluateRetrieval` 落库 `recall_evals`(`evalPersistTestResults` opt-in 默认关,生产检索始终走 `recall_runs` 无条件隔离);518 测试全绿 |
186
186
  | **v0.4.6** | ✅ 完成 | 8 项修复(向量链路 + 注入/质量/审计) | 向量链路修复(embedSingle 适配 / `autoReindexOnBoot` 存量回填 / `vector_meta` 元数据)+ 注入语义召回 `hybridInject` + 同标题追加 `content_history` + 注入长度上限(单条 300 / 整块 1500)+ 记忆质量过滤 `memoryQualityFilter` + LLM 消耗审计 `llmAudit`(表 + 埋点 + 只读 API);553 测试全绿 |
187
+ | **v0.4.7** | ✅ 完成 | schema 迁移幂等化 | 并发打开同一 db 时 `PRAGMA table_info` 检查与 ALTER 非原子,可能重复 `ADD COLUMN` 报 duplicate column name;改用 `addColumn` helper 吞掉竞态(try/catch),12 处迁移统一收口 |
187
188
  | **v0.5.0+** | 🚀 远期 | 自进化记忆 | 兴趣漂移跟踪 + 跨 workspace 记忆共享(等 DSH 支持) |
188
189
 
189
190
  > 新能力一律做成**可开关的功能**(配置启用/关闭),默认保守开启、不破坏现有行为。`failure_memories` 表与 autoDream 决策引擎已为后续反思性成长铺好路。
package/lib/store.js CHANGED
@@ -548,51 +548,39 @@ export function createStore(path) {
548
548
  db.exec("PRAGMA journal_mode = WAL;");
549
549
  db.exec(SCHEMA);
550
550
 
551
- // Schema migrations for legacy databases (idempotent).
552
- const columns = db.prepare("PRAGMA table_info(memories)").all().map((c) => c.name);
553
- if (!columns.includes("archived")) {
554
- db.exec("ALTER TABLE memories ADD COLUMN archived INTEGER NOT NULL DEFAULT 0");
555
- }
556
- if (!columns.includes("embedding")) {
557
- db.exec("ALTER TABLE memories ADD COLUMN embedding TEXT");
558
- }
559
- if (!columns.includes("last_accessed_at")) {
560
- db.exec("ALTER TABLE memories ADD COLUMN last_accessed_at TEXT");
561
- }
562
- if (!columns.includes("_full_content")) {
563
- db.exec("ALTER TABLE memories ADD COLUMN _full_content TEXT");
564
- }
565
- if (!columns.includes("epistemic_status")) {
566
- db.exec("ALTER TABLE memories ADD COLUMN epistemic_status TEXT NOT NULL DEFAULT 'subjective'");
567
- }
568
- if (!columns.includes("content_history")) {
569
- db.exec("ALTER TABLE memories ADD COLUMN content_history TEXT");
570
- }
571
- if (!columns.includes("quality_score")) {
572
- db.exec("ALTER TABLE memories ADD COLUMN quality_score REAL");
573
- }
551
+ // Schema migrations for legacy databases (idempotent). Each ADD COLUMN is
552
+ // also race-safe: two concurrently-opening processes can both pass the
553
+ // PRAGMA table_info check before either ALTERs, so the ALTER itself is
554
+ // guarded against the "duplicate column name" error SQLite raises when the
555
+ // other process won the race (SQLite has no ADD COLUMN IF NOT EXISTS).
556
+ const addColumn = (table, column, ddl) => {
557
+ const cols = db.prepare(`PRAGMA table_info(${table})`).all().map((c) => c.name);
558
+ if (!cols.includes(column)) {
559
+ try {
560
+ db.exec(ddl);
561
+ } catch (e) {
562
+ if (!/duplicate column name/i.test(String(e?.message ?? e))) throw e;
563
+ }
564
+ }
565
+ };
566
+
567
+ addColumn("memories", "archived", "ALTER TABLE memories ADD COLUMN archived INTEGER NOT NULL DEFAULT 0");
568
+ addColumn("memories", "embedding", "ALTER TABLE memories ADD COLUMN embedding TEXT");
569
+ addColumn("memories", "last_accessed_at", "ALTER TABLE memories ADD COLUMN last_accessed_at TEXT");
570
+ addColumn("memories", "_full_content", "ALTER TABLE memories ADD COLUMN _full_content TEXT");
571
+ addColumn("memories", "epistemic_status", "ALTER TABLE memories ADD COLUMN epistemic_status TEXT NOT NULL DEFAULT 'subjective'");
572
+ addColumn("memories", "content_history", "ALTER TABLE memories ADD COLUMN content_history TEXT");
573
+ addColumn("memories", "quality_score", "ALTER TABLE memories ADD COLUMN quality_score REAL");
574
574
 
575
575
  // Legacy dream_runs without policy_epoch → backfill with the default epoch.
576
- const dreamCols = db.prepare("PRAGMA table_info(dream_runs)").all().map((c) => c.name);
577
- if (!dreamCols.includes("policy_epoch")) {
578
- db.exec("ALTER TABLE dream_runs ADD COLUMN policy_epoch INTEGER NOT NULL DEFAULT 0");
579
- }
580
- if (!dreamCols.includes("run_type")) {
581
- db.exec("ALTER TABLE dream_runs ADD COLUMN run_type TEXT NOT NULL DEFAULT 'auto'");
582
- }
576
+ addColumn("dream_runs", "policy_epoch", "ALTER TABLE dream_runs ADD COLUMN policy_epoch INTEGER NOT NULL DEFAULT 0");
577
+ addColumn("dream_runs", "run_type", "ALTER TABLE dream_runs ADD COLUMN run_type TEXT NOT NULL DEFAULT 'auto'");
583
578
 
584
579
  // Legacy mirror_state without v0.3.6 generation columns → add each missing
585
580
  // column idempotently (old DBs open cleanly, no data loss).
586
- const mirrorCols = db.prepare("PRAGMA table_info(mirror_state)").all().map((c) => c.name);
587
- if (!mirrorCols.includes("generation")) {
588
- db.exec("ALTER TABLE mirror_state ADD COLUMN generation INTEGER NOT NULL DEFAULT 0");
589
- }
590
- if (!mirrorCols.includes("applied_generation")) {
591
- db.exec("ALTER TABLE mirror_state ADD COLUMN applied_generation INTEGER NOT NULL DEFAULT 0");
592
- }
593
- if (!mirrorCols.includes("type_status")) {
594
- db.exec("ALTER TABLE mirror_state ADD COLUMN type_status TEXT");
595
- }
581
+ addColumn("mirror_state", "generation", "ALTER TABLE mirror_state ADD COLUMN generation INTEGER NOT NULL DEFAULT 0");
582
+ addColumn("mirror_state", "applied_generation", "ALTER TABLE mirror_state ADD COLUMN applied_generation INTEGER NOT NULL DEFAULT 0");
583
+ addColumn("mirror_state", "type_status", "ALTER TABLE mirror_state ADD COLUMN type_status TEXT");
596
584
 
597
585
  // Audit peer F: a legacy DB may hold a non-integer generation/applied_generation
598
586
  // (pre-v0.3.9 the JS gate truncated with Math.trunc and SQLite's CHECK only
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@modusensus/dsh-mneme",
3
3
  "description": "Cross-session memory plugin for DeepSeek Harness with autoDream consolidation: SQLite store, Markdown mirrors, 7 model tools, automatic injection, session summarization, user profile/rules, custom slash commands, vector (semantic) search, and a Web GUI panel",
4
- "version": "0.4.6",
4
+ "version": "0.4.7",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "main": "lib/index.js",
package/src/store.js CHANGED
@@ -548,51 +548,39 @@ export function createStore(path) {
548
548
  db.exec("PRAGMA journal_mode = WAL;");
549
549
  db.exec(SCHEMA);
550
550
 
551
- // Schema migrations for legacy databases (idempotent).
552
- const columns = db.prepare("PRAGMA table_info(memories)").all().map((c) => c.name);
553
- if (!columns.includes("archived")) {
554
- db.exec("ALTER TABLE memories ADD COLUMN archived INTEGER NOT NULL DEFAULT 0");
555
- }
556
- if (!columns.includes("embedding")) {
557
- db.exec("ALTER TABLE memories ADD COLUMN embedding TEXT");
558
- }
559
- if (!columns.includes("last_accessed_at")) {
560
- db.exec("ALTER TABLE memories ADD COLUMN last_accessed_at TEXT");
561
- }
562
- if (!columns.includes("_full_content")) {
563
- db.exec("ALTER TABLE memories ADD COLUMN _full_content TEXT");
564
- }
565
- if (!columns.includes("epistemic_status")) {
566
- db.exec("ALTER TABLE memories ADD COLUMN epistemic_status TEXT NOT NULL DEFAULT 'subjective'");
567
- }
568
- if (!columns.includes("content_history")) {
569
- db.exec("ALTER TABLE memories ADD COLUMN content_history TEXT");
570
- }
571
- if (!columns.includes("quality_score")) {
572
- db.exec("ALTER TABLE memories ADD COLUMN quality_score REAL");
573
- }
551
+ // Schema migrations for legacy databases (idempotent). Each ADD COLUMN is
552
+ // also race-safe: two concurrently-opening processes can both pass the
553
+ // PRAGMA table_info check before either ALTERs, so the ALTER itself is
554
+ // guarded against the "duplicate column name" error SQLite raises when the
555
+ // other process won the race (SQLite has no ADD COLUMN IF NOT EXISTS).
556
+ const addColumn = (table, column, ddl) => {
557
+ const cols = db.prepare(`PRAGMA table_info(${table})`).all().map((c) => c.name);
558
+ if (!cols.includes(column)) {
559
+ try {
560
+ db.exec(ddl);
561
+ } catch (e) {
562
+ if (!/duplicate column name/i.test(String(e?.message ?? e))) throw e;
563
+ }
564
+ }
565
+ };
566
+
567
+ addColumn("memories", "archived", "ALTER TABLE memories ADD COLUMN archived INTEGER NOT NULL DEFAULT 0");
568
+ addColumn("memories", "embedding", "ALTER TABLE memories ADD COLUMN embedding TEXT");
569
+ addColumn("memories", "last_accessed_at", "ALTER TABLE memories ADD COLUMN last_accessed_at TEXT");
570
+ addColumn("memories", "_full_content", "ALTER TABLE memories ADD COLUMN _full_content TEXT");
571
+ addColumn("memories", "epistemic_status", "ALTER TABLE memories ADD COLUMN epistemic_status TEXT NOT NULL DEFAULT 'subjective'");
572
+ addColumn("memories", "content_history", "ALTER TABLE memories ADD COLUMN content_history TEXT");
573
+ addColumn("memories", "quality_score", "ALTER TABLE memories ADD COLUMN quality_score REAL");
574
574
 
575
575
  // Legacy dream_runs without policy_epoch → backfill with the default epoch.
576
- const dreamCols = db.prepare("PRAGMA table_info(dream_runs)").all().map((c) => c.name);
577
- if (!dreamCols.includes("policy_epoch")) {
578
- db.exec("ALTER TABLE dream_runs ADD COLUMN policy_epoch INTEGER NOT NULL DEFAULT 0");
579
- }
580
- if (!dreamCols.includes("run_type")) {
581
- db.exec("ALTER TABLE dream_runs ADD COLUMN run_type TEXT NOT NULL DEFAULT 'auto'");
582
- }
576
+ addColumn("dream_runs", "policy_epoch", "ALTER TABLE dream_runs ADD COLUMN policy_epoch INTEGER NOT NULL DEFAULT 0");
577
+ addColumn("dream_runs", "run_type", "ALTER TABLE dream_runs ADD COLUMN run_type TEXT NOT NULL DEFAULT 'auto'");
583
578
 
584
579
  // Legacy mirror_state without v0.3.6 generation columns → add each missing
585
580
  // column idempotently (old DBs open cleanly, no data loss).
586
- const mirrorCols = db.prepare("PRAGMA table_info(mirror_state)").all().map((c) => c.name);
587
- if (!mirrorCols.includes("generation")) {
588
- db.exec("ALTER TABLE mirror_state ADD COLUMN generation INTEGER NOT NULL DEFAULT 0");
589
- }
590
- if (!mirrorCols.includes("applied_generation")) {
591
- db.exec("ALTER TABLE mirror_state ADD COLUMN applied_generation INTEGER NOT NULL DEFAULT 0");
592
- }
593
- if (!mirrorCols.includes("type_status")) {
594
- db.exec("ALTER TABLE mirror_state ADD COLUMN type_status TEXT");
595
- }
581
+ addColumn("mirror_state", "generation", "ALTER TABLE mirror_state ADD COLUMN generation INTEGER NOT NULL DEFAULT 0");
582
+ addColumn("mirror_state", "applied_generation", "ALTER TABLE mirror_state ADD COLUMN applied_generation INTEGER NOT NULL DEFAULT 0");
583
+ addColumn("mirror_state", "type_status", "ALTER TABLE mirror_state ADD COLUMN type_status TEXT");
596
584
 
597
585
  // Audit peer F: a legacy DB may hold a non-integer generation/applied_generation
598
586
  // (pre-v0.3.9 the JS gate truncated with Math.trunc and SQLite's CHECK only