kibi-cli 0.12.3 → 0.12.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +12 -16
- package/dist/extractors/symbols-coordinator.d.ts +1 -1
- package/dist/extractors/symbols-coordinator.d.ts.map +1 -1
- package/dist/extractors/symbols-ts.js +36 -2
- package/dist/public/schemas/entity.d.ts.map +1 -1
- package/dist/public/schemas/entity.js +6 -0
- package/dist/public/symbol-granularity.d.ts +23 -0
- package/dist/public/symbol-granularity.d.ts.map +1 -0
- package/dist/public/symbol-granularity.js +68 -0
- package/dist/schemas/entity.schema.json +11 -0
- package/dist/traceability/symbol-extract.d.ts +3 -1
- package/dist/traceability/symbol-extract.d.ts.map +1 -1
- package/dist/traceability/symbol-extract.js +27 -4
- package/package.json +6 -2
- package/src/public/schemas/entity.ts +6 -0
- package/src/public/symbol-granularity.ts +125 -0
- package/src/schemas/entity.schema.json +11 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/commands/check.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/commands/check.ts"],"names":[],"mappings":"AAiFA,OAAO,EAGL,KAAK,SAAS,EAEf,MAAM,2BAA2B,CAAC;AAEnC,YAAY,EAAE,SAAS,EAAE,CAAC;AAI1B,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AA2bD,wBAAsB,YAAY,CAChC,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CA0V/B"}
|
package/dist/commands/check.js
CHANGED
|
@@ -22,6 +22,7 @@ import { extractFromManifest, extractFromManifestString, } from "../extractors/m
|
|
|
22
22
|
import { extractFromMarkdownString, } from "../extractors/markdown.js";
|
|
23
23
|
import { PrologProcess } from "../prolog.js";
|
|
24
24
|
import { escapeAtom, parseTriples, parseViolationRows, } from "../prolog/codec.js";
|
|
25
|
+
import { getBehavioralSymbolNames, getNonBehavioralSymbolNames, isAllowedGranularityReason, isTraceabilityRelationshipType, } from "../public/symbol-granularity.js";
|
|
25
26
|
import { KIBI_NO_IMPACT_DECLARATION, KIBI_SYMBOLS_MANIFEST_PATH, } from "../traceability/evidence-model.js";
|
|
26
27
|
import { getStagedFiles } from "../traceability/git-staged.js";
|
|
27
28
|
import { validateStagedMarkdown } from "../traceability/markdown-validate.js";
|
|
@@ -133,23 +134,11 @@ function buildManifestLookup(stagedFiles) {
|
|
|
133
134
|
stagedAuthoredSymbolResults,
|
|
134
135
|
};
|
|
135
136
|
}
|
|
136
|
-
const GRANULARITY_RELATIONSHIP_TYPES = new Set([
|
|
137
|
-
"implements",
|
|
138
|
-
"covered_by",
|
|
139
|
-
"executable_for",
|
|
140
|
-
]);
|
|
141
|
-
const ALLOWED_GRANULARITY_REASONS = new Set([
|
|
142
|
-
"config-artifact",
|
|
143
|
-
"module-level-behavior",
|
|
144
|
-
"extractor-miss",
|
|
145
|
-
"legacy-link",
|
|
146
|
-
]);
|
|
147
137
|
function hasTraceabilityRelationship(result) {
|
|
148
|
-
return result.relationships.some((relationship) =>
|
|
138
|
+
return result.relationships.some((relationship) => isTraceabilityRelationshipType(relationship.type));
|
|
149
139
|
}
|
|
150
140
|
function hasValidGranularityReason(result) {
|
|
151
|
-
|
|
152
|
-
return typeof reason === "string" && ALLOWED_GRANULARITY_REASONS.has(reason);
|
|
141
|
+
return isAllowedGranularityReason(result.entity.granularity_reason);
|
|
153
142
|
}
|
|
154
143
|
function createSymbolGranularityDiagnostics(options) {
|
|
155
144
|
const diagnostics = [];
|
|
@@ -168,13 +157,20 @@ function createSymbolGranularityDiagnostics(options) {
|
|
|
168
157
|
].sort();
|
|
169
158
|
if (granularNames.includes(result.entity.title))
|
|
170
159
|
continue;
|
|
160
|
+
const behavioralNames = getBehavioralSymbolNames(granularSymbols);
|
|
161
|
+
if (behavioralNames.length === 0)
|
|
162
|
+
continue;
|
|
163
|
+
const nonBehavioralNames = getNonBehavioralSymbolNames(granularSymbols);
|
|
164
|
+
const ignoredSymbolsSuggestion = nonBehavioralNames.length > 0
|
|
165
|
+
? ` Non-behavioral symbols ignored for this decision: ${nonBehavioralNames.join(", ")}.`
|
|
166
|
+
: "";
|
|
171
167
|
diagnostics.push({
|
|
172
168
|
id: "symbol_granularity_violation",
|
|
173
169
|
severity: "error",
|
|
174
170
|
files: [result.entity.source, result.sourceFile],
|
|
175
171
|
docs: ["docs/symbol-traceability-taxonomy.md"],
|
|
176
|
-
message: `Symbol ${result.entity.id} links ${result.sourceFile} coarsely while granular symbols are available: ${
|
|
177
|
-
suggestion:
|
|
172
|
+
message: `Symbol ${result.entity.id} links ${result.sourceFile} coarsely while granular symbols are available (behavioral only): ${behavioralNames.join(", ")}`,
|
|
173
|
+
suggestion: `Move ownership/coverage/test relationships to the narrow behavioral symbol, add a manifest behavioral anchor, or add granularity_reason with config-artifact, module-level-behavior, extractor-miss, or legacy-link when the coarse symbol is intentional.${ignoredSymbolsSuggestion}`,
|
|
178
174
|
});
|
|
179
175
|
}
|
|
180
176
|
return diagnostics;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type ManifestSymbolEntry, enrichSymbolCoordinatesWithTsMorph } from "./symbols-ts.js";
|
|
2
2
|
export type { ManifestSymbolEntry };
|
|
3
3
|
export type SourceAnalysisMode = "parser" | "fallback";
|
|
4
|
-
export type SourceSymbolKind = "function" | "class" | "interface" | "type" | "enum" | "variable" | "unknown";
|
|
4
|
+
export type SourceSymbolKind = "function" | "class" | "method" | "interface" | "type" | "enum" | "variable" | "unknown";
|
|
5
5
|
export interface SourceSymbolAnalysis {
|
|
6
6
|
name: string;
|
|
7
7
|
kind: SourceSymbolKind;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"symbols-coordinator.d.ts","sourceRoot":"","sources":["../../src/extractors/symbols-coordinator.ts"],"names":[],"mappings":"AAoBA,OAAO,EACL,KAAK,mBAAmB,EAExB,kCAAkC,EACnC,MAAM,iBAAiB,CAAC;AAazB,YAAY,EAAE,mBAAmB,EAAE,CAAC;AAEpC,MAAM,MAAM,kBAAkB,GAAG,QAAQ,GAAG,UAAU,CAAC;AAEvD,MAAM,MAAM,gBAAgB,GACxB,UAAU,GACV,OAAO,GACP,WAAW,GACX,MAAM,GACN,MAAM,GACN,UAAU,GACV,SAAS,CAAC;AAEd,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,gBAAgB,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,kBAAkB,CAAC;IACjC,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,MAAM,EAAE,oBAAoB,CAAC;IAC7B,OAAO,EAAE,oBAAoB,EAAE,CAAC;CACjC;AAED,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IACxC,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,oBAAoB,CAAC;CACtE;AAED,MAAM,WAAW,wBAAwB;IACvC,SAAS,CAAC,EAAE,sBAAsB,EAAE,CAAC;CACtC;AAED,UAAU,2BAA2B;IACnC,mBAAmB,EAAE,OAAO,kCAAkC,CAAC;CAChE;AA+BD,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,mBAAmB,EAAE,EAC9B,aAAa,EAAE,MAAM,EACrB,IAAI,CAAC,EAAE,OAAO,CAAC,2BAA2B,CAAC,GAC1C,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAAC;AAClC,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,wBAAwB,GACjC,oBAAoB,CAAC;AAkCxB,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,mBAAmB,EAAE,EAC9B,aAAa,EAAE,MAAM,EACrB,IAAI,CAAC,EAAE,OAAO,CAAC,2BAA2B,CAAC,GAC1C,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAqChC"}
|
|
1
|
+
{"version":3,"file":"symbols-coordinator.d.ts","sourceRoot":"","sources":["../../src/extractors/symbols-coordinator.ts"],"names":[],"mappings":"AAoBA,OAAO,EACL,KAAK,mBAAmB,EAExB,kCAAkC,EACnC,MAAM,iBAAiB,CAAC;AAazB,YAAY,EAAE,mBAAmB,EAAE,CAAC;AAEpC,MAAM,MAAM,kBAAkB,GAAG,QAAQ,GAAG,UAAU,CAAC;AAEvD,MAAM,MAAM,gBAAgB,GACxB,UAAU,GACV,OAAO,GACP,QAAQ,GACR,WAAW,GACX,MAAM,GACN,MAAM,GACN,UAAU,GACV,SAAS,CAAC;AAEd,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,gBAAgB,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,kBAAkB,CAAC;IACjC,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,MAAM,EAAE,oBAAoB,CAAC;IAC7B,OAAO,EAAE,oBAAoB,EAAE,CAAC;CACjC;AAED,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IACxC,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,oBAAoB,CAAC;CACtE;AAED,MAAM,WAAW,wBAAwB;IACvC,SAAS,CAAC,EAAE,sBAAsB,EAAE,CAAC;CACtC;AAED,UAAU,2BAA2B;IACnC,mBAAmB,EAAE,OAAO,kCAAkC,CAAC;CAChE;AA+BD,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,mBAAmB,EAAE,EAC9B,aAAa,EAAE,MAAM,EACrB,IAAI,CAAC,EAAE,OAAO,CAAC,2BAA2B,CAAC,GAC1C,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAAC;AAClC,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,wBAAwB,GACjC,oBAAoB,CAAC;AAkCxB,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,mBAAmB,EAAE,EAC9B,aAAa,EAAE,MAAM,EACrB,IAAI,CAAC,EAAE,OAAO,CAAC,2BAA2B,CAAC,GAC1C,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAqChC"}
|
|
@@ -184,10 +184,16 @@ function collectSourceSymbols(sourceFile) {
|
|
|
184
184
|
for (const decl of sourceFile.getClasses()) {
|
|
185
185
|
if (!decl.isExported())
|
|
186
186
|
continue;
|
|
187
|
-
symbols.push(toSourceSymbolAnalysis(sourceFile, decl.getName() ?? "<anonymous>", "class", decl.getNameNode() ?? decl, decl,
|
|
187
|
+
symbols.push(toSourceSymbolAnalysis(sourceFile, decl.getName() ?? "<anonymous>", "class", decl.getNameNode() ?? decl, decl, decl
|
|
188
188
|
.getJsDocs()
|
|
189
189
|
.map((doc) => doc.getFullText())
|
|
190
|
-
.join("\n")
|
|
190
|
+
.join("\n")));
|
|
191
|
+
for (const method of decl.getMethods()) {
|
|
192
|
+
symbols.push(toSourceSymbolAnalysis(sourceFile, formatMethodSymbolName(decl.getName(), method.getName()), "method", method.getNameNode() ?? method, method, `${method.getFullText()}\n${method
|
|
193
|
+
.getJsDocs()
|
|
194
|
+
.map((doc) => doc.getFullText())
|
|
195
|
+
.join("\n")}`));
|
|
196
|
+
}
|
|
191
197
|
}
|
|
192
198
|
for (const decl of sourceFile.getInterfaces()) {
|
|
193
199
|
if (!decl.isExported())
|
|
@@ -226,6 +232,9 @@ function toSourceSymbolAnalysis(sourceFile, name, kind, startNode, endNode, dire
|
|
|
226
232
|
directiveText,
|
|
227
233
|
};
|
|
228
234
|
}
|
|
235
|
+
function formatMethodSymbolName(className, methodName) {
|
|
236
|
+
return className ? `${className}.${methodName}` : methodName;
|
|
237
|
+
}
|
|
229
238
|
function chooseScriptKind(filePath) {
|
|
230
239
|
const lower = filePath.toLowerCase();
|
|
231
240
|
if (lower.endsWith(".tsx"))
|
|
@@ -252,6 +261,22 @@ function inferModuleTitle(filePath) {
|
|
|
252
261
|
return basename.length > 0 ? basename : path.basename(filePath);
|
|
253
262
|
}
|
|
254
263
|
function findNamedDeclaration(sourceFile, title) {
|
|
264
|
+
const qualifiedMethod = parseQualifiedMethodTitle(title);
|
|
265
|
+
if (qualifiedMethod) {
|
|
266
|
+
for (const cls of sourceFile.getClasses()) {
|
|
267
|
+
if (cls.getName() !== qualifiedMethod.className)
|
|
268
|
+
continue;
|
|
269
|
+
for (const method of cls.getMethods()) {
|
|
270
|
+
if (method.getName() !== qualifiedMethod.methodName)
|
|
271
|
+
continue;
|
|
272
|
+
const nameNode = method.getNameNode();
|
|
273
|
+
if (!nameNode)
|
|
274
|
+
continue;
|
|
275
|
+
return { node: method, getNameNode: () => nameNode };
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
255
280
|
const candidates = [];
|
|
256
281
|
for (const decl of sourceFile.getFunctions()) {
|
|
257
282
|
if (!decl.isExported())
|
|
@@ -357,3 +382,12 @@ function findNamedDeclaration(sourceFile, title) {
|
|
|
357
382
|
candidates.sort((a, b) => a.getNameNode().getStart() - b.getNameNode().getStart());
|
|
358
383
|
return candidates[0] ?? null;
|
|
359
384
|
}
|
|
385
|
+
function parseQualifiedMethodTitle(title) {
|
|
386
|
+
const separatorIndex = title.lastIndexOf(".");
|
|
387
|
+
if (separatorIndex <= 0 || separatorIndex === title.length - 1)
|
|
388
|
+
return null;
|
|
389
|
+
return {
|
|
390
|
+
className: title.slice(0, separatorIndex),
|
|
391
|
+
methodName: title.slice(separatorIndex + 1),
|
|
392
|
+
};
|
|
393
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entity.d.ts","sourceRoot":"","sources":["../../../src/public/schemas/entity.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"entity.d.ts","sourceRoot":"","sources":["../../../src/public/schemas/entity.ts"],"names":[],"mappings":"AAiFA,QAAA,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CA+JzC,CAAC;AAEF,eAAe,YAAY,CAAC"}
|
|
@@ -15,6 +15,8 @@
|
|
|
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
|
+
// Typed fact field enums per proposal
|
|
19
|
+
import { SYMBOL_ROLES } from "../symbol-granularity.js";
|
|
18
20
|
const factConditionals = JSON.parse(`[
|
|
19
21
|
{
|
|
20
22
|
"if": {
|
|
@@ -113,6 +115,10 @@ const entitySchema = {
|
|
|
113
115
|
"legacy-link",
|
|
114
116
|
],
|
|
115
117
|
},
|
|
118
|
+
symbol_role: {
|
|
119
|
+
type: "string",
|
|
120
|
+
enum: [...SYMBOL_ROLES],
|
|
121
|
+
},
|
|
116
122
|
type: {
|
|
117
123
|
type: "string",
|
|
118
124
|
enum: [
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export declare const SYMBOL_ROLES: readonly ["behavioral", "structural", "type-shape", "config", "module", "unknown"];
|
|
2
|
+
export type SymbolRole = (typeof SYMBOL_ROLES)[number];
|
|
3
|
+
export declare const TRACEABILITY_RELATIONSHIP_TYPES: readonly ["implements", "covered_by", "executable_for"];
|
|
4
|
+
export type TraceabilityRelationshipType = (typeof TRACEABILITY_RELATIONSHIP_TYPES)[number];
|
|
5
|
+
export declare const ALLOWED_GRANULARITY_REASONS: readonly ["config-artifact", "module-level-behavior", "extractor-miss", "legacy-link"];
|
|
6
|
+
export type GranularityReason = (typeof ALLOWED_GRANULARITY_REASONS)[number];
|
|
7
|
+
export type SymbolKind = "function" | "class" | "method" | "interface" | "type" | "variable" | "enum" | "unknown";
|
|
8
|
+
export type SourceSymbolKind = SymbolKind;
|
|
9
|
+
export interface GranularSymbolCandidate {
|
|
10
|
+
name: string;
|
|
11
|
+
kind?: SymbolKind;
|
|
12
|
+
role?: SymbolRole;
|
|
13
|
+
}
|
|
14
|
+
export declare function inferSymbolRole(kind: SymbolKind): SymbolRole;
|
|
15
|
+
export declare const inferSymbolRoleFromKind: typeof inferSymbolRole;
|
|
16
|
+
export declare function isSymbolRole(value: unknown): value is SymbolRole;
|
|
17
|
+
export declare function isTraceabilityRelationshipType(value: unknown): value is TraceabilityRelationshipType;
|
|
18
|
+
export declare function isAllowedGranularityReason(value: unknown): value is GranularityReason;
|
|
19
|
+
export declare function getSymbolRole(candidate: GranularSymbolCandidate): SymbolRole;
|
|
20
|
+
export declare function isBehavioralSymbol(candidate: GranularSymbolCandidate): boolean;
|
|
21
|
+
export declare function getBehavioralSymbolNames(candidates: GranularSymbolCandidate[]): string[];
|
|
22
|
+
export declare function getNonBehavioralSymbolNames(candidates: GranularSymbolCandidate[]): string[];
|
|
23
|
+
//# sourceMappingURL=symbol-granularity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"symbol-granularity.d.ts","sourceRoot":"","sources":["../../src/public/symbol-granularity.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,YAAY,oFAOf,CAAC;AAEX,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvD,eAAO,MAAM,+BAA+B,yDAIlC,CAAC;AAEX,MAAM,MAAM,4BAA4B,GACtC,CAAC,OAAO,+BAA+B,CAAC,CAAC,MAAM,CAAC,CAAC;AAEnD,eAAO,MAAM,2BAA2B,wFAK9B,CAAC;AAEX,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,2BAA2B,CAAC,CAAC,MAAM,CAAC,CAAC;AAE7E,MAAM,MAAM,UAAU,GAClB,UAAU,GACV,OAAO,GACP,QAAQ,GACR,WAAW,GACX,MAAM,GACN,UAAU,GACV,MAAM,GACN,SAAS,CAAC;AAEd,MAAM,MAAM,gBAAgB,GAAG,UAAU,CAAC;AAE1C,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,IAAI,CAAC,EAAE,UAAU,CAAC;CACnB;AASD,wBAAgB,eAAe,CAAC,IAAI,EAAE,UAAU,GAAG,UAAU,CAc5D;AAED,eAAO,MAAM,uBAAuB,wBAAkB,CAAC;AAEvD,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,UAAU,CAIhE;AAED,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,4BAA4B,CAIvC;AAED,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,iBAAiB,CAE5B;AAED,wBAAgB,aAAa,CAAC,SAAS,EAAE,uBAAuB,GAAG,UAAU,CAI5E;AAED,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,uBAAuB,GACjC,OAAO,CAET;AAED,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,uBAAuB,EAAE,GACpC,MAAM,EAAE,CAMV;AAED,wBAAgB,2BAA2B,CACzC,UAAU,EAAE,uBAAuB,EAAE,GACpC,MAAM,EAAE,CAQV"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export const SYMBOL_ROLES = [
|
|
2
|
+
"behavioral",
|
|
3
|
+
"structural",
|
|
4
|
+
"type-shape",
|
|
5
|
+
"config",
|
|
6
|
+
"module",
|
|
7
|
+
"unknown",
|
|
8
|
+
];
|
|
9
|
+
export const TRACEABILITY_RELATIONSHIP_TYPES = [
|
|
10
|
+
"implements",
|
|
11
|
+
"covered_by",
|
|
12
|
+
"executable_for",
|
|
13
|
+
];
|
|
14
|
+
export const ALLOWED_GRANULARITY_REASONS = [
|
|
15
|
+
"config-artifact",
|
|
16
|
+
"module-level-behavior",
|
|
17
|
+
"extractor-miss",
|
|
18
|
+
"legacy-link",
|
|
19
|
+
];
|
|
20
|
+
const traceabilityRelationshipTypeSet = new Set(TRACEABILITY_RELATIONSHIP_TYPES);
|
|
21
|
+
const allowedGranularityReasonSet = new Set(ALLOWED_GRANULARITY_REASONS);
|
|
22
|
+
export function inferSymbolRole(kind) {
|
|
23
|
+
switch (kind) {
|
|
24
|
+
case "function":
|
|
25
|
+
case "class":
|
|
26
|
+
case "method":
|
|
27
|
+
return "behavioral";
|
|
28
|
+
case "interface":
|
|
29
|
+
case "type":
|
|
30
|
+
case "enum":
|
|
31
|
+
return "type-shape";
|
|
32
|
+
case "variable":
|
|
33
|
+
case "unknown":
|
|
34
|
+
return "unknown";
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export const inferSymbolRoleFromKind = inferSymbolRole;
|
|
38
|
+
export function isSymbolRole(value) {
|
|
39
|
+
return (typeof value === "string" && SYMBOL_ROLES.some((role) => role === value));
|
|
40
|
+
}
|
|
41
|
+
export function isTraceabilityRelationshipType(value) {
|
|
42
|
+
return (typeof value === "string" && traceabilityRelationshipTypeSet.has(value));
|
|
43
|
+
}
|
|
44
|
+
export function isAllowedGranularityReason(value) {
|
|
45
|
+
return typeof value === "string" && allowedGranularityReasonSet.has(value);
|
|
46
|
+
}
|
|
47
|
+
export function getSymbolRole(candidate) {
|
|
48
|
+
if (candidate.role)
|
|
49
|
+
return candidate.role;
|
|
50
|
+
if (candidate.kind)
|
|
51
|
+
return inferSymbolRole(candidate.kind);
|
|
52
|
+
return "unknown";
|
|
53
|
+
}
|
|
54
|
+
export function isBehavioralSymbol(candidate) {
|
|
55
|
+
return getSymbolRole(candidate) === "behavioral";
|
|
56
|
+
}
|
|
57
|
+
export function getBehavioralSymbolNames(candidates) {
|
|
58
|
+
return [
|
|
59
|
+
...new Set(candidates.filter(isBehavioralSymbol).map((candidate) => candidate.name)),
|
|
60
|
+
].sort();
|
|
61
|
+
}
|
|
62
|
+
export function getNonBehavioralSymbolNames(candidates) {
|
|
63
|
+
return [
|
|
64
|
+
...new Set(candidates
|
|
65
|
+
.filter((candidate) => !isBehavioralSymbol(candidate))
|
|
66
|
+
.map((candidate) => candidate.name)),
|
|
67
|
+
].sort();
|
|
68
|
+
}
|
|
@@ -48,6 +48,17 @@
|
|
|
48
48
|
"legacy-link"
|
|
49
49
|
]
|
|
50
50
|
},
|
|
51
|
+
"symbol_role": {
|
|
52
|
+
"type": "string",
|
|
53
|
+
"enum": [
|
|
54
|
+
"behavioral",
|
|
55
|
+
"structural",
|
|
56
|
+
"type-shape",
|
|
57
|
+
"config",
|
|
58
|
+
"module",
|
|
59
|
+
"unknown"
|
|
60
|
+
]
|
|
61
|
+
},
|
|
51
62
|
"verification_scope": {
|
|
52
63
|
"type": "string",
|
|
53
64
|
"enum": ["unit", "integration", "end_to_end"]
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type SymbolKind, type SymbolRole } from "../public/symbol-granularity.js";
|
|
1
2
|
import type { HunkRange, StagedFile } from "./git-staged.js";
|
|
2
3
|
type TraceabilityRelationship = {
|
|
3
4
|
type: string;
|
|
@@ -6,7 +7,8 @@ type TraceabilityRelationship = {
|
|
|
6
7
|
export interface ExtractedSymbol {
|
|
7
8
|
id: string;
|
|
8
9
|
name: string;
|
|
9
|
-
kind:
|
|
10
|
+
kind: SymbolKind;
|
|
11
|
+
role: SymbolRole;
|
|
10
12
|
location: {
|
|
11
13
|
file: string;
|
|
12
14
|
startLine: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"symbol-extract.d.ts","sourceRoot":"","sources":["../../src/traceability/symbol-extract.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7D,KAAK,wBAAwB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAU7D,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,
|
|
1
|
+
{"version":3,"file":"symbol-extract.d.ts","sourceRoot":"","sources":["../../src/traceability/symbol-extract.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,UAAU,EACf,KAAK,UAAU,EAEhB,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7D,KAAK,wBAAwB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAU7D,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,UAAU,CAAC;IACjB,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,aAAa,CAAC,EAAE,wBAAwB,EAAE,CAAC;CAC5C;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,CAAC,EAAE,wBAAwB,EAAE,CAAC;CAC5C;AAED,MAAM,MAAM,cAAc,GAAG,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;AAE9D,wBAAgB,+BAA+B,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAG5E;AAmOD,wBAAgB,4BAA4B,CAE1C,UAAU,EAAE,UAAU,EACtB,cAAc,CAAC,EAAE,cAAc,GAC9B,eAAe,EAAE,CAyOnB"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { Project, ScriptKind } from "ts-morph";
|
|
3
3
|
import { readManifestWithCoordinateOverlay } from "../extractors/manifest.js";
|
|
4
|
+
import { inferSymbolRole, } from "../public/symbol-granularity.js";
|
|
4
5
|
const TRACEABILITY_RELATIONSHIP_TYPES = new Set([
|
|
5
6
|
"implements",
|
|
6
7
|
"covered_by",
|
|
@@ -109,6 +110,7 @@ function buildSymbolResult(stagedFile, name, kind, span, inlineReqLinks, manifes
|
|
|
109
110
|
id,
|
|
110
111
|
name,
|
|
111
112
|
kind,
|
|
113
|
+
role: inferSymbolRole(kind),
|
|
112
114
|
location: {
|
|
113
115
|
file: stagedFile.path,
|
|
114
116
|
startLine: span.startLine,
|
|
@@ -229,15 +231,30 @@ stagedFile, manifestLookup) {
|
|
|
229
231
|
const start = cls.getNameNode()?.getStart() ?? cls.getStart();
|
|
230
232
|
const end = cls.getEnd();
|
|
231
233
|
const span = getSpan(start, end);
|
|
232
|
-
const reqLinks = parseReqDirectives(
|
|
233
|
-
.getJsDocs()
|
|
234
|
-
.map((d) => d.getFullText())
|
|
235
|
-
.join("\n")}`);
|
|
234
|
+
const reqLinks = parseReqDirectives(getJsDocText(cls.getJsDocs()));
|
|
236
235
|
results.push(buildSymbolResult(stagedFile, name, "class", span, reqLinks, manifestLookup));
|
|
237
236
|
}
|
|
238
237
|
catch {
|
|
239
238
|
void stagedFile.path;
|
|
240
239
|
}
|
|
240
|
+
for (const method of typeof cls.getMethods === "function"
|
|
241
|
+
? cls.getMethods()
|
|
242
|
+
: []) {
|
|
243
|
+
try {
|
|
244
|
+
const name = formatMethodSymbolName(cls.getName(), method.getName());
|
|
245
|
+
const start = method.getNameNode()?.getStart() ?? method.getStart();
|
|
246
|
+
const end = method.getEnd();
|
|
247
|
+
const span = getSpan(start, end);
|
|
248
|
+
const reqLinks = parseReqDirectives(`${method.getFullText()}\n${method
|
|
249
|
+
.getJsDocs()
|
|
250
|
+
.map((d) => d.getFullText())
|
|
251
|
+
.join("\n")}`);
|
|
252
|
+
results.push(buildSymbolResult(stagedFile, name, "method", span, reqLinks, manifestLookup));
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
void stagedFile.path;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
241
258
|
}
|
|
242
259
|
// Enums
|
|
243
260
|
for (const en of sf.getEnums()) {
|
|
@@ -315,6 +332,12 @@ stagedFile, manifestLookup) {
|
|
|
315
332
|
}
|
|
316
333
|
return results;
|
|
317
334
|
}
|
|
335
|
+
function formatMethodSymbolName(className, methodName) {
|
|
336
|
+
return className ? `${className}.${methodName}` : methodName;
|
|
337
|
+
}
|
|
338
|
+
function getJsDocText(jsDocs) {
|
|
339
|
+
return jsDocs.map((d) => d.getFullText()).join("\n");
|
|
340
|
+
}
|
|
318
341
|
function intersectingHunks(startLine, endLine, hunks) {
|
|
319
342
|
const out = [];
|
|
320
343
|
for (const h of hunks) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kibi-cli",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Kibi CLI for knowledge base management",
|
|
6
6
|
"engines": {
|
|
@@ -69,6 +69,10 @@
|
|
|
69
69
|
"types": "./dist/public/check-types.d.ts",
|
|
70
70
|
"default": "./dist/public/check-types.js"
|
|
71
71
|
},
|
|
72
|
+
"./public/symbol-granularity": {
|
|
73
|
+
"types": "./dist/public/symbol-granularity.d.ts",
|
|
74
|
+
"default": "./dist/public/symbol-granularity.js"
|
|
75
|
+
},
|
|
72
76
|
"./schema-version": {
|
|
73
77
|
"types": "./dist/public/schema-version.d.ts",
|
|
74
78
|
"default": "./dist/public/schema-version.js"
|
|
@@ -95,7 +99,7 @@
|
|
|
95
99
|
"fast-glob": "^3.2.12",
|
|
96
100
|
"gray-matter": "^4.0.3",
|
|
97
101
|
"js-yaml": "^4.1.0",
|
|
98
|
-
"kibi-core": "^0.6.
|
|
102
|
+
"kibi-core": "^0.6.1",
|
|
99
103
|
"ts-morph": "^23.0.0",
|
|
100
104
|
"ignore": "^5.3.0"
|
|
101
105
|
},
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
// Typed fact field enums per proposal
|
|
20
|
+
import { SYMBOL_ROLES, type SymbolRole } from "../symbol-granularity.js";
|
|
21
|
+
|
|
20
22
|
type FactKind =
|
|
21
23
|
| "subject"
|
|
22
24
|
| "property_value"
|
|
@@ -127,6 +129,10 @@ const entitySchema: Record<string, unknown> = {
|
|
|
127
129
|
"legacy-link",
|
|
128
130
|
],
|
|
129
131
|
},
|
|
132
|
+
symbol_role: {
|
|
133
|
+
type: "string",
|
|
134
|
+
enum: [...SYMBOL_ROLES] satisfies SymbolRole[],
|
|
135
|
+
},
|
|
130
136
|
type: {
|
|
131
137
|
type: "string",
|
|
132
138
|
enum: [
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
export const SYMBOL_ROLES = [
|
|
2
|
+
"behavioral",
|
|
3
|
+
"structural",
|
|
4
|
+
"type-shape",
|
|
5
|
+
"config",
|
|
6
|
+
"module",
|
|
7
|
+
"unknown",
|
|
8
|
+
] as const;
|
|
9
|
+
|
|
10
|
+
export type SymbolRole = (typeof SYMBOL_ROLES)[number];
|
|
11
|
+
|
|
12
|
+
export const TRACEABILITY_RELATIONSHIP_TYPES = [
|
|
13
|
+
"implements",
|
|
14
|
+
"covered_by",
|
|
15
|
+
"executable_for",
|
|
16
|
+
] as const;
|
|
17
|
+
|
|
18
|
+
export type TraceabilityRelationshipType =
|
|
19
|
+
(typeof TRACEABILITY_RELATIONSHIP_TYPES)[number];
|
|
20
|
+
|
|
21
|
+
export const ALLOWED_GRANULARITY_REASONS = [
|
|
22
|
+
"config-artifact",
|
|
23
|
+
"module-level-behavior",
|
|
24
|
+
"extractor-miss",
|
|
25
|
+
"legacy-link",
|
|
26
|
+
] as const;
|
|
27
|
+
|
|
28
|
+
export type GranularityReason = (typeof ALLOWED_GRANULARITY_REASONS)[number];
|
|
29
|
+
|
|
30
|
+
export type SymbolKind =
|
|
31
|
+
| "function"
|
|
32
|
+
| "class"
|
|
33
|
+
| "method"
|
|
34
|
+
| "interface"
|
|
35
|
+
| "type"
|
|
36
|
+
| "variable"
|
|
37
|
+
| "enum"
|
|
38
|
+
| "unknown";
|
|
39
|
+
|
|
40
|
+
export type SourceSymbolKind = SymbolKind;
|
|
41
|
+
|
|
42
|
+
export interface GranularSymbolCandidate {
|
|
43
|
+
name: string;
|
|
44
|
+
kind?: SymbolKind;
|
|
45
|
+
role?: SymbolRole;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const traceabilityRelationshipTypeSet: ReadonlySet<string> = new Set(
|
|
49
|
+
TRACEABILITY_RELATIONSHIP_TYPES,
|
|
50
|
+
);
|
|
51
|
+
const allowedGranularityReasonSet: ReadonlySet<string> = new Set(
|
|
52
|
+
ALLOWED_GRANULARITY_REASONS,
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
export function inferSymbolRole(kind: SymbolKind): SymbolRole {
|
|
56
|
+
switch (kind) {
|
|
57
|
+
case "function":
|
|
58
|
+
case "class":
|
|
59
|
+
case "method":
|
|
60
|
+
return "behavioral";
|
|
61
|
+
case "interface":
|
|
62
|
+
case "type":
|
|
63
|
+
case "enum":
|
|
64
|
+
return "type-shape";
|
|
65
|
+
case "variable":
|
|
66
|
+
case "unknown":
|
|
67
|
+
return "unknown";
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export const inferSymbolRoleFromKind = inferSymbolRole;
|
|
72
|
+
|
|
73
|
+
export function isSymbolRole(value: unknown): value is SymbolRole {
|
|
74
|
+
return (
|
|
75
|
+
typeof value === "string" && SYMBOL_ROLES.some((role) => role === value)
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function isTraceabilityRelationshipType(
|
|
80
|
+
value: unknown,
|
|
81
|
+
): value is TraceabilityRelationshipType {
|
|
82
|
+
return (
|
|
83
|
+
typeof value === "string" && traceabilityRelationshipTypeSet.has(value)
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function isAllowedGranularityReason(
|
|
88
|
+
value: unknown,
|
|
89
|
+
): value is GranularityReason {
|
|
90
|
+
return typeof value === "string" && allowedGranularityReasonSet.has(value);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function getSymbolRole(candidate: GranularSymbolCandidate): SymbolRole {
|
|
94
|
+
if (candidate.role) return candidate.role;
|
|
95
|
+
if (candidate.kind) return inferSymbolRole(candidate.kind);
|
|
96
|
+
return "unknown";
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function isBehavioralSymbol(
|
|
100
|
+
candidate: GranularSymbolCandidate,
|
|
101
|
+
): boolean {
|
|
102
|
+
return getSymbolRole(candidate) === "behavioral";
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function getBehavioralSymbolNames(
|
|
106
|
+
candidates: GranularSymbolCandidate[],
|
|
107
|
+
): string[] {
|
|
108
|
+
return [
|
|
109
|
+
...new Set(
|
|
110
|
+
candidates.filter(isBehavioralSymbol).map((candidate) => candidate.name),
|
|
111
|
+
),
|
|
112
|
+
].sort();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function getNonBehavioralSymbolNames(
|
|
116
|
+
candidates: GranularSymbolCandidate[],
|
|
117
|
+
): string[] {
|
|
118
|
+
return [
|
|
119
|
+
...new Set(
|
|
120
|
+
candidates
|
|
121
|
+
.filter((candidate) => !isBehavioralSymbol(candidate))
|
|
122
|
+
.map((candidate) => candidate.name),
|
|
123
|
+
),
|
|
124
|
+
].sort();
|
|
125
|
+
}
|
|
@@ -48,6 +48,17 @@
|
|
|
48
48
|
"legacy-link"
|
|
49
49
|
]
|
|
50
50
|
},
|
|
51
|
+
"symbol_role": {
|
|
52
|
+
"type": "string",
|
|
53
|
+
"enum": [
|
|
54
|
+
"behavioral",
|
|
55
|
+
"structural",
|
|
56
|
+
"type-shape",
|
|
57
|
+
"config",
|
|
58
|
+
"module",
|
|
59
|
+
"unknown"
|
|
60
|
+
]
|
|
61
|
+
},
|
|
51
62
|
"verification_scope": {
|
|
52
63
|
"type": "string",
|
|
53
64
|
"enum": ["unit", "integration", "end_to_end"]
|