kibi-plugin-builtin 0.3.0 → 0.4.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,29 @@
1
1
  # kibi-plugin-builtin
2
2
 
3
+ ## 0.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - db5376c: Kibi can now analyze source in Python, Go, Rust and a broader set of common programming and configuration languages, offline, while JavaScript and TypeScript keep working as before. Staged checks read source and authored knowledge from one immutable Git tree, inspect both sides of every change, and keep incomplete analysis explicit instead of treating it as proof. Large files now return an explicit analysis failure instead of a second validation error.
8
+ - Add the asynchronous `kibi.symbol-extractor.v2` contract with validated UTF-16 ranges, bounded inputs and host-assigned provenance, plus a ts-morph v2 adapter.
9
+ - Add the optional `kibi-plugin-treesitter` package with pinned WASM grammars, queries, licenses and an approved-analyzer closure verified before import.
10
+ - Capture staged and explicit-diff snapshots from Git objects; merge duplicate symbol-manifest records deterministically and fail conflicting authored fields with `SOURCE_DUPLICATE_CONFLICT`.
11
+ - Add opt-in, bounded parser-phase and Prolog round-trip timing observations that never enter result schemas.
12
+
13
+ ### Patch Changes
14
+
15
+ - 8622782: An impact review prepared on one machine now still validates in CI, and the MCP server and the CLI agree on whether it is current. Checks only block on incomplete source analysis the author can act on: the committed side of a change never blocks, and a known analyzer limitation such as a Rust macro or a Python decorator matters only where it overlaps the changed lines. Syntax, parse, timeout and integrity problems in the staged file still block. Long-running hosts no longer grow memory with every analyzed file, and Tree-sitter analysis reuses warm workers instead of starting one per file.
16
+ - Identify the impact evaluator by its contract version and the canonical JSON of the schemas it consumes, instead of hashing the installed file tree and `process.version`. Identify the builtin analyzer runtime by its package identity rather than its installed tree.
17
+ - `kibi-runtime` bundles the CLI operations again (`kibi-cli` is a build-time dependency only); it no longer installs `kibi-cli`.
18
+ - Add one shared analysis gate (`analysisObligation`) used by staged checks, impact-review preparation and validation, and impact reports.
19
+ - Classify source paths from a single extension table in `source-classification`, with parity tests against the Tree-sitter catalog and the builtin extractor.
20
+ - Remove analyzed files from the ts-morph project after each v1 and v2 analysis.
21
+ - Add an optional `timeoutMs` budget to `SymbolExtractorV2AnalyzeInput`; the host sets it slightly below its own deadline, so providers report their own timeout. Tree-sitter uses a bounded, persistent worker pool with grammars loaded once per worker, and the host analyzes up to four changed files at a time.
22
+
23
+ - Updated dependencies [db5376c]
24
+ - Updated dependencies [8622782]
25
+ - kibi-plugin-sdk@0.4.0
26
+
3
27
  ## 0.3.0
4
28
 
5
29
  ### Minor Changes
@@ -9,7 +33,6 @@
9
33
  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
34
 
11
35
  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
36
  - 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
37
  - 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
38
  - 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.
@@ -25,7 +48,6 @@
25
48
  this repository, `kibi status` for a fresh KB drops from about 3 seconds to
26
49
  about 1.7, and a command that needs neither schema validation nor symbol
27
50
  extraction starts about 0.5 seconds sooner.
28
-
29
51
  - `kb_status_json` skips the full-entity stale-reason scan when the KB is
30
52
  fresh, because a fresh verdict already rules out every stale reason.
31
53
  - `kibi-plugin-builtin` loads ts-morph (the TypeScript compiler) on first
@@ -40,13 +62,11 @@
40
62
  ### Minor Changes
41
63
 
42
64
  - 783cc75: Coordinate enrichment, granularity candidate collection, and private-member helpers now live in `kibi-plugin-builtin`, so the CLI can analyze TypeScript/JavaScript symbols without depending on `ts-morph` directly. Hosts that only install `kibi-cli` still get the same enrichment and staged-symbol behavior through the builtin plugin.
