pi-interview 0.8.2 → 0.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/schema.ts +55 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-interview",
3
- "version": "0.8.2",
3
+ "version": "0.8.3",
4
4
  "description": "Interactive interview form extension for pi coding agent",
5
5
  "author": "Nico Bailon",
6
6
  "license": "MIT",
package/schema.ts CHANGED
@@ -23,6 +23,8 @@ export type ContentBlock = MarkdownContentBlock | CodeContentBlock;
23
23
  export interface RichOption {
24
24
  label: string;
25
25
  content?: ContentBlock;
26
+ recommended?: boolean;
27
+ conviction?: "strong" | "slight";
26
28
  }
27
29
 
28
30
  export type OptionValue = string | RichOption;
@@ -75,6 +77,50 @@ export function isRichOption(option: OptionValue): option is RichOption {
75
77
  return typeof option === "object" && option !== null && "label" in option;
76
78
  }
77
79
 
80
+ function normalizeOptionLevelRecommendations(question: Question): void {
81
+ if (!question.options) return;
82
+
83
+ const recommendedOptions: RichOption[] = [];
84
+ const convictions = new Set<"strong" | "slight">();
85
+
86
+ for (const option of question.options) {
87
+ if (!isRichOption(option)) continue;
88
+ if (option.conviction !== undefined && option.recommended !== true) {
89
+ throw new Error(
90
+ `Question "${question.id}" option "${option.label}": conviction requires recommended`
91
+ );
92
+ }
93
+ if (option.recommended !== true) continue;
94
+ recommendedOptions.push(option);
95
+ if (option.conviction) convictions.add(option.conviction);
96
+ }
97
+
98
+ if (recommendedOptions.length === 0) return;
99
+
100
+ if (question.recommended !== undefined || question.conviction !== undefined) {
101
+ throw new Error(
102
+ `Question "${question.id}": use either question-level recommended/conviction or option-level recommended flags, not both`
103
+ );
104
+ }
105
+
106
+ if (question.type === "single" && recommendedOptions.length !== 1) {
107
+ throw new Error(`Question "${question.id}": exactly one option must be recommended for single-select`);
108
+ }
109
+
110
+ if (convictions.size > 1) {
111
+ throw new Error(
112
+ `Question "${question.id}": recommended options must use the same conviction`
113
+ );
114
+ }
115
+
116
+ const recommendedLabels = recommendedOptions.map((option) => option.label);
117
+ question.recommended = question.type === "single" ? recommendedLabels[0] : recommendedLabels;
118
+ const conviction = convictions.values().next().value;
119
+ if (conviction !== undefined) {
120
+ question.conviction = conviction;
121
+ }
122
+ }
123
+
78
124
  function validateMediaBlock(block: unknown, context: string): MediaBlock {
79
125
  if (!block || typeof block !== "object") {
80
126
  throw new Error(`${context}: media must be an object`);
@@ -224,12 +270,18 @@ function validateOption(option: unknown, questionId: string, index: number): Opt
224
270
  );
225
271
  }
226
272
  if (o.content !== undefined) {
227
- return {
273
+ const result: RichOption = {
228
274
  label: o.label,
229
275
  content: validateContentBlock(o.content, `Question "${questionId}" option "${o.label}"`),
230
276
  };
277
+ if (o.recommended === true) result.recommended = true;
278
+ if (o.conviction === "strong" || o.conviction === "slight") result.conviction = o.conviction;
279
+ return result;
231
280
  }
232
- return { label: o.label };
281
+ const result: RichOption = { label: o.label };
282
+ if (o.recommended === true) result.recommended = true;
283
+ if (o.conviction === "strong" || o.conviction === "slight") result.conviction = o.conviction;
284
+ return result;
233
285
  }
234
286
  throw new Error(
235
287
  `Question "${questionId}": option at index ${index} must be a string or object with label`
@@ -354,6 +406,7 @@ export function validateQuestions(data: unknown): QuestionsFile {
354
406
  if (!q.options || q.options.length === 0) {
355
407
  throw new Error(`Question "${q.id}": options required for type "${q.type}"`);
356
408
  }
409
+ normalizeOptionLevelRecommendations(q);
357
410
  } else if (q.type === "text" || q.type === "image" || q.type === "info") {
358
411
  if (q.options) {
359
412
  throw new Error(`Question "${q.id}": options not allowed for type "${q.type}"`);