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.
Files changed (43) hide show
  1. package/bin/kibi-mcp +1 -1
  2. package/dist/diagnostics-helpers.js +22 -0
  3. package/dist/diagnostics.js +113 -4
  4. package/dist/runtime/mcp-runtime.js +43 -0
  5. package/dist/server/docs.js +11 -10
  6. package/dist/server/json-schema-to-zod.js +73 -2
  7. package/dist/server/session.js +33 -15
  8. package/dist/server/tool-registration.js +35 -59
  9. package/dist/server/tools-runtime.js +52 -2
  10. package/dist/server/tools.js +19 -5
  11. package/dist/server/transport.js +9 -5
  12. package/dist/tools/autopilot-generate.js +37 -646
  13. package/dist/tools/autopilot-test-compat.js +88 -0
  14. package/dist/tools/autopilot-test-parsers.js +120 -0
  15. package/dist/tools/check-config.js +67 -0
  16. package/dist/tools/check-format.js +92 -0
  17. package/dist/tools/check-impact.js +45 -0
  18. package/dist/tools/check-prolog.js +51 -0
  19. package/dist/tools/check.js +13 -190
  20. package/dist/tools/coverage.js +11 -25
  21. package/dist/tools/delete.js +5 -112
  22. package/dist/tools/discovery-adapter.js +23 -0
  23. package/dist/tools/entity-query.js +1 -111
  24. package/dist/tools/find-gaps.js +11 -26
  25. package/dist/tools/graph.js +11 -24
  26. package/dist/tools/model-requirement.js +8 -347
  27. package/dist/tools/mutation-context.js +28 -0
  28. package/dist/tools/query-plan-safety.js +58 -0
  29. package/dist/tools/query.js +5 -50
  30. package/dist/tools/relationship-validation.js +11 -0
  31. package/dist/tools/search.js +4 -37
  32. package/dist/tools/semantic-advisor.js +2 -40
  33. package/dist/tools/skills.js +25 -47
  34. package/dist/tools/sparql.js +12 -93
  35. package/dist/tools/status.js +5 -19
  36. package/dist/tools/suggest-predicates.js +11 -2448
  37. package/dist/tools/upsert.js +21 -794
  38. package/dist/tools/validate-upsert.js +16 -40
  39. package/dist/tools-config.js +75 -764
  40. package/package.json +4 -3
  41. package/dist/semantic-advisor/analyze-prose.js +0 -1367
  42. package/dist/semantic-advisor/prose-coverage-evaluator.js +0 -66
  43. /package/dist/{semantic-advisor/types.js → tools/check-types.js} +0 -0
@@ -1,351 +1,12 @@
1
- import { readFile } from "node:fs/promises";
2
- import path from "node:path";
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
- const STRICT_FALLBACK_CONFIDENCE = 0.69;
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 extracted = extractRequirementClaim(args);
305
- const writeSet = buildStrictWriteSet({
306
- claim: extracted.claim,
307
- statement: extracted.statement,
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
+ }
@@ -1,52 +1,7 @@
1
- import { loadEntities, paginateResults, } from "./entity-query.js";
2
- export { VALID_ENTITY_TYPES } from "./entity-query.js";
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
- const { type, id, tags, sourceFile, limit = 100, offset = 0 } = args;
9
- try {
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
+ }
@@ -1,39 +1,6 @@
1
- import { rankEntities } from "kibi-cli/search-ranking";
2
- import { resolveWorkspaceRoot } from "../workspace.js";
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
- const { query, type, limit = 20, offset = 0 } = args;
7
- const trimmedQuery = query.trim();
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 { analyzeSemanticAdvisorInput } from "../semantic-advisor/analyze-prose.js";
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
- const text = normalizeText(args.text);
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
  }