kibi-cli 0.12.7 → 0.14.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.
Files changed (114) hide show
  1. package/dist/cli.js +1 -0
  2. package/dist/commands/check.d.ts +1 -0
  3. package/dist/commands/check.d.ts.map +1 -1
  4. package/dist/commands/check.js +130 -82
  5. package/dist/commands/discovery-shared.d.ts +1 -0
  6. package/dist/commands/discovery-shared.d.ts.map +1 -1
  7. package/dist/commands/discovery-shared.js +2 -191
  8. package/dist/commands/discovery-table.d.ts +3 -0
  9. package/dist/commands/discovery-table.d.ts.map +1 -0
  10. package/dist/commands/discovery-table.js +204 -0
  11. package/dist/commands/sync/discovery.d.ts.map +1 -1
  12. package/dist/commands/sync/discovery.js +8 -1
  13. package/dist/commands/sync/extraction.d.ts +6 -1
  14. package/dist/commands/sync/extraction.d.ts.map +1 -1
  15. package/dist/commands/sync/extraction.js +7 -3
  16. package/dist/commands/sync/persistence.d.ts.map +1 -1
  17. package/dist/commands/sync/persistence.js +39 -5
  18. package/dist/extractors/manifest.d.ts.map +1 -1
  19. package/dist/extractors/manifest.js +18 -0
  20. package/dist/extractors/markdown.d.ts +6 -0
  21. package/dist/extractors/markdown.d.ts.map +1 -1
  22. package/dist/extractors/symbols-coordinator.d.ts +1 -1
  23. package/dist/extractors/symbols-coordinator.d.ts.map +1 -1
  24. package/dist/extractors/symbols-ts.js +62 -0
  25. package/dist/prolog.d.ts.map +1 -1
  26. package/dist/prolog.js +11 -7
  27. package/dist/public/impact/analyzer.d.ts +3 -0
  28. package/dist/public/impact/analyzer.d.ts.map +1 -0
  29. package/dist/public/impact/analyzer.js +54 -0
  30. package/dist/public/impact/coverage-depth-quality.d.ts +4 -0
  31. package/dist/public/impact/coverage-depth-quality.d.ts.map +1 -0
  32. package/dist/public/impact/coverage-depth-quality.js +179 -0
  33. package/dist/public/impact/diagnostics.d.ts +7 -0
  34. package/dist/public/impact/diagnostics.d.ts.map +1 -0
  35. package/dist/public/impact/diagnostics.js +122 -0
  36. package/dist/public/impact/diff-meaning.d.ts +2 -0
  37. package/dist/public/impact/diff-meaning.d.ts.map +1 -0
  38. package/dist/public/impact/diff-meaning.js +101 -0
  39. package/dist/public/impact/full-kb-quality.d.ts +10 -0
  40. package/dist/public/impact/full-kb-quality.d.ts.map +1 -0
  41. package/dist/public/impact/full-kb-quality.js +158 -0
  42. package/dist/public/impact/manifest.d.ts +5 -0
  43. package/dist/public/impact/manifest.d.ts.map +1 -0
  44. package/dist/public/impact/manifest.js +43 -0
  45. package/dist/public/impact/requirement-quality.d.ts +4 -0
  46. package/dist/public/impact/requirement-quality.d.ts.map +1 -0
  47. package/dist/public/impact/requirement-quality.js +216 -0
  48. package/dist/public/impact/source-changes.d.ts +5 -0
  49. package/dist/public/impact/source-changes.d.ts.map +1 -0
  50. package/dist/public/impact/source-changes.js +129 -0
  51. package/dist/public/impact/summaries.d.ts +6 -0
  52. package/dist/public/impact/summaries.d.ts.map +1 -0
  53. package/dist/public/impact/summaries.js +68 -0
  54. package/dist/public/impact/symbol-quality-model.d.ts +21 -0
  55. package/dist/public/impact/symbol-quality-model.d.ts.map +1 -0
  56. package/dist/public/impact/symbol-quality-model.js +116 -0
  57. package/dist/public/impact/symbol-quality.d.ts +4 -0
  58. package/dist/public/impact/symbol-quality.d.ts.map +1 -0
  59. package/dist/public/impact/symbol-quality.js +159 -0
  60. package/dist/public/impact/types.d.ts +79 -0
  61. package/dist/public/impact/types.d.ts.map +1 -0
  62. package/dist/public/impact/types.js +1 -0
  63. package/dist/public/impact-diagnostics.d.ts +7 -0
  64. package/dist/public/impact-diagnostics.d.ts.map +1 -0
  65. package/dist/public/impact-diagnostics.js +5 -0
  66. package/dist/public/schemas/entity.d.ts.map +1 -1
  67. package/dist/public/schemas/entity.js +21 -0
  68. package/dist/public/skills/kibi-usage/SKILL.md +20 -5
  69. package/dist/public/skills/kibi-usage/resources/fact-lanes.md +5 -5
  70. package/dist/public/skills/kibi-usage/resources/relationship-directions.md +30 -0
  71. package/dist/public/skills/kibi-usage/resources/workflows.md +14 -0
  72. package/dist/public/symbol-granularity.d.ts +1 -1
  73. package/dist/public/symbol-granularity.d.ts.map +1 -1
  74. package/dist/public/symbol-granularity.js +2 -0
  75. package/dist/traceability/evidence-model.d.ts +1 -1
  76. package/dist/traceability/evidence-model.d.ts.map +1 -1
  77. package/dist/traceability/evidence-model.js +1 -1
  78. package/dist/traceability/staged-diagnostics.d.ts +6 -6
  79. package/dist/traceability/staged-diagnostics.d.ts.map +1 -1
  80. package/dist/traceability/staged-diagnostics.js +12 -6
  81. package/dist/traceability/staged-impact-contract.js +1 -1
  82. package/dist/traceability/staged-symbols-manifest.d.ts.map +1 -1
  83. package/dist/traceability/staged-symbols-manifest.js +23 -1
  84. package/dist/traceability/symbol-extract.d.ts.map +1 -1
  85. package/dist/traceability/symbol-extract.js +41 -0
  86. package/dist/traceability/temp-kb.d.ts.map +1 -1
  87. package/dist/types/entities.d.ts +5 -1
  88. package/dist/types/entities.d.ts.map +1 -1
  89. package/dist/utils/prolog-query-plan-safety.d.ts +8 -0
  90. package/dist/utils/prolog-query-plan-safety.d.ts.map +1 -0
  91. package/dist/utils/prolog-query-plan-safety.js +47 -0
  92. package/dist/utils/rule-registry.d.ts.map +1 -1
  93. package/dist/utils/rule-registry.js +12 -0
  94. package/package.json +7 -9
  95. package/schema/entities.pl +4 -0
  96. package/src/public/impact/analyzer.ts +90 -0
  97. package/src/public/impact/coverage-depth-quality.ts +268 -0
  98. package/src/public/impact/diagnostics.ts +192 -0
  99. package/src/public/impact/diff-meaning.ts +121 -0
  100. package/src/public/impact/full-kb-quality.ts +203 -0
  101. package/src/public/impact/manifest.ts +57 -0
  102. package/src/public/impact/requirement-quality.ts +282 -0
  103. package/src/public/impact/source-changes.ts +180 -0
  104. package/src/public/impact/summaries.ts +100 -0
  105. package/src/public/impact/symbol-quality-model.ts +171 -0
  106. package/src/public/impact/symbol-quality.ts +207 -0
  107. package/src/public/impact/types.ts +99 -0
  108. package/src/public/impact-diagnostics.ts +22 -0
  109. package/src/public/schemas/entity.ts +23 -0
  110. package/src/public/skills/kibi-usage/SKILL.md +20 -5
  111. package/src/public/skills/kibi-usage/resources/fact-lanes.md +5 -5
  112. package/src/public/skills/kibi-usage/resources/relationship-directions.md +30 -0
  113. package/src/public/skills/kibi-usage/resources/workflows.md +14 -0
  114. package/src/public/symbol-granularity.ts +4 -0
