kibi-mcp 0.14.3 → 0.15.1
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/tools/upsert.js +114 -1
- package/dist/tools-config.js +17 -2
- package/package.json +7 -4
package/dist/tools/upsert.js
CHANGED
|
@@ -16,6 +16,9 @@
|
|
|
16
16
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
*/
|
|
18
18
|
import Ajv from "ajv";
|
|
19
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
20
|
+
import path from "node:path";
|
|
21
|
+
import { Project, ScriptKind } from "ts-morph";
|
|
19
22
|
import { escapeAtom, toPrologAtom, toPrologString, } from "kibi-cli/prolog/codec";
|
|
20
23
|
import entitySchema from "kibi-cli/schemas/entity";
|
|
21
24
|
import relationshipSchema from "kibi-cli/schemas/relationship";
|
|
@@ -23,8 +26,40 @@ import { isMcpDebugEnabled } from "../env.js";
|
|
|
23
26
|
import { refreshCoordinatesForSymbolId } from "./symbols.js";
|
|
24
27
|
let refreshCoordinatesForSymbolIdImpl = refreshCoordinatesForSymbolId;
|
|
25
28
|
const ajv = new Ajv({ strict: false });
|
|
26
|
-
const
|
|
29
|
+
const entitySchemaRecord = entitySchema;
|
|
30
|
+
const entitySchemaProperties = entitySchemaRecord.properties;
|
|
31
|
+
const normalizedEntitySchemaProperties = entitySchemaProperties !== null &&
|
|
32
|
+
typeof entitySchemaProperties === "object" &&
|
|
33
|
+
!Array.isArray(entitySchemaProperties)
|
|
34
|
+
? entitySchemaProperties
|
|
35
|
+
: {};
|
|
36
|
+
const validateEntity = ajv.compile({
|
|
37
|
+
...entitySchemaRecord,
|
|
38
|
+
properties: {
|
|
39
|
+
...normalizedEntitySchemaProperties,
|
|
40
|
+
granularity_reason: {
|
|
41
|
+
type: "string",
|
|
42
|
+
enum: [
|
|
43
|
+
"config-artifact",
|
|
44
|
+
"module-level-behavior",
|
|
45
|
+
"extractor-miss",
|
|
46
|
+
"legacy-link",
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
});
|
|
27
51
|
const validateRelationship = ajv.compile(relationshipSchema);
|
|
52
|
+
const TRACEABILITY_RELATIONSHIP_TYPES = new Set([
|
|
53
|
+
"implements",
|
|
54
|
+
"covered_by",
|
|
55
|
+
"executable_for",
|
|
56
|
+
]);
|
|
57
|
+
const ALLOWED_GRANULARITY_REASONS = new Set([
|
|
58
|
+
"config-artifact",
|
|
59
|
+
"module-level-behavior",
|
|
60
|
+
"extractor-miss",
|
|
61
|
+
"legacy-link",
|
|
62
|
+
]);
|
|
28
63
|
/**
|
|
29
64
|
* Handle kb.upsert tool calls
|
|
30
65
|
* Accepts { type, id, properties } — the flat format matching the tool schema.
|
|
@@ -76,6 +111,7 @@ export async function handleKbUpsert(prolog, args) {
|
|
|
76
111
|
}
|
|
77
112
|
}
|
|
78
113
|
validateRelationshipSources(id, relationships);
|
|
114
|
+
validateSymbolGranularity(entity, relationships);
|
|
79
115
|
// Validate strict-lane fact_kind pairing for constrains/requires_property
|
|
80
116
|
// implements REQ-011
|
|
81
117
|
await validateStrictLanePairing(prolog, relationships);
|
|
@@ -185,6 +221,83 @@ export async function handleKbUpsert(prolog, args) {
|
|
|
185
221
|
throw new Error(`Upsert execution failed: ${message}`);
|
|
186
222
|
}
|
|
187
223
|
}
|
|
224
|
+
function chooseScriptKind(filePath) {
|
|
225
|
+
const lower = filePath.toLowerCase();
|
|
226
|
+
if (lower.endsWith(".tsx"))
|
|
227
|
+
return ScriptKind.TSX;
|
|
228
|
+
if (lower.endsWith(".ts") || lower.endsWith(".mts") || lower.endsWith(".cts")) {
|
|
229
|
+
return ScriptKind.TS;
|
|
230
|
+
}
|
|
231
|
+
if (lower.endsWith(".jsx"))
|
|
232
|
+
return ScriptKind.JSX;
|
|
233
|
+
return ScriptKind.JS;
|
|
234
|
+
}
|
|
235
|
+
function hasTraceabilityRelationship(relationships) {
|
|
236
|
+
return relationships.some((relationship) => typeof relationship.type === "string" &&
|
|
237
|
+
TRACEABILITY_RELATIONSHIP_TYPES.has(relationship.type));
|
|
238
|
+
}
|
|
239
|
+
function hasAllowedGranularityReason(entity) {
|
|
240
|
+
const reason = entity.granularity_reason;
|
|
241
|
+
return (typeof reason === "string" && ALLOWED_GRANULARITY_REASONS.has(reason));
|
|
242
|
+
}
|
|
243
|
+
function collectNarrowExportNames(filePath, content) {
|
|
244
|
+
const project = new Project({ skipAddingFilesFromTsConfig: true });
|
|
245
|
+
const sourceFile = project.createSourceFile(`${filePath}::granularity`, content, {
|
|
246
|
+
overwrite: true,
|
|
247
|
+
scriptKind: chooseScriptKind(filePath),
|
|
248
|
+
});
|
|
249
|
+
const names = new Set();
|
|
250
|
+
for (const fn of sourceFile.getFunctions()) {
|
|
251
|
+
if (fn.isExported()) {
|
|
252
|
+
const name = fn.getName();
|
|
253
|
+
if (name)
|
|
254
|
+
names.add(name);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
for (const cls of sourceFile.getClasses()) {
|
|
258
|
+
if (cls.isExported()) {
|
|
259
|
+
const name = cls.getName();
|
|
260
|
+
if (name)
|
|
261
|
+
names.add(name);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
for (const iface of sourceFile.getInterfaces()) {
|
|
265
|
+
if (iface.isExported())
|
|
266
|
+
names.add(iface.getName());
|
|
267
|
+
}
|
|
268
|
+
for (const alias of sourceFile.getTypeAliases()) {
|
|
269
|
+
if (alias.isExported())
|
|
270
|
+
names.add(alias.getName());
|
|
271
|
+
}
|
|
272
|
+
for (const en of sourceFile.getEnums()) {
|
|
273
|
+
if (en.isExported())
|
|
274
|
+
names.add(en.getName());
|
|
275
|
+
}
|
|
276
|
+
return [...names].sort();
|
|
277
|
+
}
|
|
278
|
+
function validateSymbolGranularity(entity, relationships) {
|
|
279
|
+
if (entity.type !== "symbol")
|
|
280
|
+
return;
|
|
281
|
+
if (!hasTraceabilityRelationship(relationships))
|
|
282
|
+
return;
|
|
283
|
+
if (hasAllowedGranularityReason(entity))
|
|
284
|
+
return;
|
|
285
|
+
if (typeof entity.sourceFile !== "string")
|
|
286
|
+
return;
|
|
287
|
+
if (typeof entity.title !== "string")
|
|
288
|
+
return;
|
|
289
|
+
const sourcePath = path.isAbsolute(entity.sourceFile)
|
|
290
|
+
? entity.sourceFile
|
|
291
|
+
: path.resolve(process.cwd(), entity.sourceFile);
|
|
292
|
+
if (!existsSync(sourcePath))
|
|
293
|
+
return;
|
|
294
|
+
const narrowNames = collectNarrowExportNames(entity.sourceFile, readFileSync(sourcePath, "utf8"));
|
|
295
|
+
if (narrowNames.length === 0)
|
|
296
|
+
return;
|
|
297
|
+
if (narrowNames.includes(entity.title))
|
|
298
|
+
return;
|
|
299
|
+
throw new Error(`Symbol ${String(entity.id)} links ${entity.sourceFile} coarsely while granular symbols are available: ${narrowNames.join(", ")}. Move relationships to the narrow symbol or set granularity_reason to config-artifact, module-level-behavior, extractor-miss, or legacy-link.`);
|
|
300
|
+
}
|
|
188
301
|
export const __test__ = {
|
|
189
302
|
// implements REQ-vscode-traceability
|
|
190
303
|
setRefreshCoordinatesForSymbolIdForTests(fn) {
|
package/dist/tools-config.js
CHANGED
|
@@ -272,7 +272,7 @@ const BASE_TOOLS = [
|
|
|
272
272
|
},
|
|
273
273
|
{
|
|
274
274
|
name: "kb_upsert",
|
|
275
|
-
description: "Create or update one entity and optional relationships. Use for KB mutations after validating intent. Use the `relationships` array for batch creation of multiple links in a single call (e.g., linking a requirement to multiple tests or facts). Prefer modeling requirements as reusable fact links (`constrains`, `requires_property`) so consistency and contradiction checks remain queryable. Relationship endpoints must already exist in KB. For requirements, the write will be rejected if it contradicts existing current requirements that constrain the same subject with incompatible properties. To replace a conflicting requirement, include a `supersedes` relationship from the new requirement to the old one in the same request. Do not use for read-only inspection. Side effects: writes KB, may refresh symbol coordinates.",
|
|
275
|
+
description: "Create or update one entity and optional relationships. Use for KB mutations after validating intent. Use the `relationships` array for batch creation of multiple links in a single call (e.g., linking a requirement to multiple tests or facts). Prefer modeling requirements as reusable fact links (`constrains`, `requires_property`, or `requires_predicate`) so consistency and contradiction checks remain queryable. Relationship endpoints must already exist in KB. For requirements, the write will be rejected if it contradicts existing current requirements that constrain the same subject with incompatible properties. To replace a conflicting requirement, include a `supersedes` relationship from the new requirement to the old one in the same request. Do not use for read-only inspection. Side effects: writes KB, may refresh symbol coordinates.",
|
|
276
276
|
inputSchema: {
|
|
277
277
|
type: "object",
|
|
278
278
|
required: ["type", "id", "properties"],
|
|
@@ -337,12 +337,26 @@ const BASE_TOOLS = [
|
|
|
337
337
|
type: "string",
|
|
338
338
|
description: "Optional text anchor/reference. Example: 'requirements.md#L40'.",
|
|
339
339
|
},
|
|
340
|
+
sourceFile: {
|
|
341
|
+
type: "string",
|
|
342
|
+
description: "Optional code source file for symbol entities. Example: 'src/auth/login.ts'.",
|
|
343
|
+
},
|
|
344
|
+
granularity_reason: {
|
|
345
|
+
type: "string",
|
|
346
|
+
enum: [
|
|
347
|
+
"config-artifact",
|
|
348
|
+
"module-level-behavior",
|
|
349
|
+
"extractor-miss",
|
|
350
|
+
"legacy-link",
|
|
351
|
+
],
|
|
352
|
+
description: "Optional justification for a coarse file/module-level symbol traceability relationship when narrower function/class/type symbols exist.",
|
|
353
|
+
},
|
|
340
354
|
},
|
|
341
355
|
required: ["title", "status"],
|
|
342
356
|
},
|
|
343
357
|
relationships: {
|
|
344
358
|
type: "array",
|
|
345
|
-
description: "Optional relationship rows to create in the same call. For requirement encoding, prefer `constrains` + `requires_property`
|
|
359
|
+
description: "Optional relationship rows to create in the same call. For requirement encoding, prefer `constrains` + `requires_property` for strict property facts or `requires_predicate` for ontology predicate facts. Side effect: asserts edges in KB.",
|
|
346
360
|
items: {
|
|
347
361
|
type: "object",
|
|
348
362
|
required: ["type", "from", "to"],
|
|
@@ -360,6 +374,7 @@ const BASE_TOOLS = [
|
|
|
360
374
|
"constrained_by",
|
|
361
375
|
"constrains",
|
|
362
376
|
"requires_property",
|
|
377
|
+
"requires_predicate",
|
|
363
378
|
"guards",
|
|
364
379
|
"publishes",
|
|
365
380
|
"consumes",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kibi-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.1",
|
|
4
4
|
"dependencies": {
|
|
5
5
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
6
6
|
"ajv": "^8.18.0",
|
|
@@ -9,8 +9,8 @@
|
|
|
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.
|
|
13
|
-
"kibi-core": "^0.
|
|
12
|
+
"kibi-cli": "^0.12.1",
|
|
13
|
+
"kibi-core": "^0.6.0",
|
|
14
14
|
"mcpcat": "^0.1.12",
|
|
15
15
|
"ts-morph": "^23.0.0",
|
|
16
16
|
"zod": "^4.3.6"
|
|
@@ -27,7 +27,10 @@
|
|
|
27
27
|
"build": "tsc -p tsconfig.json",
|
|
28
28
|
"prepack": "npm run build"
|
|
29
29
|
},
|
|
30
|
-
"files": [
|
|
30
|
+
"files": [
|
|
31
|
+
"dist",
|
|
32
|
+
"bin"
|
|
33
|
+
],
|
|
31
34
|
"engines": {
|
|
32
35
|
"node": ">=18",
|
|
33
36
|
"bun": ">=1.0"
|