|
| 1 | +--- |
| 2 | +name: generate-openapi-from-pr |
| 3 | +description: Generate OpenAPI spec changes from an intercom monolith PR. Use when a user provides an intercom/intercom PR URL or number and wants to create the corresponding OpenAPI documentation changes in this repo. |
| 4 | +allowed-tools: Task, Read, Glob, Grep, Write, Edit, Bash, AskUserQuestion |
| 5 | +--- |
| 6 | + |
| 7 | +# Generate OpenAPI Spec from Intercom PR |
| 8 | + |
| 9 | +This skill takes an intercom monolith PR (from `intercom/intercom`) and generates the corresponding OpenAPI spec changes in this repo (`Intercom-OpenAPI`). |
| 10 | + |
| 11 | +## When to Activate |
| 12 | + |
| 13 | +- User provides an `intercom/intercom` PR URL or number |
| 14 | +- User asks to "generate docs from PR", "create OpenAPI from PR", "document this API change" |
| 15 | + |
| 16 | +## Workflow |
| 17 | + |
| 18 | +### Step 1: Parse Input |
| 19 | + |
| 20 | +Extract the PR number from the user's input. Accept: |
| 21 | +- Full URL: `https://github.com/intercom/intercom/pull/12345` |
| 22 | +- Short reference: `intercom/intercom#12345` |
| 23 | +- Just a number: `12345` (assume intercom/intercom) |
| 24 | + |
| 25 | +### Step 2: Fetch PR Details |
| 26 | + |
| 27 | +```bash |
| 28 | +# Get PR description, metadata, and state |
| 29 | +gh pr view <NUMBER> --repo intercom/intercom --json title,body,files,labels,state |
| 30 | + |
| 31 | +# Get the full diff (works for open and merged PRs) |
| 32 | +gh pr diff <NUMBER> --repo intercom/intercom |
| 33 | +``` |
| 34 | + |
| 35 | +If the diff is too large, fetch individual changed files instead: |
| 36 | +```bash |
| 37 | +gh pr view <NUMBER> --repo intercom/intercom --json files --jq '.files[].path' |
| 38 | +``` |
| 39 | + |
| 40 | +Then fetch specific files of interest (controllers, models, version changes, routes). |
| 41 | + |
| 42 | +For merged PRs where you need the full file (not just diff), fetch from the default branch: |
| 43 | +```bash |
| 44 | +gh api repos/intercom/intercom/contents/<path> --jq '.content' | base64 -d |
| 45 | +``` |
| 46 | + |
| 47 | +### Step 3: Analyze the Diff |
| 48 | + |
| 49 | +Scan the diff for these file patterns and extract API-relevant information: |
| 50 | + |
| 51 | +#### 3a. Controllers (`app/controllers/api/v3/`) |
| 52 | + |
| 53 | +Look for: |
| 54 | +- **New controller files** → new API resource with endpoints |
| 55 | +- **New actions** (`def index`, `def show`, `def create`, `def update`, `def destroy`) → new operations |
| 56 | +- **`requires_version_change`** → which version change gates this endpoint |
| 57 | +- **`render_json Api::V3::Models::XxxResponse`** → identifies the response model/presenter |
| 58 | +- **`params.slice(...).permit(...)`** or **request parser classes** (`RequestParser`, `StrongParams`) → request body fields |
| 59 | +- **Error handling** (`raise Api::V3::Errors::ApiCodedError`) → error responses |
| 60 | +- **`before_action :check_api_version!`** → version-gated endpoint |
| 61 | + |
| 62 | +#### 3b. Models/Presenters (`app/presenters/api/v3/` or `app/lib/api/v3/models/`) |
| 63 | + |
| 64 | +Look for: |
| 65 | +- **`serialized_attributes do`** blocks → response schema properties |
| 66 | +- **`attribute :field_name`** → schema field definition |
| 67 | +- **`stringify: true`** → field is string type (even if integer in DB) |
| 68 | +- **`from_model` method** → how the model maps from internal objects |
| 69 | +- **Conditional attributes** based on version → version-specific fields |
| 70 | + |
| 71 | +#### 3c. Version Changes (`app/lib/api/versioning/changes/`) |
| 72 | + |
| 73 | +Look for: |
| 74 | +- **`define_description`** → description of the API change (use in PR/commit message) |
| 75 | +- **`define_is_breaking`** → whether this is a breaking change |
| 76 | +- **`define_is_ready_for_release`** → usually `false` for new changes |
| 77 | +- **`define_transformation ... data.except(:field1, :field2)`** → these fields are NEW (removed for old versions) |
| 78 | +- **`define_transformation` with data modification** → field format/value changed between versions |
| 79 | + |
| 80 | +#### 3d. Version Registration (`app/lib/api/versioning/service.rb`) |
| 81 | + |
| 82 | +Look for which version block the new change is added to: |
| 83 | +- `UnstableVersion.new(changes: [...])` → goes in Unstable (version `0/`) |
| 84 | +- `Version.new(id: "2.15", changes: [...])` → goes in that specific version |
| 85 | + |
| 86 | +#### 3e. Routes (`config/routes/api_v3.rb`) |
| 87 | + |
| 88 | +Look for: |
| 89 | +- `resources :things` → standard CRUD: index, show, create, update, destroy |
| 90 | +- `resources :things, only: [:index, :show]` → limited operations |
| 91 | +- `member do ... end` → actions on specific resource (e.g., PUT `/things/{id}/action`) |
| 92 | +- `collection do ... end` → actions on resource collection (e.g., POST `/things/search`) |
| 93 | +- Nested resources → parent/child paths (e.g., `/contacts/{id}/tags`) |
| 94 | + |
| 95 | +#### 3f. OAuth Scopes (`app/lib/policy/api_controller_routes_oauth_scope_policy.rb`) |
| 96 | + |
| 97 | +Look for scope mappings to understand required auth scope for the endpoint. |
| 98 | + |
| 99 | +### Step 4: Ask User for Version Targeting |
| 100 | + |
| 101 | +Present the findings and ask: |
| 102 | + |
| 103 | +``` |
| 104 | +I found the following API changes in PR #XXXXX: |
| 105 | +- [list of changes found] |
| 106 | +
|
| 107 | +Which API versions should I update? |
| 108 | +- Unstable only (default for new features) |
| 109 | +- Specific versions (for bug fixes/backports) |
| 110 | +``` |
| 111 | + |
| 112 | +Default to Unstable (`descriptions/0/api.intercom.io.yaml`) unless the PR clearly targets specific versions. |
| 113 | + |
| 114 | +### Step 5: Read Target Spec File(s) |
| 115 | + |
| 116 | +Read the target spec file(s) to understand: |
| 117 | +- Existing endpoints in the same resource group (for consistent naming/style) |
| 118 | +- Existing schemas that can be reused or extended |
| 119 | +- The `intercom_version` enum (to verify version values) |
| 120 | +- Where to insert new paths/schemas (maintain alphabetical or logical grouping) |
| 121 | +- **All inline examples that reference the affected schema** — when adding a field, you must update every response example that returns that schema. Search with: `grep -n 'schemas/<name>' <spec_file>` |
| 122 | + |
| 123 | +### Step 6: Generate OpenAPI Changes |
| 124 | + |
| 125 | +Use the patterns from the companion guide files: |
| 126 | +- **[./ruby-to-openapi-mapping.md](./ruby-to-openapi-mapping.md)** — Ruby pattern → OpenAPI mapping rules |
| 127 | +- **[./openapi-patterns.md](./openapi-patterns.md)** — Concrete YAML templates |
| 128 | +- **[./version-propagation.md](./version-propagation.md)** — Cross-version propagation rules |
| 129 | + |
| 130 | +#### Adding a new field to an existing schema (most common change) |
| 131 | + |
| 132 | +This is the most frequent PR type. You must update TWO places in the spec: |
| 133 | + |
| 134 | +1. **Schema property** — add the new field to `components/schemas/<schema_name>/properties` |
| 135 | +2. **Inline response examples** — search for ALL endpoints that return this schema and update their inline example `value` objects to include the new field |
| 136 | + |
| 137 | +Example from a real PR (adding `previous_ticket_state_id` to ticket): |
| 138 | +```yaml |
| 139 | +# 1. In components/schemas/ticket/properties — add the field: |
| 140 | +previous_ticket_state_id: |
| 141 | + type: string |
| 142 | + nullable: true |
| 143 | + description: The ID of the previous ticket state. |
| 144 | + example: '7493' |
| 145 | + |
| 146 | +# 2. In EVERY endpoint response example that returns a ticket — add the value: |
| 147 | +examples: |
| 148 | + Successful response: |
| 149 | + value: |
| 150 | + type: ticket |
| 151 | + id: '494' |
| 152 | + # ... existing fields ... |
| 153 | + previous_ticket_state_id: '7490' # ADD THIS |
| 154 | +``` |
| 155 | +
|
| 156 | +**To find all examples that need updating**, search the spec file for the schema name: |
| 157 | +```bash |
| 158 | +grep -n 'schemas/ticket' descriptions/0/api.intercom.io.yaml |
| 159 | +``` |
| 160 | +Then check each endpoint that references that schema and update its inline examples. |
| 161 | + |
| 162 | +#### Required elements for every new endpoint: |
| 163 | + |
| 164 | +1. **`summary`** — short description used as page title in docs (required) |
| 165 | + |
| 166 | +2. **`description`** — longer explanation of what the endpoint does |
| 167 | + |
| 168 | +3. **`Intercom-Version` header parameter** — always reference the schema: |
| 169 | + ```yaml |
| 170 | + parameters: |
| 171 | + - name: Intercom-Version |
| 172 | + in: header |
| 173 | + schema: |
| 174 | + "$ref": "#/components/schemas/intercom_version" |
| 175 | + ``` |
| 176 | +
|
| 177 | +4. **`tags`** — group name matching existing tags or new tag for new resource |
| 178 | + |
| 179 | +5. **`operationId`** — unique, camelCase identifier (e.g., `listTags`, `createContact`, `retrieveTicket`) |
| 180 | + |
| 181 | +6. **Response with inline examples + schema ref**: |
| 182 | + ```yaml |
| 183 | + responses: |
| 184 | + '200': |
| 185 | + description: Successful response |
| 186 | + content: |
| 187 | + application/json: |
| 188 | + examples: |
| 189 | + Successful response: |
| 190 | + value: |
| 191 | + type: resource |
| 192 | + id: '123' |
| 193 | + schema: |
| 194 | + "$ref": "#/components/schemas/resource_schema" |
| 195 | + ``` |
| 196 | + |
| 197 | +7. **Standard error responses** — at minimum `401 Unauthorized`: |
| 198 | + ```yaml |
| 199 | + '401': |
| 200 | + description: Unauthorized |
| 201 | + content: |
| 202 | + application/json: |
| 203 | + examples: |
| 204 | + Unauthorized: |
| 205 | + value: |
| 206 | + type: error.list |
| 207 | + request_id: <uuid> |
| 208 | + errors: |
| 209 | + - code: unauthorized |
| 210 | + message: Access Token Invalid |
| 211 | + schema: |
| 212 | + "$ref": "#/components/schemas/error" |
| 213 | + ``` |
| 214 | + |
| 215 | +8. **Request body** (for POST/PUT) with schema and examples |
| 216 | + |
| 217 | +#### Required elements for new schemas: |
| 218 | + |
| 219 | +1. **`title`** — Title Case |
| 220 | +2. **`type: object`** |
| 221 | +3. **`x-tags`** — tag group linkage (must match a top-level tag name) |
| 222 | +4. **`description`** |
| 223 | +5. **`properties`** — each with `type`, `description`, `example` |
| 224 | +6. **`nullable: true`** — on fields that can be null |
| 225 | +7. **Timestamps** — `type: integer` + `format: date-time` |
| 226 | + |
| 227 | +#### Top-level `tags` section (for new resources) |
| 228 | + |
| 229 | +If adding a completely new API resource (not just new endpoints on an existing resource), you MUST add a tag entry to the **top-level `tags` array** at the bottom of the spec file (after `security`): |
| 230 | + |
| 231 | +```yaml |
| 232 | +tags: |
| 233 | +# ... existing tags ... |
| 234 | +- name: Your Resource |
| 235 | + description: Everything about your Resource |
| 236 | +``` |
| 237 | + |
| 238 | +This tag name must match: |
| 239 | +- The `tags` array on each endpoint (e.g., `tags: [Your Resource]`) |
| 240 | +- The `x-tags` on related schemas (e.g., `x-tags: [Your Resource]`) |
| 241 | + |
| 242 | +Existing top-level tags include: Admins, AI Content, Articles, Away Status Reasons, Brands, Calls, Companies, Contacts, Conversations, Custom Channel Events, Custom Object Instances, Data Attributes, Data Events, Data Export, Emails, Help Center, Internal Articles, Jobs, Macros, Messages, News, Notes, Segments, Subscription Types, Switch, Tags, Teams, Ticket States, Ticket Type Attributes, Ticket Types, Tickets, Visitors, Workflows. |
| 243 | + |
| 244 | +#### Reusable `components/responses` (optional) |
| 245 | + |
| 246 | +The spec defines reusable error responses in `components/responses`: |
| 247 | +- `Unauthorized` — 401 with access token invalid |
| 248 | +- `TypeNotFound` — 404 for custom object types |
| 249 | +- `ObjectNotFound` — 404 for objects/custom objects/integrations |
| 250 | +- `ValidationError` — validation errors |
| 251 | +- `BadRequest` — bad request errors |
| 252 | +- `CustomChannelNotificationSuccess` — custom channel success |
| 253 | + |
| 254 | +You can reference these with `"$ref": "#/components/responses/Unauthorized"` instead of inlining the error response, but most existing endpoints inline their errors. **Match the style of nearby endpoints** in the same resource group. |
| 255 | +
|
| 256 | +### Step 7: Apply Changes |
| 257 | +
|
| 258 | +Use the Edit tool to insert changes into the spec file(s). Be careful about: |
| 259 | +- YAML indentation (2-space indent throughout) |
| 260 | +- Inserting paths in logical order (group related endpoints together) |
| 261 | +- Inserting schemas alphabetically in `components/schemas` |
| 262 | +- Adding new top-level tags in alphabetical order in the `tags` array |
| 263 | +- Not breaking existing content |
| 264 | + |
| 265 | +### Step 8: Validate |
| 266 | + |
| 267 | +Run Fern validation: |
| 268 | +```bash |
| 269 | +fern check |
| 270 | +``` |
| 271 | + |
| 272 | +If validation fails, read the error output and fix the issues. |
| 273 | + |
| 274 | +### Step 9: Summarize |
| 275 | + |
| 276 | +Report to the user: |
| 277 | +- What was added/changed (new endpoints, new schemas, new fields, new top-level tags) |
| 278 | +- Which files were modified |
| 279 | +- Which versions were updated |
| 280 | +- Any manual follow-up needed |
| 281 | + |
| 282 | +#### Follow-up Checklist |
| 283 | + |
| 284 | +Always remind the user of remaining manual steps: |
| 285 | + |
| 286 | +1. **Review generated changes** for accuracy against the actual API behavior |
| 287 | +2. **Fern overrides** — if new endpoints were added to Unstable, check if `fern/unstable-openapi-overrides.yml` needs SDK method name entries |
| 288 | +3. **Developer-docs PR** — copy the updated spec to the `developer-docs` repo: |
| 289 | + - Copy `descriptions/0/api.intercom.io.yaml` → `docs/references/@Unstable/rest-api/api.intercom.io.yaml` |
| 290 | + - For stable versions: `descriptions/2.15/api.intercom.io.yaml` → `docs/references/@2.15/rest-api/api.intercom.io.yaml` |
| 291 | +4. **Changelog** — if the change should appear in the public changelog, update `docs/references/@<version>/changelog.md` in the developer-docs repo (newest entries at top) |
| 292 | +5. **Cross-version changes** — if this is an unversioned change (affects all versions), also update `docs/build-an-integration/learn-more/rest-apis/unversioned-changes.md` in developer-docs |
| 293 | +6. **Run `fern check`** to validate before committing |
| 294 | + |
| 295 | +## Important Notes |
| 296 | + |
| 297 | +- **Do NOT run `fern generate` without `--preview`** — this would auto-submit PRs to SDK repos |
| 298 | +- **Examples must be realistic** — use plausible IDs, emails, timestamps |
| 299 | +- **Match existing style** — look at nearby endpoints for naming and formatting conventions |
| 300 | +- **Cross-reference with existing schemas** — reuse `$ref` to existing schemas wherever possible |
| 301 | +- **Nullable fields** — always explicitly mark with `nullable: true` |
| 302 | +- **The `error` schema** is already defined — always reference it with `"$ref": "#/components/schemas/error"` |
| 303 | +- **Top-level tags** — new resources need a tag in the `tags` array at the bottom of the spec |
| 304 | +- **Servers** — the spec already defines 3 regional servers (US, EU, AU) — do not modify |
| 305 | +- **Security** — global `bearerAuth` is already configured — do not modify |
0 commit comments