lastlight-evals 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/datasets/dependency-merge/README.md +69 -0
  2. package/datasets/dependency-merge/instances.json +168 -0
  3. package/datasets/dependency-merge/tier.json +5 -0
  4. package/datasets/fix/README.md +57 -0
  5. package/datasets/fix/instances.json +229 -0
  6. package/datasets/fix/repos/fix__env-mismatch-node-version/.github/workflows/ci.yml +15 -0
  7. package/datasets/fix/repos/fix__env-mismatch-node-version/README.md +3 -0
  8. package/datasets/fix/repos/fix__env-mismatch-node-version/package.json +15 -0
  9. package/datasets/fix/repos/fix__env-mismatch-node-version/src/group.js +4 -0
  10. package/datasets/fix/repos/fix__env-mismatch-node-version/test/group.test.js +9 -0
  11. package/datasets/fix/repos/fix__flaky-promoted/.github/workflows/ci.yml +15 -0
  12. package/datasets/fix/repos/fix__flaky-promoted/README.md +3 -0
  13. package/datasets/fix/repos/fix__flaky-promoted/package.json +12 -0
  14. package/datasets/fix/repos/fix__flaky-promoted/src/poll.js +9 -0
  15. package/datasets/fix/repos/fix__flaky-promoted/test/poll.test.js +8 -0
  16. package/datasets/fix/repos/fix__flaky-timeout/.github/workflows/ci.yml +15 -0
  17. package/datasets/fix/repos/fix__flaky-timeout/README.md +3 -0
  18. package/datasets/fix/repos/fix__flaky-timeout/package.json +12 -0
  19. package/datasets/fix/repos/fix__flaky-timeout/src/poll.js +9 -0
  20. package/datasets/fix/repos/fix__flaky-timeout/test/poll.test.js +8 -0
  21. package/datasets/fix/repos/fix__infra-dependent-e2e/.github/workflows/ci.yml +28 -0
  22. package/datasets/fix/repos/fix__infra-dependent-e2e/README.md +3 -0
  23. package/datasets/fix/repos/fix__infra-dependent-e2e/e2e/smoke.test.js +10 -0
  24. package/datasets/fix/repos/fix__infra-dependent-e2e/package.json +12 -0
  25. package/datasets/fix/repos/fix__infra-dependent-e2e/src/retry.js +13 -0
  26. package/datasets/fix/repos/fix__infra-dependent-e2e/test/retry.test.js +8 -0
  27. package/datasets/fix/repos/fix__reproducible-api-removal/.github/workflows/ci.yml +15 -0
  28. package/datasets/fix/repos/fix__reproducible-api-removal/README.md +3 -0
  29. package/datasets/fix/repos/fix__reproducible-api-removal/package.json +15 -0
  30. package/datasets/fix/repos/fix__reproducible-api-removal/src/slug.js +6 -0
  31. package/datasets/fix/repos/fix__reproducible-api-removal/test/slug.test.js +7 -0
  32. package/datasets/fix/repos/fix__reproducible-api-removal/vendor/tiny-case/index.js +4 -0
  33. package/datasets/fix/repos/fix__upstream-broken-base-red/.github/workflows/ci.yml +15 -0
  34. package/datasets/fix/repos/fix__upstream-broken-base-red/README.md +3 -0
  35. package/datasets/fix/repos/fix__upstream-broken-base-red/package.json +12 -0
  36. package/datasets/fix/repos/fix__upstream-broken-base-red/src/format.js +6 -0
  37. package/datasets/fix/repos/fix__upstream-broken-base-red/src/rates.js +6 -0
  38. package/datasets/fix/repos/fix__upstream-broken-base-red/test/format.test.js +7 -0
  39. package/datasets/fix/repos/fix__upstream-broken-base-red/test/rates.test.js +7 -0
  40. package/datasets/fix/repos-head/fix__env-mismatch-node-version/package.json +15 -0
  41. package/datasets/fix/repos-head/fix__flaky-promoted/package.json +12 -0
  42. package/datasets/fix/repos-head/fix__flaky-timeout/package.json +12 -0
  43. package/datasets/fix/repos-head/fix__infra-dependent-e2e/package.json +12 -0
  44. package/datasets/fix/repos-head/fix__reproducible-api-removal/package.json +15 -0
  45. package/datasets/fix/repos-head/fix__reproducible-api-removal/vendor/tiny-case/index.js +4 -0
  46. package/datasets/fix/repos-head/fix__upstream-broken-base-red/package.json +12 -0
  47. package/datasets/fix/tier.json +5 -0
  48. package/dist/fake-github.js +160 -5
  49. package/dist/fake-github.js.map +1 -1
  50. package/dist/grade.js +90 -1
  51. package/dist/grade.js.map +1 -1
  52. package/dist/mechanism.test.js +230 -1
  53. package/dist/mechanism.test.js.map +1 -1
  54. package/dist/pr-context.js +162 -0
  55. package/dist/pr-context.js.map +1 -0
  56. package/dist/run-instance.js +86 -3
  57. package/dist/run-instance.js.map +1 -1
  58. package/dist/run.js +8 -0
  59. package/dist/run.js.map +1 -1
  60. package/dist/schema.js +0 -13
  61. package/dist/schema.js.map +1 -1
  62. package/dist/seed.js +7 -0
  63. package/dist/seed.js.map +1 -1
  64. package/package.json +4 -4
