Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 46 additions & 1 deletion .claude/skills/add-resource/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,39 @@ The user wants to manage a new PostHog resource as code, e.g.:

If the user only wants a one-off API call or a script, this is the wrong tool — this skill is for resources that should join the `load → validate → diff → execute` pipeline.

## Start with the scaffolder

Don't hand-write the 5 files. Run:

```
pnpm scaffold-resource \
--name <singular> # e.g. cohort, action, survey
--path <list-endpoint> # e.g. /api/projects/{project_id}/cohorts/
--key-field <field> # field that identifies the resource (default: name)
```

The script reads `../posthog/frontend/tmp/openapi.json` and generates `src/resources/<name>/{client,sdk,pipeline,pipeline.test,index}.ts`, then wires the resource into `src/resources/index.ts`, `src/index.ts`, `scripts/lib/registry.ts`, and flips the row in `docs/resources.md`. The scaffold is implemented at `scripts/scaffold-resource.ts`.

What you get out of the box:

- **`client.ts`** — ~95% complete. Zod `ServerXSchema` generated from the OpenAPI response component, `ListPaginated<X>Schema`, CRUD wrappers, and a `listManagedX` that filters by `iac:<plural>:` tags (or marks a TODO if the resource has no `tags` field).
- **`sdk.ts`** — a starter `X` type from the create-request schema plus the factory with `markResourceKind(spec, "x")`. **Almost always needs narrowing** — the OpenAPI request body usually exposes more than the IaC layer should.
- **`pipeline.ts`** — full structural skeleton (`xTag`, `xKeyFromTags`, `xHash`, `xPayload`, `validateXs`, `runXOp`, `pruneX`, display helpers) with `TODO(human)` markers over the per-resource judgment calls.
- **`pipeline.test.ts`** — 3-test skeleton (validate / hash / `looksLikeX`) plus a TODO list for the 5 op-selection + safety-invariant tests that this skill requires below.
- **`index.ts`** — the `ResourceModule` registration, fully wired.

What you must still do (these are the `TODO(human)` markers — *the rest of this skill explains why*):

1. **Narrow the SDK type** in `sdk.ts` — strip server-set fields, deprecated aliases, write-only options the IaC layer should not expose.
2. **Pick an identity mechanism** if the resource has no `tags` field — see the description-marker section below and `src/resources/endpoint/`.
3. **Write the hash projection** `xSpecForHash` in `pipeline.ts` — every user-intent field, no server-set fields. *This is the dangerous one* — get it wrong and every apply either rewrites unchanged rows or skips real changes.
4. **Write the create/update payload** `xPayload` — usually `spec` plus merged identity tags, but resource-specific.
5. **Tune validation** `validateXs` — at minimum key required + unique, plus resource-specific invariants.
6. **Polish display rendering** — `displayJson` is the fallback; field-by-field `scalar`/`obj`/`arr` calls give readable diffs.
7. **Add the safety-invariant tests** to `pipeline.test.ts` — the 5 cases listed in "Unit tests" below.

Then read the rest of this skill — it is the *why* behind each TODO marker and the verification flow you still owe.

## Before you touch anything

Re-read these three files. The whole architecture is in them:
Expand Down Expand Up @@ -312,7 +345,19 @@ The last step is the safety invariant smoke test. Do not skip it.

## Update the support matrix

After the resource ships, flip its row from ❌ to ✅ in `docs/resources.md`.
The scaffolder flips the matching row in `docs/resources.md` from ❌ to ✅ automatically. If it couldn't find a matching row (the heuristic uses the plural URL segment), flip it by hand.

## Checking for API drift

Run `pnpm check-resources` (implemented at `scripts/check-resources.ts`) to compare every shipped resource's `ServerXSchema` against the current OpenAPI spec. It reports three things:

- **Added fields** — present in the OpenAPI response but not in our schema. Most of these are not worth tracking (server-set metadata is excluded by default; pass `--all` to include `readOnly` fields). The ones to act on are the user-intent fields that have appeared since this resource was scaffolded.
- **Removed fields** — present in our schema but no longer in the OpenAPI response. The most important signal: we may be reading something that no longer exists.
- **Type mismatches** — coarse `string` vs `number` vs `array` differences between our schema and the API.

The script exits non-zero if any drift is found, so it fits straight into CI as a gate. Refresh `posthog/frontend/tmp/openapi.json` first (the PostHog repo's frontend codegen step produces it) before running.

The registry that drives the comparison lives in `scripts/lib/registry.ts`. The scaffolder appends new entries automatically; if you add a resource by hand, add a row there too.

## Deletes

Expand Down
4 changes: 4 additions & 0 deletions .claude/skills/add-singleton-resource/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ This skill is the playbook for resources that exist exactly **once per project**

If you're unsure: list the API. If you GET a single object (not a paginated list of objects), it's a singleton. If you'd need a `key` field on the spec to tell two of them apart in code, it's not.

## No scaffolder for singletons

The `pnpm scaffold-resource` script in `scripts/scaffold-resource.ts` is collection-only — it assumes a paginated list endpoint with `iac:<plural>:` tag identity. Singletons need a different shape (one declarative block, field-level diff, PATCH-only) and have no list endpoint to scaffold from, so hand-write the files following this skill. The `pnpm check-resources` drift check can still apply if you add a manifest entry pointing at the singleton's OpenAPI component, but the bigger value there is for collection resources.

## Before you touch anything

Re-read the same three architecture docs as `add-resource`:
Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@
"test": "vitest run",
"test:watch": "vitest",
"test:integration": "VITEST_MODE=integration vitest run",
"test:acceptance": "VITEST_MODE=acceptance vitest run"
"test:acceptance": "VITEST_MODE=acceptance vitest run",
"scaffold-resource": "tsx scripts/scaffold-resource.ts",
"check-resources": "tsx scripts/check-resources.ts"
},
"dependencies": {
"enquirer": "^2.4.1",
Expand All @@ -45,6 +47,7 @@
"@types/node": "^22.10.2",
"eslint": "^10.3.0",
"eslint-config-prettier": "^10.1.8",
"openapi-zod-client": "^1.18.3",
"prettier": "^3.8.3",
"typescript": "^5.7.2",
"typescript-eslint": "^8.59.2",
Expand Down
Loading
Loading