ace-interview-prep 0.1.0 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -50,7 +50,10 @@ npm install vitest happy-dom @testing-library/jest-dom
50
50
  ### 3. Practice
51
51
 
52
52
  ```bash
53
- # Generate a question via LLM
53
+ # Generate a question interactively (prompts for category, difficulty, topic)
54
+ ace generate
55
+
56
+ # Or pass flags to skip prompts
54
57
  ace generate --topic "debounce" --category js-ts --difficulty medium
55
58
 
56
59
  # Interactive brainstorm mode
@@ -61,43 +64,59 @@ ace add
61
64
 
62
65
  # List all questions
63
66
  ace list
67
+ ```
68
+
69
+ ### 4. Test, Review, Track
64
70
 
71
+ All commands below work in three modes:
72
+ - **Interactive** — run with no arguments to pick from a selectable list
73
+ - **Direct** — pass a slug to target a specific question
74
+ - **All** — pass `--all` to operate on every question
75
+
76
+ ```bash
65
77
  # Run tests
66
- ace test debounce
67
- ace test # run all
68
- ace test --watch # watch mode
78
+ ace test # pick from list
79
+ ace test debounce # specific question
80
+ ace test --all # run all tests
81
+ ace test --watch # watch mode (with --all)
69
82
 
70
- # Get LLM feedback
71
- ace feedback debounce
83
+ # Get LLM feedback on your solution
84
+ ace feedback # pick from list
85
+ ace feedback debounce # specific question
86
+ ace feedback --all # review all questions (confirms each one)
72
87
 
73
88
  # View scorecard
74
- ace score debounce
75
-
76
- # Reset a question
77
- ace reset debounce
89
+ ace score # pick from list
90
+ ace score debounce # specific question
91
+ ace score --all # show all scorecards
92
+
93
+ # Reset a question to its stub
94
+ ace reset # pick from list
95
+ ace reset debounce # specific question
96
+ ace reset --all # reset everything (with confirmation)
78
97
  ```
79
98
 
80
99
  ## Question Categories
81
100
 
82
- | Category | Slug | Type |
83
- |----------|------|------|
84
- | JS/TS Puzzles | `js-ts` | Coding |
85
- | Web Components | `web-components` | Coding |
86
- | React Web Apps | `react-apps` | Coding |
87
- | LeetCode Data Structures | `leetcode-ds` | Coding |
88
- | LeetCode Algorithms | `leetcode-algo` | Coding |
89
- | System Design — Frontend | `design-fe` | Design |
90
- | System Design — Backend | `design-be` | Design |
91
- | System Design — Full Stack | `design-full` | Design |
101
+ | Category | Slug | Type | Focus |
102
+ |----------|------|------|-------|
103
+ | JS/TS Puzzles | `js-ts` | Coding | Closures, async patterns, type utilities |
104
+ | React Components | `web-components` | Coding | Props, events, composition, reusable UI |
105
+ | React Web Apps | `react-apps` | Coding | Hooks, state, routing, full features |
106
+ | LeetCode Data Structures | `leetcode-ds` | Coding | Trees, graphs, heaps, hash maps |
107
+ | LeetCode Algorithms | `leetcode-algo` | Coding | DP, greedy, two pointers, sorting |
108
+ | System Design — Frontend | `design-fe` | Design | Component architecture, state, rendering |
109
+ | System Design — Backend | `design-be` | Design | APIs, databases, caching, queues |
110
+ | System Design — Full Stack | `design-full` | Design | End-to-end systems, trade-offs |
92
111
 
93
112
  ## How It Works
94
113
 
95
- 1. **Pick a question** from the dashboard (`ace list`) or generate one (`ace generate`).
114
+ 1. **Generate a question** run `ace generate` and follow the prompts (category, difficulty, topic), or use `ace generate --brainstorm` for an interactive design session.
96
115
  2. **Open the question folder** — read `README.md` for the problem statement.
