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.
Files changed (92) hide show
  1. package/CLAUDE.md +36 -0
  2. package/LICENSE +1 -1
  3. package/README.md +64 -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/auth/token.d.ts +7 -0
  10. package/dist/auth/token.js +14 -0
  11. package/dist/auth/token.js.map +1 -0
  12. package/dist/config/schema.js +1 -1
  13. package/dist/constants.d.ts +9 -9
  14. package/dist/constants.js +29 -43
  15. package/dist/constants.js.map +1 -1
  16. package/dist/database/archive.js +6 -6
  17. package/dist/database/groom.js +5 -5
  18. package/dist/database/groom.js.map +1 -1
  19. package/dist/database/queries-core.d.ts +109 -5
  20. package/dist/database/queries-core.js +546 -186
  21. package/dist/database/queries-core.js.map +1 -1
  22. package/dist/database/queries.d.ts +85 -5
  23. package/dist/database/queries.js +33 -0
  24. package/dist/database/queries.js.map +1 -1
  25. package/dist/database/schema.d.ts +1 -0
  26. package/dist/database/schema.js +2299 -406
  27. package/dist/database/schema.js.map +1 -1
  28. package/dist/embedding/embed-on-write.d.ts +7 -1
  29. package/dist/embedding/embed-on-write.js +8 -3
  30. package/dist/embedding/embed-on-write.js.map +1 -1
  31. package/dist/hook.js +17 -9
  32. package/dist/hook.js.map +1 -1
  33. package/dist/http-server.d.ts +6 -0
  34. package/dist/http-server.js +235 -0
  35. package/dist/http-server.js.map +1 -0
  36. package/dist/index.js +14 -3
  37. package/dist/index.js.map +1 -1
  38. package/dist/resources/config-guide-content.d.ts +1 -1
  39. package/dist/resources/config-guide-content.js +2 -2
  40. package/dist/server-core.d.ts +15 -0
  41. package/dist/server-core.js +113 -0
  42. package/dist/server-core.js.map +1 -0
  43. package/dist/server-instructions.js +27 -24
  44. package/dist/server-instructions.js.map +1 -1
  45. package/dist/server.d.ts +4 -1
  46. package/dist/server.js +9 -103
  47. package/dist/server.js.map +1 -1
  48. package/dist/setup.js +5 -6
  49. package/dist/setup.js.map +1 -1
  50. package/dist/store/federation.d.ts +12 -1
  51. package/dist/store/federation.js +38 -0
  52. package/dist/store/federation.js.map +1 -1
  53. package/dist/store/sync-manager.d.ts +1 -1
  54. package/dist/store/sync-manager.js +9 -9
  55. package/dist/tools/claim-tools.d.ts +1 -1
  56. package/dist/tools/claim-tools.js +30 -17
  57. package/dist/tools/claim-tools.js.map +1 -1
  58. package/dist/tools/decision-tools.d.ts +1 -1
  59. package/dist/tools/decision-tools.js +23 -7
  60. package/dist/tools/decision-tools.js.map +1 -1
  61. package/dist/tools/episode-tools.d.ts +1 -1
  62. package/dist/tools/episode-tools.js +28 -7
  63. package/dist/tools/episode-tools.js.map +1 -1
  64. package/dist/tools/file-tools.d.ts +3 -0
  65. package/dist/tools/file-tools.js +350 -0
  66. package/dist/tools/file-tools.js.map +1 -0
  67. package/dist/tools/get-tools.d.ts +1 -1
  68. package/dist/tools/get-tools.js +39 -5
  69. package/dist/tools/get-tools.js.map +1 -1
  70. package/dist/tools/helpers.d.ts +40 -0
  71. package/dist/tools/helpers.js +69 -0
  72. package/dist/tools/helpers.js.map +1 -1
  73. package/dist/tools/knowledge-tools.d.ts +1 -1
  74. package/dist/tools/knowledge-tools.js +38 -6
  75. package/dist/tools/knowledge-tools.js.map +1 -1
  76. package/dist/tools/maintenance-tools.d.ts +1 -1
  77. package/dist/tools/maintenance-tools.js +95 -52
  78. package/dist/tools/maintenance-tools.js.map +1 -1
  79. package/dist/tools/memo-tools.d.ts +7 -11
  80. package/dist/tools/memo-tools.js +509 -309
  81. package/dist/tools/memo-tools.js.map +1 -1
  82. package/dist/tools/query-tools.d.ts +1 -1
  83. package/dist/tools/query-tools.js +266 -242
  84. package/dist/tools/query-tools.js.map +1 -1
  85. package/dist/types.d.ts +513 -76
  86. package/dist/types.js +185 -33
  87. package/dist/types.js.map +1 -1
  88. package/misc/20260316_110841_groom-recipe.md +483 -0
  89. package/misc/20260316_xaml-testing-library-recipe.md +817 -0
  90. package/misc/20260331_remote-transport-recipe.md +316 -0
  91. package/package.json +4 -2
  92. package/scripts/update-license-version.sh +7 -0
