Skip to content

feat(cli, wizard): support plan mode in wizard#435

Open
calvinbrewer wants to merge 1 commit intomainfrom
wizard-plan
Open

feat(cli, wizard): support plan mode in wizard#435
calvinbrewer wants to merge 1 commit intomainfrom
wizard-plan

Conversation

@calvinbrewer
Copy link
Copy Markdown
Contributor

@calvinbrewer calvinbrewer commented May 6, 2026

Summary by CodeRabbit

  • New Features

    • Added --plan / --implement / --mode <plan|implement> CLI flags to the wizard for toggling between planning and implementation workflows.
    • Plan mode generates a .cipherstash/plan.md deliverable and skips column selection and post-implementation steps.
  • Improvements

    • Expanded handoff picker to offer all target options consistently.
    • Updated resume hints to suggest correct command (stash plan vs stash impl).
  • Documentation

    • Updated SKILL.md to document the new stash init → stash plan → stash impl → stash status lifecycle workflow.

@calvinbrewer calvinbrewer requested a review from a team as a code owner May 6, 2026 22:29
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 6, 2026

🦋 Changeset detected

Latest commit: 1a97d40

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@cipherstash/wizard Minor
stash Minor
@cipherstash/e2e Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 6, 2026

📝 Walkthrough

Walkthrough

This PR introduces plan-mode support to the Stash wizard CLI. The wizard now accepts a --mode <plan|implement> flag (defaulting to implement). In plan mode, column selection is skipped, the mode is forwarded to the gateway API, and post-agent workflow steps (install, push, migrate, call-site scanning) are bypassed. Implement mode preserves existing behavior. The CLI handoff picker for stash plan now exposes all four targets (Claude Code, Codex, AGENTS.md, CipherStash Agent).

Changes

Plan Mode Support

Layer / File(s) Summary
Type Definitions
packages/wizard/src/lib/types.ts
New WizardMode type added ('plan' | 'implement') and RunPhase reformatted for documentation.
CLI Argument Parsing
packages/wizard/src/bin/parse-args.ts, packages/wizard/src/__tests__/parse-args.test.ts
New pure parser module exports parseArgs(argv) supporting --plan, --implement, --mode <plan|implement> (with both space and = forms) and error handling for invalid modes. Last mode flag wins. Comprehensive tests cover mode resolution, shortcuts, error cases, and flag interactions.
Wizard Binary Integration
packages/wizard/src/bin/wizard.ts
Refactored environment loading into loadDotenv() function called only after argument validation. Integrated parseArgs() for mode handling. Extended help text with --plan, --implement, and --mode documentation. Added explicit modeError handling with exit code 1. Pass mode to run().
API Options Refactoring
packages/wizard/src/agent/fetch-prompt.ts, packages/wizard/src/lib/gather.ts
fetchIntegrationPrompt() and gatherContext() now accept options objects (FetchIntegrationPromptOptions, GatherContextOptions) with optional mode field (defaults to 'implement'). New types exported; signatures updated. Enables cleaner caller code.
Column Selection Logic
packages/wizard/src/lib/gather.ts
In plan mode, selectedColumns is set to empty array (TUI skipped). Implement mode retains existing DB/user selection flow. Schema discovery still runs in both modes. Added fileContainsPgTable() probe helper to scan only first 8KB of candidate files before full read, reducing large-project scan cost.
Agent Gateway Integration
packages/wizard/src/agent/fetch-prompt.ts
Mode is now included in gateway request payload to /v1/wizard/prompt endpoint. Gateway returns mode-specific prompt; plan mode expects .cipherstash/plan.md deliverable.
Execution Flow Branching
packages/wizard/src/run.ts
Major refactor: derives mode from options and branches post-agent behavior. Plan mode: records plan output and finalizes (skipping post-agent install/push/migrate and call-site scanning). Implement mode: starts call-site scan concurrently, runs post-agent steps, awaits scan result, then finalizes. Extracted shared finalize() helper for skills install, telemetry, changelog flush, and outro to eliminate duplication.
CLI Handoff Wiring
packages/cli/src/commands/impl/steps/handoff-wizard.ts, packages/cli/src/commands/impl/steps/how-to-proceed.ts
handoffWizardStep now derives mode from state.mode and passes --mode flag to wizard spawn. Resume hint varies: stash plan for plan mode, stash impl for implement mode. how-to-proceed.ts unified buildOptions() to always return all four targets (Claude Code, Codex, AGENTS.md, Wizard) regardless of mode; removed conditional mode-based option suppression. defaultChoice simplified to always default to agents-md when CLI tools missing, ignoring mode.
Tests & Documentation
packages/cli/src/commands/impl/__tests__/how-to-proceed.test.ts, skills/stash-cli/SKILL.md
Updated test expectations for new option ordering and unified default behavior. Extended SKILL.md with stash plan / stash impl / stash init lifecycle documentation, handoff targets, non-TTY behavior branching, and schema integration guidance.
Code Formatting & Minor Refactors
packages/wizard/src/agent/hooks.ts, packages/wizard/src/agent/interface.ts, packages/wizard/src/lib/detect.ts, packages/wizard/src/lib/format.ts, packages/wizard/src/lib/prerequisites.ts, packages/wizard/src/agent/__tests__/interface.test.ts
Expanded multi-line object formatting for DANGEROUS_BASH_OPERATORS, BLOCKED_BASH_PATTERNS, PROMPT_INJECTION_PATTERNS, SECRET_PATTERNS, and PACKAGE_MANAGERS constants. Reorganized imports. Reformatted assertions and return expressions. No functional behavior changes.
Metadata
.changeset/wizard-plan-mode.md
Changeset documenting minor version bump for @cipherstash/wizard and stash, summarizing plan-mode feature and behavior differences.

