mycontext-cli 4.2.0 → 4.2.2

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.
Files changed (92) hide show
  1. package/README.md +128 -133
  2. package/dist/README.md +128 -133
  3. package/dist/cli.js +76 -0
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/doctor.d.ts +18 -0
  6. package/dist/commands/doctor.d.ts.map +1 -0
  7. package/dist/commands/doctor.js +53 -0
  8. package/dist/commands/doctor.js.map +1 -0
  9. package/dist/commands/init.d.ts.map +1 -1
  10. package/dist/commands/init.js +99 -2
  11. package/dist/commands/init.js.map +1 -1
  12. package/dist/core/brain/BrainClient.d.ts +2 -0
  13. package/dist/core/brain/BrainClient.d.ts.map +1 -1
  14. package/dist/core/brain/BrainClient.js +19 -0
  15. package/dist/core/brain/BrainClient.js.map +1 -1
  16. package/dist/doctor/DoctorEngine.d.ts +5 -0
  17. package/dist/doctor/DoctorEngine.d.ts.map +1 -0
  18. package/dist/doctor/DoctorEngine.js +491 -0
  19. package/dist/doctor/DoctorEngine.js.map +1 -0
  20. package/dist/doctor/api.d.ts +4 -0
  21. package/dist/doctor/api.d.ts.map +1 -0
  22. package/dist/doctor/api.js +19 -0
  23. package/dist/doctor/api.js.map +1 -0
  24. package/dist/doctor/rules/dead-code-rules.d.ts +3 -0
  25. package/dist/doctor/rules/dead-code-rules.d.ts.map +1 -0
  26. package/dist/doctor/rules/dead-code-rules.js +269 -0
  27. package/dist/doctor/rules/dead-code-rules.js.map +1 -0
  28. package/dist/doctor/rules/index.d.ts +11 -0
  29. package/dist/doctor/rules/index.d.ts.map +1 -0
  30. package/dist/doctor/rules/index.js +21 -0
  31. package/dist/doctor/rules/index.js.map +1 -0
  32. package/dist/doctor/rules/nextjs-rules.d.ts +3 -0
  33. package/dist/doctor/rules/nextjs-rules.d.ts.map +1 -0
  34. package/dist/doctor/rules/nextjs-rules.js +357 -0
  35. package/dist/doctor/rules/nextjs-rules.js.map +1 -0
  36. package/dist/doctor/rules/node-rules.d.ts +3 -0
  37. package/dist/doctor/rules/node-rules.d.ts.map +1 -0
  38. package/dist/doctor/rules/node-rules.js +290 -0
  39. package/dist/doctor/rules/node-rules.js.map +1 -0
  40. package/dist/doctor/rules/turbo-rules.d.ts +3 -0
  41. package/dist/doctor/rules/turbo-rules.d.ts.map +1 -0
  42. package/dist/doctor/rules/turbo-rules.js +215 -0
  43. package/dist/doctor/rules/turbo-rules.js.map +1 -0
  44. package/dist/doctor/types.d.ts +87 -0
  45. package/dist/doctor/types.d.ts.map +1 -0
  46. package/dist/doctor/types.js +6 -0
  47. package/dist/doctor/types.js.map +1 -0
  48. package/dist/package.json +4 -4
  49. package/dist/services/ContextSyncer.d.ts +40 -0
  50. package/dist/services/ContextSyncer.d.ts.map +1 -0
  51. package/dist/services/ContextSyncer.js +219 -0
  52. package/dist/services/ContextSyncer.js.map +1 -0
  53. package/dist/services/ProjectScanner.d.ts +74 -0
  54. package/dist/services/ProjectScanner.d.ts.map +1 -0
  55. package/dist/services/ProjectScanner.js +343 -0
  56. package/dist/services/ProjectScanner.js.map +1 -0
  57. package/dist/services/ScaffoldAgent.d.ts +35 -0
  58. package/dist/services/ScaffoldAgent.d.ts.map +1 -0
  59. package/dist/services/ScaffoldAgent.js +228 -0
  60. package/dist/services/ScaffoldAgent.js.map +1 -0
  61. package/dist/tui/PlanningMode.d.ts +42 -0
  62. package/dist/tui/PlanningMode.d.ts.map +1 -0
  63. package/dist/tui/PlanningMode.js +401 -0
  64. package/dist/tui/PlanningMode.js.map +1 -0
  65. package/dist/tui/TUIClient.d.ts +20 -0
  66. package/dist/tui/TUIClient.d.ts.map +1 -0
  67. package/dist/tui/TUIClient.js +50 -0
  68. package/dist/tui/TUIClient.js.map +1 -0
  69. package/dist/types/mega-context.d.ts +118 -0
  70. package/dist/types/mega-context.d.ts.map +1 -0
  71. package/dist/types/mega-context.js +3 -0
  72. package/dist/types/mega-context.js.map +1 -0
  73. package/dist/types/tui.d.ts +29 -0
  74. package/dist/types/tui.d.ts.map +1 -0
  75. package/dist/types/tui.js +8 -0
  76. package/dist/types/tui.js.map +1 -0
  77. package/dist/types/unified-context.d.ts +102 -0
  78. package/dist/types/unified-context.d.ts.map +1 -0
  79. package/dist/types/unified-context.js +50 -0
  80. package/dist/types/unified-context.js.map +1 -0
  81. package/dist/utils/FileGenerator.d.ts +5 -0
  82. package/dist/utils/FileGenerator.d.ts.map +1 -1
  83. package/dist/utils/FileGenerator.js +48 -0
  84. package/dist/utils/FileGenerator.js.map +1 -1
  85. package/dist/utils/deepMerge.d.ts +14 -0
  86. package/dist/utils/deepMerge.d.ts.map +1 -0
  87. package/dist/utils/deepMerge.js +87 -0
  88. package/dist/utils/deepMerge.js.map +1 -0
  89. package/dist/utils/fileSystem.d.ts.map +1 -1
  90. package/dist/utils/fileSystem.js +32 -4
  91. package/dist/utils/fileSystem.js.map +1 -1
  92. package/package.json +24 -22
