@produtype/core 0.54.0 → 0.56.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.
|
@@ -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,
|
|
@@ -17,6 +17,8 @@ export interface CategoryScore {
|
|
|
17
17
|
assessedCount: number;
|
|
18
18
|
criticalCount: number;
|
|
19
19
|
highCount: number;
|
|
20
|
+
/** Checks in this category that reached no verdict. */
|
|
21
|
+
unknownCount: number;
|
|
20
22
|
/** True when nothing in the analysis touched this category, so the score is not evidence of health. */
|
|
21
23
|
notAssessed: boolean;
|
|
22
24
|
}
|
|
@@ -112,6 +112,14 @@ function buildCategoryScores(findings) {
|
|
|
112
112
|
verifiedCount: assessed.filter((finding) => finding.status === 'passed').length,
|
|
113
113
|
/** Checks in this category that reached a verdict either way. */
|
|
114
114
|
assessedCount: assessed.length,
|
|
115
|
+
/**
|
|
116
|
+
* Checks in this category that did not.
|
|
117
|
+
*
|
|
118
|
+
* "Taking payments: 1 check verified, nothing outstanding" appeared under "what
|
|
119
|
+
* already works" on the strength of one check out of eleven in that category.
|
|
120
|
+
* The count that stops the misreading is the one that was missing.
|
|
121
|
+
*/
|
|
122
|
+
unknownCount: categoryFindings.filter((finding) => finding.status === 'unknown').length,
|
|
115
123
|
criticalCount: actionable.filter((finding) => finding.severity === 'critical').length,
|
|
116
124
|
highCount: actionable.filter((finding) => finding.severity === 'high').length,
|
|
117
125
|
/**
|
|
@@ -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)
|
|
@@ -211,7 +229,22 @@ function buildStrengths(categoryScores, findings) {
|
|
|
211
229
|
const strengths = strongCategories.slice(0, 3).map((entry) => {
|
|
212
230
|
const label = CATEGORY_LABEL[entry.category];
|
|
213
231
|
const checks = entry.verifiedCount === 1 ? '1 check' : `${entry.verifiedCount} checks`;
|
|
214
|
-
|
|
232
|
+
/**
|
|
233
|
+
* The coverage, not a claim of completeness.
|
|
234
|
+
*
|
|
235
|
+
* "Taking payments: 1 check verified, nothing outstanding" told a reader their
|
|
236
|
+
* billing was in good shape on the strength of one check. Forty-nine of ninety
|
|
237
|
+
* strengths across the verification corpus read that way. "Nothing outstanding"
|
|
238
|
+
* means "no finding among the checks that ran", and in a category where one check
|
|
239
|
+
* ran and ten did not, that is a sentence about this analyzer rather than about
|
|
240
|
+
* the product.
|
|
241
|
+
*
|
|
242
|
+
* Saying how many did not run is what stops the misreading, and it costs four
|
|
243
|
+
* words.
|
|
244
|
+
*/
|
|
245
|
+
return entry.unknownCount > 0
|
|
246
|
+
? `${label}: ${checks} verified, ${entry.unknownCount} not assessed`
|
|
247
|
+
: `${label}: ${checks} verified`;
|
|
215
248
|
});
|
|
216
249
|
if (strengths.length === 0 && passed.length > 0) {
|
|
217
250
|
strengths.push(`${passed.length} individual ${passed.length === 1 ? 'check' : 'checks'} already pass`);
|
|
@@ -250,7 +283,7 @@ function buildExecutiveSummary(args) {
|
|
|
250
283
|
* the same praise.
|
|
251
284
|
*/
|
|
252
285
|
strengths: args.inconclusive ? [] : buildStrengths(args.categoryScores, args.findings),
|
|
253
|
-
estimatedEffort: estimateEffort(args.findings, args.profile),
|
|
286
|
+
estimatedEffort: estimateEffort(args.findings, args.profile, args.inconclusive),
|
|
254
287
|
scoreExplanation,
|
|
255
288
|
};
|
|
256
289
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@produtype/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.56.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": {
|