watashi-db 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +239 -0
  3. package/cowork-plugin/.claude-plugin/plugin.json +12 -0
  4. package/cowork-plugin/.mcp.json +8 -0
  5. package/cowork-plugin/agents/memory-keeper.md +48 -0
  6. package/cowork-plugin/skills/reflect/SKILL.md +58 -0
  7. package/cowork-plugin/skills/remember/SKILL.md +54 -0
  8. package/cowork-plugin/skills/session-start/SKILL.md +38 -0
  9. package/dist/constants.d.ts +9 -0
  10. package/dist/constants.js +43 -0
  11. package/dist/constants.js.map +1 -0
  12. package/dist/database/connection.d.ts +16 -0
  13. package/dist/database/connection.js +42 -0
  14. package/dist/database/connection.js.map +1 -0
  15. package/dist/database/queries.d.ts +227 -0
  16. package/dist/database/queries.js +730 -0
  17. package/dist/database/queries.js.map +1 -0
  18. package/dist/database/schema.d.ts +6 -0
  19. package/dist/database/schema.js +335 -0
  20. package/dist/database/schema.js.map +1 -0
  21. package/dist/index.d.ts +3 -0
  22. package/dist/index.js +57 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/prompts/bootstrap-prompts.d.ts +6 -0
  25. package/dist/prompts/bootstrap-prompts.js +69 -0
  26. package/dist/prompts/bootstrap-prompts.js.map +1 -0
  27. package/dist/provenance.d.ts +29 -0
  28. package/dist/provenance.js +33 -0
  29. package/dist/provenance.js.map +1 -0
  30. package/dist/resources/context-resources.d.ts +6 -0
  31. package/dist/resources/context-resources.js +102 -0
  32. package/dist/resources/context-resources.js.map +1 -0
  33. package/dist/tools/claim-tools.d.ts +6 -0
  34. package/dist/tools/claim-tools.js +176 -0
  35. package/dist/tools/claim-tools.js.map +1 -0
  36. package/dist/tools/decision-tools.d.ts +6 -0
  37. package/dist/tools/decision-tools.js +117 -0
  38. package/dist/tools/decision-tools.js.map +1 -0
  39. package/dist/tools/episode-tools.d.ts +7 -0
  40. package/dist/tools/episode-tools.js +128 -0
  41. package/dist/tools/episode-tools.js.map +1 -0
  42. package/dist/tools/export-tools.d.ts +6 -0
  43. package/dist/tools/export-tools.js +104 -0
  44. package/dist/tools/export-tools.js.map +1 -0
  45. package/dist/tools/profile-tools.d.ts +6 -0
  46. package/dist/tools/profile-tools.js +108 -0
  47. package/dist/tools/profile-tools.js.map +1 -0
  48. package/dist/tools/query-tools.d.ts +6 -0
  49. package/dist/tools/query-tools.js +96 -0
  50. package/dist/tools/query-tools.js.map +1 -0
  51. package/dist/types.d.ts +444 -0
  52. package/dist/types.js +191 -0
  53. package/dist/types.js.map +1 -0
  54. package/package.json +47 -0
  55. package/watashi-db-cowork-plugin.zip +0 -0
