contextforge-cli-harshil 1.0.0
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/.env.example +37 -0
- package/README.md +170 -0
- package/bin/index.js +55 -0
- package/package.json +44 -0
- package/src/ai.js +399 -0
- package/src/analyze.js +432 -0
- package/src/commands/init.js +275 -0
- package/src/context.js +107 -0
- package/src/generate.js +211 -0
- package/src/scan.js +206 -0
- package/src/utils.js +93 -0
package/.env.example
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# ============================================================
|
|
2
|
+
# ContextForge Configuration
|
|
3
|
+
# Copy this file to .env and fill in your values.
|
|
4
|
+
# Change ONLY this file — no code changes needed.
|
|
5
|
+
# ============================================================
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# ── Provider (groq | openai) ─────────────────────────────────
|
|
9
|
+
# Which AI provider to use.
|
|
10
|
+
# Leave blank to auto-detect from whichever API key is present.
|
|
11
|
+
# If both keys are set, Groq is preferred (faster + free tier).
|
|
12
|
+
AI_PROVIDER=groq
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# ── Model ────────────────────────────────────────────────────
|
|
16
|
+
# Which model to use. Leave blank to use each provider's default.
|
|
17
|
+
#
|
|
18
|
+
# Groq models:
|
|
19
|
+
# llama-3.3-70b-versatile ← default (best quality)
|
|
20
|
+
# llama-3.1-8b-instant ← fastest
|
|
21
|
+
# mixtral-8x7b-32768
|
|
22
|
+
# gemma2-9b-it
|
|
23
|
+
#
|
|
24
|
+
# OpenAI models:
|
|
25
|
+
# gpt-4o-mini ← default (cheap + fast)
|
|
26
|
+
# gpt-4o
|
|
27
|
+
# gpt-4-turbo
|
|
28
|
+
#
|
|
29
|
+
AI_MODEL=llama-3.3-70b-versatile
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# ── API Keys ─────────────────────────────────────────────────
|
|
33
|
+
# Get a free Groq key at: https://console.groq.com
|
|
34
|
+
GROQ_API_KEY=gsk_your-groq-key-here
|
|
35
|
+
|
|
36
|
+
# Get an OpenAI key at: https://platform.openai.com/api-keys
|
|
37
|
+
# OPENAI_API_KEY=sk-your-openai-key-here
|
package/README.md
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# ⚡ ContextForge
|
|
2
|
+
|
|
3
|
+
> **Automatic AI memory generation for repositories.**
|
|
4
|
+
|
|
5
|
+
Run one command → automatically generate reusable AI-ready project context.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## What It Does
|
|
10
|
+
|
|
11
|
+
ContextForge scans your repository, analyzes its architecture and stack, and generates a `context.md` file that you can paste into any AI assistant (ChatGPT, Claude, Gemini, Cursor, etc.) to give it deep, accurate knowledge of your codebase — instantly.
|
|
12
|
+
|
|
13
|
+
**No backend. No cloud. No magic. Just fast, local analysis.**
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install -g contextforge
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
### 1. Add your OpenAI API key
|
|
28
|
+
|
|
29
|
+
Create a `.env` file in your **project root** (the repo you want to analyze):
|
|
30
|
+
|
|
31
|
+
```env
|
|
32
|
+
OPENAI_API_KEY=sk-your-api-key-here
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 2. Run ContextForge
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
contextforge init
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
That's it. ContextForge will:
|
|
42
|
+
|
|
43
|
+
1. 🔍 **Scan** your repository (using fast-glob + .gitignore rules)
|
|
44
|
+
2. 🧠 **Analyze** your architecture, stack, patterns, and auth
|
|
45
|
+
3. 📁 **Identify** important files with heuristic scoring
|
|
46
|
+
4. ✨ **Generate** AI-ready context using GPT-4o-mini
|
|
47
|
+
5. 📝 **Write** `context.md` to your project root
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## CLI Options
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
contextforge init [options]
|
|
55
|
+
|
|
56
|
+
Options:
|
|
57
|
+
-o, --output <path> Output file path (default: "context.md")
|
|
58
|
+
--model <model> OpenAI model to use (default: "gpt-4o-mini")
|
|
59
|
+
--no-ai Skip OpenAI, generate structural context only
|
|
60
|
+
-v, --version Output the current version
|
|
61
|
+
-h, --help Display help
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Examples
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# Standard usage
|
|
68
|
+
contextforge init
|
|
69
|
+
|
|
70
|
+
# Use a more powerful model
|
|
71
|
+
contextforge init --model gpt-4o
|
|
72
|
+
|
|
73
|
+
# Offline / no API key — structural context only
|
|
74
|
+
contextforge init --no-ai
|
|
75
|
+
|
|
76
|
+
# Custom output path
|
|
77
|
+
contextforge init --output docs/ai-context.md
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Output Format
|
|
83
|
+
|
|
84
|
+
```markdown
|
|
85
|
+
# Project Context
|
|
86
|
+
|
|
87
|
+
> Express.js REST API with Prisma ORM and JWT authentication.
|
|
88
|
+
|
|
89
|
+
## Stack
|
|
90
|
+
- Express.js
|
|
91
|
+
- Node.js
|
|
92
|
+
- Prisma ORM
|
|
93
|
+
- JWT (jsonwebtoken)
|
|
94
|
+
- Zod
|
|
95
|
+
|
|
96
|
+
## Architecture
|
|
97
|
+
- Route-based API structure
|
|
98
|
+
- MVC / Controller pattern
|
|
99
|
+
- Service layer pattern
|
|
100
|
+
- Middleware-based request handling
|
|
101
|
+
|
|
102
|
+
## Important Modules
|
|
103
|
+
### `src/`
|
|
104
|
+
- `src/app.js`
|
|
105
|
+
- `src/routes/`
|
|
106
|
+
- `src/controllers/`
|
|
107
|
+
- `src/services/`
|
|
108
|
+
- `src/middleware/auth.js`
|
|
109
|
+
|
|
110
|
+
## Coding Patterns
|
|
111
|
+
- async/await pattern
|
|
112
|
+
- ES Modules (import/export)
|
|
113
|
+
- try/catch error handling
|
|
114
|
+
- Schema validation (Zod)
|
|
115
|
+
|
|
116
|
+
## AI Instructions
|
|
117
|
+
- This is a JavaScript project using ES Modules
|
|
118
|
+
- Backend: Express.js — follow route/controller/service layering
|
|
119
|
+
- Database: Prisma ORM — use existing query patterns
|
|
120
|
+
- Auth: JWT — never bypass the auth middleware
|
|
121
|
+
- Validate all inputs with Zod schemas
|
|
122
|
+
- Use async/await for all async operations
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## How File Importance Is Scored
|
|
128
|
+
|
|
129
|
+
ContextForge uses **lightweight heuristic scoring** — no embeddings, no vectors:
|
|
130
|
+
|
|
131
|
+
| Signal | Score |
|
|
132
|
+
|---|---|
|
|
133
|
+
| Filename matches known important files | +10 |
|
|
134
|
+
| Directory matches (auth, routes, services, etc.) | +7 |
|
|
135
|
+
| Filename contains keywords (auth, route, controller...) | +5 |
|
|
136
|
+
| Content patterns (router.get, prisma., jwt.sign...) | +2 each |
|
|
137
|
+
| File extension (JS/TS > JSON/YAML > MD) | +1–3 |
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Architecture Detection
|
|
142
|
+
|
|
143
|
+
ContextForge detects:
|
|
144
|
+
|
|
145
|
+
- **Frameworks**: Express, Fastify, NestJS, Next.js, React, Vue, Svelte, Astro, Remix...
|
|
146
|
+
- **Databases**: Prisma, Mongoose, TypeORM, Drizzle, Sequelize, PostgreSQL, MySQL, Redis...
|
|
147
|
+
- **Auth**: JWT, Passport.js, NextAuth, Clerk, Supabase, Firebase, bcrypt...
|
|
148
|
+
- **State**: Zustand, Jotai, Redux Toolkit, TanStack Query, SWR...
|
|
149
|
+
- **Validation**: Zod, Joi, Yup, class-validator...
|
|
150
|
+
- **Testing**: Jest, Vitest, Playwright, Cypress...
|
|
151
|
+
- **Patterns**: Service layer, Repository, MVC, Middleware, RBAC, File-system routing...
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Privacy
|
|
156
|
+
|
|
157
|
+
Everything runs **locally** on your machine. Your code is only sent to OpenAI when AI generation is enabled (the default). Use `--no-ai` to keep everything fully local.
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Requirements
|
|
162
|
+
|
|
163
|
+
- Node.js ≥ 18
|
|
164
|
+
- OpenAI API key (optional with `--no-ai`)
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## License
|
|
169
|
+
|
|
170
|
+
MIT
|
package/bin/index.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { program } from 'commander';
|
|
4
|
+
import dotenv from 'dotenv';
|
|
5
|
+
import { readFileSync, existsSync } from 'fs';
|
|
6
|
+
import { resolve } from 'path';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
|
|
9
|
+
// Load .env from the directory where contextforge is being run
|
|
10
|
+
const envPath = resolve(process.cwd(), '.env');
|
|
11
|
+
if (existsSync(envPath)) {
|
|
12
|
+
dotenv.config({ path: envPath });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Read package.json for version
|
|
16
|
+
const __dirname = new URL('.', import.meta.url).pathname.replace(/^\/([A-Z]:)/, '$1');
|
|
17
|
+
const pkgPath = resolve(__dirname, '..', 'package.json');
|
|
18
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
19
|
+
|
|
20
|
+
program
|
|
21
|
+
.name('contextforge')
|
|
22
|
+
.description(
|
|
23
|
+
chalk.bold.cyan('⚡ ContextForge') +
|
|
24
|
+
' — Automatic AI memory generation for repositories.'
|
|
25
|
+
)
|
|
26
|
+
.version(pkg.version, '-v, --version', 'Output the current version');
|
|
27
|
+
|
|
28
|
+
// ── contextforge init ──────────────────────────────────────────────────────
|
|
29
|
+
program
|
|
30
|
+
.command('init')
|
|
31
|
+
.description('Scan repository and generate AI-ready context.md')
|
|
32
|
+
.option('-o, --output <path>', 'Output file path', 'context.md')
|
|
33
|
+
.option(
|
|
34
|
+
'-p, --provider <provider>',
|
|
35
|
+
'AI provider to use: groq | openai | auto (default: auto — prefers Groq)',
|
|
36
|
+
'auto'
|
|
37
|
+
)
|
|
38
|
+
.option(
|
|
39
|
+
'--model <model>',
|
|
40
|
+
'Model override (e.g. llama-3.3-70b-versatile, gpt-4o). Defaults to provider\'s recommended model.'
|
|
41
|
+
)
|
|
42
|
+
.option('--no-ai', 'Skip AI and generate a structural context only')
|
|
43
|
+
.option('--force', 'Regenerate even if context.md is already up to date')
|
|
44
|
+
.action(async (options) => {
|
|
45
|
+
const { runInit } = await import('../src/commands/init.js');
|
|
46
|
+
await runInit(options);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Show help with clean exit if no command given
|
|
50
|
+
if (!process.argv.slice(2).length) {
|
|
51
|
+
program.outputHelp();
|
|
52
|
+
process.exit(0); // explicit 0 — not a failure
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
program.parse(process.argv);
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "contextforge-cli-harshil",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Automatically scan a repository and generate AI-ready project context in context.md",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"contextforge": "./bin/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"src/",
|
|
12
|
+
"README.md",
|
|
13
|
+
".env.example"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"start": "node bin/index.js",
|
|
17
|
+
"dev": "node bin/index.js"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"ai",
|
|
21
|
+
"context",
|
|
22
|
+
"cli",
|
|
23
|
+
"openai",
|
|
24
|
+
"repository",
|
|
25
|
+
"codegen",
|
|
26
|
+
"developer-tools"
|
|
27
|
+
],
|
|
28
|
+
"author": "",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"chalk": "^5.3.0",
|
|
32
|
+
"commander": "^12.1.0",
|
|
33
|
+
"dotenv": "^16.4.5",
|
|
34
|
+
"fast-glob": "^3.3.2",
|
|
35
|
+
"groq-sdk": "^1.2.0",
|
|
36
|
+
"ignore": "^5.3.1",
|
|
37
|
+
"openai": "^4.52.7",
|
|
38
|
+
"ora": "^8.1.0"
|
|
39
|
+
},
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=18.0.0"
|
|
42
|
+
},
|
|
43
|
+
"type": "module"
|
|
44
|
+
}
|
package/src/ai.js
ADDED
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ai.js
|
|
3
|
+
* Unified AI provider — supports OpenAI and Groq.
|
|
4
|
+
*
|
|
5
|
+
* Configuration priority (highest → lowest):
|
|
6
|
+
* 1. CLI flags --provider groq --model llama-3.3-70b-versatile
|
|
7
|
+
* 2. .env vars AI_PROVIDER=groq AI_MODEL=llama-3.3-70b-versatile
|
|
8
|
+
* 3. Auto-detect reads GROQ_API_KEY / OPENAI_API_KEY from .env
|
|
9
|
+
* 4. Defaults Groq → llama-3.3-70b-versatile | OpenAI → gpt-4o-mini
|
|
10
|
+
*
|
|
11
|
+
* Changing ONLY your .env file is enough to switch provider and model.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import OpenAI from 'openai';
|
|
15
|
+
import Groq from 'groq-sdk';
|
|
16
|
+
import { readFileSafe } from './scan.js';
|
|
17
|
+
|
|
18
|
+
// ── Provider registry ─────────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
export const PROVIDERS = {
|
|
21
|
+
openai: {
|
|
22
|
+
name: 'OpenAI',
|
|
23
|
+
envKey: 'OPENAI_API_KEY',
|
|
24
|
+
defaultModel: 'gpt-4o-mini',
|
|
25
|
+
models: ['gpt-4o-mini', 'gpt-4o', 'gpt-4-turbo', 'gpt-3.5-turbo'],
|
|
26
|
+
},
|
|
27
|
+
groq: {
|
|
28
|
+
name: 'Groq',
|
|
29
|
+
envKey: 'GROQ_API_KEY',
|
|
30
|
+
defaultModel: 'llama-3.3-70b-versatile',
|
|
31
|
+
models: [
|
|
32
|
+
'llama-3.3-70b-versatile',
|
|
33
|
+
'llama-3.1-70b-versatile',
|
|
34
|
+
'llama-3.1-8b-instant',
|
|
35
|
+
'llama3-70b-8192',
|
|
36
|
+
'llama3-8b-8192',
|
|
37
|
+
'mixtral-8x7b-32768',
|
|
38
|
+
'gemma2-9b-it',
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// ── Provider detection ────────────────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Detect which AI provider to use.
|
|
47
|
+
*
|
|
48
|
+
* Resolution order:
|
|
49
|
+
* 1. Explicit CLI --provider flag (hint !== 'auto')
|
|
50
|
+
* 2. AI_PROVIDER env var (set in .env)
|
|
51
|
+
* 3. Auto-detect from API keys (GROQ preferred over OpenAI)
|
|
52
|
+
*
|
|
53
|
+
* @param {'openai'|'groq'|'auto'} hint - value from --provider flag
|
|
54
|
+
* @returns {'openai'|'groq'|null}
|
|
55
|
+
*/
|
|
56
|
+
export function detectProvider(hint = 'auto') {
|
|
57
|
+
// 1. Explicit CLI flag overrides everything
|
|
58
|
+
if (hint === 'openai') return process.env.OPENAI_API_KEY ? 'openai' : null;
|
|
59
|
+
if (hint === 'groq') return process.env.GROQ_API_KEY ? 'groq' : null;
|
|
60
|
+
|
|
61
|
+
// 2. AI_PROVIDER set in .env
|
|
62
|
+
const envProvider = (process.env.AI_PROVIDER || '').toLowerCase().trim();
|
|
63
|
+
if (envProvider === 'openai') return process.env.OPENAI_API_KEY ? 'openai' : null;
|
|
64
|
+
if (envProvider === 'groq') return process.env.GROQ_API_KEY ? 'groq' : null;
|
|
65
|
+
|
|
66
|
+
// 3. Auto-detect: prefer Groq (faster, generous free tier)
|
|
67
|
+
if (process.env.GROQ_API_KEY) return 'groq';
|
|
68
|
+
if (process.env.OPENAI_API_KEY) return 'openai';
|
|
69
|
+
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Validate that the API key for the selected provider is present and looks valid.
|
|
75
|
+
*
|
|
76
|
+
* @param {'openai'|'groq'} provider
|
|
77
|
+
* @returns {{ valid: boolean, message: string }}
|
|
78
|
+
*/
|
|
79
|
+
export function validateProviderKey(provider) {
|
|
80
|
+
const cfg = PROVIDERS[provider];
|
|
81
|
+
if (!cfg) {
|
|
82
|
+
return { valid: false, message: `Unknown provider: "${provider}"` };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const key = process.env[cfg.envKey];
|
|
86
|
+
if (!key) {
|
|
87
|
+
return {
|
|
88
|
+
valid: false,
|
|
89
|
+
message:
|
|
90
|
+
`${cfg.envKey} is not set.\n` +
|
|
91
|
+
` Add it to a .env file in your project root:\n\n` +
|
|
92
|
+
` ${cfg.envKey}=your-key-here\n`,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Basic format checks
|
|
97
|
+
if (provider === 'openai' && !key.startsWith('sk-')) {
|
|
98
|
+
return {
|
|
99
|
+
valid: false,
|
|
100
|
+
message: 'OPENAI_API_KEY looks invalid (should start with "sk-"). Check your .env file.',
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
if (provider === 'groq' && !key.startsWith('gsk_')) {
|
|
104
|
+
return {
|
|
105
|
+
valid: false,
|
|
106
|
+
message: 'GROQ_API_KEY looks invalid (should start with "gsk_"). Check your .env file.',
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return { valid: true, message: '' };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ── Client creation ───────────────────────────────────────────────────────────
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Create and return the AI client for the given provider.
|
|
117
|
+
*
|
|
118
|
+
* @param {'openai'|'groq'} provider
|
|
119
|
+
* @returns {OpenAI|Groq}
|
|
120
|
+
*/
|
|
121
|
+
export function createClient(provider) {
|
|
122
|
+
if (provider === 'groq') {
|
|
123
|
+
return new Groq({ apiKey: process.env.GROQ_API_KEY });
|
|
124
|
+
}
|
|
125
|
+
return new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Resolve which model to use.
|
|
130
|
+
*
|
|
131
|
+
* Resolution order:
|
|
132
|
+
* 1. --model CLI flag
|
|
133
|
+
* 2. AI_MODEL env var (set in .env)
|
|
134
|
+
* 3. Provider's built-in default
|
|
135
|
+
*
|
|
136
|
+
* @param {string|undefined} modelFlag - value from --model CLI flag
|
|
137
|
+
* @param {'openai'|'groq'} provider
|
|
138
|
+
* @returns {string}
|
|
139
|
+
*/
|
|
140
|
+
export function resolveModel(modelFlag, provider) {
|
|
141
|
+
// 1. Explicit CLI --model flag
|
|
142
|
+
if (modelFlag) return modelFlag;
|
|
143
|
+
|
|
144
|
+
// 2. AI_MODEL in .env
|
|
145
|
+
const envModel = (process.env.AI_MODEL || '').trim();
|
|
146
|
+
if (envModel) return envModel;
|
|
147
|
+
|
|
148
|
+
// 3. Provider default
|
|
149
|
+
return PROVIDERS[provider].defaultModel;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ── Prompt builder ────────────────────────────────────────────────────────────
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Build the analysis prompt to send to the AI model.
|
|
156
|
+
*
|
|
157
|
+
* @param {Object} analysis - Result from analyzeRepository()
|
|
158
|
+
* @param {string} folderTree
|
|
159
|
+
* @returns {string}
|
|
160
|
+
*/
|
|
161
|
+
export function buildPrompt(analysis, folderTree) {
|
|
162
|
+
const {
|
|
163
|
+
projectName,
|
|
164
|
+
projectVersion,
|
|
165
|
+
projectDescription,
|
|
166
|
+
language,
|
|
167
|
+
moduleSystem,
|
|
168
|
+
stack,
|
|
169
|
+
byCategory,
|
|
170
|
+
architecturePatterns,
|
|
171
|
+
authApproach,
|
|
172
|
+
codingConventions,
|
|
173
|
+
scripts,
|
|
174
|
+
importantFiles,
|
|
175
|
+
} = analysis;
|
|
176
|
+
|
|
177
|
+
// Collect snippets from important files (budget-capped for free-tier models)
|
|
178
|
+
const snippetParts = [];
|
|
179
|
+
let totalSnippetLength = 0;
|
|
180
|
+
const MAX_TOTAL = 18_000; // ~4500 tokens — leaves room for prompt + response
|
|
181
|
+
const MAX_PER_FILE = 1_200; // ~300 tokens per file
|
|
182
|
+
|
|
183
|
+
for (const file of importantFiles) {
|
|
184
|
+
if (totalSnippetLength >= MAX_TOTAL) break;
|
|
185
|
+
const content = readFileSafe(file.absolutePath, MAX_PER_FILE);
|
|
186
|
+
if (!content) continue;
|
|
187
|
+
const snippet = `### ${file.path}\n\`\`\`\n${content}\n\`\`\``;
|
|
188
|
+
snippetParts.push(snippet);
|
|
189
|
+
totalSnippetLength += snippet.length;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// package.json summary (capped)
|
|
193
|
+
const pkgSummary = analysis.pkgJson
|
|
194
|
+
? JSON.stringify(
|
|
195
|
+
{
|
|
196
|
+
name: analysis.pkgJson.name,
|
|
197
|
+
version: analysis.pkgJson.version,
|
|
198
|
+
description: analysis.pkgJson.description,
|
|
199
|
+
scripts: analysis.pkgJson.scripts,
|
|
200
|
+
dependencies: analysis.pkgJson.dependencies,
|
|
201
|
+
devDependencies: analysis.pkgJson.devDependencies,
|
|
202
|
+
},
|
|
203
|
+
null,
|
|
204
|
+
2
|
|
205
|
+
).slice(0, 4_000)
|
|
206
|
+
: 'Not available';
|
|
207
|
+
|
|
208
|
+
const stackBlock =
|
|
209
|
+
stack.length > 0
|
|
210
|
+
? stack.map((s) => `- ${s}`).join('\n')
|
|
211
|
+
: '- No external dependencies detected';
|
|
212
|
+
|
|
213
|
+
const categoryBlock =
|
|
214
|
+
Object.entries(byCategory)
|
|
215
|
+
.map(([cat, items]) => `**${capitalize(cat)}:** ${items.join(', ')}`)
|
|
216
|
+
.join('\n') || 'N/A';
|
|
217
|
+
|
|
218
|
+
const archBlock =
|
|
219
|
+
architecturePatterns.length > 0
|
|
220
|
+
? architecturePatterns.map((p) => `- ${p}`).join('\n')
|
|
221
|
+
: '- Unable to determine';
|
|
222
|
+
|
|
223
|
+
const authBlock =
|
|
224
|
+
authApproach.length > 0
|
|
225
|
+
? authApproach.map((a) => `- ${a}`).join('\n')
|
|
226
|
+
: '- None detected';
|
|
227
|
+
|
|
228
|
+
const conventionBlock =
|
|
229
|
+
codingConventions.length > 0
|
|
230
|
+
? codingConventions.map((c) => `- ${c}`).join('\n')
|
|
231
|
+
: '- None detected';
|
|
232
|
+
|
|
233
|
+
const scriptBlock =
|
|
234
|
+
scripts.length > 0
|
|
235
|
+
? scripts.map((s) => `- \`${s}\``).join('\n')
|
|
236
|
+
: '- None';
|
|
237
|
+
|
|
238
|
+
return `You are an expert software architect. Analyze the following repository and generate a comprehensive, AI-ready project context document.
|
|
239
|
+
|
|
240
|
+
## Repository Overview
|
|
241
|
+
|
|
242
|
+
**Project Name:** ${projectName}
|
|
243
|
+
**Version:** ${projectVersion || 'N/A'}
|
|
244
|
+
**Description:** ${projectDescription || 'N/A'}
|
|
245
|
+
**Language:** ${language}
|
|
246
|
+
**Module System:** ${moduleSystem}
|
|
247
|
+
|
|
248
|
+
## Detected Technology Stack
|
|
249
|
+
|
|
250
|
+
${stackBlock}
|
|
251
|
+
|
|
252
|
+
### By Category
|
|
253
|
+
${categoryBlock}
|
|
254
|
+
|
|
255
|
+
## Detected Architecture Patterns
|
|
256
|
+
|
|
257
|
+
${archBlock}
|
|
258
|
+
|
|
259
|
+
## Detected Auth Approach
|
|
260
|
+
|
|
261
|
+
${authBlock}
|
|
262
|
+
|
|
263
|
+
## Detected Coding Conventions
|
|
264
|
+
|
|
265
|
+
${conventionBlock}
|
|
266
|
+
|
|
267
|
+
## Available Scripts
|
|
268
|
+
|
|
269
|
+
${scriptBlock}
|
|
270
|
+
|
|
271
|
+
## Folder Structure
|
|
272
|
+
|
|
273
|
+
\`\`\`
|
|
274
|
+
${folderTree}
|
|
275
|
+
\`\`\`
|
|
276
|
+
|
|
277
|
+
## package.json
|
|
278
|
+
|
|
279
|
+
\`\`\`json
|
|
280
|
+
${pkgSummary}
|
|
281
|
+
\`\`\`
|
|
282
|
+
|
|
283
|
+
## Important File Snippets
|
|
284
|
+
|
|
285
|
+
${snippetParts.join('\n\n')}
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## Your Task
|
|
290
|
+
|
|
291
|
+
Based on all of the above, generate a well-structured **context.md** document in the following exact format:
|
|
292
|
+
|
|
293
|
+
\`\`\`markdown
|
|
294
|
+
# Project Context
|
|
295
|
+
|
|
296
|
+
> [One-sentence summary of what this project does]
|
|
297
|
+
|
|
298
|
+
## Stack
|
|
299
|
+
|
|
300
|
+
[List every confirmed technology: language, runtime, frameworks, databases, auth, styling, testing, build tools]
|
|
301
|
+
|
|
302
|
+
## Architecture
|
|
303
|
+
|
|
304
|
+
[Explain the high-level design: patterns used, folder layout rationale, component interaction, data flow]
|
|
305
|
+
|
|
306
|
+
## Important Modules
|
|
307
|
+
|
|
308
|
+
[For each major area — auth, database, routing, services, etc. — list relevant paths and explain their role]
|
|
309
|
+
|
|
310
|
+
## Coding Patterns
|
|
311
|
+
|
|
312
|
+
[List observed conventions: async style, error handling, module system, typing, etc.]
|
|
313
|
+
|
|
314
|
+
## API Structure
|
|
315
|
+
|
|
316
|
+
[If applicable: REST/GraphQL/tRPC style, endpoint organization, auth middleware flow]
|
|
317
|
+
|
|
318
|
+
## Database & Data Layer
|
|
319
|
+
|
|
320
|
+
[If applicable: ORM/query approach, schema structure, migration strategy]
|
|
321
|
+
|
|
322
|
+
## Environment & Configuration
|
|
323
|
+
|
|
324
|
+
[List important environment variables and how config is managed]
|
|
325
|
+
|
|
326
|
+
## Development Workflow
|
|
327
|
+
|
|
328
|
+
[List npm scripts and what they do]
|
|
329
|
+
|
|
330
|
+
## AI Instructions
|
|
331
|
+
|
|
332
|
+
[Specific, actionable rules for an AI assistant working in this codebase. Be concrete and prescriptive.]
|
|
333
|
+
\`\`\`
|
|
334
|
+
|
|
335
|
+
Rules:
|
|
336
|
+
1. Omit any section that is not relevant to this project.
|
|
337
|
+
2. Be specific and concrete — avoid generic advice.
|
|
338
|
+
3. The **AI Instructions** section is the most important: write precise rules an AI must follow when editing this repo.
|
|
339
|
+
4. Return ONLY the markdown content — no preamble, no commentary, no wrapping fences.
|
|
340
|
+
5. Use actual file paths, module names, and library names from the data above.
|
|
341
|
+
`;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// ── AI call ───────────────────────────────────────────────────────────────────
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Call the selected provider and return the generated markdown context.
|
|
348
|
+
* Both OpenAI and Groq implement the same chat completions interface.
|
|
349
|
+
*
|
|
350
|
+
* @param {OpenAI|Groq} client
|
|
351
|
+
* @param {string} prompt
|
|
352
|
+
* @param {string} model
|
|
353
|
+
* @param {'openai'|'groq'} provider
|
|
354
|
+
* @returns {Promise<string>}
|
|
355
|
+
*/
|
|
356
|
+
export async function generateContextWithAI(client, prompt, model, provider) {
|
|
357
|
+
const TIMEOUT_MS = 60_000; // 60 seconds
|
|
358
|
+
|
|
359
|
+
const apiCall = client.chat.completions.create({
|
|
360
|
+
model,
|
|
361
|
+
messages: [
|
|
362
|
+
{
|
|
363
|
+
role: 'system',
|
|
364
|
+
content:
|
|
365
|
+
'You are a senior software architect who specializes in analyzing codebases ' +
|
|
366
|
+
'and producing precise, AI-ready documentation. Write concise, actionable ' +
|
|
367
|
+
'context documents that help AI assistants understand and contribute to projects.',
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
role: 'user',
|
|
371
|
+
content: prompt,
|
|
372
|
+
},
|
|
373
|
+
],
|
|
374
|
+
temperature: 0.3,
|
|
375
|
+
max_tokens: 3000,
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
const timeoutPromise = new Promise((_, reject) =>
|
|
379
|
+
setTimeout(
|
|
380
|
+
() => reject(new Error(`AI request timed out after ${TIMEOUT_MS / 1000}s. Try again or use --no-ai.`)),
|
|
381
|
+
TIMEOUT_MS
|
|
382
|
+
)
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
const response = await Promise.race([apiCall, timeoutPromise]);
|
|
386
|
+
|
|
387
|
+
const content = response.choices?.[0]?.message?.content;
|
|
388
|
+
if (!content) {
|
|
389
|
+
const name = PROVIDERS[provider]?.name ?? provider;
|
|
390
|
+
throw new Error(`${name} returned an empty response.`);
|
|
391
|
+
}
|
|
392
|
+
return content.trim();
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
396
|
+
|
|
397
|
+
function capitalize(str) {
|
|
398
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
399
|
+
}
|