mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-01 19:25:10 +02:00
9e244c0bed
* feat: plan-mode handshake for interactive review skills Add a preamble-level STOP-Ask handshake that fires when the user invokes any of the 4 interactive review skills (plan-ceo-review, plan-eng-review, plan-design-review, plan-devex-review) while their Claude Code session is in plan mode. Without this gate, plan mode's "this supercedes any other instructions" system-reminder outranked the skills' interactive STOP gates and the skills silently wrote plan files without any per-finding AskUserQuestion. The handshake offers 2 options (exit-and-rerun, cancel) — the original third "stay and batch" option was dropped after two independent reviewers flagged it as a silent bypass of the skills' anti-skip rule. Architecture decisions (CEO+Eng review): - Preamble-level resolver, not per-template injection (Codex finding #2) - Position 1 in preamble composition: after bash block (_SESSION_ID live), before onboarding AskUserQuestion gates (so fresh-install users see the handshake first, not drowned in telemetry/proactive/routing prompts) - Generator-only `interactive: true` frontmatter flag, following the `preamble-tier` precedent (no host-config frontmatter allowlist edits) - Host-scoped to Claude via `ctx.host === 'claude'` check inside the resolver (simpler than `suppressedResolvers` which only gates `{{}}` placeholders) - One-way-door classification in scripts/question-registry.ts for all 4 skills so question-tuning `never-ask` preferences can't suppress the gate - Synchronous telemetry write to ~/.gstack/analytics/skill-usage.jsonl on handshake fire (captures A-exit and C-cancel outcomes that terminate the skill before end-of-run telemetry runs) Also adds an explicit STOP block to plan-ceo-review Step 0C-bis so the approach-selection question can't silently skip to mode selection. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat: extend agent-sdk-runner with canUseTool for AskUserQuestion interception Test harness at test/helpers/agent-sdk-runner.ts gains an optional `canUseTool` callback parameter. When a test supplies it, the harness flips `permissionMode` from `bypassPermissions` (overlay-harness default) to `default` so the SDK actually invokes the callback on every tool use, and auto-adds `AskUserQuestion` to `allowedTools` so Claude can fire it at all. Exports a `passThroughNonAskUserQuestion` helper so tests that only want to intercept AskUserQuestion can auto-allow every other tool with one line: `return passThroughNonAskUserQuestion(toolName, input)`. This is the foundation for D14 — every future interactive-skill E2E test can now assert on AskUserQuestion shape and routing. Previous E2E tests at `test/skill-e2e.test.ts` explicitly instructed the model to skip AskUserQuestion ("non-interactive run") which meant no test could actually verify the question content or routing. 6 new unit tests in test/agent-sdk-runner.test.ts cover: - permissionMode flips to 'default' when canUseTool supplied - permissionMode stays 'bypassPermissions' when canUseTool absent - canUseTool callback reaches the SDK options - AskUserQuestion auto-added to allowedTools when canUseTool supplied - AskUserQuestion NOT added when canUseTool absent - passThroughNonAskUserQuestion helper returns allow+updatedInput Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * test: plan-mode handshake E2E coverage and unit assertions Adds 6 E2E test files and 8 new unit assertions to verify the plan-mode handshake works end-to-end and stays correct under regeneration. E2E tests (gate-tier, paid, EVALS=1 EVALS_TIER=gate): - test/skill-e2e-plan-ceo-plan-mode.test.ts — handshake fires before any Write/Edit when plan-mode distinctive phrase is present; 2-option shape (Exit/Cancel); option A routes to ExitPlanMode cleanly - test/skill-e2e-plan-eng-plan-mode.test.ts — same contract for plan-eng - test/skill-e2e-plan-design-plan-mode.test.ts — same contract for plan-design; exercises C-cancel branch instead of A-exit - test/skill-e2e-plan-devex-plan-mode.test.ts — same contract for plan-devex - test/skill-e2e-plan-mode-no-op.test.ts — negative regression: handshake must NOT fire when distinctive phrase is absent; skill proceeds normally through Step 0 (REGRESSION RULE guardrail against breaking existing interactive-review sessions) - test/e2e-harness-audit.test.ts — free unit test asserting every `interactive: true` skill has at least one canUseTool-using test file (prevents future drift where a skill opts in without coverage) Shared helper test/helpers/plan-mode-handshake-helpers.ts centralizes the canUseTool interceptor + distinctive-phrase injection so the 4 sibling E2E tests are thin wiring (~20 LOC each) and can't drift out of sync. Unit assertions added to test/gen-skill-docs.test.ts: - handshake section present in all 4 Claude-generated SKILL.md files - handshake section absent from non-interactive Claude skills (ship, review, qa, office-hours, codex, retro, cso) - handshake section absent from non-Claude host outputs (.agents, etc.) - 0C-bis STOP block present in plan-ceo-review/SKILL.md at correct position (between the "Present these approach options" line and "### 0D-prelude" header) - handshake resolver wired BEFORE generateUpgradeCheck in preamble composition order 6 new gate-tier entries added to test/helpers/touchfiles.ts so any change to the handshake resolver, preamble composition, skill templates, question registry, one-way-door classifier, or agent-sdk-runner fires the relevant E2E tests. test/touchfiles.test.ts updated for the new selection count (plan-ceo-review/** now triggers 15 tests, up from 8). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * chore(v1.11.1.0): VERSION bump + CHANGELOG entry + TODOS follow-ups Bumps from main's v1.11.0.0 to v1.11.1.0 (PATCH — bug-fix release, no new user-facing artifacts). CHANGELOG entry covers the plan-mode handshake, agent-sdk-runner canUseTool extension, and the 2 follow-up TODOs. CHANGELOG order: v1.11.1.0 (this) → v1.11.0.0 (workspace-aware ship, merged from main) → v1.10.1.0 (overlay efficacy harness). No duplicate headers. Syncs package.json version to match VERSION per the Step 12 idempotency invariant (both files must agree or /ship halts). TODOS.md: - Preserves the Testing/security-bench-haiku-responses P1 added on main - Adds P1 "Structural STOP-Ask forcing function" — broader class of the bug this release fixes - Adds P2 "Apply interactive: true to non-review skills (office-hours, codex, investigate, qa, retro, cso)" Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
685 lines
25 KiB
TypeScript
685 lines
25 KiB
TypeScript
/**
|
|
* 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-mode handshake — fires at the top of any interactive review skill
|
|
// when the user is in plan mode. Safety-critical, always asked regardless
|
|
// of user's tuning preferences. See scripts/resolvers/preamble/generate-
|
|
// plan-mode-handshake.ts.
|
|
// -----------------------------------------------------------------------
|
|
'plan-ceo-review-plan-mode-handshake': {
|
|
id: 'plan-ceo-review-plan-mode-handshake',
|
|
skill: 'plan-ceo-review',
|
|
category: 'routing',
|
|
door_type: 'one-way',
|
|
options: ['exit-and-rerun', 'cancel'],
|
|
description: "Plan mode detected — exit and rerun interactively, or cancel?",
|
|
},
|
|
'plan-eng-review-plan-mode-handshake': {
|
|
id: 'plan-eng-review-plan-mode-handshake',
|
|
skill: 'plan-eng-review',
|
|
category: 'routing',
|
|
door_type: 'one-way',
|
|
options: ['exit-and-rerun', 'cancel'],
|
|
description: "Plan mode detected — exit and rerun interactively, or cancel?",
|
|
},
|
|
'plan-design-review-plan-mode-handshake': {
|
|
id: 'plan-design-review-plan-mode-handshake',
|
|
skill: 'plan-design-review',
|
|
category: 'routing',
|
|
door_type: 'one-way',
|
|
options: ['exit-and-rerun', 'cancel'],
|
|
description: "Plan mode detected — exit and rerun interactively, or cancel?",
|
|
},
|
|
'plan-devex-review-plan-mode-handshake': {
|
|
id: 'plan-devex-review-plan-mode-handshake',
|
|
skill: 'plan-devex-review',
|
|
category: 'routing',
|
|
door_type: 'one-way',
|
|
options: ['exit-and-rerun', 'cancel'],
|
|
description: "Plan mode detected — exit and rerun interactively, or cancel?",
|
|
},
|
|
|
|
// -----------------------------------------------------------------------
|
|
// /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<string, QuestionDef>;
|
|
|
|
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<string, QuestionDef>)[id];
|
|
}
|
|
|
|
/** Get all registered one-way door question ids (used by sensitivity checker) */
|
|
export function getOneWayDoorIds(): Set<string> {
|
|
return new Set(
|
|
Object.values(QUESTIONS as Record<string, QuestionDef>)
|
|
.filter((q) => q.door_type === 'one-way')
|
|
.map((q) => q.id),
|
|
);
|
|
}
|
|
|
|
/** All registered question ids, for CI completeness checks */
|
|
export function getAllRegisteredIds(): Set<string> {
|
|
return new Set(Object.keys(QUESTIONS));
|
|
}
|
|
|
|
/** Registry stats, for /plan-tune stats */
|
|
export function getRegistryStats() {
|
|
const all = Object.values(QUESTIONS as Record<string, QuestionDef>);
|
|
const bySkill: Record<string, number> = {};
|
|
const byCategory: Record<string, number> = {};
|
|
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,
|
|
};
|
|
}
|