97
- 3. **Write your solution** in the solution file (`solution.ts`, `App.tsx`, `component.ts`, or `notes.md`).
98
- 4. **Run tests** with `ace test <slug>` to check your work.
99
- 5. **Get feedback** with `ace feedback <slug>` for an LLM-powered code or design review.
100
- 6. **Track progress** with `ace score <slug>` and `ace list`.
116
+ 3. **Write your solution** in the solution file (`solution.ts`, `App.tsx`, `Component.tsx`, or `notes.md`).
117
+ 4. **Run tests** with `ace test` to pick a question and check your work.
118
+ 5. **Get feedback** with `ace feedback` for an LLM-powered code or design review.
119
+ 6. **Track progress** with `ace score` and `ace list`.
101
120
 
102
121
  ## Configuration
103
122
 
@@ -1,7 +1,8 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
+ import prompts from "prompts";
3
4
  import chalk from "chalk";
4
- import { findQuestion, readScorecard, writeScorecard } from "../lib/scorecard.js";
5
+ import { findQuestion, readScorecard, writeScorecard, getAllQuestions, promptForSlug } from "../lib/scorecard.js";
5
6
  import { CATEGORIES, isDesignCategory } from "../lib/categories.js";
6
7
  import { chatStream, requireProvider } from "../lib/llm.js";
7
8
  import { resolveWorkspaceRoot, isWorkspaceInitialized } from "../lib/paths.js";
@@ -12,35 +13,25 @@ function loadPrompt(name) {
12
13
  function parseArgs(args) {
13
14
  let slug;
14
15
  let provider;
16
+ let all = false;
15
17
  for (let i = 0; i < args.length; i++) {
16
18
  const arg = args[i];
17
19
  if (arg === "--provider" && args[i + 1]) {
18
20
  provider = args[++i];
21
+ } else if (arg === "--all" || arg === "all") {
22
+ all = true;
19
23
  } else if (!arg.startsWith("--")) {
20
24
  slug = arg;
21
25
  }
22
26
  }
23
- return { slug, provider };
27
+ return { slug, provider, all };
24
28
  }
25
- async function run(args) {
26
- const root = resolveWorkspaceRoot();
27
- if (!isWorkspaceInitialized(root)) {
28
- console.error(chalk.red("\nError: Workspace not initialized."));
29
- console.error(chalk.dim("Run `ace init` in this folder first.\n"));
30
- process.exit(1);
31
- }
32
- const parsed = parseArgs(args);
33
- if (!parsed.slug) {
34
- console.error(chalk.red("Missing question slug."));
35
- console.error(chalk.dim("Usage: npm run ace feedback <slug>"));
36
- return;
37
- }
38
- const question = findQuestion(parsed.slug);
29
+ async function runFeedbackForSlug(slug, provider) {
30
+ const question = findQuestion(slug);
39
31
  if (!question) {
40
- console.error(chalk.red(`Question not found: ${parsed.slug}`));
32
+ console.error(chalk.red(`Question not found: ${slug}`));
41
33
  return;
42
34
  }
43
- const provider = requireProvider(parsed.provider);
44
35
  const config = CATEGORIES[question.category];
45
36
  const isDesign = isDesignCategory(question.category);
46
37
  const readmePath = path.join(question.dir, "README.md");
@@ -53,7 +44,7 @@ async function run(args) {
53
44
  const notes = fs.existsSync(notesPath) ? fs.readFileSync(notesPath, "utf-8") : "";
54
45
  if (!notes.trim() || notes.includes("<!-- List the core features")) {
55
46
  console.error(chalk.yellow("Notes file appears to be empty. Write your design notes first!"));
56
- console.error(chalk.dim(`Edit: questions/${question.category}/${parsed.slug}/notes.md`));
47
+ console.error(chalk.dim(`Edit: questions/${question.category}/${slug}/notes.md`));
57
48
  return;
58
49
  }
59
50
  const designSubType = question.category === "design-fe" ? "frontend" : question.category === "design-be" ? "backend" : "full-stack";
@@ -102,7 +93,7 @@ ${solutionContent}
102
93
  ${testContent}`;
103
94
  }
104
95
  console.log(chalk.cyan(`
105
- --- LLM ${isDesign ? "Design" : "Code"} Review: ${parsed.slug} ---`));
96
+ --- LLM ${isDesign ? "Design" : "Code"} Review: ${slug} ---`));
106
97
  console.log(chalk.dim(`Provider: ${provider}
107
98
  `));
108
99
  const messages = [
@@ -116,7 +107,7 @@ ${testContent}`;
116
107
  fullResponse += chunk;
117
108
  }
118
109
  console.log("\n");
119
- const scorecard = readScorecard(question.category, parsed.slug);
110
+ const scorecard = readScorecard(question.category, slug);
120
111
  if (scorecard) {
121
112
  scorecard.llmFeedback = fullResponse;
122
113
  const scoreMatch = fullResponse.match(/Overall.*?(\d+(?:\.\d+)?)\s*\/\s*5/i);
@@ -124,10 +115,54 @@ ${testContent}`;
124
115
  const lastAttempt = scorecard.attempts[scorecard.attempts.length - 1];
125
116
  lastAttempt.llmScore = parseFloat(scoreMatch[1]);
126
117
  }
127
- writeScorecard(question.category, parsed.slug, scorecard);
118
+ writeScorecard(question.category, slug, scorecard);
128
119
  console.log(chalk.dim("Feedback saved to scorecard."));
129
120
  }
130
121
  }
