watashi-db 0.0.11 → 0.0.13

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 (57) hide show
  1. package/LICENSE +71 -21
  2. package/README.md +9 -1
  3. package/cowork-plugin/skills/groom/SKILL.md +72 -0
  4. package/dist/config/schema.d.ts +21 -0
  5. package/dist/config/schema.js +4 -0
  6. package/dist/config/schema.js.map +1 -1
  7. package/dist/config/validator.js +1 -0
  8. package/dist/config/validator.js.map +1 -1
  9. package/dist/constants.d.ts +1 -1
  10. package/dist/constants.js +1 -0
  11. package/dist/constants.js.map +1 -1
  12. package/dist/database/groom.js +5 -4
  13. package/dist/database/groom.js.map +1 -1
  14. package/dist/database/queries-core.d.ts +28 -2
  15. package/dist/database/queries-core.js +134 -17
  16. package/dist/database/queries-core.js.map +1 -1
  17. package/dist/database/queries.d.ts +18 -0
  18. package/dist/database/queries.js +7 -0
  19. package/dist/database/queries.js.map +1 -1
  20. package/dist/database/schema.d.ts +5 -0
  21. package/dist/database/schema.js +156 -1
  22. package/dist/database/schema.js.map +1 -1
  23. package/dist/embedding/embed-on-write.d.ts +3 -0
  24. package/dist/embedding/embed-on-write.js.map +1 -1
  25. package/dist/embedding/provider.d.ts +3 -0
  26. package/dist/embedding/provider.js +38 -1
  27. package/dist/embedding/provider.js.map +1 -1
  28. package/dist/hook.js +12 -2
  29. package/dist/hook.js.map +1 -1
  30. package/dist/resources/config-guide-content.d.ts +1 -1
  31. package/dist/resources/config-guide-content.js +5 -0
  32. package/dist/resources/config-guide-content.js.map +1 -1
  33. package/dist/server-instructions.js +3 -2
  34. package/dist/server-instructions.js.map +1 -1
  35. package/dist/server.js +10 -1
  36. package/dist/server.js.map +1 -1
  37. package/dist/store/federation.d.ts +21 -0
  38. package/dist/store/federation.js +56 -0
  39. package/dist/store/federation.js.map +1 -1
  40. package/dist/store/sync-manager.d.ts +3 -0
  41. package/dist/store/sync-manager.js +50 -28
  42. package/dist/store/sync-manager.js.map +1 -1
  43. package/dist/tools/claim-tools.js +1 -1
  44. package/dist/tools/claim-tools.js.map +1 -1
  45. package/dist/tools/decision-tools.js +11 -8
  46. package/dist/tools/decision-tools.js.map +1 -1
  47. package/dist/tools/episode-tools.js +13 -9
  48. package/dist/tools/episode-tools.js.map +1 -1
  49. package/dist/tools/maintenance-tools.d.ts +1 -0
  50. package/dist/tools/maintenance-tools.js +117 -6
  51. package/dist/tools/maintenance-tools.js.map +1 -1
  52. package/dist/tools/query-tools.js +34 -4
  53. package/dist/tools/query-tools.js.map +1 -1
  54. package/dist/types.d.ts +65 -59
  55. package/dist/types.js +36 -28
  56. package/dist/types.js.map +1 -1
  57. package/package.json +2 -2
@@ -1,3 +1,4 @@
1
+ import fs from "node:fs";
1
2
  // スキーマバージョン管理
2
3
  // 2026-02-11 修正: V2追加(L2 Core v0対応)
3
4
  // 元の実装: SCHEMA_VERSION = 1(初期SPO三つ組 + FTS5 + decisions + claim_history)
@@ -29,9 +30,15 @@
29
30
  // 2026-03-03 修正: V23追加(Claim に l1_content 追加 + embedding → l1_embedding リネーム — Issue #64)
30
31
  // 2026-03-04 修正: V24追加(user_memo/plan/issue の content → l1_content + user_memos に user_input 追加 — Issue #54)
31
32
  // 2026-03-06 修正: V25追加(user_issues に kind カラム追加 — Issue #74)
32
- const SCHEMA_VERSION = 27;
33
+ // 2026-03-13 修正: V28追加(evidence → l2_evidence, falsifier → l2_falsifier リネーム)
34
+ const SCHEMA_VERSION = 28;
33
35
  /**
34
36
  * データベーススキーマの初期化とマイグレーション
37
+ *
38
+ * 2026-03-14 修正: ファイルロックで排他制御
39
+ * 複数のMCPサーバーが同時起動した場合のレースコンディションを防止。
40
+ * ロックファイルが取得できない場合は一定時間リトライし、タイムアウト時はエラー。
41
+ * 注意: EXCLUSIVE トランザクションは使えない(applyVN 内で db.transaction() を使う箇所がありネスト不可)
35
42
  */
