Skip to content

docs(cli): refresh the bundled stash-cli skill; fix the post-cutover read path and the .env.example guard#594

Open
coderdan wants to merge 3 commits into
mainfrom
docs/stash-cli-skill-refresh
Open

docs(cli): refresh the bundled stash-cli skill; fix the post-cutover read path and the .env.example guard#594
coderdan wants to merge 3 commits into
mainfrom
docs/stash-cli-skill-refresh

Conversation

@coderdan

@coderdan coderdan commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Why

skills/stash-cli/SKILL.md had drifted from packages/cli (stash@0.17.1). The trigger was three changes it didn't reflect — the stash manifest --json registry, the non-interactive/agent interface, and the dbeql move — but the audit surfaced considerably more, including two correctness bugs (see Bug fixes below).

The framing that matters: this skill is a shipped artifact, not internal documentation.

  • packages/cli/tsup.config.ts copies /skills into dist/skills/, so it ships inside the stash npm tarball.
  • installSkills() copies it into the user's .claude/skills/ or .codex/skills/ at handoff time — for all three integrations.
  • readBundledSkill() reads only SKILL.md, strips the frontmatter, and inlines the body into the user's AGENTS.md for Cursor/Windsurf/Cline.

So a wrong command here becomes a wrong command in a customer's repo, and every line is context cost in their AGENTS.md. The rewrite goes 673 → 525 lines while adding four commands and three sections, by teaching the agent to interrogate the CLI (stash manifest --json, <cmd> --help) for exact flags and reserving prose for what those can't convey: lifecycle, safety gates, and rationale.

Bug fixes (found in review)

The post-cutover read path is not automatic. The skill said reads of <col> after encrypt cutover "decrypt transparently — no application read-path change." That is true only for CipherStash Proxy, which decrypts on the wire. For SDK users — the majority — <col> holds ciphertext after the rename swap, and unwired read paths return raw eql_v2_encrypted payloads to end users. The CLI already says exactly this in packages/cli/src/commands/init/lib/setup-prompt.ts:296. The claim was inherited from the old skill and had spread to three places, all corrected. Thanks @copilot.

The wizard's agent guard blocked .env.example. SENSITIVE_FILE_PATTERNS in packages/wizard/src/agent/interface.ts was /\.env($|\.)/, which tests true against .env.example. Since wizardCanUseTool applies that guard to Edit and Write as well as Read, the wizard's agent could not create or edit the one file the doctrine explicitly instructs it to write. Now an anchored negative lookahead:

/\.env($|\.(?!(example|sample|template)$))/

.env.example / .env.sample / .env.template are reachable. .env, .env.local, .env.production, and near-miss names that do carry values (.env.example.local, .env.example.bak) stay blocked, as do auth.json, secretkey.json and credential files. Bash remains deliberately stricter — no env file is reachable from a shell; cat isn't in the bash allowlist anyway and Read/Write is the sanctioned path, so there was no reason to widen the riskiest surface. Five new test cases. Thanks @coderabbitai and @copilot.

Scope note. This PR therefore also touches packages/wizard, and carries a second changeset. The fix is the direct consequence of the doctrine change here, so it lives with it.

What else is in it

New Start here + Authentication. All setup is driven through the CLI. Agents read stash manifest --json first, then trigger stash auth login --json and surface the verification URL for a human to approve, then run npx stash init (--supabase on Supabase).

Authenticating before init is load-bearing: init's authenticate step calls login() with no options (init/steps/authenticate.ts:59-61), so it takes the interactive path and would try to open a browser on the agent's host, and fails in non-TTY without --region.

New Never read these invariant, mirrored into AGENTS-doctrine.md: never read ~/.cipherstash/secretkey.json, ~/.cipherstash/auth.json, anything under ~/.cipherstash/workspaces/, or a value-bearing env file. .env.example is the stated exception.

Corrects the dbeql move, which was half-right. db install, db upgrade and db status are deprecated aliases that warn and forward. db push, db activate, db validate, db test-connection and db migrate genuinely remain in db.

