kibi-plugin-builtin 0.2.0 → 0.3.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/CHANGELOG.md CHANGED
@@ -1,5 +1,40 @@
1
1
  # kibi-plugin-builtin
2
2
 
3
+ ## 0.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 0128b56: Kibi now helps agents keep one shared vocabulary instead of letting every requirement invent its own. `kibi check` warns when two unrelated requirements state the same obligation. Units are compared in canonical form, so "30 min" matches "1800 s", and each warning names the exact facts involved. It also flags subject keys copied from a requirement ID, subject keys that don't follow `component.aspect`, predicates whose arguments read like prose, and entity files whose name doesn't match their ID. All of these are non-blocking warnings or info notes, so existing knowledge bases keep passing.
8
+
9
+ When you model a new requirement, Kibi now ranks the subjects that already exist and either reuses one or explicitly declares a new one. It also flags claims that look like possible duplicates. An intentional restatement can be recorded with the new `restates` relationship. Skills and docs now recommend naming entities by the behavior they govern (`REQ-cli-gc`) instead of a sequence number (`REQ-042`). Existing numbered IDs stay valid.
10
+
11
+ Predicate schemas can now declare the allowed values for an argument, plus the old spellings that map onto them. New facts must use those values. `kibi check` reports predicate facts that don't match any schema, and `kibi migrate` can fix the mechanical cases after you approve the plan hash. It moves a fact to the only namespace whose schema matches, and rewrites old spellings to the declared value. Everything that needs judgment stays a review item.
12
+
13
+ - core: new `semantic_quality.pl` (`entity-id-style`, `domain-redundancy`, `domain-implication`, `subject-key-identity`, `subject-key-shape`, `ontology-quality`) and `units.pl`. Unit canonicalization is used for comparison only, and unknown or ambiguous units such as `KB` are never equated. Adds the `restates` req→req relationship and an optional `diagnosticSeverity` in the rule registry. Adds `:- encoding(utf8)` to modules that contain non-ASCII text.
14
+ - cli/mcp: `restates` is wired through the extractors, schemas, and mutation paths. `entity-id-style` warnings are reported on `kb_upsert` creates and on staged added or renamed entity files. `kb_model_requirement` returns `vocabularyAlignment` (subject decision, candidates, redundancy candidates, stamps, `fallbackUsed`). Ontology-quality thresholds can be set with `KIBI_ONTOLOGY_QUALITY_MAX_SINGLETON_RATIO` / `KIBI_ONTOLOGY_QUALITY_MIN_FACTS`. `kb_compile_intent` create mode now keeps a caller-supplied `requirementId`. `kibi check --staged` now also prints its pass line when metadata-only staged changes have only advisory findings, matching the staged-symbol path.
15
+ - cli/mcp: `predicate_schema` facts accept `argument_constants` and `argument_aliases`, stored like `rule_ir` as JSON. `kb_upsert` / `kb_validate_upsert` reject malformed vocabularies and predicate facts that use undeclared values or aliases. `kb_suggest_predicates` binds aliases to their constant and leaves undeclared values unbound. The new advisory TypeScript rule `predicate-schema-conformance` checks predicate facts against project schemas and the built-in catalog. Its mechanical repairs become automatic `predicate_schema_alignment` migration actions that carry the exact `kb_upsert` input and re-read the fact before writing.
16
+ - core: `ontology-quality` findings name the prose-like arguments.
17
+ - plugin-sdk: new `kibi.vocabulary-alignment.v1` capability (`rankSubjects`, `compareClaims`) with result validators.
18
+ - plugin-builtin: deterministic offline provider (IDF-weighted subject ranking and negation- and quantity-aware claim similarity).
19
+ - plugin-jev: Jev-backed provider. It runs in replace, augment, or shadow mode with builtin fallback, and makes network calls only when it is activated for `kb_model_requirement`. `kb_check` never calls a provider.
20
+ - runtime and agent adapters: regenerated skill mirrors with the naming guidance and `restates` direction docs.
21
+
22
+ ### Patch Changes
23
+
24
+ - e5ab646: `kibi status` and every CLI command start faster on large knowledge bases. On
25
+ this repository, `kibi status` for a fresh KB drops from about 3 seconds to
26
+ about 1.7, and a command that needs neither schema validation nor symbol
27
+ extraction starts about 0.5 seconds sooner.
28
+
29
+ - `kb_status_json` skips the full-entity stale-reason scan when the KB is
30
+ fresh, because a fresh verdict already rules out every stale reason.
31
+ - `kibi-plugin-builtin` loads ts-morph (the TypeScript compiler) on first
32
+ symbol analysis instead of at import time.
33
+ - The CLI compiles its entity and relationship JSON schemas on first use.
34
+
35
+ - Updated dependencies [0128b56]
36
+ - kibi-plugin-sdk@0.3.0
37
+
3
38
  ## 0.2.0
4
39
 
5
40
  ### Minor Changes