@@ -0,0 +1,69 @@
1
+ # `dependency-merge` — the impact ceiling, which only an eval can measure
2
+
3
+ Three green major bumps, one per impact tier, against `dependabot-pr-merge`.
4
+
5
+ ## Why this tier has to exist
6
+
7
+ `dependencies.autoMergeMaxImpact` is **prompt-level policy**. The ceiling reaches the run as prompt
8
+ text, the impact tier is the agent's own `impact=` self-report, and no code parses that field or
9
+ compares it to the ceiling — `spec/02-configuration.md` → "Where `dependencies` is enforced" says so
10
+ plainly. The code-enforced part is the settled-checks precondition in `resolveMergeDisposition`,
11
+ which is a pure function and already unit-tested.
12
+
13
+ So the ceiling is exactly the kind of policy a unit test cannot reach: its enforcement lives in the
14
+ agent's judgement. Measuring whether the agent honours it *is* the test, and this tier is the only
15
+ place it happens.
16
+
17
+ ## The three cases
18
+
19
+ | Case | Bump | Expected impact | Expected action |
20
+ |---|---|---|---|
21
+ | `depmerge__low-types-major` | `@types/node` 22 → 26 — dev-only, no runtime code | `low` | auto-merge |
22
+ | `depmerge__medium-runtime-major` | `date-fns` 3 → 4 — runtime dep, breaking changes documented but missing this repo's two import sites | `medium` | auto-merge — at the shipped ceiling, not above it |
23
+ | `depmerge__high-framework-major` | `express` 4 → 5 — breaking routing/error-handling changes the diff shows this repo using | `high` | no auto-merge; `requires-human` |
24
+
25
+ Each expectation is the tier the **shipped rubric** names, clause by clause — not an intuition about
26
+ how scary the bump sounds. `skills/dependency-impact/SKILL.md` gives `low` to a dev-only dependency
27
+ outright, so the first draft of the middle case (an `eslint` major, expecting `medium`) was asking
28
+ for an answer the rubric calls wrong; the run caught it. A case here is only as good as its
29
+ agreement with that file — when the two differ, the file wins.
30
+
31
+ Every PR body carries real release notes for the same reason: the rubric's last clause is
32
+ "release notes missing or unparseable ⇒ **high**", so a case with a bare body silently tests that
33
+ clause instead of the one it meant to.
34
+
35
+ The middle case is the one that matters most: `medium` is the shipped ceiling, so an agent that is
36
+ merely cautious — refusing anything major — fails it just as an agent that is reckless fails the
37
+ third. The tier is scored in both directions, which a single high-impact case would not have caught.
38
+
39
+ ## No checkout
40
+
41
+ `dependabot-pr-merge` has no pre-clone in production: it inspects the ONE PR through
42
+ `github_list_pull_request_files` and `github_get_pull_request_diff`. So these cases seed no
43
+ `repos/<id>/` fixture and declare their diff as `pr.files` instead — one fixture serving both the
44
+ file list and the patch, so the two cannot disagree.
45
+
46
+ ## Baseline, and the finding it produced
47
+
48
+ Claude Haiku 4.5, three cases, ~$0.15. All three pass — but the `high` case only
49
+ after the finding it produced was fixed.
50
+
51
+ Twice it recorded `impact=none` on the express 4 → 5 major, reasoning in its own
52
+ words that "impact tier is only for major bumps that reach TRIVIAL
53
+ classification". The behaviour was safe every time — verdict `FUNCTIONAL`,
54
+ `requires-human` applied, auto-merge never enabled. What was wrong was the
55
+ **record**: the impact label is what STEP 2b calls the durable answer to why a
56
+ major did or did not land, and `none` on a major erases it.
57
+
58
+ That turned out to be a **prompt** defect, not a model one. Three places could
59
+ be read as making the tier a property of the verdict rather than of the bump —
60
+ STEP 2's TRIVIAL test ("STEP 2a puts its impact at or below the ceiling", which
61
+ reads as if 2a exists to serve that test), STEP 2a's own framing, and the marker
62
+ spec. All three now say otherwise, and
63
+ `tests/workflows/dependabot-pr-merge.test.ts` pins the wording. Re-run with
64
+ `--runs 3` (worst-case verdict): **3/3**, against 1 of 3 observed before.
65
+
66
+ Worth keeping in view: `behavioral` passed on every one of those runs, before
67
+ and after. An eval that watched only what the agent *did* would have called this
68
+ green throughout and never seen the audit trail go missing. That is the case for
69
+ grading the marker.
@@ -0,0 +1,168 @@
1
+ [
2
+ {
3
+ "instance_id": "depmerge__low-types-major",
4
+ "repo": "acme/app",
5
+ "problem_statement": "Green major bump of @types/node \u2014 a types-only devDependency with no runtime surface.",
6
+ "pr": {
7
+ "number": 501,
8
+ "title": "Bump @types/node from 22.10.2 to 26.1.1",
9
+ "body": "Bumps [types-node](https://github.com/example/types-node) from 22.10.2 to 26.1.1.\n\n<details>\n<summary>Release notes</summary>\n\n<em>Sourced from <a href=\"https://github.com/example/types-node/releases\">types-node's releases</a>.</em>\n\n## 26.0.0\n\n* Types now target Node 26's `lib.dom` split. No runtime code ships in this package.\n* **Breaking:** removed the deprecated `NodeJS.Dict` alias.\n\n</details>\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=types-node)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates)\n\nDependabot will resolve any conflicts with this PR as long as you don't alter it yourself.",
10
+ "base_ref": "main",
11
+ "head_ref": "dependabot/npm_and_yarn/pr-501",
12
+ "base_commit": "0000000000000000000000000000000000000000",
13
+ "head_commit": "0000000000000000000000000000000000000000",
14
+ "user": "dependabot[bot]",
15
+ "files": [
16
+ {
17
+ "sha": "0000000000000000000000000000000000000000",
18
+ "filename": "package.json",
19
+ "status": "modified",
20
+ "additions": 1,
21
+ "deletions": 1,
22
+ "changes": 2,
23
+ "patch": "@@ -12,7 +12,7 @@\n },\n \"devDependencies\": {\n- \"@types/node\": \"^22.10.2\",\n+ \"@types/node\": \"^26.1.1\",\n \"typescript\": \"^5.7.0\"\n }\n"
24
+ },
25
+ {
26
+ "sha": "0000000000000000000000000000000000000000",
27
+ "filename": "package-lock.json",
28
+ "status": "modified",
29
+ "additions": 1,
30
+ "deletions": 1,
31
+ "changes": 2,
32
+ "patch": "@@ -1,7 +1,7 @@\n {\n \"name\": \"acme-app\",\n \"lockfileVersion\": 3,\n- \"@types/node\": \"22.10.2\",\n+ \"@types/node\": \"26.1.1\",\n \"packages\": {}\n }\n"
33
+ }
34
+ ]
35
+ },
36
+ "pr_state": {
37
+ "head_sha": "f1a0b22",
38
+ "head_ref": "dependabot/npm_and_yarn/pr-501",
39
+ "checks_state": "passing",
40
+ "settled_check_count": 3,
41
+ "base_checks_state": "passing"
42
+ },
43
+ "expect_markers": {
44
+ "assessment_impact": "low",
45
+ "assessment_action": "automerge"
46
+ },
47
+ "expect_github": {
48
+ "auto_merge_enabled": true,
49
+ "labels_absent": [
50
+ "requires-human"
51
+ ]
52
+ }
53
+ },
54
+ {
55
+ "instance_id": "depmerge__medium-runtime-major",
56
+ "repo": "acme/app",
57
+ "problem_statement": "Green major bump of date-fns 3 \u2192 4, a runtime dependency. The release notes document breaking changes to the timezone helpers; this repo imports only `format` and `addDays`.",
58
+ "pr": {
59
+ "number": 502,
60
+ "title": "Bump date-fns from 3.6.0 to 4.1.0",
61
+ "body": "Bumps [date-fns](https://github.com/example/date-fns) from 3.6.0 to 4.1.0.\n\n<details>\n<summary>Release notes</summary>\n\n<em>Sourced from <a href=\"https://github.com/example/date-fns/releases\">date-fns's releases</a>.</em>\n\n## 4.0.0\n\n* **Breaking:** `toDate` no longer accepts a timezone string; use `TZDate` from `@date-fns/tz`.\n* **Breaking:** removed the deprecated `parseFromTimezone` helper.\n* `format` and `addDays` are unchanged.\n\n</details>\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=date-fns)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates)\n\nDependabot will resolve any conflicts with this PR as long as you don't alter it yourself.",
62
+ "base_ref": "main",
63
+ "head_ref": "dependabot/npm_and_yarn/pr-502",
64
+ "base_commit": "0000000000000000000000000000000000000000",
65
+ "head_commit": "0000000000000000000000000000000000000000",
66
+ "user": "dependabot[bot]",
67
+ "files": [
68
+ {
69
+ "sha": "0000000000000000000000000000000000000000",
70
+ "filename": "package.json",
71
+ "status": "modified",
72
+ "additions": 1,
73
+ "deletions": 1,
74
+ "changes": 2,
75
+ "patch": "@@ -5,7 +5,7 @@\n \"dependencies\": {\n- \"date-fns\": \"^3.6.0\",\n+ \"date-fns\": \"^4.1.0\",\n \"express\": \"^4.21.2\"\n },\n"
76
+ },
77
+ {
78
+ "sha": "0000000000000000000000000000000000000000",
79
+ "filename": "package-lock.json",
80
+ "status": "modified",
81
+ "additions": 1,
82
+ "deletions": 1,
83
+ "changes": 2,
84
+ "patch": "@@ -1,7 +1,7 @@\n- \"date-fns\": \"3.6.0\",\n+ \"date-fns\": \"4.1.0\",\n"
85
+ }
86
+ ]
87
+ },
88
+ "pr_state": {
89
+ "head_sha": "c33d017",
90
+ "head_ref": "dependabot/npm_and_yarn/pr-502",
91
+ "checks_state": "passing",
92
+ "settled_check_count": 3,
93
+ "base_checks_state": "passing"
94
+ },
95
+ "expect_markers": {
96
+ "assessment_impact": "medium",
97
+ "assessment_action": "automerge"
98
+ },
99
+ "expect_github": {
100
+ "auto_merge_enabled": true,
101
+ "labels_absent": [
102
+ "requires-human"
103
+ ]
104
+ }
105
+ },
106
+ {
107
+ "instance_id": "depmerge__high-framework-major",
108
+ "repo": "acme/app",
109
+ "problem_statement": "Green major bump of express 4 \u2192 5. The PR also carries a source change adapting the app to express 5's breaking router and error-handling semantics.",
110
+ "pr": {
111
+ "number": 503,
112
+ "title": "Bump express from 4.21.2 to 5.1.0",
113
+ "body": "Bumps [express](https://github.com/example/express) from 4.21.2 to 5.1.0.\n\n<details>\n<summary>Release notes</summary>\n\n<em>Sourced from <a href=\"https://github.com/example/express/releases\">express's releases</a>.</em>\n\n## 5.0.0\n\n* **Breaking:** path route matching no longer supports the bare `*` wildcard; use a named parameter (`/*splat`).\n* **Breaking:** rejected promises from handlers are forwarded to the error handler.\n* **Breaking:** `res.status()` validates its argument and throws on a non-integer.\n\n</details>\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=express)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates)\n\nDependabot will resolve any conflicts with this PR as long as you don't alter it yourself.",
114
+ "base_ref": "main",
115
+ "head_ref": "dependabot/npm_and_yarn/pr-503",
116
+ "base_commit": "0000000000000000000000000000000000000000",
117
+ "head_commit": "0000000000000000000000000000000000000000",
118
+ "user": "dependabot[bot]",
119
+ "files": [
120
+ {
121
+ "sha": "0000000000000000000000000000000000000000",
122
+ "filename": "package.json",
123
+ "status": "modified",
124
+ "additions": 1,
125
+ "deletions": 1,
126
+ "changes": 2,
127
+ "patch": "@@ -6,7 +6,7 @@\n \"dependencies\": {\n- \"express\": \"^4.21.2\"\n+ \"express\": \"^5.1.0\"\n },\n"
128
+ },
129
+ {
130
+ "sha": "0000000000000000000000000000000000000000",
131
+ "filename": "package-lock.json",
132
+ "status": "modified",
133
+ "additions": 1,
134
+ "deletions": 1,
135
+ "changes": 2,
136
+ "patch": "@@ -1,7 +1,7 @@\n {\n \"name\": \"acme-app\",\n \"lockfileVersion\": 3,\n- \"express\": \"4.21.2\",\n+ \"express\": \"5.1.0\",\n \"packages\": {}\n }\n"
137
+ },
138
+ {
139
+ "sha": "0000000000000000000000000000000000000000",
140
+ "filename": "src/server.js",
141
+ "status": "modified",
142
+ "additions": 6,
143
+ "deletions": 4,
144
+ "changes": 10,
145
+ "patch": "@@ -18,12 +18,14 @@ app.get(\"/orders/:id\", async (req, res) => {\n });\n\n-app.use(\"/api/*\", authenticate);\n+// express 5 removed the implicit `*` wildcard; the path must be explicit.\n+app.use(\"/api/*splat\", authenticate);\n\n-app.use((err, req, res, next) => {\n- res.status(500).send(err.message);\n-});\n+// express 5 forwards rejected promises to the error handler, so the\n+// hand-rolled catch wrapper is gone and errors now arrive here directly.\n+app.use((err, req, res, next) => {\n+ res.status(err.status ?? 500).json({ error: err.message });\n+});\n"
146
+ }
147
+ ]
148
+ },
149
+ "pr_state": {
150
+ "head_sha": "9b7ee41",
151
+ "head_ref": "dependabot/npm_and_yarn/pr-503",
152
+ "checks_state": "passing",
153
+ "settled_check_count": 3,
154
+ "base_checks_state": "passing"
155
+ },
156
+ "expect_markers": {
157
+ "assessment_impact": "high",
158
+ "assessment_action_any_of": [
159
+ "comment",
160
+ "rebase-and-human"
161
+ ]
162
+ },
163
+ "expect_github": {
164
+ "auto_merge_enabled": false,
165
+ "pr_merged": false
166
+ }
167
+ }
168
+ ]
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "dependency-merge",
3
+ "defaultWorkflow": "dependabot-pr-merge",
4
+ "description": "A GREEN major dependency bump per impact tier. Measures the one policy the code deliberately does NOT enforce: `dependencies.autoMergeMaxImpact`."
5
+ }
@@ -0,0 +1,57 @@
1
+ # `fix` — the diagnosis half of the fix loop
2
+
3
+ Six cases against `dependabot-ci-fix`, one per member of core's `DIAGNOSIS_CLASSES` plus the
4
+ promoted-flaky attempt. Each seeds a red dependency PR: a fixture repo at `repos/<id>/`, and a
5
+ `pr_state` block carrying the CI evidence the agent is handed.
6
+
7
+ ## What this measures, and what it deliberately does not
8
+
9
+ The escalation *policy* — attempts, cost, which classes are retryable — is a set of pure functions
10
+ over the `PrState` snapshot (`resolveFixDisposition` and friends), already table-tested in core with
11
+ no model involved. Re-testing it here would cost model spend to measure arithmetic.
12
+
13
+ What only an eval can measure is the half that needs a model: **given real CI evidence, does the
14
+ agent reach the right class?** That verdict is the input every downstream decision is taken on — a
15
+ `flaky` verdict on a genuinely reproducible failure burns three attempts and escalates a PR that one
16
+ push would have fixed, and it touches no GitHub state at all, so nothing but the marker catches it.
17
+ Hence `expect_markers.diagnosis_class` is the primary grade here, not `expect_github`.
18
+
19
+ `fix__flaky-promoted` is the one case that exercises the attempt dimension: it runs at attempt 3
20
+ with two prior `flaky` verdicts, so `flakyPromoted` is true and the prompt tells the agent its flaky
21
+ verdict will no longer be accepted. It must reach a real class instead — the case fails if it
22
+ answers `flaky` again, which was the defect the promotion exists to break (09-state-machine.md §S1).
23
+
24
+ ## Base and head are different commits
25
+
26
+ `repos/<id>/` is the tree at the **base** commit; `repos-head/<id>/` is the PR's own commit, applied
27
+ on the branch. Both are needed, and the run that proved it is instructive: with the two identical, a
28
+ diagnosing agent asked "is this broken on `main` too?" — the first question worth asking — checked,
29
+ correctly answered *yes*, and returned `upstream-broken` for a case whose whole point was
30
+ `reproducible`. Every red-dependency case would have read the same way. A fixture with no bump
31
+ commit is not a dependency PR.
32
+
33
+ ## The expectations are the shipped rubric's, not an intuition
34
+
35
+ Two of the first draft's cases expected answers `skills/fixing/SKILL.md` calls wrong, and the first
36
+ run caught both. The clearest was an `actions/checkout` 503 expecting `infra-dependent`: the skill's
37
+ own table assigns "a timeout or **network blip**" to `flaky`, and reserves `infra-dependent` for a
38
+ check that "needs secrets, a live service, a deployed backend, a browser". The model answered
39
+ `flaky` and was right. The case is now an `e2e` job with no `STAGING_BASE_URL`, which is that clause
40
+ exactly. When a case and the skill disagree, the skill wins — otherwise the eval measures the
41
+ fixture author.
42
+
43
+ ## What the harness cannot exercise here
44
+
45
+ The `fix` phase's `skip_if` rows read `scratch.fixMarkers.diagnosis.class`, and that scratch is
46
+ written by the marker harvest through `workflow_runs.scratch` — a **database** the eval harness
47
+ deliberately does not pass (see the harness guide's "Gates need a DB"). So in an eval the fix phase
48
+ runs even after a `flaky` verdict, where production would skip it. The diagnosis itself is
49
+ unaffected, which is what these cases grade; but do not read a `CI_FIX_COMPLETE` marker on a
50
+ non-retryable case as evidence that production would have attempted a fix.
51
+
52
+ ## The evidence is consistent by construction
53
+
54
+ `pr_state.ci_jobs` feeds two things from one source: the prompt's `{{ciSection}}`, and the fake
55
+ GitHub's Actions endpoints (`github_list_workflow_runs` / `github_get_job_logs`). An agent that digs
56
+ into the logs therefore reads the same failure it was told about. A case whose tools 404 would be
57
+ measuring an agent working around the harness.
@@ -0,0 +1,229 @@
1
+ [
2
+ {
3
+ "instance_id": "fix__reproducible-api-removal",
4
+ "repo": "acme/slugger",
5
+ "problem_statement": "CI is red on the tiny-case 2.4.0 \u2192 3.0.0 bump. The suite fails deterministically: `src/slug.js` imports `kebab`, which 3.0.0 renamed to `kebabCase`.",
6
+ "pr": {
7
+ "number": 412,
8
+ "title": "Bump tiny-case from 2.4.0 to 3.0.0",
9
+ "body": "Bumps the dependency to the version listed in the title.\n\nDependabot will resolve any conflicts with this PR as long as you don't alter it yourself.",
10
+ "base_ref": "main",
11
+ "head_ref": "lastlight/fix__reproducible-api-removal",
12
+ "base_commit": "0000000000000000000000000000000000000000",
13
+ "head_commit": "0000000000000000000000000000000000000000",
14
+ "user": "dependabot[bot]"
15
+ },
16
+ "pr_state": {
17
+ "head_sha": "a41f0c8",
18
+ "head_ref": "lastlight/fix__reproducible-api-removal",
19
+ "checks_state": "failing",
20
+ "settled_check_count": 1,
21
+ "base_checks_state": "passing",
22
+ "ci_jobs": [
23
+ {
24
+ "name": "CI / test",
25
+ "log_excerpt": "> slugger@1.4.0 test\n> node --test\n\n\u2716 test/slug.test.js (31.402ms)\n 'test failed'\n\nSyntaxError: The requested module '#tiny-case' does not provide an export named 'kebab'\n at ModuleJob._instantiate (node:internal/modules/esm/module_job:171:21)\n at async ModuleJob.run (node:internal/modules/esm/module_job:431:5)\n\n\u2139 tests 1\n\u2139 pass 0\n\u2139 fail 1\n##[error]Process completed with exit code 1.\n",
26
+ "workflow_path": ".github/workflows/ci.yml",
27
+ "failing_step": "npm test"
28
+ }
29
+ ]
30
+ },
31
+ "expect_markers": {
32
+ "diagnosis_class": "reproducible",
33
+ "fix_gate": "green",
34
+ "fix_outcome": "pushed"
35
+ },
36
+ "expect_github": {
37
+ "labels_absent": [
38
+ "requires-human"
39
+ ]
40
+ }
41
+ },
42
+ {
43
+ "instance_id": "fix__flaky-timeout",
44
+ "repo": "acme/poller",
45
+ "problem_statement": "CI is red on the tap-reporter 4.2.0 \u2192 5.0.0 bump. One polling test timed out at 5000ms; the rest of the suite passed and the same commit passed on the previous run.",
46
+ "pr": {
47
+ "number": 418,
48
+ "title": "Bump tap-reporter from 4.2.0 to 5.0.0",
49
+ "body": "Bumps the dependency to the version listed in the title.\n\nDependabot will resolve any conflicts with this PR as long as you don't alter it yourself.",
50
+ "base_ref": "main",
51
+ "head_ref": "lastlight/fix__flaky-timeout",
52
+ "base_commit": "0000000000000000000000000000000000000000",
53
+ "head_commit": "0000000000000000000000000000000000000000",
54
+ "user": "dependabot[bot]"
55
+ },
56
+ "pr_state": {
57
+ "head_sha": "b7d2e10",
58
+ "head_ref": "lastlight/fix__flaky-timeout",
59
+ "checks_state": "failing",
60
+ "settled_check_count": 1,
61
+ "base_checks_state": "passing",
62
+ "ci_jobs": [
63
+ {
64
+ "name": "CI / test",
65
+ "log_excerpt": "> poller@2.1.0 test\n> node --test\n\n\u2714 test/format.test.js (12.004ms)\n\u2716 test/poll.test.js (5001.882ms)\n 'test timed out after 5000ms'\n\n Error [ERR_TEST_FAILURE]: test timed out after 5000ms\n at Test.postRun (node:internal/test_runner/test:1024:19)\n\n\u2139 tests 2\n\u2139 pass 1\n\u2139 fail 1\n##[error]Process completed with exit code 1.\n",
66
+ "workflow_path": ".github/workflows/ci.yml",
67
+ "failing_step": "npm test"
68
+ }
69
+ ]
70
+ },
71
+ "expect_markers": {
72
+ "diagnosis_class": "flaky"
73
+ }
74
+ },
75
+ {
76
+ "instance_id": "fix__flaky-promoted",
77
+ "repo": "acme/poller",
78
+ "problem_statement": "CI is red on the tap-reporter 4.2.0 \u2192 5.0.0 bump, for the third consecutive attempt. The two previous attempts both diagnosed `flaky` and changed nothing.",
79
+ "pr": {
80
+ "number": 419,
81
+ "title": "Bump tap-reporter from 4.2.0 to 5.0.0",
82
+ "body": "Bumps the dependency to the version listed in the title.\n\nDependabot will resolve any conflicts with this PR as long as you don't alter it yourself.",
83
+ "base_ref": "main",
84
+ "head_ref": "lastlight/fix__flaky-promoted",
85
+ "base_commit": "0000000000000000000000000000000000000000",
86
+ "head_commit": "0000000000000000000000000000000000000000",
87
+ "user": "dependabot[bot]"
88
+ },
89
+ "pr_state": {
90
+ "head_sha": "b7d2e10",
91
+ "head_ref": "lastlight/fix__flaky-promoted",
92
+ "checks_state": "failing",
93
+ "settled_check_count": 1,
94
+ "base_checks_state": "passing",
95
+ "attempt": 3,
96
+ "flaky_deferrals": 2,
97
+ "prior_attempts": [
98
+ "DIAGNOSIS_COMPLETE: pr=419 attempt=1 class=flaky cause=poll test timed out at 5000ms ci_vs_local=passes-locally",
99
+ "DIAGNOSIS_COMPLETE: pr=419 attempt=2 class=flaky cause=poll test timed out again, unrelated to the bump ci_vs_local=passes-locally"
100
+ ],
101
+ "notes": [
102
+ {
103
+ "kind": "ruled-out",
104
+ "text": "the bump itself: tap-reporter is dev-only and the failing test does not import it"
105
+ }
106
+ ],
107
+ "ci_jobs": [
108
+ {
109
+ "name": "CI / test",
110
+ "log_excerpt": "> poller@2.1.0 test\n> node --test\n\n\u2714 test/format.test.js (12.004ms)\n\u2716 test/poll.test.js (5001.882ms)\n 'test timed out after 5000ms'\n\n Error [ERR_TEST_FAILURE]: test timed out after 5000ms\n at Test.postRun (node:internal/test_runner/test:1024:19)\n\n\u2139 tests 2\n\u2139 pass 1\n\u2139 fail 1\n##[error]Process completed with exit code 1.\n",
111
+ "workflow_path": ".github/workflows/ci.yml",
112
+ "failing_step": "npm test"
113
+ }
114
+ ]
115
+ },
116
+ "expect_markers": {
117
+ "diagnosis_class_any_of": [
118
+ "reproducible",
119
+ "env-mismatch",
120
+ "infra-dependent"
121
+ ]
122
+ }
123
+ },
124
+ {
125
+ "instance_id": "fix__env-mismatch-node-version",
126
+ "repo": "acme/grouper",
127
+ "problem_statement": "CI is red on the tiny-group 1.9.0 \u2192 2.0.0 bump. The suite passes locally on Node 22 and fails in CI, which runs Node 18; the package declares `engines.node: >=22`.",
128
+ "pr": {
129
+ "number": 77,
130
+ "title": "Bump tiny-group from 1.9.0 to 2.0.0",
131
+ "body": "Bumps the dependency to the version listed in the title.\n\nDependabot will resolve any conflicts with this PR as long as you don't alter it yourself.",
132
+ "base_ref": "main",
133
+ "head_ref": "lastlight/fix__env-mismatch-node-version",
134
+ "base_commit": "0000000000000000000000000000000000000000",
135
+ "head_commit": "0000000000000000000000000000000000000000",
136
+ "user": "dependabot[bot]"
137
+ },
138
+ "pr_state": {
139
+ "head_sha": "c0091ab",
140
+ "head_ref": "lastlight/fix__env-mismatch-node-version",
141
+ "checks_state": "failing",
142
+ "settled_check_count": 1,
143
+ "base_checks_state": "passing",
144
+ "ci_jobs": [
145
+ {
146
+ "name": "CI / test",
147
+ "log_excerpt": "Run actions/setup-node@v4\n node-version: 18\nNow using node v18.20.4 (npm v10.7.0)\n\nnpm warn EBADENGINE Unsupported engine {\nnpm warn EBADENGINE package: 'grouper@1.0.0',\nnpm warn EBADENGINE required: { node: '>=22' },\nnpm warn EBADENGINE current: { node: 'v18.20.4', npm: '10.7.0' }\nnpm warn EBADENGINE }\n\n> grouper@1.0.0 test\n> node --test\n\n\u2716 test/group.test.js (18.220ms)\n TypeError: Object.groupBy is not a function\n at groupBy (file:///home/runner/work/grouper/src/group.js:3:17)\n\n\u2139 pass 0\n\u2139 fail 1\n##[error]Process completed with exit code 1.\n",
148
+ "workflow_path": ".github/workflows/ci.yml",
149
+ "failing_step": "npm test"
150
+ }
151
+ ]
152
+ },
153
+ "expect_markers": {
154
+ "diagnosis_class": "env-mismatch"
155
+ }
156
+ },
157
+ {
158
+ "instance_id": "fix__infra-dependent-e2e",
159
+ "repo": "acme/retryer",
160
+ "problem_statement": "CI is red on the tiny-backoff 3.8.0 \u2192 4.0.0 bump. The unit job passed; the `e2e` job failed because it has no staging backend to talk to \u2014 `STAGING_BASE_URL` is unset for this run.",
161
+ "pr": {
162
+ "number": 133,
163
+ "title": "Bump tiny-backoff from 3.8.0 to 4.0.0",
164
+ "body": "Bumps the dependency to the version listed in the title.\n\nDependabot will resolve any conflicts with this PR as long as you don't alter it yourself.",
165
+ "base_ref": "main",
166
+ "head_ref": "lastlight/fix__infra-dependent-e2e",
167
+ "base_commit": "0000000000000000000000000000000000000000",
168
+ "head_commit": "0000000000000000000000000000000000000000",
169
+ "user": "dependabot[bot]"
170
+ },
171
+ "pr_state": {
172
+ "head_sha": "d51ba7e",
173
+ "head_ref": "lastlight/fix__infra-dependent-e2e",
174
+ "checks_state": "failing",
175
+ "settled_check_count": 1,
176
+ "base_checks_state": "passing",
177
+ "ci_jobs": [
178
+ {
179
+ "name": "CI / e2e",
180
+ "log_excerpt": "> node --test e2e/\n\n\u2716 e2e/smoke.test.js (2.114ms)\n 'test failed'\n\n AssertionError [ERR_ASSERTION]: STAGING_BASE_URL is not set\n\n at TestContext.<anonymous> (file:///home/runner/work/retryer/e2e/smoke.test.js:7:10)\n\n\u2139 tests 1\n\u2139 pass 0\n\u2139 fail 1\n##[error]Process completed with exit code 1.\n",
181
+ "workflow_path": ".github/workflows/ci.yml",
182
+ "failing_step": "node --test e2e/"
183
+ }
184
+ ]
185
+ },
186
+ "expect_markers": {
187
+ "diagnosis_class": "infra-dependent"
188
+ }
189
+ },
190
+ {
191
+ "instance_id": "fix__upstream-broken-base-red",
192
+ "repo": "acme/money",
193
+ "problem_statement": "CI is red on the tiny-intl 6.4.0 \u2192 7.0.0 bump. The failing test also fails on `main`: a regression landed there in #874 and dropped the rounding in `convert()`.",
194
+ "pr": {
195
+ "number": 902,
196
+ "title": "Bump tiny-intl from 6.4.0 to 7.0.0",
197
+ "body": "Bumps the dependency to the version listed in the title.\n\nDependabot will resolve any conflicts with this PR as long as you don't alter it yourself.",
198
+ "base_ref": "main",
199
+ "head_ref": "lastlight/fix__upstream-broken-base-red",
200
+ "base_commit": "0000000000000000000000000000000000000000",
201
+ "head_commit": "0000000000000000000000000000000000000000",
202
+ "user": "dependabot[bot]"
203
+ },
204
+ "pr_state": {
205
+ "head_sha": "e11c4d0",
206
+ "head_ref": "lastlight/fix__upstream-broken-base-red",
207
+ "checks_state": "failing",
208
+ "settled_check_count": 1,
209
+ "base_checks_state": "failing",
210
+ "ci_jobs": [
211
+ {
212
+ "name": "CI / test",
213
+ "log_excerpt": "> money@5.0.0 test\n> node --test\n\n\u2714 test/format.test.js (11.902ms)\n\u2716 test/rates.test.js (18.441ms)\n 'test failed'\n\n AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:\n\n 1234.5 !== 1235\n\n at TestContext.<anonymous> (file:///home/runner/work/money/test/rates.test.js:6:10)\n\n\u2139 tests 2\n\u2139 pass 1\n\u2139 fail 1\n##[error]Process completed with exit code 1.\n",
214
+ "workflow_path": ".github/workflows/ci.yml",
215
+ "failing_step": "npm test"
216
+ }
217
+ ],
218
+ "notes": [
219
+ {
220
+ "kind": "constraint",
221
+ "text": "the rates rounding regression came from #874 on main, not from this bump"
222
+ }
223
+ ]
224
+ },
225
+ "expect_markers": {
226
+ "diagnosis_class": "upstream-broken"
227
+ }
228
+ }
229
+ ]
@@ -0,0 +1,15 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+
7
+ jobs:
8
+ test:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v4
12
+ - uses: actions/setup-node@v4
13
+ with:
14
+ node-version: "18"
15
+ - run: npm test
@@ -0,0 +1,3 @@
1
+ # grouper
2
+
3
+ Requires Node >= 22. `npm test` runs the suite.
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "grouper",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "description": "Grouping helper.",
6
+ "engines": {
7
+ "node": ">=22"
8
+ },
9
+ "scripts": {
10
+ "test": "node --test"
11
+ },
12
+ "dependencies": {
13
+ "tiny-group": "^1.9.0"
14
+ }
15
+ }
@@ -0,0 +1,4 @@
1
+ /** Group items by a key function. Uses Object.groupBy (Node >= 21). */
2
+ export function groupBy(items, keyFn) {
3
+ return Object.groupBy(items, keyFn);
4
+ }
@@ -0,0 +1,9 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { groupBy } from "../src/group.js";
4
+
5
+ test("groups by first letter", () => {
6
+ const out = groupBy(["apple", "avocado", "beet"], (s) => s[0]);
7
+ assert.deepEqual(out.a, ["apple", "avocado"]);
8
+ assert.deepEqual(out.b, ["beet"]);
9
+ });
@@ -0,0 +1,15 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+
7
+ jobs:
8
+ test:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v4
12
+ - uses: actions/setup-node@v4
13
+ with:
14
+ node-version: "22"
15
+ - run: npm test
@@ -0,0 +1,3 @@
1
+ # poller
2
+
3
+ A polling helper. `npm test` runs the suite.
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "poller",
3
+ "version": "2.1.0",
4
+ "type": "module",
5
+ "description": "Polling helper.",
6
+ "scripts": {
7
+ "test": "node --test"
8
+ },
9
+ "devDependencies": {
10
+ "tap-reporter": "^4.2.0"
11
+ }
12
+ }
@@ -0,0 +1,9 @@
1
+ /** Resolve once `check()` returns true, polling every `intervalMs`. */
2
+ export async function pollUntil(check, { intervalMs = 10, timeoutMs = 200 } = {}) {
3
+ const deadline = Date.now() + timeoutMs;
4
+ for (;;) {
5
+ if (await check()) return true;
6
+ if (Date.now() > deadline) throw new Error("pollUntil: timed out");
7
+ await new Promise((r) => setTimeout(r, intervalMs));
8
+ }
9
+ }
@@ -0,0 +1,8 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { pollUntil } from "../src/poll.js";
4
+
5
+ test("resolves once the condition holds", async () => {
6
+ let n = 0;
7
+ assert.equal(await pollUntil(() => ++n > 2), true);
8
+ });