ace-interview-prep 0.1.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/LICENSE +21 -0
- package/README.md +129 -0
- package/dist/commands/add.js +92 -0
- package/dist/commands/feedback.js +133 -0
- package/dist/commands/generate.js +224 -0
- package/dist/commands/init.js +100 -0
- package/dist/commands/list.js +107 -0
- package/dist/commands/reset.js +68 -0
- package/dist/commands/score.js +70 -0
- package/dist/commands/setup.js +84 -0
- package/dist/commands/test.js +85 -0
- package/dist/index.js +72 -0
- package/dist/lib/categories.js +103 -0
- package/dist/lib/config.js +61 -0
- package/dist/lib/llm.js +134 -0
- package/dist/lib/paths.js +38 -0
- package/dist/lib/scaffold.js +110 -0
- package/dist/lib/scorecard.js +116 -0
- package/dist/prompts/code-review.md +59 -0
- package/dist/prompts/design-review.md +67 -0
- package/dist/prompts/question-brainstorm.md +31 -0
- package/dist/prompts/question-generate.md +65 -0
- package/dist/templates/design/notes.md.hbs +27 -0
- package/dist/templates/js-ts/solution.test.ts.hbs +11 -0
- package/dist/templates/js-ts/solution.ts.hbs +11 -0
- package/dist/templates/leetcode-algo/solution.test.ts.hbs +11 -0
- package/dist/templates/leetcode-algo/solution.ts.hbs +11 -0
- package/dist/templates/leetcode-ds/solution.test.ts.hbs +11 -0
- package/dist/templates/leetcode-ds/solution.ts.hbs +11 -0
- package/dist/templates/react-apps/App.test.tsx.hbs +16 -0
- package/dist/templates/react-apps/App.tsx.hbs +16 -0
- package/dist/templates/readme.md.hbs +9 -0
- package/dist/templates/web-components/component.test.ts.hbs +11 -0
- package/dist/templates/web-components/component.ts.hbs +22 -0
- package/dist/templates/web-components/index.html.hbs +12 -0
- package/package.json +72 -0
- package/questions/design-be/url-shortener/README.md +23 -0
- package/questions/design-be/url-shortener/notes.md +27 -0
- package/questions/design-be/url-shortener/scorecard.json +1 -0
- package/questions/design-fe/news-feed/README.md +22 -0
- package/questions/design-fe/news-feed/notes.md +27 -0
- package/questions/design-fe/news-feed/scorecard.json +1 -0
- package/questions/design-full/google-docs/README.md +22 -0
- package/questions/design-full/google-docs/notes.md +27 -0
- package/questions/design-full/google-docs/scorecard.json +1 -0
- package/questions/js-ts/debounce/README.md +86 -0
- package/questions/js-ts/debounce/scorecard.json +9 -0
- package/questions/js-ts/debounce/solution.test.ts +128 -0
- package/questions/js-ts/debounce/solution.ts +4 -0
- package/questions/leetcode-algo/two-sum/README.md +58 -0
- package/questions/leetcode-algo/two-sum/scorecard.json +1 -0
- package/questions/leetcode-algo/two-sum/solution.test.ts +55 -0
- package/questions/leetcode-algo/two-sum/solution.ts +4 -0
- package/questions/leetcode-ds/lru-cache/README.md +70 -0
- package/questions/leetcode-ds/lru-cache/scorecard.json +1 -0
- package/questions/leetcode-ds/lru-cache/solution.test.ts +82 -0
- package/questions/leetcode-ds/lru-cache/solution.ts +14 -0
- package/questions/react-apps/todo-app/App.test.tsx +130 -0
- package/questions/react-apps/todo-app/App.tsx +10 -0
- package/questions/react-apps/todo-app/README.md +23 -0
- package/questions/react-apps/todo-app/scorecard.json +9 -0
- package/questions/web-components/star-rating/README.md +45 -0
- package/questions/web-components/star-rating/component.test.ts +64 -0
- package/questions/web-components/star-rating/component.ts +28 -0
- package/questions/web-components/star-rating/index.html +14 -0
- package/questions/web-components/star-rating/scorecard.json +9 -0
package/dist/lib/llm.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import OpenAI from "openai";
|
|
2
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { loadAceConfig } from "./config.js";
|
|
5
|
+
let cachedConfig = null;
|
|
6
|
+
function getConfig() {
|
|
7
|
+
if (!cachedConfig) {
|
|
8
|
+
cachedConfig = loadAceConfig();
|
|
9
|
+
}
|
|
10
|
+
return cachedConfig;
|
|
11
|
+
}
|
|
12
|
+
function getAvailableProviders() {
|
|
13
|
+
const config = getConfig();
|
|
14
|
+
const providers = [];
|
|
15
|
+
if (config.OPENAI_API_KEY) providers.push("openai");
|
|
16
|
+
if (config.ANTHROPIC_API_KEY) providers.push("anthropic");
|
|
17
|
+
return providers;
|
|
18
|
+
}
|
|
19
|
+
function getDefaultProvider() {
|
|
20
|
+
const providers = getAvailableProviders();
|
|
21
|
+
if (providers.length === 0) return null;
|
|
22
|
+
return providers[0];
|
|
23
|
+
}
|
|
24
|
+
function requireProvider(preferred) {
|
|
25
|
+
const config = getConfig();
|
|
26
|
+
if (preferred === "openai" || preferred === "anthropic") {
|
|
27
|
+
const key = preferred === "openai" ? "OPENAI_API_KEY" : "ANTHROPIC_API_KEY";
|
|
28
|
+
if (!config[key]) {
|
|
29
|
+
console.error(chalk.red(`Error: ${key} is not configured.`));
|
|
30
|
+
console.error(chalk.dim("Run `ace setup` to configure API keys."));
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
return preferred;
|
|
34
|
+
}
|
|
35
|
+
const provider = getDefaultProvider();
|
|
36
|
+
if (!provider) {
|
|
37
|
+
console.error(chalk.red("Error: No LLM API key found."));
|
|
38
|
+
console.error(chalk.dim("Run `ace setup` to configure API keys."));
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
return provider;
|
|
42
|
+
}
|
|
43
|
+
async function callOpenAI(messages, jsonMode = false) {
|
|
44
|
+
const config = getConfig();
|
|
45
|
+
const client = new OpenAI({ apiKey: config.OPENAI_API_KEY });
|
|
46
|
+
const response = await client.chat.completions.create({
|
|
47
|
+
model: "gpt-4o",
|
|
48
|
+
messages: messages.map((m) => ({ role: m.role, content: m.content })),
|
|
49
|
+
temperature: 0.7,
|
|
50
|
+
max_tokens: 4096,
|
|
51
|
+
...jsonMode ? { response_format: { type: "json_object" } } : {}
|
|
52
|
+
});
|
|
53
|
+
return response.choices[0]?.message?.content ?? "";
|
|
54
|
+
}
|
|
55
|
+
async function callAnthropic(messages, _jsonMode = false) {
|
|
56
|
+
const config = getConfig();
|
|
57
|
+
const client = new Anthropic({ apiKey: config.ANTHROPIC_API_KEY });
|
|
58
|
+
const systemMsg = messages.find((m) => m.role === "system");
|
|
59
|
+
const nonSystemMessages = messages.filter((m) => m.role !== "system");
|
|
60
|
+
const response = await client.messages.create({
|
|
61
|
+
model: "claude-sonnet-4-20250514",
|
|
62
|
+
max_tokens: 4096,
|
|
63
|
+
...systemMsg ? { system: systemMsg.content } : {},
|
|
64
|
+
messages: nonSystemMessages.map((m) => ({
|
|
65
|
+
role: m.role,
|
|
66
|
+
content: m.content
|
|
67
|
+
}))
|
|
68
|
+
});
|
|
69
|
+
const block = response.content[0];
|
|
70
|
+
if (block.type === "text") return block.text;
|
|
71
|
+
return "";
|
|
72
|
+
}
|
|
73
|
+
async function chat(provider, messages, jsonMode = false) {
|
|
74
|
+
if (provider === "openai") {
|
|
75
|
+
return callOpenAI(messages, jsonMode);
|
|
76
|
+
}
|
|
77
|
+
return callAnthropic(messages, jsonMode);
|
|
78
|
+
}
|
|
79
|
+
async function streamOpenAI(messages) {
|
|
80
|
+
const config = getConfig();
|
|
81
|
+
const client = new OpenAI({ apiKey: config.OPENAI_API_KEY });
|
|
82
|
+
const stream = await client.chat.completions.create({
|
|
83
|
+
model: "gpt-4o",
|
|
84
|
+
messages: messages.map((m) => ({ role: m.role, content: m.content })),
|
|
85
|
+
temperature: 0.7,
|
|
86
|
+
max_tokens: 4096,
|
|
87
|
+
stream: true
|
|
88
|
+
});
|
|
89
|
+
return {
|
|
90
|
+
async *[Symbol.asyncIterator]() {
|
|
91
|
+
for await (const chunk of stream) {
|
|
92
|
+
const delta = chunk.choices[0]?.delta?.content;
|
|
93
|
+
if (delta) yield delta;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
async function streamAnthropic(messages) {
|
|
99
|
+
const config = getConfig();
|
|
100
|
+
const client = new Anthropic({ apiKey: config.ANTHROPIC_API_KEY });
|
|
101
|
+
const systemMsg = messages.find((m) => m.role === "system");
|
|
102
|
+
const nonSystemMessages = messages.filter((m) => m.role !== "system");
|
|
103
|
+
const stream = client.messages.stream({
|
|
104
|
+
model: "claude-sonnet-4-20250514",
|
|
105
|
+
max_tokens: 4096,
|
|
106
|
+
...systemMsg ? { system: systemMsg.content } : {},
|
|
107
|
+
messages: nonSystemMessages.map((m) => ({
|
|
108
|
+
role: m.role,
|
|
109
|
+
content: m.content
|
|
110
|
+
}))
|
|
111
|
+
});
|
|
112
|
+
return {
|
|
113
|
+
async *[Symbol.asyncIterator]() {
|
|
114
|
+
for await (const event of stream) {
|
|
115
|
+
if (event.type === "content_block_delta") {
|
|
116
|
+
const delta = event.delta;
|
|
117
|
+
if ("text" in delta) yield delta.text;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
async function chatStream(provider, messages) {
|
|
124
|
+
if (provider === "openai") {
|
|
125
|
+
return streamOpenAI(messages);
|
|
126
|
+
}
|
|
127
|
+
return streamAnthropic(messages);
|
|
128
|
+
}
|
|
129
|
+
export {
|
|
130
|
+
chat,
|
|
131
|
+
chatStream,
|
|
132
|
+
getDefaultProvider,
|
|
133
|
+
requireProvider
|
|
134
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
function resolveWorkspaceRoot(startCwd = process.cwd()) {
|
|
4
|
+
let current = path.resolve(startCwd);
|
|
5
|
+
const root = path.parse(current).root;
|
|
6
|
+
while (current !== root) {
|
|
7
|
+
const questionsPath2 = path.join(current, "questions");
|
|
8
|
+
if (fs.existsSync(questionsPath2) && fs.statSync(questionsPath2).isDirectory()) {
|
|
9
|
+
return current;
|
|
10
|
+
}
|
|
11
|
+
current = path.dirname(current);
|
|
12
|
+
}
|
|
13
|
+
const questionsPath = path.join(current, "questions");
|
|
14
|
+
if (fs.existsSync(questionsPath) && fs.statSync(questionsPath).isDirectory()) {
|
|
15
|
+
return current;
|
|
16
|
+
}
|
|
17
|
+
return path.resolve(startCwd);
|
|
18
|
+
}
|
|
19
|
+
function getQuestionsDir(root) {
|
|
20
|
+
return path.join(root, "questions");
|
|
21
|
+
}
|
|
22
|
+
function isWorkspaceInitialized(root) {
|
|
23
|
+
const questionsPath = getQuestionsDir(root);
|
|
24
|
+
return fs.existsSync(questionsPath) && fs.statSync(questionsPath).isDirectory();
|
|
25
|
+
}
|
|
26
|
+
function getHomeDir() {
|
|
27
|
+
return process.env.HOME || process.env.USERPROFILE || "~";
|
|
28
|
+
}
|
|
29
|
+
function getGlobalAceDir() {
|
|
30
|
+
return path.join(getHomeDir(), ".ace");
|
|
31
|
+
}
|
|
32
|
+
export {
|
|
33
|
+
getGlobalAceDir,
|
|
34
|
+
getHomeDir,
|
|
35
|
+
getQuestionsDir,
|
|
36
|
+
isWorkspaceInitialized,
|
|
37
|
+
resolveWorkspaceRoot
|
|
38
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import Handlebars from "handlebars";
|
|
4
|
+
import { getCategoryConfig, getSuggestedTime, isDesignCategory } from "./categories.js";
|
|
5
|
+
import { createScorecard, writeScorecard } from "./scorecard.js";
|
|
6
|
+
import { resolveWorkspaceRoot, getQuestionsDir as getQuestionsDirPath } from "./paths.js";
|
|
7
|
+
const TEMPLATES_DIR = path.resolve(import.meta.dirname, "../templates");
|
|
8
|
+
function loadTemplate(templatePath) {
|
|
9
|
+
const raw = fs.readFileSync(templatePath, "utf-8");
|
|
10
|
+
return Handlebars.compile(raw);
|
|
11
|
+
}
|
|
12
|
+
function ensureDir(dir) {
|
|
13
|
+
if (!fs.existsSync(dir)) {
|
|
14
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function getQuestionDir(category, slug) {
|
|
18
|
+
const root = resolveWorkspaceRoot();
|
|
19
|
+
const questionsDir = getQuestionsDirPath(root);
|
|
20
|
+
return path.join(questionsDir, category, slug);
|
|
21
|
+
}
|
|
22
|
+
function scaffoldQuestion(opts) {
|
|
23
|
+
const config = getCategoryConfig(opts.category);
|
|
24
|
+
const questionDir = getQuestionDir(opts.category, opts.slug);
|
|
25
|
+
const suggestedTime = getSuggestedTime(opts.category, opts.difficulty);
|
|
26
|
+
if (fs.existsSync(questionDir)) {
|
|
27
|
+
throw new Error(`Question already exists: ${questionDir}`);
|
|
28
|
+
}
|
|
29
|
+
ensureDir(questionDir);
|
|
30
|
+
const templateData = {
|
|
31
|
+
title: opts.title,
|
|
32
|
+
slug: opts.slug,
|
|
33
|
+
category: config.name,
|
|
34
|
+
categorySlug: opts.category,
|
|
35
|
+
difficulty: opts.difficulty,
|
|
36
|
+
suggestedTime,
|
|
37
|
+
description: opts.description,
|
|
38
|
+
signature: opts.signature || "",
|
|
39
|
+
testCode: opts.testCode || "",
|
|
40
|
+
solutionCode: opts.solutionCode || "",
|
|
41
|
+
htmlCode: opts.htmlCode || ""
|
|
42
|
+
};
|
|
43
|
+
const readmeTemplate = loadTemplate(path.join(TEMPLATES_DIR, "readme.md.hbs"));
|
|
44
|
+
fs.writeFileSync(path.join(questionDir, "README.md"), readmeTemplate(templateData));
|
|
45
|
+
if (isDesignCategory(opts.category)) {
|
|
46
|
+
const notesTemplate = loadTemplate(path.join(TEMPLATES_DIR, "design", "notes.md.hbs"));
|
|
47
|
+
fs.writeFileSync(path.join(questionDir, "notes.md"), notesTemplate(templateData));
|
|
48
|
+
} else {
|
|
49
|
+
const templateDir = path.join(TEMPLATES_DIR, config.templateDir);
|
|
50
|
+
for (const solutionFile of config.solutionFiles) {
|
|
51
|
+
const templateFile = solutionFile + ".hbs";
|
|
52
|
+
const templatePath = path.join(templateDir, templateFile);
|
|
53
|
+
if (fs.existsSync(templatePath)) {
|
|
54
|
+
const tmpl = loadTemplate(templatePath);
|
|
55
|
+
fs.writeFileSync(path.join(questionDir, solutionFile), tmpl(templateData));
|
|
56
|
+
} else if (opts.solutionCode) {
|
|
57
|
+
fs.writeFileSync(path.join(questionDir, solutionFile), opts.solutionCode);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
for (const testFile of config.testFiles) {
|
|
61
|
+
const templateFile = testFile + ".hbs";
|
|
62
|
+
const templatePath = path.join(templateDir, templateFile);
|
|
63
|
+
if (fs.existsSync(templatePath)) {
|
|
64
|
+
const tmpl = loadTemplate(templatePath);
|
|
65
|
+
fs.writeFileSync(path.join(questionDir, testFile), tmpl(templateData));
|
|
66
|
+
} else if (opts.testCode) {
|
|
67
|
+
fs.writeFileSync(path.join(questionDir, testFile), opts.testCode);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (config.solutionFiles.includes("index.html") && opts.htmlCode) {
|
|
71
|
+
fs.writeFileSync(path.join(questionDir, "index.html"), opts.htmlCode);
|
|
72
|
+
} else {
|
|
73
|
+
const htmlTemplatePath = path.join(templateDir, "index.html.hbs");
|
|
74
|
+
if (fs.existsSync(htmlTemplatePath) && config.solutionFiles.includes("index.html")) {
|
|
75
|
+
const tmpl = loadTemplate(htmlTemplatePath);
|
|
76
|
+
fs.writeFileSync(path.join(questionDir, "index.html"), tmpl(templateData));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const scorecard = createScorecard(opts.title, opts.category, opts.difficulty);
|
|
81
|
+
writeScorecard(opts.category, opts.slug, scorecard);
|
|
82
|
+
return questionDir;
|
|
83
|
+
}
|
|
84
|
+
function getStubContent(category, file) {
|
|
85
|
+
const config = getCategoryConfig(category);
|
|
86
|
+
const templateDir = path.join(TEMPLATES_DIR, config.templateDir);
|
|
87
|
+
const templateFile = file + ".hbs";
|
|
88
|
+
const templatePath = path.join(templateDir, templateFile);
|
|
89
|
+
if (fs.existsSync(templatePath)) {
|
|
90
|
+
const tmpl = loadTemplate(templatePath);
|
|
91
|
+
return tmpl({
|
|
92
|
+
title: "",
|
|
93
|
+
slug: "",
|
|
94
|
+
category: "",
|
|
95
|
+
categorySlug: category,
|
|
96
|
+
difficulty: "",
|
|
97
|
+
suggestedTime: 0,
|
|
98
|
+
description: "",
|
|
99
|
+
signature: "",
|
|
100
|
+
testCode: "",
|
|
101
|
+
solutionCode: ""
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
return "";
|
|
105
|
+
}
|
|
106
|
+
export {
|
|
107
|
+
getQuestionDir,
|
|
108
|
+
getStubContent,
|
|
109
|
+
scaffoldQuestion
|
|
110
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { getSuggestedTime } from "./categories.js";
|
|
4
|
+
import { resolveWorkspaceRoot, getQuestionsDir } from "./paths.js";
|
|
5
|
+
function getScorecardPath(category, slug) {
|
|
6
|
+
const root = resolveWorkspaceRoot();
|
|
7
|
+
const questionsDir = getQuestionsDir(root);
|
|
8
|
+
return path.join(questionsDir, category, slug, "scorecard.json");
|
|
9
|
+
}
|
|
10
|
+
function readScorecard(category, slug) {
|
|
11
|
+
const filepath = getScorecardPath(category, slug);
|
|
12
|
+
if (!fs.existsSync(filepath)) return null;
|
|
13
|
+
return JSON.parse(fs.readFileSync(filepath, "utf-8"));
|
|
14
|
+
}
|
|
15
|
+
function writeScorecard(category, slug, scorecard) {
|
|
16
|
+
const filepath = getScorecardPath(category, slug);
|
|
17
|
+
fs.writeFileSync(filepath, JSON.stringify(scorecard, null, 2) + "\n", "utf-8");
|
|
18
|
+
}
|
|
19
|
+
function createScorecard(title, category, difficulty) {
|
|
20
|
+
return {
|
|
21
|
+
title,
|
|
22
|
+
category,
|
|
23
|
+
difficulty,
|
|
24
|
+
suggestedTime: getSuggestedTime(category, difficulty),
|
|
25
|
+
status: "untouched",
|
|
26
|
+
attempts: [],
|
|
27
|
+
llmFeedback: null
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function getCurrentAttempt(scorecard) {
|
|
31
|
+
if (scorecard.attempts.length === 0) return null;
|
|
32
|
+
return scorecard.attempts[scorecard.attempts.length - 1];
|
|
33
|
+
}
|
|
34
|
+
function startNewAttempt(scorecard) {
|
|
35
|
+
const attemptNum = scorecard.attempts.length + 1;
|
|
36
|
+
scorecard.attempts.push({
|
|
37
|
+
attempt: attemptNum,
|
|
38
|
+
testsTotal: 0,
|
|
39
|
+
testsPassed: 0,
|
|
40
|
+
llmScore: null
|
|
41
|
+
});
|
|
42
|
+
scorecard.status = "in-progress";
|
|
43
|
+
return scorecard;
|
|
44
|
+
}
|
|
45
|
+
function updateTestResults(scorecard, testsTotal, testsPassed) {
|
|
46
|
+
if (scorecard.attempts.length === 0) {
|
|
47
|
+
startNewAttempt(scorecard);
|
|
48
|
+
}
|
|
49
|
+
const current = scorecard.attempts[scorecard.attempts.length - 1];
|
|
50
|
+
current.testsTotal = testsTotal;
|
|
51
|
+
current.testsPassed = testsPassed;
|
|
52
|
+
if (testsTotal > 0 && testsPassed === testsTotal) {
|
|
53
|
+
scorecard.status = "solved";
|
|
54
|
+
} else if (testsTotal > 0) {
|
|
55
|
+
scorecard.status = "attempted";
|
|
56
|
+
}
|
|
57
|
+
return scorecard;
|
|
58
|
+
}
|
|
59
|
+
function resetScorecard(scorecard) {
|
|
60
|
+
scorecard.status = "untouched";
|
|
61
|
+
scorecard.llmFeedback = null;
|
|
62
|
+
return scorecard;
|
|
63
|
+
}
|
|
64
|
+
function getAllQuestions() {
|
|
65
|
+
const results = [];
|
|
66
|
+
const root = resolveWorkspaceRoot();
|
|
67
|
+
const questionsRoot = getQuestionsDir(root);
|
|
68
|
+
if (!fs.existsSync(questionsRoot)) return results;
|
|
69
|
+
for (const categoryDir of fs.readdirSync(questionsRoot)) {
|
|
70
|
+
const categoryPath = path.join(questionsRoot, categoryDir);
|
|
71
|
+
if (!fs.statSync(categoryPath).isDirectory()) continue;
|
|
72
|
+
for (const questionDir of fs.readdirSync(categoryPath)) {
|
|
73
|
+
const questionPath = path.join(categoryPath, questionDir);
|
|
74
|
+
if (!fs.statSync(questionPath).isDirectory()) continue;
|
|
75
|
+
const scorecardPath = path.join(questionPath, "scorecard.json");
|
|
76
|
+
if (!fs.existsSync(scorecardPath)) continue;
|
|
77
|
+
const scorecard = JSON.parse(fs.readFileSync(scorecardPath, "utf-8"));
|
|
78
|
+
results.push({
|
|
79
|
+
category: categoryDir,
|
|
80
|
+
slug: questionDir,
|
|
81
|
+
scorecard
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return results;
|
|
86
|
+
}
|
|
87
|
+
function findQuestion(slug) {
|
|
88
|
+
const root = resolveWorkspaceRoot();
|
|
89
|
+
const questionsRoot = getQuestionsDir(root);
|
|
90
|
+
if (!fs.existsSync(questionsRoot)) return null;
|
|
91
|
+
for (const categoryDir of fs.readdirSync(questionsRoot)) {
|
|
92
|
+
const categoryPath = path.join(questionsRoot, categoryDir);
|
|
93
|
+
if (!fs.statSync(categoryPath).isDirectory()) continue;
|
|
94
|
+
const questionPath = path.join(categoryPath, slug);
|
|
95
|
+
if (fs.existsSync(questionPath) && fs.statSync(questionPath).isDirectory()) {
|
|
96
|
+
return {
|
|
97
|
+
category: categoryDir,
|
|
98
|
+
slug,
|
|
99
|
+
dir: questionPath
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
export {
|
|
106
|
+
createScorecard,
|
|
107
|
+
findQuestion,
|
|
108
|
+
getAllQuestions,
|
|
109
|
+
getCurrentAttempt,
|
|
110
|
+
getScorecardPath,
|
|
111
|
+
readScorecard,
|
|
112
|
+
resetScorecard,
|
|
113
|
+
startNewAttempt,
|
|
114
|
+
updateTestResults,
|
|
115
|
+
writeScorecard
|
|
116
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Staff/Principal Engineer Code Review
|
|
2
|
+
|
|
3
|
+
You are a staff or principal engineer conducting a code review of an interview candidate's solution. Evaluate the submission as you would in a real interview setting.
|
|
4
|
+
|
|
5
|
+
## Context
|
|
6
|
+
|
|
7
|
+
You will receive:
|
|
8
|
+
- The **question** (title, description, category)
|
|
9
|
+
- The **candidate's code** (solution implementation)
|
|
10
|
+
|
|
11
|
+
## Evaluation Dimensions
|
|
12
|
+
|
|
13
|
+
Score each dimension from **1 to 5** (1 = poor, 5 = excellent):
|
|
14
|
+
|
|
15
|
+
1. **Correctness**: Does the solution produce the right output for the given inputs? Are there logical errors?
|
|
16
|
+
2. **Edge Case Handling**: Does it handle empty inputs, null/undefined, boundary values, and unusual cases?
|
|
17
|
+
3. **Time/Space Complexity**: Is the approach efficient? Does the candidate demonstrate awareness of Big O?
|
|
18
|
+
4. **Code Quality**: Is the code clean, well-structured, and maintainable?
|
|
19
|
+
5. **Readability**: Is it easy to follow? Naming, formatting, and organization.
|
|
20
|
+
6. **Idiomatic Patterns**: Does it use language/framework conventions appropriately (e.g., React hooks, TypeScript types, standard algorithms)?
|
|
21
|
+
|
|
22
|
+
## Output Format
|
|
23
|
+
|
|
24
|
+
Provide your review in the following structure:
|
|
25
|
+
|
|
26
|
+
### Scores (1–5 each)
|
|
27
|
+
|
|
28
|
+
- Correctness: X
|
|
29
|
+
- Edge Case Handling: X
|
|
30
|
+
- Time/Space Complexity: X
|
|
31
|
+
- Code Quality: X
|
|
32
|
+
- Readability: X
|
|
33
|
+
- Idiomatic Patterns: X
|
|
34
|
+
|
|
35
|
+
### Overall Assessment
|
|
36
|
+
|
|
37
|
+
One of: **Strong Hire** | **Hire** | **Lean Hire** | **No Hire**
|
|
38
|
+
|
|
39
|
+
### 3 Things Done Well
|
|
40
|
+
|
|
41
|
+
- [Specific reference to code with line/function names when possible]
|
|
42
|
+
- [Specific reference]
|
|
43
|
+
- [Specific reference]
|
|
44
|
+
|
|
45
|
+
### 3 Areas to Improve
|
|
46
|
+
|
|
47
|
+
- [Concrete suggestion with reference to code]
|
|
48
|
+
- [Concrete suggestion]
|
|
49
|
+
- [Concrete suggestion]
|
|
50
|
+
|
|
51
|
+
### Critical Issues (if any)
|
|
52
|
+
|
|
53
|
+
List any bugs, security concerns, or fundamental flaws that would block a hire. Omit this section if none.
|
|
54
|
+
|
|
55
|
+
## Guidelines
|
|
56
|
+
|
|
57
|
+
- Be **specific**: Reference actual code (function names, variable names, logic) rather than generic feedback
|
|
58
|
+
- Be **fair**: Interview code is written under time pressure; focus on what matters
|
|
59
|
+
- Be **constructive**: Frame improvements as learning opportunities, not criticism
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Staff/Principal Engineer System Design Review
|
|
2
|
+
|
|
3
|
+
You are a staff or principal engineer conducting a system design interview review. Evaluate the candidate's design as you would in a real interview.
|
|
4
|
+
|
|
5
|
+
## Context
|
|
6
|
+
|
|
7
|
+
You will receive:
|
|
8
|
+
- The **design question** (title, description, category)
|
|
9
|
+
- The **candidate's notes** (their design document or outline)
|
|
10
|
+
|
|
11
|
+
## Evaluation Dimensions
|
|
12
|
+
|
|
13
|
+
Score each dimension from **1 to 5** (1 = poor, 5 = excellent):
|
|
14
|
+
|
|
15
|
+
1. **Requirements Gathering**: Did they clarify scope, constraints, and success criteria before diving in?
|
|
16
|
+
2. **High-Level Architecture**: Is the overall system structure clear and appropriate for the problem?
|
|
17
|
+
3. **API Design**: Are endpoints, contracts, and data flows well-defined?
|
|
18
|
+
4. **Data Model**: Are schemas, storage choices, and data flow sensible?
|
|
19
|
+
5. **Deep Dive / Trade-offs**: Did they go deep on 1–2 areas and discuss alternatives?
|
|
20
|
+
6. **Communication Clarity**: Was the design easy to follow? Logical flow, diagrams, structure.
|
|
21
|
+
|
|
22
|
+
## Sub-Type Focus
|
|
23
|
+
|
|
24
|
+
Adapt your emphasis based on the design category:
|
|
25
|
+
|
|
26
|
+
- **design-fe (Frontend)**: Component architecture, state management, rendering strategy, client-side caching, accessibility
|
|
27
|
+
- **design-be (Backend)**: Scalability, database choices, API design, consistency, fault tolerance
|
|
28
|
+
- **design-full (Full Stack)**: End-to-end flow, client-server boundaries, data synchronization, deployment
|
|
29
|
+
|
|
30
|
+
## Output Format
|
|
31
|
+
|
|
32
|
+
Provide your review in the following structure:
|
|
33
|
+
|
|
34
|
+
### Scores (1–5 each)
|
|
35
|
+
|
|
36
|
+
- Requirements Gathering: X
|
|
37
|
+
- High-Level Architecture: X
|
|
38
|
+
- API Design: X
|
|
39
|
+
- Data Model: X
|
|
40
|
+
- Deep Dive / Trade-offs: X
|
|
41
|
+
- Communication Clarity: X
|
|
42
|
+
|
|
43
|
+
### Overall Assessment
|
|
44
|
+
|
|
45
|
+
One of: **Strong Hire** | **Hire** | **Lean Hire** | **No Hire**
|
|
46
|
+
|
|
47
|
+
### 3 Strengths
|
|
48
|
+
|
|
49
|
+
- [Specific reference to their design with concrete examples]
|
|
50
|
+
- [Specific reference]
|
|
51
|
+
- [Specific reference]
|
|
52
|
+
|
|
53
|
+
### 3 Areas to Improve (with concrete suggestions)
|
|
54
|
+
|
|
55
|
+
- [What to add or change, with a concrete suggestion]
|
|
56
|
+
- [What to add or change]
|
|
57
|
+
- [What to add or change]
|
|
58
|
+
|
|
59
|
+
### Critical Gaps (if any)
|
|
60
|
+
|
|
61
|
+
List any major omissions (e.g., missing scalability plan, no error handling, unclear requirements). Omit this section if none.
|
|
62
|
+
|
|
63
|
+
## Guidelines
|
|
64
|
+
|
|
65
|
+
- Be **specific**: Reference sections of their design, not generic advice
|
|
66
|
+
- Be **fair**: Interview designs are exploratory; focus on reasoning and trade-off thinking
|
|
67
|
+
- Be **constructive**: Suggest what they could add or refine, not just what's wrong
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Collaborative Interview Question Designer
|
|
2
|
+
|
|
3
|
+
You are a collaborative interview question designer helping the user explore and refine question ideas for the "ace" interview prep CLI.
|
|
4
|
+
|
|
5
|
+
## Supported Categories
|
|
6
|
+
|
|
7
|
+
- **js-ts**: JavaScript/TypeScript puzzles (closures, async, types)
|
|
8
|
+
- **web-components**: Custom elements, Shadow DOM, lifecycle
|
|
9
|
+
- **react-apps**: React components, hooks, state, rendering
|
|
10
|
+
- **leetcode-ds**: Data structure problems (trees, graphs, heaps)
|
|
11
|
+
- **leetcode-algo**: Algorithm problems (DP, greedy, two pointers)
|
|
12
|
+
- **design-fe**: Frontend system design (component architecture, state, rendering)
|
|
13
|
+
- **design-be**: Backend system design (APIs, databases, scalability)
|
|
14
|
+
- **design-full**: Full-stack system design (end-to-end flows)
|
|
15
|
+
|
|
16
|
+
## Your Role
|
|
17
|
+
|
|
18
|
+
1. **Explore**: When the user shares an interest area (e.g., "React performance", "rate limiting", "virtual scrolling"), suggest 3–5 specific question directions that would work well for interview prep.
|
|
19
|
+
2. **Refine**: Based on user feedback, adjust constraints, difficulty, or scope. Add or remove requirements as requested.
|
|
20
|
+
3. **Finalize**: When the user confirms a direction, output a clear, concise question description that can be fed directly into the question generation prompt. Include:
|
|
21
|
+
- Category
|
|
22
|
+
- Difficulty
|
|
23
|
+
- Topic/focus area
|
|
24
|
+
- 1–2 sentences of what the question should cover
|
|
25
|
+
|
|
26
|
+
## Style
|
|
27
|
+
|
|
28
|
+
- Keep responses **concise** and **conversational**
|
|
29
|
+
- Avoid long paragraphs; use bullets when listing options
|
|
30
|
+
- Ask clarifying questions when the user's intent is ambiguous
|
|
31
|
+
- Don't over-explain; match the user's level of detail
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Senior Interview Question Author
|
|
2
|
+
|
|
3
|
+
You are a senior frontend interview question author targeting staff-engineer level candidates. Your questions are rigorous, realistic, and well-scoped.
|
|
4
|
+
|
|
5
|
+
## Input
|
|
6
|
+
|
|
7
|
+
You will receive:
|
|
8
|
+
- **category**: One of `js-ts`, `web-components`, `react-apps`, `leetcode-ds`, `leetcode-algo`, `design-fe`, `design-be`, `design-full`
|
|
9
|
+
- **difficulty**: `easy`, `medium`, or `hard`
|
|
10
|
+
- **topic**: A specific area to focus on (e.g., "closures", "virtual DOM", "LRU cache", "rate limiting")
|
|
11
|
+
|
|
12
|
+
## Output Format
|
|
13
|
+
|
|
14
|
+
**IMPORTANT**: Your response MUST be valid JSON wrapped in ```json code fences. No other text before or after.
|
|
15
|
+
|
|
16
|
+
### For Coding Categories (js-ts, web-components, react-apps, leetcode-ds, leetcode-algo)
|
|
17
|
+
|
|
18
|
+
Return a JSON object with:
|
|
19
|
+
|
|
20
|
+
```json
|
|
21
|
+
{
|
|
22
|
+
"title": "Human-readable question title",
|
|
23
|
+
"slug": "kebab-case-slug",
|
|
24
|
+
"description": "Markdown description with problem statement, examples, constraints",
|
|
25
|
+
"signature": "Function signature or component interface the candidate must implement",
|
|
26
|
+
"testCode": "Full Vitest test file content as a string",
|
|
27
|
+
"solutionCode": "Stub implementation with the signature only (empty body or minimal placeholder)"
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Test requirements:**
|
|
32
|
+
- Generate 6–10 test cases covering: happy path, edge cases, and performance-sensitive scenarios
|
|
33
|
+
- For **React** questions: use `@testing-library/react` with `render` and `screen`
|
|
34
|
+
- Imports must reference the solution file correctly:
|
|
35
|
+
- `js-ts`, `leetcode-ds`, `leetcode-algo`: `import { solution } from './solution'`
|
|
36
|
+
- `react-apps`: `import App from './App'`
|
|
37
|
+
- `web-components`: import the component from the appropriate file (e.g., `'./component'`)
|
|
38
|
+
- Use `describe`, `it`, `expect` from Vitest
|
|
39
|
+
- Tests must be self-contained and runnable
|
|
40
|
+
|
|
41
|
+
**Solution stub:**
|
|
42
|
+
- Include the exact function/component signature the candidate must implement
|
|
43
|
+
- Leave the body empty or with a minimal placeholder (e.g., `throw new Error('Not implemented')` or `return null`)
|
|
44
|
+
|
|
45
|
+
### For Design Categories (design-fe, design-be, design-full)
|
|
46
|
+
|
|
47
|
+
Return a JSON object with:
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"title": "Human-readable design question title",
|
|
52
|
+
"slug": "kebab-case-slug",
|
|
53
|
+
"description": "Markdown description including a **Requirements** section (functional and non-functional)"
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
- No `signature`, `testCode`, or `solutionCode`
|
|
58
|
+
- The description must include a clear **Requirements** section that candidates can use to structure their design
|
|
59
|
+
|
|
60
|
+
## Quality Guidelines
|
|
61
|
+
|
|
62
|
+
- Questions should be achievable within the suggested time for the category and difficulty
|
|
63
|
+
- Avoid ambiguous wording; constraints and expected behavior should be explicit
|
|
64
|
+
- For LeetCode-style questions: include time/space complexity expectations in the description
|
|
65
|
+
- For React/Web Components: focus on realistic UI behavior, not toy examples
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# {{title}} — Design Notes
|
|
2
|
+
|
|
3
|
+
## Requirements
|
|
4
|
+
|
|
5
|
+
### Functional Requirements
|
|
6
|
+
<!-- List the core features and user-facing requirements -->
|
|
7
|
+
|
|
8
|
+
### Non-Functional Requirements
|
|
9
|
+
<!-- Performance, scalability, availability, security, etc. -->
|
|
10
|
+
|
|
11
|
+
## High-Level Architecture
|
|
12
|
+
<!-- Describe the overall system architecture. Include component hierarchy, services, data flow. -->
|
|
13
|
+
|
|
14
|
+
## Data Model
|
|
15
|
+
<!-- Define key data structures, schemas, client-side state shape, caching strategy -->
|
|
16
|
+
|
|
17
|
+
## API Design
|
|
18
|
+
<!-- Endpoint design, data contracts, request/response formats, error handling -->
|
|
19
|
+
|
|
20
|
+
## Deep Dive
|
|
21
|
+
<!-- Pick 1-2 areas to go deep on: rendering, state management, real-time, etc. -->
|
|
22
|
+
|
|
23
|
+
## Scaling Considerations
|
|
24
|
+
<!-- How does this scale? CDN, lazy loading, pagination, sharding, etc. -->
|
|
25
|
+
|
|
26
|
+
## Trade-offs
|
|
27
|
+
<!-- What trade-offs did you make? What alternatives did you consider? -->
|