watashi-db 0.0.13 → 0.0.14

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 (77) hide show
  1. package/CLAUDE.md +36 -0
  2. package/LICENSE +1 -1
  3. package/README.md +33 -2
  4. package/cowork-plugin/skills/groom/SKILL.md +51 -15
  5. package/cowork-plugin/skills/recall/SKILL.md +5 -6
  6. package/cowork-plugin/skills/reflect/SKILL.md +4 -4
  7. package/cowork-plugin/skills/remember/SKILL.md +3 -3
  8. package/cowork-plugin/skills/session-start/SKILL.md +3 -3
  9. package/dist/config/schema.js +1 -1
  10. package/dist/constants.d.ts +5 -1
  11. package/dist/constants.js +19 -3
  12. package/dist/constants.js.map +1 -1
  13. package/dist/database/archive.js +6 -6
  14. package/dist/database/queries-core.d.ts +75 -1
  15. package/dist/database/queries-core.js +283 -12
  16. package/dist/database/queries-core.js.map +1 -1
  17. package/dist/database/queries.d.ts +71 -1
  18. package/dist/database/queries.js +29 -0
  19. package/dist/database/queries.js.map +1 -1
  20. package/dist/database/schema.d.ts +1 -0
  21. package/dist/database/schema.js +1915 -214
  22. package/dist/database/schema.js.map +1 -1
  23. package/dist/embedding/embed-on-write.d.ts +7 -1
  24. package/dist/embedding/embed-on-write.js +8 -3
  25. package/dist/embedding/embed-on-write.js.map +1 -1
  26. package/dist/hook.js +9 -6
  27. package/dist/hook.js.map +1 -1
  28. package/dist/index.js +3 -2
  29. package/dist/index.js.map +1 -1
  30. package/dist/resources/config-guide-content.d.ts +1 -1
  31. package/dist/resources/config-guide-content.js +2 -2
  32. package/dist/server-instructions.js +16 -17
  33. package/dist/server-instructions.js.map +1 -1
  34. package/dist/server.d.ts +4 -1
  35. package/dist/server.js +42 -18
  36. package/dist/server.js.map +1 -1
  37. package/dist/setup.js +5 -6
  38. package/dist/setup.js.map +1 -1
  39. package/dist/store/federation.d.ts +12 -1
  40. package/dist/store/federation.js +38 -0
  41. package/dist/store/federation.js.map +1 -1
  42. package/dist/store/sync-manager.d.ts +1 -1
  43. package/dist/store/sync-manager.js +9 -9
  44. package/dist/tools/claim-tools.d.ts +1 -1
  45. package/dist/tools/claim-tools.js +7 -7
  46. package/dist/tools/claim-tools.js.map +1 -1
  47. package/dist/tools/decision-tools.d.ts +1 -1
  48. package/dist/tools/decision-tools.js +2 -2
  49. package/dist/tools/decision-tools.js.map +1 -1
  50. package/dist/tools/episode-tools.d.ts +1 -1
  51. package/dist/tools/episode-tools.js +2 -2
  52. package/dist/tools/episode-tools.js.map +1 -1
  53. package/dist/tools/file-tools.d.ts +3 -0
  54. package/dist/tools/file-tools.js +347 -0
  55. package/dist/tools/file-tools.js.map +1 -0
  56. package/dist/tools/get-tools.d.ts +1 -1
  57. package/dist/tools/get-tools.js +39 -5
  58. package/dist/tools/get-tools.js.map +1 -1
  59. package/dist/tools/knowledge-tools.d.ts +1 -1
  60. package/dist/tools/knowledge-tools.js +2 -2
  61. package/dist/tools/knowledge-tools.js.map +1 -1
  62. package/dist/tools/maintenance-tools.d.ts +1 -1
  63. package/dist/tools/maintenance-tools.js +38 -6
  64. package/dist/tools/maintenance-tools.js.map +1 -1
  65. package/dist/tools/memo-tools.d.ts +7 -11
  66. package/dist/tools/memo-tools.js +499 -307
  67. package/dist/tools/memo-tools.js.map +1 -1
  68. package/dist/tools/query-tools.d.ts +1 -1
  69. package/dist/tools/query-tools.js +28 -5
  70. package/dist/tools/query-tools.js.map +1 -1
  71. package/dist/types.d.ts +370 -48
  72. package/dist/types.js +124 -16
  73. package/dist/types.js.map +1 -1
  74. package/misc/20260316_110841_groom-recipe.md +483 -0
  75. package/misc/20260316_xaml-testing-library-recipe.md +817 -0
  76. package/package.json +4 -2
  77. package/scripts/update-license-version.sh +7 -0
