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
|
@@ -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 | |
|