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,143 +1,57 @@
1
- import { StoreUserMemoSchema, UpdateUserMemoSchema, } from "../types.js";
2
- import { insertUserMemo, updateUserMemo, insertUserPlan, updateUserPlan, insertUserIssue, updateUserIssue, getUserIssueById, insertAuditLog, } from "../database/queries.js";
1
+ import { StoreUserMemoSchema, UpdateUserMemoSchema, StoreUserPlanSchema, UpdateUserPlanSchema, CheckUserPlanSchema, StoreUserIssueSchema, UpdateUserIssueSchema, CommentUserIssueSchema, StoreUserTopicSchema, UpdateUserTopicSchema, } from "../types.js";
2
+ import { insertUserMemo, updateUserMemo, insertUserPlan, updateUserPlan, getUserPlanById, insertUserIssue, updateUserIssue, getUserIssueById, insertUserTopic, updateUserTopic, insertAuditLog, } from "../database/queries.js";
3
3
  import { buildProvenance } from "../provenance.js";
4
4
  import { getDatabase } from "../database/connection.js";
5
5
  import { getStoreManager, notifyWrite } from "../store/session-state.js";
6
6
  import { getClientPolicy, checkScopeWritePolicy, buildPolicyErrorResponse } from "../policy.js";
7
7
  import { applyLint, getLintMode, buildLintBlockResponse } from "../lint.js";
8
8
  import { withLintWarnings } from "./helpers.js";
