kibi-mcp 0.17.5 → 0.19.2
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 +1 -1
- package/dist/diagnostics-helpers.js +22 -0
- package/dist/diagnostics.js +113 -4
- package/dist/runtime/mcp-runtime.js +43 -0
- package/dist/server/docs.js +11 -10
- package/dist/server/json-schema-to-zod.js +73 -2
- package/dist/server/session.js +33 -15
- package/dist/server/tool-registration.js +35 -59
- package/dist/server/tools-runtime.js +52 -2
- package/dist/server/tools.js +19 -5
- package/dist/server/transport.js +9 -5
- package/dist/tools/autopilot-generate.js +37 -646
- package/dist/tools/autopilot-test-compat.js +88 -0
- package/dist/tools/autopilot-test-parsers.js +120 -0
- package/dist/tools/check-config.js +67 -0
- package/dist/tools/check-format.js +92 -0
- package/dist/tools/check-impact.js +45 -0
- package/dist/tools/check-prolog.js +51 -0
- package/dist/tools/check.js +13 -190
- package/dist/tools/coverage.js +11 -25
- package/dist/tools/delete.js +5 -112
- package/dist/tools/discovery-adapter.js +23 -0
- package/dist/tools/entity-query.js +1 -111
- package/dist/tools/find-gaps.js +11 -26
- package/dist/tools/graph.js +11 -24
- package/dist/tools/model-requirement.js +8 -347
- package/dist/tools/mutation-context.js +28 -0
- package/dist/tools/query-plan-safety.js +58 -0
- package/dist/tools/query.js +5 -50
- package/dist/tools/relationship-validation.js +11 -0
- package/dist/tools/search.js +4 -37
- package/dist/tools/semantic-advisor.js +2 -40
- package/dist/tools/skills.js +25 -47
- package/dist/tools/sparql.js +12 -93
- package/dist/tools/status.js +5 -19
- package/dist/tools/suggest-predicates.js +11 -2448
- package/dist/tools/upsert.js +21 -794
- package/dist/tools/validate-upsert.js +16 -40
- package/dist/tools-config.js +75 -764
- package/package.json +4 -3
- package/dist/semantic-advisor/analyze-prose.js +0 -1367
- package/dist/semantic-advisor/prose-coverage-evaluator.js +0 -66
- /package/dist/{semantic-advisor/types.js → tools/check-types.js} +0 -0
|
@@ -1,351 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import { buildStrictWriteSet, } from "kibi-cli/public/check-types";
|
|
4
|
-
import { getSchemaVersionStatus } from "kibi-cli/schema-version";
|
|
1
|
+
import { modelRequirementSpec, } from "kibi-cli/operations";
|
|
2
|
+
import { estimateNormativeSignalConfidence, extractRequirementClaim, getWorkspaceMigrationWarning, strictWriteSetToApplyPlan, writeSetPrimaryEntityId, } from "kibi-cli/operations/modeling/model-requirement";
|
|
5
3
|
import { resolveWorkspaceRoot } from "../workspace.js";
|
|
6
|
-
|
|
7
|
-
const NORMATIVE_SECTION_PATTERN = /\b(requirements?|polic(?:y|ies)|rules?)\b/i;
|
|
8
|
-
function normalizeText(text) {
|
|
9
|
-
const normalized = String(text ?? "").trim();
|
|
10
|
-
if (!normalized) {
|
|
11
|
-
throw new Error("Requirement modeling failed: text must be a non-empty string");
|
|
12
|
-
}
|
|
13
|
-
return normalized;
|
|
14
|
-
}
|
|
15
|
-
function normalizeOptionalString(value) {
|
|
16
|
-
const normalized = String(value ?? "").trim();
|
|
17
|
-
return normalized.length > 0 ? normalized : undefined;
|
|
18
|
-
}
|
|
19
|
-
function normalizeSourceFiles(sourceFiles) {
|
|
20
|
-
const seen = new Set();
|
|
21
|
-
const normalized = [];
|
|
22
|
-
for (const sourceFile of sourceFiles ?? []) {
|
|
23
|
-
const trimmed = String(sourceFile ?? "").trim();
|
|
24
|
-
if (!trimmed || seen.has(trimmed))
|
|
25
|
-
continue;
|
|
26
|
-
seen.add(trimmed);
|
|
27
|
-
normalized.push(trimmed);
|
|
28
|
-
}
|
|
29
|
-
return normalized;
|
|
30
|
-
}
|
|
31
|
-
function clampConfidence(confidence) {
|
|
32
|
-
const numeric = typeof confidence === "number" && Number.isFinite(confidence)
|
|
33
|
-
? confidence
|
|
34
|
-
: 0.8;
|
|
35
|
-
return Math.round(Math.min(1, Math.max(0, numeric)) * 100) / 100;
|
|
36
|
-
}
|
|
37
|
-
function normalizeClaimValue(value) {
|
|
38
|
-
if (typeof value === "string" ||
|
|
39
|
-
typeof value === "number" ||
|
|
40
|
-
typeof value === "boolean") {
|
|
41
|
-
if (typeof value === "number" && !Number.isFinite(value)) {
|
|
42
|
-
throw new Error("Requirement modeling failed: value must be a finite number");
|
|
43
|
-
}
|
|
44
|
-
return value;
|
|
45
|
-
}
|
|
46
|
-
throw new Error("Requirement modeling failed: value must be a string, number, or boolean");
|
|
47
|
-
}
|
|
48
|
-
function stripListPrefix(value) {
|
|
49
|
-
return value
|
|
50
|
-
.replace(/^\s*[-*+]\s+/, "")
|
|
51
|
-
.replace(/^\s*\d+[.)]\s+/, "")
|
|
52
|
-
.trim();
|
|
53
|
-
}
|
|
54
|
-
function trimSentenceTail(value) {
|
|
55
|
-
return value.replace(/[\s.?!:;]+$/g, "").trim();
|
|
56
|
-
}
|
|
57
|
-
function cleanSubject(value) {
|
|
58
|
-
const cleaned = trimSentenceTail(stripListPrefix(value));
|
|
59
|
-
return cleaned.replace(/^(?:the|a|an)\s+/i, "").trim() || cleaned;
|
|
60
|
-
}
|
|
61
|
-
function cleanPredicate(value) {
|
|
62
|
-
return trimSentenceTail(stripListPrefix(value)) || "statement";
|
|
63
|
-
}
|
|
64
|
-
function fallbackSubjectFromSource(source) {
|
|
65
|
-
const basename = path
|
|
66
|
-
.basename(source, path.extname(source))
|
|
67
|
-
.replace(/[-_]+/g, " ")
|
|
68
|
-
.trim();
|
|
69
|
-
return basename || "Requirement";
|
|
70
|
-
}
|
|
71
|
-
function hasExplicitClaimFields(args) {
|
|
72
|
-
return (args.subjectKey !== undefined ||
|
|
73
|
-
args.propertyKey !== undefined ||
|
|
74
|
-
args.operator !== undefined ||
|
|
75
|
-
args.value !== undefined);
|
|
76
|
-
}
|
|
77
|
-
function buildFallbackClaim(statement, source, confidence, provenance) {
|
|
78
|
-
return {
|
|
79
|
-
claim: {
|
|
80
|
-
source,
|
|
81
|
-
subjectKey: fallbackSubjectFromSource(source),
|
|
82
|
-
propertyKey: "statement",
|
|
83
|
-
operator: "eq",
|
|
84
|
-
value: statement,
|
|
85
|
-
confidence: Math.min(confidence, STRICT_FALLBACK_CONFIDENCE),
|
|
86
|
-
...(provenance ? { provenance } : {}),
|
|
87
|
-
},
|
|
88
|
-
extractionMode: "fallback",
|
|
89
|
-
extractionWarnings: [
|
|
90
|
-
"Deterministic claim extraction could not infer a strict semantic claim; emitted a review artifact instead.",
|
|
91
|
-
],
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
function extractHeuristicClaim(statement, source, confidence, provenance) {
|
|
95
|
-
const normalized = stripListPrefix(statement);
|
|
96
|
-
const retentionMatch = normalized.match(/^(?<subject>.+?)\s+(?:must|shall|should)\s+be\s+retained\s+for\s+(?<value>\d+)\s+(?<unit>day|days|month|months|year|years)\.?$/i);
|
|
97
|
-
if (retentionMatch?.groups) {
|
|
98
|
-
const { unit, subject, value } = retentionMatch.groups;
|
|
99
|
-
if (!unit || !subject || !value)
|
|
100
|
-
return null;
|
|
101
|
-
const normalizedUnit = unit.toLowerCase().startsWith("day")
|
|
102
|
-
? "Days"
|
|
103
|
-
: unit.toLowerCase().startsWith("month")
|
|
104
|
-
? "Months"
|
|
105
|
-
: "Years";
|
|
106
|
-
return {
|
|
107
|
-
claim: {
|
|
108
|
-
source,
|
|
109
|
-
subjectKey: cleanSubject(subject),
|
|
110
|
-
propertyKey: `Retention ${normalizedUnit}`,
|
|
111
|
-
operator: "eq",
|
|
112
|
-
value: Number(value),
|
|
113
|
-
confidence,
|
|
114
|
-
...(provenance ? { provenance } : {}),
|
|
115
|
-
},
|
|
116
|
-
extractionMode: "heuristic",
|
|
117
|
-
extractionWarnings: [],
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
const enabledMatch = normalized.match(/^(?<subject>.+?)\s+(?:must|shall|should)\s+be\s+(?<state>enabled|disabled)\.?$/i);
|
|
121
|
-
if (enabledMatch?.groups) {
|
|
122
|
-
const { subject, state } = enabledMatch.groups;
|
|
123
|
-
if (!subject || !state)
|
|
124
|
-
return null;
|
|
125
|
-
return {
|
|
126
|
-
claim: {
|
|
127
|
-
source,
|
|
128
|
-
subjectKey: cleanSubject(subject),
|
|
129
|
-
propertyKey: "enabled",
|
|
130
|
-
operator: "bool",
|
|
131
|
-
value: state.toLowerCase() === "enabled",
|
|
132
|
-
confidence,
|
|
133
|
-
...(provenance ? { provenance } : {}),
|
|
134
|
-
},
|
|
135
|
-
extractionMode: "heuristic",
|
|
136
|
-
extractionWarnings: [],
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
const forbiddenMatch = normalized.match(/^(?<subject>.+?)\s+(?:must|shall|should)\s+not\s+(?<predicate>.+?)\.?$/i);
|
|
140
|
-
if (forbiddenMatch?.groups) {
|
|
141
|
-
const { subject, predicate } = forbiddenMatch.groups;
|
|
142
|
-
if (!subject || !predicate)
|
|
143
|
-
return null;
|
|
144
|
-
return {
|
|
145
|
-
claim: {
|
|
146
|
-
source,
|
|
147
|
-
subjectKey: cleanSubject(subject),
|
|
148
|
-
propertyKey: cleanPredicate(predicate),
|
|
149
|
-
operator: "polarity",
|
|
150
|
-
value: "forbid",
|
|
151
|
-
confidence,
|
|
152
|
-
...(provenance ? { provenance } : {}),
|
|
153
|
-
},
|
|
154
|
-
extractionMode: "heuristic",
|
|
155
|
-
extractionWarnings: [],
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
const requiredMatch = normalized.match(/^(?<subject>.+?)\s+(?:must|shall|should)\s+(?<predicate>.+?)\.?$/i);
|
|
159
|
-
if (requiredMatch?.groups) {
|
|
160
|
-
const { subject, predicate } = requiredMatch.groups;
|
|
161
|
-
if (!subject || !predicate)
|
|
162
|
-
return null;
|
|
163
|
-
return {
|
|
164
|
-
claim: {
|
|
165
|
-
source,
|
|
166
|
-
subjectKey: cleanSubject(subject),
|
|
167
|
-
propertyKey: cleanPredicate(predicate),
|
|
168
|
-
operator: "polarity",
|
|
169
|
-
value: "require",
|
|
170
|
-
confidence,
|
|
171
|
-
...(provenance ? { provenance } : {}),
|
|
172
|
-
},
|
|
173
|
-
extractionMode: "heuristic",
|
|
174
|
-
extractionWarnings: [],
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
return null;
|
|
178
|
-
}
|
|
179
|
-
export function estimateNormativeSignalConfidence(statement, heading) {
|
|
180
|
-
const normalizedStatement = stripListPrefix(statement).toLowerCase();
|
|
181
|
-
if (!/\b(must|shall|should)\b/.test(normalizedStatement)) {
|
|
182
|
-
return 0;
|
|
183
|
-
}
|
|
184
|
-
let confidence = normalizedStatement.includes(" shall ")
|
|
185
|
-
? 0.86
|
|
186
|
-
: normalizedStatement.includes(" must ")
|
|
187
|
-
? 0.84
|
|
188
|
-
: 0.78;
|
|
189
|
-
if (heading && NORMATIVE_SECTION_PATTERN.test(heading)) {
|
|
190
|
-
confidence += 0.08;
|
|
191
|
-
}
|
|
192
|
-
return Math.round(Math.min(0.95, confidence) * 100) / 100;
|
|
193
|
-
}
|
|
194
|
-
export function extractRequirementClaim(args) {
|
|
195
|
-
const statement = normalizeText(args.text);
|
|
196
|
-
const sourceFiles = normalizeSourceFiles(args.sourceFiles);
|
|
197
|
-
const source = normalizeOptionalString(args.source) ?? sourceFiles[0];
|
|
198
|
-
if (!source) {
|
|
199
|
-
throw new Error("Requirement modeling failed: provide source or at least one sourceFiles entry");
|
|
200
|
-
}
|
|
201
|
-
const provenance = normalizeOptionalString(args.provenance);
|
|
202
|
-
const confidence = clampConfidence(args.confidence);
|
|
203
|
-
if (hasExplicitClaimFields(args)) {
|
|
204
|
-
if (!normalizeOptionalString(args.subjectKey) ||
|
|
205
|
-
!normalizeOptionalString(args.propertyKey) ||
|
|
206
|
-
args.operator === undefined ||
|
|
207
|
-
args.value === undefined) {
|
|
208
|
-
throw new Error("Requirement modeling failed: subjectKey, propertyKey, operator, and value must all be provided when any extracted claim field is supplied");
|
|
209
|
-
}
|
|
210
|
-
return {
|
|
211
|
-
statement,
|
|
212
|
-
source,
|
|
213
|
-
sourceFiles,
|
|
214
|
-
claim: {
|
|
215
|
-
source,
|
|
216
|
-
subjectKey: normalizeOptionalString(args.subjectKey),
|
|
217
|
-
propertyKey: normalizeOptionalString(args.propertyKey),
|
|
218
|
-
operator: args.operator,
|
|
219
|
-
value: normalizeClaimValue(args.value),
|
|
220
|
-
confidence,
|
|
221
|
-
...(provenance ? { provenance } : {}),
|
|
222
|
-
},
|
|
223
|
-
extractionMode: "provided",
|
|
224
|
-
extractionWarnings: [],
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
const heuristic = extractHeuristicClaim(statement, source, confidence, provenance);
|
|
228
|
-
if (heuristic) {
|
|
229
|
-
return {
|
|
230
|
-
statement,
|
|
231
|
-
source,
|
|
232
|
-
sourceFiles,
|
|
233
|
-
...heuristic,
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
return {
|
|
237
|
-
statement,
|
|
238
|
-
source,
|
|
239
|
-
sourceFiles,
|
|
240
|
-
...buildFallbackClaim(statement, source, confidence, provenance),
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
function toRelationshipPlanRows(relationships) {
|
|
244
|
-
return relationships.map((relationship) => ({
|
|
245
|
-
type: relationship.type,
|
|
246
|
-
from: relationship.from,
|
|
247
|
-
to: relationship.to,
|
|
248
|
-
}));
|
|
249
|
-
}
|
|
250
|
-
export function strictWriteSetToApplyPlan(writeSet) {
|
|
251
|
-
if (!writeSet.isStrict) {
|
|
252
|
-
return [
|
|
253
|
-
{
|
|
254
|
-
type: writeSet.observationFact.type,
|
|
255
|
-
id: writeSet.observationFact.id,
|
|
256
|
-
properties: writeSet.observationFact.properties,
|
|
257
|
-
relationships: [],
|
|
258
|
-
},
|
|
259
|
-
];
|
|
260
|
-
}
|
|
261
|
-
return [
|
|
262
|
-
{
|
|
263
|
-
type: writeSet.subjectFact.type,
|
|
264
|
-
id: writeSet.subjectFact.id,
|
|
265
|
-
properties: writeSet.subjectFact.properties,
|
|
266
|
-
relationships: [],
|
|
267
|
-
},
|
|
268
|
-
{
|
|
269
|
-
type: writeSet.propertyFact.type,
|
|
270
|
-
id: writeSet.propertyFact.id,
|
|
271
|
-
properties: writeSet.propertyFact.properties,
|
|
272
|
-
relationships: [],
|
|
273
|
-
},
|
|
274
|
-
{
|
|
275
|
-
type: writeSet.req.type,
|
|
276
|
-
id: writeSet.req.id,
|
|
277
|
-
properties: writeSet.req.properties,
|
|
278
|
-
relationships: toRelationshipPlanRows(writeSet.relationships),
|
|
279
|
-
},
|
|
280
|
-
];
|
|
281
|
-
}
|
|
282
|
-
export function writeSetPrimaryEntityId(writeSet) {
|
|
283
|
-
return writeSet.isStrict ? writeSet.req.id : writeSet.observationFact.id;
|
|
284
|
-
}
|
|
285
|
-
export async function getWorkspaceMigrationWarning(workspaceRoot = resolveWorkspaceRoot()) {
|
|
286
|
-
const configPath = path.join(workspaceRoot, ".kb", "config.json");
|
|
287
|
-
let rawConfig;
|
|
288
|
-
try {
|
|
289
|
-
rawConfig = await readFile(configPath, "utf8");
|
|
290
|
-
}
|
|
291
|
-
catch {
|
|
292
|
-
return null;
|
|
293
|
-
}
|
|
294
|
-
try {
|
|
295
|
-
const parsed = JSON.parse(rawConfig);
|
|
296
|
-
const status = getSchemaVersionStatus(parsed ?? undefined);
|
|
297
|
-
return status.warning;
|
|
298
|
-
}
|
|
299
|
-
catch {
|
|
300
|
-
return "KB config schemaVersion could not be read and should be checked before applying automated modeling.";
|
|
301
|
-
}
|
|
302
|
-
}
|
|
4
|
+
export { estimateNormativeSignalConfidence, extractRequirementClaim, getWorkspaceMigrationWarning, strictWriteSetToApplyPlan, writeSetPrimaryEntityId, };
|
|
303
5
|
export async function handleKbModelRequirement(_prolog, args) {
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
});
|
|
309
|
-
const applyPlan = strictWriteSetToApplyPlan(writeSet);
|
|
310
|
-
const migrationWarning = await getWorkspaceMigrationWarning();
|
|
311
|
-
const warnings = writeSet.isStrict
|
|
312
|
-
? []
|
|
313
|
-
: [
|
|
314
|
-
{
|
|
315
|
-
kind: "low_confidence_observation_downgrade",
|
|
316
|
-
message: `Claim confidence ${writeSet.confidence.toFixed(2)} is below the strict threshold 0.70, so Kibi emitted an observation fact instead of strict subject/property facts.`,
|
|
317
|
-
nextAction: "If this is normative, provide subjectKey, propertyKey, operator, and value explicitly, then apply the returned strict write-set sequentially.",
|
|
318
|
-
},
|
|
319
|
-
];
|
|
320
|
-
const strictSummary = writeSet.isStrict
|
|
321
|
-
? `Modeled strict requirement into ${applyPlan.length} sequential applyPlan step(s).`
|
|
322
|
-
: "Modeled a non-blocking observation review artifact; deterministic claim extraction stayed below the strict threshold.";
|
|
323
|
-
const structuredContent = {
|
|
324
|
-
statement: extracted.statement,
|
|
325
|
-
source: extracted.source,
|
|
326
|
-
sourceFiles: extracted.sourceFiles,
|
|
327
|
-
claim: extracted.claim,
|
|
328
|
-
writeSet,
|
|
329
|
-
applyPlan,
|
|
330
|
-
isStrict: writeSet.isStrict,
|
|
331
|
-
confidence: writeSet.confidence,
|
|
332
|
-
extractionMode: extracted.extractionMode,
|
|
333
|
-
extractionWarnings: extracted.extractionWarnings,
|
|
334
|
-
warnings,
|
|
335
|
-
migrationWarning,
|
|
336
|
-
};
|
|
337
|
-
return {
|
|
338
|
-
content: [
|
|
339
|
-
{
|
|
340
|
-
type: "text",
|
|
341
|
-
text: migrationWarning
|
|
342
|
-
? `${strictSummary} Migration warning included.`
|
|
343
|
-
: strictSummary,
|
|
344
|
-
},
|
|
345
|
-
],
|
|
346
|
-
structuredContent,
|
|
347
|
-
applyPlan,
|
|
348
|
-
writeSet,
|
|
349
|
-
migrationWarning,
|
|
6
|
+
const context = {
|
|
7
|
+
workspaceRoot: resolveWorkspaceRoot(),
|
|
8
|
+
signal: new AbortController().signal,
|
|
9
|
+
clock: () => new Date(),
|
|
350
10
|
};
|
|
11
|
+
return modelRequirementSpec.execute(args, context);
|
|
351
12
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import { resolveWorkspaceRoot } from "../workspace.js";
|
|
3
|
+
const filesystem = {
|
|
4
|
+
readFile: (filePath) => fs.readFile(filePath, "utf8"),
|
|
5
|
+
writeFile: (filePath, data) => fs.writeFile(filePath, data, "utf8"),
|
|
6
|
+
mkdir: async (directoryPath) => {
|
|
7
|
+
await fs.mkdir(directoryPath, { recursive: true });
|
|
8
|
+
},
|
|
9
|
+
stat: (filePath) => fs.stat(filePath),
|
|
10
|
+
};
|
|
11
|
+
// implements REQ-kibi-operation-interface-parity
|
|
12
|
+
export function createMutationContext(prolog) {
|
|
13
|
+
const port = prolog === undefined
|
|
14
|
+
? undefined
|
|
15
|
+
: {
|
|
16
|
+
query: (goal) => prolog.query(goal),
|
|
17
|
+
nextSolution: async () => null,
|
|
18
|
+
invalidateCache: () => prolog.invalidateCache(),
|
|
19
|
+
save: () => prolog.query("kb_save"),
|
|
20
|
+
};
|
|
21
|
+
return {
|
|
22
|
+
workspaceRoot: resolveWorkspaceRoot(),
|
|
23
|
+
signal: new AbortController().signal,
|
|
24
|
+
clock: () => new Date(),
|
|
25
|
+
fs: filesystem,
|
|
26
|
+
...(port === undefined ? {} : { prolog: port }),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
const GENERATOR_PATTERN = /\b(?:kb_entity|kb_relationship|member|memberchk|findall|setof|bagof)\s*\(/;
|
|
3
|
+
const NEGATION_PATTERN = /\\\+\s*/;
|
|
4
|
+
export function collectQueryPlanSafetyViolations(checksPlPath) {
|
|
5
|
+
const source = fs.readFileSync(checksPlPath, "utf8");
|
|
6
|
+
return analyzeSource(source).map((violation) => ({
|
|
7
|
+
rule: "query-plan-safety",
|
|
8
|
+
entityId: violation.predicate,
|
|
9
|
+
description: violation.description,
|
|
10
|
+
suggestion: violation.suggestion,
|
|
11
|
+
source: `${checksPlPath}:${violation.line}`,
|
|
12
|
+
}));
|
|
13
|
+
}
|
|
14
|
+
function analyzeSource(source) {
|
|
15
|
+
const lines = source.split(/\r?\n/);
|
|
16
|
+
const violations = [];
|
|
17
|
+
let clauseStart = 0;
|
|
18
|
+
let predicate = "unknown";
|
|
19
|
+
let clauseLines = [];
|
|
20
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
21
|
+
const line = lines[index] ?? "";
|
|
22
|
+
if (clauseLines.length === 0 && line.trim().length === 0) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (clauseLines.length === 0) {
|
|
26
|
+
clauseStart = index + 1;
|
|
27
|
+
predicate = predicateNameFrom(line) ?? "unknown";
|
|
28
|
+
}
|
|
29
|
+
clauseLines.push(line);
|
|
30
|
+
if (line.trim().endsWith(".")) {
|
|
31
|
+
const violation = analyzeClause(predicate, clauseStart, clauseLines);
|
|
32
|
+
if (violation)
|
|
33
|
+
violations.push(violation);
|
|
34
|
+
clauseLines = [];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return violations;
|
|
38
|
+
}
|
|
39
|
+
function predicateNameFrom(line) {
|
|
40
|
+
const match = line.match(/^\s*([a-z][a-zA-Z0-9_]*)\s*\(/);
|
|
41
|
+
return match?.[1] ?? null;
|
|
42
|
+
}
|
|
43
|
+
function analyzeClause(predicate, startLine, lines) {
|
|
44
|
+
const negationIndex = lines.findIndex((line) => NEGATION_PATTERN.test(line));
|
|
45
|
+
if (negationIndex < 0)
|
|
46
|
+
return null;
|
|
47
|
+
const hasLaterGenerator = lines
|
|
48
|
+
.slice(negationIndex + 1)
|
|
49
|
+
.some((line) => GENERATOR_PATTERN.test(line));
|
|
50
|
+
if (!hasLaterGenerator)
|
|
51
|
+
return null;
|
|
52
|
+
return {
|
|
53
|
+
predicate,
|
|
54
|
+
line: startLine + negationIndex,
|
|
55
|
+
description: "Negation appears before later generator calls in the same clause.",
|
|
56
|
+
suggestion: "Move kb_entity/kb_relationship/member/findall generators before \\+/1 so variables are bound before negation.",
|
|
57
|
+
};
|
|
58
|
+
}
|
package/dist/tools/query.js
CHANGED
|
@@ -1,52 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* Handle kb.query tool calls
|
|
5
|
-
* Reuses query logic from CLI command
|
|
6
|
-
*/
|
|
1
|
+
import { VALID_ENTITY_TYPES, querySpec } from "kibi-cli/operations";
|
|
2
|
+
import { createDiscoveryContext } from "./discovery-adapter.js";
|
|
3
|
+
export { VALID_ENTITY_TYPES };
|
|
7
4
|
export async function handleKbQuery(prolog, args) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const queryArgs = {
|
|
11
|
-
...(type !== undefined ? { type } : {}),
|
|
12
|
-
...(id !== undefined ? { id } : {}),
|
|
13
|
-
...(tags !== undefined ? { tags } : {}),
|
|
14
|
-
...(sourceFile !== undefined ? { sourceFile } : {}),
|
|
15
|
-
};
|
|
16
|
-
const results = await loadEntities(prolog, queryArgs);
|
|
17
|
-
const paginated = paginateResults(results, limit, offset);
|
|
18
|
-
// Build human-readable text with entity IDs and titles
|
|
19
|
-
let text;
|
|
20
|
-
if (results.length === 0) {
|
|
21
|
-
text = `No entities found${type ? ` of type '${type}'` : ""}.`;
|
|
22
|
-
}
|
|
23
|
-
else {
|
|
24
|
-
const details = paginated
|
|
25
|
-
.map((e) => {
|
|
26
|
-
const id = e.id.replace(/^file:\/\/.*\//, "");
|
|
27
|
-
const title = e.title;
|
|
28
|
-
const status = e.status;
|
|
29
|
-
return `${id} (${title}, status=${status})`;
|
|
30
|
-
})
|
|
31
|
-
.join(", ");
|
|
32
|
-
text = `Found ${results.length} entities${type ? ` of type '${type}'` : ""}. Showing ${paginated.length} (offset ${offset}, limit ${limit}): ${details}`;
|
|
33
|
-
}
|
|
34
|
-
// Return MCP structured response
|
|
35
|
-
return {
|
|
36
|
-
content: [
|
|
37
|
-
{
|
|
38
|
-
type: "text",
|
|
39
|
-
text,
|
|
40
|
-
},
|
|
41
|
-
],
|
|
42
|
-
structuredContent: {
|
|
43
|
-
entities: paginated,
|
|
44
|
-
count: results.length,
|
|
45
|
-
},
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
catch (error) {
|
|
49
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
50
|
-
throw new Error(`Query execution failed: ${message}`);
|
|
51
|
-
}
|
|
5
|
+
// implements REQ-kibi-operation-interface-parity
|
|
6
|
+
return querySpec.execute(args, createDiscoveryContext(prolog));
|
|
52
7
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { formatInvalidRelationshipError, formatInvalidRelationshipTuple, formatRelationshipSourceMismatch, validateLiveRelationshipTargets as validateSharedTargets, } from "kibi-cli/operations/mutation/relationships";
|
|
2
|
+
export { formatInvalidRelationshipError, formatInvalidRelationshipTuple, formatRelationshipSourceMismatch, };
|
|
3
|
+
export async function validateLiveRelationshipTargets(prolog, entity, relationships) {
|
|
4
|
+
const port = {
|
|
5
|
+
query: (goal) => prolog.query(goal),
|
|
6
|
+
nextSolution: async () => null,
|
|
7
|
+
invalidateCache: () => prolog.invalidateCache(),
|
|
8
|
+
save: () => prolog.query("kb_save"),
|
|
9
|
+
};
|
|
10
|
+
await validateSharedTargets(port, entity, relationships);
|
|
11
|
+
}
|
package/dist/tools/search.js
CHANGED
|
@@ -1,39 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { loadEntities, paginateResults, validateEntityType, } from "./entity-query.js";
|
|
4
|
-
// implements REQ-mcp-search-discovery, REQ-002
|
|
1
|
+
import { searchSpec } from "kibi-cli/operations";
|
|
2
|
+
import { createDiscoveryContext } from "./discovery-adapter.js";
|
|
5
3
|
export async function handleKbSearch(prolog, args) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
if (!trimmedQuery) {
|
|
9
|
-
throw new Error("Search execution failed: query must be a non-empty string");
|
|
10
|
-
}
|
|
11
|
-
validateEntityType(type);
|
|
12
|
-
try {
|
|
13
|
-
const workspaceRoot = resolveWorkspaceRoot();
|
|
14
|
-
const entities = await loadEntities(prolog, {
|
|
15
|
-
...(type !== undefined ? { type } : {}),
|
|
16
|
-
});
|
|
17
|
-
const matches = await rankEntities(entities, trimmedQuery, workspaceRoot);
|
|
18
|
-
const paginated = paginateResults(matches, limit, offset);
|
|
19
|
-
const text = matches.length === 0
|
|
20
|
-
? `No search results for '${trimmedQuery}'.`
|
|
21
|
-
: `Found ${matches.length} search results for '${trimmedQuery}'. Showing ${paginated.length} (offset ${offset}, limit ${limit}): ${paginated
|
|
22
|
-
.map((match) => `${match.entity.id} [${match.reasons.join(", ")}]`)
|
|
23
|
-
.join(", ")}`;
|
|
24
|
-
return {
|
|
25
|
-
content: [{ type: "text", text }],
|
|
26
|
-
structuredContent: {
|
|
27
|
-
results: paginated,
|
|
28
|
-
count: matches.length,
|
|
29
|
-
},
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
catch (error) {
|
|
33
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
34
|
-
if (message.startsWith("Search execution failed:")) {
|
|
35
|
-
throw error;
|
|
36
|
-
}
|
|
37
|
-
throw new Error(`Search execution failed: ${message}`);
|
|
38
|
-
}
|
|
4
|
+
// implements REQ-kibi-operation-interface-parity, REQ-mcp-search-discovery
|
|
5
|
+
return searchSpec.execute(args, createDiscoveryContext(prolog));
|
|
39
6
|
}
|
|
@@ -1,42 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
function normalizeText(value) {
|
|
3
|
-
const text = typeof value === "string" ? value.trim() : "";
|
|
4
|
-
if (!text) {
|
|
5
|
-
throw new Error("Semantic advisor failed: text must be a non-empty string");
|
|
6
|
-
}
|
|
7
|
-
return text;
|
|
8
|
-
}
|
|
9
|
-
function optionalString(value) {
|
|
10
|
-
const text = typeof value === "string" ? value.trim() : "";
|
|
11
|
-
return text.length > 0 ? text : undefined;
|
|
12
|
-
}
|
|
1
|
+
import { executeSemanticAdvisor } from "kibi-cli/operations";
|
|
13
2
|
export async function handleKbSemanticAdvisor(args) {
|
|
14
|
-
|
|
15
|
-
const id = optionalString(args.id) ?? "REQ-SEMANTIC-ADVISOR-PREVIEW";
|
|
16
|
-
const title = optionalString(args.title) ?? text.split(/[.!?]/, 1)[0] ?? text;
|
|
17
|
-
const source = optionalString(args.source) ?? "mcp://kibi/semantic-advisor";
|
|
18
|
-
const result = analyzeSemanticAdvisorInput({
|
|
19
|
-
payload: {
|
|
20
|
-
type: optionalString(args.type) ?? "req",
|
|
21
|
-
id,
|
|
22
|
-
properties: {
|
|
23
|
-
title,
|
|
24
|
-
status: optionalString(args.status) ?? "open",
|
|
25
|
-
source,
|
|
26
|
-
text_ref: text,
|
|
27
|
-
},
|
|
28
|
-
},
|
|
29
|
-
});
|
|
30
|
-
return {
|
|
31
|
-
content: [
|
|
32
|
-
{
|
|
33
|
-
type: "text",
|
|
34
|
-
text: `kb_semantic_advisor: ${result.receipt.summary} Suggestions: ${result.receipt.suggestions.map((suggestion) => suggestion.kind).join(", ") || "none"}.`,
|
|
35
|
-
},
|
|
36
|
-
],
|
|
37
|
-
structuredContent: {
|
|
38
|
-
receipt: result.receipt,
|
|
39
|
-
warnings: result.warnings,
|
|
40
|
-
},
|
|
41
|
-
};
|
|
3
|
+
return executeSemanticAdvisor(args);
|
|
42
4
|
}
|