kibi-cli 0.12.8 → 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 (111) 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 +117 -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 +29 -1
  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/public/impact/analyzer.d.ts +3 -0
  26. package/dist/public/impact/analyzer.d.ts.map +1 -0
  27. package/dist/public/impact/analyzer.js +54 -0
  28. package/dist/public/impact/coverage-depth-quality.d.ts +4 -0
  29. package/dist/public/impact/coverage-depth-quality.d.ts.map +1 -0
  30. package/dist/public/impact/coverage-depth-quality.js +179 -0
  31. package/dist/public/impact/diagnostics.d.ts +7 -0
  32. package/dist/public/impact/diagnostics.d.ts.map +1 -0
  33. package/dist/public/impact/diagnostics.js +122 -0
  34. package/dist/public/impact/diff-meaning.d.ts +2 -0
  35. package/dist/public/impact/diff-meaning.d.ts.map +1 -0
  36. package/dist/public/impact/diff-meaning.js +101 -0
  37. package/dist/public/impact/full-kb-quality.d.ts +10 -0
  38. package/dist/public/impact/full-kb-quality.d.ts.map +1 -0
  39. package/dist/public/impact/full-kb-quality.js +158 -0
  40. package/dist/public/impact/manifest.d.ts +5 -0
  41. package/dist/public/impact/manifest.d.ts.map +1 -0
  42. package/dist/public/impact/manifest.js +43 -0
  43. package/dist/public/impact/requirement-quality.d.ts +4 -0
  44. package/dist/public/impact/requirement-quality.d.ts.map +1 -0
  45. package/dist/public/impact/requirement-quality.js +216 -0
  46. package/dist/public/impact/source-changes.d.ts +5 -0
  47. package/dist/public/impact/source-changes.d.ts.map +1 -0
  48. package/dist/public/impact/source-changes.js +129 -0
  49. package/dist/public/impact/summaries.d.ts +6 -0
  50. package/dist/public/impact/summaries.d.ts.map +1 -0
  51. package/dist/public/impact/summaries.js +68 -0
  52. package/dist/public/impact/symbol-quality-model.d.ts +21 -0
  53. package/dist/public/impact/symbol-quality-model.d.ts.map +1 -0
  54. package/dist/public/impact/symbol-quality-model.js +116 -0
  55. package/dist/public/impact/symbol-quality.d.ts +4 -0
  56. package/dist/public/impact/symbol-quality.d.ts.map +1 -0
  57. package/dist/public/impact/symbol-quality.js +159 -0
  58. package/dist/public/impact/types.d.ts +79 -0
  59. package/dist/public/impact/types.d.ts.map +1 -0
  60. package/dist/public/impact/types.js +1 -0
  61. package/dist/public/impact-diagnostics.d.ts +7 -0
  62. package/dist/public/impact-diagnostics.d.ts.map +1 -0
  63. package/dist/public/impact-diagnostics.js +5 -0
  64. package/dist/public/schemas/entity.d.ts.map +1 -1
  65. package/dist/public/schemas/entity.js +21 -0
  66. package/dist/public/skills/kibi-usage/SKILL.md +13 -5
  67. package/dist/public/skills/kibi-usage/resources/fact-lanes.md +5 -5
  68. package/dist/public/skills/kibi-usage/resources/relationship-directions.md +30 -0
  69. package/dist/public/skills/kibi-usage/resources/workflows.md +14 -0
  70. package/dist/public/symbol-granularity.d.ts +1 -1
  71. package/dist/public/symbol-granularity.d.ts.map +1 -1
  72. package/dist/public/symbol-granularity.js +2 -0
  73. package/dist/traceability/evidence-model.d.ts +1 -1
  74. package/dist/traceability/evidence-model.d.ts.map +1 -1
  75. package/dist/traceability/evidence-model.js +1 -1
  76. package/dist/traceability/staged-diagnostics.d.ts +6 -6
  77. package/dist/traceability/staged-diagnostics.d.ts.map +1 -1
  78. package/dist/traceability/staged-diagnostics.js +12 -6
  79. package/dist/traceability/staged-impact-contract.js +1 -1
  80. package/dist/traceability/staged-symbols-manifest.d.ts.map +1 -1
  81. package/dist/traceability/staged-symbols-manifest.js +23 -1
  82. package/dist/traceability/symbol-extract.d.ts.map +1 -1
  83. package/dist/traceability/symbol-extract.js +41 -0
  84. package/dist/types/entities.d.ts +5 -1
  85. package/dist/types/entities.d.ts.map +1 -1
  86. package/dist/utils/prolog-query-plan-safety.d.ts +8 -0
  87. package/dist/utils/prolog-query-plan-safety.d.ts.map +1 -0
  88. package/dist/utils/prolog-query-plan-safety.js +47 -0
  89. package/dist/utils/rule-registry.d.ts.map +1 -1
  90. package/dist/utils/rule-registry.js +12 -0
  91. package/package.json +6 -2
  92. package/schema/entities.pl +4 -0
  93. package/src/public/impact/analyzer.ts +90 -0
  94. package/src/public/impact/coverage-depth-quality.ts +268 -0
  95. package/src/public/impact/diagnostics.ts +192 -0
  96. package/src/public/impact/diff-meaning.ts +121 -0
  97. package/src/public/impact/full-kb-quality.ts +203 -0
  98. package/src/public/impact/manifest.ts +57 -0
  99. package/src/public/impact/requirement-quality.ts +282 -0
  100. package/src/public/impact/source-changes.ts +180 -0
  101. package/src/public/impact/summaries.ts +100 -0
  102. package/src/public/impact/symbol-quality-model.ts +171 -0
  103. package/src/public/impact/symbol-quality.ts +207 -0
  104. package/src/public/impact/types.ts +99 -0
  105. package/src/public/impact-diagnostics.ts +22 -0
  106. package/src/public/schemas/entity.ts +23 -0
  107. package/src/public/skills/kibi-usage/SKILL.md +13 -5
  108. package/src/public/skills/kibi-usage/resources/fact-lanes.md +5 -5
  109. package/src/public/skills/kibi-usage/resources/relationship-directions.md +30 -0
  110. package/src/public/skills/kibi-usage/resources/workflows.md +14 -0
  111. 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;AA8bD,wBAAsB,YAAY,CAChC,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CA0W/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;
@@ -137,70 +149,6 @@ function buildManifestLookup(stagedFiles) {
137
149
  stagedAuthoredSymbolResults,
138
150
  };
139
151
  }
140
- function hasTraceabilityRelationship(result) {
141
- return result.relationships.some((relationship) => isTraceabilityRelationshipType(relationship.type));
142
- }
143
- function hasValidGranularityReason(result) {
144
- return isAllowedGranularityReason(result.entity.granularity_reason);
145
- }
146
- function createSymbolGranularityDiagnostics(options) {
147
- const diagnostics = [];
148
- for (const result of options.manifestResults) {
149
- if (!result.sourceFile)
150
- continue;
151
- if (!hasTraceabilityRelationship(result))
152
- continue;
153
- if (hasValidGranularityReason(result))
154
- continue;
155
- const granularSymbols = getGranularSymbolsForSourceFile(result.sourceFile, options.symbolsByFile, options.sourceContentByFile);
156
- if (granularSymbols.length === 0)
157
- continue;
158
- const granularNames = [
159
- ...new Set(granularSymbols.map((s) => s.name)),
160
- ].sort();
161
- if (granularNames.includes(result.entity.title))
162
- continue;
163
- const behavioralNames = getBehavioralSymbolNames(granularSymbols);
164
- if (behavioralNames.length === 0)
165
- continue;
166
- const nonBehavioralNames = getNonBehavioralSymbolNames(granularSymbols);
167
- const ignoredSymbolsSuggestion = nonBehavioralNames.length > 0
168
- ? ` Non-behavioral symbols ignored for this decision: ${nonBehavioralNames.join(", ")}.`
169
- : "";
170
- diagnostics.push({
171
- id: "symbol_granularity_violation",
172
- severity: "error",
173
- files: [result.entity.source, result.sourceFile],
174
- docs: ["docs/symbol-traceability-taxonomy.md"],
175
- message: `Symbol ${result.entity.id} links ${result.sourceFile} coarsely while granular symbols are available (behavioral only): ${behavioralNames.join(", ")}`,
176
- 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}`,
177
- });
178
- }
179
- return diagnostics;
180
- }
181
- function getGranularSymbolsForSourceFile(sourceFile, symbolsByFile, sourceContentByFile) {
182
- const stagedContent = sourceContentByFile.get(sourceFile);
183
- if (stagedContent !== undefined) {
184
- return extractSymbolsFromStagedFile({
185
- path: sourceFile,
186
- status: "M",
187
- hunkRanges: [{ start: 1, end: Number.MAX_SAFE_INTEGER }],
188
- content: stagedContent,
189
- });
190
- }
191
- const absolutePath = path.isAbsolute(sourceFile)
192
- ? sourceFile
193
- : path.resolve(process.cwd(), sourceFile);
194
- if (!existsSync(absolutePath)) {
195
- return [];
196
- }
197
- return extractSymbolsFromStagedFile({
198
- path: sourceFile,
199
- status: "M",
200
- hunkRanges: [{ start: 1, end: Number.MAX_SAFE_INTEGER }],
201
- content: readFileSync(absolutePath, "utf8"),
202
- });
203
- }
204
152
  const KIBI_ENTITY_TYPES = new Set([
205
153
  "req",
206
154
  "scenario",
@@ -257,7 +205,9 @@ function getStagedDiffText(stagedFile) {
257
205
  function formatStagedKibiDiagnostics(diagnostics) {
258
206
  return diagnostics
259
207
  .map((diagnostic) => {
260
- const lines = [`[${diagnostic.id}] ${diagnostic.message}`];
208
+ const lines = [
209
+ `[${diagnostic.severity.toUpperCase()} ${diagnostic.id}] ${diagnostic.message}`,
210
+ ];
261
211
  if (diagnostic.files.length > 0) {
262
212
  lines.push(` Files: ${diagnostic.files.join(", ")}`);
263
213
  }
@@ -269,11 +219,48 @@ function formatStagedKibiDiagnostics(diagnostics) {
269
219
  })
270
220
  .join("\n\n");
271
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
+ }
272
255
  function uniqueSorted(values) {
273
256
  return Array.from(new Set(values)).sort();
274
257
  }
258
+ function hasYamlFrontmatter(content) {
259
+ const trimmed = content.trimStart();
260
+ return trimmed.startsWith("---") && trimmed.slice(3).includes("\n---");
261
+ }
275
262
  function buildStagedKibiImpactEvidence(options) {
276
- const { stagedFiles, sourceFiles, markdownFiles, markdownResults, symbolsByFile, symbolsManifestPath, } = options;
263
+ const { stagedFiles, sourceFiles, markdownFiles, markdownResultsByPath, symbolsByFile, symbolsManifestPath, } = options;
277
264
  const sourceChanges = sourceFiles.map((file) => {
278
265
  const symbolsForFile = symbolsByFile.get(file.path) ?? [];
279
266
  const behaviorCandidate = !isTestOnlySourcePath(file.path) &&
@@ -304,13 +291,6 @@ function buildStagedKibiImpactEvidence(options) {
304
291
  stagedFiles,
305
292
  sourceFiles: behaviorSourceFiles,
306
293
  });
307
- const markdownResultsByPath = new Map();
308
- for (const [index, file] of markdownFiles.entries()) {
309
- const result = markdownResults[index];
310
- if (result) {
311
- markdownResultsByPath.set(file.path, result);
312
- }
313
- }
314
294
  const resolvedKbArtifacts = [];
315
295
  let override = null;
316
296
  for (const file of markdownFiles) {
@@ -442,23 +422,34 @@ export async function checkCommand(options) {
442
422
  console.error(`Error extracting symbols from staged file ${f.path}: ${e instanceof Error ? e.message : String(e)}`);
443
423
  }
444
424
  }
445
- 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
+ }
446
432
  const stagedSourceFilePaths = new Set(sourceFiles.map((file) => file.path));
447
433
  const scopedManifestResults = manifestResults.filter((result) => result.sourceFile !== undefined &&
448
434
  stagedSourceFilePaths.has(result.sourceFile));
449
435
  const stagedEntityResults = [
450
436
  ...scopedManifestResults,
451
- ...markdownResults,
437
+ ...markdownResultsByPath.values(),
452
438
  ];
453
439
  const stagedKibiEvidence = buildStagedKibiImpactEvidence({
454
440
  stagedFiles,
455
441
  sourceFiles,
456
442
  markdownFiles,
457
- markdownResults,
443
+ markdownResultsByPath,
458
444
  symbolsByFile,
459
445
  symbolsManifestPath,
460
446
  });
461
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
+ : []);
462
453
  const stagedAuthoredSymbolSet = new Set(stagedAuthoredSymbolResults);
463
454
  const stagedSourcePaths = new Set(sourceFiles.map((file) => file.path));
464
455
  const activeGranularityResults = authoredSymbolResults.filter((result) => stagedAuthoredSymbolSet.has(result) ||
@@ -466,16 +457,29 @@ export async function checkCommand(options) {
466
457
  stagedSourcePaths.has(result.sourceFile)));
467
458
  stagedKibiDiagnostics.push(...createSymbolGranularityDiagnostics({
468
459
  manifestResults: activeGranularityResults,
460
+ ...(activeStagedSymbolEntityIds.size > 0
461
+ ? { activeEntityIds: activeStagedSymbolEntityIds }
462
+ : {}),
469
463
  symbolsByFile,
470
464
  sourceContentByFile,
471
- }));
465
+ }), ...createSymbolQualityDiagnostics({
466
+ manifestResults: activeGranularityResults,
467
+ ...(activeStagedSymbolEntityIds.size > 0
468
+ ? { activeEntityIds: activeStagedSymbolEntityIds }
469
+ : {}),
470
+ symbolsByFile,
471
+ }), ...createSemanticReviewDiagnostics({ symbolsByFile }));
472
472
  if (allSymbols.length === 0 && stagedEntityResults.length === 0) {
473
473
  if (stagedKibiDiagnostics.length > 0) {
474
474
  console.log(formatStagedKibiDiagnostics(stagedKibiDiagnostics));
475
475
  if (options.dryRun) {
476
476
  return { exitCode: 0 };
477
477
  }
478
- return { exitCode: 1 };
478
+ return {
479
+ exitCode: hasBlockingImpactDiagnostics(stagedKibiDiagnostics)
480
+ ? 1
481
+ : 0,
482
+ };
479
483
  }
480
484
  console.log("No exported symbols or staged entities found in staged files.");
481
485
  return { exitCode: 0 };
@@ -486,7 +490,11 @@ export async function checkCommand(options) {
486
490
  if (options.dryRun) {
487
491
  return { exitCode: 0 };
488
492
  }
489
- return { exitCode: 1 };
493
+ return {
494
+ exitCode: hasBlockingImpactDiagnostics(stagedKibiDiagnostics)
495
+ ? 1
496
+ : 0,
497
+ };
490
498
  }
491
499
  console.log("✓ No violations found in staged files.");
492
500
  return { exitCode: 0 };
@@ -523,7 +531,13 @@ export async function checkCommand(options) {
523
531
  if (options.dryRun) {
524
532
  return { exitCode: 0 };
525
533
  }
526
- 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
+ };
527
541
  }
528
542
  console.log("✓ No violations found in staged symbols.");
529
543
  await cleanupTempKb(tempCtx.tempDir);
@@ -589,6 +603,7 @@ export async function checkCommand(options) {
589
603
  "strict-fact-shape",
590
604
  "strict-req-fact-pairing",
591
605
  "strict-readiness",
606
+ "query-plan-safety",
592
607
  ];
593
608
  const canUseAggregated = Array.from(effectiveRules).every((r) => supportedRules.includes(r));
594
609
  if (canUseAggregated) {
@@ -615,8 +630,24 @@ export async function checkCommand(options) {
615
630
  await runCheck("strict-req-fact-pairing", checkStrictReqFactPairing);
616
631
  await runCheck("strict-readiness", checkStrictReadiness);
617
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);
618
645
  if (violations.length === 0) {
619
646
  console.log("✓ No violations found. KB is valid.");
647
+ if (qualityOutput.length > 0) {
648
+ console.log();
649
+ console.log(qualityOutput);
650
+ }
620
651
  return { exitCode: 0 };
621
652
  }
622
653
  console.log(`Found ${violations.length} violation(s):`);
@@ -636,6 +667,10 @@ export async function checkCommand(options) {
636
667
  }
637
668
  console.log();
638
669
  }
670
+ if (qualityOutput.length > 0) {
671
+ console.log(qualityOutput);
672
+ console.log();
673
+ }
639
674
  return { exitCode: 1 };
640
675
  }
641
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"}