ai-codegen-cli-vrk 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/bin/index.js +2 -0
- package/package.json +18 -0
- package/src/aiClient.js +132 -0
- package/src/fileWriter.js +10 -0
- package/src/promptTemplate.js +43 -0
- package/src/runner.js +56 -0
package/bin/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ai-codegen-cli-vrk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Terminal-based AI code generator",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ai-codegen": "bin/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"src"
|
|
12
|
+
],
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"fs-extra": "^11.2.0",
|
|
15
|
+
"groq-sdk": "^0.7.0",
|
|
16
|
+
"readline-sync": "^1.4.10"
|
|
17
|
+
}
|
|
18
|
+
}
|
package/src/aiClient.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import Groq from "groq-sdk";
|
|
2
|
+
|
|
3
|
+
let groqClient = null;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Called once per run from runner.js
|
|
7
|
+
*/
|
|
8
|
+
export function setApiKey(apiKey) {
|
|
9
|
+
groqClient = new Groq({ apiKey });
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Internal helper to call Groq
|
|
14
|
+
*/
|
|
15
|
+
async function call(messages) {
|
|
16
|
+
if (!groqClient) {
|
|
17
|
+
throw new Error("API key not initialized");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return groqClient.chat.completions.create({
|
|
21
|
+
model: "llama-3.1-8b-instant",
|
|
22
|
+
temperature: 0,
|
|
23
|
+
response_format: { type: "json_object" },
|
|
24
|
+
messages
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* ================= PHASE 1: FILE PLAN ================= */
|
|
29
|
+
export async function generateFilePlan(task, tests) {
|
|
30
|
+
const res = await call([
|
|
31
|
+
{
|
|
32
|
+
role: "system",
|
|
33
|
+
content: `
|
|
34
|
+
Return ONLY JSON.
|
|
35
|
+
|
|
36
|
+
Generate ONLY file paths required to pass the tests.
|
|
37
|
+
|
|
38
|
+
FORMAT:
|
|
39
|
+
{
|
|
40
|
+
"files": [
|
|
41
|
+
{ "path": "models/AnyModel.js" },
|
|
42
|
+
{ "path": "controllers/anyController.js" },
|
|
43
|
+
{ "path": "routes/anyRoutes.js" },
|
|
44
|
+
{ "path": "index.js" }
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
No explanations.
|
|
49
|
+
`
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
role: "user",
|
|
53
|
+
content: `TASK:\n${task}\n\nTEST CASES:\n${tests}`
|
|
54
|
+
}
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
const parsed = JSON.parse(res.choices[0].message.content);
|
|
58
|
+
|
|
59
|
+
if (!parsed.files || !Array.isArray(parsed.files)) {
|
|
60
|
+
throw new Error("Invalid file plan");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return parsed.files.map(f => f.path);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* ================= PHASE 2: FILE CONTENT ================= */
|
|
67
|
+
export async function generateFileContent(filePath, task, tests) {
|
|
68
|
+
let file = null;
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
const res = await call([
|
|
72
|
+
{
|
|
73
|
+
role: "system",
|
|
74
|
+
content: `
|
|
75
|
+
You are generating ONLY this file:
|
|
76
|
+
|
|
77
|
+
${filePath}
|
|
78
|
+
|
|
79
|
+
RULES:
|
|
80
|
+
- Tests are the ONLY specification
|
|
81
|
+
- Minimal code only
|
|
82
|
+
- No comments
|
|
83
|
+
- No console logs
|
|
84
|
+
- Use CommonJS (require/module.exports)
|
|
85
|
+
|
|
86
|
+
FORMAT:
|
|
87
|
+
{
|
|
88
|
+
"path": "${filePath}",
|
|
89
|
+
"content": "exact source code"
|
|
90
|
+
}
|
|
91
|
+
`
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
role: "user",
|
|
95
|
+
content: `TASK:\n${task}\n\nTEST CASES:\n${tests}`
|
|
96
|
+
}
|
|
97
|
+
]);
|
|
98
|
+
|
|
99
|
+
file = JSON.parse(res.choices[0].message.content);
|
|
100
|
+
} catch {
|
|
101
|
+
file = null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/* ===== MINIMUM GENERIC FALLBACK (MODEL ONLY) ===== */
|
|
105
|
+
if (
|
|
106
|
+
!file ||
|
|
107
|
+
typeof file.path !== "string" ||
|
|
108
|
+
typeof file.content !== "string" ||
|
|
109
|
+
file.content.trim().length === 0
|
|
110
|
+
) {
|
|
111
|
+
if (filePath.startsWith("models/")) {
|
|
112
|
+
const modelName = filePath
|
|
113
|
+
.replace("models/", "")
|
|
114
|
+
.replace(".js", "");
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
path: filePath,
|
|
118
|
+
content:
|
|
119
|
+
`const mongoose = require('mongoose');
|
|
120
|
+
|
|
121
|
+
const schema = new mongoose.Schema({}, { strict: false });
|
|
122
|
+
|
|
123
|
+
module.exports = mongoose.model('${modelName}', schema);
|
|
124
|
+
`
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
throw new Error(`Invalid or empty content for ${filePath}`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return file;
|
|
132
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import fs from "fs-extra";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
export async function writeFiles(baseDir, files) {
|
|
5
|
+
for (const file of files) {
|
|
6
|
+
const fullPath = path.join(baseDir, file.path);
|
|
7
|
+
await fs.ensureDir(path.dirname(fullPath));
|
|
8
|
+
await fs.writeFile(fullPath, file.content, "utf8");
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export const systemPrompt = `
|
|
2
|
+
You are solving an automated coding exam.
|
|
3
|
+
|
|
4
|
+
ABSOLUTE PRIORITY:
|
|
5
|
+
The given test cases are the ONLY specification.
|
|
6
|
+
Ignore best practices unless tests require them.
|
|
7
|
+
|
|
8
|
+
RULES (STRICT):
|
|
9
|
+
- Generate the MINIMUM code needed to pass tests
|
|
10
|
+
- Do NOT add extra logic
|
|
11
|
+
- Do NOT add extra validation
|
|
12
|
+
- Do NOT add console logs
|
|
13
|
+
- Do NOT add comments unless required
|
|
14
|
+
- Do NOT refactor
|
|
15
|
+
- Do NOT optimize
|
|
16
|
+
- Do NOT generalize
|
|
17
|
+
|
|
18
|
+
ARCHITECTURE RULE:
|
|
19
|
+
- Create ONLY the files required by tests
|
|
20
|
+
- File names, exports, function names MUST match tests EXACTLY
|
|
21
|
+
- Error messages MUST match character-by-character
|
|
22
|
+
- Status codes MUST match exactly
|
|
23
|
+
|
|
24
|
+
OUTPUT RULES:
|
|
25
|
+
- Output ONLY valid JSON
|
|
26
|
+
- No markdown
|
|
27
|
+
- No explanations
|
|
28
|
+
- No extra text
|
|
29
|
+
|
|
30
|
+
JSON FORMAT:
|
|
31
|
+
{
|
|
32
|
+
"files": [
|
|
33
|
+
{
|
|
34
|
+
"path": "relative/path/file.js",
|
|
35
|
+
"content": "exact code"
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
IMPORTANT:
|
|
41
|
+
Passing tests is MORE IMPORTANT than clean code.
|
|
42
|
+
If a simpler solution passes tests, choose it.
|
|
43
|
+
`;
|
package/src/runner.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import readlineSync from "readline-sync";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
|
|
5
|
+
import { setApiKey, generateFilePlan, generateFileContent } from "./aiClient.js";
|
|
6
|
+
import { writeFiles } from "./fileWriter.js";
|
|
7
|
+
|
|
8
|
+
/* ----------- API KEY (---) ----------- */
|
|
9
|
+
const apiKey = readlineSync.question("--- ", {
|
|
10
|
+
hideEchoBack: true
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
if (!apiKey || apiKey.trim().length === 0) {
|
|
14
|
+
console.log("API key is required");
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
setApiKey(apiKey);
|
|
19
|
+
|
|
20
|
+
/* ----------- TASK (-) ----------- */
|
|
21
|
+
const task = readlineSync.question("- ");
|
|
22
|
+
|
|
23
|
+
/* ----------- TESTS (-- text OR file path) ----------- */
|
|
24
|
+
let testsInput = readlineSync.question("-- ");
|
|
25
|
+
let tests;
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
if (
|
|
29
|
+
(testsInput.endsWith(".js") || testsInput.endsWith(".test.js")) &&
|
|
30
|
+
fs.existsSync(testsInput)
|
|
31
|
+
) {
|
|
32
|
+
tests = fs.readFileSync(testsInput, "utf8");
|
|
33
|
+
} else {
|
|
34
|
+
tests = testsInput;
|
|
35
|
+
}
|
|
36
|
+
} catch {
|
|
37
|
+
tests = testsInput;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
(async () => {
|
|
41
|
+
try {
|
|
42
|
+
const paths = await generateFilePlan(task, tests);
|
|
43
|
+
|
|
44
|
+
const files = [];
|
|
45
|
+
for (const p of paths) {
|
|
46
|
+
const file = await generateFileContent(p, task, tests);
|
|
47
|
+
files.push(file);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
await writeFiles(process.cwd(), files);
|
|
51
|
+
fs.writeFileSync(".ai_codegen_success", "");
|
|
52
|
+
} catch (e) {
|
|
53
|
+
fs.writeFileSync(".ai_codegen_error", e.message || "error");
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
})();
|