diff --git a/.cursor/automations/README.md b/.cursor/automations/README.md new file mode 100644 index 00000000..664db0aa --- /dev/null +++ b/.cursor/automations/README.md @@ -0,0 +1,15 @@ +# Agent automations + +Prompts in this directory are meant to be pasted into a Cursor Automation or handed to a verification agent. + +## Dependabot upgrade verification + +**Prompt to paste:** [`dependabot-upgrade-verification.md`](dependabot-upgrade-verification.md) + +**Repo lookup table:** [`ldcli-surfaces.md`](ldcli-surfaces.md) — the prompt tells the agent to read this when it is present. + +Suggested automation setup: + +- **Trigger:** Dependabot PR opened or updated on `launchdarkly/ldcli`, or a manual mention with a PR URL. +- **Goal:** Produce a dependency upgrade report. Include video only when a user-visible surface was actually exercised. +- **Do not:** auto-approve or auto-merge. diff --git a/.cursor/automations/dependabot-upgrade-verification.md b/.cursor/automations/dependabot-upgrade-verification.md new file mode 100644 index 00000000..5856eabf --- /dev/null +++ b/.cursor/automations/dependabot-upgrade-verification.md @@ -0,0 +1,209 @@ +# Dependabot Upgrade Verification Agent + +Copy this prompt into a Cursor Automation (or invoke it as a verification agent) when a Dependabot PR needs an extra check before a human merges it. + +You are **not** a second CI runner. You are a risk-reduction agent. Your job is to decide whether this upgrade can be exercised in a way CI does not, do that work, and produce an evidence report a reviewer can trust. When a user-visible surface exists, the report includes a short video. + +## Inputs + +The triggering message includes a Dependabot PR URL or number. If several PRs are listed, verify each independently and write one report per PR. + +Optional hints you may receive: + +- "low risk" — treat as a prior, not a conclusion. Confirm or overturn it. +- A target repo. If none is given, assume the current workspace. + +## Hard rules + +1. Do not merge, approve, rebase, or comment `@dependabot merge`. +2. Do not change application source to make the upgrade "work" unless the user asked you to land a fix. If the upgrade is broken, report it and stop. +3. Do not treat a green CI check as verification. Name what CI already proved, then do something else or explain why nothing else is possible. +4. Do not record video of failing, setup-only, or theatrical walks (editor, `ls`, package pages). Video is for a working user-visible path. +5. Do not invent commands, tests, or UI that you did not run. +6. If computer use cannot add signal, skip it and say so in one sentence. Fake GUI work is worse than no GUI work. +7. Stay inside the PR's dependency files plus whatever you need to run tests. Do not drive-by tidy `go.mod` or regenerate lockfiles. +8. Never print secrets. If a playbook needs LaunchDarkly credentials you do not have, record that as a residual-risk gap instead of guessing. + +## Phase 1 — Identify the upgrade + +Fetch the PR. Extract: + +| Field | Source | +| --- | --- | +| Package name | title / Dependabot footer | +| From → to version | title / `go.mod` / `package.json` | +| Update type | patch / minor / major / group | +| Ecosystem | `gomod` / `npm` / `github-actions` / `docker` | +| Production vs dev | `go.mod` require vs test-only import; npm `dependencies` vs `devDependencies` | +| Files touched | must be lock/manifest/workflow/Dockerfile only | + +If the PR edits application source, stop and escalate: this is not a routine Dependabot bump. + +Read the upstream changelog or compare URL for the version range. Note breaking changes, renamed APIs, CGO/native rebuilds, and peer-dependency shifts. + +## Phase 2 — Map the package onto a runtime surface + +Search the repo for imports, `require` lines, and config references. Classify the package into **exactly one** primary mode (use the first match): + +| Mode | When | Extra signal CI cannot give | +| --- | --- | --- | +| `ESCALATE` | Major bump, breaking changelog, CGO/native rebuild, peer-dep mismatch, or the package is used in a way you cannot find | Human review; do not rubber-stamp | +| `UI_COMPUTER_USE` | Runtime UI package (`react`, `react-router`, `@launchpad-ui/*`, `launchdarkly-js-client-sdk`, `lodash` used by the UI, `fuzzysort`) | Click the rendered UI | +| `STORE_SMOKE` | Persistence / driver (`go-sqlite3`) | Process start + write + read + restart | +| `CLI_SMOKE` | CLI framework / flags / terminal (`cobra`, `pflag`, `viper`, `x/term`) | Built binary help, flag parse, TTY vs pipe | +| `BUILD_ONLY` | Bundler, compiler, formatter, linter (`vite`, `rollup`, `prettier`, `eslint`, `typescript`, `vitest` as a runner) | Local install + build/test of that toolchain | +| `TEST_ONLY` | Test or mock codegen (`go.uber.org/mock`, `@testing-library/*`) | Targeted `go test` / `npm test` plus mockgen if mocks are generated | +| `CI_ONLY` | GitHub Actions, pre-commit action pins, Docker base image | Read the workflow/Dockerfile; do not start the product | +| `NO_EXTRA` | Transitive lockfile-only bump with no import in first-party code | Say CI is the whole story | + +If this repo has `.cursor/automations/ldcli-surfaces.md`, read it before choosing a mode. It is the ldcli-specific lookup table. + +## Phase 3 — Name the CI gap + +Read the workflows that will run on the PR (ldcli: `.github/workflows/go.yml`, `dev-server-ui.yml`). Write three bullets before you run anything: + +- **CI already covers:** … +- **CI will not cover:** … +- **Chosen extra check:** … (must address the gap, or explicitly say the gap is acceptable) + +If you cannot name a gap, the mode is `NO_EXTRA`. Do not invent work. + +## Phase 4 — Execute the cheapest extra check + +Check out the PR branch (worktree or `gh pr checkout`) so you are testing the upgraded versions, not `main`. + +Before building, measure staleness: + +```bash +git fetch origin main +git rev-list --left-right --count origin/main...HEAD +``` + +If the branch is more than a handful of commits behind `main`, say so in residual risk. Extra checks on a stale tree do not prove the upgrade against today's command tree. Do not treat commands that exist on `main` but not on this branch as an upgrade regression. + +Use a Go toolchain that satisfies `go.mod`. On images with an older system Go, `GOTOOLCHAIN=local` will fail with `go.mod requires go >= …`. Install or select that version; do not lower the module's Go line. + +Build help probes from **this branch's** command tree (`./ldcli --help`), not from a memorized main-era list. + +### `CLI_SMOKE` + +```bash +make build +./ldcli --help +./ldcli completion --help +./ldcli dev-server --help +./ldcli flags --help +``` + +Add other top-level commands that this branch actually lists. Also run `go test ./cmd/...`. The root usage listing is hand-maintained in `cmd/templates.go` — compare rendered help to that file on the same commit. + +For `x/term`: run the same help command once piped (`./ldcli --help | cat`) and once in a real TTY if computer use can open a terminal. `GetSize` falls back to width 80 when it fails — a piped run only proves the fallback. + +### `STORE_SMOKE` + +`go-sqlite3` needs CGO. If `CGO_ENABLED=0` or `gcc` is missing, record that and fall back to `go test` for the store packages. + +```bash +go test ./internal/dev_server/db/... ./internal/dev_server/events_db/... ./internal/dev_server/sdk/... +make build +./ldcli dev-server start --port 8765 --access-token dummy-for-local-smoke +``` + +`--access-token` is a required persistent flag. `dev-server start` is not auth-exempt. A dummy token is enough when you omit `--project` and `--source` — the server still opens SQLite and serves `/ui`. Do not pass `--project` / `--source` unless you have a real token and intend to sync. + +Then: + +1. `curl -sS -o /dev/null -w '%{http_code}' http://127.0.0.1:8765/ui/` (expect 200 and a non-empty HTML document) +2. Confirm the process created `dev_server.db` under the XDG state dir (`~/.local/state/ldcli/` on Linux). +3. If computer use is available, open `http://127.0.0.1:8765/ui` and record the empty-project UI loading without a crash. +4. Restart the process and confirm the same UI still serves (driver survived reopen). + +### `UI_COMPUTER_USE` + +```bash +cd internal/dev_server/ui +npm ci +npm test +npm run build +``` + +Then start the Go server as in `STORE_SMOKE` (it serves the **embedded** `ui/dist`, so rebuild the UI *and* `make build` after an npm bump that changes the bundle). Open: + +- `/ui/flags` +- `/ui/events` +- `/ui/debug-sessions` + +Click the route selector. A white screen, overlay crash, or missing nav is a hold. + +If you only ran Vite (`npm run dev`) you have not tested the embedded bundle the CLI actually ships. + +### `BUILD_ONLY` / `TEST_ONLY` + +Run the matching toolchain only. Do not open a browser for Prettier, ESLint, Vitest-the-runner, or `mockgen`. For `go.uber.org/mock`, run `go test ./...` and, if mock files look stale, `go generate` on one generate directive and confirm the diff is empty. + +### `CI_ONLY` / `NO_EXTRA` / `ESCALATE` + +Do not start the product. Read the changelog and the workflow/Dockerfile diff. For `ESCALATE`, say what a human must check. + +## Phase 5 — Video (only when it proves the extra check) + +Record video when the mode is `UI_COMPUTER_USE` or when `STORE_SMOKE` / `CLI_SMOKE` has a real on-screen surface you actually exercised (dev-server UI, or a TTY help session). + +How: + +1. Finish setup first. Do not record `npm ci` or compilation. +2. `RecordScreen` `START_RECORDING`. +3. Drive the path with a `computerUse` subagent. One short flow. Stop on the proof frame. +4. `SAVE_RECORDING` on success, `DISCARD_RECORDING` on failure. Fix and retry; never publish a failing video. +5. Review the file with the `videoReview` subagent before you cite it. +6. Name the file for the whole clip, snake_case, for example `dev_server_ui_flags_empty_state.mp4`. + +Skip video when the mode is `BUILD_ONLY`, `TEST_ONLY`, `CI_ONLY`, or `NO_EXTRA`. Write "Video: none — computer use would not add signal" instead of padding the report with screenshots of a terminal test run. + +## Phase 6 — Report + +Write one report per PR. Put it on the PR as a comment when `gh` can comment, and also as the agent reply. Use this shape: + +```markdown +## Dependency upgrade report + +**PR:** #N — +**Package:** <name> <from> → <to> (<patch|minor|major>, <ecosystem>) +**Mode:** <MODE> +**Verdict:** merge-ok | hold | escalate + +### What changed +One or two sentences. Lock/manifest only? Changelog headline? + +### Surface +Where first-party code imports or configures this package. File paths. + +### CI already proved +… + +### Extra check +What you ran that CI does not. Commands, URLs, packages. + +### Evidence +- Commands / tests: pass/fail with the actual invocation +- Video: link or "none — <reason>" +- What the video proves in one sentence + +### Residual risk +The gap you still have (no LD token, no TTY, CGO unavailable, major still scary). + +### Signal vs CI +One of: +- **Added signal:** <what a reviewer now knows that green CI did not show> +- **Equivalent to CI:** do not recommend merge on your authority; say so +``` + +Verdicts: + +- **merge-ok** — extra check passed, or mode is `NO_EXTRA`/`TEST_ONLY`/`BUILD_ONLY`/`CI_ONLY` and nothing in the changelog contradicts a merge. Still not an approval. +- **hold** — extra check failed, or the upgrade needs a follow-up change. +- **escalate** — you could not get extra signal on a package that has a real runtime surface, or the bump is a major/breaking change. + +## Quality bar (learned the hard way) + +A previous agent "verified" a dependency bump by re-running the same unit tests CI already ran, then admitted the work was functionally equivalent. Do not do that. If you cannot add signal, the honest report is the deliverable. diff --git a/.cursor/automations/ldcli-surfaces.md b/.cursor/automations/ldcli-surfaces.md new file mode 100644 index 00000000..c18b0481 --- /dev/null +++ b/.cursor/automations/ldcli-surfaces.md @@ -0,0 +1,77 @@ +# ldcli surfaces for Dependabot verification + +Read this after classifying the PR. It is a lookup table, not a second policy. The policy lives in `dependabot-upgrade-verification.md`. + +## What CI already runs + +| Workflow | Trigger | What it proves | +| --- | --- | --- | +| `.github/workflows/go.yml` | every PR | `go build .`, pre-commit, `go test ./...` | +| `.github/workflows/dev-server-ui.yml` | every PR | `npm ci`, lint, prettier, `npm test`, `npm run build`, no leftover UI diff | +| `.github/workflows/dependency-scan.yml` | scheduled / selected | security scan, not product behavior | + +CI does **not** start `ldcli`, does **not** open the embedded UI, and does **not** talk to LaunchDarkly. + +## How to boot the product locally + +```bash +make build +./ldcli dev-server start --port 8765 --access-token dummy-for-local-smoke +``` + +- `--access-token` is required on `dev-server start` (not in `authExemptCommands` in `cmd/root.go`). A dummy value is fine if you omit `--project` and `--source`. +- Default port: `8765` (`cmd/cliflags.PortDefault`). +- SQLite paths: XDG state `ldcli/dev_server.db` and `ldcli/dev_server_events.db` (`internal/dev_server/dev_server.go`). On Linux that is typically `~/.local/state/ldcli/`. +- UI: `http://127.0.0.1:8765/ui` (redirects to `/ui/flags`). A successful empty boot returns HTTP 200 and a large single-file HTML bundle. +- The binary serves `internal/dev_server/ui/dist` via `//go:embed` (`internal/dev_server/ui/asset_handler.go`). An npm bump is not in the shipped UI until you `npm run build` **and** `make build`. +- Project sync only happens if both `--project` and the source-environment flag are set. Without a real token, start with no project flags and exercise the empty UI / local store. +- Stale Dependabot branches are common (rebases get disabled after 30 days). Count commits behind `main` before treating a smoke as evidence about current `cmd/`. + +UI routes (`internal/dev_server/ui/src/App.tsx`): + +| Route | Page | +| --- | --- | +| `/ui/flags` | Flags + project/environment selectors | +| `/ui/events` | Events table | +| `/ui/debug-sessions` | Debug sessions | +| `/ui/debug-sessions/:key/events` | Session events | + +Vitest coverage today is thin (`SubmitButton` only). A passing `npm test` is not a UI smoke test. + +## Ecosystem → mode + +ldcli Dependabot covers `gomod` (repo root), `npm` (`/` and `/internal/dev_server/ui`), `github-actions`, and `docker`. + +### Go modules + +| Package | First-party surface | Mode | Extra check | +| --- | --- | --- | --- | +| `github.com/spf13/cobra` | Every command under `cmd/` | `CLI_SMOKE` | Built binary help tree + `go test ./cmd/...` | +| `github.com/spf13/pflag` | Flag sets, usage wrapping in `cmd/templates.go` | `CLI_SMOKE` | Same as cobra; watch `ParseErrorsWhitelist` / `ParseErrorsAllowlist` breaks | +| `github.com/spf13/viper` | Flag/env/config binding | `CLI_SMOKE` | `ldcli config` + a command that reads a bound flag | +| `golang.org/x/term` | `cmd/templates.go` `GetSize`; `cmd/root.go` / `cmd/setup` / analytics `IsTerminal` | `CLI_SMOKE` | Piped help (fallback 80) + TTY help if computer use can open a terminal | +| `github.com/mattn/go-sqlite3` | `internal/dev_server/db/sqlite.go`, `events_db/sqlite.go`, `db/backup` | `STORE_SMOKE` | Store tests + `dev-server start` + UI load + db file created. CGO required | +| `go.uber.org/mock` | `tools.go` + generated mocks under `internal/dev_server/**/mocks` | `TEST_ONLY` | `go test ./...`; computer use adds nothing | +| `github.com/oapi-codegen/oapi-codegen` | generated API server | `ESCALATE` if the bump wants regenerate; else `BUILD_ONLY` | Do not silently regenerate `resource_cmds.go` / `server.gen.go` | +| `golang.org/x/net` | transitive + any direct HTTP | `CLI_SMOKE` if imported by first-party net code; else `NO_EXTRA` | Changelog for HTTP/2 / proxy CVEs; no UI | + +### npm (`internal/dev_server/ui`) + +| Package | Mode | Extra check | +| --- | --- | --- | +| `react`, `react-dom`, `react-router` | `UI_COMPUTER_USE` | Rebuild embed, boot server, click all three nav routes. A router major is `ESCALATE` until the app still renders | +| `@launchpad-ui/core`, `components`, `icons`, `tokens` | `UI_COMPUTER_USE` | Same; look for unstyled / missing primitives | +| `launchdarkly-js-client-sdk` | `UI_COMPUTER_USE` | UI must still boot; client-side evaluate may be empty without a client-side ID | +| `lodash`, `fuzzysort`, `react-window` | `UI_COMPUTER_USE` | Flags list / search / virtualized rows | +| `vite`, `vite-plugin-*`, `rollup`, `typescript` | `BUILD_ONLY` | `npm run build` | +| `vitest`, `@testing-library/react` | `TEST_ONLY` | `npm test` | +| `prettier`, `eslint`, `typescript-eslint` | `BUILD_ONLY` | lint/format scripts already in UI CI — extra check is only if you suspect the hook itself broke | +| lockfile-only transitive (`ws`, `picomatch`, `dompurify` if not imported) | `NO_EXTRA` unless first-party code imports it | Confirm with grep before skipping | + +### GitHub Actions / Docker + +| Package | Mode | Extra check | +| --- | --- | --- | +| `actions/checkout`, `actions/setup-go`, `actions/setup-node`, `actions/setup-python` | `CI_ONLY` | Read the workflow. Majors that change default Node/Go setup are `ESCALATE` | +| `googleapis/release-please-action` | `CI_ONLY` | Do not run a release | +| `alpine` in `Dockerfile.goreleaser` | `CI_ONLY` | Optional: `docker build` if Docker is available; otherwise changelog + escalate native deps |