43
-
44
65
  - Export `enrichSymbolCoordinatesWithTsMorph`, `collectGranularityCandidates`, `onlyCandidate`, and `isPrivateClassMember` from `kibi-plugin-builtin`
45
66
  - Thin-wrap those helpers from CLI `symbols-ts`, `symbol-granularity`, and `symbol-extract`
46
67
  - Drop `ts-morph` from `kibi-cli` runtime dependencies (kept as a test-only devDependency for AST spies)
47
68
 
48
69
  - 783cc75: Kibi ships a default capability plugin package so TypeScript symbol extraction, ontology matching, and semantic lane classification live behind the public plugin protocol instead of private CLI paths.
49
-
50
70
  - Add `kibi-plugin-builtin@0.1.0` with `kibiPlugin` exporting all three capabilities
51
71
  - Built-in ts-morph extractor, ontology pack (launcher→core→policy→product→product-tail), and sync semantic classifier
52
72
  - CLI depends on this package and thin-wraps the extractor / ontology pack for default parity
@@ -54,12 +74,10 @@
54
74
  ### Patch Changes
55
75
 
56
76
  - b375e8f: This maintenance update brings the affected package code and tests into line with Kibi's Biome checks while preserving runtime behavior. It also replaces MCP non-null assertions with receiver-preserving method calls.
57
-
58
77
  - Format affected files, sort imports, and remove unnecessary template literals.
59
78
  - Preserve EngineClient `this` when forwarding optional Prolog methods.
60
79
 
61
80
  - e6571b4: External classifiers stay limited to the two allowlisted operations, ontology matches keep their claim keys through host composition, and CLI predicate rule tables now re-export the builtin pack so advisor and modeling cannot drift. Lane selection from the builtin classifier also accepts host snake_case signal shapes, which unblocks typecheck/build for capability-plugin call sites.
62
-
63
81
  - fix(builtin): chooseLane accepts kind-only LaneSignal (unblocks analysis-receipt typecheck)
64
82
  - feat(cli): stamp claimKey on composed ontology match candidates
65
83
  - refactor(cli): re-export predicate rule tables from kibi-plugin-builtin
package/dist/index.d.ts CHANGED
@@ -8,7 +8,7 @@ export { PRODUCT_PREDICATE_RULES } from "./ontology/predicate-rules-product.js";
8
8
  export { PRODUCT_TAIL_PREDICATE_RULES } from "./ontology/predicate-rules-product-tail.js";
9
9
  export { normalizeKey, normalizePredicateToken, normalizeSubjectKey, singularize, commaList, } from "./ontology/normalize.js";
10
10
  export { createBuiltinSemanticClassifier, BuiltinSemanticClassifier, detectSignals, chooseLane, } from "./semantic/builtin-classifier.js";
11
- export { createBuiltinTsMorphSymbolExtractor, createBuiltinTsMorphSourceAnalysisProvider, } from "./symbols/ts-morph-extractor.js";
11
+ export { createBuiltinTsMorphSymbolExtractor, createBuiltinTsMorphSymbolExtractorV2, createBuiltinTsMorphSourceAnalysisProvider, } from "./symbols/ts-morph-extractor.js";
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";
@@ -29,6 +29,7 @@ export declare const kibiPlugin: {
29
29
  semanticClassifier: import("kibi-plugin-sdk").SemanticClassifierV1;
30
30
  ontologyPack: import("kibi-plugin-sdk").OntologyPackV1;
31
31
  symbolExtractor: import("kibi-plugin-sdk").SymbolExtractorV1;
32
+ symbolExtractorV2: import("kibi-plugin-sdk").SymbolExtractorV2;
32
33
  vocabularyAlignment: import("./index.js").BuiltinVocabularyAlignment;
33
34
  };
34
35
  };
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAiCA,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,qCAAqC,EACrC,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;;;;;;;;;;;;;;;;CAgBrB,CAAC;AAGH,eAAe,UAAU,CAAC"}
package/dist/index.js CHANGED
@@ -19,7 +19,7 @@ import { createRequire } from "node:module";
19
19
  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
- import { createBuiltinTsMorphSymbolExtractor } from "./symbols/ts-morph-extractor.js";
22
+ import { createBuiltinTsMorphSymbolExtractor, createBuiltinTsMorphSymbolExtractorV2, } from "./symbols/ts-morph-extractor.js";
23
23
  import { createBuiltinVocabularyAlignment } from "./vocabulary/builtin-vocabulary-alignment.js";