122
+ async function run(args) {
123
+ const root = resolveWorkspaceRoot();
124
+ if (!isWorkspaceInitialized(root)) {
125
+ console.error(chalk.red("\nError: Workspace not initialized."));
126
+ console.error(chalk.dim("Run `ace init` in this folder first.\n"));
127
+ process.exit(1);
128
+ }
129
+ const parsed = parseArgs(args);
130
+ const provider = requireProvider(parsed.provider);
131
+ if (parsed.all) {
132
+ const questions = getAllQuestions();
133
+ if (questions.length === 0) {
134
+ console.log(chalk.yellow("No questions found. Create one first with `ace generate` or `ace add`."));
135
+ return;
136
+ }
137
+ console.log(chalk.cyan(`
138
+ Running feedback for ${questions.length} question(s)...
139
+ `));
140
+ for (let i = 0; i < questions.length; i++) {
141
+ const q = questions[i];
142
+ console.log(chalk.bold(`
143
+ [${i + 1}/${questions.length}] ${q.slug}`));
144
+ const { confirm } = await prompts({
145
+ type: "confirm",
146
+ name: "confirm",
147
+ message: `Run feedback for "${q.slug}"?`,
148
+ initial: true
149
+ });
150
+ if (!confirm) {
151
+ console.log(chalk.dim("Skipped."));
152
+ continue;
153
+ }
154
+ await runFeedbackForSlug(q.slug, provider);
155
+ }
156
+ console.log(chalk.green("\nCompleted feedback for all questions."));
157
+ return;
158
+ }
159
+ let selectedSlug = parsed.slug;
160
+ if (!selectedSlug) {
161
+ selectedSlug = await promptForSlug();
162
+ if (!selectedSlug) return;
163
+ }
164
+ await runFeedbackForSlug(selectedSlug, provider);
165
+ }
131
166
  export {
132
167
  run
133
168
  };
@@ -56,6 +56,9 @@ Question type: ${categoryConfig.type}`;
56
56
  return;
57
57
  }
58
58
  const slug = parsed.slug || slugify(parsed.title || topic);
59
+ if (parsed.solutionCode) {
60
+ console.log(chalk.dim("Note: Discarded LLM solutionCode; using signature-based stub."));
61
+ }
59
62
  const questionDir = scaffoldQuestion({
60
63
  title: parsed.title || topic,
61
64
  slug,
@@ -63,8 +66,7 @@ Question type: ${categoryConfig.type}`;
63
66
  difficulty,
64
67
  description: parsed.description || "",
65
68
  signature: parsed.signature,
66
- testCode: parsed.testCode,
67
- solutionCode: parsed.solutionCode
69
+ testCode: parsed.testCode
68
70
  });
69
71
  console.log(chalk.green(`
70
72
  Created: questions/${category}/${slug}/`));
@@ -116,7 +118,11 @@ async function brainstormMode(provider) {
116
118
  type: "select",
117
119
  name: "category",
118
120
  message: "Which category?",
119
- choices: CATEGORY_SLUGS.map((s) => ({ title: CATEGORIES[s].name, value: s }))
121
+ choices: CATEGORY_SLUGS.map((s) => ({
122
+ title: CATEGORIES[s].name,
123
+ description: CATEGORIES[s].hint,
124
+ value: s
125
+ }))
120
126
  });
