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 +26 -15
- package/package.json +18 -3
- package/src/ai.js +261 -70
- package/src/analyzer.js +316 -8
- package/src/generator.js +62 -21
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
|
|
331
|
-
| **
|
|
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
|
-
| **
|
|
337
|
-
| **
|
|
338
|
-
| **
|
|
339
|
-
| **
|
|
340
|
-
| **
|
|
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
|
|
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,
|
|
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.
|
|
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
|
-
"
|
|
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 =
|
|
46
|
+
function sampleSourceFiles(rootDir, fileTree, charBudget = 32000) {
|
|
30
47
|
const samples = [];
|
|
31
48
|
let budget = charBudget;
|
|
32
49
|
|
|
33
|
-
//
|
|
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 =
|
|
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.
|
|
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
|
|
77
|
-
const snippet = raw.slice(0, 1500);
|
|
102
|
+
const content = smartRead(full, 4000);
|
|
78
103
|
samples.push({
|
|
79
|
-
file: entry.
|
|
80
|
-
content
|
|
104
|
+
file: entry.path || entry.name,
|
|
105
|
+
content,
|
|
81
106
|
});
|
|
82
|
-
budget -=
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
|
127
|
-
-
|
|
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
|
|
275
|
+
## File tree
|
|
138
276
|
${fileList}
|
|
139
277
|
|
|
140
|
-
## Source
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
304
|
+
One bullet per top-level directory, explaining its responsibility based on the actual files inside.
|
|
160
305
|
|
|
161
306
|
## 🔧 Scripts
|
|
162
|
-
|
|
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
|
-
|
|
171
|
-
- Name
|
|
172
|
-
- Describe a concrete data flow,
|
|
173
|
-
-
|
|
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
|
-
|
|
177
|
-
- "Always
|
|
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
|
-
|
|
320
|
+
6–10 prohibitions grounded in the codebase. Each must cite WHY (a file, pattern, or convention).
|
|
182
321
|
|
|
183
|
-
## 🔐 Environment &
|
|
184
|
-
|
|
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
|
|
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
|
|
246
|
-
temperature: 0.
|
|
247
|
-
max_tokens:
|
|
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
|
|
269
|
-
temperature: 0.
|
|
270
|
-
max_tokens:
|
|
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:
|
|
314
|
-
generationConfig: { temperature: 0.
|
|
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
|
|
339
|
-
temperature: 0.
|
|
340
|
-
max_tokens:
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
179
|
-
else if (
|
|
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(
|
|
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
|
|
72
|
-
<!--
|
|
73
|
-
-
|
|
74
|
-
-
|
|
75
|
-
-
|
|
76
|
-
-
|
|
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
|
|
82
|
-
<!--
|
|
83
|
-
|
|
84
|
-
- "Never
|
|
85
|
-
- "Never
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|