question-registry.ts
679 lines · scripts/question-registry.ts at 3f79d81
1/**
2 * Question Registry — typed schema for AskUserQuestion invocations across gstack.
3 *
4 * Purpose
5 * -------
6 * Every AskUserQuestion invocation is tagged with a stable question_id that maps
7 * to an entry in this registry. The registry is the substrate /plan-tune builds on:
8 * - Logging (question-log.jsonl) tags events with a registered id
9 * - Per-question preferences (question-preferences.json) are keyed by registered id
10 * - One-way door safety is declared here, not inferred from prose summaries
11 * - The psychographic signal map (scripts/psychographic-signals.ts) maps id → dimension delta
12 *
13 * Not every AskUserQuestion in gstack needs a registry entry right away. Skills
14 * often craft questions dynamically at runtime — the agent generates an ad-hoc id
15 * of the form `{skill}-{slug}` for those. The /plan-tune skill surfaces frequently-
16 * firing ad-hoc ids as candidates for registry promotion.
17 *
18 * v1 coverage target: the ~30-50 most-common recurring question categories across
19 * ship, review, office-hours, plan-ceo-review, plan-eng-review, plan-design-review,
20 * plan-devex-review, qa, investigate, and land-and-deploy. One-way doors 100%.
21 *
22 * Adding a new entry
23 * ------------------
24 * 1. Pick a kebab-case id of the form `{skill}-{what-it-asks-about}`.
25 * 2. Classify `door_type`:
26 * - `one-way` for destructive ops, architecture/data-model forks,
27 * scope-adds > 1 day CC effort, security/compliance choices.
28 * ALWAYS asked regardless of user preference.
29 * - `two-way` for everything else (can be auto-decided by explicit preference).
30 * 3. Pick the `category` that describes the question's shape.
31 * 4. Add an optional `signal_key` if this question's answer should nudge a
32 * specific psychographic dimension. The signal map in scripts/psychographic-
33 * signals.ts uses (id, user_choice) to look up the dimension delta.
34 * 5. `options` is a short list of stable option keys. UI labels can vary; keys
35 * must stay the same so preferences survive wording changes.
36 * 6. Run `bun test test/plan-tune.test.ts` to verify format + uniqueness.
37 */
39export type QuestionCategory =
40 | 'approval' // proceed/stop gate (e.g., "approve this plan?")
41 | 'clarification' // need more info to proceed
42 | 'routing' // which path to take (modes, strategies)
43 | 'cherry-pick' // opt-in scope decision (add/defer/skip)
44 | 'feedback-loop'; // inline tune: prompt, iteration feedback
46export type DoorType = 'one-way' | 'two-way';
48/**
49 * Stable keys for the most-common user choice patterns. UI labels can vary
50 * (e.g., "Add to plan" vs "Include in scope"); the stored choice is the key.
51 * Skills may emit custom keys for uncategorizable questions — those still log
52 * but don't get psychographic signal attribution.
53 */
54export type StandardOption =
55 | 'accept'
56 | 'reject'
57 | 'defer'
58 | 'skip'
59 | 'investigate'
60 | 'approve'
61 | 'deny'
62 | 'expand'
63 | 'hold'
64 | 'reduce'
65 | 'selective'
66 | 'fix-now'
67 | 'fix-later'
68 | 'ack-and-ship'
69 | 'false-positive'
70 | 'continue'
71 | 'rerun'
72 | 'stop';
74export interface QuestionDef {
75 /** Stable kebab-case id: `{skill}-{semantic-description}` */
76 id: string;
77 /** Skill that owns this question (must match a gstack skill directory name) */
78 skill: string;
79 /** Shape of the question */
80 category: QuestionCategory;
81 /** Safety classification. one-way is ALWAYS asked regardless of preference */
82 door_type: DoorType;
83 /** Stable option keys (skills may emit keys outside this list; those are logged but untagged) */
84 options?: StandardOption[] | string[];
85 /** Optional key into scripts/psychographic-signals.ts for dimension attribution */
86 signal_key?: string;
87 /** One-line description for docs and /plan-tune profile output */
88 description: string;
89}
91/**
92 * QUESTIONS — initial v1 coverage of recurring question categories.
93 * Grouped by skill for readability. Maintained by hand.
94 *
95 * When adding new skills or question types, extend this object. The CI lint
96 * test/plan-tune.test.ts verifies format, uniqueness, and required fields.
97 */
98export const QUESTIONS = {
99 // -----------------------------------------------------------------------
100 // /ship — pre-landing review, deploy, PR creation
101 // -----------------------------------------------------------------------
102 'ship-release-pipeline-missing': {
103 id: 'ship-release-pipeline-missing',
104 skill: 'ship',
105 category: 'approval',
106 door_type: 'two-way',
107 options: ['accept', 'defer', 'skip'],
108 signal_key: 'distribution-care',
109 description: "New artifact added without CI/CD release pipeline — add now, defer to TODOs, or skip?",
110 },
111 'ship-test-failure-triage': {
112 id: 'ship-test-failure-triage',
113 skill: 'ship',
114 category: 'approval',
115 door_type: 'one-way',
116 options: ['fix-now', 'investigate', 'ack-and-ship'],
117 signal_key: 'test-discipline',
118 description: "Failing tests detected — fix before shipping or investigate root cause?",
119 },
120 'ship-pre-landing-review-fix': {
121 id: 'ship-pre-landing-review-fix',
122 skill: 'ship',
123 category: 'approval',
124 door_type: 'two-way',
125 options: ['fix-now', 'skip'],
126 signal_key: 'code-quality-care',
127 description: "Pre-landing review flagged an issue — fix now or ship as-is?",
128 },
129 'ship-greptile-comment-valid': {
130 id: 'ship-greptile-comment-valid',
131 skill: 'ship',
132 category: 'approval',
133 door_type: 'two-way',
134 options: ['fix-now', 'ack-and-ship', 'false-positive'],
135 signal_key: 'code-quality-care',
136 description: "Greptile flagged a valid issue — fix, ack and ship, or mark false positive?",
137 },
138 'ship-greptile-comment-false-positive': {
139 id: 'ship-greptile-comment-false-positive',
140 skill: 'ship',
141 category: 'approval',
142 door_type: 'two-way',
143 options: ['reply', 'fix-anyway', 'ignore'],
144 description: "Greptile comment looks like a false positive — reply to explain, fix anyway, or ignore silently?",
145 },
146 'ship-todos-create': {
147 id: 'ship-todos-create',
148 skill: 'ship',
149 category: 'approval',
150 door_type: 'two-way',
151 options: ['accept', 'skip'],
152 description: "No TODOS.md found — create a skeleton file now?",
153 },
154 'ship-todos-reorganize': {
155 id: 'ship-todos-reorganize',
156 skill: 'ship',
157 category: 'approval',
158 door_type: 'two-way',
159 options: ['accept', 'skip'],
160 signal_key: 'detail-preference',
161 description: "TODOS.md doesn't follow the recommended structure — reorganize now?",
162 },
163 'ship-changelog-voice-polish': {
164 id: 'ship-changelog-voice-polish',
165 skill: 'ship',
166 category: 'approval',
167 door_type: 'two-way',
168 options: ['accept', 'skip'],
169 signal_key: 'detail-preference',
170 description: "CHANGELOG entry could be polished for voice — apply edits?",
171 },
172 'ship-version-bump-tier': {
173 id: 'ship-version-bump-tier',
174 skill: 'ship',
175 category: 'routing',
176 door_type: 'two-way',
177 options: ['major', 'minor', 'patch'],
178 description: "Version bump: major, minor, or patch?",
179 },
181 // -----------------------------------------------------------------------
182 // /review — pre-landing code review
183 // -----------------------------------------------------------------------
184 'review-finding-fix': {
185 id: 'review-finding-fix',
186 skill: 'review',
187 category: 'approval',
188 door_type: 'two-way',
189 options: ['fix-now', 'ack-and-ship', 'false-positive'],
190 signal_key: 'code-quality-care',
191 description: "Review finding — fix now, ack and ship, or false positive?",
192 },
193 'review-sql-safety': {
194 id: 'review-sql-safety',
195 skill: 'review',
196 category: 'approval',
197 door_type: 'one-way',
198 options: ['fix-now', 'investigate'],
199 description: "Potential SQL injection / unsafe query — fix or investigate further?",
200 },
201 'review-llm-trust-boundary': {
202 id: 'review-llm-trust-boundary',
203 skill: 'review',
204 category: 'approval',
205 door_type: 'one-way',
206 options: ['fix-now', 'investigate'],
207 description: "LLM trust boundary violation — fix before merge?",
208 },
209 'review-gate-disposition': {
210 id: 'review-gate-disposition',
211 skill: 'review',
212 category: 'approval',
213 door_type: 'one-way',
214 options: ['restore', 'keep-justified', 'keep-other', 'leave-open'],
215 description: "Gate edit (test, threshold, CI or suppression relaxed) — restore the gate, keep it with a reason, or leave it open for a later human?",
216 },
218 // -----------------------------------------------------------------------
219 // /office-hours — YC diagnostic + builder brainstorm
220 // -----------------------------------------------------------------------
221 'office-hours-mode-goal': {
222 id: 'office-hours-mode-goal',
223 skill: 'office-hours',
224 category: 'routing',
225 door_type: 'two-way',
226 options: ['startup', 'intrapreneur', 'hackathon', 'oss-research', 'learning', 'fun'],
227 signal_key: 'session-mode',
228 description: "What's your goal with this session? (Sets mode: startup vs builder)",
229 },
230 'office-hours-premise-confirm': {
231 id: 'office-hours-premise-confirm',
232 skill: 'office-hours',
233 category: 'approval',
234 door_type: 'two-way',
235 options: ['accept', 'reject'],
236 description: "Premise check — agree or disagree?",
237 },
238 'office-hours-cross-model-run': {
239 id: 'office-hours-cross-model-run',
240 skill: 'office-hours',
241 category: 'approval',
242 door_type: 'two-way',
243 options: ['accept', 'skip'],
244 description: "Want a second-opinion cross-model review of your brainstorm?",
245 },
246 'office-hours-landscape-privacy-gate': {
247 id: 'office-hours-landscape-privacy-gate',
248 skill: 'office-hours',
249 category: 'approval',
250 door_type: 'one-way',
251 options: ['accept', 'skip'],
252 description: "Run a web search for landscape awareness? (Sends generalized terms to search provider.)",
253 },
254 'office-hours-approach-choose': {
255 id: 'office-hours-approach-choose',
256 skill: 'office-hours',
257 category: 'routing',
258 door_type: 'two-way',
259 options: ['minimal', 'ideal', 'creative'],
260 signal_key: 'scope-appetite',
261 description: "Which implementation approach? (minimal viable vs ideal architecture vs creative lateral)",
262 },
263 'office-hours-design-doc-approve': {
264 id: 'office-hours-design-doc-approve',
265 skill: 'office-hours',
266 category: 'approval',
267 door_type: 'two-way',
268 options: ['accept', 'revise', 'restart'],
269 description: "Approve the design doc, revise sections, or start over?",
270 },
272 // -----------------------------------------------------------------------
273 // /plan-ceo-review — scope & strategy
274 // -----------------------------------------------------------------------
275 'plan-ceo-review-mode': {
276 id: 'plan-ceo-review-mode',
277 skill: 'plan-ceo-review',
278 category: 'routing',
279 door_type: 'two-way',
280 options: ['expand', 'selective', 'hold', 'reduce'],
281 signal_key: 'scope-appetite',
282 description: "Review mode: push scope up, cherry-pick expansions, hold scope, or cut to minimum?",
283 },
284 'plan-ceo-review-expansion-proposal': {
285 id: 'plan-ceo-review-expansion-proposal',
286 skill: 'plan-ceo-review',
287 category: 'cherry-pick',
288 door_type: 'two-way',
289 options: ['accept', 'defer', 'skip'],
290 signal_key: 'scope-appetite',
291 description: "Scope expansion proposal — add to plan, defer to TODOs, or skip?",
292 },
293 'plan-ceo-review-premise-revise': {
294 id: 'plan-ceo-review-premise-revise',
295 skill: 'plan-ceo-review',
296 category: 'approval',
297 door_type: 'one-way',
298 options: ['revise', 'hold'],
299 description: "Cross-model challenged an agreed premise — revise or keep?",
300 },
301 'plan-ceo-review-outside-voice': {
302 id: 'plan-ceo-review-outside-voice',
303 skill: 'plan-ceo-review',
304 category: 'approval',
305 door_type: 'two-way',
306 options: ['accept', 'skip'],
307 description: "Get an outside-voice second opinion on the plan?",
308 },
309 'plan-ceo-review-promote-to-docs': {
310 id: 'plan-ceo-review-promote-to-docs',
311 skill: 'plan-ceo-review',
312 category: 'approval',
313 door_type: 'two-way',
314 options: ['accept', 'keep-local', 'skip'],
315 description: "Promote the CEO plan to docs/designs/ in the repo?",
316 },
318 // -----------------------------------------------------------------------
319 // /plan-eng-review — architecture & tests (required gate)
320 // -----------------------------------------------------------------------
321 'plan-eng-review-arch-finding': {
322 id: 'plan-eng-review-arch-finding',
323 skill: 'plan-eng-review',
324 category: 'approval',
325 door_type: 'one-way',
326 options: ['fix-now', 'defer', 'accept-risk'],
327 signal_key: 'architecture-care',
328 description: "Architecture finding — fix, defer, or accept the risk?",
329 },
330 'plan-eng-review-scope-reduce': {
331 id: 'plan-eng-review-scope-reduce',
332 skill: 'plan-eng-review',
333 category: 'routing',
334 door_type: 'two-way',
335 options: ['reduce', 'hold'],
336 signal_key: 'scope-appetite',
337 description: "Plan touches 8+ files — reduce scope or hold?",
338 },
339 'plan-eng-review-structure': {
340 id: 'plan-eng-review-structure',
341 skill: 'plan-eng-review',
342 category: 'routing',
343 door_type: 'two-way',
344 options: ['original-arrangement', 'smaller-arrangement'],
345 description: "Complexity gate — Original arrangement or Smaller arrangement (file/class layout only)?",
346 },
347 'plan-eng-review-test-gap': {
348 id: 'plan-eng-review-test-gap',
349 skill: 'plan-eng-review',
350 category: 'approval',
351 door_type: 'two-way',
352 options: ['add-test', 'defer', 'skip'],
353 signal_key: 'test-discipline',
354 description: "Test gap identified — add now, defer, or skip?",
355 },
356 'plan-eng-review-outside-voice': {
357 id: 'plan-eng-review-outside-voice',
358 skill: 'plan-eng-review',
359 category: 'approval',
360 door_type: 'two-way',
361 options: ['accept', 'skip'],
362 description: "Get an outside-voice second opinion on the plan?",
363 },
364 'plan-eng-review-todo-add': {
365 id: 'plan-eng-review-todo-add',
366 skill: 'plan-eng-review',
367 category: 'cherry-pick',
368 door_type: 'two-way',
369 options: ['accept', 'skip', 'build-now'],
370 description: "Proposed TODO item — add to TODOs, skip, or build in this PR?",
371 },
373 // -----------------------------------------------------------------------
374 // /plan-design-review — UI/UX plan audit
375 // -----------------------------------------------------------------------
376 'plan-design-review-mode': {
377 id: 'plan-design-review-mode',
378 skill: 'plan-design-review',
379 category: 'routing',
380 door_type: 'two-way',
381 options: ['expand', 'polish', 'triage'],
382 signal_key: 'design-care',
383 description: "Design review depth: expand for competitive edge, polish every touchpoint, or triage critical gaps?",
384 },
385 'plan-design-review-fix': {
386 id: 'plan-design-review-fix',
387 skill: 'plan-design-review',
388 category: 'approval',
389 door_type: 'two-way',
390 options: ['fix-now', 'defer', 'skip'],
391 signal_key: 'design-care',
392 description: "Design issue flagged — fix now, defer to TODOs, or skip?",
393 },
395 // -----------------------------------------------------------------------
396 // /plan-devex-review — developer experience plan audit
397 // -----------------------------------------------------------------------
398 'plan-devex-review-persona': {
399 id: 'plan-devex-review-persona',
400 skill: 'plan-devex-review',
401 category: 'clarification',
402 door_type: 'two-way',
403 description: "Who is your target developer? (Determines persona for review.)",
404 },
405 'plan-devex-review-mode': {
406 id: 'plan-devex-review-mode',
407 skill: 'plan-devex-review',
408 category: 'routing',
409 door_type: 'two-way',
410 options: ['expand', 'polish', 'triage'],
411 signal_key: 'devex-care',
412 description: "DX review depth: expand for competitive advantage, polish every touchpoint, or triage critical gaps?",
413 },
414 'plan-devex-review-friction-fix': {
415 id: 'plan-devex-review-friction-fix',
416 skill: 'plan-devex-review',
417 category: 'approval',
418 door_type: 'two-way',
419 options: ['fix-now', 'defer', 'skip'],
420 signal_key: 'devex-care',
421 description: "Friction point in the developer journey — fix now, defer, or skip?",
422 },
424 // -----------------------------------------------------------------------
425 // /qa — QA testing
426 // -----------------------------------------------------------------------
427 'qa-bug-fix-scope': {
428 id: 'qa-bug-fix-scope',
429 skill: 'qa',
430 category: 'approval',
431 door_type: 'two-way',
432 options: ['fix-now', 'defer', 'skip'],
433 signal_key: 'code-quality-care',
434 description: "Bug found during QA — fix now, defer, or skip?",
435 },
436 'qa-tier': {
437 id: 'qa-tier',
438 skill: 'qa',
439 category: 'routing',
440 door_type: 'two-way',
441 options: ['quick', 'standard', 'deep'],
442 description: "QA tier: quick (critical/high only), standard (+medium), or deep (+low)?",
443 },
445 // -----------------------------------------------------------------------
446 // /investigate — root-cause debugging
447 // -----------------------------------------------------------------------
448 'investigate-hypothesis-confirm': {
449 id: 'investigate-hypothesis-confirm',
450 skill: 'investigate',
451 category: 'approval',
452 door_type: 'two-way',
453 options: ['accept', 'reject', 'refine'],
454 description: "Root-cause hypothesis — accept, reject, or refine before proceeding to fix?",
455 },
456 'investigate-fix-apply': {
457 id: 'investigate-fix-apply',
458 skill: 'investigate',
459 category: 'approval',
460 door_type: 'one-way',
461 options: ['accept', 'reject'],
462 description: "Apply the proposed fix?",
463 },
465 // -----------------------------------------------------------------------
466 // /land-and-deploy — merge + deploy + verify
467 // -----------------------------------------------------------------------
468 'land-and-deploy-merge-confirm': {
469 id: 'land-and-deploy-merge-confirm',
470 skill: 'land-and-deploy',
471 category: 'approval',
472 door_type: 'one-way',
473 options: ['accept', 'reject'],
474 signal_key: 'decision-autonomy',
475 description: "Merge this PR to base branch?",
476 },
477 'land-and-deploy-rollback': {
478 id: 'land-and-deploy-rollback',
479 skill: 'land-and-deploy',
480 category: 'approval',
481 door_type: 'one-way',
482 options: ['accept', 'reject'],
483 signal_key: 'decision-autonomy',
484 description: "Canary detected regressions — roll back the deploy?",
485 },
486 'land-and-deploy-ci-override': {
487 id: 'land-and-deploy-ci-override',
488 skill: 'land-and-deploy',
489 category: 'approval',
490 door_type: 'one-way',
491 options: ['accept', 'reject'],
492 description: "Merge this head while these named non-required CI checks are red or pending?",
493 },
494 'land-and-deploy-no-ci-confirm': {
495 id: 'land-and-deploy-no-ci-confirm',
496 skill: 'land-and-deploy',
497 category: 'approval',
498 door_type: 'one-way',
499 options: ['accept', 'reject'],
500 description: "No CI ran on this head — merge it anyway?",
501 },
503 // -----------------------------------------------------------------------
504 // /cso — security audit
505 // -----------------------------------------------------------------------
506 'cso-global-scan-approval': {
507 id: 'cso-global-scan-approval',
508 skill: 'cso',
509 category: 'approval',
510 door_type: 'one-way',
511 options: ['accept', 'deny'],
512 description: "Run a global security scan? (Scans files outside this branch.)",
513 },
514 'cso-finding-fix': {
515 id: 'cso-finding-fix',
516 skill: 'cso',
517 category: 'approval',
518 door_type: 'one-way',
519 options: ['fix-now', 'defer', 'accept-risk'],
520 description: "Security finding — fix, defer to TODOs, or accept the risk?",
521 },
523 // -----------------------------------------------------------------------
524 // /gstack-upgrade — version upgrade
525 // -----------------------------------------------------------------------
526 'gstack-upgrade-inline': {
527 id: 'gstack-upgrade-inline',
528 skill: 'gstack-upgrade',
529 category: 'approval',
530 door_type: 'two-way',
531 options: ['yes-upgrade', 'always-auto', 'not-now', 'never-ask'],
532 description: "Upgrade gstack now? (Also: always auto-upgrade, snooze, or disable the prompt.)",
533 },
535 // -----------------------------------------------------------------------
536 // Preamble one-time prompts (telemetry, proactive, routing)
537 // -----------------------------------------------------------------------
538 'preamble-telemetry-consent': {
539 id: 'preamble-telemetry-consent',
540 skill: 'preamble',
541 category: 'approval',
542 door_type: 'two-way',
543 options: ['community', 'anonymous', 'off'],
544 description: "Share usage data with gstack? community (recommended) / anonymous / off",
545 },
546 'preamble-proactive-behavior': {
547 id: 'preamble-proactive-behavior',
548 skill: 'preamble',
549 category: 'approval',
550 door_type: 'two-way',
551 options: ['core', 'all', 'off'],
552 description: "Let gstack proactively suggest skills based on conversation context? core (the four core skills, default) / all / off",
553 },
554 'preamble-routing-injection': {
555 id: 'preamble-routing-injection',
556 skill: 'preamble',
557 category: 'approval',
558 door_type: 'two-way',
559 options: ['accept', 'decline'],
560 description: "Add gstack skill routing rules to CLAUDE.md?",
561 },
562 'preamble-vendored-migration': {
563 id: 'preamble-vendored-migration',
564 skill: 'preamble',
565 category: 'approval',
566 door_type: 'two-way',
567 options: ['accept', 'keep-vendored'],
568 description: "This repo has vendored gstack (deprecated) — migrate to team mode?",
569 },
570 'preamble-completeness-intro': {
571 id: 'preamble-completeness-intro',
572 skill: 'preamble',
573 category: 'approval',
574 door_type: 'two-way',
575 options: ['accept', 'skip'],
576 description: "Open the Boil-the-Lake essay in your browser? (one-time intro)",
577 },
578 'preamble-cross-project-learnings': {
579 id: 'preamble-cross-project-learnings',
580 skill: 'preamble',
581 category: 'approval',
582 door_type: 'two-way',
583 options: ['accept', 'reject'],
584 description: "Enable cross-project learnings search? (local only, helpful for solo devs)",
585 },
587 // -----------------------------------------------------------------------
588 // /plan-tune — the skill itself
589 // -----------------------------------------------------------------------
590 'plan-tune-enable-setup': {
591 id: 'plan-tune-enable-setup',
592 skill: 'plan-tune',
593 category: 'approval',
594 door_type: 'two-way',
595 options: ['accept', 'skip'],
596 description: "Question tuning is off — enable it and set up your profile?",
597 },
598 'plan-tune-declared-dimension': {
599 id: 'plan-tune-declared-dimension',
600 skill: 'plan-tune',
601 category: 'clarification',
602 door_type: 'two-way',
603 description: "Self-declaration question (one per dimension during /plan-tune setup)",
604 },
605 'plan-tune-confirm-mutation': {
606 id: 'plan-tune-confirm-mutation',
607 skill: 'plan-tune',
608 category: 'approval',
609 door_type: 'two-way',
610 options: ['accept', 'reject'],
611 description: "Confirm profile change before writing (user sovereignty gate for free-form edits)",
612 },
614 // -----------------------------------------------------------------------
615 // /autoplan — sequential auto-review
616 // -----------------------------------------------------------------------
617 'autoplan-taste-decision': {
618 id: 'autoplan-taste-decision',
619 skill: 'autoplan',
620 category: 'approval',
621 door_type: 'two-way',
622 options: ['accept', 'override', 'investigate'],
623 description: "Autoplan surfaced a taste decision at the final gate — accept, override, or investigate?",
624 },
625 'autoplan-user-challenge': {
626 id: 'autoplan-user-challenge',
627 skill: 'autoplan',
628 category: 'approval',
629 door_type: 'one-way',
630 options: ['accept', 'reject', 'revise'],
631 description: "Both models agree your direction should change — accept, reject, or revise the plan?",
632 },
633} as const satisfies Record<string, QuestionDef>;
635export type RegisteredQuestionId = keyof typeof QUESTIONS;
637/**
638 * Runtime lookup — returns undefined for ad-hoc question_ids (not registered).
639 * Ad-hoc ids still log; they just don't get psychographic signal attribution.
640 */
641export function getQuestion(id: string): QuestionDef | undefined {
642 return (QUESTIONS as Record<string, QuestionDef>)[id];
643}
645/** Get all registered one-way door question ids (used by sensitivity checker) */
646export function getOneWayDoorIds(): Set<string> {
647 return new Set(
648 Object.values(QUESTIONS as Record<string, QuestionDef>)
649 .filter((q) => q.door_type === 'one-way')
650 .map((q) => q.id),
651 );
652}
654/** All registered question ids, for CI completeness checks */
655export function getAllRegisteredIds(): Set<string> {
656 return new Set(Object.keys(QUESTIONS));
657}
659/** Registry stats, for /plan-tune stats */
660export function getRegistryStats() {
661 const all = Object.values(QUESTIONS as Record<string, QuestionDef>);
662 const bySkill: Record<string, number> = {};
663 const byCategory: Record<string, number> = {};
664 let oneWay = 0;
665 let twoWay = 0;
666 for (const q of all) {
667 bySkill[q.skill] = (bySkill[q.skill] ?? 0) + 1;
668 byCategory[q.category] = (byCategory[q.category] ?? 0) + 1;
669 if (q.door_type === 'one-way') oneWay++;
670 else twoWay++;
671 }
672 return {
673 total: all.length,
674 one_way: oneWay,
675 two_way: twoWay,
676 by_skill: bySkill,
677 by_category: byCategory,
678 };
679}