Sequence Diagram

sequenceDiagram
  participant User
  participant CLI as stash CLI
  participant WizardBin as wizard binary
  participant Gather as gatherContext()
  participant Agent as Agent / LLM
  participant Gateway as Gateway API
  participant Finalize as finalize()

  User->>CLI: stash plan (or stash impl)
  CLI->>CLI: Determine mode: 'plan' or 'implement'
  CLI->>WizardBin: Spawn with --mode <plan|implement>

  WizardBin->>WizardBin: parseArgs() → mode
  WizardBin->>Gather: gatherContext({..., mode})

  alt plan mode
    Gather->>Gather: Skip column selection (empty list)
    Gather-->>WizardBin: GatheredContext
  else implement mode
    Gather->>Gather: Prompt & select columns
    Gather-->>WizardBin: GatheredContext
  end

  WizardBin->>Agent: Initialize agent
  WizardBin->>Gateway: fetchIntegrationPrompt({..., mode})
  Gateway-->>WizardBin: Prompt (plan-specific or impl-specific)

  WizardBin->>Agent: Execute agent with prompt

  alt plan mode
    Agent-->>WizardBin: Agent result
    WizardBin->>WizardBin: Write .cipherstash/plan.md
    WizardBin->>Finalize: finalize()
  else implement mode
    WizardBin->>WizardBin: Start call-site scan (background)
    Agent-->>WizardBin: Agent result
    WizardBin->>WizardBin: Run post-agent steps
    WizardBin->>WizardBin: Await scan result
    WizardBin->>Finalize: finalize()
  end

  Finalize->>Finalize: Install Claude skills
  Finalize->>Finalize: Record completion telemetry
  Finalize->>Finalize: Flush changelog
  Finalize-->>WizardBin: Done
  WizardBin-->>User: Output outro + next steps
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • cipherstash/stack#412: Directly related; both modify plan vs implement mode branching in CLI handoff and how-to-proceed flows.
  • cipherstash/stack#379: Directly related; both refactor fetchIntegrationPrompt() signature and mode parameter passing through agent workflow.
  • cipherstash/stack#368: Directly related; both extend wizard CLI binary and core modules (parse-args, run, gather, types) introduced in that PR.

Suggested reviewers

  • coderdan
  • auxesis
  • tobyhede

🐰 A wizard now walks two paths,
plan or build—choose your way,
Modes aligned with magic mist,
One drafts dreams, one makes the clay.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 55.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding plan mode support to the wizard. It is concise, clear, and specific about the primary feature being introduced.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch wizard-plan

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/wizard/src/lib/gather.ts (1)

9-16: 💤 Low value

Minor: two separate import blocks from node:fs could be merged.

closeSync is imported in its own statement (Line 16) separate from the five other node:fs imports (Lines 9–15). No functional impact, but a single block is conventional.

♻️ Proposed merge
 import {
   existsSync,
   openSync,
   readFileSync,
   readSync,
   readdirSync,
+  closeSync,
 } from 'node:fs'