@@ -0,0 +1,483 @@
1
+ # 蒸留機能の実用化: /groom + アーカイブ + 検索2段階化
2
+
3
+ ## Context
4
+
5
+ Episode 112件、Decision 84件が溜まっているが、蒸留(Insight/Theory/Model への統合)は Insight 3件、Theory 5件のみ。蒸留のインフラ(groom_episodes, mark_groomed, watashi_store_knowledge)は実装済みだが、ワークフローとして回す仕組みがない。蒸留済みレコードのアーカイブ機能も未実装。
6
+
7
+ **目的**: `/groom` コマンドで蒸留を実行 → 古いレコードをアーカイブ → 検索ノイズを減らしつつ必要時に archived も引ける状態にする。
8
+
9
+ ---
10
+
11
+ ## Step 1: Decision に `archived` ステータスを追加
12
+
13
+ Episode は既に `archived` を持つが Decision にはない。蒸留後のアーカイブに必要。
14
+
15
+ ### `src/constants.ts`
16
+
17
+ ```typescript
18
+ // 変更前:
19
+ export const DECISION_STATUSES = [
20
+ "active", // 有効
21
+ "reversed", // 撤回済み
22
+ "obsolete", // 廃止
23
+ "promoted", // 別Storeへ昇格済み
24
+ ] as const;
25
+
26
+ // 変更後:
27
+ export const DECISION_STATUSES = [
28
+ "active", // 有効
29
+ "reversed", // 撤回済み
30
+ "obsolete", // 廃止
31
+ "archived", // アーカイブ済み(蒸留済み)
32
+ "promoted", // 別Storeへ昇格済み
33
+ ] as const;
34
+ ```
35
+
36
+ ### `src/types.ts`
37
+
38
+ `ManageClaimSchema` の `decision_status` enum に `"archived"` を追加:
39
+
40
+ ```typescript
41
+ // 変更前:
42
+ decision_status: z.enum(["reversed", "obsolete"]).optional(),
43
+
44
+ // 変更後:
45
+ decision_status: z.enum(["reversed", "obsolete", "archived"]).optional(),
46
+ ```
47
+
48
+ > マイグレーション不要(status カラムは TEXT 型、CHECK 制約なし)
49
+
50
+ ---
51
+
52
+ ## Step 2: アーカイブ関数の実装
53
+
54
+ ### `src/database/queries-core.ts` — 新関数2つ
55
+
56
+ ```typescript
57
+ /** Episode をアーカイブ(蒸留済み + 最新N件を除外) */
58
+ export function archiveEpisodes(db: Database.Database, params: {
59
+ episodeIds?: string[];
60
+ keepLatest?: number;
61
+ scope?: string;
62
+ }): { archivedIds: string[]; archivedCount: number } {
63
+ const keepLatest = params.keepLatest ?? 20;
64
+
65
+ if (params.episodeIds && params.episodeIds.length > 0) {
66
+ // 個別指定モード: 指定IDのみアーカイブ
67
+ const updated: string[] = [];
68
+ const stmt = db.prepare(
69
+ "UPDATE episodes SET status = 'archived', updated_at = @now WHERE id = @id AND status = 'active'"
70
+ );
71
+ const now = new Date().toISOString();
72
+ for (const id of params.episodeIds) {
73
+ const result = stmt.run({ id, now });
74
+ if (result.changes > 0) updated.push(id);
75
+ }
76
+ return { archivedIds: updated, archivedCount: updated.length };
77
+ }
78
+
79
+ // 自動モード: groomed_at IS NOT NULL かつ最新 keepLatest 件を除外
80
+ const scopeFilter = params.scope ? " AND scope = @scope" : "";
81
+ const scopeValues = params.scope ? { scope: params.scope } : {};
82
+
83
+ // 最新 keepLatest 件の ID を取得(保護対象)
84
+ const protectedIds = db.prepare(`
85
+ SELECT id FROM episodes
86
+ WHERE status = 'active'${scopeFilter}
87
+ ORDER BY created_at DESC
88
+ LIMIT @keepLatest
89
+ `).all({ ...scopeValues, keepLatest }) as { id: string }[];
90
+
91
+ const protectedSet = new Set(protectedIds.map(r => r.id));
92
+
93
+ // groomed_at IS NOT NULL かつ保護対象外をアーカイブ
94
+ const candidates = db.prepare(`
95
+ SELECT id FROM episodes
96
+ WHERE status = 'active' AND groomed_at IS NOT NULL${scopeFilter}
97
+ `).all(scopeValues) as { id: string }[];
98
+
99
+ const toArchive = candidates.filter(r => !protectedSet.has(r.id));
100
+ const now = new Date().toISOString();
101
+ const stmt = db.prepare(
102
+ "UPDATE episodes SET status = 'archived', updated_at = @now WHERE id = @id"
103
+ );
104
+ for (const r of toArchive) {
105
+ stmt.run({ id: r.id, now });
106
+ }
107
+
108
+ return { archivedIds: toArchive.map(r => r.id), archivedCount: toArchive.length };
109
+ }
110
+
111
+ /** Decision をアーカイブ(最新N件を除外) */
112
+ export function archiveDecisions(db: Database.Database, params: {
113
+ decisionIds?: string[];
114
+ keepLatest?: number;
115
+ scope?: string;
116
+ }): { archivedIds: string[]; archivedCount: number } {
117
+ const keepLatest = params.keepLatest ?? 20;
118
+
119
+ if (params.decisionIds && params.decisionIds.length > 0) {
120
+ const updated: string[] = [];
121
+ const stmt = db.prepare(
122
+ "UPDATE decisions SET status = 'archived', updated_at = @now WHERE id = @id AND status = 'active'"
123
+ );
124
+ const now = new Date().toISOString();
125
+ for (const id of params.decisionIds) {
126
+ const result = stmt.run({ id, now });
127
+ if (result.changes > 0) updated.push(id);
128
+ }
129
+ return { archivedIds: updated, archivedCount: updated.length };
130
+ }
131
+
132
+ // 自動モード: 最新 keepLatest 件を除外してアーカイブ
133
+ const scopeFilter = params.scope ? " AND scope = @scope" : "";
134
+ const scopeValues = params.scope ? { scope: params.scope } : {};
135
+
136
+ const protectedIds = db.prepare(`
137
+ SELECT id FROM decisions
138
+ WHERE status = 'active'${scopeFilter}
139
+ ORDER BY created_at DESC
140
+ LIMIT @keepLatest
141
+ `).all({ ...scopeValues, keepLatest }) as { id: string }[];
142
+
143
+ const protectedSet = new Set(protectedIds.map(r => r.id));
144
+
145
+ const candidates = db.prepare(`
146
+ SELECT id FROM decisions
147
+ WHERE status = 'active'${scopeFilter}
148
+ `).all(scopeValues) as { id: string }[];
149
+
150
+ const toArchive = candidates.filter(r => !protectedSet.has(r.id));
151
+ const now = new Date().toISOString();
152
+ const stmt = db.prepare(
153
+ "UPDATE decisions SET status = 'archived', updated_at = @now WHERE id = @id"
154
+ );
155
+ for (const r of toArchive) {
156
+ stmt.run({ id: r.id, now });
157
+ }
158
+
159
+ return { archivedIds: toArchive.map(r => r.id), archivedCount: toArchive.length };
160
+ }
161
+ ```
162
+
163
+ ### `src/database/queries.ts` — ラッパー追加
164
+
165
+ ```typescript
166
+ export function archiveEpisodes(params: {
167
+ episodeIds?: string[];
168
+ keepLatest?: number;
169
+ scope?: string;
170
+ }): { archivedIds: string[]; archivedCount: number } {
171
+ return core.archiveEpisodes(getDatabase(), params);
172
+ }
173
+
174
+ export function archiveDecisions(params: {
175
+ decisionIds?: string[];
176
+ keepLatest?: number;
177
+ scope?: string;
178
+ }): { archivedIds: string[]; archivedCount: number } {
179
+ return core.archiveDecisions(getDatabase(), params);
180
+ }
181
+ ```
182
+
183
+ ---
184
+
185
+ ## Step 3: フェデレーション対応
186
+
187
+ ### `src/store/federation.ts` — 新関数
188
+
189
+ 既存の `federatedMarkEpisodesGroomed` のパターンに倣う。default store のみ対象で十分(マルチストアのアーカイブは現時点では不要)。
190
+
191
+ ```typescript
192
+ export function federatedArchiveEpisodes(
193
+ manager: StoreManager,
194
+ context: ActiveContext,
195
+ params: { episodeIds?: string[]; keepLatest?: number; scope?: string },
196
+ ): { archivedIds: string[]; archivedCount: number } {
197
+ const db = manager.getStore(context.activeStore);
198
+ return archiveEpisodes(db, params);
199
+ }
200
+
201
+ export function federatedArchiveDecisions(
202
+ manager: StoreManager,
203
+ context: ActiveContext,
204
+ params: { decisionIds?: string[]; keepLatest?: number; scope?: string },
205
+ ): { archivedIds: string[]; archivedCount: number } {
206
+ const db = manager.getStore(context.activeStore);
207
+ return archiveDecisions(db, params);
208
+ }
209
+ ```
210
+
211
+ ---
212
+
213
+ ## Step 4: maintenance-tools にアクションハンドラ追加
214
+
215
+ ### `src/types.ts` — MaintainSchema 拡張
216
+
217
+ `action` enum に追加:
218
+ ```typescript
219
+ "archive_episodes", "archive_decisions"
220
+ ```
221
+
222
+ 新パラメータ追加:
223
+ ```typescript
224
+ archive_entity_ids: z.array(z.string()).optional().describe("(archive_episodes/archive_decisions) 個別指定でアーカイブ"),
225
+ archive_keep_latest: z.number().int().min(0).default(20).optional().describe("(archive_episodes/archive_decisions) 最新N件を残す"),
226
+ ```
227
+
228
+ ### `src/tools/maintenance-tools.ts` — ハンドラ追加
229
+
230
+ ```typescript
231
+ if (params.action === "archive_episodes") {
232
+ if (params.dry_run) {
233
+ // dry_run: アーカイブ対象件数のみ返す(実際には更新しない)
234
+ // → 対象候補を SELECT で取得して件数を返す
235
+ }
236
+ const result = federatedArchiveEpisodes(manager, context, {
237
+ episodeIds: params.archive_entity_ids,
238
+ keepLatest: params.archive_keep_latest ?? 20,
239
+ scope: params.scope,
240
+ });
241
+
242
+ // 監査ログ
243
+ if (result.archivedCount > 0) {
244
+ insertAuditLog({
245
+ operation: "archive",
246
+ entity_type: "episode",
247
+ entity_id: result.archivedIds.join(","),
248
+ summary: `${result.archivedCount}件のEpisodeをアーカイブ`,
249
+ provenance,
250
+ });
251
+ }
252
+ notifyWrite();
253
+ return ok(result);
254
+ }
255
+
256
+ // archive_decisions も同様
257
+ ```
258
+
259
+ > **dry_run 対応**: `params.dry_run === true` の場合はトランザクション内で SELECT のみ実行し、対象件数とIDリストを返す。UPDATE はしない。
260
+
261
+ ---
262
+
263
+ ## Step 5: 検索の2段階フォールバック
264
+
265
+ ### 方針
266
+
267
+ - unified_search_items は active レコードのみ保持(トリガー設計)
268
+ - query モードで結果0件の場合、ソーステーブルの FTS を直接クエリして archived を含む再検索
269
+ - archived ヒット時はレスポンスに注記を付与
270
+
271
+ ### `src/database/queries-core.ts` — archived 含む検索関数
272
+
273
+ ```typescript
274
+ /**
275
+ * archived を含む FTS 検索(フォールバック用)
276
+ * unified_search_items ではなくソーステーブルの FTS を直接クエリ
277
+ */
278
+ export function searchIncludingArchived(db: Database.Database, params: {
279
+ query: string;
280
+ scope?: string;
281
+ limit: number;
282
+ }): UnifiedSearchRow[] {
283
+ const results: UnifiedSearchRow[] = [];
284
+
285
+ // 検索対象テーブル(FTS テーブル名、ソーステーブル名、entity_type)
286
+ const targets = [
287
+ { fts: "episodes_fts", table: "episodes", type: "episode" },
288
+ { fts: "decisions_fts", table: "decisions", type: "decision" },
289
+ { fts: "claims_fts", table: "claims", type: "claim" },
290
+ { fts: "theories_fts", table: "theories", type: "theory" },
291
+ { fts: "insights_fts", table: "insights", type: "insight" },
292
+ { fts: "models_fts", table: "models", type: "model" },
293
+ ] as const;
294
+
295
+ for (const target of targets) {
296
+ try {
297
+ const scopeFilter = params.scope
298
+ ? ` AND (t.scope = '${params.scope}' OR t.scope = 'global')`
299
+ : "";
300
+ const rows = db.prepare(`
301
+ SELECT t.id as entity_id, '${target.type}' as entity_type,
302
+ t.title as title_summary, t.scope, t.status,
303
+ t.created_at, t.updated_at
304
+ FROM ${target.table} t
305
+ JOIN ${target.fts} f ON f.rowid = t.rowid
306
+ WHERE ${target.fts} MATCH @query${scopeFilter}
307
+ ORDER BY rank
308
+ LIMIT @limit
309
+ `).all({ query: params.query, limit: params.limit }) as UnifiedSearchRow[];
310
+
311
+ results.push(...rows);
312
+ } catch {
313
+ // FTS テーブル未存在等はスキップ
314
+ }
315
+ }
316
+
317
+ // スコア順でソートして limit 件に制限
318
+ return results.slice(0, params.limit);
319
+ }
320
+ ```
321
+
322
+ > 注意: FTS MATCH のクエリ構文は unified_search_fts と同じ。ソーステーブルの FTS カラム構成は異なるため、必要に応じて調整。
323
+
324
+ ### `src/tools/query-tools.ts` — フォールバック組み込み
325
+
326
+ query モードのハンドラで結果が0件だった場合:
327
+
328
+ ```typescript
329
+ // 既存: federatedUnifiedSearch で検索
330
+ const results = federatedUnifiedSearch(manager, context, searchParams);
331
+
332
+ if (results.length === 0 && params.query) {
333
+ // フォールバック: archived 含む再検索
334
+ const archivedResults = federatedSearchIncludingArchived(manager, context, {
335
+ query: params.query,
336
+ scope: params.scope,
337
+ limit: params.limit ?? 20,
338
+ });
339
+
340
+ if (archivedResults.length > 0) {
341
+ // archived からのヒットを返す
342
+ return {
343
+ content: [{
344
+ type: "text",
345
+ text: JSON.stringify({
346
+ query: params.query,
347
+ results: archivedResults,
348
+ total_count: archivedResults.length,
349
+ note: "アクティブなレコードでは見つかりませんでした。アーカイブ済みレコードから検出しました。",
350
+ }, null, 2),
351
+ }],
352
+ };
353
+ }
354
+ }
355
+ ```
356
+
357
+ ### `src/store/federation.ts` — フェデレーション対応
358
+
359
+ ```typescript
360
+ export function federatedSearchIncludingArchived(
361
+ manager: StoreManager,
362
+ context: ActiveContext,
363
+ params: { query: string; scope?: string; limit: number },
364
+ ): UnifiedSearchRow[] {
365
+ // 各ストアで searchIncludingArchived を実行し結果をマージ
366
+ // 既存の federatedUnifiedSearch のパターンに倣う
367
+ }
368
+ ```
369
+
370
+ ---
371
+
372
+ ## Step 6: /groom スキルの作成
373
+
374
+ ### `cowork-plugin/skills/groom/SKILL.md` — 新規作成
375
+
376
+ ```markdown
377
+ ---
378
+ description: >
379
+ 記録の棚卸し(蒸留)を行う。未処理のEpisode/Decisionからパターンを抽出し、
380
+ Insight/Theory/Modelとして蒸留する。蒸留済みの古いレコードはアーカイブする。
381
+ ---
382
+
383
+ 記録の棚卸し(蒸留)を行います。
384
+
385
+ ## 引数
386
+ - `$ARGUMENTS`: (オプション)蒸留したいトピックやスコープ
387
+
388
+ ## 手順
389
+
390
+ ### Step 1: 素材の収集
391
+ 以下のツールを並行して呼び出し、蒸留対象のデータを収集する:
392
+ - `watashi_maintain(action='groom_episodes')`: 未グルーミングのEpisode一覧
393
+ - `watashi_search(entity_types=["decision"], limit=50)`: active Decision一覧
394
+ - `watashi_search(entity_types=["insight"], limit=20)`: 既存Insight(重複回避用)
395
+
396
+ ### Step 2: パターン分析と蒸留
397
+ 収集したEpisode/Decisionから以下を分析:
398
+
399
+ 1. **繰り返しテーマの検出**: 複数のEpisodeに共通するトピック・問題パターン
400
+ 2. **原則の統合**: 個別Episodeの l2_principles を横断分析し、汎用的な原則を抽出
401
+ 3. **既存Insightとの照合**: 重複・矛盾がないか確認
402
+
403
+ 分析結果に基づき生成:
404
+ - **Insight**: 具体的で再利用可能な知見(1つのパターン)
405
+ → `watashi_store_knowledge(kind='insight', supporting_episode_ids=[...])`
406
+ - **Theory**: 体系的なフレームワーク(複数パターンの統合)
407
+ → `watashi_store_knowledge(kind='theory', supporting_episode_ids=[...])`
408
+
409
+ l2_trigger_conditions(いつ適用するか)と l2_resolution_steps(具体的に何をするか)を必ず記入すること。
410
+ 「まとめただけ」にならないよう、「次に同じ状況になったときの行動が変わる」レベルの知見を目指す。
411
+
412
+ ### Step 3: グルーミング済みマーク
413
+ 蒸留に使用したEpisodeをマーク:
414
+ - `watashi_maintain(action='mark_groomed', episode_ids=[...])`
415
+
416
+ ### Step 4: アーカイブ(ユーザー承認制)
417
+ 蒸留済みの古いレコードをアーカイブ(最新20件は維持):
418
+ 1. `watashi_maintain(action='archive_episodes', archive_keep_latest=20, dry_run=true)` でプレビュー
419
+ 2. 件数と対象をユーザーに提示し、承認を得る
420
+ 3. 承認後 `dry_run=false` で実行
421
+ 4. Decision も同様に実行
422
+
423
+ ### Step 5: サマリー報告
424
+
425
+ ## 出力形式
426
+
427
+ ```
428
+ ## 棚卸しサマリー
429
+
430
+ ### 分析した素材
431
+ - Episode: X件(うち未グルーミング: Y件)
432
+ - Decision: Z件
433
+
434
+ ### 蒸留結果
435
+ - 新規Insight: N件
436
+ - [タイトル](出典: [Episode ID一覧])
437
+ - 新規Theory: N件(該当があれば)
438
+
439
+ ### アーカイブ
440
+ - Episode: X件をアーカイブ(最新20件は維持)
441
+ - Decision: Y件をアーカイブ(最新20件は維持)
442
+ ```
443
+
444
+ ## 注意事項
445
+ - 素材が5件未満の場合は蒸留を見送り、蓄積を促す
446
+ - アーカイブは必ず dry_run でプレビュー → ユーザー承認後に実行
447
+ - supporting_episode_ids を必ず設定する
448
+ - 既存のInsight/Theoryと重複する場合は統合を提案する
449
+ ```
450
+
451
+ ---
452
+
453
+ ## 実装順序
454
+
455
+ 1. `src/constants.ts` + `src/types.ts` — Decision archived 追加 + MaintainSchema 拡張
456
+ 2. `src/database/queries-core.ts` + `src/database/queries.ts` — アーカイブ関数 + archived 検索関数
457
+ 3. `src/store/federation.ts` — フェデレーション対応
458
+ 4. `src/tools/maintenance-tools.ts` — archive_episodes / archive_decisions ハンドラ
459
+ 5. `src/tools/query-tools.ts` — 検索2段階フォールバック
460
+ 6. `cowork-plugin/skills/groom/SKILL.md` — /groom スキル
461
+ 7. テスト追加
462
+
463
+ ## 検証方法
464
+
465
+ 1. `npm run build` でコンパイル通過
466
+ 2. `npm test` で既存839テスト通過
467
+ 3. 新規テスト:
468
+ - Episode/Decision の archive → status 変更確認
469
+ - archived レコードが unified_search_items から除外されることを確認
470
+ - 検索2段階: active で0件 → archived からヒットすることを確認
471
+ 4. 手動確認: Claude Code 再起動後、`/groom` でワークフローが動作するか
472
+
473
+ ## 既存コードの参考箇所
474
+
475
+ | 用途 | ファイル | 関数/行 |
476
+ |------|---------|---------|
477
+ | groomed Episode 取得 | queries-core.ts | `listUngroomedEpisodes` (L1235) |
478
+ | groomed マーク | queries-core.ts | `markEpisodesGroomed` (L1310) |
479
+ | Decision ステータス変更 | queries-core.ts | `updateDecisionStatus` |
480
+ | Knowledge 生成 | knowledge-tools.ts | `watashi_store_knowledge` |
481
+ | フェデレーション パターン | federation.ts | `federatedMarkEpisodesGroomed` |
482
+ | unified_search トリガー | schema.ts | L2032-2065(INSERT/UPDATE/DELETE トリガー) |
483
+ | 既存 Skill フォーマット | cowork-plugin/skills/reflect/SKILL.md | |