Documents four commands the skill omitted entirely: manifest, doctor, wizard, auth regions. Plus the non-interactive interface — there is no global --non-interactive/--yes/--json; it's stdin.isTTY && CI ∉ {1,true} with a per-command escape hatch each.

Scopes db push / db activate as EQL v2 + CipherStash Proxy only (true today), in both the skill and the README flow. Structured so that when #586 lands and flips the v3 default, only the version line needs editing.

Other corrections: adds the missing --database-url, --eql-version, --prisma-next, --proxy/--no-proxy, --region flags; corrects six programmatic API signatures (loadStashConfig, loadBundledEqlSql, downloadEqlSql, PermissionCheckResult.isSuperuser, the eqlVersion option on EQLInstaller, the undocumented resolveDatabaseUrl export); repoints the three citations of closed #447 at open #585; and marks stash env as the non-functional stub it is (double-gated, and fetchProdCredentials() returns undefined unconditionally).

README fix: it claimed stash init ends in a four-option agent-handoff menu. It doesn't — init is mechanical-only and chain-prompts to stash plan; the handoff belongs to plan / impl. This README is published to npm.

Verification

Diffed against the built CLI rather than proofread.

  • Every command and all 37 flags the skill references resolve against stash manifest --json. The only two that don't (--non-interactive, --yes) appear solely in the sentence stating they don't exist.
  • stash eql install --help matches the skill's flag table line for line.
  • CI=true stash auth login --json emits exactly the documented {"status":"error","code":"region_required",...} and exits 1.
  • stash auth regions --json returns the documented {slug,label} array.
  • stash db status prints the deprecation warning and forwards to eql status.
  • stash env warns it's experimental and does nothing.
  • Delivery path: the file lands byte-identical in dist/skills/, and the frontmatter stripper yields a body starting at # CipherStash CLI with no stray ---.
  • pnpm --filter stash test — 406 passing. pnpm --filter @cipherstash/wizard test — 150 passing.
  • The wizard regex was checked against 14 path cases (blocked and allowed) before the tests were written.

The ~/.cipherstash profile layout was confirmed by listing filenames only, and the NDJSON event ordering by reading login.ts — no file under ~/.cipherstash/ was opened, and auth login was not re-run against the live device flow.

Note

Found but deliberately not fixed here: packages/cli/README.md:6 advertises "encrypted secrets management" and the stash-secrets skill description says the same, but stash secrets returns Unknown command. Handled in #595.

…I surface

The `stash-cli` skill ships inside the `stash` tarball (`dist/skills/`) and is
copied into the user's `.claude/skills/` / `.codex/skills/`, or inlined into
their `AGENTS.md`, at handoff time. A stale skill becomes stale guidance in the
user's project, so treat it as a shipped artifact rather than internal docs.

Rewritten (673 -> 521 lines) while adding four previously undocumented commands:

- New `Start here` and `Authentication` sections. Setup is driven through the
  CLI: agents read `stash manifest --json` first, then trigger
  `stash auth login --json` and surface the verification URL for a human to
  approve, then run `stash init`. Authenticating before `init` matters --
  init's auth step takes the interactive path and would otherwise try to open a
  browser on the agent's host.
- New `Never read these` invariant, mirrored into the AGENTS.md doctrine:
  never read `~/.cipherstash/secretkey.json`, `~/.cipherstash/auth.json`,
  anything under `~/.cipherstash/workspaces/`, or `.env*`. The wizard already
  blocks these paths in code (`agent/interface.ts`), but the Claude Code, Codex
  and AGENTS.md targets had no written rule.
- Documents `manifest`, `doctor`, `wizard` and `auth regions`, plus the
  non-interactive interface: per-command escape hatches (there is no global
  `--non-interactive`/`--yes`/`--json`), exit codes, the DATABASE_URL
  resolution order, and the `auth login --json` NDJSON event contract.
- Corrects the db -> eql move. `db install`, `db upgrade` and `db status` are
  deprecated aliases that warn and forward; `db push`, `db activate`,
  `db validate`, `db test-connection` and `db migrate` remain in `db`.
