pi-interview 0.5.2 → 0.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.ts +13 -5
- package/package.json +1 -1
- package/schema.ts +20 -0
package/index.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { randomUUID } from "node:crypto";
|
|
|
9
9
|
import { execSync, execFileSync } from "node:child_process";
|
|
10
10
|
import { createRequire } from "node:module";
|
|
11
11
|
import { startInterviewServer, getActiveSessions, type ResponseItem } from "./server.js";
|
|
12
|
-
import { validateQuestions, type QuestionsFile } from "./schema.js";
|
|
12
|
+
import { validateQuestions, sanitizeLLMJSON, type QuestionsFile } from "./schema.js";
|
|
13
13
|
import { loadSettings, type InterviewThemeSettings } from "./settings.js";
|
|
14
14
|
|
|
15
15
|
interface GlimpseWindow {
|
|
@@ -185,13 +185,21 @@ function mergeThemeConfig(
|
|
|
185
185
|
|
|
186
186
|
function loadQuestions(questionsInput: string, cwd: string): SavedQuestionsFile {
|
|
187
187
|
const trimmed = questionsInput.trimStart();
|
|
188
|
-
|
|
188
|
+
const looksLikeInlineJSON =
|
|
189
|
+
trimmed.startsWith("{") ||
|
|
190
|
+
/^`{3,}(?:json|jsonc)?\s*\n?\s*\{/i.test(trimmed);
|
|
191
|
+
|
|
192
|
+
if (looksLikeInlineJSON) {
|
|
189
193
|
let data: unknown;
|
|
190
194
|
try {
|
|
191
195
|
data = JSON.parse(trimmed);
|
|
192
|
-
} catch
|
|
193
|
-
|
|
194
|
-
|
|
196
|
+
} catch {
|
|
197
|
+
try {
|
|
198
|
+
data = JSON.parse(sanitizeLLMJSON(trimmed));
|
|
199
|
+
} catch (repairErr) {
|
|
200
|
+
const message = repairErr instanceof Error ? repairErr.message : String(repairErr);
|
|
201
|
+
throw new Error(`Invalid inline JSON: ${message}`);
|
|
202
|
+
}
|
|
195
203
|
}
|
|
196
204
|
return validateQuestions(data);
|
|
197
205
|
}
|
package/package.json
CHANGED
package/schema.ts
CHANGED
|
@@ -311,6 +311,9 @@ export function validateQuestions(data: unknown): QuestionsFile {
|
|
|
311
311
|
const optionLabels = q.options?.map(getOptionLabel) ?? [];
|
|
312
312
|
|
|
313
313
|
if (q.type === "single") {
|
|
314
|
+
if (Array.isArray(q.recommended) && q.recommended.length === 1) {
|
|
315
|
+
q.recommended = q.recommended[0];
|
|
316
|
+
}
|
|
314
317
|
if (typeof q.recommended !== "string") {
|
|
315
318
|
throw new Error(`Question "${q.id}": recommended must be string for single-select`);
|
|
316
319
|
}
|
|
@@ -337,3 +340,20 @@ export function validateQuestions(data: unknown): QuestionsFile {
|
|
|
337
340
|
|
|
338
341
|
return parsed;
|
|
339
342
|
}
|
|
343
|
+
|
|
344
|
+
// Repair common LLM JSON mistakes (code fences, trailing commas, comments, smart quotes).
|
|
345
|
+
// Only called as a fallback when JSON.parse() already failed.
|
|
346
|
+
export function sanitizeLLMJSON(input: string): string {
|
|
347
|
+
let json = input.trim();
|
|
348
|
+
|
|
349
|
+
const fenceMatch = json.match(/^`{3,}(?:json|jsonc)?\s*\n([\s\S]*?)\n\s*`{3,}\s*$/i);
|
|
350
|
+
if (fenceMatch) {
|
|
351
|
+
json = fenceMatch[1];
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
json = json.replace(/^\s*\/\/.*$/gm, "");
|
|
355
|
+
json = json.replace(/,(\s*[}\]])/g, "$1");
|
|
356
|
+
json = json.replace(/\u201C|\u201D/g, '"');
|
|
357
|
+
|
|
358
|
+
return json.trim();
|
|
359
|
+
}
|