package/dist/index.d.ts CHANGED
@@ -12,6 +12,7 @@ export { createBuiltinTsMorphSymbolExtractor, createBuiltinTsMorphSourceAnalysis
12
12
  export { enrichSymbolCoordinatesWithTsMorph, type ManifestSymbolEntry, type SymbolCoordinates, } from "./symbols/ts-morph-enrichment.js";
13
13
  export { collectGranularityCandidates, type GranularitySymbolCandidate, } from "./symbols/ts-morph-granularity.js";
14
14
  export { isPrivateClassMember, onlyCandidate, } from "./symbols/ts-morph-shared.js";
15
+ export { BUILTIN_CLAIM_SIMILARITY_THRESHOLD, BUILTIN_SUBJECT_REUSE_THRESHOLD, BuiltinVocabularyAlignment, builtinSubjectDecision, claimSimilarity, createBuiltinVocabularyAlignment, rankSubjectCandidates, vocabularyTokens, type SubjectVocabularyEntry, } from "./vocabulary/builtin-vocabulary-alignment.js";
15
16
  /**
16
17
  * Default Kibi capability plugin.
17
18
  */
@@ -28,6 +29,7 @@ export declare const kibiPlugin: {
28
29
  semanticClassifier: import("kibi-plugin-sdk").SemanticClassifierV1;
29
30
  ontologyPack: import("kibi-plugin-sdk").OntologyPackV1;
30
31
  symbolExtractor: import("kibi-plugin-sdk").SymbolExtractorV1;
32
+ vocabularyAlignment: import("./index.js").BuiltinVocabularyAlignment;
31
33
  };
32
34
  };
33
35
  export default kibiPlugin;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA6BA,OAAO,EAAE,yBAAyB,EAAE,MAAM,qCAAqC,CAAC;AAEhF,OAAO,EACL,mBAAmB,EACnB,2BAA2B,GAC5B,MAAM,qCAAqC,CAAC;AAE7C,OAAO,EACL,oBAAoB,EACpB,WAAW,EACX,KAAK,aAAa,GACnB,MAAM,8BAA8B,CAAC;AAEtC,OAAO,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAE1E,OAAO,EACL,wBAAwB,EACxB,0BAA0B,GAC3B,MAAM,wCAAwC,CAAC;AAEhD,OAAO,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AAE9E,OAAO,EAAE,uBAAuB,EAAE,MAAM,uCAAuC,CAAC;AAEhF,OAAO,EAAE,4BAA4B,EAAE,MAAM,4CAA4C,CAAC;AAE1F,OAAO,EACL,YAAY,EACZ,uBAAuB,EACvB,mBAAmB,EACnB,WAAW,EACX,SAAS,GACV,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,+BAA+B,EAC/B,yBAAyB,EACzB,aAAa,EACb,UAAU,GACX,MAAM,kCAAkC,CAAC;AAE1C,OAAO,EACL,mCAAmC,EACnC,0CAA0C,GAC3C,MAAM,iCAAiC,CAAC;AAEzC,OAAO,EACL,kCAAkC,EAClC,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,GACvB,MAAM,kCAAkC,CAAC;AAE1C,OAAO,EACL,4BAA4B,EAC5B,KAAK,0BAA0B,GAChC,MAAM,mCAAmC,CAAC;AAE3C,OAAO,EACL,oBAAoB,EACpB,aAAa,GACd,MAAM,8BAA8B,CAAC;AAEtC;;GAEG;AAEH,eAAO,MAAM,UAAU;;;;;;;;;;;;;;CAcrB,CAAC;AAGH,eAAe,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA8BA,OAAO,EAAE,yBAAyB,EAAE,MAAM,qCAAqC,CAAC;AAEhF,OAAO,EACL,mBAAmB,EACnB,2BAA2B,GAC5B,MAAM,qCAAqC,CAAC;AAE7C,OAAO,EACL,oBAAoB,EACpB,WAAW,EACX,KAAK,aAAa,GACnB,MAAM,8BAA8B,CAAC;AAEtC,OAAO,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAE1E,OAAO,EACL,wBAAwB,EACxB,0BAA0B,GAC3B,MAAM,wCAAwC,CAAC;AAEhD,OAAO,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AAE9E,OAAO,EAAE,uBAAuB,EAAE,MAAM,uCAAuC,CAAC;AAEhF,OAAO,EAAE,4BAA4B,EAAE,MAAM,4CAA4C,CAAC;AAE1F,OAAO,EACL,YAAY,EACZ,uBAAuB,EACvB,mBAAmB,EACnB,WAAW,EACX,SAAS,GACV,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,+BAA+B,EAC/B,yBAAyB,EACzB,aAAa,EACb,UAAU,GACX,MAAM,kCAAkC,CAAC;AAE1C,OAAO,EACL,mCAAmC,EACnC,0CAA0C,GAC3C,MAAM,iCAAiC,CAAC;AAEzC,OAAO,EACL,kCAAkC,EAClC,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,GACvB,MAAM,kCAAkC,CAAC;AAE1C,OAAO,EACL,4BAA4B,EAC5B,KAAK,0BAA0B,GAChC,MAAM,mCAAmC,CAAC;AAE3C,OAAO,EACL,oBAAoB,EACpB,aAAa,GACd,MAAM,8BAA8B,CAAC;AAGtC,OAAO,EACL,kCAAkC,EAClC,+BAA+B,EAC/B,0BAA0B,EAC1B,sBAAsB,EACtB,eAAe,EACf,gCAAgC,EAChC,qBAAqB,EACrB,gBAAgB,EAChB,KAAK,sBAAsB,GAC5B,MAAM,8CAA8C,CAAC;AAEtD;;GAEG;AAEH,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;CAerB,CAAC;AAGH,eAAe,UAAU,CAAC"}