24
24
  const packageJson = createRequire(import.meta.url)("../package.json");
25
25
  // implements REQ-capability-plugin-builtin-parity-v1
@@ -43,7 +43,7 @@ export { normalizeKey, normalizePredicateToken, normalizeSubjectKey, singularize
43
43
  // implements REQ-capability-plugin-builtin-parity-v1
44
44
  export { createBuiltinSemanticClassifier, BuiltinSemanticClassifier, detectSignals, chooseLane, } from "./semantic/builtin-classifier.js";
45
45
  // implements REQ-capability-plugin-builtin-parity-v1
46
- export { createBuiltinTsMorphSymbolExtractor, createBuiltinTsMorphSourceAnalysisProvider, } from "./symbols/ts-morph-extractor.js";
46
+ export { createBuiltinTsMorphSymbolExtractor, createBuiltinTsMorphSymbolExtractorV2, createBuiltinTsMorphSourceAnalysisProvider, } from "./symbols/ts-morph-extractor.js";
47
47
  // implements REQ-capability-plugin-builtin-parity-v1
48
48
  export { enrichSymbolCoordinatesWithTsMorph, } from "./symbols/ts-morph-enrichment.js";
49
49
  // implements REQ-capability-plugin-builtin-parity-v1
@@ -69,6 +69,7 @@ export const kibiPlugin = defineKibiPlugin({
69
69
  semanticClassifier: createBuiltinSemanticClassifier(),
70
70
  ontologyPack: createBuiltinOntologyPack(),
71
71
  symbolExtractor: createBuiltinTsMorphSymbolExtractor(),
72
+ symbolExtractorV2: createBuiltinTsMorphSymbolExtractorV2(),
72
73
  vocabularyAlignment: createBuiltinVocabularyAlignment(),
73
74
  },
74
75
  });
@@ -1,8 +1,10 @@
1
- import type { SourceAnalysisProvider, SymbolExtractorV1 } from "kibi-plugin-sdk";
1
+ import type { SourceAnalysisProvider, SymbolExtractorV1, SymbolExtractorV2 } from "kibi-plugin-sdk";
2
2
  /**
3
3
  * Built-in ts-morph symbol extractor. Behavior matches the historical CLI
4
4
  * `createTsMorphSourceAnalysisProvider` analysis path.
5
5
  */
6
6
  export declare function createBuiltinTsMorphSymbolExtractor(): SymbolExtractorV1;
7
+ /** Built-in asynchronous ts-morph extractor for the source-bound v2 contract. */
8
+ export declare function createBuiltinTsMorphSymbolExtractorV2(): SymbolExtractorV2;
7
9
  export declare function createBuiltinTsMorphSourceAnalysisProvider(): SourceAnalysisProvider;
8
10
  //# sourceMappingURL=ts-morph-extractor.d.ts.map