- Scopes `db push` / `db activate` as EQL v2 + CipherStash Proxy only, in both
  the skill and the README's recommended flow.
- Adds the missing `--database-url`, `--eql-version`, `--prisma-next`,
  `--proxy`/`--no-proxy` and `--region` flags; corrects six programmatic API
  signatures; repoints the closed #447 citations at #585; and marks `stash env`
  as the non-functional stub it currently is.

Also fixes the README's claim that `stash init` ends in a four-option agent
handoff menu -- init is mechanical-only and chain-prompts to `stash plan`; the
handoff belongs to `plan` / `impl`.

Verified by diffing every command and flag in the skill against
`stash manifest --json`, and by exercising the documented `auth login --json`,
`auth regions --json`, `db status` deprecation and `env` stub behaviours.
@coderdan coderdan requested a review from a team as a code owner July 9, 2026 06:10
@changeset-bot

changeset-bot Bot commented Jul 9, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9c673bb

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

This PR includes changesets to release 3 packages
Name Type
stash Patch
@cipherstash/wizard Patch
@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

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR refreshes documentation for the stash CLI: a changeset entry, the package README, the AGENTS doctrine secret-handling invariant, and the stash-cli skill file. Updates cover authentication ordering, non-interactive behavior, DATABASE_URL resolution, command reference details, and Proxy-only scoping for db push/db activate. No code entities changed.

Changes

stash-cli documentation refresh

Layer / File(s) Summary
Changeset entry
.changeset/stash-cli-skill-refresh.md
Adds a stash patch changeset describing the skill/README refresh scope.
README quickstart and flow
packages/cli/README.md
Updates quickstart save-point wording, four-target agent handoff, recommended flow diagram (removing db push), and clarifies db push is Proxy-only.
AGENTS doctrine invariant
packages/cli/src/commands/init/doctrine/AGENTS-doctrine.md
Expands the "never read or echo secrets" invariant to forbid reading local credential/.env* files and to direct re-running stash auth login.
SKILL.md overview, start-here, auth/non-interactive rules
skills/stash-cli/SKILL.md
Rewrites overview/triggers, adds start-here init sequence, auth safety rules, TTY/CI non-interactive gating, exit codes, and DATABASE_URL "first hit wins" resolution.
SKILL.md config and init save-point
skills/stash-cli/SKILL.md
Documents stash.config.ts resolution and the init save-point steps, including usesProxy behavior and generated files.
SKILL.md plan/impl/status lifecycle
skills/stash-cli/SKILL.md
Documents state-driven plan/impl/status behavior, deploy-gate enforcement, and rollout/cutover dual-write safety constraints.
SKILL.md command reference
skills/stash-cli/SKILL.md
Adds setup & workflow table and expands eql/db/schema/encrypt command docs, scoping db push/db activate to Proxy.
SKILL.md env stub, programmatic API, appendix
skills/stash-cli/SKILL.md
Redefines stash env as a non-working stub, refreshes programmatic API signatures/EQLInstaller docs, and updates requirements/related skills.

Estimated code review effort: 2 (Simple) | ~15 minutes

Possibly related PRs

  • cipherstash/stack#330: The README/skill docs describe stash auth login ordering matching the stash auth command implemented in this PR.
  • cipherstash/stack#446: Documentation for stash plan/stash impl --target non-TTY handoff aligns with the underlying non-interactive flow changes.
  • cipherstash/stack#448: Documentation scoping db push/db activate to Proxy-only matches the usesProxy gating logic in that PR.

Suggested reviewers: calvinbrewer, auxesis, freshtonic

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR makes db push/activate proxy-only and updates plan/impl guidance, matching #447’s requirement to stop insisting on db push for non-Proxy users.
Out of Scope Changes check ✅ Passed The README, skill, and agent-doc updates all support the CLI-surface refresh and do not introduce unrelated scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly reflects the main doc refresh and highlights two concrete behavior fixes present in the changeset.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch docs/stash-cli-skill-refresh

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