package/dist/index.js CHANGED
@@ -20,6 +20,7 @@ import { KIBI_PLUGIN_API_VERSION, defineKibiPlugin } from "kibi-plugin-sdk";
20
20
  import { createBuiltinOntologyPack } from "./ontology/builtin-ontology-pack.js";
21
21
  import { createBuiltinSemanticClassifier } from "./semantic/builtin-classifier.js";
22
22
  import { createBuiltinTsMorphSymbolExtractor } from "./symbols/ts-morph-extractor.js";
23
+ import { createBuiltinVocabularyAlignment } from "./vocabulary/builtin-vocabulary-alignment.js";
23
24
  const packageJson = createRequire(import.meta.url)("../package.json");
24
25
  // implements REQ-capability-plugin-builtin-parity-v1
25
26
  export { createBuiltinOntologyPack } from "./ontology/builtin-ontology-pack.js";
@@ -49,6 +50,8 @@ export { enrichSymbolCoordinatesWithTsMorph, } from "./symbols/ts-morph-enrichme
49
50
  export { collectGranularityCandidates, } from "./symbols/ts-morph-granularity.js";
50
51
  // implements REQ-capability-plugin-builtin-parity-v1
51
52
  export { isPrivateClassMember, onlyCandidate, } from "./symbols/ts-morph-shared.js";
53
+ // implements REQ-kibi-vocabulary-alignment-capability
54
+ export { BUILTIN_CLAIM_SIMILARITY_THRESHOLD, BUILTIN_SUBJECT_REUSE_THRESHOLD, BuiltinVocabularyAlignment, builtinSubjectDecision, claimSimilarity, createBuiltinVocabularyAlignment, rankSubjectCandidates, vocabularyTokens, } from "./vocabulary/builtin-vocabulary-alignment.js";
52
55
  /**
53
56
  * Default Kibi capability plugin.
54
57
  */
@@ -66,6 +69,7 @@ export const kibiPlugin = defineKibiPlugin({
66
69
  semanticClassifier: createBuiltinSemanticClassifier(),
67
70
  ontologyPack: createBuiltinOntologyPack(),
68
71
  symbolExtractor: createBuiltinTsMorphSymbolExtractor(),
72
+ vocabularyAlignment: createBuiltinVocabularyAlignment(),
69
73
  },
70
74
  });
71
75
  // implements REQ-capability-plugin-builtin-parity-v1
@@ -17,11 +17,11 @@
17
17
  */
18
18
  import { access, readFile } from "node:fs/promises";
19
19
  import * as path from "node:path";
20
- import { Project, SyntaxKind, } from "ts-morph";
20
+ import { tsMorph } from "./ts-morph-runtime.js";
21
21
  import { SUPPORTED_SOURCE_EXTENSIONS, onlyCandidate, } from "./ts-morph-shared.js";
22
22
  // implements REQ-capability-plugin-builtin-parity-v1