package/dist/cli.js CHANGED
@@ -153,6 +153,7 @@ program
153
153
  .option("--staged", "Run check only against staged changes (experimental)")
154
154
  .option("--min-links <n>", "Minimum number of links required for symbol coverage", "1")
155
155
  .option("--dry-run", "Do not modify files; only print what would happen")
156
+ .option("--format <format>", "Output format: text|json", "text")
156
157
  .action(withExitCode(async (options) => {
157
158
  return checkCommand(options);
158
159
  }));
@@ -7,6 +7,7 @@ export interface CheckOptions {
7
7
  staged?: boolean;
8
8
  minLinks?: string | number;
9
9
  dryRun?: boolean;
10
+ format?: "text" | "json";
10
11
  }
11
12
  export declare function checkCommand(options: CheckOptions): Promise<{
12
13
  exitCode: number;
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/commands/check.ts"],"names":[],"mappings":"AAoFA,OAAO,EAGL,KAAK,SAAS,EAEf,MAAM,2BAA2B,CAAC;AAEnC,YAAY,EAAE,SAAS,EAAE,CAAC;AAgB1B,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;IACjB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC1B;AAkZD,wBAAsB,YAAY,CAChC,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAya/B"}
@@ -20,9 +20,9 @@ import * as path from "node:path";
20
20
  import { getBranchOverride, isCliTraceOrDebugEnabled } from "../env.js";
21
21
  import { extractFromManifest, extractFromManifestString, } from "../extractors/manifest.js";
22
22
  import { extractFromMarkdownString, } from "../extractors/markdown.js";
23
- import { PrologProcess } from "../prolog.js";
23
+ import { PrologProcess, resolveKbPlPath } 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
+ import { collectFullKbQualityDiagnostics, createSemanticReviewDiagnostics, createSymbolGranularityDiagnostics, createSymbolQualityDiagnostics, hasBlockingImpactDiagnostics, } from "../public/impact-diagnostics.js";
26
26
  import { KIBI_NO_IMPACT_DECLARATION, KIBI_SYMBOLS_MANIFEST_PATH, } from "../traceability/evidence-model.js";
27
27
  import { getStagedFiles } from "../traceability/git-staged.js";
28
28
  import { validateStagedMarkdown } from "../traceability/markdown-validate.js";
@@ -34,9 +34,21 @@ import { cleanupTempKb, consultOverlay, createOverlayFacts, createTempKb, projec
34
34
  import { formatViolations as formatStagedViolations, validateStagedSymbols, } from "../traceability/validate.js";
35
35
  import { loadConfig } from "../utils/config.js";
36
36
  import { safeCleanupProlog } from "../utils/prolog-cleanup.js";
37
+ import { analyzePrologQueryPlanSafety } from "../utils/prolog-query-plan-safety.js";
37
38
  import { RULES, getEffectiveRules, } from "../utils/rule-registry.js";
38
39
  import { runAggregatedChecks } from "./aggregated-checks.js";
39
40
  import { getCurrentBranch } from "./init-helpers.js";
41
+ function collectQueryPlanSafetyViolations() {
42
+ const checksPlPath = path.join(path.dirname(resolveKbPlPath()), "checks.pl");
43
+ const source = readFileSync(checksPlPath, "utf8");
44
+ return analyzePrologQueryPlanSafety(source).map((violation) => ({
45
+ rule: "query-plan-safety",
46
+ entityId: violation.predicate,
47
+ description: violation.description,
48
+ suggestion: violation.suggestion,
49
+ source: `${checksPlPath}:${violation.line}`,
50
+ }));
51
+ }
40
52
  function getMatchGroup(match, index = 1) {
41
53
  const value = match?.[index];
42
54
  return typeof value === "string" ? value : null;
@@ -98,6 +110,9 @@ function buildManifestLookup(stagedFiles) {
98
110
  manifestResults.push({
99
111
  entity: entry.entity,
100
112
  relationships: entry.relationships,
113
+ ...(entry.sourceFile !== undefined
114
+ ? { sourceFile: entry.sourceFile }
115
+ : {}),
101
116
  });
102
117
  const authoredSymbolResult = {
103
118
  entity: entry.entity,
@@ -134,70 +149,6 @@ function buildManifestLookup(stagedFiles) {
134
149
  stagedAuthoredSymbolResults,
135
150
  };
136
151
  }
137
- function hasTraceabilityRelationship(result) {
138
- return result.relationships.some((relationship) => isTraceabilityRelationshipType(relationship.type));
139
- }
140
- function hasValidGranularityReason(result) {
141
- return isAllowedGranularityReason(result.entity.granularity_reason);
142
- }
143
- function createSymbolGranularityDiagnostics(options) {
144
- const diagnostics = [];
145
- for (const result of options.manifestResults) {
146
- if (!result.sourceFile)
147
- continue;
148
- if (!hasTraceabilityRelationship(result))
149
- continue;
150
- if (hasValidGranularityReason(result))
151
- continue;
152
- const granularSymbols = getGranularSymbolsForSourceFile(result.sourceFile, options.symbolsByFile, options.sourceContentByFile);
153
- if (granularSymbols.length === 0)
154
- continue;
155
- const granularNames = [
156
- ...new Set(granularSymbols.map((s) => s.name)),
157
- ].sort();
158
- if (granularNames.includes(result.entity.title))
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
- : "";
167
- diagnostics.push({
168
- id: "symbol_granularity_violation",
169
- severity: "error",
170
- files: [result.entity.source, result.sourceFile],
171
- docs: ["docs/symbol-traceability-taxonomy.md"],
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}`,
174
- });
175
- }
176
- return diagnostics;
177
- }
178
- function getGranularSymbolsForSourceFile(sourceFile, symbolsByFile, sourceContentByFile) {
179
- const stagedContent = sourceContentByFile.get(sourceFile);
180
- if (stagedContent !== undefined) {
181
- return extractSymbolsFromStagedFile({
182
- path: sourceFile,
183
- status: "M",
184
- hunkRanges: [{ start: 1, end: Number.MAX_SAFE_INTEGER }],
185
- content: stagedContent,
186
- });
187
- }
188
- const absolutePath = path.isAbsolute(sourceFile)
189
- ? sourceFile
190
- : path.resolve(process.cwd(), sourceFile);
191
- if (!existsSync(absolutePath)) {
192
- return [];
193
- }
194
- return extractSymbolsFromStagedFile({
195
- path: sourceFile,
196
- status: "M",
197
- hunkRanges: [{ start: 1, end: Number.MAX_SAFE_INTEGER }],
198
- content: readFileSync(absolutePath, "utf8"),
199
- });
200
- }
201
152
  const KIBI_ENTITY_TYPES = new Set([
202
153
  "req",
203
154
  "scenario",
@@ -254,7 +205,9 @@ function getStagedDiffText(stagedFile) {
254
205
  function formatStagedKibiDiagnostics(diagnostics) {
255
206
  return diagnostics
256
207
  .map((diagnostic) => {
257
- const lines = [`[${diagnostic.id}] ${diagnostic.message}`];
208
+ const lines = [
209
+ `[${diagnostic.severity.toUpperCase()} ${diagnostic.id}] ${diagnostic.message}`,
210
+ ];
258
211
  if (diagnostic.files.length > 0) {
259
212
  lines.push(` Files: ${diagnostic.files.join(", ")}`);
260
213
  }
@@ -266,11 +219,48 @@ function formatStagedKibiDiagnostics(diagnostics) {
266
219
  })
267
220
  .join("\n\n");
268
221
  }
222
+ function formatQualityDiagnostics(diagnostics) {
223
+ if (diagnostics.length === 0) {
224
+ return "";
225
+ }
226
+ const details = diagnostics.map((diagnostic) => {
227
+ const lines = [
228
+ `[${diagnostic.severity.toUpperCase()} ${diagnostic.id}] ${diagnostic.message}`,
229
+ ];
230
+ if (diagnostic.entityId !== undefined) {
231
+ lines.push(` Entity: ${diagnostic.entityId}`);
232
+ }
233
+ if (diagnostic.files !== undefined && diagnostic.files.length > 0) {
234
+ lines.push(` Files: ${diagnostic.files.join(", ")}`);
235
+ }
236
+ if (diagnostic.docs !== undefined && diagnostic.docs.length > 0) {
237
+ lines.push(` Docs: ${diagnostic.docs.join(", ")}`);
238
+ }
239
+ lines.push(` Blocking: ${diagnostic.blocking ? "yes" : "no"}`);
240
+ lines.push(` Suggestion: ${diagnostic.suggestion}`);
241
+ return lines.join("\n");
242
+ });
243
+ return `Quality diagnostics (${diagnostics.length}):\n${details.join("\n\n")}`;
244
+ }
245
+ function printStructuredCheckResult(input) {
246
+ console.log(JSON.stringify({
247
+ structuredContent: {
248
+ violations: input.violations,
249
+ count: input.violations.length,
250
+ diagnostics: [],
251
+ qualityDiagnostics: input.qualityDiagnostics,
252
+ },
253
+ }));
254
+ }
269
255
  function uniqueSorted(values) {
270
256
  return Array.from(new Set(values)).sort();
271
257
  }
258
+ function hasYamlFrontmatter(content) {
259
+ const trimmed = content.trimStart();
260
+ return trimmed.startsWith("---") && trimmed.slice(3).includes("\n---");
261
+ }
272
262
  function buildStagedKibiImpactEvidence(options) {
273
- const { stagedFiles, sourceFiles, markdownFiles, markdownResults, symbolsByFile, symbolsManifestPath, } = options;
263
+ const { stagedFiles, sourceFiles, markdownFiles, markdownResultsByPath, symbolsByFile, symbolsManifestPath, } = options;
274
264
  const sourceChanges = sourceFiles.map((file) => {
275
265
  const symbolsForFile = symbolsByFile.get(file.path) ?? [];
276
266
  const behaviorCandidate = !isTestOnlySourcePath(file.path) &&
@@ -301,13 +291,6 @@ function buildStagedKibiImpactEvidence(options) {
301
291
  stagedFiles,
302
292
  sourceFiles: behaviorSourceFiles,
303
293
  });
304
- const markdownResultsByPath = new Map();
305
- for (const [index, file] of markdownFiles.entries()) {
306
- const result = markdownResults[index];
307
- if (result) {
308
- markdownResultsByPath.set(file.path, result);
309
- }
310
- }
311
294
  const resolvedKbArtifacts = [];
312
295
  let override = null;
313
296
  for (const file of markdownFiles) {
@@ -439,20 +422,34 @@ export async function checkCommand(options) {
439
422
  console.error(`Error extracting symbols from staged file ${f.path}: ${e instanceof Error ? e.message : String(e)}`);
440
423
  }
441
424
  }
442
- const markdownResults = markdownFiles.map((file) => extractFromMarkdownString(file.content ?? "", file.path));
425
+ const markdownResultsByPath = new Map();
426
+ for (const file of markdownFiles) {
427
+ const content = file.content ?? "";
428
+ if (hasYamlFrontmatter(content)) {
429
+ markdownResultsByPath.set(file.path, extractFromMarkdownString(content, file.path));
430
+ }
431
+ }
432
+ const stagedSourceFilePaths = new Set(sourceFiles.map((file) => file.path));
433
+ const scopedManifestResults = manifestResults.filter((result) => result.sourceFile !== undefined &&
434
+ stagedSourceFilePaths.has(result.sourceFile));
443
435
  const stagedEntityResults = [
444
- ...manifestResults,
445
- ...markdownResults,
436
+ ...scopedManifestResults,
437
+ ...markdownResultsByPath.values(),
446
438
  ];
447
439
  const stagedKibiEvidence = buildStagedKibiImpactEvidence({
448
440
  stagedFiles,
449
441
  sourceFiles,
450
442
  markdownFiles,
451
- markdownResults,
443
+ markdownResultsByPath,
452
444
  symbolsByFile,
453
445
  symbolsManifestPath,
454
446
  });
455
447
  const stagedKibiDiagnostics = collectStagedKibiDiagnostics(stagedKibiEvidence);
448
+ const activeStagedSymbolEntityIds = new Set(stagedKibiEvidence.mode.kind === "kb_changes"
449
+ ? stagedKibiEvidence.mode.kbArtifacts
450
+ .filter((artifact) => artifact.kind === "symbols_manifest")
451
+ .flatMap((artifact) => artifact.entityIds)
452
+ : []);
456
453
  const stagedAuthoredSymbolSet = new Set(stagedAuthoredSymbolResults);
457
454
  const stagedSourcePaths = new Set(sourceFiles.map((file) => file.path));
458
455
  const activeGranularityResults = authoredSymbolResults.filter((result) => stagedAuthoredSymbolSet.has(result) ||
@@ -460,10 +457,30 @@ export async function checkCommand(options) {
460
457
  stagedSourcePaths.has(result.sourceFile)));
461
458
  stagedKibiDiagnostics.push(...createSymbolGranularityDiagnostics({
462
459
  manifestResults: activeGranularityResults,
460
+ ...(activeStagedSymbolEntityIds.size > 0
461
+ ? { activeEntityIds: activeStagedSymbolEntityIds }
462
+ : {}),
463
463
  symbolsByFile,
464
464
  sourceContentByFile,
465
- }));
465
+ }), ...createSymbolQualityDiagnostics({
466
+ manifestResults: activeGranularityResults,
467
+ ...(activeStagedSymbolEntityIds.size > 0
468
+ ? { activeEntityIds: activeStagedSymbolEntityIds }
469
+ : {}),
470
+ symbolsByFile,
471
+ }), ...createSemanticReviewDiagnostics({ symbolsByFile }));
466
472
  if (allSymbols.length === 0 && stagedEntityResults.length === 0) {
473
+ if (stagedKibiDiagnostics.length > 0) {
474
+ console.log(formatStagedKibiDiagnostics(stagedKibiDiagnostics));
475
+ if (options.dryRun) {
476
+ return { exitCode: 0 };
477
+ }
478
+ return {
479
+ exitCode: hasBlockingImpactDiagnostics(stagedKibiDiagnostics)
480
+ ? 1
481
+ : 0,
482
+ };
483
+ }
467
484
  console.log("No exported symbols or staged entities found in staged files.");
468
485
  return { exitCode: 0 };
469
486
  }
@@ -473,7 +490,11 @@ export async function checkCommand(options) {
473
490
  if (options.dryRun) {
474
491
  return { exitCode: 0 };
475
492
  }
476
- return { exitCode: 1 };
493
+ return {
494
+ exitCode: hasBlockingImpactDiagnostics(stagedKibiDiagnostics)
495
+ ? 1
496
+ : 0,
497
+ };
477
498
  }
478
499
  console.log("✓ No violations found in staged files.");
479
500
  return { exitCode: 0 };
@@ -510,7 +531,13 @@ export async function checkCommand(options) {
510
531
  if (options.dryRun) {
511
532
  return { exitCode: 0 };
512
533
  }
513
- return { exitCode: 1 };
534
+ if (!hasBlockingImpactDiagnostics(stagedKibiDiagnostics)) {
535
+ console.log("✓ No violations found in staged symbols.");
536
+ return { exitCode: 0 };
537
+ }
538
+ return {
539
+ exitCode: 1,
540
+ };
514
541
  }
515
542
  console.log("✓ No violations found in staged symbols.");
516
543
  await cleanupTempKb(tempCtx.tempDir);
@@ -576,6 +603,7 @@ export async function checkCommand(options) {
576
603
  "strict-fact-shape",
577
604
  "strict-req-fact-pairing",
578
605
  "strict-readiness",
606
+ "query-plan-safety",
579
607
  ];
580
608
  const canUseAggregated = Array.from(effectiveRules).every((r) => supportedRules.includes(r));
581
609
  if (canUseAggregated) {
@@ -602,8 +630,24 @@ export async function checkCommand(options) {
602
630
  await runCheck("strict-req-fact-pairing", checkStrictReqFactPairing);
603
631
  await runCheck("strict-readiness", checkStrictReadiness);
604
632
  }
633
+ if (effectiveRules.has("query-plan-safety")) {
634
+ violations.push(...collectQueryPlanSafetyViolations());
635
+ }
636
+ const qualityDiagnostics = await collectFullKbQualityDiagnostics({
637
+ prolog: activeProlog,
638
+ hardViolationEntityIds: new Set(violations.map((violation) => violation.entityId)),
639
+ });
640
+ if (options.format === "json") {
641
+ printStructuredCheckResult({ violations, qualityDiagnostics });
642
+ return { exitCode: violations.length === 0 ? 0 : 1 };
643
+ }
644
+ const qualityOutput = formatQualityDiagnostics(qualityDiagnostics);
605
645
  if (violations.length === 0) {
606
646
  console.log("✓ No violations found. KB is valid.");
647
+ if (qualityOutput.length > 0) {
648
+ console.log();
649
+ console.log(qualityOutput);
650
+ }
607
651
  return { exitCode: 0 };
608
652
  }
609
653
  console.log(`Found ${violations.length} violation(s):`);
@@ -623,6 +667,10 @@ export async function checkCommand(options) {
623
667
  }
624
668
  console.log();
625
669
  }
670
+ if (qualityOutput.length > 0) {
671
+ console.log(qualityOutput);
672
+ console.log();
673
+ }
626
674
  return { exitCode: 1 };
627
675
  }
628
676
  catch (error) {
@@ -1,4 +1,5 @@
1
1
  import { PrologProcess, resolveKbPlPath } from "../prolog.js";
2
+ export { renderCoverageTable } from "./discovery-table.js";
2
3
  export interface DiscoveryCommandOptions {
3
4
  format?: "json" | "table";
4
5
  }
@@ -1 +1 @@
1
- {"version":3,"file":"discovery-shared.d.ts","sourceRoot":"","sources":["../../src/commands/discovery-shared.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAK9D,MAAM,WAAW,uBAAuB;IACtC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CAC3B;AAED,qDAAqD;AACrD,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,aAAa,CAAC;IAC3D,WAAW,EAAE,OAAO,eAAe,CAAC;CACrC;AAGD,wBAAsB,wBAAwB,CAAC,CAAC,EAC9C,QAAQ,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,OAAO,CAAC,CAAC,CAAC,EAC/C,IAAI,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,GAC5B,OAAO,CAAC,CAAC,CAAC,CAmCZ;AAGD,wBAAsB,iBAAiB,CAAC,CAAC,EACvC,QAAQ,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,OAAO,CAAC,CAAC,CAAC,EAC/C,IAAI,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,GAC5B,OAAO,CAAC,CAAC,CAAC,CAmBZ;AAGD,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,MAAM,CAAC,CAS5D;AAGD,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,GAC5B,MAAM,CAGR;AAGD,wBAAsB,kBAAkB,CAAC,CAAC,EACxC,MAAM,EAAE,aAAa,EACrB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,MAAM,CAAC,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,GAC5B,OAAO,CAAC,CAAC,CAAC,CAwBZ;AAGD,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,EACpC,UAAU,EAAE,OAAO,EACnB,YAAY,EAAE,MAAM,GACnB,IAAI,CAQN"}
1
+ {"version":3,"file":"discovery-shared.d.ts","sourceRoot":"","sources":["../../src/commands/discovery-shared.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAM9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAE3D,MAAM,WAAW,uBAAuB;IACtC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CAC3B;AAED,qDAAqD;AACrD,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,aAAa,CAAC;IAC3D,WAAW,EAAE,OAAO,eAAe,CAAC;CACrC;AAGD,wBAAsB,wBAAwB,CAAC,CAAC,EAC9C,QAAQ,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,OAAO,CAAC,CAAC,CAAC,EAC/C,IAAI,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,GAC5B,OAAO,CAAC,CAAC,CAAC,CAmCZ;AAGD,wBAAsB,iBAAiB,CAAC,CAAC,EACvC,QAAQ,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,OAAO,CAAC,CAAC,CAAC,EAC/C,IAAI,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,GAC5B,OAAO,CAAC,CAAC,CAAC,CAmBZ;AAGD,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,MAAM,CAAC,CAS5D;AAGD,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,GAC5B,MAAM,CAGR;AAGD,wBAAsB,kBAAkB,CAAC,CAAC,EACxC,MAAM,EAAE,aAAa,EACrB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,MAAM,CAAC,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,GAC5B,OAAO,CAAC,CAAC,CAAC,CAwBZ;AAGD,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,EACpC,UAAU,EAAE,OAAO,EACnB,YAAY,EAAE,MAAM,GACnB,IAAI,CAQN"}
@@ -1,10 +1,11 @@
1
1
  import path from "node:path";
2
- import Table from "cli-table3";
3
2
  import { getBranchOverride } from "../env.js";
4
3
  import { PrologProcess, resolveKbPlPath } from "../prolog.js";
5
4
  import { escapeAtom } from "../prolog/codec.js";
6
5
  import { safeCleanupProlog } from "../utils/prolog-cleanup.js";
6
+ import { renderDiscoveryTable } from "./discovery-table.js";
7
7
  import { getCurrentBranch } from "./init-helpers.js";
8
+ export { renderCoverageTable } from "./discovery-table.js";
8
9
  // implements REQ-003
9
10
  export async function withAttachedBranchProlog(callback, deps) {
10
11
  const createProlog = deps?.createProlog ?? ((opts) => new PrologProcess(opts));
@@ -97,193 +98,3 @@ export function printDiscoveryResult(format, structured, fallbackText) {
97
98
  const rendered = renderDiscoveryTable(structured);
98
99
  console.log(rendered || fallbackText);
99
100
  }
100
- function renderDiscoveryTable(structured) {
101
- if (!structured || typeof structured !== "object") {
102
- return null;
103
- }
104
- const payload = structured;
105
- if (Array.isArray(payload.results)) {
106
- return renderSearchTable(payload);
107
- }
108
- if (typeof payload.branch === "string" &&
109
- typeof payload.syncState === "string") {
110
- return renderStatusTable(payload);
111
- }
112
- if (Array.isArray(payload.nodes) && Array.isArray(payload.edges)) {
113
- return renderGraphTable(payload);
114
- }
115
- if (Array.isArray(payload.rows) &&
116
- payload.summary &&
117
- typeof payload.summary === "object") {
118
- return renderCoverageTable(payload);
119
- }
120
- if (Array.isArray(payload.rows)) {
121
- return renderGapsTable(payload);
122
- }
123
- return null;
124
- }
125
- function renderSearchTable(payload) {
126
- const rows = Array.isArray(payload.results) ? payload.results : [];
127
- const table = new Table({
128
- head: ["ID", "Type", "Title", "Score", "Reasons", "Snippet"],
129
- wordWrap: true,
130
- colWidths: [20, 10, 32, 8, 28, 44],
131
- });
132
- for (const row of rows) {
133
- const match = row;
134
- const entity = (match.entity ?? {});
135
- const reasons = Array.isArray(match.reasons)
136
- ? match.reasons.join(", ")
137
- : "";
138
- table.push([
139
- stringifyCell(entity.id),
140
- stringifyCell(entity.type),
141
- stringifyCell(entity.title),
142
- stringifyCell(match.score),
143
- reasons,
144
- stringifyCell(match.snippet),
145
- ]);
146
- }
147
- return [
148
- `Search results: ${stringifyCell(payload.count)} total`,
149
- table.toString(),
150
- ].join("\n");
151
- }
152
- function renderStatusTable(payload) {
153
- const table = new Table({
154
- head: ["Field", "Value"],
155
- colWidths: [18, 72],
156
- wordWrap: true,
157
- });
158
- table.push(["Branch", stringifyCell(payload.branch)], ["Sync State", stringifyCell(payload.syncState)], ["Dirty", stringifyCell(payload.dirty)], ["Snapshot", stringifyCell(payload.snapshotId)], ["Synced At", stringifyCell(payload.syncedAt)], ["KB Path", stringifyCell(payload.kbPath)]);
159
- return table.toString();
160
- }
161
- function renderGapsTable(payload) {
162
- const rows = Array.isArray(payload.rows) ? payload.rows : [];
163
- const table = new Table({
164
- head: ["ID", "Type", "Status", "Missing", "Present", "Source"],
165
- colWidths: [20, 10, 12, 24, 24, 40],
166
- wordWrap: true,
167
- });
168
- for (const row of rows) {
169
- const item = row;
170
- table.push([
171
- stringifyCell(item.id),
172
- stringifyCell(item.type),
173
- stringifyCell(item.status),
174
- joinCells(item.missingRelationships),
175
- joinCells(item.presentRelationships),
176
- stringifyCell(item.source),
177
- ]);
178
- }
179
- return [`Gap rows: ${stringifyCell(payload.count)}`, table.toString()].join("\n");
180
- }
181
- function renderCoverageTable(payload) {
182
- const summary = (payload.summary ?? {});
183
- const rows = Array.isArray(payload.rows) ? payload.rows : [];
184
- const summaryTable = new Table({
185
- head: ["Metric", "Value"],
186
- colWidths: [24, 16],
187
- });
188
- for (const [key, value] of Object.entries(summary)) {
189
- summaryTable.push([key, stringifyCell(value)]);
190
- }
191
- const firstRow = rows[0];
192
- const isRequirementCoverage = firstRow && Object.hasOwn(firstRow, "scenarioCount");
193
- const table = isRequirementCoverage
194
- ? new Table({
195
- head: [
196
- "ID",
197
- "Status",
198
- "Priority",
199
- "Coverage",
200
- "Scen",
201
- "Tests",
202
- "Symbols",
203
- "Gaps",
204
- ],
205
- colWidths: [20, 12, 12, 18, 8, 8, 10, 28],
206
- wordWrap: true,
207
- })
208
- : new Table({
209
- head: ["ID", "Type", "Coverage", "Details", "Gaps"],
210
- colWidths: [20, 10, 18, 28, 28],
211
- wordWrap: true,
212
- });
213
- for (const row of rows) {
214
- const item = row;
215
- if (isRequirementCoverage) {
216
- table.push([
217
- stringifyCell(item.id),
218
- stringifyCell(item.status),
219
- stringifyCell(item.priority),
220
- stringifyCell(item.coverageStatus),
221
- stringifyCell(item.scenarioCount),
222
- stringifyCell(item.testCount),
223
- stringifyCell(item.transitiveSymbolCount),
224
- joinCells(item.gaps),
225
- ]);
226
- }
227
- else {
228
- table.push([
229
- stringifyCell(item.id),
230
- stringifyCell(item.type),
231
- stringifyCell(item.coverageStatus),
232
- `req=${stringifyCell(item.directRequirementCount)} test=${stringifyCell(item.testCount)} count=${stringifyCell(item.count)}`,
233
- joinCells(item.gaps),
234
- ]);
235
- }
236
- }
237
- return [summaryTable.toString(), table.toString()].join("\n\n");
238
- }
239
- function renderGraphTable(payload) {
240
- const nodes = Array.isArray(payload.nodes) ? payload.nodes : [];
241
- const edges = Array.isArray(payload.edges) ? payload.edges : [];
242
- const nodeTable = new Table({
243
- head: ["Node ID", "Type", "Title", "Status"],
244
- colWidths: [22, 10, 36, 12],
245
- wordWrap: true,
246
- });
247
- for (const row of nodes) {
248
- const item = row;
249
- nodeTable.push([
250
- stringifyCell(item.id),
251
- stringifyCell(item.type),
252
- stringifyCell(item.title),
253
- stringifyCell(item.status),
254
- ]);
255
- }
256
- const edgeTable = new Table({
257
- head: ["Relationship", "From", "To"],
258
- colWidths: [18, 22, 22],
259
- wordWrap: true,
260
- });
261
- for (const row of edges) {
262
- const item = row;
263
- edgeTable.push([
264
- stringifyCell(item.type),
265
- stringifyCell(item.from),
266
- stringifyCell(item.to),
267
- ]);
268
- }
269
- return [
270
- `Nodes: ${nodes.length} Edges: ${edges.length} Truncated: ${stringifyCell(payload.truncated)}`,
271
- nodeTable.toString(),
272
- edgeTable.toString(),
273
- ].join("\n\n");
274
- }
275
- function stringifyCell(value) {
276
- if (value === null || value === undefined || value === "") {
277
- return "-";
278
- }
279
- if (typeof value === "string") {
280
- return value;
281
- }
282
- return String(value);
283
- }
284
- function joinCells(value) {
285
- if (!Array.isArray(value) || value.length === 0) {
286
- return "-";
287
- }
288
- return value.map((item) => stringifyCell(item)).join(", ");
289
- }
@@ -0,0 +1,3 @@
1
+ export declare function renderDiscoveryTable(structured: unknown): string | null;
2
+ export declare function renderCoverageTable(payload: Readonly<Record<string, unknown>>): string;
3
+ //# sourceMappingURL=discovery-table.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery-table.d.ts","sourceRoot":"","sources":["../../src/commands/discovery-table.ts"],"names":[],"mappings":"AAIA,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAmCvE;AA4ED,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GACzC,MAAM,CA+DR"}