kibi-plugin-builtin 0.5.0 → 0.5.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/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # kibi-plugin-builtin
2
2
 
3
+ ## 0.5.2
4
+
5
+ ### Patch Changes
6
+
7
+ - e9b50db: A clean `kibi check` now means the next commit passes `kibi check --staged`, and the staged gate no longer passes symbol coverage that the full check rejects. `kibi check --staged` runs the full-check `symbol-coverage` rule on the staged knowledge for every changed symbol, so a test that verifies a requirement directly no longer counts once that requirement has a scenario. Full `kibi check` adds the blocking rule `symbol-granularity`, which judges every linked symbol against its source file the way the staged gate does, so coarse links, including symbols whose title the extractor never produces, surface before a later commit trips over them; existing knowledge bases can see new violations (see Migration below). Source analysis for symbol checks no longer builds a TypeScript program over the default library files for every file, so the rule adds a few seconds to a full check, and the staged gate and coordinate refresh get faster too. `kibi sync --refresh-symbol-coordinates` no longer binds coordinates to a private class member that the staged extractor never produces; it lists the symbol as failed and says to re-point it to the public owner or remove it.
8
+
9
+ Technical summary: `kb.pl` adds `changed_symbol_no_req_coverage/2`, which restricts `symbol_no_req_coverage/2` to the `changed_symbol/1` overlay facts plus symbols whose staged manifest entry changed, and `checks.pl` adds `check_changed_symbol_coverage/2`, which renders those as `symbol-coverage` violations; the staged flow in `check.ts` calls it through `validateStagedSymbolCoverage` next to `validateStagedSymbols` and creates the temp KB whenever a symbol changed, including manifest-only edits. The rule registry adds the TypeScript rule `symbol-granularity`, evaluated in `executeCheck` by `collectSymbolGranularityViolations`, which reuses `createSymbolGranularityDiagnostics` with every working-tree declaration as a candidate, analyzes only the source files of symbols the rule judges, honours allowed `granularity_reason` values and skips files the analyzers cannot fully analyze; the `kb_check` `rules` enum gains the name. Granularity diagnostics now carry `entityId`, `source` and evidence, and name titles the extractor does not produce. The built-in ts-morph v2 extractor creates its project with `noLib`, `noResolve` and no `types`: it reads only syntactic diagnostics, which are unchanged, and per-file analysis drops from roughly 75 ms to a few milliseconds. The ts-morph coordinate enricher skips private class members and records a transient `coordinateRefreshFailure` that sync prints as the failure reason and never writes to the manifest. The unused `checkSymbolCoverage` helper and its stale "not traceable to any functional requirement" message are removed. kibi-runtime is bumped because it bundles the CLI operations that kibi-mcp serves.
10
+
11
+ Migration: `symbol-granularity` is canonical and blocking, so `kibi check`, `kb_check` and CI fail until its violations are fixed. A symbol violates it when it has `implements`, `covered_by` or `executable_for` links, no allowed `granularity_reason`, and its source file has a narrower behavioral declaration: a member of the symbol's title (a class symbol while its methods exist), or any declaration when the title names nothing the extractor produces (a non-exported helper, a private class member, a removed or renamed declaration). Run `kibi check --rules symbol-granularity` to list them, fix them with `kb_upsert`/`kb_delete` (never by editing `.kb/` files), then run `kibi sync --refresh-symbol-coordinates`:
12
+ - Title the extractor does not produce: re-point the symbol to its public owner, the exported function or class that owns the behavior. Change its `title` to the owner, or, when a symbol for the owner already exists, add the links to that symbol and delete the helper symbol.
13
+ - Class symbol while its members exist: move the links to the narrow member symbols, or delete the class symbol when its members already carry the same links.
14
+ - Coarse ownership that is intended: add an allowed `granularity_reason` (`module-level-behavior`, `config-artifact`, `extractor-miss`, `test-suite` or `legacy-link`).
15
+
16
+ ## 0.5.1
17
+
18
+ ### Patch Changes
19
+
20
+ - 8f3f586: Permission grants written the way product owners write them now model as permission rules. `kb_model` (mode predicates) accepted "may", "can", "is allowed to" and "are permitted to" but rejected "must be able to", "are able to", "have permission to" and "are entitled to" with "required semantic cue absent", so a requirement such as "An admin must be able to read any analysis request" ended as an ontology gap even with complete argument bindings. The semantic advisor now also treats "have permission to" and "are entitled to" as normative clauses, and inferred permission bindings drop the leading article and singularise the resource, so "any analysis request" and "analysis requests" bind the same actor, action and resource key that the allow/deny contradiction check compares.
21
+ - `predicate-applicability.ts`: the permission_rule cue and the normative-intent gate accept `must/shall/should be able to`, `is/are able to`, `is/are entitled to` and `has/have permission to`, including the common-verb form right after the grant.
22
+ - `clauses.ts`: `has/have (no) permission to` and `is/are (not) entitled to` are normative assertions; bare `is/are able to` stays descriptive so stored inventories keep their roles.
23
+ - `predicate-inference-1.ts`: grant and prohibition forms of the new cues bind actor, action, resource and decision; actors and resources lose their determiner and resources are singular.
24
+ - The builtin semantic classifier's permission signal recognises `permitted`, `able to`, `entitled to` and `permission to`.
25
+ - kibi-usage 2.15.1: the deny example shows the singular resource and lists the grant wordings `permission_rule` accepts.
26
+
3
27
  ## 0.5.0
