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.
- 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 +130 -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 +39 -5
- 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/prolog.d.ts.map +1 -1
- package/dist/prolog.js +11 -7
- 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 +20 -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/traceability/temp-kb.d.ts.map +1 -1
- 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 +7 -9
- 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 +20 -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;
|
|
@@ -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 = [
|
|
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,
|
|
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
|
|
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
|
-
...
|
|
445
|
-
...
|
|
436
|
+
...scopedManifestResults,
|
|
437
|
+
...markdownResultsByPath.values(),
|
|
446
438
|
];
|
|
447
439
|
const stagedKibiEvidence = buildStagedKibiImpactEvidence({
|
|
448
440
|
stagedFiles,
|
|
449
441
|
sourceFiles,
|
|
450
442
|
markdownFiles,
|
|
451
|
-
|
|
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 {
|
|
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
|
-
|
|
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 +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"}
|