121
127
  const { difficulty } = await prompts({
122
128
  type: "select",
@@ -158,6 +164,9 @@ ${brainstormSummary}`
158
164
  return;
159
165
  }
160
166
  const slug = parsed.slug || slugify(parsed.title || "brainstorm-question");
167
+ if (parsed.solutionCode) {
168
+ console.log(chalk.dim("Note: Discarded LLM solutionCode; using signature-based stub."));
169
+ }
161
170
  const questionDir = scaffoldQuestion({
162
171
  title: parsed.title,
163
172
  slug,
@@ -165,8 +174,7 @@ ${brainstormSummary}`
165
174
  difficulty,
166
175
  description: parsed.description || "",
167
176
  signature: parsed.signature,
168
- testCode: parsed.testCode,
169
- solutionCode: parsed.solutionCode
177
+ testCode: parsed.testCode
170
178
  });
171
179
  console.log(chalk.green(`
172
180
  Created: questions/${category}/${slug}/`));
@@ -187,22 +195,23 @@ async function run(args) {
187
195
  await brainstormMode(provider);
188
196
  return;
189
197
  }
190
- if (!parsed.topic) {
191
- console.error(chalk.red("Missing --topic. Use --brainstorm for interactive mode."));
192
- console.error(chalk.dim('Example: npm run ace generate -- --topic "debounce" --category js-ts --difficulty medium'));
193
- return;
194
- }
195
198
  let category = parsed.category;
196
199
  let difficulty = parsed.difficulty;
200
+ let topic = parsed.topic;
197
201
  if (!category) {
198
202
  const result = await prompts({
199
203
  type: "select",
200
204
  name: "category",
201
205
  message: "Which category?",
202
- choices: CATEGORY_SLUGS.map((s) => ({ title: CATEGORIES[s].name, value: s }))
206
+ choices: CATEGORY_SLUGS.map((s) => ({
207
+ title: CATEGORIES[s].name,
208
+ description: CATEGORIES[s].hint,
209
+ value: s
210
+ }))
203
211
  });
204
212
  category = result.category;
205
213
  }
214
+ if (!category) return;
206
215
  if (!difficulty) {
207
216
  const result = await prompts({
208
217
  type: "select",
@@ -216,8 +225,17 @@ async function run(args) {
216
225
  });
217
226
  difficulty = result.difficulty;
218
227
  }
219
- if (!category || !difficulty) return;
220
- await directMode(provider, parsed.topic, category, difficulty);
228
+ if (!difficulty) return;
229
+ if (!topic) {
230
+ const result = await prompts({
231
+ type: "text",
232
+ name: "topic",
233
+ message: "What topic do you want to practice?"
234
+ });
235
+ topic = result.topic;
236
+ }
237
+ if (!topic) return;
238
+ await directMode(provider, topic, category, difficulty);
221
239
  }
