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
|
@@ -143,9 +143,31 @@ function normalizeAuthoredManifestSymbolsForSourceFile(records, sourceFile) {
|
|
|
143
143
|
priority: typeof record.priority === "string" ? record.priority : null,
|
|
144
144
|
severity: typeof record.severity === "string" ? record.severity : null,
|
|
145
145
|
textRef: typeof record.text_ref === "string" ? record.text_ref : null,
|
|
146
|
+
granularityReason: typeof record.granularity_reason === "string"
|
|
147
|
+
? record.granularity_reason
|
|
148
|
+
: null,
|
|
146
149
|
}))
|
|
147
150
|
.sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right)));
|
|
148
151
|
}
|
|
152
|
+
function getAuthoredSymbolComparisonKey(symbol) {
|
|
153
|
+
return symbol.id ?? `${symbol.sourceFile}:${symbol.title}`;
|
|
154
|
+
}
|
|
155
|
+
function getChangedAuthoredEntityIds(headSymbols, stagedSymbols) {
|
|
156
|
+
const headSignatures = new Map(headSymbols.map((symbol) => [
|
|
157
|
+
getAuthoredSymbolComparisonKey(symbol),
|
|
158
|
+
JSON.stringify(symbol),
|
|
159
|
+
]));
|
|
160
|
+
const changedIds = stagedSymbols.flatMap((symbol) => {
|
|
161
|
+
if (symbol.id === null)
|
|
162
|
+
return [];
|
|
163
|
+
const stagedSignature = JSON.stringify(symbol);
|
|
164
|
+
return headSignatures.get(getAuthoredSymbolComparisonKey(symbol)) ===
|
|
165
|
+
stagedSignature
|
|
166
|
+
? []
|
|
167
|
+
: [symbol.id];
|
|
168
|
+
});
|
|
169
|
+
return uniqueSorted(changedIds);
|
|
170
|
+
}
|
|
149
171
|
function normalizeExpectedSymbolsForStagedFile(stagedFile) {
|
|
150
172
|
const analysis = analyzeSourceText(stagedFile.path, stagedFile.content ?? "");
|
|
151
173
|
return analysis.symbols
|
|
@@ -268,7 +290,7 @@ export function collectStagedAuthoredSymbolsManifestEvidence(options) {
|
|
|
268
290
|
}
|
|
269
291
|
entries.push({
|
|
270
292
|
sourcePath: sourceFile.path,
|
|
271
|
-
entityIds:
|
|
293
|
+
entityIds: getChangedAuthoredEntityIds(headSymbols, stagedSymbols),
|
|
272
294
|
});
|
|
273
295
|
}
|
|
274
296
|
return { path: paths.symbolsPath, entries };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"symbol-extract.d.ts","sourceRoot":"","sources":["../../src/traceability/symbol-extract.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,UAAU,EACf,KAAK,UAAU,EAEhB,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7D,KAAK,wBAAwB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAU7D,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,UAAU,CAAC;IACjB,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,aAAa,CAAC,EAAE,wBAAwB,EAAE,CAAC;CAC5C;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,CAAC,EAAE,wBAAwB,EAAE,CAAC;CAC5C;AAED,MAAM,MAAM,cAAc,GAAG,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;AAE9D,wBAAgB,+BAA+B,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAG5E;AAmOD,wBAAgB,4BAA4B,CAE1C,UAAU,EAAE,UAAU,EACtB,cAAc,CAAC,EAAE,cAAc,GAC9B,eAAe,EAAE,
|
|
1
|
+
{"version":3,"file":"symbol-extract.d.ts","sourceRoot":"","sources":["../../src/traceability/symbol-extract.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,UAAU,EACf,KAAK,UAAU,EAEhB,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7D,KAAK,wBAAwB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAU7D,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,UAAU,CAAC;IACjB,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,aAAa,CAAC,EAAE,wBAAwB,EAAE,CAAC;CAC5C;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,CAAC,EAAE,wBAAwB,EAAE,CAAC;CAC5C;AAED,MAAM,MAAM,cAAc,GAAG,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;AAE9D,wBAAgB,+BAA+B,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAG5E;AAmOD,wBAAgB,4BAA4B,CAE1C,UAAU,EAAE,UAAU,EACtB,cAAc,CAAC,EAAE,cAAc,GAC9B,eAAe,EAAE,CAwSnB"}
|
|
@@ -255,6 +255,47 @@ stagedFile, manifestLookup) {
|
|
|
255
255
|
void stagedFile.path;
|
|
256
256
|
}
|
|
257
257
|
}
|
|
258
|
+
for (const property of typeof cls.getProperties === "function"
|
|
259
|
+
? cls.getProperties()
|
|
260
|
+
: []) {
|
|
261
|
+
try {
|
|
262
|
+
const name = formatMethodSymbolName(cls.getName(), property.getName());
|
|
263
|
+
const start = property.getNameNode()?.getStart() ?? property.getStart();
|
|
264
|
+
const end = property.getEnd();
|
|
265
|
+
const span = getSpan(start, end);
|
|
266
|
+
const reqLinks = parseReqDirectives(`${property.getFullText()}\n${property
|
|
267
|
+
.getJsDocs()
|
|
268
|
+
.map((d) => d.getFullText())
|
|
269
|
+
.join("\n")}`);
|
|
270
|
+
results.push(buildSymbolResult(stagedFile, name, "property", span, reqLinks, manifestLookup));
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
void stagedFile.path;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
for (const accessor of [
|
|
277
|
+
...(typeof cls.getGetAccessors === "function"
|
|
278
|
+
? cls.getGetAccessors()
|
|
279
|
+
: []),
|
|
280
|
+
...(typeof cls.getSetAccessors === "function"
|
|
281
|
+
? cls.getSetAccessors()
|
|
282
|
+
: []),
|
|
283
|
+
]) {
|
|
284
|
+
try {
|
|
285
|
+
const name = formatMethodSymbolName(cls.getName(), accessor.getName());
|
|
286
|
+
const start = accessor.getNameNode()?.getStart() ?? accessor.getStart();
|
|
287
|
+
const end = accessor.getEnd();
|
|
288
|
+
const span = getSpan(start, end);
|
|
289
|
+
const reqLinks = parseReqDirectives(`${accessor.getFullText()}\n${accessor
|
|
290
|
+
.getJsDocs()
|
|
291
|
+
.map((d) => d.getFullText())
|
|
292
|
+
.join("\n")}`);
|
|
293
|
+
results.push(buildSymbolResult(stagedFile, name, "accessor", span, reqLinks, manifestLookup));
|
|
294
|
+
}
|
|
295
|
+
catch {
|
|
296
|
+
void stagedFile.path;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
258
299
|
}
|
|
259
300
|
// Enums
|
|
260
301
|
for (const en of sf.getEnums()) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"temp-kb.d.ts","sourceRoot":"","sources":["../../src/traceability/temp-kb.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAGV,gBAAgB,EACjB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAExD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,aAAa,CAAC;CACvB;AAMD;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CASvC;AAOD;;;GAGG;AACH,wBAAgB,iBAAiB,CAE/B,OAAO,EAAE,CAAC,IAAI,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,KAAK,aAAa,GACpD,IAAI,CAEN;AA4ID,iBAAe,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB/D;AAED,OAAO,EAAE,cAAc,EAAE,CAAC;AAG1B,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,gBAAgB,EAAE,GAC1B,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"temp-kb.d.ts","sourceRoot":"","sources":["../../src/traceability/temp-kb.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAGV,gBAAgB,EACjB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAExD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,aAAa,CAAC;CACvB;AAMD;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CASvC;AAOD;;;GAGG;AACH,wBAAgB,iBAAiB,CAE/B,OAAO,EAAE,CAAC,IAAI,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,KAAK,aAAa,GACpD,IAAI,CAEN;AA4ID,iBAAe,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB/D;AAED,OAAO,EAAE,cAAc,EAAE,CAAC;AAG1B,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,gBAAgB,EAAE,GAC1B,OAAO,CAAC,IAAI,CAAC,CA2Bf;AAED,wBAAsB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CA6C7E;AAGD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,eAAe,EAAE,GAAG,MAAM,CAkBrE;AAED,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA2BlE"}
|
package/dist/types/entities.d.ts
CHANGED
|
@@ -39,13 +39,17 @@ export interface FactFields {
|
|
|
39
39
|
examples?: string[];
|
|
40
40
|
predicate_args?: string[];
|
|
41
41
|
}
|
|
42
|
+
export interface TestVerificationFields {
|
|
43
|
+
verification_scope?: "unit" | "integration" | "end_to_end";
|
|
44
|
+
verification_perspective?: "internal" | "consumer";
|
|
45
|
+
}
|
|
42
46
|
export type Requirement = BaseEntity & {
|
|
43
47
|
type: "req";
|
|
44
48
|
};
|
|
45
49
|
export type Scenario = BaseEntity & {
|
|
46
50
|
type: "scenario";
|
|
47
51
|
};
|
|
48
|
-
export type TestEntity = BaseEntity & {
|
|
52
|
+
export type TestEntity = BaseEntity & TestVerificationFields & {
|
|
49
53
|
type: "test";
|
|
50
54
|
};
|
|
51
55
|
export type ADR = BaseEntity & {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entities.d.ts","sourceRoot":"","sources":["../../src/types/entities.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAGD,MAAM,WAAW,UAAU;IACzB,SAAS,CAAC,EACN,SAAS,GACT,gBAAgB,GAChB,aAAa,GACb,MAAM,GACN,kBAAkB,GAClB,WAAW,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,CAAC;IACtD,UAAU,CAAC,EAAE,QAAQ,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IAClD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAC;IACpD,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAC;IACjC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG;IAAE,IAAI,EAAE,KAAK,CAAA;CAAE,CAAC;AACvD,MAAM,MAAM,QAAQ,GAAG,UAAU,GAAG;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC;AACzD,MAAM,MAAM,UAAU,GAAG,UAAU,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"entities.d.ts","sourceRoot":"","sources":["../../src/types/entities.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface PrologQueryPlanSafetyViolation {
|
|
2
|
+
readonly predicate: string;
|
|
3
|
+
readonly line: number;
|
|
4
|
+
readonly description: string;
|
|
5
|
+
readonly suggestion: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function analyzePrologQueryPlanSafety(source: string): PrologQueryPlanSafetyViolation[];
|
|
8
|
+
//# sourceMappingURL=prolog-query-plan-safety.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prolog-query-plan-safety.d.ts","sourceRoot":"","sources":["../../src/utils/prolog-query-plan-safety.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,8BAA8B;IAC7C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAMD,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,MAAM,GACb,8BAA8B,EAAE,CAyBlC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const GENERATOR_PATTERN = /\b(?:kb_entity|kb_relationship|member|memberchk|findall|setof|bagof)\s*\(/;
|
|
2
|
+
const NEGATION_PATTERN = /\\\+\s*/;
|
|
3
|
+
export function analyzePrologQueryPlanSafety(source) {
|
|
4
|
+
const lines = source.split(/\r?\n/);
|
|
5
|
+
const violations = [];
|
|
6
|
+
let clauseStart = 0;
|
|
7
|
+
let predicate = "unknown";
|
|
8
|
+
let clauseLines = [];
|
|
9
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
10
|
+
const line = lines[index] ?? "";
|
|
11
|
+
if (clauseLines.length === 0 && line.trim().length === 0) {
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
if (clauseLines.length === 0) {
|
|
15
|
+
clauseStart = index + 1;
|
|
16
|
+
predicate = predicateNameFrom(line) ?? "unknown";
|
|
17
|
+
}
|
|
18
|
+
clauseLines.push(line);
|
|
19
|
+
if (line.trim().endsWith(".")) {
|
|
20
|
+
const violation = analyzeClause(predicate, clauseStart, clauseLines);
|
|
21
|
+
if (violation)
|
|
22
|
+
violations.push(violation);
|
|
23
|
+
clauseLines = [];
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return violations;
|
|
27
|
+
}
|
|
28
|
+
function predicateNameFrom(line) {
|
|
29
|
+
const match = line.match(/^\s*([a-z][a-zA-Z0-9_]*)\s*\(/);
|
|
30
|
+
return match?.[1] ?? null;
|
|
31
|
+
}
|
|
32
|
+
function analyzeClause(predicate, startLine, lines) {
|
|
33
|
+
const negationIndex = lines.findIndex((line) => NEGATION_PATTERN.test(line));
|
|
34
|
+
if (negationIndex < 0)
|
|
35
|
+
return null;
|
|
36
|
+
const hasLaterGenerator = lines
|
|
37
|
+
.slice(negationIndex + 1)
|
|
38
|
+
.some((line) => GENERATOR_PATTERN.test(line));
|
|
39
|
+
if (!hasLaterGenerator)
|
|
40
|
+
return null;
|
|
41
|
+
return {
|
|
42
|
+
predicate,
|
|
43
|
+
line: startLine + negationIndex,
|
|
44
|
+
description: "Negation appears before later generator calls in the same clause.",
|
|
45
|
+
suggestion: "Move kb_entity/kb_relationship/member/findall generators before \\+/1 so variables are bound before negation.",
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rule-registry.d.ts","sourceRoot":"","sources":["../../src/utils/rule-registry.ts"],"names":[],"mappings":"AAkBA;;;GAGG;AAEH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,OAAO,CAAC;IACxB,QAAQ,EAAE,UAAU,GAAG,WAAW,GAAG,WAAW,GAAG,cAAc,CAAC;CACnE;AAED,MAAM,WAAW,yBAAyB;IACxC,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,mCAAmC;AACnC,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,kBAAkB,EAAE,yBAAyB,CAAC;CAC/C;AAED;;;GAGG;AACH,eAAO,MAAM,KAAK,EAAE,SAAS,cAAc,
|
|
1
|
+
{"version":3,"file":"rule-registry.d.ts","sourceRoot":"","sources":["../../src/utils/rule-registry.ts"],"names":[],"mappings":"AAkBA;;;GAGG;AAEH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,OAAO,CAAC;IACxB,QAAQ,EAAE,UAAU,GAAG,WAAW,GAAG,WAAW,GAAG,cAAc,CAAC;CACnE;AAED,MAAM,WAAW,yBAAyB;IACxC,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,mCAAmC;AACnC,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,kBAAkB,EAAE,yBAAyB,CAAC;CAC/C;AAED;;;GAGG;AACH,eAAO,MAAM,KAAK,EAAE,SAAS,cAAc,EA0FjC,CAAC;AAEX;;GAEG;AACH,eAAO,MAAM,UAAU,aAAoC,CAAC;AAE5D;;GAEG;AACH,eAAO,MAAM,qBAAqB,EAAE,YAKnC,CAAC;AAEF;;;;;GAKG;AAEH,wBAAgB,iBAAiB,CAC/B,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACrC,QAAQ,CAAC,EAAE,MAAM,GAChB,GAAG,CAAC,MAAM,CAAC,CAsBb;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAK5D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAC9B,YAAY,CAWd"}
|
|
@@ -87,6 +87,18 @@ export const RULES = [
|
|
|
87
87
|
defaultEnabled: false,
|
|
88
88
|
category: "integrity",
|
|
89
89
|
},
|
|
90
|
+
{
|
|
91
|
+
name: "predicate-verifiability",
|
|
92
|
+
description: "Detect requires_predicate links that do not target ground fact_kind: predicate facts",
|
|
93
|
+
defaultEnabled: false,
|
|
94
|
+
category: "integrity",
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: "query-plan-safety",
|
|
98
|
+
description: "Detect Prolog validation clauses that place negation before generator calls",
|
|
99
|
+
defaultEnabled: true,
|
|
100
|
+
category: "integrity",
|
|
101
|
+
},
|
|
90
102
|
];
|
|
91
103
|
/**
|
|
92
104
|
* Set of all rule names for quick lookups.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kibi-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Kibi CLI for knowledge base management",
|
|
6
6
|
"engines": {
|
|
@@ -11,13 +11,7 @@
|
|
|
11
11
|
"bin": {
|
|
12
12
|
"kibi": "bin/kibi"
|
|
13
13
|
},
|
|
14
|
-
"files": [
|
|
15
|
-
"dist",
|
|
16
|
-
"bin",
|
|
17
|
-
"schema",
|
|
18
|
-
"src/schemas",
|
|
19
|
-
"src/public"
|
|
20
|
-
],
|
|
14
|
+
"files": ["dist", "bin", "schema", "src/schemas", "src/public"],
|
|
21
15
|
"scripts": {
|
|
22
16
|
"build": "tsc -p tsconfig.json && mkdir -p dist/public/skills && cp -r src/public/skills/. dist/public/skills/",
|
|
23
17
|
"prepack": "npm run build"
|
|
@@ -75,6 +69,10 @@
|
|
|
75
69
|
"types": "./dist/public/check-types.d.ts",
|
|
76
70
|
"default": "./dist/public/check-types.js"
|
|
77
71
|
},
|
|
72
|
+
"./public/impact-diagnostics": {
|
|
73
|
+
"types": "./dist/public/impact-diagnostics.d.ts",
|
|
74
|
+
"default": "./dist/public/impact-diagnostics.js"
|
|
75
|
+
},
|
|
78
76
|
"./public/symbol-granularity": {
|
|
79
77
|
"types": "./dist/public/symbol-granularity.d.ts",
|
|
80
78
|
"default": "./dist/public/symbol-granularity.js"
|
|
@@ -105,7 +103,7 @@
|
|
|
105
103
|
"fast-glob": "^3.2.12",
|
|
106
104
|
"gray-matter": "^4.0.3",
|
|
107
105
|
"js-yaml": "^4.1.0",
|
|
108
|
-
"kibi-core": "^0.
|
|
106
|
+
"kibi-core": "^0.7.0",
|
|
109
107
|
"ts-morph": "^23.0.0",
|
|
110
108
|
"ignore": "^5.3.0"
|
|
111
109
|
},
|
package/schema/entities.pl
CHANGED
|
@@ -48,6 +48,10 @@ entity_property(fact, valid_from, datetime).
|
|
|
48
48
|
entity_property(fact, valid_to, datetime).
|
|
49
49
|
entity_property(fact, canonical_key, string).
|
|
50
50
|
|
|
51
|
+
% Typed symbol metadata fields - only valid for symbol entities
|
|
52
|
+
entity_property(symbol, symbol_role, atom).
|
|
53
|
+
entity_property(symbol, granularity_reason, atom).
|
|
54
|
+
|
|
51
55
|
% Required properties for all entity types
|
|
52
56
|
required_property(Type, id) :- entity_type(Type).
|
|
53
57
|
required_property(Type, title) :- entity_type(Type).
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import type { ExtractedSymbol } from "../../traceability/symbol-extract.js";
|
|
3
|
+
import { extractSymbolsFromStagedFile } from "../../traceability/symbol-extract.js";
|
|
4
|
+
import {
|
|
5
|
+
createSemanticReviewDiagnostics,
|
|
6
|
+
createSymbolGranularityDiagnostics,
|
|
7
|
+
} from "./diagnostics.js";
|
|
8
|
+
import {
|
|
9
|
+
createImpactManifestLookup,
|
|
10
|
+
readImpactManifestResults,
|
|
11
|
+
} from "./manifest.js";
|
|
12
|
+
import { collectSourceChanges, uniqueSorted } from "./source-changes.js";
|
|
13
|
+
import {
|
|
14
|
+
buildNextActions,
|
|
15
|
+
collectLinkedEntities,
|
|
16
|
+
formatExtractedSymbols,
|
|
17
|
+
} from "./summaries.js";
|
|
18
|
+
import { createSymbolQualityDiagnostics } from "./symbol-quality.js";
|
|
19
|
+
import type {
|
|
20
|
+
ChangedFileImpactOptions,
|
|
21
|
+
ChangedFileImpactResult,
|
|
22
|
+
} from "./types.js";
|
|
23
|
+
|
|
24
|
+
export function analyzeChangedFileImpact(
|
|
25
|
+
options: ChangedFileImpactOptions,
|
|
26
|
+
): ChangedFileImpactResult {
|
|
27
|
+
const workspaceRoot = path.resolve(options.workspaceRoot);
|
|
28
|
+
const sourceChanges = collectSourceChanges({ ...options, workspaceRoot });
|
|
29
|
+
const sourceFiles = uniqueSorted(sourceChanges.map((change) => change.file));
|
|
30
|
+
const manifestResults = readImpactManifestResults(workspaceRoot);
|
|
31
|
+
const manifestLookup = createImpactManifestLookup(manifestResults);
|
|
32
|
+
const symbolsByFile = new Map<string, ExtractedSymbol[]>();
|
|
33
|
+
const sourceContentByFile = new Map<string, string>();
|
|
34
|
+
|
|
35
|
+
for (const change of sourceChanges) {
|
|
36
|
+
sourceContentByFile.set(change.file, change.content);
|
|
37
|
+
symbolsByFile.set(
|
|
38
|
+
change.file,
|
|
39
|
+
extractSymbolsFromStagedFile(
|
|
40
|
+
{
|
|
41
|
+
path: change.file,
|
|
42
|
+
status: change.status,
|
|
43
|
+
hunkRanges: [...change.hunkRanges],
|
|
44
|
+
content: change.content,
|
|
45
|
+
},
|
|
46
|
+
manifestLookup,
|
|
47
|
+
).filter((symbol) => symbol.hunkRanges.length > 0),
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const activeSourceFiles = new Set(sourceFiles);
|
|
52
|
+
const activeManifestResults = manifestResults.filter(
|
|
53
|
+
(result) =>
|
|
54
|
+
result.sourceFile !== undefined &&
|
|
55
|
+
activeSourceFiles.has(result.sourceFile),
|
|
56
|
+
);
|
|
57
|
+
const impactDiagnostics =
|
|
58
|
+
options.includeImpactDiagnostics === false
|
|
59
|
+
? []
|
|
60
|
+
: [
|
|
61
|
+
...createSymbolGranularityDiagnostics({
|
|
62
|
+
manifestResults: activeManifestResults,
|
|
63
|
+
symbolsByFile,
|
|
64
|
+
sourceContentByFile,
|
|
65
|
+
workspaceRoot,
|
|
66
|
+
}),
|
|
67
|
+
...createSymbolQualityDiagnostics({
|
|
68
|
+
manifestResults: activeManifestResults,
|
|
69
|
+
symbolsByFile,
|
|
70
|
+
}),
|
|
71
|
+
...createSemanticReviewDiagnostics({ symbolsByFile }),
|
|
72
|
+
];
|
|
73
|
+
const maxDiagnostics = options.maxDiagnostics;
|
|
74
|
+
const cappedDiagnostics =
|
|
75
|
+
maxDiagnostics !== undefined && maxDiagnostics >= 0
|
|
76
|
+
? impactDiagnostics.slice(0, maxDiagnostics)
|
|
77
|
+
: impactDiagnostics;
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
impactDiagnostics: cappedDiagnostics,
|
|
81
|
+
sourceFiles,
|
|
82
|
+
extractedSymbols: formatExtractedSymbols(symbolsByFile),
|
|
83
|
+
linkedEntities: collectLinkedEntities(
|
|
84
|
+
symbolsByFile,
|
|
85
|
+
manifestResults,
|
|
86
|
+
activeSourceFiles,
|
|
87
|
+
),
|
|
88
|
+
nextActions: buildNextActions(sourceFiles),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ExtractedEntity,
|
|
3
|
+
ExtractionResult,
|
|
4
|
+
} from "../../extractors/markdown.js";
|
|
5
|
+
import type { QualityDiagnostic } from "./types.js";
|
|
6
|
+
|
|
7
|
+
type ReviewableCoverageDepth =
|
|
8
|
+
| "unit_only"
|
|
9
|
+
| "open_or_nonpassing_tests_only"
|
|
10
|
+
| "scenario_only_no_test"
|
|
11
|
+
| "no_test_evidence";
|
|
12
|
+
|
|
13
|
+
type CoverageDepth =
|
|
14
|
+
| ReviewableCoverageDepth
|
|
15
|
+
| "direct_passing_integration"
|
|
16
|
+
| "scenario_passing_integration"
|
|
17
|
+
| "direct_passing_e2e"
|
|
18
|
+
| "scenario_passing_e2e";
|
|
19
|
+
|
|
20
|
+
type RequirementCoverage = {
|
|
21
|
+
readonly requirement: ExtractedEntity;
|
|
22
|
+
readonly scenarioCount: number;
|
|
23
|
+
readonly directTests: readonly ExtractedEntity[];
|
|
24
|
+
readonly scenarioTests: readonly ExtractedEntity[];
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type RelationshipLookup = {
|
|
28
|
+
readonly result: ExtractionResult;
|
|
29
|
+
readonly relationshipType: string;
|
|
30
|
+
readonly type: string;
|
|
31
|
+
readonly entities: ReadonlyMap<string, ExtractedEntity>;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
type IncomingLookup = {
|
|
35
|
+
readonly manifestResults: readonly ExtractionResult[];
|
|
36
|
+
readonly targetId: string;
|
|
37
|
+
readonly relationshipType: string;
|
|
38
|
+
readonly type: string;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
function entitiesById(
|
|
42
|
+
manifestResults: readonly ExtractionResult[],
|
|
43
|
+
): ReadonlyMap<string, ExtractedEntity> {
|
|
44
|
+
return new Map(
|
|
45
|
+
manifestResults.map((result) => [result.entity.id, result.entity]),
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function outgoingTargets(
|
|
50
|
+
lookup: RelationshipLookup,
|
|
51
|
+
): readonly ExtractedEntity[] {
|
|
52
|
+
return lookup.result.relationships.flatMap((relationship) => {
|
|
53
|
+
if (relationship.type !== lookup.relationshipType) return [];
|
|
54
|
+
const target = lookup.entities.get(relationship.to);
|
|
55
|
+
return target?.type === lookup.type ? [target] : [];
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function incomingSources(lookup: IncomingLookup): readonly ExtractedEntity[] {
|
|
60
|
+
return lookup.manifestResults.flatMap((result) => {
|
|
61
|
+
if (result.entity.type !== lookup.type) return [];
|
|
62
|
+
return result.relationships.some(
|
|
63
|
+
(relationship) =>
|
|
64
|
+
relationship.type === lookup.relationshipType &&
|
|
65
|
+
relationship.to === lookup.targetId,
|
|
66
|
+
)
|
|
67
|
+
? [result.entity]
|
|
68
|
+
: [];
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function uniqueEntities(
|
|
73
|
+
entities: readonly ExtractedEntity[],
|
|
74
|
+
): readonly ExtractedEntity[] {
|
|
75
|
+
return [
|
|
76
|
+
...new Map(entities.map((entity) => [entity.id, entity])).values(),
|
|
77
|
+
].toSorted((left, right) => left.id.localeCompare(right.id));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function testScope(test: ExtractedEntity): string {
|
|
81
|
+
if (test.verification_scope !== undefined) return test.verification_scope;
|
|
82
|
+
if (test.tags?.some((tag) => tag.toLowerCase() === "e2e") === true) {
|
|
83
|
+
return "end_to_end";
|
|
84
|
+
}
|
|
85
|
+
return test.source.toLowerCase().includes("e2e") ? "end_to_end" : "unknown";
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function passingTests(
|
|
89
|
+
tests: readonly ExtractedEntity[],
|
|
90
|
+
): readonly ExtractedEntity[] {
|
|
91
|
+
return tests.filter((test) => test.status === "passing");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function classifyCoverageDepth(coverage: RequirementCoverage): CoverageDepth {
|
|
95
|
+
const allTests = uniqueEntities([
|
|
96
|
+
...coverage.directTests,
|
|
97
|
+
...coverage.scenarioTests,
|
|
98
|
+
]);
|
|
99
|
+
const directPassingTests = passingTests(coverage.directTests);
|
|
100
|
+
if (directPassingTests.some((test) => testScope(test) === "end_to_end")) {
|
|
101
|
+
return "direct_passing_e2e";
|
|
102
|
+
}
|
|
103
|
+
if (directPassingTests.some((test) => testScope(test) === "integration")) {
|
|
104
|
+
return "direct_passing_integration";
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const scenarioPassingTests = passingTests(coverage.scenarioTests);
|
|
108
|
+
if (scenarioPassingTests.some((test) => testScope(test) === "end_to_end")) {
|
|
109
|
+
return "scenario_passing_e2e";
|
|
110
|
+
}
|
|
111
|
+
if (scenarioPassingTests.some((test) => testScope(test) === "integration")) {
|
|
112
|
+
return "scenario_passing_integration";
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const allPassingTests = passingTests(allTests);
|
|
116
|
+
if (
|
|
117
|
+
allPassingTests.length > 0 &&
|
|
118
|
+
allPassingTests.every((test) => testScope(test) === "unit")
|
|
119
|
+
) {
|
|
120
|
+
return "unit_only";
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (allTests.length > 0) return "open_or_nonpassing_tests_only";
|
|
124
|
+
if (coverage.scenarioCount > 0) return "scenario_only_no_test";
|
|
125
|
+
return "no_test_evidence";
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function isReviewableCoverageDepth(
|
|
129
|
+
coverageDepth: CoverageDepth,
|
|
130
|
+
): coverageDepth is ReviewableCoverageDepth {
|
|
131
|
+
return (
|
|
132
|
+
coverageDepth === "unit_only" ||
|
|
133
|
+
coverageDepth === "open_or_nonpassing_tests_only" ||
|
|
134
|
+
coverageDepth === "scenario_only_no_test" ||
|
|
135
|
+
coverageDepth === "no_test_evidence"
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function uniqueStrings(values: readonly string[]): readonly string[] {
|
|
140
|
+
return [...new Set(values)].toSorted((left, right) =>
|
|
141
|
+
left.localeCompare(right),
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function coverageDepthSuggestion(
|
|
146
|
+
coverageDepth: ReviewableCoverageDepth,
|
|
147
|
+
): string {
|
|
148
|
+
if (coverageDepth === "unit_only") {
|
|
149
|
+
return "Keep unit coverage where appropriate; add or link a passing end-to-end or scenario-backed test when this behavior warrants e2e confidence.";
|
|
150
|
+
}
|
|
151
|
+
return "Add or link passing tests, preferably end-to-end or scenario-backed when this behavior warrants e2e confidence.";
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function createDiagnostic(
|
|
155
|
+
coverage: RequirementCoverage,
|
|
156
|
+
): QualityDiagnostic | undefined {
|
|
157
|
+
const coverageDepth = classifyCoverageDepth(coverage);
|
|
158
|
+
if (!isReviewableCoverageDepth(coverageDepth)) return undefined;
|
|
159
|
+
|
|
160
|
+
const allTests = uniqueEntities([
|
|
161
|
+
...coverage.directTests,
|
|
162
|
+
...coverage.scenarioTests,
|
|
163
|
+
]);
|
|
164
|
+
return {
|
|
165
|
+
id: "coverage_depth_review",
|
|
166
|
+
severity: "review",
|
|
167
|
+
blocking: false,
|
|
168
|
+
category: "coverage",
|
|
169
|
+
entityId: coverage.requirement.id,
|
|
170
|
+
source: coverage.requirement.source,
|
|
171
|
+
files: [coverage.requirement.source],
|
|
172
|
+
message: `Requirement ${coverage.requirement.id} (${coverage.requirement.title}) has ${coverageDepth} coverage depth and may need stronger behavior-level evidence.`,
|
|
173
|
+
suggestion: coverageDepthSuggestion(coverageDepth),
|
|
174
|
+
evidence: {
|
|
175
|
+
requirementId: coverage.requirement.id,
|
|
176
|
+
title: coverage.requirement.title,
|
|
177
|
+
source: coverage.requirement.source,
|
|
178
|
+
coverageDepth,
|
|
179
|
+
directTests: coverage.directTests.map((test) => test.id),
|
|
180
|
+
scenarioTests: coverage.scenarioTests.map((test) => test.id),
|
|
181
|
+
testStatuses: uniqueStrings(allTests.map((test) => test.status)),
|
|
182
|
+
verificationScopes: uniqueStrings(allTests.map(testScope)),
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function coverageForRequirement(
|
|
188
|
+
result: ExtractionResult,
|
|
189
|
+
manifestResults: readonly ExtractionResult[],
|
|
190
|
+
entities: ReadonlyMap<string, ExtractedEntity>,
|
|
191
|
+
): RequirementCoverage {
|
|
192
|
+
const resultsById = new Map(
|
|
193
|
+
manifestResults.map((manifestResult) => [
|
|
194
|
+
manifestResult.entity.id,
|
|
195
|
+
manifestResult,
|
|
196
|
+
]),
|
|
197
|
+
);
|
|
198
|
+
const scenarios = outgoingTargets({
|
|
199
|
+
result,
|
|
200
|
+
relationshipType: "specified_by",
|
|
201
|
+
type: "scenario",
|
|
202
|
+
entities,
|
|
203
|
+
});
|
|
204
|
+
const directTests = uniqueEntities([
|
|
205
|
+
...outgoingTargets({
|
|
206
|
+
result,
|
|
207
|
+
relationshipType: "verified_by",
|
|
208
|
+
type: "test",
|
|
209
|
+
entities,
|
|
210
|
+
}),
|
|
211
|
+
...outgoingTargets({
|
|
212
|
+
result,
|
|
213
|
+
relationshipType: "covered_by",
|
|
214
|
+
type: "test",
|
|
215
|
+
entities,
|
|
216
|
+
}),
|
|
217
|
+
...incomingSources({
|
|
218
|
+
manifestResults,
|
|
219
|
+
targetId: result.entity.id,
|
|
220
|
+
relationshipType: "validates",
|
|
221
|
+
type: "test",
|
|
222
|
+
}),
|
|
223
|
+
]);
|
|
224
|
+
const scenarioTests = uniqueEntities(
|
|
225
|
+
scenarios.flatMap((scenario) => {
|
|
226
|
+
const scenarioResult = resultsById.get(scenario.id);
|
|
227
|
+
return [
|
|
228
|
+
...incomingSources({
|
|
229
|
+
manifestResults,
|
|
230
|
+
targetId: scenario.id,
|
|
231
|
+
relationshipType: "validates",
|
|
232
|
+
type: "test",
|
|
233
|
+
}),
|
|
234
|
+
...(scenarioResult === undefined
|
|
235
|
+
? []
|
|
236
|
+
: outgoingTargets({
|
|
237
|
+
result: scenarioResult,
|
|
238
|
+
relationshipType: "verified_by",
|
|
239
|
+
type: "test",
|
|
240
|
+
entities,
|
|
241
|
+
})),
|
|
242
|
+
];
|
|
243
|
+
}),
|
|
244
|
+
);
|
|
245
|
+
return {
|
|
246
|
+
requirement: result.entity,
|
|
247
|
+
scenarioCount: scenarios.length,
|
|
248
|
+
directTests,
|
|
249
|
+
scenarioTests,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function createCoverageDepthQualityDiagnostics(
|
|
254
|
+
manifestResults: readonly ExtractionResult[],
|
|
255
|
+
): readonly QualityDiagnostic[] {
|
|
256
|
+
const entities = entitiesById(manifestResults);
|
|
257
|
+
return manifestResults
|
|
258
|
+
.filter((result) => result.entity.type === "req")
|
|
259
|
+
.flatMap((result) => {
|
|
260
|
+
const diagnostic = createDiagnostic(
|
|
261
|
+
coverageForRequirement(result, manifestResults, entities),
|
|
262
|
+
);
|
|
263
|
+
return diagnostic === undefined ? [] : [diagnostic];
|
|
264
|
+
})
|
|
265
|
+
.toSorted((left, right) =>
|
|
266
|
+
(left.entityId ?? "").localeCompare(right.entityId ?? ""),
|
|
267
|
+
);
|
|
268
|
+
}
|