Skip to content

feat: split the wizard into a seperate package#368

Merged
calvinbrewer merged 1 commit intomainfrom
wizard-package
Apr 30, 2026
Merged

feat: split the wizard into a seperate package#368
calvinbrewer merged 1 commit intomainfrom
wizard-package

Conversation

@calvinbrewer
Copy link
Copy Markdown
Contributor

@calvinbrewer calvinbrewer commented Apr 30, 2026

Summary by CodeRabbit

Release Notes

  • New Features

    • AI-guided encryption wizard now available as standalone package via npx @cipherstash/wizard
    • Automatic detection of Drizzle and Supabase configurations during initialization
  • Changes

    • stash init simplified with reduced interactive prompts and auto-configured defaults
    • stash db install now ensures encryption client file is scaffolded when needed
    • schema build command streamlined with direct database introspection flow
    • Updated recommended wizard invocation path in setup workflows

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 30, 2026

🦋 Changeset detected

Latest commit: 5d3eb13

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

This PR includes changesets to release 2 packages
Name Type
@cipherstash/cli Minor
@cipherstash/wizard Minor

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 Apr 30, 2026

📝 Walkthrough

Walkthrough

The pull request extracts the wizard from the CLI into a standalone @cipherstash/wizard package, removes the integrated wizard command and related prompts from CLI, auto-detects Drizzle/Supabase configurations instead of prompting users, deterministically scaffolds encryption clients to ./src/encryption/index.ts, consolidates dependency installation, and bypasses authentication confirmation for existing sessions.

Changes

Cohort / File(s) Summary
Release Notes
.changeset/init-friction-cuts.md, .changeset/wizard-extracted-package.md, .changeset/wizard-initial-release.md
Three Changesets files documenting the extraction of the wizard package and friction reduction in stash init and stash db install through auto-detection and consolidated prompts.
CLI Wizard Removal
packages/cli/src/bin/stash.ts
Removes the wizard command entry from help text and removes the associated command dispatcher case, eliminating built-in wizard support.
CLI Init Workflow Core
packages/cli/src/commands/init/index.ts, packages/cli/src/commands/init/types.ts, packages/cli/src/commands/init/utils.ts
Removes selectConnectionStep from execution flow, eliminates ConnectionMethod type and connectionOptions metadata from providers/state, and adds combinedInstallCommands utility for consolidated package installation.
CLI Init Providers
packages/cli/src/commands/init/providers/base.ts, packages/cli/src/commands/init/providers/drizzle.ts, packages/cli/src/commands/init/providers/supabase.ts
Removes connectionOptions from all providers and updates wizard invocation commands from npx @cipherstash/cli wizard to npx @cipherstash/wizard``.
CLI Init Steps Refactoring
packages/cli/src/commands/init/steps/authenticate.ts, packages/cli/src/commands/init/steps/build-schema.ts, packages/cli/src/commands/init/steps/install-forge.ts, packages/cli/src/commands/init/steps/select-connection.ts
Simplifies authentication (removes workspace confirmation for existing sessions), deterministically scaffolds encryption client at ./src/encryption/index.ts with auto-detection, consolidates dependency install prompts and logic, and deletes the connection-selection step entirely.
CLI DB & Schema Commands
packages/cli/src/commands/db/client-scaffold.ts, packages/cli/src/commands/db/install.ts, packages/cli/src/commands/schema/build.ts
Adds new ensureEncryptionClient utility, invokes it in db install after config load, removes wizard selection prompt from schema build, and updates next-step wizard recommendations.
CLI Package Config
packages/cli/package.json, packages/cli/README.md, packages/cli/tsup.config.ts
Removes @anthropic-ai/claude-agent-sdk dependency, shortens package description, updates all wizard references and quickstart guidance to point to external @cipherstash/wizard package, and removes the post-build skills copy operation.
Wizard Package Creation
packages/wizard/package.json, packages/wizard/README.md, packages/wizard/src/bin/wizard.ts, packages/wizard/src/lib/rewrite-migrations.ts, packages/wizard/src/lib/post-agent.ts, packages/wizard/tsconfig.json, packages/wizard/tsup.config.ts
Establishes new standalone @cipherstash/wizard package with ESM CLI binary entry, help/version/debug flag handling, environment loading, migration rewrite utility for encrypted ALTER statements, and TypeScript/build configuration.
Skills Documentation
skills/stash-cli/SKILL.md
Updates guidance to remove bundled wizard workflow, consolidates CLI commands under stash prefix, adds db install flow description with Supabase submodes, references the separate @cipherstash/wizard package, and removes references to deprecated programmatic helpers (loadEncryptConfig, validateEncryptConfig).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CLI as `@cipherstash/cli`
    participant Config as Config Detection
    participant Scaffold as Client Scaffolding
    participant Wizard as `@cipherstash/wizard`

    rect rgb(230, 245, 255, 0.5)
    Note over User,Wizard: New Init Flow (Auto-Detection)
    User->>CLI: npx `@cipherstash/cli` init
    CLI->>Config: detectDrizzle(cwd)
    Config-->>CLI: drizzle config found
    CLI->>Scaffold: ensureEncryptionClient<br/>(./src/encryption/index.ts)
    Scaffold->>Scaffold: generatePlaceholderClient<br/>(detected integration)
    Scaffold-->>CLI: client scaffolded
    CLI->>User: Init complete, next steps...
    User->>User: reads "npx `@cipherstash/wizard`"
    end

    rect rgb(240, 255, 240, 0.5)
    Note over User,Wizard: Standalone Wizard Invocation
    User->>Wizard: npx `@cipherstash/wizard`
    Wizard->>Wizard: scanCodebase()
    Wizard->>Wizard: promptColumns()
    Wizard->>Wizard: Claude Agent SDK interaction<br/>(CipherStash LLM Gateway)
    Wizard->>Wizard: rewriteMigrations()
    Wizard->>Wizard: installDependencies()
    Wizard->>User: Setup complete
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • coderdan
  • auxesis