@@ -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,CAsCvE;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,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"}
@@ -20,6 +20,34 @@ import { toSourceAnalysisProvider } from "kibi-plugin-sdk";
20
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
+ function normalizeV2ScriptLanguage(language) {
24
+ const normalized = language?.trim().toLowerCase();
25
+ if (["javascript", "js", "node", "nodejs"].includes(normalized ?? "")) {
26
+ return "javascript";
27
+ }
28
+ if (["typescript", "ts"].includes(normalized ?? "")) {
29
+ return "typescript";
30
+ }
31
+ return undefined;
32
+ }
33
+ function v2ScriptKind(filePath, languageHint) {
34
+ const extension = path.extname(filePath).toLowerCase();
35
+ const language = normalizeV2ScriptLanguage(languageHint);
36
+ if (SUPPORTED_SOURCE_EXTENSIONS.has(extension)) {
37
+ if (languageHint !== undefined && language === undefined)
38
+ return undefined;
39
+ if (language !== undefined && language !== inferSourceLanguage(filePath)) {
40
+ return undefined;
41
+ }
42
+ return chooseScriptKind(filePath);
43
+ }
44
+ if (extension === "" && language !== undefined) {
45
+ return language === "typescript"
46
+ ? tsMorph().ScriptKind.TS
47
+ : tsMorph().ScriptKind.JS;
48
+ }
49
+ return undefined;
50
+ }
23
51
  /**
24
52
  * Built-in ts-morph symbol extractor. Behavior matches the historical CLI
25
53
  * `createTsMorphSourceAnalysisProvider` analysis path.
@@ -43,41 +71,175 @@ export function createBuiltinTsMorphSymbolExtractor() {
43
71
  overwrite: true,
44
72
  scriptKind: chooseScriptKind(input.path),
45
73
  });
46
- return {
47
- sourceFile: input.path,
48
- language: inferSourceLanguage(input.path),
49
- module: {
50
- title: inferModuleTitle(input.path),
74
+ try {
75
+ return {
76
+ sourceFile: input.path,
51
77
  language: inferSourceLanguage(input.path),
52
- analysisMode: "parser",
53
- },
54
- symbols: collectSourceSymbols(sourceFile),
78
+ module: {
79
+ title: inferModuleTitle(input.path),
80
+ language: inferSourceLanguage(input.path),
81
+ analysisMode: "parser",
82
+ },
83
+ symbols: collectSourceSymbols(sourceFile),
84
+ };
85
+ }
86
+ finally {
87
+ // Analysis is per file and purely syntactic; keeping files would grow
88
+ // the project without bound in a long-running daemon.
89
+ getProject().removeSourceFile(sourceFile);
90
+ }
91
+ },
92
+ };
93
+ }
94
+ /** Built-in asynchronous ts-morph extractor for the source-bound v2 contract. */
95
+ // implements REQ-capability-plugin-builtin-parity-v1
96
+ export function createBuiltinTsMorphSymbolExtractorV2() {
97
+ // Like the v1 extractor, build the project (and load the TypeScript
98
+ // compiler) on the first analysis rather than at plugin registration.
99
+ let project;
100
+ const getProject = () => {
101
+ project ??= new (tsMorph().Project)({ skipAddingFilesFromTsConfig: true });
102
+ return project;
103
+ };
104
+ return {
105
+ id: `${EXTRACTOR_ID}.v2`,
106
+ supports(input) {
107
+ return v2ScriptKind(input.path, input.language) !== undefined;
108
+ },
109
+ async analyze(input) {
110
+ const extension = path.extname(input.path).toLowerCase();
111
+ const language = normalizeV2ScriptLanguage(input.language) ??
112
+ (input.language?.trim() ||
113
+ (SUPPORTED_SOURCE_EXTENSIONS.has(extension)
114
+ ? inferSourceLanguage(input.path)
115
+ : "unknown"));
116
+ const module = {
117
+ title: inferModuleTitle(input.path),
118
+ language,
119
+ analysisMode: "parser",
55
120
  };
121
+ const scriptKind = v2ScriptKind(input.path, input.language);
122
+ if (scriptKind === undefined) {
123
+ return {
124
+ contractVersion: "kibi.symbol-extractor.v2",
125
+ status: "unsupported",
126
+ sourceFile: input.path,
127
+ language,
128
+ module,
129
+ symbols: [],
130
+ diagnostics: [
131
+ {
132
+ code: "UNSUPPORTED_SOURCE_TYPE",
133
+ message: `The built-in ts-morph extractor does not support '${path.extname(input.path) || "extensionless"}' files`,
134
+ },
135
+ ],
136
+ uncoveredRanges: [],
137
+ };
138
+ }
139
+ let sourceFile;
140
+ try {
141
+ sourceFile = getProject().createSourceFile(input.path, input.content, {
142
+ overwrite: true,
143
+ scriptKind,
144
+ });
145
+ const analyzed = sourceFile;
146
+ const diagnostics = analyzed
147
+ .getProject()
148
+ .getProgram()
149
+ .getSyntacticDiagnostics(analyzed)
150
+ .map((diagnostic) => {
151
+ const start = diagnostic.getStart();
152
+ const length = diagnostic.getLength();
153
+ const range = start === undefined
154
+ ? wholeSourceRange(analyzed, input.content)
155
+ : sourceRangeAtOffsets(analyzed, input.content, start, start + (length ?? 0));
156
+ const message = diagnostic.getMessageText();
157
+ return {
158
+ code: `TS${diagnostic.getCode()}`,
159
+ message: typeof message === "string"
160
+ ? message
161
+ : message.getMessageText(),
162
+ range,
163
+ };
164
+ });
165
+ const result = {
166
+ contractVersion: "kibi.symbol-extractor.v2",
167
+ status: diagnostics.length > 0 ? "partial" : "ok",
168
+ sourceFile: input.path,
169
+ language,
170
+ module,
171
+ symbols: collectSourceSymbols(analyzed, true),
172
+ diagnostics,
173
+ uncoveredRanges: diagnostics.map(({ range, code }) => ({
174
+ ...range,
175
+ reason: `The TypeScript parser reported ${code} at this source span.`,
176
+ })),
177
+ };
178
+ return result;
179
+ }
180
+ catch (error) {
181
+ return {
182
+ contractVersion: "kibi.symbol-extractor.v2",
183
+ status: "failed",
184
+ sourceFile: input.path,
185
+ language,
186
+ module,
187
+ symbols: [],
188
+ diagnostics: [
189
+ {
190
+ code: "SOURCE_ANALYSIS_FAILED",
191
+ message: error instanceof Error
192
+ ? error.message
193
+ : "The built-in ts-morph extractor failed",
194
+ },
195
+ ],
196
+ uncoveredRanges: [],
197
+ };
198
+ }
199
+ finally {
200
+ if (sourceFile)
201
+ getProject().removeSourceFile(sourceFile);
202
+ }
56
203
  },
57
204
  };
58
205
  }
