@workos/oagen 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/.claude-plugin/plugin.json +12 -0
  2. package/LICENSE.txt +21 -0
  3. package/README.md +197 -0
  4. package/dist/chunk-CIX5NOBF.js +263 -0
  5. package/dist/chunk-IJ5E5AE7.js +1040 -0
  6. package/dist/chunk-P5XUDUGY.js +1748 -0
  7. package/dist/cli/index.js +5603 -0
  8. package/dist/compat/index.d.ts +195 -0
  9. package/dist/compat/index.js +4453 -0
  10. package/dist/index.d.ts +300 -0
  11. package/dist/index.js +1733 -0
  12. package/dist/scripts/smoke/baseline.js +1751 -0
  13. package/dist/scripts/smoke/sdk-test.js +93 -0
  14. package/dist/smoke.js +1963 -0
  15. package/dist/types-B5t9O9Wm.d.ts +367 -0
  16. package/dist/verify/index.d.ts +76 -0
  17. package/dist/verify/index.js +336 -0
  18. package/docs/advanced/index.md +11 -0
  19. package/docs/advanced/oagen-ignore.md +144 -0
  20. package/docs/agents/architecture.md +53 -0
  21. package/docs/agents/emitters.md +69 -0
  22. package/docs/agents/testing.md +60 -0
  23. package/docs/architecture/dependency-layers.md +75 -0
  24. package/docs/architecture/emitter-contract.md +160 -0
  25. package/docs/architecture/extractor-contract.md +227 -0
  26. package/docs/architecture/ir-types.md +275 -0
  27. package/docs/architecture/non-additive-changes.md +76 -0
  28. package/docs/architecture/pipeline.md +110 -0
  29. package/docs/architecture/workflows.md +215 -0
  30. package/docs/cli.md +156 -0
  31. package/docs/contributor/index.md +10 -0
  32. package/docs/contributor/public-api.md +59 -0
  33. package/docs/contributor/versioning.md +48 -0
  34. package/docs/core/index.md +12 -0
  35. package/docs/core/minimal-emitter.md +134 -0
  36. package/docs/core/quickstart.md +142 -0
  37. package/docs/diagrams/generate-sdk-workflow.md +50 -0
  38. package/docs/index.md +40 -0
  39. package/examples/reference-emitter/README.md +41 -0
  40. package/examples/reference-emitter/oagen.config.ts +7 -0
  41. package/examples/reference-emitter/spec/github-subset.yml +400 -0
  42. package/examples/reference-emitter/src/emitter.ts +52 -0
  43. package/examples/reference-emitter/src/generators/client.ts +37 -0
  44. package/examples/reference-emitter/src/generators/config.ts +19 -0
  45. package/examples/reference-emitter/src/generators/enums.ts +16 -0
  46. package/examples/reference-emitter/src/generators/errors.ts +39 -0
  47. package/examples/reference-emitter/src/generators/models.ts +29 -0
  48. package/examples/reference-emitter/src/generators/resources.ts +60 -0
  49. package/examples/reference-emitter/src/generators/tests.ts +7 -0
  50. package/examples/reference-emitter/src/index.ts +1 -0
  51. package/examples/reference-emitter/src/naming.ts +21 -0
  52. package/examples/reference-emitter/src/type-mapper.ts +39 -0
  53. package/examples/reference-emitter/tsconfig.json +19 -0
  54. package/package.json +102 -0
  55. package/skills/generate-emitter/SKILL.md +393 -0
  56. package/skills/generate-emitter/references/common-pitfalls.md +23 -0
  57. package/skills/generate-emitter/references/generator-guide.md +201 -0
  58. package/skills/generate-emitter/references/project-scaffold.md +138 -0
  59. package/skills/generate-extractor/SKILL.md +200 -0
  60. package/skills/generate-sdk/SKILL.md +155 -0
  61. package/skills/generate-smoke-test/SKILL.md +185 -0
  62. package/skills/integrate/SKILL.md +108 -0
  63. package/skills/verify-compat/SKILL.md +151 -0
  64. package/skills/verify-smoke-test/SKILL.md +176 -0
