@vibe-interviewing/core 0.2.0 → 0.3.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.
@@ -61,6 +61,15 @@ var SetupError = class extends VibeError {
61
61
  );
62
62
  }
63
63
  };
64
+ var ScenarioFetchError = class extends VibeError {
65
+ constructor(url, reason) {
66
+ super(
67
+ `Failed to fetch scenario from URL: ${url}${reason ? ` \u2014 ${reason}` : ""}`,
68
+ "SCENARIO_FETCH_FAILED",
69
+ "Check the URL is accessible and returns valid YAML"
70
+ );
71
+ }
72
+ };
64
73
 
65
74
  // src/network/session-code.ts
66
75
  function encodeSessionCode(host, port) {
@@ -123,9 +132,10 @@ export {
123
132
  SessionNotFoundError,
124
133
  GitCloneError,
125
134
  SetupError,
135
+ ScenarioFetchError,
126
136
  encodeSessionCode,
127
137
  decodeSessionCode,
128
138
  isCloudSessionCode,
129
139
  InvalidSessionCodeError
130
140
  };
131
- //# sourceMappingURL=chunk-5PTYUK4Z.js.map
141
+ //# sourceMappingURL=chunk-CI3BD2WQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/network/session-code.ts"],"sourcesContent":["/** 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\nexport class ScenarioFetchError extends VibeError {\n constructor(url: string, reason?: string) {\n super(\n `Failed to fetch scenario from URL: ${url}${reason ? ` — ${reason}` : ''}`,\n 'SCENARIO_FETCH_FAILED',\n 'Check the URL is accessible and returns valid YAML',\n )\n }\n}\n","import { VibeError } from '../errors.js'\n\n/**\n * Encode a host:port pair into a human-typeable session code.\n *\n * Format: VIBE-XXXXXXXXXX (10 base36 chars encoding 6 bytes: 4 IP octets + 2 port bytes)\n */\nexport function encodeSessionCode(host: string, port: number): string {\n const octets = host.split('.').map(Number)\n if (octets.length !== 4 || octets.some((o) => isNaN(o) || o < 0 || o > 255)) {\n throw new InvalidSessionCodeError(`Invalid IPv4 address: ${host}`)\n }\n if (port < 1 || port > 65535) {\n throw new InvalidSessionCodeError(`Invalid port: ${port}`)\n }\n\n // Pack into 6 bytes: [ip0, ip1, ip2, ip3, port_hi, port_lo]\n const buf = Buffer.alloc(6)\n buf[0] = octets[0]!\n buf[1] = octets[1]!\n buf[2] = octets[2]!\n buf[3] = octets[3]!\n buf.writeUInt16BE(port, 4)\n\n // Convert to base36, zero-pad to 10 chars\n const num = buf.readUIntBE(0, 6)\n const code = num.toString(36).toUpperCase().padStart(10, '0')\n\n return `VIBE-${code}`\n}\n\n/**\n * Decode a session code back to host:port.\n *\n * Accepts formats: VIBE-XXXXXXXXXX, vibe-xxxxxxxxxx, or just XXXXXXXXXX\n */\nexport function decodeSessionCode(code: string): { host: string; port: number } {\n // Strip VIBE- prefix if present\n let raw = code.trim().toUpperCase()\n if (raw.startsWith('VIBE-')) {\n raw = raw.slice(5)\n }\n\n if (!/^[0-9A-Z]{1,10}$/.test(raw)) {\n throw new InvalidSessionCodeError(`Invalid session code format: ${code}`)\n }\n\n const num = parseInt(raw, 36)\n if (isNaN(num) || num > 0xffffffffffff) {\n throw new InvalidSessionCodeError(`Invalid session code: ${code}`)\n }\n\n const buf = Buffer.alloc(6)\n // Write as 6-byte big-endian\n buf.writeUIntBE(num, 0, 6)\n\n const host = `${buf[0]}.${buf[1]}.${buf[2]}.${buf[3]}`\n const port = buf.readUInt16BE(4)\n\n if (port === 0) {\n throw new InvalidSessionCodeError(`Invalid session code (port 0): ${code}`)\n }\n\n return { host, port }\n}\n\n/**\n * Check if a session code is a cloud code (6 chars) vs LAN code (10 chars).\n *\n * Cloud codes are 6 alphanumeric characters: VIBE-A3X9K2\n * LAN codes are 10 alphanumeric characters: VIBE-3R8KW1F0NX\n */\nexport function isCloudSessionCode(code: string): boolean {\n let raw = code.trim().toUpperCase()\n if (raw.startsWith('VIBE-')) {\n raw = raw.slice(5)\n }\n // Cloud codes are exactly 6 chars, LAN codes are up to 10\n return /^[0-9A-Z]{6}$/.test(raw)\n}\n\n/** Error for invalid session codes */\nexport class InvalidSessionCodeError extends VibeError {\n constructor(message: string) {\n super(message, 'INVALID_SESSION_CODE', 'Session codes look like VIBE-XXXXXXXXXX')\n }\n}\n"],"mappings":"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","names":[]}
package/dist/index.d.ts CHANGED
@@ -1,14 +1,24 @@
1
- import { S as ScenarioConfig, a as ScenarioInfo, A as AIToolLauncher, L as LaunchConfig, b as LaunchedProcess, c as StoredSession } from './session-code-BusTFalM.js';
2
- export { d as AIRules, e as AIToolNotFoundError, E as Evaluation, G as GitCloneError, I as InvalidSessionCodeError, P as ProgressCallback, R as RecordingData, f as ScenarioConfigSchema, g as ScenarioNotFoundError, h as ScenarioType, i as ScenarioValidationError, j as Session, k as SessionEvent, l as SessionEventType, m as SessionManager, n as SessionNotFoundError, o as SessionRecorder, p as SetupError, V as VibeError, q as decodeSessionCode, r as encodeSessionCode, s as isCloudSessionCode, t as toStoredSession } from './session-code-BusTFalM.js';
1
+ import { S as ScenarioConfig, a as ScenarioInfo, A as AIToolLauncher, L as LaunchConfig, b as LaunchedProcess, c as StoredSession } from './session-code-CfhXelpW.js';
2
+ export { d as AIRules, e as AIToolNotFoundError, E as Evaluation, G as GitCloneError, I as InterviewerGuide, f as InvalidSessionCodeError, K as KeySignal, P as ProgressCallback, R as RecordingData, g as ScenarioConfigSchema, h as ScenarioFetchError, i as ScenarioNotFoundError, j as ScenarioType, k as ScenarioValidationError, l as Session, m as SessionEvent, n as SessionEventType, o as SessionManager, p as SessionNotFoundError, q as SessionRecorder, r as SetupError, V as VibeError, s as decodeSessionCode, t as encodeSessionCode, u as isCloudSessionCode, v as toStoredSession } from './session-code-CfhXelpW.js';
3
3
  import 'zod';
4
4
 
5
5
  /**
6
- * Load and parse a scenario config from a YAML file.
6
+ * Check whether a string looks like a URL (http:// or https://).
7
7
  *
8
- * @param configPath - Absolute path to the scenario.yaml file
8
+ * @param input - The string to test
9
+ * @returns true if the string starts with http:// or https://
10
+ */
11
+ declare function isUrl(input: string): boolean;
12
+ /**
13
+ * Load and parse a scenario config from a YAML file or URL.
14
+ *
15
+ * Accepts either a local file path or an HTTP(S) URL. GitHub blob URLs
16
+ * are automatically converted to raw content URLs.
17
+ *
18
+ * @param pathOrUrl - Absolute path to a scenario.yaml file, or a URL
9
19
  * @returns The parsed and validated scenario config
10
20
  */
11
- declare function loadScenarioConfig(configPath: string): Promise<ScenarioConfig>;
21
+ declare function loadScenarioConfig(pathOrUrl: string): Promise<ScenarioConfig>;
12
22
  /**
13
23
  * Generate a system prompt string from a scenario's ai_rules.
14
24
  *
@@ -114,4 +124,4 @@ declare function listSessions(): Promise<StoredSession[]>;
114
124
  /** List only active (non-complete) sessions */
115
125
  declare function listActiveSessions(): Promise<StoredSession[]>;
116
126
 
117
- export { AIToolLauncher, ClaudeCodeLauncher, type DetectedTool, LaunchConfig, LaunchedProcess, ScenarioConfig, ScenarioInfo, StoredSession, type ValidationResult, deleteSession, detectInstalledTools, discoverAllScenarios, discoverBuiltInScenarios, generateSystemPrompt, getAllLaunchers, getLauncher, importRepo, listActiveSessions, listSessions, loadScenarioConfig, loadSession, saveSession, validateScenario, validateScenarioOrThrow };
127
+ export { AIToolLauncher, ClaudeCodeLauncher, type DetectedTool, LaunchConfig, LaunchedProcess, ScenarioConfig, ScenarioInfo, StoredSession, type ValidationResult, deleteSession, detectInstalledTools, discoverAllScenarios, discoverBuiltInScenarios, generateSystemPrompt, getAllLaunchers, getLauncher, importRepo, isUrl, listActiveSessions, listSessions, loadScenarioConfig, loadSession, saveSession, validateScenario, validateScenarioOrThrow };
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@ import {
2
2
  AIToolNotFoundError,
3
3
  GitCloneError,
4
4
  InvalidSessionCodeError,
5
+ ScenarioFetchError,
5
6
  ScenarioNotFoundError,
6
7
  ScenarioValidationError,
7
8
  SessionNotFoundError,
@@ -10,7 +11,7 @@ import {
10
11
  decodeSessionCode,
11
12
  encodeSessionCode,
12
13
  isCloudSessionCode
13
- } from "./chunk-5PTYUK4Z.js";
14
+ } from "./chunk-CI3BD2WQ.js";
14
15
 
15
16
  // src/scenario/types.ts
16
17
  import { z } from "zod";
@@ -28,6 +29,24 @@ var EvaluationSchema = z.object({
28
29
  /** Description of the expected fix */
29
30
  expected_fix: z.string().optional()
30
31
  });
32
+ var KeySignalSchema = z.object({
33
+ /** What behavior or skill this signal measures */
34
+ signal: z.string(),
35
+ /** What a strong candidate does (green flag) */
36
+ positive: z.string(),
37
+ /** What a weak candidate does (red flag) */
38
+ negative: z.string()
39
+ });
40
+ var InterviewerGuideSchema = z.object({
41
+ /** High-level summary of what this scenario evaluates and why */
42
+ overview: z.string(),
43
+ /** Specific behaviors to watch for, with green/red flag indicators */
44
+ key_signals: z.array(KeySignalSchema).default([]),
45
+ /** Common mistakes candidates make */
46
+ common_pitfalls: z.array(z.string()).default([]),
47
+ /** Questions to ask the candidate after the session */
48
+ debrief_questions: z.array(z.string()).default([])
49
+ });
31
50
  var PatchSchema = z.object({
32
51
  /** Path to the file relative to repo root */
33
52
  file: z.string(),
@@ -70,6 +89,8 @@ var ScenarioConfigSchema = z.object({
70
89
  acceptance_criteria: z.array(z.string()).optional(),
71
90
  /** Evaluation rubric */
72
91
  evaluation: EvaluationSchema.optional(),
92
+ /** Structured interviewer guide — what to watch for, common pitfalls, debrief questions */
93
+ interviewer_guide: InterviewerGuideSchema.optional(),
73
94
  /** License of the original project */
74
95
  license: z.string().optional()
75
96
  });
@@ -78,19 +99,63 @@ var ScenarioConfigSchema = z.object({
78
99
  import { readFile } from "fs/promises";
79
100
  import { parse as parseYaml } from "yaml";
80
101
  import { existsSync } from "fs";
81
- async function loadScenarioConfig(configPath) {
82
- if (!existsSync(configPath)) {
83
- throw new ScenarioNotFoundError(configPath);
102
+ var MAX_RESPONSE_BYTES = 1048576;
103
+ var FETCH_TIMEOUT_MS = 15e3;
104
+ function isUrl(input) {
105
+ return input.startsWith("http://") || input.startsWith("https://");
106
+ }
107
+ function toGitHubRawUrl(url) {
108
+ const match = url.match(/^https?:\/\/github\.com\/([^/]+\/[^/]+)\/blob\/(.+)$/);
109
+ if (match) {
110
+ return `https://raw.githubusercontent.com/${match[1]}/${match[2]}`;
111
+ }
112
+ return url;
113
+ }
114
+ async function fetchScenarioYaml(url) {
115
+ const rawUrl = toGitHubRawUrl(url);
116
+ let response;
117
+ try {
118
+ response = await fetch(rawUrl, {
119
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
120
+ headers: { Accept: "text/plain, application/x-yaml, */*" }
121
+ });
122
+ } catch (err) {
123
+ const message = err instanceof Error ? err.message : "unknown error";
124
+ throw new ScenarioFetchError(url, message);
125
+ }
126
+ if (!response.ok) {
127
+ throw new ScenarioFetchError(url, `HTTP ${response.status} ${response.statusText}`);
84
128
  }
85
- const raw = await readFile(configPath, "utf-8");
129
+ const contentLength = response.headers.get("content-length");
130
+ if (contentLength && parseInt(contentLength, 10) > MAX_RESPONSE_BYTES) {
131
+ throw new ScenarioFetchError(url, "response too large (>1 MB)");
132
+ }
133
+ const text = await response.text();
134
+ if (text.length > MAX_RESPONSE_BYTES) {
135
+ throw new ScenarioFetchError(url, "response too large (>1 MB)");
136
+ }
137
+ return text;
138
+ }
139
+ function parseScenarioYaml(raw, source) {
86
140
  const parsed = parseYaml(raw);
87
141
  const result = ScenarioConfigSchema.safeParse(parsed);
88
142
  if (!result.success) {
89
143
  const issues = result.error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`);
90
- throw new ScenarioValidationError("validation failed", issues);
144
+ throw new ScenarioValidationError(`validation failed (${source})`, issues);
91
145
  }
92
146
  return result.data;
93
147
  }
148
+ async function loadScenarioConfig(pathOrUrl) {
149
+ if (isUrl(pathOrUrl)) {
150
+ const raw2 = await fetchScenarioYaml(pathOrUrl);
151
+ return parseScenarioYaml(raw2, pathOrUrl);
152
+ }
153
+ if (!existsSync(pathOrUrl)) {
154
+ throw new ScenarioNotFoundError(pathOrUrl);
155
+ }
156
+ const raw = await readFile(pathOrUrl, "utf-8");
157
+ return parseScenarioYaml(raw, pathOrUrl);
158
+ }
94
159
  function generateSystemPrompt(config) {
95
160
  const lines = [];
96
161
  lines.push(`# Interview Scenario: ${config.name}`);
@@ -163,6 +228,11 @@ function validateScenario(config) {
163
228
  if (!config.evaluation) {
164
229
  warnings.push("No evaluation criteria defined");
165
230
  }
231
+ if (!config.interviewer_guide) {
232
+ warnings.push(
233
+ "No interviewer_guide defined \u2014 interviewers will have limited context during the session"
234
+ );
235
+ }
166
236
  return {
167
237
  valid: errors.length === 0,
168
238
  warnings,
@@ -607,6 +677,7 @@ export {
607
677
  GitCloneError,
608
678
  InvalidSessionCodeError,
609
679
  ScenarioConfigSchema,
680
+ ScenarioFetchError,
610
681
  ScenarioNotFoundError,
611
682
  ScenarioValidationError,
612
683
  SessionManager,
@@ -625,6 +696,7 @@ export {
625
696
  getLauncher,
626
697
  importRepo,
627
698
  isCloudSessionCode,
699
+ isUrl,
628
700
  listActiveSessions,
629
701
  listSessions,
630
702
  loadScenarioConfig,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
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"]}
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 signal to watch for during the interview, with green and red flag indicators */\nconst KeySignalSchema = z.object({\n /** What behavior or skill this signal measures */\n signal: z.string(),\n /** What a strong candidate does (green flag) */\n positive: z.string(),\n /** What a weak candidate does (red flag) */\n negative: z.string(),\n})\n\n/** Structured guide for the interviewer — shown during hosting, never to the candidate */\nconst InterviewerGuideSchema = z.object({\n /** High-level summary of what this scenario evaluates and why */\n overview: z.string(),\n /** Specific behaviors to watch for, with green/red flag indicators */\n key_signals: z.array(KeySignalSchema).default([]),\n /** Common mistakes candidates make */\n common_pitfalls: z.array(z.string()).default([]),\n /** Questions to ask the candidate after the session */\n debrief_questions: z.array(z.string()).default([]),\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 /** Structured interviewer guide — what to watch for, common pitfalls, debrief questions */\n interviewer_guide: InterviewerGuideSchema.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>\nexport type InterviewerGuide = z.infer<typeof InterviewerGuideSchema>\nexport type KeySignal = z.infer<typeof KeySignalSchema>\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, ScenarioFetchError } from '../errors.js'\nimport { ScenarioConfigSchema, type ScenarioConfig } from './types.js'\nimport { existsSync } from 'node:fs'\n\nconst MAX_RESPONSE_BYTES = 1_048_576 // 1 MB\nconst FETCH_TIMEOUT_MS = 15_000\n\n/**\n * Check whether a string looks like a URL (http:// or https://).\n *\n * @param input - The string to test\n * @returns true if the string starts with http:// or https://\n */\nexport function isUrl(input: string): boolean {\n return input.startsWith('http://') || input.startsWith('https://')\n}\n\n/**\n * Convert a GitHub blob URL to a raw content URL.\n *\n * @example\n * toGitHubRawUrl('https://github.com/owner/repo/blob/main/scenario.yaml')\n * // => 'https://raw.githubusercontent.com/owner/repo/main/scenario.yaml'\n */\nfunction toGitHubRawUrl(url: string): string {\n const match = url.match(/^https?:\\/\\/github\\.com\\/([^/]+\\/[^/]+)\\/blob\\/(.+)$/)\n if (match) {\n return `https://raw.githubusercontent.com/${match[1]}/${match[2]}`\n }\n return url\n}\n\n/**\n * Fetch scenario YAML from a URL.\n *\n * @param url - The URL to fetch (GitHub blob URLs are auto-converted to raw)\n * @returns The YAML text\n * @throws ScenarioFetchError on network failure, timeout, or oversized response\n */\nasync function fetchScenarioYaml(url: string): Promise<string> {\n const rawUrl = toGitHubRawUrl(url)\n\n let response: Response\n try {\n response = await fetch(rawUrl, {\n signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),\n headers: { Accept: 'text/plain, application/x-yaml, */*' },\n })\n } catch (err) {\n const message = err instanceof Error ? err.message : 'unknown error'\n throw new ScenarioFetchError(url, message)\n }\n\n if (!response.ok) {\n throw new ScenarioFetchError(url, `HTTP ${response.status} ${response.statusText}`)\n }\n\n const contentLength = response.headers.get('content-length')\n if (contentLength && parseInt(contentLength, 10) > MAX_RESPONSE_BYTES) {\n throw new ScenarioFetchError(url, 'response too large (>1 MB)')\n }\n\n const text = await response.text()\n if (text.length > MAX_RESPONSE_BYTES) {\n throw new ScenarioFetchError(url, 'response too large (>1 MB)')\n }\n\n return text\n}\n\n/**\n * Parse and validate scenario YAML text into a ScenarioConfig.\n *\n * @param raw - Raw YAML text\n * @param source - Source identifier (file path or URL) for error messages\n * @returns The parsed and validated scenario config\n */\nfunction parseScenarioYaml(raw: string, source: string): ScenarioConfig {\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 (${source})`, issues)\n }\n\n return result.data\n}\n\n/**\n * Load and parse a scenario config from a YAML file or URL.\n *\n * Accepts either a local file path or an HTTP(S) URL. GitHub blob URLs\n * are automatically converted to raw content URLs.\n *\n * @param pathOrUrl - Absolute path to a scenario.yaml file, or a URL\n * @returns The parsed and validated scenario config\n */\nexport async function loadScenarioConfig(pathOrUrl: string): Promise<ScenarioConfig> {\n if (isUrl(pathOrUrl)) {\n const raw = await fetchScenarioYaml(pathOrUrl)\n return parseScenarioYaml(raw, pathOrUrl)\n }\n\n if (!existsSync(pathOrUrl)) {\n throw new ScenarioNotFoundError(pathOrUrl)\n }\n\n const raw = await readFile(pathOrUrl, 'utf-8')\n return parseScenarioYaml(raw, pathOrUrl)\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 if (!config.interviewer_guide) {\n warnings.push(\n 'No interviewer_guide defined — interviewers will have limited context during the session',\n )\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":";;;;;;;;;;;;;;;;AAAA,SAAS,SAAS;AAElB,IAAM,gBAAgB,EAAE,OAAO;AAAA;AAAA,EAE7B,MAAM,EAAE,OAAO;AAAA;AAAA,EAEf,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC;AAAA;AAAA,EAEzB,WAAW,EAAE,OAAO;AACtB,CAAC;AAED,IAAM,mBAAmB,EAAE,OAAO;AAAA;AAAA,EAEhC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC;AAAA;AAAA,EAE5B,cAAc,EAAE,OAAO,EAAE,SAAS;AACpC,CAAC;AAGD,IAAM,kBAAkB,EAAE,OAAO;AAAA;AAAA,EAE/B,QAAQ,EAAE,OAAO;AAAA;AAAA,EAEjB,UAAU,EAAE,OAAO;AAAA;AAAA,EAEnB,UAAU,EAAE,OAAO;AACrB,CAAC;AAGD,IAAM,yBAAyB,EAAE,OAAO;AAAA;AAAA,EAEtC,UAAU,EAAE,OAAO;AAAA;AAAA,EAEnB,aAAa,EAAE,MAAM,eAAe,EAAE,QAAQ,CAAC,CAAC;AAAA;AAAA,EAEhD,iBAAiB,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,CAAC;AAAA;AAAA,EAE/C,mBAAmB,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,CAAC;AACnD,CAAC;AAGD,IAAM,cAAc,EAAE,OAAO;AAAA;AAAA,EAE3B,MAAM,EAAE,OAAO;AAAA;AAAA,EAEf,MAAM,EAAE,OAAO;AAAA;AAAA,EAEf,SAAS,EAAE,OAAO;AACpB,CAAC;AAGD,IAAM,qBAAqB,EAAE,KAAK,CAAC,SAAS,WAAW,UAAU,CAAC,EAAE,QAAQ,OAAO;AAG5E,IAAM,uBAAuB,EAAE,OAAO;AAAA;AAAA,EAE3C,MAAM,EAAE,OAAO;AAAA;AAAA,EAEf,aAAa,EAAE,OAAO;AAAA;AAAA,EAEtB,MAAM;AAAA;AAAA,EAEN,YAAY,EAAE,KAAK,CAAC,QAAQ,UAAU,MAAM,CAAC;AAAA;AAAA,EAE7C,gBAAgB,EAAE,OAAO;AAAA;AAAA,EAEzB,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,CAAC;AAAA;AAAA,EAGpC,MAAM,EAAE,OAAO;AAAA;AAAA,EAEf,QAAQ,EAAE,OAAO;AAAA;AAAA,EAEjB,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,CAAC;AAAA;AAAA,EAGrC,OAAO,EAAE,MAAM,WAAW,EAAE,QAAQ,CAAC,CAAC;AAAA;AAAA,EAEtC,cAAc,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,CAAC;AAAA;AAAA,EAG5C,UAAU,EAAE,OAAO;AAAA;AAAA,EAEnB,UAAU;AAAA;AAAA,EAEV,UAAU,EAAE,OAAO,EAAE,SAAS;AAAA;AAAA,EAE9B,qBAAqB,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS;AAAA;AAAA,EAGlD,YAAY,iBAAiB,SAAS;AAAA;AAAA,EAEtC,mBAAmB,uBAAuB,SAAS;AAAA;AAAA,EAEnD,SAAS,EAAE,OAAO,EAAE,SAAS;AAC/B,CAAC;;;AC/FD,SAAS,gBAAgB;AACzB,SAAS,SAAS,iBAAiB;AAGnC,SAAS,kBAAkB;AAE3B,IAAM,qBAAqB;AAC3B,IAAM,mBAAmB;AAQlB,SAAS,MAAM,OAAwB;AAC5C,SAAO,MAAM,WAAW,SAAS,KAAK,MAAM,WAAW,UAAU;AACnE;AASA,SAAS,eAAe,KAAqB;AAC3C,QAAM,QAAQ,IAAI,MAAM,sDAAsD;AAC9E,MAAI,OAAO;AACT,WAAO,qCAAqC,MAAM,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC;AAAA,EAClE;AACA,SAAO;AACT;AASA,eAAe,kBAAkB,KAA8B;AAC7D,QAAM,SAAS,eAAe,GAAG;AAEjC,MAAI;AACJ,MAAI;AACF,eAAW,MAAM,MAAM,QAAQ;AAAA,MAC7B,QAAQ,YAAY,QAAQ,gBAAgB;AAAA,MAC5C,SAAS,EAAE,QAAQ,sCAAsC;AAAA,IAC3D,CAAC;AAAA,EACH,SAAS,KAAK;AACZ,UAAM,UAAU,eAAe,QAAQ,IAAI,UAAU;AACrD,UAAM,IAAI,mBAAmB,KAAK,OAAO;AAAA,EAC3C;AAEA,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,IAAI,mBAAmB,KAAK,QAAQ,SAAS,MAAM,IAAI,SAAS,UAAU,EAAE;AAAA,EACpF;AAEA,QAAM,gBAAgB,SAAS,QAAQ,IAAI,gBAAgB;AAC3D,MAAI,iBAAiB,SAAS,eAAe,EAAE,IAAI,oBAAoB;AACrE,UAAM,IAAI,mBAAmB,KAAK,4BAA4B;AAAA,EAChE;AAEA,QAAM,OAAO,MAAM,SAAS,KAAK;AACjC,MAAI,KAAK,SAAS,oBAAoB;AACpC,UAAM,IAAI,mBAAmB,KAAK,4BAA4B;AAAA,EAChE;AAEA,SAAO;AACT;AASA,SAAS,kBAAkB,KAAa,QAAgC;AACtE,QAAM,SAAkB,UAAU,GAAG;AAErC,QAAM,SAAS,qBAAqB,UAAU,MAAM;AACpD,MAAI,CAAC,OAAO,SAAS;AACnB,UAAM,SAAS,OAAO,MAAM,OAAO,IAAI,CAAC,MAAM,KAAK,EAAE,KAAK,KAAK,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE;AACnF,UAAM,IAAI,wBAAwB,sBAAsB,MAAM,KAAK,MAAM;AAAA,EAC3E;AAEA,SAAO,OAAO;AAChB;AAWA,eAAsB,mBAAmB,WAA4C;AACnF,MAAI,MAAM,SAAS,GAAG;AACpB,UAAMA,OAAM,MAAM,kBAAkB,SAAS;AAC7C,WAAO,kBAAkBA,MAAK,SAAS;AAAA,EACzC;AAEA,MAAI,CAAC,WAAW,SAAS,GAAG;AAC1B,UAAM,IAAI,sBAAsB,SAAS;AAAA,EAC3C;AAEA,QAAM,MAAM,MAAM,SAAS,WAAW,OAAO;AAC7C,SAAO,kBAAkB,KAAK,SAAS;AACzC;AAWO,SAAS,qBAAqB,QAAgC;AACnE,QAAM,QAAkB,CAAC;AAEzB,QAAM,KAAK,yBAAyB,OAAO,IAAI,EAAE;AACjD,QAAM,KAAK,EAAE;AAGb,QAAM,mBAA2C;AAAA,IAC/C,OACE;AAAA,IACF,SACE;AAAA,IACF,UACE;AAAA,EACJ;AACA,QAAM,KAAK,kBAAkB;AAC7B,QAAM,KAAK,iBAAiB,OAAO,IAAI,KAAK,iBAAiB,OAAO,CAAE;AACtE,QAAM,KAAK,EAAE;AAEb,QAAM,KAAK,cAAc;AACzB,QAAM,KAAK,OAAO,SAAS,KAAK,KAAK,CAAC;AACtC,QAAM,KAAK,EAAE;AACb,QAAM,KAAK,UAAU;AACrB,aAAW,QAAQ,OAAO,SAAS,OAAO;AACxC,UAAM,KAAK,KAAK,IAAI,EAAE;AAAA,EACxB;AACA,QAAM,KAAK,EAAE;AAEb,QAAM,mBAA2C;AAAA,IAC/C,OAAO;AAAA,IACP,SAAS;AAAA,IACT,UAAU;AAAA,EACZ;AACA,QAAM,KAAK,MAAM,iBAAiB,OAAO,IAAI,KAAK,iBAAiB,OAAO,CAAE,EAAE;AAC9E,QAAM,KAAK,OAAO,SAAS,UAAU,KAAK,CAAC;AAE3C,SAAO,MAAM,KAAK,IAAI;AACxB;;;AC7IO,SAAS,iBAAiB,QAA0C;AACzE,QAAM,WAAqB,CAAC;AAC5B,QAAM,SAAmB,CAAC;AAG1B,MAAI,CAAC,OAAO,SAAS,KAAK,GAAG;AAC3B,WAAO,KAAK,0BAA0B;AAAA,EACxC;AAGA,MAAI,CAAC,OAAO,SAAS,KAAK,KAAK,GAAG;AAChC,WAAO,KAAK,+BAA+B;AAAA,EAC7C;AAGA,MAAI,CAAC,OAAO,KAAK,KAAK,GAAG;AACvB,WAAO,KAAK,sBAAsB;AAAA,EACpC;AAGA,MAAI,CAAC,OAAO,OAAO,KAAK,GAAG;AACzB,WAAO,KAAK,gFAA2E;AAAA,EACzF,WAAW,CAAC,oBAAoB,KAAK,OAAO,OAAO,KAAK,CAAC,GAAG;AAC1D,WAAO;AAAA,MACL;AAAA,IACF;AAAA,EACF;AAGA,MAAI,OAAO,SAAS,MAAM,WAAW,GAAG;AACtC,aAAS,KAAK,2EAAsE;AAAA,EACtF;AAGA,MAAI,OAAO,SAAS,SAAS;AAC3B,QAAI,OAAO,MAAM,WAAW,GAAG;AAC7B,eAAS,KAAK,uEAAkE;AAAA,IAClF;AACA,QAAI,CAAC,OAAO,UAAU,KAAK,GAAG;AAC5B,eAAS,KAAK,uEAAkE;AAAA,IAClF;AAAA,EACF;AAEA,MAAI,OAAO,SAAS,WAAW;AAC7B,UAAM,cACH,OAAO,uBAAuB,OAAO,oBAAoB,SAAS,KAClE,OAAO,cAAc,OAAO,WAAW,YAAY,OAAO,WAAW,SAAS,SAAS;AAC1F,QAAI,CAAC,aAAa;AAChB,eAAS;AAAA,QACP;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,MAAI,CAAC,OAAO,YAAY;AACtB,aAAS,KAAK,gCAAgC;AAAA,EAChD;AAEA,MAAI,CAAC,OAAO,mBAAmB;AAC7B,aAAS;AAAA,MACP;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,OAAO,OAAO,WAAW;AAAA,IACzB;AAAA,IACA;AAAA,EACF;AACF;AASO,SAAS,wBAAwB,QAA0C;AAChF,QAAM,SAAS,iBAAiB,MAAM;AACtC,MAAI,CAAC,OAAO,OAAO;AACjB,UAAM,IAAI,wBAAwB,8BAA8B,OAAO,MAAM;AAAA,EAC/E;AACA,SAAO;AACT;;;ACvGA,SAAS,YAAAC,iBAAgB;AACzB,SAAS,YAAY;AACrB,SAAS,cAAAC,mBAAkB;AAC3B,SAAS,SAASC,kBAAiB;AAyBnC,eAAe,0BAA2C;AACxD,MAAI;AACF,UAAM,EAAE,gBAAgB,IAAI,MAAM,OAAO,8BAA8B;AACvE,WAAO,gBAAgB;AAAA,EACzB,QAAQ;AAEN,UAAM,UAAU,KAAK,QAAQ,IAAI,GAAG,YAAY,WAAW;AAC3D,QAAIC,YAAW,OAAO,GAAG;AACvB,aAAO;AAAA,IACT;AACA,UAAM,IAAI,MAAM,uDAAuD;AAAA,EACzE;AACF;AAWA,eAAsB,2BAAoD;AACxE,QAAM,gBAAgB,MAAM,wBAAwB;AACpD,QAAM,eAAe,KAAK,eAAe,eAAe;AAExD,MAAI,CAACA,YAAW,YAAY,GAAG;AAC7B,WAAO,CAAC;AAAA,EACV;AAEA,QAAM,MAAM,MAAMC,UAAS,cAAc,OAAO;AAChD,QAAM,WAAWC,WAAU,GAAG;AAE9B,MAAI,CAAC,SAAS,aAAa,CAAC,MAAM,QAAQ,SAAS,SAAS,GAAG;AAC7D,WAAO,CAAC;AAAA,EACV;AAEA,QAAM,YAA4B,CAAC;AAEnC,aAAW,SAAS,SAAS,WAAW;AAEtC,UAAM,qBAAqB,KAAK,eAAe,MAAM,MAAM,eAAe;AAE1E,QAAIF,YAAW,kBAAkB,GAAG;AAClC,YAAM,SAAS,MAAM,mBAAmB,kBAAkB;AAC1D,gBAAU,KAAK;AAAA,QACb,MAAM,MAAM;AAAA,QACZ;AAAA,QACA,SAAS;AAAA,MACX,CAAC;AAAA,IACH,OAAO;AACL,cAAQ;AAAA,QACN,sBAAsB,MAAM,IAAI,4BAA4B,kBAAkB;AAAA,MAChF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAUA,eAAsB,uBAAgD;AACpE,SAAO,yBAAyB;AAClC;;;ACnGA,SAAS,eAAe;AACxB,SAAS,QAAAG,aAAY;AACrB,SAAS,cAAc;AAavB,eAAsB,WACpB,SACA,YACA,KACiB;AACjB,QAAM,EAAE,UAAU,IAAI,MAAM,OAAO,YAAY;AAG/C,QAAM,MAAM,iBAAiB,OAAO;AAGpC,QAAM,OAAO,cAAe,MAAM,QAAQC,MAAK,OAAO,GAAG,cAAc,CAAC;AAExE,QAAM,MAAM,UAAU;AAEtB,MAAI;AACF,QAAI,OAAO,oBAAoB,KAAK,GAAG,GAAG;AAExC,YAAM,IAAI,KAAK,CAAC,IAAI,CAAC;AACrB,YAAM,UAAU,UAAU,IAAI;AAC9B,YAAM,QAAQ,UAAU,UAAU,GAAG;AACrC,YAAM,QAAQ,MAAM,CAAC,UAAU,KAAK,WAAW,GAAG,CAAC;AACnD,YAAM,QAAQ,SAAS,CAAC,YAAY,CAAC;AAAA,IACvC,WAAW,KAAK;AAEd,YAAM,IAAI,MAAM,KAAK,MAAM,CAAC,WAAW,KAAK,YAAY,GAAG,CAAC;AAAA,IAC9D,OAAO;AACL,YAAM,IAAI,MAAM,KAAK,MAAM,CAAC,WAAW,GAAG,CAAC;AAAA,IAC7C;AAAA,EACF,SAAS,KAAK;AACZ,UAAM,IAAI,cAAc,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,EAC/E;AAEA,SAAO;AACT;AAIA,SAAS,iBAAiB,OAAuB;AAE/C,MAAI,MAAM,WAAW,SAAS,KAAK,MAAM,WAAW,UAAU,GAAG;AAC/D,WAAO;AAAA,EACT;AAGA,MAAI,MAAM,WAAW,MAAM,GAAG;AAC5B,WAAO,MAAM,QAAQ,mBAAmB,qBAAqB,EAAE,QAAQ,UAAU,EAAE;AAAA,EACrF;AAGA,MAAI,oCAAoC,KAAK,KAAK,GAAG;AACnD,WAAO,sBAAsB,KAAK;AAAA,EACpC;AAEA,SAAO;AACT;;;ACtEA,SAAS,aAAa;AACtB,SAAS,gBAAgB;AACzB,SAAS,iBAAiB;AAC1B,SAAS,YAAAC,iBAAgB;;;ACHzB,SAAS,YAAAC,WAAU,WAAW,SAAS,aAAa;AACpD,SAAS,QAAAC,aAAY;AACrB,SAAS,eAAe;AACxB,SAAS,cAAAC,mBAAkB;AAyB3B,IAAM,iBAAiBD,MAAK,QAAQ,GAAG,sBAAsB,YAAY;AAEzE,eAAe,sBAAqC;AAClD,MAAI,CAACC,YAAW,cAAc,GAAG;AAC/B,UAAM,MAAM,gBAAgB,EAAE,WAAW,KAAK,CAAC;AAAA,EACjD;AACF;AAQO,IAAM,kBAAN,MAAM,iBAAgB;AAAA,EACV,SAAyB,CAAC;AAAA,EAC1B;AAAA,EACA;AAAA,EAEjB,cAAc;AACZ,SAAK,YAAY,KAAK,IAAI;AAC1B,SAAK,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,EAC1C;AAAA;AAAA,EAGA,OAAO,MAAwB,MAAoB;AACjD,SAAK,OAAO,KAAK;AAAA,MACf,WAAW,KAAK,IAAI,IAAI,KAAK;AAAA,MAC7B;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,YAAyC;AACvC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,OAAO,WAAkC;AACvC,WAAO;AAAA,MACL;AAAA,MACA,WAAW,KAAK;AAAA,MAChB,QAAQ,CAAC,GAAG,KAAK,MAAM;AAAA,IACzB;AAAA,EACF;AAAA;AAAA,EAGA,OAAO,SAAS,MAAsC;AACpD,UAAM,WAAW,IAAI,iBAAgB;AAErC,WAAO,eAAe,UAAU,aAAa,EAAE,OAAO,KAAK,UAAU,CAAC;AACtE,WAAO,eAAe,UAAU,aAAa;AAAA,MAC3C,OAAO,IAAI,KAAK,KAAK,SAAS,EAAE,QAAQ;AAAA,IAC1C,CAAC;AACD,eAAW,SAAS,KAAK,QAAQ;AAC/B,eAAS,OAAO,KAAK,EAAE,GAAG,MAAM,CAAC;AAAA,IACnC;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,MAAM,KAAK,WAAkC;AAC3C,UAAM,oBAAoB;AAC1B,UAAM,WAAWD,MAAK,gBAAgB,GAAG,SAAS,OAAO;AACzD,UAAM,OAAO,KAAK,OAAO,SAAS;AAClC,UAAM,UAAU,UAAU,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;AAAA,EACzD;AAAA;AAAA,EAGA,aAAa,KAAK,WAA6C;AAC7D,UAAM,WAAWA,MAAK,gBAAgB,GAAG,SAAS,OAAO;AACzD,UAAM,MAAM,MAAMD,UAAS,UAAU,OAAO;AAC5C,UAAM,OAAO,KAAK,MAAM,GAAG;AAC3B,WAAO,iBAAgB,SAAS,IAAI;AAAA,EACtC;AAAA;AAAA,EAGA,aAAa,OAA0B;AACrC,UAAM,oBAAoB;AAC1B,UAAM,QAAQ,MAAM,QAAQ,cAAc;AAC1C,WAAO,MACJ,OAAO,CAAC,MAAM,EAAE,SAAS,OAAO,CAAC,EACjC,IAAI,CAAC,MAAM,EAAE,QAAQ,WAAW,EAAE,CAAC,EACnC,KAAK;AAAA,EACV;AACF;;;AD3GA,IAAM,gBAAgB,UAAU,QAAQ;AAGjC,IAAM,qBAAN,MAAmD;AAAA,EAC/C,OAAO;AAAA,EACP,cAAc;AAAA;AAAA,EAGvB,MAAM,cAAgC;AACpC,QAAI;AACF,YAAM,cAAc,UAAU,CAAC,WAAW,CAAC;AAC3C,aAAO;AAAA,IACT,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,aAAqC;AACzC,QAAI;AACF,YAAM,EAAE,OAAO,IAAI,MAAM,cAAc,UAAU,CAAC,WAAW,CAAC;AAC9D,aAAO,OAAO,KAAK;AAAA,IACrB,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,OAAO,SAAiB,QAAgD;AAC5E,UAAM,OAAiB,CAAC;AAGxB,UAAM,eAAe,MAAMG,UAAS,OAAO,kBAAkB,OAAO;AACpE,SAAK,KAAK,0BAA0B,YAAY;AAGhD,SAAK,KAAK,qBAAqB,OAAO,kBAAkB,SAAS;AAGjE,SAAK,KAAK,UAAU,cAAc,OAAO,YAAY,EAAE;AAGvD,QAAI,OAAO,OAAO;AAChB,WAAK,KAAK,WAAW,OAAO,KAAK;AAAA,IACnC;AAGA,QAAI,OAAO,mBAAmB,OAAO,gBAAgB,SAAS,GAAG;AAC/D,WAAK,KAAK,qBAAqB,GAAG,OAAO,eAAe;AAAA,IAC1D;AAGA,UAAM,eAAe,OAAO,cAAc;AAC1C,UAAM,WAAW,eAAe,IAAI,gBAAgB,IAAI;AAGxD,UAAM,OAAO,MAAM,UAAU,MAAM;AAAA,MACjC,KAAK;AAAA,MACL,OAAO,eAAe,CAAC,WAAW,QAAQ,MAAM,IAAI;AAAA,MACpD,KAAK,EAAE,GAAG,QAAQ,IAAI;AAAA,IACxB,CAAC;AAGD,QAAI,gBAAgB,UAAU;AAC5B,WAAK,QAAQ,GAAG,QAAQ,CAAC,UAAkB;AACzC,cAAM,OAAO,MAAM,SAAS,OAAO;AACnC,iBAAS,OAAO,UAAU,IAAI;AAC9B,gBAAQ,OAAO,MAAM,KAAK;AAAA,MAC5B,CAAC;AAED,WAAK,QAAQ,GAAG,QAAQ,CAAC,UAAkB;AACzC,cAAM,OAAO,MAAM,SAAS,OAAO;AACnC,iBAAS,OAAO,UAAU,IAAI;AAC9B,gBAAQ,OAAO,MAAM,KAAK;AAAA,MAC5B,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,MACL,MAAM,MACJ,IAAI,QAAQ,CAAC,YAAY;AACvB,aAAK,GAAG,QAAQ,CAAC,SAAS,QAAQ,EAAE,UAAU,QAAQ,EAAE,CAAC,CAAC;AAAA,MAC5D,CAAC;AAAA,MACH,MAAM,YAAY;AAChB,aAAK,KAAK,SAAS;AAAA,MACrB;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;;;AE3FA,IAAM,YAA8B,CAAC,IAAI,mBAAmB,CAAC;AAW7D,eAAsB,uBAAgD;AACpE,QAAM,UAA0B,CAAC;AAEjC,aAAW,YAAY,WAAW;AAChC,UAAM,YAAY,MAAM,SAAS,YAAY;AAC7C,QAAI,WAAW;AACb,YAAM,UAAU,MAAM,SAAS,WAAW;AAC1C,cAAQ,KAAK,EAAE,UAAU,QAAQ,CAAC;AAAA,IACpC;AAAA,EACF;AAEA,SAAO;AACT;AAGO,SAAS,YAAY,MAA0C;AACpE,SAAO,UAAU,KAAK,CAAC,MAAM,EAAE,SAAS,IAAI;AAC9C;AAGO,SAAS,kBAAoC;AAClD,SAAO,CAAC,GAAG,SAAS;AACtB;;;ACrCA,SAAS,gBAAgB;AACzB,SAAS,YAAAC,WAAU,aAAAC,YAAW,SAAAC,QAAO,UAAU;AAC/C,SAAS,QAAAC,aAAY;AACrB,SAAS,WAAAC,gBAAe;AACxB,SAAS,mBAAmB;;;ACJ5B,SAAS,YAAAC,WAAU,aAAAC,YAAW,WAAAC,UAAS,QAAQ,SAAAC,cAAa;AAC5D,SAAS,QAAAC,aAAY;AACrB,SAAS,WAAAC,gBAAe;AACxB,SAAS,cAAAC,mBAAkB;AAG3B,IAAM,eAAeF,MAAKC,SAAQ,GAAG,sBAAsB,UAAU;AAErE,eAAe,oBAAmC;AAChD,MAAI,CAACC,YAAW,YAAY,GAAG;AAC7B,UAAMH,OAAM,cAAc,EAAE,WAAW,KAAK,CAAC;AAAA,EAC/C;AACF;AAGA,eAAsB,YAAY,SAAuC;AACvE,QAAM,kBAAkB;AACxB,QAAM,WAAWC,MAAK,cAAc,GAAG,QAAQ,EAAE,OAAO;AACxD,QAAMH,WAAU,UAAU,KAAK,UAAU,SAAS,MAAM,CAAC,CAAC;AAC5D;AAGA,eAAsB,YAAY,IAA2C;AAC3E,QAAM,WAAWG,MAAK,cAAc,GAAG,EAAE,OAAO;AAChD,MAAI,CAACE,YAAW,QAAQ,EAAG,QAAO;AAElC,QAAM,MAAM,MAAMN,UAAS,UAAU,OAAO;AAC5C,SAAO,KAAK,MAAM,GAAG;AACvB;AAGA,eAAsB,cAAc,IAA2B;AAC7D,QAAM,WAAWI,MAAK,cAAc,GAAG,EAAE,OAAO;AAChD,MAAIE,YAAW,QAAQ,GAAG;AACxB,UAAM,OAAO,QAAQ;AAAA,EACvB;AACF;AAGA,eAAsB,eAAyC;AAC7D,QAAM,kBAAkB;AACxB,QAAM,QAAQ,MAAMJ,SAAQ,YAAY;AACxC,QAAM,WAA4B,CAAC;AAEnC,aAAW,QAAQ,OAAO;AACxB,QAAI,CAAC,KAAK,SAAS,OAAO,EAAG;AAC7B,QAAI;AACF,YAAM,MAAM,MAAMF,UAASI,MAAK,cAAc,IAAI,GAAG,OAAO;AAC5D,eAAS,KAAK,KAAK,MAAM,GAAG,CAAkB;AAAA,IAChD,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,SAAO,SAAS,KAAK,CAAC,GAAG,MAAM,IAAI,KAAK,EAAE,SAAS,EAAE,QAAQ,IAAI,IAAI,KAAK,EAAE,SAAS,EAAE,QAAQ,CAAC;AAClG;AAGA,eAAsB,qBAA+C;AACnE,QAAM,MAAM,MAAM,aAAa;AAC/B,SAAO,IAAI,OAAO,CAAC,MAAM,EAAE,WAAW,UAAU;AAClD;;;AC7BO,SAAS,gBAAgB,SAAiC;AAC/D,SAAO,EAAE,GAAG,QAAQ;AACtB;;;AFdO,IAAM,iBAAN,MAAqB;AAAA,EAC1B,YAAoB,UAA0B;AAA1B;AAAA,EAA2B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAc/C,MAAM,cACJ,QACA,SACA,YACA,SACuD;AACvD,UAAM,KAAK,YAAY,CAAC,EAAE,SAAS,KAAK;AACxC,UAAM,aAAa,WAAWG,MAAKC,SAAQ,GAAG,iBAAiB,GAAG,OAAO,IAAI,IAAI,EAAE,EAAE;AAErF,UAAM,UAAmB;AAAA,MACvB;AAAA,MACA,cAAc,OAAO;AAAA,MACrB,SAAS;AAAA,MACT,kBAAkB;AAAA,MAClB,QAAQ;AAAA,MACR,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IACpC;AAGA,iBAAa,uBAAuB;AACpC,UAAM,WAAW,OAAO,MAAM,YAAY,OAAO,MAAM;AAGvD,iBAAa,uBAAuB;AACpC,eAAW,KAAK,OAAO,OAAO;AAC5B,YAAM,WAAWD,MAAK,YAAY,EAAE,IAAI;AACxC,YAAM,UAAU,MAAME,UAAS,UAAU,OAAO;AAChD,YAAM,UAAU,QAAQ,WAAW,EAAE,MAAM,EAAE,OAAO;AACpD,UAAI,YAAY,SAAS;AACvB,cAAM,IAAI;AAAA,UACR,SAAS,EAAE,IAAI;AAAA,UACf;AAAA,QACF;AAAA,MACF;AACA,YAAMC,WAAU,UAAU,OAAO;AAAA,IACnC;AAGA,QAAI,OAAO,aAAa,SAAS,GAAG;AAClC,mBAAa,4BAA4B;AACzC,iBAAW,UAAU,OAAO,cAAc;AACxC,cAAM,GAAGH,MAAK,YAAY,MAAM,GAAG,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,MACrE;AAAA,IACF;AAGA,iBAAa,wBAAwB;AACrC,UAAM,GAAGA,MAAK,YAAY,MAAM,GAAG,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AACnE;AAAA,MACE;AAAA,MACA,EAAE,KAAK,YAAY,OAAO,SAAS;AAAA,IACrC;AAGA,UAAM,GAAGA,MAAK,YAAY,eAAe,GAAG,EAAE,OAAO,KAAK,CAAC;AAG3D,UAAMG,WAAUH,MAAK,YAAY,aAAa,GAAG;AAAA;AAAA,EAA2B,OAAO,QAAQ,EAAE;AAG7F,UAAM,YAAYA,MAAKC,SAAQ,GAAG,sBAAsB,SAAS;AACjE,UAAMG,OAAM,WAAW,EAAE,WAAW,KAAK,CAAC;AAC1C,UAAM,mBAAmBJ,MAAK,WAAW,GAAG,EAAE,KAAK;AACnD,UAAMG,WAAU,kBAAkB,qBAAqB,MAAM,CAAC;AAC9D,YAAQ,mBAAmB;AAG3B,QAAI,CAAC,SAAS,WAAW;AACvB,cAAQ,SAAS;AACjB,iBAAW,OAAO,OAAO,OAAO;AAC9B,qBAAa,YAAY,GAAG,EAAE;AAC9B,YAAI;AACF,mBAAS,KAAK,EAAE,KAAK,YAAY,OAAO,QAAQ,SAAS,IAAO,CAAC;AAAA,QACnE,SAAS,KAAK;AACZ,gBAAM,IAAI,WAAW,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,QAC5E;AAAA,MACF;AAAA,IACF;AAEA,YAAQ,SAAS;AACjB,UAAM,YAAY,gBAAgB,OAAO,CAAC;AAC1C,WAAO,EAAE,SAAS,OAAO;AAAA,EAC3B;AAAA;AAAA,EAGA,MAAM,aACJ,SACA,SACA,eAAsC,CAAC,GACR;AAC/B,UAAM,aAA2B;AAAA,MAC/B,cAAc,QAAQ;AAAA,MACtB,kBAAkB,QAAQ;AAAA,MAC1B,GAAG;AAAA,IACL;AAEA,YAAQ,SAAS,KAAK,SAAS;AAC/B,YAAQ,aAAY,oBAAI,KAAK,GAAE,YAAY;AAC3C,UAAM,YAAY,gBAAgB,OAAO,CAAC;AAE1C,UAAM,OAAO,MAAM,KAAK,SAAS,OAAO,QAAQ,SAAS,UAAU;AACnE,UAAM,SAAS,MAAM,KAAK,KAAK;AAE/B,YAAQ,SAAS;AACjB,YAAQ,eAAc,oBAAI,KAAK,GAAE,YAAY;AAC7C,UAAM,YAAY,gBAAgB,OAAO,CAAC;AAE1C,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,MAAM,eAAe,SAAiC;AACpD,UAAM,cAAc,QAAQ,EAAE;AAAA,EAChC;AAAA;AAAA,EAGA,eAAe,SAAiC;AAC9C,QAAI,CAAC,QAAQ,UAAW,QAAO;AAE/B,UAAM,UAAU,KAAK,IAAI,IAAI,IAAI,KAAK,QAAQ,SAAS,EAAE,QAAQ;AACjE,UAAM,UAAU,KAAK,MAAM,UAAU,GAAK;AAC1C,UAAM,UAAU,KAAK,MAAO,UAAU,MAAS,GAAI;AAEnD,QAAI,YAAY,EAAG,QAAO,GAAG,OAAO;AACpC,WAAO,GAAG,OAAO,KAAK,OAAO;AAAA,EAC/B;AACF;","names":["raw","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,5 +1,5 @@
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';
1
+ import { l as Session, S as ScenarioConfig, V as VibeError, P as ProgressCallback } from '../session-code-CfhXelpW.js';
2
+ export { f as InvalidSessionCodeError, s as decodeSessionCode, t as encodeSessionCode, u as isCloudSessionCode } from '../session-code-CfhXelpW.js';
3
3
  import { EventEmitter } from 'node:events';
4
4
  import 'zod';
5
5
 
@@ -4,7 +4,7 @@ import {
4
4
  decodeSessionCode,
5
5
  encodeSessionCode,
6
6
  isCloudSessionCode
7
- } from "../chunk-5PTYUK4Z.js";
7
+ } from "../chunk-CI3BD2WQ.js";
8
8
 
9
9
  // src/network/server.ts
10
10
  import { createServer } from "http";
@@ -28,6 +28,67 @@ declare const EvaluationSchema: z.ZodObject<{
28
28
  criteria: string[];
29
29
  expected_fix?: string | undefined;
30
30
  }>;
31
+ /** A signal to watch for during the interview, with green and red flag indicators */
32
+ declare const KeySignalSchema: z.ZodObject<{
33
+ /** What behavior or skill this signal measures */
34
+ signal: z.ZodString;
35
+ /** What a strong candidate does (green flag) */
36
+ positive: z.ZodString;
37
+ /** What a weak candidate does (red flag) */
38
+ negative: z.ZodString;
39
+ }, "strip", z.ZodTypeAny, {
40
+ signal: string;
41
+ positive: string;
42
+ negative: string;
43
+ }, {
44
+ signal: string;
45
+ positive: string;
46
+ negative: string;
47
+ }>;
48
+ /** Structured guide for the interviewer — shown during hosting, never to the candidate */
49
+ declare const InterviewerGuideSchema: z.ZodObject<{
50
+ /** High-level summary of what this scenario evaluates and why */
51
+ overview: z.ZodString;
52
+ /** Specific behaviors to watch for, with green/red flag indicators */
53
+ key_signals: z.ZodDefault<z.ZodArray<z.ZodObject<{
54
+ /** What behavior or skill this signal measures */
55
+ signal: z.ZodString;
56
+ /** What a strong candidate does (green flag) */
57
+ positive: z.ZodString;
58
+ /** What a weak candidate does (red flag) */
59
+ negative: z.ZodString;
60
+ }, "strip", z.ZodTypeAny, {
61
+ signal: string;
62
+ positive: string;
63
+ negative: string;
64
+ }, {
65
+ signal: string;
66
+ positive: string;
67
+ negative: string;
68
+ }>, "many">>;
69
+ /** Common mistakes candidates make */
70
+ common_pitfalls: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
71
+ /** Questions to ask the candidate after the session */
72
+ debrief_questions: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
73
+ }, "strip", z.ZodTypeAny, {
74
+ overview: string;
75
+ key_signals: {
76
+ signal: string;
77
+ positive: string;
78
+ negative: string;
79
+ }[];
80
+ common_pitfalls: string[];
81
+ debrief_questions: string[];
82
+ }, {
83
+ overview: string;
84
+ key_signals?: {
85
+ signal: string;
86
+ positive: string;
87
+ negative: string;
88
+ }[] | undefined;
89
+ common_pitfalls?: string[] | undefined;
90
+ debrief_questions?: string[] | undefined;
91
+ }>;
31
92
  /** Scenario type — determines validation rules and system prompt context */
32
93
  declare const ScenarioTypeSchema: z.ZodDefault<z.ZodEnum<["debug", "feature", "refactor"]>>;
33
94
  /** Full scenario configuration schema */
@@ -105,6 +166,50 @@ declare const ScenarioConfigSchema: z.ZodObject<{
105
166
  criteria: string[];
106
167
  expected_fix?: string | undefined;
107
168
  }>>;
169
+ /** Structured interviewer guide — what to watch for, common pitfalls, debrief questions */
170
+ interviewer_guide: z.ZodOptional<z.ZodObject<{
171
+ /** High-level summary of what this scenario evaluates and why */
172
+ overview: z.ZodString;
173
+ /** Specific behaviors to watch for, with green/red flag indicators */
174
+ key_signals: z.ZodDefault<z.ZodArray<z.ZodObject<{
175
+ /** What behavior or skill this signal measures */
176
+ signal: z.ZodString;
177
+ /** What a strong candidate does (green flag) */
178
+ positive: z.ZodString;
179
+ /** What a weak candidate does (red flag) */
180
+ negative: z.ZodString;
181
+ }, "strip", z.ZodTypeAny, {
182
+ signal: string;
183
+ positive: string;
184
+ negative: string;
185
+ }, {
186
+ signal: string;
187
+ positive: string;
188
+ negative: string;
189
+ }>, "many">>;
190
+ /** Common mistakes candidates make */
191
+ common_pitfalls: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
192
+ /** Questions to ask the candidate after the session */
193
+ debrief_questions: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
194
+ }, "strip", z.ZodTypeAny, {
195
+ overview: string;
196
+ key_signals: {
197
+ signal: string;
198
+ positive: string;
199
+ negative: string;
200
+ }[];
201
+ common_pitfalls: string[];
202
+ debrief_questions: string[];
203
+ }, {
204
+ overview: string;
205
+ key_signals?: {
206
+ signal: string;
207
+ positive: string;
208
+ negative: string;
209
+ }[] | undefined;
210
+ common_pitfalls?: string[] | undefined;
211
+ debrief_questions?: string[] | undefined;
212
+ }>>;
108
213
  /** License of the original project */
109
214
  license: z.ZodOptional<z.ZodString>;
110
215
  }, "strip", z.ZodTypeAny, {
@@ -135,6 +240,16 @@ declare const ScenarioConfigSchema: z.ZodObject<{
135
240
  criteria: string[];
136
241
  expected_fix?: string | undefined;
137
242
  } | undefined;
243
+ interviewer_guide?: {
244
+ overview: string;
245
+ key_signals: {
246
+ signal: string;
247
+ positive: string;
248
+ negative: string;
249
+ }[];
250
+ common_pitfalls: string[];
251
+ debrief_questions: string[];
252
+ } | undefined;
138
253
  license?: string | undefined;
139
254
  }, {
140
255
  name: string;
@@ -164,12 +279,24 @@ declare const ScenarioConfigSchema: z.ZodObject<{
164
279
  criteria: string[];
165
280
  expected_fix?: string | undefined;
166
281
  } | undefined;
282
+ interviewer_guide?: {
283
+ overview: string;
284
+ key_signals?: {
285
+ signal: string;
286
+ positive: string;
287
+ negative: string;
288
+ }[] | undefined;
289
+ common_pitfalls?: string[] | undefined;
290
+ debrief_questions?: string[] | undefined;
291
+ } | undefined;
167
292
  license?: string | undefined;
168
293
  }>;
169
294
  type ScenarioConfig = z.infer<typeof ScenarioConfigSchema>;
170
295
  type ScenarioType = z.infer<typeof ScenarioTypeSchema>;
171
296
  type AIRules = z.infer<typeof AIRulesSchema>;
172
297
  type Evaluation = z.infer<typeof EvaluationSchema>;
298
+ type InterviewerGuide = z.infer<typeof InterviewerGuideSchema>;
299
+ type KeySignal = z.infer<typeof KeySignalSchema>;
173
300
  /** Metadata about a discovered scenario */
174
301
  interface ScenarioInfo {
175
302
  /** Scenario name */
@@ -358,6 +485,9 @@ declare class GitCloneError extends VibeError {
358
485
  declare class SetupError extends VibeError {
359
486
  constructor(command: string, reason?: string);
360
487
  }
488
+ declare class ScenarioFetchError extends VibeError {
489
+ constructor(url: string, reason?: string);
490
+ }
361
491
 
362
492
  /**
363
493
  * Encode a host:port pair into a human-typeable session code.
@@ -386,4 +516,4 @@ declare class InvalidSessionCodeError extends VibeError {
386
516
  constructor(message: string);
387
517
  }
388
518
 
389
- export { type AIToolLauncher as A, type Evaluation as E, GitCloneError as G, InvalidSessionCodeError as I, type LaunchConfig as L, type ProgressCallback as P, type RecordingData as R, type ScenarioConfig as S, VibeError as V, type ScenarioInfo as a, type LaunchedProcess as b, type StoredSession as c, type AIRules as d, AIToolNotFoundError as e, ScenarioConfigSchema as f, ScenarioNotFoundError as g, type ScenarioType as h, ScenarioValidationError as i, type Session as j, type SessionEvent as k, type SessionEventType as l, SessionManager as m, SessionNotFoundError as n, SessionRecorder as o, SetupError as p, decodeSessionCode as q, encodeSessionCode as r, isCloudSessionCode as s, toStoredSession as t };
519
+ export { type AIToolLauncher as A, type Evaluation as E, GitCloneError as G, type InterviewerGuide as I, type KeySignal as K, type LaunchConfig as L, type ProgressCallback as P, type RecordingData as R, type ScenarioConfig as S, VibeError as V, type ScenarioInfo as a, type LaunchedProcess as b, type StoredSession as c, type AIRules as d, AIToolNotFoundError as e, InvalidSessionCodeError as f, ScenarioConfigSchema as g, ScenarioFetchError as h, ScenarioNotFoundError as i, type ScenarioType as j, ScenarioValidationError as k, type Session as l, type SessionEvent as m, type SessionEventType as n, SessionManager as o, SessionNotFoundError as p, SessionRecorder as q, SetupError as r, decodeSessionCode as s, encodeSessionCode as t, isCloudSessionCode as u, toStoredSession as v };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibe-interviewing/core",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Core library for vibe-interviewing — scenario engine, session management, AI tool launchers",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -19,7 +19,7 @@
19
19
  "simple-git": "^3.27.0",
20
20
  "yaml": "^2.7.0",
21
21
  "zod": "^3.24.0",
22
- "@vibe-interviewing/scenarios": "0.2.0"
22
+ "@vibe-interviewing/scenarios": "0.3.0"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@types/node": "^20.17.0",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/network/session-code.ts"],"sourcesContent":["/** 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 { VibeError } from '../errors.js'\n\n/**\n * Encode a host:port pair into a human-typeable session code.\n *\n * Format: VIBE-XXXXXXXXXX (10 base36 chars encoding 6 bytes: 4 IP octets + 2 port bytes)\n */\nexport function encodeSessionCode(host: string, port: number): string {\n const octets = host.split('.').map(Number)\n if (octets.length !== 4 || octets.some((o) => isNaN(o) || o < 0 || o > 255)) {\n throw new InvalidSessionCodeError(`Invalid IPv4 address: ${host}`)\n }\n if (port < 1 || port > 65535) {\n throw new InvalidSessionCodeError(`Invalid port: ${port}`)\n }\n\n // Pack into 6 bytes: [ip0, ip1, ip2, ip3, port_hi, port_lo]\n const buf = Buffer.alloc(6)\n buf[0] = octets[0]!\n buf[1] = octets[1]!\n buf[2] = octets[2]!\n buf[3] = octets[3]!\n buf.writeUInt16BE(port, 4)\n\n // Convert to base36, zero-pad to 10 chars\n const num = buf.readUIntBE(0, 6)\n const code = num.toString(36).toUpperCase().padStart(10, '0')\n\n return `VIBE-${code}`\n}\n\n/**\n * Decode a session code back to host:port.\n *\n * Accepts formats: VIBE-XXXXXXXXXX, vibe-xxxxxxxxxx, or just XXXXXXXXXX\n */\nexport function decodeSessionCode(code: string): { host: string; port: number } {\n // Strip VIBE- prefix if present\n let raw = code.trim().toUpperCase()\n if (raw.startsWith('VIBE-')) {\n raw = raw.slice(5)\n }\n\n if (!/^[0-9A-Z]{1,10}$/.test(raw)) {\n throw new InvalidSessionCodeError(`Invalid session code format: ${code}`)\n }\n\n const num = parseInt(raw, 36)\n if (isNaN(num) || num > 0xffffffffffff) {\n throw new InvalidSessionCodeError(`Invalid session code: ${code}`)\n }\n\n const buf = Buffer.alloc(6)\n // Write as 6-byte big-endian\n buf.writeUIntBE(num, 0, 6)\n\n const host = `${buf[0]}.${buf[1]}.${buf[2]}.${buf[3]}`\n const port = buf.readUInt16BE(4)\n\n if (port === 0) {\n throw new InvalidSessionCodeError(`Invalid session code (port 0): ${code}`)\n }\n\n return { host, port }\n}\n\n/**\n * Check if a session code is a cloud code (6 chars) vs LAN code (10 chars).\n *\n * Cloud codes are 6 alphanumeric characters: VIBE-A3X9K2\n * LAN codes are 10 alphanumeric characters: VIBE-3R8KW1F0NX\n */\nexport function isCloudSessionCode(code: string): boolean {\n let raw = code.trim().toUpperCase()\n if (raw.startsWith('VIBE-')) {\n raw = raw.slice(5)\n }\n // Cloud codes are exactly 6 chars, LAN codes are up to 10\n return /^[0-9A-Z]{6}$/.test(raw)\n}\n\n/** Error for invalid session codes */\nexport class InvalidSessionCodeError extends VibeError {\n constructor(message: string) {\n super(message, 'INVALID_SESSION_CODE', 'Session codes look like VIBE-XXXXXXXXXX')\n }\n}\n"],"mappings":"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","names":[]}