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.
@@ -1 +1 @@
1
- {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/commands/check.ts"],"names":[],"mappings":"AA2EA,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;AAgcD,wBAAsB,YAAY,CAChC,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CA0V/B"}
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"}
@@ -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) => GRANULARITY_RELATIONSHIP_TYPES.has(relationship.type));
138
+ return result.relationships.some((relationship) => isTraceabilityRelationshipType(relationship.type));
149
139
  }
150
140
  function hasValidGranularityReason(result) {
151
- const reason = result.entity.granularity_reason;
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: ${granularNames.join(", ")}`,
177
- suggestion: "Move ownership/coverage/test relationships to the narrow function/class/type symbol, or add granularity_reason with config-artifact, module-level-behavior, extractor-miss, or legacy-link when the coarse symbol is intentional.",
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, `${decl.getText()}\n${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":"AA+EA,QAAA,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CA2JzC,CAAC;AAEF,eAAe,YAAY,CAAC"}
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: "function" | "class" | "interface" | "type" | "variable" | "enum" | "unknown";
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,EACA,UAAU,GACV,OAAO,GACP,WAAW,GACX,MAAM,GACN,UAAU,GACV,MAAM,GACN,SAAS,CAAC;IACd,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;AAkOD,wBAAgB,4BAA4B,CAE1C,UAAU,EAAE,UAAU,EACtB,cAAc,CAAC,EAAE,cAAc,GAC9B,eAAe,EAAE,CAiNnB"}
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(`${cls.getText()}\n${cls
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",
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.0",
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"]