Migrating from 2.x to 3.0
3.0 hardens gherkin-ai for team and CI use. Most projects need no changes: every gherkin-ai.config.json produced by 2.x versions that we tested validates unchanged. The breaking changes below matter mostly to automation and to MCP users.
Start by running:
ghk upgrade # what this project needs, as a report; asks before changing anything
ghk auth status # which provider, model and credential will be used
Assisted upgrade (ghk upgrade)
ghk upgrade checks the project against everything in this guide:
| Area | What it checks |
|---|---|
| Generated code | The oldest Generated by gherkin-ai CLI vX header under outputDir. |
| Configuration | Validates against the v3 schema and adds $schema. Also reports unknown keys and retired models. |
| CI / scripts | GitHub, GitLab, Azure Pipelines, Jenkins, CircleCI, Bitbucket, package.json, Makefile, scripts/. It finds removed login flags, ghk auth used as login, converge --strict, --apply without --allow-unattended-writes, comparisons against exit code 1, and Node.js < 22. |
| MCP clients | .mcp.json, .cursor/, .vscode/ and .windsurf/ entries that are unpinned or use a relative path. |
| Legacy files | Plain-text keys in ~/.gherkin-ai/auth.json (never printed), .gherkin-ai/audit.log, .ghe/logs/. |
With --apply, or after you confirm interactively, it:
- creates the branch
ghk-upgrade-v<version>(or the one given with--branch). It refuses to run on a dirty working tree unless you pass--force; - writes the automatic config fixes;
- regenerates every feature;
- deletes files from the previous generation that are no longer generated, but only if their checksum in
.ghe/inventory.jsonproves they were never edited. Other leftovers are listed for review;--prunedeletes them too; - with
--commit, commits on that branch.
Items marked [manual] (CI files, MCP clients, credentials) are reported with a hint and are never edited. ghk upgrade --check exits with code 4 while required changes are pending. --cli also checks the npm registry for a newer CLI. --json prints the full report.
To validate the configuration layers on their own: ghk config validate.
Credentials and login
| 2.x | 3.0 |
|---|---|
ghk login --apiKey <key> (accepted, then silently ignored) |
Removed. Use the provider's environment variable, or ghk login --provider <name>, which stores the key in the OS keychain. For scripts: --api-key-stdin. |
--token, --user, --endpoint, --server |
Removed. They produced a synthetic token and pointed at a server that does not exist. |
ghk auth = alias of login |
ghk auth status shows the effective provider and credential source. |
~/.gherkin-ai/auth.json |
No longer read for keys. The provider moves to ~/.gherkin-ai/config.json (llm.provider); a legacy provider in auth.json is still honored. You can delete the file. |
The first key found in OPENAI_API_KEY, ANTHROPIC_API_KEY, GEMINI_API_KEY, … was sent to whatever provider was selected |
Keys are resolved per provider. A selected provider without its key fails with exit code 3, instead of a warning followed by an authentication error. |
LLM configuration
- New providers:
openai-compatible,azure-openai(now implemented),bedrock,gemini(now implemented),vertex. See ENTERPRISE.md §2. - The default Anthropic model is now
claude-opus-5-5(previouslyclaude-3-opus-20240229). Pin a model withllm.modelif you need a different one. LLM_PROVIDER,LLM_MODELandLLM_BASE_URLstill work. You can also set them in any config layer underllm.- Requests now time out (
llm.timeoutMs, default 120 s). Transport retries are no longer nested, so the worst case ismaxRetries + 1attempts instead of 9. - Secrets in prompts are redacted before sending.
Exit codes and output
- Exit codes follow a fixed table (
2usage,3config,4gate,5policy,6provider,7drift). In 2.x almost every failure was1. Update CI scripts that compare against1. Most should check for non-zero instead. --jsonnow guarantees one JSON document on stdout. In 2.x it only silenced the logger. It works before or after the command name.- Errors are reported as messages with hints. Stack traces only appear with
--verbose. - The CLI no longer writes
./.ghe/logs/execution.log.jsonlon every run. Setlogging.fileto keep a log file. lintandconvergeaccept--threshold.converge --strictis accepted but deprecated, because the threshold is always enforced.
Workspace handling
--project <dir>no longer creates the directory, except forghk init. A typo now exits with code2.- In CI or with
--yes, the CLI no longer switches silently into the first sub-project it finds. Pass--project. --help,config,auth,login,stacksandmcpnever prompt or change directory.
Agent-driven writes
ghk autopilotis now a dry run by default, likeverify. Add--applyto write files.- With
--apply, bothverify --auto-fixandautopilotrefuse to run:- in CI, unless you pass
--allow-unattended-writes(or setpolicy.allowUnattendedWrites); - on
mainormaster, unless you pass--force-branch(or configurepolicy.protectedBranches).
- in CI, unless you pass
- File paths returned by the model that point outside the workspace are dropped.
- Every file goes through the agent firewall (ENTERPRISE.md §14). Files it does not ALLOW are saved as
.ghe/patches/<file>.proposedinstead of being written. This covers secrets and.env*,.ghkgovernance.yaml, an existinggherkin-ai.config.json, CI workflows, migrations, infrastructure andpackage.json, plus anything listed inpolicy.firewall. .ghkgovernance.yamlis still read, and itsallowedPathsare now enforced. In 2.x onlyprotectedPathsandrequireHumanApprovalOnwere checked, so an existing file may now block writes outsideallowedPaths. ItsprotectedPathsandrequireHumanApprovalOnfeed the firewall too. Without a policy, the whole workspace is writable except the built-in denied and approval paths.
Delivery risk
ghk quality, ghk autopilot and the MCP risk tool use the rebuilt risk engine:
- it scores the git change set, not only the specs, across six weighted dimensions with evidence;
- the fixed architecture-drift baseline of 30 is gone;
- scores and levels therefore differ from 2.x.
The scorecard keeps blastRadius, testStrength, securitySensitivity, architectureDrift, overallRiskScore, riskLevel, requiresHumanApproval and factors, and adds basis, changedFiles, dimensions and approvalThreshold. Set the approval level with policy.risk.requireApprovalAt. New command: ghk risk.
Test sandbox
verify --docker now runs in the stack's toolchain image:
- without network access by default;
- with memory, CPU and process limits;
- with all capabilities dropped and a read-only root filesystem.
Tests that download dependencies need --docker-network bridge, or sandbox.network: "bridge". The image and limits can be set in sandbox.*.
MCP server
- The server is rebuilt on
@modelcontextprotocol/sdk. Tool names are unchanged, except:run_cli_loginwas removed.run_cli_agent_logonly records actions. Userun_cli_agent_log_listto read them.run_cli_auditno longer acceptsclear.
- Read-only by default. Tools that write files need
"mcp": { "allowWrite": true }in the config.run_cli_verifyandrun_cli_autopilotadditionally needGHK_ALLOW_DESTRUCTIVE=true. Tools that are not enabled are not listed. generate_contractsis now classified as read-only. It returns contracts and does not write them.- Re-run
ghk mcp install, so the client configuration pins the package version.
Web Studio
Open the URL printed by ghk web. It contains a session token, and API calls without the token are rejected. autopilot and --apply cannot be run from the browser.
Plugins
config.plugins is validated. Both ["my-plugin"] (2.x) and { "load": ["my-plugin"] } work. When plugins.allow is set, only listed plugins load. The undocumented beforeGenerate/afterGenerate hook manager (core/hooks) was removed; use the GherkinAIPlugin interface.
Telemetry and audit
.gherkin-ai/audit.log (plain text) is replaced by .gherkin-ai/audit.jsonl (structured JSON lines). Telemetry is written for every command, locally only. Disable it with telemetry.enabled: false or GHK_TELEMETRY_DISABLED=true.
Generated code
Every stack now renders the same kernel from the feature (aggregate, unit tests, HTTP contract test where there is an API, pending BDD steps bound to the .feature file) and is built and tested in CI. Generated projects are complete, buildable projects rather than loose snippets, so regenerate into a clean directory or branch and review the diff: many paths moved.
| Stack | What changed |
|---|---|
| NestJS | NestJS 11 + Prisma 6.19 + Zod 4. Layout consolidated under src/ (src/prisma/prisma.service.ts, src/infrastructure/multitenancy/, src/sagas/). package.json declares dependencies and build/test scripts (Jest 30 + cucumber-js via tsx). Prisma schema includes OutboxMessage, ProcessedEvent, SagaInstance and tenantId. Domain queries go through prisma.tenant, a client extension that replaces the removed $use middleware. NestJS 12 (ES modules) and Prisma 7 are opt-in with stack.frameworkVersion / stack.ormVersion (ENTERPRISE.md §11). |
| Express | New dedicated preset on Express 5 (previously fell back to the React/Playwright skeleton). |
| .NET | Tests live in tests/<Namespace>.Tests/ (xUnit + Reqnroll); the root .csproj excludes tests/**, features/** and the Aspire AppHost, uses Npgsql.EntityFrameworkCore.PostgreSQL and adds RootNamespace. Integration tests are tagged Category=Integration. |
| Java | Maven project (pom.xml, Spring Boot 3.3) with sources under src/main/java/com/example/<project>/; one public type per file. |
| Kotlin | New Gradle (KTS) project; previously contracts only. |
| Python | FastAPI and Django (DRF) are separate presets with pyproject.toml, pytest and pytest-bdd. |
| Go | Go module with internal/ packages, chi router, godog; contracts moved to internal/contracts/. |
| PHP | Laravel 13 (PHP ≥ 8.3, PHPUnit 12, Behat); Laravel 11 is blocked by Composer security advisories. Contracts moved to contracts/. |
| Ruby | Rails 8 API with RSpec and Cucumber. |
| Elixir | Phoenix project (Bandit, ExUnit); LiveView with frontendStack.framework: "phoenix-liveview". |
| Rust | Axum 0.7 crate (requires a recent stable toolchain). |
| Frontends | Generated as a separate project in ./frontend: React 19 + Redux Toolkit, Vue 3 + Pinia, Angular 22 (zoneless, NgRx Signals; the classic NgRx store mode was removed), Next.js 16, React Native (Expo 57), Flutter. |
| Contracts | gRPC and GraphQL are opt-in: "contracts": { "grpc": true, "graphql": true }. |
Runtime kernel. Every backend also gets the runtime kernel (RUNTIME-KERNEL.md):
- PostgreSQL persistence with idempotent commands and an outbox;
- a RabbitMQ relay and an inbox consumer with a dead-letter queue;
- sagas;
- OpenTelemetry trace propagation.
Its integration tests are excluded from the default test command and run with the stack's integration command, for example npm run test:integration or mvn verify -Pintegration, given DATABASE_URL and AMQP_URL.
Frontend API clients send a W3C traceparent header. Pass traceparent: false (Dart: traceparent: () => null) to disable it, or a function to propagate an existing trace. Cross-origin deployments must allow the header in CORS.
AWS CDK (./infrastructure) is generated when infrastructure.awsCdk is true, or by default when stack.messaging is SQS or SNS.
Identifiers are derived from the feature name with diacritics removed (Creación de Pedidos → CreacionDePedidos). To choose a shorter name, tag the feature with @aggregate:Pedido (or @aggregate(Pedido)); every generator then emits PedidoAggregate, CreatePedidoCommand, pedido.aggregate.ts and so on. Localized features (# language: es) are parsed as Gherkin. Command names come from identifiers quoted in When steps ("CreateOrderCommand" → CreateOrder) or, for English features, from the step's verb; otherwise a single Process<Feature> command is generated.
Generated toolchains target currently supported runtimes: Node 24 for frontends, .NET 8, Java 17, Python 3.12, Go 1.22, PHP 8.3, Ruby 3.3, Elixir 1.17. The CLI itself requires Node.js 22.12 or later (Node 20 is end-of-life).