feat(cli): offer agent skills install once on prisma generate - #29690
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds a shared 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify code
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. Comment |
All three S2 dispatches satisfied; scoreboard 5 PASS + 1 accepted deferral. Live pty evidence in the slice verification record. One session-limit stall (D15) retried cleanly.
size-limit report 📦
|
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 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/utils/prompt-timeout.ts`:
- Around line 5-16: The timeout helper in timeout() currently only handles the
resolved path, so rejected prompt promises are lost and can leave the outer
Promise pending. Update timeout() to forward both success and failure from the
input promise by adding rejection handling to the promise.then chain (or
equivalent) and clearing the timeout in both cases, using the timeoutId within
timeout() so promise rejections properly propagate to callers.
In `@packages/cli/src/utils/skills/skills-offer.ts`:
- Around line 145-166: The unguarded await in promptForInstall is affected by
the shared timeout() rejection behavior, so fix the underlying timeout helper
rather than adding a separate catch here. Make timeout() resolve with undefined
on timeout/rejection so promptForInstall can keep handling the existing answer
=== undefined path and return 'timeout' instead of bubbling to the top-level
catch.
- Around line 119-143: The install path in handleSkillsOfferImpl is not isolated
like submitOfferEvent, so a thrown ctx.installSkills error can escape and make
handleSkillsOffer report prompted:false after the prompt already showed. Wrap
the ctx.installSkills({ cwd: ctx.cwd }) call in its own try/catch inside
handleSkillsOfferImpl, log the failure similarly to the telemetry block, and
continue on to submitOfferEvent so the function still returns { prompted: true }
even when installation fails.
- Around line 222-231: The CLI version lookup is duplicated in getCliVersion and
the Generate entrypoint, so extract the esbuild-specific package.json read into
one shared helper and have both call it instead of using their own
eval(require('../package.json')) workaround. Move the version-resolution logic
into a single reusable utility with a clear name, keep the fallback to unknown
and debug logging there, and update the existing callers to use that helper so
the special-case behavior lives in one place.
🪄 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: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 10fdcbfc-94fd-4699-8ed7-c2a4fcc8041b
📒 Files selected for processing (6)
packages/cli/src/Generate.tspackages/cli/src/__tests__/commands/Generate.test.tspackages/cli/src/__tests__/skills-offer.vitest.tspackages/cli/src/utils/nps/survey.tspackages/cli/src/utils/prompt-timeout.tspackages/cli/src/utils/skills/skills-offer.ts
Merging this PR will regress 1 benchmark
Warning Please fix the performance issues or acknowledge them on CodSpeed. Performance Changes
Tip Investigate this regression by commenting Comparing Footnotes
|
b7edbaa to
bbd64a2
Compare
275ec9f to
cf04278
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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/__tests__/nps.test.ts`:
- Around line 312-331: Add a regression test alongside the existing
prompt-outcome tests that configures prompts.text to resolve with an answered
value of "0", invokes handleNpsSurveyImpl, and verifies the zero rating is
captured and the expected survey completion behavior occurs. Keep the test
focused on the falsy rating boundary and match the existing test setup and
assertions.
In `@packages/cli/src/utils/nps/survey.ts`:
- Around line 79-87: Update the rating check in the survey flow around
collectFeedback so a valid rating of 0 is submitted and acknowledged like other
ratings. Check for absence explicitly rather than relying on truthiness, while
preserving the existing submitSurveyEvent, thank-you message, and writeConfig
behavior.
- Around line 116-118: Update the feedbackAnswer prompts.text call to include a
timeoutMs derived from the existing promptTimeoutSecs value, matching the
deadline used by the rating prompt and preventing the follow-up survey prompt
from waiting indefinitely.
🪄 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: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 38916f32-83e1-47f5-9c59-477c7b3e2ebe
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (6)
packages/cli/package.jsonpackages/cli/src/__tests__/nps.test.tspackages/cli/src/__tests__/skills-offer.vitest.tspackages/cli/src/utils/nps/survey.tspackages/cli/src/utils/prompts.tspackages/cli/src/utils/skills/skills-offer.ts
Add handleSkillsOffer(), which offers to install the Prisma agent skills once ever per machine. The offer only fires when no skills-offer.json acknowledgement exists in the prisma config directory, the project has no Prisma skills installed already (that case writes an already-installed acknowledgement so the project is never scanned again), the session is interactive, not Deno, not CI, not a git or npm lifecycle hook, not a container, and the first CLI command was issued at least a day ago. The prompt defaults to No and times out after 30 seconds; the outcome (accepted, declined, timeout, already-installed) is persisted unconditionally and reported through a skills_offer_resolved PostHog event only when a prompt was actually shown. Accepting runs the skill-install runner with the same non-fatal manual-command warning as prisma init. The offer never throws, and the acknowledgement write and telemetry submission are additionally guarded on their own so that a read-only config dir or an offline telemetry endpoint cannot misreport a shown prompt as not shown (the Generate caller uses that flag for mutual exclusion with the NPS survey). The timeout() helper moves from the NPS survey to a shared prompt-timeout module so both prompts use the same implementation.
Wire the one-time skills offer into the non-watch generate success path, in the same block as the NPS survey and before it. When the offer actually prompts, the survey is skipped for that run so a single generate never shows two prompts; when it does not prompt, the survey runs exactly as before. Watch mode and --no-hints continue to suppress both. The offer handler is a constructor parameter defaulting to handleSkillsOffer, mirroring the existing surveyHandler injection.
The init-time install copies skills into .claude/skills/, .windsurf/skills/, and .agents/skills/, but the generate-time offer's already-installed scan only checked two of the three directories plus skills-lock.json. A project where only the Windsurf copy survives (e.g. the other directories were removed) would be re-offered the install. The scan now covers all three per-agent directories. Addresses a review finding on the initial PR.
timeout() swallowed rejections and resolved to undefined, which hid stdin failures from every caller and made a broken stream indistinguishable from a genuine timeout. Reject with the original error instead, and clear the timer on both paths so a settled prompt never leaves a timer holding the event loop open. The skills offer keeps treating an unreadable answer as no answer, so the offer is still recorded as resolved and generate never asks twice. The NPS survey now aborts on a prompt failure instead of writing an acknowledgement for a survey the user never saw. Also distinguish ENOENT/ENOTDIR from unexpected errors when probing for the acknowledgement file and the installed skill directories, so a permission error is no longer silently read as "not installed".
Release builds replace the version in package.json before pnpm build, so the version can be imported directly instead of being read through an eval wrapper at runtime. This drops the get-cli-version module along with its 'unknown' fallback, which no longer has a way to trigger, and matches how MCP.ts already reads the version.
The guard dates to #27133, where merely creating a readline interface on Deno kept `prisma generate` alive until Enter was pressed. Verified against Deno 2.9.3: creating and closing an interface, and the full question-with- timeout path, both exit cleanly and behave exactly as they do on Node, so the prompts no longer need to be skipped there.
The skills offer asked for "y" followed by Enter, which is a clumsy way to answer a yes/no question. clack's confirm submits on the y/n keypress itself, so accepting or declining is now a single key. Its prompts also take an AbortSignal, which replaces the timeout helper that raced a promise against a timer: the deadline now cancels the prompt and restores the terminal instead of walking away from a prompt still holding stdin. An aborted signal also tells a lapsed deadline apart from a user dismissing the prompt, so Ctrl+C and Esc are recorded as declining rather than as a timeout. Both prompts move behind a small Prompts interface so callers stay testable without a TTY. The NPS survey loses its readline lifecycle and the proxy that guarded against a closed stream, both of which clack owns per prompt. @clack/prompts is ESM-only and goes in devDependencies, following the convention that bundles dev dependencies into the CJS CLI build.
`if (result.rating)` treated a rating of 0 as no rating at all, so the most negative score the survey can collect was dropped: no event was captured and the user was not thanked, while the timeframe was still acknowledged so the survey never asked again. Check for an answer rather than a truthy one. The follow-up feedback prompt also had no deadline of its own and would wait indefinitely once the rating was in. It now carries the same 30s deadline as the rating prompt; a rating already given is still submitted when the follow-up lapses.
…solves The offer is shown once ever per machine, so that single conversation is also the only chance to tell the user how to install the skills in other projects. Every prompted outcome now ends with the manual install command: after a successful install it points at other projects, and after a decline, dismissal, or timeout it doubles as the way to opt back in. The failed-install warning already carries the command and stays as is. The command comes from a new manualInstallCommand export in skill-install, sharing the runner detection used by the install itself, so it renders as npx/pnpm dlx/yarn dlx/bunx to match the project at hand.
The already-installed branch wrote the machine-global acknowledgement, so the first generate run in any project that already had the skills (e.g. one set up by prisma init) silently settled the offer for every project on the machine, including ones with no skills at all. Detection is per project while the acknowledgement is per machine; conflating them spent the one prompt on a project that never saw it. The branch now stays silent without recording anything: projects with the skills are skipped on every run via detection, and the acknowledgement is only ever written for a prompt that was actually shown. The 'already-installed' outcome disappears from SkillsOfferOutcome along with its only writer.
8dc3ce1 to
7446b2e
Compare
…#29690) > **Stacked PR** — based on prisma#29689 (S1, the skill-install runner); will be retargeted to `main` once prisma#29689 merges. The diff here is only S2's two commits. Existing projects (which never ran the new `prisma init`) get exactly one polite, interactive, time-limited offer to install the [prisma/skills](https://github.com/prisma/skills) catalog, shown after a successful `prisma generate`. Second slice (S2) of the Agent-Native Prisma ORM project (prisma#29688). ## Changes - **Offer module** (`packages/cli/src/utils/skills/skills-offer.ts`): `handleSkillsOffer()` runs the full NPS-style gate chain — prior acknowledgement, already-installed detection, interactive TTY, Deno guard, CI, git hook, npm lifecycle hook, container, and "first Prisma command ≥ 1 day ago" — then asks one yes/no question with a 30-second timeout defaulting to No. Every outcome (`accepted` / `declined` / `timeout` / `already-installed`) persists `skills-offer.json` in the OS config dir, so the offer fires **once ever per machine** (unlike the NPS survey's once-per-timeframe). Accepting runs S1's pinned skill-install runner; failure prints the manual command, non-fatally. The whole body never throws — an offer failure can never fail `generate`. - **Generate wiring** (`packages/cli/src/Generate.ts`): the offer runs in the existing `!hideHints` non-watch success block, before the NPS survey; if the offer prompted, the survey is skipped for that run — at most one prompt per generate. Injectable handler mirroring the existing `surveyHandler` seam. - **Shared helper** (`packages/cli/src/utils/prompt-timeout.ts`): the `timeout()` racer extracted verbatim from `nps/survey.ts`; both prompts now share it. - **Telemetry**: a single `skills_offer_resolved` `{ outcome, cliVersion }` event through the existing PostHog capture path, only when a prompt was actually shown. - **Tests**: 27 unit tests for the module (every gate short-circuit, input variants, fake-timer timeout, four failure-isolation cases) + 4 Generate-level tests (ordering, mutual exclusion both ways, `--no-hints` and watch suppression). Injection over module mocking throughout. ## Why - **Once-ever, not once-per-timeframe**: a declined offer should never nag again; `prisma init` (S1) and docs remain the discovery paths. - **Acknowledgement only after a prompt resolves**: gated-out runs (CI, non-TTY) write nothing, so a developer whose first runs are in CI still gets the offer later on a real TTY. - **Ordering inside the resolution path** (acknowledge → install → telemetry) makes once-ever survive a crash mid-install and keeps an offline telemetry endpoint from robbing an accepting user of the install. - **Mutual exclusion with NPS** keeps the post-generate moment to a single question, ever. ## Verification (live pty runs, built CLI, isolated config dirs) - First interactive run: offer shown; `n` persists `outcome: "declined"`; second run silent. - Accept run: skills land via the S1 runner (24 SKILL.md across `.claude`/`.windsurf`/`.agents` + `skills-lock.json`); `outcome: "accepted"`. - Non-TTY run: no prompt, no acknowledgement written, generate output normal. - In prompted runs the offer was the only prompt (NPS skipped; mechanism also pinned by unit tests). Full captures: `projects/agent-native/slices/generate-skill-offer/verification.md` on the project branch (prisma#29688). **Linear:** [TML-2971](https://linear.app/prisma-company/issue/TML-2971/s2-one-time-skill-offer-on-prisma-generate)
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [@prisma/adapter-pg](https://github.com/prisma/prisma) ([source](https://github.com/prisma/prisma/tree/HEAD/packages/adapter-pg)) | imports | minor | [`7.9.1` -> `7.10.0`](https://renovatebot.com/diffs/npm/@prisma%2fadapter-pg/7.9.1/7.10.0) | | [@prisma/client](https://www.prisma.io) ([source](https://github.com/prisma/prisma/tree/HEAD/packages/client)) | imports | minor | [`7.9.1` -> `7.10.0`](https://renovatebot.com/diffs/npm/@prisma%2fclient/7.9.1/7.10.0) | --- ### Release Notes <details> <summary>prisma/prisma (@​prisma/adapter-pg)</summary> ### [`v7.10.0`](https://github.com/prisma/orm/releases/tag/7.10.0) [Compare Source](prisma/orm@7.9.1...7.10.0) ##### Prisma ORM 7.10.0 Prisma ORM 7.10.0 introduces a compatibility package for running Prisma 7 alongside newer Prisma versions, secures Prisma Studio's local server, and includes fixes across Prisma Client and the PostgreSQL, MariaDB, Neon, SQLite, and Prisma Postgres Serverless adapters. ##### Highlights ##### Run Prisma 7 alongside Prisma 8 This release introduces `@prisma/prisma7`, a compatibility package that lets you retain a matching Prisma 7 CLI and configuration while installing Prisma 8 in the same project. Once 7.10.0 is released, a side-by-side installation can use: ```sh npm install --save-dev prisma@8 @prisma/prisma7@7.10.0 npm install @prisma/client@7.10.0 ``` Use `prisma` for the directly installed Prisma 8 CLI and `prisma7` for Prisma 7: ```sh npx prisma --version npx prisma7 --version npx prisma7 generate npx prisma7 migrate dev npx prisma7 db push ``` Prisma 7 now prefers version-specific configuration files, allowing its configuration to coexist with Prisma 8's `prisma.config.*` files: ```ts // prisma7.config.ts import { defineConfig } from '@prisma/prisma7/config' export default defineConfig({ schema: 'prisma/schema.prisma', migrations: { path: 'prisma/migrations', }, }) ``` Without an explicit `--config` option, Prisma 7 searches for: 1. Root-level `prisma7.config.*` files. 2. `.config/prisma7.*` files. 3. Existing `prisma.config.*` files as a backwards-compatible fallback. The supported extensions are `.js`, `.ts`, `.mjs`, `.cjs`, `.mts`, and `.cts`. An explicit config path always takes precedence: ```sh npx prisma7 generate --config ./custom/prisma7.config.ts ``` New projects initialized by the Prisma 7 CLI use `prisma7.config.ts`. Existing projects containing only `prisma.config.*` continue to work without migration or additional warnings. If a `prisma7.config.*` file exists but cannot be loaded, Prisma reports the error rather than silently falling back to another configuration. The `prisma7` identity is carried through CLI help, version output, shell completion, initialization, migration, database, and generation guidance. Stable Prisma concepts such as `schema.prisma`, Prisma Migrate, `@prisma/client`, and `PRISMA_*` environment variables remain unchanged. Together, the separate executable and configuration namespace make it possible to operate Prisma 7 and Prisma 8 side by side without command or config-file collisions. [#​29949](prisma/orm#29949), [#​29969](prisma/orm#29969), [#​29994](prisma/orm#29994), [#​30000](prisma/orm#30000), [#​30002](prisma/orm#30002), [#​30020](prisma/orm#30020) ##### Prisma Studio security hardening Prisma Studio's local HTTP server now: - Binds explicitly to `127.0.0.1` instead of all network interfaces. - Rejects browser requests from origins other than the active `localhost` or `127.0.0.1` Studio URL. - No longer returns wildcard CORS headers. - Applies the same protections across Node.js, Bun, and Deno. This prevents network clients or malicious websites from accessing Studio's database endpoints while Studio is running. [#​29890](prisma/orm#29890) ##### Prisma Client - Fixed `P2002` errors from nested writes so `meta.modelName` identifies the model where the unique constraint violation occurred, including models using `@@map` and `@@schema`. [#​29628](prisma/orm#29628) - Fixed automatically batched `findUniqueOrThrow()` calls so every missing record rejects with `P2025`; later misses no longer resolve to `undefined`. [#​29654](prisma/orm#29654) - Parameter-chunked statements are now executed atomically in a transaction and rolled back if a later chunk fails. [#​29771](prisma/orm#29771) - Improved interactive transaction cleanup during `$disconnect()`, including transactions whose driver-level startup is still in progress. [#​28768](prisma/orm#28768) - Prevented transaction cleanup failures after a timeout or backend termination from becoming unhandled promise rejections. [#​29611](prisma/orm#29611) - Fixed fluent relation queries when relation fields are literally named `select` or `include`. [#​29683](prisma/orm#29683) - Fixed handling of `Date` and `Uint8Array` values created in other JavaScript realms, such as iframes, jsdom, and Node.js `vm` contexts. [#​29177](prisma/orm#29177) - Invalid `Date` values passed to `$queryRaw` or `$executeRaw` now throw `PrismaClientValidationError` instead of a generic error. [#​29718](prisma/orm#29718) - Fixed `moduleFormat` inference for the `prisma-client` generator in TypeScript projects using `module: "node16"` or `"nodenext"`. Generated output now follows the nearest `package.json` `type`, defaulting to CommonJS when absent. [#​29712](prisma/orm#29712) - Deserialized `Bytes` values now own standalone `ArrayBuffer`s rather than exposing unrelated contents from Node.js's shared `Buffer` pool. This applies to both regular and raw query results. [#​29701](prisma/orm#29701) - Fixed an incorrect logging context in the remote executor, including Accelerate-backed query execution. [#​28892](prisma/orm#28892) ##### Client extensions and observability - Result-extension `compute` callbacks now receive the current model name as a typed second argument: ```ts compute(data, modelName) { // ... } ``` The model name is also preserved when multiple extensions compose the same computed field. [#​29782](prisma/orm#29782) - Improved OpenTelemetry context for remotely executed queries: - `$on('query')` callbacks run within the matching `db_query` span. - Events from one operation share the same trace. - Error events are recorded as span exceptions. - Log events continue to be emitted when tracing is disabled or their reported span is unavailable. [#​28892](prisma/orm#28892) ##### Driver adapters ##### MariaDB - `@prisma/adapter-mariadb` now accepts an existing `mariadb` pool. External pools remain caller-owned unless `disposeExternalPool: true` is supplied. [#​27992](prisma/orm#27992) - Fixed pooled connection leaks during commit, rollback, and failed transaction startup. Connections are now returned with `release()` and transaction-specific listeners are removed before reuse. [#​29612](prisma/orm#29612) - Added support for bracketed IPv6 addresses in both `mysql://` and `mariadb://` connection strings. [#​29026](prisma/orm#29026) - Prevented malformed connection strings from exposing embedded passwords in retained debug output and diagnostic reports. [#​27992](prisma/orm#27992) ##### PostgreSQL, Neon, and Prisma Postgres Serverless - PostgreSQL deadlocks using SQLSTATE `40P01` are now reported as `P2034` transaction write conflicts. [#​29717](prisma/orm#29717) - PostgreSQL `RESTRICT` violations using SQLSTATE `23001` are now reported as `P2003`, preserving an available field or constraint name. [#​29554](prisma/orm#29554) - `@prisma/adapter-pg` now preserves database constraint names when reporting unique constraint violations through `P2002`. [#​29587](prisma/orm#29587) - Prisma Postgres Serverless now prefers the named constraint for `P2002`, falling back to parsed field names when no constraint name is available. [#​29801](prisma/orm#29801) - Fixed Neon HTTP adapter serialization for typed parameters such as `Bytes` and `DateTime`. [#​29747](prisma/orm#29747) ##### SQLite - `@prisma/adapter-better-sqlite3` now converts previously unhandled SQLite result codes into typed database errors instead of exposing raw driver errors. - The complete `SQLITE_BUSY` family is now mapped to socket timeout errors, with numeric extended result codes preserved where available. [#​29794](prisma/orm#29794) ##### CLI and Migrate - `prisma generate` can now offer to install Prisma's agent skills. The opt-in prompt: - Is shown at most once per machine. - Is skipped in CI, containers, Git hooks, npm lifecycle scripts, and watch mode. - Is skipped when `--no-hints` is used or Prisma skills are already installed. - Times out after 30 seconds. - Never causes generation to fail if installation is unsuccessful. [#​29690](prisma/orm#29690) - A globally installed CLI now warns during `prisma generate` when its version differs from the project's local `prisma` or `@prisma/client`, and recommends running the local CLI. The check is best-effort and does not fail generation. [#​29593](prisma/orm#29593) - `prisma version` and `prisma version --json` now include the resolved Prisma CLI package path, making global-versus-local installation issues easier to diagnose. [#​29573](prisma/orm#29573) - Empty or generator-only schema files now report `Schema must contain a datasource block` from `db pull`, `db push`, and `migrate dev`, rather than reaching the schema engine and potentially producing inconsistent errors. [#​29657](prisma/orm#29657) - CLI commands now tolerate corrupt, unreadable, or unwritable command-state files. Invalid state is reinitialized, writes are atomic, and persistence failures fall back to in-memory state. [#​29609](prisma/orm#29609) - Studio now recognizes semicolon-delimited `sqlserver://` connection strings before reporting the existing explicit message that SQL Server is not supported by Studio. [#​29623](prisma/orm#29623) - The AI-agent safety checkpoint now also covers interactive `prisma db push` confirmations involving data-loss warnings, rather than only invocations using `--accept-data-loss`. [#​29793](prisma/orm#29793) ##### Performance and reliability - Optimized query-plan execution by eagerly evaluating plans with one unconditional database operation and synchronously interpreting the remaining pure plan. Cached plans remain immutable. [#​29004](prisma/orm#29004) - Prevented call-stack overflows when rendering very large parameter lists or combining chunked results containing hundreds of thousands of rows. [#​29751](prisma/orm#29751) - Reduced ordinary query setup overhead by constructing fluent-relation field maps lazily and in linear time. Non-fluent queries no longer build this map. [#​29752](prisma/orm#29752) ##### Dependencies - Updated the transitive `fast-uri` dependency to a patched release addressing production audit advisories affecting versions through `3.1.3`. [#​29758](prisma/orm#29758) </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about these updates again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4zMC4zIiwidXBkYXRlZEluVmVyIjoiNDQuMzAuMyIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==--> Reviewed-on: https://git.oirnoir.dev/OIRNOIR/YouTube-Helper-Server/pulls/41
Existing projects (which never ran the new
prisma init) get exactly one polite, interactive, time-limited offer to install the prisma/skills catalog, shown after a successfulprisma generate. Second slice (S2) of the Agent-Native Prisma ORM project (#29688).Changes
packages/cli/src/utils/skills/skills-offer.ts):handleSkillsOffer()runs the full NPS-style gate chain — prior acknowledgement, already-installed detection, interactive TTY, Deno guard, CI, git hook, npm lifecycle hook, container, and "first Prisma command ≥ 1 day ago" — then asks one yes/no question with a 30-second timeout defaulting to No. Every outcome (accepted/declined/timeout/already-installed) persistsskills-offer.jsonin the OS config dir, so the offer fires once ever per machine (unlike the NPS survey's once-per-timeframe). Accepting runs S1's pinned skill-install runner; failure prints the manual command, non-fatally. The whole body never throws — an offer failure can never failgenerate.packages/cli/src/Generate.ts): the offer runs in the existing!hideHintsnon-watch success block, before the NPS survey; if the offer prompted, the survey is skipped for that run — at most one prompt per generate. Injectable handler mirroring the existingsurveyHandlerseam.packages/cli/src/utils/prompt-timeout.ts): thetimeout()racer extracted verbatim fromnps/survey.ts; both prompts now share it.skills_offer_resolved{ outcome, cliVersion }event through the existing PostHog capture path, only when a prompt was actually shown.--no-hintsand watch suppression). Injection over module mocking throughout.Why
prisma init(S1) and docs remain the discovery paths.Verification (live pty runs, built CLI, isolated config dirs)
npersistsoutcome: "declined"; second run silent..claude/.windsurf/.agents+skills-lock.json);outcome: "accepted".Full captures:
projects/agent-native/slices/generate-skill-offer/verification.mdon the project branch (#29688).Linear: TML-2971