@produtype/core 0.55.0 → 0.57.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/planner/buildPlan.js +12 -3
- package/dist/report/buildReport.js +11 -1
- package/dist/report/executiveSummary.js +21 -3
- package/dist/report/score.d.ts +15 -1
- package/dist/report/score.js +16 -2
- package/package.json +1 -1
|
@@ -167,9 +167,18 @@ function sortStates(states) {
|
|
|
167
167
|
}
|
|
168
168
|
return ordered;
|
|
169
169
|
}
|
|
170
|
-
function summarizePlan(tasks, actionableFindingCount) {
|
|
170
|
+
function summarizePlan(tasks, actionableFindingCount, inconclusive) {
|
|
171
171
|
if (tasks.length === 0) {
|
|
172
|
-
|
|
172
|
+
/**
|
|
173
|
+
* An empty plan means two different things and said one sentence for both.
|
|
174
|
+
*
|
|
175
|
+
* "Found no actionable remediation tasks" reads as "there is nothing to do". On a
|
|
176
|
+
* repository this analyzer could not read, there is nothing to do *because it
|
|
177
|
+
* could not look* — and that is the opposite advice.
|
|
178
|
+
*/
|
|
179
|
+
return inconclusive
|
|
180
|
+
? 'No plan: too little of this repository could be read to say what it needs.'
|
|
181
|
+
: 'ProdKit found no actionable remediation tasks from the current deterministic findings.';
|
|
173
182
|
}
|
|
174
183
|
const quickWins = tasks.filter((task) => task.effort === 'small' && (task.risk === 'low' || task.risk === 'medium')).length;
|
|
175
184
|
const highRisk = tasks.filter((task) => task.risk === 'high').length;
|
|
@@ -234,7 +243,7 @@ function buildPlan(report) {
|
|
|
234
243
|
generatedAt: new Date().toISOString(),
|
|
235
244
|
score: report.overallScore,
|
|
236
245
|
maturityLevel: report.maturityLevel,
|
|
237
|
-
summary: summarizePlan(tasks, actionableFindings.length),
|
|
246
|
+
summary: summarizePlan(tasks, actionableFindings.length, report.inconclusive),
|
|
238
247
|
phases,
|
|
239
248
|
tasks,
|
|
240
249
|
quickWins,
|
|
@@ -279,9 +279,19 @@ function buildReport(analysis, options) {
|
|
|
279
279
|
* band can be refused and both must reach the number, not only the label.
|
|
280
280
|
*/
|
|
281
281
|
const openCriticals = findings.filter((f) => f.severity === 'critical' && f.status !== 'passed' && f.status !== 'unknown').length;
|
|
282
|
+
/**
|
|
283
|
+
* A required capability the profile cannot find is the third bar.
|
|
284
|
+
*
|
|
285
|
+
* Not counted for a profile that requires nothing, and not counted where no profile
|
|
286
|
+
* was applied: both of those are the absence of a question rather than a failed
|
|
287
|
+
* answer, which is the distinction the rest of this report now draws everywhere.
|
|
288
|
+
*/
|
|
289
|
+
const blockingRequired = productProfile && productProfile.selectedProfile !== 'auto'
|
|
290
|
+
? productProfile.gap.requiredMissing + productProfile.gap.requiredPartial
|
|
291
|
+
: 0;
|
|
282
292
|
const overallScore = inconclusive
|
|
283
293
|
? null
|
|
284
|
-
: (0, score_1.mayClaimTopBand)(coverage, openCriticals)
|
|
294
|
+
: (0, score_1.mayClaimTopBand)(coverage, openCriticals, blockingRequired)
|
|
285
295
|
? scoreAfterInconclusive
|
|
286
296
|
: Math.min(scoreAfterInconclusive, score_1.TOP_BAND_CEILING);
|
|
287
297
|
const maturityLevel = overallScore === null
|
|
@@ -66,8 +66,26 @@ const DAYS_BY_SEVERITY = {
|
|
|
66
66
|
low: 0.5,
|
|
67
67
|
info: 0,
|
|
68
68
|
};
|
|
69
|
-
function estimateEffort(findings, profile) {
|
|
70
|
-
|
|
69
|
+
function estimateEffort(findings, profile, inconclusive) {
|
|
70
|
+
/**
|
|
71
|
+
* "Nothing outstanding to estimate" is a claim about the work, and a report that
|
|
72
|
+
* could not read the repository has no basis for it.
|
|
73
|
+
*
|
|
74
|
+
* A Python transcription script whose verdict says "ProdKit could not judge this
|
|
75
|
+
* project" was told there was nothing to do. It is the last number in the report and
|
|
76
|
+
* the same mistake as all the others: an empty list of findings read as an empty
|
|
77
|
+
* list of problems.
|
|
78
|
+
*/
|
|
79
|
+
if (inconclusive)
|
|
80
|
+
return 'Not estimated: too little of this repository could be read.';
|
|
81
|
+
/**
|
|
82
|
+
* The inconclusive-inference profile is no profile.
|
|
83
|
+
*
|
|
84
|
+
* It carries an empty capability set, so estimating against it measures the observed
|
|
85
|
+
* findings and calls the answer an estimate of the gap. The sentence for having no
|
|
86
|
+
* profile already exists and is the true one.
|
|
87
|
+
*/
|
|
88
|
+
if (!profile || profile.selectedProfile === 'auto')
|
|
71
89
|
return 'Not estimated without a product profile.';
|
|
72
90
|
const open = findings.filter((f) => f.status !== 'passed' && f.status !== 'unknown');
|
|
73
91
|
if (open.length === 0)
|
|
@@ -265,7 +283,7 @@ function buildExecutiveSummary(args) {
|
|
|
265
283
|
* the same praise.
|
|
266
284
|
*/
|
|
267
285
|
strengths: args.inconclusive ? [] : buildStrengths(args.categoryScores, args.findings),
|
|
268
|
-
estimatedEffort: estimateEffort(args.findings, args.profile),
|
|
286
|
+
estimatedEffort: estimateEffort(args.findings, args.profile, args.inconclusive),
|
|
269
287
|
scoreExplanation,
|
|
270
288
|
};
|
|
271
289
|
}
|
package/dist/report/score.d.ts
CHANGED
|
@@ -46,7 +46,7 @@ export declare function coverageSupportsTopBand(coverage: ScoreCoverage): boolea
|
|
|
46
46
|
* apart again: they did, and the number went on reading 98 above every real product in
|
|
47
47
|
* the corpus while the label said `partial`.
|
|
48
48
|
*/
|
|
49
|
-
export declare function mayClaimTopBand(coverage: ScoreCoverage, openCriticals: number): boolean;
|
|
49
|
+
export declare function mayClaimTopBand(coverage: ScoreCoverage, openCriticals: number, blockingRequired?: number): boolean;
|
|
50
50
|
/**
|
|
51
51
|
* How many critical findings are open.
|
|
52
52
|
*
|
|
@@ -57,4 +57,18 @@ export declare function mayClaimTopBand(coverage: ScoreCoverage, openCriticals:
|
|
|
57
57
|
* severity ceiling was added — "a category with one critical and nine passing checks is
|
|
58
58
|
* not 90% healthy" — and the report's own headline had not learned it.
|
|
59
59
|
*/
|
|
60
|
+
/**
|
|
61
|
+
* A capability the profile calls essential, missing or half-built.
|
|
62
|
+
*
|
|
63
|
+
* The third bar on the top band, and the one a reader could see for themselves.
|
|
64
|
+
* firefly-iii was labelled "Production ready" directly above the sentence "This
|
|
65
|
+
* project is not ready to launch as B2C App: 3 essential capabilities are missing or
|
|
66
|
+
* incomplete" — two labels on one page contradicting each other. Five of the
|
|
67
|
+
* eighty-two repositories measured read that way, this product's own web application
|
|
68
|
+
* among them.
|
|
69
|
+
*
|
|
70
|
+
* The two answer different questions and the words do not care: "production ready" is
|
|
71
|
+
* a claim about readiness, and something the profile calls essential and cannot find
|
|
72
|
+
* is by definition blocking. The band is the thing that has to give.
|
|
73
|
+
*/
|
|
60
74
|
export declare function computeMaturity(score: number, coverage?: ScoreCoverage, openCriticals?: number): MaturityLevel;
|
package/dist/report/score.js
CHANGED
|
@@ -66,8 +66,8 @@ function coverageSupportsTopBand(coverage) {
|
|
|
66
66
|
* apart again: they did, and the number went on reading 98 above every real product in
|
|
67
67
|
* the corpus while the label said `partial`.
|
|
68
68
|
*/
|
|
69
|
-
function mayClaimTopBand(coverage, openCriticals) {
|
|
70
|
-
return coverageSupportsTopBand(coverage) && openCriticals === 0;
|
|
69
|
+
function mayClaimTopBand(coverage, openCriticals, blockingRequired = 0) {
|
|
70
|
+
return coverageSupportsTopBand(coverage) && openCriticals === 0 && blockingRequired === 0;
|
|
71
71
|
}
|
|
72
72
|
/**
|
|
73
73
|
* How many critical findings are open.
|
|
@@ -79,6 +79,20 @@ function mayClaimTopBand(coverage, openCriticals) {
|
|
|
79
79
|
* severity ceiling was added — "a category with one critical and nine passing checks is
|
|
80
80
|
* not 90% healthy" — and the report's own headline had not learned it.
|
|
81
81
|
*/
|
|
82
|
+
/**
|
|
83
|
+
* A capability the profile calls essential, missing or half-built.
|
|
84
|
+
*
|
|
85
|
+
* The third bar on the top band, and the one a reader could see for themselves.
|
|
86
|
+
* firefly-iii was labelled "Production ready" directly above the sentence "This
|
|
87
|
+
* project is not ready to launch as B2C App: 3 essential capabilities are missing or
|
|
88
|
+
* incomplete" — two labels on one page contradicting each other. Five of the
|
|
89
|
+
* eighty-two repositories measured read that way, this product's own web application
|
|
90
|
+
* among them.
|
|
91
|
+
*
|
|
92
|
+
* The two answer different questions and the words do not care: "production ready" is
|
|
93
|
+
* a claim about readiness, and something the profile calls essential and cannot find
|
|
94
|
+
* is by definition blocking. The band is the thing that has to give.
|
|
95
|
+
*/
|
|
82
96
|
function computeMaturity(score, coverage, openCriticals = 0) {
|
|
83
97
|
if (score <= 39)
|
|
84
98
|
return 'prototype';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@produtype/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.57.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": {
|