kibi-cli 0.12.7 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/dist/cli.js +1 -0
  2. package/dist/commands/check.d.ts +1 -0
  3. package/dist/commands/check.d.ts.map +1 -1
  4. package/dist/commands/check.js +130 -82
  5. package/dist/commands/discovery-shared.d.ts +1 -0
  6. package/dist/commands/discovery-shared.d.ts.map +1 -1
  7. package/dist/commands/discovery-shared.js +2 -191
  8. package/dist/commands/discovery-table.d.ts +3 -0
  9. package/dist/commands/discovery-table.d.ts.map +1 -0
  10. package/dist/commands/discovery-table.js +204 -0
  11. package/dist/commands/sync/discovery.d.ts.map +1 -1
  12. package/dist/commands/sync/discovery.js +8 -1
  13. package/dist/commands/sync/extraction.d.ts +6 -1
  14. package/dist/commands/sync/extraction.d.ts.map +1 -1
  15. package/dist/commands/sync/extraction.js +7 -3
  16. package/dist/commands/sync/persistence.d.ts.map +1 -1
  17. package/dist/commands/sync/persistence.js +39 -5
  18. package/dist/extractors/manifest.d.ts.map +1 -1
  19. package/dist/extractors/manifest.js +18 -0
  20. package/dist/extractors/markdown.d.ts +6 -0
  21. package/dist/extractors/markdown.d.ts.map +1 -1
  22. package/dist/extractors/symbols-coordinator.d.ts +1 -1
  23. package/dist/extractors/symbols-coordinator.d.ts.map +1 -1
  24. package/dist/extractors/symbols-ts.js +62 -0
  25. package/dist/prolog.d.ts.map +1 -1
  26. package/dist/prolog.js +11 -7
  27. package/dist/public/impact/analyzer.d.ts +3 -0
  28. package/dist/public/impact/analyzer.d.ts.map +1 -0
  29. package/dist/public/impact/analyzer.js +54 -0
  30. package/dist/public/impact/coverage-depth-quality.d.ts +4 -0
  31. package/dist/public/impact/coverage-depth-quality.d.ts.map +1 -0
  32. package/dist/public/impact/coverage-depth-quality.js +179 -0
  33. package/dist/public/impact/diagnostics.d.ts +7 -0
  34. package/dist/public/impact/diagnostics.d.ts.map +1 -0
  35. package/dist/public/impact/diagnostics.js +122 -0
  36. package/dist/public/impact/diff-meaning.d.ts +2 -0
  37. package/dist/public/impact/diff-meaning.d.ts.map +1 -0
  38. package/dist/public/impact/diff-meaning.js +101 -0
  39. package/dist/public/impact/full-kb-quality.d.ts +10 -0
  40. package/dist/public/impact/full-kb-quality.d.ts.map +1 -0
  41. package/dist/public/impact/full-kb-quality.js +158 -0
  42. package/dist/public/impact/manifest.d.ts +5 -0
  43. package/dist/public/impact/manifest.d.ts.map +1 -0
  44. package/dist/public/impact/manifest.js +43 -0
  45. package/dist/public/impact/requirement-quality.d.ts +4 -0
  46. package/dist/public/impact/requirement-quality.d.ts.map +1 -0
  47. package/dist/public/impact/requirement-quality.js +216 -0
  48. package/dist/public/impact/source-changes.d.ts +5 -0
  49. package/dist/public/impact/source-changes.d.ts.map +1 -0
  50. package/dist/public/impact/source-changes.js +129 -0
  51. package/dist/public/impact/summaries.d.ts +6 -0
  52. package/dist/public/impact/summaries.d.ts.map +1 -0
  53. package/dist/public/impact/summaries.js +68 -0
  54. package/dist/public/impact/symbol-quality-model.d.ts +21 -0
  55. package/dist/public/impact/symbol-quality-model.d.ts.map +1 -0
  56. package/dist/public/impact/symbol-quality-model.js +116 -0
  57. package/dist/public/impact/symbol-quality.d.ts +4 -0
  58. package/dist/public/impact/symbol-quality.d.ts.map +1 -0
  59. package/dist/public/impact/symbol-quality.js +159 -0
  60. package/dist/public/impact/types.d.ts +79 -0
  61. package/dist/public/impact/types.d.ts.map +1 -0
  62. package/dist/public/impact/types.js +1 -0
  63. package/dist/public/impact-diagnostics.d.ts +7 -0
  64. package/dist/public/impact-diagnostics.d.ts.map +1 -0
  65. package/dist/public/impact-diagnostics.js +5 -0
  66. package/dist/public/schemas/entity.d.ts.map +1 -1
  67. package/dist/public/schemas/entity.js +21 -0
  68. package/dist/public/skills/kibi-usage/SKILL.md +20 -5
  69. package/dist/public/skills/kibi-usage/resources/fact-lanes.md +5 -5
  70. package/dist/public/skills/kibi-usage/resources/relationship-directions.md +30 -0
  71. package/dist/public/skills/kibi-usage/resources/workflows.md +14 -0
  72. package/dist/public/symbol-granularity.d.ts +1 -1
  73. package/dist/public/symbol-granularity.d.ts.map +1 -1
  74. package/dist/public/symbol-granularity.js +2 -0
  75. package/dist/traceability/evidence-model.d.ts +1 -1
  76. package/dist/traceability/evidence-model.d.ts.map +1 -1
  77. package/dist/traceability/evidence-model.js +1 -1
  78. package/dist/traceability/staged-diagnostics.d.ts +6 -6
  79. package/dist/traceability/staged-diagnostics.d.ts.map +1 -1
  80. package/dist/traceability/staged-diagnostics.js +12 -6
  81. package/dist/traceability/staged-impact-contract.js +1 -1
  82. package/dist/traceability/staged-symbols-manifest.d.ts.map +1 -1
  83. package/dist/traceability/staged-symbols-manifest.js +23 -1
  84. package/dist/traceability/symbol-extract.d.ts.map +1 -1
  85. package/dist/traceability/symbol-extract.js +41 -0
  86. package/dist/traceability/temp-kb.d.ts.map +1 -1
  87. package/dist/types/entities.d.ts +5 -1
  88. package/dist/types/entities.d.ts.map +1 -1
  89. package/dist/utils/prolog-query-plan-safety.d.ts +8 -0
  90. package/dist/utils/prolog-query-plan-safety.d.ts.map +1 -0
  91. package/dist/utils/prolog-query-plan-safety.js +47 -0
  92. package/dist/utils/rule-registry.d.ts.map +1 -1
  93. package/dist/utils/rule-registry.js +12 -0
  94. package/package.json +7 -9
  95. package/schema/entities.pl +4 -0
  96. package/src/public/impact/analyzer.ts +90 -0
  97. package/src/public/impact/coverage-depth-quality.ts +268 -0
  98. package/src/public/impact/diagnostics.ts +192 -0
  99. package/src/public/impact/diff-meaning.ts +121 -0
  100. package/src/public/impact/full-kb-quality.ts +203 -0
  101. package/src/public/impact/manifest.ts +57 -0
  102. package/src/public/impact/requirement-quality.ts +282 -0
  103. package/src/public/impact/source-changes.ts +180 -0
  104. package/src/public/impact/summaries.ts +100 -0
  105. package/src/public/impact/symbol-quality-model.ts +171 -0
  106. package/src/public/impact/symbol-quality.ts +207 -0
  107. package/src/public/impact/types.ts +99 -0
  108. package/src/public/impact-diagnostics.ts +22 -0
  109. package/src/public/schemas/entity.ts +23 -0
  110. package/src/public/skills/kibi-usage/SKILL.md +20 -5
  111. package/src/public/skills/kibi-usage/resources/fact-lanes.md +5 -5
  112. package/src/public/skills/kibi-usage/resources/relationship-directions.md +30 -0
  113. package/src/public/skills/kibi-usage/resources/workflows.md +14 -0
  114. package/src/public/symbol-granularity.ts +4 -0
