Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
e8fae1a
feat(deps): add dependency PR verifier scripts
cursoragent Oct 5, 2026
f922fd3
feat(deps): add verify-dependency-pr agent skill and playbooks
cursoragent Oct 5, 2026
ae97560
docs: point agents at the dependency PR verifier and fix pre-commit n…
cursoragent Oct 5, 2026
9ec5f74
fix(deps): harden verifier checks after trial runs
cursoragent Oct 5, 2026
9fc1059
docs(deps): use the proven go-sqlite3 check as the generated-check ex…
cursoragent Oct 5, 2026
a381f05
fix(deps): exercise the npm wrapper postinstall with the locked go-np…
cursoragent Oct 5, 2026
f7ca77e
chore: keep verifier skill and scripts out of the npm package
cursoragent Oct 5, 2026
baf9e39
docs(deps): finish the generated-check example and note npm lockfile …
cursoragent Oct 5, 2026
28e23dd
docs(deps): describe verifier invocation neutrally
cursoragent Oct 5, 2026
6e66f5c
Merge remote-tracking branch 'origin/main' into cursor/dependency-pr-…
cursoragent Oct 5, 2026
959c2e8
feat(deps): hold the verifier to a diligent-reviewer standard
cursoragent Oct 5, 2026
efc2163
test(deps): add replay fixtures for verifier regressions
cursoragent Oct 5, 2026
3ff8765
docs(deps): reframe the skill and playbooks around the diligent revie…
cursoragent Oct 5, 2026
070e326
fix(deps): ask one question when a check and the impact review cover …
cursoragent Oct 5, 2026
ed222eb
feat(deps): fall back to the public goreleaser-cross image for releas…
cursoragent Oct 5, 2026
cbeede9
test(deps): replay #829 with all release targets built
cursoragent Oct 5, 2026
4de627d
fix(deps): fix verdict and check bugs from the 2026-10-06 run
cursoragent Oct 6, 2026
4ca50b9
fix(deps): compare every old action ref and only declared outputs
cursoragent Oct 6, 2026
610ed83
feat(deps): flag undisclosed direct updates and find more upstream notes
cursoragent Oct 6, 2026
6c33e60
test(deps): replay the corrected verdicts from the 2026-10-06 run
cursoragent Oct 6, 2026
1b995f7
docs(deps): describe the findings subset rule, pr-disclosure, and not…
cursoragent Oct 6, 2026
2dc16b2
fix(deps): tidy the pr-disclosure evidence and the ERESOLVE summary
cursoragent Oct 6, 2026
044c661
fix(deps): find the upstream tag of npm packages without gitHead
cursoragent Oct 6, 2026
e8c1a89
fix(deps): count only Dependabot's text as disclosure, and accept for…
cursoragent Oct 6, 2026
2f55b4c
fix(deps): compare npm audit advisory IDs, not package names
cursoragent Oct 6, 2026
af0d73d
fix(deps): report the real coverage of upstream notes
cursoragent Oct 6, 2026
8632da7
fix(deps): fix action defaults, license questions, impact proof, and …
cursoragent Oct 6, 2026
1dee8a7
feat(deps): list the advisories that a Go update fixes at each level
cursoragent Oct 6, 2026
be0708b
test(deps): lock in the fixes from the second 2026-10-06 run
cursoragent Oct 6, 2026
02d5791
docs(deps): describe note coverage, forced updates, advisory IDs, and…
cursoragent Oct 6, 2026
2c6e22d
fix(deps): make ci-status compare expected workflows with runs on the…
cursoragent Oct 13, 2026
0e3b3d0
fix(deps): run generated checks with a private HOME and scrub tokens
cursoragent Oct 13, 2026
9ba6b44
fix(deps): resolve floating action refs in actions-coverage
cursoragent Oct 13, 2026
fcc3be0
fix(deps): ask about the effect of a changed setup-python default
cursoragent Oct 13, 2026
2b24b7a
test(deps): make the runner token test fail when no check ran
cursoragent Oct 13, 2026
6dcdb3d
fix(deps): inspect the steps of composite actions in actions-coverage
cursoragent Oct 20, 2026
10243da
fix(deps): report a UI tool crash as incomplete, not as a failure
cursoragent Oct 20, 2026
d8d6812
fix(deps): rank incomplete above needs-human
cursoragent Oct 20, 2026
c9a0cc2
fix(deps): ask for a rerun when CI fails in code that the PR does not…
cursoragent Oct 20, 2026
8f4e6e3
fix(deps): give each incomplete item its own fix
cursoragent Oct 20, 2026
c0d56ce
feat(deps): add the mocks-regenerate baseline check for go.uber.org/mock
cursoragent Oct 20, 2026
277bfd3
fix(deps): ask separate questions for separate choices in actions-cov…
cursoragent Oct 20, 2026
019322a
fix(deps): note the node24 effect and new inputs that default to on
cursoragent Oct 20, 2026
893a1b4
fix(deps): resolve floating action refs and report note gaps in upstr…
cursoragent Oct 20, 2026
78d7130
fix(deps): give release tooling the high tier in the risk map
cursoragent Oct 20, 2026
a66d036
fix(deps): add the generated check results to the evidence of each de…
cursoragent Oct 20, 2026
6582374
test(deps): match the generated check table row, not the decision evi…
cursoragent Oct 20, 2026
75f2c79
fix(deps): keep the tier reasons of the impact review
cursoragent Oct 20, 2026
18b29e7
fix(deps): show both sides of a generated check in the comment details
cursoragent Oct 20, 2026
c78630f
fix(deps): compare npm licenses at the same lockfile path
cursoragent Oct 20, 2026
c2aa7f0
fix(deps): mark a Go module outside the binary as test-only
cursoragent Oct 20, 2026
7233a94
fix(deps): label a 0.x minor update the same way in each section
cursoragent Oct 20, 2026
2900740
fix(deps): put the file output of release-snapshot in code
cursoragent Oct 20, 2026
7bdca51
fix(deps): stop rendered comments from mentioning accounts or linking…
cursoragent Oct 20, 2026
179e396
docs(deps): say that govulncheck runs in both profiles
cursoragent Oct 20, 2026
7cbd788
fix(deps): do not read a missing base license as a license change
cursoragent Oct 20, 2026
f1338ec
test(deps): replay #863, where a composite action hid a nested major …
cursoragent Oct 20, 2026
27fd7d5
test(deps): expect incomplete for #831 without an impact review
cursoragent Oct 20, 2026
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
152 changes: 152 additions & 0 deletions .agents/skills/verify-dependency-pr/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
---
name: verify-dependency-pr
description: Verify a Dependabot (or other dependency-update) PR on ldcli to the standard of a diligent reviewer, and produce a verdict comment (safe to merge, needs a human decision, block, or incomplete). Use to verify, review, triage, or check a dependency update PR, given its number or branch.
---

