@produtype/core 0.24.0 → 0.25.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.
@@ -1,2 +1,16 @@
1
1
  import type { ProjectAnalysis } from './types';
2
+ /**
3
+ * A path that is not there is not an empty repository.
4
+ *
5
+ * This check lived in the command-line tool alone, so every other caller — the MCP
6
+ * server an assistant drives, the hosted application, anyone using this as a library —
7
+ * got a report for a directory that does not exist: score 39, "no frontend, backend or
8
+ * database stack signals were detected", which reads as a verdict on a repository rather
9
+ * than a typo in a path. The command-line tool refused the same input outright.
10
+ *
11
+ * The guard belongs where the work starts, so every surface refuses it the same way.
12
+ */
13
+ export declare class ProjectPathError extends Error {
14
+ constructor(message: string);
15
+ }
2
16
  export declare function analyzeProject(projectPath: string): Promise<ProjectAnalysis>;
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.ProjectPathError = void 0;
36
37
  exports.analyzeProject = analyzeProject;
37
38
  const detectPackaging_1 = require("./detectPackaging");
38
39
  const importedDependencies_1 = require("./importedDependencies");
@@ -63,6 +64,7 @@ const detectAiSafety_1 = require("./detectAiSafety");
63
64
  const detectNotifications_1 = require("./detectNotifications");
64
65
  const detectDeployment_1 = require("./detectDeployment");
65
66
  const detectStack_1 = require("./detectStack");
67
+ const promises_1 = require("node:fs/promises");
66
68
  const packageJsonSchema = zod_1.z
67
69
  .object({
68
70
  name: zod_1.z.string().optional(),
@@ -283,8 +285,39 @@ function resolveWorkspacePackageManager(workspace) {
283
285
  return { manager: 'pip', confidence: 'manifest', warnings: [] };
284
286
  return { manager: 'unknown', confidence: 'unknown', warnings: [] };
285
287
  }
288
+ /**
289
+ * A path that is not there is not an empty repository.
290
+ *
291
+ * This check lived in the command-line tool alone, so every other caller — the MCP
292
+ * server an assistant drives, the hosted application, anyone using this as a library —
293
+ * got a report for a directory that does not exist: score 39, "no frontend, backend or
294
+ * database stack signals were detected", which reads as a verdict on a repository rather
295
+ * than a typo in a path. The command-line tool refused the same input outright.
296
+ *
297
+ * The guard belongs where the work starts, so every surface refuses it the same way.
298
+ */
299
+ class ProjectPathError extends Error {
300
+ constructor(message) {
301
+ super(message);
302
+ this.name = 'ProjectPathError';
303
+ }
304
+ }
305
+ exports.ProjectPathError = ProjectPathError;
306
+ async function assertReadableDirectory(root) {
307
+ let stats;
308
+ try {
309
+ stats = await (0, promises_1.stat)(root);
310
+ }
311
+ catch {
312
+ throw new ProjectPathError(`Project path does not exist: ${root}`);
313
+ }
314
+ if (!stats.isDirectory()) {
315
+ throw new ProjectPathError(`Project path is not a directory: ${root}`);
316
+ }
317
+ }
286
318
  async function analyzeProject(projectPath) {
287
319
  const root = path.resolve(projectPath);
320
+ await assertReadableDirectory(root);
288
321
  const allFiles = await (0, fileScanner_1.scanFiles)({ cwd: root });
289
322
  const sourceFiles = pickSource(allFiles);
290
323
  const configFiles = pickConfig(allFiles);
@@ -96,12 +96,40 @@ async function createProdkitMcpServer() {
96
96
  categoryScores: report.categoryScores,
97
97
  compliance: report.compliance,
98
98
  detectedStack: report.detectedStack,
99
- criticalIssues: report.criticalIssues.map((finding) => ({
99
+ /**
100
+ * Every finding, not only the critical ones.
101
+ *
102
+ * This returned `criticalIssues` alone, and most repositories have none — so an
103
+ * assistant asking for a report was handed a score, a summary and an empty
104
+ * list, while the category table beside it showed six areas with work
105
+ * outstanding. The findings are what the report is; the rest describes them.
106
+ */
107
+ findings: report.findings
108
+ .filter((finding) => finding.status !== 'passed' && finding.status !== 'unknown')
109
+ .map((finding) => ({
100
110
  id: finding.id,
101
111
  title: finding.title,
112
+ category: finding.category,
102
113
  severity: finding.severity,
114
+ status: finding.status,
115
+ confidence: finding.confidence,
116
+ description: finding.description,
103
117
  businessImpact: finding.businessImpact,
104
118
  recommendation: finding.recommendation,
119
+ /**
120
+ * The lines behind the claim, which is the thing this product is for.
121
+ *
122
+ * Nothing on this surface could show one: an assistant could say "your
123
+ * secrets have a fallback" and not where. Trimmed to three, because a
124
+ * model reading twenty-five copies of the same import learns nothing the
125
+ * first one did not tell it.
126
+ */
127
+ evidence: finding.evidence.slice(0, 3).map((item) => ({
128
+ type: item.type,
129
+ value: item.value,
130
+ ...(item.file ? { file: item.file } : {}),
131
+ ...(item.line ? { line: item.line } : {}),
132
+ })),
105
133
  })),
106
134
  gap: report.productProfile?.gap,
107
135
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@produtype/core",
3
- "version": "0.24.0",
3
+ "version": "0.25.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": {