Skip to content

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

Merged
coderdan merged 10 commits intomainfrom
dan/init-agent-handoff
May 4, 2026
Merged

feat(cli): hand off init to local coding agents (Claude / Codex / AGENTS.md / wizard)#395
coderdan merged 10 commits intomainfrom
dan/init-agent-handoff

Conversation

@coderdan
Copy link
Copy Markdown
Contributor

@coderdan coderdan commented May 2, 2026

Adds a four-way handoff at the end of stash init: Claude Code, Codex CLI, the CipherStash wizard, or a plain AGENTS.md for 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.

Screenshot 2026-05-04 at 9 36 39 am Screenshot 2026-05-04 at 9 36 57 am

What ships

init pipeline:
authenticate → resolve-database → build-schema → install-deps
            → install-eql → gather-context → how-to-proceed → next-steps

Per handoff:

Files written Where the agent reads from
Claude Code .claude/skills/{stash-encryption, stash-<integration>, stash-cli}/ + .cipherstash/{context.json, setup-prompt.md} Claude auto-loads .claude/skills/. Setup-prompt names the action plan.
Codex .codex/skills/<...> + sentinel-managed AGENTS.md (durable doctrine) + .cipherstash/... Codex reads AGENTS.md (rules) and auto-loads .codex/skills/ (workflows).
AGENTS.md (Cursor / Windsurf / Cline) Sentinel-managed AGENTS.md with doctrine + inlined skill content + .cipherstash/... These tools only read AGENTS.md, no skill loader.
CipherStash wizard .cipherstash/context.json then spawns stash wizard Wizard fetches its own prompt, installs its own skills.

.cipherstash/setup-prompt.md is 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:

drizzle    → stash-encryption + stash-drizzle    + stash-cli
supabase   → stash-encryption + stash-supabase   + stash-cli
postgresql → stash-encryption + stash-cli

The skills themselves are the authored ones at the repo root (/skills/); they ship inside the CLI tarball via tsup so init can copy them locally without a network round-trip. The AGENTS.md doctrine fragment ships the same way.

Architecture decisions

  1. AGENTS.md is durable doctrine, not procedural rules — per OpenAI's Codex guidance. The doctrine fragment lives at packages/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.
  2. Editor-agent fallback inlines skill content. Cursor / Windsurf / Cline don't auto-load skill directories, so the AGENTS.md handoff inlines the per-integration skill content under the same sentinel block. They get the doctrine and the API reference in one file.
  3. Re-runs are safe. AGENTS.md uses <!-- cipherstash:rulebook start/end --> sentinel-marker upsert so user content outside the block is preserved. Skill directories overwrite (latest wins). setup-prompt.md regenerates wholesale to reflect the current state.
  4. Detection is non-blocking. If claude or codex isn't on PATH, init still writes the artifacts and prints install + manual-launch instructions. Progress is never wasted.

Smoke-test status

  • Claude Code — tested extensively against real projects. Skills land in .claude/skills/, context + setup-prompt written, claude spawns with the right launch prompt, re-runs replace cleanly.
  • Codex — tested extensively. .codex/skills/ + AGENTS.md doctrine produced as expected; sentinel block re-upserts on re-runs.
  • AGENTS.md file generation — confirmed: doctrine + inlined skill content under the sentinel block, regenerates correctly on re-run. End-to-end with Cursor / Windsurf is not yet validated; tracked as a follow-up.
  • ⚠️ CipherStash wizard — functional end-to-end but the wizard itself needs UX improvements; tracked as a follow-up.
  • ✅ Bundled skills + doctrine confirmed under dist/skills/ and dist/commands/init/doctrine/ after pnpm build.