36
43
  export function initializeSchema(db) {
37
44
  // スキーマバージョン管理テーブル
@@ -43,6 +50,73 @@ export function initializeSchema(db) {
43
50
  `);
44
51
  const currentVersion = db.prepare("SELECT MAX(version) as version FROM schema_version").get();
45
52
  const version = currentVersion?.version ?? 0;
53
+ if (version >= SCHEMA_VERSION) {
54
+ return; // 既に最新
55
+ }
56
+ // マイグレーションが必要 — ファイルロックで排他制御
57
+ const dbPath = db.name; // better-sqlite3: DBファイルパス(:memory: の場合は空文字列)
58
+ const isFileDb = dbPath && dbPath !== ":memory:" && dbPath !== "";
59
+ const lockPath = isFileDb ? dbPath + ".migration-lock" : null;
60
+ if (!isFileDb || !lockPath) {
61
+ // インメモリDBの場合はロック不要(テスト環境等)
62
+ _applyMigrations(db, version);
63
+ return;
64
+ }
65
+ let lockFd = null;
66
+ try {
67
+ // ロックファイル取得(リトライあり: 500ms × 20回 = 最大10秒)
68
+ for (let i = 0; i < 20; i++) {
69
+ try {
70
+ lockFd = fs.openSync(lockPath, fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_WRONLY);
71
+ break; // ロック取得成功
72
+ }
73
+ catch (e) {
74
+ if (e.code === "EEXIST") {
75
+ // 別プロセスがマイグレーション中 — 待機
76
+ // ロックファイルが古すぎる場合(60秒以上)は強制削除(プロセスクラッシュ対応)
77
+ try {
78
+ const stat = fs.statSync(lockPath);
79
+ if (Date.now() - stat.mtimeMs > 60000) {
80
+ fs.unlinkSync(lockPath);
81
+ continue; // リトライ
82
+ }
83
+ }
84
+ catch { /* stat失敗 = 既に削除された */ }
85
+ // 500ms待機
86
+ const start = Date.now();
87
+ while (Date.now() - start < 500) { /* busy wait */ }
88
+ }
89
+ else {
90
+ throw e; // 予期しないエラー
91
+ }
92
+ }
93
+ }
94
+ if (lockFd === null) {
95
+ throw new Error("[watashi-db] マイグレーションロックの取得に失敗しました(タイムアウト)");
96
+ }
97
+ // ロック取得後にバージョンを再チェック(別プロセスが先に完了した可能性)
98
+ const recheckVersion = db.prepare("SELECT MAX(version) as version FROM schema_version").get();
99
+ const currentVer = recheckVersion?.version ?? 0;
100
+ if (currentVer >= SCHEMA_VERSION) {
101
+ return; // 別プロセスが先にマイグレーション完了
102
+ }
103
+ _applyMigrations(db, currentVer);
104
+ }
105
+ finally {
106
+ // ロックファイル解放
107
+ if (lockFd !== null) {
108
+ fs.closeSync(lockFd);
109
+ }
110
+ try {
111
+ fs.unlinkSync(lockPath);
112
+ }
113
+ catch { /* 既に削除済み */ }
114
+ }
115
+ }
116
+ /**
117
+ * マイグレーション本体(initializeSchema からファイルロック取得後に呼ばれる)
118
+ */
119
+ function _applyMigrations(db, version) {
46
120
  if (version < 1) {
47
121
  applyV1(db);
48
122
  }
@@ -124,6 +198,9 @@ export function initializeSchema(db) {
124
198
  if (version < 27) {
125
199
  applyV27(db);
126
200
  }
201
+ if (version < 28) {
202
+ applyV28(db);
203
+ }
127
204
  }
128
205
  /**
129
206
  * V1スキーマ: 初期テーブル構成
@@ -5499,6 +5576,84 @@ function applyV27(db) {
5499
5576
  END
5500
5577
  `);
5501
5578
  }