# Verify a dependency PR

You verify the PR to the standard of a diligent reviewer. After your run, a person must not have to verify anything again. A person only answers the decisions that your comment asks for.

You do not approve, request changes, merge, or push to the PR branch. Verification writes files only. The caller runs `post-comment.sh` to post the comment.

All paths are relative to the repository root. Output goes to `.verify-out/pr-<N>/`, which git ignores.

## Verdicts

| Verdict | Meaning | Exit code |
|---|---|---|
| `block` | The PR must not merge as it is. The comment gives the fix. | 1 |
| `incomplete` | A required check, gate, or review did not run, or proved nothing. A rerun or more agent work closes it, not a person. | 2 |
| `needs-human` | Verification is complete. A person must answer one or more specific questions. | 1 |
| `safe-to-merge` | Every required check and review ran and passed. | 0 |

The table gives the verdicts from strongest to weakest. If an item is incomplete, the verdict is `incomplete`, even when a question is ready. The comment still lists the questions.

A gate is a check that must pass, for example the build or the tests. A failure that also happens on base is "pre-existing" and does not count against the PR. Some checks list separate findings: actionlint, golangci-lint, prettier, and `npm ls`. If each finding of the PR also occurs on base, the failure is pre-existing. A pre-existing failure never satisfies a gate.

## The diligent reviewer standard

This table shows what must be true before the PR can be safe to merge. The tier comes from `result.json` (`.tier`). You can raise the tier, but you cannot lower it.

