Skip to content

Commit 5a8e6da

Browse files
committed
fix(cli): stop init/plan/impl crashing on Prisma Next; ship stash-prisma-next skill
SKILL_MAP was typed Record<Integration, ...> but omitted the 'prisma-next' key, so installSkills and the AGENTS.md builder hit SKILL_MAP['prisma-next'] = undefined and threw 'not iterable' for any repo the CLI detected as Prisma Next. tsc catches the missing key, but the build (tsup) transpiles without type-checking, so the error shipped in rc.2 — reachable by any Prisma user running stash init (Prisma Next is auto-detected). - Add the prisma-next SKILL_MAP entry. - Route both consumers through a new skillsFor() helper that degrades an unmapped integration to the base skill set (stash-encryption + stash-cli) instead of crashing — belt-and-braces for the next Integration variant, since tsup won't type-check the map. - New skills/stash-prisma-next/SKILL.md documenting the EQL v3 Prisma Next surface: domain-named column types (EncryptedTextSearch, EncryptedDoubleOrd, ...), cipherstashFromStackV3 wiring, runtime envelopes, the eql* operators, and EQL install via prisma-next migration apply (not stash eql install). - Tests: SKILL_MAP has a non-empty entry for every integration; skillsFor falls back for an unmapped one; buildAgentsMdBody('prisma-next', ...) inlines the skill (the exact call that used to crash); installSkills copies it. - Meta: AGENTS.md skill-map table + skills list updated. Companion to #655 (the @cipherstash/prisma-next EQL v3 package) — that PR releases the package but touches no CLI code, so this crash survives it. Stacked on feat/prisma-next-eql-v3; retarget to main when #655 lands. Claude-Session: https://claude.ai/code/session_01MxTTPaPP16m6br7Hoab94w
1 parent 06df347 commit 5a8e6da

7 files changed

Lines changed: 328 additions & 5 deletions

File tree

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
'stash': minor
3+
---
4+
5+
`stash init`, `stash plan`, and `stash impl` no longer crash on a Prisma Next
6+
project. `SKILL_MAP` was missing a `prisma-next` entry, so the skills-install
7+
and AGENTS.md-builder steps hit `SKILL_MAP[integration]``undefined` and threw
8+
"not iterable" for any repo the CLI detected as Prisma Next. The entry is added
9+
and both consumers now resolve skills through a `skillsFor()` helper that
10+
degrades an unmapped integration to the base skill set instead of crashing
11+
(`tsup` ships without type-checking, so the `Record<Integration>` type alone
12+
didn't protect the build).
13+
14+
Ships a new **`stash-prisma-next`** agent skill documenting the EQL v3 Prisma
15+
Next surface — the domain-named encrypted column types (`EncryptedTextSearch`,
16+
`EncryptedDoubleOrd`, …), `cipherstashFromStackV3` wiring, the runtime value
17+
envelopes, the `eql*` query operators, and EQL installation via
18+
`prisma-next migration apply`. It is installed for Prisma Next projects and
19+
inlined into `AGENTS.md` for editor agents.

AGENTS.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ If these variables are missing, tests that require live encryption will fail or
9292
- `e2e/*`: Cross-package end-to-end tests (package managers, supply chain, Prisma example README)
9393
- `examples/*`: Working apps (basic, prisma, supabase-worker)
9494
- `docs/plans/*`: Internal design plans. User-facing documentation lives at https://cipherstash.com/docs (not in this repo).
95-
- `skills/*`: Agent skills (`stash-cli`, `stash-encryption`, `stash-drizzle`, `stash-dynamodb`, `stash-supabase`, `stash-supply-chain-security`)
95+
- `skills/*`: Agent skills (`stash-cli`, `stash-encryption`, `stash-drizzle`, `stash-dynamodb`, `stash-supabase`, `stash-prisma-next`, `stash-supply-chain-security`)
9696

9797
## Agent Skills — these ship to customers
9898

@@ -115,7 +115,7 @@ nothing type-checks them, and the damage lands in a customer's repo, not ours.
115115
|---|---|
116116
| `packages/cli` commands, flags, or prompts | `skills/stash-cli` |
117117
| `packages/stack` encryption API, schema builders, subpath exports | `skills/stash-encryption` |
118-
| Drizzle / Supabase / DynamoDB integrations | `skills/stash-drizzle`, `skills/stash-supabase`, `skills/stash-dynamodb` |
118+
| Drizzle / Supabase / Prisma Next / DynamoDB integrations | `skills/stash-drizzle`, `skills/stash-supabase`, `skills/stash-prisma-next`, `skills/stash-dynamodb` |
119119
| The rollout/cutover lifecycle (`packages/migrate`, `stash encrypt *`) | `skills/stash-encryption` and `skills/stash-cli` |
120120
| pnpm config, CI workflows, dependency policy | `skills/stash-supply-chain-security` |
121121
| The durable agent rules themselves | `packages/cli/src/commands/init/doctrine/AGENTS-doctrine.md` |

packages/cli/src/commands/init/lib/__tests__/build-agents-md.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,13 @@ describe('buildAgentsMdBody', () => {
5151
expect(out).not.toContain('# Skill: stash-drizzle')
5252
expect(out).not.toContain('# Skill: stash-supabase')
5353
})
54+
55+
it('prisma-next inlines the stash-prisma-next skill (no crash on undefined map)', () => {
56+
// Regression: SKILL_MAP once lacked a prisma-next key, so this call
57+
// threw "SKILL_MAP[integration] is not iterable" for any Prisma repo.
58+
const out = buildAgentsMdBody('prisma-next', 'doctrine-plus-skills')
59+
expect(out).toContain('# Skill: stash-prisma-next')
60+
expect(out).toContain('# Skill: stash-encryption')
61+
expect(out).not.toContain('# Skill: stash-drizzle')
62+
})
5463
})