@@ -0,0 +1,730 @@
1
+ import { ulid } from "ulid";
2
+ import { getDatabase } from "./connection.js";
3
+ // === FTS5ヘルパー ===
4
+ /**
5
+ * FTS5クエリのサニタイズ
6
+ * 2026-02-11 追加: FTS5構文エラー耐性強化
7
+ * - 特殊文字(引用符、括弧、演算子)を除去
8
+ * - 各トークンをダブルクォートで囲み、リテラル一致に
9
+ * - 最後のトークンにワイルドカード(*)を付与(前方一致)
10
+ * - 空文字列の場合はnullを返す(FTS検索をスキップ)
11
+ */
12
+ function sanitizeFtsQuery(input) {
13
+ // FTS5の予約語・特殊文字を除去
14
+ const cleaned = input
15
+ .replace(/["""(){}[\]^~:]/g, " ") // 構造文字を除去
16
+ .replace(/\b(AND|OR|NOT|NEAR)\b/gi, " ") // 論理演算子を除去
17
+ .replace(/\*/g, " ") // 既存のワイルドカードを除去
18
+ .trim();
19
+ if (cleaned.length === 0)
20
+ return null;
21
+ // トークン分割(空白区切り)
22
+ const tokens = cleaned.split(/\s+/).filter(t => t.length > 0);
23
+ if (tokens.length === 0)
24
+ return null;
25
+ // 各トークンをダブルクォートで囲む(リテラル一致)
26
+ // 最後のトークンにだけ*(前方一致)を付与
27
+ const quoted = tokens.map((t, i) => {
28
+ const escaped = t.replace(/"/g, '""'); // ダブルクォートエスケープ
29
+ if (i === tokens.length - 1) {
30
+ return `"${escaped}"*`;
31
+ }
32
+ return `"${escaped}"`;
33
+ });
34
+ return quoted.join(" ");
35
+ }
36
+ // === Claim CRUD ===
37
+ /**
38
+ * 新規Claim挿入
39
+ * 2026-02-11 修正: client_name, client_version追加(Provenance自動付与対応)
40
+ * 元の実装: source_tool/source_sessionのみ(自己申告値)
41
+ */
42
+ export function insertClaim(params) {
43
+ const db = getDatabase();
44
+ const id = ulid();
45
+ const now = new Date().toISOString();
46
+ db.prepare(`
47
+ INSERT INTO claims (id, subject, predicate, object, category, scope, confidence, evidence, falsifier, source_tool, source_session, client_name, client_version, status, created_at, updated_at)
48
+ VALUES (@id, @subject, @predicate, @object, @category, @scope, @confidence, @evidence, @falsifier, @source_tool, @source_session, @client_name, @client_version, 'active', @created_at, @updated_at)
49
+ `).run({
50
+ id,
51
+ subject: params.subject,
52
+ predicate: params.predicate,
53
+ object: params.object,
54
+ category: params.category,
55
+ scope: params.scope,
56
+ confidence: params.confidence,
57
+ evidence: params.evidence ?? null,
58
+ falsifier: params.falsifier ?? null,
59
+ source_tool: params.source_tool ?? null,
60
+ source_session: params.source_session ?? null,
61
+ client_name: params.client_name ?? null,
62
+ client_version: params.client_version ?? null,
63
+ created_at: now,
64
+ updated_at: now,
65
+ });
66
+ return getClaimById(id);
67
+ }
68
+ /**
69
+ * Claim取得(ID指定)
70
+ */
71
+ export function getClaimById(id) {
72
+ const db = getDatabase();
73
+ return db.prepare("SELECT * FROM claims WHERE id = ?").get(id);
74
+ }
75
+ /**
76
+ * Claim更新(変更履歴付き)
77
+ */
78
+ export function updateClaim(id, updates, reason) {
79
+ const db = getDatabase();
80
+ const existing = getClaimById(id);
81
+ if (!existing)
82
+ return undefined;
83
+ const now = new Date().toISOString();
84
+ // 2026-02-11 修正: falsifier追加(L2 Core v0対応)
85
+ // 元の実装: falsifierフィールドなし
86
+ const allowedFields = ["subject", "predicate", "object", "category", "scope", "confidence", "evidence", "falsifier"];
87
+ // トランザクションで更新と履歴記録を実行
88
+ const transaction = db.transaction(() => {
89
+ for (const [field, newValue] of Object.entries(updates)) {
90
+ if (!allowedFields.includes(field))
91
+ continue;
92
+ const oldValue = existing[field];
93
+ if (oldValue === newValue)
94
+ continue;
95
+ // 変更履歴を記録
96
+ db.prepare(`
97
+ INSERT INTO claim_history (id, claim_id, field_name, old_value, new_value, reason, changed_at)
98
+ VALUES (@id, @claim_id, @field_name, @old_value, @new_value, @reason, @changed_at)
99
+ `).run({
100
+ id: ulid(),
101
+ claim_id: id,
102
+ field_name: field,
103
+ old_value: oldValue != null ? String(oldValue) : null,
104
+ new_value: newValue != null ? String(newValue) : null,
105
+ reason,
106
+ changed_at: now,
107
+ });
108
+ }
109
+ // Claim本体を更新
110
+ const setClauses = [];
111
+ const values = { id, updated_at: now };
112
+ for (const [field, value] of Object.entries(updates)) {
113
+ if (!allowedFields.includes(field))
114
+ continue;
115
+ setClauses.push(`${field} = @${field}`);
116
+ values[field] = value ?? null;
117
+ }
118
+ if (setClauses.length > 0) {
119
+ setClauses.push("updated_at = @updated_at");
120
+ db.prepare(`UPDATE claims SET ${setClauses.join(", ")} WHERE id = @id`).run(values);
121
+ }
122
+ });
123
+ transaction();
124
+ return getClaimById(id);
125
+ }
126
+ /**
127
+ * Claim撤回(削除ではなくステータス変更 + 履歴記録)
128
+ */
129
+ export function retractClaim(id, reason) {
130
+ const db = getDatabase();
131
+ const existing = getClaimById(id);
132
+ if (!existing)
133
+ return undefined;
134
+ if (existing.status !== "active")
135
+ return existing;
136
+ const now = new Date().toISOString();
137
+ db.transaction(() => {
138
+ // 変更履歴を記録
139
+ db.prepare(`
140
+ INSERT INTO claim_history (id, claim_id, field_name, old_value, new_value, reason, changed_at)
141
+ VALUES (@id, @claim_id, 'status', 'active', 'retracted', @reason, @changed_at)
142
+ `).run({
143
+ id: ulid(),
144
+ claim_id: id,
145
+ reason,
146
+ changed_at: now,
147
+ });
148
+ // ステータスを撤回に変更
149
+ db.prepare("UPDATE claims SET status = 'retracted', updated_at = @updated_at WHERE id = @id").run({
150
+ id,
151
+ updated_at: now,
152
+ });
153
+ })();
154
+ return getClaimById(id);
155
+ }
156
+ /**
157
+ * Claim置き換え(旧Claimをsuperseded化し、新旧関係を記録)
158
+ */
159
+ export function supersedeClaim(oldId, newId, reason) {
160
+ const db = getDatabase();
161
+ const oldClaim = getClaimById(oldId);
162
+ const newClaim = getClaimById(newId);
163
+ if (!oldClaim || !newClaim)
164
+ return undefined;
165
+ if (oldClaim.status !== "active")
166
+ return undefined;
167
+ const now = new Date().toISOString();
168
+ db.transaction(() => {
169
+ // 変更履歴を記録
170
+ db.prepare(`
171
+ INSERT INTO claim_history (id, claim_id, field_name, old_value, new_value, reason, changed_at)
172
+ VALUES (@id, @claim_id, 'status', 'active', 'superseded', @reason, @changed_at)
173
+ `).run({
174
+ id: ulid(),
175
+ claim_id: oldId,
176
+ reason,
177
+ changed_at: now,
178
+ });
179
+ // 旧Claimをsuperseded化
180
+ db.prepare("UPDATE claims SET status = 'superseded', updated_at = @updated_at WHERE id = @id").run({
181
+ id: oldId,
182
+ updated_at: now,
183
+ });
184
+ // 関係テーブルにsupersedes関係を記録
185
+ db.prepare(`
186
+ INSERT INTO claim_relations (id, source_claim_id, target_claim_id, relation_type, confidence, reasoning, created_at)
187
+ VALUES (@id, @source_claim_id, @target_claim_id, 'supersedes', 1.0, @reasoning, @created_at)
188
+ `).run({
189
+ id: ulid(),
190
+ source_claim_id: newId,
191
+ target_claim_id: oldId,
192
+ reasoning: reason,
193
+ created_at: now,
194
+ });
195
+ })();
196
+ return {
197
+ oldClaim: getClaimById(oldId),
198
+ newClaim: getClaimById(newId),
199
+ };
200
+ }
201
+ /**
202
+ * Decision ステータス遷移(reverse / obsolete)
203
+ */
204
+ export function updateDecisionStatus(id, newStatus, reason) {
205
+ const db = getDatabase();
206
+ const existing = getDecisionById(id);
207
+ if (!existing)
208
+ return undefined;
209
+ if (existing.status !== "active")
210
+ return existing;
211
+ const now = new Date().toISOString();
212
+ db.prepare("UPDATE decisions SET status = @status, updated_at = @updated_at WHERE id = @id").run({
213
+ id,
214
+ status: newStatus,
215
+ updated_at: now,
216
+ });
217
+ return getDecisionById(id);
218
+ }
219
+ /**
220
+ * Claim検索(条件指定)
221
+ */
222
+ export function searchClaims(params) {
223
+ const db = getDatabase();
224
+ // FTS検索の場合
225
+ // 2026-02-11 修正: FTS5構文エラー耐性強化(サニタイズ + LIKEフォールバック)
226
+ // 元の実装: params.query + "*" をそのまま渡していた(特殊文字でエラー)
227
+ if (params.query) {
228
+ const sanitized = sanitizeFtsQuery(params.query);
229
+ const conditions = [];
230
+ const values = { limit: params.limit };
231
+ if (params.status) {
232
+ conditions.push("c.status = @status");
233
+ values.status = params.status;
234
+ }
235
+ if (params.category) {
236
+ conditions.push("c.category = @category");
237
+ values.category = params.category;
238
+ }
239
+ if (params.scope) {
240
+ conditions.push("c.scope = @scope");
241
+ values.scope = params.scope;
242
+ }
243
+ // サニタイズ済みクエリがあればFTS検索を試行
244
+ if (sanitized) {
245
+ values.query = sanitized;
246
+ const whereClause = conditions.length > 0
247
+ ? "AND " + conditions.join(" AND ")
248
+ : "";
249
+ try {
250
+ return db.prepare(`
251
+ SELECT c.* FROM claims c
252
+ JOIN claims_fts f ON c.rowid = f.rowid
253
+ WHERE claims_fts MATCH @query ${whereClause}
254
+ ORDER BY rank
255
+ LIMIT @limit
256
+ `).all(values);
257
+ }
258
+ catch {
259
+ // FTS5エラー時はLIKEフォールバック(下記で処理)
260
+ }
261
+ }
262
+ // LIKEフォールバック: FTSが失敗した場合やサニタイズ後空文字の場合
263
+ const likeConditions = [...conditions];
264
+ const likeValues = { ...values, limit: params.limit };
265
+ delete likeValues.query;
266
+ likeValues.like_query = `%${params.query}%`;
267
+ likeConditions.push("(c.subject LIKE @like_query OR c.predicate LIKE @like_query OR c.object LIKE @like_query OR c.evidence LIKE @like_query)");
268
+ const likeWhere = likeConditions.length > 0
269
+ ? "WHERE " + likeConditions.join(" AND ")
270
+ : "";
271
+ return db.prepare(`
272
+ SELECT c.* FROM claims c
273
+ ${likeWhere}
274
+ ORDER BY c.updated_at DESC
275
+ LIMIT @limit
276
+ `).all(likeValues);
277
+ }
278
+ // 通常検索
279
+ const conditions = [];
280
+ const values = { limit: params.limit };
281
+ if (params.status) {
282
+ conditions.push("status = @status");
283
+ values.status = params.status;
284
+ }
285
+ if (params.category) {
286
+ conditions.push("category = @category");
287
+ values.category = params.category;
288
+ }
289
+ if (params.scope) {
290
+ conditions.push("scope = @scope");
291
+ values.scope = params.scope;
292
+ }
293
+ const whereClause = conditions.length > 0
294
+ ? "WHERE " + conditions.join(" AND ")
295
+ : "";
296
+ return db.prepare(`
297
+ SELECT * FROM claims ${whereClause}
298
+ ORDER BY updated_at DESC
299
+ LIMIT @limit
300
+ `).all(values);
301
+ }
302
+ /**
303
+ * トピックに関連するコンテキスト検索(FTS5使用)
304
+ * 2026-02-11 修正: FTS5構文エラー耐性強化(サニタイズ + LIKEフォールバック)
305
+ * 元の実装: topic + "*" をそのまま渡していた
306
+ */
307
+ export function queryContext(topic, scope, limit) {
308
+ const db = getDatabase();
309
+ const sanitized = sanitizeFtsQuery(topic);
310
+ let scopeFilter = "";
311
+ const scopeValues = {};
312
+ if (scope) {
313
+ scopeFilter = "AND c.scope = @scope";
314
+ scopeValues.scope = scope;
315
+ }
316
+ // サニタイズ済みクエリがあればFTS検索を試行
317
+ if (sanitized) {
318
+ try {
319
+ return db.prepare(`
320
+ SELECT c.* FROM claims c
321
+ JOIN claims_fts f ON c.rowid = f.rowid
322
+ WHERE claims_fts MATCH @query
323
+ AND c.status = 'active'
324
+ ${scopeFilter}
325
+ ORDER BY rank
326
+ LIMIT @limit
327
+ `).all({ query: sanitized, limit, ...scopeValues });
328
+ }
329
+ catch {
330
+ // FTS5エラー時はLIKEフォールバック
331
+ }
332
+ }
333
+ // LIKEフォールバック
334
+ const likeQuery = `%${topic}%`;
335
+ return db.prepare(`
336
+ SELECT c.* FROM claims c
337
+ WHERE c.status = 'active'
338
+ AND (c.subject LIKE @like_query OR c.predicate LIKE @like_query OR c.object LIKE @like_query OR c.evidence LIKE @like_query)
339
+ ${scopeFilter}
340
+ ORDER BY c.updated_at DESC
341
+ LIMIT @limit
342
+ `).all({ like_query: likeQuery, limit, ...scopeValues });
343
+ }
344
+ /**
345
+ * Claimの変更履歴を取得
346
+ */
347
+ export function getClaimHistory(claimId) {
348
+ const db = getDatabase();
349
+ return db.prepare("SELECT * FROM claim_history WHERE claim_id = ? ORDER BY changed_at DESC").all(claimId);
350
+ }
351
+ // === Decision CRUD ===
352
+ /**
353
+ * 意思決定ログ記録
354
+ * 2026-02-11 修正: client_name, client_version追加(Provenance自動付与対応)
355
+ * 元の実装: source_toolのみ(自己申告値)
356
+ */
357
+ export function insertDecision(params) {
358
+ const db = getDatabase();
359
+ const id = ulid();
360
+ const now = new Date().toISOString();
361
+ db.prepare(`
362
+ INSERT INTO decisions (id, title, description, reasoning, alternatives, related_claim_ids, status, source_tool, client_name, client_version, created_at, updated_at)
363
+ VALUES (@id, @title, @description, @reasoning, @alternatives, @related_claim_ids, 'active', @source_tool, @client_name, @client_version, @created_at, @updated_at)
364
+ `).run({
365
+ id,
366
+ title: params.title,
367
+ description: params.description,
368
+ reasoning: params.reasoning,
369
+ alternatives: JSON.stringify(params.alternatives),
370
+ related_claim_ids: JSON.stringify(params.related_claim_ids),
371
+ source_tool: params.source_tool ?? null,
372
+ client_name: params.client_name ?? null,
373
+ client_version: params.client_version ?? null,
374
+ created_at: now,
375
+ updated_at: now,
376
+ });
377
+ return getDecisionById(id);
378
+ }
379
+ /**
380
+ * Decision取得(ID指定)
381
+ */
382
+ export function getDecisionById(id) {
383
+ const db = getDatabase();
384
+ return db.prepare("SELECT * FROM decisions WHERE id = ?").get(id);
385
+ }
386
+ /**
387
+ * Decision検索
388
+ */
389
+ export function listDecisions(params) {
390
+ const db = getDatabase();
391
+ const conditions = [];
392
+ const values = { limit: params.limit };
393
+ if (params.status) {
394
+ conditions.push("status = @status");
395
+ values.status = params.status;
396
+ }
397
+ if (params.query) {
398
+ conditions.push("(title LIKE @query OR description LIKE @query OR reasoning LIKE @query)");
399
+ values.query = `%${params.query}%`;
400
+ }
401
+ const whereClause = conditions.length > 0
402
+ ? "WHERE " + conditions.join(" AND ")
403
+ : "";
404
+ return db.prepare(`
405
+ SELECT * FROM decisions ${whereClause}
406
+ ORDER BY created_at DESC
407
+ LIMIT @limit
408
+ `).all(values);
409
+ }
410
+ // === 統計 ===
411
+ /**
412
+ * データベース統計情報を取得
413
+ */
414
+ export function getStats() {
415
+ const db = getDatabase();
416
+ const total_claims = db.prepare("SELECT COUNT(*) as count FROM claims").get().count;
417
+ const active_claims = db.prepare("SELECT COUNT(*) as count FROM claims WHERE status = 'active'").get().count;
418
+ const retracted_claims = db.prepare("SELECT COUNT(*) as count FROM claims WHERE status = 'retracted'").get().count;
419
+ const superseded_claims = db.prepare("SELECT COUNT(*) as count FROM claims WHERE status = 'superseded'").get().count;
420
+ const total_decisions = db.prepare("SELECT COUNT(*) as count FROM decisions").get().count;
421
+ const active_decisions = db.prepare("SELECT COUNT(*) as count FROM decisions WHERE status = 'active'").get().count;
422
+ const total_history_entries = db.prepare("SELECT COUNT(*) as count FROM claim_history").get().count;
423
+ const categoryRows = db.prepare("SELECT category, COUNT(*) as count FROM claims WHERE status = 'active' GROUP BY category").all();
424
+ const categories = {};
425
+ for (const row of categoryRows) {
426
+ categories[row.category] = row.count;
427
+ }
428
+ return {
429
+ total_claims,
430
+ active_claims,
431
+ retracted_claims,
432
+ superseded_claims,
433
+ total_decisions,
434
+ active_decisions,
435
+ total_history_entries,
436
+ categories,
437
+ };
438
+ }
439
+ /**
440
+ * 全アクティブClaimを取得(リソース用)
441
+ */
442
+ export function getAllActiveClaims() {
443
+ const db = getDatabase();
444
+ return db.prepare("SELECT * FROM claims WHERE status = 'active' ORDER BY category, updated_at DESC").all();
445
+ }
446
+ /**
447
+ * 直近の意思決定を取得(リソース用)
448
+ */
449
+ export function getRecentDecisions(limit = 10) {
450
+ const db = getDatabase();
451
+ return db.prepare("SELECT * FROM decisions WHERE status = 'active' ORDER BY created_at DESC LIMIT ?").all(limit);
452
+ }
453
+ // === L2 Core v0: Claim関係 ===
454
+ /**
455
+ * Claim間関係を記録
456
+ */
457
+ export function insertClaimRelation(params) {
458
+ const db = getDatabase();
459
+ const id = ulid();
460
+ const now = new Date().toISOString();
461
+ db.prepare(`
462
+ INSERT INTO claim_relations (id, source_claim_id, target_claim_id, relation_type, confidence, reasoning, source_tool, created_at)
463
+ VALUES (@id, @source_claim_id, @target_claim_id, @relation_type, @confidence, @reasoning, @source_tool, @created_at)
464
+ `).run({
465
+ id,
466
+ source_claim_id: params.source_claim_id,
467
+ target_claim_id: params.target_claim_id,
468
+ relation_type: params.relation_type,
469
+ confidence: params.confidence,
470
+ reasoning: params.reasoning ?? null,
471
+ source_tool: params.source_tool ?? null,
472
+ created_at: now,
473
+ });
474
+ return db.prepare("SELECT * FROM claim_relations WHERE id = ?").get(id);
475
+ }
476
+ /**
477
+ * 特定Claimの関係を取得(source/target両方向)
478
+ */
479
+ export function getClaimRelations(claimId) {
480
+ const db = getDatabase();
481
+ return db.prepare("SELECT * FROM claim_relations WHERE source_claim_id = ? OR target_claim_id = ? ORDER BY created_at DESC").all(claimId, claimId);
482
+ }
483
+ // === L2 Core v0: 構造化根拠 ===
484
+ /**
485
+ * Claimの構造化根拠を記録
486
+ */
487
+ export function insertClaimEvidence(params) {
488
+ const db = getDatabase();
489
+ const id = ulid();
490
+ const now = new Date().toISOString();
491
+ db.prepare(`
492
+ INSERT INTO claim_evidence (id, claim_id, evidence_type, uri, span, content_hash, description, source_tool, created_at)
493
+ VALUES (@id, @claim_id, @evidence_type, @uri, @span, @content_hash, @description, @source_tool, @created_at)
494
+ `).run({
495
+ id,
496
+ claim_id: params.claim_id,
497
+ evidence_type: params.evidence_type,
498
+ uri: params.uri ?? null,
499
+ span: params.span ?? null,
500
+ content_hash: params.content_hash ?? null,
501
+ description: params.description ?? null,
502
+ source_tool: params.source_tool ?? null,
503
+ created_at: now,
504
+ });
505
+ return db.prepare("SELECT * FROM claim_evidence WHERE id = ?").get(id);
506
+ }
507
+ /**
508
+ * 特定Claimの根拠一覧を取得
509
+ */
510
+ export function getClaimEvidenceList(claimId) {
511
+ const db = getDatabase();
512
+ return db.prepare("SELECT * FROM claim_evidence WHERE claim_id = ? ORDER BY created_at DESC").all(claimId);
513
+ }
514
+ // === L2 Core v0: 検証ログ ===
515
+ /**
516
+ * Claimの検証結果を記録
517
+ */
518
+ export function insertClaimCheck(params) {
519
+ const db = getDatabase();
520
+ const id = ulid();
521
+ const now = new Date().toISOString();
522
+ db.prepare(`
523
+ INSERT INTO claim_checks (id, claim_id, check_type, result, details, source_tool, checked_at)
524
+ VALUES (@id, @claim_id, @check_type, @result, @details, @source_tool, @checked_at)
525
+ `).run({
526
+ id,
527
+ claim_id: params.claim_id,
528
+ check_type: params.check_type,
529
+ result: params.result,
530
+ details: params.details ?? null,
531
+ source_tool: params.source_tool ?? null,
532
+ checked_at: now,
533
+ });
534
+ return db.prepare("SELECT * FROM claim_checks WHERE id = ?").get(id);
535
+ }
536
+ /**
537
+ * 特定Claimの検証ログを取得
538
+ */
539
+ export function getClaimChecks(claimId) {
540
+ const db = getDatabase();
541
+ return db.prepare("SELECT * FROM claim_checks WHERE claim_id = ? ORDER BY checked_at DESC").all(claimId);
542
+ }
543
+ // === V3: 監査ログ ===
544
+ /**
545
+ * 監査ログを記録する内部ヘルパー
546
+ * 全書き込み操作から呼び出される
547
+ */
548
+ export function insertAuditLog(params) {
549
+ const db = getDatabase();
550
+ const id = ulid();
551
+ const now = new Date().toISOString();
552
+ db.prepare(`
553
+ INSERT INTO audit_log (id, operation, entity_type, entity_id, summary, details, client_name, client_version, session_id, source_tool, created_at)
554
+ VALUES (@id, @operation, @entity_type, @entity_id, @summary, @details, @client_name, @client_version, @session_id, @source_tool, @created_at)
555
+ `).run({
556
+ id,
557
+ operation: params.operation,
558
+ entity_type: params.entity_type,
559
+ entity_id: params.entity_id,
560
+ summary: params.summary,
561
+ details: params.details ?? null,
562
+ client_name: params.provenance?.client_name ?? null,
563
+ client_version: params.provenance?.client_version ?? null,
564
+ session_id: params.provenance?.session_id ?? null,
565
+ source_tool: params.source_tool ?? null,
566
+ created_at: now,
567
+ });
568
+ }
569
+ /**
570
+ * 特定エンティティの監査ログを取得
571
+ */
572
+ export function getAuditLog(entityType, entityId) {
573
+ const db = getDatabase();
574
+ return db.prepare("SELECT * FROM audit_log WHERE entity_type = ? AND entity_id = ? ORDER BY created_at DESC").all(entityType, entityId);
575
+ }
576
+ /**
577
+ * 監査ログ検索(全エンティティ横断)
578
+ */
579
+ export function searchAuditLog(params) {
580
+ const db = getDatabase();
581
+ const conditions = [];
582
+ const values = { limit: params.limit };
583
+ if (params.operation) {
584
+ conditions.push("operation = @operation");
585
+ values.operation = params.operation;
586
+ }
587
+ if (params.entity_type) {
588
+ conditions.push("entity_type = @entity_type");
589
+ values.entity_type = params.entity_type;
590
+ }
591
+ if (params.session_id) {
592
+ conditions.push("session_id = @session_id");
593
+ values.session_id = params.session_id;
594
+ }
595
+ const whereClause = conditions.length > 0
596
+ ? "WHERE " + conditions.join(" AND ")
597
+ : "";
598
+ return db.prepare(`
599
+ SELECT * FROM audit_log ${whereClause}
600
+ ORDER BY created_at DESC
601
+ LIMIT @limit
602
+ `).all(values);
603
+ }
604
+ // === V4: Episode CRUD ===
605
+ /**
606
+ * Episode記録
607
+ * 思考パターン・意思決定エピソードを記録する。
608
+ * context→trigger→problems→desires→decisions→outcomes→principles の流れで格納。
609
+ */
610
+ export function insertEpisode(params) {
611
+ const db = getDatabase();
612
+ const id = ulid();
613
+ const now = new Date().toISOString();
614
+ db.prepare(`
615
+ INSERT INTO episodes (id, title, context, trigger, problems, desires, decisions, outcomes, principles, evidence_refs, tags, status, client_name, client_version, source_tool, session_id, created_at, updated_at)
616
+ VALUES (@id, @title, @context, @trigger, @problems, @desires, @decisions, @outcomes, @principles, @evidence_refs, @tags, 'active', @client_name, @client_version, @source_tool, @session_id, @created_at, @updated_at)
617
+ `).run({
618
+ id,
619
+ title: params.title,
620
+ context: params.context ?? null,
621
+ trigger: params.trigger ?? null,
622
+ problems: JSON.stringify(params.problems),
623
+ desires: JSON.stringify(params.desires),
624
+ decisions: JSON.stringify(params.decisions),
625
+ outcomes: JSON.stringify(params.outcomes),
626
+ principles: JSON.stringify(params.principles),
627
+ evidence_refs: JSON.stringify(params.evidence_refs),
628
+ tags: JSON.stringify(params.tags),
629
+ client_name: params.client_name ?? null,
630
+ client_version: params.client_version ?? null,
631
+ source_tool: params.source_tool ?? null,
632
+ session_id: params.session_id ?? null,
633
+ created_at: now,
634
+ updated_at: now,
635
+ });
636
+ return getEpisodeById(id);
637
+ }
638
+ /**
639
+ * Episode取得(ID指定)
640
+ */
641
+ export function getEpisodeById(id) {
642
+ const db = getDatabase();
643
+ return db.prepare("SELECT * FROM episodes WHERE id = ?").get(id);
644
+ }
645
+ /**
646
+ * Episode一覧取得(検索・フィルタ対応)
647
+ * FTS5検索 + タグフィルタ + ステータスフィルタ
648
+ */
649
+ export function listEpisodes(params) {
650
+ const db = getDatabase();
651
+ // FTS検索の場合
652
+ if (params.query) {
653
+ const sanitized = sanitizeFtsQuery(params.query);
654
+ const conditions = [];
655
+ const values = { limit: params.limit };
656
+ if (params.status) {
657
+ conditions.push("e.status = @status");
658
+ values.status = params.status;
659
+ }
660
+ if (params.tag) {
661
+ // JSON配列内のタグをLIKEで検索
662
+ conditions.push("e.tags LIKE @tag_like");
663
+ values.tag_like = `%"${params.tag}"%`;
664
+ }
665
+ // サニタイズ済みクエリがあればFTS検索を試行
666
+ if (sanitized) {
667
+ values.query = sanitized;
668
+ const whereClause = conditions.length > 0
669
+ ? "AND " + conditions.join(" AND ")
670
+ : "";
671
+ try {
672
+ return db.prepare(`
673
+ SELECT e.* FROM episodes e
674
+ JOIN episodes_fts f ON e.rowid = f.rowid
675
+ WHERE episodes_fts MATCH @query ${whereClause}
676
+ ORDER BY rank
677
+ LIMIT @limit
678
+ `).all(values);
679
+ }
680
+ catch {
681
+ // FTS5エラー時はLIKEフォールバック
682
+ }
683
+ }
684
+ // LIKEフォールバック
685
+ const likeConditions = [...conditions];
686
+ const likeValues = { ...values, limit: params.limit };
687
+ delete likeValues.query;
688
+ likeValues.like_query = `%${params.query}%`;
689
+ likeConditions.push("(e.title LIKE @like_query OR e.context LIKE @like_query OR e.trigger LIKE @like_query OR e.principles LIKE @like_query)");
690
+ const likeWhere = likeConditions.length > 0
691
+ ? "WHERE " + likeConditions.join(" AND ")
692
+ : "";
693
+ return db.prepare(`
694
+ SELECT e.* FROM episodes e
695
+ ${likeWhere}
696
+ ORDER BY e.updated_at DESC
697
+ LIMIT @limit
698
+ `).all(likeValues);
699
+ }
700
+ // 通常検索(条件フィルタのみ)
701
+ const conditions = [];
702
+ const values = { limit: params.limit };
703
+ if (params.status) {
704
+ conditions.push("status = @status");
705
+ values.status = params.status;
706
+ }
707
+ if (params.tag) {
708
+ conditions.push("tags LIKE @tag_like");
709
+ values.tag_like = `%"${params.tag}"%`;
710
+ }
711
+ const whereClause = conditions.length > 0
712
+ ? "WHERE " + conditions.join(" AND ")
713
+ : "";
714
+ return db.prepare(`
715
+ SELECT * FROM episodes ${whereClause}
716
+ ORDER BY created_at DESC
717
+ LIMIT @limit
718
+ `).all(values);
719
+ }
720
+ /**
721
+ * Episode統計情報を取得
722
+ */
723
+ export function getEpisodeStats() {
724
+ const db = getDatabase();
725
+ const total_episodes = db.prepare("SELECT COUNT(*) as count FROM episodes").get().count;
726
+ const active_episodes = db.prepare("SELECT COUNT(*) as count FROM episodes WHERE status = 'active'").get().count;
727
+ const archived_episodes = db.prepare("SELECT COUNT(*) as count FROM episodes WHERE status = 'archived'").get().count;
728
+ return { total_episodes, active_episodes, archived_episodes };
729
+ }
730
+ //# sourceMappingURL=queries.js.map