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.
@@ -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 validateEntity = ajv.compile(entitySchema);
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) {
@@ -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` edges from req IDs to shared fact IDs to maximize reuse and detect conflicts. Side effect: asserts edges in KB.",
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.14.3",
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.11.3",
13
- "kibi-core": "^0.5.3",
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": ["dist", "bin"],
30
+ "files": [
31
+ "dist",
32
+ "bin"
33
+ ],
31
34
  "engines": {
32
35
  "node": ">=18",
33
36
  "bun": ">=1.0"