9
- /**
10
- * UserMemo関連ツール(store / update)を登録
11
- */
12
- export function registerUserMemoTools(server) {
13
- // watashi_store_user_memo: メモ・作業計画・課題を記録
14
- server.tool("watashi_store_user_memo", "Store memo/plan/issue/discussion. Specify kind.", StoreUserMemoSchema.shape, async (params, extra) => {
15
- const lint = applyLint(params, getLintMode());
9
+ // ============================================================
10
+ // ヘルパー
11
+ // ============================================================
12
+ function policyGuard(scope) {
13
+ const policy = getClientPolicy(getStoreManager().getConfig());
14
+ return checkScopeWritePolicy(policy, scope);
15
+ }
16
+ function lintGuard(params) {
17
+ const lint = applyLint(params, getLintMode());
18
+ return lint;
19
+ }
20
+ function jsonResult(data) {
21
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
22
+ }
23
+ function errorResult(msg) {
24
+ return { content: [{ type: "text", text: JSON.stringify({ success: false, error: msg }) }], isError: true };
25
+ }
26
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
27
+ function stripEmbedding(row) {
28
+ const { l1_embedding: _, ...rest } = row;
29
+ return rest;
30
+ }
31
+ function parseTags(tags) {
32
+ return JSON.parse(tags);
33
+ }
34
+ // ============================================================
35
+ // ツール登録
36
+ // ============================================================
37
+ export function registerUserMemoTools(server, toolPrefix = "watashi_") {
38
+ // ──────────────────────────────────────────────
39
+ // UserMemo: store / update
40
+ // ──────────────────────────────────────────────
41
+ server.tool(`${toolPrefix}store_user_memo`, "Store a reference memo (knowledge, procedure, external info). Append-only notes.", StoreUserMemoSchema.shape, async (params, extra) => {
42
+ const lint = lintGuard(params);
16
43
  if (lint.blocked)
17
44
  return buildLintBlockResponse(lint.warnings);
18
- const policy = getClientPolicy(getStoreManager().getConfig());
19
- const policyCheck = checkScopeWritePolicy(policy, params.scope ?? "global");
20
- if (!policyCheck.allowed)
21
- return buildPolicyErrorResponse(policyCheck.reason);
45
+ const scopeCheck = policyGuard(params.scope ?? "global");
46
+ if (!scopeCheck.allowed)
47
+ return buildPolicyErrorResponse(scopeCheck.reason);
22
48
  const provenance = buildProvenance(extra.sessionId);
23
- const kind = params.kind ?? "memo";
24
- // === kind='plan': 作業計画 ===
25
- if (kind === "plan") {
26
- let l1_embedding = null;
27
- try {
28
- const { generateUserPlanEmbedding } = await import("../embedding/embed-on-write.js");
29
- l1_embedding = await generateUserPlanEmbedding({
30
- title: params.title,
31
- l1_content: params.l1_content,
32
- });
33
- }
34
- catch { /* embedding生成失敗はスキップ */ }
35
- const plan = getDatabase().transaction(() => {
36
- const p = insertUserPlan({
37
- title: params.title,
38
- l1_content: params.l1_content,
39
- usage_policy: params.usage_policy ?? "auto",
40
- tags: params.tags ?? [],
41
- scope: params.scope ?? "global",
42
- search_summary: params.search_summary,
43
- user_input: params.user_input,
44
- l1_embedding,
45
- source_tool: params.source_tool,
46
- client_name: provenance.client_name,
47
- client_version: provenance.client_version,
48
- });
49
- insertAuditLog({
50
- operation: "create",
51
- entity_type: "user_plan",
52
- entity_id: p.id,
53
- summary: `UserPlan: ${p.title}`,
54
- provenance,
55
- source_tool: params.source_tool,
56
- });
57
- return p;
58
- })();
59
- notifyWrite();
60
- return {
61
- content: [{
62
- type: "text",
63
- text: JSON.stringify(withLintWarnings({
64
- success: true,
65
- message: `作業計画を記録しました: ${plan.title}`,
66
- plan: {
67
- ...plan,
68
- tags: JSON.parse(plan.tags),
69
- l1_embedding: undefined,
70
- },
71
- }, lint.warnings), null, 2),
72
- }],
73
- };
74
- }
75
- // === kind='issue' or 'discussion': 課題・議論(user_issues テーブルを共用) ===
76
- if (kind === "issue" || kind === "discussion") {
77
- let l1_embedding = null;
78
- try {
79
- const { generateUserIssueEmbedding } = await import("../embedding/embed-on-write.js");
80
- l1_embedding = await generateUserIssueEmbedding({
81
- title: params.title,
82
- l1_content: params.l1_content,
83
- });
84
- }
85
- catch { /* embedding生成失敗はスキップ */ }
86
- const issue = getDatabase().transaction(() => {
87
- const i = insertUserIssue({
88
- title: params.title,
89
- l1_content: params.l1_content,
90
- kind,
91
- priority: params.priority ?? "medium",
92
- usage_policy: params.usage_policy ?? "on_request",
93
- tags: params.tags ?? [],
94
- scope: params.scope ?? "global",
95
- search_summary: params.search_summary,
96
- user_input: params.user_input,
97
- l1_embedding,
98
- source_tool: params.source_tool,
99
- client_name: provenance.client_name,
100
- client_version: provenance.client_version,
101
- });
102
- insertAuditLog({
103
- operation: "create",
104
- entity_type: "user_issue",
105
- entity_id: i.id,
106
- summary: `${kind === "discussion" ? "Discussion" : "UserIssue"}: ${i.title}`,
107
- provenance,
108
- source_tool: params.source_tool,
109
- });
110
- return i;
111
- })();
112
- notifyWrite();
113
- const kindLabel = kind === "discussion" ? "議論" : "課題";
114
- return {
115
- content: [{
116
- type: "text",
117
- text: JSON.stringify(withLintWarnings({
118
- success: true,
119
- message: `${kindLabel}を記録しました: ${issue.title}`,
120
- issue: {
121
- ...issue,
122
- tags: JSON.parse(issue.tags),
123
- entries: JSON.parse(issue.entries),
124
- l1_embedding: undefined,
125
- },
126
- }, lint.warnings), null, 2),
127
- }],
128
- };
129
- }
130
- // === kind='memo': メモ(既存動作) ===
131
49
  let l1_embedding = null;
132
50
  try {
133
51
  const { generateUserMemoEmbedding } = await import("../embedding/embed-on-write.js");
134
- l1_embedding = await generateUserMemoEmbedding({
135
- title: params.title,
136
- l1_content: params.l1_content,
137
- });
52
+ l1_embedding = await generateUserMemoEmbedding({ title: params.title, l1_content: params.l1_content });
138
53
  }
139
- catch { /* embedding生成失敗はスキップ */ }
140
- // 2026-02-28 修正: insertUserMemo + auditLog をトランザクションで一括化(並行アクセス安全性)
54
+ catch { /* skip */ }
141
55
  const memo = getDatabase().transaction(() => {
142
56
  const m = insertUserMemo({
143
57
  title: params.title,
@@ -153,167 +67,37 @@ export function registerUserMemoTools(server) {
153
67
  client_version: provenance.client_version,
154
68
  });
155
69
  insertAuditLog({
156
- operation: "create",
157
- entity_type: "user_memo",
158
- entity_id: m.id,
159
- summary: `UserMemo: ${m.title}`,
160
- provenance,
161
- source_tool: params.source_tool,
70
+ operation: "create", entity_type: "user_memo", entity_id: m.id,
71
+ summary: `UserMemo: ${m.title}`, provenance, source_tool: params.source_tool,
162
72
  });
163
73
  return m;
164
74
  })();
165
75
  notifyWrite();
166
- return {
167
- content: [{
168
- type: "text",
169
- text: JSON.stringify(withLintWarnings({
170
- success: true,
171
- message: `メモを記録しました: ${memo.title}`,
172
- memo: {
173
- ...memo,
174
- tags: JSON.parse(memo.tags),
175
- l1_embedding: undefined,
176
- },
177
- }, lint.warnings), null, 2),
178
- }],
179
- };
76
+ return jsonResult(withLintWarnings({
77
+ success: true,
78
+ message: `メモを記録しました: ${memo.title}`,
79
+ memo: { ...stripEmbedding(memo), tags: parseTags(memo.tags) },
80
+ }, lint.warnings));
180
81
  });
181
- // watashi_update_user_memo: メモ・作業計画・課題を更新
182
- server.tool("watashi_update_user_memo", "Update memo/plan/issue/discussion by ID.", UpdateUserMemoSchema.shape, async (params, extra) => {
183
- const lint = applyLint(params, getLintMode());
82
+ server.tool(`${toolPrefix}update_user_memo`, "Update a memo (title, content, status, tags, etc.).", UpdateUserMemoSchema.shape, async (params, extra) => {
83
+ const lint = lintGuard(params);
184
84
  if (lint.blocked)
185
85
  return buildLintBlockResponse(lint.warnings);
186
- const provenance = buildProvenance(extra.sessionId);
187
- const kind = params.kind ?? "memo";
188
- // === kind='plan': 作業計画更新 ===
189
- if (kind === "plan") {
190
- const updates = {};
191
- if (params.title !== undefined)
192
- updates.title = params.title;
193
- if (params.l1_content !== undefined)
194
- updates.l1_content = params.l1_content;
195
- if (params.usage_policy !== undefined)
196
- updates.usage_policy = params.usage_policy;
197
- if (params.tags !== undefined)
198
- updates.tags = params.tags;
199
- if (params.scope !== undefined)
200
- updates.scope = params.scope;
201
- if (params.search_summary !== undefined)
202
- updates.search_summary = params.search_summary;
203
- if (params.status !== undefined)
204
- updates.status = params.status;
205
- const plan = getDatabase().transaction(() => {
206
- const p = updateUserPlan(params.id, updates);
207
- if (!p)
208
- return null;
209
- insertAuditLog({
210
- operation: "update",
211
- entity_type: "user_plan",
212
- entity_id: p.id,
213
- summary: `UserPlan更新: ${p.title} (${params.reason})`,
214
- details: JSON.stringify(updates),
215
- provenance,
216
- });
217
- return p;
218
- })();
219
- if (!plan) {
220
- return {
221
- content: [{
222
- type: "text",
223
- text: JSON.stringify({ success: false, error: `UserPlan ID '${params.id}' が見つかりません` }),
224
- }],
225
- isError: true,
226
- };
227
- }
228
- notifyWrite();
229
- return {
230
- content: [{
231
- type: "text",
232
- text: JSON.stringify(withLintWarnings({
233
- success: true,
234
- message: `作業計画を更新しました: ${plan.title}`,
235
- plan: {
236
- ...plan,
237
- tags: JSON.parse(plan.tags),
238
- l1_embedding: undefined,
239
- },
240
- }, lint.warnings), null, 2),
241
- }],
242
- };
86
+ // H1: scope ポリシーチェック(既存レコード + 新 scope)
87
+ const policy = getClientPolicy(getStoreManager().getConfig());
88
+ const db = getDatabase();
89
+ const existingMemo = db.prepare("SELECT scope FROM user_memos WHERE id = ?").get(params.id);
90
+ if (existingMemo) {
91
+ const oldCheck = checkScopeWritePolicy(policy, existingMemo.scope);
92
+ if (!oldCheck.allowed)
93
+ return buildPolicyErrorResponse(oldCheck.reason);
243
94
  }
244
- // === kind='issue' or 'discussion': 課題・議論更新 ===
245
- if (kind === "issue" || kind === "discussion") {
246
- const updates = {};
247
- if (params.title !== undefined)
248
- updates.title = params.title;
249
- if (params.l1_content !== undefined)
250
- updates.l1_content = params.l1_content;
251
- if (params.usage_policy !== undefined)
252
- updates.usage_policy = params.usage_policy;
253
- if (params.tags !== undefined)
254
- updates.tags = params.tags;
255
- if (params.scope !== undefined)
256
- updates.scope = params.scope;
257
- if (params.search_summary !== undefined)
258
- updates.search_summary = params.search_summary;
259
- if (params.status !== undefined)
260
- updates.status = params.status;
261
- if (params.priority !== undefined)
262
- updates.priority = params.priority;
263
- // entries 追記: 既存の entries に新しいコメントを追加
264
- if (params.entries && params.entries.length > 0) {
265
- const existing = getUserIssueById(params.id);
266
- if (existing) {
267
- const existingEntries = JSON.parse(existing.entries);
268
- const now = new Date().toISOString();
269
- for (const entry of params.entries) {
270
- existingEntries.push({ author: entry.author, content: entry.content, created_at: now });
271
- }
272
- updates.entries = JSON.stringify(existingEntries);
273
- }
274
- }
275
- const issue = getDatabase().transaction(() => {
276
- const i = updateUserIssue(params.id, updates);
277
- if (!i)
278
- return null;
279
- insertAuditLog({
280
- operation: "update",
281
- entity_type: "user_issue",
282
- entity_id: i.id,
283
- summary: `${kind === "discussion" ? "Discussion" : "UserIssue"}更新: ${i.title} (${params.reason})`,
284
- details: JSON.stringify(updates),
285
- provenance,
286
- });
287
- return i;
288
- })();
289
- if (!issue) {
290
- return {
291
- content: [{
292
- type: "text",
293
- text: JSON.stringify({ success: false, error: `UserIssue ID '${params.id}' が見つかりません` }),
294
- }],
295
- isError: true,
296
- };
297
- }
298
- notifyWrite();
299
- const kindLabel = kind === "discussion" ? "議論" : "課題";
300
- return {
301
- content: [{
302
- type: "text",
303
- text: JSON.stringify(withLintWarnings({
304
- success: true,
305
- message: `${kindLabel}を更新しました: ${issue.title}`,
306
- issue: {
307
- ...issue,
308
- tags: JSON.parse(issue.tags),
309
- entries: JSON.parse(issue.entries),
310
- l1_embedding: undefined,
311
- },
312
- }, lint.warnings), null, 2),
313
- }],
314
- };
95
+ if (params.scope !== undefined) {
96
+ const newCheck = checkScopeWritePolicy(policy, params.scope);
97
+ if (!newCheck.allowed)
98
+ return buildPolicyErrorResponse(newCheck.reason);
315
99
  }
316
- // === kind='memo': メモ更新(既存動作) ===
100
+ const provenance = buildProvenance(extra.sessionId);
317
101
  const updates = {};
318
102
  if (params.title !== undefined)
319
103
  updates.title = params.title;
@@ -329,45 +113,453 @@ export function registerUserMemoTools(server) {
329
113
  updates.search_summary = params.search_summary;
330
114
  if (params.status !== undefined)
331
115
  updates.status = params.status;
332
- // 2026-02-28 修正: updateUserMemo + auditLog をトランザクションで一括化(並行アクセス安全性)
333
116
  const memo = getDatabase().transaction(() => {
334
117
  const m = updateUserMemo(params.id, updates);
335
118
  if (!m)
336
119
  return null;
337
120
  insertAuditLog({
338
- operation: "update",
339
- entity_type: "user_memo",
340
- entity_id: m.id,
121
+ operation: "update", entity_type: "user_memo", entity_id: m.id,
341
122
  summary: `UserMemo更新: ${m.title} (${params.reason})`,
342
- details: JSON.stringify(updates),
343
- provenance,
123
+ details: JSON.stringify(updates), provenance,
344
124
  });
345
125
  return m;
346
126
  })();
347
- if (!memo) {
348
- return {
349
- content: [{
350
- type: "text",
351
- text: JSON.stringify({ success: false, error: `UserMemo ID '${params.id}' が見つかりません` }),
352
- }],
353
- isError: true,
354
- };
127
+ if (!memo)
128
+ return errorResult(`UserMemo ID '${params.id}' が見つかりません`);
129
+ notifyWrite();
130
+ return jsonResult(withLintWarnings({
131
+ success: true,
132
+ message: `メモを更新しました: ${memo.title}`,
133
+ memo: { ...stripEmbedding(memo), tags: parseTags(memo.tags) },
134
+ }, lint.warnings));
135
+ });
136
+ // ──────────────────────────────────────────────
137
+ // UserPlan: store / update / check
138
+ // ──────────────────────────────────────────────
139
+ server.tool(`${toolPrefix}store_user_plan`, "Store a checklist plan (Markdown checkboxes). Track progress with check_user_plan.", StoreUserPlanSchema.shape, async (params, extra) => {
140
+ const lint = lintGuard(params);
141
+ if (lint.blocked)
142
+ return buildLintBlockResponse(lint.warnings);
143
+ const scopeCheck = policyGuard(params.scope ?? "global");
144
+ if (!scopeCheck.allowed)
145
+ return buildPolicyErrorResponse(scopeCheck.reason);
146
+ const provenance = buildProvenance(extra.sessionId);
147
+ let l1_embedding = null;
148
+ try {
149
+ const { generateUserPlanEmbedding } = await import("../embedding/embed-on-write.js");
150
+ l1_embedding = await generateUserPlanEmbedding({ title: params.title, l1_content: params.l1_content });
151
+ }
152
+ catch { /* skip */ }
153
+ const plan = getDatabase().transaction(() => {
154
+ const p = insertUserPlan({
155
+ title: params.title,
156
+ l1_content: params.l1_content,
157
+ usage_policy: params.usage_policy ?? "auto",
158
+ tags: params.tags ?? [],
159
+ scope: params.scope ?? "global",
160
+ search_summary: params.search_summary,
161
+ user_input: params.user_input,
162
+ l1_embedding,
163
+ source_tool: params.source_tool,
164
+ client_name: provenance.client_name,
165
+ client_version: provenance.client_version,
166
+ });
167
+ insertAuditLog({
168
+ operation: "create", entity_type: "user_plan", entity_id: p.id,
169
+ summary: `UserPlan: ${p.title}`, provenance, source_tool: params.source_tool,
170
+ });
171
+ return p;
172
+ })();
173
+ notifyWrite();
174
+ return jsonResult(withLintWarnings({
175
+ success: true,
176
+ message: `作業計画を記録しました: ${plan.title}`,
177
+ plan: { ...stripEmbedding(plan), tags: parseTags(plan.tags) },
178
+ }, lint.warnings));
179
+ });
180
+ server.tool(`${toolPrefix}update_user_plan`, "Update plan metadata (title, status, tags, etc.). Use check_user_plan to check off items.", UpdateUserPlanSchema.shape, async (params, extra) => {
181
+ const lint = lintGuard(params);
182
+ if (lint.blocked)
183
+ return buildLintBlockResponse(lint.warnings);
184
+ // H1: scope ポリシーチェック
185
+ const policy = getClientPolicy(getStoreManager().getConfig());
186
+ const db = getDatabase();
187
+ const existingPlan = db.prepare("SELECT scope FROM user_plans WHERE id = ?").get(params.id);
188
+ if (existingPlan) {
189
+ const oldCheck = checkScopeWritePolicy(policy, existingPlan.scope);
190
+ if (!oldCheck.allowed)
191
+ return buildPolicyErrorResponse(oldCheck.reason);
192
+ }
193
+ if (params.scope !== undefined) {
194
+ const newCheck = checkScopeWritePolicy(policy, params.scope);
195
+ if (!newCheck.allowed)
196
+ return buildPolicyErrorResponse(newCheck.reason);
197
+ }
198
+ const provenance = buildProvenance(extra.sessionId);
199
+ const updates = {};
200
+ if (params.title !== undefined)
201
+ updates.title = params.title;
202
+ if (params.l1_content !== undefined)
203
+ updates.l1_content = params.l1_content;
204
+ if (params.usage_policy !== undefined)
205
+ updates.usage_policy = params.usage_policy;
206
+ if (params.tags !== undefined)
207
+ updates.tags = params.tags;
208
+ if (params.scope !== undefined)
209
+ updates.scope = params.scope;
210
+ if (params.search_summary !== undefined)
211
+ updates.search_summary = params.search_summary;
212
+ if (params.status !== undefined)
213
+ updates.status = params.status;
214
+ const plan = getDatabase().transaction(() => {
215
+ const p = updateUserPlan(params.id, updates);
216
+ if (!p)
217
+ return null;
218
+ insertAuditLog({
219
+ operation: "update", entity_type: "user_plan", entity_id: p.id,
220
+ summary: `UserPlan更新: ${p.title} (${params.reason})`,
221
+ details: JSON.stringify(updates), provenance,
222
+ });
223
+ return p;
224
+ })();
225
+ if (!plan)
226
+ return errorResult(`UserPlan ID '${params.id}' が見つかりません`);
227
+ notifyWrite();
228
+ return jsonResult(withLintWarnings({
229
+ success: true,
230
+ message: `作業計画を更新しました: ${plan.title}`,
231
+ plan: { ...stripEmbedding(plan), tags: parseTags(plan.tags) },
232
+ }, lint.warnings));
233
+ });
234
+ server.tool(`${toolPrefix}check_user_plan`, "Mark a checklist item as done (by index or exact text). Auto-completes plan when all items are checked.", CheckUserPlanSchema.shape, async (params, extra) => {
235
+ const provenance = buildProvenance(extra.sessionId);
236
+ const existing = getUserPlanById(params.id);
237
+ if (!existing)
238
+ return errorResult(`UserPlan ID '${params.id}' が見つかりません`);
239
+ // H1: scope ポリシーチェック
240
+ const policyCheck = policyGuard(existing.scope);
241
+ if (!policyCheck.allowed)
242
+ return buildPolicyErrorResponse(policyCheck.reason);
243
+ const content = existing.l1_content;
244
+ const lines = content.split("\n");
245
+ // チェックリスト項目(未チェック)のインデックスを収集
246
+ const uncheckedItems = [];
247
+ for (let i = 0; i < lines.length; i++) {
248
+ if (/^\s*-\s*\[ \]/.test(lines[i])) {
249
+ const text = lines[i].replace(/^\s*-\s*\[ \]\s*/, "").trim();
250
+ uncheckedItems.push({ lineIndex: i, text });
251
+ }
252
+ }
253
+ // M3: index(1始まり、未チェック項目の通し番号)または item(完全一致)で特定
254
+ let targetLineIndex = -1;
255
+ let matchedText = "";
256
+ if (params.index !== undefined) {
257
+ const idx = params.index - 1; // 0始まりに変換
258
+ if (idx < 0 || idx >= uncheckedItems.length) {
259
+ return errorResult(`インデックス ${params.index} は範囲外です。未チェック項目は ${uncheckedItems.length} 件あります。`);
260
+ }
261
+ targetLineIndex = uncheckedItems[idx].lineIndex;
262
+ matchedText = uncheckedItems[idx].text;
263
+ }
264
+ else if (params.item !== undefined) {
265
+ const itemText = params.item.trim();
266
+ const matches = uncheckedItems.filter(u => u.text === itemText);
267
+ if (matches.length === 0) {
268
+ return errorResult(`未チェック項目 '${itemText}' が見つかりません。既にチェック済みか、テキストが完全一致しません。`);
269
+ }
270
+ if (matches.length > 1) {
271
+ return errorResult(`'${itemText}' に一致する未チェック項目が ${matches.length} 件あります。index を使って指定してください。`);
272
+ }
273
+ targetLineIndex = matches[0].lineIndex;
274
+ matchedText = matches[0].text;
275
+ }
276
+ else {
277
+ return errorResult("item または index のどちらかを指定してください。");
278
+ }
279
+ lines[targetLineIndex] = lines[targetLineIndex].replace("[ ]", "[x]");
280
+ const newContent = lines.join("\n");
281
+ // 全項目チェック済みか判定
282
+ const hasUnchecked = lines.some(l => /^\s*-\s*\[ \]/.test(l));
283
+ const updates = { l1_content: newContent };
284
+ if (!hasUnchecked) {
285
+ updates.status = "completed";
286
+ }
287
+ const plan = getDatabase().transaction(() => {
288
+ const p = updateUserPlan(params.id, updates);
289
+ if (!p)
290
+ return null;
291
+ const autoCompleted = !hasUnchecked;
292
+ insertAuditLog({
293
+ operation: "update", entity_type: "user_plan", entity_id: p.id,
294
+ summary: `UserPlan check: ${matchedText}${autoCompleted ? " (auto-completed)" : ""}`,
295
+ provenance,
296
+ });
297
+ return p;
298
+ })();
299
+ if (!plan)
300
+ return errorResult(`UserPlan ID '${params.id}' の更新に失敗しました`);
301
+ notifyWrite();
302
+ const autoCompleted = !hasUnchecked;
303
+ return jsonResult({
304
+ success: true,
305
+ message: autoCompleted
306
+ ? `✓ ${matchedText} — 全項目完了!計画を completed にしました`
307
+ : `✓ ${matchedText}`,
308
+ plan: { ...stripEmbedding(plan), tags: parseTags(plan.tags) },
309
+ });
310
+ });
311
+ // ──────────────────────────────────────────────
312
+ // UserIssue: store / update / comment
313
+ // ──────────────────────────────────────────────
314
+ server.tool(`${toolPrefix}store_user_issue`, "Open an issue or discussion. Lifecycle: open → closed.", StoreUserIssueSchema.shape, async (params, extra) => {
315
+ const lint = lintGuard(params);
316
+ if (lint.blocked)
317
+ return buildLintBlockResponse(lint.warnings);
318
+ const scopeCheck = policyGuard(params.scope ?? "global");
319
+ if (!scopeCheck.allowed)
320
+ return buildPolicyErrorResponse(scopeCheck.reason);
321
+ const provenance = buildProvenance(extra.sessionId);
322
+ const kind = params.kind ?? "issue";
323
+ let l1_embedding = null;
324
+ try {
325
+ const { generateUserIssueEmbedding } = await import("../embedding/embed-on-write.js");
326
+ l1_embedding = await generateUserIssueEmbedding({ title: params.title, l1_content: params.l1_content });
355
327
  }
328
+ catch { /* skip */ }
329
+ const issue = getDatabase().transaction(() => {
330
+ const i = insertUserIssue({
331
+ title: params.title,
332
+ l1_content: params.l1_content,
333
+ kind,
334
+ priority: params.priority ?? "medium",
335
+ usage_policy: params.usage_policy ?? "on_request",
336
+ tags: params.tags ?? [],
337
+ scope: params.scope ?? "global",
338
+ search_summary: params.search_summary,
339
+ user_input: params.user_input,
340
+ l1_embedding,
341
+ source_tool: params.source_tool,
342
+ client_name: provenance.client_name,
343
+ client_version: provenance.client_version,
344
+ });
345
+ insertAuditLog({
346
+ operation: "create", entity_type: "user_issue", entity_id: i.id,
347
+ summary: `${kind === "discussion" ? "Discussion" : "UserIssue"}: ${i.title}`,
348
+ provenance, source_tool: params.source_tool,
349
+ });
350
+ return i;
351
+ })();
352
+ notifyWrite();
353
+ const kindLabel = kind === "discussion" ? "議論" : "課題";
354
+ return jsonResult(withLintWarnings({
355
+ success: true,
356
+ message: `${kindLabel}を記録しました: ${issue.title}`,
357
+ issue: { ...stripEmbedding(issue), tags: parseTags(issue.tags) },
358
+ }, lint.warnings));
359
+ });
360
+ server.tool(`${toolPrefix}update_user_issue`, "Update issue metadata (title, status, priority, tags, etc.). Use comment_user_issue to add comments.", UpdateUserIssueSchema.shape, async (params, extra) => {
361
+ const lint = lintGuard(params);
362
+ if (lint.blocked)
363
+ return buildLintBlockResponse(lint.warnings);
364
+ // H1: scope ポリシーチェック
365
+ const policy = getClientPolicy(getStoreManager().getConfig());
366
+ const db = getDatabase();
367
+ const existingIssue = db.prepare("SELECT scope FROM user_issues WHERE id = ?").get(params.id);
368
+ if (existingIssue) {
369
+ const oldCheck = checkScopeWritePolicy(policy, existingIssue.scope);
370
+ if (!oldCheck.allowed)
371
+ return buildPolicyErrorResponse(oldCheck.reason);
372
+ }
373
+ if (params.scope !== undefined) {
374
+ const newCheck = checkScopeWritePolicy(policy, params.scope);
375
+ if (!newCheck.allowed)
376
+ return buildPolicyErrorResponse(newCheck.reason);
377
+ }
378
+ const provenance = buildProvenance(extra.sessionId);
379
+ const updates = {};
380
+ if (params.title !== undefined)
381
+ updates.title = params.title;
382
+ if (params.l1_content !== undefined)
383
+ updates.l1_content = params.l1_content;
384
+ if (params.priority !== undefined)
385
+ updates.priority = params.priority;
386
+ if (params.usage_policy !== undefined)
387
+ updates.usage_policy = params.usage_policy;
388
+ if (params.tags !== undefined)
389
+ updates.tags = params.tags;
390
+ if (params.scope !== undefined)
391
+ updates.scope = params.scope;
392
+ if (params.search_summary !== undefined)
393
+ updates.search_summary = params.search_summary;
394
+ if (params.status !== undefined)
395
+ updates.status = params.status;
396
+ const issue = db.transaction(() => {
397
+ const i = updateUserIssue(params.id, updates);
398
+ if (!i)
399
+ return null;
400
+ insertAuditLog({
401
+ operation: "update", entity_type: "user_issue", entity_id: i.id,
402
+ summary: `UserIssue更新: ${i.title} (${params.reason})`,
403
+ details: JSON.stringify(updates), provenance,
404
+ });
405
+ return i;
406
+ })();
407
+ if (!issue)
408
+ return errorResult(`UserIssue ID '${params.id}' が見つかりません`);
409
+ notifyWrite();
410
+ return jsonResult(withLintWarnings({
411
+ success: true,
412
+ message: `課題を更新しました: ${issue.title}`,
413
+ issue: { ...stripEmbedding(issue), tags: parseTags(issue.tags) },
414
+ }, lint.warnings));
415
+ });
416
+ server.tool(`${toolPrefix}comment_user_issue`, "Append a timestamped comment to an issue. Preserves existing content.", CommentUserIssueSchema.shape, async (params, extra) => {
417
+ // 新M1: lint チェック(秘密情報検出)
418
+ const lint = lintGuard({ comment: params.comment });
419
+ if (lint.blocked)
420
+ return buildLintBlockResponse(lint.warnings);
421
+ const provenance = buildProvenance(extra.sessionId);
422
+ const existing = getUserIssueById(params.id);
423
+ if (!existing)
424
+ return errorResult(`UserIssue ID '${params.id}' が見つかりません`);
425
+ // H1: scope ポリシーチェック
426
+ const policyCheck = policyGuard(existing.scope);
427
+ if (!policyCheck.allowed)
428
+ return buildPolicyErrorResponse(policyCheck.reason);
429
+ const now = new Date().toISOString().slice(0, 10); // yyyy-mm-dd
430
+ const appendText = `\n\n[${now} 追記] ${params.comment}`;
431
+ const newContent = existing.l1_content + appendText;
432
+ // embedding 再生成
433
+ let l1_embedding = null;
434
+ try {
435
+ const { generateUserIssueEmbedding } = await import("../embedding/embed-on-write.js");
436
+ l1_embedding = await generateUserIssueEmbedding({ title: existing.title, l1_content: newContent });
437
+ }
438
+ catch { /* skip */ }
439
+ const updates = { l1_content: newContent };
440
+ if (l1_embedding)
441
+ updates.l1_embedding = l1_embedding;
442
+ const issue = getDatabase().transaction(() => {
443
+ const i = updateUserIssue(params.id, updates);
444
+ if (!i)
445
+ return null;
446
+ insertAuditLog({
447
+ operation: "update", entity_type: "user_issue", entity_id: i.id,
448
+ summary: `UserIssue comment: ${params.comment.slice(0, 80)}`,
449
+ provenance,
450
+ });
451
+ return i;
452
+ })();
453
+ if (!issue)
454
+ return errorResult(`UserIssue ID '${params.id}' の更新に失敗しました`);
455
+ notifyWrite();
456
+ return jsonResult({
457
+ success: true,
458
+ message: `コメントを追記しました`,
459
+ issue: { ...stripEmbedding(issue), tags: parseTags(issue.tags) },
460
+ });
461
+ });
462
+ // ──────────────────────────────────────────────
463
+ // UserTopic: store / update (変更なし)
464
+ // ──────────────────────────────────────────────
465
+ server.tool(`${toolPrefix}store_user_topic`, "Register a conversation topic for tracking across sessions. Store summary + conversation_id + cwd.", StoreUserTopicSchema.shape, async (params, extra) => {
466
+ const lint = lintGuard(params);
467
+ if (lint.blocked)
468
+ return buildLintBlockResponse(lint.warnings);
469
+ const scopeCheck = policyGuard(params.scope ?? "global");
470
+ if (!scopeCheck.allowed)
471
+ return buildPolicyErrorResponse(scopeCheck.reason);
472
+ const provenance = buildProvenance(extra.sessionId);
473
+ let l1_embedding = null;
474
+ try {
475
+ const { generateUserTopicEmbedding } = await import("../embedding/embed-on-write.js");
476
+ l1_embedding = await generateUserTopicEmbedding({ title: params.title, summary: params.summary });
477
+ }
478
+ catch { /* skip */ }
479
+ const topic = getDatabase().transaction(() => {
480
+ const t = insertUserTopic({
481
+ title: params.title,
482
+ summary: params.summary,
483
+ conversation_id: params.conversation_id,
484
+ cwd: params.cwd,
485
+ priority: params.priority ?? "medium",
486
+ tags: params.tags ?? [],
487
+ scope: params.scope ?? "global",
488
+ search_summary: params.search_summary,
489
+ l1_embedding,
490
+ source_tool: "watashi_store_user_topic",
491
+ client_name: provenance.client_name,
492
+ client_version: provenance.client_version,
493
+ });
494
+ insertAuditLog({
495
+ operation: "create", entity_type: "user_topic", entity_id: t.id,
496
+ summary: `UserTopic: ${t.title}`, provenance, source_tool: "watashi_store_user_topic",
497
+ });
498
+ return t;
499
+ })();
500
+ notifyWrite();
501
+ return jsonResult(withLintWarnings({
502
+ success: true,
503
+ message: `トピックを登録しました: ${topic.title}`,
504
+ topic: { ...stripEmbedding(topic), tags: parseTags(topic.tags) },
505
+ }, lint.warnings));
506
+ });
507
+ server.tool(`${toolPrefix}update_user_topic`, "Update a topic (summary, status, priority, etc.).", UpdateUserTopicSchema.shape, async (params, extra) => {
508
+ const lint = lintGuard(params);
509
+ if (lint.blocked)
510
+ return buildLintBlockResponse(lint.warnings);
511
+ const policy = getClientPolicy(getStoreManager().getConfig());
512
+ const db = getDatabase();
513
+ const existing = db.prepare("SELECT scope FROM user_topics WHERE id = ?").get(params.id);
514
+ if (existing) {
515
+ const oldScopeCheck = checkScopeWritePolicy(policy, existing.scope);
516
+ if (!oldScopeCheck.allowed)
517
+ return buildPolicyErrorResponse(oldScopeCheck.reason);
518
+ }
519
+ if (params.scope !== undefined) {
520
+ const newScopeCheck = checkScopeWritePolicy(policy, params.scope);
521
+ if (!newScopeCheck.allowed)
522
+ return buildPolicyErrorResponse(newScopeCheck.reason);
523
+ }
524
+ const provenance = buildProvenance(extra.sessionId);
525
+ const updates = {};
526
+ if (params.title !== undefined)
527
+ updates.title = params.title;
528
+ if (params.summary !== undefined)
529
+ updates.summary = params.summary;
530
+ if (params.conversation_id !== undefined)
531
+ updates.conversation_id = params.conversation_id;
532
+ if (params.cwd !== undefined)
533
+ updates.cwd = params.cwd;
534
+ if (params.priority !== undefined)
535
+ updates.priority = params.priority;
536
+ if (params.tags !== undefined)
537
+ updates.tags = params.tags;
538
+ if (params.scope !== undefined)
539
+ updates.scope = params.scope;
540
+ if (params.search_summary !== undefined)
541
+ updates.search_summary = params.search_summary;
542
+ if (params.status !== undefined)
543
+ updates.status = params.status;
544
+ const topic = db.transaction(() => {
545
+ const t = updateUserTopic(params.id, updates);
546
+ if (!t)
547
+ return null;
548
+ insertAuditLog({
549
+ operation: "update", entity_type: "user_topic", entity_id: t.id,
550
+ summary: `UserTopic更新: ${t.title} (${params.reason})`,
551
+ details: JSON.stringify(updates), provenance,
552
+ });
553
+ return t;
554
+ })();
555
+ if (!topic)
556
+ return errorResult(`UserTopic ID '${params.id}' が見つかりません`);
356
557
  notifyWrite();
357
- return {
358
- content: [{
359
- type: "text",
360
- text: JSON.stringify(withLintWarnings({
361
- success: true,
362
- message: `メモを更新しました: ${memo.title}`,
363
- memo: {
364
- ...memo,
365
- tags: JSON.parse(memo.tags),
366
- l1_embedding: undefined,
367
- },
368
- }, lint.warnings), null, 2),
369
- }],
370
- };
558
+ return jsonResult(withLintWarnings({
559
+ success: true,
560
+ message: `トピックを更新しました: ${topic.title}`,
561
+ topic: { ...stripEmbedding(topic), tags: parseTags(topic.tags) },
562
+ }, lint.warnings));
371
563
  });
372
564
  }
373
565
  //# sourceMappingURL=memo-tools.js.map