23
23
  export async function enrichSymbolCoordinatesWithTsMorph(entries, workspaceRoot) {
24
- const project = new Project({
24
+ const project = new (tsMorph().Project)({
25
25
  skipAddingFilesFromTsConfig: true,
26
26
  });
27
27
  const sourceFileCache = new Map();
@@ -146,7 +146,7 @@ function findNamedDeclaration(sourceFile, title) {
146
146
  for (const declaration of statement.getDeclarations()) {
147
147
  if (declaration.getName() !== qualifiedMethod.className)
148
148
  continue;
149
- const classExpression = declaration.getInitializerIfKind(SyntaxKind.ClassExpression);
149
+ const classExpression = declaration.getInitializerIfKind(tsMorph().SyntaxKind.ClassExpression);
150
150
  const match = classExpression
151
151
  ? findClassMember(classExpression, qualifiedMethod.methodName)
152
152
  : null;
@@ -248,7 +248,7 @@ function findNamedDeclaration(sourceFile, title) {
248
248
  if (!statement.isExported())
249
249
  continue;
250
250
  for (const declaration of statement.getDeclarations()) {
251
- const classExpression = declaration.getInitializerIfKind(SyntaxKind.ClassExpression);
251
+ const classExpression = declaration.getInitializerIfKind(tsMorph().SyntaxKind.ClassExpression);
252
252
  if (!classExpression)
253
253
  continue;
254
254
  for (const method of classExpression.getMethods()) {
@@ -294,7 +294,7 @@ function findNamedDeclaration(sourceFile, title) {
294
294
  if (!statement.isExported())
295
295
  continue;
296
296
  for (const declaration of statement.getDeclarations()) {
297
- const classExpression = declaration.getInitializerIfKind(SyntaxKind.ClassExpression);
297
+ const classExpression = declaration.getInitializerIfKind(tsMorph().SyntaxKind.ClassExpression);
298
298
  if (!classExpression)
299
299
  continue;
300
300
  for (const property of classExpression.getProperties()) {
@@ -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,EAItB,iBAAiB,EAClB,MAAM,iBAAiB,CAAC;AAmBzB;;;GAGG;AAEH,wBAAgB,mCAAmC,IAAI,iBAAiB,CA8BvE;AAGD,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,EAItB,iBAAiB,EAClB,MAAM,iBAAiB,CAAC;AAmBzB;;;GAGG;AAEH,wBAAgB,mCAAmC,IAAI,iBAAiB,CAsCvE;AAGD,wBAAgB,0CAA0C,IAAI,sBAAsB,CAEnF"}
@@ -17,7 +17,7 @@
17
17
  */
18
18
  import * as path from "node:path";
19
19
  import { toSourceAnalysisProvider } from "kibi-plugin-sdk";
20
- import { Project, SyntaxKind, } from "ts-morph";
20
+ import { tsMorph } from "./ts-morph-runtime.js";
21
21
  import { SUPPORTED_SOURCE_EXTENSIONS, chooseScriptKind, formatMethodSymbolName, isPrivateClassMember, } from "./ts-morph-shared.js";
22
22
  const EXTRACTOR_ID = "kibi-plugin-builtin.ts-morph";
23
23
  /**
@@ -26,16 +26,20 @@ const EXTRACTOR_ID = "kibi-plugin-builtin.ts-morph";
26
26
  */
27
27
  // implements REQ-capability-plugin-builtin-parity-v1
28
28
  export function createBuiltinTsMorphSymbolExtractor() {
29
- const project = new Project({
30
- skipAddingFilesFromTsConfig: true,
31
- });
29
+ // Plugin registration creates the extractor on every CLI start; the project
30
+ // (and the TypeScript compiler behind it) is built on the first analysis.
31
+ let project;
32
+ const getProject = () => {
33
+ project ??= new (tsMorph().Project)({ skipAddingFilesFromTsConfig: true });
34
+ return project;
35
+ };
32
36
  return {
33
37
  id: EXTRACTOR_ID,
34
38
  supports(input) {
35
39
  return SUPPORTED_SOURCE_EXTENSIONS.has(path.extname(input.path).toLowerCase());
36
40
  },
37
41
  analyze(input) {
38
- const sourceFile = project.createSourceFile(input.path, input.content, {
42
+ const sourceFile = getProject().createSourceFile(input.path, input.content, {
39
43
  overwrite: true,
40
44
  scriptKind: chooseScriptKind(input.path),
41
45
  });
@@ -110,7 +114,7 @@ function collectSourceSymbols(sourceFile) {
110
114
  `${safeFullText(statement)}\n${safeGetText(declaration)}`));
111
115
  try {
112
116
  const classExpression = typeof declaration.getInitializerIfKind === "function"
113
- ? declaration.getInitializerIfKind(SyntaxKind.ClassExpression)
117
+ ? declaration.getInitializerIfKind(tsMorph().SyntaxKind.ClassExpression)
114
118
  : undefined;
115
119
  if (classExpression) {
116
120
  appendClassMembers(sourceFile, classExpression, declaration.getName(), symbols);
@@ -15,7 +15,7 @@
15
15
  You should have received a copy of the GNU Affero General Public License
16
16
  along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
  */
18
- import { Project } from "ts-morph";
18
+ import { tsMorph } from "./ts-morph-runtime.js";
19
19
  import { chooseScriptKind, isPrivateClassMember } from "./ts-morph-shared.js";
20
20
  /**
21
21
  * Collect exported (and unique bare method) symbol candidates used by
@@ -23,7 +23,7 @@ import { chooseScriptKind, isPrivateClassMember } from "./ts-morph-shared.js";
23
23
  */
24
24
  // implements REQ-capability-plugin-builtin-parity-v1
25
25
  export function collectGranularityCandidates(filePath, content) {
26
- const source = new Project({
26
+ const source = new (tsMorph().Project)({
27
27
  skipAddingFilesFromTsConfig: true,
28
28
  }).createSourceFile(`${filePath}::granularity`, content, {
29
29
  overwrite: true,
@@ -0,0 +1,10 @@
1
+ import type * as TsMorph from "ts-morph";
2
+ /**
3
+ * ts-morph loads the full TypeScript compiler (~150 ms). Consumers import this
4
+ * package for its predicate rules and plugin registry on every CLI command, so
5
+ * the compiler is loaded on first use by a symbol extractor instead of at
6
+ * import time. ts-morph is this package's own dependency, so resolving it from
7
+ * here always finds the declared version.
8
+ */
9
+ export declare function tsMorph(): typeof TsMorph;
10
+ //# sourceMappingURL=ts-morph-runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ts-morph-runtime.d.ts","sourceRoot":"","sources":["../../src/symbols/ts-morph-runtime.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,KAAK,OAAO,MAAM,UAAU,CAAC;AAIzC;;;;;;GAMG;AACH,wBAAgB,OAAO,IAAI,OAAO,OAAO,CAGxC"}
@@ -0,0 +1,31 @@
1
+ /*
2
+ Kibi — repo-local, per-branch, queryable long-term memory for software projects
3
+ Copyright (C) 2026 Piotr Franczyk
4
+
5
+ This program is free software: you can redistribute it and/or modify
6
+ it under the terms of the GNU Affero General Public License as published by
7
+ the Free Software Foundation, either version 3 of the License, or
8
+ (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU Affero General Public License for more details.
14
+
15
+ You should have received a copy of the GNU Affero General Public License
16
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+ */
18
+ // implements REQ-capability-plugin-builtin-parity-v1
19
+ import { createRequire } from "node:module";
20
+ let loaded;
21
+ /**
22
+ * ts-morph loads the full TypeScript compiler (~150 ms). Consumers import this
23
+ * package for its predicate rules and plugin registry on every CLI command, so
24
+ * the compiler is loaded on first use by a symbol extractor instead of at
25
+ * import time. ts-morph is this package's own dependency, so resolving it from
26
+ * here always finds the declared version.
27
+ */
28
+ export function tsMorph() {
29
+ loaded ??= createRequire(import.meta.url)("ts-morph");
30
+ return loaded;
31
+ }
@@ -1,4 +1,4 @@
1
- import { Scope, ScriptKind, SyntaxKind } from "ts-morph";
1
+ import type { Scope, ScriptKind, SyntaxKind } from "ts-morph";
2
2
  export declare const SUPPORTED_SOURCE_EXTENSIONS: Set<string>;
3
3
  export declare function chooseScriptKind(filePath: string): ScriptKind;
4
4
  export declare function formatMethodSymbolName(className: string | undefined, methodName: string): string;
@@ -1 +1 @@
1
- {"version":3,"file":"ts-morph-shared.d.ts","sourceRoot":"","sources":["../../src/symbols/ts-morph-shared.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAGzD,eAAO,MAAM,2BAA2B,aAStC,CAAC;AAGH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,CAY7D;AAGD,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,UAAU,EAAE,MAAM,GACjB,MAAM,CAER;AAED;;;GAGG;AAEH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE;IAC3C,OAAO,CAAC,EAAE,MAAM,MAAM,CAAC;IACvB,WAAW,CAAC,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC;IACxC,QAAQ,CAAC,EAAE,MAAM,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;CAC1C,GAAG,OAAO,CAYV;AAGD,wBAAgB,aAAa,CAAC,CAAC,EAAE,UAAU,EAAE,SAAS,CAAC,EAAE,GAAG,CAAC,GAAG,SAAS,CAGxE"}
1
+ {"version":3,"file":"ts-morph-shared.d.ts","sourceRoot":"","sources":["../../src/symbols/ts-morph-shared.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAI9D,eAAO,MAAM,2BAA2B,aAStC,CAAC;AAGH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,CAY7D;AAGD,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,UAAU,EAAE,MAAM,GACjB,MAAM,CAER;AAED;;;GAGG;AAEH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE;IAC3C,OAAO,CAAC,EAAE,MAAM,MAAM,CAAC;IACvB,WAAW,CAAC,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC;IACxC,QAAQ,CAAC,EAAE,MAAM,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;CAC1C,GAAG,OAAO,CAYV;AAGD,wBAAgB,aAAa,CAAC,CAAC,EAAE,UAAU,EAAE,SAAS,CAAC,EAAE,GAAG,CAAC,GAAG,SAAS,CAGxE"}
@@ -15,7 +15,7 @@
15
15
  You should have received a copy of the GNU Affero General Public License
16
16
  along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
  */
18
- import { Scope, ScriptKind, SyntaxKind } from "ts-morph";
18
+ import { tsMorph } from "./ts-morph-runtime.js";
19
19
  // implements REQ-capability-plugin-builtin-parity-v1
20
20
  export const SUPPORTED_SOURCE_EXTENSIONS = new Set([
21
21
  ".ts",
@@ -31,15 +31,15 @@ export const SUPPORTED_SOURCE_EXTENSIONS = new Set([
31
31
  export function chooseScriptKind(filePath) {
32
32
  const lower = filePath.toLowerCase();
33
33
  if (lower.endsWith(".tsx"))
34
- return ScriptKind.TSX;
34
+ return tsMorph().ScriptKind.TSX;
35
35
  if (lower.endsWith(".ts") ||
36
36
  lower.endsWith(".mts") ||
37
37
  lower.endsWith(".cts")) {
38
- return ScriptKind.TS;
38
+ return tsMorph().ScriptKind.TS;
39
39
  }
40
40
  if (lower.endsWith(".jsx"))
41
- return ScriptKind.JSX;
42
- return ScriptKind.JS;
41
+ return tsMorph().ScriptKind.JSX;
42
+ return tsMorph().ScriptKind.JS;
43
43
  }
44
44
  // implements REQ-capability-plugin-builtin-parity-v1
45
45
  export function formatMethodSymbolName(className, methodName) {
@@ -56,10 +56,10 @@ export function isPrivateClassMember(member) {
56
56
  return true;
57
57
  }
58
58
  if (typeof member.hasModifier === "function") {
59
- return member.hasModifier(SyntaxKind.PrivateKeyword);
59
+ return member.hasModifier(tsMorph().SyntaxKind.PrivateKeyword);
60
60
  }
61
61
  if (typeof member.getScope === "function") {
62
- return member.getScope() === Scope.Private;
62
+ return member.getScope() === tsMorph().Scope.Private;
63
63
  }
64
64
  return false;
65
65
  }
@@ -0,0 +1,47 @@
1
+ import { type CompareClaimsInput, type CompareClaimsResult, type RankSubjectsInput, type RankSubjectsResult, type SubjectCandidate, type SubjectRankingDecision, type VocabularyAlignmentV1 } from "kibi-plugin-sdk";
2
+ /** Minimum builtin score for reusing an existing subject instead of a new one. */
3
+ export declare const BUILTIN_SUBJECT_REUSE_THRESHOLD = 0.4;
4
+ /** Conservative token-similarity bar for flagging a possible duplicate claim. */
5
+ export declare const BUILTIN_CLAIM_SIMILARITY_THRESHOLD = 0.75;
6
+ /** One existing subject fact as read from the KB. */
7
+ export type SubjectVocabularyEntry = Readonly<{
8
+ subjectKey: string;
9
+ title?: string;
10
+ factId?: string;
11
+ requirements?: readonly Readonly<{
12
+ id: string;
13
+ title?: string;
14
+ }>[];
15
+ }>;
16
+ /**
17
+ * Deterministic content tokens: lowercase alphanumeric words, stopwords
18
+ * removed, simple plural folding. Dots and underscores split subject keys.
19
+ */
20
+ export declare function vocabularyTokens(text: string): string[];
21
+ /**
22
+ * Rank existing subjects for one clause. Score = 0.5 * subject-key match +
23
+ * 0.3 * subject-title match + 0.2 * best constraining-requirement-title match,
24
+ * where a match is the IDF-weighted Tversky index over the vocabulary (tokens shared
25
+ * by every subject, such as a product name, weigh little).
26
+ * Ties break by subject key, so the ranking is fully deterministic.
27
+ */
28
+ export declare function rankSubjectCandidates(clauseText: string, vocabulary: readonly SubjectVocabularyEntry[], options?: Readonly<{
29
+ limit?: number;
30
+ }>): SubjectCandidate[];
31
+ /**
32
+ * Dice similarity over content tokens. Claims whose negation differs are never
33
+ * similar ("must expire" vs "must not expire"), and neither are claims whose
34
+ * quantities differ ("15 minutes" vs "60 minutes"): token overlap cannot tell
35
+ * which of two different bounds is meant, so the builtin stays conservative and
36
+ * leaves unit-converted paraphrases to Prolog canonicalization or a provider.
37
+ */
38
+ export declare function claimSimilarity(left: string, right: string): number;
39
+ export declare function builtinSubjectDecision(clause: RankSubjectsInput["clauses"][number]): SubjectRankingDecision;
40
+ /** Deterministic, offline vocabulary-alignment provider. */
41
+ export declare class BuiltinVocabularyAlignment implements VocabularyAlignmentV1 {
42
+ readonly id = "kibi-plugin-builtin.vocabulary-alignment";
43
+ rankSubjects(input: RankSubjectsInput): RankSubjectsResult;
44
+ compareClaims(input: CompareClaimsInput): CompareClaimsResult;
45
+ }
46
+ export declare function createBuiltinVocabularyAlignment(): BuiltinVocabularyAlignment;
47
+ //# sourceMappingURL=builtin-vocabulary-alignment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builtin-vocabulary-alignment.d.ts","sourceRoot":"","sources":["../../src/vocabulary/builtin-vocabulary-alignment.ts"],"names":[],"mappings":"AAkBA,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EAExB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC3B,MAAM,iBAAiB,CAAC;AAGzB,kFAAkF;AAElF,eAAO,MAAM,+BAA+B,MAAM,CAAC;AAEnD,iFAAiF;AAEjF,eAAO,MAAM,kCAAkC,OAAO,CAAC;AAEvD,qDAAqD;AAErD,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,SAAS,QAAQ,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,EAAE,CAAC;CACpE,CAAC,CAAC;AA8CH;;;GAGG;AAEH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAOvD;AA6CD;;;;;;GAMG;AAEH,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,SAAS,sBAAsB,EAAE,EAC7C,OAAO,GAAE,QAAQ,CAAC;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAM,GACzC,gBAAgB,EAAE,CAoDpB;AAYD;;;;;;GAMG;AAEH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAWnE;AAcD,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,iBAAiB,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,GAC3C,sBAAsB,CA4BxB;AAED,4DAA4D;AAE5D,qBAAa,0BAA2B,YAAW,qBAAqB;IACtE,QAAQ,CAAC,EAAE,8CAA8C;IAEzD,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,kBAAkB;IAI1D,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,mBAAmB;CAc9D;AAGD,wBAAgB,gCAAgC,IAAI,0BAA0B,CAE7E"}
@@ -0,0 +1,261 @@
1
+ /*
2
+ Kibi — repo-local, per-branch, queryable long-term memory for software projects
3
+ Copyright (C) 2026 Piotr Franczyk
4
+
5
+ This program is free software: you can redistribute it and/or modify
6
+ it under the terms of the GNU Affero General Public License as published by
7
+ the Free Software Foundation, either version 3 of the License, or
8
+ (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU Affero General Public License for more details.
14
+
15
+ You should have received a copy of the GNU Affero General Public License
16
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+ */
18
+ import { NEW_SUBJECT_CHOICE, } from "kibi-plugin-sdk";
19
+ import { singularize } from "../ontology/normalize.js";
20
+ /** Minimum builtin score for reusing an existing subject instead of a new one. */
21
+ // implements REQ-kibi-vocabulary-alignment-capability
22
+ export const BUILTIN_SUBJECT_REUSE_THRESHOLD = 0.4;
23
+ /** Conservative token-similarity bar for flagging a possible duplicate claim. */
24
+ // implements REQ-kibi-vocabulary-alignment-capability
25
+ export const BUILTIN_CLAIM_SIMILARITY_THRESHOLD = 0.75;
26
+ const STOPWORDS = new Set([
27
+ "a",
28
+ "an",
29
+ "and",
30
+ "are",
31
+ "as",
32
+ "at",
33
+ "be",
34
+ "by",
35
+ "can",
36
+ "for",
37
+ "from",
38
+ "has",
39
+ "have",
40
+ "in",
41
+ "into",
42
+ "is",
43
+ "it",
44
+ "its",
45
+ "may",
46
+ "must",
47
+ "of",
48
+ "on",
49
+ "or",
50
+ "shall",
51
+ "should",
52
+ "so",
53
+ "than",
54
+ "that",
55
+ "the",
56
+ "their",
57
+ "then",
58
+ "this",
59
+ "to",
60
+ "was",
61
+ "when",
62
+ "which",
63
+ "while",
64
+ "will",
65
+ "with",
66
+ ]);
67
+ const NEGATIONS = new Set(["not", "never", "no", "without", "forbid"]);
68
+ /**
69
+ * Deterministic content tokens: lowercase alphanumeric words, stopwords
70
+ * removed, simple plural folding. Dots and underscores split subject keys.
71
+ */
72
+ // implements REQ-kibi-vocabulary-alignment-capability
73
+ export function vocabularyTokens(text) {
74
+ return text
75
+ .toLowerCase()
76
+ .replace(/['’]/g, "")
77
+ .split(/[^a-z0-9]+/)
78
+ .filter((token) => token.length > 0 && !STOPWORDS.has(token))
79
+ .map((token) => singularize(token));
80
+ }
81
+ function tokenSet(text) {
82
+ return new Set(vocabularyTokens(text));
83
+ }
84
+ function round3(value) {
85
+ return Math.round(value * 1000) / 1000;
86
+ }
87
+ /** Weight of clause tokens the subject does not explain (Tversky beta). */
88
+ const UNEXPLAINED_CLAUSE_WEIGHT = 0.2;
89
+ /**
90
+ * IDF-weighted Tversky index between the clause and one target token set.
91
+ * Target tokens the clause misses count fully; clause tokens the target does
92
+ * not explain count lightly, because a subject names a component rather than
93
+ * the whole clause. A specific subject that matches more of the clause
94
+ * therefore beats a short generic one that happens to be fully covered.
95
+ */
96
+ function weightedMatch(clause, target, weight) {
97
+ let matched = 0;
98
+ let missedTarget = 0;
99
+ for (const token of target) {
100
+ const w = weight(token);
101
+ if (clause.has(token))
102
+ matched += w;
103
+ else
104
+ missedTarget += w;
105
+ }
106
+ if (matched === 0)
107
+ return 0;
108
+ let unexplainedClause = 0;
109
+ for (const token of clause) {
110
+ if (!target.has(token))
111
+ unexplainedClause += weight(token);
112
+ }
113
+ return (matched /
114
+ (matched + missedTarget + UNEXPLAINED_CLAUSE_WEIGHT * unexplainedClause));
115
+ }
116
+ /**
117
+ * Rank existing subjects for one clause. Score = 0.5 * subject-key match +
118
+ * 0.3 * subject-title match + 0.2 * best constraining-requirement-title match,
119
+ * where a match is the IDF-weighted Tversky index over the vocabulary (tokens shared
120
+ * by every subject, such as a product name, weigh little).
121
+ * Ties break by subject key, so the ranking is fully deterministic.
122
+ */
123
+ // implements REQ-kibi-vocabulary-alignment-capability
124
+ export function rankSubjectCandidates(clauseText, vocabulary, options = {}) {
125
+ const clause = tokenSet(clauseText);
126
+ if (clause.size === 0 || vocabulary.length === 0)
127
+ return [];
128
+ const documents = vocabulary.map((entry) => ({
129
+ entry,
130
+ key: tokenSet(entry.subjectKey),
131
+ title: tokenSet(entry.title ?? ""),
132
+ requirements: (entry.requirements ?? []).map((requirement) => tokenSet(requirement.title ?? "")),
133
+ }));
134
+ const documentFrequency = new Map();
135
+ for (const document of documents) {
136
+ const tokens = new Set([
137
+ ...document.key,
138
+ ...document.title,
139
+ ...document.requirements.flatMap((set) => [...set]),
140
+ ]);
141
+ for (const token of tokens) {
142
+ documentFrequency.set(token, (documentFrequency.get(token) ?? 0) + 1);
143
+ }
144
+ }
145
+ const weight = (token) => Math.log(1 + documents.length / (documentFrequency.get(token) ?? 1));
146
+ const candidates = documents.map((document) => {
147
+ const keyScore = weightedMatch(clause, document.key, weight);
148
+ const titleScore = weightedMatch(clause, document.title, weight);
149
+ const requirementScore = Math.max(0, ...document.requirements.map((set) => weightedMatch(clause, set, weight)));
150
+ const requirementTitles = (document.entry.requirements ?? [])
151
+ .map((requirement) => requirement.title)
152
+ .filter((title) => typeof title === "string");
153
+ return {
154
+ subjectKey: document.entry.subjectKey,
155
+ score: round3(0.5 * keyScore + 0.3 * titleScore + 0.2 * requirementScore),
156
+ ...(document.entry.title !== undefined
157
+ ? { title: document.entry.title }
158
+ : {}),
159
+ ...(requirementTitles.length > 0 ? { requirementTitles } : {}),
160
+ };
161
+ });
162
+ return candidates
163
+ .filter((candidate) => candidate.score > 0)
164
+ .sort((left, right) => right.score - left.score ||
165
+ left.subjectKey.localeCompare(right.subjectKey))
166
+ .slice(0, options.limit ?? 5);
167
+ }
168
+ /**
169
+ * Quantities as written: number plus the unit symbol that follows it, with the
170
+ * unit's case preserved (3 MB and 3 Mb are different quantities).
171
+ */
172
+ function quantities(text) {
173
+ return [...text.matchAll(/(\d+(?:\.\d+)?)\s*([A-Za-z%]+)?/g)]
174
+ .map((match) => `${Number(match[1])}|${match[2] ?? ""}`)
175
+ .sort();
176
+ }
177
+ /**
178
+ * Dice similarity over content tokens. Claims whose negation differs are never
179
+ * similar ("must expire" vs "must not expire"), and neither are claims whose
180
+ * quantities differ ("15 minutes" vs "60 minutes"): token overlap cannot tell
181
+ * which of two different bounds is meant, so the builtin stays conservative and
182
+ * leaves unit-converted paraphrases to Prolog canonicalization or a provider.
183
+ */
184
+ // implements REQ-kibi-vocabulary-alignment-capability
185
+ export function claimSimilarity(left, right) {
186
+ const a = tokenSet(left);
187
+ const b = tokenSet(right);
188
+ if (a.size === 0 || b.size === 0)
189
+ return 0;
190
+ if (quantities(left).join(" ") !== quantities(right).join(" "))
191
+ return 0;
192
+ const negatedA = [...a].some((token) => NEGATIONS.has(token));
193
+ const negatedB = [...b].some((token) => NEGATIONS.has(token));
194
+ if (negatedA !== negatedB)
195
+ return 0;
196
+ let shared = 0;
197
+ for (const token of a)
198
+ if (b.has(token))
199
+ shared += 1;
200
+ return round3((2 * shared) / (a.size + b.size));
201
+ }
202
+ function normalizedKey(value) {
203
+ return value
204
+ .trim()
205
+ .toLowerCase()
206
+ .replace(/[\\/]+/g, ".")
207
+ .replace(/[^a-z0-9.]+/g, "_")
208
+ .replace(/_+/g, "_")
209
+ .replace(/\.+/g, ".")
210
+ .replace(/(^[._]+|[._]+$)/g, "");
211
+ }
212
+ // implements REQ-kibi-vocabulary-alignment-capability
213
+ export function builtinSubjectDecision(clause) {
214
+ const proposed = clause.proposedSubjectKey !== undefined
215
+ ? normalizedKey(clause.proposedSubjectKey)
216
+ : undefined;
217
+ const exact = clause.candidates.find((candidate) => candidate.subjectKey === proposed);
218
+ if (exact) {
219
+ return {
220
+ claimKey: clause.claimKey,
221
+ choice: exact.subjectKey,
222
+ confidence: 1,
223
+ };
224
+ }
225
+ const best = clause.candidates[0];
226
+ if (best && best.score >= BUILTIN_SUBJECT_REUSE_THRESHOLD) {
227
+ return {
228
+ claimKey: clause.claimKey,
229
+ choice: best.subjectKey,
230
+ confidence: best.score,
231
+ };
232
+ }
233
+ return {
234
+ claimKey: clause.claimKey,
235
+ choice: NEW_SUBJECT_CHOICE,
236
+ confidence: round3(1 - (best?.score ?? 0)),
237
+ };
238
+ }
239
+ /** Deterministic, offline vocabulary-alignment provider. */
240
+ // implements REQ-kibi-vocabulary-alignment-capability
241
+ export class BuiltinVocabularyAlignment {
242
+ id = "kibi-plugin-builtin.vocabulary-alignment";
243
+ rankSubjects(input) {
244
+ return { decisions: input.clauses.map(builtinSubjectDecision) };
245
+ }
246
+ compareClaims(input) {
247
+ const judgments = input.pairs.map((pair) => {
248
+ const similarity = claimSimilarity(pair.left.claimText, pair.right.claimText);
249
+ return {
250
+ pairKey: pair.pairKey,
251
+ sameObligation: similarity >= BUILTIN_CLAIM_SIMILARITY_THRESHOLD,
252
+ confidence: similarity,
253
+ };
254
+ });
255
+ return { judgments };
256
+ }
257
+ }
258
+ // implements REQ-kibi-vocabulary-alignment-capability
259
+ export function createBuiltinVocabularyAlignment() {
260
+ return new BuiltinVocabularyAlignment();
261
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kibi-plugin-builtin",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
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",
@@ -32,7 +32,7 @@
32
32
  "access": "public"
33
33
  },
34
34
  "dependencies": {
35
- "kibi-plugin-sdk": "^0.2.0",
35
+ "kibi-plugin-sdk": "^0.3.0",
36
36
  "ts-morph": "^23.0.0"
37
37
  },
38
38
  "devDependencies": {