@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.
- package/.claude-plugin/plugin.json +12 -0
- package/LICENSE.txt +21 -0
- package/README.md +197 -0
- package/dist/chunk-CIX5NOBF.js +263 -0
- package/dist/chunk-IJ5E5AE7.js +1040 -0
- package/dist/chunk-P5XUDUGY.js +1748 -0
- package/dist/cli/index.js +5603 -0
- package/dist/compat/index.d.ts +195 -0
- package/dist/compat/index.js +4453 -0
- package/dist/index.d.ts +300 -0
- package/dist/index.js +1733 -0
- package/dist/scripts/smoke/baseline.js +1751 -0
- package/dist/scripts/smoke/sdk-test.js +93 -0
- package/dist/smoke.js +1963 -0
- package/dist/types-B5t9O9Wm.d.ts +367 -0
- package/dist/verify/index.d.ts +76 -0
- package/dist/verify/index.js +336 -0
- package/docs/advanced/index.md +11 -0
- package/docs/advanced/oagen-ignore.md +144 -0
- package/docs/agents/architecture.md +53 -0
- package/docs/agents/emitters.md +69 -0
- package/docs/agents/testing.md +60 -0
- package/docs/architecture/dependency-layers.md +75 -0
- package/docs/architecture/emitter-contract.md +160 -0
- package/docs/architecture/extractor-contract.md +227 -0
- package/docs/architecture/ir-types.md +275 -0
- package/docs/architecture/non-additive-changes.md +76 -0
- package/docs/architecture/pipeline.md +110 -0
- package/docs/architecture/workflows.md +215 -0
- package/docs/cli.md +156 -0
- package/docs/contributor/index.md +10 -0
- package/docs/contributor/public-api.md +59 -0
- package/docs/contributor/versioning.md +48 -0
- package/docs/core/index.md +12 -0
- package/docs/core/minimal-emitter.md +134 -0
- package/docs/core/quickstart.md +142 -0
- package/docs/diagrams/generate-sdk-workflow.md +50 -0
- package/docs/index.md +40 -0
- package/examples/reference-emitter/README.md +41 -0
- package/examples/reference-emitter/oagen.config.ts +7 -0
- package/examples/reference-emitter/spec/github-subset.yml +400 -0
- package/examples/reference-emitter/src/emitter.ts +52 -0
- package/examples/reference-emitter/src/generators/client.ts +37 -0
- package/examples/reference-emitter/src/generators/config.ts +19 -0
- package/examples/reference-emitter/src/generators/enums.ts +16 -0
- package/examples/reference-emitter/src/generators/errors.ts +39 -0
- package/examples/reference-emitter/src/generators/models.ts +29 -0
- package/examples/reference-emitter/src/generators/resources.ts +60 -0
- package/examples/reference-emitter/src/generators/tests.ts +7 -0
- package/examples/reference-emitter/src/index.ts +1 -0
- package/examples/reference-emitter/src/naming.ts +21 -0
- package/examples/reference-emitter/src/type-mapper.ts +39 -0
- package/examples/reference-emitter/tsconfig.json +19 -0
- package/package.json +102 -0
- package/skills/generate-emitter/SKILL.md +393 -0
- package/skills/generate-emitter/references/common-pitfalls.md +23 -0
- package/skills/generate-emitter/references/generator-guide.md +201 -0
- package/skills/generate-emitter/references/project-scaffold.md +138 -0
- package/skills/generate-extractor/SKILL.md +200 -0
- package/skills/generate-sdk/SKILL.md +155 -0
- package/skills/generate-smoke-test/SKILL.md +185 -0
- package/skills/integrate/SKILL.md +108 -0
- package/skills/verify-compat/SKILL.md +151 -0
- 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)
|