🐰 Hops with glee through the CLI refactor trail,
Auto-detect and scaffold without fail!
The wizard now stands tall and free,
A package for all the world to see.
Less friction, more magic—hip hip hooray!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% 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 pull request title 'feat: split the wizard into a separate package' accurately summarizes the main change: extracting the wizard functionality from the CLI into a standalone @cipherstash/wizard package.
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-package

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
Review rate limit: 0/1 reviews remaining, refill in 60 minutes.

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: 6

🧹 Nitpick comments (1)
packages/cli/src/commands/db/client-scaffold.ts (1)

8-20: ⚡ Quick win

Extract detectIntegration instead of maintaining two copies.

This helper now duplicates the same logic from packages/cli/src/commands/init/steps/build-schema.ts:22-29. These paths are supposed to scaffold the same template, so any future tweak risks them drifting apart. Moving the detection into a shared helper keeps init and db install aligned.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/commands/db/client-scaffold.ts` around lines 8 - 20, Extract
the duplicated logic currently in detectIntegration (which uses
detectDrizzle(cwd) and detectSupabase(databaseUrl) and returns
'drizzle'|'supabase'|'postgresql') into a shared helper module and have both
callsites import and use that single function; specifically, move the
detectIntegration implementation into a shared util (preserve its signature:
(cwd: string, databaseUrl?: string): Integration), replace the local copies in
both the db client-scaffold and the init build-schema code to import the helper,
and ensure the helper re-uses the existing detectDrizzle and detectSupabase
symbols so behavior remains identical.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/cli/README.md`:
- Around line 31-38: The fenced code block in the README's flow-diagram is
unlabeled and triggers MD040; update the opening fence for that diagram (the
triple-backtick block containing the npx flow) to include the language label
"text" so it reads ```text, leaving the block contents unchanged, to satisfy the
markdown linter (look for the flow-diagram block in packages/cli/README.md).

In `@packages/cli/src/commands/db/install.ts`:
- Around line 90-93: The install command currently calls
ensureEncryptionClient(config.client, process.cwd(), config.databaseUrl)
unconditionally which causes file scaffolding even during a dry run; update the
db install handler to detect the dry-run flag (e.g., options.dryRun or similar
CLI flag) and skip calling ensureEncryptionClient when dry-run is true (or
alternatively pass a dryRun parameter into ensureEncryptionClient and make it a
no-op in that mode) so that no files are created/modified during --dry-run.

In `@packages/cli/src/commands/init/steps/build-schema.ts`:
- Around line 34-37: The run function currently sets integration via
detectIntegration(cwd, process.env.DATABASE_URL) and ignores any explicit init
flags; update run to prefer an explicit provider passed in (from the
InitProvider parameter or a provider field on InitState) before falling back to
detectIntegration: check InitProvider (or state.provider) first and set
clientFilePath/integration from that when present, and only call
detectIntegration as a fallback; adjust usages of DEFAULT_CLIENT_PATH and any
downstream logic that reads integration to use this selected value so explicit
--supabase/--drizzle flags override auto-detection.

In `@packages/cli/src/commands/init/steps/install-forge.ts`:
- Around line 62-88: The loop currently uses a single allSucceeded flag which
loses per-command success info; change it to track success per package/command
(e.g., stackSucceeded and forgeSucceeded or a successes array keyed to commands)
inside the for loop where execSync is attempted (reference: commands,
allSucceeded, p.log.error). Set the corresponding per-command flag to true on
success and false on catch, then use those flags when building the returned
state so stackInstalled = stackPresent || stackSucceeded and forgeInstalled =
forgePresent || forgeSucceeded; keep the existing overall message logic but
compute allSucceeded as the conjunction of the per-command flags for deciding
the final success message.

In `@packages/wizard/package.json`:
- Around line 14-17: Add a proper exports map to packages/wizard/package.json
and make tsup emit both ESM and CJS: in package.json add an "exports" field that
maps "." to { "import": "./dist/index.js", "require": "./dist/index.cjs" } (and
include an entry for the CLI like " ./bin/wizard": { "import":
"./dist/bin/wizard.js", "require": "./dist/bin/wizard.cjs" } and optionally
"./package.json": "./package.json"), while keeping the existing "bin" and
"type": "module" keys; then update packages/wizard/tsup.config.ts to set format:
['esm','cjs'] so tsup outputs both ./dist/*.js and ./dist/*.cjs artifacts that
satisfy the exports.require paths.

In `@packages/wizard/src/bin/wizard.ts`:
- Around line 7-10: The current calls to config(...) in wizard.ts load .env
files in the wrong order and unconditionally load development files; change the
logic to follow Next.js precedence: determine NODE_ENV (e.g., const env =
process.env.NODE_ENV || 'development'), then if env !== 'test' load
`.env.${env}.local` first, then load `.env.${env}`, then if env !== 'test' load
`.env.local`, and finally load `.env`; only attempt
`.env.development.local`/`.env.development` when env === 'development'. Replace
the four unconditional config(...) calls with conditional config calls using
that order and update the comment to reflect the actual Next.js precedence.

---

Nitpick comments:
In `@packages/cli/src/commands/db/client-scaffold.ts`:
- Around line 8-20: Extract the duplicated logic currently in detectIntegration
(which uses detectDrizzle(cwd) and detectSupabase(databaseUrl) and returns
'drizzle'|'supabase'|'postgresql') into a shared helper module and have both
callsites import and use that single function; specifically, move the
detectIntegration implementation into a shared util (preserve its signature:
(cwd: string, databaseUrl?: string): Integration), replace the local copies in
both the db client-scaffold and the init build-schema code to import the helper,
and ensure the helper re-uses the existing detectDrizzle and detectSupabase
symbols so behavior remains identical.
🪄 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: 63098c7c-a3ab-45b1-8569-276c0b373c69

📥 Commits

Reviewing files that changed from the base of the PR and between 6e85567 and 5d3eb13.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (55)
  • .changeset/init-friction-cuts.md
  • .changeset/wizard-extracted-package.md
  • .changeset/wizard-initial-release.md
  • packages/cli/README.md
  • packages/cli/package.json
  • packages/cli/src/bin/stash.ts
  • packages/cli/src/commands/db/client-scaffold.ts
  • packages/cli/src/commands/db/install.ts
  • packages/cli/src/commands/init/index.ts
  • packages/cli/src/commands/init/providers/base.ts
  • packages/cli/src/commands/init/providers/drizzle.ts
  • packages/cli/src/commands/init/providers/supabase.ts
  • packages/cli/src/commands/init/steps/authenticate.ts
  • packages/cli/src/commands/init/steps/build-schema.ts
  • packages/cli/src/commands/init/steps/install-forge.ts
  • packages/cli/src/commands/init/steps/select-connection.ts
  • packages/cli/src/commands/init/types.ts
  • packages/cli/src/commands/init/utils.ts
  • packages/cli/src/commands/schema/build.ts
  • packages/cli/tsup.config.ts
  • packages/wizard/README.md
  • packages/wizard/package.json
  • packages/wizard/src/__tests__/agent-sdk.test.ts
  • packages/wizard/src/__tests__/commandments.test.ts
  • packages/wizard/src/__tests__/detect.test.ts
  • packages/wizard/src/__tests__/format.test.ts
  • packages/wizard/src/__tests__/gateway-messages.test.ts
  • packages/wizard/src/__tests__/health-checks.test.ts
  • packages/wizard/src/__tests__/hooks.test.ts
  • packages/wizard/src/__tests__/interface.test.ts
  • packages/wizard/src/__tests__/wizard-tools.test.ts
  • packages/wizard/src/agent/commandments.ts
  • packages/wizard/src/agent/errors.ts
  • packages/wizard/src/agent/fetch-prompt.ts
  • packages/wizard/src/agent/hooks.ts
  • packages/wizard/src/agent/interface.ts
  • packages/wizard/src/bin/wizard.ts
  • packages/wizard/src/health-checks/index.ts
  • packages/wizard/src/lib/analytics.ts
  • packages/wizard/src/lib/changelog.ts
  • packages/wizard/src/lib/constants.ts
  • packages/wizard/src/lib/detect.ts
  • packages/wizard/src/lib/format.ts
  • packages/wizard/src/lib/gather.ts
  • packages/wizard/src/lib/install-skills.ts
  • packages/wizard/src/lib/post-agent.ts
  • packages/wizard/src/lib/prerequisites.ts
  • packages/wizard/src/lib/rewrite-migrations.ts
  • packages/wizard/src/lib/types.ts
  • packages/wizard/src/lib/wire-call-sites.ts
  • packages/wizard/src/run.ts
  • packages/wizard/src/tools/wizard-tools.ts
  • packages/wizard/tsconfig.json
  • packages/wizard/tsup.config.ts
  • skills/stash-cli/SKILL.md
💤 Files with no reviewable changes (6)
  • packages/cli/tsup.config.ts
  • packages/cli/src/commands/init/steps/select-connection.ts
  • packages/cli/src/bin/stash.ts
  • packages/cli/src/commands/init/index.ts
  • packages/cli/src/commands/schema/build.ts
  • packages/cli/src/commands/init/types.ts

Comment thread packages/cli/README.md
Comment on lines 31 to 38
```
npx @cipherstash/cli init
└── npx @cipherstash/cli db install
└── npx @cipherstash/cli wizard ← fast path: AI edits your files
OR
Edit schema files by hand ← escape hatch
npx @cipherstash/cli auth login
└── npx @cipherstash/cli init
└── npx @cipherstash/cli db install
└── npx @cipherstash/wizard ← fast path: AI edits your files
OR
Edit schema files by hand ← escape hatch
```
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

Add a language to the flow-diagram fence.

The unlabeled fence on Line 31 triggers MD040. Mark it as text so docs lint stays clean.

Suggested fix
-```
+```text
 npx `@cipherstash/cli` auth login
     └── npx `@cipherstash/cli` init
             └── npx `@cipherstash/cli` db install
                     └── npx `@cipherstash/wizard`       ← fast path: AI edits your files
                             OR
                         Edit schema files by hand     ← escape hatch
</details>

<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

```suggestion

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 31-31: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/README.md` around lines 31 - 38, The fenced code block in the
README's flow-diagram is unlabeled and triggers MD040; update the opening fence
for that diagram (the triple-backtick block containing the npx flow) to include
the language label "text" so it reads ```text, leaving the block contents
unchanged, to satisfy the markdown linter (look for the flow-diagram block in
packages/cli/README.md).

