@produtype/core 0.25.0 → 0.26.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.
@@ -596,7 +596,7 @@ async function analyzeProject(projectPath) {
596
596
  || w.packageManager !== 'unknown');
597
597
  const ctx = {
598
598
  root,
599
- files: { all: allFiles, source: sourceFiles, config: configFiles },
599
+ files: { all: allFiles, source: sourceFiles, config: configFiles, unreadable: unreadableLanguages(allFiles) },
600
600
  packageJson,
601
601
  pythonDeps,
602
602
  phpDeps: unique(phpDeps),
@@ -704,6 +704,7 @@ async function analyzeProject(projectPath) {
704
704
  pythonDeps,
705
705
  workspaceStacks,
706
706
  files: {
707
+ unreadable: unreadableLanguages(allFiles),
707
708
  all: allFiles,
708
709
  source: sourceFiles,
709
710
  config: configFiles,
@@ -86,6 +86,19 @@ export interface ProjectFiles {
86
86
  source: string[];
87
87
  /** Config files. */
88
88
  config: string[];
89
+ /**
90
+ * Files in languages nothing here can read, counted by language.
91
+ *
92
+ * The warning built from this has existed for a while and said the right thing —
93
+ * "1257 Elixir files were not analysed: this reading covers only part of the
94
+ * repository" — while the same report named a profile with high confidence and scored
95
+ * it 85. Exposing the count rather than only the sentence lets the reading take its
96
+ * own warning into account instead of printing it beside a verdict that ignores it.
97
+ */
98
+ unreadable: Array<{
99
+ language: string;
100
+ files: number;
101
+ }>;
89
102
  }
90
103
  export interface PackageJson {
91
104
  name?: string;
@@ -186,7 +186,18 @@ async function createProdkitMcpServer() {
186
186
  overallScore: report.overallScore,
187
187
  expectedCapabilityScore: report.expectedCapabilityScore,
188
188
  launchReady: report.executiveSummary.launchReady,
189
+ /**
190
+ * What the verdict beside it counts.
191
+ *
192
+ * This reported `requiredMissing` alone, so a row read "1 of 3 required
193
+ * missing" next to a sentence saying three essential capabilities are
194
+ * missing or incomplete. A capability half-built is not a capability that is
195
+ * there, which is why the verdict adds the two — and why a caller shown only
196
+ * one of them cannot reconcile the row with the sentence in it.
197
+ */
198
+ requiredBlocking: (report.productProfile?.gap.requiredMissing ?? 0) + (report.productProfile?.gap.requiredPartial ?? 0),
189
199
  requiredMissing: report.productProfile?.gap.requiredMissing ?? 0,
200
+ requiredPartial: report.productProfile?.gap.requiredPartial ?? 0,
190
201
  requiredTotal: report.productProfile?.gap.requiredTotal ?? 0,
191
202
  verdict: report.executiveSummary.verdict,
192
203
  };
@@ -169,7 +169,26 @@ function buildReport(analysis, options) {
169
169
  */
170
170
  const tooLittleAssessed = requestedProfile !== 'observed-only'
171
171
  && assessedChecks < score_1.MIN_ASSESSED_FOR_A_READING;
172
- const inconclusive = nothingIdentified || tooLittleAssessed;
172
+ /**
173
+ * The report reads its own warning.
174
+ *
175
+ * Plausible is a Phoenix application: 1257 Elixir files and 215 of JavaScript around
176
+ * them. The analyzer said so — "1257 Elixir files were not analysed: this reading
177
+ * covers only part of the repository" — and in the same report called it a client
178
+ * application, with high confidence, scored 85 and not inconclusive. A warning printed
179
+ * beside a verdict that ignores it is not a warning.
180
+ *
181
+ * The test is which part is bigger. Where the languages nothing here can read
182
+ * outnumber the files it did read, the reading rests on a minority of the repository
183
+ * and cannot characterise it — whatever those remaining files happen to look like. A
184
+ * game with a handful of C++ files beside its TypeScript is unaffected, which is the
185
+ * shape this must not break: not one of the seventy-eight repositories on the machine
186
+ * this was written on crosses the line, and the first public repository that did was
187
+ * the sixth one tried.
188
+ */
189
+ const unreadableFiles = analysis.files.unreadable.reduce((total, entry) => total + entry.files, 0);
190
+ const mostlyUnreadable = unreadableFiles > analysis.files.source.length;
191
+ const inconclusive = nothingIdentified || tooLittleAssessed || mostlyUnreadable;
173
192
  // Each reason says which of the two it was, because they call for different things:
174
193
  // one is a repository this analyzer cannot read, the other is one there is barely
175
194
  // anything to read.
@@ -181,6 +200,10 @@ function buildReport(analysis, options) {
181
200
  inconclusiveReasons.push('No recognizable source files were found.');
182
201
  }
183
202
  }
203
+ if (mostlyUnreadable) {
204
+ const [largest] = [...analysis.files.unreadable].sort((left, right) => right.files - left.files);
205
+ inconclusiveReasons.push(`Most of this repository is written in ${largest.language}, which this analyzer does not read: ${unreadableFiles} of its files were skipped and ${analysis.files.source.length} were read.`);
206
+ }
184
207
  if (tooLittleAssessed) {
185
208
  inconclusiveReasons.push(`Only ${assessedChecks} check${assessedChecks === 1 ? '' : 's'} reached a verdict, which is too few to characterise this project.`);
186
209
  }
@@ -261,6 +284,7 @@ function buildReport(analysis, options) {
261
284
  observedScore,
262
285
  overallScore,
263
286
  inconclusive,
287
+ inconclusiveReasons,
264
288
  });
265
289
  const expectationMode = determineExpectationMode(requestedProfile, productProfile, expectationScore);
266
290
  const diagnostics = {
@@ -23,4 +23,6 @@ export declare function buildExecutiveSummary(args: {
23
23
  observedScore: number;
24
24
  overallScore: number;
25
25
  inconclusive: boolean;
26
+ /** Why, when the report could not form a reading. Named in the verdict. */
27
+ inconclusiveReasons?: string[];
26
28
  }): ExecutiveSummary;
@@ -109,8 +109,19 @@ function estimateEffort(findings, profile) {
109
109
  }
110
110
  function buildVerdict(args) {
111
111
  if (args.inconclusive) {
112
+ /**
113
+ * The reason, where there is one worth giving.
114
+ *
115
+ * "Check that the path points at application source" is right for a directory with
116
+ * nothing in it and wrong for a Phoenix application: the path was fine, the language
117
+ * is one this analyzer does not read, and telling its author to check their path
118
+ * sends them looking for a mistake they did not make.
119
+ */
120
+ const [reason] = args.inconclusiveReasons;
112
121
  return {
113
- verdict: 'ProdKit could not recognise this project well enough to judge it. Check that the path points at application source.',
122
+ verdict: reason
123
+ ? `ProdKit could not judge this project. ${reason}`
124
+ : 'ProdKit could not recognise this project well enough to judge it. Check that the path points at application source.',
114
125
  launchReady: false,
115
126
  };
116
127
  }
@@ -172,6 +183,7 @@ function buildExecutiveSummary(args) {
172
183
  profile: args.profile,
173
184
  maturity: args.maturity,
174
185
  inconclusive: args.inconclusive,
186
+ inconclusiveReasons: args.inconclusiveReasons ?? [],
175
187
  });
176
188
  const topRisks = (0, categoryScores_1.weakestCategories)(args.categoryScores).map((entry) => {
177
189
  const label = CATEGORY_LABEL[entry.category];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@produtype/core",
3
- "version": "0.25.0",
3
+ "version": "0.26.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": {