kibi-mcp 0.15.3 → 0.16.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/dist/diagnostics.js +1 -1
- package/dist/server/docs.js +9 -7
- package/dist/server/tools.js +6 -0
- package/dist/tools/autopilot-discovery.js +1 -1
- package/dist/tools/autopilot-generate.js +39 -17
- package/dist/tools/suggest-predicates.js +554 -0
- package/dist/tools/upsert.js +11 -0
- package/dist/tools-config.js +48 -4
- package/package.json +2 -2
package/dist/diagnostics.js
CHANGED
|
@@ -37,7 +37,7 @@ let diagnosticUsageLogPath = null;
|
|
|
37
37
|
export function initializeDiagnosticMode(enabled = DIAGNOSTIC_MODE_ENABLED) {
|
|
38
38
|
diagnosticUsageLogPath = null;
|
|
39
39
|
if (!enabled) {
|
|
40
|
-
|
|
40
|
+
process.env.KIBI_MCP_DIAGNOSTIC_MODE = "0";
|
|
41
41
|
return;
|
|
42
42
|
}
|
|
43
43
|
const workspaceRoot = resolveWorkspaceRoot();
|
package/dist/server/docs.js
CHANGED
|
@@ -38,6 +38,7 @@ function renderToolsDoc() {
|
|
|
38
38
|
lines.push("");
|
|
39
39
|
lines.push("Modeling note: Kibi has eight core entity types grouped into common authoring (req, scenario, test, fact) and supporting/system (adr, flag, event, symbol).");
|
|
40
40
|
lines.push("Only strict domain facts (`fact_kind: subject` + `property_value`) participate in contradiction inference; use `flag` for runtime/config gates and `fact_kind: observation` or `meta` for bug/workaround notes.");
|
|
41
|
+
lines.push("Predicate flow: before writing ontology prose, call `kb_suggest_predicates`; apply a suggested `fact_kind: predicate` via `requires_predicate`, or record the returned `review:ontology-gap` observation when no predicate fits.");
|
|
41
42
|
return lines.join("\n");
|
|
42
43
|
}
|
|
43
44
|
export const PROMPTS = [
|
|
@@ -135,8 +136,9 @@ export const PROMPTS = [
|
|
|
135
136
|
"3. **Create-before-link**: Create endpoint entities with `kb_upsert` before linking them.",
|
|
136
137
|
"4. **Validate intent**: If creating links, call `kb_query` for both endpoint IDs first to ensure they exist.",
|
|
137
138
|
"5. **Model requirements as facts**: For new/updated reqs, create/reuse fact entities first, then express req semantics with `constrains` + `requires_property` (automated via `kb_model_requirement`).",
|
|
138
|
-
"
|
|
139
|
-
"
|
|
139
|
+
"6. **Suggest predicates before prose**: For ontology-lane requirements, spell out the prose claim and call `kb_suggest_predicates` before writing `fact_kind: observation`. Apply the selected `fact_kind: predicate` applyPlan, then attach the returned `relationshipPlan` as `requires_predicate` while preserving existing req metadata; use the returned `review:ontology-gap` observation when no predicate fits.",
|
|
140
|
+
"7. **Mutate**: Call `kb_upsert` for create/update, or `kb_delete` for explicit removals.",
|
|
141
|
+
"8. **Targeted checks**: Run `kb_check` after meaningful mutations; specify only the rules you need.",
|
|
140
142
|
"",
|
|
141
143
|
"If a tool returns empty results, do not assume failure. Re-check filters (type, id, tags, sourceFile, limit, or offset).",
|
|
142
144
|
].join("\n"),
|
|
@@ -203,16 +205,16 @@ function registerDocResources() {
|
|
|
203
205
|
"4. Reuse the same constrained fact ID across related requirements; vary property facts only when semantics differ",
|
|
204
206
|
'5. `kb_check` with `{ "rules": ["required-fields","no-dangling-refs"] }` for targeted validation',
|
|
205
207
|
"",
|
|
208
|
+
"## Model requirements as ontology predicates",
|
|
209
|
+
'1. Spell out the requirement prose and call `kb_suggest_predicates` with `{ "text": "...", "requirementId": "REQ-..." }`',
|
|
210
|
+
"2. If candidates are returned, apply the top or user-selected `structuredContent.applyPlan` to create `fact_kind: predicate`, then attach `structuredContent.relationshipPlan` with `requires_predicate` while preserving existing req metadata",
|
|
211
|
+
"3. If no candidate fits, apply or review the returned `review:ontology-gap` observation instead of silently writing prose",
|
|
212
|
+
"",
|
|
206
213
|
"Note: Kibi has eight core entity types. Create or reuse `fact` entities first, then create `req` entities and link with `constrains` and `requires_property` (create-before-link).",
|
|
207
214
|
"Only strict domain facts are contradiction-safe. Use `flag` for runtime/config gates; use `fact` with `fact_kind: observation` or `meta` for bug/workaround notes.",
|
|
208
215
|
"",
|
|
209
216
|
"## Find missing coverage",
|
|
210
217
|
'1. `kb_find_gaps` with `{ "type": "req", "missingRelationships": ["specified_by", "verified_by"] }` to find under-linked requirements',
|
|
211
|
-
"",
|
|
212
|
-
"## Find missing coverage",
|
|
213
|
-
"",
|
|
214
|
-
"## Find missing coverage",
|
|
215
|
-
'1. `kb_find_gaps` with `{ "type": "req", "missingRelationships": ["specified_by", "verified_by"] }` to find under-linked requirements',
|
|
216
218
|
'2. `kb_coverage` with `{ "by": "req", "includePassing": false }` to review evaluated coverage rows',
|
|
217
219
|
'3. `kb_graph` with `{ "seedIds": ["REQ-001"], "direction": "both", "depth": 2 }` to inspect neighboring entities',
|
|
218
220
|
"",
|
package/dist/server/tools.js
CHANGED
|
@@ -31,6 +31,7 @@ import { handleKbQuery } from "../tools/query.js";
|
|
|
31
31
|
import { handleKbSearch } from "../tools/search.js";
|
|
32
32
|
import { handleKbSkillsList, handleKbSkillsLoad, handleKbSkillsRead, } from "../tools/skills.js";
|
|
33
33
|
import { handleKbStatus } from "../tools/status.js";
|
|
34
|
+
import { handleKbSuggestPredicates, } from "../tools/suggest-predicates.js";
|
|
34
35
|
import { handleKbUpsert } from "../tools/upsert.js";
|
|
35
36
|
const DEFAULT_TOOL_TIMEOUT_MS = 90_000;
|
|
36
37
|
const TOOL_TIMEOUT_ENV = "KIBI_MCP_TOOL_TIMEOUT_MS";
|
|
@@ -86,6 +87,7 @@ const DEFAULT_TOOLS_RUNTIME = {
|
|
|
86
87
|
handleKbSkillsRead,
|
|
87
88
|
handleKbUpsert,
|
|
88
89
|
handleKbModelRequirement,
|
|
90
|
+
handleKbSuggestPredicates,
|
|
89
91
|
handleKbAutopilotGenerate,
|
|
90
92
|
};
|
|
91
93
|
// implements REQ-008
|
|
@@ -407,6 +409,10 @@ runtime = DEFAULT_TOOLS_RUNTIME) {
|
|
|
407
409
|
const prolog = await runtime.ensureProlog();
|
|
408
410
|
return runtime.handleKbModelRequirement(prolog, args);
|
|
409
411
|
}, runtime);
|
|
412
|
+
addTool(server, "kb_suggest_predicates", toolDef("kb_suggest_predicates").description, toolDef("kb_suggest_predicates").inputSchema, async (args) => {
|
|
413
|
+
const prolog = await runtime.ensureProlog();
|
|
414
|
+
return runtime.handleKbSuggestPredicates(prolog, args);
|
|
415
|
+
}, runtime);
|
|
410
416
|
addTool(server, "kb_autopilot_generate", toolDef("kb_autopilot_generate").description, toolDef("kb_autopilot_generate").inputSchema, async (args) => {
|
|
411
417
|
const prolog = await runtime.ensureProlog();
|
|
412
418
|
return runtime.handleKbAutopilotGenerate(prolog, args);
|
|
@@ -847,7 +847,7 @@ export async function classifyActivationState(workspaceRoot, prolog) {
|
|
|
847
847
|
}
|
|
848
848
|
}
|
|
849
849
|
// Recursively collect markdown files under `dir`, excluding known ignore dirs.
|
|
850
|
-
function collectMarkdownFiles(dir, workspaceRoot, vendoredRoots) {
|
|
850
|
+
export function collectMarkdownFiles(dir, workspaceRoot, vendoredRoots) {
|
|
851
851
|
const results = [];
|
|
852
852
|
if (!fs.existsSync(dir))
|
|
853
853
|
return results;
|
|
@@ -6,6 +6,25 @@ import { buildGenericMarkdownCandidates, buildNormativeRequirementCandidates, bu
|
|
|
6
6
|
import { discoverProviderEvidence, resolveActivationPolicy, } from "./autopilot-discovery.js";
|
|
7
7
|
import { loadEntities } from "./entity-query.js";
|
|
8
8
|
import { getWorkspaceMigrationWarning } from "./model-requirement.js";
|
|
9
|
+
const defaultAutopilotGenerateDeps = {
|
|
10
|
+
buildGenericMarkdownCandidates,
|
|
11
|
+
buildNormativeRequirementCandidates,
|
|
12
|
+
buildProviderEvidenceCandidates,
|
|
13
|
+
buildSymbolManifestCandidates,
|
|
14
|
+
buildTypedMarkdownCandidates,
|
|
15
|
+
collectSourceOnlyAuthoringSignals,
|
|
16
|
+
discoverProviderEvidence,
|
|
17
|
+
getWorkspaceMigrationWarning,
|
|
18
|
+
loadEntities,
|
|
19
|
+
resolveActivationPolicy,
|
|
20
|
+
};
|
|
21
|
+
let autopilotGenerateDeps = defaultAutopilotGenerateDeps;
|
|
22
|
+
export function _setAutopilotGenerateDepsForTests(deps) {
|
|
23
|
+
autopilotGenerateDeps = { ...defaultAutopilotGenerateDeps, ...deps };
|
|
24
|
+
}
|
|
25
|
+
export function _resetAutopilotGenerateDepsForTests() {
|
|
26
|
+
autopilotGenerateDeps = defaultAutopilotGenerateDeps;
|
|
27
|
+
}
|
|
9
28
|
function clamp(value, min, max) {
|
|
10
29
|
return Math.max(min, Math.min(max, value));
|
|
11
30
|
}
|
|
@@ -377,7 +396,7 @@ _prolog, args) {
|
|
|
377
396
|
// Gather existing entity ids to suppress duplicates
|
|
378
397
|
let existingIds = new Set();
|
|
379
398
|
try {
|
|
380
|
-
const entities = await loadEntities(prolog, {});
|
|
399
|
+
const entities = await autopilotGenerateDeps.loadEntities(prolog, {});
|
|
381
400
|
for (const e of entities) {
|
|
382
401
|
const id = String(e.id ?? "");
|
|
383
402
|
if (id)
|
|
@@ -389,10 +408,10 @@ _prolog, args) {
|
|
|
389
408
|
existingIds = new Set();
|
|
390
409
|
}
|
|
391
410
|
const workspaceRoot = resolveWorkspaceRoot();
|
|
392
|
-
const activation = await resolveActivationPolicy(workspaceRoot, prolog);
|
|
411
|
+
const activation = await autopilotGenerateDeps.resolveActivationPolicy(workspaceRoot, prolog);
|
|
393
412
|
const activationState = activation.activationState;
|
|
394
|
-
const activationDiscovery = discoverProviderEvidence(workspaceRoot, activation);
|
|
395
|
-
const migrationWarning = await getWorkspaceMigrationWarning(workspaceRoot);
|
|
413
|
+
const activationDiscovery = autopilotGenerateDeps.discoverProviderEvidence(workspaceRoot, activation);
|
|
414
|
+
const migrationWarning = await autopilotGenerateDeps.getWorkspaceMigrationWarning(workspaceRoot);
|
|
396
415
|
const declaredContext = normalizeBootstrapContext(bootstrapContext);
|
|
397
416
|
const discoveredCandidatePaths = activationDiscovery.evidence.reduce((acc, item) => {
|
|
398
417
|
const relativePath = item.relativePath;
|
|
@@ -414,7 +433,7 @@ _prolog, args) {
|
|
|
414
433
|
markdownFiles: [],
|
|
415
434
|
evidence: candidateDiscovery.evidence.filter((item) => item.kind !== "generic_markdown"),
|
|
416
435
|
};
|
|
417
|
-
let sourceOnlySignals = collectSourceOnlyAuthoringSignals(guidanceDiscovery, {
|
|
436
|
+
let sourceOnlySignals = autopilotGenerateDeps.collectSourceOnlyAuthoringSignals(guidanceDiscovery, {
|
|
418
437
|
ids: existingIds,
|
|
419
438
|
workspaceRoot,
|
|
420
439
|
}, normalizedMinConfidence);
|
|
@@ -435,28 +454,31 @@ _prolog, args) {
|
|
|
435
454
|
return `${entityType}::${String(title).trim().toLowerCase().replace(/\s+/g, " ")}`;
|
|
436
455
|
}
|
|
437
456
|
if (activation.allowCandidateGeneration) {
|
|
438
|
-
typedMarkdownCandidates =
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
457
|
+
typedMarkdownCandidates =
|
|
458
|
+
autopilotGenerateDeps.buildTypedMarkdownCandidates(candidateDiscovery, {
|
|
459
|
+
ids: existingIds,
|
|
460
|
+
workspaceRoot,
|
|
461
|
+
});
|
|
462
|
+
manifestCandidates = autopilotGenerateDeps.buildSymbolManifestCandidates(candidateDiscovery, {
|
|
443
463
|
ids: existingIds,
|
|
444
464
|
workspaceRoot,
|
|
445
465
|
});
|
|
446
466
|
if (includeGenericMarkdown) {
|
|
447
|
-
genericCandidates = buildGenericMarkdownCandidates(candidateDiscovery, {
|
|
467
|
+
genericCandidates = autopilotGenerateDeps.buildGenericMarkdownCandidates(candidateDiscovery, {
|
|
448
468
|
ids: existingIds,
|
|
449
469
|
workspaceRoot,
|
|
450
470
|
}, normalizedMinConfidence);
|
|
451
|
-
normativeRequirementCandidates =
|
|
471
|
+
normativeRequirementCandidates =
|
|
472
|
+
autopilotGenerateDeps.buildNormativeRequirementCandidates(candidateDiscovery, {
|
|
473
|
+
ids: existingIds,
|
|
474
|
+
workspaceRoot,
|
|
475
|
+
}, normalizedMinConfidence);
|
|
476
|
+
}
|
|
477
|
+
providerEvidenceCandidates =
|
|
478
|
+
autopilotGenerateDeps.buildProviderEvidenceCandidates(candidateDiscovery, {
|
|
452
479
|
ids: existingIds,
|
|
453
480
|
workspaceRoot,
|
|
454
481
|
}, normalizedMinConfidence);
|
|
455
|
-
}
|
|
456
|
-
providerEvidenceCandidates = buildProviderEvidenceCandidates(candidateDiscovery, {
|
|
457
|
-
ids: existingIds,
|
|
458
|
-
workspaceRoot,
|
|
459
|
-
}, normalizedMinConfidence);
|
|
460
482
|
allCandidates = [
|
|
461
483
|
...typedMarkdownCandidates,
|
|
462
484
|
...manifestCandidates,
|
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { parseEntityFromList, parseListOfLists } from "kibi-cli/prolog/codec";
|
|
3
|
+
const DEFAULT_MIN_SCORE = 0.35;
|
|
4
|
+
const DEFAULT_MAX_CANDIDATES = 5;
|
|
5
|
+
const BUILT_IN_PREDICATE_SCHEMAS = [
|
|
6
|
+
{
|
|
7
|
+
id: "FACT-SCHEMA-STATE",
|
|
8
|
+
predicate_name: "state",
|
|
9
|
+
title: "State assertion",
|
|
10
|
+
description: "A subject has or enters a named state.",
|
|
11
|
+
argument_names: ["subject", "state"],
|
|
12
|
+
argument_types: ["entity", "state"],
|
|
13
|
+
keywords: ["state", "mode", "idle", "active", "draft", "edit"],
|
|
14
|
+
examples: ["state(editor.annotation, idle)"],
|
|
15
|
+
tags: ["state", "workflow"],
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
id: "FACT-SCHEMA-TRANSITION",
|
|
19
|
+
predicate_name: "transition",
|
|
20
|
+
title: "State transition",
|
|
21
|
+
description: "A subject transitions between states because of a trigger.",
|
|
22
|
+
argument_names: ["subject", "from_state", "to_state", "trigger"],
|
|
23
|
+
argument_types: ["entity", "state", "state", "trigger"],
|
|
24
|
+
keywords: [
|
|
25
|
+
"transition",
|
|
26
|
+
"enter",
|
|
27
|
+
"leave",
|
|
28
|
+
"idle",
|
|
29
|
+
"navigate",
|
|
30
|
+
"cancel",
|
|
31
|
+
"escape",
|
|
32
|
+
],
|
|
33
|
+
examples: ["transition(editor.annotation, draft, idle, navigation)"],
|
|
34
|
+
tags: ["state", "workflow"],
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: "FACT-SCHEMA-GUARD",
|
|
38
|
+
predicate_name: "guard",
|
|
39
|
+
title: "Behavior guard",
|
|
40
|
+
description: "A condition gates or forbids behavior for a subject.",
|
|
41
|
+
argument_names: ["subject", "condition", "expected"],
|
|
42
|
+
argument_types: ["entity", "condition", "boolean"],
|
|
43
|
+
keywords: ["guard", "unless", "readonly", "scrubbing"],
|
|
44
|
+
examples: ["guard(editor.annotation, isReadOnly, false)"],
|
|
45
|
+
tags: ["guard", "workflow"],
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: "FACT-SCHEMA-HAS-UNSAVED-CHANGES",
|
|
49
|
+
predicate_name: "has_unsaved_changes",
|
|
50
|
+
title: "Unsaved change state",
|
|
51
|
+
description: "A subject has unsaved or dirty local edits.",
|
|
52
|
+
argument_names: ["subject", "expected"],
|
|
53
|
+
argument_types: ["entity", "boolean"],
|
|
54
|
+
keywords: ["unsaved", "dirty", "draft", "edits", "changes"],
|
|
55
|
+
examples: ["has_unsaved_changes(editor.annotation, true)"],
|
|
56
|
+
tags: ["state", "persistence"],
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: "FACT-SCHEMA-COMMIT-ACTION",
|
|
60
|
+
predicate_name: "commit_action",
|
|
61
|
+
title: "Commit or save action",
|
|
62
|
+
description: "A trigger commits, saves, or persists a subject within a scope.",
|
|
63
|
+
argument_names: ["subject", "trigger", "scope"],
|
|
64
|
+
argument_types: ["entity", "trigger", "scope"],
|
|
65
|
+
keywords: [
|
|
66
|
+
"save",
|
|
67
|
+
"saves",
|
|
68
|
+
"saved",
|
|
69
|
+
"auto-save",
|
|
70
|
+
"autosave",
|
|
71
|
+
"commit",
|
|
72
|
+
"persist",
|
|
73
|
+
"navigation",
|
|
74
|
+
"navigates",
|
|
75
|
+
"draft",
|
|
76
|
+
],
|
|
77
|
+
examples: ["commit_action(editor.annotation, navigation, draft)"],
|
|
78
|
+
tags: ["persistence", "workflow"],
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: "FACT-SCHEMA-DISCARD-ACTION",
|
|
82
|
+
predicate_name: "discard_action",
|
|
83
|
+
title: "Discard action",
|
|
84
|
+
description: "A trigger discards or cancels changes for a subject within a scope.",
|
|
85
|
+
argument_names: ["subject", "trigger", "scope"],
|
|
86
|
+
argument_types: ["entity", "trigger", "scope"],
|
|
87
|
+
keywords: ["discard", "cancel", "escape", "revert", "without save"],
|
|
88
|
+
examples: ["discard_action(editor.annotation, escape, active_annotation)"],
|
|
89
|
+
tags: ["persistence", "workflow"],
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
id: "FACT-SCHEMA-ACCESSIBILITY",
|
|
93
|
+
predicate_name: "accessibility_requirement",
|
|
94
|
+
title: "Accessibility requirement",
|
|
95
|
+
description: "A subject must satisfy an accessibility standard or severity target.",
|
|
96
|
+
argument_names: ["subject", "standard", "severity"],
|
|
97
|
+
argument_types: ["entity", "standard", "severity"],
|
|
98
|
+
keywords: ["accessibility", "a11y", "wcag", "keyboard", "screen reader"],
|
|
99
|
+
examples: ["accessibility_requirement(game.flow, WCAG, high)"],
|
|
100
|
+
tags: ["accessibility", "quality"],
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
id: "FACT-SCHEMA-RETENTION-POLICY",
|
|
104
|
+
predicate_name: "retention_policy",
|
|
105
|
+
title: "Retention policy",
|
|
106
|
+
description: "A subject is retained for a bounded duration.",
|
|
107
|
+
argument_names: ["subject", "duration", "unit"],
|
|
108
|
+
argument_types: ["entity", "number", "unit"],
|
|
109
|
+
keywords: ["retain", "retained", "retention", "days", "months", "years"],
|
|
110
|
+
examples: ["retention_policy(customer.data, 7, years)"],
|
|
111
|
+
tags: ["data", "policy"],
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
id: "FACT-SCHEMA-RESOURCE-CONSTRAINT",
|
|
115
|
+
predicate_name: "resource_constraint",
|
|
116
|
+
title: "Resource constraint",
|
|
117
|
+
description: "A subject constrains a resource by operator, threshold, and unit.",
|
|
118
|
+
argument_names: ["subject", "resource", "operator", "threshold", "unit"],
|
|
119
|
+
argument_types: ["entity", "resource", "operator", "number", "unit"],
|
|
120
|
+
keywords: ["limit", "maximum", "minimum", "latency", "timeout", "size"],
|
|
121
|
+
examples: ["resource_constraint(api.search, latency, lte, 200, ms)"],
|
|
122
|
+
tags: ["performance", "constraint"],
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
id: "FACT-SCHEMA-FEATURE-GATE",
|
|
126
|
+
predicate_name: "feature_gate",
|
|
127
|
+
title: "Feature gate",
|
|
128
|
+
description: "A subject is controlled by a runtime or configuration gate.",
|
|
129
|
+
argument_names: ["subject", "gate", "expected"],
|
|
130
|
+
argument_types: ["entity", "flag", "boolean"],
|
|
131
|
+
keywords: ["flag", "feature gate", "enabled", "disabled", "kill switch"],
|
|
132
|
+
examples: ["feature_gate(checkout.v2, checkoutV2Enabled, true)"],
|
|
133
|
+
tags: ["flag", "runtime"],
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
id: "FACT-SCHEMA-EVENT-PUBLISH",
|
|
137
|
+
predicate_name: "publishes_event",
|
|
138
|
+
title: "Event publication",
|
|
139
|
+
description: "A subject publishes a domain or system event.",
|
|
140
|
+
argument_names: ["subject", "event"],
|
|
141
|
+
argument_types: ["entity", "event"],
|
|
142
|
+
keywords: ["publish", "publishes", "emit", "emits", "event"],
|
|
143
|
+
examples: ["publishes_event(order.checkout, OrderSubmitted)"],
|
|
144
|
+
tags: ["event", "architecture"],
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
id: "FACT-SCHEMA-ACCEPTANCE-RULE",
|
|
148
|
+
predicate_name: "acceptance_rule",
|
|
149
|
+
title: "Acceptance rule",
|
|
150
|
+
description: "A subject has an observable acceptance outcome.",
|
|
151
|
+
argument_names: ["subject", "outcome"],
|
|
152
|
+
argument_types: ["entity", "outcome"],
|
|
153
|
+
keywords: [
|
|
154
|
+
"acceptance",
|
|
155
|
+
"observable",
|
|
156
|
+
"outcome",
|
|
157
|
+
"must show",
|
|
158
|
+
"must display",
|
|
159
|
+
],
|
|
160
|
+
examples: ["acceptance_rule(search.results, shows_empty_state)"],
|
|
161
|
+
tags: ["acceptance", "quality"],
|
|
162
|
+
},
|
|
163
|
+
];
|
|
164
|
+
function normalizeText(text) {
|
|
165
|
+
const normalized = String(text ?? "").trim();
|
|
166
|
+
if (!normalized) {
|
|
167
|
+
throw new Error("Predicate suggestion failed: text must be a non-empty string");
|
|
168
|
+
}
|
|
169
|
+
return normalized;
|
|
170
|
+
}
|
|
171
|
+
function normalizeOptionalString(value) {
|
|
172
|
+
const normalized = String(value ?? "").trim();
|
|
173
|
+
return normalized.length > 0 ? normalized : undefined;
|
|
174
|
+
}
|
|
175
|
+
function clampInteger(value, fallback, min, max) {
|
|
176
|
+
const numeric = typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
177
|
+
return Math.min(max, Math.max(min, Math.trunc(numeric)));
|
|
178
|
+
}
|
|
179
|
+
function clampScore(value) {
|
|
180
|
+
const numeric = typeof value === "number" && Number.isFinite(value)
|
|
181
|
+
? value
|
|
182
|
+
: DEFAULT_MIN_SCORE;
|
|
183
|
+
return Math.min(1, Math.max(0, numeric));
|
|
184
|
+
}
|
|
185
|
+
function slug(value) {
|
|
186
|
+
return value
|
|
187
|
+
.trim()
|
|
188
|
+
.toLowerCase()
|
|
189
|
+
.replace(/[^a-z0-9]+/g, "_")
|
|
190
|
+
.replace(/^_+|_+$/g, "");
|
|
191
|
+
}
|
|
192
|
+
function hashId(prefix, parts) {
|
|
193
|
+
const digest = createHash("sha256")
|
|
194
|
+
.update(parts.join("\u0000"))
|
|
195
|
+
.digest("hex")
|
|
196
|
+
.slice(0, 12)
|
|
197
|
+
.toUpperCase();
|
|
198
|
+
return `${prefix}-${digest}`;
|
|
199
|
+
}
|
|
200
|
+
function inferSubject(text, subjectHint) {
|
|
201
|
+
const explicit = normalizeOptionalString(subjectHint);
|
|
202
|
+
if (explicit)
|
|
203
|
+
return explicit;
|
|
204
|
+
const lower = text.toLowerCase();
|
|
205
|
+
if (lower.includes("annotation"))
|
|
206
|
+
return "editor.annotation";
|
|
207
|
+
if (lower.includes("editor"))
|
|
208
|
+
return "editor";
|
|
209
|
+
if (lower.includes("session"))
|
|
210
|
+
return "session";
|
|
211
|
+
if (lower.includes("customer data"))
|
|
212
|
+
return "customer.data";
|
|
213
|
+
if (lower.includes("user"))
|
|
214
|
+
return "user";
|
|
215
|
+
return "requirement.subject";
|
|
216
|
+
}
|
|
217
|
+
function inferTrigger(text) {
|
|
218
|
+
const lower = text.toLowerCase();
|
|
219
|
+
if (lower.includes("navigate"))
|
|
220
|
+
return "navigation";
|
|
221
|
+
if (lower.includes("escape"))
|
|
222
|
+
return "escape";
|
|
223
|
+
if (lower.includes("cancel"))
|
|
224
|
+
return "cancel";
|
|
225
|
+
if (lower.includes("submit"))
|
|
226
|
+
return "submit";
|
|
227
|
+
return "unspecified_trigger";
|
|
228
|
+
}
|
|
229
|
+
function inferScope(text) {
|
|
230
|
+
const lower = text.toLowerCase();
|
|
231
|
+
if (lower.includes("draft"))
|
|
232
|
+
return "draft";
|
|
233
|
+
if (lower.includes("annotation"))
|
|
234
|
+
return "active_annotation";
|
|
235
|
+
if (lower.includes("session"))
|
|
236
|
+
return "session";
|
|
237
|
+
return "subject";
|
|
238
|
+
}
|
|
239
|
+
function inferArgs(schema, text, subject) {
|
|
240
|
+
const lower = text.toLowerCase();
|
|
241
|
+
switch (schema.predicate_name) {
|
|
242
|
+
case "state":
|
|
243
|
+
return [subject, lower.includes("idle") ? "idle" : "active"];
|
|
244
|
+
case "transition":
|
|
245
|
+
return [
|
|
246
|
+
subject,
|
|
247
|
+
lower.includes("edit") ? "edit" : "draft",
|
|
248
|
+
lower.includes("idle") ? "idle" : "active",
|
|
249
|
+
inferTrigger(text),
|
|
250
|
+
];
|
|
251
|
+
case "guard":
|
|
252
|
+
return [
|
|
253
|
+
subject,
|
|
254
|
+
lower.includes("readonly") ? "isReadOnly" : "condition",
|
|
255
|
+
"true",
|
|
256
|
+
];
|
|
257
|
+
case "has_unsaved_changes":
|
|
258
|
+
return [subject, lower.includes("no unsaved") ? "false" : "true"];
|
|
259
|
+
case "commit_action":
|
|
260
|
+
case "discard_action":
|
|
261
|
+
return [subject, inferTrigger(text), inferScope(text)];
|
|
262
|
+
case "accessibility_requirement":
|
|
263
|
+
return [
|
|
264
|
+
subject,
|
|
265
|
+
lower.includes("wcag") ? "WCAG" : "accessibility",
|
|
266
|
+
"required",
|
|
267
|
+
];
|
|
268
|
+
case "retention_policy":
|
|
269
|
+
return [subject, inferDuration(text), inferDurationUnit(text)];
|
|
270
|
+
case "resource_constraint":
|
|
271
|
+
return [
|
|
272
|
+
subject,
|
|
273
|
+
inferResource(text),
|
|
274
|
+
inferOperator(text),
|
|
275
|
+
inferNumber(text),
|
|
276
|
+
inferUnit(text),
|
|
277
|
+
];
|
|
278
|
+
case "feature_gate":
|
|
279
|
+
return [
|
|
280
|
+
subject,
|
|
281
|
+
inferGate(text),
|
|
282
|
+
lower.includes("disabled") ? "false" : "true",
|
|
283
|
+
];
|
|
284
|
+
case "publishes_event":
|
|
285
|
+
return [subject, inferEvent(text)];
|
|
286
|
+
case "acceptance_rule":
|
|
287
|
+
return [subject, slug(text).slice(0, 64) || "observable_outcome"];
|
|
288
|
+
default:
|
|
289
|
+
return schema.argument_names.map((name) => name === "subject" ? subject : "unknown");
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
function inferDuration(text) {
|
|
293
|
+
return text.match(/\b\d+\b/)?.[0] ?? "1";
|
|
294
|
+
}
|
|
295
|
+
function inferDurationUnit(text) {
|
|
296
|
+
const lower = text.toLowerCase();
|
|
297
|
+
if (lower.includes("year"))
|
|
298
|
+
return "years";
|
|
299
|
+
if (lower.includes("month"))
|
|
300
|
+
return "months";
|
|
301
|
+
if (lower.includes("day"))
|
|
302
|
+
return "days";
|
|
303
|
+
return "unit";
|
|
304
|
+
}
|
|
305
|
+
function inferResource(text) {
|
|
306
|
+
const lower = text.toLowerCase();
|
|
307
|
+
if (lower.includes("latency"))
|
|
308
|
+
return "latency";
|
|
309
|
+
if (lower.includes("timeout"))
|
|
310
|
+
return "timeout";
|
|
311
|
+
if (lower.includes("size"))
|
|
312
|
+
return "size";
|
|
313
|
+
return "resource";
|
|
314
|
+
}
|
|
315
|
+
function inferOperator(text) {
|
|
316
|
+
const lower = text.toLowerCase();
|
|
317
|
+
if (lower.includes("minimum") || lower.includes("at least"))
|
|
318
|
+
return "gte";
|
|
319
|
+
if (lower.includes("not exceed") ||
|
|
320
|
+
lower.includes("not be more than") ||
|
|
321
|
+
lower.includes("no more than") ||
|
|
322
|
+
lower.includes("at most") ||
|
|
323
|
+
lower.includes("maximum")) {
|
|
324
|
+
return "lte";
|
|
325
|
+
}
|
|
326
|
+
if (lower.includes("not"))
|
|
327
|
+
return "neq";
|
|
328
|
+
return "lte";
|
|
329
|
+
}
|
|
330
|
+
function inferNumber(text) {
|
|
331
|
+
return text.match(/\b\d+(?:\.\d+)?\b/)?.[0] ?? "0";
|
|
332
|
+
}
|
|
333
|
+
function inferUnit(text) {
|
|
334
|
+
const lower = text.toLowerCase();
|
|
335
|
+
if (lower.includes("ms"))
|
|
336
|
+
return "ms";
|
|
337
|
+
if (lower.includes("seconds"))
|
|
338
|
+
return "seconds";
|
|
339
|
+
if (lower.includes("mb"))
|
|
340
|
+
return "mb";
|
|
341
|
+
return "unit";
|
|
342
|
+
}
|
|
343
|
+
function inferGate(text) {
|
|
344
|
+
const quoted = text.match(/[`'"](?<gate>[A-Za-z0-9_.:-]+)[`'"]/)?.groups
|
|
345
|
+
?.gate;
|
|
346
|
+
return quoted ?? "feature_gate";
|
|
347
|
+
}
|
|
348
|
+
function inferEvent(text) {
|
|
349
|
+
const eventName = text.match(/\b[A-Z][A-Za-z0-9]+Event\b/)?.[0];
|
|
350
|
+
return eventName ?? "domain_event";
|
|
351
|
+
}
|
|
352
|
+
function scoreSchema(schema, text) {
|
|
353
|
+
const lower = text.toLowerCase();
|
|
354
|
+
const keywordHits = schema.keywords.filter((keyword) => lower.includes(keyword.toLowerCase())).length;
|
|
355
|
+
if (keywordHits === 0)
|
|
356
|
+
return 0;
|
|
357
|
+
const normalized = keywordHits / Math.max(3, schema.keywords.length / 2);
|
|
358
|
+
const score = Math.min(0.98, 0.24 + normalized * 0.5 + keywordHits * 0.06);
|
|
359
|
+
return Math.round(score * 100) / 100;
|
|
360
|
+
}
|
|
361
|
+
function schemaForCandidate(schema) {
|
|
362
|
+
return {
|
|
363
|
+
id: schema.id,
|
|
364
|
+
predicate_name: schema.predicate_name,
|
|
365
|
+
title: schema.title,
|
|
366
|
+
description: schema.description,
|
|
367
|
+
argument_names: schema.argument_names,
|
|
368
|
+
argument_types: schema.argument_types,
|
|
369
|
+
examples: schema.examples,
|
|
370
|
+
tags: schema.tags,
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
async function loadExistingPredicateSchemas(prolog, includeExistingSchemas, warnings) {
|
|
374
|
+
if (!includeExistingSchemas || prolog === null) {
|
|
375
|
+
return [];
|
|
376
|
+
}
|
|
377
|
+
try {
|
|
378
|
+
const queryResult = await prolog.query("findall([Id,'fact',Props], (kb_entity(Id, 'fact', Props), member(fact_kind=predicate_schema, Props)), Results)");
|
|
379
|
+
if (!queryResult.success) {
|
|
380
|
+
throw new Error(queryResult.error || "Query failed with unknown error");
|
|
381
|
+
}
|
|
382
|
+
const facts = queryResult.bindings.Results
|
|
383
|
+
? parseListOfLists(queryResult.bindings.Results).map(parseEntityFromList)
|
|
384
|
+
: [];
|
|
385
|
+
return facts.flatMap((fact) => predicateSchemaFromEntity(fact));
|
|
386
|
+
}
|
|
387
|
+
catch (error) {
|
|
388
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
389
|
+
warnings.push(`Existing predicate_schema facts could not be loaded: ${message}`);
|
|
390
|
+
return [];
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
function predicateSchemaFromEntity(entity) {
|
|
394
|
+
if (entity.fact_kind !== "predicate_schema")
|
|
395
|
+
return [];
|
|
396
|
+
const predicateName = normalizeOptionalString(typeof entity.predicate_name === "string"
|
|
397
|
+
? entity.predicate_name
|
|
398
|
+
: undefined);
|
|
399
|
+
if (!predicateName)
|
|
400
|
+
return [];
|
|
401
|
+
return [
|
|
402
|
+
{
|
|
403
|
+
id: String(entity.id ?? hashId("FACT-SCHEMA", [predicateName])),
|
|
404
|
+
predicate_name: predicateName,
|
|
405
|
+
title: String(entity.title ?? predicateName),
|
|
406
|
+
description: String(entity.description ??
|
|
407
|
+
`Project-local ${predicateName} predicate schema.`),
|
|
408
|
+
argument_names: stringArray(entity.argument_names),
|
|
409
|
+
argument_types: stringArray(entity.argument_types),
|
|
410
|
+
keywords: [
|
|
411
|
+
predicateName,
|
|
412
|
+
...stringArray(entity.aliases),
|
|
413
|
+
...stringArray(entity.tags),
|
|
414
|
+
],
|
|
415
|
+
examples: stringArray(entity.examples),
|
|
416
|
+
tags: stringArray(entity.tags),
|
|
417
|
+
},
|
|
418
|
+
];
|
|
419
|
+
}
|
|
420
|
+
function stringArray(value) {
|
|
421
|
+
if (!Array.isArray(value))
|
|
422
|
+
return [];
|
|
423
|
+
return value.flatMap((item) => {
|
|
424
|
+
const normalized = normalizeOptionalString(typeof item === "string" ? item : undefined);
|
|
425
|
+
return normalized ? [normalized] : [];
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
function buildSuggestion(schema, text, subject, score) {
|
|
429
|
+
const predicateArgs = inferArgs(schema, text, subject);
|
|
430
|
+
const canonicalKey = `${schema.predicate_name}(${predicateArgs.join(",")})`;
|
|
431
|
+
return {
|
|
432
|
+
id: hashId("SUGGEST", [schema.id, canonicalKey, text]),
|
|
433
|
+
predicate_name: schema.predicate_name,
|
|
434
|
+
predicate_args: predicateArgs,
|
|
435
|
+
canonical_key: canonicalKey,
|
|
436
|
+
polarity: "assert",
|
|
437
|
+
score,
|
|
438
|
+
rationale: `Matched ${schema.predicate_name} because the prose overlaps with ${schema.tags.join(", ")} cues.`,
|
|
439
|
+
schema: schemaForCandidate(schema),
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
function buildPredicateApplyPlan(suggestion, args) {
|
|
443
|
+
const factId = hashId("FACT-PRED", [
|
|
444
|
+
args.requirementId ?? "",
|
|
445
|
+
args.source ?? "",
|
|
446
|
+
suggestion.canonical_key,
|
|
447
|
+
]);
|
|
448
|
+
return [
|
|
449
|
+
{
|
|
450
|
+
type: "fact",
|
|
451
|
+
id: factId,
|
|
452
|
+
properties: {
|
|
453
|
+
title: `Predicate: ${suggestion.canonical_key}`,
|
|
454
|
+
status: "active",
|
|
455
|
+
source: args.source ?? "mcp://kibi/suggest-predicates",
|
|
456
|
+
text_ref: args.source,
|
|
457
|
+
tags: [
|
|
458
|
+
"lane:ontology",
|
|
459
|
+
"predicate-suggestion",
|
|
460
|
+
...suggestion.schema.tags.map((tag) => `predicate:${tag}`),
|
|
461
|
+
],
|
|
462
|
+
fact_kind: "predicate",
|
|
463
|
+
predicate_name: suggestion.predicate_name,
|
|
464
|
+
predicate_args: suggestion.predicate_args,
|
|
465
|
+
canonical_key: suggestion.canonical_key,
|
|
466
|
+
polarity: suggestion.polarity,
|
|
467
|
+
},
|
|
468
|
+
relationships: [],
|
|
469
|
+
},
|
|
470
|
+
];
|
|
471
|
+
}
|
|
472
|
+
function buildRelationshipPlan(factId, requirementId) {
|
|
473
|
+
if (!factId || !requirementId)
|
|
474
|
+
return null;
|
|
475
|
+
return {
|
|
476
|
+
applyAfter: factId,
|
|
477
|
+
requiresExistingReq: requirementId,
|
|
478
|
+
relationship: {
|
|
479
|
+
type: "requires_predicate",
|
|
480
|
+
from: requirementId,
|
|
481
|
+
to: factId,
|
|
482
|
+
},
|
|
483
|
+
instructions: "Apply the predicate fact first, then attach this relationship from the existing requirement without overwriting requirement metadata.",
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
function buildGapApplyPlan(text, args) {
|
|
487
|
+
const factId = hashId("FACT-ONTOLOGY-GAP", [
|
|
488
|
+
args.requirementId ?? "",
|
|
489
|
+
args.source ?? "",
|
|
490
|
+
text,
|
|
491
|
+
]);
|
|
492
|
+
return [
|
|
493
|
+
{
|
|
494
|
+
type: "fact",
|
|
495
|
+
id: factId,
|
|
496
|
+
properties: {
|
|
497
|
+
title: "Ontology gap: predicate schema needed",
|
|
498
|
+
status: "active",
|
|
499
|
+
source: args.source ?? "mcp://kibi/suggest-predicates",
|
|
500
|
+
text_ref: args.source,
|
|
501
|
+
tags: ["review:ontology-gap", "needs_schema_extension"],
|
|
502
|
+
fact_kind: "observation",
|
|
503
|
+
value_string: text,
|
|
504
|
+
},
|
|
505
|
+
relationships: [],
|
|
506
|
+
},
|
|
507
|
+
];
|
|
508
|
+
}
|
|
509
|
+
// implements REQ-mcp-suggest-predicates
|
|
510
|
+
export async function handleKbSuggestPredicates(prolog, args) {
|
|
511
|
+
const text = normalizeText(args.text);
|
|
512
|
+
const maxCandidates = clampInteger(args.maxCandidates, DEFAULT_MAX_CANDIDATES, 1, 20);
|
|
513
|
+
const minScore = clampScore(args.minScore);
|
|
514
|
+
const warnings = [];
|
|
515
|
+
const subject = inferSubject(text, args.subjectHint);
|
|
516
|
+
const existingSchemas = await loadExistingPredicateSchemas(prolog, args.includeExistingSchemas ?? true, warnings);
|
|
517
|
+
const schemas = [...existingSchemas, ...BUILT_IN_PREDICATE_SCHEMAS];
|
|
518
|
+
const candidates = schemas
|
|
519
|
+
.map((schema) => ({ schema, score: scoreSchema(schema, text) }))
|
|
520
|
+
.filter((scored) => scored.score >= minScore)
|
|
521
|
+
.sort((left, right) => {
|
|
522
|
+
if (right.score !== left.score)
|
|
523
|
+
return right.score - left.score;
|
|
524
|
+
return left.schema.predicate_name.localeCompare(right.schema.predicate_name);
|
|
525
|
+
})
|
|
526
|
+
.slice(0, maxCandidates)
|
|
527
|
+
.map((scored) => buildSuggestion(scored.schema, text, subject, scored.score));
|
|
528
|
+
const recommendedAction = candidates.length > 0 ? "apply_requires_predicate" : "record_ontology_gap";
|
|
529
|
+
const firstCandidate = candidates[0];
|
|
530
|
+
const applyPlan = firstCandidate
|
|
531
|
+
? buildPredicateApplyPlan(firstCandidate, args)
|
|
532
|
+
: buildGapApplyPlan(text, args);
|
|
533
|
+
const relationshipPlan = firstCandidate
|
|
534
|
+
? buildRelationshipPlan(String(applyPlan[0]?.id ?? ""), args.requirementId)
|
|
535
|
+
: null;
|
|
536
|
+
const textSummary = candidates.length > 0
|
|
537
|
+
? `Suggested ${candidates.length} predicate candidate(s). Top match: ${candidates[0]?.predicate_name}. Apply structured predicate facts before falling back to prose.`
|
|
538
|
+
: "No predicate candidate met the confidence threshold; record an ontology gap instead of silently writing prose.";
|
|
539
|
+
return {
|
|
540
|
+
content: [{ type: "text", text: textSummary }],
|
|
541
|
+
structuredContent: {
|
|
542
|
+
text,
|
|
543
|
+
source: args.source ?? null,
|
|
544
|
+
requirementId: args.requirementId ?? null,
|
|
545
|
+
subject,
|
|
546
|
+
candidates,
|
|
547
|
+
recommendedAction,
|
|
548
|
+
applyPlan,
|
|
549
|
+
relationshipPlan,
|
|
550
|
+
warnings,
|
|
551
|
+
},
|
|
552
|
+
applyPlan,
|
|
553
|
+
};
|
|
554
|
+
}
|
package/dist/tools/upsert.js
CHANGED
|
@@ -249,6 +249,7 @@ function collectNarrowExportNames(filePath, content) {
|
|
|
249
249
|
scriptKind: chooseScriptKind(filePath),
|
|
250
250
|
});
|
|
251
251
|
const names = new Set();
|
|
252
|
+
const methodNameCounts = new Map();
|
|
252
253
|
for (const fn of sourceFile.getFunctions()) {
|
|
253
254
|
if (fn.isExported()) {
|
|
254
255
|
const name = fn.getName();
|
|
@@ -261,8 +262,18 @@ function collectNarrowExportNames(filePath, content) {
|
|
|
261
262
|
const name = cls.getName();
|
|
262
263
|
if (name)
|
|
263
264
|
names.add(name);
|
|
265
|
+
for (const method of cls.getMethods()) {
|
|
266
|
+
const methodName = method.getName();
|
|
267
|
+
if (name)
|
|
268
|
+
names.add(`${name}.${methodName}`);
|
|
269
|
+
methodNameCounts.set(methodName, (methodNameCounts.get(methodName) ?? 0) + 1);
|
|
270
|
+
}
|
|
264
271
|
}
|
|
265
272
|
}
|
|
273
|
+
for (const [methodName, count] of methodNameCounts) {
|
|
274
|
+
if (count === 1)
|
|
275
|
+
names.add(methodName);
|
|
276
|
+
}
|
|
266
277
|
for (const iface of sourceFile.getInterfaces()) {
|
|
267
278
|
if (iface.isExported())
|
|
268
279
|
names.add(iface.getName());
|
package/dist/tools-config.js
CHANGED
|
@@ -566,6 +566,51 @@ const BASE_TOOLS = [
|
|
|
566
566
|
},
|
|
567
567
|
},
|
|
568
568
|
},
|
|
569
|
+
{
|
|
570
|
+
name: "kb_suggest_predicates",
|
|
571
|
+
description: "Suggest ontology predicate schemas for prose requirements before agents write facts. Read-only guidance returns ranked candidates, a safe predicate-fact applyPlan, a separate requires_predicate relationshipPlan when a requirement ID is supplied, or an explicit ontology-gap observation when no predicate fits.",
|
|
572
|
+
inputSchema: {
|
|
573
|
+
type: "object",
|
|
574
|
+
required: ["text"],
|
|
575
|
+
properties: {
|
|
576
|
+
text: {
|
|
577
|
+
type: "string",
|
|
578
|
+
description: "Required prose requirement or claim to classify into ontology predicates. Example: 'When users navigate away, draft edits must auto-save.'.",
|
|
579
|
+
},
|
|
580
|
+
requirementId: {
|
|
581
|
+
type: "string",
|
|
582
|
+
description: "Optional existing requirement ID. When provided, the response includes a relationshipPlan describing the req -> fact requires_predicate link to attach after preserving existing requirement metadata.",
|
|
583
|
+
},
|
|
584
|
+
source: {
|
|
585
|
+
type: "string",
|
|
586
|
+
description: "Optional provenance or text reference for generated predicate facts or ontology-gap observations.",
|
|
587
|
+
},
|
|
588
|
+
subjectHint: {
|
|
589
|
+
type: "string",
|
|
590
|
+
description: "Optional canonical subject key to use as the first predicate argument. Example: 'editor.annotation'.",
|
|
591
|
+
},
|
|
592
|
+
maxCandidates: {
|
|
593
|
+
type: "integer",
|
|
594
|
+
default: 5,
|
|
595
|
+
minimum: 1,
|
|
596
|
+
maximum: 20,
|
|
597
|
+
description: "Maximum ranked predicate candidates to return. Default: 5.",
|
|
598
|
+
},
|
|
599
|
+
minScore: {
|
|
600
|
+
type: "number",
|
|
601
|
+
default: 0.35,
|
|
602
|
+
minimum: 0,
|
|
603
|
+
maximum: 1,
|
|
604
|
+
description: "Minimum candidate score. Higher values make ontology-gap fallback more likely. Default: 0.35.",
|
|
605
|
+
},
|
|
606
|
+
includeExistingSchemas: {
|
|
607
|
+
type: "boolean",
|
|
608
|
+
default: true,
|
|
609
|
+
description: "Whether to include existing KB fact_kind=predicate_schema facts alongside Kibi's built-in predicate catalog. Default: true.",
|
|
610
|
+
},
|
|
611
|
+
},
|
|
612
|
+
},
|
|
613
|
+
},
|
|
569
614
|
{
|
|
570
615
|
name: "kb_autopilot_generate",
|
|
571
616
|
description: "Generate agent-centric bootstrap output for KB population. Read-only analysis that returns activation state, bootstrap guidance, candidate entities with evidence, payoff summary, and exact applyPlan payloads for later kb_upsert calls. No mutation side effects.",
|
|
@@ -635,11 +680,10 @@ const BASE_TOOLS = [
|
|
|
635
680
|
];
|
|
636
681
|
/**
|
|
637
682
|
* Inject _diagnostic_telemetry schema into tool inputs when diagnostic mode is enabled.
|
|
638
|
-
*
|
|
639
|
-
*
|
|
640
|
-
* covered without a CLI integration test.
|
|
683
|
+
* Exported for unit coverage; TOOLS still applies it only when the server starts
|
|
684
|
+
* with the --diagnostic-mode flag.
|
|
641
685
|
*/
|
|
642
|
-
function withDiagnosticTelemetrySchema(tools) {
|
|
686
|
+
export function withDiagnosticTelemetrySchema(tools) {
|
|
643
687
|
return tools.map((tool) => {
|
|
644
688
|
const schema = tool.inputSchema;
|
|
645
689
|
const properties = schema.properties && typeof schema.properties === "object"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kibi-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"dependencies": {
|
|
5
5
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
6
6
|
"ajv": "^8.18.0",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"fast-glob": "^3.2.12",
|
|
10
10
|
"gray-matter": "^4.0.3",
|
|
11
11
|
"js-yaml": "^4.1.0",
|
|
12
|
-
"kibi-cli": "^0.12.
|
|
12
|
+
"kibi-cli": "^0.12.4",
|
|
13
13
|
"kibi-core": "^0.6.0",
|
|
14
14
|
"mcpcat": "^0.1.12",
|
|
15
15
|
"ts-morph": "^23.0.0",
|