codebrief 1.1.9 → 1.1.11

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
@@ -325,19 +325,30 @@ Now help me with: ...
325
325
 
326
326
  ## Detected Stacks
327
327
 
328
- | Category | Detected |
329
- | ------------------- | ------------------------------------------------------------------------------------------------------------ |
330
- | **Frameworks** | Next.js, Remix, SvelteKit, Astro, Nuxt.js, React, Vue.js, Svelte, Angular, NestJS, Express.js, Fastify, Hono |
331
- | **Language** | TypeScript (via `tsconfig.json` or dep), Python |
332
- | **CSS** | Tailwind CSS, styled-components, Emotion, SASS |
333
- | **UI Libraries** | shadcn/ui, Material UI, Ant Design, Chakra UI, Mantine |
334
- | **State** | TanStack Query, Zustand, Jotai, Redux Toolkit, MobX |
335
- | **Database/ORM** | Prisma, Drizzle, Mongoose, Supabase, Firebase |
336
- | **Testing** | Vitest, Jest, Playwright, Cypress |
337
- | **Deployment** | Vercel, Netlify, Railway, Docker |
338
- | **Package Manager** | npm, pnpm, yarn, bun |
339
- | **Python** | Django, FastAPI, Flask |
340
- | **Monorepo** | pnpm workspaces, Turborepo, Lerna, npm workspaces |
328
+ | Category | Detected |
329
+ | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
330
+ | **Frameworks** | Next.js, Remix, SvelteKit, Astro, Nuxt.js, Gatsby, SolidJS, Qwik, Eleventy, React, Vue.js, Svelte, Angular, NestJS, Express, Fastify, Hono, Koa, tRPC, Electron, React Native, Expo |
331
+ | **Languages** | TypeScript, JavaScript, Python, Go, Rust, Java, Kotlin, Ruby, PHP, C# |
332
+ | **CSS** | Tailwind CSS, styled-components, Emotion, SASS/SCSS, Vanilla Extract, UnoCSS |
333
+ | **UI Libraries** | shadcn/ui, Material UI, Ant Design, Chakra UI, Mantine, Headless UI, NextUI, DaisyUI |
334
+ | **State** | TanStack Query, Zustand, Jotai, Redux Toolkit, MobX, Pinia, Vuex, Recoil, Valtio |
335
+ | **Database/ORM** | Prisma, Drizzle, TypeORM, Sequelize, Knex, Mongoose, PostgreSQL, MySQL, SQLite, Supabase, Firebase, Redis, Elasticsearch, DynamoDB |
336
+ | **Auth** | NextAuth.js, Clerk, Supabase Auth, Passport.js, Lucia Auth, JWT |
337
+ | **API / Data** | tRPC, Axios, GraphQL (Apollo), SWR |
338
+ | **Validation** | Zod, Yup, Joi, TypeBox |
339
+ | **Testing** | Vitest, Jest, Playwright, Cypress, React Testing Library, Mocha, AVA |
340
+ | **Bundlers** | Vite, Webpack, esbuild, Rollup, Turbopack, tsup, SWC |
341
+ | **Linting** | ESLint, Biome, Prettier |
342
+ | **Deployment** | Vercel, Netlify, Railway, Fly.io, Render, Google Cloud, Serverless (AWS), Docker, Docker Compose |
343
+ | **Package Manager** | npm, pnpm, yarn, bun, cargo, go modules, pip/poetry, Maven, Gradle, bundler, composer, dotnet/NuGet |
344
+ | **Python** | Django, FastAPI, Flask, Streamlit |
345
+ | **Go** | Gin, Fiber, Echo, Chi |
346
+ | **Rust** | Actix Web, Axum, Rocket, Tauri |
347
+ | **Java/Kotlin** | Spring Boot, Quarkus, Ktor, Android |
348
+ | **Ruby** | Ruby on Rails, Sinatra |
349
+ | **PHP** | Laravel, Symfony, Slim |
350
+ | **C# / .NET** | ASP.NET Core, Blazor |
351
+ | **Monorepo** | pnpm workspaces, Turborepo, Lerna, npm workspaces |
341
352
 
342
353
  ---
343
354
 
@@ -359,9 +370,9 @@ codebrief/
359
370
  src/
360
371
  index.js ← CLI entry point, argument parsing, orchestration
361
372
  scanner.js ← Directory walker (respects .gitignore + .codebriefignore)
362
- analyzer.js ← Stack detection from package.json, lock files, config files
373
+ analyzer.js ← Stack detection: 10 languages, 50+ frameworks/tools
363
374
  generator.js ← Markdown/MDC generators + section parser for --update
364
- ai.js ← AI enhancement: file sampling, prompt builder, provider calls
375
+ ai.js ← AI enhancement: file sampling, prompt builder, 6 providers
365
376
  models.js ← All provider model lists — edit here to update/add models
366
377
  package.json
367
378
  README.md
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "codebrief",
3
- "version": "1.1.9",
4
- "description": "Generate AI context files for your project in seconds",
3
+ "version": "1.1.11",
4
+ "description": "Generate AI context files for your project in seconds — supports 10 languages, 50+ frameworks, Cursor rules & Copilot instructions",
5
5
  "main": "src/index.js",
6
6
  "bin": {
7
7
  "codebrief": "src/index.js"
@@ -13,11 +13,26 @@
13
13
  "keywords": [
14
14
  "ai",
15
15
  "cursor",
16
+ "copilot",
16
17
  "context",
17
18
  "developer-tools",
18
- "cli"
19
+ "cli",
20
+ "code-analysis",
21
+ "project-context",
22
+ "llm",
23
+ "openai",
24
+ "anthropic",
25
+ "groq",
26
+ "gemini",
27
+ "ollama"
19
28
  ],
29
+ "author": "Murat Hüdavendigâr Öncü",
20
30
  "license": "MIT",
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "https://github.com/murathudavendigar/codebrief"
34
+ },
35
+ "homepage": "https://www.npmjs.com/package/codebrief",
21
36
  "files": [
22
37
  "src/"
23
38
  ]
package/src/ai.js CHANGED
@@ -11,31 +11,57 @@ const IMPORTANT_PATTERNS = [
11
11
  "package.json",
12
12
  "tsconfig.json",
13
13
  "next.config.*",
14
+ "nuxt.config.*",
14
15
  "vite.config.*",
16
+ "webpack.config.*",
15
17
  "tailwind.config.*",
18
+ "postcss.config.*",
16
19
  "prisma/schema.prisma",
17
20
  "drizzle.config.*",
18
- "README.md",
21
+ ".env.example",
22
+ "docker-compose.*",
23
+ "Dockerfile",
19
24
  "src/index.*",
20
25
  "src/main.*",
21
26
  "src/app.*",
22
27
  "src/server.*",
28
+ "src/config.*",
29
+ "src/routes.*",
30
+ "src/middleware.*",
23
31
  "lib/db.*",
24
32
  "lib/auth.*",
33
+ "lib/utils.*",
25
34
  "app/layout.*",
26
35
  "app/page.*",
36
+ "app/api/**/route.*",
37
+ "pages/_app.*",
38
+ "pages/index.*",
39
+ "server/index.*",
40
+ "server/api/**",
41
+ "controllers/*",
42
+ "models/*",
43
+ "services/*",
27
44
  ];
28
45
 