❤️ Share

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

@coderdan coderdan requested a review from Copilot July 9, 2026 06:13

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/cli/src/commands/init/doctrine/AGENTS-doctrine.md`:
- Around line 18-21: The env-file restriction in AGENTS-doctrine.md is too broad
because it bans every .env* file while also requiring placeholders in
.env.example. Update the rule so it only forbids secret-bearing runtime env
files and explicitly allows .env.example for placeholder entries; keep the rest
of the secret-handling guidance unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 01c9460d-36d1-4153-a534-267437710607

📥 Commits

Reviewing files that changed from the base of the PR and between c67169f and 1a9d190.

📒 Files selected for processing (4)
  • .changeset/stash-cli-skill-refresh.md
  • packages/cli/README.md
  • packages/cli/src/commands/init/doctrine/AGENTS-doctrine.md
  • skills/stash-cli/SKILL.md

Comment thread packages/cli/src/commands/init/doctrine/AGENTS-doctrine.md Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR refreshes the shipped stash-cli agent skill and the published CLI README to match the current stash CLI surface, with emphasis on agent/non-interactive usage, authentication flow, and safety invariants around credentials.

Changes:

  • Rewrites skills/stash-cli/SKILL.md to be manifest-driven (stash manifest --json / --help) and to document the current lifecycle (init → plan → impl → status) plus key commands/flags.
  • Updates the init doctrine (AGENTS-doctrine.md) and the CLI README to reflect the new handoff boundaries and reinforce “don’t read secrets” guidance.
  • Adds a changeset to ship the refreshed bundled skill in the stash npm package.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
skills/stash-cli/SKILL.md Major refresh of the bundled stash-cli skill to align with current CLI commands, flags, and agent workflow.
packages/cli/src/commands/init/doctrine/AGENTS-doctrine.md Extends durable agent doctrine with explicit ~/.cipherstash credential-handling restrictions.
packages/cli/README.md Corrects the quickstart narrative to place agent handoff in plan/impl (not init) and clarifies recommended flow.
.changeset/stash-cli-skill-refresh.md Ships the skill/README refresh as a stash patch release.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/cli/src/commands/init/doctrine/AGENTS-doctrine.md Outdated
Comment thread skills/stash-cli/SKILL.md Outdated
Addresses review on #594.

**The read path after `encrypt cutover` is not automatic.** The skill claimed
"Reads of `<col>` now decrypt transparently — no application read-path change."
That is only true for CipherStash Proxy, which decrypts on the wire. For SDK
users -- the majority -- `<col>` holds ciphertext after the rename swap, and
read paths must go through the encryption client or they hand raw EQL payloads
to end users. The CLI's own guidance already says so
(`init/lib/setup-prompt.ts:296`): "Post-cutover, `<col>` holds ciphertext. Read
code paths must decrypt before returning the value to callers."

The claim was inherited from the old skill, and it had propagated to three
places, all corrected here:

- `encrypt cutover` now carries an explicit callout naming `decryptModel` /
  `encryptedSupabase` / `bulkDecryptModels`, with Proxy as the stated exception.
- The `stash plan` state table described a cutover as "backfill + schema
  rename"; it is backfill, rename, read-path switch, drop.
- "Why the split exists" said the cutover ends with a "rename-swap so reads
  decrypt", which begs the question.

**`.env.example` is no longer caught by the secrets read-ban.** The doctrine
forbade reading "any `.env*` file" in the same breath as instructing agents to
add placeholder keys to `.env.example`. Narrowed to value-bearing env files,
with `.env.example` called out as the exception. Mirrored in the skill.

Verified: every command and flag still resolves against `stash manifest --json`;
406 unit tests pass.
@coderdan

coderdan commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Both review points were valid and are fixed in aad91641.

1. Post-cutover read path (@copilot) — good catch, this was the serious one

The skill claimed:

Reads of <col> now decrypt transparently — no application read-path change.

That's true only for CipherStash Proxy, which decrypts on the wire. For SDK users — the majority — <col> holds ciphertext after the rename swap, and reads must go through the encryption client. The CLI already tells agents exactly this, in packages/cli/src/commands/init/lib/setup-prompt.ts:296:

Wire the read path through the encryption client. Post-cutover, <col> holds ciphertext. Read code paths must decrypt before returning the value to callers […] Without this step, your read paths return raw eql_v2_encrypted payloads to end users.

So the skill was contradicting the CLI's own doctrine, and this is a shipped artifact that lands in customer repos. It was inherited from the old skill rather than introduced here, and it had spread to three places, all now corrected:

  • encrypt cutover — replaced with an explicit callout naming decryptModel(row, table) / encryptedSupabase / bulkDecryptModels, and stating Proxy as the one exception.
  • The stash plan state table described a cutover as "backfill + schema rename" — it is backfill, rename, read-path switch, drop (matches setup-prompt.ts:523).
  • "Why the split exists" ended with "rename-swap so reads decrypt", which begged the question.

2. .env* glob catches .env.example (@coderabbitai, @copilot)

Correct, and self-contradictory: the same paragraph told agents to put placeholder keys in .env.example. Narrowed to value-bearing env files (.env, .env.local, .env.production, …) with .env.example called out as the exception, in both AGENTS-doctrine.md and the skill.

Related, deliberately not fixed here

The wizard's runtime guard has the same over-broad match — SENSITIVE_FILE_PATTERNS in packages/wizard/src/agent/interface.ts:117 is /\.env($|\.)/, which tests true against .env.example, so a wizard-driven agent is blocked from reading it. I've left that alone: loosening a security control is not something I want to do as a drive-by in a docs PR. Worth a follow-up if it bites.

Verification

  • Every command and all flags named in the skill still resolve against stash manifest --json.
  • pnpm --filter stash test — 406 passing.
  • grep "no application read-path change" → 0 hits.

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

@coderdan Thanks for the thorough fix and the detailed writeup — appreciate you tracing the read-path claim back to setup-prompt.ts:296 and :523 and catching that it had spread to three spots. The .env* narrowing with .env.example called out as the exception makes sense too, since the previous glob was self-contradicting the guidance in the same paragraph.

Agreed on leaving SENSITIVE_FILE_PATTERNS in packages/wizard/src/agent/interface.ts alone for now — tightening a security guard shouldn't be a drive-by in a docs PR. Happy to open a follow-up issue for that if you'd like a tracking reference, otherwise I'll leave it as-is per your note.

✅ Action performed

Comments resolved. Approval is disabled; enable reviews.request_changes_workflow to allow explicit top-level @coderabbitai resolve or @coderabbitai approve commands.

Completes the review fix on #594. The doctrine now says `.env.example` is
exempt from the secrets read-ban; the wizard's runtime guard still blocked it.

`SENSITIVE_FILE_PATTERNS` matched `/\.env($|\.)/`, which tests true against
`.env.example`. Because `wizardCanUseTool` applies that guard to `Edit` and
`Write` as well as `Read`, the agent could not create or edit the one file the
doctrine explicitly instructs it to write. Committed env templates carry
placeholder key names, not values.

Narrow the rule with an anchored negative lookahead so `.env.example`,
`.env.sample` and `.env.template` are reachable, while `.env`, `.env.local`,
`.env.production` and near-miss names that do carry values
(`.env.example.local`, `.env.example.bak`) stay blocked. `auth.json`,
`secretkey.json` and credential files are untouched.

Bash remains deliberately stricter: no env file, template or not, is reachable
from a shell. `cat` isn't in the bash allowlist anyway, and Read/Write is the
sanctioned path -- so there's no reason to widen the riskiest surface. Noted
inline.

Adds five cases covering the exemption, the near-miss names, and the Bash
asymmetry. `pnpm --filter @cipherstash/wizard test` passes (150).
@coderdan coderdan changed the title docs(cli): refresh the bundled stash-cli skill against the current CLI surface docs(cli): refresh the bundled stash-cli skill; fix the post-cutover read path and the .env.example guard Jul 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants