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.
- package/dist/cli.js +1 -0
- package/dist/commands/check.d.ts +1 -0
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +117 -82
- package/dist/commands/discovery-shared.d.ts +1 -0
- package/dist/commands/discovery-shared.d.ts.map +1 -1
- package/dist/commands/discovery-shared.js +2 -191
- package/dist/commands/discovery-table.d.ts +3 -0
- package/dist/commands/discovery-table.d.ts.map +1 -0
- package/dist/commands/discovery-table.js +204 -0
- package/dist/commands/sync/discovery.d.ts.map +1 -1
- package/dist/commands/sync/discovery.js +8 -1
- package/dist/commands/sync/extraction.d.ts +6 -1
- package/dist/commands/sync/extraction.d.ts.map +1 -1
- package/dist/commands/sync/extraction.js +7 -3
- package/dist/commands/sync/persistence.d.ts.map +1 -1
- package/dist/commands/sync/persistence.js +29 -1
- package/dist/extractors/manifest.d.ts.map +1 -1
- package/dist/extractors/manifest.js +18 -0
- package/dist/extractors/markdown.d.ts +6 -0
- package/dist/extractors/markdown.d.ts.map +1 -1
- package/dist/extractors/symbols-coordinator.d.ts +1 -1
- package/dist/extractors/symbols-coordinator.d.ts.map +1 -1
- package/dist/extractors/symbols-ts.js +62 -0
- package/dist/public/impact/analyzer.d.ts +3 -0
- package/dist/public/impact/analyzer.d.ts.map +1 -0
- package/dist/public/impact/analyzer.js +54 -0
- package/dist/public/impact/coverage-depth-quality.d.ts +4 -0
- package/dist/public/impact/coverage-depth-quality.d.ts.map +1 -0
- package/dist/public/impact/coverage-depth-quality.js +179 -0
- package/dist/public/impact/diagnostics.d.ts +7 -0
- package/dist/public/impact/diagnostics.d.ts.map +1 -0
- package/dist/public/impact/diagnostics.js +122 -0
- package/dist/public/impact/diff-meaning.d.ts +2 -0
- package/dist/public/impact/diff-meaning.d.ts.map +1 -0
- package/dist/public/impact/diff-meaning.js +101 -0
- package/dist/public/impact/full-kb-quality.d.ts +10 -0
- package/dist/public/impact/full-kb-quality.d.ts.map +1 -0
- package/dist/public/impact/full-kb-quality.js +158 -0
- package/dist/public/impact/manifest.d.ts +5 -0
- package/dist/public/impact/manifest.d.ts.map +1 -0
- package/dist/public/impact/manifest.js +43 -0
- package/dist/public/impact/requirement-quality.d.ts +4 -0
- package/dist/public/impact/requirement-quality.d.ts.map +1 -0
- package/dist/public/impact/requirement-quality.js +216 -0
- package/dist/public/impact/source-changes.d.ts +5 -0
- package/dist/public/impact/source-changes.d.ts.map +1 -0
- package/dist/public/impact/source-changes.js +129 -0
- package/dist/public/impact/summaries.d.ts +6 -0
- package/dist/public/impact/summaries.d.ts.map +1 -0
- package/dist/public/impact/summaries.js +68 -0
- package/dist/public/impact/symbol-quality-model.d.ts +21 -0
- package/dist/public/impact/symbol-quality-model.d.ts.map +1 -0
- package/dist/public/impact/symbol-quality-model.js +116 -0
- package/dist/public/impact/symbol-quality.d.ts +4 -0
- package/dist/public/impact/symbol-quality.d.ts.map +1 -0
- package/dist/public/impact/symbol-quality.js +159 -0
- package/dist/public/impact/types.d.ts +79 -0
- package/dist/public/impact/types.d.ts.map +1 -0
- package/dist/public/impact/types.js +1 -0
- package/dist/public/impact-diagnostics.d.ts +7 -0
- package/dist/public/impact-diagnostics.d.ts.map +1 -0
- package/dist/public/impact-diagnostics.js +5 -0
- package/dist/public/schemas/entity.d.ts.map +1 -1
- package/dist/public/schemas/entity.js +21 -0
- package/dist/public/skills/kibi-usage/SKILL.md +13 -5
- package/dist/public/skills/kibi-usage/resources/fact-lanes.md +5 -5
- package/dist/public/skills/kibi-usage/resources/relationship-directions.md +30 -0
- package/dist/public/skills/kibi-usage/resources/workflows.md +14 -0
- package/dist/public/symbol-granularity.d.ts +1 -1
- package/dist/public/symbol-granularity.d.ts.map +1 -1
- package/dist/public/symbol-granularity.js +2 -0
- package/dist/traceability/evidence-model.d.ts +1 -1
- package/dist/traceability/evidence-model.d.ts.map +1 -1
- package/dist/traceability/evidence-model.js +1 -1
- package/dist/traceability/staged-diagnostics.d.ts +6 -6
- package/dist/traceability/staged-diagnostics.d.ts.map +1 -1
- package/dist/traceability/staged-diagnostics.js +12 -6
- package/dist/traceability/staged-impact-contract.js +1 -1
- package/dist/traceability/staged-symbols-manifest.d.ts.map +1 -1
- package/dist/traceability/staged-symbols-manifest.js +23 -1
- package/dist/traceability/symbol-extract.d.ts.map +1 -1
- package/dist/traceability/symbol-extract.js +41 -0
- package/dist/types/entities.d.ts +5 -1
- package/dist/types/entities.d.ts.map +1 -1
- package/dist/utils/prolog-query-plan-safety.d.ts +8 -0
- package/dist/utils/prolog-query-plan-safety.d.ts.map +1 -0
- package/dist/utils/prolog-query-plan-safety.js +47 -0
- package/dist/utils/rule-registry.d.ts.map +1 -1
- package/dist/utils/rule-registry.js +12 -0
- package/package.json +6 -2
- package/schema/entities.pl +4 -0
- package/src/public/impact/analyzer.ts +90 -0
- package/src/public/impact/coverage-depth-quality.ts +268 -0
- package/src/public/impact/diagnostics.ts +192 -0
- package/src/public/impact/diff-meaning.ts +121 -0
- package/src/public/impact/full-kb-quality.ts +203 -0
- package/src/public/impact/manifest.ts +57 -0
- package/src/public/impact/requirement-quality.ts +282 -0
- package/src/public/impact/source-changes.ts +180 -0
- package/src/public/impact/summaries.ts +100 -0
- package/src/public/impact/symbol-quality-model.ts +171 -0
- package/src/public/impact/symbol-quality.ts +207 -0
- package/src/public/impact/types.ts +99 -0
- package/src/public/impact-diagnostics.ts +22 -0
- package/src/public/schemas/entity.ts +23 -0
- package/src/public/skills/kibi-usage/SKILL.md +13 -5
- package/src/public/skills/kibi-usage/resources/fact-lanes.md +5 -5
- package/src/public/skills/kibi-usage/resources/relationship-directions.md +30 -0
- package/src/public/skills/kibi-usage/resources/workflows.md +14 -0
- 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
|
}));
|
package/dist/commands/check.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/commands/check.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/commands/check.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/check.js
CHANGED
|
@@ -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 {
|
|
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 = [
|
|
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,
|
|
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
|
|
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
|
-
...
|
|
437
|
+
...markdownResultsByPath.values(),
|
|
452
438
|
];
|
|
453
439
|
const stagedKibiEvidence = buildStagedKibiImpactEvidence({
|
|
454
440
|
stagedFiles,
|
|
455
441
|
sourceFiles,
|
|
456
442
|
markdownFiles,
|
|
457
|
-
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discovery-shared.d.ts","sourceRoot":"","sources":["../../src/commands/discovery-shared.ts"],"names":[],"mappings":"
|
|
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 @@
|
|
|
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"}
|