feat(cli): hand off init to local coding agents (Claude / Codex / AGENTS.md / wizard)#395
feat(cli): hand off init to local coding agents (Claude / Codex / AGENTS.md / wizard)#395
Conversation
🦋 Changeset detectedLatest commit: 4310f02 The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
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 |
|
Warning Rate limit exceeded
To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (23)
📝 WalkthroughWalkthroughThe pull request implements end-to-end agent handoff for ChangesInit Agent Handoff Pipeline
Sequence DiagramsequenceDiagram
actor User
participant CLI as stash init
participant DB as Postgres
participant AI as Agents<br/>(Claude/Codex)
participant Wizard as Agent Wizard
User->>CLI: stash init
CLI->>DB: Resolve DATABASE_URL
Note over CLI: Detect agents<br/>on PATH
CLI->>DB: Introspect public schema
User->>CLI: Select columns<br/>to encrypt
CLI->>CLI: Generate encryption<br/>client code
CLI->>CLI: Install dependencies<br/>(`@cipherstash/stack`, stash)
CLI->>DB: Install EQL extension
CLI->>CLI: Write context.json<br/>and setup-prompt.md
User->>CLI: Select handoff target
alt Claude Code Detected
CLI->>CLI: Install .claude/skills
CLI->>AI: Launch Claude<br/>with setup prompt
AI->>User: Interactive setup
else Codex Detected
CLI->>CLI: Install .codex/skills<br/>Update AGENTS.md
CLI->>AI: Launch Codex<br/>with context
AI->>User: Interactive setup
else Use Agent Wizard
CLI->>CLI: Write AGENTS.md<br/>with doctrine
CLI->>Wizard: Spawn wizard<br/>process
Wizard->>User: Interactive setup
else Editor Support
CLI->>CLI: Generate AGENTS.md<br/>with inlined rules
Note over CLI: User opens project<br/>in editor with agent
end
User-->>CLI: Setup complete
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. 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. Review rate limit: 0/1 reviews remaining, refill in 35 minutes and 4 seconds.Comment |
4ab8e16 to
ce70b4d
Compare
b392b38 to
581831d
Compare
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.
…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.
The rulebook package shipped a thinner, parallel restatement of content
already authored as Claude Code skills at the repo root (`/skills/`).
Replace it: copy the right per-integration subset of those skills into
the user's project at handoff time, and split AGENTS.md into "durable
doctrine" per OpenAI's Codex guidance.
Architecture per handoff:
- claude-code → `.claude/skills/{stash-encryption,stash-<integration>,stash-cli}/`
- codex → `.codex/skills/<...>` + AGENTS.md (sentinel-wrapped doctrine)
- agents-md → AGENTS.md only, doctrine + inlined skill content (for
Cursor / Windsurf / Cline which don't auto-load skills)
- wizard → unchanged; wizard installs its own skills
Per-integration subset extends the wizard's existing SKILL_MAP with
postgresql + dynamodb so init covers every integration the CLI knows
about.
Changes:
- New: install-skills.ts (port of wizard's helper, parameterised destDir,
no prompt — by handoff time the choice is made), build-agents-md.ts
(doctrine-only / doctrine-plus-skills modes), AGENTS-doctrine.md
(the durable-rules fragment), tests for both.
- Move: rulebook/renderers/setup-prompt.ts → init/lib/setup-prompt.ts
with its test. Setup-prompt is project-specific; it belonged with the
rest of init/ all along. Drop "Rulebook version: ..." header line;
update the rules-pointer to name the installed skills per handoff.
- Update: tsup.config.ts copies skills/ + doctrine/ into dist/ at build
time so the CLI tarball ships them. write-context.ts drops
rulebookVersion from ContextFile, adds installedSkills: string[].
Handoff steps switch from fetchRulebook+writeArtifact to
installSkills/buildAgentsMd.
- Delete: rulebook/ directory, fetch-rulebook.ts, gateway-fetch path
(no longer needed; everything ships bundled).
Net diff: -170 lines vs the previous handoff implementation. PR #506
(suite gateway endpoint) is no longer needed and should be closed.
583d1be to
5314d3f
Compare
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (4)
packages/cli/src/commands/init/utils.ts (1)
255-302: ⚖️ Poor tradeoffConsider extracting shared column-generation logic to reduce duplication.
The column generation in
generateDrizzleFromSchemas(lines 260-279) is nearly identical togenerateDrizzleFromSchema(lines 172-191). Similarly,generateGenericFromSchemasduplicatesgenerateSchemaFromDef. This duplication risks divergence if options or formatting change.Extract a shared helper for per-table code generation that both single and multi-schema functions can call.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/cli/src/commands/init/utils.ts` around lines 255 - 302, Extract the repeated per-table column generation into a single helper function (e.g., generateTableDef or generateColumnDefs) and have both generateDrizzleFromSchemas and generateDrizzleFromSchema call it; the helper should accept a SchemaDef (or SchemaDef + table var names) and return the table string block including id, createdAt and per-column lines (reusing drizzleTsType and the opts construction: dataType, equality, orderAndRange, freeTextSearch). Do the same for the generic generators by extracting the repeated logic used by generateGenericFromSchemas and generateSchemaFromDef into a shared helper and update those functions to call it so formatting and option logic is centralized. Ensure unique symbols referenced: generateDrizzleFromSchemas, generateDrizzleFromSchema, generateGenericFromSchemas, generateSchemaFromDef, and drizzleTsType are used in the refactor..changeset/cli-init-agent-handoff.md (1)
26-31: 💤 Low valueAdd a language specifier to the fenced code block.
The code block is missing a language identifier, which markdownlint flagged. Since this is a plain text mapping, use
textorplaintext.📝 Proposed fix
-``` +```text drizzle → stash-encryption + stash-drizzle + stash-cli supabase → stash-encryption + stash-supabase + stash-cli postgresql → stash-encryption + stash-cli dynamodb → stash-encryption + stash-dynamodb + stash-cli</details> <details> <summary>🤖 Prompt for AI Agents</summary>Verify each finding against the current code and only fix it if needed.
In @.changeset/cli-init-agent-handoff.md around lines 26 - 31, The fenced code
block in .changeset/cli-init-agent-handoff.md is missing a language specifier;
update the block delimiter fromtotext (or ```plaintext) so the mapping
lines (drizzle → stash-encryption..., supabase → ..., postgresql → ..., dynamodb
→ ...) are rendered as plain text; locate the exact fenced block and change only
the opening triple backticks to include the language identifier.</details> </blockquote></details> <details> <summary>packages/cli/src/commands/init/steps/install-deps.ts (1)</summary><blockquote> `96-100`: _💤 Low value_ **Minor imprecision in partial success tracking.** If one package installs successfully but the other fails, both `stackInstalled` and `cliInstalled` are set based on the composite `allSucceeded` flag. This could mark a successfully installed package as not installed. This is mitigated by the re-run behavior (next `init` will detect the installed package), but tracking per-package success would be more precise. <details> <summary>♻️ Proposed fix for per-package tracking</summary> ```diff - let allSucceeded = true + let stackSucceeded = stackPresent + let cliSucceeded = cliPresent for (const cmd of commands) { p.log.step(`Running: ${cmd}`) try { execSync(cmd, { cwd: process.cwd(), stdio: 'inherit' }) + // Track which package succeeded based on the command + if (cmd.includes(STACK_PACKAGE)) stackSucceeded = true + if (cmd.includes(CLI_PACKAGE)) cliSucceeded = true } catch (err) { const message = err instanceof Error ? err.message : String(err) p.log.error(`Install failed: ${cmd}`) p.log.error(message) - allSucceeded = false } } + const allSucceeded = stackSucceeded && cliSucceeded if (allSucceeded) { p.log.success('Stack dependencies installed.') } else { p.note( `You can retry manually:\n ${commands.join('\n ')}`, 'Manual Installation', ) } return { ...state, - stackInstalled: stackPresent || allSucceeded, - cliInstalled: cliPresent || allSucceeded, + stackInstalled: stackSucceeded, + cliInstalled: cliSucceeded, } ``` </details> <details> <summary>🤖 Prompt for AI Agents</summary> ``` Verify each finding against the current code and only fix it if needed. In `@packages/cli/src/commands/init/steps/install-deps.ts` around lines 96 - 100, The state update incorrectly uses the composite allSucceeded to set both stackInstalled and cliInstalled which can mark a succeeded package as not installed; update the logic to use per-package success flags instead of allSucceeded — i.e., set stackInstalled = stackPresent || stackSucceeded and cliInstalled = cliPresent || cliSucceeded (or the equivalent variables/results returned by the individual installation attempts) and ensure the code that runs the installs exposes/returns those per-package success booleans so the state merge uses them rather than allSucceeded. ``` </details> </blockquote></details> <details> <summary>packages/cli/src/commands/init/lib/write-context.ts (1)</summary><blockquote> `75-98`: _⚡ Quick win_ **`buildContextFile` should accept `envKeys`/`installedSkills` as parameters rather than requiring post-construction mutation.** Every call site (both `writeBaselineContextFile` and `handoffAgentsMdStep`) immediately overwrites `envKeys` and/or `installedSkills` after `buildContextFile` returns. A caller that forgets the mutation step silently emits an incorrect `context.json` with empty arrays — no type error, no runtime warning. Since `writeBaselineContextFile` already receives `envKeys` as an explicit parameter, it can pass it straight through: <details> <summary>♻️ Proposed refactor</summary> ```diff export function buildContextFile( state: InitState, + envKeys: string[] = [], + installedSkills: string[] = [], ): ContextFile { const integration = state.integration ?? 'postgresql' const clientFilePath = state.clientFilePath ?? './src/encryption/index.ts' const schemas = state.schemas if (!schemas || schemas.length === 0) { throw new Error('Schemas missing from init state — cannot write context.') } const pm = detectPackageManager() return { cliVersion: readCliVersion(), integration, encryptionClientPath: clientFilePath, packageManager: pm, installCommand: prodInstallCommand(pm, '@cipherstash/stack'), - envKeys: [], + envKeys, schemas, - installedSkills: [], + installedSkills, generatedAt: new Date().toISOString(), } } ``` Callers can then be updated to pass the fields directly instead of mutating: ```diff // writeBaselineContextFile - const ctx = buildContextFile(state) - ctx.envKeys = envKeys - writeContextFile(absPath, ctx) + writeContextFile(absPath, buildContextFile(state, envKeys)) // handoffAgentsMdStep (snippet 1) - const ctx = buildContextFile(state) - ctx.envKeys = envKeys - ctx.installedSkills = [] - writeContextFile(contextAbs, ctx) + writeContextFile(contextAbs, buildContextFile(state, envKeys, [])) ``` </details> <details> <summary>🤖 Prompt for AI Agents</summary> ``` Verify each finding against the current code and only fix it if needed. In `@packages/cli/src/commands/init/lib/write-context.ts` around lines 75 - 98, buildContextFile currently always builds ContextFile with empty envKeys and installedSkills which callers like writeBaselineContextFile and handoffAgentsMdStep immediately overwrite; change buildContextFile signature to accept envKeys: string[] and installedSkills: string[] (or optional params) and use those values when constructing the ContextFile, then update all call sites (e.g., writeBaselineContextFile and handoffAgentsMdStep) to pass their envKeys/installedSkills through instead of mutating the returned object; keep the existing defaults behavior (empty arrays) if callers omit the args to preserve backwards compatibility. ``` </details> </blockquote></details> </blockquote></details> <details> <summary>🤖 Prompt for all review comments with AI agents</summary>Verify each finding against the current code and only fix it if needed.
Inline comments:
In@packages/cli/README.md:
- Line 21: Update the "Hand off to Codex" README entry to mention that the Codex
step writes both AGENTS.md and the .codex/skills/ artifact; locate the existing
line that reads "Hand off to Codex — writesAGENTS.mdat the project root,
then launchescodex" and change it to explicitly state that the codex process
also creates the.codex/skills/...directory and files (e.g., "writes
AGENTS.mdand the.codex/skills/artifacts, then launchescodex") so users
know both outputs are produced.In
@packages/cli/src/commands/init/lib/build-agents-md.ts:
- Around line 96-107: The candidates array in readDoctrine() contains a
duplicated entry join(here, '..', 'doctrine', 'AGENTS-doctrine.md') at index 2;
remove that duplicate and replace it with the intended alternate layout (e.g., a
deeper path such as join(here, '..', '..', 'init', 'doctrine',
'AGENTS-doctrine.md') or another project-specific variant) so the candidate list
covers both sibling and deeper/dist layouts before checking existsSync and
returning readFileSync.In
@packages/cli/src/commands/init/lib/introspect.ts:
- Around line 216-229: The loop currently calls selectTableColumns(tables) each
iteration which allows selecting the same table multiple times; update the loop
so it tracks already-selected table names (from the pushed schema objects) and
pass a filtered list to selectTableColumns (or remove the chosen table from
tables) on subsequent iterations to prevent duplicates — reference the while
loop, selectTableColumns, schemas array, and the schema variable to locate where
to filter the original tables before calling selectTableColumns again.- Line 55: The pg.Client is created without a connection timeout so
client.connect() can hang; update the client instantiation (the const client =
new pg.Client({...}) that precedes client.connect()) to include a sensible
connectionTimeoutMillis (e.g. 5_000) in the options so connection attempts fail
fast and the spinner at the client.connect()/spinner logic can stop; ensure any
surrounding error handling that calls client.end() or stops the spinner still
runs when connect rejects.In
@packages/cli/src/commands/init/lib/setup-prompt.ts:
- Around line 98-112: The rulesPointer function produces a double-space and an
empty "the skills ..." phrase when installedSkills is empty; fix by making the
skill portion conditional: compute a skillPhrase (or inline conditional) that
only includes "the skill(s) " when installedSkills.length > 0, and
otherwise omits the "the <...> " prefix entirely (e.g., return "skills loaded
into.claude/skills/" or "theAGENTS.md...") so templates in branches for
handoff === 'claude-code' and 'codex' don't emit an extra space or empty
placeholder; update rulesPointer accordingly (referencing rulesPointer and the
installedSkills/skillNames logic).In
@packages/cli/src/commands/init/steps/handoff-agents-md.ts:
- Around line 61-76: The note block unconditionally references
SETUP_PROMPT_REL_PATH and tells users to open an agent even when
buildSetupPromptContext returned a falsy promptCtx and writeSetupPrompt was not
called; update the logic so the p.note message only includes the "Action plan at
…" line and the "Open your agent…" instruction when promptCtx is truthy (i.e.,
after writeSetupPrompt runs). Locate the promptCtx check around writeSetupPrompt
and SETUP_PROMPT_REL_PATH and conditionally assemble or append those two strings
to the array passed to p.note (leave the other entries — AGENTS_MD_REL_PATH and
CONTEXT_REL_PATH — unchanged) so the note accurately reflects which files were
created.In
@packages/cli/src/commands/init/steps/install-eql.ts:
- Around line 73-78: The catch block in install-eql.ts currently logs raw
err.message via p.log.error which can leak DB connection strings from
state.databaseUrl; change the catch to log a generic error message (e.g., "EQL
install failed") without embedding err.message or any unredacted data, keep the
retry note (p.note) and returning { ...state, eqlInstalled: false }, and if you
need the underlying error for diagnostics, send it to a secure/internal
debug/telemetry channel or redact the connection string first (refer to the
p.log.error call and the catch block surrounding eqlInstalled state).
Nitpick comments:
In @.changeset/cli-init-agent-handoff.md:
- Around line 26-31: The fenced code block in
.changeset/cli-init-agent-handoff.md is missing a language specifier; update the
block delimiter fromtotext (or ```plaintext) so the mapping lines
(drizzle → stash-encryption..., supabase → ..., postgresql → ..., dynamodb →
...) are rendered as plain text; locate the exact fenced block and change only
the opening triple backticks to include the language identifier.In
@packages/cli/src/commands/init/lib/write-context.ts:
- Around line 75-98: buildContextFile currently always builds ContextFile with
empty envKeys and installedSkills which callers like writeBaselineContextFile
and handoffAgentsMdStep immediately overwrite; change buildContextFile signature
to accept envKeys: string[] and installedSkills: string[] (or optional params)
and use those values when constructing the ContextFile, then update all call
sites (e.g., writeBaselineContextFile and handoffAgentsMdStep) to pass their
envKeys/installedSkills through instead of mutating the returned object; keep
the existing defaults behavior (empty arrays) if callers omit the args to
preserve backwards compatibility.In
@packages/cli/src/commands/init/steps/install-deps.ts:
- Around line 96-100: The state update incorrectly uses the composite
allSucceeded to set both stackInstalled and cliInstalled which can mark a
succeeded package as not installed; update the logic to use per-package success
flags instead of allSucceeded — i.e., set stackInstalled = stackPresent ||
stackSucceeded and cliInstalled = cliPresent || cliSucceeded (or the equivalent
variables/results returned by the individual installation attempts) and ensure
the code that runs the installs exposes/returns those per-package success
booleans so the state merge uses them rather than allSucceeded.In
@packages/cli/src/commands/init/utils.ts:
- Around line 255-302: Extract the repeated per-table column generation into a
single helper function (e.g., generateTableDef or generateColumnDefs) and have
both generateDrizzleFromSchemas and generateDrizzleFromSchema call it; the
helper should accept a SchemaDef (or SchemaDef + table var names) and return the
table string block including id, createdAt and per-column lines (reusing
drizzleTsType and the opts construction: dataType, equality, orderAndRange,
freeTextSearch). Do the same for the generic generators by extracting the
repeated logic used by generateGenericFromSchemas and generateSchemaFromDef into
a shared helper and update those functions to call it so formatting and option
logic is centralized. Ensure unique symbols referenced:
generateDrizzleFromSchemas, generateDrizzleFromSchema,
generateGenericFromSchemas, generateSchemaFromDef, and drizzleTsType are used in
the refactor.</details> <details> <summary>🪄 Autofix (Beta)</summary> Fix all unresolved CodeRabbit comments on this PR: - [ ] <!-- {"checkboxId": "4b0d0e0a-96d7-4f10-b296-3a18ea78f0b9"} --> Push a commit to this branch (recommended) - [ ] <!-- {"checkboxId": "ff5b1114-7d8c-49e6-8ac1-43f82af23a33"} --> Create a new PR with the fixes </details> --- <details> <summary>ℹ️ Review info</summary> <details> <summary>⚙️ Run configuration</summary> **Configuration used**: defaults **Review profile**: CHILL **Plan**: Pro **Run ID**: `fb3d2408-63d7-46fa-8c86-5c664bf74229` </details> <details> <summary>📥 Commits</summary> Reviewing files that changed from the base of the PR and between b108560ea33dd410e70d66e24b088aaadb0a0681 and 5314d3f699cb1d1c9018f295a1b4e21aed697ec4. </details> <details> <summary>📒 Files selected for processing (33)</summary> * `.changeset/cli-init-agent-handoff.md` * `packages/cli/README.md` * `packages/cli/package.json` * `packages/cli/src/commands/init/__tests__/detect-agents.test.ts` * `packages/cli/src/commands/init/__tests__/sentinel-upsert.test.ts` * `packages/cli/src/commands/init/__tests__/utils.test.ts` * `packages/cli/src/commands/init/detect-agents.ts` * `packages/cli/src/commands/init/doctrine/AGENTS-doctrine.md` * `packages/cli/src/commands/init/index.ts` * `packages/cli/src/commands/init/lib/__tests__/build-agents-md.test.ts` * `packages/cli/src/commands/init/lib/__tests__/install-skills.test.ts` * `packages/cli/src/commands/init/lib/__tests__/setup-prompt.test.ts` * `packages/cli/src/commands/init/lib/build-agents-md.ts` * `packages/cli/src/commands/init/lib/install-skills.ts` * `packages/cli/src/commands/init/lib/introspect.ts` * `packages/cli/src/commands/init/lib/sentinel-upsert.ts` * `packages/cli/src/commands/init/lib/setup-prompt.ts` * `packages/cli/src/commands/init/lib/write-context.ts` * `packages/cli/src/commands/init/steps/build-schema.ts` * `packages/cli/src/commands/init/steps/gather-context.ts` * `packages/cli/src/commands/init/steps/handoff-agents-md.ts` * `packages/cli/src/commands/init/steps/handoff-claude.ts` * `packages/cli/src/commands/init/steps/handoff-codex.ts` * `packages/cli/src/commands/init/steps/handoff-wizard.ts` * `packages/cli/src/commands/init/steps/how-to-proceed.ts` * `packages/cli/src/commands/init/steps/install-deps.ts` * `packages/cli/src/commands/init/steps/install-eql.ts` * `packages/cli/src/commands/init/steps/resolve-database.ts` * `packages/cli/src/commands/init/types.ts` * `packages/cli/src/commands/init/utils.ts` * `packages/cli/src/commands/schema/build.ts` * `packages/cli/src/commands/wizard/index.ts` * `packages/cli/tsup.config.ts` </details> </details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
- introspect.ts: set pg.Client connectionTimeoutMillis to 10s. The default is no timeout, so an unreachable / firewalled DB hung the spinner indefinitely. - introspect.ts: dedupe table list across the buildSchemasFromDatabase loop iterations. Previously the user could pick the same table twice and we'd push two SchemaDef entries for it, causing duplicate encrypted column declarations downstream. Skip the redundant "another table?" prompt when no tables remain. - install-eql.ts: don't echo the underlying error message on EQL install failure — Postgres client errors routinely embed the connection string (with credentials) and state.databaseUrl flows into this code path. - build-agents-md.ts: dedupe the readDoctrine candidate path list (two entries resolved to the same path). Add a deeper fallback for flattened tsup chunk layouts. - setup-prompt.ts: rulesPointer no longer emits "the skills loaded into …" (double space, no names) when installedSkills is empty. Falls back to a generic pointer. New test locks this in. - README.md: refresh the four-handoff descriptions — the Claude line still referenced the old `cipherstash-setup` SKILL.md, the Codex line omitted the `.codex/skills/` artifact and AGENTS.md doctrine, the AGENTS.md line undersold the inlined skill content.
Bugs:
- Drop `dynamodb` from SKILL_MAP — `Integration` doesn't include it, so
the entry was a TypeScript error that tsup didn't catch (it doesn't
typecheck non-entry files). Also drops the orphaned tests. The key
comes back when DynamoDB integration detection lands.
- Rename `_provider` to `provider` in resolve-database.ts; the param is
used at line 26 — the underscore prefix was misleading.
Reuse:
- New `lib/bundled-paths.ts` with `findBundledDir(name)` — replaces the
near-identical candidate-walking in `install-skills.ts` and
`build-agents-md.ts`. Memoized so the bundled-root probe runs at most
once per directory name (matters for the AGENTS.md inline-skills
mode that calls it per skill).
- New `lib/handoff-helpers.ts`:
- `spawnAgent('claude' | 'codex', prompt)` — replaces the two
near-identical `spawnClaude` / `spawnCodex` wrappers.
- `writeArtifacts(cwd, state, handoff, installedSkills)` — folds the
repeated context.json + setup-prompt.md write block from the three
non-wizard handoff steps into one place.
Efficiency:
- `detectPackageManager` is now memoized per (cwd, user-agent) pair.
It's called multiple times per init run plus on every other CLI
command, and each call did up to 4 fs.existsSync probes. Cache key
preserves test isolation under `vi.spyOn(process, 'cwd')`.
- `readCliVersion` is memoized — single-slot, since the answer is
fixed for the lifetime of the process.
Cleanup:
- Drop two narrating comments that just restated the next line of
code (the quote-escaping one in handoff-claude, the
no-skill-directory one in handoff-agents-md). Tighten the empty-
installedSkills comment in setup-prompt to one line.
Net: -134 lines, all tests + biome clean.
- AGENTS.md double-sentinel bug: buildAgentsMdBody was returning sentinel-wrapped content, which upsertManagedBlock then wrapped again. First run produced an invalid file; second run threw "malformed sentinel". Strip sentinels from the body builder; upsertManagedBlock is the single owner of the sentinel pair. - build-schema "keep existing file" branch: previously short-circuited to placeholder schemas regardless of what the database actually has. Now runs introspection in the keep-branch too so context.json reflects the live DB; codegen is the only thing skipped. - readEnvKeyNames moved from steps/gather-context.ts to lib/env-keys.ts. build-schema importing across step boundaries was a layering smell. - install-deps: per-package tracking. Re-check isPackageInstalled after the install commands instead of inferring stack/cli state from a composite allSucceeded flag — partial success was previously marked as full failure. - Claude install URL redirect: docs.claude.com/en/docs/claude-code/quickstart → code.claude.com/docs/en/quickstart. - package.json: drop redundant dist/sql and dist/rulebook from the files array. dist/ already covers them; dist/rulebook is dead since the rulebook package was deleted. - Changeset: drop the dynamodb row from the per-integration table (SKILL_MAP no longer includes it as of the simplify pass) and add a `text` language tag to the fenced block.
Two doc updates in support of #357 now that the rulebook package is gone: - `docs/plans/encryption-migrations.md`: drop "rulebook" references (5 of them) and the stale `packages/cli/src/commands/wizard/lib` paths. Re-point the agent-handoff bits at the post-#395 architecture: Claude / Codex / AGENTS.md handoffs from `init/steps/handoff-*.ts`, with the integration skill installed by init providing the per-stack guidance. Repoint `introspectDatabase` to its current home in `init/lib/introspect.ts`. - `/skills/stash-cli/SKILL.md`: add an `encrypt` section documenting every subcommand (`status`, `plan`, `advance`, `backfill`, `cutover`, `drop`) with flags, examples, and a one-line note on runner-prefix substitution so the docs are not pinned to npm. - `/skills/stash-encryption/SKILL.md`: add a "Column Migration Lifecycle" section covering the six-phase model (schema-added → dual-writing → backfilling → backfilled → cut-over → dropped), the three-source state model (`migrations.json` / `eql_v2_configuration` / `cs_migrations`), the CLI sequence, and the library `runBackfill` shape. Agents reading this skill now have the migration vocabulary they need. No CLI behaviour changes. Buckets 3+ from the audit (advance handoff integration, runner-aware help in encrypt commands, setup-prompt recommending `stash encrypt`, AGENTS-doctrine pointing at the CLI path) deferred until the encrypt-step UX has been reviewed.
Two doc updates in support of #357 now that the rulebook package is gone: - `docs/plans/encryption-migrations.md`: drop "rulebook" references (5 of them) and the stale `packages/cli/src/commands/wizard/lib` paths. Re-point the agent-handoff bits at the post-#395 architecture: Claude / Codex / AGENTS.md handoffs from `init/steps/handoff-*.ts`, with the integration skill installed by init providing the per-stack guidance. Repoint `introspectDatabase` to its current home in `init/lib/introspect.ts`. - `/skills/stash-cli/SKILL.md`: add an `encrypt` section documenting every subcommand (`status`, `plan`, `advance`, `backfill`, `cutover`, `drop`) with flags, examples, and a one-line note on runner-prefix substitution so the docs are not pinned to npm. - `/skills/stash-encryption/SKILL.md`: add a "Column Migration Lifecycle" section covering the six-phase model (schema-added → dual-writing → backfilling → backfilled → cut-over → dropped), the three-source state model (`migrations.json` / `eql_v2_configuration` / `cs_migrations`), the CLI sequence, and the library `runBackfill` shape. Agents reading this skill now have the migration vocabulary they need. No CLI behaviour changes. Buckets 3+ from the audit (advance handoff integration, runner-aware help in encrypt commands, setup-prompt recommending `stash encrypt`, AGENTS-doctrine pointing at the CLI path) deferred until the encrypt-step UX has been reviewed.
Adds a four-way handoff at the end of
stash init: Claude Code, Codex CLI, the CipherStash wizard, or a plainAGENTS.mdfor editor agents. Each handoff installs the right artifacts for the chosen tool, points the agent at a project-specific action plan, and never blocks on missing CLIs.What ships
Per handoff:
.claude/skills/{stash-encryption, stash-<integration>, stash-cli}/+.cipherstash/{context.json, setup-prompt.md}.claude/skills/. Setup-prompt names the action plan..codex/skills/<...>+ sentinel-managedAGENTS.md(durable doctrine) +.cipherstash/....codex/skills/(workflows).AGENTS.mdwith doctrine + inlined skill content +.cipherstash/....cipherstash/context.jsonthen spawnsstash wizard.cipherstash/setup-prompt.mdis the headline artifact — project-specific action plan generated from the current init state ("you've installed X, you still need to do Y, here are the exact commands"). The launch prompt for Claude / Codex points the agent at that file first; the installed skills back it up as durable reference.Per-integration skill subset:
The skills themselves are the authored ones at the repo root (
/skills/); they ship inside the CLI tarball viatsupso init can copy them locally without a network round-trip. The AGENTS.md doctrine fragment ships the same way.Architecture decisions
AGENTS.mdis durable doctrine, not procedural rules — per OpenAI's Codex guidance. The doctrine fragment lives atpackages/cli/src/commands/init/doctrine/AGENTS-doctrine.md(~80 lines: what CipherStash is, jsonb-null on creation, never log plaintext, three-phase migrations, stop-and-ask cases). Workflows / API reference live in skills loaded at the per-tool location.<!-- cipherstash:rulebook start/end -->sentinel-marker upsert so user content outside the block is preserved. Skill directories overwrite (latest wins).setup-prompt.mdregenerates wholesale to reflect the current state.claudeorcodexisn't on PATH, init still writes the artifacts and prints install + manual-launch instructions. Progress is never wasted.Smoke-test status
.claude/skills/, context + setup-prompt written,claudespawns with the right launch prompt, re-runs replace cleanly..codex/skills/+ AGENTS.md doctrine produced as expected; sentinel block re-upserts on re-runs.dist/skills/anddist/commands/init/doctrine/afterpnpm build.Out of scope (follow-ups)
dynamodbentry inSKILL_MAPand nostash-dynamodbinstall path. The skill itself exists at/skills/stash-dynamodb/and will be wired up alongside DynamoDB project detection.scripts/andreferences/subdirectories — captured in the ChatGPT note that prompted the doctrine/skills split. New issue to come.stash-secretsandstash-supply-chain-securityauto-install. Currently not bundled into the per-integration set.packages/rulebook/mirror incipherstash-js-suite(gateway endpoint already closed via #506).Test plan
install-skills,build-agents-md, and the movedsetup-prompt.pnpm buildclean; bundled skills + doctrine present in dist.pnpm exec tsc --noEmitclean for all init code (two pre-existing unrelated errors in main remain).npx biome checkon changed files clean.