Comment on lines +90 to +93
// Safety net: if the user ran `db install` without first running `init`,
// scaffold the encryption client file so config.client points somewhere
// real. No-op when the file already exists.
ensureEncryptionClient(config.client, process.cwd(), config.databaseUrl)
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 | 🟠 Major | ⚡ Quick win

--dry-run path still performs file scaffolding.

Line 93 runs ensureEncryptionClient(...) unconditionally, so db install --dry-run can still create/update the client file before dry-run exits. That breaks dry-run semantics.

Proposed fix
-  ensureEncryptionClient(config.client, process.cwd(), config.databaseUrl)
+  if (!options.dryRun) {
+    ensureEncryptionClient(config.client, process.cwd(), config.databaseUrl)
+  }
📝 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
// Safety net: if the user ran `db install` without first running `init`,
// scaffold the encryption client file so config.client points somewhere
// real. No-op when the file already exists.
ensureEncryptionClient(config.client, process.cwd(), config.databaseUrl)
// Safety net: if the user ran `db install` without first running `init`,
// scaffold the encryption client file so config.client points somewhere
// real. No-op when the file already exists.
if (!options.dryRun) {
ensureEncryptionClient(config.client, process.cwd(), config.databaseUrl)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/commands/db/install.ts` around lines 90 - 93, The install
command currently calls ensureEncryptionClient(config.client, process.cwd(),
config.databaseUrl) unconditionally which causes file scaffolding even during a
dry run; update the db install handler to detect the dry-run flag (e.g.,
options.dryRun or similar CLI flag) and skip calling ensureEncryptionClient when
dry-run is true (or alternatively pass a dryRun parameter into
ensureEncryptionClient and make it a no-op in that mode) so that no files are
created/modified during --dry-run.

Comment on lines 34 to +37
async run(state: InitState, _provider: InitProvider): Promise<InitState> {
if (!state.connectionMethod) {
p.log.warn('Skipping schema generation (no connection method selected)')
return { ...state, schemaGenerated: false }
}

const integration = toIntegration(state.connectionMethod)

const clientFilePath = await p.text({
message: 'Where should we create your encryption client?',
placeholder: DEFAULT_CLIENT_PATH,
defaultValue: DEFAULT_CLIENT_PATH,
})

if (p.isCancel(clientFilePath)) throw new CancelledError()

const resolvedPath = resolve(process.cwd(), clientFilePath)
const cwd = process.cwd()
const integration = detectIntegration(cwd, process.env.DATABASE_URL)
const clientFilePath = DEFAULT_CLIENT_PATH
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 | 🟠 Major | ⚡ Quick win

Don't let auto-detection override explicit init flags.

Lines 35-36 now choose the template solely from local detection and ignore the caller's explicit choice. A stash init --supabase / --drizzle run before DATABASE_URL or project markers exist will fall back to postgresql and scaffold the wrong client. Prefer an explicit provider/flag when one was supplied, and use detection only as the fallback.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/commands/init/steps/build-schema.ts` around lines 34 - 37,
The run function currently sets integration via detectIntegration(cwd,
process.env.DATABASE_URL) and ignores any explicit init flags; update run to
prefer an explicit provider passed in (from the InitProvider parameter or a
provider field on InitState) before falling back to detectIntegration: check
InitProvider (or state.provider) first and set clientFilePath/integration from
that when present, and only call detectIntegration as a fallback; adjust usages
of DEFAULT_CLIENT_PATH and any downstream logic that reads integration to use
this selected value so explicit --supabase/--drizzle flags override
auto-detection.

Comment on lines +62 to +88
let allSucceeded = true
for (const cmd of commands) {
p.log.step(`Running: ${cmd}`)
try {
execSync(cmd, { cwd: process.cwd(), stdio: 'inherit' })
} catch (err) {
const message = err instanceof Error ? err.message : String(err)
p.log.error(`Install failed: ${cmd}`)
p.log.error(message)
allSucceeded = false
}
}

// Install @cipherstash/cli as a dev dependency
const forgeInstalled = await installIfNeeded(
FORGE_PACKAGE,
devInstallCommand,
'dev',
)
if (allSucceeded) {
p.log.success('Stack dependencies installed.')
} else {
p.note(
`You can retry manually:\n ${commands.join('\n ')}`,
'Manual Installation',
)
}

return { ...state, forgeInstalled, stackInstalled }
return {
...state,
stackInstalled: stackPresent || allSucceeded,
forgeInstalled: forgePresent || allSucceeded,
}
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

Preserve partial install success in the returned state.

allSucceeded collapses both install commands into a single boolean. If both packages are missing and one command succeeds before the other fails, Lines 86-87 report both packages as not installed, which can misdrive later init steps and the final messaging. Track success per package/command instead of with one shared flag.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/commands/init/steps/install-forge.ts` around lines 62 - 88,
The loop currently uses a single allSucceeded flag which loses per-command
success info; change it to track success per package/command (e.g.,
stackSucceeded and forgeSucceeded or a successes array keyed to commands) inside
the for loop where execSync is attempted (reference: commands, allSucceeded,
p.log.error). Set the corresponding per-command flag to true on success and
false on catch, then use those flags when building the returned state so
stackInstalled = stackPresent || stackSucceeded and forgeInstalled =
forgePresent || forgeSucceeded; keep the existing overall message logic but
compute allSucceeded as the conjunction of the per-command flags for deciding
the final success message.

Comment on lines +14 to +17
"type": "module",
"bin": {
"stash-wizard": "./dist/bin/wizard.js"
},
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 | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
python - <<'PY'
import json, pathlib
pkg = json.loads(pathlib.Path("packages/wizard/package.json").read_text())
print("package exports:", pkg.get("exports"))
print("package type:", pkg.get("type"))
print("package bin:", pkg.get("bin"))
PY

echo
echo "tsup output formats:"
rg -n "format:|entry:|outDir:" packages/wizard/tsup.config.ts

Repository: cipherstash/stack

Length of output: 259


🏁 Script executed:

find packages -maxdepth 2 -name "package.json" -type f | head -10

Repository: cipherstash/stack

Length of output: 301


🏁 Script executed:

# Check a few other packages to see dual export pattern
for pkg in $(find packages -maxdepth 2 -name "package.json" -type f | head -5); do
  echo "=== $pkg ==="
  python3 - <<PYEOF
import json
try:
    with open("$pkg") as f:
        data = json.load(f)
        print("exports:", json.dumps(data.get("exports"), indent=2)[:200])
        print("type:", data.get("type"))
except:
    print("(could not parse)")
PYEOF
done

Repository: cipherstash/stack

Length of output: 1216


Add exports map with dual ESM/CJS support.

Lines 14–17 define only a bin-only ESM surface. The package currently has no exports map and packages/wizard/tsup.config.ts emits only format: ['esm']. This violates the repository guideline: each packages/*/package.json must declare an exports map with both import and require fields. Add the exports map with corresponding .js and .cjs entry points, and configure tsup to emit both ESM and CJS artifacts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/wizard/package.json` around lines 14 - 17, Add a proper exports map
to packages/wizard/package.json and make tsup emit both ESM and CJS: in
package.json add an "exports" field that maps "." to { "import":
"./dist/index.js", "require": "./dist/index.cjs" } (and include an entry for the
CLI like " ./bin/wizard": { "import": "./dist/bin/wizard.js", "require":
"./dist/bin/wizard.cjs" } and optionally "./package.json": "./package.json"),
while keeping the existing "bin" and "type": "module" keys; then update
packages/wizard/tsup.config.ts to set format: ['esm','cjs'] so tsup outputs both
./dist/*.js and ./dist/*.cjs artifacts that satisfy the exports.require paths.

Comment on lines +7 to +10
config({ path: '.env.local' })
config({ path: '.env.development.local' })
config({ path: '.env.development' })
config({ path: '.env' })
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 | 🟠 Major

🧩 Analysis chain

🌐 Web query:

What is the official Next.js environment variable load order for .env files (including NODE_ENV-specific and .local variants)?

💡 Result:

Official Next.js environment variable (.env) load order (first match wins) is: 1) process.env 2) .env.$(NODE_ENV).local 3) .env.local (not checked when NODE_ENV is test) 4) .env.$(NODE_ENV) 5) .env Notes: - For test environment, Next.js will not load .env.local. - Allowed NODE_ENV values are production, development, and test. - If NODE_ENV is unassigned, Next.js sets it to development for next dev, and to production for other commands.

Citations:


🏁 Script executed:

# Check if the file exists and read the relevant section
if [ -f "packages/wizard/src/bin/wizard.ts" ]; then
  echo "=== File found, reading lines 1-20 ==="
  head -20 "packages/wizard/src/bin/wizard.ts" | cat -n
else
  echo "File not found at packages/wizard/src/bin/wizard.ts"
fi

Repository: cipherstash/stack

Length of output: 1025


🏁 Script executed:

# Search for any logging calls (plaintext logging) in the wizard.ts file
rg "console\.|log\(|logger\." packages/wizard/src/bin/wizard.ts -A 2 -B 2 || echo "No logging found"

Repository: cipherstash/stack

Length of output: 175


🏁 Script executed:

# Check if NODE_ENV is referenced anywhere in the file
rg "NODE_ENV" packages/wizard/src/bin/wizard.ts || echo "NODE_ENV not referenced"

Repository: cipherstash/stack

Length of output: 85


🏁 Script executed:

# Also check the full file to understand context
wc -l packages/wizard/src/bin/wizard.ts && echo "---" && cat -n packages/wizard/src/bin/wizard.ts

Repository: cipherstash/stack

Length of output: 2611


Env file precedence/order does not match the stated Next.js behavior.

Lines 7-10 load .env.local before environment-specific files and unconditionally load .env.development* files. This contradicts the official Next.js load order, which prioritizes .env.$(NODE_ENV).local, skips .env.local when NODE_ENV is test, and only loads development files when NODE_ENV is actually "development". The hardcoded development file loading will select incorrect values in production and test environments.

The code comment claiming "Next.js precedence order" is inaccurate.

💡 Proposed fix
-config({ path: '.env.local' })
-config({ path: '.env.development.local' })
-config({ path: '.env.development' })
+const nodeEnv = process.env.NODE_ENV ?? 'development'
+config({ path: `.env.${nodeEnv}.local` })
+if (nodeEnv !== 'test') config({ path: '.env.local' })
+config({ path: `.env.${nodeEnv}` })
 config({ path: '.env' })
📝 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
config({ path: '.env.local' })
config({ path: '.env.development.local' })
config({ path: '.env.development' })
config({ path: '.env' })
const nodeEnv = process.env.NODE_ENV ?? 'development'
config({ path: `.env.${nodeEnv}.local` })
if (nodeEnv !== 'test') config({ path: '.env.local' })
config({ path: `.env.${nodeEnv}` })
config({ path: '.env' })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/wizard/src/bin/wizard.ts` around lines 7 - 10, The current calls to
config(...) in wizard.ts load .env files in the wrong order and unconditionally
load development files; change the logic to follow Next.js precedence: determine
NODE_ENV (e.g., const env = process.env.NODE_ENV || 'development'), then if env
!== 'test' load `.env.${env}.local` first, then load `.env.${env}`, then if env
!== 'test' load `.env.local`, and finally load `.env`; only attempt
`.env.development.local`/`.env.development` when env === 'development'. Replace
the four unconditional config(...) calls with conditional config calls using
that order and update the comment to reflect the actual Next.js precedence.

@calvinbrewer calvinbrewer merged commit eaea010 into main Apr 30, 2026
6 checks passed
@calvinbrewer calvinbrewer deleted the wizard-package branch April 30, 2026 00:40
@auxesis
Copy link
Copy Markdown
Contributor

auxesis commented Apr 30, 2026

@calvinbrewer will follow up on the Coderabbit-identified issues in a separate PR, as pretty much all of them are existing issues from before this PR.

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.

2 participants