29
- function sampleSourceFiles(rootDir, fileTree, charBudget = 18000) {
46
+ function sampleSourceFiles(rootDir, fileTree, charBudget = 32000) {
30
47
  const samples = [];
31
48
  let budget = charBudget;
32
49
 
33
- // First pass: prioritised files
50
+ // Smart read: for large files, take head + tail to capture imports AND exports/env vars
51
+ function smartRead(filePath, maxChars) {
52
+ const raw = fs.readFileSync(filePath, "utf-8");
53
+ if (raw.length <= maxChars) return raw;
54
+ const head = Math.floor(maxChars * 0.6);
55
+ const tail = maxChars - head - 20; // 20 for separator
56
+ return raw.slice(0, head) + "\n// ... (truncated) ...\n" + raw.slice(-tail);
57
+ }
58
+
59
+ // First pass: prioritised files (generous budget — these are the most important)
34
60
  for (const pattern of IMPORTANT_PATTERNS) {
35
61
  const full = path.join(rootDir, pattern);
36
62
  if (fs.existsSync(full)) {
37
63
  try {
38
- const content = fs.readFileSync(full, "utf-8").slice(0, 3000);
64
+ const content = smartRead(full, 6000);
39
65
  samples.push({ file: pattern, content });
40
66
  budget -= content.length;
41
67
  if (budget <= 0) break;
@@ -68,18 +94,17 @@ function sampleSourceFiles(rootDir, fileTree, charBudget = 18000) {
68
94
  const ext = path.extname(entry.name).toLowerCase();
69
95
  if (!sourceExts.has(ext)) continue;
70
96
 
71
- const full = path.join(rootDir, entry.relativePath || entry.name);
97
+ const full = path.join(rootDir, entry.path || entry.name);
72
98
  // Skip already-read files
73
99
  if (samples.some((s) => full.endsWith(s.file))) continue;
74
100
 
75
101
  try {
76
- const raw = fs.readFileSync(full, "utf-8");
77
- const snippet = raw.slice(0, 1500);
102
+ const content = smartRead(full, 4000);
78
103
  samples.push({
79
- file: entry.relativePath || entry.name,
80
- content: snippet,
104
+ file: entry.path || entry.name,
105
+ content,
81
106
  });
82
- budget -= snippet.length;
107
+ budget -= content.length;
83
108
  } catch {
84
109
  /* skip */
85
110
  }
@@ -88,14 +113,95 @@ function sampleSourceFiles(rootDir, fileTree, charBudget = 18000) {
88
113
  return samples;
89
114
  }
90
115
 
116
+ // ── Env var extractor ────────────────────────────────────────
117
+ // Scans all source files for process.env.XXX references
118
+ function extractEnvVars(rootDir, fileTree) {
119
+ const envVars = new Map(); // name → [files]
120
+ const sourceExts = new Set([
121
+ ".js",
122
+ ".ts",
123
+ ".jsx",
124
+ ".tsx",
125
+ ".vue",
126
+ ".svelte",
127
+ ".py",
128
+ ".go",
129
+ ".rs",
130
+ ".rb",
131
+ ".php",
132
+ ]);
133
+ const envRegex = /process\.env\.([A-Z_][A-Z0-9_]*)/g;
134
+ const dotenvRegex = /^([A-Z_][A-Z0-9_]*)=/gm;
135
+
136
+ for (const entry of fileTree) {
137
+ if (entry.type !== "file") continue;
138
+ const ext = path.extname(entry.name).toLowerCase();
139
+ const name = entry.name.toLowerCase();
140
+ if (!sourceExts.has(ext) && !name.startsWith(".env")) continue;
141
+
142
+ const full = path.join(rootDir, entry.path || entry.name);
143
+ try {
144
+ const raw = fs.readFileSync(full, "utf-8");
145
+ const regex = name.startsWith(".env") ? dotenvRegex : envRegex;
146
+ let match;
147
+ while ((match = regex.exec(raw)) !== null) {
148
+ const varName = match[1];
149
+ // Skip generic placeholder names
150
+ if (varName === "XXX" || varName.length < 3) continue;
151
+ if (!envVars.has(varName)) envVars.set(varName, []);
152
+ const file = entry.path || entry.name;
153
+ if (!envVars.get(varName).includes(file))
154
+ envVars.get(varName).push(file);
155
+ }
156
+ } catch {
157
+ /* skip */
158
+ }
159
+ }
160
+
161
+ return envVars;
162
+ }
163
+
164
+ // ── Export extractor ─────────────────────────────────────────
165
+ // Scans all source files for key function/class exports
166
+ function extractExports(rootDir, fileTree) {
167
+ const exports = [];
168
+ const sourceExts = new Set([".js", ".ts", ".jsx", ".tsx"]);
169
+ const patterns = [
170
+ /(?:module\.exports\s*=\s*\{([^}]+)\})/g,
171
+ /(?:exports\.(\w+)\s*=)/g,
172
+ /(?:export\s+(?:default\s+)?(?:function|class|const|let|var)\s+(\w+))/g,
173
+ ];
174
+
175
+ for (const entry of fileTree) {
176
+ if (entry.type !== "file") continue;
177
+ const ext = path.extname(entry.name).toLowerCase();
178
+ if (!sourceExts.has(ext)) continue;
179
+
180
+ const full = path.join(rootDir, entry.path || entry.name);
181
+ try {
182
+ const raw = fs.readFileSync(full, "utf-8");
183
+ const file = entry.path || entry.name;
184
+ for (const regex of patterns) {
185
+ let match;
186
+ regex.lastIndex = 0;
187
+ while ((match = regex.exec(raw)) !== null) {
188
+ exports.push({ file, exports: match[1] || match[0] });
189
+ }
190
+ }
191
+ } catch {
192
+ /* skip */
193
+ }
194
+ }
195
+
196
+ return exports;
197
+ }
198
+
91
199
  // ── Prompt builder ───────────────────────────────────────────
92
- function buildPrompt(analysis, fileTree, fileSamples) {
200
+ function buildPrompt(analysis, fileTree, fileSamples, envVars, fileExports) {
93
201
  const fileList = fileTree
94
- .slice(0, 80)
202
+ .slice(0, 120)
95
203
  .map((e) =>
96
- e.type === "dir"
97
- ? ` ${e.relativePath || e.name}/`
98
- : ` ${e.relativePath || e.name}`,
204
+ e.type === "dir" ? ` ${e.path || e.name}/` : ` ${e.path || e.name}`,
99
205
  )
100
206
  .join("\n");
101
207
 
@@ -108,41 +214,79 @@ function buildPrompt(analysis, fileTree, fileSamples) {
108
214
  .map(([k, v]) => ` ${k}: ${v}`)
109
215
  .join("\n") || " (none)";
110
216
 
111
- return `You are a senior software architect performing a deep code review. A developer ran \`codebrief\` on their project. Your job is to write a CONTEXT.md that gives an AI coding assistant (Cursor, Copilot, etc.) enough knowledge to contribute code as if it had written the project itself.
217
+ const envVarsText =
218
+ envVars.size > 0
219
+ ? Array.from(envVars.entries())
220
+ .map(
221
+ ([name, files]) =>
222
+ `- \`${name}\` — used in ${files.map((f) => "`" + f + "`").join(", ")}`,
223
+ )
224
+ .join("\n")
225
+ : "(none found)";
112
226
 
113
- CRITICAL RULES:
114
- - Every claim must be grounded in the actual files and code samples provided. No generic filler.
115
- - Reference specific file paths (e.g. \`src/lib/auth.ts\`, \`app/api/users/route.ts\`) wherever possible.
116
- - If you cannot infer something from the provided code, omit that bullet entirely — do NOT guess or write placeholders.
117
- - Architecture Notes must name real functions, classes, modules, or patterns you actually observed, not vague descriptions.
118
- - Rules for AI must reflect patterns actually present in the source — not generic best practices.
227
+ const exportsText =
228
+ fileExports.length > 0
229
+ ? fileExports
230
+ .slice(0, 30)
231
+ .map((e) => `- \`${e.file}\`: ${e.exports}`)
232
+ .join("\n")
233
+ : "(none found)";
234
+
235
+ const systemMessage = `You are a world-class software architect. You read source code and produce extremely precise, file-grounded documentation. You NEVER write generic advice. Every sentence you write must cite a real file path, function name, or pattern visible in the code you are given. If you cannot ground a claim in the actual source, you omit it entirely.`;
236
+
237
+ const userMessage = `I ran \`codebrief\` and need you to write a CONTEXT.md that lets an AI code assistant (Cursor, Copilot) understand this project so well it can write production code immediately.
238
+
239
+ ---
240
+ ## HARD RULES (violating these = failure)
241
+
242
+ 1. **File-path grounding**: Every bullet in Architecture Notes, Rules for AI, and Never Do MUST reference at least one real file path or function/export name from the code samples. No exceptions.
243
+ 2. **No negatives**: NEVER write "X is not used", "the project does not have Y", "no database detected". If something doesn't exist, simply don't mention it.
244
+ 3. **No generic advice**: NEVER write vague statements like "follow best practices", "maintain code quality", "adhere to coding standards", "ensure security". These are worthless.
245
+ 4. **Omit, don't guess**: If you can't infer something from the actual code samples, omit that section/bullet entirely. Empty sections should be removed.
246
+ 5. **Specific > exhaustive**: 5 deeply specific bullets beat 15 vague ones.
247
+
248
+ ## BAD (never write like this)
249
+ - "Authentication and session logic are not explicitly handled within the project"
250
+ - "Adhere to the project's coding standards and best practices"
251
+ - "Regular security audits are essential"
252
+ - "Error handling mechanisms are crucial for a robust application"
253
+ - "The project follows a modular structure, enhancing maintainability"
254
+
255
+ ## GOOD (write like this)
256
+ - "CLI entry point is \`src/index.js:main()\` — parses flags via \`hasFlag()\`/\`getFlagValue()\`, calls \`scanDirectory()\` → \`analyzeProject()\` → \`generateContextFile()\` in sequence"
257
+ - "AI enhancement in \`src/ai.js:enhanceWithAI()\` samples up to 32k chars of source via \`sampleSourceFiles()\`, builds a structured prompt, dispatches to the selected provider (Groq/OpenAI/Anthropic/Gemini/Grok/Ollama)"
258
+ - "Never add npm dependencies — this project uses zero deps (native \`https\`, \`fs\`, \`path\` only). See \`package.json\` dependencies field is empty."
259
+ - "All color output uses the \`c\` object from \`src/index.js\` (ANSI escape codes) — never use chalk or other color libraries"
119
260
 
261
+ ---
120
262
  ## Project metadata
121
263
  - Name: ${analysis.name}
122
264
  - Framework / Type: ${analysis.type}
123
265
  - Language: ${analysis.language}
124
266
  - Package manager: ${analysis.packageManager}
125
267
  - Stack: ${analysis.stack.join(", ") || "unknown"}
126
- - CSS framework: ${analysis.cssFramework || "none detected"}
127
- - UI library: ${analysis.uiLibrary || "none detected"}
128
- - State management: ${analysis.stateManagement || "none detected"}
129
- - Database / ORM: ${analysis.database || "none detected"}
130
- - Test framework: ${analysis.testFramework || "none detected"}
131
- - Deployment target: ${analysis.deployment || "unknown"}
268
+ - CSS: ${analysis.cssFramework || "none"} · UI: ${analysis.uiLibrary || "none"} · State: ${analysis.stateManagement || "none"}
269
+ - DB: ${analysis.database || "none"} · Tests: ${analysis.testFramework || "none"} · Deploy: ${analysis.deployment || "unknown"}
132
270
  - Monorepo: ${analysis.isMonorepo}
133
271
 
134
272
  ## Scripts
135
273
  ${scripts}
136
274
 
137
- ## File tree (up to 80 entries)
275
+ ## File tree
138
276
  ${fileList}
139
277
 
140
- ## Source file samples
278
+ ## Source code samples (READ CAREFULLY — this is your evidence)
141
279
  ${samplesText}
142
280
 
281
+ ## Environment variables found in code
282
+ ${envVarsText}
283
+
284
+ ## Module exports detected
285
+ ${exportsText}
286
+
143
287
  ---
144
288
 
145
- Produce the CONTEXT.md in exactly this structure:
289
+ Now produce the CONTEXT.md in EXACTLY this structure. Remove any section where you have nothing concrete to say. Keep the emoji in every section header EXACTLY as shown.
146
290
 
147
291
  # Project Context: ${analysis.name}
148
292
  > AI-enhanced by **codebrief** · ${new Date().toISOString().split("T")[0]}
@@ -150,44 +294,40 @@ Produce the CONTEXT.md in exactly this structure:
150
294
  ---
151
295
 
152
296
  ## 🧱 Tech Stack
153
- - List each technology and its specific role in this project (e.g. "Prisma — ORM used in \`src/db/\` for all database queries").
297
+ Bullet list. Each bullet: technology name + its specific role citing where it's used.
298
+ Example: "Node.js — runtime; entry point at \`src/index.js\`, all code is CommonJS with \`require()\`"
154
299
 
155
300
  ## 🚀 Key Files
156
- - List the 5–8 most important files a new developer should read first, with one sentence explaining what each does. Use exact paths from the file tree.
301
+ The 5–8 most important files to read first. Exact paths. One sentence each explaining what the file does and its key exports/functions.
157
302
 
158
303
  ## 📁 Folder Structure
159
- - Explain what each top-level folder is responsible for, inferred from the actual file tree.
304
+ One bullet per top-level directory, explaining its responsibility based on the actual files inside.
160
305
 
161
306
  ## 🔧 Scripts
162
- - Explain what each script actually does in the context of this project.
163
-
164
- ## 🗂️ Project Tree
165
- \`\`\`
166
- ${fileList}
167
- \`\`\`
307
+ One bullet per script. Say what it actually does, not just its command.
168
308
 
169
309
  ## 🏗️ Architecture Notes
170
- Write 8–15 bullet points. Each bullet must:
171
- - Name the specific file(s) or function(s) involved (e.g. "Auth flow starts in \`middleware.ts\`, validates JWT via \`src/lib/auth.ts:verifyToken()\`")
172
- - Describe a concrete data flow, pattern, or constraint — not a vague summary
173
- - Cover: request lifecycle, data access layer, auth/session, key abstractions, inter-module dependencies, anything surprising
310
+ 8–15 bullets. Each MUST:
311
+ - Name specific file(s), function(s), or export(s)
312
+ - Describe a concrete data flow, dependency, or design decision
313
+ - Be something useful for an AI about to write code in this project
174
314
 
175
315
  ## 🤖 Rules for AI
176
- Write 8–12 rules based on patterns you actually observed in the code. Example format:
177
- - "Always use the \`db\` instance from \`src/lib/db.ts\` never import Prisma directly"
178
- - "API routes live in \`app/api/\` and must use the response helper from \`src/lib/response.ts\`"
316
+ 8–12 rules extracted from the actual code patterns. Format:
317
+ - "Always/Never [specific action][file or pattern reference]"
179
318
 
180
319
  ## 🚫 Never Do
181
- Write 6–10 prohibitions inferred from the stack and code style. Be specific, not generic.
320
+ 6–10 prohibitions grounded in the codebase. Each must cite WHY (a file, pattern, or convention).
182
321
 
183
- ## 🔐 Environment & Security
184
- - List environment variables referenced in the code (exact names if found).
185
- - Note any auth patterns, token handling, or secrets management observed.
322
+ ## 🔐 Environment & Secrets
323
+ List actual env var names found in the code (e.g. \`GROQ_API_KEY\`, \`OPENAI_API_KEY\`). Describe how they're loaded and used. If none found, omit this section.
186
324
 
187
325
  ---
188
326
  *Re-run \`codebrief --ai\` after major refactors to keep this file current.*
189
327
 
190
- Respond with ONLY the Markdown. No preamble, no code fences wrapping the entire output.`;
328
+ Respond with ONLY the Markdown. No preamble, no wrapping code fences.`;
329
+
330
+ return { systemMessage, userMessage };
191
331
  }
192
332
 
193
333
  // ── HTTP helper (native, no deps) ────────────────────────────
@@ -233,6 +373,14 @@ async function callGroq(prompt, model) {
233
373
  " Get a free key in ~30s at https://console.groq.com",
234
374
  );
235
375
 
376
+ const messages =
377
+ typeof prompt === "string"
378
+ ? [{ role: "user", content: prompt }]
379
+ : [
380
+ { role: "system", content: prompt.systemMessage },
381
+ { role: "user", content: prompt.userMessage },
382
+ ];
383
+
236
384
  const res = await httpsPost(
237
385
  "api.groq.com",
238
386
  "/openai/v1/chat/completions",
@@ -242,9 +390,9 @@ async function callGroq(prompt, model) {
242
390
  },
243
391
  {
244
392
  model,
245
- messages: [{ role: "user", content: prompt }],
246
- temperature: 0.3,
247
- max_tokens: 4096,
393
+ messages,
394
+ temperature: 0.2,
395
+ max_tokens: 8192,
248
396
  },
249
397
  );
250
398
  return res.choices?.[0]?.message?.content || "";
@@ -256,6 +404,14 @@ async function callOpenAI(prompt, model) {
256
404
  if (!apiKey)
257
405
  throw new Error("OPENAI_API_KEY environment variable is not set.");
258
406
 
407
+ const messages =
408
+ typeof prompt === "string"
409
+ ? [{ role: "user", content: prompt }]
410
+ : [
411
+ { role: "system", content: prompt.systemMessage },
412
+ { role: "user", content: prompt.userMessage },
413
+ ];
414
+
259
415
  const res = await httpsPost(
260
416
  "api.openai.com",
261
417
  "/v1/chat/completions",
@@ -265,9 +421,9 @@ async function callOpenAI(prompt, model) {
265
421
  },
266
422
  {
267
423
  model,
268
- messages: [{ role: "user", content: prompt }],
269
- temperature: 0.3,
270
- max_tokens: 4096,
424
+ messages,
425
+ temperature: 0.2,
426
+ max_tokens: 8192,
271
427
  },
272
428
  );
273
429
  return res.choices?.[0]?.message?.content || "";
@@ -279,6 +435,20 @@ async function callAnthropic(prompt, model) {
279
435
  if (!apiKey)
280
436
  throw new Error("ANTHROPIC_API_KEY environment variable is not set.");
281
437
 
438
+ const messages =
439
+ typeof prompt === "string"
440
+ ? [{ role: "user", content: prompt }]
441
+ : [{ role: "user", content: prompt.userMessage }];
442
+
443
+ const system = typeof prompt === "string" ? undefined : prompt.systemMessage;
444
+
445
+ const body = {
446
+ model,
447
+ max_tokens: 8192,
448
+ messages,
449
+ };
450
+ if (system) body.system = system;
451
+
282
452
  const res = await httpsPost(
283
453
  "api.anthropic.com",
284
454
  "/v1/messages",
@@ -287,11 +457,7 @@ async function callAnthropic(prompt, model) {
287
457
  "x-api-key": apiKey,
288
458
  "anthropic-version": "2023-06-01",
289
459
  },
290
- {
291
- model,
292
- max_tokens: 4096,
293
- messages: [{ role: "user", content: prompt }],
294
- },
460
+ body,
295
461
  );
296
462
  return res.content?.[0]?.text || "";
297
463
  }
@@ -305,13 +471,18 @@ async function callGemini(prompt, model) {
305
471
  " Get a free key at https://aistudio.google.com/app/apikey",
306
472
  );
307
473
 
474
+ const fullText =
475
+ typeof prompt === "string"
476
+ ? prompt
477
+ : `${prompt.systemMessage}\n\n${prompt.userMessage}`;
478
+
308
479
  const res = await httpsPost(
309
480
  "generativelanguage.googleapis.com",
310
481
  `/v1beta/models/${model}:generateContent?key=${apiKey}`,
311
482
  { "Content-Type": "application/json" },
312
483
  {
313
- contents: [{ parts: [{ text: prompt }] }],
314
- generationConfig: { temperature: 0.3, maxOutputTokens: 4096 },
484
+ contents: [{ parts: [{ text: fullText }] }],
485
+ generationConfig: { temperature: 0.2, maxOutputTokens: 8192 },
315
486
  },
316
487
  );
317
488
  return res.candidates?.[0]?.content?.parts?.[0]?.text || "";
@@ -326,6 +497,14 @@ async function callGrok(prompt, model) {
326
497
  " Get a key at https://console.x.ai",
327
498
  );
328
499
 
500
+ const messages =
501
+ typeof prompt === "string"
502
+ ? [{ role: "user", content: prompt }]
503
+ : [
504
+ { role: "system", content: prompt.systemMessage },
505
+ { role: "user", content: prompt.userMessage },
506
+ ];
507
+
329
508
  const res = await httpsPost(
330
509
  "api.x.ai",
331
510
  "/v1/chat/completions",
@@ -335,9 +514,9 @@ async function callGrok(prompt, model) {
335
514
  },
336
515
  {
337
516
  model,
338
- messages: [{ role: "user", content: prompt }],
339
- temperature: 0.3,
340
- max_tokens: 4096,
517
+ messages,
518
+ temperature: 0.2,
519
+ max_tokens: 8192,
341
520
  },
342
521
  );
343
522
  return res.choices?.[0]?.message?.content || "";
@@ -347,7 +526,11 @@ async function callOllama(prompt, model) {
347
526
  model = model || getDefaultModel("ollama");
348
527
  // Ollama runs locally on port 11434 — use http
349
528
  const http = require("http");
350
- const body = JSON.stringify({ model, prompt, stream: false });
529
+ const fullText =
530
+ typeof prompt === "string"
531
+ ? prompt
532
+ : `${prompt.systemMessage}\n\n${prompt.userMessage}`;
533
+ const body = JSON.stringify({ model, prompt: fullText, stream: false });
351
534
 
352
535
  return new Promise((resolve, reject) => {
353
536
  const req = http.request(
@@ -396,7 +579,15 @@ async function enhanceWithAI(analysis, fileTree, rootDir, options = {}) {
396
579
  const { provider = "openai", model } = options;
397
580
 
398
581
  const fileSamples = sampleSourceFiles(rootDir, fileTree);
399
- const prompt = buildPrompt(analysis, fileTree, fileSamples);
582
+ const envVars = extractEnvVars(rootDir, fileTree);
583
+ const fileExports = extractExports(rootDir, fileTree);
584
+ const prompt = buildPrompt(
585
+ analysis,
586
+ fileTree,
587
+ fileSamples,
588
+ envVars,
589
+ fileExports,
590
+ );
400
591
 
401
592
  switch (provider.toLowerCase()) {
402
593
  case "groq":
package/src/analyzer.js CHANGED
@@ -29,6 +29,12 @@ function analyzeProject(rootDir) {
29
29
  stateManagement: null,
30
30
  database: null,
31
31
  deployment: null,
32
+ auth: null,
33
+ api: null,
34
+ bundler: null,
35
+ linter: null,
36
+ formatter: null,
37
+ validation: null,
32
38
  extraRules: [],
33
39
  isMonorepo: false,
34
40
  packages: [],
@@ -47,12 +53,17 @@ function analyzeProject(rootDir) {
47
53
  ...pkg.devDependencies,
48
54
  };
49
55
 
50
- // Framework detection
56
+ // Framework detection (order: full-stack first, then SPA, then backend)
51
57
  if (deps["next"]) result.type = "Next.js";
52
58
  else if (deps["@remix-run/react"]) result.type = "Remix";
53
59
  else if (deps["@sveltejs/kit"]) result.type = "SvelteKit";
54
60
  else if (deps["astro"]) result.type = "Astro";
55
61
  else if (deps["nuxt"]) result.type = "Nuxt.js";
62
+ else if (deps["gatsby"]) result.type = "Gatsby";
63
+ else if (deps["solid-js"]) result.type = "SolidJS";
64
+ else if (deps["@builder.io/qwik"]) result.type = "Qwik";
65
+ else if (deps["@eleventy/eleventy"] || deps["@11ty/eleventy"])
66
+ result.type = "Eleventy";
56
67
  else if (deps["react"]) result.type = "React";
57
68
  else if (deps["vue"]) result.type = "Vue.js";
58
69
  else if (deps["svelte"]) result.type = "Svelte";
@@ -61,6 +72,11 @@ function analyzeProject(rootDir) {
61
72
  else if (deps["express"]) result.type = "Express.js API";
62
73
  else if (deps["fastify"]) result.type = "Fastify API";
63
74
  else if (deps["hono"]) result.type = "Hono API";
75
+ else if (deps["koa"]) result.type = "Koa API";
76
+ else if (deps["@trpc/server"]) result.type = "tRPC API";
77
+ else if (deps["electron"]) result.type = "Electron";
78
+ else if (deps["react-native"]) result.type = "React Native";
79
+ else if (deps["expo"]) result.type = "Expo (React Native)";
64
80
 
65
81
  // Language
66
82
  if (deps["typescript"] || fileExists(rootDir, "tsconfig.json")) {
@@ -80,6 +96,10 @@ function analyzeProject(rootDir) {
80
96
  result.cssFramework = "Emotion CSS-in-JS";
81
97
  } else if (deps["sass"] || deps["node-sass"]) {
82
98
  result.cssFramework = "SASS/SCSS";
99
+ } else if (deps["@vanilla-extract/css"]) {
100
+ result.cssFramework = "Vanilla Extract";
101
+ } else if (deps["unocss"]) {
102
+ result.cssFramework = "UnoCSS";
83
103
  }
84
104
 
85
105
  // UI Library
@@ -99,6 +119,12 @@ function analyzeProject(rootDir) {
99
119
  result.uiLibrary = "Chakra UI";
100
120
  } else if (deps["@mantine/core"]) {
101
121
  result.uiLibrary = "Mantine";
122
+ } else if (deps["@headlessui/react"]) {
123
+ result.uiLibrary = "Headless UI";
124
+ } else if (deps["@nextui-org/react"]) {
125
+ result.uiLibrary = "NextUI";
126
+ } else if (deps["daisyui"]) {
127
+ result.uiLibrary = "DaisyUI";
102
128
  }
103
129
 
104
130
  // State management
@@ -115,6 +141,14 @@ function analyzeProject(rootDir) {
115
141
  result.stateManagement = "Redux Toolkit";
116
142
  } else if (deps["mobx"]) {
117
143
  result.stateManagement = "MobX";
144
+ } else if (deps["pinia"]) {
145
+ result.stateManagement = "Pinia (Vue)";
146
+ } else if (deps["vuex"]) {
147
+ result.stateManagement = "Vuex (Vue)";
148
+ } else if (deps["recoil"]) {
149
+ result.stateManagement = "Recoil";
150
+ } else if (deps["valtio"]) {
151
+ result.stateManagement = "Valtio";
118
152
  }
119
153
 
120
154
  // Test framework
@@ -122,6 +156,10 @@ function analyzeProject(rootDir) {
122
156
  else if (deps["jest"]) result.testFramework = "Jest";
123
157
  else if (deps["@playwright/test"]) result.testFramework = "Playwright";
124
158
  else if (deps["cypress"]) result.testFramework = "Cypress";
159
+ else if (deps["@testing-library/react"])
160
+ result.testFramework = "React Testing Library";
161
+ else if (deps["mocha"]) result.testFramework = "Mocha";
162
+ else if (deps["ava"]) result.testFramework = "AVA";
125
163
 
126
164
  // Database / ORM
127
165
  if (deps["prisma"] || deps["@prisma/client"]) {
@@ -137,7 +175,87 @@ function analyzeProject(rootDir) {
137
175
  result.database = "Supabase";
138
176
  } else if (deps["firebase"] || deps["firebase-admin"]) {
139
177
  result.database = "Firebase";
178
+ } else if (deps["typeorm"]) {
179
+ result.database = "TypeORM";
180
+ } else if (deps["sequelize"]) {
181
+ result.database = "Sequelize";
182
+ } else if (deps["knex"]) {
183
+ result.database = "Knex.js";
184
+ } else if (deps["pg"] || deps["mysql2"] || deps["better-sqlite3"]) {
185
+ result.database = deps["pg"]
186
+ ? "PostgreSQL (pg)"
187
+ : deps["mysql2"]
188
+ ? "MySQL"
189
+ : "SQLite";
190
+ } else if (deps["redis"] || deps["ioredis"]) {
191
+ result.database = "Redis";
192
+ } else if (deps["@elastic/elasticsearch"]) {
193
+ result.database = "Elasticsearch";
194
+ } else if (deps["dynamoose"] || deps["@aws-sdk/client-dynamodb"]) {
195
+ result.database = "DynamoDB";
140
196
  }
197
+
198
+ // Auth
199
+ if (deps["next-auth"] || deps["@auth/core"]) {
200
+ result.auth = "NextAuth.js (Auth.js)";
201
+ result.extraRules.push(
202
+ "Auth is managed by NextAuth.js. Use getServerSession() on the server.",
203
+ );
204
+ } else if (deps["@clerk/nextjs"] || deps["@clerk/clerk-react"]) {
205
+ result.auth = "Clerk";
206
+ } else if (deps["@supabase/auth-helpers-nextjs"]) {
207
+ result.auth = "Supabase Auth";
208
+ } else if (deps["passport"]) {
209
+ result.auth = "Passport.js";
210
+ } else if (deps["@lucia-auth/lucia"] || deps["lucia"]) {
211
+ result.auth = "Lucia Auth";
212
+ } else if (deps["jsonwebtoken"]) {
213
+ result.auth = "JWT (jsonwebtoken)";
214
+ }
215
+
216
+ // API / Data fetching
217
+ if (deps["@trpc/client"] || deps["@trpc/server"]) {
218
+ result.api = "tRPC";
219
+ result.extraRules.push(
220
+ "Use tRPC for all API communication. Never use raw fetch for internal APIs.",
221
+ );
222
+ } else if (deps["axios"]) {
223
+ result.api = "Axios";
224
+ } else if (deps["graphql"] || deps["@apollo/client"]) {
225
+ result.api = "GraphQL" + (deps["@apollo/client"] ? " (Apollo)" : "");
226
+ } else if (deps["swr"]) {
227
+ result.api = "SWR";
228
+ }
229
+
230
+ // Validation
231
+ if (deps["zod"]) {
232
+ result.validation = "Zod";
233
+ result.extraRules.push(
234
+ "Use Zod for all input validation and type inference.",
235
+ );
236
+ } else if (deps["yup"]) {
237
+ result.validation = "Yup";
238
+ } else if (deps["joi"]) {
239
+ result.validation = "Joi";
240
+ } else if (deps["@sinclair/typebox"]) {
241
+ result.validation = "TypeBox";
242
+ }
243
+
244
+ // Bundler / Build tool
245
+ if (deps["vite"]) result.bundler = "Vite";
246
+ else if (deps["webpack"]) result.bundler = "Webpack";
247
+ else if (deps["esbuild"]) result.bundler = "esbuild";
248
+ else if (deps["rollup"]) result.bundler = "Rollup";
249
+ else if (deps["turbopack"]) result.bundler = "Turbopack";
250
+ else if (deps["tsup"]) result.bundler = "tsup";
251
+ else if (deps["swc"]) result.bundler = "SWC";
252
+
253
+ // Linter / Formatter
254
+ if (deps["eslint"]) result.linter = "ESLint";
255
+ if (deps["@biomejs/biome"]) result.linter = "Biome";
256
+ if (deps["prettier"]) result.formatter = "Prettier";
257
+ if (deps["@biomejs/biome"] && !deps["prettier"])
258
+ result.formatter = "Biome";
141
259
  }
142
260
  }
143
261
 
@@ -157,28 +275,186 @@ function analyzeProject(rootDir) {
157
275
  fileExists(rootDir, "railway.toml")
158
276
  ) {
159
277
  result.deployment = "Railway";
278
+ } else if (fileExists(rootDir, "fly.toml")) {
279
+ result.deployment = "Fly.io";
280
+ } else if (fileExists(rootDir, "render.yaml")) {
281
+ result.deployment = "Render";
282
+ } else if (
283
+ fileExists(rootDir, "app.yaml") ||
284
+ fileExists(rootDir, "app.yml")
285
+ ) {
286
+ result.deployment = "Google Cloud (App Engine)";
287
+ } else if (
288
+ fileExists(rootDir, "serverless.yml") ||
289
+ fileExists(rootDir, "serverless.yaml")
290
+ ) {
291
+ result.deployment = "Serverless Framework (AWS)";
160
292
  } else if (fileExists(rootDir, "Dockerfile")) {
161
293
  result.deployment = "Docker";
294
+ } else if (
295
+ fileExists(rootDir, "docker-compose.yml") ||
296
+ fileExists(rootDir, "docker-compose.yaml")
297
+ ) {
298
+ result.deployment = "Docker Compose";
162
299
  }
163
300
 
164
301
  // ── Python project ────────────────────────────────────────────
165
302
  if (
166
303
  fileExists(rootDir, "requirements.txt") ||
167
- fileExists(rootDir, "pyproject.toml")
304
+ fileExists(rootDir, "pyproject.toml") ||
305
+ fileExists(rootDir, "setup.py") ||
306
+ fileExists(rootDir, "Pipfile")
168
307
  ) {
169
308
  result.language = "Python";
170
309
  if (fileExists(rootDir, "manage.py")) result.type = "Django";
171
- else if (fileExists(rootDir, "main.py")) {
172
- // try to detect fastapi
310
+ else if (fileExists(rootDir, "main.py") || fileExists(rootDir, "app.py")) {
173
311
  try {
174
- const req = fs.readFileSync(
175
- path.join(rootDir, "requirements.txt"),
312
+ const reqPath = fileExists(rootDir, "requirements.txt")
313
+ ? path.join(rootDir, "requirements.txt")
314
+ : null;
315
+ const pyprojectPath = fileExists(rootDir, "pyproject.toml")
316
+ ? path.join(rootDir, "pyproject.toml")
317
+ : null;
318
+ const content = reqPath
319
+ ? fs.readFileSync(reqPath, "utf-8")
320
+ : pyprojectPath
321
+ ? fs.readFileSync(pyprojectPath, "utf-8")
322
+ : "";
323
+ if (content.includes("fastapi")) result.type = "FastAPI";
324
+ else if (content.includes("flask")) result.type = "Flask";
325
+ else if (content.includes("streamlit")) result.type = "Streamlit";
326
+ } catch {}
327
+ }
328
+ if (fileExists(rootDir, "pyproject.toml"))
329
+ result.packageManager = "pip/poetry";
330
+ }
331
+
332
+ // ── Go project ──────────────────────────────────────────────
333
+ if (fileExists(rootDir, "go.mod")) {
334
+ result.language = "Go";
335
+ result.packageManager = "go modules";
336
+ try {
337
+ const goMod = fs.readFileSync(path.join(rootDir, "go.mod"), "utf-8");
338
+ if (goMod.includes("gin-gonic/gin")) result.type = "Gin (Go)";
339
+ else if (goMod.includes("gofiber/fiber")) result.type = "Fiber (Go)";
340
+ else if (goMod.includes("labstack/echo")) result.type = "Echo (Go)";
341
+ else if (goMod.includes("go-chi/chi")) result.type = "Chi (Go)";
342
+ else result.type = "Go application";
343
+ } catch {
344
+ result.type = "Go application";
345
+ }
346
+ }
347
+
348
+ // ── Rust project ────────────────────────────────────────────
349
+ if (fileExists(rootDir, "Cargo.toml")) {
350
+ result.language = "Rust";
351
+ result.packageManager = "cargo";
352
+ try {
353
+ const cargo = fs.readFileSync(path.join(rootDir, "Cargo.toml"), "utf-8");
354
+ if (cargo.includes("actix-web")) result.type = "Actix Web (Rust)";
355
+ else if (cargo.includes("axum")) result.type = "Axum (Rust)";
356
+ else if (cargo.includes("rocket")) result.type = "Rocket (Rust)";
357
+ else if (cargo.includes("tauri")) result.type = "Tauri (Rust)";
358
+ else result.type = "Rust application";
359
+ } catch {
360
+ result.type = "Rust application";
361
+ }
362
+ }
363
+
364
+ // ── Java / Kotlin project ───────────────────────────────────
365
+ if (
366
+ fileExists(rootDir, "pom.xml") ||
367
+ fileExists(rootDir, "build.gradle") ||
368
+ fileExists(rootDir, "build.gradle.kts")
369
+ ) {
370
+ result.language = fileExists(rootDir, "build.gradle.kts")
371
+ ? "Kotlin"
372
+ : "Java";
373
+ result.packageManager = fileExists(rootDir, "pom.xml") ? "Maven" : "Gradle";
374
+ if (fileExists(rootDir, "pom.xml")) {
375
+ try {
376
+ const pom = fs.readFileSync(path.join(rootDir, "pom.xml"), "utf-8");
377
+ if (pom.includes("spring-boot")) result.type = "Spring Boot";
378
+ else if (pom.includes("quarkus")) result.type = "Quarkus";
379
+ } catch {}
380
+ } else {
381
+ try {
382
+ const gradle = fs.readFileSync(
383
+ path.join(
384
+ rootDir,
385
+ fileExists(rootDir, "build.gradle.kts")
386
+ ? "build.gradle.kts"
387
+ : "build.gradle",
388
+ ),
176
389
  "utf-8",
177
390
  );
178
- if (req.includes("fastapi")) result.type = "FastAPI";
179
- else if (req.includes("flask")) result.type = "Flask";
391
+ if (gradle.includes("spring-boot")) result.type = "Spring Boot";
392
+ else if (gradle.includes("ktor")) result.type = "Ktor (Kotlin)";
393
+ else if (gradle.includes("android")) result.type = "Android";
180
394
  } catch {}
181
395
  }
396
+ if (result.type === "unknown")
397
+ result.type = `${result.language} application`;
398
+ }
399
+
400
+ // ── Ruby project ────────────────────────────────────────────
401
+ if (fileExists(rootDir, "Gemfile")) {
402
+ result.language = "Ruby";
403
+ result.packageManager = "bundler";
404
+ try {
405
+ const gemfile = fs.readFileSync(path.join(rootDir, "Gemfile"), "utf-8");
406
+ if (gemfile.includes("rails")) result.type = "Ruby on Rails";
407
+ else if (gemfile.includes("sinatra")) result.type = "Sinatra";
408
+ else result.type = "Ruby application";
409
+ } catch {
410
+ result.type = "Ruby application";
411
+ }
412
+ }
413
+
414
+ // ── PHP project ─────────────────────────────────────────────
415
+ if (fileExists(rootDir, "composer.json")) {
416
+ result.language = "PHP";
417
+ result.packageManager = "composer";
418
+ try {
419
+ const composer = readJson(path.join(rootDir, "composer.json"));
420
+ const req = {
421
+ ...(composer?.require || {}),
422
+ ...(composer?.["require-dev"] || {}),
423
+ };
424
+ if (req["laravel/framework"]) result.type = "Laravel";
425
+ else if (req["symfony/framework-bundle"]) result.type = "Symfony";
426
+ else if (req["slim/slim"]) result.type = "Slim (PHP)";
427
+ else result.type = "PHP application";
428
+ } catch {
429
+ result.type = "PHP application";
430
+ }
431
+ }
432
+
433
+ // ── C# / .NET project ──────────────────────────────────────
434
+ const csprojFiles = (() => {
435
+ try {
436
+ return fs
437
+ .readdirSync(rootDir)
438
+ .filter((f) => f.endsWith(".csproj") || f.endsWith(".sln"));
439
+ } catch {
440
+ return [];
441
+ }
442
+ })();
443
+ if (csprojFiles.length > 0) {
444
+ result.language = "C#";
445
+ result.packageManager = "dotnet / NuGet";
446
+ if (fileExists(rootDir, "Program.cs")) {
447
+ try {
448
+ const prog = fs.readFileSync(path.join(rootDir, "Program.cs"), "utf-8");
449
+ if (prog.includes("WebApplication")) result.type = "ASP.NET Core";
450
+ else if (prog.includes("Blazor")) result.type = "Blazor";
451
+ else result.type = ".NET application";
452
+ } catch {
453
+ result.type = ".NET application";
454
+ }
455
+ } else {
456
+ result.type = ".NET application";
457
+ }
182
458
  }
183
459
 
184
460
  // ── Folder conventions ────────────────────────────────────────
@@ -212,6 +488,32 @@ function analyzeProject(rootDir) {
212
488
  result.conventions.push("Middleware → /middleware");
213
489
  if (topDirs.includes("prisma"))
214
490
  result.conventions.push("Database schema → /prisma");
491
+ if (topDirs.includes("config"))
492
+ result.conventions.push("Configuration files → /config");
493
+ if (topDirs.includes("constants"))
494
+ result.conventions.push("Constants & enums → /constants");
495
+ if (topDirs.includes("layouts"))
496
+ result.conventions.push("Page layouts → /layouts");
497
+ if (topDirs.includes("features"))
498
+ result.conventions.push("Feature-based modules → /features");
499
+ if (topDirs.includes("modules"))
500
+ result.conventions.push("Domain modules → /modules");
501
+ if (topDirs.includes("pages"))
502
+ result.conventions.push("Page components → /pages");
503
+ if (topDirs.includes("routes"))
504
+ result.conventions.push("Route definitions → /routes");
505
+ if (topDirs.includes("controllers"))
506
+ result.conventions.push("Controllers → /controllers");
507
+ if (topDirs.includes("models"))
508
+ result.conventions.push("Data models → /models");
509
+ if (topDirs.includes("helpers"))
510
+ result.conventions.push("Helper functions → /helpers");
511
+ if (topDirs.includes("tests") || topDirs.includes("__tests__"))
512
+ result.conventions.push("Tests → /tests");
513
+ if (topDirs.includes("public") || topDirs.includes("static"))
514
+ result.conventions.push("Static assets → /public");
515
+ if (topDirs.includes("assets"))
516
+ result.conventions.push("Assets (images, fonts) → /assets");
215
517
 
216
518
  // ── Monorepo detection ──────────────────────────────────────
217
519
  const pkgJsonMonorepo = (() => {
@@ -256,8 +558,14 @@ function analyzeProject(rootDir) {
256
558
  result.cssFramework,
257
559
  result.uiLibrary,
258
560
  result.stateManagement,
561
+ result.auth,
562
+ result.api,
563
+ result.validation,
259
564
  result.testFramework,
260
565
  result.database,
566
+ result.bundler,
567
+ result.linter ? `Linter: ${result.linter}` : null,
568
+ result.formatter ? `Formatter: ${result.formatter}` : null,
261
569
  result.deployment ? `Deployed on ${result.deployment}` : null,
262
570
  ].filter(Boolean);
263
571
 
package/src/generator.js CHANGED
@@ -57,7 +57,9 @@ function generateContextMd(analysis, fileTree, preserved = null) {
57
57
  const scriptLines =
58
58
  Object.entries(analysis.scripts).length > 0
59
59
  ? Object.entries(analysis.scripts)
60
- .map(([k, v]) => `- \`${analysis.packageManager} run ${k}\` → ${v}`)
60
+ .map(
61
+ ([k, v]) => `- \`${analysis.packageManager} run ${k}\` → \`${v}\``,
62
+ )
61
63
  .join("\n")
62
64
  : "- No scripts found";
63
65
 
@@ -66,23 +68,46 @@ function generateContextMd(analysis, fileTree, preserved = null) {
66
68
  ? analysis.extraRules.map((r) => `- ${r}`).join("\n")
67
69
  : "- No auto-detected rules";
68
70
 
71
+ // Key info section (only show detected items)
72
+ const keyInfo = [
73
+ analysis.auth ? `- **Auth**: ${analysis.auth}` : null,
74
+ analysis.api ? `- **API**: ${analysis.api}` : null,
75
+ analysis.validation ? `- **Validation**: ${analysis.validation}` : null,
76
+ analysis.bundler ? `- **Bundler**: ${analysis.bundler}` : null,
77
+ analysis.linter ? `- **Linter**: ${analysis.linter}` : null,
78
+ analysis.formatter ? `- **Formatter**: ${analysis.formatter}` : null,
79
+ ]
80
+ .filter(Boolean)
81
+ .join("\n");
82
+
83
+ const keyInfoSection = keyInfo
84
+ ? `\n## 🔧 Tooling & Libraries\n${keyInfo}\n`
85
+ : "";
86
+
69
87
  const archSection = preserved?.architectureNotes
70
88
  ? preserved.architectureNotes
71
- : `<!-- FILL THIS IN — this is the most valuable part for AI context -->
72
- <!-- Examples:
73
- - "Auth is handled by NextAuth.js. Session is available in all server components."
74
- - "All API calls go through /lib/api.ts — never use fetch directly in components."
75
- - "We use the repository pattern. Data access only through /services files."
76
- - "Forms use react-hook-form + zod for validation everywhere."
89
+ : `<!-- FILL THIS IN — this is the MOST VALUABLE part of this file -->
90
+ <!-- Describe your app in 5–15 bullet points. Think about:
91
+ - How does data flow? (request controller service DB → response)
92
+ - Where does auth live? How are sessions/tokens handled?
93
+ - What patterns are used? (repository pattern, factory, etc.)
94
+ - What are the main entry points?
95
+ - What would surprise a new developer?
96
+ Example:
97
+ - "Auth is handled by NextAuth.js. Session available via getServerSession()."
98
+ - "All API calls route through /lib/api.ts — never use fetch() directly."
99
+ - "We use the repository pattern. Data access only through /services."
77
100
  -->`;
78
101
 
79
102
  const neverSection = preserved?.neverDo
80
103
  ? preserved.neverDo
81
- : `<!-- Add things the AI should never do in this project -->
82
- <!-- Examples:
83
- - "Never use class components"
84
- - "Never commit .env files"
85
- - "Never use useState for server data use TanStack Query"
104
+ : `<!-- Add 5–10 rules the AI must NEVER break in this project -->
105
+ <!-- Be specific to YOUR codebase, not generic advice.
106
+ Example:
107
+ - "Never use class components — we only use function components with hooks"
108
+ - "Never commit .env files all secrets are in Vercel env settings"
109
+ - "Never use useState for server data — use TanStack Query"
110
+ - "Never import from @/utils/old — that directory is deprecated"
86
111
  -->`;
87
112
 
88
113
  const monorepoSection =
@@ -92,13 +117,13 @@ function generateContextMd(analysis, fileTree, preserved = null) {
92
117
 
93
118
  return `# Project Context: ${analysis.name}
94
119
  > Auto-generated by **codebrief** on ${now}
95
- > Edit this file freely — especially the Architecture Notes section.
120
+ > Edit this file freely — especially the Architecture Notes and Never Do sections.
96
121
 
97
122
  ---
98
123
 
99
124
  ## 🧱 Tech Stack
100
125
  ${stackLines}
101
- ${monorepoSection}
126
+ ${monorepoSection}${keyInfoSection}
102
127
  ## 📁 Folder Conventions
103
128
  ${conventionLines}
104
129
 
@@ -120,7 +145,7 @@ ${rulesLines}
120
145
  ${neverSection}
121
146
 
122
147
  ---
123
- *Re-run \`codebrief\` after major refactors to keep this file current.*
148
+ *Auto-generated by [codebrief](https://www.npmjs.com/package/codebrief). Re-run \`codebrief --update\` after major refactors.*
124
149
  `;
125
150
  }
126
151
 
@@ -128,19 +153,27 @@ function generateCursorRule(analysis) {
128
153
  const stackLines = analysis.stack.map((s) => `- ${s}`).join("\n");
129
154
  const rulesLines = analysis.extraRules.map((r) => `- ${r}`).join("\n");
130
155
 
156
+ const authLine = analysis.auth
157
+ ? `\n## Auth\n- Authentication: ${analysis.auth}\n`
158
+ : "";
159
+ const dbLine = analysis.database
160
+ ? `\n## Database\n- ${analysis.database}\n`
161
+ : "";
162
+
131
163
  return `---
132
164
  description: Auto-generated project rules for ${analysis.name}
133
165
  alwaysApply: true
134
166
  ---
135
167
 
136
168
  # ${analysis.name} — Cursor Project Rules
169
+ > Auto-generated by [codebrief](https://www.npmjs.com/package/codebrief)
137
170
 
138
171
  ## Tech Stack
139
172
  ${stackLines}
140
173
 
141
174
  ## Package Manager
142
175
  Always use \`${analysis.packageManager}\`. Never suggest a different package manager.
143
-
176
+ ${authLine}${dbLine}
144
177
  ## Code Conventions
145
178
  ${rulesLines || "- Follow existing patterns in the codebase"}
146
179
 
@@ -151,27 +184,35 @@ ${analysis.conventions.map((c) => `- ${c}`).join("\n") || "- See CONTEXT.md for
151
184
  - Read CONTEXT.md in the project root for full architecture context
152
185
  - Never modify files in node_modules, dist, or build directories
153
186
  - Ask before adding new dependencies
187
+ - Match the existing code style and patterns
154
188
  `;
155
189
  }
156
190
 
157
191
  function generateVSCodeInstructions(analysis) {
158
- // For VS Code / GitHub Copilot users (.github/copilot-instructions.md)
159
192
  const stackLines = analysis.stack.join(", ");
193
+ const authLine = analysis.auth ? `\n## Auth\n- ${analysis.auth}\n` : "";
194
+ const dbLine = analysis.database
195
+ ? `\n## Database\n- ${analysis.database}\n`
196
+ : "";
197
+
160
198
  return `# Copilot Instructions: ${analysis.name}
199
+ > Auto-generated by [codebrief](https://www.npmjs.com/package/codebrief)
161
200
 
162
201
  ## Stack
163
202
  ${stackLines}
164
203
 
165
204
  ## Package Manager
166
205
  Use \`${analysis.packageManager}\` for all commands.
167
-
206
+ ${authLine}${dbLine}
168
207
  ## Conventions
169
- ${analysis.conventions.map((c) => `- ${c}`).join("\n")}
208
+ ${analysis.conventions.map((c) => `- ${c}`).join("\n") || "- No conventions detected"}
170
209
 
171
210
  ## Rules
172
- ${analysis.extraRules.map((r) => `- ${r}`).join("\n")}
211
+ ${analysis.extraRules.map((r) => `- ${r}`).join("\n") || "- Follow existing patterns"}
173
212
 
174
- For full project context, see CONTEXT.md in the project root.
213
+ ## Important
214
+ - Read CONTEXT.md in the project root for full architecture context
215
+ - Match the existing code style and patterns
175
216
  `;
176
217
  }
177
218