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.
- package/CLAUDE.md +36 -0
- package/LICENSE +1 -1
- package/README.md +33 -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/config/schema.js +1 -1
- package/dist/constants.d.ts +5 -1
- package/dist/constants.js +19 -3
- package/dist/constants.js.map +1 -1
- package/dist/database/archive.js +6 -6
- package/dist/database/queries-core.d.ts +75 -1
- package/dist/database/queries-core.js +283 -12
- package/dist/database/queries-core.js.map +1 -1
- package/dist/database/queries.d.ts +71 -1
- package/dist/database/queries.js +29 -0
- package/dist/database/queries.js.map +1 -1
- package/dist/database/schema.d.ts +1 -0
- package/dist/database/schema.js +1915 -214
- 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 +9 -6
- package/dist/hook.js.map +1 -1
- package/dist/index.js +3 -2
- 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-instructions.js +16 -17
- package/dist/server-instructions.js.map +1 -1
- package/dist/server.d.ts +4 -1
- package/dist/server.js +42 -18
- 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 +7 -7
- package/dist/tools/claim-tools.js.map +1 -1
- package/dist/tools/decision-tools.d.ts +1 -1
- package/dist/tools/decision-tools.js +2 -2
- package/dist/tools/decision-tools.js.map +1 -1
- package/dist/tools/episode-tools.d.ts +1 -1
- package/dist/tools/episode-tools.js +2 -2
- package/dist/tools/episode-tools.js.map +1 -1
- package/dist/tools/file-tools.d.ts +3 -0
- package/dist/tools/file-tools.js +347 -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/knowledge-tools.d.ts +1 -1
- package/dist/tools/knowledge-tools.js +2 -2
- package/dist/tools/knowledge-tools.js.map +1 -1
- package/dist/tools/maintenance-tools.d.ts +1 -1
- package/dist/tools/maintenance-tools.js +38 -6
- package/dist/tools/maintenance-tools.js.map +1 -1
- package/dist/tools/memo-tools.d.ts +7 -11
- package/dist/tools/memo-tools.js +499 -307
- package/dist/tools/memo-tools.js.map +1 -1
- package/dist/tools/query-tools.d.ts +1 -1
- package/dist/tools/query-tools.js +28 -5
- package/dist/tools/query-tools.js.map +1 -1
- package/dist/types.d.ts +370 -48
- package/dist/types.js +124 -16
- 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/package.json +4 -2
- package/scripts/update-license-version.sh +7 -0
package/dist/tools/memo-tools.js
CHANGED
|
@@ -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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
|
19
|
-
|
|
20
|
-
|
|
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 { /*
|
|
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
|
-
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
|
|
182
|
-
|
|
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
|
-
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
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
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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
|