@@ -1,4 +1,5 @@
1
1
  import { ulid } from "ulid";
2
+ import { DEFAULT_SEARCH_ENTITY_TYPES } from "../types.js";
2
3
  import { claimScoreExpression, computeClaimScore } from "../scoring.js";
3
4
  // === exp() サポート検出(SQLite数学関数) ===
4
5
  /** exp() が使えるかを DB ごとにキャッシュ */
@@ -748,11 +749,11 @@ export function getClaimChecks(db, claimId) {
748
749
  export function insertAuditLog(db, params) {
749
750
  const id = ulid();
750
751
  const now = new Date().toISOString();
751
- // 2026-03-03 追加 (V21): client_id でどのデバイスで作られたか追跡
752
- const clientIdRow = db.prepare("SELECT value FROM store_meta WHERE key = 'client_id'").get();
752
+ // 2026-03-03 追加 (V21): device_id でどのデバイスで作られたか追跡
753
+ const deviceIdRow = db.prepare("SELECT value FROM store_meta WHERE key = 'device_id'").get();
753
754
  db.prepare(`
754
- INSERT INTO audit_log (id, operation, entity_type, entity_id, summary, details, client_name, client_version, session_id, source_tool, client_id, created_at)
755
- VALUES (@id, @operation, @entity_type, @entity_id, @summary, @details, @client_name, @client_version, @session_id, @source_tool, @client_id, @created_at)
755
+ INSERT INTO audit_log (id, operation, entity_type, entity_id, summary, details, client_name, client_version, session_id, source_tool, device_id, created_at)
756
+ VALUES (@id, @operation, @entity_type, @entity_id, @summary, @details, @client_name, @client_version, @session_id, @source_tool, @device_id, @created_at)
756
757
  `).run({
757
758
  id,
758
759
  operation: params.operation,
@@ -764,7 +765,7 @@ export function insertAuditLog(db, params) {
764
765
  client_version: params.provenance?.client_version ?? null,
765
766
  session_id: params.provenance?.session_id ?? null,
766
767
  source_tool: params.source_tool ?? null,
767
- client_id: clientIdRow?.value ?? null,
768
+ device_id: deviceIdRow?.value ?? null,
768
769
  created_at: now,
769
770
  });
770
771
  }
@@ -1121,6 +1122,11 @@ export function getTopicSummary(db, tagLimit = 10, decisionLimit = 5, scope) {
1121
1122
  activeMemos = db.prepare(`SELECT COUNT(*) as count FROM user_memos WHERE status = 'active'${scopeFilter}`).get(scopeValues).count;
1122
1123
  }
1123
1124
  catch { /* V16未適用 */ }
1125
+ let activeFiles = 0;
1126
+ try {
1127
+ activeFiles = db.prepare(`SELECT COUNT(*) as count FROM user_files WHERE status = 'active'${scopeFilter}`).get(scopeValues).count;
1128
+ }
1129
+ catch { /* V31未適用 */ }
1124
1130
  // Issue #47: エンティティタイトルから具体的キーワード候補を抽出
1125
1131
  let topicKeywords = [];
1126
1132
  try {
@@ -1148,7 +1154,7 @@ export function getTopicSummary(db, tagLimit = 10, decisionLimit = 5, scope) {
1148
1154
  topicKeywords,
1149
1155
  recentDecisions,
1150
1156
  activeScopes,
1151
- counts: { activeClaims, activeEpisodes, activeDecisions, activeTheories, activeInsights, activeModels, activeMemos },
1157
+ counts: { activeClaims, activeEpisodes, activeDecisions, activeTheories, activeInsights, activeModels, activeMemos, activeFiles },
1152
1158
  };
1153
1159
  }
1154
1160
  // === V9: Theory CRUD ===
@@ -2551,6 +2557,10 @@ export function updateUserIssue(db, id, updates) {
2551
2557
  setClauses.push("entries = @entries");
2552
2558
  values.entries = updates.entries;
2553
2559
  }
2560
+ if (updates.l1_embedding !== undefined) {
2561
+ setClauses.push("l1_embedding = @l1_embedding");
2562
+ values.l1_embedding = updates.l1_embedding;
2563
+ }
2554
2564
  if (updates.priority !== undefined) {
2555
2565
  setClauses.push("priority = @priority");
2556
2566
  values.priority = updates.priority;
@@ -2649,6 +2659,157 @@ export function getUserIssuesWithL1Embedding(db, scope, limit = 100) {
2649
2659
  }
2650
2660
  return db.prepare("SELECT * FROM user_issues WHERE status = 'open' AND l1_embedding IS NOT NULL ORDER BY updated_at DESC LIMIT ?").all(limit);
2651
2661
  }
2662
+ export function insertUserTopic(db, params) {
2663
+ const id = ulid();
2664
+ const now = new Date().toISOString();
2665
+ // device_id を自動取得(store_meta から)
2666
+ let deviceId = params.device_id ?? null;
2667
+ if (!deviceId) {
2668
+ const row = db.prepare("SELECT value FROM store_meta WHERE key = 'device_id'").get();
2669
+ deviceId = row?.value ?? null;
2670
+ }
2671
+ db.prepare(`
2672
+ INSERT INTO user_topics (id, title, summary, device_id, cwd, conversation_id, priority, tags, scope, search_summary, l1_embedding, source_tool, client_name, client_version, status, created_at, updated_at)
2673
+ VALUES (@id, @title, @summary, @device_id, @cwd, @conversation_id, @priority, @tags, @scope, @search_summary, @l1_embedding, @source_tool, @client_name, @client_version, 'active', @created_at, @updated_at)
2674
+ `).run({
2675
+ id,
2676
+ title: params.title,
2677
+ summary: params.summary,
2678
+ device_id: deviceId,
2679
+ cwd: params.cwd ?? null,
2680
+ conversation_id: params.conversation_id ?? null,
2681
+ priority: params.priority ?? "medium",
2682
+ tags: JSON.stringify(params.tags),
2683
+ scope: params.scope ?? "global",
2684
+ search_summary: params.search_summary ?? null,
2685
+ l1_embedding: params.l1_embedding ?? null,
2686
+ source_tool: params.source_tool ?? null,
2687
+ client_name: params.client_name ?? null,
2688
+ client_version: params.client_version ?? null,
2689
+ created_at: now,
2690
+ updated_at: now,
2691
+ });
2692
+ return db.prepare("SELECT * FROM user_topics WHERE id = ?").get(id);
2693
+ }
2694
+ export function getUserTopicById(db, id) {
2695
+ return db.prepare("SELECT * FROM user_topics WHERE id = ?").get(id);
2696
+ }
2697
+ export function updateUserTopic(db, id, updates) {
2698
+ const existing = getUserTopicById(db, id);
2699
+ if (!existing)
2700
+ return undefined;
2701
+ const setClauses = [];
2702
+ const values = { id };
2703
+ if (updates.title !== undefined) {
2704
+ setClauses.push("title = @title");
2705
+ values.title = updates.title;
2706
+ }
2707
+ if (updates.summary !== undefined) {
2708
+ setClauses.push("summary = @summary");
2709
+ values.summary = updates.summary;
2710
+ }
2711
+ if (updates.conversation_id !== undefined) {
2712
+ setClauses.push("conversation_id = @conversation_id");
2713
+ values.conversation_id = updates.conversation_id;
2714
+ }
2715
+ if (updates.cwd !== undefined) {
2716
+ setClauses.push("cwd = @cwd");
2717
+ values.cwd = updates.cwd;
2718
+ }
2719
+ if (updates.priority !== undefined) {
2720
+ setClauses.push("priority = @priority");
2721
+ values.priority = updates.priority;
2722
+ }
2723
+ if (updates.tags !== undefined) {
2724
+ setClauses.push("tags = @tags");
2725
+ values.tags = JSON.stringify(updates.tags);
2726
+ }
2727
+ if (updates.scope !== undefined) {
2728
+ setClauses.push("scope = @scope");
2729
+ values.scope = updates.scope;
2730
+ }
2731
+ if (updates.search_summary !== undefined) {
2732
+ setClauses.push("search_summary = @search_summary");
2733
+ values.search_summary = updates.search_summary;
2734
+ }
2735
+ if (updates.status !== undefined) {
2736
+ setClauses.push("status = @status");
2737
+ values.status = updates.status;
2738
+ }
2739
+ if (setClauses.length === 0)
2740
+ return existing;
2741
+ // summary/title 変更時は l1_embedding を無効化(陳腐化防止、backfill_embeddings で再生成)
2742
+ if (updates.summary !== undefined || updates.title !== undefined) {
2743
+ setClauses.push("l1_embedding = NULL");
2744
+ }
2745
+ setClauses.push("updated_at = @updated_at");
2746
+ values.updated_at = new Date().toISOString();
2747
+ db.prepare(`UPDATE user_topics SET ${setClauses.join(", ")} WHERE id = @id`).run(values);
2748
+ return db.prepare("SELECT * FROM user_topics WHERE id = ?").get(id);
2749
+ }
2750
+ export function listUserTopics(db, params) {
2751
+ const sanitized = params.query ? sanitizeFtsQuery(params.query) : null;
2752
+ const limit = params.limit ?? 20;
2753
+ const conditions = [];
2754
+ const values = { limit };
2755
+ if (params.status) {
2756
+ conditions.push("m.status = @status");
2757
+ values.status = params.status;
2758
+ }
2759
+ if (params.priority) {
2760
+ conditions.push("m.priority = @priority");
2761
+ values.priority = params.priority;
2762
+ }
2763
+ if (params.tag) {
2764
+ conditions.push("m.tags LIKE @tag_like");
2765
+ values.tag_like = `%"${params.tag}"%`;
2766
+ }
2767
+ if (params.scope) {
2768
+ conditions.push("(m.scope = @scope OR m.scope = 'global')");
2769
+ values.scope = params.scope;
2770
+ }
2771
+ // FTS5検索
2772
+ if (sanitized) {
2773
+ try {
2774
+ const ftsConditions = [...conditions];
2775
+ const ftsWhere = ftsConditions.length > 0 ? "AND " + ftsConditions.join(" AND ") : "";
2776
+ const ftsResults = db.prepare(`
2777
+ SELECT m.* FROM user_topics m
2778
+ JOIN user_topics_fts f ON m.rowid = f.rowid
2779
+ WHERE user_topics_fts MATCH @query ${ftsWhere}
2780
+ ORDER BY rank
2781
+ LIMIT @limit
2782
+ `).all({ ...values, query: sanitized });
2783
+ if (ftsResults.length > 0)
2784
+ return ftsResults;
2785
+ }
2786
+ catch { /* FTSエラー時はLIKEフォールバック */ }
2787
+ }
2788
+ // LIKEフォールバック or 全件取得
2789
+ if (params.query) {
2790
+ const likeParam = `%${params.query.slice(0, 100)}%`;
2791
+ conditions.push("(m.title LIKE @like_query OR m.summary LIKE @like_query OR m.search_summary LIKE @like_query)");
2792
+ values.like_query = likeParam;
2793
+ }
2794
+ const whereClause = conditions.length > 0 ? "WHERE " + conditions.join(" AND ") : "";
2795
+ return db.prepare(`
2796
+ SELECT m.* FROM user_topics m ${whereClause}
2797
+ ORDER BY m.updated_at DESC
2798
+ LIMIT @limit
2799
+ `).all(values);
2800
+ }
2801
+ export function updateUserTopicL1Embedding(db, id, l1_embedding) {
2802
+ db.prepare("UPDATE user_topics SET l1_embedding = @l1_embedding WHERE id = @id").run({ id, l1_embedding });
2803
+ }
2804
+ export function getUserTopicsWithoutL1Embedding(db, limit) {
2805
+ return db.prepare("SELECT * FROM user_topics WHERE status = 'active' AND l1_embedding IS NULL ORDER BY updated_at DESC LIMIT ?").all(limit);
2806
+ }
2807
+ export function getUserTopicsWithL1Embedding(db, scope, limit = 100) {
2808
+ if (scope) {
2809
+ return db.prepare("SELECT * FROM user_topics WHERE status = 'active' AND l1_embedding IS NOT NULL AND (scope = ? OR scope = 'global') ORDER BY updated_at DESC LIMIT ?").all(scope, limit);
2810
+ }
2811
+ return db.prepare("SELECT * FROM user_topics WHERE status = 'active' AND l1_embedding IS NOT NULL ORDER BY updated_at DESC LIMIT ?").all(limit);
2812
+ }
2652
2813
  /**
2653
2814
  * 横断検索(FTS5 + LIKEフォールバック)
2654
2815
  * unified_search_items テーブルを使い、全6エンティティを横断してFTS5検索する。
@@ -2662,11 +2823,13 @@ export function unifiedSearch(db, query, scope, entityTypes, limit) {
2662
2823
  scopeFilter = "AND (u.scope = @scope OR u.scope = 'global')";
2663
2824
  baseValues.scope = scope;
2664
2825
  }
2826
+ // entity_types 未指定時はデフォルト検索対象を使用(user_file はオプトイン型のため除外)
2827
+ const effectiveTypes = (entityTypes && entityTypes.length > 0) ? entityTypes : [...DEFAULT_SEARCH_ENTITY_TYPES];
2665
2828
  let typeFilter = "";
2666
- if (entityTypes && entityTypes.length > 0) {
2667
- const placeholders = entityTypes.map((_, i) => `@et${i}`).join(", ");
2829
+ {
2830
+ const placeholders = effectiveTypes.map((_, i) => `@et${i}`).join(", ");
2668
2831
  typeFilter = `AND u.entity_type IN (${placeholders})`;
2669
- entityTypes.forEach((t, i) => { baseValues[`et${i}`] = t; });
2832
+ effectiveTypes.forEach((t, i) => { baseValues[`et${i}`] = t; });
2670
2833
  }
2671
2834
  // 1. FTS5検索
2672
2835
  if (sanitized) {
@@ -2727,10 +2890,12 @@ export function getUnifiedSearchWithL1Embedding(db, scope, entityTypes, limit) {
2727
2890
  conditions.push("(scope = @scope OR scope = 'global')");
2728
2891
  values.scope = scope;
2729
2892
  }
2730
- if (entityTypes && entityTypes.length > 0) {
2731
- const placeholders = entityTypes.map((_, i) => `@et${i}`).join(", ");
2893
+ // entity_types 未指定時はデフォルト検索対象を使用(user_file はオプトイン型のため除外)
2894
+ const effectiveTypes = (entityTypes && entityTypes.length > 0) ? entityTypes : [...DEFAULT_SEARCH_ENTITY_TYPES];
2895
+ {
2896
+ const placeholders = effectiveTypes.map((_, i) => `@et${i}`).join(", ");
2732
2897
  conditions.push(`entity_type IN (${placeholders})`);
2733
- entityTypes.forEach((t, i) => { values[`et${i}`] = t; });
2898
+ effectiveTypes.forEach((t, i) => { values[`et${i}`] = t; });
2734
2899
  }
2735
2900
  const whereClause = conditions.length > 0
2736
2901
  ? "WHERE " + conditions.join(" AND ")
@@ -2834,6 +2999,8 @@ const ENTITY_TYPE_TO_TABLE = {
2834
2999
  user_memo: "user_memos",
2835
3000
  user_plan: "user_plans",
2836
3001
  user_issue: "user_issues",
3002
+ user_topic: "user_topics",
3003
+ user_file: "user_files",
2837
3004
  };
2838
3005
  /**
2839
3006
  * entity_id から entity_type を解決する(unified_search_items を使用)。
@@ -2982,4 +3149,108 @@ export function applyTombstones(db) {
2982
3149
  }
2983
3150
  return deleted;
2984
3151
  }
3152
+ // ============================================================
3153
+ // UserFile (File Vault)
3154
+ // ============================================================
3155
+ export function insertUserFile(db, params) {
3156
+ const id = ulid();
3157
+ const now = new Date().toISOString();
3158
+ db.prepare(`
3159
+ INSERT INTO user_files (id, title, description, original_filename, original_encoding, file_data, file_hash, file_size, tags, scope, search_summary, l1_embedding, source_tool, client_name, client_version, status, created_at, updated_at)
3160
+ VALUES (@id, @title, @description, @original_filename, @original_encoding, @file_data, @file_hash, @file_size, @tags, @scope, @search_summary, @l1_embedding, @source_tool, @client_name, @client_version, 'active', @created_at, @updated_at)
3161
+ `).run({
3162
+ id,
3163
+ title: params.title,
3164
+ description: params.description ?? null,
3165
+ original_filename: params.original_filename,
3166
+ original_encoding: params.original_encoding ?? "utf-8",
3167
+ file_data: params.file_data,
3168
+ file_hash: params.file_hash,
3169
+ file_size: params.file_size,
3170
+ tags: JSON.stringify(params.tags),
3171
+ scope: params.scope ?? "global",
3172
+ search_summary: params.search_summary ?? null,
3173
+ l1_embedding: params.l1_embedding ?? null,
3174
+ source_tool: params.source_tool ?? null,
3175
+ client_name: params.client_name ?? null,
3176
+ client_version: params.client_version ?? null,
3177
+ created_at: now,
3178
+ updated_at: now,
3179
+ });
3180
+ return db.prepare("SELECT * FROM user_files WHERE id = ?").get(id);
3181
+ }
3182
+ export function getUserFileById(db, id) {
3183
+ return db.prepare("SELECT * FROM user_files WHERE id = ?").get(id);
3184
+ }
3185
+ export function getUserFileByTitle(db, title) {
3186
+ // 完全一致を優先、なければ部分一致
3187
+ const exact = db.prepare("SELECT * FROM user_files WHERE title = ? AND status = 'active'").get(title);
3188
+ if (exact)
3189
+ return exact;
3190
+ return db.prepare("SELECT * FROM user_files WHERE title LIKE ? AND status = 'active' ORDER BY updated_at DESC LIMIT 1").get(`%${title}%`);
3191
+ }
3192
+ export function updateUserFile(db, id, updates) {
3193
+ const setClauses = [];
3194
+ const params = { id };
3195
+ if (updates.title !== undefined) {
3196
+ setClauses.push("title = @title");
3197
+ params.title = updates.title;
3198
+ }
3199
+ if (updates.description !== undefined) {
3200
+ setClauses.push("description = @description");
3201
+ params.description = updates.description;
3202
+ }
3203
+ if (updates.file_data !== undefined) {
3204
+ setClauses.push("file_data = @file_data");
3205
+ params.file_data = updates.file_data;
3206
+ }
3207
+ if (updates.file_hash !== undefined) {
3208
+ setClauses.push("file_hash = @file_hash");
3209
+ params.file_hash = updates.file_hash;
3210
+ }
3211
+ if (updates.file_size !== undefined) {
3212
+ setClauses.push("file_size = @file_size");
3213
+ params.file_size = updates.file_size;
3214
+ }
3215
+ if (updates.tags !== undefined) {
3216
+ setClauses.push("tags = @tags");
3217
+ params.tags = JSON.stringify(updates.tags);
3218
+ }
3219
+ if (updates.scope !== undefined) {
3220
+ setClauses.push("scope = @scope");
3221
+ params.scope = updates.scope;
3222
+ }
3223
+ if (updates.search_summary !== undefined) {
3224
+ setClauses.push("search_summary = @search_summary");
3225
+ params.search_summary = updates.search_summary;
3226
+ }
3227
+ if (updates.status !== undefined) {
3228
+ setClauses.push("status = @status");
3229
+ params.status = updates.status;
3230
+ }
3231
+ if (setClauses.length === 0)
3232
+ return getUserFileById(db, id);
3233
+ setClauses.push("updated_at = @updated_at");
3234
+ params.updated_at = new Date().toISOString();
3235
+ db.prepare(`UPDATE user_files SET ${setClauses.join(", ")} WHERE id = @id`).run(params);
3236
+ return getUserFileById(db, id);
3237
+ }
3238
+ export function listUserFiles(db, params) {
3239
+ const conditions = ["status = @status"];
3240
+ const bindParams = { status: params.status ?? "active" };
3241
+ if (params.scope) {
3242
+ conditions.push("(scope = @scope OR scope = 'global')");
3243
+ bindParams.scope = params.scope;
3244
+ }
3245
+ const sql = `SELECT id, title, description, original_filename, original_encoding, file_hash, file_size, tags, scope, search_summary, status, client_name, client_version, source_tool, created_at, updated_at FROM user_files WHERE ${conditions.join(" AND ")} ORDER BY updated_at DESC`;
3246
+ let rows = db.prepare(sql).all(bindParams);
3247
+ // タグフィルタリング(アプリケーション層)
3248
+ if (params.tags && params.tags.length > 0) {
3249
+ rows = rows.filter(r => {
3250
+ const rowTags = JSON.parse(r.tags);
3251
+ return params.tags.some(t => rowTags.includes(t));
3252
+ });
3253
+ }
3254
+ return rows;
3255
+ }
2985
3256
  //# sourceMappingURL=queries-core.js.map