| Tier | Typical updates | Required |
|---|---|---|
| low | A patch of a direct dependency, a dev dependency, or a transitive-only change | The baseline checks pass. |
| medium | A minor update of a runtime dependency, or any update in a risk area (CGO, LaunchDarkly SDKs, CLI libraries, UI framework, Docker base image) | The low items, plus an impact review of each direct update. If upstream behavior changes reach ldcli, at least one generated check must prove the change. |
| high | A major update (including a 0.x minor), a code generator, release tooling, a go directive change, or more than three direct updates | The medium items, plus each breaking change mapped to the ldcli code that it touches. |

The baseline checks cover each ecosystem: build, tests, generated code, UI dist, smoke tests, transitive changes, licenses, upstream notes, and Actions interfaces. The playbooks list them.

## Procedure

### 1. Run the baseline

```bash
scripts/dependency-pr/verify.sh --pr <N> # or --branch <name>
```

The script makes two worktrees. `work/base` is the tip of the base branch. `work/pr` is the PR merged into that tip. The script classifies the updates, runs the baseline checks, and writes `result.json`, `comment.md`, and `logs/`. If Docker is available and the update touches CGO, the go directive, or release tooling, add `--profile full`.

Read these fields in `result.json`:

- `.classification`: the updates, the tier, the risk tags, and the reasons for the tier.
- `.blocks`, `.decisions`, `.incomplete`: the items that set the verdict.
- `.checks[]`: the outcome of each check. The log of a check is in `logs/<side>/<id>.log`. Its evidence is in `state/checks/<id>/<side>/`.
- `.fixes`: machine-applicable fix recipes. Do not apply them. A later step will.

If the exit code is 2 because a tool is missing, install the tool and run the script again.

### 2. Do the impact review (medium and high tier)

Open the playbook for each ecosystem in `.classification.ecosystems`:

| Ecosystem | Playbook |
|---|---|
| gomod | [playbooks/gomod.md](playbooks/gomod.md) |
| npm-ui (`internal/dev_server/ui`) | [playbooks/npm-ui.md](playbooks/npm-ui.md) |
| npm-wrapper (root `package.json`) | [playbooks/npm-wrapper.md](playbooks/npm-wrapper.md) |
| github-actions | [playbooks/github-actions.md](playbooks/github-actions.md) |
| docker | [playbooks/docker.md](playbooks/docker.md) |

For each direct update, do these steps:

1. Read the upstream notes. The `upstream-changes` check saves them in `state/checks/upstream-changes/pr/notes/`, and its details give the compare link. For an npm package in a monorepo, the notes come from the package `CHANGELOG.md`. For `golang.org/x/*` modules, the notes are the commit messages. The summary gives the coverage of each update. If the notes are partial or missing, read the upstream diff from the compare link for the rest of the range. Also read the PR body. If the `pr-disclosure` check names direct updates that the body does not name, review those updates the same as the others. Only Dependabot's own text counts as disclosure. The check ignores bot summaries and quoted release notes. An update that a named update requires is "forced", and it needs no decision.
2. Find breaking changes, security fixes, deprecations, new minimum versions (Go, Node, runner), new peer requirements, and license changes.
3. Find where ldcli uses the dependency. For Go, use `rg -l '"<module>' --glob '*.go'` and `go list -deps ./... | rg <module>`. For the UI, use `rg "from '<pkg>" internal/dev_server/ui/src`.
4. Map each upstream change to the ldcli code that it reaches. A change that no ldcli code reaches is "not reachable".
5. Read the `transitive-changes` and `license-changes` details. Explain each new module, major transitive update, and license change.

Write `agent/impact.json` in the output directory. Write an upstream issue or PR as `owner/repo#N`, because a bare `#N` links to an ldcli issue. In a `changelog` entry, the renderer adds the repository of the entry to a bare `#N`. It also puts bare `@` names, such as npm scopes, in backticks.

