kibi-mcp 0.1.0
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/bin/kibi-mcp +59 -0
- package/dist/env.js +99 -0
- package/dist/mcpcat.js +129 -0
- package/dist/server.js +673 -0
- package/dist/tools/branch.js +208 -0
- package/dist/tools/check.js +349 -0
- package/dist/tools/context.js +280 -0
- package/dist/tools/coverage-report.js +91 -0
- package/dist/tools/delete.js +100 -0
- package/dist/tools/derive.js +311 -0
- package/dist/tools/impact.js +70 -0
- package/dist/tools/list-types.js +75 -0
- package/dist/tools/prolog-list.js +176 -0
- package/dist/tools/query-relationships.js +176 -0
- package/dist/tools/query.js +364 -0
- package/dist/tools/suggest-shared-facts.js +138 -0
- package/dist/tools/symbols.js +219 -0
- package/dist/tools/upsert.js +228 -0
- package/dist/tools-config.js +448 -0
- package/dist/workspace.js +126 -0
- package/package.json +43 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Kibi — repo-local, per-branch, queryable long-term memory for software projects
|
|
3
|
+
Copyright (C) 2026 Piotr Franczyk
|
|
4
|
+
|
|
5
|
+
This program is free software: you can redistribute it and/or modify
|
|
6
|
+
it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
(at your option) any later version.
|
|
9
|
+
|
|
10
|
+
This program is distributed in the hope that it will be useful,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
GNU Affero General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
*/
|
|
18
|
+
export async function handleKbContext(prolog, args, activeBranch) {
|
|
19
|
+
const { sourceFile, branch } = args;
|
|
20
|
+
if (branch && activeBranch && branch !== activeBranch) {
|
|
21
|
+
return {
|
|
22
|
+
content: [
|
|
23
|
+
{
|
|
24
|
+
type: "text",
|
|
25
|
+
text: `Error: branch parameter is not supported server-side; set KIBI_BRANCH at startup or restart server on the desired branch. (Requested: ${branch}, Active: ${activeBranch})`,
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const safeSource = sourceFile.replace(/'/g, "\\'");
|
|
32
|
+
const entityGoal = `findall([Id,Type,Props], (kb_entities_by_source('${safeSource}', SourceIds), member(Id, SourceIds), kb_entity(Id, Type, Props)), Results)`;
|
|
33
|
+
const entityQueryResult = await prolog.query(entityGoal);
|
|
34
|
+
const entities = [];
|
|
35
|
+
const entityIds = [];
|
|
36
|
+
if (entityQueryResult.success && entityQueryResult.bindings.Results) {
|
|
37
|
+
const entitiesData = parseListOfLists(entityQueryResult.bindings.Results);
|
|
38
|
+
for (const data of entitiesData) {
|
|
39
|
+
const entity = parseEntityFromList(data);
|
|
40
|
+
entities.push({
|
|
41
|
+
id: entity.id,
|
|
42
|
+
type: entity.type,
|
|
43
|
+
title: entity.title,
|
|
44
|
+
status: entity.status,
|
|
45
|
+
tags: entity.tags || [],
|
|
46
|
+
});
|
|
47
|
+
entityIds.push(entity.id);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const relationships = [];
|
|
51
|
+
for (const entityId of entityIds) {
|
|
52
|
+
const relGoal = `findall([RelType,FromId,ToId], (kb_relationship(RelType, FromId, ToId), (FromId = '${entityId}' ; ToId = '${entityId}')), RelResults)`;
|
|
53
|
+
const relQueryResult = await prolog.query(relGoal);
|
|
54
|
+
if (relQueryResult.success && relQueryResult.bindings.RelResults) {
|
|
55
|
+
const relData = parseListOfLists(relQueryResult.bindings.RelResults);
|
|
56
|
+
for (const rel of relData) {
|
|
57
|
+
relationships.push({
|
|
58
|
+
relType: rel[0],
|
|
59
|
+
fromId: rel[1],
|
|
60
|
+
toId: rel[2],
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const text = entities.length > 0
|
|
66
|
+
? `Found ${entities.length} KB entities linked to source file "${sourceFile}": ${entities.map((e) => e.id).join(", ")}`
|
|
67
|
+
: `No KB entities found for source file "${sourceFile}"`;
|
|
68
|
+
return {
|
|
69
|
+
content: [
|
|
70
|
+
{
|
|
71
|
+
type: "text",
|
|
72
|
+
text,
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
structuredContent: {
|
|
76
|
+
sourceFile,
|
|
77
|
+
entities,
|
|
78
|
+
relationships,
|
|
79
|
+
provenance: {
|
|
80
|
+
predicate: "kb_entities_by_source",
|
|
81
|
+
deterministic: true,
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
88
|
+
throw new Error(`Context query failed: ${message}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function parseListOfLists(listStr) {
|
|
92
|
+
const cleaned = listStr.trim().replace(/^\[/, "").replace(/\]$/, "");
|
|
93
|
+
if (cleaned === "") {
|
|
94
|
+
return [];
|
|
95
|
+
}
|
|
96
|
+
const results = [];
|
|
97
|
+
let depth = 0;
|
|
98
|
+
let current = "";
|
|
99
|
+
let currentList = [];
|
|
100
|
+
for (let i = 0; i < cleaned.length; i++) {
|
|
101
|
+
const char = cleaned[i];
|
|
102
|
+
if (char === "[") {
|
|
103
|
+
depth++;
|
|
104
|
+
if (depth > 1)
|
|
105
|
+
current += char;
|
|
106
|
+
}
|
|
107
|
+
else if (char === "]") {
|
|
108
|
+
depth--;
|
|
109
|
+
if (depth === 0) {
|
|
110
|
+
if (current) {
|
|
111
|
+
currentList.push(current.trim());
|
|
112
|
+
current = "";
|
|
113
|
+
}
|
|
114
|
+
if (currentList.length > 0) {
|
|
115
|
+
results.push(currentList);
|
|
116
|
+
currentList = [];
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
current += char;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
else if (char === "," && depth === 1) {
|
|
124
|
+
if (current) {
|
|
125
|
+
currentList.push(current.trim());
|
|
126
|
+
current = "";
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
else if (char === "," && depth === 0) {
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
current += char;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return results;
|
|
136
|
+
}
|
|
137
|
+
function parseEntityFromList(data) {
|
|
138
|
+
if (data.length < 3) {
|
|
139
|
+
return {};
|
|
140
|
+
}
|
|
141
|
+
const id = data[0].trim();
|
|
142
|
+
const type = data[1].trim();
|
|
143
|
+
const propsStr = data[2].trim();
|
|
144
|
+
const props = parsePropertyList(propsStr);
|
|
145
|
+
return { ...props, id: normalizeEntityId(stripOuterQuotes(id)), type };
|
|
146
|
+
}
|
|
147
|
+
function parsePropertyList(propsStr) {
|
|
148
|
+
const props = {};
|
|
149
|
+
let cleaned = propsStr.trim();
|
|
150
|
+
if (cleaned.startsWith("[")) {
|
|
151
|
+
cleaned = cleaned.substring(1);
|
|
152
|
+
}
|
|
153
|
+
if (cleaned.endsWith("]")) {
|
|
154
|
+
cleaned = cleaned.substring(0, cleaned.length - 1);
|
|
155
|
+
}
|
|
156
|
+
const pairs = splitTopLevel(cleaned, ",");
|
|
157
|
+
for (const pair of pairs) {
|
|
158
|
+
const eqIndex = pair.indexOf("=");
|
|
159
|
+
if (eqIndex === -1)
|
|
160
|
+
continue;
|
|
161
|
+
const key = pair.substring(0, eqIndex).trim();
|
|
162
|
+
const value = pair.substring(eqIndex + 1).trim();
|
|
163
|
+
if (key === "..." || value === "..." || value === "...|...") {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
const parsed = parsePrologValue(value);
|
|
167
|
+
props[key] = parsed;
|
|
168
|
+
}
|
|
169
|
+
return props;
|
|
170
|
+
}
|
|
171
|
+
function parsePrologValue(valueInput) {
|
|
172
|
+
const value = valueInput.trim();
|
|
173
|
+
if (value.startsWith("^^(")) {
|
|
174
|
+
const innerStart = value.indexOf("(") + 1;
|
|
175
|
+
let depth = 1;
|
|
176
|
+
let innerEnd = innerStart;
|
|
177
|
+
for (let i = innerStart; i < value.length; i++) {
|
|
178
|
+
if (value[i] === "(")
|
|
179
|
+
depth++;
|
|
180
|
+
if (value[i] === ")") {
|
|
181
|
+
depth--;
|
|
182
|
+
if (depth === 0) {
|
|
183
|
+
innerEnd = i;
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
const innerContent = value.substring(innerStart, innerEnd);
|
|
189
|
+
const parts = splitTopLevel(innerContent, ",");
|
|
190
|
+
if (parts.length >= 2) {
|
|
191
|
+
let literalValue = parts[0].trim();
|
|
192
|
+
if (literalValue.startsWith('"') && literalValue.endsWith('"')) {
|
|
193
|
+
literalValue = literalValue.substring(1, literalValue.length - 1);
|
|
194
|
+
}
|
|
195
|
+
if (literalValue.startsWith("[") && literalValue.endsWith("]")) {
|
|
196
|
+
const listContent = literalValue.substring(1, literalValue.length - 1);
|
|
197
|
+
if (listContent === "") {
|
|
198
|
+
return [];
|
|
199
|
+
}
|
|
200
|
+
return listContent.split(",").map((item) => item.trim());
|
|
201
|
+
}
|
|
202
|
+
return literalValue;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (value.startsWith("file:///")) {
|
|
206
|
+
const lastSlash = value.lastIndexOf("/");
|
|
207
|
+
if (lastSlash !== -1) {
|
|
208
|
+
return value.substring(lastSlash + 1);
|
|
209
|
+
}
|
|
210
|
+
return value;
|
|
211
|
+
}
|
|
212
|
+
if (value.startsWith('"') && value.endsWith('"')) {
|
|
213
|
+
return value.substring(1, value.length - 1);
|
|
214
|
+
}
|
|
215
|
+
if (value.startsWith("'") && value.endsWith("'")) {
|
|
216
|
+
return value.substring(1, value.length - 1);
|
|
217
|
+
}
|
|
218
|
+
if (value.startsWith("[") && value.endsWith("]")) {
|
|
219
|
+
const listContent = value.substring(1, value.length - 1);
|
|
220
|
+
if (listContent === "") {
|
|
221
|
+
return [];
|
|
222
|
+
}
|
|
223
|
+
const items = listContent.split(",").map((item) => {
|
|
224
|
+
return parsePrologValue(item.trim());
|
|
225
|
+
});
|
|
226
|
+
return items;
|
|
227
|
+
}
|
|
228
|
+
return value;
|
|
229
|
+
}
|
|
230
|
+
function splitTopLevel(str, delimiter) {
|
|
231
|
+
const results = [];
|
|
232
|
+
let current = "";
|
|
233
|
+
let depth = 0;
|
|
234
|
+
let inQuotes = false;
|
|
235
|
+
for (let i = 0; i < str.length; i++) {
|
|
236
|
+
const char = str[i];
|
|
237
|
+
const prevChar = i > 0 ? str[i - 1] : "";
|
|
238
|
+
if (char === '"' && prevChar !== "\\") {
|
|
239
|
+
inQuotes = !inQuotes;
|
|
240
|
+
current += char;
|
|
241
|
+
}
|
|
242
|
+
else if (!inQuotes && (char === "[" || char === "(")) {
|
|
243
|
+
depth++;
|
|
244
|
+
current += char;
|
|
245
|
+
}
|
|
246
|
+
else if (!inQuotes && (char === "]" || char === ")")) {
|
|
247
|
+
depth--;
|
|
248
|
+
current += char;
|
|
249
|
+
}
|
|
250
|
+
else if (!inQuotes && depth === 0 && char === delimiter) {
|
|
251
|
+
if (current) {
|
|
252
|
+
results.push(current);
|
|
253
|
+
current = "";
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
current += char;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (current) {
|
|
261
|
+
results.push(current);
|
|
262
|
+
}
|
|
263
|
+
return results;
|
|
264
|
+
}
|
|
265
|
+
function stripOuterQuotes(value) {
|
|
266
|
+
if (value.startsWith("'") && value.endsWith("'")) {
|
|
267
|
+
return value.slice(1, -1);
|
|
268
|
+
}
|
|
269
|
+
if (value.startsWith('"') && value.endsWith('"')) {
|
|
270
|
+
return value.slice(1, -1);
|
|
271
|
+
}
|
|
272
|
+
return value;
|
|
273
|
+
}
|
|
274
|
+
function normalizeEntityId(value) {
|
|
275
|
+
if (!value.startsWith("file:///")) {
|
|
276
|
+
return value;
|
|
277
|
+
}
|
|
278
|
+
const idx = value.lastIndexOf("/");
|
|
279
|
+
return idx === -1 ? value : value.slice(idx + 1);
|
|
280
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Kibi — repo-local, per-branch, queryable long-term memory for software projects
|
|
3
|
+
Copyright (C) 2026 Piotr Franczyk
|
|
4
|
+
|
|
5
|
+
This program is free software: you can redistribute it and/or modify
|
|
6
|
+
it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
(at your option) any later version.
|
|
9
|
+
|
|
10
|
+
This program is distributed in the hope that it will be useful,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
GNU Affero General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
*/
|
|
18
|
+
import { parseAtomList, parsePairList } from "./prolog-list.js";
|
|
19
|
+
export async function handleKbCoverageReport(prolog, args) {
|
|
20
|
+
const requested = args.type ?? "all";
|
|
21
|
+
if (args.type && args.type !== "req" && args.type !== "symbol") {
|
|
22
|
+
throw new Error("'type' must be one of: req, symbol");
|
|
23
|
+
}
|
|
24
|
+
const coverage = {};
|
|
25
|
+
const predicates = [];
|
|
26
|
+
if (requested === "all" || requested === "req") {
|
|
27
|
+
const reqIds = await queryAtoms(prolog, "setof(Req, kb_entity(Req, req, _), Reqs)", "Reqs");
|
|
28
|
+
const gapPairs = await queryPairs(prolog, "setof([Req,Reason], coverage_gap(Req, Reason), Rows)", "Rows");
|
|
29
|
+
const gaps = gapPairs.map(([req, reason]) => ({ req, reason }));
|
|
30
|
+
coverage.requirements = {
|
|
31
|
+
total: reqIds.length,
|
|
32
|
+
with_gaps: gaps.length,
|
|
33
|
+
healthy: Math.max(reqIds.length - gaps.length, 0),
|
|
34
|
+
gaps,
|
|
35
|
+
};
|
|
36
|
+
predicates.push("coverage_gap");
|
|
37
|
+
}
|
|
38
|
+
if (requested === "all" || requested === "symbol") {
|
|
39
|
+
const symbolIds = await queryAtoms(prolog, "setof(Symbol, kb_entity(Symbol, symbol, _), Symbols)", "Symbols");
|
|
40
|
+
const untestedResult = await prolog.query("untested_symbols(Symbols)");
|
|
41
|
+
const untestedSymbols = untestedResult.success && untestedResult.bindings.Symbols
|
|
42
|
+
? parseAtomList(untestedResult.bindings.Symbols)
|
|
43
|
+
: [];
|
|
44
|
+
coverage.symbols = {
|
|
45
|
+
total: symbolIds.length,
|
|
46
|
+
untested: untestedSymbols.length,
|
|
47
|
+
tested: Math.max(symbolIds.length - untestedSymbols.length, 0),
|
|
48
|
+
untested_symbols: untestedSymbols,
|
|
49
|
+
};
|
|
50
|
+
predicates.push("untested_symbols");
|
|
51
|
+
}
|
|
52
|
+
const summaryParts = [];
|
|
53
|
+
if (coverage.requirements) {
|
|
54
|
+
summaryParts.push(`${coverage.requirements.healthy}/${coverage.requirements.total} requirements healthy`);
|
|
55
|
+
}
|
|
56
|
+
if (coverage.symbols) {
|
|
57
|
+
summaryParts.push(`${coverage.symbols.tested}/${coverage.symbols.total} symbols tested`);
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
content: [
|
|
61
|
+
{
|
|
62
|
+
type: "text",
|
|
63
|
+
text: summaryParts.length > 0
|
|
64
|
+
? `Coverage report: ${summaryParts.join("; ")}.`
|
|
65
|
+
: "Coverage report: no data.",
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
structuredContent: {
|
|
69
|
+
requested_type: requested,
|
|
70
|
+
coverage,
|
|
71
|
+
provenance: {
|
|
72
|
+
deterministic: true,
|
|
73
|
+
predicates,
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
async function queryAtoms(prolog, goal, bindingName) {
|
|
79
|
+
const result = await prolog.query(goal);
|
|
80
|
+
if (!result.success || !result.bindings[bindingName]) {
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
return parseAtomList(result.bindings[bindingName]);
|
|
84
|
+
}
|
|
85
|
+
async function queryPairs(prolog, goal, bindingName) {
|
|
86
|
+
const result = await prolog.query(goal);
|
|
87
|
+
if (!result.success || !result.bindings[bindingName]) {
|
|
88
|
+
return [];
|
|
89
|
+
}
|
|
90
|
+
return parsePairList(result.bindings[bindingName]);
|
|
91
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Kibi — repo-local, per-branch, queryable long-term memory for software projects
|
|
3
|
+
Copyright (C) 2026 Piotr Franczyk
|
|
4
|
+
|
|
5
|
+
This program is free software: you can redistribute it and/or modify
|
|
6
|
+
it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
(at your option) any later version.
|
|
9
|
+
|
|
10
|
+
This program is distributed in the hope that it will be useful,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
GNU Affero General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Handle kb.delete tool calls
|
|
20
|
+
* Prevents deletion of entities with dependents (referential integrity)
|
|
21
|
+
*/
|
|
22
|
+
export async function handleKbDelete(prolog, args) {
|
|
23
|
+
const { ids } = args;
|
|
24
|
+
if (!ids || ids.length === 0) {
|
|
25
|
+
throw new Error("At least one ID required for delete");
|
|
26
|
+
}
|
|
27
|
+
let deleted = 0;
|
|
28
|
+
let skipped = 0;
|
|
29
|
+
const errors = [];
|
|
30
|
+
try {
|
|
31
|
+
for (const id of ids) {
|
|
32
|
+
// Check if entity exists
|
|
33
|
+
const checkGoal = `kb_entity('${id}', _, _)`;
|
|
34
|
+
const checkResult = await prolog.query(checkGoal);
|
|
35
|
+
if (!checkResult.success) {
|
|
36
|
+
errors.push(`Entity ${id} does not exist`);
|
|
37
|
+
skipped++;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
// Check for dependents (entities that reference this one)
|
|
41
|
+
// Query each relationship type separately to avoid timeout with unbound Type
|
|
42
|
+
const relTypes = [
|
|
43
|
+
"depends_on",
|
|
44
|
+
"verified_by",
|
|
45
|
+
"validates",
|
|
46
|
+
"specified_by",
|
|
47
|
+
"relates_to",
|
|
48
|
+
"guards",
|
|
49
|
+
"publishes",
|
|
50
|
+
"consumes",
|
|
51
|
+
];
|
|
52
|
+
let hasDependents = false;
|
|
53
|
+
for (const relType of relTypes) {
|
|
54
|
+
const dependentsGoal = `findall(From, kb_relationship(${relType}, From, '${id}'), Dependents)`;
|
|
55
|
+
const dependentsResult = await prolog.query(dependentsGoal);
|
|
56
|
+
if (dependentsResult.success && dependentsResult.bindings.Dependents) {
|
|
57
|
+
const dependentsStr = dependentsResult.bindings.Dependents;
|
|
58
|
+
if (dependentsStr !== "[]") {
|
|
59
|
+
errors.push(`Cannot delete entity ${id}: has dependents (other entities reference it via ${relType})`);
|
|
60
|
+
skipped++;
|
|
61
|
+
hasDependents = true;
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (hasDependents) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
// No dependents, safe to delete
|
|
70
|
+
const deleteGoal = `kb_retract_entity('${id}')`;
|
|
71
|
+
const deleteResult = await prolog.query(deleteGoal);
|
|
72
|
+
if (!deleteResult.success) {
|
|
73
|
+
errors.push(`Failed to delete entity ${id}: ${deleteResult.error || "Unknown error"}`);
|
|
74
|
+
skipped++;
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
deleted++;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Save KB to disk
|
|
81
|
+
await prolog.query("kb_save");
|
|
82
|
+
return {
|
|
83
|
+
content: [
|
|
84
|
+
{
|
|
85
|
+
type: "text",
|
|
86
|
+
text: `Deleted ${deleted} entities. Skipped ${skipped}. ${errors.length > 0 ? `Errors: ${errors.join("; ")}` : ""}`,
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
structuredContent: {
|
|
90
|
+
deleted,
|
|
91
|
+
skipped,
|
|
92
|
+
errors,
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
98
|
+
throw new Error(`Delete execution failed: ${message}`);
|
|
99
|
+
}
|
|
100
|
+
}
|