@@ -0,0 +1,155 @@
1
+ ---
2
+ name: generate-sdk
3
+ description: Orchestrate generating an SDK for a target language end-to-end. Determines the right scenario (backwards-compatible or fresh) and guides through the correct sequence of sub-skills. Use when the user wants to generate an SDK, add a new language, create SDK bindings, start a new language target, or asks "how do I add X support". Also triggers for "new SDK", "language support", or "scaffold SDK".
4
+ ---
5
+
6
+ # /generate-sdk
7
+
8
+ ## Overview
9
+
10
+ Orchestrate the end-to-end workflow for generating an SDK for a target language. This skill does not implement anything itself — it determines the right scenario, sequences the correct skills, and tracks progress across steps.
11
+
12
+ ## Inputs
13
+
14
+ - **language** — target language (e.g., `ruby`, `python`, `node`, `go`)
15
+ - **scenario** — `A` (backwards-compatible with existing SDK) or `B` (fresh, no existing SDK)
16
+ - **project** — path to the emitter project
17
+ - **spec** — path to the OpenAPI spec
18
+ - **sdk_path** — path to the live SDK (Scenario A only)
19
+
20
+ ## Reference Docs
21
+
22
+ - [Workflows](../../docs/architecture/workflows.md) — Phase 1 setup vs. Phase 2 ongoing spec updates
23
+ - [Pipeline](../../docs/architecture/pipeline.md) — three-stage parse/emit/write flow
24
+
25
+ ## Architecture
26
+
27
+ Emitters, extractors, smoke tests, and their unit tests all live in the **emitter project**, not in the oagen core repo. The emitter project depends on `@workos/oagen` for types and shared utilities.
28
+
29
+ ## Context Management
30
+
31
+ Sub-skills use **subagents** (via the built-in `Explore` agent type) to keep the main context lean. Read-heavy exploration — studying existing SDKs, reading reference implementations — is delegated to isolated agents that return only structured summaries. This prevents context rot during the write-heavy generation steps that follow.
32
+
33
+ Each sub-skill documents where it uses subagents:
34
+
35
+ - `/generate-emitter` — Step 1a (SDK exploration) and Steps 3-5 (reference emitter reading)
36
+ - `/generate-extractor` — Prerequisites (SDK exploration)
37
+ - `/generate-smoke-test` — Prerequisites (reference smoke script reading) and Step 2 (SDK SERVICE_MAP)
38
+ - `/verify-compat` — Step 1 (baseline extraction spot-check)
39
+
40
+ ## Step 1: Gather Inputs
41
+
42
+ Collect these values via `AskUserQuestion`. If any were provided as arguments, skip the corresponding question.
43
+
44
+ 1. **`language`** — target language (e.g., `ruby`, `python`, `php`, `node`)
45
+ 2. **`scenario`** — which workflow to use:
46
+ > **A. Backwards compatible** — There's a published SDK with consumers who depend on its public API. The generated SDK must match the existing surface.
47
+ >
48
+ > **B. Fresh** — No existing SDK to preserve, or you're intentionally replacing one. No compat constraints.
49
+ 3. **`sdk_path`** _(Scenario A only)_ — path to the existing SDK. Validate the path exists (look for `package.json`, `Gemfile`, `go.mod`, `pyproject.toml`, or similar).
50
+ 4. **`project`** — emitter project path (e.g., `../oagen-emitters/node`). Create if needed:
51
+ ```bash
52
+ mkdir -p {project}/src/{language} && mkdir -p {project}/test/{language}
53
+ ```
54
+ 5. **`spec`** — path to the OpenAPI spec (e.g., `../openapi.yaml`)
55
+
56
+ Store all values and pass them to all sub-skill invocations.
57
+
58
+ ## Step 2: Present the Plan
59
+
60
+ ### Scenario A — Backwards compatible
61
+
62
+ ```
63
+ Step 1: /generate-emitter {language} {project} sdk_path={sdk_path} — study existing SDK, scaffold emitter
64
+ Step 2: /generate-extractor {language} sdk_path={sdk_path} — scaffold extractor
65
+ Step 3: /verify-compat {language} sdk_path={sdk_path} — extract baseline, verify
66
+ Step 4: /generate-smoke-test {language} — smoke tests against output dir
67
+ Step 5: /verify-smoke-test {language} — iterate until clean
68
+ Step 6: /integrate {language} sdk_path={sdk_path} — merge into live SDK
69
+ ```
70
+
71
+ ### Scenario B — Fresh
72
+
73
+ ```
74
+ Step 1: /generate-emitter {project} {language} — scaffold emitter
75
+ Step 2: /generate-smoke-test {language} — wire-level HTTP parity tests
76
+ Step 3: /verify-smoke-test {language} — run smoke tests
77
+ ```
78
+
79
+ > **Lifecycle note:** Scenario B is a one-time bootstrap. Once the generated SDK ships, it becomes the live SDK. All future spec updates follow Scenario A — use `--target` to integrate into the live SDK and `--api-surface` to preserve backwards compatibility.
80
+
81
+ Confirm with the user, then invoke the first skill.
82
+
83
+ ## Step 3: Run Skills in Sequence
84
+
85
+ Invoke each skill in order using the `Skill` tool, passing `project` through. After each skill, validate:
86
+
87
+ ```bash
88
+ # After /generate-emitter — design doc, entry point, and tests
89
+ ls {project}/docs/sdk-architecture/{language}.md
90
+ ls {project}/src/{language}/index.ts
91
+ cd {project} && npx vitest run test/{language}/ 2>&1 | tail -5
92
+
93
+ # After /generate-extractor — extractor registered with hints
94
+ grep -l "{language}Extractor\|{language}_extractor" {project}/oagen.config.ts
95
+ grep -l "hints" {project}/src/compat/extractors/{language}.ts
96
+
97
+ # After /verify-compat — handled by the skill itself
98
+
99
+ # After /generate-smoke-test — script exists
100
+ ls {project}/smoke/sdk-{language}.ts
101
+
102
+ # After /verify-smoke-test — handled by the skill itself
103
+
104
+ # After /integrate — changes visible in live SDK
105
+ cd {sdk_path} && git diff --stat
106
+ ```
107
+
108
+ **For Scenario A:** After `/generate-emitter`, also verify the design doc references real SDK patterns:
109
+
110
+ ```bash
111
+ grep -c "existing SDK\|from src/" {project}/docs/sdk-architecture/{language}.md
112
+ ```
113
+
114
+ If a skill fails or the user wants to pause, note where they stopped. They can resume by running the remaining skills individually.
115
+
116
+ ## Step 4: Final Checklist
117
+
118
+ Run the full validation suite:
119
+
120
+ ```bash
121
+ cd {project} && npx vitest run # emitter project
122
+ npx tsc --noEmit # oagen core type check
123
+ npx tsup # oagen core build
124
+ ```
125
+
126
+ Then present the summary:
127
+
128
+ ```
129
+ === {language} SDK support: COMPLETE ===
130
+
131
+ Scenario: {A/B}
132
+ Skills completed:
133
+ [x] /generate-emitter {language}
134
+ [x] /generate-extractor {language} (Scenario A only)
135
+ [x] /verify-compat {language} (Scenario A only)
136
+ [x] /integrate {language} (Scenario A only)
137
+ [x] /generate-smoke-test {language}
138
+
139
+ Validation:
140
+ Emitter tests: {N} passed, {N} failed
141
+ Type check: PASS/FAIL
142
+ Build: PASS/FAIL
143
+ ```
144
+
145
+ Only include this next section in the output if this is Scenario A:
146
+
147
+ ## Output
148
+
149
+ This skill produces, via its sub-skills:
150
+
151
+ - A complete language emitter registered in `oagen.config.ts`
152
+ - An API surface extractor (Scenario A only)
153
+ - A smoke test script at `smoke/sdk-{language}.ts`
154
+ - Generated SDK code at `{project}/sdk/`
155
+ - Integration into the live SDK (Scenario A only)
@@ -0,0 +1,185 @@
1
+ ---
2
+ name: generate-smoke-test
3
+ description: Create a smoke test script for a new SDK language that verifies wire-level HTTP parity against the OpenAPI spec and live API baseline. Use when adding smoke tests for a new language, verifying a generated SDK against the real API, or checking HTTP request/response correctness. Also triggers for "integration test", "wire-level test", "HTTP parity", or "end-to-end SDK test".
4
+ ---
5
+
6
+ # /generate-smoke-test
7
+
8
+ Create a self-contained smoke test script for a new SDK language that captures wire-level HTTP request/response pairs and compares them against the raw API baseline or spec-only baseline.
9
+
10
+ ## Overview
11
+
12
+ Each language's smoke test is a single file: `smoke/sdk-{lang}.ts` **in the emitter project**. It uses the target language's native HTTP interception to capture what the SDK actually sends over the wire, then outputs `SmokeResults` JSON. The diff tool compares this against a baseline and reports mismatches by severity.
13
+
14
+ The script is self-contained — no proxy, no subprocess protocol, no separate driver. It imports shared infrastructure from `@workos/oagen/smoke` and implements language-specific parts inline.
15
+
16
+ ## Resolve Paths
17
+
18
+ **Emitter project:** Use the `project` argument if provided, otherwise use `AskUserQuestion`.
19
+
20
+ **oagen core:** Check for `node_modules/@workos/oagen/`, or `src/engine/types.ts` in the current directory, otherwise ask.
21
+
22
+ ## Prerequisites
23
+
24
+ Read and understand these files before writing any code:
25
+
26
+ 1. **`@workos/oagen/smoke`** (exported from `{oagen}/scripts/smoke/shared.ts`) — `parseSpec()`, `planOperations()`, `generatePayload()` / `generateCamelPayload()`, `generateQueryParams()` / `generateCamelQueryParams()`, `IdRegistry`, `getExpectedStatusCodes()` / `isUnexpectedStatus()`, `resolvePath()`, `CapturedExchange`, `SmokeResults`
27
+
28
+ 2. **Existing smoke scripts (read via subagent)** — Use the `Agent` tool with `subagent_type: Explore` to study the closest existing smoke script. This keeps ~630+ lines of reference source out of the main context:
29
+
30
+ > Read the smoke scripts in `{emitterProject}/smoke/` (e.g., `sdk-node.ts`, `sdk-ruby.ts`). For the closest one to {language}, return: purpose, exported function signatures, structural pattern (setup → intercept → iterate → capture → cleanup), key abstractions (MethodResolution, SERVICE_MAP, interceptFetch, buildArgs), and which parts are language-specific vs. reusable. Be concise.
31
+
32
+ The Node script is the canonical reference (~630 lines) covering:
33
+ - `MethodResolution` interface and 4-tier resolution: manifest, exact match, CRUD prefix, keyword fuzzy
34
+ - `SERVICE_MAP` — IR service names to SDK property names
35
+ - `interceptFetch()` — HTTP interception with provenance capture
36
+ - `buildArgs()` — argument construction from IR operations
37
+
38
+ 3. **`{oagen}/scripts/smoke/raw.ts`** — raw baseline script
39
+ 4. **`{oagen}/scripts/smoke/diff.ts`** — diff tool severity levels (CRITICAL/WARNING/INFO)
40
+ 5. **`docs/sdk-architecture/{language}.md`** (in emitter project) — target language patterns and HTTP client
41
+
42
+ ## Resolve Spec Path
43
+
44
+ Determine the location of the OpenAPI spec before doing anything:
45
+
46
+ 1. If the `spec` argument was provided, use that.
47
+ 2. Otherwise, use `AskUserQuestion`: "Where is your OpenAPI spec located? (absolute or relative path, e.g. `../openapi.yaml`)"
48
+
49
+ Store it as `spec`.
50
+
51
+ ## Step 1: Determine HTTP Interception Strategy
52
+
53
+ The interception must capture the raw request (method, path, query params, body) and raw response (status, body). Choose based on the target language's SDK:
54
+
55
+ - **Node:** Patch `globalThis.fetch`
56
+ - **Ruby:** WebMock `stub_request` or monkey-patch `Net::HTTP`
57
+ - **Python:** `responses`, `respx` (for httpx), or `unittest.mock.patch`
58
+ - **Go:** Custom `http.RoundTripper`
59
+ - **Java/Kotlin:** OkHttp `Interceptor`
60
+
61
+ The interception code is typically ~20-30 lines. It must capture the request as-sent, let the real HTTP call proceed, capture the response, and store both in a `currentCapture` variable.
62
+
63
+ ## Step 2: Build the SERVICE_MAP
64
+
65
+ Map IR service names to SDK resource accessors. There are two sources of truth depending on the SDK:
66
+
67
+ ### Generated SDK (preferred)
68
+
69
+ For emitter-generated SDKs, the `smoke-manifest.json` (produced by the emitter's `generateManifest` hook) contains the authoritative `service` field for every operation. The smoke test should load this manifest and use its service mappings. The `SERVICE_MAP` only needs fallback entries for services not covered by the manifest.
70
+
71
+ If no manifest exists, warn the user: the emitter should implement `generateManifest`. Without it, most operations will be skipped because heuristic method resolution fails on disambiguated names.
72
+
73
+ ### Live/hand-written SDK
74
+
75
+ If `sdk_path` points to a hand-written SDK (not emitter-generated), **delegate the exploration to a subagent** to discover accessor names:
76
+
77
+ Use the `Agent` tool with `subagent_type: Explore` and a prompt like:
78
+
79
+ > Explore the SDK at `{sdk_path}`. Focus specifically on: the main client class, its resource accessor properties/methods, and how they map to domain names. Return a mapping of resource names to accessor names (e.g., Organizations → "organizations", Connections → "sso"). Only report what you actually find.
80
+
81
+ Then build the mapping:
82
+
83
+ ```typescript
84
+ const SERVICE_MAP: Record<string, string> = {
85
+ Organizations: "organizations",
86
+ Connections: "sso",
87
+ };
88
+ ```
89
+
90
+ Each language's SDK will have different accessor names — discover them by reading the SDK's client class.
91
+
92
+ ## Step 3: Implement SDK Method Resolution
93
+
94
+ Adapt the 4-tier resolution to the target language's naming conventions:
95
+
96
+ 0. **Manifest match** — Load `smoke-manifest.json` from the SDK output directory (emitter-generated, not hand-maintained). This is the **primary** resolution path for generated SDKs. The manifest maps every `HTTP_METHOD /path` to `{ sdkMethod, service }` and is produced by the emitter's `generateManifest` hook. If the manifest is missing, warn and fall through to heuristic tiers.
97
+ 1. **Exact match** — IR operation name converted to target convention
98
+ 2. **CRUD prefix match** — standard verbs (create, list, retrieve/get, update, delete) with service name tiebreaker
99
+ 3. **Keyword fuzzy match** — stem words and score overlap
100
+
101
+ Key convention differences: Ruby/Python use `snake_case`, Go uses `PascalCase`, Node uses `camelCase`.
102
+
103
+ Each resolution records provenance metadata (`ExchangeProvenance`) so findings can be traced back to the resolution path.
104
+
105
+ ## Step 4: Implement Argument Construction
106
+
107
+ Build SDK call arguments from IR operations (reference `buildArgs()` in existing smoke scripts):
108
+
109
+ - No path params + has body → `method(payload)`
110
+ - No path params + has query params → `method(queryOpts)`
111
+ - Single path param, no body/query → `method(id)` (positional)
112
+ - Complex (path params + body/query) → `method(mergedOptions)`
113
+ - Idempotent POST → append empty options object for idempotency key
114
+
115
+ Choose the right payload convention: Node uses `generateCamelPayload()`, Ruby/Python may use `generatePayload()` directly (snake_case).
116
+
117
+ ## Step 5: Write `smoke/sdk-{lang}.ts`
118
+
119
+ Create the script **in the emitter project**:
120
+
121
+ 1. Imports from `@workos/oagen/smoke`
122
+ 2. HTTP interception setup
123
+ 3. `main()` function:
124
+ - Parse CLI args, validate API key
125
+ - Parse spec via `parseSpec()`
126
+ - Load manifest from `{sdk-path}/smoke-manifest.json` (emitter-generated). If missing, log a warning — method resolution will rely on heuristic tiers and most operations will likely be skipped.
127
+ - Load and configure the SDK
128
+ - Iterate `planOperations()` groups
129
+ - For each operation: resolve SDK method, resolve path params, build args, call SDK, capture exchange
130
+ - Extract IDs via `ids.extractAndStore()`
131
+ - Track POST creates for cleanup
132
+ - Cleanup created entities in reverse
133
+ - Restore original HTTP behavior
134
+ - Write `smoke-results-sdk-{lang}.json`
135
+ 4. Summary output (successes, errors, skipped, unexpected statuses)
136
+
137
+ ## Step 6: Register the Smoke Runner
138
+
139
+ Add to `oagen.config.ts`:
140
+
141
+ ```typescript
142
+ const config: OagenConfig = {
143
+ emitters: [/* ... */],
144
+ smokeRunners: {
145
+ {language}: './smoke/sdk-{language}.ts',
146
+ },
147
+ };
148
+ ```
149
+
150
+ ## Step 7: Validate
151
+
152
+ ```bash
153
+ # Offline validation against spec baseline (no API key needed)
154
+ oagen verify --lang {lang} --output {sdk-path} --spec <spec>
155
+
156
+ # Live validation against real API (requires API key and raw baseline)
157
+ oagen verify --lang {lang} --output {sdk-path} --raw-results smoke-results-raw.json
158
+ ```
159
+
160
+ ## Emitter-Fixing Loop
161
+
162
+ During initial setup, run `oagen generate` then the smoke test until skips are minimized:
163
+
164
+ ```bash
165
+ oagen generate --lang {lang} --output {sdk-path} --spec {spec} --namespace {ns}
166
+ # The emitter produces smoke-manifest.json alongside the SDK — the smoke test loads it automatically.
167
+ oagen verify --lang {lang} --output {sdk-path} --spec {spec}
168
+ ```
169
+
170
+ If many operations are skipped with "No matching SDK method", check that the emitter's `generateManifest` is implemented and that the manifest file is being written to the SDK output directory. The manifest is the primary mechanism the smoke test uses to find SDK methods.
171
+
172
+ | Exit | Meaning | Output | Action |
173
+ | ---- | ------------- | --------------------------- | --------------------------------------- |
174
+ | 0 | Clean | — | Done |
175
+ | 1 | Findings | `smoke-diff-findings.json` | Read findings, fix emitter/smoke script |
176
+ | 2 | Compile error | `smoke-compile-errors.json` | Read errors, fix emitter |
177
+
178
+ See `scripts/smoke/README.md` (in oagen core) for the full remediation guide. See [Workflows](../../docs/architecture/workflows.md) for the overall workflow diagram.
179
+
180
+ ## Output
181
+
182
+ This skill produces, in the emitter project:
183
+
184
+ - `smoke/sdk-{language}.ts` — self-contained smoke test script
185
+ - Updated `oagen.config.ts` with the smoke runner path registered
@@ -0,0 +1,108 @@
1
+ ---
2
+ name: integrate
3
+ description: Integrate generated SDK code into a live SDK by running `oagen generate --target`. Required for every Scenario A generation cycle — initial setup and ongoing spec updates. Use when the user wants to merge generated code into an existing SDK or integrate emitter output into a live codebase.
4
+ ---
5
+
6
+ # /integrate
7
+
8
+ ## Purpose
9
+
10
+ Integrate generated SDK code into a live SDK by running `oagen generate --target`. This runs every time code is generated for a live SDK, not just once — both during initial setup and on every subsequent spec update. The writer's additive merge makes it safe to run on every regeneration.
11
+
12
+ ## Reference Docs
13
+
14
+ - [Pipeline Architecture](../../docs/architecture/pipeline.md) — how the writer's additive merge works
15
+ - [Workflows](../../docs/architecture/workflows.md) — where `/integrate` fits in the overall workflow
16
+ - [Emitter Contract](../../docs/architecture/emitter-contract.md) — overlay integration and `skipIfExists`
17
+
18
+ ## Prerequisites
19
+
20
+ Before integration:
21
+
22
+ 1. **Verify-compat must have passed** — check for an existing API surface file (`sdk-{language}-surface.json` in the emitter project). If missing, suggest running `/verify-compat` first.
23
+ 2. **The emitter should use `fileBySymbol` hints** for correct file placement — this ensures generated files land at the paths the live SDK expects.
24
+
25
+ ## Step 1: Resolve Paths
26
+
27
+ Determine required paths:
28
+
29
+ - **Language**: from argument, or use `AskUserQuestion`
30
+ - **Live SDK path** (`sdk_path`): from argument, or use `AskUserQuestion`: "Where is the live SDK? (absolute or relative path)"
31
+ - **OpenAPI spec** (`spec`): from argument, `OPENAPI_SPEC_PATH` env var, or use `AskUserQuestion`
32
+ - **Emitter project** (`project`): from argument, or detect from current directory
33
+ - **Output directory** (`output`): use a temp directory or the emitter project's `sdk/` directory
34
+ - **API surface** (`api_surface`): `sdk-{language}-surface.json` in the emitter project
35
+ - **Manifest** (`manifest`): `smoke-manifest.json` in the output directory (if it exists)
36
+
37
+ ## Step 2: Dry-Run Preview
38
+
39
+ Run `oagen generate` with `--dry-run` and `--target` to show what will happen:
40
+
41
+ ```bash
42
+ npx tsx src/cli/index.ts generate \
43
+ --spec {spec} \
44
+ --lang {language} \
45
+ --output {output} \
46
+ --target {sdk_path} \
47
+ --namespace {namespace} \
48
+ --api-surface {api_surface} \
49
+ --manifest {manifest} \
50
+ --dry-run
51
+ ```
52
+
53
+ Present the output to the user. The dry-run shows:
54
+
55
+ - Files that would be written to the output directory
56
+ - Files that would be written/merged/skipped in the target directory
57
+
58
+ ## Step 3: Confirm and Execute
59
+
60
+ Use `AskUserQuestion` to confirm:
61
+
62
+ > "The above files will be written to `{output}` and integrated into `{sdk_path}`. Proceed?"
63
+
64
+ On confirmation, run without `--dry-run`:
65
+
66
+ ```bash
67
+ npx tsx src/cli/index.ts generate \
68
+ --spec {spec} \
69
+ --lang {language} \
70
+ --output {output} \
71
+ --target {sdk_path} \
72
+ --namespace {namespace} \
73
+ --api-surface {api_surface} \
74
+ --manifest {manifest}
75
+ ```
76
+
77
+ ## Step 4: Post-Integration Verification
78
+
79
+ Suggest running the live SDK's test suite:
80
+
81
+ ```bash
82
+ cd {sdk_path} && npm test # or equivalent for the target language
83
+ ```
84
+
85
+ If tests fail, the merger's additive-only guarantees mean hand-written code was not modified — failures are likely from new symbols that need wiring up or import updates.
86
+
87
+ ## Step 5: Summary
88
+
89
+ Report what happened:
90
+
91
+ ```
92
+ === Integration Complete ===
93
+ Target: {sdk_path}
94
+ New files created: {N}
95
+ Existing files merged: {N}
96
+ Files skipped: {N}
97
+
98
+ Next steps:
99
+ - Review changes: cd {sdk_path} && git diff
100
+ - Run tests: cd {sdk_path} && npm test
101
+ - If needed, re-run /integrate after fixing issues
102
+ ```
103
+
104
+ ## Output
105
+
106
+ - Generated files written to `{output}` directory
107
+ - Files merged into the live SDK at `{sdk_path}` via the writer's additive merge
108
+ - Summary report showing new files created, existing files merged, and files skipped
@@ -0,0 +1,151 @@
1
+ ---
2
+ name: verify-compat
3
+ description: Verify that generated SDK code preserves backwards compatibility with a live SDK. Use when regenerating an SDK for a language that has an existing published SDK, checking for breaking changes, regressions, or API surface drift. Also triggers for "BC check", "backwards compatibility", "compat verification", "breaking change detection", "API surface comparison", or "regression check".
4
+ ---
5
+
6
+ # /verify-compat
7
+
8
+ Verify that generated SDK code preserves backwards compatibility with a live SDK's public API surface.
9
+
10
+ ## Overview
11
+
12
+ The compat verification workflow extracts the public API surface from a live SDK, generates code with a compatibility overlay that preserves existing names, and then diffs the result against the baseline. Any regressions (renamed methods, changed signatures, missing exports) are reported as violations.
13
+
14
+ This is the middle tier of the three-tier testing pyramid:
15
+
16
+ - **Unit tests** — verify emitter logic in isolation
17
+ - **Compat verification** — verify the API surface (names, signatures, exports) matches the live SDK
18
+ - **Smoke tests** — verify wire-level HTTP behavior against the real API
19
+
20
+ ## Reference Docs
21
+
22
+ - [Extractor Contract](../../docs/architecture/extractor-contract.md) — `Extractor` interface, `ApiSurface` type, Language Hints reference
23
+ - [Workflows](../../docs/architecture/workflows.md) — where compat verification fits in the overall setup flow
24
+ - Language hints: `src/compat/language-hints.ts` (`nodeHints`, `resolveHints`)
25
+ - Node extractor (reference): `src/compat/extractors/node.ts`
26
+ - Overlay logic: `src/compat/overlay.ts`
27
+ - Differ: `src/compat/differ.ts`
28
+
29
+ ## Resolve oagen Core Path
30
+
31
+ Check for `node_modules/@workos/oagen/`, or `src/engine/types.ts` in the current directory, otherwise ask.
32
+
33
+ ## Prerequisites
34
+
35
+ - An emitter exists for `<language>` (run `/generate-emitter` first)
36
+ - The live SDK is accessible at `--sdk-path` — must be the real, published SDK
37
+ - An extractor exists for `<language>` (run `/generate-extractor` first), with `hints: LanguageHints` properly configured for the target language
38
+ - The emitter has been run at least once to produce generated output
39
+ - The emitter's design doc (`docs/sdk-architecture/{language}.md`) exists and documents the real SDK's patterns
40
+
41
+ ## Resolve Paths
42
+
43
+ All artifacts (surface files, generated output) live **inside the emitter project** — never in `/tmp` or other throwaway locations. This ensures they persist across sessions and are available to convenience scripts.
44
+
45
+ Determine required paths:
46
+
47
+ 1. **Emitter project** (`project`): from argument, or detect from CWD (look for `oagen.config.ts`), or use `AskUserQuestion`
48
+ 2. **Spec** (`spec`): from argument, or use `AskUserQuestion`
49
+ 3. **Live SDK** (`sdk_path`): from argument, or use `AskUserQuestion`
50
+ 4. **API surface file**: `{project}/sdk-{language}-surface.json` — always in the emitter project root
51
+ 5. **Generated output**: `{project}/sdk/` — a subdirectory of the emitter project
52
+
53
+ **Validation**: The `sdk_path` MUST NOT be the same as or inside the output directory. If it is, reject it and ask again.
54
+
55
+ ## Step 1: Extract Baseline
56
+
57
+ ```bash
58
+ oagen extract --sdk-path {sdk_path} --lang <language> --output {project}/sdk-{language}-surface.json
59
+ ```
60
+
61
+ This produces `sdk-{language}-surface.json` in the **emitter project root** — the baseline snapshot of the live SDK's public API.
62
+
63
+ **Gitignore it** — it's a derived artifact:
64
+
65
+ ```bash
66
+ grep -q 'sdk-\*-surface.json' {project}/.gitignore 2>/dev/null || echo 'sdk-*-surface.json' >> {project}/.gitignore
67
+ ```
68
+
69
+ **Verify the output via subagent:** Use the `Agent` tool with `subagent_type: Explore` to spot-check the extraction. This keeps the SDK's source out of the main context:
70
+
71
+ > Explore the SDK at `{sdk_path}`. List all public classes and their public method names. Only report what you actually find — no assumptions.
72
+
73
+ Compare the subagent's findings against `<output-path>/sdk-{language}-surface.json`. If the surface looks empty or incomplete, the extractor may need fixes — run `/generate-extractor` to debug.
74
+
75
+ **Scope to API-relevant symbols:** The live SDK may export hand-written classes and interfaces that are not derivable from the OpenAPI spec (e.g., `CookieSession`, `PKCE`, `Webhooks`, signature verification utilities, HTTP client abstractions). These are out of scope for compat verification — the emitter isn't expected to generate them.
76
+
77
+ After extraction, cross-reference the baseline surface against the OpenAPI spec's operations. Symbols that have no corresponding OpenAPI operation should be noted but excluded from the compat score. Report them separately so the user knows what's excluded:
78
+
79
+ ```
80
+ Out of scope (not in OpenAPI spec): CookieSession, PKCE, Webhooks, ...
81
+ In scope (API-derived): 280 classes, 1200 interfaces, ...
82
+ ```
83
+
84
+ This prevents the compat score from being diluted by symbols the emitter can't possibly generate. The goal is to measure how well the emitter preserves the API surface, not how completely it replicates hand-written SDK features.
85
+
86
+ ## Step 2: Generate with Overlay
87
+
88
+ ```bash
89
+ oagen generate --spec {spec} --lang <language> --output {project}/sdk --api-surface {project}/sdk-{language}-surface.json
90
+ ```
91
+
92
+ The emitter receives the overlay via `EmitterContext` and uses it to preserve existing method names, class names, and type names where possible. The generated output goes to `{project}/sdk/`, not a temp directory.
93
+
94
+ ## Step 3: Verify
95
+
96
+ **IMPORTANT:** Always include `--spec` so the comparison is scoped to spec-derivable symbols only. Without it, the score includes hand-written SDK features (webhooks, PKCE, etc.) that the emitter can't generate, producing a misleadingly low compat score.
97
+
98
+ ```bash
99
+ oagen verify --spec {spec} --lang <language> --output {project}/sdk --api-surface {project}/sdk-{language}-surface.json
100
+ ```
101
+
102
+ - **Exit 0** + preservation score = all clear
103
+ - **Exit 1** + violations = review each violation (see Step 4)
104
+
105
+ ## Step 4: Interpret Results
106
+
107
+ | Category | Meaning |
108
+ | ------------------ | ------------------------------------------------------ |
109
+ | `public-api` | A public class, method, or type was renamed or removed |
110
+ | `signature` | A method's parameter list or return type changed |
111
+ | `export-structure` | A barrel export is missing or reorganized |
112
+ | `behavioral` | A behavioral contract changed (e.g., async to sync) |
113
+
114
+ | Severity | Action |
115
+ | ---------- | ------------------------------------------------------------------ |
116
+ | `breaking` | Must fix before shipping — this will break consumers |
117
+ | `warning` | Review carefully — may be intentional but could surprise consumers |
118
+
119
+ **For each violation:**
120
+
121
+ 1. Check `symbolPath` to locate the affected symbol
122
+ 2. Compare `baseline` vs `candidate` to understand the change
123
+ 3. Fix the emitter to preserve the baseline name/signature, OR
124
+ 4. If intentional, document it as a breaking change
125
+
126
+ **When violations can't be auto-fixed:** Some violations indicate structural emitter issues (wrong method signature shape, missing exports). These require changing emitter code directly — the overlay loop can only fix naming mismatches, not structural problems. Read the violation details to determine whether this is an overlay issue or an emitter issue.
127
+
128
+ ## Step 5: Self-Correcting Loop
129
+
130
+ Repeat Steps 2 and 3 until violations are resolved:
131
+
132
+ ```bash
133
+ oagen generate --spec {spec} --lang <language> --output {project}/sdk --api-surface {project}/sdk-{language}-surface.json
134
+ oagen verify --spec {spec} --lang <language> --output {project}/sdk --api-surface {project}/sdk-{language}-surface.json
135
+ ```
136
+
137
+ Continue until either all violations are resolved (exit 0) or no further improvement is possible (fix the emitter manually).
138
+
139
+ ## When to Skip
140
+
141
+ For languages where backwards compatibility isn't relevant (full rewrites, new SDKs), skip compat verification:
142
+
143
+ ```bash
144
+ oagen generate --spec <spec> --lang <language> --output <path> --no-compat-check
145
+ ```
146
+
147
+ ## Output
148
+
149
+ - `{project}/sdk-{language}-surface.json` — extracted API surface baseline
150
+ - Generated SDK at `{project}/sdk/` with compat overlay applied
151
+ - Violation report (exit 1) or clean pass (exit 0)