@produtype/core 0.16.2 → 0.17.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.
@@ -136,9 +136,6 @@ function buildReport(analysis, options) {
136
136
  inconclusiveReasons.push('No recognizable source files were found.');
137
137
  }
138
138
  }
139
- // An unrecognized project has almost no applicable detectors, so the absence
140
- // of findings must not be rewarded with a high score: cap it at prototype.
141
- const overallScore = inconclusive ? Math.min(combinedScore, 39) : combinedScore;
142
139
  /**
143
140
  * Coverage counts the expectations too, not only the observed rules.
144
141
  *
@@ -152,10 +149,34 @@ function buildReport(analysis, options) {
152
149
  const passedChecksForMaturity = findings.filter((f) => f.status === 'passed').length + satisfiedExpectations;
153
150
  const assessedChecks = findings.filter((f) => f.status !== 'unknown').length
154
151
  + Math.max(applicableExpectations - expectationFindings.length, 0);
155
- const maturityLevel = (0, score_1.computeMaturity)(overallScore, {
156
- passed: passedChecksForMaturity,
157
- assessed: assessedChecks,
158
- });
152
+ const coverage = { passed: passedChecksForMaturity, assessed: assessedChecks };
153
+ // An unrecognized project has almost no applicable detectors, so the absence
154
+ // of findings must not be rewarded with a high score: cap it at prototype.
155
+ const scoreAfterInconclusive = inconclusive ? Math.min(combinedScore, 39) : combinedScore;
156
+ /**
157
+ * The number obeys the same rule as the label.
158
+ *
159
+ * The score starts at 100 and only ever subtracts, so a repository small enough that
160
+ * almost nothing applies to it keeps most of what it was handed. The maturity label
161
+ * has refused to call that production ready since the coverage rule was added — and
162
+ * the number beside it went on saying 98, above every real product in the verification
163
+ * corpus. A Python transcription script rested on three passing checks out of four and
164
+ * outscored this product's own web application, which rests on thirty.
165
+ *
166
+ * Nothing is subtracted for thin coverage, because there is nothing to subtract for:
167
+ * the report simply may not enter the top band on evidence that cannot support it.
168
+ */
169
+ /**
170
+ * An unresolved critical bars the top band too.
171
+ *
172
+ * Counted before the score is capped, because it is one of the two reasons the top
173
+ * band can be refused and both must reach the number, not only the label.
174
+ */
175
+ const openCriticals = findings.filter((f) => f.severity === 'critical' && f.status !== 'passed' && f.status !== 'unknown').length;
176
+ const overallScore = (0, score_1.mayClaimTopBand)(coverage, openCriticals)
177
+ ? scoreAfterInconclusive
178
+ : Math.min(scoreAfterInconclusive, score_1.TOP_BAND_CEILING);
179
+ const maturityLevel = (0, score_1.computeMaturity)(overallScore, coverage, openCriticals);
159
180
  const findingsByCategory = Object.fromEntries(types_1.CATEGORIES.map((c) => [c, []]));
160
181
  for (const f of findings)
161
182
  findingsByCategory[f.category].push(f);
@@ -10,4 +10,41 @@ export interface ScoreCoverage {
10
10
  passed: number;
11
11
  assessed: number;
12
12
  }
13
- export declare function computeMaturity(score: number, coverage?: ScoreCoverage): MaturityLevel;
13
+ /**
14
+ * The highest score a report can reach when it may not claim the top band.
15
+ *
16
+ * The band boundary itself, deliberately: a report that cannot support the words
17
+ * "production ready" must not print a number that reads like it either. Two things bar
18
+ * the top band — evidence too thin to support it, and an unresolved critical — and both
19
+ * land here.
20
+ */
21
+ export declare const TOP_BAND_CEILING = 84;
22
+ /**
23
+ * Whether the report checked enough to stand behind a top-band reading.
24
+ *
25
+ * Its own function because two things ask the question — the label and the number — and
26
+ * they were answering it differently. A repository small enough that almost nothing
27
+ * applies to it was labelled `partial` and scored 98, above every real product in the
28
+ * corpus; the label had learned that absence of findings is not evidence of quality and
29
+ * the score had not.
30
+ */
31
+ export declare function coverageSupportsTopBand(coverage: ScoreCoverage): boolean;
32
+ /**
33
+ * Whether a report may print a top-band number at all.
34
+ *
35
+ * The single place the two bars are stated, so the score and the label cannot drift
36
+ * apart again: they did, and the number went on reading 98 above every real product in
37
+ * the corpus while the label said `partial`.
38
+ */
39
+ export declare function mayClaimTopBand(coverage: ScoreCoverage, openCriticals: number): boolean;
40
+ /**
41
+ * How many critical findings are open.
42
+ *
43
+ * A separate argument rather than something derived from the score, because the score
44
+ * subtracts a fixed weight and then moves on: a project with forty-two passing checks
45
+ * and one unresolved critical came out at 91 and was called production ready. The
46
+ * category scores have refused to let a critical hide behind passing checks since the
47
+ * severity ceiling was added — "a category with one critical and nine passing checks is
48
+ * not 90% healthy" — and the report's own headline had not learned it.
49
+ */
50
+ export declare function computeMaturity(score: number, coverage?: ScoreCoverage, openCriticals?: number): MaturityLevel;
@@ -1,6 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TOP_BAND_CEILING = void 0;
3
4
  exports.computeScore = computeScore;