packages/cli/src/commands/init/lib/__tests__/install-skills.test.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,35 @@ import { existsSync, mkdtempSync, readFileSync, rmSync } from 'node:fs'
22
import { tmpdir } from 'node:os'
33
import { join } from 'node:path'
44
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
5+
import type { Integration } from '../../types.js'
56
import {
67
installSkills,
78
readBundledSkill,
89
SKILL_MAP,
10+
skillsFor,
911
} from '../install-skills.js'
1012

13+
// Every integration the init flow can resolve to. Kept in lockstep with the
14+
// `Integration` union and the init provider registry — a new integration added
15+
// without a SKILL_MAP entry crashes the skills install and the AGENTS.md builder
16+
// (SKILL_MAP[integration] is undefined → "not iterable"), and `tsup` ships that
17+
// without type-checking, so this list is the runtime guard tsc can't be.
18+
const ALL_INTEGRATIONS: Integration[] = [
19+
'drizzle',
20+
'supabase',
21+
'prisma-next',
22+
'postgresql',
23+
]
24+
1125
describe('SKILL_MAP', () => {
26+
it('has a non-empty entry for every integration (no undefined → crash)', () => {
27+
for (const integration of ALL_INTEGRATIONS) {
28+
const skills = SKILL_MAP[integration]
29+
expect(skills, integration).toBeDefined()
30+
expect(skills.length, integration).toBeGreaterThan(0)
31+
}
32+
})
33+
1234
it('always includes stash-encryption and stash-cli for every integration', () => {
1335
for (const [integration, skills] of Object.entries(SKILL_MAP)) {
1436
expect(skills, integration).toContain('stash-encryption')
@@ -24,9 +46,30 @@ describe('SKILL_MAP', () => {
2446
expect(SKILL_MAP.supabase).toContain('stash-supabase')
2547
})
2648

49+
it('prisma-next includes stash-prisma-next', () => {
50+
expect(SKILL_MAP['prisma-next']).toContain('stash-prisma-next')
51+
})
52+
2753
it('postgresql skips ORM-specific skills', () => {
2854
expect(SKILL_MAP.postgresql).not.toContain('stash-drizzle')
2955
expect(SKILL_MAP.postgresql).not.toContain('stash-supabase')
56+
expect(SKILL_MAP.postgresql).not.toContain('stash-prisma-next')
57+
})
58+
})
59+
60+
describe('skillsFor', () => {
61+
it('returns the mapped skills for a known integration', () => {
62+
expect(skillsFor('prisma-next')).toEqual([
63+
'stash-encryption',
64+
'stash-prisma-next',
65+
'stash-cli',
66+
])
67+
})
68+
69+
it('falls back to the base skills for an unmapped integration (never crashes)', () => {
70+
// Simulate a future Integration variant with no SKILL_MAP entry.
71+
const skills = skillsFor('mystery-orm' as Integration)
72+
expect(skills).toEqual(['stash-encryption', 'stash-cli'])
3073
})
3174
})
3275

packages/cli/src/commands/init/lib/build-agents-md.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { join } from 'node:path'
33
import * as p from '@clack/prompts'
44
import type { Integration } from '../types.js'
55
import { findBundledDir } from './bundled-paths.js'
6-
import { readBundledSkill, SKILL_MAP } from './install-skills.js'
6+
import { readBundledSkill, skillsFor } from './install-skills.js'
77

88
export type AgentsMdMode = 'doctrine-only' | 'doctrine-plus-skills'
99

@@ -41,7 +41,7 @@ export function buildAgentsMdBody(
4141

4242
if (mode === 'doctrine-plus-skills') {
4343
const skillBodies: string[] = []
44-
for (const name of SKILL_MAP[integration]) {
44+
for (const name of skillsFor(integration)) {
4545
const body = readBundledSkill(name)
4646
if (body) {
4747
skillBodies.push(`---\n\n# Skill: ${name}\n\n${stripFrontmatter(body)}`)

packages/cli/src/commands/init/lib/install-skills.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,27 @@ import { findBundledDir } from './bundled-paths.js'
1313
export const SKILL_MAP: Record<Integration, readonly string[]> = {
1414
drizzle: ['stash-encryption', 'stash-drizzle', 'stash-cli'],
1515
supabase: ['stash-encryption', 'stash-supabase', 'stash-cli'],
16+
'prisma-next': ['stash-encryption', 'stash-prisma-next', 'stash-cli'],
1617
postgresql: ['stash-encryption', 'stash-cli'],
1718
}
1819

20+
/** The skills every integration gets — the safe fallback for an unmapped one. */
21+
const BASE_SKILLS: readonly string[] = ['stash-encryption', 'stash-cli']
22+
23+
/**
24+
* Skills for an integration, resilient to an unmapped one. `SKILL_MAP` is
25+
* typed `Record<Integration, …>`, but the build (`tsup`) transpiles without
26+
* type-checking — so a new `Integration` variant added without a `SKILL_MAP`
27+
* entry would ship as `undefined` and crash both consumers (`installSkills`,
28+
* the AGENTS.md builder) with "not iterable". Degrade to the base skill set
29+
* instead: the user still gets `stash-encryption` + `stash-cli`, never a
30+
* stack trace. (Regression-guarded by a test that pins SKILL_MAP's keys to
31+
* the init provider registry.)
32+
*/
33+
export function skillsFor(integration: Integration): readonly string[] {
34+
return SKILL_MAP[integration] ?? BASE_SKILLS
35+
}
36+
1937
/**
2038
* Copy the per-integration set of skills into `<cwd>/<destDir>/<skill>/`.
2139
*
@@ -34,7 +52,7 @@ export function installSkills(
3452
destDir: string,
3553
integration: Integration,
3654
): string[] {
37-
const skills = SKILL_MAP[integration]
55+
const skills = skillsFor(integration)
3856
const bundledRoot = findBundledDir('skills')
3957
if (!bundledRoot) {
4058
p.log.warn(

0 commit comments

Comments
 (0)