@brainforge/core 3.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/dist/index.d.ts +393 -0
- package/dist/index.js +3556 -0
- package/package.json +54 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,3556 @@
|
|
|
1
|
+
// src/state/manager.ts
|
|
2
|
+
import fs from "fs/promises";
|
|
3
|
+
import path from "path";
|
|
4
|
+
var STATE_DIR = ".brainforge";
|
|
5
|
+
var STATE_FILE = "core.json";
|
|
6
|
+
var StateManager = class {
|
|
7
|
+
constructor(workingDir) {
|
|
8
|
+
this.workingDir = workingDir;
|
|
9
|
+
this.statePath = path.join(workingDir, STATE_DIR, STATE_FILE);
|
|
10
|
+
}
|
|
11
|
+
workingDir;
|
|
12
|
+
statePath;
|
|
13
|
+
state = null;
|
|
14
|
+
async load() {
|
|
15
|
+
const raw = await fs.readFile(this.statePath, "utf-8");
|
|
16
|
+
this.state = JSON.parse(raw);
|
|
17
|
+
return this.state;
|
|
18
|
+
}
|
|
19
|
+
async save() {
|
|
20
|
+
if (!this.state) throw new Error("No state loaded \u2014 call init() or load() first.");
|
|
21
|
+
this.state.metadata.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
22
|
+
await fs.mkdir(path.dirname(this.statePath), { recursive: true });
|
|
23
|
+
await fs.writeFile(this.statePath, JSON.stringify(this.state, null, 2), "utf-8");
|
|
24
|
+
await this.writeDerivedViews();
|
|
25
|
+
}
|
|
26
|
+
async init(project) {
|
|
27
|
+
this.state = {
|
|
28
|
+
version: "3.0.0",
|
|
29
|
+
project,
|
|
30
|
+
phases: [],
|
|
31
|
+
context: {},
|
|
32
|
+
metadata: {
|
|
33
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
34
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
35
|
+
workingDirectory: this.workingDir
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
await this.save();
|
|
39
|
+
return this.state;
|
|
40
|
+
}
|
|
41
|
+
get() {
|
|
42
|
+
if (!this.state) throw new Error("State not loaded \u2014 call load() first.");
|
|
43
|
+
return this.state;
|
|
44
|
+
}
|
|
45
|
+
set(updater) {
|
|
46
|
+
if (!this.state) throw new Error("State not loaded.");
|
|
47
|
+
updater(this.state);
|
|
48
|
+
}
|
|
49
|
+
async configureAI(config) {
|
|
50
|
+
this.get().aiConfig = config;
|
|
51
|
+
await this.save();
|
|
52
|
+
}
|
|
53
|
+
async addPhase(phase) {
|
|
54
|
+
const newPhase = { ...phase, tasks: [] };
|
|
55
|
+
this.get().phases.push(newPhase);
|
|
56
|
+
await this.save();
|
|
57
|
+
return newPhase;
|
|
58
|
+
}
|
|
59
|
+
async addTask(task) {
|
|
60
|
+
const phase = this.get().phases.find((p) => p.id === task.phaseId);
|
|
61
|
+
if (!phase) throw new Error(`Phase "${task.phaseId}" not found.`);
|
|
62
|
+
phase.tasks.push(task);
|
|
63
|
+
await this.save();
|
|
64
|
+
}
|
|
65
|
+
async updateTaskStatus(taskId, status) {
|
|
66
|
+
for (const phase of this.get().phases) {
|
|
67
|
+
const task = phase.tasks.find((t) => t.id === taskId);
|
|
68
|
+
if (task) {
|
|
69
|
+
task.status = status;
|
|
70
|
+
task.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
71
|
+
await this.save();
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
throw new Error(`Task "${taskId}" not found.`);
|
|
76
|
+
}
|
|
77
|
+
async writeDerivedViews() {
|
|
78
|
+
await Promise.all([this.writeProjectMd(), this.writeStateMd()]);
|
|
79
|
+
}
|
|
80
|
+
async writeProjectMd() {
|
|
81
|
+
const s = this.state;
|
|
82
|
+
const phaseLines = s.phases.length ? s.phases.map((p) => `- [${p.status === "completed" ? "x" : " "}] **${p.name}** \u2014 ${p.description}`) : ["- No phases yet. Run `brainforge plan` to create one."];
|
|
83
|
+
const content = [
|
|
84
|
+
`# ${s.project.name}`,
|
|
85
|
+
"",
|
|
86
|
+
`> ${s.project.description}`,
|
|
87
|
+
"",
|
|
88
|
+
`**Type:** ${s.project.type} | **Language:** ${s.project.language}${s.project.framework ? ` | **Framework:** ${s.project.framework}` : ""}${s.project.studentLevel ? ` | **Level:** ${s.project.studentLevel}` : ""}`,
|
|
89
|
+
"",
|
|
90
|
+
"## Phases",
|
|
91
|
+
"",
|
|
92
|
+
...phaseLines,
|
|
93
|
+
"",
|
|
94
|
+
`---`,
|
|
95
|
+
`*Generated by BrainForge AI v3 \u2014 ${s.metadata.updatedAt}*`
|
|
96
|
+
].join("\n");
|
|
97
|
+
await fs.writeFile(path.join(this.workingDir, "PROJECT.md"), content, "utf-8");
|
|
98
|
+
}
|
|
99
|
+
async writeStateMd() {
|
|
100
|
+
const s = this.state;
|
|
101
|
+
const active = s.phases.find((p) => p.id === s.activePhaseId);
|
|
102
|
+
const totalTasks = s.phases.reduce((n, p) => n + p.tasks.length, 0);
|
|
103
|
+
const doneTasks = s.phases.reduce((n, p) => n + p.tasks.filter((t) => t.status === "done").length, 0);
|
|
104
|
+
const phaseBlocks = s.phases.map((p) => {
|
|
105
|
+
const done = p.tasks.filter((t) => t.status === "done").length;
|
|
106
|
+
return `### ${p.name} \`${p.status}\`
|
|
107
|
+
${p.description}
|
|
108
|
+
Tasks: ${done}/${p.tasks.length}`;
|
|
109
|
+
});
|
|
110
|
+
const content = [
|
|
111
|
+
"# Project State",
|
|
112
|
+
"",
|
|
113
|
+
`**Last Updated:** ${s.metadata.updatedAt}`,
|
|
114
|
+
`**Active Phase:** ${active ? active.name : "None"}`,
|
|
115
|
+
`**Progress:** ${doneTasks}/${totalTasks} tasks complete`,
|
|
116
|
+
"",
|
|
117
|
+
"## Phases",
|
|
118
|
+
"",
|
|
119
|
+
phaseBlocks.length ? phaseBlocks.join("\n\n") : "No phases yet."
|
|
120
|
+
].join("\n");
|
|
121
|
+
await fs.writeFile(path.join(this.workingDir, "STATE.md"), content, "utf-8");
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// src/state/state-machine.ts
|
|
126
|
+
function reduce(state, action) {
|
|
127
|
+
switch (action.type) {
|
|
128
|
+
case "SET_ACTIVE_PHASE":
|
|
129
|
+
return { ...state, activePhaseId: action.phaseId };
|
|
130
|
+
case "COMPLETE_PHASE": {
|
|
131
|
+
const phases = state.phases.map(
|
|
132
|
+
(p) => p.id === action.phaseId ? { ...p, status: "completed", completedAt: (/* @__PURE__ */ new Date()).toISOString() } : p
|
|
133
|
+
);
|
|
134
|
+
return { ...state, phases };
|
|
135
|
+
}
|
|
136
|
+
case "UPDATE_TASK": {
|
|
137
|
+
const phases = state.phases.map((p) => ({
|
|
138
|
+
...p,
|
|
139
|
+
tasks: p.tasks.map(
|
|
140
|
+
(t) => t.id === action.taskId ? { ...t, ...action.updates, updatedAt: (/* @__PURE__ */ new Date()).toISOString() } : t
|
|
141
|
+
)
|
|
142
|
+
}));
|
|
143
|
+
return { ...state, phases };
|
|
144
|
+
}
|
|
145
|
+
case "SET_CONTEXT":
|
|
146
|
+
return { ...state, context: { ...state.context, [action.key]: action.value } };
|
|
147
|
+
default:
|
|
148
|
+
return state;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// src/watcher/file-watcher.ts
|
|
153
|
+
import chokidar from "chokidar";
|
|
154
|
+
import { EventEmitter } from "events";
|
|
155
|
+
var FileWatcher = class extends EventEmitter {
|
|
156
|
+
watcher = null;
|
|
157
|
+
watch(rootDir, patterns = ["**/*"]) {
|
|
158
|
+
this.watcher = chokidar.watch(patterns, {
|
|
159
|
+
cwd: rootDir,
|
|
160
|
+
ignored: /(node_modules|\.git|\.brainforge)/,
|
|
161
|
+
persistent: true,
|
|
162
|
+
ignoreInitial: true
|
|
163
|
+
});
|
|
164
|
+
const emit = (type) => (filePath) => {
|
|
165
|
+
this.emit("change", {
|
|
166
|
+
type,
|
|
167
|
+
path: filePath,
|
|
168
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
169
|
+
});
|
|
170
|
+
};
|
|
171
|
+
this.watcher.on("add", emit("add")).on("change", emit("change")).on("unlink", emit("unlink"));
|
|
172
|
+
}
|
|
173
|
+
async stop() {
|
|
174
|
+
await this.watcher?.close();
|
|
175
|
+
this.watcher = null;
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
// src/prompt-engine/engine.ts
|
|
180
|
+
import Handlebars from "handlebars";
|
|
181
|
+
var BUILTIN_TEMPLATES = {
|
|
182
|
+
discuss: `You are BrainForge AI, helping a {{project.type}} developer with "{{project.name}}".
|
|
183
|
+
|
|
184
|
+
Project: {{project.description}}
|
|
185
|
+
Language: {{project.language}}{{#if project.framework}} / {{project.framework}}{{/if}}
|
|
186
|
+
{{#if project.studentLevel}}Student Level: {{project.studentLevel}}{{/if}}
|
|
187
|
+
|
|
188
|
+
The developer wants to discuss: {{topic}}
|
|
189
|
+
|
|
190
|
+
Provide structured, actionable guidance appropriate for their level. Be concise but thorough.`,
|
|
191
|
+
plan: `You are BrainForge AI. Analyze the following task for project "{{project.name}}":
|
|
192
|
+
|
|
193
|
+
Task: {{task.title}}
|
|
194
|
+
Description: {{task.description}}
|
|
195
|
+
|
|
196
|
+
Generate a structured implementation plan with:
|
|
197
|
+
1. Files to modify/create
|
|
198
|
+
2. Key functions to implement
|
|
199
|
+
3. Potential risks
|
|
200
|
+
4. Estimated complexity (1-10)
|
|
201
|
+
|
|
202
|
+
Keep the response focused and actionable.`,
|
|
203
|
+
review: `Review this code for a {{project.type}} developer{{#if project.studentLevel}} at {{project.studentLevel}} level{{/if}}.
|
|
204
|
+
|
|
205
|
+
File: {{filePath}}
|
|
206
|
+
\`\`\`{{project.language}}
|
|
207
|
+
{{code}}
|
|
208
|
+
\`\`\`
|
|
209
|
+
|
|
210
|
+
Evaluate:
|
|
211
|
+
- Correctness and logic
|
|
212
|
+
- Best practices for their experience level
|
|
213
|
+
{{#if isStudent}}- Academic integrity flags (patterns too advanced for declared level?)
|
|
214
|
+
- Can they explain every line?{{/if}}
|
|
215
|
+
- Top 3 improvement suggestions`
|
|
216
|
+
};
|
|
217
|
+
var PromptEngine = class {
|
|
218
|
+
compiled = /* @__PURE__ */ new Map();
|
|
219
|
+
constructor() {
|
|
220
|
+
for (const [name, source] of Object.entries(BUILTIN_TEMPLATES)) {
|
|
221
|
+
this.compiled.set(name, Handlebars.compile(source));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
render(templateName, context) {
|
|
225
|
+
const template = this.compiled.get(templateName);
|
|
226
|
+
if (!template) throw new Error(`Template "${templateName}" not found.`);
|
|
227
|
+
return template(context);
|
|
228
|
+
}
|
|
229
|
+
registerTemplate(name, source) {
|
|
230
|
+
this.compiled.set(name, Handlebars.compile(source));
|
|
231
|
+
}
|
|
232
|
+
listTemplates() {
|
|
233
|
+
return Array.from(this.compiled.keys());
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
// src/mapper/mapper.ts
|
|
238
|
+
import fs2 from "fs/promises";
|
|
239
|
+
import path2 from "path";
|
|
240
|
+
|
|
241
|
+
// src/mapper/extractors.ts
|
|
242
|
+
function extractFromFile(filePath, content, language) {
|
|
243
|
+
const normalizedPath = filePath.replace(/\\/g, "/");
|
|
244
|
+
switch (language) {
|
|
245
|
+
case "typescript":
|
|
246
|
+
case "javascript":
|
|
247
|
+
return extractJS(normalizedPath, content, language);
|
|
248
|
+
case "python":
|
|
249
|
+
return extractPython(normalizedPath, content);
|
|
250
|
+
case "java":
|
|
251
|
+
return extractJava(normalizedPath, content);
|
|
252
|
+
case "go":
|
|
253
|
+
return extractGo(normalizedPath, content);
|
|
254
|
+
case "php":
|
|
255
|
+
return extractPHP(normalizedPath, content);
|
|
256
|
+
default:
|
|
257
|
+
return emptyNode(normalizedPath, language);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
function extractJS(filePath, content, language) {
|
|
261
|
+
const lines = content.split("\n");
|
|
262
|
+
const functions = [];
|
|
263
|
+
const classes = [];
|
|
264
|
+
const imports = [];
|
|
265
|
+
const exports = [];
|
|
266
|
+
let currentClass = null;
|
|
267
|
+
let braceDepth = 0;
|
|
268
|
+
let classStartDepth = -1;
|
|
269
|
+
for (let i = 0; i < lines.length; i++) {
|
|
270
|
+
const line = lines[i];
|
|
271
|
+
const lineNum = i + 1;
|
|
272
|
+
braceDepth += (line.match(/\{/g) || []).length;
|
|
273
|
+
braceDepth -= (line.match(/\}/g) || []).length;
|
|
274
|
+
braceDepth = Math.max(0, braceDepth);
|
|
275
|
+
if (currentClass && braceDepth <= classStartDepth) {
|
|
276
|
+
classes.push(currentClass);
|
|
277
|
+
currentClass = null;
|
|
278
|
+
classStartDepth = -1;
|
|
279
|
+
}
|
|
280
|
+
let m = line.match(/^(?:export\s+)?(?:abstract\s+)?class\s+(\w+)/);
|
|
281
|
+
if (m) {
|
|
282
|
+
currentClass = { name: m[1], line: lineNum, isExported: /\bexport\b/.test(line), methods: [] };
|
|
283
|
+
classStartDepth = braceDepth - 1;
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
if (currentClass) {
|
|
287
|
+
const method = line.match(/^\s{2,}(?:(?:public|private|protected|static|async|override|get|set)\s+)*(\w+)\s*\([^)]*\)\s*(?::\s*[\w<>[\]|]+)?\s*\{/);
|
|
288
|
+
if (method && !["constructor", "if", "for", "while", "switch", "catch"].includes(method[1])) {
|
|
289
|
+
currentClass.methods.push(method[1]);
|
|
290
|
+
}
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
m = line.match(/^(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*[(<]/);
|
|
294
|
+
if (m) {
|
|
295
|
+
functions.push({ name: m[1], line: lineNum, isExported: /\bexport\b/.test(line), isAsync: /\basync\b/.test(line) });
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
m = line.match(/^(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\(/);
|
|
299
|
+
if (m) {
|
|
300
|
+
functions.push({ name: m[1], line: lineNum, isExported: /\bexport\b/.test(line), isAsync: /\basync\b/.test(line) });
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
m = line.match(/^import\s+.+?\s+from\s+['"]([^'"]+)['"]/);
|
|
304
|
+
if (m) {
|
|
305
|
+
imports.push(m[1]);
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
m = line.match(/^export\s+(?:default\s+)?(?:function|class|const|let|var|type|interface|enum)\s+(\w+)/);
|
|
309
|
+
if (m) {
|
|
310
|
+
exports.push(m[1]);
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
m = line.match(/^export\s+\{\s*([^}]+)\s*\}/);
|
|
314
|
+
if (m) {
|
|
315
|
+
exports.push(...m[1].split(",").map((e) => e.trim().split(/\s+/)[0]).filter(Boolean));
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (currentClass) classes.push(currentClass);
|
|
319
|
+
return { path: filePath, language, functions, classes, imports, exports, lastScanned: (/* @__PURE__ */ new Date()).toISOString() };
|
|
320
|
+
}
|
|
321
|
+
function extractPython(filePath, content) {
|
|
322
|
+
const lines = content.split("\n");
|
|
323
|
+
const functions = [];
|
|
324
|
+
const classes = [];
|
|
325
|
+
const imports = [];
|
|
326
|
+
const exports = [];
|
|
327
|
+
let currentClass = null;
|
|
328
|
+
for (let i = 0; i < lines.length; i++) {
|
|
329
|
+
const line = lines[i];
|
|
330
|
+
const lineNum = i + 1;
|
|
331
|
+
let m = line.match(/^class\s+(\w+)/);
|
|
332
|
+
if (m) {
|
|
333
|
+
if (currentClass) classes.push(currentClass);
|
|
334
|
+
currentClass = { name: m[1], line: lineNum, isExported: true, methods: [] };
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
if (currentClass && line.match(/^ (?:async\s+)?def\s+/)) {
|
|
338
|
+
m = line.match(/^ (?:async\s+)?def\s+(\w+)\s*\(/);
|
|
339
|
+
if (m) {
|
|
340
|
+
currentClass.methods.push(m[1]);
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
m = line.match(/^(?:async\s+)?def\s+(\w+)\s*\(/);
|
|
345
|
+
if (m) {
|
|
346
|
+
if (currentClass) {
|
|
347
|
+
classes.push(currentClass);
|
|
348
|
+
currentClass = null;
|
|
349
|
+
}
|
|
350
|
+
functions.push({ name: m[1], line: lineNum, isExported: true, isAsync: /^async/.test(line.trim()) });
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
m = line.match(/^import\s+([\w.]+)/);
|
|
354
|
+
if (m) {
|
|
355
|
+
imports.push(m[1]);
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
m = line.match(/^from\s+([\w.]+)\s+import/);
|
|
359
|
+
if (m) imports.push(m[1]);
|
|
360
|
+
}
|
|
361
|
+
if (currentClass) classes.push(currentClass);
|
|
362
|
+
return { path: filePath, language: "python", functions, classes, imports, exports, lastScanned: (/* @__PURE__ */ new Date()).toISOString() };
|
|
363
|
+
}
|
|
364
|
+
function extractJava(filePath, content) {
|
|
365
|
+
const lines = content.split("\n");
|
|
366
|
+
const functions = [];
|
|
367
|
+
const classes = [];
|
|
368
|
+
const imports = [];
|
|
369
|
+
for (let i = 0; i < lines.length; i++) {
|
|
370
|
+
const line = lines[i];
|
|
371
|
+
const lineNum = i + 1;
|
|
372
|
+
let m = line.match(/^import\s+([\w.]+);/);
|
|
373
|
+
if (m) {
|
|
374
|
+
imports.push(m[1]);
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
m = line.match(/(?:public|private|protected)?\s*(?:abstract\s+)?(?:class|interface|enum)\s+(\w+)/);
|
|
378
|
+
if (m) {
|
|
379
|
+
classes.push({ name: m[1], line: lineNum, isExported: /public/.test(line), methods: [] });
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
m = line.match(/(?:public|private|protected)\s+(?:static\s+)?(?:[\w<>[\]]+\s+)?(\w+)\s*\([^)]*\)\s*(?:throws\s+\w+\s*)?\{/);
|
|
383
|
+
if (m && !["if", "for", "while", "switch", "try", "catch"].includes(m[1])) {
|
|
384
|
+
functions.push({ name: m[1], line: lineNum, isExported: /public/.test(line), isAsync: false });
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return { path: filePath, language: "java", functions, classes, imports, exports: [], lastScanned: (/* @__PURE__ */ new Date()).toISOString() };
|
|
388
|
+
}
|
|
389
|
+
function extractGo(filePath, content) {
|
|
390
|
+
const lines = content.split("\n");
|
|
391
|
+
const functions = [];
|
|
392
|
+
const classes = [];
|
|
393
|
+
const imports = [];
|
|
394
|
+
for (let i = 0; i < lines.length; i++) {
|
|
395
|
+
const line = lines[i];
|
|
396
|
+
const lineNum = i + 1;
|
|
397
|
+
let m = line.match(/^import\s+"([^"]+)"/);
|
|
398
|
+
if (m) {
|
|
399
|
+
imports.push(m[1]);
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
m = line.match(/^type\s+(\w+)\s+struct/);
|
|
403
|
+
if (m) {
|
|
404
|
+
classes.push({ name: m[1], line: lineNum, isExported: /^[A-Z]/.test(m[1]), methods: [] });
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
m = line.match(/^func\s+(?:\(\w+\s+\*?\w+\)\s+)?(\w+)\s*\(/);
|
|
408
|
+
if (m) {
|
|
409
|
+
functions.push({ name: m[1], line: lineNum, isExported: /^[A-Z]/.test(m[1]), isAsync: false });
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return { path: filePath, language: "go", functions, classes, imports, exports: [], lastScanned: (/* @__PURE__ */ new Date()).toISOString() };
|
|
413
|
+
}
|
|
414
|
+
function extractPHP(filePath, content) {
|
|
415
|
+
const lines = content.split("\n");
|
|
416
|
+
const functions = [];
|
|
417
|
+
const classes = [];
|
|
418
|
+
const imports = [];
|
|
419
|
+
for (let i = 0; i < lines.length; i++) {
|
|
420
|
+
const line = lines[i];
|
|
421
|
+
const lineNum = i + 1;
|
|
422
|
+
let m = line.match(/^(?:use|require|include)\s+['"]?([^'";\s]+)/);
|
|
423
|
+
if (m) {
|
|
424
|
+
imports.push(m[1]);
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
m = line.match(/^(?:abstract\s+)?class\s+(\w+)/);
|
|
428
|
+
if (m) {
|
|
429
|
+
classes.push({ name: m[1], line: lineNum, isExported: true, methods: [] });
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
m = line.match(/^(?:public\s+|private\s+|protected\s+)?(?:static\s+)?function\s+(\w+)\s*\(/);
|
|
433
|
+
if (m) {
|
|
434
|
+
functions.push({ name: m[1], line: lineNum, isExported: /public/.test(line), isAsync: false });
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return { path: filePath, language: "php", functions, classes, imports, exports: [], lastScanned: (/* @__PURE__ */ new Date()).toISOString() };
|
|
438
|
+
}
|
|
439
|
+
function emptyNode(filePath, language) {
|
|
440
|
+
return { path: filePath, language, functions: [], classes: [], imports: [], exports: [], lastScanned: (/* @__PURE__ */ new Date()).toISOString() };
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// src/mapper/mapper.ts
|
|
444
|
+
var MAP_FILE = path2.join(".brainforge", "codebase-map.json");
|
|
445
|
+
var LANGUAGE_EXTENSIONS = {
|
|
446
|
+
typescript: [".ts", ".tsx"],
|
|
447
|
+
javascript: [".js", ".jsx", ".mjs", ".cjs"],
|
|
448
|
+
python: [".py"],
|
|
449
|
+
java: [".java"],
|
|
450
|
+
go: [".go"],
|
|
451
|
+
php: [".php"]
|
|
452
|
+
};
|
|
453
|
+
var IGNORED_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".brainforge", "dist", "build", "coverage", "__pycache__", ".venv", "vendor"]);
|
|
454
|
+
var CodebaseMapper = class {
|
|
455
|
+
constructor(workingDir) {
|
|
456
|
+
this.workingDir = workingDir;
|
|
457
|
+
}
|
|
458
|
+
workingDir;
|
|
459
|
+
async scan(language) {
|
|
460
|
+
const extensions = LANGUAGE_EXTENSIONS[language];
|
|
461
|
+
const sourceFiles = await this.findFiles(extensions);
|
|
462
|
+
const fileNodes = [];
|
|
463
|
+
for (const filePath of sourceFiles) {
|
|
464
|
+
try {
|
|
465
|
+
const content = await fs2.readFile(path2.join(this.workingDir, filePath), "utf-8");
|
|
466
|
+
fileNodes.push(extractFromFile(filePath, content, language));
|
|
467
|
+
} catch {
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
const map = {
|
|
471
|
+
version: "3.0.0",
|
|
472
|
+
scannedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
473
|
+
rootDir: this.workingDir,
|
|
474
|
+
files: fileNodes,
|
|
475
|
+
stats: {
|
|
476
|
+
totalFiles: fileNodes.length,
|
|
477
|
+
totalFunctions: fileNodes.reduce((n, f) => n + f.functions.length, 0),
|
|
478
|
+
totalClasses: fileNodes.reduce((n, f) => n + f.classes.length, 0)
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
await this.saveMap(map);
|
|
482
|
+
return map;
|
|
483
|
+
}
|
|
484
|
+
async load() {
|
|
485
|
+
const raw = await fs2.readFile(path2.join(this.workingDir, MAP_FILE), "utf-8");
|
|
486
|
+
return JSON.parse(raw);
|
|
487
|
+
}
|
|
488
|
+
findRelevantFiles(map, query) {
|
|
489
|
+
const keywords = tokenize(query);
|
|
490
|
+
if (keywords.length === 0) return map.files.slice(0, 5);
|
|
491
|
+
const scored = map.files.map((file) => {
|
|
492
|
+
let score = 0;
|
|
493
|
+
const pathLower = file.path.toLowerCase();
|
|
494
|
+
for (const kw of keywords) {
|
|
495
|
+
if (pathLower.includes(kw)) score += 3;
|
|
496
|
+
if (file.functions.some((f) => f.name.toLowerCase().includes(kw))) score += 2;
|
|
497
|
+
if (file.classes.some((c) => c.name.toLowerCase().includes(kw))) score += 2;
|
|
498
|
+
if (file.classes.some((c) => c.methods.some((m) => m.toLowerCase().includes(kw)))) score += 1;
|
|
499
|
+
}
|
|
500
|
+
return { file, score };
|
|
501
|
+
});
|
|
502
|
+
return scored.filter((s) => s.score > 0).sort((a, b) => b.score - a.score).slice(0, 8).map((s) => s.file);
|
|
503
|
+
}
|
|
504
|
+
async findFiles(extensions) {
|
|
505
|
+
const results = [];
|
|
506
|
+
await this.walk(this.workingDir, "", extensions, results);
|
|
507
|
+
return results;
|
|
508
|
+
}
|
|
509
|
+
async walk(root, rel, extensions, results) {
|
|
510
|
+
const dir = rel ? path2.join(root, rel) : root;
|
|
511
|
+
let entries;
|
|
512
|
+
try {
|
|
513
|
+
entries = await fs2.readdir(dir, { withFileTypes: true });
|
|
514
|
+
} catch {
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
for (const entry of entries) {
|
|
518
|
+
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
519
|
+
const relPath = rel ? `${rel}/${entry.name}` : entry.name;
|
|
520
|
+
if (entry.isDirectory()) {
|
|
521
|
+
await this.walk(root, relPath, extensions, results);
|
|
522
|
+
} else if (extensions.some((ext) => entry.name.endsWith(ext))) {
|
|
523
|
+
results.push(relPath);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
async saveMap(map) {
|
|
528
|
+
const dest = path2.join(this.workingDir, MAP_FILE);
|
|
529
|
+
await fs2.mkdir(path2.dirname(dest), { recursive: true });
|
|
530
|
+
await fs2.writeFile(dest, JSON.stringify(map, null, 2), "utf-8");
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
function tokenize(text) {
|
|
534
|
+
const STOP_WORDS = /* @__PURE__ */ new Set([
|
|
535
|
+
"a",
|
|
536
|
+
"an",
|
|
537
|
+
"the",
|
|
538
|
+
"and",
|
|
539
|
+
"or",
|
|
540
|
+
"but",
|
|
541
|
+
"in",
|
|
542
|
+
"on",
|
|
543
|
+
"at",
|
|
544
|
+
"to",
|
|
545
|
+
"for",
|
|
546
|
+
"of",
|
|
547
|
+
"with",
|
|
548
|
+
"by",
|
|
549
|
+
"from",
|
|
550
|
+
"as",
|
|
551
|
+
"is",
|
|
552
|
+
"was",
|
|
553
|
+
"are",
|
|
554
|
+
"were",
|
|
555
|
+
"be",
|
|
556
|
+
"been",
|
|
557
|
+
"being",
|
|
558
|
+
"have",
|
|
559
|
+
"has",
|
|
560
|
+
"had",
|
|
561
|
+
"do",
|
|
562
|
+
"does",
|
|
563
|
+
"did",
|
|
564
|
+
"will",
|
|
565
|
+
"would",
|
|
566
|
+
"could",
|
|
567
|
+
"should",
|
|
568
|
+
"may",
|
|
569
|
+
"might",
|
|
570
|
+
"i",
|
|
571
|
+
"my",
|
|
572
|
+
"we",
|
|
573
|
+
"our",
|
|
574
|
+
"it",
|
|
575
|
+
"its",
|
|
576
|
+
"this",
|
|
577
|
+
"that",
|
|
578
|
+
"which",
|
|
579
|
+
"how",
|
|
580
|
+
"what",
|
|
581
|
+
"when",
|
|
582
|
+
"where",
|
|
583
|
+
"add",
|
|
584
|
+
"create",
|
|
585
|
+
"new",
|
|
586
|
+
"implement",
|
|
587
|
+
"build",
|
|
588
|
+
"make",
|
|
589
|
+
"write",
|
|
590
|
+
"update"
|
|
591
|
+
]);
|
|
592
|
+
return text.toLowerCase().split(/\W+/).filter((w) => w.length > 2 && !STOP_WORDS.has(w));
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// src/planner/planner.ts
|
|
596
|
+
import { randomUUID } from "crypto";
|
|
597
|
+
var RISK_KEYWORDS = {
|
|
598
|
+
auth: "Authentication logic requires careful security review \u2014 never hardcode secrets.",
|
|
599
|
+
password: "Password handling must use proper hashing (bcrypt/argon2), never plain text.",
|
|
600
|
+
token: "Token validation needs expiry checks and secure storage.",
|
|
601
|
+
database: "Database changes may require migrations and backward compatibility.",
|
|
602
|
+
migration: "Migrations are irreversible \u2014 test on a copy before running in production.",
|
|
603
|
+
delete: "Deletion operations should be soft-deleted or confirmed before executing.",
|
|
604
|
+
remove: "Removal of functionality may break dependent code \u2014 check all call sites.",
|
|
605
|
+
permission: "Permission checks must be enforced on the server, not just the client.",
|
|
606
|
+
upload: "File uploads need type validation, size limits, and path sanitization.",
|
|
607
|
+
payment: "Payment flows require idempotency keys and thorough error handling.",
|
|
608
|
+
email: "Email delivery is asynchronous \u2014 queue it to avoid blocking the request.",
|
|
609
|
+
cache: "Caching logic must handle invalidation to avoid stale data.",
|
|
610
|
+
concurrent: "Concurrent access requires locks or atomic operations to prevent race conditions.",
|
|
611
|
+
async: "Unhandled promise rejections will silently fail \u2014 always await or catch."
|
|
612
|
+
};
|
|
613
|
+
var TaskPlanner = class {
|
|
614
|
+
plan(title, description, map, relevantFiles, project) {
|
|
615
|
+
const id = randomUUID();
|
|
616
|
+
const steps = this.buildSteps(title, description, relevantFiles);
|
|
617
|
+
const impactedFiles = relevantFiles.map((f) => f.path);
|
|
618
|
+
const impactedFunctions = relevantFiles.flatMap((f) => [
|
|
619
|
+
...f.functions.map((fn) => `${f.path}:${fn.name}`),
|
|
620
|
+
...f.classes.flatMap((c) => c.methods.map((m) => `${f.path}:${c.name}.${m}`))
|
|
621
|
+
]);
|
|
622
|
+
const risks = this.detectRisks(title + " " + description);
|
|
623
|
+
const complexity = this.estimateComplexity(steps.length, impactedFiles.length, risks.length);
|
|
624
|
+
const agentPrompt = this.buildAgentPrompt(title, description, steps, project);
|
|
625
|
+
return {
|
|
626
|
+
id,
|
|
627
|
+
title,
|
|
628
|
+
description,
|
|
629
|
+
impactedFiles,
|
|
630
|
+
impactedFunctions,
|
|
631
|
+
steps,
|
|
632
|
+
estimatedComplexity: complexity,
|
|
633
|
+
risks,
|
|
634
|
+
agentPrompt,
|
|
635
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
buildSteps(title, description, files) {
|
|
639
|
+
const steps = [];
|
|
640
|
+
const combined = (title + " " + description).toLowerCase();
|
|
641
|
+
if (files.length === 0) {
|
|
642
|
+
steps.push({
|
|
643
|
+
id: randomUUID(),
|
|
644
|
+
order: 1,
|
|
645
|
+
action: "create",
|
|
646
|
+
file: "src/[module]/index.ts",
|
|
647
|
+
description: `Create the main module file for: ${title}`,
|
|
648
|
+
targetFunctions: []
|
|
649
|
+
});
|
|
650
|
+
return steps;
|
|
651
|
+
}
|
|
652
|
+
for (let i = 0; i < files.length; i++) {
|
|
653
|
+
const file = files[i];
|
|
654
|
+
const action = this.inferAction(combined, file);
|
|
655
|
+
const targetFns = [
|
|
656
|
+
...file.functions.map((f) => f.name),
|
|
657
|
+
...file.classes.flatMap((c) => c.methods)
|
|
658
|
+
].slice(0, 5);
|
|
659
|
+
steps.push({
|
|
660
|
+
id: randomUUID(),
|
|
661
|
+
order: i + 1,
|
|
662
|
+
action,
|
|
663
|
+
file: file.path,
|
|
664
|
+
description: `${action === "modify" ? "Update" : "Review"} ${file.path} for: ${title}`,
|
|
665
|
+
targetFunctions: targetFns
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
return steps;
|
|
669
|
+
}
|
|
670
|
+
inferAction(combined, file) {
|
|
671
|
+
const hasCode = file.functions.length > 0 || file.classes.length > 0;
|
|
672
|
+
if (!hasCode) return "review";
|
|
673
|
+
if (/\b(add|create|new|implement|introduce)\b/.test(combined)) return "modify";
|
|
674
|
+
if (/\b(delete|remove|drop)\b/.test(combined)) return "modify";
|
|
675
|
+
return "modify";
|
|
676
|
+
}
|
|
677
|
+
detectRisks(text) {
|
|
678
|
+
const lower = text.toLowerCase();
|
|
679
|
+
return Object.entries(RISK_KEYWORDS).filter(([keyword]) => lower.includes(keyword)).map(([, message]) => message);
|
|
680
|
+
}
|
|
681
|
+
estimateComplexity(steps, files, risks) {
|
|
682
|
+
const base = 1 + steps * 0.8 + files * 0.5 + risks * 0.7;
|
|
683
|
+
return Math.min(10, Math.max(1, Math.round(base)));
|
|
684
|
+
}
|
|
685
|
+
buildAgentPrompt(title, description, steps, project) {
|
|
686
|
+
const stepList = steps.map((s) => `${s.order}. [${s.action.toUpperCase()}] ${s.file}
|
|
687
|
+
${s.description}`).join("\n");
|
|
688
|
+
return `You are implementing a task for BrainForge AI.
|
|
689
|
+
|
|
690
|
+
Project: ${project.name} (${project.language}${project.framework ? "/" + project.framework : ""})
|
|
691
|
+
Developer Type: ${project.type}${project.studentLevel ? " / " + project.studentLevel : ""}
|
|
692
|
+
|
|
693
|
+
## Task
|
|
694
|
+
${title}
|
|
695
|
+
|
|
696
|
+
${description}
|
|
697
|
+
|
|
698
|
+
## Implementation Steps
|
|
699
|
+
${stepList}
|
|
700
|
+
|
|
701
|
+
## Instructions
|
|
702
|
+
- Follow the steps in order.
|
|
703
|
+
- After each file change, verify correctness before moving to the next step.
|
|
704
|
+
- Use existing patterns and conventions already in the codebase.
|
|
705
|
+
- Do not introduce dependencies not already in use unless necessary.
|
|
706
|
+
${project.type === "student" ? "- Write code you can explain line-by-line \u2014 you will be asked to defend it." : ""}`;
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
|
|
710
|
+
// src/professor/scanner.ts
|
|
711
|
+
import fs3 from "fs/promises";
|
|
712
|
+
import path3 from "path";
|
|
713
|
+
|
|
714
|
+
// src/professor/patterns.ts
|
|
715
|
+
var PATTERNS = [
|
|
716
|
+
// ── JavaScript / TypeScript ─────────────────────────────────────────────────
|
|
717
|
+
{
|
|
718
|
+
id: "js-currying",
|
|
719
|
+
name: "Curried function",
|
|
720
|
+
explanation: "Currying (returning functions from functions) is an advanced functional programming pattern.",
|
|
721
|
+
regex: /=>\s*(?:\([^)]*\)|[a-zA-Z_$]\w*)\s*=>/,
|
|
722
|
+
languages: ["javascript", "typescript"],
|
|
723
|
+
flagForLevels: ["beginner"],
|
|
724
|
+
severity: "warn"
|
|
725
|
+
},
|
|
726
|
+
{
|
|
727
|
+
id: "js-generator",
|
|
728
|
+
name: "Generator function",
|
|
729
|
+
explanation: "`function*` and `yield` are advanced control-flow mechanisms.",
|
|
730
|
+
regex: /function\s*\*|yield\s+/,
|
|
731
|
+
languages: ["javascript", "typescript"],
|
|
732
|
+
flagForLevels: ["beginner"],
|
|
733
|
+
severity: "warn"
|
|
734
|
+
},
|
|
735
|
+
{
|
|
736
|
+
id: "js-proxy",
|
|
737
|
+
name: "Proxy / Reflect",
|
|
738
|
+
explanation: "Proxy and Reflect are advanced metaprogramming APIs.",
|
|
739
|
+
regex: /\bnew\s+Proxy\s*\(|\bReflect\./,
|
|
740
|
+
languages: ["javascript", "typescript"],
|
|
741
|
+
flagForLevels: ["beginner", "intermediate"],
|
|
742
|
+
severity: "error"
|
|
743
|
+
},
|
|
744
|
+
{
|
|
745
|
+
id: "js-symbol",
|
|
746
|
+
name: "Symbol usage",
|
|
747
|
+
explanation: "Symbols are a low-level primitive mainly used in library/framework internals.",
|
|
748
|
+
regex: /\bSymbol\s*\(/,
|
|
749
|
+
languages: ["javascript", "typescript"],
|
|
750
|
+
flagForLevels: ["beginner"],
|
|
751
|
+
severity: "warn"
|
|
752
|
+
},
|
|
753
|
+
{
|
|
754
|
+
id: "js-define-property",
|
|
755
|
+
name: "Object.defineProperty",
|
|
756
|
+
explanation: "Manual property descriptor manipulation is metaprogramming \u2014 rarely needed in application code.",
|
|
757
|
+
regex: /Object\.defineProperty\s*\(/,
|
|
758
|
+
languages: ["javascript", "typescript"],
|
|
759
|
+
flagForLevels: ["beginner", "intermediate"],
|
|
760
|
+
severity: "warn"
|
|
761
|
+
},
|
|
762
|
+
{
|
|
763
|
+
id: "js-decorator",
|
|
764
|
+
name: "Decorator",
|
|
765
|
+
explanation: "Decorators are a TypeScript/Stage-3 feature for class metaprogramming.",
|
|
766
|
+
regex: /^\s*@\w+/m,
|
|
767
|
+
languages: ["javascript", "typescript"],
|
|
768
|
+
flagForLevels: ["beginner"],
|
|
769
|
+
severity: "warn"
|
|
770
|
+
},
|
|
771
|
+
{
|
|
772
|
+
id: "ts-conditional-type",
|
|
773
|
+
name: "Conditional type / infer",
|
|
774
|
+
explanation: "`infer` in conditional types is an advanced TypeScript type-system feature.",
|
|
775
|
+
regex: /\binfer\s+\w+/,
|
|
776
|
+
languages: ["typescript"],
|
|
777
|
+
flagForLevels: ["beginner", "intermediate"],
|
|
778
|
+
severity: "warn"
|
|
779
|
+
},
|
|
780
|
+
{
|
|
781
|
+
id: "js-weakmap",
|
|
782
|
+
name: "WeakMap / WeakSet",
|
|
783
|
+
explanation: "Weak collections are a memory-management tool rarely needed in student projects.",
|
|
784
|
+
regex: /\bnew\s+Weak(?:Map|Set|Ref)\s*\(/,
|
|
785
|
+
languages: ["javascript", "typescript"],
|
|
786
|
+
flagForLevels: ["beginner"],
|
|
787
|
+
severity: "warn"
|
|
788
|
+
},
|
|
789
|
+
// ── Python ───────────────────────────────────────────────────────────────────
|
|
790
|
+
{
|
|
791
|
+
id: "py-metaclass",
|
|
792
|
+
name: "Metaclass",
|
|
793
|
+
explanation: "Metaclasses override Python class creation machinery \u2014 an expert-level feature.",
|
|
794
|
+
regex: /\bmetaclass\s*=/,
|
|
795
|
+
languages: ["python"],
|
|
796
|
+
flagForLevels: ["beginner", "intermediate"],
|
|
797
|
+
severity: "error"
|
|
798
|
+
},
|
|
799
|
+
{
|
|
800
|
+
id: "py-descriptor",
|
|
801
|
+
name: "Descriptor protocol",
|
|
802
|
+
explanation: "`__get__` / `__set__` / `__delete__` implement the descriptor protocol used in framework internals.",
|
|
803
|
+
regex: /def\s+__(?:get|set|delete)__\s*\(/,
|
|
804
|
+
languages: ["python"],
|
|
805
|
+
flagForLevels: ["beginner", "intermediate"],
|
|
806
|
+
severity: "warn"
|
|
807
|
+
},
|
|
808
|
+
{
|
|
809
|
+
id: "py-context-manager",
|
|
810
|
+
name: "Custom context manager",
|
|
811
|
+
explanation: "Implementing `__enter__` / `__exit__` is an intermediate-to-advanced pattern.",
|
|
812
|
+
regex: /def\s+__(?:enter|exit)__\s*\(/,
|
|
813
|
+
languages: ["python"],
|
|
814
|
+
flagForLevels: ["beginner"],
|
|
815
|
+
severity: "warn"
|
|
816
|
+
},
|
|
817
|
+
{
|
|
818
|
+
id: "py-abc",
|
|
819
|
+
name: "Abstract Base Class",
|
|
820
|
+
explanation: "`abc.ABC` and `@abstractmethod` are used to define interfaces \u2014 uncommon in intro projects.",
|
|
821
|
+
regex: /from\s+abc\s+import|ABC\s*\)|\@abstractmethod/,
|
|
822
|
+
languages: ["python"],
|
|
823
|
+
flagForLevels: ["beginner"],
|
|
824
|
+
severity: "warn"
|
|
825
|
+
},
|
|
826
|
+
{
|
|
827
|
+
id: "py-generator-send",
|
|
828
|
+
name: "Generator.send()",
|
|
829
|
+
explanation: "`.send()` on generators is an advanced coroutine-like pattern.",
|
|
830
|
+
regex: /\.\s*send\s*\(/,
|
|
831
|
+
languages: ["python"],
|
|
832
|
+
flagForLevels: ["beginner"],
|
|
833
|
+
severity: "warn"
|
|
834
|
+
},
|
|
835
|
+
// ── Java ──────────────────────────────────────────────────────────────────────
|
|
836
|
+
{
|
|
837
|
+
id: "java-reflection",
|
|
838
|
+
name: "Reflection API",
|
|
839
|
+
explanation: "Java Reflection bypasses compile-time checks and is a framework-level tool, not student code.",
|
|
840
|
+
regex: /\.getDeclaredMethod|\.getDeclaredField|Class\.forName\s*\(/,
|
|
841
|
+
languages: ["java"],
|
|
842
|
+
flagForLevels: ["beginner", "intermediate"],
|
|
843
|
+
severity: "error"
|
|
844
|
+
},
|
|
845
|
+
{
|
|
846
|
+
id: "java-generic-wildcard",
|
|
847
|
+
name: "Unbounded wildcard",
|
|
848
|
+
explanation: "`<?>` wildcards and generic bounds are advanced generics topics.",
|
|
849
|
+
regex: /<\s*\?\s*(?:extends|super)/,
|
|
850
|
+
languages: ["java"],
|
|
851
|
+
flagForLevels: ["beginner"],
|
|
852
|
+
severity: "warn"
|
|
853
|
+
}
|
|
854
|
+
];
|
|
855
|
+
|
|
856
|
+
// src/professor/scanner.ts
|
|
857
|
+
var LANGUAGE_EXTENSIONS2 = {
|
|
858
|
+
typescript: [".ts", ".tsx"],
|
|
859
|
+
javascript: [".js", ".jsx", ".mjs"],
|
|
860
|
+
python: [".py"],
|
|
861
|
+
java: [".java"],
|
|
862
|
+
go: [".go"],
|
|
863
|
+
php: [".php"]
|
|
864
|
+
};
|
|
865
|
+
var IGNORED_DIRS2 = /* @__PURE__ */ new Set([
|
|
866
|
+
"node_modules",
|
|
867
|
+
".git",
|
|
868
|
+
".brainforge",
|
|
869
|
+
"dist",
|
|
870
|
+
"build",
|
|
871
|
+
"coverage",
|
|
872
|
+
"__pycache__",
|
|
873
|
+
".venv",
|
|
874
|
+
"vendor"
|
|
875
|
+
]);
|
|
876
|
+
var ProfessorScanner = class {
|
|
877
|
+
constructor(workingDir) {
|
|
878
|
+
this.workingDir = workingDir;
|
|
879
|
+
}
|
|
880
|
+
workingDir;
|
|
881
|
+
async scan(language, studentLevel) {
|
|
882
|
+
const applicable = PATTERNS.filter(
|
|
883
|
+
(p) => p.languages.includes(language) && p.flagForLevels.includes(studentLevel)
|
|
884
|
+
);
|
|
885
|
+
const extensions = LANGUAGE_EXTENSIONS2[language];
|
|
886
|
+
const files = await this.findFiles(extensions);
|
|
887
|
+
const matches = [];
|
|
888
|
+
for (const relPath of files) {
|
|
889
|
+
const content = await fs3.readFile(path3.join(this.workingDir, relPath), "utf-8").catch(() => null);
|
|
890
|
+
if (!content) continue;
|
|
891
|
+
const lines = content.split("\n");
|
|
892
|
+
for (const pattern of applicable) {
|
|
893
|
+
for (let i = 0; i < lines.length; i++) {
|
|
894
|
+
const line = lines[i];
|
|
895
|
+
if (pattern.regex.test(line)) {
|
|
896
|
+
matches.push({
|
|
897
|
+
patternId: pattern.id,
|
|
898
|
+
name: pattern.name,
|
|
899
|
+
explanation: pattern.explanation,
|
|
900
|
+
severity: pattern.severity,
|
|
901
|
+
file: relPath.replace(/\\/g, "/"),
|
|
902
|
+
line: i + 1,
|
|
903
|
+
snippet: line.trim().slice(0, 120)
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
pattern.regex.lastIndex = 0;
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
const errors = matches.filter((m) => m.severity === "error").length;
|
|
911
|
+
const warnings = matches.filter((m) => m.severity === "warn").length;
|
|
912
|
+
const report = {
|
|
913
|
+
scannedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
914
|
+
language,
|
|
915
|
+
studentLevel,
|
|
916
|
+
totalFilesScanned: files.length,
|
|
917
|
+
matches,
|
|
918
|
+
summary: { errors, warnings, clean: matches.length === 0 }
|
|
919
|
+
};
|
|
920
|
+
await this.writeReport(report);
|
|
921
|
+
return report;
|
|
922
|
+
}
|
|
923
|
+
async findFiles(extensions) {
|
|
924
|
+
const results = [];
|
|
925
|
+
await this.walk("", extensions, results);
|
|
926
|
+
return results;
|
|
927
|
+
}
|
|
928
|
+
async walk(rel, extensions, results) {
|
|
929
|
+
const dir = rel ? path3.join(this.workingDir, rel) : this.workingDir;
|
|
930
|
+
let entries;
|
|
931
|
+
try {
|
|
932
|
+
entries = await fs3.readdir(dir, { withFileTypes: true });
|
|
933
|
+
} catch {
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
for (const entry of entries) {
|
|
937
|
+
if (IGNORED_DIRS2.has(entry.name)) continue;
|
|
938
|
+
const relPath = rel ? `${rel}/${entry.name}` : entry.name;
|
|
939
|
+
if (entry.isDirectory()) {
|
|
940
|
+
await this.walk(relPath, extensions, results);
|
|
941
|
+
} else if (extensions.some((ext) => entry.name.endsWith(ext))) {
|
|
942
|
+
results.push(relPath);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
async writeReport(report) {
|
|
947
|
+
const lines = [
|
|
948
|
+
"# Professor Report",
|
|
949
|
+
"",
|
|
950
|
+
`**Scanned:** ${report.scannedAt}`,
|
|
951
|
+
`**Language:** ${report.language} | **Level:** ${report.studentLevel}`,
|
|
952
|
+
`**Files scanned:** ${report.totalFilesScanned}`,
|
|
953
|
+
`**Result:** ${report.summary.clean ? "CLEAN" : `${report.summary.errors} error(s), ${report.summary.warnings} warning(s)`}`,
|
|
954
|
+
""
|
|
955
|
+
];
|
|
956
|
+
if (report.summary.clean) {
|
|
957
|
+
lines.push("No advanced patterns detected. Your code matches your declared level.");
|
|
958
|
+
} else {
|
|
959
|
+
lines.push("## Findings", "");
|
|
960
|
+
for (const m of report.matches) {
|
|
961
|
+
const icon = m.severity === "error" ? "[ERROR]" : "[WARN]";
|
|
962
|
+
lines.push(
|
|
963
|
+
`### ${icon} ${m.name}`,
|
|
964
|
+
`**File:** \`${m.file}\` (line ${m.line})`,
|
|
965
|
+
`**Pattern:** \`${m.snippet}\``,
|
|
966
|
+
`**Why this matters:** ${m.explanation}`,
|
|
967
|
+
""
|
|
968
|
+
);
|
|
969
|
+
}
|
|
970
|
+
lines.push(
|
|
971
|
+
"---",
|
|
972
|
+
"",
|
|
973
|
+
"> These findings do not mean your code is wrong. They mean you may be asked",
|
|
974
|
+
"> to explain these patterns in detail during a code defense.",
|
|
975
|
+
""
|
|
976
|
+
);
|
|
977
|
+
}
|
|
978
|
+
const dest = path3.join(this.workingDir, "PROFESSOR_REPORT.md");
|
|
979
|
+
await fs3.writeFile(dest, lines.join("\n"), "utf-8");
|
|
980
|
+
}
|
|
981
|
+
};
|
|
982
|
+
|
|
983
|
+
// src/server/server.ts
|
|
984
|
+
import http from "http";
|
|
985
|
+
import fs4 from "fs/promises";
|
|
986
|
+
import path4 from "path";
|
|
987
|
+
import { fileURLToPath } from "url";
|
|
988
|
+
import { WebSocketServer, WebSocket } from "ws";
|
|
989
|
+
var MIME = {
|
|
990
|
+
".html": "text/html; charset=utf-8",
|
|
991
|
+
".js": "application/javascript",
|
|
992
|
+
".css": "text/css",
|
|
993
|
+
".json": "application/json",
|
|
994
|
+
".svg": "image/svg+xml",
|
|
995
|
+
".ico": "image/x-icon",
|
|
996
|
+
".woff2": "font/woff2"
|
|
997
|
+
};
|
|
998
|
+
var BrainForgeServer = class {
|
|
999
|
+
constructor(workingDir, port = 3742, dashboardDir) {
|
|
1000
|
+
this.workingDir = workingDir;
|
|
1001
|
+
this.port = port;
|
|
1002
|
+
const here = path4.dirname(fileURLToPath(import.meta.url));
|
|
1003
|
+
this.dashboardDir = dashboardDir ?? path4.resolve(here, "../../../dashboard/dist");
|
|
1004
|
+
}
|
|
1005
|
+
workingDir;
|
|
1006
|
+
port;
|
|
1007
|
+
httpServer;
|
|
1008
|
+
wss;
|
|
1009
|
+
clients = /* @__PURE__ */ new Set();
|
|
1010
|
+
dashboardDir;
|
|
1011
|
+
boundPort;
|
|
1012
|
+
async start() {
|
|
1013
|
+
this.httpServer = http.createServer((req, res) => {
|
|
1014
|
+
void this.handle(req, res);
|
|
1015
|
+
});
|
|
1016
|
+
this.wss = new WebSocketServer({ server: this.httpServer });
|
|
1017
|
+
this.wss.on("connection", (ws) => {
|
|
1018
|
+
this.clients.add(ws);
|
|
1019
|
+
ws.on("close", () => this.clients.delete(ws));
|
|
1020
|
+
ws.send(JSON.stringify({ type: "connected", payload: { version: "3.0.0" } }));
|
|
1021
|
+
});
|
|
1022
|
+
await new Promise(
|
|
1023
|
+
(resolve, reject) => this.httpServer.listen(this.port, "127.0.0.1", () => {
|
|
1024
|
+
this.boundPort = this.httpServer.address().port;
|
|
1025
|
+
resolve();
|
|
1026
|
+
}).on("error", reject)
|
|
1027
|
+
);
|
|
1028
|
+
}
|
|
1029
|
+
stop() {
|
|
1030
|
+
this.clients.forEach((ws) => ws.close());
|
|
1031
|
+
this.wss?.close();
|
|
1032
|
+
this.httpServer?.close();
|
|
1033
|
+
}
|
|
1034
|
+
broadcast(type, payload) {
|
|
1035
|
+
const msg = JSON.stringify({ type, payload, ts: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1036
|
+
for (const ws of this.clients) {
|
|
1037
|
+
if (ws.readyState === WebSocket.OPEN) ws.send(msg);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
get url() {
|
|
1041
|
+
return `http://127.0.0.1:${this.boundPort}`;
|
|
1042
|
+
}
|
|
1043
|
+
async handle(req, res) {
|
|
1044
|
+
const { pathname } = new URL(req.url ?? "/", `http://localhost`);
|
|
1045
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
1046
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
1047
|
+
try {
|
|
1048
|
+
if (pathname === "/api/health") return this.json(res, { ok: true, version: "3.0.0" });
|
|
1049
|
+
if (pathname === "/api/state") return await this.fileJson(res, ".brainforge/core.json");
|
|
1050
|
+
if (pathname === "/api/map") return await this.fileJson(res, ".brainforge/codebase-map.json");
|
|
1051
|
+
if (pathname === "/api/scan") return await this.fileText(res, "PROFESSOR_REPORT.md");
|
|
1052
|
+
await this.static(res, pathname);
|
|
1053
|
+
} catch (err) {
|
|
1054
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
1055
|
+
res.end(JSON.stringify({ error: String(err) }));
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
json(res, data) {
|
|
1059
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1060
|
+
res.end(JSON.stringify(data));
|
|
1061
|
+
}
|
|
1062
|
+
async fileJson(res, rel) {
|
|
1063
|
+
try {
|
|
1064
|
+
const raw = await fs4.readFile(path4.join(this.workingDir, rel), "utf-8");
|
|
1065
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1066
|
+
res.end(raw);
|
|
1067
|
+
} catch {
|
|
1068
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
1069
|
+
res.end(JSON.stringify({ error: "not found" }));
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
async fileText(res, rel) {
|
|
1073
|
+
try {
|
|
1074
|
+
const raw = await fs4.readFile(path4.join(this.workingDir, rel), "utf-8");
|
|
1075
|
+
res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
|
|
1076
|
+
res.end(raw);
|
|
1077
|
+
} catch {
|
|
1078
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
1079
|
+
res.end("");
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
async static(res, pathname) {
|
|
1083
|
+
let target = path4.join(this.dashboardDir, pathname === "/" ? "index.html" : pathname);
|
|
1084
|
+
let found = await exists(target);
|
|
1085
|
+
if (!found) {
|
|
1086
|
+
target = path4.join(this.dashboardDir, "index.html");
|
|
1087
|
+
found = await exists(target);
|
|
1088
|
+
}
|
|
1089
|
+
if (!found) {
|
|
1090
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
1091
|
+
res.end("Dashboard not built. Run: npm run build --workspace=packages/dashboard");
|
|
1092
|
+
return;
|
|
1093
|
+
}
|
|
1094
|
+
const ext = path4.extname(target);
|
|
1095
|
+
res.writeHead(200, { "Content-Type": MIME[ext] ?? "application/octet-stream" });
|
|
1096
|
+
res.end(await fs4.readFile(target));
|
|
1097
|
+
}
|
|
1098
|
+
};
|
|
1099
|
+
async function exists(p) {
|
|
1100
|
+
return fs4.access(p).then(() => true).catch(() => false);
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
// src/defense/defense.ts
|
|
1104
|
+
import fs5 from "fs/promises";
|
|
1105
|
+
import path5 from "path";
|
|
1106
|
+
|
|
1107
|
+
// src/defense/question-bank.ts
|
|
1108
|
+
var STATIC_QUESTIONS = [
|
|
1109
|
+
// ── TypeScript / JavaScript ─────────────────────────────────────────────
|
|
1110
|
+
{
|
|
1111
|
+
id: "js-promise-explain",
|
|
1112
|
+
text: "What is a Promise, and why did you use async/await instead of .then()/.catch()?",
|
|
1113
|
+
modelAnswer: "A Promise represents a value that will be available in the future. async/await is syntactic sugar over Promises that makes asynchronous code read like synchronous code, improving readability and making error handling with try/catch more natural.",
|
|
1114
|
+
difficulty: "easy",
|
|
1115
|
+
languages: ["typescript", "javascript"],
|
|
1116
|
+
levels: ["beginner", "intermediate"]
|
|
1117
|
+
},
|
|
1118
|
+
{
|
|
1119
|
+
id: "js-export-explain",
|
|
1120
|
+
text: "What does `export` do? What is the difference between `export default` and named exports?",
|
|
1121
|
+
modelAnswer: "`export` makes a value available to other modules. Named exports (`export function foo`) let you export multiple things per file, imported with `{ foo }`. `export default` exports one main value per file, imported without braces.",
|
|
1122
|
+
difficulty: "easy",
|
|
1123
|
+
languages: ["typescript", "javascript"],
|
|
1124
|
+
levels: ["beginner"]
|
|
1125
|
+
},
|
|
1126
|
+
{
|
|
1127
|
+
id: "ts-interface-type",
|
|
1128
|
+
text: "When would you use an `interface` versus a `type` alias in TypeScript?",
|
|
1129
|
+
modelAnswer: "Both define shapes for objects. `interface` is preferred for object shapes that may be extended or implemented by classes. `type` is more flexible \u2014 it can represent unions, intersections, and primitives. In practice, either works for plain object shapes.",
|
|
1130
|
+
difficulty: "medium",
|
|
1131
|
+
languages: ["typescript"],
|
|
1132
|
+
levels: ["beginner", "intermediate"]
|
|
1133
|
+
},
|
|
1134
|
+
{
|
|
1135
|
+
id: "ts-any-vs-unknown",
|
|
1136
|
+
text: "Why is `unknown` safer than `any` in TypeScript?",
|
|
1137
|
+
modelAnswer: "`unknown` forces you to narrow the type before using it (via typeof, instanceof, or type guards), preventing accidental unsafe operations. `any` disables all type checking. Prefer `unknown` whenever the type is genuinely unknown at compile time.",
|
|
1138
|
+
difficulty: "medium",
|
|
1139
|
+
languages: ["typescript"],
|
|
1140
|
+
levels: ["intermediate", "advanced"]
|
|
1141
|
+
},
|
|
1142
|
+
{
|
|
1143
|
+
id: "js-closure",
|
|
1144
|
+
text: "What is a closure, and can you find an example in your code?",
|
|
1145
|
+
modelAnswer: "A closure is a function that captures variables from its surrounding scope even after that scope has returned. Example: a callback defined inside a function that references the outer function's variables. Closures power module patterns, event handlers, and factory functions.",
|
|
1146
|
+
difficulty: "medium",
|
|
1147
|
+
languages: ["javascript", "typescript"],
|
|
1148
|
+
levels: ["beginner", "intermediate"]
|
|
1149
|
+
},
|
|
1150
|
+
{
|
|
1151
|
+
id: "js-this-binding",
|
|
1152
|
+
text: "Explain how `this` binding works in regular functions vs. arrow functions.",
|
|
1153
|
+
modelAnswer: "In regular functions, `this` is determined at call time \u2014 it's the object before the dot, or `undefined` in strict mode. Arrow functions capture `this` lexically from their enclosing scope and never rebind it. This is why class methods often use arrow functions to avoid losing `this` context in callbacks.",
|
|
1154
|
+
difficulty: "medium",
|
|
1155
|
+
languages: ["javascript", "typescript"],
|
|
1156
|
+
levels: ["intermediate"]
|
|
1157
|
+
},
|
|
1158
|
+
{
|
|
1159
|
+
id: "js-event-loop",
|
|
1160
|
+
text: "What is the JavaScript event loop and why can't you block it?",
|
|
1161
|
+
modelAnswer: "The event loop is a single-threaded mechanism that processes one task at a time from a task queue. Blocking it (e.g., with a long synchronous loop) prevents all other code \u2014 including UI updates or incoming requests \u2014 from running. Async operations (I/O, timers) are handled off-thread and their callbacks are queued when done.",
|
|
1162
|
+
difficulty: "hard",
|
|
1163
|
+
languages: ["javascript", "typescript"],
|
|
1164
|
+
levels: ["intermediate", "advanced"]
|
|
1165
|
+
},
|
|
1166
|
+
{
|
|
1167
|
+
id: "js-prototype",
|
|
1168
|
+
text: "How does prototypal inheritance work in JavaScript? How does `class` syntax relate to it?",
|
|
1169
|
+
modelAnswer: "Every object has a `[[Prototype]]` (accessible via `__proto__`). When a property is not found on an object, JS looks up the prototype chain. `class` is syntactic sugar over prototypal inheritance \u2014 `class Foo extends Bar` sets up the prototype chain automatically.",
|
|
1170
|
+
difficulty: "hard",
|
|
1171
|
+
languages: ["javascript", "typescript"],
|
|
1172
|
+
levels: ["intermediate", "advanced"]
|
|
1173
|
+
},
|
|
1174
|
+
// ── Python ───────────────────────────────────────────────────────────────
|
|
1175
|
+
{
|
|
1176
|
+
id: "py-decorator-explain",
|
|
1177
|
+
text: "What is a Python decorator and what does it do to the function it wraps?",
|
|
1178
|
+
modelAnswer: "A decorator is a function that takes a function as input and returns a modified version. `@my_decorator` before a function is syntactic sugar for `my_func = my_decorator(my_func)`. Decorators are commonly used for logging, authentication checks, caching, and input validation.",
|
|
1179
|
+
difficulty: "medium",
|
|
1180
|
+
languages: ["python"],
|
|
1181
|
+
levels: ["intermediate", "advanced"]
|
|
1182
|
+
},
|
|
1183
|
+
{
|
|
1184
|
+
id: "py-list-comp",
|
|
1185
|
+
text: "What does this list comprehension do: `[x**2 for x in range(10) if x % 2 == 0]`?",
|
|
1186
|
+
modelAnswer: "It creates a list of squares of even numbers from 0 to 9: [0, 4, 16, 36, 64]. List comprehensions combine a for loop and optional filter into a single readable expression, and are generally faster than equivalent for loops.",
|
|
1187
|
+
difficulty: "easy",
|
|
1188
|
+
languages: ["python"],
|
|
1189
|
+
levels: ["beginner", "intermediate"]
|
|
1190
|
+
},
|
|
1191
|
+
{
|
|
1192
|
+
id: "py-dunder",
|
|
1193
|
+
text: "What are dunder (double-underscore) methods? Give two examples and explain when Python calls them.",
|
|
1194
|
+
modelAnswer: "Dunder methods (like `__init__`, `__str__`, `__len__`) define how objects respond to built-in operations. `__init__` is called when an instance is created. `__str__` is called when you use `str()` or `print()` on an object. They let you make your classes behave like built-in Python types.",
|
|
1195
|
+
difficulty: "medium",
|
|
1196
|
+
languages: ["python"],
|
|
1197
|
+
levels: ["beginner", "intermediate"]
|
|
1198
|
+
},
|
|
1199
|
+
{
|
|
1200
|
+
id: "py-generator-basics",
|
|
1201
|
+
text: "What is a generator function (`yield`)? When would you choose it over returning a list?",
|
|
1202
|
+
modelAnswer: "A generator function yields values one at a time rather than building a full list in memory. Each call to `next()` resumes execution until the next `yield`. Use generators when the full collection is large or infinite \u2014 they're memory-efficient because they produce values lazily.",
|
|
1203
|
+
difficulty: "medium",
|
|
1204
|
+
languages: ["python"],
|
|
1205
|
+
levels: ["intermediate", "advanced"]
|
|
1206
|
+
},
|
|
1207
|
+
// ── Java ─────────────────────────────────────────────────────────────────
|
|
1208
|
+
{
|
|
1209
|
+
id: "java-interface-abstract",
|
|
1210
|
+
text: "What is the difference between a Java `interface` and an `abstract class`?",
|
|
1211
|
+
modelAnswer: "An interface defines a contract of method signatures (all abstract by default, can have default/static methods since Java 8). An abstract class can have both abstract and concrete methods plus state (fields). A class can implement multiple interfaces but extend only one class. Use interfaces for capabilities (Comparable, Serializable), abstract classes for shared implementation.",
|
|
1212
|
+
difficulty: "medium",
|
|
1213
|
+
languages: ["java"],
|
|
1214
|
+
levels: ["beginner", "intermediate"]
|
|
1215
|
+
},
|
|
1216
|
+
{
|
|
1217
|
+
id: "java-generics",
|
|
1218
|
+
text: "What do generics (`<T>`) provide in Java?",
|
|
1219
|
+
modelAnswer: "Generics provide compile-time type safety for collections and methods. Instead of `List` (which accepts `Object` and requires casting), `List<String>` only allows Strings. The type parameter `<T>` is erased at runtime (type erasure), but the compiler enforces correctness.",
|
|
1220
|
+
difficulty: "medium",
|
|
1221
|
+
languages: ["java"],
|
|
1222
|
+
levels: ["intermediate"]
|
|
1223
|
+
}
|
|
1224
|
+
];
|
|
1225
|
+
var PATTERN_QUESTIONS = {
|
|
1226
|
+
"js-currying": {
|
|
1227
|
+
text: "Explain the currying pattern used in your code. What problem does it solve here?",
|
|
1228
|
+
modelAnswer: "Currying transforms a function that takes multiple arguments into a chain of functions each taking one argument. It enables partial application (pre-filling some arguments) and makes code more composable. However, it reduces readability for beginners and should only be used when the functional pattern genuinely clarifies intent.",
|
|
1229
|
+
difficulty: "hard"
|
|
1230
|
+
},
|
|
1231
|
+
"js-generator": {
|
|
1232
|
+
text: "You used a generator function (`function*`). Walk me through what happens step-by-step when code calls `.next()` on it.",
|
|
1233
|
+
modelAnswer: "Calling the generator returns an iterator without executing any code. Each `.next()` call runs until the next `yield` expression, pauses, and returns `{ value: yieldedValue, done: false }`. After the function returns, `done` becomes `true`. This lazy execution is why generators suit streaming or infinite sequences.",
|
|
1234
|
+
difficulty: "hard"
|
|
1235
|
+
},
|
|
1236
|
+
"js-proxy": {
|
|
1237
|
+
text: "Explain what `new Proxy(target, handler)` does. Why is it typically not found in student code?",
|
|
1238
|
+
modelAnswer: "A Proxy wraps an object and intercepts fundamental operations (get, set, has, etc.) via handler traps. It's used in frameworks for reactivity, validation, and ORM magic. In student code it's rare because it's a metaprogramming tool \u2014 before using it you must fully understand the operations it intercepts and its performance implications.",
|
|
1239
|
+
difficulty: "hard"
|
|
1240
|
+
},
|
|
1241
|
+
"py-metaclass": {
|
|
1242
|
+
text: "You used a metaclass. Explain what it does and why this was the right choice over a simpler alternative.",
|
|
1243
|
+
modelAnswer: 'A metaclass is "the class of a class" \u2014 it controls how classes are created. Common uses: registering subclasses, validating class definitions, adding methods automatically (like ORMs do). Alternatives like `__init_subclass__` or decorators usually achieve the same goal with less complexity. If this is in student code, the professor will ask you to justify why a metaclass was necessary.',
|
|
1244
|
+
difficulty: "hard"
|
|
1245
|
+
},
|
|
1246
|
+
"java-reflection": {
|
|
1247
|
+
text: "You used Java Reflection. Explain what it does, what the risks are, and why a direct approach wouldn't work here.",
|
|
1248
|
+
modelAnswer: "Reflection lets you inspect and call methods/fields at runtime by name, bypassing compile-time type checks. Risks: it's slow, breaks type safety, can bypass access modifiers, and makes code hard to follow. It's appropriate in frameworks (dependency injection, serialization) where the type isn't known at compile time. In most student use cases, a direct method call or interface would be simpler and safer.",
|
|
1249
|
+
difficulty: "hard"
|
|
1250
|
+
}
|
|
1251
|
+
};
|
|
1252
|
+
function buildQuestionSet(project, map, report, maxQuestions = 8) {
|
|
1253
|
+
const level = project.studentLevel ?? "intermediate";
|
|
1254
|
+
const lang = project.language;
|
|
1255
|
+
const questions = [];
|
|
1256
|
+
if (report) {
|
|
1257
|
+
for (const match of report.matches) {
|
|
1258
|
+
const pq = PATTERN_QUESTIONS[match.patternId];
|
|
1259
|
+
if (pq && questions.length < 4) {
|
|
1260
|
+
questions.push(pq);
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
const relevant = STATIC_QUESTIONS.filter(
|
|
1265
|
+
(q) => q.languages.includes(lang) && q.levels.includes(level)
|
|
1266
|
+
);
|
|
1267
|
+
const shuffled = relevant.sort(() => Math.random() - 0.5);
|
|
1268
|
+
for (const q of shuffled) {
|
|
1269
|
+
if (questions.length >= maxQuestions) break;
|
|
1270
|
+
questions.push({ text: q.text, modelAnswer: q.modelAnswer, difficulty: q.difficulty });
|
|
1271
|
+
}
|
|
1272
|
+
const allFunctions = map.files.flatMap((f) => f.functions.map((fn) => ({ file: f.path, fn })));
|
|
1273
|
+
const publicFns = allFunctions.filter((x) => x.fn.isExported).slice(0, 3);
|
|
1274
|
+
for (const { file, fn } of publicFns) {
|
|
1275
|
+
if (questions.length >= maxQuestions) break;
|
|
1276
|
+
questions.push({
|
|
1277
|
+
text: `Explain what \`${fn.name}()\` in \`${file}\` does and why it's exported.`,
|
|
1278
|
+
modelAnswer: `[Code-specific \u2014 answer based on your implementation of ${fn.name}]`,
|
|
1279
|
+
difficulty: "easy"
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
return questions.slice(0, maxQuestions);
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
// src/defense/defense.ts
|
|
1286
|
+
var MockDefense = class {
|
|
1287
|
+
constructor(workingDir) {
|
|
1288
|
+
this.workingDir = workingDir;
|
|
1289
|
+
}
|
|
1290
|
+
workingDir;
|
|
1291
|
+
selectQuestions(project, map, report, count = 8) {
|
|
1292
|
+
return buildQuestionSet(project, map, report, count);
|
|
1293
|
+
}
|
|
1294
|
+
async saveSession(session) {
|
|
1295
|
+
const lines = [
|
|
1296
|
+
"# Defense Prep Report",
|
|
1297
|
+
"",
|
|
1298
|
+
`**Project:** ${session.projectName}`,
|
|
1299
|
+
`**Language:** ${session.language} | **Level:** ${session.studentLevel}`,
|
|
1300
|
+
`**Completed:** ${session.completedAt}`,
|
|
1301
|
+
`**Average Confidence:** ${session.averageConfidence.toFixed(1)}/5`,
|
|
1302
|
+
"",
|
|
1303
|
+
"---",
|
|
1304
|
+
""
|
|
1305
|
+
];
|
|
1306
|
+
for (let i = 0; i < session.questions.length; i++) {
|
|
1307
|
+
const q = session.questions[i];
|
|
1308
|
+
const stars = "\u2605".repeat(q.confidence) + "\u2606".repeat(5 - q.confidence);
|
|
1309
|
+
lines.push(
|
|
1310
|
+
`## Q${i + 1} [${stars}]`,
|
|
1311
|
+
"",
|
|
1312
|
+
`**Question:** ${q.question}`,
|
|
1313
|
+
"",
|
|
1314
|
+
"**Your answer:**",
|
|
1315
|
+
q.answer || "*(no answer provided)*",
|
|
1316
|
+
"",
|
|
1317
|
+
"**Model answer:**",
|
|
1318
|
+
q.modelAnswer,
|
|
1319
|
+
"",
|
|
1320
|
+
"---",
|
|
1321
|
+
""
|
|
1322
|
+
);
|
|
1323
|
+
}
|
|
1324
|
+
const lowConfidence = session.questions.filter((q) => q.confidence <= 2);
|
|
1325
|
+
if (lowConfidence.length > 0) {
|
|
1326
|
+
lines.push(
|
|
1327
|
+
"## Areas to Review",
|
|
1328
|
+
"",
|
|
1329
|
+
...lowConfidence.map((q, i) => `${i + 1}. ${q.question}`),
|
|
1330
|
+
""
|
|
1331
|
+
);
|
|
1332
|
+
}
|
|
1333
|
+
await fs5.writeFile(
|
|
1334
|
+
path5.join(this.workingDir, "DEFENSE_PREP.md"),
|
|
1335
|
+
lines.join("\n"),
|
|
1336
|
+
"utf-8"
|
|
1337
|
+
);
|
|
1338
|
+
}
|
|
1339
|
+
};
|
|
1340
|
+
|
|
1341
|
+
// src/skills/registry.ts
|
|
1342
|
+
import fs6 from "fs/promises";
|
|
1343
|
+
import path6 from "path";
|
|
1344
|
+
import Handlebars2 from "handlebars";
|
|
1345
|
+
|
|
1346
|
+
// src/skills/built-in.ts
|
|
1347
|
+
var BUILT_IN_SKILLS = [
|
|
1348
|
+
// ── Backend ──────────────────────────────────────────────────────────────────
|
|
1349
|
+
{
|
|
1350
|
+
id: "backend/crud-service",
|
|
1351
|
+
name: "CRUD Service",
|
|
1352
|
+
category: "backend",
|
|
1353
|
+
description: "Generate a complete CRUD service for an entity",
|
|
1354
|
+
template: `Generate a complete CRUD service for "{{entity}}" in {{project.language}}{{#if project.framework}} using {{project.framework}}{{/if}}.
|
|
1355
|
+
|
|
1356
|
+
Entity fields: {{fields}}
|
|
1357
|
+
|
|
1358
|
+
Include:
|
|
1359
|
+
- createOne: validate inputs, insert, return created entity
|
|
1360
|
+
- findById: return entity or throw NotFoundError
|
|
1361
|
+
- findAll: list with pagination (page, limit) and optional filter by {{filterField}}
|
|
1362
|
+
- updateOne: partial update, validate changed fields, return updated entity
|
|
1363
|
+
- deleteOne: {{#if softDelete}}soft delete (set deletedAt timestamp){{else}}hard delete{{/if}}
|
|
1364
|
+
|
|
1365
|
+
Requirements:
|
|
1366
|
+
- Full TypeScript types for the entity, CreateDTO, UpdateDTO, and responses
|
|
1367
|
+
- Input validation at the service boundary
|
|
1368
|
+
- Consistent error types (NotFoundError, ValidationError)
|
|
1369
|
+
- No direct database calls \u2014 use a repository/data-access abstraction
|
|
1370
|
+
{{#if (eq project.type "student")}}- Add JSDoc comments explaining each method{{/if}}`,
|
|
1371
|
+
contextSchema: [
|
|
1372
|
+
{ key: "entity", label: "Entity name", type: "text", placeholder: "User" },
|
|
1373
|
+
{ key: "fields", label: "Entity fields", type: "text", placeholder: "id: uuid, email: string, name: string, createdAt: date" },
|
|
1374
|
+
{ key: "filterField", label: "Main filter field", type: "text", placeholder: "status", default: "status" },
|
|
1375
|
+
{ key: "softDelete", label: "Use soft delete?", type: "boolean", default: true }
|
|
1376
|
+
],
|
|
1377
|
+
tags: ["crud", "service", "api", "backend"],
|
|
1378
|
+
version: "1.0.0"
|
|
1379
|
+
},
|
|
1380
|
+
{
|
|
1381
|
+
id: "backend/api-endpoint",
|
|
1382
|
+
name: "REST API Endpoint",
|
|
1383
|
+
category: "backend",
|
|
1384
|
+
description: "Design a single REST API endpoint with full validation and error handling",
|
|
1385
|
+
template: `Design a {{method}} {{route}} endpoint in {{project.language}}{{#if project.framework}} / {{project.framework}}{{/if}}.
|
|
1386
|
+
|
|
1387
|
+
Purpose: {{purpose}}
|
|
1388
|
+
|
|
1389
|
+
Implement:
|
|
1390
|
+
1. Route handler with input validation (body/params/query)
|
|
1391
|
+
2. Business logic delegation to a service layer
|
|
1392
|
+
3. Success response shape: {{successShape}}
|
|
1393
|
+
4. Error handling: 400 (validation), 401 (auth), 404 (not found), 500 (server)
|
|
1394
|
+
5. Request/response TypeScript types
|
|
1395
|
+
6. Middleware chain: auth \u2192 validate \u2192 handle \u2192 respond
|
|
1396
|
+
|
|
1397
|
+
Include a brief usage example (curl or fetch).`,
|
|
1398
|
+
contextSchema: [
|
|
1399
|
+
{ key: "method", label: "HTTP method", type: "select", options: ["GET", "POST", "PUT", "PATCH", "DELETE"], default: "POST" },
|
|
1400
|
+
{ key: "route", label: "Route path", type: "text", placeholder: "/api/users/:id" },
|
|
1401
|
+
{ key: "purpose", label: "What does it do?", type: "text", placeholder: "Update a user's profile" },
|
|
1402
|
+
{ key: "successShape", label: "Success response shape", type: "text", placeholder: "{ user: User, updatedAt: string }" }
|
|
1403
|
+
],
|
|
1404
|
+
tags: ["api", "rest", "endpoint", "backend"],
|
|
1405
|
+
version: "1.0.0"
|
|
1406
|
+
},
|
|
1407
|
+
{
|
|
1408
|
+
id: "backend/auth-middleware",
|
|
1409
|
+
name: "Auth Middleware",
|
|
1410
|
+
category: "backend",
|
|
1411
|
+
description: "Implement authentication/authorization middleware",
|
|
1412
|
+
template: `Implement {{authType}} authentication middleware in {{project.language}}{{#if project.framework}} / {{project.framework}}{{/if}}.
|
|
1413
|
+
|
|
1414
|
+
Requirements:
|
|
1415
|
+
- Extract token from Authorization header (Bearer scheme)
|
|
1416
|
+
- Validate the token (signature, expiry, claims)
|
|
1417
|
+
- Attach decoded user to request context
|
|
1418
|
+
- Return 401 with clear message on invalid/missing token
|
|
1419
|
+
- Return 403 if user lacks required role: {{requiredRole}}
|
|
1420
|
+
- Never leak token internals in error responses
|
|
1421
|
+
|
|
1422
|
+
Also implement:
|
|
1423
|
+
- A login endpoint that issues the token
|
|
1424
|
+
- A token refresh endpoint{{#if rememberMe}} with remember-me (long-lived refresh tokens){{/if}}
|
|
1425
|
+
- Logout (token invalidation/blacklist)
|
|
1426
|
+
|
|
1427
|
+
Security requirements:
|
|
1428
|
+
- Store secrets in environment variables, never hardcode
|
|
1429
|
+
- Use a well-tested crypto library, not custom crypto
|
|
1430
|
+
- Set short expiry on access tokens (15 min recommended)`,
|
|
1431
|
+
contextSchema: [
|
|
1432
|
+
{ key: "authType", label: "Auth type", type: "select", options: ["JWT", "Session", "API Key", "OAuth2"], default: "JWT" },
|
|
1433
|
+
{ key: "requiredRole", label: "Required role (optional)", type: "text", placeholder: "admin", default: "any authenticated user" },
|
|
1434
|
+
{ key: "rememberMe", label: "Include remember-me?", type: "boolean", default: false }
|
|
1435
|
+
],
|
|
1436
|
+
tags: ["auth", "jwt", "security", "middleware"],
|
|
1437
|
+
version: "1.0.0"
|
|
1438
|
+
},
|
|
1439
|
+
{
|
|
1440
|
+
id: "backend/error-handler",
|
|
1441
|
+
name: "Error Handler",
|
|
1442
|
+
category: "backend",
|
|
1443
|
+
description: "Centralized error handling with typed error classes",
|
|
1444
|
+
template: `Implement centralized error handling in {{project.language}}{{#if project.framework}} / {{project.framework}}{{/if}}.
|
|
1445
|
+
|
|
1446
|
+
Create a typed error hierarchy:
|
|
1447
|
+
- AppError (base): message, statusCode, isOperational
|
|
1448
|
+
- ValidationError (400): field errors map
|
|
1449
|
+
- AuthenticationError (401)
|
|
1450
|
+
- AuthorizationError (403)
|
|
1451
|
+
- NotFoundError (404): resource name
|
|
1452
|
+
- ConflictError (409): duplicate resource
|
|
1453
|
+
- RateLimitError (429)
|
|
1454
|
+
- InternalError (500): hides internal details from clients
|
|
1455
|
+
|
|
1456
|
+
Global error middleware/handler that:
|
|
1457
|
+
- Catches all thrown errors
|
|
1458
|
+
- Logs operational errors at WARN, unexpected errors at ERROR
|
|
1459
|
+
- Returns JSON: { error: { code, message, {{#if includeDetails}}details{{/if}} } }
|
|
1460
|
+
- Never exposes stack traces or internal messages to clients
|
|
1461
|
+
- Handles async errors (unhandledRejection, uncaughtException)`,
|
|
1462
|
+
contextSchema: [
|
|
1463
|
+
{ key: "includeDetails", label: "Include error details in dev mode?", type: "boolean", default: true }
|
|
1464
|
+
],
|
|
1465
|
+
tags: ["error-handling", "middleware", "backend"],
|
|
1466
|
+
version: "1.0.0"
|
|
1467
|
+
},
|
|
1468
|
+
{
|
|
1469
|
+
id: "backend/validation-schema",
|
|
1470
|
+
name: "Validation Schema",
|
|
1471
|
+
category: "backend",
|
|
1472
|
+
description: "Create input validation schemas with a validation library",
|
|
1473
|
+
template: `Create validation schemas for {{entity}} in {{project.language}} using {{library}}.
|
|
1474
|
+
|
|
1475
|
+
Fields to validate:
|
|
1476
|
+
{{fields}}
|
|
1477
|
+
|
|
1478
|
+
For each field define:
|
|
1479
|
+
- Type (string, number, boolean, array, object, date)
|
|
1480
|
+
- Required vs optional
|
|
1481
|
+
- Constraints (min/max length, regex pattern, enum values, range)
|
|
1482
|
+
- Custom error messages in plain English
|
|
1483
|
+
|
|
1484
|
+
Also provide:
|
|
1485
|
+
- CreateSchema (all required fields)
|
|
1486
|
+
- UpdateSchema (all fields optional for PATCH)
|
|
1487
|
+
- QuerySchema (pagination + filters)
|
|
1488
|
+
- A validation utility function that returns typed errors`,
|
|
1489
|
+
contextSchema: [
|
|
1490
|
+
{ key: "entity", label: "Entity name", type: "text", placeholder: "User" },
|
|
1491
|
+
{ key: "fields", label: "Fields to validate", type: "text", placeholder: "email: string email, age: number 18-120, role: admin|user|viewer" },
|
|
1492
|
+
{ key: "library", label: "Validation library", type: "select", options: ["Zod", "Joi", "Yup", "class-validator", "Pydantic", "built-in"], default: "Zod" }
|
|
1493
|
+
],
|
|
1494
|
+
tags: ["validation", "schema", "backend"],
|
|
1495
|
+
version: "1.0.0"
|
|
1496
|
+
},
|
|
1497
|
+
{
|
|
1498
|
+
id: "backend/rate-limiter",
|
|
1499
|
+
name: "Rate Limiter",
|
|
1500
|
+
category: "backend",
|
|
1501
|
+
description: "Implement request rate limiting middleware",
|
|
1502
|
+
template: `Implement rate limiting middleware for a {{project.language}}{{#if project.framework}} / {{project.framework}}{{/if}} API.
|
|
1503
|
+
|
|
1504
|
+
Strategy: {{strategy}}
|
|
1505
|
+
Limits: {{limits}}
|
|
1506
|
+
|
|
1507
|
+
Requirements:
|
|
1508
|
+
- Track requests per {{window}} window per {{keyBy}}
|
|
1509
|
+
- Return 429 Too Many Requests with Retry-After header when exceeded
|
|
1510
|
+
- Include rate limit headers on every response: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset
|
|
1511
|
+
- Different limits for: authenticated users vs anonymous, admin vs regular endpoints
|
|
1512
|
+
- Storage backend: {{storage}} (must survive process restart in production)
|
|
1513
|
+
- Graceful degradation: if the rate limit store is unavailable, fail open (don't block requests) and log a warning
|
|
1514
|
+
|
|
1515
|
+
Provide the middleware, a config object, and an example of applying different limits to different route groups.`,
|
|
1516
|
+
contextSchema: [
|
|
1517
|
+
{ key: "strategy", label: "Rate limit strategy", type: "select", options: ["Fixed window", "Sliding window", "Token bucket", "Leaky bucket"], default: "Sliding window" },
|
|
1518
|
+
{ key: "limits", label: "Limits (e.g. 100 req/min per user)", type: "text", placeholder: "100 req/min per user, 1000 req/hour per IP" },
|
|
1519
|
+
{ key: "window", label: "Time window", type: "select", options: ["1 minute", "5 minutes", "1 hour", "1 day"], default: "1 minute" },
|
|
1520
|
+
{ key: "keyBy", label: "Key by", type: "select", options: ["IP address", "user ID", "API key", "IP + user ID"], default: "user ID" },
|
|
1521
|
+
{ key: "storage", label: "Storage backend", type: "select", options: ["Redis", "in-memory (dev only)", "database"], default: "Redis" }
|
|
1522
|
+
],
|
|
1523
|
+
tags: ["rate-limiting", "middleware", "backend", "security"],
|
|
1524
|
+
version: "1.0.0"
|
|
1525
|
+
},
|
|
1526
|
+
{
|
|
1527
|
+
id: "backend/caching",
|
|
1528
|
+
name: "Caching Layer",
|
|
1529
|
+
category: "backend",
|
|
1530
|
+
description: "Add a caching strategy to a service or endpoint",
|
|
1531
|
+
template: `Design a caching strategy for {{target}} in {{project.language}}{{#if project.framework}} / {{project.framework}}{{/if}}.
|
|
1532
|
+
|
|
1533
|
+
What to cache: {{whatToCache}}
|
|
1534
|
+
Cache backend: {{cacheBackend}}
|
|
1535
|
+
TTL: {{ttl}}
|
|
1536
|
+
|
|
1537
|
+
Implement:
|
|
1538
|
+
1. Cache-aside pattern: check cache \u2192 on miss, fetch from source \u2192 store in cache \u2192 return
|
|
1539
|
+
2. Cache key design: deterministic, namespaced, versioned (e.g. \`{{project.name}}:v1:users:{id}\`)
|
|
1540
|
+
3. Cache invalidation: {{invalidationStrategy}}
|
|
1541
|
+
4. Stampede protection: single-flight / mutex for concurrent misses on the same key
|
|
1542
|
+
5. Cache warming: pre-populate on startup for {{criticalKeys}}
|
|
1543
|
+
|
|
1544
|
+
Edge cases to handle:
|
|
1545
|
+
- Null/empty result caching (negative cache) to prevent DB hammering
|
|
1546
|
+
- Cache unavailability: fall through to source without crashing
|
|
1547
|
+
- Stale-while-revalidate for non-critical data
|
|
1548
|
+
|
|
1549
|
+
Provide helper functions for get-or-set and invalidate patterns.`,
|
|
1550
|
+
contextSchema: [
|
|
1551
|
+
{ key: "target", label: "What to cache", type: "text", placeholder: "User profile endpoint GET /users/:id" },
|
|
1552
|
+
{ key: "whatToCache", label: "Data being cached", type: "text", placeholder: "User objects after DB lookup" },
|
|
1553
|
+
{ key: "cacheBackend", label: "Cache backend", type: "select", options: ["Redis", "Memcached", "in-memory (Map)", "CDN edge cache"], default: "Redis" },
|
|
1554
|
+
{ key: "ttl", label: "Time-to-live", type: "text", default: "5 minutes" },
|
|
1555
|
+
{ key: "invalidationStrategy", label: "Invalidation strategy", type: "select", options: ["TTL expiry only", "Event-based (on write)", "Manual + TTL", "Cache tags"], default: "Event-based (on write)" },
|
|
1556
|
+
{ key: "criticalKeys", label: "Keys to warm on startup", type: "text", placeholder: "top 100 users, app config" }
|
|
1557
|
+
],
|
|
1558
|
+
tags: ["caching", "redis", "performance", "backend"],
|
|
1559
|
+
version: "1.0.0"
|
|
1560
|
+
},
|
|
1561
|
+
{
|
|
1562
|
+
id: "backend/file-upload",
|
|
1563
|
+
name: "File Upload Handler",
|
|
1564
|
+
category: "backend",
|
|
1565
|
+
description: "Implement secure multipart file upload with storage and validation",
|
|
1566
|
+
template: `Implement a file upload handler for {{project.language}}{{#if project.framework}} / {{project.framework}}{{/if}}.
|
|
1567
|
+
|
|
1568
|
+
Upload type: {{uploadType}}
|
|
1569
|
+
Storage: {{storage}}
|
|
1570
|
+
Allowed types: {{allowedTypes}}
|
|
1571
|
+
Max size: {{maxSize}}
|
|
1572
|
+
|
|
1573
|
+
Requirements:
|
|
1574
|
+
- Validate file type by MIME type AND magic bytes (not just extension)
|
|
1575
|
+
- Enforce max file size: {{maxSize}} \u2014 reject before fully reading the stream
|
|
1576
|
+
- Sanitize filenames: strip path traversal characters, generate a UUID-based storage name
|
|
1577
|
+
- Virus scanning hook (placeholder if no scanner available)
|
|
1578
|
+
- Progress: emit upload progress events if using streaming
|
|
1579
|
+
- Store: {{storage}} with a public URL returned to the client
|
|
1580
|
+
- Multiple files: support up to {{maxFiles}} files per request
|
|
1581
|
+
|
|
1582
|
+
Security checklist:
|
|
1583
|
+
- Never serve uploaded files from the same origin as the app (use a separate domain or CDN)
|
|
1584
|
+
- Set Content-Disposition: attachment for downloads
|
|
1585
|
+
- Strip EXIF metadata from images
|
|
1586
|
+
|
|
1587
|
+
Return: { url, filename, size, mimeType, uploadedAt }`,
|
|
1588
|
+
contextSchema: [
|
|
1589
|
+
{ key: "uploadType", label: "Upload type", type: "select", options: ["images only", "documents (PDF/DOC)", "any file", "videos"], default: "images only" },
|
|
1590
|
+
{ key: "storage", label: "Storage destination", type: "select", options: ["AWS S3", "Cloudflare R2", "local disk (dev)", "Supabase Storage"], default: "AWS S3" },
|
|
1591
|
+
{ key: "allowedTypes", label: "Allowed MIME types", type: "text", default: "image/jpeg, image/png, image/webp" },
|
|
1592
|
+
{ key: "maxSize", label: "Max file size", type: "text", default: "5 MB" },
|
|
1593
|
+
{ key: "maxFiles", label: "Max files per request", type: "text", default: "5" }
|
|
1594
|
+
],
|
|
1595
|
+
tags: ["file-upload", "storage", "backend", "security"],
|
|
1596
|
+
version: "1.0.0"
|
|
1597
|
+
},
|
|
1598
|
+
{
|
|
1599
|
+
id: "backend/event-bus",
|
|
1600
|
+
name: "Event Bus",
|
|
1601
|
+
category: "backend",
|
|
1602
|
+
description: "Implement an in-process or distributed event bus",
|
|
1603
|
+
template: `Implement an event bus for {{project.language}}{{#if project.framework}} / {{project.framework}}{{/if}}.
|
|
1604
|
+
|
|
1605
|
+
Events to support: {{events}}
|
|
1606
|
+
Bus type: {{busType}}
|
|
1607
|
+
|
|
1608
|
+
Design:
|
|
1609
|
+
1. Typed event definitions \u2014 each event has a name and a strict payload type
|
|
1610
|
+
2. Publisher: emit(eventName, payload) \u2014 fire and forget, never blocks the caller
|
|
1611
|
+
3. Subscriber: on(eventName, handler) \u2014 registers a handler, returns an unsubscribe function
|
|
1612
|
+
4. Error isolation: a handler throwing must not prevent other handlers from running
|
|
1613
|
+
5. Async handlers: all handlers run asynchronously, errors are caught and logged
|
|
1614
|
+
|
|
1615
|
+
{{#if (eq busType "distributed")}}
|
|
1616
|
+
For distributed (message queue):
|
|
1617
|
+
- Use {{broker}} as the broker
|
|
1618
|
+
- Persistent messages: events survive process restarts
|
|
1619
|
+
- At-least-once delivery: handlers must be idempotent
|
|
1620
|
+
- Dead letter queue for failed messages after 3 retries
|
|
1621
|
+
{{/if}}
|
|
1622
|
+
|
|
1623
|
+
Provide: EventBus class/module, event type definitions, and an example of wiring publisher and subscriber in the app bootstrap.`,
|
|
1624
|
+
contextSchema: [
|
|
1625
|
+
{ key: "events", label: "Events (e.g. user.created, order.paid)", type: "text", placeholder: "user.registered, payment.succeeded, order.shipped" },
|
|
1626
|
+
{ key: "busType", label: "Bus type", type: "select", options: ["in-process", "distributed"], default: "in-process" },
|
|
1627
|
+
{ key: "broker", label: "Message broker (if distributed)", type: "select", options: ["Redis Pub/Sub", "RabbitMQ", "Kafka", "BullMQ"], default: "BullMQ" }
|
|
1628
|
+
],
|
|
1629
|
+
tags: ["event-bus", "events", "architecture", "backend"],
|
|
1630
|
+
version: "1.0.0"
|
|
1631
|
+
},
|
|
1632
|
+
{
|
|
1633
|
+
id: "backend/websocket",
|
|
1634
|
+
name: "WebSocket Server",
|
|
1635
|
+
category: "backend",
|
|
1636
|
+
description: "Implement a real-time WebSocket server with room/channel support",
|
|
1637
|
+
template: `Implement a WebSocket server in {{project.language}}{{#if project.framework}} / {{project.framework}}{{/if}}.
|
|
1638
|
+
|
|
1639
|
+
Use case: {{useCase}}
|
|
1640
|
+
Events: {{events}}
|
|
1641
|
+
|
|
1642
|
+
Requirements:
|
|
1643
|
+
- Connection lifecycle: connect \u2192 authenticate \u2192 join rooms \u2192 message \u2192 disconnect
|
|
1644
|
+
- Authentication: verify JWT on the initial handshake (reject unauthenticated connections)
|
|
1645
|
+
- Rooms/channels: clients can join/leave named rooms; broadcast to room members only
|
|
1646
|
+
- Message schema: { type, payload, roomId?, timestamp }
|
|
1647
|
+
- Heartbeat: ping/pong every 30s to detect dead connections; clean up on timeout
|
|
1648
|
+
- Reconnection: emit a "missed events" catch-up on reconnect using a cursor/sequence number
|
|
1649
|
+
- Backpressure: don't queue unlimited messages for slow clients \u2014 drop or pause after {{bufferSize}} queued messages
|
|
1650
|
+
|
|
1651
|
+
Client events to handle: {{events}}
|
|
1652
|
+
Server-to-client events to emit: {{serverEvents}}
|
|
1653
|
+
|
|
1654
|
+
Provide the server setup, a typed message protocol, and a minimal client-side connection handler.`,
|
|
1655
|
+
contextSchema: [
|
|
1656
|
+
{ key: "useCase", label: "Use case", type: "text", placeholder: "Real-time collaborative document editing" },
|
|
1657
|
+
{ key: "events", label: "Client events", type: "text", placeholder: "join-room, leave-room, send-message, typing" },
|
|
1658
|
+
{ key: "serverEvents", label: "Server events", type: "text", placeholder: "message, user-joined, user-left, error" },
|
|
1659
|
+
{ key: "bufferSize", label: "Max buffer size per client", type: "text", default: "100 messages" }
|
|
1660
|
+
],
|
|
1661
|
+
tags: ["websocket", "realtime", "backend"],
|
|
1662
|
+
version: "1.0.0"
|
|
1663
|
+
},
|
|
1664
|
+
// ── Frontend ─────────────────────────────────────────────────────────────────
|
|
1665
|
+
{
|
|
1666
|
+
id: "frontend/react-component",
|
|
1667
|
+
name: "React Component",
|
|
1668
|
+
category: "frontend",
|
|
1669
|
+
description: "Scaffold a typed, accessible React component",
|
|
1670
|
+
template: `Create a React component called {{componentName}} in TypeScript.
|
|
1671
|
+
|
|
1672
|
+
Purpose: {{purpose}}
|
|
1673
|
+
|
|
1674
|
+
Props interface:
|
|
1675
|
+
{{props}}
|
|
1676
|
+
|
|
1677
|
+
Requirements:
|
|
1678
|
+
- Full TypeScript prop types with JSDoc on each prop
|
|
1679
|
+
- Accessibility: semantic HTML, ARIA attributes where needed, keyboard navigation
|
|
1680
|
+
- Loading and error states if the component fetches data
|
|
1681
|
+
- Memoization (React.memo, useMemo, useCallback) where it genuinely helps
|
|
1682
|
+
- Export: named export + default export
|
|
1683
|
+
{{#if includeStory}}- Storybook story with default, loading, and error variants{{/if}}
|
|
1684
|
+
{{#if includeTest}}- Jest + Testing Library unit tests for key interactions{{/if}}
|
|
1685
|
+
|
|
1686
|
+
Use the project's existing patterns \u2014 check for a component library or design system before creating primitives from scratch.`,
|
|
1687
|
+
contextSchema: [
|
|
1688
|
+
{ key: "componentName", label: "Component name", type: "text", placeholder: "UserProfileCard" },
|
|
1689
|
+
{ key: "purpose", label: "What does it do?", type: "text", placeholder: "Display a user's avatar, name, and role" },
|
|
1690
|
+
{ key: "props", label: "Props (one per line)", type: "text", placeholder: 'userId: string\nonClick?: () => void\nsize?: "sm" | "md" | "lg"' },
|
|
1691
|
+
{ key: "includeStory", label: "Include Storybook story?", type: "boolean", default: false },
|
|
1692
|
+
{ key: "includeTest", label: "Include unit tests?", type: "boolean", default: true }
|
|
1693
|
+
],
|
|
1694
|
+
tags: ["react", "component", "frontend", "typescript"],
|
|
1695
|
+
version: "1.0.0"
|
|
1696
|
+
},
|
|
1697
|
+
{
|
|
1698
|
+
id: "frontend/form-handler",
|
|
1699
|
+
name: "Form Handler",
|
|
1700
|
+
category: "frontend",
|
|
1701
|
+
description: "Implement a form with validation, submission, and error display",
|
|
1702
|
+
template: `Implement a {{formName}} form in {{project.language}}{{#if project.framework}} / {{project.framework}}{{/if}}.
|
|
1703
|
+
|
|
1704
|
+
Fields:
|
|
1705
|
+
{{fields}}
|
|
1706
|
+
|
|
1707
|
+
Implement:
|
|
1708
|
+
- Controlled inputs with real-time validation
|
|
1709
|
+
- Field-level error messages shown after blur
|
|
1710
|
+
- Form-level error on submission failure (API error)
|
|
1711
|
+
- Submit button disabled while submitting
|
|
1712
|
+
- Success feedback after submission
|
|
1713
|
+
- Reset form on success
|
|
1714
|
+
|
|
1715
|
+
Validation rules: {{validationRules}}
|
|
1716
|
+
On submit: call {{submitAction}}
|
|
1717
|
+
|
|
1718
|
+
Use {{formLibrary}} for form state management if it's already in the project; otherwise use React useState.`,
|
|
1719
|
+
contextSchema: [
|
|
1720
|
+
{ key: "formName", label: "Form name", type: "text", placeholder: "LoginForm" },
|
|
1721
|
+
{ key: "fields", label: "Form fields", type: "text", placeholder: "email: email, password: password (min 8 chars)" },
|
|
1722
|
+
{ key: "validationRules", label: "Validation rules", type: "text", placeholder: "email required valid format, password min 8 chars" },
|
|
1723
|
+
{ key: "submitAction", label: "Submit action", type: "text", placeholder: "authService.login(data)" },
|
|
1724
|
+
{ key: "formLibrary", label: "Form library", type: "select", options: ["React Hook Form", "Formik", "plain React state"], default: "React Hook Form" }
|
|
1725
|
+
],
|
|
1726
|
+
tags: ["form", "validation", "frontend", "react"],
|
|
1727
|
+
version: "1.0.0"
|
|
1728
|
+
},
|
|
1729
|
+
{
|
|
1730
|
+
id: "frontend/api-client",
|
|
1731
|
+
name: "API Client",
|
|
1732
|
+
category: "frontend",
|
|
1733
|
+
description: "Build a typed HTTP client service for a REST API",
|
|
1734
|
+
template: `Create a typed API client service in {{project.language}} for the {{apiName}} API.
|
|
1735
|
+
|
|
1736
|
+
Base URL: {{baseUrl}}
|
|
1737
|
+
|
|
1738
|
+
Endpoints to support:
|
|
1739
|
+
{{endpoints}}
|
|
1740
|
+
|
|
1741
|
+
Requirements:
|
|
1742
|
+
- Single axios/fetch instance with base config
|
|
1743
|
+
- Automatic auth token injection from storage (localStorage or cookie)
|
|
1744
|
+
- Request/response interceptors: add auth header, handle 401 (auto-refresh or redirect to login)
|
|
1745
|
+
- Response typing: each endpoint returns a typed Promise
|
|
1746
|
+
- Error normalization: network errors and API errors both return an ApiError type
|
|
1747
|
+
- Timeout: {{timeout}}ms default
|
|
1748
|
+
- Retry: {{retries}} retries on network failure (not on 4xx)
|
|
1749
|
+
|
|
1750
|
+
Export individual functions, not a class.`,
|
|
1751
|
+
contextSchema: [
|
|
1752
|
+
{ key: "apiName", label: "API name", type: "text", placeholder: "User API" },
|
|
1753
|
+
{ key: "baseUrl", label: "Base URL", type: "text", placeholder: "/api/v1" },
|
|
1754
|
+
{ key: "endpoints", label: "Endpoints (one per line)", type: "text", placeholder: "GET /users, POST /users, GET /users/:id, PUT /users/:id" },
|
|
1755
|
+
{ key: "timeout", label: "Timeout (ms)", type: "text", default: "10000" },
|
|
1756
|
+
{ key: "retries", label: "Retry count", type: "text", default: "3" }
|
|
1757
|
+
],
|
|
1758
|
+
tags: ["api", "http", "client", "frontend"],
|
|
1759
|
+
version: "1.0.0"
|
|
1760
|
+
},
|
|
1761
|
+
{
|
|
1762
|
+
id: "frontend/responsive-layout",
|
|
1763
|
+
name: "Responsive Layout",
|
|
1764
|
+
category: "frontend",
|
|
1765
|
+
description: "Create a responsive CSS layout structure",
|
|
1766
|
+
template: `Design a responsive {{layoutName}} layout in {{project.language}}{{#if project.framework}} using {{project.framework}}{{/if}}.
|
|
1767
|
+
|
|
1768
|
+
Layout description: {{description}}
|
|
1769
|
+
|
|
1770
|
+
Breakpoints:
|
|
1771
|
+
- Mobile: < 640px \u2014 {{mobile}}
|
|
1772
|
+
- Tablet: 640px\u20131024px \u2014 {{tablet}}
|
|
1773
|
+
- Desktop: > 1024px \u2014 {{desktop}}
|
|
1774
|
+
|
|
1775
|
+
Requirements:
|
|
1776
|
+
- CSS Grid or Flexbox (explain the choice)
|
|
1777
|
+
- No magic numbers \u2014 use design tokens/CSS variables for spacing and color
|
|
1778
|
+
- Accessible: correct heading hierarchy, landmark regions (header, main, nav, footer)
|
|
1779
|
+
- Dark mode support via prefers-color-scheme
|
|
1780
|
+
- No horizontal scroll at any viewport width
|
|
1781
|
+
- Print-friendly (hide navigation, show content)`,
|
|
1782
|
+
contextSchema: [
|
|
1783
|
+
{ key: "layoutName", label: "Layout name", type: "text", placeholder: "Dashboard" },
|
|
1784
|
+
{ key: "description", label: "Layout description", type: "text", placeholder: "Sidebar + main content + top nav" },
|
|
1785
|
+
{ key: "mobile", label: "Mobile layout", type: "text", placeholder: "single column, hamburger menu" },
|
|
1786
|
+
{ key: "tablet", label: "Tablet layout", type: "text", placeholder: "collapsible sidebar, content" },
|
|
1787
|
+
{ key: "desktop", label: "Desktop layout", type: "text", placeholder: "fixed sidebar, main + right panel" }
|
|
1788
|
+
],
|
|
1789
|
+
tags: ["layout", "css", "responsive", "frontend"],
|
|
1790
|
+
version: "1.0.0"
|
|
1791
|
+
},
|
|
1792
|
+
{
|
|
1793
|
+
id: "frontend/state-management",
|
|
1794
|
+
name: "State Management",
|
|
1795
|
+
category: "frontend",
|
|
1796
|
+
description: "Set up a client-side state management store",
|
|
1797
|
+
template: `Set up {{library}} state management in {{project.language}}{{#if project.framework}} / {{project.framework}}{{/if}}.
|
|
1798
|
+
|
|
1799
|
+
State to manage: {{stateDescription}}
|
|
1800
|
+
Slices/stores: {{slices}}
|
|
1801
|
+
|
|
1802
|
+
Requirements:
|
|
1803
|
+
- Separate concerns: server state (API data) vs client/UI state (modals, selected items)
|
|
1804
|
+
- Async actions: loading / success / error states for each API slice
|
|
1805
|
+
- Selectors: memoized derived state \u2014 don't recompute on unrelated changes
|
|
1806
|
+
- Persistence: {{persistence}} using localStorage/sessionStorage
|
|
1807
|
+
- DevTools integration: time-travel debugging in development
|
|
1808
|
+
- TypeScript: full type coverage \u2014 no \`any\` in store or selectors
|
|
1809
|
+
|
|
1810
|
+
For each slice provide:
|
|
1811
|
+
1. State shape with TypeScript interface
|
|
1812
|
+
2. Actions / reducers / thunks
|
|
1813
|
+
3. Selectors
|
|
1814
|
+
4. Example component consuming the slice
|
|
1815
|
+
|
|
1816
|
+
Also explain: when should this state live in the global store vs local component state?`,
|
|
1817
|
+
contextSchema: [
|
|
1818
|
+
{ key: "library", label: "State library", type: "select", options: ["Zustand", "Redux Toolkit", "Jotai", "Recoil", "MobX"], default: "Zustand" },
|
|
1819
|
+
{ key: "stateDescription", label: "What state to manage", type: "text", placeholder: "Current user auth, shopping cart, UI theme" },
|
|
1820
|
+
{ key: "slices", label: "Slices / stores", type: "text", placeholder: "auth, cart, ui" },
|
|
1821
|
+
{ key: "persistence", label: "Persist to storage?", type: "select", options: ["none", "auth only", "full store"], default: "auth only" }
|
|
1822
|
+
],
|
|
1823
|
+
tags: ["state", "zustand", "redux", "frontend"],
|
|
1824
|
+
version: "1.0.0"
|
|
1825
|
+
},
|
|
1826
|
+
{
|
|
1827
|
+
id: "frontend/data-table",
|
|
1828
|
+
name: "Data Table",
|
|
1829
|
+
category: "frontend",
|
|
1830
|
+
description: "Build a data table with sorting, filtering, and pagination",
|
|
1831
|
+
template: `Build a {{tableName}} data table in {{project.language}}{{#if project.framework}} / {{project.framework}}{{/if}}.
|
|
1832
|
+
|
|
1833
|
+
Data shape: {{dataShape}}
|
|
1834
|
+
Columns: {{columns}}
|
|
1835
|
+
|
|
1836
|
+
Features to implement:
|
|
1837
|
+
- Column sorting (click header to toggle asc/desc, multi-column sort optional)
|
|
1838
|
+
- Column filtering: text search + {{filterType}} per column
|
|
1839
|
+
- Pagination: {{paginationType}} with configurable page size
|
|
1840
|
+
- Row selection: {{selection}}
|
|
1841
|
+
- Column visibility toggle (show/hide columns)
|
|
1842
|
+
- Loading skeleton (not just a spinner \u2014 show column widths)
|
|
1843
|
+
- Empty state with helpful message
|
|
1844
|
+
|
|
1845
|
+
Performance:
|
|
1846
|
+
- Virtualize rows if dataset > 1000 items
|
|
1847
|
+
- Debounce filter inputs (300ms)
|
|
1848
|
+
- Memoize sort/filter computations
|
|
1849
|
+
|
|
1850
|
+
Accessibility:
|
|
1851
|
+
- Role="grid" with aria-sort on sortable headers
|
|
1852
|
+
- Keyboard navigation: arrow keys to move between cells
|
|
1853
|
+
- Announce sort/filter changes to screen readers`,
|
|
1854
|
+
contextSchema: [
|
|
1855
|
+
{ key: "tableName", label: "Table name", type: "text", placeholder: "UsersTable" },
|
|
1856
|
+
{ key: "dataShape", label: "Row data shape", type: "text", placeholder: "id, name, email, role, createdAt, status" },
|
|
1857
|
+
{ key: "columns", label: "Visible columns", type: "text", placeholder: "Name, Email, Role, Status, Created At, Actions" },
|
|
1858
|
+
{ key: "filterType", label: "Filter type", type: "select", options: ["text search only", "dropdown select", "date range", "text + dropdown"], default: "text + dropdown" },
|
|
1859
|
+
{ key: "paginationType", label: "Pagination type", type: "select", options: ["page numbers", "cursor-based", "infinite scroll", "load more button"], default: "page numbers" },
|
|
1860
|
+
{ key: "selection", label: "Row selection", type: "select", options: ["none", "single row", "multi-select with checkbox"], default: "multi-select with checkbox" }
|
|
1861
|
+
],
|
|
1862
|
+
tags: ["table", "data-grid", "frontend"],
|
|
1863
|
+
version: "1.0.0"
|
|
1864
|
+
},
|
|
1865
|
+
{
|
|
1866
|
+
id: "frontend/auth-flow",
|
|
1867
|
+
name: "Auth UI Flow",
|
|
1868
|
+
category: "frontend",
|
|
1869
|
+
description: "Implement login, register, and protected route UI flow",
|
|
1870
|
+
template: `Implement the authentication UI flow in {{project.language}}{{#if project.framework}} / {{project.framework}}{{/if}}.
|
|
1871
|
+
|
|
1872
|
+
Backend auth API: {{authApi}}
|
|
1873
|
+
Token storage: {{tokenStorage}}
|
|
1874
|
+
|
|
1875
|
+
Screens to build:
|
|
1876
|
+
1. **Login form** \u2014 email + password, remember me, forgot password link
|
|
1877
|
+
2. **Register form** \u2014 name, email, password + confirm, terms checkbox
|
|
1878
|
+
3. **Forgot password** \u2014 email input, confirmation message
|
|
1879
|
+
4. **Protected route wrapper** \u2014 redirect to /login if not authenticated, preserve intended URL
|
|
1880
|
+
|
|
1881
|
+
Auth state management:
|
|
1882
|
+
- Store: user profile + access token + refresh token + expiry
|
|
1883
|
+
- On app load: restore session from {{tokenStorage}}, validate token expiry
|
|
1884
|
+
- Auto-refresh: silently refresh access token {{refreshWindow}} before expiry
|
|
1885
|
+
- Logout: clear all tokens, invalidate session on server, redirect to /login
|
|
1886
|
+
|
|
1887
|
+
UX requirements:
|
|
1888
|
+
- Show loading state during auth operations
|
|
1889
|
+
- Surface backend error messages (wrong password, email taken)
|
|
1890
|
+
- Redirect to originally requested page after login
|
|
1891
|
+
- Logout confirmation only if user has unsaved work`,
|
|
1892
|
+
contextSchema: [
|
|
1893
|
+
{ key: "authApi", label: "Auth API base", type: "text", placeholder: "/api/auth (POST /login, POST /register, POST /refresh)" },
|
|
1894
|
+
{ key: "tokenStorage", label: "Token storage", type: "select", options: ["localStorage", "httpOnly cookie", "in-memory + refresh cookie"], default: "httpOnly cookie" },
|
|
1895
|
+
{ key: "refreshWindow", label: "Refresh how early before expiry", type: "text", default: "2 minutes" }
|
|
1896
|
+
],
|
|
1897
|
+
tags: ["auth", "login", "frontend", "react"],
|
|
1898
|
+
version: "1.0.0"
|
|
1899
|
+
},
|
|
1900
|
+
{
|
|
1901
|
+
id: "frontend/toast-notifications",
|
|
1902
|
+
name: "Toast Notifications",
|
|
1903
|
+
category: "frontend",
|
|
1904
|
+
description: "Implement a toast/snackbar notification system",
|
|
1905
|
+
template: `Implement a toast notification system in {{project.language}}{{#if project.framework}} / {{project.framework}}{{/if}}.
|
|
1906
|
+
|
|
1907
|
+
Requirements:
|
|
1908
|
+
- Types: success, error, warning, info (with distinct colors and icons)
|
|
1909
|
+
- Auto-dismiss after {{autoDismiss}}s (configurable per toast)
|
|
1910
|
+
- Manual dismiss button on each toast
|
|
1911
|
+
- Stack multiple toasts (max {{maxToasts}} visible at once, queue the rest)
|
|
1912
|
+
- Position: {{position}}
|
|
1913
|
+
- Animations: slide-in on appear, fade-out on dismiss
|
|
1914
|
+
- Accessibility: role="alert" for errors, role="status" for info; auto-focus management
|
|
1915
|
+
|
|
1916
|
+
API design (imperative, usable anywhere in the app):
|
|
1917
|
+
\`\`\`
|
|
1918
|
+
toast.success("Profile saved")
|
|
1919
|
+
toast.error("Failed to save", { duration: 0 }) // persistent
|
|
1920
|
+
toast.promise(saveProfile(), { loading: "Saving...", success: "Saved!", error: "Failed" })
|
|
1921
|
+
\`\`\`
|
|
1922
|
+
|
|
1923
|
+
Provide: ToastProvider, useToast hook, and the individual Toast component.`,
|
|
1924
|
+
contextSchema: [
|
|
1925
|
+
{ key: "position", label: "Toast position", type: "select", options: ["top-right", "top-center", "bottom-right", "bottom-center"], default: "top-right" },
|
|
1926
|
+
{ key: "autoDismiss", label: "Auto-dismiss delay (seconds)", type: "text", default: "5" },
|
|
1927
|
+
{ key: "maxToasts", label: "Max visible toasts", type: "text", default: "3" }
|
|
1928
|
+
],
|
|
1929
|
+
tags: ["toast", "notifications", "ui", "frontend"],
|
|
1930
|
+
version: "1.0.0"
|
|
1931
|
+
},
|
|
1932
|
+
{
|
|
1933
|
+
id: "frontend/infinite-scroll",
|
|
1934
|
+
name: "Infinite Scroll",
|
|
1935
|
+
category: "frontend",
|
|
1936
|
+
description: "Implement infinite scroll with intersection observer",
|
|
1937
|
+
template: `Implement infinite scroll for {{listName}} in {{project.language}}{{#if project.framework}} / {{project.framework}}{{/if}}.
|
|
1938
|
+
|
|
1939
|
+
Data source: {{dataSource}}
|
|
1940
|
+
Item component: {{itemComponent}}
|
|
1941
|
+
|
|
1942
|
+
Implementation:
|
|
1943
|
+
- Use IntersectionObserver (not scroll events) to detect when the sentinel element enters the viewport
|
|
1944
|
+
- Trigger next page load when sentinel is within {{threshold}}px of the viewport
|
|
1945
|
+
- Show a loading skeleton for new items (not a full-page spinner)
|
|
1946
|
+
- Handle: initial load, loading next page, no more pages, network error with retry
|
|
1947
|
+
- Preserve scroll position on browser back/forward navigation
|
|
1948
|
+
- Cleanup: disconnect observer on unmount
|
|
1949
|
+
|
|
1950
|
+
Cursor-based pagination:
|
|
1951
|
+
- Use cursor (not page number) to avoid duplicate/missing items if data changes
|
|
1952
|
+
- API call: GET {{dataSource}}?cursor={{cursor}}&limit={{pageSize}}
|
|
1953
|
+
- Response shape: { items: T[], nextCursor: string | null }
|
|
1954
|
+
|
|
1955
|
+
Provide: useInfiniteScroll hook + ItemList component + sentinel element pattern.`,
|
|
1956
|
+
contextSchema: [
|
|
1957
|
+
{ key: "listName", label: "List name", type: "text", placeholder: "PostFeed" },
|
|
1958
|
+
{ key: "dataSource", label: "API endpoint", type: "text", placeholder: "/api/posts" },
|
|
1959
|
+
{ key: "itemComponent", label: "Item component", type: "text", placeholder: "PostCard" },
|
|
1960
|
+
{ key: "threshold", label: "Load trigger threshold (px from bottom)", type: "text", default: "200" },
|
|
1961
|
+
{ key: "pageSize", label: "Items per page", type: "text", default: "20" }
|
|
1962
|
+
],
|
|
1963
|
+
tags: ["infinite-scroll", "pagination", "performance", "frontend"],
|
|
1964
|
+
version: "1.0.0"
|
|
1965
|
+
},
|
|
1966
|
+
// ── Database ─────────────────────────────────────────────────────────────────
|
|
1967
|
+
{
|
|
1968
|
+
id: "database/schema-design",
|
|
1969
|
+
name: "Database Schema",
|
|
1970
|
+
category: "database",
|
|
1971
|
+
description: "Design a normalized relational database schema",
|
|
1972
|
+
template: `Design a database schema for {{domain}} in {{dbType}}.
|
|
1973
|
+
|
|
1974
|
+
Entities to model: {{entities}}
|
|
1975
|
+
|
|
1976
|
+
Requirements:
|
|
1977
|
+
- Normalize to 3NF unless there's a documented performance reason to denormalize
|
|
1978
|
+
- Primary keys: {{pkStrategy}}
|
|
1979
|
+
- All tables must have: created_at, updated_at timestamps
|
|
1980
|
+
- Foreign key constraints with ON DELETE behavior specified
|
|
1981
|
+
- Indexes: primary key (auto), foreign keys, and {{indexColumns}}
|
|
1982
|
+
- Soft delete pattern where data must be auditable
|
|
1983
|
+
|
|
1984
|
+
Provide:
|
|
1985
|
+
1. Entity-relationship description
|
|
1986
|
+
2. CREATE TABLE statements
|
|
1987
|
+
3. Explanation of relationships and cardinality
|
|
1988
|
+
4. Query for the most common access pattern: {{commonQuery}}`,
|
|
1989
|
+
contextSchema: [
|
|
1990
|
+
{ key: "domain", label: "Domain / system name", type: "text", placeholder: "e-commerce order management" },
|
|
1991
|
+
{ key: "entities", label: "Entities (comma-separated)", type: "text", placeholder: "User, Order, Product, OrderItem" },
|
|
1992
|
+
{ key: "dbType", label: "Database", type: "select", options: ["PostgreSQL", "MySQL", "SQLite", "MongoDB"], default: "PostgreSQL" },
|
|
1993
|
+
{ key: "pkStrategy", label: "Primary key strategy", type: "select", options: ["UUID", "auto-increment integer", "ULID"], default: "UUID" },
|
|
1994
|
+
{ key: "indexColumns", label: "Columns to index", type: "text", placeholder: "email (unique), status, created_at" },
|
|
1995
|
+
{ key: "commonQuery", label: "Most common query", type: "text", placeholder: "Get all orders for a user with product details" }
|
|
1996
|
+
],
|
|
1997
|
+
tags: ["database", "schema", "sql"],
|
|
1998
|
+
version: "1.0.0"
|
|
1999
|
+
},
|
|
2000
|
+
{
|
|
2001
|
+
id: "database/migration-script",
|
|
2002
|
+
name: "Migration Script",
|
|
2003
|
+
category: "database",
|
|
2004
|
+
description: "Write a safe, reversible database migration",
|
|
2005
|
+
template: `Write a database migration for {{description}} in {{migrationTool}}.
|
|
2006
|
+
|
|
2007
|
+
Change: {{change}}
|
|
2008
|
+
|
|
2009
|
+
Safety requirements:
|
|
2010
|
+
- The migration must be fully reversible (down() must exactly undo up())
|
|
2011
|
+
- No data loss without explicit confirmation comment
|
|
2012
|
+
- For large tables (>1M rows), use batched operations to avoid lock timeouts
|
|
2013
|
+
- If adding a NOT NULL column to an existing table, provide a backfill default
|
|
2014
|
+
- If renaming a column, do it in 3 steps: add new \u2192 backfill \u2192 remove old (separate migrations)
|
|
2015
|
+
- Test the rollback: does down() leave the schema identical to before up()?
|
|
2016
|
+
|
|
2017
|
+
Also provide:
|
|
2018
|
+
- SQL preview of both up() and down()
|
|
2019
|
+
- Risk assessment: what breaks if this migration fails mid-way?`,
|
|
2020
|
+
contextSchema: [
|
|
2021
|
+
{ key: "description", label: "Migration description", type: "text", placeholder: "Add email verification to users table" },
|
|
2022
|
+
{ key: "change", label: "Specific change", type: "text", placeholder: "Add verified_at TIMESTAMP NULL and verification_token VARCHAR(64)" },
|
|
2023
|
+
{ key: "migrationTool", label: "Migration tool", type: "select", options: ["Knex", "Flyway", "Liquibase", "Alembic", "Prisma", "TypeORM", "raw SQL"], default: "Knex" }
|
|
2024
|
+
],
|
|
2025
|
+
tags: ["database", "migration", "sql"],
|
|
2026
|
+
version: "1.0.0"
|
|
2027
|
+
},
|
|
2028
|
+
{
|
|
2029
|
+
id: "database/orm-model",
|
|
2030
|
+
name: "ORM Model",
|
|
2031
|
+
category: "database",
|
|
2032
|
+
description: "Define ORM models with relations, validations, and hooks",
|
|
2033
|
+
template: `Define ORM models for {{entities}} in {{project.language}} using {{orm}}.
|
|
2034
|
+
|
|
2035
|
+
For each entity provide:
|
|
2036
|
+
1. Model/schema definition with all fields, types, and constraints
|
|
2037
|
+
2. Relations: one-to-one, one-to-many, many-to-many with junction tables
|
|
2038
|
+
3. Indexes: primary key, unique constraints, compound indexes for common queries
|
|
2039
|
+
4. Hooks/middleware: {{hooks}}
|
|
2040
|
+
5. Virtual fields / computed properties
|
|
2041
|
+
6. Serialization: toJSON() that excludes sensitive fields (passwords, tokens)
|
|
2042
|
+
|
|
2043
|
+
Entity descriptions:
|
|
2044
|
+
{{entityDescriptions}}
|
|
2045
|
+
|
|
2046
|
+
Also provide:
|
|
2047
|
+
- Repository pattern wrapper with typed CRUD methods
|
|
2048
|
+
- Example of an eager-loaded query with nested relations
|
|
2049
|
+
- Explain your choice of cascade settings (ON DELETE behavior for each FK)`,
|
|
2050
|
+
contextSchema: [
|
|
2051
|
+
{ key: "entities", label: "Entities to model", type: "text", placeholder: "User, Post, Comment, Tag" },
|
|
2052
|
+
{ key: "orm", label: "ORM / ODM", type: "select", options: ["Prisma", "TypeORM", "Drizzle", "Sequelize", "Mongoose", "SQLAlchemy"], default: "Prisma" },
|
|
2053
|
+
{ key: "hooks", label: "Lifecycle hooks needed", type: "text", placeholder: "hash password before save, set updatedAt, log deletes" },
|
|
2054
|
+
{ key: "entityDescriptions", label: "Entity descriptions", type: "text", placeholder: "User: has email, password hash, role. Post: belongs to User, has title, body, tags. Comment: belongs to Post and User." }
|
|
2055
|
+
],
|
|
2056
|
+
tags: ["orm", "database", "prisma", "typeorm"],
|
|
2057
|
+
version: "1.0.0"
|
|
2058
|
+
},
|
|
2059
|
+
{
|
|
2060
|
+
id: "database/query-optimization",
|
|
2061
|
+
name: "Query Optimization",
|
|
2062
|
+
category: "database",
|
|
2063
|
+
description: "Diagnose and optimize a slow database query",
|
|
2064
|
+
template: `Diagnose and optimize the following slow {{dbType}} query.
|
|
2065
|
+
|
|
2066
|
+
Query:
|
|
2067
|
+
{{query}}
|
|
2068
|
+
|
|
2069
|
+
Context:
|
|
2070
|
+
- Table sizes: {{tableSizes}}
|
|
2071
|
+
- Current indexes: {{currentIndexes}}
|
|
2072
|
+
- Query execution time: {{currentTime}}
|
|
2073
|
+
- Called: {{callFrequency}}
|
|
2074
|
+
|
|
2075
|
+
Analysis steps:
|
|
2076
|
+
1. **EXPLAIN / EXPLAIN ANALYZE** \u2014 interpret the query plan, identify sequential scans and high row estimates
|
|
2077
|
+
2. **Index recommendations** \u2014 which columns to index, composite index column order (selectivity first), partial index if applicable
|
|
2078
|
+
3. **Query rewrite** \u2014 can the same result be achieved with a more efficient query?
|
|
2079
|
+
4. **N+1 detection** \u2014 is this query inside a loop? Replace with a JOIN or subquery
|
|
2080
|
+
5. **Covering index** \u2014 can we avoid heap fetches by including all needed columns in the index?
|
|
2081
|
+
6. **Pagination** \u2014 if this is a list query, use cursor pagination not OFFSET for large tables
|
|
2082
|
+
|
|
2083
|
+
Provide:
|
|
2084
|
+
- Optimized query
|
|
2085
|
+
- Index DDL statements
|
|
2086
|
+
- Expected improvement estimate
|
|
2087
|
+
- Trade-offs (write overhead for new indexes)`,
|
|
2088
|
+
contextSchema: [
|
|
2089
|
+
{ key: "query", label: "Slow query (SQL or ORM)", type: "text", placeholder: "SELECT * FROM orders JOIN users ON ..." },
|
|
2090
|
+
{ key: "dbType", label: "Database", type: "select", options: ["PostgreSQL", "MySQL", "SQLite"], default: "PostgreSQL" },
|
|
2091
|
+
{ key: "tableSizes", label: "Table sizes", type: "text", placeholder: "orders: 2M rows, users: 50k rows" },
|
|
2092
|
+
{ key: "currentIndexes", label: "Current indexes", type: "text", placeholder: "PRIMARY KEY on id, INDEX on user_id" },
|
|
2093
|
+
{ key: "currentTime", label: "Current execution time", type: "text", placeholder: "3.2 seconds" },
|
|
2094
|
+
{ key: "callFrequency", label: "How often is it called?", type: "text", placeholder: "500 times/minute" }
|
|
2095
|
+
],
|
|
2096
|
+
tags: ["database", "performance", "sql", "optimization"],
|
|
2097
|
+
version: "1.0.0"
|
|
2098
|
+
},
|
|
2099
|
+
{
|
|
2100
|
+
id: "database/seed-data",
|
|
2101
|
+
name: "Seed Data",
|
|
2102
|
+
category: "database",
|
|
2103
|
+
description: "Write database seed scripts for development and testing",
|
|
2104
|
+
template: `Write database seed scripts for {{project.language}} using {{orm}}.
|
|
2105
|
+
|
|
2106
|
+
Entities to seed: {{entities}}
|
|
2107
|
+
|
|
2108
|
+
Requirements:
|
|
2109
|
+
- Idempotent: running the seed twice must not create duplicates (upsert or check-before-insert)
|
|
2110
|
+
- Ordered: respect foreign key constraints (seed parent records before children)
|
|
2111
|
+
- Realistic data: use a faker library for names, emails, dates \u2014 no "Test User 1"
|
|
2112
|
+
- Separate seeds: one file per entity + a master seed that runs them in order
|
|
2113
|
+
- Environment-aware: dev seeds create {{devCount}} records; test seeds create minimal fixture data
|
|
2114
|
+
- Relationships: ensure seeded records form valid, queryable relationships
|
|
2115
|
+
|
|
2116
|
+
For each entity provide:
|
|
2117
|
+
1. Factory function: \`createFakeUser(overrides?)\` for use in tests
|
|
2118
|
+
2. Bulk seed function that inserts N records efficiently (batch insert, not N queries)
|
|
2119
|
+
3. Teardown function to reset to a clean state in tests
|
|
2120
|
+
|
|
2121
|
+
Include a seed for the default admin user with documented credentials.`,
|
|
2122
|
+
contextSchema: [
|
|
2123
|
+
{ key: "entities", label: "Entities to seed", type: "text", placeholder: "User, Product, Category, Order" },
|
|
2124
|
+
{ key: "orm", label: "ORM / query builder", type: "select", options: ["Prisma", "Knex", "TypeORM", "Sequelize", "raw SQL"], default: "Prisma" },
|
|
2125
|
+
{ key: "devCount", label: "Records per entity in dev", type: "text", default: "50" }
|
|
2126
|
+
],
|
|
2127
|
+
tags: ["database", "seed", "testing", "fixtures"],
|
|
2128
|
+
version: "1.0.0"
|
|
2129
|
+
},
|
|
2130
|
+
// ── Testing ───────────────────────────────────────────────────────────────────
|
|
2131
|
+
{
|
|
2132
|
+
id: "testing/unit-test",
|
|
2133
|
+
name: "Unit Test Suite",
|
|
2134
|
+
category: "testing",
|
|
2135
|
+
description: "Write a complete unit test suite for a function or class",
|
|
2136
|
+
template: `Write a complete unit test suite for {{target}} in {{project.language}} using {{testFramework}}.
|
|
2137
|
+
|
|
2138
|
+
Code to test:
|
|
2139
|
+
{{codeDescription}}
|
|
2140
|
+
|
|
2141
|
+
Test cases to cover:
|
|
2142
|
+
1. Happy path: {{happyPath}}
|
|
2143
|
+
2. Edge cases: {{edgeCases}}
|
|
2144
|
+
3. Error cases: {{errorCases}}
|
|
2145
|
+
4. Boundary values
|
|
2146
|
+
|
|
2147
|
+
Requirements:
|
|
2148
|
+
- Mock all external dependencies (database, HTTP, file system)
|
|
2149
|
+
- Each test should have one assertion focus (Arrange-Act-Assert)
|
|
2150
|
+
- Descriptive test names: "should [action] when [condition]"
|
|
2151
|
+
- Test data factories for complex inputs (don't inline large objects)
|
|
2152
|
+
- Coverage target: all branches + all exported functions
|
|
2153
|
+
{{#if (eq project.type "student")}}- Comments explaining what each describe block is testing and why{{/if}}`,
|
|
2154
|
+
contextSchema: [
|
|
2155
|
+
{ key: "target", label: "Function/class to test", type: "text", placeholder: "UserService.createUser()" },
|
|
2156
|
+
{ key: "codeDescription", label: "Describe what it does", type: "text", placeholder: "Validates input, hashes password, saves to DB, sends welcome email" },
|
|
2157
|
+
{ key: "testFramework", label: "Test framework", type: "select", options: ["Jest", "Vitest", "Mocha", "pytest", "JUnit"], default: "Jest" },
|
|
2158
|
+
{ key: "happyPath", label: "Happy path", type: "text", placeholder: "valid input creates and returns user" },
|
|
2159
|
+
{ key: "edgeCases", label: "Edge cases", type: "text", placeholder: "duplicate email, empty name, very long input" },
|
|
2160
|
+
{ key: "errorCases", label: "Error cases", type: "text", placeholder: "DB failure, email service down" }
|
|
2161
|
+
],
|
|
2162
|
+
tags: ["testing", "unit-test", "tdd"],
|
|
2163
|
+
version: "1.0.0"
|
|
2164
|
+
},
|
|
2165
|
+
{
|
|
2166
|
+
id: "testing/tdd-plan",
|
|
2167
|
+
name: "TDD Plan",
|
|
2168
|
+
category: "testing",
|
|
2169
|
+
description: "Design a test-first implementation plan (London School TDD)",
|
|
2170
|
+
template: `Design a TDD implementation plan for {{feature}} in {{project.language}}.
|
|
2171
|
+
|
|
2172
|
+
Feature: {{description}}
|
|
2173
|
+
|
|
2174
|
+
Follow London School TDD (outside-in, mock collaborators):
|
|
2175
|
+
|
|
2176
|
+
1. Start with an acceptance test that describes the feature from the outside
|
|
2177
|
+
2. Identify the collaborators (dependencies) and mock them
|
|
2178
|
+
3. Write the first failing unit test
|
|
2179
|
+
4. Implement the minimum code to make it pass
|
|
2180
|
+
5. Refactor
|
|
2181
|
+
6. Repeat inward until the acceptance test passes
|
|
2182
|
+
|
|
2183
|
+
Produce:
|
|
2184
|
+
- Acceptance test skeleton
|
|
2185
|
+
- Unit test skeletons for each layer (controller \u2192 service \u2192 repository)
|
|
2186
|
+
- Mock definitions for each boundary
|
|
2187
|
+
- Implementation order (outermost layer first)
|
|
2188
|
+
- Definition of Done: what must be true for this feature to be "complete"?`,
|
|
2189
|
+
contextSchema: [
|
|
2190
|
+
{ key: "feature", label: "Feature name", type: "text", placeholder: "User login with JWT" },
|
|
2191
|
+
{ key: "description", label: "Feature description", type: "text", placeholder: "User provides email + password, receives JWT access token and refresh token" }
|
|
2192
|
+
],
|
|
2193
|
+
tags: ["testing", "tdd", "london-school"],
|
|
2194
|
+
version: "1.0.0"
|
|
2195
|
+
},
|
|
2196
|
+
{
|
|
2197
|
+
id: "testing/integration-test",
|
|
2198
|
+
name: "Integration Test",
|
|
2199
|
+
category: "testing",
|
|
2200
|
+
description: "Write integration tests against a real database or service",
|
|
2201
|
+
template: `Write integration tests for {{target}} in {{project.language}} using {{testFramework}}.
|
|
2202
|
+
|
|
2203
|
+
Test scope: {{scope}}
|
|
2204
|
+
|
|
2205
|
+
Setup requirements:
|
|
2206
|
+
- Spin up a test database: {{dbSetup}}
|
|
2207
|
+
- Run migrations before the test suite; roll back (or truncate) after each test
|
|
2208
|
+
- No mocking of the database \u2014 test the full stack from service to DB
|
|
2209
|
+
- Use a dedicated test schema/database to never affect development data
|
|
2210
|
+
|
|
2211
|
+
Test cases:
|
|
2212
|
+
{{testCases}}
|
|
2213
|
+
|
|
2214
|
+
Test structure:
|
|
2215
|
+
- beforeAll: start test DB, run migrations
|
|
2216
|
+
- beforeEach: seed minimal required fixtures
|
|
2217
|
+
- afterEach: truncate tables (faster than re-migrating)
|
|
2218
|
+
- afterAll: close DB connection, stop test server
|
|
2219
|
+
|
|
2220
|
+
Isolation: each test must be independent \u2014 never rely on state from a previous test
|
|
2221
|
+
|
|
2222
|
+
Provide: test helpers for creating fixtures, a test DB configuration, and the test file.`,
|
|
2223
|
+
contextSchema: [
|
|
2224
|
+
{ key: "target", label: "Feature to test", type: "text", placeholder: "User registration and login flow" },
|
|
2225
|
+
{ key: "scope", label: "Test scope", type: "text", placeholder: "POST /api/users and POST /api/auth/login" },
|
|
2226
|
+
{ key: "testFramework", label: "Test framework", type: "select", options: ["Jest", "Vitest", "pytest", "JUnit", "Go testing"], default: "Jest" },
|
|
2227
|
+
{ key: "dbSetup", label: "DB setup method", type: "select", options: ["Docker container", "SQLite in-memory", "test schema on dev DB", "Testcontainers"], default: "Docker container" },
|
|
2228
|
+
{ key: "testCases", label: "Test cases to cover", type: "text", placeholder: "successful registration, duplicate email, invalid password, login with wrong password" }
|
|
2229
|
+
],
|
|
2230
|
+
tags: ["integration-test", "testing", "database"],
|
|
2231
|
+
version: "1.0.0"
|
|
2232
|
+
},
|
|
2233
|
+
{
|
|
2234
|
+
id: "testing/e2e-test",
|
|
2235
|
+
name: "E2E Test Plan",
|
|
2236
|
+
category: "testing",
|
|
2237
|
+
description: "Create end-to-end tests for a user-facing flow",
|
|
2238
|
+
template: `Write end-to-end tests for the {{flow}} flow using {{e2eFramework}}.
|
|
2239
|
+
|
|
2240
|
+
User flow to test:
|
|
2241
|
+
{{flowDescription}}
|
|
2242
|
+
|
|
2243
|
+
Test scenarios:
|
|
2244
|
+
1. **Happy path**: full successful flow with real user inputs
|
|
2245
|
+
2. **Validation errors**: missing fields, invalid formats \u2014 UI shows error messages
|
|
2246
|
+
3. **Server errors**: simulate network failure or 500 \u2014 UI shows error state
|
|
2247
|
+
4. **Edge cases**: {{edgeCases}}
|
|
2248
|
+
|
|
2249
|
+
Requirements:
|
|
2250
|
+
- Use Page Object Model (POM) \u2014 separate UI interaction logic from test assertions
|
|
2251
|
+
- Never use \`sleep\` \u2014 use proper await conditions (element visible, network request complete)
|
|
2252
|
+
- Test IDs: use \`data-testid\` attributes on interactive elements, not CSS selectors or text
|
|
2253
|
+
- Viewport: test on {{viewports}}
|
|
2254
|
+
- Record a video / screenshot on failure for CI debugging
|
|
2255
|
+
|
|
2256
|
+
For each scenario provide:
|
|
2257
|
+
- Page Object methods
|
|
2258
|
+
- Test steps in plain English then code
|
|
2259
|
+
- What to assert (not just "page loaded" but specific data visible)`,
|
|
2260
|
+
contextSchema: [
|
|
2261
|
+
{ key: "flow", label: "User flow name", type: "text", placeholder: "Checkout" },
|
|
2262
|
+
{ key: "flowDescription", label: "Flow description", type: "text", placeholder: "User adds item to cart \u2192 enters shipping \u2192 pays \u2192 sees confirmation" },
|
|
2263
|
+
{ key: "e2eFramework", label: "E2E framework", type: "select", options: ["Playwright", "Cypress", "Selenium", "Puppeteer"], default: "Playwright" },
|
|
2264
|
+
{ key: "edgeCases", label: "Edge cases", type: "text", placeholder: "empty cart, expired payment card, address validation failure" },
|
|
2265
|
+
{ key: "viewports", label: "Viewports to test", type: "text", default: "desktop (1280x720) and mobile (390x844)" }
|
|
2266
|
+
],
|
|
2267
|
+
tags: ["e2e", "testing", "playwright", "cypress"],
|
|
2268
|
+
version: "1.0.0"
|
|
2269
|
+
},
|
|
2270
|
+
{
|
|
2271
|
+
id: "testing/mock-strategy",
|
|
2272
|
+
name: "Mock Strategy",
|
|
2273
|
+
category: "testing",
|
|
2274
|
+
description: "Design a consistent mocking strategy for a module or service",
|
|
2275
|
+
template: `Design a mocking strategy for {{target}} in {{project.language}} using {{testFramework}}.
|
|
2276
|
+
|
|
2277
|
+
Dependencies to mock: {{dependencies}}
|
|
2278
|
+
|
|
2279
|
+
Strategy:
|
|
2280
|
+
1. **What to mock**: only cross-boundary dependencies (external APIs, databases, file system, time)
|
|
2281
|
+
2. **What NOT to mock**: internal modules, pure functions \u2014 test them for real
|
|
2282
|
+
3. **Mock fidelity**: mocks must have the same interface as the real thing \u2014 no drifting
|
|
2283
|
+
|
|
2284
|
+
For each dependency provide:
|
|
2285
|
+
- Mock factory: a reusable function that returns a pre-configured mock
|
|
2286
|
+
- Default behavior: what the mock returns when not configured otherwise
|
|
2287
|
+
- Per-test override: how to configure specific behavior for edge case tests
|
|
2288
|
+
- Spy assertions: how to assert that the mock was called with the right arguments
|
|
2289
|
+
|
|
2290
|
+
Patterns:
|
|
2291
|
+
- For HTTP: {{httpMockStrategy}}
|
|
2292
|
+
- For database: use an in-memory SQLite or a repository mock
|
|
2293
|
+
- For time: mock Date.now() / clock for deterministic tests
|
|
2294
|
+
- For env vars: use a test environment config, never modify process.env directly
|
|
2295
|
+
|
|
2296
|
+
Provide the mock factory implementations and an example test using each mock.`,
|
|
2297
|
+
contextSchema: [
|
|
2298
|
+
{ key: "target", label: "Module to write tests for", type: "text", placeholder: "PaymentService" },
|
|
2299
|
+
{ key: "dependencies", label: "Dependencies to mock", type: "text", placeholder: "Stripe SDK, UserRepository, EmailService, logger" },
|
|
2300
|
+
{ key: "testFramework", label: "Test framework", type: "select", options: ["Jest", "Vitest", "Sinon", "pytest-mock"], default: "Jest" },
|
|
2301
|
+
{ key: "httpMockStrategy", label: "HTTP mock strategy", type: "select", options: ["MSW (Mock Service Worker)", "nock", "jest.fn()", "fetch-mock"], default: "MSW (Mock Service Worker)" }
|
|
2302
|
+
],
|
|
2303
|
+
tags: ["mocking", "testing", "jest", "strategy"],
|
|
2304
|
+
version: "1.0.0"
|
|
2305
|
+
},
|
|
2306
|
+
{
|
|
2307
|
+
id: "testing/api-test",
|
|
2308
|
+
name: "API Test Suite",
|
|
2309
|
+
category: "testing",
|
|
2310
|
+
description: "Write tests for a REST API endpoint",
|
|
2311
|
+
template: `Write a test suite for the {{endpoint}} API endpoint in {{project.language}} using {{testFramework}}.
|
|
2312
|
+
|
|
2313
|
+
Endpoint: {{method}} {{route}}
|
|
2314
|
+
Expected behavior: {{behavior}}
|
|
2315
|
+
|
|
2316
|
+
Test cases to cover:
|
|
2317
|
+
- **200/201**: valid request returns expected response shape
|
|
2318
|
+
- **400**: missing required fields, invalid types, constraint violations
|
|
2319
|
+
- **401**: missing/invalid/expired auth token
|
|
2320
|
+
- **403**: valid auth but insufficient permissions
|
|
2321
|
+
- **404**: resource does not exist
|
|
2322
|
+
- **409**: conflict (e.g., duplicate creation)
|
|
2323
|
+
- **500**: upstream failure (mock the DB/service to throw)
|
|
2324
|
+
|
|
2325
|
+
For each test:
|
|
2326
|
+
1. Setup: create required DB fixtures
|
|
2327
|
+
2. Request: set headers, body, path params
|
|
2328
|
+
3. Assert: status code + response body shape + side effects (DB state, events emitted)
|
|
2329
|
+
4. Teardown: no side effects left for next test
|
|
2330
|
+
|
|
2331
|
+
Provide: test server setup, auth helper (create + login test user), and request helpers.`,
|
|
2332
|
+
contextSchema: [
|
|
2333
|
+
{ key: "endpoint", label: "Endpoint to test", type: "text", placeholder: "Create Order" },
|
|
2334
|
+
{ key: "method", label: "HTTP method", type: "select", options: ["GET", "POST", "PUT", "PATCH", "DELETE"], default: "POST" },
|
|
2335
|
+
{ key: "route", label: "Route", type: "text", placeholder: "/api/orders" },
|
|
2336
|
+
{ key: "behavior", label: "What it should do", type: "text", placeholder: "Creates an order, deducts inventory, sends confirmation email" },
|
|
2337
|
+
{ key: "testFramework", label: "Test framework", type: "select", options: ["Jest + Supertest", "Vitest + Supertest", "pytest + requests", "Rest Assured"], default: "Jest + Supertest" }
|
|
2338
|
+
],
|
|
2339
|
+
tags: ["api-testing", "testing", "rest", "supertest"],
|
|
2340
|
+
version: "1.0.0"
|
|
2341
|
+
},
|
|
2342
|
+
// ── DevOps ────────────────────────────────────────────────────────────────────
|
|
2343
|
+
{
|
|
2344
|
+
id: "devops/dockerfile",
|
|
2345
|
+
name: "Dockerfile",
|
|
2346
|
+
category: "devops",
|
|
2347
|
+
description: "Write an optimized multi-stage Dockerfile",
|
|
2348
|
+
template: `Write an optimized multi-stage Dockerfile for a {{project.language}} application{{#if project.framework}} using {{project.framework}}{{/if}}.
|
|
2349
|
+
|
|
2350
|
+
App type: {{appType}}
|
|
2351
|
+
Port: {{port}}
|
|
2352
|
+
|
|
2353
|
+
Requirements:
|
|
2354
|
+
- Multi-stage: builder stage + minimal runtime stage
|
|
2355
|
+
- Use the smallest viable base image (alpine or distroless)
|
|
2356
|
+
- Run as a non-root user
|
|
2357
|
+
- Layer cache optimization: copy package files + install before copying source
|
|
2358
|
+
- .dockerignore to exclude node_modules, .env, .git, test files
|
|
2359
|
+
- Health check endpoint: {{healthCheck}}
|
|
2360
|
+
- Build args for environment (dev/staging/prod)
|
|
2361
|
+
- Environment variables documented with ENV defaults
|
|
2362
|
+
|
|
2363
|
+
Also provide the .dockerignore and a docker-compose.yml for local development with a database service.`,
|
|
2364
|
+
contextSchema: [
|
|
2365
|
+
{ key: "appType", label: "App type", type: "select", options: ["web server", "worker/background job", "CLI tool"], default: "web server" },
|
|
2366
|
+
{ key: "port", label: "Exposed port", type: "text", default: "3000" },
|
|
2367
|
+
{ key: "healthCheck", label: "Health check path", type: "text", placeholder: "/api/health", default: "/health" }
|
|
2368
|
+
],
|
|
2369
|
+
tags: ["docker", "devops", "deployment"],
|
|
2370
|
+
version: "1.0.0"
|
|
2371
|
+
},
|
|
2372
|
+
{
|
|
2373
|
+
id: "devops/github-action",
|
|
2374
|
+
name: "GitHub Actions CI",
|
|
2375
|
+
category: "devops",
|
|
2376
|
+
description: "Create a CI/CD pipeline with GitHub Actions",
|
|
2377
|
+
template: `Create a GitHub Actions CI/CD workflow for a {{project.language}} project.
|
|
2378
|
+
|
|
2379
|
+
Pipeline stages: {{stages}}
|
|
2380
|
+
|
|
2381
|
+
Requirements:
|
|
2382
|
+
- Trigger: push to main + all pull requests
|
|
2383
|
+
- Cache: dependency cache (npm/pip/maven) keyed to lock file hash
|
|
2384
|
+
- Matrix: test on Node {{nodeVersions}} (if applicable)
|
|
2385
|
+
- Fail fast: if linting fails, don't run tests
|
|
2386
|
+
- PR checks: lint, type-check, test, coverage threshold ({{coverageThreshold}}%)
|
|
2387
|
+
- Deploy job: runs only on push to main, requires all checks to pass
|
|
2388
|
+
- Secrets: never echo secrets; use GitHub Secrets for API keys
|
|
2389
|
+
- Notifications: comment on PR with test results summary
|
|
2390
|
+
|
|
2391
|
+
Deploy target: {{deployTarget}}`,
|
|
2392
|
+
contextSchema: [
|
|
2393
|
+
{ key: "stages", label: "Pipeline stages", type: "text", default: "lint, type-check, test, build, deploy" },
|
|
2394
|
+
{ key: "nodeVersions", label: "Node versions to test", type: "text", default: "20, 22" },
|
|
2395
|
+
{ key: "coverageThreshold", label: "Coverage threshold (%)", type: "text", default: "80" },
|
|
2396
|
+
{ key: "deployTarget", label: "Deploy target", type: "select", options: ["Vercel", "Railway", "AWS ECS", "Fly.io", "none"], default: "none" }
|
|
2397
|
+
],
|
|
2398
|
+
tags: ["ci-cd", "github-actions", "devops"],
|
|
2399
|
+
version: "1.0.0"
|
|
2400
|
+
},
|
|
2401
|
+
{
|
|
2402
|
+
id: "devops/env-config",
|
|
2403
|
+
name: "Environment Config",
|
|
2404
|
+
category: "devops",
|
|
2405
|
+
description: "Design environment variable management across dev/staging/prod",
|
|
2406
|
+
template: `Design environment configuration management for a {{project.language}} project.
|
|
2407
|
+
|
|
2408
|
+
Environments: {{environments}}
|
|
2409
|
+
Config values needed: {{configValues}}
|
|
2410
|
+
|
|
2411
|
+
Requirements:
|
|
2412
|
+
1. **Schema validation on startup**: use a schema (Zod/Joi/env-schema) to validate all required env vars at boot \u2014 fail fast with a clear message listing missing vars
|
|
2413
|
+
2. **Type safety**: typed config object derived from validated env vars \u2014 no raw process.env in business logic
|
|
2414
|
+
3. **Secrets vs config**: distinguish between secrets (API keys, DB passwords) and non-secret config (port, log level)
|
|
2415
|
+
4. **Local development**: .env.example with all required vars documented but no real values; .env is git-ignored
|
|
2416
|
+
5. **Environment hierarchy**: .env < .env.local < .env.{NODE_ENV} (explain override order)
|
|
2417
|
+
6. **CI/CD**: document which vars go in GitHub Secrets vs non-secret CI variables
|
|
2418
|
+
|
|
2419
|
+
File structure to create:
|
|
2420
|
+
- \`config/env.ts\` \u2014 schema + validation + typed export
|
|
2421
|
+
- \`.env.example\` \u2014 all vars with placeholder values and comments
|
|
2422
|
+
- CI environment setup instructions
|
|
2423
|
+
|
|
2424
|
+
Never log environment variables, even in debug mode.`,
|
|
2425
|
+
contextSchema: [
|
|
2426
|
+
{ key: "environments", label: "Environments", type: "text", default: "development, test, staging, production" },
|
|
2427
|
+
{ key: "configValues", label: "Config values needed", type: "text", placeholder: "DATABASE_URL, JWT_SECRET, SMTP_HOST, REDIS_URL, PORT, LOG_LEVEL" }
|
|
2428
|
+
],
|
|
2429
|
+
tags: ["config", "environment", "devops", "security"],
|
|
2430
|
+
version: "1.0.0"
|
|
2431
|
+
},
|
|
2432
|
+
{
|
|
2433
|
+
id: "devops/monitoring",
|
|
2434
|
+
name: "Monitoring Setup",
|
|
2435
|
+
category: "devops",
|
|
2436
|
+
description: "Add application monitoring, logging, and alerting",
|
|
2437
|
+
template: `Design a monitoring and observability setup for a {{project.language}}{{#if project.framework}} / {{project.framework}}{{/if}} application.
|
|
2438
|
+
|
|
2439
|
+
Stack: {{monitoringStack}}
|
|
2440
|
+
|
|
2441
|
+
The three pillars of observability:
|
|
2442
|
+
|
|
2443
|
+
**1. Logging**
|
|
2444
|
+
- Structured JSON logs with: timestamp, level, requestId, userId, duration, error
|
|
2445
|
+
- Log levels: ERROR (alerts), WARN (investigate), INFO (audit trail), DEBUG (dev only)
|
|
2446
|
+
- Never log: passwords, tokens, PII, full request/response bodies
|
|
2447
|
+
- Correlation ID: propagate through all async operations and service calls
|
|
2448
|
+
|
|
2449
|
+
**2. Metrics**
|
|
2450
|
+
- Key metrics to track: {{metrics}}
|
|
2451
|
+
- USE method for each service: Utilization, Saturation, Errors
|
|
2452
|
+
- Expose a /metrics endpoint in Prometheus format
|
|
2453
|
+
|
|
2454
|
+
**3. Traces**
|
|
2455
|
+
- Distributed tracing across: {{services}}
|
|
2456
|
+
- Trace: HTTP request \u2192 service \u2192 DB query \u2192 external API call
|
|
2457
|
+
- Sampling: 100% in dev, {{traceSampling}}% in production
|
|
2458
|
+
|
|
2459
|
+
Alerting rules:
|
|
2460
|
+
- P99 latency > 2s \u2192 page oncall
|
|
2461
|
+
- Error rate > 1% \u2192 alert
|
|
2462
|
+
- Disk > 85% \u2192 warning
|
|
2463
|
+
|
|
2464
|
+
Provide: logger setup, middleware instrumentation, and health check endpoint.`,
|
|
2465
|
+
contextSchema: [
|
|
2466
|
+
{ key: "monitoringStack", label: "Monitoring stack", type: "select", options: ["Datadog", "Grafana + Prometheus", "New Relic", "AWS CloudWatch", "OpenTelemetry (vendor-neutral)"], default: "OpenTelemetry (vendor-neutral)" },
|
|
2467
|
+
{ key: "metrics", label: "Key metrics", type: "text", default: "request rate, error rate, p50/p95/p99 latency, DB pool usage" },
|
|
2468
|
+
{ key: "services", label: "Services to trace", type: "text", placeholder: "API server, PostgreSQL, Redis, Stripe API" },
|
|
2469
|
+
{ key: "traceSampling", label: "Trace sampling % in production", type: "text", default: "10" }
|
|
2470
|
+
],
|
|
2471
|
+
tags: ["monitoring", "logging", "observability", "devops"],
|
|
2472
|
+
version: "1.0.0"
|
|
2473
|
+
},
|
|
2474
|
+
{
|
|
2475
|
+
id: "devops/deploy-checklist",
|
|
2476
|
+
name: "Deploy Checklist",
|
|
2477
|
+
category: "devops",
|
|
2478
|
+
description: "Generate a pre-deployment and rollback checklist",
|
|
2479
|
+
template: `Generate a deployment checklist for {{project.name}} deploying to {{environment}}.
|
|
2480
|
+
|
|
2481
|
+
Change being deployed: {{changeDescription}}
|
|
2482
|
+
Deploy method: {{deployMethod}}
|
|
2483
|
+
|
|
2484
|
+
**Pre-deploy checklist**
|
|
2485
|
+
- [ ] All tests pass (unit, integration, E2E)
|
|
2486
|
+
- [ ] Migration plan reviewed (is it backward-compatible with current production code?)
|
|
2487
|
+
- [ ] Feature flags in place for risky changes
|
|
2488
|
+
- [ ] Runbook updated for new operational behavior
|
|
2489
|
+
- [ ] On-call engineer notified of deploy window
|
|
2490
|
+
- [ ] Rollback procedure documented and tested
|
|
2491
|
+
- [ ] Dependencies updated and audited for CVEs
|
|
2492
|
+
- [ ] Performance tested under production-like load if traffic-path change
|
|
2493
|
+
|
|
2494
|
+
**During deploy**
|
|
2495
|
+
- [ ] Deploy to staging first; smoke test critical paths
|
|
2496
|
+
- [ ] Monitor error rate and p99 latency during rollout
|
|
2497
|
+
- [ ] Watch for increased DB CPU / slow queries from migrations
|
|
2498
|
+
|
|
2499
|
+
**Post-deploy verification**
|
|
2500
|
+
- [ ] Health check endpoints return 200
|
|
2501
|
+
- [ ] Run smoke test against production: {{smokeTests}}
|
|
2502
|
+
- [ ] Verify new feature works as expected in production
|
|
2503
|
+
|
|
2504
|
+
**Rollback procedure for {{deployMethod}}**:
|
|
2505
|
+
{{rollbackSteps}}
|
|
2506
|
+
|
|
2507
|
+
**Estimated deploy risk**: {{riskLevel}} \u2014 based on scope of change`,
|
|
2508
|
+
contextSchema: [
|
|
2509
|
+
{ key: "environment", label: "Target environment", type: "select", options: ["staging", "production", "canary"], default: "production" },
|
|
2510
|
+
{ key: "changeDescription", label: "What is being deployed", type: "text", placeholder: "New checkout flow with Stripe integration" },
|
|
2511
|
+
{ key: "deployMethod", label: "Deploy method", type: "select", options: ["blue/green", "rolling update", "canary", "in-place"], default: "rolling update" },
|
|
2512
|
+
{ key: "smokeTests", label: "Post-deploy smoke tests", type: "text", placeholder: "login, create order, view dashboard" },
|
|
2513
|
+
{ key: "rollbackSteps", label: "Rollback steps", type: "text", placeholder: "git revert + redeploy, then run down migrations" },
|
|
2514
|
+
{ key: "riskLevel", label: "Risk level", type: "select", options: ["low", "medium", "high", "critical"], default: "medium" }
|
|
2515
|
+
],
|
|
2516
|
+
tags: ["deployment", "devops", "checklist", "release"],
|
|
2517
|
+
version: "1.0.0"
|
|
2518
|
+
},
|
|
2519
|
+
// ── Academic ──────────────────────────────────────────────────────────────────
|
|
2520
|
+
{
|
|
2521
|
+
id: "academic/complexity-analysis",
|
|
2522
|
+
name: "Complexity Analysis",
|
|
2523
|
+
category: "academic",
|
|
2524
|
+
description: "Analyze time and space complexity of an algorithm",
|
|
2525
|
+
template: `Analyze the time and space complexity of the following algorithm in {{project.language}}.
|
|
2526
|
+
|
|
2527
|
+
Algorithm: {{algorithm}}
|
|
2528
|
+
Code:
|
|
2529
|
+
{{code}}
|
|
2530
|
+
|
|
2531
|
+
Provide:
|
|
2532
|
+
1. **Time complexity** \u2014 worst case, best case, average case with Big-O notation
|
|
2533
|
+
2. **Space complexity** \u2014 auxiliary space used (excluding input)
|
|
2534
|
+
3. **Step-by-step derivation** \u2014 show how you counted operations, don't just state the answer
|
|
2535
|
+
4. **Dominant terms** \u2014 which loops/recursion drives the complexity?
|
|
2536
|
+
5. **Comparison** \u2014 is there a known better algorithm for this problem? What is its complexity?
|
|
2537
|
+
6. **When it matters** \u2014 at what input size (n) does this become a problem in practice?
|
|
2538
|
+
|
|
2539
|
+
Use concrete examples: for n=10, n=1000, n=1,000,000 \u2014 approximately how many operations?`,
|
|
2540
|
+
contextSchema: [
|
|
2541
|
+
{ key: "algorithm", label: "Algorithm name", type: "text", placeholder: "Bubble sort" },
|
|
2542
|
+
{ key: "code", label: "Paste your code here", type: "text", placeholder: "def bubble_sort(arr): ..." }
|
|
2543
|
+
],
|
|
2544
|
+
tags: ["complexity", "big-o", "academic", "algorithms"],
|
|
2545
|
+
version: "1.0.0"
|
|
2546
|
+
},
|
|
2547
|
+
{
|
|
2548
|
+
id: "academic/algorithm-explain",
|
|
2549
|
+
name: "Algorithm Explanation",
|
|
2550
|
+
category: "academic",
|
|
2551
|
+
description: "Get a step-by-step explanation of an algorithm",
|
|
2552
|
+
template: `Explain the {{algorithm}} algorithm to a {{project.studentLevel}} computer science student.
|
|
2553
|
+
|
|
2554
|
+
Structure the explanation as:
|
|
2555
|
+
1. **Problem it solves** \u2014 one sentence, concrete
|
|
2556
|
+
2. **Intuition** \u2014 explain the core idea without code (analogy or visual)
|
|
2557
|
+
3. **Step-by-step walkthrough** \u2014 trace through this example: {{example}}
|
|
2558
|
+
4. **Pseudocode** \u2014 language-agnostic, focusing on logic not syntax
|
|
2559
|
+
5. **Implementation in {{project.language}}** \u2014 clean, commented code
|
|
2560
|
+
6. **Complexity** \u2014 time and space, with brief justification
|
|
2561
|
+
7. **When to use it** \u2014 and when NOT to use it (what's the alternative?)
|
|
2562
|
+
8. **Defense question** \u2014 "What would a professor ask you about this?"
|
|
2563
|
+
|
|
2564
|
+
Use concrete numbers, not vague descriptions. Show the state of data structures at each step.`,
|
|
2565
|
+
contextSchema: [
|
|
2566
|
+
{ key: "algorithm", label: "Algorithm name", type: "text", placeholder: "Binary search" },
|
|
2567
|
+
{ key: "example", label: "Concrete example input", type: "text", placeholder: "array [1, 3, 5, 7, 9], searching for 7" }
|
|
2568
|
+
],
|
|
2569
|
+
tags: ["algorithms", "explanation", "academic"],
|
|
2570
|
+
version: "1.0.0"
|
|
2571
|
+
},
|
|
2572
|
+
{
|
|
2573
|
+
id: "academic/design-pattern",
|
|
2574
|
+
name: "Design Pattern",
|
|
2575
|
+
category: "academic",
|
|
2576
|
+
description: "Explain and implement a design pattern",
|
|
2577
|
+
template: `Explain the {{pattern}} design pattern to a {{project.studentLevel}} developer and implement it in {{project.language}}.
|
|
2578
|
+
|
|
2579
|
+
Include:
|
|
2580
|
+
1. **Category** \u2014 Creational, Structural, or Behavioral
|
|
2581
|
+
2. **Problem it solves** \u2014 what goes wrong without it?
|
|
2582
|
+
3. **Structure** \u2014 participants, their roles, and relationships
|
|
2583
|
+
4. **Implementation** \u2014 complete {{project.language}} code for {{useCase}}
|
|
2584
|
+
5. **Before vs. After** \u2014 show the messy code it replaces
|
|
2585
|
+
6. **Trade-offs** \u2014 when to use it vs. when it's overkill
|
|
2586
|
+
7. **Real-world examples** \u2014 name 2 places this is used in popular frameworks/libraries
|
|
2587
|
+
8. **Common mistakes** \u2014 what do beginners misunderstand?
|
|
2588
|
+
{{#if (eq project.type "student")}}9. **Defense question** \u2014 "What would your professor ask?"{{/if}}`,
|
|
2589
|
+
contextSchema: [
|
|
2590
|
+
{ key: "pattern", label: "Pattern name", type: "text", placeholder: "Observer" },
|
|
2591
|
+
{ key: "useCase", label: "Your specific use case", type: "text", placeholder: "event system for a game where multiple UI components react to game state changes" }
|
|
2592
|
+
],
|
|
2593
|
+
tags: ["design-patterns", "oop", "academic"],
|
|
2594
|
+
version: "1.0.0"
|
|
2595
|
+
},
|
|
2596
|
+
{
|
|
2597
|
+
id: "academic/pseudocode-to-code",
|
|
2598
|
+
name: "Pseudocode to Code",
|
|
2599
|
+
category: "academic",
|
|
2600
|
+
description: "Translate pseudocode or a description into working code",
|
|
2601
|
+
template: `Translate the following pseudocode/description into working {{project.language}} code.
|
|
2602
|
+
|
|
2603
|
+
Pseudocode:
|
|
2604
|
+
{{pseudocode}}
|
|
2605
|
+
|
|
2606
|
+
Requirements:
|
|
2607
|
+
- Implement exactly what the pseudocode describes \u2014 no extra features
|
|
2608
|
+
- Variable and function names should be descriptive, not p1, p2, x, y
|
|
2609
|
+
- Handle edge cases mentioned in the pseudocode
|
|
2610
|
+
- Add type annotations (for typed languages)
|
|
2611
|
+
|
|
2612
|
+
After the implementation, provide:
|
|
2613
|
+
1. **What it does** \u2014 one sentence
|
|
2614
|
+
2. **Example run** \u2014 trace through with: {{example}}
|
|
2615
|
+
3. **Edge cases handled** \u2014 what happens with empty input, negative numbers, etc.
|
|
2616
|
+
4. **What a student should be able to explain** \u2014 3 specific questions a professor might ask`,
|
|
2617
|
+
contextSchema: [
|
|
2618
|
+
{ key: "pseudocode", label: "Your pseudocode or description", type: "text", placeholder: "function fibonacci(n):\n if n <= 1 return n\n return fibonacci(n-1) + fibonacci(n-2)" },
|
|
2619
|
+
{ key: "example", label: "Example input to trace", type: "text", placeholder: "fibonacci(5)" }
|
|
2620
|
+
],
|
|
2621
|
+
tags: ["pseudocode", "implementation", "academic"],
|
|
2622
|
+
version: "1.0.0"
|
|
2623
|
+
},
|
|
2624
|
+
{
|
|
2625
|
+
id: "academic/data-structure",
|
|
2626
|
+
name: "Data Structure Guide",
|
|
2627
|
+
category: "academic",
|
|
2628
|
+
description: "Deep explanation and implementation of a data structure",
|
|
2629
|
+
template: `Explain the {{dataStructure}} data structure to a {{project.studentLevel}} student and implement it in {{project.language}}.
|
|
2630
|
+
|
|
2631
|
+
Structure the guide:
|
|
2632
|
+
1. **What problem does it solve?** \u2014 why does this structure exist? What's awkward without it?
|
|
2633
|
+
2. **Mental model** \u2014 the simplest analogy (physical world or everyday experience)
|
|
2634
|
+
3. **Anatomy** \u2014 the key components: nodes, pointers, invariants that must always hold
|
|
2635
|
+
4. **Core operations** with complexity:
|
|
2636
|
+
- Insert: how + O(?)
|
|
2637
|
+
- Delete: how + O(?)
|
|
2638
|
+
- Search/Access: how + O(?)
|
|
2639
|
+
- Any structure-specific operations (e.g., push/pop, enqueue/dequeue, heapify)
|
|
2640
|
+
5. **Full implementation in {{project.language}}** \u2014 including all core operations
|
|
2641
|
+
6. **Step-by-step trace** \u2014 walk through this example: {{example}}
|
|
2642
|
+
7. **When to use it** \u2014 and the alternatives (what data structure competes with it and when?)
|
|
2643
|
+
8. **Common bugs** \u2014 what mistakes do beginners make when implementing it?
|
|
2644
|
+
{{#if (eq project.type "student")}}9. **Defense questions** \u2014 3 questions a professor would ask{{/if}}`,
|
|
2645
|
+
contextSchema: [
|
|
2646
|
+
{ key: "dataStructure", label: "Data structure", type: "text", placeholder: "Binary Search Tree" },
|
|
2647
|
+
{ key: "example", label: "Example to trace through", type: "text", placeholder: "Insert 5, 3, 7, 1, 4 then delete 3" }
|
|
2648
|
+
],
|
|
2649
|
+
tags: ["data-structures", "academic", "algorithms"],
|
|
2650
|
+
version: "1.0.0"
|
|
2651
|
+
},
|
|
2652
|
+
{
|
|
2653
|
+
id: "academic/system-design",
|
|
2654
|
+
name: "System Design",
|
|
2655
|
+
category: "academic",
|
|
2656
|
+
description: "Walk through a simplified system design problem",
|
|
2657
|
+
template: `Walk through a system design for {{system}} appropriate for a {{project.studentLevel}} student.
|
|
2658
|
+
|
|
2659
|
+
Scale requirements: {{scale}}
|
|
2660
|
+
Key features: {{features}}
|
|
2661
|
+
|
|
2662
|
+
Structure (adapted for student level):
|
|
2663
|
+
|
|
2664
|
+
**1. Requirements clarification**
|
|
2665
|
+
- Functional requirements: what the system must do
|
|
2666
|
+
- Non-functional requirements: scale, availability, consistency needs
|
|
2667
|
+
|
|
2668
|
+
**2. Capacity estimation**
|
|
2669
|
+
- Traffic: {{scale}} \u2014 requests/second estimate
|
|
2670
|
+
- Storage: data size estimate for 1 year
|
|
2671
|
+
- Bandwidth: read vs write ratio
|
|
2672
|
+
|
|
2673
|
+
**3. High-level design**
|
|
2674
|
+
- Components and their responsibilities
|
|
2675
|
+
- Data flow diagram (describe in text/ASCII)
|
|
2676
|
+
- Database choice and why (SQL vs NoSQL for this use case)
|
|
2677
|
+
|
|
2678
|
+
**4. Deep dive on the hardest part**
|
|
2679
|
+
- The component with the most design decisions: {{hardestPart}}
|
|
2680
|
+
- Trade-offs made and why
|
|
2681
|
+
|
|
2682
|
+
**5. Bottlenecks and scaling**
|
|
2683
|
+
- Where will this break first as scale increases?
|
|
2684
|
+
- One solution for each bottleneck
|
|
2685
|
+
|
|
2686
|
+
**6. What you'd do differently with more time**
|
|
2687
|
+
|
|
2688
|
+
Keep explanations concrete \u2014 use numbers, not vague terms like "a lot of traffic".`,
|
|
2689
|
+
contextSchema: [
|
|
2690
|
+
{ key: "system", label: "System to design", type: "text", placeholder: "URL shortener like bit.ly" },
|
|
2691
|
+
{ key: "scale", label: "Scale", type: "text", placeholder: "1M URLs created/day, 100M redirects/day" },
|
|
2692
|
+
{ key: "features", label: "Key features", type: "text", placeholder: "shorten URL, redirect, track click analytics, expiry" },
|
|
2693
|
+
{ key: "hardestPart", label: "Hardest design part", type: "text", placeholder: "generating unique short codes at high write volume" }
|
|
2694
|
+
],
|
|
2695
|
+
tags: ["system-design", "architecture", "academic", "interview"],
|
|
2696
|
+
version: "1.0.0"
|
|
2697
|
+
},
|
|
2698
|
+
{
|
|
2699
|
+
id: "academic/recursion",
|
|
2700
|
+
name: "Recursion Explained",
|
|
2701
|
+
category: "academic",
|
|
2702
|
+
description: "Visual explanation of recursive thinking and implementation",
|
|
2703
|
+
template: `Explain recursive thinking and solve {{problem}} recursively in {{project.language}}.
|
|
2704
|
+
|
|
2705
|
+
Level: {{project.studentLevel}}
|
|
2706
|
+
|
|
2707
|
+
Structure:
|
|
2708
|
+
1. **The core idea** \u2014 base case + recursive case in plain language (before any code)
|
|
2709
|
+
2. **The call stack** \u2014 draw (in ASCII) the call stack for this input: {{example}}
|
|
2710
|
+
- Show each frame: function name, parameters, return value
|
|
2711
|
+
- Show when frames are popped (where the "magic" happens)
|
|
2712
|
+
3. **Implementation** \u2014 clean recursive solution with comments on WHY each line exists
|
|
2713
|
+
4. **Trace table** \u2014 step-by-step execution for {{example}}
|
|
2714
|
+
5. **Common mistakes**:
|
|
2715
|
+
- Forgetting the base case (infinite recursion)
|
|
2716
|
+
- Wrong return value
|
|
2717
|
+
- Mutating shared state across calls
|
|
2718
|
+
6. **Memoization** \u2014 if this has overlapping subproblems, show the memoized version
|
|
2719
|
+
7. **Iterative equivalent** \u2014 rewrite using a stack/loop; explain the trade-off
|
|
2720
|
+
8. **Complexity** \u2014 time and space, including call stack space
|
|
2721
|
+
|
|
2722
|
+
The "aha moment": recursive functions trust that the recursive call works \u2014 just define what the function does for one case.`,
|
|
2723
|
+
contextSchema: [
|
|
2724
|
+
{ key: "problem", label: "Problem to solve recursively", type: "text", placeholder: "merge sort" },
|
|
2725
|
+
{ key: "example", label: "Example input to trace", type: "text", placeholder: "[5, 2, 8, 1, 9]" }
|
|
2726
|
+
],
|
|
2727
|
+
tags: ["recursion", "algorithms", "academic"],
|
|
2728
|
+
version: "1.0.0"
|
|
2729
|
+
},
|
|
2730
|
+
{
|
|
2731
|
+
id: "academic/sorting",
|
|
2732
|
+
name: "Sorting Algorithms",
|
|
2733
|
+
category: "academic",
|
|
2734
|
+
description: "Compare sorting algorithms and implement the right one for the use case",
|
|
2735
|
+
template: `Compare sorting algorithms and implement the best one for {{useCase}} in {{project.language}}.
|
|
2736
|
+
|
|
2737
|
+
Constraint: {{constraint}}
|
|
2738
|
+
|
|
2739
|
+
Comparison table for the algorithms most relevant to this use case:
|
|
2740
|
+
|
|
2741
|
+
| Algorithm | Best | Average | Worst | Space | Stable? | When to use |
|
|
2742
|
+
|-----------|------|---------|-------|-------|---------|-------------|
|
|
2743
|
+
(fill in for: bubble, insertion, selection, merge, quick, heap, counting, radix \u2014 only include the ones relevant to {{useCase}})
|
|
2744
|
+
|
|
2745
|
+
**For {{useCase}}, the best choice is {{algorithm}} because:**
|
|
2746
|
+
- [Justify based on constraints and data properties]
|
|
2747
|
+
|
|
2748
|
+
**Full implementation in {{project.language}}:**
|
|
2749
|
+
- The algorithm with comments
|
|
2750
|
+
- Step-by-step trace on: {{example}}
|
|
2751
|
+
|
|
2752
|
+
**When the built-in sort beats everything:**
|
|
2753
|
+
- Language built-in sorts (Array.sort, sorted(), Collections.sort) are highly optimized
|
|
2754
|
+
- Only roll your own when you need: stable sort with custom comparator, partial sort, or external sort
|
|
2755
|
+
|
|
2756
|
+
**Debugging tip:** write a test that verifies: sorted output matches Array.from(input).sort()`,
|
|
2757
|
+
contextSchema: [
|
|
2758
|
+
{ key: "useCase", label: "Use case / constraint", type: "text", placeholder: "sorting 10k user records by last name" },
|
|
2759
|
+
{ key: "constraint", label: "Key constraint", type: "text", placeholder: "must be stable (preserve original order of ties), memory limited" },
|
|
2760
|
+
{ key: "example", label: "Example input", type: "text", placeholder: "[64, 25, 12, 22, 11]" }
|
|
2761
|
+
],
|
|
2762
|
+
tags: ["sorting", "algorithms", "academic", "complexity"],
|
|
2763
|
+
version: "1.0.0"
|
|
2764
|
+
},
|
|
2765
|
+
// ── Review ────────────────────────────────────────────────────────────────────
|
|
2766
|
+
{
|
|
2767
|
+
id: "review/code-review",
|
|
2768
|
+
name: "Code Review",
|
|
2769
|
+
category: "review",
|
|
2770
|
+
description: "Full code review for correctness, style, and best practices",
|
|
2771
|
+
template: `Perform a thorough code review of the following {{project.language}} code.
|
|
2772
|
+
|
|
2773
|
+
Context: {{context}}
|
|
2774
|
+
|
|
2775
|
+
Code:
|
|
2776
|
+
{{code}}
|
|
2777
|
+
|
|
2778
|
+
Review dimensions:
|
|
2779
|
+
1. **Correctness** \u2014 does it do what it claims? Are there bugs?
|
|
2780
|
+
2. **Edge cases** \u2014 what inputs would break it?
|
|
2781
|
+
3. **Security** \u2014 injection, auth bypass, data exposure, secret leaks
|
|
2782
|
+
4. **Performance** \u2014 O(n\xB2) loops, N+1 queries, unnecessary allocations
|
|
2783
|
+
5. **Readability** \u2014 naming, structure, comments (are they accurate?)
|
|
2784
|
+
6. **Testability** \u2014 is it easy to unit test? Hard dependencies?
|
|
2785
|
+
7. **Maintainability** \u2014 what breaks when requirements change?
|
|
2786
|
+
{{#if isStudent}}8. **Academic integrity** \u2014 does the complexity match a {{project.studentLevel}} student? Could they explain every line?{{/if}}
|
|
2787
|
+
|
|
2788
|
+
Format: severity [CRITICAL|MAJOR|MINOR|NIT] + file:line + explanation + suggested fix`,
|
|
2789
|
+
contextSchema: [
|
|
2790
|
+
{ key: "code", label: "Code to review", type: "text", placeholder: "Paste your code here..." },
|
|
2791
|
+
{ key: "context", label: "Context / what it does", type: "text", placeholder: "Authentication service for a REST API" },
|
|
2792
|
+
{ key: "isStudent", label: "Academic integrity check?", type: "boolean", default: false }
|
|
2793
|
+
],
|
|
2794
|
+
tags: ["code-review", "review", "quality"],
|
|
2795
|
+
version: "1.0.0"
|
|
2796
|
+
},
|
|
2797
|
+
{
|
|
2798
|
+
id: "review/security-audit",
|
|
2799
|
+
name: "Security Audit",
|
|
2800
|
+
category: "review",
|
|
2801
|
+
description: "Security audit for OWASP Top 10 vulnerabilities",
|
|
2802
|
+
template: `Perform a security audit of the following {{project.language}} code{{#if project.framework}} using {{project.framework}}{{/if}}.
|
|
2803
|
+
|
|
2804
|
+
Code / feature: {{description}}
|
|
2805
|
+
|
|
2806
|
+
Check for OWASP Top 10 and beyond:
|
|
2807
|
+
1. **Injection** \u2014 SQL, NoSQL, command, LDAP injection
|
|
2808
|
+
2. **Broken Authentication** \u2014 weak sessions, token storage, password policies
|
|
2809
|
+
3. **Sensitive Data Exposure** \u2014 PII in logs, unencrypted data at rest/transit
|
|
2810
|
+
4. **Broken Access Control** \u2014 missing authorization checks, IDOR
|
|
2811
|
+
5. **Security Misconfiguration** \u2014 default creds, verbose errors, open CORS
|
|
2812
|
+
6. **XSS** \u2014 reflected, stored, DOM-based
|
|
2813
|
+
7. **CSRF** \u2014 state-changing requests without CSRF tokens
|
|
2814
|
+
8. **Insecure Dependencies** \u2014 known CVEs in dependencies
|
|
2815
|
+
9. **Insufficient Logging** \u2014 failed logins, privilege changes not logged
|
|
2816
|
+
|
|
2817
|
+
For each finding: [CRITICAL|HIGH|MEDIUM|LOW] + attack vector + proof-of-concept + remediation`,
|
|
2818
|
+
contextSchema: [
|
|
2819
|
+
{ key: "description", label: "Feature / code to audit", type: "text", placeholder: "User authentication and session management" }
|
|
2820
|
+
],
|
|
2821
|
+
tags: ["security", "audit", "owasp"],
|
|
2822
|
+
version: "1.0.0"
|
|
2823
|
+
},
|
|
2824
|
+
{
|
|
2825
|
+
id: "review/performance-review",
|
|
2826
|
+
name: "Performance Review",
|
|
2827
|
+
category: "review",
|
|
2828
|
+
description: "Profile and identify performance bottlenecks in code",
|
|
2829
|
+
template: `Review the following {{project.language}} code for performance issues.
|
|
2830
|
+
|
|
2831
|
+
Context: {{context}}
|
|
2832
|
+
Code:
|
|
2833
|
+
{{code}}
|
|
2834
|
+
|
|
2835
|
+
Analysis dimensions:
|
|
2836
|
+
1. **Algorithmic complexity** \u2014 is there a better algorithm? O(n\xB2) hidden in innocent-looking code?
|
|
2837
|
+
2. **Database** \u2014 N+1 queries, missing indexes, fetching more columns/rows than needed
|
|
2838
|
+
3. **Memory** \u2014 large allocations, memory leaks (unclosed streams, retained event listeners), unintended object retention
|
|
2839
|
+
4. **CPU** \u2014 blocking the event loop (Node.js), synchronous I/O, excessive JSON serialization
|
|
2840
|
+
5. **Network** \u2014 unnecessary round trips, large payloads, missing compression, no caching headers
|
|
2841
|
+
6. **Render performance** (if frontend) \u2014 unnecessary re-renders, missing memoization, layout thrashing
|
|
2842
|
+
|
|
2843
|
+
For each finding:
|
|
2844
|
+
- [CRITICAL|HIGH|MEDIUM|LOW] severity
|
|
2845
|
+
- What is slow and why
|
|
2846
|
+
- Profiling technique to confirm: how to measure it
|
|
2847
|
+
- Optimized code or approach
|
|
2848
|
+
- Expected improvement magnitude
|
|
2849
|
+
|
|
2850
|
+
Profiling tip: measure first, optimize second. Profile with realistic data sizes, not toy examples.`,
|
|
2851
|
+
contextSchema: [
|
|
2852
|
+
{ key: "code", label: "Code to review", type: "text", placeholder: "Paste the code or describe the function..." },
|
|
2853
|
+
{ key: "context", label: "Context (what the code does, current perf numbers)", type: "text", placeholder: "Endpoint that lists orders, takes 3s at 10k orders" }
|
|
2854
|
+
],
|
|
2855
|
+
tags: ["performance", "profiling", "review", "optimization"],
|
|
2856
|
+
version: "1.0.0"
|
|
2857
|
+
},
|
|
2858
|
+
{
|
|
2859
|
+
id: "review/accessibility-audit",
|
|
2860
|
+
name: "Accessibility Audit",
|
|
2861
|
+
category: "review",
|
|
2862
|
+
description: "Audit UI code for WCAG 2.1 accessibility compliance",
|
|
2863
|
+
template: `Perform an accessibility audit of the following {{project.language}} UI code.
|
|
2864
|
+
|
|
2865
|
+
Component/page: {{componentDescription}}
|
|
2866
|
+
Code:
|
|
2867
|
+
{{code}}
|
|
2868
|
+
|
|
2869
|
+
Check against WCAG 2.1 Level AA:
|
|
2870
|
+
|
|
2871
|
+
**Perceivable**
|
|
2872
|
+
- [ ] Images have descriptive alt text (or alt="" if decorative)
|
|
2873
|
+
- [ ] Color contrast ratio \u2265 4.5:1 for normal text, 3:1 for large text
|
|
2874
|
+
- [ ] Content not conveyed by color alone
|
|
2875
|
+
- [ ] Videos have captions
|
|
2876
|
+
|
|
2877
|
+
**Operable**
|
|
2878
|
+
- [ ] All functionality reachable by keyboard (Tab, Enter, Space, Arrow keys)
|
|
2879
|
+
- [ ] No keyboard traps (user can always Tab out)
|
|
2880
|
+
- [ ] Visible focus indicator on interactive elements
|
|
2881
|
+
- [ ] No content that flashes > 3 times/second
|
|
2882
|
+
|
|
2883
|
+
**Understandable**
|
|
2884
|
+
- [ ] Form inputs have visible labels (not just placeholder)
|
|
2885
|
+
- [ ] Error messages identify which field and how to fix it
|
|
2886
|
+
- [ ] Language attribute set on <html>
|
|
2887
|
+
|
|
2888
|
+
**Robust**
|
|
2889
|
+
- [ ] Semantic HTML (button not div for clicks, nav/main/header landmarks)
|
|
2890
|
+
- [ ] ARIA used correctly (role, aria-label, aria-describedby, aria-live)
|
|
2891
|
+
- [ ] Works with screen reader (NVDA/VoiceOver mental walkthrough)
|
|
2892
|
+
|
|
2893
|
+
For each issue: [CRITICAL|MAJOR|MINOR] + specific element + WCAG criterion + fix.`,
|
|
2894
|
+
contextSchema: [
|
|
2895
|
+
{ key: "code", label: "UI code to audit", type: "text", placeholder: "Paste the component or HTML here..." },
|
|
2896
|
+
{ key: "componentDescription", label: "What the component does", type: "text", placeholder: "Login form with email and password fields" }
|
|
2897
|
+
],
|
|
2898
|
+
tags: ["accessibility", "wcag", "a11y", "review"],
|
|
2899
|
+
version: "1.0.0"
|
|
2900
|
+
},
|
|
2901
|
+
{
|
|
2902
|
+
id: "review/api-design",
|
|
2903
|
+
name: "API Design Review",
|
|
2904
|
+
category: "review",
|
|
2905
|
+
description: "Review REST API design for consistency and best practices",
|
|
2906
|
+
template: `Review the following API design for {{project.name}}.
|
|
2907
|
+
|
|
2908
|
+
API endpoints:
|
|
2909
|
+
{{endpoints}}
|
|
2910
|
+
|
|
2911
|
+
Review dimensions:
|
|
2912
|
+
|
|
2913
|
+
**Naming & Structure**
|
|
2914
|
+
- [ ] Resources are nouns, not verbs (/users not /getUsers)
|
|
2915
|
+
- [ ] Consistent pluralization (/users/{id}/orders not mixed)
|
|
2916
|
+
- [ ] Nested resources max 2 levels deep
|
|
2917
|
+
- [ ] Query params for filtering/sorting, path params for identity
|
|
2918
|
+
|
|
2919
|
+
**HTTP Semantics**
|
|
2920
|
+
- [ ] Correct HTTP methods (GET=read, POST=create, PUT=replace, PATCH=partial, DELETE=remove)
|
|
2921
|
+
- [ ] Idempotent operations use PUT/DELETE correctly
|
|
2922
|
+
- [ ] Correct status codes (201 for create, 204 for no-body delete, 409 for conflict)
|
|
2923
|
+
|
|
2924
|
+
**Request/Response**
|
|
2925
|
+
- [ ] Consistent response envelope (data, meta, errors)
|
|
2926
|
+
- [ ] Pagination: cursor-based preferred; always include total count and nextCursor
|
|
2927
|
+
- [ ] Error shape: { error: { code, message, details? } }
|
|
2928
|
+
- [ ] Versioning strategy (URI v1 vs Accept header)
|
|
2929
|
+
|
|
2930
|
+
**Security**
|
|
2931
|
+
- [ ] No sensitive data in URLs (tokens, passwords, PII)
|
|
2932
|
+
- [ ] Rate limiting headers documented
|
|
2933
|
+
- [ ] Auth scheme documented (Bearer JWT)
|
|
2934
|
+
|
|
2935
|
+
For each issue: severity + endpoint + what's wrong + suggested fix.`,
|
|
2936
|
+
contextSchema: [
|
|
2937
|
+
{ key: "endpoints", label: "API endpoints to review", type: "text", placeholder: "GET /getUsers, POST /createOrder/:id, DELETE /user?id=123" }
|
|
2938
|
+
],
|
|
2939
|
+
tags: ["api", "rest", "design", "review"],
|
|
2940
|
+
version: "1.0.0"
|
|
2941
|
+
},
|
|
2942
|
+
// ── General ───────────────────────────────────────────────────────────────────
|
|
2943
|
+
{
|
|
2944
|
+
id: "general/refactor",
|
|
2945
|
+
name: "Refactor Plan",
|
|
2946
|
+
category: "general",
|
|
2947
|
+
description: "Plan a safe, incremental refactoring",
|
|
2948
|
+
template: `Plan a safe refactoring of {{target}} in {{project.language}}.
|
|
2949
|
+
|
|
2950
|
+
Current problem: {{problem}}
|
|
2951
|
+
Goal: {{goal}}
|
|
2952
|
+
|
|
2953
|
+
Produce a step-by-step refactoring plan where each step:
|
|
2954
|
+
- Is independently deployable (the codebase works after each step)
|
|
2955
|
+
- Has a clear rollback path
|
|
2956
|
+
- Can be reviewed in a small PR
|
|
2957
|
+
|
|
2958
|
+
Steps should cover:
|
|
2959
|
+
1. Add tests to cover current behavior (before changing anything)
|
|
2960
|
+
2. Incremental structural changes
|
|
2961
|
+
3. Rename/move phase
|
|
2962
|
+
4. Cleanup phase (remove dead code)
|
|
2963
|
+
|
|
2964
|
+
Also flag: which step has the highest risk of regression, and how to detect it?`,
|
|
2965
|
+
contextSchema: [
|
|
2966
|
+
{ key: "target", label: "What to refactor", type: "text", placeholder: "UserController (God class with 800 lines)" },
|
|
2967
|
+
{ key: "problem", label: "Current problem", type: "text", placeholder: "All user logic is in one file, hard to test, one class does too much" },
|
|
2968
|
+
{ key: "goal", label: "Goal after refactoring", type: "text", placeholder: "Split into UserService, UserRepository, UserValidator with clear responsibilities" }
|
|
2969
|
+
],
|
|
2970
|
+
tags: ["refactoring", "code-quality", "general"],
|
|
2971
|
+
version: "1.0.0"
|
|
2972
|
+
},
|
|
2973
|
+
{
|
|
2974
|
+
id: "general/debug-session",
|
|
2975
|
+
name: "Debug Session",
|
|
2976
|
+
category: "general",
|
|
2977
|
+
description: "Structured debugging guide for a bug or unexpected behavior",
|
|
2978
|
+
template: `Guide me through debugging the following issue in {{project.language}}{{#if project.framework}} / {{project.framework}}{{/if}}.
|
|
2979
|
+
|
|
2980
|
+
Bug description: {{bugDescription}}
|
|
2981
|
+
|
|
2982
|
+
Symptom: {{symptom}}
|
|
2983
|
+
Expected: {{expected}}
|
|
2984
|
+
Actual: {{actual}}
|
|
2985
|
+
|
|
2986
|
+
Apply the scientific debugging method:
|
|
2987
|
+
1. **Hypothesis** \u2014 what are the 3 most likely root causes?
|
|
2988
|
+
2. **Observation points** \u2014 where to add logging/breakpoints to confirm/deny each hypothesis
|
|
2989
|
+
3. **Bisection** \u2014 how to narrow the problem to the smallest failing case
|
|
2990
|
+
4. **Common traps** \u2014 async timing issues, mutation bugs, off-by-one, encoding issues related to this symptom
|
|
2991
|
+
5. **Fix** \u2014 once root cause is identified, what's the minimal fix?
|
|
2992
|
+
6. **Prevention** \u2014 what test would have caught this before it shipped?`,
|
|
2993
|
+
contextSchema: [
|
|
2994
|
+
{ key: "bugDescription", label: "Bug description", type: "text", placeholder: "Login fails silently for users with special characters in their email" },
|
|
2995
|
+
{ key: "symptom", label: "Symptom", type: "text", placeholder: "POST /login returns 200 but no token is returned" },
|
|
2996
|
+
{ key: "expected", label: "Expected behavior", type: "text", placeholder: 'Returns { token: "..." } and redirects to dashboard' },
|
|
2997
|
+
{ key: "actual", label: "Actual behavior", type: "text", placeholder: "Returns { } and shows a blank page" }
|
|
2998
|
+
],
|
|
2999
|
+
tags: ["debugging", "bug", "general"],
|
|
3000
|
+
version: "1.0.0"
|
|
3001
|
+
},
|
|
3002
|
+
{
|
|
3003
|
+
id: "general/documentation",
|
|
3004
|
+
name: "Documentation",
|
|
3005
|
+
category: "general",
|
|
3006
|
+
description: "Generate developer documentation for code or a system",
|
|
3007
|
+
template: `Write developer documentation for {{target}} in {{project.language}}.
|
|
3008
|
+
|
|
3009
|
+
Type of documentation needed: {{docType}}
|
|
3010
|
+
|
|
3011
|
+
Code/system description: {{description}}
|
|
3012
|
+
|
|
3013
|
+
Documentation should include:
|
|
3014
|
+
- **Overview** \u2014 what it is, what problem it solves, who uses it
|
|
3015
|
+
- **Quick start** \u2014 minimum code to get it working in 3 minutes
|
|
3016
|
+
- **API reference** \u2014 every public function/method: signature, parameters, return value, throws
|
|
3017
|
+
- **Examples** \u2014 at least 3 realistic usage examples
|
|
3018
|
+
- **Configuration** \u2014 all options, their types, defaults, and effects
|
|
3019
|
+
- **Common errors** \u2014 top 5 errors users hit and how to fix them
|
|
3020
|
+
- **Architecture note** \u2014 key design decisions and why
|
|
3021
|
+
|
|
3022
|
+
Tone: peer developer, not corporate. Assume they can read code but don't know YOUR code.`,
|
|
3023
|
+
contextSchema: [
|
|
3024
|
+
{ key: "target", label: "What to document", type: "text", placeholder: "BrainForgeClient npm package" },
|
|
3025
|
+
{ key: "description", label: "What it does", type: "text", placeholder: "A client library for the BrainForge AI API" },
|
|
3026
|
+
{ key: "docType", label: "Documentation type", type: "select", options: ["README", "API reference", "tutorial", "architecture doc"], default: "README" }
|
|
3027
|
+
],
|
|
3028
|
+
tags: ["documentation", "readme", "general"],
|
|
3029
|
+
version: "1.0.0"
|
|
3030
|
+
},
|
|
3031
|
+
{
|
|
3032
|
+
id: "general/architecture-decision",
|
|
3033
|
+
name: "Architecture Decision Record",
|
|
3034
|
+
category: "general",
|
|
3035
|
+
description: "Document a technical decision as a structured ADR",
|
|
3036
|
+
template: `Write an Architecture Decision Record (ADR) for the following decision in {{project.name}}.
|
|
3037
|
+
|
|
3038
|
+
Decision: {{decision}}
|
|
3039
|
+
Date: today
|
|
3040
|
+
|
|
3041
|
+
# ADR: {{decision}}
|
|
3042
|
+
|
|
3043
|
+
## Status
|
|
3044
|
+
Proposed
|
|
3045
|
+
|
|
3046
|
+
## Context
|
|
3047
|
+
{{context}}
|
|
3048
|
+
|
|
3049
|
+
What is the problem or opportunity that requires a decision? What forces are at play (technical constraints, team skills, budget, deadlines)?
|
|
3050
|
+
|
|
3051
|
+
## Decision
|
|
3052
|
+
We will {{chosenOption}}.
|
|
3053
|
+
|
|
3054
|
+
## Options Considered
|
|
3055
|
+
|
|
3056
|
+
**Option A: {{optionA}}**
|
|
3057
|
+
- Pros: [analyze]
|
|
3058
|
+
- Cons: [analyze]
|
|
3059
|
+
- Risk: [what could go wrong]
|
|
3060
|
+
|
|
3061
|
+
**Option B: {{optionB}}**
|
|
3062
|
+
- Pros: [analyze]
|
|
3063
|
+
- Cons: [analyze]
|
|
3064
|
+
- Risk: [what could go wrong]
|
|
3065
|
+
|
|
3066
|
+
{{#if optionC}}**Option C: {{optionC}}**
|
|
3067
|
+
- Pros: [analyze]
|
|
3068
|
+
- Cons: [analyze]{{/if}}
|
|
3069
|
+
|
|
3070
|
+
## Rationale
|
|
3071
|
+
Why {{chosenOption}} over the alternatives. Be specific \u2014 reference the context forces.
|
|
3072
|
+
|
|
3073
|
+
## Consequences
|
|
3074
|
+
**Positive:** what gets better
|
|
3075
|
+
**Negative:** what gets harder or costs more
|
|
3076
|
+
**Neutral:** things that change but are neither better nor worse
|
|
3077
|
+
|
|
3078
|
+
## Review Trigger
|
|
3079
|
+
This decision should be revisited if: {{reviewTrigger}}`,
|
|
3080
|
+
contextSchema: [
|
|
3081
|
+
{ key: "decision", label: "Decision to document", type: "text", placeholder: "Use PostgreSQL instead of MongoDB for primary data store" },
|
|
3082
|
+
{ key: "context", label: "Context / problem", type: "text", placeholder: "We need to choose a database before starting the data layer" },
|
|
3083
|
+
{ key: "chosenOption", label: "Chosen option", type: "text", placeholder: "PostgreSQL" },
|
|
3084
|
+
{ key: "optionA", label: "Option A", type: "text", placeholder: "PostgreSQL" },
|
|
3085
|
+
{ key: "optionB", label: "Option B", type: "text", placeholder: "MongoDB" },
|
|
3086
|
+
{ key: "optionC", label: "Option C (optional)", type: "text", placeholder: "DynamoDB" },
|
|
3087
|
+
{ key: "reviewTrigger", label: "Review trigger", type: "text", placeholder: "query performance degrades below 100ms P99 at 10M records" }
|
|
3088
|
+
],
|
|
3089
|
+
tags: ["adr", "architecture", "decision", "documentation"],
|
|
3090
|
+
version: "1.0.0"
|
|
3091
|
+
},
|
|
3092
|
+
{
|
|
3093
|
+
id: "general/tech-debt",
|
|
3094
|
+
name: "Tech Debt Inventory",
|
|
3095
|
+
category: "general",
|
|
3096
|
+
description: "Identify, prioritize, and plan a tech debt paydown",
|
|
3097
|
+
template: `Create a tech debt inventory and paydown plan for {{project.name}} in {{project.language}}.
|
|
3098
|
+
|
|
3099
|
+
Area of concern: {{area}}
|
|
3100
|
+
|
|
3101
|
+
**Step 1 \u2014 Identify debt items**
|
|
3102
|
+
Analyze the described codebase area for:
|
|
3103
|
+
- Design debt: poor abstractions, wrong patterns, violated SOLID/DRY
|
|
3104
|
+
- Code debt: duplicated logic, dead code, magic numbers, missing types
|
|
3105
|
+
- Test debt: missing tests, brittle tests, slow test suite
|
|
3106
|
+
- Documentation debt: outdated docs, missing ADRs, no onboarding guide
|
|
3107
|
+
- Dependency debt: outdated packages, deprecated APIs, security CVEs
|
|
3108
|
+
- Infrastructure debt: manual processes that should be automated
|
|
3109
|
+
|
|
3110
|
+
**Step 2 \u2014 Prioritize by impact \xD7 effort**
|
|
3111
|
+
For each item:
|
|
3112
|
+
| Debt Item | Type | Impact (1-5) | Effort (days) | Risk if ignored | Priority |
|
|
3113
|
+
|-----------|------|--------------|---------------|-----------------|----------|
|
|
3114
|
+
|
|
3115
|
+
**Step 3 \u2014 Paydown plan for {{sprint}} sprints**
|
|
3116
|
+
- Items to tackle this sprint (highest impact/effort ratio)
|
|
3117
|
+
- Items to defer with a trigger condition for when to revisit
|
|
3118
|
+
- Items to accept and document as intentional constraints
|
|
3119
|
+
|
|
3120
|
+
**Step 4 \u2014 Prevention**
|
|
3121
|
+
- What process change prevents this class of debt from re-accumulating?
|
|
3122
|
+
- Suggest a definition of done that includes tech debt hygiene`,
|
|
3123
|
+
contextSchema: [
|
|
3124
|
+
{ key: "area", label: "Codebase area with debt", type: "text", placeholder: "Authentication module \u2014 was written in a hackathon, never cleaned up" },
|
|
3125
|
+
{ key: "sprint", label: "Planning horizon (sprints)", type: "text", default: "3" }
|
|
3126
|
+
],
|
|
3127
|
+
tags: ["tech-debt", "refactoring", "planning", "general"],
|
|
3128
|
+
version: "1.0.0"
|
|
3129
|
+
},
|
|
3130
|
+
{
|
|
3131
|
+
id: "general/onboarding",
|
|
3132
|
+
name: "Developer Onboarding",
|
|
3133
|
+
category: "general",
|
|
3134
|
+
description: "Create a new developer onboarding guide for a codebase",
|
|
3135
|
+
template: `Write a developer onboarding guide for {{project.name}}.
|
|
3136
|
+
|
|
3137
|
+
Role being onboarded: {{role}}
|
|
3138
|
+
Stack: {{project.language}}{{#if project.framework}} / {{project.framework}}{{/if}}
|
|
3139
|
+
|
|
3140
|
+
The guide should get a developer from "just joined" to "merged first PR" in {{targetDays}} days.
|
|
3141
|
+
|
|
3142
|
+
**Day 1 \u2014 Environment setup**
|
|
3143
|
+
1. Prerequisites (what must be installed)
|
|
3144
|
+
2. Clone + install: exact commands, not "install dependencies"
|
|
3145
|
+
3. Environment variables: copy .env.example \u2192 .env, document which vars need real values for local dev
|
|
3146
|
+
4. Run the app: exact command + expected output + URL
|
|
3147
|
+
5. Run the tests: expect all green
|
|
3148
|
+
|
|
3149
|
+
**First week \u2014 Mental model**
|
|
3150
|
+
- Architecture overview: the 3 most important concepts to understand first
|
|
3151
|
+
- Key directories and what lives where
|
|
3152
|
+
- How data flows: from HTTP request to database and back
|
|
3153
|
+
- The 3 files to read first
|
|
3154
|
+
|
|
3155
|
+
**First PR checklist**
|
|
3156
|
+
- Good first issues: what type of task is appropriate for the first contribution?
|
|
3157
|
+
- PR process: branch naming, commit format, review expectations
|
|
3158
|
+
- Definition of done for this project
|
|
3159
|
+
|
|
3160
|
+
**Common pitfalls**
|
|
3161
|
+
- Top 3 things that trip up new developers on this codebase
|
|
3162
|
+
- Local gotchas (Windows/Mac differences, port conflicts, etc.)
|
|
3163
|
+
|
|
3164
|
+
**Who to ask for what** \u2014 map topics to team members/channels`,
|
|
3165
|
+
contextSchema: [
|
|
3166
|
+
{ key: "role", label: "Developer role being onboarded", type: "text", placeholder: "junior fullstack developer" },
|
|
3167
|
+
{ key: "targetDays", label: "Days to first PR", type: "text", default: "3" }
|
|
3168
|
+
],
|
|
3169
|
+
tags: ["onboarding", "documentation", "team", "general"],
|
|
3170
|
+
version: "1.0.0"
|
|
3171
|
+
},
|
|
3172
|
+
{
|
|
3173
|
+
id: "general/api-design",
|
|
3174
|
+
name: "API Design",
|
|
3175
|
+
category: "general",
|
|
3176
|
+
description: "Design a RESTful API from scratch with consistent conventions",
|
|
3177
|
+
template: `Design a REST API for {{resource}} in {{project.name}}.
|
|
3178
|
+
|
|
3179
|
+
Purpose: {{purpose}}
|
|
3180
|
+
Consumers: {{consumers}}
|
|
3181
|
+
|
|
3182
|
+
**Resource model**
|
|
3183
|
+
- Primary resource: {{resource}}
|
|
3184
|
+
- Relationships: {{relationships}}
|
|
3185
|
+
- Actions that don't map to CRUD: {{customActions}}
|
|
3186
|
+
|
|
3187
|
+
**Endpoint design**
|
|
3188
|
+
|
|
3189
|
+
For each endpoint provide: method + path + description + request body + response shape + possible error codes
|
|
3190
|
+
|
|
3191
|
+
Standard CRUD:
|
|
3192
|
+
- List (with filtering, sorting, pagination)
|
|
3193
|
+
- Get by ID
|
|
3194
|
+
- Create
|
|
3195
|
+
- Update (full replacement)
|
|
3196
|
+
- Partial update
|
|
3197
|
+
- Delete
|
|
3198
|
+
|
|
3199
|
+
Custom actions: {{customActions}}
|
|
3200
|
+
|
|
3201
|
+
**Conventions to follow**
|
|
3202
|
+
- Response envelope: { data, meta: { total, page, limit } } for lists; { data } for single
|
|
3203
|
+
- Error envelope: { error: { code, message, details? } }
|
|
3204
|
+
- Timestamps: ISO 8601 (2024-01-15T10:30:00Z)
|
|
3205
|
+
- IDs: UUID v4
|
|
3206
|
+
- Soft delete: return 404 for deleted resources (don't expose deleted=true)
|
|
3207
|
+
|
|
3208
|
+
**Versioning strategy**: URI prefix /v1/
|
|
3209
|
+
|
|
3210
|
+
Produce a full OpenAPI-style endpoint list with example request/response JSON.`,
|
|
3211
|
+
contextSchema: [
|
|
3212
|
+
{ key: "resource", label: "Primary resource", type: "text", placeholder: "Blog Post" },
|
|
3213
|
+
{ key: "purpose", label: "API purpose", type: "text", placeholder: "Content management for a blog platform" },
|
|
3214
|
+
{ key: "consumers", label: "API consumers", type: "text", placeholder: "React SPA, iOS app, third-party webhook integrations" },
|
|
3215
|
+
{ key: "relationships", label: "Relationships", type: "text", placeholder: "Post belongs to User, has many Comments and Tags" },
|
|
3216
|
+
{ key: "customActions", label: "Non-CRUD actions", type: "text", placeholder: "publish, unpublish, duplicate" }
|
|
3217
|
+
],
|
|
3218
|
+
tags: ["api", "rest", "design", "openapi"],
|
|
3219
|
+
version: "1.0.0"
|
|
3220
|
+
},
|
|
3221
|
+
{
|
|
3222
|
+
id: "general/incident-postmortem",
|
|
3223
|
+
name: "Incident Post-mortem",
|
|
3224
|
+
category: "general",
|
|
3225
|
+
description: "Write a blameless post-mortem for a production incident",
|
|
3226
|
+
template: `Write a blameless post-mortem for a production incident affecting {{project.name}}.
|
|
3227
|
+
|
|
3228
|
+
Incident summary: {{summary}}
|
|
3229
|
+
Duration: {{duration}}
|
|
3230
|
+
Impact: {{impact}}
|
|
3231
|
+
|
|
3232
|
+
# Post-mortem: {{summary}}
|
|
3233
|
+
|
|
3234
|
+
## Timeline (UTC)
|
|
3235
|
+
| Time | Event |
|
|
3236
|
+
|------|-------|
|
|
3237
|
+
(fill in detection \u2192 diagnosis \u2192 mitigation \u2192 resolution)
|
|
3238
|
+
|
|
3239
|
+
## Impact
|
|
3240
|
+
- Users affected: {{impact}}
|
|
3241
|
+
- Duration: {{duration}}
|
|
3242
|
+
- Services affected: {{services}}
|
|
3243
|
+
|
|
3244
|
+
## Root Cause
|
|
3245
|
+
(The single technical root cause \u2014 not "human error")
|
|
3246
|
+
|
|
3247
|
+
## Contributing Factors
|
|
3248
|
+
(Conditions that made the incident worse or harder to detect)
|
|
3249
|
+
|
|
3250
|
+
## What Went Well
|
|
3251
|
+
- Fast detection
|
|
3252
|
+
- Clear communication
|
|
3253
|
+
- Effective rollback
|
|
3254
|
+
|
|
3255
|
+
## What Went Poorly
|
|
3256
|
+
- (Honest assessment without blame)
|
|
3257
|
+
|
|
3258
|
+
## Action Items
|
|
3259
|
+
| Action | Owner | Due Date | Priority |
|
|
3260
|
+
|--------|-------|----------|----------|
|
|
3261
|
+
| Add alert for [metric] | | | HIGH |
|
|
3262
|
+
| Add test for [scenario] | | | HIGH |
|
|
3263
|
+
| Update runbook for [procedure] | | | MEDIUM |
|
|
3264
|
+
|
|
3265
|
+
## Lessons Learned
|
|
3266
|
+
Three concrete changes that will prevent this class of incident.
|
|
3267
|
+
|
|
3268
|
+
## Blameless Culture Note
|
|
3269
|
+
Systems fail. This post-mortem focuses on fixing systems, not assigning blame. The people involved made reasonable decisions given what they knew at the time.`,
|
|
3270
|
+
contextSchema: [
|
|
3271
|
+
{ key: "summary", label: "Incident summary", type: "text", placeholder: "Database connection pool exhaustion caused 15-minute outage" },
|
|
3272
|
+
{ key: "duration", label: "Incident duration", type: "text", placeholder: "47 minutes" },
|
|
3273
|
+
{ key: "impact", label: "User impact", type: "text", placeholder: "2,300 users unable to login; ~$8k revenue impact" },
|
|
3274
|
+
{ key: "services", label: "Services affected", type: "text", placeholder: "API server, dashboard, mobile app" }
|
|
3275
|
+
],
|
|
3276
|
+
tags: ["incident", "postmortem", "sre", "general"],
|
|
3277
|
+
version: "1.0.0"
|
|
3278
|
+
}
|
|
3279
|
+
];
|
|
3280
|
+
|
|
3281
|
+
// src/skills/registry.ts
|
|
3282
|
+
var SkillRegistry = class {
|
|
3283
|
+
skills = /* @__PURE__ */ new Map();
|
|
3284
|
+
compiled = /* @__PURE__ */ new Map();
|
|
3285
|
+
constructor() {
|
|
3286
|
+
Handlebars2.registerHelper("eq", (a, b) => a === b);
|
|
3287
|
+
Handlebars2.registerHelper("ne", (a, b) => a !== b);
|
|
3288
|
+
for (const skill of BUILT_IN_SKILLS) {
|
|
3289
|
+
this.register(skill);
|
|
3290
|
+
}
|
|
3291
|
+
}
|
|
3292
|
+
register(skill) {
|
|
3293
|
+
this.skills.set(skill.id, skill);
|
|
3294
|
+
this.compiled.set(skill.id, Handlebars2.compile(skill.template));
|
|
3295
|
+
}
|
|
3296
|
+
async loadUserSkills(skillsDir) {
|
|
3297
|
+
try {
|
|
3298
|
+
const entries = await fs6.readdir(skillsDir, { withFileTypes: true });
|
|
3299
|
+
for (const entry of entries) {
|
|
3300
|
+
if (!entry.name.endsWith(".json")) continue;
|
|
3301
|
+
try {
|
|
3302
|
+
const raw = await fs6.readFile(path6.join(skillsDir, entry.name), "utf-8");
|
|
3303
|
+
const skill = JSON.parse(raw);
|
|
3304
|
+
this.register(skill);
|
|
3305
|
+
} catch {
|
|
3306
|
+
}
|
|
3307
|
+
}
|
|
3308
|
+
} catch {
|
|
3309
|
+
}
|
|
3310
|
+
}
|
|
3311
|
+
get(id) {
|
|
3312
|
+
return this.skills.get(id);
|
|
3313
|
+
}
|
|
3314
|
+
list(filter) {
|
|
3315
|
+
let skills = Array.from(this.skills.values());
|
|
3316
|
+
if (filter?.category) skills = skills.filter((s) => s.category === filter.category);
|
|
3317
|
+
if (filter?.tag) skills = skills.filter((s) => s.tags.includes(filter.tag));
|
|
3318
|
+
return skills.sort((a, b) => a.category.localeCompare(b.category) || a.name.localeCompare(b.name));
|
|
3319
|
+
}
|
|
3320
|
+
listByCategory() {
|
|
3321
|
+
const map = /* @__PURE__ */ new Map();
|
|
3322
|
+
for (const skill of this.list()) {
|
|
3323
|
+
const group = map.get(skill.category) ?? [];
|
|
3324
|
+
group.push(skill);
|
|
3325
|
+
map.set(skill.category, group);
|
|
3326
|
+
}
|
|
3327
|
+
return map;
|
|
3328
|
+
}
|
|
3329
|
+
render(id, context) {
|
|
3330
|
+
const template = this.compiled.get(id);
|
|
3331
|
+
if (!template) throw new Error(`Skill "${id}" not found.`);
|
|
3332
|
+
return template(context);
|
|
3333
|
+
}
|
|
3334
|
+
count() {
|
|
3335
|
+
const builtin = BUILT_IN_SKILLS.length;
|
|
3336
|
+
return { builtin, custom: this.skills.size - builtin };
|
|
3337
|
+
}
|
|
3338
|
+
search(query) {
|
|
3339
|
+
const q = query.toLowerCase();
|
|
3340
|
+
return this.list().filter(
|
|
3341
|
+
(s) => s.name.toLowerCase().includes(q) || s.description.toLowerCase().includes(q) || s.tags.some((t) => t.includes(q)) || s.id.toLowerCase().includes(q)
|
|
3342
|
+
);
|
|
3343
|
+
}
|
|
3344
|
+
};
|
|
3345
|
+
|
|
3346
|
+
// src/adapters/types.ts
|
|
3347
|
+
var DEFAULT_MODELS = {
|
|
3348
|
+
claude: "claude-sonnet-4-6",
|
|
3349
|
+
openai: "gpt-4o",
|
|
3350
|
+
gemini: "gemini-1.5-pro",
|
|
3351
|
+
deepseek: "deepseek-chat"
|
|
3352
|
+
};
|
|
3353
|
+
|
|
3354
|
+
// src/adapters/claude.ts
|
|
3355
|
+
var ClaudeAdapter = class {
|
|
3356
|
+
provider = "claude";
|
|
3357
|
+
model;
|
|
3358
|
+
apiKeyEnv;
|
|
3359
|
+
constructor(config) {
|
|
3360
|
+
this.model = config.model ?? DEFAULT_MODELS.claude;
|
|
3361
|
+
this.apiKeyEnv = config.apiKeyEnv;
|
|
3362
|
+
}
|
|
3363
|
+
isConfigured() {
|
|
3364
|
+
return Boolean(process.env[this.apiKeyEnv]);
|
|
3365
|
+
}
|
|
3366
|
+
async complete(prompt, options = {}) {
|
|
3367
|
+
const apiKey = process.env[this.apiKeyEnv];
|
|
3368
|
+
if (!apiKey) {
|
|
3369
|
+
throw new Error(
|
|
3370
|
+
`Anthropic API key not found. Set the ${this.apiKeyEnv} environment variable.
|
|
3371
|
+
Get your key at https://console.anthropic.com/`
|
|
3372
|
+
);
|
|
3373
|
+
}
|
|
3374
|
+
let Anthropic;
|
|
3375
|
+
try {
|
|
3376
|
+
({ default: Anthropic } = await import("@anthropic-ai/sdk"));
|
|
3377
|
+
} catch {
|
|
3378
|
+
throw new Error(
|
|
3379
|
+
"Anthropic SDK not installed. Run: npm install @anthropic-ai/sdk"
|
|
3380
|
+
);
|
|
3381
|
+
}
|
|
3382
|
+
const client = new Anthropic({ apiKey });
|
|
3383
|
+
const response = await client.messages.create({
|
|
3384
|
+
model: this.model,
|
|
3385
|
+
max_tokens: options.maxTokens ?? 4096,
|
|
3386
|
+
...options.system ? { system: options.system } : {},
|
|
3387
|
+
messages: [{ role: "user", content: prompt }]
|
|
3388
|
+
});
|
|
3389
|
+
const block = response.content[0];
|
|
3390
|
+
return block?.type === "text" ? block.text : "";
|
|
3391
|
+
}
|
|
3392
|
+
};
|
|
3393
|
+
|
|
3394
|
+
// src/adapters/openai.ts
|
|
3395
|
+
var OpenAIAdapter = class {
|
|
3396
|
+
provider = "openai";
|
|
3397
|
+
model;
|
|
3398
|
+
apiKeyEnv;
|
|
3399
|
+
constructor(config) {
|
|
3400
|
+
this.model = config.model ?? DEFAULT_MODELS.openai;
|
|
3401
|
+
this.apiKeyEnv = config.apiKeyEnv;
|
|
3402
|
+
}
|
|
3403
|
+
isConfigured() {
|
|
3404
|
+
return Boolean(process.env[this.apiKeyEnv]);
|
|
3405
|
+
}
|
|
3406
|
+
async complete(prompt, options = {}) {
|
|
3407
|
+
const apiKey = process.env[this.apiKeyEnv];
|
|
3408
|
+
if (!apiKey) {
|
|
3409
|
+
throw new Error(
|
|
3410
|
+
`OpenAI API key not found. Set the ${this.apiKeyEnv} environment variable.
|
|
3411
|
+
Get your key at https://platform.openai.com/api-keys`
|
|
3412
|
+
);
|
|
3413
|
+
}
|
|
3414
|
+
let OpenAI;
|
|
3415
|
+
try {
|
|
3416
|
+
({ default: OpenAI } = await import("openai"));
|
|
3417
|
+
} catch {
|
|
3418
|
+
throw new Error("OpenAI SDK not installed. Run: npm install openai");
|
|
3419
|
+
}
|
|
3420
|
+
const client = new OpenAI({ apiKey });
|
|
3421
|
+
const response = await client.chat.completions.create({
|
|
3422
|
+
model: this.model,
|
|
3423
|
+
max_tokens: options.maxTokens ?? 4096,
|
|
3424
|
+
temperature: options.temperature ?? 0.7,
|
|
3425
|
+
messages: [
|
|
3426
|
+
...options.system ? [{ role: "system", content: options.system }] : [],
|
|
3427
|
+
{ role: "user", content: prompt }
|
|
3428
|
+
]
|
|
3429
|
+
});
|
|
3430
|
+
return response.choices[0]?.message.content ?? "";
|
|
3431
|
+
}
|
|
3432
|
+
};
|
|
3433
|
+
|
|
3434
|
+
// src/adapters/gemini.ts
|
|
3435
|
+
var GeminiAdapter = class {
|
|
3436
|
+
provider = "gemini";
|
|
3437
|
+
model;
|
|
3438
|
+
apiKeyEnv;
|
|
3439
|
+
constructor(config) {
|
|
3440
|
+
this.model = config.model ?? DEFAULT_MODELS.gemini;
|
|
3441
|
+
this.apiKeyEnv = config.apiKeyEnv;
|
|
3442
|
+
}
|
|
3443
|
+
isConfigured() {
|
|
3444
|
+
return Boolean(process.env[this.apiKeyEnv]);
|
|
3445
|
+
}
|
|
3446
|
+
async complete(prompt, options = {}) {
|
|
3447
|
+
const apiKey = process.env[this.apiKeyEnv];
|
|
3448
|
+
if (!apiKey) {
|
|
3449
|
+
throw new Error(
|
|
3450
|
+
`Gemini API key not found. Set the ${this.apiKeyEnv} environment variable.
|
|
3451
|
+
Get your key at https://aistudio.google.com/app/apikey`
|
|
3452
|
+
);
|
|
3453
|
+
}
|
|
3454
|
+
let GoogleGenerativeAI;
|
|
3455
|
+
try {
|
|
3456
|
+
({ GoogleGenerativeAI } = await import("@google/generative-ai"));
|
|
3457
|
+
} catch {
|
|
3458
|
+
throw new Error(
|
|
3459
|
+
"Google AI SDK not installed. Run: npm install @google/generative-ai"
|
|
3460
|
+
);
|
|
3461
|
+
}
|
|
3462
|
+
const genAI = new GoogleGenerativeAI(apiKey);
|
|
3463
|
+
const genModel = genAI.getGenerativeModel({ model: this.model });
|
|
3464
|
+
const fullPrompt = options.system ? `${options.system}
|
|
3465
|
+
|
|
3466
|
+
${prompt}` : prompt;
|
|
3467
|
+
const result = await genModel.generateContent(fullPrompt);
|
|
3468
|
+
return result.response.text();
|
|
3469
|
+
}
|
|
3470
|
+
};
|
|
3471
|
+
|
|
3472
|
+
// src/adapters/deepseek.ts
|
|
3473
|
+
var DeepSeekAdapter = class {
|
|
3474
|
+
provider = "deepseek";
|
|
3475
|
+
model;
|
|
3476
|
+
apiKeyEnv;
|
|
3477
|
+
constructor(config) {
|
|
3478
|
+
this.model = config.model ?? DEFAULT_MODELS.deepseek;
|
|
3479
|
+
this.apiKeyEnv = config.apiKeyEnv;
|
|
3480
|
+
}
|
|
3481
|
+
isConfigured() {
|
|
3482
|
+
return Boolean(process.env[this.apiKeyEnv]);
|
|
3483
|
+
}
|
|
3484
|
+
async complete(prompt, options = {}) {
|
|
3485
|
+
const apiKey = process.env[this.apiKeyEnv];
|
|
3486
|
+
if (!apiKey) {
|
|
3487
|
+
throw new Error(
|
|
3488
|
+
`DeepSeek API key not found. Set the ${this.apiKeyEnv} environment variable.
|
|
3489
|
+
Get your key at https://platform.deepseek.com/api_keys`
|
|
3490
|
+
);
|
|
3491
|
+
}
|
|
3492
|
+
let OpenAI;
|
|
3493
|
+
try {
|
|
3494
|
+
({ default: OpenAI } = await import("openai"));
|
|
3495
|
+
} catch {
|
|
3496
|
+
throw new Error(
|
|
3497
|
+
"OpenAI SDK not installed (required for DeepSeek). Run: npm install openai"
|
|
3498
|
+
);
|
|
3499
|
+
}
|
|
3500
|
+
const client = new OpenAI({
|
|
3501
|
+
apiKey,
|
|
3502
|
+
baseURL: "https://api.deepseek.com"
|
|
3503
|
+
});
|
|
3504
|
+
const response = await client.chat.completions.create({
|
|
3505
|
+
model: this.model,
|
|
3506
|
+
max_tokens: options.maxTokens ?? 4096,
|
|
3507
|
+
temperature: options.temperature ?? 0.7,
|
|
3508
|
+
messages: [
|
|
3509
|
+
...options.system ? [{ role: "system", content: options.system }] : [],
|
|
3510
|
+
{ role: "user", content: prompt }
|
|
3511
|
+
]
|
|
3512
|
+
});
|
|
3513
|
+
return response.choices[0]?.message.content ?? "";
|
|
3514
|
+
}
|
|
3515
|
+
};
|
|
3516
|
+
|
|
3517
|
+
// src/adapters/manager.ts
|
|
3518
|
+
var AdapterManager = class _AdapterManager {
|
|
3519
|
+
static create(config) {
|
|
3520
|
+
switch (config.provider) {
|
|
3521
|
+
case "claude":
|
|
3522
|
+
return new ClaudeAdapter(config);
|
|
3523
|
+
case "openai":
|
|
3524
|
+
return new OpenAIAdapter(config);
|
|
3525
|
+
case "gemini":
|
|
3526
|
+
return new GeminiAdapter(config);
|
|
3527
|
+
case "deepseek":
|
|
3528
|
+
return new DeepSeekAdapter(config);
|
|
3529
|
+
default:
|
|
3530
|
+
throw new Error(`Unknown AI provider: ${String(config.provider)}`);
|
|
3531
|
+
}
|
|
3532
|
+
}
|
|
3533
|
+
static fromState(state) {
|
|
3534
|
+
if (!state.aiConfig) return null;
|
|
3535
|
+
return _AdapterManager.create(state.aiConfig);
|
|
3536
|
+
}
|
|
3537
|
+
};
|
|
3538
|
+
export {
|
|
3539
|
+
AdapterManager,
|
|
3540
|
+
BUILT_IN_SKILLS,
|
|
3541
|
+
BrainForgeServer,
|
|
3542
|
+
ClaudeAdapter,
|
|
3543
|
+
CodebaseMapper,
|
|
3544
|
+
DeepSeekAdapter,
|
|
3545
|
+
FileWatcher,
|
|
3546
|
+
GeminiAdapter,
|
|
3547
|
+
MockDefense,
|
|
3548
|
+
OpenAIAdapter,
|
|
3549
|
+
ProfessorScanner,
|
|
3550
|
+
PromptEngine,
|
|
3551
|
+
SkillRegistry,
|
|
3552
|
+
StateManager,
|
|
3553
|
+
TaskPlanner,
|
|
3554
|
+
buildQuestionSet,
|
|
3555
|
+
reduce
|
|
3556
|
+
};
|