@produtype/core 0.17.0 → 0.18.1
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 +50 -1
- package/dist/report/buildReport.js +29 -9
- package/dist/report/score.d.ts +10 -0
- package/dist/report/score.js +11 -1
- package/package.json +1 -1
|
@@ -33,6 +33,54 @@ const phaseMeta = {
|
|
|
33
33
|
description: 'Tasks that are good candidates for safe templates or later AI-assisted remediation.',
|
|
34
34
|
},
|
|
35
35
|
};
|
|
36
|
+
/**
|
|
37
|
+
* Removes a task whose work is fully described by other tasks in the same plan.
|
|
38
|
+
*
|
|
39
|
+
* `gdpr.privacy` says "implement consent, export/erasure workflows, and retention
|
|
40
|
+
* policies", which is four GDPR capabilities restated as one sentence. Twelve of the
|
|
41
|
+
* seventy-eight repositories in the verification corpus got that step *and* all of the
|
|
42
|
+
* steps it summarises: a reader does the work and then meets an item telling them to do
|
|
43
|
+
* it. The catalogue has recorded the relationship since it was written and nothing read
|
|
44
|
+
* the field.
|
|
45
|
+
*
|
|
46
|
+
* Only when nothing the general task covers is left outstanding: every superseding
|
|
47
|
+
* capability is either in the plan already or was not a problem in the first place. With
|
|
48
|
+
* two of four present and the other two still open, the general task is the only step
|
|
49
|
+
* that reaches them, and dropping it would lose the work.
|
|
50
|
+
*
|
|
51
|
+
* The dropped task's finding keeps a step: its id moves onto the tasks that replace it,
|
|
52
|
+
* so nothing falls out of the plan's account of what it is answering.
|
|
53
|
+
*/
|
|
54
|
+
function dropSupersededTasks(states, actionableFindingIds) {
|
|
55
|
+
const plannedFindingIds = new Set(states.flatMap((state) => state.task.findingIds));
|
|
56
|
+
const survivors = [];
|
|
57
|
+
const reassigned = new Map();
|
|
58
|
+
for (const state of states) {
|
|
59
|
+
const superseders = state.supersededBy ?? [];
|
|
60
|
+
/**
|
|
61
|
+
* A capability that was never a problem needs no step, so it cannot be the reason
|
|
62
|
+
* the general task survives — but at least one of them has to be in the plan, or
|
|
63
|
+
* this task is the only thing pointing at the work. That is the observed-only case:
|
|
64
|
+
* no expectations ran, so none of the specific steps exist.
|
|
65
|
+
*/
|
|
66
|
+
const outstanding = superseders.filter((id) => !plannedFindingIds.has(id) && actionableFindingIds.has(id));
|
|
67
|
+
const anyPlanned = superseders.some((id) => plannedFindingIds.has(id));
|
|
68
|
+
const fullyCovered = superseders.length > 0 && anyPlanned && outstanding.length === 0;
|
|
69
|
+
if (!fullyCovered) {
|
|
70
|
+
survivors.push(state);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
for (const findingId of superseders) {
|
|
74
|
+
reassigned.set(findingId, [...(reassigned.get(findingId) ?? []), ...state.task.findingIds]);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return survivors.map((state) => {
|
|
78
|
+
const inherited = state.task.findingIds.flatMap((id) => reassigned.get(id) ?? []);
|
|
79
|
+
if (inherited.length === 0)
|
|
80
|
+
return state;
|
|
81
|
+
return { ...state, task: { ...state.task, findingIds: unique([...state.task.findingIds, ...inherited]) } };
|
|
82
|
+
});
|
|
83
|
+
}
|
|
36
84
|
function unique(values) {
|
|
37
85
|
return Array.from(new Set(values.filter(Boolean)));
|
|
38
86
|
}
|
|
@@ -163,10 +211,11 @@ function buildPlan(report) {
|
|
|
163
211
|
task: buildTask(seed, evidenceFiles),
|
|
164
212
|
phaseId: entry.phaseId,
|
|
165
213
|
severityRank: severityOrder[finding.severity],
|
|
214
|
+
supersededBy: entry.supersededBy,
|
|
166
215
|
});
|
|
167
216
|
}
|
|
168
217
|
}
|
|
169
|
-
const states = sortStates(Array.from(taskStates.values()));
|
|
218
|
+
const states = sortStates(dropSupersededTasks(Array.from(taskStates.values()), new Set(actionableFindings.map((finding) => finding.id))));
|
|
170
219
|
const tasks = states.map((state) => state.task);
|
|
171
220
|
const phases = buildPhases(states);
|
|
172
221
|
const quickWins = tasks.filter((task) => task.effort === 'small' && (task.risk === 'low' || task.risk === 'medium'));
|
|
@@ -127,15 +127,6 @@ function buildReport(analysis, options) {
|
|
|
127
127
|
|| analysis.stack.databases.length > 0
|
|
128
128
|
|| mobileDetected
|
|
129
129
|
|| gameEngineDetected;
|
|
130
|
-
const inconclusive = !stackDetected && analysis.workspaceStacks.length === 0;
|
|
131
|
-
const inconclusiveReasons = [];
|
|
132
|
-
if (inconclusive) {
|
|
133
|
-
inconclusiveReasons.push('No frontend, backend, or database stack signals were detected.');
|
|
134
|
-
inconclusiveReasons.push('No package manifest was found in any format this analyzer reads.');
|
|
135
|
-
if (analysis.files.source.length === 0) {
|
|
136
|
-
inconclusiveReasons.push('No recognizable source files were found.');
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
130
|
/**
|
|
140
131
|
* Coverage counts the expectations too, not only the observed rules.
|
|
141
132
|
*
|
|
@@ -150,6 +141,35 @@ function buildReport(analysis, options) {
|
|
|
150
141
|
const assessedChecks = findings.filter((f) => f.status !== 'unknown').length
|
|
151
142
|
+ Math.max(applicableExpectations - expectationFindings.length, 0);
|
|
152
143
|
const coverage = { passed: passedChecksForMaturity, assessed: assessedChecks };
|
|
144
|
+
const nothingIdentified = !stackDetected && analysis.workspaceStacks.length === 0;
|
|
145
|
+
/**
|
|
146
|
+
* Too little reached a verdict to say anything about this project.
|
|
147
|
+
*
|
|
148
|
+
* The flag used to ask whether a manifest existed, and the claim it makes is about
|
|
149
|
+
* what was learned. Two repositories with the same three verdicts came out at 39 and
|
|
150
|
+
* at 84, and the only difference between them was a `requirements.txt`: a Python
|
|
151
|
+
* script with one was judged, an equally small one without it was called unreadable.
|
|
152
|
+
*
|
|
153
|
+
* Five is where the corpus splits. Of seventy-eight repositories, eleven reach four
|
|
154
|
+
* verdicts or fewer and thirty reach ten or more; not one lands in between. Four
|
|
155
|
+
* verdicts cannot characterise a product, however many files it has.
|
|
156
|
+
*/
|
|
157
|
+
const tooLittleAssessed = assessedChecks < score_1.MIN_ASSESSED_FOR_A_READING;
|
|
158
|
+
const inconclusive = nothingIdentified || tooLittleAssessed;
|
|
159
|
+
// Each reason says which of the two it was, because they call for different things:
|
|
160
|
+
// one is a repository this analyzer cannot read, the other is one there is barely
|
|
161
|
+
// anything to read.
|
|
162
|
+
const inconclusiveReasons = [];
|
|
163
|
+
if (nothingIdentified) {
|
|
164
|
+
inconclusiveReasons.push('No frontend, backend, or database stack signals were detected.');
|
|
165
|
+
inconclusiveReasons.push('No package manifest was found in any format this analyzer reads.');
|
|
166
|
+
if (analysis.files.source.length === 0) {
|
|
167
|
+
inconclusiveReasons.push('No recognizable source files were found.');
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (tooLittleAssessed) {
|
|
171
|
+
inconclusiveReasons.push(`Only ${assessedChecks} check${assessedChecks === 1 ? '' : 's'} reached a verdict, which is too few to characterise this project.`);
|
|
172
|
+
}
|
|
153
173
|
// An unrecognized project has almost no applicable detectors, so the absence
|
|
154
174
|
// of findings must not be rewarded with a high score: cap it at prototype.
|
|
155
175
|
const scoreAfterInconclusive = inconclusive ? Math.min(combinedScore, 39) : combinedScore;
|
package/dist/report/score.d.ts
CHANGED
|
@@ -10,6 +10,16 @@ export interface ScoreCoverage {
|
|
|
10
10
|
passed: number;
|
|
11
11
|
assessed: number;
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* The fewest verdicts that can carry a reading at all.
|
|
15
|
+
*
|
|
16
|
+
* Below this the report has nothing to characterise: not a weak product, but a project
|
|
17
|
+
* this analyzer barely had anything to say about. In a corpus of seventy-eight
|
|
18
|
+
* repositories, eleven reach four verdicts or fewer and thirty reach ten or more —
|
|
19
|
+
* nothing lands in between, so the line sits in the gap rather than in the middle of a
|
|
20
|
+
* distribution.
|
|
21
|
+
*/
|
|
22
|
+
export declare const MIN_ASSESSED_FOR_A_READING = 5;
|
|
13
23
|
/**
|
|
14
24
|
* The highest score a report can reach when it may not claim the top band.
|
|
15
25
|
*
|
package/dist/report/score.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.TOP_BAND_CEILING = void 0;
|
|
3
|
+
exports.TOP_BAND_CEILING = exports.MIN_ASSESSED_FOR_A_READING = void 0;
|
|
4
4
|
exports.computeScore = computeScore;
|
|
5
5
|
exports.coverageSupportsTopBand = coverageSupportsTopBand;
|
|
6
6
|
exports.mayClaimTopBand = mayClaimTopBand;
|
|
@@ -28,6 +28,16 @@ function computeScore(findings) {
|
|
|
28
28
|
* nothing.
|
|
29
29
|
*/
|
|
30
30
|
const MIN_VERIFIED_FOR_PRODUCTION = 6;
|
|
31
|
+
/**
|
|
32
|
+
* The fewest verdicts that can carry a reading at all.
|
|
33
|
+
*
|
|
34
|
+
* Below this the report has nothing to characterise: not a weak product, but a project
|
|
35
|
+
* this analyzer barely had anything to say about. In a corpus of seventy-eight
|
|
36
|
+
* repositories, eleven reach four verdicts or fewer and thirty reach ten or more —
|
|
37
|
+
* nothing lands in between, so the line sits in the gap rather than in the middle of a
|
|
38
|
+
* distribution.
|
|
39
|
+
*/
|
|
40
|
+
exports.MIN_ASSESSED_FOR_A_READING = 5;
|
|
31
41
|
/**
|
|
32
42
|
* The highest score a report can reach when it may not claim the top band.
|
|
33
43
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@produtype/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.1",
|
|
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": {
|