5579
+ /**
5580
+ * V28: evidence → l2_evidence, falsifier → l2_falsifier リネーム + claims_fts 再構築
5581
+ * 冪等: 既に l2_evidence が存在する場合はリネームをスキップ
5582
+ */
5583
+ function applyV28(db) {
5584
+ // カラムリネーム(冪等: 既にリネーム済みならスキップ)
5585
+ if (hasColumn(db, "claims", "evidence") && !hasColumn(db, "claims", "l2_evidence")) {
5586
+ db.exec(`ALTER TABLE claims RENAME COLUMN evidence TO l2_evidence`);
5587
+ }
5588
+ if (hasColumn(db, "claims", "falsifier") && !hasColumn(db, "claims", "l2_falsifier")) {
5589
+ db.exec(`ALTER TABLE claims RENAME COLUMN falsifier TO l2_falsifier`);
5590
+ }
5591
+ // claims_fts を再構築(l2_evidence/l2_falsifier 対応)
5592
+ db.exec(`
5593
+ DROP TRIGGER IF EXISTS claims_fts_insert;
5594
+ DROP TRIGGER IF EXISTS claims_fts_update;
5595
+ DROP TRIGGER IF EXISTS claims_fts_delete;
5596
+ DROP TABLE IF EXISTS claims_fts;
5597
+
5598
+ CREATE VIRTUAL TABLE claims_fts USING fts5(
5599
+ l2_subject, l2_predicate, l2_object, l2_evidence, l1_content, search_summary,
5600
+ content='claims', content_rowid='rowid', tokenize='trigram'
5601
+ );
5602
+
5603
+ -- FTS同期トリガー: INSERT
5604
+ CREATE TRIGGER claims_fts_insert AFTER INSERT ON claims BEGIN
5605
+ INSERT INTO claims_fts(rowid, l2_subject, l2_predicate, l2_object, l2_evidence, l1_content, search_summary)
5606
+ VALUES (new.rowid, new.l2_subject, new.l2_predicate, new.l2_object, new.l2_evidence, new.l1_content, new.search_summary);
5607
+ END;
5608
+
5609
+ -- FTS同期トリガー: UPDATE
5610
+ CREATE TRIGGER claims_fts_update AFTER UPDATE ON claims BEGIN
5611
+ INSERT INTO claims_fts(claims_fts, rowid, l2_subject, l2_predicate, l2_object, l2_evidence, l1_content, search_summary)
5612
+ VALUES ('delete', old.rowid, old.l2_subject, old.l2_predicate, old.l2_object, old.l2_evidence, old.l1_content, old.search_summary);
5613
+ INSERT INTO claims_fts(rowid, l2_subject, l2_predicate, l2_object, l2_evidence, l1_content, search_summary)
5614
+ VALUES (new.rowid, new.l2_subject, new.l2_predicate, new.l2_object, new.l2_evidence, new.l1_content, new.search_summary);
5615
+ END;
5616
+
5617
+ -- FTS同期トリガー: DELETE
5618
+ CREATE TRIGGER claims_fts_delete AFTER DELETE ON claims BEGIN
5619
+ INSERT INTO claims_fts(claims_fts, rowid, l2_subject, l2_predicate, l2_object, l2_evidence, l1_content, search_summary)
5620
+ VALUES ('delete', old.rowid, old.l2_subject, old.l2_predicate, old.l2_object, old.l2_evidence, old.l1_content, old.search_summary);
5621
+ END;
5622
+
5623
+ -- FTS rebuild
5624
+ INSERT INTO claims_fts(claims_fts) VALUES('rebuild');
5625
+
5626
+ INSERT INTO schema_version (version) VALUES (28);
5627
+ `);
5628
+ // unified_search_items の claims トリガー再作成(l2_evidence/l2_falsifier 対応)
5629
+ const coal = (col) => `COALESCE(${col}, '')`;
5630
+ const columns = "entity_type, entity_id, scope, category, title_summary, search_text, search_summary, tags, created_at, updated_at";
5631
+ const claimTitle = (p) => `${p}l2_subject || ' ' || ${p}l2_predicate || ' ' || ${p}l2_object`;
5632
+ const claimSearch = (p) => `${p}l2_subject || ' ' || ${p}l2_predicate || ' ' || ${p}l2_object || ' ' || ${coal(`${p}l2_evidence`)} || ' ' || ${coal(`${p}l2_falsifier`)} || ' ' || ${coal(`${p}l1_content`)} || ' ' || ${coal(`${p}search_summary`)}`;
5633
+ db.exec(`DROP TRIGGER IF EXISTS claims_unified_insert`);
5634
+ db.exec(`DROP TRIGGER IF EXISTS claims_unified_update`);
5635
+ db.exec(`DROP TRIGGER IF EXISTS claims_unified_delete`);
5636
+ db.exec(`
5637
+ CREATE TRIGGER claims_unified_insert AFTER INSERT ON claims
5638
+ WHEN new.status = 'active' BEGIN
5639
+ INSERT OR REPLACE INTO unified_search_items(${columns})
5640
+ VALUES ('claim', new.id, new.scope, new.category, ${claimTitle("new.")}, ${claimSearch("new.")}, new.search_summary, '[]', new.created_at, new.updated_at);
5641
+ END
5642
+ `);
5643
+ db.exec(`
5644
+ CREATE TRIGGER claims_unified_update AFTER UPDATE ON claims BEGIN
5645
+ DELETE FROM unified_search_items WHERE entity_type = 'claim' AND entity_id = old.id;
5646
+ INSERT INTO unified_search_items(${columns})
5647
+ SELECT 'claim', new.id, new.scope, new.category, ${claimTitle("new.")}, ${claimSearch("new.")}, new.search_summary, '[]', new.created_at, new.updated_at
5648
+ WHERE new.status = 'active';
5649
+ END
5650
+ `);
5651
+ db.exec(`
5652
+ CREATE TRIGGER claims_unified_delete AFTER DELETE ON claims BEGIN
5653
+ DELETE FROM unified_search_items WHERE entity_type = 'claim' AND entity_id = old.id;
5654
+ END
5655
+ `);
5656
+ }
5502
5657
  /** テーブルに指定カラムが存在するかチェック */
5503
5658
  function hasColumn(db, table, column) {
5504
5659
  const columns = db.prepare(`PRAGMA table_info(${table})`).all();