```json
{
"schema": 1,
"summary": "What changed upstream, and what in ldcli it reaches.",
"tier": "medium",
"tier_reasons": ["Only if you raise the tier: the reason"],
"changelog": [{"package": "github.com/mattn/go-sqlite3", "range": "1.14.28..1.14.52", "notes": "…", "breaking": false, "url": "https://…"}],
"usage": ["internal/dev_server/db/sqlite.go"],
"behavior_changes_reachable": true,
"no_local_proof": "Only if no local check can run the changed code: the reason",
"findings": [
{"severity": "info", "text": "A fact for the reader.", "evidence": ["url or path"]},
{"severity": "decide", "question": "Accept X for Y?", "text": "Why only a person can decide this.", "evidence": ["…"]},
{"severity": "block", "text": "Concrete breakage, for example a removed API that ldcli calls.", "evidence": ["…"]}
]
}
```

The verdict is `incomplete` if `changelog` does not name every direct update, or if `behavior_changes_reachable` is missing.

Sometimes no local check can run the changed code. An example is an action that only release workflows use. In that case, set `no_local_proof` to the reason. The verdict then asks a person to accept the change without a local proof. Do not use `no_local_proof` to avoid a check that you can write.

### 3. Write generated checks

If `behavior_changes_reachable` is true, write at least one discriminating check. Follow [reference/generated-checks.md](reference/generated-checks.md). Put the checks in `generated/` in the output directory. The important rules:

- A discriminating check must fail on the old version and pass on the new version. Only such a check counts as proof.
- A guard asserts behavior that must not change. It passes on both versions. A guard never counts as proof, but a guard that fails on the PR blocks the PR.
- Assert behavior, not version strings. Test code that the update reaches.

### 4. Run the generated checks

```bash
scripts/dependency-pr/verify.sh --pr <N> --phase generated
```

This command runs each generated check on base and on the PR. It reads `agent/impact.json`, calculates the verdict again, and writes `comment.md` again. If you change only `impact.json`, use `--phase render`. If a generated check is `error`, `invalid`, or `fails-both`, fix it and run the phase again.

### 5. Report

Report the verdict, the blocks, the decisions, the incomplete items, and the path to `comment.md`. Give pre-existing problems on main separately. A generated check can find the same type of problem in future updates. In that case, recommend it for the baseline (see "Promotion" in the reference).

To post, the caller runs:

```bash
scripts/dependency-pr/post-comment.sh --out-dir .verify-out/pr-<N> --dry-run # then without --dry-run
```

The script edits one comment in place. It does not post if the PR head moved after verification.

## When to ask a person

Ask a person only when the evidence is complete and the next step is a choice. Each decision is one question that a person can answer with yes or no. Put the evidence next to it.

These items are decisions:

- Accept a change that no automated check can run, for example "Accept actions/checkout v6 in release workflows that PR CI does not run?"
- Accept a new license, a license change, or a new npm install script.
- Accept a visible change to the CLI, for example different help text or flags.
- Choose between options, for example "Remove the unused react-window instead of updating it?"

These items are not decisions:

- A stale dist, untidy `go.mod`, or generated code that is out of date. These are blocks with a fix.
- A check that did not run because a tool is missing. This item is incomplete.
- A breaking change that you have not mapped yet. Do the mapping.

## Rules

- Do not fix the PR yourself. Each failing check records a fix recipe in `result.json` for a later step.
- Do not run ad hoc commands and report them as baseline results. Put them in generated checks, so that they run on both versions.
- Do not trust the local environment. `verify.sh` removes `LD_*` variables and uses private XDG directories, because local credentials make `go test` fail. Generated checks get the same environment.
- A check that fails because of a tool or environment problem must report `incomplete`, not `fail`.
24 changes: 24 additions & 0 deletions .agents/skills/verify-dependency-pr/playbooks/docker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Playbook: Docker base image (`Dockerfile.goreleaser`)

The published image is `FROM alpine:<tag>` plus the static `ldcli` binary from goreleaser. The release builds the images. PR CI does not build them.

## Diligent reviewer standard

1. The new tag exists.
2. The release image builds from the PR with a binary made as the release makes it: CGO for SQLite, musl, static link.
3. In the image, `ldcli --version` runs, HTTPS to LaunchDarkly works with the CA bundle of the image, and the dev server starts and serves its API. The dev server uses SQLite, so this step tests CGO on musl.
4. The changes between the two base image versions are known (musl, CA bundle, busybox, end of support).

## What the baseline checks

`docker-image` (gate) covers items 1 to 3. It needs Docker and `musl-gcc` (package `musl-tools`). If one of them is missing, or if a registry lookup or image pull fails, the check reports `incomplete`, not `fail`. `binary-smoke` also runs.

