@vibe-interviewing/core 0.1.0 → 0.2.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.js CHANGED
@@ -1,3 +1,17 @@
1
+ import {
2
+ AIToolNotFoundError,
3
+ GitCloneError,
4
+ InvalidSessionCodeError,
5
+ ScenarioNotFoundError,
6
+ ScenarioValidationError,
7
+ SessionNotFoundError,
8
+ SetupError,
9
+ VibeError,
10
+ decodeSessionCode,
11
+ encodeSessionCode,
12
+ isCloudSessionCode
13
+ } from "./chunk-5PTYUK4Z.js";
14
+
1
15
  // src/scenario/types.ts
2
16
  import { z } from "zod";
3
17
  var AIRulesSchema = z.object({
@@ -22,11 +36,14 @@ var PatchSchema = z.object({
22
36
  /** The replacement text (with the bug) */
23
37
  replace: z.string()
24
38
  });
39
+ var ScenarioTypeSchema = z.enum(["debug", "feature", "refactor"]).default("debug");
25
40
  var ScenarioConfigSchema = z.object({
26
41
  /** Scenario display name */
27
42
  name: z.string(),
28
- /** One-line description */
43
+ /** One-line description (candidate-visible — describe symptoms/task, never the root cause or solution) */
29
44
  description: z.string(),
45
+ /** Scenario type: debug (find a bug), feature (build something), refactor (improve code) */
46
+ type: ScenarioTypeSchema,
30
47
  /** Difficulty level */
31
48
  difficulty: z.enum(["easy", "medium", "hard"]),
32
49
  /** Estimated time (e.g., "30-45m") */
@@ -41,12 +58,16 @@ var ScenarioConfigSchema = z.object({
41
58
  setup: z.array(z.string()).default([]),
42
59
  /** Find-and-replace patches to inject the bug after cloning */
43
60
  patch: z.array(PatchSchema).default([]),
61
+ /** Files or directories to delete after cloning (globs relative to repo root) */
62
+ delete_files: z.array(z.string()).default([]),
44
63
  /** Briefing shown to the candidate (written like a team lead message) */
45
64
  briefing: z.string(),
46
65
  /** AI behavioral rules (injected via system prompt, hidden from candidate) */
47
66
  ai_rules: AIRulesSchema,
48
- /** Interviewer reference — what the fix looks like */
49
- solution: z.string(),
67
+ /** Interviewer reference — what the fix/implementation looks like */
68
+ solution: z.string().optional(),
69
+ /** Acceptance criteria for feature scenarios (concrete, testable requirements) */
70
+ acceptance_criteria: z.array(z.string()).optional(),
50
71
  /** Evaluation rubric */
51
72
  evaluation: EvaluationSchema.optional(),
52
73
  /** License of the original project */
@@ -56,72 +77,6 @@ var ScenarioConfigSchema = z.object({
56
77
  // src/scenario/loader.ts
57
78
  import { readFile } from "fs/promises";
58
79
  import { parse as parseYaml } from "yaml";
59
-
60
- // src/errors.ts
61
- var VibeError = class extends Error {
62
- constructor(message, code, hint) {
63
- super(message);
64
- this.code = code;
65
- this.hint = hint;
66
- this.name = "VibeError";
67
- }
68
- };
69
- var ScenarioNotFoundError = class extends VibeError {
70
- constructor(name) {
71
- super(
72
- `Scenario not found: ${name}`,
73
- "SCENARIO_NOT_FOUND",
74
- "Run `vibe-interviewing list` to see available scenarios"
75
- );
76
- }
77
- };
78
- var ScenarioValidationError = class extends VibeError {
79
- constructor(message, issues) {
80
- super(`Invalid scenario config: ${message}`, "SCENARIO_VALIDATION_ERROR", issues.join("\n"));
81
- this.issues = issues;
82
- }
83
- };
84
- var AIToolNotFoundError = class _AIToolNotFoundError extends VibeError {
85
- static installHints = {
86
- "claude-code": "Install Claude Code: npm install -g @anthropic-ai/claude-code"
87
- };
88
- constructor(tool) {
89
- super(
90
- `${tool} is not installed`,
91
- "AI_TOOL_NOT_FOUND",
92
- _AIToolNotFoundError.installHints[tool] ?? `Install ${tool} and try again`
93
- );
94
- }
95
- };
96
- var SessionNotFoundError = class extends VibeError {
97
- constructor(id) {
98
- super(
99
- `Session not found: ${id}`,
100
- "SESSION_NOT_FOUND",
101
- "Run `vibe-interviewing list` to see active sessions"
102
- );
103
- }
104
- };
105
- var GitCloneError = class extends VibeError {
106
- constructor(repo, reason) {
107
- super(
108
- `Failed to clone repository: ${repo}${reason ? ` \u2014 ${reason}` : ""}`,
109
- "GIT_CLONE_FAILED",
110
- "Check the repo URL and your network connection"
111
- );
112
- }
113
- };
114
- var SetupError = class extends VibeError {
115
- constructor(command, reason) {
116
- super(
117
- `Setup command failed: ${command}${reason ? ` \u2014 ${reason}` : ""}`,
118
- "SETUP_FAILED",
119
- "Check the scenario setup commands and try again"
120
- );
121
- }
122
- };
123
-
124
- // src/scenario/loader.ts
125
80
  import { existsSync } from "fs";
126
81
  async function loadScenarioConfig(configPath) {
127
82
  if (!existsSync(configPath)) {
@@ -140,6 +95,14 @@ function generateSystemPrompt(config) {
140
95
  const lines = [];
141
96
  lines.push(`# Interview Scenario: ${config.name}`);
142
97
  lines.push("");
98
+ const typeDescriptions = {
99
+ debug: "The candidate is debugging a bug in this codebase. Guide them through the debugging process without revealing the answer.",
100
+ feature: "The candidate is building a new feature. Help them understand the requirements, plan their approach, and implement it. Offer architectural guidance but let them drive the implementation.",
101
+ refactor: "The candidate is improving existing code. Help them identify issues and plan improvements. Encourage them to explain their reasoning for changes."
102
+ };
103
+ lines.push("## Scenario Type");
104
+ lines.push(typeDescriptions[config.type] ?? typeDescriptions["debug"]);
105
+ lines.push("");
143
106
  lines.push("## Your Role");
144
107
  lines.push(config.ai_rules.role.trim());
145
108
  lines.push("");
@@ -148,13 +111,18 @@ function generateSystemPrompt(config) {
148
111
  lines.push(`- ${rule}`);
149
112
  }
150
113
  lines.push("");
151
- lines.push("## Knowledge (DO NOT share directly with the candidate)");
114
+ const knowledgeHeaders = {
115
+ debug: "Knowledge (DO NOT share directly with the candidate)",
116
+ feature: "Implementation Context (DO NOT share directly with the candidate)",
117
+ refactor: "Improvement Context (DO NOT share directly with the candidate)"
118
+ };
119
+ lines.push(`## ${knowledgeHeaders[config.type] ?? knowledgeHeaders["debug"]}`);
152
120
  lines.push(config.ai_rules.knowledge.trim());
153
121
  return lines.join("\n");
154
122
  }
155
123
 
156
124
  // src/scenario/validator.ts
157
- async function validateScenario(config) {
125
+ function validateScenario(config) {
158
126
  const warnings = [];
159
127
  const errors = [];
160
128
  if (!config.briefing.trim()) {
@@ -168,12 +136,29 @@ async function validateScenario(config) {
168
136
  }
169
137
  if (!config.commit.trim()) {
170
138
  errors.push("commit cannot be empty \u2014 pin to a specific commit SHA for reproducibility");
139
+ } else if (!/^[0-9a-f]{7,40}$/i.test(config.commit.trim())) {
140
+ errors.push(
141
+ "commit must be a hex SHA (7-40 characters) \u2014 branch/tag names are not allowed for reproducibility"
142
+ );
171
143
  }
172
144
  if (config.ai_rules.rules.length === 0) {
173
145
  warnings.push("ai_rules.rules is empty \u2014 the AI will have no behavioral constraints");
174
146
  }
175
- if (!config.solution.trim()) {
176
- warnings.push("solution is empty \u2014 interviewers will have no solution reference");
147
+ if (config.type === "debug") {
148
+ if (config.patch.length === 0) {
149
+ warnings.push("debug scenario has no patches \u2014 a bug must be injected via patch");
150
+ }
151
+ if (!config.solution?.trim()) {
152
+ warnings.push("solution is empty \u2014 interviewers will have no solution reference");
153
+ }
154
+ }
155
+ if (config.type === "feature") {
156
+ const hasCriteria = config.acceptance_criteria && config.acceptance_criteria.length > 0 || config.evaluation && config.evaluation.criteria && config.evaluation.criteria.length > 0;
157
+ if (!hasCriteria) {
158
+ warnings.push(
159
+ "feature scenario has no acceptance_criteria or evaluation.criteria \u2014 candidates need a definition of done"
160
+ );
161
+ }
177
162
  }
178
163
  if (!config.evaluation) {
179
164
  warnings.push("No evaluation criteria defined");
@@ -184,8 +169,8 @@ async function validateScenario(config) {
184
169
  errors
185
170
  };
186
171
  }
187
- async function validateScenarioOrThrow(config) {
188
- const result = await validateScenario(config);
172
+ function validateScenarioOrThrow(config) {
173
+ const result = validateScenario(config);
189
174
  if (!result.valid) {
190
175
  throw new ScenarioValidationError("scenario validation failed", result.errors);
191
176
  }
@@ -230,6 +215,10 @@ async function discoverBuiltInScenarios() {
230
215
  config,
231
216
  builtIn: true
232
217
  });
218
+ } else {
219
+ console.warn(
220
+ `Warning: scenario "${entry.name}" listed in registry but ${scenarioConfigPath} not found`
221
+ );
233
222
  }
234
223
  }
235
224
  return scenarios;
@@ -326,6 +315,9 @@ var SessionRecorder = class _SessionRecorder {
326
315
  static fromJSON(data) {
327
316
  const recorder = new _SessionRecorder();
328
317
  Object.defineProperty(recorder, "startedAt", { value: data.startedAt });
318
+ Object.defineProperty(recorder, "startTime", {
319
+ value: new Date(data.startedAt).getTime()
320
+ });
329
321
  for (const event of data.events) {
330
322
  recorder.events.push({ ...event });
331
323
  }
@@ -496,17 +488,7 @@ async function listActiveSessions() {
496
488
 
497
489
  // src/session/types.ts
498
490
  function toStoredSession(session) {
499
- return {
500
- id: session.id,
501
- scenarioName: session.scenarioName,
502
- status: session.status,
503
- workdir: session.workdir,
504
- systemPromptPath: session.systemPromptPath,
505
- aiTool: session.aiTool,
506
- createdAt: session.createdAt,
507
- startedAt: session.startedAt,
508
- completedAt: session.completedAt
509
- };
491
+ return { ...session };
510
492
  }
511
493
 
512
494
  // src/session/manager.ts
@@ -520,12 +502,13 @@ var SessionManager = class {
520
502
  * Flow:
521
503
  * 1. Clone the repo at a pinned commit
522
504
  * 2. Apply bug patches (find/replace in source files)
523
- * 3. Wipe git history so the candidate can't diff to find the bug
524
- * 4. Remove scenario.yaml from workspace (interviewer-only)
525
- * 5. Write BRIEFING.md and system prompt
526
- * 6. Run setup commands (npm install, etc.)
505
+ * 3. Delete files excluded by the scenario (e.g., tests that reveal the bug)
506
+ * 4. Wipe git history so the candidate can't diff to find the bug
507
+ * 5. Remove scenario.yaml from workspace (interviewer-only)
508
+ * 6. Write BRIEFING.md and system prompt
509
+ * 7. Run setup commands (npm install, etc.)
527
510
  */
528
- async createSession(config, workdir, onProgress) {
511
+ async createSession(config, workdir, onProgress, options) {
529
512
  const id = randomBytes(4).toString("hex");
530
513
  const sessionDir = workdir ?? join5(homedir3(), "vibe-sessions", `${config.name}-${id}`);
531
514
  const session = {
@@ -542,7 +525,7 @@ var SessionManager = class {
542
525
  for (const p of config.patch) {
543
526
  const filePath = join5(sessionDir, p.file);
544
527
  const content = await readFile6(filePath, "utf-8");
545
- const patched = content.replace(p.find, p.replace);
528
+ const patched = content.replaceAll(p.find, p.replace);
546
529
  if (patched === content) {
547
530
  throw new SetupError(
548
531
  `patch ${p.file}`,
@@ -551,12 +534,18 @@ var SessionManager = class {
551
534
  }
552
535
  await writeFile3(filePath, patched);
553
536
  }
537
+ if (config.delete_files.length > 0) {
538
+ onProgress?.("Removing excluded files...");
539
+ for (const target of config.delete_files) {
540
+ await rm(join5(sessionDir, target), { recursive: true, force: true });
541
+ }
542
+ }
554
543
  onProgress?.("Preparing workspace...");
555
544
  await rm(join5(sessionDir, ".git"), { recursive: true, force: true });
556
- execSync('git init && git add -A && git commit -m "initial"', {
557
- cwd: sessionDir,
558
- stdio: "ignore"
559
- });
545
+ execSync(
546
+ 'git init && git add -A && git -c user.name=vibe -c user.email=vibe@local commit -m "initial"',
547
+ { cwd: sessionDir, stdio: "ignore" }
548
+ );
560
549
  await rm(join5(sessionDir, "scenario.yaml"), { force: true });
561
550
  await writeFile3(join5(sessionDir, "BRIEFING.md"), `# Interview Briefing
562
551
 
@@ -566,13 +555,15 @@ ${config.briefing}`);
566
555
  const systemPromptPath = join5(promptDir, `${id}.md`);
567
556
  await writeFile3(systemPromptPath, generateSystemPrompt(config));
568
557
  session.systemPromptPath = systemPromptPath;
569
- session.status = "setting-up";
570
- for (const cmd of config.setup) {
571
- onProgress?.(`Running: ${cmd}`);
572
- try {
573
- execSync(cmd, { cwd: sessionDir, stdio: "pipe", timeout: 3e5 });
574
- } catch (err) {
575
- throw new SetupError(cmd, err instanceof Error ? err.message : String(err));
558
+ if (!options?.skipSetup) {
559
+ session.status = "setting-up";
560
+ for (const cmd of config.setup) {
561
+ onProgress?.(`Running: ${cmd}`);
562
+ try {
563
+ execSync(cmd, { cwd: sessionDir, stdio: "pipe", timeout: 3e5 });
564
+ } catch (err) {
565
+ throw new SetupError(cmd, err instanceof Error ? err.message : String(err));
566
+ }
576
567
  }
577
568
  }
578
569
  session.status = "running";
@@ -614,6 +605,7 @@ export {
614
605
  AIToolNotFoundError,
615
606
  ClaudeCodeLauncher,
616
607
  GitCloneError,
608
+ InvalidSessionCodeError,
617
609
  ScenarioConfigSchema,
618
610
  ScenarioNotFoundError,
619
611
  ScenarioValidationError,
@@ -622,14 +614,17 @@ export {
622
614
  SessionRecorder,
623
615
  SetupError,
624
616
  VibeError,
617
+ decodeSessionCode,
625
618
  deleteSession,
626
619
  detectInstalledTools,
627
620
  discoverAllScenarios,
628
621
  discoverBuiltInScenarios,
622
+ encodeSessionCode,
629
623
  generateSystemPrompt,
630
624
  getAllLaunchers,
631
625
  getLauncher,
632
626
  importRepo,
627
+ isCloudSessionCode,
633
628
  listActiveSessions,
634
629
  listSessions,
635
630
  loadScenarioConfig,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/scenario/types.ts","../src/scenario/loader.ts","../src/errors.ts","../src/scenario/validator.ts","../src/scenario/registry.ts","../src/scenario/importer.ts","../src/launcher/claude-code.ts","../src/session/recorder.ts","../src/launcher/detector.ts","../src/session/manager.ts","../src/session/store.ts","../src/session/types.ts"],"sourcesContent":["import { z } from 'zod'\n\nconst AIRulesSchema = z.object({\n /** Role description for the AI assistant */\n role: z.string(),\n /** Behavioral rules (e.g., \"don't reveal the answer\") */\n rules: z.array(z.string()),\n /** Knowledge about the bug/solution (hidden from candidate) */\n knowledge: z.string(),\n})\n\nconst EvaluationSchema = z.object({\n /** Evaluation criteria for the interviewer */\n criteria: z.array(z.string()),\n /** Description of the expected fix */\n expected_fix: z.string().optional(),\n})\n\n/** A file modification to inject the bug */\nconst PatchSchema = z.object({\n /** Path to the file relative to repo root */\n file: z.string(),\n /** The original text to find */\n find: z.string(),\n /** The replacement text (with the bug) */\n replace: z.string(),\n})\n\n/** Full scenario configuration schema */\nexport const ScenarioConfigSchema = z.object({\n /** Scenario display name */\n name: z.string(),\n /** One-line description */\n description: z.string(),\n /** Difficulty level */\n difficulty: z.enum(['easy', 'medium', 'hard']),\n /** Estimated time (e.g., \"30-45m\") */\n estimated_time: z.string(),\n /** Searchable tags */\n tags: z.array(z.string()).default([]),\n\n /** GitHub repo URL or owner/repo shorthand */\n repo: z.string(),\n /** Commit SHA to pin the clone to (ensures reproducibility) */\n commit: z.string(),\n /** Shell commands to run after cloning (e.g., [\"npm install\"]) */\n setup: z.array(z.string()).default([]),\n\n /** Find-and-replace patches to inject the bug after cloning */\n patch: z.array(PatchSchema).default([]),\n\n /** Briefing shown to the candidate (written like a team lead message) */\n briefing: z.string(),\n /** AI behavioral rules (injected via system prompt, hidden from candidate) */\n ai_rules: AIRulesSchema,\n /** Interviewer reference — what the fix looks like */\n solution: z.string(),\n\n /** Evaluation rubric */\n evaluation: EvaluationSchema.optional(),\n /** License of the original project */\n license: z.string().optional(),\n})\n\nexport type ScenarioConfig = z.infer<typeof ScenarioConfigSchema>\nexport type AIRules = z.infer<typeof AIRulesSchema>\nexport type Evaluation = z.infer<typeof EvaluationSchema>\n\n/** Metadata about a discovered scenario */\nexport interface ScenarioInfo {\n /** Scenario name */\n name: string\n /** Parsed config */\n config: ScenarioConfig\n /** Whether this is a built-in scenario */\n builtIn: boolean\n}\n","import { readFile } from 'node:fs/promises'\nimport { parse as parseYaml } from 'yaml'\nimport { ScenarioNotFoundError, ScenarioValidationError } from '../errors.js'\nimport { ScenarioConfigSchema, type ScenarioConfig } from './types.js'\nimport { existsSync } from 'node:fs'\n\n/**\n * Load and parse a scenario config from a YAML file.\n *\n * @param configPath - Absolute path to the scenario.yaml file\n * @returns The parsed and validated scenario config\n */\nexport async function loadScenarioConfig(configPath: string): Promise<ScenarioConfig> {\n if (!existsSync(configPath)) {\n throw new ScenarioNotFoundError(configPath)\n }\n\n const raw = await readFile(configPath, 'utf-8')\n const parsed: unknown = parseYaml(raw)\n\n const result = ScenarioConfigSchema.safeParse(parsed)\n if (!result.success) {\n const issues = result.error.issues.map((i) => ` ${i.path.join('.')}: ${i.message}`)\n throw new ScenarioValidationError('validation failed', issues)\n }\n\n return result.data\n}\n\n/**\n * Generate a system prompt string from a scenario's ai_rules.\n *\n * This prompt is injected into the AI tool via --append-system-prompt\n * and is hidden from the candidate.\n *\n * @param config - The scenario config containing ai_rules\n * @returns The formatted system prompt\n */\nexport function generateSystemPrompt(config: ScenarioConfig): string {\n const lines: string[] = []\n\n lines.push(`# Interview Scenario: ${config.name}`)\n lines.push('')\n lines.push('## Your Role')\n lines.push(config.ai_rules.role.trim())\n lines.push('')\n lines.push('## Rules')\n for (const rule of config.ai_rules.rules) {\n lines.push(`- ${rule}`)\n }\n lines.push('')\n lines.push('## Knowledge (DO NOT share directly with the candidate)')\n lines.push(config.ai_rules.knowledge.trim())\n\n return lines.join('\\n')\n}\n","/** Base error class for all vibe-interviewing errors */\nexport class VibeError extends Error {\n constructor(\n message: string,\n public readonly code: string,\n public readonly hint?: string,\n ) {\n super(message)\n this.name = 'VibeError'\n }\n}\n\nexport class ScenarioNotFoundError extends VibeError {\n constructor(name: string) {\n super(\n `Scenario not found: ${name}`,\n 'SCENARIO_NOT_FOUND',\n 'Run `vibe-interviewing list` to see available scenarios',\n )\n }\n}\n\nexport class ScenarioValidationError extends VibeError {\n constructor(\n message: string,\n public readonly issues: string[],\n ) {\n super(`Invalid scenario config: ${message}`, 'SCENARIO_VALIDATION_ERROR', issues.join('\\n'))\n }\n}\n\nexport class AIToolNotFoundError extends VibeError {\n static readonly installHints: Record<string, string> = {\n 'claude-code': 'Install Claude Code: npm install -g @anthropic-ai/claude-code',\n }\n\n constructor(tool: string) {\n super(\n `${tool} is not installed`,\n 'AI_TOOL_NOT_FOUND',\n AIToolNotFoundError.installHints[tool] ?? `Install ${tool} and try again`,\n )\n }\n}\n\nexport class SessionNotFoundError extends VibeError {\n constructor(id: string) {\n super(\n `Session not found: ${id}`,\n 'SESSION_NOT_FOUND',\n 'Run `vibe-interviewing list` to see active sessions',\n )\n }\n}\n\nexport class GitCloneError extends VibeError {\n constructor(repo: string, reason?: string) {\n super(\n `Failed to clone repository: ${repo}${reason ? ` — ${reason}` : ''}`,\n 'GIT_CLONE_FAILED',\n 'Check the repo URL and your network connection',\n )\n }\n}\n\nexport class SetupError extends VibeError {\n constructor(command: string, reason?: string) {\n super(\n `Setup command failed: ${command}${reason ? ` — ${reason}` : ''}`,\n 'SETUP_FAILED',\n 'Check the scenario setup commands and try again',\n )\n }\n}\n","import { ScenarioValidationError } from '../errors.js'\nimport type { ScenarioConfig } from './types.js'\n\n/** Result of validating a scenario configuration */\nexport interface ValidationResult {\n /** Whether the scenario is valid (no errors) */\n valid: boolean\n /** Non-fatal issues that should be addressed */\n warnings: string[]\n /** Fatal issues that prevent the scenario from running */\n errors: string[]\n}\n\n/**\n * Validate a scenario config for completeness and correctness.\n *\n * @param config - The scenario config to validate\n * @returns Validation result with errors and warnings\n */\nexport async function validateScenario(config: ScenarioConfig): Promise<ValidationResult> {\n const warnings: string[] = []\n const errors: string[] = []\n\n // Check briefing is not empty\n if (!config.briefing.trim()) {\n errors.push('Briefing cannot be empty')\n }\n\n // Check AI rules\n if (!config.ai_rules.role.trim()) {\n errors.push('ai_rules.role cannot be empty')\n }\n\n // Check repo is not empty\n if (!config.repo.trim()) {\n errors.push('repo cannot be empty')\n }\n\n // Check commit is pinned\n if (!config.commit.trim()) {\n errors.push('commit cannot be empty — pin to a specific commit SHA for reproducibility')\n }\n\n // Warnings for non-critical missing content\n if (config.ai_rules.rules.length === 0) {\n warnings.push('ai_rules.rules is empty — the AI will have no behavioral constraints')\n }\n\n if (!config.solution.trim()) {\n warnings.push('solution is empty — interviewers will have no solution reference')\n }\n\n if (!config.evaluation) {\n warnings.push('No evaluation criteria defined')\n }\n\n return {\n valid: errors.length === 0,\n warnings,\n errors,\n }\n}\n\n/**\n * Validate a scenario config and throw if invalid.\n *\n * @param config - The scenario config to validate\n * @returns Validation result (only returned if valid)\n * @throws ScenarioValidationError if the config has errors\n */\nexport async function validateScenarioOrThrow(config: ScenarioConfig): Promise<ValidationResult> {\n const result = await validateScenario(config)\n if (!result.valid) {\n throw new ScenarioValidationError('scenario validation failed', result.errors)\n }\n return result\n}\n","import { readFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { existsSync } from 'node:fs'\nimport { parse as parseYaml } from 'yaml'\nimport { loadScenarioConfig } from './loader.js'\nimport type { ScenarioInfo } from './types.js'\n\n/** Shape of a single entry in registry.yaml */\ninterface RegistryEntry {\n name: string\n repo: string\n commit: string\n description: string\n difficulty: 'easy' | 'medium' | 'hard'\n estimated_time: string\n}\n\n/** Shape of the registry.yaml file */\ninterface RegistryFile {\n scenarios: RegistryEntry[]\n}\n\n/**\n * Get the path to the scenarios package directory.\n *\n * Resolves via the @vibe-interviewing/scenarios package, which works both\n * in the monorepo (workspace link) and when installed from npm.\n */\nasync function getScenariosPackagePath(): Promise<string> {\n try {\n const { getScenariosDir } = await import('@vibe-interviewing/scenarios')\n return getScenariosDir()\n } catch {\n // Fallback: try relative to process.cwd() (monorepo root)\n const fromCwd = join(process.cwd(), 'packages', 'scenarios')\n if (existsSync(fromCwd)) {\n return fromCwd\n }\n throw new Error('Could not locate @vibe-interviewing/scenarios package')\n }\n}\n\n/**\n * Discover built-in scenarios from the registry.yaml file in the scenarios package.\n *\n * Each entry in the registry points to a scenario directory containing a full\n * scenario.yaml. The registry provides quick lookup metadata, while the full\n * config is loaded from the scenario's own config file.\n *\n * @returns Array of discovered scenario info objects\n */\nexport async function discoverBuiltInScenarios(): Promise<ScenarioInfo[]> {\n const scenariosPath = await getScenariosPackagePath()\n const registryPath = join(scenariosPath, 'registry.yaml')\n\n if (!existsSync(registryPath)) {\n return []\n }\n\n const raw = await readFile(registryPath, 'utf-8')\n const registry = parseYaml(raw) as RegistryFile\n\n if (!registry.scenarios || !Array.isArray(registry.scenarios)) {\n return []\n }\n\n const scenarios: ScenarioInfo[] = []\n\n for (const entry of registry.scenarios) {\n // Load the full config from the scenario's own directory\n const scenarioConfigPath = join(scenariosPath, entry.name, 'scenario.yaml')\n\n if (existsSync(scenarioConfigPath)) {\n const config = await loadScenarioConfig(scenarioConfigPath)\n scenarios.push({\n name: entry.name,\n config,\n builtIn: true,\n })\n }\n }\n\n return scenarios\n}\n\n/**\n * Discover all available scenarios.\n *\n * Currently returns only built-in scenarios from the registry.\n * Local scenario support can be added later.\n *\n * @returns Array of all discovered scenario info objects\n */\nexport async function discoverAllScenarios(): Promise<ScenarioInfo[]> {\n return discoverBuiltInScenarios()\n}\n","import { mkdtemp } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { tmpdir } from 'node:os'\nimport { GitCloneError } from '../errors.js'\n\n/**\n * Import a repository by cloning it to a local directory.\n *\n * For commit SHAs, uses a shallow fetch to avoid downloading full history.\n *\n * @param repoUrl - GitHub URL, SSH URL, or owner/repo shorthand\n * @param targetPath - Directory to clone into (defaults to a temp directory)\n * @param ref - Optional branch, tag, or commit to checkout\n * @returns The path to the cloned directory\n */\nexport async function importRepo(repoUrl: string, targetPath?: string, ref?: string): Promise<string> {\n const { simpleGit } = await import('simple-git')\n\n // Normalize shorthand (owner/repo -> https://github.com/owner/repo)\n const url = normalizeRepoUrl(repoUrl)\n\n // Clone to target path or temp directory\n const dest = targetPath ?? (await mkdtemp(join(tmpdir(), 'vibe-import-')))\n\n const git = simpleGit()\n\n try {\n if (ref && /^[0-9a-f]{7,40}$/i.test(ref)) {\n // Commit SHA — init + shallow fetch to avoid downloading full history\n await git.init([dest])\n const repoGit = simpleGit(dest)\n await repoGit.addRemote('origin', url)\n await repoGit.fetch(['origin', ref, '--depth', '1'])\n await repoGit.checkout(['FETCH_HEAD'])\n } else if (ref) {\n // Branch or tag — shallow clone\n await git.clone(url, dest, ['--depth', '1', '--branch', ref])\n } else {\n await git.clone(url, dest, ['--depth', '1'])\n }\n } catch (err) {\n throw new GitCloneError(url, err instanceof Error ? err.message : String(err))\n }\n\n return dest\n}\n\n/** Normalize various repo URL formats to full HTTPS URLs */\nfunction normalizeRepoUrl(input: string): string {\n // Already a full URL\n if (input.startsWith('http://') || input.startsWith('https://')) {\n return input\n }\n\n // Git SSH format\n if (input.startsWith('git@')) {\n return input.replace('git@github.com:', 'https://github.com/').replace(/\\.git$/, '')\n }\n\n // Shorthand: owner/repo\n if (/^[a-zA-Z0-9_-]+\\/[a-zA-Z0-9._-]+$/.test(input)) {\n return `https://github.com/${input}`\n }\n\n return input\n}\n","import { spawn } from 'node:child_process'\nimport { execFile } from 'node:child_process'\nimport { promisify } from 'node:util'\nimport { readFile } from 'node:fs/promises'\nimport type { AIToolLauncher, LaunchConfig, LaunchedProcess } from './types.js'\nimport { SessionRecorder } from '../session/recorder.js'\n\nconst execFileAsync = promisify(execFile)\n\n/** Launcher for Anthropic's Claude Code CLI */\nexport class ClaudeCodeLauncher implements AIToolLauncher {\n readonly name = 'claude-code'\n readonly displayName = 'Claude Code'\n\n /** Check if the claude CLI is installed */\n async isInstalled(): Promise<boolean> {\n try {\n await execFileAsync('claude', ['--version'])\n return true\n } catch {\n return false\n }\n }\n\n /** Get the installed Claude Code version */\n async getVersion(): Promise<string | null> {\n try {\n const { stdout } = await execFileAsync('claude', ['--version'])\n return stdout.trim()\n } catch {\n return null\n }\n }\n\n /** Launch Claude Code in the given working directory with the provided config */\n async launch(workdir: string, config: LaunchConfig): Promise<LaunchedProcess> {\n const args: string[] = []\n\n // Inject hidden system prompt\n const systemPrompt = await readFile(config.systemPromptPath, 'utf-8')\n args.push('--append-system-prompt', systemPrompt)\n\n // Set permission mode\n args.push('--permission-mode', config.permissionMode ?? 'default')\n\n // Set session name\n args.push('--name', `Interview: ${config.scenarioName}`)\n\n // Set model if specified\n if (config.model) {\n args.push('--model', config.model)\n }\n\n // Disallow tools for fairness\n if (config.disallowedTools && config.disallowedTools.length > 0) {\n args.push('--disallowedTools', ...config.disallowedTools)\n }\n\n // When recording, pipe stdout/stderr through the recorder\n const useRecording = config.recording === true\n const recorder = useRecording ? new SessionRecorder() : undefined\n\n // Spawn claude process\n const proc = spawn('claude', args, {\n cwd: workdir,\n stdio: useRecording ? ['inherit', 'pipe', 'pipe'] : 'inherit',\n env: { ...process.env },\n })\n\n // Forward piped output to the terminal and record it\n if (useRecording && recorder) {\n proc.stdout?.on('data', (chunk: Buffer) => {\n const text = chunk.toString('utf-8')\n recorder.record('stdout', text)\n process.stdout.write(chunk)\n })\n\n proc.stderr?.on('data', (chunk: Buffer) => {\n const text = chunk.toString('utf-8')\n recorder.record('stderr', text)\n process.stderr.write(chunk)\n })\n }\n\n return {\n wait: () =>\n new Promise((resolve) => {\n proc.on('exit', (code) => resolve({ exitCode: code ?? 0 }))\n }),\n kill: async () => {\n proc.kill('SIGTERM')\n },\n recorder,\n }\n }\n}\n","import { readFile, writeFile, readdir, mkdir } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { homedir } from 'node:os'\nimport { existsSync } from 'node:fs'\n\n/** Event types that can be captured during a session */\nexport type SessionEventType = 'stdout' | 'stderr' | 'command' | 'note'\n\n/** A single timestamped event captured during a session */\nexport interface SessionEvent {\n /** Milliseconds since recording started */\n timestamp: number\n /** The kind of event */\n type: SessionEventType\n /** The captured data */\n data: string\n}\n\n/** Serialized recording format */\nexport interface RecordingData {\n /** Session ID this recording belongs to */\n sessionId: string\n /** ISO string of when recording started */\n startedAt: string\n /** All captured events */\n events: SessionEvent[]\n}\n\nconst RECORDINGS_DIR = join(homedir(), '.vibe-interviewing', 'recordings')\n\nasync function ensureRecordingsDir(): Promise<void> {\n if (!existsSync(RECORDINGS_DIR)) {\n await mkdir(RECORDINGS_DIR, { recursive: true })\n }\n}\n\n/**\n * Records timestamped events during an interview session.\n *\n * Captures stdout, stderr, commands, and notes with millisecond timestamps\n * relative to when the recorder was created.\n */\nexport class SessionRecorder {\n private readonly events: SessionEvent[] = []\n private readonly startTime: number\n private readonly startedAt: string\n\n constructor() {\n this.startTime = Date.now()\n this.startedAt = new Date().toISOString()\n }\n\n /** Record a timestamped event */\n record(type: SessionEventType, data: string): void {\n this.events.push({\n timestamp: Date.now() - this.startTime,\n type,\n data,\n })\n }\n\n /** Get all recorded events */\n getEvents(): ReadonlyArray<SessionEvent> {\n return this.events\n }\n\n /** Serialize the recording to a JSON-compatible object */\n toJSON(sessionId: string): RecordingData {\n return {\n sessionId,\n startedAt: this.startedAt,\n events: [...this.events],\n }\n }\n\n /** Create a SessionRecorder pre-populated with events from serialized data */\n static fromJSON(data: RecordingData): SessionRecorder {\n const recorder = new SessionRecorder()\n // Override the startedAt via Object.defineProperty since it's readonly\n Object.defineProperty(recorder, 'startedAt', { value: data.startedAt })\n for (const event of data.events) {\n recorder.events.push({ ...event })\n }\n return recorder\n }\n\n /** Save the recording to disk */\n async save(sessionId: string): Promise<void> {\n await ensureRecordingsDir()\n const filePath = join(RECORDINGS_DIR, `${sessionId}.json`)\n const data = this.toJSON(sessionId)\n await writeFile(filePath, JSON.stringify(data, null, 2))\n }\n\n /** Load a recording from disk */\n static async load(sessionId: string): Promise<SessionRecorder> {\n const filePath = join(RECORDINGS_DIR, `${sessionId}.json`)\n const raw = await readFile(filePath, 'utf-8')\n const data = JSON.parse(raw) as RecordingData\n return SessionRecorder.fromJSON(data)\n }\n\n /** List all available recording session IDs */\n static async list(): Promise<string[]> {\n await ensureRecordingsDir()\n const files = await readdir(RECORDINGS_DIR)\n return files\n .filter((f) => f.endsWith('.json'))\n .map((f) => f.replace(/\\.json$/, ''))\n .sort()\n }\n}\n","import { ClaudeCodeLauncher } from './claude-code.js'\nimport type { AIToolLauncher } from './types.js'\n\n/** All supported AI tool launchers */\nconst launchers: AIToolLauncher[] = [new ClaudeCodeLauncher()]\n\n/** Information about a detected AI coding tool */\nexport interface DetectedTool {\n /** The launcher instance */\n launcher: AIToolLauncher\n /** Installed version string, or null if unknown */\n version: string | null\n}\n\n/** Detect which AI coding tools are installed on the system */\nexport async function detectInstalledTools(): Promise<DetectedTool[]> {\n const results: DetectedTool[] = []\n\n for (const launcher of launchers) {\n const installed = await launcher.isInstalled()\n if (installed) {\n const version = await launcher.getVersion()\n results.push({ launcher, version })\n }\n }\n\n return results\n}\n\n/** Get a launcher by its internal name */\nexport function getLauncher(name: string): AIToolLauncher | undefined {\n return launchers.find((l) => l.name === name)\n}\n\n/** Get all registered launchers */\nexport function getAllLaunchers(): AIToolLauncher[] {\n return [...launchers]\n}\n","import { execSync } from 'node:child_process'\nimport { readFile, writeFile, mkdir, rm } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { homedir } from 'node:os'\nimport { randomBytes } from 'node:crypto'\nimport type { AIToolLauncher, LaunchConfig } from '../launcher/types.js'\nimport type { ScenarioConfig } from '../scenario/types.js'\nimport { importRepo } from '../scenario/importer.js'\nimport { generateSystemPrompt } from '../scenario/loader.js'\nimport { SetupError } from '../errors.js'\nimport { saveSession, deleteSession } from './store.js'\nimport { toStoredSession } from './types.js'\nimport type { Session } from './types.js'\n\nexport type { Session }\n\n/** Callback for reporting session progress */\nexport type ProgressCallback = (stage: string) => void\n\n/** Manages the lifecycle of an interview session */\nexport class SessionManager {\n constructor(private launcher: AIToolLauncher) {}\n\n /**\n * Create a new interview session.\n *\n * Flow:\n * 1. Clone the repo at a pinned commit\n * 2. Apply bug patches (find/replace in source files)\n * 3. Wipe git history so the candidate can't diff to find the bug\n * 4. Remove scenario.yaml from workspace (interviewer-only)\n * 5. Write BRIEFING.md and system prompt\n * 6. Run setup commands (npm install, etc.)\n */\n async createSession(\n config: ScenarioConfig,\n workdir?: string,\n onProgress?: ProgressCallback,\n ): Promise<{ session: Session; config: ScenarioConfig }> {\n const id = randomBytes(4).toString('hex')\n const sessionDir = workdir ?? join(homedir(), 'vibe-sessions', `${config.name}-${id}`)\n\n const session: Session = {\n id,\n scenarioName: config.name,\n workdir: sessionDir,\n systemPromptPath: '',\n status: 'cloning',\n createdAt: new Date().toISOString(),\n }\n\n // 1. Clone the repo at the pinned commit\n onProgress?.('Cloning repository...')\n await importRepo(config.repo, sessionDir, config.commit)\n\n // 2. Apply bug patches\n onProgress?.('Injecting scenario...')\n for (const p of config.patch) {\n const filePath = join(sessionDir, p.file)\n const content = await readFile(filePath, 'utf-8')\n const patched = content.replace(p.find, p.replace)\n if (patched === content) {\n throw new SetupError(\n `patch ${p.file}`,\n `Could not find text to replace. The upstream code may have changed.`,\n )\n }\n await writeFile(filePath, patched)\n }\n\n // 3. Wipe git history so candidate can't see the injected changes\n onProgress?.('Preparing workspace...')\n await rm(join(sessionDir, '.git'), { recursive: true, force: true })\n execSync('git init && git add -A && git commit -m \"initial\"', {\n cwd: sessionDir,\n stdio: 'ignore',\n })\n\n // 4. Remove scenario.yaml from workspace (interviewer-only data)\n await rm(join(sessionDir, 'scenario.yaml'), { force: true })\n\n // 5. Write BRIEFING.md\n await writeFile(join(sessionDir, 'BRIEFING.md'), `# Interview Briefing\\n\\n${config.briefing}`)\n\n // Write system prompt OUTSIDE the workspace\n const promptDir = join(homedir(), '.vibe-interviewing', 'prompts')\n await mkdir(promptDir, { recursive: true })\n const systemPromptPath = join(promptDir, `${id}.md`)\n await writeFile(systemPromptPath, generateSystemPrompt(config))\n session.systemPromptPath = systemPromptPath\n\n // 6. Run setup commands\n session.status = 'setting-up'\n for (const cmd of config.setup) {\n onProgress?.(`Running: ${cmd}`)\n try {\n execSync(cmd, { cwd: sessionDir, stdio: 'pipe', timeout: 300000 })\n } catch (err) {\n throw new SetupError(cmd, err instanceof Error ? err.message : String(err))\n }\n }\n\n session.status = 'running'\n await saveSession(toStoredSession(session))\n return { session, config }\n }\n\n /** Launch the AI coding tool for an active session */\n async launchAITool(\n session: Session,\n _config: ScenarioConfig,\n launchConfig: Partial<LaunchConfig> = {},\n ): Promise<{ exitCode: number }> {\n const fullConfig: LaunchConfig = {\n scenarioName: session.scenarioName,\n systemPromptPath: session.systemPromptPath,\n ...launchConfig,\n }\n\n session.aiTool = this.launcher.name\n session.startedAt = new Date().toISOString()\n await saveSession(toStoredSession(session))\n\n const proc = await this.launcher.launch(session.workdir, fullConfig)\n const result = await proc.wait()\n\n session.status = 'complete'\n session.completedAt = new Date().toISOString()\n await saveSession(toStoredSession(session))\n\n return result\n }\n\n /** Destroy a session by removing its stored data */\n async destroySession(session: Session): Promise<void> {\n await deleteSession(session.id)\n }\n\n /** Get elapsed time since the AI tool was launched, formatted as a human-readable string */\n getElapsedTime(session: Session): string | null {\n if (!session.startedAt) return null\n\n const elapsed = Date.now() - new Date(session.startedAt).getTime()\n const minutes = Math.floor(elapsed / 60000)\n const seconds = Math.floor((elapsed % 60000) / 1000)\n\n if (minutes === 0) return `${seconds}s`\n return `${minutes}m ${seconds}s`\n }\n}\n","import { readFile, writeFile, readdir, unlink, mkdir } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { homedir } from 'node:os'\nimport { existsSync } from 'node:fs'\nimport type { StoredSession } from './types.js'\n\nconst SESSIONS_DIR = join(homedir(), '.vibe-interviewing', 'sessions')\n\nasync function ensureSessionsDir(): Promise<void> {\n if (!existsSync(SESSIONS_DIR)) {\n await mkdir(SESSIONS_DIR, { recursive: true })\n }\n}\n\n/** Save a session to disk */\nexport async function saveSession(session: StoredSession): Promise<void> {\n await ensureSessionsDir()\n const filePath = join(SESSIONS_DIR, `${session.id}.json`)\n await writeFile(filePath, JSON.stringify(session, null, 2))\n}\n\n/** Load a session from disk */\nexport async function loadSession(id: string): Promise<StoredSession | null> {\n const filePath = join(SESSIONS_DIR, `${id}.json`)\n if (!existsSync(filePath)) return null\n\n const raw = await readFile(filePath, 'utf-8')\n return JSON.parse(raw) as StoredSession\n}\n\n/** Delete a session from disk */\nexport async function deleteSession(id: string): Promise<void> {\n const filePath = join(SESSIONS_DIR, `${id}.json`)\n if (existsSync(filePath)) {\n await unlink(filePath)\n }\n}\n\n/** List all stored sessions */\nexport async function listSessions(): Promise<StoredSession[]> {\n await ensureSessionsDir()\n const files = await readdir(SESSIONS_DIR)\n const sessions: StoredSession[] = []\n\n for (const file of files) {\n if (!file.endsWith('.json')) continue\n try {\n const raw = await readFile(join(SESSIONS_DIR, file), 'utf-8')\n sessions.push(JSON.parse(raw) as StoredSession)\n } catch {\n // Skip corrupted files\n }\n }\n\n return sessions.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())\n}\n\n/** List only active (non-complete) sessions */\nexport async function listActiveSessions(): Promise<StoredSession[]> {\n const all = await listSessions()\n return all.filter((s) => s.status !== 'complete')\n}\n","/** Status of an interview session */\nexport type SessionStatus = 'cloning' | 'setting-up' | 'running' | 'complete'\n\n/** A live interview session */\nexport interface Session {\n /** Unique session identifier */\n id: string\n /** Name of the scenario being run */\n scenarioName: string\n /** Local working directory for the candidate */\n workdir: string\n /** Path to the system prompt file (outside workspace) */\n systemPromptPath: string\n /** Current session status */\n status: SessionStatus\n /** ISO timestamp of session creation */\n createdAt: string\n /** ISO timestamp of when the AI tool was launched */\n startedAt?: string\n /** ISO timestamp of session completion */\n completedAt?: string\n /** Name of the AI tool used */\n aiTool?: string\n}\n\n/** Serializable session data for persistence */\nexport interface StoredSession {\n /** Unique session identifier */\n id: string\n /** Name of the scenario being run */\n scenarioName: string\n /** Current session status */\n status: SessionStatus\n /** Local working directory for the candidate */\n workdir: string\n /** Path to the system prompt file */\n systemPromptPath: string\n /** Name of the AI tool used */\n aiTool?: string\n /** ISO timestamp of session creation */\n createdAt: string\n /** ISO timestamp of when the AI tool was launched */\n startedAt?: string\n /** ISO timestamp of session completion */\n completedAt?: string\n}\n\n/** Convert a Session to a StoredSession for persistence */\nexport function toStoredSession(session: Session): StoredSession {\n return {\n id: session.id,\n scenarioName: session.scenarioName,\n status: session.status,\n workdir: session.workdir,\n systemPromptPath: session.systemPromptPath,\n aiTool: session.aiTool,\n createdAt: session.createdAt,\n startedAt: session.startedAt,\n completedAt: session.completedAt,\n }\n}\n"],"mappings":"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","names":["readFile","existsSync","parseYaml","existsSync","readFile","parseYaml","join","join","readFile","readFile","join","existsSync","readFile","readFile","writeFile","mkdir","join","homedir","readFile","writeFile","readdir","mkdir","join","homedir","existsSync","join","homedir","readFile","writeFile","mkdir"]}
1
+ {"version":3,"sources":["../src/scenario/types.ts","../src/scenario/loader.ts","../src/scenario/validator.ts","../src/scenario/registry.ts","../src/scenario/importer.ts","../src/launcher/claude-code.ts","../src/session/recorder.ts","../src/launcher/detector.ts","../src/session/manager.ts","../src/session/store.ts","../src/session/types.ts"],"sourcesContent":["import { z } from 'zod'\n\nconst AIRulesSchema = z.object({\n /** Role description for the AI assistant */\n role: z.string(),\n /** Behavioral rules (e.g., \"don't reveal the answer\") */\n rules: z.array(z.string()),\n /** Knowledge about the bug/solution (hidden from candidate) */\n knowledge: z.string(),\n})\n\nconst EvaluationSchema = z.object({\n /** Evaluation criteria for the interviewer */\n criteria: z.array(z.string()),\n /** Description of the expected fix */\n expected_fix: z.string().optional(),\n})\n\n/** A file modification to inject the bug */\nconst PatchSchema = z.object({\n /** Path to the file relative to repo root */\n file: z.string(),\n /** The original text to find */\n find: z.string(),\n /** The replacement text (with the bug) */\n replace: z.string(),\n})\n\n/** Scenario type — determines validation rules and system prompt context */\nconst ScenarioTypeSchema = z.enum(['debug', 'feature', 'refactor']).default('debug')\n\n/** Full scenario configuration schema */\nexport const ScenarioConfigSchema = z.object({\n /** Scenario display name */\n name: z.string(),\n /** One-line description (candidate-visible — describe symptoms/task, never the root cause or solution) */\n description: z.string(),\n /** Scenario type: debug (find a bug), feature (build something), refactor (improve code) */\n type: ScenarioTypeSchema,\n /** Difficulty level */\n difficulty: z.enum(['easy', 'medium', 'hard']),\n /** Estimated time (e.g., \"30-45m\") */\n estimated_time: z.string(),\n /** Searchable tags */\n tags: z.array(z.string()).default([]),\n\n /** GitHub repo URL or owner/repo shorthand */\n repo: z.string(),\n /** Commit SHA to pin the clone to (ensures reproducibility) */\n commit: z.string(),\n /** Shell commands to run after cloning (e.g., [\"npm install\"]) */\n setup: z.array(z.string()).default([]),\n\n /** Find-and-replace patches to inject the bug after cloning */\n patch: z.array(PatchSchema).default([]),\n /** Files or directories to delete after cloning (globs relative to repo root) */\n delete_files: z.array(z.string()).default([]),\n\n /** Briefing shown to the candidate (written like a team lead message) */\n briefing: z.string(),\n /** AI behavioral rules (injected via system prompt, hidden from candidate) */\n ai_rules: AIRulesSchema,\n /** Interviewer reference — what the fix/implementation looks like */\n solution: z.string().optional(),\n /** Acceptance criteria for feature scenarios (concrete, testable requirements) */\n acceptance_criteria: z.array(z.string()).optional(),\n\n /** Evaluation rubric */\n evaluation: EvaluationSchema.optional(),\n /** License of the original project */\n license: z.string().optional(),\n})\n\nexport type ScenarioConfig = z.infer<typeof ScenarioConfigSchema>\nexport type ScenarioType = z.infer<typeof ScenarioTypeSchema>\nexport type AIRules = z.infer<typeof AIRulesSchema>\nexport type Evaluation = z.infer<typeof EvaluationSchema>\n\n/** Metadata about a discovered scenario */\nexport interface ScenarioInfo {\n /** Scenario name */\n name: string\n /** Parsed config */\n config: ScenarioConfig\n /** Whether this is a built-in scenario */\n builtIn: boolean\n}\n","import { readFile } from 'node:fs/promises'\nimport { parse as parseYaml } from 'yaml'\nimport { ScenarioNotFoundError, ScenarioValidationError } from '../errors.js'\nimport { ScenarioConfigSchema, type ScenarioConfig } from './types.js'\nimport { existsSync } from 'node:fs'\n\n/**\n * Load and parse a scenario config from a YAML file.\n *\n * @param configPath - Absolute path to the scenario.yaml file\n * @returns The parsed and validated scenario config\n */\nexport async function loadScenarioConfig(configPath: string): Promise<ScenarioConfig> {\n if (!existsSync(configPath)) {\n throw new ScenarioNotFoundError(configPath)\n }\n\n const raw = await readFile(configPath, 'utf-8')\n const parsed: unknown = parseYaml(raw)\n\n const result = ScenarioConfigSchema.safeParse(parsed)\n if (!result.success) {\n const issues = result.error.issues.map((i) => ` ${i.path.join('.')}: ${i.message}`)\n throw new ScenarioValidationError('validation failed', issues)\n }\n\n return result.data\n}\n\n/**\n * Generate a system prompt string from a scenario's ai_rules.\n *\n * This prompt is injected into the AI tool via --append-system-prompt\n * and is hidden from the candidate.\n *\n * @param config - The scenario config containing ai_rules\n * @returns The formatted system prompt\n */\nexport function generateSystemPrompt(config: ScenarioConfig): string {\n const lines: string[] = []\n\n lines.push(`# Interview Scenario: ${config.name}`)\n lines.push('')\n\n // Type-specific context\n const typeDescriptions: Record<string, string> = {\n debug:\n 'The candidate is debugging a bug in this codebase. Guide them through the debugging process without revealing the answer.',\n feature:\n 'The candidate is building a new feature. Help them understand the requirements, plan their approach, and implement it. Offer architectural guidance but let them drive the implementation.',\n refactor:\n 'The candidate is improving existing code. Help them identify issues and plan improvements. Encourage them to explain their reasoning for changes.',\n }\n lines.push('## Scenario Type')\n lines.push(typeDescriptions[config.type] ?? typeDescriptions['debug']!)\n lines.push('')\n\n lines.push('## Your Role')\n lines.push(config.ai_rules.role.trim())\n lines.push('')\n lines.push('## Rules')\n for (const rule of config.ai_rules.rules) {\n lines.push(`- ${rule}`)\n }\n lines.push('')\n\n const knowledgeHeaders: Record<string, string> = {\n debug: 'Knowledge (DO NOT share directly with the candidate)',\n feature: 'Implementation Context (DO NOT share directly with the candidate)',\n refactor: 'Improvement Context (DO NOT share directly with the candidate)',\n }\n lines.push(`## ${knowledgeHeaders[config.type] ?? knowledgeHeaders['debug']!}`)\n lines.push(config.ai_rules.knowledge.trim())\n\n return lines.join('\\n')\n}\n","import { ScenarioValidationError } from '../errors.js'\nimport type { ScenarioConfig } from './types.js'\n\n/** Result of validating a scenario configuration */\nexport interface ValidationResult {\n /** Whether the scenario is valid (no errors) */\n valid: boolean\n /** Non-fatal issues that should be addressed */\n warnings: string[]\n /** Fatal issues that prevent the scenario from running */\n errors: string[]\n}\n\n/**\n * Validate a scenario config for completeness and correctness.\n *\n * @param config - The scenario config to validate\n * @returns Validation result with errors and warnings\n */\nexport function validateScenario(config: ScenarioConfig): ValidationResult {\n const warnings: string[] = []\n const errors: string[] = []\n\n // Check briefing is not empty\n if (!config.briefing.trim()) {\n errors.push('Briefing cannot be empty')\n }\n\n // Check AI rules\n if (!config.ai_rules.role.trim()) {\n errors.push('ai_rules.role cannot be empty')\n }\n\n // Check repo is not empty\n if (!config.repo.trim()) {\n errors.push('repo cannot be empty')\n }\n\n // Check commit is a valid SHA\n if (!config.commit.trim()) {\n errors.push('commit cannot be empty — pin to a specific commit SHA for reproducibility')\n } else if (!/^[0-9a-f]{7,40}$/i.test(config.commit.trim())) {\n errors.push(\n 'commit must be a hex SHA (7-40 characters) — branch/tag names are not allowed for reproducibility',\n )\n }\n\n // Warnings for non-critical missing content\n if (config.ai_rules.rules.length === 0) {\n warnings.push('ai_rules.rules is empty — the AI will have no behavioral constraints')\n }\n\n // Type-specific validation\n if (config.type === 'debug') {\n if (config.patch.length === 0) {\n warnings.push('debug scenario has no patches — a bug must be injected via patch')\n }\n if (!config.solution?.trim()) {\n warnings.push('solution is empty — interviewers will have no solution reference')\n }\n }\n\n if (config.type === 'feature') {\n const hasCriteria =\n (config.acceptance_criteria && config.acceptance_criteria.length > 0) ||\n (config.evaluation && config.evaluation.criteria && config.evaluation.criteria.length > 0)\n if (!hasCriteria) {\n warnings.push(\n 'feature scenario has no acceptance_criteria or evaluation.criteria — candidates need a definition of done',\n )\n }\n }\n\n if (!config.evaluation) {\n warnings.push('No evaluation criteria defined')\n }\n\n return {\n valid: errors.length === 0,\n warnings,\n errors,\n }\n}\n\n/**\n * Validate a scenario config and throw if invalid.\n *\n * @param config - The scenario config to validate\n * @returns Validation result (only returned if valid)\n * @throws ScenarioValidationError if the config has errors\n */\nexport function validateScenarioOrThrow(config: ScenarioConfig): ValidationResult {\n const result = validateScenario(config)\n if (!result.valid) {\n throw new ScenarioValidationError('scenario validation failed', result.errors)\n }\n return result\n}\n","import { readFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { existsSync } from 'node:fs'\nimport { parse as parseYaml } from 'yaml'\nimport { loadScenarioConfig } from './loader.js'\nimport type { ScenarioInfo } from './types.js'\n\n/** Shape of a single entry in registry.yaml */\ninterface RegistryEntry {\n name: string\n repo: string\n commit: string\n description: string\n difficulty: 'easy' | 'medium' | 'hard'\n estimated_time: string\n}\n\n/** Shape of the registry.yaml file */\ninterface RegistryFile {\n scenarios: RegistryEntry[]\n}\n\n/**\n * Get the path to the scenarios package directory.\n *\n * Resolves via the @vibe-interviewing/scenarios package, which works both\n * in the monorepo (workspace link) and when installed from npm.\n */\nasync function getScenariosPackagePath(): Promise<string> {\n try {\n const { getScenariosDir } = await import('@vibe-interviewing/scenarios')\n return getScenariosDir()\n } catch {\n // Fallback: try relative to process.cwd() (monorepo root)\n const fromCwd = join(process.cwd(), 'packages', 'scenarios')\n if (existsSync(fromCwd)) {\n return fromCwd\n }\n throw new Error('Could not locate @vibe-interviewing/scenarios package')\n }\n}\n\n/**\n * Discover built-in scenarios from the registry.yaml file in the scenarios package.\n *\n * Each entry in the registry points to a scenario directory containing a full\n * scenario.yaml. The registry provides quick lookup metadata, while the full\n * config is loaded from the scenario's own config file.\n *\n * @returns Array of discovered scenario info objects\n */\nexport async function discoverBuiltInScenarios(): Promise<ScenarioInfo[]> {\n const scenariosPath = await getScenariosPackagePath()\n const registryPath = join(scenariosPath, 'registry.yaml')\n\n if (!existsSync(registryPath)) {\n return []\n }\n\n const raw = await readFile(registryPath, 'utf-8')\n const registry = parseYaml(raw) as RegistryFile\n\n if (!registry.scenarios || !Array.isArray(registry.scenarios)) {\n return []\n }\n\n const scenarios: ScenarioInfo[] = []\n\n for (const entry of registry.scenarios) {\n // Load the full config from the scenario's own directory\n const scenarioConfigPath = join(scenariosPath, entry.name, 'scenario.yaml')\n\n if (existsSync(scenarioConfigPath)) {\n const config = await loadScenarioConfig(scenarioConfigPath)\n scenarios.push({\n name: entry.name,\n config,\n builtIn: true,\n })\n } else {\n console.warn(\n `Warning: scenario \"${entry.name}\" listed in registry but ${scenarioConfigPath} not found`,\n )\n }\n }\n\n return scenarios\n}\n\n/**\n * Discover all available scenarios.\n *\n * Currently returns only built-in scenarios from the registry.\n * Local scenario support can be added later.\n *\n * @returns Array of all discovered scenario info objects\n */\nexport async function discoverAllScenarios(): Promise<ScenarioInfo[]> {\n return discoverBuiltInScenarios()\n}\n","import { mkdtemp } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { tmpdir } from 'node:os'\nimport { GitCloneError } from '../errors.js'\n\n/**\n * Import a repository by cloning it to a local directory.\n *\n * For commit SHAs, uses a shallow fetch to avoid downloading full history.\n *\n * @param repoUrl - GitHub URL, SSH URL, or owner/repo shorthand\n * @param targetPath - Directory to clone into (defaults to a temp directory)\n * @param ref - Optional branch, tag, or commit to checkout\n * @returns The path to the cloned directory\n */\nexport async function importRepo(\n repoUrl: string,\n targetPath?: string,\n ref?: string,\n): Promise<string> {\n const { simpleGit } = await import('simple-git')\n\n // Normalize shorthand (owner/repo -> https://github.com/owner/repo)\n const url = normalizeRepoUrl(repoUrl)\n\n // Clone to target path or temp directory\n const dest = targetPath ?? (await mkdtemp(join(tmpdir(), 'vibe-import-')))\n\n const git = simpleGit()\n\n try {\n if (ref && /^[0-9a-f]{7,40}$/i.test(ref)) {\n // Commit SHA — init + shallow fetch to avoid downloading full history\n await git.init([dest])\n const repoGit = simpleGit(dest)\n await repoGit.addRemote('origin', url)\n await repoGit.fetch(['origin', ref, '--depth', '1'])\n await repoGit.checkout(['FETCH_HEAD'])\n } else if (ref) {\n // Branch or tag — shallow clone\n await git.clone(url, dest, ['--depth', '1', '--branch', ref])\n } else {\n await git.clone(url, dest, ['--depth', '1'])\n }\n } catch (err) {\n throw new GitCloneError(url, err instanceof Error ? err.message : String(err))\n }\n\n return dest\n}\n\n/** Normalize various repo URL formats to full HTTPS GitHub URLs.\n * Currently only handles GitHub SSH URLs — other hosts pass through as-is. */\nfunction normalizeRepoUrl(input: string): string {\n // Already a full URL\n if (input.startsWith('http://') || input.startsWith('https://')) {\n return input\n }\n\n // Git SSH format\n if (input.startsWith('git@')) {\n return input.replace('git@github.com:', 'https://github.com/').replace(/\\.git$/, '')\n }\n\n // Shorthand: owner/repo\n if (/^[a-zA-Z0-9_-]+\\/[a-zA-Z0-9._-]+$/.test(input)) {\n return `https://github.com/${input}`\n }\n\n return input\n}\n","import { spawn } from 'node:child_process'\nimport { execFile } from 'node:child_process'\nimport { promisify } from 'node:util'\nimport { readFile } from 'node:fs/promises'\nimport type { AIToolLauncher, LaunchConfig, LaunchedProcess } from './types.js'\nimport { SessionRecorder } from '../session/recorder.js'\n\nconst execFileAsync = promisify(execFile)\n\n/** Launcher for Anthropic's Claude Code CLI */\nexport class ClaudeCodeLauncher implements AIToolLauncher {\n readonly name = 'claude-code'\n readonly displayName = 'Claude Code'\n\n /** Check if the claude CLI is installed */\n async isInstalled(): Promise<boolean> {\n try {\n await execFileAsync('claude', ['--version'])\n return true\n } catch {\n return false\n }\n }\n\n /** Get the installed Claude Code version */\n async getVersion(): Promise<string | null> {\n try {\n const { stdout } = await execFileAsync('claude', ['--version'])\n return stdout.trim()\n } catch {\n return null\n }\n }\n\n /** Launch Claude Code in the given working directory with the provided config */\n async launch(workdir: string, config: LaunchConfig): Promise<LaunchedProcess> {\n const args: string[] = []\n\n // Inject hidden system prompt\n const systemPrompt = await readFile(config.systemPromptPath, 'utf-8')\n args.push('--append-system-prompt', systemPrompt)\n\n // Set permission mode\n args.push('--permission-mode', config.permissionMode ?? 'default')\n\n // Set session name\n args.push('--name', `Interview: ${config.scenarioName}`)\n\n // Set model if specified\n if (config.model) {\n args.push('--model', config.model)\n }\n\n // Disallow tools for fairness\n if (config.disallowedTools && config.disallowedTools.length > 0) {\n args.push('--disallowedTools', ...config.disallowedTools)\n }\n\n // When recording, pipe stdout/stderr through the recorder\n const useRecording = config.recording === true\n const recorder = useRecording ? new SessionRecorder() : undefined\n\n // Spawn claude process\n const proc = spawn('claude', args, {\n cwd: workdir,\n stdio: useRecording ? ['inherit', 'pipe', 'pipe'] : 'inherit',\n env: { ...process.env },\n })\n\n // Forward piped output to the terminal and record it\n if (useRecording && recorder) {\n proc.stdout?.on('data', (chunk: Buffer) => {\n const text = chunk.toString('utf-8')\n recorder.record('stdout', text)\n process.stdout.write(chunk)\n })\n\n proc.stderr?.on('data', (chunk: Buffer) => {\n const text = chunk.toString('utf-8')\n recorder.record('stderr', text)\n process.stderr.write(chunk)\n })\n }\n\n return {\n wait: () =>\n new Promise((resolve) => {\n proc.on('exit', (code) => resolve({ exitCode: code ?? 0 }))\n }),\n kill: async () => {\n proc.kill('SIGTERM')\n },\n recorder,\n }\n }\n}\n","import { readFile, writeFile, readdir, mkdir } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { homedir } from 'node:os'\nimport { existsSync } from 'node:fs'\n\n/** Event types that can be captured during a session */\nexport type SessionEventType = 'stdout' | 'stderr' | 'command' | 'note'\n\n/** A single timestamped event captured during a session */\nexport interface SessionEvent {\n /** Milliseconds since recording started */\n timestamp: number\n /** The kind of event */\n type: SessionEventType\n /** The captured data */\n data: string\n}\n\n/** Serialized recording format */\nexport interface RecordingData {\n /** Session ID this recording belongs to */\n sessionId: string\n /** ISO string of when recording started */\n startedAt: string\n /** All captured events */\n events: SessionEvent[]\n}\n\nconst RECORDINGS_DIR = join(homedir(), '.vibe-interviewing', 'recordings')\n\nasync function ensureRecordingsDir(): Promise<void> {\n if (!existsSync(RECORDINGS_DIR)) {\n await mkdir(RECORDINGS_DIR, { recursive: true })\n }\n}\n\n/**\n * Records timestamped events during an interview session.\n *\n * Captures stdout, stderr, commands, and notes with millisecond timestamps\n * relative to when the recorder was created.\n */\nexport class SessionRecorder {\n private readonly events: SessionEvent[] = []\n private readonly startTime: number\n private readonly startedAt: string\n\n constructor() {\n this.startTime = Date.now()\n this.startedAt = new Date().toISOString()\n }\n\n /** Record a timestamped event */\n record(type: SessionEventType, data: string): void {\n this.events.push({\n timestamp: Date.now() - this.startTime,\n type,\n data,\n })\n }\n\n /** Get all recorded events */\n getEvents(): ReadonlyArray<SessionEvent> {\n return this.events\n }\n\n /** Serialize the recording to a JSON-compatible object */\n toJSON(sessionId: string): RecordingData {\n return {\n sessionId,\n startedAt: this.startedAt,\n events: [...this.events],\n }\n }\n\n /** Create a SessionRecorder pre-populated with events from serialized data */\n static fromJSON(data: RecordingData): SessionRecorder {\n const recorder = new SessionRecorder()\n // Override startedAt and startTime to preserve original timing\n Object.defineProperty(recorder, 'startedAt', { value: data.startedAt })\n Object.defineProperty(recorder, 'startTime', {\n value: new Date(data.startedAt).getTime(),\n })\n for (const event of data.events) {\n recorder.events.push({ ...event })\n }\n return recorder\n }\n\n /** Save the recording to disk */\n async save(sessionId: string): Promise<void> {\n await ensureRecordingsDir()\n const filePath = join(RECORDINGS_DIR, `${sessionId}.json`)\n const data = this.toJSON(sessionId)\n await writeFile(filePath, JSON.stringify(data, null, 2))\n }\n\n /** Load a recording from disk */\n static async load(sessionId: string): Promise<SessionRecorder> {\n const filePath = join(RECORDINGS_DIR, `${sessionId}.json`)\n const raw = await readFile(filePath, 'utf-8')\n const data = JSON.parse(raw) as RecordingData\n return SessionRecorder.fromJSON(data)\n }\n\n /** List all available recording session IDs */\n static async list(): Promise<string[]> {\n await ensureRecordingsDir()\n const files = await readdir(RECORDINGS_DIR)\n return files\n .filter((f) => f.endsWith('.json'))\n .map((f) => f.replace(/\\.json$/, ''))\n .sort()\n }\n}\n","import { ClaudeCodeLauncher } from './claude-code.js'\nimport type { AIToolLauncher } from './types.js'\n\n/** All supported AI tool launchers */\nconst launchers: AIToolLauncher[] = [new ClaudeCodeLauncher()]\n\n/** Information about a detected AI coding tool */\nexport interface DetectedTool {\n /** The launcher instance */\n launcher: AIToolLauncher\n /** Installed version string, or null if unknown */\n version: string | null\n}\n\n/** Detect which AI coding tools are installed on the system */\nexport async function detectInstalledTools(): Promise<DetectedTool[]> {\n const results: DetectedTool[] = []\n\n for (const launcher of launchers) {\n const installed = await launcher.isInstalled()\n if (installed) {\n const version = await launcher.getVersion()\n results.push({ launcher, version })\n }\n }\n\n return results\n}\n\n/** Get a launcher by its internal name */\nexport function getLauncher(name: string): AIToolLauncher | undefined {\n return launchers.find((l) => l.name === name)\n}\n\n/** Get all registered launchers */\nexport function getAllLaunchers(): AIToolLauncher[] {\n return [...launchers]\n}\n","import { execSync } from 'node:child_process'\nimport { readFile, writeFile, mkdir, rm } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { homedir } from 'node:os'\nimport { randomBytes } from 'node:crypto'\nimport type { AIToolLauncher, LaunchConfig } from '../launcher/types.js'\nimport type { ScenarioConfig } from '../scenario/types.js'\nimport { importRepo } from '../scenario/importer.js'\nimport { generateSystemPrompt } from '../scenario/loader.js'\nimport { SetupError } from '../errors.js'\nimport { saveSession, deleteSession } from './store.js'\nimport { toStoredSession } from './types.js'\nimport type { Session } from './types.js'\n\nexport type { Session }\n\n/** Callback for reporting session progress */\nexport type ProgressCallback = (stage: string) => void\n\n/** Manages the lifecycle of an interview session */\nexport class SessionManager {\n constructor(private launcher: AIToolLauncher) {}\n\n /**\n * Create a new interview session.\n *\n * Flow:\n * 1. Clone the repo at a pinned commit\n * 2. Apply bug patches (find/replace in source files)\n * 3. Delete files excluded by the scenario (e.g., tests that reveal the bug)\n * 4. Wipe git history so the candidate can't diff to find the bug\n * 5. Remove scenario.yaml from workspace (interviewer-only)\n * 6. Write BRIEFING.md and system prompt\n * 7. Run setup commands (npm install, etc.)\n */\n async createSession(\n config: ScenarioConfig,\n workdir?: string,\n onProgress?: ProgressCallback,\n options?: { skipSetup?: boolean },\n ): Promise<{ session: Session; config: ScenarioConfig }> {\n const id = randomBytes(4).toString('hex')\n const sessionDir = workdir ?? join(homedir(), 'vibe-sessions', `${config.name}-${id}`)\n\n const session: Session = {\n id,\n scenarioName: config.name,\n workdir: sessionDir,\n systemPromptPath: '',\n status: 'cloning',\n createdAt: new Date().toISOString(),\n }\n\n // 1. Clone the repo at the pinned commit\n onProgress?.('Cloning repository...')\n await importRepo(config.repo, sessionDir, config.commit)\n\n // 2. Apply bug patches\n onProgress?.('Injecting scenario...')\n for (const p of config.patch) {\n const filePath = join(sessionDir, p.file)\n const content = await readFile(filePath, 'utf-8')\n const patched = content.replaceAll(p.find, p.replace)\n if (patched === content) {\n throw new SetupError(\n `patch ${p.file}`,\n `Could not find text to replace. The upstream code may have changed.`,\n )\n }\n await writeFile(filePath, patched)\n }\n\n // 3. Delete files/directories specified by the scenario\n if (config.delete_files.length > 0) {\n onProgress?.('Removing excluded files...')\n for (const target of config.delete_files) {\n await rm(join(sessionDir, target), { recursive: true, force: true })\n }\n }\n\n // 4. Wipe git history so candidate can't see the injected changes\n onProgress?.('Preparing workspace...')\n await rm(join(sessionDir, '.git'), { recursive: true, force: true })\n execSync(\n 'git init && git add -A && git -c user.name=vibe -c user.email=vibe@local commit -m \"initial\"',\n { cwd: sessionDir, stdio: 'ignore' },\n )\n\n // 5. Remove scenario.yaml from workspace (interviewer-only data)\n await rm(join(sessionDir, 'scenario.yaml'), { force: true })\n\n // 6. Write BRIEFING.md\n await writeFile(join(sessionDir, 'BRIEFING.md'), `# Interview Briefing\\n\\n${config.briefing}`)\n\n // Write system prompt OUTSIDE the workspace\n const promptDir = join(homedir(), '.vibe-interviewing', 'prompts')\n await mkdir(promptDir, { recursive: true })\n const systemPromptPath = join(promptDir, `${id}.md`)\n await writeFile(systemPromptPath, generateSystemPrompt(config))\n session.systemPromptPath = systemPromptPath\n\n // 7. Run setup commands (skip when hosting — candidate runs setup after download)\n if (!options?.skipSetup) {\n session.status = 'setting-up'\n for (const cmd of config.setup) {\n onProgress?.(`Running: ${cmd}`)\n try {\n execSync(cmd, { cwd: sessionDir, stdio: 'pipe', timeout: 300000 })\n } catch (err) {\n throw new SetupError(cmd, err instanceof Error ? err.message : String(err))\n }\n }\n }\n\n session.status = 'running'\n await saveSession(toStoredSession(session))\n return { session, config }\n }\n\n /** Launch the AI coding tool for an active session */\n async launchAITool(\n session: Session,\n _config: ScenarioConfig,\n launchConfig: Partial<LaunchConfig> = {},\n ): Promise<{ exitCode: number }> {\n const fullConfig: LaunchConfig = {\n scenarioName: session.scenarioName,\n systemPromptPath: session.systemPromptPath,\n ...launchConfig,\n }\n\n session.aiTool = this.launcher.name\n session.startedAt = new Date().toISOString()\n await saveSession(toStoredSession(session))\n\n const proc = await this.launcher.launch(session.workdir, fullConfig)\n const result = await proc.wait()\n\n session.status = 'complete'\n session.completedAt = new Date().toISOString()\n await saveSession(toStoredSession(session))\n\n return result\n }\n\n /** Destroy a session by removing its stored data */\n async destroySession(session: Session): Promise<void> {\n await deleteSession(session.id)\n }\n\n /** Get elapsed time since the AI tool was launched, formatted as a human-readable string */\n getElapsedTime(session: Session): string | null {\n if (!session.startedAt) return null\n\n const elapsed = Date.now() - new Date(session.startedAt).getTime()\n const minutes = Math.floor(elapsed / 60000)\n const seconds = Math.floor((elapsed % 60000) / 1000)\n\n if (minutes === 0) return `${seconds}s`\n return `${minutes}m ${seconds}s`\n }\n}\n","import { readFile, writeFile, readdir, unlink, mkdir } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { homedir } from 'node:os'\nimport { existsSync } from 'node:fs'\nimport type { StoredSession } from './types.js'\n\nconst SESSIONS_DIR = join(homedir(), '.vibe-interviewing', 'sessions')\n\nasync function ensureSessionsDir(): Promise<void> {\n if (!existsSync(SESSIONS_DIR)) {\n await mkdir(SESSIONS_DIR, { recursive: true })\n }\n}\n\n/** Save a session to disk */\nexport async function saveSession(session: StoredSession): Promise<void> {\n await ensureSessionsDir()\n const filePath = join(SESSIONS_DIR, `${session.id}.json`)\n await writeFile(filePath, JSON.stringify(session, null, 2))\n}\n\n/** Load a session from disk */\nexport async function loadSession(id: string): Promise<StoredSession | null> {\n const filePath = join(SESSIONS_DIR, `${id}.json`)\n if (!existsSync(filePath)) return null\n\n const raw = await readFile(filePath, 'utf-8')\n return JSON.parse(raw) as StoredSession\n}\n\n/** Delete a session from disk */\nexport async function deleteSession(id: string): Promise<void> {\n const filePath = join(SESSIONS_DIR, `${id}.json`)\n if (existsSync(filePath)) {\n await unlink(filePath)\n }\n}\n\n/** List all stored sessions */\nexport async function listSessions(): Promise<StoredSession[]> {\n await ensureSessionsDir()\n const files = await readdir(SESSIONS_DIR)\n const sessions: StoredSession[] = []\n\n for (const file of files) {\n if (!file.endsWith('.json')) continue\n try {\n const raw = await readFile(join(SESSIONS_DIR, file), 'utf-8')\n sessions.push(JSON.parse(raw) as StoredSession)\n } catch {\n // Skip corrupted files\n }\n }\n\n return sessions.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())\n}\n\n/** List only active (non-complete) sessions */\nexport async function listActiveSessions(): Promise<StoredSession[]> {\n const all = await listSessions()\n return all.filter((s) => s.status !== 'complete')\n}\n","/** Status of an interview session */\nexport type SessionStatus = 'cloning' | 'setting-up' | 'running' | 'complete'\n\n/** Interview session — used both in-memory and for persistence */\nexport interface Session {\n /** Unique session identifier */\n id: string\n /** Name of the scenario being run */\n scenarioName: string\n /** Local working directory for the candidate */\n workdir: string\n /** Path to the system prompt file (outside workspace) */\n systemPromptPath: string\n /** Current session status */\n status: SessionStatus\n /** ISO timestamp of session creation */\n createdAt: string\n /** ISO timestamp of when the AI tool was launched */\n startedAt?: string\n /** ISO timestamp of session completion */\n completedAt?: string\n /** Name of the AI tool used */\n aiTool?: string\n}\n\n/**\n * Serializable session data for persistence.\n * Identical to Session — kept as an alias for API clarity at persistence boundaries.\n */\nexport type StoredSession = Session\n\n/** Convert a Session to a StoredSession for persistence */\nexport function toStoredSession(session: Session): StoredSession {\n return { ...session }\n}\n"],"mappings":"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","names":["readFile","existsSync","parseYaml","existsSync","readFile","parseYaml","join","join","readFile","readFile","join","existsSync","readFile","readFile","writeFile","mkdir","join","homedir","readFile","writeFile","readdir","mkdir","join","homedir","existsSync","join","homedir","readFile","writeFile","mkdir"]}
@@ -0,0 +1,81 @@
1
+ import { j as Session, S as ScenarioConfig, V as VibeError, P as ProgressCallback } from '../session-code-BusTFalM.js';
2
+ export { I as InvalidSessionCodeError, q as decodeSessionCode, r as encodeSessionCode, s as isCloudSessionCode } from '../session-code-BusTFalM.js';
3
+ import { EventEmitter } from 'node:events';
4
+ import 'zod';
5
+
6
+ /** Metadata served to candidates */
7
+ interface SessionMetadata {
8
+ scenarioName: string;
9
+ type: string;
10
+ difficulty: string;
11
+ estimatedTime: string;
12
+ briefing: string;
13
+ setupCommands: string[];
14
+ }
15
+ /** Events emitted by the session server */
16
+ interface SessionServerEvents {
17
+ 'candidate-connected': [];
18
+ 'download-complete': [];
19
+ }
20
+ /** HTTP server that serves a prepared workspace to a remote candidate */
21
+ declare class SessionServer extends EventEmitter<SessionServerEvents> {
22
+ private server;
23
+ /** Start serving the session workspace */
24
+ start(session: Session, config: ScenarioConfig, port?: number): Promise<{
25
+ code: string;
26
+ port: number;
27
+ host: string;
28
+ }>;
29
+ /** Stop the server and clean up */
30
+ stop(): Promise<void>;
31
+ }
32
+
33
+ /** Result of downloading a session from a host */
34
+ interface DownloadedSession {
35
+ /** Path to the extracted workspace */
36
+ workdir: string;
37
+ /** Path to the system prompt file */
38
+ systemPromptPath: string;
39
+ /** Session metadata from the host */
40
+ metadata: SessionMetadata;
41
+ /** Generated session ID */
42
+ id: string;
43
+ }
44
+ /** Error for network/connection failures */
45
+ declare class NetworkError extends VibeError {
46
+ constructor(message: string);
47
+ }
48
+ /**
49
+ * Download a session from a host.
50
+ *
51
+ * Fetches metadata, system prompt, and workspace tarball, then
52
+ * extracts everything to a local directory.
53
+ */
54
+ declare function downloadSession(host: string, port: number, targetDir?: string, onProgress?: ProgressCallback): Promise<DownloadedSession>;
55
+
56
+ /** Default Cloudflare Worker URL */
57
+ declare const DEFAULT_WORKER_URL = "https://api.vibe-interviewing.iar.dev";
58
+ /** Get the configured worker URL (env override or default) */
59
+ declare function getWorkerUrl(): string;
60
+ /** Error for cloud upload/download failures */
61
+ declare class CloudError extends VibeError {
62
+ constructor(message: string);
63
+ }
64
+ /**
65
+ * Upload a session to the cloud relay.
66
+ *
67
+ * Sends metadata, system prompt, and workspace tarball to the Cloudflare Worker.
68
+ * Returns a session code the candidate can use to download.
69
+ */
70
+ declare function uploadSession(workerUrl: string, metadata: SessionMetadata, systemPrompt: string, tarballPath: string, onProgress?: ProgressCallback): Promise<{
71
+ code: string;
72
+ expiresAt: string;
73
+ }>;
74
+ /**
75
+ * Download a session from the cloud relay.
76
+ *
77
+ * Fetches metadata, system prompt, and workspace tarball from the Cloudflare Worker.
78
+ */
79
+ declare function downloadSessionFromCloud(workerUrl: string, code: string, targetDir?: string, onProgress?: ProgressCallback): Promise<DownloadedSession>;
80
+
81
+ export { CloudError, DEFAULT_WORKER_URL, type DownloadedSession, NetworkError, type SessionMetadata, SessionServer, type SessionServerEvents, downloadSession, downloadSessionFromCloud, getWorkerUrl, uploadSession };