@produtype/core 0.45.0 → 0.46.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.
@@ -287,6 +287,15 @@ function buildReport(analysis, options) {
287
287
  const maturityLevel = overallScore === null
288
288
  ? 'inconclusive'
289
289
  : (0, score_1.computeMaturity)(overallScore, coverage, openCriticals);
290
+ /**
291
+ * The observed half goes with it.
292
+ *
293
+ * "Overall score: not scored" printed directly above "Observed score: 100/100" —
294
+ * the report refusing to characterise a repository and then praising it in the next
295
+ * line. The observed number is the same arithmetic over the same three checks: if
296
+ * it cannot support a reading it cannot support half of one either.
297
+ */
298
+ const reportedObservedScore = inconclusive ? null : observedScore;
290
299
  const findingsByCategory = Object.fromEntries(types_1.CATEGORIES.map((c) => [c, []]));
291
300
  for (const f of findings)
292
301
  findingsByCategory[f.category].push(f);
@@ -334,7 +343,7 @@ function buildReport(analysis, options) {
334
343
  categoryScores,
335
344
  profile: productProfile,
336
345
  maturity: maturityLevel,
337
- observedScore,
346
+ observedScore: reportedObservedScore,
338
347
  overallScore,
339
348
  inconclusive,
340
349
  inconclusiveReasons,
@@ -365,7 +374,7 @@ function buildReport(analysis, options) {
365
374
  return {
366
375
  projectPath: analysis.projectPath,
367
376
  generatedAt: new Date().toISOString(),
368
- observedScore,
377
+ observedScore: reportedObservedScore,
369
378
  expectedCapabilityScore: expectationScore,
370
379
  overallScore,
371
380
  maturityLevel,
@@ -2,7 +2,14 @@ import type { Category, Finding } from './types';
2
2
  export interface CategoryScore {
3
3
  category: Category;
4
4
  /** 0-100. 100 means nothing actionable was found in this category. */
5
- score: number;
5
+ /**
6
+ * `null` where nothing in the analysis touched this category.
7
+ *
8
+ * `notAssessed` has been beside it for a while and the number went on saying 100.
9
+ * On a report with no overall score at all, twelve categories still read 100 of 100
10
+ * — a repository the analyzer could not read, presented as perfect in twelve areas.
11
+ */
12
+ score: number | null;
6
13
  findingCount: number;
7
14
  /** Checks that ran and found what they were looking for. */
8
15
  verifiedCount: number;
@@ -103,9 +103,10 @@ function buildCategoryScores(findings) {
103
103
  */
104
104
  const worstOpen = actionable.reduce((worst, finding) => (SEVERITY_WEIGHT[finding.severity] > SEVERITY_WEIGHT[worst] ? finding.severity : worst), 'info');
105
105
  const ceiling = SEVERITY_CEILING[worstOpen];
106
+ const notAssessed = categoryFindings.every((finding) => finding.status === 'unknown');
106
107
  return {
107
108
  category,
108
- score: Math.max(0, Math.min(100, proportional, ceiling)),
109
+ score: notAssessed ? null : Math.max(0, Math.min(100, proportional, ceiling)),
109
110
  findingCount: actionable.length,
110
111
  /** Checks in this category that ran and found what they were looking for. */
111
112
  verifiedCount: assessed.filter((finding) => finding.status === 'passed').length,
@@ -122,7 +123,7 @@ function buildCategoryScores(findings) {
122
123
  * A Django school platform with no payments anywhere was reported as having
123
124
  * "no issues found in taking payments", as a strength.
124
125
  */
125
- notAssessed: categoryFindings.every((finding) => finding.status === 'unknown'),
126
+ notAssessed,
126
127
  };
127
128
  });
128
129
  }
@@ -130,6 +131,6 @@ function buildCategoryScores(findings) {
130
131
  function weakestCategories(scores, limit = 3) {
131
132
  return [...scores]
132
133
  .filter((entry) => !entry.notAssessed && entry.findingCount > 0)
133
- .sort((left, right) => left.score - right.score || right.criticalCount - left.criticalCount)
134
+ .sort((left, right) => (left.score ?? 100) - (right.score ?? 100) || right.criticalCount - left.criticalCount)
134
135
  .slice(0, limit);
135
136
  }
@@ -20,7 +20,7 @@ export declare function buildExecutiveSummary(args: {
20
20
  categoryScores: CategoryScore[];
21
21
  profile: ProductExpectationResult | undefined;
22
22
  maturity: MaturityLevel;
23
- observedScore: number;
23
+ observedScore: number | null;
24
24
  overallScore: number | null;
25
25
  inconclusive: boolean;
26
26
  /** Why, when the report could not form a reading. Named in the verdict. */
@@ -158,7 +158,7 @@ function buildStrengths(categoryScores, findings) {
158
158
  const passedCategories = new Set(passed.map((finding) => finding.category));
159
159
  const strongCategories = categoryScores
160
160
  .filter((entry) => !entry.notAssessed
161
- && entry.score >= 80
161
+ && (entry.score ?? 0) >= 80
162
162
  && entry.findingCount === 0
163
163
  && passedCategories.has(entry.category));
164
164
  /**
@@ -40,8 +40,10 @@ function profileSummary(report) {
40
40
  `- Selected profile: ${profile.selectedProfile}`,
41
41
  `- Profile: ${profile.profileTitle}`,
42
42
  `- Description: ${profile.profileDescription}`,
43
- `- Observed score: ${report.observedScore}/100`,
44
- `- Expected capability score: ${report.expectedCapabilityScore}/100`,
43
+ report.observedScore === null ? '- Observed score: not scored' : `- Observed score: ${report.observedScore}/100`,
44
+ report.expectedCapabilityScore === undefined
45
+ ? '- Expected capability score: not measured'
46
+ : `- Expected capability score: ${report.expectedCapabilityScore}/100`,
45
47
  report.overallScore === null ? '- Final score: not scored' : `- Final score: ${report.overallScore}/100`,
46
48
  '- Capability summary:',
47
49
  `- Required: ${requiredPresent} present / ${requiredMissing} missing / ${requiredPartial} partial`,
@@ -134,7 +136,7 @@ function categoryScoreSection(report) {
134
136
  */
135
137
  '| Area | Score | Verified | Open findings | Critical |',
136
138
  '| --- | --- | --- | --- | --- |',
137
- ...assessed.map((entry) => `| ${entry.category} | ${entry.score}/100 | ${entry.verifiedCount} of ${entry.assessedCount} | ${entry.findingCount} | ${entry.criticalCount} |`),
139
+ ...assessed.map((entry) => `| ${entry.category} | ${entry.score === null ? 'not scored' : `${entry.score}/100`} | ${entry.verifiedCount} of ${entry.assessedCount} | ${entry.findingCount} | ${entry.criticalCount} |`),
138
140
  '',
139
141
  ];
140
142
  }
@@ -222,7 +224,7 @@ function renderMarkdown(report) {
222
224
  '## Score',
223
225
  '',
224
226
  report.overallScore === null ? '- Overall score: not scored' : `- Overall score: ${report.overallScore}/100`,
225
- `- Observed score: ${report.observedScore}/100`,
227
+ report.observedScore === null ? '- Observed score: not scored' : `- Observed score: ${report.observedScore}/100`,
226
228
  ...(report.expectedCapabilityScore !== undefined ? [`- Expected capability score: ${report.expectedCapabilityScore}/100`] : []),
227
229
  `- Maturity level: ${report.maturityLevel}${report.inconclusive ? ' (inconclusive)' : ''}`,
228
230
  /**
@@ -238,7 +240,7 @@ function renderMarkdown(report) {
238
240
  : []),
239
241
  ...(report.inconclusive
240
242
  ? [
241
- '- Assessment: INCONCLUSIVE — the score is capped because the project could not be recognized:',
243
+ '- Assessment: INCONCLUSIVE — this report has no score, for these reasons:',
242
244
  ...report.inconclusiveReasons.map((r) => ` - ${r}`),
243
245
  ]
244
246
  : []),
@@ -88,7 +88,8 @@ export interface ReportDiagnostics {
88
88
  export interface ProductionReadinessReport {
89
89
  projectPath: string;
90
90
  generatedAt: string;
91
- observedScore: number;
91
+ /** `null` where the reading is inconclusive, for the same reason `overallScore` is. */
92
+ observedScore: number | null;
92
93
  expectedCapabilityScore?: number;
93
94
  /**
94
95
  * `null` where the reading is inconclusive, which is the same rule this product
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@produtype/core",
3
- "version": "0.45.0",
3
+ "version": "0.46.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": {