Skip to content

feat(cli): hand off init to local coding agents (Claude / Codex / AGENTS.md / wizard)#395

Open
coderdan wants to merge 6 commits intodan/stash-wizard-wrapperfrom
dan/init-agent-handoff
Open

feat(cli): hand off init to local coding agents (Claude / Codex / AGENTS.md / wizard)#395
coderdan wants to merge 6 commits intodan/stash-wizard-wrapperfrom
dan/init-agent-handoff

Conversation

@coderdan
Copy link
Copy Markdown
Contributor

@coderdan coderdan commented May 2, 2026

Summary

Stacked on #394.

stash init now runs the whole CipherStash setup as one flow — auth, resolve DATABASE_URL, introspect the database and let the user pick columns, install dependencies, install EQL, then hand off the rest to the user's local coding agent.

A four-option menu at the end picks where to go from there:

  • Hand off to Claude Code — installs .claude/skills/cipherstash-setup/SKILL.md, writes .cipherstash/context.json and .cipherstash/setup-prompt.md, spawns claude interactively. Default when claude is on PATH.
  • Hand off to Codex — writes AGENTS.md (sentinel-upserted), the same context + setup-prompt files, spawns codex interactively. Default when codex is on PATH (and claude isn't).
  • Use the CipherStash Agent — writes .cipherstash/context.json and runs stash wizard. Fallback for users without a local CLI agent.
  • Write AGENTS.md — writes AGENTS.md + .cipherstash/context.json + .cipherstash/setup-prompt.md and stops. For Cursor / Windsurf / Cline / any tool that follows the AGENTS.md convention.

If a chosen CLI agent isn't installed, init still writes the rules files and prints install + manual-launch instructions. Progress is never wasted.

The three artifacts

  • .cipherstash/context.json — pure facts: integration, encryption client path, every selected schema, env-key names (never values), package manager, install command, rulebook + CLI versions, generation timestamp. Written immediately after build-schema (with the bundled rulebook version) so it always tracks the encryption client; handoff steps refresh it with the gateway-served version when reachable. The early baseline means a mid-flow failure leaves context.json consistent with the encryption client, not stale from a previous run.
  • .cipherstash/setup-prompt.md — project-specific action plan generated from init state. "Init has done X and Y; here's exactly what to do next, with these commands and paths." Tailored per integration + package manager (e.g. drizzle-kit / Supabase / Prisma / generic). The launch prompt for Claude / Codex points the agent at this file first.
  • SKILL.md / AGENTS.md — reusable rulebook with the never-do-this list, column-type rules, ORM-specific patterns. Sentinel-marker upserted (<!-- cipherstash:rulebook start/end -->) so re-runs replace only the managed region; user edits outside survive.

Rulebook source

Versioned content lives in a new @cipherstash/rulebook package (see suite PR #506). The CLI ships with the bundled partials and renderers but prefers the gateway-served version when wizard.getstash.sh/v1/wizard/rulebook is reachable — so content updates land between CLI releases. Network failures fall through to the bundled copy silently.

CIPHERSTASH_WIZARD_URL overrides the gateway endpoint for local-dev against a wizard gateway running on localhost.

Init pipeline

authenticate
resolve-database     ← uses resolveDatabaseUrl, hard-fails if no URL resolves
build-schema         ← introspects DB + multi-select; placeholder fallback for empty DBs
install-deps         ← @cipherstash/stack + stash (renamed from install-forge)
install-eql          ← y/N confirm, runs `stash db install` programmatically
gather-context       ← detects agents, logs summary
how-to-proceed       ← four-option menu

Smoke-test status

  • ✅ Hand off to Claude Code (Drizzle integration, Supabase project) — multi-select introspection confirmed; SKILL.md + context.json + setup-prompt.md written; claude spawn launched correctly
  • ⚠️ Hand off to Codex — unit-test only; codex spawn path not yet exercised end-to-end
  • ⚠️ Use the CipherStash Agent — unit-test only; the underlying stash wizard wrapper is exercised by feat(cli): stash wizard thin-wrapper subcommand #394's smoke tests but the init→wizard chain hasn't been run end-to-end
  • ⚠️ Write AGENTS.md — unit-test only; sentinel upsert confirmed by unit tests, full flow not yet exercised end-to-end
  • ✅ Bundled-fallback path — confirmed via /etc/hosts block of wizard.getstash.sh
  • ✅ Re-run safety — confirmed by editing content outside sentinel markers between runs

Test plan for reviewer

  • pnpm --filter stash test (154 tests)
  • pnpm --filter stash build clean
  • Local end-to-end: mkdir demo && cd demo && pnpm init -y && pnpm add drizzle-ormnode /path/to/dist/bin/stash.js init → step through the four options
  • Block wizard.getstash.sh (e.g. /etc/hosts) and confirm the bundled fallback message + valid SKILL.md output

Architectural notes

  • introspectDatabase + selectTableColumns lifted from commands/schema/build.ts into commands/init/lib/introspect.ts so both stash schema build and stash init share one introspection codepath.
  • Same for generateClientFromSchemas — moved into commands/init/utils.ts.
  • packages/cli/src/rulebook/ holds the bundled markdown partials + renderers (core + drizzle / supabase / postgresql).
  • commands/init/lib/sentinel-upsert.ts is the managed-block writer used by both SKILL.md and AGENTS.md.
  • isPackageInstalled now requires both the directory AND a package.json inside it (matches what Node's resolver actually needs to load the module). The earlier "directory exists" check was treating leftover/broken installs as valid.

Related

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 2, 2026

🦋 Changeset detected

Latest commit: dad8a3f

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

This PR includes changesets to release 2 packages
Name Type
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 2, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 35c6bf4f-43e0-48ab-a6de-58038ed16fcf

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dan/init-agent-handoff

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.

@coderdan coderdan force-pushed the dan/stash-wizard-wrapper branch from 4ab8e16 to ce70b4d Compare May 2, 2026 06:16
@coderdan coderdan requested a review from a team as a code owner May 2, 2026 06:16
@coderdan coderdan force-pushed the dan/init-agent-handoff branch from 39e8420 to b392b38 Compare May 2, 2026 06:17
coderdan added 2 commits May 2, 2026 16:22
After authentication, schema generation, and Forge install, init now offers
to hand off the rest of setup to Claude Code. Choosing the handoff installs
a project-local skill at .claude/skills/cipherstash-setup/SKILL.md and
writes .cipherstash/context.json describing the integration, encryption
client path, columns, env keys, and package manager — then spawns claude
interactively.

The skill body comes from a versioned rulebook (core + integration partials)
that ships bundled with the CLI. When wizard.getstash.sh is reachable, the
CLI prefers the gateway-served version so content updates between releases
land without a CLI bump; network failures fall through to the bundled copy
silently. CIPHERSTASH_WIZARD_URL overrides the gateway endpoint for local
testing.

Re-running init upserts the managed region of SKILL.md via sentinel markers
(<!-- cipherstash:rulebook start/end -->) so user edits outside the block
are preserved.

Three completion modes are available at the new "how to proceed" step:
- Hand off to Claude Code (default when claude detected)
- Just write the rules files (no spawn — drive your own agent)
- Use the CipherStash Agent (run stash wizard later)

Phase 1 only targets Claude Code; Codex (AGENTS.md), Cursor, and Copilot
writers are scoped for follow-up phases.
…S.md)

Phase 2 of the init agent handoff. Replaces the three-mode "how to proceed"
prompt with a four-option menu so users can pick the agent that matches
their workflow:

- Hand off to Claude Code (default when claude on PATH)
- Hand off to Codex (default when codex on PATH and claude is not)
- Use the CipherStash Agent (fallback — runs `stash wizard`)
- Write AGENTS.md (default when neither CLI agent is detected)

Detection is non-blocking: a missing CLI doesn't hide the option, the
handoff step still writes the rules files and prints install +
manual-launch instructions. The user's progress is never wasted.

Adds renderAgentsMd to @cipherstash/cli's bundled rulebook (plain
markdown, no YAML frontmatter — sentinel-marker friendly). Extends
fetchRulebook to take an `agent` parameter so the gateway can serve
SKILL.md or AGENTS.md from the same endpoint and the bundled fallback
picks the right renderer.

Refactors the shared writer logic (context.json + sentinel-upserted
artifact files + CLI version walker) into lib/write-context.ts so all
four handoff steps share one implementation.
@coderdan coderdan force-pushed the dan/init-agent-handoff branch from b392b38 to 581831d Compare May 2, 2026 06:22
coderdan added 4 commits May 2, 2026 19:01
…n prompt

Three changes that turn the agent handoff from "here are the rules, figure
it out" into "here's exactly what to do":

1. **Resolve DATABASE_URL upfront and require it.** New resolve-database
   step wraps the existing resolveDatabaseUrl resolver (--flag → env →
   supabase status → interactive prompt → hard fail). The URL is threaded
   through state so downstream steps don't re-prompt.

2. **Introspect the database and let the user pick columns.** build-schema
   now connects, lists tables in the public schema, and runs the same
   multi-select UX as `stash schema build` (lifted into a shared lib).
   Empty databases still fall back to the placeholder users/email/name
   client; the action prompt notes that and asks the agent to reshape it.

3. **Install EQL during init.** New install-eql step runs `stash db install`
   programmatically after a y/N confirm, using the URL we already resolved.
   No second credential prompt. Skipping isn't a dead end — the action
   prompt's first TODO becomes "run stash db install".

4. **Write `.cipherstash/setup-prompt.md`** — a per-project action plan
   generated from init state. Lists what init already did and what's left
   with exact commands and paths (drizzle-kit generate / migrate, supabase
   migration new, etc.) tailored to the detected integration and package
   manager. Claude / Codex launch prompts now point the agent at this file
   first; the skill / AGENTS.md provides the reusable rulebook the prompt
   references. For IDE users, it's ready to paste into the first chat.

Renames install-forge → install-deps. "Forge" was the legacy name for the
CLI itself (renamed to `stash` in #383); the step installs `@cipherstash/stack`
and `stash`, so install-deps says what it actually does.

Refactor: introspectDatabase + selectTableColumns lifted from
schema/build.ts into init/lib/introspect.ts so both the standalone command
and the new init step share one codepath. generateClientFromSchemas
similarly consolidated into init/utils.ts.
A leftover `node_modules/<pkg>/` directory with no `package.json` (from a
prior aborted install, a stale workspace symlink, or npm pruning a
package mid-install) was previously treated as installed. install-deps
would skip the install — and then install-eql's call to installCommand
would scaffold `stash.config.ts`, try to load it via jiti, and crash with
`Cannot find module 'stash'` deep inside jiti's resolver.

Two changes:

1. `isPackageInstalled` now requires both the directory AND a
   `package.json` inside it. Matches what Node's resolver actually needs
   to load the module.

2. install-eql double-checks that `stash` is loadable before calling
   `installCommand`. If install-deps was skipped or rolled back, we exit
   the step with a clear error pointing the user at the right manual
   command, instead of letting the failure surface as an opaque jiti
   trace mid-flow.

Adds tests covering the directory-without-manifest case so this can't
regress silently.
Before this, `.cipherstash/context.json` was only written inside the
handoff step. If init aborted between build-schema and the handoff —
install-eql crashing, Ctrl+C, anything — the previous run's context.json
would still be on disk, and a user manually launching `claude` against
the stale file would see a placeholder schema instead of what was just
introspected.

The fix writes a baseline context.json at the end of build-schema using
the bundled rulebook version. Handoff steps still refresh it with the
gateway-served rulebook version (when reachable), but the file is no
longer dependent on the handoff completing — it tracks the encryption
client at the moment that client is generated.

This was the root cause of a tester seeing claude reading
`{"tableName":"users","columns":[...]}` after they had selected
`transactions` during the (working) introspection prompt.
…lti-table schemas)

Pre-review pass over the init agent handoff:

- Use `fileURLToPath` instead of `new URL(...).pathname` in `rulebook/partials.ts` — the latter breaks on Windows (`/C:/...`) and on paths with spaces.
- Replace the POSIX-only `/bin/sh -c command -v` with a pure-Node PATH walk in `detect-agents.ts`. Honours `PATHEXT`-style suffixes (`.cmd`, `.exe`, `.bat`) on Windows so `claude.cmd` is detected.
- Split `wizardCommand` into `runWizardSpawn` (returns exit code) and `wizardCommand` (calls `process.exit`). The init handoff path uses `runWizardSpawn` so init can finish its outro and run `next-steps` instead of aborting on a non-zero wizard exit.
- Single-quote the launch-prompt examples printed to the user when `claude`/`codex` aren't on PATH — robust against any future shell-special characters in the prompt.
- Store env-key names on `InitState` once (in `build-schema`) rather than scanning `.env*` files three times across `gather-context`, the baseline write, and the chosen handoff. Drops the awkward "side channel" comment in gather-context.
- Change `state.schema: SchemaDef` to `state.schemas: SchemaDef[]` (and same in `ContextFile`) so multi-table introspection runs are represented faithfully. Drop the unused `schema` field from `SetupPromptContext` (the renderer never read it).
- Drop the stale "Renamed from `forgeInstalled`" comment from `InitState`. The rename is in git history.
- Inline the `buildFromIntrospection` one-line forwarder.

README updated with the new init flow, the four-option handoff menu, and a `stash wizard` reference section. Outdated "edit your encryption client by hand" guidance removed in favour of the action-prompt-driven workflow.

154 stack CLI tests pass (no behaviour change in the test suite). 17 suite rulebook tests pass.
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