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.
Files changed (3) hide show
  1. package/index.ts +13 -5
  2. package/package.json +1 -1
  3. 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
- if (trimmed.startsWith("{")) {
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 (err) {
193
- const message = err instanceof Error ? err.message : String(err);
194
- throw new Error(`Invalid inline JSON: ${message}`);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-interview",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
4
4
  "description": "Interactive interview form extension for pi coding agent",
5
5
  "author": "Nico Bailon",
6
6
  "license": "MIT",
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
+ }