@produtype/core 0.4.0 → 0.5.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.
package/README.md CHANGED
@@ -45,6 +45,17 @@ npm install -g @produtype/core
45
45
  prodkit analyze .
46
46
  ```
47
47
 
48
+ The MCP server needs one extra package, and only if you use it:
49
+
50
+ ```bash
51
+ npm install @modelcontextprotocol/sdk # for prodkit-mcp
52
+ ```
53
+
54
+ It is an optional peer dependency rather than a dependency because it brings 164
55
+ packages with it — nine tenths of what this package used to install — for a server most
56
+ people never run, along with network, shell and eval access that the analyzer itself
57
+ does not use. Installing this package alone brings 19.
58
+
48
59
  The command is `prodkit`; the package is `@produtype/core`. They differ on purpose:
49
60
  `prodkit` on npm is an unrelated and actively maintained package, so this one is
50
61
  published under the `@produtype` scope. `npm i prodkit` installs somebody else's
@@ -374,10 +374,18 @@ async function analyzeProject(projectPath) {
374
374
  * group and artifact, without the version — because every rule downstream asks which
375
375
  * library is used, never which release of it.
376
376
  *
377
- * Version catalogs (`libs.androidx.core.ktx`) are deliberately not resolved. They
378
- * name an alias defined in a TOML file, and following it would mean a second reader
379
- * for a second format to learn the same fact; a project using them reports fewer
380
- * libraries rather than wrong ones.
377
+ * Version catalogs are read too, from their own file rather than by following the
378
+ * alias. That decision was made the other way this morning and was wrong in
379
+ * practice: pointed at android/nowinandroid — Google's own sample, built to
380
+ * demonstrate offline-first — the analyzer reported no local database, because every
381
+ * module says `implementation(libs.room.runtime)` and the coordinates live in
382
+ * `gradle/libs.versions.toml`. Version catalogs are the recommended practice, so
383
+ * skipping them failed precisely on the projects that follow it.
384
+ *
385
+ * No alias resolution is needed: the catalog declares `group` and `name` outright.
386
+ * The cost is that a library declared in the catalog and used by no module is still
387
+ * reported, which is the direction to err in — the catalog is the project's own
388
+ * statement about what it builds with.
381
389
  */
382
390
  const gradleDeps = [];
383
391
  for (const file of allFiles.filter((f) => /(^|\/)build\.gradle(\.kts)?$/.test(f))) {
@@ -388,6 +396,23 @@ async function analyzeProject(projectPath) {
388
396
  gradleDeps.push(`${group}:${artifact}`.toLowerCase());
389
397
  }
390
398
  }
399
+ for (const file of allFiles.filter((f) => /(^|\/)libs\.versions\.toml$/.test(f))) {
400
+ const raw = (await (0, readTextFileSafe_1.readTextFileSafe)(root, file)) ?? '';
401
+ // Two spellings, both common: group and name as separate keys, or one `module`
402
+ // holding the coordinate. Order within the line varies, so each is matched on its
403
+ // own rather than as one pattern per line.
404
+ for (const line of raw.split('\n')) {
405
+ const module = /module\s*=\s*["']([^"':]+):([^"']+)["']/.exec(line);
406
+ if (module) {
407
+ gradleDeps.push(`${module[1]}:${module[2]}`.toLowerCase());
408
+ continue;
409
+ }
410
+ const group = /group\s*=\s*["']([^"']+)["']/.exec(line);
411
+ const name = /\bname\s*=\s*["']([^"']+)["']/.exec(line);
412
+ if (group && name)
413
+ gradleDeps.push(`${group[1]}:${name[1]}`.toLowerCase());
414
+ }
415
+ }
391
416
  /**
392
417
  * Swift packages, from Package.swift and the Podfile.
393
418
  *
@@ -1,3 +1,3 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- export declare function createProdkitMcpServer(): McpServer;
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function createProdkitMcpServer(): Promise<McpServer>;
3
3
  export declare function startProdkitMcpServer(): Promise<void>;
@@ -2,8 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createProdkitMcpServer = createProdkitMcpServer;
4
4
  exports.startProdkitMcpServer = startProdkitMcpServer;
5
- const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
6
- const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
7
5
  const zod_1 = require("zod");
8
6
  const analyzeProject_1 = require("../analyzer/analyzeProject");
9
7
  const buildReport_1 = require("../report/buildReport");
@@ -36,8 +34,36 @@ function errorResult(error) {
36
34
  content: [{ type: 'text', text: `ProdKit could not analyse that path: ${message}` }],
37
35
  };
38
36
  }
39
- function createProdkitMcpServer() {
40
- const server = new mcp_js_1.McpServer({ name: 'prodkit', version: version_1.PRODKit_VERSION });
37
+ /**
38
+ * The MCP SDK, loaded when the server starts rather than when this package is imported.
39
+ *
40
+ * It was a plain dependency, and it brought 164 of this package's 183 installed
41
+ * packages with it — so everyone installing the analyzer as a library or a CLI paid
42
+ * for a server they may never run, and inherited its supply-chain surface: network,
43
+ * shell and eval, none of which the analyzer itself does.
44
+ *
45
+ * The same shape the AI layer uses for the Anthropic SDK: a variable specifier so the
46
+ * TypeScript build does not need it, a webpackIgnore hint so a bundler does not try to
47
+ * resolve it, and an error that says exactly what to install when it is absent.
48
+ */
49
+ const SDK_MCP = '@modelcontextprotocol/sdk/server/mcp.js';
50
+ const SDK_STDIO = '@modelcontextprotocol/sdk/server/stdio.js';
51
+ async function loadMcpSdk() {
52
+ try {
53
+ const [mcp, stdio] = await Promise.all([
54
+ import(/* webpackIgnore: true */ SDK_MCP),
55
+ import(/* webpackIgnore: true */ SDK_STDIO),
56
+ ]);
57
+ return { McpServer: mcp.McpServer, StdioServerTransport: stdio.StdioServerTransport };
58
+ }
59
+ catch {
60
+ throw new Error('The MCP server needs the optional @modelcontextprotocol/sdk package. '
61
+ + 'Install it alongside this one: npm install @modelcontextprotocol/sdk');
62
+ }
63
+ }
64
+ async function createProdkitMcpServer() {
65
+ const { McpServer: Server } = await loadMcpSdk();
66
+ const server = new Server({ name: 'prodkit', version: version_1.PRODKit_VERSION });
41
67
  server.registerTool('list_profiles', {
42
68
  title: 'List product profiles',
43
69
  description: 'Lists the product profiles ProdKit can evaluate a repository against, with the capabilities each one expects.',
@@ -150,6 +176,7 @@ function createProdkitMcpServer() {
150
176
  return server;
151
177
  }
152
178
  async function startProdkitMcpServer() {
153
- const server = createProdkitMcpServer();
154
- await server.connect(new stdio_js_1.StdioServerTransport());
179
+ const { StdioServerTransport } = await loadMcpSdk();
180
+ const server = await createProdkitMcpServer();
181
+ await server.connect(new StdioServerTransport());
155
182
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@produtype/core",
3
- "version": "0.4.0",
3
+ "version": "0.5.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": {
@@ -43,12 +43,20 @@
43
43
  "node": ">=18"
44
44
  },
45
45
  "dependencies": {
46
- "@modelcontextprotocol/sdk": "^1.30.0",
47
46
  "commander": "^12.1.0",
48
47
  "fast-glob": "^3.3.2",
49
48
  "zod": "^3.23.8"
50
49
  },
50
+ "peerDependencies": {
51
+ "@modelcontextprotocol/sdk": "^1.30.0"
52
+ },
53
+ "peerDependenciesMeta": {
54
+ "@modelcontextprotocol/sdk": {
55
+ "optional": true
56
+ }
57
+ },
51
58
  "devDependencies": {
59
+ "@modelcontextprotocol/sdk": "^1.30.0",
52
60
  "@types/node": "^22.20.3",
53
61
  "@typescript-eslint/eslint-plugin": "^7.16.0",
54
62
  "@typescript-eslint/parser": "^7.16.0",