@@ -0,0 +1,357 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.nextjsRules = void 0;
37
+ /**
38
+ * Next.js Rules — Best practice checks for Next.js App Router projects
39
+ */
40
+ const path = __importStar(require("path"));
41
+ // ─── Helper ───────────────────────────────────────────────────────
42
+ function diag(rule, filePath, message, opts = {}) {
43
+ return {
44
+ ruleId: rule.id,
45
+ filePath,
46
+ line: opts.line,
47
+ severity: rule.severity,
48
+ message,
49
+ help: opts.help || rule.help,
50
+ autoFixable: opts.autoFixable ?? false,
51
+ };
52
+ }
53
+ // ─── Rules ────────────────────────────────────────────────────────
54
+ const missingRootLayout = {
55
+ id: "nextjs/missing-root-layout",
56
+ name: "Root Layout Required",
57
+ category: "nextjs",
58
+ severity: "error",
59
+ description: "Next.js App Router requires a root layout.tsx",
60
+ help: "Create app/layout.tsx with html and body tags wrapping {children}",
61
+ appliesTo: ["nextjs"],
62
+ async check(ctx) {
63
+ const results = [];
64
+ const hasLayout = await ctx.fileExists("app/layout.tsx") ||
65
+ await ctx.fileExists("app/layout.jsx") ||
66
+ await ctx.fileExists("src/app/layout.tsx") ||
67
+ await ctx.fileExists("src/app/layout.jsx");
68
+ if (!hasLayout) {
69
+ results.push(diag(this, "app/layout.tsx", "No root layout found — App Router requires app/layout.tsx"));
70
+ }
71
+ return results;
72
+ },
73
+ };
74
+ const layoutHasHtmlBody = {
75
+ id: "nextjs/layout-html-body",
76
+ name: "Layout Has HTML/Body",
77
+ category: "nextjs",
78
+ severity: "error",
79
+ description: "Root layout must wrap children in <html> and <body> tags",
80
+ help: "Add <html lang='en'><body>{children}</body></html> to root layout",
81
+ appliesTo: ["nextjs"],
82
+ async check(ctx) {
83
+ const results = [];
84
+ const layoutPaths = ["app/layout.tsx", "app/layout.jsx", "src/app/layout.tsx", "src/app/layout.jsx"];
85
+ for (const lp of layoutPaths) {
86
+ const content = await ctx.readFile(lp);
87
+ if (!content)
88
+ continue;
89
+ if (!content.includes("<html")) {
90
+ results.push(diag(this, lp, "Root layout missing <html> tag"));
91
+ }
92
+ if (!content.includes("<body")) {
93
+ results.push(diag(this, lp, "Root layout missing <body> tag"));
94
+ }
95
+ break; // only check the first layout found
96
+ }
97
+ return results;
98
+ },
99
+ };
100
+ const pageDefaultExport = {
101
+ id: "nextjs/page-default-export",
102
+ name: "Page Default Export",
103
+ category: "nextjs",
104
+ severity: "error",
105
+ description: "Page files must have a default export",
106
+ help: "Add 'export default function PageName()' to your page file",
107
+ appliesTo: ["nextjs"],
108
+ async check(ctx) {
109
+ const results = [];
110
+ const pages = await ctx.findFiles(/page\.(tsx|jsx|ts|js)$/);
111
+ for (const p of pages) {
112
+ const content = await ctx.readFile(p);
113
+ if (!content)
114
+ continue;
115
+ if (!content.includes("export default")) {
116
+ results.push(diag(this, p, `Page file missing default export`));
117
+ }
118
+ }
119
+ return results;
120
+ },
121
+ };
122
+ const clientDirective = {
123
+ id: "nextjs/client-directive",
124
+ name: "Client Directive Usage",
125
+ category: "nextjs",
126
+ severity: "warning",
127
+ description: "Files using hooks/browser APIs should have 'use client' directive",
128
+ help: "Add 'use client' at the top of files using useState, useEffect, onClick, etc.",
129
+ appliesTo: ["nextjs"],
130
+ async check(ctx) {
131
+ const results = [];
132
+ const clientSignals = [
133
+ /\buseState\b/, /\buseEffect\b/, /\buseRef\b/, /\buseReducer\b/,
134
+ /\buseCallback\b/, /\buseMemo\b/, /\buseContext\b/,
135
+ /\bonClick\b/, /\bonChange\b/, /\bonSubmit\b/,
136
+ /\bwindow\b/, /\bdocument\b/,
137
+ ];
138
+ const files = await ctx.findFiles(/\.(tsx|jsx)$/);
139
+ for (const f of files) {
140
+ // Skip layout/page files that could be server components
141
+ if (/layout\.(tsx|jsx)$/.test(f) && !f.includes("components"))
142
+ continue;
143
+ const content = await ctx.readFile(f);
144
+ if (!content)
145
+ continue;
146
+ if (content.includes('"use client"') || content.includes("'use client'"))
147
+ continue;
148
+ const needsClient = clientSignals.some(sig => sig.test(content));
149
+ if (needsClient) {
150
+ // Find which hook/API triggered it
151
+ const trigger = clientSignals.find(sig => sig.test(content));
152
+ const triggerName = trigger ? trigger.source.replace(/\\b/g, "") : "client API";
153
+ results.push(diag(this, f, `Uses ${triggerName} but missing "use client" directive`, {
154
+ autoFixable: true,
155
+ }));
156
+ }
157
+ }
158
+ return results;
159
+ },
160
+ async fix(ctx, d) {
161
+ const content = await ctx.readFile(d.filePath);
162
+ if (!content)
163
+ return false;
164
+ const newContent = `"use client";\n\n${content}`;
165
+ const abs = path.join(ctx.root, d.filePath);
166
+ const { writeFile } = await Promise.resolve().then(() => __importStar(require("fs-extra")));
167
+ await writeFile(abs, newContent);
168
+ return true;
169
+ },
170
+ };
171
+ const serverComponentHooks = {
172
+ id: "nextjs/server-component-hooks",
173
+ name: "No Hooks in Server Components",
174
+ category: "nextjs",
175
+ severity: "error",
176
+ description: "Server components cannot use React hooks",
177
+ help: "Add 'use client' directive or move hooks to a client component",
178
+ appliesTo: ["nextjs"],
179
+ async check(ctx) {
180
+ const results = [];
181
+ const hookPattern = /\b(useState|useEffect|useRef|useReducer|useCallback|useMemo|useContext)\s*\(/;
182
+ const serverFiles = await ctx.findFiles(/(page|layout)\.(tsx|jsx)$/);
183
+ for (const f of serverFiles) {
184
+ const content = await ctx.readFile(f);
185
+ if (!content)
186
+ continue;
187
+ if (content.includes('"use client"') || content.includes("'use client'"))
188
+ continue;
189
+ const hookPatternLocal = /\b(useState|useEffect|useRef|useReducer|useCallback|useMemo|useContext)\s*\(/;
190
+ const lines = content.split("\n");
191
+ for (let i = 0; i < lines.length; i++) {
192
+ const line = lines[i];
193
+ if (!line)
194
+ continue;
195
+ const match = hookPatternLocal.exec(line);
196
+ if (match && match[1]) {
197
+ results.push(diag(this, f, `Server component uses hook ${match[1]}()`, {
198
+ line: i + 1,
199
+ }));
200
+ }
201
+ }
202
+ }
203
+ return results;
204
+ },
205
+ };
206
+ const metadataExport = {
207
+ id: "nextjs/metadata-export",
208
+ name: "Metadata Export",
209
+ category: "nextjs",
210
+ severity: "warning",
211
+ description: "Pages/layouts should export metadata or generateMetadata for SEO",
212
+ help: "Add 'export const metadata = { title: ... }' or 'export async function generateMetadata()'",
213
+ appliesTo: ["nextjs"],
214
+ async check(ctx) {
215
+ const results = [];
216
+ const pages = await ctx.findFiles(/page\.(tsx|jsx|ts|js)$/);
217
+ for (const p of pages) {
218
+ const content = await ctx.readFile(p);
219
+ if (!content)
220
+ continue;
221
+ // Check if there's metadata or generateMetadata
222
+ const hasMetadata = content.includes("export const metadata") ||
223
+ content.includes("export async function generateMetadata") ||
224
+ content.includes("export function generateMetadata");
225
+ if (!hasMetadata) {
226
+ results.push(diag(this, p, "Page missing metadata export for SEO"));
227
+ }
228
+ }
229
+ return results;
230
+ },
231
+ };
232
+ const imageComponent = {
233
+ id: "nextjs/image-component",
234
+ name: "Use next/image",
235
+ category: "nextjs",
236
+ severity: "warning",
237
+ description: "Use next/image instead of <img> for optimized images",
238
+ help: "Import Image from 'next/image' and replace <img> tags",
239
+ appliesTo: ["nextjs"],
240
+ async check(ctx) {
241
+ const results = [];
242
+ const files = await ctx.findFiles(/\.(tsx|jsx)$/);
243
+ for (const f of files) {
244
+ const content = await ctx.readFile(f);
245
+ if (!content)
246
+ continue;
247
+ const lines = content.split("\n");
248
+ for (let i = 0; i < lines.length; i++) {
249
+ const line = lines[i];
250
+ if (!line)
251
+ continue;
252
+ if (/<img\s/i.test(line) && !line.includes("eslint-disable")) {
253
+ results.push(diag(this, f, "Uses <img> instead of next/image", { line: i + 1 }));
254
+ }
255
+ }
256
+ }
257
+ return results;
258
+ },
259
+ };
260
+ const linkComponent = {
261
+ id: "nextjs/link-component",
262
+ name: "Use next/link",
263
+ category: "nextjs",
264
+ severity: "warning",
265
+ description: "Use next/link for internal navigation instead of <a> tags",
266
+ help: "Import Link from 'next/link' and replace <a href='/...'> with <Link href='/...'>",
267
+ appliesTo: ["nextjs"],
268
+ async check(ctx) {
269
+ const results = [];
270
+ const files = await ctx.findFiles(/\.(tsx|jsx)$/);
271
+ for (const f of files) {
272
+ const content = await ctx.readFile(f);
273
+ if (!content)
274
+ continue;
275
+ const lines = content.split("\n");
276
+ for (let i = 0; i < lines.length; i++) {
277
+ const line = lines[i];
278
+ if (!line)
279
+ continue;
280
+ // Match <a href="/something"> (internal links)
281
+ const match = /<a\s[^>]*href=["'](\/[^"']*)/i.exec(line);
282
+ if (match && !line.includes("eslint-disable")) {
283
+ results.push(diag(this, f, `Uses <a> for internal link "${match[1] ?? ""}" instead of next/link`, { line: i + 1 }));
284
+ }
285
+ }
286
+ }
287
+ return results;
288
+ },
289
+ };
290
+ const routeHandlerMethods = {
291
+ id: "nextjs/route-handler-methods",
292
+ name: "Route Handler Exports",
293
+ category: "nextjs",
294
+ severity: "error",
295
+ description: "API route handlers must export named HTTP methods (GET, POST, etc.)",
296
+ help: "Export named functions: export async function GET(request) { ... }",
297
+ appliesTo: ["nextjs"],
298
+ async check(ctx) {
299
+ const results = [];
300
+ const routes = await ctx.findFiles(/route\.(ts|js|tsx|jsx)$/);
301
+ for (const r of routes) {
302
+ const content = await ctx.readFile(r);
303
+ if (!content)
304
+ continue;
305
+ const httpMethods = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
306
+ const hasMethod = httpMethods.some(m => content.includes(`export async function ${m}`) ||
307
+ content.includes(`export function ${m}`) ||
308
+ content.includes(`export const ${m}`));
309
+ if (!hasMethod) {
310
+ results.push(diag(this, r, "Route handler doesn't export any HTTP methods (GET/POST/etc.)"));
311
+ }
312
+ }
313
+ return results;
314
+ },
315
+ };
316
+ const loadingFiles = {
317
+ id: "nextjs/loading-states",
318
+ name: "Loading States",
319
+ category: "nextjs",
320
+ severity: "warning",
321
+ description: "Route groups with data fetching should have loading.tsx for better UX",
322
+ help: "Create loading.tsx alongside page.tsx for Suspense-based loading states",
323
+ appliesTo: ["nextjs"],
324
+ async check(ctx) {
325
+ const results = [];
326
+ const pages = await ctx.findFiles(/page\.(tsx|jsx)$/);
327
+ for (const p of pages) {
328
+ const content = await ctx.readFile(p);
329
+ if (!content)
330
+ continue;
331
+ // Check if page has async data fetching
332
+ const hasAsyncData = content.includes("async function") && (content.includes("fetch(") || content.includes("await "));
333
+ if (!hasAsyncData)
334
+ continue;
335
+ const dir = path.dirname(p);
336
+ const hasLoading = await ctx.fileExists(path.join(dir, "loading.tsx")) ||
337
+ await ctx.fileExists(path.join(dir, "loading.jsx"));
338
+ if (!hasLoading) {
339
+ results.push(diag(this, p, "Page with async data has no loading.tsx for Suspense"));
340
+ }
341
+ }
342
+ return results;
343
+ },
344
+ };
345
+ exports.nextjsRules = [
346
+ missingRootLayout,
347
+ layoutHasHtmlBody,
348
+ pageDefaultExport,
349
+ clientDirective,
350
+ serverComponentHooks,
351
+ metadataExport,
352
+ imageComponent,
353
+ linkComponent,
354
+ routeHandlerMethods,
355
+ loadingFiles,
356
+ ];
357
+ //# sourceMappingURL=nextjs-rules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nextjs-rules.js","sourceRoot":"","sources":["../../../src/doctor/rules/nextjs-rules.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { DoctorRule } from "../types";
2
+ export declare const nodeRules: DoctorRule[];
3
+ //# sourceMappingURL=node-rules.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-rules.d.ts","sourceRoot":"","sources":["../../../src/doctor/rules/node-rules.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAA2B,MAAM,UAAU,CAAC;AA6QpE,eAAO,MAAM,SAAS,EAAE,UAAU,EAQjC,CAAC"}
@@ -0,0 +1,290 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.nodeRules = void 0;
37
+ /**
38
+ * Node.js / Structure Rules — General project health checks
39
+ */
40
+ const path = __importStar(require("path"));
41
+ function diag(rule, filePath, message, opts = {}) {
42
+ return {
43
+ ruleId: rule.id,
44
+ filePath,
45
+ line: opts.line,
46
+ severity: rule.severity,
47
+ message,
48
+ help: opts.help || rule.help,
49
+ autoFixable: opts.autoFixable ?? false,
50
+ };
51
+ }
52
+ // ─── Rules ────────────────────────────────────────────────────────
53
+ const singleLockFile = {
54
+ id: "node/single-lock-file",
55
+ name: "Single Lock File",
56
+ category: "node",
57
+ severity: "error",
58
+ description: "Project should have only one package manager lock file",
59
+ help: "Delete the extra lock files and stick to one package manager",
60
+ appliesTo: ["node", "nextjs", "turbo"],
61
+ async check(ctx) {
62
+ const results = [];
63
+ if (ctx.isWorkspace)
64
+ return results; // Lock file belongs in root
65
+ const lockFiles = [
66
+ { file: "package-lock.json", pm: "npm" },
67
+ { file: "yarn.lock", pm: "yarn" },
68
+ { file: "pnpm-lock.yaml", pm: "pnpm" },
69
+ { file: "bun.lockb", pm: "bun" },
70
+ ];
71
+ const found = [];
72
+ for (const lf of lockFiles) {
73
+ if (await ctx.fileExists(lf.file))
74
+ found.push(lf.file);
75
+ }
76
+ if (found.length > 1) {
77
+ const firstFound = found[0];
78
+ if (firstFound) {
79
+ results.push(diag(this, firstFound, `Multiple lock files found: ${found.join(", ")}`, { autoFixable: true, help: "Keep only the one matching your package manager" }));
80
+ }
81
+ }
82
+ else if (found.length === 0) {
83
+ results.push(diag(this, "package.json", "No lock file found — run your package manager's install", { help: `Run \`${ctx.project.packageManager} install\` to generate a lock file` }));
84
+ }
85
+ return results;
86
+ },
87
+ };
88
+ const noNestedNodeModules = {
89
+ id: "node/no-nested-node-modules",
90
+ name: "No Nested node_modules",
91
+ category: "node",
92
+ severity: "warning",
93
+ description: "Nested node_modules directories can cause dependency resolution issues",
94
+ help: "Remove nested node_modules and use workspace hoisting",
95
+ appliesTo: ["node", "nextjs", "turbo"],
96
+ async check(ctx) {
97
+ const results = [];
98
+ // Check common nested locations
99
+ const suspectDirs = await ctx.findFiles(/node_modules\/.*\/node_modules/);
100
+ // findFiles looks at files, so let's check dirs manually
101
+ const commonNested = ["src/node_modules", "lib/node_modules", "app/node_modules"];
102
+ for (const nested of commonNested) {
103
+ if (await ctx.fileExists(nested)) {
104
+ results.push(diag(this, nested, `Nested node_modules at ${nested}`, {
105
+ autoFixable: true,
106
+ help: "Delete this directory — it was likely created by accident"
107
+ }));
108
+ }
109
+ }
110
+ return results;
111
+ },
112
+ };
113
+ const tsconfigStrict = {
114
+ id: "node/tsconfig-strict",
115
+ name: "TypeScript Strict Mode",
116
+ category: "node",
117
+ severity: "warning",
118
+ description: "TypeScript strict mode catches more bugs at compile time",
119
+ help: 'Enable "strict": true in tsconfig.json compilerOptions',
120
+ appliesTo: ["node", "nextjs"],
121
+ async check(ctx) {
122
+ const results = [];
123
+ if (!ctx.project.typescript)
124
+ return results;
125
+ const tsconfig = await ctx.readJson("tsconfig.json");
126
+ if (!tsconfig)
127
+ return results;
128
+ if (!tsconfig.compilerOptions?.strict) {
129
+ results.push(diag(this, "tsconfig.json", "TypeScript strict mode is disabled", {
130
+ autoFixable: true,
131
+ }));
132
+ }
133
+ return results;
134
+ },
135
+ };
136
+ const enginesField = {
137
+ id: "node/engines-field",
138
+ name: "Engines Field",
139
+ category: "node",
140
+ severity: "warning",
141
+ description: "package.json should specify Node.js engine version for consistency",
142
+ help: 'Add "engines": { "node": ">=18" } to package.json',
143
+ appliesTo: ["node", "nextjs", "turbo"],
144
+ async check(ctx) {
145
+ const results = [];
146
+ const pkg = await ctx.readJson("package.json");
147
+ if (!pkg)
148
+ return results;
149
+ if (!pkg.engines?.node) {
150
+ results.push(diag(this, "package.json", "Missing engines.node field", {
151
+ autoFixable: true,
152
+ }));
153
+ }
154
+ return results;
155
+ },
156
+ async fix(ctx, d) {
157
+ const pkg = await ctx.readJson("package.json");
158
+ if (!pkg)
159
+ return false;
160
+ pkg.engines = { ...pkg.engines, node: ">=18" };
161
+ const { writeJson } = await Promise.resolve().then(() => __importStar(require("fs-extra")));
162
+ await writeJson(path.join(ctx.root, "package.json"), pkg, { spaces: 2 });
163
+ return true;
164
+ },
165
+ };
166
+ const gitignoreComplete = {
167
+ id: "structure/gitignore",
168
+ name: "Complete .gitignore",
169
+ category: "node",
170
+ severity: "warning",
171
+ description: ".gitignore should cover common patterns",
172
+ help: "Add missing entries to .gitignore",
173
+ appliesTo: ["node", "nextjs", "turbo"],
174
+ async check(ctx) {
175
+ const results = [];
176
+ if (ctx.isWorkspace)
177
+ return results; // .gitignore belongs in root
178
+ const content = await ctx.readFile(".gitignore");
179
+ if (!content) {
180
+ results.push(diag(this, ".gitignore", "No .gitignore file found", {
181
+ autoFixable: true,
182
+ }));
183
+ return results;
184
+ }
185
+ const requiredPatterns = [
186
+ { pattern: "node_modules", label: "node_modules" },
187
+ { pattern: ".env", label: ".env files" },
188
+ ];
189
+ // Add Next.js-specific patterns
190
+ if (ctx.project.type === "nextjs") {
191
+ requiredPatterns.push({ pattern: ".next", label: ".next build dir" });
192
+ }
193
+ for (const { pattern, label } of requiredPatterns) {
194
+ if (!content.includes(pattern)) {
195
+ results.push(diag(this, ".gitignore", `Missing ${label} pattern in .gitignore`, {
196
+ autoFixable: true,
197
+ }));
198
+ }
199
+ }
200
+ return results;
201
+ },
202
+ };
203
+ const envExample = {
204
+ id: "structure/env-example",
205
+ name: "Environment Example",
206
+ category: "node",
207
+ severity: "warning",
208
+ description: "If .env exists, .env.example should too for team onboarding",
209
+ help: "Create .env.example with placeholder values for all env vars",
210
+ appliesTo: ["node", "nextjs"],
211
+ async check(ctx) {
212
+ const results = [];
213
+ const envFiles = [".env", ".env.local"];
214
+ let hasEnv = false;
215
+ for (const ef of envFiles) {
216
+ if (await ctx.fileExists(ef)) {
217
+ hasEnv = true;
218
+ break;
219
+ }
220
+ }
221
+ if (hasEnv) {
222
+ const hasExample = await ctx.fileExists(".env.example") ||
223
+ await ctx.fileExists(".env.local.example");
224
+ if (!hasExample) {
225
+ results.push(diag(this, ".env.example", "Has .env but no .env.example — teammates won't know which vars are needed"));
226
+ }
227
+ }
228
+ return results;
229
+ },
230
+ };
231
+ const noUnusedDeps = {
232
+ id: "node/unused-deps",
233
+ name: "No Unused Dependencies",
234
+ category: "node",
235
+ severity: "warning",
236
+ description: "Dependencies in package.json should be actually imported in code",
237
+ help: "Remove unused dependencies with 'npm uninstall <pkg>'",
238
+ appliesTo: ["node", "nextjs"],
239
+ async check(ctx) {
240
+ const results = [];
241
+ const pkg = await ctx.readJson("package.json");
242
+ if (!pkg?.dependencies)
243
+ return results;
244
+ // Skip common deps that are used implicitly
245
+ const implicitDeps = new Set([
246
+ "typescript", "@types/node", "@types/react", "@types/react-dom",
247
+ "eslint", "prettier", "tailwindcss", "postcss", "autoprefixer",
248
+ "next", "react", "react-dom", "turbo", "@next/font",
249
+ "encoding", "bufferutil", "utf-8-validate",
250
+ ]);
251
+ const allSourceFiles = await ctx.findFiles(/\.(ts|tsx|js|jsx|mjs|cjs)$/);
252
+ // Read up to 200 source files to check imports
253
+ const filesToCheck = allSourceFiles.slice(0, 200);
254
+ let allContent = "";
255
+ for (const f of filesToCheck) {
256
+ const content = await ctx.readFile(f);
257
+ if (content)
258
+ allContent += content + "\n";
259
+ }
260
+ const unused = [];
261
+ for (const dep of Object.keys(pkg.dependencies)) {
262
+ if (implicitDeps.has(dep))
263
+ continue;
264
+ // Check if dep name appears anywhere in source (lazy but effective)
265
+ const depBase = dep.startsWith("@") ? dep : dep.split("/")[0];
266
+ if (!allContent.includes(`"${depBase}"`) &&
267
+ !allContent.includes(`'${depBase}'`) &&
268
+ !allContent.includes(`from "${depBase}`) &&
269
+ !allContent.includes(`from '${depBase}`) &&
270
+ !allContent.includes(`require("${depBase}`) &&
271
+ !allContent.includes(`require('${depBase}`)) {
272
+ unused.push(dep);
273
+ }
274
+ }
275
+ if (unused.length > 0) {
276
+ results.push(diag(this, "package.json", `${unused.length} potentially unused deps: ${unused.slice(0, 5).join(", ")}${unused.length > 5 ? "..." : ""}`, { help: `Check: ${unused.join(", ")}` }));
277
+ }
278
+ return results;
279
+ },
280
+ };
281
+ exports.nodeRules = [
282
+ singleLockFile,
283
+ noNestedNodeModules,
284
+ tsconfigStrict,
285
+ enginesField,
286
+ gitignoreComplete,
287
+ envExample,
288
+ noUnusedDeps,
289
+ ];
290
+ //# sourceMappingURL=node-rules.js.map