4
28
 
5
29
  ### Minor Changes
@@ -1 +1 @@
1
- {"version":3,"file":"builtin-classifier.d.ts","sourceRoot":"","sources":["../../src/semantic/builtin-classifier.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAEV,uBAAuB,EACvB,wBAAwB,EACxB,oBAAoB,EACpB,YAAY,EACZ,kBAAkB,EACnB,MAAM,iBAAiB,CAAC;AAwDzB,UAAU,cAAc;IACtB,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;IAClC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,aAAa,EAAE,YAAY,CAAC;IACrC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAGD,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,cAAc,EAAE,CAetE;AAED;;;GAGG;AAEH,wBAAgB,UAAU,CACxB,OAAO,EAAE,SAAS;IAAE,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAA;CAAE,EAAE,GACxD,YAAY,CAoBd;AA4BD,qBAAa,yBAA0B,YAAW,oBAAoB;IACpE,QAAQ,CAAC,EAAE,oCAAoC;IAE/C,QAAQ,CAAC,KAAK,EAAE,uBAAuB,GAAG,wBAAwB;CAqBnE;AAGD,wBAAgB,+BAA+B,IAAI,oBAAoB,CAEtE"}
1
+ {"version":3,"file":"builtin-classifier.d.ts","sourceRoot":"","sources":["../../src/semantic/builtin-classifier.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAEV,uBAAuB,EACvB,wBAAwB,EACxB,oBAAoB,EACpB,YAAY,EACZ,kBAAkB,EACnB,MAAM,iBAAiB,CAAC;AA0DzB,UAAU,cAAc;IACtB,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;IAClC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,aAAa,EAAE,YAAY,CAAC;IACrC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAGD,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,cAAc,EAAE,CAetE;AAED;;;GAGG;AAEH,wBAAgB,UAAU,CACxB,OAAO,EAAE,SAAS;IAAE,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAA;CAAE,EAAE,GACxD,YAAY,CAoBd;AA4BD,qBAAa,yBAA0B,YAAW,oBAAoB;IACpE,QAAQ,CAAC,EAAE,oCAAoC;IAE/C,QAAQ,CAAC,KAAK,EAAE,uBAAuB,GAAG,wBAAwB;CAqBnE;AAGD,wBAAgB,+BAA+B,IAAI,oBAAoB,CAEtE"}
@@ -39,7 +39,9 @@ const SIGNAL_PATTERNS = [
39
39
  kind: "permission",
40
40
  candidateLane: "predicate",
41
41
  confidence: 0.8,
42
- pattern: /\b(?:only|may|can|allowed|denied|forbidden|must\s+not|cannot|can't)\b/i,
42
+ // Grants ("permitted", "able to", "entitled to", "permission to") are
43
+ // permissions as much as "allowed" and the prohibition cues are.
44
+ pattern: /\b(?:only|may|can|allowed|permitted|denied|forbidden|must\s+not|cannot|can't|able\s+to|entitled\s+to|permission\s+to)\b/i,
43
45
  },
44
46
  {
45
47
  kind: "state_or_default",
@@ -15,6 +15,13 @@ export interface ManifestSymbolEntry {
15
15
  sourceEndColumn?: number;
16
16
  coordinatesGeneratedAt?: string;
17
17
  links?: string[];
18
+ /**
19
+ * Transient refresh outcome, never authored: why no coordinates were
20
+ * published for this entry. Set when the title names a declaration the
21
+ * symbol extractor does not produce (a private class member), so a refresh
22
+ * reports it instead of binding a span no other Kibi path recognizes.
23
+ */
24
+ coordinateRefreshFailure?: string;
18
25
  [key: string]: unknown;
19
26
  }
20
27
  export declare function enrichSymbolCoordinatesWithTsMorph(entries: ManifestSymbolEntry[], workspaceRoot: string): Promise<ManifestSymbolEntry[]>;
@@ -1 +1 @@
1
- {"version":3,"file":"ts-morph-enrichment.d.ts","sourceRoot":"","sources":["../../src/symbols/ts-morph-enrichment.ts"],"names":[],"mappings":"AAmCA,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,sBAAsB,EAAE,MAAM,CAAC;CAChC;AAGD,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAKD,wBAAsB,kCAAkC,CACtD,OAAO,EAAE,mBAAmB,EAAE,EAC9B,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,mBAAmB,EAAE,CAAC,CA8FhC"}
1
+ {"version":3,"file":"ts-morph-enrichment.d.ts","sourceRoot":"","sources":["../../src/symbols/ts-morph-enrichment.ts"],"names":[],"mappings":"AAoCA,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,sBAAsB,EAAE,MAAM,CAAC;CAChC;AAGD,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB;;;;;OAKG;IACH,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAKD,wBAAsB,kCAAkC,CACtD,OAAO,EAAE,mBAAmB,EAAE,EAC9B,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAuGhC"}
@@ -18,7 +18,7 @@
18
18
  import { access, readFile } from "node:fs/promises";
19
19
  import * as path from "node:path";
20
20
  import { tsMorph } from "./ts-morph-runtime.js";
21
- import { SUPPORTED_SOURCE_EXTENSIONS, onlyCandidate, } from "./ts-morph-shared.js";
21
+ import { SUPPORTED_SOURCE_EXTENSIONS, isPrivateClassMember, onlyCandidate, } from "./ts-morph-shared.js";
22
22
  // implements REQ-capability-plugin-builtin-parity-v1
23
23
  export async function enrichSymbolCoordinatesWithTsMorph(entries, workspaceRoot) {
24
24
  const project = new (tsMorph().Project)({
@@ -62,7 +62,12 @@ export async function enrichSymbolCoordinatesWithTsMorph(entries, workspaceRoot)
62
62
  }
63
63
  const match = findNamedDeclaration(sourceFile, entry.title);
64
64
  if (!match) {
65
- enriched.push(entry);
65
+ enriched.push(namesPrivateClassMember(sourceFile, entry.title)
66
+ ? {
67
+ ...entry,
68
+ coordinateRefreshFailure: privateMemberRefreshFailure(entry.title),
69
+ }
70
+ : entry);
66
71
  continue;
67
72
  }
68
73
  const nameStart = match.getNameNode().getStart();
@@ -258,6 +263,8 @@ function findNamedDeclaration(sourceFile, title) {
258
263
  const methodCandidates = [];
259
264
  for (const cls of sourceFile.getClasses()) {
260
265
  for (const method of cls.getMethods()) {
266
+ if (isPrivateClassMember(method))
267
+ continue;
261
268
  if (method.getName() !== title)
262
269
  continue;
263
270
  const nameNode = method.getNameNode();
@@ -274,6 +281,8 @@ function findNamedDeclaration(sourceFile, title) {
274
281
  if (!classExpression)
275
282
  continue;
276
283
  for (const method of classExpression.getMethods()) {
284
+ if (isPrivateClassMember(method))
285
+ continue;
277
286
  if (method.getName() !== title)
278
287
  continue;
279
288
  const nameNode = method.getNameNode();
@@ -293,6 +302,8 @@ function findNamedDeclaration(sourceFile, title) {
293
302
  const memberCandidates = [];
294
303
  for (const cls of sourceFile.getClasses()) {
295
304
  for (const property of cls.getProperties()) {
305
+ if (isPrivateClassMember(property))
306
+ continue;
296
307
  if (property.getName() !== title)
297
308
  continue;
298
309
  const nameNode = property.getNameNode();
@@ -304,6 +315,8 @@ function findNamedDeclaration(sourceFile, title) {
304
315
  ...cls.getGetAccessors(),
305
316
  ...cls.getSetAccessors(),
306
317
  ]) {
318
+ if (isPrivateClassMember(accessor))
319
+ continue;
307
320
  if (accessor.getName() !== title)
308
321
  continue;
309
322
  const nameNode = accessor.getNameNode();
@@ -320,6 +333,8 @@ function findNamedDeclaration(sourceFile, title) {
320
333
  if (!classExpression)
321
334
  continue;
322
335
  for (const property of classExpression.getProperties()) {
336
+ if (isPrivateClassMember(property))
337
+ continue;
323
338
  if (property.getName() !== title)
324
339
  continue;
325
340
  const nameNode = property.getNameNode();
@@ -334,6 +349,8 @@ function findNamedDeclaration(sourceFile, title) {
334
349
  ...classExpression.getGetAccessors(),
335
350
  ...classExpression.getSetAccessors(),
336
351
  ]) {
352
+ if (isPrivateClassMember(accessor))
353
+ continue;
337
354
  if (accessor.getName() !== title)
338
355
  continue;
339
356
  const nameNode = accessor.getNameNode();
@@ -355,16 +372,22 @@ function findNamedDeclaration(sourceFile, title) {
355
372
  candidates.sort((a, b) => a.getNameNode().getStart() - b.getNameNode().getStart());
356
373
  return candidates[0] ?? null;
357
374
  }
375
+ /**
376
+ * Public members only: the symbol extractor skips private class members, so
377
+ * a coordinate refresh must not bind a span to one either.
378
+ */
379
+ // implements REQ-capability-plugin-builtin-parity-v1
358
380
  function findClassMember(declaration, name) {
381
+ const visible = (member) => !isPrivateClassMember(member);
359
382
  for (const method of declaration.getMethods()) {
360
- if (method.getName() !== name)
383
+ if (method.getName() !== name || !visible(method))
361
384
  continue;
362
385
  const nameNode = method.getNameNode();
363
386
  if (nameNode)
364
387
  return { node: method, getNameNode: () => nameNode };
365
388
  }
366
389
  for (const property of declaration.getProperties()) {
367
- if (property.getName() !== name)
390
+ if (property.getName() !== name || !visible(property))
368
391
  continue;
369
392
  const nameNode = property.getNameNode();
370
393
  if (nameNode)
@@ -374,7 +397,7 @@ function findClassMember(declaration, name) {
374
397
  ...declaration.getGetAccessors(),
375
398
  ...declaration.getSetAccessors(),
376
399
  ]) {
377
- if (accessor.getName() !== name)
400
+ if (accessor.getName() !== name || !visible(accessor))
378
401
  continue;
379
402
  const nameNode = accessor.getNameNode();
380
403
  if (nameNode)
@@ -382,6 +405,42 @@ function findClassMember(declaration, name) {
382
405
  }
383
406
  return null;
384
407
  }
408
+ /**
409
+ * True when the title names a private member of a class in this file (as
410
+ * `Class.member` or as a bare member name), which the extractor never emits.
411
+ */
412
+ // implements REQ-capability-plugin-builtin-parity-v1
413
+ function namesPrivateClassMember(sourceFile, title) {
414
+ const qualified = parseQualifiedMethodTitle(title);
415
+ const classes = [
416
+ ...sourceFile.getClasses(),
417
+ ];
418
+ for (const statement of sourceFile.getVariableStatements()) {
419
+ for (const declaration of statement.getDeclarations()) {
420
+ const classExpression = declaration.getInitializerIfKind(tsMorph().SyntaxKind.ClassExpression);
421
+ if (classExpression)
422
+ classes.push(classExpression);
423
+ }
424
+ }
425
+ return classes.some((declaration) => {
426
+ const className = declaration.getName() ??
427
+ declaration
428
+ .getParentIfKind(tsMorph().SyntaxKind.VariableDeclaration)
429
+ ?.getName();
430
+ if (qualified && qualified.className !== className)
431
+ return false;
432
+ const memberName = qualified ? qualified.methodName : title;
433
+ return [
434
+ ...declaration.getMethods(),
435
+ ...declaration.getProperties(),
436
+ ...declaration.getGetAccessors(),
437
+ ...declaration.getSetAccessors(),
438
+ ].some((member) => member.getName() === memberName && isPrivateClassMember(member));
439
+ });
440
+ }
441
+ function privateMemberRefreshFailure(title) {
442
+ return `'${title}' is a private class member; the symbol extractor tracks only public declarations, so no coordinates are published. Re-point the symbol to the public member or class that owns the behavior, or remove it`;
443
+ }
385
444
  function parseQualifiedMethodTitle(title) {
386
445
  const separatorIndex = title.lastIndexOf(".");
387
446
  if (separatorIndex <= 0 || separatorIndex === title.length - 1)
@@ -1 +1 @@
1
- {"version":3,"file":"ts-morph-extractor.d.ts","sourceRoot":"","sources":["../../src/symbols/ts-morph-extractor.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EACV,sBAAsB,EAMtB,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,iBAAiB,CAAC;AAsDzB;;;GAGG;AAEH,wBAAgB,mCAAmC,IAAI,iBAAiB,CA4CvE;AAED,iFAAiF;AAEjF,wBAAgB,qCAAqC,IAAI,iBAAiB,CAqHzE;AA+BD,wBAAgB,0CAA0C,IAAI,sBAAsB,CAEnF"}
1
+ {"version":3,"file":"ts-morph-extractor.d.ts","sourceRoot":"","sources":["../../src/symbols/ts-morph-extractor.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EACV,sBAAsB,EAMtB,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,iBAAiB,CAAC;AAsDzB;;;GAGG;AAEH,wBAAgB,mCAAmC,IAAI,iBAAiB,CA4CvE;AAED,iFAAiF;AAEjF,wBAAgB,qCAAqC,IAAI,iBAAiB,CA2HzE;AA+BD,wBAAgB,0CAA0C,IAAI,sBAAsB,CAEnF"}
@@ -96,9 +96,15 @@ export function createBuiltinTsMorphSymbolExtractor() {
96
96
  export function createBuiltinTsMorphSymbolExtractorV2() {
97
97
  // Like the v1 extractor, build the project (and load the TypeScript
98
98
  // compiler) on the first analysis rather than at plugin registration.
99
+ // Only syntactic diagnostics are read, so the program needs neither the
100
+ // default library files nor the file's imports; loading them made every
101
+ // analysis rebuild a program over the whole lib closure.
99
102
  let project;
100
103
  const getProject = () => {
101
- project ??= new (tsMorph().Project)({ skipAddingFilesFromTsConfig: true });
104
+ project ??= new (tsMorph().Project)({
105
+ skipAddingFilesFromTsConfig: true,
106
+ compilerOptions: { noLib: true, noResolve: true, types: [] },
107
+ });
102
108
  return project;
103
109
  };
104
110
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kibi-plugin-builtin",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Default Kibi capability plugins: TS symbol extraction, ontology pack, and semantic classifier",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",