diff --git a/scripts/question-registry.ts b/scripts/question-registry.ts new file mode 100644 index 00000000..bae5950c --- /dev/null +++ b/scripts/question-registry.ts @@ -0,0 +1,645 @@ +/** + * Question Registry — typed schema for AskUserQuestion invocations across gstack. + * + * Purpose + * ------- + * Every AskUserQuestion invocation is tagged with a stable question_id that maps + * to an entry in this registry. The registry is the substrate /plan-tune builds on: + * - Logging (question-log.jsonl) tags events with a registered id + * - Per-question preferences (question-preferences.json) are keyed by registered id + * - One-way door safety is declared here, not inferred from prose summaries + * - The psychographic signal map (scripts/psychographic-signals.ts) maps id → dimension delta + * + * Not every AskUserQuestion in gstack needs a registry entry right away. Skills + * often craft questions dynamically at runtime — the agent generates an ad-hoc id + * of the form `{skill}-{slug}` for those. The /plan-tune skill surfaces frequently- + * firing ad-hoc ids as candidates for registry promotion. + * + * v1 coverage target: the ~30-50 most-common recurring question categories across + * ship, review, office-hours, plan-ceo-review, plan-eng-review, plan-design-review, + * plan-devex-review, qa, investigate, and land-and-deploy. One-way doors 100%. + * + * Adding a new entry + * ------------------ + * 1. Pick a kebab-case id of the form `{skill}-{what-it-asks-about}`. + * 2. Classify `door_type`: + * - `one-way` for destructive ops, architecture/data-model forks, + * scope-adds > 1 day CC effort, security/compliance choices. + * ALWAYS asked regardless of user preference. + * - `two-way` for everything else (can be auto-decided by explicit preference). + * 3. Pick the `category` that describes the question's shape. + * 4. Add an optional `signal_key` if this question's answer should nudge a + * specific psychographic dimension. The signal map in scripts/psychographic- + * signals.ts uses (id, user_choice) to look up the dimension delta. + * 5. `options` is a short list of stable option keys. UI labels can vary; keys + * must stay the same so preferences survive wording changes. + * 6. Run `bun test test/plan-tune.test.ts` to verify format + uniqueness. + */ + +export type QuestionCategory = + | 'approval' // proceed/stop gate (e.g., "approve this plan?") + | 'clarification' // need more info to proceed + | 'routing' // which path to take (modes, strategies) + | 'cherry-pick' // opt-in scope decision (add/defer/skip) + | 'feedback-loop'; // inline tune: prompt, iteration feedback + +export type DoorType = 'one-way' | 'two-way'; + +/** + * Stable keys for the most-common user choice patterns. UI labels can vary + * (e.g., "Add to plan" vs "Include in scope"); the stored choice is the key. + * Skills may emit custom keys for uncategorizable questions — those still log + * but don't get psychographic signal attribution. + */ +export type StandardOption = + | 'accept' + | 'reject' + | 'defer' + | 'skip' + | 'investigate' + | 'approve' + | 'deny' + | 'expand' + | 'hold' + | 'reduce' + | 'selective' + | 'fix-now' + | 'fix-later' + | 'ack-and-ship' + | 'false-positive' + | 'continue' + | 'rerun' + | 'stop'; + +export interface QuestionDef { + /** Stable kebab-case id: `{skill}-{semantic-description}` */ + id: string; + /** Skill that owns this question (must match a gstack skill directory name) */ + skill: string; + /** Shape of the question */ + category: QuestionCategory; + /** Safety classification. one-way is ALWAYS asked regardless of preference */ + door_type: DoorType; + /** Stable option keys (skills may emit keys outside this list; those are logged but untagged) */ + options?: StandardOption[] | string[]; + /** Optional key into scripts/psychographic-signals.ts for dimension attribution */ + signal_key?: string; + /** One-line description for docs and /plan-tune profile output */ + description: string; +} + +/** + * QUESTIONS — initial v1 coverage of recurring question categories. + * Grouped by skill for readability. Maintained by hand. + * + * When adding new skills or question types, extend this object. The CI lint + * test/plan-tune.test.ts verifies format, uniqueness, and required fields. + */ +export const QUESTIONS = { + // ----------------------------------------------------------------------- + // /ship — pre-landing review, deploy, PR creation + // ----------------------------------------------------------------------- + 'ship-release-pipeline-missing': { + id: 'ship-release-pipeline-missing', + skill: 'ship', + category: 'approval', + door_type: 'two-way', + options: ['accept', 'defer', 'skip'], + signal_key: 'distribution-care', + description: "New artifact added without CI/CD release pipeline — add now, defer to TODOs, or skip?", + }, + 'ship-test-failure-triage': { + id: 'ship-test-failure-triage', + skill: 'ship', + category: 'approval', + door_type: 'one-way', + options: ['fix-now', 'investigate', 'ack-and-ship'], + signal_key: 'test-discipline', + description: "Failing tests detected — fix before shipping or investigate root cause?", + }, + 'ship-pre-landing-review-fix': { + id: 'ship-pre-landing-review-fix', + skill: 'ship', + category: 'approval', + door_type: 'two-way', + options: ['fix-now', 'skip'], + signal_key: 'code-quality-care', + description: "Pre-landing review flagged an issue — fix now or ship as-is?", + }, + 'ship-greptile-comment-valid': { + id: 'ship-greptile-comment-valid', + skill: 'ship', + category: 'approval', + door_type: 'two-way', + options: ['fix-now', 'ack-and-ship', 'false-positive'], + signal_key: 'code-quality-care', + description: "Greptile flagged a valid issue — fix, ack and ship, or mark false positive?", + }, + 'ship-greptile-comment-false-positive': { + id: 'ship-greptile-comment-false-positive', + skill: 'ship', + category: 'approval', + door_type: 'two-way', + options: ['reply', 'fix-anyway', 'ignore'], + description: "Greptile comment looks like a false positive — reply to explain, fix anyway, or ignore silently?", + }, + 'ship-todos-create': { + id: 'ship-todos-create', + skill: 'ship', + category: 'approval', + door_type: 'two-way', + options: ['accept', 'skip'], + description: "No TODOS.md found — create a skeleton file now?", + }, + 'ship-todos-reorganize': { + id: 'ship-todos-reorganize', + skill: 'ship', + category: 'approval', + door_type: 'two-way', + options: ['accept', 'skip'], + signal_key: 'detail-preference', + description: "TODOS.md doesn't follow the recommended structure — reorganize now?", + }, + 'ship-changelog-voice-polish': { + id: 'ship-changelog-voice-polish', + skill: 'ship', + category: 'approval', + door_type: 'two-way', + options: ['accept', 'skip'], + signal_key: 'detail-preference', + description: "CHANGELOG entry could be polished for voice — apply edits?", + }, + 'ship-version-bump-tier': { + id: 'ship-version-bump-tier', + skill: 'ship', + category: 'routing', + door_type: 'two-way', + options: ['major', 'minor', 'patch'], + description: "Version bump: major, minor, or patch?", + }, + + // ----------------------------------------------------------------------- + // /review — pre-landing code review + // ----------------------------------------------------------------------- + 'review-finding-fix': { + id: 'review-finding-fix', + skill: 'review', + category: 'approval', + door_type: 'two-way', + options: ['fix-now', 'ack-and-ship', 'false-positive'], + signal_key: 'code-quality-care', + description: "Review finding — fix now, ack and ship, or false positive?", + }, + 'review-sql-safety': { + id: 'review-sql-safety', + skill: 'review', + category: 'approval', + door_type: 'one-way', + options: ['fix-now', 'investigate'], + description: "Potential SQL injection / unsafe query — fix or investigate further?", + }, + 'review-llm-trust-boundary': { + id: 'review-llm-trust-boundary', + skill: 'review', + category: 'approval', + door_type: 'one-way', + options: ['fix-now', 'investigate'], + description: "LLM trust boundary violation — fix before merge?", + }, + + // ----------------------------------------------------------------------- + // /office-hours — YC diagnostic + builder brainstorm + // ----------------------------------------------------------------------- + 'office-hours-mode-goal': { + id: 'office-hours-mode-goal', + skill: 'office-hours', + category: 'routing', + door_type: 'two-way', + options: ['startup', 'intrapreneur', 'hackathon', 'oss-research', 'learning', 'fun'], + signal_key: 'session-mode', + description: "What's your goal with this session? (Sets mode: startup vs builder)", + }, + 'office-hours-premise-confirm': { + id: 'office-hours-premise-confirm', + skill: 'office-hours', + category: 'approval', + door_type: 'two-way', + options: ['accept', 'reject'], + description: "Premise check — agree or disagree?", + }, + 'office-hours-cross-model-run': { + id: 'office-hours-cross-model-run', + skill: 'office-hours', + category: 'approval', + door_type: 'two-way', + options: ['accept', 'skip'], + description: "Want a second-opinion cross-model review of your brainstorm?", + }, + 'office-hours-landscape-privacy-gate': { + id: 'office-hours-landscape-privacy-gate', + skill: 'office-hours', + category: 'approval', + door_type: 'one-way', + options: ['accept', 'skip'], + description: "Run a web search for landscape awareness? (Sends generalized terms to search provider.)", + }, + 'office-hours-approach-choose': { + id: 'office-hours-approach-choose', + skill: 'office-hours', + category: 'routing', + door_type: 'two-way', + options: ['minimal', 'ideal', 'creative'], + signal_key: 'scope-appetite', + description: "Which implementation approach? (minimal viable vs ideal architecture vs creative lateral)", + }, + 'office-hours-design-doc-approve': { + id: 'office-hours-design-doc-approve', + skill: 'office-hours', + category: 'approval', + door_type: 'two-way', + options: ['accept', 'revise', 'restart'], + description: "Approve the design doc, revise sections, or start over?", + }, + + // ----------------------------------------------------------------------- + // /plan-ceo-review — scope & strategy + // ----------------------------------------------------------------------- + 'plan-ceo-review-mode': { + id: 'plan-ceo-review-mode', + skill: 'plan-ceo-review', + category: 'routing', + door_type: 'two-way', + options: ['expand', 'selective', 'hold', 'reduce'], + signal_key: 'scope-appetite', + description: "Review mode: push scope up, cherry-pick expansions, hold scope, or cut to minimum?", + }, + 'plan-ceo-review-expansion-proposal': { + id: 'plan-ceo-review-expansion-proposal', + skill: 'plan-ceo-review', + category: 'cherry-pick', + door_type: 'two-way', + options: ['accept', 'defer', 'skip'], + signal_key: 'scope-appetite', + description: "Scope expansion proposal — add to plan, defer to TODOs, or skip?", + }, + 'plan-ceo-review-premise-revise': { + id: 'plan-ceo-review-premise-revise', + skill: 'plan-ceo-review', + category: 'approval', + door_type: 'one-way', + options: ['revise', 'hold'], + description: "Cross-model challenged an agreed premise — revise or keep?", + }, + 'plan-ceo-review-outside-voice': { + id: 'plan-ceo-review-outside-voice', + skill: 'plan-ceo-review', + category: 'approval', + door_type: 'two-way', + options: ['accept', 'skip'], + description: "Get an outside-voice second opinion on the plan?", + }, + 'plan-ceo-review-promote-to-docs': { + id: 'plan-ceo-review-promote-to-docs', + skill: 'plan-ceo-review', + category: 'approval', + door_type: 'two-way', + options: ['accept', 'keep-local', 'skip'], + description: "Promote the CEO plan to docs/designs/ in the repo?", + }, + + // ----------------------------------------------------------------------- + // /plan-eng-review — architecture & tests (required gate) + // ----------------------------------------------------------------------- + 'plan-eng-review-arch-finding': { + id: 'plan-eng-review-arch-finding', + skill: 'plan-eng-review', + category: 'approval', + door_type: 'one-way', + options: ['fix-now', 'defer', 'accept-risk'], + signal_key: 'architecture-care', + description: "Architecture finding — fix, defer, or accept the risk?", + }, + 'plan-eng-review-scope-reduce': { + id: 'plan-eng-review-scope-reduce', + skill: 'plan-eng-review', + category: 'routing', + door_type: 'two-way', + options: ['reduce', 'hold'], + signal_key: 'scope-appetite', + description: "Plan touches 8+ files — reduce scope or hold?", + }, + 'plan-eng-review-test-gap': { + id: 'plan-eng-review-test-gap', + skill: 'plan-eng-review', + category: 'approval', + door_type: 'two-way', + options: ['add-test', 'defer', 'skip'], + signal_key: 'test-discipline', + description: "Test gap identified — add now, defer, or skip?", + }, + 'plan-eng-review-outside-voice': { + id: 'plan-eng-review-outside-voice', + skill: 'plan-eng-review', + category: 'approval', + door_type: 'two-way', + options: ['accept', 'skip'], + description: "Get an outside-voice second opinion on the plan?", + }, + 'plan-eng-review-todo-add': { + id: 'plan-eng-review-todo-add', + skill: 'plan-eng-review', + category: 'cherry-pick', + door_type: 'two-way', + options: ['accept', 'skip', 'build-now'], + description: "Proposed TODO item — add to TODOs, skip, or build in this PR?", + }, + + // ----------------------------------------------------------------------- + // /plan-design-review — UI/UX plan audit + // ----------------------------------------------------------------------- + 'plan-design-review-mode': { + id: 'plan-design-review-mode', + skill: 'plan-design-review', + category: 'routing', + door_type: 'two-way', + options: ['expand', 'polish', 'triage'], + signal_key: 'design-care', + description: "Design review depth: expand for competitive edge, polish every touchpoint, or triage critical gaps?", + }, + 'plan-design-review-fix': { + id: 'plan-design-review-fix', + skill: 'plan-design-review', + category: 'approval', + door_type: 'two-way', + options: ['fix-now', 'defer', 'skip'], + signal_key: 'design-care', + description: "Design issue flagged — fix now, defer to TODOs, or skip?", + }, + + // ----------------------------------------------------------------------- + // /plan-devex-review — developer experience plan audit + // ----------------------------------------------------------------------- + 'plan-devex-review-persona': { + id: 'plan-devex-review-persona', + skill: 'plan-devex-review', + category: 'clarification', + door_type: 'two-way', + description: "Who is your target developer? (Determines persona for review.)", + }, + 'plan-devex-review-mode': { + id: 'plan-devex-review-mode', + skill: 'plan-devex-review', + category: 'routing', + door_type: 'two-way', + options: ['expand', 'polish', 'triage'], + signal_key: 'devex-care', + description: "DX review depth: expand for competitive advantage, polish every touchpoint, or triage critical gaps?", + }, + 'plan-devex-review-friction-fix': { + id: 'plan-devex-review-friction-fix', + skill: 'plan-devex-review', + category: 'approval', + door_type: 'two-way', + options: ['fix-now', 'defer', 'skip'], + signal_key: 'devex-care', + description: "Friction point in the developer journey — fix now, defer, or skip?", + }, + + // ----------------------------------------------------------------------- + // /qa — QA testing + // ----------------------------------------------------------------------- + 'qa-bug-fix-scope': { + id: 'qa-bug-fix-scope', + skill: 'qa', + category: 'approval', + door_type: 'two-way', + options: ['fix-now', 'defer', 'skip'], + signal_key: 'code-quality-care', + description: "Bug found during QA — fix now, defer, or skip?", + }, + 'qa-tier': { + id: 'qa-tier', + skill: 'qa', + category: 'routing', + door_type: 'two-way', + options: ['quick', 'standard', 'deep'], + description: "QA tier: quick (critical/high only), standard (+medium), or deep (+low)?", + }, + + // ----------------------------------------------------------------------- + // /investigate — root-cause debugging + // ----------------------------------------------------------------------- + 'investigate-hypothesis-confirm': { + id: 'investigate-hypothesis-confirm', + skill: 'investigate', + category: 'approval', + door_type: 'two-way', + options: ['accept', 'reject', 'refine'], + description: "Root-cause hypothesis — accept, reject, or refine before proceeding to fix?", + }, + 'investigate-fix-apply': { + id: 'investigate-fix-apply', + skill: 'investigate', + category: 'approval', + door_type: 'one-way', + options: ['accept', 'reject'], + description: "Apply the proposed fix?", + }, + + // ----------------------------------------------------------------------- + // /land-and-deploy — merge + deploy + verify + // ----------------------------------------------------------------------- + 'land-and-deploy-merge-confirm': { + id: 'land-and-deploy-merge-confirm', + skill: 'land-and-deploy', + category: 'approval', + door_type: 'one-way', + options: ['accept', 'reject'], + description: "Merge this PR to base branch?", + }, + 'land-and-deploy-rollback': { + id: 'land-and-deploy-rollback', + skill: 'land-and-deploy', + category: 'approval', + door_type: 'one-way', + options: ['accept', 'reject'], + description: "Canary detected regressions — roll back the deploy?", + }, + + // ----------------------------------------------------------------------- + // /cso — security audit + // ----------------------------------------------------------------------- + 'cso-global-scan-approval': { + id: 'cso-global-scan-approval', + skill: 'cso', + category: 'approval', + door_type: 'one-way', + options: ['accept', 'deny'], + description: "Run a global security scan? (Scans files outside this branch.)", + }, + 'cso-finding-fix': { + id: 'cso-finding-fix', + skill: 'cso', + category: 'approval', + door_type: 'one-way', + options: ['fix-now', 'defer', 'accept-risk'], + description: "Security finding — fix, defer to TODOs, or accept the risk?", + }, + + // ----------------------------------------------------------------------- + // /gstack-upgrade — version upgrade + // ----------------------------------------------------------------------- + 'gstack-upgrade-inline': { + id: 'gstack-upgrade-inline', + skill: 'gstack-upgrade', + category: 'approval', + door_type: 'two-way', + options: ['yes-upgrade', 'always-auto', 'not-now', 'never-ask'], + description: "Upgrade gstack now? (Also: always auto-upgrade, snooze, or disable the prompt.)", + }, + + // ----------------------------------------------------------------------- + // Preamble one-time prompts (telemetry, proactive, routing) + // ----------------------------------------------------------------------- + 'preamble-telemetry-consent': { + id: 'preamble-telemetry-consent', + skill: 'preamble', + category: 'approval', + door_type: 'two-way', + options: ['community', 'anonymous', 'off'], + description: "Share usage data with gstack? community (recommended) / anonymous / off", + }, + 'preamble-proactive-behavior': { + id: 'preamble-proactive-behavior', + skill: 'preamble', + category: 'approval', + door_type: 'two-way', + options: ['on', 'off'], + description: "Let gstack proactively suggest skills based on conversation context?", + }, + 'preamble-routing-injection': { + id: 'preamble-routing-injection', + skill: 'preamble', + category: 'approval', + door_type: 'two-way', + options: ['accept', 'decline'], + description: "Add gstack skill routing rules to CLAUDE.md?", + }, + 'preamble-vendored-migration': { + id: 'preamble-vendored-migration', + skill: 'preamble', + category: 'approval', + door_type: 'two-way', + options: ['accept', 'keep-vendored'], + description: "This repo has vendored gstack (deprecated) — migrate to team mode?", + }, + 'preamble-completeness-intro': { + id: 'preamble-completeness-intro', + skill: 'preamble', + category: 'approval', + door_type: 'two-way', + options: ['accept', 'skip'], + description: "Open the Boil-the-Lake essay in your browser? (one-time intro)", + }, + 'preamble-cross-project-learnings': { + id: 'preamble-cross-project-learnings', + skill: 'preamble', + category: 'approval', + door_type: 'two-way', + options: ['accept', 'reject'], + description: "Enable cross-project learnings search? (local only, helpful for solo devs)", + }, + + // ----------------------------------------------------------------------- + // /plan-tune — the skill itself + // ----------------------------------------------------------------------- + 'plan-tune-enable-setup': { + id: 'plan-tune-enable-setup', + skill: 'plan-tune', + category: 'approval', + door_type: 'two-way', + options: ['accept', 'skip'], + description: "Question tuning is off — enable it and set up your profile?", + }, + 'plan-tune-declared-dimension': { + id: 'plan-tune-declared-dimension', + skill: 'plan-tune', + category: 'clarification', + door_type: 'two-way', + description: "Self-declaration question (one per dimension during /plan-tune setup)", + }, + 'plan-tune-confirm-mutation': { + id: 'plan-tune-confirm-mutation', + skill: 'plan-tune', + category: 'approval', + door_type: 'two-way', + options: ['accept', 'reject'], + description: "Confirm profile change before writing (user sovereignty gate for free-form edits)", + }, + + // ----------------------------------------------------------------------- + // /autoplan — sequential auto-review + // ----------------------------------------------------------------------- + 'autoplan-taste-decision': { + id: 'autoplan-taste-decision', + skill: 'autoplan', + category: 'approval', + door_type: 'two-way', + options: ['accept', 'override', 'investigate'], + description: "Autoplan surfaced a taste decision at the final gate — accept, override, or investigate?", + }, + 'autoplan-user-challenge': { + id: 'autoplan-user-challenge', + skill: 'autoplan', + category: 'approval', + door_type: 'one-way', + options: ['accept', 'reject', 'revise'], + description: "Both models agree your direction should change — accept, reject, or revise the plan?", + }, +} as const satisfies Record; + +export type RegisteredQuestionId = keyof typeof QUESTIONS; + +/** + * Runtime lookup — returns undefined for ad-hoc question_ids (not registered). + * Ad-hoc ids still log; they just don't get psychographic signal attribution. + */ +export function getQuestion(id: string): QuestionDef | undefined { + return (QUESTIONS as Record)[id]; +} + +/** Get all registered one-way door question ids (used by sensitivity checker) */ +export function getOneWayDoorIds(): Set { + return new Set( + Object.values(QUESTIONS as Record) + .filter((q) => q.door_type === 'one-way') + .map((q) => q.id), + ); +} + +/** All registered question ids, for CI completeness checks */ +export function getAllRegisteredIds(): Set { + return new Set(Object.keys(QUESTIONS)); +} + +/** Registry stats, for /plan-tune stats */ +export function getRegistryStats() { + const all = Object.values(QUESTIONS as Record); + const bySkill: Record = {}; + const byCategory: Record = {}; + let oneWay = 0; + let twoWay = 0; + for (const q of all) { + bySkill[q.skill] = (bySkill[q.skill] ?? 0) + 1; + byCategory[q.category] = (byCategory[q.category] ?? 0) + 1; + if (q.door_type === 'one-way') oneWay++; + else twoWay++; + } + return { + total: all.length, + one_way: oneWay, + two_way: twoWay, + by_skill: bySkill, + by_category: byCategory, + }; +}