|
| 1 | +--- |
| 2 | +name: generate-openapi-from-pr |
| 3 | +description: > |
| 4 | + Generate OpenAPI spec changes from an intercom monolith PR. Use this skill whenever a user |
| 5 | + provides an intercom/intercom PR URL or number, asks to generate or update OpenAPI docs, |
| 6 | + update the spec, document an API change, or mentions shipping API documentation from a PR. |
| 7 | + Also trigger when the user pastes a github.com/intercom/intercom/pull/ URL even without |
| 8 | + explicit instructions — they almost certainly want spec changes generated. This is the |
| 9 | + primary workflow for this repository. |
| 10 | +
|
| 11 | +metadata: |
| 12 | + author: team-data-foundations |
| 13 | + version: "1.0" |
| 14 | + user-invocable: true |
| 15 | + argument-hint: "<intercom-pr-url-or-number>" |
| 16 | + |
| 17 | +allowed-tools: Task, Read, Glob, Grep, Write, Edit, Bash, AskUserQuestion |
| 18 | +--- |
| 19 | + |
| 20 | +# Generate OpenAPI Spec from Intercom PR |
| 21 | + |
| 22 | +This skill takes an intercom monolith PR (from `intercom/intercom`) and generates the corresponding OpenAPI spec changes in this repo (`Intercom-OpenAPI`). |
| 23 | + |
| 24 | +## Workflow |
| 25 | + |
| 26 | +### Step 1: Parse Input |
| 27 | + |
| 28 | +Extract the PR number from the user's input. Accept: |
| 29 | +- Full URL: `https://github.com/intercom/intercom/pull/12345` |
| 30 | +- Short reference: `intercom/intercom#12345` |
| 31 | +- Just a number: `12345` (assume intercom/intercom) |
| 32 | + |
| 33 | +### Step 2: Fetch PR Details |
| 34 | + |
| 35 | +```bash |
| 36 | +# Get PR description, metadata, and state |
| 37 | +gh pr view <NUMBER> --repo intercom/intercom --json title,body,files,labels,state |
| 38 | + |
| 39 | +# Get the full diff (works for open and merged PRs) |
| 40 | +gh pr diff <NUMBER> --repo intercom/intercom |
| 41 | +``` |
| 42 | + |
| 43 | +If the diff is too large, fetch individual changed files instead: |
| 44 | +```bash |
| 45 | +gh pr view <NUMBER> --repo intercom/intercom --json files --jq '.files[].path' |
| 46 | +``` |
| 47 | + |
| 48 | +Then fetch specific files of interest (controllers, models, version changes, routes). |
| 49 | + |
| 50 | +For merged PRs where you need the full file (not just diff), fetch from the default branch: |
| 51 | +```bash |
| 52 | +gh api repos/intercom/intercom/contents/<path> --jq '.content' | base64 -d |
| 53 | +``` |
| 54 | + |
| 55 | +### Step 3: Analyze the Diff |
| 56 | + |
| 57 | +Scan the diff for these file patterns and extract API-relevant information: |
| 58 | + |
| 59 | +#### 3a. Controllers (`app/controllers/api/v3/`) |
| 60 | + |
| 61 | +Look for: |
| 62 | +- **New controller files** → new API resource with endpoints |
| 63 | +- **New actions** (`def index`, `def show`, `def create`, `def update`, `def destroy`) → new operations |
| 64 | +- **`requires_version_change`** → which version change gates this endpoint |
| 65 | +- **`render_json Api::V3::Models::XxxResponse`** → identifies the response model/presenter |
| 66 | +- **`params.slice(...).permit(...)`** or **request parser classes** (`RequestParser`, `StrongParams`) → request body fields |
| 67 | +- **Error handling** (`raise Api::V3::Errors::ApiCodedError`) → error responses |
| 68 | +- **`before_action :check_api_version!`** → version-gated endpoint |
| 69 | + |
| 70 | +#### 3b. Models/Presenters (`app/presenters/api/v3/` or `app/lib/api/v3/models/`) |
| 71 | + |
| 72 | +Look for: |
| 73 | +- **`serialized_attributes do`** blocks → response schema properties |
| 74 | +- **`attribute :field_name`** → schema field definition |
| 75 | +- **`stringify: true`** → field is string type (even if integer in DB) |
| 76 | +- **`from_model` method** → how the model maps from internal objects |
| 77 | +- **Conditional attributes** based on version → version-specific fields |
| 78 | + |
| 79 | +#### 3c. Version Changes (`app/lib/api/versioning/changes/`) |
| 80 | + |
| 81 | +Look for: |
| 82 | +- **`define_description`** → description of the API change (use in PR/commit message) |
| 83 | +- **`define_is_breaking`** → whether this is a breaking change |
| 84 | +- **`define_is_ready_for_release`** → usually `false` for new changes |
| 85 | +- **`define_transformation ... data.except(:field1, :field2)`** → these fields are NEW (removed for old versions) |
| 86 | +- **`define_transformation` with data modification** → field format/value changed between versions |
| 87 | + |
| 88 | +#### 3d. Version Registration (`app/lib/api/versioning/service.rb`) |
| 89 | + |
| 90 | +Look for which version block the new change is added to: |
| 91 | +- `UnstableVersion.new(changes: [...])` → goes in Unstable (version `0/`) |
| 92 | +- `Version.new(id: "2.15", changes: [...])` → goes in that specific version |
| 93 | + |
| 94 | +#### 3e. Routes (`config/routes/api_v3.rb`) |
| 95 | + |
| 96 | +Look for: |
| 97 | +- `resources :things` → standard CRUD: index, show, create, update, destroy |
| 98 | +- `resources :things, only: [:index, :show]` → limited operations |
| 99 | +- `member do ... end` → actions on specific resource (e.g., PUT `/things/{id}/action`) |
| 100 | +- `collection do ... end` → actions on resource collection (e.g., POST `/things/search`) |
| 101 | +- Nested resources → parent/child paths (e.g., `/contacts/{id}/tags`) |
| 102 | + |
| 103 | +#### 3f. OAuth Scopes (`app/lib/policy/api_controller_routes_oauth_scope_policy.rb`) |
| 104 | + |
| 105 | +Look for scope mappings to understand required auth scope for the endpoint. |
| 106 | + |
| 107 | +### Step 4: Ask User for Version Targeting |
| 108 | + |
| 109 | +Present the findings and ask: |
| 110 | + |
| 111 | +``` |
| 112 | +I found the following API changes in PR #XXXXX: |
| 113 | +- [list of changes found] |
| 114 | +
|
| 115 | +Which API versions should I update? |
| 116 | +- Unstable only (default for new features) |
| 117 | +- Specific versions (for bug fixes/backports) |
| 118 | +``` |
| 119 | + |
| 120 | +Default to Unstable (`descriptions/0/api.intercom.io.yaml`) unless the PR clearly targets specific versions. |
| 121 | + |
| 122 | +### Step 5: Read Target Spec File(s) |
| 123 | + |
| 124 | +Read the target spec file(s) to understand: |
| 125 | +- Existing endpoints in the same resource group (for consistent naming/style) |
| 126 | +- Existing schemas that can be reused or extended |
| 127 | +- The `intercom_version` enum (to verify version values) |
| 128 | +- Where to insert new paths/schemas (maintain alphabetical or logical grouping) |
| 129 | +- **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>` |
| 130 | +- **Existing example values** for the same resource — reuse the same style of IDs, workspace IDs, timestamps, and names that nearby endpoints use. Consistency matters more than novelty. |
| 131 | + |
| 132 | +### Step 6: Generate OpenAPI Changes |
| 133 | + |
| 134 | +Read the appropriate reference file based on what the PR changes: |
| 135 | + |
| 136 | +- **Adding/modifying fields or schemas?** → Read [./ruby-to-openapi-mapping.md](./ruby-to-openapi-mapping.md) for how Ruby presenter attributes map to OpenAPI types |
| 137 | +- **Adding new endpoints?** → Read [./openapi-patterns.md](./openapi-patterns.md) for concrete YAML templates (GET, POST, PUT, DELETE, search) |
| 138 | +- **Updating multiple versions?** → Read [./version-propagation.md](./version-propagation.md) for the decision tree on which files to update |
| 139 | + |
| 140 | +#### The two most important rules |
| 141 | + |
| 142 | +**Rule 1: Field additions require updates in TWO places.** When adding a field to a schema, you must update both the schema definition in `components/schemas` AND every inline response example that returns that schema. Find all affected examples with: |
| 143 | +```bash |
| 144 | +grep -n 'schemas/<schema_name>' descriptions/0/api.intercom.io.yaml |
| 145 | +``` |
| 146 | + |
| 147 | +**Rule 2: New resources need a top-level tag.** If adding an entirely new API resource, add an entry to the `tags` array at the bottom of the spec (alphabetical order). The tag name must match the `tags` on endpoints and `x-tags` on schemas. See existing tags in [./openapi-patterns.md](./openapi-patterns.md) under "Top-Level Tags". |
| 148 | + |
| 149 | +#### Quick checklist for new endpoints |
| 150 | + |
| 151 | +Every endpoint needs: `summary`, `description`, `operationId` (unique, camelCase), `tags`, `Intercom-Version` header parameter (`"$ref": "#/components/schemas/intercom_version"`), response with inline examples + schema `$ref`, and at minimum a `401 Unauthorized` error response. POST/PUT endpoints also need a `requestBody` with schema and examples. See [./openapi-patterns.md](./openapi-patterns.md) for complete templates. |
| 152 | + |
| 153 | +**Writing good descriptions:** Extract the description from the PR's version change `define_description` if available — it's usually well-written for the changelog. Supplement with details from the controller (constraints, validations, edge cases). A good description explains what the endpoint does AND when you'd use it, not just "You can do X." |
| 154 | + |
| 155 | +**Response example detail level:** Match the verbosity of existing examples for the same schema. If other ticket endpoints show a full ticket object with nested `ticket_parts`, `contacts`, and `linked_objects`, your example should too. If they're minimal (just `type` and `id`), keep yours minimal. Look at the nearest sibling endpoint for the right level of detail. |
| 156 | + |
| 157 | +#### Quick checklist for new schemas |
| 158 | + |
| 159 | +Every schema needs: `title` (Title Case), `type: object`, `x-tags`, `description`, and `properties` where each property has `type`, `description`, and `example`. Mark nullable fields explicitly with `nullable: true`. Timestamps use `type: integer` + `format: date-time`. |
| 160 | + |
| 161 | +### Step 7: Apply Changes |
| 162 | + |
| 163 | +Use the Edit tool to insert changes into the spec file(s). Be careful about: |
| 164 | +- YAML indentation (2-space indent throughout) |
| 165 | +- Inserting paths in logical order (group related endpoints together) |
| 166 | +- Inserting schemas alphabetically in `components/schemas` |
| 167 | +- Adding new top-level tags in alphabetical order in the `tags` array |
| 168 | +- Not breaking existing content |
| 169 | + |
| 170 | +### Step 8: Validate |
| 171 | + |
| 172 | +Run Fern validation: |
| 173 | +```bash |
| 174 | +fern check |
| 175 | +``` |
| 176 | + |
| 177 | +If `fern` is not installed, fall back to YAML syntax validation: |
| 178 | +```bash |
| 179 | +python3 -c "import yaml; yaml.safe_load(open('descriptions/0/api.intercom.io.yaml'))" && echo "YAML valid" |
| 180 | +``` |
| 181 | + |
| 182 | +If validation fails, read the error output and fix the issues. Common problems: indentation errors, missing quotes on string values that look like numbers, and duplicate keys. |
| 183 | + |
| 184 | +### Step 9: Summarize |
| 185 | + |
| 186 | +Report to the user: |
| 187 | +- What was added/changed (new endpoints, new schemas, new fields, new top-level tags) |
| 188 | +- Which files were modified |
| 189 | +- Which versions were updated |
| 190 | +- Any manual follow-up needed |
| 191 | + |
| 192 | +#### Follow-up Checklist |
| 193 | + |
| 194 | +Always remind the user of remaining manual steps: |
| 195 | + |
| 196 | +1. **Review generated changes** for accuracy against the actual API behavior |
| 197 | +2. **Fern overrides** — if new endpoints were added to Unstable, check if `fern/unstable-openapi-overrides.yml` needs SDK method name entries |
| 198 | +3. **Developer-docs PR** — copy the updated spec to the `developer-docs` repo: |
| 199 | + - Copy `descriptions/0/api.intercom.io.yaml` → `docs/references/@Unstable/rest-api/api.intercom.io.yaml` |
| 200 | + - For stable versions: `descriptions/2.15/api.intercom.io.yaml` → `docs/references/@2.15/rest-api/api.intercom.io.yaml` |
| 201 | +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) |
| 202 | +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 |
| 203 | +6. **Run `fern check`** to validate before committing |
| 204 | + |
| 205 | +## Important Notes |
| 206 | + |
| 207 | +- **Do NOT run `fern generate` without `--preview`** — this would auto-submit PRs to SDK repos |
| 208 | +- **Match existing examples** — before writing new example values, look at how nearby endpoints for the same resource format their examples. Reuse the same style of IDs (`'494'` not `'1'`), workspace IDs (`this_is_an_id664_that_should_be_at_least_`), timestamps (recent UNIX timestamps like `1719493065`), and names. Consistency across the spec is more important than creativity. |
| 209 | +- **Match existing style** — look at nearby endpoints for naming, formatting, and level of detail in response examples. If sibling endpoints show full nested objects, yours should too. |
| 210 | +- **Extract descriptions from the PR** — the version change's `define_description` is usually well-written. Use it as the basis for your endpoint description, then enrich with constraints and edge cases from the controller code. |
| 211 | +- **Cross-reference with existing schemas** — reuse `$ref` to existing schemas wherever possible |
| 212 | +- **Nullable fields** — always explicitly mark with `nullable: true` |
| 213 | +- **The `error` schema** is already defined — always reference it with `"$ref": "#/components/schemas/error"` |
| 214 | +- **Top-level tags** — new resources need a tag in the `tags` array at the bottom of the spec |
| 215 | +- **Servers** — the spec already defines 3 regional servers (US, EU, AU) — do not modify |
| 216 | +- **Security** — global `bearerAuth` is already configured — do not modify |
0 commit comments