206
+ function sourceRangeAtOffsets(sourceFile, content, startOffset, endOffset) {
207
+ const boundedStart = Math.min(Math.max(startOffset, 0), content.length);
208
+ const boundedEnd = Math.min(Math.max(endOffset, boundedStart), content.length);
209
+ const start = sourceFile.getLineAndColumnAtPos(boundedStart);
210
+ const end = sourceFile.getLineAndColumnAtPos(boundedEnd);
211
+ return {
212
+ startLine: start.line,
213
+ startColumn: Math.max(0, start.column - 1),
214
+ endLine: end.line,
215
+ endColumn: Math.max(0, end.column - 1),
216
+ };
217
+ }
218
+ function wholeSourceRange(sourceFile, content) {
219
+ return sourceRangeAtOffsets(sourceFile, content, 0, content.length);
220
+ }
59
221
  // implements REQ-capability-plugin-builtin-parity-v1
60
222
  export function createBuiltinTsMorphSourceAnalysisProvider() {
61
223
  return toSourceAnalysisProvider(createBuiltinTsMorphSymbolExtractor());
62
224
  }
63
- function collectSourceSymbols(sourceFile) {
225
+ function collectSourceSymbols(sourceFile, includeV2Fields = false) {
64
226
  const symbols = [];
65
227
  for (const decl of sourceFile.getFunctions()) {
66
228
  if (!decl.isExported())
67
229
  continue;
68
- pushSymbol(symbols, () => toSourceSymbolAnalysis(sourceFile, decl.getName() ?? "<anonymous>", "function", decl.getNameNode() ?? decl, decl,
230
+ pushSymbol(symbols, includeV2Fields, () => toSourceSymbolAnalysis(sourceFile, decl.getName() ?? "<anonymous>", "function", decl.getNameNode() ?? decl, decl,
69
231
  // Functions: leading trivia via getFullText + JSDoc (historical CLI).
70
- fullTextWithJsDocs(decl)));
232
+ fullTextWithJsDocs(decl), includeV2Fields));
71
233
  }
72
234
  for (const decl of sourceFile.getClasses()) {
73
235
  if (!decl.isExported())
74
236
  continue;
75
- pushSymbol(symbols, () => toSourceSymbolAnalysis(sourceFile, decl.getName() ?? "<anonymous>", "class", decl.getNameNode() ?? decl, decl,
237
+ pushSymbol(symbols, includeV2Fields, () => toSourceSymbolAnalysis(sourceFile, decl.getName() ?? "<anonymous>", "class", decl.getNameNode() ?? decl, decl,
76
238
  // Leading comments + JSDoc only — never the class body (method
77
239
  // `// implements` must stay on the method symbol).
78
- leadingCommentsAndJsDocs(sourceFile, decl)));
240
+ leadingCommentsAndJsDocs(sourceFile, decl), includeV2Fields));
79
241
  try {
80
- appendClassMembers(sourceFile, decl, decl.getName(), symbols);
242
+ appendClassMembers(sourceFile, decl, decl.getName(), symbols, includeV2Fields);
81
243
  }
82
244
  catch {
83
245
  // Skip malformed class member walks; keep the class symbol.
@@ -86,42 +248,44 @@ function collectSourceSymbols(sourceFile) {
86
248
  for (const decl of sourceFile.getInterfaces()) {
87
249
  if (!decl.isExported())
88
250
  continue;
89
- pushSymbol(symbols, () => toSourceSymbolAnalysis(sourceFile, decl.getName() ?? "<anonymous>", "interface", decl.getNameNode() ?? decl, decl,
251
+ pushSymbol(symbols, includeV2Fields, () => toSourceSymbolAnalysis(sourceFile, decl.getName() ?? "<anonymous>", "interface", decl.getNameNode() ?? decl, decl,
90
252
  // Historical CLI used getText(); keep calling it so characterization
91
253
  // mocks that throw from getText still isolate the failure.
92
- `${leadingCommentsAndJsDocs(sourceFile, decl)}\n${safeGetText(decl)}`));
254
+ `${leadingCommentsAndJsDocs(sourceFile, decl)}\n${safeGetText(decl)}`, includeV2Fields));
93
255
  }
94
256
  for (const decl of sourceFile.getTypeAliases()) {
95
257
  if (!decl.isExported())
96
258
  continue;
97
- pushSymbol(symbols, () => toSourceSymbolAnalysis(sourceFile, decl.getName() ?? "<anonymous>", "type", decl.getNameNode() ?? decl, decl, `${leadingCommentsAndJsDocs(sourceFile, decl)}\n${safeGetText(decl)}`));
259
+ pushSymbol(symbols, includeV2Fields, () => toSourceSymbolAnalysis(sourceFile, decl.getName() ?? "<anonymous>", "type", decl.getNameNode() ?? decl, decl, `${leadingCommentsAndJsDocs(sourceFile, decl)}\n${safeGetText(decl)}`, includeV2Fields));
98
260
  }
99
261
  for (const decl of sourceFile.getEnums()) {
100
262
  if (!decl.isExported())
101
263
  continue;
102
- pushSymbol(symbols, () => toSourceSymbolAnalysis(sourceFile, decl.getName() ?? "<anonymous>", "enum", decl.getNameNode() ?? decl, decl,
264
+ pushSymbol(symbols, includeV2Fields, () => toSourceSymbolAnalysis(sourceFile, decl.getName() ?? "<anonymous>", "enum", decl.getNameNode() ?? decl, decl,
103
265
  // Enums historically used getText(); prefer leading comments when the
104
266
  // real AST is available, else getText for test doubles.
105
- `${leadingCommentsAndJsDocs(sourceFile, decl)}\n${safeGetText(decl)}`));
267
+ `${leadingCommentsAndJsDocs(sourceFile, decl)}\n${safeGetText(decl)}`, includeV2Fields));
106
268
  }
107
269
  for (const statement of sourceFile.getVariableStatements()) {
108
270
  if (!statement.isExported())
109
271
  continue;
110
272
  for (const declaration of statement.getDeclarations()) {
111
- pushSymbol(symbols, () => toSourceSymbolAnalysis(sourceFile, declaration.getName(), "variable", declaration.getNameNode() ?? declaration, declaration,
273
+ pushSymbol(symbols, includeV2Fields, () => toSourceSymbolAnalysis(sourceFile, declaration.getName(), "variable", declaration.getNameNode() ?? declaration, declaration,
112
274
  // Statement carries leading `// implements`; declaration text is the
113
275
  // historical fallback when only getText is stubbed in tests.
114
- `${safeFullText(statement)}\n${safeGetText(declaration)}`));
276
+ `${safeFullText(statement)}\n${safeGetText(declaration)}`, includeV2Fields));
115
277
  try {
116
278
  const classExpression = typeof declaration.getInitializerIfKind === "function"
117
279
  ? declaration.getInitializerIfKind(tsMorph().SyntaxKind.ClassExpression)
118
280
  : undefined;
119
281
  if (classExpression) {
120
- appendClassMembers(sourceFile, classExpression, declaration.getName(), symbols);
282
+ appendClassMembers(sourceFile, classExpression, declaration.getName(), symbols, includeV2Fields);
121
283
  }
122
284
  }
123
- catch {
124
- // Skip malformed class-expression members; keep the variable symbol.
285
+ catch (error) {
286
+ if (includeV2Fields)
287
+ throw error;
288
+ // Preserve legacy v1 class-expression recovery.
125
289
  }
126
290
  }
127
291
  }
@@ -164,7 +328,7 @@ function safeFullText(node) {
164
328
  function safeGetText(node) {
165
329
  return typeof node.getText === "function" ? node.getText() : "";
166
330
  }
167
- function appendClassMembers(sourceFile, declaration, className, symbols) {
331
+ function appendClassMembers(sourceFile, declaration, className, symbols, includeV2Fields) {
168
332
  // Inherit class *leading* ownership comments onto members (not the class
169
333
  // body). Method-local `// implements` stay on the method via getFullText.
170
334
  const classLeading = leadingCommentsAndJsDocs(sourceFile, declaration);
@@ -174,7 +338,7 @@ function appendClassMembers(sourceFile, declaration, className, symbols) {
174
338
  for (const method of methods) {
175
339
  if (isPrivateClassMember(method))
176
340
  continue;
177
- pushSymbol(symbols, () => toSourceSymbolAnalysis(sourceFile, formatMethodSymbolName(className, method.getName()), "method", method.getNameNode() ?? method, method, `${classLeading}\n${fullTextWithJsDocs(method)}`));
341
+ pushSymbol(symbols, includeV2Fields, () => toSourceSymbolAnalysis(sourceFile, formatMethodSymbolName(className, method.getName()), "method", method.getNameNode() ?? method, method, `${classLeading}\n${fullTextWithJsDocs(method)}`, includeV2Fields, className));
178
342
  }
179
343
  const properties = typeof declaration.getProperties === "function"
180
344
  ? declaration.getProperties()
@@ -182,7 +346,7 @@ function appendClassMembers(sourceFile, declaration, className, symbols) {
182
346
  for (const property of properties) {
183
347
  if (isPrivateClassMember(property))
184
348
  continue;
185
- pushSymbol(symbols, () => toSourceSymbolAnalysis(sourceFile, formatMethodSymbolName(className, property.getName()), "property", property.getNameNode() ?? property, property, `${classLeading}\n${fullTextWithJsDocs(property)}`));
349
+ pushSymbol(symbols, includeV2Fields, () => toSourceSymbolAnalysis(sourceFile, formatMethodSymbolName(className, property.getName()), "property", property.getNameNode() ?? property, property, `${classLeading}\n${fullTextWithJsDocs(property)}`, includeV2Fields, className));
186
350
  }
187
351
  const accessors = [
188
352
  ...(typeof declaration.getGetAccessors === "function"
@@ -192,24 +356,47 @@ function appendClassMembers(sourceFile, declaration, className, symbols) {
192
356
  ? declaration.getSetAccessors()
193
357
  : []),
194
358
  ];
359
+ const pairedAccessors = new Set();
195
360
  for (const accessor of accessors) {
196
361
  if (isPrivateClassMember(accessor))
197
362
  continue;
198
- pushSymbol(symbols, () => toSourceSymbolAnalysis(sourceFile, formatMethodSymbolName(className, accessor.getName()), "accessor", accessor.getNameNode() ?? accessor, accessor, `${classLeading}\n${fullTextWithJsDocs(accessor)}`));
363
+ if (pairedAccessors.has(accessor))
364
+ continue;
365
+ if (includeV2Fields) {
366
+ const candidates = accessors.filter((candidate) => candidate.getName() === accessor.getName() &&
367
+ candidate.isStatic() === accessor.isStatic());
368
+ const other = candidates.find((candidate) => candidate !== accessor);
369
+ if (candidates.length === 2 &&
370
+ other &&
371
+ other.getKind() !== accessor.getKind()) {
372
+ const first = accessor.getStart() < other.getStart() ? accessor : other;
373
+ const last = first === accessor ? other : accessor;
374
+ // A complementary getter/setter pair is one authored property. Keep
375
+ // both bodies in its span without assigning the same ID twice.
376
+ const symbol = toSourceSymbolAnalysis(sourceFile, formatMethodSymbolName(className, accessor.getName()), "accessor", first.getNameNode() ?? first, last, `${classLeading}\n${fullTextWithJsDocs(first)}\n${fullTextWithJsDocs(last)}`, true, className);
377
+ symbols.push({ ...symbol, nativeKind: "AccessorPair" });
378
+ pairedAccessors.add(accessor);
379
+ pairedAccessors.add(other);
380
+ continue;
381
+ }
382
+ }
383
+ pushSymbol(symbols, includeV2Fields, () => toSourceSymbolAnalysis(sourceFile, formatMethodSymbolName(className, accessor.getName()), "accessor", accessor.getNameNode() ?? accessor, accessor, `${classLeading}\n${fullTextWithJsDocs(accessor)}`, includeV2Fields, className));
199
384
  }
200
385
  }
201
- function pushSymbol(symbols, build) {
386
+ function pushSymbol(symbols, strict, build) {
202
387
  try {
203
388
  symbols.push(build());
204
389
  }
205
- catch {
206
- // Skip malformed declarations; keep sibling symbols.
390
+ catch (error) {
391
+ if (strict)
392
+ throw error;
393
+ // Preserve legacy v1 recovery; v2 must expose extraction failure.
207
394
  }
208
395
  }
209
- function toSourceSymbolAnalysis(sourceFile, name, kind, startNode, endNode, directiveText) {
396
+ function toSourceSymbolAnalysis(sourceFile, name, kind, startNode, endNode, directiveText, includeV2Fields = false, containerName) {
210
397
  const start = sourceFile.getLineAndColumnAtPos(startNode.getStart());
211
398
  const end = sourceFile.getLineAndColumnAtPos(endNode.getEnd());
212
- return {
399
+ const result = {
213
400
  name,
214
401
  kind,
215
402
  startLine: start.line,
@@ -218,6 +405,35 @@ function toSourceSymbolAnalysis(sourceFile, name, kind, startNode, endNode, dire
218
405
  endColumn: Math.max(0, end.column - 1),
219
406
  directiveText,
220
407
  };
408
+ if (!includeV2Fields)
409
+ return result;
410
+ const nameKind = startNode.getKindName();
411
+ const hasNameNode = [
412
+ "Identifier",
413
+ "PrivateIdentifier",
414
+ "StringLiteral",
415
+ "NumericLiteral",
416
+ "NoSubstitutionTemplateLiteral",
417
+ "ComputedPropertyName",
418
+ ].includes(nameKind);
419
+ const nameRangeStart = sourceFile.getLineAndColumnAtPos(startNode.getStart());
420
+ const nameRangeEnd = sourceFile.getLineAndColumnAtPos(startNode.getEnd());
421
+ return {
422
+ ...result,
423
+ qualifiedName: name,
424
+ ...(containerName ? { containerName } : {}),
425
+ nativeKind: endNode.getKindName(),
426
+ ...(hasNameNode
427
+ ? {
428
+ nameRange: {
429
+ startLine: nameRangeStart.line,
430
+ startColumn: Math.max(0, nameRangeStart.column - 1),
431
+ endLine: nameRangeEnd.line,
432
+ endColumn: Math.max(0, nameRangeEnd.column - 1),
433
+ },
434
+ }
435
+ : {}),
436
+ };
221
437
  }
222
438
  function inferSourceLanguage(filePath) {
223
439
  const extension = path.extname(filePath).toLowerCase();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kibi-plugin-builtin",
3
- "version": "0.3.0",
3
+ "version": "0.4.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.3.0",
35
+ "kibi-plugin-sdk": "^0.4.0",
36
36
  "ts-morph": "^23.0.0"
37
37
  },
38
38
  "devDependencies": {