Out of scope (follow-ups)

  • DynamoDB integration: the CLI doesn't yet detect DynamoDB projects, so there's no dynamodb entry in SKILL_MAP and no stash-dynamodb install path. The skill itself exists at /skills/stash-dynamodb/ and will be wired up alongside DynamoDB project detection.
  • Author Codex-style procedural skills with scripts/ and references/ subdirectories — captured in the ChatGPT note that prompted the doctrine/skills split. New issue to come.
  • stash-secrets and stash-supply-chain-security auto-install. Currently not bundled into the per-integration set.
  • Validate the AGENTS.md handoff end-to-end with Cursor and Windsurf.
  • CipherStash wizard UX improvements.
  • Delete the now-unused packages/rulebook/ mirror in cipherstash-js-suite (gateway endpoint already closed via #506).

Test plan

  • 163 unit tests green (vitest), including new tests for install-skills, build-agents-md, and the moved setup-prompt.
  • pnpm build clean; bundled skills + doctrine present in dist.
  • pnpm exec tsc --noEmit clean for all init code (two pre-existing unrelated errors in main remain).
  • npx biome check on changed files clean.
  • Claude Code handoff smoke-tested end-to-end against a real project.
  • Codex handoff smoke-tested end-to-end against a real project.
  • AGENTS.md handoff: file generation verified (doctrine + inlined skills, sentinel-wrapped, idempotent on re-run).

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 2, 2026

🦋 Changeset detected

Latest commit: 4310f02

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

Warning

Rate limit exceeded

@coderdan has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 35 minutes and 4 seconds before requesting another review.

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 77450348-9a27-4aa8-91fe-3be4240d60b9

📥 Commits

Reviewing files that changed from the base of the PR and between 5314d3f and 4310f02.

📒 Files selected for processing (23)
  • .changeset/cli-init-agent-handoff.md
  • packages/cli/README.md
  • packages/cli/package.json
  • 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/bundled-paths.ts
  • packages/cli/src/commands/init/lib/env-keys.ts
  • packages/cli/src/commands/init/lib/handoff-helpers.ts
  • packages/cli/src/commands/init/lib/install-skills.ts
  • packages/cli/src/commands/init/lib/introspect.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/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/utils.ts
📝 Walkthrough

Walkthrough

The pull request implements end-to-end agent handoff for stash init, adding database resolution, multi-schema introspection, environment context gathering, dependency/EQL installation, and selection of one of four handoff targets (Claude Code, Codex, CLI wizard, or AGENTS.md). It introduces bundled skills management, sentinel-based Markdown upsert semantics, and generates .cipherstash/context.json and .cipherstash/setup-prompt.md to guide downstream work.

Changes

Init Agent Handoff Pipeline

Layer / File(s) Summary
Type Definitions & Interfaces
packages/cli/src/commands/init/types.ts, packages/cli/src/commands/init/detect-agents.ts, packages/cli/src/commands/init/lib/build-agents-md.ts, packages/cli/src/commands/init/lib/sentinel-upsert.ts, packages/cli/src/commands/init/lib/write-context.ts
Adds HandoffChoice union, AgentEnvironment interface, AgentsMdMode type, UpsertOptions interface, and ContextFile structure for holding detected agents, handoff choices, and output metadata.
Agent & Environment Detection
packages/cli/src/commands/init/detect-agents.ts, packages/cli/src/commands/init/steps/gather-context.ts
Implements detectAgents() to discover Claude Code/Codex on PATH and project artifacts (.claude/, AGENTS.md), plus detectEditor() for editor context. Adds readEnvKeyNames() to scan .env* files and gatherContextStep to collect detected agents and package manager.
Database Introspection & Multi-Schema Support
packages/cli/src/commands/init/lib/introspect.ts, packages/cli/src/commands/init/utils.ts
Introduces buildSchemasFromDatabase() to connect, introspect Postgres public schema, detect existing eql_v2_encrypted columns, and prompt for column selection with optional searchable encryption. Adds generateClientFromSchemas() to render multi-table encryption clients for drizzle/supabase/postgresql.
Bundled Skills & Rulebook Management
packages/cli/src/commands/init/lib/install-skills.ts, packages/cli/src/commands/init/lib/build-agents-md.ts
Defines SKILL_MAP for per-integration skill subsets, installSkills() to copy bundled skills, readBundledSkill() for per-skill content, and buildAgentsMdBody() to generate doctrine + optional inlined skill references with YAML frontmatter stripping.
Sentinel-Based File Management
packages/cli/src/commands/init/lib/sentinel-upsert.ts, packages/cli/src/commands/init/doctrine/AGENTS-doctrine.md
Implements upsertManagedBlock() to deterministically insert/replace managed regions in user files using HTML comment sentinels. Adds .cipherstash/setup-prompt.md for step-by-step action plans and AGENTS.md for agent-dialect rules.
Context & Setup File Generation
packages/cli/src/commands/init/lib/write-context.ts, packages/cli/src/commands/init/lib/setup-prompt.ts
Adds buildContextFile() and writeContextFile() to persist init metadata, readCliVersion() to resolve CLI version, and renderSetupPrompt() to generate Markdown action plans with "done" checklist and "next" tasks conditionally based on integration/handoff/install state.
Database Resolution & Schema Building
packages/cli/src/commands/init/steps/resolve-database.ts, packages/cli/src/commands/init/steps/build-schema.ts
Introduces resolveDatabaseStep to resolve DATABASE_URL once per init. Updates build-schema to derive integration from resolved URL, use multi-schema introspection, write baseline context.json immediately, and record schema provenance.
Dependency & EQL Installation
packages/cli/src/commands/init/steps/install-deps.ts, packages/cli/src/commands/init/steps/install-eql.ts
Replaces install-forge with installDepsStep to install @cipherstash/stack and stash CLI. Adds installEqlStep to prompt for and run stash db install with resolved DATABASE_URL.
Context & Agent Detection
packages/cli/src/commands/init/steps/gather-context.ts
Adds gatherContextStep to detect agents, package manager, and pre-populated env keys, emitting a one-line status and merging detected agents into init state.
Handoff Selection & Dispatch
packages/cli/src/commands/init/steps/how-to-proceed.ts
Introduces howToProceedStep with a four-option menu (Claude Code, Codex, Wizard, AGENTS.md) with detection-based hints. Dynamically selects default based on available agents and dispatches to the chosen handoff step.
Claude Code Handoff
packages/cli/src/commands/init/steps/handoff-claude.ts
Implements handoffClaudeStep to install Claude skills, write context/setup files, and launch claude binary with setup prompt or print install instructions if Claude CLI is unavailable.
Codex Handoff
packages/cli/src/commands/init/steps/handoff-codex.ts
Implements handoffCodexStep to install Codex skills, create/update AGENTS.md with doctrine block, write context file, and launch codex CLI or print install instructions if unavailable.
AGENTS.md Handoff
packages/cli/src/commands/init/steps/handoff-agents-md.ts
Implements handoffAgentsMdStep to generate AGENTS.md with inlined skills and doctrine, write .cipherstash/context.json with empty installed skills (for editor agents), and conditionally write setup prompt.
Wizard Handoff
packages/cli/src/commands/init/steps/handoff-wizard.ts
Implements handoffWizardStep to write context.json and spawn the agent wizard via runWizardSpawn().
Init Pipeline Orchestration
packages/cli/src/commands/init/index.ts
Updates STEPS array to sequence: resolve database → detect agents → build schema → install deps → install EQL → gather context → how-to-proceed (which dispatches to a handoff step).
Wizard Spawn Utility
packages/cli/src/commands/wizard/index.ts
Extracts runWizardSpawn() helper to spawn the wizard, return exit code, and allow handoff steps to reuse it.
Schema Build Refactoring
packages/cli/src/commands/schema/build.ts
Removes local introspection/type-mapping logic and delegates to shared buildSchemasFromDatabase() and generateClientFromSchemas() modules for consistency.
Packaging & Build Configuration
packages/cli/package.json, packages/cli/tsup.config.ts
Adds dist/rulebook to published files. Updates tsup onSuccess to copy bundled skills/ and doctrine/ directories into dist during build.
Documentation
packages/cli/README.md, .changeset/cli-init-agent-handoff.md
Expands CLI README with full init pipeline description and new npx stash wizard section. Documents feature in changeset.
Tests
packages/cli/src/commands/init/__tests__/\*, packages/cli/src/commands/init/lib/__tests__/\*
Adds test suites for agent detection, sentinel upsert, package installation checks, agents-md body generation, skills installation, and setup prompt rendering across multiple integration/handoff configurations.

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • stack#394 – Adds wizard subcommand export (runWizardSpawn) used by handoff-wizard and how-to-proceed steps in this PR for spawning the agent wizard.
  • stack#314 – Introduces EQL installer and CLI tooling that installEqlStep depends on for runtime extension installation via installCommand.
  • stack#336 – Overlaps with init/handoff subsystem redesign, adding complementary init steps and agent-aware flows in the same CLI codebase.

Suggested reviewers

  • calvinbrewer
  • auxesis

🐰 Hop, skip, and a prompt away!
Context and skills bundled tight,
Four handoff paths, each one just right—
Claude, Codex, Wizard, or AGENTS.
Init now does the agent dance with agents!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 74.47% 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
Title check ✅ Passed The title clearly and concisely describes the main change: adding a four-way handoff at the end of stash init to local coding agents (Claude, Codex, AGENTS.md, and the CipherStash wizard).
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

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

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.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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 35 minutes and 4 seconds.

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 2 times, most recently from b392b38 to 581831d Compare May 2, 2026 06:22
Base automatically changed from dan/stash-wizard-wrapper to main May 3, 2026 23:11
coderdan added 7 commits May 4, 2026 09:29
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.
@coderdan coderdan force-pushed the dan/init-agent-handoff branch from 583d1be to 5314d3f Compare May 3, 2026 23:30
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: 7

🧹 Nitpick comments (4)
packages/cli/src/commands/init/utils.ts (1)

255-302: ⚖️ Poor tradeoff

Consider extracting shared column-generation logic to reduce duplication.

The column generation in generateDrizzleFromSchemas (lines 260-279) is nearly identical to generateDrizzleFromSchema (lines 172-191). Similarly, generateGenericFromSchemas duplicates generateSchemaFromDef. 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 value

Add 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 text or plaintext.

📝 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 from totext (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 — writes AGENTS.md at the project root,
    then launches codex" and change it to explicitly state that the codex process
    also creates the .codex/skills/... directory and files (e.g., "writes
    AGENTS.md and the .codex/skills/ artifacts, then launches codex") 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 "the AGENTS.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 from totext (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 -->

Comment thread packages/cli/README.md Outdated
Comment thread packages/cli/src/commands/init/lib/build-agents-md.ts Outdated
Comment thread packages/cli/src/commands/init/lib/introspect.ts Outdated
Comment thread packages/cli/src/commands/init/lib/introspect.ts
Comment thread packages/cli/src/commands/init/lib/setup-prompt.ts
Comment thread packages/cli/src/commands/init/steps/handoff-agents-md.ts Outdated
Comment thread packages/cli/src/commands/init/steps/install-eql.ts Outdated
coderdan added 2 commits May 4, 2026 09:48
- 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.
Copy link
Copy Markdown
Contributor

@tobyhede tobyhede left a comment

Choose a reason for hiding this comment

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

Non-blocking feedback.

Comment thread packages/cli/src/commands/init/lib/build-agents-md.ts
Comment thread packages/cli/package.json Outdated
Comment thread packages/cli/src/commands/init/steps/handoff-claude.ts Outdated
Comment thread packages/cli/src/commands/init/steps/build-schema.ts Outdated
Comment thread .changeset/cli-init-agent-handoff.md Outdated
Comment thread packages/cli/src/commands/init/steps/build-schema.ts Outdated
Comment thread packages/cli/src/commands/init/steps/install-deps.ts Outdated
Comment thread packages/cli/src/commands/init/lib/build-agents-md.ts Outdated
Comment thread packages/cli/src/commands/init/lib/sentinel-upsert.ts
- 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.
@coderdan coderdan merged commit 9a37cb4 into main May 4, 2026
7 checks passed
@coderdan coderdan deleted the dan/init-agent-handoff branch May 4, 2026 01:06
coderdan added a commit that referenced this pull request May 4, 2026
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.
coderdan added a commit that referenced this pull request May 4, 2026
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.
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.

3 participants