5
+ exports.coverageSupportsTopBand = coverageSupportsTopBand;
6
+ exports.mayClaimTopBand = mayClaimTopBand;
4
7
  exports.computeMaturity = computeMaturity;
5
8
  const penaltyBySeverity = {
6
9
  critical: 15,
@@ -25,7 +28,48 @@ function computeScore(findings) {
25
28
  * nothing.
26
29
  */
27
30
  const MIN_VERIFIED_FOR_PRODUCTION = 6;
28
- function computeMaturity(score, coverage) {
31
+ /**
32
+ * The highest score a report can reach when it may not claim the top band.
33
+ *
34
+ * The band boundary itself, deliberately: a report that cannot support the words
35
+ * "production ready" must not print a number that reads like it either. Two things bar
36
+ * the top band — evidence too thin to support it, and an unresolved critical — and both
37
+ * land here.
38
+ */
39
+ exports.TOP_BAND_CEILING = 84;
40
+ /**
41
+ * Whether the report checked enough to stand behind a top-band reading.
42
+ *
43
+ * Its own function because two things ask the question — the label and the number — and
44
+ * they were answering it differently. A repository small enough that almost nothing
45
+ * applies to it was labelled `partial` and scored 98, above every real product in the
46
+ * corpus; the label had learned that absence of findings is not evidence of quality and
47
+ * the score had not.
48
+ */
49
+ function coverageSupportsTopBand(coverage) {
50
+ return coverage.passed >= MIN_VERIFIED_FOR_PRODUCTION && coverage.passed * 2 >= coverage.assessed;
51
+ }
52
+ /**
53
+ * Whether a report may print a top-band number at all.
54
+ *
55
+ * The single place the two bars are stated, so the score and the label cannot drift
56
+ * apart again: they did, and the number went on reading 98 above every real product in
57
+ * the corpus while the label said `partial`.
58
+ */
59
+ function mayClaimTopBand(coverage, openCriticals) {
60
+ return coverageSupportsTopBand(coverage) && openCriticals === 0;
61
+ }
62
+ /**
63
+ * How many critical findings are open.
64
+ *
65
+ * A separate argument rather than something derived from the score, because the score
66
+ * subtracts a fixed weight and then moves on: a project with forty-two passing checks
67
+ * and one unresolved critical came out at 91 and was called production ready. The
68
+ * category scores have refused to let a critical hide behind passing checks since the
69
+ * severity ceiling was added — "a category with one critical and nine passing checks is
70
+ * not 90% healthy" — and the report's own headline had not learned it.
71
+ */
72
+ function computeMaturity(score, coverage, openCriticals = 0) {
29
73
  if (score <= 39)
30
74
  return 'prototype';
31
75
  if (score <= 64)
@@ -45,8 +89,12 @@ function computeMaturity(score, coverage) {
45
89
  * Absence of findings is not evidence of quality. The top band now requires the
46
90
  * report to be able to point at things it checked and found right.
47
91
  */
48
- if (coverage && (coverage.passed < MIN_VERIFIED_FOR_PRODUCTION || coverage.passed * 2 < coverage.assessed)) {
92
+ if (coverage && !coverageSupportsTopBand(coverage)) {
49
93
  return 'partial';
50
94
  }
95
+ // Nothing is production ready while something critical is open in it. This is the one
96
+ // claim in the report a reader acts on without reading further.
97
+ if (openCriticals > 0)
98
+ return 'partial';
51
99
  return 'production_ready';
52
100
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@produtype/core",
3
- "version": "0.16.2",
3
+ "version": "0.17.0",
4
4
  "description": "Deterministic CLI and library that analyzes a web application repository and reports how far it is from production-ready for the kind of product it is meant to be.",
5
5
  "license": "MIT",
6
6
  "bin": {