prodlint 0.7.0 → 0.7.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/index.js CHANGED
@@ -71,7 +71,10 @@ function isTestFile(relativePath) {
71
71
  return /\.(test|spec)\.[jt]sx?$/.test(relativePath) || /(?:^|\/)__tests__\//.test(relativePath) || /(?:^|\/)tests?\//.test(relativePath) || /(?:^|\/)fixtures?\//.test(relativePath) || /(?:^|\/)mocks?\//.test(relativePath);
72
72
  }
73
73
  function isScriptFile(relativePath) {
74
- return /(?:^|\/)scripts?\//.test(relativePath);
74
+ if (/(?:^|\/)scripts?\//.test(relativePath)) return true;
75
+ const name = relativePath.split("/").pop() ?? "";
76
+ const base = name.replace(/\.[^.]+$/, "");
77
+ return /^(seed|migrate|setup|bootstrap|generate|codegen|sync|deploy|cleanup|reset)$/.test(base);
75
78
  }
76
79
  function isConfigFile(relativePath) {
77
80
  const name = relativePath.split("/").pop() ?? "";
@@ -240,6 +243,25 @@ function findLoopsAST(ast) {
240
243
  });
241
244
  return loops;
242
245
  }
246
+ function getImportSources(ast) {
247
+ const sources = [];
248
+ walkAST(ast.program, (node) => {
249
+ if (node.type === "ImportDeclaration") {
250
+ const decl = node;
251
+ sources.push({ source: decl.source.value, line: decl.loc?.start.line ?? 1 });
252
+ }
253
+ if (node.type === "CallExpression") {
254
+ const call = node;
255
+ if (call.callee.type === "Identifier" && call.callee.name === "require" && call.arguments.length === 1 && call.arguments[0].type === "StringLiteral") {
256
+ sources.push({ source: call.arguments[0].value, line: node.loc?.start.line ?? 1 });
257
+ }
258
+ if (call.callee.type === "Import" && call.arguments.length >= 1 && call.arguments[0].type === "StringLiteral") {
259
+ sources.push({ source: call.arguments[0].value, line: node.loc?.start.line ?? 1 });
260
+ }
261
+ }
262
+ });
263
+ return sources;
264
+ }
243
265
  function isUserInputNode(node) {
244
266
  if (node.type === "MemberExpression") {
245
267
  const mem = node;
@@ -434,6 +456,66 @@ async function readFileContext(root, relativePath) {
434
456
  return null;
435
457
  }
436
458
  }
459
+ async function getWorkspacePatterns(root, packageJson) {
460
+ const patterns = [];
461
+ if (packageJson) {
462
+ const workspaces = packageJson.workspaces;
463
+ if (Array.isArray(workspaces)) {
464
+ patterns.push(...workspaces);
465
+ } else if (workspaces && typeof workspaces === "object" && Array.isArray(workspaces.packages)) {
466
+ patterns.push(...workspaces.packages);
467
+ }
468
+ }
469
+ try {
470
+ const raw = await readFile(resolve(root, "pnpm-workspace.yaml"), "utf-8");
471
+ let inPackages = false;
472
+ for (const line of raw.split(/\r?\n/)) {
473
+ const trimmed = line.trim();
474
+ if (/^packages\s*:/.test(trimmed)) {
475
+ inPackages = true;
476
+ continue;
477
+ }
478
+ if (inPackages) {
479
+ if (/^-\s+/.test(trimmed)) {
480
+ const glob = trimmed.replace(/^-\s+/, "").replace(/^['"]|['"]$/g, "");
481
+ if (glob) patterns.push(glob);
482
+ } else if (trimmed && !trimmed.startsWith("#")) {
483
+ break;
484
+ }
485
+ }
486
+ }
487
+ } catch {
488
+ }
489
+ return patterns;
490
+ }
491
+ async function collectWorkspaceDependencies(root, patterns) {
492
+ const deps = /* @__PURE__ */ new Set();
493
+ const globPatterns = patterns.map((p) => `${p}/package.json`);
494
+ try {
495
+ const pkgFiles = await fg(globPatterns, {
496
+ cwd: root,
497
+ absolute: false,
498
+ ignore: ["**/node_modules/**"]
499
+ });
500
+ for (const pkgFile of pkgFiles) {
501
+ try {
502
+ const raw = await readFile(resolve(root, pkgFile), "utf-8");
503
+ const pkg = JSON.parse(raw);
504
+ if (pkg.name) deps.add(pkg.name);
505
+ for (const key of ["dependencies", "devDependencies", "peerDependencies"]) {
506
+ if (pkg[key] && typeof pkg[key] === "object") {
507
+ for (const dep of Object.keys(pkg[key])) {
508
+ deps.add(dep);
509
+ }
510
+ }
511
+ }
512
+ } catch {
513
+ }
514
+ }
515
+ } catch {
516
+ }
517
+ return deps;
518
+ }
437
519
  async function buildProjectContext(root, files) {
438
520
  let packageJson = null;
439
521
  let declaredDependencies = /* @__PURE__ */ new Set();
@@ -452,19 +534,26 @@ async function buildProjectContext(root, files) {
452
534
  ...packageJson?.peerDependencies ?? {}
453
535
  };
454
536
  declaredDependencies = new Set(Object.keys(deps));
455
- for (const dep of declaredDependencies) {
456
- const framework = DEPENDENCY_TO_FRAMEWORK[dep];
457
- if (framework) {
458
- detectedFrameworks.add(framework);
459
- }
537
+ } catch {
538
+ }
539
+ const workspacePatterns = await getWorkspacePatterns(root, packageJson);
540
+ if (workspacePatterns.length > 0) {
541
+ const workspaceDeps = await collectWorkspaceDependencies(root, workspacePatterns);
542
+ for (const dep of workspaceDeps) {
543
+ declaredDependencies.add(dep);
460
544
  }
461
- for (const framework of detectedFrameworks) {
462
- if (RATE_LIMIT_FRAMEWORKS.has(framework)) {
463
- hasRateLimiting = true;
464
- break;
465
- }
545
+ }
546
+ for (const dep of declaredDependencies) {
547
+ const framework = DEPENDENCY_TO_FRAMEWORK[dep];
548
+ if (framework) {
549
+ detectedFrameworks.add(framework);
550
+ }
551
+ }
552
+ for (const framework of detectedFrameworks) {
553
+ if (RATE_LIMIT_FRAMEWORKS.has(framework)) {
554
+ hasRateLimiting = true;
555
+ break;
466
556
  }
467
- } catch {
468
557
  }
469
558
  try {
470
559
  const raw = await readFile(resolve(root, "tsconfig.json"), "utf-8");
@@ -719,6 +808,32 @@ var hallucinatedImportsRule = {
719
808
  const findings = [];
720
809
  const seen = /* @__PURE__ */ new Set();
721
810
  const isNonProd = isTestFile(file.relativePath) || isScriptFile(file.relativePath);
811
+ if (file.ast) {
812
+ try {
813
+ const imports = getImportSources(file.ast);
814
+ for (const { source: importPath, line } of imports) {
815
+ if (importPath.startsWith(".") || importPath.startsWith("/")) continue;
816
+ const pkgName = getPackageName(importPath);
817
+ if (seen.has(pkgName)) continue;
818
+ seen.add(pkgName);
819
+ if (isPathAlias(importPath, project.tsconfigPaths)) continue;
820
+ if (isNodeBuiltin(pkgName)) continue;
821
+ if (IMPLICIT_PACKAGES.has(importPath) || IMPLICIT_PACKAGES.has(pkgName)) continue;
822
+ if (project.declaredDependencies.has(pkgName)) continue;
823
+ findings.push({
824
+ ruleId: "hallucinated-imports",
825
+ file: file.relativePath,
826
+ line,
827
+ column: 1,
828
+ message: `Package "${pkgName}" is imported but not in package.json`,
829
+ severity: isNonProd ? "warning" : "critical",
830
+ category: "reliability"
831
+ });
832
+ }
833
+ return findings;
834
+ } catch {
835
+ }
836
+ }
722
837
  for (let i = 0; i < file.lines.length; i++) {
723
838
  if (isCommentLine(file.lines, i, file.commentMap)) continue;
724
839
  const line = file.lines[i];
@@ -2250,8 +2365,14 @@ function scoreCatchBody(bodyLines) {
2250
2365
  const body = bodyLines.join("\n");
2251
2366
  let score = 0;
2252
2367
  if (/console\.(log|warn|error|info)\s*\(/.test(body)) score = 1;
2253
- if (/console\.(error|warn)\s*\(/.test(body) && /\b(err|error|e)\b/.test(body)) score = 2;
2254
- if (/\bthrow\b/.test(body) || /\breturn\b.*(?:error|err|status|Response|NextResponse|json)/.test(body) || /\bset\w*Error\s*\(/.test(body) || /\bres\.\w+\s*\(/.test(body)) {
2368
+ if ((/console\.(error|warn)\s*\(/.test(body) || /\b(logger|log)\.(error|warn)\s*\(/.test(body)) && /\b(err|error|e)\b/.test(body)) score = 2;
2369
+ if (/\bthrow\b/.test(body) || /\breturn\b.*(?:error|err|status|Response|NextResponse|json)/.test(body) || /\bset\w*Error\s*\(/.test(body) || /\bres\.\w+\s*\(/.test(body) || // Toast notifications (react-toastify, sonner, shadcn)
2370
+ /\btoast\.(error|warn|warning)\s*\(/.test(body) || /\btoast\s*\(\s*\{/.test(body) || // Error monitoring (Sentry, etc.)
2371
+ /\b(Sentry\.)?captureException\s*\(/.test(body) || /\b(Sentry\.)?captureMessage\s*\(/.test(body) || // Structured loggers
2372
+ /\b(logger|log)\.(error|fatal)\s*\(/.test(body) || // Error handler utilities
2373
+ /\b(handleError|reportError|logError|notifyError|showError|onError|trackError)\s*\(/.test(body) || // Express middleware: next(err)
2374
+ /\bnext\s*\(\s*(err|error|e)\s*\)/.test(body) || // Next.js notFound()
2375
+ /\bnotFound\s*\(/.test(body)) {
2255
2376
  score = 3;
2256
2377
  }
2257
2378
  const labels = {
@@ -2283,7 +2404,7 @@ var shallowCatchRule = {
2283
2404
  if (!body || !body.loc) return;
2284
2405
  const bodyStart = body.loc.start.line - 1;
2285
2406
  const bodyEnd = body.loc.end.line - 1;
2286
- const bodyLines = file.lines.slice(bodyStart + 1, bodyEnd);
2407
+ const bodyLines = bodyStart === bodyEnd ? [file.lines[bodyStart]] : file.lines.slice(bodyStart + 1, bodyEnd);
2287
2408
  const { score, label } = scoreCatchBody(bodyLines);
2288
2409
  if (score <= 1) {
2289
2410
  findings.push({
package/dist/mcp.js CHANGED
@@ -80,7 +80,10 @@ function isTestFile(relativePath) {
80
80
  return /\.(test|spec)\.[jt]sx?$/.test(relativePath) || /(?:^|\/)__tests__\//.test(relativePath) || /(?:^|\/)tests?\//.test(relativePath) || /(?:^|\/)fixtures?\//.test(relativePath) || /(?:^|\/)mocks?\//.test(relativePath);
81
81
  }
82
82
  function isScriptFile(relativePath) {
83
- return /(?:^|\/)scripts?\//.test(relativePath);
83
+ if (/(?:^|\/)scripts?\//.test(relativePath)) return true;
84
+ const name = relativePath.split("/").pop() ?? "";
85
+ const base = name.replace(/\.[^.]+$/, "");
86
+ return /^(seed|migrate|setup|bootstrap|generate|codegen|sync|deploy|cleanup|reset)$/.test(base);
84
87
  }
85
88
  function isConfigFile(relativePath) {
86
89
  const name = relativePath.split("/").pop() ?? "";
@@ -249,6 +252,25 @@ function findLoopsAST(ast) {
249
252
  });
250
253
  return loops;
251
254
  }
255
+ function getImportSources(ast) {
256
+ const sources = [];
257
+ walkAST(ast.program, (node) => {
258
+ if (node.type === "ImportDeclaration") {
259
+ const decl = node;
260
+ sources.push({ source: decl.source.value, line: decl.loc?.start.line ?? 1 });
261
+ }
262
+ if (node.type === "CallExpression") {
263
+ const call = node;
264
+ if (call.callee.type === "Identifier" && call.callee.name === "require" && call.arguments.length === 1 && call.arguments[0].type === "StringLiteral") {
265
+ sources.push({ source: call.arguments[0].value, line: node.loc?.start.line ?? 1 });
266
+ }
267
+ if (call.callee.type === "Import" && call.arguments.length >= 1 && call.arguments[0].type === "StringLiteral") {
268
+ sources.push({ source: call.arguments[0].value, line: node.loc?.start.line ?? 1 });
269
+ }
270
+ }
271
+ });
272
+ return sources;
273
+ }
252
274
  function isUserInputNode(node) {
253
275
  if (node.type === "MemberExpression") {
254
276
  const mem = node;
@@ -443,6 +465,66 @@ async function readFileContext(root, relativePath) {
443
465
  return null;
444
466
  }
445
467
  }
468
+ async function getWorkspacePatterns(root, packageJson) {
469
+ const patterns = [];
470
+ if (packageJson) {
471
+ const workspaces = packageJson.workspaces;
472
+ if (Array.isArray(workspaces)) {
473
+ patterns.push(...workspaces);
474
+ } else if (workspaces && typeof workspaces === "object" && Array.isArray(workspaces.packages)) {
475
+ patterns.push(...workspaces.packages);
476
+ }
477
+ }
478
+ try {
479
+ const raw = await readFile(resolve(root, "pnpm-workspace.yaml"), "utf-8");
480
+ let inPackages = false;
481
+ for (const line of raw.split(/\r?\n/)) {
482
+ const trimmed = line.trim();
483
+ if (/^packages\s*:/.test(trimmed)) {
484
+ inPackages = true;
485
+ continue;
486
+ }
487
+ if (inPackages) {
488
+ if (/^-\s+/.test(trimmed)) {
489
+ const glob = trimmed.replace(/^-\s+/, "").replace(/^['"]|['"]$/g, "");
490
+ if (glob) patterns.push(glob);
491
+ } else if (trimmed && !trimmed.startsWith("#")) {
492
+ break;
493
+ }
494
+ }
495
+ }
496
+ } catch {
497
+ }
498
+ return patterns;
499
+ }
500
+ async function collectWorkspaceDependencies(root, patterns) {
501
+ const deps = /* @__PURE__ */ new Set();
502
+ const globPatterns = patterns.map((p) => `${p}/package.json`);
503
+ try {
504
+ const pkgFiles = await fg(globPatterns, {
505
+ cwd: root,
506
+ absolute: false,
507
+ ignore: ["**/node_modules/**"]
508
+ });
509
+ for (const pkgFile of pkgFiles) {
510
+ try {
511
+ const raw = await readFile(resolve(root, pkgFile), "utf-8");
512
+ const pkg = JSON.parse(raw);
513
+ if (pkg.name) deps.add(pkg.name);
514
+ for (const key of ["dependencies", "devDependencies", "peerDependencies"]) {
515
+ if (pkg[key] && typeof pkg[key] === "object") {
516
+ for (const dep of Object.keys(pkg[key])) {
517
+ deps.add(dep);
518
+ }
519
+ }
520
+ }
521
+ } catch {
522
+ }
523
+ }
524
+ } catch {
525
+ }
526
+ return deps;
527
+ }
446
528
  async function buildProjectContext(root, files) {
447
529
  let packageJson = null;
448
530
  let declaredDependencies = /* @__PURE__ */ new Set();
@@ -461,19 +543,26 @@ async function buildProjectContext(root, files) {
461
543
  ...packageJson?.peerDependencies ?? {}
462
544
  };
463
545
  declaredDependencies = new Set(Object.keys(deps));
464
- for (const dep of declaredDependencies) {
465
- const framework = DEPENDENCY_TO_FRAMEWORK[dep];
466
- if (framework) {
467
- detectedFrameworks.add(framework);
468
- }
546
+ } catch {
547
+ }
548
+ const workspacePatterns = await getWorkspacePatterns(root, packageJson);
549
+ if (workspacePatterns.length > 0) {
550
+ const workspaceDeps = await collectWorkspaceDependencies(root, workspacePatterns);
551
+ for (const dep of workspaceDeps) {
552
+ declaredDependencies.add(dep);
469
553
  }
470
- for (const framework of detectedFrameworks) {
471
- if (RATE_LIMIT_FRAMEWORKS.has(framework)) {
472
- hasRateLimiting = true;
473
- break;
474
- }
554
+ }
555
+ for (const dep of declaredDependencies) {
556
+ const framework = DEPENDENCY_TO_FRAMEWORK[dep];
557
+ if (framework) {
558
+ detectedFrameworks.add(framework);
559
+ }
560
+ }
561
+ for (const framework of detectedFrameworks) {
562
+ if (RATE_LIMIT_FRAMEWORKS.has(framework)) {
563
+ hasRateLimiting = true;
564
+ break;
475
565
  }
476
- } catch {
477
566
  }
478
567
  try {
479
568
  const raw = await readFile(resolve(root, "tsconfig.json"), "utf-8");
@@ -728,6 +817,32 @@ var hallucinatedImportsRule = {
728
817
  const findings = [];
729
818
  const seen = /* @__PURE__ */ new Set();
730
819
  const isNonProd = isTestFile(file.relativePath) || isScriptFile(file.relativePath);
820
+ if (file.ast) {
821
+ try {
822
+ const imports = getImportSources(file.ast);
823
+ for (const { source: importPath, line } of imports) {
824
+ if (importPath.startsWith(".") || importPath.startsWith("/")) continue;
825
+ const pkgName = getPackageName(importPath);
826
+ if (seen.has(pkgName)) continue;
827
+ seen.add(pkgName);
828
+ if (isPathAlias(importPath, project.tsconfigPaths)) continue;
829
+ if (isNodeBuiltin(pkgName)) continue;
830
+ if (IMPLICIT_PACKAGES.has(importPath) || IMPLICIT_PACKAGES.has(pkgName)) continue;
831
+ if (project.declaredDependencies.has(pkgName)) continue;
832
+ findings.push({
833
+ ruleId: "hallucinated-imports",
834
+ file: file.relativePath,
835
+ line,
836
+ column: 1,
837
+ message: `Package "${pkgName}" is imported but not in package.json`,
838
+ severity: isNonProd ? "warning" : "critical",
839
+ category: "reliability"
840
+ });
841
+ }
842
+ return findings;
843
+ } catch {
844
+ }
845
+ }
731
846
  for (let i = 0; i < file.lines.length; i++) {
732
847
  if (isCommentLine(file.lines, i, file.commentMap)) continue;
733
848
  const line = file.lines[i];
@@ -2259,8 +2374,14 @@ function scoreCatchBody(bodyLines) {
2259
2374
  const body = bodyLines.join("\n");
2260
2375
  let score = 0;
2261
2376
  if (/console\.(log|warn|error|info)\s*\(/.test(body)) score = 1;
2262
- if (/console\.(error|warn)\s*\(/.test(body) && /\b(err|error|e)\b/.test(body)) score = 2;
2263
- if (/\bthrow\b/.test(body) || /\breturn\b.*(?:error|err|status|Response|NextResponse|json)/.test(body) || /\bset\w*Error\s*\(/.test(body) || /\bres\.\w+\s*\(/.test(body)) {
2377
+ if ((/console\.(error|warn)\s*\(/.test(body) || /\b(logger|log)\.(error|warn)\s*\(/.test(body)) && /\b(err|error|e)\b/.test(body)) score = 2;
2378
+ if (/\bthrow\b/.test(body) || /\breturn\b.*(?:error|err|status|Response|NextResponse|json)/.test(body) || /\bset\w*Error\s*\(/.test(body) || /\bres\.\w+\s*\(/.test(body) || // Toast notifications (react-toastify, sonner, shadcn)
2379
+ /\btoast\.(error|warn|warning)\s*\(/.test(body) || /\btoast\s*\(\s*\{/.test(body) || // Error monitoring (Sentry, etc.)
2380
+ /\b(Sentry\.)?captureException\s*\(/.test(body) || /\b(Sentry\.)?captureMessage\s*\(/.test(body) || // Structured loggers
2381
+ /\b(logger|log)\.(error|fatal)\s*\(/.test(body) || // Error handler utilities
2382
+ /\b(handleError|reportError|logError|notifyError|showError|onError|trackError)\s*\(/.test(body) || // Express middleware: next(err)
2383
+ /\bnext\s*\(\s*(err|error|e)\s*\)/.test(body) || // Next.js notFound()
2384
+ /\bnotFound\s*\(/.test(body)) {
2264
2385
  score = 3;
2265
2386
  }
2266
2387
  const labels = {
@@ -2292,7 +2413,7 @@ var shallowCatchRule = {
2292
2413
  if (!body || !body.loc) return;
2293
2414
  const bodyStart = body.loc.start.line - 1;
2294
2415
  const bodyEnd = body.loc.end.line - 1;
2295
- const bodyLines = file.lines.slice(bodyStart + 1, bodyEnd);
2416
+ const bodyLines = bodyStart === bodyEnd ? [file.lines[bodyStart]] : file.lines.slice(bodyStart + 1, bodyEnd);
2296
2417
  const { score, label } = scoreCatchBody(bodyLines);
2297
2418
  if (score <= 1) {
2298
2419
  findings.push({
package/package.json CHANGED
@@ -1,83 +1,83 @@
1
- {
2
- "name": "prodlint",
3
- "version": "0.7.0",
4
- "description": "The linter for vibe-coded apps — catch what AI coding tools miss",
5
- "license": "MIT",
6
- "author": "prodlint contributors",
7
- "type": "module",
8
- "repository": {
9
- "type": "git",
10
- "url": "https://github.com/prodlint/prodlint"
11
- },
12
- "bugs": {
13
- "url": "https://github.com/prodlint/prodlint/issues"
14
- },
15
- "homepage": "https://prodlint.com",
16
- "bin": {
17
- "prodlint": "./dist/cli.js",
18
- "prodlint-mcp": "./dist/mcp.js"
19
- },
20
- "main": "./dist/index.js",
21
- "types": "./dist/index.d.ts",
22
- "exports": {
23
- ".": {
24
- "import": "./dist/index.js",
25
- "types": "./dist/index.d.ts"
26
- },
27
- "./mcp": {
28
- "import": "./dist/mcp.js"
29
- }
30
- },
31
- "files": [
32
- "dist/**/*.js",
33
- "dist/**/*.d.ts",
34
- "action.yml"
35
- ],
36
- "scripts": {
37
- "build": "tsup",
38
- "dev": "tsup --watch",
39
- "test": "vitest run",
40
- "test:watch": "vitest",
41
- "lint": "node dist/cli.js .",
42
- "prepublishOnly": "npm run build"
43
- },
44
- "dependencies": {
45
- "@babel/parser": "^7.29.0",
46
- "@babel/types": "^7.29.0",
47
- "@modelcontextprotocol/sdk": "^1.26.0",
48
- "fast-glob": "^3.3.3",
49
- "picocolors": "^1.1.1",
50
- "zod": "^4.3.6"
51
- },
52
- "devDependencies": {
53
- "@types/node": "^25.2.3",
54
- "tsup": "^8.4.0",
55
- "typescript": "^5.7.0",
56
- "vitest": "^3.0.0"
57
- },
58
- "engines": {
59
- "node": ">=18"
60
- },
61
- "keywords": [
62
- "lint",
63
- "linter",
64
- "security",
65
- "ai",
66
- "ai-code",
67
- "vibe-coding",
68
- "production-readiness",
69
- "code-quality",
70
- "code-scanner",
71
- "next.js",
72
- "react",
73
- "typescript",
74
- "static-analysis",
75
- "cli",
76
- "scanner",
77
- "mcp",
78
- "mcp-server",
79
- "cursor",
80
- "copilot",
81
- "devtools"
82
- ]
83
- }
1
+ {
2
+ "name": "prodlint",
3
+ "version": "0.7.1",
4
+ "description": "The linter for vibe-coded apps — catch what AI coding tools miss",
5
+ "license": "MIT",
6
+ "author": "prodlint contributors",
7
+ "type": "module",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/prodlint/prodlint"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/prodlint/prodlint/issues"
14
+ },
15
+ "homepage": "https://prodlint.com",
16
+ "bin": {
17
+ "prodlint": "./dist/cli.js",
18
+ "prodlint-mcp": "./dist/mcp.js"
19
+ },
20
+ "main": "./dist/index.js",
21
+ "types": "./dist/index.d.ts",
22
+ "exports": {
23
+ ".": {
24
+ "import": "./dist/index.js",
25
+ "types": "./dist/index.d.ts"
26
+ },
27
+ "./mcp": {
28
+ "import": "./dist/mcp.js"
29
+ }
30
+ },
31
+ "files": [
32
+ "dist/**/*.js",
33
+ "dist/**/*.d.ts",
34
+ "action.yml"
35
+ ],
36
+ "scripts": {
37
+ "build": "tsup",
38
+ "dev": "tsup --watch",
39
+ "test": "vitest run",
40
+ "test:watch": "vitest",
41
+ "lint": "node dist/cli.js .",
42
+ "prepublishOnly": "npm run build"
43
+ },
44
+ "dependencies": {
45
+ "@babel/parser": "^7.29.0",
46
+ "@babel/types": "^7.29.0",
47
+ "@modelcontextprotocol/sdk": "^1.26.0",
48
+ "fast-glob": "^3.3.3",
49
+ "picocolors": "^1.1.1",
50
+ "zod": "^4.3.6"
51
+ },
52
+ "devDependencies": {
53
+ "@types/node": "^25.2.3",
54
+ "tsup": "^8.4.0",
55
+ "typescript": "^5.7.0",
56
+ "vitest": "^3.0.0"
57
+ },
58
+ "engines": {
59
+ "node": ">=18"
60
+ },
61
+ "keywords": [
62
+ "lint",
63
+ "linter",
64
+ "security",
65
+ "ai",
66
+ "ai-code",
67
+ "vibe-coding",
68
+ "production-readiness",
69
+ "code-quality",
70
+ "code-scanner",
71
+ "next.js",
72
+ "react",
73
+ "typescript",
74
+ "static-analysis",
75
+ "cli",
76
+ "scanner",
77
+ "mcp",
78
+ "mcp-server",
79
+ "cursor",
80
+ "copilot",
81
+ "devtools"
82
+ ]
83
+ }