-import { closeSync } from 'node:fs'
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/wizard/src/lib/gather.ts` around lines 9 - 16, Merge the two
separate node:fs import statements into one by combining existsSync, openSync,
readFileSync, readSync, readdirSync and closeSync into a single import
declaration; locate the imports at the top of the file where existsSync,
openSync, readFileSync, readSync, readdirSync and closeSync are currently
imported separately and consolidate them into one import from 'node:fs' to
follow convention and reduce redundancy.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/wizard/src/bin/parse-args.ts`:
- Around line 36-46: The space-separated --mode branch in parse-args.ts
currently skips setting modeError when --mode is the last argument; update the
if (arg === '--mode' && i + 1 < args.length) branch so that when i + 1 >=
args.length you set modeError to a clear message (e.g., "Missing value for
--mode; expected 'plan' or 'implement'") instead of silently falling through;
keep the existing validation for next === 'plan' || 'implement' and increment i
when you consume the value, and ensure mode remains unchanged when modeError is
set.

---

Nitpick comments:
In `@packages/wizard/src/lib/gather.ts`:
- Around line 9-16: Merge the two separate node:fs import statements into one by
combining existsSync, openSync, readFileSync, readSync, readdirSync and
closeSync into a single import declaration; locate the imports at the top of the
file where existsSync, openSync, readFileSync, readSync, readdirSync and
closeSync are currently imported separately and consolidate them into one import
from 'node:fs' to follow convention and reduce redundancy.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 469af2af-53e6-4dab-b600-716d4dd3bfff

📥 Commits

Reviewing files that changed from the base of the PR and between 1175e86 and 1a97d40.

📒 Files selected for processing (18)
  • .changeset/wizard-plan-mode.md
  • packages/cli/src/commands/impl/__tests__/how-to-proceed.test.ts
  • packages/cli/src/commands/impl/steps/handoff-wizard.ts
  • packages/cli/src/commands/impl/steps/how-to-proceed.ts
  • packages/wizard/src/__tests__/parse-args.test.ts
  • packages/wizard/src/agent/__tests__/interface.test.ts
  • packages/wizard/src/agent/fetch-prompt.ts
  • packages/wizard/src/agent/hooks.ts
  • packages/wizard/src/agent/interface.ts
  • packages/wizard/src/bin/parse-args.ts
  • packages/wizard/src/bin/wizard.ts
  • packages/wizard/src/lib/detect.ts
  • packages/wizard/src/lib/format.ts
  • packages/wizard/src/lib/gather.ts
  • packages/wizard/src/lib/prerequisites.ts
  • packages/wizard/src/lib/types.ts
  • packages/wizard/src/run.ts
  • skills/stash-cli/SKILL.md

Comment on lines +36 to +46
if (arg === '--mode' && i + 1 < args.length) {
const next = args[i + 1] ?? ''
if (next === 'plan' || next === 'implement') {
mode = next
i++
} else {
modeError = `Unknown --mode value: ${next}. Expected 'plan' or 'implement'.`
break
}
continue
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

--mode with no trailing value is silently ignored instead of producing a modeError.

When --mode appears as the final argument, i + 1 < args.length is false and the entire branch is skipped — leaving mode as 'implement' with no diagnostic. The --mode= form correctly surfaces an error for a missing/invalid value; the space-separated form should behave consistently.

🛡️ Proposed fix
-    if (arg === '--mode' && i + 1 < args.length) {
-      const next = args[i + 1] ?? ''
+    if (arg === '--mode') {
+      if (i + 1 >= args.length) {
+        modeError = `--mode requires a value. Expected 'plan' or 'implement'.`
+        break
+      }
+      const next = args[i + 1]
       if (next === 'plan' || next === 'implement') {
         mode = next
         i++
       } else {
         modeError = `Unknown --mode value: ${next}. Expected 'plan' or 'implement'.`
         break
       }
       continue
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (arg === '--mode' && i + 1 < args.length) {
const next = args[i + 1] ?? ''
if (next === 'plan' || next === 'implement') {
mode = next
i++
} else {
modeError = `Unknown --mode value: ${next}. Expected 'plan' or 'implement'.`
break
}
continue
}
if (arg === '--mode') {
if (i + 1 >= args.length) {
modeError = `--mode requires a value. Expected 'plan' or 'implement'.`
break
}
const next = args[i + 1]
if (next === 'plan' || next === 'implement') {
mode = next
i++
} else {
modeError = `Unknown --mode value: ${next}. Expected 'plan' or 'implement'.`
break
}
continue
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/wizard/src/bin/parse-args.ts` around lines 36 - 46, The
space-separated --mode branch in parse-args.ts currently skips setting modeError
when --mode is the last argument; update the if (arg === '--mode' && i + 1 <
args.length) branch so that when i + 1 >= args.length you set modeError to a
clear message (e.g., "Missing value for --mode; expected 'plan' or 'implement'")
instead of silently falling through; keep the existing validation for next ===
'plan' || 'implement' and increment i when you consume the value, and ensure
mode remains unchanged when modeError is set.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant