@qulib/core 0.11.0 → 0.12.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.
|
@@ -5,6 +5,24 @@ export interface ConfidenceOptions {
|
|
|
5
5
|
repo?: string;
|
|
6
6
|
json?: boolean;
|
|
7
7
|
}
|
|
8
|
+
export interface ConfidenceGateResult {
|
|
9
|
+
/** Whether any gate (--fail-on / --min-score) was requested. */
|
|
10
|
+
requested: boolean;
|
|
11
|
+
/** True when the release passes the requested gate (or none was requested). */
|
|
12
|
+
passed: boolean;
|
|
13
|
+
/** Human-readable explanation of the gate outcome. */
|
|
14
|
+
reason: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Evaluate a CI gate against a release-confidence result. Pure + side-effect-free
|
|
18
|
+
* so it is unit-testable; the CLI action turns a failed gate into a non-zero exit.
|
|
19
|
+
*
|
|
20
|
+
* - `failOn`: fail when the verdict is at or worse than this threshold
|
|
21
|
+
* (e.g. `--fail-on hold` fails on `hold` or `block`).
|
|
22
|
+
* - `minScore`: fail when the confidence score is below this (a `null` score —
|
|
23
|
+
* nothing evaluable — always fails a min-score gate).
|
|
24
|
+
*/
|
|
25
|
+
export declare function evaluateConfidenceGate(rc: ReleaseConfidence, failOn?: string, minScore?: number): ConfidenceGateResult;
|
|
8
26
|
/** Render the human-friendly report for a ReleaseConfidence result. */
|
|
9
27
|
export declare function formatConfidenceReport(rc: ReleaseConfidence, subjectRef: string): string;
|
|
10
28
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"confidence-run.d.ts","sourceRoot":"","sources":["../../src/cli/confidence-run.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQzC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAGzE,MAAM,WAAW,iBAAiB;IAChC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAiBD,uEAAuE;AACvE,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAuCxF;AAED;;;GAGG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,iBAAiB,EAC1B,GAAG,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAkC,GACxD,OAAO,CAAC,iBAAiB,CAAC,CAmE5B;AAED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"confidence-run.d.ts","sourceRoot":"","sources":["../../src/cli/confidence-run.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQzC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAGzE,MAAM,WAAW,iBAAiB;IAChC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAKD,MAAM,WAAW,oBAAoB;IACnC,gEAAgE;IAChE,SAAS,EAAE,OAAO,CAAC;IACnB,+EAA+E;IAC/E,MAAM,EAAE,OAAO,CAAC;IAChB,sDAAsD;IACtD,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CACpC,EAAE,EAAE,iBAAiB,EACrB,MAAM,CAAC,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,MAAM,GAChB,oBAAoB,CAuCtB;AAiBD,uEAAuE;AACvE,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAuCxF;AAED;;;GAGG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,iBAAiB,EAC1B,GAAG,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAkC,GACxD,OAAO,CAAC,iBAAiB,CAAC,CAmE5B;AAED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA2ChE"}
|
|
@@ -21,6 +21,50 @@ import { discoverApiSurfaceWithRepo } from '../tools/repo/api-surface.js';
|
|
|
21
21
|
import { computeApiCoverage } from '../tools/scoring/api-coverage.js';
|
|
22
22
|
import { buildConfidenceInputFromQulib } from '../tools/scoring/confidence-from-qulib.js';
|
|
23
23
|
import { computeReleaseConfidence } from '../tools/scoring/confidence.js';
|
|
24
|
+
/** Verdict severity, best (0) → worst (3). Used by the CI gate. */
|
|
25
|
+
const VERDICT_RANK = { ship: 0, caution: 1, hold: 2, block: 3 };
|
|
26
|
+
/**
|
|
27
|
+
* Evaluate a CI gate against a release-confidence result. Pure + side-effect-free
|
|
28
|
+
* so it is unit-testable; the CLI action turns a failed gate into a non-zero exit.
|
|
29
|
+
*
|
|
30
|
+
* - `failOn`: fail when the verdict is at or worse than this threshold
|
|
31
|
+
* (e.g. `--fail-on hold` fails on `hold` or `block`).
|
|
32
|
+
* - `minScore`: fail when the confidence score is below this (a `null` score —
|
|
33
|
+
* nothing evaluable — always fails a min-score gate).
|
|
34
|
+
*/
|
|
35
|
+
export function evaluateConfidenceGate(rc, failOn, minScore) {
|
|
36
|
+
const failOnNorm = failOn?.trim().toLowerCase();
|
|
37
|
+
const hasFailOn = Boolean(failOnNorm);
|
|
38
|
+
const hasMinScore = typeof minScore === 'number' && !Number.isNaN(minScore);
|
|
39
|
+
if (!hasFailOn && !hasMinScore) {
|
|
40
|
+
return { requested: false, passed: true, reason: 'no gate requested' };
|
|
41
|
+
}
|
|
42
|
+
const reasons = [];
|
|
43
|
+
let passed = true;
|
|
44
|
+
if (hasFailOn) {
|
|
45
|
+
if (!(failOnNorm in VERDICT_RANK)) {
|
|
46
|
+
throw new Error(`--fail-on must be one of: ship, caution, hold, block (got "${failOn}")`);
|
|
47
|
+
}
|
|
48
|
+
const verdictRank = VERDICT_RANK[rc.verdict] ?? 99;
|
|
49
|
+
if (verdictRank >= VERDICT_RANK[failOnNorm]) {
|
|
50
|
+
passed = false;
|
|
51
|
+
reasons.push(`verdict '${rc.verdict}' is at or worse than --fail-on '${failOnNorm}'`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (hasMinScore) {
|
|
55
|
+
const score = rc.confidenceScore;
|
|
56
|
+
if (score === null || score < minScore) {
|
|
57
|
+
passed = false;
|
|
58
|
+
reasons.push(`confidence score ${score === null ? 'null (nothing evaluable)' : score} is below --min-score ${minScore}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const scoreSuffix = rc.confidenceScore !== null ? `, score ${rc.confidenceScore}` : '';
|
|
62
|
+
return {
|
|
63
|
+
requested: true,
|
|
64
|
+
passed,
|
|
65
|
+
reason: passed ? `verdict '${rc.verdict}'${scoreSuffix} meets the gate` : reasons.join('; '),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
24
68
|
/**
|
|
25
69
|
* Resolve and validate an optional --repo path. Returns null if none was provided.
|
|
26
70
|
*/
|
|
@@ -152,11 +196,24 @@ export function registerConfidenceCommand(program) {
|
|
|
152
196
|
.option('--url <url>', 'URL of the deployed app to analyze')
|
|
153
197
|
.option('--repo <path>', 'Path to the local repository to score')
|
|
154
198
|
.option('--json', 'Emit the full ReleaseConfidence object as JSON to stdout', false)
|
|
199
|
+
.option('--fail-on <verdict>', 'CI gate: exit non-zero when the verdict is at or worse than this (caution | hold | block)')
|
|
200
|
+
.option('--min-score <n>', 'CI gate: exit non-zero when the confidence score is below this (0–100)', (v) => parseInt(v, 10))
|
|
155
201
|
.action(async (options) => {
|
|
156
|
-
await runConfidence({
|
|
202
|
+
const rc = await runConfidence({
|
|
157
203
|
url: options.url,
|
|
158
204
|
repo: options.repo,
|
|
159
205
|
json: Boolean(options.json),
|
|
160
206
|
});
|
|
207
|
+
const gate = evaluateConfidenceGate(rc, options.failOn, options.minScore);
|
|
208
|
+
if (gate.requested) {
|
|
209
|
+
const line = `[qulib] GATE: ${gate.passed ? 'PASS' : 'FAIL'} — ${gate.reason}`;
|
|
210
|
+
// Keep stdout pure JSON in --json mode; the gate line goes to stderr there.
|
|
211
|
+
if (options.json)
|
|
212
|
+
console.error(line);
|
|
213
|
+
else
|
|
214
|
+
console.log(line);
|
|
215
|
+
if (!gate.passed)
|
|
216
|
+
process.exitCode = 1;
|
|
217
|
+
}
|
|
161
218
|
});
|
|
162
219
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qulib/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Qulib — release confidence for deployed web apps. Fuses live-app quality, automation maturity, and API coverage into a single ship/caution/hold/block verdict.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Tapesh Nagarwal",
|