222
240
  export {
223
241
  run
@@ -1,11 +1,11 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
+ import { execSync } from "node:child_process";
3
4
  import chalk from "chalk";
4
5
  import { getQuestionsDir, isWorkspaceInitialized } from "../lib/paths.js";
5
6
  function parseArgs(args) {
6
7
  return {
7
- force: args.includes("--force"),
8
- writeScripts: args.includes("--write-scripts")
8
+ force: args.includes("--force")
9
9
  };
10
10
  }
11
11
  const VITEST_CONFIG_TEMPLATE = `import { defineConfig } from 'vitest/config';
@@ -22,13 +22,44 @@ export default defineConfig({
22
22
  `;
23
23
  const VITEST_SETUP_TEMPLATE = `import '@testing-library/jest-dom/vitest';
24
24
  `;
25
- const PACKAGE_JSON_SCRIPTS = {
26
- ace: "tsx cli/index.ts",
27
- test: "vitest run",
28
- "test:watch": "vitest"
25
+ const PACKAGE_JSON_TEMPLATE = {
26
+ name: "ace-workspace",
27
+ private: true,
28
+ type: "module",
29
+ scripts: {
30
+ test: "vitest run",
31
+ "test:watch": "vitest"
32
+ },
33
+ devDependencies: {
34
+ "@testing-library/jest-dom": "^6.9.1",
35
+ "@testing-library/react": "^16.3.2",
36
+ "@types/react": "^19.2.14",
37
+ "@types/react-dom": "^19.2.3",
38
+ "happy-dom": "^20.6.1",
39
+ "react": "^19.2.4",
40
+ "react-dom": "^19.2.4",
41
+ "typescript": "^5.9.3",
42
+ "vitest": "^4.0.18"
43
+ }
44
+ };
45
+ const TSCONFIG_TEMPLATE = {
46
+ compilerOptions: {
47
+ target: "ES2022",
48
+ module: "ESNext",
49
+ moduleResolution: "bundler",
50
+ esModuleInterop: true,
51
+ allowImportingTsExtensions: true,
52
+ noEmit: true,
53
+ strict: true,
54
+ skipLibCheck: true,
55
+ resolveJsonModule: true,
56
+ isolatedModules: true,
57
+ jsx: "react-jsx"
58
+ },
59
+ include: ["questions/**/*"]
29
60
  };
30
61
  async function run(args) {
31
- const { force, writeScripts } = parseArgs(args);
62
+ const { force } = parseArgs(args);
32
63
  const root = process.cwd();
33
64
  console.log(chalk.cyan("\n--- Initialize Workspace ---"));
34
65
  console.log(chalk.dim(`Workspace: ${root}
@@ -44,38 +75,54 @@ async function run(args) {
44
75
  fs.mkdirSync(questionsDir, { recursive: true });
45
76
  changes.push("Created questions/");
46
77
  }
47
- const vitestConfigPath = path.join(root, "vitest.config.ts");
48
- if (!fs.existsSync(vitestConfigPath) || force) {
49
- fs.writeFileSync(vitestConfigPath, VITEST_CONFIG_TEMPLATE, "utf-8");
50
- changes.push(force && fs.existsSync(vitestConfigPath) ? "Overwrote vitest.config.ts" : "Created vitest.config.ts");
51
- }
52
- const vitestSetupPath = path.join(root, "vitest.setup.ts");
53
- if (!fs.existsSync(vitestSetupPath) || force) {
54
- fs.writeFileSync(vitestSetupPath, VITEST_SETUP_TEMPLATE, "utf-8");
55
- changes.push(force && fs.existsSync(vitestSetupPath) ? "Overwrote vitest.setup.ts" : "Created vitest.setup.ts");
56
- }
57
78
  const packageJsonPath = path.join(root, "package.json");
58
- if (fs.existsSync(packageJsonPath)) {
59
- if (writeScripts) {
60
- try {
61
- const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
62
- pkg.scripts = pkg.scripts || {};
63
- let added = false;
64
- for (const [key, value] of Object.entries(PACKAGE_JSON_SCRIPTS)) {
65
- if (!pkg.scripts[key]) {
66
- pkg.scripts[key] = value;
67
- added = true;
68
- }
79
+ if (!fs.existsSync(packageJsonPath)) {
80
+ fs.writeFileSync(packageJsonPath, JSON.stringify(PACKAGE_JSON_TEMPLATE, null, 2) + "\n", "utf-8");
81
+ changes.push("Created package.json");
82
+ } else {
83
+ try {
84
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
85
+ let updated = false;
86
+ pkg.scripts = pkg.scripts || {};
87
+ for (const [key, value] of Object.entries(PACKAGE_JSON_TEMPLATE.scripts)) {
88
+ if (!pkg.scripts[key]) {
89
+ pkg.scripts[key] = value;
90
+ updated = true;
69
91
  }
70
- if (added) {
71
- fs.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
72
- changes.push("Added scripts to package.json");
92
+ }
93
+ pkg.devDependencies = pkg.devDependencies || {};
94
+ for (const [key, value] of Object.entries(PACKAGE_JSON_TEMPLATE.devDependencies)) {
95
+ if (!pkg.devDependencies[key]) {
96
+ pkg.devDependencies[key] = value;
97
+ updated = true;
73
98
  }
74
- } catch (err) {
75
- console.warn(chalk.yellow("Warning: Could not update package.json scripts"));
76
99
  }
100
+ if (updated) {
101
+ fs.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
102
+ changes.push("Updated package.json (scripts and devDependencies)");
103
+ }
104
+ } catch (err) {
105
+ console.warn(chalk.yellow("Warning: Could not update package.json"));
77
106
  }
78
107
  }
108
+ const tsconfigPath = path.join(root, "tsconfig.json");
109
+ const tsconfigExisted = fs.existsSync(tsconfigPath);
110
+ if (!tsconfigExisted || force) {
111
+ fs.writeFileSync(tsconfigPath, JSON.stringify(TSCONFIG_TEMPLATE, null, 2) + "\n", "utf-8");
112
+ changes.push(tsconfigExisted ? "Overwrote tsconfig.json" : "Created tsconfig.json");
113
+ }
114
+ const vitestConfigPath = path.join(root, "vitest.config.ts");
115
+ const vitestConfigExisted = fs.existsSync(vitestConfigPath);
116
+ if (!vitestConfigExisted || force) {
117
+ fs.writeFileSync(vitestConfigPath, VITEST_CONFIG_TEMPLATE, "utf-8");
118
+ changes.push(vitestConfigExisted ? "Overwrote vitest.config.ts" : "Created vitest.config.ts");
119
+ }
120
+ const vitestSetupPath = path.join(root, "vitest.setup.ts");
121
+ const vitestSetupExisted = fs.existsSync(vitestSetupPath);
122
+ if (!vitestSetupExisted || force) {
123
+ fs.writeFileSync(vitestSetupPath, VITEST_SETUP_TEMPLATE, "utf-8");
124
+ changes.push(vitestSetupExisted ? "Overwrote vitest.setup.ts" : "Created vitest.setup.ts");
125
+ }
79
126
  if (changes.length > 0) {
80
127
  console.log(chalk.green("\u2713 Workspace initialized:\n"));
81
128
  for (const change of changes) {
@@ -85,12 +132,18 @@ async function run(args) {
85
132
  console.log(chalk.green("\u2713 Workspace already initialized (no changes needed)"));
86
133
  }
87
134
  console.log();
135
+ console.log(chalk.cyan("--- Installing dependencies ---"));
136
+ try {
137
+ execSync("npm install", { cwd: root, stdio: "inherit" });
138
+ console.log(chalk.green("\n\u2713 Dependencies installed"));
139
+ } catch {
140
+ console.error(chalk.red("\n\u2717 npm install failed. Please run it manually."));
141
+ }
142
+ console.log();
88
143
  console.log(chalk.bold("Next steps:"));
89
- console.log(chalk.dim(" 1. Install dependencies (if not already installed):"));
90
- console.log(chalk.dim(" npm install vitest happy-dom @testing-library/jest-dom"));
91
- console.log(chalk.dim(" 2. Configure API keys:"));
144
+ console.log(chalk.dim(" 1. Configure API keys:"));
92
145
  console.log(chalk.dim(" ace setup"));
93
- console.log(chalk.dim(" 3. Generate or add questions:"));
146
+ console.log(chalk.dim(" 2. Generate or add questions:"));
94
147
  console.log(chalk.dim(' ace generate --topic "debounce"'));
95
148
  console.log(chalk.dim(" ace add"));
96
149
  console.log();
@@ -2,38 +2,16 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import prompts from "prompts";
4
4
  import chalk from "chalk";
5
- import { findQuestion, readScorecard, resetScorecard, startNewAttempt, writeScorecard } from "../lib/scorecard.js";
5
+ import { findQuestion, readScorecard, resetScorecard, startNewAttempt, writeScorecard, getAllQuestions, promptForSlug } from "../lib/scorecard.js";
6
6
  import { CATEGORIES, isDesignCategory } from "../lib/categories.js";
7
7
  import { getStubContent } from "../lib/scaffold.js";
8
8
  import { resolveWorkspaceRoot, isWorkspaceInitialized } from "../lib/paths.js";
9
- async function run(args) {
10
- const root = resolveWorkspaceRoot();
11
- if (!isWorkspaceInitialized(root)) {
12
- console.error(chalk.red("\nError: Workspace not initialized."));
13
- console.error(chalk.dim("Run `ace init` in this folder first.\n"));
14
- process.exit(1);
15
- }
16
- const slug = args.find((a) => !a.startsWith("--"));
17
- if (!slug) {
18
- console.error(chalk.red("Missing question slug."));
19
- console.error(chalk.dim("Usage: npm run ace reset <slug>"));
20
- return;
21
- }
9
+ function resetQuestion(slug) {
22
10
  const question = findQuestion(slug);
23
11
  if (!question) {
24
12
  console.error(chalk.red(`Question not found: ${slug}`));
25
13
  return;
26
14
  }
27
- const { confirm } = await prompts({
28
- type: "confirm",
29
- name: "confirm",
30
- message: `Reset "${slug}" to unanswered? This will clear your solution.`,
31
- initial: false
32
- });
33
- if (!confirm) {
34
- console.log(chalk.yellow("Cancelled."));
35
- return;
36
- }
37
15
  const config = CATEGORIES[question.category];
38
16
  const isDesign = isDesignCategory(question.category);
39
17
  if (isDesign) {
@@ -44,7 +22,6 @@ async function run(args) {
44
22
  }
45
23
  } else {
46
24
  for (const file of config.solutionFiles) {
47
- if (file === "index.html") continue;
48
25
  const filePath = path.join(question.dir, file);
49
26
  const stubContent = getStubContent(question.category, file);
50
27
  if (stubContent) {
@@ -59,8 +36,64 @@ async function run(args) {
59
36
  scorecard.status = "untouched";
60
37
  writeScorecard(question.category, slug, scorecard);
61
38
  }
62
- console.log(chalk.green(`
63
- Reset: questions/${question.category}/${slug}/`));
39
+ console.log(chalk.green(`Reset: questions/${question.category}/${slug}/`));
40
+ }
41
+ async function run(args) {
42
+ const root = resolveWorkspaceRoot();
43
+ if (!isWorkspaceInitialized(root)) {
44
+ console.error(chalk.red("\nError: Workspace not initialized."));
45
+ console.error(chalk.dim("Run `ace init` in this folder first.\n"));
46
+ process.exit(1);
47
+ }
48
+ const hasAll = args.includes("--all") || args.includes("all");
49
+ const slug = args.find((a) => !a.startsWith("--") && a !== "all");
50
+ if (hasAll) {
51
+ const questions = getAllQuestions();
52
+ if (questions.length === 0) {
53
+ console.log(chalk.yellow("No questions found. Create one first with `ace generate` or `ace add`."));
54
+ return;
55
+ }
56
+ const { confirm: confirm2 } = await prompts({
57
+ type: "confirm",
58
+ name: "confirm",
59
+ message: `Reset ALL ${questions.length} question(s) to unanswered? This will clear all solutions.`,
60
+ initial: false
61
+ });
62
+ if (!confirm2) {
63
+ console.log(chalk.yellow("Cancelled."));
64
+ return;
65
+ }
66
+ console.log(chalk.cyan(`
67
+ Resetting ${questions.length} question(s)...
68
+ `));
69
+ for (const q of questions) {
70
+ resetQuestion(q.slug);
71
+ }
72
+ console.log(chalk.green("\nCompleted reset for all questions."));
73
+ console.log(chalk.dim("Solution files restored to stubs. Scorecards updated."));
74
+ return;
75
+ }
76
+ let selectedSlug = slug;
77
+ if (!selectedSlug) {
78
+ selectedSlug = await promptForSlug();
79
+ if (!selectedSlug) return;
80
+ }
81
+ const question = findQuestion(selectedSlug);
82
+ if (!question) {
83
+ console.error(chalk.red(`Question not found: ${selectedSlug}`));
84
+ return;
85
+ }
86
+ const { confirm } = await prompts({
87
+ type: "confirm",
88
+ name: "confirm",
89
+ message: `Reset "${selectedSlug}" to unanswered? This will clear your solution.`,
90
+ initial: false
91
+ });
92
+ if (!confirm) {
93
+ console.log(chalk.yellow("Cancelled."));
94
+ return;
95
+ }
96
+ resetQuestion(selectedSlug);
64
97
  console.log(chalk.dim("Solution files restored to stubs. Scorecard updated."));
65
98
  }
66
99
  export {