watashi-db 0.0.13 → 0.0.15
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/CLAUDE.md +36 -0
- package/LICENSE +1 -1
- package/README.md +64 -2
- package/cowork-plugin/skills/groom/SKILL.md +51 -15
- package/cowork-plugin/skills/recall/SKILL.md +5 -6
- package/cowork-plugin/skills/reflect/SKILL.md +4 -4
- package/cowork-plugin/skills/remember/SKILL.md +3 -3
- package/cowork-plugin/skills/session-start/SKILL.md +3 -3
- package/dist/auth/token.d.ts +7 -0
- package/dist/auth/token.js +14 -0
- package/dist/auth/token.js.map +1 -0
- package/dist/config/schema.js +1 -1
- package/dist/constants.d.ts +9 -9
- package/dist/constants.js +29 -43
- package/dist/constants.js.map +1 -1
- package/dist/database/archive.js +6 -6
- package/dist/database/groom.js +5 -5
- package/dist/database/groom.js.map +1 -1
- package/dist/database/queries-core.d.ts +109 -5
- package/dist/database/queries-core.js +546 -186
- package/dist/database/queries-core.js.map +1 -1
- package/dist/database/queries.d.ts +85 -5
- package/dist/database/queries.js +33 -0
- package/dist/database/queries.js.map +1 -1
- package/dist/database/schema.d.ts +1 -0
- package/dist/database/schema.js +2299 -406
- package/dist/database/schema.js.map +1 -1
- package/dist/embedding/embed-on-write.d.ts +7 -1
- package/dist/embedding/embed-on-write.js +8 -3
- package/dist/embedding/embed-on-write.js.map +1 -1
- package/dist/hook.js +17 -9
- package/dist/hook.js.map +1 -1
- package/dist/http-server.d.ts +6 -0
- package/dist/http-server.js +235 -0
- package/dist/http-server.js.map +1 -0
- package/dist/index.js +14 -3
- package/dist/index.js.map +1 -1
- package/dist/resources/config-guide-content.d.ts +1 -1
- package/dist/resources/config-guide-content.js +2 -2
- package/dist/server-core.d.ts +15 -0
- package/dist/server-core.js +113 -0
- package/dist/server-core.js.map +1 -0
- package/dist/server-instructions.js +27 -24
- package/dist/server-instructions.js.map +1 -1
- package/dist/server.d.ts +4 -1
- package/dist/server.js +9 -103
- package/dist/server.js.map +1 -1
- package/dist/setup.js +5 -6
- package/dist/setup.js.map +1 -1
- package/dist/store/federation.d.ts +12 -1
- package/dist/store/federation.js +38 -0
- package/dist/store/federation.js.map +1 -1
- package/dist/store/sync-manager.d.ts +1 -1
- package/dist/store/sync-manager.js +9 -9
- package/dist/tools/claim-tools.d.ts +1 -1
- package/dist/tools/claim-tools.js +30 -17
- package/dist/tools/claim-tools.js.map +1 -1
- package/dist/tools/decision-tools.d.ts +1 -1
- package/dist/tools/decision-tools.js +23 -7
- package/dist/tools/decision-tools.js.map +1 -1
- package/dist/tools/episode-tools.d.ts +1 -1
- package/dist/tools/episode-tools.js +28 -7
- package/dist/tools/episode-tools.js.map +1 -1
- package/dist/tools/file-tools.d.ts +3 -0
- package/dist/tools/file-tools.js +350 -0
- package/dist/tools/file-tools.js.map +1 -0
- package/dist/tools/get-tools.d.ts +1 -1
- package/dist/tools/get-tools.js +39 -5
- package/dist/tools/get-tools.js.map +1 -1
- package/dist/tools/helpers.d.ts +40 -0
- package/dist/tools/helpers.js +69 -0
- package/dist/tools/helpers.js.map +1 -1
- package/dist/tools/knowledge-tools.d.ts +1 -1
- package/dist/tools/knowledge-tools.js +38 -6
- package/dist/tools/knowledge-tools.js.map +1 -1
- package/dist/tools/maintenance-tools.d.ts +1 -1
- package/dist/tools/maintenance-tools.js +95 -52
- package/dist/tools/maintenance-tools.js.map +1 -1
- package/dist/tools/memo-tools.d.ts +7 -11
- package/dist/tools/memo-tools.js +509 -309
- package/dist/tools/memo-tools.js.map +1 -1
- package/dist/tools/query-tools.d.ts +1 -1
- package/dist/tools/query-tools.js +266 -242
- package/dist/tools/query-tools.js.map +1 -1
- package/dist/types.d.ts +513 -76
- package/dist/types.js +185 -33
- package/dist/types.js.map +1 -1
- package/misc/20260316_110841_groom-recipe.md +483 -0
- package/misc/20260316_xaml-testing-library-recipe.md +817 -0
- package/misc/20260331_remote-transport-recipe.md +316 -0
- package/package.json +4 -2
- package/scripts/update-license-version.sh +7 -0
package/dist/database/schema.js
CHANGED
|
@@ -25,13 +25,29 @@ import fs from "node:fs";
|
|
|
25
25
|
// 2026-02-28 修正: V18追加(user_plans + user_issues テーブル新設 — Issue #57)
|
|
26
26
|
// 2026-03-01 修正: V19追加(Insight l1/l2 拡張 — Issue #53)
|
|
27
27
|
// 2026-03-01 修正: V20追加(全エンティティ l1/l2 カラム拡張 — Issue #59)
|
|
28
|
-
// 2026-03-03 修正: V21追加(audit_log に
|
|
28
|
+
// 2026-03-03 修正: V21追加(audit_log に device_id カラム追加 + backfill)
|
|
29
29
|
// 2026-03-03 修正: V22追加(entity_id インデックス追加 — Issue #60)
|
|
30
30
|
// 2026-03-03 修正: V23追加(Claim に l1_content 追加 + embedding → l1_embedding リネーム — Issue #64)
|
|
31
31
|
// 2026-03-04 修正: V24追加(user_memo/plan/issue の content → l1_content + user_memos に user_input 追加 — Issue #54)
|
|
32
32
|
// 2026-03-06 修正: V25追加(user_issues に kind カラム追加 — Issue #74)
|
|
33
33
|
// 2026-03-13 修正: V28追加(evidence → l2_evidence, falsifier → l2_falsifier リネーム)
|
|
34
|
-
|
|
34
|
+
// 2026-03-18 修正: V29追加(user_topics テーブル新設)
|
|
35
|
+
// 2026-03-19 squash: applyV1 を V30 相当のフルスキーマに書き直し。
|
|
36
|
+
// 新規DBでは applyV1 が全テーブルを作成し schema_version (1)〜(30) を一括挿入するため、
|
|
37
|
+
// V2〜V30 のマイグレーションは全てスキップされる。既存ユーザー向けに V2〜V30 はそのまま残す。
|
|
38
|
+
// 2026-03-24 修正: V31追加(File Vault — user_files テーブル + unified search統合)
|
|
39
|
+
// 2026-04-02 修正: V34追加(user_topics.device_id 欠落修復)
|
|
40
|
+
const SCHEMA_VERSION = 34;
|
|
41
|
+
// devMode フラグ(startServer から schema 初期化経路に伝播)
|
|
42
|
+
// develop.db では released: false のマイグレーションも適用する
|
|
43
|
+
let _devMode = false;
|
|
44
|
+
export function setDevMode(enabled) {
|
|
45
|
+
_devMode = enabled;
|
|
46
|
+
}
|
|
47
|
+
// V31 以降で released フラグを使う場合:
|
|
48
|
+
// if (version < 31) {
|
|
49
|
+
// if (_devMode || V31_RELEASED) applyV31(db);
|
|
50
|
+
// }
|
|
35
51
|
/**
|
|
36
52
|
* データベーススキーマの初期化とマイグレーション
|
|
37
53
|
*
|
|
@@ -50,6 +66,11 @@ export function initializeSchema(db) {
|
|
|
50
66
|
`);
|
|
51
67
|
const currentVersion = db.prepare("SELECT MAX(version) as version FROM schema_version").get();
|
|
52
68
|
const version = currentVersion?.version ?? 0;
|
|
69
|
+
// 孤立した _new テーブルの掃除(マイグレーション部分適用の残骸)
|
|
70
|
+
const orphanedNewTables = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name LIKE '%_new'").all();
|
|
71
|
+
for (const { name } of orphanedNewTables) {
|
|
72
|
+
db.exec(`DROP TABLE IF EXISTS "${name}"`);
|
|
73
|
+
}
|
|
53
74
|
if (version >= SCHEMA_VERSION) {
|
|
54
75
|
return; // 既に最新
|
|
55
76
|
}
|
|
@@ -119,6 +140,10 @@ export function initializeSchema(db) {
|
|
|
119
140
|
function _applyMigrations(db, version) {
|
|
120
141
|
if (version < 1) {
|
|
121
142
|
applyV1(db);
|
|
143
|
+
// squash 対応: applyV1 が V30 相当のフルスキーマを作成し schema_version (1)〜(30) を
|
|
144
|
+
// 一括挿入した場合、以降のマイグレーションをスキップするために version を再読み込みする
|
|
145
|
+
const row = db.prepare("SELECT MAX(version) as version FROM schema_version").get();
|
|
146
|
+
version = row?.version ?? 1;
|
|
122
147
|
}
|
|
123
148
|
if (version < 2) {
|
|
124
149
|
applyV2(db);
|
|
@@ -201,409 +226,1464 @@ function _applyMigrations(db, version) {
|
|
|
201
226
|
if (version < 28) {
|
|
202
227
|
applyV28(db);
|
|
203
228
|
}
|
|
229
|
+
if (version < 29) {
|
|
230
|
+
applyV29(db);
|
|
231
|
+
}
|
|
232
|
+
if (version < 30) {
|
|
233
|
+
applyV30(db);
|
|
234
|
+
}
|
|
235
|
+
if (version < 31) {
|
|
236
|
+
applyV31(db);
|
|
237
|
+
}
|
|
238
|
+
if (version < 32) {
|
|
239
|
+
applyV32(db);
|
|
240
|
+
}
|
|
241
|
+
if (version < 33) {
|
|
242
|
+
applyV33(db);
|
|
243
|
+
}
|
|
244
|
+
if (version < 34) {
|
|
245
|
+
applyV34(db);
|
|
246
|
+
}
|
|
204
247
|
}
|
|
205
248
|
/**
|
|
206
|
-
* V1スキーマ:
|
|
249
|
+
* V1スキーマ: V30相当のフルスキーマ(squash済み)
|
|
250
|
+
*
|
|
251
|
+
* 2026-03-19: applyV1 を V30 時点の最終テーブル定義で書き直し。
|
|
252
|
+
* 新規ユーザーが V1→V30 まで逐次マイグレーションする非効率を解消。
|
|
253
|
+
* V2〜V30 の関数は既存ユーザー向けにそのまま残してある。
|
|
254
|
+
* applyV1 の最後に schema_version (1)〜(30) を全挿入することで、
|
|
255
|
+
* _applyMigrations の各 `if (version < N)` ガードにより V2〜V30 が全てスキップされる。
|
|
256
|
+
*/
|
|
257
|
+
// SQL ヘルパー(FTSトリガー・unified_search トリガー共通)
|
|
258
|
+
const _jc = (col) => `replace(replace(replace(${col}, '["',''), '"]',''), '","',' ')`;
|
|
259
|
+
const _jcTopics = (col) => `REPLACE(REPLACE(${col}, '[', ''), ']', '')`;
|
|
260
|
+
const _coal = (col) => `COALESCE(${col}, '')`;
|
|
261
|
+
const _uniColumns = "entity_type, entity_id, scope, category, title_summary, search_text, search_summary, tags, created_at, updated_at";
|
|
262
|
+
/**
|
|
263
|
+
* unified_search_items / unified_search_fts のトリガーを作成する。
|
|
264
|
+
* applyV1(新規DB)と applyV32(既存DBマイグレーション)の両方から呼ばれる。
|
|
265
|
+
* 呼び出し前に旧トリガーの DROP が必要。
|
|
207
266
|
*/
|
|
267
|
+
function createUnifiedSearchTriggers(db) {
|
|
268
|
+
const jc = _jc, jcTopics = _jcTopics, coal = _coal, columns = _uniColumns;
|
|
269
|
+
// --- unified_search_fts triggers ---
|
|
270
|
+
db.exec(`
|
|
271
|
+
CREATE TRIGGER unified_search_fts_insert AFTER INSERT ON unified_search_items BEGIN
|
|
272
|
+
INSERT INTO unified_search_fts(rowid, search_text, title_summary, search_summary)
|
|
273
|
+
VALUES (new.rowid, new.search_text, new.title_summary, new.search_summary);
|
|
274
|
+
END
|
|
275
|
+
`);
|
|
276
|
+
db.exec(`
|
|
277
|
+
CREATE TRIGGER unified_search_fts_update AFTER UPDATE ON unified_search_items BEGIN
|
|
278
|
+
INSERT INTO unified_search_fts(unified_search_fts, rowid, search_text, title_summary, search_summary)
|
|
279
|
+
VALUES ('delete', old.rowid, old.search_text, old.title_summary, old.search_summary);
|
|
280
|
+
INSERT INTO unified_search_fts(rowid, search_text, title_summary, search_summary)
|
|
281
|
+
VALUES (new.rowid, new.search_text, new.title_summary, new.search_summary);
|
|
282
|
+
END
|
|
283
|
+
`);
|
|
284
|
+
db.exec(`
|
|
285
|
+
CREATE TRIGGER unified_search_fts_delete AFTER DELETE ON unified_search_items BEGIN
|
|
286
|
+
INSERT INTO unified_search_fts(unified_search_fts, rowid, search_text, title_summary, search_summary)
|
|
287
|
+
VALUES ('delete', old.rowid, old.search_text, old.title_summary, old.search_summary);
|
|
288
|
+
END
|
|
289
|
+
`);
|
|
290
|
+
// --- claims ---
|
|
291
|
+
const claimTitle = (p) => `${p}l2_subject || ' ' || ${p}l2_predicate || ' ' || ${p}l2_object`;
|
|
292
|
+
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`)}`;
|
|
293
|
+
db.exec(`
|
|
294
|
+
CREATE TRIGGER claims_unified_insert AFTER INSERT ON claims WHEN new.validity_status = 'active' AND new.is_archived = 0 AND new.promoted_to IS NULL BEGIN
|
|
295
|
+
INSERT OR REPLACE INTO unified_search_items(${columns})
|
|
296
|
+
VALUES ('claim', new.id, new.scope, new.category, ${claimTitle("new.")}, ${claimSearch("new.")}, new.search_summary, '[]', new.created_at, new.updated_at);
|
|
297
|
+
END
|
|
298
|
+
`);
|
|
299
|
+
db.exec(`
|
|
300
|
+
CREATE TRIGGER claims_unified_update AFTER UPDATE ON claims BEGIN
|
|
301
|
+
DELETE FROM unified_search_items WHERE entity_type = 'claim' AND entity_id = old.id;
|
|
302
|
+
INSERT INTO unified_search_items(${columns})
|
|
303
|
+
SELECT 'claim', new.id, new.scope, new.category, ${claimTitle("new.")}, ${claimSearch("new.")}, new.search_summary, '[]', new.created_at, new.updated_at WHERE new.validity_status = 'active' AND new.is_archived = 0 AND new.promoted_to IS NULL;
|
|
304
|
+
END
|
|
305
|
+
`);
|
|
306
|
+
db.exec(`
|
|
307
|
+
CREATE TRIGGER claims_unified_delete AFTER DELETE ON claims BEGIN
|
|
308
|
+
DELETE FROM unified_search_items WHERE entity_type = 'claim' AND entity_id = old.id;
|
|
309
|
+
END
|
|
310
|
+
`);
|
|
311
|
+
// --- episodes ---
|
|
312
|
+
db.exec(`
|
|
313
|
+
CREATE TRIGGER episodes_unified_insert AFTER INSERT ON episodes WHEN new.validity_status = 'active' AND new.is_archived = 0 AND new.promoted_to IS NULL BEGIN
|
|
314
|
+
INSERT OR REPLACE INTO unified_search_items(${columns})
|
|
315
|
+
VALUES ('episode', new.id, new.scope, NULL, new.title, new.title || ' ' || ${coal("new.l1_content")} || ' ' || ${coal("new.l2_context")} || ' ' || ${coal("new.l2_trigger")} || ' ' || ${jc("new.l2_problems")} || ' ' || ${jc("new.l2_outcomes")} || ' ' || ${jc("new.l2_principles")} || ' ' || ${jc("new.tags")} || ' ' || ${coal("new.search_summary")}, new.search_summary, new.tags, new.created_at, new.updated_at);
|
|
316
|
+
END
|
|
317
|
+
`);
|
|
318
|
+
db.exec(`
|
|
319
|
+
CREATE TRIGGER episodes_unified_update AFTER UPDATE ON episodes BEGIN
|
|
320
|
+
DELETE FROM unified_search_items WHERE entity_type = 'episode' AND entity_id = old.id;
|
|
321
|
+
INSERT INTO unified_search_items(${columns})
|
|
322
|
+
SELECT 'episode', new.id, new.scope, NULL, new.title, new.title || ' ' || ${coal("new.l1_content")} || ' ' || ${coal("new.l2_context")} || ' ' || ${coal("new.l2_trigger")} || ' ' || ${jc("new.l2_problems")} || ' ' || ${jc("new.l2_outcomes")} || ' ' || ${jc("new.l2_principles")} || ' ' || ${jc("new.tags")} || ' ' || ${coal("new.search_summary")}, new.search_summary, new.tags, new.created_at, new.updated_at WHERE new.validity_status = 'active' AND new.is_archived = 0 AND new.promoted_to IS NULL;
|
|
323
|
+
END
|
|
324
|
+
`);
|
|
325
|
+
db.exec(`
|
|
326
|
+
CREATE TRIGGER episodes_unified_delete AFTER DELETE ON episodes BEGIN
|
|
327
|
+
DELETE FROM unified_search_items WHERE entity_type = 'episode' AND entity_id = old.id;
|
|
328
|
+
END
|
|
329
|
+
`);
|
|
330
|
+
// --- decisions ---
|
|
331
|
+
db.exec(`
|
|
332
|
+
CREATE TRIGGER decisions_unified_insert AFTER INSERT ON decisions WHEN new.validity_status = 'active' AND new.is_archived = 0 AND new.promoted_to IS NULL BEGIN
|
|
333
|
+
INSERT OR REPLACE INTO unified_search_items(${columns})
|
|
334
|
+
VALUES ('decision', new.id, new.scope, NULL, new.title, new.title || ' ' || new.description || ' ' || ${coal("new.l1_content")} || ' ' || new.l2_reasoning || ' ' || ${jc("new.l2_alternatives")} || ' ' || ${coal("new.search_summary")}, new.search_summary, '[]', new.created_at, new.updated_at);
|
|
335
|
+
END
|
|
336
|
+
`);
|
|
337
|
+
db.exec(`
|
|
338
|
+
CREATE TRIGGER decisions_unified_update AFTER UPDATE ON decisions BEGIN
|
|
339
|
+
DELETE FROM unified_search_items WHERE entity_type = 'decision' AND entity_id = old.id;
|
|
340
|
+
INSERT INTO unified_search_items(${columns})
|
|
341
|
+
SELECT 'decision', new.id, new.scope, NULL, new.title, new.title || ' ' || new.description || ' ' || ${coal("new.l1_content")} || ' ' || new.l2_reasoning || ' ' || ${jc("new.l2_alternatives")} || ' ' || ${coal("new.search_summary")}, new.search_summary, '[]', new.created_at, new.updated_at WHERE new.validity_status = 'active' AND new.is_archived = 0 AND new.promoted_to IS NULL;
|
|
342
|
+
END
|
|
343
|
+
`);
|
|
344
|
+
db.exec(`
|
|
345
|
+
CREATE TRIGGER decisions_unified_delete AFTER DELETE ON decisions BEGIN
|
|
346
|
+
DELETE FROM unified_search_items WHERE entity_type = 'decision' AND entity_id = old.id;
|
|
347
|
+
END
|
|
348
|
+
`);
|
|
349
|
+
// --- theories, insights, models ---
|
|
350
|
+
for (const e of [
|
|
351
|
+
{ type: "theory", table: "theories" },
|
|
352
|
+
{ type: "insight", table: "insights" },
|
|
353
|
+
{ type: "model", table: "models" },
|
|
354
|
+
]) {
|
|
355
|
+
const searchExpr = (p) => `${p}title || ' ' || ${coal(`${p}description`)} || ' ' || ${coal(`${p}l1_content`)} || ' ' || ${coal(`${p}l2_core_thesis`)} || ' ' || ${jc(`${p}l2_principles`)} || ' ' || ${jc(`${p}evidence_refs`)} || ' ' || ${jc(`${p}tags`)} || ' ' || ${coal(`${p}search_summary`)}`;
|
|
356
|
+
db.exec(`
|
|
357
|
+
CREATE TRIGGER ${e.table}_unified_insert AFTER INSERT ON ${e.table} WHEN new.validity_status = 'active' AND new.is_archived = 0 AND new.promoted_to IS NULL BEGIN
|
|
358
|
+
INSERT OR REPLACE INTO unified_search_items(${columns})
|
|
359
|
+
VALUES ('${e.type}', new.id, new.scope, NULL, new.title, ${searchExpr("new.")}, new.search_summary, new.tags, new.created_at, new.updated_at);
|
|
360
|
+
END
|
|
361
|
+
`);
|
|
362
|
+
db.exec(`
|
|
363
|
+
CREATE TRIGGER ${e.table}_unified_update AFTER UPDATE ON ${e.table} BEGIN
|
|
364
|
+
DELETE FROM unified_search_items WHERE entity_type = '${e.type}' AND entity_id = old.id;
|
|
365
|
+
INSERT INTO unified_search_items(${columns})
|
|
366
|
+
SELECT '${e.type}', new.id, new.scope, NULL, new.title, ${searchExpr("new.")}, new.search_summary, new.tags, new.created_at, new.updated_at WHERE new.validity_status = 'active' AND new.is_archived = 0 AND new.promoted_to IS NULL;
|
|
367
|
+
END
|
|
368
|
+
`);
|
|
369
|
+
db.exec(`
|
|
370
|
+
CREATE TRIGGER ${e.table}_unified_delete AFTER DELETE ON ${e.table} BEGIN
|
|
371
|
+
DELETE FROM unified_search_items WHERE entity_type = '${e.type}' AND entity_id = old.id;
|
|
372
|
+
END
|
|
373
|
+
`);
|
|
374
|
+
}
|
|
375
|
+
// --- user_memos ---
|
|
376
|
+
db.exec(`
|
|
377
|
+
CREATE TRIGGER user_memos_unified_insert AFTER INSERT ON user_memos
|
|
378
|
+
WHEN new.memo_status = 'active' AND new.is_archived = 0 AND new.usage_policy != 'human_directed' BEGIN
|
|
379
|
+
INSERT OR REPLACE INTO unified_search_items(${columns})
|
|
380
|
+
VALUES ('user_memo', new.id, new.scope, NULL, new.title, new.title || ' ' || new.l1_content || ' ' || ${coal("new.user_input")} || ' ' || ${jc("new.tags")} || ' ' || ${coal("new.search_summary")}, new.search_summary, new.tags, new.created_at, new.updated_at);
|
|
381
|
+
END
|
|
382
|
+
`);
|
|
383
|
+
db.exec(`
|
|
384
|
+
CREATE TRIGGER user_memos_unified_update AFTER UPDATE ON user_memos BEGIN
|
|
385
|
+
DELETE FROM unified_search_items WHERE entity_type = 'user_memo' AND entity_id = old.id;
|
|
386
|
+
INSERT INTO unified_search_items(${columns})
|
|
387
|
+
SELECT 'user_memo', new.id, new.scope, NULL, new.title, new.title || ' ' || new.l1_content || ' ' || ${coal("new.user_input")} || ' ' || ${jc("new.tags")} || ' ' || ${coal("new.search_summary")}, new.search_summary, new.tags, new.created_at, new.updated_at
|
|
388
|
+
WHERE new.memo_status = 'active' AND new.is_archived = 0 AND new.usage_policy != 'human_directed';
|
|
389
|
+
END
|
|
390
|
+
`);
|
|
391
|
+
db.exec(`
|
|
392
|
+
CREATE TRIGGER user_memos_unified_delete AFTER DELETE ON user_memos BEGIN
|
|
393
|
+
DELETE FROM unified_search_items WHERE entity_type = 'user_memo' AND entity_id = old.id;
|
|
394
|
+
END
|
|
395
|
+
`);
|
|
396
|
+
// --- user_plans ---
|
|
397
|
+
db.exec(`
|
|
398
|
+
CREATE TRIGGER user_plans_unified_insert AFTER INSERT ON user_plans
|
|
399
|
+
WHEN new.plan_status = 'active' AND new.is_archived = 0 AND new.usage_policy != 'human_directed' BEGIN
|
|
400
|
+
INSERT OR REPLACE INTO unified_search_items(${columns})
|
|
401
|
+
VALUES ('user_plan', new.id, new.scope, NULL, new.title, new.title || ' ' || new.l1_content || ' ' || ${jc("new.tags")} || ' ' || ${coal("new.search_summary")}, new.search_summary, new.tags, new.created_at, new.updated_at);
|
|
402
|
+
END
|
|
403
|
+
`);
|
|
404
|
+
db.exec(`
|
|
405
|
+
CREATE TRIGGER user_plans_unified_update AFTER UPDATE ON user_plans BEGIN
|
|
406
|
+
DELETE FROM unified_search_items WHERE entity_type = 'user_plan' AND entity_id = old.id;
|
|
407
|
+
INSERT INTO unified_search_items(${columns})
|
|
408
|
+
SELECT 'user_plan', new.id, new.scope, NULL, new.title, new.title || ' ' || new.l1_content || ' ' || ${jc("new.tags")} || ' ' || ${coal("new.search_summary")}, new.search_summary, new.tags, new.created_at, new.updated_at
|
|
409
|
+
WHERE new.plan_status = 'active' AND new.is_archived = 0 AND new.usage_policy != 'human_directed';
|
|
410
|
+
END
|
|
411
|
+
`);
|
|
412
|
+
db.exec(`
|
|
413
|
+
CREATE TRIGGER user_plans_unified_delete AFTER DELETE ON user_plans BEGIN
|
|
414
|
+
DELETE FROM unified_search_items WHERE entity_type = 'user_plan' AND entity_id = old.id;
|
|
415
|
+
END
|
|
416
|
+
`);
|
|
417
|
+
// --- user_issues ---
|
|
418
|
+
db.exec(`
|
|
419
|
+
CREATE TRIGGER user_issues_unified_insert AFTER INSERT ON user_issues WHEN new.is_archived = 0 BEGIN
|
|
420
|
+
INSERT OR REPLACE INTO unified_search_items(${columns})
|
|
421
|
+
VALUES ('user_issue', new.id, new.scope, NULL, new.title, new.title || ' ' || new.l1_content || ' ' || ${jc("new.entries")} || ' ' || ${jc("new.tags")} || ' ' || ${coal("new.search_summary")}, new.search_summary, new.tags, new.created_at, new.updated_at);
|
|
422
|
+
END
|
|
423
|
+
`);
|
|
424
|
+
db.exec(`
|
|
425
|
+
CREATE TRIGGER user_issues_unified_update AFTER UPDATE ON user_issues BEGIN
|
|
426
|
+
DELETE FROM unified_search_items WHERE entity_type = 'user_issue' AND entity_id = old.id;
|
|
427
|
+
INSERT INTO unified_search_items(${columns})
|
|
428
|
+
SELECT 'user_issue', new.id, new.scope, NULL, new.title, new.title || ' ' || new.l1_content || ' ' || ${jc("new.entries")} || ' ' || ${jc("new.tags")} || ' ' || ${coal("new.search_summary")}, new.search_summary, new.tags, new.created_at, new.updated_at
|
|
429
|
+
WHERE new.is_archived = 0;
|
|
430
|
+
END
|
|
431
|
+
`);
|
|
432
|
+
db.exec(`
|
|
433
|
+
CREATE TRIGGER user_issues_unified_delete AFTER DELETE ON user_issues BEGIN
|
|
434
|
+
DELETE FROM unified_search_items WHERE entity_type = 'user_issue' AND entity_id = old.id;
|
|
435
|
+
END
|
|
436
|
+
`);
|
|
437
|
+
// --- user_topics ---
|
|
438
|
+
db.exec(`
|
|
439
|
+
CREATE TRIGGER user_topics_unified_insert AFTER INSERT ON user_topics WHEN new.topic_status = 'active' AND new.is_archived = 0 BEGIN
|
|
440
|
+
INSERT OR REPLACE INTO unified_search_items(${columns})
|
|
441
|
+
VALUES ('user_topic', new.id, new.scope, NULL, new.title, new.title || ' ' || new.summary || ' ' || ${jcTopics("new.tags")} || ' ' || ${coal("new.search_summary")}, new.search_summary, new.tags, new.created_at, new.updated_at);
|
|
442
|
+
END
|
|
443
|
+
`);
|
|
444
|
+
db.exec(`
|
|
445
|
+
CREATE TRIGGER user_topics_unified_update AFTER UPDATE ON user_topics BEGIN
|
|
446
|
+
DELETE FROM unified_search_items WHERE entity_type = 'user_topic' AND entity_id = old.id;
|
|
447
|
+
INSERT INTO unified_search_items(${columns})
|
|
448
|
+
SELECT 'user_topic', new.id, new.scope, NULL, new.title, new.title || ' ' || new.summary || ' ' || ${jcTopics("new.tags")} || ' ' || ${coal("new.search_summary")}, new.search_summary, new.tags, new.created_at, new.updated_at
|
|
449
|
+
WHERE new.topic_status = 'active' AND new.is_archived = 0;
|
|
450
|
+
END
|
|
451
|
+
`);
|
|
452
|
+
db.exec(`
|
|
453
|
+
CREATE TRIGGER user_topics_unified_delete AFTER DELETE ON user_topics BEGIN
|
|
454
|
+
DELETE FROM unified_search_items WHERE entity_type = 'user_topic' AND entity_id = old.id;
|
|
455
|
+
END
|
|
456
|
+
`);
|
|
457
|
+
// --- user_files ---
|
|
458
|
+
db.exec(`
|
|
459
|
+
CREATE TRIGGER user_files_unified_insert AFTER INSERT ON user_files WHEN new.file_status = 'active' AND new.is_archived = 0 BEGIN
|
|
460
|
+
INSERT OR REPLACE INTO unified_search_items(${columns})
|
|
461
|
+
VALUES ('user_file', new.id, new.scope, NULL, new.title, new.title || ' ' || COALESCE(new.description, '') || ' ' || new.original_filename || ' ' || ${jcTopics("new.tags")} || ' ' || ${coal("new.search_summary")}, new.search_summary, new.tags, new.created_at, new.updated_at);
|
|
462
|
+
END
|
|
463
|
+
`);
|
|
464
|
+
db.exec(`
|
|
465
|
+
CREATE TRIGGER user_files_unified_update AFTER UPDATE ON user_files BEGIN
|
|
466
|
+
DELETE FROM unified_search_items WHERE entity_type = 'user_file' AND entity_id = old.id;
|
|
467
|
+
INSERT INTO unified_search_items(${columns})
|
|
468
|
+
SELECT 'user_file', new.id, new.scope, NULL, new.title, new.title || ' ' || COALESCE(new.description, '') || ' ' || new.original_filename || ' ' || ${jcTopics("new.tags")} || ' ' || ${coal("new.search_summary")}, new.search_summary, new.tags, new.created_at, new.updated_at
|
|
469
|
+
WHERE new.file_status = 'active' AND new.is_archived = 0;
|
|
470
|
+
END
|
|
471
|
+
`);
|
|
472
|
+
db.exec(`
|
|
473
|
+
CREATE TRIGGER user_files_unified_delete AFTER DELETE ON user_files BEGIN
|
|
474
|
+
DELETE FROM unified_search_items WHERE entity_type = 'user_file' AND entity_id = old.id;
|
|
475
|
+
END
|
|
476
|
+
`);
|
|
477
|
+
}
|
|
208
478
|
function applyV1(db) {
|
|
479
|
+
// JSON配列クリーニングヘルパー(FTSトリガー用)
|
|
480
|
+
const jc = _jc, jcTopics = _jcTopics, coal = _coal;
|
|
481
|
+
const columns = _uniColumns;
|
|
482
|
+
// ================================================================
|
|
483
|
+
// 1. テーブル作成(CREATE TABLE + インデックス)
|
|
484
|
+
// ================================================================
|
|
209
485
|
db.exec(`
|
|
210
|
-
--
|
|
486
|
+
-- --------------------------------------------------------
|
|
487
|
+
-- claims: 知識断片(L2 SPO三つ組)
|
|
488
|
+
-- --------------------------------------------------------
|
|
211
489
|
CREATE TABLE IF NOT EXISTS claims (
|
|
212
490
|
id TEXT PRIMARY KEY,
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
491
|
+
l2_subject TEXT NOT NULL,
|
|
492
|
+
l2_predicate TEXT NOT NULL,
|
|
493
|
+
l2_object TEXT NOT NULL,
|
|
216
494
|
category TEXT NOT NULL CHECK(category IN ('preference','identity','skill','value','workflow','knowledge','custom')),
|
|
217
495
|
scope TEXT NOT NULL DEFAULT 'global',
|
|
218
496
|
confidence REAL NOT NULL DEFAULT 0.8 CHECK(confidence >= 0.0 AND confidence <= 1.0),
|
|
219
|
-
|
|
497
|
+
l2_evidence TEXT,
|
|
498
|
+
l2_falsifier TEXT,
|
|
499
|
+
l1_content TEXT,
|
|
500
|
+
search_summary TEXT,
|
|
501
|
+
l1_embedding BLOB,
|
|
502
|
+
hit_count INTEGER NOT NULL DEFAULT 0,
|
|
503
|
+
last_hit_at TEXT,
|
|
504
|
+
promoted_from_store TEXT,
|
|
505
|
+
promoted_from_id TEXT,
|
|
506
|
+
validity_status TEXT NOT NULL DEFAULT 'active' CHECK(validity_status IN ('active','invalidated','superseded')),
|
|
507
|
+
is_archived INTEGER NOT NULL DEFAULT 0,
|
|
508
|
+
promoted_to TEXT,
|
|
509
|
+
user_input TEXT,
|
|
220
510
|
source_tool TEXT,
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
511
|
+
session_id TEXT,
|
|
512
|
+
client_name TEXT,
|
|
513
|
+
client_version TEXT,
|
|
514
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f','now')),
|
|
515
|
+
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f','now'))
|
|
225
516
|
);
|
|
226
517
|
|
|
227
|
-
-- claims用インデックス
|
|
228
518
|
CREATE INDEX IF NOT EXISTS idx_claims_category ON claims(category);
|
|
229
519
|
CREATE INDEX IF NOT EXISTS idx_claims_scope ON claims(scope);
|
|
230
|
-
CREATE INDEX IF NOT EXISTS
|
|
231
|
-
CREATE INDEX IF NOT EXISTS
|
|
520
|
+
CREATE INDEX IF NOT EXISTS idx_claims_validity_status ON claims(validity_status);
|
|
521
|
+
CREATE INDEX IF NOT EXISTS idx_claims_is_archived ON claims(is_archived);
|
|
522
|
+
CREATE INDEX IF NOT EXISTS idx_claims_l2_subject ON claims(l2_subject);
|
|
232
523
|
CREATE INDEX IF NOT EXISTS idx_claims_updated ON claims(updated_at);
|
|
524
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_claims_promoted_from ON claims(promoted_from_store, promoted_from_id) WHERE promoted_from_store IS NOT NULL;
|
|
233
525
|
|
|
234
|
-
--
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
526
|
+
-- --------------------------------------------------------
|
|
527
|
+
-- episodes: エピソード記録
|
|
528
|
+
-- --------------------------------------------------------
|
|
529
|
+
CREATE TABLE IF NOT EXISTS episodes (
|
|
530
|
+
id TEXT PRIMARY KEY,
|
|
531
|
+
title TEXT NOT NULL,
|
|
532
|
+
l1_content TEXT,
|
|
533
|
+
l2_context TEXT,
|
|
534
|
+
l2_trigger TEXT,
|
|
535
|
+
l2_problems TEXT NOT NULL DEFAULT '[]',
|
|
536
|
+
l2_desires TEXT NOT NULL DEFAULT '[]',
|
|
537
|
+
l2_decisions TEXT NOT NULL DEFAULT '[]',
|
|
538
|
+
l2_outcomes TEXT NOT NULL DEFAULT '[]',
|
|
539
|
+
l2_principles TEXT NOT NULL DEFAULT '[]',
|
|
540
|
+
evidence_refs TEXT NOT NULL DEFAULT '[]',
|
|
541
|
+
tags TEXT NOT NULL DEFAULT '[]',
|
|
542
|
+
scope TEXT NOT NULL DEFAULT 'global',
|
|
543
|
+
search_summary TEXT,
|
|
544
|
+
l1_embedding BLOB,
|
|
545
|
+
promoted_from_store TEXT,
|
|
546
|
+
promoted_from_id TEXT,
|
|
547
|
+
validity_status TEXT NOT NULL DEFAULT 'active' CHECK(validity_status IN ('active','invalidated','superseded')),
|
|
548
|
+
is_archived INTEGER NOT NULL DEFAULT 0,
|
|
549
|
+
promoted_to TEXT,
|
|
550
|
+
groomed_at TEXT,
|
|
551
|
+
source_tool TEXT,
|
|
552
|
+
session_id TEXT,
|
|
553
|
+
client_name TEXT,
|
|
554
|
+
client_version TEXT,
|
|
555
|
+
user_input TEXT,
|
|
556
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f','now')),
|
|
557
|
+
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f','now'))
|
|
240
558
|
);
|
|
241
559
|
|
|
242
|
-
|
|
243
|
-
CREATE
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
CREATE TRIGGER IF NOT EXISTS claims_fts_update AFTER UPDATE ON claims BEGIN
|
|
250
|
-
INSERT INTO claims_fts(claims_fts, rowid, subject, predicate, object, evidence)
|
|
251
|
-
VALUES ('delete', old.rowid, old.subject, old.predicate, old.object, old.evidence);
|
|
252
|
-
INSERT INTO claims_fts(rowid, subject, predicate, object, evidence)
|
|
253
|
-
VALUES (new.rowid, new.subject, new.predicate, new.object, new.evidence);
|
|
254
|
-
END;
|
|
255
|
-
|
|
256
|
-
-- FTS同期トリガー: DELETE
|
|
257
|
-
CREATE TRIGGER IF NOT EXISTS claims_fts_delete AFTER DELETE ON claims BEGIN
|
|
258
|
-
INSERT INTO claims_fts(claims_fts, rowid, subject, predicate, object, evidence)
|
|
259
|
-
VALUES ('delete', old.rowid, old.subject, old.predicate, old.object, old.evidence);
|
|
260
|
-
END;
|
|
560
|
+
CREATE INDEX IF NOT EXISTS idx_episodes_scope ON episodes(scope);
|
|
561
|
+
CREATE INDEX IF NOT EXISTS idx_episodes_validity_status ON episodes(validity_status);
|
|
562
|
+
CREATE INDEX IF NOT EXISTS idx_episodes_is_archived ON episodes(is_archived);
|
|
563
|
+
CREATE INDEX IF NOT EXISTS idx_episodes_groomed ON episodes(groomed_at);
|
|
564
|
+
CREATE INDEX IF NOT EXISTS idx_episodes_created ON episodes(created_at);
|
|
565
|
+
CREATE INDEX IF NOT EXISTS idx_episodes_updated ON episodes(updated_at);
|
|
566
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_episodes_promoted_from ON episodes(promoted_from_store, promoted_from_id) WHERE promoted_from_store IS NOT NULL;
|
|
261
567
|
|
|
568
|
+
-- --------------------------------------------------------
|
|
262
569
|
-- decisions: 意思決定ログ
|
|
570
|
+
-- --------------------------------------------------------
|
|
263
571
|
CREATE TABLE IF NOT EXISTS decisions (
|
|
264
572
|
id TEXT PRIMARY KEY,
|
|
265
573
|
title TEXT NOT NULL,
|
|
266
574
|
description TEXT NOT NULL,
|
|
267
|
-
|
|
268
|
-
|
|
575
|
+
l1_content TEXT,
|
|
576
|
+
l2_reasoning TEXT NOT NULL,
|
|
577
|
+
l2_alternatives TEXT NOT NULL DEFAULT '[]',
|
|
269
578
|
related_claim_ids TEXT NOT NULL DEFAULT '[]',
|
|
270
|
-
|
|
579
|
+
scope TEXT NOT NULL DEFAULT 'global',
|
|
580
|
+
search_summary TEXT,
|
|
581
|
+
l1_embedding BLOB,
|
|
582
|
+
promoted_from_store TEXT,
|
|
583
|
+
promoted_from_id TEXT,
|
|
584
|
+
validity_status TEXT NOT NULL DEFAULT 'active' CHECK(validity_status IN ('active','invalidated','superseded')),
|
|
585
|
+
is_archived INTEGER NOT NULL DEFAULT 0,
|
|
586
|
+
promoted_to TEXT,
|
|
271
587
|
source_tool TEXT,
|
|
272
|
-
|
|
273
|
-
|
|
588
|
+
session_id TEXT,
|
|
589
|
+
client_name TEXT,
|
|
590
|
+
client_version TEXT,
|
|
591
|
+
user_input TEXT,
|
|
592
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f','now')),
|
|
593
|
+
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f','now'))
|
|
274
594
|
);
|
|
275
595
|
|
|
276
|
-
CREATE INDEX IF NOT EXISTS
|
|
596
|
+
CREATE INDEX IF NOT EXISTS idx_decisions_scope ON decisions(scope);
|
|
597
|
+
CREATE INDEX IF NOT EXISTS idx_decisions_validity_status ON decisions(validity_status);
|
|
598
|
+
CREATE INDEX IF NOT EXISTS idx_decisions_is_archived ON decisions(is_archived);
|
|
277
599
|
CREATE INDEX IF NOT EXISTS idx_decisions_created ON decisions(created_at);
|
|
600
|
+
CREATE INDEX IF NOT EXISTS idx_decisions_updated ON decisions(updated_at);
|
|
601
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_decisions_promoted_from ON decisions(promoted_from_store, promoted_from_id) WHERE promoted_from_store IS NOT NULL;
|
|
278
602
|
|
|
279
|
-
--
|
|
280
|
-
|
|
603
|
+
-- --------------------------------------------------------
|
|
604
|
+
-- theories: 体系的知識・フレームワーク
|
|
605
|
+
-- --------------------------------------------------------
|
|
606
|
+
CREATE TABLE IF NOT EXISTS theories (
|
|
281
607
|
id TEXT PRIMARY KEY,
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
608
|
+
title TEXT NOT NULL,
|
|
609
|
+
description TEXT,
|
|
610
|
+
l1_content TEXT,
|
|
611
|
+
l2_core_thesis TEXT,
|
|
612
|
+
l2_principles TEXT NOT NULL DEFAULT '[]',
|
|
613
|
+
l2_trigger_conditions TEXT NOT NULL DEFAULT '[]',
|
|
614
|
+
l2_resolution_steps TEXT NOT NULL DEFAULT '[]',
|
|
615
|
+
l2_applicable_context TEXT,
|
|
616
|
+
non_goals TEXT NOT NULL DEFAULT '[]',
|
|
617
|
+
open_questions TEXT NOT NULL DEFAULT '[]',
|
|
618
|
+
tags TEXT NOT NULL DEFAULT '[]',
|
|
619
|
+
scope TEXT NOT NULL DEFAULT 'global',
|
|
620
|
+
confidence REAL NOT NULL DEFAULT 0.8 CHECK(confidence >= 0.0 AND confidence <= 1.0),
|
|
621
|
+
search_summary TEXT,
|
|
622
|
+
l1_embedding BLOB,
|
|
623
|
+
supporting_episode_ids TEXT NOT NULL DEFAULT '[]',
|
|
624
|
+
supporting_claim_ids TEXT NOT NULL DEFAULT '[]',
|
|
625
|
+
evidence_refs TEXT NOT NULL DEFAULT '[]',
|
|
626
|
+
promoted_from_store TEXT,
|
|
627
|
+
promoted_from_id TEXT,
|
|
628
|
+
validity_status TEXT NOT NULL DEFAULT 'active' CHECK(validity_status IN ('active','invalidated','superseded')),
|
|
629
|
+
is_archived INTEGER NOT NULL DEFAULT 0,
|
|
630
|
+
promoted_to TEXT,
|
|
631
|
+
source_tool TEXT,
|
|
632
|
+
session_id TEXT,
|
|
633
|
+
client_name TEXT,
|
|
634
|
+
client_version TEXT,
|
|
635
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f','now')),
|
|
636
|
+
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f','now'))
|
|
288
637
|
);
|
|
289
638
|
|
|
290
|
-
CREATE INDEX IF NOT EXISTS
|
|
291
|
-
CREATE INDEX IF NOT EXISTS
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* V2スキーマ: L2 Core v0対応
|
|
299
|
-
* - claims に falsifier カラム追加
|
|
300
|
-
* - claim_relations テーブル追加(推論グラフ)
|
|
301
|
-
* - claim_evidence テーブル追加(構造化根拠参照)
|
|
302
|
-
* - claim_checks テーブル追加(検証ログ)
|
|
303
|
-
* 仕様: docs/specs/l2-core-v0.md
|
|
304
|
-
*/
|
|
305
|
-
function applyV2(db) {
|
|
306
|
-
db.exec(`
|
|
307
|
-
-- claims に反証条件カラム追加
|
|
308
|
-
ALTER TABLE claims ADD COLUMN falsifier TEXT;
|
|
639
|
+
CREATE INDEX IF NOT EXISTS idx_theories_scope ON theories(scope);
|
|
640
|
+
CREATE INDEX IF NOT EXISTS idx_theories_validity_status ON theories(validity_status);
|
|
641
|
+
CREATE INDEX IF NOT EXISTS idx_theories_is_archived ON theories(is_archived);
|
|
642
|
+
CREATE INDEX IF NOT EXISTS idx_theories_confidence ON theories(confidence);
|
|
643
|
+
CREATE INDEX IF NOT EXISTS idx_theories_created ON theories(created_at);
|
|
644
|
+
CREATE INDEX IF NOT EXISTS idx_theories_updated ON theories(updated_at);
|
|
645
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_theories_promoted_from ON theories(promoted_from_store, promoted_from_id) WHERE promoted_from_store IS NOT NULL;
|
|
309
646
|
|
|
310
|
-
--
|
|
311
|
-
|
|
647
|
+
-- --------------------------------------------------------
|
|
648
|
+
-- insights: 洞察(theories のサブセット、non_goals/open_questions なし)
|
|
649
|
+
-- --------------------------------------------------------
|
|
650
|
+
CREATE TABLE IF NOT EXISTS insights (
|
|
312
651
|
id TEXT PRIMARY KEY,
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
652
|
+
title TEXT NOT NULL,
|
|
653
|
+
description TEXT,
|
|
654
|
+
l1_content TEXT,
|
|
655
|
+
l2_core_thesis TEXT,
|
|
656
|
+
l2_principles TEXT NOT NULL DEFAULT '[]',
|
|
657
|
+
l2_trigger_conditions TEXT NOT NULL DEFAULT '[]',
|
|
658
|
+
l2_resolution_steps TEXT NOT NULL DEFAULT '[]',
|
|
659
|
+
l2_applicable_context TEXT,
|
|
660
|
+
tags TEXT NOT NULL DEFAULT '[]',
|
|
661
|
+
scope TEXT NOT NULL DEFAULT 'global',
|
|
662
|
+
confidence REAL NOT NULL DEFAULT 0.8 CHECK(confidence >= 0.0 AND confidence <= 1.0),
|
|
663
|
+
search_summary TEXT,
|
|
664
|
+
l1_embedding BLOB,
|
|
665
|
+
supporting_episode_ids TEXT NOT NULL DEFAULT '[]',
|
|
666
|
+
supporting_claim_ids TEXT NOT NULL DEFAULT '[]',
|
|
667
|
+
evidence_refs TEXT NOT NULL DEFAULT '[]',
|
|
668
|
+
promoted_from_store TEXT,
|
|
669
|
+
promoted_from_id TEXT,
|
|
670
|
+
validity_status TEXT NOT NULL DEFAULT 'active' CHECK(validity_status IN ('active','invalidated','superseded')),
|
|
671
|
+
is_archived INTEGER NOT NULL DEFAULT 0,
|
|
672
|
+
promoted_to TEXT,
|
|
320
673
|
source_tool TEXT,
|
|
321
|
-
|
|
674
|
+
session_id TEXT,
|
|
675
|
+
client_name TEXT,
|
|
676
|
+
client_version TEXT,
|
|
677
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f','now')),
|
|
678
|
+
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f','now'))
|
|
322
679
|
);
|
|
323
680
|
|
|
324
|
-
CREATE INDEX IF NOT EXISTS
|
|
325
|
-
CREATE INDEX IF NOT EXISTS
|
|
326
|
-
CREATE INDEX IF NOT EXISTS
|
|
681
|
+
CREATE INDEX IF NOT EXISTS idx_insights_scope ON insights(scope);
|
|
682
|
+
CREATE INDEX IF NOT EXISTS idx_insights_validity_status ON insights(validity_status);
|
|
683
|
+
CREATE INDEX IF NOT EXISTS idx_insights_is_archived ON insights(is_archived);
|
|
684
|
+
CREATE INDEX IF NOT EXISTS idx_insights_confidence ON insights(confidence);
|
|
685
|
+
CREATE INDEX IF NOT EXISTS idx_insights_created ON insights(created_at);
|
|
686
|
+
CREATE INDEX IF NOT EXISTS idx_insights_updated ON insights(updated_at);
|
|
687
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_insights_promoted_from ON insights(promoted_from_store, promoted_from_id) WHERE promoted_from_store IS NOT NULL;
|
|
327
688
|
|
|
328
|
-
--
|
|
329
|
-
|
|
689
|
+
-- --------------------------------------------------------
|
|
690
|
+
-- models: モデル(theories と同構造)
|
|
691
|
+
-- --------------------------------------------------------
|
|
692
|
+
CREATE TABLE IF NOT EXISTS models (
|
|
330
693
|
id TEXT PRIMARY KEY,
|
|
331
|
-
|
|
332
|
-
evidence_type TEXT NOT NULL CHECK(evidence_type IN (
|
|
333
|
-
'url','file','claim','decision','session_log','user_statement','external'
|
|
334
|
-
)),
|
|
335
|
-
uri TEXT,
|
|
336
|
-
span TEXT,
|
|
337
|
-
content_hash TEXT,
|
|
694
|
+
title TEXT NOT NULL,
|
|
338
695
|
description TEXT,
|
|
339
|
-
|
|
696
|
+
l1_content TEXT,
|
|
697
|
+
l2_core_thesis TEXT,
|
|
698
|
+
l2_principles TEXT NOT NULL DEFAULT '[]',
|
|
699
|
+
l2_trigger_conditions TEXT NOT NULL DEFAULT '[]',
|
|
700
|
+
l2_resolution_steps TEXT NOT NULL DEFAULT '[]',
|
|
701
|
+
l2_applicable_context TEXT,
|
|
702
|
+
non_goals TEXT NOT NULL DEFAULT '[]',
|
|
703
|
+
open_questions TEXT NOT NULL DEFAULT '[]',
|
|
704
|
+
tags TEXT NOT NULL DEFAULT '[]',
|
|
705
|
+
scope TEXT NOT NULL DEFAULT 'global',
|
|
706
|
+
confidence REAL NOT NULL DEFAULT 0.8 CHECK(confidence >= 0.0 AND confidence <= 1.0),
|
|
707
|
+
search_summary TEXT,
|
|
708
|
+
l1_embedding BLOB,
|
|
709
|
+
supporting_episode_ids TEXT NOT NULL DEFAULT '[]',
|
|
710
|
+
supporting_claim_ids TEXT NOT NULL DEFAULT '[]',
|
|
711
|
+
evidence_refs TEXT NOT NULL DEFAULT '[]',
|
|
712
|
+
promoted_from_store TEXT,
|
|
713
|
+
promoted_from_id TEXT,
|
|
714
|
+
validity_status TEXT NOT NULL DEFAULT 'active' CHECK(validity_status IN ('active','invalidated','superseded')),
|
|
715
|
+
is_archived INTEGER NOT NULL DEFAULT 0,
|
|
716
|
+
promoted_to TEXT,
|
|
340
717
|
source_tool TEXT,
|
|
341
|
-
|
|
718
|
+
session_id TEXT,
|
|
719
|
+
client_name TEXT,
|
|
720
|
+
client_version TEXT,
|
|
721
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f','now')),
|
|
722
|
+
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f','now'))
|
|
342
723
|
);
|
|
343
724
|
|
|
344
|
-
CREATE INDEX IF NOT EXISTS
|
|
345
|
-
CREATE INDEX IF NOT EXISTS
|
|
725
|
+
CREATE INDEX IF NOT EXISTS idx_models_scope ON models(scope);
|
|
726
|
+
CREATE INDEX IF NOT EXISTS idx_models_validity_status ON models(validity_status);
|
|
727
|
+
CREATE INDEX IF NOT EXISTS idx_models_is_archived ON models(is_archived);
|
|
728
|
+
CREATE INDEX IF NOT EXISTS idx_models_confidence ON models(confidence);
|
|
729
|
+
CREATE INDEX IF NOT EXISTS idx_models_created ON models(created_at);
|
|
730
|
+
CREATE INDEX IF NOT EXISTS idx_models_updated ON models(updated_at);
|
|
731
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_models_promoted_from ON models(promoted_from_store, promoted_from_id) WHERE promoted_from_store IS NOT NULL;
|
|
346
732
|
|
|
347
|
-
--
|
|
348
|
-
|
|
733
|
+
-- --------------------------------------------------------
|
|
734
|
+
-- user_memos: ユーザーメモ
|
|
735
|
+
-- --------------------------------------------------------
|
|
736
|
+
CREATE TABLE IF NOT EXISTS user_memos (
|
|
349
737
|
id TEXT PRIMARY KEY,
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
738
|
+
title TEXT NOT NULL,
|
|
739
|
+
l1_content TEXT NOT NULL,
|
|
740
|
+
usage_policy TEXT NOT NULL DEFAULT 'on_request'
|
|
741
|
+
CHECK(usage_policy IN ('auto','on_request','human_directed')),
|
|
742
|
+
tags TEXT NOT NULL DEFAULT '[]',
|
|
743
|
+
scope TEXT NOT NULL DEFAULT 'global',
|
|
744
|
+
search_summary TEXT,
|
|
745
|
+
user_input TEXT,
|
|
746
|
+
memo_status TEXT NOT NULL DEFAULT 'active'
|
|
747
|
+
CHECK(memo_status IN ('active')),
|
|
748
|
+
is_archived INTEGER NOT NULL DEFAULT 0,
|
|
749
|
+
l1_embedding BLOB,
|
|
750
|
+
client_name TEXT,
|
|
751
|
+
client_version TEXT,
|
|
356
752
|
source_tool TEXT,
|
|
357
|
-
|
|
753
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f','now')),
|
|
754
|
+
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f','now'))
|
|
358
755
|
);
|
|
359
756
|
|
|
360
|
-
CREATE INDEX IF NOT EXISTS
|
|
361
|
-
CREATE INDEX IF NOT EXISTS
|
|
362
|
-
CREATE INDEX IF NOT EXISTS
|
|
757
|
+
CREATE INDEX IF NOT EXISTS idx_user_memos_memo_status ON user_memos(memo_status);
|
|
758
|
+
CREATE INDEX IF NOT EXISTS idx_user_memos_is_archived ON user_memos(is_archived);
|
|
759
|
+
CREATE INDEX IF NOT EXISTS idx_user_memos_scope ON user_memos(scope);
|
|
760
|
+
CREATE INDEX IF NOT EXISTS idx_user_memos_usage_policy ON user_memos(usage_policy);
|
|
761
|
+
CREATE INDEX IF NOT EXISTS idx_user_memos_updated ON user_memos(updated_at);
|
|
363
762
|
|
|
364
|
-
--
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
763
|
+
-- --------------------------------------------------------
|
|
764
|
+
-- user_plans: ユーザープラン
|
|
765
|
+
-- --------------------------------------------------------
|
|
766
|
+
CREATE TABLE IF NOT EXISTS user_plans (
|
|
767
|
+
id TEXT PRIMARY KEY,
|
|
768
|
+
title TEXT NOT NULL,
|
|
769
|
+
l1_content TEXT NOT NULL,
|
|
770
|
+
usage_policy TEXT NOT NULL DEFAULT 'auto'
|
|
771
|
+
CHECK(usage_policy IN ('auto','on_request','human_directed')),
|
|
772
|
+
tags TEXT NOT NULL DEFAULT '[]',
|
|
773
|
+
scope TEXT NOT NULL DEFAULT 'global',
|
|
774
|
+
search_summary TEXT,
|
|
775
|
+
user_input TEXT,
|
|
776
|
+
plan_status TEXT NOT NULL DEFAULT 'active'
|
|
777
|
+
CHECK(plan_status IN ('active','completed')),
|
|
778
|
+
is_archived INTEGER NOT NULL DEFAULT 0,
|
|
779
|
+
l1_embedding BLOB,
|
|
780
|
+
client_name TEXT,
|
|
781
|
+
client_version TEXT,
|
|
782
|
+
source_tool TEXT,
|
|
783
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f','now')),
|
|
784
|
+
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f','now'))
|
|
785
|
+
);
|
|
380
786
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
787
|
+
CREATE INDEX IF NOT EXISTS idx_user_plans_plan_status ON user_plans(plan_status);
|
|
788
|
+
CREATE INDEX IF NOT EXISTS idx_user_plans_is_archived ON user_plans(is_archived);
|
|
789
|
+
CREATE INDEX IF NOT EXISTS idx_user_plans_scope ON user_plans(scope);
|
|
790
|
+
CREATE INDEX IF NOT EXISTS idx_user_plans_usage_policy ON user_plans(usage_policy);
|
|
791
|
+
CREATE INDEX IF NOT EXISTS idx_user_plans_updated ON user_plans(updated_at);
|
|
384
792
|
|
|
385
|
-
--
|
|
386
|
-
|
|
793
|
+
-- --------------------------------------------------------
|
|
794
|
+
-- user_issues: ユーザー課題
|
|
795
|
+
-- --------------------------------------------------------
|
|
796
|
+
CREATE TABLE IF NOT EXISTS user_issues (
|
|
387
797
|
id TEXT PRIMARY KEY,
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
798
|
+
title TEXT NOT NULL,
|
|
799
|
+
l1_content TEXT NOT NULL,
|
|
800
|
+
entries TEXT NOT NULL DEFAULT '[]',
|
|
801
|
+
kind TEXT NOT NULL DEFAULT 'issue'
|
|
802
|
+
CHECK(kind IN ('issue','discussion')),
|
|
803
|
+
priority TEXT NOT NULL DEFAULT 'medium'
|
|
804
|
+
CHECK(priority IN ('low','medium','high','critical')),
|
|
805
|
+
usage_policy TEXT NOT NULL DEFAULT 'on_request'
|
|
806
|
+
CHECK(usage_policy IN ('auto','on_request','human_directed')),
|
|
807
|
+
tags TEXT NOT NULL DEFAULT '[]',
|
|
808
|
+
scope TEXT NOT NULL DEFAULT 'global',
|
|
809
|
+
search_summary TEXT,
|
|
810
|
+
user_input TEXT,
|
|
811
|
+
issue_status TEXT NOT NULL DEFAULT 'open'
|
|
812
|
+
CHECK(issue_status IN ('open','closed')),
|
|
813
|
+
is_archived INTEGER NOT NULL DEFAULT 0,
|
|
814
|
+
l1_embedding BLOB,
|
|
395
815
|
client_name TEXT,
|
|
396
816
|
client_version TEXT,
|
|
397
|
-
session_id TEXT,
|
|
398
817
|
source_tool TEXT,
|
|
399
|
-
created_at TEXT NOT NULL DEFAULT (
|
|
818
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f','now')),
|
|
819
|
+
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f','now'))
|
|
400
820
|
);
|
|
401
821
|
|
|
402
|
-
CREATE INDEX IF NOT EXISTS
|
|
403
|
-
CREATE INDEX IF NOT EXISTS
|
|
404
|
-
CREATE INDEX IF NOT EXISTS
|
|
405
|
-
CREATE INDEX IF NOT EXISTS
|
|
822
|
+
CREATE INDEX IF NOT EXISTS idx_user_issues_issue_status ON user_issues(issue_status);
|
|
823
|
+
CREATE INDEX IF NOT EXISTS idx_user_issues_is_archived ON user_issues(is_archived);
|
|
824
|
+
CREATE INDEX IF NOT EXISTS idx_user_issues_scope ON user_issues(scope);
|
|
825
|
+
CREATE INDEX IF NOT EXISTS idx_user_issues_priority ON user_issues(priority);
|
|
826
|
+
CREATE INDEX IF NOT EXISTS idx_user_issues_updated ON user_issues(updated_at);
|
|
406
827
|
|
|
407
|
-
--
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
/**
|
|
412
|
-
* V4スキーマ: Episodeモデル追加
|
|
413
|
-
* ユーザーの思考パターン・意思決定フレームワークを記録する上位構造。
|
|
414
|
-
* 原子的事実(Claims)ではなく、文脈→問題→欲求→決定→結果→原則 の流れを格納。
|
|
415
|
-
* ChatGPT提案のEpisode構造をベースに、~20件の実データ収集でカラム妥当性を検証する。
|
|
416
|
-
*/
|
|
417
|
-
function applyV4(db) {
|
|
418
|
-
db.exec(`
|
|
419
|
-
-- audit_log のentity_type CHECK制約を拡張('episode'を追加)
|
|
420
|
-
-- SQLiteはALTER TABLE DROP CONSTRAINTをサポートしないため、テーブル再作成で対応
|
|
421
|
-
CREATE TABLE IF NOT EXISTS audit_log_new (
|
|
828
|
+
-- --------------------------------------------------------
|
|
829
|
+
-- user_topics: 話題管理
|
|
830
|
+
-- --------------------------------------------------------
|
|
831
|
+
CREATE TABLE IF NOT EXISTS user_topics (
|
|
422
832
|
id TEXT PRIMARY KEY,
|
|
423
|
-
|
|
424
|
-
'create','update','retract','supersede','reverse','obsolete','archive'
|
|
425
|
-
)),
|
|
426
|
-
entity_type TEXT NOT NULL CHECK(entity_type IN ('claim','decision','episode')),
|
|
427
|
-
entity_id TEXT NOT NULL,
|
|
833
|
+
title TEXT NOT NULL,
|
|
428
834
|
summary TEXT NOT NULL,
|
|
429
|
-
|
|
835
|
+
device_id TEXT,
|
|
836
|
+
cwd TEXT,
|
|
837
|
+
conversation_id TEXT,
|
|
838
|
+
priority TEXT NOT NULL DEFAULT 'medium'
|
|
839
|
+
CHECK(priority IN ('low','medium','high','critical')),
|
|
840
|
+
tags TEXT NOT NULL DEFAULT '[]',
|
|
841
|
+
scope TEXT NOT NULL DEFAULT 'global',
|
|
842
|
+
search_summary TEXT,
|
|
843
|
+
user_input TEXT,
|
|
844
|
+
topic_status TEXT NOT NULL DEFAULT 'active'
|
|
845
|
+
CHECK(topic_status IN ('active','closed')),
|
|
846
|
+
is_archived INTEGER NOT NULL DEFAULT 0,
|
|
847
|
+
l1_embedding BLOB,
|
|
430
848
|
client_name TEXT,
|
|
431
849
|
client_version TEXT,
|
|
432
|
-
session_id TEXT,
|
|
433
850
|
source_tool TEXT,
|
|
434
|
-
created_at TEXT NOT NULL DEFAULT (
|
|
851
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f','now')),
|
|
852
|
+
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f','now'))
|
|
435
853
|
);
|
|
436
854
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
CREATE INDEX IF NOT EXISTS
|
|
442
|
-
CREATE INDEX IF NOT EXISTS idx_audit_log_operation ON audit_log(operation);
|
|
443
|
-
CREATE INDEX IF NOT EXISTS idx_audit_log_created ON audit_log(created_at);
|
|
444
|
-
CREATE INDEX IF NOT EXISTS idx_audit_log_session ON audit_log(session_id);
|
|
855
|
+
CREATE INDEX IF NOT EXISTS idx_user_topics_topic_status ON user_topics(topic_status);
|
|
856
|
+
CREATE INDEX IF NOT EXISTS idx_user_topics_is_archived ON user_topics(is_archived);
|
|
857
|
+
CREATE INDEX IF NOT EXISTS idx_user_topics_scope ON user_topics(scope);
|
|
858
|
+
CREATE INDEX IF NOT EXISTS idx_user_topics_priority ON user_topics(priority);
|
|
859
|
+
CREATE INDEX IF NOT EXISTS idx_user_topics_updated ON user_topics(updated_at);
|
|
445
860
|
|
|
446
|
-
--
|
|
447
|
-
|
|
861
|
+
-- --------------------------------------------------------
|
|
862
|
+
-- user_files: ファイル管理(File Vault機能, V31)
|
|
863
|
+
-- --------------------------------------------------------
|
|
864
|
+
CREATE TABLE IF NOT EXISTS user_files (
|
|
448
865
|
id TEXT PRIMARY KEY,
|
|
449
866
|
title TEXT NOT NULL,
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
evidence_refs TEXT NOT NULL DEFAULT '[]',
|
|
867
|
+
description TEXT,
|
|
868
|
+
device_id TEXT,
|
|
869
|
+
original_filename TEXT NOT NULL,
|
|
870
|
+
original_encoding TEXT NOT NULL DEFAULT 'utf-8',
|
|
871
|
+
file_data TEXT NOT NULL,
|
|
872
|
+
file_hash TEXT NOT NULL,
|
|
873
|
+
file_size INTEGER NOT NULL,
|
|
458
874
|
tags TEXT NOT NULL DEFAULT '[]',
|
|
459
|
-
|
|
875
|
+
scope TEXT NOT NULL DEFAULT 'global',
|
|
876
|
+
search_summary TEXT,
|
|
877
|
+
file_status TEXT NOT NULL DEFAULT 'active'
|
|
878
|
+
CHECK(file_status IN ('active')),
|
|
879
|
+
is_archived INTEGER NOT NULL DEFAULT 0,
|
|
880
|
+
l1_embedding BLOB,
|
|
460
881
|
client_name TEXT,
|
|
461
882
|
client_version TEXT,
|
|
462
883
|
source_tool TEXT,
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
884
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f','now')),
|
|
885
|
+
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f','now'))
|
|
466
886
|
);
|
|
467
887
|
|
|
468
|
-
|
|
469
|
-
CREATE INDEX IF NOT EXISTS
|
|
470
|
-
CREATE INDEX IF NOT EXISTS
|
|
471
|
-
CREATE INDEX IF NOT EXISTS
|
|
888
|
+
CREATE INDEX IF NOT EXISTS idx_user_files_file_status ON user_files(file_status);
|
|
889
|
+
CREATE INDEX IF NOT EXISTS idx_user_files_is_archived ON user_files(is_archived);
|
|
890
|
+
CREATE INDEX IF NOT EXISTS idx_user_files_scope ON user_files(scope);
|
|
891
|
+
CREATE INDEX IF NOT EXISTS idx_user_files_title ON user_files(title);
|
|
472
892
|
|
|
473
|
-
--
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
893
|
+
-- --------------------------------------------------------
|
|
894
|
+
-- claim_history: Claim変更履歴 (V12: created_at 追加)
|
|
895
|
+
-- --------------------------------------------------------
|
|
896
|
+
CREATE TABLE IF NOT EXISTS claim_history (
|
|
897
|
+
id TEXT PRIMARY KEY,
|
|
898
|
+
claim_id TEXT NOT NULL REFERENCES claims(id),
|
|
899
|
+
field_name TEXT NOT NULL,
|
|
900
|
+
old_value TEXT,
|
|
901
|
+
new_value TEXT,
|
|
902
|
+
reason TEXT NOT NULL,
|
|
903
|
+
changed_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f','now')),
|
|
904
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f','now'))
|
|
479
905
|
);
|
|
480
906
|
|
|
481
|
-
|
|
482
|
-
CREATE
|
|
483
|
-
INSERT INTO episodes_fts(rowid, title, context, trigger, principles)
|
|
484
|
-
VALUES (new.rowid, new.title, new.context, new.trigger, new.principles);
|
|
485
|
-
END;
|
|
907
|
+
CREATE INDEX IF NOT EXISTS idx_claim_history_claim_id ON claim_history(claim_id);
|
|
908
|
+
CREATE INDEX IF NOT EXISTS idx_claim_history_changed ON claim_history(changed_at);
|
|
486
909
|
|
|
487
|
-
--
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
910
|
+
-- --------------------------------------------------------
|
|
911
|
+
-- claim_relations: Claim間の推論関係 (V12: updated_at 追加)
|
|
912
|
+
-- --------------------------------------------------------
|
|
913
|
+
CREATE TABLE IF NOT EXISTS claim_relations (
|
|
914
|
+
id TEXT PRIMARY KEY,
|
|
915
|
+
source_claim_id TEXT NOT NULL,
|
|
916
|
+
target_claim_id TEXT NOT NULL,
|
|
917
|
+
relation_type TEXT NOT NULL CHECK(relation_type IN (
|
|
918
|
+
'supports','contradicts','derives','induces','analogizes','supersedes','depends_on'
|
|
919
|
+
)),
|
|
920
|
+
confidence REAL DEFAULT 0.8 CHECK(confidence >= 0.0 AND confidence <= 1.0),
|
|
921
|
+
reasoning TEXT,
|
|
922
|
+
source_tool TEXT,
|
|
923
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f','now')),
|
|
924
|
+
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f','now'))
|
|
925
|
+
);
|
|
494
926
|
|
|
495
|
-
|
|
496
|
-
CREATE
|
|
497
|
-
|
|
498
|
-
VALUES ('delete', old.rowid, old.title, old.context, old.trigger, old.principles);
|
|
499
|
-
END;
|
|
927
|
+
CREATE INDEX IF NOT EXISTS idx_claim_relations_source ON claim_relations(source_claim_id);
|
|
928
|
+
CREATE INDEX IF NOT EXISTS idx_claim_relations_target ON claim_relations(target_claim_id);
|
|
929
|
+
CREATE INDEX IF NOT EXISTS idx_claim_relations_type ON claim_relations(relation_type);
|
|
500
930
|
|
|
501
|
-
--
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
db.exec(`ALTER TABLE episodes ADD COLUMN scope TEXT NOT NULL DEFAULT 'global'`);
|
|
519
|
-
}
|
|
520
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_episodes_scope ON episodes(scope)`);
|
|
521
|
-
// decisions に scope カラム追加(存在しない場合のみ)
|
|
522
|
-
if (!hasColumn(db, "decisions", "scope")) {
|
|
523
|
-
db.exec(`ALTER TABLE decisions ADD COLUMN scope TEXT NOT NULL DEFAULT 'global'`);
|
|
524
|
-
}
|
|
525
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_decisions_scope ON decisions(scope)`);
|
|
526
|
-
// decisions用FTS5全文検索
|
|
527
|
-
db.exec(`
|
|
528
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS decisions_fts USING fts5(
|
|
529
|
-
title, description, reasoning,
|
|
530
|
-
content='decisions',
|
|
531
|
-
content_rowid='rowid',
|
|
532
|
-
tokenize='unicode61'
|
|
533
|
-
)
|
|
534
|
-
`);
|
|
535
|
-
// FTS同期トリガー
|
|
536
|
-
db.exec(`
|
|
537
|
-
CREATE TRIGGER IF NOT EXISTS decisions_fts_insert AFTER INSERT ON decisions BEGIN
|
|
538
|
-
INSERT INTO decisions_fts(rowid, title, description, reasoning)
|
|
539
|
-
VALUES (new.rowid, new.title, new.description, new.reasoning);
|
|
540
|
-
END
|
|
541
|
-
`);
|
|
542
|
-
db.exec(`
|
|
543
|
-
CREATE TRIGGER IF NOT EXISTS decisions_fts_update AFTER UPDATE ON decisions BEGIN
|
|
544
|
-
INSERT INTO decisions_fts(decisions_fts, rowid, title, description, reasoning)
|
|
545
|
-
VALUES ('delete', old.rowid, old.title, old.description, old.reasoning);
|
|
546
|
-
INSERT INTO decisions_fts(rowid, title, description, reasoning)
|
|
547
|
-
VALUES (new.rowid, new.title, new.description, new.reasoning);
|
|
548
|
-
END
|
|
549
|
-
`);
|
|
550
|
-
db.exec(`
|
|
551
|
-
CREATE TRIGGER IF NOT EXISTS decisions_fts_delete AFTER DELETE ON decisions BEGIN
|
|
552
|
-
INSERT INTO decisions_fts(decisions_fts, rowid, title, description, reasoning)
|
|
553
|
-
VALUES ('delete', old.rowid, old.title, old.description, old.reasoning);
|
|
554
|
-
END
|
|
555
|
-
`);
|
|
556
|
-
// 既存DecisionsをFTSにバックフィル(FTSが空の場合のみ)
|
|
557
|
-
const ftsCount = db.prepare("SELECT COUNT(*) as count FROM decisions_fts").get();
|
|
558
|
-
if (ftsCount.count === 0) {
|
|
559
|
-
db.exec(`
|
|
560
|
-
INSERT INTO decisions_fts(rowid, title, description, reasoning)
|
|
561
|
-
SELECT rowid, title, description, reasoning FROM decisions
|
|
562
|
-
`);
|
|
563
|
-
}
|
|
564
|
-
// スキーマバージョン記録
|
|
565
|
-
db.exec(`INSERT INTO schema_version (version) VALUES (5)`);
|
|
566
|
-
});
|
|
567
|
-
transaction();
|
|
568
|
-
}
|
|
569
|
-
/**
|
|
570
|
-
* V6スキーマ: store_meta テーブル追加(バトンリレー用)
|
|
571
|
-
* デバイス間でDBを引き継ぐためのメタデータ管理。
|
|
572
|
-
* client_id でデバイスを識別し、last_synced_at で最終同期日時を追跡する。
|
|
573
|
-
*/
|
|
574
|
-
function applyV6(db) {
|
|
575
|
-
db.exec(`
|
|
576
|
-
-- store_meta: Store単位のメタデータ(Key-Valueストア)
|
|
577
|
-
CREATE TABLE IF NOT EXISTS store_meta (
|
|
578
|
-
key TEXT PRIMARY KEY,
|
|
579
|
-
value TEXT NOT NULL,
|
|
580
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
931
|
+
-- --------------------------------------------------------
|
|
932
|
+
-- claim_evidence: 構造化された根拠参照 (V12: updated_at 追加)
|
|
933
|
+
-- --------------------------------------------------------
|
|
934
|
+
CREATE TABLE IF NOT EXISTS claim_evidence (
|
|
935
|
+
id TEXT PRIMARY KEY,
|
|
936
|
+
claim_id TEXT NOT NULL REFERENCES claims(id),
|
|
937
|
+
evidence_type TEXT NOT NULL CHECK(evidence_type IN (
|
|
938
|
+
'url','file','claim','decision','session_log','user_statement','external'
|
|
939
|
+
)),
|
|
940
|
+
uri TEXT,
|
|
941
|
+
span TEXT,
|
|
942
|
+
content_hash TEXT,
|
|
943
|
+
description TEXT,
|
|
944
|
+
verified_at TEXT,
|
|
945
|
+
source_tool TEXT,
|
|
946
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f','now')),
|
|
947
|
+
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f','now'))
|
|
581
948
|
);
|
|
582
949
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
950
|
+
CREATE INDEX IF NOT EXISTS idx_claim_evidence_claim ON claim_evidence(claim_id);
|
|
951
|
+
CREATE INDEX IF NOT EXISTS idx_claim_evidence_type ON claim_evidence(evidence_type);
|
|
952
|
+
|
|
953
|
+
-- --------------------------------------------------------
|
|
954
|
+
-- claim_checks: 検証ログ (V12: created_at 追加)
|
|
955
|
+
-- --------------------------------------------------------
|
|
956
|
+
CREATE TABLE IF NOT EXISTS claim_checks (
|
|
957
|
+
id TEXT PRIMARY KEY,
|
|
958
|
+
claim_id TEXT NOT NULL REFERENCES claims(id),
|
|
959
|
+
check_type TEXT NOT NULL CHECK(check_type IN (
|
|
960
|
+
'fact_check','consistency','counter_example','source_verify','user_confirm','auto_expire','falsifier_eval'
|
|
961
|
+
)),
|
|
962
|
+
result TEXT NOT NULL CHECK(result IN ('passed','failed','inconclusive','skipped')),
|
|
963
|
+
details TEXT,
|
|
964
|
+
source_tool TEXT,
|
|
965
|
+
checked_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f','now')),
|
|
966
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f','now'))
|
|
967
|
+
);
|
|
968
|
+
|
|
969
|
+
CREATE INDEX IF NOT EXISTS idx_claim_checks_claim ON claim_checks(claim_id);
|
|
970
|
+
CREATE INDEX IF NOT EXISTS idx_claim_checks_type ON claim_checks(check_type);
|
|
971
|
+
CREATE INDEX IF NOT EXISTS idx_claim_checks_result ON claim_checks(result);
|
|
972
|
+
|
|
973
|
+
-- --------------------------------------------------------
|
|
974
|
+
-- audit_log: 監査ログ
|
|
975
|
+
-- --------------------------------------------------------
|
|
976
|
+
CREATE TABLE IF NOT EXISTS audit_log (
|
|
977
|
+
id TEXT PRIMARY KEY,
|
|
978
|
+
operation TEXT NOT NULL CHECK(operation IN (
|
|
979
|
+
'create','update','retract','supersede','reverse','obsolete','archive','promote'
|
|
980
|
+
)),
|
|
981
|
+
entity_type TEXT NOT NULL CHECK(entity_type IN ('claim','decision','episode','theory','insight','model','user_memo','user_plan','user_issue','user_topic','user_file')),
|
|
982
|
+
entity_id TEXT NOT NULL,
|
|
983
|
+
summary TEXT NOT NULL,
|
|
984
|
+
details TEXT,
|
|
985
|
+
client_name TEXT,
|
|
986
|
+
client_version TEXT,
|
|
987
|
+
session_id TEXT,
|
|
988
|
+
source_tool TEXT,
|
|
989
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f','now')),
|
|
990
|
+
device_id TEXT
|
|
991
|
+
);
|
|
992
|
+
|
|
993
|
+
CREATE INDEX IF NOT EXISTS idx_audit_log_entity ON audit_log(entity_type, entity_id);
|
|
994
|
+
CREATE INDEX IF NOT EXISTS idx_audit_log_operation ON audit_log(operation);
|
|
995
|
+
CREATE INDEX IF NOT EXISTS idx_audit_log_created ON audit_log(created_at);
|
|
996
|
+
CREATE INDEX IF NOT EXISTS idx_audit_log_session ON audit_log(session_id);
|
|
997
|
+
|
|
998
|
+
-- --------------------------------------------------------
|
|
999
|
+
-- store_meta: Key-Valueストア
|
|
1000
|
+
-- --------------------------------------------------------
|
|
1001
|
+
CREATE TABLE IF NOT EXISTS store_meta (
|
|
1002
|
+
key TEXT PRIMARY KEY,
|
|
1003
|
+
value TEXT NOT NULL,
|
|
1004
|
+
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f','now'))
|
|
1005
|
+
);
|
|
1006
|
+
|
|
1007
|
+
-- --------------------------------------------------------
|
|
1008
|
+
-- tombstones: 削除の同期伝播
|
|
1009
|
+
-- --------------------------------------------------------
|
|
1010
|
+
CREATE TABLE IF NOT EXISTS tombstones (
|
|
1011
|
+
id TEXT PRIMARY KEY,
|
|
1012
|
+
entity_type TEXT NOT NULL,
|
|
1013
|
+
entity_id TEXT NOT NULL,
|
|
1014
|
+
deleted_at TEXT NOT NULL,
|
|
1015
|
+
UNIQUE(entity_type, entity_id)
|
|
1016
|
+
);
|
|
1017
|
+
|
|
1018
|
+
-- --------------------------------------------------------
|
|
1019
|
+
-- unified_search_items: 横断検索テーブル
|
|
1020
|
+
-- --------------------------------------------------------
|
|
1021
|
+
CREATE TABLE IF NOT EXISTS unified_search_items (
|
|
1022
|
+
entity_type TEXT NOT NULL CHECK(entity_type IN ('claim','episode','decision','theory','insight','model','user_memo','user_plan','user_issue','user_topic','user_file')),
|
|
1023
|
+
entity_id TEXT NOT NULL,
|
|
1024
|
+
scope TEXT NOT NULL DEFAULT 'global',
|
|
1025
|
+
category TEXT,
|
|
1026
|
+
title_summary TEXT NOT NULL,
|
|
1027
|
+
search_text TEXT NOT NULL,
|
|
1028
|
+
search_summary TEXT,
|
|
1029
|
+
tags TEXT NOT NULL DEFAULT '[]',
|
|
1030
|
+
l1_embedding BLOB,
|
|
1031
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f','now')),
|
|
1032
|
+
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f','now')),
|
|
1033
|
+
PRIMARY KEY (entity_type, entity_id)
|
|
1034
|
+
);
|
|
1035
|
+
|
|
1036
|
+
CREATE INDEX IF NOT EXISTS idx_unified_search_scope ON unified_search_items(scope);
|
|
1037
|
+
CREATE INDEX IF NOT EXISTS idx_unified_search_entity_type ON unified_search_items(entity_type);
|
|
1038
|
+
CREATE INDEX IF NOT EXISTS idx_unified_search_updated ON unified_search_items(updated_at);
|
|
1039
|
+
CREATE INDEX IF NOT EXISTS idx_unified_search_entity_id ON unified_search_items(entity_id);
|
|
1040
|
+
`);
|
|
1041
|
+
// ================================================================
|
|
1042
|
+
// 2. FTS テーブル作成
|
|
1043
|
+
// ================================================================
|
|
1044
|
+
// --- claims_fts (V28 形式: l2_subject, l2_predicate, l2_object, l2_evidence) ---
|
|
1045
|
+
db.exec(`
|
|
1046
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS claims_fts USING fts5(
|
|
1047
|
+
l2_subject, l2_predicate, l2_object, l2_evidence, l1_content, search_summary,
|
|
1048
|
+
content='claims', content_rowid='rowid', tokenize='trigram'
|
|
1049
|
+
)
|
|
1050
|
+
`);
|
|
1051
|
+
// --- episodes_fts (V20 形式) ---
|
|
1052
|
+
db.exec(`
|
|
1053
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS episodes_fts USING fts5(
|
|
1054
|
+
title, l1_content, l2_context, l2_trigger, l2_principles, l2_problems, l2_desires, l2_decisions, l2_outcomes, search_summary, user_input,
|
|
1055
|
+
content='episodes', content_rowid='rowid', tokenize='trigram'
|
|
1056
|
+
)
|
|
1057
|
+
`);
|
|
1058
|
+
// --- decisions_fts (V20 形式) ---
|
|
1059
|
+
db.exec(`
|
|
1060
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS decisions_fts USING fts5(
|
|
1061
|
+
title, description, l1_content, l2_reasoning, search_summary, user_input,
|
|
1062
|
+
content='decisions', content_rowid='rowid', tokenize='trigram'
|
|
1063
|
+
)
|
|
1064
|
+
`);
|
|
1065
|
+
// --- theories_fts (V20 形式) ---
|
|
1066
|
+
db.exec(`
|
|
1067
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS theories_fts USING fts5(
|
|
1068
|
+
title, description, l1_content, l2_core_thesis, l2_principles, evidence_refs, tags, search_summary,
|
|
1069
|
+
content='theories', content_rowid='rowid', tokenize='trigram'
|
|
1070
|
+
)
|
|
1071
|
+
`);
|
|
1072
|
+
// --- models_fts (同上) ---
|
|
1073
|
+
db.exec(`
|
|
1074
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS models_fts USING fts5(
|
|
1075
|
+
title, description, l1_content, l2_core_thesis, l2_principles, evidence_refs, tags, search_summary,
|
|
1076
|
+
content='models', content_rowid='rowid', tokenize='trigram'
|
|
1077
|
+
)
|
|
1078
|
+
`);
|
|
1079
|
+
// --- insights_fts (V19 形式) ---
|
|
1080
|
+
db.exec(`
|
|
1081
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS insights_fts USING fts5(
|
|
1082
|
+
title, description, l1_content, l2_core_thesis, l2_principles, evidence_refs, tags, search_summary,
|
|
1083
|
+
content='insights', content_rowid='rowid', tokenize='trigram'
|
|
1084
|
+
)
|
|
1085
|
+
`);
|
|
1086
|
+
// --- user_memos_fts (V24 形式: l1_content, user_input) ---
|
|
1087
|
+
db.exec(`
|
|
1088
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS user_memos_fts USING fts5(
|
|
1089
|
+
title, l1_content, tags, search_summary, user_input,
|
|
1090
|
+
content='user_memos', content_rowid='rowid', tokenize='trigram'
|
|
1091
|
+
)
|
|
1092
|
+
`);
|
|
1093
|
+
// --- user_plans_fts (V24 形式: l1_content) ---
|
|
1094
|
+
db.exec(`
|
|
1095
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS user_plans_fts USING fts5(
|
|
1096
|
+
title, l1_content, tags, search_summary,
|
|
1097
|
+
content='user_plans', content_rowid='rowid', tokenize='trigram'
|
|
1098
|
+
)
|
|
1099
|
+
`);
|
|
1100
|
+
// --- user_issues_fts (V24 形式: l1_content) ---
|
|
1101
|
+
db.exec(`
|
|
1102
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS user_issues_fts USING fts5(
|
|
1103
|
+
title, l1_content, entries, tags, search_summary,
|
|
1104
|
+
content='user_issues', content_rowid='rowid', tokenize='trigram'
|
|
1105
|
+
)
|
|
1106
|
+
`);
|
|
1107
|
+
// --- user_topics_fts (V29 形式) ---
|
|
1108
|
+
db.exec(`
|
|
1109
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS user_topics_fts USING fts5(
|
|
1110
|
+
title, summary, tags, search_summary,
|
|
1111
|
+
content='user_topics', content_rowid='rowid', tokenize='trigram'
|
|
1112
|
+
)
|
|
1113
|
+
`);
|
|
1114
|
+
// --- unified_search_fts (V29 形式) ---
|
|
1115
|
+
db.exec(`
|
|
1116
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS unified_search_fts USING fts5(
|
|
1117
|
+
search_text, title_summary, search_summary,
|
|
1118
|
+
content='unified_search_items', content_rowid='rowid', tokenize='trigram'
|
|
1119
|
+
)
|
|
1120
|
+
`);
|
|
1121
|
+
// ================================================================
|
|
1122
|
+
// 3. FTS 同期トリガー作成
|
|
1123
|
+
// ================================================================
|
|
1124
|
+
// --- claims_fts triggers (V28 形式) ---
|
|
1125
|
+
db.exec(`
|
|
1126
|
+
CREATE TRIGGER claims_fts_insert AFTER INSERT ON claims BEGIN
|
|
1127
|
+
INSERT INTO claims_fts(rowid, l2_subject, l2_predicate, l2_object, l2_evidence, l1_content, search_summary)
|
|
1128
|
+
VALUES (new.rowid, new.l2_subject, new.l2_predicate, new.l2_object, new.l2_evidence, new.l1_content, new.search_summary);
|
|
1129
|
+
END
|
|
1130
|
+
`);
|
|
1131
|
+
db.exec(`
|
|
1132
|
+
CREATE TRIGGER claims_fts_update AFTER UPDATE ON claims BEGIN
|
|
1133
|
+
INSERT INTO claims_fts(claims_fts, rowid, l2_subject, l2_predicate, l2_object, l2_evidence, l1_content, search_summary)
|
|
1134
|
+
VALUES ('delete', old.rowid, old.l2_subject, old.l2_predicate, old.l2_object, old.l2_evidence, old.l1_content, old.search_summary);
|
|
1135
|
+
INSERT INTO claims_fts(rowid, l2_subject, l2_predicate, l2_object, l2_evidence, l1_content, search_summary)
|
|
1136
|
+
VALUES (new.rowid, new.l2_subject, new.l2_predicate, new.l2_object, new.l2_evidence, new.l1_content, new.search_summary);
|
|
1137
|
+
END
|
|
1138
|
+
`);
|
|
1139
|
+
db.exec(`
|
|
1140
|
+
CREATE TRIGGER claims_fts_delete AFTER DELETE ON claims BEGIN
|
|
1141
|
+
INSERT INTO claims_fts(claims_fts, rowid, l2_subject, l2_predicate, l2_object, l2_evidence, l1_content, search_summary)
|
|
1142
|
+
VALUES ('delete', old.rowid, old.l2_subject, old.l2_predicate, old.l2_object, old.l2_evidence, old.l1_content, old.search_summary);
|
|
1143
|
+
END
|
|
1144
|
+
`);
|
|
1145
|
+
// --- episodes_fts triggers ---
|
|
1146
|
+
db.exec(`
|
|
1147
|
+
CREATE TRIGGER episodes_fts_insert AFTER INSERT ON episodes BEGIN
|
|
1148
|
+
INSERT INTO episodes_fts(rowid, title, l1_content, l2_context, l2_trigger, l2_principles, l2_problems, l2_desires, l2_decisions, l2_outcomes, search_summary, user_input)
|
|
1149
|
+
VALUES (new.rowid, new.title, new.l1_content, new.l2_context, new.l2_trigger,
|
|
1150
|
+
${jc("new.l2_principles")}, ${jc("new.l2_problems")}, ${jc("new.l2_desires")},
|
|
1151
|
+
${jc("new.l2_decisions")}, ${jc("new.l2_outcomes")},
|
|
1152
|
+
new.search_summary, new.user_input);
|
|
1153
|
+
END
|
|
1154
|
+
`);
|
|
1155
|
+
db.exec(`
|
|
1156
|
+
CREATE TRIGGER episodes_fts_update AFTER UPDATE ON episodes BEGIN
|
|
1157
|
+
INSERT INTO episodes_fts(episodes_fts, rowid, title, l1_content, l2_context, l2_trigger, l2_principles, l2_problems, l2_desires, l2_decisions, l2_outcomes, search_summary, user_input)
|
|
1158
|
+
VALUES ('delete', old.rowid, old.title, old.l1_content, old.l2_context, old.l2_trigger,
|
|
1159
|
+
${jc("old.l2_principles")}, ${jc("old.l2_problems")}, ${jc("old.l2_desires")},
|
|
1160
|
+
${jc("old.l2_decisions")}, ${jc("old.l2_outcomes")},
|
|
1161
|
+
old.search_summary, old.user_input);
|
|
1162
|
+
INSERT INTO episodes_fts(rowid, title, l1_content, l2_context, l2_trigger, l2_principles, l2_problems, l2_desires, l2_decisions, l2_outcomes, search_summary, user_input)
|
|
1163
|
+
VALUES (new.rowid, new.title, new.l1_content, new.l2_context, new.l2_trigger,
|
|
1164
|
+
${jc("new.l2_principles")}, ${jc("new.l2_problems")}, ${jc("new.l2_desires")},
|
|
1165
|
+
${jc("new.l2_decisions")}, ${jc("new.l2_outcomes")},
|
|
1166
|
+
new.search_summary, new.user_input);
|
|
1167
|
+
END
|
|
1168
|
+
`);
|
|
1169
|
+
db.exec(`
|
|
1170
|
+
CREATE TRIGGER episodes_fts_delete AFTER DELETE ON episodes BEGIN
|
|
1171
|
+
INSERT INTO episodes_fts(episodes_fts, rowid, title, l1_content, l2_context, l2_trigger, l2_principles, l2_problems, l2_desires, l2_decisions, l2_outcomes, search_summary, user_input)
|
|
1172
|
+
VALUES ('delete', old.rowid, old.title, old.l1_content, old.l2_context, old.l2_trigger,
|
|
1173
|
+
${jc("old.l2_principles")}, ${jc("old.l2_problems")}, ${jc("old.l2_desires")},
|
|
1174
|
+
${jc("old.l2_decisions")}, ${jc("old.l2_outcomes")},
|
|
1175
|
+
old.search_summary, old.user_input);
|
|
1176
|
+
END
|
|
1177
|
+
`);
|
|
1178
|
+
// --- decisions_fts triggers ---
|
|
1179
|
+
db.exec(`
|
|
1180
|
+
CREATE TRIGGER decisions_fts_insert AFTER INSERT ON decisions BEGIN
|
|
1181
|
+
INSERT INTO decisions_fts(rowid, title, description, l1_content, l2_reasoning, search_summary, user_input)
|
|
1182
|
+
VALUES (new.rowid, new.title, new.description, new.l1_content, new.l2_reasoning, new.search_summary, new.user_input);
|
|
1183
|
+
END
|
|
1184
|
+
`);
|
|
1185
|
+
db.exec(`
|
|
1186
|
+
CREATE TRIGGER decisions_fts_update AFTER UPDATE ON decisions BEGIN
|
|
1187
|
+
INSERT INTO decisions_fts(decisions_fts, rowid, title, description, l1_content, l2_reasoning, search_summary, user_input)
|
|
1188
|
+
VALUES ('delete', old.rowid, old.title, old.description, old.l1_content, old.l2_reasoning, old.search_summary, old.user_input);
|
|
1189
|
+
INSERT INTO decisions_fts(rowid, title, description, l1_content, l2_reasoning, search_summary, user_input)
|
|
1190
|
+
VALUES (new.rowid, new.title, new.description, new.l1_content, new.l2_reasoning, new.search_summary, new.user_input);
|
|
1191
|
+
END
|
|
1192
|
+
`);
|
|
1193
|
+
db.exec(`
|
|
1194
|
+
CREATE TRIGGER decisions_fts_delete AFTER DELETE ON decisions BEGIN
|
|
1195
|
+
INSERT INTO decisions_fts(decisions_fts, rowid, title, description, l1_content, l2_reasoning, search_summary, user_input)
|
|
1196
|
+
VALUES ('delete', old.rowid, old.title, old.description, old.l1_content, old.l2_reasoning, old.search_summary, old.user_input);
|
|
1197
|
+
END
|
|
1198
|
+
`);
|
|
1199
|
+
// --- theories_fts triggers ---
|
|
1200
|
+
db.exec(`
|
|
1201
|
+
CREATE TRIGGER theories_fts_insert AFTER INSERT ON theories BEGIN
|
|
1202
|
+
INSERT INTO theories_fts(rowid, title, description, l1_content, l2_core_thesis, l2_principles, evidence_refs, tags, search_summary)
|
|
1203
|
+
VALUES (new.rowid, new.title, new.description, new.l1_content, new.l2_core_thesis,
|
|
1204
|
+
${jc("new.l2_principles")}, ${jc("new.evidence_refs")}, ${jc("new.tags")}, new.search_summary);
|
|
1205
|
+
END
|
|
1206
|
+
`);
|
|
1207
|
+
db.exec(`
|
|
1208
|
+
CREATE TRIGGER theories_fts_update AFTER UPDATE ON theories BEGIN
|
|
1209
|
+
INSERT INTO theories_fts(theories_fts, rowid, title, description, l1_content, l2_core_thesis, l2_principles, evidence_refs, tags, search_summary)
|
|
1210
|
+
VALUES ('delete', old.rowid, old.title, old.description, old.l1_content, old.l2_core_thesis,
|
|
1211
|
+
${jc("old.l2_principles")}, ${jc("old.evidence_refs")}, ${jc("old.tags")}, old.search_summary);
|
|
1212
|
+
INSERT INTO theories_fts(rowid, title, description, l1_content, l2_core_thesis, l2_principles, evidence_refs, tags, search_summary)
|
|
1213
|
+
VALUES (new.rowid, new.title, new.description, new.l1_content, new.l2_core_thesis,
|
|
1214
|
+
${jc("new.l2_principles")}, ${jc("new.evidence_refs")}, ${jc("new.tags")}, new.search_summary);
|
|
1215
|
+
END
|
|
1216
|
+
`);
|
|
1217
|
+
db.exec(`
|
|
1218
|
+
CREATE TRIGGER theories_fts_delete AFTER DELETE ON theories BEGIN
|
|
1219
|
+
INSERT INTO theories_fts(theories_fts, rowid, title, description, l1_content, l2_core_thesis, l2_principles, evidence_refs, tags, search_summary)
|
|
1220
|
+
VALUES ('delete', old.rowid, old.title, old.description, old.l1_content, old.l2_core_thesis,
|
|
1221
|
+
${jc("old.l2_principles")}, ${jc("old.evidence_refs")}, ${jc("old.tags")}, old.search_summary);
|
|
1222
|
+
END
|
|
1223
|
+
`);
|
|
1224
|
+
// --- models_fts triggers ---
|
|
1225
|
+
db.exec(`
|
|
1226
|
+
CREATE TRIGGER models_fts_insert AFTER INSERT ON models BEGIN
|
|
1227
|
+
INSERT INTO models_fts(rowid, title, description, l1_content, l2_core_thesis, l2_principles, evidence_refs, tags, search_summary)
|
|
1228
|
+
VALUES (new.rowid, new.title, new.description, new.l1_content, new.l2_core_thesis,
|
|
1229
|
+
${jc("new.l2_principles")}, ${jc("new.evidence_refs")}, ${jc("new.tags")}, new.search_summary);
|
|
1230
|
+
END
|
|
1231
|
+
`);
|
|
1232
|
+
db.exec(`
|
|
1233
|
+
CREATE TRIGGER models_fts_update AFTER UPDATE ON models BEGIN
|
|
1234
|
+
INSERT INTO models_fts(models_fts, rowid, title, description, l1_content, l2_core_thesis, l2_principles, evidence_refs, tags, search_summary)
|
|
1235
|
+
VALUES ('delete', old.rowid, old.title, old.description, old.l1_content, old.l2_core_thesis,
|
|
1236
|
+
${jc("old.l2_principles")}, ${jc("old.evidence_refs")}, ${jc("old.tags")}, old.search_summary);
|
|
1237
|
+
INSERT INTO models_fts(rowid, title, description, l1_content, l2_core_thesis, l2_principles, evidence_refs, tags, search_summary)
|
|
1238
|
+
VALUES (new.rowid, new.title, new.description, new.l1_content, new.l2_core_thesis,
|
|
1239
|
+
${jc("new.l2_principles")}, ${jc("new.evidence_refs")}, ${jc("new.tags")}, new.search_summary);
|
|
1240
|
+
END
|
|
1241
|
+
`);
|
|
1242
|
+
db.exec(`
|
|
1243
|
+
CREATE TRIGGER models_fts_delete AFTER DELETE ON models BEGIN
|
|
1244
|
+
INSERT INTO models_fts(models_fts, rowid, title, description, l1_content, l2_core_thesis, l2_principles, evidence_refs, tags, search_summary)
|
|
1245
|
+
VALUES ('delete', old.rowid, old.title, old.description, old.l1_content, old.l2_core_thesis,
|
|
1246
|
+
${jc("old.l2_principles")}, ${jc("old.evidence_refs")}, ${jc("old.tags")}, old.search_summary);
|
|
1247
|
+
END
|
|
1248
|
+
`);
|
|
1249
|
+
// --- insights_fts triggers ---
|
|
1250
|
+
db.exec(`
|
|
1251
|
+
CREATE TRIGGER insights_fts_insert AFTER INSERT ON insights BEGIN
|
|
1252
|
+
INSERT INTO insights_fts(rowid, title, description, l1_content, l2_core_thesis, l2_principles, evidence_refs, tags, search_summary)
|
|
1253
|
+
VALUES (new.rowid, new.title, new.description, new.l1_content, new.l2_core_thesis,
|
|
1254
|
+
${jc("new.l2_principles")}, ${jc("new.evidence_refs")}, ${jc("new.tags")}, new.search_summary);
|
|
1255
|
+
END
|
|
1256
|
+
`);
|
|
1257
|
+
db.exec(`
|
|
1258
|
+
CREATE TRIGGER insights_fts_update AFTER UPDATE ON insights BEGIN
|
|
1259
|
+
INSERT INTO insights_fts(insights_fts, rowid, title, description, l1_content, l2_core_thesis, l2_principles, evidence_refs, tags, search_summary)
|
|
1260
|
+
VALUES ('delete', old.rowid, old.title, old.description, old.l1_content, old.l2_core_thesis,
|
|
1261
|
+
${jc("old.l2_principles")}, ${jc("old.evidence_refs")}, ${jc("old.tags")}, old.search_summary);
|
|
1262
|
+
INSERT INTO insights_fts(rowid, title, description, l1_content, l2_core_thesis, l2_principles, evidence_refs, tags, search_summary)
|
|
1263
|
+
VALUES (new.rowid, new.title, new.description, new.l1_content, new.l2_core_thesis,
|
|
1264
|
+
${jc("new.l2_principles")}, ${jc("new.evidence_refs")}, ${jc("new.tags")}, new.search_summary);
|
|
1265
|
+
END
|
|
1266
|
+
`);
|
|
1267
|
+
db.exec(`
|
|
1268
|
+
CREATE TRIGGER insights_fts_delete AFTER DELETE ON insights BEGIN
|
|
1269
|
+
INSERT INTO insights_fts(insights_fts, rowid, title, description, l1_content, l2_core_thesis, l2_principles, evidence_refs, tags, search_summary)
|
|
1270
|
+
VALUES ('delete', old.rowid, old.title, old.description, old.l1_content, old.l2_core_thesis,
|
|
1271
|
+
${jc("old.l2_principles")}, ${jc("old.evidence_refs")}, ${jc("old.tags")}, old.search_summary);
|
|
1272
|
+
END
|
|
1273
|
+
`);
|
|
1274
|
+
// --- user_memos_fts triggers (V24 形式: l1_content, user_input) ---
|
|
1275
|
+
db.exec(`
|
|
1276
|
+
CREATE TRIGGER user_memos_fts_insert AFTER INSERT ON user_memos BEGIN
|
|
1277
|
+
INSERT INTO user_memos_fts(rowid, title, l1_content, tags, search_summary, user_input)
|
|
1278
|
+
VALUES (new.rowid, new.title, new.l1_content, ${jc("new.tags")}, new.search_summary, new.user_input);
|
|
1279
|
+
END
|
|
1280
|
+
`);
|
|
1281
|
+
db.exec(`
|
|
1282
|
+
CREATE TRIGGER user_memos_fts_update AFTER UPDATE ON user_memos BEGIN
|
|
1283
|
+
INSERT INTO user_memos_fts(user_memos_fts, rowid, title, l1_content, tags, search_summary, user_input)
|
|
1284
|
+
VALUES ('delete', old.rowid, old.title, old.l1_content, ${jc("old.tags")}, old.search_summary, old.user_input);
|
|
1285
|
+
INSERT INTO user_memos_fts(rowid, title, l1_content, tags, search_summary, user_input)
|
|
1286
|
+
VALUES (new.rowid, new.title, new.l1_content, ${jc("new.tags")}, new.search_summary, new.user_input);
|
|
1287
|
+
END
|
|
1288
|
+
`);
|
|
1289
|
+
db.exec(`
|
|
1290
|
+
CREATE TRIGGER user_memos_fts_delete AFTER DELETE ON user_memos BEGIN
|
|
1291
|
+
INSERT INTO user_memos_fts(user_memos_fts, rowid, title, l1_content, tags, search_summary, user_input)
|
|
1292
|
+
VALUES ('delete', old.rowid, old.title, old.l1_content, ${jc("old.tags")}, old.search_summary, old.user_input);
|
|
1293
|
+
END
|
|
1294
|
+
`);
|
|
1295
|
+
// --- user_plans_fts triggers (V24 形式: l1_content) ---
|
|
1296
|
+
db.exec(`
|
|
1297
|
+
CREATE TRIGGER user_plans_fts_insert AFTER INSERT ON user_plans BEGIN
|
|
1298
|
+
INSERT INTO user_plans_fts(rowid, title, l1_content, tags, search_summary)
|
|
1299
|
+
VALUES (new.rowid, new.title, new.l1_content, ${jc("new.tags")}, new.search_summary);
|
|
1300
|
+
END
|
|
1301
|
+
`);
|
|
1302
|
+
db.exec(`
|
|
1303
|
+
CREATE TRIGGER user_plans_fts_update AFTER UPDATE ON user_plans BEGIN
|
|
1304
|
+
INSERT INTO user_plans_fts(user_plans_fts, rowid, title, l1_content, tags, search_summary)
|
|
1305
|
+
VALUES ('delete', old.rowid, old.title, old.l1_content, ${jc("old.tags")}, old.search_summary);
|
|
1306
|
+
INSERT INTO user_plans_fts(rowid, title, l1_content, tags, search_summary)
|
|
1307
|
+
VALUES (new.rowid, new.title, new.l1_content, ${jc("new.tags")}, new.search_summary);
|
|
1308
|
+
END
|
|
1309
|
+
`);
|
|
1310
|
+
db.exec(`
|
|
1311
|
+
CREATE TRIGGER user_plans_fts_delete AFTER DELETE ON user_plans BEGIN
|
|
1312
|
+
INSERT INTO user_plans_fts(user_plans_fts, rowid, title, l1_content, tags, search_summary)
|
|
1313
|
+
VALUES ('delete', old.rowid, old.title, old.l1_content, ${jc("old.tags")}, old.search_summary);
|
|
1314
|
+
END
|
|
1315
|
+
`);
|
|
1316
|
+
// --- user_issues_fts triggers (V24 形式: l1_content) ---
|
|
1317
|
+
db.exec(`
|
|
1318
|
+
CREATE TRIGGER user_issues_fts_insert AFTER INSERT ON user_issues BEGIN
|
|
1319
|
+
INSERT INTO user_issues_fts(rowid, title, l1_content, entries, tags, search_summary)
|
|
1320
|
+
VALUES (new.rowid, new.title, new.l1_content, ${jc("new.entries")}, ${jc("new.tags")}, new.search_summary);
|
|
1321
|
+
END
|
|
1322
|
+
`);
|
|
1323
|
+
db.exec(`
|
|
1324
|
+
CREATE TRIGGER user_issues_fts_update AFTER UPDATE ON user_issues BEGIN
|
|
1325
|
+
INSERT INTO user_issues_fts(user_issues_fts, rowid, title, l1_content, entries, tags, search_summary)
|
|
1326
|
+
VALUES ('delete', old.rowid, old.title, old.l1_content, ${jc("old.entries")}, ${jc("old.tags")}, old.search_summary);
|
|
1327
|
+
INSERT INTO user_issues_fts(rowid, title, l1_content, entries, tags, search_summary)
|
|
1328
|
+
VALUES (new.rowid, new.title, new.l1_content, ${jc("new.entries")}, ${jc("new.tags")}, new.search_summary);
|
|
1329
|
+
END
|
|
1330
|
+
`);
|
|
1331
|
+
db.exec(`
|
|
1332
|
+
CREATE TRIGGER user_issues_fts_delete AFTER DELETE ON user_issues BEGIN
|
|
1333
|
+
INSERT INTO user_issues_fts(user_issues_fts, rowid, title, l1_content, entries, tags, search_summary)
|
|
1334
|
+
VALUES ('delete', old.rowid, old.title, old.l1_content, ${jc("old.entries")}, ${jc("old.tags")}, old.search_summary);
|
|
1335
|
+
END
|
|
1336
|
+
`);
|
|
1337
|
+
// --- user_topics_fts triggers (V29 形式: REPLACE パターン) ---
|
|
1338
|
+
db.exec(`
|
|
1339
|
+
CREATE TRIGGER user_topics_fts_insert AFTER INSERT ON user_topics BEGIN
|
|
1340
|
+
INSERT INTO user_topics_fts(rowid, title, summary, tags, search_summary)
|
|
1341
|
+
VALUES (new.rowid, new.title, new.summary, ${jcTopics("new.tags")}, new.search_summary);
|
|
1342
|
+
END
|
|
1343
|
+
`);
|
|
1344
|
+
db.exec(`
|
|
1345
|
+
CREATE TRIGGER user_topics_fts_update AFTER UPDATE ON user_topics BEGIN
|
|
1346
|
+
INSERT INTO user_topics_fts(user_topics_fts, rowid, title, summary, tags, search_summary)
|
|
1347
|
+
VALUES ('delete', old.rowid, old.title, old.summary, ${jcTopics("old.tags")}, old.search_summary);
|
|
1348
|
+
INSERT INTO user_topics_fts(rowid, title, summary, tags, search_summary)
|
|
1349
|
+
VALUES (new.rowid, new.title, new.summary, ${jcTopics("new.tags")}, new.search_summary);
|
|
1350
|
+
END
|
|
1351
|
+
`);
|
|
1352
|
+
db.exec(`
|
|
1353
|
+
CREATE TRIGGER user_topics_fts_delete AFTER DELETE ON user_topics BEGIN
|
|
1354
|
+
INSERT INTO user_topics_fts(user_topics_fts, rowid, title, summary, tags, search_summary)
|
|
1355
|
+
VALUES ('delete', old.rowid, old.title, old.summary, ${jcTopics("old.tags")}, old.search_summary);
|
|
1356
|
+
END
|
|
1357
|
+
`);
|
|
1358
|
+
// unified_search_items / unified_search_fts トリガー作成(抽出済み関数を呼び出し)
|
|
1359
|
+
createUnifiedSearchTriggers(db);
|
|
1360
|
+
// ================================================================
|
|
1361
|
+
// 5. store_meta 初期値
|
|
1362
|
+
// ================================================================
|
|
1363
|
+
const deviceId = generateClientId();
|
|
1364
|
+
db.prepare("INSERT OR IGNORE INTO store_meta (key, value) VALUES ('device_id', ?)").run(deviceId);
|
|
1365
|
+
db.prepare("INSERT OR IGNORE INTO store_meta (key, value) VALUES ('last_synced_at', '')").run();
|
|
1366
|
+
// ================================================================
|
|
1367
|
+
// 6. schema_version 全バージョン記録(V2〜V32 をスキップさせる)
|
|
1368
|
+
// ================================================================
|
|
1369
|
+
db.exec(`
|
|
1370
|
+
INSERT INTO schema_version (version) VALUES
|
|
1371
|
+
(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),
|
|
1372
|
+
(11),(12),(13),(14),(15),(16),(17),(18),(19),(20),
|
|
1373
|
+
(21),(22),(23),(24),(25),(26),(27),(28),(29),(30),
|
|
1374
|
+
(31),(32);
|
|
1375
|
+
`);
|
|
1376
|
+
}
|
|
1377
|
+
/**
|
|
1378
|
+
* V2スキーマ: L2 Core v0対応
|
|
1379
|
+
* - claims に falsifier カラム追加
|
|
1380
|
+
* - claim_relations テーブル追加(推論グラフ)
|
|
1381
|
+
* - claim_evidence テーブル追加(構造化根拠参照)
|
|
1382
|
+
* - claim_checks テーブル追加(検証ログ)
|
|
1383
|
+
* 仕様: docs/specs/l2-core-v0.md
|
|
1384
|
+
*/
|
|
1385
|
+
function applyV2(db) {
|
|
1386
|
+
db.exec(`
|
|
1387
|
+
-- claims に反証条件カラム追加
|
|
1388
|
+
ALTER TABLE claims ADD COLUMN falsifier TEXT;
|
|
1389
|
+
|
|
1390
|
+
-- claim_relations: Claim間の推論関係(正規化)
|
|
1391
|
+
CREATE TABLE IF NOT EXISTS claim_relations (
|
|
1392
|
+
id TEXT PRIMARY KEY,
|
|
1393
|
+
source_claim_id TEXT NOT NULL,
|
|
1394
|
+
target_claim_id TEXT NOT NULL,
|
|
1395
|
+
relation_type TEXT NOT NULL CHECK(relation_type IN (
|
|
1396
|
+
'supports','contradicts','derives','induces','analogizes','supersedes','depends_on'
|
|
1397
|
+
)),
|
|
1398
|
+
confidence REAL DEFAULT 0.8 CHECK(confidence >= 0.0 AND confidence <= 1.0),
|
|
1399
|
+
reasoning TEXT,
|
|
1400
|
+
source_tool TEXT,
|
|
1401
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1402
|
+
);
|
|
1403
|
+
|
|
1404
|
+
CREATE INDEX IF NOT EXISTS idx_claim_relations_source ON claim_relations(source_claim_id);
|
|
1405
|
+
CREATE INDEX IF NOT EXISTS idx_claim_relations_target ON claim_relations(target_claim_id);
|
|
1406
|
+
CREATE INDEX IF NOT EXISTS idx_claim_relations_type ON claim_relations(relation_type);
|
|
1407
|
+
|
|
1408
|
+
-- claim_evidence: 構造化された根拠参照
|
|
1409
|
+
CREATE TABLE IF NOT EXISTS claim_evidence (
|
|
1410
|
+
id TEXT PRIMARY KEY,
|
|
1411
|
+
claim_id TEXT NOT NULL REFERENCES claims(id),
|
|
1412
|
+
evidence_type TEXT NOT NULL CHECK(evidence_type IN (
|
|
1413
|
+
'url','file','claim','decision','session_log','user_statement','external'
|
|
1414
|
+
)),
|
|
1415
|
+
uri TEXT,
|
|
1416
|
+
span TEXT,
|
|
1417
|
+
content_hash TEXT,
|
|
1418
|
+
description TEXT,
|
|
1419
|
+
verified_at TEXT,
|
|
1420
|
+
source_tool TEXT,
|
|
1421
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1422
|
+
);
|
|
1423
|
+
|
|
1424
|
+
CREATE INDEX IF NOT EXISTS idx_claim_evidence_claim ON claim_evidence(claim_id);
|
|
1425
|
+
CREATE INDEX IF NOT EXISTS idx_claim_evidence_type ON claim_evidence(evidence_type);
|
|
1426
|
+
|
|
1427
|
+
-- claim_checks: 検証ログ
|
|
1428
|
+
CREATE TABLE IF NOT EXISTS claim_checks (
|
|
1429
|
+
id TEXT PRIMARY KEY,
|
|
1430
|
+
claim_id TEXT NOT NULL REFERENCES claims(id),
|
|
1431
|
+
check_type TEXT NOT NULL CHECK(check_type IN (
|
|
1432
|
+
'fact_check','consistency','counter_example','source_verify','user_confirm','auto_expire','falsifier_eval'
|
|
1433
|
+
)),
|
|
1434
|
+
result TEXT NOT NULL CHECK(result IN ('passed','failed','inconclusive','skipped')),
|
|
1435
|
+
details TEXT,
|
|
1436
|
+
source_tool TEXT,
|
|
1437
|
+
checked_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1438
|
+
);
|
|
1439
|
+
|
|
1440
|
+
CREATE INDEX IF NOT EXISTS idx_claim_checks_claim ON claim_checks(claim_id);
|
|
1441
|
+
CREATE INDEX IF NOT EXISTS idx_claim_checks_type ON claim_checks(check_type);
|
|
1442
|
+
CREATE INDEX IF NOT EXISTS idx_claim_checks_result ON claim_checks(result);
|
|
1443
|
+
|
|
1444
|
+
-- スキーマバージョン記録
|
|
1445
|
+
INSERT INTO schema_version (version) VALUES (2);
|
|
1446
|
+
`);
|
|
1447
|
+
}
|
|
1448
|
+
/**
|
|
1449
|
+
* V3スキーマ: Provenance自動付与対応
|
|
1450
|
+
* - claims に client_name, client_version カラム追加
|
|
1451
|
+
* - decisions に client_name, client_version カラム追加
|
|
1452
|
+
* Codexレビュー指摘: source_tool/source_sessionが自己申告値で真正性が担保されない問題への対応
|
|
1453
|
+
* サーバー側でMCP接続元クライアント情報を自動付与する仕組み
|
|
1454
|
+
*/
|
|
1455
|
+
function applyV3(db) {
|
|
1456
|
+
db.exec(`
|
|
1457
|
+
-- claims にProvenance(来歴)カラム追加
|
|
1458
|
+
ALTER TABLE claims ADD COLUMN client_name TEXT;
|
|
1459
|
+
ALTER TABLE claims ADD COLUMN client_version TEXT;
|
|
1460
|
+
|
|
1461
|
+
-- decisions にProvenance(来歴)カラム追加
|
|
1462
|
+
ALTER TABLE decisions ADD COLUMN client_name TEXT;
|
|
1463
|
+
ALTER TABLE decisions ADD COLUMN client_version TEXT;
|
|
1464
|
+
|
|
1465
|
+
-- audit_log: 全操作の監査ログ(誰が・何を・なぜ)
|
|
1466
|
+
CREATE TABLE IF NOT EXISTS audit_log (
|
|
1467
|
+
id TEXT PRIMARY KEY,
|
|
1468
|
+
operation TEXT NOT NULL CHECK(operation IN (
|
|
1469
|
+
'create','update','retract','supersede','reverse','obsolete'
|
|
1470
|
+
)),
|
|
1471
|
+
entity_type TEXT NOT NULL CHECK(entity_type IN ('claim','decision')),
|
|
1472
|
+
entity_id TEXT NOT NULL,
|
|
1473
|
+
summary TEXT NOT NULL,
|
|
1474
|
+
details TEXT,
|
|
1475
|
+
client_name TEXT,
|
|
1476
|
+
client_version TEXT,
|
|
1477
|
+
session_id TEXT,
|
|
1478
|
+
source_tool TEXT,
|
|
1479
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1480
|
+
);
|
|
1481
|
+
|
|
1482
|
+
CREATE INDEX IF NOT EXISTS idx_audit_log_entity ON audit_log(entity_type, entity_id);
|
|
1483
|
+
CREATE INDEX IF NOT EXISTS idx_audit_log_operation ON audit_log(operation);
|
|
1484
|
+
CREATE INDEX IF NOT EXISTS idx_audit_log_created ON audit_log(created_at);
|
|
1485
|
+
CREATE INDEX IF NOT EXISTS idx_audit_log_session ON audit_log(session_id);
|
|
1486
|
+
|
|
1487
|
+
-- スキーマバージョン記録
|
|
1488
|
+
INSERT INTO schema_version (version) VALUES (3);
|
|
1489
|
+
`);
|
|
1490
|
+
}
|
|
1491
|
+
/**
|
|
1492
|
+
* V4スキーマ: Episodeモデル追加
|
|
1493
|
+
* ユーザーの思考パターン・意思決定フレームワークを記録する上位構造。
|
|
1494
|
+
* 原子的事実(Claims)ではなく、文脈→問題→欲求→決定→結果→原則 の流れを格納。
|
|
1495
|
+
* ChatGPT提案のEpisode構造をベースに、~20件の実データ収集でカラム妥当性を検証する。
|
|
1496
|
+
*/
|
|
1497
|
+
function applyV4(db) {
|
|
1498
|
+
db.exec(`
|
|
1499
|
+
-- audit_log のentity_type CHECK制約を拡張('episode'を追加)
|
|
1500
|
+
-- SQLiteはALTER TABLE DROP CONSTRAINTをサポートしないため、テーブル再作成で対応
|
|
1501
|
+
CREATE TABLE IF NOT EXISTS audit_log_new (
|
|
1502
|
+
id TEXT PRIMARY KEY,
|
|
1503
|
+
operation TEXT NOT NULL CHECK(operation IN (
|
|
1504
|
+
'create','update','retract','supersede','reverse','obsolete','archive'
|
|
1505
|
+
)),
|
|
1506
|
+
entity_type TEXT NOT NULL CHECK(entity_type IN ('claim','decision','episode')),
|
|
1507
|
+
entity_id TEXT NOT NULL,
|
|
1508
|
+
summary TEXT NOT NULL,
|
|
1509
|
+
details TEXT,
|
|
1510
|
+
client_name TEXT,
|
|
1511
|
+
client_version TEXT,
|
|
1512
|
+
session_id TEXT,
|
|
1513
|
+
source_tool TEXT,
|
|
1514
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1515
|
+
);
|
|
1516
|
+
|
|
1517
|
+
INSERT INTO audit_log_new SELECT * FROM audit_log;
|
|
1518
|
+
DROP TABLE audit_log;
|
|
1519
|
+
ALTER TABLE audit_log_new RENAME TO audit_log;
|
|
1520
|
+
|
|
1521
|
+
CREATE INDEX IF NOT EXISTS idx_audit_log_entity ON audit_log(entity_type, entity_id);
|
|
1522
|
+
CREATE INDEX IF NOT EXISTS idx_audit_log_operation ON audit_log(operation);
|
|
1523
|
+
CREATE INDEX IF NOT EXISTS idx_audit_log_created ON audit_log(created_at);
|
|
1524
|
+
CREATE INDEX IF NOT EXISTS idx_audit_log_session ON audit_log(session_id);
|
|
1525
|
+
|
|
1526
|
+
-- episodes: 思考パターン・意思決定エピソード
|
|
1527
|
+
CREATE TABLE IF NOT EXISTS episodes (
|
|
1528
|
+
id TEXT PRIMARY KEY,
|
|
1529
|
+
title TEXT NOT NULL,
|
|
1530
|
+
context TEXT,
|
|
1531
|
+
trigger TEXT,
|
|
1532
|
+
problems TEXT NOT NULL DEFAULT '[]',
|
|
1533
|
+
desires TEXT NOT NULL DEFAULT '[]',
|
|
1534
|
+
decisions TEXT NOT NULL DEFAULT '[]',
|
|
1535
|
+
outcomes TEXT NOT NULL DEFAULT '[]',
|
|
1536
|
+
principles TEXT NOT NULL DEFAULT '[]',
|
|
1537
|
+
evidence_refs TEXT NOT NULL DEFAULT '[]',
|
|
1538
|
+
tags TEXT NOT NULL DEFAULT '[]',
|
|
1539
|
+
status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active','archived')),
|
|
1540
|
+
client_name TEXT,
|
|
1541
|
+
client_version TEXT,
|
|
1542
|
+
source_tool TEXT,
|
|
1543
|
+
session_id TEXT,
|
|
1544
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1545
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1546
|
+
);
|
|
1547
|
+
|
|
1548
|
+
-- episodes用インデックス
|
|
1549
|
+
CREATE INDEX IF NOT EXISTS idx_episodes_status ON episodes(status);
|
|
1550
|
+
CREATE INDEX IF NOT EXISTS idx_episodes_created ON episodes(created_at);
|
|
1551
|
+
CREATE INDEX IF NOT EXISTS idx_episodes_updated ON episodes(updated_at);
|
|
1552
|
+
|
|
1553
|
+
-- episodes用FTS5全文検索(title, context, trigger, principlesをインデックス)
|
|
1554
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS episodes_fts USING fts5(
|
|
1555
|
+
title, context, trigger, principles,
|
|
1556
|
+
content='episodes',
|
|
1557
|
+
content_rowid='rowid',
|
|
1558
|
+
tokenize='unicode61'
|
|
1559
|
+
);
|
|
1560
|
+
|
|
1561
|
+
-- FTS同期トリガー: INSERT
|
|
1562
|
+
CREATE TRIGGER IF NOT EXISTS episodes_fts_insert AFTER INSERT ON episodes BEGIN
|
|
1563
|
+
INSERT INTO episodes_fts(rowid, title, context, trigger, principles)
|
|
1564
|
+
VALUES (new.rowid, new.title, new.context, new.trigger, new.principles);
|
|
1565
|
+
END;
|
|
1566
|
+
|
|
1567
|
+
-- FTS同期トリガー: UPDATE
|
|
1568
|
+
CREATE TRIGGER IF NOT EXISTS episodes_fts_update AFTER UPDATE ON episodes BEGIN
|
|
1569
|
+
INSERT INTO episodes_fts(episodes_fts, rowid, title, context, trigger, principles)
|
|
1570
|
+
VALUES ('delete', old.rowid, old.title, old.context, old.trigger, old.principles);
|
|
1571
|
+
INSERT INTO episodes_fts(rowid, title, context, trigger, principles)
|
|
1572
|
+
VALUES (new.rowid, new.title, new.context, new.trigger, new.principles);
|
|
1573
|
+
END;
|
|
1574
|
+
|
|
1575
|
+
-- FTS同期トリガー: DELETE
|
|
1576
|
+
CREATE TRIGGER IF NOT EXISTS episodes_fts_delete AFTER DELETE ON episodes BEGIN
|
|
1577
|
+
INSERT INTO episodes_fts(episodes_fts, rowid, title, context, trigger, principles)
|
|
1578
|
+
VALUES ('delete', old.rowid, old.title, old.context, old.trigger, old.principles);
|
|
1579
|
+
END;
|
|
1580
|
+
|
|
1581
|
+
-- スキーマバージョン記録
|
|
1582
|
+
INSERT INTO schema_version (version) VALUES (4);
|
|
1583
|
+
`);
|
|
1584
|
+
}
|
|
1585
|
+
/**
|
|
1586
|
+
* V5スキーマ: Episode/Decisionスコープ統一 + Decision FTS5 + queryContext拡張
|
|
1587
|
+
* - episodes に scope カラム追加(Claimと同じ形式)
|
|
1588
|
+
* - decisions に scope カラム追加(Claimと同じ形式)
|
|
1589
|
+
* - decisions_fts テーブル作成(title, description, reasoning)
|
|
1590
|
+
* - FTS同期トリガー(INSERT/UPDATE/DELETE)
|
|
1591
|
+
* - 既存Decisionsのバックフィル
|
|
1592
|
+
*/
|
|
1593
|
+
function applyV5(db) {
|
|
1594
|
+
// 部分適用時の再実行安全性のため、列存在チェック付きで逐次実行
|
|
1595
|
+
const transaction = db.transaction(() => {
|
|
1596
|
+
// episodes に scope カラム追加(存在しない場合のみ)
|
|
1597
|
+
if (!hasColumn(db, "episodes", "scope")) {
|
|
1598
|
+
db.exec(`ALTER TABLE episodes ADD COLUMN scope TEXT NOT NULL DEFAULT 'global'`);
|
|
1599
|
+
}
|
|
1600
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_episodes_scope ON episodes(scope)`);
|
|
1601
|
+
// decisions に scope カラム追加(存在しない場合のみ)
|
|
1602
|
+
if (!hasColumn(db, "decisions", "scope")) {
|
|
1603
|
+
db.exec(`ALTER TABLE decisions ADD COLUMN scope TEXT NOT NULL DEFAULT 'global'`);
|
|
1604
|
+
}
|
|
1605
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_decisions_scope ON decisions(scope)`);
|
|
1606
|
+
// decisions用FTS5全文検索
|
|
1607
|
+
db.exec(`
|
|
1608
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS decisions_fts USING fts5(
|
|
1609
|
+
title, description, reasoning,
|
|
1610
|
+
content='decisions',
|
|
1611
|
+
content_rowid='rowid',
|
|
1612
|
+
tokenize='unicode61'
|
|
1613
|
+
)
|
|
1614
|
+
`);
|
|
1615
|
+
// FTS同期トリガー
|
|
1616
|
+
db.exec(`
|
|
1617
|
+
CREATE TRIGGER IF NOT EXISTS decisions_fts_insert AFTER INSERT ON decisions BEGIN
|
|
1618
|
+
INSERT INTO decisions_fts(rowid, title, description, reasoning)
|
|
1619
|
+
VALUES (new.rowid, new.title, new.description, new.reasoning);
|
|
1620
|
+
END
|
|
1621
|
+
`);
|
|
1622
|
+
db.exec(`
|
|
1623
|
+
CREATE TRIGGER IF NOT EXISTS decisions_fts_update AFTER UPDATE ON decisions BEGIN
|
|
1624
|
+
INSERT INTO decisions_fts(decisions_fts, rowid, title, description, reasoning)
|
|
1625
|
+
VALUES ('delete', old.rowid, old.title, old.description, old.reasoning);
|
|
1626
|
+
INSERT INTO decisions_fts(rowid, title, description, reasoning)
|
|
1627
|
+
VALUES (new.rowid, new.title, new.description, new.reasoning);
|
|
1628
|
+
END
|
|
1629
|
+
`);
|
|
1630
|
+
db.exec(`
|
|
1631
|
+
CREATE TRIGGER IF NOT EXISTS decisions_fts_delete AFTER DELETE ON decisions BEGIN
|
|
1632
|
+
INSERT INTO decisions_fts(decisions_fts, rowid, title, description, reasoning)
|
|
1633
|
+
VALUES ('delete', old.rowid, old.title, old.description, old.reasoning);
|
|
1634
|
+
END
|
|
1635
|
+
`);
|
|
1636
|
+
// 既存DecisionsをFTSにバックフィル(FTSが空の場合のみ)
|
|
1637
|
+
const ftsCount = db.prepare("SELECT COUNT(*) as count FROM decisions_fts").get();
|
|
1638
|
+
if (ftsCount.count === 0) {
|
|
1639
|
+
db.exec(`
|
|
1640
|
+
INSERT INTO decisions_fts(rowid, title, description, reasoning)
|
|
1641
|
+
SELECT rowid, title, description, reasoning FROM decisions
|
|
1642
|
+
`);
|
|
1643
|
+
}
|
|
1644
|
+
// スキーマバージョン記録
|
|
1645
|
+
db.exec(`INSERT INTO schema_version (version) VALUES (5)`);
|
|
1646
|
+
});
|
|
1647
|
+
transaction();
|
|
1648
|
+
}
|
|
1649
|
+
/**
|
|
1650
|
+
* V6スキーマ: store_meta テーブル追加(バトンリレー用)
|
|
1651
|
+
* デバイス間でDBを引き継ぐためのメタデータ管理。
|
|
1652
|
+
* device_id でデバイスを識別し、last_synced_at で最終同期日時を追跡する。
|
|
1653
|
+
*/
|
|
1654
|
+
function applyV6(db) {
|
|
1655
|
+
db.exec(`
|
|
1656
|
+
-- store_meta: Store単位のメタデータ(Key-Valueストア)
|
|
1657
|
+
CREATE TABLE IF NOT EXISTS store_meta (
|
|
1658
|
+
key TEXT PRIMARY KEY,
|
|
1659
|
+
value TEXT NOT NULL,
|
|
1660
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1661
|
+
);
|
|
1662
|
+
|
|
1663
|
+
-- スキーマバージョン記録
|
|
1664
|
+
INSERT INTO schema_version (version) VALUES (6);
|
|
1665
|
+
`);
|
|
1666
|
+
// device_id を初期生成(UUIDv4相当のランダムID)
|
|
1667
|
+
const deviceId = generateClientId();
|
|
1668
|
+
db.prepare("INSERT OR IGNORE INTO store_meta (key, value) VALUES ('device_id', ?)").run(deviceId);
|
|
1669
|
+
db.prepare("INSERT OR IGNORE INTO store_meta (key, value) VALUES ('last_synced_at', '')").run();
|
|
1670
|
+
}
|
|
1671
|
+
/**
|
|
1672
|
+
* ランダムなクライアントIDを生成(UUIDv4形式)
|
|
1673
|
+
*/
|
|
1674
|
+
function generateClientId() {
|
|
1675
|
+
const bytes = new Uint8Array(16);
|
|
1676
|
+
crypto.getRandomValues(bytes);
|
|
1677
|
+
bytes[6] = (bytes[6] & 0x0f) | 0x40; // version 4
|
|
1678
|
+
bytes[8] = (bytes[8] & 0x3f) | 0x80; // variant 1
|
|
1679
|
+
const hex = Array.from(bytes).map(b => b.toString(16).padStart(2, "0")).join("");
|
|
1680
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
1681
|
+
}
|
|
1682
|
+
/**
|
|
1683
|
+
* V7スキーマ: 昇格ツール(promote)対応
|
|
1684
|
+
* - claims.status CHECK制約に 'promoted' 追加
|
|
1685
|
+
* - decisions.status CHECK制約に 'promoted' 追加
|
|
1686
|
+
* - episodes.status CHECK制約に 'promoted' 追加
|
|
607
1687
|
* - audit_log.operation CHECK制約に 'promote' 追加
|
|
608
1688
|
* - claims/decisions/episodes に promoted_from_store, promoted_from_id カラム追加
|
|
609
1689
|
*
|
|
@@ -4004,21 +5084,25 @@ function applyV20(db) {
|
|
|
4004
5084
|
}
|
|
4005
5085
|
}
|
|
4006
5086
|
/**
|
|
4007
|
-
* V21: audit_log に
|
|
5087
|
+
* V21: audit_log に device_id カラム追加 + 既存レコードの backfill
|
|
4008
5088
|
* どのデバイスで作られたレコードか追跡するため。
|
|
4009
5089
|
* sync で複数デバイスのデータが混在する前に backfill することで、
|
|
4010
5090
|
* 既存データにもデバイス情報を付与する。
|
|
4011
5091
|
*/
|
|
4012
5092
|
function applyV21(db) {
|
|
4013
|
-
// audit_log に
|
|
5093
|
+
// audit_log に device_id カラムを追加
|
|
5094
|
+
// 注: 既存DBとの互換性のためカラム名は client_id のまま ALTER する
|
|
5095
|
+
// V29 で audit_log 再作成時に device_id にリネームされる
|
|
4014
5096
|
if (!hasColumn(db, "audit_log", "client_id")) {
|
|
4015
5097
|
db.exec(`ALTER TABLE audit_log ADD COLUMN client_id TEXT`);
|
|
4016
5098
|
}
|
|
4017
|
-
// 既存レコードに現在のデバイスの
|
|
5099
|
+
// 既存レコードに現在のデバイスの device_id を backfill
|
|
4018
5100
|
// (まだマージされていない = すべて自分のデバイスで作ったレコード)
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
db.prepare("
|
|
5101
|
+
// 注: V6 が 'client_id' キーで保存した既存DBのためフォールバック読み取り
|
|
5102
|
+
const deviceIdRow = (db.prepare("SELECT value FROM store_meta WHERE key = 'device_id'").get() ??
|
|
5103
|
+
db.prepare("SELECT value FROM store_meta WHERE key = 'client_id'").get());
|
|
5104
|
+
if (deviceIdRow) {
|
|
5105
|
+
db.prepare("UPDATE audit_log SET client_id = ? WHERE client_id IS NULL").run(deviceIdRow.value);
|
|
4022
5106
|
}
|
|
4023
5107
|
db.exec(`INSERT INTO schema_version (version) VALUES (21)`);
|
|
4024
5108
|
}
|
|
@@ -5549,110 +6633,919 @@ function applyV27(db) {
|
|
|
5549
6633
|
|
|
5550
6634
|
INSERT INTO schema_version (version) VALUES (27);
|
|
5551
6635
|
`);
|
|
5552
|
-
// unified_search_items triggers for claims(新カラム名)
|
|
5553
|
-
const jc = (col) => `replace(replace(replace(${col}, '["',''), '"]',''), '","',' ')`;
|
|
5554
|
-
const coal = (col) => `COALESCE(${col}, '')`;
|
|
5555
|
-
const columns = "entity_type, entity_id, scope, category, title_summary, search_text, search_summary, tags, created_at, updated_at";
|
|
5556
|
-
const claimTitle = (p) => `${p}l2_subject || ' ' || ${p}l2_predicate || ' ' || ${p}l2_object`;
|
|
5557
|
-
const claimSearch = (p) => `${p}l2_subject || ' ' || ${p}l2_predicate || ' ' || ${p}l2_object || ' ' || ${coal(`${p}evidence`)} || ' ' || ${coal(`${p}falsifier`)} || ' ' || ${coal(`${p}l1_content`)} || ' ' || ${coal(`${p}search_summary`)}`;
|
|
6636
|
+
// unified_search_items triggers for claims(新カラム名)
|
|
6637
|
+
const jc = (col) => `replace(replace(replace(${col}, '["',''), '"]',''), '","',' ')`;
|
|
6638
|
+
const coal = (col) => `COALESCE(${col}, '')`;
|
|
6639
|
+
const columns = "entity_type, entity_id, scope, category, title_summary, search_text, search_summary, tags, created_at, updated_at";
|
|
6640
|
+
const claimTitle = (p) => `${p}l2_subject || ' ' || ${p}l2_predicate || ' ' || ${p}l2_object`;
|
|
6641
|
+
const claimSearch = (p) => `${p}l2_subject || ' ' || ${p}l2_predicate || ' ' || ${p}l2_object || ' ' || ${coal(`${p}evidence`)} || ' ' || ${coal(`${p}falsifier`)} || ' ' || ${coal(`${p}l1_content`)} || ' ' || ${coal(`${p}search_summary`)}`;
|
|
6642
|
+
db.exec(`
|
|
6643
|
+
CREATE TRIGGER claims_unified_insert AFTER INSERT ON claims
|
|
6644
|
+
WHEN new.status = 'active' BEGIN
|
|
6645
|
+
INSERT OR REPLACE INTO unified_search_items(${columns})
|
|
6646
|
+
VALUES ('claim', new.id, new.scope, new.category, ${claimTitle("new.")}, ${claimSearch("new.")}, new.search_summary, '[]', new.created_at, new.updated_at);
|
|
6647
|
+
END
|
|
6648
|
+
`);
|
|
6649
|
+
db.exec(`
|
|
6650
|
+
CREATE TRIGGER claims_unified_update AFTER UPDATE ON claims BEGIN
|
|
6651
|
+
DELETE FROM unified_search_items WHERE entity_type = 'claim' AND entity_id = old.id;
|
|
6652
|
+
INSERT INTO unified_search_items(${columns})
|
|
6653
|
+
SELECT 'claim', new.id, new.scope, new.category, ${claimTitle("new.")}, ${claimSearch("new.")}, new.search_summary, '[]', new.created_at, new.updated_at
|
|
6654
|
+
WHERE new.status = 'active';
|
|
6655
|
+
END
|
|
6656
|
+
`);
|
|
6657
|
+
db.exec(`
|
|
6658
|
+
CREATE TRIGGER claims_unified_delete AFTER DELETE ON claims BEGIN
|
|
6659
|
+
DELETE FROM unified_search_items WHERE entity_type = 'claim' AND entity_id = old.id;
|
|
6660
|
+
END
|
|
6661
|
+
`);
|
|
6662
|
+
}
|
|
6663
|
+
/**
|
|
6664
|
+
* V28: evidence → l2_evidence, falsifier → l2_falsifier リネーム + claims_fts 再構築
|
|
6665
|
+
* 冪等: 既に l2_evidence が存在する場合はリネームをスキップ
|
|
6666
|
+
*/
|
|
6667
|
+
function applyV28(db) {
|
|
6668
|
+
// カラムリネーム(冪等: 既にリネーム済みならスキップ)
|
|
6669
|
+
if (hasColumn(db, "claims", "evidence") && !hasColumn(db, "claims", "l2_evidence")) {
|
|
6670
|
+
db.exec(`ALTER TABLE claims RENAME COLUMN evidence TO l2_evidence`);
|
|
6671
|
+
}
|
|
6672
|
+
if (hasColumn(db, "claims", "falsifier") && !hasColumn(db, "claims", "l2_falsifier")) {
|
|
6673
|
+
db.exec(`ALTER TABLE claims RENAME COLUMN falsifier TO l2_falsifier`);
|
|
6674
|
+
}
|
|
6675
|
+
// claims_fts を再構築(l2_evidence/l2_falsifier 対応)
|
|
6676
|
+
db.exec(`
|
|
6677
|
+
DROP TRIGGER IF EXISTS claims_fts_insert;
|
|
6678
|
+
DROP TRIGGER IF EXISTS claims_fts_update;
|
|
6679
|
+
DROP TRIGGER IF EXISTS claims_fts_delete;
|
|
6680
|
+
DROP TABLE IF EXISTS claims_fts;
|
|
6681
|
+
|
|
6682
|
+
CREATE VIRTUAL TABLE claims_fts USING fts5(
|
|
6683
|
+
l2_subject, l2_predicate, l2_object, l2_evidence, l1_content, search_summary,
|
|
6684
|
+
content='claims', content_rowid='rowid', tokenize='trigram'
|
|
6685
|
+
);
|
|
6686
|
+
|
|
6687
|
+
-- FTS同期トリガー: INSERT
|
|
6688
|
+
CREATE TRIGGER claims_fts_insert AFTER INSERT ON claims BEGIN
|
|
6689
|
+
INSERT INTO claims_fts(rowid, l2_subject, l2_predicate, l2_object, l2_evidence, l1_content, search_summary)
|
|
6690
|
+
VALUES (new.rowid, new.l2_subject, new.l2_predicate, new.l2_object, new.l2_evidence, new.l1_content, new.search_summary);
|
|
6691
|
+
END;
|
|
6692
|
+
|
|
6693
|
+
-- FTS同期トリガー: UPDATE
|
|
6694
|
+
CREATE TRIGGER claims_fts_update AFTER UPDATE ON claims BEGIN
|
|
6695
|
+
INSERT INTO claims_fts(claims_fts, rowid, l2_subject, l2_predicate, l2_object, l2_evidence, l1_content, search_summary)
|
|
6696
|
+
VALUES ('delete', old.rowid, old.l2_subject, old.l2_predicate, old.l2_object, old.l2_evidence, old.l1_content, old.search_summary);
|
|
6697
|
+
INSERT INTO claims_fts(rowid, l2_subject, l2_predicate, l2_object, l2_evidence, l1_content, search_summary)
|
|
6698
|
+
VALUES (new.rowid, new.l2_subject, new.l2_predicate, new.l2_object, new.l2_evidence, new.l1_content, new.search_summary);
|
|
6699
|
+
END;
|
|
6700
|
+
|
|
6701
|
+
-- FTS同期トリガー: DELETE
|
|
6702
|
+
CREATE TRIGGER claims_fts_delete AFTER DELETE ON claims BEGIN
|
|
6703
|
+
INSERT INTO claims_fts(claims_fts, rowid, l2_subject, l2_predicate, l2_object, l2_evidence, l1_content, search_summary)
|
|
6704
|
+
VALUES ('delete', old.rowid, old.l2_subject, old.l2_predicate, old.l2_object, old.l2_evidence, old.l1_content, old.search_summary);
|
|
6705
|
+
END;
|
|
6706
|
+
|
|
6707
|
+
-- FTS rebuild
|
|
6708
|
+
INSERT INTO claims_fts(claims_fts) VALUES('rebuild');
|
|
6709
|
+
|
|
6710
|
+
INSERT INTO schema_version (version) VALUES (28);
|
|
6711
|
+
`);
|
|
6712
|
+
// unified_search_items の claims トリガー再作成(l2_evidence/l2_falsifier 対応)
|
|
6713
|
+
const coal = (col) => `COALESCE(${col}, '')`;
|
|
6714
|
+
const columns = "entity_type, entity_id, scope, category, title_summary, search_text, search_summary, tags, created_at, updated_at";
|
|
6715
|
+
const claimTitle = (p) => `${p}l2_subject || ' ' || ${p}l2_predicate || ' ' || ${p}l2_object`;
|
|
6716
|
+
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`)}`;
|
|
6717
|
+
db.exec(`DROP TRIGGER IF EXISTS claims_unified_insert`);
|
|
6718
|
+
db.exec(`DROP TRIGGER IF EXISTS claims_unified_update`);
|
|
6719
|
+
db.exec(`DROP TRIGGER IF EXISTS claims_unified_delete`);
|
|
6720
|
+
db.exec(`
|
|
6721
|
+
CREATE TRIGGER claims_unified_insert AFTER INSERT ON claims
|
|
6722
|
+
WHEN new.status = 'active' BEGIN
|
|
6723
|
+
INSERT OR REPLACE INTO unified_search_items(${columns})
|
|
6724
|
+
VALUES ('claim', new.id, new.scope, new.category, ${claimTitle("new.")}, ${claimSearch("new.")}, new.search_summary, '[]', new.created_at, new.updated_at);
|
|
6725
|
+
END
|
|
6726
|
+
`);
|
|
6727
|
+
db.exec(`
|
|
6728
|
+
CREATE TRIGGER claims_unified_update AFTER UPDATE ON claims BEGIN
|
|
6729
|
+
DELETE FROM unified_search_items WHERE entity_type = 'claim' AND entity_id = old.id;
|
|
6730
|
+
INSERT INTO unified_search_items(${columns})
|
|
6731
|
+
SELECT 'claim', new.id, new.scope, new.category, ${claimTitle("new.")}, ${claimSearch("new.")}, new.search_summary, '[]', new.created_at, new.updated_at
|
|
6732
|
+
WHERE new.status = 'active';
|
|
6733
|
+
END
|
|
6734
|
+
`);
|
|
6735
|
+
db.exec(`
|
|
6736
|
+
CREATE TRIGGER claims_unified_delete AFTER DELETE ON claims BEGIN
|
|
6737
|
+
DELETE FROM unified_search_items WHERE entity_type = 'claim' AND entity_id = old.id;
|
|
6738
|
+
END
|
|
6739
|
+
`);
|
|
6740
|
+
}
|
|
6741
|
+
/**
|
|
6742
|
+
* V29: user_topics テーブル新設(話題管理)
|
|
6743
|
+
* セッション横断で議論トピックを追跡する
|
|
6744
|
+
*/
|
|
6745
|
+
function applyV29(db) {
|
|
6746
|
+
db.exec(`
|
|
6747
|
+
CREATE TABLE IF NOT EXISTS user_topics (
|
|
6748
|
+
id TEXT PRIMARY KEY,
|
|
6749
|
+
title TEXT NOT NULL,
|
|
6750
|
+
summary TEXT NOT NULL,
|
|
6751
|
+
device_id TEXT,
|
|
6752
|
+
cwd TEXT,
|
|
6753
|
+
conversation_id TEXT,
|
|
6754
|
+
priority TEXT NOT NULL DEFAULT 'medium'
|
|
6755
|
+
CHECK(priority IN ('low','medium','high','critical')),
|
|
6756
|
+
tags TEXT NOT NULL DEFAULT '[]',
|
|
6757
|
+
scope TEXT NOT NULL DEFAULT 'global',
|
|
6758
|
+
search_summary TEXT,
|
|
6759
|
+
status TEXT NOT NULL DEFAULT 'active'
|
|
6760
|
+
CHECK(status IN ('active','closed','archived')),
|
|
6761
|
+
l1_embedding BLOB,
|
|
6762
|
+
client_name TEXT,
|
|
6763
|
+
client_version TEXT,
|
|
6764
|
+
source_tool TEXT,
|
|
6765
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
6766
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
6767
|
+
)
|
|
6768
|
+
`);
|
|
6769
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_user_topics_status ON user_topics(status)`);
|
|
6770
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_user_topics_scope ON user_topics(scope)`);
|
|
6771
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_user_topics_priority ON user_topics(priority)`);
|
|
6772
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_user_topics_updated ON user_topics(updated_at)`);
|
|
6773
|
+
// FTS5
|
|
6774
|
+
db.exec(`
|
|
6775
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS user_topics_fts USING fts5(
|
|
6776
|
+
title, summary, tags, search_summary,
|
|
6777
|
+
content='user_topics', content_rowid='rowid', tokenize='trigram'
|
|
6778
|
+
)
|
|
6779
|
+
`);
|
|
6780
|
+
const jc = (col) => `REPLACE(REPLACE(${col}, '[', ''), ']', '')`;
|
|
6781
|
+
// 冪等性: 部分適用からの再実行に備え、既存トリガーを先に削除
|
|
6782
|
+
db.exec(`DROP TRIGGER IF EXISTS user_topics_fts_insert`);
|
|
6783
|
+
db.exec(`DROP TRIGGER IF EXISTS user_topics_fts_update`);
|
|
6784
|
+
db.exec(`DROP TRIGGER IF EXISTS user_topics_fts_delete`);
|
|
6785
|
+
db.exec(`
|
|
6786
|
+
CREATE TRIGGER user_topics_fts_insert AFTER INSERT ON user_topics BEGIN
|
|
6787
|
+
INSERT INTO user_topics_fts(rowid, title, summary, tags, search_summary)
|
|
6788
|
+
VALUES (new.rowid, new.title, new.summary, ${jc("new.tags")}, new.search_summary);
|
|
6789
|
+
END
|
|
6790
|
+
`);
|
|
6791
|
+
db.exec(`
|
|
6792
|
+
CREATE TRIGGER user_topics_fts_update AFTER UPDATE ON user_topics BEGIN
|
|
6793
|
+
INSERT INTO user_topics_fts(user_topics_fts, rowid, title, summary, tags, search_summary)
|
|
6794
|
+
VALUES ('delete', old.rowid, old.title, old.summary, ${jc("old.tags")}, old.search_summary);
|
|
6795
|
+
INSERT INTO user_topics_fts(rowid, title, summary, tags, search_summary)
|
|
6796
|
+
VALUES (new.rowid, new.title, new.summary, ${jc("new.tags")}, new.search_summary);
|
|
6797
|
+
END
|
|
6798
|
+
`);
|
|
6799
|
+
db.exec(`
|
|
6800
|
+
CREATE TRIGGER user_topics_fts_delete AFTER DELETE ON user_topics BEGIN
|
|
6801
|
+
INSERT INTO user_topics_fts(user_topics_fts, rowid, title, summary, tags, search_summary)
|
|
6802
|
+
VALUES ('delete', old.rowid, old.title, old.summary, ${jc("old.tags")}, old.search_summary);
|
|
6803
|
+
END
|
|
6804
|
+
`);
|
|
6805
|
+
// FTS rebuild
|
|
6806
|
+
db.exec(`INSERT INTO user_topics_fts(user_topics_fts) VALUES('rebuild')`);
|
|
6807
|
+
// audit_log の CHECK 制約に user_topic を追加
|
|
6808
|
+
// カラム順序を元のテーブル(V18 + V21 ALTER ADD COLUMN)と一致させる
|
|
6809
|
+
// 冪等性: 前回の部分適用で残存した _new テーブルを掃除
|
|
6810
|
+
db.exec(`DROP TABLE IF EXISTS audit_log_new`);
|
|
6811
|
+
// 冪等性: 前回の部分適用で既に完了している場合はスキップ
|
|
6812
|
+
if (!hasColumn(db, "audit_log", "device_id")) {
|
|
6813
|
+
// client_id → device_id リネーム + CHECK 制約更新
|
|
6814
|
+
db.exec(`
|
|
6815
|
+
CREATE TABLE audit_log_new (
|
|
6816
|
+
id TEXT PRIMARY KEY,
|
|
6817
|
+
operation TEXT NOT NULL CHECK(operation IN (
|
|
6818
|
+
'create','update','retract','supersede','reverse','obsolete','archive','promote'
|
|
6819
|
+
)),
|
|
6820
|
+
entity_type TEXT NOT NULL CHECK(entity_type IN ('claim','decision','episode','theory','insight','model','user_memo','user_plan','user_issue','user_topic')),
|
|
6821
|
+
entity_id TEXT NOT NULL,
|
|
6822
|
+
summary TEXT NOT NULL,
|
|
6823
|
+
details TEXT,
|
|
6824
|
+
client_name TEXT,
|
|
6825
|
+
client_version TEXT,
|
|
6826
|
+
session_id TEXT,
|
|
6827
|
+
source_tool TEXT,
|
|
6828
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
6829
|
+
device_id TEXT
|
|
6830
|
+
)
|
|
6831
|
+
`);
|
|
6832
|
+
db.exec(`
|
|
6833
|
+
INSERT INTO audit_log_new (id, operation, entity_type, entity_id, summary, details, client_name, client_version, session_id, source_tool, created_at, device_id)
|
|
6834
|
+
SELECT id, operation, entity_type, entity_id, summary, details, client_name, client_version, session_id, source_tool, created_at, client_id
|
|
6835
|
+
FROM audit_log
|
|
6836
|
+
`);
|
|
6837
|
+
db.exec(`DROP TABLE audit_log`);
|
|
6838
|
+
db.exec(`ALTER TABLE audit_log_new RENAME TO audit_log`);
|
|
6839
|
+
}
|
|
6840
|
+
// unified_search_items の CHECK 制約に user_topic を追加
|
|
6841
|
+
// SQLite では CHECK 制約の変更に ALTER TABLE が使えないため、テーブル再作成
|
|
6842
|
+
// 先に全ソーステーブルのunifiedトリガーを削除(DROP TABLE時の参照エラー防止)
|
|
6843
|
+
// user_topics も含める(部分適用からの再実行で既に作成済みの場合に備える)
|
|
6844
|
+
// 旧命名 (unified_search_<table>_<op>) と新命名 (<table>_unified_<op>) の両方を削除
|
|
6845
|
+
for (const table of ["claims", "episodes", "decisions", "theories", "insights", "models", "user_memos", "user_plans", "user_issues", "user_topics"]) {
|
|
6846
|
+
db.exec(`DROP TRIGGER IF EXISTS ${table}_unified_insert`);
|
|
6847
|
+
db.exec(`DROP TRIGGER IF EXISTS ${table}_unified_update`);
|
|
6848
|
+
db.exec(`DROP TRIGGER IF EXISTS ${table}_unified_delete`);
|
|
6849
|
+
db.exec(`DROP TRIGGER IF EXISTS unified_search_${table}_insert`);
|
|
6850
|
+
db.exec(`DROP TRIGGER IF EXISTS unified_search_${table}_update`);
|
|
6851
|
+
db.exec(`DROP TRIGGER IF EXISTS unified_search_${table}_delete`);
|
|
6852
|
+
}
|
|
6853
|
+
db.exec(`DROP TRIGGER IF EXISTS unified_search_fts_insert`);
|
|
6854
|
+
db.exec(`DROP TRIGGER IF EXISTS unified_search_fts_update`);
|
|
6855
|
+
db.exec(`DROP TRIGGER IF EXISTS unified_search_fts_delete`);
|
|
6856
|
+
// 冪等性: 部分適用からの再実行に対応
|
|
6857
|
+
// unified_search_items_new が残存している場合、前回の実行で unified_search_items は
|
|
6858
|
+
// 既に DROP 済みの可能性がある。その場合は _new をそのまま RENAME する。
|
|
6859
|
+
const usiExists = db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='unified_search_items'").get();
|
|
6860
|
+
const usiNewExists = db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='unified_search_items_new'").get();
|
|
6861
|
+
if (!usiNewExists) {
|
|
6862
|
+
// 通常パス: _new テーブルを作成してデータコピー
|
|
6863
|
+
db.exec(`
|
|
6864
|
+
CREATE TABLE unified_search_items_new (
|
|
6865
|
+
entity_type TEXT NOT NULL CHECK(entity_type IN ('claim','episode','decision','theory','insight','model','user_memo','user_plan','user_issue','user_topic')),
|
|
6866
|
+
entity_id TEXT NOT NULL,
|
|
6867
|
+
scope TEXT NOT NULL DEFAULT 'global',
|
|
6868
|
+
category TEXT,
|
|
6869
|
+
title_summary TEXT NOT NULL,
|
|
6870
|
+
search_text TEXT NOT NULL,
|
|
6871
|
+
search_summary TEXT,
|
|
6872
|
+
tags TEXT NOT NULL DEFAULT '[]',
|
|
6873
|
+
l1_embedding BLOB,
|
|
6874
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
6875
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
6876
|
+
PRIMARY KEY (entity_type, entity_id)
|
|
6877
|
+
)
|
|
6878
|
+
`);
|
|
6879
|
+
if (usiExists) {
|
|
6880
|
+
db.exec(`
|
|
6881
|
+
INSERT INTO unified_search_items_new (entity_type, entity_id, scope, category, title_summary, search_text, search_summary, tags, l1_embedding, created_at, updated_at)
|
|
6882
|
+
SELECT entity_type, entity_id, scope, category, title_summary, search_text, search_summary, tags, l1_embedding, created_at, updated_at
|
|
6883
|
+
FROM unified_search_items
|
|
6884
|
+
`);
|
|
6885
|
+
}
|
|
6886
|
+
}
|
|
6887
|
+
// _new が存在する状態(新規作成 or 前回残存)→ 旧テーブル削除して RENAME
|
|
6888
|
+
if (usiExists) {
|
|
6889
|
+
db.exec(`DROP TABLE unified_search_items`);
|
|
6890
|
+
}
|
|
6891
|
+
// RENAME が既に完了しているケース(_new も旧も存在しない = 既に unified_search_items になっている)はスキップ
|
|
6892
|
+
const usiNewStillExists = db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='unified_search_items_new'").get();
|
|
6893
|
+
if (usiNewStillExists) {
|
|
6894
|
+
db.exec(`ALTER TABLE unified_search_items_new RENAME TO unified_search_items`);
|
|
6895
|
+
}
|
|
6896
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_unified_search_scope ON unified_search_items(scope)`);
|
|
6897
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_unified_search_entity_type ON unified_search_items(entity_type)`);
|
|
6898
|
+
// unified_search_fts 再構築
|
|
6899
|
+
db.exec(`DROP TABLE IF EXISTS unified_search_fts`);
|
|
6900
|
+
db.exec(`
|
|
6901
|
+
CREATE VIRTUAL TABLE unified_search_fts USING fts5(
|
|
6902
|
+
search_text, title_summary, search_summary,
|
|
6903
|
+
content='unified_search_items', content_rowid='rowid', tokenize='trigram'
|
|
6904
|
+
)
|
|
6905
|
+
`);
|
|
6906
|
+
db.exec(`
|
|
6907
|
+
CREATE TRIGGER unified_search_fts_insert AFTER INSERT ON unified_search_items BEGIN
|
|
6908
|
+
INSERT INTO unified_search_fts(rowid, search_text, title_summary, search_summary)
|
|
6909
|
+
VALUES (new.rowid, new.search_text, new.title_summary, new.search_summary);
|
|
6910
|
+
END
|
|
6911
|
+
`);
|
|
6912
|
+
db.exec(`
|
|
6913
|
+
CREATE TRIGGER unified_search_fts_update AFTER UPDATE ON unified_search_items BEGIN
|
|
6914
|
+
INSERT INTO unified_search_fts(unified_search_fts, rowid, search_text, title_summary, search_summary)
|
|
6915
|
+
VALUES ('delete', old.rowid, old.search_text, old.title_summary, old.search_summary);
|
|
6916
|
+
INSERT INTO unified_search_fts(rowid, search_text, title_summary, search_summary)
|
|
6917
|
+
VALUES (new.rowid, new.search_text, new.title_summary, new.search_summary);
|
|
6918
|
+
END
|
|
6919
|
+
`);
|
|
6920
|
+
db.exec(`
|
|
6921
|
+
CREATE TRIGGER unified_search_fts_delete AFTER DELETE ON unified_search_items BEGIN
|
|
6922
|
+
INSERT INTO unified_search_fts(unified_search_fts, rowid, search_text, title_summary, search_summary)
|
|
6923
|
+
VALUES ('delete', old.rowid, old.search_text, old.title_summary, old.search_summary);
|
|
6924
|
+
END
|
|
6925
|
+
`);
|
|
6926
|
+
db.exec(`INSERT INTO unified_search_fts(unified_search_fts) VALUES('rebuild')`);
|
|
6927
|
+
// 全ソーステーブルの unified トリガーを再作成
|
|
6928
|
+
const coal = (col) => `COALESCE(${col}, '')`;
|
|
6929
|
+
const columns = "entity_type, entity_id, scope, category, title_summary, search_text, search_summary, tags, created_at, updated_at";
|
|
6930
|
+
// claims (V28 形式: l2_evidence/l2_falsifier)
|
|
6931
|
+
const claimTitle = (p) => `${p}l2_subject || ' ' || ${p}l2_predicate || ' ' || ${p}l2_object`;
|
|
6932
|
+
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`)}`;
|
|
6933
|
+
db.exec(`
|
|
6934
|
+
CREATE TRIGGER claims_unified_insert AFTER INSERT ON claims WHEN new.status = 'active' BEGIN
|
|
6935
|
+
INSERT OR REPLACE INTO unified_search_items(${columns})
|
|
6936
|
+
VALUES ('claim', new.id, new.scope, new.category, ${claimTitle("new.")}, ${claimSearch("new.")}, new.search_summary, '[]', new.created_at, new.updated_at);
|
|
6937
|
+
END
|
|
6938
|
+
`);
|
|
6939
|
+
db.exec(`
|
|
6940
|
+
CREATE TRIGGER claims_unified_update AFTER UPDATE ON claims BEGIN
|
|
6941
|
+
DELETE FROM unified_search_items WHERE entity_type = 'claim' AND entity_id = old.id;
|
|
6942
|
+
INSERT INTO unified_search_items(${columns})
|
|
6943
|
+
SELECT 'claim', new.id, new.scope, new.category, ${claimTitle("new.")}, ${claimSearch("new.")}, new.search_summary, '[]', new.created_at, new.updated_at WHERE new.status = 'active';
|
|
6944
|
+
END
|
|
6945
|
+
`);
|
|
6946
|
+
db.exec(`
|
|
6947
|
+
CREATE TRIGGER claims_unified_delete AFTER DELETE ON claims BEGIN
|
|
6948
|
+
DELETE FROM unified_search_items WHERE entity_type = 'claim' AND entity_id = old.id;
|
|
6949
|
+
END
|
|
6950
|
+
`);
|
|
6951
|
+
// episodes (V20 形式: l2_ columns)
|
|
6952
|
+
db.exec(`
|
|
6953
|
+
CREATE TRIGGER episodes_unified_insert AFTER INSERT ON episodes WHEN new.status = 'active' BEGIN
|
|
6954
|
+
INSERT OR REPLACE INTO unified_search_items(${columns})
|
|
6955
|
+
VALUES ('episode', new.id, new.scope, NULL, new.title, new.title || ' ' || ${coal("new.l1_content")} || ' ' || ${coal("new.l2_context")} || ' ' || ${coal("new.l2_trigger")} || ' ' || ${jc("new.l2_problems")} || ' ' || ${jc("new.l2_outcomes")} || ' ' || ${jc("new.l2_principles")} || ' ' || ${jc("new.tags")} || ' ' || ${coal("new.search_summary")}, new.search_summary, new.tags, new.created_at, new.updated_at);
|
|
6956
|
+
END
|
|
6957
|
+
`);
|
|
6958
|
+
db.exec(`
|
|
6959
|
+
CREATE TRIGGER episodes_unified_update AFTER UPDATE ON episodes BEGIN
|
|
6960
|
+
DELETE FROM unified_search_items WHERE entity_type = 'episode' AND entity_id = old.id;
|
|
6961
|
+
INSERT INTO unified_search_items(${columns})
|
|
6962
|
+
SELECT 'episode', new.id, new.scope, NULL, new.title, new.title || ' ' || ${coal("new.l1_content")} || ' ' || ${coal("new.l2_context")} || ' ' || ${coal("new.l2_trigger")} || ' ' || ${jc("new.l2_problems")} || ' ' || ${jc("new.l2_outcomes")} || ' ' || ${jc("new.l2_principles")} || ' ' || ${jc("new.tags")} || ' ' || ${coal("new.search_summary")}, new.search_summary, new.tags, new.created_at, new.updated_at WHERE new.status = 'active';
|
|
6963
|
+
END
|
|
6964
|
+
`);
|
|
6965
|
+
db.exec(`
|
|
6966
|
+
CREATE TRIGGER episodes_unified_delete AFTER DELETE ON episodes BEGIN
|
|
6967
|
+
DELETE FROM unified_search_items WHERE entity_type = 'episode' AND entity_id = old.id;
|
|
6968
|
+
END
|
|
6969
|
+
`);
|
|
6970
|
+
// decisions (V20 形式: l2_ columns)
|
|
6971
|
+
db.exec(`
|
|
6972
|
+
CREATE TRIGGER decisions_unified_insert AFTER INSERT ON decisions WHEN new.status = 'active' BEGIN
|
|
6973
|
+
INSERT OR REPLACE INTO unified_search_items(${columns})
|
|
6974
|
+
VALUES ('decision', new.id, new.scope, NULL, new.title, new.title || ' ' || new.description || ' ' || ${coal("new.l1_content")} || ' ' || new.l2_reasoning || ' ' || ${jc("new.l2_alternatives")} || ' ' || ${coal("new.search_summary")}, new.search_summary, '[]', new.created_at, new.updated_at);
|
|
6975
|
+
END
|
|
6976
|
+
`);
|
|
6977
|
+
db.exec(`
|
|
6978
|
+
CREATE TRIGGER decisions_unified_update AFTER UPDATE ON decisions BEGIN
|
|
6979
|
+
DELETE FROM unified_search_items WHERE entity_type = 'decision' AND entity_id = old.id;
|
|
6980
|
+
INSERT INTO unified_search_items(${columns})
|
|
6981
|
+
SELECT 'decision', new.id, new.scope, NULL, new.title, new.title || ' ' || new.description || ' ' || ${coal("new.l1_content")} || ' ' || new.l2_reasoning || ' ' || ${jc("new.l2_alternatives")} || ' ' || ${coal("new.search_summary")}, new.search_summary, '[]', new.created_at, new.updated_at WHERE new.status = 'active';
|
|
6982
|
+
END
|
|
6983
|
+
`);
|
|
6984
|
+
db.exec(`
|
|
6985
|
+
CREATE TRIGGER decisions_unified_delete AFTER DELETE ON decisions BEGIN
|
|
6986
|
+
DELETE FROM unified_search_items WHERE entity_type = 'decision' AND entity_id = old.id;
|
|
6987
|
+
END
|
|
6988
|
+
`);
|
|
6989
|
+
// theories, insights, models: 共通パターン(l2_ columns)
|
|
6990
|
+
for (const e of [
|
|
6991
|
+
{ type: "theory", table: "theories" },
|
|
6992
|
+
{ type: "insight", table: "insights" },
|
|
6993
|
+
{ type: "model", table: "models" },
|
|
6994
|
+
]) {
|
|
6995
|
+
const searchExpr = (p) => `${p}title || ' ' || ${coal(`${p}description`)} || ' ' || ${coal(`${p}l1_content`)} || ' ' || ${coal(`${p}l2_core_thesis`)} || ' ' || ${jc(`${p}l2_principles`)} || ' ' || ${jc(`${p}evidence_refs`)} || ' ' || ${jc(`${p}tags`)} || ' ' || ${coal(`${p}search_summary`)}`;
|
|
6996
|
+
db.exec(`
|
|
6997
|
+
CREATE TRIGGER ${e.table}_unified_insert AFTER INSERT ON ${e.table} WHEN new.status = 'active' BEGIN
|
|
6998
|
+
INSERT OR REPLACE INTO unified_search_items(${columns})
|
|
6999
|
+
VALUES ('${e.type}', new.id, new.scope, NULL, new.title, ${searchExpr("new.")}, new.search_summary, new.tags, new.created_at, new.updated_at);
|
|
7000
|
+
END
|
|
7001
|
+
`);
|
|
7002
|
+
db.exec(`
|
|
7003
|
+
CREATE TRIGGER ${e.table}_unified_update AFTER UPDATE ON ${e.table} BEGIN
|
|
7004
|
+
DELETE FROM unified_search_items WHERE entity_type = '${e.type}' AND entity_id = old.id;
|
|
7005
|
+
INSERT INTO unified_search_items(${columns})
|
|
7006
|
+
SELECT '${e.type}', new.id, new.scope, NULL, new.title, ${searchExpr("new.")}, new.search_summary, new.tags, new.created_at, new.updated_at WHERE new.status = 'active';
|
|
7007
|
+
END
|
|
7008
|
+
`);
|
|
7009
|
+
db.exec(`
|
|
7010
|
+
CREATE TRIGGER ${e.table}_unified_delete AFTER DELETE ON ${e.table} BEGIN
|
|
7011
|
+
DELETE FROM unified_search_items WHERE entity_type = '${e.type}' AND entity_id = old.id;
|
|
7012
|
+
END
|
|
7013
|
+
`);
|
|
7014
|
+
}
|
|
7015
|
+
// user_memos (V24 形式: l1_content, user_input)
|
|
7016
|
+
db.exec(`
|
|
7017
|
+
CREATE TRIGGER user_memos_unified_insert AFTER INSERT ON user_memos
|
|
7018
|
+
WHEN new.status = 'active' AND new.usage_policy != 'human_directed' BEGIN
|
|
7019
|
+
INSERT OR REPLACE INTO unified_search_items(${columns})
|
|
7020
|
+
VALUES ('user_memo', new.id, new.scope, NULL, new.title, new.title || ' ' || new.l1_content || ' ' || ${coal("new.user_input")} || ' ' || ${jc("new.tags")} || ' ' || ${coal("new.search_summary")}, new.search_summary, new.tags, new.created_at, new.updated_at);
|
|
7021
|
+
END
|
|
7022
|
+
`);
|
|
5558
7023
|
db.exec(`
|
|
5559
|
-
CREATE TRIGGER
|
|
5560
|
-
|
|
7024
|
+
CREATE TRIGGER user_memos_unified_update AFTER UPDATE ON user_memos BEGIN
|
|
7025
|
+
DELETE FROM unified_search_items WHERE entity_type = 'user_memo' AND entity_id = old.id;
|
|
7026
|
+
INSERT INTO unified_search_items(${columns})
|
|
7027
|
+
SELECT 'user_memo', new.id, new.scope, NULL, new.title, new.title || ' ' || new.l1_content || ' ' || ${coal("new.user_input")} || ' ' || ${jc("new.tags")} || ' ' || ${coal("new.search_summary")}, new.search_summary, new.tags, new.created_at, new.updated_at
|
|
7028
|
+
WHERE new.status = 'active' AND new.usage_policy != 'human_directed';
|
|
7029
|
+
END
|
|
7030
|
+
`);
|
|
7031
|
+
db.exec(`
|
|
7032
|
+
CREATE TRIGGER user_memos_unified_delete AFTER DELETE ON user_memos BEGIN
|
|
7033
|
+
DELETE FROM unified_search_items WHERE entity_type = 'user_memo' AND entity_id = old.id;
|
|
7034
|
+
END
|
|
7035
|
+
`);
|
|
7036
|
+
// user_plans (V24 形式: l1_content)
|
|
7037
|
+
db.exec(`
|
|
7038
|
+
CREATE TRIGGER user_plans_unified_insert AFTER INSERT ON user_plans
|
|
7039
|
+
WHEN new.status = 'active' AND new.usage_policy != 'human_directed' BEGIN
|
|
5561
7040
|
INSERT OR REPLACE INTO unified_search_items(${columns})
|
|
5562
|
-
VALUES ('
|
|
7041
|
+
VALUES ('user_plan', new.id, new.scope, NULL, new.title, new.title || ' ' || new.l1_content || ' ' || ${jc("new.tags")} || ' ' || ${coal("new.search_summary")}, new.search_summary, new.tags, new.created_at, new.updated_at);
|
|
5563
7042
|
END
|
|
5564
7043
|
`);
|
|
5565
7044
|
db.exec(`
|
|
5566
|
-
CREATE TRIGGER
|
|
5567
|
-
DELETE FROM unified_search_items WHERE entity_type = '
|
|
7045
|
+
CREATE TRIGGER user_plans_unified_update AFTER UPDATE ON user_plans BEGIN
|
|
7046
|
+
DELETE FROM unified_search_items WHERE entity_type = 'user_plan' AND entity_id = old.id;
|
|
5568
7047
|
INSERT INTO unified_search_items(${columns})
|
|
5569
|
-
SELECT '
|
|
5570
|
-
WHERE new.status = 'active';
|
|
7048
|
+
SELECT 'user_plan', new.id, new.scope, NULL, new.title, new.title || ' ' || new.l1_content || ' ' || ${jc("new.tags")} || ' ' || ${coal("new.search_summary")}, new.search_summary, new.tags, new.created_at, new.updated_at
|
|
7049
|
+
WHERE new.status = 'active' AND new.usage_policy != 'human_directed';
|
|
5571
7050
|
END
|
|
5572
7051
|
`);
|
|
5573
7052
|
db.exec(`
|
|
5574
|
-
CREATE TRIGGER
|
|
5575
|
-
DELETE FROM unified_search_items WHERE entity_type = '
|
|
7053
|
+
CREATE TRIGGER user_plans_unified_delete AFTER DELETE ON user_plans BEGIN
|
|
7054
|
+
DELETE FROM unified_search_items WHERE entity_type = 'user_plan' AND entity_id = old.id;
|
|
5576
7055
|
END
|
|
5577
7056
|
`);
|
|
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 対応)
|
|
7057
|
+
// user_issues (V24 形式: l1_content, entries)
|
|
5592
7058
|
db.exec(`
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
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);
|
|
7059
|
+
CREATE TRIGGER user_issues_unified_insert AFTER INSERT ON user_issues WHEN new.status = 'open' BEGIN
|
|
7060
|
+
INSERT OR REPLACE INTO unified_search_items(${columns})
|
|
7061
|
+
VALUES ('user_issue', new.id, new.scope, NULL, new.title, new.title || ' ' || new.l1_content || ' ' || ${jc("new.entries")} || ' ' || ${jc("new.tags")} || ' ' || ${coal("new.search_summary")}, new.search_summary, new.tags, new.created_at, new.updated_at);
|
|
7062
|
+
END
|
|
5627
7063
|
`);
|
|
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
7064
|
db.exec(`
|
|
5637
|
-
CREATE TRIGGER
|
|
5638
|
-
|
|
7065
|
+
CREATE TRIGGER user_issues_unified_update AFTER UPDATE ON user_issues BEGIN
|
|
7066
|
+
DELETE FROM unified_search_items WHERE entity_type = 'user_issue' AND entity_id = old.id;
|
|
7067
|
+
INSERT INTO unified_search_items(${columns})
|
|
7068
|
+
SELECT 'user_issue', new.id, new.scope, NULL, new.title, new.title || ' ' || new.l1_content || ' ' || ${jc("new.entries")} || ' ' || ${jc("new.tags")} || ' ' || ${coal("new.search_summary")}, new.search_summary, new.tags, new.created_at, new.updated_at
|
|
7069
|
+
WHERE new.status = 'open';
|
|
7070
|
+
END
|
|
7071
|
+
`);
|
|
7072
|
+
db.exec(`
|
|
7073
|
+
CREATE TRIGGER user_issues_unified_delete AFTER DELETE ON user_issues BEGIN
|
|
7074
|
+
DELETE FROM unified_search_items WHERE entity_type = 'user_issue' AND entity_id = old.id;
|
|
7075
|
+
END
|
|
7076
|
+
`);
|
|
7077
|
+
// user_topics
|
|
7078
|
+
db.exec(`
|
|
7079
|
+
CREATE TRIGGER user_topics_unified_insert AFTER INSERT ON user_topics WHEN new.status = 'active' BEGIN
|
|
5639
7080
|
INSERT OR REPLACE INTO unified_search_items(${columns})
|
|
5640
|
-
VALUES ('
|
|
7081
|
+
VALUES ('user_topic', new.id, new.scope, NULL, new.title, new.title || ' ' || new.summary || ' ' || ${jc("new.tags")} || ' ' || ${coal("new.search_summary")}, new.search_summary, new.tags, new.created_at, new.updated_at);
|
|
5641
7082
|
END
|
|
5642
7083
|
`);
|
|
5643
7084
|
db.exec(`
|
|
5644
|
-
CREATE TRIGGER
|
|
5645
|
-
DELETE FROM unified_search_items WHERE entity_type = '
|
|
7085
|
+
CREATE TRIGGER user_topics_unified_update AFTER UPDATE ON user_topics BEGIN
|
|
7086
|
+
DELETE FROM unified_search_items WHERE entity_type = 'user_topic' AND entity_id = old.id;
|
|
5646
7087
|
INSERT INTO unified_search_items(${columns})
|
|
5647
|
-
SELECT '
|
|
7088
|
+
SELECT 'user_topic', new.id, new.scope, NULL, new.title, new.title || ' ' || new.summary || ' ' || ${jc("new.tags")} || ' ' || ${coal("new.search_summary")}, new.search_summary, new.tags, new.created_at, new.updated_at
|
|
5648
7089
|
WHERE new.status = 'active';
|
|
5649
7090
|
END
|
|
5650
7091
|
`);
|
|
5651
7092
|
db.exec(`
|
|
5652
|
-
CREATE TRIGGER
|
|
5653
|
-
DELETE FROM unified_search_items WHERE entity_type = '
|
|
7093
|
+
CREATE TRIGGER user_topics_unified_delete AFTER DELETE ON user_topics BEGIN
|
|
7094
|
+
DELETE FROM unified_search_items WHERE entity_type = 'user_topic' AND entity_id = old.id;
|
|
5654
7095
|
END
|
|
5655
7096
|
`);
|
|
7097
|
+
db.exec(`INSERT INTO schema_version (version) VALUES (29)`);
|
|
7098
|
+
}
|
|
7099
|
+
/**
|
|
7100
|
+
* V30: client_id → device_id リネーム
|
|
7101
|
+
* audit_log, user_topics の client_id カラムを device_id に変更。
|
|
7102
|
+
* store_meta のキーも client_id → device_id に変更。
|
|
7103
|
+
* 既存DBで V29 が旧コードで適用済みの場合にカラムをリネームする。
|
|
7104
|
+
*/
|
|
7105
|
+
function applyV30(db) {
|
|
7106
|
+
// store_meta の key リネーム(V29 で既に実施済みの場合は no-op)
|
|
7107
|
+
db.exec(`UPDATE store_meta SET key = 'device_id' WHERE key = 'client_id'`);
|
|
7108
|
+
// audit_log: client_id → device_id(テーブル再作成)
|
|
7109
|
+
if (hasColumn(db, "audit_log", "client_id")) {
|
|
7110
|
+
db.exec(`
|
|
7111
|
+
CREATE TABLE audit_log_v30 (
|
|
7112
|
+
id TEXT PRIMARY KEY,
|
|
7113
|
+
operation TEXT NOT NULL CHECK(operation IN (
|
|
7114
|
+
'create','update','retract','supersede','reverse','obsolete','archive','promote'
|
|
7115
|
+
)),
|
|
7116
|
+
entity_type TEXT NOT NULL CHECK(entity_type IN ('claim','decision','episode','theory','insight','model','user_memo','user_plan','user_issue','user_topic')),
|
|
7117
|
+
entity_id TEXT NOT NULL,
|
|
7118
|
+
summary TEXT NOT NULL,
|
|
7119
|
+
details TEXT,
|
|
7120
|
+
client_name TEXT,
|
|
7121
|
+
client_version TEXT,
|
|
7122
|
+
session_id TEXT,
|
|
7123
|
+
source_tool TEXT,
|
|
7124
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
7125
|
+
device_id TEXT
|
|
7126
|
+
)
|
|
7127
|
+
`);
|
|
7128
|
+
db.exec(`
|
|
7129
|
+
INSERT INTO audit_log_v30 (id, operation, entity_type, entity_id, summary, details, client_name, client_version, session_id, source_tool, created_at, device_id)
|
|
7130
|
+
SELECT id, operation, entity_type, entity_id, summary, details, client_name, client_version, session_id, source_tool, created_at, client_id
|
|
7131
|
+
FROM audit_log
|
|
7132
|
+
`);
|
|
7133
|
+
db.exec(`DROP TABLE audit_log`);
|
|
7134
|
+
db.exec(`ALTER TABLE audit_log_v30 RENAME TO audit_log`);
|
|
7135
|
+
}
|
|
7136
|
+
// user_topics: client_id → device_id(テーブル再作成)
|
|
7137
|
+
// client_id がある場合はリネーム、device_id が欠落している場合は追加
|
|
7138
|
+
const hasUserTopics = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='user_topics'").get();
|
|
7139
|
+
if (hasUserTopics && hasColumn(db, "user_topics", "client_id")) {
|
|
7140
|
+
// FTSトリガーを削除(DROP TABLE前)
|
|
7141
|
+
db.exec(`DROP TRIGGER IF EXISTS user_topics_fts_insert`);
|
|
7142
|
+
db.exec(`DROP TRIGGER IF EXISTS user_topics_fts_update`);
|
|
7143
|
+
db.exec(`DROP TRIGGER IF EXISTS user_topics_fts_delete`);
|
|
7144
|
+
// unified トリガーも削除
|
|
7145
|
+
db.exec(`DROP TRIGGER IF EXISTS user_topics_unified_insert`);
|
|
7146
|
+
db.exec(`DROP TRIGGER IF EXISTS user_topics_unified_update`);
|
|
7147
|
+
db.exec(`DROP TRIGGER IF EXISTS user_topics_unified_delete`);
|
|
7148
|
+
db.exec(`
|
|
7149
|
+
CREATE TABLE user_topics_v30 (
|
|
7150
|
+
id TEXT PRIMARY KEY,
|
|
7151
|
+
title TEXT NOT NULL,
|
|
7152
|
+
summary TEXT NOT NULL,
|
|
7153
|
+
device_id TEXT,
|
|
7154
|
+
cwd TEXT,
|
|
7155
|
+
conversation_id TEXT,
|
|
7156
|
+
priority TEXT NOT NULL DEFAULT 'medium'
|
|
7157
|
+
CHECK(priority IN ('low','medium','high','critical')),
|
|
7158
|
+
tags TEXT NOT NULL DEFAULT '[]',
|
|
7159
|
+
scope TEXT NOT NULL DEFAULT 'global',
|
|
7160
|
+
search_summary TEXT,
|
|
7161
|
+
status TEXT NOT NULL DEFAULT 'active'
|
|
7162
|
+
CHECK(status IN ('active','closed','archived')),
|
|
7163
|
+
l1_embedding BLOB,
|
|
7164
|
+
client_name TEXT,
|
|
7165
|
+
client_version TEXT,
|
|
7166
|
+
source_tool TEXT,
|
|
7167
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
7168
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
7169
|
+
)
|
|
7170
|
+
`);
|
|
7171
|
+
db.exec(`
|
|
7172
|
+
INSERT INTO user_topics_v30 (id, title, summary, device_id, cwd, conversation_id, priority, tags, scope, search_summary, status, l1_embedding, client_name, client_version, source_tool, created_at, updated_at)
|
|
7173
|
+
SELECT id, title, summary, client_id, cwd, conversation_id, priority, tags, scope, search_summary, status, l1_embedding, client_name, client_version, source_tool, created_at, updated_at
|
|
7174
|
+
FROM user_topics
|
|
7175
|
+
`);
|
|
7176
|
+
db.exec(`DROP TABLE user_topics`);
|
|
7177
|
+
db.exec(`ALTER TABLE user_topics_v30 RENAME TO user_topics`);
|
|
7178
|
+
// インデックス再作成
|
|
7179
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_user_topics_status ON user_topics(status)`);
|
|
7180
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_user_topics_scope ON user_topics(scope)`);
|
|
7181
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_user_topics_priority ON user_topics(priority)`);
|
|
7182
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_user_topics_updated ON user_topics(updated_at)`);
|
|
7183
|
+
// FTS再構築
|
|
7184
|
+
db.exec(`DROP TABLE IF EXISTS user_topics_fts`);
|
|
7185
|
+
db.exec(`
|
|
7186
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS user_topics_fts USING fts5(
|
|
7187
|
+
title, summary, tags, search_summary,
|
|
7188
|
+
content='user_topics', content_rowid='rowid', tokenize='trigram'
|
|
7189
|
+
)
|
|
7190
|
+
`);
|
|
7191
|
+
const jc = (col) => `REPLACE(REPLACE(${col}, '[', ''), ']', '')`;
|
|
7192
|
+
db.exec(`
|
|
7193
|
+
CREATE TRIGGER user_topics_fts_insert AFTER INSERT ON user_topics BEGIN
|
|
7194
|
+
INSERT INTO user_topics_fts(rowid, title, summary, tags, search_summary)
|
|
7195
|
+
VALUES (new.rowid, new.title, new.summary, ${jc("new.tags")}, new.search_summary);
|
|
7196
|
+
END
|
|
7197
|
+
`);
|
|
7198
|
+
db.exec(`
|
|
7199
|
+
CREATE TRIGGER user_topics_fts_update AFTER UPDATE ON user_topics BEGIN
|
|
7200
|
+
INSERT INTO user_topics_fts(user_topics_fts, rowid, title, summary, tags, search_summary)
|
|
7201
|
+
VALUES ('delete', old.rowid, old.title, old.summary, ${jc("old.tags")}, old.search_summary);
|
|
7202
|
+
INSERT INTO user_topics_fts(rowid, title, summary, tags, search_summary)
|
|
7203
|
+
VALUES (new.rowid, new.title, new.summary, ${jc("new.tags")}, new.search_summary);
|
|
7204
|
+
END
|
|
7205
|
+
`);
|
|
7206
|
+
db.exec(`
|
|
7207
|
+
CREATE TRIGGER user_topics_fts_delete AFTER DELETE ON user_topics BEGIN
|
|
7208
|
+
INSERT INTO user_topics_fts(user_topics_fts, rowid, title, summary, tags, search_summary)
|
|
7209
|
+
VALUES ('delete', old.rowid, old.title, old.summary, ${jc("old.tags")}, old.search_summary);
|
|
7210
|
+
END
|
|
7211
|
+
`);
|
|
7212
|
+
db.exec(`INSERT INTO user_topics_fts(user_topics_fts) VALUES('rebuild')`);
|
|
7213
|
+
// unified トリガー再作成
|
|
7214
|
+
const coal = (col) => `COALESCE(${col}, '')`;
|
|
7215
|
+
const columns = "entity_type, entity_id, scope, category, title_summary, search_text, search_summary, tags, created_at, updated_at";
|
|
7216
|
+
db.exec(`
|
|
7217
|
+
CREATE TRIGGER user_topics_unified_insert AFTER INSERT ON user_topics WHEN new.status = 'active' BEGIN
|
|
7218
|
+
INSERT OR REPLACE INTO unified_search_items(${columns})
|
|
7219
|
+
VALUES ('user_topic', new.id, new.scope, NULL, new.title, new.title || ' ' || ${coal("new.summary")} || ' ' || ${coal("new.search_summary")}, new.search_summary, new.tags, new.created_at, new.updated_at);
|
|
7220
|
+
END
|
|
7221
|
+
`);
|
|
7222
|
+
db.exec(`
|
|
7223
|
+
CREATE TRIGGER user_topics_unified_update AFTER UPDATE ON user_topics BEGIN
|
|
7224
|
+
DELETE FROM unified_search_items WHERE entity_type = 'user_topic' AND entity_id = old.id;
|
|
7225
|
+
INSERT INTO unified_search_items(${columns})
|
|
7226
|
+
SELECT 'user_topic', new.id, new.scope, NULL, new.title, new.title || ' ' || ${coal("new.summary")} || ' ' || ${coal("new.search_summary")}, new.search_summary, new.tags, new.created_at, new.updated_at WHERE new.status = 'active';
|
|
7227
|
+
END
|
|
7228
|
+
`);
|
|
7229
|
+
db.exec(`
|
|
7230
|
+
CREATE TRIGGER user_topics_unified_delete AFTER DELETE ON user_topics BEGIN
|
|
7231
|
+
DELETE FROM unified_search_items WHERE entity_type = 'user_topic' AND entity_id = old.id;
|
|
7232
|
+
END
|
|
7233
|
+
`);
|
|
7234
|
+
}
|
|
7235
|
+
else if (hasUserTopics && !hasColumn(db, "user_topics", "device_id")) {
|
|
7236
|
+
// device_id カラムが欠落している場合(中間バージョンのV29で作成されたDB)
|
|
7237
|
+
db.exec(`ALTER TABLE user_topics ADD COLUMN device_id TEXT`);
|
|
7238
|
+
const deviceIdRow = db.prepare("SELECT value FROM store_meta WHERE key = 'device_id'").get();
|
|
7239
|
+
if (deviceIdRow) {
|
|
7240
|
+
db.prepare("UPDATE user_topics SET device_id = ? WHERE device_id IS NULL").run(deviceIdRow.value);
|
|
7241
|
+
}
|
|
7242
|
+
}
|
|
7243
|
+
db.exec(`INSERT INTO schema_version (version) VALUES (30)`);
|
|
7244
|
+
}
|
|
7245
|
+
// ============================================================
|
|
7246
|
+
// V31: user_files テーブル新設(File Vault機能)
|
|
7247
|
+
// ============================================================
|
|
7248
|
+
function applyV31(db) {
|
|
7249
|
+
const transaction = db.transaction(() => {
|
|
7250
|
+
// user_files テーブル
|
|
7251
|
+
db.exec(`
|
|
7252
|
+
CREATE TABLE IF NOT EXISTS user_files (
|
|
7253
|
+
id TEXT PRIMARY KEY,
|
|
7254
|
+
title TEXT NOT NULL,
|
|
7255
|
+
description TEXT,
|
|
7256
|
+
original_filename TEXT NOT NULL,
|
|
7257
|
+
original_encoding TEXT NOT NULL DEFAULT 'utf-8',
|
|
7258
|
+
file_data TEXT NOT NULL,
|
|
7259
|
+
file_hash TEXT NOT NULL,
|
|
7260
|
+
file_size INTEGER NOT NULL,
|
|
7261
|
+
tags TEXT NOT NULL DEFAULT '[]',
|
|
7262
|
+
scope TEXT NOT NULL DEFAULT 'global',
|
|
7263
|
+
search_summary TEXT,
|
|
7264
|
+
status TEXT NOT NULL DEFAULT 'active'
|
|
7265
|
+
CHECK(status IN ('active','archived')),
|
|
7266
|
+
l1_embedding BLOB,
|
|
7267
|
+
client_name TEXT,
|
|
7268
|
+
client_version TEXT,
|
|
7269
|
+
source_tool TEXT,
|
|
7270
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
7271
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
7272
|
+
)
|
|
7273
|
+
`);
|
|
7274
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_user_files_status ON user_files(status)`);
|
|
7275
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_user_files_scope ON user_files(scope)`);
|
|
7276
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_user_files_title ON user_files(title)`);
|
|
7277
|
+
// audit_log: entity_type CHECK に 'user_file' 追加(テーブル再作成)
|
|
7278
|
+
// 一時テーブル名は audit_log_new に統一(残骸掃除パターンと一致させる)
|
|
7279
|
+
db.exec(`DROP TABLE IF EXISTS audit_log_new`);
|
|
7280
|
+
db.exec(`
|
|
7281
|
+
CREATE TABLE audit_log_new (
|
|
7282
|
+
id TEXT PRIMARY KEY,
|
|
7283
|
+
operation TEXT NOT NULL CHECK(operation IN (
|
|
7284
|
+
'create','update','retract','supersede','reverse','obsolete','archive','promote'
|
|
7285
|
+
)),
|
|
7286
|
+
entity_type TEXT NOT NULL CHECK(entity_type IN ('claim','decision','episode','theory','insight','model','user_memo','user_plan','user_issue','user_topic','user_file')),
|
|
7287
|
+
entity_id TEXT NOT NULL,
|
|
7288
|
+
summary TEXT NOT NULL,
|
|
7289
|
+
details TEXT,
|
|
7290
|
+
client_name TEXT,
|
|
7291
|
+
client_version TEXT,
|
|
7292
|
+
session_id TEXT,
|
|
7293
|
+
source_tool TEXT,
|
|
7294
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
7295
|
+
device_id TEXT
|
|
7296
|
+
)
|
|
7297
|
+
`);
|
|
7298
|
+
db.exec(`
|
|
7299
|
+
INSERT INTO audit_log_new (id, operation, entity_type, entity_id, summary, details, client_name, client_version, session_id, source_tool, created_at, device_id)
|
|
7300
|
+
SELECT id, operation, entity_type, entity_id, summary, details, client_name, client_version, session_id, source_tool, created_at, device_id
|
|
7301
|
+
FROM audit_log
|
|
7302
|
+
`);
|
|
7303
|
+
db.exec(`DROP TABLE audit_log`);
|
|
7304
|
+
db.exec(`ALTER TABLE audit_log_new RENAME TO audit_log`);
|
|
7305
|
+
// audit_log インデックス再作成(4本すべて)
|
|
7306
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_audit_log_entity ON audit_log(entity_type, entity_id)`);
|
|
7307
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_audit_log_operation ON audit_log(operation)`);
|
|
7308
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_audit_log_created ON audit_log(created_at)`);
|
|
7309
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_audit_log_session ON audit_log(session_id)`);
|
|
7310
|
+
// unified search統合: CHECK制約更新 + 全トリガー再作成 + user_filesトリガー追加
|
|
7311
|
+
// 全トリガーを削除
|
|
7312
|
+
const triggerTables = ["claims", "episodes", "decisions", "theories", "insights", "models", "user_memos", "user_plans", "user_issues", "user_topics"];
|
|
7313
|
+
for (const t of triggerTables) {
|
|
7314
|
+
db.exec(`DROP TRIGGER IF EXISTS ${t}_unified_insert`);
|
|
7315
|
+
db.exec(`DROP TRIGGER IF EXISTS ${t}_unified_update`);
|
|
7316
|
+
db.exec(`DROP TRIGGER IF EXISTS ${t}_unified_delete`);
|
|
7317
|
+
}
|
|
7318
|
+
// FTS5 削除(content テーブル参照のため先に)
|
|
7319
|
+
db.exec(`DROP TABLE IF EXISTS unified_search_fts`);
|
|
7320
|
+
// unified_search_items 再作成(CHECK に 'user_file' 追加)
|
|
7321
|
+
db.exec(`DROP TABLE IF EXISTS unified_search_items_new`);
|
|
7322
|
+
db.exec(`
|
|
7323
|
+
CREATE TABLE unified_search_items_new (
|
|
7324
|
+
entity_type TEXT NOT NULL CHECK(entity_type IN ('claim','episode','decision','theory','insight','model','user_memo','user_plan','user_issue','user_topic','user_file')),
|
|
7325
|
+
entity_id TEXT NOT NULL,
|
|
7326
|
+
scope TEXT NOT NULL DEFAULT 'global',
|
|
7327
|
+
category TEXT,
|
|
7328
|
+
title_summary TEXT NOT NULL,
|
|
7329
|
+
search_text TEXT NOT NULL,
|
|
7330
|
+
search_summary TEXT,
|
|
7331
|
+
tags TEXT NOT NULL DEFAULT '[]',
|
|
7332
|
+
l1_embedding BLOB,
|
|
7333
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
7334
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
7335
|
+
PRIMARY KEY (entity_type, entity_id)
|
|
7336
|
+
)
|
|
7337
|
+
`);
|
|
7338
|
+
db.exec(`INSERT INTO unified_search_items_new SELECT * FROM unified_search_items`);
|
|
7339
|
+
db.exec(`DROP TABLE unified_search_items`);
|
|
7340
|
+
db.exec(`ALTER TABLE unified_search_items_new RENAME TO unified_search_items`);
|
|
7341
|
+
// インデックス再作成
|
|
7342
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_unified_search_scope ON unified_search_items(scope)`);
|
|
7343
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_unified_search_entity_type ON unified_search_items(entity_type)`);
|
|
7344
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_unified_search_updated ON unified_search_items(updated_at)`);
|
|
7345
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_unified_search_entity_id ON unified_search_items(entity_id)`);
|
|
7346
|
+
// FTS5 再構築
|
|
7347
|
+
db.exec(`
|
|
7348
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS unified_search_fts USING fts5(
|
|
7349
|
+
search_text, title_summary, search_summary,
|
|
7350
|
+
content='unified_search_items', content_rowid='rowid', tokenize='trigram'
|
|
7351
|
+
)
|
|
7352
|
+
`);
|
|
7353
|
+
db.exec(`INSERT INTO unified_search_fts(unified_search_fts) VALUES('rebuild')`);
|
|
7354
|
+
// 全トリガー再作成
|
|
7355
|
+
const jc2 = (col) => `replace(replace(replace(${col}, '["',''), '"]',''), '","',' ')`;
|
|
7356
|
+
const jcT = (col) => `REPLACE(REPLACE(${col}, '[', ''), ']', '')`;
|
|
7357
|
+
const coal2 = (col) => `COALESCE(${col}, '')`;
|
|
7358
|
+
const cols = "entity_type, entity_id, scope, category, title_summary, search_text, search_summary, tags, created_at, updated_at";
|
|
7359
|
+
// claims
|
|
7360
|
+
const ct = (p) => `${p}l2_subject || ' ' || ${p}l2_predicate || ' ' || ${p}l2_object`;
|
|
7361
|
+
const cs = (p) => `${p}l2_subject || ' ' || ${p}l2_predicate || ' ' || ${p}l2_object || ' ' || ${coal2(`${p}l2_evidence`)} || ' ' || ${coal2(`${p}l2_falsifier`)} || ' ' || ${coal2(`${p}l1_content`)} || ' ' || ${coal2(`${p}search_summary`)}`;
|
|
7362
|
+
db.exec(`CREATE TRIGGER claims_unified_insert AFTER INSERT ON claims WHEN new.status = 'active' BEGIN INSERT OR REPLACE INTO unified_search_items(${cols}) VALUES ('claim', new.id, new.scope, new.category, ${ct("new.")}, ${cs("new.")}, new.search_summary, '[]', new.created_at, new.updated_at); END`);
|
|
7363
|
+
db.exec(`CREATE TRIGGER claims_unified_update AFTER UPDATE ON claims BEGIN DELETE FROM unified_search_items WHERE entity_type = 'claim' AND entity_id = old.id; INSERT INTO unified_search_items(${cols}) SELECT 'claim', new.id, new.scope, new.category, ${ct("new.")}, ${cs("new.")}, new.search_summary, '[]', new.created_at, new.updated_at WHERE new.status = 'active'; END`);
|
|
7364
|
+
db.exec(`CREATE TRIGGER claims_unified_delete AFTER DELETE ON claims BEGIN DELETE FROM unified_search_items WHERE entity_type = 'claim' AND entity_id = old.id; END`);
|
|
7365
|
+
// episodes
|
|
7366
|
+
const es = (p) => `${p}title || ' ' || ${coal2(`${p}l1_content`)} || ' ' || ${coal2(`${p}l2_context`)} || ' ' || ${coal2(`${p}l2_trigger`)} || ' ' || ${jc2(`${p}l2_problems`)} || ' ' || ${jc2(`${p}l2_outcomes`)} || ' ' || ${jc2(`${p}l2_principles`)} || ' ' || ${jc2(`${p}tags`)} || ' ' || ${coal2(`${p}search_summary`)}`;
|
|
7367
|
+
db.exec(`CREATE TRIGGER episodes_unified_insert AFTER INSERT ON episodes WHEN new.status = 'active' BEGIN INSERT OR REPLACE INTO unified_search_items(${cols}) VALUES ('episode', new.id, new.scope, NULL, new.title, ${es("new.")}, new.search_summary, new.tags, new.created_at, new.updated_at); END`);
|
|
7368
|
+
db.exec(`CREATE TRIGGER episodes_unified_update AFTER UPDATE ON episodes BEGIN DELETE FROM unified_search_items WHERE entity_type = 'episode' AND entity_id = old.id; INSERT INTO unified_search_items(${cols}) SELECT 'episode', new.id, new.scope, NULL, new.title, ${es("new.")}, new.search_summary, new.tags, new.created_at, new.updated_at WHERE new.status = 'active'; END`);
|
|
7369
|
+
db.exec(`CREATE TRIGGER episodes_unified_delete AFTER DELETE ON episodes BEGIN DELETE FROM unified_search_items WHERE entity_type = 'episode' AND entity_id = old.id; END`);
|
|
7370
|
+
// decisions
|
|
7371
|
+
const ds = (p) => `${p}title || ' ' || ${p}description || ' ' || ${coal2(`${p}l1_content`)} || ' ' || ${p}l2_reasoning || ' ' || ${jc2(`${p}l2_alternatives`)} || ' ' || ${coal2(`${p}search_summary`)}`;
|
|
7372
|
+
db.exec(`CREATE TRIGGER decisions_unified_insert AFTER INSERT ON decisions WHEN new.status = 'active' BEGIN INSERT OR REPLACE INTO unified_search_items(${cols}) VALUES ('decision', new.id, new.scope, NULL, new.title, ${ds("new.")}, new.search_summary, '[]', new.created_at, new.updated_at); END`);
|
|
7373
|
+
db.exec(`CREATE TRIGGER decisions_unified_update AFTER UPDATE ON decisions BEGIN DELETE FROM unified_search_items WHERE entity_type = 'decision' AND entity_id = old.id; INSERT INTO unified_search_items(${cols}) SELECT 'decision', new.id, new.scope, NULL, new.title, ${ds("new.")}, new.search_summary, '[]', new.created_at, new.updated_at WHERE new.status = 'active'; END`);
|
|
7374
|
+
db.exec(`CREATE TRIGGER decisions_unified_delete AFTER DELETE ON decisions BEGIN DELETE FROM unified_search_items WHERE entity_type = 'decision' AND entity_id = old.id; END`);
|
|
7375
|
+
// theories, insights, models
|
|
7376
|
+
for (const e of [{ type: "theory", table: "theories" }, { type: "insight", table: "insights" }, { type: "model", table: "models" }]) {
|
|
7377
|
+
const s = (p) => `${p}title || ' ' || ${coal2(`${p}description`)} || ' ' || ${coal2(`${p}l1_content`)} || ' ' || ${coal2(`${p}l2_core_thesis`)} || ' ' || ${jc2(`${p}l2_principles`)} || ' ' || ${jc2(`${p}evidence_refs`)} || ' ' || ${jc2(`${p}tags`)} || ' ' || ${coal2(`${p}search_summary`)}`;
|
|
7378
|
+
db.exec(`CREATE TRIGGER ${e.table}_unified_insert AFTER INSERT ON ${e.table} WHEN new.status = 'active' BEGIN INSERT OR REPLACE INTO unified_search_items(${cols}) VALUES ('${e.type}', new.id, new.scope, NULL, new.title, ${s("new.")}, new.search_summary, new.tags, new.created_at, new.updated_at); END`);
|
|
7379
|
+
db.exec(`CREATE TRIGGER ${e.table}_unified_update AFTER UPDATE ON ${e.table} BEGIN DELETE FROM unified_search_items WHERE entity_type = '${e.type}' AND entity_id = old.id; INSERT INTO unified_search_items(${cols}) SELECT '${e.type}', new.id, new.scope, NULL, new.title, ${s("new.")}, new.search_summary, new.tags, new.created_at, new.updated_at WHERE new.status = 'active'; END`);
|
|
7380
|
+
db.exec(`CREATE TRIGGER ${e.table}_unified_delete AFTER DELETE ON ${e.table} BEGIN DELETE FROM unified_search_items WHERE entity_type = '${e.type}' AND entity_id = old.id; END`);
|
|
7381
|
+
}
|
|
7382
|
+
// user_memos
|
|
7383
|
+
const ms = (p) => `${p}title || ' ' || ${p}l1_content || ' ' || ${coal2(`${p}user_input`)} || ' ' || ${jc2(`${p}tags`)} || ' ' || ${coal2(`${p}search_summary`)}`;
|
|
7384
|
+
db.exec(`CREATE TRIGGER user_memos_unified_insert AFTER INSERT ON user_memos WHEN new.status = 'active' AND new.usage_policy != 'human_directed' BEGIN INSERT OR REPLACE INTO unified_search_items(${cols}) VALUES ('user_memo', new.id, new.scope, NULL, new.title, ${ms("new.")}, new.search_summary, new.tags, new.created_at, new.updated_at); END`);
|
|
7385
|
+
db.exec(`CREATE TRIGGER user_memos_unified_update AFTER UPDATE ON user_memos BEGIN DELETE FROM unified_search_items WHERE entity_type = 'user_memo' AND entity_id = old.id; INSERT INTO unified_search_items(${cols}) SELECT 'user_memo', new.id, new.scope, NULL, new.title, ${ms("new.")}, new.search_summary, new.tags, new.created_at, new.updated_at WHERE new.status = 'active' AND new.usage_policy != 'human_directed'; END`);
|
|
7386
|
+
db.exec(`CREATE TRIGGER user_memos_unified_delete AFTER DELETE ON user_memos BEGIN DELETE FROM unified_search_items WHERE entity_type = 'user_memo' AND entity_id = old.id; END`);
|
|
7387
|
+
// user_plans
|
|
7388
|
+
const ps = (p) => `${p}title || ' ' || ${p}l1_content || ' ' || ${jc2(`${p}tags`)} || ' ' || ${coal2(`${p}search_summary`)}`;
|
|
7389
|
+
db.exec(`CREATE TRIGGER user_plans_unified_insert AFTER INSERT ON user_plans WHEN new.status = 'active' BEGIN INSERT OR REPLACE INTO unified_search_items(${cols}) VALUES ('user_plan', new.id, new.scope, NULL, new.title, ${ps("new.")}, new.search_summary, new.tags, new.created_at, new.updated_at); END`);
|
|
7390
|
+
db.exec(`CREATE TRIGGER user_plans_unified_update AFTER UPDATE ON user_plans BEGIN DELETE FROM unified_search_items WHERE entity_type = 'user_plan' AND entity_id = old.id; INSERT INTO unified_search_items(${cols}) SELECT 'user_plan', new.id, new.scope, NULL, new.title, ${ps("new.")}, new.search_summary, new.tags, new.created_at, new.updated_at WHERE new.status = 'active'; END`);
|
|
7391
|
+
db.exec(`CREATE TRIGGER user_plans_unified_delete AFTER DELETE ON user_plans BEGIN DELETE FROM unified_search_items WHERE entity_type = 'user_plan' AND entity_id = old.id; END`);
|
|
7392
|
+
// user_issues
|
|
7393
|
+
const is2 = (p) => `${p}title || ' ' || ${p}l1_content || ' ' || ${jc2(`${p}entries`)} || ' ' || ${jc2(`${p}tags`)} || ' ' || ${coal2(`${p}search_summary`)}`;
|
|
7394
|
+
db.exec(`CREATE TRIGGER user_issues_unified_insert AFTER INSERT ON user_issues WHEN new.status = 'open' BEGIN INSERT OR REPLACE INTO unified_search_items(${cols}) VALUES ('user_issue', new.id, new.scope, NULL, new.title, ${is2("new.")}, new.search_summary, new.tags, new.created_at, new.updated_at); END`);
|
|
7395
|
+
db.exec(`CREATE TRIGGER user_issues_unified_update AFTER UPDATE ON user_issues BEGIN DELETE FROM unified_search_items WHERE entity_type = 'user_issue' AND entity_id = old.id; INSERT INTO unified_search_items(${cols}) SELECT 'user_issue', new.id, new.scope, NULL, new.title, ${is2("new.")}, new.search_summary, new.tags, new.created_at, new.updated_at WHERE new.status = 'open'; END`);
|
|
7396
|
+
db.exec(`CREATE TRIGGER user_issues_unified_delete AFTER DELETE ON user_issues BEGIN DELETE FROM unified_search_items WHERE entity_type = 'user_issue' AND entity_id = old.id; END`);
|
|
7397
|
+
// user_topics
|
|
7398
|
+
db.exec(`CREATE TRIGGER user_topics_unified_insert AFTER INSERT ON user_topics WHEN new.status = 'active' BEGIN INSERT OR REPLACE INTO unified_search_items(${cols}) VALUES ('user_topic', new.id, new.scope, NULL, new.title, new.title || ' ' || new.summary || ' ' || ${jcT("new.tags")} || ' ' || ${coal2("new.search_summary")}, new.search_summary, new.tags, new.created_at, new.updated_at); END`);
|
|
7399
|
+
db.exec(`CREATE TRIGGER user_topics_unified_update AFTER UPDATE ON user_topics BEGIN DELETE FROM unified_search_items WHERE entity_type = 'user_topic' AND entity_id = old.id; INSERT INTO unified_search_items(${cols}) SELECT 'user_topic', new.id, new.scope, NULL, new.title, new.title || ' ' || new.summary || ' ' || ${jcT("new.tags")} || ' ' || ${coal2("new.search_summary")}, new.search_summary, new.tags, new.created_at, new.updated_at WHERE new.status = 'active'; END`);
|
|
7400
|
+
db.exec(`CREATE TRIGGER user_topics_unified_delete AFTER DELETE ON user_topics BEGIN DELETE FROM unified_search_items WHERE entity_type = 'user_topic' AND entity_id = old.id; END`);
|
|
7401
|
+
// user_files
|
|
7402
|
+
db.exec(`CREATE TRIGGER user_files_unified_insert AFTER INSERT ON user_files WHEN new.status = 'active' BEGIN INSERT OR REPLACE INTO unified_search_items(${cols}) VALUES ('user_file', new.id, new.scope, NULL, new.title, new.title || ' ' || COALESCE(new.description, '') || ' ' || new.original_filename || ' ' || ${jcT("new.tags")} || ' ' || ${coal2("new.search_summary")}, new.search_summary, new.tags, new.created_at, new.updated_at); END`);
|
|
7403
|
+
db.exec(`CREATE TRIGGER user_files_unified_update AFTER UPDATE ON user_files BEGIN DELETE FROM unified_search_items WHERE entity_type = 'user_file' AND entity_id = old.id; INSERT INTO unified_search_items(${cols}) SELECT 'user_file', new.id, new.scope, NULL, new.title, new.title || ' ' || COALESCE(new.description, '') || ' ' || new.original_filename || ' ' || ${jcT("new.tags")} || ' ' || ${coal2("new.search_summary")}, new.search_summary, new.tags, new.created_at, new.updated_at WHERE new.status = 'active'; END`);
|
|
7404
|
+
db.exec(`CREATE TRIGGER user_files_unified_delete AFTER DELETE ON user_files BEGIN DELETE FROM unified_search_items WHERE entity_type = 'user_file' AND entity_id = old.id; END`);
|
|
7405
|
+
db.exec(`INSERT INTO schema_version (version) VALUES (31)`);
|
|
7406
|
+
});
|
|
7407
|
+
transaction();
|
|
7408
|
+
}
|
|
7409
|
+
/**
|
|
7410
|
+
* V32: status カラム正規化(Issue #58)
|
|
7411
|
+
* - Knowledge系: status → validity_status + is_archived + promoted_to
|
|
7412
|
+
* - User系: status → xxx_status + is_archived
|
|
7413
|
+
* - 語彙統一: retracted/reversed → invalidated, superseded/obsolete → superseded
|
|
7414
|
+
*/
|
|
7415
|
+
function applyV32(db) {
|
|
7416
|
+
const transaction = db.transaction(() => {
|
|
7417
|
+
// Knowledge系テーブル: claims, episodes, decisions, theories, insights, models
|
|
7418
|
+
const knowledgeTables = [
|
|
7419
|
+
{ name: "claims", statusMap: `CASE WHEN status IN ('retracted') THEN 'invalidated' WHEN status IN ('superseded') THEN 'superseded' WHEN status IN ('promoted','archived') THEN 'active' ELSE 'active' END` },
|
|
7420
|
+
{ name: "episodes", statusMap: `CASE WHEN status IN ('promoted','archived') THEN 'active' ELSE 'active' END` },
|
|
7421
|
+
{ name: "decisions", statusMap: `CASE WHEN status IN ('reversed') THEN 'invalidated' WHEN status IN ('obsolete') THEN 'superseded' WHEN status IN ('promoted','archived') THEN 'active' ELSE 'active' END` },
|
|
7422
|
+
{ name: "theories", statusMap: `CASE WHEN status IN ('promoted','archived') THEN 'active' ELSE 'active' END` },
|
|
7423
|
+
{ name: "insights", statusMap: `CASE WHEN status IN ('promoted','archived') THEN 'active' ELSE 'active' END` },
|
|
7424
|
+
{ name: "models", statusMap: `CASE WHEN status IN ('promoted','archived') THEN 'active' ELSE 'active' END` },
|
|
7425
|
+
];
|
|
7426
|
+
// ★ 旧トリガー(new.status を参照)を先に DROP する
|
|
7427
|
+
// 理由: UPDATE でデータ変換する際に旧トリガーが発火すると、
|
|
7428
|
+
// NULL SPO の claims 等で unified_search_items.title_summary NOT NULL 制約に違反するため。
|
|
7429
|
+
const allTriggerTables = ["claims", "episodes", "decisions", "theories", "insights", "models",
|
|
7430
|
+
"user_memos", "user_plans", "user_issues", "user_topics", "user_files"];
|
|
7431
|
+
for (const tbl of allTriggerTables) {
|
|
7432
|
+
db.exec(`DROP TRIGGER IF EXISTS ${tbl}_unified_insert`);
|
|
7433
|
+
db.exec(`DROP TRIGGER IF EXISTS ${tbl}_unified_update`);
|
|
7434
|
+
db.exec(`DROP TRIGGER IF EXISTS ${tbl}_unified_delete`);
|
|
7435
|
+
}
|
|
7436
|
+
db.exec(`DROP TRIGGER IF EXISTS unified_search_fts_insert`);
|
|
7437
|
+
db.exec(`DROP TRIGGER IF EXISTS unified_search_fts_update`);
|
|
7438
|
+
db.exec(`DROP TRIGGER IF EXISTS unified_search_fts_delete`);
|
|
7439
|
+
for (const t of knowledgeTables) {
|
|
7440
|
+
// カラム一覧を取得(動的にコピー)
|
|
7441
|
+
const cols = db.prepare(`PRAGMA table_info('${t.name}')`).all().map(c => c.name);
|
|
7442
|
+
const hasStatus = cols.includes("status");
|
|
7443
|
+
if (!hasStatus)
|
|
7444
|
+
continue; // 既にマイグレーション済み
|
|
7445
|
+
// 新カラムを追加(冪等: 部分適用済み状態からのリカバリ対応)
|
|
7446
|
+
if (!hasColumn(db, t.name, "validity_status")) {
|
|
7447
|
+
db.exec(`ALTER TABLE ${t.name} ADD COLUMN validity_status TEXT NOT NULL DEFAULT 'active'`);
|
|
7448
|
+
}
|
|
7449
|
+
if (!hasColumn(db, t.name, "is_archived")) {
|
|
7450
|
+
db.exec(`ALTER TABLE ${t.name} ADD COLUMN is_archived INTEGER NOT NULL DEFAULT 0`);
|
|
7451
|
+
}
|
|
7452
|
+
if (!hasColumn(db, t.name, "promoted_to")) {
|
|
7453
|
+
db.exec(`ALTER TABLE ${t.name} ADD COLUMN promoted_to TEXT`);
|
|
7454
|
+
}
|
|
7455
|
+
// データ変換(トリガーは既に DROP 済みなので安全)
|
|
7456
|
+
db.exec(`UPDATE ${t.name} SET validity_status = ${t.statusMap}`);
|
|
7457
|
+
db.exec(`UPDATE ${t.name} SET is_archived = 1 WHERE status = 'archived'`);
|
|
7458
|
+
// 旧 'promoted' ステータスのレコードは promoted_to が不明のため、
|
|
7459
|
+
// プレースホルダ値 'unknown' をセットする(将来 rebuild で修正可能)。
|
|
7460
|
+
db.exec(`UPDATE ${t.name} SET promoted_to = 'unknown' WHERE status = 'promoted'`);
|
|
7461
|
+
// インデックス作成
|
|
7462
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_${t.name}_validity_status ON ${t.name}(validity_status)`);
|
|
7463
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_${t.name}_is_archived ON ${t.name}(is_archived)`);
|
|
7464
|
+
}
|
|
7465
|
+
// User系テーブル
|
|
7466
|
+
const userTables = [
|
|
7467
|
+
{ name: "user_memos", col: "memo_status", defaultVal: "active", archivedFrom: "active" },
|
|
7468
|
+
{ name: "user_plans", col: "plan_status", defaultVal: "active", archivedFrom: "active" },
|
|
7469
|
+
{ name: "user_issues", col: "issue_status", defaultVal: "open", archivedFrom: "closed" },
|
|
7470
|
+
{ name: "user_topics", col: "topic_status", defaultVal: "active", archivedFrom: "closed" },
|
|
7471
|
+
{ name: "user_files", col: "file_status", defaultVal: "active", archivedFrom: "active" },
|
|
7472
|
+
];
|
|
7473
|
+
for (const t of userTables) {
|
|
7474
|
+
const cols = db.prepare(`PRAGMA table_info('${t.name}')`).all().map(c => c.name);
|
|
7475
|
+
if (!cols.includes("status"))
|
|
7476
|
+
continue;
|
|
7477
|
+
// 冪等: 部分適用済み状態からのリカバリ対応
|
|
7478
|
+
if (!hasColumn(db, t.name, t.col)) {
|
|
7479
|
+
db.exec(`ALTER TABLE ${t.name} ADD COLUMN ${t.col} TEXT NOT NULL DEFAULT '${t.defaultVal}'`);
|
|
7480
|
+
}
|
|
7481
|
+
if (!hasColumn(db, t.name, "is_archived")) {
|
|
7482
|
+
db.exec(`ALTER TABLE ${t.name} ADD COLUMN is_archived INTEGER NOT NULL DEFAULT 0`);
|
|
7483
|
+
}
|
|
7484
|
+
// データ変換: archived → is_archived=1, それ以外はそのまま(トリガーは既に DROP 済み)
|
|
7485
|
+
db.exec(`UPDATE ${t.name} SET ${t.col} = CASE WHEN status = 'archived' THEN '${t.archivedFrom}' ELSE status END`);
|
|
7486
|
+
db.exec(`UPDATE ${t.name} SET is_archived = 1 WHERE status = 'archived'`);
|
|
7487
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_${t.name}_${t.col} ON ${t.name}(${t.col})`);
|
|
7488
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_${t.name}_is_archived ON ${t.name}(is_archived)`);
|
|
7489
|
+
}
|
|
7490
|
+
// 注意: 旧 status カラムは DROP できない(SQLite の制約)が、
|
|
7491
|
+
// 新カラムが追加されているのでコードは新カラムのみ参照する。
|
|
7492
|
+
// applyV1 の squashed schema は既に新カラムで定義済み。
|
|
7493
|
+
// 新トリガーを作成(new.validity_status / new.is_archived を参照)
|
|
7494
|
+
createUnifiedSearchTriggers(db);
|
|
7495
|
+
db.exec(`INSERT INTO schema_version (version) VALUES (32)`);
|
|
7496
|
+
});
|
|
7497
|
+
transaction();
|
|
7498
|
+
}
|
|
7499
|
+
/**
|
|
7500
|
+
* V33: 共通カラム正規化
|
|
7501
|
+
*
|
|
7502
|
+
* 1. user_files に device_id 追加(store_meta から自動取得)
|
|
7503
|
+
* 2. claims: source_session → session_id リネーム(他テーブルと統一)
|
|
7504
|
+
* 3. decisions に session_id 追加
|
|
7505
|
+
* 4. user_topics, claims に user_input 追加
|
|
7506
|
+
* 5. created_at/updated_at の DEFAULT 形式統一は applyV1 のみ(既存DBはアプリコードが値を設定するため影響なし)
|
|
7507
|
+
*/
|
|
7508
|
+
function applyV33(db) {
|
|
7509
|
+
const transaction = db.transaction(() => {
|
|
7510
|
+
// 1. user_files に device_id 追加
|
|
7511
|
+
if (!hasColumn(db, "user_files", "device_id")) {
|
|
7512
|
+
db.exec(`ALTER TABLE user_files ADD COLUMN device_id TEXT`);
|
|
7513
|
+
}
|
|
7514
|
+
// 2. claims: source_session → session_id リネーム
|
|
7515
|
+
if (hasColumn(db, "claims", "source_session") && !hasColumn(db, "claims", "session_id")) {
|
|
7516
|
+
db.exec(`ALTER TABLE claims RENAME COLUMN source_session TO session_id`);
|
|
7517
|
+
}
|
|
7518
|
+
// 3. decisions に session_id 追加
|
|
7519
|
+
if (!hasColumn(db, "decisions", "session_id")) {
|
|
7520
|
+
db.exec(`ALTER TABLE decisions ADD COLUMN session_id TEXT`);
|
|
7521
|
+
}
|
|
7522
|
+
// 4. user_topics に user_input 追加
|
|
7523
|
+
if (!hasColumn(db, "user_topics", "user_input")) {
|
|
7524
|
+
db.exec(`ALTER TABLE user_topics ADD COLUMN user_input TEXT`);
|
|
7525
|
+
}
|
|
7526
|
+
// 5. claims に user_input 追加
|
|
7527
|
+
if (!hasColumn(db, "claims", "user_input")) {
|
|
7528
|
+
db.exec(`ALTER TABLE claims ADD COLUMN user_input TEXT`);
|
|
7529
|
+
}
|
|
7530
|
+
db.exec(`INSERT INTO schema_version (version) VALUES (33)`);
|
|
7531
|
+
});
|
|
7532
|
+
transaction();
|
|
7533
|
+
}
|
|
7534
|
+
/**
|
|
7535
|
+
* V34: user_topics.device_id 欠落修復
|
|
7536
|
+
*
|
|
7537
|
+
* 開発中の中間版V29で user_topics が device_id なしで作成され、
|
|
7538
|
+
* V30 適用時にストアが未オープンだったために修復が漏れたケースを補修する。
|
|
7539
|
+
*/
|
|
7540
|
+
function applyV34(db) {
|
|
7541
|
+
if (!hasColumn(db, "user_topics", "device_id")) {
|
|
7542
|
+
db.exec(`ALTER TABLE user_topics ADD COLUMN device_id TEXT`);
|
|
7543
|
+
const deviceIdRow = db.prepare("SELECT value FROM store_meta WHERE key = 'device_id'").get();
|
|
7544
|
+
if (deviceIdRow) {
|
|
7545
|
+
db.prepare("UPDATE user_topics SET device_id = ? WHERE device_id IS NULL").run(deviceIdRow.value);
|
|
7546
|
+
}
|
|
7547
|
+
}
|
|
7548
|
+
db.exec(`INSERT INTO schema_version (version) VALUES (34)`);
|
|
5656
7549
|
}
|
|
5657
7550
|
/** テーブルに指定カラムが存在するかチェック */
|
|
5658
7551
|
function hasColumn(db, table, column) {
|