codebrief 1.1.10 → 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.10",
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
@@ -117,7 +117,19 @@ function sampleSourceFiles(rootDir, fileTree, charBudget = 32000) {
117
117
  // Scans all source files for process.env.XXX references
118
118
  function extractEnvVars(rootDir, fileTree) {
119
119
  const envVars = new Map(); // name → [files]
120
- const sourceExts = new Set([".js", ".ts", ".jsx", ".tsx", ".vue", ".svelte", ".py", ".go", ".rs", ".rb", ".php"]);
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
+ ]);
121
133
  const envRegex = /process\.env\.([A-Z_][A-Z0-9_]*)/g;
122
134
  const dotenvRegex = /^([A-Z_][A-Z0-9_]*)=/gm;
123
135
 
@@ -138,9 +150,12 @@ function extractEnvVars(rootDir, fileTree) {
138
150
  if (varName === "XXX" || varName.length < 3) continue;
139
151
  if (!envVars.has(varName)) envVars.set(varName, []);
140
152
  const file = entry.path || entry.name;
141
- if (!envVars.get(varName).includes(file)) envVars.get(varName).push(file);
153
+ if (!envVars.get(varName).includes(file))
154
+ envVars.get(varName).push(file);
142
155
  }
143
- } catch { /* skip */ }
156
+ } catch {
157
+ /* skip */
158
+ }
144
159
  }
145
160
 
146
161
  return envVars;
@@ -173,7 +188,9 @@ function extractExports(rootDir, fileTree) {
173
188
  exports.push({ file, exports: match[1] || match[0] });
174
189
  }
175
190
  }
176
- } catch { /* skip */ }
191
+ } catch {
192
+ /* skip */
193
+ }
177
194
  }
178
195
 
179
196
  return exports;
@@ -184,9 +201,7 @@ function buildPrompt(analysis, fileTree, fileSamples, envVars, fileExports) {
184
201
  const fileList = fileTree
185
202
  .slice(0, 120)
186
203
  .map((e) =>
187
- e.type === "dir"
188
- ? ` ${e.path || e.name}/`
189
- : ` ${e.path || e.name}`,
204
+ e.type === "dir" ? ` ${e.path || e.name}/` : ` ${e.path || e.name}`,
190
205
  )
191
206
  .join("\n");
192
207
 
@@ -199,15 +214,23 @@ function buildPrompt(analysis, fileTree, fileSamples, envVars, fileExports) {
199
214
  .map(([k, v]) => ` ${k}: ${v}`)
200
215
  .join("\n") || " (none)";
201
216
 
202
- const envVarsText = envVars.size > 0
203
- ? Array.from(envVars.entries())
204
- .map(([name, files]) => `- \`${name}\` — used in ${files.map((f) => "`" + f + "`").join(", ")}`)
205
- .join("\n")
206
- : "(none found)";
207
-
208
- const exportsText = fileExports.length > 0
209
- ? fileExports.slice(0, 30).map((e) => `- \`${e.file}\`: ${e.exports}`).join("\n")
210
- : "(none found)";
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)";
226
+
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)";
211
234
 
212
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.`;
213
236
 
@@ -350,9 +373,13 @@ async function callGroq(prompt, model) {
350
373
  " Get a free key in ~30s at https://console.groq.com",
351
374
  );
352
375
 
353
- const messages = typeof prompt === "string"
354
- ? [{ role: "user", content: prompt }]
355
- : [{ role: "system", content: prompt.systemMessage }, { role: "user", content: prompt.userMessage }];
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
+ ];
356
383
 
357
384
  const res = await httpsPost(
358
385
  "api.groq.com",
@@ -377,9 +404,13 @@ async function callOpenAI(prompt, model) {
377
404
  if (!apiKey)
378
405
  throw new Error("OPENAI_API_KEY environment variable is not set.");
379
406
 
380
- const messages = typeof prompt === "string"
381
- ? [{ role: "user", content: prompt }]
382
- : [{ role: "system", content: prompt.systemMessage }, { role: "user", content: prompt.userMessage }];
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
+ ];
383
414
 
384
415
  const res = await httpsPost(
385
416
  "api.openai.com",
@@ -404,9 +435,10 @@ async function callAnthropic(prompt, model) {
404
435
  if (!apiKey)
405
436
  throw new Error("ANTHROPIC_API_KEY environment variable is not set.");
406
437
 
407
- const messages = typeof prompt === "string"
408
- ? [{ role: "user", content: prompt }]
409
- : [{ role: "user", content: prompt.userMessage }];
438
+ const messages =
439
+ typeof prompt === "string"
440
+ ? [{ role: "user", content: prompt }]
441
+ : [{ role: "user", content: prompt.userMessage }];
410
442
 
411
443
  const system = typeof prompt === "string" ? undefined : prompt.systemMessage;
412
444
 
@@ -439,9 +471,10 @@ async function callGemini(prompt, model) {
439
471
  " Get a free key at https://aistudio.google.com/app/apikey",
440
472
  );
441
473
 
442
- const fullText = typeof prompt === "string"
443
- ? prompt
444
- : `${prompt.systemMessage}\n\n${prompt.userMessage}`;
474
+ const fullText =
475
+ typeof prompt === "string"
476
+ ? prompt
477
+ : `${prompt.systemMessage}\n\n${prompt.userMessage}`;
445
478
 
446
479
  const res = await httpsPost(
447
480
  "generativelanguage.googleapis.com",
@@ -464,9 +497,13 @@ async function callGrok(prompt, model) {
464
497
  " Get a key at https://console.x.ai",
465
498
  );
466
499
 
467
- const messages = typeof prompt === "string"
468
- ? [{ role: "user", content: prompt }]
469
- : [{ role: "system", content: prompt.systemMessage }, { role: "user", content: prompt.userMessage }];
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
+ ];
470
507
 
471
508
  const res = await httpsPost(
472
509
  "api.x.ai",
@@ -489,9 +526,10 @@ async function callOllama(prompt, model) {
489
526
  model = model || getDefaultModel("ollama");
490
527
  // Ollama runs locally on port 11434 — use http
491
528
  const http = require("http");
492
- const fullText = typeof prompt === "string"
493
- ? prompt
494
- : `${prompt.systemMessage}\n\n${prompt.userMessage}`;
529
+ const fullText =
530
+ typeof prompt === "string"
531
+ ? prompt
532
+ : `${prompt.systemMessage}\n\n${prompt.userMessage}`;
495
533
  const body = JSON.stringify({ model, prompt: fullText, stream: false });
496
534
 
497
535
  return new Promise((resolve, reject) => {
@@ -543,7 +581,13 @@ async function enhanceWithAI(analysis, fileTree, rootDir, options = {}) {
543
581
  const fileSamples = sampleSourceFiles(rootDir, fileTree);
544
582
  const envVars = extractEnvVars(rootDir, fileTree);
545
583
  const fileExports = extractExports(rootDir, fileTree);
546
- const prompt = buildPrompt(analysis, fileTree, fileSamples, envVars, fileExports);
584
+ const prompt = buildPrompt(
585
+ analysis,
586
+ fileTree,
587
+ fileSamples,
588
+ envVars,
589
+ fileExports,
590
+ );
547
591
 
548
592
  switch (provider.toLowerCase()) {
549
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