## What the agent must do

- Read the Alpine release notes for each minor version in the range. ldcli links statically, so it needs only the kernel interface and the certificates in `/etc/ssl`.
- Write down the support status. Alpine branches get security fixes for about two years. A move away from a branch that has no more support is a security improvement.
- If a scanner is available (`docker scout cves`, `trivy image`), compare the CVE counts of the old and new image.

## When to ask a person

If the image builds and runs and the impact review is complete, the PR can be safe to merge. Ask a person only if the new base image changes what users see, for example a removed shell or a different user.
38 changes: 38 additions & 0 deletions .agents/skills/verify-dependency-pr/playbooks/github-actions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Playbook: GitHub Actions (`.github/workflows`, `.github/actions`)

## Diligent reviewer standard

1. Each third-party action uses a full commit SHA with a version comment (SEC-7924, #668).
2. The workflows pass `actionlint`.
3. For each updated action, the inputs that the repository passes still exist, and no new required input is missing. The outputs that later steps read still exist.
4. Changed input defaults, the runtime (for example `node20` to `node24`), and the runner requirements are known.
5. The `permissions` blocks of the workflows did not change, or the change is approved.
6. PR CI runs each workflow that uses the action. For a workflow that runs only at release, a person accepts the risk.
7. The upstream release notes between the two versions are known.

## What the baseline checks

| Statement | Check |
|---|---|
| 1 | `actions-pinning`. Actions from `actions/`, `github/`, and `launchdarkly/` are exempt, as in the current repository. |
| 2 | `actionlint` (v1.7.7, installed through `go install` when it is missing) |
| 3, 4, 5 | `actions-coverage`. It reads the upstream `action.yml` at both refs and traces composite actions back to the workflows that call them. It also compares the `permissions` blocks on base and PR. If the interface does not match, the PR is blocked. If workflows pin the action at different old refs (v4 and v5), the check compares each old ref with the new ref. An output that no version declares is set at run time, so the check cannot compare it. The details name it. The check resolves a floating tag (`@v4`) to the release that it points to today (`v4.4.0`). An exact pin can be older than the release of the floating tag that it replaces. It can also be older than the release of its own floating major, for example `@v6.0.3` with `@v6` at `v6.1.0`. In these cases, the check asks a person. |
| 6 | `actions-coverage`. A breaking update that a workflow without a `pull_request` trigger uses becomes a decision. If the updated action is a composite action, the check also compares its nested `uses:` refs and `run:` steps. A nested major update, or a nested action that is added or removed, makes the update breaking, whatever its own version says. If the check cannot fetch an `action.yml`, the result is incomplete. |
| 7 | `upstream-changes` |

PR CI runs only `go.yml`, `dev-server-ui.yml`, `dependency-scan.yml`, and `lint-pr-title.yml`. `release-please.yml`, `manual-publish.yml`, `check-openapi-updates.yml`, and the `publish` and `publish-npm` composite actions do not run on a PR.

## What the agent must do

- Read the release notes of each major version in the range. Note new runner minimum versions, changed defaults, and changed credential or token behavior.
- Look at the steps that use the action in workflows that PR CI does not run. Make sure that their inputs, outputs, and side effects (for example `git push` after `actions/checkout`) still work. Put each check that you can write as a guard in `generated/`.
- Dependabot does not scan the composite actions in `.github/actions/`. Note any version difference that the update creates between a workflow and a composite action.
- For release-please majors, compare `release-please-config.json` and `.release-please-manifest.json` with the configuration schema of the new version, and the outputs that `release-please.yml` reads.

## When to ask a person

- A breaking update is used in workflows that PR CI does not run. Ask: "Accept <action> <version> in <workflows>, which PR CI does not run?" Give the interface comparison, the runtime change, and the guard results as evidence.
- A workflow `permissions` block changed.
- A non-breaking update changes the default of an input that the repository does not set. If the effect is known, ask about the effect, for example: "Accept that pre-commit in go.yml will use the runner's Python 3.12 instead of the newest 3.x in the tool cache?"

No local check can run a release workflow. The comment can recommend a `manual-publish` dry run (`dry-run: true`) as follow-up.
Loading
Loading