@@ -0,0 +1,54 @@
1
+ import * as path from "node:path";
2
+ import { extractSymbolsFromStagedFile } from "../../traceability/symbol-extract.js";
3
+ import { createSemanticReviewDiagnostics, createSymbolGranularityDiagnostics, } from "./diagnostics.js";
4
+ import { createImpactManifestLookup, readImpactManifestResults, } from "./manifest.js";
5
+ import { collectSourceChanges, uniqueSorted } from "./source-changes.js";
6
+ import { buildNextActions, collectLinkedEntities, formatExtractedSymbols, } from "./summaries.js";
7
+ import { createSymbolQualityDiagnostics } from "./symbol-quality.js";
8
+ export function analyzeChangedFileImpact(options) {
9
+ const workspaceRoot = path.resolve(options.workspaceRoot);
10
+ const sourceChanges = collectSourceChanges({ ...options, workspaceRoot });
11
+ const sourceFiles = uniqueSorted(sourceChanges.map((change) => change.file));
12
+ const manifestResults = readImpactManifestResults(workspaceRoot);
13
+ const manifestLookup = createImpactManifestLookup(manifestResults);
14
+ const symbolsByFile = new Map();
15
+ const sourceContentByFile = new Map();
16
+ for (const change of sourceChanges) {
17
+ sourceContentByFile.set(change.file, change.content);
18
+ symbolsByFile.set(change.file, extractSymbolsFromStagedFile({
19
+ path: change.file,
20
+ status: change.status,
21
+ hunkRanges: [...change.hunkRanges],
22
+ content: change.content,
23
+ }, manifestLookup).filter((symbol) => symbol.hunkRanges.length > 0));
24
+ }
25
+ const activeSourceFiles = new Set(sourceFiles);
26
+ const activeManifestResults = manifestResults.filter((result) => result.sourceFile !== undefined &&
27
+ activeSourceFiles.has(result.sourceFile));
28
+ const impactDiagnostics = options.includeImpactDiagnostics === false
29
+ ? []
30
+ : [
31
+ ...createSymbolGranularityDiagnostics({
32
+ manifestResults: activeManifestResults,
33
+ symbolsByFile,
34
+ sourceContentByFile,
35
+ workspaceRoot,
36
+ }),
37
+ ...createSymbolQualityDiagnostics({
38
+ manifestResults: activeManifestResults,
39
+ symbolsByFile,
40
+ }),
41
+ ...createSemanticReviewDiagnostics({ symbolsByFile }),
42
+ ];
43
+ const maxDiagnostics = options.maxDiagnostics;
44
+ const cappedDiagnostics = maxDiagnostics !== undefined && maxDiagnostics >= 0
45
+ ? impactDiagnostics.slice(0, maxDiagnostics)
46
+ : impactDiagnostics;
47
+ return {
48
+ impactDiagnostics: cappedDiagnostics,
49
+ sourceFiles,
50
+ extractedSymbols: formatExtractedSymbols(symbolsByFile),
51
+ linkedEntities: collectLinkedEntities(symbolsByFile, manifestResults, activeSourceFiles),
52
+ nextActions: buildNextActions(sourceFiles),
53
+ };
54
+ }
@@ -0,0 +1,4 @@
1
+ import type { ExtractionResult } from "../../extractors/markdown.js";
2
+ import type { QualityDiagnostic } from "./types.js";
3
+ export declare function createCoverageDepthQualityDiagnostics(manifestResults: readonly ExtractionResult[]): readonly QualityDiagnostic[];
4
+ //# sourceMappingURL=coverage-depth-quality.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coverage-depth-quality.d.ts","sourceRoot":"","sources":["../../../src/public/impact/coverage-depth-quality.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,gBAAgB,EACjB,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAwPpD,wBAAgB,qCAAqC,CACnD,eAAe,EAAE,SAAS,gBAAgB,EAAE,GAC3C,SAAS,iBAAiB,EAAE,CAa9B"}
@@ -0,0 +1,179 @@
1
+ function entitiesById(manifestResults) {
2
+ return new Map(manifestResults.map((result) => [result.entity.id, result.entity]));
3
+ }
4
+ function outgoingTargets(lookup) {
5
+ return lookup.result.relationships.flatMap((relationship) => {
6
+ if (relationship.type !== lookup.relationshipType)
7
+ return [];
8
+ const target = lookup.entities.get(relationship.to);
9
+ return target?.type === lookup.type ? [target] : [];
10
+ });
11
+ }
12
+ function incomingSources(lookup) {
13
+ return lookup.manifestResults.flatMap((result) => {
14
+ if (result.entity.type !== lookup.type)
15
+ return [];
16
+ return result.relationships.some((relationship) => relationship.type === lookup.relationshipType &&
17
+ relationship.to === lookup.targetId)
18
+ ? [result.entity]
19
+ : [];
20
+ });
21
+ }
22
+ function uniqueEntities(entities) {
23
+ return [
24
+ ...new Map(entities.map((entity) => [entity.id, entity])).values(),
25
+ ].toSorted((left, right) => left.id.localeCompare(right.id));
26
+ }
27
+ function testScope(test) {
28
+ if (test.verification_scope !== undefined)
29
+ return test.verification_scope;
30
+ if (test.tags?.some((tag) => tag.toLowerCase() === "e2e") === true) {
31
+ return "end_to_end";
32
+ }
33
+ return test.source.toLowerCase().includes("e2e") ? "end_to_end" : "unknown";
34
+ }
35
+ function passingTests(tests) {
36
+ return tests.filter((test) => test.status === "passing");
37
+ }
38
+ function classifyCoverageDepth(coverage) {
39
+ const allTests = uniqueEntities([
40
+ ...coverage.directTests,
41
+ ...coverage.scenarioTests,
42
+ ]);
43
+ const directPassingTests = passingTests(coverage.directTests);
44
+ if (directPassingTests.some((test) => testScope(test) === "end_to_end")) {
45
+ return "direct_passing_e2e";
46
+ }
47
+ if (directPassingTests.some((test) => testScope(test) === "integration")) {
48
+ return "direct_passing_integration";
49
+ }
50
+ const scenarioPassingTests = passingTests(coverage.scenarioTests);
51
+ if (scenarioPassingTests.some((test) => testScope(test) === "end_to_end")) {
52
+ return "scenario_passing_e2e";
53
+ }
54
+ if (scenarioPassingTests.some((test) => testScope(test) === "integration")) {
55
+ return "scenario_passing_integration";
56
+ }
57
+ const allPassingTests = passingTests(allTests);
58
+ if (allPassingTests.length > 0 &&
59
+ allPassingTests.every((test) => testScope(test) === "unit")) {
60
+ return "unit_only";
61
+ }
62
+ if (allTests.length > 0)
63
+ return "open_or_nonpassing_tests_only";
64
+ if (coverage.scenarioCount > 0)
65
+ return "scenario_only_no_test";
66
+ return "no_test_evidence";
67
+ }
68
+ function isReviewableCoverageDepth(coverageDepth) {
69
+ return (coverageDepth === "unit_only" ||
70
+ coverageDepth === "open_or_nonpassing_tests_only" ||
71
+ coverageDepth === "scenario_only_no_test" ||
72
+ coverageDepth === "no_test_evidence");
73
+ }
74
+ function uniqueStrings(values) {
75
+ return [...new Set(values)].toSorted((left, right) => left.localeCompare(right));
76
+ }
77
+ function coverageDepthSuggestion(coverageDepth) {
78
+ if (coverageDepth === "unit_only") {
79
+ return "Keep unit coverage where appropriate; add or link a passing end-to-end or scenario-backed test when this behavior warrants e2e confidence.";
80
+ }
81
+ return "Add or link passing tests, preferably end-to-end or scenario-backed when this behavior warrants e2e confidence.";
82
+ }
83
+ function createDiagnostic(coverage) {
84
+ const coverageDepth = classifyCoverageDepth(coverage);
85
+ if (!isReviewableCoverageDepth(coverageDepth))
86
+ return undefined;
87
+ const allTests = uniqueEntities([
88
+ ...coverage.directTests,
89
+ ...coverage.scenarioTests,
90
+ ]);
91
+ return {
92
+ id: "coverage_depth_review",
93
+ severity: "review",
94
+ blocking: false,
95
+ category: "coverage",
96
+ entityId: coverage.requirement.id,
97
+ source: coverage.requirement.source,
98
+ files: [coverage.requirement.source],
99
+ message: `Requirement ${coverage.requirement.id} (${coverage.requirement.title}) has ${coverageDepth} coverage depth and may need stronger behavior-level evidence.`,
100
+ suggestion: coverageDepthSuggestion(coverageDepth),
101
+ evidence: {
102
+ requirementId: coverage.requirement.id,
103
+ title: coverage.requirement.title,
104
+ source: coverage.requirement.source,
105
+ coverageDepth,
106
+ directTests: coverage.directTests.map((test) => test.id),
107
+ scenarioTests: coverage.scenarioTests.map((test) => test.id),
108
+ testStatuses: uniqueStrings(allTests.map((test) => test.status)),
109
+ verificationScopes: uniqueStrings(allTests.map(testScope)),
110
+ },
111
+ };
112
+ }
113
+ function coverageForRequirement(result, manifestResults, entities) {
114
+ const resultsById = new Map(manifestResults.map((manifestResult) => [
115
+ manifestResult.entity.id,
116
+ manifestResult,
117
+ ]));
118
+ const scenarios = outgoingTargets({
119
+ result,
120
+ relationshipType: "specified_by",
121
+ type: "scenario",
122
+ entities,
123
+ });
124
+ const directTests = uniqueEntities([
125
+ ...outgoingTargets({
126
+ result,
127
+ relationshipType: "verified_by",
128
+ type: "test",
129
+ entities,
130
+ }),
131
+ ...outgoingTargets({
132
+ result,
133
+ relationshipType: "covered_by",
134
+ type: "test",
135
+ entities,
136
+ }),
137
+ ...incomingSources({
138
+ manifestResults,
139
+ targetId: result.entity.id,
140
+ relationshipType: "validates",
141
+ type: "test",
142
+ }),
143
+ ]);
144
+ const scenarioTests = uniqueEntities(scenarios.flatMap((scenario) => {
145
+ const scenarioResult = resultsById.get(scenario.id);
146
+ return [
147
+ ...incomingSources({
148
+ manifestResults,
149
+ targetId: scenario.id,
150
+ relationshipType: "validates",
151
+ type: "test",
152
+ }),
153
+ ...(scenarioResult === undefined
154
+ ? []
155
+ : outgoingTargets({
156
+ result: scenarioResult,
157
+ relationshipType: "verified_by",
158
+ type: "test",
159
+ entities,
160
+ })),
161
+ ];
162
+ }));
163
+ return {
164
+ requirement: result.entity,
165
+ scenarioCount: scenarios.length,
166
+ directTests,
167
+ scenarioTests,
168
+ };
169
+ }
170
+ export function createCoverageDepthQualityDiagnostics(manifestResults) {
171
+ const entities = entitiesById(manifestResults);
172
+ return manifestResults
173
+ .filter((result) => result.entity.type === "req")
174
+ .flatMap((result) => {
175
+ const diagnostic = createDiagnostic(coverageForRequirement(result, manifestResults, entities));
176
+ return diagnostic === undefined ? [] : [diagnostic];
177
+ })
178
+ .toSorted((left, right) => (left.entityId ?? "").localeCompare(right.entityId ?? ""));
179
+ }
@@ -0,0 +1,7 @@
1
+ import type { KibiImpactDiagnostic } from "../../traceability/staged-diagnostics.js";
2
+ import type { QualityDiagnostic, SemanticReviewDiagnosticsOptions, SymbolGranularityDiagnosticsOptions } from "./types.js";
3
+ export declare function createSymbolGranularityDiagnostics(options: SymbolGranularityDiagnosticsOptions): KibiImpactDiagnostic[];
4
+ export declare function createSemanticReviewDiagnostics(options: SemanticReviewDiagnosticsOptions): KibiImpactDiagnostic[];
5
+ export declare function hasBlockingImpactDiagnostics(diagnostics: readonly KibiImpactDiagnostic[]): boolean;
6
+ export declare function hasBlockingQualityDiagnostics(diagnostics: readonly QualityDiagnostic[]): boolean;
7
+ //# sourceMappingURL=diagnostics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diagnostics.d.ts","sourceRoot":"","sources":["../../../src/public/impact/diagnostics.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,0CAA0C,CAAC;AAWrF,OAAO,KAAK,EACV,iBAAiB,EACjB,gCAAgC,EAChC,mCAAmC,EACpC,MAAM,YAAY,CAAC;AAoFpB,wBAAgB,kCAAkC,CAChD,OAAO,EAAE,mCAAmC,GAC3C,oBAAoB,EAAE,CA+CxB;AAED,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,gCAAgC,GACxC,oBAAoB,EAAE,CAsBxB;AAED,wBAAgB,4BAA4B,CAC1C,WAAW,EAAE,SAAS,oBAAoB,EAAE,GAC3C,OAAO,CAET;AAED,wBAAgB,6BAA6B,CAC3C,WAAW,EAAE,SAAS,iBAAiB,EAAE,GACxC,OAAO,CAIT"}
@@ -0,0 +1,122 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import * as path from "node:path";
3
+ import { extractSymbolsFromStagedFile, } from "../../traceability/symbol-extract.js";
4
+ import { getBehavioralSymbolNames, getNonBehavioralSymbolNames, isAllowedGranularityReason, isTraceabilityRelationshipType, } from "../symbol-granularity.js";
5
+ function isNarrowerSymbol(manifestSymbolName, candidate, sourceSymbols) {
6
+ if (candidate.name === manifestSymbolName)
7
+ return false;
8
+ if (candidate.name.startsWith(`${manifestSymbolName}.`))
9
+ return true;
10
+ return !sourceSymbols.some((symbol) => symbol.name === manifestSymbolName);
11
+ }
12
+ function hasTraceabilityRelationship(result) {
13
+ return result.relationships.some((relationship) => isTraceabilityRelationshipType(relationship.type));
14
+ }
15
+ function hasValidGranularityReason(result) {
16
+ return isAllowedGranularityReason(result.entity.granularity_reason);
17
+ }
18
+ function readSourceSymbols(sourceFile, sourceContentByFile, workspaceRoot) {
19
+ const stagedContent = sourceContentByFile?.get(sourceFile);
20
+ if (stagedContent !== undefined) {
21
+ return extractSymbolsFromStagedFile({
22
+ path: sourceFile,
23
+ status: "M",
24
+ hunkRanges: [{ start: 1, end: Number.MAX_SAFE_INTEGER }],
25
+ content: stagedContent,
26
+ });
27
+ }
28
+ const absolutePath = path.isAbsolute(sourceFile)
29
+ ? sourceFile
30
+ : path.resolve(workspaceRoot, sourceFile);
31
+ if (!existsSync(absolutePath))
32
+ return [];
33
+ return extractSymbolsFromStagedFile({
34
+ path: sourceFile,
35
+ status: "M",
36
+ hunkRanges: [{ start: 1, end: Number.MAX_SAFE_INTEGER }],
37
+ content: readFileSync(absolutePath, "utf8"),
38
+ });
39
+ }
40
+ function getGranularSymbolsForSourceFile(sourceFile, options) {
41
+ return (options.symbolsByFile.get(sourceFile) ??
42
+ readSourceSymbols(sourceFile, options.sourceContentByFile, options.workspaceRoot ?? process.cwd()));
43
+ }
44
+ function formatRelationshipTargets(symbol) {
45
+ const targets = [
46
+ ...new Set((symbol.relationships ?? [])
47
+ .filter((relationship) => isTraceabilityRelationshipType(relationship.type))
48
+ .map((relationship) => relationship.to)),
49
+ ].sort();
50
+ return targets.length > 0 ? targets.join(", ") : "linked requirements/tests";
51
+ }
52
+ function isChangedBehavioralReviewCandidate(symbol) {
53
+ if (symbol.role !== "behavioral")
54
+ return false;
55
+ if (symbol.kind === "class")
56
+ return false;
57
+ return symbol.hunkRanges.length > 0;
58
+ }
59
+ export function createSymbolGranularityDiagnostics(options) {
60
+ const diagnostics = [];
61
+ for (const result of options.manifestResults) {
62
+ if (options.activeEntityIds !== undefined &&
63
+ !options.activeEntityIds.has(result.entity.id)) {
64
+ continue;
65
+ }
66
+ if (!result.sourceFile)
67
+ continue;
68
+ if (!hasTraceabilityRelationship(result))
69
+ continue;
70
+ if (hasValidGranularityReason(result))
71
+ continue;
72
+ const granularSymbols = getGranularSymbolsForSourceFile(result.sourceFile, options);
73
+ const narrowerSymbols = granularSymbols.filter((symbol) => isNarrowerSymbol(result.entity.title, symbol, granularSymbols));
74
+ if (narrowerSymbols.length === 0)
75
+ continue;
76
+ const behavioralNames = getBehavioralSymbolNames(narrowerSymbols);
77
+ if (behavioralNames.length === 0)
78
+ continue;
79
+ const nonBehavioralNames = getNonBehavioralSymbolNames(narrowerSymbols);
80
+ const ignoredSymbolsSuggestion = nonBehavioralNames.length > 0
81
+ ? ` Non-behavioral symbols ignored for this decision: ${nonBehavioralNames.join(", ")}.`
82
+ : "";
83
+ diagnostics.push({
84
+ id: "symbol_granularity_violation",
85
+ severity: "error",
86
+ blocking: true,
87
+ category: "symbol",
88
+ files: [result.entity.source, result.sourceFile],
89
+ docs: ["docs/symbol-traceability-taxonomy.md"],
90
+ message: `Symbol ${result.entity.id} links ${result.sourceFile} coarsely while granular symbols are available (behavioral only): ${behavioralNames.join(", ")}`,
91
+ 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}`,
92
+ });
93
+ }
94
+ return diagnostics;
95
+ }
96
+ export function createSemanticReviewDiagnostics(options) {
97
+ const diagnostics = [];
98
+ for (const symbols of options.symbolsByFile.values()) {
99
+ for (const symbol of symbols) {
100
+ if (!isChangedBehavioralReviewCandidate(symbol))
101
+ continue;
102
+ const linkedTargets = formatRelationshipTargets(symbol);
103
+ diagnostics.push({
104
+ id: "symbol_semantic_review_needed",
105
+ severity: "warning",
106
+ blocking: false,
107
+ category: "symbol",
108
+ files: [symbol.location.file],
109
+ docs: ["docs/symbol-traceability-taxonomy.md"],
110
+ message: `Changed behavioral symbol ${symbol.name} needs semantic review against linked Kibi coverage: ${linkedTargets}`,
111
+ suggestion: `Use kb_search and kb_query for ${symbol.location.file}, then verify that ${linkedTargets} actually describe and test the changed behavior or UI copy. Update Kibi links or requirements/tests while the edit context is fresh.`,
112
+ });
113
+ }
114
+ }
115
+ return diagnostics;
116
+ }
117
+ export function hasBlockingImpactDiagnostics(diagnostics) {
118
+ return hasBlockingQualityDiagnostics(diagnostics);
119
+ }
120
+ export function hasBlockingQualityDiagnostics(diagnostics) {
121
+ return diagnostics.some((diagnostic) => diagnostic.blocking || diagnostic.severity === "error");
122
+ }
@@ -0,0 +1,2 @@
1
+ export declare function hasMeaningfulSourceDiff(diffText: string): boolean;
2
+ //# sourceMappingURL=diff-meaning.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff-meaning.d.ts","sourceRoot":"","sources":["../../../src/public/impact/diff-meaning.ts"],"names":[],"mappings":"AAyGA,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAejE"}
@@ -0,0 +1,101 @@
1
+ function normalizeDiffContent(line) {
2
+ let normalized = "";
3
+ let quote = null;
4
+ let escaped = false;
5
+ for (const character of line.slice(1)) {
6
+ if (quote !== null) {
7
+ normalized += character;
8
+ if (escaped) {
9
+ escaped = false;
10
+ continue;
11
+ }
12
+ if (character === "\\") {
13
+ escaped = true;
14
+ continue;
15
+ }
16
+ if (character === quote)
17
+ quote = null;
18
+ continue;
19
+ }
20
+ if (character === '"' || character === "'" || character === "`") {
21
+ quote = character;
22
+ normalized += character;
23
+ continue;
24
+ }
25
+ if (!/\s/.test(character))
26
+ normalized += character;
27
+ }
28
+ return normalized;
29
+ }
30
+ function removeSyntacticTrailingCommas(content) {
31
+ let normalized = "";
32
+ let quote = null;
33
+ let escaped = false;
34
+ for (let index = 0; index < content.length; index += 1) {
35
+ const character = content[index];
36
+ if (character === undefined)
37
+ continue;
38
+ if (quote !== null) {
39
+ normalized += character;
40
+ if (escaped) {
41
+ escaped = false;
42
+ continue;
43
+ }
44
+ if (character === "\\") {
45
+ escaped = true;
46
+ continue;
47
+ }
48
+ if (character === quote)
49
+ quote = null;
50
+ continue;
51
+ }
52
+ if (character === '"' || character === "'" || character === "`") {
53
+ quote = character;
54
+ normalized += character;
55
+ continue;
56
+ }
57
+ const nextCharacter = content[index + 1];
58
+ if (character === "," &&
59
+ (nextCharacter === ")" || nextCharacter === "]" || nextCharacter === "}")) {
60
+ continue;
61
+ }
62
+ normalized += character;
63
+ }
64
+ return normalized;
65
+ }
66
+ function hasMeaningfulDiffHunk(diffLines) {
67
+ let removedContent = "";
68
+ let addedContent = "";
69
+ for (const line of diffLines) {
70
+ if (line.startsWith("-") && !line.startsWith("---")) {
71
+ removedContent += normalizeDiffContent(line);
72
+ continue;
73
+ }
74
+ if (line.startsWith("+") && !line.startsWith("+++")) {
75
+ addedContent += normalizeDiffContent(line);
76
+ }
77
+ }
78
+ const normalizedRemovedContent = removeSyntacticTrailingCommas(removedContent);
79
+ const normalizedAddedContent = removeSyntacticTrailingCommas(addedContent);
80
+ if (normalizedRemovedContent.length === 0 &&
81
+ normalizedAddedContent.length === 0) {
82
+ return false;
83
+ }
84
+ return normalizedRemovedContent !== normalizedAddedContent;
85
+ }
86
+ // implements REQ-cli-staged-impact-enforcement
87
+ export function hasMeaningfulSourceDiff(diffText) {
88
+ const hunkLines = [];
89
+ for (const line of diffText.split(/\r?\n/)) {
90
+ if (line.startsWith("@@")) {
91
+ if (hasMeaningfulDiffHunk(hunkLines))
92
+ return true;
93
+ hunkLines.length = 0;
94
+ continue;
95
+ }
96
+ if (hunkLines.length > 0 || line.startsWith("+") || line.startsWith("-")) {
97
+ hunkLines.push(line);
98
+ }
99
+ }
100
+ return hasMeaningfulDiffHunk(hunkLines);
101
+ }
@@ -0,0 +1,10 @@
1
+ import type { PrologProcess } from "../../prolog.js";
2
+ import type { QualityDiagnostic } from "./types.js";
3
+ type FullKbQualityDiagnosticsOptions = {
4
+ readonly prolog: PrologProcess;
5
+ readonly hardViolationEntityIds?: ReadonlySet<string>;
6
+ readonly maxDiagnostics?: number;
7
+ };
8
+ export declare function collectFullKbQualityDiagnostics(options: FullKbQualityDiagnosticsOptions): Promise<readonly QualityDiagnostic[]>;
9
+ export {};
10
+ //# sourceMappingURL=full-kb-quality.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"full-kb-quality.d.ts","sourceRoot":"","sources":["../../../src/public/impact/full-kb-quality.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AASrD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAqBpD,KAAK,+BAA+B,GAAG;IACrC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,sBAAsB,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACtD,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CAClC,CAAC;AA+IF,wBAAsB,+BAA+B,CACnD,OAAO,EAAE,+BAA+B,GACvC,OAAO,CAAC,SAAS,iBAAiB,EAAE,CAAC,CAmBvC"}
@@ -0,0 +1,158 @@
1
+ import { parseEntityFromList, parseListOfLists, parseTriples, } from "../../prolog/codec.js";
2
+ import { createCoverageDepthQualityDiagnostics } from "./coverage-depth-quality.js";
3
+ import { createRequirementQualityDiagnostics } from "./requirement-quality.js";
4
+ import { createSymbolQualityDiagnostics } from "./symbol-quality.js";
5
+ const RELATIONSHIP_TYPES = [
6
+ "depends_on",
7
+ "executable_for",
8
+ "specified_by",
9
+ "verified_by",
10
+ "validates",
11
+ "implements",
12
+ "covered_by",
13
+ "constrained_by",
14
+ "constrains",
15
+ "requires_property",
16
+ "requires_predicate",
17
+ "guards",
18
+ "publishes",
19
+ "consumes",
20
+ "supersedes",
21
+ "relates_to",
22
+ ];
23
+ function stringField(entity, key) {
24
+ const value = entity[key];
25
+ return typeof value === "string" ? value : "";
26
+ }
27
+ function optionalStringField(entity, key) {
28
+ const value = entity[key];
29
+ return typeof value === "string" && value.length > 0 ? value : undefined;
30
+ }
31
+ function optionalNumberField(entity, key) {
32
+ const value = entity[key];
33
+ return typeof value === "number" ? value : undefined;
34
+ }
35
+ function optionalStringArrayField(entity, key) {
36
+ const value = entity[key];
37
+ if (!Array.isArray(value))
38
+ return undefined;
39
+ return value.map((item) => String(item));
40
+ }
41
+ function toExtractedEntity(entity) {
42
+ const extracted = {
43
+ id: stringField(entity, "id"),
44
+ type: stringField(entity, "type"),
45
+ title: stringField(entity, "title"),
46
+ status: stringField(entity, "status"),
47
+ created_at: stringField(entity, "created_at"),
48
+ updated_at: stringField(entity, "updated_at"),
49
+ source: stringField(entity, "source"),
50
+ };
51
+ const tags = optionalStringArrayField(entity, "tags");
52
+ if (tags !== undefined)
53
+ extracted.tags = tags;
54
+ const owner = optionalStringField(entity, "owner");
55
+ if (owner !== undefined)
56
+ extracted.owner = owner;
57
+ const priority = optionalStringField(entity, "priority");
58
+ if (priority !== undefined)
59
+ extracted.priority = priority;
60
+ const severity = optionalStringField(entity, "severity");
61
+ if (severity !== undefined)
62
+ extracted.severity = severity;
63
+ const textRef = optionalStringField(entity, "text_ref");
64
+ if (textRef !== undefined)
65
+ extracted.text_ref = textRef;
66
+ const granularityReason = optionalStringField(entity, "granularity_reason");
67
+ if (granularityReason !== undefined) {
68
+ extracted.granularity_reason = granularityReason;
69
+ }
70
+ const symbolKind = optionalStringField(entity, "symbol_kind");
71
+ if (symbolKind !== undefined)
72
+ extracted.symbol_kind = symbolKind;
73
+ const symbolRole = optionalStringField(entity, "symbol_role");
74
+ if (symbolRole !== undefined)
75
+ extracted.symbol_role = symbolRole;
76
+ const verificationScope = optionalStringField(entity, "verification_scope");
77
+ if (verificationScope === "unit" ||
78
+ verificationScope === "integration" ||
79
+ verificationScope === "end_to_end") {
80
+ extracted.verification_scope = verificationScope;
81
+ }
82
+ const sourceLine = optionalNumberField(entity, "sourceLine");
83
+ if (sourceLine !== undefined)
84
+ extracted.sourceLine = sourceLine;
85
+ const sourceColumn = optionalNumberField(entity, "sourceColumn");
86
+ if (sourceColumn !== undefined)
87
+ extracted.sourceColumn = sourceColumn;
88
+ const sourceEndLine = optionalNumberField(entity, "sourceEndLine");
89
+ if (sourceEndLine !== undefined)
90
+ extracted.sourceEndLine = sourceEndLine;
91
+ const sourceEndColumn = optionalNumberField(entity, "sourceEndColumn");
92
+ if (sourceEndColumn !== undefined)
93
+ extracted.sourceEndColumn = sourceEndColumn;
94
+ const factKind = optionalStringField(entity, "fact_kind");
95
+ if (factKind === "subject" ||
96
+ factKind === "property_value" ||
97
+ factKind === "observation" ||
98
+ factKind === "meta") {
99
+ extracted.fact_kind = factKind;
100
+ }
101
+ return extracted;
102
+ }
103
+ function sourceFileFor(entity) {
104
+ return (optionalStringField(entity, "sourceFile") ??
105
+ optionalStringField(entity, "source_file"));
106
+ }
107
+ async function loadKbExtractionResults(prolog) {
108
+ const entityResult = await prolog.query("findall([Id,Type,Props], kb_entity(Id, Type, Props), Results)");
109
+ const entities = entityResult.bindings.Results
110
+ ? parseListOfLists(entityResult.bindings.Results).map(parseEntityFromList)
111
+ : [];
112
+ const relationships = new Map();
113
+ for (const relationshipType of RELATIONSHIP_TYPES) {
114
+ const relResult = await prolog.query(`findall([From,To,'${relationshipType}'], kb_relationship(${relationshipType}, From, To), Rels)`);
115
+ const rows = relResult.bindings.Rels
116
+ ? parseTriples(relResult.bindings.Rels)
117
+ : [];
118
+ for (const [from, to, type] of rows) {
119
+ const current = relationships.get(from) ?? [];
120
+ current.push({ from, to, type });
121
+ relationships.set(from, current);
122
+ }
123
+ }
124
+ return entities.map((entity) => {
125
+ const result = {
126
+ entity: toExtractedEntity(entity),
127
+ relationships: relationships.get(stringField(entity, "id")) ?? [],
128
+ };
129
+ const sourceFile = sourceFileFor(entity);
130
+ if (sourceFile !== undefined) {
131
+ result.sourceFile = sourceFile;
132
+ }
133
+ return result;
134
+ });
135
+ }
136
+ function capDiagnostics(diagnostics, maxDiagnostics) {
137
+ return maxDiagnostics !== undefined && maxDiagnostics >= 0
138
+ ? diagnostics.slice(0, maxDiagnostics)
139
+ : diagnostics;
140
+ }
141
+ export async function collectFullKbQualityDiagnostics(options) {
142
+ const manifestResults = await loadKbExtractionResults(options.prolog);
143
+ const coverageDepthDiagnostics = createCoverageDepthQualityDiagnostics(manifestResults);
144
+ const diagnostics = [
145
+ ...createRequirementQualityDiagnostics({
146
+ manifestResults,
147
+ ...(options.hardViolationEntityIds !== undefined
148
+ ? { hardViolationEntityIds: options.hardViolationEntityIds }
149
+ : {}),
150
+ }),
151
+ ...coverageDepthDiagnostics,
152
+ ...createSymbolQualityDiagnostics({
153
+ manifestResults,
154
+ symbolsByFile: new Map(),
155
+ }),
156
+ ];
157
+ return capDiagnostics(diagnostics, options.maxDiagnostics);
158
+ }
@@ -0,0 +1,5 @@
1
+ import type { ExtractionResult } from "../../extractors/markdown.js";
2
+ import type { ManifestLookup } from "../../traceability/symbol-extract.js";
3
+ export declare function readImpactManifestResults(workspaceRoot: string): readonly ExtractionResult[];
4
+ export declare function createImpactManifestLookup(manifestResults: readonly ExtractionResult[]): ManifestLookup;
5
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../../src/public/impact/manifest.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AAc3E,wBAAgB,yBAAyB,CACvC,aAAa,EAAE,MAAM,GACpB,SAAS,gBAAgB,EAAE,CAoB7B;AAED,wBAAgB,0BAA0B,CACxC,eAAe,EAAE,SAAS,gBAAgB,EAAE,GAC3C,cAAc,CAYhB"}